Преглед изворни кода

Merge branch 'master' into d3d11-annotations

gingerBill пре 8 месеци
родитељ
комит
6e49bbb668
100 измењених фајлова са 4493 додато и 3748 уклоњено
  1. 5 0
      .gitattributes
  2. 198 96
      .github/workflows/ci.yml
  3. 131 64
      .github/workflows/nightly.yml
  4. 10 5
      .gitignore
  5. 1 1
      LICENSE
  6. BIN
      LLVM-C.dll
  7. 7 1
      Makefile
  8. 2 2
      README.md
  9. 5 1
      base/builtin/builtin.odin
  10. 80 22
      base/intrinsics/intrinsics.odin
  11. 164 43
      base/runtime/core.odin
  12. 378 146
      base/runtime/core_builtin.odin
  13. 154 45
      base/runtime/core_builtin_soa.odin
  14. 15 0
      base/runtime/default_allocators_general.odin
  15. 8 14
      base/runtime/default_allocators_nil.odin
  16. 15 13
      base/runtime/default_temp_allocator_arena.odin
  17. 1 1
      base/runtime/default_temporary_allocator.odin
  18. 4 3
      base/runtime/docs.odin
  19. 0 0
      base/runtime/dynamic_array_internal.odin
  20. 112 22
      base/runtime/dynamic_map_internal.odin
  21. 13 8
      base/runtime/entry_unix.odin
  22. 0 0
      base/runtime/entry_unix_no_crt_amd64.asm
  23. 20 0
      base/runtime/entry_unix_no_crt_darwin_arm64.asm
  24. 0 0
      base/runtime/entry_unix_no_crt_i386.asm
  25. 10 0
      base/runtime/entry_unix_no_crt_riscv64.asm
  26. 39 0
      base/runtime/entry_wasm.odin
  27. 8 6
      base/runtime/entry_windows.odin
  28. 31 11
      base/runtime/error_checks.odin
  29. 119 0
      base/runtime/heap_allocator.odin
  30. 29 0
      base/runtime/heap_allocator_orca.odin
  31. 18 0
      base/runtime/heap_allocator_other.odin
  32. 38 0
      base/runtime/heap_allocator_unix.odin
  33. 39 0
      base/runtime/heap_allocator_windows.odin
  34. 145 128
      base/runtime/internal.odin
  35. 7 0
      base/runtime/os_specific.odin
  36. 26 0
      base/runtime/os_specific_bsd.odin
  37. 28 0
      base/runtime/os_specific_darwin.odin
  38. 8 0
      base/runtime/os_specific_freestanding.odin
  39. 21 0
      base/runtime/os_specific_haiku.odin
  40. 3 2
      base/runtime/os_specific_js.odin
  41. 26 0
      base/runtime/os_specific_linux.odin
  42. 43 0
      base/runtime/os_specific_orca.odin
  43. 55 0
      base/runtime/os_specific_wasi.odin
  44. 51 0
      base/runtime/os_specific_windows.odin
  45. 47 38
      base/runtime/print.odin
  46. 27 10
      base/runtime/procs.odin
  47. 2 2
      base/runtime/procs_darwin.odin
  48. 1 1
      base/runtime/procs_js.odin
  49. 97 0
      base/runtime/procs_wasm.odin
  50. 0 0
      base/runtime/procs_windows_amd64.asm
  51. 3 2
      base/runtime/procs_windows_amd64.odin
  52. 3 2
      base/runtime/procs_windows_i386.odin
  53. 127 0
      base/runtime/random_generator.odin
  54. 34 0
      base/runtime/thread_management.odin
  55. 4 4
      base/runtime/udivmod128.odin
  56. 871 0
      base/runtime/wasm_allocator.odin
  57. 19 0
      bin/RAD-LICENSE
  58. BIN
      bin/lld-link.exe
  59. BIN
      bin/llvm/windows/LLVM-C.lib
  60. BIN
      bin/llvm/windows/clang_rt.asan-x86_64.lib
  61. BIN
      bin/radlink.exe
  62. BIN
      bin/wasm-ld.exe
  63. 46 8
      build.bat
  64. 68 21
      build_odin.sh
  65. 19 0
      ci/build_linux_static.sh
  66. 0 51
      ci/create_nightly_json.py
  67. 0 34
      ci/delete_old_binaries.py
  68. 145 0
      ci/nightly.py
  69. 0 13
      ci/upload_create_nightly.sh
  70. 6 6
      core/bufio/reader.odin
  71. 1 1
      core/bufio/scanner.odin
  72. 4 1
      core/bufio/writer.odin
  73. 69 39
      core/bytes/buffer.odin
  74. 310 7
      core/bytes/bytes.odin
  75. 9 2
      core/bytes/reader.odin
  76. 11 1
      core/c/c.odin
  77. 0 25
      core/c/frontend/preprocessor/const_expr.odin
  78. 0 1510
      core/c/frontend/preprocessor/preprocess.odin
  79. 0 154
      core/c/frontend/preprocessor/unquote.odin
  80. 0 34
      core/c/frontend/tokenizer/doc.odin
  81. 0 68
      core/c/frontend/tokenizer/hide_set.odin
  82. 0 169
      core/c/frontend/tokenizer/token.odin
  83. 0 667
      core/c/frontend/tokenizer/tokenizer.odin
  84. 0 116
      core/c/frontend/tokenizer/unicode.odin
  85. 2 2
      core/c/libc/README.md
  86. 3 3
      core/c/libc/complex.odin
  87. 28 2
      core/c/libc/errno.odin
  88. 133 0
      core/c/libc/locale.odin
  89. 1 1
      core/c/libc/math.odin
  90. 12 7
      core/c/libc/setjmp.odin
  91. 1 14
      core/c/libc/signal.odin
  92. 4 10
      core/c/libc/stdarg.odin
  93. 2 2
      core/c/libc/stdatomic.odin
  94. 192 8
      core/c/libc/stdio.odin
  95. 27 4
      core/c/libc/stdlib.odin
  96. 3 2
      core/c/libc/string.odin
  97. 30 4
      core/c/libc/time.odin
  98. 2 0
      core/c/libc/types.odin
  99. 7 3
      core/c/libc/wctype.odin
  100. 56 66
      core/compress/common.odin

+ 5 - 0
.gitattributes

@@ -1 +1,6 @@
 *.odin linguist-language=Odin
 *.odin linguist-language=Odin
+* text=auto
+
+# These files must always have *nix line-endings
+Makefile text eol=lf
+*.sh text eol=lf

+ 198 - 96
.github/workflows/ci.yml

@@ -2,106 +2,162 @@ name: CI
 on: [push, pull_request, workflow_dispatch]
 on: [push, pull_request, workflow_dispatch]
 
 
 jobs:
 jobs:
-  build_linux:
+  build_netbsd:
+    name: NetBSD Build, Check, and Test
     runs-on: ubuntu-latest
     runs-on: ubuntu-latest
+    env:
+      PKGSRC_BRANCH: 2024Q3
     steps:
     steps:
-      - uses: actions/checkout@v1
-      - name: Download LLVM, botan
-        run: sudo apt-get install llvm-11 clang-11 libbotan-2-dev botan
-      - name: build odin
+    - uses: actions/checkout@v4
+    - name: Build, Check, and Test
+      timeout-minutes: 15
+      uses: vmactions/netbsd-vm@v1
+      with:
+        release: "10.0"
+        envs: PKGSRC_BRANCH
+        usesh: true
+        copyback: false
+        prepare: |
+          PKG_PATH="https://cdn.NetBSD.org/pub/pkgsrc/packages/NetBSD/amd64/$(uname -r | cut -d_ -f1)_${PKGSRC_BRANCH}/All" /usr/sbin/pkg_add pkgin
+          pkgin -y in gmake git bash python311 llvm clang
+          ln -s /usr/pkg/bin/python3.11 /usr/bin/python3
+        run: |
+          set -e -x
+          git config --global --add safe.directory $(pwd)
+          gmake release
+          ./odin version
+          ./odin report
+          gmake -C vendor/stb/src
+          gmake -C vendor/cgltf/src
+          gmake -C vendor/miniaudio/src
+          ./odin check examples/all -vet -strict-style -disallow-do -target:netbsd_amd64
+          ./odin check examples/all -vet -strict-style -disallow-do -target:netbsd_arm64
+          ./odin test tests/core/normal.odin -file -all-packages -vet -strict-style -disallow-do -define:ODIN_TEST_FANCY=false -define:ODIN_TEST_FAIL_ON_BAD_MEMORY=true
+          ./odin test tests/core/speed.odin -file -all-packages -vet -strict-style -disallow-do -o:speed -define:ODIN_TEST_FANCY=false -define:ODIN_TEST_FAIL_ON_BAD_MEMORY=true
+          ./odin test tests/vendor -all-packages -vet -strict-style -disallow-do -define:ODIN_TEST_FANCY=false -define:ODIN_TEST_FAIL_ON_BAD_MEMORY=true
+          (cd tests/issues; ./run.sh)
+          ./odin check tests/benchmark -vet -strict-style -no-entry-point
+
+  build_freebsd:
+    name: FreeBSD Build, Check, and Test
+    runs-on: ubuntu-latest
+    steps:
+    - uses: actions/checkout@v4
+    - name: Build, Check, and Test
+      timeout-minutes: 15
+      uses: vmactions/freebsd-vm@v1
+      with:
+        usesh: true
+        copyback: false
+        prepare: |
+          pkg install -y gmake git bash python3 libxml2 llvm17
+        run: |
+          # `set -e` is needed for test failures to register. https://github.com/vmactions/freebsd-vm/issues/72
+          set -e -x
+          git config --global --add safe.directory $(pwd)
+          gmake release
+          ./odin version
+          ./odin report
+          gmake -C vendor/stb/src
+          gmake -C vendor/cgltf/src
+          gmake -C vendor/miniaudio/src
+          ./odin check examples/all -vet -strict-style -disallow-do -target:freebsd_amd64
+          ./odin test tests/core/normal.odin -file -all-packages -vet -strict-style -disallow-do -define:ODIN_TEST_FANCY=false -define:ODIN_TEST_FAIL_ON_BAD_MEMORY=true
+          ./odin test tests/core/speed.odin -file -all-packages -vet -strict-style -disallow-do -o:speed -define:ODIN_TEST_FANCY=false -define:ODIN_TEST_FAIL_ON_BAD_MEMORY=true
+          ./odin test tests/vendor -all-packages -vet -strict-style -disallow-do -define:ODIN_TEST_FANCY=false -define:ODIN_TEST_FAIL_ON_BAD_MEMORY=true
+          (cd tests/issues; ./run.sh)
+          ./odin check tests/benchmark -vet -strict-style -no-entry-point
+  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:
+      - uses: actions/checkout@v4
+
+      - name: Download LLVM (Linux)
+        if: matrix.os == 'ubuntu-latest'
+        run: |
+          wget https://apt.llvm.org/llvm.sh
+          chmod +x llvm.sh
+          sudo ./llvm.sh 17
+          echo "/usr/lib/llvm-17/bin" >> $GITHUB_PATH
+
+      - name: Download LLVM (MacOS Intel)
+        if: matrix.os == 'macos-13'
+        run: |
+          brew install llvm@17 [email protected]
+          echo "/usr/local/opt/llvm@17/bin" >> $GITHUB_PATH
+
+      - name: Download LLVM (MacOS ARM)
+        if: matrix.os == 'macos-14'
+        run: |
+          brew install llvm@17 wasmtime [email protected]
+          echo "/opt/homebrew/opt/llvm@17/bin" >> $GITHUB_PATH
+
+      - name: Build Odin
         run: ./build_odin.sh release
         run: ./build_odin.sh release
       - name: Odin version
       - name: Odin version
         run: ./odin version
         run: ./odin version
-        timeout-minutes: 1
       - name: Odin report
       - name: Odin report
         run: ./odin report
         run: ./odin report
-        timeout-minutes: 1
+      - name: Compile needed Vendor
+        run: |
+          make -C vendor/stb/src
+          make -C vendor/cgltf/src
+          make -C vendor/miniaudio/src
       - name: Odin check
       - name: Odin check
         run: ./odin check examples/demo -vet
         run: ./odin check examples/demo -vet
-        timeout-minutes: 10
       - name: Odin run
       - name: Odin run
         run: ./odin run examples/demo
         run: ./odin run examples/demo
-        timeout-minutes: 10
       - name: Odin run -debug
       - name: Odin run -debug
         run: ./odin run examples/demo -debug
         run: ./odin run examples/demo -debug
-        timeout-minutes: 10
       - name: Odin check examples/all
       - name: Odin check examples/all
         run: ./odin check examples/all -strict-style
         run: ./odin check examples/all -strict-style
-        timeout-minutes: 10
-      - name: Core library tests
-        run: |
-          cd tests/core
-          make
-        timeout-minutes: 10
+      - name: Normal Core library tests
+        run: ./odin test tests/core/normal.odin -file -all-packages -vet -strict-style -disallow-do -define:ODIN_TEST_FANCY=false -define:ODIN_TEST_FAIL_ON_BAD_MEMORY=true
+      - name: Optimized Core library tests
+        run: ./odin test tests/core/speed.odin -o:speed -file -all-packages -vet -strict-style -disallow-do -define:ODIN_TEST_FANCY=false -define:ODIN_TEST_FAIL_ON_BAD_MEMORY=true
       - name: Vendor library tests
       - name: Vendor library tests
+        run: ./odin test tests/vendor -all-packages -vet -strict-style -disallow-do -define:ODIN_TEST_FANCY=false -define:ODIN_TEST_FAIL_ON_BAD_MEMORY=true
+      - name: Internals tests
+        run: ./odin test tests/internal -all-packages -vet -strict-style -disallow-do -define:ODIN_TEST_FANCY=false -define:ODIN_TEST_FAIL_ON_BAD_MEMORY=true
+      - 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
+          cd tests/issues
+          ./run.sh
+
+      - name: Check benchmarks
+        run: ./odin check tests/benchmark -vet -strict-style -no-entry-point
       - name: Odin check examples/all for Linux i386
       - name: Odin check examples/all for Linux i386
-        run: ./odin check examples/all -vet -strict-style -target:linux_i386
-        timeout-minutes: 10
+        run: ./odin check examples/all -vet -strict-style -disallow-do -target:linux_i386
+        if: matrix.os == 'ubuntu-latest'
+      - name: Odin check examples/all for Linux arm64
+        run: ./odin check examples/all -vet -strict-style -disallow-do -target:linux_arm64
+        if: matrix.os == 'ubuntu-latest'
       - 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
-        timeout-minutes: 10
+        run: ./odin check examples/all -vet -strict-style -disallow-do -target:freebsd_amd64
+        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
-        timeout-minutes: 10
-  build_macOS:
-    runs-on: macos-latest
-    steps:
-      - uses: actions/checkout@v1
-      - name: Download LLVM, botan and setup PATH
-        run: |
-          brew install llvm@13 botan
-          echo "/usr/local/opt/llvm@13/bin" >> $GITHUB_PATH
-          TMP_PATH=$(xcrun --show-sdk-path)/user/include
-          echo "CPATH=$TMP_PATH" >> $GITHUB_ENV
-      - name: build odin
-        run: ./build_odin.sh release
-      - name: Odin version
-        run: ./odin version
-        timeout-minutes: 1
-      - name: Odin report
-        run: ./odin report
-        timeout-minutes: 1
-      - name: Odin check
-        run: ./odin check examples/demo -vet
-        timeout-minutes: 10
-      - name: Odin run
-        run: ./odin run examples/demo
-        timeout-minutes: 10
-      - name: Odin run -debug
-        run: ./odin run examples/demo -debug
-        timeout-minutes: 10
-      - name: Odin check examples/all
-        run: ./odin check examples/all -strict-style
-        timeout-minutes: 10
-      - name: Core library tests
-        run: |
-          cd tests/core
-          make
-        timeout-minutes: 10
-      - name: Odin internals tests
+        run: ./odin check examples/all -vet -strict-style -disallow-do -target:openbsd_amd64
+        if: matrix.os == 'ubuntu-latest'
+
+      - name: Run demo on WASI WASM32
         run: |
         run: |
-          cd tests/internal
-          make
-        timeout-minutes: 10
-      - name: Odin check examples/all for Darwin arm64
-        run: ./odin check examples/all -vet -strict-style -target:darwin_arm64
-        timeout-minutes: 10
-      - name: Odin check examples/all for Linux arm64
-        run: ./odin check examples/all -vet -strict-style -target:linux_arm64
-        timeout-minutes: 10
+          ./odin build examples/demo -target:wasi_wasm32 -vet -strict-style -disallow-do -out:demo.wasm
+          wasmtime ./demo.wasm
+        if: matrix.os == 'macos-14'
+
   build_windows:
   build_windows:
+    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: |
@@ -109,72 +165,118 @@ 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
-        timeout-minutes: 10
+          odin run examples/demo -debug -vet -strict-style -disallow-do
       - name: Odin check examples/all
       - name: Odin check examples/all
         shell: cmd
         shell: cmd
         run: |
         run: |
           call "C:\Program Files\Microsoft Visual Studio\2022\Enterprise\VC\Auxiliary\Build\vcvars64.bat
           call "C:\Program Files\Microsoft Visual Studio\2022\Enterprise\VC\Auxiliary\Build\vcvars64.bat
-          odin check examples/all -strict-style
-        timeout-minutes: 10
+          odin check examples/all -vet -strict-style -disallow-do
       - 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 -vet -strict-style -disallow-do -define:ODIN_TEST_FANCY=false -define:ODIN_TEST_FAIL_ON_BAD_MEMORY=true
+      - name: Optimized core library tests
+        shell: cmd
+        run: |
+          call "C:\Program Files\Microsoft Visual Studio\2022\Enterprise\VC\Auxiliary\Build\vcvars64.bat
+          odin test tests/core/speed.odin -o:speed -file -all-packages -vet -strict-style -disallow-do -define:ODIN_TEST_FANCY=false -define:ODIN_TEST_FAIL_ON_BAD_MEMORY=true
       - 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
+          copy vendor\lua\5.4\windows\*.dll .
+          odin test tests/vendor -all-packages -vet -strict-style -disallow-do -define:ODIN_TEST_FANCY=false -define:ODIN_TEST_FAIL_ON_BAD_MEMORY=true
       - 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 -vet -strict-style -disallow-do -define:ODIN_TEST_FANCY=false -define:ODIN_TEST_FAIL_ON_BAD_MEMORY=true
+      - name: Check benchmarks
+        shell: cmd
+        run: |
+          call "C:\Program Files\Microsoft Visual Studio\2022\Enterprise\VC\Auxiliary\Build\vcvars64.bat
+          odin check tests/benchmark -vet -strict-style -no-entry-point
       - 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
+
+  build_linux_riscv64:
+    runs-on: ubuntu-latest
+    name: Linux riscv64 (emulated) Build, Check and Test
+    timeout-minutes: 15
+    steps:
+      - uses: actions/checkout@v4
+
+      - name: Download LLVM (Linux)
+        run: |
+          wget https://apt.llvm.org/llvm.sh
+          chmod +x llvm.sh
+          sudo ./llvm.sh 18
+          echo "/usr/lib/llvm-18/bin" >> $GITHUB_PATH
+
+      - name: Build Odin
+        run: ./build_odin.sh release
+
+      - name: Odin version
+        run: ./odin version
+
+      - name: Odin report
+        run: ./odin report
+
+      - name: Compile needed Vendor
+        run: |
+          make -C vendor/stb/src
+          make -C vendor/cgltf/src
+          make -C vendor/miniaudio/src
+
+      - name: Odin check
+        run: ./odin check examples/all -target:linux_riscv64 -vet -strict-style -disallow-do
+
+      - name: Install riscv64 toolchain and qemu
+        run: sudo apt-get install -y qemu-user qemu-user-static gcc-12-riscv64-linux-gnu libc6-riscv64-cross
+
+      - name: Odin run
+        run: ./odin run examples/demo -vet -strict-style -disallow-do -target:linux_riscv64 -extra-linker-flags:"-fuse-ld=/usr/bin/riscv64-linux-gnu-gcc-12 -static -Wl,-static" -no-rpath
+
+      - name: Odin run -debug
+        run: ./odin run examples/demo -debug -vet -strict-style -disallow-do -target:linux_riscv64 -extra-linker-flags:"-fuse-ld=/usr/bin/riscv64-linux-gnu-gcc-12 -static -Wl,-static" -no-rpath
+
+      - name: Normal Core library tests
+        run: ./odin test tests/core/normal.odin -file -all-packages -vet -strict-style -disallow-do -define:ODIN_TEST_FANCY=false -define:ODIN_TEST_FAIL_ON_BAD_MEMORY=true -target:linux_riscv64 -extra-linker-flags:"-fuse-ld=/usr/bin/riscv64-linux-gnu-gcc-12 -static -Wl,-static" -no-rpath
+
+      - name: Optimized Core library tests
+        run: ./odin test tests/core/speed.odin -o:speed -file -all-packages -vet -strict-style -disallow-do -define:ODIN_TEST_FANCY=false -define:ODIN_TEST_FAIL_ON_BAD_MEMORY=true -target:linux_riscv64 -extra-linker-flags:"-fuse-ld=/usr/bin/riscv64-linux-gnu-gcc-12 -static -Wl,-static" -no-rpath
+
+      - name: Internals tests
+        run: ./odin test tests/internal -all-packages -vet -strict-style -disallow-do -define:ODIN_TEST_FANCY=false -define:ODIN_TEST_FAIL_ON_BAD_MEMORY=true -target:linux_riscv64 -extra-linker-flags:"-fuse-ld=/usr/bin/riscv64-linux-gnu-gcc-12 -static -Wl,-static" -no-rpath

+ 131 - 64
.github/workflows/nightly.yml

@@ -7,10 +7,11 @@ on:
 
 
 jobs:
 jobs:
   build_windows:
   build_windows:
+    name: Windows Build
     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: |
@@ -29,102 +30,181 @@ jobs:
           cp LICENSE dist
           cp LICENSE dist
           cp LLVM-C.dll dist
           cp LLVM-C.dll dist
           cp -r shared dist
           cp -r shared dist
+          cp -r base dist
           cp -r core dist
           cp -r core dist
           cp -r vendor dist
           cp -r vendor dist
           cp -r bin dist
           cp -r bin dist
           cp -r examples dist
           cp -r examples dist
       - name: Upload artifact
       - name: Upload artifact
-        uses: actions/upload-artifact@v1
+        uses: actions/upload-artifact@v4
         with:
         with:
+          include-hidden-files: true
           name: windows_artifacts
           name: windows_artifacts
           path: dist
           path: dist
-  build_ubuntu:
+  build_linux:
+    name: Linux Build
     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
+      - uses: jirutka/setup-alpine@v1
+        with:
+          branch: v3.20
       - name: (Linux) Download LLVM
       - name: (Linux) Download LLVM
-        run: sudo apt-get install llvm-11 clang-11
+        run: |
+          apk add --no-cache \
+          musl-dev llvm18-dev clang18 git mold lz4 \
+          libxml2-static llvm18-static zlib-static zstd-static \
+          make
+        shell: alpine.sh --root {0}
       - name: build odin
       - name: build odin
-        run: make nightly
+        # NOTE: this build does slow compile times because of musl
+        run: ci/build_linux_static.sh
+        shell: alpine.sh {0}
       - name: Odin run
       - name: Odin run
         run: ./odin run examples/demo
         run: ./odin run examples/demo
       - name: Copy artifacts
       - name: Copy artifacts
         run: |
         run: |
-          mkdir dist
-          cp odin dist
-          cp LICENSE dist
-          cp libLLVM* dist
-          cp -r shared dist
-          cp -r core dist
-          cp -r vendor dist
-          cp -r examples dist
+          FILE="odin-linux-amd64-nightly+$(date -I)"
+          mkdir $FILE
+          cp odin $FILE
+          cp LICENSE $FILE
+          cp -r shared $FILE
+          cp -r base $FILE
+          cp -r core $FILE
+          cp -r vendor $FILE
+          cp -r examples $FILE
+          # Creating a tarball so executable permissions are retained, see https://github.com/actions/upload-artifact/issues/38
+          tar -czvf dist.tar.gz $FILE
+      - name: Odin run
+        run: |
+          FILE="odin-linux-amd64-nightly+$(date -I)"
+          $FILE/odin run examples/demo
       - name: Upload artifact
       - name: Upload artifact
-        uses: actions/upload-artifact@v1
+        uses: actions/upload-artifact@v4
         with:
         with:
-          name: ubuntu_artifacts
-          path: dist
+          name: linux_artifacts
+          path: dist.tar.gz
   build_macos:
   build_macos:
+    name: MacOS Build
     if: github.repository == 'odin-lang/Odin'
     if: github.repository == 'odin-lang/Odin'
-    runs-on: macOS-latest
+    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@13
-          echo "/usr/local/opt/llvm@13/bin" >> $GITHUB_PATH
-          TMP_PATH=$(xcrun --show-sdk-path)/user/include
-          echo "CPATH=$TMP_PATH" >> $GITHUB_ENV
+          brew install llvm@18 dylibbundler
+          echo "/usr/local/opt/llvm@18/bin" >> $GITHUB_PATH
       - name: build odin
       - name: build odin
-        run: make nightly
+        # These -L makes the linker prioritize system libraries over LLVM libraries, this is mainly to
+        # not link with libunwind bundled with LLVM but link with libunwind on the system.
+        run: CXXFLAGS="-L/usr/lib/system -L/usr/lib" make nightly
+      - name: Bundle
+        run: |
+          FILE="odin-macos-amd64-nightly+$(date -I)"
+          mkdir $FILE
+          cp odin $FILE
+          cp LICENSE $FILE
+          cp -r shared $FILE
+          cp -r base $FILE
+          cp -r core $FILE
+          cp -r vendor $FILE
+          cp -r examples $FILE
+          dylibbundler -b -x $FILE/odin -d $FILE/libs -od -p @executable_path/libs
+          # Creating a tarball so executable permissions are retained, see https://github.com/actions/upload-artifact/issues/38
+          tar -czvf dist.tar.gz $FILE
       - name: Odin run
       - name: Odin run
-        run: ./odin run examples/demo
-      - name: Copy artifacts
         run: |
         run: |
-          mkdir dist
-          cp odin dist
-          cp LICENSE dist
-          cp -r shared dist
-          cp -r core dist
-          cp -r vendor dist
-          cp -r examples dist
+          FILE="odin-macos-amd64-nightly+$(date -I)"
+          $FILE/odin run examples/demo
       - name: Upload artifact
       - name: Upload artifact
-        uses: actions/upload-artifact@v1
+        uses: actions/upload-artifact@v4
         with:
         with:
           name: macos_artifacts
           name: macos_artifacts
-          path: dist
+          path: dist.tar.gz
+  build_macos_arm:
+    name: MacOS ARM Build
+    if: github.repository == 'odin-lang/Odin'
+    runs-on: macos-14 # ARM machine
+    steps:
+      - uses: actions/checkout@v4
+      - name: Download LLVM and setup PATH
+        run: |
+          brew install llvm@18 dylibbundler
+          echo "/opt/homebrew/opt/llvm@18/bin" >> $GITHUB_PATH
+      - name: build odin
+        # These -L makes the linker prioritize system libraries over LLVM libraries, this is mainly to
+        # not link with libunwind bundled with LLVM but link with libunwind on the system.
+        run: CXXFLAGS="-L/usr/lib/system -L/usr/lib" make nightly
+      - name: Bundle
+        run: |
+          FILE="odin-macos-arm64-nightly+$(date -I)"
+          mkdir $FILE
+          cp odin $FILE
+          cp LICENSE $FILE
+          cp -r shared $FILE
+          cp -r base $FILE
+          cp -r core $FILE
+          cp -r vendor $FILE
+          cp -r examples $FILE
+          dylibbundler -b -x $FILE/odin -d $FILE/libs -od -p @executable_path/libs
+          # Creating a tarball so executable permissions are retained, see https://github.com/actions/upload-artifact/issues/38
+          tar -czvf dist.tar.gz $FILE
+      - name: Odin run
+        run: |
+          FILE="odin-macos-arm64-nightly+$(date -I)"
+          $FILE/odin run examples/demo
+      - name: Upload artifact
+        uses: actions/upload-artifact@v4
+        with:
+          name: macos_arm_artifacts
+          path: dist.tar.gz
   upload_b2:
   upload_b2:
     runs-on: [ubuntu-latest]
     runs-on: [ubuntu-latest]
-    needs: [build_windows, build_macos, build_ubuntu]
+    needs: [build_windows, build_macos, build_macos_arm, build_linux]
     steps:
     steps:
-      - uses: actions/checkout@v1
-      - uses: actions/setup-python@v2
+      - uses: actions/checkout@v4
+      - uses: actions/setup-python@v5
         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)"
 
 
       - name: Download Windows artifacts
       - name: Download Windows artifacts
-        uses: actions/download-artifact@v1
+
+        uses: actions/[email protected]
         with:
         with:
           name: windows_artifacts
           name: windows_artifacts
+          path: windows_artifacts
 
 
       - name: Download Ubuntu artifacts
       - name: Download Ubuntu artifacts
-        uses: actions/download-artifact@v1
+        uses: actions/download-artifact@v4.1.7
         with:
         with:
-          name: ubuntu_artifacts
+          name: linux_artifacts
+          path: linux_artifacts
 
 
       - name: Download macOS artifacts
       - name: Download macOS artifacts
-        uses: actions/download-artifact@v1
+        uses: actions/download-artifact@v4.1.7
         with:
         with:
           name: macos_artifacts
           name: macos_artifacts
+          path: macos_artifacts
+
+      - name: Download macOS arm artifacts
+        uses: actions/[email protected]
+        with:
+          name: macos_arm_artifacts
+          path: macos_arm_artifacts
+
+      - name: Debug
+        run: |
+          tree -L 2
 
 
       - name: Create archives and upload
       - name: Create archives and upload
         shell: bash
         shell: bash
@@ -134,23 +214,10 @@ jobs:
           BUCKET: ${{ secrets.B2_BUCKET }}
           BUCKET: ${{ secrets.B2_BUCKET }}
           DAYS_TO_KEEP: ${{ secrets.B2_DAYS_TO_KEEP }}
           DAYS_TO_KEEP: ${{ secrets.B2_DAYS_TO_KEEP }}
         run: |
         run: |
-          echo Authorizing B2 account
-          b2 authorize-account "$APPID" "$APPKEY"
-
-          echo Uploading artifcates to B2
-          chmod +x ./ci/upload_create_nightly.sh
-          ./ci/upload_create_nightly.sh "$BUCKET" windows-amd64 windows_artifacts/
-          ./ci/upload_create_nightly.sh "$BUCKET" ubuntu-amd64 ubuntu_artifacts/
-          ./ci/upload_create_nightly.sh "$BUCKET" macos-amd64 macos_artifacts/
-
-          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
+          file linux_artifacts/dist.tar.gz
+          python3 ci/nightly.py artifact windows-amd64 windows_artifacts/
+          python3 ci/nightly.py artifact linux-amd64 linux_artifacts/dist.tar.gz
+          python3 ci/nightly.py artifact macos-amd64 macos_artifacts/dist.tar.gz
+          python3 ci/nightly.py artifact macos-arm64 macos_arm_artifacts/dist.tar.gz
+          python3 ci/nightly.py prune
+          python3 ci/nightly.py json

+ 10 - 5
.gitignore

@@ -17,15 +17,12 @@
 [Rr]eleases/
 [Rr]eleases/
 x64/
 x64/
 x86/
 x86/
+!/core/simd/x86
 bld/
 bld/
 [Bb]in/
 [Bb]in/
 [Oo]bj/
 [Oo]bj/
 [Ll]og/
 [Ll]og/
 ![Cc]ore/[Ll]og/
 ![Cc]ore/[Ll]og/
-tests/documentation/verify/
-tests/documentation/all.odin-doc
-tests/internal/test_map
-tests/internal/test_rtti
 # Visual Studio 2015 cache/options directory
 # Visual Studio 2015 cache/options directory
 .vs/
 .vs/
 # Visual Studio Code options directory
 # Visual Studio Code options directory
@@ -269,11 +266,14 @@ bin/
 *.exe
 *.exe
 *.obj
 *.obj
 *.pdb
 *.pdb
+*.res
+desktop.ini
+Thumbs.db
 
 
 # - Linux/MacOS
 # - Linux/MacOS
 odin
 odin
 !odin/
 !odin/
-odin.dSYM
+**/*.dSYM
 *.bin
 *.bin
 demo.bin
 demo.bin
 libLLVM*.so*
 libLLVM*.so*
@@ -290,3 +290,8 @@ shared/
 examples/bug/
 examples/bug/
 build.sh
 build.sh
 !core/debug/
 !core/debug/
+
+# RAD debugger project file
+*.raddbg
+
+misc/featuregen/featuregen

+ 1 - 1
LICENSE

@@ -1,4 +1,4 @@
-Copyright (c) 2016-2022 Ginger Bill. All rights reserved.
+Copyright (c) 2016-2024 Ginger Bill. All rights reserved.
 
 
 Redistribution and use in source and binary forms, with or without
 Redistribution and use in source and binary forms, with or without
 modification, are permitted provided that the following conditions are met:
 modification, are permitted provided that the following conditions are met:


+ 7 - 1
Makefile

@@ -1,4 +1,4 @@
-all: debug
+all: default
 
 
 demo:
 demo:
 	./odin run examples/demo/demo.odin -file
 	./odin run examples/demo/demo.odin -file
@@ -6,12 +6,18 @@ demo:
 report:
 report:
 	./odin report
 	./odin report
 
 
+default:
+	PROGRAM=make ./build_odin.sh # debug
+
 debug:
 debug:
 	./build_odin.sh debug
 	./build_odin.sh debug
 
 
 release:
 release:
 	./build_odin.sh release
 	./build_odin.sh release
 
 
+release-native:
+	./build_odin.sh release-native
+
 release_native:
 release_native:
 	./build_odin.sh release-native
 	./build_odin.sh release-native
 
 

+ 2 - 2
README.md

@@ -76,9 +76,9 @@ Answers to common questions about Odin.
 
 
 Documentation for all the official packages part of the [core](https://pkg.odin-lang.org/core/) and [vendor](https://pkg.odin-lang.org/vendor/) library collections.
 Documentation for all the official packages part of the [core](https://pkg.odin-lang.org/core/) and [vendor](https://pkg.odin-lang.org/vendor/) library collections.
 
 
-#### [The Odin Wiki](https://github.com/odin-lang/Odin/wiki)
+#### [Odin Documentation](https://odin-lang.org/docs/)
 
 
-A wiki maintained by the Odin community.
+Documentation for the Odin language itself.
 
 
 #### [Odin Discord](https://discord.gg/sVBPHEv)
 #### [Odin Discord](https://discord.gg/sVBPHEv)
 
 

+ 5 - 1
core/builtin/builtin.odin → base/builtin/builtin.odin

@@ -1,6 +1,8 @@
 // This is purely for documentation
 // This is purely for documentation
 package builtin
 package builtin
 
 
+import "base:runtime"
+
 nil   :: nil
 nil   :: nil
 false :: 0!=0
 false :: 0!=0
 true  :: 0==0
 true  :: 0==0
@@ -110,7 +112,7 @@ typeid_of    :: proc($T: typeid) -> typeid ---
 swizzle :: proc(x: [N]T, indices: ..int) -> [len(indices)]T ---
 swizzle :: proc(x: [N]T, indices: ..int) -> [len(indices)]T ---
 
 
 complex    :: proc(real, imag: Float) -> Complex_Type ---
 complex    :: proc(real, imag: Float) -> Complex_Type ---
-quaternion :: proc(real, imag, jmag, kmag: Float) -> Quaternion_Type ---
+quaternion :: proc(imag, jmag, kmag, real: Float) -> Quaternion_Type --- // fields must be named
 real       :: proc(value: Complex_Or_Quaternion) -> Float ---
 real       :: proc(value: Complex_Or_Quaternion) -> Float ---
 imag       :: proc(value: Complex_Or_Quaternion) -> Float ---
 imag       :: proc(value: Complex_Or_Quaternion) -> Float ---
 jmag       :: proc(value: Quaternion) -> Float ---
 jmag       :: proc(value: Quaternion) -> Float ---
@@ -126,3 +128,5 @@ clamp :: proc(value, minimum, maximum: T) -> T ---
 
 
 soa_zip :: proc(slices: ...) -> #soa[]Struct ---
 soa_zip :: proc(slices: ...) -> #soa[]Struct ---
 soa_unzip :: proc(value: $S/#soa[]$E) -> (slices: ...) ---
 soa_unzip :: proc(value: $S/#soa[]$E) -> (slices: ...) ---
+
+unreachable :: proc() -> ! ---

+ 80 - 22
core/intrinsics/intrinsics.odin → base/intrinsics/intrinsics.odin

@@ -1,10 +1,18 @@
 // This is purely for documentation
 // This is purely for documentation
-//+build ignore
+#+build ignore
 package intrinsics
 package intrinsics
 
 
+import "base:runtime"
+
 // Package-Related
 // Package-Related
 is_package_imported :: proc(package_name: string) -> bool ---
 is_package_imported :: proc(package_name: string) -> bool ---
 
 
+// Matrix Related Procedures
+transpose        :: proc(m: $T/matrix[$R, $C]$E)    -> matrix[C, R]E ---
+outer_product    :: proc(a: $A/[$X]$E, b: $B/[$Y]E) -> matrix[X, Y]E ---
+hadamard_product :: proc(a, b: $T/matrix[$R, $C]$E) -> T ---
+matrix_flatten   :: proc(m: $T/matrix[$R, $C]$E)    -> [R*C]E ---
+
 // Types
 // Types
 soa_struct :: proc($N: int, $T: typeid) -> type/#soa[N]T
 soa_struct :: proc($N: int, $T: typeid) -> type/#soa[N]T
 
 
@@ -32,9 +40,12 @@ count_leading_zeros  :: proc(x: $T) -> T where type_is_integer(T) || type_is_sim
 reverse_bits         :: proc(x: $T) -> T where type_is_integer(T) || type_is_simd_vector(T) ---
 reverse_bits         :: proc(x: $T) -> T where type_is_integer(T) || type_is_simd_vector(T) ---
 byte_swap            :: proc(x: $T) -> T where type_is_integer(T) || type_is_float(T) ---
 byte_swap            :: proc(x: $T) -> T where type_is_integer(T) || type_is_float(T) ---
 
 
-overflow_add :: proc(lhs, rhs: $T) -> (T, bool) #optional_ok ---
-overflow_sub :: proc(lhs, rhs: $T) -> (T, bool) #optional_ok ---
-overflow_mul :: proc(lhs, rhs: $T) -> (T, bool) #optional_ok ---
+overflow_add :: proc(lhs, rhs: $T) -> (T, bool) where type_is_integer(T) #optional_ok ---
+overflow_sub :: proc(lhs, rhs: $T) -> (T, bool) where type_is_integer(T) #optional_ok ---
+overflow_mul :: proc(lhs, rhs: $T) -> (T, bool) where type_is_integer(T) #optional_ok ---
+
+saturating_add :: proc(lhs, rhs: $T) -> T where type_is_integer(T) ---
+saturating_sub :: proc(lhs, rhs: $T) -> T where type_is_integer(T) ---
 
 
 sqrt :: proc(x: $T) -> T where type_is_float(T) || (type_is_simd_vector(T) && type_is_float(type_elem_type(T))) ---
 sqrt :: proc(x: $T) -> T where type_is_float(T) || (type_is_simd_vector(T) && type_is_float(type_elem_type(T))) ---
 
 
@@ -63,10 +74,12 @@ prefetch_write_instruction :: proc(address: rawptr, #const locality: i32 /* 0..=
 prefetch_write_data        :: proc(address: rawptr, #const locality: i32 /* 0..=3 */) ---
 prefetch_write_data        :: proc(address: rawptr, #const locality: i32 /* 0..=3 */) ---
 
 
 // Compiler Hints
 // Compiler Hints
-expect :: proc(val, expected_val: T) -> T ---
+expect :: proc(val, expected_val: $T) -> T ---
 
 
 // Linux and Darwin Only
 // Linux and Darwin Only
 syscall :: proc(id: uintptr, args: ..uintptr) -> uintptr ---
 syscall :: proc(id: uintptr, args: ..uintptr) -> uintptr ---
+// FreeBSD, NetBSD, et cetera
+syscall_bsd :: proc(id: uintptr, args: ..uintptr) -> (uintptr, bool) ---
 
 
 
 
 // Atomics
 // Atomics
@@ -161,10 +174,23 @@ type_is_matrix           :: proc($T: typeid) -> bool ---
 
 
 type_has_nil :: proc($T: typeid) -> bool ---
 type_has_nil :: proc($T: typeid) -> bool ---
 
 
+type_is_matrix_row_major    :: proc($T: typeid) -> bool where type_is_matrix(T) ---
+type_is_matrix_column_major :: proc($T: typeid) -> bool where type_is_matrix(T) ---
+
 type_is_specialization_of :: proc($T, $S: typeid) -> bool ---
 type_is_specialization_of :: proc($T, $S: typeid) -> bool ---
-type_is_variant_of :: proc($U, $V: typeid) -> bool where type_is_union(U) ---
 
 
-type_has_field :: proc($T: typeid, $name: string) -> bool ---
+type_is_variant_of        :: proc($U, $V: typeid)          -> bool    where type_is_union(U) ---
+type_union_tag_type       :: proc($T: typeid)              -> typeid  where type_is_union(T) ---
+type_union_tag_offset     :: proc($T: typeid)              -> uintptr where type_is_union(T) ---
+type_union_base_tag_value :: proc($T: typeid)              -> int     where type_is_union(U) ---
+type_union_variant_count  :: proc($T: typeid)              -> int     where type_is_union(T) ---
+type_variant_type_of      :: proc($T: typeid, $index: int) -> typeid  where type_is_union(T) ---
+type_variant_index_of     :: proc($U, $V: typeid)          -> int     where type_is_union(U) ---
+
+type_bit_set_elem_type       :: proc($T: typeid) -> typeid where type_is_bit_set(T) ---
+type_bit_set_underlying_type :: proc($T: typeid) -> typeid where type_is_bit_set(T) ---
+
+type_has_field  :: proc($T: typeid, $name: string) -> bool ---
 type_field_type :: proc($T: typeid, $name: string) -> typeid ---
 type_field_type :: proc($T: typeid, $name: string) -> typeid ---
 
 
 type_proc_parameter_count :: proc($T: typeid) -> int where type_is_proc(T) ---
 type_proc_parameter_count :: proc($T: typeid) -> int where type_is_proc(T) ---
@@ -173,7 +199,8 @@ type_proc_return_count    :: proc($T: typeid) -> int where type_is_proc(T) ---
 type_proc_parameter_type  :: proc($T: typeid, index: int) -> typeid where type_is_proc(T) ---
 type_proc_parameter_type  :: proc($T: typeid, index: int) -> typeid where type_is_proc(T) ---
 type_proc_return_type     :: proc($T: typeid, index: int) -> typeid where type_is_proc(T) ---
 type_proc_return_type     :: proc($T: typeid, index: int) -> typeid where type_is_proc(T) ---
 
 
-type_struct_field_count :: proc($T: typeid) -> int where type_is_struct(T) ---
+type_struct_field_count          :: proc($T: typeid) -> int  where type_is_struct(T) ---
+type_struct_has_implicit_padding :: proc($T: typeid) -> bool where type_is_struct(T) ---
 
 
 type_polymorphic_record_parameter_count :: proc($T: typeid) -> typeid ---
 type_polymorphic_record_parameter_count :: proc($T: typeid) -> typeid ---
 type_polymorphic_record_parameter_value :: proc($T: typeid, index: int) -> $V ---
 type_polymorphic_record_parameter_value :: proc($T: typeid, index: int) -> $V ---
@@ -194,14 +221,21 @@ type_map_cell_info :: proc($T: typeid)           -> ^runtime.Map_Cell_Info ---
 type_convert_variants_to_pointers :: proc($T: typeid) -> typeid where type_is_union(T) ---
 type_convert_variants_to_pointers :: proc($T: typeid) -> typeid where type_is_union(T) ---
 type_merge :: proc($U, $V: typeid) -> typeid where type_is_union(U), type_is_union(V) ---
 type_merge :: proc($U, $V: typeid) -> typeid where type_is_union(U), type_is_union(V) ---
 
 
+type_has_shared_fields :: proc($U, $V: typeid) -> bool where type_is_struct(U), type_is_struct(V) ---
+
 constant_utf16_cstring :: proc($literal: string) -> [^]u16 ---
 constant_utf16_cstring :: proc($literal: string) -> [^]u16 ---
 
 
+constant_log2 :: proc($v: $T) -> T where type_is_integer(T) ---
+
 // SIMD related
 // SIMD related
 simd_add  :: proc(a, b: #simd[N]T) -> #simd[N]T ---
 simd_add  :: proc(a, b: #simd[N]T) -> #simd[N]T ---
 simd_sub  :: proc(a, b: #simd[N]T) -> #simd[N]T ---
 simd_sub  :: proc(a, b: #simd[N]T) -> #simd[N]T ---
 simd_mul  :: proc(a, b: #simd[N]T) -> #simd[N]T ---
 simd_mul  :: proc(a, b: #simd[N]T) -> #simd[N]T ---
 simd_div  :: proc(a, b: #simd[N]T) -> #simd[N]T where type_is_float(T) ---
 simd_div  :: proc(a, b: #simd[N]T) -> #simd[N]T where type_is_float(T) ---
 
 
+simd_saturating_add  :: proc(a, b: #simd[N]T) -> #simd[N]T where type_is_integer(T) ---
+simd_saturating_sub  :: proc(a, b: #simd[N]T) -> #simd[N]T where type_is_integer(T) ---
+
 // Keeps Odin's Behaviour
 // Keeps Odin's Behaviour
 // (x << y) if y <= mask else 0
 // (x << y) if y <= mask else 0
 simd_shl :: proc(a: #simd[N]T, b: #simd[N]Unsigned_Integer) -> #simd[N]T ---
 simd_shl :: proc(a: #simd[N]T, b: #simd[N]Unsigned_Integer) -> #simd[N]T ---
@@ -212,9 +246,6 @@ simd_shr :: proc(a: #simd[N]T, b: #simd[N]Unsigned_Integer) -> #simd[N]T ---
 simd_shl_masked :: proc(a: #simd[N]T, b: #simd[N]Unsigned_Integer) -> #simd[N]T ---
 simd_shl_masked :: proc(a: #simd[N]T, b: #simd[N]Unsigned_Integer) -> #simd[N]T ---
 simd_shr_masked :: proc(a: #simd[N]T, b: #simd[N]Unsigned_Integer) -> #simd[N]T ---
 simd_shr_masked :: proc(a: #simd[N]T, b: #simd[N]Unsigned_Integer) -> #simd[N]T ---
 
 
-simd_add_sat :: proc(a, b: #simd[N]T) -> #simd[N]T ---
-simd_sub_sat :: proc(a, b: #simd[N]T) -> #simd[N]T ---
-
 simd_bit_and     :: proc(a, b: #simd[N]T) -> #simd[N]T ---
 simd_bit_and     :: proc(a, b: #simd[N]T) -> #simd[N]T ---
 simd_bit_or      :: proc(a, b: #simd[N]T) -> #simd[N]T ---
 simd_bit_or      :: proc(a, b: #simd[N]T) -> #simd[N]T ---
 simd_bit_xor     :: proc(a, b: #simd[N]T) -> #simd[N]T ---
 simd_bit_xor     :: proc(a, b: #simd[N]T) -> #simd[N]T ---
@@ -243,13 +274,28 @@ simd_lanes_ge :: proc(a, b: #simd[N]T) -> #simd[N]Integer ---
 simd_extract :: proc(a: #simd[N]T, idx: uint) -> T ---
 simd_extract :: proc(a: #simd[N]T, idx: uint) -> T ---
 simd_replace :: proc(a: #simd[N]T, idx: uint, elem: T) -> #simd[N]T ---
 simd_replace :: proc(a: #simd[N]T, idx: uint, elem: T) -> #simd[N]T ---
 
 
-simd_reduce_add_ordered :: proc(a: #simd[N]T) -> T ---
-simd_reduce_mul_ordered :: proc(a: #simd[N]T) -> T ---
-simd_reduce_min         :: proc(a: #simd[N]T) -> T ---
-simd_reduce_max         :: proc(a: #simd[N]T) -> T ---
-simd_reduce_and         :: proc(a: #simd[N]T) -> T ---
-simd_reduce_or          :: proc(a: #simd[N]T) -> T ---
-simd_reduce_xor         :: proc(a: #simd[N]T) -> T ---
+simd_reduce_add_ordered :: proc(a: #simd[N]T) -> T where type_is_integer(T) || type_is_float(T)---
+simd_reduce_mul_ordered :: proc(a: #simd[N]T) -> T where type_is_integer(T) || type_is_float(T)---
+simd_reduce_min         :: proc(a: #simd[N]T) -> T where type_is_integer(T) || type_is_float(T)---
+simd_reduce_max         :: proc(a: #simd[N]T) -> T where type_is_integer(T) || type_is_float(T)---
+simd_reduce_and         :: proc(a: #simd[N]T) -> T where type_is_integer(T) || type_is_float(T)---
+simd_reduce_or          :: proc(a: #simd[N]T) -> T where type_is_integer(T) || type_is_float(T)---
+simd_reduce_xor         :: proc(a: #simd[N]T) -> T where type_is_integer(T) || type_is_float(T)---
+
+simd_reduce_any         :: proc(a: #simd[N]T) -> T where type_is_boolean(T) ---
+simd_reduce_all         :: proc(a: #simd[N]T) -> T where type_is_boolean(T) ---
+
+
+simd_gather       :: proc(ptr: #simd[N]rawptr, val: #simd[N]T, mask: #simd[N]U) -> #simd[N]T where type_is_integer(U) || type_is_boolean(U) ---
+simd_scatter      :: proc(ptr: #simd[N]rawptr, val: #simd[N]T, mask: #simd[N]U)              where type_is_integer(U) || type_is_boolean(U) ---
+
+simd_masked_load  :: proc(ptr: rawptr, val: #simd[N]T, mask: #simd[N]U) -> #simd[N]T where type_is_integer(U) || type_is_boolean(U) ---
+simd_masked_store :: proc(ptr: rawptr, val: #simd[N]T, mask: #simd[N]U)              where type_is_integer(U) || type_is_boolean(U) ---
+
+simd_masked_expand_load    :: proc(ptr: rawptr, val: #simd[N]T, mask: #simd[N]U) -> #simd[N]T where type_is_integer(U) || type_is_boolean(U) ---
+simd_masked_compress_store :: proc(ptr: rawptr, val: #simd[N]T, mask: #simd[N]U)              where type_is_integer(U) || type_is_boolean(U) ---
+
+
 
 
 simd_shuffle :: proc(a, b: #simd[N]T, indices: ..int) -> #simd[len(indices)]T ---
 simd_shuffle :: proc(a, b: #simd[N]T, indices: ..int) -> #simd[len(indices)]T ---
 simd_select  :: proc(cond: #simd[N]boolean_or_integer, true, false: #simd[N]T) -> #simd[N]T ---
 simd_select  :: proc(cond: #simd[N]boolean_or_integer, true, false: #simd[N]T) -> #simd[N]T ---
@@ -263,12 +309,22 @@ simd_nearest :: proc(a: #simd[N]any_float) -> #simd[N]any_float ---
 
 
 simd_to_bits :: proc(v: #simd[N]T) -> #simd[N]Integer where size_of(T) == size_of(Integer), type_is_unsigned(Integer) ---
 simd_to_bits :: proc(v: #simd[N]T) -> #simd[N]Integer where size_of(T) == size_of(Integer), type_is_unsigned(Integer) ---
 
 
-// equivalent a swizzle with descending indices, e.g. reserve(a, 3, 2, 1, 0)
-simd_reverse :: proc(a: #simd[N]T) -> #simd[N]T ---
+// equivalent to a swizzle with descending indices, e.g. reserve(a, 3, 2, 1, 0)
+simd_lanes_reverse :: proc(a: #simd[N]T) -> #simd[N]T ---
+
+simd_lanes_rotate_left  :: proc(a: #simd[N]T, $offset: int) -> #simd[N]T ---
+simd_lanes_rotate_right :: proc(a: #simd[N]T, $offset: int) -> #simd[N]T ---
+
+// Checks if the current target supports the given target features.
+//
+// Takes a constant comma-seperated string (eg: "sha512,sse4.1"), or a procedure type which has either
+// `@(require_target_feature)` or `@(enable_target_feature)` as its input and returns a boolean indicating
+// if all listed features are supported.
+has_target_feature :: proc($test: $T) -> bool where type_is_string(T) || type_is_proc(T) ---
 
 
-simd_rotate_left  :: proc(a: #simd[N]T, $offset: int) -> #simd[N]T ---
-simd_rotate_right :: proc(a: #simd[N]T, $offset: int) -> #simd[N]T ---
 
 
+// Returns the value of the procedure where `x` must be a call expression
+procedure_of :: proc(x: $T) -> T where type_is_proc(T) ---
 
 
 // WASM targets only
 // WASM targets only
 wasm_memory_grow :: proc(index, delta: uintptr) -> int ---
 wasm_memory_grow :: proc(index, delta: uintptr) -> int ---
@@ -280,7 +336,9 @@ wasm_memory_size :: proc(index: uintptr)        -> int ---
 // 0 - indicates that the thread blocked and then was woken up
 // 0 - indicates that the thread blocked and then was woken up
 // 1 - the loaded value from `ptr` did not match `expected`, the thread did not block
 // 1 - the loaded value from `ptr` did not match `expected`, the thread did not block
 // 2 - the thread blocked, but the timeout
 // 2 - the thread blocked, but the timeout
+@(require_target_feature="atomics")
 wasm_memory_atomic_wait32   :: proc(ptr: ^u32, expected: u32, timeout_ns: i64) -> u32 ---
 wasm_memory_atomic_wait32   :: proc(ptr: ^u32, expected: u32, timeout_ns: i64) -> u32 ---
+@(require_target_feature="atomics")
 wasm_memory_atomic_notify32 :: proc(ptr: ^u32, waiters: u32) -> (waiters_woken_up: u32) ---
 wasm_memory_atomic_notify32 :: proc(ptr: ^u32, waiters: u32) -> (waiters_woken_up: u32) ---
 
 
 // x86 Targets (i386, amd64)
 // x86 Targets (i386, amd64)

+ 164 - 43
core/runtime/core.odin → base/runtime/core.odin

@@ -18,9 +18,10 @@
 // This could change at a later date if the all these data structures are
 // This could change at a later date if the all these data structures are
 // implemented within the compiler rather than in this "preload" file
 // implemented within the compiler rather than in this "preload" file
 //
 //
+#+no-instrumentation
 package runtime
 package runtime
 
 
-import "core:intrinsics"
+import "base:intrinsics"
 
 
 // NOTE(bill): This must match the compiler's
 // NOTE(bill): This must match the compiler's
 Calling_Convention :: enum u8 {
 Calling_Convention :: enum u8 {
@@ -65,7 +66,7 @@ Type_Info_Named :: struct {
 	name: string,
 	name: string,
 	base: ^Type_Info,
 	base: ^Type_Info,
 	pkg:  string,
 	pkg:  string,
-	loc:  Source_Code_Location,
+	loc:  ^Source_Code_Location,
 }
 }
 Type_Info_Integer    :: struct {signed: bool, endianness: Platform_Endianness}
 Type_Info_Integer    :: struct {signed: bool, endianness: Platform_Endianness}
 Type_Info_Rune       :: struct {}
 Type_Info_Rune       :: struct {}
@@ -111,23 +112,32 @@ Type_Info_Parameters :: struct { // Only used for procedures parameters and resu
 }
 }
 Type_Info_Tuple :: Type_Info_Parameters // Will be removed eventually
 Type_Info_Tuple :: Type_Info_Parameters // Will be removed eventually
 
 
+Type_Info_Struct_Flags :: distinct bit_set[Type_Info_Struct_Flag; u8]
+Type_Info_Struct_Flag :: enum u8 {
+	packed    = 0,
+	raw_union = 1,
+	no_copy   = 2,
+	align     = 3,
+}
+
 Type_Info_Struct :: struct {
 Type_Info_Struct :: struct {
-	types:        []^Type_Info,
-	names:        []string,
-	offsets:      []uintptr,
-	usings:       []bool,
-	tags:         []string,
-	is_packed:    bool,
-	is_raw_union: bool,
-	is_no_copy:   bool,
-	custom_align: bool,
+	// Slice these with `field_count`
+	types:   [^]^Type_Info `fmt:"v,field_count"`,
+	names:   [^]string     `fmt:"v,field_count"`,
+	offsets: [^]uintptr    `fmt:"v,field_count"`,
+	usings:  [^]bool       `fmt:"v,field_count"`,
+	tags:    [^]string     `fmt:"v,field_count"`,
 
 
-	equal: Equal_Proc, // set only when the struct has .Comparable set but does not have .Simple_Compare set
+	field_count: i32,
+
+	flags: Type_Info_Struct_Flags,
 
 
 	// These are only set iff this structure is an SOA structure
 	// These are only set iff this structure is an SOA structure
 	soa_kind:      Type_Info_Struct_Soa_Kind,
 	soa_kind:      Type_Info_Struct_Soa_Kind,
+	soa_len:       i32,
 	soa_base_type: ^Type_Info,
 	soa_base_type: ^Type_Info,
-	soa_len:       int,
+
+	equal: Equal_Proc, // set only when the struct has .Comparable set but does not have .Simple_Compare set
 }
 }
 Type_Info_Union :: struct {
 Type_Info_Union :: struct {
 	variants:     []^Type_Info,
 	variants:     []^Type_Info,
@@ -141,9 +151,9 @@ Type_Info_Union :: struct {
 	shared_nil:   bool,
 	shared_nil:   bool,
 }
 }
 Type_Info_Enum :: struct {
 Type_Info_Enum :: struct {
-	base:      ^Type_Info,
-	names:     []string,
-	values:    []Type_Info_Enum_Value,
+	base:   ^Type_Info,
+	names:  []string,
+	values: []Type_Info_Enum_Value,
 }
 }
 Type_Info_Map :: struct {
 Type_Info_Map :: struct {
 	key:      ^Type_Info,
 	key:      ^Type_Info,
@@ -161,14 +171,6 @@ Type_Info_Simd_Vector :: struct {
 	elem_size:  int,
 	elem_size:  int,
 	count:      int,
 	count:      int,
 }
 }
-Type_Info_Relative_Pointer :: struct {
-	pointer:      ^Type_Info, // ^T
-	base_integer: ^Type_Info,
-}
-Type_Info_Relative_Multi_Pointer :: struct {
-	pointer:      ^Type_Info, // [^]T
-	base_integer: ^Type_Info,
-}
 Type_Info_Matrix :: struct {
 Type_Info_Matrix :: struct {
 	elem:         ^Type_Info,
 	elem:         ^Type_Info,
 	elem_size:    int,
 	elem_size:    int,
@@ -176,10 +178,23 @@ Type_Info_Matrix :: struct {
 	row_count:    int,
 	row_count:    int,
 	column_count: int,
 	column_count: int,
 	// Total element count = column_count * elem_stride
 	// Total element count = column_count * elem_stride
+	layout: enum u8 {
+		Column_Major, // array of column vectors
+		Row_Major,    // array of row vectors
+	},
 }
 }
 Type_Info_Soa_Pointer :: struct {
 Type_Info_Soa_Pointer :: struct {
 	elem: ^Type_Info,
 	elem: ^Type_Info,
 }
 }
+Type_Info_Bit_Field :: struct {
+	backing_type: ^Type_Info,
+	names:        [^]string     `fmt:"v,field_count"`,
+	types:        [^]^Type_Info `fmt:"v,field_count"`,
+	bit_sizes:    [^]uintptr    `fmt:"v,field_count"`,
+	bit_offsets:  [^]uintptr    `fmt:"v,field_count"`,
+	tags:         [^]string     `fmt:"v,field_count"`,
+	field_count:  int,
+}
 
 
 Type_Info_Flag :: enum u8 {
 Type_Info_Flag :: enum u8 {
 	Comparable     = 0,
 	Comparable     = 0,
@@ -218,10 +233,9 @@ Type_Info :: struct {
 		Type_Info_Map,
 		Type_Info_Map,
 		Type_Info_Bit_Set,
 		Type_Info_Bit_Set,
 		Type_Info_Simd_Vector,
 		Type_Info_Simd_Vector,
-		Type_Info_Relative_Pointer,
-		Type_Info_Relative_Multi_Pointer,
 		Type_Info_Matrix,
 		Type_Info_Matrix,
 		Type_Info_Soa_Pointer,
 		Type_Info_Soa_Pointer,
+		Type_Info_Bit_Field,
 	},
 	},
 }
 }
 
 
@@ -251,25 +265,24 @@ Typeid_Kind :: enum u8 {
 	Map,
 	Map,
 	Bit_Set,
 	Bit_Set,
 	Simd_Vector,
 	Simd_Vector,
-	Relative_Pointer,
-	Relative_Multi_Pointer,
 	Matrix,
 	Matrix,
 	Soa_Pointer,
 	Soa_Pointer,
+	Bit_Field,
 }
 }
 #assert(len(Typeid_Kind) < 32)
 #assert(len(Typeid_Kind) < 32)
 
 
-// Typeid_Bit_Field :: bit_field #align(align_of(uintptr)) {
-// 	index:    8*size_of(uintptr) - 8,
-// 	kind:     5, // Typeid_Kind
-// 	named:    1,
-// 	special:  1, // signed, cstring, etc
-// 	reserved: 1,
-// }
-// #assert(size_of(Typeid_Bit_Field) == size_of(uintptr));
+Typeid_Bit_Field :: bit_field uintptr {
+	index:    uintptr     | 8*size_of(uintptr) - 8,
+	kind:     Typeid_Kind | 5, // Typeid_Kind
+	named:    bool        | 1,
+	special:  bool        | 1, // signed, cstring, etc
+	reserved: bool        | 1,
+}
+#assert(size_of(Typeid_Bit_Field) == size_of(uintptr))
 
 
 // NOTE(bill): only the ones that are needed (not all types)
 // NOTE(bill): only the ones that are needed (not all types)
 // This will be set by the compiler
 // This will be set by the compiler
-type_table: []Type_Info
+type_table: []^Type_Info
 
 
 args__: []cstring
 args__: []cstring
 
 
@@ -284,6 +297,8 @@ when ODIN_OS == .Windows {
 		Thread_Detach  = 3,
 		Thread_Detach  = 3,
 	}
 	}
 	dll_forward_reason: DLL_Forward_Reason
 	dll_forward_reason: DLL_Forward_Reason
+
+	dll_instance: rawptr
 }
 }
 
 
 // IMPORTANT NOTE(bill): Must be in this order (as the compiler relies upon it)
 // IMPORTANT NOTE(bill): Must be in this order (as the compiler relies upon it)
@@ -295,6 +310,14 @@ Source_Code_Location :: struct {
 	procedure:    string,
 	procedure:    string,
 }
 }
 
 
+/*
+	Used by the built-in directory `#load_directory(path: string) -> []Load_Directory_File`
+*/
+Load_Directory_File :: struct {
+	name: string,
+	data: []byte, // immutable data
+}
+
 Assertion_Failure_Proc :: #type proc(prefix, message: string, loc: Source_Code_Location) -> !
 Assertion_Failure_Proc :: #type proc(prefix, message: string, loc: Source_Code_Location) -> !
 
 
 // Allocation Stuff
 // Allocation Stuff
@@ -306,6 +329,7 @@ Allocator_Mode :: enum byte {
 	Query_Features,
 	Query_Features,
 	Query_Info,
 	Query_Info,
 	Alloc_Non_Zeroed,
 	Alloc_Non_Zeroed,
+	Resize_Non_Zeroed,
 }
 }
 
 
 Allocator_Mode_Set :: distinct bit_set[Allocator_Mode]
 Allocator_Mode_Set :: distinct bit_set[Allocator_Mode]
@@ -373,11 +397,34 @@ Logger :: struct {
 	options:      Logger_Options,
 	options:      Logger_Options,
 }
 }
 
 
+
+Random_Generator_Mode :: enum {
+	Read,
+	Reset,
+	Query_Info,
+}
+
+Random_Generator_Query_Info_Flag :: enum u32 {
+	Cryptographic,
+	Uniform,
+	External_Entropy,
+	Resettable,
+}
+Random_Generator_Query_Info :: distinct bit_set[Random_Generator_Query_Info_Flag; u32]
+
+Random_Generator_Proc :: #type proc(data: rawptr, mode: Random_Generator_Mode, p: []byte)
+
+Random_Generator :: struct {
+	procedure: Random_Generator_Proc,
+	data:      rawptr,
+}
+
 Context :: struct {
 Context :: struct {
 	allocator:              Allocator,
 	allocator:              Allocator,
 	temp_allocator:         Allocator,
 	temp_allocator:         Allocator,
 	assertion_failure_proc: Assertion_Failure_Proc,
 	assertion_failure_proc: Assertion_Failure_Proc,
 	logger:                 Logger,
 	logger:                 Logger,
+	random_generator:       Random_Generator,
 
 
 	user_ptr:   rawptr,
 	user_ptr:   rawptr,
 	user_index: int,
 	user_index: int,
@@ -446,6 +493,15 @@ Raw_Soa_Pointer :: struct {
 	index: int,
 	index: int,
 }
 }
 
 
+Raw_Complex32     :: struct {real, imag: f16}
+Raw_Complex64     :: struct {real, imag: f32}
+Raw_Complex128    :: struct {real, imag: f64}
+Raw_Quaternion64  :: struct {imag, jmag, kmag: f16, real: f16}
+Raw_Quaternion128 :: struct {imag, jmag, kmag: f32, real: f32}
+Raw_Quaternion256 :: struct {imag, jmag, kmag: f64, real: f64}
+Raw_Quaternion64_Vector_Scalar  :: struct {vector: [3]f16, scalar: f16}
+Raw_Quaternion128_Vector_Scalar :: struct {vector: [3]f32, scalar: f32}
+Raw_Quaternion256_Vector_Scalar :: struct {vector: [3]f64, scalar: f64}
 
 
 
 
 /*
 /*
@@ -458,8 +514,11 @@ Raw_Soa_Pointer :: struct {
 		Essence,
 		Essence,
 		FreeBSD,
 		FreeBSD,
 		OpenBSD,
 		OpenBSD,
+		NetBSD,
+		Haiku,
 		WASI,
 		WASI,
 		JS,
 		JS,
+		Orca,
 		Freestanding,
 		Freestanding,
 	}
 	}
 */
 */
@@ -475,15 +534,29 @@ Odin_OS_Type :: type_of(ODIN_OS)
 		arm64,
 		arm64,
 		wasm32,
 		wasm32,
 		wasm64p32,
 		wasm64p32,
+		riscv64,
 	}
 	}
 */
 */
 Odin_Arch_Type :: type_of(ODIN_ARCH)
 Odin_Arch_Type :: type_of(ODIN_ARCH)
 
 
+Odin_Arch_Types :: bit_set[Odin_Arch_Type]
+
+ALL_ODIN_ARCH_TYPES :: Odin_Arch_Types{
+	.amd64,
+	.i386,
+	.arm32,
+	.arm64,
+	.wasm32,
+	.wasm64p32,
+	.riscv64,
+}
+
 /*
 /*
 	// Defined internally by the compiler
 	// Defined internally by the compiler
 	Odin_Build_Mode_Type :: enum int {
 	Odin_Build_Mode_Type :: enum int {
 		Executable,
 		Executable,
 		Dynamic,
 		Dynamic,
+		Static,
 		Object,
 		Object,
 		Assembly,
 		Assembly,
 		LLVM_IR,
 		LLVM_IR,
@@ -501,6 +574,22 @@ Odin_Build_Mode_Type :: type_of(ODIN_BUILD_MODE)
 */
 */
 Odin_Endian_Type :: type_of(ODIN_ENDIAN)
 Odin_Endian_Type :: type_of(ODIN_ENDIAN)
 
 
+Odin_OS_Types :: bit_set[Odin_OS_Type]
+
+ALL_ODIN_OS_TYPES :: Odin_OS_Types{
+	.Windows,
+	.Darwin,
+	.Linux,
+	.Essence,
+	.FreeBSD,
+	.OpenBSD,
+	.NetBSD,
+	.Haiku,
+	.WASI,
+	.JS,
+	.Orca,
+	.Freestanding,
+}
 
 
 /*
 /*
 	// Defined internally by the compiler
 	// Defined internally by the compiler
@@ -518,12 +607,25 @@ Odin_Platform_Subtarget_Type :: type_of(ODIN_PLATFORM_SUBTARGET)
 		Memory  = 1,
 		Memory  = 1,
 		Thread  = 2,
 		Thread  = 2,
 	}
 	}
-	Odin_Sanitizer_Flags :: distinct bitset[Odin_Sanitizer_Flag; u32]
+	Odin_Sanitizer_Flags :: distinct bit_set[Odin_Sanitizer_Flag; u32]
 
 
 	ODIN_SANITIZER_FLAGS // is a constant
 	ODIN_SANITIZER_FLAGS // is a constant
 */
 */
 Odin_Sanitizer_Flags :: type_of(ODIN_SANITIZER_FLAGS)
 Odin_Sanitizer_Flags :: type_of(ODIN_SANITIZER_FLAGS)
 
 
+/*
+	// Defined internally by the compiler
+	Odin_Optimization_Mode :: enum int {
+		None       = -1,
+		Minimal    =  0,
+		Size       =  1,
+		Speed      =  2,
+		Aggressive =  3,
+	}
+
+	ODIN_OPTIMIZATION_MODE // is a constant
+*/
+Odin_Optimization_Mode :: type_of(ODIN_OPTIMIZATION_MODE)
 
 
 /////////////////////////////
 /////////////////////////////
 // Init Startup Procedures //
 // Init Startup Procedures //
@@ -573,8 +675,9 @@ type_info_core :: proc "contextless" (info: ^Type_Info) -> ^Type_Info {
 	base := info
 	base := info
 	loop: for {
 	loop: for {
 		#partial switch i in base.variant {
 		#partial switch i in base.variant {
-		case Type_Info_Named:  base = i.base
-		case Type_Info_Enum:   base = i.base
+		case Type_Info_Named:     base = i.base
+		case Type_Info_Enum:      base = i.base
+		case Type_Info_Bit_Field: base = i.backing_type
 		case: break loop
 		case: break loop
 		}
 		}
 	}
 	}
@@ -589,7 +692,7 @@ __type_info_of :: proc "contextless" (id: typeid) -> ^Type_Info #no_bounds_check
 	if n < 0 || n >= len(type_table) {
 	if n < 0 || n >= len(type_table) {
 		n = 0
 		n = 0
 	}
 	}
-	return &type_table[n]
+	return type_table[n]
 }
 }
 
 
 when !ODIN_NO_RTTI {
 when !ODIN_NO_RTTI {
@@ -658,13 +761,20 @@ __init_context :: proc "contextless" (c: ^Context) {
 
 
 	c.logger.procedure = default_logger_proc
 	c.logger.procedure = default_logger_proc
 	c.logger.data = nil
 	c.logger.data = nil
+
+	c.random_generator.procedure = default_random_generator_proc
+	c.random_generator.data = nil
 }
 }
 
 
 default_assertion_failure_proc :: proc(prefix, message: string, loc: Source_Code_Location) -> ! {
 default_assertion_failure_proc :: proc(prefix, message: string, loc: Source_Code_Location) -> ! {
+	default_assertion_contextless_failure_proc(prefix, message, loc)
+}
+
+default_assertion_contextless_failure_proc :: proc "contextless" (prefix, message: string, loc: Source_Code_Location) -> ! {
 	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(" ")
 		}
 		}
@@ -673,7 +783,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()
 }
 }

+ 378 - 146
core/runtime/core_builtin.odin → base/runtime/core_builtin.odin

@@ -1,11 +1,44 @@
 package runtime
 package runtime
 
 
-import "core:intrinsics"
+import "base:intrinsics"
 
 
 @builtin
 @builtin
 Maybe :: union($T: typeid) {T}
 Maybe :: union($T: typeid) {T}
 
 
 
 
+/*
+Recovers the containing/parent struct from a pointer to one of its fields.
+Works by "walking back" to the struct's starting address using the offset between the field and the struct.
+
+Inputs:
+- ptr: Pointer to the field of a container struct
+- T: The type of the container struct
+- field_name: The name of the field in the `T` struct
+
+Returns:
+- A pointer to the container struct based on a pointer to a field in it
+
+Example:
+	package container_of
+	import "base:runtime"
+
+	Node :: struct {
+		value: int,
+		prev:  ^Node,
+		next:  ^Node,
+	}
+
+	main :: proc() {
+		node: Node
+		field_ptr := &node.next
+		container_struct_ptr: ^Node = runtime.container_of(field_ptr, Node, "next")
+		assert(container_struct_ptr == &node)
+		assert(uintptr(field_ptr) - uintptr(container_struct_ptr) == size_of(node.value) + size_of(node.prev))
+	}
+
+Output:
+	^Node
+*/
 @(builtin, require_results)
 @(builtin, require_results)
 container_of :: #force_inline proc "contextless" (ptr: $P/^$Field_Type, $T: typeid, $field_name: string) -> ^T
 container_of :: #force_inline proc "contextless" (ptr: $P/^$Field_Type, $T: typeid, $field_name: string) -> ^T
 	where intrinsics.type_has_field(T, field_name),
 	where intrinsics.type_has_field(T, field_name),
@@ -40,7 +73,7 @@ copy_slice :: proc "contextless" (dst, src: $T/[]$E) -> int {
 	}
 	}
 	return n
 	return n
 }
 }
-// `copy_from_string` is a built-in procedure that copies elements from a source slice `src` to a destination string `dst`.
+// `copy_from_string` is a built-in procedure that copies elements from a source string `src` to a destination slice `dst`.
 // The source and destination may overlap. Copy returns the number of elements copied, which will be the minimum
 // The source and destination may overlap. Copy returns the number of elements copied, which will be the minimum
 // of len(src) and len(dst).
 // of len(src) and len(dst).
 //
 //
@@ -53,7 +86,7 @@ copy_from_string :: proc "contextless" (dst: $T/[]$E/u8, src: $S/string) -> int
 	}
 	}
 	return n
 	return n
 }
 }
-// `copy` is a built-in procedure that copies elements from a source slice `src` to a destination slice/string `dst`.
+// `copy` is a built-in procedure that copies elements from a source slice/string `src` to a destination slice `dst`.
 // The source and destination may overlap. Copy returns the number of elements copied, which will be the minimum
 // The source and destination may overlap. Copy returns the number of elements copied, which will be the minimum
 // of len(src) and len(dst).
 // of len(src) and len(dst).
 @builtin
 @builtin
@@ -65,10 +98,10 @@ copy :: proc{copy_slice, copy_from_string}
 // with the old value, and reducing the length of the dynamic array by 1.
 // with the old value, and reducing the length of the dynamic array by 1.
 //
 //
 // Note: This is an O(1) operation.
 // Note: This is an O(1) operation.
-// Note: If you the elements to remain in their order, use `ordered_remove`.
+// Note: If you want the elements to remain in their order, use `ordered_remove`.
 // Note: If the index is out of bounds, this procedure will panic.
 // Note: If the index is out of bounds, this procedure will panic.
 @builtin
 @builtin
-unordered_remove :: proc(array: ^$D/[dynamic]$T, index: int, loc := #caller_location) #no_bounds_check {
+unordered_remove :: proc(array: ^$D/[dynamic]$T, #any_int index: int, loc := #caller_location) #no_bounds_check {
 	bounds_check_error_loc(loc, index, len(array))
 	bounds_check_error_loc(loc, index, len(array))
 	n := len(array)-1
 	n := len(array)-1
 	if index != n {
 	if index != n {
@@ -79,10 +112,10 @@ unordered_remove :: proc(array: ^$D/[dynamic]$T, index: int, loc := #caller_loca
 // `ordered_remove` removed the element at the specified `index` whilst keeping the order of the other elements.
 // `ordered_remove` removed the element at the specified `index` whilst keeping the order of the other elements.
 //
 //
 // Note: This is an O(N) operation.
 // Note: This is an O(N) operation.
-// Note: If you the elements do not have to remain in their order, prefer `unordered_remove`.
+// Note: If the elements do not have to remain in their order, prefer `unordered_remove`.
 // Note: If the index is out of bounds, this procedure will panic.
 // Note: If the index is out of bounds, this procedure will panic.
 @builtin
 @builtin
-ordered_remove :: proc(array: ^$D/[dynamic]$T, index: int, loc := #caller_location) #no_bounds_check {
+ordered_remove :: proc(array: ^$D/[dynamic]$T, #any_int index: int, loc := #caller_location) #no_bounds_check {
 	bounds_check_error_loc(loc, index, len(array))
 	bounds_check_error_loc(loc, index, len(array))
 	if index+1 < len(array) {
 	if index+1 < len(array) {
 		copy(array[index:], array[index+1:])
 		copy(array[index:], array[index+1:])
@@ -95,7 +128,7 @@ ordered_remove :: proc(array: ^$D/[dynamic]$T, index: int, loc := #caller_locati
 // Note: This is an O(N) operation.
 // Note: This is an O(N) operation.
 // Note: If the range is out of bounds, this procedure will panic.
 // Note: If the range is out of bounds, this procedure will panic.
 @builtin
 @builtin
-remove_range :: proc(array: ^$D/[dynamic]$T, lo, hi: int, loc := #caller_location) #no_bounds_check {
+remove_range :: proc(array: ^$D/[dynamic]$T, #any_int lo, hi: int, loc := #caller_location) #no_bounds_check {
 	slice_expr_error_lo_hi_loc(loc, lo, hi, len(array))
 	slice_expr_error_lo_hi_loc(loc, lo, hi, len(array))
 	n := max(hi-lo, 0)
 	n := max(hi-lo, 0)
 	if n > 0 {
 	if n > 0 {
@@ -109,7 +142,7 @@ remove_range :: proc(array: ^$D/[dynamic]$T, lo, hi: int, loc := #caller_locatio
 
 
 // `pop` will remove and return the end value of dynamic array `array` and reduces the length of `array` by 1.
 // `pop` will remove and return the end value of dynamic array `array` and reduces the length of `array` by 1.
 //
 //
-// Note: If the dynamic array as no elements (`len(array) == 0`), this procedure will panic.
+// Note: If the dynamic array has no elements (`len(array) == 0`), this procedure will panic.
 @builtin
 @builtin
 pop :: proc(array: ^$T/[dynamic]$E, loc := #caller_location) -> (res: E) #no_bounds_check {
 pop :: proc(array: ^$T/[dynamic]$E, loc := #caller_location) -> (res: E) #no_bounds_check {
 	assert(len(array) > 0, loc=loc)
 	assert(len(array) > 0, loc=loc)
@@ -122,7 +155,7 @@ pop :: proc(array: ^$T/[dynamic]$E, loc := #caller_location) -> (res: E) #no_bou
 // `pop_safe` trys to remove and return the end value of dynamic array `array` and reduces the length of `array` by 1.
 // `pop_safe` trys to remove and return the end value of dynamic array `array` and reduces the length of `array` by 1.
 // If the operation is not possible, it will return false.
 // If the operation is not possible, it will return false.
 @builtin
 @builtin
-pop_safe :: proc(array: ^$T/[dynamic]$E) -> (res: E, ok: bool) #no_bounds_check {
+pop_safe :: proc "contextless" (array: ^$T/[dynamic]$E) -> (res: E, ok: bool) #no_bounds_check {
 	if len(array) == 0 {
 	if len(array) == 0 {
 		return
 		return
 	}
 	}
@@ -148,7 +181,7 @@ pop_front :: proc(array: ^$T/[dynamic]$E, loc := #caller_location) -> (res: E) #
 // `pop_front_safe` trys to return and remove the first value of dynamic array `array` and reduces the length of `array` by 1.
 // `pop_front_safe` trys to return and remove the first value of dynamic array `array` and reduces the length of `array` by 1.
 // If the operation is not possible, it will return false.
 // If the operation is not possible, it will return false.
 @builtin
 @builtin
-pop_front_safe :: proc(array: ^$T/[dynamic]$E) -> (res: E, ok: bool) #no_bounds_check {
+pop_front_safe :: proc "contextless" (array: ^$T/[dynamic]$E) -> (res: E, ok: bool) #no_bounds_check {
 	if len(array) == 0 {
 	if len(array) == 0 {
 		return
 		return
 	}
 	}
@@ -163,15 +196,43 @@ pop_front_safe :: proc(array: ^$T/[dynamic]$E) -> (res: E, ok: bool) #no_bounds_
 
 
 // `clear` will set the length of a passed dynamic array or map to `0`
 // `clear` will set the length of a passed dynamic array or map to `0`
 @builtin
 @builtin
-clear :: proc{clear_dynamic_array, clear_map}
+clear :: proc{
+	clear_dynamic_array,
+	clear_map,
+
+	clear_soa_dynamic_array,
+}
 
 
 // `reserve` will try to reserve memory of a passed dynamic array or map to the requested element count (setting the `cap`).
 // `reserve` will try to reserve memory of a passed dynamic array or map to the requested element count (setting the `cap`).
 @builtin
 @builtin
-reserve :: proc{reserve_dynamic_array, reserve_map}
+reserve :: proc{
+	reserve_dynamic_array,
+	reserve_map,
+
+	reserve_soa,
+}
+
+@builtin
+non_zero_reserve :: proc{
+	non_zero_reserve_dynamic_array,
+
+	non_zero_reserve_soa,
+}
+
+// `resize` will try to resize memory of a passed dynamic array to the requested element count (setting the `len`, and possibly `cap`).
+@builtin
+resize :: proc{
+	resize_dynamic_array,
+
+	resize_soa,
+}
 
 
-// `resize` will try to resize memory of a passed dynamic array or map to the requested element count (setting the `len`, and possibly `cap`).
 @builtin
 @builtin
-resize :: proc{resize_dynamic_array}
+non_zero_resize :: proc{
+	non_zero_resize_dynamic_array,
+
+	non_zero_resize_soa,
+}
 
 
 // Shrinks the capacity of a dynamic array or map down to the current length, or the given capacity.
 // Shrinks the capacity of a dynamic array or map down to the current length, or the given capacity.
 @builtin
 @builtin
@@ -234,6 +295,8 @@ delete :: proc{
 	delete_dynamic_array,
 	delete_dynamic_array,
 	delete_slice,
 	delete_slice,
 	delete_map,
 	delete_map,
+	delete_soa_slice,
+	delete_soa_dynamic_array,
 }
 }
 
 
 
 
@@ -260,7 +323,7 @@ new_clone :: proc(data: $T, allocator := context.allocator, loc := #caller_locat
 	return
 	return
 }
 }
 
 
-DEFAULT_RESERVE_CAPACITY :: 16
+DEFAULT_DYNAMIC_ARRAY_CAPACITY :: 8
 
 
 @(require_results)
 @(require_results)
 make_aligned :: proc($T: typeid/[]$E, #any_int len: int, alignment: int, allocator := context.allocator, loc := #caller_location) -> (T, Allocator_Error) #optional_allocator_error {
 make_aligned :: proc($T: typeid/[]$E, #any_int len: int, alignment: int, allocator := context.allocator, loc := #caller_location) -> (T, Allocator_Error) #optional_allocator_error {
@@ -287,7 +350,7 @@ make_slice :: proc($T: typeid/[]$E, #any_int len: int, allocator := context.allo
 // Note: Prefer using the procedure group `make`.
 // Note: Prefer using the procedure group `make`.
 @(builtin, require_results)
 @(builtin, require_results)
 make_dynamic_array :: proc($T: typeid/[dynamic]$E, allocator := context.allocator, loc := #caller_location) -> (T, Allocator_Error) #optional_allocator_error {
 make_dynamic_array :: proc($T: typeid/[dynamic]$E, allocator := context.allocator, loc := #caller_location) -> (T, Allocator_Error) #optional_allocator_error {
-	return make_dynamic_array_len_cap(T, 0, DEFAULT_RESERVE_CAPACITY, allocator, loc)
+	return make_dynamic_array_len_cap(T, 0, 0, allocator, loc)
 }
 }
 // `make_dynamic_array_len` allocates and initializes a dynamic array. Like `new`, the first argument is a type, not a value.
 // `make_dynamic_array_len` allocates and initializes a dynamic array. Like `new`, the first argument is a type, not a value.
 // Unlike `new`, `make`'s return value is the same as the type of its argument, not a pointer to it.
 // Unlike `new`, `make`'s return value is the same as the type of its argument, not a pointer to it.
@@ -303,28 +366,47 @@ make_dynamic_array_len :: proc($T: typeid/[dynamic]$E, #any_int len: int, alloca
 // Note: Prefer using the procedure group `make`.
 // Note: Prefer using the procedure group `make`.
 @(builtin, require_results)
 @(builtin, require_results)
 make_dynamic_array_len_cap :: proc($T: typeid/[dynamic]$E, #any_int len: int, #any_int cap: int, allocator := context.allocator, loc := #caller_location) -> (array: T, err: Allocator_Error) #optional_allocator_error {
 make_dynamic_array_len_cap :: proc($T: typeid/[dynamic]$E, #any_int len: int, #any_int cap: int, allocator := context.allocator, loc := #caller_location) -> (array: T, err: Allocator_Error) #optional_allocator_error {
+	err = _make_dynamic_array_len_cap((^Raw_Dynamic_Array)(&array), size_of(E), align_of(E), len, cap, allocator, loc)
+	return
+}
+
+@(require_results)
+_make_dynamic_array_len_cap :: proc(array: ^Raw_Dynamic_Array, size_of_elem, align_of_elem: int, #any_int len: int, #any_int cap: int, allocator := context.allocator, loc := #caller_location) -> (err: Allocator_Error) {
 	make_dynamic_array_error_loc(loc, len, cap)
 	make_dynamic_array_error_loc(loc, len, cap)
-	data := mem_alloc_bytes(size_of(E)*cap, align_of(E), allocator, loc) or_return
-	s := Raw_Dynamic_Array{raw_data(data), len, cap, allocator}
-	if data == nil && size_of(E) != 0 {
-		s.len, s.cap = 0, 0
-	}
-	array = transmute(T)s
+	array.allocator = allocator // initialize allocator before just in case it fails to allocate any memory
+	data := mem_alloc_bytes(size_of_elem*cap, align_of_elem, allocator, loc) or_return
+	use_zero := data == nil && size_of_elem != 0
+	array.data = raw_data(data)
+	array.len = 0 if use_zero else len
+	array.cap = 0 if use_zero else cap
+	array.allocator = allocator
 	return
 	return
 }
 }
-// `make_map` allocates and initializes a dynamic array. Like `new`, the first argument is a type, not a value.
+
+// `make_map` initializes a map with an allocator. Like `new`, the first argument is a type, not a value.
+// Unlike `new`, `make`'s return value is the same as the type of its argument, not a pointer to it.
+//
+// Note: Prefer using the procedure group `make`.
+@(builtin, require_results)
+make_map :: proc($T: typeid/map[$K]$E, allocator := context.allocator, loc := #caller_location) -> (m: T) {
+	m.allocator = allocator
+	return m
+}
+
+// `make_map_cap` initializes a map with an allocator and allocates space using `capacity`.
+// Like `new`, the first argument is a type, not a value.
 // Unlike `new`, `make`'s return value is the same as the type of its argument, not a pointer to it.
 // Unlike `new`, `make`'s return value is the same as the type of its argument, not a pointer to it.
 //
 //
 // Note: Prefer using the procedure group `make`.
 // Note: Prefer using the procedure group `make`.
 @(builtin, require_results)
 @(builtin, require_results)
-make_map :: proc($T: typeid/map[$K]$E, #any_int capacity: int = 1<<MAP_MIN_LOG2_CAPACITY, allocator := context.allocator, loc := #caller_location) -> (m: T, err: Allocator_Error) #optional_allocator_error {
+make_map_cap :: proc($T: typeid/map[$K]$E, #any_int capacity: int, allocator := context.allocator, loc := #caller_location) -> (m: T, err: Allocator_Error) #optional_allocator_error {
 	make_map_expr_error_loc(loc, capacity)
 	make_map_expr_error_loc(loc, capacity)
 	context.allocator = allocator
 	context.allocator = allocator
 
 
 	err = reserve_map(&m, capacity, loc)
 	err = reserve_map(&m, capacity, loc)
 	return
 	return
 }
 }
-// `make_multi_pointer` allocates and initializes a dynamic array. Like `new`, the first argument is a type, not a value.
+// `make_multi_pointer` allocates and initializes a multi-pointer. Like `new`, the first argument is a type, not a value.
 // Unlike `new`, `make`'s return value is the same as the type of its argument, not a pointer to it.
 // Unlike `new`, `make`'s return value is the same as the type of its argument, not a pointer to it.
 //
 //
 // This is "similar" to doing `raw_data(make([]E, len, allocator))`.
 // This is "similar" to doing `raw_data(make([]E, len, allocator))`.
@@ -346,7 +428,7 @@ make_multi_pointer :: proc($T: typeid/[^]$E, #any_int len: int, allocator := con
 //
 //
 // Similar to `new`, the first argument is a type, not a value. Unlike new, make's return type is the same as the
 // Similar to `new`, the first argument is a type, not a value. Unlike new, make's return type is the same as the
 // type of its argument, not a pointer to it.
 // type of its argument, not a pointer to it.
-// Make uses the specified allocator, default is context.allocator, default is context.allocator
+// Make uses the specified allocator, default is context.allocator.
 @builtin
 @builtin
 make :: proc{
 make :: proc{
 	make_slice,
 	make_slice,
@@ -354,7 +436,13 @@ make :: proc{
 	make_dynamic_array_len,
 	make_dynamic_array_len,
 	make_dynamic_array_len_cap,
 	make_dynamic_array_len_cap,
 	make_map,
 	make_map,
+	make_map_cap,
 	make_multi_pointer,
 	make_multi_pointer,
+
+	make_soa_slice,
+	make_soa_dynamic_array,
+	make_soa_dynamic_array_len,
+	make_soa_dynamic_array_len_cap,
 }
 }
 
 
 
 
@@ -374,7 +462,7 @@ clear_map :: proc "contextless" (m: ^$T/map[$K]$V) {
 //
 //
 // Note: Prefer the procedure group `reserve`
 // Note: Prefer the procedure group `reserve`
 @builtin
 @builtin
-reserve_map :: proc(m: ^$T/map[$K]$V, capacity: int, loc := #caller_location) -> Allocator_Error {
+reserve_map :: proc(m: ^$T/map[$K]$V, #any_int capacity: int, loc := #caller_location) -> Allocator_Error {
 	return __dynamic_map_reserve((^Raw_Map)(m), map_info(T), uint(capacity), loc) if m != nil else nil
 	return __dynamic_map_reserve((^Raw_Map)(m), map_info(T), uint(capacity), loc) if m != nil else nil
 }
 }
 
 
@@ -404,75 +492,112 @@ delete_key :: proc(m: ^$T/map[$K]$V, key: K) -> (deleted_key: K, deleted_value:
 	return
 	return
 }
 }
 
 
+_append_elem :: #force_inline proc(array: ^Raw_Dynamic_Array, size_of_elem, align_of_elem: int, arg_ptr: rawptr, should_zero: bool, loc := #caller_location) -> (n: int, err: Allocator_Error) #optional_allocator_error {
+	if array == nil {
+		return
+	}
 
 
+	if array.cap < array.len+1 {
+		// Same behavior as _append_elems but there's only one arg, so we always just add DEFAULT_DYNAMIC_ARRAY_CAPACITY.
+		cap := 2 * array.cap + DEFAULT_DYNAMIC_ARRAY_CAPACITY
 
 
-@builtin
-append_elem :: proc(array: ^$T/[dynamic]$E, arg: E, loc := #caller_location) -> (n: int, err: Allocator_Error) #optional_allocator_error {
-	if array == nil {
-		return 0, nil
+		// do not 'or_return' here as it could be a partial success
+		err = _reserve_dynamic_array(array, size_of_elem, align_of_elem, cap, should_zero, loc)
 	}
 	}
-	when size_of(E) == 0 {
-		array := (^Raw_Dynamic_Array)(array)
+	if array.cap-array.len > 0 {
+		data := ([^]byte)(array.data)
+		assert(data != nil, loc=loc)
+		data = data[array.len*size_of_elem:]
+		intrinsics.mem_copy_non_overlapping(data, arg_ptr, size_of_elem)
 		array.len += 1
 		array.len += 1
+		n = 1
+	}
+	return
+}
+
+@builtin
+append_elem :: proc(array: ^$T/[dynamic]$E, #no_broadcast arg: E, loc := #caller_location) -> (n: int, err: Allocator_Error) #optional_allocator_error {
+	when size_of(E) == 0 {
+		(^Raw_Dynamic_Array)(array).len += 1
 		return 1, nil
 		return 1, nil
 	} else {
 	} else {
-		if cap(array) < len(array)+1 {
-			cap := 2 * cap(array) + max(8, 1)
-			err = reserve(array, cap, loc) // do not 'or_return' here as it could be a partial success
-		}
-		if cap(array)-len(array) > 0 {
-			a := (^Raw_Dynamic_Array)(array)
-			when size_of(E) != 0 {
-				data := ([^]E)(a.data)
-				assert(data != nil, loc=loc)
-				data[a.len] = arg
-			}
-			a.len += 1
-			return 1, err
-		}
-		return 0, err
+		arg := arg
+		return _append_elem((^Raw_Dynamic_Array)(array), size_of(E), align_of(E), &arg, true, loc=loc)
 	}
 	}
 }
 }
 
 
 @builtin
 @builtin
-append_elems :: proc(array: ^$T/[dynamic]$E, args: ..E, loc := #caller_location) -> (n: int, err: Allocator_Error) #optional_allocator_error {
+non_zero_append_elem :: proc(array: ^$T/[dynamic]$E, #no_broadcast arg: E, loc := #caller_location) -> (n: int, err: Allocator_Error) #optional_allocator_error {
+	when size_of(E) == 0 {
+		(^Raw_Dynamic_Array)(array).len += 1
+		return 1, nil
+	} else {
+		arg := arg
+		return _append_elem((^Raw_Dynamic_Array)(array), size_of(E), align_of(E), &arg, false, loc=loc)
+	}
+}
+
+_append_elems :: #force_inline proc(array: ^Raw_Dynamic_Array, size_of_elem, align_of_elem: int, should_zero: bool, loc := #caller_location, args: rawptr, arg_len: int) -> (n: int, err: Allocator_Error) #optional_allocator_error {
 	if array == nil {
 	if array == nil {
 		return 0, nil
 		return 0, nil
 	}
 	}
 
 
-	arg_len := len(args)
 	if arg_len <= 0 {
 	if arg_len <= 0 {
 		return 0, nil
 		return 0, nil
 	}
 	}
 
 
-	when size_of(E) == 0 {
-		array := (^Raw_Dynamic_Array)(array)
+	if array.cap < array.len+arg_len {
+		cap := 2 * array.cap + max(DEFAULT_DYNAMIC_ARRAY_CAPACITY, arg_len)
+
+		// do not 'or_return' here as it could be a partial success
+		err = _reserve_dynamic_array(array, size_of_elem, align_of_elem, cap, should_zero, loc)
+	}
+	arg_len := arg_len
+	arg_len = min(array.cap-array.len, arg_len)
+	if arg_len > 0 {
+		data := ([^]byte)(array.data)
+		assert(data != nil, loc=loc)
+		data = data[array.len*size_of_elem:]
+		intrinsics.mem_copy(data, args, size_of_elem * arg_len) // must be mem_copy (overlapping)
 		array.len += arg_len
 		array.len += arg_len
-		return arg_len, nil
+	}
+	return arg_len, err
+}
+
+@builtin
+append_elems :: proc(array: ^$T/[dynamic]$E, #no_broadcast args: ..E, loc := #caller_location) -> (n: int, err: Allocator_Error) #optional_allocator_error {
+	when size_of(E) == 0 {
+		a := (^Raw_Dynamic_Array)(array)
+		a.len += len(args)
+		return len(args), nil
 	} else {
 	} else {
-		if cap(array) < len(array)+arg_len {
-			cap := 2 * cap(array) + max(8, arg_len)
-			err = reserve(array, cap, loc)  // do not 'or_return' here as it could be a partial success
-		}
-		arg_len = min(cap(array)-len(array), arg_len)
-		if arg_len > 0 {
-			a := (^Raw_Dynamic_Array)(array)
-			when size_of(E) != 0 {
-				data := ([^]E)(a.data)
-				assert(data != nil, loc=loc)
-				intrinsics.mem_copy(&data[a.len], raw_data(args), size_of(E) * arg_len)
-			}
-			a.len += arg_len
-		}
-		return arg_len, err
+		return _append_elems((^Raw_Dynamic_Array)(array), size_of(E), align_of(E), true, loc, raw_data(args), len(args))
+	}
+}
+
+@builtin
+non_zero_append_elems :: proc(array: ^$T/[dynamic]$E, #no_broadcast args: ..E, loc := #caller_location) -> (n: int, err: Allocator_Error) #optional_allocator_error {
+	when size_of(E) == 0 {
+		a := (^Raw_Dynamic_Array)(array)
+		a.len += len(args)
+		return len(args), nil
+	} else {
+		return _append_elems((^Raw_Dynamic_Array)(array), size_of(E), align_of(E), false, loc, raw_data(args), len(args))
 	}
 	}
 }
 }
 
 
 // The append_string built-in procedure appends a string to the end of a [dynamic]u8 like type
 // The append_string built-in procedure appends a string to the end of a [dynamic]u8 like type
+_append_elem_string :: proc(array: ^$T/[dynamic]$E/u8, arg: $A/string, should_zero: bool, loc := #caller_location) -> (n: int, err: Allocator_Error) #optional_allocator_error {
+	return _append_elems((^Raw_Dynamic_Array)(array), 1, 1, should_zero, loc, raw_data(arg), len(arg))
+}
+
 @builtin
 @builtin
 append_elem_string :: proc(array: ^$T/[dynamic]$E/u8, arg: $A/string, loc := #caller_location) -> (n: int, err: Allocator_Error) #optional_allocator_error {
 append_elem_string :: proc(array: ^$T/[dynamic]$E/u8, arg: $A/string, loc := #caller_location) -> (n: int, err: Allocator_Error) #optional_allocator_error {
-	args := transmute([]E)arg
-	return append_elems(array, ..args, loc=loc)
+	return _append_elem_string(array, arg, true, loc)
+}
+@builtin
+non_zero_append_elem_string :: proc(array: ^$T/[dynamic]$E/u8, arg: $A/string, loc := #caller_location) -> (n: int, err: Allocator_Error) #optional_allocator_error {
+	return _append_elem_string(array, arg, false, loc)
 }
 }
 
 
 
 
@@ -491,7 +616,23 @@ append_string :: proc(array: ^$T/[dynamic]$E/u8, args: ..string, loc := #caller_
 }
 }
 
 
 // The append built-in procedure appends elements to the end of a dynamic array
 // The append built-in procedure appends elements to the end of a dynamic array
-@builtin append :: proc{append_elem, append_elems, append_elem_string}
+@builtin append :: proc{
+	append_elem,
+	append_elems,
+	append_elem_string,
+
+	append_soa_elem,
+	append_soa_elems,
+}
+
+@builtin non_zero_append :: proc{
+	non_zero_append_elem,
+	non_zero_append_elems,
+	non_zero_append_elem_string,
+
+	non_zero_append_soa_elem,
+	non_zero_append_soa_elems,
+}
 
 
 
 
 @builtin
 @builtin
@@ -506,7 +647,7 @@ append_nothing :: proc(array: ^$T/[dynamic]$E, loc := #caller_location) -> (n: i
 
 
 
 
 @builtin
 @builtin
-inject_at_elem :: proc(array: ^$T/[dynamic]$E, index: int, arg: E, loc := #caller_location) -> (ok: bool, err: Allocator_Error) #no_bounds_check #optional_allocator_error {
+inject_at_elem :: proc(array: ^$T/[dynamic]$E, #any_int index: int, #no_broadcast arg: E, loc := #caller_location) -> (ok: bool, err: Allocator_Error) #no_bounds_check #optional_allocator_error {
 	if array == nil {
 	if array == nil {
 		return
 		return
 	}
 	}
@@ -524,7 +665,7 @@ inject_at_elem :: proc(array: ^$T/[dynamic]$E, index: int, arg: E, loc := #calle
 }
 }
 
 
 @builtin
 @builtin
-inject_at_elems :: proc(array: ^$T/[dynamic]$E, index: int, args: ..E, loc := #caller_location) -> (ok: bool, err: Allocator_Error) #no_bounds_check #optional_allocator_error {
+inject_at_elems :: proc(array: ^$T/[dynamic]$E, #any_int index: int, #no_broadcast args: ..E, loc := #caller_location) -> (ok: bool, err: Allocator_Error) #no_bounds_check #optional_allocator_error {
 	if array == nil {
 	if array == nil {
 		return
 		return
 	}
 	}
@@ -547,7 +688,7 @@ inject_at_elems :: proc(array: ^$T/[dynamic]$E, index: int, args: ..E, loc := #c
 }
 }
 
 
 @builtin
 @builtin
-inject_at_elem_string :: proc(array: ^$T/[dynamic]$E/u8, index: int, arg: string, loc := #caller_location) -> (ok: bool, err: Allocator_Error) #no_bounds_check #optional_allocator_error {
+inject_at_elem_string :: proc(array: ^$T/[dynamic]$E/u8, #any_int index: int, arg: string, loc := #caller_location) -> (ok: bool, err: Allocator_Error) #no_bounds_check #optional_allocator_error {
 	if array == nil {
 	if array == nil {
 		return
 		return
 	}
 	}
@@ -572,7 +713,7 @@ inject_at_elem_string :: proc(array: ^$T/[dynamic]$E/u8, index: int, arg: string
 
 
 
 
 @builtin
 @builtin
-assign_at_elem :: proc(array: ^$T/[dynamic]$E, index: int, arg: E, loc := #caller_location) -> (ok: bool, err: Allocator_Error) #no_bounds_check #optional_allocator_error {
+assign_at_elem :: proc(array: ^$T/[dynamic]$E, #any_int index: int, arg: E, loc := #caller_location) -> (ok: bool, err: Allocator_Error) #no_bounds_check #optional_allocator_error {
 	if index < len(array) {
 	if index < len(array) {
 		array[index] = arg
 		array[index] = arg
 		ok = true
 		ok = true
@@ -586,12 +727,15 @@ assign_at_elem :: proc(array: ^$T/[dynamic]$E, index: int, arg: E, loc := #calle
 
 
 
 
 @builtin
 @builtin
-assign_at_elems :: proc(array: ^$T/[dynamic]$E, index: int, args: ..E, loc := #caller_location) -> (ok: bool, err: Allocator_Error) #no_bounds_check #optional_allocator_error {
-	if index+len(args) < len(array) {
+assign_at_elems :: proc(array: ^$T/[dynamic]$E, #any_int index: int, #no_broadcast args: ..E, loc := #caller_location) -> (ok: bool, err: Allocator_Error) #no_bounds_check #optional_allocator_error {
+	new_size := index + len(args)
+	if len(args) == 0 {
+		ok = true
+	} else if new_size < len(array) {
 		copy(array[index:], args)
 		copy(array[index:], args)
 		ok = true
 		ok = true
 	} else {
 	} else {
-		resize(array, index+1+len(args), loc) or_return
+		resize(array, new_size, loc) or_return
 		copy(array[index:], args)
 		copy(array[index:], args)
 		ok = true
 		ok = true
 	}
 	}
@@ -600,7 +744,7 @@ assign_at_elems :: proc(array: ^$T/[dynamic]$E, index: int, args: ..E, loc := #c
 
 
 
 
 @builtin
 @builtin
-assign_at_elem_string :: proc(array: ^$T/[dynamic]$E/u8, index: int, arg: string, loc := #caller_location) -> (ok: bool, err: Allocator_Error) #no_bounds_check #optional_allocator_error {
+assign_at_elem_string :: proc(array: ^$T/[dynamic]$E/u8, #any_int index: int, arg: string, loc := #caller_location) -> (ok: bool, err: Allocator_Error) #no_bounds_check #optional_allocator_error {
 	new_size := index + len(arg)
 	new_size := index + len(arg)
 	if len(arg) == 0 {
 	if len(arg) == 0 {
 		ok = true
 		ok = true
@@ -633,12 +777,10 @@ clear_dynamic_array :: proc "contextless" (array: ^$T/[dynamic]$E) {
 // `reserve_dynamic_array` will try to reserve memory of a passed dynamic array or map to the requested element count (setting the `cap`).
 // `reserve_dynamic_array` will try to reserve memory of a passed dynamic array or map to the requested element count (setting the `cap`).
 //
 //
 // Note: Prefer the procedure group `reserve`.
 // Note: Prefer the procedure group `reserve`.
-@builtin
-reserve_dynamic_array :: proc(array: ^$T/[dynamic]$E, capacity: int, loc := #caller_location) -> Allocator_Error {
-	if array == nil {
+_reserve_dynamic_array :: #force_inline proc(a: ^Raw_Dynamic_Array, size_of_elem, align_of_elem: int, capacity: int, should_zero: bool, loc := #caller_location) -> Allocator_Error {
+	if a == nil {
 		return nil
 		return nil
 	}
 	}
-	a := (^Raw_Dynamic_Array)(array)
 
 
 	if capacity <= a.cap {
 	if capacity <= a.cap {
 		return nil
 		return nil
@@ -649,11 +791,16 @@ reserve_dynamic_array :: proc(array: ^$T/[dynamic]$E, capacity: int, loc := #cal
 	}
 	}
 	assert(a.allocator.procedure != nil)
 	assert(a.allocator.procedure != nil)
 
 
-	old_size  := a.cap * size_of(E)
-	new_size  := capacity * size_of(E)
+	old_size  := a.cap * size_of_elem
+	new_size  := capacity * size_of_elem
 	allocator := a.allocator
 	allocator := a.allocator
 
 
-	new_data := mem_resize(a.data, old_size, new_size, align_of(E), allocator, loc) or_return
+	new_data: []byte
+	if should_zero {
+		new_data = mem_resize(a.data, old_size, new_size, align_of_elem, allocator, loc) or_return
+	} else {
+		new_data = non_zero_mem_resize(a.data, old_size, new_size, align_of_elem, allocator, loc) or_return
+	}
 	if new_data == nil && new_size > 0 {
 	if new_data == nil && new_size > 0 {
 		return .Out_Of_Memory
 		return .Out_Of_Memory
 	}
 	}
@@ -663,15 +810,26 @@ reserve_dynamic_array :: proc(array: ^$T/[dynamic]$E, capacity: int, loc := #cal
 	return nil
 	return nil
 }
 }
 
 
-// `resize_dynamic_array` will try to resize memory of a passed dynamic array or map to the requested element count (setting the `len`, and possibly `cap`).
-//
-// Note: Prefer the procedure group `resize`
 @builtin
 @builtin
-resize_dynamic_array :: proc(array: ^$T/[dynamic]$E, length: int, loc := #caller_location) -> Allocator_Error {
-	if array == nil {
+reserve_dynamic_array :: proc(array: ^$T/[dynamic]$E, #any_int capacity: int, loc := #caller_location) -> Allocator_Error {
+	return _reserve_dynamic_array((^Raw_Dynamic_Array)(array), size_of(E), align_of(E), capacity, true, loc)
+}
+
+@builtin
+non_zero_reserve_dynamic_array :: proc(array: ^$T/[dynamic]$E, #any_int capacity: int, loc := #caller_location) -> Allocator_Error {
+	return _reserve_dynamic_array((^Raw_Dynamic_Array)(array), size_of(E), align_of(E), capacity, false, loc)
+}
+
+
+_resize_dynamic_array :: #force_inline proc(a: ^Raw_Dynamic_Array, size_of_elem, align_of_elem: int, length: int, should_zero: bool, loc := #caller_location) -> Allocator_Error {
+	if a == nil {
 		return nil
 		return nil
 	}
 	}
-	a := (^Raw_Dynamic_Array)(array)
+
+	if should_zero && a.len < length {
+		num_reused := min(a.cap, length) - a.len
+		intrinsics.mem_zero(([^]byte)(a.data)[a.len*size_of_elem:], num_reused*size_of_elem)
+	}
 
 
 	if length <= a.cap {
 	if length <= a.cap {
 		a.len = max(length, 0)
 		a.len = max(length, 0)
@@ -683,11 +841,16 @@ resize_dynamic_array :: proc(array: ^$T/[dynamic]$E, length: int, loc := #caller
 	}
 	}
 	assert(a.allocator.procedure != nil)
 	assert(a.allocator.procedure != nil)
 
 
-	old_size  := a.cap * size_of(E)
-	new_size  := length * size_of(E)
+	old_size  := a.cap  * size_of_elem
+	new_size  := length * size_of_elem
 	allocator := a.allocator
 	allocator := a.allocator
 
 
-	new_data := mem_resize(a.data, old_size, new_size, align_of(E), allocator, loc) or_return
+	new_data : []byte
+	if should_zero {
+		new_data = mem_resize(a.data, old_size, new_size, align_of_elem, allocator, loc) or_return
+	} else {
+		new_data = non_zero_mem_resize(a.data, old_size, new_size, align_of_elem, allocator, loc) or_return
+	}
 	if new_data == nil && new_size > 0 {
 	if new_data == nil && new_size > 0 {
 		return .Out_Of_Memory
 		return .Out_Of_Memory
 	}
 	}
@@ -698,6 +861,19 @@ resize_dynamic_array :: proc(array: ^$T/[dynamic]$E, length: int, loc := #caller
 	return nil
 	return nil
 }
 }
 
 
+// `resize_dynamic_array` will try to resize memory of a passed dynamic array or map to the requested element count (setting the `len`, and possibly `cap`).
+//
+// Note: Prefer the procedure group `resize`
+@builtin
+resize_dynamic_array :: proc(array: ^$T/[dynamic]$E, #any_int length: int, loc := #caller_location) -> Allocator_Error {
+	return _resize_dynamic_array((^Raw_Dynamic_Array)(array), size_of(E), align_of(E), length, true, loc=loc)
+}
+
+@builtin
+non_zero_resize_dynamic_array :: proc(array: ^$T/[dynamic]$E, #any_int length: int, loc := #caller_location) -> Allocator_Error {
+	return _resize_dynamic_array((^Raw_Dynamic_Array)(array), size_of(E), align_of(E), length, false, loc=loc)
+}
+
 /*
 /*
 	Shrinks the capacity of a dynamic array down to the current length, or the given capacity.
 	Shrinks the capacity of a dynamic array down to the current length, or the given capacity.
 
 
@@ -709,11 +885,14 @@ resize_dynamic_array :: proc(array: ^$T/[dynamic]$E, length: int, loc := #caller
 
 
 	Note: Prefer the procedure group `shrink`
 	Note: Prefer the procedure group `shrink`
 */
 */
-shrink_dynamic_array :: proc(array: ^$T/[dynamic]$E, new_cap := -1, loc := #caller_location) -> (did_shrink: bool, err: Allocator_Error) {
-	if array == nil {
+shrink_dynamic_array :: proc(array: ^$T/[dynamic]$E, #any_int new_cap := -1, loc := #caller_location) -> (did_shrink: bool, err: Allocator_Error) {
+	return _shrink_dynamic_array((^Raw_Dynamic_Array)(array), size_of(E), align_of(E), new_cap, loc)
+}
+
+_shrink_dynamic_array :: proc(a: ^Raw_Dynamic_Array, size_of_elem, align_of_elem: int, new_cap := -1, loc := #caller_location) -> (did_shrink: bool, err: Allocator_Error) {
+	if a == nil {
 		return
 		return
 	}
 	}
-	a := (^Raw_Dynamic_Array)(array)
 
 
 	new_cap := new_cap if new_cap >= 0 else a.len
 	new_cap := new_cap if new_cap >= 0 else a.len
 
 
@@ -726,10 +905,10 @@ shrink_dynamic_array :: proc(array: ^$T/[dynamic]$E, new_cap := -1, loc := #call
 	}
 	}
 	assert(a.allocator.procedure != nil)
 	assert(a.allocator.procedure != nil)
 
 
-	old_size := a.cap * size_of(E)
-	new_size := new_cap * size_of(E)
+	old_size := a.cap * size_of_elem
+	new_size := new_cap * size_of_elem
 
 
-	new_data := mem_resize(a.data, old_size, new_size, align_of(E), a.allocator, loc) or_return
+	new_data := mem_resize(a.data, old_size, new_size, align_of_elem, a.allocator, loc) or_return
 
 
 	a.data = raw_data(new_data)
 	a.data = raw_data(new_data)
 	a.len = min(new_cap, a.len)
 	a.len = min(new_cap, a.len)
@@ -743,62 +922,59 @@ map_insert :: proc(m: ^$T/map[$K]$V, key: K, value: V, loc := #caller_location)
 	return (^V)(__dynamic_map_set_without_hash((^Raw_Map)(m), map_info(T), rawptr(&key), rawptr(&value), loc))
 	return (^V)(__dynamic_map_set_without_hash((^Raw_Map)(m), map_info(T), rawptr(&key), rawptr(&value), loc))
 }
 }
 
 
-
-@builtin
-incl_elem :: proc(s: ^$S/bit_set[$E; $U], elem: E) {
-	s^ |= {elem}
-}
-@builtin
-incl_elems :: proc(s: ^$S/bit_set[$E; $U], elems: ..E) {
-	for elem in elems {
-		s^ |= {elem}
-	}
-}
-@builtin
-incl_bit_set :: proc(s: ^$S/bit_set[$E; $U], other: S) {
-	s^ |= other
-}
-@builtin
-excl_elem :: proc(s: ^$S/bit_set[$E; $U], elem: E) {
-	s^ &~= {elem}
-}
-@builtin
-excl_elems :: proc(s: ^$S/bit_set[$E; $U], elems: ..E) {
-	for elem in elems {
-		s^ &~= {elem}
+// Explicitly inserts a key and value into a map `m`, the same as `map_insert`, but the return values differ.
+// - `prev_key` will return the previous pointer of a key if it exists, check `found_previous` if was previously found
+// - `value_ptr` will return the pointer of the memory where the insertion happens, and `nil` if the map failed to resize
+// - `found_previous` will be true a previous key was found
+@(builtin, require_results)
+map_upsert :: proc(m: ^$T/map[$K]$V, key: K, value: V, loc := #caller_location) -> (prev_key: K, value_ptr: ^V, found_previous: bool) {
+	key, value := key, value
+	kp, vp := __dynamic_map_set_extra_without_hash((^Raw_Map)(m), map_info(T), rawptr(&key), rawptr(&value), loc)
+	if kp != nil {
+		prev_key = (^K)(kp)^
+		found_previous = true
 	}
 	}
+	value_ptr = (^V)(vp)
+	return
 }
 }
-@builtin
-excl_bit_set :: proc(s: ^$S/bit_set[$E; $U], other: S) {
-	s^ &~= other
-}
 
 
-@builtin incl :: proc{incl_elem, incl_elems, incl_bit_set}
-@builtin excl :: proc{excl_elem, excl_elems, excl_bit_set}
+/*
+Retrieves a pointer to the key and value for a possibly just inserted entry into the map.
+
+If the `key` was not in the map `m`, an entry is inserted with the zero value and `just_inserted` will be `true`.
+Otherwise the existing entry is left untouched and pointers to its key and value are returned.
+
+If the map has to grow in order to insert the entry and the allocation fails, `err` is set and returned.
+
+If `err` is `nil`, `key_ptr` and `value_ptr` are valid pointers and will not be `nil`.
+
+WARN: User modification of the key pointed at by `key_ptr` should only be done if the new key is equal to (in hash) the old key.
+If that is not the case you will corrupt the map.
+*/
+@(builtin, require_results)
+map_entry :: proc(m: ^$T/map[$K]$V, key: K, loc := #caller_location) -> (key_ptr: ^K, value_ptr: ^V, just_inserted: bool, err: Allocator_Error) {
+	key := key
+	zero: V
+
+	_key_ptr, _value_ptr: rawptr
+	_key_ptr, _value_ptr, just_inserted, err = __dynamic_map_entry((^Raw_Map)(m), map_info(T), &key, &zero, loc)
+
+	key_ptr   = (^K)(_key_ptr)
+	value_ptr = (^V)(_value_ptr)
+	return
+}
 
 
 
 
 @builtin
 @builtin
-card :: proc(s: $S/bit_set[$E; $U]) -> int {
-	when size_of(S) == 1 {
-		return int(intrinsics.count_ones(transmute(u8)s))
-	} else when size_of(S) == 2 {
-		return int(intrinsics.count_ones(transmute(u16)s))
-	} else when size_of(S) == 4 {
-		return int(intrinsics.count_ones(transmute(u32)s))
-	} else when size_of(S) == 8 {
-		return int(intrinsics.count_ones(transmute(u64)s))
-	} else when size_of(S) == 16 {
-		return int(intrinsics.count_ones(transmute(u128)s))
-	} else {
-		#panic("Unhandled card bit_set size")
-	}
+card :: proc "contextless" (s: $S/bit_set[$E; $U]) -> int {
+	return int(intrinsics.count_ones(transmute(intrinsics.type_bit_set_underlying_type(S))s))
 }
 }
 
 
 
 
 
 
 @builtin
 @builtin
 @(disabled=ODIN_DISABLE_ASSERT)
 @(disabled=ODIN_DISABLE_ASSERT)
-assert :: proc(condition: bool, message := "", loc := #caller_location) {
+assert :: proc(condition: bool, message := #caller_expression(condition), loc := #caller_location) {
 	if !condition {
 	if !condition {
 		// NOTE(bill): This is wrapped in a procedure call
 		// NOTE(bill): This is wrapped in a procedure call
 		// to improve performance to make the CPU not
 		// to improve performance to make the CPU not
@@ -816,6 +992,24 @@ assert :: proc(condition: bool, message := "", loc := #caller_location) {
 	}
 	}
 }
 }
 
 
+// Evaluates the condition and aborts the program iff the condition is
+// false.  This routine ignores `ODIN_DISABLE_ASSERT`, and will always
+// execute.
+@builtin
+ensure :: proc(condition: bool, message := #caller_expression(condition), loc := #caller_location) {
+	if !condition {
+		@(cold)
+		internal :: proc(message: string, loc: Source_Code_Location) {
+			p := context.assertion_failure_proc
+			if p == nil {
+				p = default_assertion_failure_proc
+			}
+			p("unsatisfied ensure", message, loc)
+		}
+		internal(message, loc)
+	}
+}
+
 @builtin
 @builtin
 panic :: proc(message: string, loc := #caller_location) -> ! {
 panic :: proc(message: string, loc := #caller_location) -> ! {
 	p := context.assertion_failure_proc
 	p := context.assertion_failure_proc
@@ -833,3 +1027,41 @@ unimplemented :: proc(message := "", loc := #caller_location) -> ! {
 	}
 	}
 	p("not yet implemented", message, loc)
 	p("not yet implemented", message, loc)
 }
 }
+
+
+@builtin
+@(disabled=ODIN_DISABLE_ASSERT)
+assert_contextless :: proc "contextless" (condition: bool, message := #caller_expression(condition), loc := #caller_location) {
+	if !condition {
+		// NOTE(bill): This is wrapped in a procedure call
+		// to improve performance to make the CPU not
+		// execute speculatively, making it about an order of
+		// magnitude faster
+		@(cold)
+		internal :: proc "contextless" (message: string, loc: Source_Code_Location) {
+			default_assertion_contextless_failure_proc("runtime assertion", message, loc)
+		}
+		internal(message, loc)
+	}
+}
+
+@builtin
+ensure_contextless :: proc "contextless" (condition: bool, message := #caller_expression(condition), loc := #caller_location) {
+	if !condition {
+		@(cold)
+		internal :: proc "contextless" (message: string, loc: Source_Code_Location) {
+			default_assertion_contextless_failure_proc("unsatisfied ensure", message, loc)
+		}
+		internal(message, loc)
+	}
+}
+
+@builtin
+panic_contextless :: proc "contextless" (message: string, loc := #caller_location) -> ! {
+	default_assertion_contextless_failure_proc("panic", message, loc)
+}
+
+@builtin
+unimplemented_contextless :: proc "contextless" (message := "", loc := #caller_location) -> ! {
+	default_assertion_contextless_failure_proc("not yet implemented", message, loc)
+}

+ 154 - 45
core/runtime/core_builtin_soa.odin → base/runtime/core_builtin_soa.odin

@@ -1,6 +1,6 @@
 package runtime
 package runtime
 
 
-import "core:intrinsics"
+import "base:intrinsics"
 _ :: intrinsics
 _ :: intrinsics
 
 
 /*
 /*
@@ -55,7 +55,7 @@ raw_soa_footer_slice :: proc(array: ^$T/#soa[]$E) -> (footer: ^Raw_SOA_Footer_Sl
 	if array == nil {
 	if array == nil {
 		return nil
 		return nil
 	}
 	}
-	field_count := uintptr(intrinsics.type_struct_field_count(E))
+	field_count := uintptr(len(E) when intrinsics.type_is_array(E) else intrinsics.type_struct_field_count(E))
 	footer = (^Raw_SOA_Footer_Slice)(uintptr(array) + field_count*size_of(rawptr))
 	footer = (^Raw_SOA_Footer_Slice)(uintptr(array) + field_count*size_of(rawptr))
 	return
 	return
 }
 }
@@ -64,12 +64,7 @@ raw_soa_footer_dynamic_array :: proc(array: ^$T/#soa[dynamic]$E) -> (footer: ^Ra
 	if array == nil {
 	if array == nil {
 		return nil
 		return nil
 	}
 	}
-	field_count: uintptr
-	when intrinsics.type_is_array(E) {
-		field_count = len(E)
-	} else {
-		field_count = uintptr(intrinsics.type_struct_field_count(E))
-	}
+	field_count := uintptr(len(E) when intrinsics.type_is_array(E) else intrinsics.type_struct_field_count(E))
 	footer = (^Raw_SOA_Footer_Dynamic_Array)(uintptr(array) + field_count*size_of(rawptr))
 	footer = (^Raw_SOA_Footer_Dynamic_Array)(uintptr(array) + field_count*size_of(rawptr))
 	return
 	return
 }
 }
@@ -81,7 +76,7 @@ raw_soa_footer :: proc{
 
 
 
 
 @(builtin, require_results)
 @(builtin, require_results)
-make_soa_aligned :: proc($T: typeid/#soa[]$E, length: int, alignment: int, allocator := context.allocator, loc := #caller_location) -> (array: T, err: Allocator_Error) #optional_allocator_error {
+make_soa_aligned :: proc($T: typeid/#soa[]$E, #any_int length, alignment: int, allocator := context.allocator, loc := #caller_location) -> (array: T, err: Allocator_Error) #optional_allocator_error {
 	if length <= 0 {
 	if length <= 0 {
 		return
 		return
 	}
 	}
@@ -98,11 +93,11 @@ make_soa_aligned :: proc($T: typeid/#soa[]$E, length: int, alignment: int, alloc
 	ti = type_info_base(ti)
 	ti = type_info_base(ti)
 	si := &ti.variant.(Type_Info_Struct)
 	si := &ti.variant.(Type_Info_Struct)
 
 
-	field_count := uintptr(intrinsics.type_struct_field_count(E))
+	field_count := uintptr(len(E) when intrinsics.type_is_array(E) else intrinsics.type_struct_field_count(E))
 
 
 	total_size := 0
 	total_size := 0
 	for i in 0..<field_count {
 	for i in 0..<field_count {
-		type := si.types[i].variant.(Type_Info_Pointer).elem
+		type := si.types[i].variant.(Type_Info_Multi_Pointer).elem
 		total_size += type.size * length
 		total_size += type.size * length
 		total_size = align_forward_int(total_size, max_align)
 		total_size = align_forward_int(total_size, max_align)
 	}
 	}
@@ -126,7 +121,7 @@ make_soa_aligned :: proc($T: typeid/#soa[]$E, length: int, alignment: int, alloc
 	data := uintptr(&array)
 	data := uintptr(&array)
 	offset := 0
 	offset := 0
 	for i in 0..<field_count {
 	for i in 0..<field_count {
-		type := si.types[i].variant.(Type_Info_Pointer).elem
+		type := si.types[i].variant.(Type_Info_Multi_Pointer).elem
 
 
 		offset = align_forward_int(offset, max_align)
 		offset = align_forward_int(offset, max_align)
 
 
@@ -140,20 +135,22 @@ make_soa_aligned :: proc($T: typeid/#soa[]$E, length: int, alignment: int, alloc
 }
 }
 
 
 @(builtin, require_results)
 @(builtin, require_results)
-make_soa_slice :: proc($T: typeid/#soa[]$E, length: int, allocator := context.allocator, loc := #caller_location) -> (array: T, err: Allocator_Error) #optional_allocator_error {
+make_soa_slice :: proc($T: typeid/#soa[]$E, #any_int length: int, allocator := context.allocator, loc := #caller_location) -> (array: T, err: Allocator_Error) #optional_allocator_error {
 	return make_soa_aligned(T, length, align_of(E), allocator, loc)
 	return make_soa_aligned(T, length, align_of(E), allocator, loc)
 }
 }
 
 
 @(builtin, require_results)
 @(builtin, require_results)
 make_soa_dynamic_array :: proc($T: typeid/#soa[dynamic]$E, allocator := context.allocator, loc := #caller_location) -> (array: T, err: Allocator_Error) #optional_allocator_error {
 make_soa_dynamic_array :: proc($T: typeid/#soa[dynamic]$E, allocator := context.allocator, loc := #caller_location) -> (array: T, err: Allocator_Error) #optional_allocator_error {
 	context.allocator = allocator
 	context.allocator = allocator
-	reserve_soa(&array, DEFAULT_RESERVE_CAPACITY, loc) or_return
+	array.allocator = allocator
+	reserve_soa(&array, 0, loc) or_return
 	return array, nil
 	return array, nil
 }
 }
 
 
 @(builtin, require_results)
 @(builtin, require_results)
 make_soa_dynamic_array_len :: proc($T: typeid/#soa[dynamic]$E, #any_int length: int, allocator := context.allocator, loc := #caller_location) -> (array: T, err: Allocator_Error) #optional_allocator_error {
 make_soa_dynamic_array_len :: proc($T: typeid/#soa[dynamic]$E, #any_int length: int, allocator := context.allocator, loc := #caller_location) -> (array: T, err: Allocator_Error) #optional_allocator_error {
 	context.allocator = allocator
 	context.allocator = allocator
+	array.allocator = allocator
 	resize_soa(&array, length, loc) or_return
 	resize_soa(&array, length, loc) or_return
 	return array, nil
 	return array, nil
 }
 }
@@ -177,7 +174,7 @@ make_soa :: proc{
 
 
 
 
 @builtin
 @builtin
-resize_soa :: proc(array: ^$T/#soa[dynamic]$E, length: int, loc := #caller_location) -> Allocator_Error {
+resize_soa :: proc(array: ^$T/#soa[dynamic]$E, #any_int length: int, loc := #caller_location) -> Allocator_Error {
 	if array == nil {
 	if array == nil {
 		return nil
 		return nil
 	}
 	}
@@ -188,7 +185,27 @@ resize_soa :: proc(array: ^$T/#soa[dynamic]$E, length: int, loc := #caller_locat
 }
 }
 
 
 @builtin
 @builtin
-reserve_soa :: proc(array: ^$T/#soa[dynamic]$E, capacity: int, loc := #caller_location) -> Allocator_Error {
+non_zero_resize_soa :: proc(array: ^$T/#soa[dynamic]$E, #any_int length: int, loc := #caller_location) -> Allocator_Error {
+	if array == nil {
+		return nil
+	}
+	non_zero_reserve_soa(array, length, loc) or_return
+	footer := raw_soa_footer(array)
+	footer.len = length
+	return nil
+}
+
+@builtin
+reserve_soa :: proc(array: ^$T/#soa[dynamic]$E, #any_int capacity: int, loc := #caller_location) -> Allocator_Error {
+	return _reserve_soa(array, capacity, true, loc)
+}
+
+@builtin
+non_zero_reserve_soa :: proc(array: ^$T/#soa[dynamic]$E, #any_int capacity: int, loc := #caller_location) -> Allocator_Error {
+	return _reserve_soa(array, capacity, false, loc)
+}
+
+_reserve_soa :: proc(array: ^$T/#soa[dynamic]$E, capacity: int, zero_memory: bool, loc := #caller_location) -> Allocator_Error {
 	if array == nil {
 	if array == nil {
 		return nil
 		return nil
 	}
 	}
@@ -213,12 +230,7 @@ reserve_soa :: proc(array: ^$T/#soa[dynamic]$E, capacity: int, loc := #caller_lo
 	ti = type_info_base(ti)
 	ti = type_info_base(ti)
 	si := &ti.variant.(Type_Info_Struct)
 	si := &ti.variant.(Type_Info_Struct)
 
 
-	field_count: uintptr
-	when intrinsics.type_is_array(E) {
-		field_count = len(E)
-	} else {
-		field_count = uintptr(intrinsics.type_struct_field_count(E))
-	}
+	field_count := uintptr(len(E) when intrinsics.type_is_array(E) else intrinsics.type_struct_field_count(E))
 	assert(footer.cap == old_cap)
 	assert(footer.cap == old_cap)
 
 
 	old_size := 0
 	old_size := 0
@@ -226,7 +238,7 @@ reserve_soa :: proc(array: ^$T/#soa[dynamic]$E, capacity: int, loc := #caller_lo
 
 
 	max_align :: align_of(E)
 	max_align :: align_of(E)
 	for i in 0..<field_count {
 	for i in 0..<field_count {
-		type := si.types[i].variant.(Type_Info_Pointer).elem
+		type := si.types[i].variant.(Type_Info_Multi_Pointer).elem
 
 
 		old_size += type.size * old_cap
 		old_size += type.size * old_cap
 		new_size += type.size * capacity
 		new_size += type.size * capacity
@@ -238,7 +250,7 @@ reserve_soa :: proc(array: ^$T/#soa[dynamic]$E, capacity: int, loc := #caller_lo
 	old_data := (^rawptr)(array)^
 	old_data := (^rawptr)(array)^
 
 
 	new_bytes := array.allocator.procedure(
 	new_bytes := array.allocator.procedure(
-		array.allocator.data, .Alloc, new_size, max_align,
+		array.allocator.data, .Alloc if zero_memory else .Alloc_Non_Zeroed, new_size, max_align,
 		nil, old_size, loc,
 		nil, old_size, loc,
 	) or_return
 	) or_return
 	new_data := raw_data(new_bytes)
 	new_data := raw_data(new_bytes)
@@ -249,7 +261,7 @@ reserve_soa :: proc(array: ^$T/#soa[dynamic]$E, capacity: int, loc := #caller_lo
 	old_offset := 0
 	old_offset := 0
 	new_offset := 0
 	new_offset := 0
 	for i in 0..<field_count {
 	for i in 0..<field_count {
-		type := si.types[i].variant.(Type_Info_Pointer).elem
+		type := si.types[i].variant.(Type_Info_Multi_Pointer).elem
 
 
 		old_offset = align_forward_int(old_offset, max_align)
 		old_offset = align_forward_int(old_offset, max_align)
 		new_offset = align_forward_int(new_offset, max_align)
 		new_offset = align_forward_int(new_offset, max_align)
@@ -273,29 +285,35 @@ reserve_soa :: proc(array: ^$T/#soa[dynamic]$E, capacity: int, loc := #caller_lo
 	return nil
 	return nil
 }
 }
 
 
+
+@builtin
+append_soa_elem :: proc(array: ^$T/#soa[dynamic]$E, #no_broadcast arg: E, loc := #caller_location) -> (n: int, err: Allocator_Error) #optional_allocator_error {
+	return _append_soa_elem(array, true, arg, loc)
+}
+
 @builtin
 @builtin
-append_soa_elem :: proc(array: ^$T/#soa[dynamic]$E, arg: E, loc := #caller_location) -> (n: int, err: Allocator_Error) #optional_allocator_error {
+non_zero_append_soa_elem :: proc(array: ^$T/#soa[dynamic]$E, #no_broadcast arg: E, loc := #caller_location) -> (n: int, err: Allocator_Error) #optional_allocator_error {
+	return _append_soa_elem(array, false, arg, loc)
+}
+
+_append_soa_elem :: proc(array: ^$T/#soa[dynamic]$E, zero_memory: bool, #no_broadcast arg: E, loc := #caller_location) -> (n: int, err: Allocator_Error) #optional_allocator_error {
 	if array == nil {
 	if array == nil {
 		return 0, nil
 		return 0, nil
 	}
 	}
 
 
 	if cap(array) <= len(array) + 1 {
 	if cap(array) <= len(array) + 1 {
-		cap := 2 * cap(array) + 8
-		err = reserve_soa(array, cap, loc) // do not 'or_return' here as it could be a partial success
+		// Same behavior as append_soa_elems but there's only one arg, so we always just add DEFAULT_DYNAMIC_ARRAY_CAPACITY.
+		cap := 2 * cap(array) + DEFAULT_DYNAMIC_ARRAY_CAPACITY
+		err = _reserve_soa(array, cap, zero_memory, loc) // do not 'or_return' here as it could be a partial success
 	}
 	}
 
 
 	footer := raw_soa_footer(array)
 	footer := raw_soa_footer(array)
 
 
 	if size_of(E) > 0 && cap(array)-len(array) > 0 {
 	if size_of(E) > 0 && cap(array)-len(array) > 0 {
-		ti := type_info_of(typeid_of(T))
+		ti := type_info_of(T)
 		ti = type_info_base(ti)
 		ti = type_info_base(ti)
 		si := &ti.variant.(Type_Info_Struct)
 		si := &ti.variant.(Type_Info_Struct)
-		field_count: uintptr
-		when intrinsics.type_is_array(E) {
-			field_count = len(E)
-		} else {
-			field_count = uintptr(intrinsics.type_struct_field_count(E))
-		}
+		field_count := uintptr(len(E) when intrinsics.type_is_array(E) else intrinsics.type_struct_field_count(E))
 
 
 		data := (^rawptr)(array)^
 		data := (^rawptr)(array)^
 
 
@@ -307,7 +325,7 @@ append_soa_elem :: proc(array: ^$T/#soa[dynamic]$E, arg: E, loc := #caller_locat
 
 
 		max_align :: align_of(E)
 		max_align :: align_of(E)
 		for i in 0..<field_count {
 		for i in 0..<field_count {
-			type := si.types[i].variant.(Type_Info_Pointer).elem
+			type := si.types[i].variant.(Type_Info_Multi_Pointer).elem
 
 
 			soa_offset  = align_forward_int(soa_offset, max_align)
 			soa_offset  = align_forward_int(soa_offset, max_align)
 			item_offset = align_forward_int(item_offset, type.align)
 			item_offset = align_forward_int(item_offset, type.align)
@@ -326,7 +344,17 @@ append_soa_elem :: proc(array: ^$T/#soa[dynamic]$E, arg: E, loc := #caller_locat
 }
 }
 
 
 @builtin
 @builtin
-append_soa_elems :: proc(array: ^$T/#soa[dynamic]$E, args: ..E, loc := #caller_location) -> (n: int, err: Allocator_Error) #optional_allocator_error {
+append_soa_elems :: proc(array: ^$T/#soa[dynamic]$E, #no_broadcast args: ..E, loc := #caller_location) -> (n: int, err: Allocator_Error) #optional_allocator_error {
+	return _append_soa_elems(array, true, args=args, loc=loc)
+}
+
+@builtin
+non_zero_append_soa_elems :: proc(array: ^$T/#soa[dynamic]$E, #no_broadcast args: ..E, loc := #caller_location) -> (n: int, err: Allocator_Error) #optional_allocator_error {
+	return _append_soa_elems(array, false, args=args, loc=loc)
+}
+
+
+_append_soa_elems :: proc(array: ^$T/#soa[dynamic]$E, zero_memory: bool, #no_broadcast args: []E, loc := #caller_location) -> (n: int, err: Allocator_Error) #optional_allocator_error {
 	if array == nil {
 	if array == nil {
 		return
 		return
 	}
 	}
@@ -337,8 +365,8 @@ append_soa_elems :: proc(array: ^$T/#soa[dynamic]$E, args: ..E, loc := #caller_l
 	}
 	}
 
 
 	if cap(array) <= len(array)+arg_len {
 	if cap(array) <= len(array)+arg_len {
-		cap := 2 * cap(array) + max(8, arg_len)
-		err = reserve_soa(array, cap, loc) // do not 'or_return' here as it could be a partial success
+		cap := 2 * cap(array) + max(DEFAULT_DYNAMIC_ARRAY_CAPACITY, arg_len)
+		err = _reserve_soa(array, cap, zero_memory, loc) // do not 'or_return' here as it could be a partial success
 	}
 	}
 	arg_len = min(cap(array)-len(array), arg_len)
 	arg_len = min(cap(array)-len(array), arg_len)
 
 
@@ -347,7 +375,7 @@ append_soa_elems :: proc(array: ^$T/#soa[dynamic]$E, args: ..E, loc := #caller_l
 		ti := type_info_of(typeid_of(T))
 		ti := type_info_of(typeid_of(T))
 		ti = type_info_base(ti)
 		ti = type_info_base(ti)
 		si := &ti.variant.(Type_Info_Struct)
 		si := &ti.variant.(Type_Info_Struct)
-		field_count := uintptr(intrinsics.type_struct_field_count(E))
+		field_count := uintptr(len(E) when intrinsics.type_is_array(E) else intrinsics.type_struct_field_count(E))
 
 
 		data := (^rawptr)(array)^
 		data := (^rawptr)(array)^
 
 
@@ -358,7 +386,7 @@ append_soa_elems :: proc(array: ^$T/#soa[dynamic]$E, args: ..E, loc := #caller_l
 
 
 		max_align :: align_of(E)
 		max_align :: align_of(E)
 		for i in 0..<field_count {
 		for i in 0..<field_count {
-			type := si.types[i].variant.(Type_Info_Pointer).elem
+			type := si.types[i].variant.(Type_Info_Multi_Pointer).elem
 
 
 			soa_offset  = align_forward_int(soa_offset, max_align)
 			soa_offset  = align_forward_int(soa_offset, max_align)
 			item_offset = align_forward_int(item_offset, type.align)
 			item_offset = align_forward_int(item_offset, type.align)
@@ -389,7 +417,8 @@ append_soa :: proc{
 
 
 
 
 delete_soa_slice :: proc(array: $T/#soa[]$E, allocator := context.allocator, loc := #caller_location) -> Allocator_Error {
 delete_soa_slice :: proc(array: $T/#soa[]$E, allocator := context.allocator, loc := #caller_location) -> Allocator_Error {
-	when intrinsics.type_struct_field_count(E) != 0 {
+	field_count :: len(E) when intrinsics.type_is_array(E) else intrinsics.type_struct_field_count(E)
+	when field_count != 0 {
 		array := array
 		array := array
 		ptr := (^rawptr)(&array)^
 		ptr := (^rawptr)(&array)^
 		free(ptr, allocator, loc) or_return
 		free(ptr, allocator, loc) or_return
@@ -398,7 +427,8 @@ delete_soa_slice :: proc(array: $T/#soa[]$E, allocator := context.allocator, loc
 }
 }
 
 
 delete_soa_dynamic_array :: proc(array: $T/#soa[dynamic]$E, loc := #caller_location) -> Allocator_Error {
 delete_soa_dynamic_array :: proc(array: $T/#soa[dynamic]$E, loc := #caller_location) -> Allocator_Error {
-	when intrinsics.type_struct_field_count(E) != 0 {
+	field_count :: len(E) when intrinsics.type_is_array(E) else intrinsics.type_struct_field_count(E)
+	when field_count != 0 {
 		array := array
 		array := array
 		ptr := (^rawptr)(&array)^
 		ptr := (^rawptr)(&array)^
 		footer := raw_soa_footer(&array)
 		footer := raw_soa_footer(&array)
@@ -416,7 +446,8 @@ delete_soa :: proc{
 
 
 
 
 clear_soa_dynamic_array :: proc(array: ^$T/#soa[dynamic]$E) {
 clear_soa_dynamic_array :: proc(array: ^$T/#soa[dynamic]$E) {
-	when intrinsics.type_struct_field_count(E) != 0 {
+	field_count :: len(E) when intrinsics.type_is_array(E) else intrinsics.type_struct_field_count(E)
+	when field_count != 0 {
 		footer := raw_soa_footer(array)
 		footer := raw_soa_footer(array)
 		footer.len = 0
 		footer.len = 0
 	}
 	}
@@ -425,4 +456,82 @@ clear_soa_dynamic_array :: proc(array: ^$T/#soa[dynamic]$E) {
 @builtin
 @builtin
 clear_soa :: proc{
 clear_soa :: proc{
 	clear_soa_dynamic_array,
 	clear_soa_dynamic_array,
-}
+}
+
+// Converts soa slice into a soa dynamic array without cloning or allocating memory
+@(require_results)
+into_dynamic_soa :: proc(array: $T/#soa[]$E) -> #soa[dynamic]E {
+	d: #soa[dynamic]E
+	footer := raw_soa_footer_dynamic_array(&d)
+	footer^ = {
+		cap = len(array),
+		len = 0,
+		allocator = nil_allocator(),
+	}
+
+	field_count := uintptr(len(E) when intrinsics.type_is_array(E) else intrinsics.type_struct_field_count(E))
+
+	array := array
+	dynamic_data := ([^]rawptr)(&d)[:field_count]
+	slice_data   := ([^]rawptr)(&array)[:field_count]
+	copy(dynamic_data, slice_data)
+
+	return d
+}
+
+// `unordered_remove_soa` removed the element at the specified `index`. It does so by replacing the current end value
+// with the old value, and reducing the length of the dynamic array by 1.
+//
+// Note: This is an O(1) operation.
+// Note: If you the elements to remain in their order, use `ordered_remove_soa`.
+// Note: If the index is out of bounds, this procedure will panic.
+@builtin
+unordered_remove_soa :: proc(array: ^$T/#soa[dynamic]$E, #any_int index: int, loc := #caller_location) #no_bounds_check {
+	bounds_check_error_loc(loc, index, len(array))
+	if index+1 < len(array) {
+		ti := type_info_of(typeid_of(T))
+		ti = type_info_base(ti)
+		si := &ti.variant.(Type_Info_Struct)
+
+		field_count := uintptr(len(E) when intrinsics.type_is_array(E) else intrinsics.type_struct_field_count(E))
+
+		data := uintptr(array)
+		for i in 0..<field_count {
+			type := si.types[i].variant.(Type_Info_Multi_Pointer).elem
+
+			offset := rawptr((^uintptr)(data)^ + uintptr(index*type.size))
+			final := rawptr((^uintptr)(data)^ + uintptr((len(array)-1)*type.size))
+			mem_copy(offset, final, type.size)
+			data += size_of(rawptr)
+		}
+	}
+	raw_soa_footer_dynamic_array(array).len -= 1
+}
+
+// `ordered_remove_soa` removed the element at the specified `index` whilst keeping the order of the other elements.
+//
+// Note: This is an O(N) operation.
+// Note: If you the elements do not have to remain in their order, prefer `unordered_remove_soa`.
+// Note: If the index is out of bounds, this procedure will panic.
+@builtin
+ordered_remove_soa :: proc(array: ^$T/#soa[dynamic]$E, #any_int index: int, loc := #caller_location) #no_bounds_check {
+	bounds_check_error_loc(loc, index, len(array))
+	if index+1 < len(array) {
+		ti := type_info_of(typeid_of(T))
+		ti = type_info_base(ti)
+		si := &ti.variant.(Type_Info_Struct)
+
+		field_count := uintptr(len(E) when intrinsics.type_is_array(E) else intrinsics.type_struct_field_count(E))
+
+		data := uintptr(array)
+		for i in 0..<field_count {
+			type := si.types[i].variant.(Type_Info_Multi_Pointer).elem
+
+			offset := (^uintptr)(data)^ + uintptr(index*type.size)
+			length := type.size*(len(array) - index - 1)
+			mem_copy(rawptr(offset), rawptr(offset + uintptr(type.size)), length)
+			data += size_of(rawptr)
+		}
+	}
+	raw_soa_footer_dynamic_array(array).len -= 1
+}

+ 15 - 0
base/runtime/default_allocators_general.odin

@@ -0,0 +1,15 @@
+package runtime
+
+when ODIN_DEFAULT_TO_NIL_ALLOCATOR {
+	default_allocator_proc :: nil_allocator_proc
+	default_allocator :: nil_allocator
+} else when ODIN_DEFAULT_TO_PANIC_ALLOCATOR {
+	default_allocator_proc :: panic_allocator_proc
+	default_allocator :: panic_allocator
+} else when ODIN_OS != .Orca && (ODIN_ARCH == .wasm32 || ODIN_ARCH == .wasm64p32) {
+	default_allocator :: default_wasm_allocator
+	default_allocator_proc :: wasm_allocator_proc
+} else {
+	default_allocator :: heap_allocator
+	default_allocator_proc :: heap_allocator_proc
+}

+ 8 - 14
core/runtime/default_allocators_nil.odin → base/runtime/default_allocators_nil.odin

@@ -1,8 +1,8 @@
 package runtime
 package runtime
 
 
 nil_allocator_proc :: proc(allocator_data: rawptr, mode: Allocator_Mode,
 nil_allocator_proc :: proc(allocator_data: rawptr, mode: Allocator_Mode,
-                               size, alignment: int,
-                               old_memory: rawptr, old_size: int, loc := #caller_location) -> ([]byte, Allocator_Error) {
+                           size, alignment: int,
+                           old_memory: rawptr, old_size: int, loc := #caller_location) -> ([]byte, Allocator_Error) {
 	switch mode {
 	switch mode {
 	case .Alloc, .Alloc_Non_Zeroed:
 	case .Alloc, .Alloc_Non_Zeroed:
 		return nil, .Out_Of_Memory
 		return nil, .Out_Of_Memory
@@ -10,7 +10,7 @@ nil_allocator_proc :: proc(allocator_data: rawptr, mode: Allocator_Mode,
 		return nil, .None
 		return nil, .None
 	case .Free_All:
 	case .Free_All:
 		return nil, .Mode_Not_Implemented
 		return nil, .Mode_Not_Implemented
-	case .Resize:
+	case .Resize, .Resize_Non_Zeroed:
 		if size == 0 {
 		if size == 0 {
 			return nil, .None
 			return nil, .None
 		}
 		}
@@ -31,14 +31,6 @@ nil_allocator :: proc() -> Allocator {
 }
 }
 
 
 
 
-
-when ODIN_OS == .Freestanding {
-	default_allocator_proc :: nil_allocator_proc
-	default_allocator :: nil_allocator
-} 
-
-
-
 panic_allocator_proc :: proc(allocator_data: rawptr, mode: Allocator_Mode,
 panic_allocator_proc :: proc(allocator_data: rawptr, mode: Allocator_Mode,
                              size, alignment: int,
                              size, alignment: int,
                              old_memory: rawptr, old_size: int, loc := #caller_location) -> ([]byte, Allocator_Error) {
                              old_memory: rawptr, old_size: int, loc := #caller_location) -> ([]byte, Allocator_Error) {
@@ -55,6 +47,10 @@ panic_allocator_proc :: proc(allocator_data: rawptr, mode: Allocator_Mode,
 		if size > 0 {
 		if size > 0 {
 			panic("panic allocator, .Resize called", loc=loc)
 			panic("panic allocator, .Resize called", loc=loc)
 		}
 		}
+	case .Resize_Non_Zeroed:
+		if size > 0 {
+			panic("panic allocator, .Alloc_Non_Zeroed called", loc=loc)
+		}
 	case .Free:
 	case .Free:
 		if old_memory != nil {
 		if old_memory != nil {
 			panic("panic allocator, .Free called", loc=loc)
 			panic("panic allocator, .Free called", loc=loc)
@@ -78,9 +74,7 @@ panic_allocator_proc :: proc(allocator_data: rawptr, mode: Allocator_Mode,
 
 
 panic_allocator :: proc() -> Allocator {
 panic_allocator :: proc() -> Allocator {
 	return Allocator{
 	return Allocator{
-		procedure = nil_allocator_proc,
+		procedure = panic_allocator_proc,
 		data = nil,
 		data = nil,
 	}
 	}
 }
 }
-
-

+ 15 - 13
core/runtime/default_allocators_arena.odin → base/runtime/default_temp_allocator_arena.odin

@@ -1,6 +1,6 @@
 package runtime
 package runtime
 
 
-import "core:intrinsics"
+import "base:intrinsics"
 
 
 DEFAULT_ARENA_GROWING_MINIMUM_BLOCK_SIZE :: uint(DEFAULT_TEMP_ALLOCATOR_BACKING_SIZE)
 DEFAULT_ARENA_GROWING_MINIMUM_BLOCK_SIZE :: uint(DEFAULT_TEMP_ALLOCATOR_BACKING_SIZE)
 
 
@@ -12,6 +12,8 @@ Memory_Block :: struct {
 	capacity:  uint,
 	capacity:  uint,
 }
 }
 
 
+// NOTE: This is a growing arena that is only used for the default temp allocator.
+// For your own growing arena needs, prefer `Arena` from `core:mem/virtual`.
 Arena :: struct {
 Arena :: struct {
 	backing_allocator:  Allocator,
 	backing_allocator:  Allocator,
 	curr_block:         ^Memory_Block,
 	curr_block:         ^Memory_Block,
@@ -28,11 +30,11 @@ safe_add :: #force_inline proc "contextless" (x, y: uint) -> (uint, bool) {
 }
 }
 
 
 @(require_results)
 @(require_results)
-memory_block_alloc :: proc(allocator: Allocator, capacity: uint, loc := #caller_location) -> (block: ^Memory_Block, err: Allocator_Error) {
-	total_size := uint(capacity + size_of(Memory_Block))
-	base_offset    := uintptr(size_of(Memory_Block))
+memory_block_alloc :: proc(allocator: Allocator, capacity: uint, alignment: uint, loc := #caller_location) -> (block: ^Memory_Block, err: Allocator_Error) {
+	total_size  := uint(capacity + max(alignment, size_of(Memory_Block)))
+	base_offset := uintptr(max(alignment, size_of(Memory_Block)))
 
 
-	min_alignment: int = max(16, align_of(Memory_Block))
+	min_alignment: int = max(16, align_of(Memory_Block), int(alignment))
 	data := mem_alloc(int(total_size), min_alignment, allocator, loc) or_return
 	data := mem_alloc(int(total_size), min_alignment, allocator, loc) or_return
 	block = (^Memory_Block)(raw_data(data))
 	block = (^Memory_Block)(raw_data(data))
 	end := uintptr(raw_data(data)[len(data):])
 	end := uintptr(raw_data(data)[len(data):])
@@ -102,20 +104,20 @@ arena_alloc :: proc(arena: ^Arena, size, alignment: uint, loc := #caller_locatio
 	if size == 0 {
 	if size == 0 {
 		return
 		return
 	}
 	}
-
-	if arena.curr_block == nil || (safe_add(arena.curr_block.used, size) or_else 0) > arena.curr_block.capacity {
-		size = align_forward_uint(size, alignment)
+	
+	needed := align_forward_uint(size, alignment)
+	if arena.curr_block == nil || (safe_add(arena.curr_block.used, needed) or_else 0) > arena.curr_block.capacity {
 		if arena.minimum_block_size == 0 {
 		if arena.minimum_block_size == 0 {
 			arena.minimum_block_size = DEFAULT_ARENA_GROWING_MINIMUM_BLOCK_SIZE
 			arena.minimum_block_size = DEFAULT_ARENA_GROWING_MINIMUM_BLOCK_SIZE
 		}
 		}
 
 
-		block_size := max(size, arena.minimum_block_size)
+		block_size := max(needed, arena.minimum_block_size)
 
 
 		if arena.backing_allocator.procedure == nil {
 		if arena.backing_allocator.procedure == nil {
 			arena.backing_allocator = default_allocator()
 			arena.backing_allocator = default_allocator()
 		}
 		}
 
 
-		new_block := memory_block_alloc(arena.backing_allocator, block_size, loc) or_return
+		new_block := memory_block_alloc(arena.backing_allocator, block_size, alignment, loc) or_return
 		new_block.prev = arena.curr_block
 		new_block.prev = arena.curr_block
 		arena.curr_block = new_block
 		arena.curr_block = new_block
 		arena.total_capacity += new_block.capacity
 		arena.total_capacity += new_block.capacity
@@ -127,14 +129,14 @@ arena_alloc :: proc(arena: ^Arena, size, alignment: uint, loc := #caller_locatio
 	return
 	return
 }
 }
 
 
-// `arena_init` will initialize the arena with a usuable block.
+// `arena_init` will initialize the arena with a usable block.
 // This procedure is not necessary to use the Arena as the default zero as `arena_alloc` will set things up if necessary
 // This procedure is not necessary to use the Arena as the default zero as `arena_alloc` will set things up if necessary
 @(require_results)
 @(require_results)
 arena_init :: proc(arena: ^Arena, size: uint, backing_allocator: Allocator, loc := #caller_location) -> Allocator_Error {
 arena_init :: proc(arena: ^Arena, size: uint, backing_allocator: Allocator, loc := #caller_location) -> Allocator_Error {
 	arena^ = {}
 	arena^ = {}
 	arena.backing_allocator = backing_allocator
 	arena.backing_allocator = backing_allocator
 	arena.minimum_block_size = max(size, 1<<12) // minimum block size of 4 KiB
 	arena.minimum_block_size = max(size, 1<<12) // minimum block size of 4 KiB
-	new_block := memory_block_alloc(arena.backing_allocator, arena.minimum_block_size, loc) or_return
+	new_block := memory_block_alloc(arena.backing_allocator, arena.minimum_block_size, 0, loc) or_return
 	arena.curr_block = new_block
 	arena.curr_block = new_block
 	arena.total_capacity += new_block.capacity
 	arena.total_capacity += new_block.capacity
 	return nil
 	return nil
@@ -195,7 +197,7 @@ arena_allocator_proc :: proc(allocator_data: rawptr, mode: Allocator_Mode,
 		err = .Mode_Not_Implemented
 		err = .Mode_Not_Implemented
 	case .Free_All:
 	case .Free_All:
 		arena_free_all(arena, location)
 		arena_free_all(arena, location)
-	case .Resize:
+	case .Resize, .Resize_Non_Zeroed:
 		old_data := ([^]byte)(old_memory)
 		old_data := ([^]byte)(old_memory)
 
 
 		switch {
 		switch {

+ 1 - 1
core/runtime/default_temporary_allocator.odin → base/runtime/default_temporary_allocator.odin

@@ -1,7 +1,7 @@
 package runtime
 package runtime
 
 
 DEFAULT_TEMP_ALLOCATOR_BACKING_SIZE: int : #config(DEFAULT_TEMP_ALLOCATOR_BACKING_SIZE, 4 * Megabyte)
 DEFAULT_TEMP_ALLOCATOR_BACKING_SIZE: int : #config(DEFAULT_TEMP_ALLOCATOR_BACKING_SIZE, 4 * Megabyte)
-NO_DEFAULT_TEMP_ALLOCATOR: bool : ODIN_OS == .Freestanding || ODIN_OS == .JS || ODIN_DEFAULT_TO_NIL_ALLOCATOR
+NO_DEFAULT_TEMP_ALLOCATOR: bool : ODIN_OS == .Freestanding || ODIN_DEFAULT_TO_NIL_ALLOCATOR
 
 
 when NO_DEFAULT_TEMP_ALLOCATOR {
 when NO_DEFAULT_TEMP_ALLOCATOR {
 	Default_Temp_Allocator :: struct {}
 	Default_Temp_Allocator :: struct {}

+ 4 - 3
core/runtime/docs.odin → base/runtime/docs.odin

@@ -44,7 +44,7 @@ memcpy
 memove
 memove
 
 
 
 
-## Procedures required by the LLVM backend
+## Procedures required by the LLVM backend if u128/i128 is used
 umodti3
 umodti3
 udivti3
 udivti3
 modti3
 modti3
@@ -59,11 +59,12 @@ truncdfhf2
 gnu_h2f_ieee
 gnu_h2f_ieee
 gnu_f2h_ieee
 gnu_f2h_ieee
 extendhfsf2
 extendhfsf2
+
+## Procedures required by the LLVM backend if f16 is used
 __ashlti3 // wasm specific
 __ashlti3 // wasm specific
 __multi3  // wasm specific
 __multi3  // wasm specific
 
 
 
 
-
 ## Required an entry point is defined (i.e. 'main')
 ## Required an entry point is defined (i.e. 'main')
 
 
 args__
 args__
@@ -156,7 +157,7 @@ __dynamic_map_get // dynamic map calls
 __dynamic_map_set // dynamic map calls
 __dynamic_map_set // dynamic map calls
 
 
 
 
-## Dynamic literals ([dymamic]T and map[K]V) (can be disabled with -no-dynamic-literals)
+## Dynamic literals ([dynamic]T and map[K]V) (can be disabled with -no-dynamic-literals)
 
 
 __dynamic_array_reserve
 __dynamic_array_reserve
 __dynamic_array_append
 __dynamic_array_append

+ 0 - 0
core/runtime/dynamic_array_internal.odin → base/runtime/dynamic_array_internal.odin


+ 112 - 22
core/runtime/dynamic_map_internal.odin → base/runtime/dynamic_map_internal.odin

@@ -1,6 +1,6 @@
 package runtime
 package runtime
 
 
-import "core:intrinsics"
+import "base:intrinsics"
 _ :: intrinsics
 _ :: intrinsics
 
 
 // High performance, cache-friendly, open-addressed Robin Hood hashing hash map
 // High performance, cache-friendly, open-addressed Robin Hood hashing hash map
@@ -44,7 +44,7 @@ _ :: intrinsics
 MAP_LOAD_FACTOR :: 75
 MAP_LOAD_FACTOR :: 75
 
 
 // Minimum log2 capacity.
 // Minimum log2 capacity.
-MAP_MIN_LOG2_CAPACITY :: 6 // 64 elements
+MAP_MIN_LOG2_CAPACITY :: 3 // 8 elements
 
 
 // Has to be less than 100% though.
 // Has to be less than 100% though.
 #assert(MAP_LOAD_FACTOR < 100)
 #assert(MAP_LOAD_FACTOR < 100)
@@ -158,21 +158,21 @@ map_cell_index_static :: #force_inline proc "contextless" (cells: [^]Map_Cell($T
 	} else when (N & (N - 1)) == 0 && N <= 8*size_of(uintptr) {
 	} else when (N & (N - 1)) == 0 && N <= 8*size_of(uintptr) {
 		// Likely case, N is a power of two because T is a power of two.
 		// Likely case, N is a power of two because T is a power of two.
 
 
+		// Unique case, no need to index data here since only one element.
+		when N == 1 {
+			return &cells[index].data[0]
+		}
+
 		// Compute the integer log 2 of N, this is the shift amount to index the
 		// Compute the integer log 2 of N, this is the shift amount to index the
 		// correct cell. Odin's intrinsics.count_leading_zeros does not produce a
 		// correct cell. Odin's intrinsics.count_leading_zeros does not produce a
 		// constant, hence this approach. We only need to check up to N = 64.
 		// constant, hence this approach. We only need to check up to N = 64.
-		SHIFT :: 1 when N < 2  else
-		         2 when N < 4  else
-		         3 when N < 8  else
-		         4 when N < 16 else
-		         5 when N < 32 else 6
+		SHIFT :: 1 when N == 2  else
+		         2 when N == 4  else
+		         3 when N == 8  else
+		         4 when N == 16 else
+		         5 when N == 32 else 6
 		#assert(SHIFT <= MAP_CACHE_LINE_LOG2)
 		#assert(SHIFT <= MAP_CACHE_LINE_LOG2)
-		// Unique case, no need to index data here since only one element.
-		when N == 1 {
-			return &cells[index >> SHIFT].data[0]
-		} else {
-			return &cells[index >> SHIFT].data[index & (N - 1)]
-		}
+		return &cells[index >> SHIFT].data[index & (N - 1)]
 	} else {
 	} else {
 		// Least likely (and worst case), we pay for a division operation but we
 		// Least likely (and worst case), we pay for a division operation but we
 		// assume the compiler does not actually generate a division. N will be in the
 		// assume the compiler does not actually generate a division. N will be in the
@@ -333,7 +333,7 @@ map_kvh_data_values_dynamic :: proc "contextless" (m: Raw_Map, #no_alias info: ^
 }
 }
 
 
 
 
-@(private, require_results)
+@(require_results)
 map_total_allocation_size :: #force_inline proc "contextless" (capacity: uintptr, info: ^Map_Info) -> uintptr {
 map_total_allocation_size :: #force_inline proc "contextless" (capacity: uintptr, info: ^Map_Info) -> uintptr {
 	round :: #force_inline proc "contextless" (value: uintptr) -> uintptr {
 	round :: #force_inline proc "contextless" (value: uintptr) -> uintptr {
 		CACHE_MASK :: MAP_CACHE_LINE_SIZE - 1
 		CACHE_MASK :: MAP_CACHE_LINE_SIZE - 1
@@ -350,6 +350,12 @@ map_total_allocation_size :: #force_inline proc "contextless" (capacity: uintptr
 	return size
 	return size
 }
 }
 
 
+@(require_results)
+map_total_allocation_size_from_value :: #force_inline proc "contextless" (m: $M/map[$K]$V) -> uintptr {
+	return map_total_allocation_size(uintptr(cap(m)), map_info(M))
+}
+
+
 // The only procedure which needs access to the context is the one which allocates the map.
 // The only procedure which needs access to the context is the one which allocates the map.
 @(require_results)
 @(require_results)
 map_alloc_dynamic :: proc "odin" (info: ^Map_Info, log2_capacity: uintptr, allocator := context.allocator, loc := #caller_location) -> (result: Raw_Map, err: Allocator_Error) {
 map_alloc_dynamic :: proc "odin" (info: ^Map_Info, log2_capacity: uintptr, allocator := context.allocator, loc := #caller_location) -> (result: Raw_Map, err: Allocator_Error) {
@@ -391,7 +397,8 @@ map_alloc_dynamic :: proc "odin" (info: ^Map_Info, log2_capacity: uintptr, alloc
 // arrays to reduce variance. This swapping can only be done with memcpy since
 // arrays to reduce variance. This swapping can only be done with memcpy since
 // there is no type information.
 // there is no type information.
 //
 //
-// This procedure returns the address of the just inserted value.
+// This procedure returns the address of the just inserted value, and will
+// return 'nil' if there was no room to insert the entry
 @(require_results)
 @(require_results)
 map_insert_hash_dynamic :: proc "odin" (#no_alias m: ^Raw_Map, #no_alias info: ^Map_Info, h: Map_Hash, ik: uintptr, iv: uintptr) -> (result: uintptr) {
 map_insert_hash_dynamic :: proc "odin" (#no_alias m: ^Raw_Map, #no_alias info: ^Map_Info, h: Map_Hash, ik: uintptr, iv: uintptr) -> (result: uintptr) {
 	h        := h
 	h        := h
@@ -415,6 +422,11 @@ map_insert_hash_dynamic :: proc "odin" (#no_alias m: ^Raw_Map, #no_alias info: ^
 	tv := map_cell_index_dynamic(sv, info.vs, 1)
 	tv := map_cell_index_dynamic(sv, info.vs, 1)
 
 
 	swap_loop: for {
 	swap_loop: for {
+		if distance > mask {
+			// Failed to find an empty slot and prevent infinite loop
+			panic("unable to insert into a map")
+		}
+
 		element_hash := hs[pos]
 		element_hash := hs[pos]
 
 
 		if map_hash_is_empty(element_hash) {
 		if map_hash_is_empty(element_hash) {
@@ -565,7 +577,7 @@ map_grow_dynamic :: proc "odin" (#no_alias m: ^Raw_Map, #no_alias info: ^Map_Inf
 
 
 
 
 @(require_results)
 @(require_results)
-map_reserve_dynamic :: proc "odin" (#no_alias m: ^Raw_Map, #no_alias info: ^Map_Info, new_capacity: uintptr, loc := #caller_location) -> Allocator_Error {
+map_reserve_dynamic :: #force_no_inline proc "odin" (#no_alias m: ^Raw_Map, #no_alias info: ^Map_Info, new_capacity: uintptr, loc := #caller_location) -> Allocator_Error {
 	@(require_results)
 	@(require_results)
 	ceil_log2 :: #force_inline proc "contextless" (x: uintptr) -> uintptr {
 	ceil_log2 :: #force_inline proc "contextless" (x: uintptr) -> uintptr {
 		z := intrinsics.count_leading_zeros(x)
 		z := intrinsics.count_leading_zeros(x)
@@ -629,7 +641,7 @@ map_reserve_dynamic :: proc "odin" (#no_alias m: ^Raw_Map, #no_alias info: ^Map_
 
 
 
 
 @(require_results)
 @(require_results)
-map_shrink_dynamic :: proc "odin" (#no_alias m: ^Raw_Map, #no_alias info: ^Map_Info, loc := #caller_location) -> (did_shrink: bool, err: Allocator_Error) {
+map_shrink_dynamic :: #force_no_inline proc "odin" (#no_alias m: ^Raw_Map, #no_alias info: ^Map_Info, loc := #caller_location) -> (did_shrink: bool, err: Allocator_Error) {
 	if m.allocator.procedure == nil {
 	if m.allocator.procedure == nil {
 		m.allocator = context.allocator
 		m.allocator = context.allocator
 	}
 	}
@@ -676,7 +688,7 @@ map_shrink_dynamic :: proc "odin" (#no_alias m: ^Raw_Map, #no_alias info: ^Map_I
 }
 }
 
 
 @(require_results)
 @(require_results)
-map_free_dynamic :: proc "odin" (m: Raw_Map, info: ^Map_Info, loc := #caller_location) -> Allocator_Error {
+map_free_dynamic :: #force_no_inline proc "odin" (m: Raw_Map, info: ^Map_Info, loc := #caller_location) -> Allocator_Error {
 	ptr := rawptr(map_data(m))
 	ptr := rawptr(map_data(m))
 	size := int(map_total_allocation_size(uintptr(map_cap(m)), info))
 	size := int(map_total_allocation_size(uintptr(map_cap(m)), info))
 	err := mem_free_with_size(ptr, size, m.allocator, loc)
 	err := mem_free_with_size(ptr, size, m.allocator, loc)
@@ -688,7 +700,7 @@ map_free_dynamic :: proc "odin" (m: Raw_Map, info: ^Map_Info, loc := #caller_loc
 }
 }
 
 
 @(require_results)
 @(require_results)
-map_lookup_dynamic :: proc "contextless" (m: Raw_Map, #no_alias info: ^Map_Info, k: uintptr) -> (index: uintptr, ok: bool) {
+map_lookup_dynamic :: #force_no_inline proc "contextless" (m: Raw_Map, #no_alias info: ^Map_Info, k: uintptr) -> (index: uintptr, ok: bool) {
 	if map_len(m) == 0 {
 	if map_len(m) == 0 {
 		return 0, false
 		return 0, false
 	}
 	}
@@ -711,7 +723,7 @@ map_lookup_dynamic :: proc "contextless" (m: Raw_Map, #no_alias info: ^Map_Info,
 	}
 	}
 }
 }
 @(require_results)
 @(require_results)
-map_exists_dynamic :: proc "contextless" (m: Raw_Map, #no_alias info: ^Map_Info, k: uintptr) -> (ok: bool) {
+map_exists_dynamic :: #force_no_inline proc "contextless" (m: Raw_Map, #no_alias info: ^Map_Info, k: uintptr) -> (ok: bool) {
 	if map_len(m) == 0 {
 	if map_len(m) == 0 {
 		return false
 		return false
 	}
 	}
@@ -737,7 +749,7 @@ map_exists_dynamic :: proc "contextless" (m: Raw_Map, #no_alias info: ^Map_Info,
 
 
 
 
 @(require_results)
 @(require_results)
-map_erase_dynamic :: #force_inline proc "contextless" (#no_alias m: ^Raw_Map, #no_alias info: ^Map_Info, k: uintptr) -> (old_k, old_v: uintptr, ok: bool) {
+map_erase_dynamic :: #force_no_inline proc "contextless" (#no_alias m: ^Raw_Map, #no_alias info: ^Map_Info, k: uintptr) -> (old_k, old_v: uintptr, ok: bool) {
 	index := map_lookup_dynamic(m^, info, k) or_return
 	index := map_lookup_dynamic(m^, info, k) or_return
 	ks, vs, hs, _, _ := map_kvh_data_dynamic(m^, info)
 	ks, vs, hs, _, _ := map_kvh_data_dynamic(m^, info)
 	hs[index] |= TOMBSTONE_MASK
 	hs[index] |= TOMBSTONE_MASK
@@ -841,6 +853,33 @@ __dynamic_map_get :: proc "contextless" (#no_alias m: ^Raw_Map, #no_alias info:
 	}
 	}
 }
 }
 
 
+__dynamic_map_get_key_and_value :: proc "contextless" (#no_alias m: ^Raw_Map, #no_alias info: ^Map_Info, h: Map_Hash, key: rawptr) -> (key_ptr, value_ptr: rawptr) {
+	if m.len == 0 {
+		return nil, nil
+	}
+	pos := map_desired_position(m^, h)
+	distance := uintptr(0)
+	mask := (uintptr(1) << map_log2_cap(m^)) - 1
+	ks, vs, hs, _, _ := map_kvh_data_dynamic(m^, info)
+	for {
+		element_hash := hs[pos]
+		if map_hash_is_empty(element_hash) {
+			return nil, nil
+		} else if distance > map_probe_distance(m^, element_hash, pos) {
+			return nil, nil
+		} else if element_hash == h {
+			other_key := rawptr(map_cell_index_dynamic(ks, info.ks, pos))
+			if info.key_equal(key, other_key) {
+				key_ptr   = other_key
+				value_ptr = rawptr(map_cell_index_dynamic(vs, info.vs, pos))
+				return
+			}
+		}
+		pos = (pos + 1) & mask
+		distance += 1
+	}
+}
+
 // IMPORTANT: USED WITHIN THE COMPILER
 // IMPORTANT: USED WITHIN THE COMPILER
 __dynamic_map_check_grow :: proc "odin" (#no_alias m: ^Raw_Map, #no_alias info: ^Map_Info, loc := #caller_location) -> (err: Allocator_Error, has_grown: bool) {
 __dynamic_map_check_grow :: proc "odin" (#no_alias m: ^Raw_Map, #no_alias info: ^Map_Info, loc := #caller_location) -> (err: Allocator_Error, has_grown: bool) {
 	if m.len >= map_resize_threshold(m^) {
 	if m.len >= map_resize_threshold(m^) {
@@ -871,9 +910,60 @@ __dynamic_map_set :: proc "odin" (#no_alias m: ^Raw_Map, #no_alias info: ^Map_In
 	}
 	}
 
 
 	result := map_insert_hash_dynamic(m, info, hash, uintptr(key), uintptr(value))
 	result := map_insert_hash_dynamic(m, info, hash, uintptr(key), uintptr(value))
-	m.len += 1
+	if result != 0 {
+		m.len += 1
+	}
 	return rawptr(result)
 	return rawptr(result)
 }
 }
+__dynamic_map_set_extra_without_hash :: proc "odin" (#no_alias m: ^Raw_Map, #no_alias info: ^Map_Info, key, value: rawptr, loc := #caller_location) -> (prev_key_ptr, value_ptr: rawptr) {
+	return __dynamic_map_set_extra(m, info, info.key_hasher(key, map_seed(m^)), key, value, loc)
+}
+
+__dynamic_map_set_extra :: proc "odin" (#no_alias m: ^Raw_Map, #no_alias info: ^Map_Info, hash: Map_Hash, key, value: rawptr, loc := #caller_location) -> (prev_key_ptr, value_ptr: rawptr) {
+	if prev_key_ptr, value_ptr = __dynamic_map_get_key_and_value(m, info, hash, key); value_ptr != nil {
+		intrinsics.mem_copy_non_overlapping(value_ptr, value, info.vs.size_of_type)
+		return
+	}
+
+	hash := hash
+	err, has_grown := __dynamic_map_check_grow(m, info, loc)
+	if err != nil {
+		return nil, nil
+	}
+	if has_grown {
+		hash = info.key_hasher(key, map_seed(m^))
+	}
+
+	result := map_insert_hash_dynamic(m, info, hash, uintptr(key), uintptr(value))
+	if result != 0 {
+		m.len += 1
+	}
+	return nil, rawptr(result)
+}
+
+__dynamic_map_entry :: proc "odin" (#no_alias m: ^Raw_Map, #no_alias info: ^Map_Info, key: rawptr, zero: rawptr, loc := #caller_location) -> (key_ptr: rawptr, value_ptr: rawptr, just_inserted: bool, err: Allocator_Error) {
+	hash := info.key_hasher(key, map_seed(m^))
+
+	if key_ptr, value_ptr = __dynamic_map_get_key_and_value(m, info, hash, key); value_ptr != nil {
+		return
+	}
+
+	has_grown: bool
+	if err, has_grown = __dynamic_map_check_grow(m, info, loc); err != nil {
+		return
+	} else if has_grown {
+		hash = info.key_hasher(key, map_seed(m^))
+	}
+
+	value_ptr = rawptr(map_insert_hash_dynamic(m, info, hash, uintptr(key), uintptr(zero)))
+	assert(value_ptr != nil)
+	key_ptr = rawptr(map_cell_index_dynamic(map_data(m^), info.ks, map_desired_position(m^, hash)))
+
+	m.len += 1
+	just_inserted = true
+	return
+}
+
 
 
 // IMPORTANT: USED WITHIN THE COMPILER
 // IMPORTANT: USED WITHIN THE COMPILER
 @(private)
 @(private)

+ 13 - 8
core/runtime/entry_unix.odin → base/runtime/entry_unix.odin

@@ -1,8 +1,9 @@
-//+private
-//+build linux, darwin, freebsd, openbsd
+#+private
+#+build linux, darwin, freebsd, openbsd, netbsd, haiku
+#+no-instrumentation
 package runtime
 package runtime
 
 
-import "core:intrinsics"
+import "base:intrinsics"
 
 
 when ODIN_BUILD_MODE == .Dynamic {
 when ODIN_BUILD_MODE == .Dynamic {
 	@(link_name="_odin_entry_point", linkage="strong", require/*, link_section=".init"*/)
 	@(link_name="_odin_entry_point", linkage="strong", require/*, link_section=".init"*/)
@@ -26,8 +27,16 @@ when ODIN_BUILD_MODE == .Dynamic {
 		// to retrieve argc and argv from the stack
 		// to retrieve argc and argv from the stack
 		when ODIN_ARCH == .amd64 {
 		when ODIN_ARCH == .amd64 {
 			@require foreign import entry "entry_unix_no_crt_amd64.asm"
 			@require foreign import entry "entry_unix_no_crt_amd64.asm"
+			SYS_exit :: 60
 		} else when ODIN_ARCH == .i386 {
 		} else when ODIN_ARCH == .i386 {
 			@require foreign import entry "entry_unix_no_crt_i386.asm"
 			@require foreign import entry "entry_unix_no_crt_i386.asm"
+			SYS_exit :: 1
+		} else when ODIN_OS == .Darwin && ODIN_ARCH == .arm64 {
+			@require foreign import entry "entry_unix_no_crt_darwin_arm64.asm"
+			SYS_exit :: 1
+		} else when ODIN_ARCH == .riscv64 {
+			@require foreign import entry "entry_unix_no_crt_riscv64.asm"
+			SYS_exit :: 93
 		}
 		}
 		@(link_name="_start_odin", linkage="strong", require)
 		@(link_name="_start_odin", linkage="strong", require)
 		_start_odin :: proc "c" (argc: i32, argv: [^]cstring) -> ! {
 		_start_odin :: proc "c" (argc: i32, argv: [^]cstring) -> ! {
@@ -36,11 +45,7 @@ when ODIN_BUILD_MODE == .Dynamic {
 			#force_no_inline _startup_runtime()
 			#force_no_inline _startup_runtime()
 			intrinsics.__entry_point()
 			intrinsics.__entry_point()
 			#force_no_inline _cleanup_runtime()
 			#force_no_inline _cleanup_runtime()
-			when ODIN_ARCH == .amd64 {
-				intrinsics.syscall(/*SYS_exit = */60)
-			} else when ODIN_ARCH == .i386 {
-				intrinsics.syscall(/*SYS_exit = */1)
-			}
+			intrinsics.syscall(SYS_exit, 0)
 			unreachable()
 			unreachable()
 		}
 		}
 	} else {
 	} else {

+ 0 - 0
core/runtime/entry_unix_no_crt_amd64.asm → base/runtime/entry_unix_no_crt_amd64.asm


+ 20 - 0
base/runtime/entry_unix_no_crt_darwin_arm64.asm

@@ -0,0 +1,20 @@
+	.section __TEXT,__text
+
+	; NOTE(laytan): this should ideally be the -minimum-os-version flag but there is no nice way of preprocessing assembly in Odin.
+	; 10 seems to be the lowest it goes and I don't see it mess with any targeted os version so this seems fine.
+	.build_version macos, 10, 0
+
+	.extern __start_odin
+
+	.global _main
+	.align 2
+_main:
+	mov x5, sp       ; use x5 as the stack pointer
+
+	str x0, [x5]     ; get argc into x0 (kernel passes 32-bit int argc as 64-bits on stack to keep alignment)
+	str x1, [x5, #8] ; get argv into x1
+
+	and sp, x5, #~15 ; force 16-byte alignment of the stack
+	
+	bl __start_odin  ; call into Odin entry point
+	ret              ; should never get here

+ 0 - 0
core/runtime/entry_unix_no_crt_i386.asm → base/runtime/entry_unix_no_crt_i386.asm


+ 10 - 0
base/runtime/entry_unix_no_crt_riscv64.asm

@@ -0,0 +1,10 @@
+.text
+
+.globl _start
+
+_start:
+	ld a0, 0(sp)
+	addi a1, sp, 8
+	addi sp, sp, ~15
+	call _start_odin
+	ebreak

+ 39 - 0
base/runtime/entry_wasm.odin

@@ -0,0 +1,39 @@
+#+private
+#+build wasm32, wasm64p32
+#+no-instrumentation
+package runtime
+
+import "base:intrinsics"
+
+when !ODIN_TEST && !ODIN_NO_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()
+
+			when ODIN_OS == .WASI {
+				_wasi_setup_args()
+			}
+
+			#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()
+		}
+	}
+}

+ 8 - 6
core/runtime/entry_windows.odin → base/runtime/entry_windows.odin

@@ -1,16 +1,18 @@
-//+private
-//+build windows
+#+private
+#+build windows
+#+no-instrumentation
 package runtime
 package runtime
 
 
-import "core:intrinsics"
+import "base:intrinsics"
 
 
 when ODIN_BUILD_MODE == .Dynamic {
 when ODIN_BUILD_MODE == .Dynamic {
 	@(link_name="DllMain", linkage="strong", require)
 	@(link_name="DllMain", linkage="strong", require)
-	DllMain :: proc "stdcall" (hinstDLL: rawptr, fdwReason: u32, lpReserved: rawptr) -> b32 {
+	DllMain :: proc "system" (hinstDLL: rawptr, fdwReason: u32, lpReserved: rawptr) -> b32 {
 		context = default_context()
 		context = default_context()
 
 
-		// Populate Windows DLL-specific global
+		// Populate Windows DLL-specific globals
 		dll_forward_reason = DLL_Forward_Reason(fdwReason)
 		dll_forward_reason = DLL_Forward_Reason(fdwReason)
+		dll_instance       = hinstDLL
 
 
 		switch dll_forward_reason {
 		switch dll_forward_reason {
 		case .Process_Attach:
 		case .Process_Attach:
@@ -28,7 +30,7 @@ when ODIN_BUILD_MODE == .Dynamic {
 } else when !ODIN_TEST && !ODIN_NO_ENTRY_POINT {
 } else when !ODIN_TEST && !ODIN_NO_ENTRY_POINT {
 	when ODIN_ARCH == .i386 || ODIN_NO_CRT {
 	when ODIN_ARCH == .i386 || ODIN_NO_CRT {
 		@(link_name="mainCRTStartup", linkage="strong", require)
 		@(link_name="mainCRTStartup", linkage="strong", require)
-		mainCRTStartup :: proc "stdcall" () -> i32 {
+		mainCRTStartup :: proc "system" () -> i32 {
 			context = default_context()
 			context = default_context()
 			#force_no_inline _startup_runtime()
 			#force_no_inline _startup_runtime()
 			intrinsics.__entry_point()
 			intrinsics.__entry_point()

+ 31 - 11
core/runtime/error_checks.odin → base/runtime/error_checks.odin

@@ -1,27 +1,34 @@
 package runtime
 package runtime
 
 
+@(no_instrumentation)
 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()
 	}
 	}
 }
 }
 
 
+@(no_instrumentation)
 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()
 	}
 	}
 }
 }
 
 
 
 
+@(disabled=ODIN_NO_BOUNDS_CHECK)
 bounds_check_error :: proc "contextless" (file: string, line, column: i32, index, count: int) {
 bounds_check_error :: proc "contextless" (file: string, line, column: i32, index, count: int) {
 	if uint(index) < uint(count) {
 	if uint(index) < uint(count) {
 		return
 		return
 	}
 	}
-	@(cold)
+	@(cold, no_instrumentation)
 	handle_error :: proc "contextless" (file: string, line, column: i32, index, count: int) -> ! {
 	handle_error :: proc "contextless" (file: string, line, column: i32, index, count: int) -> ! {
 		print_caller_location(Source_Code_Location{file, line, column, ""})
 		print_caller_location(Source_Code_Location{file, line, column, ""})
 		print_string(" Index ")
 		print_string(" Index ")
@@ -34,6 +41,7 @@ bounds_check_error :: proc "contextless" (file: string, line, column: i32, index
 	handle_error(file, line, column, index, count)
 	handle_error(file, line, column, index, count)
 }
 }
 
 
+@(no_instrumentation)
 slice_handle_error :: proc "contextless" (file: string, line, column: i32, lo, hi: int, len: int) -> ! {
 slice_handle_error :: proc "contextless" (file: string, line, column: i32, lo, hi: int, len: int) -> ! {
 	print_caller_location(Source_Code_Location{file, line, column, ""})
 	print_caller_location(Source_Code_Location{file, line, column, ""})
 	print_string(" Invalid slice indices ")
 	print_string(" Invalid slice indices ")
@@ -46,6 +54,7 @@ slice_handle_error :: proc "contextless" (file: string, line, column: i32, lo, h
 	bounds_trap()
 	bounds_trap()
 }
 }
 
 
+@(no_instrumentation)
 multi_pointer_slice_handle_error :: proc "contextless" (file: string, line, column: i32, lo, hi: int) -> ! {
 multi_pointer_slice_handle_error :: proc "contextless" (file: string, line, column: i32, lo, hi: int) -> ! {
 	print_caller_location(Source_Code_Location{file, line, column, ""})
 	print_caller_location(Source_Code_Location{file, line, column, ""})
 	print_string(" Invalid slice indices ")
 	print_string(" Invalid slice indices ")
@@ -57,6 +66,7 @@ multi_pointer_slice_handle_error :: proc "contextless" (file: string, line, colu
 }
 }
 
 
 
 
+@(disabled=ODIN_NO_BOUNDS_CHECK)
 multi_pointer_slice_expr_error :: proc "contextless" (file: string, line, column: i32, lo, hi: int) {
 multi_pointer_slice_expr_error :: proc "contextless" (file: string, line, column: i32, lo, hi: int) {
 	if lo <= hi {
 	if lo <= hi {
 		return
 		return
@@ -64,6 +74,7 @@ multi_pointer_slice_expr_error :: proc "contextless" (file: string, line, column
 	multi_pointer_slice_handle_error(file, line, column, lo, hi)
 	multi_pointer_slice_handle_error(file, line, column, lo, hi)
 }
 }
 
 
+@(disabled=ODIN_NO_BOUNDS_CHECK)
 slice_expr_error_hi :: proc "contextless" (file: string, line, column: i32, hi: int, len: int) {
 slice_expr_error_hi :: proc "contextless" (file: string, line, column: i32, hi: int, len: int) {
 	if 0 <= hi && hi <= len {
 	if 0 <= hi && hi <= len {
 		return
 		return
@@ -71,6 +82,7 @@ slice_expr_error_hi :: proc "contextless" (file: string, line, column: i32, hi:
 	slice_handle_error(file, line, column, 0, hi, len)
 	slice_handle_error(file, line, column, 0, hi, len)
 }
 }
 
 
+@(disabled=ODIN_NO_BOUNDS_CHECK)
 slice_expr_error_lo_hi :: proc "contextless" (file: string, line, column: i32, lo, hi: int, len: int) {
 slice_expr_error_lo_hi :: proc "contextless" (file: string, line, column: i32, lo, hi: int, len: int) {
 	if 0 <= lo && lo <= len && lo <= hi && hi <= len {
 	if 0 <= lo && lo <= len && lo <= hi && hi <= len {
 		return
 		return
@@ -78,11 +90,12 @@ slice_expr_error_lo_hi :: proc "contextless" (file: string, line, column: i32, l
 	slice_handle_error(file, line, column, lo, hi, len)
 	slice_handle_error(file, line, column, lo, hi, len)
 }
 }
 
 
+@(disabled=ODIN_NO_BOUNDS_CHECK)
 dynamic_array_expr_error :: proc "contextless" (file: string, line, column: i32, low, high, max: int) {
 dynamic_array_expr_error :: proc "contextless" (file: string, line, column: i32, low, high, max: int) {
 	if 0 <= low && low <= high && high <= max {
 	if 0 <= low && low <= high && high <= max {
 		return
 		return
 	}
 	}
-	@(cold)
+	@(cold, no_instrumentation)
 	handle_error :: proc "contextless" (file: string, line, column: i32, low, high, max: int) -> ! {
 	handle_error :: proc "contextless" (file: string, line, column: i32, low, high, max: int) -> ! {
 		print_caller_location(Source_Code_Location{file, line, column, ""})
 		print_caller_location(Source_Code_Location{file, line, column, ""})
 		print_string(" Invalid dynamic array indices ")
 		print_string(" Invalid dynamic array indices ")
@@ -98,12 +111,13 @@ dynamic_array_expr_error :: proc "contextless" (file: string, line, column: i32,
 }
 }
 
 
 
 
+@(disabled=ODIN_NO_BOUNDS_CHECK)
 matrix_bounds_check_error :: proc "contextless" (file: string, line, column: i32, row_index, column_index, row_count, column_count: int) {
 matrix_bounds_check_error :: proc "contextless" (file: string, line, column: i32, row_index, column_index, row_count, column_count: int) {
 	if uint(row_index) < uint(row_count) &&
 	if uint(row_index) < uint(row_count) &&
 	   uint(column_index) < uint(column_count) {
 	   uint(column_index) < uint(column_count) {
 		return
 		return
 	}
 	}
-	@(cold)
+	@(cold, no_instrumentation)
 	handle_error :: proc "contextless" (file: string, line, column: i32, row_index, column_index, row_count, column_count: int) -> ! {
 	handle_error :: proc "contextless" (file: string, line, column: i32, row_index, column_index, row_count, column_count: int) -> ! {
 		print_caller_location(Source_Code_Location{file, line, column, ""})
 		print_caller_location(Source_Code_Location{file, line, column, ""})
 		print_string(" Matrix indices [")
 		print_string(" Matrix indices [")
@@ -127,7 +141,7 @@ when ODIN_NO_RTTI {
 		if ok {
 		if ok {
 			return
 			return
 		}
 		}
-		@(cold)
+		@(cold, no_instrumentation)
 		handle_error :: proc "contextless" (file: string, line, column: i32) -> ! {
 		handle_error :: proc "contextless" (file: string, line, column: i32) -> ! {
 			print_caller_location(Source_Code_Location{file, line, column, ""})
 			print_caller_location(Source_Code_Location{file, line, column, ""})
 			print_string(" Invalid type assertion\n")
 			print_string(" Invalid type assertion\n")
@@ -140,7 +154,7 @@ when ODIN_NO_RTTI {
 		if ok {
 		if ok {
 			return
 			return
 		}
 		}
-		@(cold)
+		@(cold, no_instrumentation)
 		handle_error :: proc "contextless" (file: string, line, column: i32) -> ! {
 		handle_error :: proc "contextless" (file: string, line, column: i32) -> ! {
 			print_caller_location(Source_Code_Location{file, line, column, ""})
 			print_caller_location(Source_Code_Location{file, line, column, ""})
 			print_string(" Invalid type assertion\n")
 			print_string(" Invalid type assertion\n")
@@ -153,7 +167,7 @@ when ODIN_NO_RTTI {
 		if ok {
 		if ok {
 			return
 			return
 		}
 		}
-		@(cold)
+		@(cold, no_instrumentation)
 		handle_error :: proc "contextless" (file: string, line, column: i32, from, to: typeid) -> ! {
 		handle_error :: proc "contextless" (file: string, line, column: i32, from, to: typeid) -> ! {
 			print_caller_location(Source_Code_Location{file, line, column, ""})
 			print_caller_location(Source_Code_Location{file, line, column, ""})
 			print_string(" Invalid type assertion from ")
 			print_string(" Invalid type assertion from ")
@@ -198,7 +212,7 @@ when ODIN_NO_RTTI {
 			return id
 			return id
 		}
 		}
 
 
-		@(cold)
+		@(cold, no_instrumentation)
 		handle_error :: proc "contextless" (file: string, line, column: i32, from, to: typeid, from_data: rawptr) -> ! {
 		handle_error :: proc "contextless" (file: string, line, column: i32, from, to: typeid, from_data: rawptr) -> ! {
 
 
 			actual := variant_type(from, from_data)
 			actual := variant_type(from, from_data)
@@ -220,11 +234,12 @@ when ODIN_NO_RTTI {
 }
 }
 
 
 
 
+@(disabled=ODIN_NO_BOUNDS_CHECK)
 make_slice_error_loc :: #force_inline proc "contextless" (loc := #caller_location, len: int) {
 make_slice_error_loc :: #force_inline proc "contextless" (loc := #caller_location, len: int) {
 	if 0 <= len {
 	if 0 <= len {
 		return
 		return
 	}
 	}
-	@(cold)
+	@(cold, no_instrumentation)
 	handle_error :: proc "contextless" (loc: Source_Code_Location, len: int) -> ! {
 	handle_error :: proc "contextless" (loc: Source_Code_Location, len: int) -> ! {
 		print_caller_location(loc)
 		print_caller_location(loc)
 		print_string(" Invalid slice length for make: ")
 		print_string(" Invalid slice length for make: ")
@@ -235,11 +250,12 @@ make_slice_error_loc :: #force_inline proc "contextless" (loc := #caller_locatio
 	handle_error(loc, len)
 	handle_error(loc, len)
 }
 }
 
 
+@(disabled=ODIN_NO_BOUNDS_CHECK)
 make_dynamic_array_error_loc :: #force_inline proc "contextless" (loc := #caller_location, len, cap: int) {
 make_dynamic_array_error_loc :: #force_inline proc "contextless" (loc := #caller_location, len, cap: int) {
 	if 0 <= len && len <= cap {
 	if 0 <= len && len <= cap {
 		return
 		return
 	}
 	}
-	@(cold)
+	@(cold, no_instrumentation)
 	handle_error :: proc "contextless" (loc: Source_Code_Location, len, cap: int)  -> ! {
 	handle_error :: proc "contextless" (loc: Source_Code_Location, len, cap: int)  -> ! {
 		print_caller_location(loc)
 		print_caller_location(loc)
 		print_string(" Invalid dynamic array parameters for make: ")
 		print_string(" Invalid dynamic array parameters for make: ")
@@ -252,11 +268,12 @@ make_dynamic_array_error_loc :: #force_inline proc "contextless" (loc := #caller
 	handle_error(loc, len, cap)
 	handle_error(loc, len, cap)
 }
 }
 
 
+@(disabled=ODIN_NO_BOUNDS_CHECK)
 make_map_expr_error_loc :: #force_inline proc "contextless" (loc := #caller_location, cap: int) {
 make_map_expr_error_loc :: #force_inline proc "contextless" (loc := #caller_location, cap: int) {
 	if 0 <= cap {
 	if 0 <= cap {
 		return
 		return
 	}
 	}
-	@(cold)
+	@(cold, no_instrumentation)
 	handle_error :: proc "contextless" (loc: Source_Code_Location, cap: int)  -> ! {
 	handle_error :: proc "contextless" (loc: Source_Code_Location, cap: int)  -> ! {
 		print_caller_location(loc)
 		print_caller_location(loc)
 		print_string(" Invalid map capacity for make: ")
 		print_string(" Invalid map capacity for make: ")
@@ -270,19 +287,22 @@ make_map_expr_error_loc :: #force_inline proc "contextless" (loc := #caller_loca
 
 
 
 
 
 
-
+@(disabled=ODIN_NO_BOUNDS_CHECK)
 bounds_check_error_loc :: #force_inline proc "contextless" (loc := #caller_location, index, count: int) {
 bounds_check_error_loc :: #force_inline proc "contextless" (loc := #caller_location, index, count: int) {
 	bounds_check_error(loc.file_path, loc.line, loc.column, index, count)
 	bounds_check_error(loc.file_path, loc.line, loc.column, index, count)
 }
 }
 
 
+@(disabled=ODIN_NO_BOUNDS_CHECK)
 slice_expr_error_hi_loc :: #force_inline proc "contextless" (loc := #caller_location, hi: int, len: int) {
 slice_expr_error_hi_loc :: #force_inline proc "contextless" (loc := #caller_location, hi: int, len: int) {
 	slice_expr_error_hi(loc.file_path, loc.line, loc.column, hi, len)
 	slice_expr_error_hi(loc.file_path, loc.line, loc.column, hi, len)
 }
 }
 
 
+@(disabled=ODIN_NO_BOUNDS_CHECK)
 slice_expr_error_lo_hi_loc :: #force_inline proc "contextless" (loc := #caller_location, lo, hi: int, len: int) {
 slice_expr_error_lo_hi_loc :: #force_inline proc "contextless" (loc := #caller_location, lo, hi: int, len: int) {
 	slice_expr_error_lo_hi(loc.file_path, loc.line, loc.column, lo, hi, len)
 	slice_expr_error_lo_hi(loc.file_path, loc.line, loc.column, lo, hi, len)
 }
 }
 
 
+@(disabled=ODIN_NO_BOUNDS_CHECK)
 dynamic_array_expr_error_loc :: #force_inline proc "contextless" (loc := #caller_location, low, high, max: int) {
 dynamic_array_expr_error_loc :: #force_inline proc "contextless" (loc := #caller_location, low, high, max: int) {
 	dynamic_array_expr_error(loc.file_path, loc.line, loc.column, low, high, max)
 	dynamic_array_expr_error(loc.file_path, loc.line, loc.column, low, high, max)
 }
 }

+ 119 - 0
base/runtime/heap_allocator.odin

@@ -0,0 +1,119 @@
+package runtime
+
+import "base:intrinsics"
+
+heap_allocator :: proc() -> Allocator {
+	return Allocator{
+		procedure = heap_allocator_proc,
+		data = nil,
+	}
+}
+
+heap_allocator_proc :: proc(allocator_data: rawptr, mode: Allocator_Mode,
+                            size, alignment: int,
+                            old_memory: rawptr, old_size: int, loc := #caller_location) -> ([]byte, Allocator_Error) {
+	//
+	// NOTE(tetra, 2020-01-14): The heap doesn't respect alignment.
+	// Instead, we overallocate by `alignment + size_of(rawptr) - 1`, and insert
+	// padding. We also store the original pointer returned by heap_alloc right before
+	// the pointer we return to the user.
+	//
+
+	aligned_alloc :: proc(size, alignment: int, old_ptr: rawptr, old_size: int, zero_memory := true) -> ([]byte, Allocator_Error) {
+		// Not(flysand): We need to reserve enough space for alignment, which
+		// includes the user data itself, the space to store the pointer to
+		// allocation start, as well as the padding required to align both
+		// the user data and the pointer.
+		a := max(alignment, align_of(rawptr))
+		space := a-1 + size_of(rawptr) + size
+		allocated_mem: rawptr
+
+		force_copy := old_ptr != nil && alignment > align_of(rawptr)
+
+		if old_ptr != nil && !force_copy {
+			original_old_ptr := ([^]rawptr)(old_ptr)[-1]
+			allocated_mem = heap_resize(original_old_ptr, space)
+		} else {
+			allocated_mem = heap_alloc(space, zero_memory)
+		}
+		aligned_mem := rawptr(([^]u8)(allocated_mem)[size_of(rawptr):])
+
+		ptr := uintptr(aligned_mem)
+		aligned_ptr := (ptr + uintptr(a)-1) & ~(uintptr(a)-1)
+		if allocated_mem == nil {
+			aligned_free(old_ptr)
+			aligned_free(allocated_mem)
+			return nil, .Out_Of_Memory
+		}
+
+		aligned_mem = rawptr(aligned_ptr)
+		([^]rawptr)(aligned_mem)[-1] = allocated_mem
+
+		if force_copy {
+			mem_copy_non_overlapping(aligned_mem, old_ptr, min(old_size, size))
+			aligned_free(old_ptr)
+		}
+
+		return byte_slice(aligned_mem, size), nil
+	}
+
+	aligned_free :: proc(p: rawptr) {
+		if p != nil {
+			heap_free(([^]rawptr)(p)[-1])
+		}
+	}
+
+	aligned_resize :: proc(p: rawptr, old_size: int, new_size: int, new_alignment: int, zero_memory := true) -> (new_memory: []byte, err: Allocator_Error) {
+		if p == nil {
+			return aligned_alloc(new_size, new_alignment, nil, old_size, zero_memory)
+		}
+
+		new_memory = aligned_alloc(new_size, new_alignment, p, old_size, zero_memory) or_return
+
+		// NOTE: heap_resize does not zero the new memory, so we do it
+		if zero_memory && new_size > old_size {
+			new_region := raw_data(new_memory[old_size:])
+			intrinsics.mem_zero(new_region, new_size - old_size)
+		}
+		return
+	}
+
+	switch mode {
+	case .Alloc, .Alloc_Non_Zeroed:
+		return aligned_alloc(size, alignment, nil, 0, mode == .Alloc)
+
+	case .Free:
+		aligned_free(old_memory)
+
+	case .Free_All:
+		return nil, .Mode_Not_Implemented
+
+	case .Resize, .Resize_Non_Zeroed:
+		return aligned_resize(old_memory, old_size, size, alignment, mode == .Resize)
+
+	case .Query_Features:
+		set := (^Allocator_Mode_Set)(old_memory)
+		if set != nil {
+			set^ = {.Alloc, .Alloc_Non_Zeroed, .Free, .Resize, .Resize_Non_Zeroed, .Query_Features}
+		}
+		return nil, nil
+
+	case .Query_Info:
+		return nil, .Mode_Not_Implemented
+	}
+
+	return nil, nil
+}
+
+
+heap_alloc :: proc "contextless" (size: int, zero_memory := true) -> rawptr {
+	return _heap_alloc(size, zero_memory)
+}
+
+heap_resize :: proc "contextless" (ptr: rawptr, new_size: int) -> rawptr {
+	return _heap_resize(ptr, new_size)
+}
+
+heap_free :: proc "contextless" (ptr: rawptr) {
+	_heap_free(ptr)
+}

+ 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 "contextless" (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 "contextless" (ptr: rawptr, new_size: int) -> rawptr {
+	return _orca_realloc(ptr, new_size)
+}
+
+_heap_free :: proc "contextless" (ptr: rawptr) {
+	_orca_free(ptr)
+}

+ 18 - 0
base/runtime/heap_allocator_other.odin

@@ -0,0 +1,18 @@
+#+build js, wasi, freestanding, essence
+#+private
+package runtime
+
+_heap_alloc :: proc "contextless" (size: int, zero_memory := true) -> rawptr {
+	context = default_context()
+	unimplemented("base:runtime 'heap_alloc' procedure is not supported on this platform")
+}
+
+_heap_resize :: proc "contextless" (ptr: rawptr, new_size: int) -> rawptr {
+	context = default_context()
+	unimplemented("base:runtime 'heap_resize' procedure is not supported on this platform")
+}
+
+_heap_free :: proc "contextless" (ptr: rawptr) {
+	context = default_context()
+	unimplemented("base:runtime 'heap_free' procedure is not supported on this platform")
+}

+ 38 - 0
base/runtime/heap_allocator_unix.odin

@@ -0,0 +1,38 @@
+#+build linux, darwin, freebsd, openbsd, netbsd, haiku
+#+private
+package runtime
+
+when ODIN_OS == .Darwin {
+	foreign import libc "system:System.framework"
+} else {
+	foreign import libc "system:c"
+}
+
+@(default_calling_convention="c")
+foreign libc {
+	@(link_name="malloc")   _unix_malloc   :: proc(size: int) -> rawptr ---
+	@(link_name="calloc")   _unix_calloc   :: proc(num, size: int) -> rawptr ---
+	@(link_name="free")     _unix_free     :: proc(ptr: rawptr) ---
+	@(link_name="realloc")  _unix_realloc  :: proc(ptr: rawptr, size: int) -> rawptr ---
+}
+
+_heap_alloc :: proc "contextless" (size: int, zero_memory := true) -> rawptr {
+	if size <= 0 {
+		return nil
+	}
+	if zero_memory {
+		return _unix_calloc(1, size)
+	} else {
+		return _unix_malloc(size)
+	}
+}
+
+_heap_resize :: proc "contextless" (ptr: rawptr, new_size: int) -> rawptr {
+	// NOTE: _unix_realloc doesn't guarantee new memory will be zeroed on
+	// POSIX platforms. Ensure your caller takes this into account.
+	return _unix_realloc(ptr, new_size)
+}
+
+_heap_free :: proc "contextless" (ptr: rawptr) {
+	_unix_free(ptr)
+}

+ 39 - 0
base/runtime/heap_allocator_windows.odin

@@ -0,0 +1,39 @@
+package runtime
+
+foreign import kernel32 "system:Kernel32.lib"
+
+@(private="file")
+@(default_calling_convention="system")
+foreign kernel32 {
+	// NOTE(bill): The types are not using the standard names (e.g. DWORD and LPVOID) to just minimizing the dependency
+
+	// default_allocator
+	GetProcessHeap :: proc() -> rawptr ---
+	HeapAlloc      :: proc(hHeap: rawptr, dwFlags: u32, dwBytes: uint) -> rawptr ---
+	HeapReAlloc    :: proc(hHeap: rawptr, dwFlags: u32, lpMem: rawptr, dwBytes: uint) -> rawptr ---
+	HeapFree       :: proc(hHeap: rawptr, dwFlags: u32, lpMem: rawptr) -> b32 ---
+}
+
+_heap_alloc :: proc "contextless" (size: int, zero_memory := true) -> rawptr {
+	HEAP_ZERO_MEMORY :: 0x00000008
+	return HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY if zero_memory else 0, uint(size))
+}
+_heap_resize :: proc "contextless" (ptr: rawptr, new_size: int) -> rawptr {
+	if new_size == 0 {
+		_heap_free(ptr)
+		return nil
+	}
+	if ptr == nil {
+		return _heap_alloc(new_size)
+	}
+
+	HEAP_ZERO_MEMORY :: 0x00000008
+	return HeapReAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, ptr, uint(new_size))
+}
+_heap_free :: proc "contextless" (ptr: rawptr) {
+	if ptr == nil {
+		return
+	}
+	HeapFree(GetProcessHeap(), 0, ptr)
+}
+

+ 145 - 128
core/runtime/internal.odin → base/runtime/internal.odin

@@ -1,17 +1,17 @@
+#+vet !cast
 package runtime
 package runtime
 
 
-import "core:intrinsics"
+import "base:intrinsics"
 
 
 @(private="file")
 @(private="file")
 IS_WASM :: ODIN_ARCH == .wasm32 || ODIN_ARCH == .wasm64p32
 IS_WASM :: ODIN_ARCH == .wasm32 || ODIN_ARCH == .wasm64p32
 
 
 @(private)
 @(private)
 RUNTIME_LINKAGE :: "strong" when (
 RUNTIME_LINKAGE :: "strong" when (
-	(ODIN_USE_SEPARATE_MODULES || 
+	ODIN_USE_SEPARATE_MODULES || 
 	ODIN_BUILD_MODE == .Dynamic ||
 	ODIN_BUILD_MODE == .Dynamic ||
-	!ODIN_NO_CRT) &&
-	!IS_WASM) else "internal"
-RUNTIME_REQUIRE :: !ODIN_TILDE
+	!ODIN_NO_CRT) else "internal"
+RUNTIME_REQUIRE :: false // !ODIN_TILDE
 
 
 @(private)
 @(private)
 __float16 :: f16 when __ODIN_LLVM_F16_SUPPORTED else u16
 __float16 :: f16 when __ODIN_LLVM_F16_SUPPORTED else u16
@@ -22,59 +22,33 @@ byte_slice :: #force_inline proc "contextless" (data: rawptr, len: int) -> []byt
 	return ([^]byte)(data)[:max(len, 0)]
 	return ([^]byte)(data)[:max(len, 0)]
 }
 }
 
 
-bswap_16 :: proc "contextless" (x: u16) -> u16 {
-	return x>>8 | x<<8
-}
-
-bswap_32 :: proc "contextless" (x: u32) -> u32 {
-	return x>>24 | (x>>8)&0xff00 | (x<<8)&0xff0000 | x<<24
-}
-
-bswap_64 :: proc "contextless" (x: u64) -> u64 {
-	z := x
-	z = (z & 0x00000000ffffffff) << 32 | (z & 0xffffffff00000000) >> 32
-	z = (z & 0x0000ffff0000ffff) << 16 | (z & 0xffff0000ffff0000) >> 16
-	z = (z & 0x00ff00ff00ff00ff) << 8  | (z & 0xff00ff00ff00ff00) >> 8
-	return z
-}
-
-bswap_128 :: proc "contextless" (x: u128) -> u128 {
-	z := transmute([4]u32)x
-	z[0], z[3] = bswap_32(z[3]), bswap_32(z[0])
-	z[1], z[2] = bswap_32(z[2]), bswap_32(z[1])
-	return transmute(u128)z
-}
-
-bswap_f16 :: proc "contextless" (f: f16) -> f16 {
-	x := transmute(u16)f
-	z := bswap_16(x)
-	return transmute(f16)z
-
+is_power_of_two_int :: #force_inline proc "contextless" (x: int) -> bool {
+	if x <= 0 {
+		return false
+	}
+	return (x & (x-1)) == 0
 }
 }
 
 
-bswap_f32 :: proc "contextless" (f: f32) -> f32 {
-	x := transmute(u32)f
-	z := bswap_32(x)
-	return transmute(f32)z
-
-}
+align_forward_int :: #force_inline proc "odin" (ptr, align: int) -> int {
+	assert(is_power_of_two_int(align))
 
 
-bswap_f64 :: proc "contextless" (f: f64) -> f64 {
-	x := transmute(u64)f
-	z := bswap_64(x)
-	return transmute(f64)z
+	p := ptr
+	modulo := p & (align-1)
+	if modulo != 0 {
+		p += align - modulo
+	}
+	return p
 }
 }
 
 
-
-is_power_of_two_int :: #force_inline proc(x: int) -> bool {
+is_power_of_two_uint :: #force_inline proc "contextless" (x: uint) -> bool {
 	if x <= 0 {
 	if x <= 0 {
 		return false
 		return false
 	}
 	}
 	return (x & (x-1)) == 0
 	return (x & (x-1)) == 0
 }
 }
 
 
-align_forward_int :: #force_inline proc(ptr, align: int) -> int {
-	assert(is_power_of_two_int(align))
+align_forward_uint :: #force_inline proc "odin" (ptr, align: uint) -> uint {
+	assert(is_power_of_two_uint(align))
 
 
 	p := ptr
 	p := ptr
 	modulo := p & (align-1)
 	modulo := p & (align-1)
@@ -84,14 +58,14 @@ align_forward_int :: #force_inline proc(ptr, align: int) -> int {
 	return p
 	return p
 }
 }
 
 
-is_power_of_two_uintptr :: #force_inline proc(x: uintptr) -> bool {
+is_power_of_two_uintptr :: #force_inline proc "contextless" (x: uintptr) -> bool {
 	if x <= 0 {
 	if x <= 0 {
 		return false
 		return false
 	}
 	}
 	return (x & (x-1)) == 0
 	return (x & (x-1)) == 0
 }
 }
 
 
-align_forward_uintptr :: #force_inline proc(ptr, align: uintptr) -> uintptr {
+align_forward_uintptr :: #force_inline proc "odin" (ptr, align: uintptr) -> uintptr {
 	assert(is_power_of_two_uintptr(align))
 	assert(is_power_of_two_uintptr(align))
 
 
 	p := ptr
 	p := ptr
@@ -102,6 +76,18 @@ align_forward_uintptr :: #force_inline proc(ptr, align: uintptr) -> uintptr {
 	return p
 	return p
 }
 }
 
 
+is_power_of_two :: proc {
+	is_power_of_two_int,
+	is_power_of_two_uint,
+	is_power_of_two_uintptr,
+}
+
+align_forward :: proc {
+	align_forward_int,
+	align_forward_uint,
+	align_forward_uintptr,
+}
+
 mem_zero :: proc "contextless" (data: rawptr, len: int) -> rawptr {
 mem_zero :: proc "contextless" (data: rawptr, len: int) -> rawptr {
 	if data == nil {
 	if data == nil {
 		return nil
 		return nil
@@ -132,16 +118,15 @@ mem_copy_non_overlapping :: proc "contextless" (dst, src: rawptr, len: int) -> r
 DEFAULT_ALIGNMENT :: 2*align_of(rawptr)
 DEFAULT_ALIGNMENT :: 2*align_of(rawptr)
 
 
 mem_alloc_bytes :: #force_inline proc(size: int, alignment: int = DEFAULT_ALIGNMENT, allocator := context.allocator, loc := #caller_location) -> ([]byte, Allocator_Error) {
 mem_alloc_bytes :: #force_inline proc(size: int, alignment: int = DEFAULT_ALIGNMENT, allocator := context.allocator, loc := #caller_location) -> ([]byte, Allocator_Error) {
-	if size == 0 {
-		return nil, nil
-	}
-	if allocator.procedure == nil {
+	assert(is_power_of_two_int(alignment), "Alignment must be a power of two", loc)
+	if size == 0 || allocator.procedure == nil{
 		return nil, nil
 		return nil, nil
 	}
 	}
 	return allocator.procedure(allocator.data, .Alloc, size, alignment, nil, 0, loc)
 	return allocator.procedure(allocator.data, .Alloc, size, alignment, nil, 0, loc)
 }
 }
 
 
 mem_alloc :: #force_inline proc(size: int, alignment: int = DEFAULT_ALIGNMENT, allocator := context.allocator, loc := #caller_location) -> ([]byte, Allocator_Error) {
 mem_alloc :: #force_inline proc(size: int, alignment: int = DEFAULT_ALIGNMENT, allocator := context.allocator, loc := #caller_location) -> ([]byte, Allocator_Error) {
+	assert(is_power_of_two_int(alignment), "Alignment must be a power of two", loc)
 	if size == 0 || allocator.procedure == nil {
 	if size == 0 || allocator.procedure == nil {
 		return nil, nil
 		return nil, nil
 	}
 	}
@@ -149,6 +134,7 @@ mem_alloc :: #force_inline proc(size: int, alignment: int = DEFAULT_ALIGNMENT, a
 }
 }
 
 
 mem_alloc_non_zeroed :: #force_inline proc(size: int, alignment: int = DEFAULT_ALIGNMENT, allocator := context.allocator, loc := #caller_location) -> ([]byte, Allocator_Error) {
 mem_alloc_non_zeroed :: #force_inline proc(size: int, alignment: int = DEFAULT_ALIGNMENT, allocator := context.allocator, loc := #caller_location) -> ([]byte, Allocator_Error) {
+	assert(is_power_of_two_int(alignment), "Alignment must be a power of two", loc)
 	if size == 0 || allocator.procedure == nil {
 	if size == 0 || allocator.procedure == nil {
 		return nil, nil
 		return nil, nil
 	}
 	}
@@ -187,7 +173,8 @@ mem_free_all :: #force_inline proc(allocator := context.allocator, loc := #calle
 	return
 	return
 }
 }
 
 
-mem_resize :: proc(ptr: rawptr, old_size, new_size: int, alignment: int = DEFAULT_ALIGNMENT, allocator := context.allocator, loc := #caller_location) -> (data: []byte, err: Allocator_Error) {
+_mem_resize :: #force_inline proc(ptr: rawptr, old_size, new_size: int, alignment: int = DEFAULT_ALIGNMENT, allocator := context.allocator, should_zero: bool, loc := #caller_location) -> (data: []byte, err: Allocator_Error) {
+	assert(is_power_of_two_int(alignment), "Alignment must be a power of two", loc)
 	if allocator.procedure == nil {
 	if allocator.procedure == nil {
 		return nil, nil
 		return nil, nil
 	}
 	}
@@ -198,15 +185,27 @@ mem_resize :: proc(ptr: rawptr, old_size, new_size: int, alignment: int = DEFAUL
 		}
 		}
 		return
 		return
 	} else if ptr == nil {
 	} else if ptr == nil {
-		return allocator.procedure(allocator.data, .Alloc, new_size, alignment, nil, 0, loc)
+		if should_zero {
+			return allocator.procedure(allocator.data, .Alloc, new_size, alignment, nil, 0, loc)
+		} else {
+			return allocator.procedure(allocator.data, .Alloc_Non_Zeroed, new_size, alignment, nil, 0, loc)
+		}
 	} else if old_size == new_size && uintptr(ptr) % uintptr(alignment) == 0 {
 	} else if old_size == new_size && uintptr(ptr) % uintptr(alignment) == 0 {
 		data = ([^]byte)(ptr)[:old_size]
 		data = ([^]byte)(ptr)[:old_size]
 		return
 		return
 	}
 	}
 
 
-	data, err = allocator.procedure(allocator.data, .Resize, new_size, alignment, ptr, old_size, loc)
+	if should_zero {
+		data, err = allocator.procedure(allocator.data, .Resize, new_size, alignment, ptr, old_size, loc)
+	} else {
+		data, err = allocator.procedure(allocator.data, .Resize_Non_Zeroed, new_size, alignment, ptr, old_size, loc)
+	}
 	if err == .Mode_Not_Implemented {
 	if err == .Mode_Not_Implemented {
-		data, err = allocator.procedure(allocator.data, .Alloc, new_size, alignment, nil, 0, loc)
+		if should_zero {
+			data, err = allocator.procedure(allocator.data, .Alloc, new_size, alignment, nil, 0, loc)
+		} else {
+			data, err = allocator.procedure(allocator.data, .Alloc_Non_Zeroed, new_size, alignment, nil, 0, loc)
+		}
 		if err != nil {
 		if err != nil {
 			return
 			return
 		}
 		}
@@ -216,6 +215,15 @@ mem_resize :: proc(ptr: rawptr, old_size, new_size: int, alignment: int = DEFAUL
 	return
 	return
 }
 }
 
 
+mem_resize :: proc(ptr: rawptr, old_size, new_size: int, alignment: int = DEFAULT_ALIGNMENT, allocator := context.allocator, loc := #caller_location) -> (data: []byte, err: Allocator_Error) {
+	assert(is_power_of_two_int(alignment), "Alignment must be a power of two", loc)
+	return _mem_resize(ptr, old_size, new_size, alignment, allocator, true, loc)
+}
+non_zero_mem_resize :: proc(ptr: rawptr, old_size, new_size: int, alignment: int = DEFAULT_ALIGNMENT, allocator := context.allocator, loc := #caller_location) -> (data: []byte, err: Allocator_Error) {
+	assert(is_power_of_two_int(alignment), "Alignment must be a power of two", loc)
+	return _mem_resize(ptr, old_size, new_size, alignment, allocator, false, loc)
+}
+
 memory_equal :: proc "contextless" (x, y: rawptr, n: int) -> bool {
 memory_equal :: proc "contextless" (x, y: rawptr, n: int) -> bool {
 	switch {
 	switch {
 	case n == 0: return true
 	case n == 0: return true
@@ -478,7 +486,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
@@ -499,7 +507,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},
@@ -589,36 +597,6 @@ string_decode_last_rune :: proc "contextless" (s: string) -> (rune, int) {
 	return r, size
 	return r, size
 }
 }
 
 
-
-abs_f16 :: #force_inline proc "contextless" (x: f16) -> f16 {
-	return -x if x < 0 else x
-}
-abs_f32 :: #force_inline proc "contextless" (x: f32) -> f32 {
-	return -x if x < 0 else x
-}
-abs_f64 :: #force_inline proc "contextless" (x: f64) -> f64 {
-	return -x if x < 0 else x
-}
-
-min_f16 :: #force_inline proc "contextless" (a, b: f16) -> f16 {
-	return a if a < b else b
-}
-min_f32 :: #force_inline proc "contextless" (a, b: f32) -> f32 {
-	return a if a < b else b
-}
-min_f64 :: #force_inline proc "contextless" (a, b: f64) -> f64 {
-	return a if a < b else b
-}
-max_f16 :: #force_inline proc "contextless" (a, b: f16) -> f16 {
-	return a if a > b else b
-}
-max_f32 :: #force_inline proc "contextless" (a, b: f32) -> f32 {
-	return a if a > b else b
-}
-max_f64 :: #force_inline proc "contextless" (a, b: f64) -> f64 {
-	return a if a > b else b
-}
-
 abs_complex32 :: #force_inline proc "contextless" (x: complex32) -> f16 {
 abs_complex32 :: #force_inline proc "contextless" (x: complex32) -> f16 {
 	p, q := abs(real(x)), abs(imag(x))
 	p, q := abs(real(x)), abs(imag(x))
 	if p < q {
 	if p < q {
@@ -667,21 +645,24 @@ abs_quaternion256 :: #force_inline proc "contextless" (x: quaternion256) -> f64
 
 
 
 
 quo_complex32 :: proc "contextless" (n, m: complex32) -> complex32 {
 quo_complex32 :: proc "contextless" (n, m: complex32) -> complex32 {
-	e, f: f16
+	nr, ni := f32(real(n)), f32(imag(n))
+	mr, mi := f32(real(m)), f32(imag(m))
 
 
-	if abs(real(m)) >= abs(imag(m)) {
-		ratio := imag(m) / real(m)
-		denom := real(m) + ratio*imag(m)
-		e = (real(n) + imag(n)*ratio) / denom
-		f = (imag(n) - real(n)*ratio) / denom
+	e, f: f32
+
+	if abs(mr) >= abs(mi) {
+		ratio := mi / mr
+		denom := mr + ratio*mi
+		e = (nr + ni*ratio) / denom
+		f = (ni - nr*ratio) / denom
 	} else {
 	} else {
-		ratio := real(m) / imag(m)
-		denom := imag(m) + ratio*real(m)
-		e = (real(n)*ratio + imag(n)) / denom
-		f = (imag(n)*ratio - real(n)) / denom
+		ratio := mr / mi
+		denom := mi + ratio*mr
+		e = (nr*ratio + ni) / denom
+		f = (ni*ratio - nr) / denom
 	}
 	}
 
 
-	return complex(e, f)
+	return complex(f16(e), f16(f))
 }
 }
 
 
 
 
@@ -722,15 +703,15 @@ quo_complex128 :: proc "contextless" (n, m: complex128) -> complex128 {
 }
 }
 
 
 mul_quaternion64 :: proc "contextless" (q, r: quaternion64) -> quaternion64 {
 mul_quaternion64 :: proc "contextless" (q, r: quaternion64) -> quaternion64 {
-	q0, q1, q2, q3 := real(q), imag(q), jmag(q), kmag(q)
-	r0, r1, r2, r3 := real(r), imag(r), jmag(r), kmag(r)
+	q0, q1, q2, q3 := f32(real(q)), f32(imag(q)), f32(jmag(q)), f32(kmag(q))
+	r0, r1, r2, r3 := f32(real(r)), f32(imag(r)), f32(jmag(r)), f32(kmag(r))
 
 
 	t0 := r0*q0 - r1*q1 - r2*q2 - r3*q3
 	t0 := r0*q0 - r1*q1 - r2*q2 - r3*q3
 	t1 := r0*q1 + r1*q0 - r2*q3 + r3*q2
 	t1 := r0*q1 + r1*q0 - r2*q3 + r3*q2
 	t2 := r0*q2 + r1*q3 + r2*q0 - r3*q1
 	t2 := r0*q2 + r1*q3 + r2*q0 - r3*q1
 	t3 := r0*q3 - r1*q2 + r2*q1 + r3*q0
 	t3 := r0*q3 - r1*q2 + r2*q1 + r3*q0
 
 
-	return quaternion(t0, t1, t2, t3)
+	return quaternion(w=f16(t0), x=f16(t1), y=f16(t2), z=f16(t3))
 }
 }
 
 
 mul_quaternion128 :: proc "contextless" (q, r: quaternion128) -> quaternion128 {
 mul_quaternion128 :: proc "contextless" (q, r: quaternion128) -> quaternion128 {
@@ -742,7 +723,7 @@ mul_quaternion128 :: proc "contextless" (q, r: quaternion128) -> quaternion128 {
 	t2 := r0*q2 + r1*q3 + r2*q0 - r3*q1
 	t2 := r0*q2 + r1*q3 + r2*q0 - r3*q1
 	t3 := r0*q3 - r1*q2 + r2*q1 + r3*q0
 	t3 := r0*q3 - r1*q2 + r2*q1 + r3*q0
 
 
-	return quaternion(t0, t1, t2, t3)
+	return quaternion(w=t0, x=t1, y=t2, z=t3)
 }
 }
 
 
 mul_quaternion256 :: proc "contextless" (q, r: quaternion256) -> quaternion256 {
 mul_quaternion256 :: proc "contextless" (q, r: quaternion256) -> quaternion256 {
@@ -754,12 +735,12 @@ mul_quaternion256 :: proc "contextless" (q, r: quaternion256) -> quaternion256 {
 	t2 := r0*q2 + r1*q3 + r2*q0 - r3*q1
 	t2 := r0*q2 + r1*q3 + r2*q0 - r3*q1
 	t3 := r0*q3 - r1*q2 + r2*q1 + r3*q0
 	t3 := r0*q3 - r1*q2 + r2*q1 + r3*q0
 
 
-	return quaternion(t0, t1, t2, t3)
+	return quaternion(w=t0, x=t1, y=t2, z=t3)
 }
 }
 
 
 quo_quaternion64 :: proc "contextless" (q, r: quaternion64) -> quaternion64 {
 quo_quaternion64 :: proc "contextless" (q, r: quaternion64) -> quaternion64 {
-	q0, q1, q2, q3 := real(q), imag(q), jmag(q), kmag(q)
-	r0, r1, r2, r3 := real(r), imag(r), jmag(r), kmag(r)
+	q0, q1, q2, q3 := f32(real(q)), f32(imag(q)), f32(jmag(q)), f32(kmag(q))
+	r0, r1, r2, r3 := f32(real(r)), f32(imag(r)), f32(jmag(r)), f32(kmag(r))
 
 
 	invmag2 := 1.0 / (r0*r0 + r1*r1 + r2*r2 + r3*r3)
 	invmag2 := 1.0 / (r0*r0 + r1*r1 + r2*r2 + r3*r3)
 
 
@@ -768,7 +749,7 @@ quo_quaternion64 :: proc "contextless" (q, r: quaternion64) -> quaternion64 {
 	t2 := (r0*q2 - r1*q3 - r2*q0 + r3*q1) * invmag2
 	t2 := (r0*q2 - r1*q3 - r2*q0 + r3*q1) * invmag2
 	t3 := (r0*q3 + r1*q2 + r2*q1 - r3*q0) * invmag2
 	t3 := (r0*q3 + r1*q2 + r2*q1 - r3*q0) * invmag2
 
 
-	return quaternion(t0, t1, t2, t3)
+	return quaternion(w=f16(t0), x=f16(t1), y=f16(t2), z=f16(t3))
 }
 }
 
 
 quo_quaternion128 :: proc "contextless" (q, r: quaternion128) -> quaternion128 {
 quo_quaternion128 :: proc "contextless" (q, r: quaternion128) -> quaternion128 {
@@ -782,7 +763,7 @@ quo_quaternion128 :: proc "contextless" (q, r: quaternion128) -> quaternion128 {
 	t2 := (r0*q2 - r1*q3 - r2*q0 + r3*q1) * invmag2
 	t2 := (r0*q2 - r1*q3 - r2*q0 + r3*q1) * invmag2
 	t3 := (r0*q3 + r1*q2 + r2*q1 - r3*q0) * invmag2
 	t3 := (r0*q3 + r1*q2 + r2*q1 - r3*q0) * invmag2
 
 
-	return quaternion(t0, t1, t2, t3)
+	return quaternion(w=t0, x=t1, y=t2, z=t3)
 }
 }
 
 
 quo_quaternion256 :: proc "contextless" (q, r: quaternion256) -> quaternion256 {
 quo_quaternion256 :: proc "contextless" (q, r: quaternion256) -> quaternion256 {
@@ -796,7 +777,7 @@ quo_quaternion256 :: proc "contextless" (q, r: quaternion256) -> quaternion256 {
 	t2 := (r0*q2 - r1*q3 - r2*q0 + r3*q1) * invmag2
 	t2 := (r0*q2 - r1*q3 - r2*q0 + r3*q1) * invmag2
 	t3 := (r0*q3 + r1*q2 + r2*q1 - r3*q0) * invmag2
 	t3 := (r0*q3 + r1*q2 + r2*q1 - r3*q0) * invmag2
 
 
-	return quaternion(t0, t1, t2, t3)
+	return quaternion(w=t0, x=t1, y=t2, z=t3)
 }
 }
 
 
 @(link_name="__truncsfhf2", linkage=RUNTIME_LINKAGE, require=RUNTIME_REQUIRE)
 @(link_name="__truncsfhf2", linkage=RUNTIME_LINKAGE, require=RUNTIME_REQUIRE)
@@ -856,6 +837,10 @@ truncsfhf2 :: proc "c" (value: f32) -> __float16 {
 	}
 	}
 }
 }
 
 
+@(link_name="__aeabi_d2h", linkage=RUNTIME_LINKAGE, require=RUNTIME_REQUIRE)
+aeabi_d2h :: proc "c" (value: f64) -> __float16 {
+	return truncsfhf2(f32(value))
+}
 
 
 @(link_name="__truncdfhf2", linkage=RUNTIME_LINKAGE, require=RUNTIME_REQUIRE)
 @(link_name="__truncdfhf2", linkage=RUNTIME_LINKAGE, require=RUNTIME_REQUIRE)
 truncdfhf2 :: proc "c" (value: f64) -> __float16 {
 truncdfhf2 :: proc "c" (value: f64) -> __float16 {
@@ -896,9 +881,6 @@ extendhfsf2 :: proc "c" (value: __float16) -> f32 {
 
 
 @(link_name="__floattidf", linkage=RUNTIME_LINKAGE, require=RUNTIME_REQUIRE)
 @(link_name="__floattidf", linkage=RUNTIME_LINKAGE, require=RUNTIME_REQUIRE)
 floattidf :: proc "c" (a: i128) -> f64 {
 floattidf :: proc "c" (a: i128) -> f64 {
-when IS_WASM {
-	return 0
-} else {
 	DBL_MANT_DIG :: 53
 	DBL_MANT_DIG :: 53
 	if a == 0 {
 	if a == 0 {
 		return 0.0
 		return 0.0
@@ -938,14 +920,10 @@ when IS_WASM {
 	fb[0] = u32(a)                           // mantissa-low
 	fb[0] = u32(a)                           // mantissa-low
 	return transmute(f64)fb
 	return transmute(f64)fb
 }
 }
-}
 
 
 
 
 @(link_name="__floattidf_unsigned", linkage=RUNTIME_LINKAGE, require=RUNTIME_REQUIRE)
 @(link_name="__floattidf_unsigned", linkage=RUNTIME_LINKAGE, require=RUNTIME_REQUIRE)
 floattidf_unsigned :: proc "c" (a: u128) -> f64 {
 floattidf_unsigned :: proc "c" (a: u128) -> f64 {
-when IS_WASM {
-	return 0
-} else {
 	DBL_MANT_DIG :: 53
 	DBL_MANT_DIG :: 53
 	if a == 0 {
 	if a == 0 {
 		return 0.0
 		return 0.0
@@ -983,7 +961,6 @@ when IS_WASM {
 	fb[0] = u32(a)                           // mantissa-low
 	fb[0] = u32(a)                           // mantissa-low
 	return transmute(f64)fb
 	return transmute(f64)fb
 }
 }
-}
 
 
 
 
 
 
@@ -1017,9 +994,11 @@ udivmodti4 :: proc "c" (a, b: u128, rem: ^u128) -> u128 {
 	return udivmod128(a, b, rem)
 	return udivmod128(a, b, rem)
 }
 }
 
 
-@(link_name="__udivti3", linkage=RUNTIME_LINKAGE, require=RUNTIME_REQUIRE)
-udivti3 :: proc "c" (a, b: u128) -> u128 {
-	return udivmodti4(a, b, nil)
+when !IS_WASM {
+	@(link_name="__udivti3", linkage=RUNTIME_LINKAGE, require=RUNTIME_REQUIRE)
+	udivti3 :: proc "c" (a, b: u128) -> u128 {
+		return udivmodti4(a, b, nil)
+	}
 }
 }
 
 
 
 
@@ -1031,26 +1010,44 @@ modti3 :: proc "c" (a, b: i128) -> i128 {
 	bn := (b ~ s_b) - s_b
 	bn := (b ~ s_b) - s_b
 
 
 	r: u128 = ---
 	r: u128 = ---
-	_ = udivmod128(transmute(u128)an, transmute(u128)bn, &r)
-	return (transmute(i128)r ~ s_a) - s_a
+	_ = udivmod128(u128(an), u128(bn), &r)
+	return (i128(r) ~ s_a) - s_a
 }
 }
 
 
 
 
 @(link_name="__divmodti4", linkage=RUNTIME_LINKAGE, require=RUNTIME_REQUIRE)
 @(link_name="__divmodti4", linkage=RUNTIME_LINKAGE, require=RUNTIME_REQUIRE)
 divmodti4 :: proc "c" (a, b: i128, rem: ^i128) -> i128 {
 divmodti4 :: proc "c" (a, b: i128, rem: ^i128) -> i128 {
-	u := udivmod128(transmute(u128)a, transmute(u128)b, cast(^u128)rem)
-	return transmute(i128)u
+	s_a := a >> (128 - 1) // -1 if negative or 0
+	s_b := b >> (128 - 1)
+	an := (a ~ s_a) - s_a // absolute
+	bn := (b ~ s_b) - s_b
+
+	s_b   ~= s_a // quotient sign
+	u_s_b := u128(s_b)
+	u_s_a := u128(s_a)
+
+	r: u128 = ---
+	u := i128((udivmodti4(u128(an), u128(bn), &r) ~ u_s_b) - u_s_b) // negate if negative
+	rem^ = i128((r ~ u_s_a) - u_s_a)
+	return u
 }
 }
 
 
 @(link_name="__divti3", linkage=RUNTIME_LINKAGE, require=RUNTIME_REQUIRE)
 @(link_name="__divti3", linkage=RUNTIME_LINKAGE, require=RUNTIME_REQUIRE)
 divti3 :: proc "c" (a, b: i128) -> i128 {
 divti3 :: proc "c" (a, b: i128) -> i128 {
-	u := udivmodti4(transmute(u128)a, transmute(u128)b, nil)
-	return transmute(i128)u
+	s_a := a >> (128 - 1) // -1 if negative or 0
+	s_b := b >> (128 - 1)
+	an := (a ~ s_a) - s_a // absolute
+	bn := (b ~ s_b) - s_b
+
+	s_a   ~= s_b // quotient sign
+	u_s_a := u128(s_a)
+
+	return i128((udivmodti4(u128(an), u128(bn), nil) ~ u_s_a) - u_s_a) // negate if negative
 }
 }
 
 
 
 
 @(link_name="__fixdfti", linkage=RUNTIME_LINKAGE, require=RUNTIME_REQUIRE)
 @(link_name="__fixdfti", linkage=RUNTIME_LINKAGE, require=RUNTIME_REQUIRE)
-fixdfti :: proc(a: u64) -> i128 {
+fixdfti :: proc "c" (a: u64) -> i128 {
 	significandBits :: 52
 	significandBits :: 52
 	typeWidth       :: (size_of(u64)*8)
 	typeWidth       :: (size_of(u64)*8)
 	exponentBits    :: (typeWidth - significandBits - 1)
 	exponentBits    :: (typeWidth - significandBits - 1)
@@ -1089,3 +1086,23 @@ fixdfti :: proc(a: u64) -> i128 {
 	}
 	}
 
 
 }
 }
+
+
+
+__write_bits :: proc "contextless" (dst, src: [^]byte, offset: uintptr, size: uintptr) {
+	for i in 0..<size {
+		j := offset+i
+		the_bit := byte((src[i>>3]) & (1<<(i&7)) != 0)
+		dst[j>>3] &~=       1<<(j&7)
+		dst[j>>3]  |= the_bit<<(j&7)
+	}
+}
+
+__read_bits :: proc "contextless" (dst, src: [^]byte, offset: uintptr, size: uintptr) {
+	for j in 0..<size {
+		i := offset+j
+		the_bit := byte((src[i>>3]) & (1<<(i&7)) != 0)
+		dst[j>>3] &~=       1<<(j&7)
+		dst[j>>3]  |= the_bit<<(j&7)
+	}
+}

+ 7 - 0
base/runtime/os_specific.odin

@@ -0,0 +1,7 @@
+package runtime
+
+_OS_Errno :: distinct int
+
+stderr_write :: proc "contextless" (data: []byte) -> (int, _OS_Errno) {
+	return _stderr_write(data)
+}

+ 26 - 0
base/runtime/os_specific_bsd.odin

@@ -0,0 +1,26 @@
+#+build freebsd, openbsd, netbsd
+#+private
+package runtime
+
+foreign import libc "system:c"
+
+@(default_calling_convention="c")
+foreign libc {
+	@(link_name="write")
+	_unix_write :: proc(fd: i32, buf: rawptr, size: int) -> int ---
+
+	when ODIN_OS == .NetBSD {
+		@(link_name="__errno") __error :: proc() -> ^i32 ---
+	} else {
+		__error :: proc() -> ^i32 ---
+	}
+}
+
+_stderr_write :: proc "contextless" (data: []byte) -> (int, _OS_Errno) {
+	ret := _unix_write(2, raw_data(data), len(data))
+	if ret < len(data) {
+		err := __error()
+		return int(ret), _OS_Errno(err^ if err != nil else 0)
+	}
+	return int(ret), 0
+}

+ 28 - 0
base/runtime/os_specific_darwin.odin

@@ -0,0 +1,28 @@
+#+build darwin
+#+private
+package runtime
+
+import "base:intrinsics"
+
+_stderr_write :: proc "contextless" (data: []byte) -> (int, _OS_Errno) {
+	STDERR :: 2
+	when ODIN_NO_CRT {
+		WRITE  :: 0x2000004
+		ret := intrinsics.syscall(WRITE, STDERR, uintptr(raw_data(data)), uintptr(len(data)))
+		if ret < 0 {
+			return 0, _OS_Errno(-ret)
+		}
+		return int(ret), 0
+	} else {
+		foreign {
+			write   :: proc(handle: i32, buffer: [^]byte, count: uint) -> int ---
+			__error :: proc() -> ^i32 ---
+		}
+
+		if ret := write(STDERR, raw_data(data), len(data)); ret >= 0 {
+			return int(ret), 0
+		}
+
+		return 0, _OS_Errno(__error()^)
+	}
+}

+ 8 - 0
base/runtime/os_specific_freestanding.odin

@@ -0,0 +1,8 @@
+#+build freestanding
+#+private
+package runtime
+
+// TODO(bill): reimplement `os.write`
+_stderr_write :: proc "contextless" (data: []byte) -> (int, _OS_Errno) {
+	return 0, -1
+}

+ 21 - 0
base/runtime/os_specific_haiku.odin

@@ -0,0 +1,21 @@
+#+build haiku
+#+private
+package runtime
+
+foreign import libc "system:c"
+
+foreign libc {
+	@(link_name="write")
+	_unix_write :: proc(fd: i32, buf: rawptr, size: int) -> int ---
+
+	_errnop :: proc() -> ^i32 ---
+}
+
+_stderr_write :: proc "contextless" (data: []byte) -> (int, _OS_Errno) {
+	ret := _unix_write(2, raw_data(data), len(data))
+	if ret < len(data) {
+		err := _errnop()
+		return int(ret), _OS_Errno(err^ if err != nil else 0)
+	}
+	return int(ret), 0
+}

+ 3 - 2
core/runtime/os_specific_js.odin → base/runtime/os_specific_js.odin

@@ -1,9 +1,10 @@
-//+build js
+#+build js
+#+private
 package runtime
 package runtime
 
 
 foreign import "odin_env"
 foreign import "odin_env"
 
 
-_os_write :: proc "contextless" (data: []byte) -> (int, _OS_Errno) {
+_stderr_write :: proc "contextless" (data: []byte) -> (int, _OS_Errno) {
 	foreign odin_env {
 	foreign odin_env {
 		write :: proc "contextless" (fd: u32, p: []byte) ---
 		write :: proc "contextless" (fd: u32, p: []byte) ---
 	}
 	}

+ 26 - 0
base/runtime/os_specific_linux.odin

@@ -0,0 +1,26 @@
+#+private
+package runtime
+
+import "base:intrinsics"
+
+_stderr_write :: proc "contextless" (data: []byte) -> (int, _OS_Errno) {
+	when ODIN_ARCH == .amd64 {
+		SYS_write :: uintptr(1)
+	} else when ODIN_ARCH == .arm64 {
+		SYS_write :: uintptr(64)
+	} else when ODIN_ARCH == .i386 {
+		SYS_write :: uintptr(4)
+	} else when ODIN_ARCH == .arm32 {
+		SYS_write :: uintptr(4)
+	} else when ODIN_ARCH == .riscv64 {
+		SYS_write :: uintptr(64)
+	}
+
+	stderr :: 2
+
+	ret := int(intrinsics.syscall(SYS_write, uintptr(stderr), uintptr(raw_data(data)), uintptr(len(data))))
+	if ret < 0 && ret > -4096 {
+		return 0, _OS_Errno(-ret)
+	}
+	return ret, 0
+}

+ 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
+}

+ 55 - 0
base/runtime/os_specific_wasi.odin

@@ -0,0 +1,55 @@
+#+build wasi
+#+private
+package runtime
+
+foreign import wasi "wasi_snapshot_preview1"
+
+@(default_calling_convention="contextless")
+foreign wasi {
+	fd_write :: proc(
+		fd: i32,
+		iovs: [][]byte,
+		n: ^uint,
+	) -> u16 ---
+
+	@(private="file")
+	args_sizes_get :: proc(
+		num_of_args:  ^uint,
+		size_of_args: ^uint,
+	) -> u16 ---
+
+	@(private="file")
+	args_get :: proc(
+		argv:     [^]cstring,
+		argv_buf: [^]byte,
+	) -> u16 ---
+}
+
+_stderr_write :: proc "contextless" (data: []byte) -> (int, _OS_Errno) {
+	n: uint
+	err := fd_write(1, {data}, &n)
+	return int(n), _OS_Errno(err)
+}
+
+_wasi_setup_args :: proc() {
+	num_of_args, size_of_args: uint
+	if errno := args_sizes_get(&num_of_args, &size_of_args); errno != 0 {
+		return
+	}
+
+	err: Allocator_Error
+	if args__, err = make([]cstring, num_of_args); err != nil {
+		return
+	}
+
+	args_buf: []byte
+	if args_buf, err = make([]byte, size_of_args); err != nil {
+		delete(args__)
+		return
+	}
+
+	if errno := args_get(raw_data(args__), raw_data(args_buf)); errno != 0 {
+		delete(args__)
+		delete(args_buf)
+	}
+}

+ 51 - 0
base/runtime/os_specific_windows.odin

@@ -0,0 +1,51 @@
+#+build windows
+#+private
+package runtime
+
+foreign import kernel32 "system:Kernel32.lib"
+
+@(private="file")
+@(default_calling_convention="system")
+foreign kernel32 {
+	// NOTE(bill): The types are not using the standard names (e.g. DWORD and LPVOID) to just minimizing the dependency
+
+	// stderr_write
+	GetStdHandle         :: proc(which: u32) -> rawptr ---
+	SetHandleInformation :: proc(hObject: rawptr, dwMask: u32, dwFlags: u32) -> b32 ---
+	WriteFile            :: proc(hFile: rawptr, lpBuffer: rawptr, nNumberOfBytesToWrite: u32, lpNumberOfBytesWritten: ^u32, lpOverlapped: rawptr) -> b32 ---
+	GetLastError         :: proc() -> u32 ---
+}
+
+_stderr_write :: proc "contextless" (data: []byte) -> (n: int, err: _OS_Errno) #no_bounds_check {
+	if len(data) == 0 {
+		return 0, 0
+	}
+
+	STD_ERROR_HANDLE :: ~u32(0) -12 + 1
+	HANDLE_FLAG_INHERIT :: 0x00000001
+	MAX_RW :: 1<<30
+
+	h := GetStdHandle(STD_ERROR_HANDLE)
+	when size_of(uintptr) == 8 {
+		SetHandleInformation(h, HANDLE_FLAG_INHERIT, 0)
+	}
+
+	single_write_length: u32
+	total_write: i64
+	length := i64(len(data))
+
+	for total_write < length {
+		remaining := length - total_write
+		to_write := u32(min(i32(remaining), MAX_RW))
+
+		e := WriteFile(h, &data[total_write], to_write, &single_write_length, nil)
+		if single_write_length <= 0 || !e {
+			err = _OS_Errno(GetLastError())
+			n = int(total_write)
+			return
+		}
+		total_write += i64(single_write_length)
+	}
+	n = int(total_write)
+	return
+}

+ 47 - 38
core/runtime/print.odin → base/runtime/print.odin

@@ -6,7 +6,7 @@ _INTEGER_DIGITS :: "0123456789abcdefghijklmnopqrstuvwxyz"
 _INTEGER_DIGITS_VAR := _INTEGER_DIGITS
 _INTEGER_DIGITS_VAR := _INTEGER_DIGITS
 
 
 when !ODIN_NO_RTTI {
 when !ODIN_NO_RTTI {
-	print_any_single :: proc "contextless" (arg: any) {
+	print_any_single :: #force_no_inline proc "contextless" (arg: any) {
 		x := arg
 		x := arg
 		if x.data == nil {
 		if x.data == nil {
 			print_string("nil")
 			print_string("nil")
@@ -72,7 +72,7 @@ when !ODIN_NO_RTTI {
 			print_string("<invalid-value>")
 			print_string("<invalid-value>")
 		}
 		}
 	}
 	}
-	println_any :: proc "contextless" (args: ..any) {
+	println_any :: #force_no_inline proc "contextless" (args: ..any) {
 		context = default_context()
 		context = default_context()
 		loop: for arg, i in args {
 		loop: for arg, i in args {
 			assert(arg.id != nil)
 			assert(arg.id != nil)
@@ -122,14 +122,14 @@ encode_rune :: proc "contextless" (c: rune) -> ([4]u8, int) {
 	return buf, 4
 	return buf, 4
 }
 }
 
 
-print_string :: proc "contextless" (str: string) -> (n: int) {
-	n, _ = os_write(transmute([]byte)str)
+print_string :: #force_no_inline proc "contextless" (str: string) -> (n: int) {
+	n, _ = stderr_write(transmute([]byte)str)
 	return
 	return
 }
 }
 
 
-print_strings :: proc "contextless" (args: ..string) -> (n: int) {
+print_strings :: #force_no_inline proc "contextless" (args: ..string) -> (n: int) {
 	for str in args {
 	for str in args {
-		m, err := os_write(transmute([]byte)str)
+		m, err := stderr_write(transmute([]byte)str)
 		n += m
 		n += m
 		if err != 0 {
 		if err != 0 {
 			break
 			break
@@ -138,12 +138,12 @@ print_strings :: proc "contextless" (args: ..string) -> (n: int) {
 	return
 	return
 }
 }
 
 
-print_byte :: proc "contextless" (b: byte) -> (n: int) {
-	n, _ = os_write([]byte{b})
+print_byte :: #force_no_inline proc "contextless" (b: byte) -> (n: int) {
+	n, _ = stderr_write([]byte{b})
 	return
 	return
 }
 }
 
 
-print_encoded_rune :: proc "contextless" (r: rune) {
+print_encoded_rune :: #force_no_inline proc "contextless" (r: rune) {
 	print_byte('\'')
 	print_byte('\'')
 
 
 	switch r {
 	switch r {
@@ -170,7 +170,7 @@ print_encoded_rune :: proc "contextless" (r: rune) {
 	print_byte('\'')
 	print_byte('\'')
 }
 }
 
 
-print_rune :: proc "contextless" (r: rune) -> int #no_bounds_check {
+print_rune :: #force_no_inline proc "contextless" (r: rune) -> int #no_bounds_check {
 	RUNE_SELF :: 0x80
 	RUNE_SELF :: 0x80
 
 
 	if r < RUNE_SELF {
 	if r < RUNE_SELF {
@@ -178,12 +178,12 @@ print_rune :: proc "contextless" (r: rune) -> int #no_bounds_check {
 	}
 	}
 
 
 	b, n := encode_rune(r)
 	b, n := encode_rune(r)
-	m, _ := os_write(b[:n])
+	m, _ := stderr_write(b[:n])
 	return m
 	return m
 }
 }
 
 
 
 
-print_u64 :: proc "contextless" (x: u64) #no_bounds_check {
+print_u64 :: #force_no_inline proc "contextless" (x: u64) #no_bounds_check {
 	a: [129]byte
 	a: [129]byte
 	i := len(a)
 	i := len(a)
 	b := u64(10)
 	b := u64(10)
@@ -194,11 +194,11 @@ print_u64 :: proc "contextless" (x: u64) #no_bounds_check {
 	}
 	}
 	i -= 1; a[i] = _INTEGER_DIGITS_VAR[u % b]
 	i -= 1; a[i] = _INTEGER_DIGITS_VAR[u % b]
 
 
-	os_write(a[i:])
+	stderr_write(a[i:])
 }
 }
 
 
 
 
-print_i64 :: proc "contextless" (x: i64) #no_bounds_check {
+print_i64 :: #force_no_inline proc "contextless" (x: i64) #no_bounds_check {
 	b :: i64(10)
 	b :: i64(10)
 
 
 	u := x
 	u := x
@@ -216,32 +216,36 @@ print_i64 :: proc "contextless" (x: i64) #no_bounds_check {
 		i -= 1; a[i] = '-'
 		i -= 1; a[i] = '-'
 	}
 	}
 
 
-	os_write(a[i:])
+	stderr_write(a[i:])
 }
 }
 
 
 print_uint    :: proc "contextless" (x: uint)    { print_u64(u64(x)) }
 print_uint    :: proc "contextless" (x: uint)    { print_u64(u64(x)) }
 print_uintptr :: proc "contextless" (x: uintptr) { print_u64(u64(x)) }
 print_uintptr :: proc "contextless" (x: uintptr) { print_u64(u64(x)) }
 print_int     :: proc "contextless" (x: int)     { print_i64(i64(x)) }
 print_int     :: proc "contextless" (x: int)     { print_i64(i64(x)) }
 
 
-print_caller_location :: proc "contextless" (loc: Source_Code_Location) {
+print_caller_location :: #force_no_inline proc "contextless" (loc: Source_Code_Location) {
 	print_string(loc.file_path)
 	print_string(loc.file_path)
 	when ODIN_ERROR_POS_STYLE == .Default {
 	when ODIN_ERROR_POS_STYLE == .Default {
 		print_byte('(')
 		print_byte('(')
 		print_u64(u64(loc.line))
 		print_u64(u64(loc.line))
-		print_byte(':')
-		print_u64(u64(loc.column))
+		if loc.column != 0 {
+			print_byte(':')
+			print_u64(u64(loc.column))
+		}
 		print_byte(')')
 		print_byte(')')
 	} else when ODIN_ERROR_POS_STYLE == .Unix {
 	} else when ODIN_ERROR_POS_STYLE == .Unix {
 		print_byte(':')
 		print_byte(':')
 		print_u64(u64(loc.line))
 		print_u64(u64(loc.line))
-		print_byte(':')
-		print_u64(u64(loc.column))
+		if loc.column != 0 {
+			print_byte(':')
+			print_u64(u64(loc.column))
+		}
 		print_byte(':')
 		print_byte(':')
 	} else {
 	} else {
 		#panic("unhandled ODIN_ERROR_POS_STYLE")
 		#panic("unhandled ODIN_ERROR_POS_STYLE")
 	}
 	}
 }
 }
-print_typeid :: proc "contextless" (id: typeid) {
+print_typeid :: #force_no_inline proc "contextless" (id: typeid) {
 	when ODIN_NO_RTTI {
 	when ODIN_NO_RTTI {
 		if id == nil {
 		if id == nil {
 			print_string("nil")
 			print_string("nil")
@@ -257,7 +261,9 @@ print_typeid :: proc "contextless" (id: typeid) {
 		}
 		}
 	}
 	}
 }
 }
-print_type :: proc "contextless" (ti: ^Type_Info) {
+
+@(optimization_mode="favor_size")
+print_type :: #force_no_inline proc "contextless" (ti: ^Type_Info) {
 	if ti == nil {
 	if ti == nil {
 		print_string("nil")
 		print_string("nil")
 		return
 		return
@@ -395,15 +401,16 @@ print_type :: proc "contextless" (ti: ^Type_Info) {
 		}
 		}
 
 
 		print_string("struct ")
 		print_string("struct ")
-		if info.is_packed    { print_string("#packed ") }
-		if info.is_raw_union { print_string("#raw_union ") }
-		if info.custom_align {
+		if .packed    in info.flags { print_string("#packed ") }
+		if .raw_union in info.flags { print_string("#raw_union ") }
+		if .no_copy   in info.flags { print_string("#no_copy ") }
+		if .align in info.flags {
 			print_string("#align(")
 			print_string("#align(")
 			print_u64(u64(ti.align))
 			print_u64(u64(ti.align))
 			print_string(") ")
 			print_string(") ")
 		}
 		}
 		print_byte('{')
 		print_byte('{')
-		for name, i in info.names {
+		for name, i in info.names[:info.field_count] {
 			if i > 0 { print_string(", ") }
 			if i > 0 { print_string(", ") }
 			print_string(name)
 			print_string(name)
 			print_string(": ")
 			print_string(": ")
@@ -459,24 +466,26 @@ print_type :: proc "contextless" (ti: ^Type_Info) {
 		}
 		}
 		print_byte(']')
 		print_byte(']')
 
 
+	case Type_Info_Bit_Field:
+		print_string("bit_field ")
+		print_type(info.backing_type)
+		print_string(" {")
+		for name, i in info.names[:info.field_count] {
+			if i > 0 { print_string(", ") }
+			print_string(name)
+			print_string(": ")
+			print_type(info.types[i])
+			print_string(" | ")
+			print_u64(u64(info.bit_sizes[i]))
+		}
+		print_byte('}')
+
 
 
 	case Type_Info_Simd_Vector:
 	case Type_Info_Simd_Vector:
 		print_string("#simd[")
 		print_string("#simd[")
 		print_u64(u64(info.count))
 		print_u64(u64(info.count))
 		print_byte(']')
 		print_byte(']')
 		print_type(info.elem)
 		print_type(info.elem)
-
-	case Type_Info_Relative_Pointer:
-		print_string("#relative(")
-		print_type(info.base_integer)
-		print_string(") ")
-		print_type(info.pointer)
-
-	case Type_Info_Relative_Multi_Pointer:
-		print_string("#relative(")
-		print_type(info.base_integer)
-		print_string(") ")
-		print_type(info.pointer)
 		
 		
 	case Type_Info_Matrix:
 	case Type_Info_Matrix:
 		print_string("matrix[")
 		print_string("matrix[")

+ 27 - 10
core/runtime/procs.odin → base/runtime/procs.odin

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

+ 2 - 2
core/runtime/procs_darwin.odin → base/runtime/procs_darwin.odin

@@ -1,9 +1,9 @@
-//+private
+#+private
 package runtime
 package runtime
 
 
 foreign import "system:Foundation.framework"
 foreign import "system:Foundation.framework"
 
 
-import "core:intrinsics"
+import "base:intrinsics"
 
 
 objc_id :: ^intrinsics.objc_object
 objc_id :: ^intrinsics.objc_object
 objc_Class :: ^intrinsics.objc_class
 objc_Class :: ^intrinsics.objc_class

+ 1 - 1
core/runtime/procs_js.odin → base/runtime/procs_js.odin

@@ -1,4 +1,4 @@
-//+build js
+#+build js
 package runtime
 package runtime
 
 
 init_default_context_for_js: Context
 init_default_context_for_js: Context

+ 97 - 0
base/runtime/procs_wasm.odin

@@ -0,0 +1,97 @@
+#+build wasm32, wasm64p32
+package runtime
+
+@(private="file")
+ti_int :: struct #raw_union {
+	using s: struct { lo, hi: u64 },
+	all: i128,
+}
+
+@(private="file")
+ti_uint :: struct #raw_union {
+	using s: struct { lo, hi: u64 },
+	all: u128,
+}
+
+@(link_name="__ashlti3", linkage="strong")
+__ashlti3 :: proc "contextless" (a: i128, b: u32) -> i128 {
+	bits :: 64
+	
+	input: ti_int = ---
+	result: ti_int = ---
+	input.all = a
+	if b & bits != 0 {
+		result.lo = 0
+		result.hi = input.lo << (b-bits)
+	} else {
+		if b == 0 {
+			return a
+		}
+		result.lo = input.lo<<b
+		result.hi = (input.hi<<b) | (input.lo>>(bits-b))
+	}
+	return result.all
+}
+
+__ashlti3_unsigned :: proc "contextless" (a: u128, b: u32) -> u128 {
+	return cast(u128)__ashlti3(cast(i128)a, b)
+}
+
+@(link_name="__mulddi3", linkage="strong")
+__mulddi3 :: proc "contextless" (a, b: u64) -> i128 {
+	r: ti_int
+	bits :: 32
+
+	mask :: ~u64(0) >> bits
+	r.lo = (a & mask) * (b & mask)
+	t := r.lo >> bits
+	r.lo &= mask
+	t += (a >> bits) * (b & mask)
+	r.lo += (t & mask) << bits
+	r.hi = t >> bits
+	t = r.lo >> bits
+	r.lo &= mask
+	t += (b >> bits) * (a & mask)
+	r.lo += (t & mask) << bits
+	r.hi += t >> bits
+	r.hi += (a >> bits) * (b >> bits)
+	return r.all
+}
+
+@(link_name="__multi3", linkage="strong")
+__multi3 :: proc "contextless" (a, b: i128) -> i128 {
+	x, y, r: ti_int
+
+	x.all = a
+	y.all = b
+	r.all = __mulddi3(x.lo, y.lo)
+	r.hi += x.hi*y.lo + x.lo*y.hi
+	return r.all
+}
+
+@(link_name="__udivti3", linkage="strong")
+udivti3 :: proc "c" (la, ha, lb, hb: u64) -> u128 {
+	a, b: ti_uint
+	a.lo, a.hi = la, ha
+	b.lo, b.hi = lb, hb
+	return udivmodti4(a.all, b.all, nil)
+}
+
+@(link_name="__lshrti3", linkage="strong")
+__lshrti3 :: proc "c" (a: i128, b: u32) -> i128 {
+	bits :: 64
+
+	input, result: ti_int
+	input.all = a
+	if b & bits != 0 {
+		result.hi = 0
+		result.lo = input.hi >> (b - bits)
+	} else if b == 0 {
+		return a
+	} else {
+		result.hi = input.hi >> b
+		result.lo = (input.hi << (bits - b)) | (input.lo >> b)
+	}
+
+	return result.all
+}

+ 0 - 0
core/runtime/procs_windows_amd64.asm → base/runtime/procs_windows_amd64.asm


+ 3 - 2
core/runtime/procs_windows_amd64.odin → base/runtime/procs_windows_amd64.odin

@@ -1,11 +1,12 @@
-//+private
+#+private
+#+no-instrumentation
 package runtime
 package runtime
 
 
 foreign import kernel32 "system:Kernel32.lib"
 foreign import kernel32 "system:Kernel32.lib"
 
 
 @(private)
 @(private)
 foreign kernel32 {
 foreign kernel32 {
-	RaiseException :: proc "stdcall" (dwExceptionCode, dwExceptionFlags, nNumberOfArguments: u32, lpArguments: ^uint) -> ! ---
+	RaiseException :: proc "system" (dwExceptionCode, dwExceptionFlags, nNumberOfArguments: u32, lpArguments: ^uint) -> ! ---
 }
 }
 
 
 windows_trap_array_bounds :: proc "contextless" () -> ! {
 windows_trap_array_bounds :: proc "contextless" () -> ! {

+ 3 - 2
core/runtime/procs_windows_i386.odin → base/runtime/procs_windows_i386.odin

@@ -1,4 +1,5 @@
-//+private
+#+private
+#+no-instrumentation
 package runtime
 package runtime
 
 
 @require foreign import "system:int64.lib"
 @require foreign import "system:int64.lib"
@@ -12,7 +13,7 @@ windows_trap_array_bounds :: proc "contextless" () -> ! {
 	EXCEPTION_ARRAY_BOUNDS_EXCEEDED :: 0xC000008C
 	EXCEPTION_ARRAY_BOUNDS_EXCEEDED :: 0xC000008C
 
 
 	foreign kernel32 {
 	foreign kernel32 {
-		RaiseException :: proc "stdcall" (dwExceptionCode, dwExceptionFlags, nNumberOfArguments: DWORD, lpArguments: ^ULONG_PTR) -> ! ---
+		RaiseException :: proc "system" (dwExceptionCode, dwExceptionFlags, nNumberOfArguments: DWORD, lpArguments: ^ULONG_PTR) -> ! ---
 	}
 	}
 
 
 	RaiseException(EXCEPTION_ARRAY_BOUNDS_EXCEEDED, 0, 0, nil)
 	RaiseException(EXCEPTION_ARRAY_BOUNDS_EXCEEDED, 0, 0, nil)

+ 127 - 0
base/runtime/random_generator.odin

@@ -0,0 +1,127 @@
+package runtime
+
+import "base:intrinsics"
+
+@(require_results)
+random_generator_read_bytes :: proc(rg: Random_Generator, p: []byte) -> bool {
+	if rg.procedure != nil {
+		rg.procedure(rg.data, .Read, p)
+		return true
+	}
+	return false
+}
+
+@(require_results)
+random_generator_read_ptr :: proc(rg: Random_Generator, p: rawptr, len: uint) -> bool {
+	if rg.procedure != nil {
+		rg.procedure(rg.data, .Read, ([^]byte)(p)[:len])
+		return true
+	}
+	return false
+}
+
+@(require_results)
+random_generator_query_info :: proc(rg: Random_Generator) -> (info: Random_Generator_Query_Info) {
+	if rg.procedure != nil {
+		rg.procedure(rg.data, .Query_Info, ([^]byte)(&info)[:size_of(info)])
+	}
+	return
+}
+
+
+random_generator_reset_bytes :: proc(rg: Random_Generator, p: []byte) {
+	if rg.procedure != nil {
+		rg.procedure(rg.data, .Reset, p)
+	}
+}
+
+random_generator_reset_u64 :: proc(rg: Random_Generator, p: u64) {
+	if rg.procedure != nil {
+		p := p
+		rg.procedure(rg.data, .Reset, ([^]byte)(&p)[:size_of(p)])
+	}
+}
+
+
+Default_Random_State :: struct {
+	state: u64,
+	inc:   u64,
+}
+
+default_random_generator_proc :: proc(data: rawptr, mode: Random_Generator_Mode, p: []byte) {
+	@(require_results)
+	read_u64 :: proc "contextless" (r: ^Default_Random_State) -> u64 {
+		old_state := r.state
+		r.state = old_state * 6364136223846793005 + (r.inc|1)
+		xor_shifted := (((old_state >> 59) + 5) ~ old_state) * 12605985483714917081
+		rot := (old_state >> 59)
+		return (xor_shifted >> rot) | (xor_shifted << ((-rot) & 63))
+	}
+
+	@(thread_local)
+	global_rand_seed: Default_Random_State
+
+	init :: proc "contextless" (r: ^Default_Random_State, seed: u64) {
+		seed := seed
+		if seed == 0 {
+			seed = u64(intrinsics.read_cycle_counter())
+		}
+		r.state = 0
+		r.inc = (seed << 1) | 1
+		_ = read_u64(r)
+		r.state += seed
+		_ = read_u64(r)
+	}
+
+	r: ^Default_Random_State = ---
+	if data == nil {
+		r = &global_rand_seed
+	} else {
+		r = cast(^Default_Random_State)data
+	}
+
+	switch mode {
+	case .Read:
+		if r.state == 0 && r.inc == 0 {
+			init(r, 0)
+		}
+
+		switch len(p) {
+		case size_of(u64):
+			// Fast path for a 64-bit destination.
+			intrinsics.unaligned_store((^u64)(raw_data(p)), read_u64(r))
+		case:
+			// All other cases.
+			pos := i8(0)
+			val := u64(0)
+			for &v in p {
+				if pos == 0 {
+					val = read_u64(r)
+					pos = 7
+				}
+				v = byte(val)
+				val >>= 8
+				pos -= 1
+			}
+		}
+
+	case .Reset:
+		seed: u64
+		mem_copy_non_overlapping(&seed, raw_data(p), min(size_of(seed), len(p)))
+		init(r, seed)
+
+	case .Query_Info:
+		if len(p) != size_of(Random_Generator_Query_Info) {
+			return
+		}
+		info := (^Random_Generator_Query_Info)(raw_data(p))
+		info^ += {.Uniform, .Resettable}
+	}
+}
+
+default_random_generator :: proc "contextless" (state: ^Default_Random_State = nil) -> Random_Generator {
+	return {
+		procedure = default_random_generator_proc,
+		data = state,
+	}
+}

+ 34 - 0
base/runtime/thread_management.odin

@@ -0,0 +1,34 @@
+package runtime
+
+Thread_Local_Cleaner :: #type proc "odin" ()
+
+@(private="file")
+thread_local_cleaners: [8]Thread_Local_Cleaner
+
+// Add a procedure that will be run at the end of a thread for the purpose of
+// deallocating state marked as `thread_local`.
+//
+// Intended to be called in an `init` procedure of a package with
+// dynamically-allocated memory that is stored in `thread_local` variables.
+add_thread_local_cleaner :: proc "contextless" (p: Thread_Local_Cleaner) {
+	for &v in thread_local_cleaners {
+		if v == nil {
+			v = p
+			return
+		}
+	}
+	panic_contextless("There are no more thread-local cleaner slots available.")
+}
+
+// Run all of the thread-local cleaner procedures.
+//
+// Intended to be called by the internals of a threading API at the end of a
+// thread's lifetime.
+run_thread_local_cleaners :: proc "odin" () {
+	for p in thread_local_cleaners {
+		if p == nil {
+			break
+		}
+		p()
+	}
+}

+ 4 - 4
core/runtime/udivmod128.odin → base/runtime/udivmod128.odin

@@ -1,6 +1,6 @@
 package runtime
 package runtime
 
 
-import "core:intrinsics"
+import "base:intrinsics"
 
 
 udivmod128 :: proc "c" (a, b: u128, rem: ^u128) -> u128 {
 udivmod128 :: proc "c" (a, b: u128, rem: ^u128) -> u128 {
 	_ctz :: intrinsics.count_trailing_zeros
 	_ctz :: intrinsics.count_trailing_zeros
@@ -58,7 +58,7 @@ udivmod128 :: proc "c" (a, b: u128, rem: ^u128) -> u128 {
 			return u128(n[high] >> _ctz(d[high]))
 			return u128(n[high] >> _ctz(d[high]))
 		}
 		}
 
 
-		sr = transmute(u32)(i32(_clz(d[high])) - i32(_clz(n[high])))
+		sr = u32(i32(_clz(d[high])) - i32(_clz(n[high])))
 		if sr > U64_BITS - 2 {
 		if sr > U64_BITS - 2 {
 			if rem != nil {
 			if rem != nil {
 				rem^ = a
 				rem^ = a
@@ -107,7 +107,7 @@ udivmod128 :: proc "c" (a, b: u128, rem: ^u128) -> u128 {
 				r[low]  = n[high] >> (sr - U64_BITS)
 				r[low]  = n[high] >> (sr - U64_BITS)
 			}
 			}
 		} else {
 		} else {
-			sr = transmute(u32)(i32(_clz(d[high])) - i32(_clz(n[high])))
+			sr = u32(i32(_clz(d[high])) - i32(_clz(n[high])))
 
 
 			if sr > U64_BITS - 1 {
 			if sr > U64_BITS - 1 {
 				if rem != nil {
 				if rem != nil {
@@ -143,7 +143,7 @@ udivmod128 :: proc "c" (a, b: u128, rem: ^u128) -> u128 {
 		r_all = transmute(u128)r
 		r_all = transmute(u128)r
 		s := i128(b - r_all - 1) >> (U128_BITS - 1)
 		s := i128(b - r_all - 1) >> (U128_BITS - 1)
 		carry = u32(s & 1)
 		carry = u32(s & 1)
-		r_all -= b & transmute(u128)s
+		r_all -= b & u128(s)
 		r = transmute([2]u64)r_all
 		r = transmute([2]u64)r_all
 	}
 	}
 
 

+ 871 - 0
base/runtime/wasm_allocator.odin

@@ -0,0 +1,871 @@
+#+build wasm32, wasm64p32
+package runtime
+
+import "base:intrinsics"
+
+/*
+Port of emmalloc, modified for use in Odin.
+
+Invariants:
+	- Per-allocation header overhead is 8 bytes, smallest allocated payload
+	  amount is 8 bytes, and a multiple of 4 bytes.
+	- Acquired memory blocks are subdivided into disjoint regions that lie
+	  next to each other.
+	- A region is either in used or free.
+	  Used regions may be adjacent, and a used and unused region
+	  may be adjacent, but not two unused ones - they would be
+	  merged.
+	- Memory allocation takes constant time, unless the alloc needs to wasm_memory_grow()
+	  or memory is very close to being exhausted.
+	- Free and used regions are managed inside "root regions", which are slabs
+	  of memory acquired via wasm_memory_grow().
+	- Memory retrieved using wasm_memory_grow() can not be given back to the OS.
+	  Therefore, frees are internal to the allocator.
+
+Copyright (c) 2010-2014 Emscripten authors, see AUTHORS file.
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.
+*/
+
+WASM_Allocator :: struct #no_copy {
+	// The minimum alignment of allocations.
+	alignment: uint,
+	// A region that contains as payload a single forward linked list of pointers to
+	// root regions of each disjoint region blocks.
+	list_of_all_regions: ^Root_Region,
+	// For each of the buckets, maintain a linked list head node. The head node for each
+	// free region is a sentinel node that does not actually represent any free space, but
+	// the sentinel is used to avoid awkward testing against (if node == freeRegionHeadNode)
+	// when adding and removing elements from the linked list, i.e. we are guaranteed that
+	// the sentinel node is always fixed and there, and the actual free region list elements
+	// start at free_region_buckets[i].next each.
+	free_region_buckets: [NUM_FREE_BUCKETS]Region,
+	// A bitmask that tracks the population status for each of the 64 distinct memory regions:
+	// a zero at bit position i means that the free list bucket i is empty. This bitmask is
+	// used to avoid redundant scanning of the 64 different free region buckets: instead by
+	// looking at the bitmask we can find in constant time an index to a free region bucket
+	// that contains free memory of desired size.
+	free_region_buckets_used: BUCKET_BITMASK_T,
+	// Because wasm memory can only be allocated in pages of 64k at a time, we keep any
+	// spilled/unused bytes that are left from the allocated pages here, first using this
+	// when bytes are needed.
+	spill: []byte,
+	// Mutex for thread safety, only used if the target feature "atomics" is enabled.
+	mu: Mutex_State,
+}
+
+// Not required to be called, called on first allocation otherwise.
+wasm_allocator_init :: proc(a: ^WASM_Allocator, alignment: uint = 8) {
+	assert(is_power_of_two(alignment), "alignment must be a power of two")
+	assert(alignment > 4, "alignment must be more than 4")
+
+	a.alignment = alignment
+
+	for i in 0..<NUM_FREE_BUCKETS {
+		a.free_region_buckets[i].next = &a.free_region_buckets[i]
+		a.free_region_buckets[i].prev = a.free_region_buckets[i].next
+	}
+
+	if !claim_more_memory(a, 3*size_of(Region)) {
+		panic("wasm_allocator: initial memory could not be allocated")
+	}
+}
+
+global_default_wasm_allocator_data: WASM_Allocator
+
+default_wasm_allocator :: proc() -> Allocator {
+	return wasm_allocator(&global_default_wasm_allocator_data)
+}
+
+wasm_allocator :: proc(a: ^WASM_Allocator) -> Allocator {
+	return {
+		data      = a,
+		procedure = wasm_allocator_proc,
+	}
+}
+
+wasm_allocator_proc :: proc(a: rawptr, mode: Allocator_Mode, size, alignment: int, old_memory: rawptr, old_size: int, loc := #caller_location) -> ([]byte, Allocator_Error) {
+	a := (^WASM_Allocator)(a)
+	if a == nil {
+		a = &global_default_wasm_allocator_data
+	}
+
+	if a.alignment == 0 {
+		wasm_allocator_init(a)
+	}
+
+	switch mode {
+	case .Alloc:
+		ptr := aligned_alloc(a, uint(alignment), uint(size), loc)
+		if ptr == nil {
+			return nil, .Out_Of_Memory
+		}
+		intrinsics.mem_zero(ptr, size)
+		return ([^]byte)(ptr)[:size], nil
+
+	case .Alloc_Non_Zeroed:
+		ptr := aligned_alloc(a, uint(alignment), uint(size), loc)
+		if ptr == nil {
+			return nil, .Out_Of_Memory
+		}
+		return ([^]byte)(ptr)[:size], nil
+
+	case .Resize:
+		ptr := aligned_realloc(a, old_memory, uint(alignment), uint(size), loc)
+		if ptr == nil {
+			return nil, .Out_Of_Memory
+		}
+
+		bytes := ([^]byte)(ptr)[:size]
+
+		if size > old_size {
+			new_region := raw_data(bytes[old_size:])
+			intrinsics.mem_zero(new_region, size - old_size)
+		}
+
+		return bytes, nil
+
+	case .Resize_Non_Zeroed:
+		ptr := aligned_realloc(a, old_memory, uint(alignment), uint(size), loc)
+		if ptr == nil {
+			return nil, .Out_Of_Memory
+		}
+		return ([^]byte)(ptr)[:size], nil
+
+	case .Free:
+		free(a, old_memory, loc)
+		return nil, nil
+
+	case .Free_All, .Query_Info:
+		return nil, .Mode_Not_Implemented
+
+	case .Query_Features:
+		set := (^Allocator_Mode_Set)(old_memory)
+		if set != nil {
+			set^ = {.Alloc, .Alloc_Non_Zeroed, .Free, .Resize, .Resize_Non_Zeroed, .Query_Features }
+		}
+		return nil, nil
+	}
+
+	unreachable()
+}
+
+// Returns the allocated size of the allocator (both free and used).
+// If `nil` is given, the global allocator is used.
+wasm_allocator_size :: proc(a: ^WASM_Allocator = nil) -> (size: uint) {
+	a := a
+	if a == nil {
+		a = &global_default_wasm_allocator_data
+	}
+
+	lock(a)
+	defer unlock(a)
+
+	root := a.list_of_all_regions
+	for root != nil {
+		size += uint(uintptr(root.end_ptr) - uintptr(root))
+		root = root.next
+	}
+
+	size += len(a.spill)
+
+	return
+}
+
+// Returns the amount of free memory on the allocator.
+// If `nil` is given, the global allocator is used.
+wasm_allocator_free_space :: proc(a: ^WASM_Allocator = nil) -> (free: uint) {
+	a := a
+	if a == nil {
+		a = &global_default_wasm_allocator_data
+	}
+
+	lock(a)
+	defer unlock(a)
+
+	bucket_index: u64 = 0
+	bucket_mask := a.free_region_buckets_used
+
+	for bucket_mask != 0 {
+		index_add := intrinsics.count_trailing_zeros(bucket_mask)
+		bucket_index += index_add
+		bucket_mask >>= index_add
+		for free_region := a.free_region_buckets[bucket_index].next; free_region != &a.free_region_buckets[bucket_index]; free_region = free_region.next {
+			free += free_region.size - REGION_HEADER_SIZE
+		}
+		bucket_index += 1
+		bucket_mask >>= 1
+	}
+
+	free += len(a.spill)
+
+	return
+}
+
+@(private="file")
+NUM_FREE_BUCKETS :: 64
+@(private="file")
+BUCKET_BITMASK_T :: u64
+
+// Dynamic memory is subdivided into regions, in the format
+
+// <size:u32> ..... <size:u32> | <size:u32> ..... <size:u32> | <size:u32> ..... <size:u32> | .....
+
+// That is, at the bottom and top end of each memory region, the size of that region is stored. That allows traversing the
+// memory regions backwards and forwards. Because each allocation must be at least a multiple of 4 bytes, the lowest two bits of
+// each size field is unused. Free regions are distinguished by used regions by having the FREE_REGION_FLAG bit present
+// in the size field. I.e. for free regions, the size field is odd, and for used regions, the size field reads even.
+@(private="file")
+FREE_REGION_FLAG :: 0x1
+
+// Attempts to alloc more than this many bytes would cause an overflow when calculating the size of a region,
+// therefore allocations larger than this are short-circuited immediately on entry.
+@(private="file")
+MAX_ALLOC_SIZE :: 0xFFFFFFC7
+
+// A free region has the following structure:
+// <size:uint> <prevptr> <nextptr> ... <size:uint>
+
+@(private="file")
+Region :: struct {
+	size: uint,
+	prev, next: ^Region,
+	_at_the_end_of_this_struct_size: uint,
+}
+
+// Each memory block starts with a Root_Region at the beginning.
+// The Root_Region specifies the size of the region block, and forms a linked
+// list of all Root_Regions in the program, starting with `list_of_all_regions`
+// below.
+@(private="file")
+Root_Region :: struct {
+	size:    u32,
+	next:    ^Root_Region,
+	end_ptr: ^byte,
+}
+
+@(private="file")
+Mutex_State :: enum u32 {
+	Unlocked = 0,
+	Locked   = 1,
+	Waiting  = 2,
+}
+
+@(private="file")
+lock :: proc(a: ^WASM_Allocator) {
+	when intrinsics.has_target_feature("atomics") {
+		@(cold)
+		lock_slow :: proc(a: ^WASM_Allocator, curr_state: Mutex_State) {
+			new_state := curr_state // Make a copy of it
+
+			spin_lock: for spin in 0..<i32(100) {
+				state, ok := intrinsics.atomic_compare_exchange_weak_explicit(&a.mu, .Unlocked, new_state, .Acquire, .Consume)
+				if ok {
+					return
+				}
+
+				if state == .Waiting {
+					break spin_lock
+				}
+
+				for i := min(spin+1, 32); i > 0; i -= 1 {
+					intrinsics.cpu_relax()
+				}
+			}
+
+			// Set just in case 100 iterations did not do it
+			new_state = .Waiting
+
+			for {
+				if intrinsics.atomic_exchange_explicit(&a.mu, .Waiting, .Acquire) == .Unlocked {
+					return
+				}
+
+				ret := intrinsics.wasm_memory_atomic_wait32((^u32)(&a.mu), u32(new_state), -1)
+				assert(ret != 0)
+				intrinsics.cpu_relax()
+			}
+		}
+
+
+		if v := intrinsics.atomic_exchange_explicit(&a.mu, .Locked, .Acquire); v != .Unlocked {
+			lock_slow(a, v)
+		}
+	}
+}
+
+@(private="file")
+unlock :: proc(a: ^WASM_Allocator) {
+	when intrinsics.has_target_feature("atomics") {
+		@(cold)
+		unlock_slow :: proc(a: ^WASM_Allocator) {
+			for {
+				s := intrinsics.wasm_memory_atomic_notify32((^u32)(&a.mu), 1)
+				if s >= 1 {
+					return
+				}
+			}
+		}
+
+		switch intrinsics.atomic_exchange_explicit(&a.mu, .Unlocked, .Release) {
+		case .Unlocked:
+			unreachable()
+		case .Locked:
+		// Okay
+		case .Waiting:
+			unlock_slow(a)
+		}
+	}
+}
+
+@(private="file")
+assert_locked :: proc(a: ^WASM_Allocator) {
+	when intrinsics.has_target_feature("atomics") {
+		assert(intrinsics.atomic_load(&a.mu) != .Unlocked)
+	}
+}
+
+@(private="file")
+has_alignment_uintptr :: proc(ptr: uintptr, #any_int alignment: uintptr) -> bool {
+	return ptr & (alignment-1) == 0
+}
+
+@(private="file")
+has_alignment_uint :: proc(ptr: uint, alignment: uint) -> bool {
+	return ptr & (alignment-1) == 0
+}
+
+@(private="file")
+has_alignment :: proc {
+	has_alignment_uintptr,
+	has_alignment_uint,
+}
+
+@(private="file")
+REGION_HEADER_SIZE :: 2*size_of(uint)
+
+@(private="file")
+SMALLEST_ALLOCATION_SIZE :: 2*size_of(rawptr)
+
+// Subdivide regions of free space into distinct circular doubly linked lists, where each linked list
+// represents a range of free space blocks. The following function compute_free_list_bucket() converts
+// an allocation size to the bucket index that should be looked at.
+#assert(NUM_FREE_BUCKETS == 64, "Following function is tailored specifically for the NUM_FREE_BUCKETS == 64 case")
+@(private="file")
+compute_free_list_bucket :: proc(size: uint) -> uint {
+	if size < 128 { return (size >> 3) - 1 }
+
+	clz := intrinsics.count_leading_zeros(i32(size))
+	bucket_index: i32 = ((clz > 19) \
+		?     110 - (clz<<2) + ((i32)(size >> (u32)(29-clz)) ~ 4) \
+		: min( 71 - (clz<<1) + ((i32)(size >> (u32)(30-clz)) ~ 2), NUM_FREE_BUCKETS-1))
+
+	assert(bucket_index >= 0)
+	assert(bucket_index < NUM_FREE_BUCKETS)
+	return uint(bucket_index)
+}
+
+@(private="file")
+prev_region :: proc(region: ^Region) -> ^Region {
+	prev_region_size := ([^]uint)(region)[-1]
+	prev_region_size  = prev_region_size & ~uint(FREE_REGION_FLAG)
+	return (^Region)(uintptr(region)-uintptr(prev_region_size))
+}
+
+@(private="file")
+next_region :: proc(region: ^Region) -> ^Region {
+	return (^Region)(uintptr(region)+uintptr(region.size))
+}
+
+@(private="file")
+region_ceiling_size :: proc(region: ^Region) -> uint {
+	return ([^]uint)(uintptr(region)+uintptr(region.size))[-1]
+}
+
+@(private="file")
+region_is_free :: proc(r: ^Region) -> bool {
+	return region_ceiling_size(r) & FREE_REGION_FLAG >= 1
+}
+
+@(private="file")
+region_is_in_use :: proc(r: ^Region) -> bool {
+	return r.size == region_ceiling_size(r)
+}
+
+@(private="file")
+region_payload_start_ptr :: proc(r: ^Region) -> [^]byte {
+	return ([^]byte)(r)[size_of(uint):]
+}
+
+@(private="file")
+region_payload_end_ptr :: proc(r: ^Region) -> [^]byte {
+	return ([^]byte)(r)[r.size-size_of(uint):]
+}
+
+@(private="file")
+create_used_region :: proc(ptr: rawptr, size: uint) {
+	assert(has_alignment(uintptr(ptr), size_of(uint)))
+	assert(has_alignment(size, size_of(uint)))
+	assert(size >= size_of(Region))
+
+	uptr := ([^]uint)(ptr)
+	uptr[0] = size
+	uptr[size/size_of(uint)-1] = size
+}
+
+@(private="file")
+create_free_region :: proc(ptr: rawptr, size: uint) {
+	assert(has_alignment(uintptr(ptr), size_of(uint)))
+	assert(has_alignment(size, size_of(uint)))
+	assert(size >= size_of(Region))
+
+	free_region := (^Region)(ptr)
+	free_region.size = size
+	([^]uint)(ptr)[size/size_of(uint)-1] = size | FREE_REGION_FLAG
+}
+
+@(private="file")
+prepend_to_free_list :: proc(region: ^Region, prepend_to: ^Region) {
+	assert(region_is_free(region))
+	region.next = prepend_to
+	region.prev = prepend_to.prev
+	prepend_to.prev = region
+	region.prev.next = region
+}
+
+@(private="file")
+unlink_from_free_list :: proc(region: ^Region) {
+	assert(region_is_free(region))
+	region.prev.next = region.next
+	region.next.prev = region.prev
+}
+
+@(private="file")
+link_to_free_list :: proc(a: ^WASM_Allocator, free_region: ^Region) {
+	assert(free_region.size >= size_of(Region))
+	bucket_index := compute_free_list_bucket(free_region.size-REGION_HEADER_SIZE)
+	free_list_head := &a.free_region_buckets[bucket_index]
+	free_region.prev = free_list_head
+	free_region.next = free_list_head.next
+	free_list_head.next = free_region
+	free_region.next.prev = free_region
+	a.free_region_buckets_used |= BUCKET_BITMASK_T(1) << bucket_index
+}
+
+@(private="file")
+claim_more_memory :: proc(a: ^WASM_Allocator, num_bytes: uint) -> bool {
+
+	PAGE_SIZE :: 64 * 1024
+
+	page_alloc :: proc(page_count: int) -> []byte {
+		prev_page_count := intrinsics.wasm_memory_grow(0, uintptr(page_count))
+		if prev_page_count < 0 { return nil }
+
+		ptr := ([^]byte)(uintptr(prev_page_count) * PAGE_SIZE)
+		return ptr[:page_count * PAGE_SIZE]
+	}
+
+	alloc :: proc(a: ^WASM_Allocator, num_bytes: uint) -> (bytes: [^]byte) #no_bounds_check {
+		if uint(len(a.spill)) >= num_bytes {
+			bytes = raw_data(a.spill[:num_bytes])
+			a.spill = a.spill[num_bytes:]
+			return
+		}
+
+		pages := int((num_bytes / PAGE_SIZE) + 1)
+		allocated := page_alloc(pages)
+		if allocated == nil { return nil }
+
+		// If the allocated memory is a direct continuation of the spill from before,
+		// we can just extend the spill.
+		spill_end := uintptr(raw_data(a.spill)) + uintptr(len(a.spill))
+		if spill_end == uintptr(raw_data(allocated)) {
+			raw_spill := (^Raw_Slice)(&a.spill)
+			raw_spill.len += len(allocated)
+		} else {
+			// Otherwise, we have to "waste" the previous spill.
+			// Now this is probably uncommon, and will only happen if another code path
+			// is also requesting pages.
+			a.spill = allocated
+		}
+
+		bytes = raw_data(a.spill)
+		a.spill = a.spill[num_bytes:]
+		return
+	}
+
+	num_bytes := num_bytes
+	num_bytes  = align_forward(num_bytes, a.alignment)
+
+	start_ptr := alloc(a, uint(num_bytes))
+	if start_ptr == nil { return false }
+
+	assert(has_alignment(uintptr(start_ptr), align_of(uint)))
+	end_ptr := start_ptr[num_bytes:]
+
+	end_sentinel_region := (^Region)(end_ptr[-size_of(Region):])
+	create_used_region(end_sentinel_region, size_of(Region))
+
+	// If we are the sole user of wasm_memory_grow(), it will feed us continuous/consecutive memory addresses - take advantage
+	// of that if so: instead of creating two disjoint memory regions blocks, expand the previous one to a larger size.
+	prev_alloc_end_address := a.list_of_all_regions != nil ? a.list_of_all_regions.end_ptr : nil
+	if start_ptr == prev_alloc_end_address {
+		prev_end_sentinel := prev_region((^Region)(start_ptr))
+		assert(region_is_in_use(prev_end_sentinel))
+		prev_region := prev_region(prev_end_sentinel)
+
+		a.list_of_all_regions.end_ptr = end_ptr
+
+		// Two scenarios, either the last region of the previous block was in use, in which case we need to create
+		// a new free region in the newly allocated space; or it was free, in which case we can extend that region
+		// to cover a larger size.
+		if region_is_free(prev_region) {
+			new_free_region_size := uint(uintptr(end_sentinel_region) - uintptr(prev_region))
+			unlink_from_free_list(prev_region)
+			create_free_region(prev_region, new_free_region_size)
+			link_to_free_list(a, prev_region)
+			return true
+		}
+
+		start_ptr = start_ptr[-size_of(Region):]
+	} else {
+		create_used_region(start_ptr, size_of(Region))
+
+		new_region_block := (^Root_Region)(start_ptr)
+		new_region_block.next = a.list_of_all_regions
+		new_region_block.end_ptr = end_ptr
+		a.list_of_all_regions = new_region_block
+		start_ptr = start_ptr[size_of(Region):]
+	}
+
+	create_free_region(start_ptr, uint(uintptr(end_sentinel_region)-uintptr(start_ptr)))
+	link_to_free_list(a, (^Region)(start_ptr))
+	return true
+}
+
+@(private="file")
+validate_alloc_size :: proc(size: uint) -> uint {
+	#assert(size_of(uint) >= size_of(uintptr))
+	#assert(size_of(uint)  % size_of(uintptr) == 0)
+
+	// NOTE: emmalloc aligns this forward on pointer size, but I think that is a mistake and will
+	// do bad on wasm64p32.
+
+	validated_size := size > SMALLEST_ALLOCATION_SIZE ? align_forward(size, size_of(uint)) : SMALLEST_ALLOCATION_SIZE
+	assert(validated_size >= size) // Assert we haven't wrapped.
+
+	return validated_size
+}
+
+@(private="file")
+allocate_memory :: proc(a: ^WASM_Allocator, alignment: uint, size: uint, loc := #caller_location) -> rawptr {
+
+	attempt_allocate :: proc(a: ^WASM_Allocator, free_region: ^Region, alignment, size: uint) -> rawptr {
+		assert_locked(a)
+		free_region := free_region
+
+		payload_start_ptr := uintptr(region_payload_start_ptr(free_region))
+		payload_start_ptr_aligned := align_forward(payload_start_ptr, uintptr(alignment))
+		payload_end_ptr := uintptr(region_payload_end_ptr(free_region))
+
+		if payload_start_ptr_aligned + uintptr(size) > payload_end_ptr {
+			return nil
+		}
+
+		// We have enough free space, so the memory allocation will be made into this region. Remove this free region
+		// from the list of free regions: whatever slop remains will be later added back to the free region pool.
+		unlink_from_free_list(free_region)
+
+		// Before we proceed further, fix up the boundary between this and the preceding region,
+		// so that the boundary between the two regions happens at a right spot for the payload to be aligned.
+		if payload_start_ptr != payload_start_ptr_aligned {
+			prev := prev_region(free_region)
+			assert(region_is_in_use(prev))
+			region_boundary_bump_amount := payload_start_ptr_aligned - payload_start_ptr
+			new_this_region_size := free_region.size - uint(region_boundary_bump_amount)
+			create_used_region(prev, prev.size + uint(region_boundary_bump_amount))
+			free_region = (^Region)(uintptr(free_region) + region_boundary_bump_amount)
+			free_region.size = new_this_region_size
+		}
+
+		// Next, we need to decide whether this region is so large that it should be split into two regions,
+		// one representing the newly used memory area, and at the high end a remaining leftover free area.
+		// This splitting to two is done always if there is enough space for the high end to fit a region.
+		// Carve 'size' bytes of payload off this region. So,
+		// [sz prev next sz]
+		// becomes
+		// [sz payload sz] [sz prev next sz]
+		if size_of(Region) + REGION_HEADER_SIZE + size <= free_region.size {
+			new_free_region := (^Region)(uintptr(free_region) + REGION_HEADER_SIZE + uintptr(size))
+			create_free_region(new_free_region, free_region.size - size - REGION_HEADER_SIZE)
+			link_to_free_list(a, new_free_region)
+			create_used_region(free_region, size + REGION_HEADER_SIZE)
+		} else {
+			// There is not enough space to split the free memory region into used+free parts, so consume the whole
+			// region as used memory, not leaving a free memory region behind.
+			// Initialize the free region as used by resetting the ceiling size to the same value as the size at bottom.
+			([^]uint)(uintptr(free_region) + uintptr(free_region.size))[-1] = free_region.size
+		}
+
+		return rawptr(uintptr(free_region) + size_of(uint))
+	}
+
+	assert_locked(a)
+	assert(is_power_of_two(alignment))
+	assert(size <= MAX_ALLOC_SIZE, "allocation too big", loc=loc)
+
+	alignment := alignment
+	alignment  = max(alignment, a.alignment)
+
+	size := size
+	size  = validate_alloc_size(size)
+
+	// Attempt to allocate memory starting from smallest bucket that can contain the required amount of memory.
+	// Under normal alignment conditions this should always be the first or second bucket we look at, but if
+	// performing an allocation with complex alignment, we may need to look at multiple buckets.
+	bucket_index := compute_free_list_bucket(size)
+	bucket_mask := a.free_region_buckets_used >> bucket_index
+
+	// Loop through each bucket that has free regions in it, based on bits set in free_region_buckets_used bitmap.
+	for bucket_mask != 0 {
+		index_add := intrinsics.count_trailing_zeros(bucket_mask)
+		bucket_index += uint(index_add)
+		bucket_mask >>= index_add
+		assert(bucket_index <= NUM_FREE_BUCKETS-1)
+		assert(a.free_region_buckets_used & (BUCKET_BITMASK_T(1) << bucket_index) > 0)
+
+		free_region := a.free_region_buckets[bucket_index].next
+		assert(free_region != nil)
+		if free_region != &a.free_region_buckets[bucket_index] {
+			ptr := attempt_allocate(a, free_region, alignment, size)
+			if ptr != nil {
+				return ptr
+			}
+
+			// We were not able to allocate from the first region found in this bucket, so penalize
+			// the region by cycling it to the end of the doubly circular linked list. (constant time)
+			// This provides a randomized guarantee that when performing allocations of size k to a
+			// bucket of [k-something, k+something] range, we will not always attempt to satisfy the
+			// allocation from the same available region at the front of the list, but we try each
+			// region in turn.
+			unlink_from_free_list(free_region)
+			prepend_to_free_list(free_region, &a.free_region_buckets[bucket_index])
+			// But do not stick around to attempt to look at other regions in this bucket - move
+			// to search the next populated bucket index if this did not fit. This gives a practical
+			// "allocation in constant time" guarantee, since the next higher bucket will only have
+			// regions that are all of strictly larger size than the requested allocation. Only if
+			// there is a difficult alignment requirement we may fail to perform the allocation from
+			// a region in the next bucket, and if so, we keep trying higher buckets until one of them
+			// works.
+			bucket_index += 1
+			bucket_mask >>= 1
+		} else {
+			// This bucket was not populated after all with any regions,
+			// but we just had a stale bit set to mark a populated bucket.
+			// Reset the bit to update latest status so that we do not
+			// redundantly look at this bucket again.
+			a.free_region_buckets_used &~= BUCKET_BITMASK_T(1) << bucket_index
+			bucket_mask ~= 1
+		}
+
+		assert((bucket_index == NUM_FREE_BUCKETS && bucket_mask == 0) || (bucket_mask == a.free_region_buckets_used >> bucket_index))
+	}
+
+	// None of the buckets were able to accommodate an allocation. If this happens we are almost out of memory.
+	// The largest bucket might contain some suitable regions, but we only looked at one region in that bucket, so
+	// as a last resort, loop through more free regions in the bucket that represents the largest allocations available.
+	// But only if the bucket representing largest allocations available is not any of the first thirty buckets,
+	// these represent allocatable areas less than <1024 bytes - which could be a lot of scrap.
+	// In such case, prefer to claim more memory right away.
+	largest_bucket_index := NUM_FREE_BUCKETS - 1 - intrinsics.count_leading_zeros(a.free_region_buckets_used)
+	// free_region will be null if there is absolutely no memory left. (all buckets are 100% used)
+	free_region := a.free_region_buckets_used > 0 ? a.free_region_buckets[largest_bucket_index].next : nil
+	// The 30 first free region buckets cover memory blocks < 2048 bytes, so skip looking at those here (too small)
+	if a.free_region_buckets_used >> 30 > 0 {
+		// Look only at a constant number of regions in this bucket max, to avoid bad worst case behavior.
+		// If this many regions cannot find free space, we give up and prefer to claim more memory instead.
+		max_regions_to_try_before_giving_up :: 99
+		num_tries_left := max_regions_to_try_before_giving_up
+		for ; free_region != &a.free_region_buckets[largest_bucket_index] && num_tries_left > 0; num_tries_left -= 1 {
+			ptr := attempt_allocate(a, free_region, alignment, size)
+			if ptr != nil {
+				return ptr
+			}
+			free_region = free_region.next
+		}
+	}
+
+	// We were unable to find a free memory region. Must claim more memory!
+	num_bytes_to_claim := size+size_of(Region)*3
+	if alignment > a.alignment {
+		num_bytes_to_claim += alignment
+	}
+	success := claim_more_memory(a, num_bytes_to_claim)
+	if (success) {
+		// Try allocate again with the newly available memory.
+		return allocate_memory(a, alignment, size)
+	}
+
+	// also claim_more_memory failed, we are really really constrained :( As a last resort, go back to looking at the
+	// bucket we already looked at above, continuing where the above search left off - perhaps there are
+	// regions we overlooked the first time that might be able to satisfy the allocation.
+	if free_region != nil {
+		for free_region != &a.free_region_buckets[largest_bucket_index] {
+			ptr := attempt_allocate(a, free_region, alignment, size)
+			if ptr != nil {
+				return ptr
+			}
+			free_region = free_region.next
+		}
+	}
+
+	// Fully out of memory.
+	return nil
+}
+
+@(private="file")
+aligned_alloc :: proc(a: ^WASM_Allocator, alignment, size: uint, loc := #caller_location) -> rawptr {
+	lock(a)
+	defer unlock(a)
+
+	return allocate_memory(a, alignment, size, loc)
+}
+
+@(private="file")
+free :: proc(a: ^WASM_Allocator, ptr: rawptr, loc := #caller_location) {
+	if ptr == nil {
+		return
+	}
+
+	region_start_ptr := uintptr(ptr) - size_of(uint)
+	region := (^Region)(region_start_ptr)
+	assert(has_alignment(region_start_ptr, size_of(uint)))
+
+	lock(a)
+	defer unlock(a)
+
+	size := region.size
+	assert(region_is_in_use(region), "double free or corrupt region", loc=loc)
+
+	prev_region_size_field := ([^]uint)(region)[-1]
+	prev_region_size := prev_region_size_field & ~uint(FREE_REGION_FLAG)
+	if prev_region_size_field != prev_region_size {
+		prev_region := (^Region)(uintptr(region) - uintptr(prev_region_size))
+		unlink_from_free_list(prev_region)
+		region_start_ptr = uintptr(prev_region)
+		size += prev_region_size
+	}
+
+	next_reg := next_region(region)
+	size_at_end := (^uint)(region_payload_end_ptr(next_reg))^
+	if next_reg.size != size_at_end {
+		unlink_from_free_list(next_reg)
+		size += next_reg.size
+	}
+
+	create_free_region(rawptr(region_start_ptr), size)
+	link_to_free_list(a, (^Region)(region_start_ptr))
+}
+
+@(private="file")
+aligned_realloc :: proc(a: ^WASM_Allocator, ptr: rawptr, alignment, size: uint, loc := #caller_location) -> rawptr {
+
+	attempt_region_resize :: proc(a: ^WASM_Allocator, region: ^Region, size: uint) -> bool {
+		lock(a)
+		defer unlock(a)
+
+		// First attempt to resize this region, if the next region that follows this one
+		// is a free region.
+		next_reg := next_region(region)
+		next_region_end_ptr := uintptr(next_reg) + uintptr(next_reg.size)
+		size_at_ceiling := ([^]uint)(next_region_end_ptr)[-1]
+		if next_reg.size != size_at_ceiling { // Next region is free?
+			assert(region_is_free(next_reg))
+			new_next_region_start_ptr := uintptr(region) + uintptr(size)
+			assert(has_alignment(new_next_region_start_ptr, size_of(uint)))
+			// Next region does not shrink to too small size?
+			if new_next_region_start_ptr + size_of(Region) <= next_region_end_ptr {
+				unlink_from_free_list(next_reg)
+				create_free_region(rawptr(new_next_region_start_ptr), uint(next_region_end_ptr - new_next_region_start_ptr))
+				link_to_free_list(a, (^Region)(new_next_region_start_ptr))
+				create_used_region(region, uint(new_next_region_start_ptr - uintptr(region)))
+				return true
+			}
+			// If we remove the next region altogether, allocation is satisfied?
+			if new_next_region_start_ptr <= next_region_end_ptr {
+				unlink_from_free_list(next_reg)
+				create_used_region(region, region.size + next_reg.size)
+				return true
+			}
+		} else {
+			// Next region is an used region - we cannot change its starting address. However if we are shrinking the
+			// size of this region, we can create a new free region between this and the next used region.
+			if size + size_of(Region) <= region.size {
+				free_region_size := region.size - size
+				create_used_region(region, size)
+				free_region := (^Region)(uintptr(region) + uintptr(size))
+				create_free_region(free_region, free_region_size)
+				link_to_free_list(a, free_region)
+				return true
+			} else if size <= region.size {
+				// Caller was asking to shrink the size, but due to not being able to fit a full Region in the shrunk
+				// area, we cannot actually do anything. This occurs if the shrink amount is really small. In such case,
+				// just call it success without doing any work.
+				return true
+			}
+		}
+
+		return false
+	}
+
+	if ptr == nil {
+		return aligned_alloc(a, alignment, size, loc)
+	}
+
+	if size == 0 {
+		free(a, ptr, loc)
+		return nil
+	}
+
+	if size > MAX_ALLOC_SIZE {
+		return nil
+	}
+
+	assert(is_power_of_two(alignment))
+	assert(has_alignment(uintptr(ptr), alignment), "realloc on different alignment than original allocation", loc=loc)
+
+	size := size
+	size  = validate_alloc_size(size)
+
+	region := (^Region)(uintptr(ptr) - size_of(uint))
+
+	// Attempt an in-place resize.
+	if attempt_region_resize(a, region, size + REGION_HEADER_SIZE) {
+		return ptr
+	}
+
+	// Can't do it in-place, allocate new region and copy over.
+	newptr := aligned_alloc(a, alignment, size, loc)
+	if newptr != nil {
+		intrinsics.mem_copy(newptr, ptr, min(size, region.size - REGION_HEADER_SIZE))
+		free(a, ptr, loc=loc)
+	}
+
+	return newptr
+}

+ 19 - 0
bin/RAD-LICENSE

@@ -0,0 +1,19 @@
+Copyright (c) 2024 Epic Games Tools
+
+Permission is hereby granted, free of charge, to any person obtaining a copy of
+this software and associated documentation files (the “Software”), to deal in
+the Software without restriction, including without limitation the rights to
+use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
+of the Software, and to permit persons to whom the Software is furnished to do
+so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.


BIN
bin/llvm/windows/LLVM-C.lib


BIN
bin/llvm/windows/clang_rt.asan-x86_64.lib




+ 46 - 8
build.bat

@@ -19,12 +19,16 @@ if "%VSCMD_ARG_TGT_ARCH%" neq "x64" (
 	)
 	)
 )
 )
 
 
-for /f "usebackq tokens=1,2 delims=,=- " %%i in (`wmic os get LocalDateTime /value`) do @if %%i==LocalDateTime (
-	set CURR_DATE_TIME=%%j
-)
+pushd misc
+cl /nologo get-date.c
+popd
 
 
+for /f %%i in ('misc\get-date') do (
+	set CURR_DATE_TIME=%%i
+)
 set curr_year=%CURR_DATE_TIME:~0,4%
 set curr_year=%CURR_DATE_TIME:~0,4%
 set curr_month=%CURR_DATE_TIME:~4,2%
 set curr_month=%CURR_DATE_TIME:~4,2%
+set curr_day=%CURR_DATE_TIME:~6,2%
 
 
 :: Make sure this is a decent name and not generic
 :: Make sure this is a decent name and not generic
 set exe_name=odin.exe
 set exe_name=odin.exe
@@ -45,23 +49,49 @@ if "%2" == "1" (
 	set nightly=0
 	set nightly=0
 )
 )
 
 
-set odin_version_raw="dev-%curr_year%-%curr_month%"
+if %release_mode% equ 0 (
+	set V1=%curr_year%
+	set V2=%curr_month%
+	set V3=%curr_day%
+) else (
+	set V1=%curr_year%
+	set V2=%curr_month%
+	set V3=0
+)
+set V4=0
+set odin_version_full="%V1%.%V2%.%V3%.%V4%"
+set odin_version_raw="dev-%V1%-%V2%"
 
 
 set compiler_flags= -nologo -Oi -TP -fp:precise -Gm- -MP -FC -EHsc- -GR- -GF
 set compiler_flags= -nologo -Oi -TP -fp:precise -Gm- -MP -FC -EHsc- -GR- -GF
+rem Parse source code as utf-8 even on shift-jis and other codepages
+rem See https://learn.microsoft.com/en-us/cpp/build/reference/utf-8-set-source-and-executable-character-sets-to-utf-8?view=msvc-170
+set compiler_flags= %compiler_flags% /utf-8
 set compiler_defines= -DODIN_VERSION_RAW=\"%odin_version_raw%\"
 set compiler_defines= -DODIN_VERSION_RAW=\"%odin_version_raw%\"
 
 
+rem fileversion is defined as {Major,Minor,Build,Private: u16} so a bit limited
+set rc_flags=-nologo ^
+-DV1=%V1% -DV2=%V2% -DV3=%V3% -DV4=%V4% ^
+-DVF=%odin_version_full% -DNIGHTLY=%nightly%
+
+where /Q git.exe || goto skip_git_hash
 if not exist .git\ goto skip_git_hash
 if not exist .git\ goto skip_git_hash
 for /f "tokens=1,2" %%i IN ('git show "--pretty=%%cd %%h" "--date=format:%%Y-%%m" --no-patch --no-notes HEAD') do (
 for /f "tokens=1,2" %%i IN ('git show "--pretty=%%cd %%h" "--date=format:%%Y-%%m" --no-patch --no-notes HEAD') do (
 	set odin_version_raw=dev-%%i
 	set odin_version_raw=dev-%%i
 	set GIT_SHA=%%j
 	set GIT_SHA=%%j
 )
 )
-if %ERRORLEVEL% equ 0 set compiler_defines=%compiler_defines% -DGIT_SHA=\"%GIT_SHA%\"
+if %ERRORLEVEL% equ 0 (
+	set compiler_defines=%compiler_defines% -DGIT_SHA=\"%GIT_SHA%\"
+	set rc_flags=%rc_flags% -DGIT_SHA=%GIT_SHA% -DVP=%odin_version_raw%:%GIT_SHA%
+) else (
+	set rc_flags=%rc_flags% -DVP=%odin_version_raw%
+)
 :skip_git_hash
 :skip_git_hash
 
 
 if %nightly% equ 1 set compiler_defines=%compiler_defines% -DNIGHTLY
 if %nightly% equ 1 set compiler_defines=%compiler_defines% -DNIGHTLY
 
 
 if %release_mode% EQU 0 ( rem Debug
 if %release_mode% EQU 0 ( rem Debug
 	set compiler_flags=%compiler_flags% -Od -MDd -Z7
 	set compiler_flags=%compiler_flags% -Od -MDd -Z7
+	set rc_flags=%rc_flags% -D_DEBUG
 ) else ( rem Release
 ) else ( rem Release
 	set compiler_flags=%compiler_flags% -O2 -MT -Z7
 	set compiler_flags=%compiler_flags% -O2 -MT -Z7
 	set compiler_defines=%compiler_defines% -DNO_ARRAY_BOUNDS_CHECK
 	set compiler_defines=%compiler_defines% -DNO_ARRAY_BOUNDS_CHECK
@@ -79,6 +109,8 @@ set libs= ^
 	kernel32.lib ^
 	kernel32.lib ^
 	Synchronization.lib ^
 	Synchronization.lib ^
 	bin\llvm\windows\LLVM-C.lib
 	bin\llvm\windows\LLVM-C.lib
+set odin_res=misc\odin.res
+set odin_rc=misc\odin.rc
 
 
 rem DO NOT TOUCH!
 rem DO NOT TOUCH!
 rem THIS TILDE STUFF IS FOR DEVELOPMENT ONLY!
 rem THIS TILDE STUFF IS FOR DEVELOPMENT ONLY!
@@ -90,7 +122,7 @@ if %tilde_backend% EQU 1 (
 rem DO NOT TOUCH!
 rem DO NOT TOUCH!
 
 
 
 
-set linker_flags= -incremental:no -opt:ref -subsystem:console
+set linker_flags= -incremental:no -opt:ref -subsystem:console -MANIFEST:EMBED
 
 
 if %release_mode% EQU 0 ( rem Debug
 if %release_mode% EQU 0 ( rem Debug
 	set linker_flags=%linker_flags% -debug /NATVIS:src\odin_compiler.natvis
 	set linker_flags=%linker_flags% -debug /NATVIS:src\odin_compiler.natvis
@@ -99,18 +131,24 @@ if %release_mode% EQU 0 ( rem Debug
 )
 )
 
 
 set compiler_settings=%compiler_includes% %compiler_flags% %compiler_warnings% %compiler_defines%
 set compiler_settings=%compiler_includes% %compiler_flags% %compiler_warnings% %compiler_defines%
-set linker_settings=%libs% %linker_flags%
+set linker_settings=%libs% %odin_res% %linker_flags%
 
 
 del *.pdb > NUL 2> NUL
 del *.pdb > NUL 2> NUL
 del *.ilk > NUL 2> NUL
 del *.ilk > NUL 2> NUL
 
 
+rc %rc_flags% %odin_rc%
 cl %compiler_settings% "src\main.cpp" "src\libtommath.cpp" /link %linker_settings% -OUT:%exe_name%
 cl %compiler_settings% "src\main.cpp" "src\libtommath.cpp" /link %linker_settings% -OUT:%exe_name%
+mt -nologo -inputresource:%exe_name%;#1 -manifest misc\odin.manifest -outputresource:%exe_name%;#1 -validate_manifest -identity:"odin, processorArchitecture=amd64, version=%odin_version_full%, type=win32"
 if %errorlevel% neq 0 goto end_of_build
 if %errorlevel% neq 0 goto end_of_build
 
 
 call build_vendor.bat
 call build_vendor.bat
 if %errorlevel% neq 0 goto end_of_build
 if %errorlevel% neq 0 goto end_of_build
 
 
-if %release_mode% EQU 0 odin run examples/demo
+rem If the demo doesn't run for you and your CPU is more than a decade old, try -microarch:native
+if %release_mode% EQU 0 odin run examples/demo -vet -strict-style -resource:examples/demo/demo.rc -- Hellope World
+
+rem Many non-compiler devs seem to run debug build but don't realize.
+if %release_mode% EQU 0 echo: & echo Debug compiler built. Note: run "build.bat release" if you want a faster, release mode compiler.
 
 
 del *.obj > NUL 2> NUL
 del *.obj > NUL 2> NUL
 
 

+ 68 - 21
build_odin.sh

@@ -2,7 +2,6 @@
 set -eu
 set -eu
 
 
 : ${CPPFLAGS=}
 : ${CPPFLAGS=}
-: ${CXX=clang++}
 : ${CXXFLAGS=}
 : ${CXXFLAGS=}
 : ${LDFLAGS=}
 : ${LDFLAGS=}
 : ${LLVM_CONFIG=}
 : ${LLVM_CONFIG=}
@@ -24,17 +23,32 @@ error() {
 	exit 1
 	exit 1
 }
 }
 
 
+# Brew advises people not to add llvm to their $PATH, so try and use brew to find it.
+if [ -z "$LLVM_CONFIG" ] &&  [ -n "$(command -v brew)" ]; then
+    if   [ -n "$(command -v $(brew --prefix llvm@19)/bin/llvm-config)" ]; then LLVM_CONFIG="$(brew --prefix llvm@19)/bin/llvm-config"
+    elif [ -n "$(command -v $(brew --prefix llvm@18)/bin/llvm-config)" ]; then LLVM_CONFIG="$(brew --prefix llvm@18)/bin/llvm-config"
+    elif [ -n "$(command -v $(brew --prefix llvm@17)/bin/llvm-config)" ]; then LLVM_CONFIG="$(brew --prefix llvm@17)/bin/llvm-config"
+    elif [ -n "$(command -v $(brew --prefix llvm@14)/bin/llvm-config)" ]; then LLVM_CONFIG="$(brew --prefix llvm@14)/bin/llvm-config"
+    fi
+fi
+
 if [ -z "$LLVM_CONFIG" ]; then
 if [ -z "$LLVM_CONFIG" ]; then
 	# darwin, linux, openbsd
 	# darwin, linux, openbsd
-	if   [ -n "$(command -v llvm-config-17)" ]; then LLVM_CONFIG="llvm-config-17"
+	if   [ -n "$(command -v llvm-config-19)" ]; then LLVM_CONFIG="llvm-config-19"
+	elif [ -n "$(command -v llvm-config-18)" ]; then LLVM_CONFIG="llvm-config-18"
+	elif [ -n "$(command -v llvm-config-17)" ]; then LLVM_CONFIG="llvm-config-17"
+	elif [ -n "$(command -v llvm-config-14)" ]; then LLVM_CONFIG="llvm-config-14"
 	elif [ -n "$(command -v llvm-config-13)" ]; then LLVM_CONFIG="llvm-config-13"
 	elif [ -n "$(command -v llvm-config-13)" ]; then LLVM_CONFIG="llvm-config-13"
 	elif [ -n "$(command -v llvm-config-12)" ]; then LLVM_CONFIG="llvm-config-12"
 	elif [ -n "$(command -v llvm-config-12)" ]; then LLVM_CONFIG="llvm-config-12"
 	elif [ -n "$(command -v llvm-config-11)" ]; then LLVM_CONFIG="llvm-config-11"
 	elif [ -n "$(command -v llvm-config-11)" ]; then LLVM_CONFIG="llvm-config-11"
 	# freebsd
 	# freebsd
-	elif [ -n "$(command -v llvm-config17)" ]; then  LLVM_CONFIG="llvm-config-17"
-	elif [ -n "$(command -v llvm-config13)" ]; then  LLVM_CONFIG="llvm-config-13"
-	elif [ -n "$(command -v llvm-config12)" ]; then  LLVM_CONFIG="llvm-config-12"
-	elif [ -n "$(command -v llvm-config11)" ]; then  LLVM_CONFIG="llvm-config-11"
+	elif [ -n "$(command -v llvm-config19)" ]; then  LLVM_CONFIG="llvm-config19"
+	elif [ -n "$(command -v llvm-config18)" ]; then  LLVM_CONFIG="llvm-config18"
+	elif [ -n "$(command -v llvm-config17)" ]; then  LLVM_CONFIG="llvm-config17"
+	elif [ -n "$(command -v llvm-config14)" ]; then  LLVM_CONFIG="llvm-config14"
+	elif [ -n "$(command -v llvm-config13)" ]; then  LLVM_CONFIG="llvm-config13"
+	elif [ -n "$(command -v llvm-config12)" ]; then  LLVM_CONFIG="llvm-config12"
+	elif [ -n "$(command -v llvm-config11)" ]; then  LLVM_CONFIG="llvm-config11"
 	# fallback
 	# fallback
 	elif [ -n "$(command -v llvm-config)" ]; then LLVM_CONFIG="llvm-config"
 	elif [ -n "$(command -v llvm-config)" ]; then LLVM_CONFIG="llvm-config"
 	else
 	else
@@ -42,42 +56,66 @@ if [ -z "$LLVM_CONFIG" ]; then
 	fi
 	fi
 fi
 fi
 
 
+if [ -x "$(which clang++)" ]; then
+	: ${CXX="clang++"}
+elif [ -x "$($LLVM_CONFIG --bindir)/clang++" ]; then
+	: ${CXX=$($LLVM_CONFIG --bindir)/clang++}
+else
+	error "No clang++ command found. Set CXX to proceed."
+fi
+
 LLVM_VERSION="$($LLVM_CONFIG --version)"
 LLVM_VERSION="$($LLVM_CONFIG --version)"
 LLVM_VERSION_MAJOR="$(echo $LLVM_VERSION | awk -F. '{print $1}')"
 LLVM_VERSION_MAJOR="$(echo $LLVM_VERSION | awk -F. '{print $1}')"
 LLVM_VERSION_MINOR="$(echo $LLVM_VERSION | awk -F. '{print $2}')"
 LLVM_VERSION_MINOR="$(echo $LLVM_VERSION | awk -F. '{print $2}')"
 LLVM_VERSION_PATCH="$(echo $LLVM_VERSION | awk -F. '{print $3}')"
 LLVM_VERSION_PATCH="$(echo $LLVM_VERSION | awk -F. '{print $3}')"
 
 
-if [ $LLVM_VERSION_MAJOR -lt 11 ] ||
-	([ $LLVM_VERSION_MAJOR -gt 14 ] && [ $LLVM_VERSION_MAJOR -lt 17 ]); then
-	error "Invalid LLVM version $LLVM_VERSION: must be 11, 12, 13, 14 or 17"
+if [ $LLVM_VERSION_MAJOR -lt 11 ] || ([ $LLVM_VERSION_MAJOR -gt 14 ] && [ $LLVM_VERSION_MAJOR -lt 17 ]) || [ $LLVM_VERSION_MAJOR -gt 19 ]; then
+	error "Invalid LLVM version $LLVM_VERSION: must be 11, 12, 13, 14, 17, 18 or 19"
 fi
 fi
 
 
 case "$OS_NAME" in
 case "$OS_NAME" in
 Darwin)
 Darwin)
-	if [ "$OS_ARCH" == "arm64" ]; then
-		if [ $LLVM_VERSION_MAJOR -lt 13 ] || [ $LLVM_VERSION_MAJOR -gt 17 ]; then
-			error "Darwin Arm64 requires LLVM 13, 14 or 17"
+	if [ "$OS_ARCH" = "arm64" ]; then
+		if [ $LLVM_VERSION_MAJOR -lt 13 ]; then
+			error "Invalid LLVM version $LLVM_VERSION: Darwin Arm64 requires LLVM 13, 14, 17, 18 or 19"
 		fi
 		fi
 	fi
 	fi
 
 
-	CXXFLAGS="$CXXFLAGS $($LLVM_CONFIG --cxxflags --ldflags)"
-	LDFLAGS="$LDFLAGS -liconv -ldl -framework System"
-	LDFLAGS="$LDFLAGS -lLLVM-C"
+	darwin_sysroot=
+	if [ $(which xcrun) ]; then
+		darwin_sysroot="--sysroot $(xcrun --sdk macosx --show-sdk-path)"
+	elif [[ -e "/Library/Developer/CommandLineTools/SDKs/MacOSX.sdk" ]]; then
+		darwin_sysroot="--sysroot /Library/Developer/CommandLineTools/SDKs/MacOSX.sdk"
+	else
+		echo "Warning: MacOSX.sdk not found."
+	fi
+
+	CXXFLAGS="$CXXFLAGS $($LLVM_CONFIG --cxxflags --ldflags) ${darwin_sysroot}"
+	LDFLAGS="$LDFLAGS -liconv -ldl -framework System -lLLVM"
 	;;
 	;;
 FreeBSD)
 FreeBSD)
 	CXXFLAGS="$CXXFLAGS $($LLVM_CONFIG --cxxflags --ldflags)"
 	CXXFLAGS="$CXXFLAGS $($LLVM_CONFIG --cxxflags --ldflags)"
 	LDFLAGS="$LDFLAGS $($LLVM_CONFIG --libs core native --system-libs)"
 	LDFLAGS="$LDFLAGS $($LLVM_CONFIG --libs core native --system-libs)"
 	;;
 	;;
+NetBSD)
+	CXXFLAGS="$CXXFLAGS $($LLVM_CONFIG --cxxflags --ldflags)"
+	LDFLAGS="$LDFLAGS $($LLVM_CONFIG --libs core native --system-libs)"
+	;;
 Linux)
 Linux)
 	CXXFLAGS="$CXXFLAGS $($LLVM_CONFIG --cxxflags --ldflags)"
 	CXXFLAGS="$CXXFLAGS $($LLVM_CONFIG --cxxflags --ldflags)"
 	LDFLAGS="$LDFLAGS -ldl $($LLVM_CONFIG --libs core native --system-libs --libfiles)"
 	LDFLAGS="$LDFLAGS -ldl $($LLVM_CONFIG --libs core native --system-libs --libfiles)"
 	# Copy libLLVM*.so into current directory for linking
 	# Copy libLLVM*.so into current directory for linking
 	# NOTE: This is needed by the Linux release pipeline!
 	# NOTE: This is needed by the Linux release pipeline!
-	cp $(readlink -f $($LLVM_CONFIG --libfiles)) ./
+	# cp $(readlink -f $($LLVM_CONFIG --libfiles)) ./
 	LDFLAGS="$LDFLAGS -Wl,-rpath=\$ORIGIN"
 	LDFLAGS="$LDFLAGS -Wl,-rpath=\$ORIGIN"
 	;;
 	;;
 OpenBSD)
 OpenBSD)
-	CXXFLAGS="$CXXFLAGS $($LLVM_CONFIG --cxxflags --ldflags)"
+	CXXFLAGS="$CXXFLAGS -I/usr/local/include $($LLVM_CONFIG --cxxflags --ldflags)"
+	LDFLAGS="$LDFLAGS -L/usr/local/lib -liconv"
+	LDFLAGS="$LDFLAGS $($LLVM_CONFIG --libs core native --system-libs)"
+	;;
+Haiku)
+	CXXFLAGS="$CXXFLAGS $($LLVM_CONFIG --cxxflags --ldflags) -I/system/develop/headers/private/shared -I/system/develop/headers/private/kernel"
 	LDFLAGS="$LDFLAGS -liconv"
 	LDFLAGS="$LDFLAGS -liconv"
 	LDFLAGS="$LDFLAGS $($LLVM_CONFIG --libs core native --system-libs)"
 	LDFLAGS="$LDFLAGS $($LLVM_CONFIG --libs core native --system-libs)"
 	;;
 	;;
@@ -95,7 +133,7 @@ build_odin() {
 		EXTRAFLAGS="-O3"
 		EXTRAFLAGS="-O3"
 		;;
 		;;
 	release-native)
 	release-native)
-		if [ "$OS_ARCH" == "arm64" ]; then
+		if [ "$OS_ARCH" = "arm64" ] || [ "$OS_ARCH" = "aarch64" ]; then
 			# Use preferred flag for Arm (ie arm64 / aarch64 / etc)
 			# Use preferred flag for Arm (ie arm64 / aarch64 / etc)
 			EXTRAFLAGS="-O3 -mcpu=native"
 			EXTRAFLAGS="-O3 -mcpu=native"
 		else
 		else
@@ -117,23 +155,32 @@ build_odin() {
 }
 }
 
 
 run_demo() {
 run_demo() {
-	./odin run examples/demo/demo.odin -file
+	./odin run examples/demo -vet -strict-style -- Hellope World
 }
 }
 
 
 if [ $# -eq 0 ]; then
 if [ $# -eq 0 ]; then
 	build_odin debug
 	build_odin debug
 	run_demo
 	run_demo
+
+	: ${PROGRAM:=$0}
+	printf "\nDebug compiler built. Note: run \"$PROGRAM release\" or \"$PROGRAM release-native\" if you want a faster, release mode compiler.\n"
 elif [ $# -eq 1 ]; then
 elif [ $# -eq 1 ]; then
 	case $1 in
 	case $1 in
 	report)
 	report)
-		[ ! -f "./odin" ] && build_odin debug
+		if [ ! -f "./odin" ]; then
+			build_odin debug
+			run_demo
+		fi
 		./odin report
 		./odin report
 		;;
 		;;
+	debug)
+		build_odin debug
+		run_demo
+		;;
 	*)
 	*)
 		build_odin $1
 		build_odin $1
 		;;
 		;;
 	esac
 	esac
-	run_demo
 else
 else
 	error "Too many arguments!"
 	error "Too many arguments!"
 fi
 fi

+ 19 - 0
ci/build_linux_static.sh

@@ -0,0 +1,19 @@
+#!/usr/bin/env sh
+# Intended for use in Alpine containers, see the "nightly" Github action for a list of dependencies
+
+CXX="clang++-18"
+LLVM_CONFIG="llvm-config-18"
+
+DISABLED_WARNINGS="-Wno-switch -Wno-macro-redefined -Wno-unused-value"
+
+CPPFLAGS="-DODIN_VERSION_RAW=\"dev-$(date +"%Y-%m")\""
+CXXFLAGS="-std=c++14 $($LLVM_CONFIG --cxxflags --ldflags)"
+
+LDFLAGS="-static -lm -lzstd -lz -lffi -pthread -ldl -fuse-ld=mold"
+LDFLAGS="$LDFLAGS $($LLVM_CONFIG --link-static --ldflags --libs --system-libs --libfiles)"
+LDFLAGS="$LDFLAGS -Wl,-rpath=\$ORIGIN"
+
+EXTRAFLAGS="-DNIGHTLY -O3"
+
+set -x
+$CXX src/main.cpp src/libtommath.cpp $DISABLED_WARNINGS $CPPFLAGS $CXXFLAGS $EXTRAFLAGS $LDFLAGS -o odin

+ 0 - 51
ci/create_nightly_json.py

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

+ 0 - 34
ci/delete_old_binaries.py

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

+ 145 - 0
ci/nightly.py

@@ -0,0 +1,145 @@
+import os
+import sys
+from zipfile  import ZipFile, ZIP_DEFLATED
+from b2sdk.v2 import InMemoryAccountInfo, B2Api
+from datetime import datetime, timezone
+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 is 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.now(timezone.utc).replace(hour=0, minute=0, second=0, microsecond=0)
+
+	source_archive: str
+	destination_name = f'odin-{platform}-nightly+{now.strftime("%Y-%m-%d")}'
+
+	if platform.startswith("linux") or platform.startswith("macos"):
+		destination_name += ".tar.gz"
+		source_archive = artifact
+	else:
+		destination_name += ".zip"
+		source_archive = destination_name
+
+		print(f"Creating archive {destination_name} from {artifact} and uploading to {bucket_name}")
+		with ZipFile(source_archive, 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_archive):
+		print(f"Error: archive {source_archive} not found.")
+		return 1
+
+	print("Uploading {} to {}".format(source_archive, UPLOAD_FOLDER + destination_name))
+	bucket = get_bucket()
+	res = bucket.upload_local_file(
+		source_archive,               # Local file to upload
+		"nightly/" + destination_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, tz=timezone.utc).replace(hour=0, minute=0, second=0, microsecond=0)
+		now   = datetime.now(timezone.utc).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.now(timezone.utc).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 - 13
ci/upload_create_nightly.sh

@@ -1,13 +0,0 @@
-#!/bin/bash
-
-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"
-
-7z a -bd "output/$filename" -r "$artifact"
-b2 upload-file --noProgress "$bucket" "output/$filename" "nightly/$filename"

+ 6 - 6
core/bufio/reader.odin

@@ -29,12 +29,12 @@ MIN_READ_BUFFER_SIZE :: 16
 @(private)
 @(private)
 DEFAULT_MAX_CONSECUTIVE_EMPTY_READS :: 128
 DEFAULT_MAX_CONSECUTIVE_EMPTY_READS :: 128
 
 
-reader_init :: proc(b: ^Reader, rd: io.Reader, size: int = DEFAULT_BUF_SIZE, allocator := context.allocator) {
+reader_init :: proc(b: ^Reader, rd: io.Reader, size: int = DEFAULT_BUF_SIZE, allocator := context.allocator, loc := #caller_location) {
 	size := size
 	size := size
 	size = max(size, MIN_READ_BUFFER_SIZE)
 	size = max(size, MIN_READ_BUFFER_SIZE)
 	reader_reset(b, rd)
 	reader_reset(b, rd)
 	b.buf_allocator = allocator
 	b.buf_allocator = allocator
-	b.buf = make([]byte, size, allocator)
+	b.buf = make([]byte, size, allocator, loc)
 }
 }
 
 
 reader_init_with_buf :: proc(b: ^Reader, rd: io.Reader, buf: []byte) {
 reader_init_with_buf :: proc(b: ^Reader, rd: io.Reader, buf: []byte) {
@@ -81,7 +81,7 @@ _reader_read_new_chunk :: proc(b: ^Reader) -> io.Error {
 	for i := b.max_consecutive_empty_reads; i > 0; i -= 1 {
 	for i := b.max_consecutive_empty_reads; i > 0; i -= 1 {
 		n, err := io.read(b.rd, b.buf[b.w:])
 		n, err := io.read(b.rd, b.buf[b.w:])
 		if n < 0 {
 		if n < 0 {
-			return .Negative_Read
+			return err if err != nil else .Negative_Read
 		}
 		}
 		b.w += n
 		b.w += n
 		if err != nil {
 		if err != nil {
@@ -189,7 +189,7 @@ reader_read :: proc(b: ^Reader, p: []byte) -> (n: int, err: io.Error) {
 		if len(p) >= len(b.buf) {
 		if len(p) >= len(b.buf) {
 			n, b.err = io.read(b.rd, p)
 			n, b.err = io.read(b.rd, p)
 			if n < 0 {
 			if n < 0 {
-				return 0, .Negative_Read
+				return 0, b.err if b.err != nil else .Negative_Read
 			}
 			}
 
 
 			if n > 0 {
 			if n > 0 {
@@ -202,7 +202,7 @@ reader_read :: proc(b: ^Reader, p: []byte) -> (n: int, err: io.Error) {
 		b.r, b.w = 0, 0
 		b.r, b.w = 0, 0
 		n, b.err = io.read(b.rd, b.buf)
 		n, b.err = io.read(b.rd, b.buf)
 		if n < 0 {
 		if n < 0 {
-			return 0, .Negative_Read
+			return 0, b.err if b.err != nil else .Negative_Read
 		}
 		}
 		if n == 0 {
 		if n == 0 {
 			return 0, _reader_consume_err(b)
 			return 0, _reader_consume_err(b)
@@ -290,7 +290,7 @@ reader_write_to :: proc(b: ^Reader, w: io.Writer) -> (n: i64, err: io.Error) {
 	write_buf :: proc(b: ^Reader, w: io.Writer) -> (i64, io.Error) {
 	write_buf :: proc(b: ^Reader, w: io.Writer) -> (i64, io.Error) {
 		n, err := io.write(w, b.buf[b.r:b.w])
 		n, err := io.write(w, b.buf[b.r:b.w])
 		if n < 0 {
 		if n < 0 {
-			return 0, .Negative_Write
+			return 0, err if err != nil else .Negative_Write
 		}
 		}
 		b.r += n
 		b.r += n
 		return i64(n), err
 		return i64(n), err

+ 1 - 1
core/bufio/scanner.odin

@@ -4,7 +4,7 @@ import "core:bytes"
 import "core:io"
 import "core:io"
 import "core:mem"
 import "core:mem"
 import "core:unicode/utf8"
 import "core:unicode/utf8"
-import "core:intrinsics"
+import "base:intrinsics"
 
 
 // Extra errors returns by scanning procedures
 // Extra errors returns by scanning procedures
 Scanner_Extra_Error :: enum i32 {
 Scanner_Extra_Error :: enum i32 {

+ 4 - 1
core/bufio/writer.odin

@@ -95,6 +95,10 @@ writer_write :: proc(b: ^Writer, p: []byte) -> (n: int, err: io.Error) {
 		m: int
 		m: int
 		if writer_buffered(b) == 0 {
 		if writer_buffered(b) == 0 {
 			m, b.err = io.write(b.wr, p)
 			m, b.err = io.write(b.wr, p)
+			if m < 0 && b.err == nil {
+				b.err = .Negative_Write
+				break
+			}
 		} else {
 		} else {
 			m = copy(b.buf[b.n:], p)
 			m = copy(b.buf[b.n:], p)
 			b.n += m
 			b.n += m
@@ -226,7 +230,6 @@ writer_to_writer :: proc(b: ^Writer) -> (s: io.Writer) {
 
 
 
 
 
 
-@(private)
 _writer_proc :: proc(stream_data: rawptr, mode: io.Stream_Mode, p: []byte, offset: i64, whence: io.Seek_From) -> (n: i64, err: io.Error) {
 _writer_proc :: proc(stream_data: rawptr, mode: io.Stream_Mode, p: []byte, offset: i64, whence: io.Seek_From) -> (n: i64, err: io.Error) {
 	b := (^Writer)(stream_data)
 	b := (^Writer)(stream_data)
 	#partial switch mode {
 	#partial switch mode {

+ 69 - 39
core/bytes/buffer.odin

@@ -27,19 +27,19 @@ Read_Op :: enum i8 {
 }
 }
 
 
 
 
-buffer_init :: proc(b: ^Buffer, buf: []byte) {
-	resize(&b.buf, len(buf))
+buffer_init :: proc(b: ^Buffer, buf: []byte, loc := #caller_location) {
+	resize(&b.buf, len(buf), loc=loc)
 	copy(b.buf[:], buf)
 	copy(b.buf[:], buf)
 }
 }
 
 
-buffer_init_string :: proc(b: ^Buffer, s: string) {
-	resize(&b.buf, len(s))
+buffer_init_string :: proc(b: ^Buffer, s: string, loc := #caller_location) {
+	resize(&b.buf, len(s), loc=loc)
 	copy(b.buf[:], s)
 	copy(b.buf[:], s)
 }
 }
 
 
-buffer_init_allocator :: proc(b: ^Buffer, len, cap: int, allocator := context.allocator) {
+buffer_init_allocator :: proc(b: ^Buffer, len, cap: int, allocator := context.allocator, loc := #caller_location) {
 	if b.buf == nil {
 	if b.buf == nil {
-		b.buf = make([dynamic]byte, len, cap, allocator)
+		b.buf = make([dynamic]byte, len, cap, allocator, loc)
 		return
 		return
 	}
 	}
 
 
@@ -96,28 +96,28 @@ buffer_truncate :: proc(b: ^Buffer, n: int) {
 }
 }
 
 
 @(private)
 @(private)
-_buffer_try_grow :: proc(b: ^Buffer, n: int) -> (int, bool) {
+_buffer_try_grow :: proc(b: ^Buffer, n: int, loc := #caller_location) -> (int, bool) {
 	if l := len(b.buf); n <= cap(b.buf)-l {
 	if l := len(b.buf); n <= cap(b.buf)-l {
-		resize(&b.buf, l+n)
+		resize(&b.buf, l+n, loc=loc)
 		return l, true
 		return l, true
 	}
 	}
 	return 0, false
 	return 0, false
 }
 }
 
 
 @(private)
 @(private)
-_buffer_grow :: proc(b: ^Buffer, n: int) -> int {
+_buffer_grow :: proc(b: ^Buffer, n: int, loc := #caller_location) -> int {
 	m := buffer_length(b)
 	m := buffer_length(b)
 	if m == 0 && b.off != 0 {
 	if m == 0 && b.off != 0 {
 		buffer_reset(b)
 		buffer_reset(b)
 	}
 	}
-	if i, ok := _buffer_try_grow(b, n); ok {
+	if i, ok := _buffer_try_grow(b, n, loc=loc); ok {
 		return i
 		return i
 	}
 	}
 
 
 	if b.buf == nil && n <= SMALL_BUFFER_SIZE {
 	if b.buf == nil && n <= SMALL_BUFFER_SIZE {
 		// Fixes #2756 by preserving allocator if already set on Buffer via init_buffer_allocator
 		// Fixes #2756 by preserving allocator if already set on Buffer via init_buffer_allocator
-		reserve(&b.buf, SMALL_BUFFER_SIZE)
-		resize(&b.buf, n)
+		reserve(&b.buf, SMALL_BUFFER_SIZE, loc=loc)
+		resize(&b.buf, n, loc=loc)
 		return 0
 		return 0
 	}
 	}
 
 
@@ -127,31 +127,34 @@ _buffer_grow :: proc(b: ^Buffer, n: int) -> int {
 	} else if c > max(int) - c - n {
 	} else if c > max(int) - c - n {
 		panic("bytes.Buffer: too large")
 		panic("bytes.Buffer: too large")
 	} else {
 	} else {
-		resize(&b.buf, 2*c + n)
+		resize(&b.buf, 2*c + n, loc=loc)
 		copy(b.buf[:], b.buf[b.off:])
 		copy(b.buf[:], b.buf[b.off:])
 	}
 	}
 	b.off = 0
 	b.off = 0
-	resize(&b.buf, m+n)
+	resize(&b.buf, m+n, loc=loc)
 	return m
 	return m
 }
 }
 
 
-buffer_grow :: proc(b: ^Buffer, n: int) {
+buffer_grow :: proc(b: ^Buffer, n: int, loc := #caller_location) {
 	if n < 0 {
 	if n < 0 {
 		panic("bytes.buffer_grow: negative count")
 		panic("bytes.buffer_grow: negative count")
 	}
 	}
-	m := _buffer_grow(b, n)
-	resize(&b.buf, m)
+	m := _buffer_grow(b, n, loc=loc)
+	resize(&b.buf, m, loc=loc)
 }
 }
 
 
-buffer_write_at :: proc(b: ^Buffer, p: []byte, offset: int) -> (n: int, err: io.Error) {
+buffer_write_at :: proc(b: ^Buffer, p: []byte, offset: int, loc := #caller_location) -> (n: int, err: io.Error) {
+	if len(p) == 0 {
+		return 0, nil
+	}
 	b.last_read = .Invalid
 	b.last_read = .Invalid
 	if offset < 0 {
 	if offset < 0 {
 		err = .Invalid_Offset
 		err = .Invalid_Offset
 		return
 		return
 	}
 	}
-	_, ok := _buffer_try_grow(b, offset+len(p))
+	_, ok := _buffer_try_grow(b, offset+len(p), loc=loc)
 	if !ok {
 	if !ok {
-		_ = _buffer_grow(b, offset+len(p))
+		_ = _buffer_grow(b, offset+len(p), loc=loc)
 	}
 	}
 	if len(b.buf) <= offset {
 	if len(b.buf) <= offset {
 		return 0, .Short_Write
 		return 0, .Short_Write
@@ -160,47 +163,47 @@ buffer_write_at :: proc(b: ^Buffer, p: []byte, offset: int) -> (n: int, err: io.
 }
 }
 
 
 
 
-buffer_write :: proc(b: ^Buffer, p: []byte) -> (n: int, err: io.Error) {
+buffer_write :: proc(b: ^Buffer, p: []byte, loc := #caller_location) -> (n: int, err: io.Error) {
 	b.last_read = .Invalid
 	b.last_read = .Invalid
-	m, ok := _buffer_try_grow(b, len(p))
+	m, ok := _buffer_try_grow(b, len(p), loc=loc)
 	if !ok {
 	if !ok {
-		m = _buffer_grow(b, len(p))
+		m = _buffer_grow(b, len(p), loc=loc)
 	}
 	}
 	return copy(b.buf[m:], p), nil
 	return copy(b.buf[m:], p), nil
 }
 }
 
 
-buffer_write_ptr :: proc(b: ^Buffer, ptr: rawptr, size: int) -> (n: int, err: io.Error) {
-	return buffer_write(b, ([^]byte)(ptr)[:size])
+buffer_write_ptr :: proc(b: ^Buffer, ptr: rawptr, size: int, loc := #caller_location) -> (n: int, err: io.Error) {
+	return buffer_write(b, ([^]byte)(ptr)[:size], loc=loc)
 }
 }
 
 
-buffer_write_string :: proc(b: ^Buffer, s: string) -> (n: int, err: io.Error) {
+buffer_write_string :: proc(b: ^Buffer, s: string, loc := #caller_location) -> (n: int, err: io.Error) {
 	b.last_read = .Invalid
 	b.last_read = .Invalid
-	m, ok := _buffer_try_grow(b, len(s))
+	m, ok := _buffer_try_grow(b, len(s), loc=loc)
 	if !ok {
 	if !ok {
-		m = _buffer_grow(b, len(s))
+		m = _buffer_grow(b, len(s), loc=loc)
 	}
 	}
 	return copy(b.buf[m:], s), nil
 	return copy(b.buf[m:], s), nil
 }
 }
 
 
-buffer_write_byte :: proc(b: ^Buffer, c: byte) -> io.Error {
+buffer_write_byte :: proc(b: ^Buffer, c: byte, loc := #caller_location) -> io.Error {
 	b.last_read = .Invalid
 	b.last_read = .Invalid
-	m, ok := _buffer_try_grow(b, 1)
+	m, ok := _buffer_try_grow(b, 1, loc=loc)
 	if !ok {
 	if !ok {
-		m = _buffer_grow(b, 1)
+		m = _buffer_grow(b, 1, loc=loc)
 	}
 	}
 	b.buf[m] = c
 	b.buf[m] = c
 	return nil
 	return nil
 }
 }
 
 
-buffer_write_rune :: proc(b: ^Buffer, r: rune) -> (n: int, err: io.Error) {
+buffer_write_rune :: proc(b: ^Buffer, r: rune, loc := #caller_location) -> (n: int, err: io.Error) {
 	if r < utf8.RUNE_SELF {
 	if r < utf8.RUNE_SELF {
-		buffer_write_byte(b, byte(r))
+		buffer_write_byte(b, byte(r), loc=loc)
 		return 1, nil
 		return 1, nil
 	}
 	}
 	b.last_read = .Invalid
 	b.last_read = .Invalid
-	m, ok := _buffer_try_grow(b, utf8.UTF_MAX)
+	m, ok := _buffer_try_grow(b, utf8.UTF_MAX, loc=loc)
 	if !ok {
 	if !ok {
-		m = _buffer_grow(b, utf8.UTF_MAX)
+		m = _buffer_grow(b, utf8.UTF_MAX, loc=loc)
 	}
 	}
 	res: [4]byte
 	res: [4]byte
 	res, n = utf8.encode_rune(r)
 	res, n = utf8.encode_rune(r)
@@ -246,10 +249,13 @@ buffer_read_ptr :: proc(b: ^Buffer, ptr: rawptr, size: int) -> (n: int, err: io.
 }
 }
 
 
 buffer_read_at :: proc(b: ^Buffer, p: []byte, offset: int) -> (n: int, err: io.Error) {
 buffer_read_at :: proc(b: ^Buffer, p: []byte, offset: int) -> (n: int, err: io.Error) {
+	if len(p) == 0 {
+		return 0, nil
+	}
 	b.last_read = .Invalid
 	b.last_read = .Invalid
 
 
 	if uint(offset) >= len(b.buf) {
 	if uint(offset) >= len(b.buf) {
-		err = .Invalid_Offset
+		err = .EOF
 		return
 		return
 	}
 	}
 	n = copy(p, b.buf[offset:])
 	n = copy(p, b.buf[offset:])
@@ -310,6 +316,27 @@ buffer_unread_rune :: proc(b: ^Buffer) -> io.Error {
 	return nil
 	return nil
 }
 }
 
 
+buffer_seek :: proc(b: ^Buffer, offset: i64, whence: io.Seek_From) -> (i64, io.Error) {
+	abs: i64
+	switch whence {
+	case .Start:
+		abs = offset
+	case .Current:
+		abs = i64(b.off) + offset
+	case .End:
+		abs = i64(len(b.buf)) + offset
+	case:
+		return 0, .Invalid_Whence
+	}
+
+	abs_int := int(abs)
+	if abs_int < 0 {
+		return 0, .Invalid_Offset
+	}
+	b.last_read = .Invalid
+	b.off = abs_int
+	return abs, nil
+}
 
 
 buffer_read_bytes :: proc(b: ^Buffer, delim: byte) -> (line: []byte, err: io.Error) {
 buffer_read_bytes :: proc(b: ^Buffer, delim: byte) -> (line: []byte, err: io.Error) {
 	i := index_byte(b.buf[b.off:], delim)
 	i := index_byte(b.buf[b.off:], delim)
@@ -359,7 +386,7 @@ buffer_read_from :: proc(b: ^Buffer, r: io.Reader) -> (n: i64, err: io.Error) #n
 		resize(&b.buf, i)
 		resize(&b.buf, i)
 		m, e := io.read(r, b.buf[i:cap(b.buf)])
 		m, e := io.read(r, b.buf[i:cap(b.buf)])
 		if m < 0 {
 		if m < 0 {
-			err = .Negative_Read
+			err = e if e != nil else .Negative_Read
 			return
 			return
 		}
 		}
 
 
@@ -395,14 +422,17 @@ _buffer_proc :: proc(stream_data: rawptr, mode: io.Stream_Mode, p: []byte, offse
 		return io._i64_err(buffer_write(b, p))
 		return io._i64_err(buffer_write(b, p))
 	case .Write_At:
 	case .Write_At:
 		return io._i64_err(buffer_write_at(b, p, int(offset)))
 		return io._i64_err(buffer_write_at(b, p, int(offset)))
+	case .Seek:
+		n, err = buffer_seek(b, offset, whence)
+		return
 	case .Size:
 	case .Size:
-		n = i64(buffer_capacity(b))
+		n = i64(buffer_length(b))
 		return
 		return
 	case .Destroy:
 	case .Destroy:
 		buffer_destroy(b)
 		buffer_destroy(b)
 		return
 		return
 	case .Query:
 	case .Query:
-		return io.query_utility({.Read, .Read_At, .Write, .Write_At, .Size, .Destroy})
+		return io.query_utility({.Read, .Read_At, .Write, .Write_At, .Seek, .Size, .Destroy, .Query})
 	}
 	}
 	return 0, .Empty
 	return 0, .Empty
 }
 }

+ 310 - 7
core/bytes/bytes.odin

@@ -1,9 +1,38 @@
 package bytes
 package bytes
 
 
+import "base:intrinsics"
 import "core:mem"
 import "core:mem"
+import "core:simd"
 import "core:unicode"
 import "core:unicode"
 import "core:unicode/utf8"
 import "core:unicode/utf8"
 
 
+when ODIN_ARCH == .amd64 && intrinsics.has_target_feature("avx2") {
+	@(private)
+	SCANNER_INDICES_256 : simd.u8x32 : {
+		0,  1,  2,  3,  4,  5,  6,  7,
+		8,  9, 10, 11, 12, 13, 14, 15,
+		16, 17, 18, 19, 20, 21, 22, 23,
+		24, 25, 26, 27, 28, 29, 30, 31,
+	}
+	@(private)
+	SCANNER_SENTINEL_MAX_256: simd.u8x32 : u8(0x00)
+	@(private)
+	SCANNER_SENTINEL_MIN_256: simd.u8x32 : u8(0xff)
+	@(private)
+	SIMD_REG_SIZE_256 :: 32
+}
+@(private)
+SCANNER_INDICES_128 : simd.u8x16 : {
+	0,  1,  2,  3,  4,  5,  6,  7,
+	8,  9, 10, 11, 12, 13, 14, 15,
+}
+@(private)
+SCANNER_SENTINEL_MAX_128: simd.u8x16 : u8(0x00)
+@(private)
+SCANNER_SENTINEL_MIN_128: simd.u8x16 : u8(0xff)
+@(private)
+SIMD_REG_SIZE_128 :: 16
+
 clone :: proc(s: []byte, allocator := context.allocator, loc := #caller_location) -> []byte {
 clone :: proc(s: []byte, allocator := context.allocator, loc := #caller_location) -> []byte {
 	c := make([]byte, len(s), allocator, loc)
 	c := make([]byte, len(s), allocator, loc)
 	copy(c, s)
 	copy(c, s)
@@ -293,28 +322,277 @@ split_after_iterator :: proc(s: ^[]byte, sep: []byte) -> ([]byte, bool) {
 	return _split_iterator(s, sep, len(sep))
 	return _split_iterator(s, sep, len(sep))
 }
 }
 
 
+/*
+Scan a slice of bytes for a specific byte.
+
+This procedure safely handles slices of any length, including empty slices.
+
+Inputs:
+- data: A slice of bytes.
+- c: The byte to search for.
+
+Returns:
+- index: The index of the byte `c`, or -1 if it was not found.
+*/
+index_byte :: proc "contextless" (s: []byte, c: byte) -> (index: int) #no_bounds_check {
+	i, l := 0, len(s)
+
+	// Guard against small strings.  On modern systems, it is ALWAYS
+	// worth vectorizing assuming there is a hardware vector unit, and
+	// the data size is large enough.
+	if l < SIMD_REG_SIZE_128 {
+		for /**/; i < l; i += 1 {
+			if s[i] == c {
+				return i
+			}
+		}
+		return -1
+	}
+
+	c_vec: simd.u8x16 = c
+	when !simd.IS_EMULATED {
+		// Note: While this is something that could also logically take
+		// advantage of AVX512, the various downclocking and power
+		// consumption related woes make premature to have a dedicated
+		// code path.
+		when ODIN_ARCH == .amd64 && intrinsics.has_target_feature("avx2") {
+			c_vec_256: simd.u8x32 = c
+
+			s_vecs: [4]simd.u8x32 = ---
+			c_vecs: [4]simd.u8x32 = ---
+			m_vec: [4]u8 = ---
+
+			// Scan 128-byte chunks, using 256-bit SIMD.
+			for nr_blocks := l / (4 * SIMD_REG_SIZE_256); nr_blocks > 0; nr_blocks -= 1 {
+				#unroll for j in 0..<4 {
+					s_vecs[j] = intrinsics.unaligned_load(cast(^simd.u8x32)raw_data(s[i+j*SIMD_REG_SIZE_256:]))
+					c_vecs[j] = simd.lanes_eq(s_vecs[j], c_vec_256)
+					m_vec[j] = simd.reduce_or(c_vecs[j])
+				}
+				if m_vec[0] | m_vec[1] | m_vec[2] | m_vec[3] > 0 {
+					#unroll for j in 0..<4 {
+						if m_vec[j] > 0 {
+							sel := simd.select(c_vecs[j], SCANNER_INDICES_256, SCANNER_SENTINEL_MIN_256)
+							off := simd.reduce_min(sel)
+							return i + j * SIMD_REG_SIZE_256 + int(off)
+						}
+					}
+				}
+
+				i += 4 * SIMD_REG_SIZE_256
+			}
+
+			// Scan 64-byte chunks, using 256-bit SIMD.
+			for nr_blocks := (l - i) / (2 * SIMD_REG_SIZE_256); nr_blocks > 0; nr_blocks -= 1 {
+				#unroll for j in 0..<2 {
+					s_vecs[j] = intrinsics.unaligned_load(cast(^simd.u8x32)raw_data(s[i+j*SIMD_REG_SIZE_256:]))
+					c_vecs[j] = simd.lanes_eq(s_vecs[j], c_vec_256)
+					m_vec[j] = simd.reduce_or(c_vecs[j])
+				}
+				if m_vec[0] | m_vec[1] > 0 {
+					#unroll for j in 0..<2 {
+						if m_vec[j] > 0 {
+							sel := simd.select(c_vecs[j], SCANNER_INDICES_256, SCANNER_SENTINEL_MIN_256)
+							off := simd.reduce_min(sel)
+							return i + j * SIMD_REG_SIZE_256 + int(off)
+						}
+					}
+				}
+
+				i += 2 * SIMD_REG_SIZE_256
+			}
+		} else {
+			s_vecs: [4]simd.u8x16 = ---
+			c_vecs: [4]simd.u8x16 = ---
+			m_vecs: [4]u8 = ---
+
+			// Scan 64-byte chunks, using 128-bit SIMD.
+			for nr_blocks := l / (4 * SIMD_REG_SIZE_128); nr_blocks > 0; nr_blocks -= 1 {
+				#unroll for j in 0..<4 {
+					s_vecs[j]= intrinsics.unaligned_load(cast(^simd.u8x16)raw_data(s[i+j*SIMD_REG_SIZE_128:]))
+					c_vecs[j] = simd.lanes_eq(s_vecs[j], c_vec)
+					m_vecs[j] = simd.reduce_or(c_vecs[j])
+				}
+				if m_vecs[0] | m_vecs[1] | m_vecs[2] | m_vecs[3] > 0 {
+					#unroll for j in 0..<4 {
+						if m_vecs[j] > 0 {
+							sel := simd.select(c_vecs[j], SCANNER_INDICES_128, SCANNER_SENTINEL_MIN_128)
+							off := simd.reduce_min(sel)
+							return i + j * SIMD_REG_SIZE_128 + int(off)
+						}
+					}
+				}
+
+				i += 4 * SIMD_REG_SIZE_128
+			}
+		}
+	}
 
 
-index_byte :: proc(s: []byte, c: byte) -> int {
-	for i := 0; i < len(s); i += 1 {
+	// Scan the remaining SIMD register sized chunks.
+	//
+	// Apparently LLVM does ok with 128-bit SWAR, so this path is also taken
+	// on potato targets.  Scanning more at a time when LLVM is emulating SIMD
+	// likely does not buy much, as all that does is increase GP register
+	// pressure.
+	for nr_blocks := (l - i) / SIMD_REG_SIZE_128; nr_blocks > 0; nr_blocks -= 1 {
+		s0 := intrinsics.unaligned_load(cast(^simd.u8x16)raw_data(s[i:]))
+		c0 := simd.lanes_eq(s0, c_vec)
+		if simd.reduce_or(c0) > 0 {
+			sel := simd.select(c0, SCANNER_INDICES_128, SCANNER_SENTINEL_MIN_128)
+			off := simd.reduce_min(sel)
+			return i + int(off)
+		}
+
+		i += SIMD_REG_SIZE_128
+	}
+
+	// Scan serially for the remainder.
+	for /**/; i < l; i += 1 {
 		if s[i] == c {
 		if s[i] == c {
 			return i
 			return i
 		}
 		}
 	}
 	}
+
 	return -1
 	return -1
 }
 }
 
 
-// Returns -1 if c is not present
-last_index_byte :: proc(s: []byte, c: byte) -> int {
-	for i := len(s)-1; i >= 0; i -= 1 {
+/*
+Scan a slice of bytes for a specific byte, starting from the end and working
+backwards to the start.
+
+This procedure safely handles slices of any length, including empty slices.
+
+Inputs:
+- data: A slice of bytes.
+- c: The byte to search for.
+
+Returns:
+- index: The index of the byte `c`, or -1 if it was not found.
+*/
+last_index_byte :: proc "contextless" (s: []byte, c: byte) -> int #no_bounds_check {
+	i := len(s)
+
+	// Guard against small strings.  On modern systems, it is ALWAYS
+	// worth vectorizing assuming there is a hardware vector unit, and
+	// the data size is large enough.
+	if i < SIMD_REG_SIZE_128 {
+		#reverse for ch, j in s {
+			if ch == c {
+				return j
+			}
+		}
+		return -1
+	}
+
+	c_vec: simd.u8x16 = c
+	when !simd.IS_EMULATED {
+		// Note: While this is something that could also logically take
+		// advantage of AVX512, the various downclocking and power
+		// consumption related woes make premature to have a dedicated
+		// code path.
+		when ODIN_ARCH == .amd64 && intrinsics.has_target_feature("avx2") {
+			c_vec_256: simd.u8x32 = c
+
+			s_vecs: [4]simd.u8x32 = ---
+			c_vecs: [4]simd.u8x32 = ---
+			m_vec: [4]u8 = ---
+
+			// Scan 128-byte chunks, using 256-bit SIMD.
+			for i >= 4 * SIMD_REG_SIZE_256 {
+				i -= 4 * SIMD_REG_SIZE_256
+
+				#unroll for j in 0..<4 {
+					s_vecs[j] = intrinsics.unaligned_load(cast(^simd.u8x32)raw_data(s[i+j*SIMD_REG_SIZE_256:]))
+					c_vecs[j] = simd.lanes_eq(s_vecs[j], c_vec_256)
+					m_vec[j] = simd.reduce_or(c_vecs[j])
+				}
+				if m_vec[0] | m_vec[1] | m_vec[2] | m_vec[3] > 0 {
+					#unroll for j in 0..<4 {
+						if m_vec[3-j] > 0 {
+							sel := simd.select(c_vecs[3-j], SCANNER_INDICES_256, SCANNER_SENTINEL_MAX_256)
+							off := simd.reduce_max(sel)
+							return i + (3-j) * SIMD_REG_SIZE_256 + int(off)
+						}
+					}
+				}
+			}
+
+			// Scan 64-byte chunks, using 256-bit SIMD.
+			for i >= 2 * SIMD_REG_SIZE_256 {
+				i -= 2 * SIMD_REG_SIZE_256
+
+				#unroll for j in 0..<2 {
+					s_vecs[j] = intrinsics.unaligned_load(cast(^simd.u8x32)raw_data(s[i+j*SIMD_REG_SIZE_256:]))
+					c_vecs[j] = simd.lanes_eq(s_vecs[j], c_vec_256)
+					m_vec[j] = simd.reduce_or(c_vecs[j])
+				}
+				if m_vec[0] | m_vec[1] > 0 {
+					#unroll for j in 0..<2 {
+						if m_vec[1-j] > 0 {
+							sel := simd.select(c_vecs[1-j], SCANNER_INDICES_256, SCANNER_SENTINEL_MAX_256)
+							off := simd.reduce_max(sel)
+							return i + (1-j) * SIMD_REG_SIZE_256 + int(off)
+						}
+					}
+				}
+			}
+		} else {
+			s_vecs: [4]simd.u8x16 = ---
+			c_vecs: [4]simd.u8x16 = ---
+			m_vecs: [4]u8 = ---
+
+			// Scan 64-byte chunks, using 128-bit SIMD.
+			for i >= 4 * SIMD_REG_SIZE_128 {
+				i -= 4 * SIMD_REG_SIZE_128
+
+				#unroll for j in 0..<4 {
+					s_vecs[j] = intrinsics.unaligned_load(cast(^simd.u8x16)raw_data(s[i+j*SIMD_REG_SIZE_128:]))
+					c_vecs[j] = simd.lanes_eq(s_vecs[j], c_vec)
+					m_vecs[j] = simd.reduce_or(c_vecs[j])
+				}
+				if m_vecs[0] | m_vecs[1] | m_vecs[2] | m_vecs[3] > 0 {
+					#unroll for j in 0..<4 {
+						if m_vecs[3-j] > 0 {
+							sel := simd.select(c_vecs[3-j], SCANNER_INDICES_128, SCANNER_SENTINEL_MAX_128)
+							off := simd.reduce_max(sel)
+							return i + (3-j) * SIMD_REG_SIZE_128 + int(off)
+						}
+					}
+				}
+			}
+		}
+	}
+
+	// Scan the remaining SIMD register sized chunks.
+	//
+	// Apparently LLVM does ok with 128-bit SWAR, so this path is also taken
+	// on potato targets.  Scanning more at a time when LLVM is emulating SIMD
+	// likely does not buy much, as all that does is increase GP register
+	// pressure.
+	for i >= SIMD_REG_SIZE_128 {
+		i -= SIMD_REG_SIZE_128
+
+		s0 := intrinsics.unaligned_load(cast(^simd.u8x16)raw_data(s[i:]))
+		c0 := simd.lanes_eq(s0, c_vec)
+		if simd.reduce_or(c0) > 0 {
+			sel := simd.select(c0, SCANNER_INDICES_128, SCANNER_SENTINEL_MAX_128)
+			off := simd.reduce_max(sel)
+			return i + int(off)
+		}
+	}
+
+	// Scan serially for the remainder.
+	for i > 0 {
+		i -= 1
 		if s[i] == c {
 		if s[i] == c {
 			return i
 			return i
 		}
 		}
 	}
 	}
+
 	return -1
 	return -1
 }
 }
 
 
 
 
-
 @private PRIME_RABIN_KARP :: 16777619
 @private PRIME_RABIN_KARP :: 16777619
 
 
 index :: proc(s, substr: []byte) -> int {
 index :: proc(s, substr: []byte) -> int {
@@ -895,7 +1173,7 @@ split_multi_iterator :: proc(s: ^[]byte, substrs: [][]byte, skip_empty := false)
 
 
 
 
 
 
-// scrub scruvs invalid utf-8 characters and replaces them with the replacement string
+// Scrubs invalid utf-8 characters and replaces them with the replacement string
 // Adjacent invalid bytes are only replaced once
 // Adjacent invalid bytes are only replaced once
 scrub :: proc(s: []byte, replacement: []byte, allocator := context.allocator) -> []byte {
 scrub :: proc(s: []byte, replacement: []byte, allocator := context.allocator) -> []byte {
 	str := s
 	str := s
@@ -1167,3 +1445,28 @@ fields_proc :: proc(s: []byte, f: proc(rune) -> bool, allocator := context.alloc
 
 
 	return subslices[:]
 	return subslices[:]
 }
 }
+
+// alias returns true iff a and b have a non-zero length, and any part of
+// a overlaps with b.
+alias :: proc "contextless" (a, b: []byte) -> bool {
+	a_len, b_len := len(a), len(b)
+	if a_len == 0 || b_len == 0 {
+		return false
+	}
+
+	a_start, b_start := uintptr(raw_data(a)), uintptr(raw_data(b))
+	a_end, b_end := a_start + uintptr(a_len-1), b_start + uintptr(b_len-1)
+
+	return a_start <= b_end && b_start <= a_end
+}
+
+// alias_inexactly returns true iff a and b have a non-zero length,
+// the base pointer of a and b are NOT equal, and any part of a overlaps
+// with b (ie: `alias(a, b)` with an exception that returns false for
+// `a == b`, `b = a[:len(a)-69]` and similar conditions).
+alias_inexactly :: proc "contextless" (a, b: []byte) -> bool {
+	if raw_data(a) == raw_data(b) {
+		return false
+	}
+	return alias(a, b)
+}

+ 9 - 2
core/bytes/reader.odin

@@ -9,10 +9,11 @@ Reader :: struct {
 	prev_rune: int,    // previous reading index of rune or < 0
 	prev_rune: int,    // previous reading index of rune or < 0
 }
 }
 
 
-reader_init :: proc(r: ^Reader, s: []byte) {
+reader_init :: proc(r: ^Reader, s: []byte) -> io.Stream {
 	r.s = s
 	r.s = s
 	r.i = 0
 	r.i = 0
 	r.prev_rune = -1
 	r.prev_rune = -1
+	return reader_to_stream(r)
 }
 }
 
 
 reader_to_stream :: proc(r: ^Reader) -> (s: io.Stream) {
 reader_to_stream :: proc(r: ^Reader) -> (s: io.Stream) {
@@ -33,6 +34,9 @@ reader_size :: proc(r: ^Reader) -> i64 {
 }
 }
 
 
 reader_read :: proc(r: ^Reader, p: []byte) -> (n: int, err: io.Error) {
 reader_read :: proc(r: ^Reader, p: []byte) -> (n: int, err: io.Error) {
+	if len(p) == 0 {
+		return 0, nil
+	}
 	if r.i >= i64(len(r.s)) {
 	if r.i >= i64(len(r.s)) {
 		return 0, .EOF
 		return 0, .EOF
 	}
 	}
@@ -42,6 +46,9 @@ reader_read :: proc(r: ^Reader, p: []byte) -> (n: int, err: io.Error) {
 	return
 	return
 }
 }
 reader_read_at :: proc(r: ^Reader, p: []byte, off: i64) -> (n: int, err: io.Error) {
 reader_read_at :: proc(r: ^Reader, p: []byte, off: i64) -> (n: int, err: io.Error) {
+	if len(p) == 0 {
+		return 0, nil
+	}
 	if off < 0 {
 	if off < 0 {
 		return 0, .Invalid_Offset
 		return 0, .Invalid_Offset
 	}
 	}
@@ -97,7 +104,6 @@ reader_unread_rune :: proc(r: ^Reader) -> io.Error {
 	return nil
 	return nil
 }
 }
 reader_seek :: proc(r: ^Reader, offset: i64, whence: io.Seek_From) -> (i64, io.Error) {
 reader_seek :: proc(r: ^Reader, offset: i64, whence: io.Seek_From) -> (i64, io.Error) {
-	r.prev_rune = -1
 	abs: i64
 	abs: i64
 	switch whence {
 	switch whence {
 	case .Start:
 	case .Start:
@@ -114,6 +120,7 @@ reader_seek :: proc(r: ^Reader, offset: i64, whence: io.Seek_From) -> (i64, io.E
 		return 0, .Invalid_Offset
 		return 0, .Invalid_Offset
 	}
 	}
 	r.i = abs
 	r.i = abs
+	r.prev_rune = -1
 	return abs, nil
 	return abs, nil
 }
 }
 reader_write_to :: proc(r: ^Reader, w: io.Writer) -> (n: i64, err: io.Error) {
 reader_write_to :: proc(r: ^Reader, w: io.Writer) -> (n: i64, err: io.Error) {

+ 11 - 1
core/c/c.odin

@@ -1,6 +1,6 @@
 package c
 package c
 
 
-import builtin "core:builtin"
+import builtin "base:builtin"
 
 
 char           :: builtin.u8  // assuming -funsigned-char
 char           :: builtin.u8  // assuming -funsigned-char
 
 
@@ -104,3 +104,13 @@ NULL           :: rawptr(uintptr(0))
 NDEBUG         :: !ODIN_DEBUG
 NDEBUG         :: !ODIN_DEBUG
 
 
 CHAR_BIT :: 8
 CHAR_BIT :: 8
+
+// Since there are no types in C with an alignment larger than that of
+// max_align_t, which cannot be larger than sizeof(long double) as any other
+// exposed type wouldn't be valid C, the maximum alignment possible in a
+// strictly conformant C implementation is 16 on the platforms we care about.
+// The choice of 4096 bytes for storage of this type is more than enough on all
+// relevant platforms.
+va_list :: struct #align(16) {
+	_: [4096]u8,
+}

+ 0 - 25
core/c/frontend/preprocessor/const_expr.odin

@@ -1,25 +0,0 @@
-package c_frontend_preprocess
-
-import "core:c/frontend/tokenizer"
-
-const_expr :: proc(rest: ^^Token, tok: ^Token) -> i64 {
-	// TODO(bill): Handle const_expr correctly
-	// This is effectively a mini-parser
-
-	assert(rest != nil)
-	assert(tok != nil)
-	rest^ = tokenizer.new_eof(tok)
-	switch v in tok.val {
-	case i64:
-		return v
-	case f64:
-		return i64(v)
-	case string:
-		return 0
-	case []u16:
-		// TODO
-	case []u32:
-		// TODO
-	}
-	return 0
-}

+ 0 - 1510
core/c/frontend/preprocessor/preprocess.odin

@@ -1,1510 +0,0 @@
-package c_frontend_preprocess
-
-import "../tokenizer"
-
-import "core:strings"
-import "core:strconv"
-import "core:path/filepath"
-import "core:unicode/utf8"
-import "core:unicode/utf16"
-import "core:os"
-import "core:io"
-
-@(private)
-Tokenizer :: tokenizer.Tokenizer
-@(private)
-Token :: tokenizer.Token
-
-Error_Handler :: tokenizer.Error_Handler
-
-Macro_Param :: struct {
-	next: ^Macro_Param,
-	name: string,
-}
-
-Macro_Arg :: struct {
-	next: ^Macro_Arg,
-	name: string,
-	tok: ^Token,
-	is_va_args: bool,
-}
-
-Macro_Kind :: enum u8 {
-	Function_Like,
-	Value_Like,
-}
-
-Macro_Handler :: #type proc(^Preprocessor, ^Token) -> ^Token
-
-Macro :: struct {
-	name: string,
-	kind: Macro_Kind,
-	params: ^Macro_Param,
-	va_args_name: string,
-	body: ^Token,
-	handler: Macro_Handler,
-}
-
-Cond_Incl_State :: enum u8 {
-	In_Then,
-	In_Elif,
-	In_Else,
-}
-
-Cond_Incl :: struct {
-	next: ^Cond_Incl,
-	tok:  ^Token,
-	state:    Cond_Incl_State,
-	included: bool,
-}
-
-Pragma_Handler :: #type proc(^Preprocessor, ^Token)
-
-Preprocessor :: struct {
-	// Lookup tables
-	macros:         map[string]^Macro,
-	pragma_once:    map[string]bool,
-	include_guards: map[string]string,
-	filepath_cache: map[string]string,
-
-	// Include path data
-	include_paths: []string,
-
-	// Counter for __COUNTER__ macro
-	counter: i64,
-
-	// Include information
-	cond_incl: ^Cond_Incl,
-	include_level: int,
-	include_next_index: int,
-
-	wide_char_size: int,
-
-	// Mutable data
-	err:  Error_Handler,
-	warn: Error_Handler,
-	pragma_handler: Pragma_Handler,
-	error_count:   int,
-	warning_count: int,
-}
-
-MAX_INCLUDE_LEVEL :: 1024
-
-error :: proc(cpp: ^Preprocessor, tok: ^Token, msg: string, args: ..any) {
-	if cpp.err != nil {
-		cpp.err(tok.pos, msg, ..args)
-	}
-	cpp.error_count += 1
-}
-
-warn :: proc(cpp: ^Preprocessor, tok: ^Token, msg: string, args: ..any) {
-	if cpp.warn != nil {
-		cpp.warn(tok.pos, msg, ..args)
-	}
-	cpp.warning_count += 1
-}
-
-is_hash :: proc(tok: ^Token) -> bool {
-	return tok.at_bol && tok.lit == "#"
-}
-
-skip_line :: proc(cpp: ^Preprocessor, tok: ^Token) -> ^Token {
-	tok := tok
-	if tok.at_bol {
-		return tok
-	}
-	warn(cpp, tok, "extra token")
-	for tok.at_bol {
-		tok = tok.next
-	}
-	return tok
-}
-
-
-append_token :: proc(a, b: ^Token) -> ^Token {
-	if a.kind == .EOF {
-		return b
-	}
-
-	head: Token
-	curr := &head
-
-	for tok := a; tok.kind != .EOF; tok = tok.next {
-		curr.next = tokenizer.copy_token(tok)
-		curr = curr.next
-	}
-	curr.next = b
-	return head.next
-}
-
-
-is_hex_digit :: proc(x: byte) -> bool {
-	switch x {
-	case '0'..='9', 'a'..='f', 'A'..='F':
-		return true
-	}
-	return false
-}
-from_hex :: proc(x: byte) -> i32 {
-	switch x {
-	case '0'..='9':
-		return i32(x) - '0'
-	case 'a'..='f':
-		return i32(x) - 'a' + 10
-	case 'A'..='F':
-		return i32(x) - 'A' + 10
-	}
-	return 16
-}
-
-
-convert_pp_number :: proc(tok: ^Token) {
-	convert_pp_int :: proc(tok: ^Token) -> bool {
-		p := tok.lit
-		base := 10
-		if len(p) > 2 {
-			if strings.equal_fold(p[:2], "0x") && is_hex_digit(p[2]) {
-				p = p[2:]
-				base = 16
-			} else if strings.equal_fold(p[:2], "0b") && p[2] == '0' || p[2] == '1' {
-				p = p[2:]
-				base = 2
-			}
-		}
-		if base == 10 && p[0] == '0' {
-			base = 8
-		}
-
-
-		tok.val, _ = strconv.parse_i64_of_base(p, base)
-
-		l, u: int
-
-		suf: [3]byte
-		suf_n := 0
-		i := len(p)-1
-		for /**/; i >= 0 && suf_n < len(suf); i -= 1 {
-			switch p[i] {
-			case 'l', 'L':
-				suf[suf_n] = 'l'
-				l += 1
-				suf_n += 1
-			case 'u', 'U':
-				suf[suf_n] = 'u'
-				u += 1
-				suf_n += 1
-			}
-		}
-		if i < len(p) {
-			if !is_hex_digit(p[i]) && p[i] != '.' {
-				return false
-			}
-		}
-		if u > 1 {
-			return false
-		}
-
-		if l > 2 {
-			return false
-		}
-
-		if u == 1 {
-			switch l {
-			case 0: tok.type_hint = .Unsigned_Int
-			case 1: tok.type_hint = .Unsigned_Long
-			case 2: tok.type_hint = .Unsigned_Long_Long
-			}
-		} else {
-			switch l {
-			case 0: tok.type_hint = .Int
-			case 1: tok.type_hint = .Long
-			case 2: tok.type_hint = .Long_Long
-			}
-		}
-		return true
-	}
-
-	if convert_pp_int(tok) {
-		return
-	}
-
-	fval, _ := strconv.parse_f64(tok.lit)
-	tok.val = fval
-
-	end := tok.lit[len(tok.lit)-1]
-	switch end {
-	case 'f', 'F':
-		tok.type_hint = .Float
-	case 'l', 'L':
-		tok.type_hint = .Long_Double
-	case:
-		tok.type_hint = .Double
-	}
-
-}
-
-convert_pp_char :: proc(tok: ^Token) {
-	assert(len(tok.lit) >= 2)
-	r, _, _, _ := unquote_char(tok.lit, tok.lit[0])
-	tok.val = i64(r)
-
-	tok.type_hint = .Int
-	switch tok.prefix {
-	case "u": tok.type_hint = .UTF_16
-	case "U": tok.type_hint = .UTF_32
-	case "L": tok.type_hint = .UTF_Wide
-	}
-}
-
-wide_char_size :: proc(cpp: ^Preprocessor) -> int {
-	char_size := 4
-	if cpp.wide_char_size > 0 {
-		char_size = clamp(cpp.wide_char_size, 1, 4)
-		assert(char_size & (char_size-1) == 0)
-	}
-	return char_size
-}
-
-convert_pp_string :: proc(cpp: ^Preprocessor, tok: ^Token) {
-	assert(len(tok.lit) >= 2)
-	str, _, _ := unquote_string(tok.lit)
-	tok.val = str
-
-	char_size := 1
-
-	switch tok.prefix {
-	case "u8":
-		tok.type_hint = .UTF_8
-		char_size = 1
-	case "u":
-		tok.type_hint = .UTF_16
-		char_size = 2
-	case "U":
-		tok.type_hint = .UTF_32
-		char_size = 4
-	case "L":
-		tok.type_hint = .UTF_Wide
-		char_size = wide_char_size(cpp)
-	}
-
-	switch char_size {
-	case 2:
-		n: int
-		buf := make([]u16, len(str))
-		for c in str {
-			ch := c
-			if ch < 0x10000 {
-				buf[n] = u16(ch)
-				n += 1
-			} else {
-				ch -= 0x10000
-				buf[n+0] = 0xd800 + u16((ch >> 10) & 0x3ff)
-				buf[n+1] = 0xdc00 + u16(ch & 0x3ff)
-				n += 2
-			}
-		}
-		tok.val = buf[:n]
-	case 4:
-		n: int
-		buf := make([]u32, len(str))
-		for ch in str {
-			buf[n] = u32(ch)
-			n += 1
-		}
-		tok.val = buf[:n]
-	}
-
-}
-
-convert_pp_token :: proc(cpp: ^Preprocessor, t: ^Token, is_keyword: tokenizer.Is_Keyword_Proc) {
-	switch {
-	case t.kind == .Char:
-		convert_pp_char(t)
-	case t.kind == .String:
-		convert_pp_string(cpp, t)
-	case is_keyword != nil && is_keyword(t):
-		t.kind = .Keyword
-	case t.kind == .PP_Number:
-		convert_pp_number(t)
-	}
-}
-convert_pp_tokens :: proc(cpp: ^Preprocessor, tok: ^Token, is_keyword: tokenizer.Is_Keyword_Proc) {
-	for t := tok; t != nil && t.kind != .EOF; t = t.next {
-		convert_pp_token(cpp, tok, is_keyword)
-	}
-}
-
-join_adjacent_string_literals :: proc(cpp: ^Preprocessor, initial_tok: ^Token) {
-	for tok1 := initial_tok; tok1.kind != .EOF; /**/ {
-		if tok1.kind != .String || tok1.next.kind != .String {
-			tok1 = tok1.next
-			continue
-		}
-
-		type_hint := tokenizer.Token_Type_Hint.None
-		char_size := 1
-
-		start := tok1
-		for t := tok1; t != nil && t.kind == .String; t = t.next {
-			if t.val == nil {
-				convert_pp_string(cpp, t)
-			}
-			tok1 = t.next
-			if type_hint != t.type_hint {
-				if t.type_hint != .None && type_hint != .None {
-					error(cpp, t, "unsupported non-standard concatenation of string literals of different types")
-				}
-				prev_char_size := char_size
-
-				#partial switch type_hint {
-				case .UTF_8:    char_size = max(char_size, 1)
-				case .UTF_16:   char_size = max(char_size, 2)
-				case .UTF_32:   char_size = max(char_size, 4)
-				case .UTF_Wide: char_size = max(char_size, wide_char_size(cpp))
-				}
-
-				if type_hint == .None || prev_char_size < char_size {
-					type_hint = t.type_hint
-				}
-			}
-		}
-
-		// NOTE(bill): Verbose logic in order to correctly concantenate strings, even if they different in type
-		max_len := 0
-		switch char_size {
-		case 1:
-			for t := start; t != nil && t.kind == .String; t = t.next {
-				#partial switch v in t.val {
-				case string: max_len += len(v)
-				case []u16:  max_len += 2*len(v)
-				case []u32:  max_len += 4*len(v)
-				}
-			}
-			n := 0
-			buf := make([]byte, max_len)
-			for t := start; t != nil && t.kind == .String; t = t.next {
-				#partial switch v in t.val {
-				case string:
-					n += copy(buf[n:], v)
-				case []u16:
-					for i := 0; i < len(v); /**/ {
-						c1 := v[i]
-						r: rune
-						if !utf16.is_surrogate(rune(c1)) {
-							r = rune(c1)
-							i += 1
-						} else if i+1 == len(v) {
-							r = utf16.REPLACEMENT_CHAR
-							i += 1
-						} else {
-							c2 := v[i+1]
-							i += 2
-							r = utf16.decode_surrogate_pair(rune(c1), rune(c2))
-						}
-
-						b, w := utf8.encode_rune(r)
-						n += copy(buf[n:], b[:w])
-					}
-				case []u32:
-					for r in v {
-						b, w := utf8.encode_rune(rune(r))
-						n += copy(buf[n:], b[:w])
-					}
-				}
-			}
-
-			new_tok := tokenizer.copy_token(start)
-			new_tok.lit = ""
-			new_tok.val = string(buf[:n])
-			new_tok.next = tok1
-			new_tok.type_hint = type_hint
-			start^ = new_tok^
-		case 2:
-			for t := start; t != nil && t.kind == .String; t = t.next {
-				#partial switch v in t.val {
-				case string: max_len += len(v)
-				case []u16:  max_len += len(v)
-				case []u32:  max_len += 2*len(v)
-				}
-			}
-			n := 0
-			buf := make([]u16, max_len)
-			for t := start; t != nil && t.kind == .String; t = t.next {
-				#partial switch v in t.val {
-				case string:
-					for r in v {
-						if r >= 0x10000 {
-							c1, c2 := utf16.encode_surrogate_pair(r)
-							buf[n+0] = u16(c1)
-							buf[n+1] = u16(c2)
-							n += 2
-						} else {
-							buf[n] = u16(r)
-							n += 1
-						}
-					}
-				case []u16:
-					n += copy(buf[n:], v)
-				case []u32:
-					for r in v {
-						if r >= 0x10000 {
-							c1, c2 := utf16.encode_surrogate_pair(rune(r))
-							buf[n+0] = u16(c1)
-							buf[n+1] = u16(c2)
-							n += 2
-						} else {
-							buf[n] = u16(r)
-							n += 1
-						}
-					}
-				}
-			}
-
-			new_tok := tokenizer.copy_token(start)
-			new_tok.lit = ""
-			new_tok.val = buf[:n]
-			new_tok.next = tok1
-			new_tok.type_hint = type_hint
-			start^ = new_tok^
-		case 4:
-			for t := start; t != nil && t.kind == .String; t = t.next {
-				#partial switch v in t.val {
-				case string: max_len += len(v)
-				case []u16:  max_len += len(v)
-				case []u32:  max_len += len(v)
-				}
-			}
-			n := 0
-			buf := make([]u32, max_len)
-			for t := start; t != nil && t.kind == .String; t = t.next {
-				#partial switch v in t.val {
-				case string:
-					for r in v {
-						buf[n] = u32(r)
-						n += 1
-					}
-				case []u16:
-					for i := 0; i < len(v); /**/ {
-						c1 := v[i]
-						if !utf16.is_surrogate(rune(c1)) {
-							buf[n] = u32(c1)
-							n += 1
-							i += 1
-						} else if i+1 == len(v) {
-							buf[n] = utf16.REPLACEMENT_CHAR
-							n += 1
-							i += 1
-						} else {
-							c2 := v[i+1]
-							i += 2
-							r := utf16.decode_surrogate_pair(rune(c1), rune(c2))
-							buf[n] = u32(r)
-							n += 1
-						}
-					}
-				case []u32:
-					n += copy(buf[n:], v)
-				}
-			}
-
-			new_tok := tokenizer.copy_token(start)
-			new_tok.lit = ""
-			new_tok.val = buf[:n]
-			new_tok.next = tok1
-			new_tok.type_hint = type_hint
-			start^ = new_tok^
-		}
-	}
-}
-
-
-quote_string :: proc(s: string) -> []byte {
-	b := strings.builder_make(0, len(s)+2)
-	io.write_quoted_string(strings.to_writer(&b), s, '"')
-	return b.buf[:]
-}
-
-
-_init_tokenizer_from_preprocessor :: proc(t: ^Tokenizer, cpp: ^Preprocessor) -> ^Tokenizer {
-	t.warn = cpp.warn
-	t.err = cpp.err
-	return t
-}
-
-new_string_token :: proc(cpp: ^Preprocessor, str: string, tok: ^Token) -> ^Token {
-	assert(tok != nil)
-	assert(str != "")
-	t := _init_tokenizer_from_preprocessor(&Tokenizer{}, cpp)
-	src := quote_string(str)
-	return tokenizer.inline_tokenize(t, tok, src)
-}
-
-stringize :: proc(cpp: ^Preprocessor, hash, arg: ^Token) -> ^Token {
-	s := join_tokens(arg, nil)
-	return new_string_token(cpp, s, hash)
-}
-
-
-new_number_token :: proc(cpp: ^Preprocessor, i: i64, tok: ^Token) -> ^Token {
-	t := _init_tokenizer_from_preprocessor(&Tokenizer{}, cpp)
-	buf: [32]byte
-	n := len(strconv.append_int(buf[:], i, 10))
-	src := make([]byte, n)
-	copy(src, buf[:n])
-	return tokenizer.inline_tokenize(t, tok, src)
-}
-
-
-find_macro :: proc(cpp: ^Preprocessor, tok: ^Token) -> ^Macro {
-	if tok.kind != .Ident {
-		return nil
-	}
-	return cpp.macros[tok.lit]
-}
-
-add_macro :: proc(cpp: ^Preprocessor, name: string, kind: Macro_Kind, body: ^Token) -> ^Macro {
-	m := new(Macro)
-	m.name = name
-	m.kind = kind
-	m.body = body
-	cpp.macros[name] = m
-	return m
-}
-
-
-undef_macro :: proc(cpp: ^Preprocessor, name: string) {
-	delete_key(&cpp.macros, name)
-}
-
-add_builtin :: proc(cpp: ^Preprocessor, name: string, handler: Macro_Handler) -> ^Macro {
-	m := add_macro(cpp, name, .Value_Like, nil)
-	m.handler = handler
-	return m
-}
-
-
-skip :: proc(cpp: ^Preprocessor, tok: ^Token, op: string) -> ^Token {
-	if tok.lit != op {
-		error(cpp, tok, "expected '%q'", op)
-	}
-	return tok.next
-}
-
-consume :: proc(rest: ^^Token, tok: ^Token, lit: string) -> bool {
-	if tok.lit == lit {
-		rest^ = tok.next
-		return true
-	}
-	rest^ = tok
-	return false
-}
-
-read_macro_params :: proc(cpp: ^Preprocessor, rest: ^^Token, tok: ^Token) -> (param: ^Macro_Param, va_args_name: string) {
-	head: Macro_Param
-	curr := &head
-
-	tok := tok
-	for tok.lit != ")" && tok.kind != .EOF {
-		if curr != &head {
-			tok = skip(cpp, tok, ",")
-		}
-
-		if tok.lit == "..." {
-			va_args_name = "__VA_ARGS__"
-			rest^ = skip(cpp, tok.next, ")")
-			param = head.next
-			return
-		}
-
-		if tok.kind != .Ident {
-			error(cpp, tok, "expected an identifier")
-		}
-
-		if tok.next.lit == "..." {
-			va_args_name = tok.lit
-			rest^ = skip(cpp, tok.next.next, ")")
-			param = head.next
-			return
-		}
-
-		m := new(Macro_Param)
-		m.name = tok.lit
-		curr.next = m
-		curr = curr.next
-		tok = tok.next
-	}
-
-
-	rest^ = tok.next
-	param = head.next
-	return
-}
-
-copy_line :: proc(rest: ^^Token, tok: ^Token) -> ^Token {
-	head: Token
-	curr := &head
-
-	tok := tok
-	for ; !tok.at_bol; tok = tok.next {
-		curr.next = tokenizer.copy_token(tok)
-		curr = curr.next
-	}
-	curr.next = tokenizer.new_eof(tok)
-	rest^ = tok
-	return head.next
-}
-
-read_macro_definition :: proc(cpp: ^Preprocessor, rest: ^^Token, tok: ^Token) {
-	tok := tok
-	if tok.kind != .Ident {
-		error(cpp, tok, "macro name must be an identifier")
-	}
-	name := tok.lit
-	tok = tok.next
-
-	if !tok.has_space && tok.lit == "(" {
-		params, va_args_name := read_macro_params(cpp, &tok, tok.next)
-
-		m := add_macro(cpp, name, .Function_Like, copy_line(rest, tok))
-		m.params = params
-		m.va_args_name = va_args_name
-	} else {
-		add_macro(cpp, name, .Value_Like, copy_line(rest, tok))
-	}
-}
-
-
-join_tokens :: proc(tok, end: ^Token) -> string {
-	n := 1
-	for t := tok; t != end && t.kind != .EOF; t = t.next {
-		if t != tok && t.has_space {
-			n += 1
-		}
-		n += len(t.lit)
-	}
-
-	buf := make([]byte, n)
-
-	pos := 0
-	for t := tok; t != end && t.kind != .EOF; t = t.next {
-		if t != tok && t.has_space {
-			buf[pos] = ' '
-			pos += 1
-		}
-		copy(buf[pos:], t.lit)
-		pos += len(t.lit)
-	}
-
-	return string(buf[:pos])
-}
-
-read_include_filename :: proc(cpp: ^Preprocessor, rest: ^^Token, tok: ^Token) -> (filename: string, is_quote: bool) {
-	tok := tok
-
-	if tok.kind == .String {
-		rest^ = skip_line(cpp, tok.next)
-		filename = tok.lit[1:len(tok.lit)-1]
-		is_quote = true
-		return
-	}
-
-	if tok.lit == "<" {
-		start := tok
-		for ; tok.kind != .EOF; tok = tok.next {
-			if tok.at_bol || tok.kind == .EOF {
-				error(cpp, tok, "expected '>'")
-			}
-			is_quote = false
-			if tok.lit == ">" {
-				break
-			}
-		}
-		rest^ = skip_line(cpp, tok.next)
-		filename = join_tokens(start.next, tok)
-		return
-	}
-
-	if tok.kind == .Ident {
-		tok2 := preprocess_internal(cpp, copy_line(rest, tok))
-		return read_include_filename(cpp, &tok2, tok2)
-	}
-
-	error(cpp, tok, "expected a filename")
-	return
-}
-
-skip_cond_incl :: proc(tok: ^Token) -> ^Token {
-	next_skip :: proc(tok: ^Token) -> ^Token {
-		tok := tok
-		for tok.kind != .EOF {
-			if is_hash(tok) {
-				switch tok.next.lit {
-				case "if", "ifdef", "ifndef":
-					tok = next_skip(tok.next.next)
-					continue
-
-				case "endif":
-					return tok.next.next
-				}
-			}
-			tok = tok.next
-		}
-		return tok
-	}
-
-	tok := tok
-
-	loop: for tok.kind != .EOF {
-		if is_hash(tok) {
-			switch tok.next.lit {
-			case "if", "ifdef", "ifndef":
-				tok = next_skip(tok.next.next)
-				continue loop
-
-			case "elif", "else", "endif":
-				break loop
-			}
-		}
-
-		tok = tok.next
-	}
-	return tok
-}
-
-check_for_include_guard :: proc(tok: ^Token) -> (guard: string, ok: bool) {
-	if !is_hash(tok) || tok.next.lit != "ifndef" {
-		return
-	}
-	tok := tok
-	tok = tok.next.next
-
-	if tok.kind != .Ident {
-		return
-	}
-
-	m := tok.lit
-	tok = tok.next
-
-	if !is_hash(tok) || tok.next.lit != "define" || tok.next.lit != "macro" {
-		return
-	}
-
-	for tok.kind != .EOF {
-		if !is_hash(tok) {
-			tok = tok.next
-			continue
-		}
-
-		if tok.next.lit == "endif" && tok.next.next.kind == .EOF {
-			return m, true
-		}
-
-		switch tok.lit {
-		case "if", "ifdef", "ifndef":
-			tok = skip_cond_incl(tok.next)
-		case:
-			tok = tok.next
-		}
-	}
-	return
-}
-
-include_file :: proc(cpp: ^Preprocessor, tok: ^Token, path: string, filename_tok: ^Token) -> ^Token {
-	if cpp.pragma_once[path] {
-		return tok
-	}
-
-	guard_name, guard_name_found := cpp.include_guards[path]
-	if guard_name_found && cpp.macros[guard_name] != nil {
-		return tok
-	}
-
-	if !os.exists(path) {
-		error(cpp, filename_tok, "%s: cannot open file", path)
-		return tok
-	}
-
-	cpp.include_level += 1
-	if cpp.include_level > MAX_INCLUDE_LEVEL {
-		error(cpp, tok, "exceeded maximum nest amount: %d", MAX_INCLUDE_LEVEL)
-		return tok
-	}
-
-	t := _init_tokenizer_from_preprocessor(&Tokenizer{}, cpp)
-	tok2 := tokenizer.tokenize_file(t, path, /*file.id*/1)
-	if tok2 == nil {
-		error(cpp, filename_tok, "%s: cannot open file", path)
-	}
-	cpp.include_level -= 1
-
-	guard_name, guard_name_found = check_for_include_guard(tok2)
-	if guard_name_found {
-		cpp.include_guards[path] = guard_name
-	}
-
-	return append_token(tok2, tok)
-}
-
-find_arg :: proc(args: ^Macro_Arg, tok: ^Token) -> ^Macro_Arg {
-	for ap := args; ap != nil; ap = ap.next {
-		if tok.lit == ap.name {
-			return ap
-		}
-	}
-	return nil
-}
-
-paste :: proc(cpp: ^Preprocessor, lhs, rhs: ^Token) -> ^Token {
-	buf := strings.concatenate({lhs.lit, rhs.lit})
-	t := _init_tokenizer_from_preprocessor(&Tokenizer{}, cpp)
-	tok := tokenizer.inline_tokenize(t, lhs, transmute([]byte)buf)
-	if tok.next.kind != .EOF {
-		error(cpp, lhs, "pasting forms '%s', an invalid token", buf)
-	}
-	return tok
-}
-
-has_varargs :: proc(args: ^Macro_Arg) -> bool {
-	for ap := args; ap != nil; ap = ap.next {
-		if ap.name == "__VA_ARGS__" {
-			return ap.tok.kind != .EOF
-		}
-	}
-	return false
-}
-
-substitute_token :: proc(cpp: ^Preprocessor, tok: ^Token, args: ^Macro_Arg) -> ^Token {
-	head: Token
-	curr := &head
-	tok := tok
-	for tok.kind != .EOF {
-		if tok.lit == "#" {
-			arg := find_arg(args, tok.next)
-			if arg == nil {
-				error(cpp, tok.next, "'#' is not followed by a macro parameter")
-			}
-			arg_tok := arg.tok if arg != nil else tok.next
-			curr.next = stringize(cpp, tok, arg_tok)
-			curr = curr.next
-			tok = tok.next.next
-			continue
-		}
-
-		if tok.lit == "," && tok.next.lit == "##" {
-			if arg := find_arg(args, tok.next.next); arg != nil && arg.is_va_args {
-				if arg.tok.kind == .EOF {
-					tok = tok.next.next.next
-				} else {
-					curr.next = tokenizer.copy_token(tok)
-					curr = curr.next
-					tok = tok.next.next
-				}
-				continue
-			}
-		}
-
-		if tok.lit == "##" {
-			if curr == &head {
-				error(cpp, tok, "'##' cannot appear at start of macro expansion")
-			}
-			if tok.next.kind == .EOF {
-				error(cpp, tok, "'##' cannot appear at end of macro expansion")
-			}
-
-			if arg := find_arg(args, tok.next); arg != nil {
-				if arg.tok.kind != .EOF {
-					curr^ = paste(cpp, curr, arg.tok)^
-					for t := arg.tok.next; t.kind != .EOF; t = t.next {
-						curr.next = tokenizer.copy_token(t)
-						curr = curr.next
-					}
-				}
-				tok = tok.next.next
-				continue
-			}
-
-			curr^ = paste(cpp, curr, tok.next)^
-			tok = tok.next.next
-			continue
-		}
-
-		arg := find_arg(args, tok)
-
-		if arg != nil && tok.next.lit == "##" {
-			rhs := tok.next.next
-
-			if arg.tok.kind == .EOF {
-				args2 := find_arg(args, rhs)
-				if args2 != nil {
-					for t := args.tok; t.kind != .EOF; t = t.next {
-						curr.next = tokenizer.copy_token(t)
-						curr = curr.next
-					}
-				} else {
-					curr.next = tokenizer.copy_token(rhs)
-					curr = curr.next
-				}
-				tok = rhs.next
-				continue
-			}
-
-			for t := arg.tok; t.kind != .EOF; t = t.next {
-				curr.next = tokenizer.copy_token(t)
-				curr = curr.next
-			}
-			tok = tok.next
-			continue
-		}
-
-		if tok.lit == "__VA_OPT__" && tok.next.lit == "(" {
-			opt_arg := read_macro_arg_one(cpp, &tok, tok.next.next, true)
-			if has_varargs(args) {
-				for t := opt_arg.tok; t.kind != .EOF; t = t.next {
-					curr.next = t
-					curr = curr.next
-				}
-			}
-			tok = skip(cpp, tok, ")")
-			continue
-		}
-
-		if arg != nil {
-			t := preprocess_internal(cpp, arg.tok)
-			t.at_bol = tok.at_bol
-			t.has_space = tok.has_space
-			for ; t.kind != .EOF; t = t.next {
-				curr.next = tokenizer.copy_token(t)
-				curr = curr.next
-			}
-			tok = tok.next
-			continue
-		}
-
-		curr.next = tokenizer.copy_token(tok)
-		curr = curr.next
-		tok = tok.next
-		continue
-	}
-
-	curr.next = tok
-	return head.next
-}
-
-read_macro_arg_one :: proc(cpp: ^Preprocessor, rest: ^^Token, tok: ^Token, read_rest: bool) -> ^Macro_Arg {
-	tok := tok
-	head: Token
-	curr := &head
-	level := 0
-	for {
-		if level == 0 && tok.lit == ")" {
-			break
-		}
-		if level == 0 && !read_rest && tok.lit == "," {
-			break
-		}
-
-		if tok.kind == .EOF {
-			error(cpp, tok, "premature end of input")
-		}
-
-		switch tok.lit {
-		case "(": level += 1
-		case ")": level -= 1
-		}
-
-		curr.next = tokenizer.copy_token(tok)
-		curr = curr.next
-		tok = tok.next
-	}
-	curr.next = tokenizer.new_eof(tok)
-
-	arg := new(Macro_Arg)
-	arg.tok = head.next
-	rest^ = tok
-	return arg
-}
-
-read_macro_args :: proc(cpp: ^Preprocessor, rest: ^^Token, tok: ^Token, params: ^Macro_Param, va_args_name: string) -> ^Macro_Arg {
-	tok := tok
-	start := tok
-	tok = tok.next.next
-
-	head: Macro_Arg
-	curr := &head
-
-	pp := params
-	for ; pp != nil; pp = pp.next {
-		if curr != &head {
-			tok = skip(cpp, tok, ",")
-		}
-		curr.next = read_macro_arg_one(cpp, &tok, tok, false)
-		curr = curr.next
-		curr.name = pp.name
-	}
-
-	if va_args_name != "" {
-		arg: ^Macro_Arg
-		if tok.lit == ")" {
-			arg = new(Macro_Arg)
-			arg.tok = tokenizer.new_eof(tok)
-		} else {
-			if pp != params {
-				tok = skip(cpp, tok, ",")
-			}
-			arg = read_macro_arg_one(cpp, &tok, tok, true)
-		}
-		arg.name = va_args_name
-		arg.is_va_args = true
-		curr.next = arg
-		curr = curr.next
-	} else if pp != nil {
-		error(cpp, start, "too many arguments")
-	}
-
-	skip(cpp, tok, ")")
-	rest^ = tok
-	return head.next
-}
-
-expand_macro :: proc(cpp: ^Preprocessor, rest: ^^Token, tok: ^Token) -> bool {
-	if tokenizer.hide_set_contains(tok.hide_set, tok.lit) {
-		return false
-	}
-	tok := tok
-	m := find_macro(cpp, tok)
-	if m == nil {
-		return false
-	}
-
-	if m.handler != nil {
-		rest^ = m.handler(cpp, tok)
-		rest^.next = tok.next
-		return true
-	}
-
-	if m.kind == .Value_Like {
-		hs := tokenizer.hide_set_union(tok.hide_set, tokenizer.new_hide_set(m.name))
-		body := tokenizer.add_hide_set(m.body, hs)
-		for t := body; t.kind != .EOF; t = t.next {
-			t.origin = tok
-		}
-		rest^ = append_token(body, tok.next)
-		rest^.at_bol = tok.at_bol
-		rest^.has_space = tok.has_space
-		return true
-	}
-
-	if tok.next.lit != "(" {
-		return false
-	}
-
-	macro_token := tok
-	args := read_macro_args(cpp, &tok, tok, m.params, m.va_args_name)
-	close_paren := tok
-
-	hs := tokenizer.hide_set_intersection(macro_token.hide_set, close_paren.hide_set)
-	hs = tokenizer.hide_set_union(hs, tokenizer.new_hide_set(m.name))
-
-	body := substitute_token(cpp, m.body, args)
-	body = tokenizer.add_hide_set(body, hs)
-	for t := body; t.kind != .EOF; t = t.next {
-		t.origin = macro_token
-	}
-	rest^ = append_token(body, tok.next)
-	rest^.at_bol = macro_token.at_bol
-	rest^.has_space = macro_token.has_space
-	return true
-}
-
-search_include_next :: proc(cpp: ^Preprocessor, filename: string) -> (path: string, ok: bool) {
-	for ; cpp.include_next_index < len(cpp.include_paths); cpp.include_next_index += 1 {
-		tpath := filepath.join({cpp.include_paths[cpp.include_next_index], filename}, allocator=context.temp_allocator)
-		if os.exists(tpath) {
-			return strings.clone(tpath), true
-		}
-	}
-	return
-}
-
-search_include_paths :: proc(cpp: ^Preprocessor, filename: string) -> (path: string, ok: bool) {
-	if filepath.is_abs(filename) {
-		return filename, true
-	}
-
-	if path, ok = cpp.filepath_cache[filename]; ok {
-		return
-	}
-
-	for include_path in cpp.include_paths {
-		tpath := filepath.join({include_path, filename}, allocator=context.temp_allocator)
-		if os.exists(tpath) {
-			path, ok = strings.clone(tpath), true
-			cpp.filepath_cache[filename] = path
-			return
-		}
-	}
-
-	return
-}
-
-read_const_expr :: proc(cpp: ^Preprocessor, rest: ^^Token, tok: ^Token) -> ^Token {
-	tok := tok
-	tok = copy_line(rest, tok)
-	head: Token
-	curr := &head
-	for tok.kind != .EOF {
-		if tok.lit == "defined" {
-			start := tok
-			has_paren := consume(&tok, tok.next, "(")
-			if tok.kind != .Ident {
-				error(cpp, start, "macro name must be an identifier")
-			}
-			m := find_macro(cpp, tok)
-			tok = tok.next
-
-			if has_paren {
-				tok = skip(cpp, tok, ")")
-			}
-
-			curr.next = new_number_token(cpp, 1 if m != nil else 0, start)
-			curr = curr.next
-			continue
-		}
-
-		curr.next = tok
-		curr = curr.next
-		tok = tok.next
-	}
-
-	curr.next = tok
-	return head.next
-}
-
-eval_const_expr :: proc(cpp: ^Preprocessor, rest: ^^Token, tok: ^Token) -> (val: i64) {
-	tok := tok
-	start := tok
-	expr := read_const_expr(cpp, rest, tok.next)
-	expr = preprocess_internal(cpp, expr)
-
-	if expr.kind == .EOF {
-		error(cpp, start, "no expression")
-	}
-
-	for t := expr; t.kind != .EOF; t = t.next {
-		if t.kind == .Ident {
-			next := t.next
-			t^ = new_number_token(cpp, 0, t)^
-			t.next = next
-		}
-	}
-
-	val = 1
-	convert_pp_tokens(cpp, expr, tokenizer.default_is_keyword)
-
-	rest2: ^Token
-	val = const_expr(&rest2, expr)
-	if rest2 != nil && rest2.kind != .EOF {
-		error(cpp, rest2, "extra token")
-	}
-	return
-}
-
-push_cond_incl :: proc(cpp: ^Preprocessor, tok: ^Token, included: bool) -> ^Cond_Incl {
-	ci := new(Cond_Incl)
-	ci.next = cpp.cond_incl
-	ci.state = .In_Then
-	ci.tok = tok
-	ci.included = included
-	cpp.cond_incl = ci
-	return ci
-}
-
-read_line_marker:: proc(cpp: ^Preprocessor, rest: ^^Token, tok: ^Token) {
-	tok := tok
-	start := tok
-	tok = preprocess(cpp, copy_line(rest, tok))
-	if tok.kind != .Number {
-		error(cpp, tok, "invalid line marker")
-	}
-	ival, _ := tok.val.(i64)
-	start.file.line_delta = int(ival - i64(start.pos.line))
-	tok = tok.next
-	if tok.kind == .EOF {
-		return
-	}
-
-	if tok.kind != .String {
-		error(cpp, tok, "filename expected")
-	}
-	start.file.display_name = tok.lit
-}
-
-preprocess_internal :: proc(cpp: ^Preprocessor, tok: ^Token) -> ^Token {
-	head: Token
-	curr := &head
-
-	tok := tok
-	for tok != nil && tok.kind != .EOF {
-		if expand_macro(cpp, &tok, tok) {
-			continue
-		}
-
-		if !is_hash(tok) {
-			if tok.file != nil {
-				tok.line_delta = tok.file.line_delta
-			}
-			curr.next = tok
-			curr = curr.next
-			tok = tok.next
-			continue
-		}
-
-		start := tok
-		tok = tok.next
-
-		switch tok.lit {
-		case "include":
-			filename, is_quote := read_include_filename(cpp, &tok, tok.next)
-			is_absolute := filepath.is_abs(filename)
-			if is_absolute {
-				tok = include_file(cpp, tok, filename, start.next.next)
-				continue
-			}
-
-			if is_quote {
-				dir := ""
-				if start.file != nil {
-					dir = filepath.dir(start.file.name)
-				}
-				path := filepath.join({dir, filename})
-				if os.exists(path) {
-					tok = include_file(cpp, tok, path, start.next.next)
-					continue
-				}
-			}
-
-			path, ok := search_include_paths(cpp, filename)
-			if !ok {
-				path = filename
-			}
-			tok = include_file(cpp, tok, path, start.next.next)
-			continue
-
-		case "include_next":
-			filename, _ := read_include_filename(cpp, &tok, tok.next)
-			path, ok := search_include_next(cpp, filename)
-			if !ok {
-				path = filename
-			}
-			tok = include_file(cpp, tok, path, start.next.next)
-			continue
-
-		case "define":
-			read_macro_definition(cpp, &tok, tok.next)
-			continue
-
-		case "undef":
-			tok = tok.next
-			if tok.kind != .Ident {
-				error(cpp, tok, "macro name must be an identifier")
-			}
-			undef_macro(cpp, tok.lit)
-			tok = skip_line(cpp, tok.next)
-			continue
-
-		case "if":
-			val := eval_const_expr(cpp, &tok, tok)
-			push_cond_incl(cpp, start, val != 0)
-			if val == 0 {
-				tok = skip_cond_incl(tok)
-			}
-			continue
-
-		case "ifdef":
-			defined := find_macro(cpp, tok.next)
-			push_cond_incl(cpp, tok, defined != nil)
-			tok = skip_line(cpp, tok.next.next)
-			if defined == nil {
-				tok = skip_cond_incl(tok)
-			}
-			continue
-
-		case "ifndef":
-			defined := find_macro(cpp, tok.next)
-			push_cond_incl(cpp, tok, defined != nil)
-			tok = skip_line(cpp, tok.next.next)
-			if !(defined == nil) {
-				tok = skip_cond_incl(tok)
-			}
-			continue
-
-		case "elif":
-			if cpp.cond_incl == nil || cpp.cond_incl.state == .In_Else {
-				error(cpp, start, "stray #elif")
-			}
-			if cpp.cond_incl != nil {
-				cpp.cond_incl.state = .In_Elif
-			}
-
-			if (cpp.cond_incl != nil && !cpp.cond_incl.included) && eval_const_expr(cpp, &tok, tok) != 0 {
-				cpp.cond_incl.included = true
-			} else {
-				tok = skip_cond_incl(tok)
-			}
-			continue
-
-		case "else":
-			if cpp.cond_incl == nil || cpp.cond_incl.state == .In_Else {
-				error(cpp, start, "stray #else")
-			}
-			if cpp.cond_incl != nil {
-				cpp.cond_incl.state = .In_Else
-			}
-			tok = skip_line(cpp, tok.next)
-
-			if cpp.cond_incl != nil {
-				tok = skip_cond_incl(tok)
-			}
-			continue
-
-		case "endif":
-			if cpp.cond_incl == nil {
-				error(cpp, start, "stray #endif")
-			} else {
-				cpp.cond_incl = cpp.cond_incl.next
-			}
-			tok = skip_line(cpp, tok.next)
-			continue
-
-		case "line":
-			read_line_marker(cpp, &tok, tok.next)
-			continue
-
-		case "pragma":
-			if tok.next.lit == "once" {
-				cpp.pragma_once[tok.pos.file] = true
-				tok = skip_line(cpp, tok.next.next)
-				continue
-			}
-
-			pragma_tok, pragma_end := tok, tok
-
-			for tok != nil && tok.kind != .EOF {
-				pragma_end = tok
-				tok = tok.next
-				if tok.at_bol {
-					break
-				}
-			}
-			pragma_end.next = tokenizer.new_eof(tok)
-			if cpp.pragma_handler != nil {
-				cpp.pragma_handler(cpp, pragma_tok.next)
-				continue
-			}
-
-			continue
-
-		case "error":
-			error(cpp, tok, "error")
-		}
-
-		if tok.kind == .PP_Number {
-			read_line_marker(cpp, &tok, tok)
-			continue
-		}
-
-		if !tok.at_bol {
-			error(cpp, tok, "invalid preprocessor directive")
-		}
-	}
-
-	curr.next = tok
-	return head.next
-}
-
-
-preprocess :: proc(cpp: ^Preprocessor, tok: ^Token) -> ^Token {
-	tok := tok
-	tok = preprocess_internal(cpp, tok)
-	if cpp.cond_incl != nil {
-		error(cpp, tok, "unterminated conditional directive")
-	}
-	convert_pp_tokens(cpp, tok, tokenizer.default_is_keyword)
-	join_adjacent_string_literals(cpp, tok)
-	for t := tok; t != nil; t = t.next {
-		t.pos.line += t.line_delta
-	}
-	return tok
-}
-
-
-define_macro :: proc(cpp: ^Preprocessor, name, def: string) {
-	src := transmute([]byte)def
-
-	file := new(tokenizer.File)
-	file.id = -1
-	file.src = src
-	file.name = "<built-in>"
-	file.display_name = file.name
-
-
-	t := _init_tokenizer_from_preprocessor(&Tokenizer{}, cpp)
-	tok := tokenizer.tokenize(t, file)
-	add_macro(cpp, name, .Value_Like, tok)
-}
-
-
-file_macro :: proc(cpp: ^Preprocessor, tok: ^Token) -> ^Token {
-	tok := tok
-	for tok.origin != nil {
-		tok = tok.origin
-	}
-	i := i64(tok.pos.line + tok.file.line_delta)
-	return new_number_token(cpp, i, tok)
-}
-line_macro :: proc(cpp: ^Preprocessor, tok: ^Token) -> ^Token {
-	tok := tok
-	for tok.origin != nil {
-		tok = tok.origin
-	}
-	return new_string_token(cpp, tok.file.display_name, tok)
-}
-counter_macro :: proc(cpp: ^Preprocessor, tok: ^Token) -> ^Token {
-	i := cpp.counter
-	cpp.counter += 1
-	return new_number_token(cpp, i, tok)
-}
-
-init_default_macros :: proc(cpp: ^Preprocessor) {
-	define_macro(cpp, "__C99_MACRO_WITH_VA_ARGS", "1")
-	define_macro(cpp, "__alignof__", "_Alignof")
-	define_macro(cpp, "__const__", "const")
-	define_macro(cpp, "__inline__", "inline")
-	define_macro(cpp, "__signed__", "signed")
-	define_macro(cpp, "__typeof__", "typeof")
-	define_macro(cpp, "__volatile__", "volatile")
-
-	add_builtin(cpp, "__FILE__", file_macro)
-	add_builtin(cpp, "__LINE__", line_macro)
-	add_builtin(cpp, "__COUNTER__", counter_macro)
-}
-
-init_lookup_tables :: proc(cpp: ^Preprocessor, allocator := context.allocator) {
-	context.allocator = allocator
-	reserve(&cpp.macros,         max(16, cap(cpp.macros)))
-	reserve(&cpp.pragma_once,    max(16, cap(cpp.pragma_once)))
-	reserve(&cpp.include_guards, max(16, cap(cpp.include_guards)))
-	reserve(&cpp.filepath_cache, max(16, cap(cpp.filepath_cache)))
-}
-
-
-init_defaults :: proc(cpp: ^Preprocessor, lookup_tables_allocator := context.allocator) {
-	if cpp.warn == nil {
-		cpp.warn = tokenizer.default_warn_handler
-	}
-	if cpp.err == nil {
-		cpp.err = tokenizer.default_error_handler
-	}
-	init_lookup_tables(cpp, lookup_tables_allocator)
-	init_default_macros(cpp)
-}

+ 0 - 154
core/c/frontend/preprocessor/unquote.odin

@@ -1,154 +0,0 @@
-package c_frontend_preprocess
-
-import "core:unicode/utf8"
-
-unquote_char :: proc(str: string, quote: byte) -> (r: rune, multiple_bytes: bool, tail_string: string, success: bool) {
-	hex_to_int :: proc(c: byte) -> int {
-		switch c {
-		case '0'..='9': return int(c-'0')
-		case 'a'..='f': return int(c-'a')+10
-		case 'A'..='F': return int(c-'A')+10
-		}
-		return -1
-	}
-	w: int
-
-	if str[0] == quote && quote == '"' {
-		return
-	} else if str[0] >= 0x80 {
-		r, w = utf8.decode_rune_in_string(str)
-		return r, true, str[w:], true
-	} else if str[0] != '\\' {
-		return rune(str[0]), false, str[1:], true
-	}
-
-	if len(str) <= 1 {
-		return
-	}
-	s := str
-	c := s[1]
-	s = s[2:]
-
-	switch c {
-	case: r = rune(c)
-
-	case 'a':  r = '\a'
-	case 'b':  r = '\b'
-	case 'e':  r = '\e'
-	case 'f':  r = '\f'
-	case 'n':  r = '\n'
-	case 'r':  r = '\r'
-	case 't':  r = '\t'
-	case 'v':  r = '\v'
-	case '\\': r = '\\'
-
-	case '"':  r = '"'
-	case '\'': r = '\''
-
-	case '0'..='7':
-		v := int(c-'0')
-		if len(s) < 2 {
-			return
-		}
-		for i in 0..<len(s) {
-			d := int(s[i]-'0')
-			if d < 0 || d > 7 {
-				return
-			}
-			v = (v<<3) | d
-		}
-		s = s[2:]
-		if v > 0xff {
-			return
-		}
-		r = rune(v)
-
-	case 'x', 'u', 'U':
-		count: int
-		switch c {
-		case 'x': count = 2
-		case 'u': count = 4
-		case 'U': count = 8
-		}
-
-		if len(s) < count {
-			return
-		}
-
-		for i in 0..<count {
-			d := hex_to_int(s[i])
-			if d < 0 {
-				return
-			}
-			r = (r<<4) | rune(d)
-		}
-		s = s[count:]
-		if c == 'x' {
-			break
-		}
-		if r > utf8.MAX_RUNE {
-			return
-		}
-		multiple_bytes = true
-	}
-
-	success = true
-	tail_string = s
-	return
-}
-
-unquote_string :: proc(lit: string, allocator := context.allocator) -> (res: string, allocated, success: bool) {
-	contains_rune :: proc(s: string, r: rune) -> int {
-		for c, offset in s {
-			if c == r {
-				return offset
-			}
-		}
-		return -1
-	}
-
-	assert(len(lit) >= 2)
-
-	s := lit
-	quote := '"'
-
-	if s == `""` {
-		return "", false, true
-	}
-
-	if contains_rune(s, '\n') >= 0 {
-		return s, false, false
-	}
-
-	if contains_rune(s, '\\') < 0 && contains_rune(s, quote) < 0 {
-		if quote == '"' {
-			return s, false, true
-		}
-	}
-	s = s[1:len(s)-1]
-
-
-	buf_len := 3*len(s) / 2
-	buf := make([]byte, buf_len, allocator)
-	offset := 0
-	for len(s) > 0 {
-		r, multiple_bytes, tail_string, ok := unquote_char(s, byte(quote))
-		if !ok {
-			delete(buf)
-			return s, false, false
-		}
-		s = tail_string
-		if r < 0x80 || !multiple_bytes {
-			buf[offset] = byte(r)
-			offset += 1
-		} else {
-			b, w := utf8.encode_rune(r)
-			copy(buf[offset:], b[:w])
-			offset += w
-		}
-	}
-
-	new_string := string(buf[:offset])
-
-	return new_string, true, true
-}

+ 0 - 34
core/c/frontend/tokenizer/doc.odin

@@ -1,34 +0,0 @@
-/*
-package demo
-
-import tokenizer "core:c/frontend/tokenizer"
-import preprocessor "core:c/frontend/preprocessor"
-import "core:fmt"
-
-main :: proc() {
-	t := &tokenizer.Tokenizer{};
-	tokenizer.init_defaults(t);
-
-	cpp := &preprocessor.Preprocessor{};
-	cpp.warn, cpp.err = t.warn, t.err;
-	preprocessor.init_lookup_tables(cpp);
-	preprocessor.init_default_macros(cpp);
-	cpp.include_paths = {"my/path/to/include"};
-
-	tok := tokenizer.tokenize_file(t, "the/source/file.c", 1);
-
-	tok = preprocessor.preprocess(cpp, tok);
-	if tok != nil {
-		for t := tok; t.kind != .EOF; t = t.next {
-			fmt.println(t.lit);
-		}
-	}
-
-	fmt.println("[Done]");
-}
-*/
-
-
-package c_frontend_tokenizer
-
-

+ 0 - 68
core/c/frontend/tokenizer/hide_set.odin

@@ -1,68 +0,0 @@
-package c_frontend_tokenizer
-
-// NOTE(bill): This is a really dumb approach for a hide set,
-// but it's really simple and probably fast enough in practice
-
-
-Hide_Set :: struct {
-	next: ^Hide_Set,
-	name: string,
-}
-
-
-new_hide_set :: proc(name: string) -> ^Hide_Set {
-	hs := new(Hide_Set)
-	hs.name = name
-	return hs
-}
-
-hide_set_contains :: proc(hs: ^Hide_Set, name: string) -> bool {
-	for h := hs; h != nil; h = h.next {
-		if h.name == name {
-			return true
-		}
-	}
-	return false
-}
-
-
-hide_set_union :: proc(a, b: ^Hide_Set) -> ^Hide_Set {
-	head: Hide_Set
-	curr := &head
-
-	for h := a; h != nil; h = h.next {
-		curr.next = new_hide_set(h.name)
-		curr = curr.next
-	}
-	curr.next = b
-	return head.next
-}
-
-
-hide_set_intersection :: proc(a, b: ^Hide_Set) -> ^Hide_Set {
-	head: Hide_Set
-	curr := &head
-
-	for h := a; h != nil; h = h.next {
-		if hide_set_contains(b, h.name) {
-			curr.next = new_hide_set(h.name)
-			curr = curr.next
-		}
-	}
-	return head.next
-}
-
-
-add_hide_set :: proc(tok: ^Token, hs: ^Hide_Set) -> ^Token {
-	head: Token
-	curr := &head
-
-	tok := tok
-	for ; tok != nil; tok = tok.next {
-		t := copy_token(tok)
-		t.hide_set = hide_set_union(t.hide_set, hs)
-		curr.next = t
-		curr = curr.next
-	}
-	return head.next
-}

+ 0 - 169
core/c/frontend/tokenizer/token.odin

@@ -1,169 +0,0 @@
-package c_frontend_tokenizer
-
-
-Pos :: struct {
-	file:   string,
-	line:   int,
-	column: int,
-	offset: int,
-}
-
-Token_Kind :: enum {
-	Invalid,
-	Ident,
-	Punct,
-	Keyword,
-	Char,
-	String,
-	Number,
-	PP_Number,
-	Comment,
-	EOF,
-}
-
-File :: struct {
-	name: string,
-	id:   int,
-	src:  []byte,
-
-	display_name: string,
-	line_delta:   int,
-}
-
-
-Token_Type_Hint :: enum u8 {
-	None,
-
-	Int,
-	Long,
-	Long_Long,
-
-	Unsigned_Int,
-	Unsigned_Long,
-	Unsigned_Long_Long,
-
-	Float,
-	Double,
-	Long_Double,
-
-	UTF_8,
-	UTF_16,
-	UTF_32,
-	UTF_Wide,
-}
-
-Token_Value :: union {
-	i64,
-	f64,
-	string,
-	[]u16,
-	[]u32,
-}
-
-Token :: struct {
-	kind: Token_Kind,
-	next: ^Token,
-	lit: string,
-
-	pos:   Pos,
-	file:  ^File,
-	line_delta: int,
-	at_bol:     bool,
-	has_space:  bool,
-
-	type_hint: Token_Type_Hint,
-	val: Token_Value,
-	prefix: string,
-
-	// Preprocessor values
-	hide_set: ^Hide_Set,
-	origin:   ^Token,
-}
-
-Is_Keyword_Proc :: #type proc(tok: ^Token) -> bool
-
-copy_token :: proc(tok: ^Token) -> ^Token {
-	t, _ := new_clone(tok^)
-	t.next = nil
-	return t
-}
-
-new_eof :: proc(tok: ^Token) -> ^Token {
-	t, _ := new_clone(tok^)
-	t.kind = .EOF
-	t.lit = ""
-	return t
-}
-
-default_is_keyword :: proc(tok: ^Token) -> bool {
-	if tok.kind == .Keyword {
-		return true
-	}
-	if len(tok.lit) > 0 {
-		return default_keyword_set[tok.lit]
-	}
-	return false
-}
-
-
-token_name := [Token_Kind]string {
-	.Invalid   = "invalid",
-	.Ident     = "ident",
-	.Punct     = "punct",
-	.Keyword   = "keyword",
-	.Char      = "char",
-	.String    = "string",
-	.Number    = "number",
-	.PP_Number = "preprocessor number",
-	.Comment   = "comment",
-	.EOF       = "eof",
-}
-
-default_keyword_set := map[string]bool{
-	"auto"          = true,
-	"break"         = true,
-	"case"          = true,
-	"char"          = true,
-	"const"         = true,
-	"continue"      = true,
-	"default"       = true,
-	"do"            = true,
-	"double"        = true,
-	"else"          = true,
-	"enum"          = true,
-	"extern"        = true,
-	"float"         = true,
-	"for"           = true,
-	"goto"          = true,
-	"if"            = true,
-	"int"           = true,
-	"long"          = true,
-	"register"      = true,
-	"restrict"      = true,
-	"return"        = true,
-	"short"         = true,
-	"signed"        = true,
-	"sizeof"        = true,
-	"static"        = true,
-	"struct"        = true,
-	"switch"        = true,
-	"typedef"       = true,
-	"union"         = true,
-	"unsigned"      = true,
-	"void"          = true,
-	"volatile"      = true,
-	"while"         = true,
-	"_Alignas"      = true,
-	"_Alignof"      = true,
-	"_Atomic"       = true,
-	"_Bool"         = true,
-	"_Generic"      = true,
-	"_Noreturn"     = true,
-	"_Thread_local" = true,
-	"__restrict"    = true,
-	"typeof"        = true,
-	"asm"           = true,
-	"__restrict__"  = true,
-	"__thread"      = true,
-	"__attribute__" = true,
-}

+ 0 - 667
core/c/frontend/tokenizer/tokenizer.odin

@@ -1,667 +0,0 @@
-package c_frontend_tokenizer
-
-import "core:fmt"
-import "core:os"
-import "core:strings"
-import "core:unicode/utf8"
-
-
-Error_Handler :: #type proc(pos: Pos, fmt: string, args: ..any)
-
-
-Tokenizer :: struct {
-	// Immutable data
-	path: string,
-	src:  []byte,
-
-
-	// Tokenizing state
-	ch:          rune,
-	offset:      int,
-	read_offset: int,
-	line_offset: int,
-	line_count:  int,
-
-	// Extra information for tokens
-	at_bol:    bool,
-	has_space: bool,
-
-	// Mutable data
-	err:  Error_Handler,
-	warn: Error_Handler,
-	error_count:   int,
-	warning_count: int,
-}
-
-init_defaults :: proc(t: ^Tokenizer, err: Error_Handler = default_error_handler, warn: Error_Handler = default_warn_handler) {
-	t.err = err
-	t.warn = warn
-}
-
-
-@(private)
-offset_to_pos :: proc(t: ^Tokenizer, offset: int) -> (pos: Pos) {
-	pos.file = t.path
-	pos.offset = offset
-	pos.line = t.line_count
-	pos.column = offset - t.line_offset + 1
-	return
-}
-
-default_error_handler :: proc(pos: Pos, msg: string, args: ..any) {
-	fmt.eprintf("%s(%d:%d) ", pos.file, pos.line, pos.column)
-	fmt.eprintf(msg, ..args)
-	fmt.eprintf("\n")
-}
-
-default_warn_handler :: proc(pos: Pos, msg: string, args: ..any) {
-	fmt.eprintf("%s(%d:%d) warning: ", pos.file, pos.line, pos.column)
-	fmt.eprintf(msg, ..args)
-	fmt.eprintf("\n")
-}
-
-error_offset :: proc(t: ^Tokenizer, offset: int, msg: string, args: ..any) {
-	pos := offset_to_pos(t, offset)
-	if t.err != nil {
-		t.err(pos, msg, ..args)
-	}
-	t.error_count += 1
-}
-
-warn_offset :: proc(t: ^Tokenizer, offset: int, msg: string, args: ..any) {
-	pos := offset_to_pos(t, offset)
-	if t.warn != nil {
-		t.warn(pos, msg, ..args)
-	}
-	t.warning_count += 1
-}
-
-error :: proc(t: ^Tokenizer, tok: ^Token, msg: string, args: ..any) {
-	pos := tok.pos
-	if t.err != nil {
-		t.err(pos, msg, ..args)
-	}
-	t.error_count += 1
-}
-
-warn :: proc(t: ^Tokenizer, tok: ^Token, msg: string, args: ..any) {
-	pos := tok.pos
-	if t.warn != nil {
-		t.warn(pos, msg, ..args)
-	}
-	t.warning_count += 1
-}
-
-
-advance_rune :: proc(t: ^Tokenizer) {
-	if t.read_offset < len(t.src) {
-		t.offset = t.read_offset
-		if t.ch == '\n' {
-			t.at_bol = true
-			t.line_offset = t.offset
-			t.line_count += 1
-		}
-		r, w := rune(t.src[t.read_offset]), 1
-		switch {
-		case r == 0:
-			error_offset(t, t.offset, "illegal character NUL")
-		case r >= utf8.RUNE_SELF:
-			r, w = utf8.decode_rune(t.src[t.read_offset:])
-			if r == utf8.RUNE_ERROR && w == 1 {
-				error_offset(t, t.offset, "illegal UTF-8 encoding")
-			} else if r == utf8.RUNE_BOM && t.offset > 0 {
-				error_offset(t, t.offset, "illegal byte order mark")
-			}
-		}
-		t.read_offset += w
-		t.ch = r
-	} else {
-		t.offset = len(t.src)
-		if t.ch == '\n' {
-			t.at_bol = true
-			t.line_offset = t.offset
-			t.line_count += 1
-		}
-		t.ch = -1
-	}
-}
-
-advance_rune_n :: proc(t: ^Tokenizer, n: int) {
-	for _ in 0..<n {
-		advance_rune(t)
-	}
-}
-
-is_digit :: proc(r: rune) -> bool {
-	return '0' <= r && r <= '9'
-}
-
-skip_whitespace :: proc(t: ^Tokenizer) {
-	for {
-		switch t.ch {
-		case ' ', '\t', '\r', '\v', '\f', '\n':
-			t.has_space = true
-			advance_rune(t)
-		case:
-			return
-		}
-	}
-}
-
-scan_comment :: proc(t: ^Tokenizer) -> string {
-	offset := t.offset-1
-	next := -1
-	general: {
-		if t.ch == '/'{ // line comments
-			advance_rune(t)
-			for t.ch != '\n' && t.ch >= 0 {
-				advance_rune(t)
-			}
-
-			next = t.offset
-			if t.ch == '\n' {
-				next += 1
-			}
-			break general
-		}
-
-		/* style comment */
-		advance_rune(t)
-		for t.ch >= 0 {
-			ch := t.ch
-			advance_rune(t)
-			if ch == '*' && t.ch == '/' {
-				advance_rune(t)
-				next = t.offset
-				break general
-			}
-		}
-
-		error_offset(t, offset, "comment not terminated")
-	}
-
-	lit := t.src[offset : t.offset]
-
-	// NOTE(bill): Strip CR for line comments
-	for len(lit) > 2 && lit[1] == '/' && lit[len(lit)-1] == '\r' {
-		lit = lit[:len(lit)-1]
-	}
-
-
-	return string(lit)
-}
-
-scan_identifier :: proc(t: ^Tokenizer) -> string {
-	offset := t.offset
-
-	for is_ident1(t.ch) {
-		advance_rune(t)
-	}
-
-	return string(t.src[offset : t.offset])
-}
-
-scan_string :: proc(t: ^Tokenizer) -> string {
-	offset := t.offset-1
-
-	for {
-		ch := t.ch
-		if ch == '\n' || ch < 0 {
-			error_offset(t, offset, "string literal was not terminated")
-			break
-		}
-		advance_rune(t)
-		if ch == '"' {
-			break
-		}
-		if ch == '\\' {
-			scan_escape(t)
-		}
-	}
-
-	return string(t.src[offset : t.offset])
-}
-
-digit_val :: proc(r: rune) -> int {
-	switch r {
-	case '0'..='9':
-		return int(r-'0')
-	case 'A'..='F':
-		return int(r-'A' + 10)
-	case 'a'..='f':
-		return int(r-'a' + 10)
-	}
-	return 16
-}
-
-scan_escape :: proc(t: ^Tokenizer) -> bool {
-	offset := t.offset
-
-	esc := t.ch
-	n: int
-	base, max: u32
-	switch esc {
-	case 'a', 'b', 'e', 'f', 'n', 't', 'v', 'r', '\\', '\'', '"':
-		advance_rune(t)
-		return true
-
-	case '0'..='7':
-		for digit_val(t.ch) < 8 {
-			advance_rune(t)
-		}
-		return true
-	case 'x':
-		advance_rune(t)
-		for digit_val(t.ch) < 16 {
-			advance_rune(t)
-		}
-		return true
-	case 'u':
-		advance_rune(t)
-		n, base, max = 4, 16, utf8.MAX_RUNE
-	case 'U':
-		advance_rune(t)
-		n, base, max = 8, 16, utf8.MAX_RUNE
-	case:
-		if t.ch < 0 {
-			error_offset(t, offset, "escape sequence was not terminated")
-		} else {
-			break
-		}
-		return false
-	}
-
-	x: u32
-	main_loop: for n > 0 {
-		d := u32(digit_val(t.ch))
-		if d >= base {
-			if t.ch == '"' || t.ch == '\'' {
-				break main_loop
-			}
-			if t.ch < 0 {
-				error_offset(t, t.offset, "escape sequence was not terminated")
-			} else {
-				error_offset(t, t.offset, "illegal character '%r' : %d in escape sequence", t.ch, t.ch)
-			}
-			return false
-		}
-
-		x = x*base + d
-		advance_rune(t)
-		n -= 1
-	}
-
-	if x > max || 0xd800 <= x && x <= 0xe000 {
-		error_offset(t, offset, "escape sequence is an invalid Unicode code point")
-		return false
-	}
-	return true
-}
-
-scan_rune :: proc(t: ^Tokenizer) -> string {
-	offset := t.offset-1
-	valid := true
-	n := 0
-	for {
-		ch := t.ch
-		if ch == '\n' || ch < 0 {
-			if valid {
-				error_offset(t, offset, "rune literal not terminated")
-				valid = false
-			}
-			break
-		}
-		advance_rune(t)
-		if ch == '\'' {
-			break
-		}
-		n += 1
-		if ch == '\\' {
-			if !scan_escape(t)  {
-				valid = false
-			}
-		}
-	}
-
-	if valid && n != 1 {
-		error_offset(t, offset, "illegal rune literal")
-	}
-
-	return string(t.src[offset : t.offset])
-}
-
-scan_number :: proc(t: ^Tokenizer, seen_decimal_point: bool) -> (Token_Kind, string) {
-	scan_mantissa :: proc(t: ^Tokenizer, base: int) {
-		for digit_val(t.ch) < base {
-			advance_rune(t)
-		}
-	}
-	scan_exponent :: proc(t: ^Tokenizer) {
-		if t.ch == 'e' || t.ch == 'E' || t.ch == 'p' || t.ch == 'P' {
-			advance_rune(t)
-			if t.ch == '-' || t.ch == '+' {
-				advance_rune(t)
-			}
-			if digit_val(t.ch) < 10 {
-				scan_mantissa(t, 10)
-			} else {
-				error_offset(t, t.offset, "illegal floating-point exponent")
-			}
-		}
-	}
-	scan_fraction :: proc(t: ^Tokenizer) -> (early_exit: bool) {
-		if t.ch == '.' && peek(t) == '.' {
-			return true
-		}
-		if t.ch == '.' {
-			advance_rune(t)
-			scan_mantissa(t, 10)
-		}
-		return false
-	}
-
-	check_end := true
-
-
-	offset := t.offset
-	seen_point := seen_decimal_point
-
-	if seen_point {
-		offset -= 1
-		scan_mantissa(t, 10)
-		scan_exponent(t)
-	} else {
-		if t.ch == '0' {
-			int_base :: proc(t: ^Tokenizer, base: int, msg: string) {
-				prev := t.offset
-				advance_rune(t)
-				scan_mantissa(t, base)
-				if t.offset - prev <= 1 {
-					error_offset(t, t.offset, msg)
-				}
-			}
-
-			advance_rune(t)
-			switch t.ch {
-			case 'b', 'B':
-				int_base(t, 2, "illegal binary integer")
-			case 'x', 'X':
-				int_base(t, 16, "illegal hexadecimal integer")
-			case:
-				seen_point = false
-				scan_mantissa(t, 10)
-				if t.ch == '.' {
-					seen_point = true
-					if scan_fraction(t) {
-						check_end = false
-					}
-				}
-				if check_end {
-					scan_exponent(t)
-					check_end = false
-				}
-			}
-		}
-	}
-
-	if check_end {
-		scan_mantissa(t, 10)
-
-		if !scan_fraction(t) {
-			scan_exponent(t)
-		}
-	}
-
-	return .Number, string(t.src[offset : t.offset])
-}
-
-scan_punct :: proc(t: ^Tokenizer, ch: rune) -> (kind: Token_Kind) {
-	kind = .Punct
-	switch ch {
-	case:
-		kind = .Invalid
-
-	case '<', '>':
-		if t.ch == ch {
-			advance_rune(t)
-		}
-		if t.ch == '=' {
-			advance_rune(t)
-		}
-	case '!', '+', '-', '*', '/', '%', '^', '=':
-		if t.ch == '=' {
-			advance_rune(t)
-		}
-	case '#':
-		if t.ch == '#' {
-			advance_rune(t)
-		}
-	case '&':
-		if t.ch == '=' || t.ch == '&' {
-			advance_rune(t)
-		}
-	case '|':
-		if t.ch == '=' || t.ch == '|' {
-			advance_rune(t)
-		}
-	case '(', ')', '[', ']', '{', '}':
-		// okay
-	case '~', ',', ':', ';', '?':
-		// okay
-	case '`':
-		// okay
-	case '.':
-		if t.ch == '.' && peek(t) == '.' {
-			advance_rune(t)
-			advance_rune(t) // consume last '.'
-		}
-	}
-	return
-}
-
-peek :: proc(t: ^Tokenizer) -> byte {
-	if t.read_offset < len(t.src) {
-		return t.src[t.read_offset]
-	}
-	return 0
-}
-peek_str :: proc(t: ^Tokenizer, str: string) -> bool {
-	if t.read_offset < len(t.src) {
-		return strings.has_prefix(string(t.src[t.offset:]), str)
-	}
-	return false
-}
-
-scan_literal_prefix :: proc(t: ^Tokenizer, str: string, prefix: ^string) -> bool {
-	if peek_str(t, str) {
-		offset := t.offset
-		for _ in str {
-			advance_rune(t)
-		}
-		prefix^ = string(t.src[offset:][:len(str)-1])
-		return true
-	}
-	return false
-}
-
-
-allow_next_to_be_newline :: proc(t: ^Tokenizer) -> bool {
-	if t.ch == '\n' {
-		advance_rune(t)
-		return true
-	} else if t.ch == '\r' && peek(t) == '\n' { // allow for MS-DOS style line endings
-		advance_rune(t) // \r
-		advance_rune(t) // \n
-		return true
-	}
-	return false
-}
-
-scan :: proc(t: ^Tokenizer, f: ^File) -> ^Token {
-	skip_whitespace(t)
-
-	offset := t.offset
-
-	kind: Token_Kind
-	lit: string
-	prefix: string
-
-	switch ch := t.ch; {
-	case scan_literal_prefix(t, `u8"`, &prefix):
-		kind = .String
-		lit = scan_string(t)
-	case scan_literal_prefix(t, `u"`, &prefix):
-		kind = .String
-		lit = scan_string(t)
-	case scan_literal_prefix(t, `L"`, &prefix):
-		kind = .String
-		lit = scan_string(t)
-	case scan_literal_prefix(t, `U"`, &prefix):
-		kind = .String
-		lit = scan_string(t)
-	case scan_literal_prefix(t, `u'`, &prefix):
-		kind = .Char
-		lit = scan_rune(t)
-	case scan_literal_prefix(t, `L'`, &prefix):
-		kind = .Char
-		lit = scan_rune(t)
-	case scan_literal_prefix(t, `U'`, &prefix):
-		kind = .Char
-		lit = scan_rune(t)
-
-	case is_ident0(ch):
-		lit = scan_identifier(t)
-		kind = .Ident
-	case '0' <= ch && ch <= '9':
-		kind, lit = scan_number(t, false)
-	case:
-		advance_rune(t)
-		switch ch {
-		case -1:
-			kind = .EOF
-		case '\\':
-			kind = .Punct
-			if allow_next_to_be_newline(t) {
-				t.at_bol = true
-				t.has_space = false
-				return scan(t, f)
-			}
-
-		case '.':
-			if is_digit(t.ch) {
-				kind, lit = scan_number(t, true)
-			} else {
-				kind = scan_punct(t, ch)
-			}
-		case '"':
-			kind = .String
-			lit = scan_string(t)
-		case '\'':
-			kind = .Char
-			lit = scan_rune(t)
-		case '/':
-			if t.ch == '/' || t.ch == '*' {
-				kind = .Comment
-				lit = scan_comment(t)
-				t.has_space = true
-				break
-			}
-			fallthrough
-		case:
-			kind = scan_punct(t, ch)
-			if kind == .Invalid && ch != utf8.RUNE_BOM {
-				error_offset(t, t.offset, "illegal character '%r': %d", ch, ch)
-			}
-		}
-	}
-
-	if lit == "" {
-		lit = string(t.src[offset : t.offset])
-	}
-
-	if kind == .Comment {
-		return scan(t, f)
-	}
-
-	tok := new(Token)
-	tok.kind = kind
-	tok.lit = lit
-	tok.pos = offset_to_pos(t, offset)
-	tok.file = f
-	tok.prefix = prefix
-	tok.at_bol = t.at_bol
-	tok.has_space = t.has_space
-
-	t.at_bol, t.has_space = false, false
-
-	return tok
-}
-
-tokenize :: proc(t: ^Tokenizer, f: ^File) -> ^Token {
-	setup_tokenizer: {
-		t.src = f.src
-		t.ch = ' '
-		t.offset = 0
-		t.read_offset = 0
-		t.line_offset = 0
-		t.line_count = len(t.src) > 0 ? 1 : 0
-		t.error_count = 0
-		t.path = f.name
-
-
-		advance_rune(t)
-		if t.ch == utf8.RUNE_BOM {
-			advance_rune(t)
-		}
-	}
-
-
-	t.at_bol = true
-	t.has_space = false
-
-	head: Token
-	curr := &head
-	for {
-		tok := scan(t, f)
-		if tok == nil {
-			break
-		}
-		curr.next = tok
-		curr = curr.next
-		if tok.kind == .EOF {
-			break
-		}
-	}
-
-	return head.next
-}
-
-add_new_file :: proc(t: ^Tokenizer, name: string, src: []byte, id: int) -> ^File {
-	file := new(File)
-	file.id = id
-	file.src = src
-	file.name = name
-	file.display_name = name
-	return file
-}
-
-tokenize_file :: proc(t: ^Tokenizer, path: string, id: int, loc := #caller_location) -> ^Token {
-	src, ok := os.read_entire_file(path)
-	if !ok {
-		return nil
-	}
-	return tokenize(t, add_new_file(t, path, src, id))
-}
-
-
-inline_tokenize :: proc(t: ^Tokenizer, tok: ^Token, src: []byte) -> ^Token {
-	file := new(File)
-	file.src = src
-	if tok.file != nil {
-		file.id = tok.file.id
-		file.name = tok.file.name
-		file.display_name = tok.file.name
-	}
-
-	return tokenize(t, file)
-}

+ 0 - 116
core/c/frontend/tokenizer/unicode.odin

@@ -1,116 +0,0 @@
-package c_frontend_tokenizer
-
-
-in_range :: proc(range: []rune, c: rune) -> bool #no_bounds_check {
-	for i := 0; range[i] != -1; i += 2 {
-		if range[i] <= c && c <= range[i+1] {
-			return true
-		}
-	}
-	return false
-}
-
-
-// [https://www.sigbus.info/n1570#D] C11 allows ASCII and some multibyte characters in certan Unicode ranges to be used in an identifier.
-//
-// is_ident0 returns true if a given character is acceptable as the first character of an identifier.
-is_ident0 :: proc(c: rune) -> bool {
-	return in_range(_range_ident0, c)
-}
-// is_ident0 returns true if a given character is acceptable as a non-first character of an identifier.
-is_ident1 :: proc(c: rune) -> bool {
-	return is_ident0(c) || in_range(_range_ident1, c)
-}
-
-// Returns the number of columns needed to display a given character in a fixed-width font.
-// Based on https://www.cl.cam.ac.uk/~mgk25/ucs/wcwidth.c
-char_width :: proc(c: rune) -> int {
-	switch {
-	case in_range(_range_width0, c):
-		return 0
-	case in_range(_range_width2, c):
-		return 2
-	}
-	return 1
-}
-
-display_width :: proc(str: string) -> (w: int) {
-	for c in str {
-		w += char_width(c)
-	}
-	return
-}
-
-
-
-_range_ident0 := []rune{
-	'_', '_', 'a', 'z', 'A', 'Z', '$', '$',
-	0x00A8, 0x00A8, 0x00AA, 0x00AA, 0x00AD, 0x00AD, 0x00AF, 0x00AF,
-	0x00B2, 0x00B5, 0x00B7, 0x00BA, 0x00BC, 0x00BE, 0x00C0, 0x00D6,
-	0x00D8, 0x00F6, 0x00F8, 0x00FF, 0x0100, 0x02FF, 0x0370, 0x167F,
-	0x1681, 0x180D, 0x180F, 0x1DBF, 0x1E00, 0x1FFF, 0x200B, 0x200D,
-	0x202A, 0x202E, 0x203F, 0x2040, 0x2054, 0x2054, 0x2060, 0x206F,
-	0x2070, 0x20CF, 0x2100, 0x218F, 0x2460, 0x24FF, 0x2776, 0x2793,
-	0x2C00, 0x2DFF, 0x2E80, 0x2FFF, 0x3004, 0x3007, 0x3021, 0x302F,
-	0x3031, 0x303F, 0x3040, 0xD7FF, 0xF900, 0xFD3D, 0xFD40, 0xFDCF,
-	0xFDF0, 0xFE1F, 0xFE30, 0xFE44, 0xFE47, 0xFFFD,
-	0x10000, 0x1FFFD, 0x20000, 0x2FFFD, 0x30000, 0x3FFFD, 0x40000, 0x4FFFD,
-	0x50000, 0x5FFFD, 0x60000, 0x6FFFD, 0x70000, 0x7FFFD, 0x80000, 0x8FFFD,
-	0x90000, 0x9FFFD, 0xA0000, 0xAFFFD, 0xB0000, 0xBFFFD, 0xC0000, 0xCFFFD,
-	0xD0000, 0xDFFFD, 0xE0000, 0xEFFFD,
-	-1,
-}
-
-_range_ident1 := []rune{
-	'0', '9', '$', '$', 0x0300, 0x036F, 0x1DC0, 0x1DFF, 0x20D0, 0x20FF, 0xFE20, 0xFE2F,
-	-1,
-}
-
-
-_range_width0 := []rune{
-	0x0000, 0x001F, 0x007f, 0x00a0, 0x0300, 0x036F, 0x0483, 0x0486,
-	0x0488, 0x0489, 0x0591, 0x05BD, 0x05BF, 0x05BF, 0x05C1, 0x05C2,
-	0x05C4, 0x05C5, 0x05C7, 0x05C7, 0x0600, 0x0603, 0x0610, 0x0615,
-	0x064B, 0x065E, 0x0670, 0x0670, 0x06D6, 0x06E4, 0x06E7, 0x06E8,
-	0x06EA, 0x06ED, 0x070F, 0x070F, 0x0711, 0x0711, 0x0730, 0x074A,
-	0x07A6, 0x07B0, 0x07EB, 0x07F3, 0x0901, 0x0902, 0x093C, 0x093C,
-	0x0941, 0x0948, 0x094D, 0x094D, 0x0951, 0x0954, 0x0962, 0x0963,
-	0x0981, 0x0981, 0x09BC, 0x09BC, 0x09C1, 0x09C4, 0x09CD, 0x09CD,
-	0x09E2, 0x09E3, 0x0A01, 0x0A02, 0x0A3C, 0x0A3C, 0x0A41, 0x0A42,
-	0x0A47, 0x0A48, 0x0A4B, 0x0A4D, 0x0A70, 0x0A71, 0x0A81, 0x0A82,
-	0x0ABC, 0x0ABC, 0x0AC1, 0x0AC5, 0x0AC7, 0x0AC8, 0x0ACD, 0x0ACD,
-	0x0AE2, 0x0AE3, 0x0B01, 0x0B01, 0x0B3C, 0x0B3C, 0x0B3F, 0x0B3F,
-	0x0B41, 0x0B43, 0x0B4D, 0x0B4D, 0x0B56, 0x0B56, 0x0B82, 0x0B82,
-	0x0BC0, 0x0BC0, 0x0BCD, 0x0BCD, 0x0C3E, 0x0C40, 0x0C46, 0x0C48,
-	0x0C4A, 0x0C4D, 0x0C55, 0x0C56, 0x0CBC, 0x0CBC, 0x0CBF, 0x0CBF,
-	0x0CC6, 0x0CC6, 0x0CCC, 0x0CCD, 0x0CE2, 0x0CE3, 0x0D41, 0x0D43,
-	0x0D4D, 0x0D4D, 0x0DCA, 0x0DCA, 0x0DD2, 0x0DD4, 0x0DD6, 0x0DD6,
-	0x0E31, 0x0E31, 0x0E34, 0x0E3A, 0x0E47, 0x0E4E, 0x0EB1, 0x0EB1,
-	0x0EB4, 0x0EB9, 0x0EBB, 0x0EBC, 0x0EC8, 0x0ECD, 0x0F18, 0x0F19,
-	0x0F35, 0x0F35, 0x0F37, 0x0F37, 0x0F39, 0x0F39, 0x0F71, 0x0F7E,
-	0x0F80, 0x0F84, 0x0F86, 0x0F87, 0x0F90, 0x0F97, 0x0F99, 0x0FBC,
-	0x0FC6, 0x0FC6, 0x102D, 0x1030, 0x1032, 0x1032, 0x1036, 0x1037,
-	0x1039, 0x1039, 0x1058, 0x1059, 0x1160, 0x11FF, 0x135F, 0x135F,
-	0x1712, 0x1714, 0x1732, 0x1734, 0x1752, 0x1753, 0x1772, 0x1773,
-	0x17B4, 0x17B5, 0x17B7, 0x17BD, 0x17C6, 0x17C6, 0x17C9, 0x17D3,
-	0x17DD, 0x17DD, 0x180B, 0x180D, 0x18A9, 0x18A9, 0x1920, 0x1922,
-	0x1927, 0x1928, 0x1932, 0x1932, 0x1939, 0x193B, 0x1A17, 0x1A18,
-	0x1B00, 0x1B03, 0x1B34, 0x1B34, 0x1B36, 0x1B3A, 0x1B3C, 0x1B3C,
-	0x1B42, 0x1B42, 0x1B6B, 0x1B73, 0x1DC0, 0x1DCA, 0x1DFE, 0x1DFF,
-	0x200B, 0x200F, 0x202A, 0x202E, 0x2060, 0x2063, 0x206A, 0x206F,
-	0x20D0, 0x20EF, 0x302A, 0x302F, 0x3099, 0x309A, 0xA806, 0xA806,
-	0xA80B, 0xA80B, 0xA825, 0xA826, 0xFB1E, 0xFB1E, 0xFE00, 0xFE0F,
-	0xFE20, 0xFE23, 0xFEFF, 0xFEFF, 0xFFF9, 0xFFFB, 0x10A01, 0x10A03,
-	0x10A05, 0x10A06, 0x10A0C, 0x10A0F, 0x10A38, 0x10A3A, 0x10A3F, 0x10A3F,
-	0x1D167, 0x1D169, 0x1D173, 0x1D182, 0x1D185, 0x1D18B, 0x1D1AA, 0x1D1AD,
-	0x1D242, 0x1D244, 0xE0001, 0xE0001, 0xE0020, 0xE007F, 0xE0100, 0xE01EF,
-	-1,
-}
-
-_range_width2 := []rune{
-	0x1100, 0x115F, 0x2329, 0x2329, 0x232A, 0x232A, 0x2E80, 0x303E,
-	0x3040, 0xA4CF, 0xAC00, 0xD7A3, 0xF900, 0xFAFF, 0xFE10, 0xFE19,
-	0xFE30, 0xFE6F, 0xFF00, 0xFF60, 0xFFE0, 0xFFE6, 0x1F000, 0x1F644,
-	0x20000, 0x2FFFD, 0x30000, 0x3FFFD,
-	-1,
-}

+ 2 - 2
core/c/libc/README.md

@@ -14,7 +14,7 @@ The following is a mostly-complete projection of the C11 standard library as def
 | `<inttypes.h>`    | Fully projected                                    |
 | `<inttypes.h>`    | Fully projected                                    |
 | `<iso646.h>`      | Not applicable, use Odin's operators               |
 | `<iso646.h>`      | Not applicable, use Odin's operators               |
 | `<limits.h>`      | Not projected                                      |
 | `<limits.h>`      | Not projected                                      |
-| `<locale.h>`      | Not projected                                      |
+| `<locale.h>`      | Fully projected                                    |
 | `<math.h>`        | Mostly projected, see [limitations](#Limitations)  |
 | `<math.h>`        | Mostly projected, see [limitations](#Limitations)  |
 | `<setjmp.h>`      | Fully projected                                    |
 | `<setjmp.h>`      | Fully projected                                    |
 | `<signal.h>`      | Fully projected                                    |
 | `<signal.h>`      | Fully projected                                    |
@@ -70,4 +70,4 @@ with the following copyright.
 
 
 ```
 ```
 Copyright 2021 Dale Weiler <[email protected]>.
 Copyright 2021 Dale Weiler <[email protected]>.
-```
+```

+ 3 - 3
core/c/libc/complex.odin

@@ -47,8 +47,8 @@ foreign libc {
 	clogf   :: proc(z: complex_float) -> complex_float ---
 	clogf   :: proc(z: complex_float) -> complex_float ---
 
 
 	// 7.3.8 Power and absolute-value functions
 	// 7.3.8 Power and absolute-value functions
-	cabs    :: proc(z: complex_double) -> complex_double ---
-	cabsf   :: proc(z: complex_float) -> complex_float ---
+	cabs    :: proc(z: complex_double) -> double ---
+	cabsf   :: proc(z: complex_float) -> float ---
 	cpow    :: proc(x, y: complex_double) -> complex_double ---
 	cpow    :: proc(x, y: complex_double) -> complex_double ---
 	cpowf   :: proc(x, y: complex_float) -> complex_float ---
 	cpowf   :: proc(x, y: complex_float) -> complex_float ---
 	csqrt   :: proc(z: complex_double) -> complex_double ---
 	csqrt   :: proc(z: complex_double) -> complex_double ---
@@ -67,7 +67,7 @@ foreign libc {
 	crealf  :: proc(z: complex_float) -> float ---
 	crealf  :: proc(z: complex_float) -> float ---
 }
 }
 
 
-import builtin "core:builtin"
+import builtin "base:builtin"
 
 
 complex_float  :: distinct builtin.complex64
 complex_float  :: distinct builtin.complex64
 complex_double :: distinct builtin.complex128
 complex_double :: distinct builtin.complex128

+ 28 - 2
core/c/libc/errno.odin

@@ -40,7 +40,7 @@ when ODIN_OS == .FreeBSD {
 	ERANGE :: 34
 	ERANGE :: 34
 }
 }
 
 
-when ODIN_OS == .OpenBSD {
+when ODIN_OS == .OpenBSD || ODIN_OS == .NetBSD {
 	@(private="file")
 	@(private="file")
 	@(default_calling_convention="c")
 	@(default_calling_convention="c")
 	foreign libc {
 	foreign libc {
@@ -80,10 +80,36 @@ when ODIN_OS == .Darwin {
 	ERANGE :: 34
 	ERANGE :: 34
 }
 }
 
 
+when ODIN_OS == .Haiku {
+	@(private="file")
+	@(default_calling_convention="c")
+	foreign libc {
+		@(link_name="_errnop")
+		_get_errno :: proc() -> ^int ---
+	}
+
+	@(private="file")
+	B_GENERAL_ERROR_BASE :: min(i32)
+	@(private="file")
+	B_POSIX_ERROR_BASE   :: B_GENERAL_ERROR_BASE + 0x7000
+
+	EDOM   :: B_POSIX_ERROR_BASE + 16
+	EILSEQ :: B_POSIX_ERROR_BASE + 38
+	ERANGE :: B_POSIX_ERROR_BASE + 17
+}
+
+when ODIN_OS == .JS {
+	_ :: libc
+	_get_errno :: proc "c" () -> ^int {
+		@(static) errno: int
+		return &errno
+	}
+}
+
 // Odin has no way to make an identifier "errno" behave as a function call to
 // Odin has no way to make an identifier "errno" behave as a function call to
 // read the value, or to produce an lvalue such that you can assign a different
 // read the value, or to produce an lvalue such that you can assign a different
 // error value to errno. To work around this, just expose it as a function like
 // error value to errno. To work around this, just expose it as a function like
 // it actually is.
 // it actually is.
-errno :: #force_inline proc() -> ^int {
+errno :: #force_inline proc "contextless" () -> ^int {
 	return _get_errno()
 	return _get_errno()
 }
 }

+ 133 - 0
core/c/libc/locale.odin

@@ -0,0 +1,133 @@
+package libc
+
+import "core:c"
+
+when ODIN_OS == .Windows {
+	foreign import libc "system:libucrt.lib"
+} else when ODIN_OS == .Darwin {
+	foreign import libc "system:System.framework"
+} else {
+	foreign import libc "system:c"
+}
+
+// locale.h - category macros
+
+foreign libc {
+	/*
+	Sets the components of an object with the type lconv with the values appropriate for the
+	formatting of numeric quantities (monetary and otherwise) according to the rules of the current
+	locale.
+
+	Returns: a pointer to the lconv structure, might be invalidated by subsequent calls to localeconv() and setlocale()
+
+	[[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/localeconv.html ]]
+	*/
+	localeconv :: proc() -> ^lconv ---
+
+	/*
+	Selects the appropriate piece of the global locale, as specified by the category and locale arguments,
+	and can be used to change or query the entire global locale or portions thereof.
+
+	Returns: the current locale if `locale` is `nil`, the set locale otherwise
+
+	[[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/setlocale.html ]]
+	*/
+	@(link_name=LSETLOCALE)
+	setlocale :: proc(category: Locale_Category, locale: cstring) -> cstring ---
+}
+
+Locale_Category :: enum c.int {
+	ALL      = LC_ALL,
+	COLLATE  = LC_COLLATE,
+	CTYPE    = LC_CTYPE,
+	MESSAGES = LC_MESSAGES,
+	MONETARY = LC_MONETARY,
+	NUMERIC  = LC_NUMERIC,
+	TIME     = LC_TIME,
+}
+
+when ODIN_OS == .NetBSD {
+	@(private) LSETLOCALE :: "__setlocale50"
+} else {
+	@(private) LSETLOCALE :: "setlocale"
+}
+
+when ODIN_OS == .Windows {
+	lconv :: struct {
+		decimal_point:        cstring,
+		thousand_sep:         cstring,
+		grouping:             cstring,
+		int_curr_symbol:      cstring,
+		currency_symbol:      cstring,
+		mon_decimal_points:   cstring,
+		mon_thousands_sep:    cstring,
+		mon_grouping:         cstring,
+		positive_sign:        cstring,
+		negative_sign:        cstring,
+		int_frac_digits:      c.char,
+		frac_digits:          c.char,
+		p_cs_precedes:        c.char,
+		p_sep_by_space:       c.char,
+		n_cs_precedes:        c.char,
+		n_sep_by_space:       c.char,
+		p_sign_posn:          c.char,
+		n_sign_posn:          c.char,
+		_W_decimal_point:     [^]u16 `fmt:"s,0"`,
+		_W_thousands_sep:     [^]u16 `fmt:"s,0"`,
+		_W_int_curr_symbol:   [^]u16 `fmt:"s,0"`,
+		_W_currency_symbol:   [^]u16 `fmt:"s,0"`,
+		_W_mon_decimal_point: [^]u16 `fmt:"s,0"`,
+		_W_mon_thousands_sep: [^]u16 `fmt:"s,0"`,
+		_W_positive_sign:     [^]u16 `fmt:"s,0"`,
+		_W_negative_sign:     [^]u16 `fmt:"s,0"`,
+	}
+} else {
+	lconv :: struct {
+		decimal_point:       cstring,
+		thousand_sep:        cstring,
+		grouping:            cstring,
+		int_curr_symbol:     cstring,
+		currency_symbol:     cstring,
+		mon_decimal_points:  cstring,
+		mon_thousands_sep:   cstring,
+		mon_grouping:        cstring,
+		positive_sign:       cstring,
+		negative_sign:       cstring,
+		int_frac_digits:     c.char,
+		frac_digits:         c.char,
+		p_cs_precedes:       c.char,
+		p_sep_by_space:      c.char,
+		n_cs_precedes:       c.char,
+		n_sep_by_space:      c.char,
+		p_sign_posn:         c.char,
+		n_sign_posn:         c.char,
+		_int_p_cs_precedes:  c.char,
+		_int_n_cs_precedes:  c.char,
+		_int_p_sep_by_space: c.char,
+		_int_n_sep_by_space: c.char,
+		_int_p_sign_posn:    c.char,
+		_int_n_sign_posn:    c.char,
+	}
+}
+
+when ODIN_OS == .Darwin || ODIN_OS == .FreeBSD || ODIN_OS == .NetBSD  || ODIN_OS == .OpenBSD || ODIN_OS == .Windows {
+
+	LC_ALL      :: 0
+	LC_COLLATE  :: 1
+	LC_CTYPE    :: 2
+	LC_MESSAGES :: 6
+	LC_MONETARY :: 3
+	LC_NUMERIC  :: 4
+	LC_TIME     :: 5
+
+} else when ODIN_OS == .Linux {
+
+	LC_CTYPE    :: 0
+	LC_NUMERIC  :: 1
+	LC_TIME     :: 2
+	LC_COLLATE  :: 3
+	LC_MONETARY :: 4
+	LC_MESSAGES :: 5
+	LC_ALL      :: 6
+
+}

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

@@ -2,7 +2,7 @@ package libc
 
 
 // 7.12 Mathematics
 // 7.12 Mathematics
 
 
-import "core:intrinsics"
+import "base:intrinsics"
 
 
 when ODIN_OS == .Windows {
 when ODIN_OS == .Windows {
 	foreign import libc "system:libucrt.lib"
 	foreign import libc "system:libucrt.lib"

+ 12 - 7
core/c/libc/setjmp.odin

@@ -32,24 +32,21 @@ when ODIN_OS == .Windows {
 		// the RDX register will contain zero and correctly set the flag to disable
 		// the RDX register will contain zero and correctly set the flag to disable
 		// stack unwinding.
 		// stack unwinding.
 		@(link_name="_setjmp")
 		@(link_name="_setjmp")
-		setjmp  :: proc(env: ^jmp_buf, hack: rawptr = nil) -> int ---
+		setjmp :: proc(env: ^jmp_buf, hack: rawptr = nil) -> int ---
 	}
 	}
 } else {
 } else {
 	@(default_calling_convention="c")
 	@(default_calling_convention="c")
 	foreign libc {
 	foreign libc {
 		// 7.13.1 Save calling environment
 		// 7.13.1 Save calling environment
-		//
-		// NOTE(dweiler): C11 requires setjmp be a macro, which means it won't
-		// necessarily export a symbol named setjmp but rather _setjmp in the case
-		// of musl, glibc, BSD libc, and msvcrt.
-		@(link_name="_setjmp")
-		setjmp  :: proc(env: ^jmp_buf) -> int ---
+		@(link_name=LSETJMP)
+		setjmp :: proc(env: ^jmp_buf) -> int ---
 	}
 	}
 }
 }
 
 
 @(default_calling_convention="c")
 @(default_calling_convention="c")
 foreign libc {
 foreign libc {
 	// 7.13.2 Restore calling environment
 	// 7.13.2 Restore calling environment
+	@(link_name=LLONGJMP)
 	longjmp :: proc(env: ^jmp_buf, val: int) -> ! ---
 	longjmp :: proc(env: ^jmp_buf, val: int) -> ! ---
 }
 }
 
 
@@ -64,3 +61,11 @@ foreign libc {
 // The choice of 4096 bytes for storage of this type is more than enough on all
 // The choice of 4096 bytes for storage of this type is more than enough on all
 // relevant platforms.
 // relevant platforms.
 jmp_buf :: struct #align(16) { _: [4096]char, }
 jmp_buf :: struct #align(16) { _: [4096]char, }
+
+when ODIN_OS == .NetBSD {
+	@(private) LSETJMP  :: "__setjmp14"
+	@(private) LLONGJMP :: "__longjmp14"
+} else {
+	@(private) LSETJMP  :: "setjmp"
+	@(private) LLONGJMP :: "longjmp"
+}

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

@@ -34,20 +34,7 @@ when ODIN_OS == .Windows {
 	SIGTERM :: 15
 	SIGTERM :: 15
 }
 }
 
 
-when ODIN_OS == .Linux || ODIN_OS == .FreeBSD {
-	SIG_ERR  :: rawptr(~uintptr(0))
-	SIG_DFL  :: rawptr(uintptr(0))
-	SIG_IGN  :: rawptr(uintptr(1)) 
-
-	SIGABRT  :: 6
-	SIGFPE   :: 8
-	SIGILL   :: 4
-	SIGINT   :: 2
-	SIGSEGV  :: 11
-	SIGTERM  :: 15
-}
-
-when ODIN_OS == .Darwin {
+when ODIN_OS == .Linux || ODIN_OS == .FreeBSD || ODIN_OS == .Haiku || ODIN_OS == .OpenBSD || ODIN_OS == .NetBSD || ODIN_OS == .Darwin {
 	SIG_ERR  :: rawptr(~uintptr(0))
 	SIG_ERR  :: rawptr(~uintptr(0))
 	SIG_DFL  :: rawptr(uintptr(0))
 	SIG_DFL  :: rawptr(uintptr(0))
 	SIG_IGN  :: rawptr(uintptr(1)) 
 	SIG_IGN  :: rawptr(uintptr(1)) 

+ 4 - 10
core/c/libc/stdarg.odin

@@ -2,7 +2,9 @@ package libc
 
 
 // 7.16 Variable arguments
 // 7.16 Variable arguments
 
 
-import "core:intrinsics"
+import "base:intrinsics"
+
+import "core:c"
 
 
 @(private="file")
 @(private="file")
 @(default_calling_convention="none")
 @(default_calling_convention="none")
@@ -12,15 +14,7 @@ foreign _ {
 	@(link_name="llvm.va_copy")  _va_copy  :: proc(dst, src: ^i8) ---
 	@(link_name="llvm.va_copy")  _va_copy  :: proc(dst, src: ^i8) ---
 }
 }
 
 
-// Since there are no types in C with an alignment larger than that of
-// max_align_t, which cannot be larger than sizeof(long double) as any other
-// exposed type wouldn't be valid C, the maximum alignment possible in a
-// strictly conformant C implementation is 16 on the platforms we care about.
-// The choice of 4096 bytes for storage of this type is more than enough on all
-// relevant platforms.
-va_list :: struct #align(16) {
-	_: [4096]u8,
-}
+va_list :: c.va_list
 
 
 va_start :: #force_inline proc(ap: ^va_list, _: any) {
 va_start :: #force_inline proc(ap: ^va_list, _: any) {
 	_va_start(cast(^i8)ap)
 	_va_start(cast(^i8)ap)

+ 2 - 2
core/c/libc/stdatomic.odin

@@ -2,7 +2,7 @@ package libc
 
 
 // 7.17 Atomics
 // 7.17 Atomics
 
 
-import "core:intrinsics"
+import "base:intrinsics"
 
 
 ATOMIC_BOOL_LOCK_FREE     :: true
 ATOMIC_BOOL_LOCK_FREE     :: true
 ATOMIC_CHAR_LOCK_FREE     :: true
 ATOMIC_CHAR_LOCK_FREE     :: true
@@ -235,7 +235,7 @@ atomic_compare_exchange_weak :: #force_inline proc(object, expected: ^$T, desire
 	return ok
 	return ok
 }
 }
 
 
-atomic_compare_exchange_weak_explicit :: #force_inline proc(object, expected: ^$T, desited: T, success, failure: memory_order) -> bool {
+atomic_compare_exchange_weak_explicit :: #force_inline proc(object, expected: ^$T, desired: T, success, failure: memory_order) -> bool {
 	assert(failure != .release)
 	assert(failure != .release)
 	assert(failure != .acq_rel)
 	assert(failure != .acq_rel)
 
 

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

@@ -1,5 +1,7 @@
 package libc
 package libc
 
 
+import "core:io"
+
 when ODIN_OS == .Windows {
 when ODIN_OS == .Windows {
 	foreign import libc {
 	foreign import libc {
 		"system:libucrt.lib",
 		"system:libucrt.lib",
@@ -15,6 +17,12 @@ when ODIN_OS == .Windows {
 
 
 FILE :: struct {}
 FILE :: struct {}
 
 
+Whence :: enum int {
+	SET = SEEK_SET,
+	CUR = SEEK_CUR,
+	END = SEEK_END,
+}
+
 // MSVCRT compatible.
 // MSVCRT compatible.
 when ODIN_OS == .Windows {
 when ODIN_OS == .Windows {
 	_IOFBF       :: 0x0000
 	_IOFBF       :: 0x0000
@@ -81,7 +89,31 @@ when ODIN_OS == .Linux {
 	}
 	}
 }
 }
 
 
-when ODIN_OS == .OpenBSD {
+when ODIN_OS == .JS {
+	fpos_t        :: struct #raw_union { _: [16]char, _: longlong, _: double, }
+
+	_IOFBF        :: 0
+	_IOLBF        :: 1
+	_IONBF        :: 2
+
+	BUFSIZ        :: 1024
+
+	EOF           :: int(-1)
+
+	FOPEN_MAX     :: 1000
+
+	FILENAME_MAX  :: 4096
+
+	L_tmpnam      :: 20
+
+	SEEK_SET      :: 0
+	SEEK_CUR      :: 1
+	SEEK_END      :: 2
+
+	TMP_MAX       :: 308915776
+}
+
+when ODIN_OS == .OpenBSD || ODIN_OS == .NetBSD {
 	fpos_t :: distinct i64
 	fpos_t :: distinct i64
 
 
 	_IOFBF :: 0
 	_IOFBF :: 0
@@ -99,11 +131,15 @@ when ODIN_OS == .OpenBSD {
 	SEEK_CUR :: 1
 	SEEK_CUR :: 1
 	SEEK_END :: 2
 	SEEK_END :: 2
 
 
+	TMP_MAX :: 308915776
+
 	foreign libc {
 	foreign libc {
-		stderr: ^FILE
-		stdin:  ^FILE
-		stdout: ^FILE
+		__sF: [3]FILE
 	}
 	}
+
+	stdin:  ^FILE = &__sF[0]
+	stdout: ^FILE = &__sF[1]
+	stderr: ^FILE = &__sF[2]
 }
 }
 
 
 when ODIN_OS == .FreeBSD {
 when ODIN_OS == .FreeBSD {
@@ -124,10 +160,12 @@ when ODIN_OS == .FreeBSD {
 	SEEK_CUR :: 1
 	SEEK_CUR :: 1
 	SEEK_END :: 2
 	SEEK_END :: 2
 
 
+	TMP_MAX :: 308915776
+
 	foreign libc {
 	foreign libc {
-		stderr: ^FILE
-		stdin:  ^FILE
-		stdout: ^FILE
+		@(link_name="__stderrp") stderr: ^FILE
+		@(link_name="__stdinp")  stdin:  ^FILE
+		@(link_name="__stdoutp") stdout: ^FILE
 	}
 	}
 }
 }
 
 
@@ -161,10 +199,51 @@ when ODIN_OS == .Darwin {
 	}
 	}
 }
 }
 
 
+when ODIN_OS == .Haiku {
+	fpos_t :: distinct i64
+	
+	_IOFBF        :: 0
+	_IOLBF        :: 1
+	_IONBF        :: 2
+
+	BUFSIZ        :: 8192
+
+	EOF           :: int(-1)
+
+	FOPEN_MAX     :: 128
+
+	FILENAME_MAX  :: 256
+
+	L_tmpnam      :: 512
+
+	SEEK_SET      :: 0
+	SEEK_CUR      :: 1
+	SEEK_END      :: 2
+
+	TMP_MAX       :: 32768
+
+	foreign libc {
+		stderr: ^FILE
+		stdin:  ^FILE
+		stdout: ^FILE
+	}
+}
+
+when ODIN_OS == .NetBSD {
+	@(private) LRENAME  :: "__posix_rename"
+	@(private) LFGETPOS :: "__fgetpos50"
+	@(private) LFSETPOS :: "__fsetpos50"
+} else {
+	@(private) LRENAME  :: "rename"
+	@(private) LFGETPOS :: "fgetpos"
+	@(private) LFSETPOS :: "fsetpos"
+}
+
 @(default_calling_convention="c")
 @(default_calling_convention="c")
 foreign libc {
 foreign libc {
 	// 7.21.4 Operations on files
 	// 7.21.4 Operations on files
 	remove    :: proc(filename: cstring) -> int ---
 	remove    :: proc(filename: cstring) -> int ---
+	@(link_name=LRENAME)
 	rename    :: proc(old, new: cstring) -> int ---
 	rename    :: proc(old, new: cstring) -> int ---
 	tmpfile   :: proc() -> ^FILE ---
 	tmpfile   :: proc() -> ^FILE ---
 	tmpnam    :: proc(s: [^]char) -> [^]char ---
 	tmpnam    :: proc(s: [^]char) -> [^]char ---
@@ -206,8 +285,10 @@ foreign libc {
 	fwrite    :: proc(ptr: rawptr, size: size_t, nmemb: size_t, stream: ^FILE) -> size_t ---
 	fwrite    :: proc(ptr: rawptr, size: size_t, nmemb: size_t, stream: ^FILE) -> size_t ---
 
 
 	// 7.21.9 File positioning functions
 	// 7.21.9 File positioning functions
+	@(link_name=LFGETPOS)
 	fgetpos   :: proc(stream: ^FILE, pos: ^fpos_t) -> int ---
 	fgetpos   :: proc(stream: ^FILE, pos: ^fpos_t) -> int ---
-	fseek     :: proc(stream: ^FILE, offset: long, whence: int) -> int ---
+	fseek     :: proc(stream: ^FILE, offset: long, whence: Whence) -> int ---
+	@(link_name=LFSETPOS)
 	fsetpos   :: proc(stream: ^FILE, pos: ^fpos_t) -> int ---
 	fsetpos   :: proc(stream: ^FILE, pos: ^fpos_t) -> int ---
 	ftell     :: proc(stream: ^FILE) -> long ---
 	ftell     :: proc(stream: ^FILE) -> long ---
 	rewind    :: proc(stream: ^FILE) ---
 	rewind    :: proc(stream: ^FILE) ---
@@ -218,3 +299,106 @@ foreign libc {
 	ferror    :: proc(stream: ^FILE) -> int ---
 	ferror    :: proc(stream: ^FILE) -> int ---
 	perror    :: proc(s: cstring) ---
 	perror    :: proc(s: cstring) ---
 }
 }
+
+to_stream :: proc(file: ^FILE) -> io.Stream {
+	stream_proc :: proc(stream_data: rawptr, mode: io.Stream_Mode, p: []byte, offset: i64, whence: io.Seek_From) -> (n: i64, err: io.Error) {
+		unknown_or_eof :: proc(f: ^FILE) -> io.Error {
+			switch {
+			case ferror(f) != 0:
+				return .Unknown
+			case feof(f) != 0:
+				return .EOF
+			case:
+				return nil
+			}
+		}
+
+		file := (^FILE)(stream_data)
+		switch mode {
+		case .Close:
+			if fclose(file) != 0 {
+				return 0, unknown_or_eof(file)
+			}
+
+		case .Flush:
+			if fflush(file) != 0 {
+				return 0, unknown_or_eof(file)
+			}
+
+		case .Read:
+			n = i64(fread(raw_data(p), size_of(byte), len(p), file))
+			if n == 0 { err = unknown_or_eof(file) }
+
+		case .Read_At:
+			curr := ftell(file)
+			if curr == -1 {
+				return 0, unknown_or_eof(file)
+			}
+
+			if fseek(file, long(offset), .SET) != 0 {
+				return 0, unknown_or_eof(file)
+			}
+
+			defer fseek(file, long(curr), .SET)
+
+			n = i64(fread(raw_data(p), size_of(byte), len(p), file))
+			if n == 0 { err = unknown_or_eof(file) }
+		
+		case .Write:
+			n = i64(fwrite(raw_data(p), size_of(byte), len(p), file))
+			if n == 0 { err = unknown_or_eof(file) }
+
+		case .Write_At:
+			curr := ftell(file)
+			if curr == -1 {
+				return 0, unknown_or_eof(file)
+			}
+
+			if fseek(file, long(offset), .SET) != 0 {
+				return 0, unknown_or_eof(file)
+			}
+
+			defer fseek(file, long(curr), .SET)
+
+			n = i64(fwrite(raw_data(p), size_of(byte), len(p), file))
+			if n == 0 { err = unknown_or_eof(file) }
+
+		case .Seek:
+			#assert(int(Whence.SET) == int(io.Seek_From.Start))
+			#assert(int(Whence.CUR) == int(io.Seek_From.Current))
+			#assert(int(Whence.END) == int(io.Seek_From.End))
+
+			if fseek(file, long(offset), Whence(whence)) != 0 {
+				return 0, unknown_or_eof(file)
+			}
+		
+		case .Size:
+			curr := ftell(file)
+			if curr == -1 {
+				return 0, unknown_or_eof(file)
+			}
+			defer fseek(file, curr, .SET)
+
+			if fseek(file, 0, .END) != 0 {
+				return 0, unknown_or_eof(file)
+			}
+
+			n = i64(ftell(file))
+			if n == -1 {
+				return 0, unknown_or_eof(file)
+			}
+
+		case .Destroy:
+			return 0, .Empty
+		
+		case .Query:
+			return io.query_utility({ .Close, .Flush, .Read, .Read_At, .Write, .Write_At, .Seek, .Size, .Query })
+		}
+		return
+	}
+
+	return {
+		data      = file,
+		procedure = stream_proc,
+	}
+}

+ 27 - 4
core/c/libc/stdlib.odin

@@ -10,6 +10,9 @@ when ODIN_OS == .Windows {
 	foreign import libc "system:c"
 	foreign import libc "system:c"
 }
 }
 
 
+@(require)
+import "base:runtime"
+
 when ODIN_OS == .Windows {
 when ODIN_OS == .Windows {
 	RAND_MAX :: 0x7fff
 	RAND_MAX :: 0x7fff
 
 
@@ -40,10 +43,9 @@ when ODIN_OS == .Linux {
 }
 }
 
 
 
 
-when ODIN_OS == .Darwin {
+when ODIN_OS == .Darwin || ODIN_OS == .FreeBSD || ODIN_OS == .OpenBSD {
 	RAND_MAX :: 0x7fffffff
 	RAND_MAX :: 0x7fffffff
 
 
-	// GLIBC and MUSL only
 	@(private="file")
 	@(private="file")
 	@(default_calling_convention="c")
 	@(default_calling_convention="c")
 	foreign libc {
 	foreign libc {
@@ -55,6 +57,20 @@ when ODIN_OS == .Darwin {
 	}
 	}
 }
 }
 
 
+when ODIN_OS == .NetBSD {
+	RAND_MAX :: 0x7fffffff
+
+	@(private="file")
+	@(default_calling_convention="c")
+	foreign libc {
+		__mb_cur_max: size_t
+	}
+
+	MB_CUR_MAX :: #force_inline proc() -> size_t {
+		return __mb_cur_max
+	}
+}
+
 // C does not declare what these values should be, as an implementation is free
 // C does not declare what these values should be, as an implementation is free
 // to use any two distinct values it wants to indicate success or failure.
 // to use any two distinct values it wants to indicate success or failure.
 // However, nobody actually does and everyone appears to have agreed upon these
 // However, nobody actually does and everyone appears to have agreed upon these
@@ -99,7 +115,7 @@ foreign libc {
 	at_quick_exit :: proc(func: proc "c" ()) -> int ---
 	at_quick_exit :: proc(func: proc "c" ()) -> int ---
 	exit          :: proc(status: int) -> ! ---
 	exit          :: proc(status: int) -> ! ---
 	_Exit         :: proc(status: int) -> ! ---
 	_Exit         :: proc(status: int) -> ! ---
-	getenv        :: proc(name: cstring) -> [^]char ---
+	getenv        :: proc(name: cstring) -> cstring ---
 	quick_exit    :: proc(status: int) -> ! ---
 	quick_exit    :: proc(status: int) -> ! ---
 	system        :: proc(cmd: cstring) -> int ---
 	system        :: proc(cmd: cstring) -> int ---
 
 
@@ -132,6 +148,10 @@ aligned_alloc :: #force_inline proc "c" (alignment, size: size_t) -> rawptr {
 			_aligned_malloc :: proc(size, alignment: size_t) -> rawptr ---
 			_aligned_malloc :: proc(size, alignment: size_t) -> rawptr ---
 		}
 		}
 		return _aligned_malloc(size=size, alignment=alignment)
 		return _aligned_malloc(size=size, alignment=alignment)
+	} else when ODIN_ARCH == .wasm32 || ODIN_ARCH == .wasm64p32 {
+		context = runtime.default_context()
+		data, _ := runtime.mem_alloc_bytes(auto_cast size, auto_cast alignment)
+		return raw_data(data)
 	} else {
 	} else {
 		foreign libc {
 		foreign libc {
 			aligned_alloc :: proc(alignment, size: size_t) -> rawptr ---
 			aligned_alloc :: proc(alignment, size: size_t) -> rawptr ---
@@ -147,7 +167,10 @@ aligned_free :: #force_inline proc "c" (ptr: rawptr) {
 			_aligned_free :: proc(ptr: rawptr) ---
 			_aligned_free :: proc(ptr: rawptr) ---
 		}
 		}
 		_aligned_free(ptr)
 		_aligned_free(ptr)
+	} else when ODIN_ARCH == .wasm32 || ODIN_ARCH == .wasm64p32 {
+		context = runtime.default_context()
+		runtime.mem_free(ptr)
 	} else {
 	} else {
 		free(ptr)
 		free(ptr)
 	}
 	}
-}
+}

+ 3 - 2
core/c/libc/string.odin

@@ -1,6 +1,6 @@
 package libc
 package libc
 
 
-import "core:runtime"
+import "base:runtime"
 
 
 // 7.24 String handling
 // 7.24 String handling
 
 
@@ -12,6 +12,7 @@ when ODIN_OS == .Windows {
 	foreign import libc "system:c"
 	foreign import libc "system:c"
 }
 }
 
 
+@(default_calling_convention="c")
 foreign libc {
 foreign libc {
 	// 7.24.2 Copying functions
 	// 7.24.2 Copying functions
 	memcpy   :: proc(s1, s2: rawptr, n: size_t) -> rawptr ---
 	memcpy   :: proc(s1, s2: rawptr, n: size_t) -> rawptr ---
@@ -40,7 +41,7 @@ foreign libc {
 	strtok   :: proc(s1: [^]char, s2: cstring) -> [^]char ---
 	strtok   :: proc(s1: [^]char, s2: cstring) -> [^]char ---
 
 
 	// 7.24.6 Miscellaneous functions
 	// 7.24.6 Miscellaneous functions
-	strerror :: proc(errnum: int) -> [^]char ---
+	strerror :: proc(errnum: int) -> cstring ---
 	strlen   :: proc(s: cstring) -> size_t ---
 	strlen   :: proc(s: cstring) -> size_t ---
 }
 }
 memset :: proc "c" (s: rawptr, c: int, n: size_t) -> rawptr {
 memset :: proc "c" (s: rawptr, c: int, n: size_t) -> rawptr {

+ 30 - 4
core/c/libc/time.odin

@@ -45,35 +45,61 @@ when ODIN_OS == .Windows {
 	}
 	}
 }
 }
 
 
-when ODIN_OS == .Linux || ODIN_OS == .FreeBSD || ODIN_OS == .Darwin || ODIN_OS == .OpenBSD {
+when ODIN_OS == .Linux || ODIN_OS == .FreeBSD || ODIN_OS == .Darwin || ODIN_OS == .OpenBSD || ODIN_OS == .NetBSD || ODIN_OS == .Haiku || ODIN_OS == .JS  {
 	@(default_calling_convention="c")
 	@(default_calling_convention="c")
 	foreign libc {
 	foreign libc {
 		// 7.27.2 Time manipulation functions
 		// 7.27.2 Time manipulation functions
 		clock        :: proc() -> clock_t ---
 		clock        :: proc() -> clock_t ---
+		@(link_name=LDIFFTIME)
 		difftime     :: proc(time1, time2: time_t) -> double ---
 		difftime     :: proc(time1, time2: time_t) -> double ---
+		@(link_name=LMKTIME)
 		mktime       :: proc(timeptr: ^tm) -> time_t ---
 		mktime       :: proc(timeptr: ^tm) -> time_t ---
+		@(link_name=LTIME)
 		time         :: proc(timer: ^time_t) -> time_t ---
 		time         :: proc(timer: ^time_t) -> time_t ---
 		timespec_get :: proc(ts: ^timespec, base: int) -> int ---
 		timespec_get :: proc(ts: ^timespec, base: int) -> int ---
 
 
 		// 7.27.3 Time conversion functions
 		// 7.27.3 Time conversion functions
 		asctime      :: proc(timeptr: ^tm) -> [^]char ---
 		asctime      :: proc(timeptr: ^tm) -> [^]char ---
+		@(link_name=LCTIME)
 		ctime        :: proc(timer: ^time_t) -> [^]char ---
 		ctime        :: proc(timer: ^time_t) -> [^]char ---
+		@(link_name=LGMTIME)
 		gmtime       :: proc(timer: ^time_t) -> ^tm ---
 		gmtime       :: proc(timer: ^time_t) -> ^tm ---
+		@(link_name=LLOCALTIME)
 		localtime    :: proc(timer: ^time_t) -> ^tm ---
 		localtime    :: proc(timer: ^time_t) -> ^tm ---
 		strftime     :: proc(s: [^]char, maxsize: size_t, format: cstring, timeptr: ^tm) -> size_t ---
 		strftime     :: proc(s: [^]char, maxsize: size_t, format: cstring, timeptr: ^tm) -> size_t ---
 	}
 	}
 
 
+	when ODIN_OS == .NetBSD {
+		@(private) LDIFFTIME  :: "__difftime50"
+		@(private) LMKTIME    :: "__mktime50"
+		@(private) LTIME      :: "__time50"
+		@(private) LCTIME     :: "__ctime50"
+		@(private) LGMTIME    :: "__gmtime50"
+		@(private) LLOCALTIME :: "__localtime50"
+	} else {
+		@(private) LDIFFTIME  :: "difftime"
+		@(private) LMKTIME    :: "mktime"
+		@(private) LTIME      :: "time"
+		@(private) LCTIME     :: "ctime"
+		@(private) LGMTIME    :: "gmtime"
+		@(private) LLOCALTIME :: "localtime"
+	}
+
 	when ODIN_OS == .OpenBSD {
 	when ODIN_OS == .OpenBSD {
 		CLOCKS_PER_SEC :: 100
 		CLOCKS_PER_SEC :: 100
 	} else {
 	} else {
 		CLOCKS_PER_SEC :: 1000000
 		CLOCKS_PER_SEC :: 1000000
 	}
 	}
 
 
-	TIME_UTC       :: 1
+	TIME_UTC :: 1
 
 
-	time_t         :: distinct i64
+	time_t :: distinct i64
 
 
-	clock_t        :: long
+	when ODIN_OS == .FreeBSD || ODIN_OS == .NetBSD {
+		clock_t :: distinct int32_t
+	} else {
+		clock_t :: distinct long
+	}
 
 
 	timespec :: struct {
 	timespec :: struct {
 		tv_sec:  time_t,
 		tv_sec:  time_t,

+ 2 - 0
core/c/libc/types.odin

@@ -2,6 +2,8 @@ package libc
 
 
 import "core:c"
 import "core:c"
 
 
+#assert(!ODIN_NO_CRT, `"core:c/libc" cannot be imported when '-no-crt' is used`)
+
 char           :: c.char // assuming -funsigned-char
 char           :: c.char // assuming -funsigned-char
 
 
 schar          :: c.schar
 schar          :: c.schar

+ 7 - 3
core/c/libc/wctype.odin

@@ -14,7 +14,7 @@ when ODIN_OS == .Windows {
 	wctrans_t :: distinct wchar_t
 	wctrans_t :: distinct wchar_t
 	wctype_t  :: distinct ushort
 	wctype_t  :: distinct ushort
 
 
-} else when ODIN_OS == .Linux {
+} else when ODIN_OS == .Linux || ODIN_OS == .JS {
 	wctrans_t :: distinct intptr_t
 	wctrans_t :: distinct intptr_t
 	wctype_t  :: distinct ulong
 	wctype_t  :: distinct ulong
 
 
@@ -22,14 +22,18 @@ when ODIN_OS == .Windows {
 	wctrans_t :: distinct int
 	wctrans_t :: distinct int
 	wctype_t  :: distinct u32
 	wctype_t  :: distinct u32
 
 
-} else when ODIN_OS == .OpenBSD {
+} else when ODIN_OS == .OpenBSD || ODIN_OS == .NetBSD {
 	wctrans_t :: distinct rawptr
 	wctrans_t :: distinct rawptr
 	wctype_t  :: distinct rawptr
 	wctype_t  :: distinct rawptr
 
 
 } else when ODIN_OS == .FreeBSD {
 } else when ODIN_OS == .FreeBSD {
 	wctrans_t :: distinct int
 	wctrans_t :: distinct int
 	wctype_t  :: distinct ulong
 	wctype_t  :: distinct ulong
-	
+
+} else when ODIN_OS == .Haiku {
+	wctrans_t :: distinct i32
+	wctype_t  :: distinct i32
+
 }
 }
 
 
 @(default_calling_convention="c")
 @(default_calling_convention="c")

+ 56 - 66
core/compress/common.odin

@@ -12,7 +12,7 @@ package compress
 
 
 import "core:io"
 import "core:io"
 import "core:bytes"
 import "core:bytes"
-import "core:runtime"
+import "base:runtime"
 
 
 /*
 /*
 	These settings bound how much compression algorithms will allocate for their output buffer.
 	These settings bound how much compression algorithms will allocate for their output buffer.
@@ -20,10 +20,9 @@ import "core:runtime"
 
 
 */
 */
 
 
-/*
-	When a decompression routine doesn't stream its output, but writes to a buffer,
-	we pre-allocate an output buffer to speed up decompression. The default is 1 MiB.
-*/
+
+// When a decompression routine doesn't stream its output, but writes to a buffer,
+// we pre-allocate an output buffer to speed up decompression. The default is 1 MiB.
 COMPRESS_OUTPUT_ALLOCATE_MIN :: int(#config(COMPRESS_OUTPUT_ALLOCATE_MIN, 1 << 20))
 COMPRESS_OUTPUT_ALLOCATE_MIN :: int(#config(COMPRESS_OUTPUT_ALLOCATE_MIN, 1 << 20))
 
 
 /*
 /*
@@ -34,15 +33,13 @@ COMPRESS_OUTPUT_ALLOCATE_MIN :: int(#config(COMPRESS_OUTPUT_ALLOCATE_MIN, 1 << 2
 
 
 */
 */
 when size_of(uintptr) == 8 {
 when size_of(uintptr) == 8 {
-	/*
-		For 64-bit platforms, we set the default max buffer size to 4 GiB,
-		which is GZIP and PKZIP's max payload size.
-	*/	
+
+	// For 64-bit platforms, we set the default max buffer size to 4 GiB,
+	// which is GZIP and PKZIP's max payload size.
 	COMPRESS_OUTPUT_ALLOCATE_MAX :: int(#config(COMPRESS_OUTPUT_ALLOCATE_MAX, 1 << 32))
 	COMPRESS_OUTPUT_ALLOCATE_MAX :: int(#config(COMPRESS_OUTPUT_ALLOCATE_MAX, 1 << 32))
 } else {
 } else {
-	/*
-		For 32-bit platforms, we set the default max buffer size to 512 MiB.
-	*/
+	
+	// For 32-bit platforms, we set the default max buffer size to 512 MiB.
 	COMPRESS_OUTPUT_ALLOCATE_MAX :: int(#config(COMPRESS_OUTPUT_ALLOCATE_MAX, 1 << 29))
 	COMPRESS_OUTPUT_ALLOCATE_MAX :: int(#config(COMPRESS_OUTPUT_ALLOCATE_MAX, 1 << 29))
 }
 }
 
 
@@ -69,9 +66,8 @@ General_Error :: enum {
 	Incompatible_Options,
 	Incompatible_Options,
 	Unimplemented,
 	Unimplemented,
 
 
-	/*
-		Memory errors
-	*/
+	// Memory errors
+
 	Allocation_Failed,
 	Allocation_Failed,
 	Resize_Failed,
 	Resize_Failed,
 }
 }
@@ -86,17 +82,16 @@ GZIP_Error :: enum {
 	Payload_Length_Invalid,
 	Payload_Length_Invalid,
 	Payload_CRC_Invalid,
 	Payload_CRC_Invalid,
 
 
-	/*
-		GZIP's payload can be a maximum of max(u32le), or 4 GiB.
-		If you tell it you expect it to contain more, that's obviously an error.
-	*/
+	// GZIP's payload can be a maximum of max(u32le), or 4 GiB.
+	// If you tell it you expect it to contain more, that's obviously an error.
+
 	Payload_Size_Exceeds_Max_Payload,
 	Payload_Size_Exceeds_Max_Payload,
-	/*
-		For buffered instead of streamed output, the payload size can't exceed
-		the max set by the `COMPRESS_OUTPUT_ALLOCATE_MAX` switch in compress/common.odin.
 
 
-		You can tweak this setting using `-define:COMPRESS_OUTPUT_ALLOCATE_MAX=size_in_bytes`
-	*/
+	// For buffered instead of streamed output, the payload size can't exceed
+	// the max set by the `COMPRESS_OUTPUT_ALLOCATE_MAX` switch in compress/common.odin.
+	//
+	// You can tweak this setting using `-define:COMPRESS_OUTPUT_ALLOCATE_MAX=size_in_bytes`
+
 	Output_Exceeds_COMPRESS_OUTPUT_ALLOCATE_MAX,
 	Output_Exceeds_COMPRESS_OUTPUT_ALLOCATE_MAX,
 
 
 }
 }
@@ -137,9 +132,8 @@ Context_Memory_Input :: struct #packed {
 	code_buffer:       u64,
 	code_buffer:       u64,
 	num_bits:          u64,
 	num_bits:          u64,
 
 
-	/*
-		If we know the data size, we can optimize the reads and writes.
-	*/
+	// If we know the data size, we can optimize the reads and writes.
+
 	size_packed:       i64,
 	size_packed:       i64,
 	size_unpacked:     i64,
 	size_unpacked:     i64,
 }
 }
@@ -159,18 +153,16 @@ Context_Stream_Input :: struct #packed {
 	code_buffer:       u64,
 	code_buffer:       u64,
 	num_bits:          u64,
 	num_bits:          u64,
 
 
-	/*
-		If we know the data size, we can optimize the reads and writes.
-	*/
+	// If we know the data size, we can optimize the reads and writes.
+
 	size_packed:       i64,
 	size_packed:       i64,
 	size_unpacked:     i64,
 	size_unpacked:     i64,
 
 
-	/*
-		Flags:
-			`input_fully_in_memory`
-				true  = This tells us we read input from `input_data` exclusively. [] = EOF.
-				false = Try to refill `input_data` from the `input` stream.
-	*/
+	// Flags:
+	// `input_fully_in_memory`
+	//   true  = This tells us we read input from `input_data` exclusively. [] = EOF.
+	//   false = Try to refill `input_data` from the `input` stream.
+
 	input_fully_in_memory: b8,
 	input_fully_in_memory: b8,
 
 
 	padding: [1]u8,
 	padding: [1]u8,
@@ -194,7 +186,7 @@ input_size_from_stream :: proc(z: ^Context_Stream_Input) -> (res: i64, err: Erro
 
 
 input_size :: proc{input_size_from_memory, input_size_from_stream}
 input_size :: proc{input_size_from_memory, input_size_from_stream}
 
 
-@(optimization_mode="speed")
+@(optimization_mode="favor_size")
 read_slice_from_memory :: #force_inline proc(z: ^Context_Memory_Input, size: int) -> (res: []u8, err: io.Error) {
 read_slice_from_memory :: #force_inline proc(z: ^Context_Memory_Input, size: int) -> (res: []u8, err: io.Error) {
 	#no_bounds_check {
 	#no_bounds_check {
 		if len(z.input_data) >= size {
 		if len(z.input_data) >= size {
@@ -211,10 +203,10 @@ read_slice_from_memory :: #force_inline proc(z: ^Context_Memory_Input, size: int
 	}
 	}
 }
 }
 
 
-@(optimization_mode="speed")
+@(optimization_mode="favor_size")
 read_slice_from_stream :: #force_inline proc(z: ^Context_Stream_Input, size: int) -> (res: []u8, err: io.Error) {
 read_slice_from_stream :: #force_inline proc(z: ^Context_Stream_Input, size: int) -> (res: []u8, err: io.Error) {
 	// TODO: REMOVE ALL USE OF context.temp_allocator here
 	// TODO: REMOVE ALL USE OF context.temp_allocator here
-	// the is literally no need for it
+	// there is literally no need for it
 	b := make([]u8, size, context.temp_allocator)
 	b := make([]u8, size, context.temp_allocator)
 	_ = io.read(z.input, b[:]) or_return
 	_ = io.read(z.input, b[:]) or_return
 	return b, nil
 	return b, nil
@@ -222,13 +214,13 @@ read_slice_from_stream :: #force_inline proc(z: ^Context_Stream_Input, size: int
 
 
 read_slice :: proc{read_slice_from_memory, read_slice_from_stream}
 read_slice :: proc{read_slice_from_memory, read_slice_from_stream}
 
 
-@(optimization_mode="speed")
+@(optimization_mode="favor_size")
 read_data :: #force_inline proc(z: ^$C, $T: typeid) -> (res: T, err: io.Error) {
 read_data :: #force_inline proc(z: ^$C, $T: typeid) -> (res: T, err: io.Error) {
 	b := read_slice(z, size_of(T)) or_return
 	b := read_slice(z, size_of(T)) or_return
 	return (^T)(&b[0])^, nil
 	return (^T)(&b[0])^, nil
 }
 }
 
 
-@(optimization_mode="speed")
+@(optimization_mode="favor_size")
 read_u8_from_memory :: #force_inline proc(z: ^Context_Memory_Input) -> (res: u8, err: io.Error) {
 read_u8_from_memory :: #force_inline proc(z: ^Context_Memory_Input) -> (res: u8, err: io.Error) {
 	#no_bounds_check {
 	#no_bounds_check {
 		if len(z.input_data) >= 1 {
 		if len(z.input_data) >= 1 {
@@ -240,7 +232,7 @@ read_u8_from_memory :: #force_inline proc(z: ^Context_Memory_Input) -> (res: u8,
 	return 0, .EOF
 	return 0, .EOF
 }
 }
 
 
-@(optimization_mode="speed")
+@(optimization_mode="favor_size")
 read_u8_from_stream :: #force_inline proc(z: ^Context_Stream_Input) -> (res: u8, err: io.Error) {
 read_u8_from_stream :: #force_inline proc(z: ^Context_Stream_Input) -> (res: u8, err: io.Error) {
 	b := read_slice_from_stream(z, 1) or_return
 	b := read_slice_from_stream(z, 1) or_return
 	return b[0], nil
 	return b[0], nil
@@ -248,11 +240,9 @@ read_u8_from_stream :: #force_inline proc(z: ^Context_Stream_Input) -> (res: u8,
 
 
 read_u8 :: proc{read_u8_from_memory, read_u8_from_stream}
 read_u8 :: proc{read_u8_from_memory, read_u8_from_stream}
 
 
-/*
-	You would typically only use this at the end of Inflate, to drain bits from the code buffer
-	preferentially.
-*/
-@(optimization_mode="speed")
+// You would typically only use this at the end of Inflate, to drain bits from the code buffer
+// preferentially.
+@(optimization_mode="favor_size")
 read_u8_prefer_code_buffer_lsb :: #force_inline proc(z: ^$C) -> (res: u8, err: io.Error) {
 read_u8_prefer_code_buffer_lsb :: #force_inline proc(z: ^$C) -> (res: u8, err: io.Error) {
 	if z.num_bits >= 8 {
 	if z.num_bits >= 8 {
 		res = u8(read_bits_no_refill_lsb(z, 8))
 		res = u8(read_bits_no_refill_lsb(z, 8))
@@ -267,7 +257,7 @@ read_u8_prefer_code_buffer_lsb :: #force_inline proc(z: ^$C) -> (res: u8, err: i
 	return
 	return
 }
 }
 
 
-@(optimization_mode="speed")
+@(optimization_mode="favor_size")
 peek_data_from_memory :: #force_inline proc(z: ^Context_Memory_Input, $T: typeid) -> (res: T, err: io.Error) {
 peek_data_from_memory :: #force_inline proc(z: ^Context_Memory_Input, $T: typeid) -> (res: T, err: io.Error) {
 	size :: size_of(T)
 	size :: size_of(T)
 
 
@@ -285,7 +275,7 @@ peek_data_from_memory :: #force_inline proc(z: ^Context_Memory_Input, $T: typeid
 	}
 	}
 }
 }
 
 
-@(optimization_mode="speed")
+@(optimization_mode="favor_size")
 peek_data_at_offset_from_memory :: #force_inline proc(z: ^Context_Memory_Input, $T: typeid, #any_int offset: int) -> (res: T, err: io.Error) {
 peek_data_at_offset_from_memory :: #force_inline proc(z: ^Context_Memory_Input, $T: typeid, #any_int offset: int) -> (res: T, err: io.Error) {
 	size :: size_of(T)
 	size :: size_of(T)
 
 
@@ -303,7 +293,7 @@ peek_data_at_offset_from_memory :: #force_inline proc(z: ^Context_Memory_Input,
 	}
 	}
 }
 }
 
 
-@(optimization_mode="speed")
+@(optimization_mode="favor_size")
 peek_data_from_stream :: #force_inline proc(z: ^Context_Stream_Input, $T: typeid) -> (res: T, err: io.Error) {
 peek_data_from_stream :: #force_inline proc(z: ^Context_Stream_Input, $T: typeid) -> (res: T, err: io.Error) {
 	size :: size_of(T)
 	size :: size_of(T)
 
 
@@ -327,7 +317,7 @@ peek_data_from_stream :: #force_inline proc(z: ^Context_Stream_Input, $T: typeid
 	return res, .None
 	return res, .None
 }
 }
 
 
-@(optimization_mode="speed")
+@(optimization_mode="favor_size")
 peek_data_at_offset_from_stream :: #force_inline proc(z: ^Context_Stream_Input, $T: typeid, #any_int offset: int) -> (res: T, err: io.Error) {
 peek_data_at_offset_from_stream :: #force_inline proc(z: ^Context_Stream_Input, $T: typeid, #any_int offset: int) -> (res: T, err: io.Error) {
 	size :: size_of(T)
 	size :: size_of(T)
 
 
@@ -362,14 +352,14 @@ peek_data :: proc{peek_data_from_memory, peek_data_from_stream, peek_data_at_off
 
 
 
 
 // Sliding window read back
 // Sliding window read back
-@(optimization_mode="speed")
+@(optimization_mode="favor_size")
 peek_back_byte :: #force_inline proc(z: ^$C, offset: i64) -> (res: u8, err: io.Error) {
 peek_back_byte :: #force_inline proc(z: ^$C, offset: i64) -> (res: u8, err: io.Error) {
 	// Look back into the sliding window.
 	// Look back into the sliding window.
 	return z.output.buf[z.bytes_written - offset], .None
 	return z.output.buf[z.bytes_written - offset], .None
 }
 }
 
 
 // Generalized bit reader LSB
 // Generalized bit reader LSB
-@(optimization_mode="speed")
+@(optimization_mode="favor_size")
 refill_lsb_from_memory :: #force_inline proc(z: ^Context_Memory_Input, width := i8(48)) {
 refill_lsb_from_memory :: #force_inline proc(z: ^Context_Memory_Input, width := i8(48)) {
 	refill := u64(width)
 	refill := u64(width)
 	b      := u64(0)
 	b      := u64(0)
@@ -395,7 +385,7 @@ refill_lsb_from_memory :: #force_inline proc(z: ^Context_Memory_Input, width :=
 }
 }
 
 
 // Generalized bit reader LSB
 // Generalized bit reader LSB
-@(optimization_mode="speed")
+@(optimization_mode="favor_size")
 refill_lsb_from_stream :: proc(z: ^Context_Stream_Input, width := i8(24)) {
 refill_lsb_from_stream :: proc(z: ^Context_Stream_Input, width := i8(24)) {
 	refill := u64(width)
 	refill := u64(width)
 
 
@@ -424,13 +414,13 @@ refill_lsb_from_stream :: proc(z: ^Context_Stream_Input, width := i8(24)) {
 refill_lsb :: proc{refill_lsb_from_memory, refill_lsb_from_stream}
 refill_lsb :: proc{refill_lsb_from_memory, refill_lsb_from_stream}
 
 
 
 
-@(optimization_mode="speed")
+@(optimization_mode="favor_size")
 consume_bits_lsb_from_memory :: #force_inline proc(z: ^Context_Memory_Input, width: u8) {
 consume_bits_lsb_from_memory :: #force_inline proc(z: ^Context_Memory_Input, width: u8) {
 	z.code_buffer >>= width
 	z.code_buffer >>= width
 	z.num_bits -= u64(width)
 	z.num_bits -= u64(width)
 }
 }
 
 
-@(optimization_mode="speed")
+@(optimization_mode="favor_size")
 consume_bits_lsb_from_stream :: #force_inline proc(z: ^Context_Stream_Input, width: u8) {
 consume_bits_lsb_from_stream :: #force_inline proc(z: ^Context_Stream_Input, width: u8) {
 	z.code_buffer >>= width
 	z.code_buffer >>= width
 	z.num_bits -= u64(width)
 	z.num_bits -= u64(width)
@@ -438,7 +428,7 @@ consume_bits_lsb_from_stream :: #force_inline proc(z: ^Context_Stream_Input, wid
 
 
 consume_bits_lsb :: proc{consume_bits_lsb_from_memory, consume_bits_lsb_from_stream}
 consume_bits_lsb :: proc{consume_bits_lsb_from_memory, consume_bits_lsb_from_stream}
 
 
-@(optimization_mode="speed")
+@(optimization_mode="favor_size")
 peek_bits_lsb_from_memory :: #force_inline proc(z: ^Context_Memory_Input, width: u8) -> u32 {
 peek_bits_lsb_from_memory :: #force_inline proc(z: ^Context_Memory_Input, width: u8) -> u32 {
 	if z.num_bits < u64(width) {
 	if z.num_bits < u64(width) {
 		refill_lsb(z)
 		refill_lsb(z)
@@ -446,7 +436,7 @@ peek_bits_lsb_from_memory :: #force_inline proc(z: ^Context_Memory_Input, width:
 	return u32(z.code_buffer &~ (~u64(0) << width))
 	return u32(z.code_buffer &~ (~u64(0) << width))
 }
 }
 
 
-@(optimization_mode="speed")
+@(optimization_mode="favor_size")
 peek_bits_lsb_from_stream :: #force_inline proc(z: ^Context_Stream_Input, width: u8) -> u32 {
 peek_bits_lsb_from_stream :: #force_inline proc(z: ^Context_Stream_Input, width: u8) -> u32 {
 	if z.num_bits < u64(width) {
 	if z.num_bits < u64(width) {
 		refill_lsb(z)
 		refill_lsb(z)
@@ -456,13 +446,13 @@ peek_bits_lsb_from_stream :: #force_inline proc(z: ^Context_Stream_Input, width:
 
 
 peek_bits_lsb :: proc{peek_bits_lsb_from_memory, peek_bits_lsb_from_stream}
 peek_bits_lsb :: proc{peek_bits_lsb_from_memory, peek_bits_lsb_from_stream}
 
 
-@(optimization_mode="speed")
+@(optimization_mode="favor_size")
 peek_bits_no_refill_lsb_from_memory :: #force_inline proc(z: ^Context_Memory_Input, width: u8) -> u32 {
 peek_bits_no_refill_lsb_from_memory :: #force_inline proc(z: ^Context_Memory_Input, width: u8) -> u32 {
 	assert(z.num_bits >= u64(width))
 	assert(z.num_bits >= u64(width))
 	return u32(z.code_buffer &~ (~u64(0) << width))
 	return u32(z.code_buffer &~ (~u64(0) << width))
 }
 }
 
 
-@(optimization_mode="speed")
+@(optimization_mode="favor_size")
 peek_bits_no_refill_lsb_from_stream :: #force_inline proc(z: ^Context_Stream_Input, width: u8) -> u32 {
 peek_bits_no_refill_lsb_from_stream :: #force_inline proc(z: ^Context_Stream_Input, width: u8) -> u32 {
 	assert(z.num_bits >= u64(width))
 	assert(z.num_bits >= u64(width))
 	return u32(z.code_buffer &~ (~u64(0) << width))
 	return u32(z.code_buffer &~ (~u64(0) << width))
@@ -470,14 +460,14 @@ peek_bits_no_refill_lsb_from_stream :: #force_inline proc(z: ^Context_Stream_Inp
 
 
 peek_bits_no_refill_lsb :: proc{peek_bits_no_refill_lsb_from_memory, peek_bits_no_refill_lsb_from_stream}
 peek_bits_no_refill_lsb :: proc{peek_bits_no_refill_lsb_from_memory, peek_bits_no_refill_lsb_from_stream}
 
 
-@(optimization_mode="speed")
+@(optimization_mode="favor_size")
 read_bits_lsb_from_memory :: #force_inline proc(z: ^Context_Memory_Input, width: u8) -> u32 {
 read_bits_lsb_from_memory :: #force_inline proc(z: ^Context_Memory_Input, width: u8) -> u32 {
 	k := #force_inline peek_bits_lsb(z, width)
 	k := #force_inline peek_bits_lsb(z, width)
 	#force_inline consume_bits_lsb(z, width)
 	#force_inline consume_bits_lsb(z, width)
 	return k
 	return k
 }
 }
 
 
-@(optimization_mode="speed")
+@(optimization_mode="favor_size")
 read_bits_lsb_from_stream :: #force_inline proc(z: ^Context_Stream_Input, width: u8) -> u32 {
 read_bits_lsb_from_stream :: #force_inline proc(z: ^Context_Stream_Input, width: u8) -> u32 {
 	k := peek_bits_lsb(z, width)
 	k := peek_bits_lsb(z, width)
 	consume_bits_lsb(z, width)
 	consume_bits_lsb(z, width)
@@ -486,14 +476,14 @@ read_bits_lsb_from_stream :: #force_inline proc(z: ^Context_Stream_Input, width:
 
 
 read_bits_lsb :: proc{read_bits_lsb_from_memory, read_bits_lsb_from_stream}
 read_bits_lsb :: proc{read_bits_lsb_from_memory, read_bits_lsb_from_stream}
 
 
-@(optimization_mode="speed")
+@(optimization_mode="favor_size")
 read_bits_no_refill_lsb_from_memory :: #force_inline proc(z: ^Context_Memory_Input, width: u8) -> u32 {
 read_bits_no_refill_lsb_from_memory :: #force_inline proc(z: ^Context_Memory_Input, width: u8) -> u32 {
 	k := #force_inline peek_bits_no_refill_lsb(z, width)
 	k := #force_inline peek_bits_no_refill_lsb(z, width)
 	#force_inline consume_bits_lsb(z, width)
 	#force_inline consume_bits_lsb(z, width)
 	return k
 	return k
 }
 }
 
 
-@(optimization_mode="speed")
+@(optimization_mode="favor_size")
 read_bits_no_refill_lsb_from_stream :: #force_inline proc(z: ^Context_Stream_Input, width: u8) -> u32 {
 read_bits_no_refill_lsb_from_stream :: #force_inline proc(z: ^Context_Stream_Input, width: u8) -> u32 {
 	k := peek_bits_no_refill_lsb(z, width)
 	k := peek_bits_no_refill_lsb(z, width)
 	consume_bits_lsb(z, width)
 	consume_bits_lsb(z, width)
@@ -503,14 +493,14 @@ read_bits_no_refill_lsb_from_stream :: #force_inline proc(z: ^Context_Stream_Inp
 read_bits_no_refill_lsb :: proc{read_bits_no_refill_lsb_from_memory, read_bits_no_refill_lsb_from_stream}
 read_bits_no_refill_lsb :: proc{read_bits_no_refill_lsb_from_memory, read_bits_no_refill_lsb_from_stream}
 
 
 
 
-@(optimization_mode="speed")
+@(optimization_mode="favor_size")
 discard_to_next_byte_lsb_from_memory :: proc(z: ^Context_Memory_Input) {
 discard_to_next_byte_lsb_from_memory :: proc(z: ^Context_Memory_Input) {
 	discard := u8(z.num_bits & 7)
 	discard := u8(z.num_bits & 7)
 	#force_inline consume_bits_lsb(z, discard)
 	#force_inline consume_bits_lsb(z, discard)
 }
 }
 
 
 
 
-@(optimization_mode="speed")
+@(optimization_mode="favor_size")
 discard_to_next_byte_lsb_from_stream :: proc(z: ^Context_Stream_Input) {
 discard_to_next_byte_lsb_from_stream :: proc(z: ^Context_Stream_Input) {
 	discard := u8(z.num_bits & 7)
 	discard := u8(z.num_bits & 7)
 	consume_bits_lsb(z, discard)
 	consume_bits_lsb(z, discard)

Неке датотеке нису приказане због велике количине промена