瀏覽代碼

Merge branch 'master' into tlsf-allocator

Jeroen van Rijn 1 年之前
父節點
當前提交
3c7e2659ac
共有 100 個文件被更改,包括 2644 次插入994 次删除
  1. 68 165
      .github/workflows/ci.yml
  2. 13 28
      .github/workflows/nightly.yml
  3. 13 2
      base/runtime/core.odin
  4. 1 1
      base/runtime/default_allocators_general.odin
  5. 25 11
      base/runtime/entry_wasm.odin
  6. 4 0
      base/runtime/error_checks.odin
  7. 29 0
      base/runtime/heap_allocator_orca.odin
  8. 1 1
      base/runtime/heap_allocator_other.odin
  9. 2 2
      base/runtime/internal.odin
  10. 43 0
      base/runtime/os_specific_orca.odin
  11. 1 1
      base/runtime/procs.odin
  12. 0 51
      ci/create_nightly_json.py
  13. 0 33
      ci/delete_old_binaries.py
  14. 140 0
      ci/nightly.py
  15. 0 25
      ci/upload_create_nightly.sh
  16. 1 1
      core/fmt/fmt.odin
  17. 1 0
      core/fmt/fmt_os.odin
  18. 746 0
      core/image/bmp/bmp.odin
  19. 4 0
      core/image/bmp/bmp_js.odin
  20. 34 0
      core/image/bmp/bmp_os.odin
  21. 126 0
      core/image/common.odin
  22. 1 1
      core/math/cmplx/cmplx_trig.odin
  23. 9 9
      core/math/math.odin
  24. 3 3
      core/math/math_gamma.odin
  25. 7 7
      core/math/math_lgamma.odin
  26. 1 1
      core/math/math_sincos.odin
  27. 3 3
      core/math/rand/exp.odin
  28. 3 3
      core/math/rand/normal.odin
  29. 5 1
      core/os/dir_windows.odin
  30. 1 1
      core/os/os2/internal_util.odin
  31. 6 2
      core/os/os_netbsd.odin
  32. 1 1
      core/strconv/generic_float.odin
  33. 10 7
      core/strconv/strconv.odin
  34. 1 1
      core/sync/futex_darwin.odin
  35. 6 2
      core/sync/primitives_netbsd.odin
  36. 1 0
      core/sys/info/platform_darwin.odin
  37. 3 0
      core/testing/runner.odin
  38. 12 11
      core/thread/thread_unix.odin
  39. 1 0
      core/time/time.odin
  40. 24 0
      core/time/time_orca.odin
  41. 10 0
      core/unicode/tables.odin
  42. 1 0
      src/bug_report.cpp
  43. 28 8
      src/build_settings.cpp
  44. 11 23
      src/check_builtin.cpp
  45. 6 0
      src/check_decl.cpp
  46. 29 11
      src/check_expr.cpp
  47. 14 2
      src/check_stmt.cpp
  48. 10 18
      src/check_type.cpp
  49. 6 0
      src/checker.cpp
  50. 1 0
      src/checker.hpp
  51. 1 0
      src/entity.cpp
  52. 33 32
      src/error.cpp
  53. 3 0
      src/gb/gb.h
  54. 27 13
      src/linker.cpp
  55. 9 1
      src/llvm_abi.cpp
  56. 18 2
      src/llvm_backend.cpp
  57. 51 4
      src/llvm_backend_expr.cpp
  58. 2 2
      src/llvm_backend_general.cpp
  59. 2 3
      src/llvm_backend_proc.cpp
  60. 3 1
      src/llvm_backend_stmt.cpp
  61. 32 0
      src/main.cpp
  62. 2 0
      src/threading.cpp
  63. 17 2
      src/types.cpp
  64. 0 14
      tests/benchmark/Makefile
  65. 4 0
      tests/benchmark/all.odin
  66. 0 13
      tests/benchmark/build.bat
  67. 1 0
      tests/core/.gitignore
  68. 0 115
      tests/core/Makefile
  69. 0 124
      tests/core/build.bat
  70. 91 2
      tests/core/download_assets.py
  71. 40 3
      tests/core/fmt/test_core_fmt.odin
  72. 0 2
      tests/core/image/build.bat
  73. 597 72
      tests/core/image/test_core_image.odin
  74. 2 1
      tests/core/net/test_core_net.odin
  75. 40 0
      tests/core/normal.odin
  76. 0 10
      tests/core/os/test_core_os_exit.odin
  77. 5 0
      tests/core/speed.odin
  78. 145 0
      tests/core/strconv/test_core_strconv.odin
  79. 0 22
      tests/internal/Makefile
  80. 0 9
      tests/internal/build.bat
  81. 1 1
      tests/internal/test_128.odin
  82. 1 1
      tests/internal/test_asan.odin
  83. 2 2
      tests/internal/test_map.odin
  84. 2 2
      tests/internal/test_pow.odin
  85. 2 2
      tests/internal/test_rtti.odin
  86. 2 2
      tests/internal/test_string_compare.odin
  87. 0 1
      tests/issues/run.bat
  88. 3 5
      tests/issues/run.sh
  89. 0 62
      tests/issues/test_issue_2087.odin
  90. 0 2
      tests/issues/test_issue_2395.odin
  91. 0 10
      tests/vendor/Makefile
  92. 3 0
      tests/vendor/all.odin
  93. 0 8
      tests/vendor/build.bat
  94. 1 0
      tests/vendor/glfw/test_vendor_glfw.odin
  95. 1 1
      vendor/directx/d3d11/d3d11.odin
  96. 3 3
      vendor/microui/microui.odin
  97. 2 2
      vendor/raylib/raylib.odin
  98. 1 1
      vendor/sdl2/sdl_render.odin
  99. 5 0
      vendor/x11/xlib/xlib_const.odin
  100. 24 6
      vendor/x11/xlib/xlib_procs.odin

+ 68 - 165
.github/workflows/ci.yml

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

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

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

+ 13 - 2
base/runtime/core.odin

@@ -701,7 +701,7 @@ default_assertion_failure_proc :: proc(prefix, message: string, loc: Source_Code
 	when ODIN_OS == .Freestanding {
 	when ODIN_OS == .Freestanding {
 		// Do nothing
 		// Do nothing
 	} else {
 	} else {
-		when !ODIN_DISABLE_ASSERT {
+		when ODIN_OS != .Orca && !ODIN_DISABLE_ASSERT {
 			print_caller_location(loc)
 			print_caller_location(loc)
 			print_string(" ")
 			print_string(" ")
 		}
 		}
@@ -710,7 +710,18 @@ default_assertion_failure_proc :: proc(prefix, message: string, loc: Source_Code
 			print_string(": ")
 			print_string(": ")
 			print_string(message)
 			print_string(message)
 		}
 		}
-		print_byte('\n')
+
+		when ODIN_OS == .Orca {
+			assert_fail(
+				cstring(raw_data(loc.file_path)),
+				cstring(raw_data(loc.procedure)),
+				loc.line,
+				"",
+				cstring(raw_data(orca_stderr_buffer[:orca_stderr_buffer_idx])),
+			)
+		} else {
+			print_byte('\n')
+		}
 	}
 	}
 	trap()
 	trap()
 }
 }

+ 1 - 1
base/runtime/default_allocators_general.odin

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

+ 25 - 11
base/runtime/entry_wasm.odin

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

+ 4 - 0
base/runtime/error_checks.odin

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

+ 29 - 0
base/runtime/heap_allocator_orca.odin

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

+ 1 - 1
base/runtime/heap_allocator_other.odin

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

+ 2 - 2
base/runtime/internal.odin

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

+ 43 - 0
base/runtime/os_specific_orca.odin

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

+ 1 - 1
base/runtime/procs.odin

@@ -25,7 +25,7 @@ when ODIN_NO_CRT && ODIN_OS == .Windows {
 		RtlMoveMemory(dst, src, len)
 		RtlMoveMemory(dst, src, len)
 		return dst
 		return dst
 	}
 	}
-} else when ODIN_NO_CRT || (ODIN_ARCH == .wasm32 || ODIN_ARCH == .wasm64p32) {
+} else when ODIN_NO_CRT || (ODIN_OS != .Orca && (ODIN_ARCH == .wasm32 || ODIN_ARCH == .wasm64p32)) {
 	// NOTE: on wasm, calls to these procs are generated (by LLVM) with type `i32` instead of `int`.
 	// NOTE: on wasm, calls to these procs are generated (by LLVM) with type `i32` instead of `int`.
 	//
 	//
 	// NOTE: `#any_int` is also needed, because calls that we generate (and package code)
 	// NOTE: `#any_int` is also needed, because calls that we generate (and package code)

+ 0 - 51
ci/create_nightly_json.py

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

+ 0 - 33
ci/delete_old_binaries.py

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

+ 140 - 0
ci/nightly.py

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

+ 0 - 25
ci/upload_create_nightly.sh

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

+ 1 - 1
core/fmt/fmt.odin

@@ -1495,7 +1495,7 @@ fmt_pointer :: proc(fi: ^Info, p: rawptr, verb: rune) {
 	u := u64(uintptr(p))
 	u := u64(uintptr(p))
 	switch verb {
 	switch verb {
 	case 'p', 'v', 'w':
 	case 'p', 'v', 'w':
-		if !fi.hash && verb == 'v' {
+		if !fi.hash {
 			io.write_string(fi.writer, "0x", &fi.n)
 			io.write_string(fi.writer, "0x", &fi.n)
 		}
 		}
 		_fmt_int(fi, u, 16, false, 8*size_of(rawptr), __DIGITS_UPPER)
 		_fmt_int(fi, u, 16, false, 8*size_of(rawptr), __DIGITS_UPPER)

+ 1 - 0
core/fmt/fmt_os.odin

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

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

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

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

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

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

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

+ 126 - 0
core/image/common.odin

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

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

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

+ 9 - 9
core/math/math.odin

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

+ 3 - 3
core/math/math_gamma.odin

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

+ 7 - 7
core/math/math_lgamma.odin

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

+ 1 - 1
core/math/math_sincos.odin

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

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

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

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

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

+ 5 - 1
core/os/dir_windows.odin

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

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

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

+ 6 - 2
core/os/os_netbsd.odin

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

+ 1 - 1
core/strconv/generic_float.odin

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

+ 10 - 7
core/strconv/strconv.odin

@@ -882,13 +882,16 @@ parse_f64_prefix :: proc(str: string) -> (value: f64, nr: int, ok: bool) {
 				s = s[1:]
 				s = s[1:]
 				fallthrough
 				fallthrough
 			case 'i', 'I':
 			case 'i', 'I':
-				n = common_prefix_len_ignore_case(s, "infinity")
-				if 3 < n && n < 8 { // "inf" or "infinity"
-					n = 3
-				}
-				if n == 3 || n == 8 {
+				m := common_prefix_len_ignore_case(s, "infinity")
+				if 3 <= m && m < 9 { // "inf" to "infinity"
 					f = 0h7ff00000_00000000 if sign == 1 else 0hfff00000_00000000
 					f = 0h7ff00000_00000000 if sign == 1 else 0hfff00000_00000000
-					n = nsign + 3
+					if m == 8 {
+						// We only count the entire prefix if it is precisely "infinity".
+						n = nsign + m
+					} else {
+						// The string was either only "inf" or incomplete.
+						n = nsign + 3
+					}
 					ok = true
 					ok = true
 					return
 					return
 				}
 				}
@@ -1092,7 +1095,7 @@ parse_f64_prefix :: proc(str: string) -> (value: f64, nr: int, ok: bool) {
 	}
 	}
 
 
 	trunc_block: if !trunc {
 	trunc_block: if !trunc {
-		@static pow10 := [?]f64{
+		@(static, rodata) pow10 := [?]f64{
 			1e0,  1e1,  1e2,  1e3,  1e4,  1e5,  1e6,  1e7,  1e8,  1e9,
 			1e0,  1e1,  1e2,  1e3,  1e4,  1e5,  1e6,  1e7,  1e8,  1e9,
 			1e10, 1e11, 1e12, 1e13, 1e14, 1e15, 1e16, 1e17, 1e18, 1e19,
 			1e10, 1e11, 1e12, 1e13, 1e14, 1e15, 1e16, 1e17, 1e18, 1e19,
 			1e20, 1e21, 1e22,
 			1e20, 1e21, 1e22,

+ 1 - 1
core/sync/futex_darwin.odin

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

+ 6 - 2
core/sync/primitives_netbsd.odin

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

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

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

+ 3 - 0
core/testing/runner.odin

@@ -53,6 +53,9 @@ get_log_level :: #force_inline proc() -> runtime.Logger_Level {
 		else when LOG_LEVEL == "warning" { return .Warning }
 		else when LOG_LEVEL == "warning" { return .Warning }
 		else when LOG_LEVEL == "error"   { return .Error   }
 		else when LOG_LEVEL == "error"   { return .Error   }
 		else when LOG_LEVEL == "fatal"   { return .Fatal   }
 		else when LOG_LEVEL == "fatal"   { return .Fatal   }
+		else {
+			#panic("Unknown `ODIN_TEST_LOG_LEVEL`: \"" + LOG_LEVEL + "\", possible levels are: \"debug\", \"info\", \"warning\", \"error\", or \"fatal\".")
+		}
 	}
 	}
 }
 }
 
 

+ 12 - 11
core/thread/thread_unix.odin

@@ -2,11 +2,11 @@
 // +private
 // +private
 package thread
 package thread
 
 
-import "base:intrinsics"
 import "core:sync"
 import "core:sync"
 import "core:sys/unix"
 import "core:sys/unix"
+import "core:time"
 
 
-CAS :: intrinsics.atomic_compare_exchange_strong
+CAS :: sync.atomic_compare_exchange_strong
 
 
 // NOTE(tetra): Aligned here because of core/unix/pthread_linux.odin/pthread_t.
 // NOTE(tetra): Aligned here because of core/unix/pthread_linux.odin/pthread_t.
 // Also see core/sys/darwin/mach_darwin.odin/semaphore_t.
 // Also see core/sys/darwin/mach_darwin.odin/semaphore_t.
@@ -32,11 +32,13 @@ _create :: proc(procedure: Thread_Proc, priority: Thread_Priority) -> ^Thread {
 
 
 		t.id = sync.current_thread_id()
 		t.id = sync.current_thread_id()
 
 
-		for (.Started not_in t.flags) {
-			sync.wait(&t.cond, &t.mutex)
+		for (.Started not_in sync.atomic_load(&t.flags)) {
+			// HACK: use a timeout so in the event that the condition is signalled at THIS comment's exact point
+			// (after checking flags, before starting the wait) it gets itself out of that deadlock after a ms.
+			sync.wait_with_timeout(&t.cond, &t.mutex, time.Millisecond)
 		}
 		}
 
 
-		if .Joined in t.flags {
+		if .Joined in sync.atomic_load(&t.flags) {
 			return nil
 			return nil
 		}
 		}
 
 
@@ -60,11 +62,11 @@ _create :: proc(procedure: Thread_Proc, priority: Thread_Priority) -> ^Thread {
 			t.procedure(t)
 			t.procedure(t)
 		}
 		}
 
 
-		intrinsics.atomic_store(&t.flags, t.flags + { .Done })
+		sync.atomic_or(&t.flags, { .Done })
 
 
 		sync.unlock(&t.mutex)
 		sync.unlock(&t.mutex)
 
 
-		if .Self_Cleanup in t.flags {
+		if .Self_Cleanup in sync.atomic_load(&t.flags) {
 			t.unix_thread = {}
 			t.unix_thread = {}
 			// NOTE(ftphikari): It doesn't matter which context 'free' received, right?
 			// NOTE(ftphikari): It doesn't matter which context 'free' received, right?
 			context = {}
 			context = {}
@@ -122,13 +124,12 @@ _create :: proc(procedure: Thread_Proc, priority: Thread_Priority) -> ^Thread {
 }
 }
 
 
 _start :: proc(t: ^Thread) {
 _start :: proc(t: ^Thread) {
-	// sync.guard(&t.mutex)
-	t.flags += { .Started }
+	sync.atomic_or(&t.flags, { .Started })
 	sync.signal(&t.cond)
 	sync.signal(&t.cond)
 }
 }
 
 
 _is_done :: proc(t: ^Thread) -> bool {
 _is_done :: proc(t: ^Thread) -> bool {
-	return .Done in intrinsics.atomic_load(&t.flags)
+	return .Done in sync.atomic_load(&t.flags)
 }
 }
 
 
 _join :: proc(t: ^Thread) {
 _join :: proc(t: ^Thread) {
@@ -139,7 +140,7 @@ _join :: proc(t: ^Thread) {
 	}
 	}
 
 
 	// Preserve other flags besides `.Joined`, like `.Started`.
 	// Preserve other flags besides `.Joined`, like `.Started`.
-	unjoined := intrinsics.atomic_load(&t.flags) - {.Joined}
+	unjoined := sync.atomic_load(&t.flags) - {.Joined}
 	joined   := unjoined + {.Joined}
 	joined   := unjoined + {.Joined}
 
 
 	// Try to set `t.flags` from unjoined to joined. If it returns joined,
 	// Try to set `t.flags` from unjoined to joined. If it returns joined,

+ 1 - 0
core/time/time.odin

@@ -389,6 +389,7 @@ is_leap_year :: proc "contextless" (year: int) -> (leap: bool) {
 	return year % 4 == 0 && (year % 100 != 0 || year % 400 == 0)
 	return year % 4 == 0 && (year % 100 != 0 || year % 400 == 0)
 }
 }
 
 
+@(rodata)
 days_before := [?]i32{
 days_before := [?]i32{
 	0,
 	0,
 	31,
 	31,

+ 24 - 0
core/time/time_orca.odin

@@ -0,0 +1,24 @@
+//+private
+//+build orca
+package time
+
+_IS_SUPPORTED :: false
+
+_now :: proc "contextless" () -> Time {
+	return {}
+}
+
+_sleep :: proc "contextless" (d: Duration) {
+}
+
+_tick_now :: proc "contextless" () -> Tick {
+	// mul_div_u64 :: proc "contextless" (val, num, den: i64) -> i64 {
+	// 	q := val / den
+	// 	r := val % den
+	// 	return q * num + r * num / den
+	// }
+	return {}
+}
+
+_yield :: proc "contextless" () {
+}

+ 10 - 0
core/unicode/tables.odin

@@ -12,6 +12,7 @@ package unicode
 @(private) pLo    :: pLl | pLu // a letter that is neither upper nor lower case.
 @(private) pLo    :: pLl | pLu // a letter that is neither upper nor lower case.
 @(private) pLmask :: pLo
 @(private) pLmask :: pLo
 
 
+@(rodata)
 char_properties := [MAX_LATIN1+1]u8{
 char_properties := [MAX_LATIN1+1]u8{
 	0x00 = pC,       // '\x00'
 	0x00 = pC,       // '\x00'
 	0x01 = pC,       // '\x01'
 	0x01 = pC,       // '\x01'
@@ -272,6 +273,7 @@ char_properties := [MAX_LATIN1+1]u8{
 }
 }
 
 
 
 
+@(rodata)
 alpha_ranges := [?]i32{
 alpha_ranges := [?]i32{
 	0x00d8,  0x00f6,
 	0x00d8,  0x00f6,
 	0x00f8,  0x01f5,
 	0x00f8,  0x01f5,
@@ -427,6 +429,7 @@ alpha_ranges := [?]i32{
 	0xffda,  0xffdc,
 	0xffda,  0xffdc,
 }
 }
 
 
+@(rodata)
 alpha_singlets := [?]i32{
 alpha_singlets := [?]i32{
 	0x00aa,
 	0x00aa,
 	0x00b5,
 	0x00b5,
@@ -462,6 +465,7 @@ alpha_singlets := [?]i32{
 	0xfe74,
 	0xfe74,
 }
 }
 
 
+@(rodata)
 space_ranges := [?]i32{
 space_ranges := [?]i32{
 	0x0009,  0x000d, // tab and newline
 	0x0009,  0x000d, // tab and newline
 	0x0020,  0x0020, // space
 	0x0020,  0x0020, // space
@@ -477,6 +481,7 @@ space_ranges := [?]i32{
 	0xfeff,  0xfeff,
 	0xfeff,  0xfeff,
 }
 }
 
 
+@(rodata)
 unicode_spaces := [?]i32{
 unicode_spaces := [?]i32{
 	0x0009, // tab
 	0x0009, // tab
 	0x000a, // LF
 	0x000a, // LF
@@ -494,6 +499,7 @@ unicode_spaces := [?]i32{
 	0xfeff, // unknown
 	0xfeff, // unknown
 }
 }
 
 
+@(rodata)
 to_upper_ranges := [?]i32{
 to_upper_ranges := [?]i32{
 	0x0061,  0x007a, 468, // a-z A-Z
 	0x0061,  0x007a, 468, // a-z A-Z
 	0x00e0,  0x00f6, 468,
 	0x00e0,  0x00f6, 468,
@@ -532,6 +538,7 @@ to_upper_ranges := [?]i32{
 	0xff41,  0xff5a, 468,
 	0xff41,  0xff5a, 468,
 }
 }
 
 
+@(rodata)
 to_upper_singlets := [?]i32{
 to_upper_singlets := [?]i32{
 	0x00ff, 621,
 	0x00ff, 621,
 	0x0101, 499,
 	0x0101, 499,
@@ -875,6 +882,7 @@ to_upper_singlets := [?]i32{
 	0x1ff3, 509,
 	0x1ff3, 509,
 }
 }
 
 
+@(rodata)
 to_lower_ranges := [?]i32{
 to_lower_ranges := [?]i32{
 	0x0041,  0x005a, 532, // A-Z a-z
 	0x0041,  0x005a, 532, // A-Z a-z
 	0x00c0,  0x00d6, 532, // - -
 	0x00c0,  0x00d6, 532, // - -
@@ -914,6 +922,7 @@ to_lower_ranges := [?]i32{
 	0xff21,  0xff3a, 532, // - -
 	0xff21,  0xff3a, 532, // - -
 }
 }
 
 
+@(rodata)
 to_lower_singlets := [?]i32{
 to_lower_singlets := [?]i32{
 	0x0100, 501,
 	0x0100, 501,
 	0x0102, 501,
 	0x0102, 501,
@@ -1250,6 +1259,7 @@ to_lower_singlets := [?]i32{
 	0x1ffc, 491,
 	0x1ffc, 491,
 }
 }
 
 
+@(rodata)
 to_title_singlets := [?]i32{
 to_title_singlets := [?]i32{
 	0x01c4, 501,
 	0x01c4, 501,
 	0x01c6, 499,
 	0x01c6, 499,

+ 1 - 0
src/bug_report.cpp

@@ -910,6 +910,7 @@ gb_internal void report_os_info() {
 			{"23D60",    {23,  3,  0}, "macOS", {"Sonoma",        {14,  3,  1}}},
 			{"23D60",    {23,  3,  0}, "macOS", {"Sonoma",        {14,  3,  1}}},
 			{"23E214",   {23,  4,  0}, "macOS", {"Sonoma",        {14,  4,  0}}},
 			{"23E214",   {23,  4,  0}, "macOS", {"Sonoma",        {14,  4,  0}}},
 			{"23E224",   {23,  4,  0}, "macOS", {"Sonoma",        {14,  4,  1}}},
 			{"23E224",   {23,  4,  0}, "macOS", {"Sonoma",        {14,  4,  1}}},
+			{"23F79",    {23,  5,  0}, "macOS", {"Sonoma",        {14,  5,  0}}},
 		};
 		};
 
 
 
 

+ 28 - 8
src/build_settings.cpp

@@ -1039,6 +1039,13 @@ gb_global TargetMetrics target_netbsd_amd64 = {
 	str_lit("x86_64-unknown-netbsd-elf"),
 	str_lit("x86_64-unknown-netbsd-elf"),
 };
 };
 
 
+gb_global TargetMetrics target_netbsd_arm64 = {
+	TargetOs_netbsd,
+	TargetArch_arm64,
+	8, 8, 16, 16,
+	str_lit("aarch64-unknown-netbsd-elf"),
+};
+
 gb_global TargetMetrics target_haiku_amd64 = {
 gb_global TargetMetrics target_haiku_amd64 = {
 	TargetOs_haiku,
 	TargetOs_haiku,
 	TargetArch_amd64,
 	TargetArch_amd64,
@@ -1081,7 +1088,6 @@ gb_global TargetMetrics target_orca_wasm32 = {
 	TargetArch_wasm32,
 	TargetArch_wasm32,
 	4, 4, 8, 16,
 	4, 4, 8, 16,
 	str_lit("wasm32-wasi-js"),
 	str_lit("wasm32-wasi-js"),
-	// str_lit("e-m:e-p:32:32-i64:64-n32:64-S128"),
 };
 };
 
 
 
 
@@ -1154,13 +1160,16 @@ gb_global NamedTargetMetrics named_targets[] = {
 	{ str_lit("freebsd_amd64"),       &target_freebsd_amd64  },
 	{ str_lit("freebsd_amd64"),       &target_freebsd_amd64  },
 	{ str_lit("freebsd_arm64"),       &target_freebsd_arm64  },
 	{ str_lit("freebsd_arm64"),       &target_freebsd_arm64  },
 
 
-	{ str_lit("openbsd_amd64"),       &target_openbsd_amd64  },
 	{ str_lit("netbsd_amd64"),        &target_netbsd_amd64   },
 	{ str_lit("netbsd_amd64"),        &target_netbsd_amd64   },
+	{ str_lit("netbsd_arm64"),        &target_netbsd_arm64   },
+
+	{ str_lit("openbsd_amd64"),       &target_openbsd_amd64  },
 	{ str_lit("haiku_amd64"),         &target_haiku_amd64    },
 	{ str_lit("haiku_amd64"),         &target_haiku_amd64    },
 
 
 	{ str_lit("freestanding_wasm32"), &target_freestanding_wasm32 },
 	{ str_lit("freestanding_wasm32"), &target_freestanding_wasm32 },
 	{ str_lit("wasi_wasm32"),         &target_wasi_wasm32 },
 	{ str_lit("wasi_wasm32"),         &target_wasi_wasm32 },
 	{ str_lit("js_wasm32"),           &target_js_wasm32 },
 	{ str_lit("js_wasm32"),           &target_js_wasm32 },
+	{ str_lit("orca_wasm32"),         &target_orca_wasm32 },
 
 
 	{ str_lit("freestanding_wasm64p32"), &target_freestanding_wasm64p32 },
 	{ str_lit("freestanding_wasm64p32"), &target_freestanding_wasm64p32 },
 	{ str_lit("js_wasm64p32"),           &target_js_wasm64p32 },
 	{ str_lit("js_wasm64p32"),           &target_js_wasm64p32 },
@@ -1916,7 +1925,11 @@ gb_internal void init_build_context(TargetMetrics *cross_target, Subtarget subta
 		#elif defined(GB_SYSTEM_OPENBSD)
 		#elif defined(GB_SYSTEM_OPENBSD)
 			metrics = &target_openbsd_amd64;
 			metrics = &target_openbsd_amd64;
 		#elif defined(GB_SYSTEM_NETBSD)
 		#elif defined(GB_SYSTEM_NETBSD)
-			metrics = &target_netbsd_amd64;
+			#if defined(GB_CPU_ARM)
+				metrics = &target_netbsd_arm64;
+			#else
+				metrics = &target_netbsd_amd64;
+			#endif
 		#elif defined(GB_SYSTEM_HAIKU)
 		#elif defined(GB_SYSTEM_HAIKU)
 			metrics = &target_haiku_amd64;
 			metrics = &target_haiku_amd64;
 		#elif defined(GB_CPU_ARM)
 		#elif defined(GB_CPU_ARM)
@@ -2022,6 +2035,9 @@ gb_internal void init_build_context(TargetMetrics *cross_target, Subtarget subta
 			bc->link_flags = str_lit("/machine:x86 ");
 			bc->link_flags = str_lit("/machine:x86 ");
 			break;
 			break;
 		}
 		}
+	} else if (bc->metrics.os == TargetOs_darwin) {
+		bc->link_flags = concatenate3_strings(permanent_allocator(),
+			str_lit("-target "), bc->metrics.target_triplet, str_lit(" "));
 	} else if (is_arch_wasm()) {
 	} else if (is_arch_wasm()) {
 		gbString link_flags = gb_string_make(heap_allocator(), " ");
 		gbString link_flags = gb_string_make(heap_allocator(), " ");
 		// link_flags = gb_string_appendc(link_flags, "--export-all ");
 		// link_flags = gb_string_appendc(link_flags, "--export-all ");
@@ -2032,16 +2048,20 @@ gb_internal void init_build_context(TargetMetrics *cross_target, Subtarget subta
 		// }
 		// }
 		if (bc->no_entry_point || bc->metrics.os == TargetOs_orca) {
 		if (bc->no_entry_point || bc->metrics.os == TargetOs_orca) {
 			link_flags = gb_string_appendc(link_flags, "--no-entry ");
 			link_flags = gb_string_appendc(link_flags, "--no-entry ");
-			bc->no_entry_point = true; // just in case for the "orca" target
 		}
 		}
-		
+
 		bc->link_flags = make_string_c(link_flags);
 		bc->link_flags = make_string_c(link_flags);
-		
+
 		// Disallow on wasm
 		// Disallow on wasm
 		bc->use_separate_modules = false;
 		bc->use_separate_modules = false;
 	} else {
 	} else {
-		bc->link_flags = concatenate3_strings(permanent_allocator(),
-			str_lit("-target "), bc->metrics.target_triplet, str_lit(" "));
+		// NOTE: for targets other than darwin, we don't specify a `-target` link flag.
+		// This is because we don't support cross-linking and clang is better at figuring
+		// out what the actual target for linking is,
+		// for example, on x86/alpine/musl it HAS to be `x86_64-alpine-linux-musl` to link correctly.
+		//
+		// Note that codegen will still target the triplet we specify, but the intricate details of
+		// a target shouldn't matter as much to codegen (if it does at all) as it does to linking.
 	}
 	}
 
 
 	// NOTE: needs to be done after adding the -target flag to the linker flags so the linker
 	// NOTE: needs to be done after adding the -target flag to the linker flags so the linker

+ 11 - 23
src/check_builtin.cpp

@@ -2419,6 +2419,9 @@ gb_internal bool check_builtin_procedure(CheckerContext *c, Operand *operand, As
 		if (arg_count > max_count) {
 		if (arg_count > max_count) {
 			error(call, "Too many 'swizzle' indices, %td > %td", arg_count, max_count);
 			error(call, "Too many 'swizzle' indices, %td > %td", arg_count, max_count);
 			return false;
 			return false;
+		} else if (arg_count < 2) {
+			error(call, "Not enough 'swizzle' indices, %td < 2", arg_count);
+			return false;
 		}
 		}
 
 
 		if (type->kind == Type_Array) {
 		if (type->kind == Type_Array) {
@@ -5912,15 +5915,9 @@ gb_internal bool check_builtin_procedure(CheckerContext *c, Operand *operand, As
 		if (operand->mode != Addressing_Type) {
 		if (operand->mode != Addressing_Type) {
 			error(operand->expr, "Expected a record type for '%.*s'", LIT(builtin_name));
 			error(operand->expr, "Expected a record type for '%.*s'", LIT(builtin_name));
 		} else {
 		} else {
-			Type *bt = base_type(operand->type);
-			if (bt->kind == Type_Struct) {
-				if (bt->Struct.polymorphic_params != nullptr) {
-					operand->value = exact_value_i64(bt->Struct.polymorphic_params->Tuple.variables.count);
-				}
-			} else if (bt->kind == Type_Union) {
-				if (bt->Union.polymorphic_params != nullptr) {
-					operand->value = exact_value_i64(bt->Union.polymorphic_params->Tuple.variables.count);
-				}
+			TypeTuple *tuple = get_record_polymorphic_params(operand->type);
+			if (tuple) {
+				operand->value = exact_value_i64(tuple->variables.count);
 			} else {
 			} else {
 				error(operand->expr, "Expected a record type for '%.*s'", LIT(builtin_name));
 				error(operand->expr, "Expected a record type for '%.*s'", LIT(builtin_name));
 			}
 			}
@@ -5952,20 +5949,11 @@ gb_internal bool check_builtin_procedure(CheckerContext *c, Operand *operand, As
 			Entity *param = nullptr;
 			Entity *param = nullptr;
 			i64 count = 0;
 			i64 count = 0;
 
 
-			Type *bt = base_type(operand->type);
-			if (bt->kind == Type_Struct) {
-				if (bt->Struct.polymorphic_params != nullptr) {
-					count = bt->Struct.polymorphic_params->Tuple.variables.count;
-					if (index < count) {
-						param = bt->Struct.polymorphic_params->Tuple.variables[cast(isize)index];
-					}
-				}
-			} else if (bt->kind == Type_Union) {
-				if (bt->Union.polymorphic_params != nullptr) {
-					count = bt->Union.polymorphic_params->Tuple.variables.count;
-					if (index < count) {
-						param = bt->Union.polymorphic_params->Tuple.variables[cast(isize)index];
-					}
+			TypeTuple *tuple = get_record_polymorphic_params(operand->type);
+			if (tuple) {
+				count = tuple->variables.count;
+				if (index < count) {
+					param = tuple->variables[cast(isize)index];
 				}
 				}
 			} else {
 			} else {
 				error(operand->expr, "Expected a specialized polymorphic record type for '%.*s'", LIT(builtin_name));
 				error(operand->expr, "Expected a specialized polymorphic record type for '%.*s'", LIT(builtin_name));

+ 6 - 0
src/check_decl.cpp

@@ -1264,6 +1264,9 @@ gb_internal void check_global_variable_decl(CheckerContext *ctx, Entity *&e, Ast
 	if (ac.is_static) {
 	if (ac.is_static) {
 		error(e->token, "@(static) is not supported for global variables, nor required");
 		error(e->token, "@(static) is not supported for global variables, nor required");
 	}
 	}
+	if (ac.rodata) {
+		e->Variable.is_rodata = true;
+	}
 	ac.link_name = handle_link_name(ctx, e->token, ac.link_name, ac.link_prefix, ac.link_suffix);
 	ac.link_name = handle_link_name(ctx, e->token, ac.link_name, ac.link_prefix, ac.link_suffix);
 
 
 	if (is_arch_wasm() && e->Variable.thread_local_model.len != 0) {
 	if (is_arch_wasm() && e->Variable.thread_local_model.len != 0) {
@@ -1350,6 +1353,9 @@ gb_internal void check_global_variable_decl(CheckerContext *ctx, Entity *&e, Ast
 	Operand o = {};
 	Operand o = {};
 	check_expr_with_type_hint(ctx, &o, init_expr, e->type);
 	check_expr_with_type_hint(ctx, &o, init_expr, e->type);
 	check_init_variable(ctx, e, &o, str_lit("variable declaration"));
 	check_init_variable(ctx, e, &o, str_lit("variable declaration"));
+	if (e->Variable.is_rodata && o.mode != Addressing_Constant) {
+		error(o.expr, "Variables declared with @(rodata) must have constant initialization");
+	}
 
 
 	check_rtti_type_disallowed(e->token, e->type, "A variable declaration is using a type, %s, which has been disallowed");
 	check_rtti_type_disallowed(e->token, e->type, "A variable declaration is using a type, %s, which has been disallowed");
 }
 }

+ 29 - 11
src/check_expr.cpp

@@ -281,8 +281,20 @@ gb_internal void error_operand_not_expression(Operand *o) {
 
 
 gb_internal void error_operand_no_value(Operand *o) {
 gb_internal void error_operand_no_value(Operand *o) {
 	if (o->mode == Addressing_NoValue) {
 	if (o->mode == Addressing_NoValue) {
-		gbString err = expr_to_string(o->expr);
 		Ast *x = unparen_expr(o->expr);
 		Ast *x = unparen_expr(o->expr);
+
+		if (x->kind == Ast_CallExpr) {
+			Ast *p = unparen_expr(x->CallExpr.proc);
+			if (p->kind == Ast_BasicDirective) {
+				String tag = p->BasicDirective.name.string;
+				if (tag == "panic" ||
+				    tag == "assert") {
+					return;
+				}
+			}
+		}
+
+		gbString err = expr_to_string(o->expr);
 		if (x->kind == Ast_CallExpr) {
 		if (x->kind == Ast_CallExpr) {
 			error(o->expr, "'%s' call does not return a value and cannot be used as a value", err);
 			error(o->expr, "'%s' call does not return a value and cannot be used as a value", err);
 		} else {
 		} else {
@@ -3338,6 +3350,9 @@ gb_internal void check_cast(CheckerContext *c, Operand *x, Type *type) {
 	if (is_type_untyped(x->type)) {
 	if (is_type_untyped(x->type)) {
 		Type *final_type = type;
 		Type *final_type = type;
 		if (is_const_expr && !is_type_constant_type(type)) {
 		if (is_const_expr && !is_type_constant_type(type)) {
+			if (is_type_union(type)) {
+				convert_to_typed(c, x, type);
+			}
 			final_type = default_type(x->type);
 			final_type = default_type(x->type);
 		}
 		}
 		update_untyped_expr_type(c, x->expr, final_type, true);
 		update_untyped_expr_type(c, x->expr, final_type, true);
@@ -4286,7 +4301,8 @@ gb_internal void convert_to_typed(CheckerContext *c, Operand *operand, Type *tar
 		} else {
 		} else {
 			switch (operand->type->Basic.kind) {
 			switch (operand->type->Basic.kind) {
 			case Basic_UntypedBool:
 			case Basic_UntypedBool:
-				if (!is_type_boolean(target_type)) {
+				if (!is_type_boolean(target_type) &&
+				    !is_type_integer(target_type)) {
 					operand->mode = Addressing_Invalid;
 					operand->mode = Addressing_Invalid;
 					convert_untyped_error(c, operand, target_type);
 					convert_untyped_error(c, operand, target_type);
 					return;
 					return;
@@ -7331,14 +7347,9 @@ gb_internal CallArgumentError check_polymorphic_record_type(CheckerContext *c, O
 			gbString s = gb_string_make_reserve(heap_allocator(), e->token.string.len+3);
 			gbString s = gb_string_make_reserve(heap_allocator(), e->token.string.len+3);
 			s = gb_string_append_fmt(s, "%.*s(", LIT(e->token.string));
 			s = gb_string_append_fmt(s, "%.*s(", LIT(e->token.string));
 
 
-			Type *params = nullptr;
-			switch (bt->kind) {
-			case Type_Struct: params = bt->Struct.polymorphic_params; break;
-			case Type_Union:  params = bt->Union.polymorphic_params;  break;
-			}
-
-			if (params != nullptr) for_array(i, params->Tuple.variables) {
-				Entity *v = params->Tuple.variables[i];
+			TypeTuple *tuple = get_record_polymorphic_params(e->type);
+			if (tuple != nullptr) for_array(i, tuple->variables) {
+				Entity *v = tuple->variables[i];
 				String name = v->token.string;
 				String name = v->token.string;
 				if (i > 0) {
 				if (i > 0) {
 					s = gb_string_append_fmt(s, ", ");
 					s = gb_string_append_fmt(s, ", ");
@@ -8342,7 +8353,7 @@ gb_internal ExprKind check_basic_directive_expr(CheckerContext *c, Operand *o, A
 		    name == "assert" ||
 		    name == "assert" ||
 		    name == "defined" ||
 		    name == "defined" ||
 		    name == "config" ||
 		    name == "config" ||
-			name == "exists" ||
+		    name == "exists" ||
 		    name == "load" ||
 		    name == "load" ||
 		    name == "load_hash" ||
 		    name == "load_hash" ||
 		    name == "load_directory" ||
 		    name == "load_directory" ||
@@ -8866,6 +8877,10 @@ gb_internal void check_compound_literal_field_values(CheckerContext *c, Slice<As
 					case Type_Array:
 					case Type_Array:
 						ft = bt->Array.elem;
 						ft = bt->Array.elem;
 						break;
 						break;
+					case Type_BitField:
+						is_constant = false;
+						ft = bt->BitField.fields[index]->type;
+						break;
 					default:
 					default:
 						GB_PANIC("invalid type: %s", type_to_string(ft));
 						GB_PANIC("invalid type: %s", type_to_string(ft));
 						break;
 						break;
@@ -8892,6 +8907,9 @@ gb_internal void check_compound_literal_field_values(CheckerContext *c, Slice<As
 				case Type_Array:
 				case Type_Array:
 					nested_ft = bt->Array.elem;
 					nested_ft = bt->Array.elem;
 					break;
 					break;
+				case Type_BitField:
+					nested_ft = bt->BitField.fields[index]->type;
+					break;
 				default:
 				default:
 					GB_PANIC("invalid type %s", type_to_string(nested_ft));
 					GB_PANIC("invalid type %s", type_to_string(nested_ft));
 					break;
 					break;

+ 14 - 2
src/check_stmt.cpp

@@ -501,6 +501,9 @@ gb_internal Type *check_assignment_variable(CheckerContext *ctx, Operand *lhs, O
 		return nullptr;
 		return nullptr;
 
 
 	case Addressing_Variable:
 	case Addressing_Variable:
+		if (e && e->kind == Entity_Variable && e->Variable.is_rodata) {
+			error(lhs->expr, "Assignment to variable '%.*s' marked as @(rodata) is not allowed", LIT(e->token.string));
+		}
 		break;
 		break;
 
 
 	case Addressing_MapIndex: {
 	case Addressing_MapIndex: {
@@ -1252,8 +1255,6 @@ gb_internal void check_switch_stmt(CheckerContext *ctx, Ast *node, u32 mod_flags
 					error_line("\t%.*s\n", LIT(f->token.string));
 					error_line("\t%.*s\n", LIT(f->token.string));
 				}
 				}
 			}
 			}
-			error_line("\n");
-
 			error_line("\tSuggestion: Was '#partial switch' wanted?\n");
 			error_line("\tSuggestion: Was '#partial switch' wanted?\n");
 		}
 		}
 	}
 	}
@@ -2055,6 +2056,13 @@ gb_internal void check_value_decl_stmt(CheckerContext *ctx, Ast *node, u32 mod_f
 				}
 				}
 			}
 			}
 		}
 		}
+		if (ac.rodata) {
+			if (ac.is_static) {
+				e->Variable.is_rodata = true;
+			} else {
+				error(e->token, "Only global or @(static) variables can have @(rodata) applied");
+			}
+		}
 		if (ac.thread_local_model != "") {
 		if (ac.thread_local_model != "") {
 			String name = e->token.string;
 			String name = e->token.string;
 			if (name == "_") {
 			if (name == "_") {
@@ -2493,6 +2501,10 @@ gb_internal void check_return_stmt(CheckerContext *ctx, Ast *node) {
 					unsafe_return_error(o, "the address of an indexed variable", f->type);
 					unsafe_return_error(o, "the address of an indexed variable", f->type);
 				}
 				}
 			}
 			}
+		} else if (o.mode == Addressing_Constant && is_type_slice(o.type)) {
+			ERROR_BLOCK();
+			unsafe_return_error(o, "a compound literal of a slice");
+			error_line("\tNote: A constant slice value will use the memory of the current stack frame\n");
 		}
 		}
 	}
 	}
 
 

+ 10 - 18
src/check_type.cpp

@@ -564,19 +564,7 @@ gb_internal bool check_record_poly_operand_specialization(CheckerContext *ctx, T
 gb_internal Entity *find_polymorphic_record_entity(GenTypesData *found_gen_types, isize param_count, Array<Operand> const &ordered_operands) {
 gb_internal Entity *find_polymorphic_record_entity(GenTypesData *found_gen_types, isize param_count, Array<Operand> const &ordered_operands) {
 	for (Entity *e : found_gen_types->types) {
 	for (Entity *e : found_gen_types->types) {
 		Type *t = base_type(e->type);
 		Type *t = base_type(e->type);
-		TypeTuple *tuple = nullptr;
-		switch (t->kind) {
-		case Type_Struct:
-			if (t->Struct.polymorphic_params) {
-				tuple = &t->Struct.polymorphic_params->Tuple;
-			}
-			break;
-		case Type_Union:
-			if (t->Union.polymorphic_params) {
-				tuple = &t->Union.polymorphic_params->Tuple;
-			}
-			break;
-		}
+		TypeTuple *tuple = get_record_polymorphic_params(t);
 		GB_ASSERT_MSG(tuple != nullptr, "%s :: %s", type_to_string(e->type), type_to_string(t));
 		GB_ASSERT_MSG(tuple != nullptr, "%s :: %s", type_to_string(e->type), type_to_string(t));
 		GB_ASSERT(param_count == tuple->variables.count);
 		GB_ASSERT(param_count == tuple->variables.count);
 
 
@@ -663,6 +651,8 @@ gb_internal void check_struct_type(CheckerContext *ctx, Type *struct_type, Ast *
 		&struct_type->Struct.is_polymorphic,
 		&struct_type->Struct.is_polymorphic,
 		node, poly_operands
 		node, poly_operands
 	);
 	);
+	wait_signal_set(&struct_type->Struct.polymorphic_wait_signal);
+
 	struct_type->Struct.is_poly_specialized = check_record_poly_operand_specialization(ctx, struct_type, poly_operands, &struct_type->Struct.is_polymorphic);
 	struct_type->Struct.is_poly_specialized = check_record_poly_operand_specialization(ctx, struct_type, poly_operands, &struct_type->Struct.is_polymorphic);
 	if (original_type_for_poly) {
 	if (original_type_for_poly) {
 		GB_ASSERT(named_type != nullptr);
 		GB_ASSERT(named_type != nullptr);
@@ -712,6 +702,8 @@ gb_internal void check_union_type(CheckerContext *ctx, Type *union_type, Ast *no
 		&union_type->Union.is_polymorphic,
 		&union_type->Union.is_polymorphic,
 		node, poly_operands
 		node, poly_operands
 	);
 	);
+	wait_signal_set(&union_type->Union.polymorphic_wait_signal);
+
 	union_type->Union.is_poly_specialized = check_record_poly_operand_specialization(ctx, union_type, poly_operands, &union_type->Union.is_polymorphic);
 	union_type->Union.is_poly_specialized = check_record_poly_operand_specialization(ctx, union_type, poly_operands, &union_type->Union.is_polymorphic);
 	if (original_type_for_poly) {
 	if (original_type_for_poly) {
 		GB_ASSERT(named_type != nullptr);
 		GB_ASSERT(named_type != nullptr);
@@ -784,7 +776,7 @@ gb_internal void check_union_type(CheckerContext *ctx, Type *union_type, Ast *no
 			}
 			}
 		}
 		}
 		if (variants.count < 2) {
 		if (variants.count < 2) {
-			error(ut->align, "A union with #no_nil must have at least 2 variants");
+			error(node, "A union with #no_nil must have at least 2 variants");
 		}
 		}
 		break;
 		break;
 	}
 	}
@@ -1457,8 +1449,8 @@ gb_internal bool check_type_specialization_to(CheckerContext *ctx, Type *special
 		    s->Struct.polymorphic_params != nullptr &&
 		    s->Struct.polymorphic_params != nullptr &&
 		    t->Struct.polymorphic_params != nullptr) {
 		    t->Struct.polymorphic_params != nullptr) {
 
 
-			TypeTuple *s_tuple = &s->Struct.polymorphic_params->Tuple;
-			TypeTuple *t_tuple = &t->Struct.polymorphic_params->Tuple;
+			TypeTuple *s_tuple = get_record_polymorphic_params(s);
+			TypeTuple *t_tuple = get_record_polymorphic_params(t);
 			GB_ASSERT(t_tuple->variables.count == s_tuple->variables.count);
 			GB_ASSERT(t_tuple->variables.count == s_tuple->variables.count);
 			for_array(i, s_tuple->variables) {
 			for_array(i, s_tuple->variables) {
 				Entity *s_e = s_tuple->variables[i];
 				Entity *s_e = s_tuple->variables[i];
@@ -1510,8 +1502,8 @@ gb_internal bool check_type_specialization_to(CheckerContext *ctx, Type *special
 		    s->Union.polymorphic_params != nullptr &&
 		    s->Union.polymorphic_params != nullptr &&
 		    t->Union.polymorphic_params != nullptr) {
 		    t->Union.polymorphic_params != nullptr) {
 
 
-			TypeTuple *s_tuple = &s->Union.polymorphic_params->Tuple;
-			TypeTuple *t_tuple = &t->Union.polymorphic_params->Tuple;
+			TypeTuple *s_tuple = get_record_polymorphic_params(s);
+			TypeTuple *t_tuple = get_record_polymorphic_params(t);
 			GB_ASSERT(t_tuple->variables.count == s_tuple->variables.count);
 			GB_ASSERT(t_tuple->variables.count == s_tuple->variables.count);
 			for_array(i, s_tuple->variables) {
 			for_array(i, s_tuple->variables) {
 				Entity *s_e = s_tuple->variables[i];
 				Entity *s_e = s_tuple->variables[i];

+ 6 - 0
src/checker.cpp

@@ -3628,6 +3628,12 @@ gb_internal DECL_ATTRIBUTE_PROC(var_decl_attribute) {
 		}
 		}
 		ac->is_static = true;
 		ac->is_static = true;
 		return true;
 		return true;
+	} else if (name == "rodata") {
+		if (value != nullptr) {
+			error(elem, "'rodata' does not have any parameters");
+		}
+		ac->rodata = true;
+		return true;
 	} else if (name == "thread_local") {
 	} else if (name == "thread_local") {
 		ExactValue ev = check_decl_attribute_value(c, value);
 		ExactValue ev = check_decl_attribute_value(c, value);
 		if (ac->init_expr_list_count > 0) {
 		if (ac->init_expr_list_count > 0) {

+ 1 - 0
src/checker.hpp

@@ -133,6 +133,7 @@ struct AttributeContext {
 	bool    entry_point_only      : 1;
 	bool    entry_point_only      : 1;
 	bool    instrumentation_enter : 1;
 	bool    instrumentation_enter : 1;
 	bool    instrumentation_exit  : 1;
 	bool    instrumentation_exit  : 1;
+	bool    rodata                : 1;
 	u32 optimization_mode; // ProcedureOptimizationMode
 	u32 optimization_mode; // ProcedureOptimizationMode
 	i64 foreign_import_priority_index;
 	i64 foreign_import_priority_index;
 	String extra_linker_flags;
 	String extra_linker_flags;

+ 1 - 0
src/entity.cpp

@@ -230,6 +230,7 @@ struct Entity {
 			bool       is_foreign;
 			bool       is_foreign;
 			bool       is_export;
 			bool       is_export;
 			bool       is_global;
 			bool       is_global;
+			bool       is_rodata;
 		} Variable;
 		} Variable;
 		struct {
 		struct {
 			Type * type_parameter_specialization;
 			Type * type_parameter_specialization;

+ 33 - 32
src/error.cpp

@@ -390,8 +390,6 @@ gb_internal void error_va(TokenPos const &pos, TokenPos end, char const *fmt, va
 			error_out_empty();
 			error_out_empty();
 		} else {
 		} else {
 			error_out_pos(pos);
 			error_out_pos(pos);
-		}
-		if (has_ansi_terminal_colours()) {
 			error_out_coloured("Error: ", TerminalStyle_Normal, TerminalColour_Red);
 			error_out_coloured("Error: ", TerminalStyle_Normal, TerminalColour_Red);
 		}
 		}
 		error_out_va(fmt, va);
 		error_out_va(fmt, va);
@@ -407,29 +405,31 @@ gb_internal void warning_va(TokenPos const &pos, TokenPos end, char const *fmt,
 		error_va(pos, end, fmt, va);
 		error_va(pos, end, fmt, va);
 		return;
 		return;
 	}
 	}
+	if (global_ignore_warnings()) {
+		return;
+	}
+
 	global_error_collector.warning_count.fetch_add(1);
 	global_error_collector.warning_count.fetch_add(1);
 	mutex_lock(&global_error_collector.mutex);
 	mutex_lock(&global_error_collector.mutex);
 
 
 	push_error_value(pos, ErrorValue_Warning);
 	push_error_value(pos, ErrorValue_Warning);
 
 
-	if (!global_ignore_warnings()) {
-		if (pos.line == 0) {
+	if (pos.line == 0) {
+		error_out_empty();
+		error_out_coloured("Warning: ", TerminalStyle_Normal, TerminalColour_Yellow);
+		error_out_va(fmt, va);
+		error_out("\n");
+	} else {
+		// global_error_collector.prev = pos;
+		if (json_errors()) {
 			error_out_empty();
 			error_out_empty();
-			error_out_coloured("Warning: ", TerminalStyle_Normal, TerminalColour_Yellow);
-			error_out_va(fmt, va);
-			error_out("\n");
 		} else {
 		} else {
-			// global_error_collector.prev = pos;
-			if (json_errors()) {
-				error_out_empty();
-			} else {
-				error_out_pos(pos);
-			}
+			error_out_pos(pos);
 			error_out_coloured("Warning: ", TerminalStyle_Normal, TerminalColour_Yellow);
 			error_out_coloured("Warning: ", TerminalStyle_Normal, TerminalColour_Yellow);
-			error_out_va(fmt, va);
-			error_out("\n");
-			show_error_on_line(pos, end);
 		}
 		}
+		error_out_va(fmt, va);
+		error_out("\n");
+		show_error_on_line(pos, end);
 	}
 	}
 	try_pop_error_value();
 	try_pop_error_value();
 	mutex_unlock(&global_error_collector.mutex);
 	mutex_unlock(&global_error_collector.mutex);
@@ -544,30 +544,31 @@ gb_internal void syntax_warning_va(TokenPos const &pos, TokenPos end, char const
 		syntax_error_va(pos, end, fmt, va);
 		syntax_error_va(pos, end, fmt, va);
 		return;
 		return;
 	}
 	}
+	if (global_ignore_warnings()) {
+		return;
+	}
 	mutex_lock(&global_error_collector.mutex);
 	mutex_lock(&global_error_collector.mutex);
 	global_error_collector.warning_count++;
 	global_error_collector.warning_count++;
 
 
 
 
 	push_error_value(pos, ErrorValue_Warning);
 	push_error_value(pos, ErrorValue_Warning);
 
 
-	if (!global_ignore_warnings()) {
-		if (pos.line == 0) {
+	if (pos.line == 0) {
+		error_out_empty();
+		error_out_coloured("Syntax Warning: ", TerminalStyle_Normal, TerminalColour_Yellow);
+		error_out_va(fmt, va);
+		error_out("\n");
+	} else {
+		// global_error_collector.prev = pos;
+		if (json_errors()) {
 			error_out_empty();
 			error_out_empty();
-			error_out_coloured("Syntax Warning: ", TerminalStyle_Normal, TerminalColour_Yellow);
-			error_out_va(fmt, va);
-			error_out("\n");
 		} else {
 		} else {
-			// global_error_collector.prev = pos;
-			if (json_errors()) {
-				error_out_empty();
-			} else {
-				error_out_pos(pos);
-			}
-			error_out_coloured("Syntax Warning: ", TerminalStyle_Normal, TerminalColour_Yellow);
-			error_out_va(fmt, va);
-			error_out("\n");
-			// show_error_on_line(pos, end);
+			error_out_pos(pos);
 		}
 		}
+		error_out_coloured("Syntax Warning: ", TerminalStyle_Normal, TerminalColour_Yellow);
+		error_out_va(fmt, va);
+		error_out("\n");
+		// show_error_on_line(pos, end);
 	}
 	}
 
 
 	try_pop_error_value();
 	try_pop_error_value();
@@ -838,4 +839,4 @@ gb_internal void print_all_errors(void) {
 	gb_file_write(f, res, gb_string_length(res));
 	gb_file_write(f, res, gb_string_length(res));
 
 
 	errors_already_printed = true;
 	errors_already_printed = true;
-}
+}

+ 3 - 0
src/gb/gb.h

@@ -256,6 +256,7 @@ extern "C" {
 
 
 #if defined(GB_SYSTEM_NETBSD)
 #if defined(GB_SYSTEM_NETBSD)
 	#include <stdio.h>
 	#include <stdio.h>
+	#include <lwp.h>
 	#define lseek64 lseek
 	#define lseek64 lseek
 #endif
 #endif
 
 
@@ -3027,6 +3028,8 @@ gb_inline u32 gb_thread_current_id(void) {
 	thread_id = find_thread(NULL);
 	thread_id = find_thread(NULL);
 #elif defined(GB_SYSTEM_FREEBSD)
 #elif defined(GB_SYSTEM_FREEBSD)
 	thread_id = pthread_getthreadid_np();
 	thread_id = pthread_getthreadid_np();
+#elif defined(GB_SYSTEM_NETBSD)
+	thread_id = (u32)_lwp_self();
 #else
 #else
 	#error Unsupported architecture for gb_thread_current_id()
 	#error Unsupported architecture for gb_thread_current_id()
 #endif
 #endif

+ 27 - 13
src/linker.cpp

@@ -13,6 +13,7 @@ struct LinkerData {
 };
 };
 
 
 gb_internal i32 system_exec_command_line_app(char const *name, char const *fmt, ...);
 gb_internal i32 system_exec_command_line_app(char const *name, char const *fmt, ...);
+gb_internal bool system_exec_command_line_app_output(char const *command, gbString *output);
 
 
 #if defined(GB_SYSTEM_OSX)
 #if defined(GB_SYSTEM_OSX)
 gb_internal void linker_enable_system_library_linking(LinkerData *ld) {
 gb_internal void linker_enable_system_library_linking(LinkerData *ld) {
@@ -69,27 +70,40 @@ gb_internal i32 linker_stage(LinkerData *gen) {
 	if (is_arch_wasm()) {
 	if (is_arch_wasm()) {
 		timings_start_section(timings, str_lit("wasm-ld"));
 		timings_start_section(timings, str_lit("wasm-ld"));
 
 
-		String extra_orca_flags = {};
+		gbString extra_orca_flags = gb_string_make(temporary_allocator(), "");
+
+		gbString inputs = gb_string_make(temporary_allocator(), "");
+		inputs = gb_string_append_fmt(inputs, "\"%.*s.o\"", LIT(output_filename));
 
 
-	#if defined(GB_SYSTEM_WINDOWS)
 		if (build_context.metrics.os == TargetOs_orca) {
 		if (build_context.metrics.os == TargetOs_orca) {
-			extra_orca_flags = str_lit(" W:/orca/installation/dev-afb9591/bin/liborca_wasm.a --export-dynamic");
+			gbString orca_sdk_path = gb_string_make(temporary_allocator(), "");
+			if (!system_exec_command_line_app_output("orca sdk-path", &orca_sdk_path)) {
+				gb_printf_err("executing `orca sdk-path` failed, make sure Orca is installed and added to your path\n");
+				return 1;
+			}
+			if (gb_string_length(orca_sdk_path) == 0) {
+				gb_printf_err("executing `orca sdk-path` did not produce output\n");
+				return 1;
+			}
+			inputs = gb_string_append_fmt(inputs, " \"%s/orca-libc/lib/crt1.o\" \"%s/orca-libc/lib/libc.o\"", orca_sdk_path, orca_sdk_path);
+
+			extra_orca_flags = gb_string_append_fmt(extra_orca_flags, " -L \"%s/bin\" -lorca_wasm --export-dynamic", orca_sdk_path);
 		}
 		}
 
 
+
+	#if defined(GB_SYSTEM_WINDOWS)
 		result = system_exec_command_line_app("wasm-ld",
 		result = system_exec_command_line_app("wasm-ld",
-			"\"%.*s\\bin\\wasm-ld\" \"%.*s.o\" -o \"%.*s\" %.*s %.*s %.*s",
+			"\"%.*s\\bin\\wasm-ld\" %s -o \"%.*s\" %.*s %.*s %s",
 			LIT(build_context.ODIN_ROOT),
 			LIT(build_context.ODIN_ROOT),
-			LIT(output_filename), LIT(output_filename), LIT(build_context.link_flags), LIT(build_context.extra_linker_flags),
-			LIT(extra_orca_flags));
+			inputs, LIT(output_filename), LIT(build_context.link_flags), LIT(build_context.extra_linker_flags),
+			extra_orca_flags);
 	#else
 	#else
-		if (build_context.metrics.os == TargetOs_orca) {
-					extra_orca_flags = str_lit(" -L . -lorca --export-dynamic");
-				}
-
 		result = system_exec_command_line_app("wasm-ld",
 		result = system_exec_command_line_app("wasm-ld",
-			"wasm-ld \"%.*s.o\" -o \"%.*s\" %.*s %.*s %.*s",
-			LIT(output_filename), LIT(output_filename), LIT(build_context.link_flags), LIT(build_context.extra_linker_flags),
-			LIT(extra_orca_flags));
+			"wasm-ld %s -o \"%.*s\" %.*s %.*s %s",
+			inputs, LIT(output_filename),
+			LIT(build_context.link_flags),
+			LIT(build_context.extra_linker_flags),
+			extra_orca_flags);
 	#endif
 	#endif
 		return result;
 		return result;
 	}
 	}

+ 9 - 1
src/llvm_abi.cpp

@@ -900,7 +900,15 @@ namespace lbAbiAmd64SysV {
 		}
 		}
 
 
 		switch (LLVMGetTypeKind(t)) {
 		switch (LLVMGetTypeKind(t)) {
-		case LLVMIntegerTypeKind:
+		case LLVMIntegerTypeKind: {
+			i64 s = t_size;
+			while (s > 0) {
+				unify(cls, ix + off/8, RegClass_Int);
+				off += 8;
+				s   -= 8;
+			}
+			break;
+		}
 		case LLVMPointerTypeKind:
 		case LLVMPointerTypeKind:
 		case LLVMHalfTypeKind:
 		case LLVMHalfTypeKind:
 			unify(cls, ix + off/8, RegClass_Int);
 			unify(cls, ix + off/8, RegClass_Int);

+ 18 - 2
src/llvm_backend.cpp

@@ -1160,6 +1160,10 @@ gb_internal lbProcedure *lb_create_startup_runtime(lbModule *main_module, lbProc
 				if (is_type_untyped_nil(init.type)) {
 				if (is_type_untyped_nil(init.type)) {
 					LLVMSetInitializer(var.var.value, LLVMConstNull(global_type));
 					LLVMSetInitializer(var.var.value, LLVMConstNull(global_type));
 					var.is_initialized = true;
 					var.is_initialized = true;
+
+					if (e->Variable.is_rodata) {
+						LLVMSetGlobalConstant(var.var.value, true);
+					}
 					continue;
 					continue;
 				}
 				}
 				GB_PANIC("Invalid init value, got %s", expr_to_string(init_expr));
 				GB_PANIC("Invalid init value, got %s", expr_to_string(init_expr));
@@ -1174,6 +1178,10 @@ gb_internal lbProcedure *lb_create_startup_runtime(lbModule *main_module, lbProc
 					}
 					}
 					LLVMSetInitializer(var.var.value, init.value);
 					LLVMSetInitializer(var.var.value, init.value);
 					var.is_initialized = true;
 					var.is_initialized = true;
+
+					if (e->Variable.is_rodata) {
+						LLVMSetGlobalConstant(var.var.value, true);
+					}
 					continue;
 					continue;
 				}
 				}
 			} else {
 			} else {
@@ -1206,8 +1214,9 @@ gb_internal lbProcedure *lb_create_startup_runtime(lbModule *main_module, lbProc
 
 
 			var.is_initialized = true;
 			var.is_initialized = true;
 		}
 		}
+
+
 	}
 	}
-	
 	CheckerInfo *info = main_module->gen->info;
 	CheckerInfo *info = main_module->gen->info;
 	
 	
 	for (Entity *e : info->init_procedures) {
 	for (Entity *e : info->init_procedures) {
@@ -3210,14 +3219,21 @@ gb_internal bool lb_generate_code(lbGenerator *gen) {
 						lbValue init = lb_const_value(m, tav.type, v);
 						lbValue init = lb_const_value(m, tav.type, v);
 						LLVMSetInitializer(g.value, init.value);
 						LLVMSetInitializer(g.value, init.value);
 						var.is_initialized = true;
 						var.is_initialized = true;
+						if (e->kind == Entity_Variable && e->Variable.is_rodata) {
+							LLVMSetGlobalConstant(g.value, true);
+						}
 					}
 					}
 				}
 				}
 			}
 			}
 			if (!var.is_initialized && is_type_untyped_nil(tav.type)) {
 			if (!var.is_initialized && is_type_untyped_nil(tav.type)) {
 				var.is_initialized = true;
 				var.is_initialized = true;
+				if (e->kind == Entity_Variable && e->Variable.is_rodata) {
+					LLVMSetGlobalConstant(g.value, true);
+				}
 			}
 			}
+		} else if (e->kind == Entity_Variable && e->Variable.is_rodata) {
+			LLVMSetGlobalConstant(g.value, true);
 		}
 		}
-
 		array_add(&global_variables, var);
 		array_add(&global_variables, var);
 
 
 		lb_add_entity(m, e, g);
 		lb_add_entity(m, e, g);

+ 51 - 4
src/llvm_backend_expr.cpp

@@ -504,6 +504,10 @@ gb_internal bool lb_is_matrix_simdable(Type *t) {
 	if ((mt->Matrix.row_count & 1) ^ (mt->Matrix.column_count & 1)) {
 	if ((mt->Matrix.row_count & 1) ^ (mt->Matrix.column_count & 1)) {
 		return false;
 		return false;
 	}
 	}
+	if (mt->Matrix.is_row_major) {
+		// TODO(bill): make #row_major matrices work with SIMD
+		return false;
+	}
 	
 	
 	if (elem->kind == Type_Basic) {
 	if (elem->kind == Type_Basic) {
 		switch (elem->Basic.kind) {
 		switch (elem->Basic.kind) {
@@ -1869,13 +1873,40 @@ gb_internal lbValue lb_emit_conv(lbProcedure *p, lbValue value, Type *t) {
 			lbValue res_i128 = lb_emit_runtime_call(p, call, args);
 			lbValue res_i128 = lb_emit_runtime_call(p, call, args);
 			return lb_emit_conv(p, res_i128, t);
 			return lb_emit_conv(p, res_i128, t);
 		}
 		}
+		i64 sz = type_size_of(src);
 
 
 		lbValue res = {};
 		lbValue res = {};
 		res.type = t;
 		res.type = t;
 		if (is_type_unsigned(dst)) {
 		if (is_type_unsigned(dst)) {
-			res.value = LLVMBuildFPToUI(p->builder, value.value, lb_type(m, t), "");
+			switch (sz) {
+			case 2:
+			case 4:
+				res.value = LLVMBuildFPToUI(p->builder, value.value, lb_type(m, t_u32), "");
+				res.value = LLVMBuildIntCast2(p->builder, res.value, lb_type(m, t), false, "");
+				break;
+			case 8:
+				res.value = LLVMBuildFPToUI(p->builder, value.value, lb_type(m, t_u64), "");
+				res.value = LLVMBuildIntCast2(p->builder, res.value, lb_type(m, t), false, "");
+				break;
+			default:
+				GB_PANIC("Unhandled float type");
+				break;
+			}
 		} else {
 		} else {
-			res.value = LLVMBuildFPToSI(p->builder, value.value, lb_type(m, t), "");
+			switch (sz) {
+			case 2:
+			case 4:
+				res.value = LLVMBuildFPToSI(p->builder, value.value, lb_type(m, t_i32), "");
+				res.value = LLVMBuildIntCast2(p->builder, res.value, lb_type(m, t), true, "");
+				break;
+			case 8:
+				res.value = LLVMBuildFPToSI(p->builder, value.value, lb_type(m, t_i64), "");
+				res.value = LLVMBuildIntCast2(p->builder, res.value, lb_type(m, t), true, "");
+				break;
+			default:
+				GB_PANIC("Unhandled float type");
+				break;
+			}
 		}
 		}
 		return res;
 		return res;
 	}
 	}
@@ -4533,10 +4564,26 @@ gb_internal lbAddr lb_build_addr_compound_lit(lbProcedure *p, Ast *expr) {
 						if (lb_is_nested_possibly_constant(type, sel, elem)) {
 						if (lb_is_nested_possibly_constant(type, sel, elem)) {
 							continue;
 							continue;
 						}
 						}
-						lbValue dst = lb_emit_deep_field_gep(p, comp_lit_ptr, sel);
 						field_expr = lb_build_expr(p, elem);
 						field_expr = lb_build_expr(p, elem);
 						field_expr = lb_emit_conv(p, field_expr, sel.entity->type);
 						field_expr = lb_emit_conv(p, field_expr, sel.entity->type);
-						lb_emit_store(p, dst, field_expr);
+						if (sel.is_bit_field) {
+							Selection sub_sel = trim_selection(sel);
+							lbValue trimmed_dst = lb_emit_deep_field_gep(p, comp_lit_ptr, sub_sel);
+							Type *bf = base_type(type_deref(trimmed_dst.type));
+							if (is_type_pointer(bf)) {
+								trimmed_dst = lb_emit_load(p, trimmed_dst);
+								bf = base_type(type_deref(trimmed_dst.type));
+							}
+							GB_ASSERT(bf->kind == Type_BitField);
+
+							isize idx = sel.index[sel.index.count-1];
+							lbAddr dst = lb_addr_bit_field(trimmed_dst, bf->BitField.fields[idx]->type, bf->BitField.bit_offsets[idx], bf->BitField.bit_sizes[idx]);
+							lb_addr_store(p, dst, field_expr);
+
+						} else {
+							lbValue dst = lb_emit_deep_field_gep(p, comp_lit_ptr, sel);
+							lb_emit_store(p, dst, field_expr);
+						}
 						continue;
 						continue;
 					}
 					}
 
 

+ 2 - 2
src/llvm_backend_general.cpp

@@ -1383,8 +1383,6 @@ gb_internal lbValue lb_addr_load(lbProcedure *p, lbAddr const &addr) {
 
 
 		LLVMTypeRef vector_type = nullptr;
 		LLVMTypeRef vector_type = nullptr;
 		if (lb_try_vector_cast(p->module, addr.addr, &vector_type)) {
 		if (lb_try_vector_cast(p->module, addr.addr, &vector_type)) {
-			LLVMSetAlignment(res.addr.value, cast(unsigned)lb_alignof(vector_type));
-
 			LLVMValueRef vp = LLVMBuildPointerCast(p->builder, addr.addr.value, LLVMPointerType(vector_type, 0), "");
 			LLVMValueRef vp = LLVMBuildPointerCast(p->builder, addr.addr.value, LLVMPointerType(vector_type, 0), "");
 			LLVMValueRef v = LLVMBuildLoad2(p->builder, vector_type, vp, "");
 			LLVMValueRef v = LLVMBuildLoad2(p->builder, vector_type, vp, "");
 			LLVMValueRef scalars[4] = {};
 			LLVMValueRef scalars[4] = {};
@@ -1394,6 +1392,8 @@ gb_internal lbValue lb_addr_load(lbProcedure *p, lbAddr const &addr) {
 			LLVMValueRef mask = LLVMConstVector(scalars, addr.swizzle.count);
 			LLVMValueRef mask = LLVMConstVector(scalars, addr.swizzle.count);
 			LLVMValueRef sv = llvm_basic_shuffle(p, v, mask);
 			LLVMValueRef sv = llvm_basic_shuffle(p, v, mask);
 
 
+			LLVMSetAlignment(res.addr.value, cast(unsigned)lb_alignof(LLVMTypeOf(sv)));
+
 			LLVMValueRef dst = LLVMBuildPointerCast(p->builder, ptr.value, LLVMPointerType(LLVMTypeOf(sv), 0), "");
 			LLVMValueRef dst = LLVMBuildPointerCast(p->builder, ptr.value, LLVMPointerType(LLVMTypeOf(sv), 0), "");
 			LLVMBuildStore(p->builder, sv, dst);
 			LLVMBuildStore(p->builder, sv, dst);
 		} else {
 		} else {

+ 2 - 3
src/llvm_backend_proc.cpp

@@ -710,13 +710,12 @@ gb_internal void lb_begin_procedure_body(lbProcedure *p) {
 	lb_set_debug_position_to_procedure_begin(p);
 	lb_set_debug_position_to_procedure_begin(p);
 	if (p->debug_info != nullptr) {
 	if (p->debug_info != nullptr) {
 		if (p->context_stack.count != 0) {
 		if (p->context_stack.count != 0) {
+			lbBlock *prev_block = p->curr_block;
 			p->curr_block = p->decl_block;
 			p->curr_block = p->decl_block;
 			lb_add_debug_context_variable(p, lb_find_or_generate_context_ptr(p));
 			lb_add_debug_context_variable(p, lb_find_or_generate_context_ptr(p));
+			p->curr_block = prev_block;
 		}
 		}
-
 	}
 	}
-
-	lb_start_block(p, p->entry_block);
 }
 }
 
 
 gb_internal void lb_end_procedure_body(lbProcedure *p) {
 gb_internal void lb_end_procedure_body(lbProcedure *p) {

+ 3 - 1
src/llvm_backend_stmt.cpp

@@ -1850,7 +1850,9 @@ gb_internal void lb_build_static_variables(lbProcedure *p, AstValueDecl *vd) {
 		LLVMSetInitializer(global, LLVMConstNull(lb_type(p->module, e->type)));
 		LLVMSetInitializer(global, LLVMConstNull(lb_type(p->module, e->type)));
 		if (value.value != nullptr) {
 		if (value.value != nullptr) {
 			LLVMSetInitializer(global, value.value);
 			LLVMSetInitializer(global, value.value);
-		} else {
+		}
+		if (e->Variable.is_rodata) {
+			LLVMSetGlobalConstant(global, true);
 		}
 		}
 		if (e->Variable.thread_local_model != "") {
 		if (e->Variable.thread_local_model != "") {
 			LLVMSetThreadLocal(global, true);
 			LLVMSetThreadLocal(global, true);

+ 32 - 0
src/main.cpp

@@ -155,6 +155,38 @@ gb_internal i32 system_exec_command_line_app(char const *name, char const *fmt,
 	return exit_code;
 	return exit_code;
 }
 }
 
 
+#if defined(GB_SYSTEM_WINDOWS)
+#define popen _popen
+#define pclose _pclose
+#endif
+
+gb_internal bool system_exec_command_line_app_output(char const *command, gbString *output) {
+	GB_ASSERT(output);
+
+	u8 buffer[256];
+	FILE *stream;
+	stream = popen(command, "r");
+	if (!stream) {
+		return false;
+	}
+	defer (pclose(stream));
+
+	while (!feof(stream)) {
+		size_t n = fread(buffer, 1, 255, stream);
+		*output = gb_string_append_length(*output, buffer, n);
+
+		if (ferror(stream)) {
+			return false;
+		}
+	}
+
+	if (build_context.show_system_calls) {
+		gb_printf_err("[SYSTEM CALL OUTPUT] %s -> %s\n", command, *output);
+	}
+
+	return true;
+}
+
 gb_internal Array<String> setup_args(int argc, char const **argv) {
 gb_internal Array<String> setup_args(int argc, char const **argv) {
 	gbAllocator a = heap_allocator();
 	gbAllocator a = heap_allocator();
 
 

+ 2 - 0
src/threading.cpp

@@ -494,6 +494,8 @@ gb_internal u32 thread_current_id(void) {
 	thread_id = find_thread(NULL);
 	thread_id = find_thread(NULL);
 #elif defined(GB_SYSTEM_FREEBSD)
 #elif defined(GB_SYSTEM_FREEBSD)
 	thread_id = pthread_getthreadid_np();
 	thread_id = pthread_getthreadid_np();
+#elif defined(GB_SYSTEM_NETBSD)
+	thread_id = (u32)_lwp_self();
 #else
 #else
 	#error Unsupported architecture for thread_current_id()
 	#error Unsupported architecture for thread_current_id()
 #endif
 #endif

+ 17 - 2
src/types.cpp

@@ -140,6 +140,7 @@ struct TypeStruct {
 	i64             custom_field_align;
 	i64             custom_field_align;
 	Type *          polymorphic_params; // Type_Tuple
 	Type *          polymorphic_params; // Type_Tuple
 	Type *          polymorphic_parent;
 	Type *          polymorphic_parent;
+	Wait_Signal     polymorphic_wait_signal;
 
 
 	Type *          soa_elem;
 	Type *          soa_elem;
 	i32             soa_count;
 	i32             soa_count;
@@ -167,6 +168,7 @@ struct TypeUnion {
 	i64           custom_align;
 	i64           custom_align;
 	Type *        polymorphic_params; // Type_Tuple
 	Type *        polymorphic_params; // Type_Tuple
 	Type *        polymorphic_parent;
 	Type *        polymorphic_parent;
+	Wait_Signal   polymorphic_wait_signal;
 
 
 	i16           tag_size;
 	i16           tag_size;
 	bool          is_polymorphic;
 	bool          is_polymorphic;
@@ -457,6 +459,15 @@ gb_internal Selection sub_selection(Selection const &sel, isize offset) {
 	return res;
 	return res;
 }
 }
 
 
+gb_internal Selection trim_selection(Selection const &sel) {
+	Selection res = {};
+	res.index.data = sel.index.data;
+	res.index.count = gb_max(sel.index.count - 1, 0);
+	res.index.capacity = res.index.count;
+	return res;
+}
+
+
 gb_global Type basic_types[] = {
 gb_global Type basic_types[] = {
 	{Type_Basic, {Basic_Invalid,           0,                                          0, STR_LIT("invalid type")}},
 	{Type_Basic, {Basic_Invalid,           0,                                          0, STR_LIT("invalid type")}},
 
 
@@ -1084,6 +1095,7 @@ gb_internal Type *alloc_type_struct() {
 gb_internal Type *alloc_type_struct_complete() {
 gb_internal Type *alloc_type_struct_complete() {
 	Type *t = alloc_type(Type_Struct);
 	Type *t = alloc_type(Type_Struct);
 	wait_signal_set(&t->Struct.fields_wait_signal);
 	wait_signal_set(&t->Struct.fields_wait_signal);
+	wait_signal_set(&t->Struct.polymorphic_wait_signal);
 	return t;
 	return t;
 }
 }
 
 
@@ -1482,10 +1494,10 @@ gb_internal i64 matrix_align_of(Type *t, struct TypePath *tp) {
 	i64 total_expected_size = row_count*t->Matrix.column_count*elem_size;
 	i64 total_expected_size = row_count*t->Matrix.column_count*elem_size;
 	// i64 min_alignment = prev_pow2(elem_align * row_count);
 	// i64 min_alignment = prev_pow2(elem_align * row_count);
 	i64 min_alignment = prev_pow2(total_expected_size);
 	i64 min_alignment = prev_pow2(total_expected_size);
-	while ((total_expected_size % min_alignment) != 0) {
+	while (total_expected_size != 0 && (total_expected_size % min_alignment) != 0) {
 		min_alignment >>= 1;
 		min_alignment >>= 1;
 	}
 	}
-	GB_ASSERT(min_alignment >= elem_align);
+	min_alignment = gb_max(min_alignment, elem_align);
 	
 	
 	i64 align = gb_min(min_alignment, build_context.max_simd_align);
 	i64 align = gb_min(min_alignment, build_context.max_simd_align);
 	return align;
 	return align;
@@ -2127,15 +2139,18 @@ gb_internal bool is_type_polymorphic_record_unspecialized(Type *t) {
 	return false;
 	return false;
 }
 }
 
 
+
 gb_internal TypeTuple *get_record_polymorphic_params(Type *t) {
 gb_internal TypeTuple *get_record_polymorphic_params(Type *t) {
 	t = base_type(t);
 	t = base_type(t);
 	switch (t->kind) {
 	switch (t->kind) {
 	case Type_Struct:
 	case Type_Struct:
+		wait_signal_until_available(&t->Struct.polymorphic_wait_signal);
 		if (t->Struct.polymorphic_params) {
 		if (t->Struct.polymorphic_params) {
 			return &t->Struct.polymorphic_params->Tuple;
 			return &t->Struct.polymorphic_params->Tuple;
 		}
 		}
 		break;
 		break;
 	case Type_Union:
 	case Type_Union:
+		wait_signal_until_available(&t->Union.polymorphic_wait_signal);
 		if (t->Union.polymorphic_params) {
 		if (t->Union.polymorphic_params) {
 			return &t->Union.polymorphic_params->Tuple;
 			return &t->Union.polymorphic_params->Tuple;
 		}
 		}

+ 0 - 14
tests/benchmark/Makefile

@@ -1,14 +0,0 @@
-ODIN=../../odin
-COMMON=-no-bounds-check -vet -strict-style -define:ODIN_TEST_FANCY=false
-
-all: crypto_bench \
-     hash_bench
-
-crypto_bench:
-	$(ODIN) test crypto $(COMMON) -o:speed -out:bench_crypto
-
-hash_bench:
-	$(ODIN) test hash $(COMMON) -o:speed -out:bench_hash
-
-clean:
-	rm bench_*

+ 4 - 0
tests/benchmark/all.odin

@@ -0,0 +1,4 @@
+package benchmarks
+
+@(require) import "crypto"
+@(require) import "hash"

+ 0 - 13
tests/benchmark/build.bat

@@ -1,13 +0,0 @@
-@echo off
-set COMMON=-no-bounds-check -vet -strict-style -define:ODIN_TEST_FANCY=false
-set PATH_TO_ODIN==..\..\odin
-
-echo ---
-echo Running core:crypto benchmarks
-echo ---
-%PATH_TO_ODIN% test crypto %COMMON% -o:speed -out:bench_crypto.exe || exit /b
-
-echo ---
-echo Running core:hash benchmarks
-echo ---
-%PATH_TO_ODIN% test hash %COMMON% -o:speed -out:bench_hash.exe || exit /b

+ 1 - 0
tests/core/.gitignore

@@ -1,3 +1,4 @@
+*.bmp
 *.zip
 *.zip
 *.png
 *.png
 math_big_test_library.*
 math_big_test_library.*

+ 0 - 115
tests/core/Makefile

@@ -1,115 +0,0 @@
-ODIN=../../odin
-PYTHON=$(shell which python3)
-COMMON=-no-bounds-check -vet -strict-style -define:ODIN_TEST_FANCY=false
-
-all: all_bsd \
-     net_test
-
-all_bsd: download_test_assets \
-         c_libc_test \
-         compress_test \
-         container_test \
-         crypto_test \
-         encoding_test \
-         filepath_test \
-         fmt_test \
-         hash_test \
-         i18n_test \
-         image_test \
-         linalg_glsl_math_test \
-         match_test \
-         math_test \
-         mem_test \
-         noise_test \
-         odin_test \
-         os_exit_test \
-         reflect_test \
-         runtime_test \
-         slice_test \
-         strings_test \
-         thread_test \
-         time_test
-
-download_test_assets:
-	$(PYTHON) download_assets.py
-
-c_libc_test:
-	$(ODIN) test c/libc $(COMMON) -out:test_core_libc
-
-compress_test:
-	$(ODIN) test compress $(COMMON) -out:test_core_compress
-
-container_test:
-	$(ODIN) test container $(COMMON) -out:test_core_container
-
-crypto_test:
-	$(ODIN) test crypto $(COMMON) -o:speed -out:test_crypto
-
-encoding_test:
-	$(ODIN) test encoding/base64 $(COMMON) -out:test_base64
-	$(ODIN) test encoding/cbor   $(COMMON) -out:test_cbor
-	$(ODIN) test encoding/hex    $(COMMON) -out:test_hex
-	$(ODIN) test encoding/hxa    $(COMMON) -out:test_hxa
-	$(ODIN) test encoding/json   $(COMMON) -out:test_json
-	$(ODIN) test encoding/varint $(COMMON) -out:test_varint
-	$(ODIN) test encoding/xml    $(COMMON) -out:test_xml
-
-filepath_test:
-	$(ODIN) test path/filepath $(COMMON) -out:test_core_filepath
-
-fmt_test:
-	$(ODIN) test fmt $(COMMON) -out:test_core_fmt
-
-hash_test:
-	$(ODIN) test hash $(COMMON) -o:speed -out:test_hash
-
-image_test:
-	$(ODIN) test image $(COMMON) -out:test_core_image
-
-i18n_test:
-	$(ODIN) test text/i18n $(COMMON) -out:test_core_i18n
-
-match_test:
-	$(ODIN) test text/match $(COMMON) -out:test_core_match
-
-math_test:
-	$(ODIN) test math $(COMMON) -out:test_core_math
-
-linalg_glsl_math_test:
-	$(ODIN) test math/linalg/glsl $(COMMON) -out:test_linalg_glsl_math
-
-noise_test:
-	$(ODIN) test math/noise $(COMMON) -out:test_noise
-
-mem_test:
-	$(ODIN) test mem $(COMMON) -out:test_core_mem
-
-net_test:
-	$(ODIN) test net $(COMMON) -out:test_core_net
-
-os_exit_test:
-	$(ODIN) run os/test_core_os_exit.odin -file -out:test_core_os_exit && exit 1 || exit 0
-
-odin_test:
-	$(ODIN) test odin $(COMMON) -out:test_core_odin
-
-reflect_test:
-	$(ODIN) test reflect $(COMMON) -out:test_core_reflect
-
-runtime_test:
-	$(ODIN) test runtime $(COMMON) -out:test_core_runtime
-
-slice_test:
-	$(ODIN) test slice $(COMMON) -out:test_core_slice
-
-strings_test:
-	$(ODIN) test strings $(COMMON) -out:test_core_strings
-
-thread_test:
-	$(ODIN) test thread $(COMMON) -out:test_core_thread
-
-time_test:
-	$(ODIN) test time $(COMMON) -out:test_core_time
-
-clean:
-	rm test_*

+ 0 - 124
tests/core/build.bat

@@ -1,124 +0,0 @@
-@echo off
-set COMMON=-no-bounds-check -vet -strict-style -define:ODIN_TEST_FANCY=false
-set PATH_TO_ODIN==..\..\odin
-python3 download_assets.py
-echo ---
-echo Running core:c/libc tests
-echo ---
-%PATH_TO_ODIN% test c\libc %COMMON% -out:test_libc.exe || exit /b
-
-echo ---
-echo Running core:compress tests
-echo ---
-%PATH_TO_ODIN% test compress %COMMON% -out:test_core_compress.exe || exit /b
-
-echo ---
-echo Running core:container tests
-echo ---
-%PATH_TO_ODIN% test container %COMMON% -out:test_core_container.exe || exit /b
-
-echo ---
-echo Running core:crypto tests
-echo ---
-%PATH_TO_ODIN% test crypto %COMMON% -o:speed -out:test_crypto.exe || exit /b
-
-echo ---
-echo Running core:encoding tests
-echo ---
-%PATH_TO_ODIN% test encoding/base64 %COMMON% -out:test_base64.exe || exit /b
-%PATH_TO_ODIN% test encoding/cbor   %COMMON% -out:test_cbor.exe   || exit /b
-%PATH_TO_ODIN% test encoding/hex    %COMMON% -out:test_hex.exe    || exit /b
-%PATH_TO_ODIN% test encoding/hxa    %COMMON% -out:test_hxa.exe    || exit /b
-%PATH_TO_ODIN% test encoding/json   %COMMON% -out:test_json.exe   || exit /b
-%PATH_TO_ODIN% test encoding/varint %COMMON% -out:test_varint.exe || exit /b
-%PATH_TO_ODIN% test encoding/xml    %COMMON% -out:test_xml.exe    || exit /b
-
-echo ---
-echo Running core:path/filepath tests
-echo ---
-%PATH_TO_ODIN% test path/filepath %COMMON% -out:test_core_filepath.exe || exit /b
-
-echo ---
-echo Running core:fmt tests
-echo ---
-%PATH_TO_ODIN% test fmt %COMMON% -out:test_core_fmt.exe || exit /b
-
-echo ---
-echo Running core:hash tests
-echo ---
-%PATH_TO_ODIN% test hash %COMMON% -o:speed -out:test_core_hash.exe || exit /b
-
-echo ---
-echo Running core:image tests
-echo ---
-%PATH_TO_ODIN% test image %COMMON% -out:test_core_image.exe || exit /b
-
-echo ---
-echo Running core:text/i18n tests
-echo ---
-%PATH_TO_ODIN% test text\i18n %COMMON% -out:test_core_i18n.exe || exit /b
-
-echo ---
-echo Running text:match tests
-echo ---
-%PATH_TO_ODIN% test text/match %COMMON% -out:test_core_match.exe || exit /b
-
-echo ---
-echo Running core:math tests
-echo ---
-%PATH_TO_ODIN% test math %COMMON% -out:test_core_math.exe || exit /b
-
-echo ---
-echo Running core:math/linalg/glsl tests
-echo ---
-%PATH_TO_ODIN% test math/linalg/glsl %COMMON% -out:test_linalg_glsl.exe || exit /b
-
-echo ---
-echo Running core:math/noise tests
-echo ---
-%PATH_TO_ODIN% test math/noise %COMMON% -out:test_noise.exe || exit /b
-
-echo ---
-echo Running core:mem tests
-echo ---
-%PATH_TO_ODIN% test mem %COMMON% -out:test_core_mem.exe || exit /b
-
-echo ---
-echo Running core:net
-echo ---
-%PATH_TO_ODIN% test net %COMMON% -out:test_core_net.exe || exit /b
-
-echo ---
-echo Running core:odin tests
-echo ---
-%PATH_TO_ODIN% test odin %COMMON% -o:size -out:test_core_odin.exe || exit /b
-
-echo ---
-echo Running core:reflect tests
-echo ---
-%PATH_TO_ODIN% test reflect %COMMON% -out:test_core_reflect.exe || exit /b
-
-echo ---
-echo Running core:runtime tests
-echo ---
-%PATH_TO_ODIN% test runtime %COMMON% -out:test_core_runtime.exe || exit /b
-
-echo ---
-echo Running core:slice tests
-echo ---
-%PATH_TO_ODIN% test slice %COMMON% -out:test_core_slice.exe || exit /b
-
-echo ---
-echo Running core:strings tests
-echo ---
-%PATH_TO_ODIN% test strings %COMMON% -out:test_core_strings.exe || exit /b
-
-echo ---
-echo Running core:thread tests
-echo ---
-%PATH_TO_ODIN% test thread %COMMON% -out:test_core_thread.exe || exit /b
-
-echo ---
-echo Running core:time tests
-echo ---
-%PATH_TO_ODIN% test time %COMMON% -out:test_core_time.exe || exit /b

+ 91 - 2
tests/core/download_assets.py

@@ -7,8 +7,8 @@ import zipfile
 import hashlib
 import hashlib
 import hmac
 import hmac
 
 
-TEST_SUITES        = ['PNG', 'XML']
-DOWNLOAD_BASE_PATH = "assets/{}"
+TEST_SUITES        = ['PNG', 'XML', 'BMP']
+DOWNLOAD_BASE_PATH = sys.argv[1] + "/{}"
 ASSETS_BASE_URL    = "https://raw.githubusercontent.com/odin-lang/test-assets/master/{}/{}"
 ASSETS_BASE_URL    = "https://raw.githubusercontent.com/odin-lang/test-assets/master/{}/{}"
 HMAC_KEY           = "https://odin-lang.org"
 HMAC_KEY           = "https://odin-lang.org"
 HMAC_HASH          = hashlib.sha3_512
 HMAC_HASH          = hashlib.sha3_512
@@ -192,6 +192,94 @@ HMAC_DIGESTS = {
 	'z06n2c08.png': "94268c1998de1f4304d24219e31175def7375cc26e2bbfc7d1ac20465a42fae49bcc8ff7626873138b537588e8bce21b6d5e1373efaade1f83cae455334074aa",
 	'z06n2c08.png': "94268c1998de1f4304d24219e31175def7375cc26e2bbfc7d1ac20465a42fae49bcc8ff7626873138b537588e8bce21b6d5e1373efaade1f83cae455334074aa",
 	'z09n2c08.png': "3cbb1bb58d78ecc9dd5568a8e9093ba020b63449ef3ab102f98fac4220fc9619feaa873336a25f3c1ad99cfb3e5d32bcfe52d966bc8640d1d5ba4e061741743e",
 	'z09n2c08.png': "3cbb1bb58d78ecc9dd5568a8e9093ba020b63449ef3ab102f98fac4220fc9619feaa873336a25f3c1ad99cfb3e5d32bcfe52d966bc8640d1d5ba4e061741743e",
 
 
+	'ba-bm.bmp':           "2f76d46b1b9bea62e08e7fc5306452a495616cb7af7a0cbb79237ed457b083418d5859c9e6cfd0d9fbf1fe24495319b6f206135f36f2bd19330de01a8eaf20c8",
+	'badbitcount.bmp':     "2d37e22aa2e659416c950815841e5a402f2e9c21eb677390fc026eefaeb5be64345a7ef0fac2965a2cae8abe78c1e12086a7d93d8e62cc8659b35168c82f6d5f",
+	'badbitssize.bmp':     "f59cc30827bcb56f7e946dcffcaab22a5e197f2e3884cf80a2e596f5653f5203b3927674d9d5190486239964e65228f4e3f359cdd2f7d061b09846f5f26bfaa9",
+	'baddens1.bmp':        "aa84bebc41b3d50329269da9ee61fd7e1518ffd0e8f733af6872323bc46ace6ed1c9931a65a367d97b8b2cb2aa772ccd94fd3def0a79fd1c0baf185d669c386f",
+	'baddens2.bmp':        "5c254a8cde716fae77ebf20294a404383fd6afc705d783c5418762e7c4138aa621625bc6d08a8946ee3f1e8c40c767681a39806735bb3b3026fee5eb91d8fadc",
+	'badfilesize.bmp':     "9019b6853a91f69bd246f9b28da47007aec871c0e46fea7cd6ab5c30460a6938a1b09da8fa7ba8895650e37ce14a79d4183e9f2401eb510f60455410e2266eb5",
+	'badheadersize.bmp':   "90412d7c3bff7336d5e0c7ae899d8a53b82235072034f00783fb2403479447cd2959644f7ec70ae0988f99cc49b63356c8710b808ddd2280e19dca484f34074e",
+	'badpalettesize.bmp':  "d914a89f7b78fcdd6ab4433c176355755687b65c3cfc23db57de9d04447c440fa31d993db184940c1dc09b37e8e044324d8237877d3d1b1ad5657c4929d8435a",
+	'badplanes.bmp':       "46f583d4a43ef0c9964765b9d8820369955f0568a4eae0bf215434f508e8e03457bd759b73c344c2f88de7f33fc5379517ce3cf5b2e5a16ebc20c05df73aa723",
+	'badrle.bmp':          "a64e1551fd60159ff469ce25e1f5b4575dc462684f4ff66c7ea69b2990c7c9d2547b72237020e2d001f69dfd31f1ac45e0a9630d0ddd11c77584881f3e25609e",
+	'badrle4.bmp':         "2bd22418010b1ac3eac50932ed06e578411ac2741bfa50a9edd1b360686efa28c74df8b14d92e05b711eeb88a5e826256c6a5cf5a0176a29369fb92b336efb93",
+	'badrle4bis.bmp':      "d7a24ab095e1ca5e888dd1bcb732b19bb1983f787c64c1eb5a273da0f58c4b8cd137197df9ac47572a74c3026aab5af1f08551a2121af37b8941cffa71df1951",
+	'badrle4ter.bmp':      "825cc5361378d44524205b117825f95228c4d093d39ac2fc2ab755be743df78784529f2019418deca31059f3e46889a66658e7424b4f896668ee4cfa281574bc",
+	'badrlebis.bmp':       "f41acfd4f989302bb5ec42a2e759a56f71a5ecac5a814842e32542742ca015464f8579ebeec0e7e9cea45e2aafe51456cfe18b48b509bc3704f992bcc9d321af",
+	'badrleter.bmp':       "a8f3e0b0668fc4f43353028d5fca87d6cac6ff0c917c4e7a61c624918360ff598ec9eaa32f5c6a070da9bf6e90c58426f5a901fdab9dfb0a4fdca0c72ba67de4",
+	'badwidth.bmp':        "68f192a55b8de66f8e13fe316647262a5e4641365eb77d4987c84ab1eae35b7cba20827277cd569583543819de70ec75f383367f72cd229e48743ad1e45bfa9e",
+	'pal1.bmp':            "0194c9b501ac7e043fab78746e6f142e0c880917d0fd6dbb7215765b8fc1ce4403ad85146c555665ba6e37d3b47edad5e687b9260e7a61a27d8a059bc81bb525",
+	'pal1bg.bmp':          "3aafc29122bd6e97d88d740be1f61cb9febe8373d19ae6d731f4af776c868dd489260287bf0cf1c960f9d9afcbc7448e83e45435d3e42e913823c0f5c2a80d9f",
+	'pal1huffmsb.bmp':     "4e122f602c3556f4f5ab45f9e13a617d8210d81f587d08cbd6c2110dc6231573aec92a6344aeb4734c00d3dcf380130f53a887002756811d8edd6bc5aabbafc0",
+	'pal1p1.bmp':          "33e2b2b1c1bed43ba64888d7739eb830c7789857352513de08b6e35718ac0e421afcdae0e7bab97c25d1ad972eb4f09e2c6556c416d4d7367c545330c4123df0",
+	'pal1wb.bmp':          "bc583ad4eaae40f5d2e3a6280aeb3c62ee11b2cf05ba7c8386f9578587e29b66819293992bdcd31c2750c21cd9bf97daa603ce1051fbfdd40fadbc1860156853",
+	'pal2.bmp':            "7b560ba972cf58ca1ed01910fa4f630ca74e657d46d134e2ac0df733eb5773d0a1788e745d5240efa18f182bd8dce22c7ac7cee6f99ddc946a27b65297762764",
+	'pal2color.bmp':       "b868a8aaa22fac3aa86bbd5270eb5ffee06959461be8177880829d838be0391d9617d11d73fab1643520a92364dc333c25c0510bb2628c8fb945719518d2675f",
+	'pal4.bmp':            "53a39fdb86630c828d9003a1e95dbd59c47524c4ec044d8ce72e1b643166b4c2b6ec06ab5191cb25d17be2fcb18bd7a9e0b7ec169722e6d89b725609a15b1df1",
+	'pal4gs.bmp':          "ab4c2078943afdf19bcc02b1ebbe5a69cfa93d1152f7882db6176c39b917191a2760fbb2127e5207b0bfb3dafd711593a6aed61d312807605913062aa1ce9c2f",
+	'pal4rle.bmp':         "c86c86280b75a252ccf484e4bba2df45d3747dc1e4879795e925613959a0c451e2fc4890532e8aef9911e38e45e7d6a8baf29d57e573d26c20923a5823700443",
+	'pal4rlecut.bmp':      "f38d485dbb8e67bdeaefba181f9a05556a986ed3f834edca723c088e813764bb2b42240d4fbb938a1775370b79b9ea2f14277ffe9c7247c1e0e77766fec27189",
+	'pal4rletrns.bmp':     "b81e7fed38854d201a3199ce50ca05e92ca287c860797142857ac20b4a2f28952b058e21687c0fae60712f5784cd2c950ce70148ba1316efe31d4f3fc4006817",
+	'pal8-0.bmp':          "f39a4f1827c52bb620d975f8c72f5e95f90ac6c65ae0a6776ff1ad95808c090de17cbd182188a85157396fd9649ea4b5d84bb7c9175ab49ce2845da214c16bff",
+	'pal8.bmp':            "be27e55a866cbb655fdd917435cd6a5b62c20ae0d6ef7c1533c5a01dd9a893f058cc4ba2d902ab9315380009808e06b7f180116c9b790587cf62aa770c7a4a07",
+	'pal8badindex.bmp':    "bd5fc036985ae705182915a560dee2e5dfb3bd8b50932337b9085e190259c66e6bae5fbc813a261d352a60dcb0755798bdc251d6c2a0b638a7e337ba58811811",
+	'pal8gs.bmp':          "228f944b3e45359f62a2839d4e7b94d7f3a1074ad9e25661fdb9e8fff4c15581c85a7bb0ac75c92b95c7537ececc9d80b835cfe55bc7560a513118224a9ed36f",
+	'pal8nonsquare.bmp':   "b8adc9b03880975b232849ea1e8f87705937929d743df3d35420902b32557065354ab71d0d8176646bf0ad72c583c884cfcd1511017260ffca8c41d5a358a3eb",
+	'pal8offs.bmp':        "c92f1e1835d753fd8484be5198b2b8a2660d5e54117f6c4fc6d2ebc8d1def72a8d09cd820b1b8dcee15740b47151d62b8b7aca0b843e696252e28226b51361cf",
+	'pal8os2-hs.bmp':      "477c04048787eb412f192e7fe47ae96f14d7995391e78b10cc4c365f8c762f60c54cad7ef9d1705a78bd490a578fb346ee0a383c3a3fdf790558a12589eb04eb",
+	'pal8os2-sz.bmp':      "fd0eeb733be9b39f492d0f67dd28fc67207149e41691c206d4de4c693b5dea9458b88699a781383e7050a3b343259659aae64fec0616c98f3f8555cbf5c9e46c",
+	'pal8os2.bmp':         "cdab3ed7bc9f38d89117332a21418b3c916a99a8d8fb6b7ce456d54288c96152af12c0380293b04e96594a7867b83be5c99913d224c9750c7d38295924e0735a",
+	'pal8os2sp.bmp':       "f6e595a6db992ab7d1f79442d31f39f648061e7de13e51b07933283df065ce405c0208e6101ac916e4eb0613e412116f008510021a2d17543aa7f0a32349c96f",
+	'pal8os2v2-16.bmp':    "f52877d434218aa6b772a7aa0aaba4c2ae6ce35ecfa6876943bb350fdf9554f1f763a8d5bb054362fb8f9848eb71ce14a371f4a76da4b9475cdcee4f286109a4",
+	'pal8os2v2-40sz.bmp':  "9481691ada527df1f529316d44b5857c6a840c5dafa7e9795c9cb92dac02c6cc35739d3f6ce33d4ab6ff6bcd6b949741e89dc8c42cf52ad4546ff58cd3b5b66a",
+	'pal8os2v2-sz.bmp':    "99cd2836f90591cd27b0c8696ecff1e7a1debcef284bbe5d21e68759270c1bfe1f32ee8f576c49f3e64d8f4e4d9096574f3c8c79bfdae0545689da18364de3e7",
+	'pal8os2v2.bmp':       "7859b265956c7d369db7a0a357ce09bcda74e98954de88f454cae5e7cb021222146687a7770ce0cc2c58f1439c7c21c45c0c27464944e73913e1c88afc836c8a",
+	'pal8oversizepal.bmp': "e778864e0669a33fce27c0ccd5b6460b572a5db01975e8d56acec8a9447e1c58d6051ad3516cfa96a39f4eb7f2576154188ea62ec187bcf4ae323883499383c0",
+	'pal8rle.bmp':         "88942a1cd2e36d1e0f0e2748a888034057919c7ec0f8d9b2664deb1daf1a6e45ed3e722dff5d810f413d6fc182e700a16d6563dd25f67dc6d135d751cd736dea",
+	'pal8rlecut.bmp':      "cda9fa274cde590aeaca81516e0465684cfae84e934eb983301801e978e6e2e9c590d22af992d9912e51bb9c2761945276bdbe0b6c47f3a021514414e1f3f455",
+	'pal8rletrns.bmp':     "0b2d5618dc9c81caa72c070070a4245dd9cd3de5d344b76ce9c15d0eeb72e2675efc264201f8709dfcffd234df09e76d6f328f16f2ad873ba846f870cadfa486",
+	'pal8topdown.bmp':     "d470a2b7556fa88eac820427cb900f59a121732cdb4a7f3530ed457798139c946a884a34ab79d822feb84c2ca6f4d9a65f6e792994eafc3a189948b9e4543546",
+	'pal8v4.bmp':          "0382610e32c49d5091a096cb48a54ebbf44d9ba1def96e2f30826fd3ddf249f5aed70ca5b74c484b6cdc3924f4d4bfed2f5194ad0bcf1d99bfaa3a619e299d86",
+	'pal8v5.bmp':          "50fadaa93aac2a377b565c4dc852fd4602538863b913cb43155f5ad7cf79928127ca28b33e5a3b0230076ea4a6e339e3bf57f019333f42c4e9f003a8f2376325",
+	'pal8w124.bmp':        "e54a901b9badda655cad828d226b381831aea7e36aec8729147e9e95a9f2b21a9d74d93756e908e812902a01197f1379fe7e35498dbafed02e27c853a24097b7",
+	'pal8w125.bmp':        "d7a04d45ef5b3830da071ca598f1e2a413c46834968b2db7518985cf8d8c7380842145899e133e71355b6b7d040ee9e97adec1e928ce4739282e0533058467c0",
+	'pal8w126.bmp':        "4b93845a98797679423c669c541a248b4cdfee80736f01cec29d8b40584bf55a27835c80656a2bf5c7ad3ed211c1f7d3c7d5831a6726904b39f10043a76b658d",
+	'reallybig.bmp':       "babbf0335bac63fd2e95a89210c61ae6bbaaeeab5f07974034e76b4dc2a5c755f77501e3d056479357445aac442e2807d7170ec44067bab8fd35742f0e7b8440",
+	'rgb16-231.bmp':       "611a87cb5d29f16ef71971714a3b0e3863a6df51fff16ce4d4df8ee028442f9ce03669fb5d7a6a838a12a75d8a887b56b5a2e44a3ad62f4ef3fc2e238c33f6a1",
+	'rgb16-3103.bmp':      "7fdff66f4d94341da522b4e40586b3b8c327be9778e461bca1600e938bfbaa872b484192b35cd84d9430ca20aa922ec0301567a74fb777c808400819db90b09d",
+	'rgb16-565.bmp':       "777883f64b9ae80d77bf16c6d062082b7a4702f8260c183680afee6ec26e48681bcca75f0f81c470be1ac8fcb55620b3af5ce31d9e114b582dfd82300a3d6654",
+	'rgb16-565pal.bmp':    "57e9dcf159415b3574a1b343a484391b0859ab2f480e22157f2a84bc188fde141a48826f960c6f30b1d1f17ef6503ec3afc883a2f25ff09dd50c437244f9ae7f",
+	'rgb16-880.bmp':       "8d61183623002da4f7a0a66b42aa58a120e3a91578bb0c4a5e2c5ba7d08b875d43a22f2b5b3a449d3caf4cc303cb05111dd1d5169953f288493b7ea3c2423d24",
+	'rgb16.bmp':           "1c0fe56661d4998edd76efedda520a441171d42ae4dad95b350e3b61deda984c3a3255392481fe1903e5e751357da3f35164935e323377f015774280036ba39e",
+	'rgb16bfdef.bmp':      "ed55d086e27ba472076df418be0046b740944958afeb84d05aa2bbe578dec27ced122ffefb6d549e1d07e05eb608979b3ac9b1bd809f8237cf0984ffdaa24716",
+	'rgb16faketrns.bmp':   "9cd4a8e05fe125649e360715851ef912e78a56d30e0ea1b1cfb6eaafd386437d45de9c1e1a845dd8d63ff5a414832355b8ae0e2a96d72a42a7205e8a2742d37c",
+	'rgb24.bmp':           "4f0ce2978bbfea948798b2fdcc4bdbe8983a6c94d1b7326f39daa6059368e08ebf239260984b64eeb0948f7c8089a523e74b7fa6b0437f9205d8af8891340389",
+	'rgb24largepal.bmp':   "b377aee1594c5d9fc806a70bc62ee83cf8d1852b4a2b18fd3e9409a31aa3b5a4cf5e3b4af2cbdebcef2b5861b7985a248239684a72072437c50151adc524e9df",
+	'rgb24pal.bmp':        "f40bb6e01f6ecb3d55aa992bf1d1e2988ea5eb11e3e58a0c59a4fea2448de26f231f45e9f378b7ee1bdd529ec57a1de38ea536e397f5e1ac6998921e066ab427",
+	'rgb24png.bmp':        "c60890bbd79c12472205665959eb6e2dd2103671571f80117b9e963f897cffca103181374a4797f53c7768af01a705e830a0de4dd8fab7241d24c17bee4a4dbe",
+	'rgb24rle24.bmp':      "ea0ff3f512dd04176d14b43dfbee73ac7f1913aa1b77587e187e271781c7dacec880cec73850c4127ea9f8dd885f069e281f159bb5232e93cbb2d1ee9cb50438",
+	'rgb32-111110.bmp':    "732884e300d4edbcf31556a038947beefc0c5e749131a66d2d7aa1d4ec8c8eba07833133038a03bbe4c4fa61a805a5df1f797b5853339ee6a8140478f5f70a76",
+	'rgb32-7187.bmp':      "4c55aab2e4ecf63dc30b04e5685c5d9fba7354ca0e1327d7c4b15d6da10ac66ca1cea6f0303f9c5f046da3bcd2566275384e0e7bb14fcc5196ec39ca90fac194",
+	'rgb32-xbgr.bmp':      "1e9f240eaec6ac2833f8c719f1fb53cc7551809936620e871ccacfab26402e1afc6503b9f707e4ec25f15529e3ce6433c7f999d5714af31dfb856eb67e772f64",
+	'rgb32.bmp':           "32033dbf9462e5321b1182ba739624ed535aa4d33b775ffeeaf09d2d4cb663e4c3505e8c05489d940f754dde4b50a2e0b0688b21d06755e717e6e511b0947525",
+	'rgb32bf.bmp':         "7243c215827a9b4a1d7d52d67fb04ffb43b0b176518fbdab43d413e2a0c18883b106797f1acd85ba68d494ec939b0caab8789564670d058caf0e1175ce7983fb",
+	'rgb32bfdef.bmp':      "a81433babb67ce714285346a77bfccd19cf6203ac1d8245288855aff20cf38146a783f4a7eac221db63d1ee31345da1329e945b432f0e7bcf279ea88ff5bb302",
+	'rgb32fakealpha.bmp':  "abecaf1b5bfad322de7aec897efe7aa6525f2a77a0af86cc0a0a366ed1650da703cf4b7b117a7ba34f21d03a8a0045e5821248cdefa00b0c78e01d434b55e746",
+	'rgb32h52.bmp':        "707d734393c83384bc75720330075ec9ffefc69167343259ebf95f9393948364a40f33712619f962e7056483b73334584570962c16da212cd5291f764b3f2cd1",
+	'rgba16-1924.bmp':     "3e41a5d8d951bac580c780370ca21e0783de8154f4081106bc58d1185bb2815fc5b7f08f2a1c75cd205fc52c888e9d07c91856651603a2d756c9cfc392585598",
+	'rgba16-4444.bmp':     "a6662186b23bd434a7e019d2a71cd95f53a47b64a1afea4c27ae1120685d041a9ff98800a43a9d8f332682670585bdb2fa77ff77b6def65139fe725323f91561",
+	'rgba16-5551.bmp':     "a7d9f8ae7f8349cd6df651ab9d814411939fa2a235506ddfdd0df5a8f8abcf75552c32481ea035ff29e683bdcd34da68eb23730857e0716b79af51d69a60757b",
+	'rgba32-1.bmp':        "3958d18d2a7f32ada69cb11e0b4397821225a5c40acc7b6d36ff28ee4565e150cc508971278a2ddf8948aaff86f66ec6a0c24513db44962d81b79c4239b3e612",
+	'rgba32-1010102.bmp':  "59a28db5563caf954d31b20a1d1cc59366fcfd258b7ba2949f7281978460a3d94bedcc314c089243dd7463bb18d36a9473355158a7d903912cb25b98eab6b068",
+	'rgba32-2.bmp':        "9b7e5965ff9888f011540936ab6b3022edf9f6c5d7e541d6882cb81820cf1d68326d65695a6f0d01999ac10a496a504440906aa45e413da593405563c54c1a05",
+	'rgba32-61754.bmp':    "784ae0a32b19fa925e0c86dbff3bd38d80904d0fa7dc3b03e9d4f707d42b1604c1f54229e901ccc249cab8c2976d58b1e16980157d9bf3dbc4e035e2b2fd1779",
+	'rgba32-81284.bmp':    "fcfca645017c0d15d44b08598a90d238d063763fd06db665d9a8e36ef5099ce0bf4d440e615c6d6b1bf99f38230d4848318bfa1e6d9bfdd6dfd521cc633ba110",
+	'rgba32abf.bmp':       "2883d676966d298d235196f102e044e52ef18f3cb5bb0dd84738c679f0a1901181483ca2df1cccf6e4b3b4e98be39e81de69c9a58f0d70bc3ebb0fcea80daa0c",
+	'rgba32h56.bmp':       "507d0caf29ccb011c83c0c069c21105ea1d58e06b92204f9c612f26102123a7680eae53fef023c701952d903e11b61f8aa07618c381ea08f6808c523f5a84546",
+	'rgba64.bmp':          "d01f14f649c1c33e3809508cc6f089dd2ab0a538baf833a91042f2e54eca3f8e409908e15fa8763b059d7fa022cf5c074d9f5720eed5293a4c922e131c2eae68",
+	'rletopdown.bmp':      "37500893aad0b40656aa80fd5c7c5f9b35d033018b8070d8b1d7baeb34c90f90462288b13295204b90aa3e5c9be797d22a328e3714ab259334e879a09a3de175",
+	'shortfile.bmp':       "be3ffade7999304f00f9b7d152b5b27811ad1166d0fd43004392467a28f44b6a4ec02a23c0296bacd4f02f8041cd824b9ca6c9fc31fed27e36e572113bb47d73",
+
 	'unicode.xml':  "e0cdc94f07fdbb15eea811ed2ae6dcf494a83d197dafe6580c740270feb0d8f5f7146d4a7d4c2d2ea25f8bd9678bc986123484b39399819a6b7262687959d1ae",
 	'unicode.xml':  "e0cdc94f07fdbb15eea811ed2ae6dcf494a83d197dafe6580c740270feb0d8f5f7146d4a7d4c2d2ea25f8bd9678bc986123484b39399819a6b7262687959d1ae",
 }
 }
 
 
@@ -233,6 +321,7 @@ def try_download_and_unpack_zip(suite):
 
 
 				hmac_digest = hmac.new(HMAC_KEY.encode(), file_data, HMAC_HASH).hexdigest()
 				hmac_digest = hmac.new(HMAC_KEY.encode(), file_data, HMAC_HASH).hexdigest()
 				print("{} *{}".format(hmac_digest, file.filename))
 				print("{} *{}".format(hmac_digest, file.filename))
+
 				if not hmac.compare_digest(hmac_digest, HMAC_DIGESTS[file.filename]):
 				if not hmac.compare_digest(hmac_digest, HMAC_DIGESTS[file.filename]):
 				 	print("FAIL! Expected: {}".format(HMAC_DIGESTS[file.filename]))
 				 	print("FAIL! Expected: {}".format(HMAC_DIGESTS[file.filename]))
 				 	return 4
 				 	return 4

+ 40 - 3
tests/core/fmt/test_core_fmt.odin

@@ -221,8 +221,45 @@ test_fmt_python_syntax :: proc(t: ^testing.T) {
 	check(t, "%!(MISSING CLOSE BRACE)%!(EXTRA 1)",          "{0", 1 )
 	check(t, "%!(MISSING CLOSE BRACE)%!(EXTRA 1)",          "{0", 1 )
 }
 }
 
 
+@(test)
+test_pointers :: proc(t: ^testing.T) {
+	S :: struct { i: int }
+	a: rawptr
+	b: ^int
+	c: ^S
+	d: ^S = cast(^S)cast(uintptr)0xFFFF
+
+	check(t, "0x0", "%p", a)
+	check(t, "0x0", "%p", b)
+	check(t, "0x0", "%p", c)
+	check(t, "0xFFFF", "%p", d)
+
+	check(t, "0x0", "%#p", a)
+	check(t, "0x0", "%#p", b)
+	check(t, "0x0", "%#p", c)
+	check(t, "0xFFFF", "%#p", d)
+
+	check(t, "0x0",   "%v", a)
+	check(t, "0x0",   "%v", b)
+	check(t, "<nil>", "%v", c)
+
+	check(t, "0x0",   "%#v", a)
+	check(t, "0x0",   "%#v", b)
+	check(t, "<nil>", "%#v", c)
+
+	check(t, "0x0000", "%4p", a)
+	check(t, "0x0000", "%4p", b)
+	check(t, "0x0000", "%4p", c)
+	check(t, "0xFFFF", "%4p", d)
+
+	check(t, "0x0000", "%#4p", a)
+	check(t, "0x0000", "%#4p", b)
+	check(t, "0x0000", "%#4p", c)
+	check(t, "0xFFFF", "%#4p", d)
+}
+
 @(private)
 @(private)
-check :: proc(t: ^testing.T, exp: string, format: string, args: ..any) {
+check :: proc(t: ^testing.T, exp: string, format: string, args: ..any, loc := #caller_location) {
 	got := fmt.tprintf(format, ..args)
 	got := fmt.tprintf(format, ..args)
-	testing.expectf(t, got == exp, "(%q, %v): %q != %q", format, args, got, exp)
-}
+	testing.expectf(t, got == exp, "(%q, %v): %q != %q", format, args, got, exp, loc = loc)
+}

+ 0 - 2
tests/core/image/build.bat

@@ -1,2 +0,0 @@
-@echo off
-odin test . -define:ODIN_TEST_TRACK_MEMORY=true -define:ODIN_TEST_PROGRESS_WIDTH=12 -vet -strict-style

+ 597 - 72
tests/core/image/test_core_image.odin

@@ -1,11 +1,11 @@
 /*
 /*
-	Copyright 2021 Jeroen van Rijn <[email protected]>.
+	Copyright 2021-2024 Jeroen van Rijn <[email protected]>.
 	Made available under Odin's BSD-3 license.
 	Made available under Odin's BSD-3 license.
 
 
 	List of contributors:
 	List of contributors:
 		Jeroen van Rijn: Initial implementation.
 		Jeroen van Rijn: Initial implementation.
 
 
-	A test suite for PNG + QOI.
+	A test suite for PNG, TGA, NetPBM, QOI and BMP.
 */
 */
 package test_core_image
 package test_core_image
 
 
@@ -13,6 +13,7 @@ import "core:testing"
 
 
 import "core:compress"
 import "core:compress"
 import "core:image"
 import "core:image"
+import "core:image/bmp"
 import pbm "core:image/netpbm"
 import pbm "core:image/netpbm"
 import "core:image/png"
 import "core:image/png"
 import "core:image/qoi"
 import "core:image/qoi"
@@ -24,16 +25,17 @@ import "core:strings"
 import "core:mem"
 import "core:mem"
 import "core:time"
 import "core:time"
 
 
-TEST_SUITE_PATH :: ODIN_ROOT + "tests/core/assets/PNG/"
+TEST_SUITE_PATH_PNG :: ODIN_ROOT + "tests/core/assets/PNG"
+TEST_SUITE_PATH_BMP :: ODIN_ROOT + "tests/core/assets/BMP"
 
 
 I_Error :: image.Error
 I_Error :: image.Error
 
 
-PNG_Test :: struct {
+Test :: struct {
 	file:   string,
 	file:   string,
 	tests:  []struct {
 	tests:  []struct {
 		options:        image.Options,
 		options:        image.Options,
 		expected_error: image.Error,
 		expected_error: image.Error,
-		dims:           PNG_Dims,
+		dims:           Dims,
 		hash:           u32,
 		hash:           u32,
 	},
 	},
 }
 }
@@ -46,19 +48,18 @@ Blend_BG_Keep        :: image.Options{.blend_background, .alpha_add_if_missing}
 Return_Metadata      :: image.Options{.return_metadata}
 Return_Metadata      :: image.Options{.return_metadata}
 No_Channel_Expansion :: image.Options{.do_not_expand_channels, .return_metadata}
 No_Channel_Expansion :: image.Options{.do_not_expand_channels, .return_metadata}
 
 
-PNG_Dims :: struct {
+Dims :: struct {
 	width:     int,
 	width:     int,
 	height:    int,
 	height:    int,
 	channels:  int,
 	channels:  int,
 	depth:     int,
 	depth:     int,
 }
 }
 
 
-Basic_PNG_Tests       := []PNG_Test{
+Basic_PNG_Tests := []Test{
 	/*
 	/*
 		Basic format tests:
 		Basic format tests:
 			http://www.schaik.com/pngsuite/pngsuite_bas_png.html
 			http://www.schaik.com/pngsuite/pngsuite_bas_png.html
 	*/
 	*/
-
 	{
 	{
 		"basn0g01", // Black and white.
 		"basn0g01", // Black and white.
 		{
 		{
@@ -166,7 +167,7 @@ Basic_PNG_Tests       := []PNG_Test{
 	},
 	},
 }
 }
 
 
-Interlaced_PNG_Tests  := []PNG_Test{
+Interlaced_PNG_Tests  := []Test{
 	/*
 	/*
 		Interlaced format tests:
 		Interlaced format tests:
 			http://www.schaik.com/pngsuite/pngsuite_int_png.html
 			http://www.schaik.com/pngsuite/pngsuite_int_png.html
@@ -284,9 +285,9 @@ Interlaced_PNG_Tests  := []PNG_Test{
 	},
 	},
 }
 }
 
 
-Odd_Sized_PNG_Tests   := []PNG_Test{
+Odd_Sized_PNG_Tests := []Test{
 	/*
 	/*
-"        PngSuite", // Odd sizes / PNG-files:
+		"PngSuite", // Odd sizes / PNG-files:
 			http://www.schaik.com/pngsuite/pngsuite_siz_png.html
 			http://www.schaik.com/pngsuite/pngsuite_siz_png.html
 
 
 		This tests curious sizes with and without interlacing.
 		This tests curious sizes with and without interlacing.
@@ -510,7 +511,7 @@ Odd_Sized_PNG_Tests   := []PNG_Test{
 	},
 	},
 }
 }
 
 
-PNG_bKGD_Tests        := []PNG_Test{
+PNG_bKGD_Tests := []Test{
 	/*
 	/*
 "        PngSuite", // Background colors / PNG-files:
 "        PngSuite", // Background colors / PNG-files:
 			http://www.schaik.com/pngsuite/pngsuite_bck_png.html
 			http://www.schaik.com/pngsuite/pngsuite_bck_png.html
@@ -597,7 +598,7 @@ PNG_bKGD_Tests        := []PNG_Test{
 	},
 	},
 }
 }
 
 
-PNG_tRNS_Tests        := []PNG_Test{
+PNG_tRNS_Tests := []Test{
 	/*
 	/*
 		PngSuite - Transparency:
 		PngSuite - Transparency:
 			http://www.schaik.com/pngsuite/pngsuite_trn_png.html
 			http://www.schaik.com/pngsuite/pngsuite_trn_png.html
@@ -759,7 +760,7 @@ PNG_tRNS_Tests        := []PNG_Test{
 	},
 	},
 }
 }
 
 
-PNG_Filter_Tests      := []PNG_Test{
+PNG_Filter_Tests := []Test{
 	/*
 	/*
 		PngSuite - Image filtering:
 		PngSuite - Image filtering:
 
 
@@ -838,7 +839,7 @@ PNG_Filter_Tests      := []PNG_Test{
 	},
 	},
 }
 }
 
 
-PNG_Varied_IDAT_Tests := []PNG_Test{
+PNG_Varied_IDAT_Tests := []Test{
 	/*
 	/*
 		PngSuite - Chunk ordering:
 		PngSuite - Chunk ordering:
 
 
@@ -897,7 +898,7 @@ PNG_Varied_IDAT_Tests := []PNG_Test{
 	},
 	},
 }
 }
 
 
-PNG_ZLIB_Levels_Tests := []PNG_Test{
+PNG_ZLIB_Levels_Tests := []Test{
 	/*
 	/*
 		PngSuite - Zlib compression:
 		PngSuite - Zlib compression:
 
 
@@ -938,7 +939,7 @@ PNG_ZLIB_Levels_Tests := []PNG_Test{
 	},
 	},
 }
 }
 
 
-PNG_sPAL_Tests        := []PNG_Test{
+PNG_sPAL_Tests := []Test{
 	/*
 	/*
 		PngSuite - Additional palettes:
 		PngSuite - Additional palettes:
 
 
@@ -985,7 +986,7 @@ PNG_sPAL_Tests        := []PNG_Test{
 	},
 	},
 }
 }
 
 
-PNG_Ancillary_Tests   := []PNG_Test{
+PNG_Ancillary_Tests := []Test{
 	/*
 	/*
 		PngSuite" - Ancillary chunks:
 		PngSuite" - Ancillary chunks:
 
 
@@ -1153,7 +1154,7 @@ PNG_Ancillary_Tests   := []PNG_Test{
 }
 }
 
 
 
 
-Corrupt_PNG_Tests   := []PNG_Test{
+Corrupt_PNG_Tests := []Test{
 	/*
 	/*
 		PngSuite - Corrupted files / PNG-files:
 		PngSuite - Corrupted files / PNG-files:
 
 
@@ -1249,7 +1250,7 @@ Corrupt_PNG_Tests   := []PNG_Test{
 
 
 }
 }
 
 
-No_Postprocesing_Tests := []PNG_Test{
+No_Postprocesing_Tests := []Test{
 	/*
 	/*
 		These are some custom tests where we skip expanding to RGB(A).
 		These are some custom tests where we skip expanding to RGB(A).
 	*/
 	*/
@@ -1273,8 +1274,6 @@ No_Postprocesing_Tests := []PNG_Test{
 	},
 	},
 }
 }
 
 
-
-
 Text_Title      :: "PngSuite"
 Text_Title      :: "PngSuite"
 Text_Software   :: "Created on a NeXTstation color using \"pnmtopng\"."
 Text_Software   :: "Created on a NeXTstation color using \"pnmtopng\"."
 Text_Descrption :: "A compilation of a set of images created to test the\nvarious color-types of the PNG format. Included are\nblack&white, color, paletted, with alpha channel, with\ntransparency formats. All bit-depths allowed according\nto the spec are present."
 Text_Descrption :: "A compilation of a set of images created to test the\nvarious color-types of the PNG format. Included are\nblack&white, color, paletted, with alpha channel, with\ntransparency formats. All bit-depths allowed according\nto the spec are present."
@@ -1453,9 +1452,9 @@ png_test_no_postproc :: proc(t: ^testing.T) {
 	run_png_suite(t, No_Postprocesing_Tests)
 	run_png_suite(t, No_Postprocesing_Tests)
 }
 }
 
 
-run_png_suite :: proc(t: ^testing.T, suite: []PNG_Test) {
+run_png_suite :: proc(t: ^testing.T, suite: []Test) {
 	for file in suite {
 	for file in suite {
-		test_file := strings.concatenate({TEST_SUITE_PATH, file.file, ".png"})
+		test_file := strings.concatenate({TEST_SUITE_PATH_PNG, "/", file.file, ".png"}, context.allocator)
 		defer delete(test_file)
 		defer delete(test_file)
 
 
 		img: ^png.Image
 		img: ^png.Image
@@ -1468,20 +1467,18 @@ run_png_suite :: proc(t: ^testing.T, suite: []PNG_Test) {
 			img, err = png.load(test_file, test.options)
 			img, err = png.load(test_file, test.options)
 
 
 			passed := (test.expected_error == nil && err == nil) || (test.expected_error == err)
 			passed := (test.expected_error == nil && err == nil) || (test.expected_error == err)
-			testing.expectf(t, passed, "%v failed with %v.", test_file, err)
+			testing.expectf(t, passed, "%q failed to load with error %v.", file.file, err)
 
 
 			if err == nil { // No point in running the other tests if it didn't load.
 			if err == nil { // No point in running the other tests if it didn't load.
 				pixels := bytes.buffer_to_bytes(&img.pixels)
 				pixels := bytes.buffer_to_bytes(&img.pixels)
 
 
-				// This struct compare fails at -opt:2 if PNG_Dims is not #packed.
-				dims := PNG_Dims{img.width, img.height, img.channels, img.depth}
+				dims      := Dims{img.width, img.height, img.channels, img.depth}
 				dims_pass := test.dims == dims
 				dims_pass := test.dims == dims
-
-				testing.expectf(t, dims_pass, "%v has %v, expected: %v.", file.file, dims, test.dims)
+				testing.expectf(t, dims_pass, "%v has %v, expected: %v", file.file, dims, test.dims)
 				passed &= dims_pass
 				passed &= dims_pass
 
 
 				png_hash := hash.crc32(pixels)
 				png_hash := hash.crc32(pixels)
-				testing.expectf(t, test.hash == png_hash, "%v test %v hash is %08x, expected %08x with %v.", file.file, count, png_hash, test.hash, test.options)
+				testing.expectf(t, test.hash == png_hash, "%v test %v hash is %08x, expected %08x with %v", file.file, count, png_hash, test.hash, test.options)
 
 
 				passed &= test.hash == png_hash
 				passed &= test.hash == png_hash
 
 
@@ -1492,16 +1489,16 @@ run_png_suite :: proc(t: ^testing.T, suite: []PNG_Test) {
 						defer bytes.buffer_destroy(&qoi_buffer)
 						defer bytes.buffer_destroy(&qoi_buffer)
 						qoi_save_err := qoi.save(&qoi_buffer, img)
 						qoi_save_err := qoi.save(&qoi_buffer, img)
 
 
-						testing.expectf(t, qoi_save_err == nil, "%v test %v QOI save failed with %v.", file.file, count, qoi_save_err)
+						testing.expectf(t, qoi_save_err == nil, "%v test %v QOI save failed with %v", file.file, count, qoi_save_err)
 
 
 						if qoi_save_err == nil {
 						if qoi_save_err == nil {
 							qoi_img, qoi_load_err := qoi.load(qoi_buffer.buf[:])
 							qoi_img, qoi_load_err := qoi.load(qoi_buffer.buf[:])
 							defer qoi.destroy(qoi_img)
 							defer qoi.destroy(qoi_img)
 
 
-							testing.expectf(t, qoi_load_err == nil, "%v test %v QOI load failed with %v.", file.file, count, qoi_load_err)
+							testing.expectf(t, qoi_load_err == nil, "%v test %v QOI load failed with %v", file.file, count, qoi_load_err)
 
 
 							qoi_hash := hash.crc32(qoi_img.pixels.buf[:])
 							qoi_hash := hash.crc32(qoi_img.pixels.buf[:])
-							testing.expectf(t, qoi_hash == png_hash, "%v test %v QOI load hash is %08x, expected it match PNG's %08x with %v.", file.file, count, qoi_hash, png_hash, test.options)
+							testing.expectf(t, qoi_hash == png_hash, "%v test %v QOI load hash is %08x, expected it match PNG's %08x with %v", file.file, count, qoi_hash, png_hash, test.options)
 						}
 						}
 					}
 					}
 
 
@@ -1511,15 +1508,15 @@ run_png_suite :: proc(t: ^testing.T, suite: []PNG_Test) {
 						defer bytes.buffer_destroy(&tga_buffer)
 						defer bytes.buffer_destroy(&tga_buffer)
 						tga_save_err := tga.save(&tga_buffer, img)
 						tga_save_err := tga.save(&tga_buffer, img)
 
 
-						testing.expectf(t, tga_save_err == nil, "%v test %v TGA save failed with %v.", file.file, count, tga_save_err)
+						testing.expectf(t, tga_save_err == nil, "%v test %v TGA save failed with %v", file.file, count, tga_save_err)
 						if tga_save_err == nil {
 						if tga_save_err == nil {
 							tga_img, tga_load_err := tga.load(tga_buffer.buf[:])
 							tga_img, tga_load_err := tga.load(tga_buffer.buf[:])
 							defer tga.destroy(tga_img)
 							defer tga.destroy(tga_img)
 
 
-							testing.expectf(t, tga_load_err == nil, "%v test %v TGA load failed with %v.", file.file, count, tga_load_err)
+							testing.expectf(t, tga_load_err == nil, "%v test %v TGA load failed with %v", file.file, count, tga_load_err)
 
 
 							tga_hash := hash.crc32(tga_img.pixels.buf[:])
 							tga_hash := hash.crc32(tga_img.pixels.buf[:])
-							testing.expectf(t, tga_hash == png_hash, "%v test %v TGA load hash is %08x, expected it match PNG's %08x with %v.", file.file, count, tga_hash, png_hash, test.options)
+							testing.expectf(t, tga_hash == png_hash, "%v test %v TGA load hash is %08x, expected it match PNG's %08x with %v", file.file, count, tga_hash, png_hash, test.options)
 						}
 						}
 					}
 					}
 
 
@@ -1528,18 +1525,18 @@ run_png_suite :: proc(t: ^testing.T, suite: []PNG_Test) {
 						pbm_buf, pbm_save_err := pbm.save_to_buffer(img)
 						pbm_buf, pbm_save_err := pbm.save_to_buffer(img)
 						defer delete(pbm_buf)
 						defer delete(pbm_buf)
 
 
-						testing.expectf(t, pbm_save_err == nil, "%v test %v PBM save failed with %v.", file.file, count, pbm_save_err)
+						testing.expectf(t, pbm_save_err == nil, "%v test %v PBM save failed with %v", file.file, count, pbm_save_err)
 
 
 						if pbm_save_err == nil {
 						if pbm_save_err == nil {
 							// Try to load it again.
 							// Try to load it again.
 							pbm_img, pbm_load_err := pbm.load(pbm_buf)
 							pbm_img, pbm_load_err := pbm.load(pbm_buf)
 							defer pbm.destroy(pbm_img)
 							defer pbm.destroy(pbm_img)
 
 
-							testing.expectf(t, pbm_load_err == nil, "%v test %v PBM load failed with %v.", file.file, count, pbm_load_err)
+							testing.expectf(t, pbm_load_err == nil, "%v test %v PBM load failed with %v", file.file, count, pbm_load_err)
 
 
 							if pbm_load_err == nil {
 							if pbm_load_err == nil {
 								pbm_hash := hash.crc32(pbm_img.pixels.buf[:])
 								pbm_hash := hash.crc32(pbm_img.pixels.buf[:])
-								testing.expectf(t, pbm_hash == png_hash, "%v test %v PBM load hash is %08x, expected it match PNG's %08x with %v.", file.file, count, pbm_hash, png_hash, test.options)
+								testing.expectf(t, pbm_hash == png_hash, "%v test %v PBM load hash is %08x, expected it match PNG's %08x with %v", file.file, count, pbm_hash, png_hash, test.options)
 							}
 							}
 						}
 						}
 					}
 					}
@@ -1553,18 +1550,18 @@ run_png_suite :: proc(t: ^testing.T, suite: []PNG_Test) {
 							pbm_buf, pbm_save_err := pbm.save_to_buffer(img, pbm_info)
 							pbm_buf, pbm_save_err := pbm.save_to_buffer(img, pbm_info)
 							defer delete(pbm_buf)
 							defer delete(pbm_buf)
 
 
-							testing.expectf(t, pbm_save_err == nil, "%v test %v PBM save failed with %v.", file.file, count, pbm_save_err)
+							testing.expectf(t, pbm_save_err == nil, "%v test %v PBM save failed with %v", file.file, count, pbm_save_err)
 
 
 							if pbm_save_err == nil {
 							if pbm_save_err == nil {
 								// Try to load it again.
 								// Try to load it again.
 								pbm_img, pbm_load_err := pbm.load(pbm_buf)
 								pbm_img, pbm_load_err := pbm.load(pbm_buf)
 								defer pbm.destroy(pbm_img)
 								defer pbm.destroy(pbm_img)
 
 
-								testing.expectf(t, pbm_load_err == nil, "%v test %v PBM load failed with %v.", file.file, count, pbm_load_err)
+								testing.expectf(t, pbm_load_err == nil, "%v test %v PBM load failed with %v", file.file, count, pbm_load_err)
 
 
 								if pbm_load_err == nil {
 								if pbm_load_err == nil {
 									pbm_hash := hash.crc32(pbm_img.pixels.buf[:])
 									pbm_hash := hash.crc32(pbm_img.pixels.buf[:])
-									testing.expectf(t, pbm_hash == png_hash, "%v test %v PBM load hash is %08x, expected it match PNG's %08x with %v.", file.file, count, pbm_hash, png_hash, test.options)
+									testing.expectf(t, pbm_hash == png_hash, "%v test %v PBM load hash is %08x, expected it match PNG's %08x with %v", file.file, count, pbm_hash, png_hash, test.options)
 								}
 								}
 							}
 							}
 						}
 						}
@@ -1653,7 +1650,7 @@ run_png_suite :: proc(t: ^testing.T, suite: []PNG_Test) {
 								case "pp0n2c16", "pp0n6a08":
 								case "pp0n2c16", "pp0n6a08":
 									gamma, gamma_ok := png.gamma(c)
 									gamma, gamma_ok := png.gamma(c)
 									expected_gamma := f32(1.0)
 									expected_gamma := f32(1.0)
-									testing.expectf(t, gamma == expected_gamma && gamma_ok, "%v test %v gAMA is %v, expected %v.", file.file, count, gamma, expected_gamma)
+									testing.expectf(t, gamma == expected_gamma && gamma_ok, "%v test %v gAMA is %v, expected %v", file.file, count, gamma, expected_gamma)
 								}
 								}
 							case .PLTE:
 							case .PLTE:
 								switch(file.file) {
 								switch(file.file) {
@@ -1661,7 +1658,7 @@ run_png_suite :: proc(t: ^testing.T, suite: []PNG_Test) {
 									plte, plte_ok := png.plte(c)
 									plte, plte_ok := png.plte(c)
 
 
 									expected_plte_len := u16(216)
 									expected_plte_len := u16(216)
-									testing.expectf(t, expected_plte_len == plte.used && plte_ok, "%v test %v PLTE length is %v, expected %v.", file.file, count, plte.used, expected_plte_len)
+									testing.expectf(t, expected_plte_len == plte.used && plte_ok, "%v test %v PLTE length is %v, expected %v", file.file, count, plte.used, expected_plte_len)
 								}
 								}
 							case .sPLT:
 							case .sPLT:
 								switch(file.file) {
 								switch(file.file) {
@@ -1669,10 +1666,10 @@ run_png_suite :: proc(t: ^testing.T, suite: []PNG_Test) {
 									splt, splt_ok := png.splt(c)
 									splt, splt_ok := png.splt(c)
 
 
 									expected_splt_len  := u16(216)
 									expected_splt_len  := u16(216)
-									testing.expectf(t, expected_splt_len == splt.used && splt_ok, "%v test %v sPLT length is %v, expected %v.", file.file, count, splt.used, expected_splt_len)
+									testing.expectf(t, expected_splt_len == splt.used && splt_ok, "%v test %v sPLT length is %v, expected %v", file.file, count, splt.used, expected_splt_len)
 
 
 									expected_splt_name := "six-cube"
 									expected_splt_name := "six-cube"
-									testing.expectf(t, expected_splt_name == splt.name && splt_ok, "%v test %v sPLT name is %v, expected %v.", file.file, count, splt.name, expected_splt_name)
+									testing.expectf(t, expected_splt_name == splt.name && splt_ok, "%v test %v sPLT name is %v, expected %v", file.file, count, splt.name, expected_splt_name)
 
 
 									png.splt_destroy(splt)
 									png.splt_destroy(splt)
 								}
 								}
@@ -1686,31 +1683,31 @@ run_png_suite :: proc(t: ^testing.T, suite: []PNG_Test) {
 										g = png.CIE_1931{x = 0.3000, y = 0.6000},
 										g = png.CIE_1931{x = 0.3000, y = 0.6000},
 										b = png.CIE_1931{x = 0.1500, y = 0.0600},
 										b = png.CIE_1931{x = 0.1500, y = 0.0600},
 									}
 									}
-									testing.expectf(t, expected_chrm == chrm && chrm_ok, "%v test %v cHRM is %v, expected %v.", file.file, count, chrm, expected_chrm)
+									testing.expectf(t, expected_chrm == chrm && chrm_ok, "%v test %v cHRM is %v, expected %v", file.file, count, chrm, expected_chrm)
 								}
 								}
 							case .pHYs:
 							case .pHYs:
 								phys, phys_ok := png.phys(c)
 								phys, phys_ok := png.phys(c)
 								switch (file.file) {
 								switch (file.file) {
 								case "cdfn2c08":
 								case "cdfn2c08":
 									expected_phys := png.pHYs{ppu_x =    1, ppu_y =    4, unit = .Unknown}
 									expected_phys := png.pHYs{ppu_x =    1, ppu_y =    4, unit = .Unknown}
-									testing.expectf(t, expected_phys == phys && phys_ok, "%v test %v cHRM is %v, expected %v.", file.file, count, phys, expected_phys)
+									testing.expectf(t, expected_phys == phys && phys_ok, "%v test %v cHRM is %v, expected %v", file.file, count, phys, expected_phys)
 								case "cdhn2c08":
 								case "cdhn2c08":
 									expected_phys := png.pHYs{ppu_x =    4, ppu_y =    1, unit = .Unknown}
 									expected_phys := png.pHYs{ppu_x =    4, ppu_y =    1, unit = .Unknown}
-									testing.expectf(t, expected_phys == phys && phys_ok, "%v test %v cHRM is %v, expected %v.", file.file, count, phys, expected_phys)
+									testing.expectf(t, expected_phys == phys && phys_ok, "%v test %v cHRM is %v, expected %v", file.file, count, phys, expected_phys)
 								case "cdsn2c08":
 								case "cdsn2c08":
 									expected_phys := png.pHYs{ppu_x =    1, ppu_y =    1, unit = .Unknown}
 									expected_phys := png.pHYs{ppu_x =    1, ppu_y =    1, unit = .Unknown}
-									testing.expectf(t, expected_phys == phys && phys_ok, "%v test %v cHRM is %v, expected %v.", file.file, count, phys, expected_phys)
+									testing.expectf(t, expected_phys == phys && phys_ok, "%v test %v cHRM is %v, expected %v", file.file, count, phys, expected_phys)
 								case "cdun2c08":
 								case "cdun2c08":
 									expected_phys := png.pHYs{ppu_x = 1000, ppu_y = 1000, unit = .Meter}
 									expected_phys := png.pHYs{ppu_x = 1000, ppu_y = 1000, unit = .Meter}
-									testing.expectf(t, expected_phys == phys && phys_ok, "%v test %v cHRM is %v, expected %v.", file.file, count, phys, expected_phys)
+									testing.expectf(t, expected_phys == phys && phys_ok, "%v test %v cHRM is %v, expected %v", file.file, count, phys, expected_phys)
 								}
 								}
 							case .hIST:
 							case .hIST:
 								hist, hist_ok := png.hist(c)
 								hist, hist_ok := png.hist(c)
 								switch (file.file) {
 								switch (file.file) {
 								case "ch1n3p04":
 								case "ch1n3p04":
-									testing.expectf(t, hist.used == 15 && hist_ok, "%v test %v hIST has %v entries, expected %v.", file.file, count, hist.used, 15)
+									testing.expectf(t, hist.used == 15 && hist_ok, "%v test %v hIST has %v entries, expected %v", file.file, count, hist.used, 15)
 								case "ch2n3p08":
 								case "ch2n3p08":
-									testing.expectf(t, hist.used == 256 && hist_ok, "%v test %v hIST has %v entries, expected %v.", file.file, count, hist.used, 256)
+									testing.expectf(t, hist.used == 256 && hist_ok, "%v test %v hIST has %v entries, expected %v", file.file, count, hist.used, 256)
 								}
 								}
 							case .tIME:
 							case .tIME:
 								png_time, png_time_ok := png.time(c)
 								png_time, png_time_ok := png.time(c)
@@ -1731,8 +1728,8 @@ run_png_suite :: proc(t: ^testing.T, suite: []PNG_Test) {
 									expected_core = time.Time{_nsec = 946684799000000000}
 									expected_core = time.Time{_nsec = 946684799000000000}
 
 
 								}
 								}
-								testing.expectf(t, png_time  == expected_time && png_time_ok,  "%v test %v tIME was %v, expected %v.", file.file, count, png_time, expected_time)
-								testing.expectf(t, core_time == expected_core && core_time_ok, "%v test %v tIME->core:time is %v, expected %v.", file.file, count, core_time, expected_core)
+								testing.expectf(t, png_time  == expected_time && png_time_ok,  "%v test %v tIME was %v, expected %v", file.file, count, png_time, expected_time)
+								testing.expectf(t, core_time == expected_core && core_time_ok, "%v test %v tIME->core:time is %v, expected %v", file.file, count, core_time, expected_core)
 							case .sBIT:
 							case .sBIT:
 								sbit, sbit_ok  := png.sbit(c)
 								sbit, sbit_ok  := png.sbit(c)
 								expected_sbit: [4]u8
 								expected_sbit: [4]u8
@@ -1753,7 +1750,7 @@ run_png_suite :: proc(t: ^testing.T, suite: []PNG_Test) {
 								case "cdfn2c08", "cdhn2c08", "cdsn2c08", "cdun2c08", "ch1n3p04", "basn3p04":
 								case "cdfn2c08", "cdhn2c08", "cdsn2c08", "cdun2c08", "ch1n3p04", "basn3p04":
 									expected_sbit = [4]u8{ 4,  4,  4,  0}
 									expected_sbit = [4]u8{ 4,  4,  4,  0}
 								}
 								}
-								testing.expectf(t, sbit == expected_sbit && sbit_ok, "%v test %v sBIT was %v, expected %v.", file.file, count, sbit, expected_sbit)
+								testing.expectf(t, sbit == expected_sbit && sbit_ok, "%v test %v sBIT was %v, expected %v", file.file, count, sbit, expected_sbit)
 							case .tEXt, .zTXt:
 							case .tEXt, .zTXt:
 								text, text_ok := png.text(c)
 								text, text_ok := png.text(c)
 								defer png.text_destroy(text)
 								defer png.text_destroy(text)
@@ -1765,7 +1762,7 @@ run_png_suite :: proc(t: ^testing.T, suite: []PNG_Test) {
 									if file.file in Expected_Text {
 									if file.file in Expected_Text {
 										if text.keyword in Expected_Text[file.file] {
 										if text.keyword in Expected_Text[file.file] {
 											test_text := Expected_Text[file.file][text.keyword].text
 											test_text := Expected_Text[file.file][text.keyword].text
-											testing.expectf(t, text.text == test_text && text_ok, "%v test %v text keyword {{%v}}:'%v', expected '%v'.", file.file, count, text.keyword, text.text, test_text)
+											testing.expectf(t, text.text == test_text && text_ok, "%v test %v text keyword {{%v}}:'%v', expected '%v'", file.file, count, text.keyword, text.text, test_text)
 										}
 										}
 									}
 									}
 								}
 								}
@@ -1778,44 +1775,44 @@ run_png_suite :: proc(t: ^testing.T, suite: []PNG_Test) {
 									if file.file in Expected_Text {
 									if file.file in Expected_Text {
 										if text.keyword in Expected_Text[file.file] {
 										if text.keyword in Expected_Text[file.file] {
 											test := Expected_Text[file.file][text.keyword]
 											test := Expected_Text[file.file][text.keyword]
-											testing.expectf(t, text.language == test.language && text_ok, "%v test %v text keyword {{%v}}:'%v', expected '%v'.", file.file, count, text.keyword, text, test)
-											testing.expectf(t, text.keyword_localized == test.keyword_localized && text_ok, "%v test %v text keyword {{%v}}:'%v', expected '%v'.", file.file, count, text.keyword, text, test)
+											testing.expectf(t, text.language == test.language && text_ok, "%v test %v text keyword {{%v}}:'%v', expected '%v'", file.file, count, text.keyword, text, test)
+											testing.expectf(t, text.keyword_localized == test.keyword_localized && text_ok, "%v test %v text keyword {{%v}}:'%v', expected '%v'", file.file, count, text.keyword, text, test)
 										}
 										}
 									}
 									}
 								case "ctfn0g04": // international UTF-8, finnish
 								case "ctfn0g04": // international UTF-8, finnish
 									if file.file in Expected_Text {
 									if file.file in Expected_Text {
 										if text.keyword in Expected_Text[file.file] {
 										if text.keyword in Expected_Text[file.file] {
 											test := Expected_Text[file.file][text.keyword]
 											test := Expected_Text[file.file][text.keyword]
-											testing.expectf(t, text.text == test.text && text_ok, "%v test %v text keyword {{%v}}:'%v', expected '%v'.", file.file, count, text.keyword, text, test)
-											testing.expectf(t, text.language == test.language && text_ok, "%v test %v text keyword {{%v}}:'%v', expected '%v'.", file.file, count, text.keyword, text, test)
-											testing.expectf(t, text.keyword_localized == test.keyword_localized && text_ok, "%v test %v text keyword {{%v}}:'%v', expected '%v'.", file.file, count, text.keyword, text, test)
+											testing.expectf(t, text.text == test.text && text_ok, "%v test %v text keyword {{%v}}:'%v', expected '%v'", file.file, count, text.keyword, text, test)
+											testing.expectf(t, text.language == test.language && text_ok, "%v test %v text keyword {{%v}}:'%v', expected '%v'", file.file, count, text.keyword, text, test)
+											testing.expectf(t, text.keyword_localized == test.keyword_localized && text_ok, "%v test %v text keyword {{%v}}:'%v', expected '%v'", file.file, count, text.keyword, text, test)
 										}
 										}
 									}
 									}
 								case "ctgn0g04": // international UTF-8, greek
 								case "ctgn0g04": // international UTF-8, greek
 									if file.file in Expected_Text {
 									if file.file in Expected_Text {
 										if text.keyword in Expected_Text[file.file] {
 										if text.keyword in Expected_Text[file.file] {
 											test := Expected_Text[file.file][text.keyword]
 											test := Expected_Text[file.file][text.keyword]
-											testing.expectf(t, text.text == test.text && text_ok, "%v test %v text keyword {{%v}}:'%v', expected '%v'.", file.file, count, text.keyword, text, test)
-											testing.expectf(t, text.language == test.language && text_ok, "%v test %v text keyword {{%v}}:'%v', expected '%v'.", file.file, count, text.keyword, text, test)
-											testing.expectf(t, text.keyword_localized == test.keyword_localized && text_ok, "%v test %v text keyword {{%v}}:'%v', expected '%v'.", file.file, count, text.keyword, text, test)
+											testing.expectf(t, text.text == test.text && text_ok, "%v test %v text keyword {{%v}}:'%v', expected '%v'", file.file, count, text.keyword, text, test)
+											testing.expectf(t, text.language == test.language && text_ok, "%v test %v text keyword {{%v}}:'%v', expected '%v'", file.file, count, text.keyword, text, test)
+											testing.expectf(t, text.keyword_localized == test.keyword_localized && text_ok, "%v test %v text keyword {{%v}}:'%v', expected '%v'", file.file, count, text.keyword, text, test)
 										}
 										}
 									}
 									}
 								case "cthn0g04": // international UTF-8, hindi
 								case "cthn0g04": // international UTF-8, hindi
 									if file.file in Expected_Text {
 									if file.file in Expected_Text {
 										if text.keyword in Expected_Text[file.file] {
 										if text.keyword in Expected_Text[file.file] {
 											test := Expected_Text[file.file][text.keyword]
 											test := Expected_Text[file.file][text.keyword]
-											testing.expectf(t, text.text == test.text && text_ok, "%v test %v text keyword {{%v}}:'%v', expected '%v'.", file.file, count, text.keyword, text, test)
-											testing.expectf(t, text.language == test.language && text_ok, "%v test %v text keyword {{%v}}:'%v', expected '%v'.", file.file, count, text.keyword, text, test)
-											testing.expectf(t, text.keyword_localized == test.keyword_localized && text_ok, "%v test %v text keyword {{%v}}:'%v', expected '%v'.", file.file, count, text.keyword, text, test)
+											testing.expectf(t, text.text == test.text && text_ok, "%v test %v text keyword {{%v}}:'%v', expected '%v'", file.file, count, text.keyword, text, test)
+											testing.expectf(t, text.language == test.language && text_ok, "%v test %v text keyword {{%v}}:'%v', expected '%v'", file.file, count, text.keyword, text, test)
+											testing.expectf(t, text.keyword_localized == test.keyword_localized && text_ok, "%v test %v text keyword {{%v}}:'%v', expected '%v'", file.file, count, text.keyword, text, test)
 										}
 										}
 									}
 									}
 								case "ctjn0g04": // international UTF-8, japanese
 								case "ctjn0g04": // international UTF-8, japanese
 									if file.file in Expected_Text {
 									if file.file in Expected_Text {
 										if text.keyword in Expected_Text[file.file] {
 										if text.keyword in Expected_Text[file.file] {
 											test := Expected_Text[file.file][text.keyword]
 											test := Expected_Text[file.file][text.keyword]
-											testing.expectf(t, text.text == test.text && text_ok, "%v test %v text keyword {{%v}}:'%v', expected '%v'.", file.file, count, text.keyword, text, test)
-											testing.expectf(t, text.language == test.language && text_ok, "%v test %v text keyword {{%v}}:'%v', expected '%v'.", file.file, count, text.keyword, text, test)
-											testing.expectf(t, text.keyword_localized == test.keyword_localized && text_ok, "%v test %v text keyword {{%v}}:'%v', expected '%v'.", file.file, count, text.keyword, text, test)
+											testing.expectf(t, text.text == test.text && text_ok, "%v test %v text keyword {{%v}}:'%v', expected '%v'", file.file, count, text.keyword, text, test)
+											testing.expectf(t, text.language == test.language && text_ok, "%v test %v text keyword {{%v}}:'%v', expected '%v'", file.file, count, text.keyword, text, test)
+											testing.expectf(t, text.keyword_localized == test.keyword_localized && text_ok, "%v test %v text keyword {{%v}}:'%v', expected '%v'", file.file, count, text.keyword, text, test)
 										}
 										}
 									}
 									}
 								}
 								}
@@ -1823,7 +1820,7 @@ run_png_suite :: proc(t: ^testing.T, suite: []PNG_Test) {
 								if file.file == "exif2c08" { // chunk with jpeg exif data
 								if file.file == "exif2c08" { // chunk with jpeg exif data
 									exif, exif_ok := png.exif(c)
 									exif, exif_ok := png.exif(c)
 									testing.expectf(t, exif.byte_order == .big_endian && exif_ok, "%v test %v eXIf byte order '%v', expected 'big_endian'.", file.file, count, exif.byte_order)
 									testing.expectf(t, exif.byte_order == .big_endian && exif_ok, "%v test %v eXIf byte order '%v', expected 'big_endian'.", file.file, count, exif.byte_order)
-									testing.expectf(t, len(exif.data)  == 978         && exif_ok, "%v test %v eXIf data length '%v', expected '%v'.", file.file, len(exif.data), 978)
+									testing.expectf(t, len(exif.data)  == 978         && exif_ok, "%v test %v eXIf data length '%v', expected '%v'", file.file, len(exif.data), 978)
 								}
 								}
 							}
 							}
 						}
 						}
@@ -1833,4 +1830,532 @@ run_png_suite :: proc(t: ^testing.T, suite: []PNG_Test) {
 			png.destroy(img)
 			png.destroy(img)
 		}
 		}
 	}
 	}
+	return
+}
+
+/*
+	Basic format tests:
+		https://entropymine.com/jason/bmpsuite/bmpsuite/html/bmpsuite.html - Version 2.8; 2023-11-28
+
+	The BMP Suite image generator itself is GPL, and isn't included, nor did it have its code referenced.
+	We do thank the author for the well-researched test suite, which we are free to include:
+
+		"Image files generated by this program are not covered by this license, and are
+		in the public domain (except for the embedded ICC profiles)."
+
+	The files with embedded ICC profiles aren't part of Odin's test assets. We don't support BMP metadata.
+	We don't support all "possibly correct" images, and thus only ship a subset of these from the BMP Suite.
+*/
+Basic_BMP_Tests := []Test{
+	{
+		"pal1", {
+			{Default,         nil, {127, 64, 3,  8}, 0x_3ce8_1fae},
+		},
+	},
+	{
+		"pal1wb", {
+			{Default,         nil, {127, 64, 3,  8}, 0x_3ce8_1fae},
+		},
+	},
+	{
+		"pal1bg", {
+			{Default,         nil, {127, 64, 3,  8}, 0x_9e91_174a},
+		},
+	},
+	{
+		"pal4", {
+			{Default,         nil, {127, 64, 3,  8}, 0x_288e_4371},
+		},
+	},
+	{
+		"pal4gs", {
+			{Default,         nil, {127, 64, 3,  8}, 0x_452d_a01a},
+		},
+	},
+	{
+		"pal4rle", {
+			{Default,         nil, {127, 64, 3,  8}, 0x_288e_4371},
+		},
+	},
+	{
+		"pal8", {
+			{Default,         nil, {127, 64, 3,  8}, 0x_3845_4155},
+		},
+	},
+	{
+		"pal8-0", {
+			{Default,         nil, {127, 64, 3,  8}, 0x_3845_4155},
+		},
+	},
+	{
+		"pal8gs", {
+			{Default,         nil, {127, 64, 3,  8}, 0x_09c2_7834},
+		},
+	},
+	{
+		"pal8rle", {
+			{Default,         nil, {127, 64, 3,  8}, 0x_3845_4155},
+		},
+	},
+	{
+		"pal8w126", {
+			{Default,         nil, {126, 63, 3,  8}, 0x_bb66_4cda},
+		},
+	},
+	{
+		"pal8w125", {
+			{Default,         nil, {125, 62, 3,  8}, 0x_3ab8_f7c5},
+		},
+	},
+	{
+		"pal8w124", {
+			{Default,         nil, {124, 61, 3,  8}, 0x_b53e_e6c8},
+		},
+	},
+	{
+		"pal8topdown", {
+			{Default,         nil, {127, 64, 3,  8}, 0x_3845_4155},
+		},
+	},
+	{
+		"pal8nonsquare", {
+			{Default,         nil, {127, 32, 3,  8}, 0x_8409_c689},
+		},
+	},
+	{
+		"pal8v4", {
+			{Default,         nil, {127, 64, 3,  8}, 0x_3845_4155},
+		},
+	},
+	{
+		"pal8v5", {
+			{Default,         nil, {127, 64, 3,  8}, 0x_3845_4155},
+		},
+	},
+	{
+		"rgb16", {
+			{Default,         nil, {127, 64, 3,  8}, 0x_8b6f_81a2},
+		},
+	},
+	{
+		"rgb16bfdef", {
+			{Default,         nil, {127, 64, 3,  8}, 0x_8b6f_81a2},
+		},
+	},
+	{
+		"rgb16-565", {
+			{Default,         nil, {127, 64, 3,  8}, 0x_8c73_a2ff},
+		},
+	},
+	{
+		"rgb16-565pal", {
+			{Default,         nil, {127, 64, 3,  8}, 0x_8c73_a2ff},
+		},
+	},
+	{
+		"rgb24", {
+			{Default,         nil, {127, 64, 3,  8}, 0x_025b_ba0a},
+		},
+	},
+	{
+		"rgb24pal", {
+			{Default,         nil, {127, 64, 3,  8}, 0x_025b_ba0a},
+		},
+	},
+	{
+		"rgb32", {
+			{Default,         nil, {127, 64, 3,  8}, 0x_025b_ba0a},
+		},
+	},
+	{
+		"rgb32bf", {
+			{Default,         nil, {127, 64, 3,  8}, 0x_025b_ba0a},
+		},
+	},
+	{
+		"rgb32bfdef", {
+			{Default,         nil, {127, 64, 3,  8}, 0x_025b_ba0a},
+		},
+	},
+}
+
+OS2_Tests := []Test{
+	{
+		"pal8os2", { // An OS/2-style bitmap. This format can be called OS/2 BMPv1, or Windows BMPv2.
+			{Default,         nil, {127, 64, 3,  8}, 0x_3845_4155},
+		},
+	},
+	{
+		"pal8os2-sz", { // An OS/2-style bitmap. This format can be called OS/2 BMPv1, or Windows BMPv2.
+			{Default,         nil, {127, 64, 3,  8}, 0x_3845_4155},
+		},
+	},
+	{
+		"pal8os2-hs", { // An OS/2-style bitmap. This format can be called OS/2 BMPv1, or Windows BMPv2.
+			{Default,         nil, {127, 64, 3,  8}, 0x_3845_4155},
+		},
+	},
+	{
+		"pal8os2sp", { // An OS/2-style bitmap. This format can be called OS/2 BMPv1, or Windows BMPv2.
+			{Default,         nil, {127, 64, 3,  8}, 0x_3845_4155},
+		},
+	},
+	{
+		"pal8os2v2", { // An OS/2-style bitmap. This format can be called OS/2 BMPv1, or Windows BMPv2.
+			{Default,         nil, {127, 64, 3,  8}, 0x_3845_4155},
+		},
+	},
+	{
+		"pal8os2v2-16", { // An OS/2-style bitmap. This format can be called OS/2 BMPv1, or Windows BMPv2.
+			{Default,         nil, {127, 64, 3,  8}, 0x_3845_4155},
+		},
+	},
+	{
+		"pal8os2v2-sz", { // An OS/2-style bitmap. This format can be called OS/2 BMPv1, or Windows BMPv2.
+			{Default,         nil, {127, 64, 3,  8}, 0x_3845_4155},
+		},
+	},
+	{
+		"pal8os2v2-40sz", { // An OS/2-style bitmap. This format can be called OS/2 BMPv1, or Windows BMPv2.
+			{Default,         nil, {127, 64, 3,  8}, 0x_3845_4155},
+		},
+	},
+}
+
+// BMP files that aren't 100% to spec. Some we support, some we don't.
+Questionable_BMP_Tests := []Test{
+	{
+		"pal1p1", { // Spec says 1-bit image has 2 palette entries. This one has 1.
+			{Default,         nil, {127, 64, 3,  8}, 0x_2b54_2560},
+		},
+	},
+	{
+		"pal2", { // 2-bit. Allowed on Windows CE. Irfanview doesn't support it.
+			{Default,         nil, {127, 64, 3,  8}, 0x_0da2_7594},
+		},
+	},
+	{
+		"pal2color", { // 2-bit, with color palette.
+			{Default,         nil, {127, 64, 3,  8}, 0x_f0d8_c5d6},
+		},
+	},
+	{
+		"pal8offs", { // 300 palette entries (yes, only 256 can be used)
+			{Default,         nil, {127, 64, 3,  8}, 0x_3845_4155},
+		},
+	},
+	{
+		"pal8oversizepal", { // Some padding between palette and image data
+			{Default,         nil, {127, 64, 3,  8}, 0x_3845_4155},
+		},
+	},
+	{
+		"pal4rletrns", { // Using palette tricks to skip pixels
+			{Default,         nil, {127, 64, 3,  8}, 0x_eed4_e744},
+		},
+	},
+	{
+		"pal4rlecut", { // Using palette tricks to skip pixels
+			{Default,         nil, {127, 64, 3,  8}, 0x_473fbc7d},
+		},
+	},
+	{
+		"pal8rletrns", { // Using palette tricks to skip pixels
+			{Default,         nil, {127, 64, 3,  8}, 0x_fe1f_e560},
+		},
+	},
+	{
+		"pal8rlecut", { // Using palette tricks to skip pixels
+			{Default,         nil, {127, 64, 3,  8}, 0x_bd04_3619},
+		},
+	},
+	{
+		"rgb16faketrns", { // Using palette tricks to skip pixels
+			{Default,         nil, {127, 64, 3,  8}, 0x_8b6f_81a2},
+		},
+	},
+	{
+		"rgb16-231", { // Custom bit fields
+			{Default,         nil, {127, 64, 3,  8}, 0x_7393_a163},
+		},
+	},
+	{
+		"rgb16-3103", { // Custom bit fields
+			{Default,         nil, {127, 64, 3,  8}, 0x_3b66_2189},
+		},
+	},
+	{
+		"rgba16-4444", { // Custom bit fields
+			{Default,         nil, {127, 64, 3,  8}, 0x_b785_1f9f},
+		},
+	},
+	{
+		"rgba16-5551", { // Custom bit fields
+			{Default,         nil, {127, 64, 3,  8}, 0x_8b6f_81a2},
+		},
+	},
+	{
+		"rgba16-1924", { // Custom bit fields
+			{Default,         nil, {127, 64, 3,  8}, 0x_f038_2bed},
+		},
+	},
+	{
+		"rgb32-xbgr", { // Custom bit fields
+			{Default,         nil, {127, 64, 3,  8}, 0x_025b_ba0a},
+		},
+	},
+	{
+		"rgb32fakealpha", { // Custom bit fields
+			{Default,         nil, {127, 64, 3,  8}, 0x_025b_ba0a},
+		},
+	},
+	{
+		"rgb32-111110", { // Custom bit fields
+			{Default,         nil, {127, 64, 3,  8}, 0x_b2c7_a8ff},
+		},
+	},
+	{
+		"rgb32-7187", { // Custom bit fields
+			{Default,         nil, {127, 64, 3,  8}, 0x_b93a_4291},
+		},
+	},
+	{
+		"rgba32-1", { // Custom bit fields
+			{Default,         nil, {127, 64, 3,  8}, 0x_7b67_823d},
+		},
+	},
+	{
+		"rgba32-2", { // Custom bit fields
+			{Default,         nil, {127, 64, 3,  8}, 0x_7b67_823d},
+		},
+	},
+	{
+		"rgba32-1010102", { // Custom bit fields
+			{Default,         nil, {127, 64, 3,  8}, 0x_aa42_0b16},
+		},
+	},
+	{
+		"rgba32-81284", { // Custom bit fields
+			{Default,         nil, {127, 64, 3,  8}, 0x_28a2_4c16},
+		},
+	},
+	{
+		"rgba32-61754", { // Custom bit fields
+			{Default,         nil, {127, 64, 3,  8}, 0x_4aae_26ed},
+		},
+	},
+	{
+		"rgba32abf", { // Custom bit fields
+			{Default,         nil, {127, 64, 3,  8}, 0x_7b67_823d},
+		},
+	},
+	{
+		"rgb32h52", { // Truncated header (RGB bit fields included)
+			{Default,         nil, {127, 64, 3,  8}, 0x_025b_ba0a},
+		},
+	},
+	{
+		"rgba32h56", { // Truncated header (RGBA bit fields included)
+			{Default,         nil, {127, 64, 3,  8}, 0x_7b67_823d},
+		},
+	},
+}
+
+// Unsupported BMP features, or malformed images.
+Unsupported_BMP_Tests := []Test{
+	{
+		"ba-bm", { // An OS/2 Bitmap array. We don't support this BA format.
+			{Default, .Unsupported_OS2_File,    {127, 32, 3,  8}, 0x_0000_0000},
+		},
+	},
+	{
+		"pal1huffmsb", { // An OS/2 file with Huffman 1D compression
+			{Default, .Unsupported_Compression, {127, 32, 3,  8}, 0x_0000_0000},
+		},
+	},
+	{
+		"rgb24rle24", { // An OS/2 file with RLE24 compression
+			{Default, .Unsupported_Compression, {127, 64, 3,  8}, 0x_0000_0000},
+		},
+	},
+	{
+		"rgba64", { // An OS/2 file with RLE24 compression
+			{Default, .Unsupported_BPP,         {127, 64, 3,  8}, 0x_0000_0000},
+		},
+	},
+}
+
+// Malformed / malicious files
+Known_Bad_BMP_Tests := []Test{
+	{
+		"badbitcount", {
+			{Default, .Unsupported_BPP, {127, 64, 3, 8}, 0x_3ce81fae},
+		},
+	},
+	{
+		"badbitssize", {
+			{Default, nil, {127, 64, 3, 8}, 0x_3ce8_1fae},
+		},
+	},
+	{
+		"baddens1", {
+			{Default, nil, {127, 64, 3, 8}, 0x_3ce8_1fae},
+		},
+	},
+	{
+		"baddens2", {
+			{Default, nil, {127, 64, 3, 8}, 0x_3ce8_1fae},
+		},
+	},
+	{
+		"badfilesize", {
+			{Default, nil, {127, 64, 3, 8}, 0x_3ce8_1fae},
+		},
+	},
+	{
+		"badheadersize", {
+			{Default, nil, {127, 64, 3, 8}, 0x_3ce8_1fae},
+		},
+	},
+	{
+		"badpalettesize", {
+			{Default, nil, {127, 64, 3, 8}, 0x_3845_4155},
+		},
+	},
+	{
+		"badplanes", {
+			{Default, nil, {127, 64, 3, 8}, 0x_3ce8_1fae},
+		},
+	},
+	{
+		"badrle", {
+			{Default, nil, {127, 64, 3, 8}, 0x_1457_aae4},
+		},
+	},
+	{
+		"badrle4", {
+			{Default, nil, {127, 64, 3, 8}, 0x_6764_d2ac},
+		},
+	},
+	{
+		"badrle4bis", {
+			{Default, nil, {127, 64, 3, 8}, 0x_935d_bb37},
+		},
+	},
+	{
+		"badrle4ter", {
+			{Default, nil, {127, 64, 3, 8}, 0x_f2ba_5b08},
+		},
+	},
+	{
+		"badrlebis", {
+			{Default, nil, {127, 64, 3, 8}, 0x_07e2_d730},
+		},
+	},
+	{
+		"badrleter", {
+			{Default, nil, {127, 64, 3, 8}, 0x_a874_2742},
+		},
+	},
+	{
+		"badwidth", {
+			{Default, nil, {127, 64, 3, 8}, 0x_3ce8_1fae},
+		},
+	},
+	{
+		"pal8badindex", {
+			{Default, nil, {127, 64, 3, 8}, 0x_0450_0d02},
+		},
+	},
+	{
+		"reallybig", {
+			{Default, .Image_Dimensions_Too_Large, {3000000, 2000000, 1, 24}, 0x_0000_0000},
+		},
+	},
+	{
+		"rgb16-880", {
+			{Default, nil, {127, 64, 3, 8}, 0x_f1c2_0c73},
+		},
+	},
+	{
+		"rletopdown", {
+			{Default, nil, {127, 64, 3, 8}, 0x_3845_4155},
+		},
+	},
+	{
+		"shortfile", {
+			{Default, .Short_Buffer, {127, 64, 1, 1}, 0x_0000_0000},
+		},
+	},
+}
+
+@test
+bmp_test_basic :: proc(t: ^testing.T) {
+	run_bmp_suite(t, Basic_BMP_Tests)
+}
+
+@test
+bmp_test_os2 :: proc(t: ^testing.T) {
+	run_bmp_suite(t, OS2_Tests)
+}
+
+@test
+bmp_test_questionable :: proc(t: ^testing.T) {
+	run_bmp_suite(t, Questionable_BMP_Tests)
+}
+
+@test
+bmp_test_unsupported :: proc(t: ^testing.T) {
+	run_bmp_suite(t, Unsupported_BMP_Tests)
+}
+
+@test
+bmp_test_known_bad :: proc(t: ^testing.T) {
+	run_bmp_suite(t, Known_Bad_BMP_Tests)
+}
+
+run_bmp_suite :: proc(t: ^testing.T, suite: []Test) {
+	for file in suite {
+		test_file := strings.concatenate({TEST_SUITE_PATH_BMP, "/", file.file, ".bmp"}, context.allocator)
+		defer delete(test_file)
+
+		for test in file.tests {
+			img, err := bmp.load(test_file, test.options)
+
+			passed := (test.expected_error == nil && err == nil) || (test.expected_error == err)
+			testing.expectf(t, passed, "%q failed to load with error %v.", file.file, err)
+
+			if err == nil { // No point in running the other tests if it didn't load.
+				pixels := bytes.buffer_to_bytes(&img.pixels)
+
+				dims   := Dims{img.width, img.height, img.channels, img.depth}
+				testing.expectf(t, test.dims == dims, "%v has %v, expected: %v.", file.file, dims, test.dims)
+
+				img_hash := hash.crc32(pixels)
+				testing.expectf(t, test.hash == img_hash, "%v test #1's hash is %08x, expected %08x with %v.", file.file, img_hash, test.hash, test.options)
+
+				// Save to BMP file in memory
+				buf: bytes.Buffer
+				save_err := bmp.save(&buf, img)
+				testing.expectf(t, save_err == nil, "expected saving to BMP in memory not to raise error, got %v", save_err)
+
+				// Reload BMP from memory
+				reload_img, reload_err := bmp.load(buf.buf[:])
+				testing.expectf(t, reload_err == nil, "expected reloading BMP from memory not to raise error, got %v", reload_err)
+
+				testing.expect(t, img.width    == reload_img.width    && img.height == reload_img.height, "expected saved BMP to have the same dimensions")
+				testing.expect(t, img.channels == reload_img.channels && img.depth  == reload_img.depth,  "expected saved BMP to have the same dimensions")
+
+				reload_pixels := bytes.buffer_to_bytes(&reload_img.pixels)
+				reload_hash   := hash.crc32(reload_pixels)
+
+				testing.expectf(t, img_hash == reload_hash, "expected saved BMP to have the same pixel hash (%08x), got %08x", img_hash, reload_hash)
+
+				bytes.buffer_destroy(&buf)
+				bmp.destroy(reload_img)
+			}
+			bmp.destroy(img)
+		}
+	}
+	return
 }
 }

+ 2 - 1
tests/core/net/test_core_net.odin

@@ -8,6 +8,7 @@
 
 
 	A test suite for `core:net`
 	A test suite for `core:net`
 */
 */
+//+build !netbsd
 package test_core_net
 package test_core_net
 
 
 import "core:testing"
 import "core:testing"
@@ -552,4 +553,4 @@ binstr_to_address :: proc(t: ^testing.T, binstr: string) -> (address: net.Addres
 		return nil
 		return nil
 	}
 	}
 	panic("Invalid test case")
 	panic("Invalid test case")
-}
+}

+ 40 - 0
tests/core/normal.odin

@@ -0,0 +1,40 @@
+package tests_core
+
+import rlibc "core:c/libc"
+
+@(init)
+download_assets :: proc() {
+	if rlibc.system("python3 " + ODIN_ROOT + "tests/core/download_assets.py " + ODIN_ROOT + "tests/core/assets") != 0 {
+		panic("downloading test assets failed!")
+	}
+}
+
+@(require) import "c/libc"
+@(require) import "compress"
+@(require) import "container"
+@(require) import "encoding/base64"
+@(require) import "encoding/cbor"
+@(require) import "encoding/hex"
+@(require) import "encoding/hxa"
+@(require) import "encoding/json"
+@(require) import "encoding/varint"
+@(require) import "encoding/xml"
+@(require) import "fmt"
+@(require) import "image"
+@(require) import "math"
+@(require) import "math/big"
+@(require) import "math/linalg/glsl"
+@(require) import "math/noise"
+@(require) import "mem"
+@(require) import "net"
+@(require) import "odin"
+@(require) import "path/filepath"
+@(require) import "reflect"
+@(require) import "runtime"
+@(require) import "slice"
+@(require) import "strconv"
+@(require) import "strings"
+@(require) import "text/i18n"
+@(require) import "text/match"
+@(require) import "thread"
+@(require) import "time"

+ 0 - 10
tests/core/os/test_core_os_exit.odin

@@ -1,10 +0,0 @@
-// Tests that Odin run returns exit code of built executable on Unix
-// Needs exit status to be inverted to return 0 on success, e.g.
-// $(./odin run tests/core/os/test_core_os_exit.odin && exit 1 || exit 0)
-package test_core_os_exit
-
-import "core:os"
-
-main :: proc() {
-	os.exit(1)
-}

+ 5 - 0
tests/core/speed.odin

@@ -0,0 +1,5 @@
+// Tests intended to be ran with optimizations on
+package tests_core
+
+@(require) import "crypto"
+@(require) import "hash"

+ 145 - 0
tests/core/strconv/test_core_strconv.odin

@@ -0,0 +1,145 @@
+package test_core_strconv
+
+import "core:math"
+import "core:strconv"
+import "core:testing"
+
+@(test)
+test_float :: proc(t: ^testing.T) {
+	n: int
+	f: f64
+	ok: bool
+
+	f, ok = strconv.parse_f64("1.2", &n)
+	testing.expect_value(t, f, 1.2)
+	testing.expect_value(t, n, 3)
+	testing.expect_value(t, ok, true)
+
+	f, ok = strconv.parse_f64("1.2a", &n)
+	testing.expect_value(t, f, 1.2)
+	testing.expect_value(t, n, 3)
+	testing.expect_value(t, ok, false)
+
+	f, ok = strconv.parse_f64("+", &n)
+	testing.expect_value(t, f, 0)
+	testing.expect_value(t, n, 0)
+	testing.expect_value(t, ok, false)
+
+	f, ok = strconv.parse_f64("-", &n)
+	testing.expect_value(t, f, 0)
+	testing.expect_value(t, n, 0)
+	testing.expect_value(t, ok, false)
+
+}
+
+@(test)
+test_nan :: proc(t: ^testing.T) {
+	n: int
+	f: f64
+	ok: bool
+
+	f, ok = strconv.parse_f64("nan", &n)
+	testing.expect_value(t, math.classify(f), math.Float_Class.NaN)
+	testing.expect_value(t, n, 3)
+	testing.expect_value(t, ok, true)
+
+	f, ok = strconv.parse_f64("nAN", &n)
+	testing.expect_value(t, math.classify(f), math.Float_Class.NaN)
+	testing.expect_value(t, n, 3)
+	testing.expect_value(t, ok, true)
+
+	f, ok = strconv.parse_f64("Nani", &n)
+	testing.expect_value(t, math.classify(f), math.Float_Class.NaN)
+	testing.expect_value(t, n, 3)
+	testing.expect_value(t, ok, false)
+}
+
+@(test)
+test_infinity :: proc(t: ^testing.T) {
+	pos_inf := math.inf_f64(+1)
+	neg_inf := math.inf_f64(-1)
+
+	n: int
+	s := "infinity"
+
+	for i in 0 ..< len(s) + 1 {
+		ss := s[:i]
+		f, ok := strconv.parse_f64(ss, &n)
+		if i >= 3 { // "inf" .. "infinity"
+			expected_n := 8 if i == 8 else 3
+			expected_ok := i == 3 || i == 8
+			testing.expect_value(t, f, pos_inf)
+			testing.expect_value(t, n, expected_n)
+			testing.expect_value(t, ok, expected_ok)
+			testing.expect_value(t, math.classify(f), math.Float_Class.Inf)
+		} else { // invalid substring
+			testing.expect_value(t, f, 0)
+			testing.expect_value(t, n, 0)
+			testing.expect_value(t, ok, false)
+			testing.expect_value(t, math.classify(f), math.Float_Class.Zero)
+		}
+	}
+	
+	s = "+infinity"
+	for i in 0 ..< len(s) + 1 {
+		ss := s[:i]
+		f, ok := strconv.parse_f64(ss, &n)
+		if i >= 4 { // "+inf" .. "+infinity"
+			expected_n := 9 if i == 9 else 4
+			expected_ok := i == 4 || i == 9
+			testing.expect_value(t, f, pos_inf)
+			testing.expect_value(t, n, expected_n)
+			testing.expect_value(t, ok, expected_ok)
+			testing.expect_value(t, math.classify(f), math.Float_Class.Inf)
+		} else { // invalid substring
+			testing.expect_value(t, f, 0)
+			testing.expect_value(t, n, 0)
+			testing.expect_value(t, ok, false)
+			testing.expect_value(t, math.classify(f), math.Float_Class.Zero)
+		}
+	}
+
+	s = "-infinity"
+	for i in 0 ..< len(s) + 1 {
+		ss := s[:i]
+		f, ok := strconv.parse_f64(ss, &n)
+		if i >= 4 { // "-inf" .. "infinity"
+			expected_n := 9 if i == 9 else 4
+			expected_ok := i == 4 || i == 9
+			testing.expect_value(t, f, neg_inf)
+			testing.expect_value(t, n, expected_n)
+			testing.expect_value(t, ok, expected_ok)
+			testing.expect_value(t, math.classify(f), math.Float_Class.Neg_Inf)
+		} else { // invalid substring
+			testing.expect_value(t, f, 0)
+			testing.expect_value(t, n, 0)
+			testing.expect_value(t, ok, false)
+			testing.expect_value(t, math.classify(f), math.Float_Class.Zero)
+		}
+	}
+
+	// Make sure odd casing works.
+	batch := [?]string {"INFiniTY", "iNfInItY", "InFiNiTy"}
+	for ss in batch {
+		f, ok := strconv.parse_f64(ss, &n)
+		testing.expect_value(t, f, pos_inf)
+		testing.expect_value(t, n, 8)
+		testing.expect_value(t, ok, true)
+		testing.expect_value(t, math.classify(f), math.Float_Class.Inf)
+	}
+
+	// Explicitly check how trailing characters are handled.
+	s = "infinityyyy"
+	f, ok := strconv.parse_f64(s, &n)
+	testing.expect_value(t, f, pos_inf)
+	testing.expect_value(t, n, 8)
+	testing.expect_value(t, ok, false)
+	testing.expect_value(t, math.classify(f), math.Float_Class.Inf)
+
+	s = "inflippity"
+	f, ok = strconv.parse_f64(s, &n)
+	testing.expect_value(t, f, pos_inf)
+	testing.expect_value(t, n, 3)
+	testing.expect_value(t, ok, false)
+	testing.expect_value(t, math.classify(f), math.Float_Class.Inf)
+}

+ 0 - 22
tests/internal/Makefile

@@ -1,22 +0,0 @@
-ODIN=../../odin
-COMMON=-file -vet -strict-style -o:minimal
-
-all: asan_test rtti_test map_test pow_test 128_test string_compare_test
-
-rtti_test:
-	$(ODIN) test test_rtti.odin $(COMMON)
-
-map_test:
-	$(ODIN) test test_map.odin $(COMMON)
-
-pow_test:
-	$(ODIN) test test_pow.odin $(COMMON)
-
-128_test:
-	$(ODIN) test test_128.odin $(COMMON)
-
-asan_test:
-	$(ODIN) test test_asan.odin $(COMMON) -sanitize:address -debug
-
-string_compare_test:
-	$(ODIN) test test_string_compare.odin $(COMMON)

+ 0 - 9
tests/internal/build.bat

@@ -1,9 +0,0 @@
-@echo off
-set PATH_TO_ODIN==..\..\odin
-set COMMON=-file -vet -strict-style -o:minimal
-%PATH_TO_ODIN% test test_rtti.odin %COMMON% || exit /b
-%PATH_TO_ODIN% test test_map.odin  %COMMON% || exit /b
-%PATH_TO_ODIN% test test_pow.odin  %COMMON% || exit /b
-%PATH_TO_ODIN% test test_asan.odin %COMMON% || exit /b
-%PATH_TO_ODIN% test test_128.odin  %COMMON% || exit /b
-%PATH_TO_ODIN% test test_string_compare.odin %COMMON% || exit /b

+ 1 - 1
tests/internal/test_128.odin

@@ -1,4 +1,4 @@
-package test_128
+package test_internal
 
 
 import "core:testing"
 import "core:testing"
 
 

+ 1 - 1
tests/internal/test_asan.odin

@@ -1,5 +1,5 @@
 // Intended to contain code that would trigger asan easily if the abi was set up badly.
 // Intended to contain code that would trigger asan easily if the abi was set up badly.
-package test_asan
+package test_internal
 
 
 import "core:testing"
 import "core:testing"
 
 

+ 2 - 2
tests/internal/test_map.odin

@@ -1,4 +1,4 @@
-package test_internal_map
+package test_internal
 
 
 import "core:log"
 import "core:log"
 import "base:intrinsics"
 import "base:intrinsics"
@@ -309,4 +309,4 @@ set_delete_random_key_value :: proc(t: ^testing.T) {
 		}
 		}
 		seed_incr += 1
 		seed_incr += 1
 	}
 	}
-}
+}

+ 2 - 2
tests/internal/test_pow.odin

@@ -1,4 +1,4 @@
-package test_internal_math_pow
+package test_internal
 
 
 @(require) import "core:log"
 @(require) import "core:log"
 import "core:math"
 import "core:math"
@@ -42,4 +42,4 @@ pow_test :: proc(t: ^testing.T) {
 			testing.expectf(t,  _v1 == _v2, "Expected math.pow2_f16(%d) == math.pow(2, %d) (= %04x), got %04x", exp, exp, _v1, _v2)
 			testing.expectf(t,  _v1 == _v2, "Expected math.pow2_f16(%d) == math.pow(2, %d) (= %04x), got %04x", exp, exp, _v1, _v2)
 		}
 		}
 	}
 	}
-}
+}

+ 2 - 2
tests/internal/test_rtti.odin

@@ -1,4 +1,4 @@
-package test_internal_rtti
+package test_internal
 
 
 import "core:fmt"
 import "core:fmt"
 import "core:testing"
 import "core:testing"
@@ -43,4 +43,4 @@ rtti_test :: proc(t: ^testing.T) {
 	l_s := fmt.tprintf("%s", l_buggy)
 	l_s := fmt.tprintf("%s", l_buggy)
 	testing.expectf(t, g_s == EXPECTED_REPR, "Expected fmt.tprintf(\"%%s\", g_s)) to return \"%v\", got \"%v\"", EXPECTED_REPR, g_s)
 	testing.expectf(t, g_s == EXPECTED_REPR, "Expected fmt.tprintf(\"%%s\", g_s)) to return \"%v\", got \"%v\"", EXPECTED_REPR, g_s)
 	testing.expectf(t, l_s == EXPECTED_REPR, "Expected fmt.tprintf(\"%%s\", l_s)) to return \"%v\", got \"%v\"", EXPECTED_REPR, l_s)
 	testing.expectf(t, l_s == EXPECTED_REPR, "Expected fmt.tprintf(\"%%s\", l_s)) to return \"%v\", got \"%v\"", EXPECTED_REPR, l_s)
-}
+}

+ 2 - 2
tests/internal/test_string_compare.odin

@@ -1,4 +1,4 @@
-package test_internal_string_compare
+package test_internal
 
 
 import "core:testing"
 import "core:testing"
 
 
@@ -54,4 +54,4 @@ string_compare :: proc(t: ^testing.T) {
 			}
 			}
 		}
 		}
 	}
 	}
-}
+}

+ 0 - 1
tests/issues/run.bat

@@ -10,7 +10,6 @@ set COMMON=-define:ODIN_TEST_FANCY=false -file -vet -strict-style
 ..\..\..\odin test ..\test_issue_829.odin  %COMMON%   || exit /b
 ..\..\..\odin test ..\test_issue_829.odin  %COMMON%   || exit /b
 ..\..\..\odin test ..\test_issue_1592.odin %COMMON%  || exit /b
 ..\..\..\odin test ..\test_issue_1592.odin %COMMON%  || exit /b
 ..\..\..\odin test ..\test_issue_2056.odin %COMMON%  || exit /b
 ..\..\..\odin test ..\test_issue_2056.odin %COMMON%  || exit /b
-..\..\..\odin test ..\test_issue_2087.odin %COMMON%  || exit /b
 ..\..\..\odin build ..\test_issue_2113.odin %COMMON% -debug || exit /b
 ..\..\..\odin build ..\test_issue_2113.odin %COMMON% -debug || exit /b
 ..\..\..\odin test ..\test_issue_2466.odin %COMMON%  || exit /b
 ..\..\..\odin test ..\test_issue_2466.odin %COMMON%  || exit /b
 ..\..\..\odin test ..\test_issue_2615.odin %COMMON%  || exit /b
 ..\..\..\odin test ..\test_issue_2615.odin %COMMON%  || exit /b

+ 3 - 5
tests/issues/run.sh

@@ -1,4 +1,4 @@
-#!/bin/bash
+#!/usr/bin/env bash
 set -eu
 set -eu
 
 
 mkdir -p build
 mkdir -p build
@@ -6,23 +6,21 @@ pushd build
 ODIN=../../../odin
 ODIN=../../../odin
 COMMON="-define:ODIN_TEST_FANCY=false -file -vet -strict-style"
 COMMON="-define:ODIN_TEST_FANCY=false -file -vet -strict-style"
 
 
-NO_NIL_ERR="Error: "
-
 set -x
 set -x
 
 
 $ODIN test ../test_issue_829.odin  $COMMON
 $ODIN test ../test_issue_829.odin  $COMMON
 $ODIN test ../test_issue_1592.odin $COMMON
 $ODIN test ../test_issue_1592.odin $COMMON
 $ODIN test ../test_issue_2056.odin $COMMON
 $ODIN test ../test_issue_2056.odin $COMMON
-$ODIN test ../test_issue_2087.odin $COMMON
 $ODIN build ../test_issue_2113.odin $COMMON -debug
 $ODIN build ../test_issue_2113.odin $COMMON -debug
 $ODIN test ../test_issue_2466.odin $COMMON
 $ODIN test ../test_issue_2466.odin $COMMON
 $ODIN test ../test_issue_2615.odin $COMMON
 $ODIN test ../test_issue_2615.odin $COMMON
 $ODIN test ../test_issue_2637.odin $COMMON
 $ODIN test ../test_issue_2637.odin $COMMON
 $ODIN test ../test_issue_2666.odin $COMMON
 $ODIN test ../test_issue_2666.odin $COMMON
-if [[ $($ODIN build ../test_issue_2395.odin $COMMON 2>&1 >/dev/null | grep -c "$NO_NIL_ERR") -eq 2 ]] ; then
+if [[ $($ODIN build ../test_issue_2395.odin $COMMON 2>&1 >/dev/null | grep -c "Error:") -eq 2 ]] ; then
 	echo "SUCCESSFUL 1/1"
 	echo "SUCCESSFUL 1/1"
 else
 else
 	echo "SUCCESSFUL 0/1"
 	echo "SUCCESSFUL 0/1"
+	exit 1
 fi
 fi
 
 
 set +x
 set +x

+ 0 - 62
tests/issues/test_issue_2087.odin

@@ -1,62 +0,0 @@
-// Tests issue #2087 https://github.com/odin-lang/Odin/issues/2087
-package test_issues
-
-import "core:math"
-import "core:strconv"
-import "core:testing"
-
-@(test)
-test_parse_float :: proc(t: ^testing.T) {
-	{
-		f, ok := strconv.parse_f64("1.2")
-		testing.expect(t, ok && f == 1.2, "expected f64(1.2), fully consumed")
-		f, ok = strconv.parse_f64("1.2a")
-		testing.expect(t, !ok && f == 1.2, "expected f64(1.2), partially consumed")
-		f, ok = strconv.parse_f64("+")
-		testing.expect(t, !ok && f == 0.0, "expected f64(0.0), with ok=false")
-		f, ok = strconv.parse_f64("-")
-		testing.expect(t, !ok && f == 0.0, "expected f64(0.0), with ok=false")
-
-
-		f, ok = strconv.parse_f64("inf")
-		testing.expect(t, ok && math.classify(f) == math.Float_Class.Inf, "expected f64(+inf), fully consumed")
-		f, ok = strconv.parse_f64("+inf")
-		testing.expect(t, ok && math.classify(f) == math.Float_Class.Inf, "expected f64(+inf), fully consumed")
-		f, ok = strconv.parse_f64("-inf")
-		testing.expect(t, ok && math.classify(f) == math.Float_Class.Neg_Inf, "expected f64(-inf), fully consumed")
-		f, ok = strconv.parse_f64("inFinity")
-		testing.expect(t, !ok && math.classify(f) == math.Float_Class.Inf, "expected f64(+inf), partially consumed")
-		f, ok = strconv.parse_f64("+InFinity")
-		testing.expect(t, !ok && math.classify(f) == math.Float_Class.Inf, "expected f64(+inf), partially consumed")
-		f, ok = strconv.parse_f64("-InfiniTy")
-		testing.expect(t, !ok && math.classify(f) == math.Float_Class.Neg_Inf, "expected f64(-inf), partially consumed")
-		f, ok = strconv.parse_f64("nan")
-		testing.expect(t, ok && math.classify(f) == math.Float_Class.NaN, "expected f64(nan), fully consumed")
-		f, ok = strconv.parse_f64("nAN")
-		testing.expect(t, ok && math.classify(f) == math.Float_Class.NaN, "expected f64(nan), fully consumed")
-	}
-	{
-		f, ok := strconv.parse_f32("1.2")
-		testing.expect(t, ok && f == 1.2, "expected f32(1.2), fully consumed")
-
-		f, ok = strconv.parse_f32("1.2a")
-		testing.expect(t, !ok && f == 1.2, "expected f32(1.2), partially consumed")
-
-		f, ok = strconv.parse_f32("inf")
-		testing.expect(t, ok && math.classify(f) == math.Float_Class.Inf, "expected f32(+inf), fully consumed")
-		f, ok = strconv.parse_f32("+inf")
-		testing.expect(t, ok && math.classify(f) == math.Float_Class.Inf, "expected f32(+inf), fully consumed")
-		f, ok = strconv.parse_f32("-inf")
-		testing.expect(t, ok && math.classify(f) == math.Float_Class.Neg_Inf, "expected f32(-inf), fully consumed")
-		f, ok = strconv.parse_f32("inFinity")
-		testing.expect(t, !ok && math.classify(f) == math.Float_Class.Inf, "expected f32(+inf), partially consumed")
-		f, ok = strconv.parse_f32("+InFinity")
-		testing.expect(t, !ok && math.classify(f) == math.Float_Class.Inf, "expected f32(+inf), partially consumed")
-		f, ok = strconv.parse_f32("-InfiniTy")
-		testing.expect(t, !ok && math.classify(f) == math.Float_Class.Neg_Inf, "expected f32(-inf), partially consumed")
-		f, ok = strconv.parse_f32("nan")
-		testing.expect(t, ok && math.classify(f) == math.Float_Class.NaN, "expected f32(nan), fully consumed")
-		f, ok = strconv.parse_f32("nAN")
-		testing.expect(t, ok && math.classify(f) == math.Float_Class.NaN, "expected f32(nan), fully consumed")
-	}
-}

+ 0 - 2
tests/issues/test_issue_2395.odin

@@ -5,8 +5,6 @@
 // exactly 2 errors from the invalid unions
 // exactly 2 errors from the invalid unions
 package test_issues
 package test_issues
 
 
-import "core:testing"
-
 ValidUnion :: union($T: typeid) #no_nil {
 ValidUnion :: union($T: typeid) #no_nil {
     T,
     T,
     f32,
     f32,

+ 0 - 10
tests/vendor/Makefile

@@ -1,10 +0,0 @@
-ODIN=../../odin
-ODINFLAGS=
-
-OS=$(shell uname)
-
-ifeq ($(OS), OpenBSD)
-    ODINFLAGS:=$(ODINFLAGS) -extra-linker-flags:-L/usr/local/lib
-endif
-
-all:

+ 3 - 0
tests/vendor/all.odin

@@ -0,0 +1,3 @@
+package tests_vendor
+
+@(require) import "glfw"

+ 0 - 8
tests/vendor/build.bat

@@ -1,8 +0,0 @@
-@echo off
-set COMMON=-show-timings -no-bounds-check -vet -strict-style -define:ODIN_TEST_FANCY=false
-set PATH_TO_ODIN==..\..\odin
-
-echo ---
-echo Running vendor:glfw tests
-echo ---
-%PATH_TO_ODIN% test glfw %COMMON% -out:vendor_glfw.exe || exit /b

+ 1 - 0
tests/vendor/glfw/test_vendor_glfw.odin

@@ -1,3 +1,4 @@
+//+build darwin, windows
 package test_vendor_glfw
 package test_vendor_glfw
 
 
 import "core:testing"
 import "core:testing"

+ 1 - 1
vendor/directx/d3d11/d3d11.odin

@@ -3374,7 +3374,7 @@ CREATE_DEVICE_FLAG :: enum u32 {
 	DEBUGGABLE                                    = 6,
 	DEBUGGABLE                                    = 6,
 	PREVENT_ALTERING_LAYER_SETTINGS_FROM_REGISTRY = 7,
 	PREVENT_ALTERING_LAYER_SETTINGS_FROM_REGISTRY = 7,
 	DISABLE_GPU_TIMEOUT                           = 8,
 	DISABLE_GPU_TIMEOUT                           = 8,
-	VIDEO_SUPPORT                                 = 12,
+	VIDEO_SUPPORT                                 = 11,
 }
 }
 
 
 PFN_CREATE_DEVICE :: #type proc "c" (a0: ^dxgi.IAdapter, a1: DRIVER_TYPE, a2: HMODULE, a3: u32, a4: ^FEATURE_LEVEL, a5: u32, a6: u32, a7: ^^IDevice, a8: ^FEATURE_LEVEL, a9: ^^IDeviceContext) -> HRESULT
 PFN_CREATE_DEVICE :: #type proc "c" (a0: ^dxgi.IAdapter, a1: DRIVER_TYPE, a2: HMODULE, a3: u32, a4: ^FEATURE_LEVEL, a5: u32, a6: u32, a7: ^^IDevice, a8: ^FEATURE_LEVEL, a9: ^^IDeviceContext) -> HRESULT

+ 3 - 3
vendor/microui/microui.odin

@@ -622,7 +622,7 @@ push_command :: proc(ctx: ^Context, $Type: typeid, extra_size := 0) -> ^Type {
 	return cmd
 	return cmd
 }
 }
 
 
-next_command :: proc(ctx: ^Context, pcmd: ^^Command) -> bool {
+next_command :: proc "contextless" (ctx: ^Context, pcmd: ^^Command) -> bool {
 	cmd := pcmd^
 	cmd := pcmd^
 	defer pcmd^ = cmd
 	defer pcmd^ = cmd
 	if cmd != nil { 
 	if cmd != nil { 
@@ -630,7 +630,7 @@ next_command :: proc(ctx: ^Context, pcmd: ^^Command) -> bool {
 	} else {
 	} else {
 		cmd = (^Command)(&ctx.command_list.items[0])
 		cmd = (^Command)(&ctx.command_list.items[0])
 	}
 	}
-	invalid_command :: #force_inline proc(ctx: ^Context) -> ^Command {
+	invalid_command :: #force_inline proc "contextless" (ctx: ^Context) -> ^Command {
 		return (^Command)(&ctx.command_list.items[ctx.command_list.idx])
 		return (^Command)(&ctx.command_list.items[ctx.command_list.idx])
 	}
 	}
 	for cmd != invalid_command(ctx) {
 	for cmd != invalid_command(ctx) {
@@ -643,7 +643,7 @@ next_command :: proc(ctx: ^Context, pcmd: ^^Command) -> bool {
 	return false
 	return false
 }
 }
 
 
-next_command_iterator :: proc(ctx: ^Context, pcm: ^^Command) -> (Command_Variant, bool) {
+next_command_iterator :: proc "contextless" (ctx: ^Context, pcm: ^^Command) -> (Command_Variant, bool) {
 	if next_command(ctx, pcm) {
 	if next_command(ctx, pcm) {
 		return pcm^.variant, true
 		return pcm^.variant, true
 	}
 	}

+ 2 - 2
vendor/raylib/raylib.odin

@@ -118,8 +118,8 @@ when ODIN_OS == .Windows {
 } else when ODIN_OS == .Darwin {
 } else when ODIN_OS == .Darwin {
 	foreign import lib {
 	foreign import lib {
 		"macos" +
 		"macos" +
-			"-arm64" when ODIN_ARCH == .arm64 else "" +
-			"/libraylib" + ".500.dylib" when RAYLIB_SHARED else ".a",
+			("-arm64" when ODIN_ARCH == .arm64 else "") +
+			"/libraylib" + (".500.dylib" when RAYLIB_SHARED else ".a"),
 		"system:Cocoa.framework",
 		"system:Cocoa.framework",
 		"system:OpenGL.framework",
 		"system:OpenGL.framework",
 		"system:IOKit.framework",
 		"system:IOKit.framework",

+ 1 - 1
vendor/sdl2/sdl_render.odin

@@ -76,7 +76,7 @@ foreign lib {
 	GetRenderer                  :: proc(window:   ^Window) -> ^Renderer ---
 	GetRenderer                  :: proc(window:   ^Window) -> ^Renderer ---
 	GetRendererInfo              :: proc(renderer: ^Renderer, info: ^RendererInfo) -> c.int ---
 	GetRendererInfo              :: proc(renderer: ^Renderer, info: ^RendererInfo) -> c.int ---
 	GetRendererOutputSize        :: proc(renderer: ^Renderer, w, h: ^c.int) -> c.int ---
 	GetRendererOutputSize        :: proc(renderer: ^Renderer, w, h: ^c.int) -> c.int ---
-	CreateTexture                :: proc(renderer: ^Renderer, format: u32, access: TextureAccess, w, h: c.int) -> ^Texture ---
+	CreateTexture                :: proc(renderer: ^Renderer, format: PixelFormatEnum, access: TextureAccess, w, h: c.int) -> ^Texture ---
 	CreateTextureFromSurface     :: proc(renderer: ^Renderer, surface: ^Surface) -> ^Texture ---
 	CreateTextureFromSurface     :: proc(renderer: ^Renderer, surface: ^Surface) -> ^Texture ---
 	QueryTexture                 :: proc(texture:  ^Texture, format: ^u32, access, w, h: ^c.int) -> c.int ---
 	QueryTexture                 :: proc(texture:  ^Texture, format: ^u32, access, w, h: ^c.int) -> c.int ---
 	SetTextureColorMod           :: proc(texture:  ^Texture, r, g, b: u8) -> c.int ---
 	SetTextureColorMod           :: proc(texture:  ^Texture, r, g, b: u8) -> c.int ---

+ 5 - 0
vendor/x11/xlib/xlib_const.odin

@@ -17,6 +17,11 @@ AllTemporary    :: 0
 CurrentTime     :: 0
 CurrentTime     :: 0
 NoSymbol        :: 0
 NoSymbol        :: 0
 
 
+PropModeReplace :: 0
+PropModePrepend :: 1
+PropModeAppend  :: 2
+
+XA_ATOM              :: Atom(4)
 XA_WM_CLASS          :: Atom(67)
 XA_WM_CLASS          :: Atom(67)
 XA_WM_CLIENT_MACHINE :: Atom(36)
 XA_WM_CLIENT_MACHINE :: Atom(36)
 XA_WM_COMMAND        :: Atom(34)
 XA_WM_COMMAND        :: Atom(34)

+ 24 - 6
vendor/x11/xlib/xlib_procs.odin

@@ -6,6 +6,16 @@ foreign xlib {
 	@(link_name="_Xdebug") _Xdebug: i32
 	@(link_name="_Xdebug") _Xdebug: i32
 }
 }
 
 
+foreign import xcursor "system:Xcursor"
+@(default_calling_convention="c", link_prefix="X")
+foreign xcursor {
+	cursorGetTheme         :: proc(display: ^Display) -> cstring ---
+	cursorGetDefaultSize   :: proc(display: ^Display) -> i32 ---
+	cursorLibraryLoadImage :: proc(name: cstring, theme: cstring, size: i32) -> rawptr ---
+	cursorImageLoadCursor  :: proc(display: ^Display, img: rawptr) -> Cursor ---
+	cursorImageDestroy     :: proc(img: rawptr) ---
+}
+
 /* ----  X11/Xlib.h ---------------------------------------------------------*/
 /* ----  X11/Xlib.h ---------------------------------------------------------*/
 
 
 @(default_calling_convention="c", link_prefix="X")
 @(default_calling_convention="c", link_prefix="X")
@@ -20,11 +30,9 @@ foreign xlib {
 	NoOp              :: proc(display: ^Display) ---
 	NoOp              :: proc(display: ^Display) ---
 	// Display macros (connection)
 	// Display macros (connection)
 	ConnectionNumber  :: proc(display: ^Display) -> i32 ---
 	ConnectionNumber  :: proc(display: ^Display) -> i32 ---
-	ExtendedMaxRequestSize ::
-	                      proc(display: ^Display) -> int ---
+	ExtendedMaxRequestSize :: proc(display: ^Display) -> int ---
 	MaxRequestSize    :: proc(display: ^Display) -> int ---
 	MaxRequestSize    :: proc(display: ^Display) -> int ---
-	LastKnownRequestProcessed ::
-	                      proc(display: ^Display) -> uint ---
+	LastKnownRequestProcessed :: proc(display: ^Display) -> uint ---
 	NextRequest       :: proc(display: ^Display) -> uint ---
 	NextRequest       :: proc(display: ^Display) -> uint ---
 	ProtocolVersion   :: proc(display: ^Display) -> i32 ---
 	ProtocolVersion   :: proc(display: ^Display) -> i32 ---
 	ProtocolRevision  :: proc(display: ^Display) -> i32 ---
 	ProtocolRevision  :: proc(display: ^Display) -> i32 ---
@@ -46,8 +54,7 @@ foreign xlib {
 	DefaultRootWindow :: proc(display: ^Display) -> Window ---
 	DefaultRootWindow :: proc(display: ^Display) -> Window ---
 	DefaultScreen     :: proc(display: ^Display) -> i32 ---
 	DefaultScreen     :: proc(display: ^Display) -> i32 ---
 	DefaultVisual     :: proc(display: ^Display, screen_no: i32) -> ^Visual ---
 	DefaultVisual     :: proc(display: ^Display, screen_no: i32) -> ^Visual ---
-	DefaultScreenOfDisplay ::
-	                      proc(display: ^Display) -> ^Screen ---
+	DefaultScreenOfDisplay :: proc(display: ^Display) -> ^Screen ---
 	// Display macros (other)
 	// Display macros (other)
 	RootWindow        :: proc(display: ^Display, screen_no: i32) -> Window ---
 	RootWindow        :: proc(display: ^Display, screen_no: i32) -> Window ---
 	ScreenCount       :: proc(display: ^Display) -> i32 ---
 	ScreenCount       :: proc(display: ^Display) -> i32 ---
@@ -1619,6 +1626,17 @@ foreign xlib {
 		) -> b32 ---
 		) -> b32 ---
 	DestroyImage :: proc(image: ^XImage) ---
 	DestroyImage :: proc(image: ^XImage) ---
 	ResourceManagerString :: proc(display: ^Display) -> cstring ---
 	ResourceManagerString :: proc(display: ^Display) -> cstring ---
+	utf8SetWMProperties :: proc(
+		display:      ^Display,
+		window:       Window,
+		window_name:  cstring,
+		icon_name:    cstring,
+		argv:         ^cstring,
+		argc:         i32,
+		normal_hints: ^XSizeHints,
+		wm_hints:     ^XWMHints,
+		class_hints:  ^XClassHint,
+	) ---
 }
 }
 
 
 @(default_calling_convention="c")
 @(default_calling_convention="c")