Bläddra i källkod

Merge branch 'master' into d3d11-annotations

gingerBill 8 månader sedan
förälder
incheckning
6e49bbb668
100 ändrade filer med 4493 tillägg och 3748 borttagningar
  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
+* 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]
 
 jobs:
-  build_linux:
+  build_netbsd:
+    name: NetBSD Build, Check, and Test
     runs-on: ubuntu-latest
+    env:
+      PKGSRC_BRANCH: 2024Q3
     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
       - name: Odin version
         run: ./odin version
-        timeout-minutes: 1
       - name: Odin report
         run: ./odin report
-        timeout-minutes: 1
+      - name: Compile needed Vendor
+        run: |
+          make -C vendor/stb/src
+          make -C vendor/cgltf/src
+          make -C vendor/miniaudio/src
       - 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: 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
+        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: |
-          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
-        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
-        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
-        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: |
-          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:
+    name: Windows Build, Check, and Test
     runs-on: windows-2022
+    timeout-minutes: 15
     steps:
-      - uses: actions/checkout@v1
+      - uses: actions/checkout@v4
       - name: build Odin
         shell: cmd
         run: |
@@ -109,72 +165,118 @@ jobs:
           ./build.bat 1
       - name: Odin version
         run: ./odin version
-        timeout-minutes: 1
       - name: Odin report
         run: ./odin report
-        timeout-minutes: 1
       - name: Odin check
         shell: cmd
         run: |
           call "C:\Program Files\Microsoft Visual Studio\2022\Enterprise\VC\Auxiliary\Build\vcvars64.bat
           odin check examples/demo -vet
-        timeout-minutes: 10
       - name: Odin run
         shell: cmd
         run: |
           call "C:\Program Files\Microsoft Visual Studio\2022\Enterprise\VC\Auxiliary\Build\vcvars64.bat
           odin run examples/demo
-        timeout-minutes: 10
       - name: Odin run -debug
         shell: cmd
         run: |
           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
         shell: cmd
         run: |
           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
         shell: cmd
         run: |
           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
         shell: cmd
         run: |
           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
         shell: cmd
         run: |
           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
         shell: cmd
         run: |
           call "C:\Program Files\Microsoft Visual Studio\2022\Enterprise\VC\Auxiliary\Build\vcvars64.bat
           cd tests\documentation
           call build.bat
-        timeout-minutes: 10
       - name: core:math/big tests
         shell: cmd
         run: |
           call "C:\Program Files\Microsoft Visual Studio\2022\Enterprise\VC\Auxiliary\Build\vcvars64.bat
           cd tests\core\math\big
           call build.bat
-        timeout-minutes: 10
       - name: Odin check examples/all for Windows 32bits
         shell: cmd
         run: |
           call "C:\Program Files\Microsoft Visual Studio\2022\Enterprise\VC\Auxiliary\Build\vcvars64.bat
           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:
   build_windows:
+    name: Windows Build
     if: github.repository == 'odin-lang/Odin'
     runs-on: windows-2022
     steps:
-      - uses: actions/checkout@v1
+      - uses: actions/checkout@v4
       - name: build Odin
         shell: cmd
         run: |
@@ -29,102 +30,181 @@ jobs:
           cp LICENSE dist
           cp LLVM-C.dll dist
           cp -r shared dist
+          cp -r base dist
           cp -r core dist
           cp -r vendor dist
           cp -r bin dist
           cp -r examples dist
       - name: Upload artifact
-        uses: actions/upload-artifact@v1
+        uses: actions/upload-artifact@v4
         with:
+          include-hidden-files: true
           name: windows_artifacts
           path: dist
-  build_ubuntu:
+  build_linux:
+    name: Linux Build
     if: github.repository == 'odin-lang/Odin'
     runs-on: ubuntu-latest
     steps:
-      - uses: actions/checkout@v1
+      - uses: actions/checkout@v4
+      - uses: jirutka/setup-alpine@v1
+        with:
+          branch: v3.20
       - 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
-        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
         run: ./odin run examples/demo
       - name: Copy artifacts
         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
-        uses: actions/upload-artifact@v1
+        uses: actions/upload-artifact@v4
         with:
-          name: ubuntu_artifacts
-          path: dist
+          name: linux_artifacts
+          path: dist.tar.gz
   build_macos:
+    name: MacOS Build
     if: github.repository == 'odin-lang/Odin'
-    runs-on: macOS-latest
+    runs-on: macos-13
     steps:
-      - uses: actions/checkout@v1
+      - uses: actions/checkout@v4
       - name: Download LLVM and setup PATH
         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
-        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
-        run: ./odin run examples/demo
-      - name: Copy artifacts
         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
-        uses: actions/upload-artifact@v1
+        uses: actions/upload-artifact@v4
         with:
           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:
     runs-on: [ubuntu-latest]
-    needs: [build_windows, build_macos, build_ubuntu]
+    needs: [build_windows, build_macos, build_macos_arm, build_linux]
     steps:
-      - uses: actions/checkout@v1
-      - uses: actions/setup-python@v2
+      - uses: actions/checkout@v4
+      - uses: actions/setup-python@v5
         with:
           python-version: '3.8.x'
 
-      - name: Install B2 CLI
+      - name: Install B2 SDK
         shell: bash
         run: |
           python -m pip install --upgrade pip
-          pip install --upgrade b2
+          pip install --upgrade b2sdk
 
       - name: Display Python version
         run: python -c "import sys; print(sys.version)"
 
       - name: Download Windows artifacts
-        uses: actions/download-artifact@v1
+
+        uses: actions/[email protected]
         with:
           name: windows_artifacts
+          path: windows_artifacts
 
       - name: Download Ubuntu artifacts
-        uses: actions/download-artifact@v1
+        uses: actions/download-artifact@v4.1.7
         with:
-          name: ubuntu_artifacts
+          name: linux_artifacts
+          path: linux_artifacts
 
       - name: Download macOS artifacts
-        uses: actions/download-artifact@v1
+        uses: actions/download-artifact@v4.1.7
         with:
           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
         shell: bash
@@ -134,23 +214,10 @@ jobs:
           BUCKET: ${{ secrets.B2_BUCKET }}
           DAYS_TO_KEEP: ${{ secrets.B2_DAYS_TO_KEEP }}
         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/
 x64/
 x86/
+!/core/simd/x86
 bld/
 [Bb]in/
 [Oo]bj/
 [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
 .vs/
 # Visual Studio Code options directory
@@ -269,11 +266,14 @@ bin/
 *.exe
 *.obj
 *.pdb
+*.res
+desktop.ini
+Thumbs.db
 
 # - Linux/MacOS
 odin
 !odin/
-odin.dSYM
+**/*.dSYM
 *.bin
 demo.bin
 libLLVM*.so*
@@ -290,3 +290,8 @@ shared/
 examples/bug/
 build.sh
 !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
 modification, are permitted provided that the following conditions are met:

BIN
LLVM-C.dll


+ 7 - 1
Makefile

@@ -1,4 +1,4 @@
-all: debug
+all: default
 
 demo:
 	./odin run examples/demo/demo.odin -file
@@ -6,12 +6,18 @@ demo:
 report:
 	./odin report
 
+default:
+	PROGRAM=make ./build_odin.sh # debug
+
 debug:
 	./build_odin.sh debug
 
 release:
 	./build_odin.sh release
 
+release-native:
+	./build_odin.sh release-native
+
 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.
 
-#### [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)
 

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

@@ -1,6 +1,8 @@
 // This is purely for documentation
 package builtin
 
+import "base:runtime"
+
 nil   :: nil
 false :: 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 ---
 
 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 ---
 imag       :: proc(value: Complex_Or_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_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
-//+build ignore
+#+build ignore
 package intrinsics
 
+import "base:runtime"
+
 // Package-Related
 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
 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) ---
 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))) ---
 
@@ -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 */) ---
 
 // Compiler Hints
-expect :: proc(val, expected_val: T) -> T ---
+expect :: proc(val, expected_val: $T) -> T ---
 
 // Linux and Darwin Only
 syscall :: proc(id: uintptr, args: ..uintptr) -> uintptr ---
+// FreeBSD, NetBSD, et cetera
+syscall_bsd :: proc(id: uintptr, args: ..uintptr) -> (uintptr, bool) ---
 
 
 // Atomics
@@ -161,10 +174,23 @@ type_is_matrix           :: 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_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_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_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_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_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_log2 :: proc($v: $T) -> T where type_is_integer(T) ---
+
 // SIMD related
 simd_add  :: 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_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
 // (x << y) if y <= mask else 0
 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_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_or      :: 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_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_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) ---
 
-// 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_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
 // 1 - the loaded value from `ptr` did not match `expected`, the thread did not block
 // 2 - the thread blocked, but the timeout
+@(require_target_feature="atomics")
 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) ---
 
 // 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
 // implemented within the compiler rather than in this "preload" file
 //
+#+no-instrumentation
 package runtime
 
-import "core:intrinsics"
+import "base:intrinsics"
 
 // NOTE(bill): This must match the compiler's
 Calling_Convention :: enum u8 {
@@ -65,7 +66,7 @@ Type_Info_Named :: struct {
 	name: string,
 	base: ^Type_Info,
 	pkg:  string,
-	loc:  Source_Code_Location,
+	loc:  ^Source_Code_Location,
 }
 Type_Info_Integer    :: struct {signed: bool, endianness: Platform_Endianness}
 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_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 {
-	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
 	soa_kind:      Type_Info_Struct_Soa_Kind,
+	soa_len:       i32,
 	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 {
 	variants:     []^Type_Info,
@@ -141,9 +151,9 @@ Type_Info_Union :: struct {
 	shared_nil:   bool,
 }
 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 {
 	key:      ^Type_Info,
@@ -161,14 +171,6 @@ Type_Info_Simd_Vector :: struct {
 	elem_size:  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 {
 	elem:         ^Type_Info,
 	elem_size:    int,
@@ -176,10 +178,23 @@ Type_Info_Matrix :: struct {
 	row_count:    int,
 	column_count: int,
 	// 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 {
 	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 {
 	Comparable     = 0,
@@ -218,10 +233,9 @@ Type_Info :: struct {
 		Type_Info_Map,
 		Type_Info_Bit_Set,
 		Type_Info_Simd_Vector,
-		Type_Info_Relative_Pointer,
-		Type_Info_Relative_Multi_Pointer,
 		Type_Info_Matrix,
 		Type_Info_Soa_Pointer,
+		Type_Info_Bit_Field,
 	},
 }
 
@@ -251,25 +265,24 @@ Typeid_Kind :: enum u8 {
 	Map,
 	Bit_Set,
 	Simd_Vector,
-	Relative_Pointer,
-	Relative_Multi_Pointer,
 	Matrix,
 	Soa_Pointer,
+	Bit_Field,
 }
 #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)
 // This will be set by the compiler
-type_table: []Type_Info
+type_table: []^Type_Info
 
 args__: []cstring
 
@@ -284,6 +297,8 @@ when ODIN_OS == .Windows {
 		Thread_Detach  = 3,
 	}
 	dll_forward_reason: DLL_Forward_Reason
+
+	dll_instance: rawptr
 }
 
 // IMPORTANT NOTE(bill): Must be in this order (as the compiler relies upon it)
@@ -295,6 +310,14 @@ Source_Code_Location :: struct {
 	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) -> !
 
 // Allocation Stuff
@@ -306,6 +329,7 @@ Allocator_Mode :: enum byte {
 	Query_Features,
 	Query_Info,
 	Alloc_Non_Zeroed,
+	Resize_Non_Zeroed,
 }
 
 Allocator_Mode_Set :: distinct bit_set[Allocator_Mode]
@@ -373,11 +397,34 @@ Logger :: struct {
 	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 {
 	allocator:              Allocator,
 	temp_allocator:         Allocator,
 	assertion_failure_proc: Assertion_Failure_Proc,
 	logger:                 Logger,
+	random_generator:       Random_Generator,
 
 	user_ptr:   rawptr,
 	user_index: int,
@@ -446,6 +493,15 @@ Raw_Soa_Pointer :: struct {
 	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,
 		FreeBSD,
 		OpenBSD,
+		NetBSD,
+		Haiku,
 		WASI,
 		JS,
+		Orca,
 		Freestanding,
 	}
 */
@@ -475,15 +534,29 @@ Odin_OS_Type :: type_of(ODIN_OS)
 		arm64,
 		wasm32,
 		wasm64p32,
+		riscv64,
 	}
 */
 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
 	Odin_Build_Mode_Type :: enum int {
 		Executable,
 		Dynamic,
+		Static,
 		Object,
 		Assembly,
 		LLVM_IR,
@@ -501,6 +574,22 @@ Odin_Build_Mode_Type :: type_of(ODIN_BUILD_MODE)
 */
 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
@@ -518,12 +607,25 @@ Odin_Platform_Subtarget_Type :: type_of(ODIN_PLATFORM_SUBTARGET)
 		Memory  = 1,
 		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 :: 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 //
@@ -573,8 +675,9 @@ type_info_core :: proc "contextless" (info: ^Type_Info) -> ^Type_Info {
 	base := info
 	loop: for {
 		#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
 		}
 	}
@@ -589,7 +692,7 @@ __type_info_of :: proc "contextless" (id: typeid) -> ^Type_Info #no_bounds_check
 	if n < 0 || n >= len(type_table) {
 		n = 0
 	}
-	return &type_table[n]
+	return type_table[n]
 }
 
 when !ODIN_NO_RTTI {
@@ -658,13 +761,20 @@ __init_context :: proc "contextless" (c: ^Context) {
 
 	c.logger.procedure = default_logger_proc
 	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_contextless_failure_proc(prefix, message, loc)
+}
+
+default_assertion_contextless_failure_proc :: proc "contextless" (prefix, message: string, loc: Source_Code_Location) -> ! {
 	when ODIN_OS == .Freestanding {
 		// Do nothing
 	} else {
-		when !ODIN_DISABLE_ASSERT {
+		when ODIN_OS != .Orca && !ODIN_DISABLE_ASSERT {
 			print_caller_location(loc)
 			print_string(" ")
 		}
@@ -673,7 +783,18 @@ default_assertion_failure_proc :: proc(prefix, message: string, loc: Source_Code
 			print_string(": ")
 			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()
 }

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

@@ -1,11 +1,44 @@
 package runtime
 
-import "core:intrinsics"
+import "base:intrinsics"
 
 @builtin
 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)
 container_of :: #force_inline proc "contextless" (ptr: $P/^$Field_Type, $T: typeid, $field_name: string) -> ^T
 	where intrinsics.type_has_field(T, field_name),
@@ -40,7 +73,7 @@ copy_slice :: proc "contextless" (dst, src: $T/[]$E) -> int {
 	}
 	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
 // 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
 }
-// `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
 // of len(src) and len(dst).
 @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.
 //
 // 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.
 @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))
 	n := len(array)-1
 	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.
 //
 // 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.
 @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))
 	if index+1 < len(array) {
 		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: If the range is out of bounds, this procedure will panic.
 @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))
 	n := max(hi-lo, 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.
 //
-// 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
 pop :: proc(array: ^$T/[dynamic]$E, loc := #caller_location) -> (res: E) #no_bounds_check {
 	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.
 // If the operation is not possible, it will return false.
 @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 {
 		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.
 // If the operation is not possible, it will return false.
 @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 {
 		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`
 @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`).
 @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
-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.
 @builtin
@@ -234,6 +295,8 @@ delete :: proc{
 	delete_dynamic_array,
 	delete_slice,
 	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
 }
 
-DEFAULT_RESERVE_CAPACITY :: 16
+DEFAULT_DYNAMIC_ARRAY_CAPACITY :: 8
 
 @(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 {
@@ -287,7 +350,7 @@ make_slice :: proc($T: typeid/[]$E, #any_int len: int, allocator := context.allo
 // Note: Prefer using the procedure group `make`.
 @(builtin, require_results)
 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.
 // 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`.
 @(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 {
+	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)
-	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
 }
-// `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.
 //
 // Note: Prefer using the procedure group `make`.
 @(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)
 	context.allocator = allocator
 
 	err = reserve_map(&m, capacity, loc)
 	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.
 //
 // 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
 // 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
 make :: proc{
 	make_slice,
@@ -354,7 +436,13 @@ make :: proc{
 	make_dynamic_array_len,
 	make_dynamic_array_len_cap,
 	make_map,
+	make_map_cap,
 	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`
 @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
 }
 
@@ -404,75 +492,112 @@ delete_key :: proc(m: ^$T/map[$K]$V, key: K) -> (deleted_key: K, deleted_value:
 	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
+		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
 	} 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
-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 {
 		return 0, nil
 	}
 
-	arg_len := len(args)
 	if arg_len <= 0 {
 		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
-		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 {
-		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
+_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
 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
-@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
@@ -506,7 +647,7 @@ append_nothing :: proc(array: ^$T/[dynamic]$E, loc := #caller_location) -> (n: i
 
 
 @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 {
 		return
 	}
@@ -524,7 +665,7 @@ inject_at_elem :: proc(array: ^$T/[dynamic]$E, index: int, arg: E, loc := #calle
 }
 
 @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 {
 		return
 	}
@@ -547,7 +688,7 @@ inject_at_elems :: proc(array: ^$T/[dynamic]$E, index: int, args: ..E, loc := #c
 }
 
 @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 {
 		return
 	}
@@ -572,7 +713,7 @@ inject_at_elem_string :: proc(array: ^$T/[dynamic]$E/u8, index: int, arg: string
 
 
 @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) {
 		array[index] = arg
 		ok = true
@@ -586,12 +727,15 @@ assign_at_elem :: proc(array: ^$T/[dynamic]$E, index: int, arg: E, loc := #calle
 
 
 @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)
 		ok = true
 	} else {
-		resize(array, index+1+len(args), loc) or_return
+		resize(array, new_size, loc) or_return
 		copy(array[index:], args)
 		ok = true
 	}
@@ -600,7 +744,7 @@ assign_at_elems :: proc(array: ^$T/[dynamic]$E, index: int, args: ..E, loc := #c
 
 
 @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)
 	if len(arg) == 0 {
 		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`).
 //
 // 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
 	}
-	a := (^Raw_Dynamic_Array)(array)
 
 	if capacity <= a.cap {
 		return nil
@@ -649,11 +791,16 @@ reserve_dynamic_array :: proc(array: ^$T/[dynamic]$E, capacity: int, loc := #cal
 	}
 	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
 
-	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 {
 		return .Out_Of_Memory
 	}
@@ -663,15 +810,26 @@ reserve_dynamic_array :: proc(array: ^$T/[dynamic]$E, capacity: int, loc := #cal
 	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, 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
 	}
-	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 {
 		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)
 
-	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
 
-	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 {
 		return .Out_Of_Memory
 	}
@@ -698,6 +861,19 @@ resize_dynamic_array :: proc(array: ^$T/[dynamic]$E, length: int, loc := #caller
 	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.
 
@@ -709,11 +885,14 @@ resize_dynamic_array :: proc(array: ^$T/[dynamic]$E, length: int, loc := #caller
 
 	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
 	}
-	a := (^Raw_Dynamic_Array)(array)
 
 	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)
 
-	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.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))
 }
 
-
-@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
-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
 @(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 {
 		// NOTE(bill): This is wrapped in a procedure call
 		// 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
 panic :: proc(message: string, loc := #caller_location) -> ! {
 	p := context.assertion_failure_proc
@@ -833,3 +1027,41 @@ unimplemented :: proc(message := "", loc := #caller_location) -> ! {
 	}
 	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
 
-import "core:intrinsics"
+import "base:intrinsics"
 _ :: intrinsics
 
 /*
@@ -55,7 +55,7 @@ raw_soa_footer_slice :: proc(array: ^$T/#soa[]$E) -> (footer: ^Raw_SOA_Footer_Sl
 	if array == 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))
 	return
 }
@@ -64,12 +64,7 @@ raw_soa_footer_dynamic_array :: proc(array: ^$T/#soa[dynamic]$E) -> (footer: ^Ra
 	if array == 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))
 	return
 }
@@ -81,7 +76,7 @@ raw_soa_footer :: proc{
 
 
 @(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 {
 		return
 	}
@@ -98,11 +93,11 @@ make_soa_aligned :: proc($T: typeid/#soa[]$E, length: int, alignment: int, alloc
 	ti = type_info_base(ti)
 	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
 	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 = 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)
 	offset := 0
 	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)
 
@@ -140,20 +135,22 @@ make_soa_aligned :: proc($T: typeid/#soa[]$E, length: int, alignment: int, alloc
 }
 
 @(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)
 }
 
 @(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 {
 	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
 }
 
 @(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 {
 	context.allocator = allocator
+	array.allocator = allocator
 	resize_soa(&array, length, loc) or_return
 	return array, nil
 }
@@ -177,7 +174,7 @@ make_soa :: proc{
 
 
 @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 {
 		return nil
 	}
@@ -188,7 +185,27 @@ resize_soa :: proc(array: ^$T/#soa[dynamic]$E, length: int, loc := #caller_locat
 }
 
 @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 {
 		return nil
 	}
@@ -213,12 +230,7 @@ reserve_soa :: proc(array: ^$T/#soa[dynamic]$E, capacity: int, loc := #caller_lo
 	ti = type_info_base(ti)
 	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)
 
 	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)
 	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
 		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)^
 
 	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,
 	) or_return
 	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
 	new_offset := 0
 	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)
 		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
 }
 
+
+@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
-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 {
 		return 0, nil
 	}
 
 	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)
 
 	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)
 		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)^
 
@@ -307,7 +325,7 @@ append_soa_elem :: proc(array: ^$T/#soa[dynamic]$E, arg: E, loc := #caller_locat
 
 		max_align :: align_of(E)
 		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)
 			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
-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 {
 		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 {
-		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)
 
@@ -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_base(ti)
 		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)^
 
@@ -358,7 +386,7 @@ append_soa_elems :: proc(array: ^$T/#soa[dynamic]$E, args: ..E, loc := #caller_l
 
 		max_align :: align_of(E)
 		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)
 			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 {
-	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
 		ptr := (^rawptr)(&array)^
 		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 {
-	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
 		ptr := (^rawptr)(&array)^
 		footer := raw_soa_footer(&array)
@@ -416,7 +446,8 @@ delete_soa :: proc{
 
 
 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.len = 0
 	}
@@ -425,4 +456,82 @@ clear_soa_dynamic_array :: proc(array: ^$T/#soa[dynamic]$E) {
 @builtin
 clear_soa :: proc{
 	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
 
 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 {
 	case .Alloc, .Alloc_Non_Zeroed:
 		return nil, .Out_Of_Memory
@@ -10,7 +10,7 @@ nil_allocator_proc :: proc(allocator_data: rawptr, mode: Allocator_Mode,
 		return nil, .None
 	case .Free_All:
 		return nil, .Mode_Not_Implemented
-	case .Resize:
+	case .Resize, .Resize_Non_Zeroed:
 		if size == 0 {
 			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,
                              size, alignment: int,
                              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 {
 			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:
 		if old_memory != nil {
 			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 {
 	return Allocator{
-		procedure = nil_allocator_proc,
+		procedure = panic_allocator_proc,
 		data = nil,
 	}
 }
-
-

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

@@ -1,6 +1,6 @@
 package runtime
 
-import "core:intrinsics"
+import "base:intrinsics"
 
 DEFAULT_ARENA_GROWING_MINIMUM_BLOCK_SIZE :: uint(DEFAULT_TEMP_ALLOCATOR_BACKING_SIZE)
 
@@ -12,6 +12,8 @@ Memory_Block :: struct {
 	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 {
 	backing_allocator:  Allocator,
 	curr_block:         ^Memory_Block,
@@ -28,11 +30,11 @@ safe_add :: #force_inline proc "contextless" (x, y: uint) -> (uint, bool) {
 }
 
 @(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
 	block = (^Memory_Block)(raw_data(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 {
 		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 {
 			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 {
 			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
 		arena.curr_block = new_block
 		arena.total_capacity += new_block.capacity
@@ -127,14 +129,14 @@ arena_alloc :: proc(arena: ^Arena, size, alignment: uint, loc := #caller_locatio
 	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
 @(require_results)
 arena_init :: proc(arena: ^Arena, size: uint, backing_allocator: Allocator, loc := #caller_location) -> Allocator_Error {
 	arena^ = {}
 	arena.backing_allocator = backing_allocator
 	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.total_capacity += new_block.capacity
 	return nil
@@ -195,7 +197,7 @@ arena_allocator_proc :: proc(allocator_data: rawptr, mode: Allocator_Mode,
 		err = .Mode_Not_Implemented
 	case .Free_All:
 		arena_free_all(arena, location)
-	case .Resize:
+	case .Resize, .Resize_Non_Zeroed:
 		old_data := ([^]byte)(old_memory)
 
 		switch {

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

@@ -1,7 +1,7 @@
 package runtime
 
 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 {
 	Default_Temp_Allocator :: struct {}

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

@@ -44,7 +44,7 @@ memcpy
 memove
 
 
-## Procedures required by the LLVM backend
+## Procedures required by the LLVM backend if u128/i128 is used
 umodti3
 udivti3
 modti3
@@ -59,11 +59,12 @@ truncdfhf2
 gnu_h2f_ieee
 gnu_f2h_ieee
 extendhfsf2
+
+## Procedures required by the LLVM backend if f16 is used
 __ashlti3 // wasm specific
 __multi3  // wasm specific
 
 
-
 ## Required an entry point is defined (i.e. 'main')
 
 args__
@@ -156,7 +157,7 @@ __dynamic_map_get // 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_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
 
-import "core:intrinsics"
+import "base:intrinsics"
 _ :: intrinsics
 
 // High performance, cache-friendly, open-addressed Robin Hood hashing hash map
@@ -44,7 +44,7 @@ _ :: intrinsics
 MAP_LOAD_FACTOR :: 75
 
 // Minimum log2 capacity.
-MAP_MIN_LOG2_CAPACITY :: 6 // 64 elements
+MAP_MIN_LOG2_CAPACITY :: 3 // 8 elements
 
 // Has to be less than 100% though.
 #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) {
 		// 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
 		// 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.
-		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)
-		// 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 {
 		// 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
@@ -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 {
 	round :: #force_inline proc "contextless" (value: uintptr) -> uintptr {
 		CACHE_MASK :: MAP_CACHE_LINE_SIZE - 1
@@ -350,6 +350,12 @@ map_total_allocation_size :: #force_inline proc "contextless" (capacity: uintptr
 	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.
 @(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) {
@@ -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
 // 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)
 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
@@ -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)
 
 	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]
 
 		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)
-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)
 	ceil_log2 :: #force_inline proc "contextless" (x: uintptr) -> uintptr {
 		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)
-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 {
 		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)
-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))
 	size := int(map_total_allocation_size(uintptr(map_cap(m)), info))
 	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)
-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 {
 		return 0, false
 	}
@@ -711,7 +723,7 @@ map_lookup_dynamic :: proc "contextless" (m: Raw_Map, #no_alias info: ^Map_Info,
 	}
 }
 @(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 {
 		return false
 	}
@@ -737,7 +749,7 @@ map_exists_dynamic :: proc "contextless" (m: Raw_Map, #no_alias info: ^Map_Info,
 
 
 @(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
 	ks, vs, hs, _, _ := map_kvh_data_dynamic(m^, info)
 	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
 __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^) {
@@ -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))
-	m.len += 1
+	if result != 0 {
+		m.len += 1
+	}
 	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
 @(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
 
-import "core:intrinsics"
+import "base:intrinsics"
 
 when ODIN_BUILD_MODE == .Dynamic {
 	@(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
 		when ODIN_ARCH == .amd64 {
 			@require foreign import entry "entry_unix_no_crt_amd64.asm"
+			SYS_exit :: 60
 		} else when ODIN_ARCH == .i386 {
 			@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)
 		_start_odin :: proc "c" (argc: i32, argv: [^]cstring) -> ! {
@@ -36,11 +45,7 @@ when ODIN_BUILD_MODE == .Dynamic {
 			#force_no_inline _startup_runtime()
 			intrinsics.__entry_point()
 			#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()
 		}
 	} 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
 
-import "core:intrinsics"
+import "base:intrinsics"
 
 when ODIN_BUILD_MODE == .Dynamic {
 	@(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()
 
-		// Populate Windows DLL-specific global
+		// Populate Windows DLL-specific globals
 		dll_forward_reason = DLL_Forward_Reason(fdwReason)
+		dll_instance       = hinstDLL
 
 		switch dll_forward_reason {
 		case .Process_Attach:
@@ -28,7 +30,7 @@ when ODIN_BUILD_MODE == .Dynamic {
 } else when !ODIN_TEST && !ODIN_NO_ENTRY_POINT {
 	when ODIN_ARCH == .i386 || ODIN_NO_CRT {
 		@(link_name="mainCRTStartup", linkage="strong", require)
-		mainCRTStartup :: proc "stdcall" () -> i32 {
+		mainCRTStartup :: proc "system" () -> i32 {
 			context = default_context()
 			#force_no_inline _startup_runtime()
 			intrinsics.__entry_point()

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

@@ -1,27 +1,34 @@
 package runtime
 
+@(no_instrumentation)
 bounds_trap :: proc "contextless" () -> ! {
 	when ODIN_OS == .Windows {
 		windows_trap_array_bounds()
+	} else when ODIN_OS == .Orca {
+		abort_ext("", "", 0, "bounds trap")
 	} else {
 		trap()
 	}
 }
 
+@(no_instrumentation)
 type_assertion_trap :: proc "contextless" () -> ! {
 	when ODIN_OS == .Windows {
 		windows_trap_type_assertion()
+	} else when ODIN_OS == .Orca {
+		abort_ext("", "", 0, "type assertion trap")
 	} else {
 		trap()
 	}
 }
 
 
+@(disabled=ODIN_NO_BOUNDS_CHECK)
 bounds_check_error :: proc "contextless" (file: string, line, column: i32, index, count: int) {
 	if uint(index) < uint(count) {
 		return
 	}
-	@(cold)
+	@(cold, no_instrumentation)
 	handle_error :: proc "contextless" (file: string, line, column: i32, index, count: int) -> ! {
 		print_caller_location(Source_Code_Location{file, line, column, ""})
 		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)
 }
 
+@(no_instrumentation)
 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_string(" Invalid slice indices ")
@@ -46,6 +54,7 @@ slice_handle_error :: proc "contextless" (file: string, line, column: i32, lo, h
 	bounds_trap()
 }
 
+@(no_instrumentation)
 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_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) {
 	if lo <= hi {
 		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)
 }
 
+@(disabled=ODIN_NO_BOUNDS_CHECK)
 slice_expr_error_hi :: proc "contextless" (file: string, line, column: i32, hi: int, len: int) {
 	if 0 <= hi && hi <= len {
 		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)
 }
 
+@(disabled=ODIN_NO_BOUNDS_CHECK)
 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 {
 		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)
 }
 
+@(disabled=ODIN_NO_BOUNDS_CHECK)
 dynamic_array_expr_error :: proc "contextless" (file: string, line, column: i32, low, high, max: int) {
 	if 0 <= low && low <= high && high <= max {
 		return
 	}
-	@(cold)
+	@(cold, no_instrumentation)
 	handle_error :: proc "contextless" (file: string, line, column: i32, low, high, max: int) -> ! {
 		print_caller_location(Source_Code_Location{file, line, column, ""})
 		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) {
 	if uint(row_index) < uint(row_count) &&
 	   uint(column_index) < uint(column_count) {
 		return
 	}
-	@(cold)
+	@(cold, no_instrumentation)
 	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_string(" Matrix indices [")
@@ -127,7 +141,7 @@ when ODIN_NO_RTTI {
 		if ok {
 			return
 		}
-		@(cold)
+		@(cold, no_instrumentation)
 		handle_error :: proc "contextless" (file: string, line, column: i32) -> ! {
 			print_caller_location(Source_Code_Location{file, line, column, ""})
 			print_string(" Invalid type assertion\n")
@@ -140,7 +154,7 @@ when ODIN_NO_RTTI {
 		if ok {
 			return
 		}
-		@(cold)
+		@(cold, no_instrumentation)
 		handle_error :: proc "contextless" (file: string, line, column: i32) -> ! {
 			print_caller_location(Source_Code_Location{file, line, column, ""})
 			print_string(" Invalid type assertion\n")
@@ -153,7 +167,7 @@ when ODIN_NO_RTTI {
 		if ok {
 			return
 		}
-		@(cold)
+		@(cold, no_instrumentation)
 		handle_error :: proc "contextless" (file: string, line, column: i32, from, to: typeid) -> ! {
 			print_caller_location(Source_Code_Location{file, line, column, ""})
 			print_string(" Invalid type assertion from ")
@@ -198,7 +212,7 @@ when ODIN_NO_RTTI {
 			return id
 		}
 
-		@(cold)
+		@(cold, no_instrumentation)
 		handle_error :: proc "contextless" (file: string, line, column: i32, from, to: typeid, from_data: rawptr) -> ! {
 
 			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) {
 	if 0 <= len {
 		return
 	}
-	@(cold)
+	@(cold, no_instrumentation)
 	handle_error :: proc "contextless" (loc: Source_Code_Location, len: int) -> ! {
 		print_caller_location(loc)
 		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)
 }
 
+@(disabled=ODIN_NO_BOUNDS_CHECK)
 make_dynamic_array_error_loc :: #force_inline proc "contextless" (loc := #caller_location, len, cap: int) {
 	if 0 <= len && len <= cap {
 		return
 	}
-	@(cold)
+	@(cold, no_instrumentation)
 	handle_error :: proc "contextless" (loc: Source_Code_Location, len, cap: int)  -> ! {
 		print_caller_location(loc)
 		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)
 }
 
+@(disabled=ODIN_NO_BOUNDS_CHECK)
 make_map_expr_error_loc :: #force_inline proc "contextless" (loc := #caller_location, cap: int) {
 	if 0 <= cap {
 		return
 	}
-	@(cold)
+	@(cold, no_instrumentation)
 	handle_error :: proc "contextless" (loc: Source_Code_Location, cap: int)  -> ! {
 		print_caller_location(loc)
 		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.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.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.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.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
 
-import "core:intrinsics"
+import "base:intrinsics"
 
 @(private="file")
 IS_WASM :: ODIN_ARCH == .wasm32 || ODIN_ARCH == .wasm64p32
 
 @(private)
 RUNTIME_LINKAGE :: "strong" when (
-	(ODIN_USE_SEPARATE_MODULES || 
+	ODIN_USE_SEPARATE_MODULES || 
 	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)
 __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)]
 }
 
-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 {
 		return false
 	}
 	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
 	modulo := p & (align-1)
@@ -84,14 +58,14 @@ align_forward_int :: #force_inline proc(ptr, align: int) -> int {
 	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 {
 		return false
 	}
 	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))
 
 	p := ptr
@@ -102,6 +76,18 @@ align_forward_uintptr :: #force_inline proc(ptr, align: uintptr) -> uintptr {
 	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 {
 	if data == 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)
 
 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 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) {
+	assert(is_power_of_two_int(alignment), "Alignment must be a power of two", loc)
 	if size == 0 || allocator.procedure == 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) {
+	assert(is_power_of_two_int(alignment), "Alignment must be a power of two", loc)
 	if size == 0 || allocator.procedure == nil {
 		return nil, nil
 	}
@@ -187,7 +173,8 @@ mem_free_all :: #force_inline proc(allocator := context.allocator, loc := #calle
 	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 {
 		return nil, nil
 	}
@@ -198,15 +185,27 @@ mem_resize :: proc(ptr: rawptr, old_size, new_size: int, alignment: int = DEFAUL
 		}
 		return
 	} 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 {
 		data = ([^]byte)(ptr)[:old_size]
 		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 {
-		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 {
 			return
 		}
@@ -216,6 +215,15 @@ mem_resize :: proc(ptr: rawptr, old_size, new_size: int, alignment: int = DEFAUL
 	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 {
 	switch {
 	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) {
 	// 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, // 0x10-0x1f
 		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}
 
-	@static accept_ranges := [5]Accept_Range{
+	@(static, rodata) accept_ranges := [5]Accept_Range{
 		{0x80, 0xbf},
 		{0xa0, 0xbf},
 		{0x80, 0x9f},
@@ -589,36 +597,6 @@ string_decode_last_rune :: proc "contextless" (s: string) -> (rune, int) {
 	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 {
 	p, q := abs(real(x)), abs(imag(x))
 	if p < q {
@@ -667,21 +645,24 @@ abs_quaternion256 :: #force_inline proc "contextless" (x: quaternion256) -> f64
 
 
 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 {
-		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 {
-	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
 	t1 := r0*q1 + r1*q0 - r2*q3 + r3*q2
 	t2 := r0*q2 + r1*q3 + r2*q0 - r3*q1
 	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 {
@@ -742,7 +723,7 @@ mul_quaternion128 :: proc "contextless" (q, r: quaternion128) -> quaternion128 {
 	t2 := r0*q2 + r1*q3 + r2*q0 - r3*q1
 	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 {
@@ -754,12 +735,12 @@ mul_quaternion256 :: proc "contextless" (q, r: quaternion256) -> quaternion256 {
 	t2 := r0*q2 + r1*q3 + r2*q0 - r3*q1
 	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 {
-	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)
 
@@ -768,7 +749,7 @@ quo_quaternion64 :: proc "contextless" (q, r: quaternion64) -> quaternion64 {
 	t2 := (r0*q2 - r1*q3 - r2*q0 + r3*q1) * 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 {
@@ -782,7 +763,7 @@ quo_quaternion128 :: proc "contextless" (q, r: quaternion128) -> quaternion128 {
 	t2 := (r0*q2 - r1*q3 - r2*q0 + r3*q1) * 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 {
@@ -796,7 +777,7 @@ quo_quaternion256 :: proc "contextless" (q, r: quaternion256) -> quaternion256 {
 	t2 := (r0*q2 - r1*q3 - r2*q0 + r3*q1) * 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)
@@ -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)
 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)
 floattidf :: proc "c" (a: i128) -> f64 {
-when IS_WASM {
-	return 0
-} else {
 	DBL_MANT_DIG :: 53
 	if a == 0 {
 		return 0.0
@@ -938,14 +920,10 @@ when IS_WASM {
 	fb[0] = u32(a)                           // mantissa-low
 	return transmute(f64)fb
 }
-}
 
 
 @(link_name="__floattidf_unsigned", linkage=RUNTIME_LINKAGE, require=RUNTIME_REQUIRE)
 floattidf_unsigned :: proc "c" (a: u128) -> f64 {
-when IS_WASM {
-	return 0
-} else {
 	DBL_MANT_DIG :: 53
 	if a == 0 {
 		return 0.0
@@ -983,7 +961,6 @@ when IS_WASM {
 	fb[0] = u32(a)                           // mantissa-low
 	return transmute(f64)fb
 }
-}
 
 
 
@@ -1017,9 +994,11 @@ udivmodti4 :: proc "c" (a, b: u128, rem: ^u128) -> u128 {
 	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
 
 	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)
 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)
 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)
-fixdfti :: proc(a: u64) -> i128 {
+fixdfti :: proc "c" (a: u64) -> i128 {
 	significandBits :: 52
 	typeWidth       :: (size_of(u64)*8)
 	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
 
 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 {
 		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
 
 when !ODIN_NO_RTTI {
-	print_any_single :: proc "contextless" (arg: any) {
+	print_any_single :: #force_no_inline proc "contextless" (arg: any) {
 		x := arg
 		if x.data == nil {
 			print_string("nil")
@@ -72,7 +72,7 @@ when !ODIN_NO_RTTI {
 			print_string("<invalid-value>")
 		}
 	}
-	println_any :: proc "contextless" (args: ..any) {
+	println_any :: #force_no_inline proc "contextless" (args: ..any) {
 		context = default_context()
 		loop: for arg, i in args {
 			assert(arg.id != nil)
@@ -122,14 +122,14 @@ encode_rune :: proc "contextless" (c: rune) -> ([4]u8, int) {
 	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
 }
 
-print_strings :: proc "contextless" (args: ..string) -> (n: int) {
+print_strings :: #force_no_inline proc "contextless" (args: ..string) -> (n: int) {
 	for str in args {
-		m, err := os_write(transmute([]byte)str)
+		m, err := stderr_write(transmute([]byte)str)
 		n += m
 		if err != 0 {
 			break
@@ -138,12 +138,12 @@ print_strings :: proc "contextless" (args: ..string) -> (n: int) {
 	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
 }
 
-print_encoded_rune :: proc "contextless" (r: rune) {
+print_encoded_rune :: #force_no_inline proc "contextless" (r: rune) {
 	print_byte('\'')
 
 	switch r {
@@ -170,7 +170,7 @@ print_encoded_rune :: proc "contextless" (r: rune) {
 	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
 
 	if r < RUNE_SELF {
@@ -178,12 +178,12 @@ print_rune :: proc "contextless" (r: rune) -> int #no_bounds_check {
 	}
 
 	b, n := encode_rune(r)
-	m, _ := os_write(b[:n])
+	m, _ := stderr_write(b[:n])
 	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
 	i := len(a)
 	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]
 
-	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)
 
 	u := x
@@ -216,32 +216,36 @@ print_i64 :: proc "contextless" (x: i64) #no_bounds_check {
 		i -= 1; a[i] = '-'
 	}
 
-	os_write(a[i:])
+	stderr_write(a[i:])
 }
 
 print_uint    :: proc "contextless" (x: uint)    { 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_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)
 	when ODIN_ERROR_POS_STYLE == .Default {
 		print_byte('(')
 		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(')')
 	} else when ODIN_ERROR_POS_STYLE == .Unix {
 		print_byte(':')
 		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(':')
 	} else {
 		#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 {
 		if id == 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 {
 		print_string("nil")
 		return
@@ -395,15 +401,16 @@ print_type :: proc "contextless" (ti: ^Type_Info) {
 		}
 
 		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_u64(u64(ti.align))
 			print_string(") ")
 		}
 		print_byte('{')
-		for name, i in info.names {
+		for name, i in info.names[:info.field_count] {
 			if i > 0 { print_string(", ") }
 			print_string(name)
 			print_string(": ")
@@ -459,24 +466,26 @@ print_type :: proc "contextless" (ti: ^Type_Info) {
 		}
 		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:
 		print_string("#simd[")
 		print_u64(u64(info.count))
 		print_byte(']')
 		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:
 		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"
 	
 	@(private="file")
-	@(default_calling_convention="stdcall")
+	@(default_calling_convention="system")
 	foreign lib {
 		RtlMoveMemory :: proc(dst, s: rawptr, length: int) ---
 		RtlFillMemory :: proc(dst: rawptr, length: int, fill: i32) ---
@@ -25,21 +25,38 @@ when ODIN_NO_CRT && ODIN_OS == .Windows {
 		RtlMoveMemory(dst, src, len)
 		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)
-	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 {
 			b := byte(val)
 			p := ([^]byte)(ptr)
-			for i := 0; i < len; i += 1 {
+			for i := int_t(0); i < len; i += 1 {
 				p[i] = b
 			}
 		}
 		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)
-	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)
 		if d == s || len == 0 {
 			return dst
@@ -52,7 +69,7 @@ when ODIN_NO_CRT && ODIN_OS == .Windows {
 		}
 
 		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]
 			}
 			return dst
@@ -60,10 +77,10 @@ when ODIN_NO_CRT && ODIN_OS == .Windows {
 		return memcpy(dst, src, len)
 	}
 	@(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)
 		if d != s {
-			for i := 0; i < len; i += 1 {
+			for i := int_t(0); i < len; i += 1 {
 				d[i] = s[i]
 			}
 		}
@@ -81,4 +98,4 @@ when ODIN_NO_CRT && ODIN_OS == .Windows {
 		}
 		return ptr
 	}
-}
+}

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

@@ -1,9 +1,9 @@
-//+private
+#+private
 package runtime
 
 foreign import "system:Foundation.framework"
 
-import "core:intrinsics"
+import "base:intrinsics"
 
 objc_id :: ^intrinsics.objc_object
 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
 
 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
 
 foreign import kernel32 "system:Kernel32.lib"
 
 @(private)
 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" () -> ! {

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

@@ -1,4 +1,5 @@
-//+private
+#+private
+#+no-instrumentation
 package runtime
 
 @require foreign import "system:int64.lib"
@@ -12,7 +13,7 @@ windows_trap_array_bounds :: proc "contextless" () -> ! {
 	EXCEPTION_ARRAY_BOUNDS_EXCEEDED :: 0xC000008C
 
 	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)

+ 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
 
-import "core:intrinsics"
+import "base:intrinsics"
 
 udivmod128 :: proc "c" (a, b: u128, rem: ^u128) -> u128 {
 	_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]))
 		}
 
-		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 rem != nil {
 				rem^ = a
@@ -107,7 +107,7 @@ udivmod128 :: proc "c" (a, b: u128, rem: ^u128) -> u128 {
 				r[low]  = n[high] >> (sr - U64_BITS)
 			}
 		} 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 rem != nil {
@@ -143,7 +143,7 @@ udivmod128 :: proc "c" (a, b: u128, rem: ^u128) -> u128 {
 		r_all = transmute(u128)r
 		s := i128(b - r_all - 1) >> (U128_BITS - 1)
 		carry = u32(s & 1)
-		r_all -= b & transmute(u128)s
+		r_all -= b & u128(s)
 		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/lld-link.exe


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


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


BIN
bin/radlink.exe


BIN
bin/wasm-ld.exe


+ 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_month=%CURR_DATE_TIME:~4,2%
+set curr_day=%CURR_DATE_TIME:~6,2%
 
 :: Make sure this is a decent name and not generic
 set exe_name=odin.exe
@@ -45,23 +49,49 @@ if "%2" == "1" (
 	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
+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%\"
 
+rem fileversion is defined as {Major,Minor,Build,Private: u16} so a bit limited
+set rc_flags=-nologo ^
+-DV1=%V1% -DV2=%V2% -DV3=%V3% -DV4=%V4% ^
+-DVF=%odin_version_full% -DNIGHTLY=%nightly%
+
+where /Q git.exe || goto skip_git_hash
 if not exist .git\ goto skip_git_hash
 for /f "tokens=1,2" %%i IN ('git show "--pretty=%%cd %%h" "--date=format:%%Y-%%m" --no-patch --no-notes HEAD') do (
 	set odin_version_raw=dev-%%i
 	set GIT_SHA=%%j
 )
-if %ERRORLEVEL% equ 0 set compiler_defines=%compiler_defines% -DGIT_SHA=\"%GIT_SHA%\"
+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
 
 if %nightly% equ 1 set compiler_defines=%compiler_defines% -DNIGHTLY
 
 if %release_mode% EQU 0 ( rem Debug
 	set compiler_flags=%compiler_flags% -Od -MDd -Z7
+	set rc_flags=%rc_flags% -D_DEBUG
 ) else ( rem Release
 	set compiler_flags=%compiler_flags% -O2 -MT -Z7
 	set compiler_defines=%compiler_defines% -DNO_ARRAY_BOUNDS_CHECK
@@ -79,6 +109,8 @@ set libs= ^
 	kernel32.lib ^
 	Synchronization.lib ^
 	bin\llvm\windows\LLVM-C.lib
+set odin_res=misc\odin.res
+set odin_rc=misc\odin.rc
 
 rem DO NOT TOUCH!
 rem THIS TILDE STUFF IS FOR DEVELOPMENT ONLY!
@@ -90,7 +122,7 @@ if %tilde_backend% EQU 1 (
 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
 	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 linker_settings=%libs% %linker_flags%
+set linker_settings=%libs% %odin_res% %linker_flags%
 
 del *.pdb > 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%
+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
 
 call build_vendor.bat
 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
 

+ 68 - 21
build_odin.sh

@@ -2,7 +2,6 @@
 set -eu
 
 : ${CPPFLAGS=}
-: ${CXX=clang++}
 : ${CXXFLAGS=}
 : ${LDFLAGS=}
 : ${LLVM_CONFIG=}
@@ -24,17 +23,32 @@ error() {
 	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
 	# 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-12)" ]; then LLVM_CONFIG="llvm-config-12"
 	elif [ -n "$(command -v llvm-config-11)" ]; then LLVM_CONFIG="llvm-config-11"
 	# 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
 	elif [ -n "$(command -v llvm-config)" ]; then LLVM_CONFIG="llvm-config"
 	else
@@ -42,42 +56,66 @@ if [ -z "$LLVM_CONFIG" ]; then
 	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_MAJOR="$(echo $LLVM_VERSION | awk -F. '{print $1}')"
 LLVM_VERSION_MINOR="$(echo $LLVM_VERSION | awk -F. '{print $2}')"
 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
 
 case "$OS_NAME" in
 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
 
-	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)
 	CXXFLAGS="$CXXFLAGS $($LLVM_CONFIG --cxxflags --ldflags)"
 	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)
 	CXXFLAGS="$CXXFLAGS $($LLVM_CONFIG --cxxflags --ldflags)"
 	LDFLAGS="$LDFLAGS -ldl $($LLVM_CONFIG --libs core native --system-libs --libfiles)"
 	# Copy libLLVM*.so into current directory for linking
 	# 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"
 	;;
 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 $($LLVM_CONFIG --libs core native --system-libs)"
 	;;
@@ -95,7 +133,7 @@ build_odin() {
 		EXTRAFLAGS="-O3"
 		;;
 	release-native)
-		if [ "$OS_ARCH" == "arm64" ]; then
+		if [ "$OS_ARCH" = "arm64" ] || [ "$OS_ARCH" = "aarch64" ]; then
 			# Use preferred flag for Arm (ie arm64 / aarch64 / etc)
 			EXTRAFLAGS="-O3 -mcpu=native"
 		else
@@ -117,23 +155,32 @@ build_odin() {
 }
 
 run_demo() {
-	./odin run examples/demo/demo.odin -file
+	./odin run examples/demo -vet -strict-style -- Hellope World
 }
 
 if [ $# -eq 0 ]; then
 	build_odin debug
 	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
 	case $1 in
 	report)
-		[ ! -f "./odin" ] && build_odin debug
+		if [ ! -f "./odin" ]; then
+			build_odin debug
+			run_demo
+		fi
 		./odin report
 		;;
+	debug)
+		build_odin debug
+		run_demo
+		;;
 	*)
 		build_odin $1
 		;;
 	esac
-	run_demo
 else
 	error "Too many arguments!"
 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)
 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 = max(size, MIN_READ_BUFFER_SIZE)
 	reader_reset(b, rd)
 	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) {
@@ -81,7 +81,7 @@ _reader_read_new_chunk :: proc(b: ^Reader) -> io.Error {
 	for i := b.max_consecutive_empty_reads; i > 0; i -= 1 {
 		n, err := io.read(b.rd, b.buf[b.w:])
 		if n < 0 {
-			return .Negative_Read
+			return err if err != nil else .Negative_Read
 		}
 		b.w += n
 		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) {
 			n, b.err = io.read(b.rd, p)
 			if n < 0 {
-				return 0, .Negative_Read
+				return 0, b.err if b.err != nil else .Negative_Read
 			}
 
 			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
 		n, b.err = io.read(b.rd, b.buf)
 		if n < 0 {
-			return 0, .Negative_Read
+			return 0, b.err if b.err != nil else .Negative_Read
 		}
 		if n == 0 {
 			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) {
 		n, err := io.write(w, b.buf[b.r:b.w])
 		if n < 0 {
-			return 0, .Negative_Write
+			return 0, err if err != nil else .Negative_Write
 		}
 		b.r += n
 		return i64(n), err

+ 1 - 1
core/bufio/scanner.odin

@@ -4,7 +4,7 @@ import "core:bytes"
 import "core:io"
 import "core:mem"
 import "core:unicode/utf8"
-import "core:intrinsics"
+import "base:intrinsics"
 
 // Extra errors returns by scanning procedures
 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
 		if writer_buffered(b) == 0 {
 			m, b.err = io.write(b.wr, p)
+			if m < 0 && b.err == nil {
+				b.err = .Negative_Write
+				break
+			}
 		} else {
 			m = copy(b.buf[b.n:], p)
 			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) {
 	b := (^Writer)(stream_data)
 	#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)
 }
 
-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)
 }
 
-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 {
-		b.buf = make([dynamic]byte, len, cap, allocator)
+		b.buf = make([dynamic]byte, len, cap, allocator, loc)
 		return
 	}
 
@@ -96,28 +96,28 @@ buffer_truncate :: proc(b: ^Buffer, n: int) {
 }
 
 @(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 {
-		resize(&b.buf, l+n)
+		resize(&b.buf, l+n, loc=loc)
 		return l, true
 	}
 	return 0, false
 }
 
 @(private)
-_buffer_grow :: proc(b: ^Buffer, n: int) -> int {
+_buffer_grow :: proc(b: ^Buffer, n: int, loc := #caller_location) -> int {
 	m := buffer_length(b)
 	if m == 0 && b.off != 0 {
 		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
 	}
 
 	if b.buf == nil && n <= SMALL_BUFFER_SIZE {
 		// 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
 	}
 
@@ -127,31 +127,34 @@ _buffer_grow :: proc(b: ^Buffer, n: int) -> int {
 	} else if c > max(int) - c - n {
 		panic("bytes.Buffer: too large")
 	} else {
-		resize(&b.buf, 2*c + n)
+		resize(&b.buf, 2*c + n, loc=loc)
 		copy(b.buf[:], b.buf[b.off:])
 	}
 	b.off = 0
-	resize(&b.buf, m+n)
+	resize(&b.buf, m+n, loc=loc)
 	return m
 }
 
-buffer_grow :: proc(b: ^Buffer, n: int) {
+buffer_grow :: proc(b: ^Buffer, n: int, loc := #caller_location) {
 	if n < 0 {
 		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
 	if offset < 0 {
 		err = .Invalid_Offset
 		return
 	}
-	_, ok := _buffer_try_grow(b, offset+len(p))
+	_, ok := _buffer_try_grow(b, offset+len(p), loc=loc)
 	if !ok {
-		_ = _buffer_grow(b, offset+len(p))
+		_ = _buffer_grow(b, offset+len(p), loc=loc)
 	}
 	if len(b.buf) <= offset {
 		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
-	m, ok := _buffer_try_grow(b, len(p))
+	m, ok := _buffer_try_grow(b, len(p), loc=loc)
 	if !ok {
-		m = _buffer_grow(b, len(p))
+		m = _buffer_grow(b, len(p), loc=loc)
 	}
 	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
-	m, ok := _buffer_try_grow(b, len(s))
+	m, ok := _buffer_try_grow(b, len(s), loc=loc)
 	if !ok {
-		m = _buffer_grow(b, len(s))
+		m = _buffer_grow(b, len(s), loc=loc)
 	}
 	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
-	m, ok := _buffer_try_grow(b, 1)
+	m, ok := _buffer_try_grow(b, 1, loc=loc)
 	if !ok {
-		m = _buffer_grow(b, 1)
+		m = _buffer_grow(b, 1, loc=loc)
 	}
 	b.buf[m] = c
 	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 {
-		buffer_write_byte(b, byte(r))
+		buffer_write_byte(b, byte(r), loc=loc)
 		return 1, nil
 	}
 	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 {
-		m = _buffer_grow(b, utf8.UTF_MAX)
+		m = _buffer_grow(b, utf8.UTF_MAX, loc=loc)
 	}
 	res: [4]byte
 	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) {
+	if len(p) == 0 {
+		return 0, nil
+	}
 	b.last_read = .Invalid
 
 	if uint(offset) >= len(b.buf) {
-		err = .Invalid_Offset
+		err = .EOF
 		return
 	}
 	n = copy(p, b.buf[offset:])
@@ -310,6 +316,27 @@ buffer_unread_rune :: proc(b: ^Buffer) -> io.Error {
 	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) {
 	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)
 		m, e := io.read(r, b.buf[i:cap(b.buf)])
 		if m < 0 {
-			err = .Negative_Read
+			err = e if e != nil else .Negative_Read
 			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))
 	case .Write_At:
 		return io._i64_err(buffer_write_at(b, p, int(offset)))
+	case .Seek:
+		n, err = buffer_seek(b, offset, whence)
+		return
 	case .Size:
-		n = i64(buffer_capacity(b))
+		n = i64(buffer_length(b))
 		return
 	case .Destroy:
 		buffer_destroy(b)
 		return
 	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
 }

+ 310 - 7
core/bytes/bytes.odin

@@ -1,9 +1,38 @@
 package bytes
 
+import "base:intrinsics"
 import "core:mem"
+import "core:simd"
 import "core:unicode"
 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 {
 	c := make([]byte, len(s), allocator, loc)
 	copy(c, s)
@@ -293,28 +322,277 @@ split_after_iterator :: proc(s: ^[]byte, sep: []byte) -> ([]byte, bool) {
 	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 {
 			return i
 		}
 	}
+
 	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 {
 			return i
 		}
 	}
+
 	return -1
 }
 
 
-
 @private PRIME_RABIN_KARP :: 16777619
 
 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
 scrub :: proc(s: []byte, replacement: []byte, allocator := context.allocator) -> []byte {
 	str := s
@@ -1167,3 +1445,28 @@ fields_proc :: proc(s: []byte, f: proc(rune) -> bool, allocator := context.alloc
 
 	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
 }
 
-reader_init :: proc(r: ^Reader, s: []byte) {
+reader_init :: proc(r: ^Reader, s: []byte) -> io.Stream {
 	r.s = s
 	r.i = 0
 	r.prev_rune = -1
+	return reader_to_stream(r)
 }
 
 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) {
+	if len(p) == 0 {
+		return 0, nil
+	}
 	if r.i >= i64(len(r.s)) {
 		return 0, .EOF
 	}
@@ -42,6 +46,9 @@ reader_read :: proc(r: ^Reader, p: []byte) -> (n: int, err: io.Error) {
 	return
 }
 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 {
 		return 0, .Invalid_Offset
 	}
@@ -97,7 +104,6 @@ reader_unread_rune :: proc(r: ^Reader) -> io.Error {
 	return nil
 }
 reader_seek :: proc(r: ^Reader, offset: i64, whence: io.Seek_From) -> (i64, io.Error) {
-	r.prev_rune = -1
 	abs: i64
 	switch whence {
 	case .Start:
@@ -114,6 +120,7 @@ reader_seek :: proc(r: ^Reader, offset: i64, whence: io.Seek_From) -> (i64, io.E
 		return 0, .Invalid_Offset
 	}
 	r.i = abs
+	r.prev_rune = -1
 	return abs, nil
 }
 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
 
-import builtin "core:builtin"
+import builtin "base:builtin"
 
 char           :: builtin.u8  // assuming -funsigned-char
 
@@ -104,3 +104,13 @@ NULL           :: rawptr(uintptr(0))
 NDEBUG         :: !ODIN_DEBUG
 
 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                                    |
 | `<iso646.h>`      | Not applicable, use Odin's operators               |
 | `<limits.h>`      | Not projected                                      |
-| `<locale.h>`      | Not projected                                      |
+| `<locale.h>`      | Fully projected                                    |
 | `<math.h>`        | Mostly projected, see [limitations](#Limitations)  |
 | `<setjmp.h>`      | Fully projected                                    |
 | `<signal.h>`      | Fully projected                                    |
@@ -70,4 +70,4 @@ with the following copyright.
 
 ```
 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 ---
 
 	// 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 ---
 	cpowf   :: proc(x, y: complex_float) -> complex_float ---
 	csqrt   :: proc(z: complex_double) -> complex_double ---
@@ -67,7 +67,7 @@ foreign libc {
 	crealf  :: proc(z: complex_float) -> float ---
 }
 
-import builtin "core:builtin"
+import builtin "base:builtin"
 
 complex_float  :: distinct builtin.complex64
 complex_double :: distinct builtin.complex128

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

@@ -40,7 +40,7 @@ when ODIN_OS == .FreeBSD {
 	ERANGE :: 34
 }
 
-when ODIN_OS == .OpenBSD {
+when ODIN_OS == .OpenBSD || ODIN_OS == .NetBSD {
 	@(private="file")
 	@(default_calling_convention="c")
 	foreign libc {
@@ -80,10 +80,36 @@ when ODIN_OS == .Darwin {
 	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
 // 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
 // it actually is.
-errno :: #force_inline proc() -> ^int {
+errno :: #force_inline proc "contextless" () -> ^int {
 	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
 
-import "core:intrinsics"
+import "base:intrinsics"
 
 when ODIN_OS == .Windows {
 	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
 		// stack unwinding.
 		@(link_name="_setjmp")
-		setjmp  :: proc(env: ^jmp_buf, hack: rawptr = nil) -> int ---
+		setjmp :: proc(env: ^jmp_buf, hack: rawptr = nil) -> int ---
 	}
 } else {
 	@(default_calling_convention="c")
 	foreign libc {
 		// 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")
 foreign libc {
 	// 7.13.2 Restore calling environment
+	@(link_name=LLONGJMP)
 	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
 // relevant platforms.
 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
 }
 
-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_DFL  :: rawptr(uintptr(0))
 	SIG_IGN  :: rawptr(uintptr(1)) 

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

@@ -2,7 +2,9 @@ package libc
 
 // 7.16 Variable arguments
 
-import "core:intrinsics"
+import "base:intrinsics"
+
+import "core:c"
 
 @(private="file")
 @(default_calling_convention="none")
@@ -12,15 +14,7 @@ foreign _ {
 	@(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(cast(^i8)ap)

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

@@ -2,7 +2,7 @@ package libc
 
 // 7.17 Atomics
 
-import "core:intrinsics"
+import "base:intrinsics"
 
 ATOMIC_BOOL_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
 }
 
-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 != .acq_rel)
 

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

@@ -1,5 +1,7 @@
 package libc
 
+import "core:io"
+
 when ODIN_OS == .Windows {
 	foreign import libc {
 		"system:libucrt.lib",
@@ -15,6 +17,12 @@ when ODIN_OS == .Windows {
 
 FILE :: struct {}
 
+Whence :: enum int {
+	SET = SEEK_SET,
+	CUR = SEEK_CUR,
+	END = SEEK_END,
+}
+
 // MSVCRT compatible.
 when ODIN_OS == .Windows {
 	_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
 
 	_IOFBF :: 0
@@ -99,11 +131,15 @@ when ODIN_OS == .OpenBSD {
 	SEEK_CUR :: 1
 	SEEK_END :: 2
 
+	TMP_MAX :: 308915776
+
 	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 {
@@ -124,10 +160,12 @@ when ODIN_OS == .FreeBSD {
 	SEEK_CUR :: 1
 	SEEK_END :: 2
 
+	TMP_MAX :: 308915776
+
 	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")
 foreign libc {
 	// 7.21.4 Operations on files
 	remove    :: proc(filename: cstring) -> int ---
+	@(link_name=LRENAME)
 	rename    :: proc(old, new: cstring) -> int ---
 	tmpfile   :: proc() -> ^FILE ---
 	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 ---
 
 	// 7.21.9 File positioning functions
+	@(link_name=LFGETPOS)
 	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 ---
 	ftell     :: proc(stream: ^FILE) -> long ---
 	rewind    :: proc(stream: ^FILE) ---
@@ -218,3 +299,106 @@ foreign libc {
 	ferror    :: proc(stream: ^FILE) -> int ---
 	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"
 }
 
+@(require)
+import "base:runtime"
+
 when ODIN_OS == .Windows {
 	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
 
-	// GLIBC and MUSL only
 	@(private="file")
 	@(default_calling_convention="c")
 	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
 // 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
@@ -99,7 +115,7 @@ foreign libc {
 	at_quick_exit :: proc(func: proc "c" ()) -> int ---
 	exit          :: proc(status: int) -> ! ---
 	_Exit         :: proc(status: int) -> ! ---
-	getenv        :: proc(name: cstring) -> [^]char ---
+	getenv        :: proc(name: cstring) -> cstring ---
 	quick_exit    :: proc(status: 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 ---
 		}
 		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 {
 		foreign libc {
 			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(ptr)
+	} else when ODIN_ARCH == .wasm32 || ODIN_ARCH == .wasm64p32 {
+		context = runtime.default_context()
+		runtime.mem_free(ptr)
 	} else {
 		free(ptr)
 	}
-}
+}

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

@@ -1,6 +1,6 @@
 package libc
 
-import "core:runtime"
+import "base:runtime"
 
 // 7.24 String handling
 
@@ -12,6 +12,7 @@ when ODIN_OS == .Windows {
 	foreign import libc "system:c"
 }
 
+@(default_calling_convention="c")
 foreign libc {
 	// 7.24.2 Copying functions
 	memcpy   :: proc(s1, s2: rawptr, n: size_t) -> rawptr ---
@@ -40,7 +41,7 @@ foreign libc {
 	strtok   :: proc(s1: [^]char, s2: cstring) -> [^]char ---
 
 	// 7.24.6 Miscellaneous functions
-	strerror :: proc(errnum: int) -> [^]char ---
+	strerror :: proc(errnum: int) -> cstring ---
 	strlen   :: proc(s: cstring) -> size_t ---
 }
 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")
 	foreign libc {
 		// 7.27.2 Time manipulation functions
 		clock        :: proc() -> clock_t ---
+		@(link_name=LDIFFTIME)
 		difftime     :: proc(time1, time2: time_t) -> double ---
+		@(link_name=LMKTIME)
 		mktime       :: proc(timeptr: ^tm) -> time_t ---
+		@(link_name=LTIME)
 		time         :: proc(timer: ^time_t) -> time_t ---
 		timespec_get :: proc(ts: ^timespec, base: int) -> int ---
 
 		// 7.27.3 Time conversion functions
 		asctime      :: proc(timeptr: ^tm) -> [^]char ---
+		@(link_name=LCTIME)
 		ctime        :: proc(timer: ^time_t) -> [^]char ---
+		@(link_name=LGMTIME)
 		gmtime       :: proc(timer: ^time_t) -> ^tm ---
+		@(link_name=LLOCALTIME)
 		localtime    :: proc(timer: ^time_t) -> ^tm ---
 		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 {
 		CLOCKS_PER_SEC :: 100
 	} else {
 		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 {
 		tv_sec:  time_t,

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

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

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

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

+ 56 - 66
core/compress/common.odin

@@ -12,7 +12,7 @@ package compress
 
 import "core:io"
 import "core:bytes"
-import "core:runtime"
+import "base:runtime"
 
 /*
 	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))
 
 /*
@@ -34,15 +33,13 @@ COMPRESS_OUTPUT_ALLOCATE_MIN :: int(#config(COMPRESS_OUTPUT_ALLOCATE_MIN, 1 << 2
 
 */
 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))
 } 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))
 }
 
@@ -69,9 +66,8 @@ General_Error :: enum {
 	Incompatible_Options,
 	Unimplemented,
 
-	/*
-		Memory errors
-	*/
+	// Memory errors
+
 	Allocation_Failed,
 	Resize_Failed,
 }
@@ -86,17 +82,16 @@ GZIP_Error :: enum {
 	Payload_Length_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,
-	/*
-		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,
 
 }
@@ -137,9 +132,8 @@ Context_Memory_Input :: struct #packed {
 	code_buffer:       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_unpacked:     i64,
 }
@@ -159,18 +153,16 @@ Context_Stream_Input :: struct #packed {
 	code_buffer:       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_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,
 
 	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}
 
-@(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) {
 	#no_bounds_check {
 		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) {
 	// 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)
 	_ = io.read(z.input, b[:]) or_return
 	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}
 
-@(optimization_mode="speed")
+@(optimization_mode="favor_size")
 read_data :: #force_inline proc(z: ^$C, $T: typeid) -> (res: T, err: io.Error) {
 	b := read_slice(z, size_of(T)) or_return
 	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) {
 	#no_bounds_check {
 		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
 }
 
-@(optimization_mode="speed")
+@(optimization_mode="favor_size")
 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
 	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}
 
-/*
-	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) {
 	if z.num_bits >= 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
 }
 
-@(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) {
 	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) {
 	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) {
 	size :: size_of(T)
 
@@ -327,7 +317,7 @@ peek_data_from_stream :: #force_inline proc(z: ^Context_Stream_Input, $T: typeid
 	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) {
 	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
-@(optimization_mode="speed")
+@(optimization_mode="favor_size")
 peek_back_byte :: #force_inline proc(z: ^$C, offset: i64) -> (res: u8, err: io.Error) {
 	// Look back into the sliding window.
 	return z.output.buf[z.bytes_written - offset], .None
 }
 
 // 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 := u64(width)
 	b      := u64(0)
@@ -395,7 +385,7 @@ refill_lsb_from_memory :: #force_inline proc(z: ^Context_Memory_Input, width :=
 }
 
 // Generalized bit reader LSB
-@(optimization_mode="speed")
+@(optimization_mode="favor_size")
 refill_lsb_from_stream :: proc(z: ^Context_Stream_Input, width := i8(24)) {
 	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}
 
 
-@(optimization_mode="speed")
+@(optimization_mode="favor_size")
 consume_bits_lsb_from_memory :: #force_inline proc(z: ^Context_Memory_Input, width: u8) {
 	z.code_buffer >>= 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) {
 	z.code_buffer >>= 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}
 
-@(optimization_mode="speed")
+@(optimization_mode="favor_size")
 peek_bits_lsb_from_memory :: #force_inline proc(z: ^Context_Memory_Input, width: u8) -> u32 {
 	if z.num_bits < u64(width) {
 		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))
 }
 
-@(optimization_mode="speed")
+@(optimization_mode="favor_size")
 peek_bits_lsb_from_stream :: #force_inline proc(z: ^Context_Stream_Input, width: u8) -> u32 {
 	if z.num_bits < u64(width) {
 		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}
 
-@(optimization_mode="speed")
+@(optimization_mode="favor_size")
 peek_bits_no_refill_lsb_from_memory :: #force_inline proc(z: ^Context_Memory_Input, width: u8) -> u32 {
 	assert(z.num_bits >= u64(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 {
 	assert(z.num_bits >= u64(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}
 
-@(optimization_mode="speed")
+@(optimization_mode="favor_size")
 read_bits_lsb_from_memory :: #force_inline proc(z: ^Context_Memory_Input, width: u8) -> u32 {
 	k := #force_inline peek_bits_lsb(z, width)
 	#force_inline consume_bits_lsb(z, width)
 	return k
 }
 
-@(optimization_mode="speed")
+@(optimization_mode="favor_size")
 read_bits_lsb_from_stream :: #force_inline proc(z: ^Context_Stream_Input, width: u8) -> u32 {
 	k := peek_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}
 
-@(optimization_mode="speed")
+@(optimization_mode="favor_size")
 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)
 	#force_inline consume_bits_lsb(z, width)
 	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 {
 	k := peek_bits_no_refill_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}
 
 
-@(optimization_mode="speed")
+@(optimization_mode="favor_size")
 discard_to_next_byte_lsb_from_memory :: proc(z: ^Context_Memory_Input) {
 	discard := u8(z.num_bits & 7)
 	#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 := u8(z.num_bits & 7)
 	consume_bits_lsb(z, discard)

Vissa filer visades inte eftersom för många filer har ändrats