Browse Source

Merge branch 'master' into macharena

Colin Davidson 2 months ago
parent
commit
389439ccb9
100 changed files with 3043 additions and 1325 deletions
  1. 45 51
      .github/workflows/ci.yml
  2. 60 0
      .github/workflows/cover.yml
  3. 1 0
      .gitignore
  4. 2 2
      base/runtime/core_builtin.odin
  5. 7 7
      base/runtime/default_temp_allocator_arena.odin
  6. 74 0
      base/sanitizer/memory.odin
  7. 75 0
      check_all.bat
  8. 78 0
      check_all.sh
  9. 1 0
      codecov.yml
  10. 280 56
      core/container/queue/queue.odin
  11. 23 0
      core/dynlib/lb_haiku.odin
  12. 2 1
      core/encoding/xml/xml_reader.odin
  13. 2 2
      core/flags/constants.odin
  14. 16 4
      core/flags/doc.odin
  15. 1 1
      core/flags/errors.odin
  16. 3 3
      core/flags/example/example.odin
  17. 4 4
      core/flags/internal_assignment.odin
  18. 1 1
      core/flags/internal_parsing.odin
  19. 13 8
      core/flags/internal_validation.odin
  20. 2 2
      core/flags/parsing.odin
  21. 15 15
      core/flags/usage.odin
  22. 2 3
      core/math/big/api.odin
  23. 2 2
      core/math/big/internal.odin
  24. 1 1
      core/math/big/radix.odin
  25. 5 3
      core/math/big/rat.odin
  26. 268 162
      core/mem/allocators.odin
  27. 4 4
      core/mem/rollback_stack_allocator.odin
  28. 15 14
      core/mem/tlsf/tlsf_internal.odin
  29. 12 12
      core/mem/virtual/arena.odin
  30. 5 5
      core/mem/virtual/virtual.odin
  31. 4 0
      core/net/common.odin
  32. 29 47
      core/net/dns.odin
  33. 1 1
      core/net/dns_unix.odin
  34. 17 0
      core/net/errors.odin
  35. 17 0
      core/net/errors_darwin.odin
  36. 18 0
      core/net/errors_freebsd.odin
  37. 16 0
      core/net/errors_linux.odin
  38. 7 0
      core/net/errors_others.odin
  39. 11 0
      core/net/errors_windows.odin
  40. 8 1
      core/net/socket.odin
  41. 15 2
      core/net/socket_darwin.odin
  42. 16 2
      core/net/socket_freebsd.odin
  43. 15 2
      core/net/socket_linux.odin
  44. 16 2
      core/net/socket_windows.odin
  45. 10 5
      core/odin/parser/parser.odin
  46. 39 2
      core/os/env_windows.odin
  47. 4 0
      core/os/errors.odin
  48. 53 0
      core/os/os.odin
  49. 79 5
      core/os/os2/env.odin
  50. 18 1
      core/os/os2/env_linux.odin
  51. 31 1
      core/os/os2/env_posix.odin
  52. 29 1
      core/os/os2/env_wasi.odin
  53. 31 1
      core/os/os2/env_windows.odin
  54. 2 2
      core/os/os2/errors.odin
  55. 1 0
      core/os/os2/file_linux.odin
  56. 1 9
      core/os/os2/user_posix.odin
  57. 37 3
      core/os/os_darwin.odin
  58. 40 7
      core/os/os_freebsd.odin
  59. 41 6
      core/os/os_haiku.odin
  60. 22 2
      core/os/os_js.odin
  61. 40 7
      core/os/os_linux.odin
  62. 40 7
      core/os/os_netbsd.odin
  63. 40 6
      core/os/os_openbsd.odin
  64. 30 1
      core/os/os_wasi.odin
  65. 9 1
      core/os/os_windows.odin
  66. 19 0
      core/slice/slice.odin
  67. 107 48
      core/sync/chan/chan.odin
  68. 9 0
      core/sys/darwin/xnu_system_call_wrappers.odin
  69. 558 558
      core/sys/es/api.odin
  70. 5 0
      core/sys/freebsd/constants.odin
  71. 1 0
      core/sys/info/platform_darwin.odin
  72. 18 15
      core/sys/linux/bits.odin
  73. 4 1
      core/sys/linux/constants.odin
  74. 3 8
      core/sys/linux/types.odin
  75. 4 1
      core/sys/posix/dlfcn.odin
  76. 3 1
      core/sys/posix/posix.odin
  77. 10 0
      core/sys/posix/posix_other.odin
  78. 5 0
      core/sys/posix/posix_unix.odin
  79. 1 1
      core/sys/windows/gdi32.odin
  80. 1 0
      core/sys/windows/kernel32.odin
  81. 68 4
      core/sys/windows/util.odin
  82. 2 2
      core/sys/windows/winmm.odin
  83. 5 6
      core/terminal/internal.odin
  84. 12 5
      core/testing/runner.odin
  85. 24 1
      core/testing/signal_handler.odin
  86. 35 7
      core/testing/signal_handler_libc.odin
  87. 73 0
      core/testing/testing.odin
  88. 0 4
      core/text/regex/regex.odin
  89. 26 13
      core/thread/thread.odin
  90. 14 0
      core/thread/thread_pool.odin
  91. 5 7
      core/thread/thread_unix.odin
  92. 9 9
      core/thread/thread_windows.odin
  93. 1 6
      core/time/timezone/tzif.odin
  94. 4 1
      examples/all/all_vendor.odin
  95. 1 0
      src/bug_report.cpp
  96. 8 1
      src/build_settings.cpp
  97. 5 0
      src/check_builtin.cpp
  98. 8 0
      src/check_decl.cpp
  99. 178 129
      src/check_expr.cpp
  100. 11 10
      src/check_stmt.cpp

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

@@ -30,13 +30,13 @@ jobs:
           gmake -C vendor/stb/src
           gmake -C vendor/stb/src
           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:netbsd_amd64
-          ./odin check examples/all -vet -strict-style -disallow-do -target:netbsd_arm64
-          ./odin check examples/all/sdl3 -vet -strict-style -disallow-do -target:netbsd_amd64 -no-entry-point
-          ./odin check examples/all/sdl3 -vet -strict-style -disallow-do -target:netbsd_arm64 -no-entry-point
-          ./odin test tests/core/normal.odin -file -all-packages -vet -strict-style -disallow-do -define:ODIN_TEST_FANCY=false -define:ODIN_TEST_FAIL_ON_BAD_MEMORY=true
-          ./odin test tests/core/speed.odin -file -all-packages -vet -strict-style -disallow-do -o:speed -define:ODIN_TEST_FANCY=false -define:ODIN_TEST_FAIL_ON_BAD_MEMORY=true
-          ./odin test tests/vendor -all-packages -vet -strict-style -disallow-do -define:ODIN_TEST_FANCY=false -define:ODIN_TEST_FAIL_ON_BAD_MEMORY=true
+          ./odin check examples/all -vet -vet-tabs -strict-style -vet-style -warnings-as-errors -disallow-do -target:netbsd_amd64
+          ./odin check examples/all -vet -vet-tabs -strict-style -vet-style -warnings-as-errors -disallow-do -target:netbsd_arm64
+          ./odin check examples/all/sdl3 -vet -vet-tabs -strict-style -vet-style -warnings-as-errors -disallow-do -target:netbsd_amd64 -no-entry-point
+          ./odin check examples/all/sdl3 -vet -vet-tabs -strict-style -vet-style -warnings-as-errors -disallow-do -target:netbsd_arm64 -no-entry-point
+          ./odin test tests/core/normal.odin -file -all-packages -vet -vet-tabs -strict-style -vet-style -warnings-as-errors -disallow-do -define:ODIN_TEST_FANCY=false -define:ODIN_TEST_FAIL_ON_BAD_MEMORY=true
+          ./odin test tests/core/speed.odin -file -vet -vet-tabs -strict-style -vet-style -warnings-as-errors -disallow-do -o:speed -define:ODIN_TEST_FANCY=false -define:ODIN_TEST_FAIL_ON_BAD_MEMORY=true
+          ./odin test tests/vendor -all-packages -vet -vet-tabs -strict-style -vet-style -warnings-as-errors -disallow-do -define:ODIN_TEST_FANCY=false -define:ODIN_TEST_FAIL_ON_BAD_MEMORY=true
           (cd tests/issues; ./run.sh)
           (cd tests/issues; ./run.sh)
           ./odin check tests/benchmark -vet -strict-style -no-entry-point
           ./odin check tests/benchmark -vet -strict-style -no-entry-point
 
 
@@ -63,11 +63,11 @@ jobs:
           gmake -C vendor/stb/src
           gmake -C vendor/stb/src
           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/sdl3 -vet -strict-style -disallow-do -target:freebsd_amd64 -no-entry-point
-          ./odin test tests/core/normal.odin -file -all-packages -vet -strict-style -disallow-do -define:ODIN_TEST_FANCY=false -define:ODIN_TEST_FAIL_ON_BAD_MEMORY=true
-          ./odin test tests/core/speed.odin -file -all-packages -vet -strict-style -disallow-do -o:speed -define:ODIN_TEST_FANCY=false -define:ODIN_TEST_FAIL_ON_BAD_MEMORY=true
-          ./odin test tests/vendor -all-packages -vet -strict-style -disallow-do -define:ODIN_TEST_FANCY=false -define:ODIN_TEST_FAIL_ON_BAD_MEMORY=true
+          ./odin check examples/all -vet -vet-tabs -strict-style -vet-style -warnings-as-errors -disallow-do -target:freebsd_amd64
+          ./odin check examples/all/sdl3 -vet -vet-tabs -strict-style -vet-style -warnings-as-errors -disallow-do -target:freebsd_amd64 -no-entry-point
+          ./odin test tests/core/normal.odin -file -all-packages -vet -vet-tabs -strict-style -vet-style -warnings-as-errors -disallow-do -define:ODIN_TEST_FANCY=false -define:ODIN_TEST_FAIL_ON_BAD_MEMORY=true
+          ./odin test tests/core/speed.odin -file -all-packages -vet -vet-tabs -strict-style -vet-style -warnings-as-errors -disallow-do -o:speed -define:ODIN_TEST_FANCY=false -define:ODIN_TEST_FAIL_ON_BAD_MEMORY=true
+          ./odin test tests/vendor -all-packages -vet -vet-tabs -strict-style -vet-style -warnings-as-errors -disallow-do -define:ODIN_TEST_FANCY=false -define:ODIN_TEST_FAIL_ON_BAD_MEMORY=true
           (cd tests/issues; ./run.sh)
           (cd tests/issues; ./run.sh)
           ./odin check tests/benchmark -vet -strict-style -no-entry-point
           ./odin check tests/benchmark -vet -strict-style -no-entry-point
   ci:
   ci:
@@ -75,7 +75,7 @@ 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: [macos-13, macos-14, ubuntu-latest]
+        os: [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') || (matrix.os == 'ubuntu-latest' && '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
@@ -123,17 +123,17 @@ jobs:
       - name: Odin run -debug
       - name: Odin run -debug
         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 -vet -vet-tabs -strict-style -vet-style -warnings-as-errors -disallow-do
       - name: Odin check examples/all/sdl3
       - name: Odin check examples/all/sdl3
-        run: ./odin check examples/all/sdl3  -strict-style -vet -disallow-do -no-entry-point
+        run: ./odin check examples/all/sdl3 -vet -vet-tabs -strict-style -vet-style -warnings-as-errors -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 -sanitize:address
+        run: ./odin test tests/core/normal.odin -file -all-packages -vet -vet-tabs -strict-style -vet-style -warnings-as-errors -disallow-do -define:ODIN_TEST_FANCY=false -define:ODIN_TEST_FAIL_ON_BAD_MEMORY=true -sanitize:address
       - name: Optimized Core library tests
       - 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 -sanitize:address
+        run: ./odin test tests/core/speed.odin -o:speed -file -all-packages -vet -vet-tabs -strict-style -vet-style -warnings-as-errors -disallow-do -define:ODIN_TEST_FANCY=false -define:ODIN_TEST_FAIL_ON_BAD_MEMORY=true -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 -sanitize:address
+        run: ./odin test tests/vendor -all-packages -vet -vet-tabs -strict-style -vet-style -warnings-as-errors -disallow-do -define:ODIN_TEST_FANCY=false -define:ODIN_TEST_FAIL_ON_BAD_MEMORY=true -sanitize:address
       - 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 -sanitize:address
+        run: ./odin test tests/internal -all-packages -vet -vet-tabs -strict-style -vet-style -warnings-as-errors -disallow-do -define:ODIN_TEST_FANCY=false -define:ODIN_TEST_FAIL_ON_BAD_MEMORY=true -sanitize:address
       - name: GitHub Issue tests
       - name: GitHub Issue tests
         run: |
         run: |
           cd tests/issues
           cd tests/issues
@@ -141,43 +141,43 @@ jobs:
 
 
       - name: Run demo on WASI WASM32
       - name: Run demo on WASI WASM32
         run: |
         run: |
-          ./odin build examples/demo -target:wasi_wasm32 -vet -strict-style -disallow-do -out:demo
+          ./odin build examples/demo -target:wasi_wasm32 -vet -vet-tabs -strict-style -vet-style -warnings-as-errors -disallow-do -out:demo
           wasmtime ./demo.wasm
           wasmtime ./demo.wasm
         if: matrix.os == 'macos-14'
         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 -vet-tabs -strict-style -vet-style -warnings-as-errors -disallow-do -no-entry-point
       - name: Odin check examples/all for Linux i386
       - name: Odin check examples/all for 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
+        run: ./odin check examples/all -vet -vet-tabs -strict-style -vet-style -warnings-as-errors -disallow-do -target:linux_i386
       - name: Odin check examples/all for Linux arm64
       - name: Odin check examples/all for 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
+        run: ./odin check examples/all -vet -vet-tabs -strict-style -vet-style -warnings-as-errors -disallow-do -target:linux_arm64
       - name: Odin check examples/all for FreeBSD amd64
       - name: Odin check examples/all for 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
+        run: ./odin check examples/all -vet -vet-tabs -strict-style -vet-style -warnings-as-errors -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'
         if: matrix.os == 'ubuntu-latest'
-        run: ./odin check examples/all -vet -strict-style -disallow-do -target:openbsd_amd64
+        run: ./odin check examples/all -vet -vet-tabs -strict-style -vet-style -warnings-as-errors -disallow-do -target:openbsd_amd64
       - name: Odin check examples/all for js_wasm32
       - name: Odin check examples/all for js_wasm32
         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_wasm32
+        run: ./odin check examples/all -vet -vet-tabs -strict-style -vet-style -warnings-as-errors -disallow-do -no-entry-point -target:js_wasm32
       - name: Odin check examples/all for js_wasm64p32
       - 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
+        run: ./odin check examples/all -vet -vet-tabs -strict-style -vet-style -warnings-as-errors -disallow-do -no-entry-point -target:js_wasm64p32
 
 
       - name: Odin check examples/all/sdl3 for Linux i386
       - name: Odin check examples/all/sdl3 for Linux i386
         if: matrix.os == 'ubuntu-latest'
         if: matrix.os == 'ubuntu-latest'
-        run: ./odin check examples/all/sdl3 -vet -strict-style -disallow-do -no-entry-point -target:linux_i386
+        run: ./odin check examples/all/sdl3 -vet -vet-tabs -strict-style -vet-style -warnings-as-errors -disallow-do -no-entry-point -target:linux_i386
       - name: Odin check examples/all/sdl3 for Linux arm64
       - name: Odin check examples/all/sdl3 for Linux arm64
         if: matrix.os == 'ubuntu-latest'
         if: matrix.os == 'ubuntu-latest'
-        run: ./odin check examples/all/sdl3 -vet -strict-style -disallow-do -no-entry-point -target:linux_arm64
+        run: ./odin check examples/all/sdl3 -vet -vet-tabs -strict-style -vet-style -warnings-as-errors -disallow-do -no-entry-point -target:linux_arm64
       - name: Odin check examples/all/sdl3 for FreeBSD amd64
       - name: Odin check examples/all/sdl3 for FreeBSD amd64
         if: matrix.os == 'ubuntu-latest'
         if: matrix.os == 'ubuntu-latest'
-        run: ./odin check examples/all/sdl3 -vet -strict-style -disallow-do -no-entry-point -target:freebsd_amd64
+        run: ./odin check examples/all/sdl3 -vet -vet-tabs -strict-style -vet-style -warnings-as-errors -disallow-do -no-entry-point -target:freebsd_amd64
       - name: Odin check examples/all/sdl3 for OpenBSD amd64
       - name: Odin check examples/all/sdl3 for OpenBSD amd64
         if: matrix.os == 'ubuntu-latest'
         if: matrix.os == 'ubuntu-latest'
-        run: ./odin check examples/all/sdl3 -vet -strict-style -disallow-do -no-entry-point -target:openbsd_amd64
+        run: ./odin check examples/all/sdl3 -vet -vet-tabs -strict-style -vet-style -warnings-as-errors -disallow-do -no-entry-point -target:openbsd_amd64
 
 
   build_windows:
   build_windows:
     name: Windows Build, Check, and Test
     name: Windows Build, Check, and Test
@@ -208,38 +208,38 @@ jobs:
         shell: cmd
         shell: cmd
         run: |
         run: |
           call "C:\Program Files\Microsoft Visual Studio\2022\Enterprise\VC\Auxiliary\Build\vcvars64.bat
           call "C:\Program Files\Microsoft Visual Studio\2022\Enterprise\VC\Auxiliary\Build\vcvars64.bat
-          odin run examples/demo -debug -vet -strict-style -disallow-do
+          odin run examples/demo -debug -vet -vet-tabs -strict-style -vet-style -warnings-as-errors -disallow-do
       - name: Odin check examples/all
       - name: Odin check examples/all
         shell: cmd
         shell: cmd
         run: |
         run: |
           call "C:\Program Files\Microsoft Visual Studio\2022\Enterprise\VC\Auxiliary\Build\vcvars64.bat
           call "C:\Program Files\Microsoft Visual Studio\2022\Enterprise\VC\Auxiliary\Build\vcvars64.bat
-          odin check examples/all -vet -strict-style -disallow-do
+          odin check examples/all -vet -vet-tabs -strict-style -vet-style -warnings-as-errors -disallow-do
       - name: Odin check examples/all/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 examples/all/sdl3 -vet -strict-style -disallow-do -no-entry-point
+          odin check examples/all/sdl3 -vet -vet-tabs -strict-style -vet-style -warnings-as-errors -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 -sanitize:address
+          odin test tests/core/normal.odin -file -all-packages -vet -vet-tabs -strict-style -vet-style -warnings-as-errors -disallow-do -define:ODIN_TEST_FANCY=false -define:ODIN_TEST_FAIL_ON_BAD_MEMORY=true -sanitize:address
       - name: Optimized core library tests
       - 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 -sanitize:address
+          odin test tests/core/speed.odin -o:speed -file -all-packages -vet -vet-tabs -strict-style -vet-style -warnings-as-errors -disallow-do -define:ODIN_TEST_FANCY=false -define:ODIN_TEST_FAIL_ON_BAD_MEMORY=true -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 -sanitize:address
+          odin test tests/vendor -all-packages -vet -vet-tabs -strict-style -vet-style -warnings-as-errors -disallow-do -define:ODIN_TEST_FANCY=false -define:ODIN_TEST_FAIL_ON_BAD_MEMORY=true -sanitize:address
       - name: Odin internals tests
       - 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 -sanitize:address
+          odin test tests/internal -all-packages -vet -vet-tabs -strict-style -vet-style -warnings-as-errors -disallow-do -define:ODIN_TEST_FANCY=false -define:ODIN_TEST_FAIL_ON_BAD_MEMORY=true -sanitize:address
       - name: Check issues
       - name: Check issues
         shell: cmd
         shell: cmd
         run: |
         run: |
@@ -257,12 +257,6 @@ jobs:
           call "C:\Program Files\Microsoft Visual Studio\2022\Enterprise\VC\Auxiliary\Build\vcvars64.bat
           call "C:\Program Files\Microsoft Visual Studio\2022\Enterprise\VC\Auxiliary\Build\vcvars64.bat
           cd tests\documentation
           cd tests\documentation
           call build.bat
           call build.bat
-      - name: core:math/big tests
-        shell: cmd
-        run: |
-          call "C:\Program Files\Microsoft Visual Studio\2022\Enterprise\VC\Auxiliary\Build\vcvars64.bat
-          cd tests\core\math\big
-          call build.bat
       - name: Odin check examples/all for Windows 32bits
       - name: Odin check examples/all for Windows 32bits
         shell: cmd
         shell: cmd
         run: |
         run: |
@@ -299,25 +293,25 @@ jobs:
           make -C vendor/miniaudio/src
           make -C vendor/miniaudio/src
 
 
       - 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 -vet-tabs -strict-style -vet-style -warnings-as-errors -disallow-do
 
 
       - name: Odin check examples/all/sdl3
       - name: Odin check examples/all/sdl3
-        run: ./odin check examples/all/sdl3 -target:linux_riscv64 -vet -strict-style -disallow-do -no-entry-point
+        run: ./odin check examples/all/sdl3 -target:linux_riscv64 -vet -vet-tabs -strict-style -vet-style -warnings-as-errors -disallow-do -no-entry-point
 
 
       - name: Install riscv64 toolchain and qemu
       - 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
 
 
       - name: Odin run
       - name: Odin run
-        run: ./odin run examples/demo -vet -strict-style -disallow-do -target:linux_riscv64 -extra-linker-flags:"-fuse-ld=/usr/bin/riscv64-linux-gnu-gcc-12 -static -Wl,-static" -no-rpath
+        run: ./odin run examples/demo -vet -vet-tabs -strict-style -vet-style -warnings-as-errors -disallow-do -target:linux_riscv64 -extra-linker-flags:"-fuse-ld=/usr/bin/riscv64-linux-gnu-gcc-12 -static -Wl,-static" -no-rpath
 
 
       - name: Odin run -debug
       - name: Odin run -debug
-        run: ./odin run examples/demo -debug -vet -strict-style -disallow-do -target:linux_riscv64 -extra-linker-flags:"-fuse-ld=/usr/bin/riscv64-linux-gnu-gcc-12 -static -Wl,-static" -no-rpath
+        run: ./odin run examples/demo -debug -vet -vet-tabs -strict-style -vet-style -warnings-as-errors -disallow-do -target:linux_riscv64 -extra-linker-flags:"-fuse-ld=/usr/bin/riscv64-linux-gnu-gcc-12 -static -Wl,-static" -no-rpath
 
 
       - name: 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 -target:linux_riscv64 -extra-linker-flags:"-fuse-ld=/usr/bin/riscv64-linux-gnu-gcc-12 -static -Wl,-static" -no-rpath
+        run: ./odin test tests/core/normal.odin -file -all-packages -vet -vet-tabs -strict-style -vet-style -warnings-as-errors -disallow-do -define:ODIN_TEST_FANCY=false -define:ODIN_TEST_FAIL_ON_BAD_MEMORY=true -target:linux_riscv64 -extra-linker-flags:"-fuse-ld=/usr/bin/riscv64-linux-gnu-gcc-12 -static -Wl,-static" -no-rpath
 
 
       - name: Optimized Core library tests
       - name: Optimized Core library tests
-        run: ./odin test tests/core/speed.odin -o:speed -file -all-packages -vet -strict-style -disallow-do -define:ODIN_TEST_FANCY=false -define:ODIN_TEST_FAIL_ON_BAD_MEMORY=true -target:linux_riscv64 -extra-linker-flags:"-fuse-ld=/usr/bin/riscv64-linux-gnu-gcc-12 -static -Wl,-static" -no-rpath
+        run: ./odin test tests/core/speed.odin -o:speed -file -all-packages -vet -vet-tabs -strict-style -vet-style -warnings-as-errors -disallow-do -define:ODIN_TEST_FANCY=false -define:ODIN_TEST_FAIL_ON_BAD_MEMORY=true -target:linux_riscv64 -extra-linker-flags:"-fuse-ld=/usr/bin/riscv64-linux-gnu-gcc-12 -static -Wl,-static" -no-rpath
 
 
       - name: Internals tests
       - name: Internals tests
-        run: ./odin test tests/internal -all-packages -vet -strict-style -disallow-do -define:ODIN_TEST_FANCY=false -define:ODIN_TEST_FAIL_ON_BAD_MEMORY=true -target:linux_riscv64 -extra-linker-flags:"-fuse-ld=/usr/bin/riscv64-linux-gnu-gcc-12 -static -Wl,-static" -no-rpath
+        run: ./odin test tests/internal -all-packages -vet -vet-tabs -strict-style -vet-style -warnings-as-errors -disallow-do -define:ODIN_TEST_FANCY=false -define:ODIN_TEST_FAIL_ON_BAD_MEMORY=true -target:linux_riscv64 -extra-linker-flags:"-fuse-ld=/usr/bin/riscv64-linux-gnu-gcc-12 -static -Wl,-static" -no-rpath

+ 60 - 0
.github/workflows/cover.yml

@@ -0,0 +1,60 @@
+name: Test Coverage
+on: [push, pull_request, workflow_dispatch]
+
+jobs:
+  build_linux_amd64:
+    runs-on: ubuntu-latest
+    name: Linux AMD64 Test Coverage
+    timeout-minutes: 60
+    steps:
+      - uses: actions/checkout@v4
+
+      - 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: Install kcov
+        run: |
+          sudo apt-get update
+          sudo apt-get install binutils-dev build-essential cmake libssl-dev libcurl4-openssl-dev libelf-dev libstdc++-12-dev zlib1g-dev libdw-dev libiberty-dev
+          git clone https://github.com/SimonKagstrom/kcov.git
+          mkdir kcov/build
+          cd kcov/build
+          cmake ..
+          sudo make
+          sudo make install
+          cd ../..
+          kcov --version
+
+      - name: Build Odin
+        run: ./build_odin.sh release
+
+      - name: Odin report
+        run: ./odin report
+
+      - name: Normal Core library tests
+        run: |
+          ./odin build tests/core/normal.odin -build-mode:test -debug -file -all-packages -vet -strict-style -disallow-do -define:ODIN_TEST_FANCY=false -define:ODIN_TEST_FAIL_ON_BAD_MEMORY=true -target:linux_amd64
+          mkdir kcov-out
+          kcov --exclude-path=tests,/usr kcov-out ./normal.bin .
+
+      - name: Optimized Core library tests
+        run: |
+          ./odin build tests/core/speed.odin -build-mode:test -debug -file -all-packages -vet -strict-style -disallow-do -define:ODIN_TEST_FANCY=false -define:ODIN_TEST_FAIL_ON_BAD_MEMORY=true -target:linux_amd64
+          kcov --exclude-path=tests,/usr kcov-out ./speed.bin .
+
+      - name: Internals tests
+        run: |
+          ./odin build tests/internal -build-mode:test -debug -all-packages -vet -strict-style -disallow-do -define:ODIN_TEST_FANCY=false -define:ODIN_TEST_FAIL_ON_BAD_MEMORY=true -target:linux_amd64
+          kcov --exclude-path=tests,/usr kcov-out ./internal .
+
+      - uses: codecov/codecov-action@v5
+        with:
+          name: Ubuntu Coverage # optional
+          token: ${{ secrets.CODECOV_TOKEN }}
+          verbose: true # optional (default = false
+          directory: kcov-out/kcov-merged

+ 1 - 0
.gitignore

@@ -277,6 +277,7 @@ odin
 *.bin
 *.bin
 demo.bin
 demo.bin
 libLLVM*.so*
 libLLVM*.so*
+*.a
 
 
 # shared collection
 # shared collection
 shared/
 shared/

+ 2 - 2
base/runtime/core_builtin.odin

@@ -67,7 +67,7 @@ init_global_temporary_allocator :: proc(size: int, backup_allocator := context.a
 // Prefer the procedure group `copy`.
 // Prefer the procedure group `copy`.
 @builtin
 @builtin
 copy_slice :: proc "contextless" (dst, src: $T/[]$E) -> int {
 copy_slice :: proc "contextless" (dst, src: $T/[]$E) -> int {
-	n := max(0, min(len(dst), len(src)))
+	n := min(len(dst), len(src))
 	if n > 0 {
 	if n > 0 {
 		intrinsics.mem_copy(raw_data(dst), raw_data(src), n*size_of(E))
 		intrinsics.mem_copy(raw_data(dst), raw_data(src), n*size_of(E))
 	}
 	}
@@ -80,7 +80,7 @@ copy_slice :: proc "contextless" (dst, src: $T/[]$E) -> int {
 // Prefer the procedure group `copy`.
 // Prefer the procedure group `copy`.
 @builtin
 @builtin
 copy_from_string :: proc "contextless" (dst: $T/[]$E/u8, src: $S/string) -> int {
 copy_from_string :: proc "contextless" (dst: $T/[]$E/u8, src: $S/string) -> int {
-	n := max(0, min(len(dst), len(src)))
+	n := min(len(dst), len(src))
 	if n > 0 {
 	if n > 0 {
 		intrinsics.mem_copy(raw_data(dst), raw_data(src), n)
 		intrinsics.mem_copy(raw_data(dst), raw_data(src), n)
 	}
 	}

+ 7 - 7
base/runtime/default_temp_allocator_arena.odin

@@ -1,7 +1,7 @@
 package runtime
 package runtime
 
 
 import "base:intrinsics"
 import "base:intrinsics"
-import "base:sanitizer"
+// 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)
 
 
@@ -44,7 +44,7 @@ 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)
+	// sanitizer.address_poison(block.base, block.capacity)
 
 
 	// Should be zeroed
 	// Should be zeroed
 	assert(block.used == 0)
 	assert(block.used == 0)
@@ -55,7 +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)
+		// 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)
 	}
 	}
 }
 }
@@ -87,7 +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])
+	// sanitizer.address_unpoison(block.base[block.used:block.used+size])
 	block.used += size
 	block.used += size
 	return
 	return
 }
 }
@@ -167,7 +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)
+		// sanitizer.address_poison(arena.curr_block.base, arena.curr_block.capacity)
 	}
 	}
 	arena.total_used = 0
 	arena.total_used = 0
 }
 }
@@ -232,7 +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)
+					// sanitizer.address_unpoison(data)
 					return
 					return
 				}
 				}
 			}
 			}
@@ -306,7 +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])
+			// 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
 		}
 		}

+ 74 - 0
base/sanitizer/memory.odin

@@ -0,0 +1,74 @@
+#+no-instrumentation
+package sanitizer
+
+@(private="file")
+MSAN_ENABLED :: .Memory in ODIN_SANITIZER_FLAGS
+
+@(private="file")
+@(default_calling_convention="system")
+foreign {
+	__msan_unpoison :: proc(addr: rawptr, size: uint) ---
+}
+
+/*
+Marks a slice as fully initialized.
+
+Code instrumented with `-sanitize:memory` will be permitted to access any
+address within the slice as if it had already been initialized.
+
+When msan is not enabled this procedure does nothing.
+*/
+memory_unpoison_slice :: proc "contextless" (region: $T/[]$E) {
+	when MSAN_ENABLED {
+		__msan_unpoison(raw_data(region),  size_of(E) * len(region))
+	}
+}
+
+/*
+Marks a pointer as fully initialized.
+
+Code instrumented with `-sanitize:memory` will be permitted to access memory
+within the region the pointer points to as if it had already been initialized.
+
+When msan is not enabled this procedure does nothing.
+*/
+memory_unpoison_ptr :: proc "contextless" (ptr: ^$T) {
+	when MSAN_ENABLED {
+		__msan_unpoison(ptr, size_of(T))
+	}
+}
+
+/*
+Marks the region covering `[ptr, ptr+len)` as fully initialized.
+
+Code instrumented with `-sanitize:memory` will be permitted to access memory
+within this range as if it had already been initialized.
+
+When msan is not enabled this procedure does nothing.
+*/
+memory_unpoison_rawptr :: proc "contextless" (ptr: rawptr, len: int) {
+	when MSAN_ENABLED {
+		__msan_unpoison(ptr, uint(len))
+	}
+}
+
+/*
+Marks the region covering `[ptr, ptr+len)` as fully initialized.
+
+Code instrumented with `-sanitize:memory` will be permitted to access memory
+within this range as if it had already been initialized.
+
+When msan is not enabled this procedure does nothing.
+*/
+memory_unpoison_rawptr_uint :: proc "contextless" (ptr: rawptr, len: uint) {
+	when MSAN_ENABLED {
+		__msan_unpoison(ptr, len)
+	}
+}
+
+memory_unpoison :: proc {
+	memory_unpoison_slice,
+	memory_unpoison_ptr,
+	memory_unpoison_rawptr,
+	memory_unpoison_rawptr_uint,
+}

+ 75 - 0
check_all.bat

@@ -0,0 +1,75 @@
+@echo off
+
+if "%1" == "" (
+	echo Checking darwin_amd64 - expect vendor:cgltf panic
+	odin check examples\all -vet -vet-tabs -strict-style -vet-style -warnings-as-errors -disallow-do -target:darwin_amd64
+	echo Checking darwin_arm64 - expect vendor:cgltf panic
+	odin check examples\all -vet -vet-tabs -strict-style -vet-style -warnings-as-errors -disallow-do -target:darwin_arm64
+	echo Checking linux_i386
+	odin check examples\all -vet -vet-tabs -strict-style -vet-style -warnings-as-errors -disallow-do -target:linux_i386
+	echo Checking linux_amd64
+	odin check examples\all -vet -vet-tabs -strict-style -vet-style -warnings-as-errors -disallow-do -target:linux_amd64
+	echo Checking linux_arm64
+	odin check examples\all -vet -vet-tabs -strict-style -vet-style -warnings-as-errors -disallow-do -target:linux_arm64
+	echo Checking linux_arm32
+	odin check examples\all -vet -vet-tabs -strict-style -vet-style -warnings-as-errors -disallow-do -target:linux_arm32
+	echo Checking linux_riscv64
+	odin check examples\all -vet -vet-tabs -strict-style -vet-style -warnings-as-errors -disallow-do -target:linux_riscv64
+	echo Checking windows_i386
+	odin check examples\all -vet -vet-tabs -strict-style -vet-style -warnings-as-errors -disallow-do -target:windows_i386
+	echo Checking windows_amd64
+	odin check examples\all -vet -vet-tabs -strict-style -vet-style -warnings-as-errors -disallow-do -target:windows_amd64
+	echo Checking freebsd_amd64
+	odin check examples\all -vet -vet-tabs -strict-style -vet-style -warnings-as-errors -disallow-do -target:freebsd_amd64
+	echo Checking freebsd_arm64
+	odin check examples\all -vet -vet-tabs -strict-style -vet-style -warnings-as-errors -disallow-do -target:freebsd_arm64
+	echo Checking netbsd_amd64
+	odin check examples\all -vet -vet-tabs -strict-style -vet-style -warnings-as-errors -disallow-do -target:netbsd_amd64
+	echo Checking netbsd_arm64
+	odin check examples\all -vet -vet-tabs -strict-style -vet-style -warnings-as-errors -disallow-do -target:netbsd_arm64
+	echo Checking openbsd_amd64
+	odin check examples\all -vet -vet-tabs -strict-style -vet-style -warnings-as-errors -disallow-do -target:openbsd_amd64
+)
+
+if "%1" == "freestanding" (
+	echo Checking freestanding_wasm32
+	odin check examples\all -vet -vet-tabs -strict-style -vet-style -warnings-as-errors -disallow-do -target:freestanding_wasm32
+	echo Checking freestanding_wasm64p32
+	odin check examples\all -vet -vet-tabs -strict-style -vet-style -warnings-as-errors -disallow-do -target:freestanding_wasm64p32
+	echo Checking freestanding_amd64_sysv
+	odin check examples\all -vet -vet-tabs -strict-style -vet-style -warnings-as-errors -disallow-do -target:freestanding_amd64_sysv
+	echo Checking freestanding_amd64_win64
+	odin check examples\all -vet -vet-tabs -strict-style -vet-style -warnings-as-errors -disallow-do -target:freestanding_amd64_win64
+	echo Checking freestanding_arm64
+	odin check examples\all -vet -vet-tabs -strict-style -vet-style -warnings-as-errors -disallow-do -target:freestanding_arm64
+	echo Checking freestanding_arm32
+	odin check examples\all -vet -vet-tabs -strict-style -vet-style -warnings-as-errors -disallow-do -target:freestanding_arm32
+	echo Checking freestanding_riscv64
+	odin check examples\all -vet -vet-tabs -strict-style -vet-style -warnings-as-errors -disallow-do -target:freestanding_riscv64
+)
+
+if "%1" == "rare" (
+	echo Checking essence_amd64
+	odin check examples\all -vet -vet-tabs -strict-style -vet-style -warnings-as-errors -disallow-do -target:essence_amd64
+	echo Checking freebsd_i386
+	odin check examples\all -vet -vet-tabs -strict-style -vet-style -warnings-as-errors -disallow-do -target:freebsd_i386
+	echo Checking haiku_amd64
+	odin check examples\all -vet -vet-tabs -strict-style -vet-style -warnings-as-errors -disallow-do -target:haiku_amd64
+)
+
+if "%1" == "wasm" (
+	echo Checking freestanding_wasm32
+	odin check examples\all -vet -vet-tabs -strict-style -vet-style -warnings-as-errors -disallow-do -target:freestanding_wasm32
+	echo Checking freestanding_wasm64p32
+	odin check examples\all -vet -vet-tabs -strict-style -vet-style -warnings-as-errors -disallow-do -target:freestanding_wasm64p32
+	echo Checking wasi_wasm64p32
+	odin check examples\all -vet -vet-tabs -strict-style -vet-style -warnings-as-errors -disallow-do -target:wasi_wasm64p32
+	echo Checking wasi_wasm32
+	odin check examples\all -vet -vet-tabs -strict-style -vet-style -warnings-as-errors -disallow-do -target:wasi_wasm32
+	echo Checking js_wasm32
+	odin check examples\all -vet -vet-tabs -strict-style -vet-style -warnings-as-errors -disallow-do -target:js_wasm32
+	echo Checking orca_wasm32
+	odin check examples\all -vet -vet-tabs -strict-style -vet-style -warnings-as-errors -disallow-do -target:orca_wasm32
+	echo Checking js_wasm64p32
+	odin check examples\all -vet -vet-tabs -strict-style -vet-style -warnings-as-errors -disallow-do -target:js_wasm64p32
+)

+ 78 - 0
check_all.sh

@@ -0,0 +1,78 @@
+#!/bin/sh
+
+case $1 in
+freestanding)
+	echo Checking freestanding_wasm32
+	odin check examples/all -vet -vet-tabs -strict-style -vet-style -warnings-as-errors -disallow-do -target:freestanding_wasm32
+	echo Checking freestanding_wasm64p32
+	odin check examples/all -vet -vet-tabs -strict-style -vet-style -warnings-as-errors -disallow-do -target:freestanding_wasm64p32
+	echo Checking freestanding_amd64_sysv
+	odin check examples/all -vet -vet-tabs -strict-style -vet-style -warnings-as-errors -disallow-do -target:freestanding_amd64_sysv
+	echo Checking freestanding_amd64_win64
+	odin check examples/all -vet -vet-tabs -strict-style -vet-style -warnings-as-errors -disallow-do -target:freestanding_amd64_win64
+	echo Checking freestanding_arm64
+	odin check examples/all -vet -vet-tabs -strict-style -vet-style -warnings-as-errors -disallow-do -target:freestanding_arm64
+	echo Checking freestanding_arm32
+	odin check examples/all -vet -vet-tabs -strict-style -vet-style -warnings-as-errors -disallow-do -target:freestanding_arm32
+	echo Checking freestanding_riscv64
+	odin check examples/all -vet -vet-tabs -strict-style -vet-style -warnings-as-errors -disallow-do -target:freestanding_riscv64
+	;;
+
+rare)
+	echo Checking essence_amd64
+	odin check examples/all -vet -vet-tabs -strict-style -vet-style -warnings-as-errors -disallow-do -target:essence_amd64
+	echo Checking freebsd_i386
+	odin check examples/all -vet -vet-tabs -strict-style -vet-style -warnings-as-errors -disallow-do -target:freebsd_i386
+	echo Checking haiku_amd64
+	odin check examples/all -vet -vet-tabs -strict-style -vet-style -warnings-as-errors -disallow-do -target:haiku_amd64
+	;;
+
+wasm)
+	echo Checking freestanding_wasm32
+	odin check examples/all -vet -vet-tabs -strict-style -vet-style -warnings-as-errors -disallow-do -target:freestanding_wasm32
+	echo Checking freestanding_wasm64p32
+	odin check examples/all -vet -vet-tabs -strict-style -vet-style -warnings-as-errors -disallow-do -target:freestanding_wasm64p32
+	echo Checking wasi_wasm64p32
+	odin check examples/all -vet -vet-tabs -strict-style -vet-style -warnings-as-errors -disallow-do -target:wasi_wasm64p32
+	echo Checking wasi_wasm32
+	odin check examples/all -vet -vet-tabs -strict-style -vet-style -warnings-as-errors -disallow-do -target:wasi_wasm32
+	echo Checking js_wasm32
+	odin check examples/all -vet -vet-tabs -strict-style -vet-style -warnings-as-errors -disallow-do -target:js_wasm32
+	echo Checking orca_wasm32
+	odin check examples/all -vet -vet-tabs -strict-style -vet-style -warnings-as-errors -disallow-do -target:orca_wasm32
+	echo Checking js_wasm64p32
+	odin check examples/all -vet -vet-tabs -strict-style -vet-style -warnings-as-errors -disallow-do -target:js_wasm64p32
+	;;
+
+*)
+	echo Checking darwin_amd64 - expect vendor:cgltf panic
+	odin check examples/all -vet -vet-tabs -strict-style -vet-style -warnings-as-errors -disallow-do -target:darwin_amd64
+	echo Checking darwin_arm64 - expect vendor:cgltf panic
+	odin check examples/all -vet -vet-tabs -strict-style -vet-style -warnings-as-errors -disallow-do -target:darwin_arm64
+	echo Checking linux_i386
+	odin check examples/all -vet -vet-tabs -strict-style -vet-style -warnings-as-errors -disallow-do -target:linux_i386
+	echo Checking linux_amd64
+	odin check examples/all -vet -vet-tabs -strict-style -vet-style -warnings-as-errors -disallow-do -target:linux_amd64
+	echo Checking linux_arm64
+	odin check examples/all -vet -vet-tabs -strict-style -vet-style -warnings-as-errors -disallow-do -target:linux_arm64
+	echo Checking linux_arm32
+	odin check examples/all -vet -vet-tabs -strict-style -vet-style -warnings-as-errors -disallow-do -target:linux_arm32
+	echo Checking linux_riscv64
+	odin check examples/all -vet -vet-tabs -strict-style -vet-style -warnings-as-errors -disallow-do -target:linux_riscv64
+	echo Checking windows_i386
+	odin check examples/all -vet -vet-tabs -strict-style -vet-style -warnings-as-errors -disallow-do -target:windows_i386
+	echo Checking windows_amd64
+	odin check examples/all -vet -vet-tabs -strict-style -vet-style -warnings-as-errors -disallow-do -target:windows_amd64
+	echo Checking freebsd_amd64
+	odin check examples/all -vet -vet-tabs -strict-style -vet-style -warnings-as-errors -disallow-do -target:freebsd_amd64
+	echo Checking freebsd_arm64
+	odin check examples/all -vet -vet-tabs -strict-style -vet-style -warnings-as-errors -disallow-do -target:freebsd_arm64
+	echo Checking netbsd_amd64
+	odin check examples/all -vet -vet-tabs -strict-style -vet-style -warnings-as-errors -disallow-do -target:netbsd_amd64
+	echo Checking netbsd_arm64
+	odin check examples/all -vet -vet-tabs -strict-style -vet-style -warnings-as-errors -disallow-do -target:netbsd_arm64
+	echo Checking openbsd_amd64
+	odin check examples/all -vet -vet-tabs -strict-style -vet-style -warnings-as-errors -disallow-do -target:openbsd_amd64
+	;;
+
+esac

+ 1 - 0
codecov.yml

@@ -0,0 +1 @@
+comment: false

+ 280 - 56
core/container/queue/queue.odin

@@ -4,7 +4,13 @@ import "base:builtin"
 import "base:runtime"
 import "base:runtime"
 _ :: runtime
 _ :: runtime
 
 
-// Dynamically resizable double-ended queue/ring-buffer
+/*
+`Queue` is a dynamically resizable double-ended queue/ring-buffer.
+
+Being double-ended means that either end may be pushed onto or popped from
+across the same block of memory, in any order, thus providing both stack and
+queue-like behaviors in the same data structure.
+*/
 Queue :: struct($T: typeid) {
 Queue :: struct($T: typeid) {
 	data:   [dynamic]T,
 	data:   [dynamic]T,
 	len:    uint,
 	len:    uint,
@@ -13,18 +19,31 @@ Queue :: struct($T: typeid) {
 
 
 DEFAULT_CAPACITY :: 16
 DEFAULT_CAPACITY :: 16
 
 
-// Procedure to initialize a queue
+/*
+Initialize a `Queue` with a starting `capacity` and an `allocator`.
+*/
 init :: proc(q: ^$Q/Queue($T), capacity := DEFAULT_CAPACITY, allocator := context.allocator) -> runtime.Allocator_Error {
 init :: proc(q: ^$Q/Queue($T), capacity := DEFAULT_CAPACITY, allocator := context.allocator) -> runtime.Allocator_Error {
-	if q.data.allocator.procedure == nil {
-		q.data.allocator = allocator
-	}
 	clear(q)
 	clear(q)
+	q.data = transmute([dynamic]T)runtime.Raw_Dynamic_Array{
+		data = nil,
+		len = 0,
+		cap = 0,
+		allocator = allocator,
+	}
 	return reserve(q, capacity)
 	return reserve(q, capacity)
 }
 }
 
 
-// Procedure to initialize a queue from a fixed backing slice.
-// The contents of the `backing` will be overwritten as items are pushed onto the `Queue`.
-// Any previous contents are not available.
+/*
+Initialize a `Queue` from a fixed `backing` slice into which modifications are
+made directly.
+
+The contents of the `backing` will be overwritten as items are pushed onto the
+`Queue`. Any previous contents will not be available through the API but are
+not explicitly zeroed either.
+
+Note that procedures which need space to work (`push_back`, ...) will fail if
+the backing slice runs out of space.
+*/
 init_from_slice :: proc(q: ^$Q/Queue($T), backing: []T) -> bool {
 init_from_slice :: proc(q: ^$Q/Queue($T), backing: []T) -> bool {
 	clear(q)
 	clear(q)
 	q.data = transmute([dynamic]T)runtime.Raw_Dynamic_Array{
 	q.data = transmute([dynamic]T)runtime.Raw_Dynamic_Array{
@@ -36,8 +55,14 @@ init_from_slice :: proc(q: ^$Q/Queue($T), backing: []T) -> bool {
 	return true
 	return true
 }
 }
 
 
-// Procedure to initialize a queue from a fixed backing slice.
-// Existing contents are preserved and available on the queue.
+/*
+Initialize a `Queue` from a fixed `backing` slice into which modifications are
+made directly.
+
+The contents of the queue will start out with all of the elements in `backing`,
+effectively creating a full queue from the slice. As such, no procedures will
+be able to add more elements to the queue until some are taken off.
+*/
 init_with_contents :: proc(q: ^$Q/Queue($T), backing: []T) -> bool {
 init_with_contents :: proc(q: ^$Q/Queue($T), backing: []T) -> bool {
 	clear(q)
 	clear(q)
 	q.data = transmute([dynamic]T)runtime.Raw_Dynamic_Array{
 	q.data = transmute([dynamic]T)runtime.Raw_Dynamic_Array{
@@ -50,84 +75,200 @@ init_with_contents :: proc(q: ^$Q/Queue($T), backing: []T) -> bool {
 	return true
 	return true
 }
 }
 
 
-// Procedure to destroy a queue
+/*
+Delete memory that has been dynamically allocated from a `Queue` that was setup with `init`.
+
+Note that this procedure should not be used on queues setup with
+`init_from_slice` or `init_with_contents`, as neither of those procedures keep
+track of the allocator state of the underlying `backing` slice.
+*/
 destroy :: proc(q: ^$Q/Queue($T)) {
 destroy :: proc(q: ^$Q/Queue($T)) {
 	delete(q.data)
 	delete(q.data)
 }
 }
 
 
-// The length of the queue
+/*
+Return the length of the queue.
+*/
 len :: proc(q: $Q/Queue($T)) -> int {
 len :: proc(q: $Q/Queue($T)) -> int {
 	return int(q.len)
 	return int(q.len)
 }
 }
 
 
-// The current capacity of the queue
+/*
+Return the capacity of the queue.
+*/
 cap :: proc(q: $Q/Queue($T)) -> int {
 cap :: proc(q: $Q/Queue($T)) -> int {
 	return builtin.len(q.data)
 	return builtin.len(q.data)
 }
 }
 
 
-// Remaining space in the queue (cap-len)
+/*
+Return the remaining space in the queue.
+
+This will be `cap() - len()`.
+*/
 space :: proc(q: $Q/Queue($T)) -> int {
 space :: proc(q: $Q/Queue($T)) -> int {
 	return builtin.len(q.data) - int(q.len)
 	return builtin.len(q.data) - int(q.len)
 }
 }
 
 
-// Reserve enough space for at least the specified capacity
+/*
+Reserve enough space in the queue for at least the specified capacity.
+
+This may return an error if allocation failed.
+*/
 reserve :: proc(q: ^$Q/Queue($T), capacity: int) -> runtime.Allocator_Error {
 reserve :: proc(q: ^$Q/Queue($T), capacity: int) -> runtime.Allocator_Error {
 	if capacity > space(q^) {
 	if capacity > space(q^) {
-		return _grow(q, uint(capacity)) 
+		return _grow(q, uint(capacity))
 	}
 	}
 	return nil
 	return nil
 }
 }
 
 
+/*
+Shrink a queue's dynamically allocated array.
+
+This has no effect if the queue was initialized with a backing slice.
+*/
+shrink :: proc(q: ^$Q/Queue($T), temp_allocator := context.temp_allocator, loc := #caller_location) {
+	if q.data.allocator.procedure == runtime.nil_allocator_proc {
+		return
+	}
+
+	if q.len > 0 && q.offset > 0 {
+		// Make the array contiguous again.
+		buffer := make([]T, q.len, temp_allocator)
+		defer delete(buffer, temp_allocator)
+
+		right := uint(builtin.len(q.data)) - q.offset
+		copy(buffer[:],      q.data[q.offset:])
+		copy(buffer[right:], q.data[:q.offset])
+
+		copy(q.data[:], buffer[:])
+
+		q.offset = 0
+	}
+
+	builtin.shrink(&q.data, q.len, loc)
+}
+
+/*
+Get the element at index `i`.
 
 
+This will raise a bounds checking error if `i` is an invalid index.
+*/
 get :: proc(q: ^$Q/Queue($T), #any_int i: int, loc := #caller_location) -> T {
 get :: proc(q: ^$Q/Queue($T), #any_int i: int, loc := #caller_location) -> T {
-	runtime.bounds_check_error_loc(loc, i, builtin.len(q.data))
+	runtime.bounds_check_error_loc(loc, i, int(q.len))
 
 
 	idx := (uint(i)+q.offset)%builtin.len(q.data)
 	idx := (uint(i)+q.offset)%builtin.len(q.data)
 	return q.data[idx]
 	return q.data[idx]
 }
 }
 
 
-front :: proc(q: ^$Q/Queue($T)) -> T {
+/*
+Get a pointer to the element at index `i`.
+
+This will raise a bounds checking error if `i` is an invalid index.
+*/
+get_ptr :: proc(q: ^$Q/Queue($T), #any_int i: int, loc := #caller_location) -> ^T {
+	runtime.bounds_check_error_loc(loc, i, int(q.len))
+
+	idx := (uint(i)+q.offset)%builtin.len(q.data)
+	return &q.data[idx]
+}
+
+/*
+Set the element at index `i` to `val`.
+
+This will raise a bounds checking error if `i` is an invalid index.
+*/
+set :: proc(q: ^$Q/Queue($T), #any_int i: int, val: T, loc := #caller_location) {
+	runtime.bounds_check_error_loc(loc, i, int(q.len))
+
+	idx := (uint(i)+q.offset)%builtin.len(q.data)
+	q.data[idx] = val
+}
+
+/*
+Get the element at the front of the queue.
+
+This will raise a bounds checking error if the queue is empty.
+*/
+front :: proc(q: ^$Q/Queue($T), loc := #caller_location) -> T {
+	when !ODIN_NO_BOUNDS_CHECK {
+		ensure(q.len > 0, "Queue is empty.", loc)
+	}
 	return q.data[q.offset]
 	return q.data[q.offset]
 }
 }
-front_ptr :: proc(q: ^$Q/Queue($T)) -> ^T {
+
+/*
+Get a pointer to the element at the front of the queue.
+
+This will raise a bounds checking error if the queue is empty.
+*/
+front_ptr :: proc(q: ^$Q/Queue($T), loc := #caller_location) -> ^T {
+	when !ODIN_NO_BOUNDS_CHECK {
+		ensure(q.len > 0, "Queue is empty.", loc)
+	}
 	return &q.data[q.offset]
 	return &q.data[q.offset]
 }
 }
 
 
-back :: proc(q: ^$Q/Queue($T)) -> T {
+/*
+Get the element at the back of the queue.
+
+This will raise a bounds checking error if the queue is empty.
+*/
+back :: proc(q: ^$Q/Queue($T), loc := #caller_location) -> T {
+	when !ODIN_NO_BOUNDS_CHECK {
+		ensure(q.len > 0, "Queue is empty.", loc)
+	}
 	idx := (q.offset+uint(q.len - 1))%builtin.len(q.data)
 	idx := (q.offset+uint(q.len - 1))%builtin.len(q.data)
 	return q.data[idx]
 	return q.data[idx]
 }
 }
-back_ptr :: proc(q: ^$Q/Queue($T)) -> ^T {
+
+/*
+Get a pointer to the element at the back of the queue.
+
+This will raise a bounds checking error if the queue is empty.
+*/
+back_ptr :: proc(q: ^$Q/Queue($T), loc := #caller_location) -> ^T {
+	when !ODIN_NO_BOUNDS_CHECK {
+		ensure(q.len > 0, "Queue is empty.", loc)
+	}
 	idx := (q.offset+uint(q.len - 1))%builtin.len(q.data)
 	idx := (q.offset+uint(q.len - 1))%builtin.len(q.data)
 	return &q.data[idx]
 	return &q.data[idx]
 }
 }
 
 
-set :: proc(q: ^$Q/Queue($T), #any_int i: int, val: T, loc := #caller_location) {
-	runtime.bounds_check_error_loc(loc, i, builtin.len(q.data))
-	
-	idx := (uint(i)+q.offset)%builtin.len(q.data)
-	q.data[idx] = val
-}
-get_ptr :: proc(q: ^$Q/Queue($T), #any_int i: int, loc := #caller_location) -> ^T {
-	runtime.bounds_check_error_loc(loc, i, builtin.len(q.data))
-	
-	idx := (uint(i)+q.offset)%builtin.len(q.data)
-	return &q.data[idx]
-}
 
 
+@(deprecated="Use `front_ptr` instead")
 peek_front :: proc(q: ^$Q/Queue($T), loc := #caller_location) -> ^T {
 peek_front :: proc(q: ^$Q/Queue($T), loc := #caller_location) -> ^T {
-	runtime.bounds_check_error_loc(loc, 0, builtin.len(q.data))
-	idx := q.offset%builtin.len(q.data)
-	return &q.data[idx]
+	return front_ptr(q, loc)
 }
 }
 
 
+@(deprecated="Use `back_ptr` instead")
 peek_back :: proc(q: ^$Q/Queue($T), loc := #caller_location) -> ^T {
 peek_back :: proc(q: ^$Q/Queue($T), loc := #caller_location) -> ^T {
-	runtime.bounds_check_error_loc(loc, int(q.len - 1), builtin.len(q.data))
-	idx := (uint(q.len - 1)+q.offset)%builtin.len(q.data)
-	return &q.data[idx]
+	return back_ptr(q, loc)
 }
 }
 
 
-// Push an element to the back of the queue
+/*
+Push an element to the back of the queue.
+
+If there is no more space left and allocation fails to get more, this will
+return false with an `Allocator_Error`.
+
+Example:
+
+	import "base:runtime"
+	import "core:container/queue"
+
+	// This demonstrates typical queue behavior (First-In First-Out).
+	main :: proc() {
+		q: queue.Queue(int)
+		queue.init(&q)
+		queue.push_back(&q, 1)
+		queue.push_back(&q, 2)
+		queue.push_back(&q, 3)
+		// q.data is now [1, 2, 3, ...]
+		assert(queue.pop_front(&q) == 1)
+		assert(queue.pop_front(&q) == 2)
+		assert(queue.pop_front(&q) == 3)
+	}
+*/
 push_back :: proc(q: ^$Q/Queue($T), elem: T) -> (ok: bool, err: runtime.Allocator_Error) {
 push_back :: proc(q: ^$Q/Queue($T), elem: T) -> (ok: bool, err: runtime.Allocator_Error) {
 	if space(q^) == 0 {
 	if space(q^) == 0 {
 		_grow(q) or_return
 		_grow(q) or_return
@@ -138,27 +279,78 @@ push_back :: proc(q: ^$Q/Queue($T), elem: T) -> (ok: bool, err: runtime.Allocato
 	return true, nil
 	return true, nil
 }
 }
 
 
-// Push an element to the front of the queue
+/*
+Push an element to the front of the queue.
+
+If there is no more space left and allocation fails to get more, this will
+return false with an `Allocator_Error`.
+
+Example:
+
+	import "base:runtime"
+	import "core:container/queue"
+
+	// This demonstrates stack behavior (First-In Last-Out).
+	main :: proc() {
+		q: queue.Queue(int)
+		queue.init(&q)
+		queue.push_back(&q, 1)
+		queue.push_back(&q, 2)
+		queue.push_back(&q, 3)
+		// q.data is now [1, 2, 3, ...]
+		assert(queue.pop_back(&q) == 3)
+		assert(queue.pop_back(&q) == 2)
+		assert(queue.pop_back(&q) == 1)
+	}
+*/
 push_front :: proc(q: ^$Q/Queue($T), elem: T) -> (ok: bool, err: runtime.Allocator_Error)  {
 push_front :: proc(q: ^$Q/Queue($T), elem: T) -> (ok: bool, err: runtime.Allocator_Error)  {
 	if space(q^) == 0 {
 	if space(q^) == 0 {
 		_grow(q) or_return
 		_grow(q) or_return
-	}	
+	}
 	q.offset = uint(q.offset - 1 + builtin.len(q.data)) % builtin.len(q.data)
 	q.offset = uint(q.offset - 1 + builtin.len(q.data)) % builtin.len(q.data)
 	q.len += 1
 	q.len += 1
 	q.data[q.offset] = elem
 	q.data[q.offset] = elem
 	return true, nil
 	return true, nil
 }
 }
 
 
+/*
+Pop an element from the back of the queue.
 
 
-// Pop an element from the back of the queue
+This will raise a bounds checking error if the queue is empty.
+
+Example:
+
+	import "base:runtime"
+	import "core:container/queue"
+
+	// This demonstrates stack behavior (First-In Last-Out) at the far end of the data array.
+	main :: proc() {
+		q: queue.Queue(int)
+		queue.init(&q)
+		queue.push_front(&q, 1)
+		queue.push_front(&q, 2)
+		queue.push_front(&q, 3)
+		// q.data is now [..., 3, 2, 1]
+		log.infof("%#v", q)
+		assert(queue.pop_front(&q) == 3)
+		assert(queue.pop_front(&q) == 2)
+		assert(queue.pop_front(&q) == 1)
+	}
+*/
 pop_back :: proc(q: ^$Q/Queue($T), loc := #caller_location) -> (elem: T) {
 pop_back :: proc(q: ^$Q/Queue($T), loc := #caller_location) -> (elem: T) {
-	assert(condition=q.len > 0, loc=loc)
+	when !ODIN_NO_BOUNDS_CHECK {
+		ensure(q.len > 0, "Queue is empty.", loc)
+	}
 	q.len -= 1
 	q.len -= 1
 	idx := (q.offset+uint(q.len))%builtin.len(q.data)
 	idx := (q.offset+uint(q.len))%builtin.len(q.data)
 	elem = q.data[idx]
 	elem = q.data[idx]
 	return
 	return
 }
 }
-// Safely pop an element from the back of the queue
+
+/*
+Pop an element from the back of the queue if one exists and return true.
+Otherwise, return a nil element and false.
+*/
 pop_back_safe :: proc(q: ^$Q/Queue($T)) -> (elem: T, ok: bool) {
 pop_back_safe :: proc(q: ^$Q/Queue($T)) -> (elem: T, ok: bool) {
 	if q.len > 0 {
 	if q.len > 0 {
 		q.len -= 1
 		q.len -= 1
@@ -169,15 +361,25 @@ pop_back_safe :: proc(q: ^$Q/Queue($T)) -> (elem: T, ok: bool) {
 	return
 	return
 }
 }
 
 
-// Pop an element from the front of the queue
+/*
+Pop an element from the front of the queue
+
+This will raise a bounds checking error if the queue is empty.
+*/
 pop_front :: proc(q: ^$Q/Queue($T), loc := #caller_location) -> (elem: T) {
 pop_front :: proc(q: ^$Q/Queue($T), loc := #caller_location) -> (elem: T) {
-	assert(condition=q.len > 0, loc=loc)
+	when !ODIN_NO_BOUNDS_CHECK {
+		ensure(q.len > 0, "Queue is empty.", loc)
+	}
 	elem = q.data[q.offset]
 	elem = q.data[q.offset]
 	q.offset = (q.offset+1)%builtin.len(q.data)
 	q.offset = (q.offset+1)%builtin.len(q.data)
 	q.len -= 1
 	q.len -= 1
 	return
 	return
 }
 }
-// Safely pop an element from the front of the queue
+
+/*
+Pop an element from the front of the queue if one exists and return true.
+Otherwise, return a nil element and false.
+*/
 pop_front_safe :: proc(q: ^$Q/Queue($T)) -> (elem: T, ok: bool) {
 pop_front_safe :: proc(q: ^$Q/Queue($T)) -> (elem: T, ok: bool) {
 	if q.len > 0 {
 	if q.len > 0 {
 		elem = q.data[q.offset]
 		elem = q.data[q.offset]
@@ -188,13 +390,18 @@ pop_front_safe :: proc(q: ^$Q/Queue($T)) -> (elem: T, ok: bool) {
 	return
 	return
 }
 }
 
 
-// Push multiple elements to the back of the queue
+/*
+Push many elements at once to the back of the queue.
+
+If there is not enough space left and allocation fails to get more, this will
+return false with an `Allocator_Error`.
+*/
 push_back_elems :: proc(q: ^$Q/Queue($T), elems: ..T) -> (ok: bool, err: runtime.Allocator_Error)  {
 push_back_elems :: proc(q: ^$Q/Queue($T), elems: ..T) -> (ok: bool, err: runtime.Allocator_Error)  {
 	n := uint(builtin.len(elems))
 	n := uint(builtin.len(elems))
 	if space(q^) < int(n) {
 	if space(q^) < int(n) {
 		_grow(q, q.len + n) or_return
 		_grow(q, q.len + n) or_return
 	}
 	}
-	
+
 	sz := uint(builtin.len(q.data))
 	sz := uint(builtin.len(q.data))
 	insert_from := (q.offset + q.len) % sz
 	insert_from := (q.offset + q.len) % sz
 	insert_to := n
 	insert_to := n
@@ -207,19 +414,31 @@ push_back_elems :: proc(q: ^$Q/Queue($T), elems: ..T) -> (ok: bool, err: runtime
 	return true, nil
 	return true, nil
 }
 }
 
 
-// Consume `n` elements from the front of the queue
+/*
+Consume `n` elements from the back of the queue.
+
+This will raise a bounds checking error if the queue does not have enough elements.
+*/
 consume_front :: proc(q: ^$Q/Queue($T), n: int, loc := #caller_location) {
 consume_front :: proc(q: ^$Q/Queue($T), n: int, loc := #caller_location) {
-	assert(condition=int(q.len) >= n, loc=loc)
+	when !ODIN_NO_BOUNDS_CHECK {
+		ensure(q.len >= uint(n), "Queue does not have enough elements to consume.", loc)
+	}
 	if n > 0 {
 	if n > 0 {
 		nu := uint(n)
 		nu := uint(n)
 		q.offset = (q.offset + nu) % builtin.len(q.data)
 		q.offset = (q.offset + nu) % builtin.len(q.data)
-		q.len -= nu	
+		q.len -= nu
 	}
 	}
 }
 }
 
 
-// Consume `n` elements from the back of the queue
+/*
+Consume `n` elements from the back of the queue.
+
+This will raise a bounds checking error if the queue does not have enough elements.
+*/
 consume_back :: proc(q: ^$Q/Queue($T), n: int, loc := #caller_location) {
 consume_back :: proc(q: ^$Q/Queue($T), n: int, loc := #caller_location) {
-	assert(condition=int(q.len) >= n, loc=loc)
+	when !ODIN_NO_BOUNDS_CHECK {
+		ensure(q.len >= uint(n), "Queue does not have enough elements to consume.", loc)
+	}
 	if n > 0 {
 	if n > 0 {
 		q.len -= uint(n)
 		q.len -= uint(n)
 	}
 	}
@@ -231,9 +450,14 @@ append_elem  :: push_back
 append_elems :: push_back_elems
 append_elems :: push_back_elems
 push   :: proc{push_back, push_back_elems}
 push   :: proc{push_back, push_back_elems}
 append :: proc{push_back, push_back_elems}
 append :: proc{push_back, push_back_elems}
+enqueue :: push_back
+dequeue :: pop_front
 
 
 
 
-// Clear the contents of the queue
+/*
+Reset the queue's length and offset to zero, letting it write new elements over
+old memory, in effect clearing the accessible contents.
+*/
 clear :: proc(q: ^$Q/Queue($T)) {
 clear :: proc(q: ^$Q/Queue($T)) {
 	q.len = 0
 	q.len = 0
 	q.offset = 0
 	q.offset = 0

+ 23 - 0
core/dynlib/lb_haiku.odin

@@ -0,0 +1,23 @@
+#+build haiku
+#+private
+package dynlib
+
+import "base:runtime"
+
+_LIBRARY_FILE_EXTENSION :: ""
+
+_load_library :: proc(path: string, global_symbols: bool, allocator: runtime.Allocator) -> (Library, bool) {
+	return nil, false
+}
+
+_unload_library :: proc(library: Library) -> bool {
+	return false
+}
+
+_symbol_address :: proc(library: Library, symbol: string, allocator: runtime.Allocator) -> (ptr: rawptr, found: bool) {
+	return nil, false
+}
+
+_last_error :: proc() -> string {
+	return ""
+}

+ 2 - 1
core/encoding/xml/xml_reader.odin

@@ -175,7 +175,7 @@ parse_bytes :: proc(data: []u8, options := DEFAULT_OPTIONS, path := "", error_ha
 		data = bytes.clone(data)
 		data = bytes.clone(data)
 	}
 	}
 
 
-	t := &Tokenizer{}
+	t := new(Tokenizer)
 	init(t, string(data), path, error_handler)
 	init(t, string(data), path, error_handler)
 
 
 	doc = new(Document)
 	doc = new(Document)
@@ -403,6 +403,7 @@ destroy :: proc(doc: ^Document) {
 	}
 	}
 	delete(doc.strings_to_free)
 	delete(doc.strings_to_free)
 
 
+	free(doc.tokenizer)
 	free(doc)
 	free(doc)
 }
 }
 
 

+ 2 - 2
core/flags/constants.odin

@@ -19,7 +19,7 @@ SUBTAG_NAME       :: "name"
 SUBTAG_POS        :: "pos"
 SUBTAG_POS        :: "pos"
 SUBTAG_REQUIRED   :: "required"
 SUBTAG_REQUIRED   :: "required"
 SUBTAG_HIDDEN     :: "hidden"
 SUBTAG_HIDDEN     :: "hidden"
-SUBTAG_VARIADIC   :: "variadic"
+SUBTAG_MANIFOLD   :: "manifold"
 SUBTAG_FILE       :: "file"
 SUBTAG_FILE       :: "file"
 SUBTAG_PERMS      :: "perms"
 SUBTAG_PERMS      :: "perms"
 SUBTAG_INDISTINCT :: "indistinct"
 SUBTAG_INDISTINCT :: "indistinct"
@@ -28,7 +28,7 @@ TAG_USAGE         :: "usage"
 
 
 UNDOCUMENTED_FLAG :: "<This flag has not been documented yet.>"
 UNDOCUMENTED_FLAG :: "<This flag has not been documented yet.>"
 
 
-INTERNAL_VARIADIC_FLAG   :: "varg"
+INTERNAL_OVERFLOW_FLAG   :: #config(ODIN_CORE_FLAGS_OVERFLOW_FLAG, "overflow")
 
 
 RESERVED_HELP_FLAG       :: "help"
 RESERVED_HELP_FLAG       :: "help"
 RESERVED_HELP_FLAG_SHORT :: "h"
 RESERVED_HELP_FLAG_SHORT :: "h"

+ 16 - 4
core/flags/doc.odin

@@ -20,6 +20,17 @@ The format is similar to the Odin binary's way of handling compiler flags.
 	-<map>:<key>=<value>  set map[key] to value
 	-<map>:<key>=<value>  set map[key] to value
 
 
 
 
+Unhandled Arguments:
+
+All unhandled positional arguments are placed into the `overflow` field on a
+struct, if it exists. In UNIX-style parsing, the existence of a `--` on the
+command line will also pass all arguments afterwards into this field.
+
+If desired, the name of the field may be changed from `overflow` to any string
+by setting the `ODIN_CORE_FLAGS_OVERFLOW_FLAG` compile-time config option with
+`-define:ODIN_CORE_FLAGS_OVERFLOW_FLAG=<name>`.
+
+
 Struct Tags:
 Struct Tags:
 
 
 Users of the `core:encoding/json` package may be familiar with using tags to
 Users of the `core:encoding/json` package may be familiar with using tags to
@@ -32,7 +43,7 @@ Under the `args` tag, there are the following subtags:
 - `pos=N`: place positional argument `N` into this flag.
 - `pos=N`: place positional argument `N` into this flag.
 - `hidden`: hide this flag from the usage documentation.
 - `hidden`: hide this flag from the usage documentation.
 - `required`: cause verification to fail if this argument is not set.
 - `required`: cause verification to fail if this argument is not set.
-- `variadic`: take all remaining arguments when set, UNIX-style only.
+- `manifold=N`: take several arguments at once, UNIX-style only.
 - `file`: for `os.Handle` types, file open mode.
 - `file`: for `os.Handle` types, file open mode.
 - `perms`: for `os.Handle` types, file open permissions.
 - `perms`: for `os.Handle` types, file open permissions.
 - `indistinct`: allow the setting of distinct types by their base type.
 - `indistinct`: allow the setting of distinct types by their base type.
@@ -47,8 +58,9 @@ you want to require 3 and only 3 arguments in a dynamic array, you would
 specify `required=3<4`.
 specify `required=3<4`.
 
 
 
 
-`variadic` may be given a number (`variadic=N`) above 1 to limit how many extra
-arguments it consumes.
+`manifold` may be given a number (`manifold=N`) above 1 to limit how many extra
+arguments it consumes at once. If this number is not specified, it will take as
+many arguments as can be converted to the underlying element type.
 
 
 
 
 `file` determines the file open mode for an `os.Handle`.
 `file` determines the file open mode for an `os.Handle`.
@@ -160,7 +172,7 @@ at parse time.
 	--flag
 	--flag
 	--flag=argument
 	--flag=argument
 	--flag argument
 	--flag argument
-	--flag argument repeating-argument
+	--flag argument (manifold-argument)
 
 
 `-flag` may also be substituted for `--flag`.
 `-flag` may also be substituted for `--flag`.
 
 

+ 1 - 1
core/flags/errors.odin

@@ -4,7 +4,7 @@ import "core:os"
 
 
 Parse_Error_Reason :: enum {
 Parse_Error_Reason :: enum {
 	None,
 	None,
-	// An extra positional argument was given, and there is no `varg` field.
+	// An extra positional argument was given, and there is no `overflow` field.
 	Extra_Positional,
 	Extra_Positional,
 	// The underlying type does not support the string value it is being set to.
 	// The underlying type does not support the string value it is being set to.
 	Bad_Value,
 	Bad_Value,

+ 3 - 3
core/flags/example/example.odin

@@ -107,14 +107,14 @@ main :: proc() {
 
 
 		// assignments: map[string]u8 `args:"name=assign" usage:"Number of jobs per worker."`,
 		// assignments: map[string]u8 `args:"name=assign" usage:"Number of jobs per worker."`,
 
 
-		// (Variadic) Only available in UNIX style:
+		// (Manifold) Only available in UNIX style:
 
 
-		// bots: [dynamic]string `args:"variadic=2,required"`,
+		// bots: [dynamic]string `args:"manifold=2,required"`,
 
 
 		verbose: bool `usage:"Show verbose output."`,
 		verbose: bool `usage:"Show verbose output."`,
 		debug: bool `args:"hidden" usage:"print debug info"`,
 		debug: bool `args:"hidden" usage:"print debug info"`,
 
 
-		varg: [dynamic]string `usage:"Any extra arguments go here."`,
+		overflow: [dynamic]string `usage:"Any extra arguments go here."`,
 	}
 	}
 
 
 	opt: Options
 	opt: Options

+ 4 - 4
core/flags/internal_assignment.odin

@@ -33,9 +33,9 @@ push_positional :: #force_no_inline proc (model: ^$T, parser: ^Parser, arg: stri
 	field, index, has_pos_assigned := get_field_by_pos(model, pos)
 	field, index, has_pos_assigned := get_field_by_pos(model, pos)
 
 
 	if !has_pos_assigned {
 	if !has_pos_assigned {
-		when intrinsics.type_has_field(T, INTERNAL_VARIADIC_FLAG) {
+		when intrinsics.type_has_field(T, INTERNAL_OVERFLOW_FLAG) {
 			// Add it to the fallback array.
 			// Add it to the fallback array.
-			field = reflect.struct_field_by_name(T, INTERNAL_VARIADIC_FLAG)
+			field = reflect.struct_field_by_name(T, INTERNAL_OVERFLOW_FLAG)
 		} else {
 		} else {
 			return Parse_Error {
 			return Parse_Error {
 				.Extra_Positional,
 				.Extra_Positional,
@@ -117,8 +117,8 @@ set_unix_flag :: proc(model: ^$T, parser: ^Parser, name: string) -> (future_args
 	case runtime.Type_Info_Dynamic_Array:
 	case runtime.Type_Info_Dynamic_Array:
 		future_args = 1
 		future_args = 1
 		if tag, ok := reflect.struct_tag_lookup(field.tag, TAG_ARGS); ok {
 		if tag, ok := reflect.struct_tag_lookup(field.tag, TAG_ARGS); ok {
-			if length, is_variadic := get_struct_subtag(tag, SUBTAG_VARIADIC); is_variadic {
-				// Variadic arrays may specify how many arguments they consume at once.
+			if length, is_manifold := get_struct_subtag(tag, SUBTAG_MANIFOLD); is_manifold {
+				// Manifold arrays may specify how many arguments they consume at once.
 				// Otherwise, they take everything that's left.
 				// Otherwise, they take everything that's left.
 				if value, value_ok := strconv.parse_u64_of_base(length, 10); value_ok {
 				if value, value_ok := strconv.parse_u64_of_base(length, 10); value_ok {
 					future_args = cast(int)value
 					future_args = cast(int)value

+ 1 - 1
core/flags/internal_parsing.odin

@@ -95,7 +95,7 @@ parse_one_unix_arg :: proc(model: ^$T, parser: ^Parser, arg: string) -> (
 				// `--`, and only `--`.
 				// `--`, and only `--`.
 				// Everything from now on will be treated as an argument.
 				// Everything from now on will be treated as an argument.
 				future_args = max(int)
 				future_args = max(int)
-				current_flag = INTERNAL_VARIADIC_FLAG
+				current_flag = INTERNAL_OVERFLOW_FLAG
 				return
 				return
 			}
 			}
 		}
 		}

+ 13 - 8
core/flags/internal_validation.odin

@@ -59,7 +59,8 @@ validate_structure :: proc(model_type: $T, style: Parsing_Style, loc := #caller_
 			}
 			}
 		}
 		}
 
 
-		if pos_str, has_pos := get_struct_subtag(args_tag, SUBTAG_POS); has_pos {
+		pos_str, has_pos := get_struct_subtag(args_tag, SUBTAG_POS)
+		if has_pos {
 			#partial switch specific_type_info in field.type.variant {
 			#partial switch specific_type_info in field.type.variant {
 			case runtime.Type_Info_Map:
 			case runtime.Type_Info_Map:
 				fmt.panicf("%T.%s has `%s` defined, and this does not make sense on a map type.",
 				fmt.panicf("%T.%s has `%s` defined, and this does not make sense on a map type.",
@@ -79,7 +80,7 @@ validate_structure :: proc(model_type: $T, style: Parsing_Style, loc := #caller_
 			fmt.assertf(!reflect.is_boolean(field.type), "%T.%s is a required boolean. This is disallowed.",
 			fmt.assertf(!reflect.is_boolean(field.type), "%T.%s is a required boolean. This is disallowed.",
 				model_type, field.name, loc = loc)
 				model_type, field.name, loc = loc)
 
 
-			fmt.assertf(field.name != INTERNAL_VARIADIC_FLAG, "%T.%s is defined as required. This is disallowed.",
+			fmt.assertf(field.name != INTERNAL_OVERFLOW_FLAG, "%T.%s is defined as required. This is disallowed.",
 				model_type, field.name, loc = loc)
 				model_type, field.name, loc = loc)
 
 
 			if len(requirement) > 0 {
 			if len(requirement) > 0 {
@@ -109,24 +110,28 @@ validate_structure :: proc(model_type: $T, style: Parsing_Style, loc := #caller_
 			}
 			}
 		}
 		}
 
 
-		if length, is_variadic := get_struct_subtag(args_tag, SUBTAG_VARIADIC); is_variadic {
+		if length, is_manifold := get_struct_subtag(args_tag, SUBTAG_MANIFOLD); is_manifold {
+			fmt.assertf(!has_pos,
+				"%T.%s has both `%s` and `%s` defined. This is disallowed.\n\tSuggestion: Use a dynamic array field named `%s` to accept unspecified positional arguments.",
+				model_type, field.name, SUBTAG_POS, SUBTAG_MANIFOLD, INTERNAL_OVERFLOW_FLAG, loc = loc)
+
 			if value, parse_ok := strconv.parse_u64_of_base(length, 10); parse_ok {
 			if value, parse_ok := strconv.parse_u64_of_base(length, 10); parse_ok {
 				fmt.assertf(value > 0,
 				fmt.assertf(value > 0,
 					"%T.%s has `%s` set to %i. It must be greater than zero.",
 					"%T.%s has `%s` set to %i. It must be greater than zero.",
-					model_type, field.name, value, SUBTAG_VARIADIC, loc = loc)
+					model_type, field.name, value, SUBTAG_MANIFOLD, loc = loc)
 				fmt.assertf(value != 1,
 				fmt.assertf(value != 1,
-					"%T.%s has `%s` set to 1. This has no effect.",
-					model_type, field.name, SUBTAG_VARIADIC, loc = loc)
+					"%T.%s has `%s` set to 1. This is equivalent to not defining `%s`.",
+					model_type, field.name, SUBTAG_MANIFOLD, SUBTAG_MANIFOLD, loc = loc)
 			}
 			}
 
 
 			#partial switch specific_type_info in field.type.variant {
 			#partial switch specific_type_info in field.type.variant {
 			case runtime.Type_Info_Dynamic_Array:
 			case runtime.Type_Info_Dynamic_Array:
 				fmt.assertf(style != .Odin,
 				fmt.assertf(style != .Odin,
 					"%T.%s has `%s` defined, but this only makes sense in UNIX-style parsing mode.",
 					"%T.%s has `%s` defined, but this only makes sense in UNIX-style parsing mode.",
-					model_type, field.name, SUBTAG_VARIADIC, loc = loc)
+					model_type, field.name, SUBTAG_MANIFOLD, loc = loc)
 			case:
 			case:
 				fmt.panicf("%T.%s has `%s` defined, but this only makes sense on dynamic arrays.",
 				fmt.panicf("%T.%s has `%s` defined, but this only makes sense on dynamic arrays.",
-					model_type, field.name, SUBTAG_VARIADIC, loc = loc)
+					model_type, field.name, SUBTAG_MANIFOLD, loc = loc)
 			}
 			}
 		}
 		}
 
 

+ 2 - 2
core/flags/parsing.odin

@@ -6,7 +6,7 @@ package flags
 Parsing_Style :: enum {
 Parsing_Style :: enum {
 	// Odin-style: `-flag`, `-flag:option`, `-map:key=value`
 	// Odin-style: `-flag`, `-flag:option`, `-map:key=value`
 	Odin,
 	Odin,
-	// UNIX-style: `-flag` or `--flag`, `--flag=argument`, `--flag argument repeating-argument`
+	// UNIX-style: `-flag` or `--flag`, `--flag=argument`, `--flag argument (manifold-argument)`
 	Unix,
 	Unix,
 }
 }
 
 
@@ -61,7 +61,7 @@ parse :: proc(
 		}
 		}
 
 
 	case .Unix:
 	case .Unix:
-		// Support for `-flag argument (repeating-argument ...)`
+		// Support for `-flag argument (manifold-argument ...)`
 		future_args: int
 		future_args: int
 		current_flag: string
 		current_flag: string
 
 

+ 15 - 15
core/flags/usage.odin

@@ -30,18 +30,18 @@ write_usage :: proc(out: io.Writer, data_type: typeid, program: string = "", sty
 		is_positional: bool,
 		is_positional: bool,
 		is_required: bool,
 		is_required: bool,
 		is_boolean: bool,
 		is_boolean: bool,
-		is_variadic: bool,
-		variadic_length: int,
+		is_manifold: bool,
+		manifold_length: int,
 	}
 	}
 
 
 	//
 	//
 	// POSITIONAL+REQUIRED, POSITIONAL, REQUIRED, NON_REQUIRED+NON_POSITIONAL, ...
 	// POSITIONAL+REQUIRED, POSITIONAL, REQUIRED, NON_REQUIRED+NON_POSITIONAL, ...
 	//
 	//
 	sort_flags :: proc(i, j: Flag) -> slice.Ordering {
 	sort_flags :: proc(i, j: Flag) -> slice.Ordering {
-		// `varg` goes to the end.
-		if i.name == INTERNAL_VARIADIC_FLAG {
+		// `overflow` goes to the end.
+		if i.name == INTERNAL_OVERFLOW_FLAG {
 			return .Greater
 			return .Greater
-		} else if j.name == INTERNAL_VARIADIC_FLAG {
+		} else if j.name == INTERNAL_OVERFLOW_FLAG {
 			return .Less
 			return .Less
 		}
 		}
 
 
@@ -120,10 +120,10 @@ write_usage :: proc(out: io.Writer, data_type: typeid, program: string = "", sty
 				flag.is_required = true
 				flag.is_required = true
 				flag.required_min, flag.required_max, _ = parse_requirements(requirement)
 				flag.required_min, flag.required_max, _ = parse_requirements(requirement)
 			}
 			}
-			if length_str, is_variadic := get_struct_subtag(args_tag, SUBTAG_VARIADIC); is_variadic {
-				flag.is_variadic = true
+			if length_str, is_manifold := get_struct_subtag(args_tag, SUBTAG_MANIFOLD); is_manifold {
+				flag.is_manifold = true
 				if length, parse_ok := strconv.parse_u64_of_base(length_str, 10); parse_ok {
 				if length, parse_ok := strconv.parse_u64_of_base(length_str, 10); parse_ok {
-					flag.variadic_length = cast(int)length
+					flag.manifold_length = cast(int)length
 				}
 				}
 			}
 			}
 		}
 		}
@@ -147,15 +147,15 @@ write_usage :: proc(out: io.Writer, data_type: typeid, program: string = "", sty
 		case runtime.Type_Info_Dynamic_Array:
 		case runtime.Type_Info_Dynamic_Array:
 			requirement_spec := describe_array_requirements(flag)
 			requirement_spec := describe_array_requirements(flag)
 
 
-			if flag.is_variadic || flag.name == INTERNAL_VARIADIC_FLAG {
-				if flag.variadic_length == 0 {
+			if flag.is_manifold || flag.name == INTERNAL_OVERFLOW_FLAG {
+				if flag.manifold_length == 0 {
 					flag.type_description = fmt.tprintf("<%v, ...>%s",
 					flag.type_description = fmt.tprintf("<%v, ...>%s",
 						specific_type_info.elem.id,
 						specific_type_info.elem.id,
 						requirement_spec)
 						requirement_spec)
 				} else {
 				} else {
 					flag.type_description = fmt.tprintf("<%v, %i at once>%s",
 					flag.type_description = fmt.tprintf("<%v, %i at once>%s",
 						specific_type_info.elem.id,
 						specific_type_info.elem.id,
-						flag.variadic_length,
+						flag.manifold_length,
 						requirement_spec)
 						requirement_spec)
 				}
 				}
 			} else {
 			} else {
@@ -177,7 +177,7 @@ write_usage :: proc(out: io.Writer, data_type: typeid, program: string = "", sty
 			}
 			}
 		}
 		}
 
 
-		if flag.name == INTERNAL_VARIADIC_FLAG {
+		if flag.name == INTERNAL_OVERFLOW_FLAG {
 			flag.full_length = len(flag.type_description)
 			flag.full_length = len(flag.type_description)
 		} else if flag.is_boolean {
 		} else if flag.is_boolean {
 			flag.full_length = len(flag_prefix) + len(flag.name) + len(flag.type_description)
 			flag.full_length = len(flag_prefix) + len(flag.name) + len(flag.type_description)
@@ -201,13 +201,13 @@ write_usage :: proc(out: io.Writer, data_type: typeid, program: string = "", sty
 		strings.write_string(&builder, program)
 		strings.write_string(&builder, program)
 
 
 		for flag in visible_flags {
 		for flag in visible_flags {
-			if keep_it_short && !(flag.is_required || flag.is_positional || flag.name == INTERNAL_VARIADIC_FLAG) {
+			if keep_it_short && !(flag.is_required || flag.is_positional || flag.name == INTERNAL_OVERFLOW_FLAG) {
 				continue
 				continue
 			}
 			}
 
 
 			strings.write_byte(&builder, ' ')
 			strings.write_byte(&builder, ' ')
 
 
-			if flag.name == INTERNAL_VARIADIC_FLAG {
+			if flag.name == INTERNAL_OVERFLOW_FLAG {
 				strings.write_string(&builder, "...")
 				strings.write_string(&builder, "...")
 				continue
 				continue
 			}
 			}
@@ -252,7 +252,7 @@ write_usage :: proc(out: io.Writer, data_type: typeid, program: string = "", sty
 
 
 		strings.write_byte(&builder, '\t')
 		strings.write_byte(&builder, '\t')
 
 
-		if flag.name == INTERNAL_VARIADIC_FLAG {
+		if flag.name == INTERNAL_OVERFLOW_FLAG {
 			strings.write_string(&builder, flag.type_description)
 			strings.write_string(&builder, flag.type_description)
 		} else {
 		} else {
 			strings.write_string(&builder, flag_prefix)
 			strings.write_string(&builder, flag_prefix)

+ 2 - 3
core/math/big/api.odin

@@ -160,6 +160,5 @@ destroy :: proc {
 		int_destroy :: proc(integers: ..^Int)
 		int_destroy :: proc(integers: ..^Int)
 	*/
 	*/
 	int_destroy,
 	int_destroy,
-}
-
-
+	internal_rat_destroy,
+}

+ 2 - 2
core/math/big/internal.odin

@@ -1660,13 +1660,13 @@ internal_int_sqrt :: proc(dest, src: ^Int, allocator := context.allocator) -> (e
 
 
 		if internal_gte(y, x) {
 		if internal_gte(y, x) {
 			internal_swap(dest, x)
 			internal_swap(dest, x)
-			return nil
+			return internal_clamp(dest)
 		}
 		}
 		internal_swap(x, y)
 		internal_swap(x, y)
 	}
 	}
 
 
 	internal_swap(dest, x)
 	internal_swap(dest, x)
-	return err
+	return internal_clamp(dest)
 }
 }
 internal_sqrt :: proc { internal_int_sqrt, }
 internal_sqrt :: proc { internal_int_sqrt, }
 
 

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

@@ -310,7 +310,7 @@ int_atoi :: proc(res: ^Int, input: string, radix := i8(10), allocator := context
 		res.sign = sign
 		res.sign = sign
 	}
 	}
 
 
-	return nil
+	return internal_clamp(res)
 }
 }
 
 
 
 

+ 5 - 3
core/math/big/rat.odin

@@ -157,6 +157,8 @@ internal_rat_norm :: proc(z: ^Rat, allocator := context.allocator) -> (err: Erro
 		z.b.sign = .Zero_or_Positive
 		z.b.sign = .Zero_or_Positive
 		
 		
 		f := &Int{}
 		f := &Int{}
+		defer internal_int_destroy(f)
+		
 		internal_int_gcd(f, &z.a, &z.b) or_return
 		internal_int_gcd(f, &z.a, &z.b) or_return
 		if !internal_int_equals_digit(f, 1) {
 		if !internal_int_equals_digit(f, 1) {
 			f.sign = .Zero_or_Positive
 			f.sign = .Zero_or_Positive
@@ -378,9 +380,6 @@ internal_rat_to_float :: proc($T: typeid, z: ^Rat, allocator := context.allocato
 	}
 	}
 	
 	
 	has_sign := a.sign != b.sign
 	has_sign := a.sign != b.sign
-	defer if has_sign {
-		f = -builtin.abs(f)
-	}
 	
 	
 	exp := alen - blen
 	exp := alen - blen
 	a2, b2 := &Int{}, &Int{}
 	a2, b2 := &Int{}, &Int{}
@@ -440,6 +439,9 @@ internal_rat_to_float :: proc($T: typeid, z: ^Rat, allocator := context.allocato
 	if math.is_inf(f, 0) {
 	if math.is_inf(f, 0) {
 		exact = false
 		exact = false
 	}
 	}
+	if has_sign {
+		f = -builtin.abs(f)
+	}
 	return
 	return
 }
 }
 
 

File diff suppressed because it is too large
+ 268 - 162
core/mem/allocators.odin


+ 4 - 4
core/mem/rollback_stack_allocator.odin

@@ -1,7 +1,7 @@
 package mem
 package mem
 
 
 import "base:runtime"
 import "base:runtime"
-import "base:sanitizer"
+// import "base:sanitizer"
 
 
 /*
 /*
 Rollback stack default block size.
 Rollback stack default block size.
@@ -134,7 +134,7 @@ 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)
+	// sanitizer.address_poison(stack.head.buffer)
 }
 }
 
 
 /*
 /*
@@ -241,7 +241,7 @@ rb_alloc_bytes_non_zeroed :: proc(
 			block.offset = cast(uintptr)len(block.buffer)
 			block.offset = cast(uintptr)len(block.buffer)
 		}
 		}
 		res := ptr[:size]
 		res := ptr[:size]
-		sanitizer.address_unpoison(res)
+		// sanitizer.address_unpoison(res)
 		return res, nil
 		return res, nil
 	}
 	}
 	return nil, .Out_Of_Memory
 	return nil, .Out_Of_Memory
@@ -338,7 +338,7 @@ rb_resize_bytes_non_zeroed :: proc(
 					block.offset += cast(uintptr)size - cast(uintptr)old_size
 					block.offset += cast(uintptr)size - cast(uintptr)old_size
 				}
 				}
 				res := (ptr)[:size]
 				res := (ptr)[:size]
-				sanitizer.address_unpoison(res)
+				// sanitizer.address_unpoison(res)
 				#no_bounds_check return res, nil
 				#no_bounds_check return res, nil
 			}
 			}
 		}
 		}

+ 15 - 14
core/mem/tlsf/tlsf_internal.odin

@@ -10,7 +10,7 @@
 package mem_tlsf
 package mem_tlsf
 
 
 import "base:intrinsics"
 import "base:intrinsics"
-import "base:sanitizer"
+// 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.
@@ -210,7 +210,7 @@ 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)
+			// 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{
@@ -257,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, no_sanitize_address)
+@(private, require_results)
 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 {
@@ -267,6 +267,7 @@ alloc_bytes :: proc(control: ^Allocator, size: uint, align: uint) -> (res: []byt
 }
 }
 
 
 
 
+@(no_sanitize_address)
 free_with_size :: proc(control: ^Allocator, ptr: rawptr, size: uint) {
 free_with_size :: proc(control: ^Allocator, ptr: rawptr, size: uint) {
 	assert(control != nil)
 	assert(control != nil)
 	// `size` is currently ignored
 	// `size` is currently ignored
@@ -276,7 +277,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)
+	// 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)
@@ -320,7 +321,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)
+	// 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]
@@ -483,19 +484,19 @@ block_mark_as_used :: proc(block: ^Block_Header) {
 	block_set_used(block)
 	block_set_used(block)
 }
 }
 
 
-@(private, require_results, no_sanitize_address)
+@(private, require_results)
 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, no_sanitize_address)
+@(private, require_results)
 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, no_sanitize_address)
+@(private, require_results)
 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
@@ -505,7 +506,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, no_sanitize_address)
+@(private, require_results)
 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
@@ -519,7 +520,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, no_sanitize_address)
+@(private, require_results)
 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
@@ -537,7 +538,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, no_sanitize_address)
+@(optimization_mode="favor_size", private, require_results)
 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.
@@ -550,7 +551,7 @@ mapping_insert :: proc(size: uint) -> (fl, sl: i32) {
 	return
 	return
 }
 }
 
 
-@(optimization_mode="favor_size", private, require_results, no_sanitize_address)
+@(optimization_mode="favor_size", private, require_results)
 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 {
@@ -561,7 +562,7 @@ 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, no_sanitize_address)
+@(optimization_mode="favor_size", private, require_results)
 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))
 }
 }
@@ -788,7 +789,7 @@ block_prepare_used :: proc(control: ^Allocator, block: ^Block_Header, size: uint
 		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)
+		// sanitizer.address_unpoison(res)
 	}
 	}
 	return
 	return
 }
 }

+ 12 - 12
core/mem/virtual/arena.odin

@@ -3,7 +3,7 @@ package mem_virtual
 import "core:mem"
 import "core:mem"
 import "core:sync"
 import "core:sync"
 
 
-import "base:sanitizer"
+// 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).
@@ -55,7 +55,7 @@ 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])
+	// sanitizer.address_poison(arena.curr_block.base[:arena.curr_block.committed])
 	return
 	return
 }
 }
 
 
@@ -68,7 +68,7 @@ arena_init_static :: proc(arena: ^Arena, reserved: uint = DEFAULT_ARENA_STATIC_R
 	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])
+	// sanitizer.address_poison(arena.curr_block.base[:arena.curr_block.committed])
 	return
 	return
 }
 }
 
 
@@ -82,7 +82,7 @@ arena_init_buffer :: proc(arena: ^Arena, buffer: []byte) -> (err: Allocator_Erro
 
 
 	arena.kind = .Buffer
 	arena.kind = .Buffer
 
 
-	sanitizer.address_poison(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)
@@ -163,7 +163,7 @@ arena_alloc :: proc(arena: ^Arena, size: uint, alignment: uint, loc := #caller_l
 		arena.total_used = arena.curr_block.used
 		arena.total_used = arena.curr_block.used
 	}
 	}
 
 
-	sanitizer.address_unpoison(data)
+	// sanitizer.address_unpoison(data)
 	return
 	return
 }
 }
 
 
@@ -182,7 +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])
+		// 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
@@ -200,7 +200,7 @@ 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])
+		// sanitizer.address_poison(free_block.base[:free_block.committed])
 		memory_block_dealloc(free_block)
 		memory_block_dealloc(free_block)
 	}
 	}
 }
 }
@@ -219,9 +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])
+			// 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])
+			// 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:
@@ -349,7 +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])
+				// sanitizer.address_poison(old_data[size:old_size])
 				return
 				return
 			}
 			}
 
 
@@ -363,7 +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)
+					// sanitizer.address_unpoison(data)
 					return
 					return
 				}
 				}
 			}
 			}
@@ -374,7 +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])
+		// 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)

+ 5 - 5
core/mem/virtual/virtual.odin

@@ -2,7 +2,7 @@ package mem_virtual
 
 
 import "core:mem"
 import "core:mem"
 import "base:intrinsics"
 import "base:intrinsics"
-import "base:sanitizer"
+// import "base:sanitizer"
 import "base:runtime"
 import "base:runtime"
 _ :: runtime
 _ :: runtime
 
 
@@ -22,7 +22,7 @@ reserve :: proc "contextless" (size: uint) -> (data: []byte, err: Allocator_Erro
 
 
 @(no_sanitize_address)
 @(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)
+	// sanitizer.address_unpoison(data, size)
 	return _commit(data, size)
 	return _commit(data, size)
 }
 }
 
 
@@ -35,13 +35,13 @@ reserve_and_commit :: proc "contextless" (size: uint) -> (data: []byte, err: All
 
 
 @(no_sanitize_address)
 @(no_sanitize_address)
 decommit :: proc "contextless" (data: rawptr, size: uint) {
 decommit :: proc "contextless" (data: rawptr, size: uint) {
-	sanitizer.address_poison(data, size)
+	// sanitizer.address_poison(data, size)
 	_decommit(data, size)
 	_decommit(data, size)
 }
 }
 
 
 @(no_sanitize_address)
 @(no_sanitize_address)
 release :: proc "contextless" (data: rawptr, size: uint) {
 release :: proc "contextless" (data: rawptr, size: uint) {
-	sanitizer.address_unpoison(data, size)
+	// sanitizer.address_unpoison(data, size)
 	_release(data, size)
 	_release(data, size)
 }
 }
 
 
@@ -179,7 +179,7 @@ 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)
+	// sanitizer.address_unpoison(data)
 	return
 	return
 }
 }
 
 

+ 4 - 0
core/net/common.odin

@@ -64,6 +64,7 @@ Network_Error :: union #shared_nil {
 	UDP_Recv_Error,
 	UDP_Recv_Error,
 	Shutdown_Error,
 	Shutdown_Error,
 	Interfaces_Error,
 	Interfaces_Error,
+	Socket_Info_Error,
 	Socket_Option_Error,
 	Socket_Option_Error,
 	Set_Blocking_Error,
 	Set_Blocking_Error,
 	Parse_Endpoint_Error,
 	Parse_Endpoint_Error,
@@ -260,6 +261,9 @@ DNS_Configuration :: struct {
 	resolv_conf: string,
 	resolv_conf: string,
 	hosts_file:  string,
 	hosts_file:  string,
 
 
+	resolv_conf_buf: [128]u8,
+	hosts_file_buf:  [128]u8,
+
 	// TODO: Allow loading these up with `reload_configuration()` call or the like,
 	// TODO: Allow loading these up with `reload_configuration()` call or the like,
 	// so we don't have to do it each call.
 	// so we don't have to do it each call.
 	name_servers:       []Endpoint,
 	name_servers:       []Endpoint,

+ 29 - 47
core/net/dns.odin

@@ -22,69 +22,43 @@ package net
 		Haesbaert:       Security fixes
 		Haesbaert:       Security fixes
 */
 */
 
 
+@(require) import "base:runtime"
 import "core:mem"
 import "core:mem"
 import "core:strings"
 import "core:strings"
 import "core:time"
 import "core:time"
 import "core:os"
 import "core:os"
 import "core:math/rand"
 import "core:math/rand"
-/*
-	Default configuration for DNS resolution.
-*/
+@(require) import "core:sync"
+
+dns_config_initialized: sync.Once
 when ODIN_OS == .Windows {
 when ODIN_OS == .Windows {
-	DEFAULT_DNS_CONFIGURATION :: DNS_Configuration{
-		resolv_conf        = "",
-		hosts_file         = "%WINDIR%\\system32\\drivers\\etc\\hosts",
+	dns_configuration := DNS_Configuration{
+		resolv_conf = "",
+		hosts_file  = "%WINDIR%\\system32\\drivers\\etc\\hosts",
 	}
 	}
 } else when ODIN_OS == .Linux || ODIN_OS == .Darwin || ODIN_OS == .FreeBSD || ODIN_OS == .OpenBSD || ODIN_OS == .NetBSD {
 } else when ODIN_OS == .Linux || ODIN_OS == .Darwin || ODIN_OS == .FreeBSD || ODIN_OS == .OpenBSD || ODIN_OS == .NetBSD {
-	DEFAULT_DNS_CONFIGURATION :: DNS_Configuration{
-		resolv_conf        = "/etc/resolv.conf",
-		hosts_file         = "/etc/hosts",
+	dns_configuration := DNS_Configuration{
+		resolv_conf = "/etc/resolv.conf",
+		hosts_file  = "/etc/hosts",
 	}
 	}
 } else {
 } else {
 	#panic("Please add a configuration for this OS.")
 	#panic("Please add a configuration for this OS.")
 }
 }
 
 
-@(init)
+/*
+	Replaces environment placeholders in `dns_configuration`. Only necessary on Windows.
+	Is automatically called, once, by `get_dns_records_*`.
+*/
+@(private)
 init_dns_configuration :: proc() {
 init_dns_configuration :: proc() {
-	/*
-		Resolve %ENVIRONMENT% placeholders in their paths.
-	*/
-	dns_configuration.resolv_conf, _ = replace_environment_path(dns_configuration.resolv_conf)
-	dns_configuration.hosts_file,  _ = replace_environment_path(dns_configuration.hosts_file)
-}
-
-@(fini, private)
-destroy_dns_configuration :: proc() {
-	delete(dns_configuration.resolv_conf)
-	dns_configuration.resolv_conf = ""
-	delete(dns_configuration.hosts_file)
-	dns_configuration.hosts_file = ""
-}
-
-dns_configuration := DEFAULT_DNS_CONFIGURATION
-
-// Always allocates for consistency.
-replace_environment_path :: proc(path: string, allocator := context.allocator) -> (res: string, ok: bool) {
-	// Nothing to replace. Return a clone of the original.
-	if strings.count(path, "%") != 2 {
-		return strings.clone(path, allocator), true
+	when ODIN_OS == .Windows {
+		runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD()
+		val := os.replace_environment_placeholders(dns_configuration.hosts_file, context.temp_allocator)
+		copy(dns_configuration.hosts_file_buf[:], val)
+		dns_configuration.hosts_file = string(dns_configuration.hosts_file_buf[:len(val)])
 	}
 	}
-
-	left  := strings.index(path, "%") + 1
-	assert(left > 0 && left <= len(path)) // should be covered by there being two %
-
-	right := strings.index(path[left:], "%") + 1
-	assert(right > 0 && right <= len(path)) // should be covered by there being two %
-
-	env_key := path[left: right]
-	env_val := os.get_env(env_key, allocator)
-	defer delete(env_val)
-
-	res, _ = strings.replace(path, path[left - 1: right + 1], env_val, 1, allocator)
-	return res, true
 }
 }
 
 
-
 /*
 /*
 	Resolves a hostname to exactly one IP4 and IP6 endpoint.
 	Resolves a hostname to exactly one IP4 and IP6 endpoint.
 	It's then up to you which one you use.
 	It's then up to you which one you use.
@@ -204,6 +178,9 @@ resolve_ip6 :: proc(hostname_and_maybe_port: string) -> (ep6: Endpoint, err: Net
 	See `destroy_records`.
 	See `destroy_records`.
 */
 */
 get_dns_records_from_os :: proc(hostname: string, type: DNS_Record_Type, allocator := context.allocator) -> (records: []DNS_Record, err: DNS_Error) {
 get_dns_records_from_os :: proc(hostname: string, type: DNS_Record_Type, allocator := context.allocator) -> (records: []DNS_Record, err: DNS_Error) {
+	when ODIN_OS == .Windows {
+		sync.once_do(&dns_config_initialized, init_dns_configuration)
+	}
 	return _get_dns_records_os(hostname, type, allocator)
 	return _get_dns_records_os(hostname, type, allocator)
 }
 }
 
 
@@ -219,6 +196,9 @@ get_dns_records_from_os :: proc(hostname: string, type: DNS_Record_Type, allocat
 	See `destroy_records`.
 	See `destroy_records`.
 */
 */
 get_dns_records_from_nameservers :: proc(hostname: string, type: DNS_Record_Type, name_servers: []Endpoint, host_overrides: []DNS_Record, allocator := context.allocator) -> (records: []DNS_Record, err: DNS_Error) {
 get_dns_records_from_nameservers :: proc(hostname: string, type: DNS_Record_Type, name_servers: []Endpoint, host_overrides: []DNS_Record, allocator := context.allocator) -> (records: []DNS_Record, err: DNS_Error) {
+	when ODIN_OS == .Windows {
+		sync.once_do(&dns_config_initialized, init_dns_configuration)
+	}
 	context.allocator = allocator
 	context.allocator = allocator
 
 
 	if type != .SRV {
 	if type != .SRV {
@@ -440,6 +420,8 @@ load_hosts :: proc(hosts_file_path: string, allocator := context.allocator) -> (
 		splits := strings.fields(line)
 		splits := strings.fields(line)
 		defer delete(splits)
 		defer delete(splits)
 
 
+		(len(splits) >= 2) or_continue
+
 		ip_str := splits[0]
 		ip_str := splits[0]
 		addr := parse_address(ip_str)
 		addr := parse_address(ip_str)
 		if addr == nil {
 		if addr == nil {
@@ -886,4 +868,4 @@ parse_response :: proc(response: []u8, filter: DNS_Record_Type = nil, allocator
 	xid = hdr.id
 	xid = hdr.id
 
 
 	return _records[:], xid, true
 	return _records[:], xid, true
-}
+}

+ 1 - 1
core/net/dns_unix.odin

@@ -79,4 +79,4 @@ _get_dns_records_os :: proc(hostname: string, type: DNS_Record_Type, allocator :
 	}
 	}
 
 
 	return get_dns_records_from_nameservers(hostname, type, name_servers, host_overrides[:])
 	return get_dns_records_from_nameservers(hostname, type, name_servers, host_overrides[:])
-}
+}

+ 17 - 0
core/net/errors.odin

@@ -246,6 +246,23 @@ Shutdown_Error :: enum i32 {
 	Unknown,
 	Unknown,
 }
 }
 
 
+Socket_Info_Error :: enum i32 {
+	None,
+	// No network connection, or the network stack is not initialized.
+	Network_Unreachable,
+	// Not enough space in internal tables/buffers to create a new socket, or an unsupported protocol is given.
+	Insufficient_Resources,
+	// Socket is invalid or not connected, or the manner given is invalid.
+	Invalid_Argument,
+	// The socket is valid, but unsupported by this opperation.
+	Unsupported_Socket,
+	// Connection was closed/aborted/shutdown.
+	Connection_Closed,
+
+	// An error unable to be categorized in above categories, `last_platform_error` may have more info.
+	Unknown,
+}
+
 Socket_Option_Error :: enum i32 {
 Socket_Option_Error :: enum i32 {
 	None,
 	None,
 	// No network connection, or the network stack is not initialized.
 	// No network connection, or the network stack is not initialized.

+ 17 - 0
core/net/errors_darwin.odin

@@ -226,6 +226,23 @@ _shutdown_error :: proc() -> Shutdown_Error {
 	}
 	}
 }
 }
 
 
+_socket_info_error :: proc() -> Socket_Info_Error {
+	#partial switch posix.errno() {
+	case .EBADF, .ENOTSOCK:
+		return .Invalid_Argument
+	case .ENOTCONN:
+		return .Network_Unreachable
+	case .EOPNOTSUPP:
+		return .Unsupported_Socket
+	case .EINVAL:
+		return .Connection_Closed
+	case .ENOBUFS:
+		return .Insufficient_Resources
+	case:
+		return .Unknown
+	}
+}
+
 _socket_option_error :: proc() -> Socket_Option_Error {
 _socket_option_error :: proc() -> Socket_Option_Error {
 	#partial switch posix.errno() {
 	#partial switch posix.errno() {
 	case .ENOBUFS:
 	case .ENOBUFS:

+ 18 - 0
core/net/errors_freebsd.odin

@@ -255,6 +255,24 @@ _shutdown_error :: proc(errno: freebsd.Errno) -> Shutdown_Error {
 	}
 	}
 }
 }
 
 
+_socket_info_error :: proc(errno: freebsd.Errno) -> Socket_Info_Error {
+	assert(errno != nil)
+	_last_error = errno
+
+	#partial switch errno {
+	case .EBADF, .ENOTSOCK, .EINVAL, .EFAULT:
+		return .Invalid_Argument
+	case .ENOTCONN:
+		return .Network_Unreachable
+	case .ECONNRESET:
+		return .Connection_Closed
+	case .ENOBUFS:
+		return .Insufficient_Resources
+	case:
+		return .Unknown
+	}
+}
+
 _socket_option_error :: proc(errno: freebsd.Errno) -> Socket_Option_Error {
 _socket_option_error :: proc(errno: freebsd.Errno) -> Socket_Option_Error {
 	assert(errno != nil)
 	assert(errno != nil)
 	_last_error = errno
 	_last_error = errno

+ 16 - 0
core/net/errors_linux.odin

@@ -258,6 +258,22 @@ _shutdown_error :: proc(errno: linux.Errno) -> Shutdown_Error {
 	}
 	}
 }
 }
 
 
+_socket_info_error :: proc(errno: linux.Errno) -> Socket_Info_Error {
+	assert(errno != nil)
+	_last_error = errno
+
+	#partial switch errno {
+	case .EBADF, .ENOTSOCK, .EFAULT, .EINVAL:
+		return .Invalid_Argument
+	case .ENOTCONN:
+		return .Network_Unreachable
+	case .ENOBUFS:
+		return .Insufficient_Resources
+	case:
+		return .Unknown
+	}
+}
+
 _socket_option_error :: proc(errno: linux.Errno) -> Socket_Option_Error {
 _socket_option_error :: proc(errno: linux.Errno) -> Socket_Option_Error {
 	assert(errno != nil)
 	assert(errno != nil)
 	_last_error = errno
 	_last_error = errno

+ 7 - 0
core/net/errors_others.odin

@@ -18,3 +18,10 @@ _last_platform_error_string :: proc() -> string {
 _set_last_platform_error :: proc(err: i32) {
 _set_last_platform_error :: proc(err: i32) {
 	_last_error = err
 	_last_error = err
 }
 }
+
+Parse_Endpoint_Error :: enum u32 {
+	None          = 0,
+	Bad_Port      = 1,
+	Bad_Address,
+	Bad_Hostname,
+}

+ 11 - 0
core/net/errors_windows.odin

@@ -234,6 +234,17 @@ _shutdown_error :: proc() -> Shutdown_Error {
 	}
 	}
 }
 }
 
 
+_socket_info_error :: proc() -> Socket_Info_Error {
+	#partial switch win.System_Error(win.WSAGetLastError()) {
+	case .WSAEFAULT, .WSAEINPROGRESS, .WSAENOTSOCK, .WSAEINVAL:
+		return .Invalid_Argument
+	case .WSANOTINITIALISED, .WSAENETDOWN, .WSAENOTCONN:
+		return .Network_Unreachable
+	case:
+		return .Unknown
+	}
+}
+
 _socket_option_error :: proc() -> Socket_Option_Error {
 _socket_option_error :: proc() -> Socket_Option_Error {
 	#partial switch win.System_Error(win.WSAGetLastError()) {
 	#partial switch win.System_Error(win.WSAGetLastError()) {
 	case .WSAENETDOWN, .WSANOTINITIALISED:
 	case .WSAENETDOWN, .WSANOTINITIALISED:

+ 8 - 1
core/net/socket.odin

@@ -174,10 +174,17 @@ listen_tcp :: proc(interface_endpoint: Endpoint, backlog := 1000) -> (socket: TC
 /*
 /*
 	Returns the endpoint that the given socket is listening / bound on.
 	Returns the endpoint that the given socket is listening / bound on.
 */
 */
-bound_endpoint :: proc(socket: Any_Socket) -> (endpoint: Endpoint, err: Listen_Error) {
+bound_endpoint :: proc(socket: Any_Socket) -> (endpoint: Endpoint, err: Socket_Info_Error) {
 	return _bound_endpoint(socket)
 	return _bound_endpoint(socket)
 }
 }
 
 
+/*
+	Returns the endpoint that the given socket is connected to. (Peer's endpoint)
+*/
+peer_endpoint :: proc(socket: Any_Socket) -> (endpoint: Endpoint, err: Socket_Info_Error) {
+	return _peer_endpoint(socket)
+}
+
 accept_tcp :: proc(socket: TCP_Socket, options := DEFAULT_TCP_OPTIONS) -> (client: TCP_Socket, source: Endpoint, err: Accept_Error) {
 accept_tcp :: proc(socket: TCP_Socket, options := DEFAULT_TCP_OPTIONS) -> (client: TCP_Socket, source: Endpoint, err: Accept_Error) {
 	return _accept_tcp(socket, options)
 	return _accept_tcp(socket, options)
 }
 }

+ 15 - 2
core/net/socket_darwin.odin

@@ -137,11 +137,24 @@ _listen_tcp :: proc(interface_endpoint: Endpoint, backlog := 1000) -> (skt: TCP_
 }
 }
 
 
 @(private)
 @(private)
-_bound_endpoint :: proc(sock: Any_Socket) -> (ep: Endpoint, err: Listen_Error) {
+_bound_endpoint :: proc(sock: Any_Socket) -> (ep: Endpoint, err: Socket_Info_Error) {
 	addr: posix.sockaddr_storage
 	addr: posix.sockaddr_storage
 	addr_len := posix.socklen_t(size_of(addr))
 	addr_len := posix.socklen_t(size_of(addr))
 	if posix.getsockname(posix.FD(any_socket_to_socket(sock)), (^posix.sockaddr)(&addr), &addr_len) != .OK {
 	if posix.getsockname(posix.FD(any_socket_to_socket(sock)), (^posix.sockaddr)(&addr), &addr_len) != .OK {
-		err = _listen_error()
+		err = _socket_info_error()
+		return
+	}
+
+	ep = _sockaddr_to_endpoint(&addr)
+	return
+}
+
+@(private)
+_peer_endpoint :: proc(sock: Any_Socket) -> (ep: Endpoint, err: Socket_Info_Error) {
+	addr: posix.sockaddr_storage
+	addr_len := posix.socklen_t(size_of(addr))
+	if posix.getpeername(posix.FD(any_socket_to_socket(sock)), (^posix.sockaddr)(&addr), &addr_len) != .OK {
+		err = _socket_info_error()
 		return
 		return
 	}
 	}
 
 

+ 16 - 2
core/net/socket_freebsd.odin

@@ -140,12 +140,26 @@ _listen_tcp :: proc(interface_endpoint: Endpoint, backlog := 1000) -> (socket: T
 }
 }
 
 
 @(private)
 @(private)
-_bound_endpoint :: proc(sock: Any_Socket) -> (ep: Endpoint, err: Listen_Error) {
+_bound_endpoint :: proc(sock: Any_Socket) -> (ep: Endpoint, err: Socket_Info_Error) {
 	sockaddr: freebsd.Socket_Address_Storage
 	sockaddr: freebsd.Socket_Address_Storage
 
 
 	errno := freebsd.getsockname(cast(Fd)any_socket_to_socket(sock), &sockaddr)
 	errno := freebsd.getsockname(cast(Fd)any_socket_to_socket(sock), &sockaddr)
 	if errno != nil {
 	if errno != nil {
-		err = _listen_error(errno)
+		err = _socket_info_error(errno)
+		return
+	}
+
+	ep = _sockaddr_to_endpoint(&sockaddr)
+	return
+}
+
+@(private)
+_peer_endpoint :: proc(sock: Any_Socket) -> (ep: Endpoint, err: Socket_Info_Error) {
+	sockaddr: freebsd.Socket_Address_Storage
+
+	errno := freebsd.getpeername(cast(Fd)any_socket_to_socket(sock), &sockaddr)
+	if errno != nil {
+		err = _socket_info_error(errno)
 		return
 		return
 	}
 	}
 
 

+ 15 - 2
core/net/socket_linux.odin

@@ -218,11 +218,24 @@ _listen_tcp :: proc(endpoint: Endpoint, backlog := 1000) -> (socket: TCP_Socket,
 }
 }
 
 
 @(private)
 @(private)
-_bound_endpoint :: proc(sock: Any_Socket) -> (ep: Endpoint, err: Listen_Error) {
+_bound_endpoint :: proc(sock: Any_Socket) -> (ep: Endpoint, err: Socket_Info_Error) {
 	addr: linux.Sock_Addr_Any
 	addr: linux.Sock_Addr_Any
 	errno := linux.getsockname(_unwrap_os_socket(sock), &addr)
 	errno := linux.getsockname(_unwrap_os_socket(sock), &addr)
 	if errno != .NONE {
 	if errno != .NONE {
-		err = _listen_error(errno)
+		err = _socket_info_error(errno)
+		return
+	}
+
+	ep = _wrap_os_addr(addr)
+	return
+}
+
+@(private)
+_peer_endpoint :: proc(sock: Any_Socket) -> (ep: Endpoint, err: Socket_Info_Error) {
+	addr: linux.Sock_Addr_Any
+	errno := linux.getpeername(_unwrap_os_socket(sock), &addr)
+	if errno != .NONE {
+		err = _socket_info_error(errno)
 		return
 		return
 	}
 	}
 
 

+ 16 - 2
core/net/socket_windows.odin

@@ -177,11 +177,25 @@ _listen_tcp :: proc(interface_endpoint: Endpoint, backlog := 1000) -> (socket: T
 }
 }
 
 
 @(private)
 @(private)
-_bound_endpoint :: proc(sock: Any_Socket) -> (ep: Endpoint, err: Listen_Error) {
+_bound_endpoint :: proc(sock: Any_Socket) -> (ep: Endpoint, err: Socket_Info_Error) {
 	sockaddr: win.SOCKADDR_STORAGE_LH
 	sockaddr: win.SOCKADDR_STORAGE_LH
 	sockaddrlen := c.int(size_of(sockaddr))
 	sockaddrlen := c.int(size_of(sockaddr))
 	if win.getsockname(win.SOCKET(any_socket_to_socket(sock)), &sockaddr, &sockaddrlen) == win.SOCKET_ERROR {
 	if win.getsockname(win.SOCKET(any_socket_to_socket(sock)), &sockaddr, &sockaddrlen) == win.SOCKET_ERROR {
-		err = _listen_error()
+		err = _socket_info_error()
+		return
+	}
+
+	ep = _sockaddr_to_endpoint(&sockaddr)
+	return
+}
+
+@(private)
+_peer_endpoint :: proc(sock: Any_Socket) -> (ep: Endpoint, err: Socket_Info_Error) {
+	sockaddr: win.SOCKADDR_STORAGE_LH
+	sockaddrlen := c.int(size_of(sockaddr))
+	res := win.getpeername(win.SOCKET(any_socket_to_socket(sock)), &sockaddr, &sockaddrlen)
+	if res < 0 {
+		err = _socket_info_error()
 		return
 		return
 	}
 	}
 
 

+ 10 - 5
core/odin/parser/parser.odin

@@ -2307,6 +2307,7 @@ parse_operand :: proc(p: ^Parser, lhs: bool) -> ^ast.Expr {
 		open := expect_token(p, .Open_Paren)
 		open := expect_token(p, .Open_Paren)
 		p.expr_level += 1
 		p.expr_level += 1
 		expr := parse_expr(p, false)
 		expr := parse_expr(p, false)
+		skip_possible_newline(p)
 		p.expr_level -= 1
 		p.expr_level -= 1
 		close := expect_token(p, .Close_Paren)
 		close := expect_token(p, .Close_Paren)
 
 
@@ -2922,6 +2923,8 @@ parse_operand :: proc(p: ^Parser, lhs: bool) -> ^ast.Expr {
 
 
 		fields: [dynamic]^ast.Bit_Field_Field
 		fields: [dynamic]^ast.Bit_Field_Field
 		for p.curr_tok.kind != .Close_Brace && p.curr_tok.kind != .EOF {
 		for p.curr_tok.kind != .Close_Brace && p.curr_tok.kind != .EOF {
+			docs := p.lead_comment
+
 			name := parse_ident(p)
 			name := parse_ident(p)
 			expect_token(p, .Colon)
 			expect_token(p, .Colon)
 			type := parse_type(p)
 			type := parse_type(p)
@@ -2932,6 +2935,7 @@ parse_operand :: proc(p: ^Parser, lhs: bool) -> ^ast.Expr {
 			if p.curr_tok.kind == .String {
 			if p.curr_tok.kind == .String {
 				tag = expect_token(p, .String)
 				tag = expect_token(p, .String)
 			}
 			}
+			ok := allow_token(p, .Comma)
 
 
 			field := ast.new(ast.Bit_Field_Field, name.pos, bit_size)
 			field := ast.new(ast.Bit_Field_Field, name.pos, bit_size)
 
 
@@ -2939,10 +2943,14 @@ parse_operand :: proc(p: ^Parser, lhs: bool) -> ^ast.Expr {
 			field.type     = type
 			field.type     = type
 			field.bit_size = bit_size
 			field.bit_size = bit_size
 			field.tag      = tag
 			field.tag      = tag
+			field.docs     = docs
+			field.comments = p.line_comment
 
 
 			append(&fields, field)
 			append(&fields, field)
 
 
-			allow_token(p, .Comma) or_break
+			if !ok {
+				break
+			}
 		}
 		}
 
 
 		close := expect_closing_brace_of_field_list(p)
 		close := expect_closing_brace_of_field_list(p)
@@ -3526,6 +3534,7 @@ parse_binary_expr :: proc(p: ^Parser, lhs: bool, prec_in: int) -> ^ast.Expr {
 			case .When:
 			case .When:
 				x := expr
 				x := expr
 				cond := parse_expr(p, lhs)
 				cond := parse_expr(p, lhs)
+				skip_possible_newline(p)
 				else_tok := expect_token(p, .Else)
 				else_tok := expect_token(p, .Else)
 				y := parse_expr(p, lhs)
 				y := parse_expr(p, lhs)
 				te := ast.new(ast.Ternary_When_Expr, expr.pos, end_pos(p.prev_tok))
 				te := ast.new(ast.Ternary_When_Expr, expr.pos, end_pos(p.prev_tok))
@@ -3780,10 +3789,6 @@ parse_import_decl :: proc(p: ^Parser, kind := Import_Decl_Kind.Standard) -> ^ast
 		import_name.pos = p.curr_tok.pos
 		import_name.pos = p.curr_tok.pos
 	}
 	}
 
 
-	if !is_using && is_blank_ident(import_name) {
-		error(p, import_name.pos, "illegal import name: '_'")
-	}
-
 	path := expect_token_after(p, .String, "import")
 	path := expect_token_after(p, .String, "import")
 
 
 	decl := ast.new(ast.Import_Decl, tok.pos, end_pos(path))
 	decl := ast.new(ast.Import_Decl, tok.pos, end_pos(path))

+ 39 - 2
core/os/env_windows.odin

@@ -8,7 +8,7 @@ import "base:runtime"
 // Otherwise the returned value will be empty and the boolean will be false
 // Otherwise the returned value will be empty and the boolean will be false
 // NOTE: the value will be allocated with the supplied allocator
 // NOTE: the value will be allocated with the supplied allocator
 @(require_results)
 @(require_results)
-lookup_env :: proc(key: string, allocator := context.allocator) -> (value: string, found: bool) {
+lookup_env_alloc :: proc(key: string, allocator := context.allocator) -> (value: string, found: bool) {
 	if key == "" {
 	if key == "" {
 		return
 		return
 	}
 	}
@@ -29,17 +29,54 @@ lookup_env :: proc(key: string, allocator := context.allocator) -> (value: strin
 	return
 	return
 }
 }
 
 
+// This version of `lookup_env` doesn't allocate and instead requires the user to provide a buffer.
+// Note that it is limited to environment names and values of 512 utf-16 values each
+// due to the necessary utf-8 <> utf-16 conversion.
+@(require_results)
+lookup_env_buffer :: proc(buf: []u8, key: string) -> (value: string, err: Error) {
+	key_buf: [513]u16
+	wkey := win32.utf8_to_wstring(key_buf[:], key)
+	if wkey == nil {
+		return "", .Buffer_Full
+	}
+
+	n2 := win32.GetEnvironmentVariableW(wkey, nil, 0)
+	if n2 == 0 {
+		return "", .Env_Var_Not_Found
+	}
+
+	val_buf: [513]u16
+	n2 = win32.GetEnvironmentVariableW(wkey, raw_data(val_buf[:]), u32(len(val_buf[:])))
+	if n2 == 0 {
+		return "", .Env_Var_Not_Found
+	} else if int(n2) > len(buf) {
+		return "", .Buffer_Full
+	}
+
+	value = win32.utf16_to_utf8(buf, val_buf[:n2])
+
+	return value, nil
+}
+lookup_env :: proc{lookup_env_alloc, lookup_env_buffer}
 
 
 // get_env retrieves the value of the environment variable named by the key
 // get_env retrieves the value of the environment variable named by the key
 // It returns the value, which will be empty if the variable is not present
 // It returns the value, which will be empty if the variable is not present
 // To distinguish between an empty value and an unset value, use lookup_env
 // To distinguish between an empty value and an unset value, use lookup_env
 // NOTE: the value will be allocated with the supplied allocator
 // NOTE: the value will be allocated with the supplied allocator
 @(require_results)
 @(require_results)
-get_env :: proc(key: string, allocator := context.allocator) -> (value: string) {
+get_env_alloc :: proc(key: string, allocator := context.allocator) -> (value: string) {
 	value, _ = lookup_env(key, allocator)
 	value, _ = lookup_env(key, allocator)
 	return
 	return
 }
 }
 
 
+@(require_results)
+get_env_buf :: proc(buf: []u8, key: string) -> (value: string) {
+	value, _ = lookup_env(buf, key)
+	return
+}
+get_env :: proc{get_env_alloc, get_env_buf}
+
+
 // set_env sets the value of the environment variable named by the key
 // set_env sets the value of the environment variable named by the key
 set_env :: proc(key, value: string) -> Error {
 set_env :: proc(key, value: string) -> Error {
 	k := win32.utf8_to_wstring(key)
 	k := win32.utf8_to_wstring(key)

+ 4 - 0
core/os/errors.odin

@@ -35,6 +35,9 @@ General_Error :: enum u32 {
 
 
 	File_Is_Pipe,
 	File_Is_Pipe,
 	Not_Dir,
 	Not_Dir,
+
+	// Environment variable not found.
+	Env_Var_Not_Found,
 }
 }
 
 
 
 
@@ -82,6 +85,7 @@ error_string :: proc "contextless" (ferr: Error) -> string {
 		case .Pattern_Has_Separator: return "pattern has separator"
 		case .Pattern_Has_Separator: return "pattern has separator"
 		case .File_Is_Pipe:      return "file is pipe"
 		case .File_Is_Pipe:      return "file is pipe"
 		case .Not_Dir:           return "file is not directory"
 		case .Not_Dir:           return "file is not directory"
+		case .Env_Var_Not_Found: return "environment variable not found"
 		}
 		}
 	case io.Error:
 	case io.Error:
 		switch e {
 		switch e {

+ 53 - 0
core/os/os.odin

@@ -4,6 +4,7 @@ import "base:intrinsics"
 import "base:runtime"
 import "base:runtime"
 import "core:io"
 import "core:io"
 import "core:strconv"
 import "core:strconv"
+import "core:strings"
 import "core:unicode/utf8"
 import "core:unicode/utf8"
 
 
 
 
@@ -210,3 +211,55 @@ heap_free   :: runtime.heap_free
 processor_core_count :: proc() -> int {
 processor_core_count :: proc() -> int {
 	return _processor_core_count()
 	return _processor_core_count()
 }
 }
+
+// Always allocates for consistency.
+replace_environment_placeholders :: proc(path: string, allocator := context.allocator) -> (res: string) {
+	path := path
+
+	sb: strings.Builder
+	strings.builder_init_none(&sb, allocator)
+	for len(path) > 0 {
+		switch path[0] {
+		case '%': // Windows
+			when ODIN_OS == .Windows {
+				for r, i in path[1:] {
+					if r == '%' {
+						env_key := path[1:i+1]
+						env_val := get_env(env_key, context.temp_allocator)
+						strings.write_string(&sb, env_val)
+						path = path[i+1:] // % is part of key, so skip 1 character extra
+					}
+				}
+			} else {
+				strings.write_rune(&sb, rune(path[0]))
+			}
+
+		case '$': // Posix
+			when ODIN_OS != .Windows {
+				env_key := ""
+				dollar_loop: for r, i in path[1:] {
+					switch r {
+					case 'A'..='Z', 'a'..='z', '0'..='9', '_': // Part of key ident
+					case:
+						env_key = path[1:i+1]
+						break dollar_loop
+					}
+				}
+				if len(env_key) > 0 {
+					env_val := get_env(env_key, context.temp_allocator)
+					strings.write_string(&sb, env_val)
+					path = path[len(env_key):]
+				}
+
+			} else {
+				strings.write_rune(&sb, rune(path[0]))
+			}
+
+		case:
+			strings.write_rune(&sb, rune(path[0]))
+		}
+
+		path = path[1:]
+	}
+	return strings.to_string(sb)
+}

+ 79 - 5
core/os/os2/env.odin

@@ -1,26 +1,49 @@
 package os2
 package os2
 
 
 import "base:runtime"
 import "base:runtime"
+import "core:strings"
 
 
-// get_env retrieves the value of the environment variable named by the key
+// `get_env` retrieves the value of the environment variable named by the key
 // It returns the value, which will be empty if the variable is not present
 // It returns the value, which will be empty if the variable is not present
 // To distinguish between an empty value and an unset value, use lookup_env
 // To distinguish between an empty value and an unset value, use lookup_env
 // NOTE: the value will be allocated with the supplied allocator
 // NOTE: the value will be allocated with the supplied allocator
 @(require_results)
 @(require_results)
-get_env :: proc(key: string, allocator: runtime.Allocator) -> string {
+get_env_alloc :: proc(key: string, allocator: runtime.Allocator) -> string {
 	value, _ := lookup_env(key, allocator)
 	value, _ := lookup_env(key, allocator)
 	return value
 	return value
 }
 }
 
 
-// lookup_env gets the value of the environment variable named by the key
+// `get_env` retrieves the value of the environment variable named by the key
+// It returns the value, which will be empty if the variable is not present
+// To distinguish between an empty value and an unset value, use lookup_env
+// NOTE: this version takes a backing buffer for the string value
+@(require_results)
+get_env_buf :: proc(buf: []u8, key: string) -> string {
+	value, _ := lookup_env(buf, key)
+	return value
+}
+
+get_env :: proc{get_env_alloc, get_env_buf}
+
+// `lookup_env` gets the value of the environment variable named by the key
 // If the variable is found in the environment the value (which can be empty) is returned and the boolean is true
 // If the variable is found in the environment the value (which can be empty) is returned and the boolean is true
 // Otherwise the returned value will be empty and the boolean will be false
 // Otherwise the returned value will be empty and the boolean will be false
 // NOTE: the value will be allocated with the supplied allocator
 // NOTE: the value will be allocated with the supplied allocator
 @(require_results)
 @(require_results)
-lookup_env :: proc(key: string, allocator: runtime.Allocator) -> (value: string, found: bool) {
-	return _lookup_env(key, allocator)
+lookup_env_alloc :: proc(key: string, allocator: runtime.Allocator) -> (value: string, found: bool) {
+	return _lookup_env_alloc(key, allocator)
+}
+
+// This version of `lookup_env` doesn't allocate and instead requires the user to provide a buffer.
+// Note that it is limited to environment names and values of 512 utf-16 values each
+// due to the necessary utf-8 <> utf-16 conversion.
+@(require_results)
+lookup_env_buf :: proc(buf: []u8, key: string) -> (value: string, err: Error) {
+	return _lookup_env_buf(buf, key)
 }
 }
 
 
+lookup_env :: proc{lookup_env_alloc, lookup_env_buf}
+
 // set_env sets the value of the environment variable named by the key
 // set_env sets the value of the environment variable named by the key
 // Returns Error on failure
 // Returns Error on failure
 set_env :: proc(key, value: string) -> Error {
 set_env :: proc(key, value: string) -> Error {
@@ -45,4 +68,55 @@ environ :: proc(allocator: runtime.Allocator) -> ([]string, Error) {
 	return _environ(allocator)
 	return _environ(allocator)
 }
 }
 
 
+// Always allocates for consistency.
+replace_environment_placeholders :: proc(path: string, allocator: runtime.Allocator) -> (res: string) {
+	path := path
+
+	sb: strings.Builder
+	strings.builder_init_none(&sb, allocator)
+
+	for len(path) > 0 {
+		switch path[0] {
+		case '%': // Windows
+			when ODIN_OS == .Windows {
+				for r, i in path[1:] {
+					if r == '%' {
+						env_key := path[1:i+1]
+						env_val := get_env(env_key, context.temp_allocator)
+						strings.write_string(&sb, env_val)
+						path = path[i+1:] // % is part of key, so skip 1 character extra
+					}
+				}
+			} else {
+				strings.write_rune(&sb, rune(path[0]))
+			}
+
+		case '$': // Posix
+			when ODIN_OS != .Windows {
+				env_key := ""
+				dollar_loop: for r, i in path[1:] {
+					switch r {
+					case 'A'..='Z', 'a'..='z', '0'..='9', '_': // Part of key ident
+					case:
+						env_key = path[1:i+1]
+						break dollar_loop
+					}
+				}
+				if len(env_key) > 0 {
+					env_val := get_env(env_key, context.temp_allocator)
+					strings.write_string(&sb, env_val)
+					path = path[len(env_key):]
+				}
+
+			} else {
+				strings.write_rune(&sb, rune(path[0]))
+			}
+
+		case:
+			strings.write_rune(&sb, rune(path[0]))
+		}
 
 
+		path = path[1:]
+	}
+	return strings.to_string(sb)
+}

+ 18 - 1
core/os/os2/env_linux.odin

@@ -41,7 +41,7 @@ _lookup :: proc(key: string) -> (value: string, idx: int) {
 	return "", -1
 	return "", -1
 }
 }
 
 
-_lookup_env :: proc(key: string, allocator: runtime.Allocator) -> (value: string, found: bool) {
+_lookup_env_alloc :: proc(key: string, allocator: runtime.Allocator) -> (value: string, found: bool) {
 	if intrinsics.atomic_load_explicit(&_org_env_begin, .Acquire) == 0 {
 	if intrinsics.atomic_load_explicit(&_org_env_begin, .Acquire) == 0 {
 		_build_env()
 		_build_env()
 	}
 	}
@@ -53,6 +53,23 @@ _lookup_env :: proc(key: string, allocator: runtime.Allocator) -> (value: string
 	return
 	return
 }
 }
 
 
+_lookup_env_buf :: proc(buf: []u8, key: string) -> (value: string, err: Error) {
+	if intrinsics.atomic_load_explicit(&_org_env_begin, .Acquire) == 0 {
+		_build_env()
+	}
+
+	if v, idx := _lookup(key); idx != -1 {
+		if len(buf) >= len(v) {
+			copy(buf, v)
+			return string(buf[:len(v)]), nil
+		}
+		return "", .Buffer_Full
+	}
+	return "", .Env_Var_Not_Found
+}
+
+_lookup_env :: proc{_lookup_env_alloc, _lookup_env_buf}
+
 _set_env :: proc(key, v_new: string) -> Error {
 _set_env :: proc(key, v_new: string) -> Error {
 	if intrinsics.atomic_load_explicit(&_org_env_begin, .Acquire) == 0 {
 	if intrinsics.atomic_load_explicit(&_org_env_begin, .Acquire) == 0 {
 		_build_env()
 		_build_env()

+ 31 - 1
core/os/os2/env_posix.odin

@@ -7,7 +7,7 @@ import "base:runtime"
 import "core:strings"
 import "core:strings"
 import "core:sys/posix"
 import "core:sys/posix"
 
 
-_lookup_env :: proc(key: string, allocator: runtime.Allocator) -> (value: string, found: bool) {
+_lookup_env_alloc :: proc(key: string, allocator: runtime.Allocator) -> (value: string, found: bool) {
 	if key == "" {
 	if key == "" {
 		return
 		return
 	}
 	}
@@ -26,6 +26,36 @@ _lookup_env :: proc(key: string, allocator: runtime.Allocator) -> (value: string
 	return
 	return
 }
 }
 
 
+_lookup_env_buf :: proc(buf: []u8, key: string) -> (value: string, error: Error) {
+	if key == "" {
+		return
+	}
+
+	if len(key) + 1 > len(buf) {
+		return "", .Buffer_Full
+	} else {
+		copy(buf, key)
+	}
+
+	cval := posix.getenv(cstring(raw_data(buf)))
+	if cval == nil {
+		return
+	}
+
+	if value = string(cval); value == "" {
+		return "", .Env_Var_Not_Found
+	} else {
+		if len(value) > len(buf) {
+			return "", .Buffer_Full
+		} else {
+			copy(buf, value)
+			return string(buf[:len(value)]), nil
+		}
+	}
+}
+
+_lookup_env :: proc{_lookup_env_alloc, _lookup_env_buf}
+
 _set_env :: proc(key, value: string) -> (err: Error) {
 _set_env :: proc(key, value: string) -> (err: Error) {
 	temp_allocator := TEMP_ALLOCATOR_GUARD({})
 	temp_allocator := TEMP_ALLOCATOR_GUARD({})
 
 

+ 29 - 1
core/os/os2/env_wasi.odin

@@ -67,7 +67,7 @@ delete_string_if_not_original :: proc(str: string) {
 }
 }
 
 
 @(require_results)
 @(require_results)
-_lookup_env :: proc(key: string, allocator: runtime.Allocator) -> (value: string, found: bool) {
+_lookup_env_alloc :: proc(key: string, allocator: runtime.Allocator) -> (value: string, found: bool) {
 	if err := build_env(); err != nil {
 	if err := build_env(); err != nil {
 		return
 		return
 	}
 	}
@@ -79,6 +79,34 @@ _lookup_env :: proc(key: string, allocator: runtime.Allocator) -> (value: string
 	return
 	return
 }
 }
 
 
+_lookup_env_buf :: proc(buf: []u8, key: string) -> (value: string, error: Error) {
+	if key == "" {
+		return
+	}
+
+	if len(key) + 1 > len(buf) {
+		return "", .Buffer_Full
+	} else {
+		copy(buf, key)
+	}
+
+	sync.shared_guard(&g_env_mutex)
+
+	val, ok := g_env[key]
+
+	if !ok {
+		return "", .Env_Var_Not_Found
+	} else {
+		if len(val) > len(buf) {
+			return "", .Buffer_Full
+		} else {
+			copy(buf, val)
+			return string(buf[:len(val)]), nil
+		}
+	}
+}
+_lookup_env :: proc{_lookup_env_alloc, _lookup_env_buf}
+
 @(require_results)
 @(require_results)
 _set_env :: proc(key, value: string) -> (err: Error) {
 _set_env :: proc(key, value: string) -> (err: Error) {
 	build_env() or_return
 	build_env() or_return

+ 31 - 1
core/os/os2/env_windows.odin

@@ -4,7 +4,7 @@ package os2
 import win32 "core:sys/windows"
 import win32 "core:sys/windows"
 import "base:runtime"
 import "base:runtime"
 
 
-_lookup_env :: proc(key: string, allocator: runtime.Allocator) -> (value: string, found: bool) {
+_lookup_env_alloc :: proc(key: string, allocator: runtime.Allocator) -> (value: string, found: bool) {
 	if key == "" {
 	if key == "" {
 		return
 		return
 	}
 	}
@@ -36,6 +36,36 @@ _lookup_env :: proc(key: string, allocator: runtime.Allocator) -> (value: string
 	return
 	return
 }
 }
 
 
+// This version of `lookup_env` doesn't allocate and instead requires the user to provide a buffer.
+// Note that it is limited to environment names and values of 512 utf-16 values each
+// due to the necessary utf-8 <> utf-16 conversion.
+@(require_results)
+_lookup_env_buf :: proc(buf: []u8, key: string) -> (value: string, err: Error) {
+	key_buf: [513]u16
+	wkey := win32.utf8_to_wstring(key_buf[:], key)
+	if wkey == nil {
+		return "", .Buffer_Full
+	}
+
+	n2 := win32.GetEnvironmentVariableW(wkey, nil, 0)
+	if n2 == 0 {
+		return "", .Env_Var_Not_Found
+	}
+
+	val_buf: [513]u16
+	n2 = win32.GetEnvironmentVariableW(wkey, raw_data(val_buf[:]), u32(len(val_buf[:])))
+	if n2 == 0 {
+		return "", .Env_Var_Not_Found
+	} else if int(n2) > len(buf) {
+		return "", .Buffer_Full
+	}
+
+	value = win32.utf16_to_utf8(buf, val_buf[:n2])
+
+	return value, nil
+}
+_lookup_env :: proc{_lookup_env_alloc, _lookup_env_buf}
+
 _set_env :: proc(key, value: string) -> Error {
 _set_env :: proc(key, value: string) -> Error {
 	temp_allocator := TEMP_ALLOCATOR_GUARD({})
 	temp_allocator := TEMP_ALLOCATOR_GUARD({})
 	k := win32_utf8_to_wstring(key,   temp_allocator) or_return
 	k := win32_utf8_to_wstring(key,   temp_allocator) or_return

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

@@ -28,7 +28,7 @@ General_Error :: enum u32 {
 	Pattern_Has_Separator,
 	Pattern_Has_Separator,
 
 
 	No_HOME_Variable,
 	No_HOME_Variable,
-	Wordexp_Failed,
+	Env_Var_Not_Found,
 
 
 	Unsupported,
 	Unsupported,
 }
 }
@@ -77,7 +77,7 @@ error_string :: proc(ferr: Error) -> string {
 		case .Unsupported:            return "unsupported"
 		case .Unsupported:            return "unsupported"
 		case .Pattern_Has_Separator:  return "pattern has separator"
 		case .Pattern_Has_Separator:  return "pattern has separator"
 		case .No_HOME_Variable:       return "no $HOME variable"
 		case .No_HOME_Variable:       return "no $HOME variable"
-		case .Wordexp_Failed:         return "posix.wordexp was unable to expand"
+		case .Env_Var_Not_Found:      return "environment variable not found"
 		}
 		}
 	case io.Error:
 	case io.Error:
 		switch e {
 		switch e {

+ 1 - 0
core/os/os2/file_linux.odin

@@ -269,6 +269,7 @@ _write_at :: proc(f: ^File_Impl, p: []byte, offset: i64) -> (nt: i64, err: Error
 	return
 	return
 }
 }
 
 
+@(no_sanitize_memory)
 _file_size :: proc(f: ^File_Impl) -> (n: i64, err: Error) {
 _file_size :: proc(f: ^File_Impl) -> (n: i64, err: Error) {
 	// TODO: Identify 0-sized "pseudo" files and return No_Size. This would
 	// TODO: Identify 0-sized "pseudo" files and return No_Size. This would
 	//       eliminate the need for the _read_entire_pseudo_file procs.
 	//       eliminate the need for the _read_entire_pseudo_file procs.

+ 1 - 9
core/os/os2/user_posix.odin

@@ -4,7 +4,6 @@ package os2
 import "base:runtime"
 import "base:runtime"
 import "core:encoding/ini"
 import "core:encoding/ini"
 import "core:strings"
 import "core:strings"
-import "core:sys/posix"
 
 
 _user_cache_dir :: proc(allocator: runtime.Allocator) -> (dir: string, err: Error) {
 _user_cache_dir :: proc(allocator: runtime.Allocator) -> (dir: string, err: Error) {
 	#partial switch ODIN_OS {
 	#partial switch ODIN_OS {
@@ -169,14 +168,7 @@ _xdg_user_dirs_lookup :: proc(xdg_key: string, allocator: runtime.Allocator) ->
 
 
 	for k, v in ini.iterate(&it) {
 	for k, v in ini.iterate(&it) {
 		if k == xdg_key {
 		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 replace_environment_placeholders(v, allocator), nil
 		}
 		}
 	}
 	}
 	return
 	return

+ 37 - 3
core/os/os_darwin.odin

@@ -1090,9 +1090,10 @@ flush :: proc(fd: Handle) -> Error {
 }
 }
 
 
 @(require_results)
 @(require_results)
-lookup_env :: proc(key: string, allocator := context.allocator) -> (value: string, found: bool) {
+lookup_env_alloc :: proc(key: string, allocator := context.allocator) -> (value: string, found: bool) {
 	runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD(ignore = context.temp_allocator == allocator)
 	runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD(ignore = context.temp_allocator == allocator)
 	path_str := strings.clone_to_cstring(key, context.temp_allocator)
 	path_str := strings.clone_to_cstring(key, context.temp_allocator)
+	// NOTE(tetra): Lifetime of 'cstr' is unclear, but _unix_free(cstr) segfaults.
 	cstr := _unix_getenv(path_str)
 	cstr := _unix_getenv(path_str)
 	if cstr == nil {
 	if cstr == nil {
 		return "", false
 		return "", false
@@ -1101,11 +1102,39 @@ lookup_env :: proc(key: string, allocator := context.allocator) -> (value: strin
 }
 }
 
 
 @(require_results)
 @(require_results)
-get_env :: proc(key: string, allocator := context.allocator) -> (value: string) {
+lookup_env_buffer :: proc(buf: []u8, key: string) -> (value: string, err: Error) {
+	if len(key) + 1 > len(buf) {
+		return "", .Buffer_Full
+	} else {
+		copy(buf, key)
+	}
+
+	if value = string(_unix_getenv(cstring(raw_data(buf)))); value == "" {
+		return "", .Env_Var_Not_Found
+	} else {
+		if len(value) > len(buf) {
+			return "", .Buffer_Full
+		} else {
+			copy(buf, value)
+			return string(buf[:len(value)]), nil
+		}
+	}
+}
+lookup_env :: proc{lookup_env_alloc, lookup_env_buffer}
+
+@(require_results)
+get_env_alloc :: proc(key: string, allocator := context.allocator) -> (value: string) {
 	value, _ = lookup_env(key, allocator)
 	value, _ = lookup_env(key, allocator)
 	return
 	return
 }
 }
 
 
+@(require_results)
+get_env_buf :: proc(buf: []u8, key: string) -> (value: string) {
+	value, _ = lookup_env(buf, key)
+	return
+}
+get_env :: proc{get_env_alloc, get_env_buf}
+
 set_env :: proc(key, value: string) -> Error {
 set_env :: proc(key, value: string) -> Error {
 	runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD()
 	runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD()
 	key_cstring := strings.clone_to_cstring(key, context.temp_allocator)
 	key_cstring := strings.clone_to_cstring(key, context.temp_allocator)
@@ -1231,7 +1260,7 @@ _processor_core_count :: proc() -> int {
 	return 1
 	return 1
 }
 }
 
 
-@(require_results)
+@(private, require_results)
 _alloc_command_line_arguments :: proc() -> []string {
 _alloc_command_line_arguments :: proc() -> []string {
 	res := make([]string, len(runtime.args__))
 	res := make([]string, len(runtime.args__))
 	for _, i in res {
 	for _, i in res {
@@ -1240,6 +1269,11 @@ _alloc_command_line_arguments :: proc() -> []string {
 	return res
 	return res
 }
 }
 
 
+@(private, fini)
+_delete_command_line_arguments :: proc() {
+	delete(args)
+}
+
 socket :: proc(domain: int, type: int, protocol: int) -> (Socket, Error) {
 socket :: proc(domain: int, type: int, protocol: int) -> (Socket, Error) {
 	result := _unix_socket(c.int(domain), c.int(type), c.int(protocol))
 	result := _unix_socket(c.int(domain), c.int(type), c.int(protocol))
 	if result < 0 {
 	if result < 0 {

+ 40 - 7
core/os/os_freebsd.odin

@@ -662,7 +662,7 @@ last_write_time_by_name :: proc(name: string) -> (File_Time, Error) {
 	return File_Time(modified), nil
 	return File_Time(modified), nil
 }
 }
 
 
-@(private, require_results)
+@(private, require_results, no_sanitize_memory)
 _stat :: proc(path: string) -> (OS_Stat, Error) {
 _stat :: proc(path: string) -> (OS_Stat, Error) {
 	runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD()
 	runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD()
 	cstr := strings.clone_to_cstring(path, context.temp_allocator)
 	cstr := strings.clone_to_cstring(path, context.temp_allocator)
@@ -674,7 +674,7 @@ _stat :: proc(path: string) -> (OS_Stat, Error) {
 	return s, nil
 	return s, nil
 }
 }
 
 
-@(private, require_results)
+@(private, require_results, no_sanitize_memory)
 _lstat :: proc(path: string) -> (OS_Stat, Error) {
 _lstat :: proc(path: string) -> (OS_Stat, Error) {
 	runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD()
 	runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD()
 	cstr := strings.clone_to_cstring(path, context.temp_allocator)
 	cstr := strings.clone_to_cstring(path, context.temp_allocator)
@@ -688,7 +688,7 @@ _lstat :: proc(path: string) -> (OS_Stat, Error) {
 	return s, nil
 	return s, nil
 }
 }
 
 
-@(private, require_results)
+@(private, require_results, no_sanitize_memory)
 _fstat :: proc(fd: Handle) -> (OS_Stat, Error) {
 _fstat :: proc(fd: Handle) -> (OS_Stat, Error) {
 	s: OS_Stat = ---
 	s: OS_Stat = ---
 	result := _unix_fstat(fd, &s)
 	result := _unix_fstat(fd, &s)
@@ -827,10 +827,10 @@ access :: proc(path: string, mask: int) -> (bool, Error) {
 }
 }
 
 
 @(require_results)
 @(require_results)
-lookup_env :: proc(key: string, allocator := context.allocator) -> (value: string, found: bool) {
+lookup_env_alloc :: proc(key: string, allocator := context.allocator) -> (value: string, found: bool) {
 	runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD(ignore = context.temp_allocator == allocator)
 	runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD(ignore = context.temp_allocator == allocator)
-
 	path_str := strings.clone_to_cstring(key, context.temp_allocator)
 	path_str := strings.clone_to_cstring(key, context.temp_allocator)
+	// NOTE(tetra): Lifetime of 'cstr' is unclear, but _unix_free(cstr) segfaults.
 	cstr := _unix_getenv(path_str)
 	cstr := _unix_getenv(path_str)
 	if cstr == nil {
 	if cstr == nil {
 		return "", false
 		return "", false
@@ -839,11 +839,39 @@ lookup_env :: proc(key: string, allocator := context.allocator) -> (value: strin
 }
 }
 
 
 @(require_results)
 @(require_results)
-get_env :: proc(key: string, allocator := context.allocator) -> (value: string) {
+lookup_env_buffer :: proc(buf: []u8, key: string) -> (value: string, err: Error) {
+	if len(key) + 1 > len(buf) {
+		return "", .Buffer_Full
+	} else {
+		copy(buf, key)
+	}
+
+	if value = string(_unix_getenv(cstring(raw_data(buf)))); value == "" {
+		return "", .Env_Var_Not_Found
+	} else {
+		if len(value) > len(buf) {
+			return "", .Buffer_Full
+		} else {
+			copy(buf, value)
+			return string(buf[:len(value)]), nil
+		}
+	}
+}
+lookup_env :: proc{lookup_env_alloc, lookup_env_buffer}
+
+@(require_results)
+get_env_alloc :: proc(key: string, allocator := context.allocator) -> (value: string) {
 	value, _ = lookup_env(key, allocator)
 	value, _ = lookup_env(key, allocator)
 	return
 	return
 }
 }
 
 
+@(require_results)
+get_env_buf :: proc(buf: []u8, key: string) -> (value: string) {
+	value, _ = lookup_env(buf, key)
+	return
+}
+get_env :: proc{get_env_alloc, get_env_buf}
+
 @(require_results)
 @(require_results)
 get_current_directory :: proc(allocator := context.allocator) -> string {
 get_current_directory :: proc(allocator := context.allocator) -> string {
 	context.allocator = allocator
 	context.allocator = allocator
@@ -936,7 +964,7 @@ _processor_core_count :: proc() -> int {
 }
 }
 
 
 
 
-@(require_results)
+@(private, require_results)
 _alloc_command_line_arguments :: proc() -> []string {
 _alloc_command_line_arguments :: proc() -> []string {
 	res := make([]string, len(runtime.args__))
 	res := make([]string, len(runtime.args__))
 	for arg, i in runtime.args__ {
 	for arg, i in runtime.args__ {
@@ -944,3 +972,8 @@ _alloc_command_line_arguments :: proc() -> []string {
 	}
 	}
 	return res
 	return res
 }
 }
+
+@(private, fini)
+_delete_command_line_arguments :: proc() {
+	delete(args)
+}

+ 41 - 6
core/os/os_haiku.odin

@@ -316,7 +316,7 @@ file_size :: proc(fd: Handle) -> (i64, Error) {
 // "Argv" arguments converted to Odin strings
 // "Argv" arguments converted to Odin strings
 args := _alloc_command_line_arguments()
 args := _alloc_command_line_arguments()
 
 
-@(require_results)
+@(private, require_results)
 _alloc_command_line_arguments :: proc() -> []string {
 _alloc_command_line_arguments :: proc() -> []string {
 	res := make([]string, len(runtime.args__))
 	res := make([]string, len(runtime.args__))
 	for arg, i in runtime.args__ {
 	for arg, i in runtime.args__ {
@@ -325,7 +325,12 @@ _alloc_command_line_arguments :: proc() -> []string {
 	return res
 	return res
 }
 }
 
 
-@(private, require_results)
+@(private, fini)
+_delete_command_line_arguments :: proc() {
+	delete(args)
+}
+
+@(private, require_results, no_sanitize_memory)
 _stat :: proc(path: string) -> (OS_Stat, Error) {
 _stat :: proc(path: string) -> (OS_Stat, Error) {
 	runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD()
 	runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD()
 	cstr := strings.clone_to_cstring(path, context.temp_allocator)
 	cstr := strings.clone_to_cstring(path, context.temp_allocator)
@@ -339,7 +344,7 @@ _stat :: proc(path: string) -> (OS_Stat, Error) {
 	return s, nil
 	return s, nil
 }
 }
 
 
-@(private, require_results)
+@(private, require_results, no_sanitize_memory)
 _lstat :: proc(path: string) -> (OS_Stat, Error) {
 _lstat :: proc(path: string) -> (OS_Stat, Error) {
 	runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD()
 	runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD()
 	cstr := strings.clone_to_cstring(path, context.temp_allocator)
 	cstr := strings.clone_to_cstring(path, context.temp_allocator)
@@ -353,7 +358,7 @@ _lstat :: proc(path: string) -> (OS_Stat, Error) {
 	return s, nil
 	return s, nil
 }
 }
 
 
-@(private, require_results)
+@(private, require_results, no_sanitize_memory)
 _fstat :: proc(fd: Handle) -> (OS_Stat, Error) {
 _fstat :: proc(fd: Handle) -> (OS_Stat, Error) {
 	// deliberately uninitialized
 	// deliberately uninitialized
 	s: OS_Stat = ---
 	s: OS_Stat = ---
@@ -463,9 +468,10 @@ access :: proc(path: string, mask: int) -> (bool, Error) {
 }
 }
 
 
 @(require_results)
 @(require_results)
-lookup_env :: proc(key: string, allocator := context.allocator) -> (value: string, found: bool) {
+lookup_env_alloc :: proc(key: string, allocator := context.allocator) -> (value: string, found: bool) {
 	runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD(ignore = context.temp_allocator == allocator)
 	runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD(ignore = context.temp_allocator == allocator)
 	path_str := strings.clone_to_cstring(key, context.temp_allocator)
 	path_str := strings.clone_to_cstring(key, context.temp_allocator)
+	// NOTE(tetra): Lifetime of 'cstr' is unclear, but _unix_free(cstr) segfaults.
 	cstr := _unix_getenv(path_str)
 	cstr := _unix_getenv(path_str)
 	if cstr == nil {
 	if cstr == nil {
 		return "", false
 		return "", false
@@ -474,11 +480,40 @@ lookup_env :: proc(key: string, allocator := context.allocator) -> (value: strin
 }
 }
 
 
 @(require_results)
 @(require_results)
-get_env :: proc(key: string, allocator := context.allocator) -> (value: string) {
+lookup_env_buffer :: proc(buf: []u8, key: string) -> (value: string, err: Error) {
+	if len(key) + 1 > len(buf) {
+		return "", .Buffer_Full
+	} else {
+		copy(buf, key)
+	}
+
+	if value = string(_unix_getenv(cstring(raw_data(buf)))); value == "" {
+		return "", .Env_Var_Not_Found
+	} else {
+		if len(value) > len(buf) {
+			return "", .Buffer_Full
+		} else {
+			copy(buf, value)
+			return string(buf[:len(value)]), nil
+		}
+	}
+}
+lookup_env :: proc{lookup_env_alloc, lookup_env_buffer}
+
+@(require_results)
+get_env_alloc :: proc(key: string, allocator := context.allocator) -> (value: string) {
 	value, _ = lookup_env(key, allocator)
 	value, _ = lookup_env(key, allocator)
 	return
 	return
 }
 }
 
 
+@(require_results)
+get_env_buf :: proc(buf: []u8, key: string) -> (value: string) {
+	value, _ = lookup_env(buf, key)
+	return
+}
+get_env :: proc{get_env_alloc, get_env_buf}
+
+
 @(private, require_results)
 @(private, require_results)
 _processor_core_count :: proc() -> int {
 _processor_core_count :: proc() -> int {
 	info: haiku.system_info
 	info: haiku.system_info

+ 22 - 2
core/os/os_js.odin

@@ -250,6 +250,26 @@ current_thread_id :: proc "contextless" () -> int {
 	return 0
 	return 0
 }
 }
 
 
-lookup_env :: proc(key: string, allocator := context.allocator) -> (value: string, found: bool) {
+@(require_results)
+lookup_env_alloc :: proc(key: string, allocator := context.allocator) -> (value: string, found: bool) {
 	return "", false
 	return "", false
-}
+}
+
+@(require_results)
+lookup_env_buffer :: proc(buf: []u8, key: string) -> (value: string, err: Error) {
+	return "", .Env_Var_Not_Found
+}
+lookup_env :: proc{lookup_env_alloc, lookup_env_buffer}
+
+@(require_results)
+get_env_alloc :: proc(key: string, allocator := context.allocator) -> (value: string) {
+	value, _ = lookup_env(key, allocator)
+	return
+}
+
+@(require_results)
+get_env_buf :: proc(buf: []u8, key: string) -> (value: string) {
+	value, _ = lookup_env(buf, key)
+	return
+}
+get_env :: proc{get_env_alloc, get_env_buf}

+ 40 - 7
core/os/os_linux.odin

@@ -674,7 +674,7 @@ seek :: proc(fd: Handle, offset: i64, whence: int) -> (i64, Error) {
 	return i64(res), nil
 	return i64(res), nil
 }
 }
 
 
-@(require_results)
+@(require_results, no_sanitize_memory)
 file_size :: proc(fd: Handle) -> (i64, Error) {
 file_size :: proc(fd: Handle) -> (i64, Error) {
 	// deliberately uninitialized; the syscall fills this buffer for us
 	// deliberately uninitialized; the syscall fills this buffer for us
 	s: OS_Stat = ---
 	s: OS_Stat = ---
@@ -794,7 +794,7 @@ last_write_time_by_name :: proc(name: string) -> (time: File_Time, err: Error) {
 	return File_Time(modified), nil
 	return File_Time(modified), nil
 }
 }
 
 
-@(private, require_results)
+@(private, require_results, no_sanitize_memory)
 _stat :: proc(path: string) -> (OS_Stat, Error) {
 _stat :: proc(path: string) -> (OS_Stat, Error) {
 	runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD()
 	runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD()
 	cstr := strings.clone_to_cstring(path, context.temp_allocator)
 	cstr := strings.clone_to_cstring(path, context.temp_allocator)
@@ -808,7 +808,7 @@ _stat :: proc(path: string) -> (OS_Stat, Error) {
 	return s, nil
 	return s, nil
 }
 }
 
 
-@(private, require_results)
+@(private, require_results, no_sanitize_memory)
 _lstat :: proc(path: string) -> (OS_Stat, Error) {
 _lstat :: proc(path: string) -> (OS_Stat, Error) {
 	runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD()
 	runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD()
 	cstr := strings.clone_to_cstring(path, context.temp_allocator)
 	cstr := strings.clone_to_cstring(path, context.temp_allocator)
@@ -822,7 +822,7 @@ _lstat :: proc(path: string) -> (OS_Stat, Error) {
 	return s, nil
 	return s, nil
 }
 }
 
 
-@(private, require_results)
+@(private, require_results, no_sanitize_memory)
 _fstat :: proc(fd: Handle) -> (OS_Stat, Error) {
 _fstat :: proc(fd: Handle) -> (OS_Stat, Error) {
 	// deliberately uninitialized; the syscall fills this buffer for us
 	// deliberately uninitialized; the syscall fills this buffer for us
 	s: OS_Stat = ---
 	s: OS_Stat = ---
@@ -946,7 +946,7 @@ access :: proc(path: string, mask: int) -> (bool, Error) {
 }
 }
 
 
 @(require_results)
 @(require_results)
-lookup_env :: proc(key: string, allocator := context.allocator) -> (value: string, found: bool) {
+lookup_env_alloc :: proc(key: string, allocator := context.allocator) -> (value: string, found: bool) {
 	runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD(ignore = context.temp_allocator == allocator)
 	runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD(ignore = context.temp_allocator == allocator)
 	path_str := strings.clone_to_cstring(key, context.temp_allocator)
 	path_str := strings.clone_to_cstring(key, context.temp_allocator)
 	// NOTE(tetra): Lifetime of 'cstr' is unclear, but _unix_free(cstr) segfaults.
 	// NOTE(tetra): Lifetime of 'cstr' is unclear, but _unix_free(cstr) segfaults.
@@ -958,11 +958,39 @@ lookup_env :: proc(key: string, allocator := context.allocator) -> (value: strin
 }
 }
 
 
 @(require_results)
 @(require_results)
-get_env :: proc(key: string, allocator := context.allocator) -> (value: string) {
+lookup_env_buffer :: proc(buf: []u8, key: string) -> (value: string, err: Error) {
+	if len(key) + 1 > len(buf) {
+		return "", .Buffer_Full
+	} else {
+		copy(buf, key)
+	}
+
+	if value = string(_unix_getenv(cstring(raw_data(buf)))); value == "" {
+		return "", .Env_Var_Not_Found
+	} else {
+		if len(value) > len(buf) {
+			return "", .Buffer_Full
+		} else {
+			copy(buf, value)
+			return string(buf[:len(value)]), nil
+		}
+	}
+}
+lookup_env :: proc{lookup_env_alloc, lookup_env_buffer}
+
+@(require_results)
+get_env_alloc :: proc(key: string, allocator := context.allocator) -> (value: string) {
 	value, _ = lookup_env(key, allocator)
 	value, _ = lookup_env(key, allocator)
 	return
 	return
 }
 }
 
 
+@(require_results)
+get_env_buf :: proc(buf: []u8, key: string) -> (value: string) {
+	value, _ = lookup_env(buf, key)
+	return
+}
+get_env :: proc{get_env_alloc, get_env_buf}
+
 set_env :: proc(key, value: string) -> Error {
 set_env :: proc(key, value: string) -> Error {
 	runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD()
 	runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD()
 	key_cstring := strings.clone_to_cstring(key, context.temp_allocator)
 	key_cstring := strings.clone_to_cstring(key, context.temp_allocator)
@@ -1069,7 +1097,7 @@ _processor_core_count :: proc() -> int {
 	return int(_unix_get_nprocs())
 	return int(_unix_get_nprocs())
 }
 }
 
 
-@(require_results)
+@(private, require_results)
 _alloc_command_line_arguments :: proc() -> []string {
 _alloc_command_line_arguments :: proc() -> []string {
 	res := make([]string, len(runtime.args__))
 	res := make([]string, len(runtime.args__))
 	for arg, i in runtime.args__ {
 	for arg, i in runtime.args__ {
@@ -1078,6 +1106,11 @@ _alloc_command_line_arguments :: proc() -> []string {
 	return res
 	return res
 }
 }
 
 
+@(private, fini)
+_delete_command_line_arguments :: proc() {
+	delete(args)
+}
+
 @(require_results)
 @(require_results)
 socket :: proc(domain: int, type: int, protocol: int) -> (Socket, Error) {
 socket :: proc(domain: int, type: int, protocol: int) -> (Socket, Error) {
 	result := unix.sys_socket(domain, type, protocol)
 	result := unix.sys_socket(domain, type, protocol)

+ 40 - 7
core/os/os_netbsd.odin

@@ -724,7 +724,7 @@ last_write_time_by_name :: proc(name: string) -> (time: File_Time, err: Error) {
 	return File_Time(modified), nil
 	return File_Time(modified), nil
 }
 }
 
 
-@(private, require_results)
+@(private, require_results, no_sanitize_memory)
 _stat :: proc(path: string) -> (OS_Stat, Error) {
 _stat :: proc(path: string) -> (OS_Stat, Error) {
 	runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD()
 	runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD()
 	cstr := strings.clone_to_cstring(path, context.temp_allocator)
 	cstr := strings.clone_to_cstring(path, context.temp_allocator)
@@ -736,7 +736,7 @@ _stat :: proc(path: string) -> (OS_Stat, Error) {
 	return s, nil
 	return s, nil
 }
 }
 
 
-@(private, require_results)
+@(private, require_results, no_sanitize_memory)
 _lstat :: proc(path: string) -> (OS_Stat, Error) {
 _lstat :: proc(path: string) -> (OS_Stat, Error) {
 	runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD()
 	runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD()
 	cstr := strings.clone_to_cstring(path, context.temp_allocator)
 	cstr := strings.clone_to_cstring(path, context.temp_allocator)
@@ -750,7 +750,7 @@ _lstat :: proc(path: string) -> (OS_Stat, Error) {
 	return s, nil
 	return s, nil
 }
 }
 
 
-@(private, require_results)
+@(private, require_results, no_sanitize_memory)
 _fstat :: proc(fd: Handle) -> (OS_Stat, Error) {
 _fstat :: proc(fd: Handle) -> (OS_Stat, Error) {
 	s: OS_Stat = ---
 	s: OS_Stat = ---
 	result := _unix_fstat(fd, &s)
 	result := _unix_fstat(fd, &s)
@@ -874,10 +874,10 @@ access :: proc(path: string, mask: int) -> (bool, Error) {
 }
 }
 
 
 @(require_results)
 @(require_results)
-lookup_env :: proc(key: string, allocator := context.allocator) -> (value: string, found: bool) {
+lookup_env_alloc :: proc(key: string, allocator := context.allocator) -> (value: string, found: bool) {
 	runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD(ignore = context.temp_allocator == allocator)
 	runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD(ignore = context.temp_allocator == allocator)
-
 	path_str := strings.clone_to_cstring(key, context.temp_allocator)
 	path_str := strings.clone_to_cstring(key, context.temp_allocator)
+	// NOTE(tetra): Lifetime of 'cstr' is unclear, but _unix_free(cstr) segfaults.
 	cstr := _unix_getenv(path_str)
 	cstr := _unix_getenv(path_str)
 	if cstr == nil {
 	if cstr == nil {
 		return "", false
 		return "", false
@@ -886,11 +886,39 @@ lookup_env :: proc(key: string, allocator := context.allocator) -> (value: strin
 }
 }
 
 
 @(require_results)
 @(require_results)
-get_env :: proc(key: string, allocator := context.allocator) -> (value: string) {
+lookup_env_buffer :: proc(buf: []u8, key: string) -> (value: string, err: Error) {
+	if len(key) + 1 > len(buf) {
+		return "", .Buffer_Full
+	} else {
+		copy(buf, key)
+	}
+
+	if value = string(_unix_getenv(cstring(raw_data(buf)))); value == "" {
+		return "", .Env_Var_Not_Found
+	} else {
+		if len(value) > len(buf) {
+			return "", .Buffer_Full
+		} else {
+			copy(buf, value)
+			return string(buf[:len(value)]), nil
+		}
+	}
+}
+lookup_env :: proc{lookup_env_alloc, lookup_env_buffer}
+
+@(require_results)
+get_env_alloc :: proc(key: string, allocator := context.allocator) -> (value: string) {
 	value, _ = lookup_env(key, allocator)
 	value, _ = lookup_env(key, allocator)
 	return
 	return
 }
 }
 
 
+@(require_results)
+get_env_buf :: proc(buf: []u8, key: string) -> (value: string) {
+	value, _ = lookup_env(buf, key)
+	return
+}
+get_env :: proc{get_env_alloc, get_env_buf}
+
 @(require_results)
 @(require_results)
 get_current_directory :: proc(allocator := context.allocator) -> string {
 get_current_directory :: proc(allocator := context.allocator) -> string {
 	context.allocator = allocator
 	context.allocator = allocator
@@ -986,7 +1014,7 @@ _processor_core_count :: proc() -> int {
 	return 1
 	return 1
 }
 }
 
 
-@(require_results)
+@(private, require_results)
 _alloc_command_line_arguments :: proc() -> []string {
 _alloc_command_line_arguments :: proc() -> []string {
 	res := make([]string, len(runtime.args__))
 	res := make([]string, len(runtime.args__))
 	for arg, i in runtime.args__ {
 	for arg, i in runtime.args__ {
@@ -994,3 +1022,8 @@ _alloc_command_line_arguments :: proc() -> []string {
 	}
 	}
 	return res
 	return res
 }
 }
+
+@(private, fini)
+_delete_command_line_arguments :: proc() {
+	delete(args)
+}

+ 40 - 6
core/os/os_openbsd.odin

@@ -639,7 +639,7 @@ last_write_time_by_name :: proc(name: string) -> (time: File_Time, err: Error) {
 	return File_Time(modified), nil
 	return File_Time(modified), nil
 }
 }
 
 
-@(private, require_results)
+@(private, require_results, no_sanitize_memory)
 _stat :: proc(path: string) -> (OS_Stat, Error) {
 _stat :: proc(path: string) -> (OS_Stat, Error) {
 	runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD()
 	runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD()
 	cstr := strings.clone_to_cstring(path, context.temp_allocator)
 	cstr := strings.clone_to_cstring(path, context.temp_allocator)
@@ -653,7 +653,7 @@ _stat :: proc(path: string) -> (OS_Stat, Error) {
 	return s, nil
 	return s, nil
 }
 }
 
 
-@(private, require_results)
+@(private, require_results, no_sanitize_memory)
 _lstat :: proc(path: string) -> (OS_Stat, Error) {
 _lstat :: proc(path: string) -> (OS_Stat, Error) {
 	runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD()
 	runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD()
 	cstr := strings.clone_to_cstring(path, context.temp_allocator)
 	cstr := strings.clone_to_cstring(path, context.temp_allocator)
@@ -667,7 +667,7 @@ _lstat :: proc(path: string) -> (OS_Stat, Error) {
 	return s, nil
 	return s, nil
 }
 }
 
 
-@(private, require_results)
+@(private, require_results, no_sanitize_memory)
 _fstat :: proc(fd: Handle) -> (OS_Stat, Error) {
 _fstat :: proc(fd: Handle) -> (OS_Stat, Error) {
 	// deliberately uninitialized
 	// deliberately uninitialized
 	s: OS_Stat = ---
 	s: OS_Stat = ---
@@ -787,9 +787,10 @@ access :: proc(path: string, mask: int) -> (bool, Error) {
 }
 }
 
 
 @(require_results)
 @(require_results)
-lookup_env :: proc(key: string, allocator := context.allocator) -> (value: string, found: bool) {
+lookup_env_alloc :: proc(key: string, allocator := context.allocator) -> (value: string, found: bool) {
 	runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD(ignore = context.temp_allocator == allocator)
 	runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD(ignore = context.temp_allocator == allocator)
 	path_str := strings.clone_to_cstring(key, context.temp_allocator)
 	path_str := strings.clone_to_cstring(key, context.temp_allocator)
+	// NOTE(tetra): Lifetime of 'cstr' is unclear, but _unix_free(cstr) segfaults.
 	cstr := _unix_getenv(path_str)
 	cstr := _unix_getenv(path_str)
 	if cstr == nil {
 	if cstr == nil {
 		return "", false
 		return "", false
@@ -798,11 +799,39 @@ lookup_env :: proc(key: string, allocator := context.allocator) -> (value: strin
 }
 }
 
 
 @(require_results)
 @(require_results)
-get_env :: proc(key: string, allocator := context.allocator) -> (value: string) {
+lookup_env_buffer :: proc(buf: []u8, key: string) -> (value: string, err: Error) {
+	if len(key) + 1 > len(buf) {
+		return "", .Buffer_Full
+	} else {
+		copy(buf, key)
+	}
+
+	if value = string(_unix_getenv(cstring(raw_data(buf)))); value == "" {
+		return "", .Env_Var_Not_Found
+	} else {
+		if len(value) > len(buf) {
+			return "", .Buffer_Full
+		} else {
+			copy(buf, value)
+			return string(buf[:len(value)]), nil
+		}
+	}
+}
+lookup_env :: proc{lookup_env_alloc, lookup_env_buffer}
+
+@(require_results)
+get_env_alloc :: proc(key: string, allocator := context.allocator) -> (value: string) {
 	value, _ = lookup_env(key, allocator)
 	value, _ = lookup_env(key, allocator)
 	return
 	return
 }
 }
 
 
+@(require_results)
+get_env_buf :: proc(buf: []u8, key: string) -> (value: string) {
+	value, _ = lookup_env(buf, key)
+	return
+}
+get_env :: proc{get_env_alloc, get_env_buf}
+
 @(require_results)
 @(require_results)
 get_current_directory :: proc(allocator := context.allocator) -> string {
 get_current_directory :: proc(allocator := context.allocator) -> string {
 	context.allocator = allocator
 	context.allocator = allocator
@@ -885,7 +914,7 @@ _processor_core_count :: proc() -> int {
 	return int(_sysconf(_SC_NPROCESSORS_ONLN))
 	return int(_sysconf(_SC_NPROCESSORS_ONLN))
 }
 }
 
 
-@(require_results)
+@(private, require_results)
 _alloc_command_line_arguments :: proc() -> []string {
 _alloc_command_line_arguments :: proc() -> []string {
 	res := make([]string, len(runtime.args__))
 	res := make([]string, len(runtime.args__))
 	for arg, i in runtime.args__ {
 	for arg, i in runtime.args__ {
@@ -893,3 +922,8 @@ _alloc_command_line_arguments :: proc() -> []string {
 	}
 	}
 	return res
 	return res
 }
 }
+
+@(private, fini)
+_delete_command_line_arguments :: proc() {
+	delete(args)
+}

+ 30 - 1
core/os/os_wasi.odin

@@ -27,7 +27,7 @@ stderr: Handle = 2
 
 
 args := _alloc_command_line_arguments()
 args := _alloc_command_line_arguments()
 
 
-@(require_results)
+@(private, require_results)
 _alloc_command_line_arguments :: proc() -> (args: []string) {
 _alloc_command_line_arguments :: proc() -> (args: []string) {
 	args = make([]string, len(runtime.args__))
 	args = make([]string, len(runtime.args__))
 	for &arg, i in args {
 	for &arg, i in args {
@@ -36,6 +36,11 @@ _alloc_command_line_arguments :: proc() -> (args: []string) {
 	return
 	return
 }
 }
 
 
+@(private, fini)
+_delete_command_line_arguments :: proc() {
+	delete(args)
+}
+
 // WASI works with "preopened" directories, the environment retrieves directories
 // WASI works with "preopened" directories, the environment retrieves directories
 // (for example with `wasmtime --dir=. module.wasm`) and those given directories
 // (for example with `wasmtime --dir=. module.wasm`) and those given directories
 // are the only ones accessible by the application.
 // are the only ones accessible by the application.
@@ -239,3 +244,27 @@ exit :: proc "contextless" (code: int) -> ! {
 	runtime._cleanup_runtime_contextless()
 	runtime._cleanup_runtime_contextless()
 	wasi.proc_exit(wasi.exitcode_t(code))
 	wasi.proc_exit(wasi.exitcode_t(code))
 }
 }
+
+@(require_results)
+lookup_env_alloc :: proc(key: string, allocator := context.allocator) -> (value: string, found: bool) {
+	return "", false
+}
+
+@(require_results)
+lookup_env_buffer :: proc(buf: []u8, key: string) -> (value: string, err: Error) {
+	return "", .Env_Var_Not_Found
+}
+lookup_env :: proc{lookup_env_alloc, lookup_env_buffer}
+
+@(require_results)
+get_env_alloc :: proc(key: string, allocator := context.allocator) -> (value: string) {
+	value, _ = lookup_env(key, allocator)
+	return
+}
+
+@(require_results)
+get_env_buf :: proc(buf: []u8, key: string) -> (value: string) {
+	value, _ = lookup_env(buf, key)
+	return
+}
+get_env :: proc{get_env_alloc, get_env_buf}

+ 9 - 1
core/os/os_windows.odin

@@ -193,7 +193,7 @@ current_thread_id :: proc "contextless" () -> int {
 
 
 
 
 
 
-@(require_results)
+@(private, require_results)
 _alloc_command_line_arguments :: proc() -> []string {
 _alloc_command_line_arguments :: proc() -> []string {
 	arg_count: i32
 	arg_count: i32
 	arg_list_ptr := win32.CommandLineToArgvW(win32.GetCommandLineW(), &arg_count)
 	arg_list_ptr := win32.CommandLineToArgvW(win32.GetCommandLineW(), &arg_count)
@@ -215,6 +215,14 @@ _alloc_command_line_arguments :: proc() -> []string {
 	return arg_list
 	return arg_list
 }
 }
 
 
+@(private, fini)
+_delete_command_line_arguments :: proc() {
+	for s in args {
+		delete(s)
+	}
+	delete(args)
+}
+
 /*
 /*
 	Windows 11 (preview) has the same major and minor version numbers
 	Windows 11 (preview) has the same major and minor version numbers
 	as Windows 10: 10 and 0 respectively.
 	as Windows 10: 10 and 0 respectively.

+ 19 - 0
core/slice/slice.odin

@@ -387,6 +387,25 @@ has_prefix :: proc(array: $T/[]$E, needle: T) -> bool where intrinsics.type_is_c
 	return false
 	return false
 }
 }
 
 
+/*
+	return the suffix length common between slices `a` and `b`.
+
+	slice.suffix_length([]u8{1, 2, 3, 4}, []u8{1, 2, 3, 4}) -> 4
+	slice.suffix_length([]u8{1, 2, 3, 4}, []u8{3, 4}) -> 2
+	slice.suffix_length([]u8{1, 2, 3, 4}, []u8{1}) -> 0
+	slice.suffix_length([]u8{1, 2, 3, 4}, []u8{1, 3, 5}) -> 0
+	slice.suffix_length([]u8{3, 4, 5}, []u8{3, 5}) -> 1
+*/
+@(require_results)
+suffix_length :: proc(a, b: $T/[]$E) -> (n: int) where intrinsics.type_is_comparable(E) {
+	len_a, len_b := len(a), len(b)
+	_len := builtin.min(len_a, len_b)
+
+	#no_bounds_check for i := 1; i <= _len && a[len_a - i] == b[len_b - i]; i += 1 {
+		n += 1
+	}
+	return
+}
 
 
 @(require_results)
 @(require_results)
 has_suffix :: proc(array: $T/[]$E, needle: T) -> bool where intrinsics.type_is_comparable(E) {
 has_suffix :: proc(array: $T/[]$E, needle: T) -> bool where intrinsics.type_is_comparable(E) {

+ 107 - 48
core/sync/chan/chan.odin

@@ -7,6 +7,14 @@ import "core:mem"
 import "core:sync"
 import "core:sync"
 import "core:math/rand"
 import "core:math/rand"
 
 
+when ODIN_TEST {
+/*
+Hook for testing _try_select_raw allowing the test harness to manipulate the
+channels prior to the select actually operating on them.
+*/
+__try_select_raw_pause : proc() = nil
+}
+
 /*
 /*
 Determines what operations `Chan` supports.
 Determines what operations `Chan` supports.
 */
 */
@@ -75,6 +83,8 @@ Raw_Chan :: struct {
 	r_waiting:       int,  // guarded by `mutex`
 	r_waiting:       int,  // guarded by `mutex`
 	w_waiting:       int,  // guarded by `mutex`
 	w_waiting:       int,  // guarded by `mutex`
 
 
+	did_read: bool, // lets a sender know if the value was read
+
 	// Buffered
 	// Buffered
 	queue: ^Raw_Queue,
 	queue: ^Raw_Queue,
 
 
@@ -412,8 +422,8 @@ as_recv :: #force_inline proc "contextless" (c: $C/Chan($T, $D)) -> (r: Chan(T,
 Sends the specified message, blocking the current thread if:
 Sends the specified message, blocking the current thread if:
 - the channel is unbuffered
 - the channel is unbuffered
 - the channel's buffer is full
 - the channel's buffer is full
-until the channel is being read from. `send` will return
-`false` when attempting to send on an already closed channel.
+until the channel is being read from or the channel is closed. `send` will
+return `false` when attempting to send on an already closed channel.
 
 
 **Inputs**
 **Inputs**
 - `c`: The channel
 - `c`: The channel
@@ -484,8 +494,9 @@ try_send :: proc "contextless" (c: $C/Chan($T, $D), data: T) -> (ok: bool) where
 Reads a message from the channel, blocking the current thread if:
 Reads a message from the channel, blocking the current thread if:
 - the channel is unbuffered
 - the channel is unbuffered
 - the channel's buffer is empty
 - the channel's buffer is empty
-until the channel is being written to. `recv` will return
-`false` when attempting to receive a message on an already closed channel.
+until the channel is being written to or the channel is closed. `recv` will
+return `false` when attempting to receive a message on an already closed
+channel.
 
 
 **Inputs**
 **Inputs**
 - `c`: The channel
 - `c`: The channel
@@ -558,8 +569,8 @@ try_recv :: proc "contextless" (c: $C/Chan($T)) -> (data: T, ok: bool) where C.D
 Sends the specified message, blocking the current thread if:
 Sends the specified message, blocking the current thread if:
 - the channel is unbuffered
 - the channel is unbuffered
 - the channel's buffer is full
 - the channel's buffer is full
-until the channel is being read from. `send_raw` will return
-`false` when attempting to send on an already closed channel.
+until the channel is being read from or the channel is closed. `send_raw` will
+return `false` when attempting to send on an already closed channel.
 
 
 Note: The message referenced by `msg_out` must match the size
 Note: The message referenced by `msg_out` must match the size
 and alignment used when the `Raw_Chan` was created.
 and alignment used when the `Raw_Chan` was created.
@@ -619,12 +630,23 @@ send_raw :: proc "contextless" (c: ^Raw_Chan, msg_in: rawptr) -> (ok: bool) {
 			return false
 			return false
 		}
 		}
 
 
+		c.did_read = false
+		defer c.did_read = false
+
 		mem.copy(c.unbuffered_data, msg_in, int(c.msg_size))
 		mem.copy(c.unbuffered_data, msg_in, int(c.msg_size))
+
 		c.w_waiting += 1
 		c.w_waiting += 1
+
 		if c.r_waiting > 0 {
 		if c.r_waiting > 0 {
 			sync.signal(&c.r_cond)
 			sync.signal(&c.r_cond)
 		}
 		}
+
 		sync.wait(&c.w_cond, &c.mutex)
 		sync.wait(&c.w_cond, &c.mutex)
+
+		if c.closed && !c.did_read {
+			return false
+		}
+
 		ok = true
 		ok = true
 	}
 	}
 	return
 	return
@@ -634,8 +656,9 @@ send_raw :: proc "contextless" (c: ^Raw_Chan, msg_in: rawptr) -> (ok: bool) {
 Reads a message from the channel, blocking the current thread if:
 Reads a message from the channel, blocking the current thread if:
 - the channel is unbuffered
 - the channel is unbuffered
 - the channel's buffer is empty
 - the channel's buffer is empty
-until the channel is being written to. `recv_raw` will return
-`false` when attempting to receive a message on an already closed channel.
+until the channel is being written to or the channel is closed. `recv_raw`
+will return `false` when attempting to receive a message on an already closed
+channel.
 
 
 Note: The location pointed to by `msg_out` must match the size
 Note: The location pointed to by `msg_out` must match the size
 and alignment used when the `Raw_Chan` was created.
 and alignment used when the `Raw_Chan` was created.
@@ -698,8 +721,7 @@ recv_raw :: proc "contextless" (c: ^Raw_Chan, msg_out: rawptr) -> (ok: bool) {
 	} else if c.unbuffered_data != nil { // unbuffered
 	} else if c.unbuffered_data != nil { // unbuffered
 		sync.guard(&c.mutex)
 		sync.guard(&c.mutex)
 
 
-		for !c.closed &&
-			c.w_waiting == 0 {
+		for !c.closed && c.w_waiting == 0 {
 			c.r_waiting += 1
 			c.r_waiting += 1
 			sync.wait(&c.r_cond, &c.mutex)
 			sync.wait(&c.r_cond, &c.mutex)
 			c.r_waiting -= 1
 			c.r_waiting -= 1
@@ -712,6 +734,7 @@ recv_raw :: proc "contextless" (c: ^Raw_Chan, msg_out: rawptr) -> (ok: bool) {
 		mem.copy(msg_out, c.unbuffered_data, int(c.msg_size))
 		mem.copy(msg_out, c.unbuffered_data, int(c.msg_size))
 		c.w_waiting -= 1
 		c.w_waiting -= 1
 
 
+		c.did_read = true
 		sync.signal(&c.w_cond)
 		sync.signal(&c.w_cond)
 		ok = true
 		ok = true
 	}
 	}
@@ -771,7 +794,7 @@ try_send_raw :: proc "contextless" (c: ^Raw_Chan, msg_in: rawptr) -> (ok: bool)
 	} else if c.unbuffered_data != nil { // unbuffered
 	} else if c.unbuffered_data != nil { // unbuffered
 		sync.guard(&c.mutex)
 		sync.guard(&c.mutex)
 
 
-		if c.closed {
+		if c.closed || c.r_waiting - c.w_waiting <= 0 {
 			return false
 			return false
 		}
 		}
 
 
@@ -835,7 +858,7 @@ try_recv_raw :: proc "contextless" (c: ^Raw_Chan, msg_out: rawptr) -> bool {
 	} else if c.unbuffered_data != nil { // unbuffered
 	} else if c.unbuffered_data != nil { // unbuffered
 		sync.guard(&c.mutex)
 		sync.guard(&c.mutex)
 
 
-		if c.closed || c.w_waiting == 0 {
+		if c.closed || c.w_waiting - c.r_waiting <= 0 {
 			return false
 			return false
 		}
 		}
 
 
@@ -1038,8 +1061,9 @@ is_closed :: proc "contextless" (c: ^Raw_Chan) -> bool {
 }
 }
 
 
 /*
 /*
-Returns whether a message is ready to be read, i.e.,
-if a call to `recv` or `recv_raw` would block
+Returns whether a message can be read without blocking the current
+thread. Specifically, it checks if the channel is buffered and not full,
+or if there is already a writer attempting to send a message.
 
 
 **Inputs**
 **Inputs**
 - `c`: The channel
 - `c`: The channel
@@ -1067,7 +1091,7 @@ can_recv :: proc "contextless" (c: ^Raw_Chan) -> bool {
 	if is_buffered(c) {
 	if is_buffered(c) {
 		return c.queue.len > 0
 		return c.queue.len > 0
 	}
 	}
-	return c.w_waiting > 0
+	return c.w_waiting - c.r_waiting > 0
 }
 }
 
 
 
 
@@ -1080,7 +1104,7 @@ or if there is already a reader waiting for a message.
 - `c`: The channel
 - `c`: The channel
 
 
 **Returns**
 **Returns**
-- `true` if a message can be send, `false` otherwise
+- `true` if a message can be sent, `false` otherwise
 
 
 Example:
 Example:
 
 
@@ -1102,18 +1126,30 @@ can_send :: proc "contextless" (c: ^Raw_Chan) -> bool {
 	if is_buffered(c) {
 	if is_buffered(c) {
 		return c.queue.len < c.queue.cap
 		return c.queue.len < c.queue.cap
 	}
 	}
-	return c.w_waiting == 0
+	return c.r_waiting - c.w_waiting > 0
+}
+
+/*
+Specifies the direction of the selected channel.
+*/
+Select_Status :: enum {
+	None,
+	Recv,
+	Send,
 }
 }
 
 
 
 
 /*
 /*
-Attempts to either send or receive messages on the specified channels.
+Attempts to either send or receive messages on the specified channels without blocking.
 
 
-`select_raw` first identifies which channels have messages ready to be received
+`try_select_raw` first identifies which channels have messages ready to be received
 and which are available for sending. It then randomly selects one operation
 and which are available for sending. It then randomly selects one operation
 (either a send or receive) to perform.
 (either a send or receive) to perform.
 
 
+If no channels have messages ready, the procedure is a noop.
+
 Note: Each message in `send_msgs` corresponds to the send channel at the same index in `sends`.
 Note: Each message in `send_msgs` corresponds to the send channel at the same index in `sends`.
+If the message is nil, corresponding send channel will be skipped.
 
 
 **Inputs**
 **Inputs**
 - `recv`: A slice of channels to read from
 - `recv`: A slice of channels to read from
@@ -1145,18 +1181,18 @@ Example:
 		// where the value from the read should be stored
 		// where the value from the read should be stored
 		received_value: int
 		received_value: int
 
 
-		idx, ok := chan.select_raw(receive_chans[:], send_chans[:], msgs[:], &received_value)
+		idx, ok := chan.try_select_raw(receive_chans[:], send_chans[:], msgs[:], &received_value)
 		fmt.println("SELECT:        ", idx, ok)
 		fmt.println("SELECT:        ", idx, ok)
 		fmt.println("RECEIVED VALUE ", received_value)
 		fmt.println("RECEIVED VALUE ", received_value)
 
 
-		idx, ok = chan.select_raw(receive_chans[:], send_chans[:], msgs[:], &received_value)
+		idx, ok = chan.try_select_raw(receive_chans[:], send_chans[:], msgs[:], &received_value)
 		fmt.println("SELECT:        ", idx, ok)
 		fmt.println("SELECT:        ", idx, ok)
 		fmt.println("RECEIVED VALUE ", received_value)
 		fmt.println("RECEIVED VALUE ", received_value)
 
 
 		// closing of a channel also affects the select operation
 		// closing of a channel also affects the select operation
 		chan.close(c)
 		chan.close(c)
 
 
-		idx, ok = chan.select_raw(receive_chans[:], send_chans[:], msgs[:], &received_value)
+		idx, ok = chan.try_select_raw(receive_chans[:], send_chans[:], msgs[:], &received_value)
 		fmt.println("SELECT:        ", idx, ok)
 		fmt.println("SELECT:        ", idx, ok)
 	}
 	}
 
 
@@ -1170,7 +1206,7 @@ Output:
 
 
 */
 */
 @(require_results)
 @(require_results)
-select_raw :: proc "odin" (recvs: []^Raw_Chan, sends: []^Raw_Chan, send_msgs: []rawptr, recv_out: rawptr) -> (select_idx: int, ok: bool) #no_bounds_check {
+try_select_raw :: proc "odin" (recvs: []^Raw_Chan, sends: []^Raw_Chan, send_msgs: []rawptr, recv_out: rawptr) -> (select_idx: int, status: Select_Status) #no_bounds_check {
 	Select_Op :: struct {
 	Select_Op :: struct {
 		idx:     int, // local to the slice that was given
 		idx:     int, // local to the slice that was given
 		is_recv: bool,
 		is_recv: bool,
@@ -1178,43 +1214,66 @@ select_raw :: proc "odin" (recvs: []^Raw_Chan, sends: []^Raw_Chan, send_msgs: []
 
 
 	candidate_count := builtin.len(recvs)+builtin.len(sends)
 	candidate_count := builtin.len(recvs)+builtin.len(sends)
 	candidates := ([^]Select_Op)(intrinsics.alloca(candidate_count*size_of(Select_Op), align_of(Select_Op)))
 	candidates := ([^]Select_Op)(intrinsics.alloca(candidate_count*size_of(Select_Op), align_of(Select_Op)))
-	count := 0
 
 
-	for c, i in recvs {
-		if can_recv(c) {
-			candidates[count] = {
-				is_recv = true,
-				idx     = i,
+	try_loop: for {
+		count := 0
+
+		for c, i in recvs {
+			if can_recv(c) {
+				candidates[count] = {
+					is_recv = true,
+					idx     = i,
+				}
+				count += 1
 			}
 			}
-			count += 1
 		}
 		}
-	}
 
 
-	for c, i in sends {
-		if can_send(c) {
-			candidates[count] = {
-				is_recv = false,
-				idx     = i,
+		for c, i in sends {
+			if i > builtin.len(send_msgs)-1 || send_msgs[i] == nil {
+				continue
+			}
+			if can_send(c)  {
+				candidates[count] = {
+					is_recv = false,
+					idx     = i,
+				}
+				count += 1
 			}
 			}
-			count += 1
 		}
 		}
-	}
 
 
-	if count == 0 {
-		return
-	}
+		if count == 0 {
+			return -1, .None
+		}
 
 
-	select_idx = rand.int_max(count) if count > 0 else 0
+		when ODIN_TEST {
+			if __try_select_raw_pause != nil {
+				__try_select_raw_pause()
+			}
+		}
+
+		candidate_idx := rand.int_max(count) if count > 0 else 0
 
 
-	sel := candidates[select_idx]
-	if sel.is_recv {
-		ok = recv_raw(recvs[sel.idx], recv_out)
-	} else {
-		ok = send_raw(sends[sel.idx], send_msgs[sel.idx])
+		sel := candidates[candidate_idx]
+		if sel.is_recv {
+			status = .Recv
+			if !try_recv_raw(recvs[sel.idx], recv_out) {
+				continue try_loop
+			}
+		} else {
+			status = .Send
+			if !try_send_raw(sends[sel.idx], send_msgs[sel.idx]) {
+				continue try_loop
+			}
+		}
+
+		return sel.idx, status
 	}
 	}
-	return
 }
 }
 
 
+@(require_results, deprecated = "use try_select_raw")
+select_raw :: proc "odin" (recvs: []^Raw_Chan, sends: []^Raw_Chan, send_msgs: []rawptr, recv_out: rawptr) -> (select_idx: int, status: Select_Status) #no_bounds_check {
+	return try_select_raw(recvs, sends, send_msgs, recv_out)
+}
 
 
 /*
 /*
 `Raw_Queue` is a non-thread-safe queue implementation designed to store messages
 `Raw_Queue` is a non-thread-safe queue implementation designed to store messages

+ 9 - 0
core/sys/darwin/xnu_system_call_wrappers.odin

@@ -223,6 +223,11 @@ _Proc_Bsdinfo :: struct {
 
 
 /*--==========================================================================--*/
 /*--==========================================================================--*/
 
 
+/* Get window size */
+TIOCGWINSZ :: 0x40087468
+
+/*--==========================================================================--*/
+
 syscall_fsync :: #force_inline proc "contextless" (fildes: c.int) -> bool {
 syscall_fsync :: #force_inline proc "contextless" (fildes: c.int) -> bool {
 	return !(cast(bool)intrinsics.syscall(unix_offset_syscall(.fsync), uintptr(fildes)))
 	return !(cast(bool)intrinsics.syscall(unix_offset_syscall(.fsync), uintptr(fildes)))
 }
 }
@@ -275,6 +280,10 @@ syscall_lseek :: #force_inline proc "contextless" (fd: c.int, offset: i64, whenc
 	return cast(i64)intrinsics.syscall(unix_offset_syscall(.lseek), uintptr(fd), uintptr(offset), uintptr(whence))
 	return cast(i64)intrinsics.syscall(unix_offset_syscall(.lseek), uintptr(fd), uintptr(offset), uintptr(whence))
 }
 }
 
 
+syscall_ioctl :: #force_inline proc "contextless" (fd: c.int, request: u32, arg: rawptr) -> c.int {
+	return (cast(c.int)intrinsics.syscall(unix_offset_syscall(.ioctl), uintptr(fd), uintptr(request), uintptr(arg)))
+}
+
 syscall_gettid :: #force_inline proc "contextless" () -> u64 {
 syscall_gettid :: #force_inline proc "contextless" () -> u64 {
 	return cast(u64)intrinsics.syscall(unix_offset_syscall(.gettid))
 	return cast(u64)intrinsics.syscall(unix_offset_syscall(.gettid))
 }
 }

File diff suppressed because it is too large
+ 558 - 558
core/sys/es/api.odin


+ 5 - 0
core/sys/freebsd/constants.odin

@@ -0,0 +1,5 @@
+package sys_freebsd
+
+/* Get window size */
+TIOCGWINSZ :: 0x40087468
+

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

@@ -34,6 +34,7 @@ init_platform :: proc() {
 	} else {
 	} else {
 		os_version.platform = .MacOS
 		os_version.platform = .MacOS
 		switch version.majorVersion {
 		switch version.majorVersion {
+		case 26: ws(&b, "macOS Tahoe")
 		case 15: ws(&b, "macOS Sequoia")
 		case 15: ws(&b, "macOS Sequoia")
 		case 14: ws(&b, "macOS Sonoma")
 		case 14: ws(&b, "macOS Sonoma")
 		case 13: ws(&b, "macOS Ventura")
 		case 13: ws(&b, "macOS Ventura")

+ 18 - 15
core/sys/linux/bits.odin

@@ -1618,36 +1618,39 @@ PER_HPUX        :: 0x0010
 PER_MASK        :: 0x00ff
 PER_MASK        :: 0x00ff
 
 
 /*
 /*
-	Bits for access modes for shared memory
+	Bits for SystemV IPC flags.
+
+	In this enum, access modes are common for any shared memory. Prefixed
+	entries (i.e. `IPC_` or `SHM_`) denote flags, where `IPC_` are common flags
+	for all SystemV IPC primitives, and `SHM_`, `SEM_` and `MSG_` are specific
+	to shared memory segments, semaphores and message queues respectively.
+	
+	These bits overlap, because they are meant to be used within the
+	context of specific procedures. Creation flags, used for `*get` procedures,
+	and usage flags used by all other IPC procedures. Do not mix creation and
+	usage flags, as well as flags prefixed differently (excluding `IPC_`
+	prefix).
 */
 */
-IPC_Mode_Bits :: enum {
+IPC_Flags_Bits :: enum {
+	// Access modes for shared memory.
 	WROTH  = 1,
 	WROTH  = 1,
 	RDOTH  = 2,
 	RDOTH  = 2,
 	WRGRP  = 4,
 	WRGRP  = 4,
 	RDGRP  = 5,
 	RDGRP  = 5,
 	WRUSR  = 7,
 	WRUSR  = 7,
 	RDUSR  = 8,
 	RDUSR  = 8,
-	DEST   = 9,
-	LOCKED = 10,
-}
-
-/*
-	Shared memory flags bits
-*/
-IPC_Flags_Bits :: enum {
+	// Creation flags for shared memory.
 	IPC_CREAT     = 9,
 	IPC_CREAT     = 9,
 	IPC_EXCL      = 10,
 	IPC_EXCL      = 10,
-	IPC_NOWAIT    = 11,
-	// Semaphore
-	SEM_UNDO      = 9,
-	// Shared memory
 	SHM_HUGETLB   = 11,
 	SHM_HUGETLB   = 11,
 	SHM_NORESERVE = 12,
 	SHM_NORESERVE = 12,
+	// Usage flags for shared memory.
+	IPC_NOWAIT    = 11,
+	SEM_UNDO      = 9,
 	SHM_RDONLY    = 12,
 	SHM_RDONLY    = 12,
 	SHM_RND       = 13,
 	SHM_RND       = 13,
 	SHM_REMAP     = 14,
 	SHM_REMAP     = 14,
 	SHM_EXEC      = 15,
 	SHM_EXEC      = 15,
-	// Message queue
 	MSG_NOERROR   = 12,
 	MSG_NOERROR   = 12,
 	MSG_EXCEPT    = 13,
 	MSG_EXCEPT    = 13,
 	MSG_COPY      = 14,
 	MSG_COPY      = 14,

+ 4 - 1
core/sys/linux/constants.odin

@@ -391,4 +391,7 @@ MAP_HUGE_256MB      :: transmute(Map_Flags)(u32(28) << MAP_HUGE_SHIFT)
 MAP_HUGE_512MB      :: transmute(Map_Flags)(u32(29) << MAP_HUGE_SHIFT)
 MAP_HUGE_512MB      :: transmute(Map_Flags)(u32(29) << MAP_HUGE_SHIFT)
 MAP_HUGE_1GB        :: transmute(Map_Flags)(u32(30) << MAP_HUGE_SHIFT)
 MAP_HUGE_1GB        :: transmute(Map_Flags)(u32(30) << MAP_HUGE_SHIFT)
 MAP_HUGE_2GB        :: transmute(Map_Flags)(u32(31) << MAP_HUGE_SHIFT)
 MAP_HUGE_2GB        :: transmute(Map_Flags)(u32(31) << MAP_HUGE_SHIFT)
-MAP_HUGE_16GB       :: transmute(Map_Flags)(u32(34) << MAP_HUGE_SHIFT)
+MAP_HUGE_16GB       :: transmute(Map_Flags)(u32(34) << MAP_HUGE_SHIFT)
+
+/* Get window size */
+TIOCGWINSZ :: 0x5413

+ 3 - 8
core/sys/linux/types.odin

@@ -937,17 +937,12 @@ IO_Vec :: struct {
 }
 }
 
 
 /*
 /*
-	Access mode for shared memory
-*/
-IPC_Mode :: bit_set[IPC_Mode_Bits; u32]
-
-/*
-	Flags used by IPC objects
+	Access modes and flags used by SystemV IPC procedures.
 */
 */
 IPC_Flags :: bit_set[IPC_Flags_Bits; i16]
 IPC_Flags :: bit_set[IPC_Flags_Bits; i16]
 
 
 /*
 /*
-	Permissions for IPC objects
+	Permissions for SystemV IPC primitives.
 */
 */
 IPC_Perm :: struct {
 IPC_Perm :: struct {
 	key:  Key,
 	key:  Key,
@@ -955,7 +950,7 @@ IPC_Perm :: struct {
 	gid:  u32,
 	gid:  u32,
 	cuid: u32,
 	cuid: u32,
 	cgid: u32,
 	cgid: u32,
-	mode: IPC_Mode,
+	mode: IPC_Flags, // Only contains mode flags.
 	seq:  u16,
 	seq:  u16,
 	_:    [2 + 2*size_of(int)]u8,
 	_:    [2 + 2*size_of(int)]u8,
 }
 }

+ 4 - 1
core/sys/posix/dlfcn.odin

@@ -8,7 +8,10 @@ when ODIN_OS == .Darwin {
 } else when ODIN_OS == .FreeBSD || ODIN_OS == .NetBSD {
 } else when ODIN_OS == .FreeBSD || ODIN_OS == .NetBSD {
 	foreign import lib "system:dl"
 	foreign import lib "system:dl"
 } else {
 } else {
-	foreign import lib "system:c"
+	foreign import lib {
+		"system:c",
+		"system:dl",
+	}
 }
 }
 
 
 // dlfcn.h - dynamic linking
 // dlfcn.h - dynamic linking

+ 3 - 1
core/sys/posix/posix.odin

@@ -31,7 +31,7 @@ Unimplemented headers:
 - iso646.h | Impossible
 - iso646.h | Impossible
 - math.h | See `core:c/libc`
 - math.h | See `core:c/libc`
 - mqueue.h | Targets don't seem to have implemented it
 - mqueue.h | Targets don't seem to have implemented it
-- regex.h | See `core:regex`
+- regex.h | See `core:text/regex`
 - search.h | Not useful in Odin
 - search.h | Not useful in Odin
 - spawn.h | Use `fork`, `execve`, etc.
 - spawn.h | Use `fork`, `execve`, etc.
 - stdarg.h | See `core:c/libc`
 - stdarg.h | See `core:c/libc`
@@ -53,6 +53,8 @@ import "base:intrinsics"
 
 
 import "core:c"
 import "core:c"
 
 
+IS_SUPPORTED :: _IS_SUPPORTED
+
 result :: enum c.int {
 result :: enum c.int {
  	// Use `errno` and `strerror` for more information.
  	// Use `errno` and `strerror` for more information.
 	FAIL = -1,
 	FAIL = -1,

+ 10 - 0
core/sys/posix/posix_other.odin

@@ -0,0 +1,10 @@
+#+build !linux
+#+build !darwin
+#+build !netbsd
+#+build !openbsd
+#+build !freebsd
+#+build !haiku
+package posix
+
+_IS_SUPPORTED :: false
+

+ 5 - 0
core/sys/posix/posix_unix.odin

@@ -0,0 +1,5 @@
+#+build linux, darwin, netbsd, openbsd, freebsd, haiku
+package posix
+
+_IS_SUPPORTED :: true
+

+ 1 - 1
core/sys/windows/gdi32.odin

@@ -350,4 +350,4 @@ NEWTEXTMETRICW :: struct {
 	ntmAvgWidth:        UINT,
 	ntmAvgWidth:        UINT,
 }
 }
 
 
-FONTENUMPROCW :: #type proc(lpelf: ^ENUMLOGFONTW, lpntm: ^NEWTEXTMETRICW, FontType: DWORD, lParam: LPARAM) -> INT
+FONTENUMPROCW :: #type proc "system" (lpelf: ^ENUMLOGFONTW, lpntm: ^NEWTEXTMETRICW, FontType: DWORD, lParam: LPARAM) -> INT

+ 1 - 0
core/sys/windows/kernel32.odin

@@ -168,6 +168,7 @@ foreign kernel32 {
 	ResumeThread :: proc(thread: HANDLE) -> DWORD ---
 	ResumeThread :: proc(thread: HANDLE) -> DWORD ---
 	GetThreadPriority :: proc(thread: HANDLE) -> c_int ---
 	GetThreadPriority :: proc(thread: HANDLE) -> c_int ---
 	SetThreadPriority :: proc(thread: HANDLE, priority: c_int) -> BOOL ---
 	SetThreadPriority :: proc(thread: HANDLE, priority: c_int) -> BOOL ---
+	GetThreadDescription :: proc(hThread: HANDLE, ppszThreadDescription: ^PCWSTR) -> HRESULT ---
 	SetThreadDescription :: proc(hThread: HANDLE, lpThreadDescription: PCWSTR) -> HRESULT ---
 	SetThreadDescription :: proc(hThread: HANDLE, lpThreadDescription: PCWSTR) -> HRESULT ---
 	GetExitCodeThread :: proc(thread: HANDLE, exit_code: ^DWORD) -> BOOL ---
 	GetExitCodeThread :: proc(thread: HANDLE, exit_code: ^DWORD) -> BOOL ---
 	TerminateThread :: proc(thread: HANDLE, exit_code: DWORD) -> BOOL ---
 	TerminateThread :: proc(thread: HANDLE, exit_code: DWORD) -> BOOL ---

+ 68 - 4
core/sys/windows/util.odin

@@ -75,7 +75,7 @@ LANGIDFROMLCID :: #force_inline proc "contextless" (lcid: LCID) -> LANGID {
 	return LANGID(lcid)
 	return LANGID(lcid)
 }
 }
 
 
-utf8_to_utf16 :: proc(s: string, allocator := context.temp_allocator) -> []u16 {
+utf8_to_utf16_alloc :: proc(s: string, allocator := context.temp_allocator) -> []u16 {
 	if len(s) < 1 {
 	if len(s) < 1 {
 		return nil
 		return nil
 	}
 	}
@@ -101,14 +101,42 @@ utf8_to_utf16 :: proc(s: string, allocator := context.temp_allocator) -> []u16 {
 	}
 	}
 	return text[:n]
 	return text[:n]
 }
 }
-utf8_to_wstring :: proc(s: string, allocator := context.temp_allocator) -> wstring {
+
+utf8_to_utf16_buf :: proc(buf: []u16, s: string) -> []u16 {
+	n1 := MultiByteToWideChar(CP_UTF8, MB_ERR_INVALID_CHARS, raw_data(s), i32(len(s)), nil, 0)
+	if n1 == 0 {
+		return nil
+	} else if int(n1) > len(buf) {
+		return nil
+	}
+
+	n1 = MultiByteToWideChar(CP_UTF8, MB_ERR_INVALID_CHARS, raw_data(s), i32(len(s)), raw_data(buf[:]), n1)
+	if n1 == 0 {
+		return nil
+	} else if int(n1) > len(buf) {
+		return nil
+	}
+	return buf[:n1]
+}
+utf8_to_utf16 :: proc{utf8_to_utf16_alloc, utf8_to_utf16_buf}
+
+utf8_to_wstring_alloc :: proc(s: string, allocator := context.temp_allocator) -> wstring {
 	if res := utf8_to_utf16(s, allocator); len(res) > 0 {
 	if res := utf8_to_utf16(s, allocator); len(res) > 0 {
 		return raw_data(res)
 		return raw_data(res)
 	}
 	}
 	return nil
 	return nil
 }
 }
 
 
-wstring_to_utf8 :: proc(s: wstring, N: int, allocator := context.temp_allocator) -> (res: string, err: runtime.Allocator_Error) {
+utf8_to_wstring_buf :: proc(buf: []u16, s: string) -> wstring {
+	if res := utf8_to_utf16(buf, s); len(res) > 0 {
+		return raw_data(res)
+	}
+	return nil
+}
+
+utf8_to_wstring :: proc{utf8_to_wstring_alloc, utf8_to_wstring_buf}
+
+wstring_to_utf8_alloc :: proc(s: wstring, N: int, allocator := context.temp_allocator) -> (res: string, err: runtime.Allocator_Error) {
 	context.allocator = allocator
 	context.allocator = allocator
 
 
 	if N == 0 {
 	if N == 0 {
@@ -142,13 +170,49 @@ wstring_to_utf8 :: proc(s: wstring, N: int, allocator := context.temp_allocator)
 	return string(text[:n]), nil
 	return string(text[:n]), nil
 }
 }
 
 
-utf16_to_utf8 :: proc(s: []u16, allocator := context.temp_allocator) -> (res: string, err: runtime.Allocator_Error) {
+wstring_to_utf8_buf :: proc(buf: []u8, s: wstring) -> (res: string) {
+	n := WideCharToMultiByte(CP_UTF8, WC_ERR_INVALID_CHARS, s, -1, nil, 0, nil, nil)
+	if n == 0 {
+		return
+	} else if int(n) > len(buf) {
+		return
+	}
+
+	n2 := WideCharToMultiByte(CP_UTF8, WC_ERR_INVALID_CHARS, s, -1, raw_data(buf), n, nil, nil)
+	if n2 == 0 {
+		return
+	} else if int(n2) > len(buf) {
+		return
+	}
+
+	for i in 0..<n2 {
+		if buf[i] == 0 {
+			n2 = i
+			break
+		}
+	}
+	return string(buf[:n2])
+}
+
+wstring_to_utf8 :: proc{wstring_to_utf8_alloc, wstring_to_utf8_buf}
+
+utf16_to_utf8_alloc :: proc(s: []u16, allocator := context.temp_allocator) -> (res: string, err: runtime.Allocator_Error) {
 	if len(s) == 0 {
 	if len(s) == 0 {
 		return "", nil
 		return "", nil
 	}
 	}
 	return wstring_to_utf8(raw_data(s), len(s), allocator)
 	return wstring_to_utf8(raw_data(s), len(s), allocator)
 }
 }
 
 
+utf16_to_utf8_buf :: proc(buf: []u8, s: []u16) -> (res: string) {
+	if len(s) == 0 {
+		return
+	}
+	return wstring_to_utf8(buf, raw_data(s))
+}
+
+utf16_to_utf8 :: proc{utf16_to_utf8_alloc, utf16_to_utf8_buf}
+
+
 // AdvAPI32, NetAPI32 and UserENV helpers.
 // AdvAPI32, NetAPI32 and UserENV helpers.
 
 
 allowed_username :: proc(username: string) -> bool {
 allowed_username :: proc(username: string) -> bool {

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

@@ -589,7 +589,7 @@ WAVE_FORMAT_FLAC                       :: 0xF1AC /* flac.sourceforge.net */
 WAVE_FORMAT_EXTENSIBLE                 :: 0xFFFE /* Microsoft */
 WAVE_FORMAT_EXTENSIBLE                 :: 0xFFFE /* Microsoft */
 
 
 
 
-WAVEFORMATEX :: struct {
+WAVEFORMATEX :: struct #packed {
 	wFormatTag:      WORD,
 	wFormatTag:      WORD,
 	nChannels:       WORD,
 	nChannels:       WORD,
 	nSamplesPerSec:  DWORD,
 	nSamplesPerSec:  DWORD,
@@ -603,7 +603,7 @@ LPCWAVEFORMATEX :: ^WAVEFORMATEX
 //  New wave format development should be based on the WAVEFORMATEXTENSIBLE structure.
 //  New wave format development should be based on the WAVEFORMATEXTENSIBLE structure.
 //  WAVEFORMATEXTENSIBLE allows you to avoid having to register a new format tag with Microsoft.
 //  WAVEFORMATEXTENSIBLE allows you to avoid having to register a new format tag with Microsoft.
 //  Simply define a new GUID value for the WAVEFORMATEXTENSIBLE.SubFormat field and use WAVE_FORMAT_EXTENSIBLE in the WAVEFORMATEXTENSIBLE.Format.wFormatTag field.
 //  Simply define a new GUID value for the WAVEFORMATEXTENSIBLE.SubFormat field and use WAVE_FORMAT_EXTENSIBLE in the WAVEFORMATEXTENSIBLE.Format.wFormatTag field.
-WAVEFORMATEXTENSIBLE :: struct {
+WAVEFORMATEXTENSIBLE :: struct #packed {
 	using Format: WAVEFORMATEX,
 	using Format: WAVEFORMATEX,
 	Samples: struct #raw_union {
 	Samples: struct #raw_union {
 		wValidBitsPerSample: WORD,      /* bits of precision  */
 		wValidBitsPerSample: WORD,      /* bits of precision  */

+ 5 - 6
core/terminal/internal.odin

@@ -11,17 +11,17 @@ import "core:strings"
 // - [[ https://invisible-island.net/ncurses/terminfo.src.html ]]
 // - [[ https://invisible-island.net/ncurses/terminfo.src.html ]]
 
 
 get_no_color :: proc() -> bool {
 get_no_color :: proc() -> bool {
-	if no_color, ok := os.lookup_env("NO_COLOR"); ok {
-		defer delete(no_color)
+	buf: [128]u8
+	if no_color, err := os.lookup_env(buf[:], "NO_COLOR"); err == nil {
 		return no_color != ""
 		return no_color != ""
 	}
 	}
 	return false
 	return false
 }
 }
 
 
 get_environment_color :: proc() -> Color_Depth {
 get_environment_color :: proc() -> Color_Depth {
+	buf: [128]u8
 	// `COLORTERM` is non-standard but widespread and unambiguous.
 	// `COLORTERM` is non-standard but widespread and unambiguous.
-	if colorterm, ok := os.lookup_env("COLORTERM"); ok {
-		defer delete(colorterm)
+	if colorterm, err := os.lookup_env(buf[:], "COLORTERM"); err == nil {
 		// These are the only values that are typically advertised that have
 		// These are the only values that are typically advertised that have
 		// anything to do with color depth.
 		// anything to do with color depth.
 		if colorterm == "truecolor" || colorterm == "24bit" {
 		if colorterm == "truecolor" || colorterm == "24bit" {
@@ -29,8 +29,7 @@ get_environment_color :: proc() -> Color_Depth {
 		}
 		}
 	}
 	}
 
 
-	if term, ok := os.lookup_env("TERM"); ok {
-		defer delete(term)
+	if term, err := os.lookup_env(buf[:], "TERM"); err == nil {
 		if strings.contains(term, "-truecolor") {
 		if strings.contains(term, "-truecolor") {
 			return .True_Color
 			return .True_Color
 		}
 		}

+ 12 - 5
core/testing/runner.odin

@@ -57,6 +57,8 @@ SHARED_RANDOM_SEED    : u64    : #config(ODIN_TEST_RANDOM_SEED, 0)
 // Set the lowest log level for this test run.
 // Set the lowest log level for this test run.
 LOG_LEVEL_DEFAULT     : string : "debug" when ODIN_DEBUG else "info"
 LOG_LEVEL_DEFAULT     : string : "debug" when ODIN_DEBUG else "info"
 LOG_LEVEL             : string : #config(ODIN_TEST_LOG_LEVEL, LOG_LEVEL_DEFAULT)
 LOG_LEVEL             : string : #config(ODIN_TEST_LOG_LEVEL, LOG_LEVEL_DEFAULT)
+// Report a message at the info level when a test has changed its state.
+LOG_STATE_CHANGES     : bool   : #config(ODIN_TEST_LOG_STATE_CHANGES, false)
 // Show only the most necessary logging information.
 // Show only the most necessary logging information.
 USING_SHORT_LOGS      : bool   : #config(ODIN_TEST_SHORT_LOGS, false)
 USING_SHORT_LOGS      : bool   : #config(ODIN_TEST_SHORT_LOGS, false)
 // Output a report of the tests to the given path.
 // Output a report of the tests to the given path.
@@ -631,8 +633,8 @@ runner :: proc(internal_tests: []Internal_Test) -> bool {
 						total_done_count += 1
 						total_done_count += 1
 					}
 					}
 
 
-					when ODIN_DEBUG {
-						log.debugf("Test #%i %s.%s changed state to %v.", task_channel.test_index, it.pkg, it.name, event.new_state)
+					when LOG_STATE_CHANGES {
+						log.infof("Test #%i %s.%s changed state to %v.", task_channel.test_index, it.pkg, it.name, event.new_state)
 					}
 					}
 
 
 					pkg.last_change_state = event.new_state
 					pkg.last_change_state = event.new_state
@@ -741,7 +743,8 @@ runner :: proc(internal_tests: []Internal_Test) -> bool {
 
 
 
 
 		if test_index, reason, ok := should_stop_test(); ok {
 		if test_index, reason, ok := should_stop_test(); ok {
-			#no_bounds_check report.all_test_states[test_index] = .Failed
+			passed := reason == .Successful_Stop
+			#no_bounds_check report.all_test_states[test_index] = .Successful if passed else .Failed
 			#no_bounds_check it := internal_tests[test_index]
 			#no_bounds_check it := internal_tests[test_index]
 			#no_bounds_check pkg := report.packages_by_name[it.pkg]
 			#no_bounds_check pkg := report.packages_by_name[it.pkg]
 			pkg.frame_ready = false
 			pkg.frame_ready = false
@@ -762,7 +765,7 @@ runner :: proc(internal_tests: []Internal_Test) -> bool {
 			fmt.assertf(task_data != nil, "A signal (%v) was raised to stop test #%i %s.%s, but its task data is missing.",
 			fmt.assertf(task_data != nil, "A signal (%v) was raised to stop test #%i %s.%s, but its task data is missing.",
 				reason, test_index, it.pkg, it.name)
 				reason, test_index, it.pkg, it.name)
 
 
-			if !task_data.t._fail_now_called {
+			if !passed && !task_data.t._fail_now_called {
 				if test_index not_in failed_test_reason_map {
 				if test_index not_in failed_test_reason_map {
 					// We only write a new error message here if there wasn't one
 					// We only write a new error message here if there wasn't one
 					// already, because the message we can provide based only on
 					// already, because the message we can provide based only on
@@ -780,7 +783,11 @@ runner :: proc(internal_tests: []Internal_Test) -> bool {
 
 
 			end_t(&task_data.t)
 			end_t(&task_data.t)
 
 
-			total_failure_count += 1
+			if passed {
+				total_success_count += 1
+			} else {
+				total_failure_count += 1
+			}
 			total_done_count += 1
 			total_done_count += 1
 		}
 		}
 
 

+ 24 - 1
core/testing/signal_handler.odin

@@ -12,8 +12,26 @@ package testing
 import "base:runtime"
 import "base:runtime"
 import "core:log"
 import "core:log"
 
 
+@(private, thread_local)
+local_test_expected_failures: struct {
+	signal:         i32,
+
+	message_count:  int,
+	messages:       [MAX_EXPECTED_ASSERTIONS_PER_TEST]string,
+
+	location_count: int,
+	locations:      [MAX_EXPECTED_ASSERTIONS_PER_TEST]runtime.Source_Code_Location,
+}
+
+@(private, thread_local)
+local_test_assertion_raised: struct {
+	message: string,
+	location: runtime.Source_Code_Location,
+}
+
 Stop_Reason :: enum {
 Stop_Reason :: enum {
 	Unknown,
 	Unknown,
+	Successful_Stop,
 	Illegal_Instruction,
 	Illegal_Instruction,
 	Arithmetic_Error,
 	Arithmetic_Error,
 	Segmentation_Fault,
 	Segmentation_Fault,
@@ -21,7 +39,12 @@ Stop_Reason :: enum {
 }
 }
 
 
 test_assertion_failure_proc :: proc(prefix, message: string, loc: runtime.Source_Code_Location) -> ! {
 test_assertion_failure_proc :: proc(prefix, message: string, loc: runtime.Source_Code_Location) -> ! {
-	log.fatalf("%s: %s", prefix, message, location = loc)
+	if local_test_expected_failures.message_count + local_test_expected_failures.location_count > 0 {
+		local_test_assertion_raised = { message, loc }
+		log.debugf("%s\n\tmessage: %q\n\tlocation: %w", prefix, message, loc)
+	} else {
+		log.fatalf("%s: %s", prefix, message, location = loc)
+	}
 	runtime.trap()
 	runtime.trap()
 }
 }
 
 

+ 35 - 7
core/testing/signal_handler_libc.odin

@@ -20,7 +20,8 @@ import "core:terminal/ansi"
 
 
 @(private="file") stop_test_gate:   sync.Mutex
 @(private="file") stop_test_gate:   sync.Mutex
 @(private="file") stop_test_index:  libc.sig_atomic_t
 @(private="file") stop_test_index:  libc.sig_atomic_t
-@(private="file") stop_test_reason: libc.sig_atomic_t
+@(private="file") stop_test_signal: libc.sig_atomic_t
+@(private="file") stop_test_passed: libc.sig_atomic_t
 @(private="file") stop_test_alert:  libc.sig_atomic_t
 @(private="file") stop_test_alert:  libc.sig_atomic_t
 
 
 @(private="file", thread_local)
 @(private="file", thread_local)
@@ -99,7 +100,30 @@ This is a dire bug and should be reported to the Odin developers.
 
 
 	if sync.mutex_guard(&stop_test_gate) {
 	if sync.mutex_guard(&stop_test_gate) {
 		intrinsics.atomic_store(&stop_test_index, local_test_index)
 		intrinsics.atomic_store(&stop_test_index, local_test_index)
-		intrinsics.atomic_store(&stop_test_reason, cast(libc.sig_atomic_t)sig)
+		intrinsics.atomic_store(&stop_test_signal, cast(libc.sig_atomic_t)sig)
+		passed: bool
+		check_passing: {
+			if location := local_test_assertion_raised.location; location != {} {
+				for i in 0..<local_test_expected_failures.location_count {
+					if local_test_expected_failures.locations[i] == location {
+						passed = true
+						break check_passing
+					}
+				}
+			}
+			if message := local_test_assertion_raised.message; message != "" {
+				for i in 0..<local_test_expected_failures.message_count {
+					if local_test_expected_failures.messages[i] == message {
+						passed = true
+						break check_passing
+					}
+				}
+			}
+			if signal := local_test_expected_failures.signal; signal == sig {
+				passed = true
+			}
+		}
+		intrinsics.atomic_store(&stop_test_passed, cast(libc.sig_atomic_t)passed)
 		intrinsics.atomic_store(&stop_test_alert, 1)
 		intrinsics.atomic_store(&stop_test_alert, 1)
 
 
 		for {
 		for {
@@ -154,11 +178,15 @@ _should_stop_test :: proc() -> (test_index: int, reason: Stop_Reason, ok: bool)
 		intrinsics.atomic_store(&stop_test_alert, 0)
 		intrinsics.atomic_store(&stop_test_alert, 0)
 
 
 		test_index = cast(int)intrinsics.atomic_load(&stop_test_index)
 		test_index = cast(int)intrinsics.atomic_load(&stop_test_index)
-		switch intrinsics.atomic_load(&stop_test_reason) {
-		case libc.SIGFPE: reason = .Arithmetic_Error
-		case libc.SIGILL: reason = .Illegal_Instruction
-		case libc.SIGSEGV: reason = .Segmentation_Fault
-		case      SIGTRAP: reason = .Unhandled_Trap
+		if cast(bool)intrinsics.atomic_load(&stop_test_passed) {
+			reason = .Successful_Stop
+		} else {
+			switch intrinsics.atomic_load(&stop_test_signal) {
+			case libc.SIGFPE: reason = .Arithmetic_Error
+			case libc.SIGILL: reason = .Illegal_Instruction
+			case libc.SIGSEGV: reason = .Segmentation_Fault
+			case      SIGTRAP: reason = .Unhandled_Trap
+			}
 		}
 		}
 		ok = true
 		ok = true
 	}
 	}

+ 73 - 0
core/testing/testing.odin

@@ -21,6 +21,8 @@ import "core:mem"
 _ :: reflect // alias reflect to nothing to force visibility for -vet
 _ :: reflect // alias reflect to nothing to force visibility for -vet
 _ :: mem     // in case TRACKING_MEMORY is not enabled
 _ :: mem     // in case TRACKING_MEMORY is not enabled
 
 
+MAX_EXPECTED_ASSERTIONS_PER_TEST :: 5
+
 // IMPORTANT NOTE: Compiler requires this layout
 // IMPORTANT NOTE: Compiler requires this layout
 Test_Signature :: proc(^T)
 Test_Signature :: proc(^T)
 
 
@@ -155,3 +157,74 @@ set_fail_timeout :: proc(t: ^T, duration: time.Duration, loc := #caller_location
 		location = loc,
 		location = loc,
 	})
 	})
 }
 }
+
+/*
+Let the test runner know that it should expect an assertion failure from a
+specific location in the source code for this test.
+
+In the event that an assertion fails, a debug message will be logged with its
+exact message and location in a copyable format to make it convenient to write
+tests which use this API.
+
+This procedure may be called up to 5 times with different locations.
+
+This is a limitation for the sake of simplicity in the implementation, and you
+should consider breaking up your tests into smaller procedures if you need to
+check for asserts in more than 2 places.
+*/
+expect_assert_from :: proc(t: ^T, expected_place: runtime.Source_Code_Location, caller_loc := #caller_location) {
+	count := local_test_expected_failures.location_count
+	if count == MAX_EXPECTED_ASSERTIONS_PER_TEST {
+		panic("This test cannot handle that many expected assertions based on matching the location.", caller_loc)
+	}
+	local_test_expected_failures.locations[count] = expected_place
+	local_test_expected_failures.location_count += 1
+}
+
+/*
+Let the test runner know that it should expect an assertion failure with a
+specific message for this test.
+
+In the event that an assertion fails, a debug message will be logged with its
+exact message and location in a copyable format to make it convenient to write
+tests which use this API.
+
+This procedure may be called up to 5 times with different messages.
+
+This is a limitation for the sake of simplicity in the implementation, and you
+should consider breaking up your tests into smaller procedures if you need to
+check for more than a couple different assertion messages.
+*/
+expect_assert_message :: proc(t: ^T, expected_message: string, caller_loc := #caller_location) {
+	count := local_test_expected_failures.message_count
+	if count == MAX_EXPECTED_ASSERTIONS_PER_TEST {
+		panic("This test cannot handle that many expected assertions based on matching the message.", caller_loc)
+	}
+	local_test_expected_failures.messages[count] = expected_message
+	local_test_expected_failures.message_count += 1
+}
+
+expect_assert :: proc {
+	expect_assert_from,
+	expect_assert_message,
+}
+
+/*
+Let the test runner know that it should expect a signal to be raised within
+this test.
+
+This API is for advanced users, as arbitrary signals will not be caught; only
+the ones already handled by the test runner, such as
+
+- SIGINT,                           (interrupt)
+- SIGTERM,                          (polite termination)
+- SIGILL,                           (illegal instruction)
+- SIGFPE,                           (arithmetic error)
+- SIGSEGV, and                      (segmentation fault)
+- SIGTRAP (only on POSIX systems).  (trap / debug trap)
+
+Note that only one signal can be expected per test.
+*/
+expect_signal :: proc(t: ^T, #any_int sig: i32) {
+	local_test_expected_failures.signal = sig
+}

+ 0 - 4
core/text/regex/regex.odin

@@ -28,8 +28,6 @@ Creation_Error :: enum {
 	Expected_Delimiter,
 	Expected_Delimiter,
 	// An unknown letter was supplied to `create_by_user` after the last delimiter.
 	// An unknown letter was supplied to `create_by_user` after the last delimiter.
 	Unknown_Flag,
 	Unknown_Flag,
-	// An unsupported flag was supplied.
-	Unsupported_Flag,
 }
 }
 
 
 Error :: union #shared_nil {
 Error :: union #shared_nil {
@@ -69,7 +67,6 @@ Regular_Expression :: struct {
 
 
 /*
 /*
 An iterator to repeatedly match a pattern against a string, to be used with `*_iterator` procedures.
 An iterator to repeatedly match a pattern against a string, to be used with `*_iterator` procedures.
-Note: Does not handle `.Multiline` properly.
 */
 */
 Match_Iterator :: struct {
 Match_Iterator :: struct {
 	regex:    Regular_Expression,
 	regex:    Regular_Expression,
@@ -436,7 +433,6 @@ match_with_preallocated_capture :: proc(
 
 
 /*
 /*
 Iterate over a `Match_Iterator` and return successive captures.
 Iterate over a `Match_Iterator` and return successive captures.
-Note: Does not handle `.Multiline` properly.
 
 
 Inputs:
 Inputs:
 - it: Pointer to the `Match_Iterator` to iterate over.
 - it: Pointer to the `Match_Iterator` to iterate over.

+ 26 - 13
core/thread/thread.odin

@@ -39,7 +39,8 @@ Type representing a thread handle and the associated with that thread data.
 Thread :: struct {
 Thread :: struct {
 	using specific: Thread_Os_Specific,
 	using specific: Thread_Os_Specific,
 	flags: bit_set[Thread_State; u8],
 	flags: bit_set[Thread_State; u8],
-	// Thread ID.
+	// Thread ID. Depending on the platform, may start out as 0 (zero) until the thread
+	// has had a chance to run.
 	id: int,
 	id: int,
 	// The thread procedure.
 	// The thread procedure.
 	procedure: Thread_Proc,
 	procedure: Thread_Proc,
@@ -257,8 +258,10 @@ to execute. The thread will have priority specified by the `priority` parameter.
 
 
 If `self_cleanup` is specified, after the thread finishes the execution of the
 If `self_cleanup` is specified, after the thread finishes the execution of the
 `fn` procedure, the resources associated with the thread are going to be
 `fn` procedure, the resources associated with the thread are going to be
-automatically freed. **Do not** dereference the `^Thread` pointer, if this
-flag is specified.
+automatically freed.
+
+**Do not** dereference the `^Thread` pointer, if this flag is specified.
+That includes calling `join`, which needs to dereference ^Thread`.
 
 
 **IMPORTANT**: If `init_context` is specified and the default temporary allocator
 **IMPORTANT**: If `init_context` is specified and the default temporary allocator
 is used, the thread procedure needs to call `runtime.default_temp_allocator_destroy()`
 is used, the thread procedure needs to call `runtime.default_temp_allocator_destroy()`
@@ -290,8 +293,10 @@ to execute. The thread will have priority specified by the `priority` parameter.
 
 
 If `self_cleanup` is specified, after the thread finishes the execution of the
 If `self_cleanup` is specified, after the thread finishes the execution of the
 `fn` procedure, the resources associated with the thread are going to be
 `fn` procedure, the resources associated with the thread are going to be
-automatically freed. **Do not** dereference the `^Thread` pointer, if this
-flag is specified.
+automatically freed.
+
+**Do not** dereference the `^Thread` pointer, if this flag is specified.
+That includes calling `join`, which needs to dereference ^Thread`.
 
 
 **IMPORTANT**: If `init_context` is specified and the default temporary allocator
 **IMPORTANT**: If `init_context` is specified and the default temporary allocator
 is used, the thread procedure needs to call `runtime.default_temp_allocator_destroy()`
 is used, the thread procedure needs to call `runtime.default_temp_allocator_destroy()`
@@ -327,8 +332,10 @@ to execute. The thread will have priority specified by the `priority` parameter.
 
 
 If `self_cleanup` is specified, after the thread finishes the execution of the
 If `self_cleanup` is specified, after the thread finishes the execution of the
 `fn` procedure, the resources associated with the thread are going to be
 `fn` procedure, the resources associated with the thread are going to be
-automatically freed. **Do not** dereference the `^Thread` pointer, if this
-flag is specified.
+automatically freed.
+
+**Do not** dereference the `^Thread` pointer, if this flag is specified.
+That includes calling `join`, which needs to dereference ^Thread`.
 
 
 **IMPORTANT**: If `init_context` is specified and the default temporary allocator
 **IMPORTANT**: If `init_context` is specified and the default temporary allocator
 is used, the thread procedure needs to call `runtime.default_temp_allocator_destroy()`
 is used, the thread procedure needs to call `runtime.default_temp_allocator_destroy()`
@@ -370,8 +377,10 @@ to execute. The thread will have priority specified by the `priority` parameter.
 
 
 If `self_cleanup` is specified, after the thread finishes the execution of the
 If `self_cleanup` is specified, after the thread finishes the execution of the
 `fn` procedure, the resources associated with the thread are going to be
 `fn` procedure, the resources associated with the thread are going to be
-automatically freed. **Do not** dereference the `^Thread` pointer, if this
-flag is specified.
+automatically freed.
+
+**Do not** dereference the `^Thread` pointer, if this flag is specified.
+That includes calling `join`, which needs to dereference ^Thread`.
 
 
 **IMPORTANT**: If `init_context` is specified and the default temporary allocator
 **IMPORTANT**: If `init_context` is specified and the default temporary allocator
 is used, the thread procedure needs to call `runtime.default_temp_allocator_destroy()`
 is used, the thread procedure needs to call `runtime.default_temp_allocator_destroy()`
@@ -419,8 +428,10 @@ to execute. The thread will have priority specified by the `priority` parameter.
 
 
 If `self_cleanup` is specified, after the thread finishes the execution of the
 If `self_cleanup` is specified, after the thread finishes the execution of the
 `fn` procedure, the resources associated with the thread are going to be
 `fn` procedure, the resources associated with the thread are going to be
-automatically freed. **Do not** dereference the `^Thread` pointer, if this
-flag is specified.
+automatically freed.
+
+**Do not** dereference the `^Thread` pointer, if this flag is specified.
+That includes calling `join`, which needs to dereference ^Thread`.
 
 
 **IMPORTANT**: If `init_context` is specified and the default temporary allocator
 **IMPORTANT**: If `init_context` is specified and the default temporary allocator
 is used, the thread procedure needs to call `runtime.default_temp_allocator_destroy()`
 is used, the thread procedure needs to call `runtime.default_temp_allocator_destroy()`
@@ -470,8 +481,10 @@ to execute. The thread will have priority specified by the `priority` parameter.
 
 
 If `self_cleanup` is specified, after the thread finishes the execution of the
 If `self_cleanup` is specified, after the thread finishes the execution of the
 `fn` procedure, the resources associated with the thread are going to be
 `fn` procedure, the resources associated with the thread are going to be
-automatically freed. **Do not** dereference the `^Thread` pointer, if this
-flag is specified.
+automatically freed.
+
+**Do not** dereference the `^Thread` pointer, if this flag is specified.
+That includes calling `join`, which needs to dereference ^Thread`.
 
 
 **IMPORTANT**: If `init_context` is specified and the default temporary allocator
 **IMPORTANT**: If `init_context` is specified and the default temporary allocator
 is used, the thread procedure needs to call `runtime.default_temp_allocator_destroy()`
 is used, the thread procedure needs to call `runtime.default_temp_allocator_destroy()`

+ 14 - 0
core/thread/thread_pool.odin

@@ -120,6 +120,20 @@ pool_join :: proc(pool: ^Pool) {
 
 
 	yield()
 	yield()
 
 
+	unstarted_count: int
+	for t in pool.threads {
+		flags := intrinsics.atomic_load(&t.flags)
+		if .Started not_in flags {
+			unstarted_count += 1
+		}
+	}
+
+	// most likely the user forgot to call `pool_start`
+	// exit here, so we don't hang forever
+	if len(pool.threads) == unstarted_count {
+		return
+	}
+
 	started_count: int
 	started_count: int
 	for started_count < len(pool.threads) {
 	for started_count < len(pool.threads) {
 		started_count = 0
 		started_count = 0

+ 5 - 7
core/thread/thread_unix.odin

@@ -29,14 +29,10 @@ _create :: proc(procedure: Thread_Proc, priority: Thread_Priority) -> ^Thread {
 
 
 		t.id = sync.current_thread_id()
 		t.id = sync.current_thread_id()
 
 
-		if .Started not_in sync.atomic_load(&t.flags) {
+		for (.Started not_in sync.atomic_load(&t.flags)) {
 			sync.wait(&t.start_ok)
 			sync.wait(&t.start_ok)
 		}
 		}
 
 
-		if .Joined in sync.atomic_load(&t.flags) {
-			return nil
-		}
-
 		// Enable thread's cancelability.
 		// Enable thread's cancelability.
 		// NOTE(laytan): Darwin does not correctly/fully support all of this, not doing this does
 		// NOTE(laytan): Darwin does not correctly/fully support all of this, not doing this does
 		// actually make pthread_cancel work in the capacity of my tests, while executing this would
 		// actually make pthread_cancel work in the capacity of my tests, while executing this would
@@ -124,7 +120,6 @@ _create :: proc(procedure: Thread_Proc, priority: Thread_Priority) -> ^Thread {
 		free(thread, thread.creation_allocator)
 		free(thread, thread.creation_allocator)
 		return nil
 		return nil
 	}
 	}
-
 	return thread
 	return thread
 }
 }
 
 
@@ -149,10 +144,13 @@ _join :: proc(t: ^Thread) {
 
 
 	// Prevent non-started threads from blocking main thread with initial wait
 	// Prevent non-started threads from blocking main thread with initial wait
 	// condition.
 	// condition.
-	if .Started not_in sync.atomic_load(&t.flags) {
+	for (.Started not_in sync.atomic_load(&t.flags)) {
 		_start(t)
 		_start(t)
 	}
 	}
+
 	posix.pthread_join(t.unix_thread, nil)
 	posix.pthread_join(t.unix_thread, nil)
+
+	t.flags += {.Joined}
 }
 }
 
 
 _join_multiple :: proc(threads: ..^Thread) {
 _join_multiple :: proc(threads: ..^Thread) {

+ 9 - 9
core/thread/thread_windows.odin

@@ -13,6 +13,7 @@ Thread_Os_Specific :: struct {
 	win32_thread:    win32.HANDLE,
 	win32_thread:    win32.HANDLE,
 	win32_thread_id: win32.DWORD,
 	win32_thread_id: win32.DWORD,
 	mutex:           sync.Mutex,
 	mutex:           sync.Mutex,
+	start_ok:        sync.Sema,
 }
 }
 
 
 _thread_priority_map := [Thread_Priority]i32{
 _thread_priority_map := [Thread_Priority]i32{
@@ -27,12 +28,10 @@ _create :: proc(procedure: Thread_Proc, priority: Thread_Priority) -> ^Thread {
 	__windows_thread_entry_proc :: proc "system" (t_: rawptr) -> win32.DWORD {
 	__windows_thread_entry_proc :: proc "system" (t_: rawptr) -> win32.DWORD {
 		t := (^Thread)(t_)
 		t := (^Thread)(t_)
 
 
-		if .Joined in sync.atomic_load(&t.flags) {
-			return 0
+		for (.Started not_in sync.atomic_load(&t.flags)) {
+			sync.wait(&t.start_ok)
 		}
 		}
 
 
-		t.id = sync.current_thread_id()
-
 		{
 		{
 			init_context := t.init_context
 			init_context := t.init_context
 
 
@@ -76,6 +75,7 @@ _create :: proc(procedure: Thread_Proc, priority: Thread_Priority) -> ^Thread {
 	thread.procedure       = procedure
 	thread.procedure       = procedure
 	thread.win32_thread    = win32_thread
 	thread.win32_thread    = win32_thread
 	thread.win32_thread_id = win32_thread_id
 	thread.win32_thread_id = win32_thread_id
+	thread.id              = int(win32_thread_id)
 
 
 	ok := win32.SetThreadPriority(win32_thread, _thread_priority_map[priority])
 	ok := win32.SetThreadPriority(win32_thread, _thread_priority_map[priority])
 	assert(ok == true)
 	assert(ok == true)
@@ -103,16 +103,15 @@ _join :: proc(t: ^Thread) {
 		return
 		return
 	}
 	}
 
 
-	t.flags += {.Joined}
-
-	if .Started not_in t.flags {
-		t.flags += {.Started}
-		win32.ResumeThread(t.win32_thread)
+	for (.Started not_in sync.atomic_load(&t.flags)) {
+		_start(t)
 	}
 	}
 
 
 	win32.WaitForSingleObject(t.win32_thread, win32.INFINITE)
 	win32.WaitForSingleObject(t.win32_thread, win32.INFINITE)
 	win32.CloseHandle(t.win32_thread)
 	win32.CloseHandle(t.win32_thread)
 	t.win32_thread = win32.INVALID_HANDLE
 	t.win32_thread = win32.INVALID_HANDLE
+
+	t.flags += {.Joined}
 }
 }
 
 
 _join_multiple :: proc(threads: ..^Thread) {
 _join_multiple :: proc(threads: ..^Thread) {
@@ -136,6 +135,7 @@ _join_multiple :: proc(threads: ..^Thread) {
 	for t in threads {
 	for t in threads {
 		win32.CloseHandle(t.win32_thread)
 		win32.CloseHandle(t.win32_thread)
 		t.win32_thread = win32.INVALID_HANDLE
 		t.win32_thread = win32.INVALID_HANDLE
+		t.flags += {.Joined}
 	}
 	}
 }
 }
 
 

+ 1 - 6
core/time/timezone/tzif.odin

@@ -577,12 +577,7 @@ parse_tzif :: proc(_buffer: []u8, region_name: string, allocator := context.allo
 	footer_str := string(buffer[:end_idx])
 	footer_str := string(buffer[:end_idx])
 
 
 	// UTC is a special case, we don't need to alloc
 	// UTC is a special case, we don't need to alloc
-	if len(local_time_types) == 1 {
-		name := cstring(raw_data(timezone_string_table[local_time_types[0].idx:]))
-		if name != "UTC" {
-			return
-		}
-
+	if len(local_time_types) == 1 && local_time_types[0].utoff == 0 {
 		return nil, true
 		return nil, true
 	}
 	}
 
 

+ 4 - 1
examples/all/all_vendor.odin

@@ -45,4 +45,7 @@ package all
 @(require) import stbi "vendor:stb/image"
 @(require) import stbi "vendor:stb/image"
 @(require) import "vendor:stb/rect_pack"
 @(require) import "vendor:stb/rect_pack"
 @(require) import "vendor:stb/truetype"
 @(require) import "vendor:stb/truetype"
-@(require) import "vendor:stb/vorbis"
+@(require) import "vendor:stb/vorbis"
+
+
+@(require) import "vendor:kb_text_shape"

+ 1 - 0
src/bug_report.cpp

@@ -540,6 +540,7 @@ gb_internal void report_os_info() {
 		}
 		}
 
 
 		switch (major) {
 		switch (major) {
+		case 26: gb_printf("macOS Tahoe"); break;
 		case 15: gb_printf("macOS Sequoia"); break;
 		case 15: gb_printf("macOS Sequoia"); break;
 		case 14: gb_printf("macOS Sonoma"); break;
 		case 14: gb_printf("macOS Sonoma"); break;
 		case 13: gb_printf("macOS Ventura"); break;
 		case 13: gb_printf("macOS Ventura"); break;

+ 8 - 1
src/build_settings.cpp

@@ -385,6 +385,13 @@ enum LinkerChoice : i32 {
 	Linker_COUNT,
 	Linker_COUNT,
 };
 };
 
 
+enum SourceCodeLocationInfo : u8 {
+	SourceCodeLocationInfo_Normal = 0,
+	SourceCodeLocationInfo_Obfuscated = 1,
+	SourceCodeLocationInfo_Filename = 2,
+	SourceCodeLocationInfo_None = 3,
+};
+
 String linker_choices[Linker_COUNT] = {
 String linker_choices[Linker_COUNT] = {
 	str_lit("default"),
 	str_lit("default"),
 	str_lit("lld"),
 	str_lit("lld"),
@@ -512,7 +519,7 @@ struct BuildContext {
 
 
 	bool   dynamic_map_calls;
 	bool   dynamic_map_calls;
 
 
-	bool   obfuscate_source_code_locations;
+	SourceCodeLocationInfo source_code_location_info;
 
 
 	bool   min_link_libs;
 	bool   min_link_libs;
 
 

+ 5 - 0
src/check_builtin.cpp

@@ -148,6 +148,11 @@ gb_internal bool does_require_msgSend_stret(Type *return_type) {
 	if (return_type == nullptr) {
 	if (return_type == nullptr) {
 		return false;
 		return false;
 	}
 	}
+
+	if (build_context.metrics.os != TargetOs_darwin) {
+		return false;
+	}
+
 	if (build_context.metrics.arch == TargetArch_i386 || build_context.metrics.arch == TargetArch_amd64) {
 	if (build_context.metrics.arch == TargetArch_i386 || build_context.metrics.arch == TargetArch_amd64) {
 		i64 struct_limit = type_size_of(t_uintptr) << 1;
 		i64 struct_limit = type_size_of(t_uintptr) << 1;
 		return type_size_of(return_type) > struct_limit;
 		return type_size_of(return_type) > struct_limit;

+ 8 - 0
src/check_decl.cpp

@@ -1334,12 +1334,16 @@ gb_internal void check_proc_decl(CheckerContext *ctx, Entity *e, DeclInfo *d) {
 		has_instrumentation = false;
 		has_instrumentation = false;
 		e->flags |= EntityFlag_Require;
 		e->flags |= EntityFlag_Require;
 	} else if (ac.instrumentation_enter) {
 	} else if (ac.instrumentation_enter) {
+		init_core_source_code_location(ctx->checker);
 		if (!is_valid_instrumentation_call(e->type)) {
 		if (!is_valid_instrumentation_call(e->type)) {
 			init_core_source_code_location(ctx->checker);
 			init_core_source_code_location(ctx->checker);
 			gbString s = type_to_string(e->type);
 			gbString s = type_to_string(e->type);
 			error(e->token, "@(instrumentation_enter) procedures must have the type '%s', got %s", instrumentation_proc_type_str, s);
 			error(e->token, "@(instrumentation_enter) procedures must have the type '%s', got %s", instrumentation_proc_type_str, s);
 			gb_string_free(s);
 			gb_string_free(s);
 		}
 		}
+		if ((e->scope->flags & (ScopeFlag_File|ScopeFlag_Pkg)) == 0) {
+			error(e->token, "@(instrumentation_enter) procedures must be declared at the file scope");
+		}
 		MUTEX_GUARD(&ctx->info->instrumentation_mutex);
 		MUTEX_GUARD(&ctx->info->instrumentation_mutex);
 		if (ctx->info->instrumentation_enter_entity != nullptr) {
 		if (ctx->info->instrumentation_enter_entity != nullptr) {
 			error(e->token, "@(instrumentation_enter) has already been set");
 			error(e->token, "@(instrumentation_enter) has already been set");
@@ -1356,6 +1360,9 @@ gb_internal void check_proc_decl(CheckerContext *ctx, Entity *e, DeclInfo *d) {
 			error(e->token, "@(instrumentation_exit) procedures must have the type '%s', got %s", instrumentation_proc_type_str, s);
 			error(e->token, "@(instrumentation_exit) procedures must have the type '%s', got %s", instrumentation_proc_type_str, s);
 			gb_string_free(s);
 			gb_string_free(s);
 		}
 		}
+		if ((e->scope->flags & (ScopeFlag_File|ScopeFlag_Pkg)) == 0) {
+			error(e->token, "@(instrumentation_exit) procedures must be declared at the file scope");
+		}
 		MUTEX_GUARD(&ctx->info->instrumentation_mutex);
 		MUTEX_GUARD(&ctx->info->instrumentation_mutex);
 		if (ctx->info->instrumentation_exit_entity != nullptr) {
 		if (ctx->info->instrumentation_exit_entity != nullptr) {
 			error(e->token, "@(instrumentation_exit) has already been set");
 			error(e->token, "@(instrumentation_exit) has already been set");
@@ -1370,6 +1377,7 @@ gb_internal void check_proc_decl(CheckerContext *ctx, Entity *e, DeclInfo *d) {
 	e->Procedure.has_instrumentation = has_instrumentation;
 	e->Procedure.has_instrumentation = has_instrumentation;
 
 
 	e->Procedure.no_sanitize_address = ac.no_sanitize_address;
 	e->Procedure.no_sanitize_address = ac.no_sanitize_address;
+	e->Procedure.no_sanitize_memory  = ac.no_sanitize_memory;
 
 
 	e->deprecated_message = ac.deprecated_message;
 	e->deprecated_message = ac.deprecated_message;
 	e->warning_message = ac.warning_message;
 	e->warning_message = ac.warning_message;

+ 178 - 129
src/check_expr.cpp

@@ -2424,27 +2424,27 @@ gb_internal void check_assignment_error_suggestion(CheckerContext *c, Operand *o
 		Type *s = src->Array.elem;
 		Type *s = src->Array.elem;
 		Type *d = dst->Slice.elem;
 		Type *d = dst->Slice.elem;
 		if (are_types_identical(s, d)) {
 		if (are_types_identical(s, d)) {
-			error_line("\tSuggestion: the array expression may be sliced with %s[:]\n", a);
+			error_line("\tSuggestion: The array expression may be sliced with %s[:]\n", a);
 		}
 		}
 	} else if (is_type_dynamic_array(src) && is_type_slice(dst)) {
 	} else if (is_type_dynamic_array(src) && is_type_slice(dst)) {
 		Type *s = src->DynamicArray.elem;
 		Type *s = src->DynamicArray.elem;
 		Type *d = dst->Slice.elem;
 		Type *d = dst->Slice.elem;
 		if (are_types_identical(s, d)) {
 		if (are_types_identical(s, d)) {
-			error_line("\tSuggestion: the dynamic array expression may be sliced with %s[:]\n", a);
+			error_line("\tSuggestion: The dynamic array expression may be sliced with %s[:]\n", a);
 		}
 		}
 	}else if (are_types_identical(src, dst) && !are_types_identical(o->type, type)) {
 	}else if (are_types_identical(src, dst) && !are_types_identical(o->type, type)) {
-		error_line("\tSuggestion: the expression may be directly casted to type %s\n", b);
+		error_line("\tSuggestion: The expression may be directly casted to type %s\n", b);
 	} else if (are_types_identical(src, t_string) && is_type_u8_slice(dst)) {
 	} else if (are_types_identical(src, t_string) && is_type_u8_slice(dst)) {
-		error_line("\tSuggestion: a string may be transmuted to %s\n", b);
-		error_line("\t            This is an UNSAFE operation as string data is assumed to be immutable, \n");
+		error_line("\tSuggestion: A string may be transmuted to %s\n", b);
+		error_line("\t            This is an UNSAFE operation as string data is assumed to be immutable,\n");
 		error_line("\t            whereas slices in general are assumed to be mutable.\n");
 		error_line("\t            whereas slices in general are assumed to be mutable.\n");
 	} else if (is_type_u8_slice(src) && are_types_identical(dst, t_string) && o->mode != Addressing_Constant) {
 	} else if (is_type_u8_slice(src) && are_types_identical(dst, t_string) && o->mode != Addressing_Constant) {
-		error_line("\tSuggestion: the expression may be casted to %s\n", b);
+		error_line("\tSuggestion: The expression may be casted to %s\n", b);
 	} else if (check_integer_exceed_suggestion(c, o, type, max_bit_size)) {
 	} else if (check_integer_exceed_suggestion(c, o, type, max_bit_size)) {
 		return;
 		return;
 	} else if (is_expr_inferred_fixed_array(c->type_hint_expr) && is_type_array_like(type) && is_type_array_like(o->type)) {
 	} else if (is_expr_inferred_fixed_array(c->type_hint_expr) && is_type_array_like(type) && is_type_array_like(o->type)) {
 		gbString s = expr_to_string(c->type_hint_expr);
 		gbString s = expr_to_string(c->type_hint_expr);
-		error_line("\tSuggestion: make sure that `%s` is attached to the compound literal directly\n", s);
+		error_line("\tSuggestion: Make sure that `%s` is attached to the compound literal directly\n", s);
 		gb_string_free(s);
 		gb_string_free(s);
 	} else if (is_type_pointer(type) &&
 	} else if (is_type_pointer(type) &&
 	           o->mode == Addressing_Variable &&
 	           o->mode == Addressing_Variable &&
@@ -3086,126 +3086,106 @@ gb_internal void check_shift(CheckerContext *c, Operand *x, Operand *y, Ast *nod
 	GB_ASSERT(node->kind == Ast_BinaryExpr);
 	GB_ASSERT(node->kind == Ast_BinaryExpr);
 	ast_node(be, BinaryExpr, node);
 	ast_node(be, BinaryExpr, node);
 
 
-	ExactValue x_val = {};
-	if (x->mode == Addressing_Constant) {
-		x_val = exact_value_to_integer(x->value);
+	bool y_is_untyped = is_type_untyped(y->type);
+	if (y_is_untyped) {
+		convert_to_typed(c, y, t_untyped_integer);
+		if (y->mode == Addressing_Invalid) {
+			x->mode = Addressing_Invalid;
+			return;
+		}
+	} else if (!is_type_unsigned(y->type)) {
+		gbString y_str = expr_to_string(y->expr);
+		error(y->expr, "Shift amount '%s' must be an unsigned integer", y_str);
+		gb_string_free(y_str);
+		x->mode = Addressing_Invalid;
+		return;
 	}
 	}
 
 
 	bool x_is_untyped = is_type_untyped(x->type);
 	bool x_is_untyped = is_type_untyped(x->type);
-	if (!(is_type_integer(x->type) || (x_is_untyped && x_val.kind == ExactValue_Integer))) {
-		gbString err_str = expr_to_string(x->expr);
-		error(node, "Shifted operand '%s' must be an integer", err_str);
-		gb_string_free(err_str);
+	if (!(x_is_untyped || is_type_integer(x->type))) {
+		gbString x_str = expr_to_string(x->expr);
+		error(x->expr, "Shifted operand '%s' must be an integer", x_str);
+		gb_string_free(x_str);
 		x->mode = Addressing_Invalid;
 		x->mode = Addressing_Invalid;
 		return;
 		return;
 	}
 	}
 
 
-	if (is_type_unsigned(y->type)) {
-
-	} else if (is_type_untyped(y->type)) {
-		convert_to_typed(c, y, t_untyped_integer);
-		if (y->mode == Addressing_Invalid) {
+	if (y->mode == Addressing_Constant) {
+		if (big_int_is_neg(&y->value.value_integer)) {
+			gbString y_str = expr_to_string(y->expr);
+			error(y->expr, "Shift amount '%s' cannot be negative", y_str);
+			gb_string_free(y_str);
 			x->mode = Addressing_Invalid;
 			x->mode = Addressing_Invalid;
 			return;
 			return;
 		}
 		}
-	} else {
-		gbString err_str = expr_to_string(y->expr);
-		error(node, "Shift amount '%s' must be an unsigned integer", err_str);
-		gb_string_free(err_str);
-		x->mode = Addressing_Invalid;
-		return;
-	}
 
 
+		BigInt max_shift = {};
+		big_int_from_u64(&max_shift, MAX_BIG_INT_SHIFT);
 
 
-	if (x->mode == Addressing_Constant) {
-		if (y->mode == Addressing_Constant) {
-			ExactValue y_val = exact_value_to_integer(y->value);
-			if (y_val.kind != ExactValue_Integer) {
-				gbString err_str = expr_to_string(y->expr);
-				error(node, "Shift amount '%s' must be an unsigned integer", err_str);
-				gb_string_free(err_str);
-				x->mode = Addressing_Invalid;
-				return;
-			}
+		if (big_int_cmp(&y->value.value_integer, &max_shift) > 0) {
+			gbString y_str = expr_to_string(y->expr);
+			error(y->expr, "Shift amount '%s' must be <= %u", y_str, MAX_BIG_INT_SHIFT);
+			gb_string_free(y_str);
+			x->mode = Addressing_Invalid;
+			return;
+		}
 
 
-			BigInt max_shift = {};
-			big_int_from_u64(&max_shift, MAX_BIG_INT_SHIFT);
+		if (x->mode == Addressing_Constant) {
+			if (x_is_untyped) {
+				convert_to_typed(c, x, t_untyped_integer);
+				if (x->mode == Addressing_Invalid) {
+					return;
+				}
 
 
-			if (big_int_cmp(&y_val.value_integer, &max_shift) > 0) {
-				gbString err_str = expr_to_string(y->expr);
-				error(node, "Shift amount too large: '%s'", err_str);
-				gb_string_free(err_str);
-				x->mode = Addressing_Invalid;
-				return;
-			}
+				x->expr = node;
+				x->value = exact_value_shift(be->op.kind, exact_value_to_integer(x->value), exact_value_to_integer(y->value));
 
 
-			if (!is_type_integer(x->type)) {
-				// NOTE(bill): It could be an untyped float but still representable
-				// as an integer
-				x->type = t_untyped_integer;
+				return;
 			}
 			}
 
 
 			x->expr = node;
 			x->expr = node;
-			x->value = exact_value_shift(be->op.kind, x_val, y_val);
+			x->value = exact_value_shift(be->op.kind, x->value, y->value);
 
 
+			check_is_expressible(c, x, x->type);
 
 
-			if (is_type_typed(x->type)) {
-				check_is_expressible(c, x, x->type);
-			}
 			return;
 			return;
 		}
 		}
 
 
-		TokenPos pos = ast_token(x->expr).pos;
+		if (y_is_untyped) {
+			convert_to_typed(c, y, t_uint);
+		}
+
+		return;
+	}
+
+	if (x->mode == Addressing_Constant) {
 		if (x_is_untyped) {
 		if (x_is_untyped) {
-			if (x->expr != nullptr) {
-				x->expr->tav.is_lhs = true;
-			}
-			x->mode = Addressing_Value;
 			if (type_hint) {
 			if (type_hint) {
 				if (is_type_integer(type_hint)) {
 				if (is_type_integer(type_hint)) {
 					convert_to_typed(c, x, type_hint);
 					convert_to_typed(c, x, type_hint);
+				} else if (is_type_any(type_hint)) {
+					convert_to_typed(c, x, default_type(t_untyped_integer));
 				} else {
 				} else {
 					gbString x_str = expr_to_string(x->expr);
 					gbString x_str = expr_to_string(x->expr);
-					gbString to_type = type_to_string(type_hint);
-					error(node, "Conversion of shifted operand '%s' to '%s' is not allowed", x_str, to_type);
+					gbString type_str = type_to_string(type_hint);
+					error(x->expr, "Shifted operand '%s' cannot convert to non-integer type '%s'", x_str, type_str);
 					gb_string_free(x_str);
 					gb_string_free(x_str);
-					gb_string_free(to_type);
+					gb_string_free(type_str);
 					x->mode = Addressing_Invalid;
 					x->mode = Addressing_Invalid;
+					return;
 				}
 				}
-			} else if (!is_type_integer(x->type)) {
-				gbString x_str = expr_to_string(x->expr);
-				error(node, "Non-integer shifted operand '%s' is not allowed", x_str);
-				gb_string_free(x_str);
-				x->mode = Addressing_Invalid;
+			} else {
+				check_is_expressible(c, x, default_type(t_untyped_integer));
+			}
+			if (x->mode == Addressing_Invalid) {
+				return;
 			}
 			}
-			// x->value = x_val;
-			return;
 		}
 		}
-	}
 
 
-	if (y->mode == Addressing_Constant && big_int_is_neg(&y->value.value_integer)) {
-		gbString err_str = expr_to_string(y->expr);
-		error(node, "Shift amount cannot be negative: '%s'", err_str);
-		gb_string_free(err_str);
-	}
-
-	if (!is_type_integer(x->type)) {
-		gbString err_str = expr_to_string(x->expr);
-		error(node, "Shift operand '%s' must be an integer", err_str);
-		gb_string_free(err_str);
-		x->mode = Addressing_Invalid;
-		return;
-	}
-
-	if (is_type_untyped(y->type)) {
-		convert_to_typed(c, y, t_uint);
+		x->mode = Addressing_Value;
 	}
 	}
-
-	x->mode = Addressing_Value;
 }
 }
 
 
-
-
 gb_internal bool check_is_castable_to(CheckerContext *c, Operand *operand, Type *y) {
 gb_internal bool check_is_castable_to(CheckerContext *c, Operand *operand, Type *y) {
 	if (check_is_assignable_to(c, operand, y)) {
 	if (check_is_assignable_to(c, operand, y)) {
 		return true;
 		return true;
@@ -5884,12 +5864,12 @@ typedef u32 UnpackFlags;
 enum UnpackFlag : u32 {
 enum UnpackFlag : u32 {
 	UnpackFlag_None       = 0,
 	UnpackFlag_None       = 0,
 	UnpackFlag_AllowOk    = 1<<0,
 	UnpackFlag_AllowOk    = 1<<0,
-	UnpackFlag_IsVariadic = 1<<1,
-	UnpackFlag_AllowUndef = 1<<2,
+	UnpackFlag_AllowUndef = 1<<1,
 };
 };
 
 
 
 
-gb_internal bool check_unpack_arguments(CheckerContext *ctx, Entity **lhs, isize lhs_count, Array<Operand> *operands, Slice<Ast *> const &rhs_arguments, UnpackFlags flags) {
+gb_internal bool check_unpack_arguments(CheckerContext *ctx, Entity **lhs, isize lhs_count, Array<Operand> *operands, Slice<Ast *> const &rhs_arguments, UnpackFlags flags,
+	isize variadic_index = -1) {
 	auto const &add_dependencies_from_unpacking = [](CheckerContext *c, Entity **lhs, isize lhs_count, isize tuple_index, isize tuple_count) -> isize {
 	auto const &add_dependencies_from_unpacking = [](CheckerContext *c, Entity **lhs, isize lhs_count, isize tuple_index, isize tuple_count) -> isize {
 		if (lhs == nullptr || c->decl == nullptr) {
 		if (lhs == nullptr || c->decl == nullptr) {
 			return tuple_count;
 			return tuple_count;
@@ -5914,11 +5894,14 @@ gb_internal bool check_unpack_arguments(CheckerContext *ctx, Entity **lhs, isize
 		return tuple_count;
 		return tuple_count;
 	};
 	};
 
 
-
 	bool allow_ok    = (flags & UnpackFlag_AllowOk) != 0;
 	bool allow_ok    = (flags & UnpackFlag_AllowOk) != 0;
-	bool is_variadic = (flags & UnpackFlag_IsVariadic) != 0;
 	bool allow_undef = (flags & UnpackFlag_AllowUndef) != 0;
 	bool allow_undef = (flags & UnpackFlag_AllowUndef) != 0;
 
 
+	bool is_variadic = variadic_index > -1;
+	if (!is_variadic) {
+		variadic_index = lhs_count;
+	}
+
 	bool optional_ok = false;
 	bool optional_ok = false;
 	isize tuple_index = 0;
 	isize tuple_index = 0;
 	for (Ast *rhs : rhs_arguments) {
 	for (Ast *rhs : rhs_arguments) {
@@ -5934,26 +5917,18 @@ gb_internal bool check_unpack_arguments(CheckerContext *ctx, Entity **lhs, isize
 
 
 		Type *type_hint = nullptr;
 		Type *type_hint = nullptr;
 
 
-
-		if (lhs != nullptr && tuple_index < lhs_count) {
-			// NOTE(bill): override DeclInfo for dependency
-			Entity *e = lhs[tuple_index];
-			if (e != nullptr) {
-				type_hint = e->type;
-				if (e->flags & EntityFlag_Ellipsis) {
-					GB_ASSERT(is_type_slice(e->type));
-					GB_ASSERT(e->type->kind == Type_Slice);
-					type_hint = e->type->Slice.elem;
+		if (lhs != nullptr) {
+			if (tuple_index < variadic_index) {
+				// NOTE(bill): override DeclInfo for dependency
+				Entity *e = lhs[tuple_index];
+				if (e != nullptr) {
+					type_hint = e->type;
 				}
 				}
-			}
-		} else if (lhs != nullptr && tuple_index >= lhs_count && is_variadic) {
-			// NOTE(bill): override DeclInfo for dependency
-			Entity *e = lhs[lhs_count-1];
-			if (e != nullptr) {
-				type_hint = e->type;
-				if (e->flags & EntityFlag_Ellipsis) {
+			} else if (is_variadic) {
+				Entity *e = lhs[variadic_index];
+				if (e != nullptr) {
+					GB_ASSERT(e->flags & EntityFlag_Ellipsis);
 					GB_ASSERT(is_type_slice(e->type));
 					GB_ASSERT(is_type_slice(e->type));
-					GB_ASSERT(e->type->kind == Type_Slice);
 					type_hint = e->type->Slice.elem;
 					type_hint = e->type->Slice.elem;
 				}
 				}
 			}
 			}
@@ -6493,17 +6468,16 @@ gb_internal bool is_call_expr_field_value(AstCallExpr *ce) {
 	return ce->args[0]->kind == Ast_FieldValue;
 	return ce->args[0]->kind == Ast_FieldValue;
 }
 }
 
 
-gb_internal Entity **populate_proc_parameter_list(CheckerContext *c, Type *proc_type, isize *lhs_count_, bool *is_variadic) {
+gb_internal Entity **populate_proc_parameter_list(CheckerContext *c, Type *proc_type, isize *lhs_count_) {
 	Entity **lhs = nullptr;
 	Entity **lhs = nullptr;
 	isize lhs_count = -1;
 	isize lhs_count = -1;
 
 
-	if (proc_type == nullptr) {
+	if (proc_type == nullptr || proc_type == t_invalid) {
 		return nullptr;
 		return nullptr;
 	}
 	}
 
 
 	GB_ASSERT(is_type_proc(proc_type));
 	GB_ASSERT(is_type_proc(proc_type));
 	TypeProc *pt = &base_type(proc_type)->Proc;
 	TypeProc *pt = &base_type(proc_type)->Proc;
-	*is_variadic = pt->variadic;
 
 
 	if (!pt->is_polymorphic || pt->is_poly_specialized) {
 	if (!pt->is_polymorphic || pt->is_poly_specialized) {
 		if (pt->params != nullptr) {
 		if (pt->params != nullptr) {
@@ -6697,6 +6671,9 @@ gb_internal bool check_call_arguments_single(CheckerContext *c, Ast *call, Opera
 
 
 	GB_ASSERT(proc_type != nullptr);
 	GB_ASSERT(proc_type != nullptr);
 	proc_type = base_type(proc_type);
 	proc_type = base_type(proc_type);
+	if (proc_type == t_invalid) {
+		return false;
+	}
 	GB_ASSERT(proc_type->kind == Type_Proc);
 	GB_ASSERT(proc_type->kind == Type_Proc);
 
 
 	CallArgumentError err = check_call_arguments_internal(c, call, e, proc_type, positional_operands, named_operands, show_error_mode, data);
 	CallArgumentError err = check_call_arguments_internal(c, call, e, proc_type, positional_operands, named_operands, show_error_mode, data);
@@ -6830,7 +6807,7 @@ gb_internal CallArgumentData check_call_arguments_proc_group(CheckerContext *c,
 
 
 	Entity **lhs = nullptr;
 	Entity **lhs = nullptr;
 	isize lhs_count = -1;
 	isize lhs_count = -1;
-	bool is_variadic = false;
+	i32 variadic_index = -1;
 
 
 	auto positional_operands = array_make<Operand>(heap_allocator(), 0, 0);
 	auto positional_operands = array_make<Operand>(heap_allocator(), 0, 0);
 	auto named_operands = array_make<Operand>(heap_allocator(), 0, 0);
 	auto named_operands = array_make<Operand>(heap_allocator(), 0, 0);
@@ -6839,9 +6816,14 @@ gb_internal CallArgumentData check_call_arguments_proc_group(CheckerContext *c,
 
 
 	if (procs.count == 1) {
 	if (procs.count == 1) {
 		Entity *e = procs[0];
 		Entity *e = procs[0];
-
-		lhs = populate_proc_parameter_list(c, e->type, &lhs_count, &is_variadic);
-		check_unpack_arguments(c, lhs, lhs_count, &positional_operands, positional_args, is_variadic ? UnpackFlag_IsVariadic : UnpackFlag_None);
+		Type *pt = base_type(e->type);
+		if (pt != nullptr && is_type_proc(pt)) {
+			lhs = populate_proc_parameter_list(c, pt, &lhs_count);
+			if (pt->Proc.variadic) {
+				variadic_index = pt->Proc.variadic_index;
+			}
+		}
+		check_unpack_arguments(c, lhs, lhs_count, &positional_operands, positional_args, UnpackFlag_None, variadic_index);
 
 
 		if (check_named_arguments(c, e->type, named_args, &named_operands, true)) {
 		if (check_named_arguments(c, e->type, named_args, &named_operands, true)) {
 			check_call_arguments_single(c, call, operand,
 			check_call_arguments_single(c, call, operand,
@@ -6901,11 +6883,30 @@ gb_internal CallArgumentData check_call_arguments_proc_group(CheckerContext *c,
 					}
 					}
 					lhs[param_index] = e;
 					lhs[param_index] = e;
 				}
 				}
+
+				for (Entity *p : procs) {
+					Type *pt = base_type(p->type);
+					if (!(pt != nullptr && is_type_proc(pt))) {
+						continue;
+					}
+					
+					if (pt->Proc.is_polymorphic) {
+						if (variadic_index == -1) {
+							variadic_index = pt->Proc.variadic_index;
+						} else if (variadic_index != pt->Proc.variadic_index) {
+							variadic_index = -1;
+							break;
+						}
+					} else {
+						variadic_index = -1;
+						break;
+					}
+				}
 			}
 			}
 		}
 		}
 	}
 	}
 
 
-	check_unpack_arguments(c, lhs, lhs_count, &positional_operands, positional_args, is_variadic ? UnpackFlag_IsVariadic : UnpackFlag_None);
+	check_unpack_arguments(c, lhs, lhs_count, &positional_operands, positional_args, UnpackFlag_None, variadic_index);
 
 
 	for_array(i, named_args) {
 	for_array(i, named_args) {
 		Ast *arg = named_args[i];
 		Ast *arg = named_args[i];
@@ -7343,13 +7344,16 @@ gb_internal CallArgumentData check_call_arguments(CheckerContext *c, Operand *op
 	defer (array_free(&named_operands));
 	defer (array_free(&named_operands));
 
 
 	if (positional_args.count > 0) {
 	if (positional_args.count > 0) {
-		isize lhs_count = -1;
-		bool is_variadic = false;
 		Entity **lhs =  nullptr;
 		Entity **lhs =  nullptr;
+		isize lhs_count = -1;
+		i32 variadic_index = -1;
 		if (pt != nullptr)  {
 		if (pt != nullptr)  {
-			lhs = populate_proc_parameter_list(c, proc_type, &lhs_count, &is_variadic);
+			lhs = populate_proc_parameter_list(c, proc_type, &lhs_count);
+			if (pt->variadic) {
+				variadic_index = pt->variadic_index;
+			}
 		}
 		}
-		check_unpack_arguments(c, lhs, lhs_count, &positional_operands, positional_args, is_variadic ? UnpackFlag_IsVariadic : UnpackFlag_None);
+		check_unpack_arguments(c, lhs, lhs_count, &positional_operands, positional_args, UnpackFlag_None, variadic_index);
 	}
 	}
 
 
 	if (named_args.count > 0) {
 	if (named_args.count > 0) {
@@ -8756,23 +8760,52 @@ gb_internal ExprKind check_basic_directive_expr(CheckerContext *c, Operand *o, A
 	String name = bd->name.string;
 	String name = bd->name.string;
 	if (name == "file") {
 	if (name == "file") {
 		String file = get_file_path_string(bd->token.pos.file_id);
 		String file = get_file_path_string(bd->token.pos.file_id);
-		if (build_context.obfuscate_source_code_locations) {
+		switch (build_context.source_code_location_info) {
+		case SourceCodeLocationInfo_Normal:
+			break;
+		case SourceCodeLocationInfo_Obfuscated:
 			file = obfuscate_string(file, "F");
 			file = obfuscate_string(file, "F");
+			break;
+		case SourceCodeLocationInfo_Filename:
+			file = last_path_element(file);
+			break;
+		case SourceCodeLocationInfo_None:
+			file = str_lit("");
+			break;
 		}
 		}
 		o->type = t_untyped_string;
 		o->type = t_untyped_string;
 		o->value = exact_value_string(file);
 		o->value = exact_value_string(file);
 	} else if (name == "directory") {
 	} else if (name == "directory") {
 		String file = get_file_path_string(bd->token.pos.file_id);
 		String file = get_file_path_string(bd->token.pos.file_id);
 		String path = dir_from_path(file);
 		String path = dir_from_path(file);
-		if (build_context.obfuscate_source_code_locations) {
+		switch (build_context.source_code_location_info) {
+		case SourceCodeLocationInfo_Normal:
+			break;
+		case SourceCodeLocationInfo_Obfuscated:
 			path = obfuscate_string(path, "D");
 			path = obfuscate_string(path, "D");
+			break;
+		case SourceCodeLocationInfo_Filename:
+			path = last_path_element(path);
+			break;
+		case SourceCodeLocationInfo_None:
+			path = str_lit("");
+			break;
 		}
 		}
 		o->type = t_untyped_string;
 		o->type = t_untyped_string;
 		o->value = exact_value_string(path);
 		o->value = exact_value_string(path);
 	} else if (name == "line") {
 	} else if (name == "line") {
 		i32 line = bd->token.pos.line;
 		i32 line = bd->token.pos.line;
-		if (build_context.obfuscate_source_code_locations) {
+		switch (build_context.source_code_location_info) {
+		case SourceCodeLocationInfo_Normal:
+			break;
+		case SourceCodeLocationInfo_Obfuscated:
 			line = obfuscate_i32(line);
 			line = obfuscate_i32(line);
+			break;
+		case SourceCodeLocationInfo_Filename:
+			break;
+		case SourceCodeLocationInfo_None:
+			line = 0;
+			break;
 		}
 		}
 		o->type = t_untyped_integer;
 		o->type = t_untyped_integer;
 		o->value = exact_value_i64(line);
 		o->value = exact_value_i64(line);
@@ -8783,8 +8816,17 @@ gb_internal ExprKind check_basic_directive_expr(CheckerContext *c, Operand *o, A
 			o->value = exact_value_string(str_lit(""));
 			o->value = exact_value_string(str_lit(""));
 		} else {
 		} else {
 			String p = c->proc_name;
 			String p = c->proc_name;
-			if (build_context.obfuscate_source_code_locations) {
+			switch (build_context.source_code_location_info) {
+			case SourceCodeLocationInfo_Normal:
+				break;
+			case SourceCodeLocationInfo_Obfuscated:
 				p = obfuscate_string(p, "P");
 				p = obfuscate_string(p, "P");
+				break;
+			case SourceCodeLocationInfo_Filename:
+				break;
+			case SourceCodeLocationInfo_None:
+				p = str_lit("");
+				break;
 			}
 			}
 			o->type = t_untyped_string;
 			o->type = t_untyped_string;
 			o->value = exact_value_string(p);
 			o->value = exact_value_string(p);
@@ -11087,7 +11129,7 @@ gb_internal ExprKind check_expr_base_internal(CheckerContext *c, Operand *o, Ast
 	case_ast_node(u, Uninit, node);
 	case_ast_node(u, Uninit, node);
 		o->mode = Addressing_Value;
 		o->mode = Addressing_Value;
 		o->type = t_untyped_uninit;
 		o->type = t_untyped_uninit;
-		error(node, "Use of --- outside of variable declaration");
+		error(node, "Global variables will always be zeroed if left unassigned, --- is disallowed");
 	case_end;
 	case_end;
 
 
 
 
@@ -11319,6 +11361,13 @@ gb_internal ExprKind check_expr_base_internal(CheckerContext *c, Operand *o, Ast
 		node->viral_state_flags |= de->expr->viral_state_flags;
 		node->viral_state_flags |= de->expr->viral_state_flags;
 
 
 		if (o->mode == Addressing_Invalid) {
 		if (o->mode == Addressing_Invalid) {
+			o->mode = Addressing_Invalid;
+			o->expr = node;
+			return kind;
+		} else if (o->mode == Addressing_Type) {
+ 			gbString str = expr_to_string(o->expr);
+			error(o->expr, "Cannot dereference '%s' because it is a type", str);
+
 			o->mode = Addressing_Invalid;
 			o->mode = Addressing_Invalid;
 			o->expr = node;
 			o->expr = node;
 			return kind;
 			return kind;

+ 11 - 10
src/check_stmt.cpp

@@ -418,7 +418,7 @@ gb_internal bool check_is_terminating(Ast *node, String const &label) {
 
 
 
 
 
 
-gb_internal Type *check_assignment_variable(CheckerContext *ctx, Operand *lhs, Operand *rhs) {
+gb_internal Type *check_assignment_variable(CheckerContext *ctx, Operand *lhs, Operand *rhs, String context_name) {
 	if (rhs->mode == Addressing_Invalid) {
 	if (rhs->mode == Addressing_Invalid) {
 		return nullptr;
 		return nullptr;
 	}
 	}
@@ -430,7 +430,7 @@ gb_internal Type *check_assignment_variable(CheckerContext *ctx, Operand *lhs, O
 
 
 	Ast *node = unparen_expr(lhs->expr);
 	Ast *node = unparen_expr(lhs->expr);
 
 
-	check_no_copy_assignment(*rhs, str_lit("assignment"));
+	check_no_copy_assignment(*rhs, context_name);
 
 
 	// NOTE(bill): Ignore assignments to '_'
 	// NOTE(bill): Ignore assignments to '_'
 	if (is_blank_ident(node)) {
 	if (is_blank_ident(node)) {
@@ -630,7 +630,7 @@ gb_internal Type *check_assignment_variable(CheckerContext *ctx, Operand *lhs, O
 		ctx->bit_field_bit_size = lhs_e->Variable.bit_field_bit_size;
 		ctx->bit_field_bit_size = lhs_e->Variable.bit_field_bit_size;
 	}
 	}
 
 
-	check_assignment(ctx, rhs, assignment_type, str_lit("assignment"));
+	check_assignment(ctx, rhs, assignment_type, context_name);
 
 
 	ctx->bit_field_bit_size = prev_bit_field_bit_size;
 	ctx->bit_field_bit_size = prev_bit_field_bit_size;
 
 
@@ -2418,7 +2418,7 @@ gb_internal void check_assign_stmt(CheckerContext *ctx, Ast *node) {
 
 
 		isize lhs_count = as->lhs.count;
 		isize lhs_count = as->lhs.count;
 		if (lhs_count == 0) {
 		if (lhs_count == 0) {
-			error(as->op, "Missing lhs in assignment statement");
+			error(as->op, "Missing LHS in assignment statement");
 			return;
 			return;
 		}
 		}
 
 
@@ -2451,7 +2451,7 @@ gb_internal void check_assign_stmt(CheckerContext *ctx, Ast *node) {
 			if (lhs_to_ignore[i]) {
 			if (lhs_to_ignore[i]) {
 				continue;
 				continue;
 			}
 			}
-			check_assignment_variable(ctx, &lhs_operands[i], &rhs_operands[i]);
+			check_assignment_variable(ctx, &lhs_operands[i], &rhs_operands[i], str_lit("assignment"));
 		}
 		}
 		if (lhs_count != rhs_count) {
 		if (lhs_count != rhs_count) {
 			error(as->lhs[0], "Assignment count mismatch '%td' = '%td'", lhs_count, rhs_count);
 			error(as->lhs[0], "Assignment count mismatch '%td' = '%td'", lhs_count, rhs_count);
@@ -2461,11 +2461,11 @@ gb_internal void check_assign_stmt(CheckerContext *ctx, Ast *node) {
 		// a += 1; // Single-sided
 		// a += 1; // Single-sided
 		Token op = as->op;
 		Token op = as->op;
 		if (as->lhs.count != 1 || as->rhs.count != 1) {
 		if (as->lhs.count != 1 || as->rhs.count != 1) {
-			error(op, "Assignment operation '%.*s' requires single-valued expressions", LIT(op.string));
+			error(op, "Assignment operator '%.*s' requires single-valued operands", LIT(op.string));
 			return;
 			return;
 		}
 		}
 		if (!gb_is_between(op.kind, Token__AssignOpBegin+1, Token__AssignOpEnd-1)) {
 		if (!gb_is_between(op.kind, Token__AssignOpBegin+1, Token__AssignOpEnd-1)) {
-			error(op, "Unknown Assignment operation '%.*s'", LIT(op.string));
+			error(op, "Unknown assignment operator '%.*s'", LIT(op.string));
 			return;
 			return;
 		}
 		}
 		Operand lhs = {Addressing_Invalid};
 		Operand lhs = {Addressing_Invalid};
@@ -2474,15 +2474,16 @@ gb_internal void check_assign_stmt(CheckerContext *ctx, Ast *node) {
 		ast_node(be, BinaryExpr, binary_expr);
 		ast_node(be, BinaryExpr, binary_expr);
 		be->op = op;
 		be->op = op;
 		be->op.kind = cast(TokenKind)(cast(i32)be->op.kind - (Token_AddEq - Token_Add));
 		be->op.kind = cast(TokenKind)(cast(i32)be->op.kind - (Token_AddEq - Token_Add));
-		 // NOTE(bill): Only use the first one will be used
+		// NOTE(bill): Only use the first one will be used
 		be->left  = as->lhs[0];
 		be->left  = as->lhs[0];
 		be->right = as->rhs[0];
 		be->right = as->rhs[0];
 
 
 		check_expr(ctx, &lhs, as->lhs[0]);
 		check_expr(ctx, &lhs, as->lhs[0]);
 		check_binary_expr(ctx, &rhs, binary_expr, nullptr, true);
 		check_binary_expr(ctx, &rhs, binary_expr, nullptr, true);
 		if (rhs.mode != Addressing_Invalid) {
 		if (rhs.mode != Addressing_Invalid) {
-			// NOTE(bill): Only use the first one will be used
-			check_assignment_variable(ctx, &lhs, &rhs);
+			be->op.string = substring(be->op.string, 0, be->op.string.len - 1);
+			rhs.expr = binary_expr;
+			check_assignment_variable(ctx, &lhs, &rhs, str_lit("assignment operation"));
 		}
 		}
 	}
 	}
 }
 }

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