Browse Source

Merge branch 'master' of https://github.com/odin-lang/Odin

Franz Höltermann 1 year ago
parent
commit
c6c00c706a
100 changed files with 6009 additions and 693 deletions
  1. 75 131
      .github/workflows/ci.yml
  2. 13 28
      .github/workflows/nightly.yml
  3. 2 0
      base/builtin/builtin.odin
  4. 4 0
      base/intrinsics/intrinsics.odin
  5. 35 2
      base/runtime/core.odin
  6. 1 1
      base/runtime/default_allocators_general.odin
  7. 1 1
      base/runtime/docs.odin
  8. 25 11
      base/runtime/entry_wasm.odin
  9. 4 0
      base/runtime/error_checks.odin
  10. 29 0
      base/runtime/heap_allocator_orca.odin
  11. 1 1
      base/runtime/heap_allocator_other.odin
  12. 2 2
      base/runtime/internal.odin
  13. 43 0
      base/runtime/os_specific_orca.odin
  14. 16 10
      base/runtime/procs.odin
  15. 14 14
      base/runtime/wasm_allocator.odin
  16. 0 51
      ci/create_nightly_json.py
  17. 0 34
      ci/delete_old_binaries.py
  18. 140 0
      ci/nightly.py
  19. 0 25
      ci/upload_create_nightly.sh
  20. 2 2
      core/bufio/reader.odin
  21. 35 35
      core/bytes/buffer.odin
  22. 1 1
      core/c/libc/signal.odin
  23. 8 6
      core/c/libc/stdio.odin
  24. 28 0
      core/crypto/_aes/aes.odin
  25. 96 0
      core/crypto/_aes/ct64/api.odin
  26. 265 0
      core/crypto/_aes/ct64/ct64.odin
  27. 135 0
      core/crypto/_aes/ct64/ct64_dec.odin
  28. 95 0
      core/crypto/_aes/ct64/ct64_enc.odin
  29. 179 0
      core/crypto/_aes/ct64/ct64_keysched.odin
  30. 136 0
      core/crypto/_aes/ct64/ghash.odin
  31. 75 0
      core/crypto/_aes/ct64/helpers.odin
  32. 22 0
      core/crypto/aes/aes.odin
  33. 199 0
      core/crypto/aes/aes_ctr.odin
  34. 57 0
      core/crypto/aes/aes_ecb.odin
  35. 253 0
      core/crypto/aes/aes_gcm.odin
  36. 41 0
      core/crypto/aes/aes_impl.odin
  37. 43 0
      core/crypto/aes/aes_impl_hw_gen.odin
  38. 137 0
      core/encoding/ansi/ansi.odin
  39. 20 0
      core/encoding/ansi/doc.odin
  40. 2 2
      core/encoding/cbor/cbor.odin
  41. 54 53
      core/encoding/cbor/coding.odin
  42. 8 8
      core/encoding/cbor/marshal.odin
  43. 52 50
      core/encoding/cbor/unmarshal.odin
  44. 5 6
      core/encoding/hex/hex.odin
  45. 16 15
      core/encoding/hxa/hxa.odin
  46. 20 22
      core/encoding/hxa/read.odin
  47. 189 0
      core/encoding/ini/ini.odin
  48. 2 2
      core/encoding/json/marshal.odin
  49. 33 32
      core/encoding/json/parser.odin
  50. 7 7
      core/encoding/json/types.odin
  51. 21 5
      core/fmt/fmt.odin
  52. 1 0
      core/fmt/fmt_os.odin
  53. 746 0
      core/image/bmp/bmp.odin
  54. 4 0
      core/image/bmp/bmp_js.odin
  55. 34 0
      core/image/bmp/bmp_os.odin
  56. 161 1
      core/image/common.odin
  57. 27 39
      core/image/png/png.odin
  58. 26 15
      core/log/file_console_logger.odin
  59. 60 0
      core/math/big/combinatorics.odin
  60. 1 0
      core/math/big/radix.odin
  61. 1 1
      core/math/cmplx/cmplx_trig.odin
  62. 10 15
      core/math/linalg/general.odin
  63. 3 3
      core/math/linalg/specific.odin
  64. 9 9
      core/math/math.odin
  65. 3 3
      core/math/math_gamma.odin
  66. 7 7
      core/math/math_lgamma.odin
  67. 1 1
      core/math/math_sincos.odin
  68. 3 3
      core/math/rand/exp.odin
  69. 3 3
      core/math/rand/normal.odin
  70. 9 6
      core/mem/raw.odin
  71. 341 0
      core/mem/rollback_stack_allocator.odin
  72. 36 0
      core/mem/tlsf/LICENSE
  73. 156 0
      core/mem/tlsf/tlsf.odin
  74. 738 0
      core/mem/tlsf/tlsf_internal.odin
  75. 14 0
      core/mem/tracking_allocator.odin
  76. 1 1
      core/odin/ast/ast.odin
  77. 2 0
      core/odin/ast/clone.odin
  78. 1 0
      core/odin/ast/walk.odin
  79. 37 2
      core/odin/parser/parser.odin
  80. 5 1
      core/os/dir_windows.odin
  81. 1 1
      core/os/os2/internal_util.odin
  82. 1 1
      core/os/os_darwin.odin
  83. 6 2
      core/os/os_netbsd.odin
  84. 49 0
      core/simd/x86/aes.odin
  85. 105 0
      core/slice/permute.odin
  86. 1 1
      core/strconv/generic_float.odin
  87. 284 8
      core/strconv/strconv.odin
  88. 4 4
      core/strings/builder.odin
  89. 1 1
      core/sync/futex_darwin.odin
  90. 6 2
      core/sync/primitives_netbsd.odin
  91. 1 0
      core/sys/info/platform_darwin.odin
  92. 5 0
      core/sys/linux/sys.odin
  93. 9 0
      core/sys/linux/types.odin
  94. 2 2
      core/sys/unix/pthread_freebsd.odin
  95. 2 2
      core/sys/unix/pthread_openbsd.odin
  96. 1 0
      core/sys/unix/pthread_unix.odin
  97. 2 2
      core/sys/windows/kernel32.odin
  98. 48 0
      core/testing/events.odin
  99. 71 0
      core/testing/logging.odin
  100. 329 0
      core/testing/reporting.odin

+ 75 - 131
.github/workflows/ci.yml

@@ -10,7 +10,7 @@ jobs:
     steps:
     steps:
     - uses: actions/checkout@v4
     - uses: actions/checkout@v4
     - name: Build, Check, and Test
     - name: Build, Check, and Test
-      timeout-minutes: 25
+      timeout-minutes: 15
       uses: vmactions/netbsd-vm@v1
       uses: vmactions/netbsd-vm@v1
       with:
       with:
         release: "10.0"
         release: "10.0"
@@ -24,157 +24,106 @@ jobs:
           /usr/sbin/pkg_add https://github.com/andreas-jonsson/llvm17-netbsd-bin/releases/download/pkgsrc-current/llvm-17.0.6.tgz
           /usr/sbin/pkg_add https://github.com/andreas-jonsson/llvm17-netbsd-bin/releases/download/pkgsrc-current/llvm-17.0.6.tgz
           /usr/sbin/pkg_add https://github.com/andreas-jonsson/llvm17-netbsd-bin/releases/download/pkgsrc-current/clang-17.0.6.tgz
           /usr/sbin/pkg_add https://github.com/andreas-jonsson/llvm17-netbsd-bin/releases/download/pkgsrc-current/clang-17.0.6.tgz
           ln -s /usr/pkg/bin/python3.11 /usr/bin/python3
           ln -s /usr/pkg/bin/python3.11 /usr/bin/python3
-          ln -s /usr/pkg/bin/bash /bin/bash
         run: |
         run: |
           git config --global --add safe.directory $(pwd)
           git config --global --add safe.directory $(pwd)
           gmake release
           gmake release
           ./odin version
           ./odin version
           ./odin report
           ./odin report
+          gmake -C vendor/stb/src
+          gmake -C vendor/cgltf/src
+          gmake -C vendor/miniaudio/src
           ./odin check examples/all -vet -strict-style -target:netbsd_amd64
           ./odin check examples/all -vet -strict-style -target:netbsd_amd64
-          (cd tests/core; gmake all_bsd)
-          (cd tests/internal; gmake all_bsd)
+          ./odin check examples/all -vet -strict-style -target:netbsd_arm64
+          ./odin test tests/core/normal.odin -file -all-packages -define:ODIN_TEST_FANCY=false
+          ./odin test tests/core/speed.odin -file -all-packages -o:speed -define:ODIN_TEST_FANCY=false
+          ./odin test tests/vendor -all-packages -define:ODIN_TEST_FANCY=false
+          ./odin test tests/benchmark -all-packages -define:ODIN_TEST_FANCY=false
           (cd tests/issues; ./run.sh)
           (cd tests/issues; ./run.sh)
-  build_linux:
-    name: Ubuntu Build, Check, and Test
-    runs-on: ubuntu-latest
+  ci:
+    strategy:
+      fail-fast: false
+      matrix:
+        # MacOS 13 runs on Intel, 14 runs on ARM
+        os: [ubuntu-latest, macos-13, macos-14]
+    runs-on: ${{ matrix.os }}
+    name: ${{ matrix.os == 'macos-14' && 'MacOS ARM' || (matrix.os == 'macos-13' && 'MacOS Intel' || 'Ubuntu') }} Build, Check, and Test
+    timeout-minutes: 15
     steps:
     steps:
-      - uses: actions/checkout@v1
-      - name: Download LLVM
+      - uses: actions/checkout@v4
+
+      - name: Download LLVM (Linux)
+        if: matrix.os == 'ubuntu-latest'
         run: |
         run: |
           wget https://apt.llvm.org/llvm.sh
           wget https://apt.llvm.org/llvm.sh
           chmod +x llvm.sh
           chmod +x llvm.sh
           sudo ./llvm.sh 17
           sudo ./llvm.sh 17
           echo "/usr/lib/llvm-17/bin" >> $GITHUB_PATH
           echo "/usr/lib/llvm-17/bin" >> $GITHUB_PATH
-      - name: build odin
+
+      - name: Download LLVM (MacOS Intel)
+        if: matrix.os == 'macos-13'
+        run: |
+          brew install llvm@17
+          echo "/usr/local/opt/llvm@17/bin" >> $GITHUB_PATH
+
+      - name: Download LLVM (MacOS ARM)
+        if: matrix.os == 'macos-14'
+        run: |
+          brew install llvm@17
+          echo "/opt/homebrew/opt/llvm@17/bin" >> $GITHUB_PATH
+
+      - name: Build Odin
         run: ./build_odin.sh release
         run: ./build_odin.sh release
       - name: Odin version
       - name: Odin version
         run: ./odin version
         run: ./odin version
-        timeout-minutes: 1
       - name: Odin report
       - name: Odin report
         run: ./odin report
         run: ./odin report
-        timeout-minutes: 1
+      - name: Compile needed Vendor
+        run: |
+          make -C vendor/stb/src
+          make -C vendor/cgltf/src
+          make -C vendor/miniaudio/src
       - name: Odin check
       - name: Odin check
         run: ./odin check examples/demo -vet
         run: ./odin check examples/demo -vet
-        timeout-minutes: 10
       - name: Odin run
       - name: Odin run
         run: ./odin run examples/demo
         run: ./odin run examples/demo
-        timeout-minutes: 10
       - name: Odin run -debug
       - name: Odin run -debug
         run: ./odin run examples/demo -debug
         run: ./odin run examples/demo -debug
-        timeout-minutes: 10
       - name: Odin check examples/all
       - name: Odin check examples/all
         run: ./odin check examples/all -strict-style
         run: ./odin check examples/all -strict-style
-        timeout-minutes: 10
-      - name: Core library tests
-        run: |
-          cd tests/core
-          make
-        timeout-minutes: 10
+      - name: Normal Core library tests
+        run: ./odin test tests/core/normal.odin -file -all-packages -define:ODIN_TEST_FANCY=false
+      - name: Optimized Core library tests
+        run: ./odin test tests/core/speed.odin -o:speed -file -all-packages -define:ODIN_TEST_FANCY=false
       - name: Vendor library tests
       - name: Vendor library tests
-        run: |
-          cd tests/vendor
-          make
-        timeout-minutes: 10
-      - name: Odin internals tests
-        run: |
-          cd tests/internal
-          make
-        timeout-minutes: 10
+        run: ./odin test tests/vendor -all-packages -define:ODIN_TEST_FANCY=false
+      - name: Internals tests
+        run: ./odin test tests/internal -all-packages -define:ODIN_TEST_FANCY=false
+      - name: Core library benchmarks
+        run: ./odin test tests/benchmark -all-packages -define:ODIN_TEST_FANCY=false
+      - name: GitHub Issue tests
+        run: |
+          cd tests/issues
+          ./run.sh
+
       - name: Odin check examples/all for Linux i386
       - name: Odin check examples/all for Linux i386
         run: ./odin check examples/all -vet -strict-style -target:linux_i386
         run: ./odin check examples/all -vet -strict-style -target:linux_i386
-        timeout-minutes: 10
+        if: matrix.os == 'ubuntu-latest'
       - name: Odin check examples/all for Linux arm64
       - name: Odin check examples/all for Linux arm64
         run: ./odin check examples/all -vet -strict-style -target:linux_arm64
         run: ./odin check examples/all -vet -strict-style -target:linux_arm64
-        timeout-minutes: 10
+        if: matrix.os == 'ubuntu-latest'
       - name: Odin check examples/all for FreeBSD amd64
       - name: Odin check examples/all for FreeBSD amd64
         run: ./odin check examples/all -vet -strict-style -target:freebsd_amd64
         run: ./odin check examples/all -vet -strict-style -target:freebsd_amd64
-        timeout-minutes: 10
+        if: matrix.os == 'ubuntu-latest'
       - name: Odin check examples/all for OpenBSD amd64
       - name: Odin check examples/all for OpenBSD amd64
         run: ./odin check examples/all -vet -strict-style -target:openbsd_amd64
         run: ./odin check examples/all -vet -strict-style -target:openbsd_amd64
-        timeout-minutes: 10
-  build_macOS:
-    name: MacOS Build, Check, and Test
-    runs-on: macos-13
-    steps:
-      - uses: actions/checkout@v1
-      - name: Download LLVM, and setup PATH
-        run: |
-          brew install llvm@17
-          echo "/usr/local/opt/llvm@17/bin" >> $GITHUB_PATH
-      - name: build odin
-        run: ./build_odin.sh release
-      - name: Odin version
-        run: ./odin version
-        timeout-minutes: 1
-      - name: Odin report
-        run: ./odin report
-        timeout-minutes: 1
-      - name: Odin check
-        run: ./odin check examples/demo -vet
-        timeout-minutes: 10
-      - name: Odin run
-        run: ./odin run examples/demo
-        timeout-minutes: 10
-      - name: Odin run -debug
-        run: ./odin run examples/demo -debug
-        timeout-minutes: 10
-      - name: Odin check examples/all
-        run: ./odin check examples/all -strict-style
-        timeout-minutes: 10
-      - name: Core library tests
-        run: |
-          cd tests/core
-          make
-        timeout-minutes: 10
-      - name: Odin internals tests
-        run: |
-          cd tests/internal
-          make
-        timeout-minutes: 10
-  build_macOS_arm:
-    name: MacOS ARM Build, Check, and Test
-    runs-on: macos-14 # This is an arm/m1 runner.
-    steps:
-      - uses: actions/checkout@v1
-      - name: Download LLVM and setup PATH
-        run: |
-          brew install llvm@17
-          echo "/opt/homebrew/opt/llvm@17/bin" >> $GITHUB_PATH
-      - name: build odin
-        run: ./build_odin.sh release
-      - name: Odin version
-        run: ./odin version
-        timeout-minutes: 1
-      - name: Odin report
-        run: ./odin report
-        timeout-minutes: 1
-      - name: Odin check
-        run: ./odin check examples/demo -vet
-        timeout-minutes: 10
-      - name: Odin run
-        run: ./odin run examples/demo
-        timeout-minutes: 10
-      - name: Odin run -debug
-        run: ./odin run examples/demo -debug
-        timeout-minutes: 10
-      - name: Odin check examples/all
-        run: ./odin check examples/all -strict-style
-        timeout-minutes: 10
-      - name: Core library tests
-        run: |
-          cd tests/core
-          make
-        timeout-minutes: 10
-      - name: Odin internals tests
-        run: |
-          cd tests/internal
-          make
-        timeout-minutes: 10
+        if: matrix.os == 'ubuntu-latest'
+
   build_windows:
   build_windows:
     name: Windows Build, Check, and Test
     name: Windows Build, Check, and Test
     runs-on: windows-2022
     runs-on: windows-2022
+    timeout-minutes: 15
     steps:
     steps:
-      - uses: actions/checkout@v1
+      - uses: actions/checkout@v4
       - name: build Odin
       - name: build Odin
         shell: cmd
         shell: cmd
         run: |
         run: |
@@ -182,72 +131,67 @@ jobs:
           ./build.bat 1
           ./build.bat 1
       - name: Odin version
       - name: Odin version
         run: ./odin version
         run: ./odin version
-        timeout-minutes: 1
       - name: Odin report
       - name: Odin report
         run: ./odin report
         run: ./odin report
-        timeout-minutes: 1
       - name: Odin check
       - name: Odin check
         shell: cmd
         shell: cmd
         run: |
         run: |
           call "C:\Program Files\Microsoft Visual Studio\2022\Enterprise\VC\Auxiliary\Build\vcvars64.bat
           call "C:\Program Files\Microsoft Visual Studio\2022\Enterprise\VC\Auxiliary\Build\vcvars64.bat
           odin check examples/demo -vet
           odin check examples/demo -vet
-        timeout-minutes: 10
       - name: Odin run
       - name: Odin run
         shell: cmd
         shell: cmd
         run: |
         run: |
           call "C:\Program Files\Microsoft Visual Studio\2022\Enterprise\VC\Auxiliary\Build\vcvars64.bat
           call "C:\Program Files\Microsoft Visual Studio\2022\Enterprise\VC\Auxiliary\Build\vcvars64.bat
           odin run examples/demo
           odin run examples/demo
-        timeout-minutes: 10
       - name: Odin run -debug
       - name: Odin run -debug
         shell: cmd
         shell: cmd
         run: |
         run: |
           call "C:\Program Files\Microsoft Visual Studio\2022\Enterprise\VC\Auxiliary\Build\vcvars64.bat
           call "C:\Program Files\Microsoft Visual Studio\2022\Enterprise\VC\Auxiliary\Build\vcvars64.bat
           odin run examples/demo -debug
           odin run examples/demo -debug
-        timeout-minutes: 10
       - name: Odin check examples/all
       - name: Odin check examples/all
         shell: cmd
         shell: cmd
         run: |
         run: |
           call "C:\Program Files\Microsoft Visual Studio\2022\Enterprise\VC\Auxiliary\Build\vcvars64.bat
           call "C:\Program Files\Microsoft Visual Studio\2022\Enterprise\VC\Auxiliary\Build\vcvars64.bat
           odin check examples/all -strict-style
           odin check examples/all -strict-style
-        timeout-minutes: 10
       - name: Core library tests
       - name: Core library tests
         shell: cmd
         shell: cmd
         run: |
         run: |
           call "C:\Program Files\Microsoft Visual Studio\2022\Enterprise\VC\Auxiliary\Build\vcvars64.bat
           call "C:\Program Files\Microsoft Visual Studio\2022\Enterprise\VC\Auxiliary\Build\vcvars64.bat
-          cd tests\core
-          call build.bat
-        timeout-minutes: 10
+          odin test tests/core/normal.odin -file -all-packages -define:ODIN_TEST_FANCY=false
+      - 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 -define:ODIN_TEST_FANCY=false
+      - name: Core library benchmarks
+        shell: cmd
+        run: |
+          call "C:\Program Files\Microsoft Visual Studio\2022\Enterprise\VC\Auxiliary\Build\vcvars64.bat
+          odin test tests/benchmark -all-packages -define:ODIN_TEST_FANCY=false
       - name: Vendor library tests
       - name: Vendor library tests
         shell: cmd
         shell: cmd
         run: |
         run: |
           call "C:\Program Files\Microsoft Visual Studio\2022\Enterprise\VC\Auxiliary\Build\vcvars64.bat
           call "C:\Program Files\Microsoft Visual Studio\2022\Enterprise\VC\Auxiliary\Build\vcvars64.bat
-          cd tests\vendor
-          call build.bat
-        timeout-minutes: 10
+          odin test tests/vendor -all-packages -define:ODIN_TEST_FANCY=false
       - name: Odin internals tests
       - name: Odin internals tests
         shell: cmd
         shell: cmd
         run: |
         run: |
           call "C:\Program Files\Microsoft Visual Studio\2022\Enterprise\VC\Auxiliary\Build\vcvars64.bat
           call "C:\Program Files\Microsoft Visual Studio\2022\Enterprise\VC\Auxiliary\Build\vcvars64.bat
-          cd tests\internal
-          call build.bat
-        timeout-minutes: 10
+          odin test tests/internal -all-packages -define:ODIN_TEST_FANCY=false
       - name: Odin documentation tests
       - name: Odin documentation tests
         shell: cmd
         shell: cmd
         run: |
         run: |
           call "C:\Program Files\Microsoft Visual Studio\2022\Enterprise\VC\Auxiliary\Build\vcvars64.bat
           call "C:\Program Files\Microsoft Visual Studio\2022\Enterprise\VC\Auxiliary\Build\vcvars64.bat
           cd tests\documentation
           cd tests\documentation
           call build.bat
           call build.bat
-        timeout-minutes: 10
       - name: core:math/big tests
       - name: core:math/big tests
         shell: cmd
         shell: cmd
         run: |
         run: |
           call "C:\Program Files\Microsoft Visual Studio\2022\Enterprise\VC\Auxiliary\Build\vcvars64.bat
           call "C:\Program Files\Microsoft Visual Studio\2022\Enterprise\VC\Auxiliary\Build\vcvars64.bat
           cd tests\core\math\big
           cd tests\core\math\big
           call build.bat
           call build.bat
-        timeout-minutes: 10
       - name: Odin check examples/all for Windows 32bits
       - name: Odin check examples/all for Windows 32bits
         shell: cmd
         shell: cmd
         run: |
         run: |
           call "C:\Program Files\Microsoft Visual Studio\2022\Enterprise\VC\Auxiliary\Build\vcvars64.bat
           call "C:\Program Files\Microsoft Visual Studio\2022\Enterprise\VC\Auxiliary\Build\vcvars64.bat
           odin check examples/all -strict-style -target:windows_i386
           odin check examples/all -strict-style -target:windows_i386
-        timeout-minutes: 10

+ 13 - 28
.github/workflows/nightly.yml

@@ -11,7 +11,7 @@ jobs:
     if: github.repository == 'odin-lang/Odin'
     if: github.repository == 'odin-lang/Odin'
     runs-on: windows-2022
     runs-on: windows-2022
     steps:
     steps:
-      - uses: actions/checkout@v1
+      - uses: actions/checkout@v4
       - name: build Odin
       - name: build Odin
         shell: cmd
         shell: cmd
         run: |
         run: |
@@ -45,7 +45,7 @@ jobs:
     if: github.repository == 'odin-lang/Odin'
     if: github.repository == 'odin-lang/Odin'
     runs-on: ubuntu-latest
     runs-on: ubuntu-latest
     steps:
     steps:
-      - uses: actions/checkout@v1
+      - uses: actions/checkout@v4
       - name: (Linux) Download LLVM
       - name: (Linux) Download LLVM
         run: |
         run: |
           wget https://apt.llvm.org/llvm.sh
           wget https://apt.llvm.org/llvm.sh
@@ -79,7 +79,7 @@ jobs:
     if: github.repository == 'odin-lang/Odin'
     if: github.repository == 'odin-lang/Odin'
     runs-on: macos-13
     runs-on: macos-13
     steps:
     steps:
-      - uses: actions/checkout@v1
+      - uses: actions/checkout@v4
       - name: Download LLVM and setup PATH
       - name: Download LLVM and setup PATH
         run: |
         run: |
           brew install llvm@17 dylibbundler
           brew install llvm@17 dylibbundler
@@ -113,7 +113,7 @@ jobs:
     if: github.repository == 'odin-lang/Odin'
     if: github.repository == 'odin-lang/Odin'
     runs-on: macos-14 # ARM machine
     runs-on: macos-14 # ARM machine
     steps:
     steps:
-      - uses: actions/checkout@v1
+      - uses: actions/checkout@v4
       - name: Download LLVM and setup PATH
       - name: Download LLVM and setup PATH
         run: |
         run: |
           brew install llvm@17 dylibbundler
           brew install llvm@17 dylibbundler
@@ -146,16 +146,16 @@ jobs:
     runs-on: [ubuntu-latest]
     runs-on: [ubuntu-latest]
     needs: [build_windows, build_macos, build_macos_arm, build_ubuntu]
     needs: [build_windows, build_macos, build_macos_arm, build_ubuntu]
     steps:
     steps:
-      - uses: actions/checkout@v1
+      - uses: actions/checkout@v4
       - uses: actions/setup-python@v2
       - uses: actions/setup-python@v2
         with:
         with:
           python-version: '3.8.x'
           python-version: '3.8.x'
 
 
-      - name: Install B2 CLI
+      - name: Install B2 SDK
         shell: bash
         shell: bash
         run: |
         run: |
           python -m pip install --upgrade pip
           python -m pip install --upgrade pip
-          pip install --upgrade b2
+          pip install --upgrade b2sdk
 
 
       - name: Display Python version
       - name: Display Python version
         run: python -c "import sys; print(sys.version)"
         run: python -c "import sys; print(sys.version)"
@@ -188,24 +188,9 @@ jobs:
           BUCKET: ${{ secrets.B2_BUCKET }}
           BUCKET: ${{ secrets.B2_BUCKET }}
           DAYS_TO_KEEP: ${{ secrets.B2_DAYS_TO_KEEP }}
           DAYS_TO_KEEP: ${{ secrets.B2_DAYS_TO_KEEP }}
         run: |
         run: |
-          echo Authorizing B2 account
-          b2 authorize-account "$APPID" "$APPKEY"
-
-          echo Uploading artifcates to B2
-          chmod +x ./ci/upload_create_nightly.sh
-          ./ci/upload_create_nightly.sh "$BUCKET" windows-amd64 windows_artifacts/
-          ./ci/upload_create_nightly.sh "$BUCKET" ubuntu-amd64 ubuntu_artifacts/dist.zip
-          ./ci/upload_create_nightly.sh "$BUCKET" macos-amd64 macos_artifacts/dist.zip
-          ./ci/upload_create_nightly.sh "$BUCKET" macos-arm64 macos_arm_artifacts/dist.zip
-
-          echo Deleting old artifacts in B2
-          python3 ci/delete_old_binaries.py "$BUCKET" "$DAYS_TO_KEEP"
-
-          echo Creating nightly.json
-          python3 ci/create_nightly_json.py "$BUCKET" > nightly.json
-
-          echo Uploading nightly.json
-          b2 upload-file "$BUCKET" nightly.json nightly.json
-
-          echo Clear B2 account info
-          b2 clear-account
+          python3 ci/nightly.py artifact windows-amd64 windows_artifacts/
+          python3 ci/nightly.py artifact ubuntu-amd64 ubuntu_artifacts/dist.zip
+          python3 ci/nightly.py artifact macos-amd64 macos_artifacts/dist.zip
+          python3 ci/nightly.py artifact macos-arm64 macos_arm_artifacts/dist.zip
+          python3 ci/nightly.py prune
+          python3 ci/nightly.py json

+ 2 - 0
base/builtin/builtin.odin

@@ -126,3 +126,5 @@ clamp :: proc(value, minimum, maximum: T) -> T ---
 
 
 soa_zip :: proc(slices: ...) -> #soa[]Struct ---
 soa_zip :: proc(slices: ...) -> #soa[]Struct ---
 soa_unzip :: proc(value: $S/#soa[]$E) -> (slices: ...) ---
 soa_unzip :: proc(value: $S/#soa[]$E) -> (slices: ...) ---
+
+unreachable :: proc() -> ! ---

+ 4 - 0
base/intrinsics/intrinsics.odin

@@ -295,6 +295,10 @@ simd_rotate_right :: proc(a: #simd[N]T, $offset: int) -> #simd[N]T ---
 // if all listed features are supported.
 // if all listed features are supported.
 has_target_feature :: proc($test: $T) -> bool where type_is_string(T) || type_is_proc(T) ---
 has_target_feature :: proc($test: $T) -> bool where type_is_string(T) || type_is_proc(T) ---
 
 
+
+// Returns the value of the procedure where `x` must be a call expression
+procedure_of :: proc(x: $T) -> T where type_is_proc(T) ---
+
 // WASM targets only
 // WASM targets only
 wasm_memory_grow :: proc(index, delta: uintptr) -> int ---
 wasm_memory_grow :: proc(index, delta: uintptr) -> int ---
 wasm_memory_size :: proc(index: uintptr)        -> int ---
 wasm_memory_size :: proc(index: uintptr)        -> int ---

+ 35 - 2
base/runtime/core.odin

@@ -470,6 +470,15 @@ Raw_Soa_Pointer :: struct {
 	index: int,
 	index: int,
 }
 }
 
 
+Raw_Complex32     :: struct {real, imag: f16}
+Raw_Complex64     :: struct {real, imag: f32}
+Raw_Complex128    :: struct {real, imag: f64}
+Raw_Quaternion64  :: struct {imag, jmag, kmag: f16, real: f16}
+Raw_Quaternion128 :: struct {imag, jmag, kmag: f32, real: f32}
+Raw_Quaternion256 :: struct {imag, jmag, kmag: f64, real: f64}
+Raw_Quaternion64_Vector_Scalar  :: struct {vector: [3]f16, scalar: f16}
+Raw_Quaternion128_Vector_Scalar :: struct {vector: [3]f32, scalar: f32}
+Raw_Quaternion256_Vector_Scalar :: struct {vector: [3]f64, scalar: f64}
 
 
 
 
 /*
 /*
@@ -551,6 +560,19 @@ Odin_Platform_Subtarget_Type :: type_of(ODIN_PLATFORM_SUBTARGET)
 */
 */
 Odin_Sanitizer_Flags :: type_of(ODIN_SANITIZER_FLAGS)
 Odin_Sanitizer_Flags :: type_of(ODIN_SANITIZER_FLAGS)
 
 
+/*
+	// Defined internally by the compiler
+	Odin_Optimization_Mode :: enum int {
+		None       = -1,
+		Minimal    =  0,
+		Size       =  1,
+		Speed      =  2,
+		Aggressive =  3,
+	}
+
+	ODIN_OPTIMIZATION_MODE // is a constant
+*/
+Odin_Optimization_Mode :: type_of(ODIN_OPTIMIZATION_MODE)
 
 
 /////////////////////////////
 /////////////////////////////
 // Init Startup Procedures //
 // Init Startup Procedures //
@@ -692,7 +714,7 @@ default_assertion_failure_proc :: proc(prefix, message: string, loc: Source_Code
 	when ODIN_OS == .Freestanding {
 	when ODIN_OS == .Freestanding {
 		// Do nothing
 		// Do nothing
 	} else {
 	} else {
-		when !ODIN_DISABLE_ASSERT {
+		when ODIN_OS != .Orca && !ODIN_DISABLE_ASSERT {
 			print_caller_location(loc)
 			print_caller_location(loc)
 			print_string(" ")
 			print_string(" ")
 		}
 		}
@@ -701,7 +723,18 @@ default_assertion_failure_proc :: proc(prefix, message: string, loc: Source_Code
 			print_string(": ")
 			print_string(": ")
 			print_string(message)
 			print_string(message)
 		}
 		}
-		print_byte('\n')
+
+		when ODIN_OS == .Orca {
+			assert_fail(
+				cstring(raw_data(loc.file_path)),
+				cstring(raw_data(loc.procedure)),
+				loc.line,
+				"",
+				cstring(raw_data(orca_stderr_buffer[:orca_stderr_buffer_idx])),
+			)
+		} else {
+			print_byte('\n')
+		}
 	}
 	}
 	trap()
 	trap()
 }
 }

+ 1 - 1
base/runtime/default_allocators_general.odin

@@ -6,7 +6,7 @@ when ODIN_DEFAULT_TO_NIL_ALLOCATOR {
 } else when ODIN_DEFAULT_TO_PANIC_ALLOCATOR {
 } else when ODIN_DEFAULT_TO_PANIC_ALLOCATOR {
 	default_allocator_proc :: panic_allocator_proc
 	default_allocator_proc :: panic_allocator_proc
 	default_allocator :: panic_allocator
 	default_allocator :: panic_allocator
-} else when ODIN_ARCH == .wasm32 || ODIN_ARCH == .wasm64p32 {
+} else when ODIN_OS != .Orca && (ODIN_ARCH == .wasm32 || ODIN_ARCH == .wasm64p32) {
 	default_allocator :: default_wasm_allocator
 	default_allocator :: default_wasm_allocator
 	default_allocator_proc :: wasm_allocator_proc
 	default_allocator_proc :: wasm_allocator_proc
 } else {
 } else {

+ 1 - 1
base/runtime/docs.odin

@@ -157,7 +157,7 @@ __dynamic_map_get // dynamic map calls
 __dynamic_map_set // dynamic map calls
 __dynamic_map_set // dynamic map calls
 
 
 
 
-## Dynamic literals ([dymamic]T and map[K]V) (can be disabled with -no-dynamic-literals)
+## Dynamic literals ([dynamic]T and map[K]V) (can be disabled with -no-dynamic-literals)
 
 
 __dynamic_array_reserve
 __dynamic_array_reserve
 __dynamic_array_append
 __dynamic_array_append

+ 25 - 11
base/runtime/entry_wasm.odin

@@ -6,15 +6,29 @@ package runtime
 import "base:intrinsics"
 import "base:intrinsics"
 
 
 when !ODIN_TEST && !ODIN_NO_ENTRY_POINT {
 when !ODIN_TEST && !ODIN_NO_ENTRY_POINT {
-	@(link_name="_start", linkage="strong", require, export)
-	_start :: proc "c" () {
-		context = default_context()
-		#force_no_inline _startup_runtime()
-		intrinsics.__entry_point()
+	when ODIN_OS == .Orca {
+		@(linkage="strong", require, export)
+		oc_on_init :: proc "c" () {
+			context = default_context()
+			#force_no_inline _startup_runtime()
+			intrinsics.__entry_point()
+		}
+		@(linkage="strong", require, export)
+		oc_on_terminate :: proc "c" () {
+			context = default_context()
+			#force_no_inline _cleanup_runtime()
+		}
+	} else {
+		@(link_name="_start", linkage="strong", require, export)
+		_start :: proc "c" () {
+			context = default_context()
+			#force_no_inline _startup_runtime()
+			intrinsics.__entry_point()
+		}
+		@(link_name="_end", linkage="strong", require, export)
+		_end :: proc "c" () {
+			context = default_context()
+			#force_no_inline _cleanup_runtime()
+		}
 	}
 	}
-	@(link_name="_end", linkage="strong", require, export)
-	_end :: proc "c" () {
-		context = default_context()
-		#force_no_inline _cleanup_runtime()
-	}
-}
+}

+ 4 - 0
base/runtime/error_checks.odin

@@ -4,6 +4,8 @@ package runtime
 bounds_trap :: proc "contextless" () -> ! {
 bounds_trap :: proc "contextless" () -> ! {
 	when ODIN_OS == .Windows {
 	when ODIN_OS == .Windows {
 		windows_trap_array_bounds()
 		windows_trap_array_bounds()
+	} else when ODIN_OS == .Orca {
+		abort_ext("", "", 0, "bounds trap")
 	} else {
 	} else {
 		trap()
 		trap()
 	}
 	}
@@ -13,6 +15,8 @@ bounds_trap :: proc "contextless" () -> ! {
 type_assertion_trap :: proc "contextless" () -> ! {
 type_assertion_trap :: proc "contextless" () -> ! {
 	when ODIN_OS == .Windows {
 	when ODIN_OS == .Windows {
 		windows_trap_type_assertion()
 		windows_trap_type_assertion()
+	} else when ODIN_OS == .Orca {
+		abort_ext("", "", 0, "type assertion trap")
 	} else {
 	} else {
 		trap()
 		trap()
 	}
 	}

+ 29 - 0
base/runtime/heap_allocator_orca.odin

@@ -0,0 +1,29 @@
+//+build orca
+//+private
+package runtime
+
+foreign {
+	@(link_name="malloc")   _orca_malloc   :: proc "c" (size: int) -> rawptr ---
+	@(link_name="calloc")   _orca_calloc   :: proc "c" (num, size: int) -> rawptr ---
+	@(link_name="free")     _orca_free     :: proc "c" (ptr: rawptr) ---
+	@(link_name="realloc")  _orca_realloc  :: proc "c" (ptr: rawptr, size: int) -> rawptr ---
+}
+
+_heap_alloc :: proc(size: int, zero_memory := true) -> rawptr {
+	if size <= 0 {
+		return nil
+	}
+	if zero_memory {
+		return _orca_calloc(1, size)
+	} else {
+		return _orca_malloc(size)
+	}
+}
+
+_heap_resize :: proc(ptr: rawptr, new_size: int) -> rawptr {
+	return _orca_realloc(ptr, new_size)
+}
+
+_heap_free :: proc(ptr: rawptr) {
+	_orca_free(ptr)
+}

+ 1 - 1
base/runtime/heap_allocator_other.odin

@@ -12,4 +12,4 @@ _heap_resize :: proc(ptr: rawptr, new_size: int) -> rawptr {
 
 
 _heap_free :: proc(ptr: rawptr) {
 _heap_free :: proc(ptr: rawptr) {
 	unimplemented("base:runtime 'heap_free' procedure is not supported on this platform")
 	unimplemented("base:runtime 'heap_free' procedure is not supported on this platform")
-}
+}

+ 2 - 2
base/runtime/internal.odin

@@ -483,7 +483,7 @@ quaternion256_ne :: #force_inline proc "contextless" (a, b: quaternion256) -> bo
 string_decode_rune :: #force_inline proc "contextless" (s: string) -> (rune, int) {
 string_decode_rune :: #force_inline proc "contextless" (s: string) -> (rune, int) {
 	// NOTE(bill): Duplicated here to remove dependency on package unicode/utf8
 	// NOTE(bill): Duplicated here to remove dependency on package unicode/utf8
 
 
-	@static accept_sizes := [256]u8{
+	@(static, rodata) accept_sizes := [256]u8{
 		0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, // 0x00-0x0f
 		0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, // 0x00-0x0f
 		0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, // 0x10-0x1f
 		0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, // 0x10-0x1f
 		0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, // 0x20-0x2f
 		0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, // 0x20-0x2f
@@ -504,7 +504,7 @@ string_decode_rune :: #force_inline proc "contextless" (s: string) -> (rune, int
 	}
 	}
 	Accept_Range :: struct {lo, hi: u8}
 	Accept_Range :: struct {lo, hi: u8}
 
 
-	@static accept_ranges := [5]Accept_Range{
+	@(static, rodata) accept_ranges := [5]Accept_Range{
 		{0x80, 0xbf},
 		{0x80, 0xbf},
 		{0xa0, 0xbf},
 		{0xa0, 0xbf},
 		{0x80, 0x9f},
 		{0x80, 0x9f},

+ 43 - 0
base/runtime/os_specific_orca.odin

@@ -0,0 +1,43 @@
+//+build orca
+//+private
+package runtime
+
+import "base:intrinsics"
+
+// Constants allowing to specify the level of logging verbosity.
+log_level :: enum u32 {
+	// Only errors are logged.
+	ERROR = 0,
+	// Only warnings and errors are logged.
+	WARNING = 1,
+	// All messages are logged.
+	INFO = 2,
+	COUNT = 3,
+}
+
+@(default_calling_convention="c", link_prefix="oc_")
+foreign {
+	abort_ext   :: proc(file: cstring, function: cstring, line: i32, fmt: cstring, #c_vararg args: ..any) -> ! ---
+	assert_fail :: proc(file: cstring, function: cstring, line: i32, src: cstring, fmt: cstring, #c_vararg args: ..any) -> ! ---
+	log_ext     :: proc(level: log_level, function: cstring, file: cstring, line: i32, fmt: cstring, #c_vararg args: ..any) ---
+}
+
+// NOTE: This is all pretty gross, don't look.
+
+// WASM is single threaded so this should be fine.
+orca_stderr_buffer:     [4096]byte
+orca_stderr_buffer_idx: int
+
+_stderr_write :: proc "contextless" (data: []byte) -> (int, _OS_Errno) {
+	for b in data {
+		orca_stderr_buffer[orca_stderr_buffer_idx] = b
+		orca_stderr_buffer_idx += 1
+
+		if b == '\n' || orca_stderr_buffer_idx == len(orca_stderr_buffer)-1 {
+			log_ext(.ERROR, "", "", 0, cstring(raw_data(orca_stderr_buffer[:orca_stderr_buffer_idx])))
+			orca_stderr_buffer_idx = 0
+		}
+	}
+
+	return len(data), 0
+}

+ 16 - 10
base/runtime/procs.odin

@@ -25,13 +25,19 @@ when ODIN_NO_CRT && ODIN_OS == .Windows {
 		RtlMoveMemory(dst, src, len)
 		RtlMoveMemory(dst, src, len)
 		return dst
 		return dst
 	}
 	}
-} else when ODIN_NO_CRT || (ODIN_ARCH == .wasm32 || ODIN_ARCH == .wasm64p32) {
+} else when ODIN_NO_CRT || (ODIN_OS != .Orca && (ODIN_ARCH == .wasm32 || ODIN_ARCH == .wasm64p32)) {
+	// NOTE: on wasm, calls to these procs are generated (by LLVM) with type `i32` instead of `int`.
+	//
+	// NOTE: `#any_int` is also needed, because calls that we generate (and package code)
+	//       will be using `int` and need to be converted.
+	int_t :: i32 when ODIN_ARCH == .wasm64p32 else int
+
 	@(link_name="memset", linkage="strong", require)
 	@(link_name="memset", linkage="strong", require)
-	memset :: proc "c" (ptr: rawptr, val: i32, len: int) -> rawptr {
+	memset :: proc "c" (ptr: rawptr, val: i32, #any_int len: int_t) -> rawptr {
 		if ptr != nil && len != 0 {
 		if ptr != nil && len != 0 {
 			b := byte(val)
 			b := byte(val)
 			p := ([^]byte)(ptr)
 			p := ([^]byte)(ptr)
-			for i := 0; i < len; i += 1 {
+			for i := int_t(0); i < len; i += 1 {
 				p[i] = b
 				p[i] = b
 			}
 			}
 		}
 		}
@@ -39,10 +45,10 @@ when ODIN_NO_CRT && ODIN_OS == .Windows {
 	}
 	}
 
 
 	@(link_name="bzero", linkage="strong", require)
 	@(link_name="bzero", linkage="strong", require)
-	bzero :: proc "c" (ptr: rawptr, len: int) -> rawptr {
+	bzero :: proc "c" (ptr: rawptr, #any_int len: int_t) -> rawptr {
 		if ptr != nil && len != 0 {
 		if ptr != nil && len != 0 {
 			p := ([^]byte)(ptr)
 			p := ([^]byte)(ptr)
-			for i := 0; i < len; i += 1 {
+			for i := int_t(0); i < len; i += 1 {
 				p[i] = 0
 				p[i] = 0
 			}
 			}
 		}
 		}
@@ -50,7 +56,7 @@ when ODIN_NO_CRT && ODIN_OS == .Windows {
 	}
 	}
 
 
 	@(link_name="memmove", linkage="strong", require)
 	@(link_name="memmove", linkage="strong", require)
-	memmove :: proc "c" (dst, src: rawptr, len: int) -> rawptr {
+	memmove :: proc "c" (dst, src: rawptr, #any_int len: int_t) -> rawptr {
 		d, s := ([^]byte)(dst), ([^]byte)(src)
 		d, s := ([^]byte)(dst), ([^]byte)(src)
 		if d == s || len == 0 {
 		if d == s || len == 0 {
 			return dst
 			return dst
@@ -63,7 +69,7 @@ when ODIN_NO_CRT && ODIN_OS == .Windows {
 		}
 		}
 
 
 		if s > d && uintptr(s)-uintptr(d) < uintptr(len) {
 		if s > d && uintptr(s)-uintptr(d) < uintptr(len) {
-			for i := 0; i < len; i += 1 {
+			for i := int_t(0); i < len; i += 1 {
 				d[i] = s[i]
 				d[i] = s[i]
 			}
 			}
 			return dst
 			return dst
@@ -71,10 +77,10 @@ when ODIN_NO_CRT && ODIN_OS == .Windows {
 		return memcpy(dst, src, len)
 		return memcpy(dst, src, len)
 	}
 	}
 	@(link_name="memcpy", linkage="strong", require)
 	@(link_name="memcpy", linkage="strong", require)
-	memcpy :: proc "c" (dst, src: rawptr, len: int) -> rawptr {
+	memcpy :: proc "c" (dst, src: rawptr, #any_int len: int_t) -> rawptr {
 		d, s := ([^]byte)(dst), ([^]byte)(src)
 		d, s := ([^]byte)(dst), ([^]byte)(src)
 		if d != s {
 		if d != s {
-			for i := 0; i < len; i += 1 {
+			for i := int_t(0); i < len; i += 1 {
 				d[i] = s[i]
 				d[i] = s[i]
 			}
 			}
 		}
 		}
@@ -92,4 +98,4 @@ when ODIN_NO_CRT && ODIN_OS == .Windows {
 		}
 		}
 		return ptr
 		return ptr
 	}
 	}
-}
+}

+ 14 - 14
base/runtime/wasm_allocator.odin

@@ -7,20 +7,20 @@ import "base:intrinsics"
 Port of emmalloc, modified for use in Odin.
 Port of emmalloc, modified for use in Odin.
 
 
 Invariants:
 Invariants:
- - Per-allocation header overhead is 8 bytes, smallest allocated payload
-   amount is 8 bytes, and a multiple of 4 bytes.
- - Acquired memory blocks are subdivided into disjoint regions that lie
-   next to each other.
- - A region is either in used or free.
-   Used regions may be adjacent, and a used and unused region
-   may be adjacent, but not two unused ones - they would be
-   merged.
- - Memory allocation takes constant time, unless the alloc needs to wasm_memory_grow()
-   or memory is very close to being exhausted.
- - Free and used regions are managed inside "root regions", which are slabs
-   of memory acquired via wasm_memory_grow().
- - Memory retrieved using wasm_memory_grow() can not be given back to the OS.
-   Therefore, frees are internal to the allocator.
+	- Per-allocation header overhead is 8 bytes, smallest allocated payload
+	  amount is 8 bytes, and a multiple of 4 bytes.
+	- Acquired memory blocks are subdivided into disjoint regions that lie
+	  next to each other.
+	- A region is either in used or free.
+	  Used regions may be adjacent, and a used and unused region
+	  may be adjacent, but not two unused ones - they would be
+	  merged.
+	- Memory allocation takes constant time, unless the alloc needs to wasm_memory_grow()
+	  or memory is very close to being exhausted.
+	- Free and used regions are managed inside "root regions", which are slabs
+	  of memory acquired via wasm_memory_grow().
+	- Memory retrieved using wasm_memory_grow() can not be given back to the OS.
+	  Therefore, frees are internal to the allocator.
 
 
 Copyright (c) 2010-2014 Emscripten authors, see AUTHORS file.
 Copyright (c) 2010-2014 Emscripten authors, see AUTHORS file.
 
 

+ 0 - 51
ci/create_nightly_json.py

@@ -1,51 +0,0 @@
-import subprocess
-import sys
-import json
-import datetime
-import urllib.parse
-import sys
-
-def main():
-    files_by_date = {}
-    bucket = sys.argv[1]
-
-    files_lines = execute_cli(f"b2 ls --long {bucket} nightly").split("\n")
-    for x in files_lines:
-        parts = x.split(" ", 1)
-        if parts[0]:
-            json_str = execute_cli(f"b2 get-file-info {parts[0]}")
-            data = json.loads(json_str)
-            name = remove_prefix(data['fileName'], "nightly/")
-            url = f"https://f001.backblazeb2.com/file/{bucket}/nightly/{urllib.parse.quote_plus(name)}"
-            sha1 = data['contentSha1']
-            size = int(data['size'])
-            ts = int(data['fileInfo']['src_last_modified_millis'])
-            date = datetime.datetime.fromtimestamp(ts/1000).strftime('%Y-%m-%d')
-            
-            if date not in files_by_date.keys():
-                files_by_date[date] = []
-
-            files_by_date[date].append({
-                                            'name': name,
-                                            'url': url,
-                                            'sha1': sha1,
-                                            'sizeInBytes': size,
-                                         })
-
-    now = datetime.datetime.utcnow().isoformat()
-
-    print(json.dumps({
-                        'last_updated' : now,
-                        'files': files_by_date
-                     }, sort_keys=True, indent=4))
-
-def remove_prefix(text, prefix):
-    return text[text.startswith(prefix) and len(prefix):]
-
-def execute_cli(command):
-    sb = subprocess.Popen(command, shell=True, stdout=subprocess.PIPE)
-    return sb.stdout.read().decode("utf-8");
-
-if __name__ == '__main__':
-    sys.exit(main())
-

+ 0 - 34
ci/delete_old_binaries.py

@@ -1,34 +0,0 @@
-import subprocess
-import sys
-import json
-import datetime
-import urllib.parse
-import sys
-
-def main():
-    files_by_date = {}
-    bucket = sys.argv[1]
-    days_to_keep = int(sys.argv[2])
-    print(f"Looking for binaries to delete older than {days_to_keep} days")
-
-    files_lines = execute_cli(f"b2 ls --long --versions {bucket} nightly").split("\n")
-    for x in files_lines:
-        parts = [y for y in x.split(' ') if y]
-
-        if parts and parts[0]:
-            date = datetime.datetime.strptime(parts[2], '%Y-%m-%d').replace(hour=0, minute=0, second=0, microsecond=0)
-            now = datetime.datetime.utcnow().replace(hour=0, minute=0, second=0, microsecond=0)
-            delta = now - date
-
-            if delta.days > days_to_keep:
-                print(f'Deleting {parts[5]}')
-                execute_cli(f'b2 delete-file-version {parts[0]}')
-
-
-def execute_cli(command):
-    sb = subprocess.Popen(command, shell=True, stdout=subprocess.PIPE)
-    return sb.stdout.read().decode("utf-8");
-
-if __name__ == '__main__':
-    sys.exit(main())
-

+ 140 - 0
ci/nightly.py

@@ -0,0 +1,140 @@
+import os
+import sys
+from zipfile  import ZipFile, ZIP_DEFLATED
+from b2sdk.v2 import InMemoryAccountInfo, B2Api
+from datetime import datetime
+import json
+
+UPLOAD_FOLDER = "nightly/"
+
+info   = InMemoryAccountInfo()
+b2_api = B2Api(info)
+application_key_id = os.environ['APPID']
+application_key    = os.environ['APPKEY']
+bucket_name        = os.environ['BUCKET']
+days_to_keep       = os.environ['DAYS_TO_KEEP']
+
+def auth() -> bool:
+	try:
+		realm = b2_api.account_info.get_realm()
+		return True # Already authenticated
+	except:
+		pass        # Not yet authenticated
+
+	err = b2_api.authorize_account("production", application_key_id, application_key)
+	return err == None
+
+def get_bucket():
+	if not auth(): sys.exit(1)
+	return b2_api.get_bucket_by_name(bucket_name)
+
+def remove_prefix(text: str, prefix: str) -> str:
+	return text[text.startswith(prefix) and len(prefix):]
+
+def create_and_upload_artifact_zip(platform: str, artifact: str) -> int:
+	now = datetime.utcnow().replace(hour=0, minute=0, second=0, microsecond=0)
+	destination_zip_name = "odin-{}-nightly+{}.zip".format(platform, now.strftime("%Y-%m-%d"))
+
+	source_zip_name = artifact
+	if not artifact.endswith(".zip"):
+		print(f"Creating archive {destination_zip_name} from {artifact} and uploading to {bucket_name}")
+
+		source_zip_name = destination_zip_name
+		with ZipFile(source_zip_name, mode='w', compression=ZIP_DEFLATED, compresslevel=9) as z:
+			for root, directory, filenames in os.walk(artifact):
+				for file in filenames:
+					file_path = os.path.join(root, file)
+					zip_path  = os.path.join("dist", os.path.relpath(file_path, artifact))
+					z.write(file_path, zip_path)
+
+		if not os.path.exists(source_zip_name):
+			print(f"Error: Newly created ZIP archive {source_zip_name} not found.")
+			return 1
+
+	print("Uploading {} to {}".format(source_zip_name, UPLOAD_FOLDER + destination_zip_name))
+	bucket = get_bucket()
+	res = bucket.upload_local_file(
+		source_zip_name,                   # Local file to upload
+		"nightly/" + destination_zip_name, # B2 destination path
+	)
+	return 0
+
+def prune_artifacts():
+	print(f"Looking for binaries to delete older than {days_to_keep} days")
+
+	bucket = get_bucket()
+	for file, _ in bucket.ls(UPLOAD_FOLDER, latest_only=False):
+		# Timestamp is in milliseconds
+		date  = datetime.fromtimestamp(file.upload_timestamp / 1_000.0).replace(hour=0, minute=0, second=0, microsecond=0)
+		now   = datetime.utcnow().replace(hour=0, minute=0, second=0, microsecond=0)
+		delta = now - date
+
+		if delta.days > int(days_to_keep):
+			print("Deleting {}".format(file.file_name))
+			file.delete()
+
+	return 0
+
+def update_nightly_json():
+	print(f"Updating nightly.json with files {days_to_keep} days or newer")
+
+	files_by_date = {}
+
+	bucket = get_bucket()
+
+	for file, _ in bucket.ls(UPLOAD_FOLDER, latest_only=True):
+		# Timestamp is in milliseconds
+		date = datetime.fromtimestamp(file.upload_timestamp / 1_000.0).replace(hour=0, minute=0, second=0, microsecond=0).strftime('%Y-%m-%d')
+		name = remove_prefix(file.file_name, UPLOAD_FOLDER)
+		sha1 = file.content_sha1
+		size = file.size
+		url  = bucket.get_download_url(file.file_name)
+
+		if date not in files_by_date.keys():
+			files_by_date[date] = []
+
+		files_by_date[date].append({
+			'name':        name,
+			'url':         url,
+			'sha1':        sha1,
+			'sizeInBytes': size,
+		})
+
+	now = datetime.utcnow().isoformat()
+
+	nightly = json.dumps({
+		'last_updated' : now,
+		'files': files_by_date
+	}, sort_keys=True, indent=4, ensure_ascii=False).encode('utf-8')
+
+	res = bucket.upload_bytes(
+		nightly,        # JSON bytes
+		"nightly.json", # B2 destination path
+	)
+	return 0
+
+if __name__ == "__main__":
+	if len(sys.argv) == 1:
+		print("Usage: {} <verb> [arguments]".format(sys.argv[0]))
+		print("\tartifact <platform prefix> <artifact path>\n\t\tCreates and uploads a platform artifact zip.")
+		print("\tprune\n\t\tDeletes old artifacts from bucket")
+		print("\tjson\n\t\tUpdate and upload nightly.json")
+		sys.exit(1)
+	else:
+		command = sys.argv[1].lower()
+		if command == "artifact":
+			if len(sys.argv) != 4:
+				print("Usage: {} artifact <platform prefix> <artifact path>".format(sys.argv[0]))
+				print("Error: Expected artifact command to be given platform prefix and artifact path.\n")
+				sys.exit(1)
+
+			res = create_and_upload_artifact_zip(sys.argv[2], sys.argv[3])
+			sys.exit(res)
+
+		elif command == "prune":
+			res = prune_artifacts()
+			sys.exit(res)
+
+		elif command == "json":
+			res = update_nightly_json()
+			sys.exit(res)

+ 0 - 25
ci/upload_create_nightly.sh

@@ -1,25 +0,0 @@
-#!/bin/bash
-
-set -e
-
-bucket=$1
-platform=$2
-artifact=$3
-
-now=$(date +'%Y-%m-%d')
-filename="odin-$platform-nightly+$now.zip"
-
-echo "Creating archive $filename from $artifact and uploading to $bucket"
-
-# If this is already zipped up (done before artifact upload to keep permissions in tact), just move it.
-if [ "${artifact: -4}" == ".zip" ]
-then
-	echo "Artifact already a zip"
-	mkdir -p "output"
-	mv "$artifact" "output/$filename"
-else
-	echo "Artifact needs to be zipped"
-	7z a -bd "output/$filename" -r "$artifact"
-fi
-
-b2 upload-file --noProgress "$bucket" "output/$filename" "nightly/$filename"

+ 2 - 2
core/bufio/reader.odin

@@ -29,12 +29,12 @@ MIN_READ_BUFFER_SIZE :: 16
 @(private)
 @(private)
 DEFAULT_MAX_CONSECUTIVE_EMPTY_READS :: 128
 DEFAULT_MAX_CONSECUTIVE_EMPTY_READS :: 128
 
 
-reader_init :: proc(b: ^Reader, rd: io.Reader, size: int = DEFAULT_BUF_SIZE, allocator := context.allocator) {
+reader_init :: proc(b: ^Reader, rd: io.Reader, size: int = DEFAULT_BUF_SIZE, allocator := context.allocator, loc := #caller_location) {
 	size := size
 	size := size
 	size = max(size, MIN_READ_BUFFER_SIZE)
 	size = max(size, MIN_READ_BUFFER_SIZE)
 	reader_reset(b, rd)
 	reader_reset(b, rd)
 	b.buf_allocator = allocator
 	b.buf_allocator = allocator
-	b.buf = make([]byte, size, allocator)
+	b.buf = make([]byte, size, allocator, loc)
 }
 }
 
 
 reader_init_with_buf :: proc(b: ^Reader, rd: io.Reader, buf: []byte) {
 reader_init_with_buf :: proc(b: ^Reader, rd: io.Reader, buf: []byte) {

+ 35 - 35
core/bytes/buffer.odin

@@ -27,19 +27,19 @@ Read_Op :: enum i8 {
 }
 }
 
 
 
 
-buffer_init :: proc(b: ^Buffer, buf: []byte) {
-	resize(&b.buf, len(buf))
+buffer_init :: proc(b: ^Buffer, buf: []byte, loc := #caller_location) {
+	resize(&b.buf, len(buf), loc=loc)
 	copy(b.buf[:], buf)
 	copy(b.buf[:], buf)
 }
 }
 
 
-buffer_init_string :: proc(b: ^Buffer, s: string) {
-	resize(&b.buf, len(s))
+buffer_init_string :: proc(b: ^Buffer, s: string, loc := #caller_location) {
+	resize(&b.buf, len(s), loc=loc)
 	copy(b.buf[:], s)
 	copy(b.buf[:], s)
 }
 }
 
 
-buffer_init_allocator :: proc(b: ^Buffer, len, cap: int, allocator := context.allocator) {
+buffer_init_allocator :: proc(b: ^Buffer, len, cap: int, allocator := context.allocator, loc := #caller_location) {
 	if b.buf == nil {
 	if b.buf == nil {
-		b.buf = make([dynamic]byte, len, cap, allocator)
+		b.buf = make([dynamic]byte, len, cap, allocator, loc)
 		return
 		return
 	}
 	}
 
 
@@ -96,28 +96,28 @@ buffer_truncate :: proc(b: ^Buffer, n: int) {
 }
 }
 
 
 @(private)
 @(private)
-_buffer_try_grow :: proc(b: ^Buffer, n: int) -> (int, bool) {
+_buffer_try_grow :: proc(b: ^Buffer, n: int, loc := #caller_location) -> (int, bool) {
 	if l := len(b.buf); n <= cap(b.buf)-l {
 	if l := len(b.buf); n <= cap(b.buf)-l {
-		resize(&b.buf, l+n)
+		resize(&b.buf, l+n, loc=loc)
 		return l, true
 		return l, true
 	}
 	}
 	return 0, false
 	return 0, false
 }
 }
 
 
 @(private)
 @(private)
-_buffer_grow :: proc(b: ^Buffer, n: int) -> int {
+_buffer_grow :: proc(b: ^Buffer, n: int, loc := #caller_location) -> int {
 	m := buffer_length(b)
 	m := buffer_length(b)
 	if m == 0 && b.off != 0 {
 	if m == 0 && b.off != 0 {
 		buffer_reset(b)
 		buffer_reset(b)
 	}
 	}
-	if i, ok := _buffer_try_grow(b, n); ok {
+	if i, ok := _buffer_try_grow(b, n, loc=loc); ok {
 		return i
 		return i
 	}
 	}
 
 
 	if b.buf == nil && n <= SMALL_BUFFER_SIZE {
 	if b.buf == nil && n <= SMALL_BUFFER_SIZE {
 		// Fixes #2756 by preserving allocator if already set on Buffer via init_buffer_allocator
 		// Fixes #2756 by preserving allocator if already set on Buffer via init_buffer_allocator
-		reserve(&b.buf, SMALL_BUFFER_SIZE)
-		resize(&b.buf, n)
+		reserve(&b.buf, SMALL_BUFFER_SIZE, loc=loc)
+		resize(&b.buf, n, loc=loc)
 		return 0
 		return 0
 	}
 	}
 
 
@@ -127,31 +127,31 @@ _buffer_grow :: proc(b: ^Buffer, n: int) -> int {
 	} else if c > max(int) - c - n {
 	} else if c > max(int) - c - n {
 		panic("bytes.Buffer: too large")
 		panic("bytes.Buffer: too large")
 	} else {
 	} else {
-		resize(&b.buf, 2*c + n)
+		resize(&b.buf, 2*c + n, loc=loc)
 		copy(b.buf[:], b.buf[b.off:])
 		copy(b.buf[:], b.buf[b.off:])
 	}
 	}
 	b.off = 0
 	b.off = 0
-	resize(&b.buf, m+n)
+	resize(&b.buf, m+n, loc=loc)
 	return m
 	return m
 }
 }
 
 
-buffer_grow :: proc(b: ^Buffer, n: int) {
+buffer_grow :: proc(b: ^Buffer, n: int, loc := #caller_location) {
 	if n < 0 {
 	if n < 0 {
 		panic("bytes.buffer_grow: negative count")
 		panic("bytes.buffer_grow: negative count")
 	}
 	}
-	m := _buffer_grow(b, n)
-	resize(&b.buf, m)
+	m := _buffer_grow(b, n, loc=loc)
+	resize(&b.buf, m, loc=loc)
 }
 }
 
 
-buffer_write_at :: proc(b: ^Buffer, p: []byte, offset: int) -> (n: int, err: io.Error) {
+buffer_write_at :: proc(b: ^Buffer, p: []byte, offset: int, loc := #caller_location) -> (n: int, err: io.Error) {
 	b.last_read = .Invalid
 	b.last_read = .Invalid
 	if offset < 0 {
 	if offset < 0 {
 		err = .Invalid_Offset
 		err = .Invalid_Offset
 		return
 		return
 	}
 	}
-	_, ok := _buffer_try_grow(b, offset+len(p))
+	_, ok := _buffer_try_grow(b, offset+len(p), loc=loc)
 	if !ok {
 	if !ok {
-		_ = _buffer_grow(b, offset+len(p))
+		_ = _buffer_grow(b, offset+len(p), loc=loc)
 	}
 	}
 	if len(b.buf) <= offset {
 	if len(b.buf) <= offset {
 		return 0, .Short_Write
 		return 0, .Short_Write
@@ -160,47 +160,47 @@ buffer_write_at :: proc(b: ^Buffer, p: []byte, offset: int) -> (n: int, err: io.
 }
 }
 
 
 
 
-buffer_write :: proc(b: ^Buffer, p: []byte) -> (n: int, err: io.Error) {
+buffer_write :: proc(b: ^Buffer, p: []byte, loc := #caller_location) -> (n: int, err: io.Error) {
 	b.last_read = .Invalid
 	b.last_read = .Invalid
-	m, ok := _buffer_try_grow(b, len(p))
+	m, ok := _buffer_try_grow(b, len(p), loc=loc)
 	if !ok {
 	if !ok {
-		m = _buffer_grow(b, len(p))
+		m = _buffer_grow(b, len(p), loc=loc)
 	}
 	}
 	return copy(b.buf[m:], p), nil
 	return copy(b.buf[m:], p), nil
 }
 }
 
 
-buffer_write_ptr :: proc(b: ^Buffer, ptr: rawptr, size: int) -> (n: int, err: io.Error) {
-	return buffer_write(b, ([^]byte)(ptr)[:size])
+buffer_write_ptr :: proc(b: ^Buffer, ptr: rawptr, size: int, loc := #caller_location) -> (n: int, err: io.Error) {
+	return buffer_write(b, ([^]byte)(ptr)[:size], loc=loc)
 }
 }
 
 
-buffer_write_string :: proc(b: ^Buffer, s: string) -> (n: int, err: io.Error) {
+buffer_write_string :: proc(b: ^Buffer, s: string, loc := #caller_location) -> (n: int, err: io.Error) {
 	b.last_read = .Invalid
 	b.last_read = .Invalid
-	m, ok := _buffer_try_grow(b, len(s))
+	m, ok := _buffer_try_grow(b, len(s), loc=loc)
 	if !ok {
 	if !ok {
-		m = _buffer_grow(b, len(s))
+		m = _buffer_grow(b, len(s), loc=loc)
 	}
 	}
 	return copy(b.buf[m:], s), nil
 	return copy(b.buf[m:], s), nil
 }
 }
 
 
-buffer_write_byte :: proc(b: ^Buffer, c: byte) -> io.Error {
+buffer_write_byte :: proc(b: ^Buffer, c: byte, loc := #caller_location) -> io.Error {
 	b.last_read = .Invalid
 	b.last_read = .Invalid
-	m, ok := _buffer_try_grow(b, 1)
+	m, ok := _buffer_try_grow(b, 1, loc=loc)
 	if !ok {
 	if !ok {
-		m = _buffer_grow(b, 1)
+		m = _buffer_grow(b, 1, loc=loc)
 	}
 	}
 	b.buf[m] = c
 	b.buf[m] = c
 	return nil
 	return nil
 }
 }
 
 
-buffer_write_rune :: proc(b: ^Buffer, r: rune) -> (n: int, err: io.Error) {
+buffer_write_rune :: proc(b: ^Buffer, r: rune, loc := #caller_location) -> (n: int, err: io.Error) {
 	if r < utf8.RUNE_SELF {
 	if r < utf8.RUNE_SELF {
-		buffer_write_byte(b, byte(r))
+		buffer_write_byte(b, byte(r), loc=loc)
 		return 1, nil
 		return 1, nil
 	}
 	}
 	b.last_read = .Invalid
 	b.last_read = .Invalid
-	m, ok := _buffer_try_grow(b, utf8.UTF_MAX)
+	m, ok := _buffer_try_grow(b, utf8.UTF_MAX, loc=loc)
 	if !ok {
 	if !ok {
-		m = _buffer_grow(b, utf8.UTF_MAX)
+		m = _buffer_grow(b, utf8.UTF_MAX, loc=loc)
 	}
 	}
 	res: [4]byte
 	res: [4]byte
 	res, n = utf8.encode_rune(r)
 	res, n = utf8.encode_rune(r)

+ 1 - 1
core/c/libc/signal.odin

@@ -34,7 +34,7 @@ when ODIN_OS == .Windows {
 	SIGTERM :: 15
 	SIGTERM :: 15
 }
 }
 
 
-when ODIN_OS == .Linux || ODIN_OS == .FreeBSD {
+when ODIN_OS == .Linux || ODIN_OS == .FreeBSD || ODIN_OS == .Haiku || ODIN_OS == .OpenBSD || ODIN_OS == .NetBSD {
 	SIG_ERR  :: rawptr(~uintptr(0))
 	SIG_ERR  :: rawptr(~uintptr(0))
 	SIG_DFL  :: rawptr(uintptr(0))
 	SIG_DFL  :: rawptr(uintptr(0))
 	SIG_IGN  :: rawptr(uintptr(1)) 
 	SIG_IGN  :: rawptr(uintptr(1)) 

+ 8 - 6
core/c/libc/stdio.odin

@@ -102,10 +102,12 @@ when ODIN_OS == .OpenBSD || ODIN_OS == .NetBSD {
 	SEEK_END :: 2
 	SEEK_END :: 2
 
 
 	foreign libc {
 	foreign libc {
-		stderr: ^FILE
-		stdin:  ^FILE
-		stdout: ^FILE
+		__sF: [3]FILE
 	}
 	}
+
+	stdin:  ^FILE = &__sF[0]
+	stdout: ^FILE = &__sF[1]
+	stderr: ^FILE = &__sF[2]
 }
 }
 
 
 when ODIN_OS == .FreeBSD {
 when ODIN_OS == .FreeBSD {
@@ -127,9 +129,9 @@ when ODIN_OS == .FreeBSD {
 	SEEK_END :: 2
 	SEEK_END :: 2
 
 
 	foreign libc {
 	foreign libc {
-		stderr: ^FILE
-		stdin:  ^FILE
-		stdout: ^FILE
+		@(link_name="__stderrp") stderr: ^FILE
+		@(link_name="__stdinp")  stdin:  ^FILE
+		@(link_name="__stdoutp") stdout: ^FILE
 	}
 	}
 }
 }
 
 

+ 28 - 0
core/crypto/_aes/aes.odin

@@ -0,0 +1,28 @@
+package _aes
+
+// KEY_SIZE_128 is the AES-128 key size in bytes.
+KEY_SIZE_128 :: 16
+// KEY_SIZE_192 is the AES-192 key size in bytes.
+KEY_SIZE_192 :: 24
+// KEY_SIZE_256 is the AES-256 key size in bytes.
+KEY_SIZE_256 :: 32
+
+// BLOCK_SIZE is the AES block size in bytes.
+BLOCK_SIZE :: 16
+
+// ROUNDS_128 is the number of rounds for AES-128.
+ROUNDS_128 :: 10
+// ROUNDS_192 is the number of rounds for AES-192.
+ROUNDS_192 :: 12
+// ROUNDS_256 is the number of rounds for AES-256.
+ROUNDS_256 :: 14
+
+// GHASH_KEY_SIZE is the GHASH key size in bytes.
+GHASH_KEY_SIZE :: 16
+// GHASH_BLOCK_SIZE is the GHASH block size in bytes.
+GHASH_BLOCK_SIZE :: 16
+// GHASH_TAG_SIZE is the GHASH tag size in bytes.
+GHASH_TAG_SIZE :: 16
+
+// RCON is the AES keyschedule round constants.
+RCON := [10]byte{0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80, 0x1B, 0x36}

+ 96 - 0
core/crypto/_aes/ct64/api.odin

@@ -0,0 +1,96 @@
+package aes_ct64
+
+import "base:intrinsics"
+import "core:mem"
+
+STRIDE :: 4
+
+// Context is a keyed AES (ECB) instance.
+Context :: struct {
+	_sk_exp:         [120]u64,
+	_num_rounds:     int,
+	_is_initialized: bool,
+}
+
+// init initializes a context for AES with the provided key.
+init :: proc(ctx: ^Context, key: []byte) {
+	skey: [30]u64 = ---
+
+	ctx._num_rounds = keysched(skey[:], key)
+	skey_expand(ctx._sk_exp[:], skey[:], ctx._num_rounds)
+	ctx._is_initialized = true
+}
+
+// encrypt_block sets `dst` to `AES-ECB-Encrypt(src)`.
+encrypt_block :: proc(ctx: ^Context, dst, src: []byte) {
+	assert(ctx._is_initialized)
+
+	q: [8]u64
+	load_blockx1(&q, src)
+	_encrypt(&q, ctx._sk_exp[:], ctx._num_rounds)
+	store_blockx1(dst, &q)
+}
+
+// encrypt_block sets `dst` to `AES-ECB-Decrypt(src)`.
+decrypt_block :: proc(ctx: ^Context, dst, src: []byte) {
+	assert(ctx._is_initialized)
+
+	q: [8]u64
+	load_blockx1(&q, src)
+	_decrypt(&q, ctx._sk_exp[:], ctx._num_rounds)
+	store_blockx1(dst, &q)
+}
+
+// encrypt_blocks sets `dst` to `AES-ECB-Encrypt(src[0], .. src[n])`.
+encrypt_blocks :: proc(ctx: ^Context, dst, src: [][]byte) {
+	assert(ctx._is_initialized)
+
+	q: [8]u64 = ---
+	src, dst := src, dst
+
+	n := len(src)
+	for n > 4 {
+		load_blocks(&q, src[0:4])
+		_encrypt(&q, ctx._sk_exp[:], ctx._num_rounds)
+		store_blocks(dst[0:4], &q)
+
+		src = src[4:]
+		dst = dst[4:]
+		n -= 4
+	}
+	if n > 0 {
+		load_blocks(&q, src)
+		_encrypt(&q, ctx._sk_exp[:], ctx._num_rounds)
+		store_blocks(dst, &q)
+	}
+}
+
+// decrypt_blocks sets dst to `AES-ECB-Decrypt(src[0], .. src[n])`.
+decrypt_blocks :: proc(ctx: ^Context, dst, src: [][]byte) {
+	assert(ctx._is_initialized)
+
+	q: [8]u64 = ---
+	src, dst := src, dst
+
+	n := len(src)
+	for n > 4 {
+		load_blocks(&q, src[0:4])
+		_decrypt(&q, ctx._sk_exp[:], ctx._num_rounds)
+		store_blocks(dst[0:4], &q)
+
+		src = src[4:]
+		dst = dst[4:]
+		n -= 4
+	}
+	if n > 0 {
+		load_blocks(&q, src)
+		_decrypt(&q, ctx._sk_exp[:], ctx._num_rounds)
+		store_blocks(dst, &q)
+	}
+}
+
+// reset sanitizes the Context.  The Context must be re-initialized to
+// be used again.
+reset :: proc(ctx: ^Context) {
+	mem.zero_explicit(ctx, size_of(ctx))
+}

+ 265 - 0
core/crypto/_aes/ct64/ct64.odin

@@ -0,0 +1,265 @@
+// Copyright (c) 2016 Thomas Pornin <[email protected]>
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions
+// are met:
+//
+//   1. Redistributions of source code must retain the above copyright
+//      notice, this list of conditions and the following disclaimer.
+//
+// THIS SOFTWARE IS PROVIDED BY THE AUTHORS “AS IS” AND ANY EXPRESS OR
+// IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+// ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY
+// DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE
+// GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+// WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+// THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+package aes_ct64
+
+import "base:intrinsics"
+
+// Bitsliced AES for 64-bit general purpose (integer) registers.  Each
+// invocation will process up to 4 blocks at a time.  This implementation
+// is derived from the BearSSL ct64 code, and distributed under a 1-clause
+// BSD license with permission from the original author.
+//
+// WARNING: "hic sunt dracones"
+//
+// This package also deliberately exposes enough internals to be able to
+// function as a replacement for `AESENC` and `AESDEC` from AES-NI, to
+// allow the implementation of non-AES primitives that use the AES round
+// function such as AEGIS and Deoxys-II.  This should ONLY be done when
+// implementing something other than AES itself.
+
+sub_bytes :: proc "contextless" (q: ^[8]u64) {
+	// This S-box implementation is a straightforward translation of
+	// the circuit described by Boyar and Peralta in "A new
+	// combinational logic minimization technique with applications
+	// to cryptology" (https://eprint.iacr.org/2009/191.pdf).
+	//
+	// Note that variables x* (input) and s* (output) are numbered
+	// in "reverse" order (x0 is the high bit, x7 is the low bit).
+
+	x0 := q[7]
+	x1 := q[6]
+	x2 := q[5]
+	x3 := q[4]
+	x4 := q[3]
+	x5 := q[2]
+	x6 := q[1]
+	x7 := q[0]
+
+	// Top linear transformation.
+	y14 := x3 ~ x5
+	y13 := x0 ~ x6
+	y9 := x0 ~ x3
+	y8 := x0 ~ x5
+	t0 := x1 ~ x2
+	y1 := t0 ~ x7
+	y4 := y1 ~ x3
+	y12 := y13 ~ y14
+	y2 := y1 ~ x0
+	y5 := y1 ~ x6
+	y3 := y5 ~ y8
+	t1 := x4 ~ y12
+	y15 := t1 ~ x5
+	y20 := t1 ~ x1
+	y6 := y15 ~ x7
+	y10 := y15 ~ t0
+	y11 := y20 ~ y9
+	y7 := x7 ~ y11
+	y17 := y10 ~ y11
+	y19 := y10 ~ y8
+	y16 := t0 ~ y11
+	y21 := y13 ~ y16
+	y18 := x0 ~ y16
+
+	// Non-linear section.
+	t2 := y12 & y15
+	t3 := y3 & y6
+	t4 := t3 ~ t2
+	t5 := y4 & x7
+	t6 := t5 ~ t2
+	t7 := y13 & y16
+	t8 := y5 & y1
+	t9 := t8 ~ t7
+	t10 := y2 & y7
+	t11 := t10 ~ t7
+	t12 := y9 & y11
+	t13 := y14 & y17
+	t14 := t13 ~ t12
+	t15 := y8 & y10
+	t16 := t15 ~ t12
+	t17 := t4 ~ t14
+	t18 := t6 ~ t16
+	t19 := t9 ~ t14
+	t20 := t11 ~ t16
+	t21 := t17 ~ y20
+	t22 := t18 ~ y19
+	t23 := t19 ~ y21
+	t24 := t20 ~ y18
+
+	t25 := t21 ~ t22
+	t26 := t21 & t23
+	t27 := t24 ~ t26
+	t28 := t25 & t27
+	t29 := t28 ~ t22
+	t30 := t23 ~ t24
+	t31 := t22 ~ t26
+	t32 := t31 & t30
+	t33 := t32 ~ t24
+	t34 := t23 ~ t33
+	t35 := t27 ~ t33
+	t36 := t24 & t35
+	t37 := t36 ~ t34
+	t38 := t27 ~ t36
+	t39 := t29 & t38
+	t40 := t25 ~ t39
+
+	t41 := t40 ~ t37
+	t42 := t29 ~ t33
+	t43 := t29 ~ t40
+	t44 := t33 ~ t37
+	t45 := t42 ~ t41
+	z0 := t44 & y15
+	z1 := t37 & y6
+	z2 := t33 & x7
+	z3 := t43 & y16
+	z4 := t40 & y1
+	z5 := t29 & y7
+	z6 := t42 & y11
+	z7 := t45 & y17
+	z8 := t41 & y10
+	z9 := t44 & y12
+	z10 := t37 & y3
+	z11 := t33 & y4
+	z12 := t43 & y13
+	z13 := t40 & y5
+	z14 := t29 & y2
+	z15 := t42 & y9
+	z16 := t45 & y14
+	z17 := t41 & y8
+
+	// Bottom linear transformation.
+	t46 := z15 ~ z16
+	t47 := z10 ~ z11
+	t48 := z5 ~ z13
+	t49 := z9 ~ z10
+	t50 := z2 ~ z12
+	t51 := z2 ~ z5
+	t52 := z7 ~ z8
+	t53 := z0 ~ z3
+	t54 := z6 ~ z7
+	t55 := z16 ~ z17
+	t56 := z12 ~ t48
+	t57 := t50 ~ t53
+	t58 := z4 ~ t46
+	t59 := z3 ~ t54
+	t60 := t46 ~ t57
+	t61 := z14 ~ t57
+	t62 := t52 ~ t58
+	t63 := t49 ~ t58
+	t64 := z4 ~ t59
+	t65 := t61 ~ t62
+	t66 := z1 ~ t63
+	s0 := t59 ~ t63
+	s6 := t56 ~ ~t62
+	s7 := t48 ~ ~t60
+	t67 := t64 ~ t65
+	s3 := t53 ~ t66
+	s4 := t51 ~ t66
+	s5 := t47 ~ t65
+	s1 := t64 ~ ~s3
+	s2 := t55 ~ ~t67
+
+	q[7] = s0
+	q[6] = s1
+	q[5] = s2
+	q[4] = s3
+	q[3] = s4
+	q[2] = s5
+	q[1] = s6
+	q[0] = s7
+}
+
+orthogonalize :: proc "contextless" (q: ^[8]u64) {
+	CL2 :: 0x5555555555555555
+	CH2 :: 0xAAAAAAAAAAAAAAAA
+	q[0], q[1] = (q[0] & CL2) | ((q[1] & CL2) << 1), ((q[0] & CH2) >> 1) | (q[1] & CH2)
+	q[2], q[3] = (q[2] & CL2) | ((q[3] & CL2) << 1), ((q[2] & CH2) >> 1) | (q[3] & CH2)
+	q[4], q[5] = (q[4] & CL2) | ((q[5] & CL2) << 1), ((q[4] & CH2) >> 1) | (q[5] & CH2)
+	q[6], q[7] = (q[6] & CL2) | ((q[7] & CL2) << 1), ((q[6] & CH2) >> 1) | (q[7] & CH2)
+
+	CL4 :: 0x3333333333333333
+	CH4 :: 0xCCCCCCCCCCCCCCCC
+	q[0], q[2] = (q[0] & CL4) | ((q[2] & CL4) << 2), ((q[0] & CH4) >> 2) | (q[2] & CH4)
+	q[1], q[3] = (q[1] & CL4) | ((q[3] & CL4) << 2), ((q[1] & CH4) >> 2) | (q[3] & CH4)
+	q[4], q[6] = (q[4] & CL4) | ((q[6] & CL4) << 2), ((q[4] & CH4) >> 2) | (q[6] & CH4)
+	q[5], q[7] = (q[5] & CL4) | ((q[7] & CL4) << 2), ((q[5] & CH4) >> 2) | (q[7] & CH4)
+
+	CL8 :: 0x0F0F0F0F0F0F0F0F
+	CH8 :: 0xF0F0F0F0F0F0F0F0
+	q[0], q[4] = (q[0] & CL8) | ((q[4] & CL8) << 4), ((q[0] & CH8) >> 4) | (q[4] & CH8)
+	q[1], q[5] = (q[1] & CL8) | ((q[5] & CL8) << 4), ((q[1] & CH8) >> 4) | (q[5] & CH8)
+	q[2], q[6] = (q[2] & CL8) | ((q[6] & CL8) << 4), ((q[2] & CH8) >> 4) | (q[6] & CH8)
+	q[3], q[7] = (q[3] & CL8) | ((q[7] & CL8) << 4), ((q[3] & CH8) >> 4) | (q[7] & CH8)
+}
+
+@(require_results)
+interleave_in :: proc "contextless" (w: []u32) -> (q0, q1: u64) #no_bounds_check {
+	if len(w) < 4 {
+		intrinsics.trap()
+	}
+	x0, x1, x2, x3 := u64(w[0]), u64(w[1]), u64(w[2]), u64(w[3])
+	x0 |= (x0 << 16)
+	x1 |= (x1 << 16)
+	x2 |= (x2 << 16)
+	x3 |= (x3 << 16)
+	x0 &= 0x0000FFFF0000FFFF
+	x1 &= 0x0000FFFF0000FFFF
+	x2 &= 0x0000FFFF0000FFFF
+	x3 &= 0x0000FFFF0000FFFF
+	x0 |= (x0 << 8)
+	x1 |= (x1 << 8)
+	x2 |= (x2 << 8)
+	x3 |= (x3 << 8)
+	x0 &= 0x00FF00FF00FF00FF
+	x1 &= 0x00FF00FF00FF00FF
+	x2 &= 0x00FF00FF00FF00FF
+	x3 &= 0x00FF00FF00FF00FF
+	q0 = x0 | (x2 << 8)
+	q1 = x1 | (x3 << 8)
+	return
+}
+
+@(require_results)
+interleave_out :: proc "contextless" (q0, q1: u64) -> (w0, w1, w2, w3: u32) {
+	x0 := q0 & 0x00FF00FF00FF00FF
+	x1 := q1 & 0x00FF00FF00FF00FF
+	x2 := (q0 >> 8) & 0x00FF00FF00FF00FF
+	x3 := (q1 >> 8) & 0x00FF00FF00FF00FF
+	x0 |= (x0 >> 8)
+	x1 |= (x1 >> 8)
+	x2 |= (x2 >> 8)
+	x3 |= (x3 >> 8)
+	x0 &= 0x0000FFFF0000FFFF
+	x1 &= 0x0000FFFF0000FFFF
+	x2 &= 0x0000FFFF0000FFFF
+	x3 &= 0x0000FFFF0000FFFF
+	w0 = u32(x0) | u32(x0 >> 16)
+	w1 = u32(x1) | u32(x1 >> 16)
+	w2 = u32(x2) | u32(x2 >> 16)
+	w3 = u32(x3) | u32(x3 >> 16)
+	return
+}
+
+@(private)
+rotr32 :: #force_inline proc "contextless" (x: u64) -> u64 {
+	return (x << 32) | (x >> 32)
+}

+ 135 - 0
core/crypto/_aes/ct64/ct64_dec.odin

@@ -0,0 +1,135 @@
+// Copyright (c) 2016 Thomas Pornin <[email protected]>
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions
+// are met:
+//
+//   1. Redistributions of source code must retain the above copyright
+//      notice, this list of conditions and the following disclaimer.
+//
+// THIS SOFTWARE IS PROVIDED BY THE AUTHORS “AS IS” AND ANY EXPRESS OR
+// IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+// ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY
+// DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE
+// GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+// WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+// THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+package aes_ct64
+
+import "base:intrinsics"
+
+inv_sub_bytes :: proc "contextless" (q: ^[8]u64) {
+	// AES S-box is:
+	//   S(x) = A(I(x)) ^ 0x63
+	// where I() is inversion in GF(256), and A() is a linear
+	// transform (0 is formally defined to be its own inverse).
+	// Since inversion is an involution, the inverse S-box can be
+	// computed from the S-box as:
+	//   iS(x) = B(S(B(x ^ 0x63)) ^ 0x63)
+	// where B() is the inverse of A(). Indeed, for any y in GF(256):
+	//   iS(S(y)) = B(A(I(B(A(I(y)) ^ 0x63 ^ 0x63))) ^ 0x63 ^ 0x63) = y
+	//
+	// Note: we reuse the implementation of the forward S-box,
+	// instead of duplicating it here, so that total code size is
+	// lower. By merging the B() transforms into the S-box circuit
+	// we could make faster CBC decryption, but CBC decryption is
+	// already quite faster than CBC encryption because we can
+	// process four blocks in parallel.
+
+	q0 := ~q[0]
+	q1 := ~q[1]
+	q2 := q[2]
+	q3 := q[3]
+	q4 := q[4]
+	q5 := ~q[5]
+	q6 := ~q[6]
+	q7 := q[7]
+	q[7] = q1 ~ q4 ~ q6
+	q[6] = q0 ~ q3 ~ q5
+	q[5] = q7 ~ q2 ~ q4
+	q[4] = q6 ~ q1 ~ q3
+	q[3] = q5 ~ q0 ~ q2
+	q[2] = q4 ~ q7 ~ q1
+	q[1] = q3 ~ q6 ~ q0
+	q[0] = q2 ~ q5 ~ q7
+
+	sub_bytes(q)
+
+	q0 = ~q[0]
+	q1 = ~q[1]
+	q2 = q[2]
+	q3 = q[3]
+	q4 = q[4]
+	q5 = ~q[5]
+	q6 = ~q[6]
+	q7 = q[7]
+	q[7] = q1 ~ q4 ~ q6
+	q[6] = q0 ~ q3 ~ q5
+	q[5] = q7 ~ q2 ~ q4
+	q[4] = q6 ~ q1 ~ q3
+	q[3] = q5 ~ q0 ~ q2
+	q[2] = q4 ~ q7 ~ q1
+	q[1] = q3 ~ q6 ~ q0
+	q[0] = q2 ~ q5 ~ q7
+}
+
+inv_shift_rows :: proc "contextless" (q: ^[8]u64) {
+	for x, i in q {
+		q[i] =
+			(x & 0x000000000000FFFF) |
+			((x & 0x000000000FFF0000) << 4) |
+			((x & 0x00000000F0000000) >> 12) |
+			((x & 0x000000FF00000000) << 8) |
+			((x & 0x0000FF0000000000) >> 8) |
+			((x & 0x000F000000000000) << 12) |
+			((x & 0xFFF0000000000000) >> 4)
+	}
+}
+
+inv_mix_columns :: proc "contextless" (q: ^[8]u64) {
+	q0 := q[0]
+	q1 := q[1]
+	q2 := q[2]
+	q3 := q[3]
+	q4 := q[4]
+	q5 := q[5]
+	q6 := q[6]
+	q7 := q[7]
+	r0 := (q0 >> 16) | (q0 << 48)
+	r1 := (q1 >> 16) | (q1 << 48)
+	r2 := (q2 >> 16) | (q2 << 48)
+	r3 := (q3 >> 16) | (q3 << 48)
+	r4 := (q4 >> 16) | (q4 << 48)
+	r5 := (q5 >> 16) | (q5 << 48)
+	r6 := (q6 >> 16) | (q6 << 48)
+	r7 := (q7 >> 16) | (q7 << 48)
+
+	q[0] = q5 ~ q6 ~ q7 ~ r0 ~ r5 ~ r7 ~ rotr32(q0 ~ q5 ~ q6 ~ r0 ~ r5)
+	q[1] = q0 ~ q5 ~ r0 ~ r1 ~ r5 ~ r6 ~ r7 ~ rotr32(q1 ~ q5 ~ q7 ~ r1 ~ r5 ~ r6)
+	q[2] = q0 ~ q1 ~ q6 ~ r1 ~ r2 ~ r6 ~ r7 ~ rotr32(q0 ~ q2 ~ q6 ~ r2 ~ r6 ~ r7)
+	q[3] = q0 ~ q1 ~ q2 ~ q5 ~ q6 ~ r0 ~ r2 ~ r3 ~ r5 ~ rotr32(q0 ~ q1 ~ q3 ~ q5 ~ q6 ~ q7 ~ r0 ~ r3 ~ r5 ~ r7)
+	q[4] = q1 ~ q2 ~ q3 ~ q5 ~ r1 ~ r3 ~ r4 ~ r5 ~ r6 ~ r7 ~ rotr32(q1 ~ q2 ~ q4 ~ q5 ~ q7 ~ r1 ~ r4 ~ r5 ~ r6)
+	q[5] = q2 ~ q3 ~ q4 ~ q6 ~ r2 ~ r4 ~ r5 ~ r6 ~ r7 ~ rotr32(q2 ~ q3 ~ q5 ~ q6 ~ r2 ~ r5 ~ r6 ~ r7)
+	q[6] = q3 ~ q4 ~ q5 ~ q7 ~ r3 ~ r5 ~ r6 ~ r7 ~ rotr32(q3 ~ q4 ~ q6 ~ q7 ~ r3 ~ r6 ~ r7)
+	q[7] = q4 ~ q5 ~ q6 ~ r4 ~ r6 ~ r7 ~ rotr32(q4 ~ q5 ~ q7 ~ r4 ~ r7)
+}
+
+@(private)
+_decrypt :: proc "contextless" (q: ^[8]u64, skey: []u64, num_rounds: int) {
+	add_round_key(q, skey[num_rounds << 3:])
+	for u := num_rounds - 1; u > 0; u -= 1 {
+		inv_shift_rows(q)
+		inv_sub_bytes(q)
+		add_round_key(q, skey[u << 3:])
+		inv_mix_columns(q)
+	}
+	inv_shift_rows(q)
+	inv_sub_bytes(q)
+	add_round_key(q, skey)
+}

+ 95 - 0
core/crypto/_aes/ct64/ct64_enc.odin

@@ -0,0 +1,95 @@
+// Copyright (c) 2016 Thomas Pornin <[email protected]>
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions
+// are met:
+//
+//   1. Redistributions of source code must retain the above copyright
+//      notice, this list of conditions and the following disclaimer.
+//
+// THIS SOFTWARE IS PROVIDED BY THE AUTHORS “AS IS” AND ANY EXPRESS OR
+// IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+// ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY
+// DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE
+// GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+// WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+// THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+package aes_ct64
+
+import "base:intrinsics"
+
+add_round_key :: proc "contextless" (q: ^[8]u64, sk: []u64) #no_bounds_check {
+	if len(sk) < 8 {
+		intrinsics.trap()
+	}
+
+	q[0] ~= sk[0]
+	q[1] ~= sk[1]
+	q[2] ~= sk[2]
+	q[3] ~= sk[3]
+	q[4] ~= sk[4]
+	q[5] ~= sk[5]
+	q[6] ~= sk[6]
+	q[7] ~= sk[7]
+}
+
+shift_rows :: proc "contextless" (q: ^[8]u64) {
+	for x, i in q {
+		q[i] =
+			(x & 0x000000000000FFFF) |
+			((x & 0x00000000FFF00000) >> 4) |
+			((x & 0x00000000000F0000) << 12) |
+			((x & 0x0000FF0000000000) >> 8) |
+			((x & 0x000000FF00000000) << 8) |
+			((x & 0xF000000000000000) >> 12) |
+			((x & 0x0FFF000000000000) << 4)
+	}
+}
+
+mix_columns :: proc "contextless" (q: ^[8]u64) {
+	q0 := q[0]
+	q1 := q[1]
+	q2 := q[2]
+	q3 := q[3]
+	q4 := q[4]
+	q5 := q[5]
+	q6 := q[6]
+	q7 := q[7]
+	r0 := (q0 >> 16) | (q0 << 48)
+	r1 := (q1 >> 16) | (q1 << 48)
+	r2 := (q2 >> 16) | (q2 << 48)
+	r3 := (q3 >> 16) | (q3 << 48)
+	r4 := (q4 >> 16) | (q4 << 48)
+	r5 := (q5 >> 16) | (q5 << 48)
+	r6 := (q6 >> 16) | (q6 << 48)
+	r7 := (q7 >> 16) | (q7 << 48)
+
+	q[0] = q7 ~ r7 ~ r0 ~ rotr32(q0 ~ r0)
+	q[1] = q0 ~ r0 ~ q7 ~ r7 ~ r1 ~ rotr32(q1 ~ r1)
+	q[2] = q1 ~ r1 ~ r2 ~ rotr32(q2 ~ r2)
+	q[3] = q2 ~ r2 ~ q7 ~ r7 ~ r3 ~ rotr32(q3 ~ r3)
+	q[4] = q3 ~ r3 ~ q7 ~ r7 ~ r4 ~ rotr32(q4 ~ r4)
+	q[5] = q4 ~ r4 ~ r5 ~ rotr32(q5 ~ r5)
+	q[6] = q5 ~ r5 ~ r6 ~ rotr32(q6 ~ r6)
+	q[7] = q6 ~ r6 ~ r7 ~ rotr32(q7 ~ r7)
+}
+
+@(private)
+_encrypt :: proc "contextless" (q: ^[8]u64, skey: []u64, num_rounds: int) {
+	add_round_key(q, skey)
+	for u in 1 ..< num_rounds {
+		sub_bytes(q)
+		shift_rows(q)
+		mix_columns(q)
+		add_round_key(q, skey[u << 3:])
+	}
+	sub_bytes(q)
+	shift_rows(q)
+	add_round_key(q, skey[num_rounds << 3:])
+}

+ 179 - 0
core/crypto/_aes/ct64/ct64_keysched.odin

@@ -0,0 +1,179 @@
+// Copyright (c) 2016 Thomas Pornin <[email protected]>
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions
+// are met:
+//
+//   1. Redistributions of source code must retain the above copyright
+//      notice, this list of conditions and the following disclaimer.
+//
+// THIS SOFTWARE IS PROVIDED BY THE AUTHORS “AS IS” AND ANY EXPRESS OR
+// IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+// ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY
+// DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE
+// GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+// WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+// THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+package aes_ct64
+
+import "base:intrinsics"
+import "core:crypto/_aes"
+import "core:encoding/endian"
+import "core:mem"
+
+@(private, require_results)
+sub_word :: proc "contextless" (x: u32) -> u32 {
+	q := [8]u64{u64(x), 0, 0, 0, 0, 0, 0, 0}
+
+	orthogonalize(&q)
+	sub_bytes(&q)
+	orthogonalize(&q)
+	ret := u32(q[0])
+
+	mem.zero_explicit(&q[0], size_of(u64))
+
+	return ret
+}
+
+@(private, require_results)
+keysched :: proc(comp_skey: []u64, key: []byte) -> int {
+	num_rounds, key_len := 0, len(key)
+	switch key_len {
+	case _aes.KEY_SIZE_128:
+		num_rounds = _aes.ROUNDS_128
+	case _aes.KEY_SIZE_192:
+		num_rounds = _aes.ROUNDS_192
+	case _aes.KEY_SIZE_256:
+		num_rounds = _aes.ROUNDS_256
+	case:
+		panic("crypto/aes: invalid AES key size")
+	}
+
+	skey: [60]u32 = ---
+	nk, nkf := key_len >> 2, (num_rounds + 1) << 2
+	for i in 0 ..< nk {
+		skey[i] = endian.unchecked_get_u32le(key[i << 2:])
+	}
+	tmp := skey[(key_len >> 2) - 1]
+	for i, j, k := nk, 0, 0; i < nkf; i += 1 {
+		if j == 0 {
+			tmp = (tmp << 24) | (tmp >> 8)
+			tmp = sub_word(tmp) ~ u32(_aes.RCON[k])
+		} else if nk > 6 && j == 4 {
+			tmp = sub_word(tmp)
+		}
+		tmp ~= skey[i - nk]
+		skey[i] = tmp
+		if j += 1; j == nk {
+			j = 0
+			k += 1
+		}
+	}
+
+	q: [8]u64 = ---
+	for i, j := 0, 0; i < nkf; i, j = i + 4, j + 2 {
+		q[0], q[4] = interleave_in(skey[i:])
+		q[1] = q[0]
+		q[2] = q[0]
+		q[3] = q[0]
+		q[5] = q[4]
+		q[6] = q[4]
+		q[7] = q[4]
+		orthogonalize(&q)
+		comp_skey[j + 0] =
+			(q[0] & 0x1111111111111111) |
+			(q[1] & 0x2222222222222222) |
+			(q[2] & 0x4444444444444444) |
+			(q[3] & 0x8888888888888888)
+		comp_skey[j + 1] =
+			(q[4] & 0x1111111111111111) |
+			(q[5] & 0x2222222222222222) |
+			(q[6] & 0x4444444444444444) |
+			(q[7] & 0x8888888888888888)
+	}
+
+	mem.zero_explicit(&skey, size_of(skey))
+	mem.zero_explicit(&q, size_of(q))
+
+	return num_rounds
+}
+
+@(private)
+skey_expand :: proc "contextless" (skey, comp_skey: []u64, num_rounds: int) {
+	n := (num_rounds + 1) << 1
+	for u, v := 0, 0; u < n; u, v = u + 1, v + 4 {
+		x0 := comp_skey[u]
+		x1, x2, x3 := x0, x0, x0
+		x0 &= 0x1111111111111111
+		x1 &= 0x2222222222222222
+		x2 &= 0x4444444444444444
+		x3 &= 0x8888888888888888
+		x1 >>= 1
+		x2 >>= 2
+		x3 >>= 3
+		skey[v + 0] = (x0 << 4) - x0
+		skey[v + 1] = (x1 << 4) - x1
+		skey[v + 2] = (x2 << 4) - x2
+		skey[v + 3] = (x3 << 4) - x3
+	}
+}
+
+orthogonalize_roundkey :: proc "contextless" (qq: []u64, key: []byte) {
+	if len(qq) < 8 || len(key) != 16 {
+		intrinsics.trap()
+	}
+
+	skey: [4]u32 = ---
+	skey[0] = endian.unchecked_get_u32le(key[0:])
+	skey[1] = endian.unchecked_get_u32le(key[4:])
+	skey[2] = endian.unchecked_get_u32le(key[8:])
+	skey[3] = endian.unchecked_get_u32le(key[12:])
+
+	q: [8]u64 = ---
+	q[0], q[4] = interleave_in(skey[:])
+	q[1] = q[0]
+	q[2] = q[0]
+	q[3] = q[0]
+	q[5] = q[4]
+	q[6] = q[4]
+	q[7] = q[4]
+	orthogonalize(&q)
+
+	comp_skey: [2]u64 = ---
+	comp_skey[0] =
+		(q[0] & 0x1111111111111111) |
+		(q[1] & 0x2222222222222222) |
+		(q[2] & 0x4444444444444444) |
+		(q[3] & 0x8888888888888888)
+	comp_skey[1] =
+		(q[4] & 0x1111111111111111) |
+		(q[5] & 0x2222222222222222) |
+		(q[6] & 0x4444444444444444) |
+		(q[7] & 0x8888888888888888)
+
+	for x, u in comp_skey {
+		x0 := x
+		x1, x2, x3 := x0, x0, x0
+		x0 &= 0x1111111111111111
+		x1 &= 0x2222222222222222
+		x2 &= 0x4444444444444444
+		x3 &= 0x8888888888888888
+		x1 >>= 1
+		x2 >>= 2
+		x3 >>= 3
+		qq[u * 4 + 0] = (x0 << 4) - x0
+		qq[u * 4 + 1] = (x1 << 4) - x1
+		qq[u * 4 + 2] = (x2 << 4) - x2
+		qq[u * 4 + 3] = (x3 << 4) - x3
+	}
+
+	mem.zero_explicit(&skey, size_of(skey))
+	mem.zero_explicit(&q, size_of(q))
+	mem.zero_explicit(&comp_skey, size_of(comp_skey))
+}

+ 136 - 0
core/crypto/_aes/ct64/ghash.odin

@@ -0,0 +1,136 @@
+// Copyright (c) 2016 Thomas Pornin <[email protected]>
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions
+// are met:
+//
+//   1. Redistributions of source code must retain the above copyright
+//      notice, this list of conditions and the following disclaimer.
+//
+// THIS SOFTWARE IS PROVIDED BY THE AUTHORS “AS IS” AND ANY EXPRESS OR
+// IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+// ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY
+// DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE
+// GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+// WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+// THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+package aes_ct64
+
+import "base:intrinsics"
+import "core:crypto/_aes"
+import "core:encoding/endian"
+
+@(private = "file")
+bmul64 :: proc "contextless" (x, y: u64) -> u64 {
+	x0 := x & 0x1111111111111111
+	x1 := x & 0x2222222222222222
+	x2 := x & 0x4444444444444444
+	x3 := x & 0x8888888888888888
+	y0 := y & 0x1111111111111111
+	y1 := y & 0x2222222222222222
+	y2 := y & 0x4444444444444444
+	y3 := y & 0x8888888888888888
+	z0 := (x0 * y0) ~ (x1 * y3) ~ (x2 * y2) ~ (x3 * y1)
+	z1 := (x0 * y1) ~ (x1 * y0) ~ (x2 * y3) ~ (x3 * y2)
+	z2 := (x0 * y2) ~ (x1 * y1) ~ (x2 * y0) ~ (x3 * y3)
+	z3 := (x0 * y3) ~ (x1 * y2) ~ (x2 * y1) ~ (x3 * y0)
+	z0 &= 0x1111111111111111
+	z1 &= 0x2222222222222222
+	z2 &= 0x4444444444444444
+	z3 &= 0x8888888888888888
+	return z0 | z1 | z2 | z3
+}
+
+@(private = "file")
+rev64 :: proc "contextless" (x: u64) -> u64 {
+	x := x
+	x = ((x & 0x5555555555555555) << 1) | ((x >> 1) & 0x5555555555555555)
+	x = ((x & 0x3333333333333333) << 2) | ((x >> 2) & 0x3333333333333333)
+	x = ((x & 0x0F0F0F0F0F0F0F0F) << 4) | ((x >> 4) & 0x0F0F0F0F0F0F0F0F)
+	x = ((x & 0x00FF00FF00FF00FF) << 8) | ((x >> 8) & 0x00FF00FF00FF00FF)
+	x = ((x & 0x0000FFFF0000FFFF) << 16) | ((x >> 16) & 0x0000FFFF0000FFFF)
+	return (x << 32) | (x >> 32)
+}
+
+// ghash calculates the GHASH of data, with the key `key`, and input `dst`
+// and `data`, and stores the resulting digest in `dst`.
+//
+// Note: `dst` is both an input and an output, to support easy implementation
+// of GCM.
+ghash :: proc "contextless" (dst, key, data: []byte) {
+	if len(dst) != _aes.GHASH_BLOCK_SIZE || len(key) != _aes.GHASH_BLOCK_SIZE {
+		intrinsics.trap()
+	}
+
+	buf := data
+	l := len(buf)
+
+	y1 := endian.unchecked_get_u64be(dst[0:])
+	y0 := endian.unchecked_get_u64be(dst[8:])
+	h1 := endian.unchecked_get_u64be(key[0:])
+	h0 := endian.unchecked_get_u64be(key[8:])
+	h0r := rev64(h0)
+	h1r := rev64(h1)
+	h2 := h0 ~ h1
+	h2r := h0r ~ h1r
+
+	src: []byte
+	for l > 0 {
+		if l >= _aes.GHASH_BLOCK_SIZE {
+			src = buf
+			buf = buf[_aes.GHASH_BLOCK_SIZE:]
+			l -= _aes.GHASH_BLOCK_SIZE
+		} else {
+			tmp: [_aes.GHASH_BLOCK_SIZE]byte
+			copy(tmp[:], buf)
+			src = tmp[:]
+			l = 0
+		}
+		y1 ~= endian.unchecked_get_u64be(src)
+		y0 ~= endian.unchecked_get_u64be(src[8:])
+
+		y0r := rev64(y0)
+		y1r := rev64(y1)
+		y2 := y0 ~ y1
+		y2r := y0r ~ y1r
+
+		z0 := bmul64(y0, h0)
+		z1 := bmul64(y1, h1)
+		z2 := bmul64(y2, h2)
+		z0h := bmul64(y0r, h0r)
+		z1h := bmul64(y1r, h1r)
+		z2h := bmul64(y2r, h2r)
+		z2 ~= z0 ~ z1
+		z2h ~= z0h ~ z1h
+		z0h = rev64(z0h) >> 1
+		z1h = rev64(z1h) >> 1
+		z2h = rev64(z2h) >> 1
+
+		v0 := z0
+		v1 := z0h ~ z2
+		v2 := z1 ~ z2h
+		v3 := z1h
+
+		v3 = (v3 << 1) | (v2 >> 63)
+		v2 = (v2 << 1) | (v1 >> 63)
+		v1 = (v1 << 1) | (v0 >> 63)
+		v0 = (v0 << 1)
+
+		v2 ~= v0 ~ (v0 >> 1) ~ (v0 >> 2) ~ (v0 >> 7)
+		v1 ~= (v0 << 63) ~ (v0 << 62) ~ (v0 << 57)
+		v3 ~= v1 ~ (v1 >> 1) ~ (v1 >> 2) ~ (v1 >> 7)
+		v2 ~= (v1 << 63) ~ (v1 << 62) ~ (v1 << 57)
+
+		y0 = v2
+		y1 = v3
+	}
+
+	endian.unchecked_put_u64be(dst[0:], y1)
+	endian.unchecked_put_u64be(dst[8:], y0)
+}

+ 75 - 0
core/crypto/_aes/ct64/helpers.odin

@@ -0,0 +1,75 @@
+package aes_ct64
+
+import "base:intrinsics"
+import "core:crypto/_aes"
+import "core:encoding/endian"
+
+load_blockx1 :: proc "contextless" (q: ^[8]u64, src: []byte) {
+	if len(src) != _aes.BLOCK_SIZE {
+		intrinsics.trap()
+	}
+
+	w: [4]u32 = ---
+	w[0] = endian.unchecked_get_u32le(src[0:])
+	w[1] = endian.unchecked_get_u32le(src[4:])
+	w[2] = endian.unchecked_get_u32le(src[8:])
+	w[3] = endian.unchecked_get_u32le(src[12:])
+	q[0], q[4] = interleave_in(w[:])
+	orthogonalize(q)
+}
+
+store_blockx1 :: proc "contextless" (dst: []byte, q: ^[8]u64) {
+	if len(dst) != _aes.BLOCK_SIZE {
+		intrinsics.trap()
+	}
+
+	orthogonalize(q)
+	w0, w1, w2, w3 := interleave_out(q[0], q[4])
+	endian.unchecked_put_u32le(dst[0:], w0)
+	endian.unchecked_put_u32le(dst[4:], w1)
+	endian.unchecked_put_u32le(dst[8:], w2)
+	endian.unchecked_put_u32le(dst[12:], w3)
+}
+
+load_blocks :: proc "contextless" (q: ^[8]u64, src: [][]byte) {
+	if n := len(src); n > STRIDE || n == 0 {
+		intrinsics.trap()
+	}
+
+	w: [4]u32 = ---
+	for s, i in src {
+		if len(s) != _aes.BLOCK_SIZE {
+			intrinsics.trap()
+		}
+
+		w[0] = endian.unchecked_get_u32le(s[0:])
+		w[1] = endian.unchecked_get_u32le(s[4:])
+		w[2] = endian.unchecked_get_u32le(s[8:])
+		w[3] = endian.unchecked_get_u32le(s[12:])
+		q[i], q[i + 4] = interleave_in(w[:])
+	}
+	orthogonalize(q)
+}
+
+store_blocks :: proc "contextless" (dst: [][]byte, q: ^[8]u64) {
+	if n := len(dst); n > STRIDE || n == 0 {
+		intrinsics.trap()
+	}
+
+	orthogonalize(q)
+	for d, i in dst {
+		// Allow storing [0,4] blocks.
+		if d == nil {
+			break
+		}
+		if len(d) != _aes.BLOCK_SIZE {
+			intrinsics.trap()
+		}
+
+		w0, w1, w2, w3 := interleave_out(q[i], q[i + 4])
+		endian.unchecked_put_u32le(d[0:], w0)
+		endian.unchecked_put_u32le(d[4:], w1)
+		endian.unchecked_put_u32le(d[8:], w2)
+		endian.unchecked_put_u32le(d[12:], w3)
+	}
+}

+ 22 - 0
core/crypto/aes/aes.odin

@@ -0,0 +1,22 @@
+/*
+package aes implements the AES block cipher and some common modes.
+
+See:
+- https://nvlpubs.nist.gov/nistpubs/FIPS/NIST.FIPS.197-upd1.pdf
+- https://nvlpubs.nist.gov/nistpubs/Legacy/SP/nistspecialpublication800-38a.pdf
+- https://nvlpubs.nist.gov/nistpubs/Legacy/SP/nistspecialpublication800-38d.pdf
+*/
+
+package aes
+
+import "core:crypto/_aes"
+
+// KEY_SIZE_128 is the AES-128 key size in bytes.
+KEY_SIZE_128 :: _aes.KEY_SIZE_128
+// KEY_SIZE_192 is the AES-192 key size in bytes.
+KEY_SIZE_192 :: _aes.KEY_SIZE_192
+// KEY_SIZE_256 is the AES-256 key size in bytes.
+KEY_SIZE_256 :: _aes.KEY_SIZE_256
+
+// BLOCK_SIZE is the AES block size in bytes.
+BLOCK_SIZE :: _aes.BLOCK_SIZE

+ 199 - 0
core/crypto/aes/aes_ctr.odin

@@ -0,0 +1,199 @@
+package aes
+
+import "core:crypto/_aes/ct64"
+import "core:encoding/endian"
+import "core:math/bits"
+import "core:mem"
+
+// CTR_IV_SIZE is the size of the CTR mode IV in bytes.
+CTR_IV_SIZE :: 16
+
+// Context_CTR is a keyed AES-CTR instance.
+Context_CTR :: struct {
+	_impl:           Context_Impl,
+	_buffer:         [BLOCK_SIZE]byte,
+	_off:            int,
+	_ctr_hi:         u64,
+	_ctr_lo:         u64,
+	_is_initialized: bool,
+}
+
+// init_ctr initializes a Context_CTR with the provided key and IV.
+init_ctr :: proc(ctx: ^Context_CTR, key, iv: []byte, impl := Implementation.Hardware) {
+	if len(iv) != CTR_IV_SIZE {
+		panic("crypto/aes: invalid CTR IV size")
+	}
+
+	init_impl(&ctx._impl, key, impl)
+	ctx._off = BLOCK_SIZE
+	ctx._ctr_hi = endian.unchecked_get_u64be(iv[0:])
+	ctx._ctr_lo = endian.unchecked_get_u64be(iv[8:])
+	ctx._is_initialized = true
+}
+
+// xor_bytes_ctr XORs each byte in src with bytes taken from the AES-CTR
+// keystream, and writes the resulting output to dst.  dst and src MUST
+// alias exactly or not at all.
+xor_bytes_ctr :: proc(ctx: ^Context_CTR, dst, src: []byte) {
+	assert(ctx._is_initialized)
+
+	// TODO: Enforcing that dst and src alias exactly or not at all
+	// is a good idea, though odd aliasing should be extremely uncommon.
+
+	src, dst := src, dst
+	if dst_len := len(dst); dst_len < len(src) {
+		src = src[:dst_len]
+	}
+
+	for remaining := len(src); remaining > 0; {
+		// Process multiple blocks at once
+		if ctx._off == BLOCK_SIZE {
+			if nr_blocks := remaining / BLOCK_SIZE; nr_blocks > 0 {
+				direct_bytes := nr_blocks * BLOCK_SIZE
+				ctr_blocks(ctx, dst, src, nr_blocks)
+				remaining -= direct_bytes
+				if remaining == 0 {
+					return
+				}
+				dst = dst[direct_bytes:]
+				src = src[direct_bytes:]
+			}
+
+			// If there is a partial block, generate and buffer 1 block
+			// worth of keystream.
+			ctr_blocks(ctx, ctx._buffer[:], nil, 1)
+			ctx._off = 0
+		}
+
+		// Process partial blocks from the buffered keystream.
+		to_xor := min(BLOCK_SIZE - ctx._off, remaining)
+		buffered_keystream := ctx._buffer[ctx._off:]
+		for i := 0; i < to_xor; i = i + 1 {
+			dst[i] = buffered_keystream[i] ~ src[i]
+		}
+		ctx._off += to_xor
+		dst = dst[to_xor:]
+		src = src[to_xor:]
+		remaining -= to_xor
+	}
+}
+
+// keystream_bytes_ctr fills dst with the raw AES-CTR keystream output.
+keystream_bytes_ctr :: proc(ctx: ^Context_CTR, dst: []byte) {
+	assert(ctx._is_initialized)
+
+	dst := dst
+	for remaining := len(dst); remaining > 0; {
+		// Process multiple blocks at once
+		if ctx._off == BLOCK_SIZE {
+			if nr_blocks := remaining / BLOCK_SIZE; nr_blocks > 0 {
+				direct_bytes := nr_blocks * BLOCK_SIZE
+				ctr_blocks(ctx, dst, nil, nr_blocks)
+				remaining -= direct_bytes
+				if remaining == 0 {
+					return
+				}
+				dst = dst[direct_bytes:]
+			}
+
+			// If there is a partial block, generate and buffer 1 block
+			// worth of keystream.
+			ctr_blocks(ctx, ctx._buffer[:], nil, 1)
+			ctx._off = 0
+		}
+
+		// Process partial blocks from the buffered keystream.
+		to_copy := min(BLOCK_SIZE - ctx._off, remaining)
+		buffered_keystream := ctx._buffer[ctx._off:]
+		copy(dst[:to_copy], buffered_keystream[:to_copy])
+		ctx._off += to_copy
+		dst = dst[to_copy:]
+		remaining -= to_copy
+	}
+}
+
+// reset_ctr sanitizes the Context_CTR.  The Context_CTR must be
+// re-initialized to be used again.
+reset_ctr :: proc "contextless" (ctx: ^Context_CTR) {
+	reset_impl(&ctx._impl)
+	ctx._off = 0
+	ctx._ctr_hi = 0
+	ctx._ctr_lo = 0
+	mem.zero_explicit(&ctx._buffer, size_of(ctx._buffer))
+	ctx._is_initialized = false
+}
+
+@(private)
+ctr_blocks :: proc(ctx: ^Context_CTR, dst, src: []byte, nr_blocks: int) {
+	// Use the optimized hardware implementation if available.
+	if _, is_hw := ctx._impl.(Context_Impl_Hardware); is_hw {
+		ctr_blocks_hw(ctx, dst, src, nr_blocks)
+		return
+	}
+
+	// Portable implementation.
+	ct64_inc_ctr := #force_inline proc "contextless" (dst: []byte, hi, lo: u64) -> (u64, u64) {
+		endian.unchecked_put_u64be(dst[0:], hi)
+		endian.unchecked_put_u64be(dst[8:], lo)
+
+		hi, lo := hi, lo
+		carry: u64
+		lo, carry = bits.add_u64(lo, 1, 0)
+		hi, _ = bits.add_u64(hi, 0, carry)
+		return hi, lo
+	}
+
+	impl := &ctx._impl.(ct64.Context)
+	src, dst := src, dst
+	nr_blocks := nr_blocks
+	ctr_hi, ctr_lo := ctx._ctr_hi, ctx._ctr_lo
+
+	tmp: [ct64.STRIDE][BLOCK_SIZE]byte = ---
+	ctrs: [ct64.STRIDE][]byte = ---
+	for i in 0 ..< ct64.STRIDE {
+		ctrs[i] = tmp[i][:]
+	}
+	for nr_blocks > 0 {
+		n := min(ct64.STRIDE, nr_blocks)
+		blocks := ctrs[:n]
+
+		for i in 0 ..< n {
+			ctr_hi, ctr_lo = ct64_inc_ctr(blocks[i], ctr_hi, ctr_lo)
+		}
+		ct64.encrypt_blocks(impl, blocks, blocks)
+
+		xor_blocks(dst, src, blocks)
+
+		if src != nil {
+			src = src[n * BLOCK_SIZE:]
+		}
+		dst = dst[n * BLOCK_SIZE:]
+		nr_blocks -= n
+	}
+
+	// Write back the counter.
+	ctx._ctr_hi, ctx._ctr_lo = ctr_hi, ctr_lo
+
+	mem.zero_explicit(&tmp, size_of(tmp))
+}
+
+@(private)
+xor_blocks :: #force_inline proc "contextless" (dst, src: []byte, blocks: [][]byte) {
+	// Note: This would be faster `core:simd` was used, however if
+	// performance of this implementation matters to where that
+	// optimization would be worth it, use chacha20poly1305, or a
+	// CPU that isn't e-waste.
+	if src != nil {
+		#no_bounds_check {
+			for i in 0 ..< len(blocks) {
+				off := i * BLOCK_SIZE
+				for j in 0 ..< BLOCK_SIZE {
+					blocks[i][j] ~= src[off + j]
+				}
+			}
+		}
+	}
+	for i in 0 ..< len(blocks) {
+		copy(dst[i * BLOCK_SIZE:], blocks[i])
+	}
+}

+ 57 - 0
core/crypto/aes/aes_ecb.odin

@@ -0,0 +1,57 @@
+package aes
+
+import "core:crypto/_aes/ct64"
+
+// Context_ECB is a keyed AES-ECB instance.
+//
+// WARNING: Using ECB mode is strongly discouraged unless it is being
+// used to implement higher level constructs.
+Context_ECB :: struct {
+	_impl:           Context_Impl,
+	_is_initialized: bool,
+}
+
+// init_ecb initializes a Context_ECB with the provided key.
+init_ecb :: proc(ctx: ^Context_ECB, key: []byte, impl := Implementation.Hardware) {
+	init_impl(&ctx._impl, key, impl)
+	ctx._is_initialized = true
+}
+
+// encrypt_ecb encrypts the BLOCK_SIZE buffer src, and writes the result to dst.
+encrypt_ecb :: proc(ctx: ^Context_ECB, dst, src: []byte) {
+	assert(ctx._is_initialized)
+
+	if len(dst) != BLOCK_SIZE || len(src) != BLOCK_SIZE {
+		panic("crypto/aes: invalid buffer size(s)")
+	}
+
+	switch &impl in ctx._impl {
+	case ct64.Context:
+		ct64.encrypt_block(&impl, dst, src)
+	case Context_Impl_Hardware:
+		encrypt_block_hw(&impl, dst, src)
+	}
+}
+
+// decrypt_ecb decrypts the BLOCK_SIZE buffer src, and writes the result to dst.
+decrypt_ecb :: proc(ctx: ^Context_ECB, dst, src: []byte) {
+	assert(ctx._is_initialized)
+
+	if len(dst) != BLOCK_SIZE || len(src) != BLOCK_SIZE {
+		panic("crypto/aes: invalid buffer size(s)")
+	}
+
+	switch &impl in ctx._impl {
+	case ct64.Context:
+		ct64.decrypt_block(&impl, dst, src)
+	case Context_Impl_Hardware:
+		decrypt_block_hw(&impl, dst, src)
+	}
+}
+
+// reset_ecb sanitizes the Context_ECB.  The Context_ECB must be
+// re-initialized to be used again.
+reset_ecb :: proc "contextless" (ctx: ^Context_ECB) {
+	reset_impl(&ctx._impl)
+	ctx._is_initialized = false
+}

+ 253 - 0
core/crypto/aes/aes_gcm.odin

@@ -0,0 +1,253 @@
+package aes
+
+import "core:crypto"
+import "core:crypto/_aes"
+import "core:crypto/_aes/ct64"
+import "core:encoding/endian"
+import "core:mem"
+
+// GCM_NONCE_SIZE is the size of the GCM nonce in bytes.
+GCM_NONCE_SIZE :: 12
+// GCM_TAG_SIZE is the size of a GCM tag in bytes.
+GCM_TAG_SIZE :: _aes.GHASH_TAG_SIZE
+
+@(private)
+GCM_A_MAX :: max(u64) / 8 // 2^64 - 1 bits -> bytes
+@(private)
+GCM_P_MAX :: 0xfffffffe0 // 2^39 - 256 bits -> bytes
+
+// Context_GCM is a keyed AES-GCM instance.
+Context_GCM :: struct {
+	_impl:           Context_Impl,
+	_is_initialized: bool,
+}
+
+// init_gcm initializes a Context_GCM with the provided key.
+init_gcm :: proc(ctx: ^Context_GCM, key: []byte, impl := Implementation.Hardware) {
+	init_impl(&ctx._impl, key, impl)
+	ctx._is_initialized = true
+}
+
+// seal_gcm encrypts the plaintext and authenticates the aad and ciphertext,
+// with the provided Context_GCM and nonce, stores the output in dst and tag.
+//
+// dst and plaintext MUST alias exactly or not at all.
+seal_gcm :: proc(ctx: ^Context_GCM, dst, tag, nonce, aad, plaintext: []byte) {
+	assert(ctx._is_initialized)
+
+	gcm_validate_common_slice_sizes(tag, nonce, aad, plaintext)
+	if len(dst) != len(plaintext) {
+		panic("crypto/aes: invalid destination ciphertext size")
+	}
+
+	if impl, is_hw := ctx._impl.(Context_Impl_Hardware); is_hw {
+		gcm_seal_hw(&impl, dst, tag, nonce, aad, plaintext)
+		return
+	}
+
+	h: [_aes.GHASH_KEY_SIZE]byte
+	j0: [_aes.GHASH_BLOCK_SIZE]byte
+	s: [_aes.GHASH_TAG_SIZE]byte
+	init_ghash_ct64(ctx, &h, &j0, nonce)
+
+	// Note: Our GHASH implementation handles appending padding.
+	ct64.ghash(s[:], h[:], aad)
+	gctr_ct64(ctx, dst, &s, plaintext, &h, nonce, true)
+	final_ghash_ct64(&s, &h, &j0, len(aad), len(plaintext))
+	copy(tag, s[:])
+
+	mem.zero_explicit(&h, len(h))
+	mem.zero_explicit(&j0, len(j0))
+}
+
+// open_gcm authenticates the aad and ciphertext, and decrypts the ciphertext,
+// with the provided Context_GCM, nonce, and tag, and stores the output in dst,
+// returning true iff the authentication was successful.  If authentication
+// fails, the destination buffer will be zeroed.
+//
+// dst and plaintext MUST alias exactly or not at all.
+open_gcm :: proc(ctx: ^Context_GCM, dst, nonce, aad, ciphertext, tag: []byte) -> bool {
+	assert(ctx._is_initialized)
+
+	gcm_validate_common_slice_sizes(tag, nonce, aad, ciphertext)
+	if len(dst) != len(ciphertext) {
+		panic("crypto/aes: invalid destination plaintext size")
+	}
+
+	if impl, is_hw := ctx._impl.(Context_Impl_Hardware); is_hw {
+		return gcm_open_hw(&impl, dst, nonce, aad, ciphertext, tag)
+	}
+
+	h: [_aes.GHASH_KEY_SIZE]byte
+	j0: [_aes.GHASH_BLOCK_SIZE]byte
+	s: [_aes.GHASH_TAG_SIZE]byte
+	init_ghash_ct64(ctx, &h, &j0, nonce)
+
+	ct64.ghash(s[:], h[:], aad)
+	gctr_ct64(ctx, dst, &s, ciphertext, &h, nonce, false)
+	final_ghash_ct64(&s, &h, &j0, len(aad), len(ciphertext))
+
+	ok := crypto.compare_constant_time(s[:], tag) == 1
+	if !ok {
+		mem.zero_explicit(raw_data(dst), len(dst))
+	}
+
+	mem.zero_explicit(&h, len(h))
+	mem.zero_explicit(&j0, len(j0))
+	mem.zero_explicit(&s, len(s))
+
+	return ok
+}
+
+// reset_ctr sanitizes the Context_GCM.  The Context_GCM must be
+// re-initialized to be used again.
+reset_gcm :: proc "contextless" (ctx: ^Context_GCM) {
+	reset_impl(&ctx._impl)
+	ctx._is_initialized = false
+}
+
+@(private)
+gcm_validate_common_slice_sizes :: proc(tag, nonce, aad, text: []byte) {
+	if len(tag) != GCM_TAG_SIZE {
+		panic("crypto/aes: invalid GCM tag size")
+	}
+
+	// The specification supports nonces in the range [1, 2^64) bits
+	// however per NIST SP 800-38D 5.2.1.1:
+	//
+	// > For IVs, it is recommended that implementations restrict support
+	// > to the length of 96 bits, to promote interoperability, efficiency,
+	// > and simplicity of design.
+	if len(nonce) != GCM_NONCE_SIZE {
+		panic("crypto/aes: invalid GCM nonce size")
+	}
+
+	if aad_len := u64(len(aad)); aad_len > GCM_A_MAX {
+		panic("crypto/aes: oversized GCM aad")
+	}
+	if text_len := u64(len(text)); text_len > GCM_P_MAX {
+		panic("crypto/aes: oversized GCM src data")
+	}
+}
+
+@(private = "file")
+init_ghash_ct64 :: proc(
+	ctx: ^Context_GCM,
+	h: ^[_aes.GHASH_KEY_SIZE]byte,
+	j0: ^[_aes.GHASH_BLOCK_SIZE]byte,
+	nonce: []byte,
+) {
+	impl := &ctx._impl.(ct64.Context)
+
+	// 1. Let H = CIPH(k, 0^128)
+	ct64.encrypt_block(impl, h[:], h[:])
+
+	// ECB encrypt j0, so that we can just XOR with the tag.  In theory
+	// this could be processed along with the final GCTR block, to
+	// potentially save a call to AES-ECB, but... just use AES-NI.
+	copy(j0[:], nonce)
+	j0[_aes.GHASH_BLOCK_SIZE - 1] = 1
+	ct64.encrypt_block(impl, j0[:], j0[:])
+}
+
+@(private = "file")
+final_ghash_ct64 :: proc(
+	s: ^[_aes.GHASH_BLOCK_SIZE]byte,
+	h: ^[_aes.GHASH_KEY_SIZE]byte,
+	j0: ^[_aes.GHASH_BLOCK_SIZE]byte,
+	a_len: int,
+	t_len: int,
+) {
+	blk: [_aes.GHASH_BLOCK_SIZE]byte
+	endian.unchecked_put_u64be(blk[0:], u64(a_len) * 8)
+	endian.unchecked_put_u64be(blk[8:], u64(t_len) * 8)
+
+	ct64.ghash(s[:], h[:], blk[:])
+	for i in 0 ..< len(s) {
+		s[i] ~= j0[i]
+	}
+}
+
+@(private = "file")
+gctr_ct64 :: proc(
+	ctx: ^Context_GCM,
+	dst: []byte,
+	s: ^[_aes.GHASH_BLOCK_SIZE]byte,
+	src: []byte,
+	h: ^[_aes.GHASH_KEY_SIZE]byte,
+	nonce: []byte,
+	is_seal: bool,
+) {
+	ct64_inc_ctr32 := #force_inline proc "contextless" (dst: []byte, ctr: u32) -> u32 {
+		endian.unchecked_put_u32be(dst[12:], ctr)
+		return ctr + 1
+	}
+
+	// 2. Define a block J_0 as follows:
+	//    if len(IV) = 96, then let J0 = IV || 0^31 || 1
+	//
+	// Note: We only support 96 bit IVs.
+	tmp, tmp2: [ct64.STRIDE][BLOCK_SIZE]byte = ---, ---
+	ctrs, blks: [ct64.STRIDE][]byte = ---, ---
+	ctr: u32 = 2
+	for i in 0 ..< ct64.STRIDE {
+		// Setup scratch space for the keystream.
+		blks[i] = tmp2[i][:]
+
+		// Pre-copy the IV to all the counter blocks.
+		ctrs[i] = tmp[i][:]
+		copy(ctrs[i], nonce)
+	}
+
+	// We stitch the GCTR and GHASH operations together, so that only
+	// one pass over the ciphertext is required.
+
+	impl := &ctx._impl.(ct64.Context)
+	src, dst := src, dst
+
+	nr_blocks := len(src) / BLOCK_SIZE
+	for nr_blocks > 0 {
+		n := min(ct64.STRIDE, nr_blocks)
+		l := n * BLOCK_SIZE
+
+		if !is_seal {
+			ct64.ghash(s[:], h[:], src[:l])
+		}
+
+		// The keystream is written to a separate buffer, as we will
+		// reuse the first 96-bits of each counter.
+		for i in 0 ..< n {
+			ctr = ct64_inc_ctr32(ctrs[i], ctr)
+		}
+		ct64.encrypt_blocks(impl, blks[:n], ctrs[:n])
+
+		xor_blocks(dst, src, blks[:n])
+
+		if is_seal {
+			ct64.ghash(s[:], h[:], dst[:l])
+		}
+
+		src = src[l:]
+		dst = dst[l:]
+		nr_blocks -= n
+	}
+	if l := len(src); l > 0 {
+		if !is_seal {
+			ct64.ghash(s[:], h[:], src[:l])
+		}
+
+		ct64_inc_ctr32(ctrs[0], ctr)
+		ct64.encrypt_block(impl, ctrs[0], ctrs[0])
+
+		for i in 0 ..< l {
+			dst[i] = src[i] ~ ctrs[0][i]
+		}
+
+		if is_seal {
+			ct64.ghash(s[:], h[:], dst[:l])
+		}
+	}
+
+	mem.zero_explicit(&tmp, size_of(tmp))
+	mem.zero_explicit(&tmp2, size_of(tmp2))
+}

+ 41 - 0
core/crypto/aes/aes_impl.odin

@@ -0,0 +1,41 @@
+package aes
+
+import "core:crypto/_aes/ct64"
+import "core:mem"
+import "core:reflect"
+
+@(private)
+Context_Impl :: union {
+	ct64.Context,
+	Context_Impl_Hardware,
+}
+
+// Implementation is an AES implementation.  Most callers will not need
+// to use this as the package will automatically select the most performant
+// implementation available (See `is_hardware_accelerated()`).
+Implementation :: enum {
+	Portable,
+	Hardware,
+}
+
+@(private)
+init_impl :: proc(ctx: ^Context_Impl, key: []byte, impl: Implementation) {
+	impl := impl
+	if !is_hardware_accelerated() {
+		impl = .Portable
+	}
+
+	switch impl {
+	case .Portable:
+		reflect.set_union_variant_typeid(ctx^, typeid_of(ct64.Context))
+		ct64.init(&ctx.(ct64.Context), key)
+	case .Hardware:
+		reflect.set_union_variant_typeid(ctx^, typeid_of(Context_Impl_Hardware))
+		init_impl_hw(&ctx.(Context_Impl_Hardware), key)
+	}
+}
+
+@(private)
+reset_impl :: proc "contextless" (ctx: ^Context_Impl) {
+	mem.zero_explicit(ctx, size_of(Context_Impl))
+}

+ 43 - 0
core/crypto/aes/aes_impl_hw_gen.odin

@@ -0,0 +1,43 @@
+package aes
+
+@(private = "file")
+ERR_HW_NOT_SUPPORTED :: "crypto/aes: hardware implementation unsupported"
+
+// is_hardware_accelerated returns true iff hardware accelerated AES
+// is supported.
+is_hardware_accelerated :: proc "contextless" () -> bool {
+	return false
+}
+
+@(private)
+Context_Impl_Hardware :: struct {}
+
+@(private)
+init_impl_hw :: proc(ctx: ^Context_Impl_Hardware, key: []byte) {
+	panic(ERR_HW_NOT_SUPPORTED)
+}
+
+@(private)
+encrypt_block_hw :: proc(ctx: ^Context_Impl_Hardware, dst, src: []byte) {
+	panic(ERR_HW_NOT_SUPPORTED)
+}
+
+@(private)
+decrypt_block_hw :: proc(ctx: ^Context_Impl_Hardware, dst, src: []byte) {
+	panic(ERR_HW_NOT_SUPPORTED)
+}
+
+@(private)
+ctr_blocks_hw :: proc(ctx: ^Context_CTR, dst, src: []byte, nr_blocks: int) {
+	panic(ERR_HW_NOT_SUPPORTED)
+}
+
+@(private)
+gcm_seal_hw :: proc(ctx: ^Context_Impl_Hardware, dst, tag, nonce, aad, plaintext: []byte) {
+	panic(ERR_HW_NOT_SUPPORTED)
+}
+
+@(private)
+gcm_open_hw :: proc(ctx: ^Context_Impl_Hardware, dst, nonce, aad, ciphertext, tag: []byte) -> bool {
+	panic(ERR_HW_NOT_SUPPORTED)
+}

+ 137 - 0
core/encoding/ansi/ansi.odin

@@ -0,0 +1,137 @@
+package ansi
+
+BEL     :: "\a" // Bell
+BS      :: "\b" // Backspace
+ESC     :: "\e" // Escape
+
+// Fe Escape sequences
+
+CSI     :: ESC + "["  // Control Sequence Introducer
+OSC     :: ESC + "]"  // Operating System Command
+ST      :: ESC + "\\" // String Terminator
+
+// CSI sequences
+
+CUU     :: "A"  // Cursor Up
+CUD     :: "B"  // Cursor Down
+CUF     :: "C"  // Cursor Forward
+CUB     :: "D"  // Cursor Back
+CNL     :: "E"  // Cursor Next Line
+CPL     :: "F"  // Cursor Previous Line
+CHA     :: "G"  // Cursor Horizontal Absolute
+CUP     :: "H"  // Cursor Position
+ED      :: "J"  // Erase in Display
+EL      :: "K"  // Erase in Line
+SU      :: "S"  // Scroll Up
+SD      :: "T"  // Scroll Down
+HVP     :: "f"  // Horizontal Vertical Position
+SGR     :: "m"  // Select Graphic Rendition
+AUX_ON  :: "5i" // AUX Port On
+AUX_OFF :: "4i" // AUX Port Off
+DSR     :: "6n" // Device Status Report
+
+// CSI: private sequences
+
+SCP          :: "s"    // Save Current Cursor Position
+RCP          :: "u"    // Restore Saved Cursor Position
+DECAWM_ON    :: "?7h"  // Auto Wrap Mode (Enabled)
+DECAWM_OFF   :: "?7l"  // Auto Wrap Mode (Disabled)
+DECTCEM_SHOW :: "?25h" // Text Cursor Enable Mode (Visible)
+DECTCEM_HIDE :: "?25l" // Text Cursor Enable Mode (Invisible)
+
+// SGR sequences
+
+RESET                   :: "0"
+BOLD                    :: "1"
+FAINT                   :: "2"
+ITALIC                  :: "3" // Not widely supported.
+UNDERLINE               :: "4"
+BLINK_SLOW              :: "5"
+BLINK_RAPID             :: "6" // Not widely supported.
+INVERT                  :: "7" // Also known as reverse video.
+HIDE                    :: "8" // Not widely supported.
+STRIKE                  :: "9"
+FONT_PRIMARY            :: "10"
+FONT_ALT1               :: "11"
+FONT_ALT2               :: "12"
+FONT_ALT3               :: "13"
+FONT_ALT4               :: "14"
+FONT_ALT5               :: "15"
+FONT_ALT6               :: "16"
+FONT_ALT7               :: "17"
+FONT_ALT8               :: "18"
+FONT_ALT9               :: "19"
+FONT_FRAKTUR            :: "20" // Rarely supported.
+UNDERLINE_DOUBLE        :: "21" // May be interpreted as "disable bold."
+NO_BOLD_FAINT           :: "22"
+NO_ITALIC_BLACKLETTER   :: "23"
+NO_UNDERLINE            :: "24"
+NO_BLINK                :: "25"
+PROPORTIONAL_SPACING    :: "26"
+NO_REVERSE              :: "27"
+NO_HIDE                 :: "28"
+NO_STRIKE               :: "29"
+
+FG_BLACK                :: "30"
+FG_RED                  :: "31"
+FG_GREEN                :: "32"
+FG_YELLOW               :: "33"
+FG_BLUE                 :: "34"
+FG_MAGENTA              :: "35"
+FG_CYAN                 :: "36"
+FG_WHITE                :: "37"
+FG_COLOR                :: "38"
+FG_COLOR_8_BIT          :: "38;5" // Followed by ";n" where n is in 0..=255
+FG_COLOR_24_BIT         :: "38;2" // Followed by ";r;g;b" where r,g,b are in 0..=255
+FG_DEFAULT              :: "39"
+
+BG_BLACK                :: "40"
+BG_RED                  :: "41"
+BG_GREEN                :: "42"
+BG_YELLOW               :: "43"
+BG_BLUE                 :: "44"
+BG_MAGENTA              :: "45"
+BG_CYAN                 :: "46"
+BG_WHITE                :: "47"
+BG_COLOR                :: "48"
+BG_COLOR_8_BIT          :: "48;5" // Followed by ";n" where n is in 0..=255
+BG_COLOR_24_BIT         :: "48;2" // Followed by ";r;g;b" where r,g,b are in 0..=255
+BG_DEFAULT              :: "49"
+
+NO_PROPORTIONAL_SPACING :: "50"
+FRAMED                  :: "51"
+ENCIRCLED               :: "52"
+OVERLINED               :: "53"
+NO_FRAME_ENCIRCLE       :: "54"
+NO_OVERLINE             :: "55"
+
+// SGR: non-standard bright colors
+
+FG_BRIGHT_BLACK         :: "90" // Also known as grey.
+FG_BRIGHT_RED           :: "91"
+FG_BRIGHT_GREEN         :: "92"
+FG_BRIGHT_YELLOW        :: "93"
+FG_BRIGHT_BLUE          :: "94"
+FG_BRIGHT_MAGENTA       :: "95"
+FG_BRIGHT_CYAN          :: "96"
+FG_BRIGHT_WHITE         :: "97"
+
+BG_BRIGHT_BLACK         :: "100" // Also known as grey.
+BG_BRIGHT_RED           :: "101"
+BG_BRIGHT_GREEN         :: "102"
+BG_BRIGHT_YELLOW        :: "103"
+BG_BRIGHT_BLUE          :: "104"
+BG_BRIGHT_MAGENTA       :: "105"
+BG_BRIGHT_CYAN          :: "106"
+BG_BRIGHT_WHITE         :: "107"
+
+// Fp Escape sequences
+
+DECSC :: ESC + "7" // DEC Save Cursor
+DECRC :: ESC + "8" // DEC Restore Cursor
+
+// OSC sequences
+
+WINDOW_TITLE :: "2"  // Followed by ";<text>" ST.
+HYPERLINK    :: "8"  // Followed by ";[params];<URI>" ST. Closed by OSC HYPERLINK ";;" ST.
+CLIPBOARD    :: "52" // Followed by ";c;<Base64-encoded string>" ST.

+ 20 - 0
core/encoding/ansi/doc.odin

@@ -0,0 +1,20 @@
+/*
+package ansi implements constant references to many widely-supported ANSI
+escape codes, primarily used in terminal emulators for enhanced graphics, such
+as colors, text styling, and animated displays.
+
+For example, you can print out a line of cyan text like this:
+	fmt.println(ansi.CSI + ansi.FG_CYAN + ansi.SGR + "Hellope!" + ansi.CSI + ansi.RESET + ansi.SGR)
+
+Multiple SGR (Select Graphic Rendition) codes can be joined by semicolons:
+	fmt.println(ansi.CSI + ansi.BOLD + ";" + ansi.FG_BLUE + ansi.SGR + "Hellope!" + ansi.CSI + ansi.RESET + ansi.SGR)
+
+If your terminal supports 24-bit true color mode, you can also do this:
+	fmt.println(ansi.CSI + ansi.FG_COLOR_24_BIT + ";0;255;255" + ansi.SGR + "Hellope!" + ansi.CSI + ansi.RESET + ansi.SGR)
+
+For more information, see:
+	1. https://en.wikipedia.org/wiki/ANSI_escape_code
+	2. https://www.vt100.net/docs/vt102-ug/chapter5.html
+	3. https://invisible-island.net/xterm/ctlseqs/ctlseqs.html
+*/
+package ansi

+ 2 - 2
core/encoding/cbor/cbor.odin

@@ -320,8 +320,8 @@ to_diagnostic_format :: proc {
 
 
 // Turns the given CBOR value into a human-readable string.
 // Turns the given CBOR value into a human-readable string.
 // See docs on the proc group `diagnose` for more info.
 // See docs on the proc group `diagnose` for more info.
-to_diagnostic_format_string :: proc(val: Value, padding := 0, allocator := context.allocator) -> (string, mem.Allocator_Error) #optional_allocator_error {
-	b := strings.builder_make(allocator)
+to_diagnostic_format_string :: proc(val: Value, padding := 0, allocator := context.allocator, loc := #caller_location) -> (string, mem.Allocator_Error) #optional_allocator_error {
+	b := strings.builder_make(allocator, loc)
 	w := strings.to_stream(&b)
 	w := strings.to_stream(&b)
 	err := to_diagnostic_format_writer(w, val, padding)
 	err := to_diagnostic_format_writer(w, val, padding)
 	if err == .EOF {
 	if err == .EOF {

+ 54 - 53
core/encoding/cbor/coding.odin

@@ -95,24 +95,25 @@ decode :: decode_from
 
 
 // Decodes the given string as CBOR.
 // Decodes the given string as CBOR.
 // See docs on the proc group `decode` for more information.
 // See docs on the proc group `decode` for more information.
-decode_from_string :: proc(s: string, flags: Decoder_Flags = {}, allocator := context.allocator) -> (v: Value, err: Decode_Error) {
+decode_from_string :: proc(s: string, flags: Decoder_Flags = {}, allocator := context.allocator, loc := #caller_location) -> (v: Value, err: Decode_Error) {
 	r: strings.Reader
 	r: strings.Reader
 	strings.reader_init(&r, s)
 	strings.reader_init(&r, s)
-	return decode_from_reader(strings.reader_to_stream(&r), flags, allocator)
+	return decode_from_reader(strings.reader_to_stream(&r), flags, allocator, loc)
 }
 }
 
 
 // Reads a CBOR value from the given reader.
 // Reads a CBOR value from the given reader.
 // See docs on the proc group `decode` for more information.
 // See docs on the proc group `decode` for more information.
-decode_from_reader :: proc(r: io.Reader, flags: Decoder_Flags = {}, allocator := context.allocator) -> (v: Value, err: Decode_Error) {
+decode_from_reader :: proc(r: io.Reader, flags: Decoder_Flags = {}, allocator := context.allocator, loc := #caller_location) -> (v: Value, err: Decode_Error) {
 	return decode_from_decoder(
 	return decode_from_decoder(
 		Decoder{ DEFAULT_MAX_PRE_ALLOC, flags, r },
 		Decoder{ DEFAULT_MAX_PRE_ALLOC, flags, r },
 		allocator=allocator,
 		allocator=allocator,
+		loc = loc,
 	)
 	)
 }
 }
 
 
 // Reads a CBOR value from the given decoder.
 // Reads a CBOR value from the given decoder.
 // See docs on the proc group `decode` for more information.
 // See docs on the proc group `decode` for more information.
-decode_from_decoder :: proc(d: Decoder, allocator := context.allocator) -> (v: Value, err: Decode_Error) {
+decode_from_decoder :: proc(d: Decoder, allocator := context.allocator, loc := #caller_location) -> (v: Value, err: Decode_Error) {
 	context.allocator = allocator
 	context.allocator = allocator
 	
 	
 	d := d
 	d := d
@@ -121,13 +122,13 @@ decode_from_decoder :: proc(d: Decoder, allocator := context.allocator) -> (v: V
 		d.max_pre_alloc = DEFAULT_MAX_PRE_ALLOC
 		d.max_pre_alloc = DEFAULT_MAX_PRE_ALLOC
 	}
 	}
 
 
-	v, err = _decode_from_decoder(d)
+	v, err = _decode_from_decoder(d, {}, allocator, loc)
 	// Normal EOF does not exist here, we try to read the exact amount that is said to be provided.
 	// Normal EOF does not exist here, we try to read the exact amount that is said to be provided.
 	if err == .EOF { err = .Unexpected_EOF }
 	if err == .EOF { err = .Unexpected_EOF }
 	return
 	return
 }
 }
 
 
-_decode_from_decoder :: proc(d: Decoder, hdr: Header = Header(0)) -> (v: Value, err: Decode_Error) {
+_decode_from_decoder :: proc(d: Decoder, hdr: Header = Header(0), allocator := context.allocator, loc := #caller_location) -> (v: Value, err: Decode_Error) {
 	hdr := hdr
 	hdr := hdr
 	r := d.reader
 	r := d.reader
 	if hdr == Header(0) { hdr = _decode_header(r) or_return }
 	if hdr == Header(0) { hdr = _decode_header(r) or_return }
@@ -161,11 +162,11 @@ _decode_from_decoder :: proc(d: Decoder, hdr: Header = Header(0)) -> (v: Value,
 	switch maj {
 	switch maj {
 	case .Unsigned: return _decode_tiny_u8(add)
 	case .Unsigned: return _decode_tiny_u8(add)
 	case .Negative: return Negative_U8(_decode_tiny_u8(add) or_return), nil
 	case .Negative: return Negative_U8(_decode_tiny_u8(add) or_return), nil
-	case .Bytes:    return _decode_bytes_ptr(d, add)
-	case .Text:     return _decode_text_ptr(d, add)
-	case .Array:    return _decode_array_ptr(d, add)
-	case .Map:      return _decode_map_ptr(d, add)
-	case .Tag:      return _decode_tag_ptr(d, add)
+	case .Bytes:    return _decode_bytes_ptr(d, add, .Bytes, allocator, loc)
+	case .Text:     return _decode_text_ptr(d, add, allocator, loc)
+	case .Array:    return _decode_array_ptr(d, add, allocator, loc)
+	case .Map:      return _decode_map_ptr(d, add, allocator, loc)
+	case .Tag:      return _decode_tag_ptr(d, add, allocator, loc)
 	case .Other:    return _decode_tiny_simple(add)
 	case .Other:    return _decode_tiny_simple(add)
 	case:           return nil, .Bad_Major
 	case:           return nil, .Bad_Major
 	}
 	}
@@ -203,27 +204,27 @@ encode :: encode_into
 
 
 // Encodes the CBOR value into binary CBOR allocated on the given allocator.
 // Encodes the CBOR value into binary CBOR allocated on the given allocator.
 // See the docs on the proc group `encode_into` for more info.
 // See the docs on the proc group `encode_into` for more info.
-encode_into_bytes :: proc(v: Value, flags := ENCODE_SMALL, allocator := context.allocator, temp_allocator := context.temp_allocator) -> (data: []byte, err: Encode_Error) {
-	b := strings.builder_make(allocator) or_return
+encode_into_bytes :: proc(v: Value, flags := ENCODE_SMALL, allocator := context.allocator, temp_allocator := context.temp_allocator, loc := #caller_location) -> (data: []byte, err: Encode_Error) {
+	b := strings.builder_make(allocator, loc) or_return
 	encode_into_builder(&b, v, flags, temp_allocator) or_return
 	encode_into_builder(&b, v, flags, temp_allocator) or_return
 	return b.buf[:], nil
 	return b.buf[:], nil
 }
 }
 
 
 // Encodes the CBOR value into binary CBOR written to the given builder.
 // Encodes the CBOR value into binary CBOR written to the given builder.
 // See the docs on the proc group `encode_into` for more info.
 // See the docs on the proc group `encode_into` for more info.
-encode_into_builder :: proc(b: ^strings.Builder, v: Value, flags := ENCODE_SMALL, temp_allocator := context.temp_allocator) -> Encode_Error {
-	return encode_into_writer(strings.to_stream(b), v, flags, temp_allocator)
+encode_into_builder :: proc(b: ^strings.Builder, v: Value, flags := ENCODE_SMALL, temp_allocator := context.temp_allocator, loc := #caller_location) -> Encode_Error {
+	return encode_into_writer(strings.to_stream(b), v, flags, temp_allocator, loc=loc)
 }
 }
 
 
 // Encodes the CBOR value into binary CBOR written to the given writer.
 // Encodes the CBOR value into binary CBOR written to the given writer.
 // See the docs on the proc group `encode_into` for more info.
 // See the docs on the proc group `encode_into` for more info.
-encode_into_writer :: proc(w: io.Writer, v: Value, flags := ENCODE_SMALL, temp_allocator := context.temp_allocator) -> Encode_Error {
-	return encode_into_encoder(Encoder{flags, w, temp_allocator}, v)
+encode_into_writer :: proc(w: io.Writer, v: Value, flags := ENCODE_SMALL, temp_allocator := context.temp_allocator, loc := #caller_location) -> Encode_Error {
+	return encode_into_encoder(Encoder{flags, w, temp_allocator}, v, loc=loc)
 }
 }
 
 
 // Encodes the CBOR value into binary CBOR written to the given encoder.
 // Encodes the CBOR value into binary CBOR written to the given encoder.
 // See the docs on the proc group `encode_into` for more info.
 // See the docs on the proc group `encode_into` for more info.
-encode_into_encoder :: proc(e: Encoder, v: Value) -> Encode_Error {
+encode_into_encoder :: proc(e: Encoder, v: Value, loc := #caller_location) -> Encode_Error {
 	e := e
 	e := e
 
 
 	if e.temp_allocator.procedure == nil {
 	if e.temp_allocator.procedure == nil {
@@ -366,21 +367,21 @@ _encode_u64_exact :: proc(w: io.Writer, v: u64, major: Major = .Unsigned) -> (er
 	return
 	return
 }
 }
 
 
-_decode_bytes_ptr :: proc(d: Decoder, add: Add, type: Major = .Bytes) -> (v: ^Bytes, err: Decode_Error) {
-	v = new(Bytes) or_return
-	defer if err != nil { free(v) }
+_decode_bytes_ptr :: proc(d: Decoder, add: Add, type: Major = .Bytes, allocator := context.allocator, loc := #caller_location) -> (v: ^Bytes, err: Decode_Error) {
+	v = new(Bytes, allocator, loc) or_return
+	defer if err != nil { free(v, allocator, loc) }
 
 
-	v^ = _decode_bytes(d, add, type) or_return
+	v^ = _decode_bytes(d, add, type, allocator, loc) or_return
 	return
 	return
 }
 }
 
 
-_decode_bytes :: proc(d: Decoder, add: Add, type: Major = .Bytes, allocator := context.allocator) -> (v: Bytes, err: Decode_Error) {
+_decode_bytes :: proc(d: Decoder, add: Add, type: Major = .Bytes, allocator := context.allocator, loc := #caller_location) -> (v: Bytes, err: Decode_Error) {
 	context.allocator = allocator
 	context.allocator = allocator
 
 
 	add := add
 	add := add
 	n, scap := _decode_len_str(d, add) or_return
 	n, scap := _decode_len_str(d, add) or_return
 	
 	
-	buf := strings.builder_make(0, scap) or_return
+	buf := strings.builder_make(0, scap, allocator, loc) or_return
 	defer if err != nil { strings.builder_destroy(&buf) }
 	defer if err != nil { strings.builder_destroy(&buf) }
 	buf_stream := strings.to_stream(&buf)
 	buf_stream := strings.to_stream(&buf)
 
 
@@ -426,40 +427,40 @@ _encode_bytes :: proc(e: Encoder, val: Bytes, major: Major = .Bytes) -> (err: En
 	return
 	return
 }
 }
 
 
-_decode_text_ptr :: proc(d: Decoder, add: Add) -> (v: ^Text, err: Decode_Error) {
-	v = new(Text) or_return
+_decode_text_ptr :: proc(d: Decoder, add: Add, allocator := context.allocator, loc := #caller_location) -> (v: ^Text, err: Decode_Error) {
+	v = new(Text, allocator, loc) or_return
 	defer if err != nil { free(v) }
 	defer if err != nil { free(v) }
 
 
-	v^ = _decode_text(d, add) or_return
+	v^ = _decode_text(d, add, allocator, loc) or_return
 	return
 	return
 }
 }
 
 
-_decode_text :: proc(d: Decoder, add: Add, allocator := context.allocator) -> (v: Text, err: Decode_Error) {
-	return (Text)(_decode_bytes(d, add, .Text, allocator) or_return), nil
+_decode_text :: proc(d: Decoder, add: Add, allocator := context.allocator, loc := #caller_location) -> (v: Text, err: Decode_Error) {
+	return (Text)(_decode_bytes(d, add, .Text, allocator, loc) or_return), nil
 }
 }
 
 
 _encode_text :: proc(e: Encoder, val: Text) -> Encode_Error {
 _encode_text :: proc(e: Encoder, val: Text) -> Encode_Error {
     return _encode_bytes(e, transmute([]byte)val, .Text)
     return _encode_bytes(e, transmute([]byte)val, .Text)
 }
 }
 
 
-_decode_array_ptr :: proc(d: Decoder, add: Add) -> (v: ^Array, err: Decode_Error) {
-	v = new(Array) or_return
+_decode_array_ptr :: proc(d: Decoder, add: Add, allocator := context.allocator, loc := #caller_location) -> (v: ^Array, err: Decode_Error) {
+	v = new(Array, allocator, loc) or_return
 	defer if err != nil { free(v) }
 	defer if err != nil { free(v) }
 
 
-	v^ = _decode_array(d, add) or_return
+	v^ = _decode_array(d, add, allocator, loc) or_return
 	return
 	return
 }
 }
 
 
-_decode_array :: proc(d: Decoder, add: Add) -> (v: Array, err: Decode_Error) {
+_decode_array :: proc(d: Decoder, add: Add, allocator := context.allocator, loc := #caller_location) -> (v: Array, err: Decode_Error) {
 	n, scap := _decode_len_container(d, add) or_return
 	n, scap := _decode_len_container(d, add) or_return
-	array := make([dynamic]Value, 0, scap) or_return
+	array := make([dynamic]Value, 0, scap, allocator, loc) or_return
 	defer if err != nil {
 	defer if err != nil {
-		for entry in array { destroy(entry) }
-		delete(array)
+		for entry in array { destroy(entry, allocator) }
+		delete(array, loc)
 	}
 	}
 	
 	
 	for i := 0; n == -1 || i < n; i += 1 {
 	for i := 0; n == -1 || i < n; i += 1 {
-		val, verr := _decode_from_decoder(d)
+		val, verr := _decode_from_decoder(d, {}, allocator, loc)
 		if n == -1 && verr == .Break {
 		if n == -1 && verr == .Break {
 			break
 			break
 		} else if verr != nil {
 		} else if verr != nil {
@@ -485,39 +486,39 @@ _encode_array :: proc(e: Encoder, arr: Array) -> Encode_Error {
     return nil
     return nil
 }
 }
 
 
-_decode_map_ptr :: proc(d: Decoder, add: Add) -> (v: ^Map, err: Decode_Error) {
-	v = new(Map) or_return
+_decode_map_ptr :: proc(d: Decoder, add: Add, allocator := context.allocator, loc := #caller_location) -> (v: ^Map, err: Decode_Error) {
+	v = new(Map, allocator, loc) or_return
 	defer if err != nil { free(v) }
 	defer if err != nil { free(v) }
 
 
-	v^ = _decode_map(d, add) or_return
+	v^ = _decode_map(d, add, allocator, loc) or_return
 	return
 	return
 }
 }
 
 
-_decode_map :: proc(d: Decoder, add: Add) -> (v: Map, err: Decode_Error) {
+_decode_map :: proc(d: Decoder, add: Add, allocator := context.allocator, loc := #caller_location) -> (v: Map, err: Decode_Error) {
 	n, scap := _decode_len_container(d, add) or_return
 	n, scap := _decode_len_container(d, add) or_return
-	items := make([dynamic]Map_Entry, 0, scap) or_return
+	items := make([dynamic]Map_Entry, 0, scap, allocator, loc) or_return
 	defer if err != nil { 
 	defer if err != nil { 
 		for entry in items {
 		for entry in items {
 			destroy(entry.key)
 			destroy(entry.key)
 			destroy(entry.value)
 			destroy(entry.value)
 		}
 		}
-		delete(items)
+		delete(items, loc)
 	}
 	}
 
 
 	for i := 0; n == -1 || i < n; i += 1 {
 	for i := 0; n == -1 || i < n; i += 1 {
-		key, kerr := _decode_from_decoder(d)
+		key, kerr := _decode_from_decoder(d, {}, allocator, loc)
 		if n == -1 && kerr == .Break {
 		if n == -1 && kerr == .Break {
 			break
 			break
 		} else if kerr != nil {
 		} else if kerr != nil {
 			return nil, kerr
 			return nil, kerr
 		} 
 		} 
 
 
-		value := _decode_from_decoder(d) or_return
+		value := _decode_from_decoder(d, {}, allocator, loc) or_return
 
 
 		append(&items, Map_Entry{
 		append(&items, Map_Entry{
 			key   = key,
 			key   = key,
 			value = value,
 			value = value,
-		}) or_return
+		}, loc) or_return
 	}
 	}
 
 
 	if .Shrink_Excess in d.flags { shrink(&items) }
 	if .Shrink_Excess in d.flags { shrink(&items) }
@@ -578,20 +579,20 @@ _encode_map :: proc(e: Encoder, m: Map) -> (err: Encode_Error) {
     return nil
     return nil
 }
 }
 
 
-_decode_tag_ptr :: proc(d: Decoder, add: Add) -> (v: Value, err: Decode_Error) {
-	tag := _decode_tag(d, add) or_return
+_decode_tag_ptr :: proc(d: Decoder, add: Add, allocator := context.allocator, loc := #caller_location) -> (v: Value, err: Decode_Error) {
+	tag := _decode_tag(d, add, allocator, loc) or_return
 	if t, ok := tag.?; ok {
 	if t, ok := tag.?; ok {
 		defer if err != nil { destroy(t.value) }
 		defer if err != nil { destroy(t.value) }
-		tp := new(Tag) or_return
+		tp := new(Tag, allocator, loc) or_return
 		tp^ = t
 		tp^ = t
 		return tp, nil
 		return tp, nil
 	}
 	}
 
 
 	// no error, no tag, this was the self described CBOR tag, skip it.
 	// no error, no tag, this was the self described CBOR tag, skip it.
-	return _decode_from_decoder(d)
+	return _decode_from_decoder(d, {}, allocator, loc)
 }
 }
 
 
-_decode_tag :: proc(d: Decoder, add: Add) -> (v: Maybe(Tag), err: Decode_Error) {
+_decode_tag :: proc(d: Decoder, add: Add, allocator := context.allocator, loc := #caller_location) -> (v: Maybe(Tag), err: Decode_Error) {
 	num := _decode_uint_as_u64(d.reader, add) or_return
 	num := _decode_uint_as_u64(d.reader, add) or_return
 
 
 	// CBOR can be wrapped in a tag that decoders can use to see/check if the binary data is CBOR.
 	// CBOR can be wrapped in a tag that decoders can use to see/check if the binary data is CBOR.
@@ -602,7 +603,7 @@ _decode_tag :: proc(d: Decoder, add: Add) -> (v: Maybe(Tag), err: Decode_Error)
 
 
 	t := Tag{
 	t := Tag{
 		number = num,
 		number = num,
-		value = _decode_from_decoder(d) or_return,
+		value = _decode_from_decoder(d, {}, allocator, loc) or_return,
 	}
 	}
 
 
 	if nested, ok := t.value.(^Tag); ok {
 	if nested, ok := t.value.(^Tag); ok {
@@ -883,4 +884,4 @@ _encode_deterministic_f64 :: proc(w: io.Writer, v: f64) -> io.Error {
 	}
 	}
 
 
 	return _encode_f64_exact(w, v)
 	return _encode_f64_exact(w, v)
-}
+}

+ 8 - 8
core/encoding/cbor/marshal.odin

@@ -45,8 +45,8 @@ marshal :: marshal_into
 
 
 // Marshals the given value into a CBOR byte stream (allocated using the given allocator).
 // Marshals the given value into a CBOR byte stream (allocated using the given allocator).
 // See docs on the `marshal_into` proc group for more info.
 // See docs on the `marshal_into` proc group for more info.
-marshal_into_bytes :: proc(v: any, flags := ENCODE_SMALL, allocator := context.allocator, temp_allocator := context.temp_allocator) -> (bytes: []byte, err: Marshal_Error) {
-	b, alloc_err := strings.builder_make(allocator)
+marshal_into_bytes :: proc(v: any, flags := ENCODE_SMALL, allocator := context.allocator, temp_allocator := context.temp_allocator, loc := #caller_location) -> (bytes: []byte, err: Marshal_Error) {
+	b, alloc_err := strings.builder_make(allocator, loc=loc)
  	// The builder as a stream also returns .EOF if it ran out of memory so this is consistent.
  	// The builder as a stream also returns .EOF if it ran out of memory so this is consistent.
 	if alloc_err != nil {
 	if alloc_err != nil {
 		return nil, .EOF
 		return nil, .EOF
@@ -54,7 +54,7 @@ marshal_into_bytes :: proc(v: any, flags := ENCODE_SMALL, allocator := context.a
 
 
 	defer if err != nil { strings.builder_destroy(&b) }
 	defer if err != nil { strings.builder_destroy(&b) }
 
 
-	if err = marshal_into_builder(&b, v, flags, temp_allocator); err != nil {
+	if err = marshal_into_builder(&b, v, flags, temp_allocator, loc=loc); err != nil {
 		return
 		return
 	}
 	}
 
 
@@ -63,20 +63,20 @@ marshal_into_bytes :: proc(v: any, flags := ENCODE_SMALL, allocator := context.a
 
 
 // Marshals the given value into a CBOR byte stream written to the given builder.
 // Marshals the given value into a CBOR byte stream written to the given builder.
 // See docs on the `marshal_into` proc group for more info.
 // See docs on the `marshal_into` proc group for more info.
-marshal_into_builder :: proc(b: ^strings.Builder, v: any, flags := ENCODE_SMALL, temp_allocator := context.temp_allocator) -> Marshal_Error {
-	return marshal_into_writer(strings.to_writer(b), v, flags, temp_allocator)
+marshal_into_builder :: proc(b: ^strings.Builder, v: any, flags := ENCODE_SMALL, temp_allocator := context.temp_allocator, loc := #caller_location) -> Marshal_Error {
+	return marshal_into_writer(strings.to_writer(b), v, flags, temp_allocator, loc=loc)
 }
 }
 
 
 // Marshals the given value into a CBOR byte stream written to the given writer.
 // Marshals the given value into a CBOR byte stream written to the given writer.
 // See docs on the `marshal_into` proc group for more info.
 // See docs on the `marshal_into` proc group for more info.
-marshal_into_writer :: proc(w: io.Writer, v: any, flags := ENCODE_SMALL, temp_allocator := context.temp_allocator) -> Marshal_Error {
+marshal_into_writer :: proc(w: io.Writer, v: any, flags := ENCODE_SMALL, temp_allocator := context.temp_allocator, loc := #caller_location) -> Marshal_Error {
 	encoder := Encoder{flags, w, temp_allocator}
 	encoder := Encoder{flags, w, temp_allocator}
-	return marshal_into_encoder(encoder, v)
+	return marshal_into_encoder(encoder, v, loc=loc)
 }
 }
 
 
 // Marshals the given value into a CBOR byte stream written to the given encoder.
 // Marshals the given value into a CBOR byte stream written to the given encoder.
 // See docs on the `marshal_into` proc group for more info.
 // See docs on the `marshal_into` proc group for more info.
-marshal_into_encoder :: proc(e: Encoder, v: any) -> (err: Marshal_Error) {
+marshal_into_encoder :: proc(e: Encoder, v: any, loc :=  #caller_location) -> (err: Marshal_Error) {
 	e := e
 	e := e
 
 
 	if e.temp_allocator.procedure == nil {
 	if e.temp_allocator.procedure == nil {

+ 52 - 50
core/encoding/cbor/unmarshal.odin

@@ -31,8 +31,8 @@ unmarshal :: proc {
 	unmarshal_from_string,
 	unmarshal_from_string,
 }
 }
 
 
-unmarshal_from_reader :: proc(r: io.Reader, ptr: ^$T, flags := Decoder_Flags{}, allocator := context.allocator, temp_allocator := context.temp_allocator) -> (err: Unmarshal_Error) {
-	err = unmarshal_from_decoder(Decoder{ DEFAULT_MAX_PRE_ALLOC, flags, r }, ptr, allocator, temp_allocator)
+unmarshal_from_reader :: proc(r: io.Reader, ptr: ^$T, flags := Decoder_Flags{}, allocator := context.allocator, temp_allocator := context.temp_allocator, loc := #caller_location) -> (err: Unmarshal_Error) {
+	err = unmarshal_from_decoder(Decoder{ DEFAULT_MAX_PRE_ALLOC, flags, r }, ptr, allocator, temp_allocator, loc)
 
 
 	// Normal EOF does not exist here, we try to read the exact amount that is said to be provided.
 	// Normal EOF does not exist here, we try to read the exact amount that is said to be provided.
 	if err == .EOF { err = .Unexpected_EOF }
 	if err == .EOF { err = .Unexpected_EOF }
@@ -40,21 +40,21 @@ unmarshal_from_reader :: proc(r: io.Reader, ptr: ^$T, flags := Decoder_Flags{},
 }
 }
 
 
 // Unmarshals from a string, see docs on the proc group `Unmarshal` for more info.
 // Unmarshals from a string, see docs on the proc group `Unmarshal` for more info.
-unmarshal_from_string :: proc(s: string, ptr: ^$T, flags := Decoder_Flags{}, allocator := context.allocator, temp_allocator := context.temp_allocator) -> (err: Unmarshal_Error) {
+unmarshal_from_string :: proc(s: string, ptr: ^$T, flags := Decoder_Flags{}, allocator := context.allocator, temp_allocator := context.temp_allocator, loc := #caller_location) -> (err: Unmarshal_Error) {
 	sr: strings.Reader
 	sr: strings.Reader
 	r := strings.to_reader(&sr, s)
 	r := strings.to_reader(&sr, s)
 
 
-	err = unmarshal_from_reader(r, ptr, flags, allocator, temp_allocator)
+	err = unmarshal_from_reader(r, ptr, flags, allocator, temp_allocator, loc)
 
 
 	// Normal EOF does not exist here, we try to read the exact amount that is said to be provided.
 	// Normal EOF does not exist here, we try to read the exact amount that is said to be provided.
 	if err == .EOF { err = .Unexpected_EOF }
 	if err == .EOF { err = .Unexpected_EOF }
 	return
 	return
 }
 }
 
 
-unmarshal_from_decoder :: proc(d: Decoder, ptr: ^$T, allocator := context.allocator, temp_allocator := context.temp_allocator) -> (err: Unmarshal_Error) {
+unmarshal_from_decoder :: proc(d: Decoder, ptr: ^$T, allocator := context.allocator, temp_allocator := context.temp_allocator, loc := #caller_location) -> (err: Unmarshal_Error) {
 	d := d
 	d := d
 
 
-	err = _unmarshal_any_ptr(d, ptr, nil, allocator, temp_allocator)
+	err = _unmarshal_any_ptr(d, ptr, nil, allocator, temp_allocator, loc)
 
 
 	// Normal EOF does not exist here, we try to read the exact amount that is said to be provided.
 	// Normal EOF does not exist here, we try to read the exact amount that is said to be provided.
 	if err == .EOF { err = .Unexpected_EOF }
 	if err == .EOF { err = .Unexpected_EOF }
@@ -62,7 +62,7 @@ unmarshal_from_decoder :: proc(d: Decoder, ptr: ^$T, allocator := context.alloca
 
 
 }
 }
 
 
-_unmarshal_any_ptr :: proc(d: Decoder, v: any, hdr: Maybe(Header) = nil, allocator := context.allocator, temp_allocator := context.temp_allocator) -> Unmarshal_Error {
+_unmarshal_any_ptr :: proc(d: Decoder, v: any, hdr: Maybe(Header) = nil, allocator := context.allocator, temp_allocator := context.temp_allocator, loc := #caller_location) -> Unmarshal_Error {
 	context.allocator = allocator
 	context.allocator = allocator
 	context.temp_allocator = temp_allocator
 	context.temp_allocator = temp_allocator
 	v := v
 	v := v
@@ -78,10 +78,10 @@ _unmarshal_any_ptr :: proc(d: Decoder, v: any, hdr: Maybe(Header) = nil, allocat
 	}
 	}
 	
 	
 	data := any{(^rawptr)(v.data)^, ti.variant.(reflect.Type_Info_Pointer).elem.id}	
 	data := any{(^rawptr)(v.data)^, ti.variant.(reflect.Type_Info_Pointer).elem.id}	
-	return _unmarshal_value(d, data, hdr.? or_else (_decode_header(d.reader) or_return))
+	return _unmarshal_value(d, data, hdr.? or_else (_decode_header(d.reader) or_return), allocator, temp_allocator, loc)
 }
 }
 
 
-_unmarshal_value :: proc(d: Decoder, v: any, hdr: Header) -> (err: Unmarshal_Error) {
+_unmarshal_value :: proc(d: Decoder, v: any, hdr: Header, allocator := context.allocator, temp_allocator := context.temp_allocator, loc := #caller_location) -> (err: Unmarshal_Error) {
 	v := v
 	v := v
 	ti := reflect.type_info_base(type_info_of(v.id))
 	ti := reflect.type_info_base(type_info_of(v.id))
 	r := d.reader
 	r := d.reader
@@ -104,7 +104,7 @@ _unmarshal_value :: proc(d: Decoder, v: any, hdr: Header) -> (err: Unmarshal_Err
 	// Allow generic unmarshal by doing it into a `Value`.
 	// Allow generic unmarshal by doing it into a `Value`.
 	switch &dst in v {
 	switch &dst in v {
 	case Value:
 	case Value:
-		dst = err_conv(_decode_from_decoder(d, hdr)) or_return
+		dst = err_conv(_decode_from_decoder(d, hdr, allocator, loc)) or_return
 		return
 		return
 	}
 	}
 
 
@@ -308,7 +308,7 @@ _unmarshal_value :: proc(d: Decoder, v: any, hdr: Header) -> (err: Unmarshal_Err
 		if impl, ok := _tag_implementations_nr[nr]; ok {
 		if impl, ok := _tag_implementations_nr[nr]; ok {
 			return impl->unmarshal(d, nr, v)
 			return impl->unmarshal(d, nr, v)
 		} else if nr == TAG_OBJECT_TYPE {
 		} else if nr == TAG_OBJECT_TYPE {
-			return _unmarshal_union(d, v, ti, hdr)
+			return _unmarshal_union(d, v, ti, hdr, loc=loc)
 		} else {
 		} else {
 			// Discard the tag info and unmarshal as its value.
 			// Discard the tag info and unmarshal as its value.
 			return _unmarshal_value(d, v, _decode_header(r) or_return)
 			return _unmarshal_value(d, v, _decode_header(r) or_return)
@@ -316,19 +316,19 @@ _unmarshal_value :: proc(d: Decoder, v: any, hdr: Header) -> (err: Unmarshal_Err
 
 
 		return _unsupported(v, hdr, add)
 		return _unsupported(v, hdr, add)
 
 
-	case .Bytes: return _unmarshal_bytes(d, v, ti, hdr, add)
-	case .Text:  return _unmarshal_string(d, v, ti, hdr, add)
-	case .Array: return _unmarshal_array(d, v, ti, hdr, add)
-	case .Map:   return _unmarshal_map(d, v, ti, hdr, add)
+	case .Bytes: return _unmarshal_bytes(d, v, ti, hdr, add, allocator=allocator, loc=loc)
+	case .Text:  return _unmarshal_string(d, v, ti, hdr, add, allocator=allocator, loc=loc)
+	case .Array: return _unmarshal_array(d, v, ti, hdr, add, allocator=allocator, loc=loc)
+	case .Map:   return _unmarshal_map(d, v, ti, hdr, add, allocator=allocator, loc=loc)
 
 
 	case:        return .Bad_Major
 	case:        return .Bad_Major
 	}
 	}
 }
 }
 
 
-_unmarshal_bytes :: proc(d: Decoder, v: any, ti: ^reflect.Type_Info, hdr: Header, add: Add) -> (err: Unmarshal_Error) {
+_unmarshal_bytes :: proc(d: Decoder, v: any, ti: ^reflect.Type_Info, hdr: Header, add: Add, allocator := context.allocator, loc := #caller_location) -> (err: Unmarshal_Error) {
 	#partial switch t in ti.variant {
 	#partial switch t in ti.variant {
 	case reflect.Type_Info_String:
 	case reflect.Type_Info_String:
-		bytes := err_conv(_decode_bytes(d, add)) or_return
+		bytes := err_conv(_decode_bytes(d, add, allocator=allocator, loc=loc)) or_return
 
 
 		if t.is_cstring {
 		if t.is_cstring {
 			raw  := (^cstring)(v.data)
 			raw  := (^cstring)(v.data)
@@ -347,7 +347,7 @@ _unmarshal_bytes :: proc(d: Decoder, v: any, ti: ^reflect.Type_Info, hdr: Header
 
 
 		if elem_base.id != byte { return _unsupported(v, hdr) }
 		if elem_base.id != byte { return _unsupported(v, hdr) }
 
 
-		bytes := err_conv(_decode_bytes(d, add)) or_return
+		bytes := err_conv(_decode_bytes(d, add, allocator=allocator, loc=loc)) or_return
 		raw   := (^mem.Raw_Slice)(v.data)
 		raw   := (^mem.Raw_Slice)(v.data)
 		raw^   = transmute(mem.Raw_Slice)bytes
 		raw^   = transmute(mem.Raw_Slice)bytes
 		return
 		return
@@ -357,12 +357,12 @@ _unmarshal_bytes :: proc(d: Decoder, v: any, ti: ^reflect.Type_Info, hdr: Header
 
 
 		if elem_base.id != byte { return _unsupported(v, hdr) }
 		if elem_base.id != byte { return _unsupported(v, hdr) }
 		
 		
-		bytes         := err_conv(_decode_bytes(d, add)) or_return
+		bytes         := err_conv(_decode_bytes(d, add, allocator=allocator, loc=loc)) or_return
 		raw           := (^mem.Raw_Dynamic_Array)(v.data)
 		raw           := (^mem.Raw_Dynamic_Array)(v.data)
 		raw.data       = raw_data(bytes)
 		raw.data       = raw_data(bytes)
 		raw.len        = len(bytes)
 		raw.len        = len(bytes)
 		raw.cap        = len(bytes)
 		raw.cap        = len(bytes)
-		raw.allocator  = context.allocator
+		raw.allocator  = allocator
 		return
 		return
 
 
 	case reflect.Type_Info_Array:
 	case reflect.Type_Info_Array:
@@ -385,10 +385,10 @@ _unmarshal_bytes :: proc(d: Decoder, v: any, ti: ^reflect.Type_Info, hdr: Header
 	return _unsupported(v, hdr)
 	return _unsupported(v, hdr)
 }
 }
 
 
-_unmarshal_string :: proc(d: Decoder, v: any, ti: ^reflect.Type_Info, hdr: Header, add: Add) -> (err: Unmarshal_Error) {
+_unmarshal_string :: proc(d: Decoder, v: any, ti: ^reflect.Type_Info, hdr: Header, add: Add, allocator := context.allocator, temp_allocator := context.temp_allocator, loc := #caller_location) -> (err: Unmarshal_Error) {
 	#partial switch t in ti.variant {
 	#partial switch t in ti.variant {
 	case reflect.Type_Info_String:
 	case reflect.Type_Info_String:
-		text := err_conv(_decode_text(d, add)) or_return
+		text := err_conv(_decode_text(d, add, allocator, loc)) or_return
 
 
 		if t.is_cstring {
 		if t.is_cstring {
 			raw := (^cstring)(v.data)
 			raw := (^cstring)(v.data)
@@ -403,8 +403,8 @@ _unmarshal_string :: proc(d: Decoder, v: any, ti: ^reflect.Type_Info, hdr: Heade
 
 
 	// Enum by its variant name.
 	// Enum by its variant name.
 	case reflect.Type_Info_Enum:
 	case reflect.Type_Info_Enum:
-		text := err_conv(_decode_text(d, add, allocator=context.temp_allocator)) or_return
-		defer delete(text, context.temp_allocator)
+		text := err_conv(_decode_text(d, add, allocator=temp_allocator, loc=loc)) or_return
+		defer delete(text, temp_allocator, loc)
 
 
 		for name, i in t.names {
 		for name, i in t.names {
 			if name == text {
 			if name == text {
@@ -414,8 +414,8 @@ _unmarshal_string :: proc(d: Decoder, v: any, ti: ^reflect.Type_Info, hdr: Heade
 		}
 		}
 	
 	
 	case reflect.Type_Info_Rune:
 	case reflect.Type_Info_Rune:
-		text := err_conv(_decode_text(d, add, allocator=context.temp_allocator)) or_return
-		defer delete(text, context.temp_allocator)
+		text := err_conv(_decode_text(d, add, allocator=temp_allocator, loc=loc)) or_return
+		defer delete(text, temp_allocator, loc)
 
 
 		r := (^rune)(v.data)
 		r := (^rune)(v.data)
 		dr, n := utf8.decode_rune(text)
 		dr, n := utf8.decode_rune(text)
@@ -430,13 +430,15 @@ _unmarshal_string :: proc(d: Decoder, v: any, ti: ^reflect.Type_Info, hdr: Heade
 	return _unsupported(v, hdr)
 	return _unsupported(v, hdr)
 }
 }
 
 
-_unmarshal_array :: proc(d: Decoder, v: any, ti: ^reflect.Type_Info, hdr: Header, add: Add) -> (err: Unmarshal_Error) {
+_unmarshal_array :: proc(d: Decoder, v: any, ti: ^reflect.Type_Info, hdr: Header, add: Add, allocator := context.allocator, loc := #caller_location) -> (err: Unmarshal_Error) {
 	assign_array :: proc(
 	assign_array :: proc(
 		d: Decoder,
 		d: Decoder,
 		da: ^mem.Raw_Dynamic_Array,
 		da: ^mem.Raw_Dynamic_Array,
 		elemt: ^reflect.Type_Info,
 		elemt: ^reflect.Type_Info,
 		length: int,
 		length: int,
 		growable := true,
 		growable := true,
+		allocator := context.allocator,
+		loc := #caller_location,
 	) -> (out_of_space: bool, err: Unmarshal_Error) {
 	) -> (out_of_space: bool, err: Unmarshal_Error) {
 		for idx: uintptr = 0; length == -1 || idx < uintptr(length); idx += 1 {
 		for idx: uintptr = 0; length == -1 || idx < uintptr(length); idx += 1 {
 			elem_ptr := rawptr(uintptr(da.data) + idx*uintptr(elemt.size))
 			elem_ptr := rawptr(uintptr(da.data) + idx*uintptr(elemt.size))
@@ -450,13 +452,13 @@ _unmarshal_array :: proc(d: Decoder, v: any, ti: ^reflect.Type_Info, hdr: Header
 				if !growable { return true, .Out_Of_Memory }
 				if !growable { return true, .Out_Of_Memory }
 
 
 				cap := 2 * da.cap
 				cap := 2 * da.cap
-				ok := runtime.__dynamic_array_reserve(da, elemt.size, elemt.align, cap)
+				ok := runtime.__dynamic_array_reserve(da, elemt.size, elemt.align, cap, loc)
  				
  				
 				// NOTE: Might be lying here, but it is at least an allocator error.
 				// NOTE: Might be lying here, but it is at least an allocator error.
 				if !ok { return false, .Out_Of_Memory }
 				if !ok { return false, .Out_Of_Memory }
 			}
 			}
 			
 			
-			err = _unmarshal_value(d, elem, hdr)
+			err = _unmarshal_value(d, elem, hdr, allocator=allocator, loc=loc)
 			if length == -1 && err == .Break { break }
 			if length == -1 && err == .Break { break }
 			if err != nil { return }
 			if err != nil { return }
 
 
@@ -469,10 +471,10 @@ _unmarshal_array :: proc(d: Decoder, v: any, ti: ^reflect.Type_Info, hdr: Header
 	// Allow generically storing the values array.
 	// Allow generically storing the values array.
 	switch &dst in v {
 	switch &dst in v {
 	case ^Array:
 	case ^Array:
-		dst = err_conv(_decode_array_ptr(d, add)) or_return
+		dst = err_conv(_decode_array_ptr(d, add, allocator=allocator, loc=loc)) or_return
 		return
 		return
 	case Array:
 	case Array:
-		dst = err_conv(_decode_array(d, add)) or_return
+		dst = err_conv(_decode_array(d, add, allocator=allocator, loc=loc)) or_return
 		return
 		return
 	}
 	}
 
 
@@ -480,8 +482,8 @@ _unmarshal_array :: proc(d: Decoder, v: any, ti: ^reflect.Type_Info, hdr: Header
 	case reflect.Type_Info_Slice:
 	case reflect.Type_Info_Slice:
 		length, scap := err_conv(_decode_len_container(d, add)) or_return
 		length, scap := err_conv(_decode_len_container(d, add)) or_return
 
 
-		data := mem.alloc_bytes_non_zeroed(t.elem.size * scap, t.elem.align) or_return
-		defer if err != nil { mem.free_bytes(data) }
+		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, length, context.allocator }
 
 
@@ -489,7 +491,7 @@ _unmarshal_array :: proc(d: Decoder, v: any, ti: ^reflect.Type_Info, hdr: Header
 
 
 		if .Shrink_Excess in d.flags {
 		if .Shrink_Excess in d.flags {
 			// Ignoring an error here, but this is not critical to succeed.
 			// Ignoring an error here, but this is not critical to succeed.
-			_ = runtime.__dynamic_array_shrink(&da, t.elem.size, t.elem.align, da.len)
+			_ = runtime.__dynamic_array_shrink(&da, t.elem.size, t.elem.align, da.len, loc=loc)
 		}
 		}
 
 
 		raw      := (^mem.Raw_Slice)(v.data)
 		raw      := (^mem.Raw_Slice)(v.data)
@@ -500,8 +502,8 @@ _unmarshal_array :: proc(d: Decoder, v: any, ti: ^reflect.Type_Info, hdr: Header
 	case reflect.Type_Info_Dynamic_Array:
 	case reflect.Type_Info_Dynamic_Array:
 		length, scap := err_conv(_decode_len_container(d, add)) or_return
 		length, scap := err_conv(_decode_len_container(d, add)) or_return
 
 
-		data := mem.alloc_bytes_non_zeroed(t.elem.size * scap, t.elem.align) or_return
-		defer if err != nil { mem.free_bytes(data) }
+		data := mem.alloc_bytes_non_zeroed(t.elem.size * scap, t.elem.align, loc=loc) or_return
+		defer if err != nil { mem.free_bytes(data, allocator=allocator, loc=loc) }
 
 
 		raw           := (^mem.Raw_Dynamic_Array)(v.data)
 		raw           := (^mem.Raw_Dynamic_Array)(v.data)
 		raw.data       = raw_data(data) 
 		raw.data       = raw_data(data) 
@@ -513,7 +515,7 @@ _unmarshal_array :: proc(d: Decoder, v: any, ti: ^reflect.Type_Info, hdr: Header
 
 
 		if .Shrink_Excess in d.flags {
 		if .Shrink_Excess in d.flags {
 			// Ignoring an error here, but this is not critical to succeed.
 			// Ignoring an error here, but this is not critical to succeed.
-			_ = runtime.__dynamic_array_shrink(raw, t.elem.size, t.elem.align, raw.len)
+			_ = runtime.__dynamic_array_shrink(raw, t.elem.size, t.elem.align, raw.len, loc=loc)
 		}
 		}
 		return
 		return
 
 
@@ -525,7 +527,7 @@ _unmarshal_array :: proc(d: Decoder, v: any, ti: ^reflect.Type_Info, hdr: Header
 			return _unsupported(v, hdr)
 			return _unsupported(v, hdr)
 		}
 		}
 
 
-		da := mem.Raw_Dynamic_Array{rawptr(v.data), 0, length, context.allocator }
+		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
 		out_of_space := assign_array(d, &da, t.elem, length, growable=false) or_return
 		if out_of_space { return _unsupported(v, hdr) }
 		if out_of_space { return _unsupported(v, hdr) }
@@ -539,7 +541,7 @@ _unmarshal_array :: proc(d: Decoder, v: any, ti: ^reflect.Type_Info, hdr: Header
 			return _unsupported(v, hdr)
 			return _unsupported(v, hdr)
 		}
 		}
 
 
-		da := mem.Raw_Dynamic_Array{rawptr(v.data), 0, length, context.allocator }
+		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
 		out_of_space := assign_array(d, &da, t.elem, length, growable=false) or_return
 		if out_of_space { return _unsupported(v, hdr) }
 		if out_of_space { return _unsupported(v, hdr) }
@@ -553,7 +555,7 @@ _unmarshal_array :: proc(d: Decoder, v: any, ti: ^reflect.Type_Info, hdr: Header
 			return _unsupported(v, hdr)
 			return _unsupported(v, hdr)
 		}
 		}
 
 
-		da := mem.Raw_Dynamic_Array{rawptr(v.data), 0, 2, context.allocator }
+		da := mem.Raw_Dynamic_Array{rawptr(v.data), 0, 2, allocator }
 
 
 		info: ^runtime.Type_Info
 		info: ^runtime.Type_Info
 		switch ti.id {
 		switch ti.id {
@@ -575,7 +577,7 @@ _unmarshal_array :: proc(d: Decoder, v: any, ti: ^reflect.Type_Info, hdr: Header
 			return _unsupported(v, hdr)
 			return _unsupported(v, hdr)
 		}
 		}
 
 
-		da := mem.Raw_Dynamic_Array{rawptr(v.data), 0, 4, context.allocator }
+		da := mem.Raw_Dynamic_Array{rawptr(v.data), 0, 4, allocator }
 
 
 		info: ^runtime.Type_Info
 		info: ^runtime.Type_Info
 		switch ti.id {
 		switch ti.id {
@@ -593,17 +595,17 @@ _unmarshal_array :: proc(d: Decoder, v: any, ti: ^reflect.Type_Info, hdr: Header
 	}
 	}
 }
 }
 
 
-_unmarshal_map :: proc(d: Decoder, v: any, ti: ^reflect.Type_Info, hdr: Header, add: Add) -> (err: Unmarshal_Error) {
+_unmarshal_map :: proc(d: Decoder, v: any, ti: ^reflect.Type_Info, hdr: Header, add: Add, allocator := context.allocator, loc := #caller_location) -> (err: Unmarshal_Error) {
 	r := d.reader
 	r := d.reader
-	decode_key :: proc(d: Decoder, v: any, allocator := context.allocator) -> (k: string, err: Unmarshal_Error) {
+	decode_key :: proc(d: Decoder, v: any, allocator := context.allocator, loc := #caller_location) -> (k: string, err: Unmarshal_Error) {
 		entry_hdr := _decode_header(d.reader) or_return
 		entry_hdr := _decode_header(d.reader) or_return
 		entry_maj, entry_add := _header_split(entry_hdr)
 		entry_maj, entry_add := _header_split(entry_hdr)
 		#partial switch entry_maj {
 		#partial switch entry_maj {
 		case .Text:
 		case .Text:
-			k = err_conv(_decode_text(d, entry_add, allocator)) or_return
+			k = err_conv(_decode_text(d, entry_add, allocator=allocator, loc=loc)) or_return
 			return
 			return
 		case .Bytes:
 		case .Bytes:
-			bytes := err_conv(_decode_bytes(d, entry_add, allocator=allocator)) or_return
+			bytes := err_conv(_decode_bytes(d, entry_add, allocator=allocator, loc=loc)) or_return
 			k = string(bytes)
 			k = string(bytes)
 			return
 			return
 		case:
 		case:
@@ -615,10 +617,10 @@ _unmarshal_map :: proc(d: Decoder, v: any, ti: ^reflect.Type_Info, hdr: Header,
 	// Allow generically storing the map array.
 	// Allow generically storing the map array.
 	switch &dst in v {
 	switch &dst in v {
 	case ^Map:
 	case ^Map:
-		dst = err_conv(_decode_map_ptr(d, add)) or_return
+		dst = err_conv(_decode_map_ptr(d, add, allocator=allocator, loc=loc)) or_return
 		return
 		return
 	case Map:
 	case Map:
-		dst = err_conv(_decode_map(d, add)) or_return
+		dst = err_conv(_decode_map(d, add, allocator=allocator, loc=loc)) or_return
 		return
 		return
 	}
 	}
 
 
@@ -754,7 +756,7 @@ _unmarshal_map :: proc(d: Decoder, v: any, ti: ^reflect.Type_Info, hdr: Header,
 // Unmarshal into a union, based on the `TAG_OBJECT_TYPE` tag of the spec, it denotes a tag which
 // Unmarshal into a union, based on the `TAG_OBJECT_TYPE` tag of the spec, it denotes a tag which
 // contains an array of exactly two elements, the first is a textual representation of the following
 // contains an array of exactly two elements, the first is a textual representation of the following
 // CBOR value's type.
 // CBOR value's type.
-_unmarshal_union :: proc(d: Decoder, v: any, ti: ^reflect.Type_Info, hdr: Header) -> (err: Unmarshal_Error) {
+_unmarshal_union :: proc(d: Decoder, v: any, ti: ^reflect.Type_Info, hdr: Header, loc := #caller_location) -> (err: Unmarshal_Error) {
 	r := d.reader
 	r := d.reader
 	#partial switch t in ti.variant {
 	#partial switch t in ti.variant {
 	case reflect.Type_Info_Union:
 	case reflect.Type_Info_Union:
@@ -792,7 +794,7 @@ _unmarshal_union :: proc(d: Decoder, v: any, ti: ^reflect.Type_Info, hdr: Header
 			case reflect.Type_Info_Named:
 			case reflect.Type_Info_Named:
 				if vti.name == target_name {
 				if vti.name == target_name {
 					reflect.set_union_variant_raw_tag(v, tag)
 					reflect.set_union_variant_raw_tag(v, tag)
-					return _unmarshal_value(d, any{v.data, variant.id}, _decode_header(r) or_return)
+					return _unmarshal_value(d, any{v.data, variant.id}, _decode_header(r) or_return, loc=loc)
 				}
 				}
 
 
 			case:
 			case:
@@ -804,7 +806,7 @@ _unmarshal_union :: proc(d: Decoder, v: any, ti: ^reflect.Type_Info, hdr: Header
 				
 				
 				if variant_name == target_name {
 				if variant_name == target_name {
 					reflect.set_union_variant_raw_tag(v, tag)
 					reflect.set_union_variant_raw_tag(v, tag)
-					return _unmarshal_value(d, any{v.data, variant.id}, _decode_header(r) or_return)
+					return _unmarshal_value(d, any{v.data, variant.id}, _decode_header(r) or_return, loc=loc)
 				}
 				}
 			}
 			}
 		}
 		}

+ 5 - 6
core/encoding/hex/hex.odin

@@ -2,8 +2,8 @@ package encoding_hex
 
 
 import "core:strings"
 import "core:strings"
 
 
-encode :: proc(src: []byte, allocator := context.allocator) -> []byte #no_bounds_check {
-	dst := make([]byte, len(src) * 2, allocator)
+encode :: proc(src: []byte, allocator := context.allocator, loc := #caller_location) -> []byte #no_bounds_check {
+	dst := make([]byte, len(src) * 2, allocator, loc)
 	for i, j := 0, 0; i < len(src); i += 1 {
 	for i, j := 0, 0; i < len(src); i += 1 {
 		v := src[i]
 		v := src[i]
 		dst[j]   = HEXTABLE[v>>4]
 		dst[j]   = HEXTABLE[v>>4]
@@ -15,12 +15,12 @@ encode :: proc(src: []byte, allocator := context.allocator) -> []byte #no_bounds
 }
 }
 
 
 
 
-decode :: proc(src: []byte, allocator := context.allocator) -> (dst: []byte, ok: bool) #no_bounds_check {
+decode :: proc(src: []byte, allocator := context.allocator, loc := #caller_location) -> (dst: []byte, ok: bool) #no_bounds_check {
 	if len(src) % 2 == 1 {
 	if len(src) % 2 == 1 {
 		return
 		return
 	}
 	}
 
 
-	dst = make([]byte, len(src) / 2, allocator)
+	dst = make([]byte, len(src) / 2, allocator, loc)
 	for i, j := 0, 1; j < len(src); j += 2 {
 	for i, j := 0, 1; j < len(src); j += 2 {
 		p := src[j-1]
 		p := src[j-1]
 		q := src[j]
 		q := src[j]
@@ -69,5 +69,4 @@ hex_digit :: proc(char: byte) -> (u8, bool) {
 	case 'A' ..= 'F': return char - 'A' + 10, true
 	case 'A' ..= 'F': return char - 'A' + 10, true
 	case:             return 0, false
 	case:             return 0, false
 	}
 	}
-}
-
+}

+ 16 - 15
core/encoding/hxa/hxa.odin

@@ -160,34 +160,35 @@ CONVENTION_SOFT_TRANSFORM :: "transform"
 
 
 /* destroy procedures */
 /* destroy procedures */
 
 
-meta_destroy :: proc(meta: Meta, allocator := context.allocator) {
+meta_destroy :: proc(meta: Meta, allocator := context.allocator, loc := #caller_location) {
 	if nested, ok := meta.value.([]Meta); ok {
 	if nested, ok := meta.value.([]Meta); ok {
 		for m in nested {
 		for m in nested {
-			meta_destroy(m)
+			meta_destroy(m, loc=loc)
 		}
 		}
-		delete(nested, allocator)
+		delete(nested, allocator, loc=loc)
 	}
 	}
 }
 }
-nodes_destroy :: proc(nodes: []Node, allocator := context.allocator) {
+nodes_destroy :: proc(nodes: []Node, allocator := context.allocator, loc := #caller_location) {
 	for node in nodes {
 	for node in nodes {
 		for meta in node.meta_data {
 		for meta in node.meta_data {
-			meta_destroy(meta)
+			meta_destroy(meta, loc=loc)
 		}
 		}
-		delete(node.meta_data, allocator)
+		delete(node.meta_data, allocator, loc=loc)
 
 
 		switch n in node.content {
 		switch n in node.content {
 		case Node_Geometry:
 		case Node_Geometry:
-			delete(n.corner_stack, allocator)
-			delete(n.edge_stack, allocator)
-			delete(n.face_stack, allocator)
+			delete(n.corner_stack, allocator, loc=loc)
+			delete(n.vertex_stack, allocator, loc=loc)
+			delete(n.edge_stack,   allocator, loc=loc)
+			delete(n.face_stack,   allocator, loc=loc)
 		case Node_Image:
 		case Node_Image:
-			delete(n.image_stack, allocator)
+			delete(n.image_stack,  allocator, loc=loc)
 		}
 		}
 	}
 	}
-	delete(nodes, allocator)
+	delete(nodes, allocator, loc=loc)
 }
 }
 
 
-file_destroy :: proc(file: File) {
-	nodes_destroy(file.nodes, file.allocator)
-	delete(file.backing, file.allocator)
-}
+file_destroy :: proc(file: File, loc := #caller_location) {
+	nodes_destroy(file.nodes, file.allocator, loc=loc)
+	delete(file.backing, file.allocator, loc=loc)
+}

+ 20 - 22
core/encoding/hxa/read.odin

@@ -11,24 +11,21 @@ Read_Error :: enum {
 	Unable_To_Read_File,
 	Unable_To_Read_File,
 }
 }
 
 
-read_from_file :: proc(filename: string, print_error := false, allocator := context.allocator) -> (file: File, err: Read_Error) {
+read_from_file :: proc(filename: string, print_error := false, allocator := context.allocator, loc := #caller_location) -> (file: File, err: Read_Error) {
 	context.allocator = allocator
 	context.allocator = allocator
 
 
-	data, ok := os.read_entire_file(filename)
+	data, ok := os.read_entire_file(filename, allocator, loc)
 	if !ok {
 	if !ok {
 		err = .Unable_To_Read_File
 		err = .Unable_To_Read_File
+		delete(data, allocator, loc)
 		return
 		return
 	}
 	}
-	defer if !ok {
-		delete(data)
-	} else {
-		file.backing = data
-	}
-	file, err = read(data, filename, print_error, allocator)
+	file, err = read(data, filename, print_error, allocator, loc)
+	file.backing   = data
 	return
 	return
 }
 }
 
 
-read :: proc(data: []byte, filename := "<input>", print_error := false, allocator := context.allocator) -> (file: File, err: Read_Error) {
+read :: proc(data: []byte, filename := "<input>", print_error := false, allocator := context.allocator, loc := #caller_location) -> (file: File, err: Read_Error) {
 	Reader :: struct {
 	Reader :: struct {
 		filename:    string,
 		filename:    string,
 		data:        []byte,
 		data:        []byte,
@@ -79,8 +76,8 @@ read :: proc(data: []byte, filename := "<input>", print_error := false, allocato
 		return string(data[:len]), nil
 		return string(data[:len]), nil
 	}
 	}
 
 
-	read_meta :: proc(r: ^Reader, capacity: u32le) -> (meta_data: []Meta, err: Read_Error) {
-		meta_data = make([]Meta, int(capacity))
+	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
 		count := 0
 		defer meta_data = meta_data[:count]
 		defer meta_data = meta_data[:count]
 		for &m in meta_data {
 		for &m in meta_data {
@@ -111,10 +108,10 @@ read :: proc(data: []byte, filename := "<input>", print_error := false, allocato
 		return
 		return
 	}
 	}
 
 
-	read_layer_stack :: proc(r: ^Reader, capacity: u32le) -> (layers: Layer_Stack, err: Read_Error) {
+	read_layer_stack :: proc(r: ^Reader, capacity: u32le, allocator := context.allocator, loc := #caller_location) -> (layers: Layer_Stack, err: Read_Error) {
 		stack_count := read_value(r, u32le) or_return
 		stack_count := read_value(r, u32le) or_return
 		layer_count := 0
 		layer_count := 0
-		layers = make(Layer_Stack, stack_count)
+		layers = make(Layer_Stack, stack_count, allocator=allocator, loc=loc)
 		defer layers = layers[:layer_count]
 		defer layers = layers[:layer_count]
 		for &layer in layers {
 		for &layer in layers {
 			layer.name = read_name(r) or_return
 			layer.name = read_name(r) or_return
@@ -170,7 +167,8 @@ read :: proc(data: []byte, filename := "<input>", print_error := false, allocato
 
 
 	node_count := 0
 	node_count := 0
 	file.header = header^
 	file.header = header^
-	file.nodes = make([]Node, header.internal_node_count)
+	file.nodes = make([]Node, header.internal_node_count, allocator=allocator, loc=loc)
+	file.allocator = allocator
 	defer if err != nil {
 	defer if err != nil {
 		nodes_destroy(file.nodes)
 		nodes_destroy(file.nodes)
 		file.nodes = nil
 		file.nodes = nil
@@ -198,15 +196,15 @@ read :: proc(data: []byte, filename := "<input>", print_error := false, allocato
 		case .Geometry:
 		case .Geometry:
 			g: Node_Geometry
 			g: Node_Geometry
 
 
-			g.vertex_count      = read_value(r, u32le)                     or_return
-			g.vertex_stack      = read_layer_stack(r, g.vertex_count)      or_return
-			g.edge_corner_count = read_value(r, u32le)                     or_return
-			g.corner_stack      = read_layer_stack(r, g.edge_corner_count) or_return
+			g.vertex_count      = read_value(r, u32le)                               or_return
+			g.vertex_stack      = read_layer_stack(r, g.vertex_count, loc=loc)       or_return
+			g.edge_corner_count = read_value(r, u32le)                               or_return
+			g.corner_stack      = read_layer_stack(r, g.edge_corner_count, loc=loc)  or_return
 			if header.version > 2 {
 			if header.version > 2 {
-				g.edge_stack = read_layer_stack(r, g.edge_corner_count) or_return
+				g.edge_stack = read_layer_stack(r, g.edge_corner_count, loc=loc) or_return
 			}
 			}
-			g.face_count = read_value(r, u32le)              or_return
-			g.face_stack = read_layer_stack(r, g.face_count) or_return
+			g.face_count = read_value(r, u32le)                       or_return
+			g.face_stack = read_layer_stack(r, g.face_count, loc=loc) or_return
 
 
 			node.content = g
 			node.content = g
 
 
@@ -233,4 +231,4 @@ read :: proc(data: []byte, filename := "<input>", print_error := false, allocato
 	}
 	}
 
 
 	return
 	return
-}
+}

+ 189 - 0
core/encoding/ini/ini.odin

@@ -0,0 +1,189 @@
+package encoding_ini
+
+import "base:runtime"
+import "base:intrinsics"
+import "core:strings"
+import "core:strconv"
+import "core:io"
+import "core:os"
+import "core:fmt"
+_ :: fmt
+
+Options :: struct {
+	comment: string,
+	key_lower_case: bool,
+}
+
+DEFAULT_OPTIONS :: Options {
+	comment = ";",
+	key_lower_case = false,
+}
+
+Iterator :: struct {
+	section: string,
+	_src:    string,
+	options: Options,
+}
+
+iterator_from_string :: proc(src: string, options := DEFAULT_OPTIONS) -> Iterator {
+	return {
+		section = "",
+		options = options,
+		_src = src,
+	}
+}
+
+
+// Returns the raw `key` and `value`. `ok` will be false if no more key=value pairs cannot be found.
+// They key and value may be quoted, which may require the use of `strconv.unquote_string`.
+iterate :: proc(it: ^Iterator) -> (key, value: string, ok: bool) {
+	for line_ in strings.split_lines_iterator(&it._src) {
+		line := strings.trim_space(line_)
+
+		if len(line) == 0 {
+			continue
+		}
+
+		if line[0] == '[' {
+			end_idx := strings.index_byte(line, ']')
+			if end_idx < 0 {
+				end_idx = len(line)
+			}
+			it.section = line[1:end_idx]
+			continue
+		}
+
+		if it.options.comment != "" && strings.has_prefix(line, it.options.comment) {
+			continue
+		}
+
+		equal := strings.index(line, " =") // check for things keys that `ctrl+= = zoom_in`
+		quote := strings.index_byte(line, '"')
+		if equal < 0 || quote > 0 && quote < equal {
+			equal = strings.index_byte(line, '=')
+			if equal < 0 {
+				continue
+			}
+		} else {
+			equal += 1
+		}
+
+		key = strings.trim_space(line[:equal])
+		value = strings.trim_space(line[equal+1:])
+		ok = true
+		return
+	}
+
+	it.section = ""
+	return
+}
+
+Map :: distinct map[string]map[string]string
+
+load_map_from_string :: proc(src: string, allocator: runtime.Allocator, options := DEFAULT_OPTIONS) -> (m: Map, err: runtime.Allocator_Error) {
+	unquote :: proc(val: string) -> (string, runtime.Allocator_Error) {
+		v, allocated, ok := strconv.unquote_string(val)
+		if !ok {
+			return strings.clone(val)
+		}
+		if allocated {
+			return v, nil
+		}
+		return strings.clone(v)
+
+	}
+
+	context.allocator = allocator
+
+	it := iterator_from_string(src, options)
+
+	for key, value in iterate(&it) {
+		section := it.section
+		if section not_in m {
+			section = strings.clone(section) or_return
+			m[section] = {}
+		}
+
+		// store key-value pair
+		pairs := &m[section]
+		new_key := unquote(key) or_return
+		if options.key_lower_case {
+			old_key := new_key
+			new_key = strings.to_lower(key) or_return
+			delete(old_key) or_return
+		}
+		pairs[new_key] = unquote(value) or_return
+	}
+	return
+}
+
+load_map_from_path :: proc(path: string, allocator: runtime.Allocator, options := DEFAULT_OPTIONS) -> (m: Map, err: runtime.Allocator_Error, ok: bool) {
+	data := os.read_entire_file(path, allocator) or_return
+	defer delete(data, allocator)
+	m, err = load_map_from_string(string(data), allocator, options)
+	ok = err != nil
+	defer if !ok {
+		delete_map(m)
+	}
+	return
+}
+
+save_map_to_string :: proc(m: Map, allocator: runtime.Allocator) -> (data: string) {
+	b := strings.builder_make(allocator)
+	_, _ = write_map(strings.to_writer(&b), m)
+	return strings.to_string(b)
+}
+
+delete_map :: proc(m: Map) {
+	allocator := m.allocator
+	for section, pairs in m {
+		for key, value in pairs {
+			delete(key, allocator)
+			delete(value, allocator)
+		}
+		delete(section)
+	}
+	delete(m)
+}
+
+write_section :: proc(w: io.Writer, name: string, n_written: ^int = nil) -> (n: int, err: io.Error) {
+	defer if n_written != nil { n_written^ += n }
+	io.write_byte  (w, '[',  &n) or_return
+	io.write_string(w, name, &n) or_return
+	io.write_byte  (w, ']',  &n) or_return
+	return
+}
+
+write_pair :: proc(w: io.Writer, key: string, value: $T, n_written: ^int = nil) -> (n: int, err: io.Error) {
+	defer if n_written != nil { n_written^ += n }
+	io.write_string(w, key,   &n) or_return
+	io.write_string(w, " = ", &n) or_return
+	when intrinsics.type_is_string(T) {
+		val := string(value)
+		if len(val) > 0 && (val[0] == ' ' || val[len(val)-1] == ' ') {
+			io.write_quoted_string(w, val, n_written=&n) or_return
+		} else {
+			io.write_string(w, val, &n) or_return
+		}
+	} else {
+		n += fmt.wprint(w, value)
+	}
+	io.write_byte(w, '\n', &n) or_return
+	return
+}
+
+write_map :: proc(w: io.Writer, m: Map) -> (n: int, err: io.Error) {
+	section_index := 0
+	for section, pairs in m {
+		if section_index == 0 && section == "" {
+			// ignore section
+		} else {
+			write_section(w, section, &n) or_return
+		}
+		for key, value in pairs {
+			write_pair(w, key, value, &n) or_return
+		}
+		section_index += 1
+	}
+	return
+}

+ 2 - 2
core/encoding/json/marshal.odin

@@ -62,8 +62,8 @@ Marshal_Options :: struct {
 	mjson_skipped_first_braces_end: bool,
 	mjson_skipped_first_braces_end: bool,
 }
 }
 
 
-marshal :: proc(v: any, opt: Marshal_Options = {}, allocator := context.allocator) -> (data: []byte, err: Marshal_Error) {
-	b := strings.builder_make(allocator)
+marshal :: proc(v: any, opt: Marshal_Options = {}, allocator := context.allocator, loc := #caller_location) -> (data: []byte, err: Marshal_Error) {
+	b := strings.builder_make(allocator, loc)
 	defer if err != nil {
 	defer if err != nil {
 		strings.builder_destroy(&b)
 		strings.builder_destroy(&b)
 	}
 	}

+ 33 - 32
core/encoding/json/parser.odin

@@ -28,27 +28,27 @@ make_parser_from_string :: proc(data: string, spec := DEFAULT_SPECIFICATION, par
 }
 }
 
 
 
 
-parse :: proc(data: []byte, spec := DEFAULT_SPECIFICATION, parse_integers := false, allocator := context.allocator) -> (Value, Error) {
-	return parse_string(string(data), spec, parse_integers, allocator)
+parse :: proc(data: []byte, spec := DEFAULT_SPECIFICATION, parse_integers := false, allocator := context.allocator, loc := #caller_location) -> (Value, Error) {
+	return parse_string(string(data), spec, parse_integers, allocator, loc)
 }
 }
 
 
-parse_string :: proc(data: string, spec := DEFAULT_SPECIFICATION, parse_integers := false, allocator := context.allocator) -> (Value, Error) {
+parse_string :: proc(data: string, spec := DEFAULT_SPECIFICATION, parse_integers := false, allocator := context.allocator, loc := #caller_location) -> (Value, Error) {
 	context.allocator = allocator
 	context.allocator = allocator
 	p := make_parser_from_string(data, spec, parse_integers, allocator)
 	p := make_parser_from_string(data, spec, parse_integers, allocator)
 
 
 	switch p.spec {
 	switch p.spec {
 	case .JSON:
 	case .JSON:
-		return parse_object(&p)
+		return parse_object(&p, loc)
 	case .JSON5:
 	case .JSON5:
-		return parse_value(&p)
+		return parse_value(&p, loc)
 	case .SJSON:
 	case .SJSON:
 		#partial switch p.curr_token.kind {
 		#partial switch p.curr_token.kind {
 		case .Ident, .String:
 		case .Ident, .String:
-			return parse_object_body(&p, .EOF)
+			return parse_object_body(&p, .EOF, loc)
 		}
 		}
-		return parse_value(&p)
+		return parse_value(&p, loc)
 	}
 	}
-	return parse_object(&p)
+	return parse_object(&p, loc)
 }
 }
 
 
 token_end_pos :: proc(tok: Token) -> Pos {
 token_end_pos :: proc(tok: Token) -> Pos {
@@ -106,7 +106,7 @@ parse_comma :: proc(p: ^Parser) -> (do_break: bool) {
 	return false
 	return false
 }
 }
 
 
-parse_value :: proc(p: ^Parser) -> (value: Value, err: Error) {
+parse_value :: proc(p: ^Parser, loc := #caller_location) -> (value: Value, err: Error) {
 	err = .None
 	err = .None
 	token := p.curr_token
 	token := p.curr_token
 	#partial switch token.kind {
 	#partial switch token.kind {
@@ -142,13 +142,13 @@ parse_value :: proc(p: ^Parser) -> (value: Value, err: Error) {
 		
 		
 	case .String:
 	case .String:
 		advance_token(p)
 		advance_token(p)
-		return unquote_string(token, p.spec, p.allocator)
+		return unquote_string(token, p.spec, p.allocator, loc)
 
 
 	case .Open_Brace:
 	case .Open_Brace:
-		return parse_object(p)
+		return parse_object(p, loc)
 
 
 	case .Open_Bracket:
 	case .Open_Bracket:
-		return parse_array(p)
+		return parse_array(p, loc)
 
 
 	case:
 	case:
 		if p.spec != .JSON {
 		if p.spec != .JSON {
@@ -176,7 +176,7 @@ parse_value :: proc(p: ^Parser) -> (value: Value, err: Error) {
 	return
 	return
 }
 }
 
 
-parse_array :: proc(p: ^Parser) -> (value: Value, err: Error) {
+parse_array :: proc(p: ^Parser, loc := #caller_location) -> (value: Value, err: Error) {
 	err = .None
 	err = .None
 	expect_token(p, .Open_Bracket) or_return
 	expect_token(p, .Open_Bracket) or_return
 
 
@@ -184,14 +184,14 @@ parse_array :: proc(p: ^Parser) -> (value: Value, err: Error) {
 	array.allocator = p.allocator
 	array.allocator = p.allocator
 	defer if err != nil {
 	defer if err != nil {
 		for elem in array {
 		for elem in array {
-			destroy_value(elem)
+			destroy_value(elem, loc=loc)
 		}
 		}
-		delete(array)
+		delete(array, loc)
 	}
 	}
 
 
 	for p.curr_token.kind != .Close_Bracket {
 	for p.curr_token.kind != .Close_Bracket {
-		elem := parse_value(p) or_return
-		append(&array, elem)
+		elem := parse_value(p, loc) or_return
+		append(&array, elem, loc)
 		
 		
 		if parse_comma(p) {
 		if parse_comma(p) {
 			break
 			break
@@ -228,38 +228,39 @@ clone_string :: proc(s: string, allocator: mem.Allocator, loc := #caller_locatio
 	return
 	return
 }
 }
 
 
-parse_object_key :: proc(p: ^Parser, key_allocator: mem.Allocator) -> (key: string, err: Error) {
+parse_object_key :: proc(p: ^Parser, key_allocator: mem.Allocator, loc := #caller_location) -> (key: string, err: Error) {
 	tok := p.curr_token
 	tok := p.curr_token
 	if p.spec != .JSON {
 	if p.spec != .JSON {
 		if allow_token(p, .Ident) {
 		if allow_token(p, .Ident) {
-			return clone_string(tok.text, key_allocator)
+			return clone_string(tok.text, key_allocator, loc)
 		}
 		}
 	}
 	}
 	if tok_err := expect_token(p, .String); tok_err != nil {
 	if tok_err := expect_token(p, .String); tok_err != nil {
 		err = .Expected_String_For_Object_Key
 		err = .Expected_String_For_Object_Key
 		return
 		return
 	}
 	}
-	return unquote_string(tok, p.spec, key_allocator)
+	return unquote_string(tok, p.spec, key_allocator, loc)
 }
 }
 
 
-parse_object_body :: proc(p: ^Parser, end_token: Token_Kind) -> (obj: Object, err: Error) {
-	obj.allocator = p.allocator
+parse_object_body :: proc(p: ^Parser, end_token: Token_Kind, loc := #caller_location) -> (obj: Object, err: Error) {
+	obj = make(Object, allocator=p.allocator, loc=loc)
+
 	defer if err != nil {
 	defer if err != nil {
 		for key, elem in obj {
 		for key, elem in obj {
-			delete(key, p.allocator)
-			destroy_value(elem)
+			delete(key, p.allocator, loc)
+			destroy_value(elem, loc=loc)
 		}
 		}
-		delete(obj)
+		delete(obj, loc)
 	}
 	}
 
 
 	for p.curr_token.kind != end_token {
 	for p.curr_token.kind != end_token {
-		key := parse_object_key(p, p.allocator) or_return
+		key := parse_object_key(p, p.allocator, loc) or_return
 		parse_colon(p) or_return
 		parse_colon(p) or_return
-		elem := parse_value(p) or_return
+		elem := parse_value(p, loc) or_return
 
 
 		if key in obj {
 		if key in obj {
 			err = .Duplicate_Object_Key
 			err = .Duplicate_Object_Key
-			delete(key, p.allocator)
+			delete(key, p.allocator, loc)
 			return
 			return
 		}
 		}
 
 
@@ -267,7 +268,7 @@ parse_object_body :: proc(p: ^Parser, end_token: Token_Kind) -> (obj: Object, er
 		// inserting empty key/values into the object and for those we do not
 		// inserting empty key/values into the object and for those we do not
 		// want to allocate anything
 		// want to allocate anything
 		if key != "" {
 		if key != "" {
-			reserve_error := reserve(&obj, len(obj) + 1)
+			reserve_error := reserve(&obj, len(obj) + 1, loc)
 			if reserve_error == mem.Allocator_Error.Out_Of_Memory {
 			if reserve_error == mem.Allocator_Error.Out_Of_Memory {
 				return nil, .Out_Of_Memory
 				return nil, .Out_Of_Memory
 			}
 			}
@@ -281,9 +282,9 @@ parse_object_body :: proc(p: ^Parser, end_token: Token_Kind) -> (obj: Object, er
 	return obj, .None
 	return obj, .None
 }
 }
 
 
-parse_object :: proc(p: ^Parser) -> (value: Value, err: Error) {
+parse_object :: proc(p: ^Parser, loc := #caller_location) -> (value: Value, err: Error) {
 	expect_token(p, .Open_Brace) or_return
 	expect_token(p, .Open_Brace) or_return
-	obj := parse_object_body(p, .Close_Brace) or_return
+	obj := parse_object_body(p, .Close_Brace, loc) or_return
 	expect_token(p, .Close_Brace) or_return
 	expect_token(p, .Close_Brace) or_return
 	return obj, .None
 	return obj, .None
 }
 }
@@ -480,4 +481,4 @@ unquote_string :: proc(token: Token, spec: Specification, allocator := context.a
 	}
 	}
 
 
 	return string(b[:w]), nil
 	return string(b[:w]), nil
-}
+}

+ 7 - 7
core/encoding/json/types.odin

@@ -89,22 +89,22 @@ Error :: enum {
 
 
 
 
 
 
-destroy_value :: proc(value: Value, allocator := context.allocator) {
+destroy_value :: proc(value: Value, allocator := context.allocator, loc := #caller_location) {
 	context.allocator = allocator
 	context.allocator = allocator
 	#partial switch v in value {
 	#partial switch v in value {
 	case Object:
 	case Object:
 		for key, elem in v {
 		for key, elem in v {
-			delete(key)
-			destroy_value(elem)
+			delete(key, loc=loc)
+			destroy_value(elem, loc=loc)
 		}
 		}
-		delete(v)
+		delete(v, loc=loc)
 	case Array:
 	case Array:
 		for elem in v {
 		for elem in v {
-			destroy_value(elem)
+			destroy_value(elem, loc=loc)
 		}
 		}
-		delete(v)
+		delete(v, loc=loc)
 	case String:
 	case String:
-		delete(v)
+		delete(v, loc=loc)
 	}
 	}
 }
 }
 
 

+ 21 - 5
core/fmt/fmt.odin

@@ -2,6 +2,7 @@ package fmt
 
 
 import "base:intrinsics"
 import "base:intrinsics"
 import "base:runtime"
 import "base:runtime"
+import "core:math"
 import "core:math/bits"
 import "core:math/bits"
 import "core:mem"
 import "core:mem"
 import "core:io"
 import "core:io"
@@ -1494,7 +1495,7 @@ fmt_pointer :: proc(fi: ^Info, p: rawptr, verb: rune) {
 	u := u64(uintptr(p))
 	u := u64(uintptr(p))
 	switch verb {
 	switch verb {
 	case 'p', 'v', 'w':
 	case 'p', 'v', 'w':
-		if !fi.hash && verb == 'v' {
+		if !fi.hash {
 			io.write_string(fi.writer, "0x", &fi.n)
 			io.write_string(fi.writer, "0x", &fi.n)
 		}
 		}
 		_fmt_int(fi, u, 16, false, 8*size_of(rawptr), __DIGITS_UPPER)
 		_fmt_int(fi, u, 16, false, 8*size_of(rawptr), __DIGITS_UPPER)
@@ -2968,6 +2969,21 @@ fmt_value :: proc(fi: ^Info, v: any, verb: rune) {
 		fmt_bit_field(fi, v, verb, info, "")
 		fmt_bit_field(fi, v, verb, info, "")
 	}
 	}
 }
 }
+// This proc helps keep some of the code around whether or not to print an
+// intermediate plus sign in complexes and quaternions more readable.
+@(private)
+_cq_should_print_intermediate_plus :: proc "contextless" (fi: ^Info, f: f64) -> bool {
+	if !fi.plus && f >= 0 {
+		#partial switch math.classify(f) {
+		case .Neg_Zero, .Inf:
+			// These two classes print their own signs.
+			return false
+		case:
+			return true
+		}
+	}
+	return false
+}
 // Formats a complex number based on the given formatting verb
 // Formats a complex number based on the given formatting verb
 //
 //
 // Inputs:
 // Inputs:
@@ -2981,7 +2997,7 @@ fmt_complex :: proc(fi: ^Info, c: complex128, bits: int, verb: rune) {
 	case 'f', 'F', 'v', 'h', 'H', 'w':
 	case 'f', 'F', 'v', 'h', 'H', 'w':
 		r, i := real(c), imag(c)
 		r, i := real(c), imag(c)
 		fmt_float(fi, r, bits/2, verb)
 		fmt_float(fi, r, bits/2, verb)
-		if !fi.plus && i >= 0 {
+		if _cq_should_print_intermediate_plus(fi, i) {
 			io.write_rune(fi.writer, '+', &fi.n)
 			io.write_rune(fi.writer, '+', &fi.n)
 		}
 		}
 		fmt_float(fi, i, bits/2, verb)
 		fmt_float(fi, i, bits/2, verb)
@@ -3007,19 +3023,19 @@ fmt_quaternion  :: proc(fi: ^Info, q: quaternion256, bits: int, verb: rune) {
 
 
 		fmt_float(fi, r, bits/4, verb)
 		fmt_float(fi, r, bits/4, verb)
 
 
-		if !fi.plus && i >= 0 {
+		if _cq_should_print_intermediate_plus(fi, i) {
 			io.write_rune(fi.writer, '+', &fi.n)
 			io.write_rune(fi.writer, '+', &fi.n)
 		}
 		}
 		fmt_float(fi, i, bits/4, verb)
 		fmt_float(fi, i, bits/4, verb)
 		io.write_rune(fi.writer, 'i', &fi.n)
 		io.write_rune(fi.writer, 'i', &fi.n)
 
 
-		if !fi.plus && j >= 0 {
+		if _cq_should_print_intermediate_plus(fi, j) {
 			io.write_rune(fi.writer, '+', &fi.n)
 			io.write_rune(fi.writer, '+', &fi.n)
 		}
 		}
 		fmt_float(fi, j, bits/4, verb)
 		fmt_float(fi, j, bits/4, verb)
 		io.write_rune(fi.writer, 'j', &fi.n)
 		io.write_rune(fi.writer, 'j', &fi.n)
 
 
-		if !fi.plus && k >= 0 {
+		if _cq_should_print_intermediate_plus(fi, k) {
 			io.write_rune(fi.writer, '+', &fi.n)
 			io.write_rune(fi.writer, '+', &fi.n)
 		}
 		}
 		fmt_float(fi, k, bits/4, verb)
 		fmt_float(fi, k, bits/4, verb)

+ 1 - 0
core/fmt/fmt_os.odin

@@ -1,5 +1,6 @@
 //+build !freestanding
 //+build !freestanding
 //+build !js
 //+build !js
+//+build !orca
 package fmt
 package fmt
 
 
 import "base:runtime"
 import "base:runtime"

+ 746 - 0
core/image/bmp/bmp.odin

@@ -0,0 +1,746 @@
+// package bmp implements a Microsoft BMP image reader
+package core_image_bmp
+
+import "core:image"
+import "core:bytes"
+import "core:compress"
+import "core:mem"
+import "base:intrinsics"
+import "base:runtime"
+
+Error   :: image.Error
+Image   :: image.Image
+Options :: image.Options
+
+RGB_Pixel  :: image.RGB_Pixel
+RGBA_Pixel :: image.RGBA_Pixel
+
+FILE_HEADER_SIZE :: 14
+INFO_STUB_SIZE   :: FILE_HEADER_SIZE + size_of(image.BMP_Version)
+
+save_to_buffer  :: proc(output: ^bytes.Buffer, img: ^Image, options := Options{}, allocator := context.allocator) -> (err: Error) {
+	context.allocator = allocator
+
+	if img == nil {
+		return .Invalid_Input_Image
+	}
+
+	if output == nil {
+		return .Invalid_Output
+	}
+
+	pixels := img.width * img.height
+	if pixels == 0 || pixels > image.MAX_DIMENSIONS {
+		return .Invalid_Input_Image
+	}
+
+	// While the BMP spec (and our loader) support more fanciful image types,
+	// `bmp.save` supports only 3 and 4 channel images with a bit depth of 8.
+	if img.depth != 8 || img.channels < 3 || img.channels > 4 {
+		return .Invalid_Input_Image
+	}
+
+	if img.channels * pixels != len(img.pixels.buf) {
+		return .Invalid_Input_Image
+	}
+
+	// Calculate and allocate size.
+	header_size       := u32le(image.BMP_Version.V3)
+	total_header_size := header_size + 14 // file header = 14
+	pixel_count_bytes := u32le(align4(img.width * img.channels) * img.height)
+
+	header := image.BMP_Header{
+		// File header
+		magic            = .Bitmap,
+		size             = total_header_size + pixel_count_bytes,
+		_res1            = 0,
+		_res2            = 0,
+		pixel_offset     = total_header_size,
+		// V3
+		info_size        = .V3,
+		width            = i32le(img.width),
+		height           = i32le(img.height),
+		planes           = 1,
+		bpp              = u16le(8 * img.channels),
+		compression      = .RGB,
+		image_size       = pixel_count_bytes,
+		pels_per_meter   = {2835, 2835}, // 72 DPI
+		colors_used      = 0,
+		colors_important = 0,
+	}
+	written := 0
+
+	if resize(&output.buf, int(header.size)) != nil {
+	 	return .Unable_To_Allocate_Or_Resize
+	}
+
+	header_bytes := transmute([size_of(image.BMP_Header)]u8)header
+	written += int(total_header_size)
+	copy(output.buf[:], header_bytes[:written])
+
+	switch img.channels {
+	case 3:
+		row_bytes  := img.width * img.channels
+		row_padded := align4(row_bytes)
+		pixels := mem.slice_data_cast([]RGB_Pixel, img.pixels.buf[:])
+		for y in 0..<img.height {
+			row_offset := row_padded * (img.height - y - 1) + written
+			for x in 0..<img.width {
+				pix_offset := 3 * x
+				output.buf[row_offset + pix_offset + 0] = pixels[0].b
+				output.buf[row_offset + pix_offset + 1] = pixels[0].g
+				output.buf[row_offset + pix_offset + 2] = pixels[0].r
+				pixels = pixels[1:]
+			}
+		}
+
+	case 4:
+		row_bytes  := img.width * img.channels
+		pixels := mem.slice_data_cast([]RGBA_Pixel, img.pixels.buf[:])
+		for y in 0..<img.height {
+			row_offset := row_bytes * (img.height - y - 1) + written
+			for x in 0..<img.width {
+				pix_offset := 4 * x
+				output.buf[row_offset + pix_offset + 0] = pixels[0].b
+				output.buf[row_offset + pix_offset + 1] = pixels[0].g
+				output.buf[row_offset + pix_offset + 2] = pixels[0].r
+				output.buf[row_offset + pix_offset + 3] = pixels[0].a
+				pixels = pixels[1:]
+			}
+		}
+	}
+	return
+}
+
+
+load_from_bytes :: proc(data: []byte, options := Options{}, allocator := context.allocator) -> (img: ^Image, err: Error) {
+	ctx := &compress.Context_Memory_Input{
+		input_data = data,
+	}
+
+	img, err = load_from_context(ctx, options, allocator)
+	return img, err
+}
+
+@(optimization_mode="speed")
+load_from_context :: proc(ctx: ^$C, options := Options{}, allocator := context.allocator) -> (img: ^Image, err: Error) {
+	context.allocator = allocator
+	options := options
+
+	// For compress.read_slice(), until that's rewritten to not use temp allocator
+	runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD()
+
+	if .info in options {
+		options |= {.return_metadata, .do_not_decompress_image}
+		options -= {.info}
+	}
+
+	if .return_header in options && .return_metadata in options {
+		options -= {.return_header}
+	}
+
+	info_buf: [size_of(image.BMP_Header)]u8
+
+	// Read file header (14) + info size (4)
+	stub_data := compress.read_slice(ctx, INFO_STUB_SIZE) or_return
+	copy(info_buf[:], stub_data[:])
+	stub_info := transmute(image.BMP_Header)info_buf
+
+	if stub_info.magic != .Bitmap {
+		for v in image.BMP_Magic {
+			if stub_info.magic == v {
+				return img, .Unsupported_OS2_File
+			}
+		}
+		return img, .Invalid_Signature
+	}
+
+	info: image.BMP_Header
+	switch stub_info.info_size {
+	case .OS2_v1:
+		// Read the remainder of the header
+		os2_data := compress.read_data(ctx, image.OS2_Header) or_return
+
+		info = transmute(image.BMP_Header)info_buf
+		info.width  = i32le(os2_data.width)
+		info.height = i32le(os2_data.height)
+		info.planes = os2_data.planes
+		info.bpp    = os2_data.bpp
+
+		switch info.bpp {
+		case 1, 4, 8, 24:
+		case:
+			return img, .Unsupported_BPP
+		}
+
+	case .ABBR_16 ..= .V5:
+		// Sizes include V3, V4, V5 and OS2v2 outright, but can also handle truncated headers.
+		// Sometimes called BITMAPV2INFOHEADER or BITMAPV3INFOHEADER.
+		// Let's just try to process it.
+
+		to_read   := int(stub_info.info_size) - size_of(image.BMP_Version)
+		info_data := compress.read_slice(ctx, to_read) or_return
+		copy(info_buf[INFO_STUB_SIZE:], info_data[:])
+
+		// Update info struct with the rest of the data we read
+		info = transmute(image.BMP_Header)info_buf
+
+	case:
+		return img, .Unsupported_BMP_Version
+	}
+
+	/* TODO(Jeroen): Add a "strict" option to catch these non-issues that violate spec?
+	if info.planes != 1 {
+		return img, .Invalid_Planes_Value
+	}
+	*/
+
+	if img == nil {
+		img = new(Image)
+	}
+	img.which = .BMP
+
+	img.metadata = new_clone(image.BMP_Info{
+		info = info,
+	})
+
+	img.width    = abs(int(info.width))
+	img.height   = abs(int(info.height))
+	img.channels = 3
+	img.depth    = 8
+
+	if img.width == 0 || img.height == 0 {
+		return img, .Invalid_Image_Dimensions
+	}
+
+	total_pixels := abs(img.width * img.height)
+	if total_pixels > image.MAX_DIMENSIONS {
+		return img, .Image_Dimensions_Too_Large
+	}
+
+	// TODO(Jeroen): Handle RGBA.
+	switch info.compression {
+	case .Bit_Fields, .Alpha_Bit_Fields:
+		switch info.bpp {
+		case 16, 32:
+			make_output(img, allocator)           or_return
+			decode_rgb(ctx, img, info, allocator) or_return
+		case:
+			if is_os2(info.info_size) {
+				return img, .Unsupported_Compression
+			}
+			return img, .Unsupported_BPP
+		}
+	case .RGB:
+		make_output(img, allocator)           or_return
+		decode_rgb(ctx, img, info, allocator) or_return
+	case .RLE4, .RLE8:
+		make_output(img, allocator)           or_return
+		decode_rle(ctx, img, info, allocator) or_return
+	case .CMYK, .CMYK_RLE4, .CMYK_RLE8: fallthrough
+	case .PNG, .JPEG:                   fallthrough
+	case: return img, .Unsupported_Compression
+	}
+
+	// Flipped vertically
+	if info.height < 0 {
+		pixels := mem.slice_data_cast([]RGB_Pixel, img.pixels.buf[:])
+		for y in 0..<img.height / 2 {
+			for x in 0..<img.width {
+				top := y * img.width + x
+				bot := (img.height - y - 1) * img.width + x
+
+				pixels[top], pixels[bot] = pixels[bot], pixels[top]
+			}
+		}
+	}
+	return
+}
+
+is_os2 :: proc(version: image.BMP_Version) -> (res: bool) {
+	#partial switch version {
+	case .OS2_v1, .OS2_v2: return true
+	case: return false
+	}
+}
+
+make_output :: proc(img: ^Image, allocator := context.allocator) -> (err: Error) {
+	assert(img != nil)
+	bytes_needed := img.channels * img.height * img.width
+	img.pixels.buf = make([dynamic]u8, bytes_needed, allocator)
+	if len(img.pixels.buf) != bytes_needed {
+		return .Unable_To_Allocate_Or_Resize
+	}
+	return
+}
+
+write :: proc(img: ^Image, x, y: int, pix: RGB_Pixel) -> (err: Error) {
+	if y >= img.height || x >= img.width {
+		return .Corrupt
+	}
+	out := mem.slice_data_cast([]RGB_Pixel, img.pixels.buf[:])
+	assert(img.height >= 1 && img.width >= 1)
+	out[(img.height - y - 1) * img.width + x] = pix
+	return
+}
+
+Bitmask :: struct {
+	mask:  [4]u32le `fmt:"b"`,
+	shift: [4]u32le,
+	bits:  [4]u32le,
+}
+
+read_or_make_bit_masks :: proc(ctx: ^$C, info: image.BMP_Header) -> (res: Bitmask, read: int, err: Error) {
+	ctz :: intrinsics.count_trailing_zeros
+	c1s :: intrinsics.count_ones
+
+	#partial switch info.compression {
+	case .RGB:
+		switch info.bpp {
+		case 16:
+			return {
+				mask  = {31 << 10, 31 << 5, 31, 0},
+				shift = {      10,       5,  0, 0},
+				bits  = {       5,       5,  5, 0},
+			}, int(4 * info.colors_used), nil
+
+		case 32:
+			return {
+				mask  = {255 << 16, 255 << 8, 255, 255 << 24},
+				shift = {       16,        8,        0,   24},
+				bits  = {        8,        8,        8,    8},
+			}, int(4 * info.colors_used), nil
+
+		case: return {}, 0, .Unsupported_BPP
+		}
+	case .Bit_Fields, .Alpha_Bit_Fields:
+		bf := info.masks
+		alpha_mask := false
+		bit_count: u32le
+
+		#partial switch info.info_size {
+		case .ABBR_52 ..= .V5:
+			// All possible BMP header sizes 52+ bytes long, includes V4 + V5
+			// Bit fields were read as part of the header
+			// V3 header is 40 bytes. We need 56 at a minimum for RGBA bit fields in the next section.
+			if info.info_size >= .ABBR_56 {
+				alpha_mask = true
+			}
+
+		case .V3:
+			// Version 3 doesn't have a bit field embedded, but can still have a 3 or 4 color bit field.
+			// Because it wasn't read as part of the header, we need to read it now.
+
+			if info.compression == .Alpha_Bit_Fields {
+				bf = compress.read_data(ctx, [4]u32le) or_return
+				alpha_mask = true
+				read = 16
+			} else {
+				bf.xyz = compress.read_data(ctx, [3]u32le) or_return
+				read = 12
+			}
+
+		case:
+			// Bit fields are unhandled for this BMP version
+			return {}, 0, .Bitfield_Version_Unhandled
+		}
+
+		if alpha_mask {
+			res = {
+				mask  = {bf.r,      bf.g,      bf.b,      bf.a},
+				shift = {ctz(bf.r), ctz(bf.g), ctz(bf.b), ctz(bf.a)},
+				bits  = {c1s(bf.r), c1s(bf.g), c1s(bf.b), c1s(bf.a)},
+			}
+
+			bit_count = res.bits.r + res.bits.g + res.bits.b + res.bits.a
+		} else {
+			res = {
+				mask  = {bf.r,      bf.g,      bf.b,      0},
+				shift = {ctz(bf.r), ctz(bf.g), ctz(bf.b), 0},
+				bits  = {c1s(bf.r), c1s(bf.g), c1s(bf.b), 0},
+			}
+
+			bit_count = res.bits.r + res.bits.g + res.bits.b
+		}
+
+		if bit_count > u32le(info.bpp) {
+			err = .Bitfield_Sum_Exceeds_BPP
+		}
+
+		overlapped := res.mask.r | res.mask.g | res.mask.b | res.mask.a
+		if c1s(overlapped) < bit_count {
+			err = .Bitfield_Overlapped
+		}
+		return res, read, err
+
+	case:
+		return {}, 0, .Unsupported_Compression
+	}
+	return
+}
+
+scale :: proc(val: $T, mask, shift, bits: u32le) -> (res: u8) {
+	if bits == 0 { return 0 } // Guard against malformed bit fields
+	v := (u32le(val) & mask) >> shift
+	mask_in := u32le(1 << bits) - 1
+	return u8(v * 255 / mask_in)
+}
+
+decode_rgb :: proc(ctx: ^$C, img: ^Image, info: image.BMP_Header, allocator := context.allocator) -> (err: Error) {
+	pixel_offset := int(info.pixel_offset)
+	pixel_offset -= int(info.info_size) + FILE_HEADER_SIZE
+
+	palette: [256]RGBA_Pixel
+
+	// Palette size is info.colors_used if populated. If not it's min(1 << bpp, offset to the pixels / channel count)
+	colors_used := min(256, 1 << info.bpp if info.colors_used == 0 else info.colors_used)
+	max_colors  := pixel_offset / 3 if info.info_size == .OS2_v1 else pixel_offset / 4
+	colors_used  = min(colors_used, u32le(max_colors))
+
+	switch info.bpp {
+	case 1:
+		if info.info_size == .OS2_v1 {
+			// 2 x RGB palette of instead of variable RGBA palette
+			for i in 0..<colors_used {
+				palette[i].rgb = image.read_data(ctx, RGB_Pixel) or_return
+			}
+			pixel_offset -= int(3 * colors_used)
+		} else {
+			for i in 0..<colors_used {
+				palette[i] = image.read_data(ctx, RGBA_Pixel) or_return
+			}
+			pixel_offset -= int(4 * colors_used)
+		}
+		skip_space(ctx, pixel_offset)
+
+		stride := (img.width + 7) / 8
+		for y in 0..<img.height {
+			data := compress.read_slice(ctx, stride) or_return
+			for x in 0..<img.width {
+				shift := u8(7 - (x & 0x07))
+				p := (data[x / 8] >> shift) & 0x01
+				write(img, x, y, palette[p].bgr) or_return
+			}
+		}
+
+	case 2: // Non-standard on modern Windows, but was allowed on WinCE
+		for i in 0..<colors_used {
+			palette[i] = image.read_data(ctx, RGBA_Pixel) or_return
+		}
+		pixel_offset -= int(4 * colors_used)
+		skip_space(ctx, pixel_offset)
+
+		stride := (img.width + 3) / 4
+		for y in 0..<img.height {
+			data := compress.read_slice(ctx, stride) or_return
+			for x in 0..<img.width {
+				shift := 6 - (x & 0x03) << 1
+				p := (data[x / 4] >> u8(shift)) & 0x03
+				write(img, x, y, palette[p].bgr) or_return
+			}
+		}
+
+	case 4:
+		if info.info_size == .OS2_v1 {
+			// 16 x RGB palette of instead of variable RGBA palette
+			for i in 0..<colors_used {
+				palette[i].rgb = image.read_data(ctx, RGB_Pixel) or_return
+			}
+			pixel_offset -= int(3 * colors_used)
+		} else {
+			for i in 0..<colors_used {
+				palette[i] = image.read_data(ctx, RGBA_Pixel) or_return
+			}
+			pixel_offset -= int(4 * colors_used)
+		}
+		skip_space(ctx, pixel_offset)
+
+		stride := (img.width + 1) / 2
+		for y in 0..<img.height {
+			data := compress.read_slice(ctx, stride) or_return
+			for x in 0..<img.width {
+				p := data[x / 2] >> 4 if x & 1 == 0 else data[x / 2]
+				write(img, x, y, palette[p & 0x0f].bgr) or_return
+			}
+		}
+
+	case 8:
+		if info.info_size == .OS2_v1 {
+			// 256 x RGB palette of instead of variable RGBA palette
+			for i in 0..<colors_used {
+				palette[i].rgb = image.read_data(ctx, RGB_Pixel) or_return
+			}
+			pixel_offset -= int(3 * colors_used)
+		} else {
+			for i in 0..<colors_used {
+				palette[i] = image.read_data(ctx, RGBA_Pixel) or_return
+			}
+			pixel_offset -= int(4 * colors_used)
+		}
+		skip_space(ctx, pixel_offset)
+
+		stride := align4(img.width)
+		for y in 0..<img.height {
+			data := compress.read_slice(ctx, stride) or_return
+			for x in 0..<img.width {
+				write(img, x, y, palette[data[x]].bgr) or_return
+			}
+		}
+
+	case 16:
+		bm, read := read_or_make_bit_masks(ctx, info) or_return
+		// Skip optional palette and other data
+		pixel_offset -= read
+		skip_space(ctx, pixel_offset)
+
+		stride := align4(img.width * 2)
+		for y in 0..<img.height {
+			data   := compress.read_slice(ctx, stride) or_return
+			pixels := mem.slice_data_cast([]u16le, data)
+			for x in 0..<img.width {
+				v := pixels[x]
+				r := scale(v, bm.mask.r, bm.shift.r, bm.bits.r)
+				g := scale(v, bm.mask.g, bm.shift.g, bm.bits.g)
+				b := scale(v, bm.mask.b, bm.shift.b, bm.bits.b)
+				write(img, x, y, RGB_Pixel{r, g, b}) or_return
+			}
+		}
+
+	case 24:
+		// Eat useless palette and other padding
+		skip_space(ctx, pixel_offset)
+
+		stride := align4(img.width * 3)
+		for y in 0..<img.height {
+			data   := compress.read_slice(ctx, stride) or_return
+			pixels := mem.slice_data_cast([]RGB_Pixel, data)
+			for x in 0..<img.width {
+				write(img, x, y, pixels[x].bgr) or_return
+			}
+		}
+
+	case 32:
+		bm, read := read_or_make_bit_masks(ctx, info) or_return
+		// Skip optional palette and other data
+		pixel_offset -= read
+		skip_space(ctx, pixel_offset)
+
+		for y in 0..<img.height {
+			data   := compress.read_slice(ctx, img.width * size_of(RGBA_Pixel)) or_return
+			pixels := mem.slice_data_cast([]u32le, data)
+			for x in 0..<img.width {
+				v := pixels[x]
+				r := scale(v, bm.mask.r, bm.shift.r, bm.bits.r)
+				g := scale(v, bm.mask.g, bm.shift.g, bm.bits.g)
+				b := scale(v, bm.mask.b, bm.shift.b, bm.bits.b)
+				write(img, x, y, RGB_Pixel{r, g, b}) or_return
+			}
+		}
+
+	case:
+		return .Unsupported_BPP
+	}
+	return nil
+}
+
+decode_rle :: proc(ctx: ^$C, img: ^Image, info: image.BMP_Header, allocator := context.allocator) -> (err: Error) {
+	pixel_offset := int(info.pixel_offset)
+	pixel_offset -= int(info.info_size) + FILE_HEADER_SIZE
+
+	bytes_needed := size_of(RGB_Pixel) * img.height * img.width
+	if resize(&img.pixels.buf, bytes_needed) != nil {
+		return .Unable_To_Allocate_Or_Resize
+	}
+	out := mem.slice_data_cast([]RGB_Pixel, img.pixels.buf[:])
+	assert(len(out) == img.height * img.width)
+
+	palette: [256]RGBA_Pixel
+
+	switch info.bpp {
+	case 4:
+		colors_used := info.colors_used if info.colors_used > 0 else 16
+		colors_used  = min(colors_used, 16)
+
+		for i in 0..<colors_used {
+			palette[i] = image.read_data(ctx, RGBA_Pixel) or_return
+			pixel_offset -= size_of(RGBA_Pixel)
+		}
+		skip_space(ctx, pixel_offset)
+
+		pixel_size := info.size - info.pixel_offset
+		remaining  := compress.input_size(ctx) or_return
+		if remaining < i64(pixel_size) {
+			return .Corrupt
+		}
+
+		data := make([]u8, int(pixel_size) + 4)
+		defer delete(data)
+
+		for i in 0..<pixel_size {
+			data[i] = image.read_u8(ctx) or_return
+		}
+
+		y, x := 0, 0
+		index := 0
+		for {
+			if len(data[index:]) < 2 {
+				return .Corrupt
+			}
+
+			if data[index] > 0 {
+				for count in 0..<data[index] {
+					if count & 1 == 1 {
+						write(img, x, y, palette[(data[index + 1] >> 0) & 0x0f].bgr)
+					} else {
+						write(img, x, y, palette[(data[index + 1] >> 4) & 0x0f].bgr)
+					}
+					x += 1
+				}
+				index += 2
+			} else {
+				switch data[index + 1] {
+				case 0: // EOL
+					x = 0; y += 1
+					index += 2
+				case 1: // EOB
+					return
+				case 2:	// MOVE
+					x += int(data[index + 2])
+					y += int(data[index + 3])
+					index += 4
+				case:   // Literals
+					run_length := int(data[index + 1])
+					aligned    := (align4(run_length) >> 1) + 2
+
+					if index + aligned >= len(data) {
+						return .Corrupt
+					}
+
+					for count in 0..<run_length {
+						val := data[index + 2 + count / 2]
+						if count & 1 == 1 {
+							val &= 0xf
+						} else {
+							val  = val >> 4
+						}
+						write(img, x, y, palette[val].bgr)
+						x += 1
+					}
+					index += aligned
+				}
+			}
+		}
+
+	case 8:
+		colors_used := info.colors_used if info.colors_used > 0 else 256
+		colors_used  = min(colors_used, 256)
+
+		for i in 0..<colors_used {
+			palette[i] = image.read_data(ctx, RGBA_Pixel) or_return
+			pixel_offset -= size_of(RGBA_Pixel)
+		}
+		skip_space(ctx, pixel_offset)
+
+		pixel_size := info.size - info.pixel_offset
+		remaining  := compress.input_size(ctx) or_return
+		if remaining < i64(pixel_size) {
+			return .Corrupt
+		}
+
+		data := make([]u8, int(pixel_size) + 4)
+		defer delete(data)
+
+		for i in 0..<pixel_size {
+			data[i] = image.read_u8(ctx) or_return
+		}
+
+		y, x := 0, 0
+		index := 0
+		for {
+			if len(data[index:]) < 2 {
+				return .Corrupt
+			}
+
+			if data[index] > 0 {
+				for _ in 0..<data[index] {
+					write(img, x, y, palette[data[index + 1]].bgr)
+					x += 1
+				}
+				index += 2
+			} else {
+				switch data[index + 1] {
+				case 0: // EOL
+					x = 0; y += 1
+					index += 2
+				case 1: // EOB
+					return
+				case 2:	// MOVE
+					x += int(data[index + 2])
+					y += int(data[index + 3])
+					index += 4
+				case:   // Literals
+					run_length := int(data[index + 1])
+					aligned    := align2(run_length) + 2
+
+					if index + aligned >= len(data) {
+						return .Corrupt
+					}
+					for count in 0..<run_length {
+						write(img, x, y, palette[data[index + 2 + count]].bgr)
+						x += 1
+					}
+					index += aligned
+				}
+			}
+		}
+
+	case:
+		return .Unsupported_BPP
+	}
+	return nil
+}
+
+align2 :: proc(width: int) -> (stride: int) {
+	stride = width
+	if width & 1 != 0 {
+		stride += 2 - (width & 1)
+	}
+	return
+}
+
+align4 :: proc(width: int) -> (stride: int) {
+	stride = width
+	if width & 3 != 0 {
+		stride += 4 - (width & 3)
+	}
+	return
+}
+
+skip_space :: proc(ctx: ^$C, bytes_to_skip: int) -> (err: Error) {
+	if bytes_to_skip < 0 {
+		return .Corrupt
+	}
+	for _ in 0..<bytes_to_skip {
+		image.read_u8(ctx) or_return
+	}
+	return
+}
+
+// Cleanup of image-specific data.
+destroy :: proc(img: ^Image) {
+	if img == nil {
+		// Nothing to do. Load must've returned with an error.
+		return
+	}
+
+	bytes.buffer_destroy(&img.pixels)
+	if v, ok := img.metadata.(^image.BMP_Info); ok {
+	 	free(v)
+	}
+	free(img)
+}
+
+@(init, private)
+_register :: proc() {
+	image.register(.BMP, load_from_bytes, destroy)
+}

+ 4 - 0
core/image/bmp/bmp_js.odin

@@ -0,0 +1,4 @@
+//+build js
+package core_image_bmp
+
+load :: proc{load_from_bytes, load_from_context}

+ 34 - 0
core/image/bmp/bmp_os.odin

@@ -0,0 +1,34 @@
+//+build !js
+package core_image_bmp
+
+import "core:os"
+import "core:bytes"
+
+load :: proc{load_from_file, load_from_bytes, load_from_context}
+
+load_from_file :: proc(filename: string, options := Options{}, allocator := context.allocator) -> (img: ^Image, err: Error) {
+	context.allocator = allocator
+
+	data, ok := os.read_entire_file(filename)
+	defer delete(data)
+
+	if ok {
+		return load_from_bytes(data, options)
+	} else {
+		return nil, .Unable_To_Read_File
+	}
+}
+
+save :: proc{save_to_buffer, save_to_file}
+
+save_to_file :: proc(output: string, img: ^Image, options := Options{}, allocator := context.allocator) -> (err: Error) {
+	context.allocator = allocator
+
+	out := &bytes.Buffer{}
+	defer bytes.buffer_destroy(out)
+
+	save_to_buffer(out, img, options) or_return
+	write_ok := os.write_entire_file(output, out.buf[:])
+
+	return nil if write_ok else .Unable_To_Write_File
+}

+ 161 - 1
core/image/common.odin

@@ -12,6 +12,7 @@ package image
 
 
 import "core:bytes"
 import "core:bytes"
 import "core:mem"
 import "core:mem"
+import "core:io"
 import "core:compress"
 import "core:compress"
 import "base:runtime"
 import "base:runtime"
 
 
@@ -62,6 +63,7 @@ Image_Metadata :: union #shared_nil {
 	^PNG_Info,
 	^PNG_Info,
 	^QOI_Info,
 	^QOI_Info,
 	^TGA_Info,
 	^TGA_Info,
+	^BMP_Info,
 }
 }
 
 
 
 
@@ -159,11 +161,13 @@ Error :: union #shared_nil {
 	Netpbm_Error,
 	Netpbm_Error,
 	PNG_Error,
 	PNG_Error,
 	QOI_Error,
 	QOI_Error,
+	BMP_Error,
 
 
 	compress.Error,
 	compress.Error,
 	compress.General_Error,
 	compress.General_Error,
 	compress.Deflate_Error,
 	compress.Deflate_Error,
 	compress.ZLIB_Error,
 	compress.ZLIB_Error,
+	io.Error,
 	runtime.Allocator_Error,
 	runtime.Allocator_Error,
 }
 }
 
 
@@ -196,6 +200,128 @@ General_Image_Error :: enum {
 	Unable_To_Allocate_Or_Resize,
 	Unable_To_Allocate_Or_Resize,
 }
 }
 
 
+/*
+	BMP-specific
+*/
+BMP_Error :: enum {
+	None = 0,
+	Invalid_File_Size,
+	Unsupported_BMP_Version,
+	Unsupported_OS2_File,
+	Unsupported_Compression,
+	Unsupported_BPP,
+	Invalid_Stride,
+	Invalid_Color_Count,
+	Implausible_File_Size,
+	Bitfield_Version_Unhandled, // We don't (yet) handle bit fields for this BMP version.
+	Bitfield_Sum_Exceeds_BPP,   // Total mask bit count > bpp
+	Bitfield_Overlapped,        // Channel masks overlap
+}
+
+// img.metadata is wrapped in a struct in case we need to add to it later
+// without putting it in BMP_Header
+BMP_Info :: struct {
+	info: BMP_Header,
+}
+
+BMP_Magic :: enum u16le {
+	Bitmap            = 0x4d42, // 'BM'
+	OS2_Bitmap_Array  = 0x4142, // 'BA'
+	OS2_Icon          = 0x4349, // 'IC',
+	OS2_Color_Icon    = 0x4943, // 'CI'
+	OS2_Pointer       = 0x5450, // 'PT'
+	OS2_Color_Pointer = 0x5043, // 'CP'
+}
+
+// See: http://justsolve.archiveteam.org/wiki/BMP#Well-known_versions
+BMP_Version :: enum u32le {
+	OS2_v1    = 12,  // BITMAPCOREHEADER  (Windows V2 / OS/2 version 1.0)
+	OS2_v2    = 64,  // BITMAPCOREHEADER2 (OS/2 version 2.x)
+	V3        = 40,  // BITMAPINFOHEADER
+	V4        = 108, // BITMAPV4HEADER
+	V5        = 124, // BITMAPV5HEADER
+
+	ABBR_16   = 16,  // Abbreviated
+	ABBR_24   = 24,  // ..
+	ABBR_48   = 48,  // ..
+	ABBR_52   = 52,  // ..
+	ABBR_56   = 56,  // ..
+}
+
+BMP_Header :: struct #packed {
+	// File header
+	magic:            BMP_Magic,
+	size:             u32le,
+	_res1:            u16le, // Reserved; must be zero
+	_res2:            u16le, // Reserved; must be zero
+	pixel_offset:     u32le, // Offset in bytes, from the beginning of BMP_Header to the pixel data
+	// V3
+	info_size:        BMP_Version,
+	width:            i32le,
+	height:           i32le,
+	planes:           u16le,
+	bpp:              u16le,
+	compression:      BMP_Compression,
+	image_size:       u32le,
+	pels_per_meter:   [2]u32le,
+	colors_used:      u32le,
+	colors_important: u32le, // OS2_v2 is equal up to here
+	// V4
+	masks:            [4]u32le `fmt:"32b"`,
+	colorspace:       BMP_Logical_Color_Space,
+	endpoints:        BMP_CIEXYZTRIPLE,
+	gamma:            [3]BMP_GAMMA16_16,
+	// V5
+	intent:           BMP_Gamut_Mapping_Intent,
+	profile_data:     u32le,
+	profile_size:     u32le,
+	reserved:         u32le,
+}
+#assert(size_of(BMP_Header) == 138)
+
+OS2_Header :: struct #packed {
+	// BITMAPCOREHEADER minus info_size field
+	width:            i16le,
+	height:           i16le,
+	planes:           u16le,
+	bpp:              u16le,
+}
+#assert(size_of(OS2_Header) == 8)
+
+BMP_Compression :: enum u32le {
+	RGB              = 0x0000,
+	RLE8             = 0x0001,
+	RLE4             = 0x0002,
+	Bit_Fields       = 0x0003, // If Windows
+	Huffman1D        = 0x0003, // If OS2v2
+	JPEG             = 0x0004, // If Windows
+	RLE24            = 0x0004, // If OS2v2
+	PNG              = 0x0005,
+	Alpha_Bit_Fields = 0x0006,
+	CMYK             = 0x000B,
+	CMYK_RLE8        = 0x000C,
+	CMYK_RLE4        = 0x000D,
+}
+
+BMP_Logical_Color_Space :: enum u32le {
+	CALIBRATED_RGB      = 0x00000000,
+	sRGB                = 0x73524742, // 'sRGB'
+	WINDOWS_COLOR_SPACE = 0x57696E20, // 'Win '
+}
+
+BMP_FXPT2DOT30   :: u32le
+BMP_CIEXYZ       :: [3]BMP_FXPT2DOT30
+BMP_CIEXYZTRIPLE :: [3]BMP_CIEXYZ
+BMP_GAMMA16_16   :: [2]u16le
+
+BMP_Gamut_Mapping_Intent :: enum u32le {
+	INVALID          = 0x00000000, // If not V5, this field will just be zero-initialized and not valid.
+	ABS_COLORIMETRIC = 0x00000008,
+	BUSINESS         = 0x00000001,
+	GRAPHICS         = 0x00000002,
+	IMAGES           = 0x00000004,
+}
+
 /*
 /*
 	Netpbm-specific definitions
 	Netpbm-specific definitions
 */
 */
@@ -1133,6 +1259,40 @@ apply_palette_rgba :: proc(img: ^Image, palette: [256]RGBA_Pixel, allocator := c
 }
 }
 apply_palette :: proc{apply_palette_rgb, apply_palette_rgba}
 apply_palette :: proc{apply_palette_rgb, apply_palette_rgba}
 
 
+blend_single_channel :: #force_inline proc(fg, alpha, bg: $T) -> (res: T) where T == u8 || T == u16 {
+	MAX :: 256 when T == u8 else 65536
+
+	c := u32(fg) * (MAX - u32(alpha)) + u32(bg) * (1 + u32(alpha))
+	return T(c & (MAX - 1))
+}
+
+blend_pixel :: #force_inline proc(fg: [$N]$T, alpha: T, bg: [N]T) -> (res: [N]T) where (T == u8 || T == u16), N >= 1 && N <= 4 {
+	MAX :: 256 when T == u8 else 65536
+
+	when N == 1 {
+		r := u32(fg.r) * (MAX - u32(alpha)) + u32(bg.r) * (1 + u32(alpha))
+		return {T(r & (MAX - 1))}
+	}
+	when N == 2 {
+		r := u32(fg.r) * (MAX - u32(alpha)) + u32(bg.r) * (1 + u32(alpha))
+		g := u32(fg.g) * (MAX - u32(alpha)) + u32(bg.g) * (1 + u32(alpha))
+		return {T(r & (MAX - 1)), T(g & (MAX - 1))}
+	}
+	when N == 3 || N == 4 {
+		r := u32(fg.r) * (MAX - u32(alpha)) + u32(bg.r) * (1 + u32(alpha))
+		g := u32(fg.g) * (MAX - u32(alpha)) + u32(bg.g) * (1 + u32(alpha))
+		b := u32(fg.b) * (MAX - u32(alpha)) + u32(bg.b) * (1 + u32(alpha))
+
+		when N == 3 {
+			return {T(r & (MAX - 1)), T(g & (MAX - 1)), T(b & (MAX - 1))}
+		} else {
+			return {T(r & (MAX - 1)), T(g & (MAX - 1)), T(b & (MAX - 1)), MAX - 1}
+		}
+	}
+	unreachable()
+}
+blend :: proc{blend_single_channel, blend_pixel}
+
 
 
 // Replicates grayscale values into RGB(A) 8- or 16-bit images as appropriate.
 // Replicates grayscale values into RGB(A) 8- or 16-bit images as appropriate.
 // Returns early with `false` if already an RGB(A) image.
 // Returns early with `false` if already an RGB(A) image.
@@ -1245,4 +1405,4 @@ write_bytes :: proc(buf: ^bytes.Buffer, data: []u8) -> (err: compress.General_Er
 		return .Resize_Failed
 		return .Resize_Failed
 	}
 	}
 	return nil
 	return nil
-}
+}

+ 27 - 39
core/image/png/png.odin

@@ -597,7 +597,7 @@ load_from_context :: proc(ctx: ^$C, options := Options{}, allocator := context.a
 				dsc   := depth_scale_table
 				dsc   := depth_scale_table
 				scale := dsc[info.header.bit_depth]
 				scale := dsc[info.header.bit_depth]
 				if scale != 1 {
 				if scale != 1 {
-					key := mem.slice_data_cast([]u16be, c.data)[0] * u16be(scale)
+					key := (^u16be)(raw_data(c.data))^ * u16be(scale)
 					c.data = []u8{0, u8(key & 255)}
 					c.data = []u8{0, u8(key & 255)}
 				}
 				}
 			}
 			}
@@ -735,59 +735,48 @@ load_from_context :: proc(ctx: ^$C, options := Options{}, allocator := context.a
 			return {}, .Unable_To_Allocate_Or_Resize
 			return {}, .Unable_To_Allocate_Or_Resize
 		}
 		}
 
 
-		i := 0; j := 0
-
 		// If we don't have transparency or drop it without applying it, we can do this:
 		// If we don't have transparency or drop it without applying it, we can do this:
 		if (!seen_trns || (seen_trns && .alpha_drop_if_present in options && .alpha_premultiply not_in options)) && .alpha_add_if_missing not_in options {
 		if (!seen_trns || (seen_trns && .alpha_drop_if_present in options && .alpha_premultiply not_in options)) && .alpha_add_if_missing not_in options {
-			for h := 0; h < int(img.height); h += 1 {
-				for w := 0; w < int(img.width);  w += 1 {
-					c := _plte.entries[temp.buf[i]]
-					t.buf[j  ] = c.r
-					t.buf[j+1] = c.g
-					t.buf[j+2] = c.b
-					i += 1; j += 3
-				}
+			output := mem.slice_data_cast([]image.RGB_Pixel, t.buf[:])
+			for pal_idx, idx in temp.buf {
+				output[idx] = _plte.entries[pal_idx]
 			}
 			}
 		} else if add_alpha || .alpha_drop_if_present in options {
 		} else if add_alpha || .alpha_drop_if_present in options {
-			bg := [3]f32{0, 0, 0}
+			bg := PLTE_Entry{0, 0, 0}
 			if premultiply && seen_bkgd {
 			if premultiply && seen_bkgd {
 				c16 := img.background.([3]u16)
 				c16 := img.background.([3]u16)
-				bg = [3]f32{f32(c16.r), f32(c16.g), f32(c16.b)}
+				bg = {u8(c16.r), u8(c16.g), u8(c16.b)}
 			}
 			}
 
 
 			no_alpha := (.alpha_drop_if_present in options || premultiply) && .alpha_add_if_missing not_in options
 			no_alpha := (.alpha_drop_if_present in options || premultiply) && .alpha_add_if_missing not_in options
 			blend_background := seen_bkgd && .blend_background in options
 			blend_background := seen_bkgd && .blend_background in options
 
 
-			for h := 0; h < int(img.height); h += 1 {
-				for w := 0; w < int(img.width);  w += 1 {
-					index := temp.buf[i]
+			if no_alpha {
+				output := mem.slice_data_cast([]image.RGB_Pixel, t.buf[:])
+				for orig, idx in temp.buf {
+					c := _plte.entries[orig]
+					a := int(orig) < len(trns.data) ? trns.data[orig] : 255
 
 
-					c     := _plte.entries[index]
-					a     := int(index) < len(trns.data) ? trns.data[index] : 255
-					alpha := f32(a) / 255.0
+					if blend_background {
+						output[idx] = image.blend(c, a, bg)
+					} else if premultiply {
+						output[idx] = image.blend(PLTE_Entry{}, a, c)
+					}
+				}
+			} else {
+				output := mem.slice_data_cast([]image.RGBA_Pixel, t.buf[:])
+				for orig, idx in temp.buf {
+					c := _plte.entries[orig]
+					a := int(orig) < len(trns.data) ? trns.data[orig] : 255
 
 
 					if blend_background {
 					if blend_background {
-						c.r = u8((1.0 - alpha) * bg[0] + f32(c.r) * alpha)
-						c.g = u8((1.0 - alpha) * bg[1] + f32(c.g) * alpha)
-						c.b = u8((1.0 - alpha) * bg[2] + f32(c.b) * alpha)
+						c = image.blend(c, a, bg)
 						a = 255
 						a = 255
 					} else if premultiply {
 					} else if premultiply {
-						c.r = u8(f32(c.r) * alpha)
-						c.g = u8(f32(c.g) * alpha)
-						c.b = u8(f32(c.b) * alpha)
+						c = image.blend(PLTE_Entry{}, a, c)
 					}
 					}
 
 
-					t.buf[j  ] = c.r
-					t.buf[j+1] = c.g
-					t.buf[j+2] = c.b
-					i += 1
-
-					if no_alpha {
-						j += 3
-					} else {
-						t.buf[j+3] = u8(a)
-						j += 4
-					}
+					output[idx] = {c.r, c.g, c.b, u8(a)}
 				}
 				}
 			}
 			}
 		} else {
 		} else {
@@ -1015,8 +1004,8 @@ load_from_context :: proc(ctx: ^$C, options := Options{}, allocator := context.a
 			return {}, .Unable_To_Allocate_Or_Resize
 			return {}, .Unable_To_Allocate_Or_Resize
 		}
 		}
 
 
-		p := mem.slice_data_cast([]u8, temp.buf[:])
-		o := mem.slice_data_cast([]u8, t.buf[:])
+		p := temp.buf[:]
+		o := t.buf[:]
 
 
 		switch raw_image_channels {
 		switch raw_image_channels {
 		case 1:
 		case 1:
@@ -1627,7 +1616,6 @@ defilter :: proc(img: ^Image, filter_bytes: ^bytes.Buffer, header: ^image.PNG_IH
 	return nil
 	return nil
 }
 }
 
 
-
 @(init, private)
 @(init, private)
 _register :: proc() {
 _register :: proc() {
 	image.register(.PNG, load_from_bytes, destroy)
 	image.register(.PNG, load_from_bytes, destroy)

+ 26 - 15
core/log/file_console_logger.odin

@@ -1,6 +1,7 @@
 //+build !freestanding
 //+build !freestanding
 package log
 package log
 
 
+import "core:encoding/ansi"
 import "core:fmt"
 import "core:fmt"
 import "core:strings"
 import "core:strings"
 import "core:os"
 import "core:os"
@@ -70,18 +71,10 @@ file_console_logger_proc :: proc(logger_data: rawptr, level: Level, text: string
 	backing: [1024]byte //NOTE(Hoej): 1024 might be too much for a header backing, unless somebody has really long paths.
 	backing: [1024]byte //NOTE(Hoej): 1024 might be too much for a header backing, unless somebody has really long paths.
 	buf := strings.builder_from_bytes(backing[:])
 	buf := strings.builder_from_bytes(backing[:])
 
 
-	do_level_header(options, level, &buf)
+	do_level_header(options, &buf, level)
 
 
 	when time.IS_SUPPORTED {
 	when time.IS_SUPPORTED {
-		if Full_Timestamp_Opts & options != nil {
-			fmt.sbprint(&buf, "[")
-			t := time.now()
-			y, m, d := time.date(t)
-			h, min, s := time.clock(t)
-			if .Date in options { fmt.sbprintf(&buf, "%d-%02d-%02d ", y, m, d)    }
-			if .Time in options { fmt.sbprintf(&buf, "%02d:%02d:%02d", h, min, s) }
-			fmt.sbprint(&buf, "] ")
-		}
+		do_time_header(options, &buf, time.now())
 	}
 	}
 
 
 	do_location_header(options, &buf, location)
 	do_location_header(options, &buf, location)
@@ -99,12 +92,12 @@ file_console_logger_proc :: proc(logger_data: rawptr, level: Level, text: string
 	fmt.fprintf(h, "%s%s\n", strings.to_string(buf), text)
 	fmt.fprintf(h, "%s%s\n", strings.to_string(buf), text)
 }
 }
 
 
-do_level_header :: proc(opts: Options, level: Level, str: ^strings.Builder) {
+do_level_header :: proc(opts: Options, str: ^strings.Builder, level: Level) {
 
 
-	RESET     :: "\x1b[0m"
-	RED       :: "\x1b[31m"
-	YELLOW    :: "\x1b[33m"
-	DARK_GREY :: "\x1b[90m"
+	RESET     :: ansi.CSI + ansi.RESET           + ansi.SGR
+	RED       :: ansi.CSI + ansi.FG_RED          + ansi.SGR
+	YELLOW    :: ansi.CSI + ansi.FG_YELLOW       + ansi.SGR
+	DARK_GREY :: ansi.CSI + ansi.FG_BRIGHT_BLACK + ansi.SGR
 
 
 	col := RESET
 	col := RESET
 	switch level {
 	switch level {
@@ -125,6 +118,24 @@ do_level_header :: proc(opts: Options, level: Level, str: ^strings.Builder) {
 	}
 	}
 }
 }
 
 
+do_time_header :: proc(opts: Options, buf: ^strings.Builder, t: time.Time) {
+	when time.IS_SUPPORTED {
+		if Full_Timestamp_Opts & opts != nil {
+			fmt.sbprint(buf, "[")
+			y, m, d := time.date(t)
+			h, min, s := time.clock(t)
+			if .Date in opts {
+				fmt.sbprintf(buf, "%d-%02d-%02d", y, m, d)
+				if .Time in opts {
+					fmt.sbprint(buf, " ")
+				}
+			}
+			if .Time in opts { fmt.sbprintf(buf, "%02d:%02d:%02d", h, min, s) }
+			fmt.sbprint(buf, "] ")
+		}
+	}
+}
+
 do_location_header :: proc(opts: Options, buf: ^strings.Builder, location := #caller_location) {
 do_location_header :: proc(opts: Options, buf: ^strings.Builder, location := #caller_location) {
 	if Location_Header_Opts & opts == nil {
 	if Location_Header_Opts & opts == nil {
 		return
 		return

+ 60 - 0
core/math/big/combinatorics.odin

@@ -0,0 +1,60 @@
+package math_big
+
+/*
+	With `n` items, calculate how many ways that `r` of them can be ordered.
+*/
+permutations_with_repetition :: int_pow_int
+
+/*
+	With `n` items, calculate how many ways that `r` of them can be ordered without any repeats.
+*/
+permutations_without_repetition :: proc(dest: ^Int, n, r: int) -> (error: Error)  {
+	if n == r {
+		return factorial(dest, n)
+	}
+
+	tmp := &Int{}
+	defer internal_destroy(tmp)
+
+	//    n!
+	// --------
+	// (n - r)!
+	factorial(dest, n)     or_return
+	factorial(tmp,  n - r) or_return
+	div(dest, dest, tmp)   or_return
+
+	return
+}
+
+/*
+	With `n` items, calculate how many ways that `r` of them can be chosen.
+
+	Also known as the multiset coefficient or (n multichoose k).
+*/
+combinations_with_repetition :: proc(dest: ^Int, n, r: int) -> (error: Error) {
+	// (n + r - 1)!
+	// ------------
+	// r!  (n - 1)!
+	return combinations_without_repetition(dest, n + r - 1, r)
+}
+
+/*
+	With `n` items, calculate how many ways that `r` of them can be chosen without any repeats.
+
+	Also known as the binomial coefficient or (n choose k).
+*/
+combinations_without_repetition :: proc(dest: ^Int, n, r: int) -> (error: Error) {
+	tmp_a, tmp_b := &Int{}, &Int{}
+	defer internal_destroy(tmp_a, tmp_b)
+
+	//      n! 
+	// ------------
+	// r!  (n - r)!
+	factorial(dest, n)       or_return
+	factorial(tmp_a, r)      or_return
+	factorial(tmp_b, n - r)  or_return
+	mul(tmp_a, tmp_a, tmp_b) or_return
+	div(dest, dest, tmp_a)   or_return
+
+	return
+}

+ 1 - 0
core/math/big/radix.odin

@@ -315,6 +315,7 @@ int_atoi :: proc(res: ^Int, input: string, radix := i8(10), allocator := context
 
 
 
 
 atoi :: proc { int_atoi, }
 atoi :: proc { int_atoi, }
+string_to_int :: int_atoi
 
 
 /*
 /*
 	We size for `string` by default.
 	We size for `string` by default.

+ 1 - 1
core/math/cmplx/cmplx_trig.odin

@@ -350,7 +350,7 @@ _reduce_pi_f64 :: proc "contextless" (x: f64) -> f64 #no_bounds_check {
 	// that is, 1/PI = SUM bdpi[i]*2^(-64*i).
 	// that is, 1/PI = SUM bdpi[i]*2^(-64*i).
 	// 19 64-bit digits give 1216 bits of precision
 	// 19 64-bit digits give 1216 bits of precision
 	// to handle the largest possible f64 exponent.
 	// to handle the largest possible f64 exponent.
-	@static bdpi := [?]u64{
+	@(static, rodata) bdpi := [?]u64{
 		0x0000000000000000,
 		0x0000000000000000,
 		0x517cc1b727220a94,
 		0x517cc1b727220a94,
 		0xfe13abe8fa9a6ee0,
 		0xfe13abe8fa9a6ee0,

+ 10 - 15
core/math/linalg/general.odin

@@ -3,6 +3,7 @@ package linalg
 import "core:math"
 import "core:math"
 import "base:builtin"
 import "base:builtin"
 import "base:intrinsics"
 import "base:intrinsics"
+import "base:runtime"
 
 
 // Generic
 // Generic
 
 
@@ -223,33 +224,27 @@ quaternion_mul_quaternion :: proc "contextless" (q1, q2: $Q) -> Q where IS_QUATE
 
 
 @(require_results)
 @(require_results)
 quaternion64_mul_vector3 :: proc "contextless" (q: $Q/quaternion64, v: $V/[3]$F/f16) -> V {
 quaternion64_mul_vector3 :: proc "contextless" (q: $Q/quaternion64, v: $V/[3]$F/f16) -> V {
-	Raw_Quaternion :: struct {xyz: [3]f16, r: f16}
-
-	q := transmute(Raw_Quaternion)q
+	q := transmute(runtime.Raw_Quaternion64_Vector_Scalar)q
 	v := v
 	v := v
 
 
-	t := cross(2*q.xyz, v)
-	return V(v + q.r*t + cross(q.xyz, t))
+	t := cross(2*q.vector, v)
+	return V(v + q.scalar*t + cross(q.vector, t))
 }
 }
 @(require_results)
 @(require_results)
 quaternion128_mul_vector3 :: proc "contextless" (q: $Q/quaternion128, v: $V/[3]$F/f32) -> V {
 quaternion128_mul_vector3 :: proc "contextless" (q: $Q/quaternion128, v: $V/[3]$F/f32) -> V {
-	Raw_Quaternion :: struct {xyz: [3]f32, r: f32}
-
-	q := transmute(Raw_Quaternion)q
+	q := transmute(runtime.Raw_Quaternion128_Vector_Scalar)q
 	v := v
 	v := v
 
 
-	t := cross(2*q.xyz, v)
-	return V(v + q.r*t + cross(q.xyz, t))
+	t := cross(2*q.vector, v)
+	return V(v + q.scalar*t + cross(q.vector, t))
 }
 }
 @(require_results)
 @(require_results)
 quaternion256_mul_vector3 :: proc "contextless" (q: $Q/quaternion256, v: $V/[3]$F/f64) -> V {
 quaternion256_mul_vector3 :: proc "contextless" (q: $Q/quaternion256, v: $V/[3]$F/f64) -> V {
-	Raw_Quaternion :: struct {xyz: [3]f64, r: f64}
-
-	q := transmute(Raw_Quaternion)q
+	q := transmute(runtime.Raw_Quaternion256_Vector_Scalar)q
 	v := v
 	v := v
 
 
-	t := cross(2*q.xyz, v)
-	return V(v + q.r*t + cross(q.xyz, t))
+	t := cross(2*q.vector, v)
+	return V(v + q.scalar*t + cross(q.vector, t))
 }
 }
 quaternion_mul_vector3 :: proc{quaternion64_mul_vector3, quaternion128_mul_vector3, quaternion256_mul_vector3}
 quaternion_mul_vector3 :: proc{quaternion64_mul_vector3, quaternion128_mul_vector3, quaternion256_mul_vector3}
 
 

+ 3 - 3
core/math/linalg/specific.odin

@@ -527,7 +527,7 @@ angle_from_quaternion :: proc{
 @(require_results)
 @(require_results)
 axis_from_quaternion_f16 :: proc "contextless" (q: Quaternionf16) -> Vector3f16 {
 axis_from_quaternion_f16 :: proc "contextless" (q: Quaternionf16) -> Vector3f16 {
 	t1 := 1 - q.w*q.w
 	t1 := 1 - q.w*q.w
-	if t1 < 0 {
+	if t1 <= 0 {
 		return {0, 0, 1}
 		return {0, 0, 1}
 	}
 	}
 	t2 := 1.0 / math.sqrt(t1)
 	t2 := 1.0 / math.sqrt(t1)
@@ -536,7 +536,7 @@ axis_from_quaternion_f16 :: proc "contextless" (q: Quaternionf16) -> Vector3f16
 @(require_results)
 @(require_results)
 axis_from_quaternion_f32 :: proc "contextless" (q: Quaternionf32) -> Vector3f32 {
 axis_from_quaternion_f32 :: proc "contextless" (q: Quaternionf32) -> Vector3f32 {
 	t1 := 1 - q.w*q.w
 	t1 := 1 - q.w*q.w
-	if t1 < 0 {
+	if t1 <= 0 {
 		return {0, 0, 1}
 		return {0, 0, 1}
 	}
 	}
 	t2 := 1.0 / math.sqrt(t1)
 	t2 := 1.0 / math.sqrt(t1)
@@ -545,7 +545,7 @@ axis_from_quaternion_f32 :: proc "contextless" (q: Quaternionf32) -> Vector3f32
 @(require_results)
 @(require_results)
 axis_from_quaternion_f64 :: proc "contextless" (q: Quaternionf64) -> Vector3f64 {
 axis_from_quaternion_f64 :: proc "contextless" (q: Quaternionf64) -> Vector3f64 {
 	t1 := 1 - q.w*q.w
 	t1 := 1 - q.w*q.w
-	if t1 < 0 {
+	if t1 <= 0 {
 		return {0, 0, 1}
 		return {0, 0, 1}
 	}
 	}
 	t2 := 1.0 / math.sqrt(t1)
 	t2 := 1.0 / math.sqrt(t1)

+ 9 - 9
core/math/math.odin

@@ -130,10 +130,10 @@ pow10 :: proc{
 
 
 @(require_results)
 @(require_results)
 pow10_f16 :: proc "contextless" (n: f16) -> f16 {
 pow10_f16 :: proc "contextless" (n: f16) -> f16 {
-	@static pow10_pos_tab := [?]f16{
+	@(static, rodata) pow10_pos_tab := [?]f16{
 		1e00, 1e01, 1e02, 1e03, 1e04,
 		1e00, 1e01, 1e02, 1e03, 1e04,
 	}
 	}
-	@static pow10_neg_tab := [?]f16{
+	@(static, rodata) pow10_neg_tab := [?]f16{
 		1e-00, 1e-01, 1e-02, 1e-03, 1e-04, 1e-05, 1e-06, 1e-07,
 		1e-00, 1e-01, 1e-02, 1e-03, 1e-04, 1e-05, 1e-06, 1e-07,
 	}
 	}
 
 
@@ -151,13 +151,13 @@ pow10_f16 :: proc "contextless" (n: f16) -> f16 {
 
 
 @(require_results)
 @(require_results)
 pow10_f32 :: proc "contextless" (n: f32) -> f32 {
 pow10_f32 :: proc "contextless" (n: f32) -> f32 {
-	@static pow10_pos_tab := [?]f32{
+	@(static, rodata) pow10_pos_tab := [?]f32{
 		1e00, 1e01, 1e02, 1e03, 1e04, 1e05, 1e06, 1e07, 1e08, 1e09,
 		1e00, 1e01, 1e02, 1e03, 1e04, 1e05, 1e06, 1e07, 1e08, 1e09,
 		1e10, 1e11, 1e12, 1e13, 1e14, 1e15, 1e16, 1e17, 1e18, 1e19,
 		1e10, 1e11, 1e12, 1e13, 1e14, 1e15, 1e16, 1e17, 1e18, 1e19,
 		1e20, 1e21, 1e22, 1e23, 1e24, 1e25, 1e26, 1e27, 1e28, 1e29,
 		1e20, 1e21, 1e22, 1e23, 1e24, 1e25, 1e26, 1e27, 1e28, 1e29,
 		1e30, 1e31, 1e32, 1e33, 1e34, 1e35, 1e36, 1e37, 1e38,
 		1e30, 1e31, 1e32, 1e33, 1e34, 1e35, 1e36, 1e37, 1e38,
 	}
 	}
-	@static pow10_neg_tab := [?]f32{
+	@(static, rodata) pow10_neg_tab := [?]f32{
 		1e-00, 1e-01, 1e-02, 1e-03, 1e-04, 1e-05, 1e-06, 1e-07, 1e-08, 1e-09,
 		1e-00, 1e-01, 1e-02, 1e-03, 1e-04, 1e-05, 1e-06, 1e-07, 1e-08, 1e-09,
 		1e-10, 1e-11, 1e-12, 1e-13, 1e-14, 1e-15, 1e-16, 1e-17, 1e-18, 1e-19,
 		1e-10, 1e-11, 1e-12, 1e-13, 1e-14, 1e-15, 1e-16, 1e-17, 1e-18, 1e-19,
 		1e-20, 1e-21, 1e-22, 1e-23, 1e-24, 1e-25, 1e-26, 1e-27, 1e-28, 1e-29,
 		1e-20, 1e-21, 1e-22, 1e-23, 1e-24, 1e-25, 1e-26, 1e-27, 1e-28, 1e-29,
@@ -179,16 +179,16 @@ pow10_f32 :: proc "contextless" (n: f32) -> f32 {
 
 
 @(require_results)
 @(require_results)
 pow10_f64 :: proc "contextless" (n: f64) -> f64 {
 pow10_f64 :: proc "contextless" (n: f64) -> f64 {
-	@static pow10_tab := [?]f64{
+	@(static, rodata) pow10_tab := [?]f64{
 		1e00, 1e01, 1e02, 1e03, 1e04, 1e05, 1e06, 1e07, 1e08, 1e09,
 		1e00, 1e01, 1e02, 1e03, 1e04, 1e05, 1e06, 1e07, 1e08, 1e09,
 		1e10, 1e11, 1e12, 1e13, 1e14, 1e15, 1e16, 1e17, 1e18, 1e19,
 		1e10, 1e11, 1e12, 1e13, 1e14, 1e15, 1e16, 1e17, 1e18, 1e19,
 		1e20, 1e21, 1e22, 1e23, 1e24, 1e25, 1e26, 1e27, 1e28, 1e29,
 		1e20, 1e21, 1e22, 1e23, 1e24, 1e25, 1e26, 1e27, 1e28, 1e29,
 		1e30, 1e31,
 		1e30, 1e31,
 	}
 	}
-	@static pow10_pos_tab32 := [?]f64{
+	@(static, rodata) pow10_pos_tab32 := [?]f64{
 		1e00, 1e32, 1e64, 1e96, 1e128, 1e160, 1e192, 1e224, 1e256, 1e288,
 		1e00, 1e32, 1e64, 1e96, 1e128, 1e160, 1e192, 1e224, 1e256, 1e288,
 	}
 	}
-	@static pow10_neg_tab32 := [?]f64{
+	@(static, rodata) pow10_neg_tab32 := [?]f64{
 		1e-00, 1e-32, 1e-64, 1e-96, 1e-128, 1e-160, 1e-192, 1e-224, 1e-256, 1e-288, 1e-320,
 		1e-00, 1e-32, 1e-64, 1e-96, 1e-128, 1e-160, 1e-192, 1e-224, 1e-256, 1e-288, 1e-320,
 	}
 	}
 
 
@@ -1274,7 +1274,7 @@ binomial :: proc "contextless" (n, k: int) -> int {
 @(require_results)
 @(require_results)
 factorial :: proc "contextless" (n: int) -> int {
 factorial :: proc "contextless" (n: int) -> int {
 	when size_of(int) == size_of(i64) {
 	when size_of(int) == size_of(i64) {
-		@static table := [21]int{
+		@(static, rodata) table := [21]int{
 			1,
 			1,
 			1,
 			1,
 			2,
 			2,
@@ -1298,7 +1298,7 @@ factorial :: proc "contextless" (n: int) -> int {
 			2_432_902_008_176_640_000,
 			2_432_902_008_176_640_000,
 		}
 		}
 	} else {
 	} else {
-		@static table := [13]int{
+		@(static, rodata) table := [13]int{
 			1,
 			1,
 			1,
 			1,
 			2,
 			2,

+ 3 - 3
core/math/math_gamma.odin

@@ -67,7 +67,7 @@ package math
 // masks any imprecision in the polynomial.
 // masks any imprecision in the polynomial.
 @(private="file", require_results)
 @(private="file", require_results)
 stirling :: proc "contextless" (x: f64) -> (f64, f64) {
 stirling :: proc "contextless" (x: f64) -> (f64, f64) {
-	@(static) gamS := [?]f64{
+	@(static, rodata) gamS := [?]f64{
 		+7.87311395793093628397e-04,
 		+7.87311395793093628397e-04,
 		-2.29549961613378126380e-04,
 		-2.29549961613378126380e-04,
 		-2.68132617805781232825e-03,
 		-2.68132617805781232825e-03,
@@ -103,7 +103,7 @@ gamma_f64 :: proc "contextless" (x: f64) -> f64 {
 		return false
 		return false
 	}
 	}
 	
 	
-	@(static) gamP := [?]f64{
+	@(static, rodata) gamP := [?]f64{
 		1.60119522476751861407e-04,
 		1.60119522476751861407e-04,
 		1.19135147006586384913e-03,
 		1.19135147006586384913e-03,
 		1.04213797561761569935e-02,
 		1.04213797561761569935e-02,
@@ -112,7 +112,7 @@ gamma_f64 :: proc "contextless" (x: f64) -> f64 {
 		4.94214826801497100753e-01,
 		4.94214826801497100753e-01,
 		9.99999999999999996796e-01,
 		9.99999999999999996796e-01,
 	}
 	}
-	@(static) gamQ := [?]f64{
+	@(static, rodata) gamQ := [?]f64{
 		-2.31581873324120129819e-05,
 		-2.31581873324120129819e-05,
 		+5.39605580493303397842e-04,
 		+5.39605580493303397842e-04,
 		-4.45641913851797240494e-03,
 		-4.45641913851797240494e-03,

+ 7 - 7
core/math/math_lgamma.odin

@@ -123,7 +123,7 @@ lgamma_f64 :: proc "contextless" (x: f64) -> (lgamma: f64, sign: int) {
 		return -x
 		return -x
 	}
 	}
 	
 	
-	@static lgamA := [?]f64{
+	@(static, rodata) lgamA := [?]f64{
 		0h3FB3C467E37DB0C8,
 		0h3FB3C467E37DB0C8,
 		0h3FD4A34CC4A60FAD,
 		0h3FD4A34CC4A60FAD,
 		0h3FB13E001A5562A7,
 		0h3FB13E001A5562A7,
@@ -137,7 +137,7 @@ lgamma_f64 :: proc "contextless" (x: f64) -> (lgamma: f64, sign: int) {
 		0h3EFA7074428CFA52,
 		0h3EFA7074428CFA52,
 		0h3F07858E90A45837,
 		0h3F07858E90A45837,
 	}
 	}
-	@static lgamR := [?]f64{
+	@(static, rodata) lgamR := [?]f64{
 		1.0,
 		1.0,
 		0h3FF645A762C4AB74,
 		0h3FF645A762C4AB74,
 		0h3FE71A1893D3DCDC,
 		0h3FE71A1893D3DCDC,
@@ -146,7 +146,7 @@ lgamma_f64 :: proc "contextless" (x: f64) -> (lgamma: f64, sign: int) {
 		0h3F497DDACA41A95B,
 		0h3F497DDACA41A95B,
 		0h3EDEBAF7A5B38140,
 		0h3EDEBAF7A5B38140,
 	}
 	}
-	@static lgamS := [?]f64{
+	@(static, rodata) lgamS := [?]f64{
 		0hBFB3C467E37DB0C8,
 		0hBFB3C467E37DB0C8,
 		0h3FCB848B36E20878,
 		0h3FCB848B36E20878,
 		0h3FD4D98F4F139F59,
 		0h3FD4D98F4F139F59,
@@ -155,7 +155,7 @@ lgamma_f64 :: proc "contextless" (x: f64) -> (lgamma: f64, sign: int) {
 		0h3F5E26B67368F239,
 		0h3F5E26B67368F239,
 		0h3F00BFECDD17E945,
 		0h3F00BFECDD17E945,
 	}
 	}
-	@static lgamT := [?]f64{
+	@(static, rodata) lgamT := [?]f64{
 		0h3FDEF72BC8EE38A2,
 		0h3FDEF72BC8EE38A2,
 		0hBFC2E4278DC6C509,
 		0hBFC2E4278DC6C509,
 		0h3FB08B4294D5419B,
 		0h3FB08B4294D5419B,
@@ -172,7 +172,7 @@ lgamma_f64 :: proc "contextless" (x: f64) -> (lgamma: f64, sign: int) {
 		0hBF347F24ECC38C38,
 		0hBF347F24ECC38C38,
 		0h3F35FD3EE8C2D3F4,
 		0h3F35FD3EE8C2D3F4,
 	}
 	}
-	@static lgamU := [?]f64{
+	@(static, rodata) lgamU := [?]f64{
 		0hBFB3C467E37DB0C8,
 		0hBFB3C467E37DB0C8,
 		0h3FE4401E8B005DFF,
 		0h3FE4401E8B005DFF,
 		0h3FF7475CD119BD6F,
 		0h3FF7475CD119BD6F,
@@ -180,7 +180,7 @@ lgamma_f64 :: proc "contextless" (x: f64) -> (lgamma: f64, sign: int) {
 		0h3FCD4EAEF6010924,
 		0h3FCD4EAEF6010924,
 		0h3F8B678BBF2BAB09,
 		0h3F8B678BBF2BAB09,
 	}
 	}
-	@static lgamV := [?]f64{
+	@(static, rodata) lgamV := [?]f64{
 		1.0,
 		1.0,
 		0h4003A5D7C2BD619C,
 		0h4003A5D7C2BD619C,
 		0h40010725A42B18F5,
 		0h40010725A42B18F5,
@@ -188,7 +188,7 @@ lgamma_f64 :: proc "contextless" (x: f64) -> (lgamma: f64, sign: int) {
 		0h3FBAAE55D6537C88,
 		0h3FBAAE55D6537C88,
 		0h3F6A5ABB57D0CF61,
 		0h3F6A5ABB57D0CF61,
 	}
 	}
-	@static lgamW := [?]f64{
+	@(static, rodata) lgamW := [?]f64{
 		0h3FDACFE390C97D69,
 		0h3FDACFE390C97D69,
 		0h3FB555555555553B,
 		0h3FB555555555553B,
 		0hBF66C16C16B02E5C,
 		0hBF66C16C16B02E5C,

+ 1 - 1
core/math/math_sincos.odin

@@ -234,7 +234,7 @@ _trig_reduce_f64 :: proc "contextless" (x: f64) -> (j: u64, z: f64) #no_bounds_c
 	// that is, 4/pi = Sum bd_pi4[i]*2^(-64*i)
 	// that is, 4/pi = Sum bd_pi4[i]*2^(-64*i)
 	// 19 64-bit digits and the leading one bit give 1217 bits
 	// 19 64-bit digits and the leading one bit give 1217 bits
 	// of precision to handle the largest possible f64 exponent.
 	// of precision to handle the largest possible f64 exponent.
-	@static bd_pi4 := [?]u64{
+	@(static, rodata) bd_pi4 := [?]u64{
 		0x0000000000000001,
 		0x0000000000000001,
 		0x45f306dc9c882a53,
 		0x45f306dc9c882a53,
 		0xf84eafa3ea69bb81,
 		0xf84eafa3ea69bb81,

+ 3 - 3
core/math/rand/exp.odin

@@ -19,7 +19,7 @@ import "core:math"
 exp_float64 :: proc(r: ^Rand = nil) -> f64 {
 exp_float64 :: proc(r: ^Rand = nil) -> f64 {
 	re :: 7.69711747013104972
 	re :: 7.69711747013104972
 
 
-	@(static)
+	@(static, rodata)
 	ke := [256]u32{
 	ke := [256]u32{
 		0xe290a139, 0x0, 0x9beadebc, 0xc377ac71, 0xd4ddb990,
 		0xe290a139, 0x0, 0x9beadebc, 0xc377ac71, 0xd4ddb990,
 		0xde893fb8, 0xe4a8e87c, 0xe8dff16a, 0xebf2deab, 0xee49a6e8,
 		0xde893fb8, 0xe4a8e87c, 0xe8dff16a, 0xebf2deab, 0xee49a6e8,
@@ -74,7 +74,7 @@ exp_float64 :: proc(r: ^Rand = nil) -> f64 {
 		0xf7b577d2, 0xf69c650c, 0xf51530f0, 0xf2cb0e3c, 0xeeefb15d,
 		0xf7b577d2, 0xf69c650c, 0xf51530f0, 0xf2cb0e3c, 0xeeefb15d,
 		0xe6da6ecf,
 		0xe6da6ecf,
 	}
 	}
-	@(static)
+	@(static, rodata)
 	we := [256]f32{
 	we := [256]f32{
 		2.0249555e-09, 1.486674e-11, 2.4409617e-11, 3.1968806e-11,
 		2.0249555e-09, 1.486674e-11, 2.4409617e-11, 3.1968806e-11,
 		3.844677e-11, 4.4228204e-11, 4.9516443e-11, 5.443359e-11,
 		3.844677e-11, 4.4228204e-11, 4.9516443e-11, 5.443359e-11,
@@ -141,7 +141,7 @@ exp_float64 :: proc(r: ^Rand = nil) -> f64 {
 		1.2393786e-09, 1.276585e-09, 1.3193139e-09, 1.3695435e-09,
 		1.2393786e-09, 1.276585e-09, 1.3193139e-09, 1.3695435e-09,
 		1.4305498e-09, 1.508365e-09, 1.6160854e-09, 1.7921248e-09,
 		1.4305498e-09, 1.508365e-09, 1.6160854e-09, 1.7921248e-09,
 	}
 	}
-	@(static)
+	@(static, rodata)
 	fe := [256]f32{
 	fe := [256]f32{
 		1, 0.9381437, 0.90046996, 0.87170434, 0.8477855, 0.8269933,
 		1, 0.9381437, 0.90046996, 0.87170434, 0.8477855, 0.8269933,
 		0.8084217, 0.7915276, 0.77595687, 0.7614634, 0.7478686,
 		0.8084217, 0.7915276, 0.77595687, 0.7614634, 0.7478686,

+ 3 - 3
core/math/rand/normal.odin

@@ -21,7 +21,7 @@ import "core:math"
 norm_float64 :: proc(r: ^Rand = nil) -> f64 {
 norm_float64 :: proc(r: ^Rand = nil) -> f64 {
 	rn :: 3.442619855899
 	rn :: 3.442619855899
 
 
-	@(static)
+	@(static, rodata)
 	kn := [128]u32{
 	kn := [128]u32{
 		0x76ad2212, 0x00000000, 0x600f1b53, 0x6ce447a6, 0x725b46a2,
 		0x76ad2212, 0x00000000, 0x600f1b53, 0x6ce447a6, 0x725b46a2,
 		0x7560051d, 0x774921eb, 0x789a25bd, 0x799045c3, 0x7a4bce5d,
 		0x7560051d, 0x774921eb, 0x789a25bd, 0x799045c3, 0x7a4bce5d,
@@ -50,7 +50,7 @@ norm_float64 :: proc(r: ^Rand = nil) -> f64 {
 		0x7da61a1e, 0x7d72a0fb, 0x7d30e097, 0x7cd9b4ab, 0x7c600f1a,
 		0x7da61a1e, 0x7d72a0fb, 0x7d30e097, 0x7cd9b4ab, 0x7c600f1a,
 		0x7ba90bdc, 0x7a722176, 0x77d664e5,
 		0x7ba90bdc, 0x7a722176, 0x77d664e5,
 	}
 	}
-	@(static)
+	@(static, rodata)
 	wn := [128]f32{
 	wn := [128]f32{
 		1.7290405e-09, 1.2680929e-10, 1.6897518e-10, 1.9862688e-10,
 		1.7290405e-09, 1.2680929e-10, 1.6897518e-10, 1.9862688e-10,
 		2.2232431e-10, 2.4244937e-10, 2.601613e-10,  2.7611988e-10,
 		2.2232431e-10, 2.4244937e-10, 2.601613e-10,  2.7611988e-10,
@@ -85,7 +85,7 @@ norm_float64 :: proc(r: ^Rand = nil) -> f64 {
 		1.2601323e-09, 1.2857697e-09, 1.3146202e-09, 1.347784e-09,
 		1.2601323e-09, 1.2857697e-09, 1.3146202e-09, 1.347784e-09,
 		1.3870636e-09, 1.4357403e-09, 1.5008659e-09, 1.6030948e-09,
 		1.3870636e-09, 1.4357403e-09, 1.5008659e-09, 1.6030948e-09,
 	}
 	}
-	@(static)
+	@(static, rodata)
 	fn := [128]f32{
 	fn := [128]f32{
 		1.00000000,  0.9635997,   0.9362827,   0.9130436,   0.89228165,
 		1.00000000,  0.9635997,   0.9362827,   0.9130436,   0.89228165,
 		0.87324303,  0.8555006,   0.8387836,   0.8229072,   0.8077383,
 		0.87324303,  0.8555006,   0.8387836,   0.8229072,   0.8077383,

+ 9 - 6
core/mem/raw.odin

@@ -11,12 +11,15 @@ Raw_Dynamic_Array :: runtime.Raw_Dynamic_Array
 Raw_Map           :: runtime.Raw_Map
 Raw_Map           :: runtime.Raw_Map
 Raw_Soa_Pointer   :: runtime.Raw_Soa_Pointer
 Raw_Soa_Pointer   :: runtime.Raw_Soa_Pointer
 
 
-Raw_Complex64     :: struct {real, imag: f32}
-Raw_Complex128    :: struct {real, imag: f64}
-Raw_Quaternion128 :: struct {imag, jmag, kmag: f32, real: f32}
-Raw_Quaternion256 :: struct {imag, jmag, kmag: f64, real: f64}
-Raw_Quaternion128_Vector_Scalar :: struct {vector: [3]f32, scalar: f32}
-Raw_Quaternion256_Vector_Scalar :: struct {vector: [3]f64, scalar: f64}
+Raw_Complex32     :: runtime.Raw_Complex32
+Raw_Complex64     :: runtime.Raw_Complex64
+Raw_Complex128    :: runtime.Raw_Complex128
+Raw_Quaternion64  :: runtime.Raw_Quaternion64
+Raw_Quaternion128 :: runtime.Raw_Quaternion128
+Raw_Quaternion256 :: runtime.Raw_Quaternion256
+Raw_Quaternion64_Vector_Scalar  :: runtime.Raw_Quaternion64_Vector_Scalar
+Raw_Quaternion128_Vector_Scalar :: runtime.Raw_Quaternion128_Vector_Scalar
+Raw_Quaternion256_Vector_Scalar :: runtime.Raw_Quaternion256_Vector_Scalar
 
 
 make_any :: proc "contextless" (data: rawptr, id: typeid) -> any {
 make_any :: proc "contextless" (data: rawptr, id: typeid) -> any {
 	return transmute(any)Raw_Any{data, id}
 	return transmute(any)Raw_Any{data, id}

+ 341 - 0
core/mem/rollback_stack_allocator.odin

@@ -0,0 +1,341 @@
+package mem
+
+// The Rollback Stack Allocator was designed for the test runner to be fast,
+// able to grow, and respect the Tracking Allocator's requirement for
+// individual frees. It is not overly concerned with fragmentation, however.
+//
+// It has support for expansion when configured with a block allocator and
+// limited support for out-of-order frees.
+//
+// Allocation has constant-time best and usual case performance.
+// At worst, it is linear according to the number of memory blocks.
+//
+// Allocation follows a first-fit strategy when there are multiple memory
+// blocks.
+//
+// Freeing has constant-time best and usual case performance.
+// At worst, it is linear according to the number of memory blocks and number
+// of freed items preceding the last item in a block.
+//
+// Resizing has constant-time performance, if it's the last item in a block, or
+// the new size is smaller. Naturally, this becomes linear-time if there are
+// multiple blocks to search for the pointer's owning block. Otherwise, the
+// allocator defaults to a combined alloc & free operation internally.
+//
+// Out-of-order freeing is accomplished by collapsing a run of freed items
+// from the last allocation backwards.
+//
+// Each allocation has an overhead of 8 bytes and any extra bytes to satisfy
+// the requested alignment.
+
+import "base:runtime"
+
+ROLLBACK_STACK_DEFAULT_BLOCK_SIZE :: 4 * Megabyte
+
+// This limitation is due to the size of `prev_ptr`, but it is only for the
+// head block; any allocation in excess of the allocator's `block_size` is
+// valid, so long as the block allocator can handle it.
+//
+// This is because allocations over the block size are not split up if the item
+// within is freed; they are immediately returned to the block allocator.
+ROLLBACK_STACK_MAX_HEAD_BLOCK_SIZE :: 2 * Gigabyte
+
+
+Rollback_Stack_Header :: bit_field u64 {
+	prev_offset:  uintptr | 32,
+	is_free:         bool |  1,
+	prev_ptr:     uintptr | 31,
+}
+
+Rollback_Stack_Block :: struct {
+	next_block: ^Rollback_Stack_Block,
+	last_alloc: rawptr,
+	offset: uintptr,
+	buffer: []byte,
+}
+
+Rollback_Stack :: struct {
+	head: ^Rollback_Stack_Block,
+	block_size: int,
+	block_allocator: Allocator,
+}
+
+
+@(private="file", require_results)
+rb_ptr_in_bounds :: proc(block: ^Rollback_Stack_Block, ptr: rawptr) -> bool {
+	start := raw_data(block.buffer)
+	end   := start[block.offset:]
+	return start < ptr && ptr <= end
+}
+
+@(private="file", require_results)
+rb_find_ptr :: proc(stack: ^Rollback_Stack, ptr: rawptr) -> (
+	parent: ^Rollback_Stack_Block,
+	block:  ^Rollback_Stack_Block,
+	header: ^Rollback_Stack_Header,
+	err: Allocator_Error,
+) {
+	for block = stack.head; block != nil; block = block.next_block {
+		if rb_ptr_in_bounds(block, ptr) {
+			header = cast(^Rollback_Stack_Header)(cast(uintptr)ptr - size_of(Rollback_Stack_Header))
+			return
+		}
+		parent = block
+	}
+	return nil, nil, nil, .Invalid_Pointer
+}
+
+@(private="file", require_results)
+rb_find_last_alloc :: proc(stack: ^Rollback_Stack, ptr: rawptr) -> (
+	block: ^Rollback_Stack_Block,
+	header: ^Rollback_Stack_Header,
+	ok: bool,
+) {
+	for block = stack.head; block != nil; block = block.next_block {
+		if block.last_alloc == ptr {
+			header = cast(^Rollback_Stack_Header)(cast(uintptr)ptr - size_of(Rollback_Stack_Header))
+			return block, header, true
+		}
+	}
+	return nil, nil, false
+}
+
+@(private="file")
+rb_rollback_block :: proc(block: ^Rollback_Stack_Block, header: ^Rollback_Stack_Header) {
+	header := header
+	for block.offset > 0 && header.is_free {
+		block.offset = header.prev_offset
+		block.last_alloc = raw_data(block.buffer)[header.prev_ptr:]
+		header = cast(^Rollback_Stack_Header)(raw_data(block.buffer)[header.prev_ptr - size_of(Rollback_Stack_Header):])
+	}
+}
+
+@(private="file", require_results)
+rb_free :: proc(stack: ^Rollback_Stack, ptr: rawptr) -> Allocator_Error {
+	parent, block, header := rb_find_ptr(stack, ptr) or_return
+	if header.is_free {
+		return .Invalid_Pointer
+	}
+	header.is_free = true
+	if block.last_alloc == ptr {
+		block.offset = header.prev_offset
+		rb_rollback_block(block, header)
+	}
+	if parent != nil && block.offset == 0 {
+		parent.next_block = block.next_block
+		runtime.mem_free_with_size(block, size_of(Rollback_Stack_Block) + len(block.buffer), stack.block_allocator)
+	}
+	return nil
+}
+
+@(private="file")
+rb_free_all :: proc(stack: ^Rollback_Stack) {
+	for block := stack.head.next_block; block != nil; /**/ {
+		next_block := block.next_block
+		runtime.mem_free_with_size(block, size_of(Rollback_Stack_Block) + len(block.buffer), stack.block_allocator)
+		block = next_block
+	}
+
+	stack.head.next_block = nil
+	stack.head.last_alloc = nil
+	stack.head.offset = 0
+}
+
+@(private="file", require_results)
+rb_resize :: proc(stack: ^Rollback_Stack, ptr: rawptr, old_size, size, alignment: int) -> (result: []byte, err: Allocator_Error) {
+	if ptr != nil {
+		if block, _, ok := rb_find_last_alloc(stack, ptr); ok {
+			// `block.offset` should never underflow because it is contingent
+			// on `old_size` in the first place, assuming sane arguments.
+			assert(block.offset >= cast(uintptr)old_size, "Rollback Stack Allocator received invalid `old_size`.")
+
+			if block.offset + cast(uintptr)size - cast(uintptr)old_size < cast(uintptr)len(block.buffer) {
+				// Prevent singleton allocations from fragmenting by forbidding
+				// them to shrink, removing the possibility of overflow bugs.
+				if len(block.buffer) <= stack.block_size {
+					block.offset += cast(uintptr)size - cast(uintptr)old_size
+				}
+				#no_bounds_check return (cast([^]byte)ptr)[:size], nil
+			}
+		}
+	}
+
+	result = rb_alloc(stack, size, alignment) or_return
+	runtime.mem_copy_non_overlapping(raw_data(result), ptr, old_size)
+	err = rb_free(stack, ptr)
+
+	return
+}
+
+@(private="file", require_results)
+rb_alloc :: proc(stack: ^Rollback_Stack, size, alignment: int) -> (result: []byte, err: Allocator_Error) {
+	parent: ^Rollback_Stack_Block
+	for block := stack.head; /**/; block = block.next_block {
+		when !ODIN_DISABLE_ASSERT {
+			allocated_new_block: bool
+		}
+
+		if block == nil {
+			if stack.block_allocator.procedure == nil {
+				return nil, .Out_Of_Memory
+			}
+
+			minimum_size_required := size_of(Rollback_Stack_Header) + size + alignment - 1
+			new_block_size := max(minimum_size_required, stack.block_size)
+			block = rb_make_block(new_block_size, stack.block_allocator) or_return
+			parent.next_block = block
+			when !ODIN_DISABLE_ASSERT {
+				allocated_new_block = true
+			}
+		}
+
+		start := raw_data(block.buffer)[block.offset:]
+		padding := cast(uintptr)calc_padding_with_header(cast(uintptr)start, cast(uintptr)alignment, size_of(Rollback_Stack_Header))
+
+		if block.offset + padding + cast(uintptr)size > cast(uintptr)len(block.buffer) {
+			when !ODIN_DISABLE_ASSERT {
+				if allocated_new_block {
+					panic("Rollback Stack Allocator allocated a new block but did not use it.")
+				}
+			}
+			parent = block
+			continue
+		}
+
+		header := cast(^Rollback_Stack_Header)(start[padding - size_of(Rollback_Stack_Header):])
+		ptr := start[padding:]
+
+		header^ = {
+			prev_offset = block.offset,
+			prev_ptr = uintptr(0) if block.last_alloc == nil else cast(uintptr)block.last_alloc - cast(uintptr)raw_data(block.buffer),
+			is_free = false,
+		}
+
+		block.last_alloc = ptr
+		block.offset += padding + cast(uintptr)size
+
+		if len(block.buffer) > stack.block_size {
+			// This block exceeds the allocator's standard block size and is considered a singleton.
+			// Prevent any further allocations on it.
+			block.offset = cast(uintptr)len(block.buffer)
+		}
+		
+		#no_bounds_check return ptr[:size], nil
+	}
+
+	return nil, .Out_Of_Memory
+}
+
+@(private="file", require_results)
+rb_make_block :: proc(size: int, allocator: Allocator) -> (block: ^Rollback_Stack_Block, err: Allocator_Error) {
+	buffer := runtime.mem_alloc(size_of(Rollback_Stack_Block) + size, align_of(Rollback_Stack_Block), allocator) or_return
+
+	block = cast(^Rollback_Stack_Block)raw_data(buffer)
+	#no_bounds_check block.buffer = buffer[size_of(Rollback_Stack_Block):]
+	return
+}
+
+
+rollback_stack_init_buffered :: proc(stack: ^Rollback_Stack, buffer: []byte, location := #caller_location) {
+	MIN_SIZE :: size_of(Rollback_Stack_Block) + size_of(Rollback_Stack_Header) + size_of(rawptr)
+	assert(len(buffer) >= MIN_SIZE, "User-provided buffer to Rollback Stack Allocator is too small.", location)
+
+	block := cast(^Rollback_Stack_Block)raw_data(buffer)
+	block^ = {}
+	#no_bounds_check block.buffer = buffer[size_of(Rollback_Stack_Block):]
+
+	stack^ = {}
+	stack.head = block
+	stack.block_size = len(block.buffer)
+}
+
+rollback_stack_init_dynamic :: proc(
+	stack: ^Rollback_Stack,
+	block_size : int = ROLLBACK_STACK_DEFAULT_BLOCK_SIZE,
+	block_allocator := context.allocator,
+	location := #caller_location,
+) -> Allocator_Error {
+	assert(block_size >= size_of(Rollback_Stack_Header) + size_of(rawptr), "Rollback Stack Allocator block size is too small.", location)
+	when size_of(int) > 4 {
+		// It's impossible to specify an argument in excess when your integer
+		// size is insufficient; check only on platforms with big enough ints.
+		assert(block_size <= ROLLBACK_STACK_MAX_HEAD_BLOCK_SIZE, "Rollback Stack Allocators cannot support head blocks larger than 2 gigabytes.", location)
+	}
+
+	block := rb_make_block(block_size, block_allocator) or_return
+
+	stack^ = {}
+	stack.head = block
+	stack.block_size = block_size
+	stack.block_allocator = block_allocator
+
+	return nil
+}
+
+rollback_stack_init :: proc {
+	rollback_stack_init_buffered,
+	rollback_stack_init_dynamic,
+}
+
+rollback_stack_destroy :: proc(stack: ^Rollback_Stack) {
+	if stack.block_allocator.procedure != nil {
+		rb_free_all(stack)
+		free(stack.head, stack.block_allocator)
+	}
+	stack^ = {}
+}
+
+@(require_results)
+rollback_stack_allocator :: proc(stack: ^Rollback_Stack) -> Allocator {
+	return Allocator {
+		data = stack,
+		procedure = rollback_stack_allocator_proc,
+	}
+}
+
+@(require_results)
+rollback_stack_allocator_proc :: proc(allocator_data: rawptr, mode: Allocator_Mode,
+                                      size, alignment: int,
+                                      old_memory: rawptr, old_size: int, location := #caller_location,
+) -> (result: []byte, err: Allocator_Error) {
+	stack := cast(^Rollback_Stack)allocator_data
+
+	switch mode {
+	case .Alloc, .Alloc_Non_Zeroed:
+		assert(size >= 0, "Size must be positive or zero.", location)
+		assert(is_power_of_two(cast(uintptr)alignment), "Alignment must be a power of two.", location)
+		result = rb_alloc(stack, size, alignment) or_return
+
+		if mode == .Alloc {
+			zero_slice(result)
+		}
+
+	case .Free:
+		err = rb_free(stack, old_memory)
+
+	case .Free_All:
+		rb_free_all(stack)
+
+	case .Resize, .Resize_Non_Zeroed:
+		assert(size >= 0, "Size must be positive or zero.", location)
+		assert(old_size >= 0, "Old size must be positive or zero.", location)
+		assert(is_power_of_two(cast(uintptr)alignment), "Alignment must be a power of two.", location)
+		result = rb_resize(stack, old_memory, old_size, size, alignment) or_return
+
+		#no_bounds_check if mode == .Resize && size > old_size {
+			zero_slice(result[old_size:])
+		}
+
+	case .Query_Features:
+		set := (^Allocator_Mode_Set)(old_memory)
+		if set != nil {
+			set^ = {.Alloc, .Alloc_Non_Zeroed, .Free, .Free_All, .Resize, .Resize_Non_Zeroed}
+		}
+		return nil, nil
+
+	case .Query_Info:
+		return nil, .Mode_Not_Implemented
+	}
+
+	return
+}

+ 36 - 0
core/mem/tlsf/LICENSE

@@ -0,0 +1,36 @@
+Original BSD-3 license:
+
+Two Level Segregated Fit memory allocator, version 3.1.
+Written by Matthew Conte
+	http://tlsf.baisoku.org
+
+Based on the original documentation by Miguel Masmano:
+	http://www.gii.upv.es/tlsf/main/docs
+
+This implementation was written to the specification
+of the document, therefore no GPL restrictions apply.
+ 
+Copyright (c) 2006-2016, Matthew Conte
+All rights reserved.
+ 
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+     * Redistributions of source code must retain the above copyright
+       notice, this list of conditions and the following disclaimer.
+     * Redistributions in binary form must reproduce the above copyright
+       notice, this list of conditions and the following disclaimer in the
+       documentation and/or other materials provided with the distribution.
+     * Neither the name of the copyright holder nor the
+       names of its contributors may be used to endorse or promote products
+       derived from this software without specific prior written permission.
+ 
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+DISCLAIMED. IN NO EVENT SHALL MATTHEW CONTE BE LIABLE FOR ANY
+DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

+ 156 - 0
core/mem/tlsf/tlsf.odin

@@ -0,0 +1,156 @@
+/*
+	Copyright 2024 Jeroen van Rijn <[email protected]>.
+	Made available under Odin's BSD-3 license.
+
+	List of contributors:
+		Matt Conte:      Original C implementation, see LICENSE file in this package
+		Jeroen van Rijn: Source port
+*/
+
+// package mem_tlsf implements a Two Level Segregated Fit memory allocator.
+package mem_tlsf
+
+import "base:runtime"
+
+Error :: enum byte {
+	None                      = 0,
+	Invalid_Backing_Allocator = 1,
+	Invalid_Alignment         = 2,
+	Backing_Buffer_Too_Small  = 3,
+	Backing_Buffer_Too_Large  = 4,
+	Backing_Allocator_Error   = 5,
+}
+
+
+Allocator :: struct {
+	// Empty lists point at this block to indicate they are free.
+	block_null: Block_Header,
+
+	// Bitmaps for free lists.
+	fl_bitmap: u32                  `fmt:"-"`,
+	sl_bitmap: [FL_INDEX_COUNT]u32  `fmt:"-"`,
+
+	// Head of free lists.
+	blocks: [FL_INDEX_COUNT][SL_INDEX_COUNT]^Block_Header `fmt:"-"`,
+
+	// Keep track of pools so we can deallocate them.
+	// If `pool.allocator` is blank, we don't do anything.
+	// We also use this linked list of pools to report
+	// statistics like how much memory is still available,
+	// fragmentation, etc.
+	pool: Pool,
+}
+#assert(size_of(Allocator) % ALIGN_SIZE == 0)
+
+
+
+
+@(require_results)
+allocator :: proc(t: ^Allocator) -> runtime.Allocator {
+	return runtime.Allocator{
+		procedure = allocator_proc,
+		data      = t,
+	}
+}
+
+@(require_results)
+init_from_buffer :: proc(control: ^Allocator, buf: []byte) -> Error {
+	assert(control != nil)
+	if uintptr(raw_data(buf)) % ALIGN_SIZE != 0 {
+		return .Invalid_Alignment
+	}
+
+	pool_bytes := align_down(len(buf) - POOL_OVERHEAD, ALIGN_SIZE)
+	if pool_bytes < BLOCK_SIZE_MIN {
+		return .Backing_Buffer_Too_Small
+	} else if pool_bytes > BLOCK_SIZE_MAX {
+		return .Backing_Buffer_Too_Large
+	}
+
+	clear(control)
+	return pool_add(control, buf[:])
+}
+
+@(require_results)
+init_from_allocator :: proc(control: ^Allocator, backing: runtime.Allocator, initial_pool_size: int, new_pool_size := 0) -> Error {
+	assert(control != nil)
+	pool_bytes := align_up(uint(initial_pool_size) + POOL_OVERHEAD, ALIGN_SIZE)
+	if pool_bytes < BLOCK_SIZE_MIN {
+		return .Backing_Buffer_Too_Small
+	} else if pool_bytes > BLOCK_SIZE_MAX {
+		return .Backing_Buffer_Too_Large
+	}
+
+	buf, backing_err := runtime.make_aligned([]byte, pool_bytes, ALIGN_SIZE, backing)
+	if backing_err != nil {
+		return .Backing_Allocator_Error
+	}
+	err := init_from_buffer(control, buf)
+	control.pool = Pool{
+		data      = buf,
+		allocator = backing,
+	}
+	return err
+}
+init :: proc{init_from_buffer, init_from_allocator}
+
+destroy :: proc(control: ^Allocator) {
+	if control == nil { return }
+
+	// No need to call `pool_remove` or anything, as they're they're embedded in the backing memory.
+	// We do however need to free the `Pool` tracking entities and the backing memory itself.
+	// As `Allocator` is embedded in the first backing slice, the `control` pointer will be
+	// invalid after this call.
+	for p := control.pool.next; p != nil; {
+		next := p.next
+
+		// Free the allocation on the backing allocator
+		runtime.delete(p.data, p.allocator)
+		free(p, p.allocator)
+
+		p = next
+	}
+}
+
+allocator_proc :: proc(allocator_data: rawptr, mode: runtime.Allocator_Mode,
+                       size, alignment: int,
+                       old_memory: rawptr, old_size: int, location := #caller_location) -> ([]byte, runtime.Allocator_Error)  {
+
+	control := (^Allocator)(allocator_data)
+	if control == nil {
+		return nil, .Invalid_Argument
+	}
+
+	switch mode {
+	case .Alloc:
+		return alloc_bytes(control, uint(size), uint(alignment))
+	case .Alloc_Non_Zeroed:
+		return alloc_bytes_non_zeroed(control, uint(size), uint(alignment))
+
+	case .Free:
+		free_with_size(control, old_memory, uint(old_size))
+		return nil, nil
+
+	case .Free_All:
+		clear(control)
+		return nil, nil
+
+	case .Resize:
+		return resize(control, old_memory, uint(old_size), uint(size), uint(alignment))
+
+	case .Resize_Non_Zeroed:
+		return resize_non_zeroed(control, old_memory, uint(old_size), uint(size), uint(alignment))
+
+	case .Query_Features:
+		set := (^runtime.Allocator_Mode_Set)(old_memory)
+		if set != nil {
+			set^ = {.Alloc, .Alloc_Non_Zeroed, .Free, .Free_All, .Resize, .Resize_Non_Zeroed, .Query_Features}
+		}
+		return nil, nil
+
+	case .Query_Info:
+		return nil, .Mode_Not_Implemented
+	}
+
+	return nil, nil
+}

+ 738 - 0
core/mem/tlsf/tlsf_internal.odin

@@ -0,0 +1,738 @@
+/*
+	Copyright 2024 Jeroen van Rijn <[email protected]>.
+	Made available under Odin's BSD-3 license.
+
+	List of contributors:
+		Matt Conte:      Original C implementation, see LICENSE file in this package
+		Jeroen van Rijn: Source port
+*/
+
+
+package mem_tlsf
+
+import "base:intrinsics"
+import "base:runtime"
+// import "core:fmt"
+
+// log2 of number of linear subdivisions of block sizes.
+// Larger values require more memory in the control structure.
+// Values of 4 or 5 are typical.
+TLSF_SL_INDEX_COUNT_LOG2 :: #config(TLSF_SL_INDEX_COUNT_LOG2, 5)
+
+// All allocation sizes and addresses are aligned to 4/8 bytes
+ALIGN_SIZE_LOG2 :: 3 when size_of(uintptr) == 8 else 2
+
+// We can increase this to support larger allocation sizes,
+// at the expense of more overhead in the TLSF structure
+FL_INDEX_MAX :: 32 when size_of(uintptr) == 8 else 30
+#assert(FL_INDEX_MAX < 36)
+
+ALIGN_SIZE          :: 1 << ALIGN_SIZE_LOG2
+SL_INDEX_COUNT      :: 1 << TLSF_SL_INDEX_COUNT_LOG2
+FL_INDEX_SHIFT      :: TLSF_SL_INDEX_COUNT_LOG2 + ALIGN_SIZE_LOG2
+FL_INDEX_COUNT      :: FL_INDEX_MAX - FL_INDEX_SHIFT + 1
+SMALL_BLOCK_SIZE    :: 1 << FL_INDEX_SHIFT
+
+/*
+We support allocations of sizes up to (1 << `FL_INDEX_MAX`) bits.
+However, because we linearly subdivide the second-level lists, and
+our minimum size granularity is 4 bytes, it doesn't make sense to
+create first-level lists for sizes smaller than `SL_INDEX_COUNT` * 4,
+or (1 << (`TLSF_SL_INDEX_COUNT_LOG2` + 2)) bytes, as there we will be
+trying to split size ranges into more slots than we have available.
+Instead, we calculate the minimum threshold size, and place all
+blocks below that size into the 0th first-level list.
+*/
+
+// SL_INDEX_COUNT must be <= number of bits in sl_bitmap's storage tree
+#assert(size_of(uint) * 8 >= SL_INDEX_COUNT)
+
+// Ensure we've properly tuned our sizes.
+#assert(ALIGN_SIZE == SMALL_BLOCK_SIZE / SL_INDEX_COUNT)
+
+#assert(size_of(Allocator) % ALIGN_SIZE == 0)
+
+Pool :: struct {
+	data:      []u8 `fmt:"-"`,
+	allocator: runtime.Allocator,
+	next:      ^Pool,
+}
+
+
+/*
+Block header structure.
+
+There are several implementation subtleties involved:
+- The `prev_phys_block` field is only valid if the previous block is free.
+- The `prev_phys_block` field is actually stored at the end of the
+	previous block. It appears at the beginning of this structure only to
+	simplify the implementation.
+- The `next_free` / `prev_free` fields are only valid if the block is free.
+*/
+Block_Header :: struct {
+	prev_phys_block: ^Block_Header,
+	size:            uint, // The size of this block, excluding the block header
+
+	// Next and previous free blocks.
+	next_free: ^Block_Header,
+	prev_free: ^Block_Header,
+}
+#assert(offset_of(Block_Header, prev_phys_block) == 0)
+
+/*
+Since block sizes are always at least a multiple of 4, the two least
+significant bits of the size field are used to store the block status:
+- bit 0: whether block is busy or free
+- bit 1: whether previous block is busy or free
+*/
+BLOCK_HEADER_FREE      :: uint(1 << 0)
+BLOCK_HEADER_PREV_FREE :: uint(1 << 1)
+
+/*
+The size of the block header exposed to used blocks is the `size` field.
+The `prev_phys_block` field is stored *inside* the previous free block.
+*/
+BLOCK_HEADER_OVERHEAD :: uint(size_of(uint))
+
+POOL_OVERHEAD :: 2 * BLOCK_HEADER_OVERHEAD
+
+// User data starts directly after the size field in a used block.
+BLOCK_START_OFFSET :: offset_of(Block_Header, size) + size_of(Block_Header{}.size)
+
+/*
+A free block must be large enough to store its header minus the size of
+the `prev_phys_block` field, and no larger than the number of addressable
+bits for `FL_INDEX`.
+*/
+BLOCK_SIZE_MIN :: uint(size_of(Block_Header) - size_of(^Block_Header))
+BLOCK_SIZE_MAX :: uint(1) << FL_INDEX_MAX
+
+/*
+	TLSF achieves O(1) cost for `alloc` and `free` operations by limiting
+	the search for a free block to a free list of guaranteed size
+	adequate to fulfill the request, combined with efficient free list
+	queries using bitmasks and architecture-specific bit-manipulation
+	routines.
+
+	NOTE: TLSF spec relies on ffs/fls returning value 0..31.
+*/
+
+@(require_results)
+ffs :: proc "contextless" (word: u32) -> (bit: i32) {
+	return -1 if word == 0 else i32(intrinsics.count_trailing_zeros(word))
+}
+
+@(require_results)
+fls :: proc "contextless" (word: u32) -> (bit: i32) {
+	N :: (size_of(u32) * 8) - 1
+	return i32(N - intrinsics.count_leading_zeros(word))
+}
+
+@(require_results)
+fls_uint :: proc "contextless" (size: uint) -> (bit: i32) {
+	N :: (size_of(uint) * 8) - 1
+	return i32(N - intrinsics.count_leading_zeros(size))
+}
+
+@(require_results)
+block_size :: proc "contextless" (block: ^Block_Header) -> (size: uint) {
+	return block.size &~ (BLOCK_HEADER_FREE | BLOCK_HEADER_PREV_FREE)
+}
+
+block_set_size :: proc "contextless" (block: ^Block_Header, size: uint) {
+	old_size := block.size
+	block.size = size | (old_size & (BLOCK_HEADER_FREE | BLOCK_HEADER_PREV_FREE))
+}
+
+@(require_results)
+block_is_last :: proc "contextless" (block: ^Block_Header) -> (is_last: bool) {
+	return block_size(block) == 0
+}
+
+@(require_results)
+block_is_free :: proc "contextless" (block: ^Block_Header) -> (is_free: bool) {
+	return (block.size & BLOCK_HEADER_FREE) == BLOCK_HEADER_FREE
+}
+
+block_set_free :: proc "contextless" (block: ^Block_Header) {
+	block.size |= BLOCK_HEADER_FREE
+}
+
+block_set_used :: proc "contextless" (block: ^Block_Header) {
+	block.size &~= BLOCK_HEADER_FREE
+}
+
+@(require_results)
+block_is_prev_free :: proc "contextless" (block: ^Block_Header) -> (is_prev_free: bool) {
+	return (block.size & BLOCK_HEADER_PREV_FREE) == BLOCK_HEADER_PREV_FREE
+}
+
+block_set_prev_free :: proc "contextless" (block: ^Block_Header) {
+	block.size |= BLOCK_HEADER_PREV_FREE
+}
+
+block_set_prev_used :: proc "contextless" (block: ^Block_Header) {
+	block.size &~= BLOCK_HEADER_PREV_FREE
+}
+
+@(require_results)
+block_from_ptr :: proc(ptr: rawptr) -> (block_ptr: ^Block_Header) {
+	return (^Block_Header)(uintptr(ptr) - BLOCK_START_OFFSET)
+}
+
+@(require_results)
+block_to_ptr   :: proc(block: ^Block_Header) -> (ptr: rawptr) {
+	return rawptr(uintptr(block) + BLOCK_START_OFFSET)
+}
+
+// Return location of next block after block of given size.
+@(require_results)
+offset_to_block :: proc(ptr: rawptr, size: uint) -> (block: ^Block_Header) {
+	return (^Block_Header)(uintptr(ptr) + uintptr(size))
+}
+
+@(require_results)
+offset_to_block_backwards :: proc(ptr: rawptr, size: uint) -> (block: ^Block_Header) {
+	return (^Block_Header)(uintptr(ptr) - uintptr(size))
+}
+
+// Return location of previous block.
+@(require_results)
+block_prev :: proc(block: ^Block_Header) -> (prev: ^Block_Header) {
+	assert(block_is_prev_free(block), "previous block must be free")
+	return block.prev_phys_block
+}
+
+// Return location of next existing block.
+@(require_results)
+block_next :: proc(block: ^Block_Header) -> (next: ^Block_Header) {
+	return offset_to_block(block_to_ptr(block), block_size(block) - BLOCK_HEADER_OVERHEAD)
+}
+
+// Link a new block with its physical neighbor, return the neighbor.
+@(require_results)
+block_link_next :: proc(block: ^Block_Header) -> (next: ^Block_Header) {
+	next = block_next(block)
+	next.prev_phys_block = block
+ 	return
+}
+
+block_mark_as_free :: proc(block: ^Block_Header) {
+	// Link the block to the next block, first.
+	next := block_link_next(block)
+	block_set_prev_free(next)
+	block_set_free(block)
+}
+
+block_mark_as_used :: proc(block: ^Block_Header) {
+	next := block_next(block)
+	block_set_prev_used(next)
+	block_set_used(block)
+}
+
+@(require_results)
+align_up :: proc(x, align: uint) -> (aligned: uint) {
+	assert(0 == (align & (align - 1)), "must align to a power of two")
+	return (x + (align - 1)) &~ (align - 1)
+}
+
+@(require_results)
+align_down :: proc(x, align: uint) -> (aligned: uint) {
+	assert(0 == (align & (align - 1)), "must align to a power of two")
+	return x - (x & (align - 1))
+}
+
+@(require_results)
+align_ptr :: proc(ptr: rawptr, align: uint) -> (aligned: rawptr) {
+	assert(0 == (align & (align - 1)), "must align to a power of two")
+	align_mask := uintptr(align) - 1
+	_ptr       := uintptr(ptr)
+	_aligned   := (_ptr + align_mask) &~ (align_mask)
+	return rawptr(_aligned)
+}
+
+// Adjust an allocation size to be aligned to word size, and no smaller than internal minimum.
+@(require_results)
+adjust_request_size :: proc(size, align: uint) -> (adjusted: uint) {
+	if size == 0 {
+		return 0
+	}
+
+	// aligned size must not exceed `BLOCK_SIZE_MAX`, or we'll go out of bounds on `sl_bitmap`.
+	if aligned := align_up(size, align); aligned < BLOCK_SIZE_MAX {
+		adjusted = min(aligned, BLOCK_SIZE_MAX)
+	}
+	return
+}
+
+// Adjust an allocation size to be aligned to word size, and no smaller than internal minimum.
+@(require_results)
+adjust_request_size_with_err :: proc(size, align: uint) -> (adjusted: uint, err: runtime.Allocator_Error) {
+	if size == 0 {
+		return 0, nil
+	}
+
+	// aligned size must not exceed `BLOCK_SIZE_MAX`, or we'll go out of bounds on `sl_bitmap`.
+	if aligned := align_up(size, align); aligned < BLOCK_SIZE_MAX {
+		adjusted = min(aligned, BLOCK_SIZE_MAX)
+	} else {
+		err = .Out_Of_Memory
+	}
+	return
+}
+
+// TLSF utility functions. In most cases these are direct translations of
+// the documentation in the research paper.
+
+@(optimization_mode="speed", require_results)
+mapping_insert :: proc(size: uint) -> (fl, sl: i32) {
+	if size < SMALL_BLOCK_SIZE {
+		// Store small blocks in first list.
+		sl = i32(size) / (SMALL_BLOCK_SIZE / SL_INDEX_COUNT)
+	} else {
+		fl = fls_uint(size)
+		sl = i32(size >> (uint(fl) - TLSF_SL_INDEX_COUNT_LOG2)) ~ (1 << TLSF_SL_INDEX_COUNT_LOG2)
+		fl -= (FL_INDEX_SHIFT - 1)
+	}
+	return
+}
+
+@(optimization_mode="speed", require_results)
+mapping_round :: #force_inline proc(size: uint) -> (rounded: uint) {
+	rounded = size
+	if size >= SMALL_BLOCK_SIZE {
+		round := uint(1 << (uint(fls_uint(size) - TLSF_SL_INDEX_COUNT_LOG2))) - 1
+		rounded += round
+	}
+	return
+}
+
+// This version rounds up to the next block size (for allocations)
+@(optimization_mode="speed", require_results)
+mapping_search :: proc(size: uint) -> (fl, sl: i32) {
+	return mapping_insert(mapping_round(size))
+}
+
+@(require_results)
+search_suitable_block :: proc(control: ^Allocator, fli, sli: ^i32) -> (block: ^Block_Header) {
+	// First, search for a block in the list associated with the given fl/sl index.
+	fl := fli^; sl := sli^
+
+	sl_map := control.sl_bitmap[fli^] & (~u32(0) << uint(sl))
+	if sl_map == 0 {
+		// No block exists. Search in the next largest first-level list.
+		fl_map := control.fl_bitmap & (~u32(0) << uint(fl + 1))
+		if fl_map == 0 {
+			// No free blocks available, memory has been exhausted.
+			return {}
+		}
+
+		fl = ffs(fl_map)
+		fli^ = fl
+		sl_map = control.sl_bitmap[fl]
+	}
+	assert(sl_map != 0, "internal error - second level bitmap is null")
+	sl = ffs(sl_map)
+	sli^ = sl
+
+	// Return the first block in the free list.
+	return control.blocks[fl][sl]
+}
+
+// Remove a free block from the free list.
+remove_free_block :: proc(control: ^Allocator, block: ^Block_Header, fl: i32, sl: i32) {
+	prev := block.prev_free
+	next := block.next_free
+	assert(prev != nil, "prev_free can not be nil")
+	assert(next != nil, "next_free can not be nil")
+	next.prev_free = prev
+	prev.next_free = next
+
+	// If this block is the head of the free list, set new head.
+	if control.blocks[fl][sl] == block {
+		control.blocks[fl][sl] = next
+
+		// If the new head is nil, clear the bitmap
+		if next == &control.block_null {
+			control.sl_bitmap[fl] &~= (u32(1) << uint(sl))
+
+			// If the second bitmap is now empty, clear the fl bitmap
+			if control.sl_bitmap[fl] == 0 {
+				control.fl_bitmap &~= (u32(1) << uint(fl))
+			}
+		}
+	}
+}
+
+// Insert a free block into the free block list.
+insert_free_block :: proc(control: ^Allocator, block: ^Block_Header, fl: i32, sl: i32) {
+	current := control.blocks[fl][sl]
+	assert(current != nil, "free lists cannot have a nil entry")
+	assert(block   != nil, "cannot insert a nil entry into the free list")
+	block.next_free = current
+	block.prev_free = &control.block_null
+	current.prev_free = block
+
+	assert(block_to_ptr(block) == align_ptr(block_to_ptr(block), ALIGN_SIZE), "block not properly aligned")
+
+	// Insert the new block at the head of the list, and mark the first- and second-level bitmaps appropriately.
+	control.blocks[fl][sl] = block
+	control.fl_bitmap     |= (u32(1) << uint(fl))
+	control.sl_bitmap[fl] |= (u32(1) << uint(sl))
+}
+
+// Remove a given block from the free list.
+block_remove :: proc(control: ^Allocator, block: ^Block_Header) {
+	fl, sl := mapping_insert(block_size(block))
+	remove_free_block(control, block, fl, sl)
+}
+
+// Insert a given block into the free list.
+block_insert :: proc(control: ^Allocator, block: ^Block_Header) {
+	fl, sl := mapping_insert(block_size(block))
+	insert_free_block(control, block, fl, sl)
+}
+
+@(require_results)
+block_can_split :: proc(block: ^Block_Header, size: uint) -> (can_split: bool) {
+	return block_size(block) >= size_of(Block_Header) + size
+}
+
+// Split a block into two, the second of which is free.
+@(require_results)
+block_split :: proc(block: ^Block_Header, size: uint) -> (remaining: ^Block_Header) {
+	// Calculate the amount of space left in the remaining block.
+	remaining = offset_to_block(block_to_ptr(block), size - BLOCK_HEADER_OVERHEAD)
+
+	remain_size := block_size(block) - (size + BLOCK_HEADER_OVERHEAD)
+
+	assert(block_to_ptr(remaining) == align_ptr(block_to_ptr(remaining), ALIGN_SIZE),
+		"remaining block not aligned properly")
+
+	assert(block_size(block) == remain_size + size + BLOCK_HEADER_OVERHEAD)
+	block_set_size(remaining, remain_size)
+	assert(block_size(remaining) >= BLOCK_SIZE_MIN, "block split with invalid size")
+
+	block_set_size(block, size)
+	block_mark_as_free(remaining)
+
+	return remaining
+}
+
+// Absorb a free block's storage into an adjacent previous free block.
+@(require_results)
+block_absorb :: proc(prev: ^Block_Header, block: ^Block_Header) -> (absorbed: ^Block_Header) {
+	assert(!block_is_last(prev), "previous block can't be last")
+	// Note: Leaves flags untouched.
+	prev.size += block_size(block) + BLOCK_HEADER_OVERHEAD
+	_ = block_link_next(prev)
+	return prev
+}
+
+// Merge a just-freed block with an adjacent previous free block.
+@(require_results)
+block_merge_prev :: proc(control: ^Allocator, block: ^Block_Header) -> (merged: ^Block_Header) {
+	merged = block
+	if (block_is_prev_free(block)) {
+		prev := block_prev(block)
+		assert(prev != nil,         "prev physical block can't be nil")
+		assert(block_is_free(prev), "prev block is not free though marked as such")
+		block_remove(control, prev)
+		merged = block_absorb(prev, block)
+	}
+	return merged
+}
+
+// Merge a just-freed block with an adjacent free block.
+@(require_results)
+block_merge_next :: proc(control: ^Allocator, block: ^Block_Header) -> (merged: ^Block_Header) {
+	merged = block
+	next  := block_next(block)
+	assert(next != nil, "next physical block can't be nil")
+
+	if (block_is_free(next)) {
+		assert(!block_is_last(block), "previous block can't be last")
+		block_remove(control, next)
+		merged = block_absorb(block, next)
+	}
+	return merged
+}
+
+// Trim any trailing block space off the end of a free block, return to pool.
+block_trim_free :: proc(control: ^Allocator, block: ^Block_Header, size: uint) {
+	assert(block_is_free(block), "block must be free")
+	if (block_can_split(block, size)) {
+		remaining_block := block_split(block, size)
+		_ = block_link_next(block)
+		block_set_prev_free(remaining_block)
+		block_insert(control, remaining_block)
+	}
+}
+
+// Trim any trailing block space off the end of a used block, return to pool.
+block_trim_used :: proc(control: ^Allocator, block: ^Block_Header, size: uint) {
+	assert(!block_is_free(block), "Block must be used")
+	if (block_can_split(block, size)) {
+		// If the next block is free, we must coalesce.
+		remaining_block := block_split(block, size)
+		block_set_prev_used(remaining_block)
+
+		remaining_block = block_merge_next(control, remaining_block)
+		block_insert(control, remaining_block)
+	}
+}
+
+// Trim leading block space, return to pool.
+@(require_results)
+block_trim_free_leading :: proc(control: ^Allocator, block: ^Block_Header, size: uint) -> (remaining: ^Block_Header) {
+	remaining = block
+	if block_can_split(block, size) {
+		// We want the 2nd block.
+		remaining = block_split(block, size - BLOCK_HEADER_OVERHEAD)
+		block_set_prev_free(remaining)
+
+		_ = block_link_next(block)
+		block_insert(control, block)
+	}
+	return remaining
+}
+
+@(require_results)
+block_locate_free :: proc(control: ^Allocator, size: uint) -> (block: ^Block_Header) {
+	fl, sl: i32
+	if size != 0 {
+		fl, sl = mapping_search(size)
+
+		/*
+		`mapping_search` can futz with the size, so for excessively large sizes it can sometimes wind up
+		with indices that are off the end of the block array. So, we protect against that here,
+		since this is the only call site of `mapping_search`. Note that we don't need to check `sl`,
+		as it comes from a modulo operation that guarantees it's always in range.
+		*/
+		if fl < FL_INDEX_COUNT {
+			block = search_suitable_block(control, &fl, &sl)
+		}
+	}
+
+	if block != nil {
+		assert(block_size(block) >= size)
+		remove_free_block(control, block, fl, sl)
+	}
+	return block
+}
+
+@(require_results)
+block_prepare_used :: proc(control: ^Allocator, block: ^Block_Header, size: uint) -> (res: []byte, err: runtime.Allocator_Error) {
+	if block != nil {
+		assert(size != 0, "Size must be non-zero")
+		block_trim_free(control, block, size)
+		block_mark_as_used(block)
+		res = ([^]byte)(block_to_ptr(block))[:size]
+	}
+	return
+}
+
+// Clear control structure and point all empty lists at the null block
+clear :: proc(control: ^Allocator) {
+	control.block_null.next_free = &control.block_null
+	control.block_null.prev_free = &control.block_null
+
+	control.fl_bitmap = 0
+	for i in 0..<FL_INDEX_COUNT {
+		control.sl_bitmap[i] = 0
+		for j in 0..<SL_INDEX_COUNT {
+			control.blocks[i][j] = &control.block_null
+		}
+	}
+}
+
+@(require_results)
+pool_add :: proc(control: ^Allocator, pool: []u8) -> (err: Error) {
+	assert(uintptr(raw_data(pool)) % ALIGN_SIZE == 0, "Added memory must be aligned")
+
+	pool_overhead := POOL_OVERHEAD
+	pool_bytes := align_down(len(pool) - pool_overhead, ALIGN_SIZE)
+
+	if pool_bytes < BLOCK_SIZE_MIN {
+		return .Backing_Buffer_Too_Small
+	} else if pool_bytes > BLOCK_SIZE_MAX {
+		return .Backing_Buffer_Too_Large
+	}
+
+	// Create the main free block. Offset the start of the block slightly,
+	// so that the `prev_phys_block` field falls outside of the pool -
+	// it will never be used.
+	block := offset_to_block_backwards(raw_data(pool), BLOCK_HEADER_OVERHEAD)
+
+	block_set_size(block, pool_bytes)
+	block_set_free(block)
+	block_set_prev_used(block)
+	block_insert(control, block)
+
+	// Split the block to create a zero-size sentinel block
+	next := block_link_next(block)
+	block_set_size(next, 0)
+	block_set_used(next)
+	block_set_prev_free(next)
+	return
+}
+
+pool_remove :: proc(control: ^Allocator, pool: []u8) {
+	block := offset_to_block_backwards(raw_data(pool), BLOCK_HEADER_OVERHEAD)
+
+	assert(block_is_free(block),               "Block should be free")
+	assert(!block_is_free(block_next(block)),  "Next block should not be free")
+	assert(block_size(block_next(block)) == 0, "Next block size should be zero")
+
+	fl, sl := mapping_insert(block_size(block))
+	remove_free_block(control, block, fl, sl)
+}
+
+@(require_results)
+alloc_bytes_non_zeroed :: proc(control: ^Allocator, size: uint, align: uint) -> (res: []byte, err: runtime.Allocator_Error) {
+	assert(control != nil)
+	adjust := adjust_request_size(size, ALIGN_SIZE)
+
+	GAP_MINIMUM :: size_of(Block_Header)
+	size_with_gap := adjust_request_size(adjust + align + GAP_MINIMUM, align)
+
+	aligned_size := size_with_gap if adjust != 0 && align > ALIGN_SIZE else adjust
+	if aligned_size == 0 && size > 0 {
+		return nil, .Out_Of_Memory
+	}
+
+	block  := block_locate_free(control, aligned_size)
+	if block == nil {
+		return nil, .Out_Of_Memory
+	}
+	ptr := block_to_ptr(block)
+	aligned := align_ptr(ptr, align)
+	gap := uint(int(uintptr(aligned)) - int(uintptr(ptr)))
+
+	if gap != 0 && gap < GAP_MINIMUM {
+		gap_remain := GAP_MINIMUM - gap
+		offset := uintptr(max(gap_remain, align))
+		next_aligned := rawptr(uintptr(aligned) + offset)
+
+		aligned = align_ptr(next_aligned, align)
+
+		gap = uint(int(uintptr(aligned)) - int(uintptr(ptr)))
+	}
+
+	if gap != 0 {
+		assert(gap >= GAP_MINIMUM, "gap size too small")
+		block = block_trim_free_leading(control, block, gap)
+	}
+
+	return block_prepare_used(control, block, adjust)
+}
+
+@(require_results)
+alloc_bytes :: proc(control: ^Allocator, size: uint, align: uint) -> (res: []byte, err: runtime.Allocator_Error) {
+	res, err = alloc_bytes_non_zeroed(control, size, align)
+	if err != nil {
+		intrinsics.mem_zero(raw_data(res), len(res))
+	}
+	return
+}
+
+
+free_with_size :: proc(control: ^Allocator, ptr: rawptr, size: uint) {
+	assert(control != nil)
+	// `size` is currently ignored
+	if ptr == nil {
+		return
+	}
+
+	block := block_from_ptr(ptr)
+	assert(!block_is_free(block), "block already marked as free") // double free
+	block_mark_as_free(block)
+	block = block_merge_prev(control, block)
+	block = block_merge_next(control, block)
+	block_insert(control, block)
+}
+
+
+@(require_results)
+resize :: proc(control: ^Allocator, ptr: rawptr, old_size, new_size: uint, alignment: uint) -> (res: []byte, err: runtime.Allocator_Error) {
+	assert(control != nil)
+	if ptr != nil && new_size == 0 {
+		free_with_size(control, ptr, old_size)
+		return
+	} else if ptr == nil {
+		return alloc_bytes(control, new_size, alignment)
+	}
+
+	block := block_from_ptr(ptr)
+	next := block_next(block)
+
+	curr_size := block_size(block)
+	combined := curr_size + block_size(next) + BLOCK_HEADER_OVERHEAD
+	adjust := adjust_request_size(new_size, max(ALIGN_SIZE, alignment))
+
+	assert(!block_is_free(block), "block already marked as free") // double free
+
+	min_size := min(curr_size, new_size, old_size)
+
+	if adjust > curr_size && (!block_is_free(next) || adjust > combined) {
+		res = alloc_bytes(control, new_size, alignment) or_return
+		if res != nil {
+			copy(res, ([^]byte)(ptr)[:min_size])
+			free_with_size(control, ptr, curr_size)
+		}
+		return
+	}
+	if adjust > curr_size {
+		_ = block_merge_next(control, block)
+		block_mark_as_used(block)
+	}
+
+	block_trim_used(control, block, adjust)
+	res = ([^]byte)(ptr)[:new_size]
+
+	if min_size < new_size {
+		to_zero := ([^]byte)(ptr)[min_size:new_size]
+		runtime.mem_zero(raw_data(to_zero), len(to_zero))
+	}
+	return
+}
+
+@(require_results)
+resize_non_zeroed :: proc(control: ^Allocator, ptr: rawptr, old_size, new_size: uint, alignment: uint) -> (res: []byte, err: runtime.Allocator_Error) {
+	assert(control != nil)
+	if ptr != nil && new_size == 0 {
+		free_with_size(control, ptr, old_size)
+		return
+	} else if ptr == nil {
+		return alloc_bytes_non_zeroed(control, new_size, alignment)
+	}
+
+	block := block_from_ptr(ptr)
+	next := block_next(block)
+
+	curr_size := block_size(block)
+	combined := curr_size + block_size(next) + BLOCK_HEADER_OVERHEAD
+	adjust := adjust_request_size(new_size, max(ALIGN_SIZE, alignment))
+
+	assert(!block_is_free(block), "block already marked as free") // double free
+
+	min_size := min(curr_size, new_size, old_size)
+
+	if adjust > curr_size && (!block_is_free(next) || adjust > combined) {
+		res = alloc_bytes_non_zeroed(control, new_size, alignment) or_return
+		if res != nil {
+			copy(res, ([^]byte)(ptr)[:min_size])
+			free_with_size(control, ptr, old_size)
+		}
+		return
+	}
+
+	if adjust > curr_size {
+		_ = block_merge_next(control, block)
+		block_mark_as_used(block)
+	}
+
+	block_trim_used(control, block, adjust)
+	res = ([^]byte)(ptr)[:new_size]
+	return
+}

+ 14 - 0
core/mem/tracking_allocator.odin

@@ -47,6 +47,7 @@ tracking_allocator_destroy :: proc(t: ^Tracking_Allocator) {
 }
 }
 
 
 
 
+// Clear only the current allocation data while keeping the totals intact.
 tracking_allocator_clear :: proc(t: ^Tracking_Allocator) {
 tracking_allocator_clear :: proc(t: ^Tracking_Allocator) {
 	sync.mutex_lock(&t.mutex)
 	sync.mutex_lock(&t.mutex)
 	clear(&t.allocation_map)
 	clear(&t.allocation_map)
@@ -55,6 +56,19 @@ tracking_allocator_clear :: proc(t: ^Tracking_Allocator) {
 	sync.mutex_unlock(&t.mutex)
 	sync.mutex_unlock(&t.mutex)
 }
 }
 
 
+// Reset all of a Tracking Allocator's allocation data back to zero.
+tracking_allocator_reset :: proc(t: ^Tracking_Allocator) {
+	sync.mutex_lock(&t.mutex)
+	clear(&t.allocation_map)
+	clear(&t.bad_free_array)
+	t.total_memory_allocated = 0
+	t.total_allocation_count = 0
+	t.total_memory_freed = 0
+	t.total_free_count = 0
+	t.peak_memory_allocated = 0
+	t.current_memory_allocated = 0
+	sync.mutex_unlock(&t.mutex)
+}
 
 
 @(require_results)
 @(require_results)
 tracking_allocator :: proc(data: ^Tracking_Allocator) -> Allocator {
 tracking_allocator :: proc(data: ^Tracking_Allocator) -> Allocator {

+ 1 - 1
core/odin/ast/ast.odin

@@ -753,7 +753,7 @@ Array_Type :: struct {
 	using node: Expr,
 	using node: Expr,
 	open:  tokenizer.Pos,
 	open:  tokenizer.Pos,
 	tag:   ^Expr,
 	tag:   ^Expr,
-	len:   ^Expr, // Ellipsis node for [?]T arrray types, nil for slice types
+	len:   ^Expr, // Ellipsis node for [?]T array types, nil for slice types
 	close: tokenizer.Pos,
 	close: tokenizer.Pos,
 	elem:  ^Expr,
 	elem:  ^Expr,
 }
 }

+ 2 - 0
core/odin/ast/clone.odin

@@ -278,7 +278,9 @@ clone_node :: proc(node: ^Node) -> ^Node {
 			r.foreign_library = clone(r.foreign_library)
 			r.foreign_library = clone(r.foreign_library)
 			r.body            = clone(r.body)
 			r.body            = clone(r.body)
 		case ^Foreign_Import_Decl:
 		case ^Foreign_Import_Decl:
+			r.attributes = clone_dynamic_array(r.attributes)
 			r.name = auto_cast clone(r.name)
 			r.name = auto_cast clone(r.name)
+			r.fullpaths  = clone_array(r.fullpaths)
 		case ^Proc_Group:
 		case ^Proc_Group:
 			r.args = clone(r.args)
 			r.args = clone(r.args)
 		case ^Attribute:
 		case ^Attribute:

+ 1 - 0
core/odin/ast/walk.odin

@@ -320,6 +320,7 @@ walk :: proc(v: ^Visitor, node: ^Node) {
 		if n.comment != nil {
 		if n.comment != nil {
 			walk(v, n.comment)
 			walk(v, n.comment)
 		}
 		}
+		walk_expr_list(v, n.fullpaths)
 
 
 	case ^Proc_Group:
 	case ^Proc_Group:
 		walk_expr_list(v, n.args)
 		walk_expr_list(v, n.args)

+ 37 - 2
core/odin/parser/parser.odin

@@ -1204,7 +1204,7 @@ parse_foreign_decl :: proc(p: ^Parser) -> ^ast.Decl {
 			path := expect_token(p, .String)
 			path := expect_token(p, .String)
 			reserve(&fullpaths, 1)
 			reserve(&fullpaths, 1)
 			bl := ast.new(ast.Basic_Lit, path.pos, end_pos(path))
 			bl := ast.new(ast.Basic_Lit, path.pos, end_pos(path))
-			bl.tok = tok
+			bl.tok = path
 			append(&fullpaths, bl)
 			append(&fullpaths, bl)
 		}
 		}
 
 
@@ -1455,7 +1455,7 @@ parse_stmt :: proc(p: ^Parser) -> ^ast.Stmt {
 		case "unroll":
 		case "unroll":
 			return parse_unrolled_for_loop(p, tag)
 			return parse_unrolled_for_loop(p, tag)
 		case "reverse":
 		case "reverse":
-			stmt := parse_for_stmt(p)
+			stmt := parse_stmt(p)
 
 
 			if range, is_range := stmt.derived.(^ast.Range_Stmt); is_range {
 			if range, is_range := stmt.derived.(^ast.Range_Stmt); is_range {
 				if range.reverse {
 				if range.reverse {
@@ -3515,6 +3515,25 @@ parse_simple_stmt :: proc(p: ^Parser, flags: Stmt_Allow_Flags) -> ^ast.Stmt {
 	case op.kind == .Colon:
 	case op.kind == .Colon:
 		expect_token_after(p, .Colon, "identifier list")
 		expect_token_after(p, .Colon, "identifier list")
 		if .Label in flags && len(lhs) == 1 {
 		if .Label in flags && len(lhs) == 1 {
+			is_partial := false
+			is_reverse := false
+
+			partial_token: tokenizer.Token
+			if p.curr_tok.kind == .Hash {
+				name := peek_token(p)
+				if name.kind == .Ident && name.text == "partial" &&
+				   peek_token(p, 1).kind == .Switch {
+					partial_token = expect_token(p, .Hash)
+					expect_token(p, .Ident)
+					is_partial = true
+				} else if name.kind == .Ident && name.text == "reverse" &&
+				          peek_token(p, 1).kind == .For {
+					partial_token = expect_token(p, .Hash)
+					expect_token(p, .Ident)
+					is_reverse = true
+				}
+			}
+
 			#partial switch p.curr_tok.kind {
 			#partial switch p.curr_tok.kind {
 			case .Open_Brace, .If, .For, .Switch:
 			case .Open_Brace, .If, .For, .Switch:
 				label := lhs[0]
 				label := lhs[0]
@@ -3529,6 +3548,22 @@ parse_simple_stmt :: proc(p: ^Parser, flags: Stmt_Allow_Flags) -> ^ast.Stmt {
 					case ^ast.Type_Switch_Stmt: n.label = label
 					case ^ast.Type_Switch_Stmt: n.label = label
 					case ^ast.Range_Stmt:	    n.label = label
 					case ^ast.Range_Stmt:	    n.label = label
 					}
 					}
+
+					if is_partial {
+						#partial switch n in stmt.derived_stmt {
+						case ^ast.Switch_Stmt:      n.partial = true
+						case ^ast.Type_Switch_Stmt: n.partial = true
+						case:
+							error(p, partial_token.pos, "incorrect use of directive, use '%s: #partial switch'", partial_token.text)
+						}
+					}
+					if is_reverse {
+						#partial switch n in stmt.derived_stmt {
+						case ^ast.Range_Stmt: n.reverse = true
+						case:
+							error(p, partial_token.pos, "incorrect use of directive, use '%s: #reverse for'", partial_token.text)
+						}
+					}
 				}
 				}
 
 
 				return stmt
 				return stmt

+ 5 - 1
core/os/dir_windows.odin

@@ -87,8 +87,12 @@ read_dir :: proc(fd: Handle, n: int, allocator := context.allocator) -> (fi: []F
 
 
 	find_data := &win32.WIN32_FIND_DATAW{}
 	find_data := &win32.WIN32_FIND_DATAW{}
 	find_handle := win32.FindFirstFileW(raw_data(wpath_search), find_data)
 	find_handle := win32.FindFirstFileW(raw_data(wpath_search), find_data)
+	if find_handle == win32.INVALID_HANDLE_VALUE {
+		err = Errno(win32.GetLastError())
+		return dfi[:], err
+	}
 	defer win32.FindClose(find_handle)
 	defer win32.FindClose(find_handle)
-	for n != 0 && find_handle != nil {
+	for n != 0 {
 		fi: File_Info
 		fi: File_Info
 		fi = find_data_to_file_info(path, find_data)
 		fi = find_data_to_file_info(path, find_data)
 		if fi.name != "" {
 		if fi.name != "" {

+ 1 - 1
core/os/os2/internal_util.odin

@@ -111,7 +111,7 @@ next_random :: proc(r: ^[2]u64) -> u64 {
 
 
 @(require_results)
 @(require_results)
 random_string :: proc(buf: []byte) -> string {
 random_string :: proc(buf: []byte) -> string {
-	@static digits := "0123456789"
+	@(static, rodata) digits := "0123456789"
 
 
 	u := next_random(&random_string_seed)
 	u := next_random(&random_string_seed)
 
 

+ 1 - 1
core/os/os_darwin.odin

@@ -442,7 +442,7 @@ F_GETPATH :: 50 // return the full path of the fd
 foreign libc {
 foreign libc {
 	@(link_name="__error") __error :: proc() -> ^c.int ---
 	@(link_name="__error") __error :: proc() -> ^c.int ---
 
 
-	@(link_name="open")             _unix_open          :: proc(path: cstring, flags: i32, mode: u16) -> Handle ---
+	@(link_name="open")             _unix_open          :: proc(path: cstring, flags: i32, #c_vararg args: ..any) -> Handle ---
 	@(link_name="close")            _unix_close         :: proc(handle: Handle) -> c.int ---
 	@(link_name="close")            _unix_close         :: proc(handle: Handle) -> c.int ---
 	@(link_name="read")             _unix_read          :: proc(handle: Handle, buffer: rawptr, count: c.size_t) -> int ---
 	@(link_name="read")             _unix_read          :: proc(handle: Handle, buffer: rawptr, count: c.size_t) -> int ---
 	@(link_name="write")            _unix_write         :: proc(handle: Handle, buffer: rawptr, count: c.size_t) -> int ---
 	@(link_name="write")            _unix_write         :: proc(handle: Handle, buffer: rawptr, count: c.size_t) -> int ---

+ 6 - 2
core/os/os_netbsd.odin

@@ -5,7 +5,6 @@ foreign import libc "system:c"
 
 
 import "base:runtime"
 import "base:runtime"
 import "core:strings"
 import "core:strings"
-import "core:sys/unix"
 import "core:c"
 import "core:c"
 
 
 Handle :: distinct i32
 Handle :: distinct i32
@@ -328,6 +327,11 @@ foreign dl {
 	@(link_name="dlerror")          _unix_dlerror       :: proc() -> cstring ---
 	@(link_name="dlerror")          _unix_dlerror       :: proc() -> cstring ---
 }
 }
 
 
+@(private)
+foreign libc {
+	_lwp_self :: proc() -> i32 ---
+}
+
 // NOTE(phix): Perhaps share the following functions with FreeBSD if they turn out to be the same in the end.
 // NOTE(phix): Perhaps share the following functions with FreeBSD if they turn out to be the same in the end.
 
 
 is_path_separator :: proc(r: rune) -> bool {
 is_path_separator :: proc(r: rune) -> bool {
@@ -721,7 +725,7 @@ exit :: proc "contextless" (code: int) -> ! {
 }
 }
 
 
 current_thread_id :: proc "contextless" () -> int {
 current_thread_id :: proc "contextless" () -> int {
-	return cast(int) unix.pthread_self()
+	return int(_lwp_self())
 }
 }
 
 
 dlopen :: proc(filename: string, flags: int) -> rawptr {
 dlopen :: proc(filename: string, flags: int) -> rawptr {

+ 49 - 0
core/simd/x86/aes.odin

@@ -0,0 +1,49 @@
+//+build i386, amd64
+package simd_x86
+
+@(require_results, enable_target_feature = "aes")
+_mm_aesdec :: #force_inline proc "c" (a, b: __m128i) -> __m128i {
+	return aesdec(a, b)
+}
+
+@(require_results, enable_target_feature = "aes")
+_mm_aesdeclast :: #force_inline proc "c" (a, b: __m128i) -> __m128i {
+	return aesdeclast(a, b)
+}
+
+@(require_results, enable_target_feature = "aes")
+_mm_aesenc :: #force_inline proc "c" (a, b: __m128i) -> __m128i {
+	return aesenc(a, b)
+}
+
+@(require_results, enable_target_feature = "aes")
+_mm_aesenclast :: #force_inline proc "c" (a, b: __m128i) -> __m128i {
+	return aesenclast(a, b)
+}
+
+@(require_results, enable_target_feature = "aes")
+_mm_aesimc :: #force_inline proc "c" (a: __m128i) -> __m128i {
+	return aesimc(a)
+}
+
+@(require_results, enable_target_feature = "aes")
+_mm_aeskeygenassist :: #force_inline proc "c" (a: __m128i, $IMM8: u8) -> __m128i {
+	return aeskeygenassist(a, u8(IMM8))
+}
+
+
+@(private, default_calling_convention = "none")
+foreign _ {
+	@(link_name = "llvm.x86.aesni.aesdec")
+	aesdec :: proc(a, b: __m128i) -> __m128i ---
+	@(link_name = "llvm.x86.aesni.aesdeclast")
+	aesdeclast :: proc(a, b: __m128i) -> __m128i ---
+	@(link_name = "llvm.x86.aesni.aesenc")
+	aesenc :: proc(a, b: __m128i) -> __m128i ---
+	@(link_name = "llvm.x86.aesni.aesenclast")
+	aesenclast :: proc(a, b: __m128i) -> __m128i ---
+	@(link_name = "llvm.x86.aesni.aesimc")
+	aesimc :: proc(a: __m128i) -> __m128i ---
+	@(link_name = "llvm.x86.aesni.aeskeygenassist")
+	aeskeygenassist :: proc(a: __m128i, imm8: u8) -> __m128i ---
+}

+ 105 - 0
core/slice/permute.odin

@@ -0,0 +1,105 @@
+package slice
+
+import "base:runtime"
+
+// An in-place permutation iterator.
+Permutation_Iterator :: struct($T: typeid) {
+	index: int,
+	slice: []T,
+	counters: []int,
+}
+
+/*
+Make an iterator to permute a slice in-place.
+
+*Allocates Using Provided Allocator*
+
+This procedure allocates some state to assist in permutation and does not make
+a copy of the underlying slice. If you want to permute a slice without altering
+the underlying data, use `clone` to create a copy, then permute that instead.
+
+Inputs:
+- slice: The slice to permute.
+- allocator: (default is context.allocator)
+
+Returns:
+- iter: The iterator, to be passed to `permute`.
+- error: An `Allocator_Error`, if allocation failed.
+*/
+make_permutation_iterator :: proc(
+	slice: []$T,
+	allocator := context.allocator,
+) -> (
+	iter: Permutation_Iterator(T),
+	error: runtime.Allocator_Error,
+) #optional_allocator_error {
+	iter.slice = slice
+	iter.counters = make([]int, len(iter.slice), allocator) or_return
+
+	return
+}
+/*
+Free the state allocated by `make_permutation_iterator`.
+
+Inputs:
+- iter: The iterator created by `make_permutation_iterator`.
+- allocator: The allocator used to create the iterator. (default is context.allocator)
+*/
+destroy_permutation_iterator :: proc(
+	iter: Permutation_Iterator($T),
+	allocator := context.allocator,
+) {
+	delete(iter.counters, allocator = allocator)
+}
+/*
+Permute a slice in-place.
+
+Note that the first iteration will always be the original, unpermuted slice.
+
+Inputs:
+- iter: The iterator created by `make_permutation_iterator`.
+
+Returns:
+- ok: True if the permutation succeeded, false if the iteration is complete.
+*/
+permute :: proc(iter: ^Permutation_Iterator($T)) -> (ok: bool) {
+	// This is an iterative, resumable implementation of Heap's algorithm.
+	//
+	// The original algorithm was described by B. R. Heap as "Permutations by
+	// interchanges" in The Computer Journal, 1963.
+	//
+	// This implementation is based on the nonrecursive version described by
+	// Robert Sedgewick in "Permutation Generation Methods" which was published
+	// in ACM Computing Surveys in 1977.
+
+	i := iter.index
+
+	if i == 0 {
+		iter.index = 1
+		return true
+	}
+
+	n := len(iter.counters)
+	#no_bounds_check for i < n {
+		if iter.counters[i] < i {
+			if i & 1 == 0 {
+				iter.slice[0], iter.slice[i] = iter.slice[i], iter.slice[0]
+			} else {
+				iter.slice[iter.counters[i]], iter.slice[i] = iter.slice[i], iter.slice[iter.counters[i]]
+			}
+
+			iter.counters[i] += 1
+			i = 1
+
+			break
+		} else {
+			iter.counters[i] = 0
+			i += 1
+		}
+	}
+	if i == n {
+		return false
+	}
+	iter.index = i
+	return true
+}

+ 1 - 1
core/strconv/generic_float.odin

@@ -375,7 +375,7 @@ decimal_to_float_bits :: proc(d: ^decimal.Decimal, info: ^Float_Info) -> (b: u64
 		return
 		return
 	}
 	}
 
 
-	@static power_table := [?]int{1, 3, 6, 9, 13, 16, 19, 23, 26}
+	@(static, rodata) power_table := [?]int{1, 3, 6, 9, 13, 16, 19, 23, 26}
 
 
 	exp = 0
 	exp = 0
 	for d.decimal_point > 0 {
 	for d.decimal_point > 0 {

+ 284 - 8
core/strconv/strconv.odin

@@ -835,17 +835,21 @@ Example:
 
 
 		n, _, ok = strconv.parse_f64_prefix("12.34e2")
 		n, _, ok = strconv.parse_f64_prefix("12.34e2")
 		fmt.printfln("%.3f %v", n, ok)
 		fmt.printfln("%.3f %v", n, ok)
+
+		n, _, ok = strconv.parse_f64_prefix("13.37 hellope")
+		fmt.printfln("%.3f %v", n, ok)
 	}
 	}
 
 
 Output:
 Output:
 
 
 	0.000 false
 	0.000 false
 	1234.000 true
 	1234.000 true
+	13.370 true
 
 
 **Returns**  
 **Returns**  
 - value: The parsed 64-bit floating point number.
 - value: The parsed 64-bit floating point number.
 - nr: The length of the parsed substring.
 - nr: The length of the parsed substring.
-- ok: `false` if a base 10 float could not be found, or if the input string contained more than just the number.
+- ok: `false` if a base 10 float could not be found
 */
 */
 parse_f64_prefix :: proc(str: string) -> (value: f64, nr: int, ok: bool) {
 parse_f64_prefix :: proc(str: string) -> (value: f64, nr: int, ok: bool) {
 	common_prefix_len_ignore_case :: proc "contextless" (s, prefix: string) -> int {
 	common_prefix_len_ignore_case :: proc "contextless" (s, prefix: string) -> int {
@@ -878,13 +882,16 @@ parse_f64_prefix :: proc(str: string) -> (value: f64, nr: int, ok: bool) {
 				s = s[1:]
 				s = s[1:]
 				fallthrough
 				fallthrough
 			case 'i', 'I':
 			case 'i', 'I':
-				n = common_prefix_len_ignore_case(s, "infinity")
-				if 3 < n && n < 8 { // "inf" or "infinity"
-					n = 3
-				}
-				if n == 3 || n == 8 {
+				m := common_prefix_len_ignore_case(s, "infinity")
+				if 3 <= m && m < 9 { // "inf" to "infinity"
 					f = 0h7ff00000_00000000 if sign == 1 else 0hfff00000_00000000
 					f = 0h7ff00000_00000000 if sign == 1 else 0hfff00000_00000000
-					n = nsign + 3
+					if m == 8 {
+						// We only count the entire prefix if it is precisely "infinity".
+						n = nsign + m
+					} else {
+						// The string was either only "inf" or incomplete.
+						n = nsign + 3
+					}
 					ok = true
 					ok = true
 					return
 					return
 				}
 				}
@@ -1088,7 +1095,7 @@ parse_f64_prefix :: proc(str: string) -> (value: f64, nr: int, ok: bool) {
 	}
 	}
 
 
 	trunc_block: if !trunc {
 	trunc_block: if !trunc {
-		@static pow10 := [?]f64{
+		@(static, rodata) pow10 := [?]f64{
 			1e0,  1e1,  1e2,  1e3,  1e4,  1e5,  1e6,  1e7,  1e8,  1e9,
 			1e0,  1e1,  1e2,  1e3,  1e4,  1e5,  1e6,  1e7,  1e8,  1e9,
 			1e10, 1e11, 1e12, 1e13, 1e14, 1e15, 1e16, 1e17, 1e18, 1e19,
 			1e10, 1e11, 1e12, 1e13, 1e14, 1e15, 1e16, 1e17, 1e18, 1e19,
 			1e20, 1e21, 1e22,
 			1e20, 1e21, 1e22,
@@ -1124,6 +1131,275 @@ parse_f64_prefix :: proc(str: string) -> (value: f64, nr: int, ok: bool) {
 	ok = !overflow
 	ok = !overflow
 	return
 	return
 }
 }
+/*
+Parses a 128-bit complex number from a string
+
+**Inputs**  
+- str: The input string containing a 128-bit complex number.
+- n: An optional pointer to an int to store the length of the parsed substring (default: nil).
+
+Example:
+
+	import "core:fmt"
+	import "core:strconv"
+	parse_complex128_example :: proc() {
+		n: int
+		c, ok := strconv.parse_complex128("3+1i", &n)
+		fmt.printfln("%v %i %t", c, n, ok)
+
+		c, ok = strconv.parse_complex128("5+7i hellope", &n)
+		fmt.printfln("%v %i %t", c, n, ok)
+	}
+	
+Output:
+
+	3+1i 4 true
+	5+7i 4 false
+	
+**Returns**  
+- value: The parsed 128-bit complex number.
+- ok: `false` if a complex number could not be found, or if the input string contained more than just the number.
+*/
+parse_complex128 :: proc(str: string, n: ^int = nil) -> (value: complex128, ok: bool) {
+	real_value, imag_value: f64
+	nr_r, nr_i: int
+
+	real_value, nr_r, _ = parse_f64_prefix(str)
+	imag_value, nr_i, _ = parse_f64_prefix(str[nr_r:])
+
+	i_parsed := len(str) >= nr_r + nr_i + 1 && str[nr_r + nr_i] == 'i'
+	if !i_parsed {
+		// No `i` means we refuse to treat the second float we parsed as an
+		// imaginary value.
+		imag_value = 0
+		nr_i = 0
+	}
+
+	ok = i_parsed && len(str) == nr_r + nr_i + 1
+
+	if n != nil {
+		n^ = nr_r + nr_i + (1 if i_parsed else 0)
+	}
+
+	value = complex(real_value, imag_value)
+	return 
+}
+/*
+Parses a 64-bit complex number from a string
+
+**Inputs**  
+- str: The input string containing a 64-bit complex number.
+- n: An optional pointer to an int to store the length of the parsed substring (default: nil).
+
+Example:
+
+	import "core:fmt"
+	import "core:strconv"
+	parse_complex64_example :: proc() {
+		n: int
+		c, ok := strconv.parse_complex64("3+1i", &n)
+		fmt.printfln("%v %i %t", c, n, ok)
+
+		c, ok = strconv.parse_complex64("5+7i hellope", &n)
+		fmt.printfln("%v %i %t", c, n, ok)
+	}
+	
+Output:
+
+	3+1i 4 true
+	5+7i 4 false
+	
+**Returns**  
+- value: The parsed 64-bit complex number.
+- ok: `false` if a complex number could not be found, or if the input string contained more than just the number.
+*/
+parse_complex64 :: proc(str: string, n: ^int = nil) -> (value: complex64, ok: bool) {
+	v: complex128 = ---
+	v, ok = parse_complex128(str, n)
+	return cast(complex64)v, ok
+}
+/*
+Parses a 32-bit complex number from a string
+
+**Inputs**  
+- str: The input string containing a 32-bit complex number.
+- n: An optional pointer to an int to store the length of the parsed substring (default: nil).
+
+Example:
+
+	import "core:fmt"
+	import "core:strconv"
+	parse_complex32_example :: proc() {
+		n: int
+		c, ok := strconv.parse_complex32("3+1i", &n)
+		fmt.printfln("%v %i %t", c, n, ok)
+
+		c, ok = strconv.parse_complex32("5+7i hellope", &n)
+		fmt.printfln("%v %i %t", c, n, ok)
+	}
+	
+Output:
+
+	3+1i 4 true
+	5+7i 4 false
+	
+**Returns**  
+- value: The parsed 32-bit complex number.
+- ok: `false` if a complex number could not be found, or if the input string contained more than just the number.
+*/
+parse_complex32 :: proc(str: string, n: ^int = nil) -> (value: complex32, ok: bool) {
+	v: complex128 = ---
+	v, ok = parse_complex128(str, n)
+	return cast(complex32)v, ok
+}
+/*
+Parses a 256-bit quaternion from a string
+
+**Inputs**  
+- str: The input string containing a 256-bit quaternion.
+- n: An optional pointer to an int to store the length of the parsed substring (default: nil).
+
+Example:
+
+	import "core:fmt"
+	import "core:strconv"
+	parse_quaternion256_example :: proc() {
+		n: int
+		q, ok := strconv.parse_quaternion256("1+2i+3j+4k", &n)
+		fmt.printfln("%v %i %t", q, n, ok)
+
+		q, ok = strconv.parse_quaternion256("1+2i+3j+4k hellope", &n)
+		fmt.printfln("%v %i %t", q, n, ok)
+	}
+	
+Output:
+
+	1+2i+3j+4k 10 true
+	1+2i+3j+4k 10 false
+	
+**Returns**  
+- value: The parsed 256-bit quaternion.
+- ok: `false` if a quaternion could not be found, or if the input string contained more than just the quaternion.
+*/
+parse_quaternion256 :: proc(str: string, n: ^int = nil) -> (value: quaternion256, ok: bool) {
+	iterate_and_assign :: proc (iter: ^string, terminator: byte, nr_total: ^int, state: bool) -> (value: f64, ok: bool) {
+		if !state {
+			return
+		}
+
+		nr: int
+		value, nr, _ = parse_f64_prefix(iter^)
+		iter^ = iter[nr:]
+
+		if len(iter) > 0 && iter[0] == terminator {
+			iter^ = iter[1:]
+			nr_total^ += nr + 1
+			ok = true
+		} else {
+			value = 0
+		}
+
+		return
+	}
+
+	real_value, imag_value, jmag_value, kmag_value: f64
+	nr: int
+
+	real_value, nr, _ = parse_f64_prefix(str)
+	iter := str[nr:]
+
+	// Need to have parsed at least something in order to get started.
+	ok = nr > 0
+
+	// Quaternion parsing is done this way to honour the rest of the API with
+	// regards to partial parsing. Otherwise, we could error out early.
+	imag_value, ok = iterate_and_assign(&iter, 'i', &nr, ok)
+	jmag_value, ok = iterate_and_assign(&iter, 'j', &nr, ok)
+	kmag_value, ok = iterate_and_assign(&iter, 'k', &nr, ok)
+
+	if len(iter) != 0 {
+		ok = false
+	}
+
+	if n != nil {
+		n^ = nr
+	}
+
+	value = quaternion(
+		real = real_value,
+		imag = imag_value,
+		jmag = jmag_value,
+		kmag = kmag_value)
+	return
+}
+/*
+Parses a 128-bit quaternion from a string
+
+**Inputs**  
+- str: The input string containing a 128-bit quaternion.
+- n: An optional pointer to an int to store the length of the parsed substring (default: nil).
+
+Example:
+
+	import "core:fmt"
+	import "core:strconv"
+	parse_quaternion128_example :: proc() {
+		n: int
+		q, ok := strconv.parse_quaternion128("1+2i+3j+4k", &n)
+		fmt.printfln("%v %i %t", q, n, ok)
+
+		q, ok = strconv.parse_quaternion128("1+2i+3j+4k hellope", &n)
+		fmt.printfln("%v %i %t", q, n, ok)
+	}
+	
+Output:
+
+	1+2i+3j+4k 10 true
+	1+2i+3j+4k 10 false
+	
+**Returns**  
+- value: The parsed 128-bit quaternion.
+- ok: `false` if a quaternion could not be found, or if the input string contained more than just the quaternion.
+*/
+parse_quaternion128 :: proc(str: string, n: ^int = nil) -> (value: quaternion128, ok: bool) {
+	v: quaternion256 = ---
+	v, ok = parse_quaternion256(str, n)
+	return cast(quaternion128)v, ok
+}
+/*
+Parses a 64-bit quaternion from a string
+
+**Inputs**  
+- str: The input string containing a 64-bit quaternion.
+- n: An optional pointer to an int to store the length of the parsed substring (default: nil).
+
+Example:
+
+	import "core:fmt"
+	import "core:strconv"
+	parse_quaternion64_example :: proc() {
+		n: int
+		q, ok := strconv.parse_quaternion64("1+2i+3j+4k", &n)
+		fmt.printfln("%v %i %t", q, n, ok)
+
+		q, ok = strconv.parse_quaternion64("1+2i+3j+4k hellope", &n)
+		fmt.printfln("%v %i %t", q, n, ok)
+	}
+	
+Output:
+
+	1+2i+3j+4k 10 true
+	1+2i+3j+4k 10 false
+	
+**Returns**  
+- value: The parsed 64-bit quaternion.
+- ok: `false` if a quaternion could not be found, or if the input string contained more than just the quaternion.
+*/
+parse_quaternion64 :: proc(str: string, n: ^int = nil) -> (value: quaternion64, ok: bool) {
+	v: quaternion256 = ---
+	v, ok = parse_quaternion256(str, n)
+	return cast(quaternion64)v, ok
+}
 /* 
 /* 
 Appends a boolean value as a string to the given buffer
 Appends a boolean value as a string to the given buffer
 
 

+ 4 - 4
core/strings/builder.odin

@@ -350,9 +350,9 @@ Output:
 	ab
 	ab
 
 
 */
 */
-write_byte :: proc(b: ^Builder, x: byte) -> (n: int) {
+write_byte :: proc(b: ^Builder, x: byte, loc := #caller_location) -> (n: int) {
 	n0 := len(b.buf)
 	n0 := len(b.buf)
-	append(&b.buf, x)
+	append(&b.buf, x, loc)
 	n1 := len(b.buf)
 	n1 := len(b.buf)
 	return n1-n0
 	return n1-n0
 }
 }
@@ -380,9 +380,9 @@ NOTE: The backing dynamic array may be fixed in capacity or fail to resize, `n`
 Returns:
 Returns:
 - n: The number of bytes appended
 - n: The number of bytes appended
 */
 */
-write_bytes :: proc(b: ^Builder, x: []byte) -> (n: int) {
+write_bytes :: proc(b: ^Builder, x: []byte, loc := #caller_location) -> (n: int) {
 	n0 := len(b.buf)
 	n0 := len(b.buf)
-	append(&b.buf, ..x)
+	append(&b.buf, ..x, loc=loc)
 	n1 := len(b.buf)
 	n1 := len(b.buf)
 	return n1-n0
 	return n1-n0
 }
 }

+ 1 - 1
core/sync/futex_darwin.odin

@@ -52,7 +52,7 @@ _futex_wait_with_timeout :: proc "contextless" (f: ^Futex, expected: u32, durati
 		}
 		}
 	} else {
 	} else {
 
 
-	timeout_ns := u32(duration) * 1000
+	timeout_ns := u32(duration)
 	s := __ulock_wait(UL_COMPARE_AND_WAIT | ULF_NO_ERRNO, f, u64(expected), timeout_ns)
 	s := __ulock_wait(UL_COMPARE_AND_WAIT | ULF_NO_ERRNO, f, u64(expected), timeout_ns)
 	if s >= 0 {
 	if s >= 0 {
 		return true
 		return true

+ 6 - 2
core/sync/primitives_netbsd.odin

@@ -1,8 +1,12 @@
 //+private
 //+private
 package sync
 package sync
 
 
-import "core:sys/unix"
+foreign import libc "system:c"
+
+foreign libc {
+	_lwp_self :: proc "c" () -> i32 ---
+}
 
 
 _current_thread_id :: proc "contextless" () -> int {
 _current_thread_id :: proc "contextless" () -> int {
-	return cast(int) unix.pthread_self()
+	return int(_lwp_self())
 }
 }

+ 1 - 0
core/sys/info/platform_darwin.odin

@@ -527,6 +527,7 @@ macos_release_map: map[string]Darwin_To_Release = {
 	"23D60"      = {{23, 3, 0}, "macOS", {"Sonoma",         {14, 3, 1}}},
 	"23D60"      = {{23, 3, 0}, "macOS", {"Sonoma",         {14, 3, 1}}},
 	"23E214"     = {{23, 4, 0}, "macOS", {"Sonoma",         {14, 4, 0}}},
 	"23E214"     = {{23, 4, 0}, "macOS", {"Sonoma",         {14, 4, 0}}},
 	"23E224"     = {{23, 4, 0}, "macOS", {"Sonoma",         {14, 4, 1}}},
 	"23E224"     = {{23, 4, 0}, "macOS", {"Sonoma",         {14, 4, 1}}},
+	"23F79"      = {{23, 5, 0}, "macOS", {"Sonoma",         {14, 5, 0}}},
 }
 }
 
 
 @(private)
 @(private)

+ 5 - 0
core/sys/linux/sys.odin

@@ -487,6 +487,7 @@ connect :: proc "contextless" (sock: Fd, addr: ^$T) -> (Errno)
 where
 where
 	T == Sock_Addr_In ||
 	T == Sock_Addr_In ||
 	T == Sock_Addr_In6 ||
 	T == Sock_Addr_In6 ||
+	T == Sock_Addr_Un ||
 	T == Sock_Addr_Any
 	T == Sock_Addr_Any
 {
 {
 	ret := syscall(SYS_connect, sock, addr, size_of(T))
 	ret := syscall(SYS_connect, sock, addr, size_of(T))
@@ -502,6 +503,7 @@ accept :: proc "contextless" (sock: Fd, addr: ^$T, sockflags: Socket_FD_Flags =
 where
 where
 	T == Sock_Addr_In ||
 	T == Sock_Addr_In ||
 	T == Sock_Addr_In6 ||
 	T == Sock_Addr_In6 ||
+	T == Sock_Addr_Un ||
 	T == Sock_Addr_Any
 	T == Sock_Addr_Any
 {
 {
 	addr_len: i32 = size_of(T)
 	addr_len: i32 = size_of(T)
@@ -514,6 +516,7 @@ recvfrom :: proc "contextless" (sock: Fd, buf: []u8, flags: Socket_Msg, addr: ^$
 where
 where
 	T == Sock_Addr_In ||
 	T == Sock_Addr_In ||
 	T == Sock_Addr_In6 ||
 	T == Sock_Addr_In6 ||
+	T == Sock_Addr_Un ||
 	T == Sock_Addr_Any
 	T == Sock_Addr_Any
 {
 {
 	addr_len: i32 = size_of(T)
 	addr_len: i32 = size_of(T)
@@ -531,6 +534,7 @@ sendto :: proc "contextless" (sock: Fd, buf: []u8, flags: Socket_Msg, addr: ^$T)
 where
 where
 	T == Sock_Addr_In ||
 	T == Sock_Addr_In ||
 	T == Sock_Addr_In6 ||
 	T == Sock_Addr_In6 ||
+	T == Sock_Addr_Un ||
 	T == Sock_Addr_Any
 	T == Sock_Addr_Any
 {
 {
 	ret := syscall(SYS_sendto, sock, raw_data(buf), len(buf), transmute(i32) flags, addr, size_of(T))
 	ret := syscall(SYS_sendto, sock, raw_data(buf), len(buf), transmute(i32) flags, addr, size_of(T))
@@ -590,6 +594,7 @@ bind :: proc "contextless" (sock: Fd, addr: ^$T) -> (Errno)
 where
 where
 	T == Sock_Addr_In ||
 	T == Sock_Addr_In ||
 	T == Sock_Addr_In6 ||
 	T == Sock_Addr_In6 ||
+	T == Sock_Addr_Un ||
 	T == Sock_Addr_Any
 	T == Sock_Addr_Any
 {
 {
 	ret := syscall(SYS_bind, sock, addr, size_of(T))
 	ret := syscall(SYS_bind, sock, addr, size_of(T))

+ 9 - 0
core/sys/linux/types.odin

@@ -631,6 +631,14 @@ Sock_Addr_In6 :: struct #packed {
 	sin6_scope_id: u32,
 	sin6_scope_id: u32,
 }
 }
 
 
+/*
+  Struct representing Unix Domain Socket address
+*/
+Sock_Addr_Un :: struct #packed {
+	sun_family: Address_Family,
+	sun_path:   [108]u8,
+}
+
 /*
 /*
 	Struct representing an arbitrary socket address.
 	Struct representing an arbitrary socket address.
 */
 */
@@ -641,6 +649,7 @@ Sock_Addr_Any :: struct #raw_union {
 	},
 	},
 	using ipv4: Sock_Addr_In,
 	using ipv4: Sock_Addr_In,
 	using ipv6: Sock_Addr_In6,
 	using ipv6: Sock_Addr_In6,
+	using uds: Sock_Addr_Un,
 }
 }
 
 
 /*
 /*

+ 2 - 2
core/sys/unix/pthread_freebsd.odin

@@ -95,7 +95,7 @@ sem_t :: struct {
 PTHREAD_CANCEL_ENABLE       :: 0
 PTHREAD_CANCEL_ENABLE       :: 0
 PTHREAD_CANCEL_DISABLE      :: 1
 PTHREAD_CANCEL_DISABLE      :: 1
 PTHREAD_CANCEL_DEFERRED     :: 0
 PTHREAD_CANCEL_DEFERRED     :: 0
-PTHREAD_CANCEL_ASYNCHRONOUS :: 1
+PTHREAD_CANCEL_ASYNCHRONOUS :: 2
 
 
 foreign import "system:pthread"
 foreign import "system:pthread"
 
 
@@ -119,4 +119,4 @@ foreign pthread {
 	pthread_setcancelstate :: proc (state: c.int, old_state: ^c.int) -> c.int ---
 	pthread_setcancelstate :: proc (state: c.int, old_state: ^c.int) -> c.int ---
 	pthread_setcanceltype  :: proc (type:  c.int, old_type:  ^c.int) -> c.int ---
 	pthread_setcanceltype  :: proc (type:  c.int, old_type:  ^c.int) -> c.int ---
 	pthread_cancel         :: proc (thread: pthread_t) -> c.int ---
 	pthread_cancel         :: proc (thread: pthread_t) -> c.int ---
-}
+}

+ 2 - 2
core/sys/unix/pthread_openbsd.odin

@@ -49,7 +49,7 @@ sem_t :: distinct rawptr
 PTHREAD_CANCEL_ENABLE       :: 0
 PTHREAD_CANCEL_ENABLE       :: 0
 PTHREAD_CANCEL_DISABLE      :: 1
 PTHREAD_CANCEL_DISABLE      :: 1
 PTHREAD_CANCEL_DEFERRED     :: 0
 PTHREAD_CANCEL_DEFERRED     :: 0
-PTHREAD_CANCEL_ASYNCHRONOUS :: 1
+PTHREAD_CANCEL_ASYNCHRONOUS :: 2
 
 
 foreign import libc "system:c"
 foreign import libc "system:c"
 
 
@@ -71,4 +71,4 @@ foreign libc {
 	pthread_setcancelstate :: proc (state: c.int, old_state: ^c.int) -> c.int ---
 	pthread_setcancelstate :: proc (state: c.int, old_state: ^c.int) -> c.int ---
 	pthread_setcanceltype  :: proc (type:  c.int, old_type:  ^c.int) -> c.int ---
 	pthread_setcanceltype  :: proc (type:  c.int, old_type:  ^c.int) -> c.int ---
 	pthread_cancel         :: proc (thread: pthread_t) -> c.int ---
 	pthread_cancel         :: proc (thread: pthread_t) -> c.int ---
-}
+}

+ 1 - 0
core/sys/unix/pthread_unix.odin

@@ -116,4 +116,5 @@ foreign pthread {
 	pthread_mutexattr_setpshared :: proc(attrs: ^pthread_mutexattr_t, value: c.int) -> c.int ---
 	pthread_mutexattr_setpshared :: proc(attrs: ^pthread_mutexattr_t, value: c.int) -> c.int ---
 	pthread_mutexattr_getpshared :: proc(attrs: ^pthread_mutexattr_t, result: ^c.int) -> c.int ---
 	pthread_mutexattr_getpshared :: proc(attrs: ^pthread_mutexattr_t, result: ^c.int) -> c.int ---
 
 
+	pthread_testcancel :: proc () ---
 }
 }

+ 2 - 2
core/sys/windows/kernel32.odin

@@ -454,9 +454,9 @@ foreign kernel32 {
 	// [MS-Docs](https://learn.microsoft.com/en-us/windows/win32/api/winbase/nf-winbase-setfilecompletionnotificationmodes)
 	// [MS-Docs](https://learn.microsoft.com/en-us/windows/win32/api/winbase/nf-winbase-setfilecompletionnotificationmodes)
 	SetFileCompletionNotificationModes :: proc(FileHandle: HANDLE, Flags: u8) -> BOOL ---
 	SetFileCompletionNotificationModes :: proc(FileHandle: HANDLE, Flags: u8) -> BOOL ---
 	// [MS-Docs](https://learn.microsoft.com/en-us/windows/win32/api/ioapiset/nf-ioapiset-createiocompletionport)
 	// [MS-Docs](https://learn.microsoft.com/en-us/windows/win32/api/ioapiset/nf-ioapiset-createiocompletionport)
-	CreateIoCompletionPort :: proc(FileHandle: HANDLE, ExistingCompletionPort: HANDLE, CompletionKey: ^uintptr, NumberOfConcurrentThreads: DWORD) -> HANDLE ---
+	CreateIoCompletionPort :: proc(FileHandle: HANDLE, ExistingCompletionPort: HANDLE, CompletionKey: ULONG_PTR, NumberOfConcurrentThreads: DWORD) -> HANDLE ---
 	//[MS-Docs](https://learn.microsoft.com/en-us/windows/win32/api/ioapiset/nf-ioapiset-getqueuedcompletionstatus)
 	//[MS-Docs](https://learn.microsoft.com/en-us/windows/win32/api/ioapiset/nf-ioapiset-getqueuedcompletionstatus)
-	GetQueuedCompletionStatus :: proc(CompletionPort: HANDLE, lpNumberOfBytesTransferred: ^DWORD, lpCompletionKey: uintptr, lpOverlapped: ^^OVERLAPPED, dwMilliseconds: DWORD) -> BOOL ---
+	GetQueuedCompletionStatus :: proc(CompletionPort: HANDLE, lpNumberOfBytesTransferred: ^DWORD, lpCompletionKey: PULONG_PTR, lpOverlapped: ^^OVERLAPPED, dwMilliseconds: DWORD) -> BOOL ---
 	// [MS-Docs](https://learn.microsoft.com/en-us/windows/win32/api/ioapiset/nf-ioapiset-getqueuedcompletionstatusex)
 	// [MS-Docs](https://learn.microsoft.com/en-us/windows/win32/api/ioapiset/nf-ioapiset-getqueuedcompletionstatusex)
 	GetQueuedCompletionStatusEx :: proc(CompletionPort: HANDLE, lpCompletionPortEntries: ^OVERLAPPED_ENTRY, ulCount: c_ulong, ulNumEntriesRemoved: ^c_ulong, dwMilliseconds: DWORD, fAlertable: BOOL) -> BOOL ---
 	GetQueuedCompletionStatusEx :: proc(CompletionPort: HANDLE, lpCompletionPortEntries: ^OVERLAPPED_ENTRY, ulCount: c_ulong, ulNumEntriesRemoved: ^c_ulong, dwMilliseconds: DWORD, fAlertable: BOOL) -> BOOL ---
 	// [MS-Docs](https://learn.microsoft.com/en-us/windows/win32/api/ioapiset/nf-ioapiset-postqueuedcompletionstatus)
 	// [MS-Docs](https://learn.microsoft.com/en-us/windows/win32/api/ioapiset/nf-ioapiset-postqueuedcompletionstatus)

+ 48 - 0
core/testing/events.odin

@@ -0,0 +1,48 @@
+//+private
+package testing
+
+import "base:runtime"
+import "core:sync/chan"
+import "core:time"
+
+Test_State :: enum {
+	Ready,
+	Running,
+	Successful,
+	Failed,
+}
+
+Update_Channel :: chan.Chan(Channel_Event)
+Update_Channel_Sender :: chan.Chan(Channel_Event, .Send)
+
+Task_Channel :: struct {
+	channel: Update_Channel,
+	test_index: int,
+}
+
+Event_New_Test :: struct {
+	test_index: int,
+}
+
+Event_State_Change :: struct {
+	new_state: Test_State,
+}
+
+Event_Set_Fail_Timeout :: struct {
+	at_time: time.Time,
+	location: runtime.Source_Code_Location,
+}
+
+Event_Log_Message :: struct {
+	level: runtime.Logger_Level,
+	text: string,
+	time: time.Time,
+	formatted_text: string,
+}
+
+Channel_Event :: union {
+	Event_New_Test,
+	Event_State_Change,
+	Event_Set_Fail_Timeout,
+	Event_Log_Message,
+}

+ 71 - 0
core/testing/logging.odin

@@ -0,0 +1,71 @@
+//+private
+package testing
+
+import "base:runtime"
+import "core:fmt"
+import pkg_log "core:log"
+import "core:strings"
+import "core:sync/chan"
+import "core:time"
+
+Default_Test_Logger_Opts :: runtime.Logger_Options {
+	.Level,
+	.Terminal_Color,
+	.Short_File_Path,
+	.Line,
+	.Procedure,
+	.Date, .Time,
+}
+
+Log_Message :: struct {
+	level: runtime.Logger_Level,
+	text: string,
+	time: time.Time,
+	// `text` may be allocated differently, depending on where a log message
+	// originates from.
+	allocator: runtime.Allocator,
+}
+
+test_logger_proc :: proc(logger_data: rawptr, level: runtime.Logger_Level, text: string, options: runtime.Logger_Options, location := #caller_location) {
+	t := cast(^T)logger_data
+
+	if level >= .Error {
+		t.error_count += 1
+	}
+
+	cloned_text, clone_error := strings.clone(text, t._log_allocator)
+	assert(clone_error == nil, "Error while cloning string in test thread logger proc.")
+
+	now := time.now()
+
+	chan.send(t.channel, Event_Log_Message {
+		level = level,
+		text = cloned_text,
+		time = now,
+		formatted_text = format_log_text(level, text, options, location, now, t._log_allocator),
+	})
+}
+
+runner_logger_proc :: proc(logger_data: rawptr, level: runtime.Logger_Level, text: string, options: runtime.Logger_Options, location := #caller_location) {
+	log_messages := cast(^[dynamic]Log_Message)logger_data
+
+	now := time.now()
+
+	append(log_messages, Log_Message {
+		level = level,
+		text = format_log_text(level, text, options, location, now),
+		time = now,
+		allocator = context.allocator,
+	})
+}
+
+format_log_text :: proc(level: runtime.Logger_Level, text: string, options: runtime.Logger_Options, location: runtime.Source_Code_Location, at_time: time.Time, allocator := context.allocator) -> string{
+	backing: [1024]byte
+	buf := strings.builder_from_bytes(backing[:])
+
+	pkg_log.do_level_header(options, &buf, level)
+	pkg_log.do_time_header(options, &buf, at_time)
+	pkg_log.do_location_header(options, &buf, location)
+
+	return fmt.aprintf("%s%s", strings.to_string(buf), text, allocator = allocator)
+}

+ 329 - 0
core/testing/reporting.odin

@@ -0,0 +1,329 @@
+//+private
+package testing
+
+import "base:runtime"
+import "core:encoding/ansi"
+import "core:fmt"
+import "core:io"
+import "core:mem"
+import "core:path/filepath"
+import "core:strings"
+
+// Definitions of colors for use in the test runner.
+SGR_RESET   :: ansi.CSI + ansi.RESET           + ansi.SGR
+SGR_READY   :: ansi.CSI + ansi.FG_BRIGHT_BLACK + ansi.SGR
+SGR_RUNNING :: ansi.CSI + ansi.FG_YELLOW       + ansi.SGR
+SGR_SUCCESS :: ansi.CSI + ansi.FG_GREEN        + ansi.SGR
+SGR_FAILED  :: ansi.CSI + ansi.FG_RED          + ansi.SGR
+
+MAX_PROGRESS_WIDTH :: 100
+
+// More than enough bytes to cover long package names, long test names, dozens
+// of ANSI codes, et cetera.
+LINE_BUFFER_SIZE :: (MAX_PROGRESS_WIDTH * 8 + 224) * runtime.Byte
+
+PROGRESS_COLUMN_SPACING :: 2
+
+Package_Run :: struct {
+	name: string,
+	header: string,
+
+	frame_ready: bool,
+
+	redraw_buffer: [LINE_BUFFER_SIZE]byte,
+	redraw_string: string,
+
+	last_change_state: Test_State,
+	last_change_name: string,
+
+	tests: []Internal_Test,
+	test_states: []Test_State,
+}
+
+Report :: struct {
+	packages: []Package_Run,
+	packages_by_name: map[string]^Package_Run,
+
+	pkg_column_len: int,
+	test_column_len: int,
+	progress_width: int,
+
+	all_tests: []Internal_Test,
+	all_test_states: []Test_State,
+}
+
+// Organize all tests by package and sort out test state data.
+make_report :: proc(internal_tests: []Internal_Test) -> (report: Report, error: runtime.Allocator_Error) {
+	assert(len(internal_tests) > 0, "make_report called with no tests")
+
+	packages: [dynamic]Package_Run
+
+	report.all_tests = internal_tests
+	report.all_test_states = make([]Test_State, len(internal_tests)) or_return
+
+	// First, figure out what belongs where.
+	#no_bounds_check cur_pkg := internal_tests[0].pkg
+	pkg_start: int
+
+	// This loop assumes the tests are sorted by package already.
+	for it, index in internal_tests {
+		if cur_pkg != it.pkg {
+			#no_bounds_check { 
+				append(&packages, Package_Run {
+					name = cur_pkg,
+					tests = report.all_tests[pkg_start:index],
+					test_states = report.all_test_states[pkg_start:index],
+				}) or_return
+			}
+
+			when PROGRESS_WIDTH == 0 {
+				report.progress_width = max(report.progress_width, index - pkg_start)
+			}
+
+			pkg_start = index
+			report.pkg_column_len = max(report.pkg_column_len, len(cur_pkg))
+			cur_pkg = it.pkg
+		}
+		report.test_column_len = max(report.test_column_len, len(it.name))
+	}
+
+	// Handle the last (or only) package.
+	#no_bounds_check {
+		append(&packages, Package_Run {
+			name = cur_pkg,
+			header = cur_pkg,
+			tests = report.all_tests[pkg_start:],
+			test_states = report.all_test_states[pkg_start:],
+		}) or_return
+	}
+	when PROGRESS_WIDTH == 0 {
+		report.progress_width = max(report.progress_width, len(internal_tests) - pkg_start)
+	} else {
+		report.progress_width = PROGRESS_WIDTH
+	}
+	report.progress_width = min(report.progress_width, MAX_PROGRESS_WIDTH)
+
+	report.pkg_column_len = PROGRESS_COLUMN_SPACING + max(report.pkg_column_len, len(cur_pkg))
+
+	shrink(&packages) or_return
+
+	for &pkg in packages {
+		pkg.header = fmt.aprintf("%- *[1]s[", pkg.name, report.pkg_column_len)
+		assert(len(pkg.header) > 0, "Error allocating package header string.")
+
+		// This is safe because the array is done resizing, and it has the same
+		// lifetime as the map.
+		report.packages_by_name[pkg.name] = &pkg
+	}
+
+	// It's okay to discard the dynamic array's allocator information here,
+	// because its capacity has been shrunk to its length, it was allocated by
+	// the caller's context allocator, and it will be deallocated by the same.
+	//
+	// `delete_slice` is equivalent to `delete_dynamic_array` in this case.
+	report.packages = packages[:]
+
+	return
+}
+
+destroy_report :: proc(report: ^Report) {
+	for pkg in report.packages {
+		delete(pkg.header)
+	}
+
+	delete(report.packages)
+	delete(report.packages_by_name)
+	delete(report.all_test_states)
+}
+
+redraw_package :: proc(w: io.Writer, report: Report, pkg: ^Package_Run) {
+	if pkg.frame_ready {
+		io.write_string(w, pkg.redraw_string)
+		return
+	}
+
+	// Write the output line here so we can cache it.
+	line_builder := strings.builder_from_bytes(pkg.redraw_buffer[:])
+	line_writer  := strings.to_writer(&line_builder)
+
+	highest_run_index: int
+	failed_count: int
+	done_count: int
+	#no_bounds_check for i := 0; i < len(pkg.test_states); i += 1 {
+		switch pkg.test_states[i] {
+		case .Ready:
+			continue
+		case .Running:
+			highest_run_index = max(highest_run_index, i)
+		case .Successful:
+			done_count += 1
+		case .Failed:
+			failed_count += 1
+			done_count += 1
+		}
+	}
+
+	start := max(0, highest_run_index - (report.progress_width - 1))
+	end   := min(start + report.progress_width, len(pkg.test_states))
+
+	// This variable is to keep track of the last ANSI code emitted, in
+	// order to avoid repeating the same code over in a sequence.
+	//
+	// This should help reduce screen flicker.
+	last_state := Test_State(-1)
+
+	io.write_string(line_writer, pkg.header)
+
+	#no_bounds_check for state in pkg.test_states[start:end] {
+		switch state {
+		case .Ready:
+			if last_state != state {
+				io.write_string(line_writer, SGR_READY)
+				last_state = state
+			}
+		case .Running:
+			if last_state != state {
+				io.write_string(line_writer, SGR_RUNNING)
+				last_state = state
+			}
+		case .Successful:
+			if last_state != state {
+				io.write_string(line_writer, SGR_SUCCESS)
+				last_state = state
+			}
+		case .Failed:
+			if last_state != state {
+				io.write_string(line_writer, SGR_FAILED)
+				last_state = state
+			}
+		}
+		io.write_byte(line_writer, '|')
+	}
+
+	for _ in 0 ..< report.progress_width - (end - start) {
+		io.write_byte(line_writer, ' ')
+	}
+
+	io.write_string(line_writer, SGR_RESET + "] ")
+
+	ticker: string
+	if done_count == len(pkg.test_states) {
+		ticker = "[package done]"
+		if failed_count > 0 {
+			ticker = fmt.tprintf("%s (" + SGR_FAILED + "%i" + SGR_RESET + " failed)", ticker, failed_count)
+		}
+	} else {
+		if len(pkg.last_change_name) == 0 {
+			#no_bounds_check pkg.last_change_name = pkg.tests[0].name
+		}
+
+		switch pkg.last_change_state {
+		case .Ready:
+			ticker = fmt.tprintf(SGR_READY + "%s" + SGR_RESET, pkg.last_change_name)
+		case .Running:
+			ticker = fmt.tprintf(SGR_RUNNING + "%s" + SGR_RESET, pkg.last_change_name)
+		case .Failed:
+			ticker = fmt.tprintf(SGR_FAILED + "%s" + SGR_RESET, pkg.last_change_name)
+		case .Successful:
+			ticker = fmt.tprintf(SGR_SUCCESS + "%s" + SGR_RESET, pkg.last_change_name)
+		}
+	}
+
+	if done_count == len(pkg.test_states) {
+		fmt.wprintfln(line_writer, "     % 4i :: %s",
+			len(pkg.test_states),
+			ticker,
+		)
+	} else {
+		fmt.wprintfln(line_writer, "% 4i/% 4i :: %s",
+			done_count,
+			len(pkg.test_states),
+			ticker,
+		)
+	}
+
+	pkg.redraw_string = strings.to_string(line_builder)
+	pkg.frame_ready = true
+	io.write_string(w, pkg.redraw_string)
+}
+
+redraw_report :: proc(w: io.Writer, report: Report) {
+	// If we print a line longer than the user's terminal can handle, it may
+	// wrap around, shifting the progress report out of alignment.
+	//
+	// There are ways to get the current terminal width, and that would be the
+	// ideal way to handle this, but it would require system-specific code such
+	// as setting STDIN to be non-blocking in order to read the response from
+	// the ANSI DSR escape code, or reading environment variables.
+	//
+	// The DECAWM escape codes control whether or not the terminal will wrap
+	// long lines or overwrite the last visible character.
+	// This should be fine for now.
+	//
+	// Note that we only do this for the animated summary; log messages are
+	// still perfectly fine to wrap, as they're printed in their own batch,
+	// whereas the animation depends on each package being only on one line.
+	//
+	// Of course, if you resize your terminal while it's printing, things can
+	// still break...
+	fmt.wprint(w, ansi.CSI + ansi.DECAWM_OFF)
+	for &pkg in report.packages {
+		redraw_package(w, report, &pkg)
+	}
+	fmt.wprint(w, ansi.CSI + ansi.DECAWM_ON)
+}
+
+needs_to_redraw :: proc(report: Report) -> bool {
+	for pkg in report.packages {
+		if !pkg.frame_ready {
+			return true
+		}
+	}
+
+	return false
+}
+
+draw_status_bar :: proc(w: io.Writer, threads_string: string, total_done_count, total_test_count: int) {
+	if total_done_count == total_test_count {
+		// All tests are done; print a blank line to maintain the same height
+		// of the progress report.
+		fmt.wprintln(w)
+	} else {
+		fmt.wprintfln(w,
+			"%s % 4i/% 4i :: total",
+			threads_string,
+			total_done_count,
+			total_test_count)
+	}
+}
+
+write_memory_report :: proc(w: io.Writer, tracker: ^mem.Tracking_Allocator, pkg, name: string) {
+	fmt.wprintf(w,
+		"<% 10M/% 10M> <% 10M> (% 5i/% 5i) :: %s.%s",
+		tracker.current_memory_allocated,
+		tracker.total_memory_allocated,
+		tracker.peak_memory_allocated,
+		tracker.total_free_count,
+		tracker.total_allocation_count,
+		pkg,
+		name)
+
+	for ptr, entry in tracker.allocation_map {
+		fmt.wprintf(w,
+			"\n        +++ leak % 10M @ %p [%s:%i:%s()]",
+			entry.size,
+			ptr,
+			filepath.base(entry.location.file_path),
+			entry.location.line,
+			entry.location.procedure)
+		}
+
+	for entry in tracker.bad_free_array {
+		fmt.wprintf(w,
+			"\n        +++ bad free        @ %p [%s:%i:%s()]",
+			entry.memory,
+			filepath.base(entry.location.file_path),
+			entry.location.line,
+			entry.location.procedure)
+	}
+}

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