Ver código fonte

Merge branch 'master' into bill/raddebugger-custom-section

gingerBill 3 meses atrás
pai
commit
c383e550f9
100 arquivos alterados com 4267 adições e 945 exclusões
  1. 35 33
      .github/workflows/ci.yml
  2. 2 1
      .gitignore
  3. 2 1
      base/builtin/builtin.odin
  4. 7 1
      base/intrinsics/intrinsics.odin
  5. 1 0
      base/runtime/internal.odin
  6. 19 6
      base/runtime/procs_darwin.odin
  7. 19 25
      build.bat
  8. 4 1
      build_odin.sh
  9. 4 6
      core/container/priority_queue/priority_queue.odin
  10. 14 8
      core/encoding/json/unmarshal.odin
  11. 53 11
      core/log/file_console_logger.odin
  12. 20 20
      core/odin/parser/parser.odin
  13. 42 41
      core/os/os2/allocators.odin
  14. 44 17
      core/os/os2/dir.odin
  15. 2 1
      core/os/os2/dir_linux.odin
  16. 17 0
      core/os/os2/dir_posix_darwin.odin
  17. 5 5
      core/os/os2/dir_windows.odin
  18. 7 7
      core/os/os2/env_posix.odin
  19. 2 2
      core/os/os2/env_wasi.odin
  20. 10 10
      core/os/os2/env_windows.odin
  21. 2 2
      core/os/os2/errors.odin
  22. 13 4
      core/os/os2/file.odin
  23. 29 29
      core/os/os2/file_linux.odin
  24. 36 35
      core/os/os2/file_posix.odin
  25. 28 0
      core/os/os2/file_posix_darwin.odin
  26. 2 2
      core/os/os2/file_posix_other.odin
  27. 32 45
      core/os/os2/file_windows.odin
  28. 0 5
      core/os/os2/internal_util.odin
  29. 4 4
      core/os/os2/path.odin
  30. 29 9
      core/os/os2/path_linux.odin
  31. 2 2
      core/os/os2/path_netbsd.odin
  32. 3 3
      core/os/os2/path_openbsd.odin
  33. 30 13
      core/os/os2/path_posix.odin
  34. 0 21
      core/os/os2/path_posixfs.odin
  35. 2 2
      core/os/os2/path_wasi.odin
  36. 23 19
      core/os/os2/path_windows.odin
  37. 24 24
      core/os/os2/process_linux.odin
  38. 9 9
      core/os/os2/process_posix.odin
  39. 4 3
      core/os/os2/process_posix_darwin.odin
  40. 25 23
      core/os/os2/process_windows.odin
  41. 4 4
      core/os/os2/stat.odin
  42. 4 4
      core/os/os2/stat_linux.odin
  43. 9 8
      core/os/os2/stat_posix.odin
  44. 68 45
      core/os/os2/stat_windows.odin
  45. 11 9
      core/os/os2/temp_file.odin
  46. 2 2
      core/os/os2/temp_file_linux.odin
  47. 2 2
      core/os/os2/temp_file_windows.odin
  48. 11 11
      core/os/os2/user.odin
  49. 33 0
      core/strconv/strconv.odin
  50. 1 1
      core/strings/builder.odin
  51. 1 1
      core/sys/darwin/Foundation/NSApplication.odin
  52. 46 0
      core/sys/darwin/Foundation/NSArray.odin
  53. 528 93
      core/sys/darwin/Foundation/NSMenu.odin
  54. 460 0
      core/sys/darwin/Foundation/NSMenuItem.odin
  55. 136 0
      core/sys/darwin/Foundation/objc_helper.odin
  56. 67 0
      core/sys/darwin/copyfile.odin
  57. 1 0
      core/sys/darwin/darwin.odin
  58. 0 2
      core/sys/darwin/sync.odin
  59. 0 10
      core/sys/darwin/xnu_system_call_wrappers.odin
  60. 4 2
      core/sys/linux/bits.odin
  61. 19 0
      core/sys/linux/constants.odin
  62. 3 1
      core/sys/linux/types.odin
  63. 50 18
      core/sys/linux/wrappers.odin
  64. 172 0
      core/sys/windows/scan_codes.odin
  65. 0 0
      core/terminal/ansi/ansi.odin
  66. 0 0
      core/terminal/ansi/doc.odin
  67. 4 0
      core/terminal/doc.odin
  68. 87 0
      core/terminal/internal.odin
  69. 36 0
      core/terminal/terminal.odin
  70. 16 0
      core/terminal/terminal_posix.odin
  71. 60 0
      core/terminal/terminal_windows.odin
  72. 1 1
      core/testing/reporting.odin
  73. 94 66
      core/testing/runner.odin
  74. 0 22
      core/testing/runner_windows.odin
  75. 7 5
      core/testing/signal_handler_libc.odin
  76. 5 1
      examples/all/all_linux.odin
  77. 5 2
      examples/all/all_main.odin
  78. 0 4
      examples/all/all_vendor.odin
  79. 2 0
      examples/all/all_vendor_windows.odin
  80. 1 1
      misc/get-date.c
  81. 7 1
      src/bug_report.cpp
  82. 27 9
      src/build_settings.cpp
  83. 326 3
      src/check_builtin.cpp
  84. 177 36
      src/check_decl.cpp
  85. 42 16
      src/check_expr.cpp
  86. 2 0
      src/check_stmt.cpp
  87. 10 1
      src/check_type.cpp
  88. 135 2
      src/checker.cpp
  89. 22 2
      src/checker.hpp
  90. 10 0
      src/checker_builtin_procs.hpp
  91. 4 0
      src/entity.cpp
  92. 14 1
      src/linker.cpp
  93. 14 28
      src/llvm_abi.cpp
  94. 665 43
      src/llvm_backend.cpp
  95. 3 1
      src/llvm_backend.hpp
  96. 4 1
      src/llvm_backend_const.cpp
  97. 56 0
      src/llvm_backend_debug.cpp
  98. 17 9
      src/llvm_backend_expr.cpp
  99. 100 25
      src/llvm_backend_general.cpp
  100. 76 2
      src/llvm_backend_proc.cpp

+ 35 - 33
.github/workflows/ci.yml

@@ -75,32 +75,35 @@ jobs:
       fail-fast: false
       matrix:
         # MacOS 13 runs on Intel, 14 runs on ARM
-        os: [ubuntu-latest, macos-13, macos-14]
+        os: [macos-13, macos-14, ubuntu-latest]
     runs-on: ${{ matrix.os }}
-    name: ${{ matrix.os == 'macos-14' && 'MacOS ARM' || (matrix.os == 'macos-13' && 'MacOS Intel' || 'Ubuntu') }} Build, Check, and Test
+    name: ${{ matrix.os == 'macos-14' && 'MacOS ARM' || (matrix.os == 'macos-13' && 'MacOS Intel') || (matrix.os == 'ubuntu-latest' && 'Ubuntu') }} Build, Check, and Test
     timeout-minutes: 15
     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 20
-          echo "/usr/lib/llvm-20/bin" >> $GITHUB_PATH
+      - uses: actions/checkout@v4
 
       - name: Download LLVM (MacOS Intel)
         if: matrix.os == 'macos-13'
         run: |
           brew update
           brew install llvm@20 [email protected] lld
+          echo "$(brew --prefix llvm@20)/bin" >> $GITHUB_PATH
 
       - name: Download LLVM (MacOS ARM)
         if: matrix.os == 'macos-14'
         run: |
           brew update
           brew install llvm@20 wasmtime [email protected] lld
+          echo "$(brew --prefix llvm@20)/bin" >> $GITHUB_PATH
+
+      - name: Download LLVM (Ubuntu)
+        if: matrix.os == 'ubuntu-latest'
+        run: |
+          wget https://apt.llvm.org/llvm.sh
+          chmod +x llvm.sh
+          sudo ./llvm.sh 20
+          echo "/usr/lib/llvm-20/bin" >> $GITHUB_PATH
 
       - name: Build Odin
         run: ./build_odin.sh release
@@ -124,52 +127,51 @@ jobs:
       - name: Odin check vendor/sdl3
         run: ./odin check vendor/sdl3  -strict-style -vet -disallow-do -no-entry-point
       - name: Normal Core library tests
-        run: ./odin test tests/core/normal.odin -file -all-packages -vet -strict-style -disallow-do -define:ODIN_TEST_FANCY=false -define:ODIN_TEST_FAIL_ON_BAD_MEMORY=true
+        run: ./odin test tests/core/normal.odin -file -all-packages -vet -strict-style -disallow-do -define:ODIN_TEST_FANCY=false -define:ODIN_TEST_FAIL_ON_BAD_MEMORY=true -sanitize:address
       - 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
+        run: ./odin test tests/core/speed.odin -o:speed -file -all-packages -vet -strict-style -disallow-do -define:ODIN_TEST_FANCY=false -define:ODIN_TEST_FAIL_ON_BAD_MEMORY=true -sanitize:address
       - 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
+        run: ./odin test tests/vendor -all-packages -vet -strict-style -disallow-do -define:ODIN_TEST_FANCY=false -define:ODIN_TEST_FAIL_ON_BAD_MEMORY=true -sanitize:address
       - 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
+        run: ./odin test tests/internal -all-packages -vet -strict-style -disallow-do -define:ODIN_TEST_FANCY=false -define:ODIN_TEST_FAIL_ON_BAD_MEMORY=true -sanitize:address
       - name: GitHub Issue tests
         run: |
           cd tests/issues
           ./run.sh
 
+      - name: Run demo on WASI WASM32
+        run: |
+          ./odin build examples/demo -target:wasi_wasm32 -vet -strict-style -disallow-do -out:demo
+          wasmtime ./demo.wasm
+        if: matrix.os == 'macos-14'
+
       - name: Check benchmarks
         run: ./odin check tests/benchmark -vet -strict-style -no-entry-point
       - name: Odin check examples/all for Linux i386
-        run: ./odin check examples/all -vet -strict-style -disallow-do -target:linux_i386
         if: matrix.os == 'ubuntu-latest'
+        run: ./odin check examples/all -vet -strict-style -disallow-do -target:linux_i386
       - 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'
+        run: ./odin check examples/all -vet -strict-style -disallow-do -target:linux_arm64
       - name: Odin check examples/all for FreeBSD amd64
-        run: ./odin check examples/all -vet -strict-style -disallow-do -target:freebsd_amd64
         if: matrix.os == 'ubuntu-latest'
+        run: ./odin check examples/all -vet -strict-style -disallow-do -target:freebsd_amd64
       - name: Odin check examples/all for OpenBSD amd64
-        run: ./odin check examples/all -vet -strict-style -disallow-do -target:openbsd_amd64
         if: matrix.os == 'ubuntu-latest'
+        run: ./odin check examples/all -vet -strict-style -disallow-do -target:openbsd_amd64
 
       - name: Odin check vendor/sdl3 for Linux i386
-        run: ./odin check vendor/sdl3 -vet -strict-style -disallow-do -no-entry-point -target:linux_i386
         if: matrix.os == 'ubuntu-latest'
+        run: ./odin check vendor/sdl3 -vet -strict-style -disallow-do -no-entry-point -target:linux_i386
       - name: Odin check vendor/sdl3 for Linux arm64
-        run: ./odin check vendor/sdl3 -vet -strict-style -disallow-do -no-entry-point -target:linux_arm64
         if: matrix.os == 'ubuntu-latest'
+        run: ./odin check vendor/sdl3 -vet -strict-style -disallow-do -no-entry-point -target:linux_arm64
       - name: Odin check vendor/sdl3 for FreeBSD amd64
-        run: ./odin check vendor/sdl3 -vet -strict-style -disallow-do -no-entry-point -target:freebsd_amd64
         if: matrix.os == 'ubuntu-latest'
+        run: ./odin check vendor/sdl3 -vet -strict-style -disallow-do -no-entry-point -target:freebsd_amd64
       - name: Odin check vendor/sdl3 for OpenBSD amd64
-        run: ./odin check vendor/sdl3 -vet -strict-style -disallow-do -no-entry-point -target:openbsd_amd64
         if: matrix.os == 'ubuntu-latest'
-
-
-      - name: Run demo on WASI WASM32
-        run: |
-          ./odin build examples/demo -target:wasi_wasm32 -vet -strict-style -disallow-do -out:demo
-          wasmtime ./demo.wasm
-        if: matrix.os == 'macos-14'
+        run: ./odin check vendor/sdl3 -vet -strict-style -disallow-do -no-entry-point -target:openbsd_amd64
 
   build_windows:
     name: Windows Build, Check, and Test
@@ -215,23 +217,23 @@ jobs:
         shell: cmd
         run: |
           call "C:\Program Files\Microsoft Visual Studio\2022\Enterprise\VC\Auxiliary\Build\vcvars64.bat
-          odin test tests/core/normal.odin -file -all-packages -vet -strict-style -disallow-do -define:ODIN_TEST_FANCY=false -define:ODIN_TEST_FAIL_ON_BAD_MEMORY=true
+          odin test tests/core/normal.odin -file -all-packages -vet -strict-style -disallow-do -define:ODIN_TEST_FANCY=false -define:ODIN_TEST_FAIL_ON_BAD_MEMORY=true -sanitize:address
       - 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
+          odin test tests/core/speed.odin -o:speed -file -all-packages -vet -strict-style -disallow-do -define:ODIN_TEST_FANCY=false -define:ODIN_TEST_FAIL_ON_BAD_MEMORY=true -sanitize:address
       - name: Vendor library tests
         shell: cmd
         run: |
           call "C:\Program Files\Microsoft Visual Studio\2022\Enterprise\VC\Auxiliary\Build\vcvars64.bat
           copy vendor\lua\5.4\windows\*.dll .
-          odin test tests/vendor -all-packages -vet -strict-style -disallow-do -define:ODIN_TEST_FANCY=false -define:ODIN_TEST_FAIL_ON_BAD_MEMORY=true
+          odin test tests/vendor -all-packages -vet -strict-style -disallow-do -define:ODIN_TEST_FANCY=false -define:ODIN_TEST_FAIL_ON_BAD_MEMORY=true -sanitize:address
       - name: Odin internals tests
         shell: cmd
         run: |
           call "C:\Program Files\Microsoft Visual Studio\2022\Enterprise\VC\Auxiliary\Build\vcvars64.bat
-          odin test tests/internal -all-packages -vet -strict-style -disallow-do -define:ODIN_TEST_FANCY=false -define:ODIN_TEST_FAIL_ON_BAD_MEMORY=true
+          odin test tests/internal -all-packages -vet -strict-style -disallow-do -define:ODIN_TEST_FANCY=false -define:ODIN_TEST_FAIL_ON_BAD_MEMORY=true -sanitize:address
       - name: Check issues
         shell: cmd
         run: |

+ 2 - 1
.gitignore

@@ -293,5 +293,6 @@ build.sh
 
 # RAD debugger project file
 *.raddbg
-
+*.rdi
+tests/issues/build/*
 misc/featuregen/featuregen

+ 2 - 1
base/builtin/builtin.odin

@@ -119,7 +119,8 @@ jmag       :: proc(value: Quaternion) -> Float ---
 kmag       :: proc(value: Quaternion) -> Float ---
 conj       :: proc(value: Complex_Or_Quaternion) -> Complex_Or_Quaternion ---
 
-expand_values :: proc(value: Struct_Or_Array) -> (A, B, C, ...) ---
+expand_values   :: proc(value: Struct_Or_Array) -> (A, B, C, ...) ---
+compress_values :: proc(values: ...) -> Struct_Or_Array_Like_Type ---
 
 min   :: proc(values: ..T) -> T ---
 max   :: proc(values: ..T) -> T ---

+ 7 - 1
base/intrinsics/intrinsics.odin

@@ -221,6 +221,9 @@ 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_integer_to_unsigned :: proc($T: typeid) -> type where type_is_integer(T), !type_is_unsigned(T) ---
+type_integer_to_signed   :: proc($T: typeid) -> type where type_is_integer(T), type_is_unsigned(T) ---
+
 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 ---
@@ -357,15 +360,18 @@ x86_xgetbv :: proc(cx: u32) -> (eax, edx: u32) ---
 objc_object   :: struct{}
 objc_selector :: struct{}
 objc_class    :: struct{}
+objc_ivar     :: struct{}
+
 objc_id    :: ^objc_object
 objc_SEL   :: ^objc_selector
 objc_Class :: ^objc_class
+objc_Ivar  :: ^objc_ivar
 
 objc_find_selector     :: proc($name: string) -> objc_SEL   ---
 objc_register_selector :: proc($name: string) -> objc_SEL   ---
 objc_find_class        :: proc($name: string) -> objc_Class ---
 objc_register_class    :: proc($name: string) -> objc_Class ---
-
+objc_ivar_get          :: proc(self: ^$T) -> ^$U ---
 
 valgrind_client_request :: proc(default: uintptr, request: uintptr, a0, a1, a2, a3, a4: uintptr) -> uintptr ---
 

+ 1 - 0
base/runtime/internal.odin

@@ -1109,6 +1109,7 @@ __read_bits :: proc "contextless" (dst, src: [^]byte, offset: uintptr, size: uin
 
 when .Address in ODIN_SANITIZER_FLAGS {
 	foreign {
+		@(require)
 		__asan_unpoison_memory_region :: proc "system" (address: rawptr, size: uint) ---
 	}
 }

+ 19 - 6
base/runtime/procs_darwin.odin

@@ -2,21 +2,34 @@
 package runtime
 
 @(priority_index=-1e6)
-foreign import "system:Foundation.framework"
+foreign import ObjC "system:objc"
 
 import "base:intrinsics"
 
-objc_id :: ^intrinsics.objc_object
+objc_id    :: ^intrinsics.objc_object
 objc_Class :: ^intrinsics.objc_class
-objc_SEL :: ^intrinsics.objc_selector
+objc_SEL   :: ^intrinsics.objc_selector
+objc_Ivar  :: ^intrinsics.objc_ivar
+objc_BOOL  :: bool
 
-foreign Foundation {
-	objc_lookUpClass :: proc "c" (name: cstring) -> objc_Class ---
+
+objc_IMP :: proc "c" (object: objc_id, sel: objc_SEL, #c_vararg args: ..any) -> objc_id
+
+foreign ObjC {
 	sel_registerName :: proc "c" (name: cstring) -> objc_SEL ---
-	objc_allocateClassPair :: proc "c" (superclass: objc_Class, name: cstring, extraBytes: uint) -> objc_Class ---
 
 	objc_msgSend        :: proc "c" (self: objc_id, op: objc_SEL, #c_vararg args: ..any) ---
 	objc_msgSend_fpret  :: proc "c" (self: objc_id, op: objc_SEL, #c_vararg args: ..any) -> f64 ---
 	objc_msgSend_fp2ret :: proc "c" (self: objc_id, op: objc_SEL, #c_vararg args: ..any) -> complex128 ---
 	objc_msgSend_stret  :: proc "c" (self: objc_id, op: objc_SEL, #c_vararg args: ..any) ---
+
+	objc_lookUpClass          :: proc "c" (name: cstring) -> objc_Class ---
+	objc_allocateClassPair    :: proc "c" (superclass: objc_Class, name: cstring, extraBytes: uint) -> objc_Class ---
+	objc_registerClassPair    :: proc "c" (cls : objc_Class) ---
+	class_addMethod           :: proc "c" (cls: objc_Class, name: objc_SEL, imp: objc_IMP, types: cstring) -> objc_BOOL ---
+	class_addIvar             :: proc "c" (cls: objc_Class, name: cstring, size: uint, alignment: u8, types: cstring) -> objc_BOOL ---
+	class_getInstanceVariable :: proc "c" (cls : objc_Class, name: cstring) -> objc_Ivar ---
+	class_getInstanceSize     :: proc "c" (cls : objc_Class) -> uint ---
+	ivar_getOffset            :: proc "c" (v: objc_Ivar) -> uintptr ---
 }
+

+ 19 - 25
build.bat

@@ -19,16 +19,27 @@ if "%VSCMD_ARG_TGT_ARCH%" neq "x64" (
 	)
 )
 
+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-%%d" --no-patch --no-notes HEAD') do (
+	set CURR_DATE_TIME=%%i
+	set GIT_SHA=%%j
+)
+if %ERRORLEVEL% equ 0 (
+	goto have_git_hash_and_date
+)
+:skip_git_hash
 pushd misc
 cl /nologo get-date.c
-popd
-
-for /f %%i in ('misc\get-date') do (
+for /f %%i in ('get-date') do (
 	set CURR_DATE_TIME=%%i
+	rem Don't set GIT_SHA
 )
+popd
+:have_git_hash_and_date
 set curr_year=%CURR_DATE_TIME:~0,4%
-set curr_month=%CURR_DATE_TIME:~4,2%
-set curr_day=%CURR_DATE_TIME:~6,2%
+set curr_month=%CURR_DATE_TIME:~5,2%
+set curr_day=%CURR_DATE_TIME:~8,2%
 
 :: Make sure this is a decent name and not generic
 set exe_name=odin.exe
@@ -61,31 +72,14 @@ if %release_mode% equ 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%\"
+set compiler_defines= -DODIN_VERSION_RAW=\"%odin_version_raw%\" -DGIT_SHA=\"%GIT_SHA%\"
 
 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%\"
-	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
+set rc_flags="-DGIT_SHA=%GIT_SHA% -DVP=dev-%V1%-%V2%:%GIT_SHA% nologo -DV1=%V1% -DV2=%V2% -DV3=%V3% -DV4=%V4% -DVF=%odin_version_full% -DNIGHTLY=%nightly%"
 
 if %nightly% equ 1 set compiler_defines=%compiler_defines% -DNIGHTLY
 
@@ -153,4 +147,4 @@ if %release_mode% EQU 0 echo: & echo Debug compiler built. Note: run "build.bat
 
 del *.obj > NUL 2> NUL
 
-:end_of_build
+:end_of_build

+ 4 - 1
build_odin.sh

@@ -6,7 +6,6 @@ set -eu
 : ${LDFLAGS=}
 : ${LLVM_CONFIG=}
 
-CPPFLAGS="$CPPFLAGS -DODIN_VERSION_RAW=\"dev-$(date +"%Y-%m")\""
 CXXFLAGS="$CXXFLAGS -std=c++14"
 DISABLED_WARNINGS="-Wno-switch -Wno-macro-redefined -Wno-unused-value"
 LDFLAGS="$LDFLAGS -pthread -lm"
@@ -15,8 +14,12 @@ OS_NAME="$(uname -s)"
 
 if [ -d ".git" ] && [ -n "$(command -v git)" ]; then
 	GIT_SHA=$(git show --pretty='%h' --no-patch --no-notes HEAD)
+	GIT_DATE=$(git show "--pretty=%cd" "--date=format:%Y-%m" --no-patch --no-notes HEAD)
 	CPPFLAGS="$CPPFLAGS -DGIT_SHA=\"$GIT_SHA\""
+else
+	GIT_DATE=$(date +"%Y-%m")
 fi
+CPPFLAGS="$CPPFLAGS -DODIN_VERSION_RAW=\"dev-$GIT_DATE\""
 
 error() {
 	printf "ERROR: %s\n" "$1"

+ 4 - 6
core/container/priority_queue/priority_queue.odin

@@ -133,12 +133,10 @@ pop_safe :: proc(pq: ^$Q/Priority_Queue($T), loc := #caller_location) -> (value:
 remove :: proc(pq: ^$Q/Priority_Queue($T), i: int) -> (value: T, ok: bool) {
 	n := builtin.len(pq.queue)
 	if 0 <= i && i < n {
-		if n != i {
-			pq.swap(pq.queue[:], i, n)
-			_shift_down(pq, i, n)
-			_shift_up(pq, i)
-		}
-		value, ok = builtin.pop_safe(&pq.queue)
+		pq.swap(pq.queue[:], i, n-1)
+		_shift_down(pq, i, n-1)
+		_shift_up(pq, i)
+		value, ok = builtin.pop(&pq.queue), true
 	}
 	return
 }

+ 14 - 8
core/encoding/json/unmarshal.odin

@@ -406,6 +406,9 @@ unmarshal_expect_token :: proc(p: ^Parser, kind: Token_Kind, loc := #caller_loca
 	return prev
 }
 
+// Struct tags can include not only the name of the JSON key, but also a tag such as `omitempty`.
+// Example: `json:"key_name,omitempty"`
+// This returns the first field as `json_name`, and the rest are returned as `extra`.
 @(private)
 json_name_from_tag_value :: proc(value: string) -> (json_name, extra: string) {
 	json_name = value
@@ -441,12 +444,6 @@ unmarshal_object :: proc(p: ^Parser, v: any, end_token: Token_Kind) -> (err: Unm
 			defer delete(key, p.allocator)
 			
 			unmarshal_expect_token(p, .Colon)						
-			
-			field_test :: #force_inline proc "contextless" (field_used: [^]byte, offset: uintptr) -> bool {
-				prev_set := field_used[offset/8] & byte(offset&7) != 0
-				field_used[offset/8] |= byte(offset&7)
-				return prev_set
-			}
 
 			field_used_bytes := (reflect.size_of_typeid(ti.id)+7)/8
 			field_used := intrinsics.alloca(field_used_bytes + 1, 1) // + 1 to not overflow on size_of 0 types.
@@ -465,7 +462,9 @@ unmarshal_object :: proc(p: ^Parser, v: any, end_token: Token_Kind) -> (err: Unm
 			
 			if use_field_idx < 0 {
 				for field, field_idx in fields {
-					if key == field.name {
+					tag_value := reflect.struct_tag_get(field.tag, "json")
+					json_name, _ := json_name_from_tag_value(tag_value)
+					if json_name == "" && key == field.name {
 						use_field_idx = field_idx
 						break
 					}
@@ -486,7 +485,9 @@ unmarshal_object :: proc(p: ^Parser, v: any, end_token: Token_Kind) -> (err: Unm
 						}
 					}
 
-					if field.name == key || (field.tag != "" && reflect.struct_tag_get(field.tag, "json") == key) {
+					tag_value := reflect.struct_tag_get(field.tag, "json")
+					json_name, _ := json_name_from_tag_value(tag_value)
+					if (json_name == "" && field.name == key) || json_name == key {
 						offset = field.offset
 						type = field.type
 						found = true
@@ -508,6 +509,11 @@ unmarshal_object :: proc(p: ^Parser, v: any, end_token: Token_Kind) -> (err: Unm
 			}
 
 			if field_found {
+				field_test :: #force_inline proc "contextless" (field_used: [^]byte, offset: uintptr) -> bool {
+					prev_set := field_used[offset/8] & byte(offset&7) != 0
+					field_used[offset/8] |= byte(offset&7)
+					return prev_set
+				}
 				if field_test(field_used, offset) {
 					return .Multiple_Use_Field
 				}

+ 53 - 11
core/log/file_console_logger.odin

@@ -2,10 +2,12 @@
 #+build !orca
 package log
 
-import "core:encoding/ansi"
+import "base:runtime"
 import "core:fmt"
 import "core:strings"
 import "core:os"
+import "core:terminal"
+import "core:terminal/ansi"
 import "core:time"
 
 Level_Headers := [?]string{
@@ -37,11 +39,36 @@ File_Console_Logger_Data :: struct {
 	ident: string,
 }
 
+@(private) global_subtract_stdout_options: Options
+@(private) global_subtract_stderr_options: Options
+
+@(init, private)
+init_standard_stream_status :: proc() {
+	// NOTE(Feoramund): While it is technically possible for these streams to
+	// be redirected during the runtime of the program, the cost of checking on
+	// every single log message is not worth it to support such an
+	// uncommonly-used feature.
+	if terminal.color_enabled {
+		// This is done this way because it's possible that only one of these
+		// streams could be redirected to a file.
+		if !terminal.is_terminal(os.stdout) {
+			global_subtract_stdout_options = {.Terminal_Color}
+		}
+		if !terminal.is_terminal(os.stderr) {
+			global_subtract_stderr_options = {.Terminal_Color}
+		}
+	} else {
+		// Override any terminal coloring.
+		global_subtract_stdout_options = {.Terminal_Color}
+		global_subtract_stderr_options = {.Terminal_Color}
+	}
+}
+
 create_file_logger :: proc(h: os.Handle, lowest := Level.Debug, opt := Default_File_Logger_Opts, ident := "", allocator := context.allocator) -> Logger {
 	data := new(File_Console_Logger_Data, allocator)
 	data.file_handle = h
 	data.ident = ident
-	return Logger{file_console_logger_proc, data, lowest, opt}
+	return Logger{file_logger_proc, data, lowest, opt}
 }
 
 destroy_file_logger :: proc(log: Logger, allocator := context.allocator) {
@@ -56,19 +83,15 @@ create_console_logger :: proc(lowest := Level.Debug, opt := Default_Console_Logg
 	data := new(File_Console_Logger_Data, allocator)
 	data.file_handle = os.INVALID_HANDLE
 	data.ident = ident
-	return Logger{file_console_logger_proc, data, lowest, opt}
+	return Logger{console_logger_proc, data, lowest, opt}
 }
 
 destroy_console_logger :: proc(log: Logger, allocator := context.allocator) {
 	free(log.data, allocator)
 }
 
-file_console_logger_proc :: proc(logger_data: rawptr, level: Level, text: string, options: Options, location := #caller_location) {
-	data := cast(^File_Console_Logger_Data)logger_data
-	h: os.Handle = os.stdout if level <= Level.Error else os.stderr
-	if data.file_handle != os.INVALID_HANDLE {
-		h = data.file_handle
-	}
+@(private)
+_file_console_logger_proc :: proc(h: os.Handle, ident: string, level: Level, text: string, options: Options, location: runtime.Source_Code_Location) {
 	backing: [1024]byte //NOTE(Hoej): 1024 might be too much for a header backing, unless somebody has really long paths.
 	buf := strings.builder_from_bytes(backing[:])
 
@@ -86,13 +109,32 @@ file_console_logger_proc :: proc(logger_data: rawptr, level: Level, text: string
 		fmt.sbprintf(&buf, "[{}] ", os.current_thread_id())
 	}
 
-	if data.ident != "" {
-		fmt.sbprintf(&buf, "[%s] ", data.ident)
+	if ident != "" {
+		fmt.sbprintf(&buf, "[%s] ", ident)
 	}
 	//TODO(Hoej): When we have better atomics and such, make this thread-safe
 	fmt.fprintf(h, "%s%s\n", strings.to_string(buf), text)
 }
 
+file_logger_proc :: proc(logger_data: rawptr, level: Level, text: string, options: Options, location := #caller_location) {
+	data := cast(^File_Console_Logger_Data)logger_data
+	_file_console_logger_proc(data.file_handle, data.ident, level, text, options, location)
+}
+
+console_logger_proc :: proc(logger_data: rawptr, level: Level, text: string, options: Options, location := #caller_location) {
+	options := options
+	data := cast(^File_Console_Logger_Data)logger_data
+	h: os.Handle = ---
+	if level < Level.Error {
+		h = os.stdout
+		options -= global_subtract_stdout_options
+	} else {
+		h = os.stderr
+		options -= global_subtract_stderr_options
+	}
+	_file_console_logger_proc(h, data.ident, level, text, options, location)
+}
+
 do_level_header :: proc(opts: Options, str: ^strings.Builder, level: Level) {
 
 	RESET     :: ansi.CSI + ansi.RESET           + ansi.SGR

+ 20 - 20
core/odin/parser/parser.odin

@@ -1276,28 +1276,28 @@ parse_unrolled_for_loop :: proc(p: ^Parser, inline_tok: tokenizer.Token) -> ^ast
 			args = make([dynamic]^ast.Expr)
 			for p.curr_tok.kind != .Close_Paren &&
 			    p.curr_tok.kind != .EOF {
-			    	arg := parse_value(p)
-
-			    	if p.curr_tok.kind == .Eq {
-			    		eq := expect_token(p, .Eq)
-			    		if arg != nil {
-			    			if _, ok := arg.derived.(^ast.Ident); !ok {
-			    				error(p, arg.pos, "expected an identifier for 'key=value'")
-			    			}
-			    		}
-			    		value := parse_value(p)
-			    		fv := ast.new(ast.Field_Value, arg.pos, value)
-			    		fv.field = arg
-			    		fv.sep   = eq.pos
-			    		fv.value = value
-
-			    		arg = fv
-			    	}
-
-			    	append(&args, arg)
+				arg := parse_value(p)
+
+				if p.curr_tok.kind == .Eq {
+					eq := expect_token(p, .Eq)
+					if arg != nil {
+						if _, ok := arg.derived.(^ast.Ident); !ok {
+							error(p, arg.pos, "expected an identifier for 'key=value'")
+						}
+					}
+					value := parse_value(p)
+					fv := ast.new(ast.Field_Value, arg.pos, value)
+					fv.field = arg
+					fv.sep   = eq.pos
+					fv.value = value
+
+					arg = fv
+				}
+
+				append(&args, arg)
 
 				allow_token(p, .Comma) or_break
-			    }
+			}
 		}
 
 		p.expr_level -= 1

+ 42 - 41
core/os/os2/allocators.odin

@@ -8,43 +8,13 @@ file_allocator :: proc() -> runtime.Allocator {
 	return heap_allocator()
 }
 
-temp_allocator_proc :: runtime.arena_allocator_proc
-
 @(private="file")
 MAX_TEMP_ARENA_COUNT :: 2
-
+@(private="file")
+MAX_TEMP_ARENA_COLLISIONS :: MAX_TEMP_ARENA_COUNT - 1
 @(private="file", thread_local)
 global_default_temp_allocator_arenas: [MAX_TEMP_ARENA_COUNT]runtime.Arena
 
-@(private="file", thread_local)
-global_default_temp_allocator_index: uint
-
-
-@(require_results)
-temp_allocator :: proc() -> runtime.Allocator {
-	arena := &global_default_temp_allocator_arenas[global_default_temp_allocator_index]
-	if arena.backing_allocator.procedure == nil {
-		arena.backing_allocator = heap_allocator()
-	}
-
-	return runtime.Allocator{
-		procedure = temp_allocator_proc,
-		data      = arena,
-	}
-}
-
-
-
-@(require_results)
-temp_allocator_temp_begin :: proc(loc := #caller_location) -> (temp: runtime.Arena_Temp) {
-	temp = runtime.arena_temp_begin(&global_default_temp_allocator_arenas[global_default_temp_allocator_index], loc)
-	return
-}
-
-temp_allocator_temp_end :: proc(temp: runtime.Arena_Temp, loc := #caller_location) {
-	runtime.arena_temp_end(temp, loc)
-}
-
 @(fini, private)
 temp_allocator_fini :: proc() {
 	for &arena in global_default_temp_allocator_arenas {
@@ -53,18 +23,49 @@ temp_allocator_fini :: proc() {
 	global_default_temp_allocator_arenas = {}
 }
 
-TEMP_ALLOCATOR_GUARD_END :: proc(temp: runtime.Arena_Temp, loc := #caller_location) {
-	runtime.arena_temp_end(temp, loc)
-	if temp.arena != nil {
-		global_default_temp_allocator_index = (global_default_temp_allocator_index-1)%MAX_TEMP_ARENA_COUNT
-	}
+Temp_Allocator :: struct {
+	using arena: ^runtime.Arena,
+	using allocator: runtime.Allocator,
+	tmp: runtime.Arena_Temp,
+	loc: runtime.Source_Code_Location,
+}
+	
+TEMP_ALLOCATOR_GUARD_END :: proc(temp: Temp_Allocator) {
+	runtime.arena_temp_end(temp.tmp, temp.loc)
 }
 
 @(deferred_out=TEMP_ALLOCATOR_GUARD_END)
-TEMP_ALLOCATOR_GUARD :: #force_inline proc(loc := #caller_location) -> (runtime.Arena_Temp, runtime.Source_Code_Location) {
-	global_default_temp_allocator_index = (global_default_temp_allocator_index+1)%MAX_TEMP_ARENA_COUNT
-	tmp := temp_allocator_temp_begin(loc)
-	return tmp, loc
+TEMP_ALLOCATOR_GUARD :: #force_inline proc(collisions: []runtime.Allocator, loc := #caller_location) -> Temp_Allocator {
+	assert(len(collisions) <= MAX_TEMP_ARENA_COLLISIONS, "Maximum collision count exceeded. MAX_TEMP_ARENA_COUNT must be increased!")
+	good_arena: ^runtime.Arena
+	for i in 0..<MAX_TEMP_ARENA_COUNT {
+		good_arena = &global_default_temp_allocator_arenas[i]
+		for c in collisions {
+			if good_arena == c.data {
+				good_arena = nil
+			}
+		}
+		if good_arena != nil {
+			break
+		}
+	}
+	assert(good_arena != nil)
+	if good_arena.backing_allocator.procedure == nil {
+		good_arena.backing_allocator = heap_allocator()
+	}
+	tmp := runtime.arena_temp_begin(good_arena, loc)
+	return { good_arena, runtime.arena_allocator(good_arena), tmp, loc }
+}
+
+temp_allocator_begin :: runtime.arena_temp_begin
+temp_allocator_end :: runtime.arena_temp_end
+@(deferred_out=_temp_allocator_end)
+temp_allocator_scope :: proc(tmp: Temp_Allocator) -> (runtime.Arena_Temp) {
+	return temp_allocator_begin(tmp.arena)
+}
+@(private="file")
+_temp_allocator_end :: proc(tmp: runtime.Arena_Temp) {
+	temp_allocator_end(tmp)
 }
 
 @(init, private)

+ 44 - 17
core/os/os2/dir.odin

@@ -2,6 +2,7 @@ package os2
 
 import "base:runtime"
 import "core:slice"
+import "core:strings"
 
 read_dir :: read_directory
 
@@ -18,12 +19,12 @@ read_directory :: proc(f: ^File, n: int, allocator: runtime.Allocator) -> (files
 		size = 100
 	}
 
-	TEMP_ALLOCATOR_GUARD()
+	temp_allocator := TEMP_ALLOCATOR_GUARD({ allocator })
 
 	it := read_directory_iterator_create(f)
 	defer _read_directory_iterator_destroy(&it)
 
-	dfi := make([dynamic]File_Info, 0, size, temp_allocator())
+	dfi := make([dynamic]File_Info, 0, size, temp_allocator)
 	defer if err != nil {
 		for fi in dfi {
 			file_info_delete(fi, allocator)
@@ -194,28 +195,54 @@ read_directory_iterator :: proc(it: ^Read_Directory_Iterator) -> (fi: File_Info,
 }
 
 // Recursively copies a directory to `dst` from `src`
-copy_directory :: proc(dst, src: string, dst_perm := 0o755) -> Error {
-	switch err := make_directory_all(dst, dst_perm); err {
-	case nil, .Exist:
-		// okay
-	case:
+copy_directory_all :: proc(dst, src: string, dst_perm := 0o755) -> Error {
+	when #defined(_copy_directory_all_native) {
+		return _copy_directory_all_native(dst, src, dst_perm)
+	} else {
+		return _copy_directory_all(dst, src, dst_perm)
+	}
+}
+
+@(private)
+_copy_directory_all :: proc(dst, src: string, dst_perm := 0o755) -> Error {
+	err := make_directory(dst, dst_perm)
+	if err != nil && err != .Exist {
 		return err
 	}
 
-	TEMP_ALLOCATOR_GUARD()
+	temp_allocator := TEMP_ALLOCATOR_GUARD({})
+
+	abs_src := get_absolute_path(src, temp_allocator) or_return
+	abs_dst := get_absolute_path(dst, temp_allocator) or_return
+
+	dst_buf := make([dynamic]byte, 0, len(abs_dst) + 256, temp_allocator) or_return
 
-	file_infos := read_all_directory_by_path(src, temp_allocator()) or_return
-	for fi in file_infos {
-		TEMP_ALLOCATOR_GUARD()
+	w: Walker
+	walker_init_path(&w, src)
+	defer walker_destroy(&w)
 
-		dst_path := join_path({dst, fi.name}, temp_allocator()) or_return
-		src_path := fi.fullpath
+	for info in walker_walk(&w) {
+		_ = walker_error(&w) or_break
 
-		if fi.type == .Directory {
-			copy_directory(dst_path, src_path) or_return
+		rel := strings.trim_prefix(info.fullpath, abs_src)
+
+		non_zero_resize(&dst_buf, 0)
+		reserve(&dst_buf, len(abs_dst) + len(Path_Separator_String) + len(rel)) or_return
+		append(&dst_buf, abs_dst)
+		append(&dst_buf, Path_Separator_String)
+		append(&dst_buf, rel)
+
+		if info.type == .Directory {
+			err = make_directory(string(dst_buf[:]), dst_perm)
+			if err != nil && err != .Exist {
+				return err
+			}
 		} else {
-			copy_file(dst_path, src_path) or_return
+			copy_file(string(dst_buf[:]), info.fullpath) or_return
 		}
 	}
+
+	_ = walker_error(&w) or_return
+
 	return nil
-}
+}

+ 2 - 1
core/os/os2/dir_linux.odin

@@ -78,7 +78,8 @@ _read_directory_iterator :: proc(it: ^Read_Directory_Iterator) -> (fi: File_Info
 	it.impl.prev_fi = fi
 
 	if err != nil {
-		path, _ := _get_full_path(entry_fd, temp_allocator())
+		temp_allocator := TEMP_ALLOCATOR_GUARD({})
+		path, _ := _get_full_path(entry_fd, temp_allocator)
 		read_directory_iterator_set_error(it, path, err)
 	}
 

+ 17 - 0
core/os/os2/dir_posix_darwin.odin

@@ -0,0 +1,17 @@
+#+private
+package os2
+
+import "core:sys/darwin"
+
+_copy_directory_all_native :: proc(dst, src: string, dst_perm := 0o755) -> (err: Error) {
+	temp_allocator := TEMP_ALLOCATOR_GUARD({})
+
+	csrc := clone_to_cstring(src, temp_allocator) or_return
+	cdst := clone_to_cstring(dst, temp_allocator) or_return
+
+	if darwin.copyfile(csrc, cdst, nil, darwin.COPYFILE_ALL + {.RECURSIVE}) < 0 {
+		err = _get_platform_error()
+	}
+
+	return
+}

+ 5 - 5
core/os/os2/dir_windows.odin

@@ -14,7 +14,9 @@ find_data_to_file_info :: proc(base_path: string, d: ^win32.WIN32_FIND_DATAW, al
 	if d.cFileName[0] == '.' && d.cFileName[1] == '.' && d.cFileName[2] == 0 {
 		return
 	}
-	path := concatenate({base_path, `\`, win32_wstring_to_utf8(raw_data(d.cFileName[:]), temp_allocator()) or_else ""}, allocator) or_return
+
+	temp_allocator := TEMP_ALLOCATOR_GUARD({ allocator })
+	path := concatenate({base_path, `\`, win32_wstring_to_utf8(raw_data(d.cFileName[:]), temp_allocator) or_else ""}, allocator) or_return
 
 	handle := win32.HANDLE(_open_internal(path, {.Read}, 0o666) or_else 0)
 	defer win32.CloseHandle(handle)
@@ -49,8 +51,6 @@ Read_Directory_Iterator_Impl :: struct {
 
 @(require_results)
 _read_directory_iterator :: proc(it: ^Read_Directory_Iterator) -> (fi: File_Info, index: int, ok: bool) {
-	TEMP_ALLOCATOR_GUARD()
-
 	for !it.impl.no_more_files {
 		err: Error
 		file_info_delete(it.impl.prev_fi, file_allocator())
@@ -116,9 +116,9 @@ _read_directory_iterator_init :: proc(it: ^Read_Directory_Iterator, f: ^File) {
 		wpath = impl.wname[:i]
 	}
 
-	TEMP_ALLOCATOR_GUARD()
+	temp_allocator := TEMP_ALLOCATOR_GUARD({})
 
-	wpath_search := make([]u16, len(wpath)+3, temp_allocator())
+	wpath_search := make([]u16, len(wpath)+3, temp_allocator)
 	copy(wpath_search, wpath)
 	wpath_search[len(wpath)+0] = '\\'
 	wpath_search[len(wpath)+1] = '*'

+ 7 - 7
core/os/os2/env_posix.odin

@@ -12,9 +12,9 @@ _lookup_env :: proc(key: string, allocator: runtime.Allocator) -> (value: string
 		return
 	}
 
-	TEMP_ALLOCATOR_GUARD()
+	temp_allocator := TEMP_ALLOCATOR_GUARD({ allocator })
 
-	ckey := strings.clone_to_cstring(key, temp_allocator())
+	ckey := strings.clone_to_cstring(key, temp_allocator)
 	cval := posix.getenv(ckey)
 	if cval == nil {
 		return
@@ -27,10 +27,10 @@ _lookup_env :: proc(key: string, allocator: runtime.Allocator) -> (value: string
 }
 
 _set_env :: proc(key, value: string) -> (err: Error) {
-	TEMP_ALLOCATOR_GUARD()
+	temp_allocator := TEMP_ALLOCATOR_GUARD({})
 
-	ckey := strings.clone_to_cstring(key, temp_allocator()) or_return
-	cval := strings.clone_to_cstring(value, temp_allocator()) or_return
+	ckey := strings.clone_to_cstring(key, temp_allocator) or_return
+	cval := strings.clone_to_cstring(value, temp_allocator) or_return
 
 	if posix.setenv(ckey, cval, true) != nil {
 		err = _get_platform_error_from_errno()
@@ -39,9 +39,9 @@ _set_env :: proc(key, value: string) -> (err: Error) {
 }
 
 _unset_env :: proc(key: string) -> (ok: bool) {
-	TEMP_ALLOCATOR_GUARD()
+	temp_allocator := TEMP_ALLOCATOR_GUARD({})
 
-	ckey := strings.clone_to_cstring(key, temp_allocator())
+	ckey := strings.clone_to_cstring(key, temp_allocator)
 
 	ok = posix.unsetenv(ckey) == .OK
 	return

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

@@ -39,9 +39,9 @@ build_env :: proc() -> (err: Error) {
 	g_env_buf = make([]byte, size_of_envs, file_allocator()) or_return
 	defer if err != nil { delete(g_env_buf, file_allocator()) }
 
-	TEMP_ALLOCATOR_GUARD()
+	temp_allocator := TEMP_ALLOCATOR_GUARD({})
 
-	envs := make([]cstring, num_envs, temp_allocator()) or_return
+	envs := make([]cstring, num_envs, temp_allocator) or_return
 
 	_err = wasi.environ_get(raw_data(envs), raw_data(g_env_buf))
 	if _err != nil {

+ 10 - 10
core/os/os2/env_windows.odin

@@ -8,8 +8,8 @@ _lookup_env :: proc(key: string, allocator: runtime.Allocator) -> (value: string
 	if key == "" {
 		return
 	}
-	TEMP_ALLOCATOR_GUARD()
-	wkey, _ := win32_utf8_to_wstring(key, temp_allocator())
+	temp_allocator := TEMP_ALLOCATOR_GUARD({ allocator })
+	wkey, _ := win32_utf8_to_wstring(key, temp_allocator)
 
 	n := win32.GetEnvironmentVariableW(wkey, nil, 0)
 	if n == 0 {
@@ -20,7 +20,7 @@ _lookup_env :: proc(key: string, allocator: runtime.Allocator) -> (value: string
 		return "", true
 	}
 
-	b := make([]u16, n+1, temp_allocator())
+	b := make([]u16, n+1, temp_allocator)
 
 	n = win32.GetEnvironmentVariableW(wkey, raw_data(b), u32(len(b)))
 	if n == 0 {
@@ -37,9 +37,9 @@ _lookup_env :: proc(key: string, allocator: runtime.Allocator) -> (value: string
 }
 
 _set_env :: proc(key, value: string) -> Error {
-	TEMP_ALLOCATOR_GUARD()
-	k := win32_utf8_to_wstring(key,   temp_allocator()) or_return
-	v := win32_utf8_to_wstring(value, temp_allocator()) or_return
+	temp_allocator := TEMP_ALLOCATOR_GUARD({})
+	k := win32_utf8_to_wstring(key,   temp_allocator) or_return
+	v := win32_utf8_to_wstring(value, temp_allocator) or_return
 
 	if !win32.SetEnvironmentVariableW(k, v) {
 		return _get_platform_error()
@@ -48,14 +48,14 @@ _set_env :: proc(key, value: string) -> Error {
 }
 
 _unset_env :: proc(key: string) -> bool {
-	TEMP_ALLOCATOR_GUARD()
-	k, _ := win32_utf8_to_wstring(key, temp_allocator())
+	temp_allocator := TEMP_ALLOCATOR_GUARD({})
+	k, _ := win32_utf8_to_wstring(key, temp_allocator)
 	return bool(win32.SetEnvironmentVariableW(k, nil))
 }
 
 _clear_env :: proc() {
-	TEMP_ALLOCATOR_GUARD()
-	envs, _ := environ(temp_allocator())
+	temp_allocator := TEMP_ALLOCATOR_GUARD({})
+	envs, _ := environ(temp_allocator)
 	for env in envs {
 		for j in 1..<len(env) {
 			if env[j] == '=' {

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

@@ -108,12 +108,12 @@ error_string :: proc(ferr: Error) -> string {
 }
 
 print_error :: proc(f: ^File, ferr: Error, msg: string) {
-	TEMP_ALLOCATOR_GUARD()
+	temp_allocator := TEMP_ALLOCATOR_GUARD({})
 	err_str := error_string(ferr)
 
 	// msg + ": " + err_str + '\n'
 	length := len(msg) + 2 + len(err_str) + 1
-	buf := make([]u8, length, temp_allocator())
+	buf := make([]u8, length, temp_allocator)
 
 	copy(buf, msg)
 	buf[len(msg)] = ':'

+ 13 - 4
core/os/os2/file.odin

@@ -291,8 +291,8 @@ exists :: proc(path: string) -> bool {
 
 @(require_results)
 is_file :: proc(path: string) -> bool {
-	TEMP_ALLOCATOR_GUARD()
-	fi, err := stat(path, temp_allocator())
+	temp_allocator := TEMP_ALLOCATOR_GUARD({})
+	fi, err := stat(path, temp_allocator)
 	if err != nil {
 		return false
 	}
@@ -303,8 +303,8 @@ is_dir :: is_directory
 
 @(require_results)
 is_directory :: proc(path: string) -> bool {
-	TEMP_ALLOCATOR_GUARD()
-	fi, err := stat(path, temp_allocator())
+	temp_allocator := TEMP_ALLOCATOR_GUARD({})
+	fi, err := stat(path, temp_allocator)
 	if err != nil {
 		return false
 	}
@@ -313,6 +313,15 @@ is_directory :: proc(path: string) -> bool {
 
 
 copy_file :: proc(dst_path, src_path: string) -> Error {
+	when #defined(_copy_file_native) {
+		return _copy_file_native(dst_path, src_path)
+	} else {
+		return _copy_file(dst_path, src_path)
+	}
+}
+
+@(private)
+_copy_file :: proc(dst_path, src_path: string) -> Error {
 	src := open(src_path) or_return
 	defer close(src)
 

+ 29 - 29
core/os/os2/file_linux.odin

@@ -66,8 +66,8 @@ _standard_stream_init :: proc() {
 }
 
 _open :: proc(name: string, flags: File_Flags, perm: int) -> (f: ^File, err: Error) {
-	TEMP_ALLOCATOR_GUARD()
-	name_cstr := temp_cstring(name) or_return
+	temp_allocator := TEMP_ALLOCATOR_GUARD({})
+	name_cstr := clone_to_cstring(name, temp_allocator) or_return
 
 	// Just default to using O_NOCTTY because needing to open a controlling
 	// terminal would be incredibly rare. This has no effect on files while
@@ -299,8 +299,8 @@ _truncate :: proc(f: ^File, size: i64) -> Error {
 }
 
 _remove :: proc(name: string) -> Error {
-	TEMP_ALLOCATOR_GUARD()
-	name_cstr := temp_cstring(name) or_return
+	temp_allocator := TEMP_ALLOCATOR_GUARD({})
+	name_cstr := clone_to_cstring(name, temp_allocator) or_return
 
 	if fd, errno := linux.open(name_cstr, _OPENDIR_FLAGS + {.NOFOLLOW}); errno == .NONE {
 		linux.close(fd)
@@ -311,25 +311,25 @@ _remove :: proc(name: string) -> Error {
 }
 
 _rename :: proc(old_name, new_name: string) -> Error {
-	TEMP_ALLOCATOR_GUARD()
-	old_name_cstr := temp_cstring(old_name) or_return
-	new_name_cstr := temp_cstring(new_name) or_return
+	temp_allocator := TEMP_ALLOCATOR_GUARD({})
+	old_name_cstr := clone_to_cstring(old_name, temp_allocator) or_return
+	new_name_cstr := clone_to_cstring(new_name, temp_allocator) or_return
 
 	return _get_platform_error(linux.rename(old_name_cstr, new_name_cstr))
 }
 
 _link :: proc(old_name, new_name: string) -> Error {
-	TEMP_ALLOCATOR_GUARD()
-	old_name_cstr := temp_cstring(old_name) or_return
-	new_name_cstr := temp_cstring(new_name) or_return
+	temp_allocator := TEMP_ALLOCATOR_GUARD({})
+	old_name_cstr := clone_to_cstring(old_name, temp_allocator) or_return
+	new_name_cstr := clone_to_cstring(new_name, temp_allocator) or_return
 
 	return _get_platform_error(linux.link(old_name_cstr, new_name_cstr))
 }
 
 _symlink :: proc(old_name, new_name: string) -> Error {
-	TEMP_ALLOCATOR_GUARD()
-	old_name_cstr := temp_cstring(old_name) or_return
-	new_name_cstr := temp_cstring(new_name) or_return
+	temp_allocator := TEMP_ALLOCATOR_GUARD({})
+	old_name_cstr := clone_to_cstring(old_name, temp_allocator) or_return
+	new_name_cstr := clone_to_cstring(new_name, temp_allocator) or_return
 	return _get_platform_error(linux.symlink(old_name_cstr, new_name_cstr))
 }
 
@@ -352,14 +352,14 @@ _read_link_cstr :: proc(name_cstr: cstring, allocator: runtime.Allocator) -> (st
 }
 
 _read_link :: proc(name: string, allocator: runtime.Allocator) -> (s: string, e: Error) {
-	TEMP_ALLOCATOR_GUARD()
-	name_cstr := temp_cstring(name) or_return
+	temp_allocator := TEMP_ALLOCATOR_GUARD({ allocator })
+	name_cstr := clone_to_cstring(name, temp_allocator) or_return
 	return _read_link_cstr(name_cstr, allocator)
 }
 
 _chdir :: proc(name: string) -> Error {
-	TEMP_ALLOCATOR_GUARD()
-	name_cstr := temp_cstring(name) or_return
+	temp_allocator := TEMP_ALLOCATOR_GUARD({})
+	name_cstr := clone_to_cstring(name, temp_allocator) or_return
 	return _get_platform_error(linux.chdir(name_cstr))
 }
 
@@ -369,8 +369,8 @@ _fchdir :: proc(f: ^File) -> Error {
 }
 
 _chmod :: proc(name: string, mode: int) -> Error {
-	TEMP_ALLOCATOR_GUARD()
-	name_cstr := temp_cstring(name) or_return
+	temp_allocator := TEMP_ALLOCATOR_GUARD({})
+	name_cstr := clone_to_cstring(name, temp_allocator) or_return
 	return _get_platform_error(linux.chmod(name_cstr, transmute(linux.Mode)(u32(mode))))
 }
 
@@ -381,15 +381,15 @@ _fchmod :: proc(f: ^File, mode: int) -> Error {
 
 // NOTE: will throw error without super user priviledges
 _chown :: proc(name: string, uid, gid: int) -> Error {
-	TEMP_ALLOCATOR_GUARD()
-	name_cstr := temp_cstring(name) or_return
+	temp_allocator := TEMP_ALLOCATOR_GUARD({})
+	name_cstr := clone_to_cstring(name, temp_allocator) or_return
 	return _get_platform_error(linux.chown(name_cstr, linux.Uid(uid), linux.Gid(gid)))
 }
 
 // NOTE: will throw error without super user priviledges
 _lchown :: proc(name: string, uid, gid: int) -> Error {
-	TEMP_ALLOCATOR_GUARD()
-	name_cstr := temp_cstring(name) or_return
+	temp_allocator := TEMP_ALLOCATOR_GUARD({})
+	name_cstr := clone_to_cstring(name, temp_allocator) or_return
 	return _get_platform_error(linux.lchown(name_cstr, linux.Uid(uid), linux.Gid(gid)))
 }
 
@@ -400,8 +400,8 @@ _fchown :: proc(f: ^File, uid, gid: int) -> Error {
 }
 
 _chtimes :: proc(name: string, atime, mtime: time.Time) -> Error {
-	TEMP_ALLOCATOR_GUARD()
-	name_cstr := temp_cstring(name) or_return
+	temp_allocator := TEMP_ALLOCATOR_GUARD({})
+	name_cstr := clone_to_cstring(name, temp_allocator) or_return
 	times := [2]linux.Time_Spec {
 		{
 			uint(atime._nsec) / uint(time.Second),
@@ -431,8 +431,8 @@ _fchtimes :: proc(f: ^File, atime, mtime: time.Time) -> Error {
 }
 
 _exists :: proc(name: string) -> bool {
-	TEMP_ALLOCATOR_GUARD()
-	name_cstr, _ := temp_cstring(name)
+	temp_allocator := TEMP_ALLOCATOR_GUARD({})
+	name_cstr, _ := clone_to_cstring(name, temp_allocator)
 	return linux.access(name_cstr, linux.F_OK) == .NONE
 }
 
@@ -440,8 +440,8 @@ _exists :: proc(name: string) -> bool {
 _read_entire_pseudo_file :: proc { _read_entire_pseudo_file_string, _read_entire_pseudo_file_cstring }
 
 _read_entire_pseudo_file_string :: proc(name: string, allocator: runtime.Allocator) -> (b: []u8, e: Error) {
-	TEMP_ALLOCATOR_GUARD()
-	name_cstr := clone_to_cstring(name, temp_allocator()) or_return
+	temp_allocator := TEMP_ALLOCATOR_GUARD({ allocator })
+	name_cstr := clone_to_cstring(name, temp_allocator) or_return
 	return _read_entire_pseudo_file_cstring(name_cstr, allocator)
 }
 

+ 36 - 35
core/os/os2/file_posix.odin

@@ -69,8 +69,8 @@ _open :: proc(name: string, flags: File_Flags, perm: int) -> (f: ^File, err: Err
 	if .Trunc       in flags { sys_flags += {.TRUNC} }
 	if .Inheritable in flags { sys_flags -= {.CLOEXEC} }
 
-	TEMP_ALLOCATOR_GUARD()
-	cname := temp_cstring(name)
+	temp_allocator := TEMP_ALLOCATOR_GUARD({})
+	cname := clone_to_cstring(name, temp_allocator) or_return
 
 	fd := posix.open(cname, sys_flags, transmute(posix.mode_t)posix._mode_t(perm))
 	if fd < 0 {
@@ -183,39 +183,39 @@ _truncate :: proc(f: ^File, size: i64) -> Error {
 	return nil
 }
 
-_remove :: proc(name: string) -> Error {
-	TEMP_ALLOCATOR_GUARD()
-	cname := temp_cstring(name)
+_remove :: proc(name: string) -> (err: Error) {
+	temp_allocator := TEMP_ALLOCATOR_GUARD({})
+	cname := clone_to_cstring(name, temp_allocator) or_return
 	if posix.remove(cname) != 0 {
 		return _get_platform_error()
 	}
 	return nil
 }
 
-_rename :: proc(old_path, new_path: string) -> Error {
-	TEMP_ALLOCATOR_GUARD()
-	cold := temp_cstring(old_path)
-	cnew := temp_cstring(new_path)
+_rename :: proc(old_path, new_path: string) -> (err: Error) {
+	temp_allocator := TEMP_ALLOCATOR_GUARD({})
+	cold := clone_to_cstring(old_path, temp_allocator) or_return
+	cnew := clone_to_cstring(new_path, temp_allocator) or_return
 	if posix.rename(cold, cnew) != 0 {
 		return _get_platform_error()
 	}
 	return nil
 }
 
-_link :: proc(old_name, new_name: string) -> Error {
-	TEMP_ALLOCATOR_GUARD()
-	cold := temp_cstring(old_name)
-	cnew := temp_cstring(new_name)
+_link :: proc(old_name, new_name: string) -> (err: Error) {
+	temp_allocator := TEMP_ALLOCATOR_GUARD({})
+	cold := clone_to_cstring(old_name, temp_allocator) or_return
+	cnew := clone_to_cstring(new_name, temp_allocator) or_return
 	if posix.link(cold, cnew) != .OK {
 		return _get_platform_error()
 	}
 	return nil
 }
 
-_symlink :: proc(old_name, new_name: string) -> Error {
-	TEMP_ALLOCATOR_GUARD()
-	cold := temp_cstring(old_name)
-	cnew := temp_cstring(new_name)
+_symlink :: proc(old_name, new_name: string) -> (err: Error) {
+	temp_allocator := TEMP_ALLOCATOR_GUARD({})
+	cold := clone_to_cstring(old_name, temp_allocator) or_return
+	cnew := clone_to_cstring(new_name, temp_allocator) or_return
 	if posix.symlink(cold, cnew) != .OK {
 		return _get_platform_error()
 	}
@@ -223,8 +223,8 @@ _symlink :: proc(old_name, new_name: string) -> Error {
 }
 
 _read_link :: proc(name: string, allocator: runtime.Allocator) -> (s: string, err: Error) {
-	TEMP_ALLOCATOR_GUARD()
-	cname := temp_cstring(name)
+	temp_allocator := TEMP_ALLOCATOR_GUARD({ allocator })
+	cname := clone_to_cstring(name, temp_allocator) or_return
 
 	buf: [dynamic]byte
 	buf.allocator = allocator
@@ -268,9 +268,9 @@ _read_link :: proc(name: string, allocator: runtime.Allocator) -> (s: string, er
 	}
 }
 
-_chdir :: proc(name: string) -> Error {
-	TEMP_ALLOCATOR_GUARD()
-	cname := temp_cstring(name)
+_chdir :: proc(name: string) -> (err: Error) {
+	temp_allocator := TEMP_ALLOCATOR_GUARD({})
+	cname := clone_to_cstring(name, temp_allocator) or_return
 	if posix.chdir(cname) != .OK {
 		return _get_platform_error()
 	}
@@ -291,9 +291,9 @@ _fchmod :: proc(f: ^File, mode: int) -> Error {
 	return nil
 }
 
-_chmod :: proc(name: string, mode: int) -> Error {
-	TEMP_ALLOCATOR_GUARD()
-	cname := temp_cstring(name)
+_chmod :: proc(name: string, mode: int) -> (err: Error) {
+	temp_allocator := TEMP_ALLOCATOR_GUARD({})
+	cname := clone_to_cstring(name, temp_allocator) or_return
 	if posix.chmod(cname, transmute(posix.mode_t)posix._mode_t(mode)) != .OK {
 		return _get_platform_error()
 	}
@@ -307,9 +307,9 @@ _fchown :: proc(f: ^File, uid, gid: int) -> Error {
 	return nil
 }
 
-_chown :: proc(name: string, uid, gid: int) -> Error {
-	TEMP_ALLOCATOR_GUARD()
-	cname := temp_cstring(name)
+_chown :: proc(name: string, uid, gid: int) -> (err: Error) {
+	temp_allocator := TEMP_ALLOCATOR_GUARD({})
+	cname := clone_to_cstring(name, temp_allocator) or_return
 	if posix.chown(cname, posix.uid_t(uid), posix.gid_t(gid)) != .OK {
 		return _get_platform_error()
 	}
@@ -317,15 +317,15 @@ _chown :: proc(name: string, uid, gid: int) -> Error {
 }
 
 _lchown :: proc(name: string, uid, gid: int) -> Error {
-	TEMP_ALLOCATOR_GUARD()
-	cname := temp_cstring(name)
+	temp_allocator := TEMP_ALLOCATOR_GUARD({})
+	cname := clone_to_cstring(name, temp_allocator) or_return
 	if posix.lchown(cname, posix.uid_t(uid), posix.gid_t(gid)) != .OK {
 		return _get_platform_error()
 	}
 	return nil
 }
 
-_chtimes :: proc(name: string, atime, mtime: time.Time) -> Error {
+_chtimes :: proc(name: string, atime, mtime: time.Time) -> (err: Error) {
 	times := [2]posix.timeval{
 		{
 			tv_sec  = posix.time_t(atime._nsec/1e9),           /* seconds */
@@ -337,8 +337,8 @@ _chtimes :: proc(name: string, atime, mtime: time.Time) -> Error {
 		},
 	}
 
-	TEMP_ALLOCATOR_GUARD()
-	cname := temp_cstring(name)
+	temp_allocator := TEMP_ALLOCATOR_GUARD({})
+	cname := clone_to_cstring(name, temp_allocator) or_return
 
 	if posix.utimes(cname, &times) != .OK {
 		return _get_platform_error()
@@ -365,8 +365,9 @@ _fchtimes :: proc(f: ^File, atime, mtime: time.Time) -> Error {
 }
 
 _exists :: proc(path: string) -> bool {
-	TEMP_ALLOCATOR_GUARD()
-	cpath := temp_cstring(path)
+	temp_allocator := TEMP_ALLOCATOR_GUARD({})
+	cpath, err := clone_to_cstring(path, temp_allocator)
+	if err != nil { return false }
 	return posix.access(cpath) == .OK
 }
 

+ 28 - 0
core/os/os2/file_posix_darwin.odin

@@ -3,6 +3,7 @@ package os2
 
 import "base:runtime"
 
+import "core:sys/darwin"
 import "core:sys/posix"
 
 _posix_absolute_path :: proc(fd: posix.FD, name: string, allocator: runtime.Allocator) -> (path: cstring, err: Error) {
@@ -16,3 +17,30 @@ _posix_absolute_path :: proc(fd: posix.FD, name: string, allocator: runtime.Allo
 
 	return clone_to_cstring(string(cstring(&buf[0])), allocator)
 }
+
+_copy_file_native :: proc(dst_path, src_path: string) -> (err: Error) {
+	temp_allocator := TEMP_ALLOCATOR_GUARD({})
+
+	csrc := clone_to_cstring(src_path, temp_allocator) or_return
+	cdst := clone_to_cstring(dst_path, temp_allocator) or_return
+
+	// Disallow directories, as specified by the generic implementation.
+
+	stat: posix.stat_t
+	if posix.stat(csrc, &stat) != .OK {
+		err = _get_platform_error()
+		return
+	}
+
+	if posix.S_ISDIR(stat.st_mode) {
+		err = .Invalid_File
+		return
+	}
+
+	ret := darwin.copyfile(csrc, cdst, nil, darwin.COPYFILE_ALL)
+	if ret < 0 {
+		err = _get_platform_error()
+	}
+
+	return
+}

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

@@ -7,8 +7,8 @@ import "base:runtime"
 import "core:sys/posix"
 
 _posix_absolute_path :: proc(fd: posix.FD, name: string, allocator: runtime.Allocator) -> (path: cstring, err: Error) {
-	TEMP_ALLOCATOR_GUARD()
-	cname := temp_cstring(name)
+	temp_allocator := TEMP_ALLOCATOR_GUARD({ allocator })
+	cname := clone_to_cstring(name, temp_allocator)
 
 	buf: [posix.PATH_MAX]byte
 	path = posix.realpath(cname, raw_data(buf[:]))

+ 32 - 45
core/os/os2/file_windows.odin

@@ -12,30 +12,7 @@ import win32 "core:sys/windows"
 
 INVALID_HANDLE :: ~uintptr(0)
 
-// NOTE(Jeroen): We don't translate mode flags for Linux when given to `chmod`.
-//               Let's not do so for Windows for `chmod` or `read_directory_iterator` either.
-//               They're *not* portable between Windows and non-Windows platforms.
-//
-//               It also leads to information loss as flags like Archive, Hidden and System have no equivalent there.
-//               We can of course parse them so we can set the `.Symlink` and `.Directory` type, but we shouldn't pretend
-//               that 0o644 is meaningful when returned as a mode.
-//               `C:\bootmgr` as an example has attributes read only, hidden, system, archive. In no way is it sensible to replace that with 0o444.
-FILE_ATTRIBUTE_READONLY            :: win32.FILE_ATTRIBUTE_READONLY            // 0x00000001
-FILE_ATTRIBUTE_HIDDEN              :: win32.FILE_ATTRIBUTE_HIDDEN              // 0x00000002
-FILE_ATTRIBUTE_SYSTEM              :: win32.FILE_ATTRIBUTE_SYSTEM              // 0x00000004
-FILE_ATTRIBUTE_DIRECTORY           :: win32.FILE_ATTRIBUTE_DIRECTORY           // 0x00000010
-FILE_ATTRIBUTE_ARCHIVE             :: win32.FILE_ATTRIBUTE_ARCHIVE             // 0x00000020
-FILE_ATTRIBUTE_DEVICE              :: win32.FILE_ATTRIBUTE_DEVICE              // 0x00000040
-FILE_ATTRIBUTE_NORMAL              :: win32.FILE_ATTRIBUTE_NORMAL              // 0x00000080
-FILE_ATTRIBUTE_TEMPORARY           :: win32.FILE_ATTRIBUTE_TEMPORARY           // 0x00000100
-FILE_ATTRIBUTE_SPARSE_FILE         :: win32.FILE_ATTRIBUTE_SPARSE_FILE         // 0x00000200
-FILE_ATTRIBUTE_REPARSE_Point       :: win32.FILE_ATTRIBUTE_REPARSE_Point       // 0x00000400
-FILE_ATTRIBUTE_REPARSE_POINT       :: win32.FILE_ATTRIBUTE_REPARSE_POINT       // 0x00000400
-FILE_ATTRIBUTE_COMPRESSED          :: win32.FILE_ATTRIBUTE_COMPRESSED          // 0x00000800
-FILE_ATTRIBUTE_OFFLINE             :: win32.FILE_ATTRIBUTE_OFFLINE             // 0x00001000
-FILE_ATTRIBUTE_NOT_CONTENT_INDEXED :: win32.FILE_ATTRIBUTE_NOT_CONTENT_INDEXED // 0x00002000
-FILE_ATTRIBUTE_ENCRYPTED           :: win32.FILE_ATTRIBUTE_ENCRYPTED           // 0x00004000
-
+S_IWRITE :: 0o200
 _ERROR_BAD_NETPATH :: 53
 MAX_RW :: 1<<30
 
@@ -109,9 +86,9 @@ _open_internal :: proc(name: string, flags: File_Flags, perm: int) -> (handle: u
 		err = .Not_Exist
 		return
 	}
-	TEMP_ALLOCATOR_GUARD()
+	temp_allocator := TEMP_ALLOCATOR_GUARD({})
 
-	path := _fix_long_path(name, temp_allocator()) or_return
+	path := _fix_long_path(name, temp_allocator) or_return
 	access: u32
 	switch flags & {.Read, .Write} {
 	case {.Read}:         access = win32.FILE_GENERIC_READ
@@ -145,7 +122,7 @@ _open_internal :: proc(name: string, flags: File_Flags, perm: int) -> (handle: u
 	}
 
 	attrs: u32 = win32.FILE_ATTRIBUTE_NORMAL|win32.FILE_FLAG_BACKUP_SEMANTICS
-	if u32(perm) & FILE_ATTRIBUTE_NORMAL == 0 {
+	if perm & S_IWRITE == 0 {
 		attrs = win32.FILE_ATTRIBUTE_READONLY
 		if create_mode == win32.CREATE_ALWAYS {
 			// NOTE(bill): Open has just asked to create a file in read-only mode.
@@ -580,8 +557,8 @@ _truncate :: proc(f: ^File, size: i64) -> Error {
 }
 
 _remove :: proc(name: string) -> Error {
-	TEMP_ALLOCATOR_GUARD()
-	p := _fix_long_path(name, temp_allocator()) or_return
+	temp_allocator := TEMP_ALLOCATOR_GUARD({})
+	p := _fix_long_path(name, temp_allocator) or_return
 	err, err1: Error
 	if !win32.DeleteFileW(p) {
 		err = _get_platform_error()
@@ -618,9 +595,9 @@ _remove :: proc(name: string) -> Error {
 }
 
 _rename :: proc(old_path, new_path: string) -> Error {
-	TEMP_ALLOCATOR_GUARD()
-	from := _fix_long_path(old_path, temp_allocator()) or_return
-	to   := _fix_long_path(new_path, temp_allocator()) or_return
+	temp_allocator := TEMP_ALLOCATOR_GUARD({})
+	from := _fix_long_path(old_path, temp_allocator) or_return
+	to   := _fix_long_path(new_path, temp_allocator) or_return
 	if win32.MoveFileExW(from, to, win32.MOVEFILE_REPLACE_EXISTING) {
 		return nil
 	}
@@ -629,9 +606,9 @@ _rename :: proc(old_path, new_path: string) -> Error {
 }
 
 _link :: proc(old_name, new_name: string) -> Error {
-	TEMP_ALLOCATOR_GUARD()
-	o := _fix_long_path(old_name, temp_allocator()) or_return
-	n := _fix_long_path(new_name, temp_allocator()) or_return
+	temp_allocator := TEMP_ALLOCATOR_GUARD({})
+	o := _fix_long_path(old_name, temp_allocator) or_return
+	n := _fix_long_path(new_name, temp_allocator) or_return
 	if win32.CreateHardLinkW(n, o, nil) {
 		return nil
 	}
@@ -692,9 +669,9 @@ _normalize_link_path :: proc(p: []u16, allocator: runtime.Allocator) -> (str: st
 		return "", _get_platform_error()
 	}
 
-	TEMP_ALLOCATOR_GUARD()
+	temp_allocator := TEMP_ALLOCATOR_GUARD({ allocator })
 
-	buf := make([]u16, n+1, temp_allocator())
+	buf := make([]u16, n+1, temp_allocator)
 	n = win32.GetFinalPathNameByHandleW(handle, raw_data(buf), u32(len(buf)), win32.VOLUME_NAME_DOS)
 	if n == 0 {
 		return "", _get_platform_error()
@@ -718,9 +695,9 @@ _read_link :: proc(name: string, allocator: runtime.Allocator) -> (s: string, er
 	@thread_local
 	rdb_buf: [MAXIMUM_REPARSE_DATA_BUFFER_SIZE]byte
 
-	TEMP_ALLOCATOR_GUARD()
+	temp_allocator := TEMP_ALLOCATOR_GUARD({ allocator })
 
-	p      := _fix_long_path(name, temp_allocator()) or_return
+	p      := _fix_long_path(name, temp_allocator) or_return
 	handle := _open_sym_link(p) or_return
 	defer win32.CloseHandle(handle)
 
@@ -771,10 +748,20 @@ _fchmod :: proc(f: ^File, mode: int) -> Error {
 	if f == nil || f.impl == nil {
 		return nil
 	}
+	d: win32.BY_HANDLE_FILE_INFORMATION
+	if !win32.GetFileInformationByHandle(_handle(f), &d) {
+		return _get_platform_error()
+	}
+	attrs := d.dwFileAttributes
+	if mode & S_IWRITE != 0 {
+		attrs &~= win32.FILE_ATTRIBUTE_READONLY
+	} else {
+		attrs |= win32.FILE_ATTRIBUTE_READONLY
+	}
 
 	info: win32.FILE_BASIC_INFO
-	info.FileAttributes = win32.DWORD(mode)
-	if !win32.SetFileInformationByHandle(_handle(f), .FileBasicInfo, &info, size_of(info)) {
+	info.FileAttributes = attrs
+	if !win32.SetFileInformationByHandle(_handle(f), .FileBasicInfo, &info, size_of(d)) {
 		return _get_platform_error()
 	}
 	return nil
@@ -785,8 +772,8 @@ _fchown :: proc(f: ^File, uid, gid: int) -> Error {
 }
 
 _chdir :: proc(name: string) -> Error {
-	TEMP_ALLOCATOR_GUARD()
-	p := _fix_long_path(name, temp_allocator()) or_return
+	temp_allocator := TEMP_ALLOCATOR_GUARD({})
+	p := _fix_long_path(name, temp_allocator) or_return
 	if !win32.SetCurrentDirectoryW(p) {
 		return _get_platform_error()
 	}
@@ -834,8 +821,8 @@ _fchtimes :: proc(f: ^File, atime, mtime: time.Time) -> Error {
 }
 
 _exists :: proc(path: string) -> bool {
-	TEMP_ALLOCATOR_GUARD()
-	wpath, _ := _fix_long_path(path, temp_allocator())
+	temp_allocator := TEMP_ALLOCATOR_GUARD({})
+	wpath, _ := _fix_long_path(path, temp_allocator)
 	attribs := win32.GetFileAttributesW(wpath)
 	return attribs != win32.INVALID_FILE_ATTRIBUTES
 }

+ 0 - 5
core/os/os2/internal_util.odin

@@ -43,11 +43,6 @@ clone_to_cstring :: proc(s: string, allocator: runtime.Allocator) -> (res: cstri
 	return cstring(&buf[0]), nil
 }
 
-@(require_results)
-temp_cstring :: proc(s: string) -> (cstring, runtime.Allocator_Error) #optional_allocator_error {
-	return clone_to_cstring(s, temp_allocator())
-}
-
 @(require_results)
 string_from_null_terminated_bytes :: proc(b: []byte) -> (res: string) {
 	s := string(b)

+ 4 - 4
core/os/os2/path.odin

@@ -119,11 +119,11 @@ clean_path :: proc(path: string, allocator: runtime.Allocator) -> (cleaned: stri
 		return strings.clone(".", allocator)
 	}
 
-	TEMP_ALLOCATOR_GUARD()
+	temp_allocator := TEMP_ALLOCATOR_GUARD({ allocator })
 
 	// The extra byte is to simplify appending path elements by letting the
 	// loop to end each with a separator. We'll trim the last one when we're done.
-	buffer := make([]u8, len(path) + 1, temp_allocator()) or_return
+	buffer := make([]u8, len(path) + 1, temp_allocator) or_return
 
 	// This is the only point where Windows and POSIX differ, as Windows has
 	// alphabet-based volumes for root paths.
@@ -326,8 +326,8 @@ For example, `join_path({"/home", "foo", "bar.txt"})` will result in `"/home/foo
 join_path :: proc(elems: []string, allocator: runtime.Allocator) -> (joined: string, err: Error) {
 	for e, i in elems {
 		if e != "" {
-			TEMP_ALLOCATOR_GUARD()
-			p := strings.join(elems[i:], Path_Separator_String, temp_allocator()) or_return
+			temp_allocator := TEMP_ALLOCATOR_GUARD({ allocator })
+			p := strings.join(elems[i:], Path_Separator_String, temp_allocator) or_return
 			return clean_path(p, allocator)
 		}
 	}

+ 29 - 9
core/os/os2/path_linux.odin

@@ -18,8 +18,8 @@ _is_path_separator :: proc(c: byte) -> bool {
 }
 
 _mkdir :: proc(path: string, perm: int) -> Error {
-	TEMP_ALLOCATOR_GUARD()
-	path_cstr := temp_cstring(path) or_return
+	temp_allocator := TEMP_ALLOCATOR_GUARD({})
+	path_cstr := clone_to_cstring(path, temp_allocator) or_return
 	return _get_platform_error(linux.mkdir(path_cstr, transmute(linux.Mode)u32(perm)))
 }
 
@@ -52,9 +52,9 @@ _mkdir_all :: proc(path: string, perm: int) -> Error {
 		}
 		return _get_platform_error(errno)
 	}
-	TEMP_ALLOCATOR_GUARD()
+	temp_allocator := TEMP_ALLOCATOR_GUARD({})
 	// need something we can edit, and use to generate cstrings
-	path_bytes := make([]u8, len(path) + 1, temp_allocator())
+	path_bytes := make([]u8, len(path) + 1, temp_allocator)
 
 	// zero terminate the byte slice to make it a valid cstring
 	copy(path_bytes, path)
@@ -129,8 +129,8 @@ _remove_all :: proc(path: string) -> Error {
 		return nil
 	}
 
-	TEMP_ALLOCATOR_GUARD()
-	path_cstr := temp_cstring(path) or_return
+	temp_allocator := TEMP_ALLOCATOR_GUARD({})
+	path_cstr := clone_to_cstring(path, temp_allocator) or_return
 
 	fd, errno := linux.open(path_cstr, _OPENDIR_FLAGS)
 	#partial switch errno {
@@ -168,14 +168,16 @@ _get_working_directory :: proc(allocator: runtime.Allocator) -> (string, Error)
 }
 
 _set_working_directory :: proc(dir: string) -> Error {
-	dir_cstr := temp_cstring(dir) or_return
+	temp_allocator := TEMP_ALLOCATOR_GUARD({})
+
+	dir_cstr := clone_to_cstring(dir, temp_allocator) or_return
 	return _get_platform_error(linux.chdir(dir_cstr))
 }
 
 _get_executable_path :: proc(allocator: runtime.Allocator) -> (path: string, err: Error) {
-	TEMP_ALLOCATOR_GUARD()
+	temp_allocator := TEMP_ALLOCATOR_GUARD({ allocator })
 
-	buf := make([dynamic]byte, 1024, temp_allocator()) or_return
+	buf := make([dynamic]byte, 1024, temp_allocator) or_return
 	for {
 		n, errno := linux.readlink("/proc/self/exe", buf[:])
 		if errno != .NONE {
@@ -205,3 +207,21 @@ _get_full_path :: proc(fd: linux.Fd, allocator: runtime.Allocator) -> (fullpath:
 	}
 	return
 }
+
+_get_absolute_path :: proc(path: string, allocator: runtime.Allocator) -> (absolute_path: string, err: Error) {
+	rel := path
+	if rel == "" {
+		rel = "."
+	}
+
+	temp_allocator := TEMP_ALLOCATOR_GUARD({ allocator })
+
+	fd, errno := linux.open(clone_to_cstring(path, temp_allocator) or_return, {})
+	if errno != nil {
+		err = _get_platform_error(errno)
+		return
+	}
+	defer linux.close(fd)
+
+	return _get_full_path(fd, allocator)
+}

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

@@ -5,9 +5,9 @@ import "base:runtime"
 import "core:sys/posix"
 
 _get_executable_path :: proc(allocator: runtime.Allocator) -> (path: string, err: Error) {
-	TEMP_ALLOCATOR_GUARD()
+	temp_allocator := TEMP_ALLOCATOR_GUARD({ allocator })
 
-	buf := make([dynamic]byte, 1024, temp_allocator()) or_return
+	buf := make([dynamic]byte, 1024, temp_allocator) or_return
 	for {
 		n := posix.readlink("/proc/curproc/exe", raw_data(buf), len(buf))
 		if n < 0 {

+ 3 - 3
core/os/os2/path_openbsd.odin

@@ -35,11 +35,11 @@ _get_executable_path :: proc(allocator: runtime.Allocator) -> (path: string, err
 		return real(arg, allocator)
 	}
 
-	TEMP_ALLOCATOR_GUARD()
+	temp_allocator := TEMP_ALLOCATOR_GUARD({ allocator })
 
-	buf := strings.builder_make(temp_allocator())
+	buf := strings.builder_make(temp_allocator)
 
-	paths := get_env("PATH", temp_allocator())
+	paths := get_env("PATH", temp_allocator)
 	for dir in strings.split_iterator(&paths, ":") {
 		strings.builder_reset(&buf)
 		strings.write_string(&buf, dir)

+ 30 - 13
core/os/os2/path_posix.odin

@@ -14,9 +14,9 @@ _is_path_separator :: proc(c: byte) -> bool {
 	return c == _Path_Separator
 }
 
-_mkdir :: proc(name: string, perm: int) -> Error {
-	TEMP_ALLOCATOR_GUARD()
-	cname := temp_cstring(name)
+_mkdir :: proc(name: string, perm: int) -> (err: Error) {
+	temp_allocator := TEMP_ALLOCATOR_GUARD({})
+	cname := clone_to_cstring(name, temp_allocator) or_return
 	if posix.mkdir(cname, transmute(posix.mode_t)posix._mode_t(perm)) != .OK {
 		return _get_platform_error()
 	}
@@ -28,13 +28,13 @@ _mkdir_all :: proc(path: string, perm: int) -> Error {
 		return .Invalid_Path
 	}
 
-	TEMP_ALLOCATOR_GUARD()
+	temp_allocator := TEMP_ALLOCATOR_GUARD({})
 
 	if exists(path) {
 		return .Exist
 	}
 
-	clean_path := clean_path(path, temp_allocator()) or_return
+	clean_path := clean_path(path, temp_allocator) or_return
 	return internal_mkdir_all(clean_path, perm)
 
 	internal_mkdir_all :: proc(path: string, perm: int) -> Error {
@@ -52,9 +52,9 @@ _mkdir_all :: proc(path: string, perm: int) -> Error {
 	}
 }
 
-_remove_all :: proc(path: string) -> Error {
-	TEMP_ALLOCATOR_GUARD()
-	cpath := temp_cstring(path)
+_remove_all :: proc(path: string) -> (err: Error) {
+	temp_allocator := TEMP_ALLOCATOR_GUARD({})
+	cpath := clone_to_cstring(path, temp_allocator) or_return
 
 	dir := posix.opendir(cpath)
 	if dir == nil {
@@ -78,7 +78,7 @@ _remove_all :: proc(path: string) -> Error {
 			continue
 		}
 
-		fullpath, _ := concatenate({path, "/", string(cname), "\x00"}, temp_allocator())
+		fullpath, _ := concatenate({path, "/", string(cname), "\x00"}, temp_allocator)
 		if entry.d_type == .DIR {
 			_remove_all(fullpath[:len(fullpath)-1]) or_return
 		} else {
@@ -95,10 +95,10 @@ _remove_all :: proc(path: string) -> Error {
 }
 
 _get_working_directory :: proc(allocator: runtime.Allocator) -> (dir: string, err: Error) {
-	TEMP_ALLOCATOR_GUARD()
+	temp_allocator := TEMP_ALLOCATOR_GUARD({ allocator })
 
 	buf: [dynamic]byte
-	buf.allocator = temp_allocator()
+	buf.allocator = temp_allocator
 	size := uint(posix.PATH_MAX)
 
 	cwd: cstring
@@ -116,10 +116,27 @@ _get_working_directory :: proc(allocator: runtime.Allocator) -> (dir: string, er
 }
 
 _set_working_directory :: proc(dir: string) -> (err: Error) {
-	TEMP_ALLOCATOR_GUARD()
-	cdir := temp_cstring(dir)
+	temp_allocator := TEMP_ALLOCATOR_GUARD({})
+	cdir := clone_to_cstring(dir, temp_allocator) or_return
 	if posix.chdir(cdir) != .OK {
 		err = _get_platform_error()
 	}
 	return
 }
+
+_get_absolute_path :: proc(path: string, allocator: runtime.Allocator) -> (absolute_path: string, err: Error) {
+	rel := path
+	if rel == "" {
+		rel = "."
+	}
+	temp_allocator := TEMP_ALLOCATOR_GUARD({ allocator })
+	rel_cstr := clone_to_cstring(rel, temp_allocator) or_return
+	path_ptr := posix.realpath(rel_cstr, nil)
+	if path_ptr == nil {
+		return "", Platform_Error(posix.errno())
+	}
+	defer posix.free(path_ptr)
+
+	path_str := clone_string(string(path_ptr), allocator) or_return
+	return path_str, nil
+}

+ 0 - 21
core/os/os2/path_posixfs.odin

@@ -4,10 +4,6 @@ package os2
 
 // This implementation is for all systems that have POSIX-compliant filesystem paths.
 
-import "base:runtime"
-import "core:strings"
-import "core:sys/posix"
-
 _are_paths_identical :: proc(a, b: string) -> (identical: bool) {
 	return a == b
 }
@@ -26,23 +22,6 @@ _is_absolute_path :: proc(path: string) -> bool {
 	return len(path) > 0 && _is_path_separator(path[0])
 }
 
-_get_absolute_path :: proc(path: string, allocator: runtime.Allocator) -> (absolute_path: string, err: Error) {
-	rel := path
-	if rel == "" {
-		rel = "."
-	}
-	TEMP_ALLOCATOR_GUARD()
-	rel_cstr := strings.clone_to_cstring(rel, temp_allocator())
-	path_ptr := posix.realpath(rel_cstr, nil)
-	if path_ptr == nil {
-		return "", Platform_Error(posix.errno())
-	}
-	defer posix.free(path_ptr)
-
-	path_str := strings.clone(string(path_ptr), allocator)
-	return path_str, nil
-}
-
 _get_relative_path_handle_start :: proc(base, target: string) -> bool {
 	base_rooted   := len(base)   > 0 && _is_path_separator(base[0])
 	target_rooted := len(target) > 0 && _is_path_separator(target[0])

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

@@ -28,13 +28,13 @@ _mkdir_all :: proc(path: string, perm: int) -> Error {
 		return .Invalid_Path
 	}
 
-	TEMP_ALLOCATOR_GUARD()
+	temp_allocator := TEMP_ALLOCATOR_GUARD({})
 
 	if exists(path) {
 		return .Exist
 	}
 
-	clean_path := clean_path(path, temp_allocator())
+	clean_path := clean_path(path, temp_allocator)
 	return internal_mkdir_all(clean_path)
 
 	internal_mkdir_all :: proc(path: string) -> Error {

+ 23 - 19
core/os/os2/path_windows.odin

@@ -14,8 +14,8 @@ _is_path_separator :: proc(c: byte) -> bool {
 }
 
 _mkdir :: proc(name: string, perm: int) -> Error {
-	TEMP_ALLOCATOR_GUARD()
-	if !win32.CreateDirectoryW(_fix_long_path(name, temp_allocator()) or_return, nil) {
+	temp_allocator := TEMP_ALLOCATOR_GUARD({})
+	if !win32.CreateDirectoryW(_fix_long_path(name, temp_allocator) or_return, nil) {
 		return _get_platform_error()
 	}
 	return nil
@@ -33,9 +33,9 @@ _mkdir_all :: proc(path: string, perm: int) -> Error {
 		return p, false, nil
 	}
 
-	TEMP_ALLOCATOR_GUARD()
+	temp_allocator := TEMP_ALLOCATOR_GUARD({})
 
-	dir_stat, err := stat(path, temp_allocator())
+	dir_stat, err := stat(path, temp_allocator)
 	if err == nil {
 		if dir_stat.type == .Directory {
 			return nil
@@ -63,7 +63,7 @@ _mkdir_all :: proc(path: string, perm: int) -> Error {
 
 	err = mkdir(path, perm)
 	if err != nil {
-		new_dir_stat, err1 := lstat(path, temp_allocator())
+		new_dir_stat, err1 := lstat(path, temp_allocator)
 		if err1 == nil && new_dir_stat.type == .Directory {
 			return nil
 		}
@@ -82,8 +82,8 @@ _remove_all :: proc(path: string) -> Error {
 		return nil
 	}
 
-	TEMP_ALLOCATOR_GUARD()
-	dir := win32_utf8_to_wstring(path, temp_allocator()) or_return
+	temp_allocator := TEMP_ALLOCATOR_GUARD({})
+	dir := win32_utf8_to_wstring(path, temp_allocator) or_return
 
 	empty: [1]u16
 
@@ -109,10 +109,10 @@ _remove_all :: proc(path: string) -> Error {
 _get_working_directory :: proc(allocator: runtime.Allocator) -> (dir: string, err: Error) {
 	win32.AcquireSRWLockExclusive(&cwd_lock)
 
-	TEMP_ALLOCATOR_GUARD()
+	temp_allocator := TEMP_ALLOCATOR_GUARD({ allocator })
 
 	sz_utf16 := win32.GetCurrentDirectoryW(0, nil)
-	dir_buf_wstr := make([]u16, sz_utf16, temp_allocator()) or_return
+	dir_buf_wstr := make([]u16, sz_utf16, temp_allocator) or_return
 
 	sz_utf16 = win32.GetCurrentDirectoryW(win32.DWORD(len(dir_buf_wstr)), raw_data(dir_buf_wstr))
 	assert(int(sz_utf16)+1 == len(dir_buf_wstr)) // the second time, it _excludes_ the NUL.
@@ -123,8 +123,8 @@ _get_working_directory :: proc(allocator: runtime.Allocator) -> (dir: string, er
 }
 
 _set_working_directory :: proc(dir: string) -> (err: Error) {
-	TEMP_ALLOCATOR_GUARD()
-	wstr := win32_utf8_to_wstring(dir, temp_allocator()) or_return
+	temp_allocator := TEMP_ALLOCATOR_GUARD({})
+	wstr := win32_utf8_to_wstring(dir, temp_allocator) or_return
 
 	win32.AcquireSRWLockExclusive(&cwd_lock)
 
@@ -138,9 +138,9 @@ _set_working_directory :: proc(dir: string) -> (err: Error) {
 }
 
 _get_executable_path :: proc(allocator: runtime.Allocator) -> (path: string, err: Error) {
-	TEMP_ALLOCATOR_GUARD()
+	temp_allocator := TEMP_ALLOCATOR_GUARD({ allocator })
 
-	buf := make([dynamic]u16, 512, temp_allocator()) or_return
+	buf := make([dynamic]u16, 512, temp_allocator) or_return
 	for {
 		ret := win32.GetModuleFileNameW(nil, raw_data(buf), win32.DWORD(len(buf)))
 		if ret == 0 {
@@ -187,7 +187,6 @@ init_long_path_support :: proc() {
 	if value == 1 {
 		can_use_long_paths = true
 	}
-
 }
 
 @(require_results)
@@ -222,10 +221,10 @@ _fix_long_path_internal :: proc(path: string) -> string {
 		return path
 	}
 
-	TEMP_ALLOCATOR_GUARD()
+	temp_allocator := TEMP_ALLOCATOR_GUARD({})
 
 	PREFIX :: `\\?`
-	path_buf := make([]byte, len(PREFIX)+len(path)+1, temp_allocator())
+	path_buf := make([]byte, len(PREFIX)+len(path)+1, temp_allocator)
 	copy(path_buf, PREFIX)
 	n := len(path)
 	r, w := 0, len(PREFIX)
@@ -271,6 +270,11 @@ _clean_path_handle_start :: proc(path: string, buffer: []u8) -> (rooted: bool, s
 			start += 1
 		}
 		copy(buffer, path[:start])
+		for n in 0..<start {
+			if _is_path_separator(buffer[n]) {
+				buffer[n] = _Path_Separator
+			}
+		}
 	}
 	return
 }
@@ -297,14 +301,14 @@ _get_absolute_path :: proc(path: string, allocator: runtime.Allocator) -> (absol
 	if rel == "" {
 		rel = "."
 	}
-	TEMP_ALLOCATOR_GUARD()
-	rel_utf16 := win32.utf8_to_utf16(rel, temp_allocator())
+	temp_allocator := TEMP_ALLOCATOR_GUARD({ allocator })
+	rel_utf16 := win32.utf8_to_utf16(rel, temp_allocator)
 	n := win32.GetFullPathNameW(raw_data(rel_utf16), 0, nil, nil)
 	if n == 0 {
 		return "", Platform_Error(win32.GetLastError())
 	}
 
-	buf := make([]u16, n, temp_allocator()) or_return
+	buf := make([]u16, n, temp_allocator) or_return
 	n = win32.GetFullPathNameW(raw_data(rel_utf16), u32(n), raw_data(buf), nil)
 	if n == 0 {
 		return "", Platform_Error(win32.GetLastError())

+ 24 - 24
core/os/os2/process_linux.odin

@@ -50,7 +50,7 @@ _get_ppid :: proc() -> int {
 
 @(private="package")
 _process_list :: proc(allocator: runtime.Allocator) -> (list: []int, err: Error) {
-	TEMP_ALLOCATOR_GUARD()
+	temp_allocator := TEMP_ALLOCATOR_GUARD({ allocator })
 
 	dir_fd, errno := linux.open("/proc/", _OPENDIR_FLAGS)
 	#partial switch errno {
@@ -68,9 +68,9 @@ _process_list :: proc(allocator: runtime.Allocator) -> (list: []int, err: Error)
 	}
 	defer linux.close(dir_fd)
 
-	dynamic_list := make([dynamic]int, temp_allocator()) or_return
+	dynamic_list := make([dynamic]int, temp_allocator) or_return
 
-	buf := make([dynamic]u8, 128, 128, temp_allocator()) or_return
+	buf := make([dynamic]u8, 128, 128, temp_allocator) or_return
 	loop: for {
 		buflen: int
 		buflen, errno = linux.getdents(dir_fd, buf[:])
@@ -100,7 +100,7 @@ _process_list :: proc(allocator: runtime.Allocator) -> (list: []int, err: Error)
 
 @(private="package")
 _process_info_by_pid :: proc(pid: int, selection: Process_Info_Fields, allocator: runtime.Allocator) -> (info: Process_Info, err: Error) {
-	TEMP_ALLOCATOR_GUARD()
+	temp_allocator := TEMP_ALLOCATOR_GUARD({ allocator })
 
 	info.pid = pid
 
@@ -126,7 +126,7 @@ _process_info_by_pid :: proc(pid: int, selection: Process_Info_Fields, allocator
 
 		passwd_bytes: []u8
 		passwd_err: Error
-		passwd_bytes, passwd_err = _read_entire_pseudo_file_cstring("/etc/passwd", temp_allocator())
+		passwd_bytes, passwd_err = _read_entire_pseudo_file_cstring("/etc/passwd", temp_allocator)
 		if passwd_err != nil {
 			err = passwd_err
 			break username_if
@@ -168,7 +168,7 @@ _process_info_by_pid :: proc(pid: int, selection: Process_Info_Fields, allocator
 		strings.write_int(&path_builder, pid)
 		strings.write_string(&path_builder, "/cmdline")
 
-		cmdline_bytes, cmdline_err := _read_entire_pseudo_file(strings.to_cstring(&path_builder) or_return, temp_allocator())
+		cmdline_bytes, cmdline_err := _read_entire_pseudo_file(strings.to_cstring(&path_builder) or_return, temp_allocator)
 		if cmdline_err != nil || len(cmdline_bytes) == 0 {
 			err = cmdline_err
 			break cmdline_if
@@ -189,7 +189,7 @@ _process_info_by_pid :: proc(pid: int, selection: Process_Info_Fields, allocator
 			strings.write_int(&path_builder, pid)
 			strings.write_string(&path_builder, "/cwd")
 
-			cwd, cwd_err = _read_link_cstr(strings.to_cstring(&path_builder) or_return, temp_allocator()) // allowed to fail
+			cwd, cwd_err = _read_link_cstr(strings.to_cstring(&path_builder) or_return, temp_allocator) // allowed to fail
 			if cwd_err == nil && .Working_Dir in selection {
 				info.working_dir = strings.clone(cwd, allocator) or_return
 				info.fields += {.Working_Dir}
@@ -245,7 +245,7 @@ _process_info_by_pid :: proc(pid: int, selection: Process_Info_Fields, allocator
 		strings.write_int(&path_builder, pid)
 		strings.write_string(&path_builder, "/stat")
 
-		proc_stat_bytes, stat_err := _read_entire_pseudo_file(strings.to_cstring(&path_builder) or_return, temp_allocator())
+		proc_stat_bytes, stat_err := _read_entire_pseudo_file(strings.to_cstring(&path_builder) or_return, temp_allocator)
 		if stat_err != nil {
 			err = stat_err
 			break stat_if
@@ -284,7 +284,7 @@ _process_info_by_pid :: proc(pid: int, selection: Process_Info_Fields, allocator
 			Nice,
 			//... etc,
 		}
-		stat_fields := strings.split(stats, " ", temp_allocator()) or_return
+		stat_fields := strings.split(stats, " ", temp_allocator) or_return
 
 		if len(stat_fields) <= int(Fields.Nice) {
 			break stat_if
@@ -327,7 +327,7 @@ _process_info_by_pid :: proc(pid: int, selection: Process_Info_Fields, allocator
 		strings.write_int(&path_builder, pid)
 		strings.write_string(&path_builder, "/exe")
 
-		if exe_bytes, exe_err := _read_link(strings.to_string(path_builder), temp_allocator()); exe_err == nil {
+		if exe_bytes, exe_err := _read_link(strings.to_string(path_builder), temp_allocator); exe_err == nil {
 			info.executable_path = strings.clone(string(exe_bytes), allocator) or_return
 			info.fields += {.Executable_Path}
 		} else {
@@ -341,7 +341,7 @@ _process_info_by_pid :: proc(pid: int, selection: Process_Info_Fields, allocator
 		strings.write_int(&path_builder, pid)
 		strings.write_string(&path_builder, "/environ")
 
-		if env_bytes, env_err := _read_entire_pseudo_file(strings.to_cstring(&path_builder) or_return, temp_allocator()); env_err == nil {
+		if env_bytes, env_err := _read_entire_pseudo_file(strings.to_cstring(&path_builder) or_return, temp_allocator); env_err == nil {
 			env := string(env_bytes)
 
 			env_list := make([dynamic]string, allocator) or_return
@@ -392,7 +392,7 @@ _process_open :: proc(pid: int, _: Process_Open_Flags) -> (process: Process, err
 
 @(private="package")
 _process_start :: proc(desc: Process_Desc) -> (process: Process, err: Error) {
-	TEMP_ALLOCATOR_GUARD()
+	temp_allocator := TEMP_ALLOCATOR_GUARD({})
 
 	if len(desc.command) == 0 {
 		return process, .Invalid_Command
@@ -401,7 +401,7 @@ _process_start :: proc(desc: Process_Desc) -> (process: Process, err: Error) {
 	dir_fd := linux.AT_FDCWD
 	errno: linux.Errno
 	if desc.working_dir != "" {
-		dir_cstr := temp_cstring(desc.working_dir) or_return
+		dir_cstr := clone_to_cstring(desc.working_dir, temp_allocator) or_return
 		if dir_fd, errno = linux.open(dir_cstr, _OPENDIR_FLAGS); errno != .NONE {
 			return process, _get_platform_error(errno)
 		}
@@ -414,10 +414,10 @@ _process_start :: proc(desc: Process_Desc) -> (process: Process, err: Error) {
 	exe_path: cstring
 	executable_name := desc.command[0]
 	if strings.index_byte(executable_name, '/') < 0 {
-		path_env := get_env("PATH", temp_allocator())
-		path_dirs := split_path_list(path_env, temp_allocator()) or_return
+		path_env := get_env("PATH", temp_allocator)
+		path_dirs := split_path_list(path_env, temp_allocator) or_return
 
-		exe_builder := strings.builder_make(temp_allocator()) or_return
+		exe_builder := strings.builder_make(temp_allocator) or_return
 
 		found: bool
 		for dir in path_dirs {
@@ -444,7 +444,7 @@ _process_start :: proc(desc: Process_Desc) -> (process: Process, err: Error) {
 			}
 		}
 	} else {
-		exe_path = temp_cstring(executable_name) or_return
+		exe_path = clone_to_cstring(executable_name, temp_allocator) or_return
 		if linux.access(exe_path, linux.X_OK) != .NONE {
 			return process, .Not_Exist
 		}
@@ -452,20 +452,20 @@ _process_start :: proc(desc: Process_Desc) -> (process: Process, err: Error) {
 
 	// args and environment need to be a list of cstrings
 	// that are terminated by a nil pointer.
-	cargs := make([]cstring, len(desc.command) + 1, temp_allocator()) or_return
+	cargs := make([]cstring, len(desc.command) + 1, temp_allocator) or_return
 	for command, i in desc.command {
-		cargs[i] = temp_cstring(command) or_return
+		cargs[i] = clone_to_cstring(command, temp_allocator) or_return
 	}
 
 	// Use current process' environment if description didn't provide it.
 	env: [^]cstring
 	if desc.env == nil {
 		// take this process's current environment
-		env = raw_data(export_cstring_environment(temp_allocator()))
+		env = raw_data(export_cstring_environment(temp_allocator))
 	} else {
-		cenv := make([]cstring, len(desc.env) + 1, temp_allocator()) or_return
+		cenv := make([]cstring, len(desc.env) + 1, temp_allocator) or_return
 		for env, i in desc.env {
-			cenv[i] = temp_cstring(env) or_return
+			cenv[i] = clone_to_cstring(env, temp_allocator) or_return
 		}
 		env = &cenv[0]
 	}
@@ -593,7 +593,7 @@ _process_start :: proc(desc: Process_Desc) -> (process: Process, err: Error) {
 }
 
 _process_state_update_times :: proc(state: ^Process_State) -> (err: Error) {
-	TEMP_ALLOCATOR_GUARD()
+	temp_allocator := TEMP_ALLOCATOR_GUARD({})
 
 	stat_path_buf: [48]u8
 	path_builder := strings.builder_from_bytes(stat_path_buf[:])
@@ -602,7 +602,7 @@ _process_state_update_times :: proc(state: ^Process_State) -> (err: Error) {
 	strings.write_string(&path_builder, "/stat")
 
 	stat_buf: []u8
-	stat_buf, err = _read_entire_pseudo_file(strings.to_cstring(&path_builder) or_return, temp_allocator())
+	stat_buf, err = _read_entire_pseudo_file(strings.to_cstring(&path_builder) or_return, temp_allocator)
 	if err != nil {
 		return
 	}

+ 9 - 9
core/os/os2/process_posix.odin

@@ -52,14 +52,14 @@ _process_start :: proc(desc: Process_Desc) -> (process: Process, err: Error) {
 		return
 	}
 
-	TEMP_ALLOCATOR_GUARD()
+	temp_allocator := TEMP_ALLOCATOR_GUARD({})
 
 	// search PATH if just a plain name is provided.
-	exe_builder := strings.builder_make(temp_allocator())
+	exe_builder := strings.builder_make(temp_allocator)
 	exe_name    := desc.command[0]
 	if strings.index_byte(exe_name, '/') < 0 {
-		path_env  := get_env("PATH", temp_allocator())
-		path_dirs := split_path_list(path_env, temp_allocator()) or_return
+		path_env  := get_env("PATH", temp_allocator)
+		path_dirs := split_path_list(path_env, temp_allocator) or_return
 
 		found: bool
 		for dir in path_dirs {
@@ -108,12 +108,12 @@ _process_start :: proc(desc: Process_Desc) -> (process: Process, err: Error) {
 	}
 
 	cwd: cstring; if desc.working_dir != "" {
-		cwd = temp_cstring(desc.working_dir)
+		cwd = clone_to_cstring(desc.working_dir, temp_allocator) or_return
 	}
 
-	cmd := make([]cstring, len(desc.command) + 1, temp_allocator())
+	cmd := make([]cstring, len(desc.command) + 1, temp_allocator)
 	for part, i in desc.command {
-		cmd[i] = temp_cstring(part)
+		cmd[i] = clone_to_cstring(part, temp_allocator) or_return
 	}
 
 	env: [^]cstring
@@ -121,9 +121,9 @@ _process_start :: proc(desc: Process_Desc) -> (process: Process, err: Error) {
 		// take this process's current environment
 		env = posix.environ
 	} else {
-		cenv := make([]cstring, len(desc.env) + 1, temp_allocator())
+		cenv := make([]cstring, len(desc.env) + 1, temp_allocator)
 		for env, i in desc.env {
-			cenv[i] = temp_cstring(env)
+			cenv[i] = clone_to_cstring(env, temp_allocator) or_return
 		}
 		env = raw_data(cenv)
 	}

+ 4 - 3
core/os/os2/process_posix_darwin.odin

@@ -50,6 +50,7 @@ _process_info_by_pid :: proc(pid: int, selection: Process_Info_Fields, allocator
 	}
 
 
+	temp_allocator := TEMP_ALLOCATOR_GUARD({ allocator })
 	info.pid = pid
 
 	// Thought on errors is: allocation failures return immediately (also why the non-allocation stuff is done first),
@@ -127,7 +128,7 @@ _process_info_by_pid :: proc(pid: int, selection: Process_Info_Fields, allocator
 			break args
 		}
 
-		buf := runtime.make_aligned([]byte, length, 4, temp_allocator())
+		buf := runtime.make_aligned([]byte, length, 4, temp_allocator)
 		if sysctl(raw_data(mib), 3, raw_data(buf), &length, nil, 0) != .OK {
 			if err == nil {
 				err = _get_platform_error()
@@ -239,9 +240,9 @@ _process_list :: proc(allocator: runtime.Allocator) -> (list: []int, err: Error)
 		return
 	}
 
-	TEMP_ALLOCATOR_GUARD()
+	temp_allocator := TEMP_ALLOCATOR_GUARD({ allocator })
 
-	buffer := make([]i32, ret, temp_allocator())
+	buffer := make([]i32, ret, temp_allocator)
 	ret = darwin.proc_listallpids(raw_data(buffer), ret*size_of(i32))
 	if ret < 0 {
 		err = _get_platform_error()

+ 25 - 23
core/os/os2/process_windows.odin

@@ -162,9 +162,10 @@ _process_info_by_pid :: proc(pid: int, selection: Process_Info_Fields, allocator
 		if err != nil {
 			break read_peb
 		}
+		temp_allocator := TEMP_ALLOCATOR_GUARD({ allocator })
 		if selection >= {.Command_Line, .Command_Args} {
-			TEMP_ALLOCATOR_GUARD()
-			cmdline_w := make([]u16, process_params.CommandLine.Length, temp_allocator()) or_return
+			temp_allocator_scope(temp_allocator)
+			cmdline_w := make([]u16, process_params.CommandLine.Length, temp_allocator) or_return
 			_, err = read_memory_as_slice(ph, process_params.CommandLine.Buffer, cmdline_w)
 			if err != nil {
 				break read_peb
@@ -179,9 +180,9 @@ _process_info_by_pid :: proc(pid: int, selection: Process_Info_Fields, allocator
 			}
 		}
 		if .Environment in selection {
-			TEMP_ALLOCATOR_GUARD()
+			temp_allocator_scope(temp_allocator)
 			env_len := process_params.EnvironmentSize / 2
-			envs_w := make([]u16, env_len, temp_allocator()) or_return
+			envs_w := make([]u16, env_len, temp_allocator) or_return
 			_, err = read_memory_as_slice(ph, process_params.Environment, envs_w)
 			if err != nil {
 				break read_peb
@@ -190,8 +191,8 @@ _process_info_by_pid :: proc(pid: int, selection: Process_Info_Fields, allocator
 			info.fields += {.Environment}
 		}
 		if .Working_Dir in selection {
-			TEMP_ALLOCATOR_GUARD()
-			cwd_w := make([]u16, process_params.CurrentDirectoryPath.Length, temp_allocator()) or_return
+			temp_allocator_scope(temp_allocator)
+			cwd_w := make([]u16, process_params.CurrentDirectoryPath.Length, temp_allocator) or_return
 			_, err = read_memory_as_slice(ph, process_params.CurrentDirectoryPath.Buffer, cwd_w)
 			if err != nil {
 				break read_peb
@@ -272,9 +273,10 @@ _process_info_by_handle :: proc(process: Process, selection: Process_Info_Fields
 		if err != nil {
 			break read_peb
 		}
+		temp_allocator := TEMP_ALLOCATOR_GUARD({ allocator })
 		if selection >= {.Command_Line, .Command_Args} {
-			TEMP_ALLOCATOR_GUARD()
-			cmdline_w := make([]u16, process_params.CommandLine.Length, temp_allocator()) or_return
+			temp_allocator_scope(temp_allocator)
+			cmdline_w := make([]u16, process_params.CommandLine.Length, temp_allocator) or_return
 			_, err = read_memory_as_slice(ph, process_params.CommandLine.Buffer, cmdline_w)
 			if err != nil {
 				break read_peb
@@ -289,9 +291,9 @@ _process_info_by_handle :: proc(process: Process, selection: Process_Info_Fields
 			}
 		}
 		if .Environment in selection {
-			TEMP_ALLOCATOR_GUARD()
+			temp_allocator_scope(temp_allocator)
 			env_len := process_params.EnvironmentSize / 2
-			envs_w := make([]u16, env_len, temp_allocator()) or_return
+			envs_w := make([]u16, env_len, temp_allocator) or_return
 			_, err = read_memory_as_slice(ph, process_params.Environment, envs_w)
 			if err != nil {
 				break read_peb
@@ -300,8 +302,8 @@ _process_info_by_handle :: proc(process: Process, selection: Process_Info_Fields
 			info.fields += {.Environment}
 		}
 		if .Working_Dir in selection {
-			TEMP_ALLOCATOR_GUARD()
-			cwd_w := make([]u16, process_params.CurrentDirectoryPath.Length, temp_allocator()) or_return
+			temp_allocator_scope(temp_allocator)
+			cwd_w := make([]u16, process_params.CurrentDirectoryPath.Length, temp_allocator) or_return
 			_, err = read_memory_as_slice(ph, process_params.CurrentDirectoryPath.Buffer, cwd_w)
 			if err != nil {
 				break read_peb
@@ -419,15 +421,15 @@ _process_open :: proc(pid: int, flags: Process_Open_Flags) -> (process: Process,
 
 @(private="package")
 _process_start :: proc(desc: Process_Desc) -> (process: Process, err: Error) {
-	TEMP_ALLOCATOR_GUARD()
-	command_line   := _build_command_line(desc.command, temp_allocator())
-	command_line_w := win32_utf8_to_wstring(command_line, temp_allocator()) or_return
+	temp_allocator := TEMP_ALLOCATOR_GUARD({})
+	command_line   := _build_command_line(desc.command, temp_allocator)
+	command_line_w := win32_utf8_to_wstring(command_line, temp_allocator) or_return
 	environment := desc.env
 	if desc.env == nil {
-		environment = environ(temp_allocator()) or_return
+		environment = environ(temp_allocator) or_return
 	}
-	environment_block   := _build_environment_block(environment, temp_allocator())
-	environment_block_w := win32_utf8_to_utf16(environment_block, temp_allocator()) or_return
+	environment_block   := _build_environment_block(environment, temp_allocator)
+	environment_block_w := win32_utf8_to_utf16(environment_block, temp_allocator) or_return
 
 	stderr_handle: win32.HANDLE	
 	stdout_handle: win32.HANDLE	
@@ -474,7 +476,7 @@ _process_start :: proc(desc: Process_Desc) -> (process: Process, err: Error) {
 		stdin_handle = win32.HANDLE((^File_Impl)(desc.stdin.impl).fd)
 	}
 
-	working_dir_w := (win32_utf8_to_wstring(desc.working_dir, temp_allocator()) or_else nil) if len(desc.working_dir) > 0 else nil
+	working_dir_w := (win32_utf8_to_wstring(desc.working_dir, temp_allocator) or_else nil) if len(desc.working_dir) > 0 else nil
 	process_info: win32.PROCESS_INFORMATION
 	ok := win32.CreateProcessW(
 		nil,
@@ -612,7 +614,7 @@ _process_exe_by_pid :: proc(pid: int, allocator: runtime.Allocator) -> (exe_path
 }
 
 _get_process_user :: proc(process_handle: win32.HANDLE, allocator: runtime.Allocator) -> (full_username: string, err: Error) {
-	TEMP_ALLOCATOR_GUARD()
+	temp_allocator := TEMP_ALLOCATOR_GUARD({ allocator })
 	token_handle: win32.HANDLE
 	if !win32.OpenProcessToken(process_handle, win32.TOKEN_QUERY, &token_handle) {
 		err = _get_platform_error()
@@ -627,7 +629,7 @@ _get_process_user :: proc(process_handle: win32.HANDLE, allocator: runtime.Alloc
 		}
 		err = nil
 	}
-	token_user := (^win32.TOKEN_USER)(raw_data(make([]u8, token_user_size, temp_allocator()) or_return))
+	token_user := (^win32.TOKEN_USER)(raw_data(make([]u8, token_user_size, temp_allocator) or_return))
 	if !win32.GetTokenInformation(token_handle, .TokenUser, token_user, token_user_size, &token_user_size) {
 		err = _get_platform_error()
 		return
@@ -643,8 +645,8 @@ _get_process_user :: proc(process_handle: win32.HANDLE, allocator: runtime.Alloc
 		err = _get_platform_error()
 		return
 	}
-	username := win32_utf16_to_utf8(username_w[:username_chrs], temp_allocator()) or_return
-	domain   := win32_utf16_to_utf8(domain_w[:domain_chrs], temp_allocator()) or_return
+	username := win32_utf16_to_utf8(username_w[:username_chrs], temp_allocator) or_return
+	domain   := win32_utf16_to_utf8(domain_w[:domain_chrs], temp_allocator) or_return
 	return strings.concatenate({domain, "\\", username}, allocator)
 }
 

+ 4 - 4
core/os/os2/stat.odin

@@ -73,14 +73,14 @@ last_write_time_by_name :: modification_time_by_path
 
 @(require_results)
 modification_time :: proc(f: ^File) -> (time.Time, Error) {
-	TEMP_ALLOCATOR_GUARD()
-	fi, err := fstat(f, temp_allocator())
+	temp_allocator := TEMP_ALLOCATOR_GUARD({})
+	fi, err := fstat(f, temp_allocator)
 	return fi.modification_time, err
 }
 
 @(require_results)
 modification_time_by_path :: proc(path: string) -> (time.Time, Error) {
-	TEMP_ALLOCATOR_GUARD()
-	fi, err := stat(path, temp_allocator())
+	temp_allocator := TEMP_ALLOCATOR_GUARD({})
+	fi, err := stat(path, temp_allocator)
 	return fi.modification_time, err
 }

+ 4 - 4
core/os/os2/stat_linux.odin

@@ -47,8 +47,8 @@ _fstat_internal :: proc(fd: linux.Fd, allocator: runtime.Allocator) -> (fi: File
 
 // NOTE: _stat and _lstat are using _fstat to avoid a race condition when populating fullpath
 _stat :: proc(name: string, allocator: runtime.Allocator) -> (fi: File_Info, err: Error) {
-	TEMP_ALLOCATOR_GUARD()
-	name_cstr := temp_cstring(name) or_return
+	temp_allocator := TEMP_ALLOCATOR_GUARD({ allocator })
+	name_cstr := clone_to_cstring(name, temp_allocator) or_return
 
 	fd, errno := linux.open(name_cstr, {})
 	if errno != .NONE {
@@ -59,8 +59,8 @@ _stat :: proc(name: string, allocator: runtime.Allocator) -> (fi: File_Info, err
 }
 
 _lstat :: proc(name: string, allocator: runtime.Allocator) -> (fi: File_Info, err: Error) {
-	TEMP_ALLOCATOR_GUARD()
-	name_cstr := temp_cstring(name) or_return
+	temp_allocator := TEMP_ALLOCATOR_GUARD({ allocator })
+	name_cstr := clone_to_cstring(name, temp_allocator) or_return
 
 	fd, errno := linux.open(name_cstr, {.PATH, .NOFOLLOW})
 	if errno != .NONE {

+ 9 - 8
core/os/os2/stat_posix.odin

@@ -69,8 +69,8 @@ _stat :: proc(name: string, allocator: runtime.Allocator) -> (fi: File_Info, err
 		return
 	}
 
-	TEMP_ALLOCATOR_GUARD()
-	cname := temp_cstring(name) or_return
+	temp_allocator := TEMP_ALLOCATOR_GUARD({ allocator })
+	cname := clone_to_cstring(name, temp_allocator) or_return
 
 	fd := posix.open(cname, {})
 	if fd == -1 {
@@ -96,33 +96,34 @@ _lstat :: proc(name: string, allocator: runtime.Allocator) -> (fi: File_Info, er
 		return
 	}
 
-	TEMP_ALLOCATOR_GUARD()
+	temp_allocator := TEMP_ALLOCATOR_GUARD({ allocator })
 
 	// NOTE: can't use realpath or open (+ fcntl F_GETPATH) here because it tries to resolve symlinks.
 
 	// NOTE: This might not be correct when given "/symlink/foo.txt",
 	// you would want that to resolve "/symlink", but not resolve "foo.txt".
 
-	fullpath := clean_path(name, temp_allocator()) or_return
+	fullpath := clean_path(name, temp_allocator) or_return
 	assert(len(fullpath) > 0)
 	switch {
 	case fullpath[0] == '/':
 		// nothing.
 	case fullpath == ".":
-		fullpath = getwd(temp_allocator()) or_return
+		fullpath = getwd(temp_allocator) or_return
 	case len(fullpath) > 1 && fullpath[0] == '.' && fullpath[1] == '/':
 		fullpath = fullpath[2:]
 		fallthrough
 	case:
 		fullpath = concatenate({
-			getwd(temp_allocator()) or_return,
+			getwd(temp_allocator) or_return,
 			"/",
 			fullpath,
-		}, temp_allocator()) or_return
+		}, temp_allocator) or_return
 	}
 
 	stat: posix.stat_t
-	if posix.lstat(temp_cstring(fullpath), &stat) != .OK {
+	c_fullpath := clone_to_cstring(fullpath, temp_allocator) or_return
+	if posix.lstat(c_fullpath, &stat) != .OK {
 		err = _get_platform_error()
 		return
 	}

+ 68 - 45
core/os/os2/stat_windows.odin

@@ -45,15 +45,15 @@ full_path_from_name :: proc(name: string, allocator: runtime.Allocator) -> (path
 		name = "."
 	}
 
-	TEMP_ALLOCATOR_GUARD()
+	temp_allocator := TEMP_ALLOCATOR_GUARD({ allocator })
 
-	p := win32_utf8_to_utf16(name, temp_allocator()) or_return
+	p := win32_utf8_to_utf16(name, temp_allocator) or_return
 
 	n := win32.GetFullPathNameW(raw_data(p), 0, nil, nil)
 	if n == 0 {
 		return "", _get_platform_error()
 	}
-	buf := make([]u16, n+1, temp_allocator())
+	buf := make([]u16, n+1, temp_allocator)
 	n = win32.GetFullPathNameW(raw_data(p), u32(len(buf)), raw_data(buf), nil)
 	if n == 0 {
 		return "", _get_platform_error()
@@ -65,9 +65,9 @@ internal_stat :: proc(name: string, create_file_attributes: u32, allocator: runt
 	if len(name) == 0 {
 		return {}, .Not_Exist
 	}
-	TEMP_ALLOCATOR_GUARD()
+	temp_allocator := TEMP_ALLOCATOR_GUARD({ allocator })
 
-	wname := _fix_long_path(name, temp_allocator()) or_return
+	wname := _fix_long_path(name, temp_allocator) or_return
 	fa: win32.WIN32_FILE_ATTRIBUTE_DATA
 	ok := win32.GetFileAttributesExW(wname, win32.GetFileExInfoStandard, &fa)
 	if ok && fa.dwFileAttributes & win32.FILE_ATTRIBUTE_REPARSE_POINT == 0 {
@@ -137,9 +137,9 @@ _cleanpath_from_handle :: proc(f: ^File, allocator: runtime.Allocator) -> (strin
 		return "", _get_platform_error()
 	}
 
-	TEMP_ALLOCATOR_GUARD()
+	temp_allocator := TEMP_ALLOCATOR_GUARD({ allocator })
 
-	buf := make([]u16, max(n, 260)+1, temp_allocator())
+	buf := make([]u16, max(n, 260)+1, temp_allocator)
 	n = win32.GetFinalPathNameByHandleW(h, raw_data(buf), u32(len(buf)), 0)
 	return _cleanpath_from_buf(buf[:n], allocator)
 }
@@ -155,9 +155,9 @@ _cleanpath_from_handle_u16 :: proc(f: ^File) -> ([]u16, Error) {
 		return nil, _get_platform_error()
 	}
 
-	TEMP_ALLOCATOR_GUARD()
+	temp_allocator := TEMP_ALLOCATOR_GUARD({})
 
-	buf := make([]u16, max(n, 260)+1, temp_allocator())
+	buf := make([]u16, max(n, 260)+1, temp_allocator)
 	n = win32.GetFinalPathNameByHandleW(h, raw_data(buf), u32(len(buf)), 0)
 	return _cleanpath_strip_prefix(buf[:n]), nil
 }
@@ -212,15 +212,11 @@ _file_type_from_create_file :: proc(wname: win32.wstring, create_file_attributes
 }
 
 _file_type_mode_from_file_attributes :: proc(file_attributes: win32.DWORD, h: win32.HANDLE, ReparseTag: win32.DWORD) -> (type: File_Type, mode: int) {
-	// NOTE(Jeroen): We don't translate mode flags for Linux when given to `chmod`.
-	//               Let's not do so for Windows for `chmod` or `read_directory_iterator` either.
-	//               They're *not* portable between Windows and non-Windows platforms.
-	//
-	//               It also leads to information loss as flags like Archive, Hidden and System have no equivalent there.
-	//               We can of course parse them so we can set the `.Symlink` and `.Directory` type, but we shouldn't pretend
-	//               that 0o644 is meaningful when returned as a mode.
-	//               `C:\bootmgr` as an example has attributes read only, hidden, system, archive. In no way is it sensible to replace that with 0o444.
-	mode = int(file_attributes)
+	if file_attributes & win32.FILE_ATTRIBUTE_READONLY != 0 {
+		mode |= 0o444
+	} else {
+		mode |= 0o666
+	}
 
 	is_sym := false
 	if file_attributes & win32.FILE_ATTRIBUTE_REPARSE_POINT == 0 {
@@ -233,6 +229,7 @@ _file_type_mode_from_file_attributes :: proc(file_attributes: win32.DWORD, h: wi
 		type = .Symlink
 	} else if file_attributes & win32.FILE_ATTRIBUTE_DIRECTORY != 0 {
 		type = .Directory
+		mode |= 0o111
 	} else if h != nil {
 		type = file_type(h)
 	}
@@ -329,42 +326,68 @@ _is_reserved_name :: proc(path: string) -> bool {
 	return false
 }
 
-_is_UNC :: proc(path: string) -> bool {
-	return _volume_name_len(path) > 2
-}
-
-_volume_name_len :: proc(path: string) -> int {
+_volume_name_len :: proc(path: string) -> (length: int) {
 	if len(path) < 2 {
 		return 0
 	}
-	c := path[0]
+
 	if path[1] == ':' {
-		switch c {
+		switch path[0] {
 		case 'a'..='z', 'A'..='Z':
 			return 2
 		}
 	}
 
-	// URL: https://msdn.microsoft.com/en-us/library/windows/desktop/aa365247(v=vs.85).aspx
-	if l := len(path); l >= 5 && _is_path_separator(path[0]) && _is_path_separator(path[1]) &&
-		!_is_path_separator(path[2]) && path[2] != '.' {
-		for n := 3; n < l-1; n += 1 {
-			if _is_path_separator(path[n]) {
-				n += 1
-				if !_is_path_separator(path[n]) {
-					if path[n] == '.' {
-						break
-					}
-				}
-				for ; n < l; n += 1 {
-					if _is_path_separator(path[n]) {
-						break
-					}
-				}
-				return n
+	/*
+		See: URL: https://msdn.microsoft.com/en-us/library/windows/desktop/aa365247(v=vs.85).aspx
+		Further allowed paths can be of the form of:
+		- \\server\share or \\server\share\more\path
+		- \\?\C:\...
+		- \\.\PhysicalDriveX
+	*/
+	// Any remaining kind of path has to start with two slashes.
+	if !_is_path_separator(path[0]) || !_is_path_separator(path[1]) {
+		return 0
+	}
+
+	// Device path. The volume name is the whole string
+	if len(path) >= 5 && path[2] == '.' && _is_path_separator(path[3]) {
+		return len(path)
+	}
+
+	// We're a UNC share `\\host\share`, file namespace `\\?\C:` or UNC in file namespace `\\?\\host\share`
+	prefix := 2
+
+	// File namespace.
+	if len(path) >= 5 && path[2] == '?' && _is_path_separator(path[3]) {
+		if _is_path_separator(path[4]) {
+			// `\\?\\` UNC path in file namespace
+			prefix = 5
+		}
+
+		if len(path) >= 6 && path[5] == ':' {
+			switch path[4] {
+			case 'a'..='z', 'A'..='Z':
+				return 6
+			case:
+				return 0
 			}
-			break
 		}
 	}
-	return 0
-}
+
+	// UNC path, minimum version of the volume is `\\h\s` for host, share.
+	// Can also contain an IP address in the host position.
+	slash_count := 0
+	for i in prefix..<len(path) {
+		// Host needs to be at least 1 character
+		if _is_path_separator(path[i]) && i > 0 {
+			slash_count += 1
+
+			if slash_count == 2 {
+				return i
+			}
+		}
+	}
+
+	return len(path)
+}

+ 11 - 9
core/os/os2/temp_file.odin

@@ -15,13 +15,13 @@ MAX_ATTEMPTS :: 1<<13 // Should be enough for everyone, right?
 // The caller must `close` the file once finished with.
 @(require_results)
 create_temp_file :: proc(dir, pattern: string) -> (f: ^File, err: Error) {
-	TEMP_ALLOCATOR_GUARD()
-	dir := dir if dir != "" else temp_directory(temp_allocator()) or_return
+	temp_allocator := TEMP_ALLOCATOR_GUARD({})
+	dir := dir if dir != "" else temp_directory(temp_allocator) or_return
 	prefix, suffix := _prefix_and_suffix(pattern) or_return
 	prefix = temp_join_path(dir, prefix) or_return
 
 	rand_buf: [10]byte
-	name_buf := make([]byte, len(prefix)+len(rand_buf)+len(suffix), temp_allocator())
+	name_buf := make([]byte, len(prefix)+len(rand_buf)+len(suffix), temp_allocator)
 
 	attempts := 0
 	for {
@@ -47,13 +47,13 @@ mkdir_temp :: make_directory_temp
 // If `dir` is an empty tring, `temp_directory()` will be used.
 @(require_results)
 make_directory_temp :: proc(dir, pattern: string, allocator: runtime.Allocator) -> (temp_path: string, err: Error) {
-	TEMP_ALLOCATOR_GUARD()
-	dir := dir if dir != "" else temp_directory(temp_allocator()) or_return
+	temp_allocator := TEMP_ALLOCATOR_GUARD({ allocator })
+	dir := dir if dir != "" else temp_directory(temp_allocator) or_return
 	prefix, suffix := _prefix_and_suffix(pattern) or_return
 	prefix = temp_join_path(dir, prefix) or_return
 
 	rand_buf: [10]byte
-	name_buf := make([]byte, len(prefix)+len(rand_buf)+len(suffix), temp_allocator())
+	name_buf := make([]byte, len(prefix)+len(rand_buf)+len(suffix), temp_allocator)
 
 	attempts := 0
 	for {
@@ -70,7 +70,7 @@ make_directory_temp :: proc(dir, pattern: string, allocator: runtime.Allocator)
 			return "", err
 		}
 		if err == .Not_Exist {
-			if _, serr := stat(dir, temp_allocator()); serr == .Not_Exist {
+			if _, serr := stat(dir, temp_allocator); serr == .Not_Exist {
 				return "", serr
 			}
 		}
@@ -89,9 +89,11 @@ temp_directory :: proc(allocator: runtime.Allocator) -> (string, Error) {
 
 @(private="file")
 temp_join_path :: proc(dir, name: string) -> (string, runtime.Allocator_Error) {
+	temp_allocator := TEMP_ALLOCATOR_GUARD({})
+
 	if len(dir) > 0 && is_path_separator(dir[len(dir)-1]) {
-		return concatenate({dir, name}, temp_allocator(),)
+		return concatenate({dir, name}, temp_allocator,)
 	}
 
-	return concatenate({dir, Path_Separator_String, name}, temp_allocator())
+	return concatenate({dir, Path_Separator_String, name}, temp_allocator)
 }

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

@@ -4,8 +4,8 @@ package os2
 import "base:runtime"
 
 _temp_dir :: proc(allocator: runtime.Allocator) -> (string, runtime.Allocator_Error) {
-	TEMP_ALLOCATOR_GUARD()
-	tmpdir := get_env("TMPDIR", temp_allocator())
+	temp_allocator := TEMP_ALLOCATOR_GUARD({ allocator })
+	tmpdir := get_env("TMPDIR", temp_allocator)
 	if tmpdir == "" {
 		tmpdir = "/tmp"
 	}

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

@@ -9,9 +9,9 @@ _temp_dir :: proc(allocator: runtime.Allocator) -> (string, runtime.Allocator_Er
 	if n == 0 {
 		return "", nil
 	}
-	TEMP_ALLOCATOR_GUARD()
+	temp_allocator := TEMP_ALLOCATOR_GUARD({ allocator })
 
-	b := make([]u16, max(win32.MAX_PATH, n), temp_allocator())
+	b := make([]u16, max(win32.MAX_PATH, n), temp_allocator)
 	n = win32.GetTempPathW(u32(len(b)), raw_data(b))
 
 	if n == 3 && b[1] == ':' && b[2] == '\\' {

+ 11 - 11
core/os/os2/user.odin

@@ -4,27 +4,27 @@ import "base:runtime"
 
 @(require_results)
 user_cache_dir :: proc(allocator: runtime.Allocator) -> (dir: string, err: Error) {
-	TEMP_ALLOCATOR_GUARD()
+	temp_allocator := TEMP_ALLOCATOR_GUARD({ allocator })
 
 	#partial switch ODIN_OS {
 	case .Windows:
-		dir = get_env("LocalAppData", temp_allocator())
+		dir = get_env("LocalAppData", temp_allocator)
 		if dir != "" {
-			dir = clone_string(dir, allocator) or_return
+			dir = clone_string(dir, temp_allocator) or_return
 		}
 	case .Darwin:
-		dir = get_env("HOME", temp_allocator())
+		dir = get_env("HOME", temp_allocator)
 		if dir != "" {
-			dir = concatenate({dir, "/Library/Caches"}, allocator) or_return
+			dir = concatenate({dir, "/Library/Caches"}, temp_allocator) or_return
 		}
 	case: // All other UNIX systems
 		dir = get_env("XDG_CACHE_HOME", allocator)
 		if dir == "" {
-			dir = get_env("HOME", temp_allocator())
+			dir = get_env("HOME", temp_allocator)
 			if dir == "" {
 				return
 			}
-			dir = concatenate({dir, "/.cache"}, allocator) or_return
+			dir = concatenate({dir, "/.cache"}, temp_allocator) or_return
 		}
 	}
 	if dir == "" {
@@ -35,23 +35,23 @@ user_cache_dir :: proc(allocator: runtime.Allocator) -> (dir: string, err: Error
 
 @(require_results)
 user_config_dir :: proc(allocator: runtime.Allocator) -> (dir: string, err: Error) {
-	TEMP_ALLOCATOR_GUARD()
+	temp_allocator := TEMP_ALLOCATOR_GUARD({ allocator })
 
 	#partial switch ODIN_OS {
 	case .Windows:
-		dir = get_env("AppData", temp_allocator())
+		dir = get_env("AppData", temp_allocator)
 		if dir != "" {
 			dir = clone_string(dir, allocator) or_return
 		}
 	case .Darwin:
-		dir = get_env("HOME", temp_allocator())
+		dir = get_env("HOME", temp_allocator)
 		if dir != "" {
 			dir = concatenate({dir, "/.config"}, allocator) or_return
 		}
 	case: // All other UNIX systems
 		dir = get_env("XDG_CONFIG_HOME", allocator)
 		if dir == "" {
-			dir = get_env("HOME", temp_allocator())
+			dir = get_env("HOME", temp_allocator)
 			if dir == "" {
 				return
 			}

+ 33 - 0
core/strconv/strconv.odin

@@ -1095,6 +1095,39 @@ parse_f64_prefix :: proc(str: string) -> (value: f64, nr: int, ok: bool) {
 		return transmute(f64)bits, ok
 	}
 
+	if len(str) > 2 && str[0] == '0' && str[1] == 'h' {
+		nr = 2
+
+		as_int: u64
+		digits: int
+		for r in str[2:] {
+			if r == '_' {
+				nr += 1
+				continue
+			}
+			v := u64(_digit_value(r))
+			if v >= 16 {
+				break
+			}
+			as_int *= 16
+			as_int += v
+			digits += 1
+		}
+		nr += digits
+		ok = len(str) == nr
+
+		switch digits {
+		case 4:
+			value = cast(f64)transmute(f16)cast(u16)as_int
+		case 8:
+			value = cast(f64)transmute(f32)cast(u32)as_int
+		case 16:
+			value = transmute(f64)as_int
+		case:
+			ok = false
+		}
+		return
+	}
 
 	if value, nr, ok = check_special(str); ok {
 		return

+ 1 - 1
core/strings/builder.odin

@@ -311,7 +311,7 @@ Returns:
 - res: A cstring of the Builder's buffer upon success
 - err: An optional allocator error if one occured, `nil` otherwise
 */
-to_cstring :: proc(b: ^Builder) -> (res: cstring, err: mem.Allocator_Error) {
+to_cstring :: proc(b: ^Builder) -> (res: cstring, err: mem.Allocator_Error) #optional_allocator_error {
 	n := append(&b.buf, 0) or_return
 	if n != 1 {
 		return nil, .Out_Of_Memory

+ 1 - 1
core/sys/darwin/Foundation/NSApplication.odin

@@ -99,7 +99,7 @@ Application_setTitle :: proc "c" (self: ^Application, title: ^String) {
 }
 
 @(objc_type=Application, objc_name="mainMenu")
-Window_mainMenu :: proc "c" (self: ^Application) -> ^Menu {
+Application_mainMenu :: proc "c" (self: ^Application) -> ^Menu {
 	return msgSend(^Menu, self, "mainMenu")
 }
 

+ 46 - 0
core/sys/darwin/Foundation/NSArray.odin

@@ -40,3 +40,49 @@ Array_objectAs :: proc "c" (self: ^Array, index: UInteger, $T: typeid) -> T wher
 Array_count :: proc "c" (self: ^Array) -> UInteger {
 	return msgSend(UInteger, self, "count")
 }
+
+
+@(objc_class="NSMutableArray")
+MutableArray :: struct {
+	using _: Copying(MutableArray),
+}
+
+@(objc_type=MutableArray, objc_name="alloc", objc_is_class_method=true)
+MutableArray_alloc :: proc "c" () -> ^MutableArray {
+	return msgSend(^MutableArray, MutableArray, "alloc")
+}
+
+@(objc_type=MutableArray, objc_name="init")
+MutableArray_init :: proc "c" (self: ^MutableArray) -> ^MutableArray {
+	return msgSend(^MutableArray, self, "init")
+}
+
+@(objc_type=MutableArray, objc_name="initWithObjects")
+MutableArray_initWithObjects :: proc "c" (self: ^MutableArray, objects: [^]^Object, count: UInteger) -> ^MutableArray {
+	return msgSend(^MutableArray, self, "initWithObjects:count:", objects, count)
+}
+
+@(objc_type=MutableArray, objc_name="initWithCoder")
+MutableArray_initWithCoder :: proc "c" (self: ^MutableArray, coder: ^Coder) -> ^MutableArray {
+	return msgSend(^MutableArray, self, "initWithCoder:", coder)
+}
+
+@(objc_type=MutableArray, objc_name="object")
+MutableArray_object :: proc "c" (self: ^MutableArray, index: UInteger) -> ^Object {
+	return msgSend(^Object, self, "objectAtIndex:", index)
+}
+@(objc_type=MutableArray, objc_name="objectAs")
+MutableArray_objectAs :: proc "c" (self: ^MutableArray, index: UInteger, $T: typeid) -> T where intrinsics.type_is_pointer(T), intrinsics.type_is_subtype_of(T, ^Object)  {
+	return (T)(MutableArray_object(self, index))
+}
+
+@(objc_type=MutableArray, objc_name="count")
+MutableArray_count :: proc "c" (self: ^MutableArray) -> UInteger {
+	return msgSend(UInteger, self, "count")
+}
+
+
+@(objc_type=MutableArray, objc_name="exchangeObjectAtIndex")
+MutableArray_exchangeObjectAtIndex :: proc "c" (self: ^MutableArray, idx1, idx2: UInteger) {
+	msgSend(nil, self, "exchangeObjectAtIndex:withObjectAtIndex:", idx1, idx2)
+}

+ 528 - 93
core/sys/darwin/Foundation/NSMenu.odin

@@ -2,127 +2,562 @@ package objc_Foundation
 
 import "base:builtin"
 import "base:intrinsics"
+import "core:c"
 
-KeyEquivalentModifierFlag :: enum UInteger {
-	CapsLock   = 16, // Set if Caps Lock key is pressed.
-	Shift      = 17, // Set if Shift key is pressed.
-	Control    = 18, // Set if Control key is pressed.
-	Option     = 19, // Set if Option or Alternate key is pressed.
-	Command    = 20, // Set if Command key is pressed.
-	NumericPad = 21, // Set if any key in the numeric keypad is pressed.
-	Help       = 22, // Set if the Help key is pressed.
-	Function   = 23, // Set if any function key is pressed.
+
+MenuSelectionMode :: enum c.long {
+	Automatic = 0,
+	SelectOne = 1,
+	SelectAny = 2,
 }
-KeyEquivalentModifierMask :: distinct bit_set[KeyEquivalentModifierFlag; UInteger]
 
-// Used to retrieve only the device-independent modifier flags, allowing applications to mask off the device-dependent modifier flags, including event coalescing information.
-KeyEventModifierFlagDeviceIndependentFlagsMask := transmute(KeyEquivalentModifierMask)_KeyEventModifierFlagDeviceIndependentFlagsMask
-@(private) _KeyEventModifierFlagDeviceIndependentFlagsMask := UInteger(0xffff0000)
+MenuPresentationStyle :: enum c.long {
+	Regular = 0,
+	Palette = 1,
+}
 
+UserInterfaceLayoutDirection :: enum c.long {
+	LeftToRight = 0,
+	RightToLeft = 1,
+}
 
-MenuItemCallback :: proc "c" (unused: rawptr, name: SEL, sender: ^Object)
+MenuPropertyItem :: enum c.ulong {
+	Title                    = 0,
+	AttributedTitle          = 1,
+	KeyEquivalent            = 2,
+	Image                    = 3,
+	Enabled                  = 4,
+	AccessibilityDescription = 5,
+}
+MenuProperties :: distinct bit_set[MenuPropertyItem; c.ulong]
 
 
-@(objc_class="NSMenuItem")
-MenuItem :: struct {using _: Object}
+@(objc_class="NSMenu")
+Menu :: struct {using _: Object}
 
-@(objc_type=MenuItem, objc_name="alloc", objc_is_class_method=true)
-MenuItem_alloc :: proc "c" () -> ^MenuItem {
-	return msgSend(^MenuItem, MenuItem, "alloc")
+@(objc_type=Menu, objc_name="init")
+Menu_init :: proc "c" (self: ^Menu) -> ^Menu {
+	return msgSend(^Menu, self, "init")
 }
 
-@(objc_type=MenuItem, objc_name="registerActionCallback", objc_is_class_method=true)
-MenuItem_registerActionCallback :: proc "c" (name: cstring, callback: MenuItemCallback) -> SEL {
-	s := string(name)
-	n := len(s)
-	sel: SEL
-	if n > 0 && s[n-1] != ':' {
-		col_name := intrinsics.alloca(n+2, 1)
-		builtin.copy(col_name[:n], s)
-		col_name[n] = ':'
-		col_name[n+1] = 0
-		sel = sel_registerName(cstring(col_name))
-	} else {
-		sel = sel_registerName(name)
-	}
-	if callback != nil {
-		class_addMethod(intrinsics.objc_find_class("NSObject"), sel, auto_cast callback, "v@:@")
-	}
-	return sel
-}
 
-@(objc_type=MenuItem, objc_name="separatorItem", objc_is_class_method=true)
-MenuItem_separatorItem :: proc "c" () -> ^MenuItem {
-	return msgSend(^MenuItem, MenuItem, "separatorItem")
+@(objc_type=Menu, objc_name="initWithTitle")
+Menu_initWithTitle :: #force_inline proc "c" (self: ^Menu, title: ^String) -> ^Menu {
+	return msgSend(^Menu, self, "initWithTitle:", title)
 }
-
-@(objc_type=MenuItem, objc_name="init")
-MenuItem_init :: proc "c" (self: ^MenuItem) -> ^MenuItem {
-	return msgSend(^MenuItem, self, "init")
+@(objc_type=Menu, objc_name="initWithCoder")
+Menu_initWithCoder :: #force_inline proc "c" (self: ^Menu, coder: ^Coder) -> ^Menu {
+	return msgSend(^Menu, self, "initWithCoder:", coder)
 }
-
-@(objc_type=MenuItem, objc_name="initWithTitle")
-MenuItem_initWithTitle :: proc "c" (self: ^MenuItem, title: ^String, action: SEL, keyEquivalent: ^String) -> ^MenuItem {
-	return msgSend(^MenuItem, self, "initWithTitle:action:keyEquivalent:", title, action, keyEquivalent)
+@(objc_type=Menu, objc_name="popUpContextMenu_withEvent_forView", objc_is_class_method=true)
+Menu_popUpContextMenu_withEvent_forView :: #force_inline proc "c" (menu: ^Menu, event: ^Event, view: ^View) {
+	msgSend(nil, Menu, "popUpContextMenu:withEvent:forView:", menu, event, view)
 }
-
-@(objc_type=MenuItem, objc_name="setKeyEquivalentModifierMask")
-MenuItem_setKeyEquivalentModifierMask :: proc "c" (self: ^MenuItem, modifierMask: KeyEquivalentModifierMask) {
-	msgSend(nil, self, "setKeyEquivalentModifierMask:", modifierMask)
+// @(objc_type=Menu, objc_name="popUpContextMenu_withEvent_forView_withFont", objc_is_class_method=true)
+// Menu_popUpContextMenu_withEvent_forView_withFont :: #force_inline proc "c" (menu: ^Menu, event: ^Event, view: ^View, font: ^Font) {
+// 	msgSend(nil, Menu, "popUpContextMenu:withEvent:forView:withFont:", menu, event, view, font)
+// }
+@(objc_type=Menu, objc_name="popUpMenuPositioningItem")
+Menu_popUpMenuPositioningItem :: #force_inline proc "c" (self: ^Menu, item: ^MenuItem, location: Point, view: ^View) -> bool {
+	return msgSend(bool, self, "popUpMenuPositioningItem:atLocation:inView:", item, location, view)
 }
-
-@(objc_type=MenuItem, objc_name="keyEquivalentModifierMask")
-MenuItem_keyEquivalentModifierMask :: proc "c" (self: ^MenuItem) -> KeyEquivalentModifierMask {
-	return msgSend(KeyEquivalentModifierMask, self, "keyEquivalentModifierMask")
+@(objc_type=Menu, objc_name="setMenuBarVisible", objc_is_class_method=true)
+Menu_setMenuBarVisible :: #force_inline proc "c" (visible: bool) {
+	msgSend(nil, Menu, "setMenuBarVisible:", visible)
 }
-
-@(objc_type=MenuItem, objc_name="setSubmenu")
-MenuItem_setSubmenu :: proc "c" (self: ^MenuItem, submenu: ^Menu) {
-	msgSend(nil, self, "setSubmenu:", submenu)
+@(objc_type=Menu, objc_name="menuBarVisible", objc_is_class_method=true)
+Menu_menuBarVisible :: #force_inline proc "c" () -> bool {
+	return msgSend(bool, Menu, "menuBarVisible")
 }
-
-@(objc_type=MenuItem, objc_name="title")
-MenuItem_title :: proc "c" (self: ^MenuItem) -> ^String {
+@(objc_type=Menu, objc_name="insertItem")
+Menu_insertItem :: #force_inline proc "c" (self: ^Menu, newItem: ^MenuItem, index: Integer) {
+	msgSend(nil, self, "insertItem:atIndex:", newItem, index)
+}
+@(objc_type=Menu, objc_name="addItem")
+Menu_addItem :: #force_inline proc "c" (self: ^Menu, newItem: ^MenuItem) {
+	msgSend(nil, self, "addItem:", newItem)
+}
+@(objc_type=Menu, objc_name="insertItemWithTitle")
+Menu_insertItemWithTitle :: #force_inline proc "c" (self: ^Menu, string: ^String, selector: SEL, charCode: ^String, index: Integer) -> ^MenuItem {
+	return msgSend(^MenuItem, self, "insertItemWithTitle:action:keyEquivalent:atIndex:", string, selector, charCode, index)
+}
+@(objc_type=Menu, objc_name="addItemWithTitle")
+Menu_addItemWithTitle :: #force_inline proc "c" (self: ^Menu, string: ^String, selector: SEL, charCode: ^String) -> ^MenuItem {
+	return msgSend(^MenuItem, self, "addItemWithTitle:action:keyEquivalent:", string, selector, charCode)
+}
+@(objc_type=Menu, objc_name="removeItemAtIndex")
+Menu_removeItemAtIndex :: #force_inline proc "c" (self: ^Menu, index: Integer) {
+	msgSend(nil, self, "removeItemAtIndex:", index)
+}
+@(objc_type=Menu, objc_name="removeItem")
+Menu_removeItem :: #force_inline proc "c" (self: ^Menu, item: ^MenuItem) {
+	msgSend(nil, self, "removeItem:", item)
+}
+@(objc_type=Menu, objc_name="setSubmenu")
+Menu_setSubmenu :: #force_inline proc "c" (self: ^Menu, menu: ^Menu, item: ^MenuItem) {
+	msgSend(nil, self, "setSubmenu:forItem:", menu, item)
+}
+@(objc_type=Menu, objc_name="removeAllItems")
+Menu_removeAllItems :: #force_inline proc "c" (self: ^Menu) {
+	msgSend(nil, self, "removeAllItems")
+}
+@(objc_type=Menu, objc_name="itemAtIndex")
+Menu_itemAtIndex :: #force_inline proc "c" (self: ^Menu, index: Integer) -> ^MenuItem {
+	return msgSend(^MenuItem, self, "itemAtIndex:", index)
+}
+@(objc_type=Menu, objc_name="indexOfItem")
+Menu_indexOfItem :: #force_inline proc "c" (self: ^Menu, item: ^MenuItem) -> Integer {
+	return msgSend(Integer, self, "indexOfItem:", item)
+}
+@(objc_type=Menu, objc_name="indexOfItemWithTitle")
+Menu_indexOfItemWithTitle :: #force_inline proc "c" (self: ^Menu, title: ^String) -> Integer {
+	return msgSend(Integer, self, "indexOfItemWithTitle:", title)
+}
+@(objc_type=Menu, objc_name="indexOfItemWithTag")
+Menu_indexOfItemWithTag :: #force_inline proc "c" (self: ^Menu, tag: Integer) -> Integer {
+	return msgSend(Integer, self, "indexOfItemWithTag:", tag)
+}
+@(objc_type=Menu, objc_name="indexOfItemWithRepresentedObject")
+Menu_indexOfItemWithRepresentedObject :: #force_inline proc "c" (self: ^Menu, object: id) -> Integer {
+	return msgSend(Integer, self, "indexOfItemWithRepresentedObject:", object)
+}
+@(objc_type=Menu, objc_name="indexOfItemWithSubmenu")
+Menu_indexOfItemWithSubmenu :: #force_inline proc "c" (self: ^Menu, submenu: ^Menu) -> Integer {
+	return msgSend(Integer, self, "indexOfItemWithSubmenu:", submenu)
+}
+@(objc_type=Menu, objc_name="indexOfItemWithTarget")
+Menu_indexOfItemWithTarget :: #force_inline proc "c" (self: ^Menu, target: id, actionSelector: SEL) -> Integer {
+	return msgSend(Integer, self, "indexOfItemWithTarget:andAction:", target, actionSelector)
+}
+@(objc_type=Menu, objc_name="itemWithTitle")
+Menu_itemWithTitle :: #force_inline proc "c" (self: ^Menu, title: ^String) -> ^MenuItem {
+	return msgSend(^MenuItem, self, "itemWithTitle:", title)
+}
+@(objc_type=Menu, objc_name="itemWithTag")
+Menu_itemWithTag :: #force_inline proc "c" (self: ^Menu, tag: Integer) -> ^MenuItem {
+	return msgSend(^MenuItem, self, "itemWithTag:", tag)
+}
+@(objc_type=Menu, objc_name="update")
+Menu_update :: #force_inline proc "c" (self: ^Menu) {
+	msgSend(nil, self, "update")
+}
+@(objc_type=Menu, objc_name="performKeyEquivalent")
+Menu_performKeyEquivalent :: #force_inline proc "c" (self: ^Menu, event: ^Event) -> bool {
+	return msgSend(bool, self, "performKeyEquivalent:", event)
+}
+@(objc_type=Menu, objc_name="itemChanged")
+Menu_itemChanged :: #force_inline proc "c" (self: ^Menu, item: ^MenuItem) {
+	msgSend(nil, self, "itemChanged:", item)
+}
+@(objc_type=Menu, objc_name="performActionForItemAtIndex")
+Menu_performActionForItemAtIndex :: #force_inline proc "c" (self: ^Menu, index: Integer) {
+	msgSend(nil, self, "performActionForItemAtIndex:", index)
+}
+@(objc_type=Menu, objc_name="cancelTracking")
+Menu_cancelTracking :: #force_inline proc "c" (self: ^Menu) {
+	msgSend(nil, self, "cancelTracking")
+}
+@(objc_type=Menu, objc_name="cancelTrackingWithoutAnimation")
+Menu_cancelTrackingWithoutAnimation :: #force_inline proc "c" (self: ^Menu) {
+	msgSend(nil, self, "cancelTrackingWithoutAnimation")
+}
+@(objc_type=Menu, objc_name="title")
+Menu_title :: #force_inline proc "c" (self: ^Menu) -> ^String {
 	return msgSend(^String, self, "title")
 }
-
-@(objc_type=MenuItem, objc_name="setTitle")
-MenuItem_setTitle :: proc "c" (self: ^MenuItem, title: ^String) -> ^String {
-	return msgSend(^String, self, "title:", title)
+@(objc_type=Menu, objc_name="setTitle")
+Menu_setTitle :: #force_inline proc "c" (self: ^Menu, title: ^String) {
+	msgSend(nil, self, "setTitle:", title)
+}
+@(objc_type=Menu, objc_name="supermenu")
+Menu_supermenu :: #force_inline proc "c" (self: ^Menu) -> ^Menu {
+	return msgSend(^Menu, self, "supermenu")
+}
+@(objc_type=Menu, objc_name="setSupermenu")
+Menu_setSupermenu :: #force_inline proc "c" (self: ^Menu, supermenu: ^Menu) {
+	msgSend(nil, self, "setSupermenu:", supermenu)
+}
+@(objc_type=Menu, objc_name="itemArray")
+Menu_itemArray :: #force_inline proc "c" (self: ^Menu) -> ^Array {
+	return msgSend(^Array, self, "itemArray")
+}
+@(objc_type=Menu, objc_name="setItemArray")
+Menu_setItemArray :: #force_inline proc "c" (self: ^Menu, itemArray: ^Array) {
+	msgSend(nil, self, "setItemArray:", itemArray)
+}
+@(objc_type=Menu, objc_name="numberOfItems")
+Menu_numberOfItems :: #force_inline proc "c" (self: ^Menu) -> Integer {
+	return msgSend(Integer, self, "numberOfItems")
+}
+@(objc_type=Menu, objc_name="autoenablesItems")
+Menu_autoenablesItems :: #force_inline proc "c" (self: ^Menu) -> bool {
+	return msgSend(bool, self, "autoenablesItems")
+}
+@(objc_type=Menu, objc_name="setAutoenablesItems")
+Menu_setAutoenablesItems :: #force_inline proc "c" (self: ^Menu, autoenablesItems: bool) {
+	msgSend(nil, self, "setAutoenablesItems:", autoenablesItems)
+}
+@(objc_type=Menu, objc_name="delegate")
+Menu_delegate :: #force_inline proc "c" (self: ^Menu) -> ^MenuDelegate {
+	return msgSend(^MenuDelegate, self, "delegate")
+}
+@(objc_type=Menu, objc_name="setDelegate")
+Menu_setDelegate :: #force_inline proc "c" (self: ^Menu, delegate: ^MenuDelegate) {
+	msgSend(nil, self, "setDelegate:", delegate)
+}
+@(objc_type=Menu, objc_name="menuBarHeight")
+Menu_menuBarHeight :: #force_inline proc "c" (self: ^Menu) -> Float {
+	return msgSend(Float, self, "menuBarHeight")
+}
+@(objc_type=Menu, objc_name="highlightedItem")
+Menu_highlightedItem :: #force_inline proc "c" (self: ^Menu) -> ^MenuItem {
+	return msgSend(^MenuItem, self, "highlightedItem")
+}
+@(objc_type=Menu, objc_name="minimumWidth")
+Menu_minimumWidth :: #force_inline proc "c" (self: ^Menu) -> Float {
+	return msgSend(Float, self, "minimumWidth")
+}
+@(objc_type=Menu, objc_name="setMinimumWidth")
+Menu_setMinimumWidth :: #force_inline proc "c" (self: ^Menu, minimumWidth: Float) {
+	msgSend(nil, self, "setMinimumWidth:", minimumWidth)
+}
+@(objc_type=Menu, objc_name="size")
+Menu_size :: #force_inline proc "c" (self: ^Menu) -> Size {
+	return msgSend(Size, self, "size")
+}
+// @(objc_type=Menu, objc_name="font")
+// Menu_font :: #force_inline proc "c" (self: ^Menu) -> ^Font {
+// 	return msgSend(^Font, self, "font")
+// }
+// @(objc_type=Menu, objc_name="setFont")
+// Menu_setFont :: #force_inline proc "c" (self: ^Menu, font: ^Font) {
+// 	msgSend(nil, self, "setFont:", font)
+// }
+@(objc_type=Menu, objc_name="allowsContextMenuPlugIns")
+Menu_allowsContextMenuPlugIns :: #force_inline proc "c" (self: ^Menu) -> bool {
+	return msgSend(bool, self, "allowsContextMenuPlugIns")
+}
+@(objc_type=Menu, objc_name="setAllowsContextMenuPlugIns")
+Menu_setAllowsContextMenuPlugIns :: #force_inline proc "c" (self: ^Menu, allowsContextMenuPlugIns: bool) {
+	msgSend(nil, self, "setAllowsContextMenuPlugIns:", allowsContextMenuPlugIns)
+}
+@(objc_type=Menu, objc_name="showsStateColumn")
+Menu_showsStateColumn :: #force_inline proc "c" (self: ^Menu) -> bool {
+	return msgSend(bool, self, "showsStateColumn")
+}
+@(objc_type=Menu, objc_name="setShowsStateColumn")
+Menu_setShowsStateColumn :: #force_inline proc "c" (self: ^Menu, showsStateColumn: bool) {
+	msgSend(nil, self, "setShowsStateColumn:", showsStateColumn)
+}
+@(objc_type=Menu, objc_name="userInterfaceLayoutDirection")
+Menu_userInterfaceLayoutDirection :: #force_inline proc "c" (self: ^Menu) -> UserInterfaceLayoutDirection {
+	return msgSend(UserInterfaceLayoutDirection, self, "userInterfaceLayoutDirection")
+}
+@(objc_type=Menu, objc_name="setUserInterfaceLayoutDirection")
+Menu_setUserInterfaceLayoutDirection :: #force_inline proc "c" (self: ^Menu, userInterfaceLayoutDirection: UserInterfaceLayoutDirection) {
+	msgSend(nil, self, "setUserInterfaceLayoutDirection:", userInterfaceLayoutDirection)
+}
+@(objc_type=Menu, objc_name="paletteMenuWithColors_titles_selectionHandler", objc_is_class_method=true)
+Menu_paletteMenuWithColors_titles_selectionHandler :: #force_inline proc "c" (colors: ^Array, itemTitles: ^Array, onSelectionChange: proc "c" (_arg_0: ^Menu)) -> ^Menu {
+	return msgSend(^Menu, Menu, "paletteMenuWithColors:titles:selectionHandler:", colors, itemTitles, onSelectionChange)
+}
+// @(objc_type=Menu, objc_name="paletteMenuWithColors_titles_templateImage_selectionHandler", objc_is_class_method=true)
+// Menu_paletteMenuWithColors_titles_templateImage_selectionHandler :: #force_inline proc "c" (colors: ^Array, itemTitles: ^Array, image: ^Image, onSelectionChange: proc "c" (_arg_0: ^Menu)) -> ^Menu {
+// 	return msgSend(^Menu, Menu, "paletteMenuWithColors:titles:templateImage:selectionHandler:", colors, itemTitles, image, onSelectionChange)
+// }
+@(objc_type=Menu, objc_name="presentationStyle")
+Menu_presentationStyle :: #force_inline proc "c" (self: ^Menu) -> MenuPresentationStyle {
+	return msgSend(MenuPresentationStyle, self, "presentationStyle")
+}
+@(objc_type=Menu, objc_name="setPresentationStyle")
+Menu_setPresentationStyle :: #force_inline proc "c" (self: ^Menu, presentationStyle: MenuPresentationStyle) {
+	msgSend(nil, self, "setPresentationStyle:", presentationStyle)
+}
+@(objc_type=Menu, objc_name="selectionMode")
+Menu_selectionMode :: #force_inline proc "c" (self: ^Menu) -> MenuSelectionMode {
+	return msgSend(MenuSelectionMode, self, "selectionMode")
+}
+@(objc_type=Menu, objc_name="setSelectionMode")
+Menu_setSelectionMode :: #force_inline proc "c" (self: ^Menu, selectionMode: MenuSelectionMode) {
+	msgSend(nil, self, "setSelectionMode:", selectionMode)
+}
+@(objc_type=Menu, objc_name="selectedItems")
+Menu_selectedItems :: #force_inline proc "c" (self: ^Menu) -> ^Array {
+	return msgSend(^Array, self, "selectedItems")
+}
+@(objc_type=Menu, objc_name="setSelectedItems")
+Menu_setSelectedItems :: #force_inline proc "c" (self: ^Menu, selectedItems: ^Array) {
+	msgSend(nil, self, "setSelectedItems:", selectedItems)
+}
+@(objc_type=Menu, objc_name="submenuAction")
+Menu_submenuAction :: #force_inline proc "c" (self: ^Menu, sender: id) {
+	msgSend(nil, self, "submenuAction:", sender)
+}
+@(objc_type=Menu, objc_name="propertiesToUpdate")
+Menu_propertiesToUpdate :: #force_inline proc "c" (self: ^Menu) -> MenuProperties {
+	return msgSend(MenuProperties, self, "propertiesToUpdate")
+}
+@(objc_type=Menu, objc_name="setMenuRepresentation")
+Menu_setMenuRepresentation :: #force_inline proc "c" (self: ^Menu, menuRep: id) {
+	msgSend(nil, self, "setMenuRepresentation:", menuRep)
+}
+@(objc_type=Menu, objc_name="menuRepresentation")
+Menu_menuRepresentation :: #force_inline proc "c" (self: ^Menu) -> id {
+	return msgSend(id, self, "menuRepresentation")
+}
+@(objc_type=Menu, objc_name="setContextMenuRepresentation")
+Menu_setContextMenuRepresentation :: #force_inline proc "c" (self: ^Menu, menuRep: id) {
+	msgSend(nil, self, "setContextMenuRepresentation:", menuRep)
+}
+@(objc_type=Menu, objc_name="contextMenuRepresentation")
+Menu_contextMenuRepresentation :: #force_inline proc "c" (self: ^Menu) -> id {
+	return msgSend(id, self, "contextMenuRepresentation")
+}
+@(objc_type=Menu, objc_name="setTearOffMenuRepresentation")
+Menu_setTearOffMenuRepresentation :: #force_inline proc "c" (self: ^Menu, menuRep: id) {
+	msgSend(nil, self, "setTearOffMenuRepresentation:", menuRep)
+}
+@(objc_type=Menu, objc_name="tearOffMenuRepresentation")
+Menu_tearOffMenuRepresentation :: #force_inline proc "c" (self: ^Menu) -> id {
+	return msgSend(id, self, "tearOffMenuRepresentation")
+}
+@(objc_type=Menu, objc_name="menuZone", objc_is_class_method=true)
+Menu_menuZone :: #force_inline proc "c" () -> ^Zone {
+	return msgSend(^Zone, Menu, "menuZone")
+}
+@(objc_type=Menu, objc_name="setMenuZone", objc_is_class_method=true)
+Menu_setMenuZone :: #force_inline proc "c" (zone: ^Zone) {
+	msgSend(nil, Menu, "setMenuZone:", zone)
+}
+@(objc_type=Menu, objc_name="attachedMenu")
+Menu_attachedMenu :: #force_inline proc "c" (self: ^Menu) -> ^Menu {
+	return msgSend(^Menu, self, "attachedMenu")
+}
+@(objc_type=Menu, objc_name="isAttached")
+Menu_isAttached :: #force_inline proc "c" (self: ^Menu) -> bool {
+	return msgSend(bool, self, "isAttached")
+}
+@(objc_type=Menu, objc_name="sizeToFit")
+Menu_sizeToFit :: #force_inline proc "c" (self: ^Menu) {
+	msgSend(nil, self, "sizeToFit")
+}
+@(objc_type=Menu, objc_name="locationForSubmenu")
+Menu_locationForSubmenu :: #force_inline proc "c" (self: ^Menu, submenu: ^Menu) -> Point {
+	return msgSend(Point, self, "locationForSubmenu:", submenu)
+}
+@(objc_type=Menu, objc_name="helpRequested")
+Menu_helpRequested :: #force_inline proc "c" (self: ^Menu, eventPtr: ^Event) {
+	msgSend(nil, self, "helpRequested:", eventPtr)
+}
+@(objc_type=Menu, objc_name="menuChangedMessagesEnabled")
+Menu_menuChangedMessagesEnabled :: #force_inline proc "c" (self: ^Menu) -> bool {
+	return msgSend(bool, self, "menuChangedMessagesEnabled")
+}
+@(objc_type=Menu, objc_name="setMenuChangedMessagesEnabled")
+Menu_setMenuChangedMessagesEnabled :: #force_inline proc "c" (self: ^Menu, menuChangedMessagesEnabled: bool) {
+	msgSend(nil, self, "setMenuChangedMessagesEnabled:", menuChangedMessagesEnabled)
+}
+@(objc_type=Menu, objc_name="isTornOff")
+Menu_isTornOff :: #force_inline proc "c" (self: ^Menu) -> bool {
+	return msgSend(bool, self, "isTornOff")
+}
+@(objc_type=Menu, objc_name="load", objc_is_class_method=true)
+Menu_load :: #force_inline proc "c" () {
+	msgSend(nil, Menu, "load")
+}
+@(objc_type=Menu, objc_name="initialize", objc_is_class_method=true)
+Menu_initialize :: #force_inline proc "c" () {
+	msgSend(nil, Menu, "initialize")
+}
+@(objc_type=Menu, objc_name="new", objc_is_class_method=true)
+Menu_new :: #force_inline proc "c" () -> ^Menu {
+	return msgSend(^Menu, Menu, "new")
+}
+@(objc_type=Menu, objc_name="allocWithZone", objc_is_class_method=true)
+Menu_allocWithZone :: #force_inline proc "c" (zone: ^Zone) -> ^Menu {
+	return msgSend(^Menu, Menu, "allocWithZone:", zone)
 }
-
-
-
-@(objc_class="NSMenu")
-Menu :: struct {using _: Object}
-
 @(objc_type=Menu, objc_name="alloc", objc_is_class_method=true)
-Menu_alloc :: proc "c" () -> ^Menu {
+Menu_alloc :: #force_inline proc "c" () -> ^Menu {
 	return msgSend(^Menu, Menu, "alloc")
 }
+@(objc_type=Menu, objc_name="copyWithZone", objc_is_class_method=true)
+Menu_copyWithZone :: #force_inline proc "c" (zone: ^Zone) -> id {
+	return msgSend(id, Menu, "copyWithZone:", zone)
+}
+@(objc_type=Menu, objc_name="mutableCopyWithZone", objc_is_class_method=true)
+Menu_mutableCopyWithZone :: #force_inline proc "c" (zone: ^Zone) -> id {
+	return msgSend(id, Menu, "mutableCopyWithZone:", zone)
+}
+@(objc_type=Menu, objc_name="instancesRespondToSelector", objc_is_class_method=true)
+Menu_instancesRespondToSelector :: #force_inline proc "c" (aSelector: SEL) -> bool {
+	return msgSend(bool, Menu, "instancesRespondToSelector:", aSelector)
+}
+@(objc_type=Menu, objc_name="conformsToProtocol", objc_is_class_method=true)
+Menu_conformsToProtocol :: #force_inline proc "c" (protocol: ^Protocol) -> bool {
+	return msgSend(bool, Menu, "conformsToProtocol:", protocol)
+}
+@(objc_type=Menu, objc_name="instanceMethodForSelector", objc_is_class_method=true)
+Menu_instanceMethodForSelector :: #force_inline proc "c" (aSelector: SEL) -> IMP {
+	return msgSend(IMP, Menu, "instanceMethodForSelector:", aSelector)
+}
+// @(objc_type=Menu, objc_name="instanceMethodSignatureForSelector", objc_is_class_method=true)
+// Menu_instanceMethodSignatureForSelector :: #force_inline proc "c" (aSelector: SEL) -> ^MethodSignature {
+// 	return msgSend(^MethodSignature, Menu, "instanceMethodSignatureForSelector:", aSelector)
+// }
+@(objc_type=Menu, objc_name="isSubclassOfClass", objc_is_class_method=true)
+Menu_isSubclassOfClass :: #force_inline proc "c" (aClass: Class) -> bool {
+	return msgSend(bool, Menu, "isSubclassOfClass:", aClass)
+}
+@(objc_type=Menu, objc_name="resolveClassMethod", objc_is_class_method=true)
+Menu_resolveClassMethod :: #force_inline proc "c" (sel: SEL) -> bool {
+	return msgSend(bool, Menu, "resolveClassMethod:", sel)
+}
+@(objc_type=Menu, objc_name="resolveInstanceMethod", objc_is_class_method=true)
+Menu_resolveInstanceMethod :: #force_inline proc "c" (sel: SEL) -> bool {
+	return msgSend(bool, Menu, "resolveInstanceMethod:", sel)
+}
+@(objc_type=Menu, objc_name="hash", objc_is_class_method=true)
+Menu_hash :: #force_inline proc "c" () -> UInteger {
+	return msgSend(UInteger, Menu, "hash")
+}
+@(objc_type=Menu, objc_name="superclass", objc_is_class_method=true)
+Menu_superclass :: #force_inline proc "c" () -> Class {
+	return msgSend(Class, Menu, "superclass")
+}
+@(objc_type=Menu, objc_name="class", objc_is_class_method=true)
+Menu_class :: #force_inline proc "c" () -> Class {
+	return msgSend(Class, Menu, "class")
+}
+@(objc_type=Menu, objc_name="description", objc_is_class_method=true)
+Menu_description :: #force_inline proc "c" () -> ^String {
+	return msgSend(^String, Menu, "description")
+}
+@(objc_type=Menu, objc_name="debugDescription", objc_is_class_method=true)
+Menu_debugDescription :: #force_inline proc "c" () -> ^String {
+	return msgSend(^String, Menu, "debugDescription")
+}
+@(objc_type=Menu, objc_name="version", objc_is_class_method=true)
+Menu_version :: #force_inline proc "c" () -> Integer {
+	return msgSend(Integer, Menu, "version")
+}
+@(objc_type=Menu, objc_name="setVersion", objc_is_class_method=true)
+Menu_setVersion :: #force_inline proc "c" (aVersion: Integer) {
+	msgSend(nil, Menu, "setVersion:", aVersion)
+}
+@(objc_type=Menu, objc_name="poseAsClass", objc_is_class_method=true)
+Menu_poseAsClass :: #force_inline proc "c" (aClass: Class) {
+	msgSend(nil, Menu, "poseAsClass:", aClass)
+}
+@(objc_type=Menu, objc_name="cancelPreviousPerformRequestsWithTarget_selector_object", objc_is_class_method=true)
+Menu_cancelPreviousPerformRequestsWithTarget_selector_object :: #force_inline proc "c" (aTarget: id, aSelector: SEL, anArgument: id) {
+	msgSend(nil, Menu, "cancelPreviousPerformRequestsWithTarget:selector:object:", aTarget, aSelector, anArgument)
+}
+@(objc_type=Menu, objc_name="cancelPreviousPerformRequestsWithTarget_", objc_is_class_method=true)
+Menu_cancelPreviousPerformRequestsWithTarget_ :: #force_inline proc "c" (aTarget: id) {
+	msgSend(nil, Menu, "cancelPreviousPerformRequestsWithTarget:", aTarget)
+}
+@(objc_type=Menu, objc_name="accessInstanceVariablesDirectly", objc_is_class_method=true)
+Menu_accessInstanceVariablesDirectly :: #force_inline proc "c" () -> bool {
+	return msgSend(bool, Menu, "accessInstanceVariablesDirectly")
+}
+@(objc_type=Menu, objc_name="useStoredAccessor", objc_is_class_method=true)
+Menu_useStoredAccessor :: #force_inline proc "c" () -> bool {
+	return msgSend(bool, Menu, "useStoredAccessor")
+}
+@(objc_type=Menu, objc_name="keyPathsForValuesAffectingValueForKey", objc_is_class_method=true)
+Menu_keyPathsForValuesAffectingValueForKey :: #force_inline proc "c" (key: ^String) -> ^Set {
+	return msgSend(^Set, Menu, "keyPathsForValuesAffectingValueForKey:", key)
+}
+@(objc_type=Menu, objc_name="automaticallyNotifiesObserversForKey", objc_is_class_method=true)
+Menu_automaticallyNotifiesObserversForKey :: #force_inline proc "c" (key: ^String) -> bool {
+	return msgSend(bool, Menu, "automaticallyNotifiesObserversForKey:", key)
+}
+@(objc_type=Menu, objc_name="setKeys", objc_is_class_method=true)
+Menu_setKeys :: #force_inline proc "c" (keys: ^Array, dependentKey: ^String) {
+	msgSend(nil, Menu, "setKeys:triggerChangeNotificationsForDependentKey:", keys, dependentKey)
+}
+@(objc_type=Menu, objc_name="classFallbacksForKeyedArchiver", objc_is_class_method=true)
+Menu_classFallbacksForKeyedArchiver :: #force_inline proc "c" () -> ^Array {
+	return msgSend(^Array, Menu, "classFallbacksForKeyedArchiver")
+}
+@(objc_type=Menu, objc_name="classForKeyedUnarchiver", objc_is_class_method=true)
+Menu_classForKeyedUnarchiver :: #force_inline proc "c" () -> Class {
+	return msgSend(Class, Menu, "classForKeyedUnarchiver")
+}
+@(objc_type=Menu, objc_name="exposeBinding", objc_is_class_method=true)
+Menu_exposeBinding :: #force_inline proc "c" (binding: ^String) {
+	msgSend(nil, Menu, "exposeBinding:", binding)
+}
+@(objc_type=Menu, objc_name="setDefaultPlaceholder", objc_is_class_method=true)
+Menu_setDefaultPlaceholder :: #force_inline proc "c" (placeholder: id, marker: id, binding: ^String) {
+	msgSend(nil, Menu, "setDefaultPlaceholder:forMarker:withBinding:", placeholder, marker, binding)
+}
+@(objc_type=Menu, objc_name="defaultPlaceholderForMarker", objc_is_class_method=true)
+Menu_defaultPlaceholderForMarker :: #force_inline proc "c" (marker: id, binding: ^String) -> id {
+	return msgSend(id, Menu, "defaultPlaceholderForMarker:withBinding:", marker, binding)
+}
+@(objc_type=Menu, objc_name="popUpContextMenu")
+Menu_popUpContextMenu :: proc {
+	Menu_popUpContextMenu_withEvent_forView,
+	// Menu_popUpContextMenu_withEvent_forView_withFont,
+}
 
-@(objc_type=Menu, objc_name="init")
-Menu_init :: proc "c" (self: ^Menu) -> ^Menu {
-	return msgSend(^Menu, self, "init")
+@(objc_type=Menu, objc_name="paletteMenuWithColors")
+Menu_paletteMenuWithColors :: proc {
+	Menu_paletteMenuWithColors_titles_selectionHandler,
+	// Menu_paletteMenuWithColors_titles_templateImage_selectionHandler,
 }
 
-@(objc_type=Menu, objc_name="initWithTitle")
-Menu_initWithTitle :: proc "c" (self: ^Menu, title: ^String) -> ^Menu {
-	return msgSend(^Menu, self, "initWithTitle:", title)
+@(objc_type=Menu, objc_name="cancelPreviousPerformRequestsWithTarget")
+Menu_cancelPreviousPerformRequestsWithTarget :: proc {
+	Menu_cancelPreviousPerformRequestsWithTarget_selector_object,
+	Menu_cancelPreviousPerformRequestsWithTarget_,
 }
 
 
-@(objc_type=Menu, objc_name="addItem")
-Menu_addItem :: proc "c" (self: ^Menu, item: ^MenuItem) {
-	msgSend(nil, self, "addItem:", item)
-}
 
-@(objc_type=Menu, objc_name="addItemWithTitle")
-Menu_addItemWithTitle :: proc "c" (self: ^Menu, title: ^String, selector: SEL, keyEquivalent: ^String) -> ^MenuItem {
-	return msgSend(^MenuItem, self, "addItemWithTitle:action:keyEquivalent:", title, selector, keyEquivalent)
-}
 
-@(objc_type=Menu, objc_name="itemArray")
-Menu_itemArray :: proc "c" (self: ^Menu) -> ^Array {
-	return msgSend(^Array, self, "itemArray")
-}
+
+
+
+@(objc_class="NSMenuDelegate")
+MenuDelegate :: struct {using _: Object, using _: ObjectProtocol}
+
+@(objc_type=MenuDelegate, objc_name="menuNeedsUpdate")
+MenuDelegate_menuNeedsUpdate :: #force_inline proc "c" (self: ^MenuDelegate, menu: ^Menu) {
+	msgSend(nil, self, "menuNeedsUpdate:", menu)
+}
+@(objc_type=MenuDelegate, objc_name="numberOfItemsInMenu")
+MenuDelegate_numberOfItemsInMenu :: #force_inline proc "c" (self: ^MenuDelegate, menu: ^Menu) -> Integer {
+	return msgSend(Integer, self, "numberOfItemsInMenu:", menu)
+}
+@(objc_type=MenuDelegate, objc_name="menu_updateItem_atIndex_shouldCancel")
+MenuDelegate_menu_updateItem_atIndex_shouldCancel :: #force_inline proc "c" (self: ^MenuDelegate, menu: ^Menu, item: ^MenuItem, index: Integer, shouldCancel: bool) -> bool {
+	return msgSend(bool, self, "menu:updateItem:atIndex:shouldCancel:", menu, item, index, shouldCancel)
+}
+@(objc_type=MenuDelegate, objc_name="menuHasKeyEquivalent")
+MenuDelegate_menuHasKeyEquivalent :: #force_inline proc "c" (self: ^MenuDelegate, menu: ^Menu, event: ^Event, target: ^id, action: ^SEL) -> bool {
+	return msgSend(bool, self, "menuHasKeyEquivalent:forEvent:target:action:", menu, event, target, action)
+}
+@(objc_type=MenuDelegate, objc_name="menuWillOpen")
+MenuDelegate_menuWillOpen :: #force_inline proc "c" (self: ^MenuDelegate, menu: ^Menu) {
+	msgSend(nil, self, "menuWillOpen:", menu)
+}
+@(objc_type=MenuDelegate, objc_name="menuDidClose")
+MenuDelegate_menuDidClose :: #force_inline proc "c" (self: ^MenuDelegate, menu: ^Menu) {
+	msgSend(nil, self, "menuDidClose:", menu)
+}
+@(objc_type=MenuDelegate, objc_name="menu_willHighlightItem")
+MenuDelegate_menu_willHighlightItem :: #force_inline proc "c" (self: ^MenuDelegate, menu: ^Menu, item: ^MenuItem) {
+	msgSend(nil, self, "menu:willHighlightItem:", menu, item)
+}
+@(objc_type=MenuDelegate, objc_name="confinementRectForMenu")
+MenuDelegate_confinementRectForMenu :: #force_inline proc "c" (self: ^MenuDelegate, menu: ^Menu, screen: ^Screen) -> Rect {
+	return msgSend(Rect, self, "confinementRectForMenu:onScreen:", menu, screen)
+}
+@(objc_type=MenuDelegate, objc_name="menu")
+MenuDelegate_menu :: proc {
+	MenuDelegate_menu_updateItem_atIndex_shouldCancel,
+	MenuDelegate_menu_willHighlightItem,
+}

+ 460 - 0
core/sys/darwin/Foundation/NSMenuItem.odin

@@ -0,0 +1,460 @@
+package objc_Foundation
+
+import "base:builtin"
+import "base:intrinsics"
+
+KeyEquivalentModifierFlag :: EventModifierFlag
+KeyEquivalentModifierMask :: EventModifierFlags
+
+// Used to retrieve only the device-independent modifier flags, allowing applications to mask off the device-dependent modifier flags, including event coalescing information.
+KeyEventModifierFlagDeviceIndependentFlagsMask := transmute(KeyEquivalentModifierMask)_KeyEventModifierFlagDeviceIndependentFlagsMask
+@(private) _KeyEventModifierFlagDeviceIndependentFlagsMask := UInteger(0xffff0000)
+
+MenuItemCallback :: proc "c" (unused: rawptr, name: SEL, sender: ^Object)
+
+@(objc_class="NSMenuItem")
+MenuItem :: struct {using _: Object}
+
+@(objc_type=MenuItem, objc_name="registerActionCallback", objc_is_class_method=true)
+MenuItem_registerActionCallback :: proc "c" (name: cstring, callback: MenuItemCallback) -> SEL {
+	s := string(name)
+	n := len(s)
+	sel: SEL
+	if n > 0 && s[n-1] != ':' {
+		col_name := intrinsics.alloca(n+2, 1)
+		builtin.copy(col_name[:n], s)
+		col_name[n] = ':'
+		col_name[n+1] = 0
+		sel = sel_registerName(cstring(col_name))
+	} else {
+		sel = sel_registerName(name)
+	}
+	if callback != nil {
+		class_addMethod(intrinsics.objc_find_class("NSObject"), sel, auto_cast callback, "v@:@")
+	}
+	return sel
+}
+
+@(objc_type=MenuItem, objc_name="init")
+MenuItem_init :: proc "c" (self: ^MenuItem) -> ^MenuItem {
+	return msgSend(^MenuItem, self, "init")
+}
+
+
+@(objc_type=MenuItem, objc_name="separatorItem", objc_is_class_method=true)
+MenuItem_separatorItem :: #force_inline proc "c" () -> ^MenuItem {
+	return msgSend(^MenuItem, MenuItem, "separatorItem")
+}
+@(objc_type=MenuItem, objc_name="sectionHeaderWithTitle", objc_is_class_method=true)
+MenuItem_sectionHeaderWithTitle :: #force_inline proc "c" (title: ^String) -> ^MenuItem {
+	return msgSend(^MenuItem, MenuItem, "sectionHeaderWithTitle:", title)
+}
+@(objc_type=MenuItem, objc_name="initWithTitle")
+MenuItem_initWithTitle :: #force_inline proc "c" (self: ^MenuItem, string: ^String, selector: SEL, charCode: ^String) -> ^MenuItem {
+	return msgSend(^MenuItem, self, "initWithTitle:action:keyEquivalent:", string, selector, charCode)
+}
+@(objc_type=MenuItem, objc_name="initWithCoder")
+MenuItem_initWithCoder :: #force_inline proc "c" (self: ^MenuItem, coder: ^Coder) -> ^MenuItem {
+	return msgSend(^MenuItem, self, "initWithCoder:", coder)
+}
+@(objc_type=MenuItem, objc_name="usesUserKeyEquivalents", objc_is_class_method=true)
+MenuItem_usesUserKeyEquivalents :: #force_inline proc "c" () -> bool {
+	return msgSend(bool, MenuItem, "usesUserKeyEquivalents")
+}
+@(objc_type=MenuItem, objc_name="setUsesUserKeyEquivalents", objc_is_class_method=true)
+MenuItem_setUsesUserKeyEquivalents :: #force_inline proc "c" (usesUserKeyEquivalents: bool) {
+	msgSend(nil, MenuItem, "setUsesUserKeyEquivalents:", usesUserKeyEquivalents)
+}
+@(objc_type=MenuItem, objc_name="menu")
+MenuItem_menu :: #force_inline proc "c" (self: ^MenuItem) -> ^Menu {
+	return msgSend(^Menu, self, "menu")
+}
+@(objc_type=MenuItem, objc_name="setMenu")
+MenuItem_setMenu :: #force_inline proc "c" (self: ^MenuItem, menu: ^Menu) {
+	msgSend(nil, self, "setMenu:", menu)
+}
+@(objc_type=MenuItem, objc_name="hasSubmenu")
+MenuItem_hasSubmenu :: #force_inline proc "c" (self: ^MenuItem) -> bool {
+	return msgSend(bool, self, "hasSubmenu")
+}
+@(objc_type=MenuItem, objc_name="submenu")
+MenuItem_submenu :: #force_inline proc "c" (self: ^MenuItem) -> ^Menu {
+	return msgSend(^Menu, self, "submenu")
+}
+@(objc_type=MenuItem, objc_name="setSubmenu")
+MenuItem_setSubmenu :: #force_inline proc "c" (self: ^MenuItem, submenu: ^Menu) {
+	msgSend(nil, self, "setSubmenu:", submenu)
+}
+@(objc_type=MenuItem, objc_name="parentItem")
+MenuItem_parentItem :: #force_inline proc "c" (self: ^MenuItem) -> ^MenuItem {
+	return msgSend(^MenuItem, self, "parentItem")
+}
+@(objc_type=MenuItem, objc_name="title")
+MenuItem_title :: #force_inline proc "c" (self: ^MenuItem) -> ^String {
+	return msgSend(^String, self, "title")
+}
+@(objc_type=MenuItem, objc_name="setTitle")
+MenuItem_setTitle :: #force_inline proc "c" (self: ^MenuItem, title: ^String) {
+	msgSend(nil, self, "setTitle:", title)
+}
+// @(objc_type=MenuItem, objc_name="attributedTitle")
+// MenuItem_attributedTitle :: #force_inline proc "c" (self: ^MenuItem) -> ^AttributedString {
+// 	return msgSend(^AttributedString, self, "attributedTitle")
+// }
+// @(objc_type=MenuItem, objc_name="setAttributedTitle")
+// MenuItem_setAttributedTitle :: #force_inline proc "c" (self: ^MenuItem, attributedTitle: ^AttributedString) {
+// 	msgSend(nil, self, "setAttributedTitle:", attributedTitle)
+// }
+@(objc_type=MenuItem, objc_name="subtitle")
+MenuItem_subtitle :: #force_inline proc "c" (self: ^MenuItem) -> ^String {
+	return msgSend(^String, self, "subtitle")
+}
+@(objc_type=MenuItem, objc_name="setSubtitle")
+MenuItem_setSubtitle :: #force_inline proc "c" (self: ^MenuItem, subtitle: ^String) {
+	msgSend(nil, self, "setSubtitle:", subtitle)
+}
+@(objc_type=MenuItem, objc_name="isSeparatorItem")
+MenuItem_isSeparatorItem :: #force_inline proc "c" (self: ^MenuItem) -> bool {
+	return msgSend(bool, self, "isSeparatorItem")
+}
+@(objc_type=MenuItem, objc_name="isSectionHeader")
+MenuItem_isSectionHeader :: #force_inline proc "c" (self: ^MenuItem) -> bool {
+	return msgSend(bool, self, "isSectionHeader")
+}
+@(objc_type=MenuItem, objc_name="keyEquivalent")
+MenuItem_keyEquivalent :: #force_inline proc "c" (self: ^MenuItem) -> ^String {
+	return msgSend(^String, self, "keyEquivalent")
+}
+@(objc_type=MenuItem, objc_name="setKeyEquivalent")
+MenuItem_setKeyEquivalent :: #force_inline proc "c" (self: ^MenuItem, keyEquivalent: ^String) {
+	msgSend(nil, self, "setKeyEquivalent:", keyEquivalent)
+}
+@(objc_type=MenuItem, objc_name="keyEquivalentModifierMask")
+MenuItem_keyEquivalentModifierMask :: #force_inline proc "c" (self: ^MenuItem) -> EventModifierFlags {
+	return msgSend(EventModifierFlags, self, "keyEquivalentModifierMask")
+}
+@(objc_type=MenuItem, objc_name="setKeyEquivalentModifierMask")
+MenuItem_setKeyEquivalentModifierMask :: #force_inline proc "c" (self: ^MenuItem, keyEquivalentModifierMask: EventModifierFlags) {
+	msgSend(nil, self, "setKeyEquivalentModifierMask:", keyEquivalentModifierMask)
+}
+@(objc_type=MenuItem, objc_name="userKeyEquivalent")
+MenuItem_userKeyEquivalent :: #force_inline proc "c" (self: ^MenuItem) -> ^String {
+	return msgSend(^String, self, "userKeyEquivalent")
+}
+@(objc_type=MenuItem, objc_name="allowsKeyEquivalentWhenHidden")
+MenuItem_allowsKeyEquivalentWhenHidden :: #force_inline proc "c" (self: ^MenuItem) -> bool {
+	return msgSend(bool, self, "allowsKeyEquivalentWhenHidden")
+}
+@(objc_type=MenuItem, objc_name="setAllowsKeyEquivalentWhenHidden")
+MenuItem_setAllowsKeyEquivalentWhenHidden :: #force_inline proc "c" (self: ^MenuItem, allowsKeyEquivalentWhenHidden: bool) {
+	msgSend(nil, self, "setAllowsKeyEquivalentWhenHidden:", allowsKeyEquivalentWhenHidden)
+}
+@(objc_type=MenuItem, objc_name="allowsAutomaticKeyEquivalentLocalization")
+MenuItem_allowsAutomaticKeyEquivalentLocalization :: #force_inline proc "c" (self: ^MenuItem) -> bool {
+	return msgSend(bool, self, "allowsAutomaticKeyEquivalentLocalization")
+}
+@(objc_type=MenuItem, objc_name="setAllowsAutomaticKeyEquivalentLocalization")
+MenuItem_setAllowsAutomaticKeyEquivalentLocalization :: #force_inline proc "c" (self: ^MenuItem, allowsAutomaticKeyEquivalentLocalization: bool) {
+	msgSend(nil, self, "setAllowsAutomaticKeyEquivalentLocalization:", allowsAutomaticKeyEquivalentLocalization)
+}
+@(objc_type=MenuItem, objc_name="allowsAutomaticKeyEquivalentMirroring")
+MenuItem_allowsAutomaticKeyEquivalentMirroring :: #force_inline proc "c" (self: ^MenuItem) -> bool {
+	return msgSend(bool, self, "allowsAutomaticKeyEquivalentMirroring")
+}
+@(objc_type=MenuItem, objc_name="setAllowsAutomaticKeyEquivalentMirroring")
+MenuItem_setAllowsAutomaticKeyEquivalentMirroring :: #force_inline proc "c" (self: ^MenuItem, allowsAutomaticKeyEquivalentMirroring: bool) {
+	msgSend(nil, self, "setAllowsAutomaticKeyEquivalentMirroring:", allowsAutomaticKeyEquivalentMirroring)
+}
+// @(objc_type=MenuItem, objc_name="image")
+// MenuItem_image :: #force_inline proc "c" (self: ^MenuItem) -> ^Image {
+// 	return msgSend(^Image, self, "image")
+// }
+// @(objc_type=MenuItem, objc_name="setImage")
+// MenuItem_setImage :: #force_inline proc "c" (self: ^MenuItem, image: ^Image) {
+// 	msgSend(nil, self, "setImage:", image)
+// }
+// @(objc_type=MenuItem, objc_name="state")
+// MenuItem_state :: #force_inline proc "c" (self: ^MenuItem) -> ControlStateValue {
+// 	return msgSend(ControlStateValue, self, "state")
+// }
+// @(objc_type=MenuItem, objc_name="setState")
+// MenuItem_setState :: #force_inline proc "c" (self: ^MenuItem, state: ControlStateValue) {
+// 	msgSend(nil, self, "setState:", state)
+// }
+// @(objc_type=MenuItem, objc_name="onStateImage")
+// MenuItem_onStateImage :: #force_inline proc "c" (self: ^MenuItem) -> ^Image {
+// 	return msgSend(^Image, self, "onStateImage")
+// }
+// @(objc_type=MenuItem, objc_name="setOnStateImage")
+// MenuItem_setOnStateImage :: #force_inline proc "c" (self: ^MenuItem, onStateImage: ^Image) {
+// 	msgSend(nil, self, "setOnStateImage:", onStateImage)
+// }
+// @(objc_type=MenuItem, objc_name="offStateImage")
+// MenuItem_offStateImage :: #force_inline proc "c" (self: ^MenuItem) -> ^Image {
+// 	return msgSend(^Image, self, "offStateImage")
+// }
+// @(objc_type=MenuItem, objc_name="setOffStateImage")
+// MenuItem_setOffStateImage :: #force_inline proc "c" (self: ^MenuItem, offStateImage: ^Image) {
+// 	msgSend(nil, self, "setOffStateImage:", offStateImage)
+// }
+// @(objc_type=MenuItem, objc_name="mixedStateImage")
+// MenuItem_mixedStateImage :: #force_inline proc "c" (self: ^MenuItem) -> ^Image {
+// 	return msgSend(^Image, self, "mixedStateImage")
+// }
+// @(objc_type=MenuItem, objc_name="setMixedStateImage")
+// MenuItem_setMixedStateImage :: #force_inline proc "c" (self: ^MenuItem, mixedStateImage: ^Image) {
+// 	msgSend(nil, self, "setMixedStateImage:", mixedStateImage)
+// }
+@(objc_type=MenuItem, objc_name="isEnabled")
+MenuItem_isEnabled :: #force_inline proc "c" (self: ^MenuItem) -> bool {
+	return msgSend(bool, self, "isEnabled")
+}
+@(objc_type=MenuItem, objc_name="setEnabled")
+MenuItem_setEnabled :: #force_inline proc "c" (self: ^MenuItem, enabled: bool) {
+	msgSend(nil, self, "setEnabled:", enabled)
+}
+@(objc_type=MenuItem, objc_name="isAlternate")
+MenuItem_isAlternate :: #force_inline proc "c" (self: ^MenuItem) -> bool {
+	return msgSend(bool, self, "isAlternate")
+}
+@(objc_type=MenuItem, objc_name="setAlternate")
+MenuItem_setAlternate :: #force_inline proc "c" (self: ^MenuItem, alternate: bool) {
+	msgSend(nil, self, "setAlternate:", alternate)
+}
+@(objc_type=MenuItem, objc_name="indentationLevel")
+MenuItem_indentationLevel :: #force_inline proc "c" (self: ^MenuItem) -> Integer {
+	return msgSend(Integer, self, "indentationLevel")
+}
+@(objc_type=MenuItem, objc_name="setIndentationLevel")
+MenuItem_setIndentationLevel :: #force_inline proc "c" (self: ^MenuItem, indentationLevel: Integer) {
+	msgSend(nil, self, "setIndentationLevel:", indentationLevel)
+}
+@(objc_type=MenuItem, objc_name="target")
+MenuItem_target :: #force_inline proc "c" (self: ^MenuItem) -> id {
+	return msgSend(id, self, "target")
+}
+@(objc_type=MenuItem, objc_name="setTarget")
+MenuItem_setTarget :: #force_inline proc "c" (self: ^MenuItem, target: id) {
+	msgSend(nil, self, "setTarget:", target)
+}
+@(objc_type=MenuItem, objc_name="action")
+MenuItem_action :: #force_inline proc "c" (self: ^MenuItem) -> SEL {
+	return msgSend(SEL, self, "action")
+}
+@(objc_type=MenuItem, objc_name="setAction")
+MenuItem_setAction :: #force_inline proc "c" (self: ^MenuItem, action: SEL) {
+	msgSend(nil, self, "setAction:", action)
+}
+@(objc_type=MenuItem, objc_name="tag")
+MenuItem_tag :: #force_inline proc "c" (self: ^MenuItem) -> Integer {
+	return msgSend(Integer, self, "tag")
+}
+@(objc_type=MenuItem, objc_name="setTag")
+MenuItem_setTag :: #force_inline proc "c" (self: ^MenuItem, tag: Integer) {
+	msgSend(nil, self, "setTag:", tag)
+}
+@(objc_type=MenuItem, objc_name="representedObject")
+MenuItem_representedObject :: #force_inline proc "c" (self: ^MenuItem) -> id {
+	return msgSend(id, self, "representedObject")
+}
+@(objc_type=MenuItem, objc_name="setRepresentedObject")
+MenuItem_setRepresentedObject :: #force_inline proc "c" (self: ^MenuItem, representedObject: id) {
+	msgSend(nil, self, "setRepresentedObject:", representedObject)
+}
+@(objc_type=MenuItem, objc_name="view")
+MenuItem_view :: #force_inline proc "c" (self: ^MenuItem) -> ^View {
+	return msgSend(^View, self, "view")
+}
+@(objc_type=MenuItem, objc_name="setView")
+MenuItem_setView :: #force_inline proc "c" (self: ^MenuItem, view: ^View) {
+	msgSend(nil, self, "setView:", view)
+}
+@(objc_type=MenuItem, objc_name="isHighlighted")
+MenuItem_isHighlighted :: #force_inline proc "c" (self: ^MenuItem) -> bool {
+	return msgSend(bool, self, "isHighlighted")
+}
+@(objc_type=MenuItem, objc_name="isHidden")
+MenuItem_isHidden :: #force_inline proc "c" (self: ^MenuItem) -> bool {
+	return msgSend(bool, self, "isHidden")
+}
+@(objc_type=MenuItem, objc_name="setHidden")
+MenuItem_setHidden :: #force_inline proc "c" (self: ^MenuItem, hidden: bool) {
+	msgSend(nil, self, "setHidden:", hidden)
+}
+@(objc_type=MenuItem, objc_name="isHiddenOrHasHiddenAncestor")
+MenuItem_isHiddenOrHasHiddenAncestor :: #force_inline proc "c" (self: ^MenuItem) -> bool {
+	return msgSend(bool, self, "isHiddenOrHasHiddenAncestor")
+}
+@(objc_type=MenuItem, objc_name="toolTip")
+MenuItem_toolTip :: #force_inline proc "c" (self: ^MenuItem) -> ^String {
+	return msgSend(^String, self, "toolTip")
+}
+@(objc_type=MenuItem, objc_name="setToolTip")
+MenuItem_setToolTip :: #force_inline proc "c" (self: ^MenuItem, toolTip: ^String) {
+	msgSend(nil, self, "setToolTip:", toolTip)
+}
+// @(objc_type=MenuItem, objc_name="badge")
+// MenuItem_badge :: #force_inline proc "c" (self: ^MenuItem) -> ^MenuItemBadge {
+// 	return msgSend(^MenuItemBadge, self, "badge")
+// }
+// @(objc_type=MenuItem, objc_name="setBadge")
+// MenuItem_setBadge :: #force_inline proc "c" (self: ^MenuItem, badge: ^MenuItemBadge) {
+// 	msgSend(nil, self, "setBadge:", badge)
+// }
+@(objc_type=MenuItem, objc_name="setMnemonicLocation")
+MenuItem_setMnemonicLocation :: #force_inline proc "c" (self: ^MenuItem, location: UInteger) {
+	msgSend(nil, self, "setMnemonicLocation:", location)
+}
+@(objc_type=MenuItem, objc_name="mnemonicLocation")
+MenuItem_mnemonicLocation :: #force_inline proc "c" (self: ^MenuItem) -> UInteger {
+	return msgSend(UInteger, self, "mnemonicLocation")
+}
+@(objc_type=MenuItem, objc_name="mnemonic")
+MenuItem_mnemonic :: #force_inline proc "c" (self: ^MenuItem) -> ^String {
+	return msgSend(^String, self, "mnemonic")
+}
+@(objc_type=MenuItem, objc_name="setTitleWithMnemonic")
+MenuItem_setTitleWithMnemonic :: #force_inline proc "c" (self: ^MenuItem, stringWithAmpersand: ^String) {
+	msgSend(nil, self, "setTitleWithMnemonic:", stringWithAmpersand)
+}
+@(objc_type=MenuItem, objc_name="load", objc_is_class_method=true)
+MenuItem_load :: #force_inline proc "c" () {
+	msgSend(nil, MenuItem, "load")
+}
+@(objc_type=MenuItem, objc_name="initialize", objc_is_class_method=true)
+MenuItem_initialize :: #force_inline proc "c" () {
+	msgSend(nil, MenuItem, "initialize")
+}
+@(objc_type=MenuItem, objc_name="new", objc_is_class_method=true)
+MenuItem_new :: #force_inline proc "c" () -> ^MenuItem {
+	return msgSend(^MenuItem, MenuItem, "new")
+}
+@(objc_type=MenuItem, objc_name="allocWithZone", objc_is_class_method=true)
+MenuItem_allocWithZone :: #force_inline proc "c" (zone: ^Zone) -> ^MenuItem {
+	return msgSend(^MenuItem, MenuItem, "allocWithZone:", zone)
+}
+@(objc_type=MenuItem, objc_name="alloc", objc_is_class_method=true)
+MenuItem_alloc :: #force_inline proc "c" () -> ^MenuItem {
+	return msgSend(^MenuItem, MenuItem, "alloc")
+}
+@(objc_type=MenuItem, objc_name="copyWithZone", objc_is_class_method=true)
+MenuItem_copyWithZone :: #force_inline proc "c" (zone: ^Zone) -> id {
+	return msgSend(id, MenuItem, "copyWithZone:", zone)
+}
+@(objc_type=MenuItem, objc_name="mutableCopyWithZone", objc_is_class_method=true)
+MenuItem_mutableCopyWithZone :: #force_inline proc "c" (zone: ^Zone) -> id {
+	return msgSend(id, MenuItem, "mutableCopyWithZone:", zone)
+}
+@(objc_type=MenuItem, objc_name="instancesRespondToSelector", objc_is_class_method=true)
+MenuItem_instancesRespondToSelector :: #force_inline proc "c" (aSelector: SEL) -> bool {
+	return msgSend(bool, MenuItem, "instancesRespondToSelector:", aSelector)
+}
+@(objc_type=MenuItem, objc_name="conformsToProtocol", objc_is_class_method=true)
+MenuItem_conformsToProtocol :: #force_inline proc "c" (protocol: ^Protocol) -> bool {
+	return msgSend(bool, MenuItem, "conformsToProtocol:", protocol)
+}
+@(objc_type=MenuItem, objc_name="instanceMethodForSelector", objc_is_class_method=true)
+MenuItem_instanceMethodForSelector :: #force_inline proc "c" (aSelector: SEL) -> IMP {
+	return msgSend(IMP, MenuItem, "instanceMethodForSelector:", aSelector)
+}
+// @(objc_type=MenuItem, objc_name="instanceMethodSignatureForSelector", objc_is_class_method=true)
+// MenuItem_instanceMethodSignatureForSelector :: #force_inline proc "c" (aSelector: SEL) -> ^MethodSignature {
+// 	return msgSend(^MethodSignature, MenuItem, "instanceMethodSignatureForSelector:", aSelector)
+// }
+@(objc_type=MenuItem, objc_name="isSubclassOfClass", objc_is_class_method=true)
+MenuItem_isSubclassOfClass :: #force_inline proc "c" (aClass: Class) -> bool {
+	return msgSend(bool, MenuItem, "isSubclassOfClass:", aClass)
+}
+@(objc_type=MenuItem, objc_name="resolveClassMethod", objc_is_class_method=true)
+MenuItem_resolveClassMethod :: #force_inline proc "c" (sel: SEL) -> bool {
+	return msgSend(bool, MenuItem, "resolveClassMethod:", sel)
+}
+@(objc_type=MenuItem, objc_name="resolveInstanceMethod", objc_is_class_method=true)
+MenuItem_resolveInstanceMethod :: #force_inline proc "c" (sel: SEL) -> bool {
+	return msgSend(bool, MenuItem, "resolveInstanceMethod:", sel)
+}
+@(objc_type=MenuItem, objc_name="hash", objc_is_class_method=true)
+MenuItem_hash :: #force_inline proc "c" () -> UInteger {
+	return msgSend(UInteger, MenuItem, "hash")
+}
+@(objc_type=MenuItem, objc_name="superclass", objc_is_class_method=true)
+MenuItem_superclass :: #force_inline proc "c" () -> Class {
+	return msgSend(Class, MenuItem, "superclass")
+}
+@(objc_type=MenuItem, objc_name="class", objc_is_class_method=true)
+MenuItem_class :: #force_inline proc "c" () -> Class {
+	return msgSend(Class, MenuItem, "class")
+}
+@(objc_type=MenuItem, objc_name="description", objc_is_class_method=true)
+MenuItem_description :: #force_inline proc "c" () -> ^String {
+	return msgSend(^String, MenuItem, "description")
+}
+@(objc_type=MenuItem, objc_name="debugDescription", objc_is_class_method=true)
+MenuItem_debugDescription :: #force_inline proc "c" () -> ^String {
+	return msgSend(^String, MenuItem, "debugDescription")
+}
+@(objc_type=MenuItem, objc_name="version", objc_is_class_method=true)
+MenuItem_version :: #force_inline proc "c" () -> Integer {
+	return msgSend(Integer, MenuItem, "version")
+}
+@(objc_type=MenuItem, objc_name="setVersion", objc_is_class_method=true)
+MenuItem_setVersion :: #force_inline proc "c" (aVersion: Integer) {
+	msgSend(nil, MenuItem, "setVersion:", aVersion)
+}
+@(objc_type=MenuItem, objc_name="poseAsClass", objc_is_class_method=true)
+MenuItem_poseAsClass :: #force_inline proc "c" (aClass: Class) {
+	msgSend(nil, MenuItem, "poseAsClass:", aClass)
+}
+@(objc_type=MenuItem, objc_name="cancelPreviousPerformRequestsWithTarget_selector_object", objc_is_class_method=true)
+MenuItem_cancelPreviousPerformRequestsWithTarget_selector_object :: #force_inline proc "c" (aTarget: id, aSelector: SEL, anArgument: id) {
+	msgSend(nil, MenuItem, "cancelPreviousPerformRequestsWithTarget:selector:object:", aTarget, aSelector, anArgument)
+}
+@(objc_type=MenuItem, objc_name="cancelPreviousPerformRequestsWithTarget_", objc_is_class_method=true)
+MenuItem_cancelPreviousPerformRequestsWithTarget_ :: #force_inline proc "c" (aTarget: id) {
+	msgSend(nil, MenuItem, "cancelPreviousPerformRequestsWithTarget:", aTarget)
+}
+@(objc_type=MenuItem, objc_name="accessInstanceVariablesDirectly", objc_is_class_method=true)
+MenuItem_accessInstanceVariablesDirectly :: #force_inline proc "c" () -> bool {
+	return msgSend(bool, MenuItem, "accessInstanceVariablesDirectly")
+}
+@(objc_type=MenuItem, objc_name="useStoredAccessor", objc_is_class_method=true)
+MenuItem_useStoredAccessor :: #force_inline proc "c" () -> bool {
+	return msgSend(bool, MenuItem, "useStoredAccessor")
+}
+@(objc_type=MenuItem, objc_name="keyPathsForValuesAffectingValueForKey", objc_is_class_method=true)
+MenuItem_keyPathsForValuesAffectingValueForKey :: #force_inline proc "c" (key: ^String) -> ^Set {
+	return msgSend(^Set, MenuItem, "keyPathsForValuesAffectingValueForKey:", key)
+}
+@(objc_type=MenuItem, objc_name="automaticallyNotifiesObserversForKey", objc_is_class_method=true)
+MenuItem_automaticallyNotifiesObserversForKey :: #force_inline proc "c" (key: ^String) -> bool {
+	return msgSend(bool, MenuItem, "automaticallyNotifiesObserversForKey:", key)
+}
+@(objc_type=MenuItem, objc_name="setKeys", objc_is_class_method=true)
+MenuItem_setKeys :: #force_inline proc "c" (keys: ^Array, dependentKey: ^String) {
+	msgSend(nil, MenuItem, "setKeys:triggerChangeNotificationsForDependentKey:", keys, dependentKey)
+}
+@(objc_type=MenuItem, objc_name="classFallbacksForKeyedArchiver", objc_is_class_method=true)
+MenuItem_classFallbacksForKeyedArchiver :: #force_inline proc "c" () -> ^Array {
+	return msgSend(^Array, MenuItem, "classFallbacksForKeyedArchiver")
+}
+@(objc_type=MenuItem, objc_name="classForKeyedUnarchiver", objc_is_class_method=true)
+MenuItem_classForKeyedUnarchiver :: #force_inline proc "c" () -> Class {
+	return msgSend(Class, MenuItem, "classForKeyedUnarchiver")
+}
+@(objc_type=MenuItem, objc_name="exposeBinding", objc_is_class_method=true)
+MenuItem_exposeBinding :: #force_inline proc "c" (binding: ^String) {
+	msgSend(nil, MenuItem, "exposeBinding:", binding)
+}
+@(objc_type=MenuItem, objc_name="setDefaultPlaceholder", objc_is_class_method=true)
+MenuItem_setDefaultPlaceholder :: #force_inline proc "c" (placeholder: id, marker: id, binding: ^String) {
+	msgSend(nil, MenuItem, "setDefaultPlaceholder:forMarker:withBinding:", placeholder, marker, binding)
+}
+@(objc_type=MenuItem, objc_name="defaultPlaceholderForMarker", objc_is_class_method=true)
+MenuItem_defaultPlaceholderForMarker :: #force_inline proc "c" (marker: id, binding: ^String) -> id {
+	return msgSend(id, MenuItem, "defaultPlaceholderForMarker:withBinding:", marker, binding)
+}
+@(objc_type=MenuItem, objc_name="cancelPreviousPerformRequestsWithTarget")
+MenuItem_cancelPreviousPerformRequestsWithTarget :: proc {
+	MenuItem_cancelPreviousPerformRequestsWithTarget_selector_object,
+	MenuItem_cancelPreviousPerformRequestsWithTarget_,
+}

+ 136 - 0
core/sys/darwin/Foundation/objc_helper.odin

@@ -0,0 +1,136 @@
+package objc_Foundation
+
+import "base:runtime"
+import "base:intrinsics"
+
+Subclasser_Proc :: proc(cls: Class, vtable: rawptr)
+
+Object_VTable_Info :: struct {
+	vtable: rawptr,
+	size:   uint,
+	impl:   Subclasser_Proc,
+}
+
+Class_VTable_Info :: struct {
+	_context:    runtime.Context,
+	super_vtable:    rawptr,
+	protocol_vtable: rawptr,
+}
+
+@(require_results)
+class_get_metaclass :: #force_inline proc "contextless" (cls: Class) -> Class {
+	return (^Class)(cls)^
+}
+
+@(require_results)
+object_get_vtable_info :: proc "contextless" (obj: id) -> ^Class_VTable_Info {
+	return (^Class_VTable_Info)(object_getIndexedIvars(obj))
+}
+
+@(require_results)
+make_subclasser :: #force_inline proc(vtable: ^$T, impl: proc(cls: Class, vt: ^T)) -> Object_VTable_Info {
+	return Object_VTable_Info{
+		vtable = vtable,
+		size   = size_of(T),
+		impl   = (Subclasser_Proc)(impl),
+	}
+}
+
+@(require_results)
+register_subclass :: proc(
+	class_name:           cstring,
+	superclass:           Class,
+	superclass_overrides: Maybe(Object_VTable_Info) = nil,
+	protocol:             Maybe(Object_VTable_Info) = nil,
+	_context:             Maybe(runtime.Context)    = nil,
+) -> Class {
+	assert(superclass != nil)
+
+	super_size: uint
+	proto_size: uint
+
+	if superclass_overrides != nil {
+		// Align to 8-byte boundary
+		super_size = (superclass_overrides.?.size + 7)/8 * 8
+	}
+
+	if protocol != nil {
+		// Align to 8-byte boundary
+		proto_size = (protocol.?.size + 7)/8 * 8
+	}
+
+	cls := objc_lookUpClass(class_name)
+	if cls != nil {
+		return cls
+	}
+
+	extra_size := uint(size_of(Class_VTable_Info)) + 8 + super_size + proto_size
+
+	cls = objc_allocateClassPair(superclass, class_name, extra_size)
+	assert(cls != nil)
+
+	if s, ok := superclass_overrides.?; ok {
+		s.impl(cls, s.vtable)
+	}
+
+	if p, ok := protocol.?; ok {
+		p.impl(cls, p.vtable)
+	}
+
+	objc_registerClassPair(cls)
+	meta_cls    := class_get_metaclass(cls)
+	meta_size   := uint(class_getInstanceSize(meta_cls))
+
+	// Offsets are always aligned to 8-byte boundary
+	info_offset         := (meta_size + 7) / 8 * 8
+	super_vtable_offset := (info_offset + size_of(Class_VTable_Info) + 7) / 8 * 8
+	ptoto_vtable_offset := super_vtable_offset + super_size
+
+
+	p_info := (^Class_VTable_Info)(([^]u8)(cls)[info_offset:])
+	p_super_vtable := ([^]u8)(cls)[super_vtable_offset:]
+	p_proto_vtable := ([^]u8)(cls)[ptoto_vtable_offset:]
+
+	intrinsics.mem_zero(p_info, size_of(Class_VTable_Info))
+
+	// Assign the context
+	p_info._context = _context.? or_else context
+
+	if s, ok := superclass_overrides.?; ok {
+		p_info.super_vtable = p_super_vtable
+		intrinsics.mem_copy(p_super_vtable, s.vtable, super_size)
+	}
+	if p, ok := protocol.?; ok {
+		p_info.protocol_vtable = p_proto_vtable
+		intrinsics.mem_copy(p_proto_vtable, p.vtable, p.size)
+	}
+
+	return cls
+}
+
+@(require_results)
+class_get_vtable_info :: proc "contextless" (cls: Class) -> ^Class_VTable_Info {
+	meta_cls  := class_get_metaclass(cls)
+	meta_size := uint(class_getInstanceSize(meta_cls))
+
+	// Align to 8-byte boundary
+	info_offset := (meta_size+7) / 8 * 8
+
+	p_cls := ([^]u8)(cls)[info_offset:]
+	ctx := (^Class_VTable_Info)(p_cls)
+	return ctx
+}
+
+@(require_results)
+alloc_user_object :: proc "contextless" (cls: Class, _context: Maybe(runtime.Context) = nil) -> id {
+	info := class_get_vtable_info(cls)
+
+	obj := class_createInstance(cls, size_of(Class_VTable_Info))
+	obj_info := (^Class_VTable_Info)(object_getIndexedIvars(obj))
+	obj_info^ = info^
+
+	if _context != nil {
+		obj_info._context = _context.?
+	}
+	return obj
+}

+ 67 - 0
core/sys/darwin/copyfile.odin

@@ -0,0 +1,67 @@
+package darwin
+
+import "core:sys/posix"
+
+copyfile_state_t :: distinct rawptr
+
+copyfile_flags :: bit_set[enum {
+	ACL,
+	STAT,
+	XATTR,
+	DATA,
+
+	RECURSIVE = 15,
+
+	CHECK,
+	EXCL,
+	NOFOLLOW_SRC,
+	NOFOLLOW_DST,
+	MOVE,
+	UNLINK,
+	PACK,
+	UNPACK,
+
+	CLONE,
+	CLONE_FORCE,
+	RUN_IN_PLACE,
+	DATA_SPARSE,
+	PRESERVE_DST_TRACKED,
+	VERBOSE = 30,
+}; u32]
+
+COPYFILE_SECURITY :: copyfile_flags{.STAT, .ACL}
+COPYFILE_METADATA :: COPYFILE_SECURITY + copyfile_flags{.XATTR}
+COPYFILE_ALL      :: COPYFILE_METADATA + copyfile_flags{.DATA}
+
+COPYFILE_NOFOLLOW :: copyfile_flags{.NOFOLLOW_SRC, .NOFOLLOW_DST}
+
+copyfile_state_flag :: enum u32 {
+	SRC_FD = 1,
+	SRC_FILENAME,
+	DST_FD,
+	DST_FILENAME,
+	QUARANTINE,
+	STATUS_CB,
+	STATUS_CTX,
+	COPIED,
+	XATTRNAME,
+	WAS_CLONED,
+	SRC_BSIZE,
+	DST_BSIZE,
+	BSIZE,
+	FORBID_CROSS_MOUNT,
+	NOCPROTECT,
+	PRESERVE_SUID,
+	RECURSIVE_SRC_FTSENT,
+	FORBID_DST_EXISTING_SYMLINKS,
+}
+
+foreign system {
+	copyfile  :: proc(from, to: cstring,  state: copyfile_state_t, flags: copyfile_flags) -> i32 ---
+	fcopyfile :: proc(from, to: posix.FD, state: copyfile_state_t, flags: copyfile_flags) -> i32 ---
+
+	copyfile_state_alloc :: proc() -> copyfile_state_t ---
+	copyfile_state_free  :: proc(state: copyfile_state_t) -> posix.result ---
+	copyfile_state_get   :: proc(state: copyfile_state_t, flag: copyfile_state_flag, dst: rawptr) -> posix.result ---
+	copyfile_state_set   :: proc(state: copyfile_state_t, flag: copyfile_state_flag, src: rawptr) -> posix.result ---
+}

+ 1 - 0
core/sys/darwin/darwin.odin

@@ -3,6 +3,7 @@ package darwin
 
 import "core:c"
 
+@(export)
 foreign import system "system:System.framework"
 
 Bool :: b8

+ 0 - 2
core/sys/darwin/sync.odin

@@ -1,7 +1,5 @@
 package darwin
 
-foreign import system "system:System.framework"
-
 // #define OS_WAIT_ON_ADDR_AVAILABILITY \
 // 	__API_AVAILABLE(macos(14.4), ios(17.4), tvos(17.4), watchos(10.4))
 when ODIN_OS == .Darwin {

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

@@ -19,16 +19,6 @@ X_OK  :: c.int((1 << 0))  /* test for execute or search permission */
 W_OK  :: c.int((1 << 1))  /* test for write permission */
 R_OK  :: c.int((1 << 2))  /* test for read permission */
 
-/* copyfile flags */
-COPYFILE_ACL   :: (1 << 0)
-COPYFILE_STAT  :: (1 << 1)
-COPYFILE_XATTR :: (1 << 2)
-COPYFILE_DATA  :: (1 << 3)
-
-COPYFILE_SECURITY :: (COPYFILE_STAT | COPYFILE_ACL)
-COPYFILE_METADATA :: (COPYFILE_SECURITY | COPYFILE_XATTR)
-COPYFILE_ALL	  :: (COPYFILE_METADATA | COPYFILE_DATA)
-
 /* syslimits.h */
 PATH_MAX	:: 1024	/* max bytes in pathname */
 

+ 4 - 2
core/sys/linux/bits.odin

@@ -579,7 +579,7 @@ Inotify_Event_Bits :: enum u32 {
 /*
 	Bits for Mem_Protection bitfield
 */
-Mem_Protection_Bits :: enum{
+Mem_Protection_Bits :: enum {
 	READ      = 0,
 	WRITE     = 1,
 	EXEC      = 2,
@@ -594,11 +594,13 @@ Mem_Protection_Bits :: enum{
 
 /*
 	Bits for Map_Flags
+
+	See `constants.odin` for `MAP_SHARED_VALIDATE` and `MAP_HUGE_16KB`, et al.
 */
 Map_Flags_Bits :: enum {
 	SHARED          = 0,
 	PRIVATE         = 1,
-	SHARED_VALIDATE = 2,
+	DROPPABLE       = 3,
 	FIXED           = 4,
 	ANONYMOUS       = 5,
 	// platform-dependent section start

+ 19 - 0
core/sys/linux/constants.odin

@@ -373,3 +373,22 @@ PTRACE_SECCOMP_GET_FILTER     :: PTrace_Seccomp_Get_Filter_Type(.SECCOMP_GET_FIL
 PTRACE_SECCOMP_GET_METADATA   :: PTrace_Seccomp_Get_Metadata_Type(.SECCOMP_GET_METADATA)
 PTRACE_GET_SYSCALL_INFO       :: PTrace_Get_Syscall_Info_Type(.GET_SYSCALL_INFO)
 PTRACE_GET_RSEQ_CONFIGURATION :: PTrace_Get_RSeq_Configuration_Type(.GET_RSEQ_CONFIGURATION)
+
+MAP_SHARED_VALIDATE :: Map_Flags{.SHARED, .PRIVATE}
+
+MAP_HUGE_SHIFT :: 26
+MAP_HUGE_MASK  :: 63
+
+MAP_HUGE_16KB       :: transmute(Map_Flags)(u32(14) << MAP_HUGE_SHIFT)
+MAP_HUGE_64KB       :: transmute(Map_Flags)(u32(16) << MAP_HUGE_SHIFT)
+MAP_HUGE_512KB      :: transmute(Map_Flags)(u32(19) << MAP_HUGE_SHIFT)
+MAP_HUGE_1MB        :: transmute(Map_Flags)(u32(20) << MAP_HUGE_SHIFT)
+MAP_HUGE_2MB        :: transmute(Map_Flags)(u32(21) << MAP_HUGE_SHIFT)
+MAP_HUGE_8MB        :: transmute(Map_Flags)(u32(23) << MAP_HUGE_SHIFT)
+MAP_HUGE_16MB       :: transmute(Map_Flags)(u32(24) << MAP_HUGE_SHIFT)
+MAP_HUGE_32MB       :: transmute(Map_Flags)(u32(25) << MAP_HUGE_SHIFT)
+MAP_HUGE_256MB      :: transmute(Map_Flags)(u32(28) << MAP_HUGE_SHIFT)
+MAP_HUGE_512MB      :: transmute(Map_Flags)(u32(29) << MAP_HUGE_SHIFT)
+MAP_HUGE_1GB        :: transmute(Map_Flags)(u32(30) << MAP_HUGE_SHIFT)
+MAP_HUGE_2GB        :: transmute(Map_Flags)(u32(31) << MAP_HUGE_SHIFT)
+MAP_HUGE_16GB       :: transmute(Map_Flags)(u32(34) << MAP_HUGE_SHIFT)

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

@@ -288,7 +288,7 @@ Rename_Flags :: bit_set[Rename_Flags_Bits; u32]
 
 /*
 	Directory entry record.
-	Recommended iterate these with `dirent_iterator()`,
+	Recommended to iterate these with `dirent_iterate_buf()`,
 	and obtain the name via `dirent_name()`.
 */
 Dirent :: struct {
@@ -368,6 +368,8 @@ Mem_Protection :: bit_set[Mem_Protection_Bits; i32]
 
 /*
 	Flags for mmap.
+
+	See `constants.odin` for `MAP_SHARED_VALIDATE` and `MAP_HUGE_16KB`, et al.
 */
 Map_Flags :: bit_set[Map_Flags_Bits; i32]
 

+ 50 - 18
core/sys/linux/wrappers.odin

@@ -54,22 +54,45 @@ WCOREDUMP :: #force_inline proc "contextless" (s: u32) -> bool {
 // TODO: sigaddset etc
 
 
-/// Iterate the results of getdents
-/// Only iterates as much data as loaded in the buffer
-/// In case you need to iterate *all* files in a directory
-/// consider using dirent_get_iterate
-///
-/// Example of using dirent_iterate_buf
-///   // Get dirents into a buffer
-///   buf: [128]u8
-///   sys.getdents(dirfd, buf[:])
-///   // Print the names of the files
-///   for dir in sys.dirent_iterate_buf(buf[:], &offs) {
-///       name := sys.dirent_name(dir)
-///       fmt.println(name)
-///   }
-/// This function doesn't automatically make a request
-/// for the buffer to be refilled
+/*
+Iterate the results of `getdents()`.
+
+This procedure extracts a directory entry from `buf` at the offset `offs`.
+`offs` will be modified to store an offset to the possible next directory entry
+in `buf`. The procedure only iterates as much data as loaded in the buffer and
+does not automatically make a request for the buffer to be refilled.
+
+Inputs:
+- buf: A byte buffer with data from `getdents()`
+- offs: An offset to the next possible directory entry in `buf`
+
+Returns:
+- A pointer to a directory entry in `buf`, or `nil`
+- A bool value denoting if a valid directory entry is returned
+
+Example:
+
+    import "core:fmt"
+    import "core:sys/linux"
+
+    print_names :: proc(dirfd: linux.Fd) {
+        // Get dirents into a buffer.
+        buf: [128]u8
+        // Loop until there are no more entries.
+        for {
+            written, err := linux.getdents(dirfd, buf[:])
+            if err != .NONE || written == 0 {
+                break
+            }
+            // Print the names of the files.
+            offset : int
+            for dir in linux.dirent_iterate_buf(buf[:written], &offset) {
+                name := linux.dirent_name(dir)
+                fmt.println(name)
+            }
+        }
+    }
+*/
 dirent_iterate_buf :: proc "contextless" (buf: []u8, offs: ^int) -> (d: ^Dirent, cont: bool) {
 	// Stopped iterating when there's no space left
 	if offs^ >= len(buf) {
@@ -82,8 +105,17 @@ dirent_iterate_buf :: proc "contextless" (buf: []u8, offs: ^int) -> (d: ^Dirent,
 	return dirent, true
 }
 
-/// Obtain the name of dirent as a string
-/// The lifetime of the string is bound to the lifetime of the provided dirent structure
+/*
+Obtain the name of dirent as a string.
+
+The lifetime of the returned string is bound to the lifetime of the provided dirent structure.
+
+Inputs:
+- dirent: A directory entry
+
+Returns:
+- A name of the entry
+*/
 dirent_name :: proc "contextless" (dirent: ^Dirent) -> string #no_bounds_check {
 	str := ([^]u8)(&dirent.name)
 	// Dirents are aligned to 8 bytes, so there is guaranteed to be a null

+ 172 - 0
core/sys/windows/scan_codes.odin

@@ -0,0 +1,172 @@
+#+build windows
+package sys_windows
+
+// Win32 scan codes for QWERTY layout
+// https://learn.microsoft.com/en-us/windows/win32/inputdev/about-keyboard-input#scan-codes
+
+KB_SYS_POWERDOWN :: 0xE05E
+KB_SYS_SLEEP     :: 0xE05F
+KB_SYS_WAKEUP    :: 0xE063
+KB_ERR_ROLLOVER  :: 0x00FF
+
+KB_A :: 0x001E
+KB_B :: 0x0030
+KB_C :: 0x002E
+KB_D :: 0x0020
+KB_E :: 0x0012
+KB_F :: 0x0021
+KB_G :: 0x0022
+KB_H :: 0x0023
+KB_I :: 0x0017
+KB_J :: 0x0024
+KB_K :: 0x0025
+KB_L :: 0x0026
+KB_M :: 0x0032
+KB_N :: 0x0031
+KB_O :: 0x0018
+KB_P :: 0x0019
+KB_Q :: 0x0010
+KB_R :: 0x0013
+KB_S :: 0x001F
+KB_T :: 0x0014
+KB_U :: 0x0016
+KB_V :: 0x002F
+KB_W :: 0x0011
+KB_X :: 0x002D
+KB_Y :: 0x0015
+KB_Z :: 0x002C
+
+KB_1_BANG         :: 0x0002
+KB_2_AT           :: 0x0003
+KB_3_HASH         :: 0x0004
+KB_4_DOLLAR       :: 0x0005
+KB_5_PERCENT      :: 0x0006
+KB_6_CARET        :: 0x0007
+KB_7_AMPERSAND    :: 0x0008
+KB_8_STAR         :: 0x0009
+KB_9_LEFTBRACKET  :: 0x000A
+KB_0_RIGHTBRACKET :: 0x000B
+
+KB_RETURN_ENTER      :: 0x001C
+KB_ESCAPE            :: 0x0001
+KB_DELETE            :: 0x000E
+KB_TAB               :: 0x000F
+KB_SPACEBAR          :: 0x0039
+KB_DASH_UNDERSCORE   :: 0x000C
+KB_EQUALS_PLUS       :: 0x000D
+KB_LEFTBRACE         :: 0x001A
+KB_RIGHTBRACE        :: 0x001B
+KB_PIPE_SLASH        :: 0x002B
+KB_NONUS             :: 0x002B
+KB_SEMICOLON_COLON   :: 0x0027
+KB_APOSTR_DOUBLEQUOT :: 0x0028
+KB_GRAVEACC_TILDE    :: 0x0029
+KB_COMMA             :: 0x0033
+KB_PERIOD            :: 0x0034
+KB_QUESTIONMARK      :: 0x0035
+KB_CAPSLOCK          :: 0x003A
+
+KB_F1  :: 0x003B
+KB_F2  :: 0x003C
+KB_F3  :: 0x003D
+KB_F4  :: 0x003E
+KB_F5  :: 0x003F
+KB_F6  :: 0x0040
+KB_F7  :: 0x0041
+KB_F8  :: 0x0042
+KB_F9  :: 0x0043
+KB_F10 :: 0x0044
+KB_F11 :: 0x0057
+KB_F12 :: 0x0058
+
+KB_PRINTSCREEN   :: 0xE037
+KB_SCROLLLOCK    :: 0x0046
+KB_PAUSE         :: 0xE11D45
+KB_INSERT        :: 0xE052
+KB_HOME          :: 0xE047
+KB_PAGEUP        :: 0xE049
+KB_DELETEFORWARD :: 0xE053
+KB_END           :: 0xE04F
+KB_PAGEDOWN      :: 0xE051
+KB_RIGHTARROW    :: 0xE04D
+KB_LEFTARROW     :: 0xE04B
+KB_DOWNARROW     :: 0xE050
+KB_UPARROW       :: 0xE048
+
+KP_NUMLOCK_CLEAR :: 0x0045
+KP_FORWARDSLASH  :: 0xE035
+KP_STAR          :: 0x0037
+KP_DASH          :: 0x004A
+KP_PLUS          :: 0x004E
+KP_ENTER         :: 0xE01C
+KP_1_END         :: 0x004F
+KP_2_DOWNARROW   :: 0x0050
+KP_3_PAGEDN      :: 0x0051
+KP_4_LEFTARROW   :: 0x004B
+KP_5             :: 0x004C
+KP_6_RIGHTARROW  :: 0x004D
+KP_7_HOME        :: 0x0047
+KP_8_UPARROW     :: 0x0048
+KP_9_PAGEUP      :: 0x0049
+KP_0_INSERT      :: 0x0052
+KP_PERIOD        :: 0x0053
+
+KB_NONUS_SLASHBAR :: 0x0056
+KB_APPLICATION    :: 0xE05D
+KB_POWER          :: 0xE05E
+KB_EQUALS         :: 0x0059
+KB_F13            :: 0x0064
+KB_F14            :: 0x0065
+KB_F15            :: 0x0066
+KB_F16            :: 0x0067
+KB_F17            :: 0x0068
+KB_F18            :: 0x0069
+KB_F19            :: 0x006A
+KB_F20            :: 0x006B
+KB_F21            :: 0x006C
+KB_F22            :: 0x006D
+KB_F23            :: 0x006E
+KB_F24            :: 0x0076
+
+KP_COMMA :: 0x007E
+
+KB_INTERNATIONAL1 :: 0x0073
+KB_INTERNATIONAL2 :: 0x0070
+KB_INTERNATIONAL3 :: 0x007D
+KB_INTERNATIONAL4 :: 0x0079
+KB_INTERNATIONAL5 :: 0x007B
+KB_INTERNATIONAL6 :: 0x005C
+
+KB_LANG1 :: 0x0072
+KB_LANG2 :: 0x0071
+KB_LANG3 :: 0x0078
+KB_LANG4 :: 0x0077
+KB_LANG5 :: 0x0076
+
+KB_LEFTCONTROL  :: 0x001D
+KB_LEFTSHIFT    :: 0x002A
+KB_LEFTALT      :: 0x0038
+KB_LEFTGUI      :: 0xE05B
+KB_RIGHTCONTROL :: 0xE01D
+KB_RIGHTSHIFT   :: 0x0036
+KB_RIGHTALT     :: 0xE038
+KB_RIGHTGUI     :: 0xE05C
+
+FN_SCANNEXTTRACK          :: 0xE019
+FN_SCANPREVTRACK          :: 0xE010
+FN_STOP                   :: 0xE024
+FN_PLAY_PAUSE             :: 0xE022
+FN_MUTE                   :: 0xE020
+FN_VOLUMEINC              :: 0xE030
+FN_VOLUMEDEC              :: 0xE02E
+FN_AL_CONSUMERCTRLCONFIG  :: 0xE06D
+FN_AL_EMAILREADER         :: 0xE06C
+FN_AL_CALCULATOR          :: 0xE021
+FN_AL_LOCALMACHINEBROWSER :: 0xE06B
+FN_AC_SEARCH              :: 0xE065
+FN_AC_HOME                :: 0xE032
+FN_AC_BACK                :: 0xE06A
+FN_AC_FORWARD             :: 0xE069
+FN_AC_STOP                :: 0xE068
+FN_AC_REFRESH             :: 0xE067
+FN_AC_BOOKMARKS           :: 0xE066

+ 0 - 0
core/encoding/ansi/ansi.odin → core/terminal/ansi/ansi.odin


+ 0 - 0
core/encoding/ansi/doc.odin → core/terminal/ansi/doc.odin


+ 4 - 0
core/terminal/doc.odin

@@ -0,0 +1,4 @@
+/*
+This package is for interacting with the command line interface of the system.
+*/
+package terminal

+ 87 - 0
core/terminal/internal.odin

@@ -0,0 +1,87 @@
+#+private
+package terminal
+
+import "core:os"
+import "core:strings"
+
+// Reference documentation:
+//
+// - [[ https://no-color.org/ ]]
+// - [[ https://github.com/termstandard/colors ]]
+// - [[ https://invisible-island.net/ncurses/terminfo.src.html ]]
+
+get_no_color :: proc() -> bool {
+	if no_color, ok := os.lookup_env("NO_COLOR"); ok {
+		defer delete(no_color)
+		return no_color != ""
+	}
+	return false
+}
+
+get_environment_color :: proc() -> Color_Depth {
+	// `COLORTERM` is non-standard but widespread and unambiguous.
+	if colorterm, ok := os.lookup_env("COLORTERM"); ok {
+		defer delete(colorterm)
+		// These are the only values that are typically advertised that have
+		// anything to do with color depth.
+		if colorterm == "truecolor" || colorterm == "24bit" {
+			return .True_Color
+		}
+	}
+
+	if term, ok := os.lookup_env("TERM"); ok {
+		defer delete(term)
+		if strings.contains(term, "-truecolor") {
+			return .True_Color
+		}
+		if strings.contains(term, "-256color") {
+			return .Eight_Bit
+		}
+		if strings.contains(term, "-16color") {
+			return .Four_Bit
+		}
+
+		// The `terminfo` database, which is stored in binary on *nix
+		// platforms, has an undocumented format that is not guaranteed to be
+		// portable, so beyond this point, we can only make safe assumptions.
+		//
+		// This section should only be necessary for terminals that do not
+		// define any of the previous environment values.
+		//
+		// Only a small sampling of some common values are checked here.
+		switch term {
+		case "ansi":       fallthrough
+		case "konsole":    fallthrough
+		case "putty":      fallthrough
+		case "rxvt":       fallthrough
+		case "rxvt-color": fallthrough
+		case "screen":     fallthrough
+		case "st":         fallthrough
+		case "tmux":       fallthrough
+		case "vte":        fallthrough
+		case "xterm":      fallthrough
+		case "xterm-color":
+			return .Three_Bit
+		}
+	}
+
+	return .None
+}
+
+@(init)
+init_terminal :: proc() {
+	_init_terminal()
+
+	// We respect `NO_COLOR` specifically as a color-disabler but not as a
+	// blanket ban on any terminal manipulation codes, hence why this comes
+	// after `_init_terminal` which will allow Windows to enable Virtual
+	// Terminal Processing for non-color control sequences.
+	if !get_no_color() {
+		color_enabled = color_depth > .None
+	}
+}
+
+@(fini)
+fini_terminal :: proc() {
+	_fini_terminal()
+}

+ 36 - 0
core/terminal/terminal.odin

@@ -0,0 +1,36 @@
+package terminal
+
+import "core:os"
+
+/*
+This describes the range of colors that a terminal is capable of supporting.
+*/
+Color_Depth :: enum {
+	None,       // No color support
+	Three_Bit,  // 8 colors
+	Four_Bit,   // 16 colors
+	Eight_Bit,  // 256 colors
+	True_Color, // 24-bit true color
+}
+
+/*
+Returns true if the file `handle` is attached to a terminal.
+
+This is normally true for `os.stdout` and `os.stderr` unless they are
+redirected to a file.
+*/
+@(require_results)
+is_terminal :: proc(handle: os.Handle) -> bool {
+	return _is_terminal(handle)
+}
+
+/*
+This is true if the terminal is accepting any form of colored text output.
+*/
+color_enabled: bool
+
+/*
+This value reports the color depth support as reported by the terminal at the
+start of the program.
+*/
+color_depth: Color_Depth

+ 16 - 0
core/terminal/terminal_posix.odin

@@ -0,0 +1,16 @@
+#+private
+#+build linux, darwin, netbsd, openbsd, freebsd, haiku
+package terminal
+
+import "core:os"
+import "core:sys/posix"
+
+_is_terminal :: proc(handle: os.Handle) -> bool {
+	return bool(posix.isatty(posix.FD(handle)))
+}
+
+_init_terminal :: proc() {
+	color_depth = get_environment_color()
+}
+
+_fini_terminal :: proc() { }

+ 60 - 0
core/terminal/terminal_windows.odin

@@ -0,0 +1,60 @@
+#+private
+package terminal
+
+import "core:os"
+import "core:sys/windows"
+
+_is_terminal :: proc(handle: os.Handle) -> bool {
+	is_tty := windows.GetFileType(windows.HANDLE(handle)) == windows.FILE_TYPE_CHAR
+	return is_tty
+}
+
+old_modes: [2]struct{
+	handle: windows.DWORD,
+	mode: windows.DWORD,
+} = {
+	{windows.STD_OUTPUT_HANDLE, 0},
+	{windows.STD_ERROR_HANDLE, 0},
+}
+
+@(init)
+_init_terminal :: proc() {
+	vtp_enabled: bool
+
+	for &v in old_modes {
+		handle := windows.GetStdHandle(v.handle)
+		if handle == windows.INVALID_HANDLE || handle == nil {
+			return
+		}
+		if windows.GetConsoleMode(handle, &v.mode) {
+			windows.SetConsoleMode(handle, v.mode | windows.ENABLE_PROCESSED_OUTPUT | windows.ENABLE_VIRTUAL_TERMINAL_PROCESSING)
+
+			new_mode: windows.DWORD
+			windows.GetConsoleMode(handle, &new_mode)
+
+			if new_mode & (windows.ENABLE_PROCESSED_OUTPUT | windows.ENABLE_VIRTUAL_TERMINAL_PROCESSING) != 0 {
+				vtp_enabled = true
+			}
+		}
+	}
+
+	if vtp_enabled {
+		// This color depth is available on Windows 10 since build 10586.
+		color_depth = .Four_Bit
+	} else {
+		// The user may be on a non-default terminal emulator.
+		color_depth = get_environment_color()
+	}
+}
+
+@(fini)
+_fini_terminal :: proc() {
+	for v in old_modes {
+		handle := windows.GetStdHandle(v.handle)
+		if handle == windows.INVALID_HANDLE || handle == nil {
+			return
+		}
+		
+		windows.SetConsoleMode(handle, v.mode)
+	}
+}

+ 1 - 1
core/testing/reporting.odin

@@ -10,12 +10,12 @@ package testing
 */
 
 import "base:runtime"
-import "core:encoding/ansi"
 import "core:fmt"
 import "core:io"
 import "core:mem"
 import "core:path/filepath"
 import "core:strings"
+import "core:terminal/ansi"
 
 // Definitions of colors for use in the test runner.
 SGR_RESET   :: ansi.CSI + ansi.RESET           + ansi.SGR

+ 94 - 66
core/testing/runner.odin

@@ -13,7 +13,6 @@ package testing
 import "base:intrinsics"
 import "base:runtime"
 import "core:bytes"
-import "core:encoding/ansi"
 @require import "core:encoding/base64"
 @require import "core:encoding/json"
 import "core:fmt"
@@ -25,6 +24,8 @@ import "core:os"
 import "core:slice"
 @require import "core:strings"
 import "core:sync/chan"
+import "core:terminal"
+import "core:terminal/ansi"
 import "core:thread"
 import "core:time"
 
@@ -44,6 +45,7 @@ PER_THREAD_MEMORY     : int    : #config(ODIN_TEST_THREAD_MEMORY, mem.ROLLBACK_S
 // The format is: `package.test_name,test_name_only,...`
 TEST_NAMES            : string : #config(ODIN_TEST_NAMES, "")
 // Show the fancy animated progress report.
+// This requires terminal color support, as well as STDOUT to not be redirected to a file.
 FANCY_OUTPUT          : bool   : #config(ODIN_TEST_FANCY, true)
 // Copy failed tests to the clipboard when done.
 USE_CLIPBOARD         : bool   : #config(ODIN_TEST_CLIPBOARD, false)
@@ -70,6 +72,9 @@ get_log_level :: #force_inline proc() -> runtime.Logger_Level {
 	}
 }
 
+@(private) global_log_colors_disabled: bool
+@(private) global_ansi_disabled: bool
+
 JSON :: struct {
 	total:    int,
 	success:  int,
@@ -129,11 +134,16 @@ run_test_task :: proc(task: thread.Task) {
 	
 	context.assertion_failure_proc = test_assertion_failure_proc
 
+	logger_options := Default_Test_Logger_Opts
+	if global_log_colors_disabled {
+		logger_options -= {.Terminal_Color}
+	}
+
 	context.logger = {
 		procedure = test_logger_proc,
 		data = &data.t,
 		lowest_level = get_log_level(),
-		options = Default_Test_Logger_Opts,
+		options = logger_options,
 	}
 
 	random_generator_state: runtime.Default_Random_State
@@ -204,13 +214,16 @@ runner :: proc(internal_tests: []Internal_Test) -> bool {
 		}
 	}
 
-	when ODIN_OS == .Windows {
-		console_ansi_init()
-	}
-
 	stdout := io.to_writer(os.stream_from_handle(os.stdout))
 	stderr := io.to_writer(os.stream_from_handle(os.stderr))
 
+	// The animations are only ever shown through STDOUT;
+	// STDERR is used exclusively for logging regardless of error level.
+	global_log_colors_disabled = !terminal.color_enabled || !terminal.is_terminal(os.stderr)
+	global_ansi_disabled       = !terminal.is_terminal(os.stdout)
+
+	should_show_animations := FANCY_OUTPUT && terminal.color_enabled && !global_ansi_disabled
+
 	// -- Prepare test data.
 
 	alloc_error: mem.Allocator_Error
@@ -268,12 +281,12 @@ runner :: proc(internal_tests: []Internal_Test) -> bool {
 	total_done_count    := 0
 	total_test_count    := len(internal_tests)
 
-	when !FANCY_OUTPUT {
-		// This is strictly for updating the window title when the progress
-		// report is disabled. We're otherwise able to depend on the call to
-		// `needs_to_redraw`.
-		last_done_count := -1
-	}
+
+	// This is strictly for updating the window title when the progress
+	// report is disabled. We're otherwise able to depend on the call to
+	// `needs_to_redraw`.
+	last_done_count := -1
+
 
 	if total_test_count == 0 {
 		// Exit early.
@@ -342,31 +355,31 @@ runner :: proc(internal_tests: []Internal_Test) -> bool {
 	fmt.assertf(alloc_error == nil, "Error allocating memory for test report: %v", alloc_error)
 	defer destroy_report(&report)
 
-	when FANCY_OUTPUT {
-		// We cannot make use of the ANSI save/restore cursor codes, because they
-		// work by absolute screen coordinates. This will cause unnecessary
-		// scrollback if we print at the bottom of someone's terminal.
-		ansi_redraw_string := fmt.aprintf(
-			// ANSI for "go up N lines then erase the screen from the cursor forward."
-			ansi.CSI + "%i" + ansi.CPL + ansi.CSI + ansi.ED +
-			// We'll combine this with the window title format string, since it
-			// can be printed at the same time.
-			"%s",
-			// 1 extra line for the status bar.
-			1 + len(report.packages), OSC_WINDOW_TITLE)
-		assert(len(ansi_redraw_string) > 0, "Error allocating ANSI redraw string.")
-		defer delete(ansi_redraw_string)
-
-		thread_count_status_string: string = ---
-		{
-			PADDING :: PROGRESS_COLUMN_SPACING + PROGRESS_WIDTH
 
-			unpadded := fmt.tprintf("%i thread%s", thread_count, "" if thread_count == 1 else "s")
-			thread_count_status_string = fmt.aprintf("%- *[1]s", unpadded, report.pkg_column_len + PADDING)
-			assert(len(thread_count_status_string) > 0, "Error allocating thread count status string.")
-		}
-		defer delete(thread_count_status_string)
+	// We cannot make use of the ANSI save/restore cursor codes, because they
+	// work by absolute screen coordinates. This will cause unnecessary
+	// scrollback if we print at the bottom of someone's terminal.
+	ansi_redraw_string := fmt.aprintf(
+		// ANSI for "go up N lines then erase the screen from the cursor forward."
+		ansi.CSI + "%i" + ansi.CPL + ansi.CSI + ansi.ED +
+		// We'll combine this with the window title format string, since it
+		// can be printed at the same time.
+		"%s",
+		// 1 extra line for the status bar.
+		1 + len(report.packages), OSC_WINDOW_TITLE)
+	assert(len(ansi_redraw_string) > 0, "Error allocating ANSI redraw string.")
+	defer delete(ansi_redraw_string)
+
+	thread_count_status_string: string = ---
+	{
+		PADDING :: PROGRESS_COLUMN_SPACING + PROGRESS_WIDTH
+
+		unpadded := fmt.tprintf("%i thread%s", thread_count, "" if thread_count == 1 else "s")
+		thread_count_status_string = fmt.aprintf("%- *[1]s", unpadded, report.pkg_column_len + PADDING)
+		assert(len(thread_count_status_string) > 0, "Error allocating thread count status string.")
 	}
+	defer delete(thread_count_status_string)
+
 
 	task_data_slots: []Task_Data = ---
 	task_data_slots, alloc_error = make([]Task_Data, thread_count)
@@ -442,11 +455,16 @@ runner :: proc(internal_tests: []Internal_Test) -> bool {
 	// digging through the source to divine everywhere it is used for that.
 	shared_log_allocator := context.allocator
 
+	logger_options := Default_Test_Logger_Opts - {.Short_File_Path, .Line, .Procedure}
+	if global_log_colors_disabled {
+		logger_options -= {.Terminal_Color}
+	}
+
 	context.logger = {
 		procedure = runner_logger_proc,
 		data = &log_messages,
 		lowest_level = get_log_level(),
-		options = Default_Test_Logger_Opts - {.Short_File_Path, .Line, .Procedure},
+		options = logger_options,
 	}
 
 	run_index: int
@@ -481,11 +499,13 @@ runner :: proc(internal_tests: []Internal_Test) -> bool {
 
 	setup_signal_handler()
 
-	fmt.wprint(stdout, ansi.CSI + ansi.DECTCEM_HIDE)
+	if !global_ansi_disabled {
+		fmt.wprint(stdout, ansi.CSI + ansi.DECTCEM_HIDE)
+	}
 
-	when FANCY_OUTPUT {
-		signals_were_raised := false
+	signals_were_raised := false
 
+	if should_show_animations {
 		redraw_report(stdout, report)
 		draw_status_bar(stdout, thread_count_status_string, total_done_count, total_test_count)
 	}
@@ -703,22 +723,22 @@ runner :: proc(internal_tests: []Internal_Test) -> bool {
 			break main_loop
 		}
 
-		when FANCY_OUTPUT {
-			// Because the bounds checking procs send directly to STDERR with
-			// no way to redirect or handle them, we need to at least try to
-			// let the user see those messages when using the animated progress
-			// report. This flag may be set by the block of code below if a
-			// signal is raised.
-			//
-			// It'll be purely by luck if the output is interleaved properly,
-			// given the nature of non-thread-safe printing.
-			//
-			// At worst, if Odin did not print any error for this signal, we'll
-			// just re-display the progress report. The fatal log error message
-			// should be enough to clue the user in that something dire has
-			// occurred.
-			bypass_progress_overwrite := false
-		}
+
+		// Because the bounds checking procs send directly to STDERR with
+		// no way to redirect or handle them, we need to at least try to
+		// let the user see those messages when using the animated progress
+		// report. This flag may be set by the block of code below if a
+		// signal is raised.
+		//
+		// It'll be purely by luck if the output is interleaved properly,
+		// given the nature of non-thread-safe printing.
+		//
+		// At worst, if Odin did not print any error for this signal, we'll
+		// just re-display the progress report. The fatal log error message
+		// should be enough to clue the user in that something dire has
+		// occurred.
+		bypass_progress_overwrite := false
+
 
 		if test_index, reason, ok := should_stop_test(); ok {
 			#no_bounds_check report.all_test_states[test_index] = .Failed
@@ -752,7 +772,7 @@ runner :: proc(internal_tests: []Internal_Test) -> bool {
 					log.fatalf("Caught signal to stop test #%i %s.%s for: %v.", test_index, it.pkg, it.name, reason)
 				}
 
-				when FANCY_OUTPUT {
+				if should_show_animations {
 					bypass_progress_overwrite = true
 					signals_were_raised = true
 				}
@@ -766,7 +786,7 @@ runner :: proc(internal_tests: []Internal_Test) -> bool {
 
 		// -- Redraw.
 
-		when FANCY_OUTPUT {
+		if should_show_animations {
 			if len(log_messages) == 0 && !needs_to_redraw(report) {
 				continue main_loop
 			}
@@ -776,7 +796,9 @@ runner :: proc(internal_tests: []Internal_Test) -> bool {
 			}
 		} else {
 			if total_done_count != last_done_count {
-				fmt.wprintf(stdout, OSC_WINDOW_TITLE, total_done_count, total_test_count)
+				if !global_ansi_disabled {
+					fmt.wprintf(stdout, OSC_WINDOW_TITLE, total_done_count, total_test_count)
+				}
 				last_done_count = total_done_count
 			}
 
@@ -801,7 +823,7 @@ runner :: proc(internal_tests: []Internal_Test) -> bool {
 		clear(&log_messages)
 		bytes.buffer_reset(&batch_buffer)
 
-		when FANCY_OUTPUT {
+		if should_show_animations {
 			redraw_report(batch_writer, report)
 			draw_status_bar(batch_writer, thread_count_status_string, total_done_count, total_test_count)
 			fmt.wprint(stdout, bytes.buffer_to_string(&batch_buffer))
@@ -822,7 +844,7 @@ runner :: proc(internal_tests: []Internal_Test) -> bool {
 
 	finished_in := time.since(start_time)
 
-	when !FANCY_OUTPUT {
+	if !should_show_animations || !terminal.is_terminal(os.stderr) {
 		// One line to space out the results, since we don't have the status
 		// bar in plain mode.
 		fmt.wprintln(batch_writer)
@@ -836,24 +858,28 @@ runner :: proc(internal_tests: []Internal_Test) -> bool {
 	
 	if total_done_count != total_test_count {
 		not_run_count := total_test_count - total_done_count
+		message := " %i %s left undone." if global_log_colors_disabled else " " + SGR_READY + "%i" + SGR_RESET + " %s left undone."
 		fmt.wprintf(batch_writer,
-			" " + SGR_READY + "%i" + SGR_RESET + " %s left undone.",
+			message,
 			not_run_count,
 			"test was" if not_run_count == 1 else "tests were")
 	}
 
 	if total_success_count == total_test_count {
+		message := " %s successful." if global_log_colors_disabled else " %s " + SGR_SUCCESS + "successful." + SGR_RESET
 		fmt.wprintfln(batch_writer,
-			" %s " + SGR_SUCCESS + "successful." + SGR_RESET,
+			message,
 			"The test was" if total_test_count == 1 else "All tests were")
 	} else if total_failure_count > 0 {
 		if total_failure_count == total_test_count {
+			message := " %s failed." if global_log_colors_disabled else " %s " + SGR_FAILED + "failed." + SGR_RESET
 			fmt.wprintfln(batch_writer,
-				" %s " + SGR_FAILED + "failed." + SGR_RESET,
+				message,
 				"The test" if total_test_count == 1 else "All tests")
 		} else {
+			message := " %i test%s failed." if global_log_colors_disabled else " " + SGR_FAILED + "%i" + SGR_RESET + " test%s failed."
 			fmt.wprintfln(batch_writer,
-				" " + SGR_FAILED + "%i" + SGR_RESET + " test%s failed.",
+				message,
 				total_failure_count,
 				"" if total_failure_count == 1 else "s")
 		}
@@ -907,9 +933,11 @@ runner :: proc(internal_tests: []Internal_Test) -> bool {
 		}
 	}
 
-	fmt.wprint(stdout, ansi.CSI + ansi.DECTCEM_SHOW)
+	if !global_ansi_disabled {
+		fmt.wprint(stdout, ansi.CSI + ansi.DECTCEM_SHOW)
+	}
 
-	when FANCY_OUTPUT {
+	if should_show_animations {
 		if signals_were_raised {
 			fmt.wprintln(batch_writer, `
 Signals were raised during this test run. Log messages are likely to have collided with each other.

+ 0 - 22
core/testing/runner_windows.odin

@@ -1,22 +0,0 @@
-#+private
-package testing
-
-import win32 "core:sys/windows"
-
-console_ansi_init :: proc() {
-	stdout := win32.GetStdHandle(win32.STD_OUTPUT_HANDLE)
-	if stdout != win32.INVALID_HANDLE && stdout != nil {
-		old_console_mode: u32
-		if win32.GetConsoleMode(stdout, &old_console_mode) {
-			win32.SetConsoleMode(stdout, old_console_mode | win32.ENABLE_VIRTUAL_TERMINAL_PROCESSING)
-		}
-	}
-
-	stderr := win32.GetStdHandle(win32.STD_ERROR_HANDLE)
-	if stderr != win32.INVALID_HANDLE && stderr != nil {
-		old_console_mode: u32
-		if win32.GetConsoleMode(stderr, &old_console_mode) {
-			win32.SetConsoleMode(stderr, old_console_mode | win32.ENABLE_VIRTUAL_TERMINAL_PROCESSING)
-		}
-	}
-}

+ 7 - 5
core/testing/signal_handler_libc.odin

@@ -12,9 +12,9 @@ package testing
 
 import "base:intrinsics"
 import "core:c/libc"
-import "core:encoding/ansi"
-import "core:sync"
 import "core:os"
+import "core:sync"
+import "core:terminal/ansi"
 
 @(private="file") stop_runner_flag: libc.sig_atomic_t
 
@@ -63,9 +63,11 @@ stop_test_callback :: proc "c" (sig: libc.int) {
 		// NOTE(Feoramund): Using these write calls in a signal handler is
 		// undefined behavior in C99 but possibly tolerated in POSIX 2008.
 		// Either way, we may as well try to salvage what we can.
-		show_cursor := ansi.CSI + ansi.DECTCEM_SHOW
-		libc.fwrite(raw_data(show_cursor), size_of(byte), len(show_cursor), libc.stdout)
-		libc.fflush(libc.stdout)
+		if !global_ansi_disabled {
+			show_cursor := ansi.CSI + ansi.DECTCEM_SHOW
+			libc.fwrite(raw_data(show_cursor), size_of(byte), len(show_cursor), libc.stdout)
+			libc.fflush(libc.stdout)
+		}
 
 		// This is an attempt at being compliant by avoiding printf.
 		sigbuf: [8]byte

+ 5 - 1
examples/all/all_linux.odin

@@ -3,4 +3,8 @@ package all
 
 import linux "core:sys/linux"
 
-_ :: linux
+import xlib  "vendor:x11/xlib"
+
+_ :: linux
+
+_ :: xlib

+ 5 - 2
examples/all/all_main.odin

@@ -58,7 +58,6 @@ import trace            "core:debug/trace"
 import dynlib           "core:dynlib"
 import net              "core:net"
 
-import ansi             "core:encoding/ansi"
 import base32           "core:encoding/base32"
 import base64           "core:encoding/base64"
 import cbor             "core:encoding/cbor"
@@ -129,6 +128,9 @@ import strings          "core:strings"
 import sync             "core:sync"
 import testing          "core:testing"
 
+import terminal         "core:terminal"
+import ansi             "core:terminal/ansi"
+
 import edit             "core:text/edit"
 import i18n             "core:text/i18n"
 import match            "core:text/match"
@@ -201,7 +203,6 @@ _ :: pe
 _ :: trace
 _ :: dynlib
 _ :: net
-_ :: ansi
 _ :: base32
 _ :: base64
 _ :: csv
@@ -257,6 +258,8 @@ _ :: strconv
 _ :: strings
 _ :: sync
 _ :: testing
+_ :: terminal
+_ :: ansi
 _ :: scanner
 _ :: i18n
 _ :: match

+ 0 - 4
examples/all/all_vendor.odin

@@ -28,8 +28,6 @@ import nvg       "vendor:nanovg"
 import nvg_gl    "vendor:nanovg/gl"
 import fontstash "vendor:fontstash"
 
-import xlib       "vendor:x11/xlib"
-
 _ :: cgltf
 // _ :: commonmark
 _ :: ENet
@@ -57,8 +55,6 @@ _ :: nvg
 _ :: nvg_gl
 _ :: fontstash
 
-_ :: xlib
-
 
 // NOTE: needed for doc generator
 

+ 2 - 0
examples/all/all_vendor_windows.odin

@@ -3,8 +3,10 @@ package all
 import wgpu "vendor:wgpu"
 import b2 "vendor:box2d"
 import game_input "vendor:windows/GameInput"
+import XAudio2 "vendor:windows/XAudio2"
 
 _ :: wgpu
 _ :: b2
 _ :: game_input
+_ :: XAudio2
 

+ 1 - 1
misc/get-date.c

@@ -9,5 +9,5 @@
 int main(int arg_count, char const **arg_ptr) {
 	time_t t = time(NULL);
 	struct tm* now = localtime(&t);
-	printf("%04d%02d%02d", now->tm_year + 1900, now->tm_mon + 1, now->tm_mday);
+	printf("%04d-%02d-%02d", now->tm_year + 1900, now->tm_mon + 1, now->tm_mday);
 }

+ 7 - 1
src/bug_report.cpp

@@ -667,8 +667,14 @@ gb_internal void print_bug_report_help() {
 	gb_printf("-nightly");
 	#endif
 
+	String version = {};
+
 	#ifdef GIT_SHA
-	gb_printf(":%s", GIT_SHA);
+	version.text = cast(u8 *)GIT_SHA;
+	version.len  = gb_strlen(GIT_SHA);
+	if (version != "") {
+		gb_printf(":%.*s", LIT(version));
+	}
 	#endif
 
 	gb_printf("\n");

+ 27 - 9
src/build_settings.cpp

@@ -459,6 +459,7 @@ struct BuildContext {
 	bool   ignore_unknown_attributes;
 	bool   no_bounds_check;
 	bool   no_type_assert;
+	bool   dynamic_literals;  // Opt-in to `#+feature dynamic-literals` project-wide.
 	bool   no_output_files;
 	bool   no_crt;
 	bool   no_rpath;
@@ -1915,12 +1916,6 @@ gb_internal void init_build_context(TargetMetrics *cross_target, Subtarget subta
 	}
 
 
-	// TODO: Static map calls are bugged on `amd64sysv` abi.
-	if (bc->metrics.os != TargetOs_windows && bc->metrics.arch == TargetArch_amd64) {
-		// ENFORCE DYNAMIC MAP CALLS
-		bc->dynamic_map_calls = true;
-	}
-
 	bc->ODIN_VALGRIND_SUPPORT = false;
 	if (build_context.metrics.os != TargetOs_windows) {
 		switch (bc->metrics.arch) {
@@ -2214,11 +2209,34 @@ gb_internal bool init_build_paths(String init_filename) {
 			while (output_name.len > 0 && (output_name[output_name.len-1] == '/' || output_name[output_name.len-1] == '\\')) {
 				output_name.len -= 1;
 			}
+			// Only trim the extension if it's an Odin source file.
+			// This lets people build folders with extensions or files beginning with dots.
+			if (path_extension(output_name) == ".odin" && !path_is_directory(output_name)) {
+				output_name = remove_extension_from_path(output_name);
+			}
 			output_name = remove_directory_from_path(output_name);
-			output_name = remove_extension_from_path(output_name);
 			output_name = copy_string(ha, string_trim_whitespace(output_name));
-			output_path = path_from_string(ha, output_name);
-			
+			// This is `path_from_string` without the extension trimming.
+			Path res = {};
+			if (output_name.len > 0) {
+				String fullpath = path_to_full_path(ha, output_name);
+				defer (gb_free(ha, fullpath.text));
+
+				res.basename = directory_from_path(fullpath);
+				res.basename = copy_string(ha, res.basename);
+
+				if (path_is_directory(fullpath)) {
+					if (res.basename.len > 0 && res.basename.text[res.basename.len - 1] == '/') {
+						res.basename.len--;
+					}
+				} else {
+					isize name_start = (res.basename.len > 0) ? res.basename.len + 1 : res.basename.len;
+					res.name         = substring(fullpath, name_start, fullpath.len);
+					res.name         = copy_string(ha, res.name);
+				}
+			}
+			output_path = res;
+
 			// Note(Dragos): This is a fix for empty filenames
 			// Turn the trailing folder into the file name
 			if (output_path.name.len == 0) {

+ 326 - 3
src/check_builtin.cpp

@@ -223,9 +223,9 @@ gb_internal void add_objc_proc_type(CheckerContext *c, Ast *call, Type *return_t
 	data.kind = kind;
 	data.proc_type = alloc_type_proc(scope, params, param_types.count, results, results->Tuple.variables.count, false, ProcCC_CDecl);
 
-	mutex_lock(&c->info->objc_types_mutex);
+	mutex_lock(&c->info->objc_objc_msgSend_mutex);
 	map_set(&c->info->objc_msgSend_types, call, data);
-	mutex_unlock(&c->info->objc_types_mutex);
+	mutex_unlock(&c->info->objc_objc_msgSend_mutex);
 
 	try_to_add_package_dependency(c, "runtime", "objc_msgSend");
 	try_to_add_package_dependency(c, "runtime", "objc_msgSend_fpret");
@@ -387,6 +387,59 @@ gb_internal bool check_builtin_objc_procedure(CheckerContext *c, Operand *operan
 		try_to_add_package_dependency(c, "runtime", "objc_allocateClassPair");
 		return true;
 	} break;
+
+	case BuiltinProc_objc_ivar_get:
+	{
+		Type *self_type = nullptr;
+
+		Operand self = {};
+		check_expr_or_type(c, &self, ce->args[0]);
+
+		if (!is_operand_value(self) || !check_is_assignable_to(c, &self, t_objc_id)) {
+			gbString e = expr_to_string(self.expr);
+			gbString t = type_to_string(self.type);
+			error(self.expr, "'%.*s' expected a type or value derived from intrinsics.objc_object, got '%s' of type %s", LIT(builtin_name), e, t);
+			gb_string_free(t);
+			gb_string_free(e);
+			return false;
+		} else if (!is_type_pointer(self.type)) {
+			gbString e = expr_to_string(self.expr);
+			gbString t = type_to_string(self.type);
+			error(self.expr, "'%.*s' expected a pointer of a value derived from intrinsics.objc_object, got '%s' of type %s", LIT(builtin_name), e, t);
+			gb_string_free(t);
+			gb_string_free(e);
+			return false;
+		}
+
+		self_type = type_deref(self.type);
+
+		if (!(self_type->kind == Type_Named &&
+			self_type->Named.type_name != nullptr &&
+			self_type->Named.type_name->TypeName.objc_class_name != "")) {
+			gbString t = type_to_string(self_type);
+			error(self.expr, "'%.*s' expected a named type with the attribute @(objc_class=<string>) , got type %s", LIT(builtin_name), t);
+			gb_string_free(t);
+			return false;
+		}
+
+		Type *ivar_type = self_type->Named.type_name->TypeName.objc_ivar;
+		if (ivar_type == nullptr) {
+			gbString t = type_to_string(self_type);
+			error(self.expr, "'%.*s' requires that type %s have the attribute @(objc_ivar=<ivar_type_name>).", LIT(builtin_name), t);
+			gb_string_free(t);
+			return false;
+		}
+
+		if (type_hint != nullptr && type_hint->kind == Type_Pointer && type_hint->Pointer.elem == ivar_type) {
+			operand->type = type_hint;
+		} else {
+			operand->type = alloc_type_pointer(ivar_type);
+		}
+
+		operand->mode = Addressing_Value;
+		return true;
+
+	} break;
 	}
 }
 
@@ -2167,7 +2220,8 @@ gb_internal bool check_builtin_procedure(CheckerContext *c, Operand *operand, As
 	case BuiltinProc_objc_find_selector: 
 	case BuiltinProc_objc_find_class: 
 	case BuiltinProc_objc_register_selector: 
-	case BuiltinProc_objc_register_class: 
+	case BuiltinProc_objc_register_class:
+	case BuiltinProc_objc_ivar_get:
 		return check_builtin_objc_procedure(c, operand, call, id, type_hint);
 
 	case BuiltinProc___entry_point:
@@ -3189,6 +3243,194 @@ gb_internal bool check_builtin_procedure(CheckerContext *c, Operand *operand, As
 		break;
 	}
 
+	case BuiltinProc_compress_values: {
+		Operand *ops = gb_alloc_array(temporary_allocator(), Operand, ce->args.count);
+
+		isize value_count = 0;
+
+		for_array(i, ce->args) {
+			Ast *arg = ce->args[i];
+			Operand *op = &ops[i];
+			check_multi_expr(c, op, arg);
+			if (op->mode == Addressing_Invalid) {
+				return false;
+			}
+
+			if (op->type == nullptr || op->type == t_invalid) {
+				gbString s = expr_to_string(op->expr);
+				error(op->expr, "Invalid expression to '%.*s', got %s", LIT(builtin_name), s);
+				gb_string_free(s);
+			}
+			if (is_type_tuple(op->type)) {
+				value_count += op->type->Tuple.variables.count;
+			} else {
+				value_count += 1;
+			}
+		}
+
+		GB_ASSERT(value_count >= 1);
+
+		if (value_count == 1) {
+			*operand = ops[0];
+			break;
+		}
+
+		if (type_hint != nullptr) {
+			Type *th = base_type(type_hint);
+			if (th->kind == Type_Struct) {
+				if (value_count == th->Struct.fields.count) {
+					isize index = 0;
+					for_array(i, ce->args) {
+						Operand *op = &ops[i];
+						if (is_type_tuple(op->type)) {
+							for (Entity *v : op->type->Tuple.variables) {
+								Operand x = {};
+								x.mode = Addressing_Value;
+								x.type = v->type;
+								check_assignment(c, &x, th->Struct.fields[index++]->type, builtin_name);
+								if (x.mode == Addressing_Invalid) {
+									return false;
+								}
+							}
+						} else {
+							check_assignment(c, op, th->Struct.fields[index++]->type, builtin_name);
+							if (op->mode == Addressing_Invalid) {
+								return false;
+							}
+						}
+					}
+
+					operand->type = type_hint;
+					operand->mode = Addressing_Value;
+					break;
+				}
+			} else if (is_type_array_like(th)) {
+				if (cast(i64)value_count == get_array_type_count(th)) {
+					Type *elem = base_array_type(th);
+					for_array(i, ce->args) {
+						Operand *op = &ops[i];
+						if (is_type_tuple(op->type)) {
+							for (Entity *v : op->type->Tuple.variables) {
+								Operand x = {};
+								x.mode = Addressing_Value;
+								x.type = v->type;
+								check_assignment(c, &x, elem, builtin_name);
+								if (x.mode == Addressing_Invalid) {
+									return false;
+								}
+							}
+						} else {
+							check_assignment(c, op, elem, builtin_name);
+							if (op->mode == Addressing_Invalid) {
+								return false;
+							}
+						}
+					}
+
+					operand->type = type_hint;
+					operand->mode = Addressing_Value;
+					break;
+				}
+			}
+		}
+
+		bool all_types_the_same = true;
+		Type *last_type = nullptr;
+		for_array(i, ce->args) {
+			Operand *op = &ops[i];
+			if (is_type_tuple(op->type)) {
+				if (last_type == nullptr) {
+					op->type->Tuple.variables[0]->type;
+				}
+				for (Entity *v : op->type->Tuple.variables) {
+					if (!are_types_identical(last_type, v->type)) {
+						all_types_the_same = false;
+						break;
+					}
+					last_type = v->type;
+				}
+			} else {
+				if (last_type == nullptr) {
+					last_type = op->type;
+				} else {
+					if (!are_types_identical(last_type, op->type)) {
+						all_types_the_same = false;
+						break;
+					}
+					last_type = op->type;
+				}
+			}
+		}
+
+		if (all_types_the_same) {
+			Type *elem_type = default_type(last_type);
+			if (is_type_untyped(elem_type)) {
+				gbString s = expr_to_string(call);
+				error(call, "Invalid use of '%s' in '%.*s'", s, LIT(builtin_name));
+				gb_string_free(s);
+				return false;
+			}
+
+			operand->type = alloc_type_array(elem_type, value_count);
+			operand->mode = Addressing_Value;
+		} else {
+			Type *st = alloc_type_struct_complete();
+			st->Struct.fields = slice_make<Entity *>(permanent_allocator(), value_count);
+			st->Struct.tags = gb_alloc_array(permanent_allocator(), String, value_count);
+			st->Struct.offsets = gb_alloc_array(permanent_allocator(), i64, value_count);
+
+			Scope *scope = create_scope(c->info, nullptr);
+
+			Token token = {};
+			token.kind = Token_Ident;
+			token.pos = ast_token(call).pos;
+
+			isize index = 0;
+			for_array(i, ce->args) {
+				Operand *op = &ops[i];
+				if (is_type_tuple(op->type)) {
+					for (Entity *v : op->type->Tuple.variables) {
+						Type *t = default_type(v->type);
+						if (is_type_untyped(t)) {
+							gbString s = expr_to_string(op->expr);
+							error(op->expr, "Invalid use of '%s' in '%.*s'", s, LIT(builtin_name));
+							gb_string_free(s);
+							return false;
+						}
+
+						gbString s = gb_string_make_reserve(permanent_allocator(), 32);
+						s = gb_string_append_fmt(s, "v%lld", cast(long long)index);
+						token.string = make_string_c(s);
+						Entity *e = alloc_entity_field(scope, token, t, false, cast(i32)index, EntityState_Resolved);
+						st->Struct.fields[index++] = e;
+					}
+				} else {
+					Type *t = default_type(op->type);
+					if (is_type_untyped(t)) {
+						gbString s = expr_to_string(op->expr);
+						error(op->expr, "Invalid use of '%s' in '%.*s'", s, LIT(builtin_name));
+						gb_string_free(s);
+						return false;
+					}
+
+					gbString s = gb_string_make_reserve(permanent_allocator(), 32);
+					s = gb_string_append_fmt(s, "v%lld", cast(long long)index);
+					token.string = make_string_c(s);
+					Entity *e = alloc_entity_field(scope, token, t, false, cast(i32)index, EntityState_Resolved);
+					st->Struct.fields[index++] = e;
+				}
+			}
+
+
+			gb_unused(type_size_of(st));
+
+			operand->type = st;
+			operand->mode = Addressing_Value;
+		}
+		break;
+	}
+
+
 	case BuiltinProc_min: {
 		// min :: proc($T: typeid) -> ordered
 		// min :: proc(a: ..ordered) -> ordered
@@ -5635,6 +5877,87 @@ gb_internal bool check_builtin_procedure(CheckerContext *c, Operand *operand, As
 		}
 		operand->mode = Addressing_Type;
 		break;
+	case BuiltinProc_type_integer_to_unsigned:
+		if (operand->mode != Addressing_Type) {
+			error(operand->expr, "Expected a type for '%.*s'", LIT(builtin_name));
+			return false;
+		} 
+
+		if (is_type_polymorphic(operand->type)) {
+			gbString t = type_to_string(operand->type);
+			error(operand->expr, "Expected a non-polymorphic type for '%.*s', got %s", LIT(builtin_name), t);
+			gb_string_free(t);
+			return false;
+		}
+
+		{
+			Type *bt = base_type(operand->type);
+
+			if (bt->kind != Type_Basic || 
+				(bt->Basic.flags & BasicFlag_Unsigned) != 0 || 
+				(bt->Basic.flags & BasicFlag_Integer) == 0) {
+				gbString t = type_to_string(operand->type);
+				error(operand->expr, "Expected a signed integer type for '%.*s', got %s", LIT(builtin_name), t);
+				gb_string_free(t);
+				return false;
+			}
+
+			if ((bt->Basic.flags & BasicFlag_Untyped) != 0) {
+				gbString t = type_to_string(operand->type);
+				error(operand->expr, "Expected a non-untyped integer type for '%.*s', got %s", LIT(builtin_name), t);
+				gb_string_free(t);
+				return false;
+			}
+
+			Type *u_type = &basic_types[bt->Basic.kind + 1];
+
+			operand->type = u_type;
+		}
+		break;
+	case BuiltinProc_type_integer_to_signed:
+		if (operand->mode != Addressing_Type) {
+			error(operand->expr, "Expected a type for '%.*s'", LIT(builtin_name));
+			return false;
+		} 
+
+		if (is_type_polymorphic(operand->type)) {
+			gbString t = type_to_string(operand->type);
+			error(operand->expr, "Expected a non-polymorphic type for '%.*s', got %s", LIT(builtin_name), t);
+			gb_string_free(t);
+			return false;
+		}
+
+		{
+			Type *bt = base_type(operand->type);
+
+			if (bt->kind != Type_Basic || 
+				(bt->Basic.flags & BasicFlag_Unsigned) == 0 || 
+				(bt->Basic.flags & BasicFlag_Integer) == 0) {
+				gbString t = type_to_string(operand->type);
+				error(operand->expr, "Expected an unsigned integer type for '%.*s', got %s", LIT(builtin_name), t);
+				gb_string_free(t);
+				return false;
+			}
+
+			if ((bt->Basic.flags & BasicFlag_Untyped) != 0) {
+				gbString t = type_to_string(operand->type);
+				error(operand->expr, "Expected a non-untyped integer type for '%.*s', got %s", LIT(builtin_name), t);
+				gb_string_free(t);
+				return false;
+			}
+
+			if (bt->Basic.kind == Basic_uintptr) {
+				gbString t = type_to_string(operand->type);
+				error(operand->expr, "Type %s does not have a signed integer mapping for '%.*s'", t, LIT(builtin_name));
+				gb_string_free(t);
+				return false;
+			}
+
+			Type *u_type = &basic_types[bt->Basic.kind - 1];
+
+			operand->type = u_type;
+		}
+		break;
 	case BuiltinProc_type_merge:
 		{
 			operand->mode = Addressing_Type;

+ 177 - 36
src/check_decl.cpp

@@ -524,12 +524,90 @@ gb_internal void check_type_decl(CheckerContext *ctx, Entity *e, Ast *init_expr,
 	if (decl != nullptr) {
 		AttributeContext ac = {};
 		check_decl_attributes(ctx, decl->attributes, type_decl_attribute, &ac);
+
 		if (e->kind == Entity_TypeName && ac.objc_class != "") {
+
 			e->TypeName.objc_class_name = ac.objc_class;
 
+			if (ac.objc_is_implementation) {
+				e->TypeName.objc_is_implementation = ac.objc_is_implementation;
+				e->TypeName.objc_superclass        = ac.objc_superclass;
+				e->TypeName.objc_ivar              = ac.objc_ivar;
+				e->TypeName.objc_context_provider  = ac.objc_context_provider;
+
+				mutex_lock(&ctx->info->objc_class_name_mutex);
+				bool class_exists = string_set_update(&ctx->info->obcj_class_name_set, ac.objc_class);
+				mutex_unlock(&ctx->info->objc_class_name_mutex);
+				if (class_exists) {
+					error(e->token, "@(objc_class) name '%.*s' has already been used elsewhere", LIT(ac.objc_class));
+				}
+
+				mpsc_enqueue(&ctx->info->objc_class_implementations, e);
+
+				GB_ASSERT(e->TypeName.objc_ivar == nullptr || e->TypeName.objc_ivar->kind == Type_Named);
+
+				// Enqueue the contex_provider proc to be checked after it is resolved
+				if (e->TypeName.objc_context_provider != nullptr) {
+					mpsc_enqueue(&ctx->checker->procs_with_objc_context_provider_to_check, e);
+				}
+
+				// TODO(harold): I think there's a Check elsewhere in the checker for checking cycles.
+				//					See about moving this to the right location.
+				// Ensure superclass hierarchy are all Objective-C classes and does not cycle
+
+				// NOTE(harold): We check for superclass unconditionally (before checking if super is null)
+				//				 because this should be the case 99.99% of the time. Not subclassing something that
+				//				 is, or is the child of, NSObject means the objc runtime messaging will not properly work on this type.
+				TypeSet super_set{};
+				type_set_init(&super_set, 8);
+				defer (type_set_destroy(&super_set));
+
+				type_set_update(&super_set, e->type);
+
+				Type *super = ac.objc_superclass;
+				while (super != nullptr) {
+					if (type_set_update(&super_set, super)) {
+						error(e->token, "@(objc_superclass) Superclass hierarchy cycle encountered");
+						break;
+					}
+
+					check_single_global_entity(ctx->checker, super->Named.type_name, super->Named.type_name->decl_info);
+
+					if (super->kind != Type_Named) {
+						error(e->token, "@(objc_superclass) Referenced type must be a named struct");
+						break;
+					}
+
+					Type* named_type = base_named_type(super);
+					GB_ASSERT(named_type->kind == Type_Named);
+
+					if (!is_type_objc_object(named_type)) {
+						error(e->token, "@(objc_superclass) Superclass '%.*s' must be an Objective-C class", LIT(named_type->Named.name));
+						break;
+					}
+
+					if (named_type->Named.type_name->TypeName.objc_class_name == "") {
+						error(e->token, "@(objc_superclass) Superclass '%.*s' must have a valid @(objc_class) attribute", LIT(named_type->Named.name));
+						break;
+					}
+
+					super = named_type->Named.type_name->TypeName.objc_superclass;
+				}
+			} else {
+				if (ac.objc_superclass != nullptr) {
+					error(e->token, "@(objc_superclass) may only be applied when the @(obj_implement) attribute is also applied");
+				} else if (ac.objc_ivar != nullptr) {
+					error(e->token, "@(objc_ivar) may only be applied when the @(obj_implement) attribute is also applied");
+				} else if (ac.objc_context_provider != nullptr) {
+					error(e->token, "@(objc_context_provider) may only be applied when the @(obj_implement) attribute is also applied");
+				}
+			}
+
 			if (type_size_of(e->type) > 0) {
 				error(e->token, "@(objc_class) marked type must be of zero size");
 			}
+		} else if (ac.objc_is_implementation) {
+			error(e->token, "@(objc_implement) may only be applied when the @(objc_class) attribute is also applied");
 		}
 	}
 
@@ -922,7 +1000,7 @@ gb_internal String handle_link_name(CheckerContext *ctx, Token token, String lin
 }
 
 
-gb_internal void check_objc_methods(CheckerContext *ctx, Entity *e, AttributeContext const &ac) {
+gb_internal void check_objc_methods(CheckerContext *ctx, Entity *e, AttributeContext &ac) {
 	if (!(ac.objc_name.len || ac.objc_is_class_method || ac.objc_type)) {
 		return;
 	}
@@ -934,49 +1012,108 @@ gb_internal void check_objc_methods(CheckerContext *ctx, Entity *e, AttributeCon
 		error(e->token, "@(objc_name) is required with @(objc_type)");
 	} else {
 		Type *t = ac.objc_type;
-		if (t->kind == Type_Named) {
-			Entity *tn = t->Named.type_name;
 
-			GB_ASSERT(tn->kind == Entity_TypeName);
+		GB_ASSERT(t->kind == Type_Named);	// NOTE(harold): This is already checked for at the attribute resolution stage.
+		Entity *tn = t->Named.type_name;
 
-			if (tn->scope != e->scope) {
-				error(e->token, "@(objc_name) attribute may only be applied to procedures and types within the same scope");
-			} else {
-				mutex_lock(&global_type_name_objc_metadata_mutex);
-				defer (mutex_unlock(&global_type_name_objc_metadata_mutex));
+		GB_ASSERT(tn->kind == Entity_TypeName);
 
-				if (!tn->TypeName.objc_metadata) {
-					tn->TypeName.objc_metadata = create_type_name_obj_c_metadata();
-				}
-				auto *md = tn->TypeName.objc_metadata;
-				mutex_lock(md->mutex);
-				defer (mutex_unlock(md->mutex));
-
-				if (!ac.objc_is_class_method) {
-					bool ok = true;
-					for (TypeNameObjCMetadataEntry const &entry : md->value_entries) {
-						if (entry.name == ac.objc_name) {
-							error(e->token, "Previous declaration of @(objc_name=\"%.*s\")", LIT(ac.objc_name));
-							ok = false;
-							break;
-						}
+		if (tn->scope != e->scope) {
+			error(e->token, "@(objc_name) attribute may only be applied to procedures and types within the same scope");
+		} else {
+
+			// Enable implementation by default if the class is an implementer too and
+			// @objc_implement was not set to false explicitly in this proc.
+			bool implement = tn->TypeName.objc_is_implementation;
+			if (ac.objc_is_disabled_implement) {
+				implement = false;
+			}
+
+			if (implement) {
+				GB_ASSERT(e->kind == Entity_Procedure);
+
+				auto &proc = e->type->Proc;
+				Type *first_param = proc.param_count > 0 ? proc.params->Tuple.variables[0]->type : t_untyped_nil;
+
+				if (!tn->TypeName.objc_is_implementation) {
+					error(e->token, "@(objc_is_implement) attribute may only be applied to procedures whose class also have @(objc_is_implement) applied");
+				} else if (!ac.objc_is_class_method && !(first_param->kind == Type_Pointer && internal_check_is_assignable_to(t, first_param->Pointer.elem))) {
+					error(e->token, "Objective-C instance methods implementations require the first parameter to be a pointer to the class type set by @(objc_type)");
+				} else if (proc.calling_convention == ProcCC_Odin && !tn->TypeName.objc_context_provider) {
+					error(e->token, "Objective-C methods with Odin calling convention can only be used with classes that have @(objc_context_provider) set");
+				} else if (ac.objc_is_class_method && proc.calling_convention != ProcCC_CDecl) {
+					error(e->token, "Objective-C class methods (objc_is_class_method=true) that have @objc_is_implementation can only use \"c\" calling convention");
+				} else if (proc.result_count > 1) {
+					error(e->token, "Objective-C method implementations may return at most 1 value");
+				} else {
+					// Always export unconditionally
+					// NOTE(harold): This means check_objc_methods() MUST be called before
+					//				 e->Procedure.is_export is set in check_proc_decl()!
+					if (ac.is_export) {
+						error(e->token, "Explicit export not allowed when @(objc_implement) is set. It set exported implicitly");
 					}
-					if (ok) {
-						array_add(&md->value_entries, TypeNameObjCMetadataEntry{ac.objc_name, e});
+					if (ac.link_name != "") {
+						error(e->token, "Explicit linkage not allowed when @(objc_implement) is set. It set to \"strong\" implicitly");
 					}
-				} else {
-					bool ok = true;
-					for (TypeNameObjCMetadataEntry const &entry : md->type_entries) {
-						if (entry.name == ac.objc_name) {
-							error(e->token, "Previous declaration of @(objc_name=\"%.*s\")", LIT(ac.objc_name));
-							ok = false;
-							break;
-						}
+
+					ac.is_export = true;
+					ac.linkage   = STR_LIT("strong");
+
+					auto method = ObjcMethodData{ ac, e };
+					method.ac.objc_selector = ac.objc_selector != "" ? ac.objc_selector : ac.objc_name;
+
+					CheckerInfo *info = ctx->info;
+					mutex_lock(&info->objc_method_mutex);
+					defer (mutex_unlock(&info->objc_method_mutex));
+
+					Array<ObjcMethodData>* method_list = map_get(&info->objc_method_implementations, t);
+					if (method_list) {
+						array_add(method_list, method);
+					} else {
+						auto list = array_make<ObjcMethodData>(permanent_allocator(), 1, 8);
+						list[0] = method;
+
+						map_set(&info->objc_method_implementations, t, list);
 					}
-					if (ok) {
-						array_add(&md->type_entries, TypeNameObjCMetadataEntry{ac.objc_name, e});
+				}
+			} else if (ac.objc_selector != "") {
+				error(e->token, "@(objc_selector) may only be applied to procedures that are Objective-C implementations.");
+			}
+
+			mutex_lock(&global_type_name_objc_metadata_mutex);
+			defer (mutex_unlock(&global_type_name_objc_metadata_mutex));
+
+			if (!tn->TypeName.objc_metadata) {
+				tn->TypeName.objc_metadata = create_type_name_obj_c_metadata();
+			}
+			auto *md = tn->TypeName.objc_metadata;
+			mutex_lock(md->mutex);
+			defer (mutex_unlock(md->mutex));
+
+			if (!ac.objc_is_class_method) {
+				bool ok = true;
+				for (TypeNameObjCMetadataEntry const &entry : md->value_entries) {
+					if (entry.name == ac.objc_name) {
+						error(e->token, "Previous declaration of @(objc_name=\"%.*s\")", LIT(ac.objc_name));
+						ok = false;
+						break;
+					}
+				}
+				if (ok) {
+					array_add(&md->value_entries, TypeNameObjCMetadataEntry{ac.objc_name, e});
+				}
+			} else {
+				bool ok = true;
+				for (TypeNameObjCMetadataEntry const &entry : md->type_entries) {
+					if (entry.name == ac.objc_name) {
+						error(e->token, "Previous declaration of @(objc_name=\"%.*s\")", LIT(ac.objc_name));
+						ok = false;
+						break;
 					}
 				}
+				if (ok) {
+					array_add(&md->type_entries, TypeNameObjCMetadataEntry{ac.objc_name, e});
+				}
 			}
 		}
 	}
@@ -1145,6 +1282,9 @@ gb_internal void check_proc_decl(CheckerContext *ctx, Entity *e, DeclInfo *d) {
 		break;
 	}
 
+	// NOTE(harold): For Objective-C method implementations, this must happen after
+	//				 check_objc_methods() is called as it re-sets ac.is_export to true unconditionally.
+	//				 The same is true for the linkage, set below.
 	e->Procedure.entry_point_only = ac.entry_point_only;
 	e->Procedure.is_export = ac.is_export;
 
@@ -1245,6 +1385,7 @@ gb_internal void check_proc_decl(CheckerContext *ctx, Entity *e, DeclInfo *d) {
 		}
 	}
 
+	// NOTE(harold): See export/linkage note above(where is_export is assigned) regarding Objective-C method implementations
 	bool is_foreign = e->Procedure.is_foreign;
 	bool is_export  = e->Procedure.is_export;
 	

+ 42 - 16
src/check_expr.cpp

@@ -643,7 +643,7 @@ gb_internal bool find_or_generate_polymorphic_procedure(CheckerContext *old_c, E
 
 gb_internal bool check_polymorphic_procedure_assignment(CheckerContext *c, Operand *operand, Type *type, Ast *poly_def_node, PolyProcData *poly_proc_data) {
 	if (operand->expr == nullptr) return false;
-	Entity *base_entity = entity_of_node(operand->expr);
+	Entity *base_entity = entity_from_expr(operand->expr);
 	if (base_entity == nullptr) return false;
 	return find_or_generate_polymorphic_procedure(c, base_entity, type, nullptr, poly_def_node, poly_proc_data);
 }
@@ -995,14 +995,34 @@ gb_internal i64 assign_score_function(i64 distance, bool is_variadic=false) {
 
 
 gb_internal bool check_is_assignable_to_with_score(CheckerContext *c, Operand *operand, Type *type, i64 *score_, bool is_variadic=false, bool allow_array_programming=true) {
-	i64 score = 0;
-	i64 distance = check_distance_between_types(c, operand, type, allow_array_programming);
-	bool ok = distance >= 0;
-	if (ok) {
-		score = assign_score_function(distance, is_variadic);
+	if (c == nullptr) {
+		GB_ASSERT(operand->mode == Addressing_Value);
+		GB_ASSERT(is_type_typed(operand->type));
+	}
+	if (operand->mode == Addressing_Invalid || type == t_invalid) {
+		if (score_) *score_ = 0;
+		return false;
+	}
+
+	// Handle polymorphic procedure used as default parameter
+	if (operand->mode == Addressing_Value && is_type_proc(type) && is_type_proc(operand->type)) {
+		Entity *e = entity_from_expr(operand->expr);
+		if (e != nullptr && e->kind == Entity_Procedure && is_type_polymorphic(e->type) && !is_type_polymorphic(type)) {
+			// Special case: Allow a polymorphic procedure to be used as default value for concrete proc type
+			// during the initial check. It will be properly instantiated when actually used.
+			if (score_) *score_ = assign_score_function(1);
+			return true;
+		}
+	}
+
+	i64 score = check_distance_between_types(c, operand, type, allow_array_programming);
+	if (score >= 0) {
+		if (score_) *score_ = assign_score_function(score, is_variadic);
+		return true;
 	}
-	if (score_) *score_ = score;
-	return ok;
+
+	if (score_) *score_ = 0;
+	return false;
 }
 
 
@@ -5441,8 +5461,18 @@ gb_internal Entity *check_selector(CheckerContext *c, Operand *operand, Ast *nod
 		}
 	}
 
+	if (operand->type && is_type_simd_vector(type_deref(operand->type))) {
+		String field_name = selector->Ident.token.string;
+		if (field_name.len == 1) {
+			error(op_expr, "Extracting an element from a #simd array using .%.*s syntax is disallowed, prefer `simd.extract`", LIT(field_name));
+		} else {
+			error(op_expr, "Extracting elements from a #simd array using .%.*s syntax is disallowed, prefer `swizzle`", LIT(field_name));
+		}
+		return nullptr;
+	}
+
 	if (entity == nullptr && selector->kind == Ast_Ident && operand->type != nullptr &&
-	    (is_type_array(type_deref(operand->type)) || is_type_simd_vector(type_deref(operand->type)))) {
+	    (is_type_array(type_deref(operand->type)))) {
 		String field_name = selector->Ident.token.string;
 		if (1 < field_name.len && field_name.len <= 4) {
 			u8 swizzles_xyzw[4] = {'x', 'y', 'z', 'w'};
@@ -5497,7 +5527,7 @@ gb_internal Entity *check_selector(CheckerContext *c, Operand *operand, Ast *nod
 
 			Type *original_type = operand->type;
 			Type *array_type = base_type(type_deref(original_type));
-			GB_ASSERT(array_type->kind == Type_Array || array_type->kind == Type_SimdVector);
+			GB_ASSERT(array_type->kind == Type_Array);
 
 			i64 array_count = get_array_type_count(array_type);
 
@@ -5538,10 +5568,6 @@ gb_internal Entity *check_selector(CheckerContext *c, Operand *operand, Ast *nod
 				break;
 			}
 
-			if (array_type->kind == Type_SimdVector) {
-				operand->mode = Addressing_Value;
-			}
-
 			Entity *swizzle_entity = alloc_entity_variable(nullptr, make_token_ident(field_name), operand->type, EntityState_Resolved);
 			add_type_and_value(c, operand->expr, operand->mode, operand->type, operand->value);
 			return swizzle_entity;
@@ -9413,7 +9439,7 @@ gb_internal bool is_expr_inferred_fixed_array(Ast *type_expr) {
 }
 
 gb_internal bool check_for_dynamic_literals(CheckerContext *c, Ast *node, AstCompoundLit *cl) {
-	if (cl->elems.count > 0 && (check_feature_flags(c, node) & OptInFeatureFlag_DynamicLiterals) == 0) {
+	if (cl->elems.count > 0 && (check_feature_flags(c, node) & OptInFeatureFlag_DynamicLiterals) == 0 && !build_context.dynamic_literals) {
 		ERROR_BLOCK();
 		error(node, "Compound literals of dynamic types are disabled by default");
 		error_line("\tSuggestion: If you want to enable them for this specific file, add '#+feature dynamic-literals' at the top of the file\n");
@@ -10996,7 +11022,7 @@ gb_internal ExprKind check_expr_base_internal(CheckerContext *c, Operand *o, Ast
 		return kind;
 	case_end;
 
-	case_ast_node(i, Implicit, node)
+	case_ast_node(i, Implicit, node);
 		switch (i->kind) {
 		case Token_context:
 			{

+ 2 - 0
src/check_stmt.cpp

@@ -2108,10 +2108,12 @@ gb_internal void check_value_decl_stmt(CheckerContext *ctx, Ast *node, u32 mod_f
 		if (init_type == nullptr) {
 			init_type = t_invalid;
 		} else if (is_type_polymorphic(base_type(init_type))) {
+			/* DISABLED: This error seems too aggressive for instantiated generic types.
 			gbString str = type_to_string(init_type);
 			error(vd->type, "Invalid use of a polymorphic type '%s' in variable declaration", str);
 			gb_string_free(str);
 			init_type = t_invalid;
+			*/
 		}
 		if (init_type == t_invalid && entity_count == 1 && (mod_flags & (Stmt_BreakAllowed|Stmt_FallthroughAllowed))) {
 			Entity *e = entities[0];

+ 10 - 1
src/check_type.cpp

@@ -1910,9 +1910,18 @@ gb_internal Type *check_get_params(CheckerContext *ctx, Scope *scope, Ast *_para
 			case ParameterValue_Location:
 			case ParameterValue_Expression:
 			case ParameterValue_Value:
+				// Special case for polymorphic procedures as default values
+				if (param_value.ast_value != nullptr) {
+					Entity *e = entity_from_expr(param_value.ast_value);
+					if (e != nullptr && e->kind == Entity_Procedure && is_type_polymorphic(e->type)) {
+						// Allow polymorphic procedures as default parameter values
+						// The type will be correctly determined at call site
+						break;
+					}
+				}
 				gbString str = type_to_string(type);
 				error(params[i], "A default value for a parameter must not be a polymorphic constant type, got %s", str);
-				gb_string_free(str);
+					gb_string_free(str);
 				break;
 			}
 		}

+ 135 - 2
src/checker.cpp

@@ -728,12 +728,17 @@ gb_internal void check_scope_usage_internal(Checker *c, Scope *scope, u64 vet_fl
 		bool is_unused = false;
 		if (vet_unused && check_vet_unused(c, e, &ve_unused)) {
 			is_unused = true;
-		} else if (vet_unused_procedures &&
-		           e->kind == Entity_Procedure) {
+		} else if (vet_unused_procedures && e->kind == Entity_Procedure) {
 			if (e->flags&EntityFlag_Used) {
 				is_unused = false;
 			} else if (e->flags & EntityFlag_Require) {
 				is_unused = false;
+			} else if (e->flags & EntityFlag_Init) {
+				is_unused = false;
+			} else if (e->flags & EntityFlag_Fini) {
+				is_unused = false;
+			} else if (e->Procedure.is_export) {
+				is_unused = false;
 			} else if (e->pkg && e->pkg->kind == Package_Init && e->token.string == "main") {
 				is_unused = false;
 			} else {
@@ -1351,10 +1356,12 @@ gb_internal void init_universal(void) {
 		t_objc_object   = add_global_type_name(intrinsics_pkg->scope, str_lit("objc_object"),   alloc_type_struct_complete());
 		t_objc_selector = add_global_type_name(intrinsics_pkg->scope, str_lit("objc_selector"), alloc_type_struct_complete());
 		t_objc_class    = add_global_type_name(intrinsics_pkg->scope, str_lit("objc_class"),    alloc_type_struct_complete());
+		t_objc_ivar     = add_global_type_name(intrinsics_pkg->scope, str_lit("objc_ivar"),     alloc_type_struct_complete());
 
 		t_objc_id       = alloc_type_pointer(t_objc_object);
 		t_objc_SEL      = alloc_type_pointer(t_objc_selector);
 		t_objc_Class    = alloc_type_pointer(t_objc_class);
+		t_objc_Ivar     = alloc_type_pointer(t_objc_ivar);
 	}
 }
 
@@ -1387,6 +1394,10 @@ gb_internal void init_checker_info(CheckerInfo *i) {
 	array_init(&i->defineables, a);
 
 	map_init(&i->objc_msgSend_types);
+	mpsc_init(&i->objc_class_implementations, a);
+	string_set_init(&i->obcj_class_name_set, 0);
+	map_init(&i->objc_method_implementations);
+
 	string_map_init(&i->load_file_cache);
 	array_init(&i->all_procedures, heap_allocator());
 
@@ -1497,6 +1508,8 @@ gb_internal void init_checker(Checker *c) {
 
 	TIME_SECTION("init proc queues");
 	mpsc_init(&c->procs_with_deferred_to_check, a); //, 1<<10);
+	mpsc_init(&c->procs_with_objc_context_provider_to_check, a);
+
 
 	// NOTE(bill): 1 Mi elements should be enough on average
 	array_init(&c->procs_to_check, heap_allocator(), 0, 1<<20);
@@ -3662,6 +3675,33 @@ gb_internal DECL_ATTRIBUTE_PROC(proc_decl_attribute) {
 			}
 		}
 		return true;
+	} else if (name == "objc_implement") {
+		ExactValue ev = check_decl_attribute_value(c, value);
+		if (ev.kind == ExactValue_Bool) {
+			ac->objc_is_implementation = ev.value_bool;
+
+			if (!ac->objc_is_implementation) {
+				ac->objc_is_disabled_implement = true;
+			}
+		} else if (ev.kind == ExactValue_Invalid) {
+			ac->objc_is_implementation = true;
+		} else {
+			error(elem, "Expected a boolean value, or no value, for '%.*s'", LIT(name));
+		}
+
+		return true;
+	} else if (name == "objc_selector") {
+		ExactValue ev = check_decl_attribute_value(c, value);
+		if (ev.kind == ExactValue_String) {
+			if (string_is_valid_identifier(ev.value_string)) {
+				ac->objc_selector = ev.value_string;
+			} else {
+				error(elem, "Invalid identifier for '%.*s', got '%.*s'", LIT(name), LIT(ev.value_string));
+			}
+		} else {
+			error(elem, "Expected a string value for '%.*s'", LIT(name));
+		}
+		return true;
 	} else if (name == "require_target_feature") {
 		ExactValue ev = check_decl_attribute_value(c, value);
 		if (ev.kind == ExactValue_String) {
@@ -3907,6 +3947,51 @@ gb_internal DECL_ATTRIBUTE_PROC(type_decl_attribute) {
 			ac->objc_class = ev.value_string;
 		}
 		return true;
+	} else if (name == "objc_implement") {
+		ExactValue ev = check_decl_attribute_value(c, value);
+		if (ev.kind == ExactValue_Bool) {
+			ac->objc_is_implementation = ev.value_bool;
+		} else if (ev.kind == ExactValue_Invalid) {
+			ac->objc_is_implementation = true;
+		} else {
+			error(elem, "Expected a boolean value, or no value, for '%.*s'", LIT(name));
+		}
+		return true;
+	} else if (name == "objc_superclass") {
+		Type *objc_superclass = check_type(c, value);
+
+		if (objc_superclass != nullptr) {
+			ac->objc_superclass = objc_superclass;
+		} else {
+			error(value, "'%.*s' expected a named type", LIT(name));
+		}
+		return true;
+	} else if (name == "objc_ivar") {
+		Type *objc_ivar = check_type(c, value);
+
+		if (objc_ivar != nullptr && objc_ivar->kind == Type_Named) {
+			ac->objc_ivar = objc_ivar;
+		} else {
+			error(value, "'%.*s' expected a named type", LIT(name));
+		}
+		return true;
+	} else if (name == "objc_context_provider") {
+		Operand o = {};
+		check_expr(c, &o, value);
+		Entity *e = entity_of_node(o.expr);
+
+		if (e != nullptr) {
+			if (ac->objc_context_provider != nullptr) {
+				error(elem, "Previous usage of a 'objc_context_provider' attribute");
+			}
+			if (e->kind != Entity_Procedure) {
+				error(elem, "'objc_context_provider' must refer to a procedure");
+			} else {
+				ac->objc_context_provider = e;
+			}
+
+			return true;
+		}
 	}
 	return false;
 }
@@ -6240,6 +6325,12 @@ gb_internal void check_deferred_procedures(Checker *c) {
 			continue;
 		}
 
+		if (dst->flags & EntityFlag_Disabled) {
+			// Prevent procedures that have been disabled from acting as deferrals.
+			src->Procedure.deferred_procedure = {};
+			continue;
+		}
+
 		GB_ASSERT(is_type_proc(src->type));
 		GB_ASSERT(is_type_proc(dst->type));
 		Type *src_params = base_type(src->type)->Proc.params;
@@ -6395,6 +6486,44 @@ gb_internal void check_deferred_procedures(Checker *c) {
 
 }
 
+gb_internal void check_objc_context_provider_procedures(Checker *c) {
+	for (Entity *e = nullptr; mpsc_dequeue(&c->procs_with_objc_context_provider_to_check, &e); /**/) {
+		GB_ASSERT(e->kind == Entity_TypeName);
+
+		Entity *proc_entity = e->TypeName.objc_context_provider;
+		GB_ASSERT(proc_entity->kind == Entity_Procedure);
+
+		auto &proc = proc_entity->type->Proc;
+
+		Type *return_type = proc.result_count != 1 ? t_untyped_nil : base_named_type(proc.results->Tuple.variables[0]->type);
+		if (return_type != t_context) {
+			error(proc_entity->token, "The @(objc_context_provider) procedure must only return a context.");
+		}
+
+		const char *self_param_err = "The @(objc_context_provider) procedure must take as a parameter a single pointer to the @(objc_type) value.";
+		if (proc.param_count != 1) {
+			error(proc_entity->token, self_param_err);
+		}
+
+		Type *self_param = base_type(proc.params->Tuple.variables[0]->type);
+		if (self_param->kind != Type_Pointer) {
+			error(proc_entity->token, self_param_err);
+		}
+
+		Type *self_type = base_named_type(self_param->Pointer.elem);
+		if (!internal_check_is_assignable_to(self_type, e->type) &&
+			!(e->TypeName.objc_ivar && internal_check_is_assignable_to(self_type, e->TypeName.objc_ivar))) {
+			error(proc_entity->token, self_param_err);
+		}
+		if (proc.calling_convention != ProcCC_CDecl && proc.calling_convention != ProcCC_Contextless) {
+			error(e->token, self_param_err);
+		}
+		if (proc.is_polymorphic) {
+			error(e->token, self_param_err);
+		}
+	}
+}
+
 gb_internal void check_unique_package_names(Checker *c) {
 	ERROR_BLOCK();
 
@@ -6555,6 +6684,7 @@ gb_internal void check_update_dependency_tree_for_procedures(Checker *c) {
 	}
 }
 
+
 gb_internal void check_parsed_files(Checker *c) {
 	TIME_SECTION("map full filepaths to scope");
 	add_type_info_type(&c->builtin_ctx, t_invalid);
@@ -6664,6 +6794,9 @@ gb_internal void check_parsed_files(Checker *c) {
 	TIME_SECTION("check deferred procedures");
 	check_deferred_procedures(c);
 
+	TIME_SECTION("check objc context provider procedures");
+	check_objc_context_provider_procedures(c);
+
 	TIME_SECTION("calculate global init order");
 	calculate_global_init_order(c);
 

+ 22 - 2
src/checker.hpp

@@ -149,8 +149,14 @@ struct AttributeContext {
 
 	String  objc_class;
 	String  objc_name;
-	bool    objc_is_class_method;
+	String  objc_selector;
 	Type *  objc_type;
+	Type *  objc_superclass;
+	Type *  objc_ivar;
+	Entity *objc_context_provider;
+	bool    objc_is_class_method;
+	bool    objc_is_implementation;     // This struct or proc provides a class/method implementation, not a binding to an existing type.
+	bool    objc_is_disabled_implement; // This means the method explicitly set @objc_implement to false so it won't be inferred from the class' attribute.
 
 	String require_target_feature; // required by the target micro-architecture
 	String enable_target_feature;  // will be enabled for the procedure only
@@ -366,6 +372,11 @@ struct ObjcMsgData {
 	Type *proc_type;
 };
 
+struct ObjcMethodData {
+	AttributeContext ac;
+	Entity *proc_entity;
+};
+
 enum LoadFileTier {
 	LoadFileTier_Invalid,
 	LoadFileTier_Exists,
@@ -477,9 +488,17 @@ struct CheckerInfo {
 
 	MPSCQueue<Ast *> intrinsics_entry_point_usage;
 
-	BlockingMutex objc_types_mutex;
+	BlockingMutex objc_objc_msgSend_mutex;
 	PtrMap<Ast *, ObjcMsgData> objc_msgSend_types;
 
+	BlockingMutex objc_class_name_mutex;
+	StringSet obcj_class_name_set;
+	MPSCQueue<Entity *> objc_class_implementations;
+
+	BlockingMutex objc_method_mutex;
+	PtrMap<Type *, Array<ObjcMethodData>> objc_method_implementations;
+
+
 	BlockingMutex load_file_mutex;
 	StringMap<LoadFileCache *> load_file_cache;
 
@@ -556,6 +575,7 @@ struct Checker {
 	CheckerContext builtin_ctx;
 
 	MPSCQueue<Entity *> procs_with_deferred_to_check;
+	MPSCQueue<Entity *> procs_with_objc_context_provider_to_check;
 	Array<ProcInfo *> procs_to_check;
 
 	BlockingMutex nested_proc_lits_mutex;

+ 10 - 0
src/checker_builtin_procs.hpp

@@ -26,6 +26,7 @@ enum BuiltinProcId {
 	BuiltinProc_conj,
 
 	BuiltinProc_expand_values,
+	BuiltinProc_compress_values,
 
 	BuiltinProc_min,
 	BuiltinProc_max,
@@ -234,6 +235,9 @@ BuiltinProc__type_begin,
 	BuiltinProc_type_convert_variants_to_pointers,
 	BuiltinProc_type_merge,
 
+	BuiltinProc_type_integer_to_unsigned,
+	BuiltinProc_type_integer_to_signed,
+
 BuiltinProc__type_simple_boolean_begin,
 	BuiltinProc_type_is_boolean,
 	BuiltinProc_type_is_integer,
@@ -338,6 +342,7 @@ BuiltinProc__type_end,
 	BuiltinProc_objc_find_class,
 	BuiltinProc_objc_register_selector,
 	BuiltinProc_objc_register_class,
+	BuiltinProc_objc_ivar_get,
 
 	BuiltinProc_constant_utf16_cstring,
 
@@ -375,6 +380,7 @@ gb_global BuiltinProc builtin_procs[BuiltinProc_COUNT] = {
 	{STR_LIT("conj"),             1, false, Expr_Expr, BuiltinProcPkg_builtin},
 
 	{STR_LIT("expand_values"),    1, false, Expr_Expr, BuiltinProcPkg_builtin},
+	{STR_LIT("compress_values"),  1, true,  Expr_Expr, BuiltinProcPkg_builtin},
 
 	{STR_LIT("min"),              1, true,  Expr_Expr, BuiltinProcPkg_builtin},
 	{STR_LIT("max"),              1, true,  Expr_Expr, BuiltinProcPkg_builtin},
@@ -582,6 +588,9 @@ gb_global BuiltinProc builtin_procs[BuiltinProc_COUNT] = {
 	{STR_LIT("type_convert_variants_to_pointers"), 1, false, Expr_Expr, BuiltinProcPkg_intrinsics},
 	{STR_LIT("type_merge"),                2, false, Expr_Expr, BuiltinProcPkg_intrinsics},
 
+	{STR_LIT("type_integer_to_unsigned"),  1, false, Expr_Expr, BuiltinProcPkg_intrinsics},
+	{STR_LIT("type_integer_to_signed"),    1, false, Expr_Expr, BuiltinProcPkg_intrinsics},
+
 	{STR_LIT(""), 0, false, Expr_Stmt, BuiltinProcPkg_intrinsics},
 	{STR_LIT("type_is_boolean"),           1, false, Expr_Expr, BuiltinProcPkg_intrinsics},
 	{STR_LIT("type_is_integer"),           1, false, Expr_Expr, BuiltinProcPkg_intrinsics},
@@ -686,6 +695,7 @@ gb_global BuiltinProc builtin_procs[BuiltinProc_COUNT] = {
 	{STR_LIT("objc_find_class"),        1, false, Expr_Expr, BuiltinProcPkg_intrinsics},
 	{STR_LIT("objc_register_selector"), 1, false, Expr_Expr, BuiltinProcPkg_intrinsics, false, true},
 	{STR_LIT("objc_register_class"),    1, false, Expr_Expr, BuiltinProcPkg_intrinsics, false, true},
+	{STR_LIT("objc_ivar_get"),          1, false, Expr_Expr, BuiltinProcPkg_intrinsics, false, true},
 
 	{STR_LIT("constant_utf16_cstring"), 1, false, Expr_Expr, BuiltinProcPkg_intrinsics},
 

+ 4 - 0
src/entity.cpp

@@ -235,6 +235,10 @@ struct Entity {
 			Type * type_parameter_specialization;
 			String ir_mangled_name;
 			bool   is_type_alias;
+			bool   objc_is_implementation;
+			Type*  objc_superclass;
+			Type*  objc_ivar;
+			Entity*objc_context_provider;
 			String objc_class_name;
 			TypeNameObjCMetadata *objc_metadata;
 		} TypeName;

+ 14 - 1
src/linker.cpp

@@ -769,7 +769,17 @@ try_cross_linking:;
 			gbString platform_lib_str = gb_string_make(heap_allocator(), "");
 			defer (gb_string_free(platform_lib_str));
 			if (build_context.metrics.os == TargetOs_darwin) {
-				platform_lib_str = gb_string_appendc(platform_lib_str, "-Wl,-syslibroot /Library/Developer/CommandLineTools/SDKs/MacOSX.sdk -L/usr/local/lib ");
+				// Get the MacOSX SDK path.
+				gbString darwin_sdk_path = gb_string_make(temporary_allocator(), "");
+				if (!system_exec_command_line_app_output("xcrun --sdk macosx --show-sdk-path", &darwin_sdk_path)) {
+					darwin_sdk_path = gb_string_set(darwin_sdk_path, "/Library/Developer/CommandLineTools/SDKs/MacOSX.sdk");
+				} else {
+					// Trim the trailing newline.
+					darwin_sdk_path = gb_string_trim_space(darwin_sdk_path);
+				}
+				platform_lib_str = gb_string_append_fmt(platform_lib_str, "--sysroot %s ", darwin_sdk_path);
+
+				platform_lib_str = gb_string_appendc(platform_lib_str, "-L/usr/local/lib ");
 
 				// Homebrew's default library path, checking if it exists to avoid linking warnings.
 				if (gb_file_exists("/opt/homebrew/lib")) {
@@ -791,6 +801,9 @@ try_cross_linking:;
 					// This points the linker to where the entry point is
 					link_settings = gb_string_appendc(link_settings, "-e _main ");
 				}
+			} else if (build_context.metrics.os == TargetOs_freebsd) {
+				// FreeBSD pkg installs third-party shared libraries in /usr/local/lib.
+				platform_lib_str = gb_string_appendc(platform_lib_str, "-Wl,-L/usr/local/lib ");
 			} else if (build_context.metrics.os == TargetOs_openbsd) {
 				// OpenBSD ports install shared libraries in /usr/local/lib. Also, we must explicitly link libpthread.
 				platform_lib_str = gb_string_appendc(platform_lib_str, "-lpthread -Wl,-L/usr/local/lib ");

+ 14 - 28
src/llvm_abi.cpp

@@ -977,7 +977,7 @@ namespace lbAbiAmd64SysV {
 			return types[0];
 		}
 
-		return LLVMStructTypeInContext(c, types.data, cast(unsigned)types.count, sz == 0);
+		return LLVMStructTypeInContext(c, types.data, cast(unsigned)types.count, false);
 	}
 
 	gb_internal void classify_with(LLVMTypeRef t, Array<RegClass> *cls, i64 ix, i64 off) {
@@ -1231,38 +1231,24 @@ namespace lbAbiArm64 {
 			}
 		} else {
 			i64 size = lb_sizeof(return_type);
-			if (size <= 16) {
-				LLVMTypeRef cast_type = nullptr;
-
-				if (size == 0) {
-					cast_type = LLVMStructTypeInContext(c, nullptr, 0, false);
-				} else if (size <= 8) {
-					cast_type = LLVMIntTypeInContext(c, cast(unsigned)(size*8));
-				} else {
-					unsigned count = cast(unsigned)((size+7)/8);
-
-					LLVMTypeRef llvm_i64 = LLVMIntTypeInContext(c, 64);
-					LLVMTypeRef *types = gb_alloc_array(temporary_allocator(), LLVMTypeRef, count);
-
-					i64 size_copy = size;
-					for (unsigned i = 0; i < count; i++) {
-						if (size_copy >= 8) {
-							types[i] = llvm_i64;
-						} else {
-							types[i] = LLVMIntTypeInContext(c, 8*cast(unsigned)size_copy);
-						}
-						size_copy -= 8;
-					}
-					GB_ASSERT(size_copy <= 0);
-					cast_type = LLVMStructTypeInContext(c, types, count, true);
-				}
-				return lb_arg_type_direct(return_type, cast_type, nullptr, nullptr);
-			} else {
+			if (size > 16) {
 				LB_ABI_MODIFY_RETURN_IF_TUPLE_MACRO();
 
 				LLVMAttributeRef attr = lb_create_enum_attribute_with_type(c, "sret", return_type);
 				return lb_arg_type_indirect(return_type, attr);
 			}
+
+			GB_ASSERT(size <= 16);
+			LLVMTypeRef cast_type = nullptr;
+			if (size == 0) {
+				cast_type = LLVMStructTypeInContext(c, nullptr, 0, false);
+			} else if (size <= 8) {
+				cast_type = LLVMIntTypeInContext(c, cast(unsigned)(size*8));
+			} else {
+				LLVMTypeRef llvm_i64 = LLVMIntTypeInContext(c, 64);
+				cast_type = llvm_array_type(llvm_i64, 2);
+			}
+			return lb_arg_type_direct(return_type, cast_type, nullptr, nullptr);
 		}
 	}
     

+ 665 - 43
src/llvm_backend.cpp

@@ -1173,6 +1173,344 @@ gb_internal lbProcedure *lb_create_objc_names(lbModule *main_module) {
 	return p;
 }
 
+String lb_get_objc_type_encoding(Type *t, isize pointer_depth = 0) {
+	// NOTE(harold): See https://developer.apple.com/library/archive/documentation/Cocoa/Conceptual/ObjCRuntimeGuide/Articles/ocrtTypeEncodings.html#//apple_ref/doc/uid/TP40008048-CH100
+
+	// NOTE(harold): Darwin targets are always 64-bit. Should we drop this and assume "q" always?
+	#define INT_SIZE_ENCODING (build_context.metrics.int_size == 4 ? "i" : "q")
+	switch (t->kind) {
+	case Type_Basic: {
+		switch (t->Basic.kind) {
+		case Basic_Invalid:
+			return str_lit("?");
+
+		case Basic_llvm_bool:
+		case Basic_bool:
+		case Basic_b8:
+			return str_lit("B");
+
+		case Basic_b16:
+			return str_lit("C");
+		case Basic_b32:
+			return str_lit("I");
+		case Basic_b64:
+			return str_lit("q");
+		case Basic_i8:
+			return str_lit("c");
+		case Basic_u8:
+			return str_lit("C");
+		case Basic_i16:
+		case Basic_i16le:
+		case Basic_i16be:
+			return str_lit("s");
+		case Basic_u16:
+		case Basic_u16le:
+		case Basic_u16be:
+			return str_lit("S");
+		case Basic_i32:
+		case Basic_i32le:
+		case Basic_i32be:
+			return str_lit("i");
+		case Basic_u32le:
+		case Basic_u32:
+		case Basic_u32be:
+			return str_lit("I");
+		case Basic_i64:
+		case Basic_i64le:
+		case Basic_i64be:
+			return str_lit("q");
+		case Basic_u64:
+		case Basic_u64le:
+		case Basic_u64be:
+			return str_lit("Q");
+		case Basic_i128:
+		case Basic_i128le:
+		case Basic_i128be:
+			return str_lit("t");
+		case Basic_u128:
+		case Basic_u128le:
+		case Basic_u128be:
+			return str_lit("T");
+		case Basic_rune:
+			return str_lit("I");
+		case Basic_f16:
+		case Basic_f16le:
+		case Basic_f16be:
+			return str_lit("s");    // @harold: Closest we've got?
+		case Basic_f32:
+		case Basic_f32le:
+		case Basic_f32be:
+			return str_lit("f");
+		case Basic_f64:
+		case Basic_f64le:
+		case Basic_f64be:
+			return str_lit("d");
+
+		case Basic_complex32:		return str_lit("{complex32=ss}");	// No f16 encoding, so fallback to i16, as above in Basic_f16*
+		case Basic_complex64:		return str_lit("{complex64=ff}");
+		case Basic_complex128:		return str_lit("{complex128=dd}");
+		case Basic_quaternion64:    return str_lit("{quaternion64=ssss}");
+		case Basic_quaternion128:   return str_lit("{quaternion128=ffff}");
+		case Basic_quaternion256:   return str_lit("{quaternion256=dddd}");
+
+		case Basic_int:
+			return str_lit(INT_SIZE_ENCODING);
+		case Basic_uint:
+			return build_context.metrics.int_size == 4 ? str_lit("I") : str_lit("Q");
+		case Basic_uintptr:
+		case Basic_rawptr:
+			return str_lit("^v");
+
+		case Basic_string:
+			return build_context.metrics.int_size == 4 ? str_lit("{string=*i}") : str_lit("{string=*q}");
+
+		case Basic_cstring: return str_lit("*");
+		case Basic_any:     return str_lit("{any=^v^v}");  // rawptr + ^Type_Info
+
+		case Basic_typeid:
+			GB_ASSERT(t->Basic.size == 8);
+			return str_lit("q");
+
+		// Untyped types
+		case Basic_UntypedBool:
+		case Basic_UntypedInteger:
+		case Basic_UntypedFloat:
+		case Basic_UntypedComplex:
+		case Basic_UntypedQuaternion:
+		case Basic_UntypedString:
+		case Basic_UntypedRune:
+		case Basic_UntypedNil:
+		case Basic_UntypedUninit:
+			GB_PANIC("Untyped types cannot be @encoded()");
+			return str_lit("?");
+		}
+		break;
+	}
+
+	case Type_Named:
+	case Type_Struct:
+	case Type_Union: {
+		Type* base = t;
+		if (base->kind == Type_Named) {
+			base = base_type(base);
+			if(base->kind != Type_Struct && base->kind != Type_Union) {
+				return lb_get_objc_type_encoding(base, pointer_depth);
+			}
+		}
+
+		const bool is_union = base->kind == Type_Union;
+		if (!is_union) {
+			// Check for objc_SEL
+			if (internal_check_is_assignable_to(base, t_objc_SEL)) {
+				return str_lit(":");
+			}
+
+			// Check for objc_Class
+			if (internal_check_is_assignable_to(base, t_objc_SEL)) {
+				return str_lit("#");
+			}
+
+			// Treat struct as an Objective-C Class?
+			if (has_type_got_objc_class_attribute(base) && pointer_depth == 0) {
+				return str_lit("#");
+			}
+		}
+
+		if (is_type_objc_object(base)) {
+			return str_lit("@");
+		}
+
+
+		gbString s = gb_string_make_reserve(temporary_allocator(), 16);
+		s = gb_string_append_length(s, is_union ? "(" :"{", 1);
+		if (t->kind == Type_Named) {
+			s = gb_string_append_length(s, t->Named.name.text, t->Named.name.len);
+		}
+
+		// Write fields
+		if (pointer_depth < 2) {
+			s = gb_string_append_length(s, "=", 1);
+
+			if (!is_union) {
+				for( auto& f : base->Struct.fields ) {
+					String field_type = lb_get_objc_type_encoding(f->type, pointer_depth);
+					s = gb_string_append_length(s, field_type.text, field_type.len);
+				}
+			} else {
+				for( auto& v : base->Union.variants ) {
+					String variant_type = lb_get_objc_type_encoding(v, pointer_depth);
+					s = gb_string_append_length(s, variant_type.text, variant_type.len);
+				}
+			}
+		}
+
+		s = gb_string_append_length(s, is_union ? ")" :"}", 1);
+
+		return make_string_c(s);
+	}
+
+	case Type_Generic:
+		GB_PANIC("Generic types cannot be @encoded()");
+		return str_lit("?");
+
+	case Type_Pointer: {
+		String pointee = lb_get_objc_type_encoding(t->Pointer.elem, pointer_depth +1);
+		// Special case for Objective-C Objects
+		if (pointer_depth == 0 && pointee == "@") {
+			return pointee;
+		}
+
+		return concatenate_strings(temporary_allocator(), str_lit("^"), pointee);
+	}
+
+	case Type_MultiPointer:
+		return concatenate_strings(temporary_allocator(), str_lit("^"), lb_get_objc_type_encoding(t->Pointer.elem, pointer_depth +1));
+
+	case Type_Array: {
+		String type_str = lb_get_objc_type_encoding(t->Array.elem, pointer_depth);
+
+		gbString s = gb_string_make_reserve(temporary_allocator(), type_str.len + 8);
+		s = gb_string_append_fmt(s, "[%lld%.*s]", t->Array.count, LIT(type_str));
+		return make_string_c(s);
+	}
+
+	case Type_EnumeratedArray: {
+		String type_str = lb_get_objc_type_encoding(t->EnumeratedArray.elem, pointer_depth);
+
+		gbString s = gb_string_make_reserve(temporary_allocator(), type_str.len + 8);
+		s = gb_string_append_fmt(s, "[%lld%.*s]", t->EnumeratedArray.count, LIT(type_str));
+		return make_string_c(s);
+	}
+
+	case Type_Slice: {
+		String type_str = lb_get_objc_type_encoding(t->Slice.elem, pointer_depth);
+		gbString s = gb_string_make_reserve(temporary_allocator(), type_str.len + 8);
+		s = gb_string_append_fmt(s, "{slice=^%.*s%s}", LIT(type_str), INT_SIZE_ENCODING);
+		return make_string_c(s);
+	}
+
+	case Type_DynamicArray: {
+		String type_str = lb_get_objc_type_encoding(t->DynamicArray.elem, pointer_depth);
+		gbString s = gb_string_make_reserve(temporary_allocator(), type_str.len + 8);
+		s = gb_string_append_fmt(s, "{dynamic=^%.*s%s%sAllocator={?^v}}", LIT(type_str), INT_SIZE_ENCODING, INT_SIZE_ENCODING);
+		return make_string_c(s);
+	}
+
+	case Type_Map:
+		return str_lit("{^v^v{Allocator=?^v}}");
+	case Type_Enum:
+		return lb_get_objc_type_encoding(t->Enum.base_type, pointer_depth);
+	case Type_Tuple:
+		// NOTE(harold): Is this type allowed here?
+		return str_lit("?");
+	case Type_Proc:
+		return str_lit("?");
+	case Type_BitSet:
+		return lb_get_objc_type_encoding(t->BitSet.underlying, pointer_depth);
+
+	case Type_SimdVector: {
+		String type_str = lb_get_objc_type_encoding(t->SimdVector.elem, pointer_depth);
+		gbString s = gb_string_make_reserve(temporary_allocator(), type_str.len + 5);
+		gb_string_append_fmt(s, "[%lld%.*s]", t->SimdVector.count, LIT(type_str));
+		return make_string_c(s);
+	}
+
+	case Type_Matrix: {
+		String type_str = lb_get_objc_type_encoding(t->Matrix.elem, pointer_depth);
+		gbString s = gb_string_make_reserve(temporary_allocator(), type_str.len + 5);
+		i64 element_count = t->Matrix.column_count * t->Matrix.row_count;
+		gb_string_append_fmt(s, "[%lld%.*s]", element_count, LIT(type_str));
+		return make_string_c(s);
+	}
+
+	case Type_BitField:
+		return lb_get_objc_type_encoding(t->BitField.backing_type, pointer_depth);
+	case Type_SoaPointer: {
+		gbString s = gb_string_make_reserve(temporary_allocator(), 8);
+		s = gb_string_append_fmt(s, "{=^v%s}", INT_SIZE_ENCODING);
+		return make_string_c(s);
+	}
+
+	} // End switch t->kind
+	#undef INT_SIZE_ENCODING
+
+	GB_PANIC("Unreachable");
+	return str_lit("");
+}
+
+struct lbObjCGlobalClass {
+	lbObjCGlobal g;
+	lbValue      class_value;    // Local registered class value
+};
+
+gb_internal void lb_register_objc_thing(
+	StringSet &handled,
+	lbModule *m,
+	Array<lbValue> &args,
+	Array<lbObjCGlobalClass> &class_impls,
+	StringMap<lbObjCGlobalClass> &class_map,
+	lbProcedure *p,
+	lbObjCGlobal const &g,
+	char const *call
+) {
+	if (string_set_update(&handled, g.name)) {
+		return;
+	}
+
+	lbAddr addr = {};
+	lbValue *found = string_map_get(&m->members, g.global_name);
+	if (found) {
+		addr = lb_addr(*found);
+	} else {
+		lbValue v = {};
+		LLVMTypeRef t = lb_type(m, g.type);
+		v.value = LLVMAddGlobal(m->mod, t, g.global_name);
+		v.type = alloc_type_pointer(g.type);
+		addr = lb_addr(v);
+		LLVMSetInitializer(v.value, LLVMConstNull(t));
+	}
+
+	lbValue class_ptr  = {};
+	lbValue class_name = lb_const_value(m, t_cstring, exact_value_string(g.name));
+
+	// If this class requires an implementation, save it for registration below.
+	if (g.class_impl_type != nullptr) {
+
+		// Make sure the superclass has been initialized before us
+		lbValue superclass_value = lb_const_nil(m, t_objc_Class);
+
+		auto &tn = g.class_impl_type->Named.type_name->TypeName;
+		Type *superclass = tn.objc_superclass;
+		if (superclass != nullptr) {
+			auto& superclass_global = string_map_must_get(&class_map, superclass->Named.type_name->TypeName.objc_class_name);
+			lb_register_objc_thing(handled, m, args, class_impls, class_map, p, superclass_global.g, call);
+			GB_ASSERT(superclass_global.class_value.value);
+
+			superclass_value = superclass_global.class_value;
+		}
+
+		args.count = 3;
+		args[0] = superclass_value;
+		args[1] = class_name;
+		args[2] = lb_const_int(m, t_uint, 0);
+		class_ptr = lb_emit_runtime_call(p, "objc_allocateClassPair", args);
+
+		array_add(&class_impls, lbObjCGlobalClass{g, class_ptr});
+	}
+	else {
+		args.count = 1;
+		args[0] = class_name;
+		class_ptr = lb_emit_runtime_call(p, call, args);
+	}
+
+	lb_addr_store(p, addr, class_ptr);
+
+	lbObjCGlobalClass* class_global = string_map_get(&class_map, g.name);
+	if (class_global != nullptr) {
+		class_global->class_value = class_ptr;
+	}
+}
+
 gb_internal void lb_finalize_objc_names(lbGenerator *gen, lbProcedure *p) {
 	if (p == nullptr) {
 		return;
@@ -1186,38 +1524,329 @@ gb_internal void lb_finalize_objc_names(lbGenerator *gen, lbProcedure *p) {
 	string_set_init(&handled);
 	defer (string_set_destroy(&handled));
 
-	auto args = array_make<lbValue>(temporary_allocator(), 1);
+	auto args        = array_make<lbValue>(temporary_allocator(), 3, 8);
+	auto class_impls = array_make<lbObjCGlobalClass>(temporary_allocator(), 0, 16);
 
-	LLVMSetLinkage(p->value, LLVMInternalLinkage);
-	lb_begin_procedure_body(p);
+	// Register all class implementations unconditionally, even if not statically referenced
+	for (Entity *e = {}; mpsc_dequeue(&gen->info->objc_class_implementations, &e); /**/) {
+		GB_ASSERT(e->kind == Entity_TypeName && e->TypeName.objc_is_implementation);
+		lb_handle_objc_find_or_register_class(p, e->TypeName.objc_class_name, e->type);
+	}
 
-	auto register_thing = [&handled, &m, &args](lbProcedure *p, lbObjCGlobal const &g, char const *call) {
-		if (!string_set_update(&handled, g.name)) {
-			lbAddr addr = {};
-			lbValue *found = string_map_get(&m->members, g.global_name);
-			if (found) {
-				addr = lb_addr(*found);
-			} else {
-				lbValue v = {};
-				LLVMTypeRef t = lb_type(m, g.type);
-				v.value = LLVMAddGlobal(m->mod, t, g.global_name);
-				v.type = alloc_type_pointer(g.type);
-				addr = lb_addr(v);
-				LLVMSetInitializer(v.value, LLVMConstNull(t));
+	// Ensure classes that have been implicitly referenced through
+	// the objc_superclass attribute have a global variable available for them.
+	TypeSet class_set{};
+	type_set_init(&class_set, gen->objc_classes.count+16);
+	defer (type_set_destroy(&class_set));
+
+	auto referenced_classes = array_make<lbObjCGlobal>(temporary_allocator());
+	for (lbObjCGlobal g = {}; mpsc_dequeue(&gen->objc_classes, &g); /**/) {
+		array_add(&referenced_classes, g);
+
+		Type *cls = g.class_impl_type;
+		while (cls) {
+			if (type_set_update(&class_set, cls)) {
+				break;
 			}
+			GB_ASSERT(cls->kind == Type_Named);
 
-			args[0] = lb_const_value(m, t_cstring, exact_value_string(g.name));
-			lbValue ptr = lb_emit_runtime_call(p, call, args);
-			lb_addr_store(p, addr, ptr);
+			cls = cls->Named.type_name->TypeName.objc_superclass;
 		}
-	};
+	}
 
+	for (auto pair : class_set) {
+		auto& tn = pair.type->Named.type_name->TypeName;
+		Type *class_impl = !tn.objc_is_implementation ? nullptr : pair.type;
+		lb_handle_objc_find_or_register_class(p, tn.objc_class_name, class_impl);
+	}
 	for (lbObjCGlobal g = {}; mpsc_dequeue(&gen->objc_classes, &g); /**/) {
-		register_thing(p, g, "objc_lookUpClass");
+		array_add( &referenced_classes, g );
+	}
+
+	// Add all class globals to a map so that we can look them up dynamically
+	// in order to resolve out-of-order because classes that are being implemented
+	// require their superclasses to be registered before them.
+	StringMap<lbObjCGlobalClass> global_class_map{};
+	string_map_init(&global_class_map, (usize)gen->objc_classes.count);
+	defer (string_map_destroy(&global_class_map));
+
+	for (lbObjCGlobal g :referenced_classes) {
+		string_map_set(&global_class_map, g.name, lbObjCGlobalClass{g});
+	}
+
+	LLVMSetLinkage(p->value, LLVMInternalLinkage);
+	lb_begin_procedure_body(p);
+
+	// Register class globals, gathering classes that must be implemented
+	for (auto& kv : global_class_map) {
+		lb_register_objc_thing(handled, m, args, class_impls, global_class_map, p, kv.value.g, "objc_lookUpClass");
+	}
+
+	// Prefetch selectors for implemented methods so that they can also be registered.
+	for (const auto& cd : class_impls) {
+		auto& g = cd.g;
+		Type *class_type = g.class_impl_type;
+
+		Array<ObjcMethodData>* methods = map_get(&m->info->objc_method_implementations, class_type);
+		if (!methods) {
+			continue;
+		}
+
+		for (const ObjcMethodData& md : *methods) {
+			lb_handle_objc_find_or_register_selector(p, md.ac.objc_selector);
+		}
 	}
 
+	// Now we can register all referenced selectors
 	for (lbObjCGlobal g = {}; mpsc_dequeue(&gen->objc_selectors, &g); /**/) {
-		register_thing(p, g, "sel_registerName");
+		lb_register_objc_thing(handled, m, args, class_impls, global_class_map, p, g, "sel_registerName");
+	}
+
+
+	// Emit method wrapper implementations and registration
+	auto wrapper_args     = array_make<Type *>(temporary_allocator(), 2, 8);
+	auto get_context_args = array_make<lbValue>(temporary_allocator(), 1);
+
+
+	PtrMap<Type *, lbObjCGlobal> ivar_map{};
+	map_init(&ivar_map, gen->objc_ivars.count);
+
+	for (lbObjCGlobal g = {}; mpsc_dequeue(&gen->objc_ivars, &g); /**/) {
+		map_set(&ivar_map, g.class_impl_type, g);
+	}
+
+	for (const auto &cd : class_impls) {
+		auto &g = cd.g;
+		Type *class_type = g.class_impl_type;
+		Type *class_ptr_type = alloc_type_pointer(class_type);
+		lbValue class_value = cd.class_value;
+
+		Type *ivar_type = class_type->Named.type_name->TypeName.objc_ivar;
+
+		Entity *context_provider = class_type->Named.type_name->TypeName.objc_context_provider;
+		Type *contex_provider_self_ptr_type = nullptr;
+		Type *contex_provider_self_named_type = nullptr;
+		bool is_context_provider_ivar = false;
+		lbValue context_provider_proc_value{};
+
+		if (context_provider) {
+			context_provider_proc_value = lb_find_procedure_value_from_entity(m, context_provider);
+
+			contex_provider_self_ptr_type = base_type(context_provider->type->Proc.params->Tuple.variables[0]->type);
+			GB_ASSERT(contex_provider_self_ptr_type->kind == Type_Pointer);
+			contex_provider_self_named_type = base_named_type(type_deref(contex_provider_self_ptr_type));
+
+			is_context_provider_ivar = ivar_type != nullptr && internal_check_is_assignable_to(contex_provider_self_named_type, ivar_type);
+		}
+
+
+		Array<ObjcMethodData> *methods = map_get(&m->info->objc_method_implementations, class_type);
+		if (!methods) {
+			continue;
+		}
+
+		for (const ObjcMethodData &md : *methods) {
+			GB_ASSERT( md.proc_entity->kind == Entity_Procedure);
+			Type *method_type = md.proc_entity->type;
+
+			String proc_name = make_string_c("__$objc_method::");
+			proc_name = concatenate_strings(temporary_allocator(), proc_name, g.name);
+			proc_name = concatenate_strings(temporary_allocator(), proc_name, str_lit("::"));
+			proc_name = concatenate_strings( permanent_allocator(), proc_name, md.ac.objc_name);
+
+			wrapper_args.count = 2;
+			wrapper_args[0] = md.ac.objc_is_class_method ? t_objc_Class : class_ptr_type;
+			wrapper_args[1] = t_objc_SEL;
+
+			isize method_param_count  = method_type->Proc.param_count;
+			isize method_param_offset = 0;
+
+			if (!md.ac.objc_is_class_method) {
+				GB_ASSERT(method_param_count >= 1);
+				method_param_count -= 1;
+				method_param_offset = 1;
+			}
+
+			for (isize i = 0; i < method_param_count; i++) {
+				array_add(&wrapper_args, method_type->Proc.params->Tuple.variables[method_param_offset+i]->type);
+			}
+
+			Type *wrapper_args_tuple = alloc_type_tuple_from_field_types(wrapper_args.data, wrapper_args.count, false, true);
+			Type *wrapper_results_tuple = nullptr;
+
+			if (method_type->Proc.result_count > 0) {
+				GB_ASSERT(method_type->Proc.result_count == 1);
+				wrapper_results_tuple = alloc_type_tuple_from_field_types(&method_type->Proc.results->Tuple.variables[0]->type, 1, false, true);
+			}
+
+			Type *wrapper_proc_type = alloc_type_proc(nullptr, wrapper_args_tuple, wrapper_args_tuple->Tuple.variables.count,
+														wrapper_results_tuple, method_type->Proc.result_count, false, ProcCC_CDecl);
+
+			lbProcedure *wrapper_proc = lb_create_dummy_procedure(m, proc_name, wrapper_proc_type);
+			lb_add_attribute_to_proc(wrapper_proc->module, wrapper_proc->value, "nounwind");
+
+			// Emit the wrapper
+			LLVMSetLinkage(wrapper_proc->value, LLVMExternalLinkage);
+			lb_begin_procedure_body(wrapper_proc);
+			{
+				if (method_type->Proc.calling_convention == ProcCC_Odin) {
+					GB_ASSERT(context_provider);
+
+					// Emit the get odin context call
+
+					get_context_args[0] = lbValue {
+						wrapper_proc->raw_input_parameters[0],
+						contex_provider_self_ptr_type,
+					};
+
+					if (is_context_provider_ivar) {
+						// The context provider takes the ivar's type.
+						// Emit an objc_ivar_get call and use that pointer for 'self' instead.
+						lbValue real_self {
+							wrapper_proc->raw_input_parameters[0],
+							class_ptr_type
+						};
+						get_context_args[0] = lb_handle_objc_ivar_for_objc_object_pointer(wrapper_proc, real_self);
+					}
+
+					lbValue context	     = lb_emit_call(wrapper_proc, context_provider_proc_value, get_context_args);
+					lbAddr  context_addr = lb_addr(lb_address_from_load_or_generate_local(wrapper_proc, context));
+					lb_push_context_onto_stack(wrapper_proc, context_addr);
+				}
+
+
+				auto method_call_args = array_make<lbValue>(temporary_allocator(), method_param_count + method_param_offset);
+
+				if (!md.ac.objc_is_class_method) {
+					method_call_args[0] = lbValue {
+						wrapper_proc->raw_input_parameters[0],
+						class_ptr_type,
+					};
+				}
+
+				for (isize i = 0; i < method_param_count; i++) {
+					method_call_args[i+method_param_offset] = lbValue {
+						wrapper_proc->raw_input_parameters[i+2],
+						method_type->Proc.params->Tuple.variables[i+method_param_offset]->type,
+					};
+				}
+				lbValue method_proc_value = lb_find_procedure_value_from_entity(m, md.proc_entity);
+
+				// Call real procedure for method from here, passing the parameters expected, if any.
+				lbValue return_value = lb_emit_call(wrapper_proc, method_proc_value, method_call_args);
+
+				if (wrapper_results_tuple != nullptr) {
+					auto &result_var = method_type->Proc.results->Tuple.variables[0];
+					return_value = lb_emit_conv(wrapper_proc, return_value, result_var->type);
+					lb_build_return_stmt_internal(wrapper_proc, return_value, result_var->token.pos);
+				}
+			}
+			lb_end_procedure_body(wrapper_proc);
+
+
+			// Add the method to the class
+			String method_encoding = str_lit("v");
+			// TODO (harold): Checker must ensure that objc_methods have a single return value or none!
+			GB_ASSERT(method_type->Proc.result_count <= 1);
+			if (method_type->Proc.result_count != 0) {
+				method_encoding = lb_get_objc_type_encoding(method_type->Proc.results->Tuple.variables[0]->type);
+			}
+
+			if (!md.ac.objc_is_class_method) {
+				method_encoding = concatenate_strings(temporary_allocator(), method_encoding, str_lit("@:"));
+			} else {
+				method_encoding = concatenate_strings(temporary_allocator(), method_encoding, str_lit("#:"));
+			}
+
+			for (isize i = method_param_offset; i < method_param_count; i++) {
+				Type *param_type = method_type->Proc.params->Tuple.variables[i]->type;
+				String param_encoding = lb_get_objc_type_encoding(param_type);
+
+				method_encoding = concatenate_strings(temporary_allocator(), method_encoding, param_encoding);
+			}
+
+			// Emit method registration
+			lbAddr* sel_address = string_map_get(&m->objc_selectors, md.ac.objc_selector);
+			GB_ASSERT(sel_address);
+			lbValue selector_value = lb_addr_load(p, *sel_address);
+
+			args.count = 4;
+			args[0] = class_value;    // Class
+			args[1] = selector_value; // SEL
+			args[2] = lbValue { wrapper_proc->value, wrapper_proc->type };
+			args[3] = lb_const_value(m, t_cstring, exact_value_string(method_encoding));
+
+			// TODO(harold): Emit check BOOL result and panic if false.
+			lb_emit_runtime_call(p, "class_addMethod", args);
+
+		} // End methods
+
+		// Add ivar if we have one
+		if (ivar_type != nullptr) {
+			// Register a single ivar for this class
+			Type *ivar_base = ivar_type->Named.base;
+
+			// @note(harold): The alignment is supposed to be passed as log2(alignment): https://developer.apple.com/documentation/objectivec/class_addivar(_:_:_:_:_:)?language=objc
+			const i64 size      = type_size_of(ivar_base);
+			const i64 alignment = (i64)floor_log2((u64)type_align_of(ivar_base));
+
+			// NOTE(harold): I've opted to not emit the type encoding for ivars in order to keep the data private.
+			//               If there is desire in the future to emit the type encoding for introspection through the Obj-C runtime,
+			//               then perhaps an option can be added for it then.
+			// Should we pass the actual type encoding? Might not be ideal for obfuscation.
+			String ivar_name  = str_lit("__$ivar");
+			String ivar_types = str_lit("{= }");	//lb_get_objc_type_encoding(ivar_type);
+			args.count = 5;
+			args[0] = class_value;
+			args[1] = lb_const_value(m, t_cstring, exact_value_string(ivar_name));
+			args[2] = lb_const_value(m, t_uint, exact_value_u64((u64)size));
+			args[3] = lb_const_value(m, t_u8, exact_value_u64((u64)alignment));
+			args[4] = lb_const_value(m, t_cstring, exact_value_string(ivar_types));
+			lb_emit_runtime_call(p, "class_addIvar", args);
+		}
+
+		// Complete the class registration
+		args.count = 1;
+		args[0] = class_value;
+		lb_emit_runtime_call(p, "objc_registerClassPair", args);
+	}
+
+	// Register ivar offsets for any `objc_ivar_get` expressions emitted.
+	for (auto const& kv : ivar_map) {
+		lbObjCGlobal const& g = kv.value;
+		lbAddr ivar_addr = {};
+		lbValue *found = string_map_get(&m->members, g.global_name);
+
+		if (found) {
+			ivar_addr = lb_addr(*found);
+			GB_ASSERT(ivar_addr.addr.type == t_int_ptr);
+		} else {
+			// Defined in an external package, define it now in the main package
+			LLVMTypeRef t = lb_type(m, t_int);
+
+			lbValue global{};
+			global.value = LLVMAddGlobal(m->mod, t, g.global_name);
+			global.type  = t_int_ptr;
+
+			LLVMSetInitializer(global.value, LLVMConstInt(t, 0, true));
+
+			ivar_addr = lb_addr(global);
+		}
+
+		String class_name = g.class_impl_type->Named.type_name->TypeName.objc_class_name;
+		lbValue class_value = string_map_must_get(&global_class_map, class_name).class_value;
+
+		args.count = 2;
+		args[0] = class_value;
+		args[1] = lb_const_value(m, t_cstring, exact_value_string(str_lit("__$ivar")));
+		lbValue ivar = lb_emit_runtime_call(p, "class_getInstanceVariable", args);
+
+		args.count = 1;
+		args[0] = ivar;
+		lbValue ivar_offset     = lb_emit_runtime_call(p, "ivar_getOffset", args);
+		lbValue ivar_offset_int = lb_emit_conv(p, ivar_offset, t_int);
+
+		lb_addr_store(p, ivar_addr, ivar_offset_int);
 	}
 
 	lb_end_procedure_body(p);
@@ -1278,6 +1907,10 @@ gb_internal lbProcedure *lb_create_startup_runtime(lbModule *main_module, lbProc
 	lb_add_attribute_to_proc(p->module, p->value, "optnone");
 	lb_add_attribute_to_proc(p->module, p->value, "noinline");
 
+	// Make sure shared libraries call their own runtime startup on Linux.
+	LLVMSetVisibility(p->value, LLVMHiddenVisibility);
+	LLVMSetLinkage(p->value, LLVMWeakAnyLinkage);
+
 	lb_begin_procedure_body(p);
 
 	lb_setup_type_info_data(main_module);
@@ -1344,14 +1977,14 @@ gb_internal lbProcedure *lb_create_startup_runtime(lbModule *main_module, lbProc
 				gbString var_name = gb_string_make(permanent_allocator(), "__$global_any::");
 				gbString e_str = string_canonical_entity_name(temporary_allocator(), e);
 				var_name = gb_string_append_length(var_name, e_str, gb_strlen(e_str));
-				lbAddr g = lb_add_global_generated_with_name(main_module, var_type, var.init, make_string_c(var_name));
+				lbAddr g = lb_add_global_generated_with_name(main_module, var_type, {}, make_string_c(var_name));
 				lb_addr_store(p, g, var.init);
 				lbValue gp = lb_addr_get_ptr(p, g);
 
 				lbValue data = lb_emit_struct_ep(p, var.var, 0);
 				lbValue ti   = lb_emit_struct_ep(p, var.var, 1);
 				lb_emit_store(p, data, lb_emit_conv(p, gp, t_rawptr));
-				lb_emit_store(p, ti,   lb_type_info(p, var_type));
+				lb_emit_store(p, ti,   lb_typeid(p->module, var_type));
 			} else {
 				LLVMTypeRef vt = llvm_addr_type(p->module, var.var);
 				lbValue src0 = lb_emit_conv(p, var.init, t);
@@ -1387,6 +2020,10 @@ gb_internal lbProcedure *lb_create_cleanup_runtime(lbModule *main_module) { // C
 	lb_add_attribute_to_proc(p->module, p->value, "optnone");
 	lb_add_attribute_to_proc(p->module, p->value, "noinline");
 
+	// Make sure shared libraries call their own runtime cleanup on Linux.
+	LLVMSetVisibility(p->value, LLVMHiddenVisibility);
+	LLVMSetLinkage(p->value, LLVMWeakAnyLinkage);
+
 	lb_begin_procedure_body(p);
 
 	CheckerInfo *info = main_module->gen->info;
@@ -2185,7 +2822,6 @@ gb_internal void lb_generate_procedure(lbModule *m, lbProcedure *p) {
 		p->is_done = true;
 		m->curr_procedure = nullptr;
 	}
-	lb_end_procedure(p);
 
 	// Add Flags
 	if (p->entity && p->entity->kind == Entity_Procedure && p->entity->Procedure.is_memcpy_like) {
@@ -2494,6 +3130,7 @@ gb_internal bool lb_generate_code(lbGenerator *gen) {
 				LLVMSetInitializer(g, LLVMConstNull(lb_type(m, t)));
 				LLVMSetLinkage(g, LLVMInternalLinkage);
 				lb_make_global_private_const(g);
+				lb_set_odin_rtti_section(g);
 				return lb_addr({g, alloc_type_pointer(t)});
 			};
 
@@ -2565,24 +3202,9 @@ gb_internal bool lb_generate_code(lbGenerator *gen) {
 		lbValue g = {};
 		g.value = LLVMAddGlobal(m->mod, lb_type(m, e->type), alloc_cstring(permanent_allocator(), name));
 		g.type = alloc_type_pointer(e->type);
-		if (e->Variable.thread_local_model != "") {
-			LLVMSetThreadLocal(g.value, true);
-
-			String m = e->Variable.thread_local_model;
-			LLVMThreadLocalMode mode = LLVMGeneralDynamicTLSModel;
-			if (m == "default") {
-				mode = LLVMGeneralDynamicTLSModel;
-			} else if (m == "localdynamic") {
-				mode = LLVMLocalDynamicTLSModel;
-			} else if (m == "initialexec") {
-				mode = LLVMInitialExecTLSModel;
-			} else if (m == "localexec") {
-				mode = LLVMLocalExecTLSModel;
-			} else {
-				GB_PANIC("Unhandled thread local mode %.*s", LIT(m));
-			}
-			LLVMSetThreadLocalMode(g.value, mode);
-		}
+
+		lb_apply_thread_local_model(g.value, e->Variable.thread_local_model);
+
 		if (is_foreign) {
 			LLVMSetLinkage(g.value, LLVMExternalLinkage);
 			LLVMSetDLLStorageClass(g.value, LLVMDLLImportStorageClass);

+ 3 - 1
src/llvm_backend.hpp

@@ -196,6 +196,7 @@ struct lbModule {
 
 	StringMap<lbAddr> objc_classes;
 	StringMap<lbAddr> objc_selectors;
+	StringMap<lbAddr> objc_ivars;
 
 	PtrMap<u64/*type hash*/, lbAddr> map_cell_info_map; // address of runtime.Map_Info
 	PtrMap<u64/*type hash*/, lbAddr> map_info_map;      // address of runtime.Map_Cell_Info
@@ -219,6 +220,7 @@ struct lbObjCGlobal {
 	gbString  global_name;
 	String    name;
 	Type *    type;
+	Type *    class_impl_type;  // This is set when the class has the objc_implement attribute set to true.
 };
 
 struct lbGenerator : LinkerData {
@@ -240,6 +242,7 @@ struct lbGenerator : LinkerData {
 	MPSCQueue<lbEntityCorrection> entities_to_correct_linkage;
 	MPSCQueue<lbObjCGlobal> objc_selectors;
 	MPSCQueue<lbObjCGlobal> objc_classes;
+  MPSCQueue<lbObjCGlobal> objc_ivars;
 	MPSCQueue<String> raddebug_section_strings;
 };
 
@@ -410,7 +413,6 @@ gb_internal LLVMAttributeRef lb_create_enum_attribute_with_type(LLVMContextRef c
 gb_internal void lb_add_proc_attribute_at_index(lbProcedure *p, isize index, char const *name, u64 value);
 gb_internal void lb_add_proc_attribute_at_index(lbProcedure *p, isize index, char const *name);
 gb_internal lbProcedure *lb_create_procedure(lbModule *module, Entity *entity, bool ignore_body=false);
-gb_internal void lb_end_procedure(lbProcedure *p);
 
 
 gb_internal LLVMTypeRef lb_type(lbModule *m, Type *type);

+ 4 - 1
src/llvm_backend_const.cpp

@@ -533,7 +533,10 @@ gb_internal lbValue lb_const_value(lbModule *m, Type *type, ExactValue value, lb
 			Entity *e = entity_from_expr(expr);
 			res = lb_find_procedure_value_from_entity(m, e);
 		}
-		GB_ASSERT(res.value != nullptr);
+		if (res.value == nullptr) {
+			// This is an unspecialized polymorphic procedure, return nil or dummy value
+			return lb_const_nil(m, original_type);
+		}
 		GB_ASSERT(LLVMGetValueKind(res.value) == LLVMFunctionValueKind);
 
 		if (LLVMGetIntrinsicID(res.value) == 0) {

+ 56 - 0
src/llvm_backend_debug.cpp

@@ -1374,3 +1374,59 @@ gb_internal void lb_add_debug_info_for_global_constant_from_entity(lbGenerator *
 		}
 	}
 }
+
+gb_internal void lb_add_debug_label(lbProcedure *p, Ast *label, lbBlock *target) {
+// NOTE(tf2spi): LLVM-C DILabel API used only existed for major versions 20+
+#if LLVM_VERSION_MAJOR >= 20
+	if (p == nullptr || p->debug_info == nullptr) {
+		return;
+	}
+	if (target == nullptr || label == nullptr || label->kind != Ast_Label) {
+		return;
+	}
+	Token label_token = label->Label.token;
+	if (is_blank_ident(label_token.string)) {
+		return;
+	}
+	lbModule *m = p->module;
+	if (m == nullptr) {
+		return;
+	}
+
+	AstFile *file = label->file();
+	LLVMMetadataRef llvm_file = lb_get_llvm_metadata(m, file);
+	if (llvm_file == nullptr) {
+		debugf("llvm file not found for label\n");
+		return;
+	}
+	LLVMMetadataRef llvm_scope = p->debug_info;
+	if(llvm_scope == nullptr) {
+		debugf("llvm scope not found for label\n");
+		return;
+	}
+	LLVMMetadataRef llvm_debug_loc = lb_debug_location_from_token_pos(p, label_token.pos);
+	LLVMBasicBlockRef llvm_block = target->block;
+	if (llvm_block == nullptr || llvm_debug_loc == nullptr) {
+		return;
+	}
+	LLVMMetadataRef llvm_label = LLVMDIBuilderCreateLabel(
+		m->debug_builder,
+		llvm_scope,
+		(const char *)label_token.string.text,
+		(size_t)label_token.string.len,
+		llvm_file,
+		label_token.pos.line,
+
+		// NOTE(tf2spi): Defaults to false in LLVM API, but I'd rather not take chances
+		//               Always preserve the label no matter what when debugging
+		true
+	);
+	GB_ASSERT(llvm_label != nullptr);
+	(void)LLVMDIBuilderInsertLabelAtEnd(
+		m->debug_builder,
+		llvm_label,
+		llvm_debug_loc,
+		llvm_block
+	);
+#endif
+}

+ 17 - 9
src/llvm_backend_expr.cpp

@@ -4844,7 +4844,7 @@ gb_internal lbAddr lb_build_addr_compound_lit(lbProcedure *p, Ast *expr) {
 		if (cl->elems.count == 0) {
 			break;
 		}
-		GB_ASSERT(expr->file()->feature_flags & OptInFeatureFlag_DynamicLiterals);
+		GB_ASSERT(expr->file()->feature_flags & OptInFeatureFlag_DynamicLiterals || build_context.dynamic_literals);
 
 		lbValue err = lb_dynamic_map_reserve(p, v.addr, 2*cl->elems.count, pos);
 		gb_unused(err);
@@ -5154,8 +5154,6 @@ gb_internal lbAddr lb_build_addr_internal(lbProcedure *p, Ast *expr) {
 				return lb_build_addr(p, unparen_expr(se->selector));
 			}
 
-
-			Type *type = base_type(tav.type);
 			if (tav.mode == Addressing_Type) { // Addressing_Type
 				Selection sel = lookup_field(tav.type, selector, true);
 				if (sel.pseudo_field) {
@@ -5190,18 +5188,29 @@ gb_internal lbAddr lb_build_addr_internal(lbProcedure *p, Ast *expr) {
 				return lb_addr_swizzle(a, type, swizzle_count, swizzle_indices);
 			}
 
-			Selection sel = lookup_field(type, selector, false);
+			Selection sel = lookup_field(tav.type, selector, false);
 			GB_ASSERT(sel.entity != nullptr);
-			if (sel.pseudo_field) {
-				GB_ASSERT(sel.entity->kind == Entity_Procedure || sel.entity->kind == Entity_ProcGroup);
+			if (sel.pseudo_field && (sel.entity->kind == Entity_Procedure || sel.entity->kind == Entity_ProcGroup)) {
 				Entity *e = entity_of_node(sel_node);
 				GB_ASSERT(e->kind == Entity_Procedure);
 				return lb_addr(lb_find_value_from_entity(p->module, e));
 			}
 
-			if (sel.is_bit_field) {
-				lbAddr addr = lb_build_addr(p, se->expr);
+			lbAddr addr = lb_build_addr(p, se->expr);
+
+			// NOTE(harold): Only allow ivar pseudo field access on indirect selectors.
+			//				 It is incoherent otherwise as Objective-C objects are zero-sized.
+			Type *deref_type = type_deref(tav.type);
+			if (tav.type->kind == Type_Pointer && deref_type->kind == Type_Named && deref_type->Named.type_name->TypeName.objc_ivar) {
+				// NOTE(harold): We need to load the ivar from the current address and
+				//				 replace addr with the loaded ivar addr to apply the selector load properly.
+				addr = lb_addr(lb_emit_load(p, addr.addr));
 
+				lbValue ivar_ptr = lb_handle_objc_ivar_for_objc_object_pointer(p, addr.addr);
+				addr = lb_addr(ivar_ptr);
+			}
+
+			if (sel.is_bit_field) {
 				Selection sub_sel = sel;
 				sub_sel.index.count -= 1;
 
@@ -5227,7 +5236,6 @@ gb_internal lbAddr lb_build_addr_internal(lbProcedure *p, Ast *expr) {
 			}
 
 			{
-				lbAddr addr = lb_build_addr(p, se->expr);
 				if (addr.kind == lbAddr_Map) {
 					lbValue v = lb_addr_load(p, addr);
 					lbValue a = lb_address_from_load_or_generate_local(p, v);

+ 100 - 25
src/llvm_backend_general.cpp

@@ -101,6 +101,7 @@ gb_internal void lb_init_module(lbModule *m, Checker *c) {
 
 	string_map_init(&m->objc_classes);
 	string_map_init(&m->objc_selectors);
+	string_map_init(&m->objc_ivars);
 
 	map_init(&m->map_info_map, 0);
 	map_init(&m->map_cell_info_map, 0);
@@ -173,8 +174,9 @@ gb_internal bool lb_init_generator(lbGenerator *gen, Checker *c) {
 	mpsc_init(&gen->entities_to_correct_linkage, heap_allocator());
 	mpsc_init(&gen->objc_selectors, heap_allocator());
 	mpsc_init(&gen->objc_classes, heap_allocator());
+  mpsc_init(&gen->objc_ivars, heap_allocator());
 	mpsc_init(&gen->raddebug_section_strings, heap_allocator());
-
+  
 	return true;
 }
 
@@ -886,8 +888,8 @@ gb_internal void lb_addr_store(lbProcedure *p, lbAddr addr, lbValue value) {
 			Type *t = base_type(type_deref(addr.addr.type));
 			GB_ASSERT(t->kind == Type_Struct && t->Struct.soa_kind != StructSoa_None);
 			lbValue len = lb_soa_struct_len(p, addr.addr);
-			if (addr.soa.index_expr != nullptr) {
-				lb_emit_bounds_check(p, ast_token(addr.soa.index_expr), index, len);
+			if (addr.soa.index_expr != nullptr && (!lb_is_const(addr.soa.index) || t->Struct.soa_kind != StructSoa_Fixed)) {
+				lb_emit_bounds_check(p, ast_token(addr.soa.index_expr), addr.soa.index, len);
 			}
 		}
 
@@ -2213,6 +2215,14 @@ gb_internal LLVMTypeRef lb_type_internal(lbModule *m, Type *type) {
 
 	case Type_BitField:
 		return lb_type_internal(m, type->BitField.backing_type);
+        
+	case Type_Generic:
+		if (type->Generic.specialized) {
+			return lb_type_internal(m, type->Generic.specialized);
+		} else {
+			// For unspecialized generics, use a pointer type as a placeholder
+			return LLVMPointerType(LLVMInt8TypeInContext(m->ctx), 0);
+		}
 	}
 
 	GB_PANIC("Invalid type %s", type_to_string(type));
@@ -2378,6 +2388,29 @@ gb_internal void lb_add_attribute_to_proc_with_string(lbModule *m, LLVMValueRef
 }
 
 
+gb_internal bool lb_apply_thread_local_model(LLVMValueRef value, String model) {
+	if (model != "") {
+		LLVMSetThreadLocal(value, true);
+
+		LLVMThreadLocalMode mode = LLVMGeneralDynamicTLSModel;
+		if (model == "default") {
+			mode = LLVMGeneralDynamicTLSModel;
+		} else if (model == "localdynamic") {
+			mode = LLVMLocalDynamicTLSModel;
+		} else if (model == "initialexec") {
+			mode = LLVMInitialExecTLSModel;
+		} else if (model == "localexec") {
+			mode = LLVMLocalExecTLSModel;
+		} else {
+			GB_PANIC("Unhandled thread local mode %.*s", LIT(model));
+		}
+		LLVMSetThreadLocalMode(value, mode);
+		return true;
+	}
+
+	return false;
+}
+
 
 gb_internal void lb_add_edge(lbBlock *from, lbBlock *to) {
 	LLVMValueRef instr = LLVMGetLastInstruction(from->block);
@@ -2516,10 +2549,13 @@ general_end:;
 		}
 	}
 
-	src_size = align_formula(src_size, src_align);
-	dst_size = align_formula(dst_size, dst_align);
+	// NOTE(laytan): even though this logic seems sound, the Address Sanitizer does not
+	// want you to load/store the space of a value that is there for alignment.
+#if 0
+	i64 aligned_src_size = align_formula(src_size, src_align);
+	i64 aligned_dst_size = align_formula(dst_size, dst_align);
 
-	if (LLVMIsALoadInst(val) && (src_size >= dst_size && src_align >= dst_align)) {
+	if (LLVMIsALoadInst(val) && (aligned_src_size >= aligned_dst_size && src_align >= dst_align)) {
 		LLVMValueRef val_ptr = LLVMGetOperand(val, 0);
 		val_ptr = LLVMBuildPointerCast(p->builder, val_ptr, LLVMPointerType(dst_type, 0), "");
 		LLVMValueRef loaded_val = OdinLLVMBuildLoad(p, dst_type, val_ptr);
@@ -2527,8 +2563,57 @@ general_end:;
 		// LLVMSetAlignment(loaded_val, gb_min(src_align, dst_align));
 
 		return loaded_val;
+	}
+#endif
+
+	if (src_size > dst_size) {
+		GB_ASSERT(p->decl_block != p->curr_block);
+		// NOTE(laytan): src is bigger than dst, need to memcpy the part of src we want.
+
+		LLVMValueRef val_ptr; 
+		if (LLVMIsALoadInst(val)) {
+			val_ptr = LLVMGetOperand(val, 0);
+		} else if (LLVMIsAAllocaInst(val)) {
+			val_ptr = LLVMBuildPointerCast(p->builder, val, LLVMPointerType(src_type, 0), "");
+		} else {
+			// NOTE(laytan): we need a pointer to memcpy from.
+			LLVMValueRef val_copy = llvm_alloca(p, src_type, src_align);
+			val_ptr = LLVMBuildPointerCast(p->builder, val_copy, LLVMPointerType(src_type, 0), "");
+			LLVMBuildStore(p->builder, val, val_ptr);
+		}
+
+		i64 max_align = gb_max(lb_alignof(src_type), lb_alignof(dst_type));
+		max_align = gb_max(max_align, 16);
+
+		LLVMValueRef ptr = llvm_alloca(p, dst_type, max_align);
+		LLVMValueRef nptr = LLVMBuildPointerCast(p->builder, ptr, LLVMPointerType(dst_type, 0), "");
+
+		LLVMTypeRef types[3] = {
+			lb_type(p->module, t_rawptr),
+			lb_type(p->module, t_rawptr),
+			lb_type(p->module, t_int)
+		};
+
+		LLVMValueRef args[4] = {
+			nptr,
+			val_ptr,
+			LLVMConstInt(LLVMIntTypeInContext(p->module->ctx, 8*cast(unsigned)build_context.int_size), dst_size, 0),
+			LLVMConstInt(LLVMInt1TypeInContext(p->module->ctx), 0, 0),
+		};
+
+		lb_call_intrinsic(
+			p,
+			"llvm.memcpy.inline",
+			args,
+			gb_count_of(args),
+			types,
+			gb_count_of(types)
+		);
+
+		return OdinLLVMBuildLoad(p, dst_type, ptr);
 	} else {
 		GB_ASSERT(p->decl_block != p->curr_block);
+		GB_ASSERT(dst_size >= src_size);
 
 		i64 max_align = gb_max(lb_alignof(src_type), lb_alignof(dst_type));
 		max_align = gb_max(max_align, 16);
@@ -2729,6 +2814,14 @@ gb_internal lbValue lb_find_procedure_value_from_entity(lbModule *m, Entity *e)
 	ignore_body = other_module != m;
 
 	lbProcedure *missing_proc = lb_create_procedure(m, e, ignore_body);
+	if (missing_proc == nullptr) {
+		// This is an unspecialized polymorphic procedure, which should not be codegen'd
+		lbValue dummy = {};
+		dummy.value = nullptr;
+		dummy.type = nullptr;
+		return dummy;
+	}
+
 	if (ignore_body) {
 		mutex_lock(&gen->anonymous_proc_lits_mutex);
 		defer (mutex_unlock(&gen->anonymous_proc_lits_mutex));
@@ -2921,25 +3014,7 @@ gb_internal lbValue lb_find_value_from_entity(lbModule *m, Entity *e) {
 
 			lb_set_entity_from_other_modules_linkage_correctly(other_module, e, name);
 
-			if (e->Variable.thread_local_model != "") {
-				LLVMSetThreadLocal(g.value, true);
-
-				String m = e->Variable.thread_local_model;
-				LLVMThreadLocalMode mode = LLVMGeneralDynamicTLSModel;
-				if (m == "default") {
-					mode = LLVMGeneralDynamicTLSModel;
-				} else if (m == "localdynamic") {
-					mode = LLVMLocalDynamicTLSModel;
-				} else if (m == "initialexec") {
-					mode = LLVMInitialExecTLSModel;
-				} else if (m == "localexec") {
-					mode = LLVMLocalExecTLSModel;
-				} else {
-					GB_PANIC("Unhandled thread local mode %.*s", LIT(m));
-				}
-				LLVMSetThreadLocalMode(g.value, mode);
-			}
-
+			lb_apply_thread_local_model(g.value, e->Variable.thread_local_model);
 
 			return g;
 		}

+ 76 - 2
src/llvm_backend_proc.cpp

@@ -67,6 +67,14 @@ gb_internal void lb_mem_copy_non_overlapping(lbProcedure *p, lbValue dst, lbValu
 gb_internal lbProcedure *lb_create_procedure(lbModule *m, Entity *entity, bool ignore_body) {
 	GB_ASSERT(entity != nullptr);
 	GB_ASSERT(entity->kind == Entity_Procedure);
+	// Skip codegen for unspecialized polymorphic procedures
+	if (is_type_polymorphic(entity->type) && !entity->Procedure.is_foreign) {
+		Type *bt = base_type(entity->type);
+		if (bt->kind == Type_Proc && bt->Proc.is_polymorphic && !bt->Proc.is_poly_specialized) {
+			// Do not generate code for unspecialized polymorphic procedures
+			return nullptr;
+		}
+	}
 	if (!entity->Procedure.is_foreign) {
 		if ((entity->flags & EntityFlag_ProcBodyChecked) == 0) {
 			GB_PANIC("%.*s :: %s (was parapoly: %d %d)", LIT(entity->token.string), type_to_string(entity->type), is_type_polymorphic(entity->type, true), is_type_polymorphic(entity->type, false));
@@ -783,8 +791,7 @@ gb_internal void lb_end_procedure_body(lbProcedure *p) {
 
 	p->curr_block = nullptr;
 	p->state_flags = 0;
-}
-gb_internal void lb_end_procedure(lbProcedure *p) {
+
 	LLVMDisposeBuilder(p->builder);
 }
 
@@ -817,6 +824,10 @@ gb_internal void lb_build_nested_proc(lbProcedure *p, AstProcLit *pd, Entity *e)
 	e->Procedure.link_name = name;
 
 	lbProcedure *nested_proc = lb_create_procedure(p->module, e);
+	if (nested_proc == nullptr) {
+		// This is an unspecialized polymorphic procedure, skip codegen
+		return;
+	}
 	e->code_gen_procedure = nested_proc;
 
 	lbValue value = {};
@@ -2235,6 +2246,68 @@ gb_internal lbValue lb_build_builtin_proc(lbProcedure *p, Ast *expr, TypeAndValu
 		return lb_emit_load(p, tuple);
 	}
 
+	case BuiltinProc_compress_values: {
+		isize value_count = 0;
+		for (Ast *arg : ce->args) {
+			Type *t = arg->tav.type;
+			if (is_type_tuple(t)) {
+				value_count += t->Tuple.variables.count;
+			} else {
+				value_count += 1;
+			}
+		}
+
+		if (value_count == 1) {
+			lbValue x = lb_build_expr(p, ce->args[0]);
+			x = lb_emit_conv(p, x, tv.type);
+			return x;
+		}
+
+		Type *dt = base_type(tv.type);
+		lbAddr addr = lb_add_local_generated(p, tv.type, true);
+		if (is_type_struct(dt) || is_type_tuple(dt)) {
+			i32 index = 0;
+			for (Ast *arg : ce->args) {
+				lbValue x = lb_build_expr(p, arg);
+				if (is_type_tuple(x.type)) {
+					for (isize i = 0; i < x.type->Tuple.variables.count; i++) {
+						lbValue y = lb_emit_tuple_ev(p, x, cast(i32)i);
+						lbValue ptr = lb_emit_struct_ep(p, addr.addr, index++);
+						y = lb_emit_conv(p, y, type_deref(ptr.type));
+						lb_emit_store(p, ptr, y);
+					}
+				} else {
+					lbValue ptr = lb_emit_struct_ep(p, addr.addr, index++);
+					x = lb_emit_conv(p, x, type_deref(ptr.type));
+					lb_emit_store(p, ptr, x);
+				}
+			}
+			GB_ASSERT(index == value_count);
+		} else if (is_type_array_like(dt)) {
+			i32 index = 0;
+			for (Ast *arg : ce->args) {
+				lbValue x = lb_build_expr(p, arg);
+				if (is_type_tuple(x.type)) {
+					for (isize i = 0; i < x.type->Tuple.variables.count; i++) {
+						lbValue y = lb_emit_tuple_ev(p, x, cast(i32)i);
+						lbValue ptr = lb_emit_array_epi(p, addr.addr, index++);
+						y = lb_emit_conv(p, y, type_deref(ptr.type));
+						lb_emit_store(p, ptr, y);
+					}
+				} else {
+					lbValue ptr = lb_emit_array_epi(p, addr.addr, index++);
+					x = lb_emit_conv(p, x, type_deref(ptr.type));
+					lb_emit_store(p, ptr, x);
+				}
+			}
+			GB_ASSERT(index == value_count);
+		} else {
+			GB_PANIC("TODO(bill): compress_values -> %s", type_to_string(tv.type));
+		}
+
+		return lb_addr_load(p, addr);
+	}
+
 	case BuiltinProc_min: {
 		Type *t = type_of_expr(expr);
 		if (ce->args.count == 2) {
@@ -3375,6 +3448,7 @@ gb_internal lbValue lb_build_builtin_proc(lbProcedure *p, Ast *expr, TypeAndValu
 	case BuiltinProc_objc_find_class:        return lb_handle_objc_find_class(p, expr);
 	case BuiltinProc_objc_register_selector: return lb_handle_objc_register_selector(p, expr);
 	case BuiltinProc_objc_register_class:    return lb_handle_objc_register_class(p, expr);
+	case BuiltinProc_objc_ivar_get:          return lb_handle_objc_ivar_get(p, expr);
 
 
 	case BuiltinProc_constant_utf16_cstring:

Alguns arquivos não foram mostrados porque muitos arquivos mudaram nesse diff