Selaa lähdekoodia

Merge branch 'master' into macharena

Colin Davidson 2 kuukautta sitten
vanhempi
commit
389439ccb9
100 muutettua tiedostoa jossa 3043 lisäystä ja 1325 poistoa
  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/cgltf/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)
           ./odin check tests/benchmark -vet -strict-style -no-entry-point
 
@@ -63,11 +63,11 @@ jobs:
           gmake -C vendor/stb/src
           gmake -C vendor/cgltf/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)
           ./odin check tests/benchmark -vet -strict-style -no-entry-point
   ci:
@@ -75,7 +75,7 @@ jobs:
       fail-fast: false
       matrix:
         # 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 }}
     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
@@ -123,17 +123,17 @@ jobs:
       - name: Odin run -debug
         run: ./odin run examples/demo -debug
       - 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
-        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
-        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
-        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
-        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
-        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
         run: |
           cd tests/issues
@@ -141,43 +141,43 @@ jobs:
 
       - name: Run demo on WASI WASM32
         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
         if: matrix.os == 'macos-14'
 
       - 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
         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
         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
         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
         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
         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
         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
         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
         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
         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
         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:
     name: Windows Build, Check, and Test
@@ -208,38 +208,38 @@ jobs:
         shell: cmd
         run: |
           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
         shell: cmd
         run: |
           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
         shell: cmd
         run: |
           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
         shell: cmd
         run: |
           call "C:\Program Files\Microsoft Visual Studio\2022\Enterprise\VC\Auxiliary\Build\vcvars64.bat
-          odin test tests/core/normal.odin -file -all-packages -vet -strict-style -disallow-do -define:ODIN_TEST_FANCY=false -define:ODIN_TEST_FAIL_ON_BAD_MEMORY=true -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
         shell: cmd
         run: |
           call "C:\Program Files\Microsoft Visual Studio\2022\Enterprise\VC\Auxiliary\Build\vcvars64.bat
-          odin test tests/core/speed.odin -o:speed -file -all-packages -vet -strict-style -disallow-do -define:ODIN_TEST_FANCY=false -define:ODIN_TEST_FAIL_ON_BAD_MEMORY=true -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
         shell: cmd
         run: |
           call "C:\Program Files\Microsoft Visual Studio\2022\Enterprise\VC\Auxiliary\Build\vcvars64.bat
           copy vendor\lua\5.4\windows\*.dll .
-          odin test tests/vendor -all-packages -vet -strict-style -disallow-do -define:ODIN_TEST_FANCY=false -define:ODIN_TEST_FAIL_ON_BAD_MEMORY=true -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
         shell: cmd
         run: |
           call "C:\Program Files\Microsoft Visual Studio\2022\Enterprise\VC\Auxiliary\Build\vcvars64.bat
-          odin test tests/internal -all-packages -vet -strict-style -disallow-do -define:ODIN_TEST_FANCY=false -define:ODIN_TEST_FAIL_ON_BAD_MEMORY=true -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
         shell: cmd
         run: |
@@ -257,12 +257,6 @@ jobs:
           call "C:\Program Files\Microsoft Visual Studio\2022\Enterprise\VC\Auxiliary\Build\vcvars64.bat
           cd tests\documentation
           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
         shell: cmd
         run: |
@@ -299,25 +293,25 @@ jobs:
           make -C vendor/miniaudio/src
 
       - 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
-        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
         run: sudo apt-get install -y qemu-user qemu-user-static gcc-12-riscv64-linux-gnu libc6-riscv64-cross
 
       - 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
-        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
-        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
-        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
-        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
 demo.bin
 libLLVM*.so*
+*.a
 
 # shared collection
 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`.
 @builtin
 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 {
 		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`.
 @builtin
 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 {
 		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
 
 import "base:intrinsics"
-import "base:sanitizer"
+// import "base:sanitizer"
 
 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.capacity = uint(end - uintptr(block.base))
 
-	sanitizer.address_poison(block.base, block.capacity)
+	// sanitizer.address_poison(block.base, block.capacity)
 
 	// Should be zeroed
 	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) {
 	if block_to_free != nil {
 		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)
 	}
 }
@@ -87,7 +87,7 @@ alloc_from_memory_block :: proc(block: ^Memory_Block, min_size, alignment: uint)
 		return
 	}
 	data = block.base[block.used+alignment_offset:][:min_size]
-	sanitizer.address_unpoison(block.base[block.used:block.used+size])
+	// sanitizer.address_unpoison(block.base[block.used:block.used+size])
 	block.used += size
 	return
 }
@@ -167,7 +167,7 @@ arena_free_all :: proc(arena: ^Arena, loc := #caller_location) {
 	if arena.curr_block != nil {
 		intrinsics.mem_zero(arena.curr_block.base, arena.curr_block.used)
 		arena.curr_block.used = 0
-		sanitizer.address_poison(arena.curr_block.base, arena.curr_block.capacity)
+		// sanitizer.address_poison(arena.curr_block.base, arena.curr_block.capacity)
 	}
 	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
 					block.used = uint(new_end)
 					data = block.base[start:new_end]
-					sanitizer.address_unpoison(data)
+					// sanitizer.address_unpoison(data)
 					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)
 			amount_to_zero := block.used-temp.used
 			intrinsics.mem_zero(block.base[temp.used:], amount_to_zero)
-			sanitizer.address_poison(block.base[temp.used:block.capacity])
+			// sanitizer.address_poison(block.base[temp.used:block.capacity])
 			block.used = temp.used
 			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"
 _ :: 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) {
 	data:   [dynamic]T,
 	len:    uint,
@@ -13,18 +19,31 @@ Queue :: struct($T: typeid) {
 
 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 {
-	if q.data.allocator.procedure == nil {
-		q.data.allocator = allocator
-	}
 	clear(q)
+	q.data = transmute([dynamic]T)runtime.Raw_Dynamic_Array{
+		data = nil,
+		len = 0,
+		cap = 0,
+		allocator = allocator,
+	}
 	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 {
 	clear(q)
 	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
 }
 
-// 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 {
 	clear(q)
 	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
 }
 
-// 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)) {
 	delete(q.data)
 }
 
-// The length of the queue
+/*
+Return the length of the queue.
+*/
 len :: proc(q: $Q/Queue($T)) -> int {
 	return int(q.len)
 }
 
-// The current capacity of the queue
+/*
+Return the capacity of the queue.
+*/
 cap :: proc(q: $Q/Queue($T)) -> int {
 	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 {
 	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 {
 	if capacity > space(q^) {
-		return _grow(q, uint(capacity)) 
+		return _grow(q, uint(capacity))
 	}
 	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 {
-	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)
 	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]
 }
-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]
 }
 
-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)
 	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)
 	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 {
-	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 {
-	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) {
 	if space(q^) == 0 {
 		_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
 }
 
-// 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)  {
 	if space(q^) == 0 {
 		_grow(q) or_return
-	}	
+	}
 	q.offset = uint(q.offset - 1 + builtin.len(q.data)) % builtin.len(q.data)
 	q.len += 1
 	q.data[q.offset] = elem
 	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) {
-	assert(condition=q.len > 0, loc=loc)
+	when !ODIN_NO_BOUNDS_CHECK {
+		ensure(q.len > 0, "Queue is empty.", loc)
+	}
 	q.len -= 1
 	idx := (q.offset+uint(q.len))%builtin.len(q.data)
 	elem = q.data[idx]
 	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) {
 	if q.len > 0 {
 		q.len -= 1
@@ -169,15 +361,25 @@ pop_back_safe :: proc(q: ^$Q/Queue($T)) -> (elem: T, ok: bool) {
 	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) {
-	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]
 	q.offset = (q.offset+1)%builtin.len(q.data)
 	q.len -= 1
 	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) {
 	if q.len > 0 {
 		elem = q.data[q.offset]
@@ -188,13 +390,18 @@ pop_front_safe :: proc(q: ^$Q/Queue($T)) -> (elem: T, ok: bool) {
 	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)  {
 	n := uint(builtin.len(elems))
 	if space(q^) < int(n) {
 		_grow(q, q.len + n) or_return
 	}
-	
+
 	sz := uint(builtin.len(q.data))
 	insert_from := (q.offset + q.len) % sz
 	insert_to := n
@@ -207,19 +414,31 @@ push_back_elems :: proc(q: ^$Q/Queue($T), elems: ..T) -> (ok: bool, err: runtime
 	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) {
-	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 {
 		nu := uint(n)
 		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) {
-	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 {
 		q.len -= uint(n)
 	}
@@ -231,9 +450,14 @@ append_elem  :: push_back
 append_elems :: push_back_elems
 push   :: 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)) {
 	q.len = 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)
 	}
 
-	t := &Tokenizer{}
+	t := new(Tokenizer)
 	init(t, string(data), path, error_handler)
 
 	doc = new(Document)
@@ -403,6 +403,7 @@ destroy :: proc(doc: ^Document) {
 	}
 	delete(doc.strings_to_free)
 
+	free(doc.tokenizer)
 	free(doc)
 }
 

+ 2 - 2
core/flags/constants.odin

@@ -19,7 +19,7 @@ SUBTAG_NAME       :: "name"
 SUBTAG_POS        :: "pos"
 SUBTAG_REQUIRED   :: "required"
 SUBTAG_HIDDEN     :: "hidden"
-SUBTAG_VARIADIC   :: "variadic"
+SUBTAG_MANIFOLD   :: "manifold"
 SUBTAG_FILE       :: "file"
 SUBTAG_PERMS      :: "perms"
 SUBTAG_INDISTINCT :: "indistinct"
@@ -28,7 +28,7 @@ TAG_USAGE         :: "usage"
 
 UNDOCUMENTED_FLAG :: "<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_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
 
 
+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:
 
 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.
 - `hidden`: hide this flag from the usage documentation.
 - `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.
 - `perms`: for `os.Handle` types, file open permissions.
 - `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`.
 
 
-`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`.
@@ -160,7 +172,7 @@ at parse time.
 	--flag
 	--flag=argument
 	--flag argument
-	--flag argument repeating-argument
+	--flag argument (manifold-argument)
 
 `-flag` may also be substituted for `--flag`.
 

+ 1 - 1
core/flags/errors.odin

@@ -4,7 +4,7 @@ import "core:os"
 
 Parse_Error_Reason :: enum {
 	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,
 	// The underlying type does not support the string value it is being set to.
 	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."`,
 
-		// (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."`,
 		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

+ 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)
 
 	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.
-			field = reflect.struct_field_by_name(T, INTERNAL_VARIADIC_FLAG)
+			field = reflect.struct_field_by_name(T, INTERNAL_OVERFLOW_FLAG)
 		} else {
 			return Parse_Error {
 				.Extra_Positional,
@@ -117,8 +117,8 @@ set_unix_flag :: proc(model: ^$T, parser: ^Parser, name: string) -> (future_args
 	case runtime.Type_Info_Dynamic_Array:
 		future_args = 1
 		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.
 				if value, value_ok := strconv.parse_u64_of_base(length, 10); value_ok {
 					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 `--`.
 				// Everything from now on will be treated as an argument.
 				future_args = max(int)
-				current_flag = INTERNAL_VARIADIC_FLAG
+				current_flag = INTERNAL_OVERFLOW_FLAG
 				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 {
 			case runtime.Type_Info_Map:
 				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.",
 				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)
 
 			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 {
 				fmt.assertf(value > 0,
 					"%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,
-					"%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 {
 			case runtime.Type_Info_Dynamic_Array:
 				fmt.assertf(style != .Odin,
 					"%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:
 				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 {
 	// Odin-style: `-flag`, `-flag:option`, `-map:key=value`
 	Odin,
-	// UNIX-style: `-flag` or `--flag`, `--flag=argument`, `--flag argument repeating-argument`
+	// UNIX-style: `-flag` or `--flag`, `--flag=argument`, `--flag argument (manifold-argument)`
 	Unix,
 }
 
@@ -61,7 +61,7 @@ parse :: proc(
 		}
 
 	case .Unix:
-		// Support for `-flag argument (repeating-argument ...)`
+		// Support for `-flag argument (manifold-argument ...)`
 		future_args: int
 		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_required: bool,
 		is_boolean: bool,
-		is_variadic: bool,
-		variadic_length: int,
+		is_manifold: bool,
+		manifold_length: int,
 	}
 
 	//
 	// POSITIONAL+REQUIRED, POSITIONAL, REQUIRED, NON_REQUIRED+NON_POSITIONAL, ...
 	//
 	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
-		} else if j.name == INTERNAL_VARIADIC_FLAG {
+		} else if j.name == INTERNAL_OVERFLOW_FLAG {
 			return .Less
 		}
 
@@ -120,10 +120,10 @@ write_usage :: proc(out: io.Writer, data_type: typeid, program: string = "", sty
 				flag.is_required = true
 				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 {
-					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:
 			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",
 						specific_type_info.elem.id,
 						requirement_spec)
 				} else {
 					flag.type_description = fmt.tprintf("<%v, %i at once>%s",
 						specific_type_info.elem.id,
-						flag.variadic_length,
+						flag.manifold_length,
 						requirement_spec)
 				}
 			} 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)
 		} else if flag.is_boolean {
 			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)
 
 		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
 			}
 
 			strings.write_byte(&builder, ' ')
 
-			if flag.name == INTERNAL_VARIADIC_FLAG {
+			if flag.name == INTERNAL_OVERFLOW_FLAG {
 				strings.write_string(&builder, "...")
 				continue
 			}
@@ -252,7 +252,7 @@ write_usage :: proc(out: io.Writer, data_type: typeid, program: string = "", sty
 
 		strings.write_byte(&builder, '\t')
 
-		if flag.name == INTERNAL_VARIADIC_FLAG {
+		if flag.name == INTERNAL_OVERFLOW_FLAG {
 			strings.write_string(&builder, flag.type_description)
 		} else {
 			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,
-}
-
-
+	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) {
 			internal_swap(dest, x)
-			return nil
+			return internal_clamp(dest)
 		}
 		internal_swap(x, y)
 	}
 
 	internal_swap(dest, x)
-	return err
+	return internal_clamp(dest)
 }
 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
 	}
 
-	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
 		
 		f := &Int{}
+		defer internal_int_destroy(f)
+		
 		internal_int_gcd(f, &z.a, &z.b) or_return
 		if !internal_int_equals_digit(f, 1) {
 			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
-	defer if has_sign {
-		f = -builtin.abs(f)
-	}
 	
 	exp := alen - blen
 	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) {
 		exact = false
 	}
+	if has_sign {
+		f = -builtin.abs(f)
+	}
 	return
 }
 

Tiedoston diff-näkymää rajattu, sillä se on liian suuri
+ 268 - 162
core/mem/allocators.odin


+ 4 - 4
core/mem/rollback_stack_allocator.odin

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

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

@@ -10,7 +10,7 @@
 package mem_tlsf
 
 import "base:intrinsics"
-import "base:sanitizer"
+// import "base:sanitizer"
 import "base:runtime"
 
 // 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
 			}
 
-			sanitizer.address_poison(new_pool_buf)
+			// sanitizer.address_poison(new_pool_buf)
 
 			// Allocate a new link in the `control.pool` tracking structure.
 			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)
 }
 
-@(private, require_results, no_sanitize_address)
+@(private, require_results)
 alloc_bytes :: proc(control: ^Allocator, size: uint, align: uint) -> (res: []byte, err: runtime.Allocator_Error) {
 	res, err = alloc_bytes_non_zeroed(control, size, align)
 	if err == nil {
@@ -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) {
 	assert(control != nil)
 	// `size` is currently ignored
@@ -276,7 +277,7 @@ free_with_size :: proc(control: ^Allocator, ptr: rawptr, size: uint) {
 
 	block := block_from_ptr(ptr)
 	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 = block_merge_prev(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)
 	res = ([^]byte)(ptr)[:new_size]
-	sanitizer.address_unpoison(res)
+	// sanitizer.address_unpoison(res)
 
 	if 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)
 }
 
-@(private, require_results, no_sanitize_address)
+@(private, require_results)
 align_up :: proc(x, align: uint) -> (aligned: uint) {
 	assert(0 == (align & (align - 1)), "must align to a power of two")
 	return (x + (align - 1)) &~ (align - 1)
 }
 
-@(private, require_results, no_sanitize_address)
+@(private, require_results)
 align_down :: proc(x, align: uint) -> (aligned: uint) {
 	assert(0 == (align & (align - 1)), "must align to a power of two")
 	return x - (x & (align - 1))
 }
 
-@(private, require_results, no_sanitize_address)
+@(private, require_results)
 align_ptr :: proc(ptr: rawptr, align: uint) -> (aligned: rawptr) {
 	assert(0 == (align & (align - 1)), "must align to a power of two")
 	align_mask := uintptr(align) - 1
@@ -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.
-@(private, require_results, no_sanitize_address)
+@(private, require_results)
 adjust_request_size :: proc(size, align: uint) -> (adjusted: uint) {
 	if size == 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.
-@(private, require_results, no_sanitize_address)
+@(private, require_results)
 adjust_request_size_with_err :: proc(size, align: uint) -> (adjusted: uint, err: runtime.Allocator_Error) {
 	if size == 0 {
 		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
 // 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) {
 	if size < SMALL_BLOCK_SIZE {
 		// Store small blocks in first list.
@@ -550,7 +551,7 @@ mapping_insert :: proc(size: uint) -> (fl, sl: i32) {
 	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) {
 	rounded = 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)
-@(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) {
 	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_mark_as_used(block)
 		res = ([^]byte)(block_to_ptr(block))[:size]
-		sanitizer.address_unpoison(res)
+		// sanitizer.address_unpoison(res)
 	}
 	return
 }

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

@@ -3,7 +3,7 @@ package mem_virtual
 import "core:mem"
 import "core:sync"
 
-import "base:sanitizer"
+// import "base:sanitizer"
 
 Arena_Kind :: enum uint {
 	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 {
 		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
 }
 
@@ -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.total_used     = 0
 	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
 }
 
@@ -82,7 +82,7 @@ arena_init_buffer :: proc(arena: ^Arena, buffer: []byte) -> (err: Allocator_Erro
 
 	arena.kind = .Buffer
 
-	sanitizer.address_poison(buffer[:])
+	// sanitizer.address_poison(buffer[:])
 
 	block_base := raw_data(buffer)
 	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
 	}
 
-	sanitizer.address_unpoison(data)
+	// sanitizer.address_unpoison(data)
 	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])
 		}
 		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
 	} else if pos == 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.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)
 	}
 }
@@ -219,9 +219,9 @@ arena_free_all :: proc(arena: ^Arena, loc := #caller_location) {
 		if arena.curr_block != nil {
 			curr_block_used := int(arena.curr_block.used)
 			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)
-			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
 	case .Static, .Buffer:
@@ -349,7 +349,7 @@ arena_allocator_proc :: proc(allocator_data: rawptr, mode: mem.Allocator_Mode,
 			if size < old_size {
 				// shrink data in-place
 				data = old_data[:size]
-				sanitizer.address_poison(old_data[size:old_size])
+				// sanitizer.address_poison(old_data[size:old_size])
 				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
 					arena.total_used += block.used - prev_used
 					data = block.base[start:new_end]
-					sanitizer.address_unpoison(data)
+					// sanitizer.address_unpoison(data)
 					return
 				}
 			}
@@ -374,7 +374,7 @@ arena_allocator_proc :: proc(allocator_data: rawptr, mode: mem.Allocator_Mode,
 			return
 		}
 		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
 	case .Query_Features:
 		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 "base:intrinsics"
-import "base:sanitizer"
+// import "base:sanitizer"
 import "base:runtime"
 _ :: runtime
 
@@ -22,7 +22,7 @@ reserve :: proc "contextless" (size: uint) -> (data: []byte, err: Allocator_Erro
 
 @(no_sanitize_address)
 commit :: proc "contextless" (data: rawptr, size: uint) -> Allocator_Error {
-	sanitizer.address_unpoison(data, size)
+	// sanitizer.address_unpoison(data, size)
 	return _commit(data, size)
 }
 
@@ -35,13 +35,13 @@ reserve_and_commit :: proc "contextless" (size: uint) -> (data: []byte, err: All
 
 @(no_sanitize_address)
 decommit :: proc "contextless" (data: rawptr, size: uint) {
-	sanitizer.address_poison(data, size)
+	// sanitizer.address_poison(data, size)
 	_decommit(data, size)
 }
 
 @(no_sanitize_address)
 release :: proc "contextless" (data: rawptr, size: uint) {
-	sanitizer.address_unpoison(data, size)
+	// sanitizer.address_unpoison(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]
 	block.used += size
-	sanitizer.address_unpoison(data)
+	// sanitizer.address_unpoison(data)
 	return
 }
 

+ 4 - 0
core/net/common.odin

@@ -64,6 +64,7 @@ Network_Error :: union #shared_nil {
 	UDP_Recv_Error,
 	Shutdown_Error,
 	Interfaces_Error,
+	Socket_Info_Error,
 	Socket_Option_Error,
 	Set_Blocking_Error,
 	Parse_Endpoint_Error,
@@ -260,6 +261,9 @@ DNS_Configuration :: struct {
 	resolv_conf: 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,
 	// so we don't have to do it each call.
 	name_servers:       []Endpoint,

+ 29 - 47
core/net/dns.odin

@@ -22,69 +22,43 @@ package net
 		Haesbaert:       Security fixes
 */
 
+@(require) import "base:runtime"
 import "core:mem"
 import "core:strings"
 import "core:time"
 import "core:os"
 import "core:math/rand"
-/*
-	Default configuration for DNS resolution.
-*/
+@(require) import "core:sync"
+
+dns_config_initialized: sync.Once
 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 {
-	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 {
 	#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() {
-	/*
-		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.
 	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`.
 */
 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)
 }
 
@@ -219,6 +196,9 @@ get_dns_records_from_os :: proc(hostname: string, type: DNS_Record_Type, allocat
 	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) {
+	when ODIN_OS == .Windows {
+		sync.once_do(&dns_config_initialized, init_dns_configuration)
+	}
 	context.allocator = allocator
 
 	if type != .SRV {
@@ -440,6 +420,8 @@ load_hosts :: proc(hosts_file_path: string, allocator := context.allocator) -> (
 		splits := strings.fields(line)
 		defer delete(splits)
 
+		(len(splits) >= 2) or_continue
+
 		ip_str := splits[0]
 		addr := parse_address(ip_str)
 		if addr == nil {
@@ -886,4 +868,4 @@ parse_response :: proc(response: []u8, filter: DNS_Record_Type = nil, allocator
 	xid = hdr.id
 
 	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[:])
-}
+}

+ 17 - 0
core/net/errors.odin

@@ -246,6 +246,23 @@ Shutdown_Error :: enum i32 {
 	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 {
 	None,
 	// 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 {
 	#partial switch posix.errno() {
 	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 {
 	assert(errno != nil)
 	_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 {
 	assert(errno != nil)
 	_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) {
 	_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 {
 	#partial switch win.System_Error(win.WSAGetLastError()) {
 	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.
 */
-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)
 }
 
+/*
+	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) {
 	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)
-_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_len := posix.socklen_t(size_of(addr))
 	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
 	}
 

+ 16 - 2
core/net/socket_freebsd.odin

@@ -140,12 +140,26 @@ _listen_tcp :: proc(interface_endpoint: Endpoint, backlog := 1000) -> (socket: T
 }
 
 @(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
 
 	errno := freebsd.getsockname(cast(Fd)any_socket_to_socket(sock), &sockaddr)
 	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
 	}
 

+ 15 - 2
core/net/socket_linux.odin

@@ -218,11 +218,24 @@ _listen_tcp :: proc(endpoint: Endpoint, backlog := 1000) -> (socket: TCP_Socket,
 }
 
 @(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
 	errno := linux.getsockname(_unwrap_os_socket(sock), &addr)
 	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
 	}
 

+ 16 - 2
core/net/socket_windows.odin

@@ -177,11 +177,25 @@ _listen_tcp :: proc(interface_endpoint: Endpoint, backlog := 1000) -> (socket: T
 }
 
 @(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
 	sockaddrlen := c.int(size_of(sockaddr))
 	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
 	}
 

+ 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)
 		p.expr_level += 1
 		expr := parse_expr(p, false)
+		skip_possible_newline(p)
 		p.expr_level -= 1
 		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
 		for p.curr_tok.kind != .Close_Brace && p.curr_tok.kind != .EOF {
+			docs := p.lead_comment
+
 			name := parse_ident(p)
 			expect_token(p, .Colon)
 			type := parse_type(p)
@@ -2932,6 +2935,7 @@ parse_operand :: proc(p: ^Parser, lhs: bool) -> ^ast.Expr {
 			if p.curr_tok.kind == .String {
 				tag = expect_token(p, .String)
 			}
+			ok := allow_token(p, .Comma)
 
 			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.bit_size = bit_size
 			field.tag      = tag
+			field.docs     = docs
+			field.comments = p.line_comment
 
 			append(&fields, field)
 
-			allow_token(p, .Comma) or_break
+			if !ok {
+				break
+			}
 		}
 
 		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:
 				x := expr
 				cond := parse_expr(p, lhs)
+				skip_possible_newline(p)
 				else_tok := expect_token(p, .Else)
 				y := parse_expr(p, lhs)
 				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
 	}
 
-	if !is_using && is_blank_ident(import_name) {
-		error(p, import_name.pos, "illegal import name: '_'")
-	}
-
 	path := expect_token_after(p, .String, "import")
 
 	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
 // NOTE: the value will be allocated with the supplied allocator
 @(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 == "" {
 		return
 	}
@@ -29,17 +29,54 @@ lookup_env :: proc(key: string, allocator := context.allocator) -> (value: strin
 	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
 // 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: the value will be allocated with the supplied allocator
 @(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)
 	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 :: proc(key, value: string) -> Error {
 	k := win32.utf8_to_wstring(key)

+ 4 - 0
core/os/errors.odin

@@ -35,6 +35,9 @@ General_Error :: enum u32 {
 
 	File_Is_Pipe,
 	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 .File_Is_Pipe:      return "file is pipe"
 		case .Not_Dir:           return "file is not directory"
+		case .Env_Var_Not_Found: return "environment variable not found"
 		}
 	case io.Error:
 		switch e {

+ 53 - 0
core/os/os.odin

@@ -4,6 +4,7 @@ import "base:intrinsics"
 import "base:runtime"
 import "core:io"
 import "core:strconv"
+import "core:strings"
 import "core:unicode/utf8"
 
 
@@ -210,3 +211,55 @@ heap_free   :: runtime.heap_free
 processor_core_count :: proc() -> int {
 	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
 
 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
 // To distinguish between an empty value and an unset value, use lookup_env
 // NOTE: the value will be allocated with the supplied allocator
 @(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)
 	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
 // Otherwise the returned value will be empty and the boolean will be false
 // NOTE: the value will be allocated with the supplied allocator
 @(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
 // Returns Error on failure
 set_env :: proc(key, value: string) -> Error {
@@ -45,4 +68,55 @@ environ :: proc(allocator: runtime.Allocator) -> ([]string, Error) {
 	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
 }
 
-_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 {
 		_build_env()
 	}
@@ -53,6 +53,23 @@ _lookup_env :: proc(key: string, allocator: runtime.Allocator) -> (value: string
 	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 {
 	if intrinsics.atomic_load_explicit(&_org_env_begin, .Acquire) == 0 {
 		_build_env()

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

@@ -7,7 +7,7 @@ import "base:runtime"
 import "core:strings"
 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 == "" {
 		return
 	}
@@ -26,6 +26,36 @@ _lookup_env :: proc(key: string, allocator: runtime.Allocator) -> (value: string
 	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) {
 	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)
-_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 {
 		return
 	}
@@ -79,6 +79,34 @@ _lookup_env :: proc(key: string, allocator: runtime.Allocator) -> (value: string
 	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)
 _set_env :: proc(key, value: string) -> (err: Error) {
 	build_env() or_return

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

@@ -4,7 +4,7 @@ package os2
 import win32 "core:sys/windows"
 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 == "" {
 		return
 	}
@@ -36,6 +36,36 @@ _lookup_env :: proc(key: string, allocator: runtime.Allocator) -> (value: string
 	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 {
 	temp_allocator := TEMP_ALLOCATOR_GUARD({})
 	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,
 
 	No_HOME_Variable,
-	Wordexp_Failed,
+	Env_Var_Not_Found,
 
 	Unsupported,
 }
@@ -77,7 +77,7 @@ error_string :: proc(ferr: Error) -> string {
 		case .Unsupported:            return "unsupported"
 		case .Pattern_Has_Separator:  return "pattern has separator"
 		case .No_HOME_Variable:       return "no $HOME variable"
-		case .Wordexp_Failed:         return "posix.wordexp was unable to expand"
+		case .Env_Var_Not_Found:      return "environment variable not found"
 		}
 	case io.Error:
 		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
 }
 
+@(no_sanitize_memory)
 _file_size :: proc(f: ^File_Impl) -> (n: i64, err: Error) {
 	// TODO: Identify 0-sized "pseudo" files and return No_Size. This would
 	//       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 "core:encoding/ini"
 import "core:strings"
-import "core:sys/posix"
 
 _user_cache_dir :: proc(allocator: runtime.Allocator) -> (dir: string, err: Error) {
 	#partial switch ODIN_OS {
@@ -169,14 +168,7 @@ _xdg_user_dirs_lookup :: proc(xdg_key: string, allocator: runtime.Allocator) ->
 
 	for k, v in ini.iterate(&it) {
 		if k == xdg_key {
-			we: posix.wordexp_t
-			defer posix.wordfree(&we)
-
-			if _err := posix.wordexp(strings.clone_to_cstring(v, temp_allocator), &we, nil); _err != nil || we.we_wordc != 1 {
-				return "", .Wordexp_Failed
-			}
-
-			return strings.clone_from_cstring(we.we_wordv[0], allocator)
+			return replace_environment_placeholders(v, allocator), nil
 		}
 	}
 	return

+ 37 - 3
core/os/os_darwin.odin

@@ -1090,9 +1090,10 @@ flush :: proc(fd: Handle) -> Error {
 }
 
 @(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)
 	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)
 	if cstr == nil {
 		return "", false
@@ -1101,11 +1102,39 @@ lookup_env :: proc(key: string, allocator := context.allocator) -> (value: strin
 }
 
 @(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)
 	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 {
 	runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD()
 	key_cstring := strings.clone_to_cstring(key, context.temp_allocator)
@@ -1231,7 +1260,7 @@ _processor_core_count :: proc() -> int {
 	return 1
 }
 
-@(require_results)
+@(private, require_results)
 _alloc_command_line_arguments :: proc() -> []string {
 	res := make([]string, len(runtime.args__))
 	for _, i in res {
@@ -1240,6 +1269,11 @@ _alloc_command_line_arguments :: proc() -> []string {
 	return res
 }
 
+@(private, fini)
+_delete_command_line_arguments :: proc() {
+	delete(args)
+}
+
 socket :: proc(domain: int, type: int, protocol: int) -> (Socket, Error) {
 	result := _unix_socket(c.int(domain), c.int(type), c.int(protocol))
 	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
 }
 
-@(private, require_results)
+@(private, require_results, no_sanitize_memory)
 _stat :: proc(path: string) -> (OS_Stat, Error) {
 	runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD()
 	cstr := strings.clone_to_cstring(path, context.temp_allocator)
@@ -674,7 +674,7 @@ _stat :: proc(path: string) -> (OS_Stat, Error) {
 	return s, nil
 }
 
-@(private, require_results)
+@(private, require_results, no_sanitize_memory)
 _lstat :: proc(path: string) -> (OS_Stat, Error) {
 	runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD()
 	cstr := strings.clone_to_cstring(path, context.temp_allocator)
@@ -688,7 +688,7 @@ _lstat :: proc(path: string) -> (OS_Stat, Error) {
 	return s, nil
 }
 
-@(private, require_results)
+@(private, require_results, no_sanitize_memory)
 _fstat :: proc(fd: Handle) -> (OS_Stat, Error) {
 	s: OS_Stat = ---
 	result := _unix_fstat(fd, &s)
@@ -827,10 +827,10 @@ access :: proc(path: string, mask: int) -> (bool, Error) {
 }
 
 @(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)
-
 	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)
 	if cstr == nil {
 		return "", false
@@ -839,11 +839,39 @@ lookup_env :: proc(key: string, allocator := context.allocator) -> (value: strin
 }
 
 @(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)
 	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)
 get_current_directory :: proc(allocator := context.allocator) -> string {
 	context.allocator = allocator
@@ -936,7 +964,7 @@ _processor_core_count :: proc() -> int {
 }
 
 
-@(require_results)
+@(private, require_results)
 _alloc_command_line_arguments :: proc() -> []string {
 	res := make([]string, len(runtime.args__))
 	for arg, i in runtime.args__ {
@@ -944,3 +972,8 @@ _alloc_command_line_arguments :: proc() -> []string {
 	}
 	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
 args := _alloc_command_line_arguments()
 
-@(require_results)
+@(private, require_results)
 _alloc_command_line_arguments :: proc() -> []string {
 	res := make([]string, len(runtime.args__))
 	for arg, i in runtime.args__ {
@@ -325,7 +325,12 @@ _alloc_command_line_arguments :: proc() -> []string {
 	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) {
 	runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD()
 	cstr := strings.clone_to_cstring(path, context.temp_allocator)
@@ -339,7 +344,7 @@ _stat :: proc(path: string) -> (OS_Stat, Error) {
 	return s, nil
 }
 
-@(private, require_results)
+@(private, require_results, no_sanitize_memory)
 _lstat :: proc(path: string) -> (OS_Stat, Error) {
 	runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD()
 	cstr := strings.clone_to_cstring(path, context.temp_allocator)
@@ -353,7 +358,7 @@ _lstat :: proc(path: string) -> (OS_Stat, Error) {
 	return s, nil
 }
 
-@(private, require_results)
+@(private, require_results, no_sanitize_memory)
 _fstat :: proc(fd: Handle) -> (OS_Stat, Error) {
 	// deliberately uninitialized
 	s: OS_Stat = ---
@@ -463,9 +468,10 @@ access :: proc(path: string, mask: int) -> (bool, Error) {
 }
 
 @(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)
 	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)
 	if cstr == nil {
 		return "", false
@@ -474,11 +480,40 @@ lookup_env :: proc(key: string, allocator := context.allocator) -> (value: strin
 }
 
 @(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)
 	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)
 _processor_core_count :: proc() -> int {
 	info: haiku.system_info

+ 22 - 2
core/os/os_js.odin

@@ -250,6 +250,26 @@ current_thread_id :: proc "contextless" () -> int {
 	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
-}
+}
+
+@(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
 }
 
-@(require_results)
+@(require_results, no_sanitize_memory)
 file_size :: proc(fd: Handle) -> (i64, Error) {
 	// deliberately uninitialized; the syscall fills this buffer for us
 	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
 }
 
-@(private, require_results)
+@(private, require_results, no_sanitize_memory)
 _stat :: proc(path: string) -> (OS_Stat, Error) {
 	runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD()
 	cstr := strings.clone_to_cstring(path, context.temp_allocator)
@@ -808,7 +808,7 @@ _stat :: proc(path: string) -> (OS_Stat, Error) {
 	return s, nil
 }
 
-@(private, require_results)
+@(private, require_results, no_sanitize_memory)
 _lstat :: proc(path: string) -> (OS_Stat, Error) {
 	runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD()
 	cstr := strings.clone_to_cstring(path, context.temp_allocator)
@@ -822,7 +822,7 @@ _lstat :: proc(path: string) -> (OS_Stat, Error) {
 	return s, nil
 }
 
-@(private, require_results)
+@(private, require_results, no_sanitize_memory)
 _fstat :: proc(fd: Handle) -> (OS_Stat, Error) {
 	// deliberately uninitialized; the syscall fills this buffer for us
 	s: OS_Stat = ---
@@ -946,7 +946,7 @@ access :: proc(path: string, mask: int) -> (bool, Error) {
 }
 
 @(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)
 	path_str := strings.clone_to_cstring(key, context.temp_allocator)
 	// 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)
-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)
 	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 {
 	runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD()
 	key_cstring := strings.clone_to_cstring(key, context.temp_allocator)
@@ -1069,7 +1097,7 @@ _processor_core_count :: proc() -> int {
 	return int(_unix_get_nprocs())
 }
 
-@(require_results)
+@(private, require_results)
 _alloc_command_line_arguments :: proc() -> []string {
 	res := make([]string, len(runtime.args__))
 	for arg, i in runtime.args__ {
@@ -1078,6 +1106,11 @@ _alloc_command_line_arguments :: proc() -> []string {
 	return res
 }
 
+@(private, fini)
+_delete_command_line_arguments :: proc() {
+	delete(args)
+}
+
 @(require_results)
 socket :: proc(domain: int, type: int, protocol: int) -> (Socket, Error) {
 	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
 }
 
-@(private, require_results)
+@(private, require_results, no_sanitize_memory)
 _stat :: proc(path: string) -> (OS_Stat, Error) {
 	runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD()
 	cstr := strings.clone_to_cstring(path, context.temp_allocator)
@@ -736,7 +736,7 @@ _stat :: proc(path: string) -> (OS_Stat, Error) {
 	return s, nil
 }
 
-@(private, require_results)
+@(private, require_results, no_sanitize_memory)
 _lstat :: proc(path: string) -> (OS_Stat, Error) {
 	runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD()
 	cstr := strings.clone_to_cstring(path, context.temp_allocator)
@@ -750,7 +750,7 @@ _lstat :: proc(path: string) -> (OS_Stat, Error) {
 	return s, nil
 }
 
-@(private, require_results)
+@(private, require_results, no_sanitize_memory)
 _fstat :: proc(fd: Handle) -> (OS_Stat, Error) {
 	s: OS_Stat = ---
 	result := _unix_fstat(fd, &s)
@@ -874,10 +874,10 @@ access :: proc(path: string, mask: int) -> (bool, Error) {
 }
 
 @(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)
-
 	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)
 	if cstr == nil {
 		return "", false
@@ -886,11 +886,39 @@ lookup_env :: proc(key: string, allocator := context.allocator) -> (value: strin
 }
 
 @(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)
 	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)
 get_current_directory :: proc(allocator := context.allocator) -> string {
 	context.allocator = allocator
@@ -986,7 +1014,7 @@ _processor_core_count :: proc() -> int {
 	return 1
 }
 
-@(require_results)
+@(private, require_results)
 _alloc_command_line_arguments :: proc() -> []string {
 	res := make([]string, len(runtime.args__))
 	for arg, i in runtime.args__ {
@@ -994,3 +1022,8 @@ _alloc_command_line_arguments :: proc() -> []string {
 	}
 	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
 }
 
-@(private, require_results)
+@(private, require_results, no_sanitize_memory)
 _stat :: proc(path: string) -> (OS_Stat, Error) {
 	runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD()
 	cstr := strings.clone_to_cstring(path, context.temp_allocator)
@@ -653,7 +653,7 @@ _stat :: proc(path: string) -> (OS_Stat, Error) {
 	return s, nil
 }
 
-@(private, require_results)
+@(private, require_results, no_sanitize_memory)
 _lstat :: proc(path: string) -> (OS_Stat, Error) {
 	runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD()
 	cstr := strings.clone_to_cstring(path, context.temp_allocator)
@@ -667,7 +667,7 @@ _lstat :: proc(path: string) -> (OS_Stat, Error) {
 	return s, nil
 }
 
-@(private, require_results)
+@(private, require_results, no_sanitize_memory)
 _fstat :: proc(fd: Handle) -> (OS_Stat, Error) {
 	// deliberately uninitialized
 	s: OS_Stat = ---
@@ -787,9 +787,10 @@ access :: proc(path: string, mask: int) -> (bool, Error) {
 }
 
 @(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)
 	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)
 	if cstr == nil {
 		return "", false
@@ -798,11 +799,39 @@ lookup_env :: proc(key: string, allocator := context.allocator) -> (value: strin
 }
 
 @(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)
 	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)
 get_current_directory :: proc(allocator := context.allocator) -> string {
 	context.allocator = allocator
@@ -885,7 +914,7 @@ _processor_core_count :: proc() -> int {
 	return int(_sysconf(_SC_NPROCESSORS_ONLN))
 }
 
-@(require_results)
+@(private, require_results)
 _alloc_command_line_arguments :: proc() -> []string {
 	res := make([]string, len(runtime.args__))
 	for arg, i in runtime.args__ {
@@ -893,3 +922,8 @@ _alloc_command_line_arguments :: proc() -> []string {
 	}
 	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()
 
-@(require_results)
+@(private, require_results)
 _alloc_command_line_arguments :: proc() -> (args: []string) {
 	args = make([]string, len(runtime.args__))
 	for &arg, i in args {
@@ -36,6 +36,11 @@ _alloc_command_line_arguments :: proc() -> (args: []string) {
 	return
 }
 
+@(private, fini)
+_delete_command_line_arguments :: proc() {
+	delete(args)
+}
+
 // WASI works with "preopened" directories, the environment retrieves directories
 // (for example with `wasmtime --dir=. module.wasm`) and those given directories
 // are the only ones accessible by the application.
@@ -239,3 +244,27 @@ exit :: proc "contextless" (code: int) -> ! {
 	runtime._cleanup_runtime_contextless()
 	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 {
 	arg_count: i32
 	arg_list_ptr := win32.CommandLineToArgvW(win32.GetCommandLineW(), &arg_count)
@@ -215,6 +215,14 @@ _alloc_command_line_arguments :: proc() -> []string {
 	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
 	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 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)
 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: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.
 */
@@ -75,6 +83,8 @@ Raw_Chan :: struct {
 	r_waiting:       int,  // guarded by `mutex`
 	w_waiting:       int,  // guarded by `mutex`
 
+	did_read: bool, // lets a sender know if the value was read
+
 	// Buffered
 	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:
 - the channel is unbuffered
 - 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**
 - `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:
 - the channel is unbuffered
 - 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**
 - `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:
 - the channel is unbuffered
 - 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
 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
 		}
 
+		c.did_read = false
+		defer c.did_read = false
+
 		mem.copy(c.unbuffered_data, msg_in, int(c.msg_size))
+
 		c.w_waiting += 1
+
 		if c.r_waiting > 0 {
 			sync.signal(&c.r_cond)
 		}
+
 		sync.wait(&c.w_cond, &c.mutex)
+
+		if c.closed && !c.did_read {
+			return false
+		}
+
 		ok = true
 	}
 	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:
 - the channel is unbuffered
 - 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
 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
 		sync.guard(&c.mutex)
 
-		for !c.closed &&
-			c.w_waiting == 0 {
+		for !c.closed && c.w_waiting == 0 {
 			c.r_waiting += 1
 			sync.wait(&c.r_cond, &c.mutex)
 			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))
 		c.w_waiting -= 1
 
+		c.did_read = true
 		sync.signal(&c.w_cond)
 		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
 		sync.guard(&c.mutex)
 
-		if c.closed {
+		if c.closed || c.r_waiting - c.w_waiting <= 0 {
 			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
 		sync.guard(&c.mutex)
 
-		if c.closed || c.w_waiting == 0 {
+		if c.closed || c.w_waiting - c.r_waiting <= 0 {
 			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**
 - `c`: The channel
@@ -1067,7 +1091,7 @@ can_recv :: proc "contextless" (c: ^Raw_Chan) -> bool {
 	if is_buffered(c) {
 		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
 
 **Returns**
-- `true` if a message can be send, `false` otherwise
+- `true` if a message can be sent, `false` otherwise
 
 Example:
 
@@ -1102,18 +1126,30 @@ can_send :: proc "contextless" (c: ^Raw_Chan) -> bool {
 	if is_buffered(c) {
 		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
 (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`.
+If the message is nil, corresponding send channel will be skipped.
 
 **Inputs**
 - `recv`: A slice of channels to read from
@@ -1145,18 +1181,18 @@ Example:
 		// where the value from the read should be stored
 		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("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("RECEIVED VALUE ", received_value)
 
 		// closing of a channel also affects the select operation
 		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)
 	}
 
@@ -1170,7 +1206,7 @@ Output:
 
 */
 @(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 {
 		idx:     int, // local to the slice that was given
 		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)
 	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

+ 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 {
 	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))
 }
 
+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 {
 	return cast(u64)intrinsics.syscall(unix_offset_syscall(.gettid))
 }

Tiedoston diff-näkymää rajattu, sillä se on liian suuri
+ 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 {
 		os_version.platform = .MacOS
 		switch version.majorVersion {
+		case 26: ws(&b, "macOS Tahoe")
 		case 15: ws(&b, "macOS Sequoia")
 		case 14: ws(&b, "macOS Sonoma")
 		case 13: ws(&b, "macOS Ventura")

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

@@ -1618,36 +1618,39 @@ PER_HPUX        :: 0x0010
 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,
 	RDOTH  = 2,
 	WRGRP  = 4,
 	RDGRP  = 5,
 	WRUSR  = 7,
 	RDUSR  = 8,
-	DEST   = 9,
-	LOCKED = 10,
-}
-
-/*
-	Shared memory flags bits
-*/
-IPC_Flags_Bits :: enum {
+	// Creation flags for shared memory.
 	IPC_CREAT     = 9,
 	IPC_EXCL      = 10,
-	IPC_NOWAIT    = 11,
-	// Semaphore
-	SEM_UNDO      = 9,
-	// Shared memory
 	SHM_HUGETLB   = 11,
 	SHM_NORESERVE = 12,
+	// Usage flags for shared memory.
+	IPC_NOWAIT    = 11,
+	SEM_UNDO      = 9,
 	SHM_RDONLY    = 12,
 	SHM_RND       = 13,
 	SHM_REMAP     = 14,
 	SHM_EXEC      = 15,
-	// Message queue
 	MSG_NOERROR   = 12,
 	MSG_EXCEPT    = 13,
 	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_1GB        :: transmute(Map_Flags)(u32(30) << 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]
 
 /*
-	Permissions for IPC objects
+	Permissions for SystemV IPC primitives.
 */
 IPC_Perm :: struct {
 	key:  Key,
@@ -955,7 +950,7 @@ IPC_Perm :: struct {
 	gid:  u32,
 	cuid: u32,
 	cgid: u32,
-	mode: IPC_Mode,
+	mode: IPC_Flags, // Only contains mode flags.
 	seq:  u16,
 	_:    [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 {
 	foreign import lib "system:dl"
 } else {
-	foreign import lib "system:c"
+	foreign import lib {
+		"system:c",
+		"system:dl",
+	}
 }
 
 // dlfcn.h - dynamic linking

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

@@ -31,7 +31,7 @@ Unimplemented headers:
 - iso646.h | Impossible
 - math.h | See `core:c/libc`
 - 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
 - spawn.h | Use `fork`, `execve`, etc.
 - stdarg.h | See `core:c/libc`
@@ -53,6 +53,8 @@ import "base:intrinsics"
 
 import "core:c"
 
+IS_SUPPORTED :: _IS_SUPPORTED
+
 result :: enum c.int {
  	// Use `errno` and `strerror` for more information.
 	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,
 }
 
-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 ---
 	GetThreadPriority :: proc(thread: HANDLE) -> c_int ---
 	SetThreadPriority :: proc(thread: HANDLE, priority: c_int) -> BOOL ---
+	GetThreadDescription :: proc(hThread: HANDLE, ppszThreadDescription: ^PCWSTR) -> HRESULT ---
 	SetThreadDescription :: proc(hThread: HANDLE, lpThreadDescription: PCWSTR) -> HRESULT ---
 	GetExitCodeThread :: 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)
 }
 
-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 {
 		return nil
 	}
@@ -101,14 +101,42 @@ utf8_to_utf16 :: proc(s: string, allocator := context.temp_allocator) -> []u16 {
 	}
 	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 {
 		return raw_data(res)
 	}
 	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
 
 	if N == 0 {
@@ -142,13 +170,49 @@ wstring_to_utf8 :: proc(s: wstring, N: int, allocator := context.temp_allocator)
 	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 {
 		return "", nil
 	}
 	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.
 
 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 */
 
 
-WAVEFORMATEX :: struct {
+WAVEFORMATEX :: struct #packed {
 	wFormatTag:      WORD,
 	nChannels:       WORD,
 	nSamplesPerSec:  DWORD,
@@ -603,7 +603,7 @@ LPCWAVEFORMATEX :: ^WAVEFORMATEX
 //  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.
 //  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,
 	Samples: struct #raw_union {
 		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 ]]
 
 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 false
 }
 
 get_environment_color :: proc() -> Color_Depth {
+	buf: [128]u8
 	// `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
 		// anything to do with color depth.
 		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") {
 			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.
 LOG_LEVEL_DEFAULT     : string : "debug" when ODIN_DEBUG else "info"
 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.
 USING_SHORT_LOGS      : bool   : #config(ODIN_TEST_SHORT_LOGS, false)
 // 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
 					}
 
-					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
@@ -741,7 +743,8 @@ runner :: proc(internal_tests: []Internal_Test) -> bool {
 
 
 		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 pkg := report.packages_by_name[it.pkg]
 			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.",
 				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 {
 					// We only write a new error message here if there wasn't one
 					// 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)
 
-			total_failure_count += 1
+			if passed {
+				total_success_count += 1
+			} else {
+				total_failure_count += 1
+			}
 			total_done_count += 1
 		}
 

+ 24 - 1
core/testing/signal_handler.odin

@@ -12,8 +12,26 @@ package testing
 import "base:runtime"
 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 {
 	Unknown,
+	Successful_Stop,
 	Illegal_Instruction,
 	Arithmetic_Error,
 	Segmentation_Fault,
@@ -21,7 +39,12 @@ Stop_Reason :: enum {
 }
 
 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()
 }
 

+ 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_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", 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) {
 		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)
 
 		for {
@@ -154,11 +178,15 @@ _should_stop_test :: proc() -> (test_index: int, reason: Stop_Reason, ok: bool)
 		intrinsics.atomic_store(&stop_test_alert, 0)
 
 		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
 	}

+ 73 - 0
core/testing/testing.odin

@@ -21,6 +21,8 @@ import "core:mem"
 _ :: reflect // alias reflect to nothing to force visibility for -vet
 _ :: mem     // in case TRACKING_MEMORY is not enabled
 
+MAX_EXPECTED_ASSERTIONS_PER_TEST :: 5
+
 // IMPORTANT NOTE: Compiler requires this layout
 Test_Signature :: proc(^T)
 
@@ -155,3 +157,74 @@ set_fail_timeout :: proc(t: ^T, duration: time.Duration, loc := #caller_location
 		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,
 	// An unknown letter was supplied to `create_by_user` after the last delimiter.
 	Unknown_Flag,
-	// An unsupported flag was supplied.
-	Unsupported_Flag,
 }
 
 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.
-Note: Does not handle `.Multiline` properly.
 */
 Match_Iterator :: struct {
 	regex:    Regular_Expression,
@@ -436,7 +433,6 @@ match_with_preallocated_capture :: proc(
 
 /*
 Iterate over a `Match_Iterator` and return successive captures.
-Note: Does not handle `.Multiline` properly.
 
 Inputs:
 - 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 {
 	using specific: Thread_Os_Specific,
 	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,
 	// The thread procedure.
 	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
 `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
 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
 `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
 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
 `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
 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
 `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
 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
 `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
 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
 `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
 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()
 
+	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
 	for started_count < len(pool.threads) {
 		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()
 
-		if .Started not_in sync.atomic_load(&t.flags) {
+		for (.Started not_in sync.atomic_load(&t.flags)) {
 			sync.wait(&t.start_ok)
 		}
 
-		if .Joined in sync.atomic_load(&t.flags) {
-			return nil
-		}
-
 		// Enable thread's cancelability.
 		// 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
@@ -124,7 +120,6 @@ _create :: proc(procedure: Thread_Proc, priority: Thread_Priority) -> ^Thread {
 		free(thread, thread.creation_allocator)
 		return nil
 	}
-
 	return thread
 }
 
@@ -149,10 +144,13 @@ _join :: proc(t: ^Thread) {
 
 	// Prevent non-started threads from blocking main thread with initial wait
 	// condition.
-	if .Started not_in sync.atomic_load(&t.flags) {
+	for (.Started not_in sync.atomic_load(&t.flags)) {
 		_start(t)
 	}
+
 	posix.pthread_join(t.unix_thread, nil)
+
+	t.flags += {.Joined}
 }
 
 _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_id: win32.DWORD,
 	mutex:           sync.Mutex,
+	start_ok:        sync.Sema,
 }
 
 _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 {
 		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
 
@@ -76,6 +75,7 @@ _create :: proc(procedure: Thread_Proc, priority: Thread_Priority) -> ^Thread {
 	thread.procedure       = procedure
 	thread.win32_thread    = win32_thread
 	thread.win32_thread_id = win32_thread_id
+	thread.id              = int(win32_thread_id)
 
 	ok := win32.SetThreadPriority(win32_thread, _thread_priority_map[priority])
 	assert(ok == true)
@@ -103,16 +103,15 @@ _join :: proc(t: ^Thread) {
 		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.CloseHandle(t.win32_thread)
 	t.win32_thread = win32.INVALID_HANDLE
+
+	t.flags += {.Joined}
 }
 
 _join_multiple :: proc(threads: ..^Thread) {
@@ -136,6 +135,7 @@ _join_multiple :: proc(threads: ..^Thread) {
 	for t in threads {
 		win32.CloseHandle(t.win32_thread)
 		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])
 
 	// 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
 	}
 

+ 4 - 1
examples/all/all_vendor.odin

@@ -45,4 +45,7 @@ package all
 @(require) import stbi "vendor:stb/image"
 @(require) import "vendor:stb/rect_pack"
 @(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) {
+		case 26: gb_printf("macOS Tahoe"); break;
 		case 15: gb_printf("macOS Sequoia"); break;
 		case 14: gb_printf("macOS Sonoma"); break;
 		case 13: gb_printf("macOS Ventura"); break;

+ 8 - 1
src/build_settings.cpp

@@ -385,6 +385,13 @@ enum LinkerChoice : i32 {
 	Linker_COUNT,
 };
 
+enum SourceCodeLocationInfo : u8 {
+	SourceCodeLocationInfo_Normal = 0,
+	SourceCodeLocationInfo_Obfuscated = 1,
+	SourceCodeLocationInfo_Filename = 2,
+	SourceCodeLocationInfo_None = 3,
+};
+
 String linker_choices[Linker_COUNT] = {
 	str_lit("default"),
 	str_lit("lld"),
@@ -512,7 +519,7 @@ struct BuildContext {
 
 	bool   dynamic_map_calls;
 
-	bool   obfuscate_source_code_locations;
+	SourceCodeLocationInfo source_code_location_info;
 
 	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) {
 		return false;
 	}
+
+	if (build_context.metrics.os != TargetOs_darwin) {
+		return false;
+	}
+
 	if (build_context.metrics.arch == TargetArch_i386 || build_context.metrics.arch == TargetArch_amd64) {
 		i64 struct_limit = type_size_of(t_uintptr) << 1;
 		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;
 		e->flags |= EntityFlag_Require;
 	} else if (ac.instrumentation_enter) {
+		init_core_source_code_location(ctx->checker);
 		if (!is_valid_instrumentation_call(e->type)) {
 			init_core_source_code_location(ctx->checker);
 			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);
 			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);
 		if (ctx->info->instrumentation_enter_entity != nullptr) {
 			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);
 			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);
 		if (ctx->info->instrumentation_exit_entity != nullptr) {
 			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.no_sanitize_address = ac.no_sanitize_address;
+	e->Procedure.no_sanitize_memory  = ac.no_sanitize_memory;
 
 	e->deprecated_message = ac.deprecated_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 *d = dst->Slice.elem;
 		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)) {
 		Type *s = src->DynamicArray.elem;
 		Type *d = dst->Slice.elem;
 		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)) {
-		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)) {
-		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");
 	} 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)) {
 		return;
 	} 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);
-		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);
 	} else if (is_type_pointer(type) &&
 	           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);
 	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);
-	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;
 		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;
 			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->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;
 		}
 
-		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->expr != nullptr) {
-				x->expr->tav.is_lhs = true;
-			}
-			x->mode = Addressing_Value;
 			if (type_hint) {
 				if (is_type_integer(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 {
 					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(to_type);
+					gb_string_free(type_str);
 					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) {
 	if (check_is_assignable_to(c, operand, y)) {
 		return true;
@@ -5884,12 +5864,12 @@ typedef u32 UnpackFlags;
 enum UnpackFlag : u32 {
 	UnpackFlag_None       = 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 {
 		if (lhs == nullptr || c->decl == nullptr) {
 			return tuple_count;
@@ -5914,11 +5894,14 @@ gb_internal bool check_unpack_arguments(CheckerContext *ctx, Entity **lhs, isize
 		return tuple_count;
 	};
 
-
 	bool allow_ok    = (flags & UnpackFlag_AllowOk) != 0;
-	bool is_variadic = (flags & UnpackFlag_IsVariadic) != 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;
 	isize tuple_index = 0;
 	for (Ast *rhs : rhs_arguments) {
@@ -5934,26 +5917,18 @@ gb_internal bool check_unpack_arguments(CheckerContext *ctx, Entity **lhs, isize
 
 		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(e->type->kind == Type_Slice);
 					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;
 }
 
-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;
 	isize lhs_count = -1;
 
-	if (proc_type == nullptr) {
+	if (proc_type == nullptr || proc_type == t_invalid) {
 		return nullptr;
 	}
 
 	GB_ASSERT(is_type_proc(proc_type));
 	TypeProc *pt = &base_type(proc_type)->Proc;
-	*is_variadic = pt->variadic;
 
 	if (!pt->is_polymorphic || pt->is_poly_specialized) {
 		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);
 	proc_type = base_type(proc_type);
+	if (proc_type == t_invalid) {
+		return false;
+	}
 	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);
@@ -6830,7 +6807,7 @@ gb_internal CallArgumentData check_call_arguments_proc_group(CheckerContext *c,
 
 	Entity **lhs = nullptr;
 	isize lhs_count = -1;
-	bool is_variadic = false;
+	i32 variadic_index = -1;
 
 	auto positional_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) {
 		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)) {
 			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;
 				}
+
+				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) {
 		Ast *arg = named_args[i];
@@ -7343,13 +7344,16 @@ gb_internal CallArgumentData check_call_arguments(CheckerContext *c, Operand *op
 	defer (array_free(&named_operands));
 
 	if (positional_args.count > 0) {
-		isize lhs_count = -1;
-		bool is_variadic = false;
 		Entity **lhs =  nullptr;
+		isize lhs_count = -1;
+		i32 variadic_index = -1;
 		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) {
@@ -8756,23 +8760,52 @@ gb_internal ExprKind check_basic_directive_expr(CheckerContext *c, Operand *o, A
 	String name = bd->name.string;
 	if (name == "file") {
 		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");
+			break;
+		case SourceCodeLocationInfo_Filename:
+			file = last_path_element(file);
+			break;
+		case SourceCodeLocationInfo_None:
+			file = str_lit("");
+			break;
 		}
 		o->type = t_untyped_string;
 		o->value = exact_value_string(file);
 	} else if (name == "directory") {
 		String file = get_file_path_string(bd->token.pos.file_id);
 		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");
+			break;
+		case SourceCodeLocationInfo_Filename:
+			path = last_path_element(path);
+			break;
+		case SourceCodeLocationInfo_None:
+			path = str_lit("");
+			break;
 		}
 		o->type = t_untyped_string;
 		o->value = exact_value_string(path);
 	} else if (name == "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);
+			break;
+		case SourceCodeLocationInfo_Filename:
+			break;
+		case SourceCodeLocationInfo_None:
+			line = 0;
+			break;
 		}
 		o->type = t_untyped_integer;
 		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(""));
 		} else {
 			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");
+				break;
+			case SourceCodeLocationInfo_Filename:
+				break;
+			case SourceCodeLocationInfo_None:
+				p = str_lit("");
+				break;
 			}
 			o->type = t_untyped_string;
 			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);
 		o->mode = Addressing_Value;
 		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;
 
 
@@ -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;
 
 		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->expr = node;
 			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) {
 		return nullptr;
 	}
@@ -430,7 +430,7 @@ gb_internal Type *check_assignment_variable(CheckerContext *ctx, Operand *lhs, O
 
 	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 '_'
 	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;
 	}
 
-	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;
 
@@ -2418,7 +2418,7 @@ gb_internal void check_assign_stmt(CheckerContext *ctx, Ast *node) {
 
 		isize lhs_count = as->lhs.count;
 		if (lhs_count == 0) {
-			error(as->op, "Missing lhs in assignment statement");
+			error(as->op, "Missing LHS in assignment statement");
 			return;
 		}
 
@@ -2451,7 +2451,7 @@ gb_internal void check_assign_stmt(CheckerContext *ctx, Ast *node) {
 			if (lhs_to_ignore[i]) {
 				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) {
 			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
 		Token op = as->op;
 		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;
 		}
 		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;
 		}
 		Operand lhs = {Addressing_Invalid};
@@ -2474,15 +2474,16 @@ gb_internal void check_assign_stmt(CheckerContext *ctx, Ast *node) {
 		ast_node(be, BinaryExpr, binary_expr);
 		be->op = op;
 		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->right = as->rhs[0];
 
 		check_expr(ctx, &lhs, as->lhs[0]);
 		check_binary_expr(ctx, &rhs, binary_expr, nullptr, true);
 		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"));
 		}
 	}
 }

Kaikkia tiedostoja ei voida näyttää, sillä liian monta tiedostoa muuttui tässä diffissä