Jelajahi Sumber

Merge branch 'master' into macharena

Colin Davidson 3 bulan lalu
induk
melakukan
04481e0fd2
100 mengubah file dengan 2765 tambahan dan 1063 penghapusan
  1. 53 45
      .github/workflows/ci.yml
  2. 2 1
      .gitignore
  3. 231 8
      base/builtin/builtin.odin
  4. 13 2
      base/intrinsics/intrinsics.odin
  5. 9 0
      base/runtime/core_builtin.odin
  6. 8 0
      base/runtime/default_temp_allocator_arena.odin
  7. 10 2
      base/runtime/heap_allocator_windows.odin
  8. 214 109
      base/runtime/internal.odin
  9. 19 6
      base/runtime/procs_darwin.odin
  10. 309 7
      base/sanitizer/address.odin
  11. 38 0
      base/sanitizer/doc.odin
  12. 19 25
      build.bat
  13. 4 1
      build_odin.sh
  14. 1 1
      core/bufio/reader.odin
  15. 2 2
      core/bytes/bytes.odin
  16. 0 3
      core/compress/common.odin
  17. 1 1
      core/container/lru/lru_cache.odin
  18. 4 6
      core/container/priority_queue/priority_queue.odin
  19. 1 1
      core/crypto/_aes/hw_intel/api.odin
  20. 2 2
      core/crypto/_chacha20/simd128/chacha20_simd128.odin
  21. 1 1
      core/crypto/_chacha20/simd256/chacha20_simd256.odin
  22. 1 1
      core/crypto/sha2/sha2_impl_hw_intel.odin
  23. 3 3
      core/encoding/cbor/cbor.odin
  24. 36 0
      core/encoding/cbor/marshal.odin
  25. 32 1
      core/encoding/cbor/unmarshal.odin
  26. 0 2
      core/encoding/csv/doc.odin
  27. 1 1
      core/encoding/csv/reader.odin
  28. 4 4
      core/encoding/json/marshal.odin
  29. 2 2
      core/encoding/json/tokenizer.odin
  30. 14 8
      core/encoding/json/unmarshal.odin
  31. 4 4
      core/fmt/fmt.odin
  32. 1 82
      core/hash/crc32.odin
  33. 0 3
      core/image/png/png.odin
  34. 8 8
      core/io/util.odin
  35. 53 11
      core/log/file_console_logger.odin
  36. 1 1
      core/math/big/private.odin
  37. 1 1
      core/math/big/radix.odin
  38. 10 5
      core/math/fixed/fixed.odin
  39. 1 1
      core/math/rand/rand.odin
  40. 25 5
      core/mem/mem.odin
  41. 31 19
      core/mem/rollback_stack_allocator.odin
  42. 1 1
      core/mem/tlsf/tlsf.odin
  43. 51 43
      core/mem/tlsf/tlsf_internal.odin
  44. 10 1
      core/mem/tracking_allocator.odin
  45. 32 11
      core/mem/virtual/arena.odin
  46. 17 8
      core/mem/virtual/virtual.odin
  47. 3 0
      core/mem/virtual/virtual_platform.odin
  48. 11 1
      core/mem/virtual/virtual_windows.odin
  49. 1 1
      core/net/url.odin
  50. 20 20
      core/odin/parser/parser.odin
  51. 1 1
      core/os/os.odin
  52. 42 41
      core/os/os2/allocators.odin
  53. 44 17
      core/os/os2/dir.odin
  54. 2 1
      core/os/os2/dir_linux.odin
  55. 17 0
      core/os/os2/dir_posix_darwin.odin
  56. 5 5
      core/os/os2/dir_windows.odin
  57. 7 7
      core/os/os2/env_posix.odin
  58. 2 2
      core/os/os2/env_wasi.odin
  59. 10 10
      core/os/os2/env_windows.odin
  60. 21 16
      core/os/os2/errors.odin
  61. 13 4
      core/os/os2/file.odin
  62. 29 29
      core/os/os2/file_linux.odin
  63. 36 35
      core/os/os2/file_posix.odin
  64. 28 0
      core/os/os2/file_posix_darwin.odin
  65. 2 2
      core/os/os2/file_posix_other.odin
  66. 1 1
      core/os/os2/file_util.odin
  67. 27 34
      core/os/os2/file_windows.odin
  68. 0 5
      core/os/os2/internal_util.odin
  69. 4 4
      core/os/os2/path.odin
  70. 29 9
      core/os/os2/path_linux.odin
  71. 2 2
      core/os/os2/path_netbsd.odin
  72. 3 3
      core/os/os2/path_openbsd.odin
  73. 30 13
      core/os/os2/path_posix.odin
  74. 0 21
      core/os/os2/path_posixfs.odin
  75. 2 2
      core/os/os2/path_wasi.odin
  76. 23 19
      core/os/os2/path_windows.odin
  77. 1 12
      core/os/os2/process.odin
  78. 50 41
      core/os/os2/process_linux.odin
  79. 9 11
      core/os/os2/process_posix.odin
  80. 4 3
      core/os/os2/process_posix_darwin.odin
  81. 0 2
      core/os/os2/process_wasi.odin
  82. 63 32
      core/os/os2/process_windows.odin
  83. 4 4
      core/os/os2/stat.odin
  84. 4 4
      core/os/os2/stat_linux.odin
  85. 9 8
      core/os/os2/stat_posix.odin
  86. 87 45
      core/os/os2/stat_windows.odin
  87. 11 9
      core/os/os2/temp_file.odin
  88. 2 2
      core/os/os2/temp_file_linux.odin
  89. 2 2
      core/os/os2/temp_file_windows.odin
  90. 133 63
      core/os/os2/user.odin
  91. 183 0
      core/os/os2/user_posix.odin
  92. 79 0
      core/os/os2/user_windows.odin
  93. 4 0
      core/os/os_js.odin
  94. 36 0
      core/path/filepath/path_js.odin
  95. 211 10
      core/simd/simd.odin
  96. 79 0
      core/simd/x86/bmi.odin
  97. 46 0
      core/simd/x86/bmi2.odin
  98. 0 46
      core/sort/sort.odin
  99. 15 15
      core/strconv/decimal/decimal.odin
  100. 38 0
      core/strconv/deprecated.odin

+ 53 - 45
.github/workflows/ci.yml

@@ -32,8 +32,8 @@ jobs:
           gmake -C vendor/miniaudio/src
           gmake -C vendor/miniaudio/src
           ./odin check examples/all -vet -strict-style -disallow-do -target:netbsd_amd64
           ./odin check examples/all -vet -strict-style -disallow-do -target:netbsd_amd64
           ./odin check examples/all -vet -strict-style -disallow-do -target:netbsd_arm64
           ./odin check examples/all -vet -strict-style -disallow-do -target:netbsd_arm64
-          ./odin check vendor/sdl3  -vet -strict-style -disallow-do -target:netbsd_amd64 -no-entry-point
-          ./odin check vendor/sdl3  -vet -strict-style -disallow-do -target:netbsd_arm64 -no-entry-point
+          ./odin check examples/all/sdl3 -vet -strict-style -disallow-do -target:netbsd_amd64 -no-entry-point
+          ./odin check examples/all/sdl3 -vet -strict-style -disallow-do -target:netbsd_arm64 -no-entry-point
           ./odin test tests/core/normal.odin -file -all-packages -vet -strict-style -disallow-do -define:ODIN_TEST_FANCY=false -define:ODIN_TEST_FAIL_ON_BAD_MEMORY=true
           ./odin test tests/core/normal.odin -file -all-packages -vet -strict-style -disallow-do -define:ODIN_TEST_FANCY=false -define:ODIN_TEST_FAIL_ON_BAD_MEMORY=true
           ./odin test tests/core/speed.odin -file -all-packages -vet -strict-style -disallow-do -o:speed -define:ODIN_TEST_FANCY=false -define:ODIN_TEST_FAIL_ON_BAD_MEMORY=true
           ./odin test tests/core/speed.odin -file -all-packages -vet -strict-style -disallow-do -o:speed -define:ODIN_TEST_FANCY=false -define:ODIN_TEST_FAIL_ON_BAD_MEMORY=true
           ./odin test tests/vendor -all-packages -vet -strict-style -disallow-do -define:ODIN_TEST_FANCY=false -define:ODIN_TEST_FAIL_ON_BAD_MEMORY=true
           ./odin test tests/vendor -all-packages -vet -strict-style -disallow-do -define:ODIN_TEST_FANCY=false -define:ODIN_TEST_FAIL_ON_BAD_MEMORY=true
@@ -64,7 +64,7 @@ jobs:
           gmake -C vendor/cgltf/src
           gmake -C vendor/cgltf/src
           gmake -C vendor/miniaudio/src
           gmake -C vendor/miniaudio/src
           ./odin check examples/all -vet -strict-style -disallow-do -target:freebsd_amd64
           ./odin check examples/all -vet -strict-style -disallow-do -target:freebsd_amd64
-          ./odin check vendor/sdl3  -vet -strict-style -disallow-do -target:freebsd_amd64 -no-entry-point
+          ./odin check examples/all/sdl3 -vet -strict-style -disallow-do -target:freebsd_amd64 -no-entry-point
           ./odin test tests/core/normal.odin -file -all-packages -vet -strict-style -disallow-do -define:ODIN_TEST_FANCY=false -define:ODIN_TEST_FAIL_ON_BAD_MEMORY=true
           ./odin test tests/core/normal.odin -file -all-packages -vet -strict-style -disallow-do -define:ODIN_TEST_FANCY=false -define:ODIN_TEST_FAIL_ON_BAD_MEMORY=true
           ./odin test tests/core/speed.odin -file -all-packages -vet -strict-style -disallow-do -o:speed -define:ODIN_TEST_FANCY=false -define:ODIN_TEST_FAIL_ON_BAD_MEMORY=true
           ./odin test tests/core/speed.odin -file -all-packages -vet -strict-style -disallow-do -o:speed -define:ODIN_TEST_FANCY=false -define:ODIN_TEST_FAIL_ON_BAD_MEMORY=true
           ./odin test tests/vendor -all-packages -vet -strict-style -disallow-do -define:ODIN_TEST_FANCY=false -define:ODIN_TEST_FAIL_ON_BAD_MEMORY=true
           ./odin test tests/vendor -all-packages -vet -strict-style -disallow-do -define:ODIN_TEST_FANCY=false -define:ODIN_TEST_FAIL_ON_BAD_MEMORY=true
@@ -75,32 +75,35 @@ jobs:
       fail-fast: false
       fail-fast: false
       matrix:
       matrix:
         # MacOS 13 runs on Intel, 14 runs on ARM
         # MacOS 13 runs on Intel, 14 runs on ARM
-        os: [ubuntu-latest, macos-13, macos-14]
+        os: [macos-13, macos-14, ubuntu-latest]
     runs-on: ${{ matrix.os }}
     runs-on: ${{ matrix.os }}
-    name: ${{ matrix.os == 'macos-14' && 'MacOS ARM' || (matrix.os == 'macos-13' && 'MacOS Intel' || 'Ubuntu') }} Build, Check, and Test
+    name: ${{ matrix.os == 'macos-14' && 'MacOS ARM' || (matrix.os == 'macos-13' && 'MacOS Intel') || (matrix.os == 'ubuntu-latest' && 'Ubuntu') }} Build, Check, and Test
     timeout-minutes: 15
     timeout-minutes: 15
     steps:
     steps:
-      - uses: actions/checkout@v4
 
 
-      - name: Download LLVM (Linux)
-        if: matrix.os == 'ubuntu-latest'
-        run: |
-          wget https://apt.llvm.org/llvm.sh
-          chmod +x llvm.sh
-          sudo ./llvm.sh 20
-          echo "/usr/lib/llvm-20/bin" >> $GITHUB_PATH
+      - uses: actions/checkout@v4
 
 
       - name: Download LLVM (MacOS Intel)
       - name: Download LLVM (MacOS Intel)
         if: matrix.os == 'macos-13'
         if: matrix.os == 'macos-13'
         run: |
         run: |
           brew update
           brew update
           brew install llvm@20 [email protected] lld
           brew install llvm@20 [email protected] lld
+          echo "$(brew --prefix llvm@20)/bin" >> $GITHUB_PATH
 
 
       - name: Download LLVM (MacOS ARM)
       - name: Download LLVM (MacOS ARM)
         if: matrix.os == 'macos-14'
         if: matrix.os == 'macos-14'
         run: |
         run: |
           brew update
           brew update
           brew install llvm@20 wasmtime [email protected] lld
           brew install llvm@20 wasmtime [email protected] lld
+          echo "$(brew --prefix llvm@20)/bin" >> $GITHUB_PATH
+
+      - name: Download LLVM (Ubuntu)
+        if: matrix.os == 'ubuntu-latest'
+        run: |
+          wget https://apt.llvm.org/llvm.sh
+          chmod +x llvm.sh
+          sudo ./llvm.sh 20
+          echo "/usr/lib/llvm-20/bin" >> $GITHUB_PATH
 
 
       - name: Build Odin
       - name: Build Odin
         run: ./build_odin.sh release
         run: ./build_odin.sh release
@@ -121,55 +124,60 @@ jobs:
         run: ./odin run examples/demo -debug
         run: ./odin run examples/demo -debug
       - name: Odin check examples/all
       - name: Odin check examples/all
         run: ./odin check examples/all -strict-style -vet -disallow-do
         run: ./odin check examples/all -strict-style -vet -disallow-do
-      - name: Odin check vendor/sdl3
-        run: ./odin check vendor/sdl3  -strict-style -vet -disallow-do -no-entry-point
+      - name: Odin check examples/all/sdl3
+        run: ./odin check examples/all/sdl3  -strict-style -vet -disallow-do -no-entry-point
       - name: Normal Core library tests
       - name: Normal Core library tests
-        run: ./odin test tests/core/normal.odin -file -all-packages -vet -strict-style -disallow-do -define:ODIN_TEST_FANCY=false -define:ODIN_TEST_FAIL_ON_BAD_MEMORY=true
+        run: ./odin test tests/core/normal.odin -file -all-packages -vet -strict-style -disallow-do -define:ODIN_TEST_FANCY=false -define:ODIN_TEST_FAIL_ON_BAD_MEMORY=true -sanitize:address
       - name: Optimized Core library tests
       - name: Optimized Core library tests
-        run: ./odin test tests/core/speed.odin -o:speed -file -all-packages -vet -strict-style -disallow-do -define:ODIN_TEST_FANCY=false -define:ODIN_TEST_FAIL_ON_BAD_MEMORY=true
+        run: ./odin test tests/core/speed.odin -o:speed -file -all-packages -vet -strict-style -disallow-do -define:ODIN_TEST_FANCY=false -define:ODIN_TEST_FAIL_ON_BAD_MEMORY=true -sanitize:address
       - name: Vendor library tests
       - name: Vendor library tests
-        run: ./odin test tests/vendor -all-packages -vet -strict-style -disallow-do -define:ODIN_TEST_FANCY=false -define:ODIN_TEST_FAIL_ON_BAD_MEMORY=true
+        run: ./odin test tests/vendor -all-packages -vet -strict-style -disallow-do -define:ODIN_TEST_FANCY=false -define:ODIN_TEST_FAIL_ON_BAD_MEMORY=true -sanitize:address
       - name: Internals tests
       - name: Internals tests
-        run: ./odin test tests/internal -all-packages -vet -strict-style -disallow-do -define:ODIN_TEST_FANCY=false -define:ODIN_TEST_FAIL_ON_BAD_MEMORY=true
+        run: ./odin test tests/internal -all-packages -vet -strict-style -disallow-do -define:ODIN_TEST_FANCY=false -define:ODIN_TEST_FAIL_ON_BAD_MEMORY=true -sanitize:address
       - name: GitHub Issue tests
       - name: GitHub Issue tests
         run: |
         run: |
           cd tests/issues
           cd tests/issues
           ./run.sh
           ./run.sh
 
 
+      - name: Run demo on WASI WASM32
+        run: |
+          ./odin build examples/demo -target:wasi_wasm32 -vet -strict-style -disallow-do -out:demo
+          wasmtime ./demo.wasm
+        if: matrix.os == 'macos-14'
+
       - name: Check benchmarks
       - name: Check benchmarks
         run: ./odin check tests/benchmark -vet -strict-style -no-entry-point
         run: ./odin check tests/benchmark -vet -strict-style -no-entry-point
       - name: Odin check examples/all for Linux i386
       - name: Odin check examples/all for Linux i386
-        run: ./odin check examples/all -vet -strict-style -disallow-do -target:linux_i386
         if: matrix.os == 'ubuntu-latest'
         if: matrix.os == 'ubuntu-latest'
+        run: ./odin check examples/all -vet -strict-style -disallow-do -target:linux_i386
       - name: Odin check examples/all for Linux arm64
       - name: Odin check examples/all for Linux arm64
-        run: ./odin check examples/all -vet -strict-style -disallow-do -target:linux_arm64
         if: matrix.os == 'ubuntu-latest'
         if: matrix.os == 'ubuntu-latest'
+        run: ./odin check examples/all -vet -strict-style -disallow-do -target:linux_arm64
       - name: Odin check examples/all for FreeBSD amd64
       - name: Odin check examples/all for FreeBSD amd64
-        run: ./odin check examples/all -vet -strict-style -disallow-do -target:freebsd_amd64
         if: matrix.os == 'ubuntu-latest'
         if: matrix.os == 'ubuntu-latest'
+        run: ./odin check examples/all -vet -strict-style -disallow-do -target:freebsd_amd64
       - name: Odin check examples/all for OpenBSD amd64
       - name: Odin check examples/all for OpenBSD amd64
+        if: matrix.os == 'ubuntu-latest'
         run: ./odin check examples/all -vet -strict-style -disallow-do -target:openbsd_amd64
         run: ./odin check examples/all -vet -strict-style -disallow-do -target:openbsd_amd64
+      - name: Odin check examples/all for js_wasm32
+        if: matrix.os == 'ubuntu-latest'
+        run: ./odin check examples/all -vet -strict-style -disallow-do -no-entry-point -target:js_wasm32
+      - name: Odin check examples/all for js_wasm64p32
         if: matrix.os == 'ubuntu-latest'
         if: matrix.os == 'ubuntu-latest'
+        run: ./odin check examples/all -vet -strict-style -disallow-do -no-entry-point -target:js_wasm64p32
 
 
-      - name: Odin check vendor/sdl3 for Linux i386
-        run: ./odin check vendor/sdl3 -vet -strict-style -disallow-do -no-entry-point -target:linux_i386
+      - name: Odin check examples/all/sdl3 for Linux i386
         if: matrix.os == 'ubuntu-latest'
         if: matrix.os == 'ubuntu-latest'
-      - name: Odin check vendor/sdl3 for Linux arm64
-        run: ./odin check vendor/sdl3 -vet -strict-style -disallow-do -no-entry-point -target:linux_arm64
+        run: ./odin check examples/all/sdl3 -vet -strict-style -disallow-do -no-entry-point -target:linux_i386
+      - name: Odin check examples/all/sdl3 for Linux arm64
         if: matrix.os == 'ubuntu-latest'
         if: matrix.os == 'ubuntu-latest'
-      - name: Odin check vendor/sdl3 for FreeBSD amd64
-        run: ./odin check vendor/sdl3 -vet -strict-style -disallow-do -no-entry-point -target:freebsd_amd64
+        run: ./odin check examples/all/sdl3 -vet -strict-style -disallow-do -no-entry-point -target:linux_arm64
+      - name: Odin check examples/all/sdl3 for FreeBSD amd64
         if: matrix.os == 'ubuntu-latest'
         if: matrix.os == 'ubuntu-latest'
-      - name: Odin check vendor/sdl3 for OpenBSD amd64
-        run: ./odin check vendor/sdl3 -vet -strict-style -disallow-do -no-entry-point -target:openbsd_amd64
+        run: ./odin check examples/all/sdl3 -vet -strict-style -disallow-do -no-entry-point -target:freebsd_amd64
+      - name: Odin check examples/all/sdl3 for OpenBSD amd64
         if: matrix.os == 'ubuntu-latest'
         if: matrix.os == 'ubuntu-latest'
-
-
-      - name: Run demo on WASI WASM32
-        run: |
-          ./odin build examples/demo -target:wasi_wasm32 -vet -strict-style -disallow-do -out:demo
-          wasmtime ./demo.wasm
-        if: matrix.os == 'macos-14'
+        run: ./odin check examples/all/sdl3 -vet -strict-style -disallow-do -no-entry-point -target:openbsd_amd64
 
 
   build_windows:
   build_windows:
     name: Windows Build, Check, and Test
     name: Windows Build, Check, and Test
@@ -206,32 +214,32 @@ jobs:
         run: |
         run: |
           call "C:\Program Files\Microsoft Visual Studio\2022\Enterprise\VC\Auxiliary\Build\vcvars64.bat
           call "C:\Program Files\Microsoft Visual Studio\2022\Enterprise\VC\Auxiliary\Build\vcvars64.bat
           odin check examples/all -vet -strict-style -disallow-do
           odin check examples/all -vet -strict-style -disallow-do
-      - name: Odin check vendor/sdl3
+      - name: Odin check examples/all/sdl3
         shell: cmd
         shell: cmd
         run: |
         run: |
           call "C:\Program Files\Microsoft Visual Studio\2022\Enterprise\VC\Auxiliary\Build\vcvars64.bat
           call "C:\Program Files\Microsoft Visual Studio\2022\Enterprise\VC\Auxiliary\Build\vcvars64.bat
-          odin check vendor/sdl3 -vet -strict-style -disallow-do -no-entry-point
+          odin check examples/all/sdl3 -vet -strict-style -disallow-do -no-entry-point
       - name: Core library tests
       - name: Core library tests
         shell: cmd
         shell: cmd
         run: |
         run: |
           call "C:\Program Files\Microsoft Visual Studio\2022\Enterprise\VC\Auxiliary\Build\vcvars64.bat
           call "C:\Program Files\Microsoft Visual Studio\2022\Enterprise\VC\Auxiliary\Build\vcvars64.bat
-          odin test tests/core/normal.odin -file -all-packages -vet -strict-style -disallow-do -define:ODIN_TEST_FANCY=false -define:ODIN_TEST_FAIL_ON_BAD_MEMORY=true
+          odin test tests/core/normal.odin -file -all-packages -vet -strict-style -disallow-do -define:ODIN_TEST_FANCY=false -define:ODIN_TEST_FAIL_ON_BAD_MEMORY=true -sanitize:address
       - name: Optimized core library tests
       - name: Optimized core library tests
         shell: cmd
         shell: cmd
         run: |
         run: |
           call "C:\Program Files\Microsoft Visual Studio\2022\Enterprise\VC\Auxiliary\Build\vcvars64.bat
           call "C:\Program Files\Microsoft Visual Studio\2022\Enterprise\VC\Auxiliary\Build\vcvars64.bat
-          odin test tests/core/speed.odin -o:speed -file -all-packages -vet -strict-style -disallow-do -define:ODIN_TEST_FANCY=false -define:ODIN_TEST_FAIL_ON_BAD_MEMORY=true
+          odin test tests/core/speed.odin -o:speed -file -all-packages -vet -strict-style -disallow-do -define:ODIN_TEST_FANCY=false -define:ODIN_TEST_FAIL_ON_BAD_MEMORY=true -sanitize:address
       - name: Vendor library tests
       - name: Vendor library tests
         shell: cmd
         shell: cmd
         run: |
         run: |
           call "C:\Program Files\Microsoft Visual Studio\2022\Enterprise\VC\Auxiliary\Build\vcvars64.bat
           call "C:\Program Files\Microsoft Visual Studio\2022\Enterprise\VC\Auxiliary\Build\vcvars64.bat
           copy vendor\lua\5.4\windows\*.dll .
           copy vendor\lua\5.4\windows\*.dll .
-          odin test tests/vendor -all-packages -vet -strict-style -disallow-do -define:ODIN_TEST_FANCY=false -define:ODIN_TEST_FAIL_ON_BAD_MEMORY=true
+          odin test tests/vendor -all-packages -vet -strict-style -disallow-do -define:ODIN_TEST_FANCY=false -define:ODIN_TEST_FAIL_ON_BAD_MEMORY=true -sanitize:address
       - name: Odin internals tests
       - name: Odin internals tests
         shell: cmd
         shell: cmd
         run: |
         run: |
           call "C:\Program Files\Microsoft Visual Studio\2022\Enterprise\VC\Auxiliary\Build\vcvars64.bat
           call "C:\Program Files\Microsoft Visual Studio\2022\Enterprise\VC\Auxiliary\Build\vcvars64.bat
-          odin test tests/internal -all-packages -vet -strict-style -disallow-do -define:ODIN_TEST_FANCY=false -define:ODIN_TEST_FAIL_ON_BAD_MEMORY=true
+          odin test tests/internal -all-packages -vet -strict-style -disallow-do -define:ODIN_TEST_FANCY=false -define:ODIN_TEST_FAIL_ON_BAD_MEMORY=true -sanitize:address
       - name: Check issues
       - name: Check issues
         shell: cmd
         shell: cmd
         run: |
         run: |
@@ -293,8 +301,8 @@ jobs:
       - name: Odin check examples/all
       - name: Odin check examples/all
         run: ./odin check examples/all -target:linux_riscv64 -vet -strict-style -disallow-do
         run: ./odin check examples/all -target:linux_riscv64 -vet -strict-style -disallow-do
 
 
-      - name: Odin check vendor/sdl3
-        run: ./odin check vendor/sdl3 -target:linux_riscv64 -vet -strict-style -disallow-do -no-entry-point
+      - name: Odin check examples/all/sdl3
+        run: ./odin check examples/all/sdl3 -target:linux_riscv64 -vet -strict-style -disallow-do -no-entry-point
 
 
       - name: Install riscv64 toolchain and qemu
       - name: Install riscv64 toolchain and qemu
         run: sudo apt-get install -y qemu-user qemu-user-static gcc-12-riscv64-linux-gnu libc6-riscv64-cross
         run: sudo apt-get install -y qemu-user qemu-user-static gcc-12-riscv64-linux-gnu libc6-riscv64-cross

+ 2 - 1
.gitignore

@@ -293,5 +293,6 @@ build.sh
 
 
 # RAD debugger project file
 # RAD debugger project file
 *.raddbg
 *.raddbg
-
+*.rdi
+tests/issues/build/*
 misc/featuregen/featuregen
 misc/featuregen/featuregen

+ 231 - 8
base/builtin/builtin.odin

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

+ 13 - 2
base/intrinsics/intrinsics.odin

@@ -169,6 +169,7 @@ type_is_union            :: proc($T: typeid) -> bool ---
 type_is_enum             :: proc($T: typeid) -> bool ---
 type_is_enum             :: proc($T: typeid) -> bool ---
 type_is_proc             :: proc($T: typeid) -> bool ---
 type_is_proc             :: proc($T: typeid) -> bool ---
 type_is_bit_set          :: proc($T: typeid) -> bool ---
 type_is_bit_set          :: proc($T: typeid) -> bool ---
+type_is_bit_field        :: proc($T: typeid) -> bool ---
 type_is_simd_vector      :: proc($T: typeid) -> bool ---
 type_is_simd_vector      :: proc($T: typeid) -> bool ---
 type_is_matrix           :: proc($T: typeid) -> bool ---
 type_is_matrix           :: proc($T: typeid) -> bool ---
 
 
@@ -221,6 +222,9 @@ type_map_cell_info :: proc($T: typeid)           -> ^runtime.Map_Cell_Info ---
 type_convert_variants_to_pointers :: proc($T: typeid) -> typeid where type_is_union(T) ---
 type_convert_variants_to_pointers :: proc($T: typeid) -> typeid where type_is_union(T) ---
 type_merge :: proc($U, $V: typeid) -> typeid where type_is_union(U), type_is_union(V) ---
 type_merge :: proc($U, $V: typeid) -> typeid where type_is_union(U), type_is_union(V) ---
 
 
+type_integer_to_unsigned :: proc($T: typeid) -> type where type_is_integer(T), !type_is_unsigned(T) ---
+type_integer_to_signed   :: proc($T: typeid) -> type where type_is_integer(T), type_is_unsigned(T) ---
+
 type_has_shared_fields :: proc($U, $V: typeid) -> bool where type_is_struct(U), type_is_struct(V) ---
 type_has_shared_fields :: proc($U, $V: typeid) -> bool where type_is_struct(U), type_is_struct(V) ---
 
 
 constant_utf16_cstring :: proc($literal: string) -> [^]u16 ---
 constant_utf16_cstring :: proc($literal: string) -> [^]u16 ---
@@ -274,8 +278,12 @@ simd_lanes_ge :: proc(a, b: #simd[N]T) -> #simd[N]Integer ---
 simd_extract :: proc(a: #simd[N]T, idx: uint) -> T ---
 simd_extract :: proc(a: #simd[N]T, idx: uint) -> T ---
 simd_replace :: proc(a: #simd[N]T, idx: uint, elem: T) -> #simd[N]T ---
 simd_replace :: proc(a: #simd[N]T, idx: uint, elem: T) -> #simd[N]T ---
 
 
+simd_reduce_add_bisect  :: proc(a: #simd[N]T) -> T where type_is_integer(T) || type_is_float(T)---
+simd_reduce_mul_bisect  :: proc(a: #simd[N]T) -> T where type_is_integer(T) || type_is_float(T)---
 simd_reduce_add_ordered :: proc(a: #simd[N]T) -> T where type_is_integer(T) || type_is_float(T)---
 simd_reduce_add_ordered :: proc(a: #simd[N]T) -> T where type_is_integer(T) || type_is_float(T)---
 simd_reduce_mul_ordered :: proc(a: #simd[N]T) -> T where type_is_integer(T) || type_is_float(T)---
 simd_reduce_mul_ordered :: proc(a: #simd[N]T) -> T where type_is_integer(T) || type_is_float(T)---
+simd_reduce_add_pairs   :: proc(a: #simd[N]T) -> T where type_is_integer(T) || type_is_float(T)---
+simd_reduce_mul_pairs   :: proc(a: #simd[N]T) -> T where type_is_integer(T) || type_is_float(T)---
 simd_reduce_min         :: proc(a: #simd[N]T) -> T where type_is_integer(T) || type_is_float(T)---
 simd_reduce_min         :: proc(a: #simd[N]T) -> T where type_is_integer(T) || type_is_float(T)---
 simd_reduce_max         :: proc(a: #simd[N]T) -> T where type_is_integer(T) || type_is_float(T)---
 simd_reduce_max         :: proc(a: #simd[N]T) -> T where type_is_integer(T) || type_is_float(T)---
 simd_reduce_and         :: proc(a: #simd[N]T) -> T where type_is_integer(T) || type_is_float(T)---
 simd_reduce_and         :: proc(a: #simd[N]T) -> T where type_is_integer(T) || type_is_float(T)---
@@ -298,7 +306,7 @@ simd_masked_store :: proc(ptr: rawptr, val: #simd[N]T, mask: #simd[N]U)
 simd_masked_expand_load    :: proc(ptr: rawptr, val: #simd[N]T, mask: #simd[N]U) -> #simd[N]T where type_is_integer(U) || type_is_boolean(U) ---
 simd_masked_expand_load    :: proc(ptr: rawptr, val: #simd[N]T, mask: #simd[N]U) -> #simd[N]T where type_is_integer(U) || type_is_boolean(U) ---
 simd_masked_compress_store :: proc(ptr: rawptr, val: #simd[N]T, mask: #simd[N]U)              where type_is_integer(U) || type_is_boolean(U) ---
 simd_masked_compress_store :: proc(ptr: rawptr, val: #simd[N]T, mask: #simd[N]U)              where type_is_integer(U) || type_is_boolean(U) ---
 
 
-
+simd_indices :: proc($T: typeid/#simd[$N]$E) -> T where type_is_numeric(T) ---
 
 
 simd_shuffle :: proc(a, b: #simd[N]T, indices: ..int) -> #simd[len(indices)]T ---
 simd_shuffle :: proc(a, b: #simd[N]T, indices: ..int) -> #simd[len(indices)]T ---
 simd_select  :: proc(cond: #simd[N]boolean_or_integer, true, false: #simd[N]T) -> #simd[N]T ---
 simd_select  :: proc(cond: #simd[N]boolean_or_integer, true, false: #simd[N]T) -> #simd[N]T ---
@@ -353,15 +361,18 @@ x86_xgetbv :: proc(cx: u32) -> (eax, edx: u32) ---
 objc_object   :: struct{}
 objc_object   :: struct{}
 objc_selector :: struct{}
 objc_selector :: struct{}
 objc_class    :: struct{}
 objc_class    :: struct{}
+objc_ivar     :: struct{}
+
 objc_id    :: ^objc_object
 objc_id    :: ^objc_object
 objc_SEL   :: ^objc_selector
 objc_SEL   :: ^objc_selector
 objc_Class :: ^objc_class
 objc_Class :: ^objc_class
+objc_Ivar  :: ^objc_ivar
 
 
 objc_find_selector     :: proc($name: string) -> objc_SEL   ---
 objc_find_selector     :: proc($name: string) -> objc_SEL   ---
 objc_register_selector :: proc($name: string) -> objc_SEL   ---
 objc_register_selector :: proc($name: string) -> objc_SEL   ---
 objc_find_class        :: proc($name: string) -> objc_Class ---
 objc_find_class        :: proc($name: string) -> objc_Class ---
 objc_register_class    :: proc($name: string) -> objc_Class ---
 objc_register_class    :: proc($name: string) -> objc_Class ---
-
+objc_ivar_get          :: proc(self: ^$T) -> ^$U ---
 
 
 valgrind_client_request :: proc(default: uintptr, request: uintptr, a0, a1, a2, a3, a4: uintptr) -> uintptr ---
 valgrind_client_request :: proc(default: uintptr, request: uintptr, a0, a1, a2, a3, a4: uintptr) -> uintptr ---
 
 

+ 9 - 0
base/runtime/core_builtin.odin

@@ -648,6 +648,9 @@ append_nothing :: proc(array: ^$T/[dynamic]$E, loc := #caller_location) -> (n: i
 
 
 @builtin
 @builtin
 inject_at_elem :: proc(array: ^$T/[dynamic]$E, #any_int index: int, #no_broadcast arg: E, loc := #caller_location) -> (ok: bool, err: Allocator_Error) #no_bounds_check #optional_allocator_error {
 inject_at_elem :: proc(array: ^$T/[dynamic]$E, #any_int index: int, #no_broadcast arg: E, loc := #caller_location) -> (ok: bool, err: Allocator_Error) #no_bounds_check #optional_allocator_error {
+	when !ODIN_NO_BOUNDS_CHECK {
+		ensure(index >= 0, "Index must be positive.", loc)
+	}
 	if array == nil {
 	if array == nil {
 		return
 		return
 	}
 	}
@@ -666,6 +669,9 @@ inject_at_elem :: proc(array: ^$T/[dynamic]$E, #any_int index: int, #no_broadcas
 
 
 @builtin
 @builtin
 inject_at_elems :: proc(array: ^$T/[dynamic]$E, #any_int index: int, #no_broadcast args: ..E, loc := #caller_location) -> (ok: bool, err: Allocator_Error) #no_bounds_check #optional_allocator_error {
 inject_at_elems :: proc(array: ^$T/[dynamic]$E, #any_int index: int, #no_broadcast args: ..E, loc := #caller_location) -> (ok: bool, err: Allocator_Error) #no_bounds_check #optional_allocator_error {
+	when !ODIN_NO_BOUNDS_CHECK {
+		ensure(index >= 0, "Index must be positive.", loc)
+	}
 	if array == nil {
 	if array == nil {
 		return
 		return
 	}
 	}
@@ -689,6 +695,9 @@ inject_at_elems :: proc(array: ^$T/[dynamic]$E, #any_int index: int, #no_broadca
 
 
 @builtin
 @builtin
 inject_at_elem_string :: proc(array: ^$T/[dynamic]$E/u8, #any_int index: int, arg: string, loc := #caller_location) -> (ok: bool, err: Allocator_Error) #no_bounds_check #optional_allocator_error {
 inject_at_elem_string :: proc(array: ^$T/[dynamic]$E/u8, #any_int index: int, arg: string, loc := #caller_location) -> (ok: bool, err: Allocator_Error) #no_bounds_check #optional_allocator_error {
+	when !ODIN_NO_BOUNDS_CHECK {
+		ensure(index >= 0, "Index must be positive.", loc)
+	}
 	if array == nil {
 	if array == nil {
 		return
 		return
 	}
 	}

+ 8 - 0
base/runtime/default_temp_allocator_arena.odin

@@ -1,6 +1,7 @@
 package runtime
 package runtime
 
 
 import "base:intrinsics"
 import "base:intrinsics"
+import "base:sanitizer"
 
 
 DEFAULT_ARENA_GROWING_MINIMUM_BLOCK_SIZE :: uint(DEFAULT_TEMP_ALLOCATOR_BACKING_SIZE)
 DEFAULT_ARENA_GROWING_MINIMUM_BLOCK_SIZE :: uint(DEFAULT_TEMP_ALLOCATOR_BACKING_SIZE)
 
 
@@ -43,6 +44,8 @@ memory_block_alloc :: proc(allocator: Allocator, capacity: uint, alignment: uint
 	block.base = ([^]byte)(uintptr(block) + base_offset)
 	block.base = ([^]byte)(uintptr(block) + base_offset)
 	block.capacity = uint(end - uintptr(block.base))
 	block.capacity = uint(end - uintptr(block.base))
 
 
+	sanitizer.address_poison(block.base, block.capacity)
+
 	// Should be zeroed
 	// Should be zeroed
 	assert(block.used == 0)
 	assert(block.used == 0)
 	assert(block.prev == nil)
 	assert(block.prev == nil)
@@ -52,6 +55,7 @@ memory_block_alloc :: proc(allocator: Allocator, capacity: uint, alignment: uint
 memory_block_dealloc :: proc(block_to_free: ^Memory_Block, loc := #caller_location) {
 memory_block_dealloc :: proc(block_to_free: ^Memory_Block, loc := #caller_location) {
 	if block_to_free != nil {
 	if block_to_free != nil {
 		allocator := block_to_free.allocator
 		allocator := block_to_free.allocator
+		sanitizer.address_unpoison(block_to_free.base, block_to_free.capacity)
 		mem_free(block_to_free, allocator, loc)
 		mem_free(block_to_free, allocator, loc)
 	}
 	}
 }
 }
@@ -83,6 +87,7 @@ alloc_from_memory_block :: proc(block: ^Memory_Block, min_size, alignment: uint)
 		return
 		return
 	}
 	}
 	data = block.base[block.used+alignment_offset:][:min_size]
 	data = block.base[block.used+alignment_offset:][:min_size]
+	sanitizer.address_unpoison(block.base[block.used:block.used+size])
 	block.used += size
 	block.used += size
 	return
 	return
 }
 }
@@ -162,6 +167,7 @@ arena_free_all :: proc(arena: ^Arena, loc := #caller_location) {
 	if arena.curr_block != nil {
 	if arena.curr_block != nil {
 		intrinsics.mem_zero(arena.curr_block.base, arena.curr_block.used)
 		intrinsics.mem_zero(arena.curr_block.base, arena.curr_block.used)
 		arena.curr_block.used = 0
 		arena.curr_block.used = 0
+		sanitizer.address_poison(arena.curr_block.base, arena.curr_block.capacity)
 	}
 	}
 	arena.total_used = 0
 	arena.total_used = 0
 }
 }
@@ -226,6 +232,7 @@ arena_allocator_proc :: proc(allocator_data: rawptr, mode: Allocator_Mode,
 					// grow data in-place, adjusting next allocation
 					// grow data in-place, adjusting next allocation
 					block.used = uint(new_end)
 					block.used = uint(new_end)
 					data = block.base[start:new_end]
 					data = block.base[start:new_end]
+					sanitizer.address_unpoison(data)
 					return
 					return
 				}
 				}
 			}
 			}
@@ -299,6 +306,7 @@ arena_temp_end :: proc(temp: Arena_Temp, loc := #caller_location) {
 			assert(block.used >= temp.used, "out of order use of arena_temp_end", loc)
 			assert(block.used >= temp.used, "out of order use of arena_temp_end", loc)
 			amount_to_zero := block.used-temp.used
 			amount_to_zero := block.used-temp.used
 			intrinsics.mem_zero(block.base[temp.used:], amount_to_zero)
 			intrinsics.mem_zero(block.base[temp.used:], amount_to_zero)
+			sanitizer.address_poison(block.base[temp.used:block.capacity])
 			block.used = temp.used
 			block.used = temp.used
 			arena.total_used -= amount_to_zero
 			arena.total_used -= amount_to_zero
 		}
 		}

+ 10 - 2
base/runtime/heap_allocator_windows.odin

@@ -1,5 +1,7 @@
 package runtime
 package runtime
 
 
+import "../sanitizer"
+
 foreign import kernel32 "system:Kernel32.lib"
 foreign import kernel32 "system:Kernel32.lib"
 
 
 @(private="file")
 @(private="file")
@@ -16,7 +18,10 @@ foreign kernel32 {
 
 
 _heap_alloc :: proc "contextless" (size: int, zero_memory := true) -> rawptr {
 _heap_alloc :: proc "contextless" (size: int, zero_memory := true) -> rawptr {
 	HEAP_ZERO_MEMORY :: 0x00000008
 	HEAP_ZERO_MEMORY :: 0x00000008
-	return HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY if zero_memory else 0, uint(size))
+	ptr := HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY if zero_memory else 0, uint(size))
+	// NOTE(lucas): asan not guarunteed to unpoison win32 heap out of the box, do it ourselves
+	sanitizer.address_unpoison(ptr, size)
+	return ptr
 }
 }
 _heap_resize :: proc "contextless" (ptr: rawptr, new_size: int) -> rawptr {
 _heap_resize :: proc "contextless" (ptr: rawptr, new_size: int) -> rawptr {
 	if new_size == 0 {
 	if new_size == 0 {
@@ -28,7 +33,10 @@ _heap_resize :: proc "contextless" (ptr: rawptr, new_size: int) -> rawptr {
 	}
 	}
 
 
 	HEAP_ZERO_MEMORY :: 0x00000008
 	HEAP_ZERO_MEMORY :: 0x00000008
-	return HeapReAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, ptr, uint(new_size))
+	new_ptr := HeapReAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, ptr, uint(new_size))
+	// NOTE(lucas): asan not guarunteed to unpoison win32 heap out of the box, do it ourselves
+	sanitizer.address_unpoison(new_ptr, new_size)
+	return new_ptr
 }
 }
 _heap_free :: proc "contextless" (ptr: rawptr) {
 _heap_free :: proc "contextless" (ptr: rawptr) {
 	if ptr == nil {
 	if ptr == nil {

+ 214 - 109
base/runtime/internal.odin

@@ -16,6 +16,12 @@ RUNTIME_REQUIRE :: false // !ODIN_TILDE
 @(private)
 @(private)
 __float16 :: f16 when __ODIN_LLVM_F16_SUPPORTED else u16
 __float16 :: f16 when __ODIN_LLVM_F16_SUPPORTED else u16
 
 
+HAS_HARDWARE_SIMD :: false when (ODIN_ARCH == .amd64 || ODIN_ARCH == .i386) && !intrinsics.has_target_feature("sse2") else
+	false when (ODIN_ARCH == .arm64 || ODIN_ARCH == .arm32) && !intrinsics.has_target_feature("neon") else
+	false when (ODIN_ARCH == .wasm64p32 || ODIN_ARCH == .wasm32) && !intrinsics.has_target_feature("simd128") else
+	false when (ODIN_ARCH == .riscv64) && !intrinsics.has_target_feature("v") else
+	true
+
 
 
 @(private)
 @(private)
 byte_slice :: #force_inline proc "contextless" (data: rawptr, len: int) -> []byte #no_bounds_check {
 byte_slice :: #force_inline proc "contextless" (data: rawptr, len: int) -> []byte #no_bounds_check {
@@ -229,151 +235,242 @@ memory_equal :: proc "contextless" (x, y: rawptr, n: int) -> bool {
 	case n == 0: return true
 	case n == 0: return true
 	case x == y: return true
 	case x == y: return true
 	}
 	}
-	a, b := ([^]byte)(x), ([^]byte)(y)
-	length := uint(n)
-
-	for i := uint(0); i < length; i += 1 {
-		if a[i] != b[i] {
-			return false
-		}
-	}
-	return true
-	
-/*
-
-	when size_of(uint) == 8 {
-		if word_length := length >> 3; word_length != 0 {
-			for _ in 0..<word_length {
-				if intrinsics.unaligned_load((^u64)(a)) != intrinsics.unaligned_load((^u64)(b)) {
-					return false
+	a, b := cast([^]byte)x, cast([^]byte)y
+
+	n := uint(n)
+	i := uint(0)
+	m := uint(0)
+
+	if n >= 8 {
+		when HAS_HARDWARE_SIMD {
+			// Avoid using 256-bit SIMD on platforms where its emulation is
+			// likely to be less than ideal.
+			when ODIN_ARCH == .amd64 && intrinsics.has_target_feature("avx2") {
+				m = n / 32 * 32
+				for /**/; i < m; i += 32 {
+					load_a := intrinsics.unaligned_load(cast(^#simd[32]u8)&a[i])
+					load_b := intrinsics.unaligned_load(cast(^#simd[32]u8)&b[i])
+					ne := intrinsics.simd_lanes_ne(load_a, load_b)
+					if intrinsics.simd_reduce_or(ne) != 0 {
+						return false
+					}
 				}
 				}
-				a = a[size_of(u64):]
-				b = b[size_of(u64):]
 			}
 			}
 		}
 		}
-		
-		if length & 4 != 0 {
-			if intrinsics.unaligned_load((^u32)(a)) != intrinsics.unaligned_load((^u32)(b)) {
+
+		m = (n-i) / 16 * 16
+		for /**/; i < m; i += 16 {
+			load_a := intrinsics.unaligned_load(cast(^#simd[16]u8)&a[i])
+			load_b := intrinsics.unaligned_load(cast(^#simd[16]u8)&b[i])
+			ne := intrinsics.simd_lanes_ne(load_a, load_b)
+			if intrinsics.simd_reduce_or(ne) != 0 {
 				return false
 				return false
 			}
 			}
-			a = a[size_of(u32):]
-			b = b[size_of(u32):]
 		}
 		}
-		
-		if length & 2 != 0 {
-			if intrinsics.unaligned_load((^u16)(a)) != intrinsics.unaligned_load((^u16)(b)) {
+
+		m = (n-i) / 8 * 8
+		for /**/; i < m; i += 8 {
+			if intrinsics.unaligned_load(cast(^uintptr)&a[i]) != intrinsics.unaligned_load(cast(^uintptr)&b[i]) {
 				return false
 				return false
 			}
 			}
-			a = a[size_of(u16):]
-			b = b[size_of(u16):]
 		}
 		}
-		
-		if length & 1 != 0 && a[0] != b[0] {
-			return false	
+	}
+
+	for /**/; i < n; i += 1 {
+		if a[i] != b[i] {
+			return false
 		}
 		}
-		return true
-	} else {
-		if word_length := length >> 2; word_length != 0 {
-			for _ in 0..<word_length {
-				if intrinsics.unaligned_load((^u32)(a)) != intrinsics.unaligned_load((^u32)(b)) {
-					return false
+	}
+	return true
+}
+
+memory_compare :: proc "contextless" (x, y: rawptr, n: int) -> int #no_bounds_check {
+	switch {
+	case x == y:   return 0
+	case x == nil: return -1
+	case y == nil: return +1
+	}
+	a, b := cast([^]byte)x, cast([^]byte)y
+	
+	n := uint(n)
+	i := uint(0)
+	m := uint(0)
+
+	when HAS_HARDWARE_SIMD {
+		when ODIN_ARCH == .amd64 && intrinsics.has_target_feature("avx2") {
+			m = n / 32 * 32
+			for /**/; i < m; i += 32 {
+				load_a := intrinsics.unaligned_load(cast(^#simd[32]u8)&a[i])
+				load_b := intrinsics.unaligned_load(cast(^#simd[32]u8)&b[i])
+				comparison := intrinsics.simd_lanes_ne(load_a, load_b)
+				if intrinsics.simd_reduce_or(comparison) != 0 {
+					sentinel: #simd[32]u8 = u8(0xFF)
+					indices := intrinsics.simd_indices(#simd[32]u8)
+					index_select := intrinsics.simd_select(comparison, indices, sentinel)
+					index_reduce := cast(uint)intrinsics.simd_reduce_min(index_select)
+					return -1 if a[i+index_reduce] < b[i+index_reduce] else +1
 				}
 				}
-				a = a[size_of(u32):]
-				b = b[size_of(u32):]
 			}
 			}
 		}
 		}
-		
-		length &= 3
-		
-		if length != 0 {
-			for i in 0..<length {
-				if a[i] != b[i] {
-					return false
-				}
-			}
+	}
+
+	m = (n-i) / 16 * 16
+	for /**/; i < m; i += 16 {
+		load_a := intrinsics.unaligned_load(cast(^#simd[16]u8)&a[i])
+		load_b := intrinsics.unaligned_load(cast(^#simd[16]u8)&b[i])
+		comparison := intrinsics.simd_lanes_ne(load_a, load_b)
+		if intrinsics.simd_reduce_or(comparison) != 0 {
+			sentinel: #simd[16]u8 = u8(0xFF)
+			indices := intrinsics.simd_indices(#simd[16]u8)
+			index_select := intrinsics.simd_select(comparison, indices, sentinel)
+			index_reduce := cast(uint)intrinsics.simd_reduce_min(index_select)
+			return -1 if a[i+index_reduce] < b[i+index_reduce] else +1
 		}
 		}
+	}
 
 
-		return true
+	// 64-bit SIMD is faster than using a `uintptr` to detect a difference then
+	// re-iterating with the byte-by-byte loop, at least on AMD64.
+	m = (n-i) / 8 * 8
+	for /**/; i < m; i += 8 {
+		load_a := intrinsics.unaligned_load(cast(^#simd[8]u8)&a[i])
+		load_b := intrinsics.unaligned_load(cast(^#simd[8]u8)&b[i])
+		comparison := intrinsics.simd_lanes_ne(load_a, load_b)
+		if intrinsics.simd_reduce_or(comparison) != 0 {
+			sentinel: #simd[8]u8 = u8(0xFF)
+			indices := intrinsics.simd_indices(#simd[8]u8)
+			index_select := intrinsics.simd_select(comparison, indices, sentinel)
+			index_reduce := cast(uint)intrinsics.simd_reduce_min(index_select)
+			return -1 if a[i+index_reduce] < b[i+index_reduce] else +1
+		}
 	}
 	}
-*/
 
 
+	for /**/; i < n; i += 1 {
+		if a[i] ~ b[i] != 0 {
+			return -1 if int(a[i]) - int(b[i]) < 0 else +1
+		}
+	}
+	return 0
 }
 }
-memory_compare :: proc "contextless" (a, b: rawptr, n: int) -> int #no_bounds_check {
-	switch {
-	case a == b:   return 0
-	case a == nil: return -1
-	case b == nil: return +1
-	}
-
-	x := uintptr(a)
-	y := uintptr(b)
-	n := uintptr(n)
-
-	SU :: size_of(uintptr)
-	fast := n/SU + 1
-	offset := (fast-1)*SU
-	curr_block := uintptr(0)
-	if n < SU {
-		fast = 0
-	}
-
-	for /**/; curr_block < fast; curr_block += 1 {
-		va := (^uintptr)(x + curr_block * size_of(uintptr))^
-		vb := (^uintptr)(y + curr_block * size_of(uintptr))^
-		if va ~ vb != 0 {
-			for pos := curr_block*SU; pos < n; pos += 1 {
-				a := (^byte)(x+pos)^
-				b := (^byte)(y+pos)^
-				if a ~ b != 0 {
-					return -1 if (int(a) - int(b)) < 0 else +1
+
+memory_compare_zero :: proc "contextless" (a: rawptr, n: int) -> int #no_bounds_check {
+	n := uint(n)
+	i := uint(0)
+	m := uint(0)
+
+	// Because we're comparing against zero, we never return -1, as that would
+	// indicate the compared value is less than zero.
+	//
+	// Note that a zero return value here means equality.
+
+	bytes := ([^]u8)(a)
+
+	if n >= 8 {
+		when HAS_HARDWARE_SIMD {
+			when ODIN_ARCH == .amd64 && intrinsics.has_target_feature("avx2") {
+				scanner32: #simd[32]u8
+				m = n / 32 * 32
+				for /**/; i < m; i += 32 {
+					load := intrinsics.unaligned_load(cast(^#simd[32]u8)&bytes[i])
+					ne := intrinsics.simd_lanes_ne(scanner32, load)
+					if intrinsics.simd_reduce_or(ne) > 0 {
+						return 1
+					}
 				}
 				}
 			}
 			}
 		}
 		}
-	}
 
 
-	for /**/; offset < n; offset += 1 {
-		a := (^byte)(x+offset)^
-		b := (^byte)(y+offset)^
-		if a ~ b != 0 {
-			return -1 if (int(a) - int(b)) < 0 else +1
+		scanner16: #simd[16]u8
+		m = (n-i) / 16 * 16
+		for /**/; i < m; i += 16 {
+			load := intrinsics.unaligned_load(cast(^#simd[16]u8)&bytes[i])
+			ne := intrinsics.simd_lanes_ne(scanner16, load)
+			if intrinsics.simd_reduce_or(ne) != 0 {
+				return 1
+			}
+		}
+
+		m = (n-i) / 8 * 8
+		for /**/; i < m; i += 8 {
+			if intrinsics.unaligned_load(cast(^uintptr)&bytes[i]) != 0 {
+				return 1
+			}
 		}
 		}
 	}
 	}
 
 
+	for /**/; i < n; i += 1 {
+		if bytes[i] != 0 {
+			return 1
+		}
+	}
 	return 0
 	return 0
 }
 }
 
 
-memory_compare_zero :: proc "contextless" (a: rawptr, n: int) -> int #no_bounds_check {
-	x := uintptr(a)
-	n := uintptr(n)
-
-	SU :: size_of(uintptr)
-	fast := n/SU + 1
-	offset := (fast-1)*SU
-	curr_block := uintptr(0)
-	if n < SU {
-		fast = 0
-	}
-
-	for /**/; curr_block < fast; curr_block += 1 {
-		va := (^uintptr)(x + curr_block * size_of(uintptr))^
-		if va ~ 0 != 0 {
-			for pos := curr_block*SU; pos < n; pos += 1 {
-				a := (^byte)(x+pos)^
-				if a ~ 0 != 0 {
-					return -1 if int(a) < 0 else +1
+memory_prefix_length :: proc "contextless" (x, y: rawptr, n: int) -> (idx: int) #no_bounds_check {
+	switch {
+	case x == y:   return n
+	case x == nil: return 0
+	case y == nil: return 0
+	}
+	a, b := cast([^]byte)x, cast([^]byte)y
+
+	n := uint(n)
+	i := uint(0)
+	m := uint(0)
+
+	when HAS_HARDWARE_SIMD {
+		when ODIN_ARCH == .amd64 && intrinsics.has_target_feature("avx2") {
+			m = n / 32 * 32
+			for /**/; i < m; i += 32 {
+				load_a := intrinsics.unaligned_load(cast(^#simd[32]u8)&a[i])
+				load_b := intrinsics.unaligned_load(cast(^#simd[32]u8)&b[i])
+				comparison := intrinsics.simd_lanes_ne(load_a, load_b)
+				if intrinsics.simd_reduce_or(comparison) != 0 {
+					sentinel: #simd[32]u8 = u8(0xFF)
+					indices := intrinsics.simd_indices(#simd[32]u8)
+					index_select := intrinsics.simd_select(comparison, indices, sentinel)
+					index_reduce := cast(uint)intrinsics.simd_reduce_min(index_select)
+					return int(i + index_reduce)
 				}
 				}
 			}
 			}
 		}
 		}
 	}
 	}
 
 
-	for /**/; offset < n; offset += 1 {
-		a := (^byte)(x+offset)^
-		if a ~ 0 != 0 {
-			return -1 if int(a) < 0 else +1
+	m = (n-i) / 16 * 16
+	for /**/; i < m; i += 16 {
+		load_a := intrinsics.unaligned_load(cast(^#simd[16]u8)&a[i])
+		load_b := intrinsics.unaligned_load(cast(^#simd[16]u8)&b[i])
+		comparison := intrinsics.simd_lanes_ne(load_a, load_b)
+		if intrinsics.simd_reduce_or(comparison) != 0 {
+			sentinel: #simd[16]u8 = u8(0xFF)
+			indices := intrinsics.simd_indices(#simd[16]u8)
+			index_select := intrinsics.simd_select(comparison, indices, sentinel)
+			index_reduce := cast(uint)intrinsics.simd_reduce_min(index_select)
+			return int(i + index_reduce)
 		}
 		}
 	}
 	}
 
 
-	return 0
+	// 64-bit SIMD is faster than using a `uintptr` to detect a difference then
+	// re-iterating with the byte-by-byte loop, at least on AMD64.
+	m = (n-i) / 8 * 8
+	for /**/; i < m; i += 8 {
+		load_a := intrinsics.unaligned_load(cast(^#simd[8]u8)&a[i])
+		load_b := intrinsics.unaligned_load(cast(^#simd[8]u8)&b[i])
+		comparison := intrinsics.simd_lanes_ne(load_a, load_b)
+		if intrinsics.simd_reduce_or(comparison) != 0 {
+			sentinel: #simd[8]u8 = u8(0xFF)
+			indices := intrinsics.simd_indices(#simd[8]u8)
+			index_select := intrinsics.simd_select(comparison, indices, sentinel)
+			index_reduce := cast(uint)intrinsics.simd_reduce_min(index_select)
+			return int(i + index_reduce)
+		}
+	}
+
+	for /**/; i < n; i += 1 {
+		if a[i] ~ b[i] != 0 {
+			return int(i)
+		}
+	}
+	return int(n)
 }
 }
 
 
 string_eq :: proc "contextless" (lhs, rhs: string) -> bool {
 string_eq :: proc "contextless" (lhs, rhs: string) -> bool {
@@ -1106,3 +1203,11 @@ __read_bits :: proc "contextless" (dst, src: [^]byte, offset: uintptr, size: uin
 		dst[j>>3]  |= the_bit<<(j&7)
 		dst[j>>3]  |= the_bit<<(j&7)
 	}
 	}
 }
 }
+
+when .Address in ODIN_SANITIZER_FLAGS {
+	foreign {
+		@(require)
+		__asan_unpoison_memory_region :: proc "system" (address: rawptr, size: uint) ---
+	}
+}
+

+ 19 - 6
base/runtime/procs_darwin.odin

@@ -2,21 +2,34 @@
 package runtime
 package runtime
 
 
 @(priority_index=-1e6)
 @(priority_index=-1e6)
-foreign import "system:Foundation.framework"
+foreign import ObjC "system:objc"
 
 
 import "base:intrinsics"
 import "base:intrinsics"
 
 
-objc_id :: ^intrinsics.objc_object
+objc_id    :: ^intrinsics.objc_object
 objc_Class :: ^intrinsics.objc_class
 objc_Class :: ^intrinsics.objc_class
-objc_SEL :: ^intrinsics.objc_selector
+objc_SEL   :: ^intrinsics.objc_selector
+objc_Ivar  :: ^intrinsics.objc_ivar
+objc_BOOL  :: bool
 
 
-foreign Foundation {
-	objc_lookUpClass :: proc "c" (name: cstring) -> objc_Class ---
+
+objc_IMP :: proc "c" (object: objc_id, sel: objc_SEL, #c_vararg args: ..any) -> objc_id
+
+foreign ObjC {
 	sel_registerName :: proc "c" (name: cstring) -> objc_SEL ---
 	sel_registerName :: proc "c" (name: cstring) -> objc_SEL ---
-	objc_allocateClassPair :: proc "c" (superclass: objc_Class, name: cstring, extraBytes: uint) -> objc_Class ---
 
 
 	objc_msgSend        :: proc "c" (self: objc_id, op: objc_SEL, #c_vararg args: ..any) ---
 	objc_msgSend        :: proc "c" (self: objc_id, op: objc_SEL, #c_vararg args: ..any) ---
 	objc_msgSend_fpret  :: proc "c" (self: objc_id, op: objc_SEL, #c_vararg args: ..any) -> f64 ---
 	objc_msgSend_fpret  :: proc "c" (self: objc_id, op: objc_SEL, #c_vararg args: ..any) -> f64 ---
 	objc_msgSend_fp2ret :: proc "c" (self: objc_id, op: objc_SEL, #c_vararg args: ..any) -> complex128 ---
 	objc_msgSend_fp2ret :: proc "c" (self: objc_id, op: objc_SEL, #c_vararg args: ..any) -> complex128 ---
 	objc_msgSend_stret  :: proc "c" (self: objc_id, op: objc_SEL, #c_vararg args: ..any) ---
 	objc_msgSend_stret  :: proc "c" (self: objc_id, op: objc_SEL, #c_vararg args: ..any) ---
+
+	objc_lookUpClass          :: proc "c" (name: cstring) -> objc_Class ---
+	objc_allocateClassPair    :: proc "c" (superclass: objc_Class, name: cstring, extraBytes: uint) -> objc_Class ---
+	objc_registerClassPair    :: proc "c" (cls : objc_Class) ---
+	class_addMethod           :: proc "c" (cls: objc_Class, name: objc_SEL, imp: objc_IMP, types: cstring) -> objc_BOOL ---
+	class_addIvar             :: proc "c" (cls: objc_Class, name: cstring, size: uint, alignment: u8, types: cstring) -> objc_BOOL ---
+	class_getInstanceVariable :: proc "c" (cls : objc_Class, name: cstring) -> objc_Ivar ---
+	class_getInstanceSize     :: proc "c" (cls : objc_Class) -> uint ---
+	ivar_getOffset            :: proc "c" (v: objc_Ivar) -> uintptr ---
 }
 }
+

+ 309 - 7
base/sanitizer/address.odin

@@ -40,9 +40,10 @@ Address_Access_Type :: enum {
 	write,
 	write,
 }
 }
 
 
-Address_Located_Address_String :: struct {
+Address_Located_Address :: struct {
 	category: string,
 	category: string,
 	name: string,
 	name: string,
+	region: []byte,
 }
 }
 
 
 Address_Shadow_Mapping :: struct {
 Address_Shadow_Mapping :: struct {
@@ -50,30 +51,81 @@ Address_Shadow_Mapping :: struct {
 	offset: uint,
 	offset: uint,
 }
 }
 
 
+/*
+Marks a slice as unaddressable
+
+Code instrumented with `-sanitize:address` is forbidden from accessing any address
+within the slice. This procedure is not thread-safe because no two threads can
+poison or unpoison memory in the same memory region region simultaneously.
+
+When asan is not enabled this procedure does nothing.
+*/
+@(no_sanitize_address)
 address_poison_slice :: proc "contextless" (region: $T/[]$E) {
 address_poison_slice :: proc "contextless" (region: $T/[]$E) {
 	when ASAN_ENABLED {
 	when ASAN_ENABLED {
 		__asan_poison_memory_region(raw_data(region), size_of(E) * len(region))
 		__asan_poison_memory_region(raw_data(region), size_of(E) * len(region))
 	}
 	}
 }
 }
 
 
+/*
+Marks a slice as addressable
+
+Code instrumented with `-sanitize:address` is allowed to access any address
+within the slice again. This procedure is not thread-safe because no two threads
+can poison or unpoison memory in the same memory region region simultaneously.
+
+When asan is not enabled this procedure does nothing.
+*/
+@(no_sanitize_address)
 address_unpoison_slice :: proc "contextless" (region: $T/[]$E) {
 address_unpoison_slice :: proc "contextless" (region: $T/[]$E) {
 	when ASAN_ENABLED {
 	when ASAN_ENABLED {
 		__asan_unpoison_memory_region(raw_data(region), size_of(E) * len(region))
 		__asan_unpoison_memory_region(raw_data(region), size_of(E) * len(region))
 	}
 	}
 }
 }
 
 
+/*
+Marks a pointer as unaddressable
+
+Code instrumented with `-sanitize:address` is forbidden from accessing any address
+within the region the pointer points to. This procedure is not thread-safe because no
+two threads can poison or unpoison memory in the same memory region region simultaneously.
+
+When asan is not enabled this procedure does nothing.
+*/
+@(no_sanitize_address)
 address_poison_ptr :: proc "contextless" (ptr: ^$T) {
 address_poison_ptr :: proc "contextless" (ptr: ^$T) {
 	when ASAN_ENABLED {
 	when ASAN_ENABLED {
 		__asan_poison_memory_region(ptr, size_of(T))
 		__asan_poison_memory_region(ptr, size_of(T))
 	}
 	}
 }
 }
 
 
+/*
+Marks a pointer as addressable
+
+Code instrumented with `-sanitize:address` is allowed to access any address
+within the region the pointer points to again. This procedure is not thread-safe
+because no two threads can poison or unpoison memory in the same memory region
+region simultaneously.
+
+When asan is not enabled this procedure does nothing.
+*/
+@(no_sanitize_address)
 address_unpoison_ptr :: proc "contextless" (ptr: ^$T) {
 address_unpoison_ptr :: proc "contextless" (ptr: ^$T) {
 	when ASAN_ENABLED {
 	when ASAN_ENABLED {
 		__asan_unpoison_memory_region(ptr, size_of(T))
 		__asan_unpoison_memory_region(ptr, size_of(T))
 	}
 	}
 }
 }
 
 
+/*
+Marks the region covering `[ptr, ptr+len)` as unaddressable
+
+Code instrumented with `-sanitize:address` is forbidden from accessing any address
+within the region. This procedure is not thread-safe because no two threads can
+poison or unpoison memory in the same memory region region simultaneously.
+
+When asan is not enabled this procedure does nothing.
+*/
+@(no_sanitize_address)
 address_poison_rawptr :: proc "contextless" (ptr: rawptr, len: int) {
 address_poison_rawptr :: proc "contextless" (ptr: rawptr, len: int) {
 	when ASAN_ENABLED {
 	when ASAN_ENABLED {
 		assert_contextless(len >= 0)
 		assert_contextless(len >= 0)
@@ -81,6 +133,32 @@ address_poison_rawptr :: proc "contextless" (ptr: rawptr, len: int) {
 	}
 	}
 }
 }
 
 
+/*
+Marks the region covering `[ptr, ptr+len)` as unaddressable
+
+Code instrumented with `-sanitize:address` is forbidden from accessing any address
+within the region. This procedure is not thread-safe because no two threads can
+poison or unpoison memory in the same memory region region simultaneously.
+
+When asan is not enabled this procedure does nothing.
+*/
+@(no_sanitize_address)
+address_poison_rawptr_uint :: proc "contextless" (ptr: rawptr, len: uint) {
+	when ASAN_ENABLED {
+		__asan_poison_memory_region(ptr, len)
+	}
+}
+
+/*
+Marks the region covering `[ptr, ptr+len)` as addressable
+
+Code instrumented with `-sanitize:address` is allowed to access any address
+within the region again. This procedure is not thread-safe because no two
+threads can poison or unpoison memory in the same memory region region simultaneously.
+
+When asan is not enabled this procedure does nothing.
+*/
+@(no_sanitize_address)
 address_unpoison_rawptr :: proc "contextless" (ptr: rawptr, len: int) {
 address_unpoison_rawptr :: proc "contextless" (ptr: rawptr, len: int) {
 	when ASAN_ENABLED {
 	when ASAN_ENABLED {
 		assert_contextless(len >= 0)
 		assert_contextless(len >= 0)
@@ -88,25 +166,61 @@ address_unpoison_rawptr :: proc "contextless" (ptr: rawptr, len: int) {
 	}
 	}
 }
 }
 
 
+/*
+Marks the region covering `[ptr, ptr+len)` as addressable
+
+Code instrumented with `-sanitize:address` is allowed to access any address
+within the region again. This procedure is not thread-safe because no two
+threads can poison or unpoison memory in the same memory region region simultaneously.
+
+When asan is not enabled this procedure does nothing.
+*/
+@(no_sanitize_address)
+address_unpoison_rawptr_uint :: proc "contextless" (ptr: rawptr, len: uint) {
+	when ASAN_ENABLED {
+		__asan_unpoison_memory_region(ptr, len)
+	}
+}
+
 address_poison :: proc {
 address_poison :: proc {
 	address_poison_slice,
 	address_poison_slice,
 	address_poison_ptr,
 	address_poison_ptr,
 	address_poison_rawptr,
 	address_poison_rawptr,
+	address_poison_rawptr_uint,
 }
 }
 
 
 address_unpoison :: proc {
 address_unpoison :: proc {
 	address_unpoison_slice,
 	address_unpoison_slice,
 	address_unpoison_ptr,
 	address_unpoison_ptr,
 	address_unpoison_rawptr,
 	address_unpoison_rawptr,
+	address_unpoison_rawptr_uint,
 }
 }
 
 
+/*
+Registers a callback to be run when asan detects a memory error right before terminating
+the process.
+
+This can be used for logging and/or debugging purposes.
+
+When asan is not enabled this procedure does nothing.
+*/
+@(no_sanitize_address)
 address_set_death_callback :: proc "contextless" (callback: Address_Death_Callback) {
 address_set_death_callback :: proc "contextless" (callback: Address_Death_Callback) {
 	when ASAN_ENABLED {
 	when ASAN_ENABLED {
 		__sanitizer_set_death_callback(callback)
 		__sanitizer_set_death_callback(callback)
 	}
 	}
 }
 }
 
 
-address_region_is_poisoned_slice :: proc "contextless" (region: []$T/$E) -> rawptr {
+/*
+Checks if the memory region covered by the slice is poisoned.
+
+If it is poisoned this procedure returns the address which would result
+in an asan error.
+
+When asan is not enabled this procedure returns `nil`.
+*/
+@(no_sanitize_address)
+address_region_is_poisoned_slice :: proc "contextless" (region: $T/[]$E) -> rawptr {
 	when ASAN_ENABLED {
 	when ASAN_ENABLED {
 		return __asan_region_is_poisoned(raw_data(region), size_of(E) * len(region))
 		return __asan_region_is_poisoned(raw_data(region), size_of(E) * len(region))
 	} else {
 	} else {
@@ -114,6 +228,15 @@ address_region_is_poisoned_slice :: proc "contextless" (region: []$T/$E) -> rawp
 	}
 	}
 }
 }
 
 
+/*
+Checks if the memory region pointed to by the pointer is poisoned.
+
+If it is poisoned this procedure returns the address which would result
+in an asan error.
+
+When asan is not enabled this procedure returns `nil`.
+*/
+@(no_sanitize_address)
 address_region_is_poisoned_ptr :: proc "contextless" (ptr: ^$T) -> rawptr {
 address_region_is_poisoned_ptr :: proc "contextless" (ptr: ^$T) -> rawptr {
 	when ASAN_ENABLED {
 	when ASAN_ENABLED {
 		return __asan_region_is_poisoned(ptr, size_of(T))
 		return __asan_region_is_poisoned(ptr, size_of(T))
@@ -122,6 +245,15 @@ address_region_is_poisoned_ptr :: proc "contextless" (ptr: ^$T) -> rawptr {
 	}
 	}
 }
 }
 
 
+/*
+Checks if the memory region covered by `[ptr, ptr+len)` is poisoned.
+
+If it is poisoned this procedure returns the address which would result
+in an asan error.
+
+When asan is not enabled this procedure returns `nil`.
+*/
+@(no_sanitize_address)
 address_region_is_poisoned_rawptr :: proc "contextless" (region: rawptr, len: int) -> rawptr {
 address_region_is_poisoned_rawptr :: proc "contextless" (region: rawptr, len: int) -> rawptr {
 	when ASAN_ENABLED {
 	when ASAN_ENABLED {
 		assert_contextless(len >= 0)
 		assert_contextless(len >= 0)
@@ -131,13 +263,41 @@ address_region_is_poisoned_rawptr :: proc "contextless" (region: rawptr, len: in
 	}
 	}
 }
 }
 
 
+/*
+Checks if the memory region covered by `[ptr, ptr+len)` is poisoned.
+
+If it is poisoned this procedure returns the address which would result
+in an asan error.
+
+When asan is not enabled this procedure returns `nil`.
+*/
+@(no_sanitize_address)
+address_region_is_poisoned_rawptr_uint :: proc "contextless" (region: rawptr, len: uint) -> rawptr {
+	when ASAN_ENABLED {
+		return __asan_region_is_poisoned(region, len)
+	} else {
+		return nil
+	}
+}
+
+
 address_region_is_poisoned :: proc {
 address_region_is_poisoned :: proc {
 	address_region_is_poisoned_slice,
 	address_region_is_poisoned_slice,
 	address_region_is_poisoned_ptr,
 	address_region_is_poisoned_ptr,
 	address_region_is_poisoned_rawptr,
 	address_region_is_poisoned_rawptr,
+	address_region_is_poisoned_rawptr_uint,
 }
 }
 
 
-address_address_is_poisoned :: proc "contextless" (address: rawptr) -> bool {
+/*
+Checks if the address is poisoned.
+
+If it is poisoned this procedure returns `true`, otherwise it returns
+`false`.
+
+When asan is not enabled this procedure returns `false`.
+*/
+@(no_sanitize_address)
+address_is_poisoned :: proc "contextless" (address: rawptr) -> bool {
 	when ASAN_ENABLED {
 	when ASAN_ENABLED {
 		return __asan_address_is_poisoned(address) != 0
 		return __asan_address_is_poisoned(address) != 0
 	} else {
 	} else {
@@ -145,12 +305,27 @@ address_address_is_poisoned :: proc "contextless" (address: rawptr) -> bool {
 	}
 	}
 }
 }
 
 
+/*
+Describes the sanitizer state for an address.
+
+This procedure prints the description out to `stdout`.
+
+When asan is not enabled this procedure does nothing.
+*/
+@(no_sanitize_address)
 address_describe_address :: proc "contextless" (address: rawptr) {
 address_describe_address :: proc "contextless" (address: rawptr) {
 	when ASAN_ENABLED {
 	when ASAN_ENABLED {
 		__asan_describe_address(address)
 		__asan_describe_address(address)
 	}
 	}
 }
 }
 
 
+/*
+Returns `true` if an asan error has occured, otherwise it returns
+`false`.
+
+When asan is not enabled this procedure returns `false`.
+*/
+@(no_sanitize_address)
 address_report_present :: proc "contextless" () -> bool {
 address_report_present :: proc "contextless" () -> bool {
 	when ASAN_ENABLED {
 	when ASAN_ENABLED {
 		return __asan_report_present() != 0
 		return __asan_report_present() != 0
@@ -159,6 +334,14 @@ address_report_present :: proc "contextless" () -> bool {
 	}
 	}
 }
 }
 
 
+/*
+Returns the program counter register value of an asan error.
+
+If no asan error has occurd `nil` is returned.
+
+When asan is not enabled this procedure returns `nil`.
+*/
+@(no_sanitize_address)
 address_get_report_pc :: proc "contextless" () -> rawptr {
 address_get_report_pc :: proc "contextless" () -> rawptr {
 	when ASAN_ENABLED {
 	when ASAN_ENABLED {
 		return __asan_get_report_pc()
 		return __asan_get_report_pc()
@@ -167,6 +350,14 @@ address_get_report_pc :: proc "contextless" () -> rawptr {
 	}
 	}
 }
 }
 
 
+/*
+Returns the base pointer register value of an asan error.
+
+If no asan error has occurd `nil` is returned.
+
+When asan is not enabled this procedure returns `nil`.
+*/
+@(no_sanitize_address)
 address_get_report_bp :: proc "contextless" () -> rawptr {
 address_get_report_bp :: proc "contextless" () -> rawptr {
 	when ASAN_ENABLED {
 	when ASAN_ENABLED {
 		return __asan_get_report_bp()
 		return __asan_get_report_bp()
@@ -175,6 +366,14 @@ address_get_report_bp :: proc "contextless" () -> rawptr {
 	}
 	}
 }
 }
 
 
+/*
+Returns the stack pointer register value of an asan error.
+
+If no asan error has occurd `nil` is returned.
+
+When asan is not enabled this procedure returns `nil`.
+*/
+@(no_sanitize_address)
 address_get_report_sp :: proc "contextless" () -> rawptr {
 address_get_report_sp :: proc "contextless" () -> rawptr {
 	when ASAN_ENABLED {
 	when ASAN_ENABLED {
 		return __asan_get_report_sp()
 		return __asan_get_report_sp()
@@ -183,6 +382,14 @@ address_get_report_sp :: proc "contextless" () -> rawptr {
 	}
 	}
 }
 }
 
 
+/*
+Returns the report buffer address of an asan error.
+
+If no asan error has occurd `nil` is returned.
+
+When asan is not enabled this procedure returns `nil`.
+*/
+@(no_sanitize_address)
 address_get_report_address :: proc "contextless" () -> rawptr {
 address_get_report_address :: proc "contextless" () -> rawptr {
 	when ASAN_ENABLED {
 	when ASAN_ENABLED {
 		return __asan_get_report_address()
 		return __asan_get_report_address()
@@ -191,14 +398,33 @@ address_get_report_address :: proc "contextless" () -> rawptr {
 	}
 	}
 }
 }
 
 
+/*
+Returns the address access type of an asan error.
+
+If no asan error has occurd `.none` is returned.
+
+When asan is not enabled this procedure returns `.none`.
+*/
+@(no_sanitize_address)
 address_get_report_access_type :: proc "contextless" () -> Address_Access_Type {
 address_get_report_access_type :: proc "contextless" () -> Address_Access_Type {
 	when ASAN_ENABLED {
 	when ASAN_ENABLED {
+		if ! address_report_present() {
+			return .none
+		}
 		return __asan_get_report_access_type() == 0 ? .read : .write
 		return __asan_get_report_access_type() == 0 ? .read : .write
 	} else {
 	} else {
 		return .none
 		return .none
 	}
 	}
 }
 }
 
 
+/*
+Returns the access size of an asan error.
+
+If no asan error has occurd `0` is returned.
+
+When asan is not enabled this procedure returns `0`.
+*/
+@(no_sanitize_address)
 address_get_report_access_size :: proc "contextless" () -> uint {
 address_get_report_access_size :: proc "contextless" () -> uint {
 	when ASAN_ENABLED {
 	when ASAN_ENABLED {
 		return __asan_get_report_access_size()
 		return __asan_get_report_access_size()
@@ -207,25 +433,52 @@ address_get_report_access_size :: proc "contextless" () -> uint {
 	}
 	}
 }
 }
 
 
+/*
+Returns the bug description of an asan error.
+
+If no asan error has occurd an empty string is returned.
+
+When asan is not enabled this procedure returns an empty string.
+*/
+@(no_sanitize_address)
 address_get_report_description :: proc "contextless" () -> string {
 address_get_report_description :: proc "contextless" () -> string {
 	when ASAN_ENABLED {
 	when ASAN_ENABLED {
 		return string(__asan_get_report_description())
 		return string(__asan_get_report_description())
 	} else {
 	} else {
-		return "unknown"
+		return ""
 	}
 	}
 }
 }
 
 
-address_locate_address :: proc "contextless" (addr: rawptr, data: []byte) -> (Address_Located_Address_String, []byte) {
+/*
+Returns asan information about the address provided, writing the category into `data`.
+
+The information provided include:
+* The category of the address, i.e. stack, global, heap, etc.
+* The name of the variable this address belongs to
+* The memory region of the address
+
+When asan is not enabled this procedure returns zero initialised values.
+*/
+@(no_sanitize_address)
+address_locate_address :: proc "contextless" (addr: rawptr, data: []byte) -> Address_Located_Address {
 	when ASAN_ENABLED {
 	when ASAN_ENABLED {
 		out_addr: rawptr
 		out_addr: rawptr
 		out_size: uint
 		out_size: uint
 		str := __asan_locate_address(addr, raw_data(data), len(data), &out_addr, &out_size)
 		str := __asan_locate_address(addr, raw_data(data), len(data), &out_addr, &out_size)
-		return { string(str), string(cstring(raw_data(data))) }, (cast([^]byte)out_addr)[:out_size]
+		return { string(str), string(cstring(raw_data(data))), (cast([^]byte)out_addr)[:out_size] }, 
 	} else {
 	} else {
-		return { "", "" }, {}
+		return { "", "", {} }
 	}
 	}
 }
 }
 
 
+/*
+Returns the allocation stack trace and thread id for a heap address.
+
+The stack trace is filled into the `data` slice.
+
+When asan is not enabled this procedure returns a zero initialised value.
+*/
+@(no_sanitize_address)
 address_get_alloc_stack_trace :: proc "contextless" (addr: rawptr, data: []rawptr) -> ([]rawptr, int) {
 address_get_alloc_stack_trace :: proc "contextless" (addr: rawptr, data: []rawptr) -> ([]rawptr, int) {
 	when ASAN_ENABLED {
 	when ASAN_ENABLED {
 		out_thread: i32
 		out_thread: i32
@@ -236,6 +489,14 @@ address_get_alloc_stack_trace :: proc "contextless" (addr: rawptr, data: []rawpt
 	}
 	}
 }
 }
 
 
+/*
+Returns the free stack trace and thread id for a heap address.
+
+The stack trace is filled into the `data` slice.
+
+When asan is not enabled this procedure returns zero initialised values.
+*/
+@(no_sanitize_address)
 address_get_free_stack_trace :: proc "contextless" (addr: rawptr, data: []rawptr) -> ([]rawptr, int) {
 address_get_free_stack_trace :: proc "contextless" (addr: rawptr, data: []rawptr) -> ([]rawptr, int) {
 	when ASAN_ENABLED {
 	when ASAN_ENABLED {
 		out_thread: i32
 		out_thread: i32
@@ -246,6 +507,12 @@ address_get_free_stack_trace :: proc "contextless" (addr: rawptr, data: []rawptr
 	}
 	}
 }
 }
 
 
+/*
+Returns the current asan shadow memory mapping.
+
+When asan is not enabled this procedure returns a zero initialised value.
+*/
+@(no_sanitize_address)
 address_get_shadow_mapping :: proc "contextless" () -> Address_Shadow_Mapping {
 address_get_shadow_mapping :: proc "contextless" () -> Address_Shadow_Mapping {
 	when ASAN_ENABLED {
 	when ASAN_ENABLED {
 		result: Address_Shadow_Mapping
 		result: Address_Shadow_Mapping
@@ -256,12 +523,26 @@ address_get_shadow_mapping :: proc "contextless" () -> Address_Shadow_Mapping {
 	}
 	}
 }
 }
 
 
+/*
+Prints asan statistics to `stderr`
+
+When asan is not enabled this procedure does nothing.
+*/
+@(no_sanitize_address)
 address_print_accumulated_stats :: proc "contextless" () {
 address_print_accumulated_stats :: proc "contextless" () {
 	when ASAN_ENABLED {
 	when ASAN_ENABLED {
 		__asan_print_accumulated_stats()
 		__asan_print_accumulated_stats()
 	}
 	}
 }
 }
 
 
+/*
+Returns the address of the current fake stack used by asan.
+
+This pointer can be then used for `address_is_in_fake_stack`.
+
+When asan is not enabled this procedure returns `nil`.
+*/
+@(no_sanitize_address)
 address_get_current_fake_stack :: proc "contextless" () -> rawptr {
 address_get_current_fake_stack :: proc "contextless" () -> rawptr {
 	when ASAN_ENABLED {
 	when ASAN_ENABLED {
 		return __asan_get_current_fake_stack()
 		return __asan_get_current_fake_stack()
@@ -270,6 +551,12 @@ address_get_current_fake_stack :: proc "contextless" () -> rawptr {
 	}
 	}
 }
 }
 
 
+/*
+Returns if an address belongs to a given fake stack and if so the region of the fake frame.
+
+When asan is not enabled this procedure returns zero initialised values.
+*/
+@(no_sanitize_address)
 address_is_in_fake_stack :: proc "contextless" (fake_stack: rawptr, addr: rawptr) -> ([]byte, bool) {
 address_is_in_fake_stack :: proc "contextless" (fake_stack: rawptr, addr: rawptr) -> ([]byte, bool) {
 	when ASAN_ENABLED {
 	when ASAN_ENABLED {
 		begin: rawptr
 		begin: rawptr
@@ -283,12 +570,27 @@ address_is_in_fake_stack :: proc "contextless" (fake_stack: rawptr, addr: rawptr
 	}
 	}
 }
 }
 
 
+/*
+Performs shadow memory cleanup for the current thread before a procedure with no return is called
+i.e. a procedure such as `panic` and `os.exit`.
+
+When asan is not enabled this procedure does nothing.
+*/
+@(no_sanitize_address)
 address_handle_no_return :: proc "contextless" () {
 address_handle_no_return :: proc "contextless" () {
 	when ASAN_ENABLED {
 	when ASAN_ENABLED {
 		__asan_handle_no_return()
 		__asan_handle_no_return()
 	}
 	}
 }
 }
 
 
+/*
+Updates the allocation stack trace for the given address.
+
+Returns `true` if successful, otherwise it returns `false`.
+
+When asan is not enabled this procedure returns `false`.
+*/
+@(no_sanitize_address)
 address_update_allocation_context :: proc "contextless" (addr: rawptr) -> bool {
 address_update_allocation_context :: proc "contextless" (addr: rawptr) -> bool {
 	when ASAN_ENABLED {
 	when ASAN_ENABLED {
 		return __asan_update_allocation_context(addr) != 0
 		return __asan_update_allocation_context(addr) != 0

+ 38 - 0
base/sanitizer/doc.odin

@@ -0,0 +1,38 @@
+/*
+The `sanitizer` package implements various procedures for interacting with sanitizers
+from user code.
+
+An odin project can be linked with various sanitizers to help identify various different
+bugs. These sanitizers are:
+
+## Address
+
+Enabled with `-sanitize:address` when building an odin project.
+
+The address sanitizer (asan) is a runtime memory error detector used to help find common memory
+related bugs. Typically asan interacts with libc but Odin code can be marked up to interact
+with the asan runtime to extend the memory error detection outside of libc using this package.
+For more information about asan see: https://clang.llvm.org/docs/AddressSanitizer.html
+
+Procedures can be made exempt from asan when marked up with @(no_sanitize_address)
+
+## Memory
+
+Enabled with `-sanitize:memory` when building an odin project.
+
+The memory sanitizer is another runtime memory error detector with the sole purpose to catch the
+use of uninitialized memory. This is not a very common bug in Odin as by default everything is
+set to zero when initialised (ZII).
+For more information about the memory sanitizer see: https://clang.llvm.org/docs/MemorySanitizer.html
+
+## Thread
+
+Enabled with `-sanitize:thread` when building an odin project.
+
+The thread sanitizer is a runtime data race detector. It can be used to detect if multiple threads
+are concurrently writing and accessing a memory location without proper syncronisation.
+For more information about the thread sanitizer see: https://clang.llvm.org/docs/ThreadSanitizer.html
+
+*/
+package sanitizer
+

+ 19 - 25
build.bat

@@ -19,16 +19,27 @@ if "%VSCMD_ARG_TGT_ARCH%" neq "x64" (
 	)
 	)
 )
 )
 
 
+where /Q git.exe || goto skip_git_hash
+if not exist .git\ goto skip_git_hash
+for /f "tokens=1,2" %%i IN ('git show "--pretty=%%cd %%h" "--date=format:%%Y-%%m-%%d" --no-patch --no-notes HEAD') do (
+	set CURR_DATE_TIME=%%i
+	set GIT_SHA=%%j
+)
+if %ERRORLEVEL% equ 0 (
+	goto have_git_hash_and_date
+)
+:skip_git_hash
 pushd misc
 pushd misc
 cl /nologo get-date.c
 cl /nologo get-date.c
-popd
-
-for /f %%i in ('misc\get-date') do (
+for /f %%i in ('get-date') do (
 	set CURR_DATE_TIME=%%i
 	set CURR_DATE_TIME=%%i
+	rem Don't set GIT_SHA
 )
 )
+popd
+:have_git_hash_and_date
 set curr_year=%CURR_DATE_TIME:~0,4%
 set curr_year=%CURR_DATE_TIME:~0,4%
-set curr_month=%CURR_DATE_TIME:~4,2%
-set curr_day=%CURR_DATE_TIME:~6,2%
+set curr_month=%CURR_DATE_TIME:~5,2%
+set curr_day=%CURR_DATE_TIME:~8,2%
 
 
 :: Make sure this is a decent name and not generic
 :: Make sure this is a decent name and not generic
 set exe_name=odin.exe
 set exe_name=odin.exe
@@ -61,31 +72,14 @@ if %release_mode% equ 0 (
 set V4=0
 set V4=0
 set odin_version_full="%V1%.%V2%.%V3%.%V4%"
 set odin_version_full="%V1%.%V2%.%V3%.%V4%"
 set odin_version_raw="dev-%V1%-%V2%"
 set odin_version_raw="dev-%V1%-%V2%"
-
 set compiler_flags= -nologo -Oi -TP -fp:precise -Gm- -MP -FC -EHsc- -GR- -GF
 set compiler_flags= -nologo -Oi -TP -fp:precise -Gm- -MP -FC -EHsc- -GR- -GF
 rem Parse source code as utf-8 even on shift-jis and other codepages
 rem Parse source code as utf-8 even on shift-jis and other codepages
 rem See https://learn.microsoft.com/en-us/cpp/build/reference/utf-8-set-source-and-executable-character-sets-to-utf-8?view=msvc-170
 rem See https://learn.microsoft.com/en-us/cpp/build/reference/utf-8-set-source-and-executable-character-sets-to-utf-8?view=msvc-170
 set compiler_flags= %compiler_flags% /utf-8
 set compiler_flags= %compiler_flags% /utf-8
-set compiler_defines= -DODIN_VERSION_RAW=\"%odin_version_raw%\"
+set compiler_defines= -DODIN_VERSION_RAW=\"%odin_version_raw%\" -DGIT_SHA=\"%GIT_SHA%\"
 
 
 rem fileversion is defined as {Major,Minor,Build,Private: u16} so a bit limited
 rem fileversion is defined as {Major,Minor,Build,Private: u16} so a bit limited
-set rc_flags=-nologo ^
--DV1=%V1% -DV2=%V2% -DV3=%V3% -DV4=%V4% ^
--DVF=%odin_version_full% -DNIGHTLY=%nightly%
-
-where /Q git.exe || goto skip_git_hash
-if not exist .git\ goto skip_git_hash
-for /f "tokens=1,2" %%i IN ('git show "--pretty=%%cd %%h" "--date=format:%%Y-%%m" --no-patch --no-notes HEAD') do (
-	set odin_version_raw=dev-%%i
-	set GIT_SHA=%%j
-)
-if %ERRORLEVEL% equ 0 (
-	set compiler_defines=%compiler_defines% -DGIT_SHA=\"%GIT_SHA%\"
-	set rc_flags=%rc_flags% -DGIT_SHA=%GIT_SHA% -DVP=%odin_version_raw%:%GIT_SHA%
-) else (
-	set rc_flags=%rc_flags% -DVP=%odin_version_raw%
-)
-:skip_git_hash
+set rc_flags="-DGIT_SHA=%GIT_SHA% -DVP=dev-%V1%-%V2%:%GIT_SHA% nologo -DV1=%V1% -DV2=%V2% -DV3=%V3% -DV4=%V4% -DVF=%odin_version_full% -DNIGHTLY=%nightly%"
 
 
 if %nightly% equ 1 set compiler_defines=%compiler_defines% -DNIGHTLY
 if %nightly% equ 1 set compiler_defines=%compiler_defines% -DNIGHTLY
 
 
@@ -153,4 +147,4 @@ if %release_mode% EQU 0 echo: & echo Debug compiler built. Note: run "build.bat
 
 
 del *.obj > NUL 2> NUL
 del *.obj > NUL 2> NUL
 
 
-:end_of_build
+:end_of_build

+ 4 - 1
build_odin.sh

@@ -6,7 +6,6 @@ set -eu
 : ${LDFLAGS=}
 : ${LDFLAGS=}
 : ${LLVM_CONFIG=}
 : ${LLVM_CONFIG=}
 
 
-CPPFLAGS="$CPPFLAGS -DODIN_VERSION_RAW=\"dev-$(date +"%Y-%m")\""
 CXXFLAGS="$CXXFLAGS -std=c++14"
 CXXFLAGS="$CXXFLAGS -std=c++14"
 DISABLED_WARNINGS="-Wno-switch -Wno-macro-redefined -Wno-unused-value"
 DISABLED_WARNINGS="-Wno-switch -Wno-macro-redefined -Wno-unused-value"
 LDFLAGS="$LDFLAGS -pthread -lm"
 LDFLAGS="$LDFLAGS -pthread -lm"
@@ -15,8 +14,12 @@ OS_NAME="$(uname -s)"
 
 
 if [ -d ".git" ] && [ -n "$(command -v git)" ]; then
 if [ -d ".git" ] && [ -n "$(command -v git)" ]; then
 	GIT_SHA=$(git show --pretty='%h' --no-patch --no-notes HEAD)
 	GIT_SHA=$(git show --pretty='%h' --no-patch --no-notes HEAD)
+	GIT_DATE=$(git show "--pretty=%cd" "--date=format:%Y-%m" --no-patch --no-notes HEAD)
 	CPPFLAGS="$CPPFLAGS -DGIT_SHA=\"$GIT_SHA\""
 	CPPFLAGS="$CPPFLAGS -DGIT_SHA=\"$GIT_SHA\""
+else
+	GIT_DATE=$(date +"%Y-%m")
 fi
 fi
+CPPFLAGS="$CPPFLAGS -DODIN_VERSION_RAW=\"dev-$GIT_DATE\""
 
 
 error() {
 error() {
 	printf "ERROR: %s\n" "$1"
 	printf "ERROR: %s\n" "$1"

+ 1 - 1
core/bufio/reader.odin

@@ -257,7 +257,7 @@ reader_read_rune :: proc(b: ^Reader) -> (r: rune, size: int, err: io.Error) {
 	for b.r+utf8.UTF_MAX > b.w &&
 	for b.r+utf8.UTF_MAX > b.w &&
 	    !utf8.full_rune(b.buf[b.r:b.w]) &&
 	    !utf8.full_rune(b.buf[b.r:b.w]) &&
 	    b.err == nil &&
 	    b.err == nil &&
-	    b.w-b.w < len(b.buf) {
+	    b.w-b.r < len(b.buf) {
 		_reader_read_new_chunk(b) or_return
 		_reader_read_new_chunk(b) or_return
 	}
 	}
 
 

+ 2 - 2
core/bytes/bytes.odin

@@ -350,7 +350,7 @@ index_byte :: proc "contextless" (s: []byte, c: byte) -> (index: int) #no_bounds
 	}
 	}
 
 
 	c_vec: simd.u8x16 = c
 	c_vec: simd.u8x16 = c
-	when !simd.IS_EMULATED {
+	when simd.HAS_HARDWARE_SIMD {
 		// Note: While this is something that could also logically take
 		// Note: While this is something that could also logically take
 		// advantage of AVX512, the various downclocking and power
 		// advantage of AVX512, the various downclocking and power
 		// consumption related woes make premature to have a dedicated
 		// consumption related woes make premature to have a dedicated
@@ -485,7 +485,7 @@ last_index_byte :: proc "contextless" (s: []byte, c: byte) -> int #no_bounds_che
 	}
 	}
 
 
 	c_vec: simd.u8x16 = c
 	c_vec: simd.u8x16 = c
-	when !simd.IS_EMULATED {
+	when simd.HAS_HARDWARE_SIMD {
 		// Note: While this is something that could also logically take
 		// Note: While this is something that could also logically take
 		// advantage of AVX512, the various downclocking and power
 		// advantage of AVX512, the various downclocking and power
 		// consumption related woes make premature to have a dedicated
 		// consumption related woes make premature to have a dedicated

+ 0 - 3
core/compress/common.odin

@@ -139,9 +139,6 @@ Context_Memory_Input :: struct #packed {
 }
 }
 when size_of(rawptr) == 8 {
 when size_of(rawptr) == 8 {
 	#assert(size_of(Context_Memory_Input) == 64)
 	#assert(size_of(Context_Memory_Input) == 64)
-} else {
-	// e.g. `-target:windows_i386`
-	#assert(size_of(Context_Memory_Input) == 52)
 }
 }
 
 
 Context_Stream_Input :: struct #packed {
 Context_Stream_Input :: struct #packed {

+ 1 - 1
core/container/lru/lru_cache.odin

@@ -129,7 +129,7 @@ remove :: proc(c: ^$C/Cache($Key, $Value), key: Key) -> bool {
 		return false
 		return false
 	}
 	}
 	_remove_node(c, e)
 	_remove_node(c, e)
-	free(node, c.node_allocator)
+	free(e, c.node_allocator)
 	c.count -= 1
 	c.count -= 1
 	return true
 	return true
 }
 }

+ 4 - 6
core/container/priority_queue/priority_queue.odin

@@ -133,12 +133,10 @@ pop_safe :: proc(pq: ^$Q/Priority_Queue($T), loc := #caller_location) -> (value:
 remove :: proc(pq: ^$Q/Priority_Queue($T), i: int) -> (value: T, ok: bool) {
 remove :: proc(pq: ^$Q/Priority_Queue($T), i: int) -> (value: T, ok: bool) {
 	n := builtin.len(pq.queue)
 	n := builtin.len(pq.queue)
 	if 0 <= i && i < n {
 	if 0 <= i && i < n {
-		if n != i {
-			pq.swap(pq.queue[:], i, n)
-			_shift_down(pq, i, n)
-			_shift_up(pq, i)
-		}
-		value, ok = builtin.pop_safe(&pq.queue)
+		pq.swap(pq.queue[:], i, n-1)
+		_shift_down(pq, i, n-1)
+		_shift_up(pq, i)
+		value, ok = builtin.pop(&pq.queue), true
 	}
 	}
 	return
 	return
 }
 }

+ 1 - 1
core/crypto/_aes/hw_intel/api.odin

@@ -6,7 +6,7 @@ import "core:sys/info"
 // is_supported returns true iff hardware accelerated AES
 // is_supported returns true iff hardware accelerated AES
 // is supported.
 // is supported.
 is_supported :: proc "contextless" () -> bool {
 is_supported :: proc "contextless" () -> bool {
-	features, ok := info.cpu_features.?
+	features, ok := info.cpu.features.?
 	if !ok {
 	if !ok {
 		return false
 		return false
 	}
 	}

+ 2 - 2
core/crypto/_chacha20/simd128/chacha20_simd128.odin

@@ -39,7 +39,7 @@ when ODIN_ARCH == .arm64 || ODIN_ARCH == .arm32 {
 
 
 // Some targets lack runtime feature detection, and will flat out refuse
 // Some targets lack runtime feature detection, and will flat out refuse
 // to load binaries that have unknown instructions.  This is distinct from
 // to load binaries that have unknown instructions.  This is distinct from
-// `simd.IS_EMULATED` as actually good designs support runtime feature
+// `simd.HAS_HARDWARE_SIMD` as actually good designs support runtime feature
 // detection and that constant establishes a baseline.
 // detection and that constant establishes a baseline.
 //
 //
 // See:
 // See:
@@ -227,7 +227,7 @@ is_performant :: proc "contextless" () -> bool {
 			req_features :: info.CPU_Features{.V}
 			req_features :: info.CPU_Features{.V}
 		}
 		}
 
 
-		features, ok := info.cpu_features.?
+		features, ok := info.cpu.features.?
 		if !ok {
 		if !ok {
 			return false
 			return false
 		}
 		}

+ 1 - 1
core/crypto/_chacha20/simd256/chacha20_simd256.odin

@@ -41,7 +41,7 @@ _VEC_TWO: simd.u64x4 : {2, 0, 2, 0}
 is_performant :: proc "contextless" () -> bool {
 is_performant :: proc "contextless" () -> bool {
 	req_features :: info.CPU_Features{.avx, .avx2}
 	req_features :: info.CPU_Features{.avx, .avx2}
 
 
-	features, ok := info.cpu_features.?
+	features, ok := info.cpu.features.?
 	if !ok {
 	if !ok {
 		return false
 		return false
 	}
 	}

+ 1 - 1
core/crypto/sha2/sha2_impl_hw_intel.odin

@@ -52,7 +52,7 @@ K_15 :: simd.u64x2{0xa4506ceb90befffa, 0xc67178f2bef9a3f7}
 // is_hardware_accelerated_256 returns true iff hardware accelerated
 // is_hardware_accelerated_256 returns true iff hardware accelerated
 // SHA-224/SHA-256 is supported.
 // SHA-224/SHA-256 is supported.
 is_hardware_accelerated_256 :: proc "contextless" () -> bool {
 is_hardware_accelerated_256 :: proc "contextless" () -> bool {
-	features, ok := info.cpu_features.?
+	features, ok := info.cpu.features.?
 	if !ok {
 	if !ok {
 		return false
 		return false
 	}
 	}

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

@@ -385,17 +385,17 @@ to_diagnostic_format_writer :: proc(w: io.Writer, val: Value, padding := 0) -> i
 	// which we want for the diagnostic format.
 	// which we want for the diagnostic format.
 	case f16:
 	case f16:
 		buf: [64]byte
 		buf: [64]byte
-		str := strconv.append_float(buf[:], f64(v), 'f', 2*size_of(f16), 8*size_of(f16))
+		str := strconv.write_float(buf[:], f64(v), 'f', 2*size_of(f16), 8*size_of(f16))
 		if str[0] == '+' && str != "+Inf" { str = str[1:] }
 		if str[0] == '+' && str != "+Inf" { str = str[1:] }
 		io.write_string(w, str) or_return
 		io.write_string(w, str) or_return
 	case f32:
 	case f32:
 		buf: [128]byte
 		buf: [128]byte
-		str := strconv.append_float(buf[:], f64(v), 'f', 2*size_of(f32), 8*size_of(f32))
+		str := strconv.write_float(buf[:], f64(v), 'f', 2*size_of(f32), 8*size_of(f32))
 		if str[0] == '+' && str != "+Inf" { str = str[1:] }
 		if str[0] == '+' && str != "+Inf" { str = str[1:] }
 		io.write_string(w, str) or_return
 		io.write_string(w, str) or_return
 	case f64:
 	case f64:
 		buf: [256]byte
 		buf: [256]byte
-		str := strconv.append_float(buf[:], f64(v), 'f', 2*size_of(f64), 8*size_of(f64))
+		str := strconv.write_float(buf[:], f64(v), 'f', 2*size_of(f64), 8*size_of(f64))
 		if str[0] == '+' && str != "+Inf" { str = str[1:] }
 		if str[0] == '+' && str != "+Inf" { str = str[1:] }
 		io.write_string(w, str) or_return
 		io.write_string(w, str) or_return
 
 

+ 36 - 0
core/encoding/cbor/marshal.odin

@@ -612,6 +612,42 @@ _marshal_into_encoder :: proc(e: Encoder, v: any, ti: ^runtime.Type_Info) -> (er
 		case:
 		case:
 			panic("unknown bit_size size")
 			panic("unknown bit_size size")
 		}
 		}
+	case runtime.Type_Info_Matrix:
+		count := info.column_count * info.elem_stride
+		err_conv(_encode_u64(e, u64(count), .Array)) or_return
+
+		if impl, ok := _tag_implementations_type[info.elem.id]; ok {
+			for i in 0..<count {
+				data := uintptr(v.data) + uintptr(i*info.elem_size)
+				impl->marshal(e, any{rawptr(data), info.elem.id}) or_return
+			}
+			return
+		}
+
+		elem_ti := runtime.type_info_core(type_info_of(info.elem.id))
+		for i in 0..<count {
+			data := uintptr(v.data) + uintptr(i*info.elem_size)
+			_marshal_into_encoder(e, any{rawptr(data), info.elem.id}, elem_ti) or_return
+		}
+		return
+
+	case runtime.Type_Info_Simd_Vector:
+		err_conv(_encode_u64(e, u64(info.count), .Array)) or_return
+
+		if impl, ok := _tag_implementations_type[info.elem.id]; ok {
+			for i in 0..<info.count {
+				data := uintptr(v.data) + uintptr(i*info.elem_size)
+				impl->marshal(e, any{rawptr(data), info.elem.id}) or_return
+			}
+			return
+		}
+
+		elem_ti := runtime.type_info_core(type_info_of(info.elem.id))
+		for i in 0..<info.count {
+			data := uintptr(v.data) + uintptr(i*info.elem_size)
+			_marshal_into_encoder(e, any{rawptr(data), info.elem.id}, elem_ti) or_return
+		}
+		return
 	}
 	}
 
 
 	return _unsupported(v.id, nil)
 	return _unsupported(v.id, nil)

+ 32 - 1
core/encoding/cbor/unmarshal.odin

@@ -29,6 +29,7 @@ an input.
 unmarshal :: proc {
 unmarshal :: proc {
 	unmarshal_from_reader,
 	unmarshal_from_reader,
 	unmarshal_from_string,
 	unmarshal_from_string,
+	unmarshal_from_bytes,
 }
 }
 
 
 unmarshal_from_reader :: proc(r: io.Reader, ptr: ^$T, flags := Decoder_Flags{}, allocator := context.allocator, temp_allocator := context.temp_allocator, loc := #caller_location) -> (err: Unmarshal_Error) {
 unmarshal_from_reader :: proc(r: io.Reader, ptr: ^$T, flags := Decoder_Flags{}, allocator := context.allocator, temp_allocator := context.temp_allocator, loc := #caller_location) -> (err: Unmarshal_Error) {
@@ -51,6 +52,11 @@ unmarshal_from_string :: proc(s: string, ptr: ^$T, flags := Decoder_Flags{}, all
 	return
 	return
 }
 }
 
 
+// Unmarshals from a slice of bytes, see docs on the proc group `Unmarshal` for more info.
+unmarshal_from_bytes :: proc(bytes: []byte, ptr: ^$T, flags := Decoder_Flags{}, allocator := context.allocator, temp_allocator := context.temp_allocator, loc := #caller_location) -> (err: Unmarshal_Error) {
+	return unmarshal_from_string(string(bytes), ptr, flags, allocator, temp_allocator, loc)
+}
+
 unmarshal_from_decoder :: proc(d: Decoder, ptr: ^$T, allocator := context.allocator, temp_allocator := context.temp_allocator, loc := #caller_location) -> (err: Unmarshal_Error) {
 unmarshal_from_decoder :: proc(d: Decoder, ptr: ^$T, allocator := context.allocator, temp_allocator := context.temp_allocator, loc := #caller_location) -> (err: Unmarshal_Error) {
 	d := d
 	d := d
 
 
@@ -487,7 +493,7 @@ _unmarshal_array :: proc(d: Decoder, v: any, ti: ^reflect.Type_Info, hdr: Header
 		data := mem.alloc_bytes_non_zeroed(t.elem.size * scap, t.elem.align, allocator=allocator, loc=loc) or_return
 		data := mem.alloc_bytes_non_zeroed(t.elem.size * scap, t.elem.align, allocator=allocator, loc=loc) or_return
 		defer if err != nil { mem.free_bytes(data, allocator=allocator, loc=loc) }
 		defer if err != nil { mem.free_bytes(data, allocator=allocator, loc=loc) }
 
 
-		da := mem.Raw_Dynamic_Array{raw_data(data), 0, length, context.allocator }
+		da := mem.Raw_Dynamic_Array{raw_data(data), 0, scap, context.allocator }
 
 
 		assign_array(d, &da, t.elem, length) or_return
 		assign_array(d, &da, t.elem, length) or_return
 
 
@@ -585,6 +591,31 @@ _unmarshal_array :: proc(d: Decoder, v: any, ti: ^reflect.Type_Info, hdr: Header
 		if out_of_space { return _unsupported(v, hdr) }
 		if out_of_space { return _unsupported(v, hdr) }
 		return
 		return
 
 
+	case reflect.Type_Info_Matrix:
+		count := t.column_count * t.elem_stride
+		length, _ := err_conv(_decode_len_container(d, add)) or_return
+		if length > count {
+			return _unsupported(v, hdr)
+		}
+
+		da := mem.Raw_Dynamic_Array{rawptr(v.data), 0, length, allocator }
+
+		out_of_space := assign_array(d, &da, t.elem, length, growable=false) or_return
+		if out_of_space { return _unsupported(v, hdr) }
+		return
+
+	case reflect.Type_Info_Simd_Vector:
+		length, _ := err_conv(_decode_len_container(d, add)) or_return
+		if length > t.count {
+			return _unsupported(v, hdr)
+		}
+
+		da := mem.Raw_Dynamic_Array{rawptr(v.data), 0, length, allocator }
+
+		out_of_space := assign_array(d, &da, t.elem, length, growable=false) or_return
+		if out_of_space { return _unsupported(v, hdr) }
+		return
+
 	case: return _unsupported(v, hdr)
 	case: return _unsupported(v, hdr)
 	}
 	}
 }
 }

+ 0 - 2
core/encoding/csv/doc.odin

@@ -63,8 +63,6 @@ Example:
 	read_csv_from_string :: proc(filename: string) {
 	read_csv_from_string :: proc(filename: string) {
 		r: csv.Reader
 		r: csv.Reader
 		r.trim_leading_space  = true
 		r.trim_leading_space  = true
-		r.reuse_record        = true // Without it you have to delete(record)
-		r.reuse_record_buffer = true // Without it you have to each of the fields within it
 		defer csv.reader_destroy(&r)
 		defer csv.reader_destroy(&r)
 
 
 		csv_data, ok := os.read_entire_file(filename)
 		csv_data, ok := os.read_entire_file(filename)

+ 1 - 1
core/encoding/csv/reader.odin

@@ -130,7 +130,7 @@ reader_destroy :: proc(r: ^Reader) {
 	for record, row_idx in csv.iterator_next(&r) { ... }
 	for record, row_idx in csv.iterator_next(&r) { ... }
 
 
 	TIP: If you process the results within the loop and don't need to own the results,
 	TIP: If you process the results within the loop and don't need to own the results,
-	you can set the Reader's `reuse_record` and `reuse_record_reuse_record_buffer` to true;
+	you can set the Reader's `reuse_record` and `reuse_record_buffer` to true;
 	you won't need to delete the record or its fields.
 	you won't need to delete the record or its fields.
 */
 */
 iterator_next :: proc(r: ^Reader) -> (record: []string, idx: int, err: Error, more: bool) {
 iterator_next :: proc(r: ^Reader) -> (record: []string, idx: int, err: Error, more: bool) {

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

@@ -108,13 +108,13 @@ marshal_to_writer :: proc(w: io.Writer, v: any, opt: ^Marshal_Options) -> (err:
 		if opt.write_uint_as_hex && (opt.spec == .JSON5 || opt.spec == .MJSON) {
 		if opt.write_uint_as_hex && (opt.spec == .JSON5 || opt.spec == .MJSON) {
 			switch i in a {
 			switch i in a {
 			case u8, u16, u32, u64, u128:
 			case u8, u16, u32, u64, u128:
-				s = strconv.append_bits_128(buf[:], u, 16, info.signed, 8*ti.size, "0123456789abcdef", { .Prefix })
+				s = strconv.write_bits_128(buf[:], u, 16, info.signed, 8*ti.size, "0123456789abcdef", { .Prefix })
 
 
 			case:
 			case:
-				s = strconv.append_bits_128(buf[:], u, 10, info.signed, 8*ti.size, "0123456789", nil)
+				s = strconv.write_bits_128(buf[:], u, 10, info.signed, 8*ti.size, "0123456789", nil)
 			}
 			}
 		} else {
 		} else {
-			s = strconv.append_bits_128(buf[:], u, 10, info.signed, 8*ti.size, "0123456789", nil)
+			s = strconv.write_bits_128(buf[:], u, 10, info.signed, 8*ti.size, "0123456789", nil)
 		}
 		}
 
 
 		io.write_string(w, s) or_return
 		io.write_string(w, s) or_return
@@ -286,7 +286,7 @@ marshal_to_writer :: proc(w: io.Writer, v: any, opt: ^Marshal_Options) -> (err:
 						case runtime.Type_Info_Integer:
 						case runtime.Type_Info_Integer:
 							buf: [40]byte
 							buf: [40]byte
 							u := cast_any_int_to_u128(ka)
 							u := cast_any_int_to_u128(ka)
-							name = strconv.append_bits_128(buf[:], u, 10, info.signed, 8*kti.size, "0123456789", nil)
+							name = strconv.write_bits_128(buf[:], u, 10, info.signed, 8*kti.size, "0123456789", nil)
 							
 							
 							opt_write_key(w, opt, name) or_return
 							opt_write_key(w, opt, name) or_return
 						case: return .Unsupported_Type
 						case: return .Unsupported_Type

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

@@ -101,7 +101,7 @@ get_token :: proc(t: ^Tokenizer) -> (token: Token, err: Error) {
 		}
 		}
 	}
 	}
 
 
-	scan_espace :: proc(t: ^Tokenizer) -> bool {
+	scan_escape :: proc(t: ^Tokenizer) -> bool {
 		switch t.r {
 		switch t.r {
 		case '"', '\'', '\\', '/', 'b', 'n', 'r', 't', 'f':
 		case '"', '\'', '\\', '/', 'b', 'n', 'r', 't', 'f':
 			next_rune(t)
 			next_rune(t)
@@ -310,7 +310,7 @@ get_token :: proc(t: ^Tokenizer) -> (token: Token, err: Error) {
 				break
 				break
 			}
 			}
 			if r == '\\' {
 			if r == '\\' {
-				scan_espace(t)
+				scan_escape(t)
 			}
 			}
 		}
 		}
 
 

+ 14 - 8
core/encoding/json/unmarshal.odin

@@ -406,6 +406,9 @@ unmarshal_expect_token :: proc(p: ^Parser, kind: Token_Kind, loc := #caller_loca
 	return prev
 	return prev
 }
 }
 
 
+// Struct tags can include not only the name of the JSON key, but also a tag such as `omitempty`.
+// Example: `json:"key_name,omitempty"`
+// This returns the first field as `json_name`, and the rest are returned as `extra`.
 @(private)
 @(private)
 json_name_from_tag_value :: proc(value: string) -> (json_name, extra: string) {
 json_name_from_tag_value :: proc(value: string) -> (json_name, extra: string) {
 	json_name = value
 	json_name = value
@@ -441,12 +444,6 @@ unmarshal_object :: proc(p: ^Parser, v: any, end_token: Token_Kind) -> (err: Unm
 			defer delete(key, p.allocator)
 			defer delete(key, p.allocator)
 			
 			
 			unmarshal_expect_token(p, .Colon)						
 			unmarshal_expect_token(p, .Colon)						
-			
-			field_test :: #force_inline proc "contextless" (field_used: [^]byte, offset: uintptr) -> bool {
-				prev_set := field_used[offset/8] & byte(offset&7) != 0
-				field_used[offset/8] |= byte(offset&7)
-				return prev_set
-			}
 
 
 			field_used_bytes := (reflect.size_of_typeid(ti.id)+7)/8
 			field_used_bytes := (reflect.size_of_typeid(ti.id)+7)/8
 			field_used := intrinsics.alloca(field_used_bytes + 1, 1) // + 1 to not overflow on size_of 0 types.
 			field_used := intrinsics.alloca(field_used_bytes + 1, 1) // + 1 to not overflow on size_of 0 types.
@@ -465,7 +462,9 @@ unmarshal_object :: proc(p: ^Parser, v: any, end_token: Token_Kind) -> (err: Unm
 			
 			
 			if use_field_idx < 0 {
 			if use_field_idx < 0 {
 				for field, field_idx in fields {
 				for field, field_idx in fields {
-					if key == field.name {
+					tag_value := reflect.struct_tag_get(field.tag, "json")
+					json_name, _ := json_name_from_tag_value(tag_value)
+					if json_name == "" && key == field.name {
 						use_field_idx = field_idx
 						use_field_idx = field_idx
 						break
 						break
 					}
 					}
@@ -486,7 +485,9 @@ unmarshal_object :: proc(p: ^Parser, v: any, end_token: Token_Kind) -> (err: Unm
 						}
 						}
 					}
 					}
 
 
-					if field.name == key || (field.tag != "" && reflect.struct_tag_get(field.tag, "json") == key) {
+					tag_value := reflect.struct_tag_get(field.tag, "json")
+					json_name, _ := json_name_from_tag_value(tag_value)
+					if (json_name == "" && field.name == key) || json_name == key {
 						offset = field.offset
 						offset = field.offset
 						type = field.type
 						type = field.type
 						found = true
 						found = true
@@ -508,6 +509,11 @@ unmarshal_object :: proc(p: ^Parser, v: any, end_token: Token_Kind) -> (err: Unm
 			}
 			}
 
 
 			if field_found {
 			if field_found {
+				field_test :: #force_inline proc "contextless" (field_used: [^]byte, offset: uintptr) -> bool {
+					prev_set := field_used[offset/8] & byte(offset&7) != 0
+					field_used[offset/8] |= byte(offset&7)
+					return prev_set
+				}
 				if field_test(field_used, offset) {
 				if field_test(field_used, offset) {
 					return .Multiple_Use_Field
 					return .Multiple_Use_Field
 				}
 				}

+ 4 - 4
core/fmt/fmt.odin

@@ -1122,7 +1122,7 @@ _fmt_int :: proc(fi: ^Info, u: u64, base: int, is_signed: bool, bit_size: int, d
 	flags: strconv.Int_Flags
 	flags: strconv.Int_Flags
 	if fi.hash && !fi.zero && start == 0 { flags += {.Prefix} }
 	if fi.hash && !fi.zero && start == 0 { flags += {.Prefix} }
 	if fi.plus                           { flags += {.Plus}   }
 	if fi.plus                           { flags += {.Plus}   }
-	s := strconv.append_bits(buf[start:], u, base, is_signed, bit_size, digits, flags)
+	s := strconv.write_bits(buf[start:], u, base, is_signed, bit_size, digits, flags)
 	prev_zero := fi.zero
 	prev_zero := fi.zero
 	defer fi.zero = prev_zero
 	defer fi.zero = prev_zero
 	fi.zero = false
 	fi.zero = false
@@ -1207,7 +1207,7 @@ _fmt_int_128 :: proc(fi: ^Info, u: u128, base: int, is_signed: bool, bit_size: i
 	flags: strconv.Int_Flags
 	flags: strconv.Int_Flags
 	if fi.hash && !fi.zero && start == 0 { flags += {.Prefix} }
 	if fi.hash && !fi.zero && start == 0 { flags += {.Prefix} }
 	if fi.plus                           { flags += {.Plus}   }
 	if fi.plus                           { flags += {.Plus}   }
-	s := strconv.append_bits_128(buf[start:], u, base, is_signed, bit_size, digits, flags)
+	s := strconv.write_bits_128(buf[start:], u, base, is_signed, bit_size, digits, flags)
 
 
 	if fi.hash && fi.zero && fi.indent == 0 {
 	if fi.hash && fi.zero && fi.indent == 0 {
 		c: byte = 0
 		c: byte = 0
@@ -1272,7 +1272,7 @@ _fmt_memory :: proc(fi: ^Info, u: u64, is_signed: bool, bit_size: int, units: st
 	}
 	}
 
 
 	buf: [256]byte
 	buf: [256]byte
-	str := strconv.append_float(buf[:], amt, 'f', prec, 64)
+	str := strconv.write_float(buf[:], amt, 'f', prec, 64)
 
 
 	// Add the unit at the end.
 	// Add the unit at the end.
 	copy(buf[len(str):], units[off:off+unit_len])
 	copy(buf[len(str):], units[off:off+unit_len])
@@ -1424,7 +1424,7 @@ _fmt_float_as :: proc(fi: ^Info, v: f64, bit_size: int, verb: rune, float_fmt: b
 	buf: [386]byte
 	buf: [386]byte
 
 
 	// Can return "NaN", "+Inf", "-Inf", "+<value>", "-<value>".
 	// Can return "NaN", "+Inf", "-Inf", "+<value>", "-<value>".
-	str := strconv.append_float(buf[:], v, float_fmt, prec, bit_size)
+	str := strconv.write_float(buf[:], v, float_fmt, prec, bit_size)
 
 
 	if !fi.plus {
 	if !fi.plus {
 		// Strip sign from "+<value>" but not "+Inf".
 		// Strip sign from "+<value>" but not "+Inf".

+ 1 - 82
core/hash/crc32.odin

@@ -317,85 +317,4 @@ crc32_table := [8][256]u32{
 		0xff6b144a, 0x33c114d4, 0xbd4e1337, 0x71e413a9, 0x7b211ab0, 0xb78b1a2e, 0x39041dcd, 0xf5ae1d53,
 		0xff6b144a, 0x33c114d4, 0xbd4e1337, 0x71e413a9, 0x7b211ab0, 0xb78b1a2e, 0x39041dcd, 0xf5ae1d53,
 		0x2c8e0fff, 0xe0240f61, 0x6eab0882, 0xa201081c, 0xa8c40105, 0x646e019b, 0xeae10678, 0x264b06e6,
 		0x2c8e0fff, 0xe0240f61, 0x6eab0882, 0xa201081c, 0xa8c40105, 0x646e019b, 0xeae10678, 0x264b06e6,
 	},
 	},
-}
-
-
-
-/*
-@(optimization_mode="speed")
-crc32 :: proc "contextless" (data: []byte, seed := u32(0)) -> u32 {
-	result := ~u32(seed);
-	 #no_bounds_check for b in data {
-		result = result>>8 ~ _crc32_table[(result ~ u32(b)) & 0xff];
-	}
-	return ~result;
-}
-
-
-@private _crc32_table := [256]u32{
-	0x00000000, 0x77073096, 0xee0e612c, 0x990951ba,
-	0x076dc419, 0x706af48f, 0xe963a535, 0x9e6495a3,
-	0x0edb8832, 0x79dcb8a4, 0xe0d5e91e, 0x97d2d988,
-	0x09b64c2b, 0x7eb17cbd, 0xe7b82d07, 0x90bf1d91,
-	0x1db71064, 0x6ab020f2, 0xf3b97148, 0x84be41de,
-	0x1adad47d, 0x6ddde4eb, 0xf4d4b551, 0x83d385c7,
-	0x136c9856, 0x646ba8c0, 0xfd62f97a, 0x8a65c9ec,
-	0x14015c4f, 0x63066cd9, 0xfa0f3d63, 0x8d080df5,
-	0x3b6e20c8, 0x4c69105e, 0xd56041e4, 0xa2677172,
-	0x3c03e4d1, 0x4b04d447, 0xd20d85fd, 0xa50ab56b,
-	0x35b5a8fa, 0x42b2986c, 0xdbbbc9d6, 0xacbcf940,
-	0x32d86ce3, 0x45df5c75, 0xdcd60dcf, 0xabd13d59,
-	0x26d930ac, 0x51de003a, 0xc8d75180, 0xbfd06116,
-	0x21b4f4b5, 0x56b3c423, 0xcfba9599, 0xb8bda50f,
-	0x2802b89e, 0x5f058808, 0xc60cd9b2, 0xb10be924,
-	0x2f6f7c87, 0x58684c11, 0xc1611dab, 0xb6662d3d,
-	0x76dc4190, 0x01db7106, 0x98d220bc, 0xefd5102a,
-	0x71b18589, 0x06b6b51f, 0x9fbfe4a5, 0xe8b8d433,
-	0x7807c9a2, 0x0f00f934, 0x9609a88e, 0xe10e9818,
-	0x7f6a0dbb, 0x086d3d2d, 0x91646c97, 0xe6635c01,
-	0x6b6b51f4, 0x1c6c6162, 0x856530d8, 0xf262004e,
-	0x6c0695ed, 0x1b01a57b, 0x8208f4c1, 0xf50fc457,
-	0x65b0d9c6, 0x12b7e950, 0x8bbeb8ea, 0xfcb9887c,
-	0x62dd1ddf, 0x15da2d49, 0x8cd37cf3, 0xfbd44c65,
-	0x4db26158, 0x3ab551ce, 0xa3bc0074, 0xd4bb30e2,
-	0x4adfa541, 0x3dd895d7, 0xa4d1c46d, 0xd3d6f4fb,
-	0x4369e96a, 0x346ed9fc, 0xad678846, 0xda60b8d0,
-	0x44042d73, 0x33031de5, 0xaa0a4c5f, 0xdd0d7cc9,
-	0x5005713c, 0x270241aa, 0xbe0b1010, 0xc90c2086,
-	0x5768b525, 0x206f85b3, 0xb966d409, 0xce61e49f,
-	0x5edef90e, 0x29d9c998, 0xb0d09822, 0xc7d7a8b4,
-	0x59b33d17, 0x2eb40d81, 0xb7bd5c3b, 0xc0ba6cad,
-	0xedb88320, 0x9abfb3b6, 0x03b6e20c, 0x74b1d29a,
-	0xead54739, 0x9dd277af, 0x04db2615, 0x73dc1683,
-	0xe3630b12, 0x94643b84, 0x0d6d6a3e, 0x7a6a5aa8,
-	0xe40ecf0b, 0x9309ff9d, 0x0a00ae27, 0x7d079eb1,
-	0xf00f9344, 0x8708a3d2, 0x1e01f268, 0x6906c2fe,
-	0xf762575d, 0x806567cb, 0x196c3671, 0x6e6b06e7,
-	0xfed41b76, 0x89d32be0, 0x10da7a5a, 0x67dd4acc,
-	0xf9b9df6f, 0x8ebeeff9, 0x17b7be43, 0x60b08ed5,
-	0xd6d6a3e8, 0xa1d1937e, 0x38d8c2c4, 0x4fdff252,
-	0xd1bb67f1, 0xa6bc5767, 0x3fb506dd, 0x48b2364b,
-	0xd80d2bda, 0xaf0a1b4c, 0x36034af6, 0x41047a60,
-	0xdf60efc3, 0xa867df55, 0x316e8eef, 0x4669be79,
-	0xcb61b38c, 0xbc66831a, 0x256fd2a0, 0x5268e236,
-	0xcc0c7795, 0xbb0b4703, 0x220216b9, 0x5505262f,
-	0xc5ba3bbe, 0xb2bd0b28, 0x2bb45a92, 0x5cb36a04,
-	0xc2d7ffa7, 0xb5d0cf31, 0x2cd99e8b, 0x5bdeae1d,
-	0x9b64c2b0, 0xec63f226, 0x756aa39c, 0x026d930a,
-	0x9c0906a9, 0xeb0e363f, 0x72076785, 0x05005713,
-	0x95bf4a82, 0xe2b87a14, 0x7bb12bae, 0x0cb61b38,
-	0x92d28e9b, 0xe5d5be0d, 0x7cdcefb7, 0x0bdbdf21,
-	0x86d3d2d4, 0xf1d4e242, 0x68ddb3f8, 0x1fda836e,
-	0x81be16cd, 0xf6b9265b, 0x6fb077e1, 0x18b74777,
-	0x88085ae6, 0xff0f6a70, 0x66063bca, 0x11010b5c,
-	0x8f659eff, 0xf862ae69, 0x616bffd3, 0x166ccf45,
-	0xa00ae278, 0xd70dd2ee, 0x4e048354, 0x3903b3c2,
-	0xa7672661, 0xd06016f7, 0x4969474d, 0x3e6e77db,
-	0xaed16a4a, 0xd9d65adc, 0x40df0b66, 0x37d83bf0,
-	0xa9bcae53, 0xdebb9ec5, 0x47b2cf7f, 0x30b5ffe9,
-	0xbdbdf21c, 0xcabac28a, 0x53b39330, 0x24b4a3a6,
-	0xbad03605, 0xcdd70693, 0x54de5729, 0x23d967bf,
-	0xb3667a2e, 0xc4614ab8, 0x5d681b02, 0x2a6f2b94,
-	0xb40bbe37, 0xc30c8ea1, 0x5a05df1b, 0x2d02ef8d,
-};
-*/
+}

+ 0 - 3
core/image/png/png.odin

@@ -1212,7 +1212,6 @@ Filter_Params :: struct #packed {
 
 
 depth_scale_table :: []u8{0, 0xff, 0x55, 0, 0x11, 0,0,0, 0x01}
 depth_scale_table :: []u8{0, 0xff, 0x55, 0, 0x11, 0,0,0, 0x01}
 
 
-// @(optimization_mode="speed")
 defilter_8 :: proc(params: ^Filter_Params) -> (ok: bool) {
 defilter_8 :: proc(params: ^Filter_Params) -> (ok: bool) {
 
 
 	using params
 	using params
@@ -1273,7 +1272,6 @@ defilter_8 :: proc(params: ^Filter_Params) -> (ok: bool) {
 	return
 	return
 }
 }
 
 
-// @(optimization_mode="speed")
 defilter_less_than_8 :: proc(params: ^Filter_Params) -> bool #no_bounds_check {
 defilter_less_than_8 :: proc(params: ^Filter_Params) -> bool #no_bounds_check {
 
 
 	using params
 	using params
@@ -1436,7 +1434,6 @@ defilter_less_than_8 :: proc(params: ^Filter_Params) -> bool #no_bounds_check {
 	return true
 	return true
 }
 }
 
 
-// @(optimization_mode="speed")
 defilter_16 :: proc(params: ^Filter_Params) -> bool {
 defilter_16 :: proc(params: ^Filter_Params) -> bool {
 	using params
 	using params
 
 

+ 8 - 8
core/io/util.odin

@@ -22,12 +22,12 @@ write_ptr_at :: proc(w: Writer_At, p: rawptr, byte_size: int, offset: i64, n_wri
 
 
 write_u64 :: proc(w: Writer, i: u64, base: int = 10, n_written: ^int = nil) -> (n: int, err: Error) {
 write_u64 :: proc(w: Writer, i: u64, base: int = 10, n_written: ^int = nil) -> (n: int, err: Error) {
 	buf: [32]byte
 	buf: [32]byte
-	s := strconv.append_bits(buf[:], i, base, false, 64, strconv.digits, nil)
+	s := strconv.write_bits(buf[:], i, base, false, 64, strconv.digits, nil)
 	return write_string(w, s, n_written)
 	return write_string(w, s, n_written)
 }
 }
 write_i64 :: proc(w: Writer, i: i64, base: int = 10, n_written: ^int = nil) -> (n: int, err: Error) {
 write_i64 :: proc(w: Writer, i: i64, base: int = 10, n_written: ^int = nil) -> (n: int, err: Error) {
 	buf: [32]byte
 	buf: [32]byte
-	s := strconv.append_bits(buf[:], u64(i), base, true, 64, strconv.digits, nil)
+	s := strconv.write_bits(buf[:], u64(i), base, true, 64, strconv.digits, nil)
 	return write_string(w, s, n_written)
 	return write_string(w, s, n_written)
 }
 }
 
 
@@ -40,18 +40,18 @@ write_int :: proc(w: Writer, i: int, base: int = 10, n_written: ^int = nil) -> (
 
 
 write_u128 :: proc(w: Writer, i: u128, base: int = 10, n_written: ^int = nil) -> (n: int, err: Error) {
 write_u128 :: proc(w: Writer, i: u128, base: int = 10, n_written: ^int = nil) -> (n: int, err: Error) {
 	buf: [39]byte
 	buf: [39]byte
-	s := strconv.append_bits_128(buf[:], i, base, false, 128, strconv.digits, nil)
+	s := strconv.write_bits_128(buf[:], i, base, false, 128, strconv.digits, nil)
 	return write_string(w, s, n_written)
 	return write_string(w, s, n_written)
 }
 }
 write_i128 :: proc(w: Writer, i: i128, base: int = 10, n_written: ^int = nil) -> (n: int, err: Error) {
 write_i128 :: proc(w: Writer, i: i128, base: int = 10, n_written: ^int = nil) -> (n: int, err: Error) {
 	buf: [40]byte
 	buf: [40]byte
-	s := strconv.append_bits_128(buf[:], u128(i), base, true, 128, strconv.digits, nil)
+	s := strconv.write_bits_128(buf[:], u128(i), base, true, 128, strconv.digits, nil)
 	return write_string(w, s, n_written)
 	return write_string(w, s, n_written)
 }
 }
 write_f16 :: proc(w: Writer, val: f16, n_written: ^int = nil) -> (n: int, err: Error) {
 write_f16 :: proc(w: Writer, val: f16, n_written: ^int = nil) -> (n: int, err: Error) {
 	buf: [386]byte
 	buf: [386]byte
 
 
-	str := strconv.append_float(buf[1:], f64(val), 'f', 2*size_of(val), 8*size_of(val))
+	str := strconv.write_float(buf[1:], f64(val), 'f', 2*size_of(val), 8*size_of(val))
 	s := buf[:len(str)+1]
 	s := buf[:len(str)+1]
 	if s[1] == '+' || s[1] == '-' {
 	if s[1] == '+' || s[1] == '-' {
 		s = s[1:]
 		s = s[1:]
@@ -67,7 +67,7 @@ write_f16 :: proc(w: Writer, val: f16, n_written: ^int = nil) -> (n: int, err: E
 write_f32 :: proc(w: Writer, val: f32, n_written: ^int = nil) -> (n: int, err: Error) {
 write_f32 :: proc(w: Writer, val: f32, n_written: ^int = nil) -> (n: int, err: Error) {
 	buf: [386]byte
 	buf: [386]byte
 
 
-	str := strconv.append_float(buf[1:], f64(val), 'f', 2*size_of(val), 8*size_of(val))
+	str := strconv.write_float(buf[1:], f64(val), 'f', 2*size_of(val), 8*size_of(val))
 	s := buf[:len(str)+1]
 	s := buf[:len(str)+1]
 	if s[1] == '+' || s[1] == '-' {
 	if s[1] == '+' || s[1] == '-' {
 		s = s[1:]
 		s = s[1:]
@@ -83,7 +83,7 @@ write_f32 :: proc(w: Writer, val: f32, n_written: ^int = nil) -> (n: int, err: E
 write_f64 :: proc(w: Writer, val: f64, n_written: ^int = nil) -> (n: int, err: Error) {
 write_f64 :: proc(w: Writer, val: f64, n_written: ^int = nil) -> (n: int, err: Error) {
 	buf: [386]byte
 	buf: [386]byte
 
 
-	str := strconv.append_float(buf[1:], val, 'f', 2*size_of(val), 8*size_of(val))
+	str := strconv.write_float(buf[1:], val, 'f', 2*size_of(val), 8*size_of(val))
 	s := buf[:len(str)+1]
 	s := buf[:len(str)+1]
 	if s[1] == '+' || s[1] == '-' {
 	if s[1] == '+' || s[1] == '-' {
 		s = s[1:]
 		s = s[1:]
@@ -130,7 +130,7 @@ write_encoded_rune :: proc(w: Writer, r: rune, write_quote := true, n_written: ^
 			write_string(w, `\x`, &n) or_return
 			write_string(w, `\x`, &n) or_return
 			
 			
 			buf: [2]byte
 			buf: [2]byte
-			s := strconv.append_bits(buf[:], u64(r), 16, true, 64, strconv.digits, nil)
+			s := strconv.write_bits(buf[:], u64(r), 16, true, 64, strconv.digits, nil)
 			switch len(s) {
 			switch len(s) {
 			case 0: 
 			case 0: 
 				write_string(w, "00", &n) or_return
 				write_string(w, "00", &n) or_return

+ 53 - 11
core/log/file_console_logger.odin

@@ -2,10 +2,12 @@
 #+build !orca
 #+build !orca
 package log
 package log
 
 
-import "core:encoding/ansi"
+import "base:runtime"
 import "core:fmt"
 import "core:fmt"
 import "core:strings"
 import "core:strings"
 import "core:os"
 import "core:os"
+import "core:terminal"
+import "core:terminal/ansi"
 import "core:time"
 import "core:time"
 
 
 Level_Headers := [?]string{
 Level_Headers := [?]string{
@@ -37,11 +39,36 @@ File_Console_Logger_Data :: struct {
 	ident: string,
 	ident: string,
 }
 }
 
 
+@(private) global_subtract_stdout_options: Options
+@(private) global_subtract_stderr_options: Options
+
+@(init, private)
+init_standard_stream_status :: proc() {
+	// NOTE(Feoramund): While it is technically possible for these streams to
+	// be redirected during the runtime of the program, the cost of checking on
+	// every single log message is not worth it to support such an
+	// uncommonly-used feature.
+	if terminal.color_enabled {
+		// This is done this way because it's possible that only one of these
+		// streams could be redirected to a file.
+		if !terminal.is_terminal(os.stdout) {
+			global_subtract_stdout_options = {.Terminal_Color}
+		}
+		if !terminal.is_terminal(os.stderr) {
+			global_subtract_stderr_options = {.Terminal_Color}
+		}
+	} else {
+		// Override any terminal coloring.
+		global_subtract_stdout_options = {.Terminal_Color}
+		global_subtract_stderr_options = {.Terminal_Color}
+	}
+}
+
 create_file_logger :: proc(h: os.Handle, lowest := Level.Debug, opt := Default_File_Logger_Opts, ident := "", allocator := context.allocator) -> Logger {
 create_file_logger :: proc(h: os.Handle, lowest := Level.Debug, opt := Default_File_Logger_Opts, ident := "", allocator := context.allocator) -> Logger {
 	data := new(File_Console_Logger_Data, allocator)
 	data := new(File_Console_Logger_Data, allocator)
 	data.file_handle = h
 	data.file_handle = h
 	data.ident = ident
 	data.ident = ident
-	return Logger{file_console_logger_proc, data, lowest, opt}
+	return Logger{file_logger_proc, data, lowest, opt}
 }
 }
 
 
 destroy_file_logger :: proc(log: Logger, allocator := context.allocator) {
 destroy_file_logger :: proc(log: Logger, allocator := context.allocator) {
@@ -56,19 +83,15 @@ create_console_logger :: proc(lowest := Level.Debug, opt := Default_Console_Logg
 	data := new(File_Console_Logger_Data, allocator)
 	data := new(File_Console_Logger_Data, allocator)
 	data.file_handle = os.INVALID_HANDLE
 	data.file_handle = os.INVALID_HANDLE
 	data.ident = ident
 	data.ident = ident
-	return Logger{file_console_logger_proc, data, lowest, opt}
+	return Logger{console_logger_proc, data, lowest, opt}
 }
 }
 
 
 destroy_console_logger :: proc(log: Logger, allocator := context.allocator) {
 destroy_console_logger :: proc(log: Logger, allocator := context.allocator) {
 	free(log.data, allocator)
 	free(log.data, allocator)
 }
 }
 
 
-file_console_logger_proc :: proc(logger_data: rawptr, level: Level, text: string, options: Options, location := #caller_location) {
-	data := cast(^File_Console_Logger_Data)logger_data
-	h: os.Handle = os.stdout if level <= Level.Error else os.stderr
-	if data.file_handle != os.INVALID_HANDLE {
-		h = data.file_handle
-	}
+@(private)
+_file_console_logger_proc :: proc(h: os.Handle, ident: string, level: Level, text: string, options: Options, location: runtime.Source_Code_Location) {
 	backing: [1024]byte //NOTE(Hoej): 1024 might be too much for a header backing, unless somebody has really long paths.
 	backing: [1024]byte //NOTE(Hoej): 1024 might be too much for a header backing, unless somebody has really long paths.
 	buf := strings.builder_from_bytes(backing[:])
 	buf := strings.builder_from_bytes(backing[:])
 
 
@@ -86,13 +109,32 @@ file_console_logger_proc :: proc(logger_data: rawptr, level: Level, text: string
 		fmt.sbprintf(&buf, "[{}] ", os.current_thread_id())
 		fmt.sbprintf(&buf, "[{}] ", os.current_thread_id())
 	}
 	}
 
 
-	if data.ident != "" {
-		fmt.sbprintf(&buf, "[%s] ", data.ident)
+	if ident != "" {
+		fmt.sbprintf(&buf, "[%s] ", ident)
 	}
 	}
 	//TODO(Hoej): When we have better atomics and such, make this thread-safe
 	//TODO(Hoej): When we have better atomics and such, make this thread-safe
 	fmt.fprintf(h, "%s%s\n", strings.to_string(buf), text)
 	fmt.fprintf(h, "%s%s\n", strings.to_string(buf), text)
 }
 }
 
 
+file_logger_proc :: proc(logger_data: rawptr, level: Level, text: string, options: Options, location := #caller_location) {
+	data := cast(^File_Console_Logger_Data)logger_data
+	_file_console_logger_proc(data.file_handle, data.ident, level, text, options, location)
+}
+
+console_logger_proc :: proc(logger_data: rawptr, level: Level, text: string, options: Options, location := #caller_location) {
+	options := options
+	data := cast(^File_Console_Logger_Data)logger_data
+	h: os.Handle = ---
+	if level < Level.Error {
+		h = os.stdout
+		options -= global_subtract_stdout_options
+	} else {
+		h = os.stderr
+		options -= global_subtract_stderr_options
+	}
+	_file_console_logger_proc(h, data.ident, level, text, options, location)
+}
+
 do_level_header :: proc(opts: Options, str: ^strings.Builder, level: Level) {
 do_level_header :: proc(opts: Options, str: ^strings.Builder, level: Level) {
 
 
 	RESET     :: ansi.CSI + ansi.RESET           + ansi.SGR
 	RESET     :: ansi.CSI + ansi.RESET           + ansi.SGR

+ 1 - 1
core/math/big/private.odin

@@ -1370,8 +1370,8 @@ _private_int_div_recursive :: proc(quotient, remainder, a, b: ^Int, allocator :=
 
 
 /*
 /*
 	Slower bit-bang division... also smaller.
 	Slower bit-bang division... also smaller.
+	Prefer `_int_div_school` for speed.
 */
 */
-@(deprecated="Use `_int_div_school`, it's 3.5x faster.")
 _private_int_div_small :: proc(quotient, remainder, numerator, denominator: ^Int) -> (err: Error) {
 _private_int_div_small :: proc(quotient, remainder, numerator, denominator: ^Int) -> (err: Error) {
 
 
 	ta, tb, tq, q := &Int{}, &Int{}, &Int{}, &Int{}
 	ta, tb, tq, q := &Int{}, &Int{}, &Int{}, &Int{}

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

@@ -280,7 +280,7 @@ int_atoi :: proc(res: ^Int, input: string, radix := i8(10), allocator := context
 		}
 		}
 
 
 		pos := ch - '+'
 		pos := ch - '+'
-		if RADIX_TABLE_REVERSE_SIZE <= pos {
+		if RADIX_TABLE_REVERSE_SIZE <= u32(pos) {
 			break
 			break
 		}
 		}
 		y := RADIX_TABLE_REVERSE[pos]
 		y := RADIX_TABLE_REVERSE[pos]

+ 10 - 5
core/math/fixed/fixed.odin

@@ -103,7 +103,7 @@ round :: proc(x: $T/Fixed($Backing, $Fraction_Width)) -> Backing {
 }
 }
 
 
 @(require_results)
 @(require_results)
-append :: proc(dst: []byte, x: $T/Fixed($Backing, $Fraction_Width)) -> string {
+write :: proc(dst: []byte, x: $T/Fixed($Backing, $Fraction_Width)) -> string {
 	Integer_Width :: 8*size_of(Backing) - Fraction_Width
 	Integer_Width :: 8*size_of(Backing) - Fraction_Width
 
 
 	x := x
 	x := x
@@ -124,16 +124,16 @@ append :: proc(dst: []byte, x: $T/Fixed($Backing, $Fraction_Width)) -> string {
 
 
 		when size_of(Backing) < 16 {
 		when size_of(Backing) < 16 {
 			T :: u64
 			T :: u64
-			append_uint :: strconv.append_uint
+			write_uint :: strconv.write_uint
 		} else {
 		} else {
 			T :: u128
 			T :: u128
-			append_uint :: strconv.append_u128
+			write_uint :: strconv.write_u128
 		}
 		}
 
 
 		integer := T(x.i) >> Fraction_Width
 		integer := T(x.i) >> Fraction_Width
 		fraction := T(x.i) & (1<<Fraction_Width - 1)
 		fraction := T(x.i) & (1<<Fraction_Width - 1)
 
 
-		s := append_uint(buf[i:], integer, 10)
+		s := write_uint(buf[i:], integer, 10)
 		i += len(s)
 		i += len(s)
 		if fraction != 0 {
 		if fraction != 0 {
 			buf[i] = '.'
 			buf[i] = '.'
@@ -155,7 +155,7 @@ append :: proc(dst: []byte, x: $T/Fixed($Backing, $Fraction_Width)) -> string {
 @(require_results)
 @(require_results)
 to_string :: proc(x: $T/Fixed($Backing, $Fraction_Width), allocator := context.allocator) -> string {
 to_string :: proc(x: $T/Fixed($Backing, $Fraction_Width), allocator := context.allocator) -> string {
 	buf: [48]byte
 	buf: [48]byte
-	s := append(buf[:], x)
+	s := write(buf[:], x)
 	str := make([]byte, len(s), allocator)
 	str := make([]byte, len(s), allocator)
 	copy(str, s)
 	copy(str, s)
 	return string(str)
 	return string(str)
@@ -294,3 +294,8 @@ _power_of_two_table := [129]string{
 	"85070591730234615865843651857942052864",
 	"85070591730234615865843651857942052864",
 	"170141183460469231731687303715884105728",
 	"170141183460469231731687303715884105728",
 }
 }
+
+@(deprecated="Use write instead")
+append :: proc(dst: []byte, x: $T/Fixed($Backing, $Fraction_Width)) -> string {
+	return write(dst, x)
+}

+ 1 - 1
core/math/rand/rand.odin

@@ -350,7 +350,7 @@ Example:
 Possible Output:
 Possible Output:
 
 
 	6
 	6
-	500
+	13
 
 
 */
 */
 @(require_results)
 @(require_results)

+ 25 - 5
core/mem/mem.odin

@@ -315,18 +315,38 @@ check_zero_ptr :: proc(ptr: rawptr, len: int) -> bool {
 Offset a given pointer by a given amount.
 Offset a given pointer by a given amount.
 
 
 This procedure offsets the pointer `ptr` to an object of type `T`, by the amount
 This procedure offsets the pointer `ptr` to an object of type `T`, by the amount
-of bytes specified by `offset*size_of(T)`, and returns the pointer `ptr`.
+of bytes specified by `offset * size_of(T)`, and returns the pointer `ptr`.
 
 
 **Note**: Prefer to use multipointer types, if possible.
 **Note**: Prefer to use multipointer types, if possible.
 */
 */
 ptr_offset :: intrinsics.ptr_offset
 ptr_offset :: intrinsics.ptr_offset
 
 
 /*
 /*
-Offset a given pointer by a given amount backwards.
+Subtract two pointers of the same type, and return the number of `T` between them.
 
 
-This procedure offsets the pointer `ptr` to an object of type `T`, by the amount
-of bytes specified by `offset*size_of(T)` in the negative direction, and
-returns the pointer `ptr`.
+This procedure subtracts pointer `b` from pointer `a`, both of type `^T`,
+and returns an integer count of the `T` between them.
+
+**Inputs**
+- `a`: A pointer to a type T
+- `b`: A pointer to a type T
+
+**Returns**
+- `b` - `a` in items of T as an `int`.
+
+Example:
+
+	import "core:mem"
+	import "core:fmt"
+
+	ptr_sub_example :: proc() {
+		arr: [2]int
+		fmt.println(mem.ptr_sub(&arr[1], &arr[0]))
+	}
+
+Output:
+
+	1
 */
 */
 ptr_sub :: intrinsics.ptr_sub
 ptr_sub :: intrinsics.ptr_sub
 
 

+ 31 - 19
core/mem/rollback_stack_allocator.odin

@@ -1,6 +1,7 @@
 package mem
 package mem
 
 
 import "base:runtime"
 import "base:runtime"
+import "base:sanitizer"
 
 
 /*
 /*
 Rollback stack default block size.
 Rollback stack default block size.
@@ -47,14 +48,14 @@ Rollback_Stack :: struct {
 	block_allocator: Allocator,
 	block_allocator: Allocator,
 }
 }
 
 
-@(private="file", require_results)
+@(private="file", require_results, no_sanitize_address)
 rb_ptr_in_bounds :: proc(block: ^Rollback_Stack_Block, ptr: rawptr) -> bool {
 rb_ptr_in_bounds :: proc(block: ^Rollback_Stack_Block, ptr: rawptr) -> bool {
 	start := raw_data(block.buffer)
 	start := raw_data(block.buffer)
 	end   := start[block.offset:]
 	end   := start[block.offset:]
 	return start < ptr && ptr <= end
 	return start < ptr && ptr <= end
 }
 }
 
 
-@(private="file", require_results)
+@(private="file", require_results, no_sanitize_address)
 rb_find_ptr :: proc(stack: ^Rollback_Stack, ptr: rawptr) -> (
 rb_find_ptr :: proc(stack: ^Rollback_Stack, ptr: rawptr) -> (
 	parent: ^Rollback_Stack_Block,
 	parent: ^Rollback_Stack_Block,
 	block:  ^Rollback_Stack_Block,
 	block:  ^Rollback_Stack_Block,
@@ -71,7 +72,7 @@ rb_find_ptr :: proc(stack: ^Rollback_Stack, ptr: rawptr) -> (
 	return nil, nil, nil, .Invalid_Pointer
 	return nil, nil, nil, .Invalid_Pointer
 }
 }
 
 
-@(private="file", require_results)
+@(private="file", require_results, no_sanitize_address)
 rb_find_last_alloc :: proc(stack: ^Rollback_Stack, ptr: rawptr) -> (
 rb_find_last_alloc :: proc(stack: ^Rollback_Stack, ptr: rawptr) -> (
 	block: ^Rollback_Stack_Block,
 	block: ^Rollback_Stack_Block,
 	header: ^Rollback_Stack_Header,
 	header: ^Rollback_Stack_Header,
@@ -86,9 +87,10 @@ rb_find_last_alloc :: proc(stack: ^Rollback_Stack, ptr: rawptr) -> (
 	return nil, nil, false
 	return nil, nil, false
 }
 }
 
 
-@(private="file")
+@(private="file", no_sanitize_address)
 rb_rollback_block :: proc(block: ^Rollback_Stack_Block, header: ^Rollback_Stack_Header) {
 rb_rollback_block :: proc(block: ^Rollback_Stack_Block, header: ^Rollback_Stack_Header) {
 	header := header
 	header := header
+
 	for block.offset > 0 && header.is_free {
 	for block.offset > 0 && header.is_free {
 		block.offset = header.prev_offset
 		block.offset = header.prev_offset
 		block.last_alloc = raw_data(block.buffer)[header.prev_ptr:]
 		block.last_alloc = raw_data(block.buffer)[header.prev_ptr:]
@@ -99,9 +101,10 @@ rb_rollback_block :: proc(block: ^Rollback_Stack_Block, header: ^Rollback_Stack_
 /*
 /*
 Free memory to a rollback stack allocator.
 Free memory to a rollback stack allocator.
 */
 */
-@(private="file", require_results)
+@(private="file", require_results, no_sanitize_address)
 rb_free :: proc(stack: ^Rollback_Stack, ptr: rawptr) -> Allocator_Error {
 rb_free :: proc(stack: ^Rollback_Stack, ptr: rawptr) -> Allocator_Error {
 	parent, block, header := rb_find_ptr(stack, ptr) or_return
 	parent, block, header := rb_find_ptr(stack, ptr) or_return
+
 	if header.is_free {
 	if header.is_free {
 		return .Invalid_Pointer
 		return .Invalid_Pointer
 	}
 	}
@@ -120,7 +123,7 @@ rb_free :: proc(stack: ^Rollback_Stack, ptr: rawptr) -> Allocator_Error {
 /*
 /*
 Free all memory owned by the rollback stack allocator.
 Free all memory owned by the rollback stack allocator.
 */
 */
-@(private="file")
+@(private="file", no_sanitize_address)
 rb_free_all :: proc(stack: ^Rollback_Stack) {
 rb_free_all :: proc(stack: ^Rollback_Stack) {
 	for block := stack.head.next_block; block != nil; /**/ {
 	for block := stack.head.next_block; block != nil; /**/ {
 		next_block := block.next_block
 		next_block := block.next_block
@@ -131,12 +134,13 @@ rb_free_all :: proc(stack: ^Rollback_Stack) {
 	stack.head.next_block = nil
 	stack.head.next_block = nil
 	stack.head.last_alloc = nil
 	stack.head.last_alloc = nil
 	stack.head.offset = 0
 	stack.head.offset = 0
+	sanitizer.address_poison(stack.head.buffer)
 }
 }
 
 
 /*
 /*
 Allocate memory using the rollback stack allocator.
 Allocate memory using the rollback stack allocator.
 */
 */
-@(require_results)
+@(require_results, no_sanitize_address)
 rb_alloc :: proc(
 rb_alloc :: proc(
 	stack: ^Rollback_Stack,
 	stack: ^Rollback_Stack,
 	size: int,
 	size: int,
@@ -153,7 +157,7 @@ rb_alloc :: proc(
 /*
 /*
 Allocate memory using the rollback stack allocator.
 Allocate memory using the rollback stack allocator.
 */
 */
-@(require_results)
+@(require_results, no_sanitize_address)
 rb_alloc_bytes :: proc(
 rb_alloc_bytes :: proc(
 	stack: ^Rollback_Stack,
 	stack: ^Rollback_Stack,
 	size: int,
 	size: int,
@@ -170,7 +174,7 @@ rb_alloc_bytes :: proc(
 /*
 /*
 Allocate non-initialized memory using the rollback stack allocator.
 Allocate non-initialized memory using the rollback stack allocator.
 */
 */
-@(require_results)
+@(require_results, no_sanitize_address)
 rb_alloc_non_zeroed :: proc(
 rb_alloc_non_zeroed :: proc(
 	stack: ^Rollback_Stack,
 	stack: ^Rollback_Stack,
 	size: int,
 	size: int,
@@ -184,7 +188,7 @@ rb_alloc_non_zeroed :: proc(
 /*
 /*
 Allocate non-initialized memory using the rollback stack allocator.
 Allocate non-initialized memory using the rollback stack allocator.
 */
 */
-@(require_results)
+@(require_results, no_sanitize_address)
 rb_alloc_bytes_non_zeroed :: proc(
 rb_alloc_bytes_non_zeroed :: proc(
 	stack: ^Rollback_Stack,
 	stack: ^Rollback_Stack,
 	size: int,
 	size: int,
@@ -194,6 +198,7 @@ rb_alloc_bytes_non_zeroed :: proc(
 	assert(size >= 0, "Size must be positive or zero.", loc)
 	assert(size >= 0, "Size must be positive or zero.", loc)
 	assert(is_power_of_two(cast(uintptr)alignment), "Alignment must be a power of two.", loc)
 	assert(is_power_of_two(cast(uintptr)alignment), "Alignment must be a power of two.", loc)
 	parent: ^Rollback_Stack_Block
 	parent: ^Rollback_Stack_Block
+
 	for block := stack.head; /**/; block = block.next_block {
 	for block := stack.head; /**/; block = block.next_block {
 		when !ODIN_DISABLE_ASSERT {
 		when !ODIN_DISABLE_ASSERT {
 			allocated_new_block: bool
 			allocated_new_block: bool
@@ -235,7 +240,9 @@ rb_alloc_bytes_non_zeroed :: proc(
 			// Prevent any further allocations on it.
 			// Prevent any further allocations on it.
 			block.offset = cast(uintptr)len(block.buffer)
 			block.offset = cast(uintptr)len(block.buffer)
 		}
 		}
-		#no_bounds_check return ptr[:size], nil
+		res := ptr[:size]
+		sanitizer.address_unpoison(res)
+		return res, nil
 	}
 	}
 	return nil, .Out_Of_Memory
 	return nil, .Out_Of_Memory
 }
 }
@@ -243,7 +250,7 @@ rb_alloc_bytes_non_zeroed :: proc(
 /*
 /*
 Resize an allocation owned by rollback stack allocator.
 Resize an allocation owned by rollback stack allocator.
 */
 */
-@(require_results)
+@(require_results, no_sanitize_address)
 rb_resize :: proc(
 rb_resize :: proc(
 	stack: ^Rollback_Stack,
 	stack: ^Rollback_Stack,
 	old_ptr: rawptr,
 	old_ptr: rawptr,
@@ -266,7 +273,7 @@ rb_resize :: proc(
 /*
 /*
 Resize an allocation owned by rollback stack allocator.
 Resize an allocation owned by rollback stack allocator.
 */
 */
-@(require_results)
+@(require_results, no_sanitize_address)
 rb_resize_bytes :: proc(
 rb_resize_bytes :: proc(
 	stack: ^Rollback_Stack,
 	stack: ^Rollback_Stack,
 	old_memory: []byte,
 	old_memory: []byte,
@@ -289,7 +296,7 @@ rb_resize_bytes :: proc(
 Resize an allocation owned by rollback stack allocator without explicit
 Resize an allocation owned by rollback stack allocator without explicit
 zero-initialization.
 zero-initialization.
 */
 */
-@(require_results)
+@(require_results, no_sanitize_address)
 rb_resize_non_zeroed :: proc(
 rb_resize_non_zeroed :: proc(
 	stack: ^Rollback_Stack,
 	stack: ^Rollback_Stack,
 	old_ptr: rawptr,
 	old_ptr: rawptr,
@@ -306,7 +313,7 @@ rb_resize_non_zeroed :: proc(
 Resize an allocation owned by rollback stack allocator without explicit
 Resize an allocation owned by rollback stack allocator without explicit
 zero-initialization.
 zero-initialization.
 */
 */
-@(require_results)
+@(require_results, no_sanitize_address)
 rb_resize_bytes_non_zeroed :: proc(
 rb_resize_bytes_non_zeroed :: proc(
 	stack: ^Rollback_Stack,
 	stack: ^Rollback_Stack,
 	old_memory: []byte,
 	old_memory: []byte,
@@ -330,7 +337,9 @@ rb_resize_bytes_non_zeroed :: proc(
 				if len(block.buffer) <= stack.block_size {
 				if len(block.buffer) <= stack.block_size {
 					block.offset += cast(uintptr)size - cast(uintptr)old_size
 					block.offset += cast(uintptr)size - cast(uintptr)old_size
 				}
 				}
-				#no_bounds_check return (ptr)[:size], nil
+				res := (ptr)[:size]
+				sanitizer.address_unpoison(res)
+				#no_bounds_check return res, nil
 			}
 			}
 		}
 		}
 	}
 	}
@@ -340,7 +349,7 @@ rb_resize_bytes_non_zeroed :: proc(
 	return
 	return
 }
 }
 
 
-@(private="file", require_results)
+@(private="file", require_results, no_sanitize_address)
 rb_make_block :: proc(size: int, allocator: Allocator) -> (block: ^Rollback_Stack_Block, err: Allocator_Error) {
 rb_make_block :: proc(size: int, allocator: Allocator) -> (block: ^Rollback_Stack_Block, err: Allocator_Error) {
 	buffer := runtime.mem_alloc(size_of(Rollback_Stack_Block) + size, align_of(Rollback_Stack_Block), allocator) or_return
 	buffer := runtime.mem_alloc(size_of(Rollback_Stack_Block) + size, align_of(Rollback_Stack_Block), allocator) or_return
 	block = cast(^Rollback_Stack_Block)raw_data(buffer)
 	block = cast(^Rollback_Stack_Block)raw_data(buffer)
@@ -351,6 +360,7 @@ rb_make_block :: proc(size: int, allocator: Allocator) -> (block: ^Rollback_Stac
 /*
 /*
 Initialize the rollback stack allocator using a fixed backing buffer.
 Initialize the rollback stack allocator using a fixed backing buffer.
 */
 */
+@(no_sanitize_address)
 rollback_stack_init_buffered :: proc(stack: ^Rollback_Stack, buffer: []byte, location := #caller_location) {
 rollback_stack_init_buffered :: proc(stack: ^Rollback_Stack, buffer: []byte, location := #caller_location) {
 	MIN_SIZE :: size_of(Rollback_Stack_Block) + size_of(Rollback_Stack_Header) + size_of(rawptr)
 	MIN_SIZE :: size_of(Rollback_Stack_Block) + size_of(Rollback_Stack_Header) + size_of(rawptr)
 	assert(len(buffer) >= MIN_SIZE, "User-provided buffer to Rollback Stack Allocator is too small.", location)
 	assert(len(buffer) >= MIN_SIZE, "User-provided buffer to Rollback Stack Allocator is too small.", location)
@@ -365,6 +375,7 @@ rollback_stack_init_buffered :: proc(stack: ^Rollback_Stack, buffer: []byte, loc
 /*
 /*
 Initialize the rollback stack alocator using a backing block allocator.
 Initialize the rollback stack alocator using a backing block allocator.
 */
 */
+@(no_sanitize_address)
 rollback_stack_init_dynamic :: proc(
 rollback_stack_init_dynamic :: proc(
 	stack: ^Rollback_Stack,
 	stack: ^Rollback_Stack,
 	block_size : int = ROLLBACK_STACK_DEFAULT_BLOCK_SIZE,
 	block_size : int = ROLLBACK_STACK_DEFAULT_BLOCK_SIZE,
@@ -396,6 +407,7 @@ rollback_stack_init :: proc {
 /*
 /*
 Destroy a rollback stack.
 Destroy a rollback stack.
 */
 */
+@(no_sanitize_address)
 rollback_stack_destroy :: proc(stack: ^Rollback_Stack) {
 rollback_stack_destroy :: proc(stack: ^Rollback_Stack) {
 	if stack.block_allocator.procedure != nil {
 	if stack.block_allocator.procedure != nil {
 		rb_free_all(stack)
 		rb_free_all(stack)
@@ -435,7 +447,7 @@ from the last allocation backwards.
 Each allocation has an overhead of 8 bytes and any extra bytes to satisfy
 Each allocation has an overhead of 8 bytes and any extra bytes to satisfy
 the requested alignment.
 the requested alignment.
 */
 */
-@(require_results)
+@(require_results, no_sanitize_address)
 rollback_stack_allocator :: proc(stack: ^Rollback_Stack) -> Allocator {
 rollback_stack_allocator :: proc(stack: ^Rollback_Stack) -> Allocator {
 	return Allocator {
 	return Allocator {
 		data = stack,
 		data = stack,
@@ -443,7 +455,7 @@ rollback_stack_allocator :: proc(stack: ^Rollback_Stack) -> Allocator {
 	}
 	}
 }
 }
 
 
-@(require_results)
+@(require_results, no_sanitize_address)
 rollback_stack_allocator_proc :: proc(
 rollback_stack_allocator_proc :: proc(
 	allocator_data: rawptr,
 	allocator_data: rawptr,
 	mode: Allocator_Mode,
 	mode: Allocator_Mode,

+ 1 - 1
core/mem/tlsf/tlsf.odin

@@ -198,4 +198,4 @@ fls :: proc "contextless" (word: u32) -> (bit: i32) {
 fls_uint :: proc "contextless" (size: uint) -> (bit: i32) {
 fls_uint :: proc "contextless" (size: uint) -> (bit: i32) {
 	N :: (size_of(uint) * 8) - 1
 	N :: (size_of(uint) * 8) - 1
 	return i32(N - intrinsics.count_leading_zeros(size))
 	return i32(N - intrinsics.count_leading_zeros(size))
-}
+}

+ 51 - 43
core/mem/tlsf/tlsf_internal.odin

@@ -10,6 +10,7 @@
 package mem_tlsf
 package mem_tlsf
 
 
 import "base:intrinsics"
 import "base:intrinsics"
+import "base:sanitizer"
 import "base:runtime"
 import "base:runtime"
 
 
 // log2 of number of linear subdivisions of block sizes.
 // log2 of number of linear subdivisions of block sizes.
@@ -209,6 +210,8 @@ alloc_bytes_non_zeroed :: proc(control: ^Allocator, size: uint, align: uint) ->
 				return nil, .Out_Of_Memory
 				return nil, .Out_Of_Memory
 			}
 			}
 
 
+			sanitizer.address_poison(new_pool_buf)
+
 			// Allocate a new link in the `control.pool` tracking structure.
 			// Allocate a new link in the `control.pool` tracking structure.
 			new_pool := new_clone(Pool{
 			new_pool := new_clone(Pool{
 				data      = new_pool_buf,
 				data      = new_pool_buf,
@@ -254,7 +257,7 @@ alloc_bytes_non_zeroed :: proc(control: ^Allocator, size: uint, align: uint) ->
 	return block_prepare_used(control, block, adjust)
 	return block_prepare_used(control, block, adjust)
 }
 }
 
 
-@(private, require_results)
+@(private, require_results, no_sanitize_address)
 alloc_bytes :: proc(control: ^Allocator, size: uint, align: uint) -> (res: []byte, err: runtime.Allocator_Error) {
 alloc_bytes :: proc(control: ^Allocator, size: uint, align: uint) -> (res: []byte, err: runtime.Allocator_Error) {
 	res, err = alloc_bytes_non_zeroed(control, size, align)
 	res, err = alloc_bytes_non_zeroed(control, size, align)
 	if err == nil {
 	if err == nil {
@@ -273,6 +276,7 @@ free_with_size :: proc(control: ^Allocator, ptr: rawptr, size: uint) {
 
 
 	block := block_from_ptr(ptr)
 	block := block_from_ptr(ptr)
 	assert(!block_is_free(block), "block already marked as free") // double free
 	assert(!block_is_free(block), "block already marked as free") // double free
+	sanitizer.address_poison(ptr, block.size)
 	block_mark_as_free(block)
 	block_mark_as_free(block)
 	block = block_merge_prev(control, block)
 	block = block_merge_prev(control, block)
 	block = block_merge_next(control, block)
 	block = block_merge_next(control, block)
@@ -316,6 +320,7 @@ resize :: proc(control: ^Allocator, ptr: rawptr, old_size, new_size: uint, align
 
 
 	block_trim_used(control, block, adjust)
 	block_trim_used(control, block, adjust)
 	res = ([^]byte)(ptr)[:new_size]
 	res = ([^]byte)(ptr)[:new_size]
+	sanitizer.address_unpoison(res)
 
 
 	if min_size < new_size {
 	if min_size < new_size {
 		to_zero := ([^]byte)(ptr)[min_size:new_size]
 		to_zero := ([^]byte)(ptr)[min_size:new_size]
@@ -374,95 +379,96 @@ resize_non_zeroed :: proc(control: ^Allocator, ptr: rawptr, old_size, new_size:
 	NOTE: TLSF spec relies on ffs/fls returning a value in the range 0..31.
 	NOTE: TLSF spec relies on ffs/fls returning a value in the range 0..31.
 */
 */
 
 
-@(private, require_results)
+@(private, require_results, no_sanitize_address)
 block_size :: proc "contextless" (block: ^Block_Header) -> (size: uint) {
 block_size :: proc "contextless" (block: ^Block_Header) -> (size: uint) {
 	return block.size &~ (BLOCK_HEADER_FREE | BLOCK_HEADER_PREV_FREE)
 	return block.size &~ (BLOCK_HEADER_FREE | BLOCK_HEADER_PREV_FREE)
 }
 }
 
 
-@(private)
+@(private, no_sanitize_address)
 block_set_size :: proc "contextless" (block: ^Block_Header, size: uint) {
 block_set_size :: proc "contextless" (block: ^Block_Header, size: uint) {
 	old_size := block.size
 	old_size := block.size
 	block.size = size | (old_size & (BLOCK_HEADER_FREE | BLOCK_HEADER_PREV_FREE))
 	block.size = size | (old_size & (BLOCK_HEADER_FREE | BLOCK_HEADER_PREV_FREE))
 }
 }
 
 
-@(private, require_results)
+@(private, require_results, no_sanitize_address)
 block_is_last :: proc "contextless" (block: ^Block_Header) -> (is_last: bool) {
 block_is_last :: proc "contextless" (block: ^Block_Header) -> (is_last: bool) {
 	return block_size(block) == 0
 	return block_size(block) == 0
 }
 }
 
 
-@(private, require_results)
+@(private, require_results, no_sanitize_address)
 block_is_free :: proc "contextless" (block: ^Block_Header) -> (is_free: bool) {
 block_is_free :: proc "contextless" (block: ^Block_Header) -> (is_free: bool) {
 	return (block.size & BLOCK_HEADER_FREE) == BLOCK_HEADER_FREE
 	return (block.size & BLOCK_HEADER_FREE) == BLOCK_HEADER_FREE
 }
 }
 
 
-@(private)
+@(private, no_sanitize_address)
 block_set_free :: proc "contextless" (block: ^Block_Header) {
 block_set_free :: proc "contextless" (block: ^Block_Header) {
 	block.size |= BLOCK_HEADER_FREE
 	block.size |= BLOCK_HEADER_FREE
 }
 }
 
 
-@(private)
+@(private, no_sanitize_address)
 block_set_used :: proc "contextless" (block: ^Block_Header) {
 block_set_used :: proc "contextless" (block: ^Block_Header) {
 	block.size &~= BLOCK_HEADER_FREE
 	block.size &~= BLOCK_HEADER_FREE
 }
 }
 
 
-@(private, require_results)
+@(private, require_results, no_sanitize_address)
 block_is_prev_free :: proc "contextless" (block: ^Block_Header) -> (is_prev_free: bool) {
 block_is_prev_free :: proc "contextless" (block: ^Block_Header) -> (is_prev_free: bool) {
 	return (block.size & BLOCK_HEADER_PREV_FREE) == BLOCK_HEADER_PREV_FREE
 	return (block.size & BLOCK_HEADER_PREV_FREE) == BLOCK_HEADER_PREV_FREE
 }
 }
 
 
-@(private)
+@(private, no_sanitize_address)
 block_set_prev_free :: proc "contextless" (block: ^Block_Header) {
 block_set_prev_free :: proc "contextless" (block: ^Block_Header) {
 	block.size |= BLOCK_HEADER_PREV_FREE
 	block.size |= BLOCK_HEADER_PREV_FREE
 }
 }
 
 
-@(private)
+@(private, no_sanitize_address)
 block_set_prev_used :: proc "contextless" (block: ^Block_Header) {
 block_set_prev_used :: proc "contextless" (block: ^Block_Header) {
 	block.size &~= BLOCK_HEADER_PREV_FREE
 	block.size &~= BLOCK_HEADER_PREV_FREE
 }
 }
 
 
-@(private, require_results)
+@(private, require_results, no_sanitize_address)
 block_from_ptr :: proc(ptr: rawptr) -> (block_ptr: ^Block_Header) {
 block_from_ptr :: proc(ptr: rawptr) -> (block_ptr: ^Block_Header) {
 	return (^Block_Header)(uintptr(ptr) - BLOCK_START_OFFSET)
 	return (^Block_Header)(uintptr(ptr) - BLOCK_START_OFFSET)
 }
 }
 
 
-@(private, require_results)
+@(private, require_results, no_sanitize_address)
 block_to_ptr   :: proc(block: ^Block_Header) -> (ptr: rawptr) {
 block_to_ptr   :: proc(block: ^Block_Header) -> (ptr: rawptr) {
 	return rawptr(uintptr(block) + BLOCK_START_OFFSET)
 	return rawptr(uintptr(block) + BLOCK_START_OFFSET)
 }
 }
 
 
 // Return location of next block after block of given size.
 // Return location of next block after block of given size.
-@(private, require_results)
+@(private, require_results, no_sanitize_address)
 offset_to_block :: proc(ptr: rawptr, size: uint) -> (block: ^Block_Header) {
 offset_to_block :: proc(ptr: rawptr, size: uint) -> (block: ^Block_Header) {
 	return (^Block_Header)(uintptr(ptr) + uintptr(size))
 	return (^Block_Header)(uintptr(ptr) + uintptr(size))
 }
 }
 
 
-@(private, require_results)
+@(private, require_results, no_sanitize_address)
 offset_to_block_backwards :: proc(ptr: rawptr, size: uint) -> (block: ^Block_Header) {
 offset_to_block_backwards :: proc(ptr: rawptr, size: uint) -> (block: ^Block_Header) {
 	return (^Block_Header)(uintptr(ptr) - uintptr(size))
 	return (^Block_Header)(uintptr(ptr) - uintptr(size))
 }
 }
 
 
 // Return location of previous block.
 // Return location of previous block.
-@(private, require_results)
+@(private, require_results, no_sanitize_address)
 block_prev :: proc(block: ^Block_Header) -> (prev: ^Block_Header) {
 block_prev :: proc(block: ^Block_Header) -> (prev: ^Block_Header) {
 	assert(block_is_prev_free(block), "previous block must be free")
 	assert(block_is_prev_free(block), "previous block must be free")
+
 	return block.prev_phys_block
 	return block.prev_phys_block
 }
 }
 
 
 // Return location of next existing block.
 // Return location of next existing block.
-@(private, require_results)
+@(private, require_results, no_sanitize_address)
 block_next :: proc(block: ^Block_Header) -> (next: ^Block_Header) {
 block_next :: proc(block: ^Block_Header) -> (next: ^Block_Header) {
 	return offset_to_block(block_to_ptr(block), block_size(block) - BLOCK_HEADER_OVERHEAD)
 	return offset_to_block(block_to_ptr(block), block_size(block) - BLOCK_HEADER_OVERHEAD)
 }
 }
 
 
 // Link a new block with its physical neighbor, return the neighbor.
 // Link a new block with its physical neighbor, return the neighbor.
-@(private, require_results)
+@(private, require_results, no_sanitize_address)
 block_link_next :: proc(block: ^Block_Header) -> (next: ^Block_Header) {
 block_link_next :: proc(block: ^Block_Header) -> (next: ^Block_Header) {
 	next = block_next(block)
 	next = block_next(block)
 	next.prev_phys_block = block
 	next.prev_phys_block = block
 	return
 	return
 }
 }
 
 
-@(private)
+@(private, no_sanitize_address)
 block_mark_as_free :: proc(block: ^Block_Header) {
 block_mark_as_free :: proc(block: ^Block_Header) {
 	// Link the block to the next block, first.
 	// Link the block to the next block, first.
 	next := block_link_next(block)
 	next := block_link_next(block)
@@ -470,26 +476,26 @@ block_mark_as_free :: proc(block: ^Block_Header) {
 	block_set_free(block)
 	block_set_free(block)
 }
 }
 
 
-@(private)
+@(private, no_sanitize_address)
 block_mark_as_used :: proc(block: ^Block_Header) {
 block_mark_as_used :: proc(block: ^Block_Header) {
 	next := block_next(block)
 	next := block_next(block)
 	block_set_prev_used(next)
 	block_set_prev_used(next)
 	block_set_used(block)
 	block_set_used(block)
 }
 }
 
 
-@(private, require_results)
+@(private, require_results, no_sanitize_address)
 align_up :: proc(x, align: uint) -> (aligned: uint) {
 align_up :: proc(x, align: uint) -> (aligned: uint) {
 	assert(0 == (align & (align - 1)), "must align to a power of two")
 	assert(0 == (align & (align - 1)), "must align to a power of two")
 	return (x + (align - 1)) &~ (align - 1)
 	return (x + (align - 1)) &~ (align - 1)
 }
 }
 
 
-@(private, require_results)
+@(private, require_results, no_sanitize_address)
 align_down :: proc(x, align: uint) -> (aligned: uint) {
 align_down :: proc(x, align: uint) -> (aligned: uint) {
 	assert(0 == (align & (align - 1)), "must align to a power of two")
 	assert(0 == (align & (align - 1)), "must align to a power of two")
 	return x - (x & (align - 1))
 	return x - (x & (align - 1))
 }
 }
 
 
-@(private, require_results)
+@(private, require_results, no_sanitize_address)
 align_ptr :: proc(ptr: rawptr, align: uint) -> (aligned: rawptr) {
 align_ptr :: proc(ptr: rawptr, align: uint) -> (aligned: rawptr) {
 	assert(0 == (align & (align - 1)), "must align to a power of two")
 	assert(0 == (align & (align - 1)), "must align to a power of two")
 	align_mask := uintptr(align) - 1
 	align_mask := uintptr(align) - 1
@@ -499,7 +505,7 @@ align_ptr :: proc(ptr: rawptr, align: uint) -> (aligned: rawptr) {
 }
 }
 
 
 // Adjust an allocation size to be aligned to word size, and no smaller than internal minimum.
 // Adjust an allocation size to be aligned to word size, and no smaller than internal minimum.
-@(private, require_results)
+@(private, require_results, no_sanitize_address)
 adjust_request_size :: proc(size, align: uint) -> (adjusted: uint) {
 adjust_request_size :: proc(size, align: uint) -> (adjusted: uint) {
 	if size == 0 {
 	if size == 0 {
 		return 0
 		return 0
@@ -513,7 +519,7 @@ adjust_request_size :: proc(size, align: uint) -> (adjusted: uint) {
 }
 }
 
 
 // Adjust an allocation size to be aligned to word size, and no smaller than internal minimum.
 // Adjust an allocation size to be aligned to word size, and no smaller than internal minimum.
-@(private, require_results)
+@(private, require_results, no_sanitize_address)
 adjust_request_size_with_err :: proc(size, align: uint) -> (adjusted: uint, err: runtime.Allocator_Error) {
 adjust_request_size_with_err :: proc(size, align: uint) -> (adjusted: uint, err: runtime.Allocator_Error) {
 	if size == 0 {
 	if size == 0 {
 		return 0, nil
 		return 0, nil
@@ -531,7 +537,7 @@ adjust_request_size_with_err :: proc(size, align: uint) -> (adjusted: uint, err:
 // TLSF utility functions. In most cases these are direct translations of
 // TLSF utility functions. In most cases these are direct translations of
 // the documentation in the research paper.
 // the documentation in the research paper.
 
 
-@(optimization_mode="favor_size", private, require_results)
+@(optimization_mode="favor_size", private, require_results, no_sanitize_address)
 mapping_insert :: proc(size: uint) -> (fl, sl: i32) {
 mapping_insert :: proc(size: uint) -> (fl, sl: i32) {
 	if size < SMALL_BLOCK_SIZE {
 	if size < SMALL_BLOCK_SIZE {
 		// Store small blocks in first list.
 		// Store small blocks in first list.
@@ -544,7 +550,7 @@ mapping_insert :: proc(size: uint) -> (fl, sl: i32) {
 	return
 	return
 }
 }
 
 
-@(optimization_mode="favor_size", private, require_results)
+@(optimization_mode="favor_size", private, require_results, no_sanitize_address)
 mapping_round :: #force_inline proc(size: uint) -> (rounded: uint) {
 mapping_round :: #force_inline proc(size: uint) -> (rounded: uint) {
 	rounded = size
 	rounded = size
 	if size >= SMALL_BLOCK_SIZE {
 	if size >= SMALL_BLOCK_SIZE {
@@ -555,12 +561,12 @@ mapping_round :: #force_inline proc(size: uint) -> (rounded: uint) {
 }
 }
 
 
 // This version rounds up to the next block size (for allocations)
 // This version rounds up to the next block size (for allocations)
-@(optimization_mode="favor_size", private, require_results)
+@(optimization_mode="favor_size", private, require_results, no_sanitize_address)
 mapping_search :: proc(size: uint) -> (fl, sl: i32) {
 mapping_search :: proc(size: uint) -> (fl, sl: i32) {
 	return mapping_insert(mapping_round(size))
 	return mapping_insert(mapping_round(size))
 }
 }
 
 
-@(private, require_results)
+@(private, require_results, no_sanitize_address)
 search_suitable_block :: proc(control: ^Allocator, fli, sli: ^i32) -> (block: ^Block_Header) {
 search_suitable_block :: proc(control: ^Allocator, fli, sli: ^i32) -> (block: ^Block_Header) {
 	// First, search for a block in the list associated with the given fl/sl index.
 	// First, search for a block in the list associated with the given fl/sl index.
 	fl := fli^; sl := sli^
 	fl := fli^; sl := sli^
@@ -587,7 +593,7 @@ search_suitable_block :: proc(control: ^Allocator, fli, sli: ^i32) -> (block: ^B
 }
 }
 
 
 // Remove a free block from the free list.
 // Remove a free block from the free list.
-@(private)
+@(private, no_sanitize_address)
 remove_free_block :: proc(control: ^Allocator, block: ^Block_Header, fl: i32, sl: i32) {
 remove_free_block :: proc(control: ^Allocator, block: ^Block_Header, fl: i32, sl: i32) {
 	prev := block.prev_free
 	prev := block.prev_free
 	next := block.next_free
 	next := block.next_free
@@ -613,7 +619,7 @@ remove_free_block :: proc(control: ^Allocator, block: ^Block_Header, fl: i32, sl
 }
 }
 
 
 // Insert a free block into the free block list.
 // Insert a free block into the free block list.
-@(private)
+@(private, no_sanitize_address)
 insert_free_block :: proc(control: ^Allocator, block: ^Block_Header, fl: i32, sl: i32) {
 insert_free_block :: proc(control: ^Allocator, block: ^Block_Header, fl: i32, sl: i32) {
 	current := control.blocks[fl][sl]
 	current := control.blocks[fl][sl]
 	assert(current != nil, "free lists cannot have a nil entry")
 	assert(current != nil, "free lists cannot have a nil entry")
@@ -631,26 +637,26 @@ insert_free_block :: proc(control: ^Allocator, block: ^Block_Header, fl: i32, sl
 }
 }
 
 
 // Remove a given block from the free list.
 // Remove a given block from the free list.
-@(private)
+@(private, no_sanitize_address)
 block_remove :: proc(control: ^Allocator, block: ^Block_Header) {
 block_remove :: proc(control: ^Allocator, block: ^Block_Header) {
 	fl, sl := mapping_insert(block_size(block))
 	fl, sl := mapping_insert(block_size(block))
 	remove_free_block(control, block, fl, sl)
 	remove_free_block(control, block, fl, sl)
 }
 }
 
 
 // Insert a given block into the free list.
 // Insert a given block into the free list.
-@(private)
+@(private, no_sanitize_address)
 block_insert :: proc(control: ^Allocator, block: ^Block_Header) {
 block_insert :: proc(control: ^Allocator, block: ^Block_Header) {
 	fl, sl := mapping_insert(block_size(block))
 	fl, sl := mapping_insert(block_size(block))
 	insert_free_block(control, block, fl, sl)
 	insert_free_block(control, block, fl, sl)
 }
 }
 
 
-@(private, require_results)
+@(private, require_results, no_sanitize_address)
 block_can_split :: proc(block: ^Block_Header, size: uint) -> (can_split: bool) {
 block_can_split :: proc(block: ^Block_Header, size: uint) -> (can_split: bool) {
 	return block_size(block) >= size_of(Block_Header) + size
 	return block_size(block) >= size_of(Block_Header) + size
 }
 }
 
 
 // Split a block into two, the second of which is free.
 // Split a block into two, the second of which is free.
-@(private, require_results)
+@(private, require_results, no_sanitize_address)
 block_split :: proc(block: ^Block_Header, size: uint) -> (remaining: ^Block_Header) {
 block_split :: proc(block: ^Block_Header, size: uint) -> (remaining: ^Block_Header) {
 	// Calculate the amount of space left in the remaining block.
 	// Calculate the amount of space left in the remaining block.
 	remaining = offset_to_block(block_to_ptr(block), size - BLOCK_HEADER_OVERHEAD)
 	remaining = offset_to_block(block_to_ptr(block), size - BLOCK_HEADER_OVERHEAD)
@@ -671,9 +677,10 @@ block_split :: proc(block: ^Block_Header, size: uint) -> (remaining: ^Block_Head
 }
 }
 
 
 // Absorb a free block's storage into an adjacent previous free block.
 // Absorb a free block's storage into an adjacent previous free block.
-@(private, require_results)
+@(private, require_results, no_sanitize_address)
 block_absorb :: proc(prev: ^Block_Header, block: ^Block_Header) -> (absorbed: ^Block_Header) {
 block_absorb :: proc(prev: ^Block_Header, block: ^Block_Header) -> (absorbed: ^Block_Header) {
 	assert(!block_is_last(prev), "previous block can't be last")
 	assert(!block_is_last(prev), "previous block can't be last")
+
 	// Note: Leaves flags untouched.
 	// Note: Leaves flags untouched.
 	prev.size += block_size(block) + BLOCK_HEADER_OVERHEAD
 	prev.size += block_size(block) + BLOCK_HEADER_OVERHEAD
 	_ = block_link_next(prev)
 	_ = block_link_next(prev)
@@ -681,7 +688,7 @@ block_absorb :: proc(prev: ^Block_Header, block: ^Block_Header) -> (absorbed: ^B
 }
 }
 
 
 // Merge a just-freed block with an adjacent previous free block.
 // Merge a just-freed block with an adjacent previous free block.
-@(private, require_results)
+@(private, require_results, no_sanitize_address)
 block_merge_prev :: proc(control: ^Allocator, block: ^Block_Header) -> (merged: ^Block_Header) {
 block_merge_prev :: proc(control: ^Allocator, block: ^Block_Header) -> (merged: ^Block_Header) {
 	merged = block
 	merged = block
 	if (block_is_prev_free(block)) {
 	if (block_is_prev_free(block)) {
@@ -695,7 +702,7 @@ block_merge_prev :: proc(control: ^Allocator, block: ^Block_Header) -> (merged:
 }
 }
 
 
 // Merge a just-freed block with an adjacent free block.
 // Merge a just-freed block with an adjacent free block.
-@(private, require_results)
+@(private, require_results, no_sanitize_address)
 block_merge_next :: proc(control: ^Allocator, block: ^Block_Header) -> (merged: ^Block_Header) {
 block_merge_next :: proc(control: ^Allocator, block: ^Block_Header) -> (merged: ^Block_Header) {
 	merged = block
 	merged = block
 	next  := block_next(block)
 	next  := block_next(block)
@@ -710,7 +717,7 @@ block_merge_next :: proc(control: ^Allocator, block: ^Block_Header) -> (merged:
 }
 }
 
 
 // Trim any trailing block space off the end of a free block, return to pool.
 // Trim any trailing block space off the end of a free block, return to pool.
-@(private)
+@(private, no_sanitize_address)
 block_trim_free :: proc(control: ^Allocator, block: ^Block_Header, size: uint) {
 block_trim_free :: proc(control: ^Allocator, block: ^Block_Header, size: uint) {
 	assert(block_is_free(block), "block must be free")
 	assert(block_is_free(block), "block must be free")
 	if (block_can_split(block, size)) {
 	if (block_can_split(block, size)) {
@@ -722,7 +729,7 @@ block_trim_free :: proc(control: ^Allocator, block: ^Block_Header, size: uint) {
 }
 }
 
 
 // Trim any trailing block space off the end of a used block, return to pool.
 // Trim any trailing block space off the end of a used block, return to pool.
-@(private)
+@(private, no_sanitize_address)
 block_trim_used :: proc(control: ^Allocator, block: ^Block_Header, size: uint) {
 block_trim_used :: proc(control: ^Allocator, block: ^Block_Header, size: uint) {
 	assert(!block_is_free(block), "Block must be used")
 	assert(!block_is_free(block), "Block must be used")
 	if (block_can_split(block, size)) {
 	if (block_can_split(block, size)) {
@@ -736,7 +743,7 @@ block_trim_used :: proc(control: ^Allocator, block: ^Block_Header, size: uint) {
 }
 }
 
 
 // Trim leading block space, return to pool.
 // Trim leading block space, return to pool.
-@(private, require_results)
+@(private, require_results, no_sanitize_address)
 block_trim_free_leading :: proc(control: ^Allocator, block: ^Block_Header, size: uint) -> (remaining: ^Block_Header) {
 block_trim_free_leading :: proc(control: ^Allocator, block: ^Block_Header, size: uint) -> (remaining: ^Block_Header) {
 	remaining = block
 	remaining = block
 	if block_can_split(block, size) {
 	if block_can_split(block, size) {
@@ -750,7 +757,7 @@ block_trim_free_leading :: proc(control: ^Allocator, block: ^Block_Header, size:
 	return remaining
 	return remaining
 }
 }
 
 
-@(private, require_results)
+@(private, require_results, no_sanitize_address)
 block_locate_free :: proc(control: ^Allocator, size: uint) -> (block: ^Block_Header) {
 block_locate_free :: proc(control: ^Allocator, size: uint) -> (block: ^Block_Header) {
 	fl, sl: i32
 	fl, sl: i32
 	if size != 0 {
 	if size != 0 {
@@ -774,13 +781,14 @@ block_locate_free :: proc(control: ^Allocator, size: uint) -> (block: ^Block_Hea
 	return block
 	return block
 }
 }
 
 
-@(private, require_results)
+@(private, require_results, no_sanitize_address)
 block_prepare_used :: proc(control: ^Allocator, block: ^Block_Header, size: uint) -> (res: []byte, err: runtime.Allocator_Error) {
 block_prepare_used :: proc(control: ^Allocator, block: ^Block_Header, size: uint) -> (res: []byte, err: runtime.Allocator_Error) {
 	if block != nil {
 	if block != nil {
 		assert(size != 0, "Size must be non-zero")
 		assert(size != 0, "Size must be non-zero")
 		block_trim_free(control, block, size)
 		block_trim_free(control, block, size)
 		block_mark_as_used(block)
 		block_mark_as_used(block)
 		res = ([^]byte)(block_to_ptr(block))[:size]
 		res = ([^]byte)(block_to_ptr(block))[:size]
+		sanitizer.address_unpoison(res)
 	}
 	}
 	return
 	return
-}
+}

+ 10 - 1
core/mem/tracking_allocator.odin

@@ -64,6 +64,7 @@ This procedure initializes the tracking allocator `t` with a backing allocator
 specified with `backing_allocator`. The `internals_allocator` will used to
 specified with `backing_allocator`. The `internals_allocator` will used to
 allocate the tracked data.
 allocate the tracked data.
 */
 */
+@(no_sanitize_address)
 tracking_allocator_init :: proc(t: ^Tracking_Allocator, backing_allocator: Allocator, internals_allocator := context.allocator) {
 tracking_allocator_init :: proc(t: ^Tracking_Allocator, backing_allocator: Allocator, internals_allocator := context.allocator) {
 	t.backing = backing_allocator
 	t.backing = backing_allocator
 	t.allocation_map.allocator = internals_allocator
 	t.allocation_map.allocator = internals_allocator
@@ -77,6 +78,7 @@ tracking_allocator_init :: proc(t: ^Tracking_Allocator, backing_allocator: Alloc
 /*
 /*
 Destroy the tracking allocator.
 Destroy the tracking allocator.
 */
 */
+@(no_sanitize_address)
 tracking_allocator_destroy :: proc(t: ^Tracking_Allocator) {
 tracking_allocator_destroy :: proc(t: ^Tracking_Allocator) {
 	delete(t.allocation_map)
 	delete(t.allocation_map)
 	delete(t.bad_free_array)
 	delete(t.bad_free_array)
@@ -90,6 +92,7 @@ This procedure clears the tracked data from a tracking allocator.
 **Note**: This procedure clears only the current allocation data while keeping
 **Note**: This procedure clears only the current allocation data while keeping
 the totals intact.
 the totals intact.
 */
 */
+@(no_sanitize_address)
 tracking_allocator_clear :: proc(t: ^Tracking_Allocator) {
 tracking_allocator_clear :: proc(t: ^Tracking_Allocator) {
 	sync.mutex_lock(&t.mutex)
 	sync.mutex_lock(&t.mutex)
 	clear(&t.allocation_map)
 	clear(&t.allocation_map)
@@ -103,6 +106,7 @@ Reset the tracking allocator.
 
 
 Reset all of a Tracking Allocator's allocation data back to zero.
 Reset all of a Tracking Allocator's allocation data back to zero.
 */
 */
+@(no_sanitize_address)
 tracking_allocator_reset :: proc(t: ^Tracking_Allocator) {
 tracking_allocator_reset :: proc(t: ^Tracking_Allocator) {
 	sync.mutex_lock(&t.mutex)
 	sync.mutex_lock(&t.mutex)
 	clear(&t.allocation_map)
 	clear(&t.allocation_map)
@@ -124,6 +128,7 @@ Override Tracking_Allocator.bad_free_callback to have something else happen. For
 example, you can use tracking_allocator_bad_free_callback_add_to_array to return
 example, you can use tracking_allocator_bad_free_callback_add_to_array to return
 the tracking allocator to the old behavior, where the bad_free_array was used.
 the tracking allocator to the old behavior, where the bad_free_array was used.
 */
 */
+@(no_sanitize_address)
 tracking_allocator_bad_free_callback_panic :: proc(t: ^Tracking_Allocator, memory: rawptr, location: runtime.Source_Code_Location) {
 tracking_allocator_bad_free_callback_panic :: proc(t: ^Tracking_Allocator, memory: rawptr, location: runtime.Source_Code_Location) {
 	runtime.print_caller_location(location)
 	runtime.print_caller_location(location)
 	runtime.print_string(" Tracking allocator error: Bad free of pointer ")
 	runtime.print_string(" Tracking allocator error: Bad free of pointer ")
@@ -136,6 +141,7 @@ tracking_allocator_bad_free_callback_panic :: proc(t: ^Tracking_Allocator, memor
 Alternative behavior for a bad free: Store in `bad_free_array`. If you use this,
 Alternative behavior for a bad free: Store in `bad_free_array`. If you use this,
 then you must make sure to check Tracking_Allocator.bad_free_array at some point.
 then you must make sure to check Tracking_Allocator.bad_free_array at some point.
 */
 */
+@(no_sanitize_address)
 tracking_allocator_bad_free_callback_add_to_array :: proc(t: ^Tracking_Allocator, memory: rawptr, location: runtime.Source_Code_Location) {
 tracking_allocator_bad_free_callback_add_to_array :: proc(t: ^Tracking_Allocator, memory: rawptr, location: runtime.Source_Code_Location) {
 	append(&t.bad_free_array, Tracking_Allocator_Bad_Free_Entry {
 	append(&t.bad_free_array, Tracking_Allocator_Bad_Free_Entry {
 		memory = memory,
 		memory = memory,
@@ -175,7 +181,7 @@ Example:
 		}
 		}
 	}
 	}
 */
 */
-@(require_results)
+@(require_results, no_sanitize_address)
 tracking_allocator :: proc(data: ^Tracking_Allocator) -> Allocator {
 tracking_allocator :: proc(data: ^Tracking_Allocator) -> Allocator {
 	return Allocator{
 	return Allocator{
 		data = data,
 		data = data,
@@ -183,6 +189,7 @@ tracking_allocator :: proc(data: ^Tracking_Allocator) -> Allocator {
 	}
 	}
 }
 }
 
 
+@(no_sanitize_address)
 tracking_allocator_proc :: proc(
 tracking_allocator_proc :: proc(
 	allocator_data: rawptr,
 	allocator_data: rawptr,
 	mode: Allocator_Mode,
 	mode: Allocator_Mode,
@@ -191,6 +198,7 @@ tracking_allocator_proc :: proc(
 	old_size: int,
 	old_size: int,
 	loc := #caller_location,
 	loc := #caller_location,
 ) -> (result: []byte, err: Allocator_Error) {
 ) -> (result: []byte, err: Allocator_Error) {
+	@(no_sanitize_address)
 	track_alloc :: proc(data: ^Tracking_Allocator, entry: ^Tracking_Allocator_Entry) {
 	track_alloc :: proc(data: ^Tracking_Allocator, entry: ^Tracking_Allocator_Entry) {
 		data.total_memory_allocated += i64(entry.size)
 		data.total_memory_allocated += i64(entry.size)
 		data.total_allocation_count += 1
 		data.total_allocation_count += 1
@@ -200,6 +208,7 @@ tracking_allocator_proc :: proc(
 		}
 		}
 	}
 	}
 
 
+	@(no_sanitize_address)
 	track_free :: proc(data: ^Tracking_Allocator, entry: ^Tracking_Allocator_Entry) {
 	track_free :: proc(data: ^Tracking_Allocator, entry: ^Tracking_Allocator_Entry) {
 		data.total_memory_freed += i64(entry.size)
 		data.total_memory_freed += i64(entry.size)
 		data.total_free_count += 1
 		data.total_free_count += 1

+ 32 - 11
core/mem/virtual/arena.odin

@@ -3,6 +3,8 @@ package mem_virtual
 import "core:mem"
 import "core:mem"
 import "core:sync"
 import "core:sync"
 
 
+import "base:sanitizer"
+
 Arena_Kind :: enum uint {
 Arena_Kind :: enum uint {
 	Growing = 0, // Chained memory blocks (singly linked list).
 	Growing = 0, // Chained memory blocks (singly linked list).
 	Static  = 1, // Fixed reservation sized.
 	Static  = 1, // Fixed reservation sized.
@@ -43,7 +45,7 @@ DEFAULT_ARENA_STATIC_RESERVE_SIZE :: mem.Gigabyte when size_of(uintptr) == 8 els
 
 
 // Initialization of an `Arena` to be a `.Growing` variant.
 // Initialization of an `Arena` to be a `.Growing` variant.
 // A growing arena is a linked list of `Memory_Block`s allocated with virtual memory.
 // A growing arena is a linked list of `Memory_Block`s allocated with virtual memory.
-@(require_results)
+@(require_results, no_sanitize_address)
 arena_init_growing :: proc(arena: ^Arena, reserved: uint = DEFAULT_ARENA_GROWING_MINIMUM_BLOCK_SIZE) -> (err: Allocator_Error) {
 arena_init_growing :: proc(arena: ^Arena, reserved: uint = DEFAULT_ARENA_GROWING_MINIMUM_BLOCK_SIZE) -> (err: Allocator_Error) {
 	arena.kind           = .Growing
 	arena.kind           = .Growing
 	arena.curr_block     = memory_block_alloc(0, reserved, {}) or_return
 	arena.curr_block     = memory_block_alloc(0, reserved, {}) or_return
@@ -53,24 +55,26 @@ arena_init_growing :: proc(arena: ^Arena, reserved: uint = DEFAULT_ARENA_GROWING
 	if arena.minimum_block_size == 0 {
 	if arena.minimum_block_size == 0 {
 		arena.minimum_block_size = reserved
 		arena.minimum_block_size = reserved
 	}
 	}
+	sanitizer.address_poison(arena.curr_block.base[:arena.curr_block.committed])
 	return
 	return
 }
 }
 
 
 
 
 // Initialization of an `Arena` to be a `.Static` variant.
 // Initialization of an `Arena` to be a `.Static` variant.
 // A static arena contains a single `Memory_Block` allocated with virtual memory.
 // A static arena contains a single `Memory_Block` allocated with virtual memory.
-@(require_results)
+@(require_results, no_sanitize_address)
 arena_init_static :: proc(arena: ^Arena, reserved: uint = DEFAULT_ARENA_STATIC_RESERVE_SIZE, commit_size: uint = DEFAULT_ARENA_STATIC_COMMIT_SIZE) -> (err: Allocator_Error) {
 arena_init_static :: proc(arena: ^Arena, reserved: uint = DEFAULT_ARENA_STATIC_RESERVE_SIZE, commit_size: uint = DEFAULT_ARENA_STATIC_COMMIT_SIZE) -> (err: Allocator_Error) {
 	arena.kind           = .Static
 	arena.kind           = .Static
 	arena.curr_block     = memory_block_alloc(commit_size, reserved, {}) or_return
 	arena.curr_block     = memory_block_alloc(commit_size, reserved, {}) or_return
 	arena.total_used     = 0
 	arena.total_used     = 0
 	arena.total_reserved = arena.curr_block.reserved
 	arena.total_reserved = arena.curr_block.reserved
+	sanitizer.address_poison(arena.curr_block.base[:arena.curr_block.committed])
 	return
 	return
 }
 }
 
 
 // Initialization of an `Arena` to be a `.Buffer` variant.
 // Initialization of an `Arena` to be a `.Buffer` variant.
 // A buffer arena contains single `Memory_Block` created from a user provided []byte.
 // A buffer arena contains single `Memory_Block` created from a user provided []byte.
-@(require_results)
+@(require_results, no_sanitize_address)
 arena_init_buffer :: proc(arena: ^Arena, buffer: []byte) -> (err: Allocator_Error) {
 arena_init_buffer :: proc(arena: ^Arena, buffer: []byte) -> (err: Allocator_Error) {
 	if len(buffer) < size_of(Memory_Block) {
 	if len(buffer) < size_of(Memory_Block) {
 		return .Out_Of_Memory
 		return .Out_Of_Memory
@@ -78,7 +82,7 @@ arena_init_buffer :: proc(arena: ^Arena, buffer: []byte) -> (err: Allocator_Erro
 
 
 	arena.kind = .Buffer
 	arena.kind = .Buffer
 
 
-	mem.zero_slice(buffer)
+	sanitizer.address_poison(buffer[:])
 
 
 	block_base := raw_data(buffer)
 	block_base := raw_data(buffer)
 	block := (^Memory_Block)(block_base)
 	block := (^Memory_Block)(block_base)
@@ -94,7 +98,7 @@ arena_init_buffer :: proc(arena: ^Arena, buffer: []byte) -> (err: Allocator_Erro
 }
 }
 
 
 // Allocates memory from the provided arena.
 // Allocates memory from the provided arena.
-@(require_results)
+@(require_results, no_sanitize_address)
 arena_alloc :: proc(arena: ^Arena, size: uint, alignment: uint, loc := #caller_location) -> (data: []byte, err: Allocator_Error) {
 arena_alloc :: proc(arena: ^Arena, size: uint, alignment: uint, loc := #caller_location) -> (data: []byte, err: Allocator_Error) {
 	assert(alignment & (alignment-1) == 0, "non-power of two alignment", loc)
 	assert(alignment & (alignment-1) == 0, "non-power of two alignment", loc)
 
 
@@ -158,10 +162,13 @@ arena_alloc :: proc(arena: ^Arena, size: uint, alignment: uint, loc := #caller_l
 		data, err = alloc_from_memory_block(arena.curr_block, size, alignment, default_commit_size=0)
 		data, err = alloc_from_memory_block(arena.curr_block, size, alignment, default_commit_size=0)
 		arena.total_used = arena.curr_block.used
 		arena.total_used = arena.curr_block.used
 	}
 	}
+
+	sanitizer.address_unpoison(data)
 	return
 	return
 }
 }
 
 
 // Resets the memory of a Static or Buffer arena to a specific `position` (offset) and zeroes the previously used memory.
 // Resets the memory of a Static or Buffer arena to a specific `position` (offset) and zeroes the previously used memory.
+@(no_sanitize_address)
 arena_static_reset_to :: proc(arena: ^Arena, pos: uint, loc := #caller_location) -> bool {
 arena_static_reset_to :: proc(arena: ^Arena, pos: uint, loc := #caller_location) -> bool {
 	sync.mutex_guard(&arena.mutex)
 	sync.mutex_guard(&arena.mutex)
 
 
@@ -175,6 +182,7 @@ arena_static_reset_to :: proc(arena: ^Arena, pos: uint, loc := #caller_location)
 			mem.zero_slice(arena.curr_block.base[arena.curr_block.used:][:prev_pos-pos])
 			mem.zero_slice(arena.curr_block.base[arena.curr_block.used:][:prev_pos-pos])
 		}
 		}
 		arena.total_used = arena.curr_block.used
 		arena.total_used = arena.curr_block.used
+		sanitizer.address_poison(arena.curr_block.base[:arena.curr_block.committed])
 		return true
 		return true
 	} else if pos == 0 {
 	} else if pos == 0 {
 		arena.total_used = 0
 		arena.total_used = 0
@@ -184,6 +192,7 @@ arena_static_reset_to :: proc(arena: ^Arena, pos: uint, loc := #caller_location)
 }
 }
 
 
 // Frees the last memory block of a Growing Arena
 // Frees the last memory block of a Growing Arena
+@(no_sanitize_address)
 arena_growing_free_last_memory_block :: proc(arena: ^Arena, loc := #caller_location) {
 arena_growing_free_last_memory_block :: proc(arena: ^Arena, loc := #caller_location) {
 	if free_block := arena.curr_block; free_block != nil {
 	if free_block := arena.curr_block; free_block != nil {
 		assert(arena.kind == .Growing, "expected a .Growing arena", loc)
 		assert(arena.kind == .Growing, "expected a .Growing arena", loc)
@@ -191,11 +200,13 @@ arena_growing_free_last_memory_block :: proc(arena: ^Arena, loc := #caller_locat
 		arena.total_reserved -= free_block.reserved
 		arena.total_reserved -= free_block.reserved
 
 
 		arena.curr_block = free_block.prev
 		arena.curr_block = free_block.prev
+		sanitizer.address_poison(free_block.base[:free_block.committed])
 		memory_block_dealloc(free_block)
 		memory_block_dealloc(free_block)
 	}
 	}
 }
 }
 
 
 // Deallocates all but the first memory block of the arena and resets the allocator's usage to 0.
 // Deallocates all but the first memory block of the arena and resets the allocator's usage to 0.
+@(no_sanitize_address)
 arena_free_all :: proc(arena: ^Arena, loc := #caller_location) {
 arena_free_all :: proc(arena: ^Arena, loc := #caller_location) {
 	switch arena.kind {
 	switch arena.kind {
 	case .Growing:
 	case .Growing:
@@ -208,7 +219,9 @@ arena_free_all :: proc(arena: ^Arena, loc := #caller_location) {
 		if arena.curr_block != nil {
 		if arena.curr_block != nil {
 			curr_block_used := int(arena.curr_block.used)
 			curr_block_used := int(arena.curr_block.used)
 			arena.curr_block.used = 0
 			arena.curr_block.used = 0
+			sanitizer.address_unpoison(arena.curr_block.base[:curr_block_used])
 			mem.zero(arena.curr_block.base, curr_block_used)
 			mem.zero(arena.curr_block.base, curr_block_used)
+			sanitizer.address_poison(arena.curr_block.base[:arena.curr_block.committed])
 		}
 		}
 		arena.total_used = 0
 		arena.total_used = 0
 	case .Static, .Buffer:
 	case .Static, .Buffer:
@@ -219,6 +232,7 @@ arena_free_all :: proc(arena: ^Arena, loc := #caller_location) {
 
 
 // Frees all of the memory allocated by the arena and zeros all of the values of an arena.
 // Frees all of the memory allocated by the arena and zeros all of the values of an arena.
 // A buffer based arena does not `delete` the provided `[]byte` bufffer.
 // A buffer based arena does not `delete` the provided `[]byte` bufffer.
+@(no_sanitize_address)
 arena_destroy :: proc(arena: ^Arena, loc := #caller_location) {
 arena_destroy :: proc(arena: ^Arena, loc := #caller_location) {
 	sync.mutex_guard(&arena.mutex)
 	sync.mutex_guard(&arena.mutex)
 	switch arena.kind {
 	switch arena.kind {
@@ -250,7 +264,7 @@ arena_static_bootstrap_new :: proc{
 }
 }
 
 
 // Ability to bootstrap allocate a struct with an arena within the struct itself using the growing variant strategy.
 // Ability to bootstrap allocate a struct with an arena within the struct itself using the growing variant strategy.
-@(require_results)
+@(require_results, no_sanitize_address)
 arena_growing_bootstrap_new_by_offset :: proc($T: typeid, offset_to_arena: uintptr, minimum_block_size: uint = DEFAULT_ARENA_GROWING_MINIMUM_BLOCK_SIZE) -> (ptr: ^T, err: Allocator_Error) {
 arena_growing_bootstrap_new_by_offset :: proc($T: typeid, offset_to_arena: uintptr, minimum_block_size: uint = DEFAULT_ARENA_GROWING_MINIMUM_BLOCK_SIZE) -> (ptr: ^T, err: Allocator_Error) {
 	bootstrap: Arena
 	bootstrap: Arena
 	bootstrap.kind = .Growing
 	bootstrap.kind = .Growing
@@ -266,13 +280,13 @@ arena_growing_bootstrap_new_by_offset :: proc($T: typeid, offset_to_arena: uintp
 }
 }
 
 
 // Ability to bootstrap allocate a struct with an arena within the struct itself using the growing variant strategy.
 // Ability to bootstrap allocate a struct with an arena within the struct itself using the growing variant strategy.
-@(require_results)
+@(require_results, no_sanitize_address)
 arena_growing_bootstrap_new_by_name :: proc($T: typeid, $field_name: string, minimum_block_size: uint = DEFAULT_ARENA_GROWING_MINIMUM_BLOCK_SIZE) -> (ptr: ^T, err: Allocator_Error) {
 arena_growing_bootstrap_new_by_name :: proc($T: typeid, $field_name: string, minimum_block_size: uint = DEFAULT_ARENA_GROWING_MINIMUM_BLOCK_SIZE) -> (ptr: ^T, err: Allocator_Error) {
 	return arena_growing_bootstrap_new_by_offset(T, offset_of_by_string(T, field_name), minimum_block_size)
 	return arena_growing_bootstrap_new_by_offset(T, offset_of_by_string(T, field_name), minimum_block_size)
 }
 }
 
 
 // Ability to bootstrap allocate a struct with an arena within the struct itself using the static variant strategy.
 // Ability to bootstrap allocate a struct with an arena within the struct itself using the static variant strategy.
-@(require_results)
+@(require_results, no_sanitize_address)
 arena_static_bootstrap_new_by_offset :: proc($T: typeid, offset_to_arena: uintptr, reserved: uint) -> (ptr: ^T, err: Allocator_Error) {
 arena_static_bootstrap_new_by_offset :: proc($T: typeid, offset_to_arena: uintptr, reserved: uint) -> (ptr: ^T, err: Allocator_Error) {
 	bootstrap: Arena
 	bootstrap: Arena
 	bootstrap.kind = .Static
 	bootstrap.kind = .Static
@@ -288,19 +302,20 @@ arena_static_bootstrap_new_by_offset :: proc($T: typeid, offset_to_arena: uintpt
 }
 }
 
 
 // Ability to bootstrap allocate a struct with an arena within the struct itself using the static variant strategy.
 // Ability to bootstrap allocate a struct with an arena within the struct itself using the static variant strategy.
-@(require_results)
+@(require_results, no_sanitize_address)
 arena_static_bootstrap_new_by_name :: proc($T: typeid, $field_name: string, reserved: uint) -> (ptr: ^T, err: Allocator_Error) {
 arena_static_bootstrap_new_by_name :: proc($T: typeid, $field_name: string, reserved: uint) -> (ptr: ^T, err: Allocator_Error) {
 	return arena_static_bootstrap_new_by_offset(T, offset_of_by_string(T, field_name), reserved)
 	return arena_static_bootstrap_new_by_offset(T, offset_of_by_string(T, field_name), reserved)
 }
 }
 
 
 
 
 // Create an `Allocator` from the provided `Arena`
 // Create an `Allocator` from the provided `Arena`
-@(require_results)
+@(require_results, no_sanitize_address)
 arena_allocator :: proc(arena: ^Arena) -> mem.Allocator {
 arena_allocator :: proc(arena: ^Arena) -> mem.Allocator {
 	return mem.Allocator{arena_allocator_proc, arena}
 	return mem.Allocator{arena_allocator_proc, arena}
 }
 }
 
 
 // The allocator procedure used by an `Allocator` produced by `arena_allocator`
 // The allocator procedure used by an `Allocator` produced by `arena_allocator`
+@(no_sanitize_address)
 arena_allocator_proc :: proc(allocator_data: rawptr, mode: mem.Allocator_Mode,
 arena_allocator_proc :: proc(allocator_data: rawptr, mode: mem.Allocator_Mode,
                              size, alignment: int,
                              size, alignment: int,
                              old_memory: rawptr, old_size: int,
                              old_memory: rawptr, old_size: int,
@@ -334,6 +349,7 @@ arena_allocator_proc :: proc(allocator_data: rawptr, mode: mem.Allocator_Mode,
 			if size < old_size {
 			if size < old_size {
 				// shrink data in-place
 				// shrink data in-place
 				data = old_data[:size]
 				data = old_data[:size]
+				sanitizer.address_poison(old_data[size:old_size])
 				return
 				return
 			}
 			}
 
 
@@ -347,6 +363,7 @@ arena_allocator_proc :: proc(allocator_data: rawptr, mode: mem.Allocator_Mode,
 					_ = alloc_from_memory_block(block, new_end - old_end, 1, default_commit_size=arena.default_commit_size) or_return
 					_ = alloc_from_memory_block(block, new_end - old_end, 1, default_commit_size=arena.default_commit_size) or_return
 					arena.total_used += block.used - prev_used
 					arena.total_used += block.used - prev_used
 					data = block.base[start:new_end]
 					data = block.base[start:new_end]
+					sanitizer.address_unpoison(data)
 					return
 					return
 				}
 				}
 			}
 			}
@@ -357,6 +374,7 @@ arena_allocator_proc :: proc(allocator_data: rawptr, mode: mem.Allocator_Mode,
 			return
 			return
 		}
 		}
 		copy(new_memory, old_data[:old_size])
 		copy(new_memory, old_data[:old_size])
+		sanitizer.address_poison(old_data[:old_size])
 		return new_memory, nil
 		return new_memory, nil
 	case .Query_Features:
 	case .Query_Features:
 		set := (^mem.Allocator_Mode_Set)(old_memory)
 		set := (^mem.Allocator_Mode_Set)(old_memory)
@@ -382,7 +400,7 @@ Arena_Temp :: struct {
 }
 }
 
 
 // Begins the section of temporary arena memory.
 // Begins the section of temporary arena memory.
-@(require_results)
+@(require_results, no_sanitize_address)
 arena_temp_begin :: proc(arena: ^Arena, loc := #caller_location) -> (temp: Arena_Temp) {
 arena_temp_begin :: proc(arena: ^Arena, loc := #caller_location) -> (temp: Arena_Temp) {
 	assert(arena != nil, "nil arena", loc)
 	assert(arena != nil, "nil arena", loc)
 	sync.mutex_guard(&arena.mutex)
 	sync.mutex_guard(&arena.mutex)
@@ -397,6 +415,7 @@ arena_temp_begin :: proc(arena: ^Arena, loc := #caller_location) -> (temp: Arena
 }
 }
 
 
 // Ends the section of temporary arena memory by resetting the memory to the stored position.
 // Ends the section of temporary arena memory by resetting the memory to the stored position.
+@(no_sanitize_address)
 arena_temp_end :: proc(temp: Arena_Temp, loc := #caller_location) {
 arena_temp_end :: proc(temp: Arena_Temp, loc := #caller_location) {
 	assert(temp.arena != nil, "nil arena", loc)
 	assert(temp.arena != nil, "nil arena", loc)
 	arena := temp.arena
 	arena := temp.arena
@@ -432,6 +451,7 @@ arena_temp_end :: proc(temp: Arena_Temp, loc := #caller_location) {
 }
 }
 
 
 // Ignore the use of a `arena_temp_begin` entirely by __not__ resetting to the stored position.
 // Ignore the use of a `arena_temp_begin` entirely by __not__ resetting to the stored position.
+@(no_sanitize_address)
 arena_temp_ignore :: proc(temp: Arena_Temp, loc := #caller_location) {
 arena_temp_ignore :: proc(temp: Arena_Temp, loc := #caller_location) {
 	assert(temp.arena != nil, "nil arena", loc)
 	assert(temp.arena != nil, "nil arena", loc)
 	arena := temp.arena
 	arena := temp.arena
@@ -442,6 +462,7 @@ arena_temp_ignore :: proc(temp: Arena_Temp, loc := #caller_location) {
 }
 }
 
 
 // Asserts that all uses of `Arena_Temp` has been used by an `Arena`
 // Asserts that all uses of `Arena_Temp` has been used by an `Arena`
+@(no_sanitize_address)
 arena_check_temp :: proc(arena: ^Arena, loc := #caller_location) {
 arena_check_temp :: proc(arena: ^Arena, loc := #caller_location) {
 	assert(arena.temp_count == 0, "Arena_Temp not been ended", loc)
 	assert(arena.temp_count == 0, "Arena_Temp not been ended", loc)
 }
 }

+ 17 - 8
core/mem/virtual/virtual.odin

@@ -2,6 +2,7 @@ package mem_virtual
 
 
 import "core:mem"
 import "core:mem"
 import "base:intrinsics"
 import "base:intrinsics"
+import "base:sanitizer"
 import "base:runtime"
 import "base:runtime"
 _ :: runtime
 _ :: runtime
 
 
@@ -14,27 +15,33 @@ platform_memory_init :: proc() {
 
 
 Allocator_Error :: mem.Allocator_Error
 Allocator_Error :: mem.Allocator_Error
 
 
-@(require_results)
+@(require_results, no_sanitize_address)
 reserve :: proc "contextless" (size: uint) -> (data: []byte, err: Allocator_Error) {
 reserve :: proc "contextless" (size: uint) -> (data: []byte, err: Allocator_Error) {
 	return _reserve(size)
 	return _reserve(size)
 }
 }
 
 
+@(no_sanitize_address)
 commit :: proc "contextless" (data: rawptr, size: uint) -> Allocator_Error {
 commit :: proc "contextless" (data: rawptr, size: uint) -> Allocator_Error {
+	sanitizer.address_unpoison(data, size)
 	return _commit(data, size)
 	return _commit(data, size)
 }
 }
 
 
-@(require_results)
+@(require_results, no_sanitize_address)
 reserve_and_commit :: proc "contextless" (size: uint) -> (data: []byte, err: Allocator_Error) {
 reserve_and_commit :: proc "contextless" (size: uint) -> (data: []byte, err: Allocator_Error) {
 	data = reserve(size) or_return
 	data = reserve(size) or_return
 	commit(raw_data(data), size) or_return
 	commit(raw_data(data), size) or_return
 	return
 	return
 }
 }
 
 
+@(no_sanitize_address)
 decommit :: proc "contextless" (data: rawptr, size: uint) {
 decommit :: proc "contextless" (data: rawptr, size: uint) {
+	sanitizer.address_poison(data, size)
 	_decommit(data, size)
 	_decommit(data, size)
 }
 }
 
 
+@(no_sanitize_address)
 release :: proc "contextless" (data: rawptr, size: uint) {
 release :: proc "contextless" (data: rawptr, size: uint) {
+	sanitizer.address_unpoison(data, size)
 	_release(data, size)
 	_release(data, size)
 }
 }
 
 
@@ -46,13 +53,11 @@ Protect_Flag :: enum u32 {
 Protect_Flags :: distinct bit_set[Protect_Flag; u32]
 Protect_Flags :: distinct bit_set[Protect_Flag; u32]
 Protect_No_Access :: Protect_Flags{}
 Protect_No_Access :: Protect_Flags{}
 
 
+@(no_sanitize_address)
 protect :: proc "contextless" (data: rawptr, size: uint, flags: Protect_Flags) -> bool {
 protect :: proc "contextless" (data: rawptr, size: uint, flags: Protect_Flags) -> bool {
 	return _protect(data, size, flags)
 	return _protect(data, size, flags)
 }
 }
 
 
-
-
-
 Memory_Block :: struct {
 Memory_Block :: struct {
 	prev: ^Memory_Block,
 	prev: ^Memory_Block,
 	base:      [^]byte,
 	base:      [^]byte,
@@ -66,13 +71,13 @@ Memory_Block_Flag :: enum u32 {
 Memory_Block_Flags :: distinct bit_set[Memory_Block_Flag; u32]
 Memory_Block_Flags :: distinct bit_set[Memory_Block_Flag; u32]
 
 
 
 
-@(private="file", require_results)
+@(private="file", require_results, no_sanitize_address)
 align_formula :: #force_inline proc "contextless" (size, align: uint) -> uint {
 align_formula :: #force_inline proc "contextless" (size, align: uint) -> uint {
 	result := size + align-1
 	result := size + align-1
 	return result - result%align
 	return result - result%align
 }
 }
 
 
-@(require_results)
+@(require_results, no_sanitize_address)
 memory_block_alloc :: proc(committed, reserved: uint, alignment: uint = 0, flags: Memory_Block_Flags = {}) -> (block: ^Memory_Block, err: Allocator_Error) {
 memory_block_alloc :: proc(committed, reserved: uint, alignment: uint = 0, flags: Memory_Block_Flags = {}) -> (block: ^Memory_Block, err: Allocator_Error) {
 	page_size := DEFAULT_PAGE_SIZE
 	page_size := DEFAULT_PAGE_SIZE
 	assert(mem.is_power_of_two(uintptr(page_size)))
 	assert(mem.is_power_of_two(uintptr(page_size)))
@@ -116,8 +121,9 @@ memory_block_alloc :: proc(committed, reserved: uint, alignment: uint = 0, flags
 	return &pmblock.block, nil
 	return &pmblock.block, nil
 }
 }
 
 
-@(require_results)
+@(require_results, no_sanitize_address)
 alloc_from_memory_block :: proc(block: ^Memory_Block, min_size, alignment: uint, default_commit_size: uint = 0) -> (data: []byte, err: Allocator_Error) {
 alloc_from_memory_block :: proc(block: ^Memory_Block, min_size, alignment: uint, default_commit_size: uint = 0) -> (data: []byte, err: Allocator_Error) {
+	@(no_sanitize_address)
 	calc_alignment_offset :: proc "contextless" (block: ^Memory_Block, alignment: uintptr) -> uint {
 	calc_alignment_offset :: proc "contextless" (block: ^Memory_Block, alignment: uintptr) -> uint {
 		alignment_offset := uint(0)
 		alignment_offset := uint(0)
 		ptr := uintptr(block.base[block.used:])
 		ptr := uintptr(block.base[block.used:])
@@ -128,6 +134,7 @@ alloc_from_memory_block :: proc(block: ^Memory_Block, min_size, alignment: uint,
 		return alignment_offset
 		return alignment_offset
 		
 		
 	}
 	}
+	@(no_sanitize_address)
 	do_commit_if_necessary :: proc(block: ^Memory_Block, size: uint, default_commit_size: uint) -> (err: Allocator_Error) {
 	do_commit_if_necessary :: proc(block: ^Memory_Block, size: uint, default_commit_size: uint) -> (err: Allocator_Error) {
 		if block.committed - block.used < size {
 		if block.committed - block.used < size {
 			pmblock := (^Platform_Memory_Block)(block)
 			pmblock := (^Platform_Memory_Block)(block)
@@ -172,10 +179,12 @@ alloc_from_memory_block :: proc(block: ^Memory_Block, min_size, alignment: uint,
 
 
 	data = block.base[block.used+alignment_offset:][:min_size]
 	data = block.base[block.used+alignment_offset:][:min_size]
 	block.used += size
 	block.used += size
+	sanitizer.address_unpoison(data)
 	return
 	return
 }
 }
 
 
 
 
+@(no_sanitize_address)
 memory_block_dealloc :: proc(block_to_free: ^Memory_Block) {
 memory_block_dealloc :: proc(block_to_free: ^Memory_Block) {
 	if block := (^Platform_Memory_Block)(block_to_free); block != nil {
 	if block := (^Platform_Memory_Block)(block_to_free); block != nil {
 		platform_memory_free(block)
 		platform_memory_free(block)

+ 3 - 0
core/mem/virtual/virtual_platform.odin

@@ -7,6 +7,7 @@ Platform_Memory_Block :: struct {
 	reserved:   uint,
 	reserved:   uint,
 } 
 } 
 
 
+@(no_sanitize_address)
 platform_memory_alloc :: proc "contextless" (to_commit, to_reserve: uint) -> (block: ^Platform_Memory_Block, err: Allocator_Error) {
 platform_memory_alloc :: proc "contextless" (to_commit, to_reserve: uint) -> (block: ^Platform_Memory_Block, err: Allocator_Error) {
 	to_commit, to_reserve := to_commit, to_reserve
 	to_commit, to_reserve := to_commit, to_reserve
 	to_reserve = max(to_commit, to_reserve)
 	to_reserve = max(to_commit, to_reserve)
@@ -26,12 +27,14 @@ platform_memory_alloc :: proc "contextless" (to_commit, to_reserve: uint) -> (bl
 }
 }
 
 
 
 
+@(no_sanitize_address)
 platform_memory_free :: proc "contextless" (block: ^Platform_Memory_Block) {
 platform_memory_free :: proc "contextless" (block: ^Platform_Memory_Block) {
 	if block != nil {
 	if block != nil {
 		release(block, block.reserved)
 		release(block, block.reserved)
 	}
 	}
 }
 }
 
 
+@(no_sanitize_address)
 platform_memory_commit :: proc "contextless" (block: ^Platform_Memory_Block, to_commit: uint) -> (err: Allocator_Error) {
 platform_memory_commit :: proc "contextless" (block: ^Platform_Memory_Block, to_commit: uint) -> (err: Allocator_Error) {
 	if to_commit < block.committed {
 	if to_commit < block.committed {
 		return nil
 		return nil

+ 11 - 1
core/mem/virtual/virtual_windows.odin

@@ -83,6 +83,8 @@ foreign Kernel32 {
 		dwNumberOfBytesToMap: uint,
 		dwNumberOfBytesToMap: uint,
 	) -> rawptr ---
 	) -> rawptr ---
 }
 }
+
+@(no_sanitize_address)
 _reserve :: proc "contextless" (size: uint) -> (data: []byte, err: Allocator_Error) {
 _reserve :: proc "contextless" (size: uint) -> (data: []byte, err: Allocator_Error) {
 	result := VirtualAlloc(nil, size, MEM_RESERVE, PAGE_READWRITE)
 	result := VirtualAlloc(nil, size, MEM_RESERVE, PAGE_READWRITE)
 	if result == nil {
 	if result == nil {
@@ -93,6 +95,7 @@ _reserve :: proc "contextless" (size: uint) -> (data: []byte, err: Allocator_Err
 	return
 	return
 }
 }
 
 
+@(no_sanitize_address)
 _commit :: proc "contextless" (data: rawptr, size: uint) -> Allocator_Error {
 _commit :: proc "contextless" (data: rawptr, size: uint) -> Allocator_Error {
 	result := VirtualAlloc(data, size, MEM_COMMIT, PAGE_READWRITE)
 	result := VirtualAlloc(data, size, MEM_COMMIT, PAGE_READWRITE)
 	if result == nil {
 	if result == nil {
@@ -107,12 +110,18 @@ _commit :: proc "contextless" (data: rawptr, size: uint) -> Allocator_Error {
 	}
 	}
 	return nil
 	return nil
 }
 }
+
+@(no_sanitize_address)
 _decommit :: proc "contextless" (data: rawptr, size: uint) {
 _decommit :: proc "contextless" (data: rawptr, size: uint) {
 	VirtualFree(data, size, MEM_DECOMMIT)
 	VirtualFree(data, size, MEM_DECOMMIT)
 }
 }
+
+@(no_sanitize_address)
 _release :: proc "contextless" (data: rawptr, size: uint) {
 _release :: proc "contextless" (data: rawptr, size: uint) {
 	VirtualFree(data, 0, MEM_RELEASE)
 	VirtualFree(data, 0, MEM_RELEASE)
 }
 }
+
+@(no_sanitize_address)
 _protect :: proc "contextless" (data: rawptr, size: uint, flags: Protect_Flags) -> bool {
 _protect :: proc "contextless" (data: rawptr, size: uint, flags: Protect_Flags) -> bool {
 	pflags: u32
 	pflags: u32
 	pflags = PAGE_NOACCESS
 	pflags = PAGE_NOACCESS
@@ -136,7 +145,7 @@ _protect :: proc "contextless" (data: rawptr, size: uint, flags: Protect_Flags)
 }
 }
 
 
 
 
-
+@(no_sanitize_address)
 _platform_memory_init :: proc() {
 _platform_memory_init :: proc() {
 	sys_info: SYSTEM_INFO
 	sys_info: SYSTEM_INFO
 	GetSystemInfo(&sys_info)
 	GetSystemInfo(&sys_info)
@@ -147,6 +156,7 @@ _platform_memory_init :: proc() {
 }
 }
 
 
 
 
+@(no_sanitize_address)
 _map_file :: proc "contextless" (fd: uintptr, size: i64, flags: Map_File_Flags) -> (data: []byte, error: Map_File_Error) {
 _map_file :: proc "contextless" (fd: uintptr, size: i64, flags: Map_File_Flags) -> (data: []byte, error: Map_File_Error) {
 	page_flags: u32
 	page_flags: u32
 	if flags == {.Read} {
 	if flags == {.Read} {

+ 1 - 1
core/net/url.odin

@@ -125,7 +125,7 @@ percent_encode :: proc(s: string, allocator := context.allocator) -> string {
 			bytes, n := utf8.encode_rune(ch)
 			bytes, n := utf8.encode_rune(ch)
 			for byte in bytes[:n] {
 			for byte in bytes[:n] {
 				buf: [2]u8 = ---
 				buf: [2]u8 = ---
-				t := strconv.append_int(buf[:], i64(byte), 16)
+				t := strconv.write_int(buf[:], i64(byte), 16)
 				strings.write_rune(&b, '%')
 				strings.write_rune(&b, '%')
 				strings.write_string(&b, t)
 				strings.write_string(&b, t)
 			}
 			}

+ 20 - 20
core/odin/parser/parser.odin

@@ -1276,28 +1276,28 @@ parse_unrolled_for_loop :: proc(p: ^Parser, inline_tok: tokenizer.Token) -> ^ast
 			args = make([dynamic]^ast.Expr)
 			args = make([dynamic]^ast.Expr)
 			for p.curr_tok.kind != .Close_Paren &&
 			for p.curr_tok.kind != .Close_Paren &&
 			    p.curr_tok.kind != .EOF {
 			    p.curr_tok.kind != .EOF {
-			    	arg := parse_value(p)
-
-			    	if p.curr_tok.kind == .Eq {
-			    		eq := expect_token(p, .Eq)
-			    		if arg != nil {
-			    			if _, ok := arg.derived.(^ast.Ident); !ok {
-			    				error(p, arg.pos, "expected an identifier for 'key=value'")
-			    			}
-			    		}
-			    		value := parse_value(p)
-			    		fv := ast.new(ast.Field_Value, arg.pos, value)
-			    		fv.field = arg
-			    		fv.sep   = eq.pos
-			    		fv.value = value
-
-			    		arg = fv
-			    	}
-
-			    	append(&args, arg)
+				arg := parse_value(p)
+
+				if p.curr_tok.kind == .Eq {
+					eq := expect_token(p, .Eq)
+					if arg != nil {
+						if _, ok := arg.derived.(^ast.Ident); !ok {
+							error(p, arg.pos, "expected an identifier for 'key=value'")
+						}
+					}
+					value := parse_value(p)
+					fv := ast.new(ast.Field_Value, arg.pos, value)
+					fv.field = arg
+					fv.sep   = eq.pos
+					fv.value = value
+
+					arg = fv
+				}
+
+				append(&args, arg)
 
 
 				allow_token(p, .Comma) or_break
 				allow_token(p, .Comma) or_break
-			    }
+			}
 		}
 		}
 
 
 		p.expr_level -= 1
 		p.expr_level -= 1

+ 1 - 1
core/os/os.odin

@@ -57,7 +57,7 @@ write_encoded_rune :: proc(f: Handle, r: rune) -> (n: int, err: Error) {
 		if r < 32 {
 		if r < 32 {
 			if wrap(write_string(f, "\\x"), &n, &err) { return }
 			if wrap(write_string(f, "\\x"), &n, &err) { return }
 			b: [2]byte
 			b: [2]byte
-			s := strconv.append_bits(b[:], u64(r), 16, true, 64, strconv.digits, nil)
+			s := strconv.write_bits(b[:], u64(r), 16, true, 64, strconv.digits, nil)
 			switch len(s) {
 			switch len(s) {
 			case 0: if wrap(write_string(f, "00"), &n, &err) { return }
 			case 0: if wrap(write_string(f, "00"), &n, &err) { return }
 			case 1: if wrap(write_rune(f, '0'), &n, &err)    { return }
 			case 1: if wrap(write_rune(f, '0'), &n, &err)    { return }

+ 42 - 41
core/os/os2/allocators.odin

@@ -8,43 +8,13 @@ file_allocator :: proc() -> runtime.Allocator {
 	return heap_allocator()
 	return heap_allocator()
 }
 }
 
 
-temp_allocator_proc :: runtime.arena_allocator_proc
-
 @(private="file")
 @(private="file")
 MAX_TEMP_ARENA_COUNT :: 2
 MAX_TEMP_ARENA_COUNT :: 2
-
+@(private="file")
+MAX_TEMP_ARENA_COLLISIONS :: MAX_TEMP_ARENA_COUNT - 1
 @(private="file", thread_local)
 @(private="file", thread_local)
 global_default_temp_allocator_arenas: [MAX_TEMP_ARENA_COUNT]runtime.Arena
 global_default_temp_allocator_arenas: [MAX_TEMP_ARENA_COUNT]runtime.Arena
 
 
-@(private="file", thread_local)
-global_default_temp_allocator_index: uint
-
-
-@(require_results)
-temp_allocator :: proc() -> runtime.Allocator {
-	arena := &global_default_temp_allocator_arenas[global_default_temp_allocator_index]
-	if arena.backing_allocator.procedure == nil {
-		arena.backing_allocator = heap_allocator()
-	}
-
-	return runtime.Allocator{
-		procedure = temp_allocator_proc,
-		data      = arena,
-	}
-}
-
-
-
-@(require_results)
-temp_allocator_temp_begin :: proc(loc := #caller_location) -> (temp: runtime.Arena_Temp) {
-	temp = runtime.arena_temp_begin(&global_default_temp_allocator_arenas[global_default_temp_allocator_index], loc)
-	return
-}
-
-temp_allocator_temp_end :: proc(temp: runtime.Arena_Temp, loc := #caller_location) {
-	runtime.arena_temp_end(temp, loc)
-}
-
 @(fini, private)
 @(fini, private)
 temp_allocator_fini :: proc() {
 temp_allocator_fini :: proc() {
 	for &arena in global_default_temp_allocator_arenas {
 	for &arena in global_default_temp_allocator_arenas {
@@ -53,18 +23,49 @@ temp_allocator_fini :: proc() {
 	global_default_temp_allocator_arenas = {}
 	global_default_temp_allocator_arenas = {}
 }
 }
 
 
-TEMP_ALLOCATOR_GUARD_END :: proc(temp: runtime.Arena_Temp, loc := #caller_location) {
-	runtime.arena_temp_end(temp, loc)
-	if temp.arena != nil {
-		global_default_temp_allocator_index = (global_default_temp_allocator_index-1)%MAX_TEMP_ARENA_COUNT
-	}
+Temp_Allocator :: struct {
+	using arena: ^runtime.Arena,
+	using allocator: runtime.Allocator,
+	tmp: runtime.Arena_Temp,
+	loc: runtime.Source_Code_Location,
+}
+	
+TEMP_ALLOCATOR_GUARD_END :: proc(temp: Temp_Allocator) {
+	runtime.arena_temp_end(temp.tmp, temp.loc)
 }
 }
 
 
 @(deferred_out=TEMP_ALLOCATOR_GUARD_END)
 @(deferred_out=TEMP_ALLOCATOR_GUARD_END)
-TEMP_ALLOCATOR_GUARD :: #force_inline proc(loc := #caller_location) -> (runtime.Arena_Temp, runtime.Source_Code_Location) {
-	global_default_temp_allocator_index = (global_default_temp_allocator_index+1)%MAX_TEMP_ARENA_COUNT
-	tmp := temp_allocator_temp_begin(loc)
-	return tmp, loc
+TEMP_ALLOCATOR_GUARD :: #force_inline proc(collisions: []runtime.Allocator, loc := #caller_location) -> Temp_Allocator {
+	assert(len(collisions) <= MAX_TEMP_ARENA_COLLISIONS, "Maximum collision count exceeded. MAX_TEMP_ARENA_COUNT must be increased!")
+	good_arena: ^runtime.Arena
+	for i in 0..<MAX_TEMP_ARENA_COUNT {
+		good_arena = &global_default_temp_allocator_arenas[i]
+		for c in collisions {
+			if good_arena == c.data {
+				good_arena = nil
+			}
+		}
+		if good_arena != nil {
+			break
+		}
+	}
+	assert(good_arena != nil)
+	if good_arena.backing_allocator.procedure == nil {
+		good_arena.backing_allocator = heap_allocator()
+	}
+	tmp := runtime.arena_temp_begin(good_arena, loc)
+	return { good_arena, runtime.arena_allocator(good_arena), tmp, loc }
+}
+
+temp_allocator_begin :: runtime.arena_temp_begin
+temp_allocator_end :: runtime.arena_temp_end
+@(deferred_out=_temp_allocator_end)
+temp_allocator_scope :: proc(tmp: Temp_Allocator) -> (runtime.Arena_Temp) {
+	return temp_allocator_begin(tmp.arena)
+}
+@(private="file")
+_temp_allocator_end :: proc(tmp: runtime.Arena_Temp) {
+	temp_allocator_end(tmp)
 }
 }
 
 
 @(init, private)
 @(init, private)

+ 44 - 17
core/os/os2/dir.odin

@@ -2,6 +2,7 @@ package os2
 
 
 import "base:runtime"
 import "base:runtime"
 import "core:slice"
 import "core:slice"
+import "core:strings"
 
 
 read_dir :: read_directory
 read_dir :: read_directory
 
 
@@ -18,12 +19,12 @@ read_directory :: proc(f: ^File, n: int, allocator: runtime.Allocator) -> (files
 		size = 100
 		size = 100
 	}
 	}
 
 
-	TEMP_ALLOCATOR_GUARD()
+	temp_allocator := TEMP_ALLOCATOR_GUARD({ allocator })
 
 
 	it := read_directory_iterator_create(f)
 	it := read_directory_iterator_create(f)
 	defer _read_directory_iterator_destroy(&it)
 	defer _read_directory_iterator_destroy(&it)
 
 
-	dfi := make([dynamic]File_Info, 0, size, temp_allocator())
+	dfi := make([dynamic]File_Info, 0, size, temp_allocator)
 	defer if err != nil {
 	defer if err != nil {
 		for fi in dfi {
 		for fi in dfi {
 			file_info_delete(fi, allocator)
 			file_info_delete(fi, allocator)
@@ -194,28 +195,54 @@ read_directory_iterator :: proc(it: ^Read_Directory_Iterator) -> (fi: File_Info,
 }
 }
 
 
 // Recursively copies a directory to `dst` from `src`
 // Recursively copies a directory to `dst` from `src`
-copy_directory :: proc(dst, src: string, dst_perm := 0o755) -> Error {
-	switch err := make_directory_all(dst, dst_perm); err {
-	case nil, .Exist:
-		// okay
-	case:
+copy_directory_all :: proc(dst, src: string, dst_perm := 0o755) -> Error {
+	when #defined(_copy_directory_all_native) {
+		return _copy_directory_all_native(dst, src, dst_perm)
+	} else {
+		return _copy_directory_all(dst, src, dst_perm)
+	}
+}
+
+@(private)
+_copy_directory_all :: proc(dst, src: string, dst_perm := 0o755) -> Error {
+	err := make_directory(dst, dst_perm)
+	if err != nil && err != .Exist {
 		return err
 		return err
 	}
 	}
 
 
-	TEMP_ALLOCATOR_GUARD()
+	temp_allocator := TEMP_ALLOCATOR_GUARD({})
+
+	abs_src := get_absolute_path(src, temp_allocator) or_return
+	abs_dst := get_absolute_path(dst, temp_allocator) or_return
+
+	dst_buf := make([dynamic]byte, 0, len(abs_dst) + 256, temp_allocator) or_return
 
 
-	file_infos := read_all_directory_by_path(src, temp_allocator()) or_return
-	for fi in file_infos {
-		TEMP_ALLOCATOR_GUARD()
+	w: Walker
+	walker_init_path(&w, src)
+	defer walker_destroy(&w)
 
 
-		dst_path := join_path({dst, fi.name}, temp_allocator()) or_return
-		src_path := fi.fullpath
+	for info in walker_walk(&w) {
+		_ = walker_error(&w) or_break
 
 
-		if fi.type == .Directory {
-			copy_directory(dst_path, src_path) or_return
+		rel := strings.trim_prefix(info.fullpath, abs_src)
+
+		non_zero_resize(&dst_buf, 0)
+		reserve(&dst_buf, len(abs_dst) + len(Path_Separator_String) + len(rel)) or_return
+		append(&dst_buf, abs_dst)
+		append(&dst_buf, Path_Separator_String)
+		append(&dst_buf, rel)
+
+		if info.type == .Directory {
+			err = make_directory(string(dst_buf[:]), dst_perm)
+			if err != nil && err != .Exist {
+				return err
+			}
 		} else {
 		} else {
-			copy_file(dst_path, src_path) or_return
+			copy_file(string(dst_buf[:]), info.fullpath) or_return
 		}
 		}
 	}
 	}
+
+	_ = walker_error(&w) or_return
+
 	return nil
 	return nil
-}
+}

+ 2 - 1
core/os/os2/dir_linux.odin

@@ -78,7 +78,8 @@ _read_directory_iterator :: proc(it: ^Read_Directory_Iterator) -> (fi: File_Info
 	it.impl.prev_fi = fi
 	it.impl.prev_fi = fi
 
 
 	if err != nil {
 	if err != nil {
-		path, _ := _get_full_path(entry_fd, temp_allocator())
+		temp_allocator := TEMP_ALLOCATOR_GUARD({})
+		path, _ := _get_full_path(entry_fd, temp_allocator)
 		read_directory_iterator_set_error(it, path, err)
 		read_directory_iterator_set_error(it, path, err)
 	}
 	}
 
 

+ 17 - 0
core/os/os2/dir_posix_darwin.odin

@@ -0,0 +1,17 @@
+#+private
+package os2
+
+import "core:sys/darwin"
+
+_copy_directory_all_native :: proc(dst, src: string, dst_perm := 0o755) -> (err: Error) {
+	temp_allocator := TEMP_ALLOCATOR_GUARD({})
+
+	csrc := clone_to_cstring(src, temp_allocator) or_return
+	cdst := clone_to_cstring(dst, temp_allocator) or_return
+
+	if darwin.copyfile(csrc, cdst, nil, darwin.COPYFILE_ALL + {.RECURSIVE}) < 0 {
+		err = _get_platform_error()
+	}
+
+	return
+}

+ 5 - 5
core/os/os2/dir_windows.odin

@@ -14,7 +14,9 @@ find_data_to_file_info :: proc(base_path: string, d: ^win32.WIN32_FIND_DATAW, al
 	if d.cFileName[0] == '.' && d.cFileName[1] == '.' && d.cFileName[2] == 0 {
 	if d.cFileName[0] == '.' && d.cFileName[1] == '.' && d.cFileName[2] == 0 {
 		return
 		return
 	}
 	}
-	path := concatenate({base_path, `\`, win32_utf16_to_utf8(d.cFileName[:], temp_allocator()) or_else ""}, allocator) or_return
+
+	temp_allocator := TEMP_ALLOCATOR_GUARD({ allocator })
+	path := concatenate({base_path, `\`, win32_wstring_to_utf8(raw_data(d.cFileName[:]), temp_allocator) or_else ""}, allocator) or_return
 
 
 	handle := win32.HANDLE(_open_internal(path, {.Read}, 0o666) or_else 0)
 	handle := win32.HANDLE(_open_internal(path, {.Read}, 0o666) or_else 0)
 	defer win32.CloseHandle(handle)
 	defer win32.CloseHandle(handle)
@@ -49,8 +51,6 @@ Read_Directory_Iterator_Impl :: struct {
 
 
 @(require_results)
 @(require_results)
 _read_directory_iterator :: proc(it: ^Read_Directory_Iterator) -> (fi: File_Info, index: int, ok: bool) {
 _read_directory_iterator :: proc(it: ^Read_Directory_Iterator) -> (fi: File_Info, index: int, ok: bool) {
-	TEMP_ALLOCATOR_GUARD()
-
 	for !it.impl.no_more_files {
 	for !it.impl.no_more_files {
 		err: Error
 		err: Error
 		file_info_delete(it.impl.prev_fi, file_allocator())
 		file_info_delete(it.impl.prev_fi, file_allocator())
@@ -116,9 +116,9 @@ _read_directory_iterator_init :: proc(it: ^Read_Directory_Iterator, f: ^File) {
 		wpath = impl.wname[:i]
 		wpath = impl.wname[:i]
 	}
 	}
 
 
-	TEMP_ALLOCATOR_GUARD()
+	temp_allocator := TEMP_ALLOCATOR_GUARD({})
 
 
-	wpath_search := make([]u16, len(wpath)+3, temp_allocator())
+	wpath_search := make([]u16, len(wpath)+3, temp_allocator)
 	copy(wpath_search, wpath)
 	copy(wpath_search, wpath)
 	wpath_search[len(wpath)+0] = '\\'
 	wpath_search[len(wpath)+0] = '\\'
 	wpath_search[len(wpath)+1] = '*'
 	wpath_search[len(wpath)+1] = '*'

+ 7 - 7
core/os/os2/env_posix.odin

@@ -12,9 +12,9 @@ _lookup_env :: proc(key: string, allocator: runtime.Allocator) -> (value: string
 		return
 		return
 	}
 	}
 
 
-	TEMP_ALLOCATOR_GUARD()
+	temp_allocator := TEMP_ALLOCATOR_GUARD({ allocator })
 
 
-	ckey := strings.clone_to_cstring(key, temp_allocator())
+	ckey := strings.clone_to_cstring(key, temp_allocator)
 	cval := posix.getenv(ckey)
 	cval := posix.getenv(ckey)
 	if cval == nil {
 	if cval == nil {
 		return
 		return
@@ -27,10 +27,10 @@ _lookup_env :: proc(key: string, allocator: runtime.Allocator) -> (value: string
 }
 }
 
 
 _set_env :: proc(key, value: string) -> (err: Error) {
 _set_env :: proc(key, value: string) -> (err: Error) {
-	TEMP_ALLOCATOR_GUARD()
+	temp_allocator := TEMP_ALLOCATOR_GUARD({})
 
 
-	ckey := strings.clone_to_cstring(key, temp_allocator()) or_return
-	cval := strings.clone_to_cstring(key, temp_allocator()) or_return
+	ckey := strings.clone_to_cstring(key, temp_allocator) or_return
+	cval := strings.clone_to_cstring(value, temp_allocator) or_return
 
 
 	if posix.setenv(ckey, cval, true) != nil {
 	if posix.setenv(ckey, cval, true) != nil {
 		err = _get_platform_error_from_errno()
 		err = _get_platform_error_from_errno()
@@ -39,9 +39,9 @@ _set_env :: proc(key, value: string) -> (err: Error) {
 }
 }
 
 
 _unset_env :: proc(key: string) -> (ok: bool) {
 _unset_env :: proc(key: string) -> (ok: bool) {
-	TEMP_ALLOCATOR_GUARD()
+	temp_allocator := TEMP_ALLOCATOR_GUARD({})
 
 
-	ckey := strings.clone_to_cstring(key, temp_allocator())
+	ckey := strings.clone_to_cstring(key, temp_allocator)
 
 
 	ok = posix.unsetenv(ckey) == .OK
 	ok = posix.unsetenv(ckey) == .OK
 	return
 	return

+ 2 - 2
core/os/os2/env_wasi.odin

@@ -39,9 +39,9 @@ build_env :: proc() -> (err: Error) {
 	g_env_buf = make([]byte, size_of_envs, file_allocator()) or_return
 	g_env_buf = make([]byte, size_of_envs, file_allocator()) or_return
 	defer if err != nil { delete(g_env_buf, file_allocator()) }
 	defer if err != nil { delete(g_env_buf, file_allocator()) }
 
 
-	TEMP_ALLOCATOR_GUARD()
+	temp_allocator := TEMP_ALLOCATOR_GUARD({})
 
 
-	envs := make([]cstring, num_envs, temp_allocator()) or_return
+	envs := make([]cstring, num_envs, temp_allocator) or_return
 
 
 	_err = wasi.environ_get(raw_data(envs), raw_data(g_env_buf))
 	_err = wasi.environ_get(raw_data(envs), raw_data(g_env_buf))
 	if _err != nil {
 	if _err != nil {

+ 10 - 10
core/os/os2/env_windows.odin

@@ -8,8 +8,8 @@ _lookup_env :: proc(key: string, allocator: runtime.Allocator) -> (value: string
 	if key == "" {
 	if key == "" {
 		return
 		return
 	}
 	}
-	TEMP_ALLOCATOR_GUARD()
-	wkey, _ := win32_utf8_to_wstring(key, temp_allocator())
+	temp_allocator := TEMP_ALLOCATOR_GUARD({ allocator })
+	wkey, _ := win32_utf8_to_wstring(key, temp_allocator)
 
 
 	n := win32.GetEnvironmentVariableW(wkey, nil, 0)
 	n := win32.GetEnvironmentVariableW(wkey, nil, 0)
 	if n == 0 {
 	if n == 0 {
@@ -20,7 +20,7 @@ _lookup_env :: proc(key: string, allocator: runtime.Allocator) -> (value: string
 		return "", true
 		return "", true
 	}
 	}
 
 
-	b := make([]u16, n+1, temp_allocator())
+	b := make([]u16, n+1, temp_allocator)
 
 
 	n = win32.GetEnvironmentVariableW(wkey, raw_data(b), u32(len(b)))
 	n = win32.GetEnvironmentVariableW(wkey, raw_data(b), u32(len(b)))
 	if n == 0 {
 	if n == 0 {
@@ -37,9 +37,9 @@ _lookup_env :: proc(key: string, allocator: runtime.Allocator) -> (value: string
 }
 }
 
 
 _set_env :: proc(key, value: string) -> Error {
 _set_env :: proc(key, value: string) -> Error {
-	TEMP_ALLOCATOR_GUARD()
-	k := win32_utf8_to_wstring(key,   temp_allocator()) or_return
-	v := win32_utf8_to_wstring(value, temp_allocator()) or_return
+	temp_allocator := TEMP_ALLOCATOR_GUARD({})
+	k := win32_utf8_to_wstring(key,   temp_allocator) or_return
+	v := win32_utf8_to_wstring(value, temp_allocator) or_return
 
 
 	if !win32.SetEnvironmentVariableW(k, v) {
 	if !win32.SetEnvironmentVariableW(k, v) {
 		return _get_platform_error()
 		return _get_platform_error()
@@ -48,14 +48,14 @@ _set_env :: proc(key, value: string) -> Error {
 }
 }
 
 
 _unset_env :: proc(key: string) -> bool {
 _unset_env :: proc(key: string) -> bool {
-	TEMP_ALLOCATOR_GUARD()
-	k, _ := win32_utf8_to_wstring(key, temp_allocator())
+	temp_allocator := TEMP_ALLOCATOR_GUARD({})
+	k, _ := win32_utf8_to_wstring(key, temp_allocator)
 	return bool(win32.SetEnvironmentVariableW(k, nil))
 	return bool(win32.SetEnvironmentVariableW(k, nil))
 }
 }
 
 
 _clear_env :: proc() {
 _clear_env :: proc() {
-	TEMP_ALLOCATOR_GUARD()
-	envs, _ := environ(temp_allocator())
+	temp_allocator := TEMP_ALLOCATOR_GUARD({})
+	envs, _ := environ(temp_allocator)
 	for env in envs {
 	for env in envs {
 		for j in 1..<len(env) {
 		for j in 1..<len(env) {
 			if env[j] == '=' {
 			if env[j] == '=' {

+ 21 - 16
core/os/os2/errors.odin

@@ -27,6 +27,9 @@ General_Error :: enum u32 {
 
 
 	Pattern_Has_Separator,
 	Pattern_Has_Separator,
 
 
+	No_HOME_Variable,
+	Wordexp_Failed,
+
 	Unsupported,
 	Unsupported,
 }
 }
 
 
@@ -59,20 +62,22 @@ error_string :: proc(ferr: Error) -> string {
 	case General_Error:
 	case General_Error:
 		switch e {
 		switch e {
 		case .None: return ""
 		case .None: return ""
-		case .Permission_Denied: return "permission denied"
-		case .Exist:             return "file already exists"
-		case .Not_Exist:         return "file does not exist"
-		case .Closed:            return "file already closed"
-		case .Timeout:           return "i/o timeout"
-		case .Broken_Pipe:       return "Broken pipe"
-		case .No_Size:           return "file has no definite size"
-		case .Invalid_File:      return "invalid file"
-		case .Invalid_Dir:       return "invalid directory"
-		case .Invalid_Path:      return "invalid path"
-		case .Invalid_Callback:  return "invalid callback"
-		case .Invalid_Command:   return "invalid command"
-		case .Unsupported:       return "unsupported"
-		case .Pattern_Has_Separator: return "pattern has separator"
+		case .Permission_Denied:      return "permission denied"
+		case .Exist:                  return "file already exists"
+		case .Not_Exist:              return "file does not exist"
+		case .Closed:                 return "file already closed"
+		case .Timeout:                return "i/o timeout"
+		case .Broken_Pipe:            return "Broken pipe"
+		case .No_Size:                return "file has no definite size"
+		case .Invalid_File:           return "invalid file"
+		case .Invalid_Dir:            return "invalid directory"
+		case .Invalid_Path:           return "invalid path"
+		case .Invalid_Callback:       return "invalid callback"
+		case .Invalid_Command:        return "invalid command"
+		case .Unsupported:            return "unsupported"
+		case .Pattern_Has_Separator:  return "pattern has separator"
+		case .No_HOME_Variable:       return "no $HOME variable"
+		case .Wordexp_Failed:         return "posix.wordexp was unable to expand"
 		}
 		}
 	case io.Error:
 	case io.Error:
 		switch e {
 		switch e {
@@ -108,12 +113,12 @@ error_string :: proc(ferr: Error) -> string {
 }
 }
 
 
 print_error :: proc(f: ^File, ferr: Error, msg: string) {
 print_error :: proc(f: ^File, ferr: Error, msg: string) {
-	TEMP_ALLOCATOR_GUARD()
+	temp_allocator := TEMP_ALLOCATOR_GUARD({})
 	err_str := error_string(ferr)
 	err_str := error_string(ferr)
 
 
 	// msg + ": " + err_str + '\n'
 	// msg + ": " + err_str + '\n'
 	length := len(msg) + 2 + len(err_str) + 1
 	length := len(msg) + 2 + len(err_str) + 1
-	buf := make([]u8, length, temp_allocator())
+	buf := make([]u8, length, temp_allocator)
 
 
 	copy(buf, msg)
 	copy(buf, msg)
 	buf[len(msg)] = ':'
 	buf[len(msg)] = ':'

+ 13 - 4
core/os/os2/file.odin

@@ -291,8 +291,8 @@ exists :: proc(path: string) -> bool {
 
 
 @(require_results)
 @(require_results)
 is_file :: proc(path: string) -> bool {
 is_file :: proc(path: string) -> bool {
-	TEMP_ALLOCATOR_GUARD()
-	fi, err := stat(path, temp_allocator())
+	temp_allocator := TEMP_ALLOCATOR_GUARD({})
+	fi, err := stat(path, temp_allocator)
 	if err != nil {
 	if err != nil {
 		return false
 		return false
 	}
 	}
@@ -303,8 +303,8 @@ is_dir :: is_directory
 
 
 @(require_results)
 @(require_results)
 is_directory :: proc(path: string) -> bool {
 is_directory :: proc(path: string) -> bool {
-	TEMP_ALLOCATOR_GUARD()
-	fi, err := stat(path, temp_allocator())
+	temp_allocator := TEMP_ALLOCATOR_GUARD({})
+	fi, err := stat(path, temp_allocator)
 	if err != nil {
 	if err != nil {
 		return false
 		return false
 	}
 	}
@@ -313,6 +313,15 @@ is_directory :: proc(path: string) -> bool {
 
 
 
 
 copy_file :: proc(dst_path, src_path: string) -> Error {
 copy_file :: proc(dst_path, src_path: string) -> Error {
+	when #defined(_copy_file_native) {
+		return _copy_file_native(dst_path, src_path)
+	} else {
+		return _copy_file(dst_path, src_path)
+	}
+}
+
+@(private)
+_copy_file :: proc(dst_path, src_path: string) -> Error {
 	src := open(src_path) or_return
 	src := open(src_path) or_return
 	defer close(src)
 	defer close(src)
 
 

+ 29 - 29
core/os/os2/file_linux.odin

@@ -66,8 +66,8 @@ _standard_stream_init :: proc() {
 }
 }
 
 
 _open :: proc(name: string, flags: File_Flags, perm: int) -> (f: ^File, err: Error) {
 _open :: proc(name: string, flags: File_Flags, perm: int) -> (f: ^File, err: Error) {
-	TEMP_ALLOCATOR_GUARD()
-	name_cstr := temp_cstring(name) or_return
+	temp_allocator := TEMP_ALLOCATOR_GUARD({})
+	name_cstr := clone_to_cstring(name, temp_allocator) or_return
 
 
 	// Just default to using O_NOCTTY because needing to open a controlling
 	// Just default to using O_NOCTTY because needing to open a controlling
 	// terminal would be incredibly rare. This has no effect on files while
 	// terminal would be incredibly rare. This has no effect on files while
@@ -299,8 +299,8 @@ _truncate :: proc(f: ^File, size: i64) -> Error {
 }
 }
 
 
 _remove :: proc(name: string) -> Error {
 _remove :: proc(name: string) -> Error {
-	TEMP_ALLOCATOR_GUARD()
-	name_cstr := temp_cstring(name) or_return
+	temp_allocator := TEMP_ALLOCATOR_GUARD({})
+	name_cstr := clone_to_cstring(name, temp_allocator) or_return
 
 
 	if fd, errno := linux.open(name_cstr, _OPENDIR_FLAGS + {.NOFOLLOW}); errno == .NONE {
 	if fd, errno := linux.open(name_cstr, _OPENDIR_FLAGS + {.NOFOLLOW}); errno == .NONE {
 		linux.close(fd)
 		linux.close(fd)
@@ -311,25 +311,25 @@ _remove :: proc(name: string) -> Error {
 }
 }
 
 
 _rename :: proc(old_name, new_name: string) -> Error {
 _rename :: proc(old_name, new_name: string) -> Error {
-	TEMP_ALLOCATOR_GUARD()
-	old_name_cstr := temp_cstring(old_name) or_return
-	new_name_cstr := temp_cstring(new_name) or_return
+	temp_allocator := TEMP_ALLOCATOR_GUARD({})
+	old_name_cstr := clone_to_cstring(old_name, temp_allocator) or_return
+	new_name_cstr := clone_to_cstring(new_name, temp_allocator) or_return
 
 
 	return _get_platform_error(linux.rename(old_name_cstr, new_name_cstr))
 	return _get_platform_error(linux.rename(old_name_cstr, new_name_cstr))
 }
 }
 
 
 _link :: proc(old_name, new_name: string) -> Error {
 _link :: proc(old_name, new_name: string) -> Error {
-	TEMP_ALLOCATOR_GUARD()
-	old_name_cstr := temp_cstring(old_name) or_return
-	new_name_cstr := temp_cstring(new_name) or_return
+	temp_allocator := TEMP_ALLOCATOR_GUARD({})
+	old_name_cstr := clone_to_cstring(old_name, temp_allocator) or_return
+	new_name_cstr := clone_to_cstring(new_name, temp_allocator) or_return
 
 
 	return _get_platform_error(linux.link(old_name_cstr, new_name_cstr))
 	return _get_platform_error(linux.link(old_name_cstr, new_name_cstr))
 }
 }
 
 
 _symlink :: proc(old_name, new_name: string) -> Error {
 _symlink :: proc(old_name, new_name: string) -> Error {
-	TEMP_ALLOCATOR_GUARD()
-	old_name_cstr := temp_cstring(old_name) or_return
-	new_name_cstr := temp_cstring(new_name) or_return
+	temp_allocator := TEMP_ALLOCATOR_GUARD({})
+	old_name_cstr := clone_to_cstring(old_name, temp_allocator) or_return
+	new_name_cstr := clone_to_cstring(new_name, temp_allocator) or_return
 	return _get_platform_error(linux.symlink(old_name_cstr, new_name_cstr))
 	return _get_platform_error(linux.symlink(old_name_cstr, new_name_cstr))
 }
 }
 
 
@@ -352,14 +352,14 @@ _read_link_cstr :: proc(name_cstr: cstring, allocator: runtime.Allocator) -> (st
 }
 }
 
 
 _read_link :: proc(name: string, allocator: runtime.Allocator) -> (s: string, e: Error) {
 _read_link :: proc(name: string, allocator: runtime.Allocator) -> (s: string, e: Error) {
-	TEMP_ALLOCATOR_GUARD()
-	name_cstr := temp_cstring(name) or_return
+	temp_allocator := TEMP_ALLOCATOR_GUARD({ allocator })
+	name_cstr := clone_to_cstring(name, temp_allocator) or_return
 	return _read_link_cstr(name_cstr, allocator)
 	return _read_link_cstr(name_cstr, allocator)
 }
 }
 
 
 _chdir :: proc(name: string) -> Error {
 _chdir :: proc(name: string) -> Error {
-	TEMP_ALLOCATOR_GUARD()
-	name_cstr := temp_cstring(name) or_return
+	temp_allocator := TEMP_ALLOCATOR_GUARD({})
+	name_cstr := clone_to_cstring(name, temp_allocator) or_return
 	return _get_platform_error(linux.chdir(name_cstr))
 	return _get_platform_error(linux.chdir(name_cstr))
 }
 }
 
 
@@ -369,8 +369,8 @@ _fchdir :: proc(f: ^File) -> Error {
 }
 }
 
 
 _chmod :: proc(name: string, mode: int) -> Error {
 _chmod :: proc(name: string, mode: int) -> Error {
-	TEMP_ALLOCATOR_GUARD()
-	name_cstr := temp_cstring(name) or_return
+	temp_allocator := TEMP_ALLOCATOR_GUARD({})
+	name_cstr := clone_to_cstring(name, temp_allocator) or_return
 	return _get_platform_error(linux.chmod(name_cstr, transmute(linux.Mode)(u32(mode))))
 	return _get_platform_error(linux.chmod(name_cstr, transmute(linux.Mode)(u32(mode))))
 }
 }
 
 
@@ -381,15 +381,15 @@ _fchmod :: proc(f: ^File, mode: int) -> Error {
 
 
 // NOTE: will throw error without super user priviledges
 // NOTE: will throw error without super user priviledges
 _chown :: proc(name: string, uid, gid: int) -> Error {
 _chown :: proc(name: string, uid, gid: int) -> Error {
-	TEMP_ALLOCATOR_GUARD()
-	name_cstr := temp_cstring(name) or_return
+	temp_allocator := TEMP_ALLOCATOR_GUARD({})
+	name_cstr := clone_to_cstring(name, temp_allocator) or_return
 	return _get_platform_error(linux.chown(name_cstr, linux.Uid(uid), linux.Gid(gid)))
 	return _get_platform_error(linux.chown(name_cstr, linux.Uid(uid), linux.Gid(gid)))
 }
 }
 
 
 // NOTE: will throw error without super user priviledges
 // NOTE: will throw error without super user priviledges
 _lchown :: proc(name: string, uid, gid: int) -> Error {
 _lchown :: proc(name: string, uid, gid: int) -> Error {
-	TEMP_ALLOCATOR_GUARD()
-	name_cstr := temp_cstring(name) or_return
+	temp_allocator := TEMP_ALLOCATOR_GUARD({})
+	name_cstr := clone_to_cstring(name, temp_allocator) or_return
 	return _get_platform_error(linux.lchown(name_cstr, linux.Uid(uid), linux.Gid(gid)))
 	return _get_platform_error(linux.lchown(name_cstr, linux.Uid(uid), linux.Gid(gid)))
 }
 }
 
 
@@ -400,8 +400,8 @@ _fchown :: proc(f: ^File, uid, gid: int) -> Error {
 }
 }
 
 
 _chtimes :: proc(name: string, atime, mtime: time.Time) -> Error {
 _chtimes :: proc(name: string, atime, mtime: time.Time) -> Error {
-	TEMP_ALLOCATOR_GUARD()
-	name_cstr := temp_cstring(name) or_return
+	temp_allocator := TEMP_ALLOCATOR_GUARD({})
+	name_cstr := clone_to_cstring(name, temp_allocator) or_return
 	times := [2]linux.Time_Spec {
 	times := [2]linux.Time_Spec {
 		{
 		{
 			uint(atime._nsec) / uint(time.Second),
 			uint(atime._nsec) / uint(time.Second),
@@ -431,8 +431,8 @@ _fchtimes :: proc(f: ^File, atime, mtime: time.Time) -> Error {
 }
 }
 
 
 _exists :: proc(name: string) -> bool {
 _exists :: proc(name: string) -> bool {
-	TEMP_ALLOCATOR_GUARD()
-	name_cstr, _ := temp_cstring(name)
+	temp_allocator := TEMP_ALLOCATOR_GUARD({})
+	name_cstr, _ := clone_to_cstring(name, temp_allocator)
 	return linux.access(name_cstr, linux.F_OK) == .NONE
 	return linux.access(name_cstr, linux.F_OK) == .NONE
 }
 }
 
 
@@ -440,8 +440,8 @@ _exists :: proc(name: string) -> bool {
 _read_entire_pseudo_file :: proc { _read_entire_pseudo_file_string, _read_entire_pseudo_file_cstring }
 _read_entire_pseudo_file :: proc { _read_entire_pseudo_file_string, _read_entire_pseudo_file_cstring }
 
 
 _read_entire_pseudo_file_string :: proc(name: string, allocator: runtime.Allocator) -> (b: []u8, e: Error) {
 _read_entire_pseudo_file_string :: proc(name: string, allocator: runtime.Allocator) -> (b: []u8, e: Error) {
-	TEMP_ALLOCATOR_GUARD()
-	name_cstr := clone_to_cstring(name, temp_allocator()) or_return
+	temp_allocator := TEMP_ALLOCATOR_GUARD({ allocator })
+	name_cstr := clone_to_cstring(name, temp_allocator) or_return
 	return _read_entire_pseudo_file_cstring(name_cstr, allocator)
 	return _read_entire_pseudo_file_cstring(name_cstr, allocator)
 }
 }
 
 

+ 36 - 35
core/os/os2/file_posix.odin

@@ -69,8 +69,8 @@ _open :: proc(name: string, flags: File_Flags, perm: int) -> (f: ^File, err: Err
 	if .Trunc       in flags { sys_flags += {.TRUNC} }
 	if .Trunc       in flags { sys_flags += {.TRUNC} }
 	if .Inheritable in flags { sys_flags -= {.CLOEXEC} }
 	if .Inheritable in flags { sys_flags -= {.CLOEXEC} }
 
 
-	TEMP_ALLOCATOR_GUARD()
-	cname := temp_cstring(name)
+	temp_allocator := TEMP_ALLOCATOR_GUARD({})
+	cname := clone_to_cstring(name, temp_allocator) or_return
 
 
 	fd := posix.open(cname, sys_flags, transmute(posix.mode_t)posix._mode_t(perm))
 	fd := posix.open(cname, sys_flags, transmute(posix.mode_t)posix._mode_t(perm))
 	if fd < 0 {
 	if fd < 0 {
@@ -183,39 +183,39 @@ _truncate :: proc(f: ^File, size: i64) -> Error {
 	return nil
 	return nil
 }
 }
 
 
-_remove :: proc(name: string) -> Error {
-	TEMP_ALLOCATOR_GUARD()
-	cname := temp_cstring(name)
+_remove :: proc(name: string) -> (err: Error) {
+	temp_allocator := TEMP_ALLOCATOR_GUARD({})
+	cname := clone_to_cstring(name, temp_allocator) or_return
 	if posix.remove(cname) != 0 {
 	if posix.remove(cname) != 0 {
 		return _get_platform_error()
 		return _get_platform_error()
 	}
 	}
 	return nil
 	return nil
 }
 }
 
 
-_rename :: proc(old_path, new_path: string) -> Error {
-	TEMP_ALLOCATOR_GUARD()
-	cold := temp_cstring(old_path)
-	cnew := temp_cstring(new_path)
+_rename :: proc(old_path, new_path: string) -> (err: Error) {
+	temp_allocator := TEMP_ALLOCATOR_GUARD({})
+	cold := clone_to_cstring(old_path, temp_allocator) or_return
+	cnew := clone_to_cstring(new_path, temp_allocator) or_return
 	if posix.rename(cold, cnew) != 0 {
 	if posix.rename(cold, cnew) != 0 {
 		return _get_platform_error()
 		return _get_platform_error()
 	}
 	}
 	return nil
 	return nil
 }
 }
 
 
-_link :: proc(old_name, new_name: string) -> Error {
-	TEMP_ALLOCATOR_GUARD()
-	cold := temp_cstring(old_name)
-	cnew := temp_cstring(new_name)
+_link :: proc(old_name, new_name: string) -> (err: Error) {
+	temp_allocator := TEMP_ALLOCATOR_GUARD({})
+	cold := clone_to_cstring(old_name, temp_allocator) or_return
+	cnew := clone_to_cstring(new_name, temp_allocator) or_return
 	if posix.link(cold, cnew) != .OK {
 	if posix.link(cold, cnew) != .OK {
 		return _get_platform_error()
 		return _get_platform_error()
 	}
 	}
 	return nil
 	return nil
 }
 }
 
 
-_symlink :: proc(old_name, new_name: string) -> Error {
-	TEMP_ALLOCATOR_GUARD()
-	cold := temp_cstring(old_name)
-	cnew := temp_cstring(new_name)
+_symlink :: proc(old_name, new_name: string) -> (err: Error) {
+	temp_allocator := TEMP_ALLOCATOR_GUARD({})
+	cold := clone_to_cstring(old_name, temp_allocator) or_return
+	cnew := clone_to_cstring(new_name, temp_allocator) or_return
 	if posix.symlink(cold, cnew) != .OK {
 	if posix.symlink(cold, cnew) != .OK {
 		return _get_platform_error()
 		return _get_platform_error()
 	}
 	}
@@ -223,8 +223,8 @@ _symlink :: proc(old_name, new_name: string) -> Error {
 }
 }
 
 
 _read_link :: proc(name: string, allocator: runtime.Allocator) -> (s: string, err: Error) {
 _read_link :: proc(name: string, allocator: runtime.Allocator) -> (s: string, err: Error) {
-	TEMP_ALLOCATOR_GUARD()
-	cname := temp_cstring(name)
+	temp_allocator := TEMP_ALLOCATOR_GUARD({ allocator })
+	cname := clone_to_cstring(name, temp_allocator) or_return
 
 
 	buf: [dynamic]byte
 	buf: [dynamic]byte
 	buf.allocator = allocator
 	buf.allocator = allocator
@@ -268,9 +268,9 @@ _read_link :: proc(name: string, allocator: runtime.Allocator) -> (s: string, er
 	}
 	}
 }
 }
 
 
-_chdir :: proc(name: string) -> Error {
-	TEMP_ALLOCATOR_GUARD()
-	cname := temp_cstring(name)
+_chdir :: proc(name: string) -> (err: Error) {
+	temp_allocator := TEMP_ALLOCATOR_GUARD({})
+	cname := clone_to_cstring(name, temp_allocator) or_return
 	if posix.chdir(cname) != .OK {
 	if posix.chdir(cname) != .OK {
 		return _get_platform_error()
 		return _get_platform_error()
 	}
 	}
@@ -291,9 +291,9 @@ _fchmod :: proc(f: ^File, mode: int) -> Error {
 	return nil
 	return nil
 }
 }
 
 
-_chmod :: proc(name: string, mode: int) -> Error {
-	TEMP_ALLOCATOR_GUARD()
-	cname := temp_cstring(name)
+_chmod :: proc(name: string, mode: int) -> (err: Error) {
+	temp_allocator := TEMP_ALLOCATOR_GUARD({})
+	cname := clone_to_cstring(name, temp_allocator) or_return
 	if posix.chmod(cname, transmute(posix.mode_t)posix._mode_t(mode)) != .OK {
 	if posix.chmod(cname, transmute(posix.mode_t)posix._mode_t(mode)) != .OK {
 		return _get_platform_error()
 		return _get_platform_error()
 	}
 	}
@@ -307,9 +307,9 @@ _fchown :: proc(f: ^File, uid, gid: int) -> Error {
 	return nil
 	return nil
 }
 }
 
 
-_chown :: proc(name: string, uid, gid: int) -> Error {
-	TEMP_ALLOCATOR_GUARD()
-	cname := temp_cstring(name)
+_chown :: proc(name: string, uid, gid: int) -> (err: Error) {
+	temp_allocator := TEMP_ALLOCATOR_GUARD({})
+	cname := clone_to_cstring(name, temp_allocator) or_return
 	if posix.chown(cname, posix.uid_t(uid), posix.gid_t(gid)) != .OK {
 	if posix.chown(cname, posix.uid_t(uid), posix.gid_t(gid)) != .OK {
 		return _get_platform_error()
 		return _get_platform_error()
 	}
 	}
@@ -317,15 +317,15 @@ _chown :: proc(name: string, uid, gid: int) -> Error {
 }
 }
 
 
 _lchown :: proc(name: string, uid, gid: int) -> Error {
 _lchown :: proc(name: string, uid, gid: int) -> Error {
-	TEMP_ALLOCATOR_GUARD()
-	cname := temp_cstring(name)
+	temp_allocator := TEMP_ALLOCATOR_GUARD({})
+	cname := clone_to_cstring(name, temp_allocator) or_return
 	if posix.lchown(cname, posix.uid_t(uid), posix.gid_t(gid)) != .OK {
 	if posix.lchown(cname, posix.uid_t(uid), posix.gid_t(gid)) != .OK {
 		return _get_platform_error()
 		return _get_platform_error()
 	}
 	}
 	return nil
 	return nil
 }
 }
 
 
-_chtimes :: proc(name: string, atime, mtime: time.Time) -> Error {
+_chtimes :: proc(name: string, atime, mtime: time.Time) -> (err: Error) {
 	times := [2]posix.timeval{
 	times := [2]posix.timeval{
 		{
 		{
 			tv_sec  = posix.time_t(atime._nsec/1e9),           /* seconds */
 			tv_sec  = posix.time_t(atime._nsec/1e9),           /* seconds */
@@ -337,8 +337,8 @@ _chtimes :: proc(name: string, atime, mtime: time.Time) -> Error {
 		},
 		},
 	}
 	}
 
 
-	TEMP_ALLOCATOR_GUARD()
-	cname := temp_cstring(name)
+	temp_allocator := TEMP_ALLOCATOR_GUARD({})
+	cname := clone_to_cstring(name, temp_allocator) or_return
 
 
 	if posix.utimes(cname, &times) != .OK {
 	if posix.utimes(cname, &times) != .OK {
 		return _get_platform_error()
 		return _get_platform_error()
@@ -365,8 +365,9 @@ _fchtimes :: proc(f: ^File, atime, mtime: time.Time) -> Error {
 }
 }
 
 
 _exists :: proc(path: string) -> bool {
 _exists :: proc(path: string) -> bool {
-	TEMP_ALLOCATOR_GUARD()
-	cpath := temp_cstring(path)
+	temp_allocator := TEMP_ALLOCATOR_GUARD({})
+	cpath, err := clone_to_cstring(path, temp_allocator)
+	if err != nil { return false }
 	return posix.access(cpath) == .OK
 	return posix.access(cpath) == .OK
 }
 }
 
 

+ 28 - 0
core/os/os2/file_posix_darwin.odin

@@ -3,6 +3,7 @@ package os2
 
 
 import "base:runtime"
 import "base:runtime"
 
 
+import "core:sys/darwin"
 import "core:sys/posix"
 import "core:sys/posix"
 
 
 _posix_absolute_path :: proc(fd: posix.FD, name: string, allocator: runtime.Allocator) -> (path: cstring, err: Error) {
 _posix_absolute_path :: proc(fd: posix.FD, name: string, allocator: runtime.Allocator) -> (path: cstring, err: Error) {
@@ -16,3 +17,30 @@ _posix_absolute_path :: proc(fd: posix.FD, name: string, allocator: runtime.Allo
 
 
 	return clone_to_cstring(string(cstring(&buf[0])), allocator)
 	return clone_to_cstring(string(cstring(&buf[0])), allocator)
 }
 }
+
+_copy_file_native :: proc(dst_path, src_path: string) -> (err: Error) {
+	temp_allocator := TEMP_ALLOCATOR_GUARD({})
+
+	csrc := clone_to_cstring(src_path, temp_allocator) or_return
+	cdst := clone_to_cstring(dst_path, temp_allocator) or_return
+
+	// Disallow directories, as specified by the generic implementation.
+
+	stat: posix.stat_t
+	if posix.stat(csrc, &stat) != .OK {
+		err = _get_platform_error()
+		return
+	}
+
+	if posix.S_ISDIR(stat.st_mode) {
+		err = .Invalid_File
+		return
+	}
+
+	ret := darwin.copyfile(csrc, cdst, nil, darwin.COPYFILE_ALL)
+	if ret < 0 {
+		err = _get_platform_error()
+	}
+
+	return
+}

+ 2 - 2
core/os/os2/file_posix_other.odin

@@ -7,8 +7,8 @@ import "base:runtime"
 import "core:sys/posix"
 import "core:sys/posix"
 
 
 _posix_absolute_path :: proc(fd: posix.FD, name: string, allocator: runtime.Allocator) -> (path: cstring, err: Error) {
 _posix_absolute_path :: proc(fd: posix.FD, name: string, allocator: runtime.Allocator) -> (path: cstring, err: Error) {
-	TEMP_ALLOCATOR_GUARD()
-	cname := temp_cstring(name)
+	temp_allocator := TEMP_ALLOCATOR_GUARD({ allocator })
+	cname := clone_to_cstring(name, temp_allocator)
 
 
 	buf: [posix.PATH_MAX]byte
 	buf: [posix.PATH_MAX]byte
 	path = posix.realpath(cname, raw_data(buf[:]))
 	path = posix.realpath(cname, raw_data(buf[:]))

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

@@ -59,7 +59,7 @@ write_encoded_rune :: proc(f: ^File, r: rune) -> (n: int, err: Error) {
 		if r < 32 {
 		if r < 32 {
 			if wrap(write_string(f, "\\x"), &n, &err) { return }
 			if wrap(write_string(f, "\\x"), &n, &err) { return }
 			b: [2]byte
 			b: [2]byte
-			s := strconv.append_bits(b[:], u64(r), 16, true, 64, strconv.digits, nil)
+			s := strconv.write_bits(b[:], u64(r), 16, true, 64, strconv.digits, nil)
 			switch len(s) {
 			switch len(s) {
 			case 0: if wrap(write_string(f, "00"), &n, &err) { return }
 			case 0: if wrap(write_string(f, "00"), &n, &err) { return }
 			case 1: if wrap(write_rune(f, '0'), &n, &err)    { return }
 			case 1: if wrap(write_rune(f, '0'), &n, &err)    { return }

+ 27 - 34
core/os/os2/file_windows.odin

@@ -86,9 +86,9 @@ _open_internal :: proc(name: string, flags: File_Flags, perm: int) -> (handle: u
 		err = .Not_Exist
 		err = .Not_Exist
 		return
 		return
 	}
 	}
-	TEMP_ALLOCATOR_GUARD()
+	temp_allocator := TEMP_ALLOCATOR_GUARD({})
 
 
-	path := _fix_long_path(name, temp_allocator()) or_return
+	path := _fix_long_path(name, temp_allocator) or_return
 	access: u32
 	access: u32
 	switch flags & {.Read, .Write} {
 	switch flags & {.Read, .Write} {
 	case {.Read}:         access = win32.FILE_GENERIC_READ
 	case {.Read}:         access = win32.FILE_GENERIC_READ
@@ -508,11 +508,12 @@ _file_size :: proc(f: ^File_Impl) -> (n: i64, err: Error) {
 	length: win32.LARGE_INTEGER
 	length: win32.LARGE_INTEGER
 	handle := _handle(&f.file)
 	handle := _handle(&f.file)
 	if f.kind == .Pipe {
 	if f.kind == .Pipe {
-		bytesAvail: u32
-		if win32.PeekNamedPipe(handle, nil, 0, nil, &bytesAvail, nil) {
-			return i64(bytesAvail), nil
+		bytes_available: u32
+		if win32.PeekNamedPipe(handle, nil, 0, nil, &bytes_available, nil) {
+			return i64(bytes_available), nil
 		} else {
 		} else {
-			return 0, .No_Size
+			err = _get_platform_error()
+			return
 		}
 		}
 	}
 	}
 	if !win32.GetFileSizeEx(handle, &length) {
 	if !win32.GetFileSizeEx(handle, &length) {
@@ -556,8 +557,8 @@ _truncate :: proc(f: ^File, size: i64) -> Error {
 }
 }
 
 
 _remove :: proc(name: string) -> Error {
 _remove :: proc(name: string) -> Error {
-	TEMP_ALLOCATOR_GUARD()
-	p := _fix_long_path(name, temp_allocator()) or_return
+	temp_allocator := TEMP_ALLOCATOR_GUARD({})
+	p := _fix_long_path(name, temp_allocator) or_return
 	err, err1: Error
 	err, err1: Error
 	if !win32.DeleteFileW(p) {
 	if !win32.DeleteFileW(p) {
 		err = _get_platform_error()
 		err = _get_platform_error()
@@ -594,9 +595,9 @@ _remove :: proc(name: string) -> Error {
 }
 }
 
 
 _rename :: proc(old_path, new_path: string) -> Error {
 _rename :: proc(old_path, new_path: string) -> Error {
-	TEMP_ALLOCATOR_GUARD()
-	from := _fix_long_path(old_path, temp_allocator()) or_return
-	to   := _fix_long_path(new_path, temp_allocator()) or_return
+	temp_allocator := TEMP_ALLOCATOR_GUARD({})
+	from := _fix_long_path(old_path, temp_allocator) or_return
+	to   := _fix_long_path(new_path, temp_allocator) or_return
 	if win32.MoveFileExW(from, to, win32.MOVEFILE_REPLACE_EXISTING) {
 	if win32.MoveFileExW(from, to, win32.MOVEFILE_REPLACE_EXISTING) {
 		return nil
 		return nil
 	}
 	}
@@ -605,9 +606,9 @@ _rename :: proc(old_path, new_path: string) -> Error {
 }
 }
 
 
 _link :: proc(old_name, new_name: string) -> Error {
 _link :: proc(old_name, new_name: string) -> Error {
-	TEMP_ALLOCATOR_GUARD()
-	o := _fix_long_path(old_name, temp_allocator()) or_return
-	n := _fix_long_path(new_name, temp_allocator()) or_return
+	temp_allocator := TEMP_ALLOCATOR_GUARD({})
+	o := _fix_long_path(old_name, temp_allocator) or_return
+	n := _fix_long_path(new_name, temp_allocator) or_return
 	if win32.CreateHardLinkW(n, o, nil) {
 	if win32.CreateHardLinkW(n, o, nil) {
 		return nil
 		return nil
 	}
 	}
@@ -668,9 +669,9 @@ _normalize_link_path :: proc(p: []u16, allocator: runtime.Allocator) -> (str: st
 		return "", _get_platform_error()
 		return "", _get_platform_error()
 	}
 	}
 
 
-	TEMP_ALLOCATOR_GUARD()
+	temp_allocator := TEMP_ALLOCATOR_GUARD({ allocator })
 
 
-	buf := make([]u16, n+1, temp_allocator())
+	buf := make([]u16, n+1, temp_allocator)
 	n = win32.GetFinalPathNameByHandleW(handle, raw_data(buf), u32(len(buf)), win32.VOLUME_NAME_DOS)
 	n = win32.GetFinalPathNameByHandleW(handle, raw_data(buf), u32(len(buf)), win32.VOLUME_NAME_DOS)
 	if n == 0 {
 	if n == 0 {
 		return "", _get_platform_error()
 		return "", _get_platform_error()
@@ -694,9 +695,9 @@ _read_link :: proc(name: string, allocator: runtime.Allocator) -> (s: string, er
 	@thread_local
 	@thread_local
 	rdb_buf: [MAXIMUM_REPARSE_DATA_BUFFER_SIZE]byte
 	rdb_buf: [MAXIMUM_REPARSE_DATA_BUFFER_SIZE]byte
 
 
-	TEMP_ALLOCATOR_GUARD()
+	temp_allocator := TEMP_ALLOCATOR_GUARD({ allocator })
 
 
-	p      := _fix_long_path(name, temp_allocator()) or_return
+	p      := _fix_long_path(name, temp_allocator) or_return
 	handle := _open_sym_link(p) or_return
 	handle := _open_sym_link(p) or_return
 	defer win32.CloseHandle(handle)
 	defer win32.CloseHandle(handle)
 
 
@@ -771,8 +772,8 @@ _fchown :: proc(f: ^File, uid, gid: int) -> Error {
 }
 }
 
 
 _chdir :: proc(name: string) -> Error {
 _chdir :: proc(name: string) -> Error {
-	TEMP_ALLOCATOR_GUARD()
-	p := _fix_long_path(name, temp_allocator()) or_return
+	temp_allocator := TEMP_ALLOCATOR_GUARD({})
+	p := _fix_long_path(name, temp_allocator) or_return
 	if !win32.SetCurrentDirectoryW(p) {
 	if !win32.SetCurrentDirectoryW(p) {
 		return _get_platform_error()
 		return _get_platform_error()
 	}
 	}
@@ -799,19 +800,11 @@ _chtimes :: proc(name: string, atime, mtime: time.Time) -> Error {
 	defer close(f)
 	defer close(f)
 	return _fchtimes(f, atime, mtime)
 	return _fchtimes(f, atime, mtime)
 }
 }
+
 _fchtimes :: proc(f: ^File, atime, mtime: time.Time) -> Error {
 _fchtimes :: proc(f: ^File, atime, mtime: time.Time) -> Error {
 	if f == nil || f.impl == nil {
 	if f == nil || f.impl == nil {
 		return nil
 		return nil
 	}
 	}
-	d: win32.BY_HANDLE_FILE_INFORMATION
-	if !win32.GetFileInformationByHandle(_handle(f), &d) {
-		return _get_platform_error()
-	}
-
-	to_windows_time :: #force_inline proc(t: time.Time) -> win32.LARGE_INTEGER {
-		// a 64-bit value representing the number of 100-nanosecond intervals since January 1, 1601 (UTC)
-		return win32.LARGE_INTEGER(time.time_to_unix_nano(t) * 100 + 116444736000000000)
-	}
 
 
 	atime, mtime := atime, mtime
 	atime, mtime := atime, mtime
 	if time.time_to_unix_nano(atime) < time.time_to_unix_nano(mtime) {
 	if time.time_to_unix_nano(atime) < time.time_to_unix_nano(mtime) {
@@ -819,17 +812,17 @@ _fchtimes :: proc(f: ^File, atime, mtime: time.Time) -> Error {
 	}
 	}
 
 
 	info: win32.FILE_BASIC_INFO
 	info: win32.FILE_BASIC_INFO
-	info.LastAccessTime = to_windows_time(atime)
-	info.LastWriteTime  = to_windows_time(mtime)
-	if !win32.SetFileInformationByHandle(_handle(f), .FileBasicInfo, &info, size_of(d)) {
+	info.LastAccessTime = time_as_filetime(atime)
+	info.LastWriteTime  = time_as_filetime(mtime)
+	if !win32.SetFileInformationByHandle(_handle(f), .FileBasicInfo, &info, size_of(info)) {
 		return _get_platform_error()
 		return _get_platform_error()
 	}
 	}
 	return nil
 	return nil
 }
 }
 
 
 _exists :: proc(path: string) -> bool {
 _exists :: proc(path: string) -> bool {
-	TEMP_ALLOCATOR_GUARD()
-	wpath, _ := _fix_long_path(path, temp_allocator())
+	temp_allocator := TEMP_ALLOCATOR_GUARD({})
+	wpath, _ := _fix_long_path(path, temp_allocator)
 	attribs := win32.GetFileAttributesW(wpath)
 	attribs := win32.GetFileAttributesW(wpath)
 	return attribs != win32.INVALID_FILE_ATTRIBUTES
 	return attribs != win32.INVALID_FILE_ATTRIBUTES
 }
 }

+ 0 - 5
core/os/os2/internal_util.odin

@@ -43,11 +43,6 @@ clone_to_cstring :: proc(s: string, allocator: runtime.Allocator) -> (res: cstri
 	return cstring(&buf[0]), nil
 	return cstring(&buf[0]), nil
 }
 }
 
 
-@(require_results)
-temp_cstring :: proc(s: string) -> (cstring, runtime.Allocator_Error) #optional_allocator_error {
-	return clone_to_cstring(s, temp_allocator())
-}
-
 @(require_results)
 @(require_results)
 string_from_null_terminated_bytes :: proc(b: []byte) -> (res: string) {
 string_from_null_terminated_bytes :: proc(b: []byte) -> (res: string) {
 	s := string(b)
 	s := string(b)

+ 4 - 4
core/os/os2/path.odin

@@ -119,11 +119,11 @@ clean_path :: proc(path: string, allocator: runtime.Allocator) -> (cleaned: stri
 		return strings.clone(".", allocator)
 		return strings.clone(".", allocator)
 	}
 	}
 
 
-	TEMP_ALLOCATOR_GUARD()
+	temp_allocator := TEMP_ALLOCATOR_GUARD({ allocator })
 
 
 	// The extra byte is to simplify appending path elements by letting the
 	// The extra byte is to simplify appending path elements by letting the
 	// loop to end each with a separator. We'll trim the last one when we're done.
 	// loop to end each with a separator. We'll trim the last one when we're done.
-	buffer := make([]u8, len(path) + 1, temp_allocator()) or_return
+	buffer := make([]u8, len(path) + 1, temp_allocator) or_return
 
 
 	// This is the only point where Windows and POSIX differ, as Windows has
 	// This is the only point where Windows and POSIX differ, as Windows has
 	// alphabet-based volumes for root paths.
 	// alphabet-based volumes for root paths.
@@ -326,8 +326,8 @@ For example, `join_path({"/home", "foo", "bar.txt"})` will result in `"/home/foo
 join_path :: proc(elems: []string, allocator: runtime.Allocator) -> (joined: string, err: Error) {
 join_path :: proc(elems: []string, allocator: runtime.Allocator) -> (joined: string, err: Error) {
 	for e, i in elems {
 	for e, i in elems {
 		if e != "" {
 		if e != "" {
-			TEMP_ALLOCATOR_GUARD()
-			p := strings.join(elems[i:], Path_Separator_String, temp_allocator()) or_return
+			temp_allocator := TEMP_ALLOCATOR_GUARD({ allocator })
+			p := strings.join(elems[i:], Path_Separator_String, temp_allocator) or_return
 			return clean_path(p, allocator)
 			return clean_path(p, allocator)
 		}
 		}
 	}
 	}

+ 29 - 9
core/os/os2/path_linux.odin

@@ -18,8 +18,8 @@ _is_path_separator :: proc(c: byte) -> bool {
 }
 }
 
 
 _mkdir :: proc(path: string, perm: int) -> Error {
 _mkdir :: proc(path: string, perm: int) -> Error {
-	TEMP_ALLOCATOR_GUARD()
-	path_cstr := temp_cstring(path) or_return
+	temp_allocator := TEMP_ALLOCATOR_GUARD({})
+	path_cstr := clone_to_cstring(path, temp_allocator) or_return
 	return _get_platform_error(linux.mkdir(path_cstr, transmute(linux.Mode)u32(perm)))
 	return _get_platform_error(linux.mkdir(path_cstr, transmute(linux.Mode)u32(perm)))
 }
 }
 
 
@@ -52,9 +52,9 @@ _mkdir_all :: proc(path: string, perm: int) -> Error {
 		}
 		}
 		return _get_platform_error(errno)
 		return _get_platform_error(errno)
 	}
 	}
-	TEMP_ALLOCATOR_GUARD()
+	temp_allocator := TEMP_ALLOCATOR_GUARD({})
 	// need something we can edit, and use to generate cstrings
 	// need something we can edit, and use to generate cstrings
-	path_bytes := make([]u8, len(path) + 1, temp_allocator())
+	path_bytes := make([]u8, len(path) + 1, temp_allocator)
 
 
 	// zero terminate the byte slice to make it a valid cstring
 	// zero terminate the byte slice to make it a valid cstring
 	copy(path_bytes, path)
 	copy(path_bytes, path)
@@ -129,8 +129,8 @@ _remove_all :: proc(path: string) -> Error {
 		return nil
 		return nil
 	}
 	}
 
 
-	TEMP_ALLOCATOR_GUARD()
-	path_cstr := temp_cstring(path) or_return
+	temp_allocator := TEMP_ALLOCATOR_GUARD({})
+	path_cstr := clone_to_cstring(path, temp_allocator) or_return
 
 
 	fd, errno := linux.open(path_cstr, _OPENDIR_FLAGS)
 	fd, errno := linux.open(path_cstr, _OPENDIR_FLAGS)
 	#partial switch errno {
 	#partial switch errno {
@@ -168,14 +168,16 @@ _get_working_directory :: proc(allocator: runtime.Allocator) -> (string, Error)
 }
 }
 
 
 _set_working_directory :: proc(dir: string) -> Error {
 _set_working_directory :: proc(dir: string) -> Error {
-	dir_cstr := temp_cstring(dir) or_return
+	temp_allocator := TEMP_ALLOCATOR_GUARD({})
+
+	dir_cstr := clone_to_cstring(dir, temp_allocator) or_return
 	return _get_platform_error(linux.chdir(dir_cstr))
 	return _get_platform_error(linux.chdir(dir_cstr))
 }
 }
 
 
 _get_executable_path :: proc(allocator: runtime.Allocator) -> (path: string, err: Error) {
 _get_executable_path :: proc(allocator: runtime.Allocator) -> (path: string, err: Error) {
-	TEMP_ALLOCATOR_GUARD()
+	temp_allocator := TEMP_ALLOCATOR_GUARD({ allocator })
 
 
-	buf := make([dynamic]byte, 1024, temp_allocator()) or_return
+	buf := make([dynamic]byte, 1024, temp_allocator) or_return
 	for {
 	for {
 		n, errno := linux.readlink("/proc/self/exe", buf[:])
 		n, errno := linux.readlink("/proc/self/exe", buf[:])
 		if errno != .NONE {
 		if errno != .NONE {
@@ -205,3 +207,21 @@ _get_full_path :: proc(fd: linux.Fd, allocator: runtime.Allocator) -> (fullpath:
 	}
 	}
 	return
 	return
 }
 }
+
+_get_absolute_path :: proc(path: string, allocator: runtime.Allocator) -> (absolute_path: string, err: Error) {
+	rel := path
+	if rel == "" {
+		rel = "."
+	}
+
+	temp_allocator := TEMP_ALLOCATOR_GUARD({ allocator })
+
+	fd, errno := linux.open(clone_to_cstring(path, temp_allocator) or_return, {})
+	if errno != nil {
+		err = _get_platform_error(errno)
+		return
+	}
+	defer linux.close(fd)
+
+	return _get_full_path(fd, allocator)
+}

+ 2 - 2
core/os/os2/path_netbsd.odin

@@ -5,9 +5,9 @@ import "base:runtime"
 import "core:sys/posix"
 import "core:sys/posix"
 
 
 _get_executable_path :: proc(allocator: runtime.Allocator) -> (path: string, err: Error) {
 _get_executable_path :: proc(allocator: runtime.Allocator) -> (path: string, err: Error) {
-	TEMP_ALLOCATOR_GUARD()
+	temp_allocator := TEMP_ALLOCATOR_GUARD({ allocator })
 
 
-	buf := make([dynamic]byte, 1024, temp_allocator()) or_return
+	buf := make([dynamic]byte, 1024, temp_allocator) or_return
 	for {
 	for {
 		n := posix.readlink("/proc/curproc/exe", raw_data(buf), len(buf))
 		n := posix.readlink("/proc/curproc/exe", raw_data(buf), len(buf))
 		if n < 0 {
 		if n < 0 {

+ 3 - 3
core/os/os2/path_openbsd.odin

@@ -35,11 +35,11 @@ _get_executable_path :: proc(allocator: runtime.Allocator) -> (path: string, err
 		return real(arg, allocator)
 		return real(arg, allocator)
 	}
 	}
 
 
-	TEMP_ALLOCATOR_GUARD()
+	temp_allocator := TEMP_ALLOCATOR_GUARD({ allocator })
 
 
-	buf := strings.builder_make(temp_allocator())
+	buf := strings.builder_make(temp_allocator)
 
 
-	paths := get_env("PATH", temp_allocator())
+	paths := get_env("PATH", temp_allocator)
 	for dir in strings.split_iterator(&paths, ":") {
 	for dir in strings.split_iterator(&paths, ":") {
 		strings.builder_reset(&buf)
 		strings.builder_reset(&buf)
 		strings.write_string(&buf, dir)
 		strings.write_string(&buf, dir)

+ 30 - 13
core/os/os2/path_posix.odin

@@ -14,9 +14,9 @@ _is_path_separator :: proc(c: byte) -> bool {
 	return c == _Path_Separator
 	return c == _Path_Separator
 }
 }
 
 
-_mkdir :: proc(name: string, perm: int) -> Error {
-	TEMP_ALLOCATOR_GUARD()
-	cname := temp_cstring(name)
+_mkdir :: proc(name: string, perm: int) -> (err: Error) {
+	temp_allocator := TEMP_ALLOCATOR_GUARD({})
+	cname := clone_to_cstring(name, temp_allocator) or_return
 	if posix.mkdir(cname, transmute(posix.mode_t)posix._mode_t(perm)) != .OK {
 	if posix.mkdir(cname, transmute(posix.mode_t)posix._mode_t(perm)) != .OK {
 		return _get_platform_error()
 		return _get_platform_error()
 	}
 	}
@@ -28,13 +28,13 @@ _mkdir_all :: proc(path: string, perm: int) -> Error {
 		return .Invalid_Path
 		return .Invalid_Path
 	}
 	}
 
 
-	TEMP_ALLOCATOR_GUARD()
+	temp_allocator := TEMP_ALLOCATOR_GUARD({})
 
 
 	if exists(path) {
 	if exists(path) {
 		return .Exist
 		return .Exist
 	}
 	}
 
 
-	clean_path := clean_path(path, temp_allocator()) or_return
+	clean_path := clean_path(path, temp_allocator) or_return
 	return internal_mkdir_all(clean_path, perm)
 	return internal_mkdir_all(clean_path, perm)
 
 
 	internal_mkdir_all :: proc(path: string, perm: int) -> Error {
 	internal_mkdir_all :: proc(path: string, perm: int) -> Error {
@@ -52,9 +52,9 @@ _mkdir_all :: proc(path: string, perm: int) -> Error {
 	}
 	}
 }
 }
 
 
-_remove_all :: proc(path: string) -> Error {
-	TEMP_ALLOCATOR_GUARD()
-	cpath := temp_cstring(path)
+_remove_all :: proc(path: string) -> (err: Error) {
+	temp_allocator := TEMP_ALLOCATOR_GUARD({})
+	cpath := clone_to_cstring(path, temp_allocator) or_return
 
 
 	dir := posix.opendir(cpath)
 	dir := posix.opendir(cpath)
 	if dir == nil {
 	if dir == nil {
@@ -78,7 +78,7 @@ _remove_all :: proc(path: string) -> Error {
 			continue
 			continue
 		}
 		}
 
 
-		fullpath, _ := concatenate({path, "/", string(cname), "\x00"}, temp_allocator())
+		fullpath, _ := concatenate({path, "/", string(cname), "\x00"}, temp_allocator)
 		if entry.d_type == .DIR {
 		if entry.d_type == .DIR {
 			_remove_all(fullpath[:len(fullpath)-1]) or_return
 			_remove_all(fullpath[:len(fullpath)-1]) or_return
 		} else {
 		} else {
@@ -95,10 +95,10 @@ _remove_all :: proc(path: string) -> Error {
 }
 }
 
 
 _get_working_directory :: proc(allocator: runtime.Allocator) -> (dir: string, err: Error) {
 _get_working_directory :: proc(allocator: runtime.Allocator) -> (dir: string, err: Error) {
-	TEMP_ALLOCATOR_GUARD()
+	temp_allocator := TEMP_ALLOCATOR_GUARD({ allocator })
 
 
 	buf: [dynamic]byte
 	buf: [dynamic]byte
-	buf.allocator = temp_allocator()
+	buf.allocator = temp_allocator
 	size := uint(posix.PATH_MAX)
 	size := uint(posix.PATH_MAX)
 
 
 	cwd: cstring
 	cwd: cstring
@@ -116,10 +116,27 @@ _get_working_directory :: proc(allocator: runtime.Allocator) -> (dir: string, er
 }
 }
 
 
 _set_working_directory :: proc(dir: string) -> (err: Error) {
 _set_working_directory :: proc(dir: string) -> (err: Error) {
-	TEMP_ALLOCATOR_GUARD()
-	cdir := temp_cstring(dir)
+	temp_allocator := TEMP_ALLOCATOR_GUARD({})
+	cdir := clone_to_cstring(dir, temp_allocator) or_return
 	if posix.chdir(cdir) != .OK {
 	if posix.chdir(cdir) != .OK {
 		err = _get_platform_error()
 		err = _get_platform_error()
 	}
 	}
 	return
 	return
 }
 }
+
+_get_absolute_path :: proc(path: string, allocator: runtime.Allocator) -> (absolute_path: string, err: Error) {
+	rel := path
+	if rel == "" {
+		rel = "."
+	}
+	temp_allocator := TEMP_ALLOCATOR_GUARD({ allocator })
+	rel_cstr := clone_to_cstring(rel, temp_allocator) or_return
+	path_ptr := posix.realpath(rel_cstr, nil)
+	if path_ptr == nil {
+		return "", Platform_Error(posix.errno())
+	}
+	defer posix.free(path_ptr)
+
+	path_str := clone_string(string(path_ptr), allocator) or_return
+	return path_str, nil
+}

+ 0 - 21
core/os/os2/path_posixfs.odin

@@ -4,10 +4,6 @@ package os2
 
 
 // This implementation is for all systems that have POSIX-compliant filesystem paths.
 // This implementation is for all systems that have POSIX-compliant filesystem paths.
 
 
-import "base:runtime"
-import "core:strings"
-import "core:sys/posix"
-
 _are_paths_identical :: proc(a, b: string) -> (identical: bool) {
 _are_paths_identical :: proc(a, b: string) -> (identical: bool) {
 	return a == b
 	return a == b
 }
 }
@@ -26,23 +22,6 @@ _is_absolute_path :: proc(path: string) -> bool {
 	return len(path) > 0 && _is_path_separator(path[0])
 	return len(path) > 0 && _is_path_separator(path[0])
 }
 }
 
 
-_get_absolute_path :: proc(path: string, allocator: runtime.Allocator) -> (absolute_path: string, err: Error) {
-	rel := path
-	if rel == "" {
-		rel = "."
-	}
-	TEMP_ALLOCATOR_GUARD()
-	rel_cstr := strings.clone_to_cstring(rel, temp_allocator())
-	path_ptr := posix.realpath(rel_cstr, nil)
-	if path_ptr == nil {
-		return "", Platform_Error(posix.errno())
-	}
-	defer posix.free(path_ptr)
-
-	path_str := strings.clone(string(path_ptr), allocator)
-	return path_str, nil
-}
-
 _get_relative_path_handle_start :: proc(base, target: string) -> bool {
 _get_relative_path_handle_start :: proc(base, target: string) -> bool {
 	base_rooted   := len(base)   > 0 && _is_path_separator(base[0])
 	base_rooted   := len(base)   > 0 && _is_path_separator(base[0])
 	target_rooted := len(target) > 0 && _is_path_separator(target[0])
 	target_rooted := len(target) > 0 && _is_path_separator(target[0])

+ 2 - 2
core/os/os2/path_wasi.odin

@@ -28,13 +28,13 @@ _mkdir_all :: proc(path: string, perm: int) -> Error {
 		return .Invalid_Path
 		return .Invalid_Path
 	}
 	}
 
 
-	TEMP_ALLOCATOR_GUARD()
+	temp_allocator := TEMP_ALLOCATOR_GUARD({})
 
 
 	if exists(path) {
 	if exists(path) {
 		return .Exist
 		return .Exist
 	}
 	}
 
 
-	clean_path := clean_path(path, temp_allocator())
+	clean_path := clean_path(path, temp_allocator)
 	return internal_mkdir_all(clean_path)
 	return internal_mkdir_all(clean_path)
 
 
 	internal_mkdir_all :: proc(path: string) -> Error {
 	internal_mkdir_all :: proc(path: string) -> Error {

+ 23 - 19
core/os/os2/path_windows.odin

@@ -14,8 +14,8 @@ _is_path_separator :: proc(c: byte) -> bool {
 }
 }
 
 
 _mkdir :: proc(name: string, perm: int) -> Error {
 _mkdir :: proc(name: string, perm: int) -> Error {
-	TEMP_ALLOCATOR_GUARD()
-	if !win32.CreateDirectoryW(_fix_long_path(name, temp_allocator()) or_return, nil) {
+	temp_allocator := TEMP_ALLOCATOR_GUARD({})
+	if !win32.CreateDirectoryW(_fix_long_path(name, temp_allocator) or_return, nil) {
 		return _get_platform_error()
 		return _get_platform_error()
 	}
 	}
 	return nil
 	return nil
@@ -33,9 +33,9 @@ _mkdir_all :: proc(path: string, perm: int) -> Error {
 		return p, false, nil
 		return p, false, nil
 	}
 	}
 
 
-	TEMP_ALLOCATOR_GUARD()
+	temp_allocator := TEMP_ALLOCATOR_GUARD({})
 
 
-	dir_stat, err := stat(path, temp_allocator())
+	dir_stat, err := stat(path, temp_allocator)
 	if err == nil {
 	if err == nil {
 		if dir_stat.type == .Directory {
 		if dir_stat.type == .Directory {
 			return nil
 			return nil
@@ -63,7 +63,7 @@ _mkdir_all :: proc(path: string, perm: int) -> Error {
 
 
 	err = mkdir(path, perm)
 	err = mkdir(path, perm)
 	if err != nil {
 	if err != nil {
-		new_dir_stat, err1 := lstat(path, temp_allocator())
+		new_dir_stat, err1 := lstat(path, temp_allocator)
 		if err1 == nil && new_dir_stat.type == .Directory {
 		if err1 == nil && new_dir_stat.type == .Directory {
 			return nil
 			return nil
 		}
 		}
@@ -82,8 +82,8 @@ _remove_all :: proc(path: string) -> Error {
 		return nil
 		return nil
 	}
 	}
 
 
-	TEMP_ALLOCATOR_GUARD()
-	dir := win32_utf8_to_wstring(path, temp_allocator()) or_return
+	temp_allocator := TEMP_ALLOCATOR_GUARD({})
+	dir := win32_utf8_to_wstring(path, temp_allocator) or_return
 
 
 	empty: [1]u16
 	empty: [1]u16
 
 
@@ -109,10 +109,10 @@ _remove_all :: proc(path: string) -> Error {
 _get_working_directory :: proc(allocator: runtime.Allocator) -> (dir: string, err: Error) {
 _get_working_directory :: proc(allocator: runtime.Allocator) -> (dir: string, err: Error) {
 	win32.AcquireSRWLockExclusive(&cwd_lock)
 	win32.AcquireSRWLockExclusive(&cwd_lock)
 
 
-	TEMP_ALLOCATOR_GUARD()
+	temp_allocator := TEMP_ALLOCATOR_GUARD({ allocator })
 
 
 	sz_utf16 := win32.GetCurrentDirectoryW(0, nil)
 	sz_utf16 := win32.GetCurrentDirectoryW(0, nil)
-	dir_buf_wstr := make([]u16, sz_utf16, temp_allocator()) or_return
+	dir_buf_wstr := make([]u16, sz_utf16, temp_allocator) or_return
 
 
 	sz_utf16 = win32.GetCurrentDirectoryW(win32.DWORD(len(dir_buf_wstr)), raw_data(dir_buf_wstr))
 	sz_utf16 = win32.GetCurrentDirectoryW(win32.DWORD(len(dir_buf_wstr)), raw_data(dir_buf_wstr))
 	assert(int(sz_utf16)+1 == len(dir_buf_wstr)) // the second time, it _excludes_ the NUL.
 	assert(int(sz_utf16)+1 == len(dir_buf_wstr)) // the second time, it _excludes_ the NUL.
@@ -123,8 +123,8 @@ _get_working_directory :: proc(allocator: runtime.Allocator) -> (dir: string, er
 }
 }
 
 
 _set_working_directory :: proc(dir: string) -> (err: Error) {
 _set_working_directory :: proc(dir: string) -> (err: Error) {
-	TEMP_ALLOCATOR_GUARD()
-	wstr := win32_utf8_to_wstring(dir, temp_allocator()) or_return
+	temp_allocator := TEMP_ALLOCATOR_GUARD({})
+	wstr := win32_utf8_to_wstring(dir, temp_allocator) or_return
 
 
 	win32.AcquireSRWLockExclusive(&cwd_lock)
 	win32.AcquireSRWLockExclusive(&cwd_lock)
 
 
@@ -138,9 +138,9 @@ _set_working_directory :: proc(dir: string) -> (err: Error) {
 }
 }
 
 
 _get_executable_path :: proc(allocator: runtime.Allocator) -> (path: string, err: Error) {
 _get_executable_path :: proc(allocator: runtime.Allocator) -> (path: string, err: Error) {
-	TEMP_ALLOCATOR_GUARD()
+	temp_allocator := TEMP_ALLOCATOR_GUARD({ allocator })
 
 
-	buf := make([dynamic]u16, 512, temp_allocator()) or_return
+	buf := make([dynamic]u16, 512, temp_allocator) or_return
 	for {
 	for {
 		ret := win32.GetModuleFileNameW(nil, raw_data(buf), win32.DWORD(len(buf)))
 		ret := win32.GetModuleFileNameW(nil, raw_data(buf), win32.DWORD(len(buf)))
 		if ret == 0 {
 		if ret == 0 {
@@ -187,7 +187,6 @@ init_long_path_support :: proc() {
 	if value == 1 {
 	if value == 1 {
 		can_use_long_paths = true
 		can_use_long_paths = true
 	}
 	}
-
 }
 }
 
 
 @(require_results)
 @(require_results)
@@ -222,10 +221,10 @@ _fix_long_path_internal :: proc(path: string) -> string {
 		return path
 		return path
 	}
 	}
 
 
-	TEMP_ALLOCATOR_GUARD()
+	temp_allocator := TEMP_ALLOCATOR_GUARD({})
 
 
 	PREFIX :: `\\?`
 	PREFIX :: `\\?`
-	path_buf := make([]byte, len(PREFIX)+len(path)+1, temp_allocator())
+	path_buf := make([]byte, len(PREFIX)+len(path)+1, temp_allocator)
 	copy(path_buf, PREFIX)
 	copy(path_buf, PREFIX)
 	n := len(path)
 	n := len(path)
 	r, w := 0, len(PREFIX)
 	r, w := 0, len(PREFIX)
@@ -271,6 +270,11 @@ _clean_path_handle_start :: proc(path: string, buffer: []u8) -> (rooted: bool, s
 			start += 1
 			start += 1
 		}
 		}
 		copy(buffer, path[:start])
 		copy(buffer, path[:start])
+		for n in 0..<start {
+			if _is_path_separator(buffer[n]) {
+				buffer[n] = _Path_Separator
+			}
+		}
 	}
 	}
 	return
 	return
 }
 }
@@ -297,14 +301,14 @@ _get_absolute_path :: proc(path: string, allocator: runtime.Allocator) -> (absol
 	if rel == "" {
 	if rel == "" {
 		rel = "."
 		rel = "."
 	}
 	}
-	TEMP_ALLOCATOR_GUARD()
-	rel_utf16 := win32.utf8_to_utf16(rel, temp_allocator())
+	temp_allocator := TEMP_ALLOCATOR_GUARD({ allocator })
+	rel_utf16 := win32.utf8_to_utf16(rel, temp_allocator)
 	n := win32.GetFullPathNameW(raw_data(rel_utf16), 0, nil, nil)
 	n := win32.GetFullPathNameW(raw_data(rel_utf16), 0, nil, nil)
 	if n == 0 {
 	if n == 0 {
 		return "", Platform_Error(win32.GetLastError())
 		return "", Platform_Error(win32.GetLastError())
 	}
 	}
 
 
-	buf := make([]u16, n, temp_allocator()) or_return
+	buf := make([]u16, n, temp_allocator) or_return
 	n = win32.GetFullPathNameW(raw_data(rel_utf16), u32(n), raw_data(buf), nil)
 	n = win32.GetFullPathNameW(raw_data(rel_utf16), u32(n), raw_data(buf), nil)
 	if n == 0 {
 	if n == 0 {
 		return "", Platform_Error(win32.GetLastError())
 		return "", Platform_Error(win32.GetLastError())

+ 1 - 12
core/os/os2/process.odin

@@ -264,7 +264,7 @@ specific process, even after it has died.
 **Note(linux)**: The `handle` will be referring to pidfd.
 **Note(linux)**: The `handle` will be referring to pidfd.
 */
 */
 Process :: struct {
 Process :: struct {
-	pid: int,
+	pid:    int,
 	handle: uintptr,
 	handle: uintptr,
 }
 }
 
 
@@ -290,21 +290,10 @@ process_open :: proc(pid: int, flags := Process_Open_Flags {}) -> (Process, Erro
 	return _process_open(pid, flags)
 	return _process_open(pid, flags)
 }
 }
 
 
-
-/*
-OS-specific process attributes.
-*/
-Process_Attributes :: struct {
-	sys_attr: _Sys_Process_Attributes,
-}
-
 /*
 /*
 	The description of how a process should be created.
 	The description of how a process should be created.
 */
 */
 Process_Desc :: struct {
 Process_Desc :: struct {
-	// OS-specific attributes.
-	sys_attr: Process_Attributes,
-
 	// The working directory of the process. If the string has length 0, the
 	// The working directory of the process. If the string has length 0, the
 	// working directory is assumed to be the current working directory of the
 	// working directory is assumed to be the current working directory of the
 	// current process.
 	// current process.

+ 50 - 41
core/os/os2/process_linux.odin

@@ -50,7 +50,7 @@ _get_ppid :: proc() -> int {
 
 
 @(private="package")
 @(private="package")
 _process_list :: proc(allocator: runtime.Allocator) -> (list: []int, err: Error) {
 _process_list :: proc(allocator: runtime.Allocator) -> (list: []int, err: Error) {
-	TEMP_ALLOCATOR_GUARD()
+	temp_allocator := TEMP_ALLOCATOR_GUARD({ allocator })
 
 
 	dir_fd, errno := linux.open("/proc/", _OPENDIR_FLAGS)
 	dir_fd, errno := linux.open("/proc/", _OPENDIR_FLAGS)
 	#partial switch errno {
 	#partial switch errno {
@@ -68,9 +68,9 @@ _process_list :: proc(allocator: runtime.Allocator) -> (list: []int, err: Error)
 	}
 	}
 	defer linux.close(dir_fd)
 	defer linux.close(dir_fd)
 
 
-	dynamic_list := make([dynamic]int, temp_allocator()) or_return
+	dynamic_list := make([dynamic]int, temp_allocator) or_return
 
 
-	buf := make([dynamic]u8, 128, 128, temp_allocator()) or_return
+	buf := make([dynamic]u8, 128, 128, temp_allocator) or_return
 	loop: for {
 	loop: for {
 		buflen: int
 		buflen: int
 		buflen, errno = linux.getdents(dir_fd, buf[:])
 		buflen, errno = linux.getdents(dir_fd, buf[:])
@@ -100,7 +100,7 @@ _process_list :: proc(allocator: runtime.Allocator) -> (list: []int, err: Error)
 
 
 @(private="package")
 @(private="package")
 _process_info_by_pid :: proc(pid: int, selection: Process_Info_Fields, allocator: runtime.Allocator) -> (info: Process_Info, err: Error) {
 _process_info_by_pid :: proc(pid: int, selection: Process_Info_Fields, allocator: runtime.Allocator) -> (info: Process_Info, err: Error) {
-	TEMP_ALLOCATOR_GUARD()
+	temp_allocator := TEMP_ALLOCATOR_GUARD({ allocator })
 
 
 	info.pid = pid
 	info.pid = pid
 
 
@@ -126,7 +126,7 @@ _process_info_by_pid :: proc(pid: int, selection: Process_Info_Fields, allocator
 
 
 		passwd_bytes: []u8
 		passwd_bytes: []u8
 		passwd_err: Error
 		passwd_err: Error
-		passwd_bytes, passwd_err = _read_entire_pseudo_file_cstring("/etc/passwd", temp_allocator())
+		passwd_bytes, passwd_err = _read_entire_pseudo_file_cstring("/etc/passwd", temp_allocator)
 		if passwd_err != nil {
 		if passwd_err != nil {
 			err = passwd_err
 			err = passwd_err
 			break username_if
 			break username_if
@@ -162,13 +162,13 @@ _process_info_by_pid :: proc(pid: int, selection: Process_Info_Fields, allocator
 		}
 		}
 	}
 	}
 
 
-	cmdline_if: if selection & {.Working_Dir, .Command_Line, .Command_Args, .Executable_Path} != {} {
+	cmdline_if: if selection & {.Working_Dir, .Command_Line, .Command_Args} != {} {
 		strings.builder_reset(&path_builder)
 		strings.builder_reset(&path_builder)
 		strings.write_string(&path_builder, "/proc/")
 		strings.write_string(&path_builder, "/proc/")
 		strings.write_int(&path_builder, pid)
 		strings.write_int(&path_builder, pid)
 		strings.write_string(&path_builder, "/cmdline")
 		strings.write_string(&path_builder, "/cmdline")
 
 
-		cmdline_bytes, cmdline_err := _read_entire_pseudo_file(strings.to_cstring(&path_builder) or_return, temp_allocator())
+		cmdline_bytes, cmdline_err := _read_entire_pseudo_file(strings.to_cstring(&path_builder) or_return, temp_allocator)
 		if cmdline_err != nil || len(cmdline_bytes) == 0 {
 		if cmdline_err != nil || len(cmdline_bytes) == 0 {
 			err = cmdline_err
 			err = cmdline_err
 			break cmdline_if
 			break cmdline_if
@@ -178,18 +178,18 @@ _process_info_by_pid :: proc(pid: int, selection: Process_Info_Fields, allocator
 		terminator := strings.index_byte(cmdline, 0)
 		terminator := strings.index_byte(cmdline, 0)
 		assert(terminator > 0)
 		assert(terminator > 0)
 
 
-		command_line_exec := cmdline[:terminator]
+		// command_line_exec := cmdline[:terminator]
 
 
 		// Still need cwd if the execution on the command line is relative.
 		// Still need cwd if the execution on the command line is relative.
 		cwd: string
 		cwd: string
 		cwd_err: Error
 		cwd_err: Error
-		if .Working_Dir in selection || (.Executable_Path in selection && command_line_exec[0] != '/') {
+		if .Working_Dir in selection {
 			strings.builder_reset(&path_builder)
 			strings.builder_reset(&path_builder)
 			strings.write_string(&path_builder, "/proc/")
 			strings.write_string(&path_builder, "/proc/")
 			strings.write_int(&path_builder, pid)
 			strings.write_int(&path_builder, pid)
 			strings.write_string(&path_builder, "/cwd")
 			strings.write_string(&path_builder, "/cwd")
 
 
-			cwd, cwd_err = _read_link_cstr(strings.to_cstring(&path_builder) or_return, temp_allocator()) // allowed to fail
+			cwd, cwd_err = _read_link_cstr(strings.to_cstring(&path_builder) or_return, temp_allocator) // allowed to fail
 			if cwd_err == nil && .Working_Dir in selection {
 			if cwd_err == nil && .Working_Dir in selection {
 				info.working_dir = strings.clone(cwd, allocator) or_return
 				info.working_dir = strings.clone(cwd, allocator) or_return
 				info.fields += {.Working_Dir}
 				info.fields += {.Working_Dir}
@@ -199,18 +199,6 @@ _process_info_by_pid :: proc(pid: int, selection: Process_Info_Fields, allocator
 			}
 			}
 		}
 		}
 
 
-		if .Executable_Path in selection {
-			if cmdline[0] == '/' {
-				info.executable_path = strings.clone(cmdline[:terminator], allocator) or_return
-				info.fields += {.Executable_Path}
-			} else if cwd_err == nil {
-				info.executable_path = join_path({ cwd, cmdline[:terminator] }, allocator) or_return
-				info.fields += {.Executable_Path}
-			} else {
-				break cmdline_if
-			}
-		}
-
 		if selection & {.Command_Line, .Command_Args} != {} {
 		if selection & {.Command_Line, .Command_Args} != {} {
 			// skip to first arg
 			// skip to first arg
 			//cmdline = cmdline[terminator + 1:]
 			//cmdline = cmdline[terminator + 1:]
@@ -257,7 +245,7 @@ _process_info_by_pid :: proc(pid: int, selection: Process_Info_Fields, allocator
 		strings.write_int(&path_builder, pid)
 		strings.write_int(&path_builder, pid)
 		strings.write_string(&path_builder, "/stat")
 		strings.write_string(&path_builder, "/stat")
 
 
-		proc_stat_bytes, stat_err := _read_entire_pseudo_file(strings.to_cstring(&path_builder) or_return, temp_allocator())
+		proc_stat_bytes, stat_err := _read_entire_pseudo_file(strings.to_cstring(&path_builder) or_return, temp_allocator)
 		if stat_err != nil {
 		if stat_err != nil {
 			err = stat_err
 			err = stat_err
 			break stat_if
 			break stat_if
@@ -296,7 +284,7 @@ _process_info_by_pid :: proc(pid: int, selection: Process_Info_Fields, allocator
 			Nice,
 			Nice,
 			//... etc,
 			//... etc,
 		}
 		}
-		stat_fields := strings.split(stats, " ", temp_allocator()) or_return
+		stat_fields := strings.split(stats, " ", temp_allocator) or_return
 
 
 		if len(stat_fields) <= int(Fields.Nice) {
 		if len(stat_fields) <= int(Fields.Nice) {
 			break stat_if
 			break stat_if
@@ -323,13 +311,37 @@ _process_info_by_pid :: proc(pid: int, selection: Process_Info_Fields, allocator
 		}
 		}
 	}
 	}
 
 
+	if .Executable_Path in selection {
+		/*
+		NOTE(Jeroen):
+
+		The old version returned the wrong executable path for things like `bash` or `sh`,
+		for whom `/proc/<pid>/cmdline` will just report "bash" or "sh",
+		resulting in misleading paths like `$PWD/sh`, even though that executable doesn't exist there.
+
+		Thanks to Yawning for suggesting `/proc/self/exe`.
+		*/
+
+		strings.builder_reset(&path_builder)
+		strings.write_string(&path_builder, "/proc/")
+		strings.write_int(&path_builder, pid)
+		strings.write_string(&path_builder, "/exe")
+
+		if exe_bytes, exe_err := _read_link(strings.to_string(path_builder), temp_allocator); exe_err == nil {
+			info.executable_path = strings.clone(string(exe_bytes), allocator) or_return
+			info.fields += {.Executable_Path}
+		} else {
+			err = exe_err
+		}
+	}
+
 	if .Environment in selection {
 	if .Environment in selection {
 		strings.builder_reset(&path_builder)
 		strings.builder_reset(&path_builder)
 		strings.write_string(&path_builder, "/proc/")
 		strings.write_string(&path_builder, "/proc/")
 		strings.write_int(&path_builder, pid)
 		strings.write_int(&path_builder, pid)
 		strings.write_string(&path_builder, "/environ")
 		strings.write_string(&path_builder, "/environ")
 
 
-		if env_bytes, env_err := _read_entire_pseudo_file(strings.to_cstring(&path_builder) or_return, temp_allocator()); env_err == nil {
+		if env_bytes, env_err := _read_entire_pseudo_file(strings.to_cstring(&path_builder) or_return, temp_allocator); env_err == nil {
 			env := string(env_bytes)
 			env := string(env_bytes)
 
 
 			env_list := make([dynamic]string, allocator) or_return
 			env_list := make([dynamic]string, allocator) or_return
@@ -378,12 +390,9 @@ _process_open :: proc(pid: int, _: Process_Open_Flags) -> (process: Process, err
 	return
 	return
 }
 }
 
 
-@(private="package")
-_Sys_Process_Attributes :: struct {}
-
 @(private="package")
 @(private="package")
 _process_start :: proc(desc: Process_Desc) -> (process: Process, err: Error) {
 _process_start :: proc(desc: Process_Desc) -> (process: Process, err: Error) {
-	TEMP_ALLOCATOR_GUARD()
+	temp_allocator := TEMP_ALLOCATOR_GUARD({})
 
 
 	if len(desc.command) == 0 {
 	if len(desc.command) == 0 {
 		return process, .Invalid_Command
 		return process, .Invalid_Command
@@ -392,7 +401,7 @@ _process_start :: proc(desc: Process_Desc) -> (process: Process, err: Error) {
 	dir_fd := linux.AT_FDCWD
 	dir_fd := linux.AT_FDCWD
 	errno: linux.Errno
 	errno: linux.Errno
 	if desc.working_dir != "" {
 	if desc.working_dir != "" {
-		dir_cstr := temp_cstring(desc.working_dir) or_return
+		dir_cstr := clone_to_cstring(desc.working_dir, temp_allocator) or_return
 		if dir_fd, errno = linux.open(dir_cstr, _OPENDIR_FLAGS); errno != .NONE {
 		if dir_fd, errno = linux.open(dir_cstr, _OPENDIR_FLAGS); errno != .NONE {
 			return process, _get_platform_error(errno)
 			return process, _get_platform_error(errno)
 		}
 		}
@@ -405,10 +414,10 @@ _process_start :: proc(desc: Process_Desc) -> (process: Process, err: Error) {
 	exe_path: cstring
 	exe_path: cstring
 	executable_name := desc.command[0]
 	executable_name := desc.command[0]
 	if strings.index_byte(executable_name, '/') < 0 {
 	if strings.index_byte(executable_name, '/') < 0 {
-		path_env := get_env("PATH", temp_allocator())
-		path_dirs := split_path_list(path_env, temp_allocator()) or_return
+		path_env := get_env("PATH", temp_allocator)
+		path_dirs := split_path_list(path_env, temp_allocator) or_return
 
 
-		exe_builder := strings.builder_make(temp_allocator()) or_return
+		exe_builder := strings.builder_make(temp_allocator) or_return
 
 
 		found: bool
 		found: bool
 		for dir in path_dirs {
 		for dir in path_dirs {
@@ -435,7 +444,7 @@ _process_start :: proc(desc: Process_Desc) -> (process: Process, err: Error) {
 			}
 			}
 		}
 		}
 	} else {
 	} else {
-		exe_path = temp_cstring(executable_name) or_return
+		exe_path = clone_to_cstring(executable_name, temp_allocator) or_return
 		if linux.access(exe_path, linux.X_OK) != .NONE {
 		if linux.access(exe_path, linux.X_OK) != .NONE {
 			return process, .Not_Exist
 			return process, .Not_Exist
 		}
 		}
@@ -443,20 +452,20 @@ _process_start :: proc(desc: Process_Desc) -> (process: Process, err: Error) {
 
 
 	// args and environment need to be a list of cstrings
 	// args and environment need to be a list of cstrings
 	// that are terminated by a nil pointer.
 	// that are terminated by a nil pointer.
-	cargs := make([]cstring, len(desc.command) + 1, temp_allocator()) or_return
+	cargs := make([]cstring, len(desc.command) + 1, temp_allocator) or_return
 	for command, i in desc.command {
 	for command, i in desc.command {
-		cargs[i] = temp_cstring(command) or_return
+		cargs[i] = clone_to_cstring(command, temp_allocator) or_return
 	}
 	}
 
 
 	// Use current process' environment if description didn't provide it.
 	// Use current process' environment if description didn't provide it.
 	env: [^]cstring
 	env: [^]cstring
 	if desc.env == nil {
 	if desc.env == nil {
 		// take this process's current environment
 		// take this process's current environment
-		env = raw_data(export_cstring_environment(temp_allocator()))
+		env = raw_data(export_cstring_environment(temp_allocator))
 	} else {
 	} else {
-		cenv := make([]cstring, len(desc.env) + 1, temp_allocator()) or_return
+		cenv := make([]cstring, len(desc.env) + 1, temp_allocator) or_return
 		for env, i in desc.env {
 		for env, i in desc.env {
-			cenv[i] = temp_cstring(env) or_return
+			cenv[i] = clone_to_cstring(env, temp_allocator) or_return
 		}
 		}
 		env = &cenv[0]
 		env = &cenv[0]
 	}
 	}
@@ -584,7 +593,7 @@ _process_start :: proc(desc: Process_Desc) -> (process: Process, err: Error) {
 }
 }
 
 
 _process_state_update_times :: proc(state: ^Process_State) -> (err: Error) {
 _process_state_update_times :: proc(state: ^Process_State) -> (err: Error) {
-	TEMP_ALLOCATOR_GUARD()
+	temp_allocator := TEMP_ALLOCATOR_GUARD({})
 
 
 	stat_path_buf: [48]u8
 	stat_path_buf: [48]u8
 	path_builder := strings.builder_from_bytes(stat_path_buf[:])
 	path_builder := strings.builder_from_bytes(stat_path_buf[:])
@@ -593,7 +602,7 @@ _process_state_update_times :: proc(state: ^Process_State) -> (err: Error) {
 	strings.write_string(&path_builder, "/stat")
 	strings.write_string(&path_builder, "/stat")
 
 
 	stat_buf: []u8
 	stat_buf: []u8
-	stat_buf, err = _read_entire_pseudo_file(strings.to_cstring(&path_builder) or_return, temp_allocator())
+	stat_buf, err = _read_entire_pseudo_file(strings.to_cstring(&path_builder) or_return, temp_allocator)
 	if err != nil {
 	if err != nil {
 		return
 		return
 	}
 	}

+ 9 - 11
core/os/os2/process_posix.odin

@@ -46,22 +46,20 @@ _current_process_info :: proc(selection: Process_Info_Fields, allocator: runtime
 	return _process_info_by_pid(_get_pid(), selection, allocator)
 	return _process_info_by_pid(_get_pid(), selection, allocator)
 }
 }
 
 
-_Sys_Process_Attributes :: struct {}
-
 _process_start :: proc(desc: Process_Desc) -> (process: Process, err: Error) {
 _process_start :: proc(desc: Process_Desc) -> (process: Process, err: Error) {
 	if len(desc.command) == 0 {
 	if len(desc.command) == 0 {
 		err = .Invalid_Path
 		err = .Invalid_Path
 		return
 		return
 	}
 	}
 
 
-	TEMP_ALLOCATOR_GUARD()
+	temp_allocator := TEMP_ALLOCATOR_GUARD({})
 
 
 	// search PATH if just a plain name is provided.
 	// search PATH if just a plain name is provided.
-	exe_builder := strings.builder_make(temp_allocator())
+	exe_builder := strings.builder_make(temp_allocator)
 	exe_name    := desc.command[0]
 	exe_name    := desc.command[0]
 	if strings.index_byte(exe_name, '/') < 0 {
 	if strings.index_byte(exe_name, '/') < 0 {
-		path_env  := get_env("PATH", temp_allocator())
-		path_dirs := split_path_list(path_env, temp_allocator()) or_return
+		path_env  := get_env("PATH", temp_allocator)
+		path_dirs := split_path_list(path_env, temp_allocator) or_return
 
 
 		found: bool
 		found: bool
 		for dir in path_dirs {
 		for dir in path_dirs {
@@ -110,12 +108,12 @@ _process_start :: proc(desc: Process_Desc) -> (process: Process, err: Error) {
 	}
 	}
 
 
 	cwd: cstring; if desc.working_dir != "" {
 	cwd: cstring; if desc.working_dir != "" {
-		cwd = temp_cstring(desc.working_dir)
+		cwd = clone_to_cstring(desc.working_dir, temp_allocator) or_return
 	}
 	}
 
 
-	cmd := make([]cstring, len(desc.command) + 1, temp_allocator())
+	cmd := make([]cstring, len(desc.command) + 1, temp_allocator)
 	for part, i in desc.command {
 	for part, i in desc.command {
-		cmd[i] = temp_cstring(part)
+		cmd[i] = clone_to_cstring(part, temp_allocator) or_return
 	}
 	}
 
 
 	env: [^]cstring
 	env: [^]cstring
@@ -123,9 +121,9 @@ _process_start :: proc(desc: Process_Desc) -> (process: Process, err: Error) {
 		// take this process's current environment
 		// take this process's current environment
 		env = posix.environ
 		env = posix.environ
 	} else {
 	} else {
-		cenv := make([]cstring, len(desc.env) + 1, temp_allocator())
+		cenv := make([]cstring, len(desc.env) + 1, temp_allocator)
 		for env, i in desc.env {
 		for env, i in desc.env {
-			cenv[i] = temp_cstring(env)
+			cenv[i] = clone_to_cstring(env, temp_allocator) or_return
 		}
 		}
 		env = raw_data(cenv)
 		env = raw_data(cenv)
 	}
 	}

+ 4 - 3
core/os/os2/process_posix_darwin.odin

@@ -50,6 +50,7 @@ _process_info_by_pid :: proc(pid: int, selection: Process_Info_Fields, allocator
 	}
 	}
 
 
 
 
+	temp_allocator := TEMP_ALLOCATOR_GUARD({ allocator })
 	info.pid = pid
 	info.pid = pid
 
 
 	// Thought on errors is: allocation failures return immediately (also why the non-allocation stuff is done first),
 	// Thought on errors is: allocation failures return immediately (also why the non-allocation stuff is done first),
@@ -127,7 +128,7 @@ _process_info_by_pid :: proc(pid: int, selection: Process_Info_Fields, allocator
 			break args
 			break args
 		}
 		}
 
 
-		buf := runtime.make_aligned([]byte, length, 4, temp_allocator())
+		buf := runtime.make_aligned([]byte, length, 4, temp_allocator)
 		if sysctl(raw_data(mib), 3, raw_data(buf), &length, nil, 0) != .OK {
 		if sysctl(raw_data(mib), 3, raw_data(buf), &length, nil, 0) != .OK {
 			if err == nil {
 			if err == nil {
 				err = _get_platform_error()
 				err = _get_platform_error()
@@ -239,9 +240,9 @@ _process_list :: proc(allocator: runtime.Allocator) -> (list: []int, err: Error)
 		return
 		return
 	}
 	}
 
 
-	TEMP_ALLOCATOR_GUARD()
+	temp_allocator := TEMP_ALLOCATOR_GUARD({ allocator })
 
 
-	buffer := make([]i32, ret, temp_allocator())
+	buffer := make([]i32, ret, temp_allocator)
 	ret = darwin.proc_listallpids(raw_data(buffer), ret*size_of(i32))
 	ret = darwin.proc_listallpids(raw_data(buffer), ret*size_of(i32))
 	if ret < 0 {
 	if ret < 0 {
 		err = _get_platform_error()
 		err = _get_platform_error()

+ 0 - 2
core/os/os2/process_wasi.odin

@@ -44,8 +44,6 @@ _current_process_info :: proc(selection: Process_Info_Fields, allocator: runtime
 	return
 	return
 }
 }
 
 
-_Sys_Process_Attributes :: struct {}
-
 _process_start :: proc(desc: Process_Desc) -> (process: Process, err: Error) {
 _process_start :: proc(desc: Process_Desc) -> (process: Process, err: Error) {
 	err = .Unsupported
 	err = .Unsupported
 	return
 	return

+ 63 - 32
core/os/os2/process_windows.odin

@@ -162,9 +162,10 @@ _process_info_by_pid :: proc(pid: int, selection: Process_Info_Fields, allocator
 		if err != nil {
 		if err != nil {
 			break read_peb
 			break read_peb
 		}
 		}
+		temp_allocator := TEMP_ALLOCATOR_GUARD({ allocator })
 		if selection >= {.Command_Line, .Command_Args} {
 		if selection >= {.Command_Line, .Command_Args} {
-			TEMP_ALLOCATOR_GUARD()
-			cmdline_w := make([]u16, process_params.CommandLine.Length, temp_allocator()) or_return
+			temp_allocator_scope(temp_allocator)
+			cmdline_w := make([]u16, process_params.CommandLine.Length, temp_allocator) or_return
 			_, err = read_memory_as_slice(ph, process_params.CommandLine.Buffer, cmdline_w)
 			_, err = read_memory_as_slice(ph, process_params.CommandLine.Buffer, cmdline_w)
 			if err != nil {
 			if err != nil {
 				break read_peb
 				break read_peb
@@ -179,9 +180,9 @@ _process_info_by_pid :: proc(pid: int, selection: Process_Info_Fields, allocator
 			}
 			}
 		}
 		}
 		if .Environment in selection {
 		if .Environment in selection {
-			TEMP_ALLOCATOR_GUARD()
+			temp_allocator_scope(temp_allocator)
 			env_len := process_params.EnvironmentSize / 2
 			env_len := process_params.EnvironmentSize / 2
-			envs_w := make([]u16, env_len, temp_allocator()) or_return
+			envs_w := make([]u16, env_len, temp_allocator) or_return
 			_, err = read_memory_as_slice(ph, process_params.Environment, envs_w)
 			_, err = read_memory_as_slice(ph, process_params.Environment, envs_w)
 			if err != nil {
 			if err != nil {
 				break read_peb
 				break read_peb
@@ -190,8 +191,8 @@ _process_info_by_pid :: proc(pid: int, selection: Process_Info_Fields, allocator
 			info.fields += {.Environment}
 			info.fields += {.Environment}
 		}
 		}
 		if .Working_Dir in selection {
 		if .Working_Dir in selection {
-			TEMP_ALLOCATOR_GUARD()
-			cwd_w := make([]u16, process_params.CurrentDirectoryPath.Length, temp_allocator()) or_return
+			temp_allocator_scope(temp_allocator)
+			cwd_w := make([]u16, process_params.CurrentDirectoryPath.Length, temp_allocator) or_return
 			_, err = read_memory_as_slice(ph, process_params.CurrentDirectoryPath.Buffer, cwd_w)
 			_, err = read_memory_as_slice(ph, process_params.CurrentDirectoryPath.Buffer, cwd_w)
 			if err != nil {
 			if err != nil {
 				break read_peb
 				break read_peb
@@ -272,9 +273,10 @@ _process_info_by_handle :: proc(process: Process, selection: Process_Info_Fields
 		if err != nil {
 		if err != nil {
 			break read_peb
 			break read_peb
 		}
 		}
+		temp_allocator := TEMP_ALLOCATOR_GUARD({ allocator })
 		if selection >= {.Command_Line, .Command_Args} {
 		if selection >= {.Command_Line, .Command_Args} {
-			TEMP_ALLOCATOR_GUARD()
-			cmdline_w := make([]u16, process_params.CommandLine.Length, temp_allocator()) or_return
+			temp_allocator_scope(temp_allocator)
+			cmdline_w := make([]u16, process_params.CommandLine.Length, temp_allocator) or_return
 			_, err = read_memory_as_slice(ph, process_params.CommandLine.Buffer, cmdline_w)
 			_, err = read_memory_as_slice(ph, process_params.CommandLine.Buffer, cmdline_w)
 			if err != nil {
 			if err != nil {
 				break read_peb
 				break read_peb
@@ -289,9 +291,9 @@ _process_info_by_handle :: proc(process: Process, selection: Process_Info_Fields
 			}
 			}
 		}
 		}
 		if .Environment in selection {
 		if .Environment in selection {
-			TEMP_ALLOCATOR_GUARD()
+			temp_allocator_scope(temp_allocator)
 			env_len := process_params.EnvironmentSize / 2
 			env_len := process_params.EnvironmentSize / 2
-			envs_w := make([]u16, env_len, temp_allocator()) or_return
+			envs_w := make([]u16, env_len, temp_allocator) or_return
 			_, err = read_memory_as_slice(ph, process_params.Environment, envs_w)
 			_, err = read_memory_as_slice(ph, process_params.Environment, envs_w)
 			if err != nil {
 			if err != nil {
 				break read_peb
 				break read_peb
@@ -300,8 +302,8 @@ _process_info_by_handle :: proc(process: Process, selection: Process_Info_Fields
 			info.fields += {.Environment}
 			info.fields += {.Environment}
 		}
 		}
 		if .Working_Dir in selection {
 		if .Working_Dir in selection {
-			TEMP_ALLOCATOR_GUARD()
-			cwd_w := make([]u16, process_params.CurrentDirectoryPath.Length, temp_allocator()) or_return
+			temp_allocator_scope(temp_allocator)
+			cwd_w := make([]u16, process_params.CurrentDirectoryPath.Length, temp_allocator) or_return
 			_, err = read_memory_as_slice(ph, process_params.CurrentDirectoryPath.Buffer, cwd_w)
 			_, err = read_memory_as_slice(ph, process_params.CurrentDirectoryPath.Buffer, cwd_w)
 			if err != nil {
 			if err != nil {
 				break read_peb
 				break read_peb
@@ -417,35 +419,64 @@ _process_open :: proc(pid: int, flags: Process_Open_Flags) -> (process: Process,
 	return
 	return
 }
 }
 
 
-@(private="package")
-_Sys_Process_Attributes :: struct {}
-
 @(private="package")
 @(private="package")
 _process_start :: proc(desc: Process_Desc) -> (process: Process, err: Error) {
 _process_start :: proc(desc: Process_Desc) -> (process: Process, err: Error) {
-	TEMP_ALLOCATOR_GUARD()
-	command_line   := _build_command_line(desc.command, temp_allocator())
-	command_line_w := win32_utf8_to_wstring(command_line, temp_allocator()) or_return
+	temp_allocator := TEMP_ALLOCATOR_GUARD({})
+	command_line   := _build_command_line(desc.command, temp_allocator)
+	command_line_w := win32_utf8_to_wstring(command_line, temp_allocator) or_return
 	environment := desc.env
 	environment := desc.env
 	if desc.env == nil {
 	if desc.env == nil {
-		environment = environ(temp_allocator()) or_return
+		environment = environ(temp_allocator) or_return
+	}
+	environment_block   := _build_environment_block(environment, temp_allocator)
+	environment_block_w := win32_utf8_to_utf16(environment_block, temp_allocator) or_return
+
+	stderr_handle: win32.HANDLE	
+	stdout_handle: win32.HANDLE	
+	stdin_handle:  win32.HANDLE	
+
+	null_handle: win32.HANDLE
+	if desc.stdout == nil || desc.stderr == nil || desc.stdin == nil {
+		null_handle = win32.CreateFileW(
+			win32.L("NUL"),
+			win32.GENERIC_READ|win32.GENERIC_WRITE,
+			win32.FILE_SHARE_READ|win32.FILE_SHARE_WRITE,
+			&win32.SECURITY_ATTRIBUTES{
+				nLength        = size_of(win32.SECURITY_ATTRIBUTES),
+				bInheritHandle = true,
+			},
+			win32.OPEN_EXISTING,
+			win32.FILE_ATTRIBUTE_NORMAL,
+			nil,
+		)
+		// Opening NUL should always succeed.
+		assert(null_handle != nil)
+	}
+	// NOTE(laytan): I believe it is fine to close this handle right after CreateProcess,
+	// and we don't have to hold onto this until the process exits.
+	defer if null_handle != nil {
+		win32.CloseHandle(null_handle)
 	}
 	}
-	environment_block   := _build_environment_block(environment, temp_allocator())
-	environment_block_w := win32_utf8_to_utf16(environment_block, temp_allocator()) or_return
-	stderr_handle       := win32.GetStdHandle(win32.STD_ERROR_HANDLE)
-	stdout_handle       := win32.GetStdHandle(win32.STD_OUTPUT_HANDLE)
-	stdin_handle        := win32.GetStdHandle(win32.STD_INPUT_HANDLE)
 
 
-	if desc.stdout != nil {
+	if desc.stdout == nil {
+		stdout_handle = null_handle
+	} else {
 		stdout_handle = win32.HANDLE((^File_Impl)(desc.stdout.impl).fd)
 		stdout_handle = win32.HANDLE((^File_Impl)(desc.stdout.impl).fd)
 	}
 	}
-	if desc.stderr != nil {
+
+	if desc.stderr == nil {
+		stderr_handle = null_handle
+	} else {
 		stderr_handle = win32.HANDLE((^File_Impl)(desc.stderr.impl).fd)
 		stderr_handle = win32.HANDLE((^File_Impl)(desc.stderr.impl).fd)
 	}
 	}
-	if desc.stdin != nil {
+
+	if desc.stdin == nil {
+		stdin_handle = null_handle
+	} else {
 		stdin_handle = win32.HANDLE((^File_Impl)(desc.stdin.impl).fd)
 		stdin_handle = win32.HANDLE((^File_Impl)(desc.stdin.impl).fd)
 	}
 	}
 
 
-	working_dir_w := (win32_utf8_to_wstring(desc.working_dir, temp_allocator()) or_else nil) if len(desc.working_dir) > 0 else nil
+	working_dir_w := (win32_utf8_to_wstring(desc.working_dir, temp_allocator) or_else nil) if len(desc.working_dir) > 0 else nil
 	process_info: win32.PROCESS_INFORMATION
 	process_info: win32.PROCESS_INFORMATION
 	ok := win32.CreateProcessW(
 	ok := win32.CreateProcessW(
 		nil,
 		nil,
@@ -583,7 +614,7 @@ _process_exe_by_pid :: proc(pid: int, allocator: runtime.Allocator) -> (exe_path
 }
 }
 
 
 _get_process_user :: proc(process_handle: win32.HANDLE, allocator: runtime.Allocator) -> (full_username: string, err: Error) {
 _get_process_user :: proc(process_handle: win32.HANDLE, allocator: runtime.Allocator) -> (full_username: string, err: Error) {
-	TEMP_ALLOCATOR_GUARD()
+	temp_allocator := TEMP_ALLOCATOR_GUARD({ allocator })
 	token_handle: win32.HANDLE
 	token_handle: win32.HANDLE
 	if !win32.OpenProcessToken(process_handle, win32.TOKEN_QUERY, &token_handle) {
 	if !win32.OpenProcessToken(process_handle, win32.TOKEN_QUERY, &token_handle) {
 		err = _get_platform_error()
 		err = _get_platform_error()
@@ -598,7 +629,7 @@ _get_process_user :: proc(process_handle: win32.HANDLE, allocator: runtime.Alloc
 		}
 		}
 		err = nil
 		err = nil
 	}
 	}
-	token_user := (^win32.TOKEN_USER)(raw_data(make([]u8, token_user_size, temp_allocator()) or_return))
+	token_user := (^win32.TOKEN_USER)(raw_data(make([]u8, token_user_size, temp_allocator) or_return))
 	if !win32.GetTokenInformation(token_handle, .TokenUser, token_user, token_user_size, &token_user_size) {
 	if !win32.GetTokenInformation(token_handle, .TokenUser, token_user, token_user_size, &token_user_size) {
 		err = _get_platform_error()
 		err = _get_platform_error()
 		return
 		return
@@ -614,8 +645,8 @@ _get_process_user :: proc(process_handle: win32.HANDLE, allocator: runtime.Alloc
 		err = _get_platform_error()
 		err = _get_platform_error()
 		return
 		return
 	}
 	}
-	username := win32_utf16_to_utf8(username_w[:username_chrs], temp_allocator()) or_return
-	domain   := win32_utf16_to_utf8(domain_w[:domain_chrs], temp_allocator()) or_return
+	username := win32_utf16_to_utf8(username_w[:username_chrs], temp_allocator) or_return
+	domain   := win32_utf16_to_utf8(domain_w[:domain_chrs], temp_allocator) or_return
 	return strings.concatenate({domain, "\\", username}, allocator)
 	return strings.concatenate({domain, "\\", username}, allocator)
 }
 }
 
 

+ 4 - 4
core/os/os2/stat.odin

@@ -73,14 +73,14 @@ last_write_time_by_name :: modification_time_by_path
 
 
 @(require_results)
 @(require_results)
 modification_time :: proc(f: ^File) -> (time.Time, Error) {
 modification_time :: proc(f: ^File) -> (time.Time, Error) {
-	TEMP_ALLOCATOR_GUARD()
-	fi, err := fstat(f, temp_allocator())
+	temp_allocator := TEMP_ALLOCATOR_GUARD({})
+	fi, err := fstat(f, temp_allocator)
 	return fi.modification_time, err
 	return fi.modification_time, err
 }
 }
 
 
 @(require_results)
 @(require_results)
 modification_time_by_path :: proc(path: string) -> (time.Time, Error) {
 modification_time_by_path :: proc(path: string) -> (time.Time, Error) {
-	TEMP_ALLOCATOR_GUARD()
-	fi, err := stat(path, temp_allocator())
+	temp_allocator := TEMP_ALLOCATOR_GUARD({})
+	fi, err := stat(path, temp_allocator)
 	return fi.modification_time, err
 	return fi.modification_time, err
 }
 }

+ 4 - 4
core/os/os2/stat_linux.odin

@@ -47,8 +47,8 @@ _fstat_internal :: proc(fd: linux.Fd, allocator: runtime.Allocator) -> (fi: File
 
 
 // NOTE: _stat and _lstat are using _fstat to avoid a race condition when populating fullpath
 // NOTE: _stat and _lstat are using _fstat to avoid a race condition when populating fullpath
 _stat :: proc(name: string, allocator: runtime.Allocator) -> (fi: File_Info, err: Error) {
 _stat :: proc(name: string, allocator: runtime.Allocator) -> (fi: File_Info, err: Error) {
-	TEMP_ALLOCATOR_GUARD()
-	name_cstr := temp_cstring(name) or_return
+	temp_allocator := TEMP_ALLOCATOR_GUARD({ allocator })
+	name_cstr := clone_to_cstring(name, temp_allocator) or_return
 
 
 	fd, errno := linux.open(name_cstr, {})
 	fd, errno := linux.open(name_cstr, {})
 	if errno != .NONE {
 	if errno != .NONE {
@@ -59,8 +59,8 @@ _stat :: proc(name: string, allocator: runtime.Allocator) -> (fi: File_Info, err
 }
 }
 
 
 _lstat :: proc(name: string, allocator: runtime.Allocator) -> (fi: File_Info, err: Error) {
 _lstat :: proc(name: string, allocator: runtime.Allocator) -> (fi: File_Info, err: Error) {
-	TEMP_ALLOCATOR_GUARD()
-	name_cstr := temp_cstring(name) or_return
+	temp_allocator := TEMP_ALLOCATOR_GUARD({ allocator })
+	name_cstr := clone_to_cstring(name, temp_allocator) or_return
 
 
 	fd, errno := linux.open(name_cstr, {.PATH, .NOFOLLOW})
 	fd, errno := linux.open(name_cstr, {.PATH, .NOFOLLOW})
 	if errno != .NONE {
 	if errno != .NONE {

+ 9 - 8
core/os/os2/stat_posix.odin

@@ -69,8 +69,8 @@ _stat :: proc(name: string, allocator: runtime.Allocator) -> (fi: File_Info, err
 		return
 		return
 	}
 	}
 
 
-	TEMP_ALLOCATOR_GUARD()
-	cname := temp_cstring(name) or_return
+	temp_allocator := TEMP_ALLOCATOR_GUARD({ allocator })
+	cname := clone_to_cstring(name, temp_allocator) or_return
 
 
 	fd := posix.open(cname, {})
 	fd := posix.open(cname, {})
 	if fd == -1 {
 	if fd == -1 {
@@ -96,33 +96,34 @@ _lstat :: proc(name: string, allocator: runtime.Allocator) -> (fi: File_Info, er
 		return
 		return
 	}
 	}
 
 
-	TEMP_ALLOCATOR_GUARD()
+	temp_allocator := TEMP_ALLOCATOR_GUARD({ allocator })
 
 
 	// NOTE: can't use realpath or open (+ fcntl F_GETPATH) here because it tries to resolve symlinks.
 	// NOTE: can't use realpath or open (+ fcntl F_GETPATH) here because it tries to resolve symlinks.
 
 
 	// NOTE: This might not be correct when given "/symlink/foo.txt",
 	// NOTE: This might not be correct when given "/symlink/foo.txt",
 	// you would want that to resolve "/symlink", but not resolve "foo.txt".
 	// you would want that to resolve "/symlink", but not resolve "foo.txt".
 
 
-	fullpath := clean_path(name, temp_allocator()) or_return
+	fullpath := clean_path(name, temp_allocator) or_return
 	assert(len(fullpath) > 0)
 	assert(len(fullpath) > 0)
 	switch {
 	switch {
 	case fullpath[0] == '/':
 	case fullpath[0] == '/':
 		// nothing.
 		// nothing.
 	case fullpath == ".":
 	case fullpath == ".":
-		fullpath = getwd(temp_allocator()) or_return
+		fullpath = getwd(temp_allocator) or_return
 	case len(fullpath) > 1 && fullpath[0] == '.' && fullpath[1] == '/':
 	case len(fullpath) > 1 && fullpath[0] == '.' && fullpath[1] == '/':
 		fullpath = fullpath[2:]
 		fullpath = fullpath[2:]
 		fallthrough
 		fallthrough
 	case:
 	case:
 		fullpath = concatenate({
 		fullpath = concatenate({
-			getwd(temp_allocator()) or_return,
+			getwd(temp_allocator) or_return,
 			"/",
 			"/",
 			fullpath,
 			fullpath,
-		}, temp_allocator()) or_return
+		}, temp_allocator) or_return
 	}
 	}
 
 
 	stat: posix.stat_t
 	stat: posix.stat_t
-	if posix.lstat(temp_cstring(fullpath), &stat) != .OK {
+	c_fullpath := clone_to_cstring(fullpath, temp_allocator) or_return
+	if posix.lstat(c_fullpath, &stat) != .OK {
 		err = _get_platform_error()
 		err = _get_platform_error()
 		return
 		return
 	}
 	}

+ 87 - 45
core/os/os2/stat_windows.odin

@@ -45,15 +45,15 @@ full_path_from_name :: proc(name: string, allocator: runtime.Allocator) -> (path
 		name = "."
 		name = "."
 	}
 	}
 
 
-	TEMP_ALLOCATOR_GUARD()
+	temp_allocator := TEMP_ALLOCATOR_GUARD({ allocator })
 
 
-	p := win32_utf8_to_utf16(name, temp_allocator()) or_return
+	p := win32_utf8_to_utf16(name, temp_allocator) or_return
 
 
 	n := win32.GetFullPathNameW(raw_data(p), 0, nil, nil)
 	n := win32.GetFullPathNameW(raw_data(p), 0, nil, nil)
 	if n == 0 {
 	if n == 0 {
 		return "", _get_platform_error()
 		return "", _get_platform_error()
 	}
 	}
-	buf := make([]u16, n+1, temp_allocator())
+	buf := make([]u16, n+1, temp_allocator)
 	n = win32.GetFullPathNameW(raw_data(p), u32(len(buf)), raw_data(buf), nil)
 	n = win32.GetFullPathNameW(raw_data(p), u32(len(buf)), raw_data(buf), nil)
 	if n == 0 {
 	if n == 0 {
 		return "", _get_platform_error()
 		return "", _get_platform_error()
@@ -65,9 +65,9 @@ internal_stat :: proc(name: string, create_file_attributes: u32, allocator: runt
 	if len(name) == 0 {
 	if len(name) == 0 {
 		return {}, .Not_Exist
 		return {}, .Not_Exist
 	}
 	}
-	TEMP_ALLOCATOR_GUARD()
+	temp_allocator := TEMP_ALLOCATOR_GUARD({ allocator })
 
 
-	wname := _fix_long_path(name, temp_allocator()) or_return
+	wname := _fix_long_path(name, temp_allocator) or_return
 	fa: win32.WIN32_FILE_ATTRIBUTE_DATA
 	fa: win32.WIN32_FILE_ATTRIBUTE_DATA
 	ok := win32.GetFileAttributesExW(wname, win32.GetFileExInfoStandard, &fa)
 	ok := win32.GetFileAttributesExW(wname, win32.GetFileExInfoStandard, &fa)
 	if ok && fa.dwFileAttributes & win32.FILE_ATTRIBUTE_REPARSE_POINT == 0 {
 	if ok && fa.dwFileAttributes & win32.FILE_ATTRIBUTE_REPARSE_POINT == 0 {
@@ -137,9 +137,9 @@ _cleanpath_from_handle :: proc(f: ^File, allocator: runtime.Allocator) -> (strin
 		return "", _get_platform_error()
 		return "", _get_platform_error()
 	}
 	}
 
 
-	TEMP_ALLOCATOR_GUARD()
+	temp_allocator := TEMP_ALLOCATOR_GUARD({ allocator })
 
 
-	buf := make([]u16, max(n, 260)+1, temp_allocator())
+	buf := make([]u16, max(n, 260)+1, temp_allocator)
 	n = win32.GetFinalPathNameByHandleW(h, raw_data(buf), u32(len(buf)), 0)
 	n = win32.GetFinalPathNameByHandleW(h, raw_data(buf), u32(len(buf)), 0)
 	return _cleanpath_from_buf(buf[:n], allocator)
 	return _cleanpath_from_buf(buf[:n], allocator)
 }
 }
@@ -155,9 +155,9 @@ _cleanpath_from_handle_u16 :: proc(f: ^File) -> ([]u16, Error) {
 		return nil, _get_platform_error()
 		return nil, _get_platform_error()
 	}
 	}
 
 
-	TEMP_ALLOCATOR_GUARD()
+	temp_allocator := TEMP_ALLOCATOR_GUARD({})
 
 
-	buf := make([]u16, max(n, 260)+1, temp_allocator())
+	buf := make([]u16, max(n, 260)+1, temp_allocator)
 	n = win32.GetFinalPathNameByHandleW(h, raw_data(buf), u32(len(buf)), 0)
 	n = win32.GetFinalPathNameByHandleW(h, raw_data(buf), u32(len(buf)), 0)
 	return _cleanpath_strip_prefix(buf[:n]), nil
 	return _cleanpath_strip_prefix(buf[:n]), nil
 }
 }
@@ -236,14 +236,30 @@ _file_type_mode_from_file_attributes :: proc(file_attributes: win32.DWORD, h: wi
 	return
 	return
 }
 }
 
 
+// a 64-bit value representing the number of 100-nanosecond intervals since January 1, 1601 (UTC)
+time_as_filetime :: #force_inline proc(t: time.Time) -> (ft: win32.LARGE_INTEGER) {
+	win := u64(t._nsec / 100) + 116444736000000000
+	return win32.LARGE_INTEGER(win)
+}
+
+filetime_as_time_li :: #force_inline proc(ft: win32.LARGE_INTEGER) -> (t: time.Time) {
+	return {_nsec=(i64(ft) - 116444736000000000) * 100}
+}
+
+filetime_as_time_ft :: #force_inline proc(ft: win32.FILETIME) -> (t: time.Time) {
+	return filetime_as_time_li(win32.LARGE_INTEGER(ft.dwLowDateTime) + win32.LARGE_INTEGER(ft.dwHighDateTime) << 32)
+}
+
+filetime_as_time :: proc{filetime_as_time_ft, filetime_as_time_li}
+
 _file_info_from_win32_file_attribute_data :: proc(d: ^win32.WIN32_FILE_ATTRIBUTE_DATA, name: string, allocator: runtime.Allocator) -> (fi: File_Info, e: Error) {
 _file_info_from_win32_file_attribute_data :: proc(d: ^win32.WIN32_FILE_ATTRIBUTE_DATA, name: string, allocator: runtime.Allocator) -> (fi: File_Info, e: Error) {
 	fi.size = i64(d.nFileSizeHigh)<<32 + i64(d.nFileSizeLow)
 	fi.size = i64(d.nFileSizeHigh)<<32 + i64(d.nFileSizeLow)
 	type, mode := _file_type_mode_from_file_attributes(d.dwFileAttributes, nil, 0)
 	type, mode := _file_type_mode_from_file_attributes(d.dwFileAttributes, nil, 0)
 	fi.type = type
 	fi.type = type
 	fi.mode |= mode
 	fi.mode |= mode
-	fi.creation_time     = time.unix(0, win32.FILETIME_as_unix_nanoseconds(d.ftCreationTime))
-	fi.modification_time = time.unix(0, win32.FILETIME_as_unix_nanoseconds(d.ftLastWriteTime))
-	fi.access_time       = time.unix(0, win32.FILETIME_as_unix_nanoseconds(d.ftLastAccessTime))
+	fi.creation_time     = filetime_as_time(d.ftCreationTime)
+	fi.modification_time = filetime_as_time(d.ftLastWriteTime)
+	fi.access_time       = filetime_as_time(d.ftLastAccessTime)
 	fi.fullpath, e = full_path_from_name(name, allocator)
 	fi.fullpath, e = full_path_from_name(name, allocator)
 	fi.name = basename(fi.fullpath)
 	fi.name = basename(fi.fullpath)
 	return
 	return
@@ -254,9 +270,9 @@ _file_info_from_win32_find_data :: proc(d: ^win32.WIN32_FIND_DATAW, name: string
 	type, mode := _file_type_mode_from_file_attributes(d.dwFileAttributes, nil, 0)
 	type, mode := _file_type_mode_from_file_attributes(d.dwFileAttributes, nil, 0)
 	fi.type = type
 	fi.type = type
 	fi.mode |= mode
 	fi.mode |= mode
-	fi.creation_time     = time.unix(0, win32.FILETIME_as_unix_nanoseconds(d.ftCreationTime))
-	fi.modification_time = time.unix(0, win32.FILETIME_as_unix_nanoseconds(d.ftLastWriteTime))
-	fi.access_time       = time.unix(0, win32.FILETIME_as_unix_nanoseconds(d.ftLastAccessTime))
+	fi.creation_time     = filetime_as_time(d.ftCreationTime)
+	fi.modification_time = filetime_as_time(d.ftLastWriteTime)
+	fi.access_time       = filetime_as_time(d.ftLastAccessTime)
 	fi.fullpath, e = full_path_from_name(name, allocator)
 	fi.fullpath, e = full_path_from_name(name, allocator)
 	fi.name = basename(fi.fullpath)
 	fi.name = basename(fi.fullpath)
 	return
 	return
@@ -286,9 +302,9 @@ _file_info_from_get_file_information_by_handle :: proc(path: string, h: win32.HA
 	type, mode := _file_type_mode_from_file_attributes(d.dwFileAttributes, h, 0)
 	type, mode := _file_type_mode_from_file_attributes(d.dwFileAttributes, h, 0)
 	fi.type = type
 	fi.type = type
 	fi.mode |= mode
 	fi.mode |= mode
-	fi.creation_time     = time.unix(0, win32.FILETIME_as_unix_nanoseconds(d.ftCreationTime))
-	fi.modification_time = time.unix(0, win32.FILETIME_as_unix_nanoseconds(d.ftLastWriteTime))
-	fi.access_time       = time.unix(0, win32.FILETIME_as_unix_nanoseconds(d.ftLastAccessTime))
+	fi.creation_time     = filetime_as_time(d.ftCreationTime)
+	fi.modification_time = filetime_as_time(d.ftLastWriteTime)
+	fi.access_time       = filetime_as_time(d.ftLastAccessTime)
 	return fi, nil
 	return fi, nil
 }
 }
 
 
@@ -310,42 +326,68 @@ _is_reserved_name :: proc(path: string) -> bool {
 	return false
 	return false
 }
 }
 
 
-_is_UNC :: proc(path: string) -> bool {
-	return _volume_name_len(path) > 2
-}
-
-_volume_name_len :: proc(path: string) -> int {
+_volume_name_len :: proc(path: string) -> (length: int) {
 	if len(path) < 2 {
 	if len(path) < 2 {
 		return 0
 		return 0
 	}
 	}
-	c := path[0]
+
 	if path[1] == ':' {
 	if path[1] == ':' {
-		switch c {
+		switch path[0] {
 		case 'a'..='z', 'A'..='Z':
 		case 'a'..='z', 'A'..='Z':
 			return 2
 			return 2
 		}
 		}
 	}
 	}
 
 
-	// URL: https://msdn.microsoft.com/en-us/library/windows/desktop/aa365247(v=vs.85).aspx
-	if l := len(path); l >= 5 && _is_path_separator(path[0]) && _is_path_separator(path[1]) &&
-		!_is_path_separator(path[2]) && path[2] != '.' {
-		for n := 3; n < l-1; n += 1 {
-			if _is_path_separator(path[n]) {
-				n += 1
-				if !_is_path_separator(path[n]) {
-					if path[n] == '.' {
-						break
-					}
-				}
-				for ; n < l; n += 1 {
-					if _is_path_separator(path[n]) {
-						break
-					}
-				}
-				return n
+	/*
+		See: URL: https://msdn.microsoft.com/en-us/library/windows/desktop/aa365247(v=vs.85).aspx
+		Further allowed paths can be of the form of:
+		- \\server\share or \\server\share\more\path
+		- \\?\C:\...
+		- \\.\PhysicalDriveX
+	*/
+	// Any remaining kind of path has to start with two slashes.
+	if !_is_path_separator(path[0]) || !_is_path_separator(path[1]) {
+		return 0
+	}
+
+	// Device path. The volume name is the whole string
+	if len(path) >= 5 && path[2] == '.' && _is_path_separator(path[3]) {
+		return len(path)
+	}
+
+	// We're a UNC share `\\host\share`, file namespace `\\?\C:` or UNC in file namespace `\\?\\host\share`
+	prefix := 2
+
+	// File namespace.
+	if len(path) >= 5 && path[2] == '?' && _is_path_separator(path[3]) {
+		if _is_path_separator(path[4]) {
+			// `\\?\\` UNC path in file namespace
+			prefix = 5
+		}
+
+		if len(path) >= 6 && path[5] == ':' {
+			switch path[4] {
+			case 'a'..='z', 'A'..='Z':
+				return 6
+			case:
+				return 0
+			}
+		}
+	}
+
+	// UNC path, minimum version of the volume is `\\h\s` for host, share.
+	// Can also contain an IP address in the host position.
+	slash_count := 0
+	for i in prefix..<len(path) {
+		// Host needs to be at least 1 character
+		if _is_path_separator(path[i]) && i > 0 {
+			slash_count += 1
+
+			if slash_count == 2 {
+				return i
 			}
 			}
-			break
 		}
 		}
 	}
 	}
-	return 0
-}
+
+	return len(path)
+}

+ 11 - 9
core/os/os2/temp_file.odin

@@ -15,13 +15,13 @@ MAX_ATTEMPTS :: 1<<13 // Should be enough for everyone, right?
 // The caller must `close` the file once finished with.
 // The caller must `close` the file once finished with.
 @(require_results)
 @(require_results)
 create_temp_file :: proc(dir, pattern: string) -> (f: ^File, err: Error) {
 create_temp_file :: proc(dir, pattern: string) -> (f: ^File, err: Error) {
-	TEMP_ALLOCATOR_GUARD()
-	dir := dir if dir != "" else temp_directory(temp_allocator()) or_return
+	temp_allocator := TEMP_ALLOCATOR_GUARD({})
+	dir := dir if dir != "" else temp_directory(temp_allocator) or_return
 	prefix, suffix := _prefix_and_suffix(pattern) or_return
 	prefix, suffix := _prefix_and_suffix(pattern) or_return
 	prefix = temp_join_path(dir, prefix) or_return
 	prefix = temp_join_path(dir, prefix) or_return
 
 
 	rand_buf: [10]byte
 	rand_buf: [10]byte
-	name_buf := make([]byte, len(prefix)+len(rand_buf)+len(suffix), temp_allocator())
+	name_buf := make([]byte, len(prefix)+len(rand_buf)+len(suffix), temp_allocator)
 
 
 	attempts := 0
 	attempts := 0
 	for {
 	for {
@@ -47,13 +47,13 @@ mkdir_temp :: make_directory_temp
 // If `dir` is an empty tring, `temp_directory()` will be used.
 // If `dir` is an empty tring, `temp_directory()` will be used.
 @(require_results)
 @(require_results)
 make_directory_temp :: proc(dir, pattern: string, allocator: runtime.Allocator) -> (temp_path: string, err: Error) {
 make_directory_temp :: proc(dir, pattern: string, allocator: runtime.Allocator) -> (temp_path: string, err: Error) {
-	TEMP_ALLOCATOR_GUARD()
-	dir := dir if dir != "" else temp_directory(temp_allocator()) or_return
+	temp_allocator := TEMP_ALLOCATOR_GUARD({ allocator })
+	dir := dir if dir != "" else temp_directory(temp_allocator) or_return
 	prefix, suffix := _prefix_and_suffix(pattern) or_return
 	prefix, suffix := _prefix_and_suffix(pattern) or_return
 	prefix = temp_join_path(dir, prefix) or_return
 	prefix = temp_join_path(dir, prefix) or_return
 
 
 	rand_buf: [10]byte
 	rand_buf: [10]byte
-	name_buf := make([]byte, len(prefix)+len(rand_buf)+len(suffix), temp_allocator())
+	name_buf := make([]byte, len(prefix)+len(rand_buf)+len(suffix), temp_allocator)
 
 
 	attempts := 0
 	attempts := 0
 	for {
 	for {
@@ -70,7 +70,7 @@ make_directory_temp :: proc(dir, pattern: string, allocator: runtime.Allocator)
 			return "", err
 			return "", err
 		}
 		}
 		if err == .Not_Exist {
 		if err == .Not_Exist {
-			if _, serr := stat(dir, temp_allocator()); serr == .Not_Exist {
+			if _, serr := stat(dir, temp_allocator); serr == .Not_Exist {
 				return "", serr
 				return "", serr
 			}
 			}
 		}
 		}
@@ -89,9 +89,11 @@ temp_directory :: proc(allocator: runtime.Allocator) -> (string, Error) {
 
 
 @(private="file")
 @(private="file")
 temp_join_path :: proc(dir, name: string) -> (string, runtime.Allocator_Error) {
 temp_join_path :: proc(dir, name: string) -> (string, runtime.Allocator_Error) {
+	temp_allocator := TEMP_ALLOCATOR_GUARD({})
+
 	if len(dir) > 0 && is_path_separator(dir[len(dir)-1]) {
 	if len(dir) > 0 && is_path_separator(dir[len(dir)-1]) {
-		return concatenate({dir, name}, temp_allocator(),)
+		return concatenate({dir, name}, temp_allocator,)
 	}
 	}
 
 
-	return concatenate({dir, Path_Separator_String, name}, temp_allocator())
+	return concatenate({dir, Path_Separator_String, name}, temp_allocator)
 }
 }

+ 2 - 2
core/os/os2/temp_file_linux.odin

@@ -4,8 +4,8 @@ package os2
 import "base:runtime"
 import "base:runtime"
 
 
 _temp_dir :: proc(allocator: runtime.Allocator) -> (string, runtime.Allocator_Error) {
 _temp_dir :: proc(allocator: runtime.Allocator) -> (string, runtime.Allocator_Error) {
-	TEMP_ALLOCATOR_GUARD()
-	tmpdir := get_env("TMPDIR", temp_allocator())
+	temp_allocator := TEMP_ALLOCATOR_GUARD({ allocator })
+	tmpdir := get_env("TMPDIR", temp_allocator)
 	if tmpdir == "" {
 	if tmpdir == "" {
 		tmpdir = "/tmp"
 		tmpdir = "/tmp"
 	}
 	}

+ 2 - 2
core/os/os2/temp_file_windows.odin

@@ -9,9 +9,9 @@ _temp_dir :: proc(allocator: runtime.Allocator) -> (string, runtime.Allocator_Er
 	if n == 0 {
 	if n == 0 {
 		return "", nil
 		return "", nil
 	}
 	}
-	TEMP_ALLOCATOR_GUARD()
+	temp_allocator := TEMP_ALLOCATOR_GUARD({ allocator })
 
 
-	b := make([]u16, max(win32.MAX_PATH, n), temp_allocator())
+	b := make([]u16, max(win32.MAX_PATH, n), temp_allocator)
 	n = win32.GetTempPathW(u32(len(b)), raw_data(b))
 	n = win32.GetTempPathW(u32(len(b)), raw_data(b))
 
 
 	if n == 3 && b[1] == ':' && b[2] == '\\' {
 	if n == 3 && b[1] == ':' && b[2] == '\\' {

+ 133 - 63
core/os/os2/user.odin

@@ -2,78 +2,148 @@ package os2
 
 
 import "base:runtime"
 import "base:runtime"
 
 
+// ```
+// Windows:  C:\Users\Alice
+// macOS:    /Users/Alice
+// Linux:    /home/alice
+// ```
+@(require_results)
+user_home_dir :: proc(allocator: runtime.Allocator) -> (dir: string, err: Error) {
+	return _user_home_dir(allocator)
+}
+
+// Files that applications can regenerate/refetch at a loss of speed, e.g. shader caches
+//
+// Sometimes deleted for system maintenance
+//
+// ```
+// Windows:  C:\Users\Alice\AppData\Local
+// macOS:    /Users/Alice/Library/Caches
+// Linux:    /home/alice/.cache
+// ```
 @(require_results)
 @(require_results)
 user_cache_dir :: proc(allocator: runtime.Allocator) -> (dir: string, err: Error) {
 user_cache_dir :: proc(allocator: runtime.Allocator) -> (dir: string, err: Error) {
-	TEMP_ALLOCATOR_GUARD()
+	return _user_cache_dir(allocator)
+}
+
+// User-hidden application data
+//
+// ```
+// Windows:  C:\Users\Alice\AppData\Local ("C:\Users\Alice\AppData\Roaming" if `roaming`)
+// macOS:    /Users/Alice/Library/Application Support
+// Linux:    /home/alice/.local/share
+// ```
+//
+// NOTE: (Windows only) `roaming` is for syncing across multiple devices within a *domain network*
+@(require_results)
+user_data_dir :: proc(allocator: runtime.Allocator, roaming := false) -> (dir: string, err: Error) {
+	return _user_data_dir(allocator, roaming)
+}
 
 
-	#partial switch ODIN_OS {
-	case .Windows:
-		dir = get_env("LocalAppData", temp_allocator())
-		if dir != "" {
-			dir = clone_string(dir, allocator) or_return
-		}
-	case .Darwin:
-		dir = get_env("HOME", temp_allocator())
-		if dir != "" {
-			dir = concatenate({dir, "/Library/Caches"}, allocator) or_return
-		}
-	case: // All other UNIX systems
-		dir = get_env("XDG_CACHE_HOME", allocator)
-		if dir == "" {
-			dir = get_env("HOME", temp_allocator())
-			if dir == "" {
-				return
-			}
-			dir = concatenate({dir, "/.cache"}, allocator) or_return
-		}
-	}
-	if dir == "" {
-		err = .Invalid_Path
-	}
-	return
+// Non-essential application data, e.g. history, ui layout state
+//
+// ```
+// Windows:  C:\Users\Alice\AppData\Local
+// macOS:    /Users/Alice/Library/Application Support
+// Linux:    /home/alice/.local/state
+// ```
+@(require_results)
+user_state_dir :: proc(allocator: runtime.Allocator) -> (dir: string, err: Error) {
+	return _user_state_dir(allocator)
 }
 }
 
 
+// Application log files
+//
+// ```
+// Windows:  C:\Users\Alice\AppData\Local
+// macOS:    /Users/Alice/Library/Logs
+// Linux:    /home/alice/.local/state
+// ```
 @(require_results)
 @(require_results)
-user_config_dir :: proc(allocator: runtime.Allocator) -> (dir: string, err: Error) {
-	TEMP_ALLOCATOR_GUARD()
+user_log_dir :: proc(allocator: runtime.Allocator) -> (dir: string, err: Error) {
+	return _user_log_dir(allocator)
+}
 
 
-	#partial switch ODIN_OS {
-	case .Windows:
-		dir = get_env("AppData", temp_allocator())
-		if dir != "" {
-			dir = clone_string(dir, allocator) or_return
-		}
-	case .Darwin:
-		dir = get_env("HOME", temp_allocator())
-		if dir != "" {
-			dir = concatenate({dir, "/.config"}, allocator) or_return
-		}
-	case: // All other UNIX systems
-		dir = get_env("XDG_CONFIG_HOME", allocator)
-		if dir == "" {
-			dir = get_env("HOME", temp_allocator())
-			if dir == "" {
-				return
-			}
-			dir = concatenate({dir, "/.config"}, allocator) or_return
-		}
-	}
-	if dir == "" {
-		err = .Invalid_Path
-	}
-	return
+// Application settings/preferences
+//
+// ```
+// Windows:  C:\Users\Alice\AppData\Local ("C:\Users\Alice\AppData\Roaming" if `roaming`)
+// macOS:    /Users/Alice/Library/Application Support
+// Linux:    /home/alice/.config
+// ```
+//
+// NOTE: (Windows only) `roaming` is for syncing across multiple devices within a *domain network*
+@(require_results)
+user_config_dir :: proc(allocator: runtime.Allocator, roaming := false) -> (dir: string, err: Error) {
+	return _user_config_dir(allocator, roaming)
 }
 }
 
 
+// ```
+// Windows:  C:\Users\Alice\Music
+// macOS:    /Users/Alice/Music
+// Linux:    /home/alice/Music
+// ```
 @(require_results)
 @(require_results)
-user_home_dir :: proc(allocator: runtime.Allocator) -> (dir: string, err: Error) {
-	env := "HOME"
-	#partial switch ODIN_OS {
-	case .Windows:
-		env = "USERPROFILE"
-	}
-	if v := get_env(env, allocator); v != "" {
-		return v, nil
-	}
-	return "", .Invalid_Path
+user_music_dir :: proc(allocator: runtime.Allocator) -> (dir: string, err: Error) {
+	return _user_music_dir(allocator)
+}
+
+// ```
+// Windows:  C:\Users\Alice\Desktop
+// macOS:    /Users/Alice/Desktop
+// Linux:    /home/alice/Desktop
+// ```
+@(require_results)
+user_desktop_dir :: proc(allocator: runtime.Allocator) -> (dir: string, err: Error) {
+	return _user_desktop_dir(allocator)
+}
+
+// ```
+// Windows:  C:\Users\Alice\Documents
+// macOS:    /Users/Alice/Documents
+// Linux:    /home/alice/Documents
+// ```
+@(require_results)
+user_documents_dir :: proc(allocator: runtime.Allocator) -> (dir: string, err: Error) {
+	return _user_documents_dir(allocator)
+}
+
+// ```
+// Windows:  C:\Users\Alice\Downloads
+// macOS:    /Users/Alice/Downloads
+// Linux:    /home/alice/Downloads
+// ```
+@(require_results)
+user_downloads_dir :: proc(allocator: runtime.Allocator) -> (dir: string, err: Error) {
+	return _user_downloads_dir(allocator)
 }
 }
 
 
+// ```
+// Windows:  C:\Users\Alice\Pictures
+// macOS:    /Users/Alice/Pictures
+// Linux:    /home/alice/Pictures
+// ```
+@(require_results)
+user_pictures_dir :: proc(allocator: runtime.Allocator) -> (dir: string, err: Error) {
+	return _user_pictures_dir(allocator)
+}
+
+// ```
+// Windows:  C:\Users\Alice\Public
+// macOS:    /Users/Alice/Public
+// Linux:    /home/alice/Public
+// ```
+@(require_results)
+user_public_dir :: proc(allocator: runtime.Allocator) -> (dir: string, err: Error) {
+	return _user_public_dir(allocator)
+}
+
+// ```
+// Windows:  C:\Users\Alice\Videos
+// macOS:    /Users/Alice/Movies
+// Linux:    /home/alice/Videos
+// ```
+@(require_results)
+user_videos_dir :: proc(allocator: runtime.Allocator) -> (dir: string, err: Error) {
+	return _user_videos_dir(allocator)
+}

+ 183 - 0
core/os/os2/user_posix.odin

@@ -0,0 +1,183 @@
+#+build !windows
+package os2
+
+import "base:runtime"
+import "core:encoding/ini"
+import "core:strings"
+import "core:sys/posix"
+
+_user_cache_dir :: proc(allocator: runtime.Allocator) -> (dir: string, err: Error) {
+	#partial switch ODIN_OS {
+	case .Darwin:
+		return _xdg_lookup("", "/Library/Caches", allocator)
+	case: // Unix
+		return _xdg_lookup("XDG_CACHE_HOME", "/.cache", allocator)
+	}
+}
+
+_user_config_dir :: proc(allocator: runtime.Allocator, _roaming: bool) -> (dir: string, err: Error) {
+	#partial switch ODIN_OS {
+	case .Darwin:
+		return _xdg_lookup("", "/Library/Application Support", allocator)
+	case: // Unix
+		return _xdg_lookup("XDG_CONFIG_HOME", "/.config", allocator)
+	}
+}
+
+_user_state_dir :: proc(allocator: runtime.Allocator) -> (dir: string, err: Error) {
+	#partial switch ODIN_OS {
+	case .Darwin:
+		return _xdg_lookup("", "/Library/Application Support", allocator)
+	case: // Unix
+		return _xdg_lookup("XDG_STATE_HOME", "/.local/state", allocator)
+	}
+}
+
+_user_log_dir :: proc(allocator: runtime.Allocator) -> (dir: string, err: Error) {
+	#partial switch ODIN_OS {
+	case .Darwin:
+		return _xdg_lookup("", "/Library/Logs", allocator)
+	case: // Unix
+		return _xdg_lookup("XDG_STATE_HOME", "/.local/state", allocator)
+	}
+}
+
+_user_data_dir :: proc(allocator: runtime.Allocator, _roaming: bool) -> (dir: string, err: Error) {
+	#partial switch ODIN_OS {
+	case .Darwin:
+		return _xdg_lookup("", "/Library/Application Support", allocator)
+	case: // Unix
+		return _xdg_lookup("XDG_DATA_HOME", "/.local/share", allocator)
+	}
+}
+
+_user_music_dir :: proc(allocator: runtime.Allocator) -> (dir: string, err: Error) {
+	#partial switch ODIN_OS {
+	case .Darwin:
+		return _xdg_lookup("", "/Music", allocator)
+	case: // Unix
+		return _xdg_lookup("XDG_MUSIC_DIR", "/Music", allocator)
+	}
+}
+
+_user_desktop_dir :: proc(allocator: runtime.Allocator) -> (dir: string, err: Error) {
+	#partial switch ODIN_OS {
+	case .Darwin:
+		return _xdg_lookup("", "/Desktop", allocator)
+	case: // Unix
+		return _xdg_lookup("XDG_DESKTOP_DIR", "/Desktop", allocator)
+	}
+}
+
+_user_documents_dir :: proc(allocator: runtime.Allocator) -> (dir: string, err: Error) {
+	#partial switch ODIN_OS {
+	case .Darwin:
+		return _xdg_lookup("", "/Documents", allocator)
+	case: // Unix
+		return _xdg_lookup("XDG_DOCUMENTS_DIR", "/Documents", allocator)
+	}
+}
+
+_user_downloads_dir :: proc(allocator: runtime.Allocator) -> (dir: string, err: Error) {
+	#partial switch ODIN_OS {
+	case .Darwin:
+		return _xdg_lookup("", "/Downloads", allocator)
+	case: // Unix
+		return _xdg_lookup("XDG_DOWNLOAD_DIR", "/Downloads", allocator)
+	}
+}
+
+_user_pictures_dir :: proc(allocator: runtime.Allocator) -> (dir: string, err: Error) {
+	#partial switch ODIN_OS {
+	case .Darwin:
+		return _xdg_lookup("", "/Pictures", allocator)
+	case: // Unix
+		return _xdg_lookup("XDG_PICTURES_DIR", "/Pictures", allocator)
+	}
+}
+
+_user_public_dir :: proc(allocator: runtime.Allocator) -> (dir: string, err: Error) {
+	#partial switch ODIN_OS {
+	case .Darwin:
+		return _xdg_lookup("", "/Public", allocator)
+	case: // Unix
+		return _xdg_lookup("XDG_PUBLICSHARE_DIR", "/Public", allocator)
+	}
+}
+
+_user_videos_dir :: proc(allocator: runtime.Allocator) -> (dir: string, err: Error) {
+	#partial switch ODIN_OS {
+	case .Darwin:
+		return _xdg_lookup("", "/Movies", allocator)
+	case: // Unix
+		return _xdg_lookup("XDG_VIDEOS_DIR", "/Videos", allocator)
+	}
+}
+
+_user_home_dir :: proc(allocator: runtime.Allocator) -> (dir: string, err: Error) {
+	if v := get_env("HOME", allocator); v != "" {
+		return v, nil
+	}
+	err = .No_HOME_Variable
+	return
+}
+
+_xdg_lookup :: proc(xdg_key: string, fallback_suffix: string, allocator: runtime.Allocator) -> (dir: string, err: Error) {
+	temp_allocator  := TEMP_ALLOCATOR_GUARD({ allocator })
+
+	if xdg_key == "" { // Darwin doesn't have XDG paths.
+		dir = get_env("HOME", temp_allocator)
+		if dir == "" {
+			err = .No_HOME_Variable
+			return
+		}
+		return concatenate({dir, fallback_suffix}, allocator)
+	} else {
+		if strings.ends_with(xdg_key, "_DIR") {
+			dir = _xdg_user_dirs_lookup(xdg_key, allocator) or_return
+		} else {
+			dir = get_env(xdg_key, allocator)
+		}
+
+		if dir == "" {
+			dir = get_env("HOME", temp_allocator)
+			if dir == "" {
+				err = .No_HOME_Variable
+				return
+			}
+			dir = concatenate({dir, fallback_suffix}, allocator) or_return
+		}
+		return
+	}
+}
+
+// If `<config-dir>/user-dirs.dirs` doesn't exist, or `xdg_key` can't be found there: returns `""`
+_xdg_user_dirs_lookup :: proc(xdg_key: string, allocator: runtime.Allocator) -> (dir: string, err: Error) {
+	temp_allocator  := TEMP_ALLOCATOR_GUARD({ allocator })
+	config_dir      := user_config_dir(temp_allocator) or_return
+	user_dirs_path  := concatenate({config_dir, "/user-dirs.dirs"}, temp_allocator) or_return
+	content         := read_entire_file(user_dirs_path, temp_allocator) or_return
+
+	it := ini.Iterator{
+		section = "",
+		_src    = string(content),
+		options = ini.Options{
+			comment        = "#",
+			key_lower_case = false,
+		},
+	}
+
+	for k, v in ini.iterate(&it) {
+		if k == xdg_key {
+			we: posix.wordexp_t
+			defer posix.wordfree(&we)
+
+			if _err := posix.wordexp(strings.clone_to_cstring(v, temp_allocator), &we, nil); _err != nil || we.we_wordc != 1 {
+				return "", .Wordexp_Failed
+			}
+
+			return strings.clone_from_cstring(we.we_wordv[0], allocator)
+		}
+	}
+	return
+}

+ 79 - 0
core/os/os2/user_windows.odin

@@ -0,0 +1,79 @@
+package os2
+
+import "base:runtime"
+@(require) import win32 "core:sys/windows"
+
+_local_appdata :: proc(allocator: runtime.Allocator) -> (dir: string, err: Error) {
+	guid := win32.FOLDERID_LocalAppData
+	return _get_known_folder_path(&guid, allocator)
+}
+
+_local_appdata_or_roaming :: proc(allocator: runtime.Allocator, roaming: bool) -> (dir: string, err: Error) {
+	guid := win32.FOLDERID_LocalAppData
+	if roaming {
+		guid = win32.FOLDERID_RoamingAppData
+	}
+	return _get_known_folder_path(&guid, allocator)
+}
+
+_user_config_dir :: _local_appdata_or_roaming
+_user_data_dir :: _local_appdata_or_roaming
+
+_user_state_dir :: _local_appdata
+_user_log_dir :: _local_appdata
+_user_cache_dir :: _local_appdata
+
+_user_home_dir :: proc(allocator: runtime.Allocator) -> (dir: string, err: Error) {
+	guid := win32.FOLDERID_Profile
+	return _get_known_folder_path(&guid, allocator)
+}
+
+_user_music_dir :: proc(allocator: runtime.Allocator) -> (dir: string, err: Error) {
+	guid := win32.FOLDERID_Music
+	return _get_known_folder_path(&guid, allocator)
+}
+
+_user_desktop_dir :: proc(allocator: runtime.Allocator) -> (dir: string, err: Error) {
+	guid := win32.FOLDERID_Desktop
+	return _get_known_folder_path(&guid, allocator)
+}
+
+_user_documents_dir :: proc(allocator: runtime.Allocator) -> (dir: string, err: Error) {
+	guid := win32.FOLDERID_Documents
+	return _get_known_folder_path(&guid, allocator)
+}
+
+_user_downloads_dir :: proc(allocator: runtime.Allocator) -> (dir: string, err: Error) {
+	guid := win32.FOLDERID_Downloads
+	return _get_known_folder_path(&guid, allocator)
+}
+
+_user_pictures_dir :: proc(allocator: runtime.Allocator) -> (dir: string, err: Error) {
+	guid := win32.FOLDERID_Pictures
+	return _get_known_folder_path(&guid, allocator)
+}
+
+_user_public_dir :: proc(allocator: runtime.Allocator) -> (dir: string, err: Error) {
+	guid := win32.FOLDERID_Public
+	return _get_known_folder_path(&guid, allocator)
+}
+
+_user_videos_dir :: proc(allocator: runtime.Allocator) -> (dir: string, err: Error) {
+	guid := win32.FOLDERID_Videos
+	return _get_known_folder_path(&guid, allocator)
+}
+
+_get_known_folder_path :: proc(rfid: win32.REFKNOWNFOLDERID, allocator: runtime.Allocator) -> (dir: string, err: Error) {
+	// https://learn.microsoft.com/en-us/windows/win32/api/shlobj_core/nf-shlobj_core-shgetknownfolderpath
+	// See also `known_folders.odin` in `core:sys/windows` for the GUIDs.
+	path_w: win32.LPWSTR
+	res  := win32.SHGetKnownFolderPath(rfid, 0, nil, &path_w)
+	defer win32.CoTaskMemFree(path_w)
+
+	if res != 0 {
+		return "", .Invalid_Path
+	}
+
+	dir, _ = win32.wstring_to_utf8(path_w, -1, allocator)
+	return
+}

+ 4 - 0
core/os/os_js.odin

@@ -249,3 +249,7 @@ exit :: proc "contextless" (code: int) -> ! {
 current_thread_id :: proc "contextless" () -> int {
 current_thread_id :: proc "contextless" () -> int {
 	return 0
 	return 0
 }
 }
+
+lookup_env :: proc(key: string, allocator := context.allocator) -> (value: string, found: bool) {
+	return "", false
+}

+ 36 - 0
core/path/filepath/path_js.odin

@@ -0,0 +1,36 @@
+package filepath
+
+import "base:runtime"
+
+import "core:strings"
+
+SEPARATOR :: '/'
+SEPARATOR_STRING :: `/`
+LIST_SEPARATOR :: ':'
+
+is_reserved_name :: proc(path: string) -> bool {
+	return false
+}
+
+is_abs :: proc(path: string) -> bool {
+	return strings.has_prefix(path, "/")
+}
+
+abs :: proc(path: string, allocator := context.allocator) -> (string, bool) {
+	if is_abs(path) {
+		return strings.clone(string(path), allocator), true
+	}
+
+	return path, false
+}
+
+join :: proc(elems: []string, allocator := context.allocator) -> (joined: string, err: runtime.Allocator_Error) #optional_allocator_error {
+	for e, i in elems {
+		if e != "" {
+			runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD(ignore = context.temp_allocator == allocator)
+			p := strings.join(elems[i:], SEPARATOR_STRING, context.temp_allocator) or_return
+			return clean(p, allocator)
+		}
+	}
+	return "", nil
+}

+ 211 - 10
core/simd/simd.odin

@@ -21,20 +21,17 @@ package simd
 
 
 import "base:builtin"
 import "base:builtin"
 import "base:intrinsics"
 import "base:intrinsics"
+import "base:runtime"
 
 
 /*
 /*
 Check if SIMD is software-emulated on a target platform.
 Check if SIMD is software-emulated on a target platform.
 
 
-This value is `false`, when the compile-time target has the hardware support for
-at 128-bit (or wider) SIMD. If the compile-time target lacks the hardware support
-for 128-bit SIMD, this value is `true`, and all SIMD operations will likely be
+This value is `true`, when the compile-time target has the hardware support for
+at least 128-bit (or wider) SIMD. If the compile-time target lacks the hardware support
+for 128-bit SIMD, this value is `false`, and all SIMD operations will likely be
 emulated.
 emulated.
 */
 */
-IS_EMULATED :: true when (ODIN_ARCH == .amd64 || ODIN_ARCH == .i386) && !intrinsics.has_target_feature("sse2") else
-	true when (ODIN_ARCH == .arm64 || ODIN_ARCH == .arm32) && !intrinsics.has_target_feature("neon") else
-	true when (ODIN_ARCH == .wasm64p32 || ODIN_ARCH == .wasm32) && !intrinsics.has_target_feature("simd128") else
-	true when (ODIN_ARCH == .riscv64) && !intrinsics.has_target_feature("v") else
-	false
+HAS_HARDWARE_SIMD :: runtime.HAS_HARDWARE_SIMD
 
 
 /*
 /*
 Vector of 16 `u8` lanes (128 bits).
 Vector of 16 `u8` lanes (128 bits).
@@ -1759,7 +1756,103 @@ Returns:
 replace :: intrinsics.simd_replace
 replace :: intrinsics.simd_replace
 
 
 /*
 /*
-Reduce a vector to a scalar by adding up all the lanes.
+Reduce a vector to a scalar by adding up all the lanes in a bisecting fashion.
+
+This procedure returns a scalar that is the sum of all lanes, calculated by
+bisecting the vector into two parts, where the first contains lanes [0, N/2)
+and the second contains lanes [N/2, N), and adding the two halves element-wise
+to produce N/2 values. This is repeated until only a single element remains.
+This order may be faster to compute than the ordered sum for floats, as it can
+often be better parallelized.
+
+The order of the sum may be important for accounting for precision errors in
+floating-point computation, as floating-point addition is not associative, that
+is `(a+b)+c` may not be equal to `a+(b+c)`.
+
+Inputs:
+- `v`: The vector to reduce.
+
+Result:
+- Sum of all lanes, as a scalar.
+
+**Operation**:
+
+	for n > 1 {
+		n = n / 2
+		for i in 0 ..< n {
+			a[i] += a[i+n]
+		}
+	}
+	res := a[0]
+
+Graphical representation of the operation for N=4:
+
+	     +-----------------------+
+	     | v0  | v1  | v2  | v3  |
+	     +-----------------------+
+	        |     |     |     |
+	       [+]<-- | ---'      |
+	        |    [+]<--------'
+	        |     |
+	        `>[+]<'
+	           |
+	           v
+	        +-----+
+	result: | y0  |
+	        +-----+
+*/
+reduce_add_bisect :: intrinsics.simd_reduce_add_bisect
+
+/*
+Reduce a vector to a scalar by multiplying up all the lanes in a bisecting fashion.
+
+This procedure returns a scalar that is the product of all lanes, calculated by
+bisecting the vector into two parts, where the first contains indices [0, N/2)
+and the second contains indices [N/2, N), and multiplying the two halves
+together element-wise to produce N/2 values. This is repeated until only a
+single element remains. This order may be faster to compute than the ordered
+product for floats, as it can often be better parallelized.
+
+The order of the product may be important for accounting for precision errors
+in floating-point computation, as floating-point multiplication is not
+associative, that is `(a*b)*c` may not be equal to `a*(b*c)`.
+
+Inputs:
+- `v`: The vector to reduce.
+
+Result:
+- Product of all lanes, as a scalar.
+
+**Operation**:
+
+	for n > 1 {
+		n = n / 2
+		for i in 0 ..< n {
+			a[i] *= a[i+n]
+		}
+	}
+	res := a[0]
+
+Graphical representation of the operation for N=4:
+
+	     +-----------------------+
+	     | v0  | v1  | v2  | v3  |
+	     +-----------------------+
+	        |     |     |     |
+	       [x]<-- | ---'      |
+	        |    [x]<--------'
+	        |     |
+	        `>[x]<'
+	           |
+	           v
+	        +-----+
+	result: | y0  |
+	        +-----+
+*/
+reduce_mul_bisect :: intrinsics.simd_reduce_mul_bisect
+
+/*
+Reduce a vector to a scalar by adding up all the lanes in an ordered fashion.
 
 
 This procedure returns a scalar that is the ordered sum of all lanes. The
 This procedure returns a scalar that is the ordered sum of all lanes. The
 ordered sum may be important for accounting for precision errors in
 ordered sum may be important for accounting for precision errors in
@@ -1782,7 +1875,7 @@ Result:
 reduce_add_ordered :: intrinsics.simd_reduce_add_ordered
 reduce_add_ordered :: intrinsics.simd_reduce_add_ordered
 
 
 /*
 /*
-Reduce a vector to a scalar by multiplying all the lanes.
+Reduce a vector to a scalar by multiplying all the lanes in an ordered fashion.
 
 
 This procedure returns a scalar that is the ordered product of all lanes.
 This procedure returns a scalar that is the ordered product of all lanes.
 The ordered product may be important for accounting for precision errors in
 The ordered product may be important for accounting for precision errors in
@@ -1804,6 +1897,100 @@ Result:
 */
 */
 reduce_mul_ordered :: intrinsics.simd_reduce_mul_ordered
 reduce_mul_ordered :: intrinsics.simd_reduce_mul_ordered
 
 
+/*
+Reduce a vector to a scalar by adding up all the lanes in a pairwise fashion.
+
+This procedure returns a scalar that is the sum of all lanes, calculated by
+adding each even-indexed element with the following odd-indexed element to
+produce N/2 values. This is repeated until only a single element remains. This
+order is supported by hardware instructions for some types/architectures (e.g.
+i16/i32/f32/f64 on x86 SSE, i8/i16/i32/f32 on ARM NEON).
+
+The order of the sum may be important for accounting for precision errors in
+floating-point computation, as floating-point addition is not associative, that
+is `(a+b)+c` may not be equal to `a+(b+c)`.
+
+Inputs:
+- `v`: The vector to reduce.
+
+Result:
+- Sum of all lanes, as a scalar.
+
+**Operation**:
+
+	for n > 1 {
+		n = n / 2
+		for i in 0 ..< n {
+			a[i] = a[2*i+0] + a[2*i+1]
+		}
+	}
+	res := a[0]
+
+Graphical representation of the operation for N=4:
+
+	   +-----------------------+
+	v: | v0  | v1  | v2  | v3  |
+	   +-----------------------+
+	      |     |     |     |
+	      `>[+]<'     `>[+]<'
+	         |           |
+	         `--->[+]<--'
+	               |
+	               v
+	            +-----+
+	    result: | y0  |
+	            +-----+
+*/
+reduce_add_pairs :: intrinsics.simd_reduce_add_pairs
+
+/*
+Reduce a vector to a scalar by multiplying all the lanes in a pairwise fashion.
+
+This procedure returns a scalar that is the product of all lanes, calculated by
+bisecting the vector into two parts, where the first contains lanes [0, N/2)
+and the second contains lanes [N/2, N), and multiplying the two halves together
+multiplying each even-indexed element with the following odd-indexed element to
+produce N/2 values. This is repeated until only a single element remains. This
+order may be faster to compute than the ordered product for floats, as it can
+often be better parallelized.
+
+The order of the product may be important for accounting for precision errors
+in floating-point computation, as floating-point multiplication is not
+associative, that is `(a*b)*c` may not be equal to `a*(b*c)`.
+
+Inputs:
+- `v`: The vector to reduce.
+
+Result:
+- Product of all lanes, as a scalar.
+
+**Operation**:
+
+	for n > 1 {
+		n = n / 2
+		for i in 0 ..< n {
+			a[i] = a[2*i+0] * a[2*i+1]
+		}
+	}
+	res := a[0]
+
+Graphical representation of the operation for N=4:
+
+	   +-----------------------+
+	v: | v0  | v1  | v2  | v3  |
+	   +-----------------------+
+	      |     |     |     |
+	      `>[x]<'     `>[x]<'
+	         |           |
+	         `--->[x]<--'
+	               |
+	               v
+	            +-----+
+	    result: | y0  |
+	            +-----+
+*/
+reduce_mul_pairs :: intrinsics.simd_reduce_mul_pairs
+
 /*
 /*
 Reduce a vector to a scalar by finding the minimum value between all of the lanes.
 Reduce a vector to a scalar by finding the minimum value between all of the lanes.
 
 
@@ -2510,3 +2697,17 @@ Example:
 recip :: #force_inline proc "contextless" (v: $T/#simd[$LANES]$E) -> T where intrinsics.type_is_float(E) {
 recip :: #force_inline proc "contextless" (v: $T/#simd[$LANES]$E) -> T where intrinsics.type_is_float(E) {
 	return T(1) / v
 	return T(1) / v
 }
 }
+
+
+/*
+Create a vector where each lane contains the index of that lane.
+Inputs:
+- `V`: The type of the vector to create.
+Result:
+- A vector of the given type, where each lane contains the index of that lane.
+**Operation**:
+	for i in 0 ..< N {
+		res[i] = i
+	}
+*/
+indices :: intrinsics.simd_indices

+ 79 - 0
core/simd/x86/bmi.odin

@@ -0,0 +1,79 @@
+#+build i386, amd64
+package simd_x86
+
+import "base:intrinsics"
+
+@(require_results, enable_target_feature="bmi")
+_andn_u32 :: #force_inline proc "c" (a, b: u32) -> u32 {
+	return a &~ b
+}
+@(require_results, enable_target_feature="bmi")
+_andn_u64 :: #force_inline proc "c" (a, b: u64) -> u64 {
+	return a &~ b
+}
+
+@(require_results, enable_target_feature="bmi")
+_bextr_u32 :: #force_inline proc "c" (a, start, len: u32) -> u32 {
+	return bextr_u32(a, (start & 0xff) | (len << 8))
+}
+@(require_results, enable_target_feature="bmi")
+_bextr_u64 :: #force_inline proc "c" (a: u64, start, len: u32) -> u64 {
+	return bextr_u64(a, cast(u64)((start & 0xff) | (len << 8)))
+}
+
+@(require_results, enable_target_feature="bmi")
+_bextr2_u32 :: #force_inline proc "c" (a, control: u32) -> u32 {
+	return bextr_u32(a, control)
+}
+@(require_results, enable_target_feature="bmi")
+_bextr2_u64 :: #force_inline proc "c" (a, control: u64) -> u64 {
+	return bextr_u64(a, control)
+}
+
+@(require_results, enable_target_feature="bmi")
+_blsi_u32 :: #force_inline proc "c" (a: u32) -> u32 {
+	return a & -a
+}
+@(require_results, enable_target_feature="bmi")
+_blsi_u64 :: #force_inline proc "c" (a: u64) -> u64 {
+	return a & -a
+}
+
+@(require_results, enable_target_feature="bmi")
+_blsmsk_u32 :: #force_inline proc "c" (a: u32) -> u32 {
+	return a ~ (a-1)
+}
+@(require_results, enable_target_feature="bmi")
+_blsmsk_u64 :: #force_inline proc "c" (a: u64) -> u64 {
+	return a ~ (a-1)
+}
+
+@(require_results, enable_target_feature="bmi")
+_blsr_u32 :: #force_inline proc "c" (a: u32) -> u32 {
+	return a & (a-1)
+}
+@(require_results, enable_target_feature="bmi")
+_blsr_u64 :: #force_inline proc "c" (a: u64) -> u64 {
+	return a & (a-1)
+}
+
+@(require_results, enable_target_feature = "bmi")
+_tzcnt_u16 :: #force_inline proc "c" (a: u16) -> u16 {
+	return intrinsics.count_trailing_zeros(a)
+}
+@(require_results, enable_target_feature = "bmi")
+_tzcnt_u32 :: #force_inline proc "c" (a: u32) -> u32 {
+	return intrinsics.count_trailing_zeros(a)
+}
+@(require_results, enable_target_feature = "bmi")
+_tzcnt_u64 :: #force_inline proc "c" (a: u64) -> u64 {
+	return intrinsics.count_trailing_zeros(a)
+}
+
+@(private, default_calling_convention = "none")
+foreign _ {
+	@(link_name = "llvm.x86.bmi.bextr.32")
+	bextr_u32 :: proc(a, control: u32) -> u32 ---
+	@(link_name = "llvm.x86.bmi.bextr.64")
+	bextr_u64 :: proc(a, control: u64) -> u64 ---
+}

+ 46 - 0
core/simd/x86/bmi2.odin

@@ -0,0 +1,46 @@
+#+build i386, amd64
+package simd_x86
+
+@(require_results, enable_target_feature = "bmi2")
+_bzhi_u32 :: #force_inline proc "c" (a, index: u32) -> u32 {
+	return bzhi_u32(a, index)
+}
+@(require_results, enable_target_feature = "bmi2")
+_bzhi_u64 :: #force_inline proc "c" (a, index: u64) -> u64 {
+	return bzhi_u64(a, index)
+}
+
+@(require_results, enable_target_feature = "bmi2")
+_pdep_u32 :: #force_inline proc "c" (a, mask: u32) -> u32 {
+	return pdep_u32(a, mask)
+}
+@(require_results, enable_target_feature = "bmi2")
+_pdep_u64 :: #force_inline proc "c" (a, mask: u64) -> u64 {
+	return pdep_u64(a, mask)
+}
+
+@(require_results, enable_target_feature = "bmi2")
+_pext_u32 :: #force_inline proc "c" (a, mask: u32) -> u32 {
+	return pext_u32(a, mask)
+}
+@(require_results, enable_target_feature = "bmi2")
+_pext_u64 :: #force_inline proc "c" (a, mask: u64) -> u64 {
+	return pext_u64(a, mask)
+}
+
+
+@(private, default_calling_convention = "none")
+foreign _ {
+	@(link_name = "llvm.x86.bmi.bzhi.32")
+	bzhi_u32 :: proc(a, index: u32) -> u32 ---
+	@(link_name = "llvm.x86.bmi.bzhi.64")
+	bzhi_u64 :: proc(a, index: u64) -> u64 ---
+	@(link_name = "llvm.x86.bmi.pdep.32")
+	pdep_u32 :: proc(a, mask: u32) -> u32 ---
+	@(link_name = "llvm.x86.bmi.pdep.64")
+	pdep_u64 :: proc(a, mask: u64) -> u64 ---
+	@(link_name = "llvm.x86.bmi.pext.32")
+	pext_u32 :: proc(a, mask: u32) -> u32 ---
+	@(link_name = "llvm.x86.bmi.pext.64")
+	pext_u64 :: proc(a, mask: u64) -> u64 ---
+}

+ 0 - 46
core/sort/sort.odin

@@ -30,14 +30,6 @@ sort :: proc(it: Interface) {
 	_quick_sort(it, 0, n, max_depth(n))
 	_quick_sort(it, 0, n, max_depth(n))
 }
 }
 
 
-
-@(deprecated="use slice.sort")
-slice :: proc(array: $T/[]$E) where ORD(E) {
-	_slice.sort(array)
-	// s := array;
-	// sort(slice_interface(&s));
-}
-
 slice_interface :: proc(s: ^$T/[]$E) -> Interface where ORD(E) {
 slice_interface :: proc(s: ^$T/[]$E) -> Interface where ORD(E) {
 	return Interface{
 	return Interface{
 		collection = rawptr(s),
 		collection = rawptr(s),
@@ -80,31 +72,6 @@ reverse_sort :: proc(it: Interface) {
 	sort(reverse_interface(&it))
 	sort(reverse_interface(&it))
 }
 }
 
 
-@(deprecated="use slice.reverse")
-reverse_slice :: proc(array: $T/[]$E) where ORD(E) {
-	_slice.reverse(array)
-	/*
-	s := array;
-	sort(Interface{
-		collection = rawptr(&s),
-		len = proc(it: Interface) -> int {
-			s := (^T)(it.collection);
-			return len(s^);
-		},
-		less = proc(it: Interface, i, j: int) -> bool {
-			s := (^T)(it.collection);
-			return s[j] < s[i]; // manual set up
-		},
-		swap = proc(it: Interface, i, j: int) {
-			s := (^T)(it.collection);
-			s[i], s[j] = s[j], s[i];
-		},
-	});
-	*/
-}
-
-
-
 is_sorted :: proc(it: Interface) -> bool {
 is_sorted :: proc(it: Interface) -> bool {
 	n := it->len()
 	n := it->len()
 	for i := n-1; i > 0; i -= 1 {
 	for i := n-1; i > 0; i -= 1 {
@@ -294,11 +261,6 @@ _insertion_sort :: proc(it: Interface, a, b: int) {
 	}
 	}
 }
 }
 
 
-
-
-
-
-// @(deprecated="use sort.sort or slice.sort_by")
 bubble_sort_proc :: proc(array: $A/[]$T, f: proc(T, T) -> int) {
 bubble_sort_proc :: proc(array: $A/[]$T, f: proc(T, T) -> int) {
 	assert(f != nil)
 	assert(f != nil)
 	count := len(array)
 	count := len(array)
@@ -327,7 +289,6 @@ bubble_sort_proc :: proc(array: $A/[]$T, f: proc(T, T) -> int) {
 	}
 	}
 }
 }
 
 
-// @(deprecated="use sort.sort_slice or slice.sort")
 bubble_sort :: proc(array: $A/[]$T) where intrinsics.type_is_ordered(T) {
 bubble_sort :: proc(array: $A/[]$T) where intrinsics.type_is_ordered(T) {
 	count := len(array)
 	count := len(array)
 
 
@@ -355,7 +316,6 @@ bubble_sort :: proc(array: $A/[]$T) where intrinsics.type_is_ordered(T) {
 	}
 	}
 }
 }
 
 
-// @(deprecated="use sort.sort or slice.sort_by")
 quick_sort_proc :: proc(array: $A/[]$T, f: proc(T, T) -> int) {
 quick_sort_proc :: proc(array: $A/[]$T, f: proc(T, T) -> int) {
 	assert(f != nil)
 	assert(f != nil)
 	a := array
 	a := array
@@ -384,7 +344,6 @@ quick_sort_proc :: proc(array: $A/[]$T, f: proc(T, T) -> int) {
 	quick_sort_proc(a[i:n], f)
 	quick_sort_proc(a[i:n], f)
 }
 }
 
 
-// @(deprecated="use sort.sort_slice or slice.sort")
 quick_sort :: proc(array: $A/[]$T) where intrinsics.type_is_ordered(T) {
 quick_sort :: proc(array: $A/[]$T) where intrinsics.type_is_ordered(T) {
 	a := array
 	a := array
 	n := len(a)
 	n := len(a)
@@ -420,7 +379,6 @@ _log2 :: proc(x: int) -> int {
 	return res
 	return res
 }
 }
 
 
-// @(deprecated="use sort.sort or slice.sort_by")
 merge_sort_proc :: proc(array: $A/[]$T, f: proc(T, T) -> int) {
 merge_sort_proc :: proc(array: $A/[]$T, f: proc(T, T) -> int) {
 	merge :: proc(a: A, start, mid, end: int, f: proc(T, T) -> int) {
 	merge :: proc(a: A, start, mid, end: int, f: proc(T, T) -> int) {
 		s, m := start, mid
 		s, m := start, mid
@@ -462,7 +420,6 @@ merge_sort_proc :: proc(array: $A/[]$T, f: proc(T, T) -> int) {
 	internal_sort(array, 0, len(array)-1, f)
 	internal_sort(array, 0, len(array)-1, f)
 }
 }
 
 
-// @(deprecated="use sort.sort_slice or slice.sort")
 merge_sort :: proc(array: $A/[]$T) where intrinsics.type_is_ordered(T) {
 merge_sort :: proc(array: $A/[]$T) where intrinsics.type_is_ordered(T) {
 	merge :: proc(a: A, start, mid, end: int) {
 	merge :: proc(a: A, start, mid, end: int) {
 		s, m := start, mid
 		s, m := start, mid
@@ -504,8 +461,6 @@ merge_sort :: proc(array: $A/[]$T) where intrinsics.type_is_ordered(T) {
 	internal_sort(array, 0, len(array)-1)
 	internal_sort(array, 0, len(array)-1)
 }
 }
 
 
-
-// @(deprecated="use sort.sort or slice.sort_by")
 heap_sort_proc :: proc(array: $A/[]$T, f: proc(T, T) -> int) {
 heap_sort_proc :: proc(array: $A/[]$T, f: proc(T, T) -> int) {
 	sift_proc :: proc(a: A, pi: int, n: int, f: proc(T, T) -> int) #no_bounds_check {
 	sift_proc :: proc(a: A, pi: int, n: int, f: proc(T, T) -> int) #no_bounds_check {
 		p := pi
 		p := pi
@@ -540,7 +495,6 @@ heap_sort_proc :: proc(array: $A/[]$T, f: proc(T, T) -> int) {
 	}
 	}
 }
 }
 
 
-// @(deprecated="use sort.sort_slice or slice.sort")
 heap_sort :: proc(array: $A/[]$T) where intrinsics.type_is_ordered(T) {
 heap_sort :: proc(array: $A/[]$T) where intrinsics.type_is_ordered(T) {
 	sift :: proc(a: A, pi: int, n: int) #no_bounds_check {
 	sift :: proc(a: A, pi: int, n: int) #no_bounds_check {
 		p := pi
 		p := pi

+ 15 - 15
core/strconv/decimal/decimal.odin

@@ -12,11 +12,11 @@ Decimal :: struct {
 Sets a Decimal from a given string `s`. The string is expected to represent a float. Stores parsed number in the given Decimal structure.
 Sets a Decimal from a given string `s`. The string is expected to represent a float. Stores parsed number in the given Decimal structure.
 If parsing fails, the Decimal will be left in an undefined state.
 If parsing fails, the Decimal will be left in an undefined state.
 
 
-**Inputs**  
+**Inputs**
 - d: Pointer to a Decimal struct where the parsed result will be stored
 - d: Pointer to a Decimal struct where the parsed result will be stored
 - s: The input string representing the floating-point number
 - s: The input string representing the floating-point number
 
 
-**Returns**  
+**Returns**
 - ok: A boolean indicating whether the parsing was successful
 - ok: A boolean indicating whether the parsing was successful
 */
 */
 set :: proc(d: ^Decimal, s: string) -> (ok: bool) {
 set :: proc(d: ^Decimal, s: string) -> (ok: bool) {
@@ -104,11 +104,11 @@ set :: proc(d: ^Decimal, s: string) -> (ok: bool) {
 /*
 /*
 Converts a Decimal to a string representation, using the provided buffer as storage.
 Converts a Decimal to a string representation, using the provided buffer as storage.
 
 
-**Inputs**  
+**Inputs**
 - buf: A byte slice buffer to hold the resulting string
 - buf: A byte slice buffer to hold the resulting string
 - a: The struct to be converted to a string
 - a: The struct to be converted to a string
 
 
-**Returns**  
+**Returns**
 - A string representation of the Decimal
 - A string representation of the Decimal
 */
 */
 decimal_to_string :: proc(buf: []byte, a: ^Decimal) -> string {
 decimal_to_string :: proc(buf: []byte, a: ^Decimal) -> string {
@@ -150,7 +150,7 @@ decimal_to_string :: proc(buf: []byte, a: ^Decimal) -> string {
 /*
 /*
 Trims trailing zeros in the given Decimal, updating the count and decimal_point values as needed.
 Trims trailing zeros in the given Decimal, updating the count and decimal_point values as needed.
 
 
-**Inputs**  
+**Inputs**
 - a: Pointer to the Decimal struct to be trimmed
 - a: Pointer to the Decimal struct to be trimmed
 */
 */
 trim :: proc(a: ^Decimal) {
 trim :: proc(a: ^Decimal) {
@@ -166,7 +166,7 @@ Converts a given u64 integer `idx` to its Decimal representation in the provided
 
 
 **Used for internal Decimal Operations.**
 **Used for internal Decimal Operations.**
 
 
-**Inputs**  
+**Inputs**
 - a: Where the result will be stored
 - a: Where the result will be stored
 - idx: The value to be assigned to the Decimal
 - idx: The value to be assigned to the Decimal
 */
 */
@@ -190,11 +190,11 @@ assign :: proc(a: ^Decimal, idx: u64) {
 	trim(a)
 	trim(a)
 }
 }
 /*
 /*
-Shifts the Decimal value to the right by k positions. 
+Shifts the Decimal value to the right by k positions.
 
 
 **Used for internal Decimal Operations.**
 **Used for internal Decimal Operations.**
 
 
-**Inputs**  
+**Inputs**
 - a: The Decimal struct to be shifted
 - a: The Decimal struct to be shifted
 - k: The number of positions to shift right
 - k: The number of positions to shift right
 */
 */
@@ -344,7 +344,7 @@ Shifts the decimal of the input value to the left by `k` places
 
 
 WARNING: asserts `k < 61`
 WARNING: asserts `k < 61`
 
 
-**Inputs**  
+**Inputs**
 - a: The Decimal to be modified
 - a: The Decimal to be modified
 - k: The number of places to shift the decimal to the left
 - k: The number of places to shift the decimal to the left
 */
 */
@@ -405,7 +405,7 @@ shift_left :: proc(a: ^Decimal, k: uint) #no_bounds_check {
 /*
 /*
 Shifts the decimal of the input value by the specified number of places
 Shifts the decimal of the input value by the specified number of places
 
 
-**Inputs**  
+**Inputs**
 - a: The Decimal to be modified
 - a: The Decimal to be modified
 - i: The number of places to shift the decimal (positive for left shift, negative for right shift)
 - i: The number of places to shift the decimal (positive for left shift, negative for right shift)
 */
 */
@@ -435,7 +435,7 @@ shift :: proc(a: ^Decimal, i: int) {
 /*
 /*
 Determines if the Decimal can be rounded up at the given digit index
 Determines if the Decimal can be rounded up at the given digit index
 
 
-**Inputs**  
+**Inputs**
 - a: The Decimal to check
 - a: The Decimal to check
 - nd: The digit index to consider for rounding up
 - nd: The digit index to consider for rounding up
 
 
@@ -455,7 +455,7 @@ can_round_up :: proc(a: ^Decimal, nd: int) -> bool {
 /*
 /*
 Rounds the Decimal at the given digit index
 Rounds the Decimal at the given digit index
 
 
-**Inputs**  
+**Inputs**
 - a: The Decimal to be modified
 - a: The Decimal to be modified
 - nd: The digit index to round
 - nd: The digit index to round
 */
 */
@@ -470,7 +470,7 @@ round :: proc(a: ^Decimal, nd: int) {
 /*
 /*
 Rounds the Decimal up at the given digit index
 Rounds the Decimal up at the given digit index
 
 
-**Inputs**  
+**Inputs**
 - a: The Decimal to be modified
 - a: The Decimal to be modified
 - nd: The digit index to round up
 - nd: The digit index to round up
 */
 */
@@ -493,7 +493,7 @@ round_up :: proc(a: ^Decimal, nd: int) {
 /*
 /*
 Rounds down the decimal value to the specified number of decimal places
 Rounds down the decimal value to the specified number of decimal places
 
 
-**Inputs**  
+**Inputs**
 - a: The Decimal value to be rounded down
 - a: The Decimal value to be rounded down
 - nd: The number of decimal places to round down to
 - nd: The number of decimal places to round down to
 
 
@@ -522,7 +522,7 @@ round_down :: proc(a: ^Decimal, nd: int) {
 /*
 /*
 Extracts the rounded integer part of a decimal value
 Extracts the rounded integer part of a decimal value
 
 
-**Inputs**  
+**Inputs**
 - a: A pointer to the Decimal value to extract the rounded integer part from
 - a: A pointer to the Decimal value to extract the rounded integer part from
 
 
 WARNING: There are no guarantees about overflow.
 WARNING: There are no guarantees about overflow.

+ 38 - 0
core/strconv/deprecated.odin

@@ -0,0 +1,38 @@
+package strconv
+
+// (2025-06-05) These procedures are to be removed at a later release.
+
+@(deprecated="Use write_bits instead")
+append_bits :: proc(buf: []byte, x: u64, base: int, is_signed: bool, bit_size: int, digits: string, flags: Int_Flags) -> string {
+	return write_bits(buf, x, base, is_signed, bit_size, digits, flags)
+}
+
+@(deprecated="Use write_bits_128 instead")
+append_bits_128 :: proc(buf: []byte, x: u128, base: int, is_signed: bool, bit_size: int, digits: string, flags: Int_Flags) -> string {
+	return write_bits_128(buf, x, base, is_signed, bit_size, digits, flags)
+}
+
+@(deprecated="Use write_bool instead")
+append_bool :: proc(buf: []byte, b: bool) -> string {
+	return write_bool(buf, b)
+}
+
+@(deprecated="Use write_uint instead")
+append_uint :: proc(buf: []byte, u: u64, base: int) -> string {
+	return write_uint(buf, u, base)
+}
+
+@(deprecated="Use write_int instead")
+append_int :: proc(buf: []byte, i: i64, base: int) -> string {
+	return write_int(buf, i, base)
+}
+
+@(deprecated="Use write_u128 instead")
+append_u128 :: proc(buf: []byte, u: u128, base: int) -> string {
+	return write_u128(buf, u, base)
+}
+
+@(deprecated="Use write_float instead")
+append_float :: proc(buf: []byte, f: f64, fmt: byte, prec, bit_size: int) -> string {
+	return write_float(buf, f, fmt, prec, bit_size)
+}

Beberapa file tidak ditampilkan karena terlalu banyak file yang berubah dalam diff ini