2
0
jason 1 жил өмнө
parent
commit
9625798513
100 өөрчлөгдсөн 6590 нэмэгдсэн , 2527 устгасан
  1. 5 3
      .github/workflows/ci.yml
  2. 0 1
      .github/workflows/nightly.yml
  3. 31 16
      base/intrinsics/intrinsics.odin
  4. 3 2
      base/runtime/core.odin
  5. 15 8
      base/runtime/heap_allocator.odin
  6. 1 1
      build_odin.sh
  7. 3 3
      core/crypto/rand_windows.odin
  8. 3 3
      core/encoding/csv/example.odin
  9. 1 1
      core/flags/errors.odin
  10. 2 2
      core/flags/internal_rtti.odin
  11. 65 2
      core/image/common.odin
  12. 1 1
      core/image/general_os.odin
  13. 1 1
      core/image/png/example.odin
  14. 1 1
      core/image/png/helpers.odin
  15. 2 1
      core/math/linalg/general.odin
  16. 1 1
      core/math/rand/rand.odin
  17. 2 2
      core/mem/virtual/file.odin
  18. 40 32
      core/net/socket_darwin.odin
  19. 1 1
      core/odin/ast/ast.odin
  20. 2 1
      core/odin/parser/parser.odin
  21. 0 73
      core/os/dir_bsd.odin
  22. 0 69
      core/os/dir_darwin.odin
  23. 0 72
      core/os/dir_linux.odin
  24. 0 71
      core/os/dir_openbsd.odin
  25. 62 0
      core/os/dir_unix.odin
  26. 11 10
      core/os/dir_windows.odin
  27. 19 19
      core/os/env_windows.odin
  28. 322 0
      core/os/errors.odin
  29. 83 93
      core/os/file_windows.odin
  30. 90 58
      core/os/os.odin
  31. 27 11
      core/os/os2/allocators.odin
  32. 3 1
      core/os/os2/dir.odin
  33. 14 1
      core/os/os2/dir_windows.odin
  34. 2 1
      core/os/os2/errors.odin
  35. 2 2
      core/os/os2/errors_linux.odin
  36. 9 0
      core/os/os2/file.odin
  37. 72 10
      core/os/os2/file_linux.odin
  38. 20 5
      core/os/os2/file_util.odin
  39. 58 10
      core/os/os2/file_windows.odin
  40. 6 5
      core/os/os2/path_windows.odin
  41. 2 2
      core/os/os2/stat.odin
  42. 7 9
      core/os/os2/stat_windows.odin
  43. 7 4
      core/os/os2/temp_file_linux.odin
  44. 382 230
      core/os/os_darwin.odin
  45. 29 24
      core/os/os_essence.odin
  46. 334 196
      core/os/os_freebsd.odin
  47. 101 69
      core/os/os_haiku.odin
  48. 106 61
      core/os/os_js.odin
  49. 293 258
      core/os/os_linux.odin
  50. 393 218
      core/os/os_netbsd.odin
  51. 342 205
      core/os/os_openbsd.odin
  52. 25 18
      core/os/os_wasi.odin
  53. 108 50
      core/os/os_windows.odin
  54. 18 38
      core/os/stat_unix.odin
  55. 36 36
      core/os/stat_windows.odin
  56. 16 33
      core/os/stream.odin
  57. 2 2
      core/path/filepath/match.odin
  58. 5 5
      core/path/filepath/path_windows.odin
  59. 14 21
      core/path/filepath/walk.odin
  60. 4 2
      core/prof/spall/doc.odin
  61. 2 2
      core/prof/spall/spall.odin
  62. 3 12
      core/prof/spall/spall_linux.odin
  63. 4 9
      core/prof/spall/spall_unix.odin
  64. 4 5
      core/prof/spall/spall_windows.odin
  65. 58 0
      core/reflect/reflect.odin
  66. 14 2
      core/simd/simd.odin
  67. 8 8
      core/simd/x86/sse2.odin
  68. 441 33
      core/sync/atomic.odin
  69. 21 0
      core/sync/doc.odin
  70. 391 92
      core/sync/extended.odin
  71. 5 6
      core/sync/futex_haiku.odin
  72. 420 56
      core/sync/primitives.odin
  73. 4 0
      core/sys/darwin/Foundation/NSOpenPanel.odin
  74. 1 1
      core/sys/haiku/os.odin
  75. 1 0
      core/sys/wasm/wasi/wasi_api.odin
  76. 37 0
      core/sys/windows/advapi32.odin
  77. 298 0
      core/sys/windows/codepage.odin
  78. 289 76
      core/sys/windows/gdi32.odin
  79. 33 14
      core/sys/windows/kernel32.odin
  80. 38 0
      core/sys/windows/ntdll.odin
  81. 15 1
      core/sys/windows/ole32.odin
  82. 25 0
      core/sys/windows/shcore.odin
  83. 1 0
      core/sys/windows/shell32.odin
  84. 121 4
      core/sys/windows/types.odin
  85. 216 70
      core/sys/windows/user32.odin
  86. 59 2
      core/sys/windows/util.odin
  87. 227 3
      core/sys/windows/winerror.odin
  88. 16 1
      core/sys/windows/winmm.odin
  89. 31 0
      core/sys/windows/winnls.odin
  90. 92 0
      core/sys/windows/winver.odin
  91. 2 2
      core/testing/runner.odin
  92. 1 1
      core/text/edit/text_edit.odin
  93. 319 30
      core/time/time.odin
  94. 1 1
      examples/demo/demo.odin
  95. 11 3
      src/build_settings.cpp
  96. 115 7
      src/check_builtin.cpp
  97. 31 0
      src/check_expr.cpp
  98. 1 1
      src/check_type.cpp
  99. 2 2
      src/checker.cpp
  100. 28 8
      src/checker_builtin_procs.hpp

+ 5 - 3
.github/workflows/ci.yml

@@ -18,10 +18,11 @@ jobs:
         usesh: true
         copyback: false
         prepare: |
-          PKG_PATH="https://cdn.NetBSD.org/pub/pkgsrc/packages/NetBSD/$(uname -p)/$(uname -r | cut -d_ -f1)_${PKGSRC_BRANCH}/All" /usr/sbin/pkg_add pkgin
+          PKG_PATH="https://cdn.NetBSD.org/pub/pkgsrc/packages/NetBSD/$(uname -p)/10.0_2024Q2/All" /usr/sbin/pkg_add pkgin
           pkgin -y in gmake git bash python311 llvm clang
           ln -s /usr/pkg/bin/python3.11 /usr/bin/python3
         run: |
+          set -e -x
           git config --global --add safe.directory $(pwd)
           gmake release
           ./odin version
@@ -88,13 +89,13 @@ jobs:
       - name: Download LLVM (MacOS Intel)
         if: matrix.os == 'macos-13'
         run: |
-          brew install llvm@17
+          brew install llvm@17 [email protected]
           echo "/usr/local/opt/llvm@17/bin" >> $GITHUB_PATH
 
       - name: Download LLVM (MacOS ARM)
         if: matrix.os == 'macos-14'
         run: |
-          brew install llvm@17 wasmtime
+          brew install llvm@17 wasmtime [email protected]
           echo "/opt/homebrew/opt/llvm@17/bin" >> $GITHUB_PATH
 
       - name: Build Odin
@@ -204,6 +205,7 @@ jobs:
         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 -define:ODIN_TEST_FANCY=false
       - name: Odin internals tests
         shell: cmd

+ 0 - 1
.github/workflows/nightly.yml

@@ -61,7 +61,6 @@ jobs:
           mkdir dist
           cp odin dist
           cp LICENSE dist
-          cp libLLVM* dist
           cp -r shared dist
           cp -r base dist
           cp -r core dist

+ 31 - 16
base/intrinsics/intrinsics.odin

@@ -42,8 +42,8 @@ overflow_add :: proc(lhs, rhs: $T) -> (T, bool) where type_is_integer(T) #option
 overflow_sub :: proc(lhs, rhs: $T) -> (T, bool) where type_is_integer(T) #optional_ok ---
 overflow_mul :: proc(lhs, rhs: $T) -> (T, bool) where type_is_integer(T) #optional_ok ---
 
-add_sat :: proc(lhs, rhs: $T) -> T where type_is_integer(T) ---
-sub_sat :: proc(lhs, rhs: $T) -> T where type_is_integer(T) ---
+saturating_add :: proc(lhs, rhs: $T) -> T where type_is_integer(T) ---
+saturating_sub :: proc(lhs, rhs: $T) -> T where type_is_integer(T) ---
 
 sqrt :: proc(x: $T) -> T where type_is_float(T) || (type_is_simd_vector(T) && type_is_float(type_elem_type(T))) ---
 
@@ -227,6 +227,9 @@ simd_sub  :: proc(a, b: #simd[N]T) -> #simd[N]T ---
 simd_mul  :: proc(a, b: #simd[N]T) -> #simd[N]T ---
 simd_div  :: proc(a, b: #simd[N]T) -> #simd[N]T where type_is_float(T) ---
 
+simd_saturating_add  :: proc(a, b: #simd[N]T) -> #simd[N]T where type_is_integer(T) ---
+simd_saturating_sub  :: proc(a, b: #simd[N]T) -> #simd[N]T where type_is_integer(T) ---
+
 // Keeps Odin's Behaviour
 // (x << y) if y <= mask else 0
 simd_shl :: proc(a: #simd[N]T, b: #simd[N]Unsigned_Integer) -> #simd[N]T ---
@@ -237,9 +240,6 @@ simd_shr :: proc(a: #simd[N]T, b: #simd[N]Unsigned_Integer) -> #simd[N]T ---
 simd_shl_masked :: proc(a: #simd[N]T, b: #simd[N]Unsigned_Integer) -> #simd[N]T ---
 simd_shr_masked :: proc(a: #simd[N]T, b: #simd[N]Unsigned_Integer) -> #simd[N]T ---
 
-simd_add_sat :: proc(a, b: #simd[N]T) -> #simd[N]T ---
-simd_sub_sat :: proc(a, b: #simd[N]T) -> #simd[N]T ---
-
 simd_bit_and     :: proc(a, b: #simd[N]T) -> #simd[N]T ---
 simd_bit_or      :: proc(a, b: #simd[N]T) -> #simd[N]T ---
 simd_bit_xor     :: proc(a, b: #simd[N]T) -> #simd[N]T ---
@@ -268,13 +268,28 @@ simd_lanes_ge :: proc(a, b: #simd[N]T) -> #simd[N]Integer ---
 simd_extract :: proc(a: #simd[N]T, idx: uint) -> T ---
 simd_replace :: proc(a: #simd[N]T, idx: uint, elem: T) -> #simd[N]T ---
 
-simd_reduce_add_ordered :: proc(a: #simd[N]T) -> T ---
-simd_reduce_mul_ordered :: proc(a: #simd[N]T) -> T ---
-simd_reduce_min         :: proc(a: #simd[N]T) -> T ---
-simd_reduce_max         :: proc(a: #simd[N]T) -> T ---
-simd_reduce_and         :: proc(a: #simd[N]T) -> T ---
-simd_reduce_or          :: proc(a: #simd[N]T) -> T ---
-simd_reduce_xor         :: proc(a: #simd[N]T) -> T ---
+simd_reduce_add_ordered :: proc(a: #simd[N]T) -> T where type_is_integer(T) || type_is_float(T)---
+simd_reduce_mul_ordered :: proc(a: #simd[N]T) -> T where type_is_integer(T) || type_is_float(T)---
+simd_reduce_min         :: proc(a: #simd[N]T) -> T where type_is_integer(T) || type_is_float(T)---
+simd_reduce_max         :: proc(a: #simd[N]T) -> T where type_is_integer(T) || type_is_float(T)---
+simd_reduce_and         :: proc(a: #simd[N]T) -> T where type_is_integer(T) || type_is_float(T)---
+simd_reduce_or          :: proc(a: #simd[N]T) -> T where type_is_integer(T) || type_is_float(T)---
+simd_reduce_xor         :: proc(a: #simd[N]T) -> T where type_is_integer(T) || type_is_float(T)---
+
+simd_reduce_any         :: proc(a: #simd[N]T) -> T where type_is_boolean(T) ---
+simd_reduce_all         :: proc(a: #simd[N]T) -> T where type_is_boolean(T) ---
+
+
+simd_gather       :: proc(ptr: #simd[N]rawptr, val: #simd[N]T, mask: #simd[N]U) -> #simd[N]T where type_is_integer(U) || type_is_boolean(U) ---
+simd_scatter      :: proc(ptr: #simd[N]rawptr, val: #simd[N]T, mask: #simd[N]U)              where type_is_integer(U) || type_is_boolean(U) ---
+
+simd_masked_load  :: proc(ptr: rawptr, val: #simd[N]T, mask: #simd[N]U) -> #simd[N]T where type_is_integer(U) || type_is_boolean(U) ---
+simd_masked_store :: proc(ptr: rawptr, val: #simd[N]T, mask: #simd[N]U)              where type_is_integer(U) || type_is_boolean(U) ---
+
+simd_masked_expand_load    :: proc(ptr: rawptr, val: #simd[N]T, mask: #simd[N]U) -> #simd[N]T where type_is_integer(U) || type_is_boolean(U) ---
+simd_masked_compress_store :: proc(ptr: rawptr, val: #simd[N]T, mask: #simd[N]U)              where type_is_integer(U) || type_is_boolean(U) ---
+
+
 
 simd_shuffle :: proc(a, b: #simd[N]T, indices: ..int) -> #simd[len(indices)]T ---
 simd_select  :: proc(cond: #simd[N]boolean_or_integer, true, false: #simd[N]T) -> #simd[N]T ---
@@ -288,11 +303,11 @@ simd_nearest :: proc(a: #simd[N]any_float) -> #simd[N]any_float ---
 
 simd_to_bits :: proc(v: #simd[N]T) -> #simd[N]Integer where size_of(T) == size_of(Integer), type_is_unsigned(Integer) ---
 
-// equivalent a swizzle with descending indices, e.g. reserve(a, 3, 2, 1, 0)
-simd_reverse :: proc(a: #simd[N]T) -> #simd[N]T ---
+// equivalent to a swizzle with descending indices, e.g. reserve(a, 3, 2, 1, 0)
+simd_lanes_reverse :: proc(a: #simd[N]T) -> #simd[N]T ---
 
-simd_rotate_left  :: proc(a: #simd[N]T, $offset: int) -> #simd[N]T ---
-simd_rotate_right :: proc(a: #simd[N]T, $offset: int) -> #simd[N]T ---
+simd_lanes_rotate_left  :: proc(a: #simd[N]T, $offset: int) -> #simd[N]T ---
+simd_lanes_rotate_right :: proc(a: #simd[N]T, $offset: int) -> #simd[N]T ---
 
 // Checks if the current target supports the given target features.
 //

+ 3 - 2
base/runtime/core.odin

@@ -525,11 +525,12 @@ Raw_Quaternion256_Vector_Scalar :: struct {vector: [3]f64, scalar: f64}
 		Linux,
 		Essence,
 		FreeBSD,
-		Haiku,
 		OpenBSD,
 		NetBSD,
+		Haiku,
 		WASI,
 		JS,
+		Orca,
 		Freestanding,
 	}
 */
@@ -589,7 +590,7 @@ Odin_Platform_Subtarget_Type :: type_of(ODIN_PLATFORM_SUBTARGET)
 		Memory  = 1,
 		Thread  = 2,
 	}
-	Odin_Sanitizer_Flags :: distinct bitset[Odin_Sanitizer_Flag; u32]
+	Odin_Sanitizer_Flags :: distinct bit_set[Odin_Sanitizer_Flag; u32]
 
 	ODIN_SANITIZER_FLAGS // is a constant
 */

+ 15 - 8
base/runtime/heap_allocator.odin

@@ -19,12 +19,15 @@ heap_allocator_proc :: proc(allocator_data: rawptr, mode: Allocator_Mode,
 	// the pointer we return to the user.
 	//
 
-	aligned_alloc :: proc(size, alignment: int, old_ptr: rawptr = nil, zero_memory := true) -> ([]byte, Allocator_Error) {
+	aligned_alloc :: proc(size, alignment: int, old_ptr: rawptr, old_size: int, zero_memory := true) -> ([]byte, Allocator_Error) {
 		a := max(alignment, align_of(rawptr))
 		space := size + a - 1
 
 		allocated_mem: rawptr
-		if old_ptr != nil {
+
+		force_copy := old_ptr != nil && a > align_of(rawptr)
+
+		if !force_copy && old_ptr != nil {
 			original_old_ptr := ([^]rawptr)(old_ptr)[-1]
 			allocated_mem = heap_resize(original_old_ptr, space+size_of(rawptr))
 		} else {
@@ -36,12 +39,19 @@ heap_allocator_proc :: proc(allocator_data: rawptr, mode: Allocator_Mode,
 		aligned_ptr := (ptr - 1 + uintptr(a)) & -uintptr(a)
 		diff := int(aligned_ptr - ptr)
 		if (size + diff) > space || allocated_mem == nil {
+			aligned_free(old_ptr)
+			aligned_free(allocated_mem)
 			return nil, .Out_Of_Memory
 		}
 
 		aligned_mem = rawptr(aligned_ptr)
 		([^]rawptr)(aligned_mem)[-1] = allocated_mem
 
+		if force_copy {
+			mem_copy_non_overlapping(aligned_mem, old_ptr, old_size)
+			aligned_free(old_ptr)
+		}
+
 		return byte_slice(aligned_mem, size), nil
 	}
 
@@ -53,10 +63,10 @@ heap_allocator_proc :: proc(allocator_data: rawptr, mode: Allocator_Mode,
 
 	aligned_resize :: proc(p: rawptr, old_size: int, new_size: int, new_alignment: int, zero_memory := true) -> (new_memory: []byte, err: Allocator_Error) {
 		if p == nil {
-			return nil, nil
+			return aligned_alloc(new_size, new_alignment, nil, old_size, zero_memory)
 		}
 
-		new_memory = aligned_alloc(new_size, new_alignment, p, zero_memory) or_return
+		new_memory = aligned_alloc(new_size, new_alignment, p, old_size, zero_memory) or_return
 
 		// NOTE: heap_resize does not zero the new memory, so we do it
 		if zero_memory && new_size > old_size {
@@ -68,7 +78,7 @@ heap_allocator_proc :: proc(allocator_data: rawptr, mode: Allocator_Mode,
 
 	switch mode {
 	case .Alloc, .Alloc_Non_Zeroed:
-		return aligned_alloc(size, alignment, nil, mode == .Alloc)
+		return aligned_alloc(size, alignment, nil, 0, mode == .Alloc)
 
 	case .Free:
 		aligned_free(old_memory)
@@ -77,9 +87,6 @@ heap_allocator_proc :: proc(allocator_data: rawptr, mode: Allocator_Mode,
 		return nil, .Mode_Not_Implemented
 
 	case .Resize, .Resize_Non_Zeroed:
-		if old_memory == nil {
-			return aligned_alloc(size, alignment, nil, mode == .Resize)
-		}
 		return aligned_resize(old_memory, old_size, size, alignment, mode == .Resize)
 
 	case .Query_Features:

+ 1 - 1
build_odin.sh

@@ -95,7 +95,7 @@ Linux)
 	LDFLAGS="$LDFLAGS -ldl $($LLVM_CONFIG --libs core native --system-libs --libfiles)"
 	# Copy libLLVM*.so into current directory for linking
 	# NOTE: This is needed by the Linux release pipeline!
-	cp $(readlink -f $($LLVM_CONFIG --libfiles)) ./
+	# cp $(readlink -f $($LLVM_CONFIG --libfiles)) ./
 	LDFLAGS="$LDFLAGS -Wl,-rpath=\$ORIGIN"
 	;;
 OpenBSD)

+ 3 - 3
core/crypto/rand_windows.odin

@@ -8,9 +8,9 @@ HAS_RAND_BYTES :: true
 
 @(private)
 _rand_bytes :: proc(dst: []byte) {
-	ret := (os.Errno)(win32.BCryptGenRandom(nil, raw_data(dst), u32(len(dst)), win32.BCRYPT_USE_SYSTEM_PREFERRED_RNG))
-	if ret != os.ERROR_NONE {
-		switch ret {
+	ret := os.Platform_Error(win32.BCryptGenRandom(nil, raw_data(dst), u32(len(dst)), win32.BCRYPT_USE_SYSTEM_PREFERRED_RNG))
+	if ret != nil {
+		#partial switch ret {
 		case os.ERROR_INVALID_HANDLE:
 			// The handle to the first parameter is invalid.
 			// This should not happen here, since we explicitly pass nil to it

+ 3 - 3
core/encoding/csv/example.odin

@@ -38,9 +38,9 @@ iterate_csv_from_stream :: proc(filename: string) {
 	r.reuse_record_buffer = true // Without it you have to each of the fields within it
 	defer csv.reader_destroy(&r)
 
-	handle, errno := os.open(filename)
-	if errno != os.ERROR_NONE {
-		fmt.printfln("Error opening file: %v", filename)
+	handle, err := os.open(filename)
+	if err != nil {
+		fmt.eprintfln("Error opening file: %v", filename)
 		return
 	}
 	defer os.close(handle)

+ 1 - 1
core/flags/errors.odin

@@ -28,7 +28,7 @@ Parse_Error :: struct {
 // Provides more granular information than what just a string could hold.
 Open_File_Error :: struct {
 	filename: string,
-	errno: os.Errno,
+	errno: os.Error,
 	mode: int,
 	perms: int,
 }

+ 2 - 2
core/flags/internal_rtti.odin

@@ -254,8 +254,8 @@ parse_and_set_pointer_by_named_type :: proc(ptr: rawptr, str: string, data_type:
 		}
 
 		handle, errno := os.open(str, mode, perms)
-		if errno != 0 {
-			// NOTE(Feoramund): os.Errno is system-dependent, and there's
+		if errno != nil {
+			// NOTE(Feoramund): os.Error is system-dependent, and there's
 			// currently no good way to translate them all into strings.
 			//
 			// The upcoming `os2` package will hopefully solve this.

+ 65 - 2
core/image/common.odin

@@ -112,7 +112,8 @@ Image_Option:
 
 	`.alpha_drop_if_present`
 		If the image has an alpha channel, drop it.
-		You may want to use `.alpha_premultiply` in this case.
+		You may want to use `.alpha_
+		tiply` in this case.
 
 		NOTE: For PNG, this also skips handling of the tRNS chunk, if present,
 		unless you select `alpha_premultiply`.
@@ -587,6 +588,32 @@ Channel :: enum u8 {
 	A = 4,
 }
 
+// Take a slice of pixels (`[]RGBA_Pixel`, etc), and return an `Image`
+// Don't call `destroy` on the resulting `Image`. Instead, delete the original `pixels` slice.
+pixels_to_image :: proc(pixels: [][$N]$E, width: int, height: int) -> (img: Image, ok: bool) where E == u8 || E == u16, N >= 1, N <= 4 {
+	if len(pixels) != width * height {
+		return {}, false
+	}
+
+	img.height   = height
+	img.width    = width
+	img.depth    = 8 when E == u8 else 16
+	img.channels = N
+
+	s := transmute(runtime.Raw_Slice)pixels
+	d := runtime.Raw_Dynamic_Array{
+		data = s.data,
+		len  = s.len * size_of(E) * N,
+		cap  = s.len * size_of(E) * N,
+		allocator = runtime.nil_allocator(),
+	}
+	img.pixels = bytes.Buffer{
+		buf = transmute([dynamic]u8)d,
+	}
+
+	return img, true
+}
+
 // When you have an RGB(A) image, but want a particular channel.
 return_single_channel :: proc(img: ^Image, channel: Channel) -> (res: ^Image, ok: bool) {
 	// Were we actually given a valid image?
@@ -1266,7 +1293,7 @@ blend_single_channel :: #force_inline proc(fg, alpha, bg: $T) -> (res: T) where
 	return T(c & (MAX - 1))
 }
 
-blend_pixel :: #force_inline proc(fg: [$N]$T, alpha: T, bg: [N]T) -> (res: [N]T) where (T == u8 || T == u16), N >= 1 && N <= 4 {
+blend_pixel :: #force_inline proc(fg: [$N]$T, alpha: T, bg: [N]T) -> (res: [N]T) where (T == u8 || T == u16), N >= 1, N <= 4 {
 	MAX :: 256 when T == u8 else 65536
 
 	when N == 1 {
@@ -1293,6 +1320,42 @@ blend_pixel :: #force_inline proc(fg: [$N]$T, alpha: T, bg: [N]T) -> (res: [N]T)
 }
 blend :: proc{blend_single_channel, blend_pixel}
 
+// For all pixels of the image, multiplies R, G and B by Alpha. This is useful mainly for games rendering anti-aliased transparent sprites.
+// Grayscale with alpha images are supported as well.
+// Note that some image formats like QOI explicitly do NOT support premultiplied alpha, so you will end up with a non-standard file.
+premultiply_alpha :: proc(img: ^Image) -> (ok: bool) {
+	switch {
+	case img.channels == 2 && img.depth == 8:
+		pixels := mem.slice_data_cast([]GA_Pixel, img.pixels.buf[:])
+		for &pixel in pixels {
+			pixel.r = u8(u32(pixel.r) * u32(pixel.g) / 0xFF)
+		}
+		return true
+	case img.channels == 2 && img.depth == 16:
+		pixels := mem.slice_data_cast([]GA_Pixel_16, img.pixels.buf[:])
+		for &pixel in pixels {
+			pixel.r = u16(u32(pixel.r) * u32(pixel.g) / 0xFFFF)
+		}
+		return true
+	case img.channels == 4 && img.depth == 8:
+		pixels := mem.slice_data_cast([]RGBA_Pixel, img.pixels.buf[:])
+		for &pixel in pixels {
+			pixel.r = u8(u32(pixel.r) * u32(pixel.a) / 0xFF)
+			pixel.g = u8(u32(pixel.g) * u32(pixel.a) / 0xFF)
+			pixel.b = u8(u32(pixel.b) * u32(pixel.a) / 0xFF)
+		}
+		return true
+	case img.channels == 4 && img.depth == 16:
+		pixels := mem.slice_data_cast([]RGBA_Pixel_16, img.pixels.buf[:])
+		for &pixel in pixels {
+			pixel.r = u16(u32(pixel.r) * u32(pixel.a) / 0xFFFF)
+			pixel.g = u16(u32(pixel.g) * u32(pixel.a) / 0xFFFF)
+			pixel.b = u16(u32(pixel.b) * u32(pixel.a) / 0xFFFF)
+		}
+		return true
+	case: return false
+	}
+}
 
 // Replicates grayscale values into RGB(A) 8- or 16-bit images as appropriate.
 // Returns early with `false` if already an RGB(A) image.

+ 1 - 1
core/image/general_os.odin

@@ -27,7 +27,7 @@ which :: proc{
 
 which_file :: proc(path: string) -> Which_File_Type {
 	f, err := os.open(path)
-	if err != 0 {
+	if err != nil {
 		return .Unknown
 	}
 	header: [128]byte

+ 1 - 1
core/image/png/example.odin

@@ -213,7 +213,7 @@ write_image_as_ppm :: proc(filename: string, image: ^image.Image) -> (success: b
 	}
 
 	fd, err := open(filename, flags, mode)
-	if err != 0 {
+	if err != nil {
 		return false
 	}
 	defer close(fd)

+ 1 - 1
core/image/png/helpers.odin

@@ -450,7 +450,7 @@ when false {
 		}
 
 		fd, fderr := open(filename, flags, mode)
-		if fderr != 0 {
+		if fderr != nil {
 			return .Cannot_Open_File
 		}
 		defer close(fd)

+ 2 - 1
core/math/linalg/general.odin

@@ -275,7 +275,8 @@ to_ptr :: proc{vector_to_ptr, matrix_to_ptr}
 vector_angle_between :: proc "contextless" (a, b: $V/[$N]$E) -> E {
 	a0 := normalize0(a)
 	b0 := normalize0(b)
-	return math.acos(dot(a0, b0))
+	d  := clamp(dot(a0, b0), -1, +1)
+	return math.acos(d)
 }
 quaternion64_angle_between :: proc "contextless" (a, b: $Q/quaternion64) -> f16 {
 	c := normalize0(conj(a) * b)

+ 1 - 1
core/math/rand/rand.odin

@@ -58,7 +58,7 @@ Example:
 	import "core:fmt"
 
 	set_global_seed_example :: proc() {
-		rand.set_global_seed(1)
+		rand.reset(1)
 		fmt.println(rand.uint64())
 	}
 

+ 2 - 2
core/mem/virtual/file.odin

@@ -24,7 +24,7 @@ map_file :: proc{
 
 map_file_from_path :: proc(filename: string, flags: Map_File_Flags) -> (data: []byte, error: Map_File_Error) {
 	fd, err := os.open(filename, os.O_RDWR)
-	if err != 0 {
+	if err != nil {
 		return nil, .Open_Failure
 	}
 	defer os.close(fd)
@@ -34,7 +34,7 @@ map_file_from_path :: proc(filename: string, flags: Map_File_Flags) -> (data: []
 
 map_file_from_file_descriptor :: proc(fd: uintptr, flags: Map_File_Flags) -> (data: []byte, error: Map_File_Error) {
 	size, os_err := os.file_size(os.Handle(fd))
-	if os_err != 0 {
+	if os_err != nil {
 		return nil, .Stat_Failure
 	}
 	if size < 0 {

+ 40 - 32
core/net/socket_darwin.odin

@@ -53,9 +53,9 @@ _create_socket :: proc(family: Address_Family, protocol: Socket_Protocol) -> (so
 		unreachable()
 	}
 
-	sock, ok := os.socket(c_family, c_type, c_protocol)
-	if ok != os.ERROR_NONE {
-		err = Create_Socket_Error(ok)
+	sock, sock_err := os.socket(c_family, c_type, c_protocol)
+	if sock_err != nil {
+		err = Create_Socket_Error(os.is_platform_error(sock_err) or_else -1)
 		return
 	}
 
@@ -84,8 +84,8 @@ _dial_tcp_from_endpoint :: proc(endpoint: Endpoint, options := default_tcp_optio
 
 	sockaddr := _endpoint_to_sockaddr(endpoint)
 	res := os.connect(os.Socket(skt), (^os.SOCKADDR)(&sockaddr), i32(sockaddr.len))
-	if res != os.ERROR_NONE {
-		err = Dial_Error(res)
+	if res != nil {
+		err = Dial_Error(os.is_platform_error(res) or_else -1)
 		return
 	}
 
@@ -100,11 +100,11 @@ _bind :: proc(skt: Any_Socket, ep: Endpoint) -> (err: Network_Error) {
 	sockaddr := _endpoint_to_sockaddr(ep)
 	s := any_socket_to_socket(skt)
 	res := os.bind(os.Socket(s), (^os.SOCKADDR)(&sockaddr), i32(sockaddr.len))
-	if res != os.ERROR_NONE {
+	if res != nil {
 		if res == os.EACCES && ep.port <= MAX_PRIVILEGED_PORT {
 			err = .Privileged_Port_Without_Root
 		} else {
-			err = Bind_Error(res)
+			err = Bind_Error(os.is_platform_error(res) or_else -1)
 		}
 	}
 	return
@@ -128,8 +128,8 @@ _listen_tcp :: proc(interface_endpoint: Endpoint, backlog := 1000) -> (skt: TCP_
 	bind(sock, interface_endpoint) or_return
 
 	res := os.listen(os.Socket(skt), backlog)
-	if res != os.ERROR_NONE {
-		err = Listen_Error(res)
+	if res != nil {
+		err = Listen_Error(os.is_platform_error(res) or_else -1)
 		return
 	}
 
@@ -141,9 +141,9 @@ _accept_tcp :: proc(sock: TCP_Socket, options := default_tcp_options) -> (client
 	sockaddr: os.SOCKADDR_STORAGE_LH
 	sockaddrlen := c.int(size_of(sockaddr))
 
-	client_sock, ok := os.accept(os.Socket(sock), cast(^os.SOCKADDR) &sockaddr, &sockaddrlen)
-	if ok != os.ERROR_NONE {
-		err = Accept_Error(ok)
+	client_sock, client_sock_err := os.accept(os.Socket(sock), cast(^os.SOCKADDR) &sockaddr, &sockaddrlen)
+	if client_sock_err != nil {
+		err = Accept_Error(os.is_platform_error(client_sock_err) or_else -1)
 		return
 	}
 	client = TCP_Socket(client_sock)
@@ -162,9 +162,9 @@ _recv_tcp :: proc(skt: TCP_Socket, buf: []byte) -> (bytes_read: int, err: Networ
 	if len(buf) <= 0 {
 		return
 	}
-	res, ok := os.recv(os.Socket(skt), buf, 0)
-	if ok != os.ERROR_NONE {
-		err = TCP_Recv_Error(ok)
+	res, res_err := os.recv(os.Socket(skt), buf, 0)
+	if res_err != nil {
+		err = TCP_Recv_Error(os.is_platform_error(res_err) or_else -1)
 		return
 	}
 	return int(res), nil
@@ -178,9 +178,9 @@ _recv_udp :: proc(skt: UDP_Socket, buf: []byte) -> (bytes_read: int, remote_endp
 
 	from: os.SOCKADDR_STORAGE_LH
 	fromsize := c.int(size_of(from))
-	res, ok := os.recvfrom(os.Socket(skt), buf, 0, cast(^os.SOCKADDR) &from, &fromsize)
-	if ok != os.ERROR_NONE {
-		err = UDP_Recv_Error(ok)
+	res, res_err := os.recvfrom(os.Socket(skt), buf, 0, cast(^os.SOCKADDR) &from, &fromsize)
+	if res_err != nil {
+		err = UDP_Recv_Error(os.is_platform_error(res_err) or_else -1)
 		return
 	}
 
@@ -194,9 +194,13 @@ _send_tcp :: proc(skt: TCP_Socket, buf: []byte) -> (bytes_written: int, err: Net
 	for bytes_written < len(buf) {
 		limit := min(int(max(i32)), len(buf) - bytes_written)
 		remaining := buf[bytes_written:][:limit]
-		res, ok := os.send(os.Socket(skt), remaining, 0)
-		if ok != os.ERROR_NONE {
-			err = TCP_Send_Error(ok)
+		res, res_err := os.send(os.Socket(skt), remaining, os.MSG_NOSIGNAL)
+		if res_err == os.EPIPE {
+			// EPIPE arises if the socket has been closed remotely.
+			err = TCP_Send_Error.Connection_Closed
+			return
+		} else if res_err != nil {
+			err = TCP_Send_Error(os.is_platform_error(res_err) or_else -1)
 			return
 		}
 		bytes_written += int(res)
@@ -210,9 +214,13 @@ _send_udp :: proc(skt: UDP_Socket, buf: []byte, to: Endpoint) -> (bytes_written:
 	for bytes_written < len(buf) {
 		limit := min(1<<31, len(buf) - bytes_written)
 		remaining := buf[bytes_written:][:limit]
-		res, ok := os.sendto(os.Socket(skt), remaining, 0, cast(^os.SOCKADDR)&toaddr, i32(toaddr.len))
-		if ok != os.ERROR_NONE {
-			err = UDP_Send_Error(ok)
+		res, res_err := os.sendto(os.Socket(skt), remaining, os.MSG_NOSIGNAL, cast(^os.SOCKADDR)&toaddr, i32(toaddr.len))
+		if res_err == os.EPIPE {
+			// EPIPE arises if the socket has been closed remotely.
+			err = UDP_Send_Error.Not_Socket
+			return
+		} else if res_err != nil {
+			err = UDP_Send_Error(os.is_platform_error(res_err) or_else -1)
 			return
 		}
 		bytes_written += int(res)
@@ -224,8 +232,8 @@ _send_udp :: proc(skt: UDP_Socket, buf: []byte, to: Endpoint) -> (bytes_written:
 _shutdown :: proc(skt: Any_Socket, manner: Shutdown_Manner) -> (err: Network_Error) {
 	s := any_socket_to_socket(skt)
 	res := os.shutdown(os.Socket(s), int(manner))
-	if res != os.ERROR_NONE {
-		return Shutdown_Error(res)
+	if res != nil {
+		return Shutdown_Error(os.is_platform_error(res) or_else -1)
 	}
 	return
 }
@@ -302,8 +310,8 @@ _set_option :: proc(s: Any_Socket, option: Socket_Option, value: any, loc := #ca
 
 	skt := any_socket_to_socket(s)
 	res := os.setsockopt(os.Socket(skt), int(level), int(option), ptr, len)
-	if res != os.ERROR_NONE {
-		return Socket_Option_Error(res)
+	if res != nil {
+		return Socket_Option_Error(os.is_platform_error(res) or_else -1)
 	}
 
 	return nil
@@ -314,8 +322,8 @@ _set_blocking :: proc(socket: Any_Socket, should_block: bool) -> (err: Network_E
 	socket := any_socket_to_socket(socket)
 
 	flags, getfl_err := os.fcntl(int(socket), os.F_GETFL, 0)
-	if getfl_err != os.ERROR_NONE {
-		return Set_Blocking_Error(getfl_err)
+	if getfl_err != nil {
+		return Set_Blocking_Error(os.is_platform_error(getfl_err) or_else -1)
 	}
 
 	if should_block {
@@ -325,8 +333,8 @@ _set_blocking :: proc(socket: Any_Socket, should_block: bool) -> (err: Network_E
 	}
 
 	_, setfl_err := os.fcntl(int(socket), os.F_SETFL, flags)
-	if setfl_err != os.ERROR_NONE {
-		return Set_Blocking_Error(setfl_err)
+	if setfl_err != nil {
+		return Set_Blocking_Error(os.is_platform_error(setfl_err) or_else -1)
 	}
 
 	return nil

+ 1 - 1
core/odin/ast/ast.odin

@@ -757,7 +757,7 @@ Array_Type :: struct {
 	using node: Expr,
 	open:  tokenizer.Pos,
 	tag:   ^Expr,
-	len:   ^Expr, // Ellipsis node for [?]T array types, nil for slice types
+	len:   ^Expr, // Unary_Expr node for [?]T array types, nil for slice types
 	close: tokenizer.Pos,
 	elem:  ^Expr,
 }

+ 2 - 1
core/odin/parser/parser.odin

@@ -1778,6 +1778,7 @@ parse_var_type :: proc(p: ^Parser, flags: ast.Field_Flags) -> ^ast.Expr {
 			type = ast.new(ast.Bad_Expr, tok.pos, end_pos(tok))
 		}
 		e := ast.new(ast.Ellipsis, type.pos, type)
+		e.tok = tok.kind
 		e.expr = type
 		return e
 	}
@@ -2844,7 +2845,7 @@ parse_operand :: proc(p: ^Parser, lhs: bool) -> ^ast.Expr {
 
 		close := expect_closing_brace_of_field_list(p)
 
-		bf := ast.new(ast.Bit_Field_Type, tok.pos, close.pos)
+		bf := ast.new(ast.Bit_Field_Type, tok.pos, end_pos(close))
 
 		bf.tok_pos      = tok.pos
 		bf.backing_type = backing_type

+ 0 - 73
core/os/dir_bsd.odin

@@ -1,73 +0,0 @@
-//+build freebsd, netbsd
-package os
-
-import "core:mem"
-
-read_dir :: proc(fd: Handle, n: int, allocator := context.allocator) -> (fi: []File_Info, err: Errno) {
-	dirp: Dir
-	dirp, err = _fdopendir(fd)
-	if err != ERROR_NONE {
-		return
-	}
-
-	defer _closedir(dirp)
-
-	dirpath: string
-	dirpath, err = absolute_path_from_handle(fd)
-
-	if err != ERROR_NONE {
-		return
-	}
-
-	defer delete(dirpath)
-
-	n := n
-	size := n
-	if n <= 0 {
-		n = -1
-		size = 100
-	}
-
-	dfi := make([dynamic]File_Info, 0, size, allocator)
-
-	for {
-		entry: Dirent
-		end_of_stream: bool
-		entry, err, end_of_stream = _readdir(dirp)
-		if err != ERROR_NONE {
-			for fi_ in dfi {
-				file_info_delete(fi_, allocator)
-			}
-			delete(dfi)
-			return
-		} else if end_of_stream {
-			break
-		}
-
-		fi_: File_Info
-		filename := cast(string)(transmute(cstring)mem.Raw_Cstring{ data = &entry.name[0] })
-
-		if filename == "." || filename == ".." {
-			continue
-		}
-
-		fullpath := make([]byte, len(dirpath)+1+len(filename), context.temp_allocator)
-		copy(fullpath, dirpath)
-		copy(fullpath[len(dirpath):], "/")
-		copy(fullpath[len(dirpath)+1:], filename)
-		defer delete(fullpath, context.temp_allocator)
-
-		fi_, err = stat(string(fullpath), allocator)
-		if err != ERROR_NONE {
-			for fi__ in dfi {
-				file_info_delete(fi__, allocator)
-			}
-			delete(dfi)
-			return
-		}
-
-		append(&dfi, fi_)
-	}
-
-	return dfi[:], ERROR_NONE
-}

+ 0 - 69
core/os/dir_darwin.odin

@@ -1,69 +0,0 @@
-package os
-
-import "core:strings"
-import "core:mem"
-
-read_dir :: proc(fd: Handle, n: int, allocator := context.allocator) -> (fi: []File_Info, err: Errno) {
-	dirp: Dir
-	dirp, err = _fdopendir(fd)
-	if err != ERROR_NONE {
-		return
-	}
-
-	defer _closedir(dirp)
-
-	dirpath: string
-	dirpath, err = absolute_path_from_handle(fd)
-	if err != ERROR_NONE {
-		return
-	}
-
-	defer delete(dirpath)
-
-	n := n
-	size := n
-	if n <= 0 {
-		n = -1
-		size = 100
-	}
-
-	dfi := make([dynamic]File_Info, 0, size, allocator)
-
-	for {
-		entry: Dirent
-		end_of_stream: bool
-		entry, err, end_of_stream = _readdir(dirp)
-		if err != ERROR_NONE {
-			for fi_ in dfi {
-				file_info_delete(fi_, allocator)
-			}
-			delete(dfi)
-			return
-		} else if end_of_stream {
-			break
-		}
-
-		fi_: File_Info
-		filename := cast(string)(transmute(cstring)mem.Raw_Cstring{ data = &entry.name[0] })
-
-		if filename == "." || filename == ".." {
-			continue
-		}
-
-		fullpath := strings.join( []string{ dirpath, filename }, "/", context.temp_allocator)
-		defer delete(fullpath, context.temp_allocator)
-
-		fi_, err = stat(fullpath, allocator)
-		if err != ERROR_NONE {
-			for fi__ in dfi {
-				file_info_delete(fi__, allocator)
-			}
-			delete(dfi)
-			return
-		}
-
-		append(&dfi, fi_)
-	}
-
-	return dfi[:], ERROR_NONE
-}

+ 0 - 72
core/os/dir_linux.odin

@@ -1,72 +0,0 @@
-package os
-
-import "core:strings"
-import "core:mem"
-import "base:runtime"
-
-read_dir :: proc(fd: Handle, n: int, allocator := context.allocator) -> (fi: []File_Info, err: Errno) {
-	dirp: Dir
-	dirp, err = _fdopendir(fd)
-	if err != ERROR_NONE {
-		return
-	}
-
-	defer _closedir(dirp)
-
-	dirpath: string
-	dirpath, err = absolute_path_from_handle(fd)
-
-	if err != ERROR_NONE {
-		return
-	}
-
-	defer delete(dirpath)
-
-	n := n
-	size := n
-	if n <= 0 {
-		n = -1
-		size = 100
-	}
-
-	dfi := make([dynamic]File_Info, 0, size, allocator)
-
-	for {
-		entry: Dirent
-		end_of_stream: bool
-		entry, err, end_of_stream = _readdir(dirp)
-		if err != ERROR_NONE {
-			for fi_ in dfi {
-				file_info_delete(fi_, allocator)
-			}
-			delete(dfi)
-			return
-		} else if end_of_stream {
-			break
-		}
-
-		fi_: File_Info
-		filename := cast(string)(transmute(cstring)mem.Raw_Cstring{ data = &entry.name[0] })
-
-		if filename == "." || filename == ".." {
-			continue
-		}
-
-		runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD(ignore = context.temp_allocator == allocator)
-		fullpath := strings.join( []string{ dirpath, filename }, "/", context.temp_allocator)
-		defer delete(fullpath, context.temp_allocator)
-
-		fi_, err = stat(fullpath, allocator)
-		if err != ERROR_NONE {
-			for fi__ in dfi {
-				file_info_delete(fi__, allocator)
-			}
-			delete(dfi)
-			return
-		}
-
-		append(&dfi, fi_)
-	}
-
-	return dfi[:], ERROR_NONE
-}

+ 0 - 71
core/os/dir_openbsd.odin

@@ -1,71 +0,0 @@
-package os
-
-import "core:strings"
-import "core:mem"
-
-read_dir :: proc(fd: Handle, n: int, allocator := context.allocator) -> (fi: []File_Info, err: Errno) {
-	dirp: Dir
-	dirp, err = _fdopendir(fd)
-	if err != ERROR_NONE {
-		return
-	}
-
-	defer _closedir(dirp)
-
-	// XXX OpenBSD
-	dirpath: string
-	dirpath, err = absolute_path_from_handle(fd)
-
-	if err != ERROR_NONE {
-		return
-	}
-
-	defer delete(dirpath)
-
-	n := n
-	size := n
-	if n <= 0 {
-		n = -1
-		size = 100
-	}
-
-	dfi := make([dynamic]File_Info, 0, size, allocator)
-
-	for {
-		entry: Dirent
-		end_of_stream: bool
-		entry, err, end_of_stream = _readdir(dirp)
-		if err != ERROR_NONE {
-			for fi_ in dfi {
-				file_info_delete(fi_, allocator)
-			}
-			delete(dfi)
-			return
-		} else if end_of_stream {
-			break
-		}
-
-		fi_: File_Info
-		filename := cast(string)(transmute(cstring)mem.Raw_Cstring{ data = &entry.name[0] })
-
-		if filename == "." || filename == ".." {
-			continue
-		}
-
-		fullpath := strings.join( []string{ dirpath, filename }, "/", context.temp_allocator)
-		defer delete(fullpath, context.temp_allocator)
-
-		fi_, err = stat(fullpath, allocator)
-		if err != ERROR_NONE {
-			for fi__ in dfi {
-				file_info_delete(fi__, allocator)
-			}
-			delete(dfi)
-			return
-		}
-
-		append(&dfi, fi_)
-	}
-
-	return dfi[:], ERROR_NONE
-}

+ 62 - 0
core/os/dir_unix.odin

@@ -0,0 +1,62 @@
+//+build darwin, linux, netbsd, freebsd, openbsd
+package os
+
+import "core:strings"
+
+@(require_results)
+read_dir :: proc(fd: Handle, n: int, allocator := context.allocator) -> (fi: []File_Info, err: Error) {
+	dirp := _fdopendir(fd) or_return
+	defer _closedir(dirp)
+
+	dirpath := absolute_path_from_handle(fd) or_return
+	defer delete(dirpath)
+
+	n := n
+	size := n
+	if n <= 0 {
+		n = -1
+		size = 100
+	}
+
+	dfi := make([dynamic]File_Info, 0, size, allocator) or_return
+	defer if err != nil {
+		for fi_ in dfi {
+			file_info_delete(fi_, allocator)
+		}
+		delete(dfi)
+	}
+
+	for {
+		entry: Dirent
+		end_of_stream: bool
+		entry, err, end_of_stream = _readdir(dirp)
+		if err != nil {
+			return
+		} else if end_of_stream {
+			break
+		}
+
+		fi_: File_Info
+		filename := string(cstring(&entry.name[0]))
+
+		if filename == "." || filename == ".." {
+			continue
+		}
+
+		fullpath := strings.join({ dirpath, filename }, "/", allocator)
+
+		s: OS_Stat
+		s, err = _lstat(fullpath)
+		if err != nil {
+			delete(fullpath, allocator)
+			return
+		}
+		_fill_file_info_from_stat(&fi_, s)
+		fi_.fullpath = fullpath
+		fi_.name = path_base(fi_.fullpath)
+
+		append(&dfi, fi_)
+	}
+
+	return dfi[:], nil
+}

+ 11 - 10
core/os/dir_windows.odin

@@ -4,7 +4,9 @@ import win32 "core:sys/windows"
 import "core:strings"
 import "base:runtime"
 
-read_dir :: proc(fd: Handle, n: int, allocator := context.allocator) -> (fi: []File_Info, err: Errno) {
+@(require_results)
+read_dir :: proc(fd: Handle, n: int, allocator := context.allocator) -> (fi: []File_Info, err: Error) {
+	@(require_results)
 	find_data_to_file_info :: proc(base_path: string, d: ^win32.WIN32_FIND_DATAW) -> (fi: File_Info) {
 		// Ignore "." and ".."
 		if d.cFileName[0] == '.' && d.cFileName[1] == 0 {
@@ -57,7 +59,7 @@ read_dir :: proc(fd: Handle, n: int, allocator := context.allocator) -> (fi: []F
 
 	dir_fi, _ := file_info_from_get_file_information_by_handle("", h)
 	if !dir_fi.is_dir {
-		return nil, ERROR_FILE_IS_NOT_DIR
+		return nil, .Not_Dir
 	}
 
 	n := n
@@ -68,15 +70,14 @@ read_dir :: proc(fd: Handle, n: int, allocator := context.allocator) -> (fi: []F
 	}
 	runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD(ignore = context.temp_allocator == allocator)
 
-	wpath: []u16
-	wpath, err = cleanpath_from_handle_u16(fd, context.temp_allocator)
-	if len(wpath) == 0 || err != ERROR_NONE {
+	wpath := cleanpath_from_handle_u16(fd, context.temp_allocator) or_return
+	if len(wpath) == 0 {
 		return
 	}
 
-	dfi := make([dynamic]File_Info, 0, size)
+	dfi := make([dynamic]File_Info, 0, size) or_return
 
-	wpath_search := make([]u16, len(wpath)+3, context.temp_allocator)
+	wpath_search := make([]u16, len(wpath)+3, context.temp_allocator) or_return
 	copy(wpath_search, wpath)
 	wpath_search[len(wpath)+0] = '\\'
 	wpath_search[len(wpath)+1] = '*'
@@ -88,7 +89,7 @@ read_dir :: proc(fd: Handle, n: int, allocator := context.allocator) -> (fi: []F
 	find_data := &win32.WIN32_FIND_DATAW{}
 	find_handle := win32.FindFirstFileW(raw_data(wpath_search), find_data)
 	if find_handle == win32.INVALID_HANDLE_VALUE {
-		err = Errno(win32.GetLastError())
+		err = get_last_error()
 		return dfi[:], err
 	}
 	defer win32.FindClose(find_handle)
@@ -101,7 +102,7 @@ read_dir :: proc(fd: Handle, n: int, allocator := context.allocator) -> (fi: []F
 		}
 
 		if !win32.FindNextFileW(find_handle, find_data) {
-			e := Errno(win32.GetLastError())
+			e := get_last_error()
 			if e == ERROR_NO_MORE_FILES {
 				break
 			}
@@ -109,5 +110,5 @@ read_dir :: proc(fd: Handle, n: int, allocator := context.allocator) -> (fi: []F
 		}
 	}
 
-	return dfi[:], ERROR_NONE
+	return dfi[:], nil
 }

+ 19 - 19
core/os/env_windows.odin

@@ -7,27 +7,22 @@ import "base:runtime"
 // If the variable is found in the environment the value (which can be empty) is returned and the boolean is true
 // Otherwise the returned value will be empty and the boolean will be false
 // NOTE: the value will be allocated with the supplied allocator
+@(require_results)
 lookup_env :: proc(key: string, allocator := context.allocator) -> (value: string, found: bool) {
 	if key == "" {
 		return
 	}
 	wkey := win32.utf8_to_wstring(key)
 	n := win32.GetEnvironmentVariableW(wkey, nil, 0)
-	if n == 0 {
-		err := win32.GetLastError()
-		if err == u32(ERROR_ENVVAR_NOT_FOUND) {
-			return "", false
-		}
+	if n == 0 && get_last_error() == ERROR_ENVVAR_NOT_FOUND {
+		return "", false
 	}
 	runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD(ignore = context.temp_allocator == allocator)
 
-	b := make([dynamic]u16, n, context.temp_allocator)
+	b, _ := make([dynamic]u16, n, context.temp_allocator)
 	n = win32.GetEnvironmentVariableW(wkey, raw_data(b), u32(len(b)))
-	if n == 0 {
-		err := win32.GetLastError()
-		if err == u32(ERROR_ENVVAR_NOT_FOUND) {
-			return "", false
-		}
+	if n == 0 && get_last_error() == ERROR_ENVVAR_NOT_FOUND {
+		return "", false
 	}
 	value, _ = win32.utf16_to_utf8(b[:n], allocator)
 	found = true
@@ -39,41 +34,46 @@ lookup_env :: proc(key: string, allocator := context.allocator) -> (value: strin
 // It returns the value, which will be empty if the variable is not present
 // To distinguish between an empty value and an unset value, use lookup_env
 // NOTE: the value will be allocated with the supplied allocator
+@(require_results)
 get_env :: proc(key: string, allocator := context.allocator) -> (value: string) {
 	value, _ = lookup_env(key, allocator)
 	return
 }
 
 // set_env sets the value of the environment variable named by the key
-set_env :: proc(key, value: string) -> Errno {
+set_env :: proc(key, value: string) -> Error {
 	k := win32.utf8_to_wstring(key)
 	v := win32.utf8_to_wstring(value)
 
 	if !win32.SetEnvironmentVariableW(k, v) {
-		return Errno(win32.GetLastError())
+		return get_last_error()
 	}
-	return 0
+	return nil
 }
 
 // unset_env unsets a single environment variable
-unset_env :: proc(key: string) -> Errno {
+unset_env :: proc(key: string) -> Error {
 	k := win32.utf8_to_wstring(key)
 	if !win32.SetEnvironmentVariableW(k, nil) {
-		return Errno(win32.GetLastError())
+		return get_last_error()
 	}
-	return 0
+	return nil
 }
 
 // environ returns a copy of strings representing the environment, in the form "key=value"
 // NOTE: the slice of strings and the strings with be allocated using the supplied allocator
+@(require_results)
 environ :: proc(allocator := context.allocator) -> []string {
-	envs := cast([^]win32.WCHAR)(win32.GetEnvironmentStringsW())
+	envs := ([^]win32.WCHAR)(win32.GetEnvironmentStringsW())
 	if envs == nil {
 		return nil
 	}
 	defer win32.FreeEnvironmentStringsW(envs)
 
-	r := make([dynamic]string, 0, 50, allocator)
+	r, err := make([dynamic]string, 0, 50, allocator)
+	if err != nil {
+		return nil
+	}
 	for from, i := 0, 0; true; i += 1 {
 		if c := envs[i]; c == 0 {
 			if i <= from {

+ 322 - 0
core/os/errors.odin

@@ -0,0 +1,322 @@
+package os
+
+import "base:intrinsics"
+import "base:runtime"
+import "core:io"
+
+Platform_Error :: _Platform_Error
+#assert(size_of(Platform_Error) <= 4)
+#assert(intrinsics.type_has_nil(Platform_Error))
+
+General_Error :: enum u32 {
+	None,
+
+	Permission_Denied,
+	Exist,
+	Not_Exist,
+	Closed,
+
+	Timeout,
+
+	Broken_Pipe,
+
+	// Indicates that an attempt to retrieve a file's size was made, but the
+	// file doesn't have a size.
+	No_Size,
+
+	Invalid_File,
+	Invalid_Dir,
+	Invalid_Path,
+	Invalid_Callback,
+
+	Pattern_Has_Separator,
+
+	Unsupported,
+
+	File_Is_Pipe,
+	Not_Dir,
+}
+
+
+Errno :: Error // alias for legacy use
+
+Error :: union #shared_nil {
+	General_Error,
+	io.Error,
+	runtime.Allocator_Error,
+	Platform_Error,
+}
+#assert(size_of(Error) == 8)
+
+ERROR_NONE :: Error{}
+
+ERROR_EOF :: io.Error.EOF
+
+@(require_results)
+is_platform_error :: proc "contextless" (ferr: Error) -> (err: i32, ok: bool) {
+	v := ferr.(Platform_Error) or_else {}
+	return i32(v), i32(v) != 0
+}
+
+@(require_results)
+error_string :: proc "contextless" (ferr: Error) -> string {
+	if ferr == nil {
+		return ""
+	}
+	switch e in ferr {
+	case General_Error:
+		switch e {
+		case .None: return ""
+		case .Permission_Denied: return "permission denied"
+		case .Exist:             return "file already exists"
+		case .Not_Exist:         return "file does not exist"
+		case .Closed:            return "file already closed"
+		case .Timeout:           return "i/o timeout"
+		case .Broken_Pipe:       return "Broken pipe"
+		case .No_Size:           return "file has no definite size"
+		case .Invalid_File:      return "invalid file"
+		case .Invalid_Dir:       return "invalid directory"
+		case .Invalid_Path:      return "invalid path"
+		case .Invalid_Callback:  return "invalid callback"
+		case .Unsupported:       return "unsupported"
+		case .Pattern_Has_Separator: return "pattern has separator"
+		case .File_Is_Pipe:      return "file is pipe"
+		case .Not_Dir:           return "file is not directory"
+		}
+	case io.Error:
+		switch e {
+		case .None: return ""
+		case .EOF:               return "eof"
+		case .Unexpected_EOF:    return "unexpected eof"
+		case .Short_Write:       return "short write"
+		case .Invalid_Write:     return "invalid write result"
+		case .Short_Buffer:      return "short buffer"
+		case .No_Progress:       return "multiple read calls return no data or error"
+		case .Invalid_Whence:    return "invalid whence"
+		case .Invalid_Offset:    return "invalid offset"
+		case .Invalid_Unread:    return "invalid unread"
+		case .Negative_Read:     return "negative read"
+		case .Negative_Write:    return "negative write"
+		case .Negative_Count:    return "negative count"
+		case .Buffer_Full:       return "buffer full"
+		case .Unknown, .Empty: //
+		}
+	case runtime.Allocator_Error:
+		switch e {
+		case .None:                 return ""
+		case .Out_Of_Memory:        return "out of memory"
+		case .Invalid_Pointer:      return "invalid allocator pointer"
+		case .Invalid_Argument:     return "invalid allocator argument"
+		case .Mode_Not_Implemented: return "allocator mode not implemented"
+		}
+	case Platform_Error:
+		return _error_string(e)
+	}
+
+	return "unknown error"
+}
+
+print_error :: proc(f: Handle, ferr: Error, msg: string) -> (n: int, err: Error) {
+	err_str := error_string(ferr)
+
+	// msg + ": " + err_str + '\n'
+	length := len(msg) + 2 + len(err_str) + 1
+	buf_ := intrinsics.alloca(length, 1)
+	buf := buf_[:length]
+
+	copy(buf, msg)
+	buf[len(msg)] = ':'
+	buf[len(msg) + 1] = ' '
+	copy(buf[len(msg) + 2:], err_str)
+	buf[length - 1] = '\n'
+	return write(f, buf)
+}
+
+
+@(require_results, private)
+_error_string :: proc "contextless" (e: Platform_Error) -> string where intrinsics.type_is_enum(Platform_Error) {
+	if e == nil {
+		return ""
+	}
+
+	when ODIN_OS == .Darwin {
+		if s := string(_darwin_string_error(i32(e))); s != "" {
+			return s
+		}
+	}
+
+	when ODIN_OS != .Linux {
+		@(require_results)
+		binary_search :: proc "contextless" (array: $A/[]$T, key: T) -> (index: int, found: bool) #no_bounds_check {
+			n := len(array)
+			left, right := 0, n
+			for left < right {
+				mid := int(uint(left+right) >> 1)
+				if array[mid] < key {
+					left = mid+1
+				} else {
+					// equal or greater
+					right = mid
+				}
+			}
+			return left, left < n && array[left] == key
+		}
+
+		err := runtime.Type_Info_Enum_Value(e)
+
+		ti := &runtime.type_info_base(type_info_of(Platform_Error)).variant.(runtime.Type_Info_Enum)
+		if idx, ok := binary_search(ti.values, err); ok {
+			return ti.names[idx]
+		}
+	} else {
+		@(rodata, static)
+		pe_strings := [Platform_Error]string{
+			.NONE            = "",
+			.EPERM           = "Operation not permitted",
+			.ENOENT          = "No such file or directory",
+			.ESRCH           = "No such process",
+			.EINTR           = "Interrupted system call",
+			.EIO             = "Input/output error",
+			.ENXIO           = "No such device or address",
+			.E2BIG           = "Argument list too long",
+			.ENOEXEC         = "Exec format error",
+			.EBADF           = "Bad file descriptor",
+			.ECHILD          = "No child processes",
+			.EAGAIN          = "Resource temporarily unavailable",
+			.ENOMEM          = "Cannot allocate memory",
+			.EACCES          = "Permission denied",
+			.EFAULT          = "Bad address",
+			.ENOTBLK         = "Block device required",
+			.EBUSY           = "Device or resource busy",
+			.EEXIST          = "File exists",
+			.EXDEV           = "Invalid cross-device link",
+			.ENODEV          = "No such device",
+			.ENOTDIR         = "Not a directory",
+			.EISDIR          = "Is a directory",
+			.EINVAL          = "Invalid argument",
+			.ENFILE          = "Too many open files in system",
+			.EMFILE          = "Too many open files",
+			.ENOTTY          = "Inappropriate ioctl for device",
+			.ETXTBSY         = "Text file busy",
+			.EFBIG           = "File too large",
+			.ENOSPC          = "No space left on device",
+			.ESPIPE          = "Illegal seek",
+			.EROFS           = "Read-only file system",
+			.EMLINK          = "Too many links",
+			.EPIPE           = "Broken pipe",
+			.EDOM            = "Numerical argument out of domain",
+			.ERANGE          = "Numerical result out of range",
+			.EDEADLK         = "Resource deadlock avoided",
+			.ENAMETOOLONG    = "File name too long",
+			.ENOLCK          = "No locks available",
+			.ENOSYS          = "Function not implemented",
+			.ENOTEMPTY       = "Directory not empty",
+			.ELOOP           = "Too many levels of symbolic links",
+			.EUNKNOWN_41     = "Unknown Error (41)",
+			.ENOMSG          = "No message of desired type",
+			.EIDRM           = "Identifier removed",
+			.ECHRNG          = "Channel number out of range",
+			.EL2NSYNC        = "Level 2 not synchronized",
+			.EL3HLT          = "Level 3 halted",
+			.EL3RST          = "Level 3 reset",
+			.ELNRNG          = "Link number out of range",
+			.EUNATCH         = "Protocol driver not attached",
+			.ENOCSI          = "No CSI structure available",
+			.EL2HLT          = "Level 2 halted",
+			.EBADE           = "Invalid exchange",
+			.EBADR           = "Invalid request descriptor",
+			.EXFULL          = "Exchange full",
+			.ENOANO          = "No anode",
+			.EBADRQC         = "Invalid request code",
+			.EBADSLT         = "Invalid slot",
+			.EUNKNOWN_58     = "Unknown Error (58)",
+			.EBFONT          = "Bad font file format",
+			.ENOSTR          = "Device not a stream",
+			.ENODATA         = "No data available",
+			.ETIME           = "Timer expired",
+			.ENOSR           = "Out of streams resources",
+			.ENONET          = "Machine is not on the network",
+			.ENOPKG          = "Package not installed",
+			.EREMOTE         = "Object is remote",
+			.ENOLINK         = "Link has been severed",
+			.EADV            = "Advertise error",
+			.ESRMNT          = "Srmount error",
+			.ECOMM           = "Communication error on send",
+			.EPROTO          = "Protocol error",
+			.EMULTIHOP       = "Multihop attempted",
+			.EDOTDOT         = "RFS specific error",
+			.EBADMSG         = "Bad message",
+			.EOVERFLOW       = "Value too large for defined data type",
+			.ENOTUNIQ        = "Name not unique on network",
+			.EBADFD          = "File descriptor in bad state",
+			.EREMCHG         = "Remote address changed",
+			.ELIBACC         = "Can not access a needed shared library",
+			.ELIBBAD         = "Accessing a corrupted shared library",
+			.ELIBSCN         = ".lib section in a.out corrupted",
+			.ELIBMAX         = "Attempting to link in too many shared libraries",
+			.ELIBEXEC        = "Cannot exec a shared library directly",
+			.EILSEQ          = "Invalid or incomplete multibyte or wide character",
+			.ERESTART        = "Interrupted system call should be restarted",
+			.ESTRPIPE        = "Streams pipe error",
+			.EUSERS          = "Too many users",
+			.ENOTSOCK        = "Socket operation on non-socket",
+			.EDESTADDRREQ    = "Destination address required",
+			.EMSGSIZE        = "Message too long",
+			.EPROTOTYPE      = "Protocol wrong type for socket",
+			.ENOPROTOOPT     = "Protocol not available",
+			.EPROTONOSUPPORT = "Protocol not supported",
+			.ESOCKTNOSUPPORT = "Socket type not supported",
+			.EOPNOTSUPP      = "Operation not supported",
+			.EPFNOSUPPORT    = "Protocol family not supported",
+			.EAFNOSUPPORT    = "Address family not supported by protocol",
+			.EADDRINUSE      = "Address already in use",
+			.EADDRNOTAVAIL   = "Cannot assign requested address",
+			.ENETDOWN        = "Network is down",
+			.ENETUNREACH     = "Network is unreachable",
+			.ENETRESET       = "Network dropped connection on reset",
+			.ECONNABORTED    = "Software caused connection abort",
+			.ECONNRESET      = "Connection reset by peer",
+			.ENOBUFS         = "No buffer space available",
+			.EISCONN         = "Transport endpoint is already connected",
+			.ENOTCONN        = "Transport endpoint is not connected",
+			.ESHUTDOWN       = "Cannot send after transport endpoint shutdown",
+			.ETOOMANYREFS    = "Too many references: cannot splice",
+			.ETIMEDOUT       = "Connection timed out",
+			.ECONNREFUSED    = "Connection refused",
+			.EHOSTDOWN       = "Host is down",
+			.EHOSTUNREACH    = "No route to host",
+			.EALREADY        = "Operation already in progress",
+			.EINPROGRESS     = "Operation now in progress",
+			.ESTALE          = "Stale file handle",
+			.EUCLEAN         = "Structure needs cleaning",
+			.ENOTNAM         = "Not a XENIX named type file",
+			.ENAVAIL         = "No XENIX semaphores available",
+			.EISNAM          = "Is a named type file",
+			.EREMOTEIO       = "Remote I/O error",
+			.EDQUOT          = "Disk quota exceeded",
+			.ENOMEDIUM       = "No medium found",
+			.EMEDIUMTYPE     = "Wrong medium type",
+			.ECANCELED       = "Operation canceled",
+			.ENOKEY          = "Required key not available",
+			.EKEYEXPIRED     = "Key has expired",
+			.EKEYREVOKED     = "Key has been revoked",
+			.EKEYREJECTED    = "Key was rejected by service",
+			.EOWNERDEAD      = "Owner died",
+			.ENOTRECOVERABLE = "State not recoverable",
+			.ERFKILL         = "Operation not possible due to RF-kill",
+			.EHWPOISON       = "Memory page has hardware error",
+		}
+		if Platform_Error.NONE <= e && e <= max(Platform_Error) {
+			return pe_strings[e]
+		}
+	}
+	return "<unknown platform error>"
+}
+
+@(private, require_results)
+error_to_io_error :: proc(ferr: Error) -> io.Error {
+	if ferr == nil {
+		return .None
+	}
+	return ferr.(io.Error) or_else .Unknown
+}

+ 83 - 93
core/os/file_windows.odin

@@ -5,13 +5,15 @@ import "base:intrinsics"
 import "base:runtime"
 import "core:unicode/utf16"
 
+@(require_results)
 is_path_separator :: proc(c: byte) -> bool {
 	return c == '/' || c == '\\'
 }
 
-open :: proc(path: string, mode: int = O_RDONLY, perm: int = 0) -> (Handle, Errno) {
+@(require_results)
+open :: proc(path: string, mode: int = O_RDONLY, perm: int = 0) -> (Handle, Error) {
 	if len(path) == 0 {
-		return INVALID_HANDLE, ERROR_FILE_NOT_FOUND
+		return INVALID_HANDLE, General_Error.Not_Exist
 	}
 
 	access: u32
@@ -52,32 +54,31 @@ open :: proc(path: string, mode: int = O_RDONLY, perm: int = 0) -> (Handle, Errn
 	wide_path := win32.utf8_to_wstring(path)
 	handle := Handle(win32.CreateFileW(wide_path, access, share_mode, sa, create_mode, win32.FILE_ATTRIBUTE_NORMAL|win32.FILE_FLAG_BACKUP_SEMANTICS, nil))
 	if handle != INVALID_HANDLE {
-		return handle, ERROR_NONE
+		return handle, nil
 	}
 
-	err := Errno(win32.GetLastError())
-	return INVALID_HANDLE, err
+	return INVALID_HANDLE, get_last_error()
 }
 
-close :: proc(fd: Handle) -> Errno {
+close :: proc(fd: Handle) -> Error {
 	if !win32.CloseHandle(win32.HANDLE(fd)) {
-		return Errno(win32.GetLastError())
+		return get_last_error()
 	}
-	return ERROR_NONE
+	return nil
 }
 
-flush :: proc(fd: Handle) -> (err: Errno) {
+flush :: proc(fd: Handle) -> (err: Error) {
 	if !win32.FlushFileBuffers(win32.HANDLE(fd)) {
-		err = Errno(win32.GetLastError())
+		err = get_last_error()
 	}
 	return
 }
 
 
 
-write :: proc(fd: Handle, data: []byte) -> (int, Errno) {
+write :: proc(fd: Handle, data: []byte) -> (int, Error) {
 	if len(data) == 0 {
-		return 0, ERROR_NONE
+		return 0, nil
 	}
 
 	single_write_length: win32.DWORD
@@ -90,25 +91,24 @@ write :: proc(fd: Handle, data: []byte) -> (int, Errno) {
 
 		e := win32.WriteFile(win32.HANDLE(fd), &data[total_write], to_write, &single_write_length, nil)
 		if single_write_length <= 0 || !e {
-			err := Errno(win32.GetLastError())
-			return int(total_write), err
+			return int(total_write), get_last_error()
 		}
 		total_write += i64(single_write_length)
 	}
-	return int(total_write), ERROR_NONE
+	return int(total_write), nil
 }
 
-@(private="file")
-read_console :: proc(handle: win32.HANDLE, b: []byte) -> (n: int, err: Errno) {
+@(private="file", require_results)
+read_console :: proc(handle: win32.HANDLE, b: []byte) -> (n: int, err: Error) {
 	if len(b) == 0 {
-		return 0, 0
+		return 0, nil
 	}
 	
 	BUF_SIZE :: 386
 	buf16: [BUF_SIZE]u16
 	buf8: [4*BUF_SIZE]u8
 
-	for n < len(b) && err == 0 {
+	for n < len(b) && err == nil {
 		min_read := max(len(b)/4, 1 if len(b) > 0 else 0)
 		max_read := u32(min(BUF_SIZE, min_read))
 		if max_read == 0 {
@@ -118,7 +118,7 @@ read_console :: proc(handle: win32.HANDLE, b: []byte) -> (n: int, err: Errno) {
 		single_read_length: u32
 		ok := win32.ReadConsoleW(handle, &buf16[0], max_read, &single_read_length, nil)
 		if !ok {
-			err = Errno(win32.GetLastError())
+			err = get_last_error()
 		}
 
 		buf8_len := utf16.decode_to_utf8(buf8[:], buf16[:single_read_length])
@@ -149,9 +149,9 @@ read_console :: proc(handle: win32.HANDLE, b: []byte) -> (n: int, err: Errno) {
 	return
 }
 
-read :: proc(fd: Handle, data: []byte) -> (total_read: int, err: Errno) {
+read :: proc(fd: Handle, data: []byte) -> (total_read: int, err: Error) {
 	if len(data) == 0 {
-		return 0, ERROR_NONE
+		return 0, nil
 	}
 	
 	handle := win32.HANDLE(fd)
@@ -165,7 +165,7 @@ read :: proc(fd: Handle, data: []byte) -> (total_read: int, err: Errno) {
 
 	if is_console {
 		total_read, err = read_console(handle, data[total_read:][:to_read])
-		if err != 0 {
+		if err != nil {
 			return total_read, err
 		}
 	} else {
@@ -175,18 +175,18 @@ read :: proc(fd: Handle, data: []byte) -> (total_read: int, err: Errno) {
 			// Successful read can mean two things, including EOF, see:
 			// https://learn.microsoft.com/en-us/windows/win32/fileio/testing-for-the-end-of-a-file
 			if bytes_read == 0 {
-				return 0, ERROR_HANDLE_EOF
+				return 0, .EOF
 			} else {
-				return int(bytes_read), ERROR_NONE
+				return int(bytes_read), nil
 			}
 		} else {
-			return 0, Errno(win32.GetLastError())
+			return 0, get_last_error()
 		}
 	}
-	return total_read, ERROR_NONE
+	return total_read, nil
 }
 
-seek :: proc(fd: Handle, offset: i64, whence: int) -> (i64, Errno) {
+seek :: proc(fd: Handle, offset: i64, whence: int) -> (i64, Error) {
 	w: u32
 	switch whence {
 	case 0: w = win32.FILE_BEGIN
@@ -197,22 +197,23 @@ seek :: proc(fd: Handle, offset: i64, whence: int) -> (i64, Errno) {
 	lo := i32(offset)
 	ft := win32.GetFileType(win32.HANDLE(fd))
 	if ft == win32.FILE_TYPE_PIPE {
-		return 0, ERROR_FILE_IS_PIPE
+		return 0, .File_Is_Pipe
 	}
 
 	dw_ptr := win32.SetFilePointer(win32.HANDLE(fd), lo, &hi, w)
 	if dw_ptr == win32.INVALID_SET_FILE_POINTER {
-		err := Errno(win32.GetLastError())
+		err := get_last_error()
 		return 0, err
 	}
-	return i64(hi)<<32 + i64(dw_ptr), ERROR_NONE
+	return i64(hi)<<32 + i64(dw_ptr), nil
 }
 
-file_size :: proc(fd: Handle) -> (i64, Errno) {
+@(require_results)
+file_size :: proc(fd: Handle) -> (i64, Error) {
 	length: win32.LARGE_INTEGER
-	err: Errno
+	err: Error
 	if !win32.GetFileSizeEx(win32.HANDLE(fd), &length) {
-		err = Errno(win32.GetLastError())
+		err = get_last_error()
 	}
 	return i64(length), err
 }
@@ -220,10 +221,9 @@ file_size :: proc(fd: Handle) -> (i64, Errno) {
 
 @(private)
 MAX_RW :: 1<<30
-ERROR_EOF :: 38
 
 @(private)
-pread :: proc(fd: Handle, data: []byte, offset: i64) -> (int, Errno) {
+pread :: proc(fd: Handle, data: []byte, offset: i64) -> (int, Error) {
 	buf := data
 	if len(buf) > MAX_RW {
 		buf = buf[:MAX_RW]
@@ -239,15 +239,15 @@ pread :: proc(fd: Handle, data: []byte, offset: i64) -> (int, Errno) {
 
 	h := win32.HANDLE(fd)
 	done: win32.DWORD
-	e: Errno
+	e: Error
 	if !win32.ReadFile(h, raw_data(buf), u32(len(buf)), &done, &o) {
-		e = Errno(win32.GetLastError())
+		e = get_last_error()
 		done = 0
 	}
 	return int(done), e
 }
 @(private)
-pwrite :: proc(fd: Handle, data: []byte, offset: i64) -> (int, Errno) {
+pwrite :: proc(fd: Handle, data: []byte, offset: i64) -> (int, Error) {
 	buf := data
 	if len(buf) > MAX_RW {
 		buf = buf[:MAX_RW]
@@ -261,9 +261,9 @@ pwrite :: proc(fd: Handle, data: []byte, offset: i64) -> (int, Errno) {
 
 	h := win32.HANDLE(fd)
 	done: win32.DWORD
-	e: Errno
+	e: Error
 	if !win32.WriteFile(h, raw_data(buf), u32(len(buf)), &done, &o) {
-		e = Errno(win32.GetLastError())
+		e = get_last_error()
 		done = 0
 	}
 	return int(done), e
@@ -279,19 +279,19 @@ on Windows, read_at changes the position of the file cursor, on *nix, it does no
 
 will read from the location twice on *nix, and from two different locations on Windows
 */
-read_at :: proc(fd: Handle, data: []byte, offset: i64) -> (n: int, err: Errno) {
+read_at :: proc(fd: Handle, data: []byte, offset: i64) -> (n: int, err: Error) {
 	if offset < 0 {
-		return 0, ERROR_NEGATIVE_OFFSET
+		return 0, .Invalid_Offset
 	}
 
 	b, offset := data, offset
 	for len(b) > 0 {
 		m, e := pread(fd, b, offset)
 		if e == ERROR_EOF {
-			err = 0
+			err = nil
 			break
 		}
-		if e != 0 {
+		if e != nil {
 			err = e
 			break
 		}
@@ -311,18 +311,14 @@ on Windows, write_at changes the position of the file cursor, on *nix, it does n
 
 will write to the location twice on *nix, and to two different locations on Windows
 */
-write_at :: proc(fd: Handle, data: []byte, offset: i64) -> (n: int, err: Errno) {
+write_at :: proc(fd: Handle, data: []byte, offset: i64) -> (n: int, err: Error) {
 	if offset < 0 {
-		return 0, ERROR_NEGATIVE_OFFSET
+		return 0, .Invalid_Offset
 	}
 
 	b, offset := data, offset
 	for len(b) > 0 {
-		m, e := pwrite(fd, b, offset)
-		if e != 0 {
-			err = e
-			break
-		}
+		m := pwrite(fd, b, offset) or_return
 		n += m
 		b = b[m:]
 		offset += i64(m)
@@ -338,6 +334,7 @@ stdout := get_std_handle(uint(win32.STD_OUTPUT_HANDLE))
 stderr := get_std_handle(uint(win32.STD_ERROR_HANDLE))
 
 
+@(require_results)
 get_std_handle :: proc "contextless" (h: uint) -> Handle {
 	fd := win32.GetStdHandle(win32.DWORD(h))
 	return Handle(fd)
@@ -352,6 +349,7 @@ exists :: proc(path: string) -> bool {
 	return attribs != win32.INVALID_FILE_ATTRIBUTES
 }
 
+@(require_results)
 is_file :: proc(path: string) -> bool {
 	runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD()
 	wpath := win32.utf8_to_wstring(path, context.temp_allocator)
@@ -363,6 +361,7 @@ is_file :: proc(path: string) -> bool {
 	return false
 }
 
+@(require_results)
 is_dir :: proc(path: string) -> bool {
 	runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD()
 	wpath := win32.utf8_to_wstring(path, context.temp_allocator)
@@ -377,13 +376,14 @@ is_dir :: proc(path: string) -> bool {
 // NOTE(tetra): GetCurrentDirectory is not thread safe with SetCurrentDirectory and GetFullPathName
 @private cwd_lock := win32.SRWLOCK{} // zero is initialized
 
+@(require_results)
 get_current_directory :: proc(allocator := context.allocator) -> string {
 	win32.AcquireSRWLockExclusive(&cwd_lock)
 
 	runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD(ignore = context.temp_allocator == allocator)
 
 	sz_utf16 := win32.GetCurrentDirectoryW(0, nil)
-	dir_buf_wstr := make([]u16, sz_utf16, context.temp_allocator) // the first time, it _includes_ the NUL.
+	dir_buf_wstr, _ := make([]u16, sz_utf16, context.temp_allocator) // the first time, it _includes_ the NUL.
 
 	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.
@@ -393,14 +393,14 @@ get_current_directory :: proc(allocator := context.allocator) -> string {
 	return win32.utf16_to_utf8(dir_buf_wstr, allocator) or_else ""
 }
 
-set_current_directory :: proc(path: string) -> (err: Errno) {
+set_current_directory :: proc(path: string) -> (err: Error) {
 	runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD()
 	wstr := win32.utf8_to_wstring(path, context.temp_allocator)
 
 	win32.AcquireSRWLockExclusive(&cwd_lock)
 
 	if !win32.SetCurrentDirectoryW(wstr) {
-		err = Errno(win32.GetLastError())
+		err = get_last_error()
 	}
 
 	win32.ReleaseSRWLockExclusive(&cwd_lock)
@@ -409,31 +409,31 @@ set_current_directory :: proc(path: string) -> (err: Errno) {
 }
 change_directory :: set_current_directory
 
-make_directory :: proc(path: string, mode: u32 = 0) -> (err: Errno) {
+make_directory :: proc(path: string, mode: u32 = 0) -> (err: Error) {
 	runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD()
 	// Mode is unused on Windows, but is needed on *nix
 	wpath := win32.utf8_to_wstring(path, context.temp_allocator)
 
 	if !win32.CreateDirectoryW(wpath, nil) {
-		err = Errno(win32.GetLastError())
+		err = get_last_error()
 	}
 	return
 }
 
 
-remove_directory :: proc(path: string) -> (err: Errno) {
+remove_directory :: proc(path: string) -> (err: Error) {
 	runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD()
 	wpath := win32.utf8_to_wstring(path, context.temp_allocator)
 
 	if !win32.RemoveDirectoryW(wpath) {
-		err = Errno(win32.GetLastError())
+		err = get_last_error()
 	}
 	return
 }
 
 
 
-@(private)
+@(private, require_results)
 is_abs :: proc(path: string) -> bool {
 	if len(path) > 0 && path[0] == '/' {
 		return true
@@ -449,7 +449,7 @@ is_abs :: proc(path: string) -> bool {
 	return false
 }
 
-@(private)
+@(private, require_results)
 fix_long_path :: proc(path: string) -> string {
 	if len(path) < 248 {
 		return path
@@ -464,7 +464,7 @@ fix_long_path :: proc(path: string) -> string {
 
 	prefix :: `\\?`
 
-	path_buf := make([]byte, len(prefix)+len(path)+len(`\`), context.temp_allocator)
+	path_buf, _ := make([]byte, len(prefix)+len(path)+len(`\`), context.temp_allocator)
 	copy(path_buf, prefix)
 	n := len(path)
 	r, w := 0, len(prefix)
@@ -494,80 +494,69 @@ fix_long_path :: proc(path: string) -> string {
 }
 
 
-link :: proc(old_name, new_name: string) -> (err: Errno) {
+link :: proc(old_name, new_name: string) -> (err: Error) {
 	runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD()
 	n := win32.utf8_to_wstring(fix_long_path(new_name))
 	o := win32.utf8_to_wstring(fix_long_path(old_name))
-	return Errno(win32.CreateHardLinkW(n, o, nil))
+	return Platform_Error(win32.CreateHardLinkW(n, o, nil))
 }
 
-unlink :: proc(path: string) -> (err: Errno) {
+unlink :: proc(path: string) -> (err: Error) {
 	runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD()
 	wpath := win32.utf8_to_wstring(path, context.temp_allocator)
 
 	if !win32.DeleteFileW(wpath) {
-		err = Errno(win32.GetLastError())
+		err = get_last_error()
 	}
 	return
 }
 
 
 
-rename :: proc(old_path, new_path: string) -> (err: Errno) {
+rename :: proc(old_path, new_path: string) -> (err: Error) {
 	runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD()
 	from := win32.utf8_to_wstring(old_path, context.temp_allocator)
 	to := win32.utf8_to_wstring(new_path, context.temp_allocator)
 
 	if !win32.MoveFileExW(from, to, win32.MOVEFILE_REPLACE_EXISTING) {
-		err = Errno(win32.GetLastError())
+		err = get_last_error()
 	}
 	return
 }
 
 
-ftruncate :: proc(fd: Handle, length: i64) -> (err: Errno) {
-	curr_off, e := seek(fd, 0, 1)
-	if e != 0 {
-		return e
-	}
+ftruncate :: proc(fd: Handle, length: i64) -> (err: Error) {
+	curr_off := seek(fd, 0, 1) or_return
 	defer seek(fd, curr_off, 0)
-	_, e = seek(fd, length, 0)
-	if e != 0 {
-		return e
-	}
+	_= seek(fd, length, 0) or_return
 	ok := win32.SetEndOfFile(win32.HANDLE(fd))
 	if !ok {
-		return Errno(win32.GetLastError())
+		return get_last_error()
 	}
-	return ERROR_NONE
+	return nil
 }
 
-truncate :: proc(path: string, length: i64) -> (err: Errno) {
-	fd: Handle
-	fd, err = open(path, O_WRONLY|O_CREATE, 0o666)
-	if err != 0 {
-		return
-	}
+truncate :: proc(path: string, length: i64) -> (err: Error) {
+	fd := open(path, O_WRONLY|O_CREATE, 0o666) or_return
 	defer close(fd)
-	err = ftruncate(fd, length)
-	return
+	return ftruncate(fd, length)
 }
 
 
-remove :: proc(name: string) -> Errno {
+remove :: proc(name: string) -> Error {
 	p := win32.utf8_to_wstring(fix_long_path(name))
 	err, err1: win32.DWORD
 	if !win32.DeleteFileW(p) {
 		err = win32.GetLastError()
 	}
 	if err == 0 {
-		return 0
+		return nil
 	}
 	if !win32.RemoveDirectoryW(p) {
 		err1 = win32.GetLastError()
 	}
 	if err1 == 0 {
-		return 0
+		return nil
 	}
 
 	if err != err1 {
@@ -588,16 +577,17 @@ remove :: proc(name: string) -> Errno {
 		}
 	}
 
-	return Errno(err)
+	return Platform_Error(err)
 }
 
 
-pipe :: proc() -> (r, w: Handle, err: Errno) {
+@(require_results)
+pipe :: proc() -> (r, w: Handle, err: Error) {
 	sa: win32.SECURITY_ATTRIBUTES
 	sa.nLength = size_of(win32.SECURITY_ATTRIBUTES)
 	sa.bInheritHandle = true
 	if !win32.CreatePipe((^win32.HANDLE)(&r), (^win32.HANDLE)(&w), &sa, 0) {
-		err = Errno(win32.GetLastError())
+		err = get_last_error()
 	}
 	return
 }

+ 90 - 58
core/os/os.odin

@@ -1,6 +1,8 @@
 package os
 
+import "base:intrinsics"
 import "base:runtime"
+import "core:io"
 import "core:strconv"
 import "core:unicode/utf8"
 
@@ -13,15 +15,15 @@ SEEK_SET :: 0
 SEEK_CUR :: 1
 SEEK_END :: 2
 
-write_string :: proc(fd: Handle, str: string) -> (int, Errno) {
+write_string :: proc(fd: Handle, str: string) -> (int, Error) {
 	return write(fd, transmute([]byte)str)
 }
 
-write_byte :: proc(fd: Handle, b: byte) -> (int, Errno) {
+write_byte :: proc(fd: Handle, b: byte) -> (int, Error) {
 	return write(fd, []byte{b})
 }
 
-write_rune :: proc(fd: Handle, r: rune) -> (int, Errno) {
+write_rune :: proc(fd: Handle, r: rune) -> (int, Error) {
 	if r < utf8.RUNE_SELF {
 		return write_byte(fd, byte(r))
 	}
@@ -30,113 +32,145 @@ write_rune :: proc(fd: Handle, r: rune) -> (int, Errno) {
 	return write(fd, b[:n])
 }
 
-write_encoded_rune :: proc(fd: Handle, r: rune) {
-	write_byte(fd, '\'')
+write_encoded_rune :: proc(f: Handle, r: rune) -> (n: int, err: Error) {
+	wrap :: proc(m: int, merr: Error, n: ^int, err: ^Error) -> bool {
+		n^ += m
+		if merr != nil {
+			err^ = merr
+			return true
+		}
+		return false
+	}
+
+	if wrap(write_byte(f, '\''), &n, &err) { return }
 
 	switch r {
-	case '\a': write_string(fd, "\\a")
-	case '\b': write_string(fd, "\\b")
-	case '\e': write_string(fd, "\\e")
-	case '\f': write_string(fd, "\\f")
-	case '\n': write_string(fd, "\\n")
-	case '\r': write_string(fd, "\\r")
-	case '\t': write_string(fd, "\\t")
-	case '\v': write_string(fd, "\\v")
+	case '\a': if wrap(write_string(f, "\\a"), &n, &err) { return }
+	case '\b': if wrap(write_string(f, "\\b"), &n, &err) { return }
+	case '\e': if wrap(write_string(f, "\\e"), &n, &err) { return }
+	case '\f': if wrap(write_string(f, "\\f"), &n, &err) { return }
+	case '\n': if wrap(write_string(f, "\\n"), &n, &err) { return }
+	case '\r': if wrap(write_string(f, "\\r"), &n, &err) { return }
+	case '\t': if wrap(write_string(f, "\\t"), &n, &err) { return }
+	case '\v': if wrap(write_string(f, "\\v"), &n, &err) { return }
 	case:
 		if r < 32 {
-			write_string(fd, "\\x")
+			if wrap(write_string(f, "\\x"), &n, &err) { return }
 			b: [2]byte
 			s := strconv.append_bits(b[:], u64(r), 16, true, 64, strconv.digits, nil)
 			switch len(s) {
-			case 0: write_string(fd, "00")
-			case 1: write_rune(fd, '0')
-			case 2: write_string(fd, s)
+			case 0: if wrap(write_string(f, "00"), &n, &err) { return }
+			case 1: if wrap(write_rune(f, '0'), &n, &err)    { return }
+			case 2: if wrap(write_string(f, s), &n, &err)    { return }
 			}
 		} else {
-			write_rune(fd, r)
+			if wrap(write_rune(f, r), &n, &err) { return }
 		}
 	}
-	write_byte(fd, '\'')
+	_ = wrap(write_byte(f, '\''), &n, &err)
+	return
 }
 
-read_at_least :: proc(fd: Handle, buf: []byte, min: int) -> (n: int, err: Errno) {
+read_at_least :: proc(fd: Handle, buf: []byte, min: int) -> (n: int, err: Error) {
 	if len(buf) < min {
-		return 0, -1
+		return 0, io.Error.Short_Buffer
 	}
 	nn := max(int)
-	for nn > 0 && n < min && err == 0 {
+	for nn > 0 && n < min && err == nil {
 		nn, err = read(fd, buf[n:])
 		n += nn
 	}
 	if n >= min {
-		err = 0
+		err = nil
 	}
 	return
 }
 
-read_full :: proc(fd: Handle, buf: []byte) -> (n: int, err: Errno) {
+read_full :: proc(fd: Handle, buf: []byte) -> (n: int, err: Error) {
 	return read_at_least(fd, buf, len(buf))
 }
 
 
+@(require_results)
 file_size_from_path :: proc(path: string) -> i64 {
 	fd, err := open(path, O_RDONLY, 0)
-	if err != 0 {
+	if err != nil {
 		return -1
 	}
 	defer close(fd)
 
 	length: i64
-	if length, err = file_size(fd); err != 0 {
+	if length, err = file_size(fd); err != nil {
 		return -1
 	}
 	return length
 }
 
+@(require_results)
 read_entire_file_from_filename :: proc(name: string, allocator := context.allocator, loc := #caller_location) -> (data: []byte, success: bool) {
+	err: Error
+	data, err = read_entire_file_from_filename_or_err(name, allocator, loc)
+	success = err == nil
+	return
+}
+
+@(require_results)
+read_entire_file_from_handle :: proc(fd: Handle, allocator := context.allocator, loc := #caller_location) -> (data: []byte, success: bool) {
+	err: Error
+	data, err = read_entire_file_from_handle_or_err(fd, allocator, loc)
+	success = err == nil
+	return
+}
+
+read_entire_file :: proc {
+	read_entire_file_from_filename,
+	read_entire_file_from_handle,
+}
+
+@(require_results)
+read_entire_file_from_filename_or_err :: proc(name: string, allocator := context.allocator, loc := #caller_location) -> (data: []byte, err: Error) {
 	context.allocator = allocator
 
-	fd, err := open(name, O_RDONLY, 0)
-	if err != 0 {
-		return nil, false
-	}
+	fd := open(name, O_RDONLY, 0) or_return
 	defer close(fd)
 
-	return read_entire_file_from_handle(fd, allocator, loc)
+	return read_entire_file_from_handle_or_err(fd, allocator, loc)
 }
 
-read_entire_file_from_handle :: proc(fd: Handle, allocator := context.allocator, loc := #caller_location) -> (data: []byte, success: bool) {
+@(require_results)
+read_entire_file_from_handle_or_err :: proc(fd: Handle, allocator := context.allocator, loc := #caller_location) -> (data: []byte, err: Error) {
 	context.allocator = allocator
 
-	length: i64
-	err: Errno
-	if length, err = file_size(fd); err != 0 {
-		return nil, false
-	}
-
+	length := file_size(fd) or_return
 	if length <= 0 {
-		return nil, true
+		return nil, nil
 	}
 
-	data = make([]byte, int(length), allocator, loc)
+	data = make([]byte, int(length), allocator, loc) or_return
 	if data == nil {
-		return nil, false
+		return nil, nil
 	}
-
-	bytes_read, read_err := read_full(fd, data)
-	if read_err != ERROR_NONE {
-		delete(data)
-		return nil, false
+	defer if err != nil {
+		delete(data, allocator)
 	}
-	return data[:bytes_read], true
+
+	bytes_read := read_full(fd, data) or_return
+	data = data[:bytes_read]
+	return
 }
 
-read_entire_file :: proc {
-	read_entire_file_from_filename,
-	read_entire_file_from_handle,
+read_entire_file_or_err :: proc {
+	read_entire_file_from_filename_or_err,
+	read_entire_file_from_handle_or_err,
 }
 
+
 write_entire_file :: proc(name: string, data: []byte, truncate := true) -> (success: bool) {
+	return write_entire_file_or_err(name, data, truncate) == nil
+}
+
+@(require_results)
+write_entire_file_or_err :: proc(name: string, data: []byte, truncate := true) -> Error {
 	flags: int = O_WRONLY|O_CREATE
 	if truncate {
 		flags |= O_TRUNC
@@ -148,21 +182,18 @@ write_entire_file :: proc(name: string, data: []byte, truncate := true) -> (succ
 		mode = S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH
 	}
 
-	fd, err := open(name, flags, mode)
-	if err != 0 {
-		return false
-	}
+	fd := open(name, flags, mode) or_return
 	defer close(fd)
 
-	_, write_err := write(fd, data)
-	return write_err == 0
+	_ = write(fd, data) or_return
+	return nil
 }
 
-write_ptr :: proc(fd: Handle, data: rawptr, len: int) -> (int, Errno) {
+write_ptr :: proc(fd: Handle, data: rawptr, len: int) -> (int, Error) {
 	return write(fd, ([^]byte)(data)[:len])
 }
 
-read_ptr :: proc(fd: Handle, data: rawptr, len: int) -> (int, Errno) {
+read_ptr :: proc(fd: Handle, data: rawptr, len: int) -> (int, Error) {
 	return read(fd, ([^]byte)(data)[:len])
 }
 
@@ -173,6 +204,7 @@ heap_alloc  :: runtime.heap_alloc
 heap_resize :: runtime.heap_resize
 heap_free   :: runtime.heap_free
 
+@(require_results)
 processor_core_count :: proc() -> int {
 	return _processor_core_count()
 }

+ 27 - 11
core/os/os2/allocators.odin

@@ -10,20 +10,29 @@ file_allocator :: proc() -> runtime.Allocator {
 
 temp_allocator_proc :: runtime.arena_allocator_proc
 
+@(private="file")
+MAX_TEMP_ARENA_COUNT :: 2
+
+@(private="file", thread_local)
+global_default_temp_allocator_arenas: [MAX_TEMP_ARENA_COUNT]runtime.Arena
+
 @(private="file", thread_local)
-global_default_temp_allocator_arena: runtime.Arena
+global_default_temp_allocator_index: uint
+
 
 @(require_results)
 temp_allocator :: proc() -> runtime.Allocator {
 	return runtime.Allocator{
 		procedure = temp_allocator_proc,
-		data      = &global_default_temp_allocator_arena,
+		data      = &global_default_temp_allocator_arenas[global_default_temp_allocator_index],
 	}
 }
 
+
+
 @(require_results)
 temp_allocator_temp_begin :: proc(loc := #caller_location) -> (temp: runtime.Arena_Temp) {
-	temp = runtime.arena_temp_begin(&global_default_temp_allocator_arena, loc)
+	temp = runtime.arena_temp_begin(&global_default_temp_allocator_arenas[global_default_temp_allocator_index], loc)
 	return
 }
 
@@ -33,16 +42,23 @@ temp_allocator_temp_end :: proc(temp: runtime.Arena_Temp, loc := #caller_locatio
 
 @(fini, private)
 temp_allocator_fini :: proc() {
-	runtime.arena_destroy(&global_default_temp_allocator_arena)
-	global_default_temp_allocator_arena = {}
+	for &arena in global_default_temp_allocator_arenas {
+		runtime.arena_destroy(&arena)
+	}
+	global_default_temp_allocator_arenas = {}
 }
 
-@(deferred_out=temp_allocator_temp_end)
-TEMP_ALLOCATOR_GUARD :: #force_inline proc(ignore := false, loc := #caller_location) -> (runtime.Arena_Temp, runtime.Source_Code_Location) {
-	if ignore {
-		return {}, loc
-	} else {
-		return temp_allocator_temp_begin(loc), loc
+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
 	}
 }
 
+@(deferred_out=TEMP_ALLOCATOR_GUARD_END)
+TEMP_ALLOCATOR_GUARD :: #force_inline proc(loc := #caller_location) -> (runtime.Arena_Temp, runtime.Source_Code_Location) {
+	tmp := temp_allocator_temp_begin(loc)
+	global_default_temp_allocator_index = (global_default_temp_allocator_index+1)%MAX_TEMP_ARENA_COUNT
+	return tmp, loc
+}
+

+ 3 - 1
core/os/os2/dir.odin

@@ -3,6 +3,8 @@ package os2
 import "base:runtime"
 import "core:slice"
 
+read_dir :: read_directory
+
 @(require_results)
 read_directory :: proc(f: ^File, n: int, allocator: runtime.Allocator) -> (files: []File_Info, err: Error) {
 	if f == nil {
@@ -57,6 +59,7 @@ read_all_directory_by_path :: proc(path: string, allocator: runtime.Allocator) -
 }
 
 
+
 Read_Directory_Iterator :: struct {
 	f:    ^File,
 	impl: Read_Directory_Iterator_Impl,
@@ -72,7 +75,6 @@ read_directory_iterator_destroy :: proc(it: ^Read_Directory_Iterator) {
 	_read_directory_iterator_destroy(it)
 }
 
-
 // NOTE(bill): `File_Info` does not need to deleted on each iteration. Any copies must be manually copied with `file_info_clone`
 @(require_results)
 read_directory_iterator :: proc(it: ^Read_Directory_Iterator) -> (fi: File_Info, index: int, ok: bool) {

+ 14 - 1
core/os/os2/dir_windows.odin

@@ -23,10 +23,21 @@ find_data_to_file_info :: proc(base_path: string, d: ^win32.WIN32_FIND_DATAW, al
 
 	fi.type, fi.mode = _file_type_mode_from_file_attributes(d.dwFileAttributes, nil, d.dwReserved0)
 
-	// fi.inode             = u128(u64(d.nFileIndexHigh)<<32 + u64(d.nFileIndexLow))
 	fi.creation_time     = time.unix(0, win32.FILETIME_as_unix_nanoseconds(d.ftCreationTime))
 	fi.modification_time = time.unix(0, win32.FILETIME_as_unix_nanoseconds(d.ftLastWriteTime))
 	fi.access_time       = time.unix(0, win32.FILETIME_as_unix_nanoseconds(d.ftLastAccessTime))
+
+
+	handle := win32.HANDLE(_open_internal(path, {.Read}, 0o666) or_else 0)
+	defer win32.CloseHandle(handle)
+
+	if file_id_info: win32.FILE_ID_INFO; handle != nil && win32.GetFileInformationByHandleEx(handle, .FileIdInfo, &file_id_info, size_of(file_id_info)) {
+		#assert(size_of(fi.inode) == size_of(file_id_info.FileId))
+		#assert(size_of(fi.inode) == 16)
+		runtime.mem_copy_non_overlapping(&fi.inode, &file_id_info.FileId, 16)
+	}
+
+
 	return
 }
 
@@ -46,6 +57,8 @@ _read_directory_iterator :: proc(it: ^Read_Directory_Iterator) -> (fi: File_Info
 		return
 	}
 
+	TEMP_ALLOCATOR_GUARD()
+
 	for !it.impl.no_more_files {
 		err: Error
 		file_info_delete(it.impl.prev_fi, file_allocator())

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

@@ -42,13 +42,14 @@ Error :: union #shared_nil {
 ERROR_NONE :: Error{}
 
 
-
+@(require_results)
 is_platform_error :: proc(ferr: Error) -> (err: i32, ok: bool) {
 	v := ferr.(Platform_Error) or_else {}
 	return i32(v), i32(v) != 0
 }
 
 
+@(require_results)
 error_string :: proc(ferr: Error) -> string {
 	if ferr == nil {
 		return ""

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

@@ -4,8 +4,8 @@ package os2
 import "core:sys/linux"
 
 @(rodata)
-_errno_strings : [linux.Errno]string = {
-	.NONE            = "Success",
+_errno_strings := [linux.Errno]string{
+	.NONE            = "",
 	.EPERM           = "Operation not permitted",
 	.ENOENT          = "No such file or directory",
 	.ESRCH           = "No such process",

+ 9 - 0
core/os/os2/file.odin

@@ -104,6 +104,15 @@ open :: proc(name: string, flags := File_Flags{.Read}, perm := 0o777) -> (^File,
 	return _open(name, flags, perm)
 }
 
+// @(require_results)
+// open_buffered :: proc(name: string, buffer_size: uint, flags := File_Flags{.Read}, perm := 0o777) -> (^File, Error) {
+// 	if buffer_size == 0 {
+// 		return _open(name, flags, perm)
+// 	}
+// 	return _open_buffered(name, buffer_size, flags, perm)
+// }
+
+
 @(require_results)
 new_file :: proc(handle: uintptr, name: string) -> ^File {
 	return _new_file(handle, name) or_else panic("Out of memory")

+ 72 - 10
core/os/os2/file_linux.odin

@@ -1,9 +1,10 @@
 //+private
 package os2
 
+import "base:runtime"
 import "core:io"
 import "core:time"
-import "base:runtime"
+import "core:sync"
 import "core:sys/linux"
 
 File_Impl :: struct {
@@ -11,6 +12,10 @@ File_Impl :: struct {
 	name: string,
 	fd: linux.Fd,
 	allocator: runtime.Allocator,
+
+	buffer:   []byte,
+	rw_mutex: sync.RW_Mutex, // read write calls
+	p_mutex:  sync.Mutex, // pread pwrite calls
 }
 
 _stdin := File{
@@ -49,9 +54,9 @@ _standard_stream_init :: proc() {
 		fd = 2,
 	}
 
-	stdin_impl.allocator  = _file_allocator()
-	stdout_impl.allocator = _file_allocator()
-	stderr_impl.allocator = _file_allocator()
+	stdin_impl.allocator  = file_allocator()
+	stdout_impl.allocator = file_allocator()
+	stderr_impl.allocator = file_allocator()
 
 	_stdin.impl  = &stdin_impl
 	_stdout.impl = &stdout_impl
@@ -67,10 +72,6 @@ _standard_stream_init :: proc() {
 	stderr = &_stderr
 }
 
-_file_allocator :: proc() -> runtime.Allocator {
-	return heap_allocator()
-}
-
 _open :: proc(name: string, flags: File_Flags, perm: int) -> (f: ^File, err: Error) {
 	TEMP_ALLOCATOR_GUARD()
 	name_cstr := temp_cstring(name) or_return
@@ -116,13 +117,30 @@ _new_file :: proc(fd: uintptr, _: string = "") -> (f: ^File, err: Error) {
 	return &impl.file, nil
 }
 
+
+@(require_results)
+_open_buffered :: proc(name: string, buffer_size: uint, flags := File_Flags{.Read}, perm := 0o777) -> (f: ^File, err: Error) {
+	assert(buffer_size > 0)
+	f, err = _open(name, flags, perm)
+	if f != nil && err == nil {
+		impl := (^File_Impl)(f.impl)
+		impl.buffer = make([]byte, buffer_size, file_allocator())
+		f.stream.procedure = _file_stream_buffered_proc
+	}
+	return
+}
+
 _destroy :: proc(f: ^File_Impl) -> Error {
 	if f == nil {
 		return nil
 	}
 	a := f.allocator
-	delete(f.name, a)
-	free(f, a)
+	err0 := delete(f.name, a)
+	err1 := delete(f.buffer, a)
+	err2 := free(f, a)
+	err0 or_return
+	err1 or_return
+	err2 or_return
 	return nil
 }
 
@@ -470,3 +488,47 @@ _file_stream_proc :: proc(stream_data: rawptr, mode: io.Stream_Mode, p: []byte,
 	return 0, .Empty
 }
 
+
+@(private="package")
+_file_stream_buffered_proc :: proc(stream_data: rawptr, mode: io.Stream_Mode, p: []byte, offset: i64, whence: io.Seek_From) -> (n: i64, err: io.Error) {
+	f := (^File_Impl)(stream_data)
+	ferr: Error
+	switch mode {
+	case .Read:
+		n, ferr = _read(f, p)
+		err = error_to_io_error(ferr)
+		return
+	case .Read_At:
+		n, ferr = _read_at(f, p, offset)
+		err = error_to_io_error(ferr)
+		return
+	case .Write:
+		n, ferr = _write(f, p)
+		err = error_to_io_error(ferr)
+		return
+	case .Write_At:
+		n, ferr = _write_at(f, p, offset)
+		err = error_to_io_error(ferr)
+		return
+	case .Seek:
+		n, ferr = _seek(f, offset, whence)
+		err = error_to_io_error(ferr)
+		return
+	case .Size:
+		n, ferr = _file_size(f)
+		err = error_to_io_error(ferr)
+		return
+	case .Flush:
+		ferr = _flush(f)
+		err = error_to_io_error(ferr)
+		return
+	case .Close, .Destroy:
+		ferr = _close(f)
+		err = error_to_io_error(ferr)
+		return
+	case .Query:
+		return io.query_utility({.Read, .Read_At, .Write, .Write_At, .Seek, .Size, .Flush, .Close, .Destroy, .Query})
+	}
+	return 0, .Empty
+}
+

+ 20 - 5
core/os/os2/file_util.odin

@@ -73,6 +73,24 @@ write_encoded_rune :: proc(f: ^File, r: rune) -> (n: int, err: Error) {
 	return
 }
 
+read_at_least :: proc(f: ^File, buf: []byte, min: int) -> (n: int, err: Error) {
+	if len(buf) < min {
+		return 0, .Short_Buffer
+	}
+	nn := max(int)
+	for nn > 0 && n < min && err == nil {
+		nn, err = read(f, buf[n:])
+		n += nn
+	}
+	if n >= min {
+		err = nil
+	}
+	return
+}
+
+read_full :: proc(f: ^File, buf: []byte) -> (n: int, err: Error) {
+	return read_at_least(f, buf, len(buf))
+}
 
 write_ptr :: proc(f: ^File, data: rawptr, len: int) -> (n: int, err: Error) {
 	return write(f, ([^]byte)(data)[:len])
@@ -155,11 +173,8 @@ write_entire_file :: proc(name: string, data: []byte, perm: int, truncate := tru
 	if truncate {
 		flags |= O_TRUNC
 	}
-	f, err := open(name, flags, perm)
-	if err != nil {
-		return err
-	}
-	_, err = write(f, data)
+	f := open(name, flags, perm) or_return
+	_, err := write(f, data)
 	if cerr := close(f); cerr != nil && err == nil {
 		err = cerr
 	}

+ 58 - 10
core/os/os2/file_windows.odin

@@ -33,6 +33,11 @@ File_Impl :: struct {
 
 	allocator: runtime.Allocator,
 
+	r_buf: []byte,
+	w_buf: []byte,
+	w_n:   int,
+	max_consecutive_empty_writes: int,
+
 	rw_mutex: sync.RW_Mutex, // read write calls
 	p_mutex:  sync.Mutex, // pread pwrite calls
 }
@@ -60,8 +65,9 @@ _open_internal :: proc(name: string, flags: File_Flags, perm: int) -> (handle: u
 		err = .Not_Exist
 		return
 	}
+	TEMP_ALLOCATOR_GUARD()
 
-	path := _fix_long_path(name) or_return
+	path := _fix_long_path(name, temp_allocator()) or_return
 	access: u32
 	switch flags & {.Read, .Write} {
 	case {.Read}:         access = win32.FILE_GENERIC_READ
@@ -164,6 +170,26 @@ _new_file :: proc(handle: uintptr, name: string) -> (f: ^File, err: Error) {
 	return &impl.file, nil
 }
 
+
+@(require_results)
+_open_buffered :: proc(name: string, buffer_size: uint, flags := File_Flags{.Read}, perm := 0o777) -> (f: ^File, err: Error) {
+	assert(buffer_size > 0)
+	flags := flags if flags != nil else {.Read}
+	handle := _open_internal(name, flags, perm) or_return
+	return _new_file_buffered(handle, name, buffer_size)
+}
+
+_new_file_buffered :: proc(handle: uintptr, name: string, buffer_size: uint) -> (f: ^File, err: Error) {
+	f, err = _new_file(handle, name)
+	if f != nil && err == nil {
+		impl := (^File_Impl)(f.impl)
+		impl.r_buf = make([]byte, buffer_size, file_allocator())
+		impl.w_buf = make([]byte, buffer_size, file_allocator())
+	}
+	return
+}
+
+
 _fd :: proc(f: ^File) -> uintptr {
 	if f == nil || f.impl == nil {
 		return INVALID_HANDLE
@@ -180,9 +206,13 @@ _destroy :: proc(f: ^File_Impl) -> Error {
 	err0 := free(f.wname, a)
 	err1 := delete(f.name, a)
 	err2 := free(f, a)
+	err3 := delete(f.r_buf, a)
+	err4 := delete(f.w_buf, a)
 	err0 or_return
 	err1 or_return
 	err2 or_return
+	err3 or_return
+	err4 or_return
 	return nil
 }
 
@@ -230,6 +260,10 @@ _seek :: proc(f: ^File_Impl, offset: i64, whence: io.Seek_From) -> (ret: i64, er
 }
 
 _read :: proc(f: ^File_Impl, p: []byte) -> (n: i64, err: Error) {
+	return _read_internal(f, p)
+}
+
+_read_internal :: proc(f: ^File_Impl, p: []byte) -> (n: i64, err: Error) {
 	read_console :: proc(handle: win32.HANDLE, b: []byte) -> (n: int, err: Error) {
 		if len(b) == 0 {
 			return 0, nil
@@ -351,6 +385,9 @@ _read_at :: proc(f: ^File_Impl, p: []byte, offset: i64) -> (n: i64, err: Error)
 }
 
 _write :: proc(f: ^File_Impl, p: []byte) -> (n: i64, err: Error) {
+	return _write_internal(f, p)
+}
+_write_internal :: proc(f: ^File_Impl, p: []byte) -> (n: i64, err: Error) {
 	if len(p) == 0 {
 		return
 	}
@@ -435,6 +472,9 @@ _sync :: proc(f: ^File) -> Error {
 }
 
 _flush :: proc(f: ^File_Impl) -> Error {
+	return _flush(f)
+}
+_flush_internal :: proc(f: ^File_Impl) -> Error {
 	handle := _handle(&f.file)
 	if !win32.FlushFileBuffers(handle) {
 		return _get_platform_error()
@@ -457,7 +497,8 @@ _truncate :: proc(f: ^File, size: i64) -> Error {
 }
 
 _remove :: proc(name: string) -> Error {
-	p := _fix_long_path(name) or_return
+	TEMP_ALLOCATOR_GUARD()
+	p := _fix_long_path(name, temp_allocator()) or_return
 	err, err1: Error
 	if !win32.DeleteFileW(p) {
 		err = _get_platform_error()
@@ -494,8 +535,9 @@ _remove :: proc(name: string) -> Error {
 }
 
 _rename :: proc(old_path, new_path: string) -> Error {
-	from := _fix_long_path(old_path) or_return
-	to   := _fix_long_path(new_path) or_return
+	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
 	}
@@ -504,8 +546,9 @@ _rename :: proc(old_path, new_path: string) -> Error {
 }
 
 _link :: proc(old_name, new_name: string) -> Error {
-	o := _fix_long_path(old_name) or_return
-	n := _fix_long_path(new_name) or_return
+	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
 	}
@@ -592,8 +635,10 @@ _read_link :: proc(name: string, allocator: runtime.Allocator) -> (s: string, er
 	@thread_local
 	rdb_buf: [MAXIMUM_REPARSE_DATA_BUFFER_SIZE]byte
 
-	p      := _fix_long_path(name) or_return
-	handle := _open_sym_link(p)    or_return
+	TEMP_ALLOCATOR_GUARD()
+
+	p      := _fix_long_path(name, temp_allocator()) or_return
+	handle := _open_sym_link(p) or_return
 	defer win32.CloseHandle(handle)
 
 	bytes_returned: u32
@@ -667,7 +712,8 @@ _fchown :: proc(f: ^File, uid, gid: int) -> Error {
 }
 
 _chdir :: proc(name: string) -> Error {
-	p := _fix_long_path(name) or_return
+	TEMP_ALLOCATOR_GUARD()
+	p := _fix_long_path(name, temp_allocator()) or_return
 	if !win32.SetCurrentDirectoryW(p) {
 		return _get_platform_error()
 	}
@@ -723,7 +769,8 @@ _fchtimes :: proc(f: ^File, atime, mtime: time.Time) -> Error {
 }
 
 _exists :: proc(path: string) -> bool {
-	wpath, _ := _fix_long_path(path)
+	TEMP_ALLOCATOR_GUARD()
+	wpath, _ := _fix_long_path(path, temp_allocator())
 	attribs := win32.GetFileAttributesW(wpath)
 	return attribs != win32.INVALID_FILE_ATTRIBUTES
 }
@@ -773,6 +820,7 @@ _file_stream_proc :: proc(stream_data: rawptr, mode: io.Stream_Mode, p: []byte,
 
 
 
+
 @(private="package", require_results)
 win32_utf8_to_wstring :: proc(s: string, allocator: runtime.Allocator) -> (ws: [^]u16, err: runtime.Allocator_Error) {
 	ws = raw_data(win32_utf8_to_utf16(s, allocator) or_return)

+ 6 - 5
core/os/os2/path_windows.odin

@@ -13,7 +13,8 @@ _is_path_separator :: proc(c: byte) -> bool {
 }
 
 _mkdir :: proc(name: string, perm: int) -> Error {
-	if !win32.CreateDirectoryW(_fix_long_path(name) or_return, nil) {
+	TEMP_ALLOCATOR_GUARD()
+	if !win32.CreateDirectoryW(_fix_long_path(name, temp_allocator()) or_return, nil) {
 		return _get_platform_error()
 	}
 	return nil
@@ -169,13 +170,13 @@ init_long_path_support :: proc() {
 }
 
 @(require_results)
-_fix_long_path_slice :: proc(path: string) -> ([]u16, runtime.Allocator_Error) {
-	return win32_utf8_to_utf16(_fix_long_path_internal(path), temp_allocator())
+_fix_long_path_slice :: proc(path: string, allocator: runtime.Allocator) -> ([]u16, runtime.Allocator_Error) {
+	return win32_utf8_to_utf16(_fix_long_path_internal(path), allocator)
 }
 
 @(require_results)
-_fix_long_path :: proc(path: string) -> (win32.wstring, runtime.Allocator_Error) {
-	return win32_utf8_to_wstring(_fix_long_path_internal(path), temp_allocator())
+_fix_long_path :: proc(path: string, allocator: runtime.Allocator) -> (win32.wstring, runtime.Allocator_Error) {
+	return win32_utf8_to_wstring(_fix_long_path_internal(path), allocator)
 }
 
 @(require_results)

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

@@ -12,8 +12,8 @@ File_Info :: struct {
 	name:              string,
 
 	inode:             u128, // might be zero if cannot be determined
-	size:              i64,
-	mode:              int,
+	size:              i64 `fmt:"M"`,
+	mode:              int `fmt:"o"`,
 	type:              File_Type,
 
 	creation_time:     time.Time,

+ 7 - 9
core/os/os2/stat_windows.odin

@@ -6,25 +6,22 @@ import "core:time"
 import "core:strings"
 import win32 "core:sys/windows"
 
-_fstat :: proc(f: ^File, allocator: runtime.Allocator) -> (File_Info, Error) {
+_fstat :: proc(f: ^File, allocator: runtime.Allocator) -> (fi: File_Info, err: Error) {
 	if f == nil || (^File_Impl)(f.impl).fd == nil {
-		return {}, nil
+		return
 	}
 
-	path, err := _cleanpath_from_handle(f, allocator)
-	if err != nil {
-		return {}, err
-	}
+	path := _cleanpath_from_handle(f, allocator) or_return
 
 	h := _handle(f)
 	switch win32.GetFileType(h) {
 	case win32.FILE_TYPE_PIPE, win32.FILE_TYPE_CHAR:
-		fi := File_Info {
+		fi = File_Info {
 			fullpath = path,
 			name = basename(path),
 			type = file_type(h),
 		}
-		return fi, nil
+		return
 	}
 
 	return _file_info_from_get_file_information_by_handle(path, h, allocator)
@@ -67,8 +64,9 @@ internal_stat :: proc(name: string, create_file_attributes: u32, allocator: runt
 	if len(name) == 0 {
 		return {}, .Not_Exist
 	}
+	TEMP_ALLOCATOR_GUARD()
 
-	wname := _fix_long_path(name) 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 {

+ 7 - 4
core/os/os2/temp_file_linux.odin

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

Файлын зөрүү хэтэрхий том тул дарагдсан байна
+ 382 - 230
core/os/os_darwin.odin


+ 29 - 24
core/os/os_essence.odin

@@ -2,54 +2,59 @@ package os
 
 import "core:sys/es"
 
-Handle :: distinct int;
-Errno :: distinct int;
+Handle :: distinct int
+_Platform_Error :: enum i32 {NONE}
 
-ERROR_NONE :: (Errno) (es.SUCCESS);
+// ERROR_NONE :: Error(es.SUCCESS)
 
-O_RDONLY :: 0x1;
-O_WRONLY :: 0x2;
-O_CREATE :: 0x4;
-O_TRUNC  :: 0x8;
+O_RDONLY :: 0x1
+O_WRONLY :: 0x2
+O_CREATE :: 0x4
+O_TRUNC  :: 0x8
 
-stderr : Handle = 0; 
+stderr : Handle = 0
 
 current_thread_id :: proc "contextless" () -> int {
-	return (int) (es.ThreadGetID(es.CURRENT_THREAD));
+	return (int) (es.ThreadGetID(es.CURRENT_THREAD))
 }
 
 heap_alloc :: proc(size: int, zero_memory := true) -> rawptr {
-	return es.HeapAllocate(size, zero_memory);
+	return es.HeapAllocate(size, zero_memory)
 }
 
 heap_free :: proc(ptr: rawptr) {
-	es.HeapFree(ptr);
+	es.HeapFree(ptr)
 }
 
 heap_resize :: proc(ptr: rawptr, new_size: int) -> rawptr {
-	return es.HeapReallocate(ptr, new_size, false);
+	return es.HeapReallocate(ptr, new_size, false)
 }
 
-open :: proc(path: string, flags: int = O_RDONLY, mode: int = 0) -> (Handle, Errno) {
-	return (Handle) (0), (Errno) (1);
+open :: proc(path: string, flags: int = O_RDONLY, mode: int = 0) -> (Handle, Error) {
+	return (Handle) (0), (Error) (1)
 }
 
-close :: proc(fd: Handle) -> Errno {
-	return (Errno) (1);
+close :: proc(fd: Handle) -> Error {
+	return (Error) (1)
 }
 
-file_size :: proc(fd: Handle) -> (i64, Errno) {
-	return (i64) (0), (Errno) (1);
+file_size :: proc(fd: Handle) -> (i64, Error) {
+	return (i64) (0), (Error) (1)
 }
 
-read :: proc(fd: Handle, data: []byte) -> (int, Errno) {
-	return (int) (0), (Errno) (1);
+read :: proc(fd: Handle, data: []byte) -> (int, Error) {
+	return (int) (0), (Error) (1)
 }
 
-write :: proc(fd: Handle, data: []u8) -> (int, Errno) {
-	return (int) (0), (Errno) (1);
+write :: proc(fd: Handle, data: []u8) -> (int, Error) {
+	return (int) (0), (Error) (1)
 }
 
-seek :: proc(fd: Handle, offset: i64, whence: int) -> (i64, Errno) {
-	return (i64) (0), (Errno) (1);
+seek :: proc(fd: Handle, offset: i64, whence: int) -> (i64, Error) {
+	return (i64) (0), (Error) (1)
 }
+
+flush :: proc(fd: Handle) -> Error {
+	// do nothing
+	return nil
+}

+ 334 - 196
core/os/os_freebsd.odin

@@ -9,105 +9,200 @@ import "core:c"
 
 Handle :: distinct i32
 File_Time :: distinct u64
-Errno :: distinct i32
 
 INVALID_HANDLE :: ~Handle(0)
 
-ERROR_NONE:      Errno : 0
-EPERM:           Errno : 1
-ENOENT:          Errno : 2
-ESRCH:           Errno : 3
-EINTR:           Errno : 4
-EIO:             Errno : 5
-ENXIO:           Errno : 6
-E2BIG:           Errno : 7
-ENOEXEC:         Errno : 8
-EBADF:           Errno : 9
-ECHILD:          Errno : 10
-EBEADLK:         Errno : 11
-ENOMEM:          Errno : 12
-EACCESS:         Errno : 13
-EFAULT:          Errno : 14
-ENOTBLK:         Errno : 15
-EBUSY:           Errno : 16
-EEXIST:          Errno : 17
-EXDEV:           Errno : 18
-ENODEV:          Errno : 19
-ENOTDIR:         Errno : 20
-EISDIR:          Errno : 21
-EINVAL:          Errno : 22
-ENFILE:          Errno : 23
-EMFILE:          Errno : 24
-ENOTTY:          Errno : 25
-ETXTBSY:         Errno : 26
-EFBIG:           Errno : 27
-ENOSPC:          Errno : 28
-ESPIPE:          Errno : 29
-EROFS:           Errno : 30
-EMLINK:          Errno : 31
-EPIPE:           Errno : 32
-EDOM:            Errno : 33
-ERANGE:          Errno : 34 /* Result too large */
-EAGAIN:          Errno : 35
-EINPROGRESS:     Errno : 36
-EALREADY:        Errno : 37
-ENOTSOCK:        Errno : 38
-EDESTADDRREQ:    Errno : 39
-EMSGSIZE:        Errno : 40
-EPROTOTYPE:      Errno : 41
-ENOPROTOOPT:     Errno : 42
-EPROTONOSUPPORT: Errno : 43
-ESOCKTNOSUPPORT: Errno : 44
-EOPNOTSUPP:      Errno : 45
-EPFNOSUPPORT:    Errno : 46
-EAFNOSUPPORT:    Errno : 47
-EADDRINUSE:      Errno : 48
-EADDRNOTAVAIL:   Errno : 49
-ENETDOWN:        Errno : 50
-ENETUNREACH:     Errno : 51
-ENETRESET:       Errno : 52
-ECONNABORTED:    Errno : 53
-ECONNRESET:      Errno : 54
-ENOBUFS:         Errno : 55
-EISCONN:         Errno : 56
-ENOTCONN:        Errno : 57
-ESHUTDOWN:       Errno : 58
-ETIMEDOUT:       Errno : 60
-ECONNREFUSED:    Errno : 61
-ELOOP:           Errno : 62
-ENAMETOOLING:    Errno : 63
-EHOSTDOWN:       Errno : 64
-EHOSTUNREACH:    Errno : 65
-ENOTEMPTY:       Errno : 66
-EPROCLIM:        Errno : 67
-EUSERS:          Errno : 68
-EDQUOT:          Errno : 69
-ESTALE:          Errno : 70
-EBADRPC:         Errno : 72
-ERPCMISMATCH:    Errno : 73
-EPROGUNAVAIL:    Errno : 74
-EPROGMISMATCH:   Errno : 75
-EPROCUNAVAIL:    Errno : 76
-ENOLCK:          Errno : 77
-ENOSYS:          Errno : 78
-EFTYPE:          Errno : 79
-EAUTH:           Errno : 80
-ENEEDAUTH:       Errno : 81
-EIDRM:           Errno : 82
-ENOMSG:          Errno : 83
-EOVERFLOW:       Errno : 84
-ECANCELED:       Errno : 85
-EILSEQ:          Errno : 86
-ENOATTR:         Errno : 87
-EDOOFUS:         Errno : 88
-EBADMSG:         Errno : 89
-EMULTIHOP:       Errno : 90
-ENOLINK:         Errno : 91
-EPROTO:          Errno : 92
-ENOTCAPABLE:     Errno : 93
-ECAPMODE:        Errno : 94
-ENOTRECOVERABLE: Errno : 95
-EOWNERDEAD:      Errno : 96
+_Platform_Error :: enum i32 {
+	NONE            = 0,
+	EPERM           = 1,
+	ENOENT          = 2,
+	ESRCH           = 3,
+	EINTR           = 4,
+	EIO             = 5,
+	ENXIO           = 6,
+	E2BIG           = 7,
+	ENOEXEC         = 8,
+	EBADF           = 9,
+	ECHILD          = 10,
+	EBEADLK         = 11,
+	ENOMEM          = 12,
+	EACCESS         = 13,
+	EFAULT          = 14,
+	ENOTBLK         = 15,
+	EBUSY           = 16,
+	EEXIST          = 17,
+	EXDEV           = 18,
+	ENODEV          = 19,
+	ENOTDIR         = 20,
+	EISDIR          = 21,
+	EINVAL          = 22,
+	ENFILE          = 23,
+	EMFILE          = 24,
+	ENOTTY          = 25,
+	ETXTBSY         = 26,
+	EFBIG           = 27,
+	ENOSPC          = 28,
+	ESPIPE          = 29,
+	EROFS           = 30,
+	EMLINK          = 31,
+	EPIPE           = 32,
+	EDOM            = 33,
+	ERANGE          = 34, /* Result too large */
+	EAGAIN          = 35,
+	EINPROGRESS     = 36,
+	EALREADY        = 37,
+	ENOTSOCK        = 38,
+	EDESTADDRREQ    = 39,
+	EMSGSIZE        = 40,
+	EPROTOTYPE      = 41,
+	ENOPROTOOPT     = 42,
+	EPROTONOSUPPORT = 43,
+	ESOCKTNOSUPPORT = 44,
+	EOPNOTSUPP      = 45,
+	EPFNOSUPPORT    = 46,
+	EAFNOSUPPORT    = 47,
+	EADDRINUSE      = 48,
+	EADDRNOTAVAIL   = 49,
+	ENETDOWN        = 50,
+	ENETUNREACH     = 51,
+	ENETRESET       = 52,
+	ECONNABORTED    = 53,
+	ECONNRESET      = 54,
+	ENOBUFS         = 55,
+	EISCONN         = 56,
+	ENOTCONN        = 57,
+	ESHUTDOWN       = 58,
+	ETIMEDOUT       = 60,
+	ECONNREFUSED    = 61,
+	ELOOP           = 62,
+	ENAMETOOLING    = 63,
+	EHOSTDOWN       = 64,
+	EHOSTUNREACH    = 65,
+	ENOTEMPTY       = 66,
+	EPROCLIM        = 67,
+	EUSERS          = 68,
+	EDQUOT          = 69,
+	ESTALE          = 70,
+	EBADRPC         = 72,
+	ERPCMISMATCH    = 73,
+	EPROGUNAVAIL    = 74,
+	EPROGMISMATCH   = 75,
+	EPROCUNAVAIL    = 76,
+	ENOLCK          = 77,
+	ENOSYS          = 78,
+	EFTYPE          = 79,
+	EAUTH           = 80,
+	ENEEDAUTH       = 81,
+	EIDRM           = 82,
+	ENOMSG          = 83,
+	EOVERFLOW       = 84,
+	ECANCELED       = 85,
+	EILSEQ          = 86,
+	ENOATTR         = 87,
+	EDOOFUS         = 88,
+	EBADMSG         = 89,
+	EMULTIHOP       = 90,
+	ENOLINK         = 91,
+	EPROTO          = 92,
+	ENOTCAPABLE     = 93,
+	ECAPMODE        = 94,
+	ENOTRECOVERABLE = 95,
+	EOWNERDEAD      = 96,
+}
+EPERM           :: Platform_Error.EPERM
+ENOENT          :: Platform_Error.ENOENT
+ESRCH           :: Platform_Error.ESRCH
+EINTR           :: Platform_Error.EINTR
+EIO             :: Platform_Error.EIO
+ENXIO           :: Platform_Error.ENXIO
+E2BIG           :: Platform_Error.E2BIG
+ENOEXEC         :: Platform_Error.ENOEXEC
+EBADF           :: Platform_Error.EBADF
+ECHILD          :: Platform_Error.ECHILD
+EBEADLK         :: Platform_Error.EBEADLK
+ENOMEM          :: Platform_Error.ENOMEM
+EACCESS         :: Platform_Error.EACCESS
+EFAULT          :: Platform_Error.EFAULT
+ENOTBLK         :: Platform_Error.ENOTBLK
+EBUSY           :: Platform_Error.EBUSY
+EEXIST          :: Platform_Error.EEXIST
+EXDEV           :: Platform_Error.EXDEV
+ENODEV          :: Platform_Error.ENODEV
+ENOTDIR         :: Platform_Error.ENOTDIR
+EISDIR          :: Platform_Error.EISDIR
+EINVAL          :: Platform_Error.EINVAL
+ENFILE          :: Platform_Error.ENFILE
+EMFILE          :: Platform_Error.EMFILE
+ENOTTY          :: Platform_Error.ENOTTY
+ETXTBSY         :: Platform_Error.ETXTBSY
+EFBIG           :: Platform_Error.EFBIG
+ENOSPC          :: Platform_Error.ENOSPC
+ESPIPE          :: Platform_Error.ESPIPE
+EROFS           :: Platform_Error.EROFS
+EMLINK          :: Platform_Error.EMLINK
+EPIPE           :: Platform_Error.EPIPE
+EDOM            :: Platform_Error.EDOM
+ERANGE          :: Platform_Error.ERANGE
+EAGAIN          :: Platform_Error.EAGAIN
+EINPROGRESS     :: Platform_Error.EINPROGRESS
+EALREADY        :: Platform_Error.EALREADY
+ENOTSOCK        :: Platform_Error.ENOTSOCK
+EDESTADDRREQ    :: Platform_Error.EDESTADDRREQ
+EMSGSIZE        :: Platform_Error.EMSGSIZE
+EPROTOTYPE      :: Platform_Error.EPROTOTYPE
+ENOPROTOOPT     :: Platform_Error.ENOPROTOOPT
+EPROTONOSUPPORT :: Platform_Error.EPROTONOSUPPORT
+ESOCKTNOSUPPORT :: Platform_Error.ESOCKTNOSUPPORT
+EOPNOTSUPP      :: Platform_Error.EOPNOTSUPP
+EPFNOSUPPORT    :: Platform_Error.EPFNOSUPPORT
+EAFNOSUPPORT    :: Platform_Error.EAFNOSUPPORT
+EADDRINUSE      :: Platform_Error.EADDRINUSE
+EADDRNOTAVAIL   :: Platform_Error.EADDRNOTAVAIL
+ENETDOWN        :: Platform_Error.ENETDOWN
+ENETUNREACH     :: Platform_Error.ENETUNREACH
+ENETRESET       :: Platform_Error.ENETRESET
+ECONNABORTED    :: Platform_Error.ECONNABORTED
+ECONNRESET      :: Platform_Error.ECONNRESET
+ENOBUFS         :: Platform_Error.ENOBUFS
+EISCONN         :: Platform_Error.EISCONN
+ENOTCONN        :: Platform_Error.ENOTCONN
+ESHUTDOWN       :: Platform_Error.ESHUTDOWN
+ETIMEDOUT       :: Platform_Error.ETIMEDOUT
+ECONNREFUSED    :: Platform_Error.ECONNREFUSED
+ELOOP           :: Platform_Error.ELOOP
+ENAMETOOLING    :: Platform_Error.ENAMETOOLING
+EHOSTDOWN       :: Platform_Error.EHOSTDOWN
+EHOSTUNREACH    :: Platform_Error.EHOSTUNREACH
+ENOTEMPTY       :: Platform_Error.ENOTEMPTY
+EPROCLIM        :: Platform_Error.EPROCLIM
+EUSERS          :: Platform_Error.EUSERS
+EDQUOT          :: Platform_Error.EDQUOT
+ESTALE          :: Platform_Error.ESTALE
+EBADRPC         :: Platform_Error.EBADRPC
+ERPCMISMATCH    :: Platform_Error.ERPCMISMATCH
+EPROGUNAVAIL    :: Platform_Error.EPROGUNAVAIL
+EPROGMISMATCH   :: Platform_Error.EPROGMISMATCH
+EPROCUNAVAIL    :: Platform_Error.EPROCUNAVAIL
+ENOLCK          :: Platform_Error.ENOLCK
+ENOSYS          :: Platform_Error.ENOSYS
+EFTYPE          :: Platform_Error.EFTYPE
+EAUTH           :: Platform_Error.EAUTH
+ENEEDAUTH       :: Platform_Error.ENEEDAUTH
+EIDRM           :: Platform_Error.EIDRM
+ENOMSG          :: Platform_Error.ENOMSG
+EOVERFLOW       :: Platform_Error.EOVERFLOW
+ECANCELED       :: Platform_Error.ECANCELED
+EILSEQ          :: Platform_Error.EILSEQ
+ENOATTR         :: Platform_Error.ENOATTR
+EDOOFUS         :: Platform_Error.EDOOFUS
+EBADMSG         :: Platform_Error.EBADMSG
+EMULTIHOP       :: Platform_Error.EMULTIHOP
+ENOLINK         :: Platform_Error.ENOLINK
+EPROTO          :: Platform_Error.EPROTO
+ENOTCAPABLE     :: Platform_Error.ENOTCAPABLE
+ECAPMODE        :: Platform_Error.ECAPMODE
+ENOTRECOVERABLE :: Platform_Error.ENOTRECOVERABLE
+EOWNERDEAD      :: Platform_Error.EOWNERDEAD
 
 O_RDONLY   :: 0x00000
 O_WRONLY   :: 0x00001
@@ -258,13 +353,13 @@ S_ISGID :: 0o2000 // Set group id on execution
 S_ISVTX :: 0o1000 // Directory restrcted delete
 
 
-S_ISLNK  :: #force_inline proc(m: mode_t) -> bool { return (m & S_IFMT) == S_IFLNK  }
-S_ISREG  :: #force_inline proc(m: mode_t) -> bool { return (m & S_IFMT) == S_IFREG  }
-S_ISDIR  :: #force_inline proc(m: mode_t) -> bool { return (m & S_IFMT) == S_IFDIR  }
-S_ISCHR  :: #force_inline proc(m: mode_t) -> bool { return (m & S_IFMT) == S_IFCHR  }
-S_ISBLK  :: #force_inline proc(m: mode_t) -> bool { return (m & S_IFMT) == S_IFBLK  }
-S_ISFIFO :: #force_inline proc(m: mode_t) -> bool { return (m & S_IFMT) == S_IFIFO  }
-S_ISSOCK :: #force_inline proc(m: mode_t) -> bool { return (m & S_IFMT) == S_IFSOCK }
+@(require_results) S_ISLNK  :: #force_inline proc(m: mode_t) -> bool { return (m & S_IFMT) == S_IFLNK  }
+@(require_results) S_ISREG  :: #force_inline proc(m: mode_t) -> bool { return (m & S_IFMT) == S_IFREG  }
+@(require_results) S_ISDIR  :: #force_inline proc(m: mode_t) -> bool { return (m & S_IFMT) == S_IFDIR  }
+@(require_results) S_ISCHR  :: #force_inline proc(m: mode_t) -> bool { return (m & S_IFMT) == S_IFCHR  }
+@(require_results) S_ISBLK  :: #force_inline proc(m: mode_t) -> bool { return (m & S_IFMT) == S_IFBLK  }
+@(require_results) S_ISFIFO :: #force_inline proc(m: mode_t) -> bool { return (m & S_IFMT) == S_IFIFO  }
+@(require_results) S_ISSOCK :: #force_inline proc(m: mode_t) -> bool { return (m & S_IFMT) == S_IFSOCK }
 
 F_OK :: 0 // Test for file existance
 X_OK :: 1 // Test for execute permission
@@ -274,7 +369,7 @@ R_OK :: 4 // Test for read permission
 F_KINFO :: 22
 
 foreign libc {
-	@(link_name="__error")		__errno_location :: proc() -> ^c.int ---
+	@(link_name="__error")		__Error_location :: proc() -> ^c.int ---
 
 	@(link_name="open")             _unix_open          :: proc(path: cstring, flags: c.int, mode: c.int) -> Handle ---
 	@(link_name="close")            _unix_close         :: proc(fd: Handle) -> c.int ---
@@ -320,30 +415,38 @@ foreign dl {
 	@(link_name="pthread_getthreadid_np") pthread_getthreadid_np :: proc() -> c.int ---
 }
 
+@(require_results)
 is_path_separator :: proc(r: rune) -> bool {
 	return r == '/'
 }
 
-get_last_error :: proc "contextless" () -> int {
-	return int(__errno_location()^)
+@(require_results, no_instrumentation)
+get_last_error :: proc "contextless" () -> Error {
+	return Platform_Error(__Error_location()^)
 }
 
-open :: proc(path: string, flags: int = O_RDONLY, mode: int = 0) -> (Handle, Errno) {
+@(require_results)
+open :: proc(path: string, flags: int = O_RDONLY, mode: int = 0) -> (Handle, Error) {
 	runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD()
 	cstr := strings.clone_to_cstring(path, context.temp_allocator)
 	handle := _unix_open(cstr, c.int(flags), c.int(mode))
 	if handle == -1 {
-		return INVALID_HANDLE, Errno(get_last_error())
+		return INVALID_HANDLE, get_last_error()
 	}
-	return handle, ERROR_NONE
+	return handle, nil
 }
 
-close :: proc(fd: Handle) -> Errno {
+close :: proc(fd: Handle) -> Error {
 	result := _unix_close(fd)
 	if result == -1 {
-		return Errno(get_last_error())
+		return get_last_error()
 	}
-	return ERROR_NONE
+	return nil
+}
+
+flush :: proc(fd: Handle) -> Error {
+	// do nothing
+	return nil
 }
 
 // If you read or write more than `INT_MAX` bytes, FreeBSD returns `EINVAL`.
@@ -354,124 +457,148 @@ close :: proc(fd: Handle) -> Errno {
 @(private)
 MAX_RW :: 1 << 30
 
-read :: proc(fd: Handle, data: []byte) -> (int, Errno) {
+read :: proc(fd: Handle, data: []byte) -> (int, Error) {
 	to_read    := min(c.size_t(len(data)), MAX_RW)
 	bytes_read := _unix_read(fd, &data[0], to_read)
 	if bytes_read == -1 {
-		return -1, Errno(get_last_error())
+		return -1, get_last_error()
 	}
-	return int(bytes_read), ERROR_NONE
+	return int(bytes_read), nil
 }
 
-write :: proc(fd: Handle, data: []byte) -> (int, Errno) {
+write :: proc(fd: Handle, data: []byte) -> (int, Error) {
 	if len(data) == 0 {
-		return 0, ERROR_NONE
+		return 0, nil
 	}
 
 	to_write      := min(c.size_t(len(data)), MAX_RW)
 	bytes_written := _unix_write(fd, &data[0], to_write)
 	if bytes_written == -1 {
-		return -1, Errno(get_last_error())
+		return -1, get_last_error()
+	}
+	return int(bytes_written), nil
+}
+
+read_at :: proc(fd: Handle, data: []byte, offset: i64) -> (n: int, err: Error) {
+	curr := seek(fd, offset, SEEK_CUR) or_return
+	n, err = read(fd, data)
+	_, err1 := seek(fd, curr, SEEK_SET)
+	if err1 != nil && err == nil {
+		err = err1
+	}
+	return
+}
+
+write_at :: proc(fd: Handle, data: []byte, offset: i64) -> (n: int, err: Error) {
+	curr := seek(fd, offset, SEEK_CUR) or_return
+	n, err = write(fd, data)
+	_, err1 := seek(fd, curr, SEEK_SET)
+	if err1 != nil && err == nil {
+		err = err1
 	}
-	return int(bytes_written), ERROR_NONE
+	return
 }
 
-seek :: proc(fd: Handle, offset: i64, whence: int) -> (i64, Errno) {
+seek :: proc(fd: Handle, offset: i64, whence: int) -> (i64, Error) {
 	res := _unix_seek(fd, offset, c.int(whence))
 	if res == -1 {
-		return -1, Errno(get_last_error())
+		return -1, get_last_error()
 	}
-	return res, ERROR_NONE
+	return res, nil
 }
 
-file_size :: proc(fd: Handle) -> (i64, Errno) {
-	s, err := _fstat(fd)
-	if err != ERROR_NONE {
-		return -1, err
-	}
-	return s.size, ERROR_NONE
+@(require_results)
+file_size :: proc(fd: Handle) -> (size: i64, err: Error) {
+	size = -1
+	s := _fstat(fd) or_return
+	size = s.size
+	return
 }
 
-rename :: proc(old_path, new_path: string) -> Errno {
+rename :: proc(old_path, new_path: string) -> Error {
 	runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD()
 	old_path_cstr := strings.clone_to_cstring(old_path, context.temp_allocator)
 	new_path_cstr := strings.clone_to_cstring(new_path, context.temp_allocator)
 	res := _unix_rename(old_path_cstr, new_path_cstr)
 	if res == -1 {
-		return Errno(get_last_error())
+		return get_last_error()
 	}
-	return ERROR_NONE
+	return nil
 }
 
-remove :: proc(path: string) -> Errno {
+remove :: proc(path: string) -> Error {
 	runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD()
 	path_cstr := strings.clone_to_cstring(path, context.temp_allocator)
 	res := _unix_unlink(path_cstr)
 	if res == -1 {
-		return Errno(get_last_error())
+		return get_last_error()
 	}
-	return ERROR_NONE
+	return nil
 }
 
-make_directory :: proc(path: string, mode: mode_t = 0o775) -> Errno {
+make_directory :: proc(path: string, mode: mode_t = 0o775) -> Error {
 	runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD()
 	path_cstr := strings.clone_to_cstring(path, context.temp_allocator)
 	res := _unix_mkdir(path_cstr, mode)
 	if res == -1 {
-		return Errno(get_last_error())
+		return get_last_error()
 	}
-	return ERROR_NONE
+	return nil
 }
 
-remove_directory :: proc(path: string) -> Errno {
+remove_directory :: proc(path: string) -> Error {
 	runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD()
 	path_cstr := strings.clone_to_cstring(path, context.temp_allocator)
 	res := _unix_rmdir(path_cstr)
 	if res == -1 {
-		return Errno(get_last_error())
+		return get_last_error()
 	}
-	return ERROR_NONE
+	return nil
 }
 
+@(require_results)
 is_file_handle :: proc(fd: Handle) -> bool {
 	s, err := _fstat(fd)
-	if err != ERROR_NONE {
+	if err != nil {
 		return false
 	}
 	return S_ISREG(s.mode)
 }
 
+@(require_results)
 is_file_path :: proc(path: string, follow_links: bool = true) -> bool {
 	s: OS_Stat
-	err: Errno
+	err: Error
 	if follow_links {
 		s, err = _stat(path)
 	} else {
 		s, err = _lstat(path)
 	}
-	if err != ERROR_NONE {
+	if err != nil {
 		return false
 	}
 	return S_ISREG(s.mode)
 }
 
+@(require_results)
 is_dir_handle :: proc(fd: Handle) -> bool {
 	s, err := _fstat(fd)
-	if err != ERROR_NONE {
+	if err != nil {
 		return false
 	}
 	return S_ISDIR(s.mode)
 }
 
+@(require_results)
 is_dir_path :: proc(path: string, follow_links: bool = true) -> bool {
 	s: OS_Stat
-	err: Errno
+	err: Error
 	if follow_links {
 		s, err = _stat(path)
 	} else {
 		s, err = _lstat(path)
 	}
-	if err != ERROR_NONE {
+	if err != nil {
 		return false
 	}
 	return S_ISDIR(s.mode)
@@ -490,38 +617,40 @@ stderr: Handle = 2
 last_write_time :: proc(fd: Handle) -> File_Time {}                                                                  
 last_write_time_by_name :: proc(name: string) -> File_Time {}                                                        
 */
-last_write_time :: proc(fd: Handle) -> (File_Time, Errno) {
+@(require_results)
+last_write_time :: proc(fd: Handle) -> (File_Time, Error) {
 	s, err := _fstat(fd)
-	if err != ERROR_NONE {
+	if err != nil {
 		return 0, err
 	}
 	modified := s.modified.seconds * 1_000_000_000 + s.modified.nanoseconds
-	return File_Time(modified), ERROR_NONE
+	return File_Time(modified), nil
 }
 
-last_write_time_by_name :: proc(name: string) -> (File_Time, Errno) {
+@(require_results)
+last_write_time_by_name :: proc(name: string) -> (File_Time, Error) {
 	s, err := _stat(name)
-	if err != ERROR_NONE {
+	if err != nil {
 		return 0, err
 	}
 	modified := s.modified.seconds * 1_000_000_000 + s.modified.nanoseconds
-	return File_Time(modified), ERROR_NONE
+	return File_Time(modified), nil
 }
 
-@private
-_stat :: proc(path: string) -> (OS_Stat, Errno) {
+@(private, require_results)
+_stat :: proc(path: string) -> (OS_Stat, Error) {
 	runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD()
 	cstr := strings.clone_to_cstring(path, context.temp_allocator)
 	s: OS_Stat = ---
 	result := _unix_lstat(cstr, &s)
 	if result == -1 {
-		return s, Errno(get_last_error())
+		return s, get_last_error()
 	}
-	return s, ERROR_NONE
+	return s, nil
 }
 
-@private
-_lstat :: proc(path: string) -> (OS_Stat, Errno) {
+@(private, require_results)
+_lstat :: proc(path: string) -> (OS_Stat, Error) {
 	runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD()
 	cstr := strings.clone_to_cstring(path, context.temp_allocator)
 	
@@ -529,54 +658,53 @@ _lstat :: proc(path: string) -> (OS_Stat, Errno) {
 	s: OS_Stat = ---
 	res := _unix_lstat(cstr, &s)
 	if res == -1 {
-		return s, Errno(get_last_error())
+		return s, get_last_error()
 	}
-	return s, ERROR_NONE
+	return s, nil
 }
 
-@private
-_fstat :: proc(fd: Handle) -> (OS_Stat, Errno) {
+@(private, require_results)
+_fstat :: proc(fd: Handle) -> (OS_Stat, Error) {
 	s: OS_Stat = ---
 	result := _unix_fstat(fd, &s)
 	if result == -1 {
-		return s, Errno(get_last_error())
+		return s, get_last_error()
 	}
-	return s, ERROR_NONE
+	return s, nil
 }
 
-@private
-_fdopendir :: proc(fd: Handle) -> (Dir, Errno) {
+@(private, require_results)
+_fdopendir :: proc(fd: Handle) -> (Dir, Error) {
 	dirp := _unix_fdopendir(fd)
 	if dirp == cast(Dir)nil {
-		return nil, Errno(get_last_error())
+		return nil, get_last_error()
 	}
-	return dirp, ERROR_NONE
+	return dirp, nil
 }
 
-@private
-_closedir :: proc(dirp: Dir) -> Errno {
+@(private)
+_closedir :: proc(dirp: Dir) -> Error {
 	rc := _unix_closedir(dirp)
 	if rc != 0 {
-		return Errno(get_last_error())
+		return get_last_error()
 	}
-	return ERROR_NONE
+	return nil
 }
 
-@private
+@(private)
 _rewinddir :: proc(dirp: Dir) {
 	_unix_rewinddir(dirp)
 }
 
-@private
-_readdir :: proc(dirp: Dir) -> (entry: Dirent, err: Errno, end_of_stream: bool) {
+@(private, require_results)
+_readdir :: proc(dirp: Dir) -> (entry: Dirent, err: Error, end_of_stream: bool) {
 	result: ^Dirent
 	rc := _unix_readdir_r(dirp, &entry, &result)
 
 	if rc != 0 {
-		err = Errno(get_last_error())
+		err = get_last_error()
 		return
 	}
-	err = ERROR_NONE
 
 	if result == nil {
 		end_of_stream = true
@@ -586,8 +714,8 @@ _readdir :: proc(dirp: Dir) -> (entry: Dirent, err: Errno, end_of_stream: bool)
 	return
 }
 
-@private
-_readlink :: proc(path: string) -> (string, Errno) {
+@(private, require_results)
+_readlink :: proc(path: string) -> (string, Error) {
 	runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD(ignore = context.temp_allocator == context.allocator)
 
 	path_cstr := strings.clone_to_cstring(path, context.temp_allocator)
@@ -598,20 +726,21 @@ _readlink :: proc(path: string) -> (string, Errno) {
 		rc := _unix_readlink(path_cstr, &(buf[0]), bufsz)
 		if rc == -1 {
 			delete(buf)
-			return "", Errno(get_last_error())
+			return "", get_last_error()
 		} else if rc == int(bufsz) {
 			bufsz += MAX_PATH
 			delete(buf)
 			buf = make([]byte, bufsz)
 		} else {
-			return strings.string_from_ptr(&buf[0], rc), ERROR_NONE
+			return strings.string_from_ptr(&buf[0], rc), nil
 		}	
 	}
 
-	return "", Errno{}
+	return "", Error{}
 }
 
-absolute_path_from_handle :: proc(fd: Handle) -> (string, Errno) {
+@(require_results)
+absolute_path_from_handle :: proc(fd: Handle) -> (string, Error) {
 	// NOTE(Feoramund): The situation isn't ideal, but this was the best way I
 	// could find to implement this. There are a couple outstanding bug reports
 	// regarding the desire to retrieve an absolute path from a handle, but to
@@ -626,14 +755,15 @@ absolute_path_from_handle :: proc(fd: Handle) -> (string, Errno) {
 
 	res := _unix_fcntl(fd, F_KINFO, cast(uintptr)&kinfo)
 	if res == -1 {
-		return "", Errno(get_last_error())
+		return "", get_last_error()
 	}
 
 	path := strings.clone_from_cstring_bounded(cast(cstring)&kinfo.path[0], len(kinfo.path))
-	return path, ERROR_NONE
+	return path, nil
 }
 
-absolute_path_from_relative :: proc(rel: string) -> (path: string, err: Errno) {
+@(require_results)
+absolute_path_from_relative :: proc(rel: string) -> (path: string, err: Error) {
 	rel := rel
 	if rel == "" {
 		rel = "."
@@ -644,27 +774,28 @@ absolute_path_from_relative :: proc(rel: string) -> (path: string, err: Errno) {
 
 	path_ptr := _unix_realpath(rel_cstr, nil)
 	if path_ptr == nil {
-		return "", Errno(get_last_error())
+		return "", get_last_error()
 	}
 	defer _unix_free(path_ptr)
 
 
 	path = strings.clone(string(cstring(path_ptr)))
 
-	return path, ERROR_NONE
+	return path, nil
 }
 
-access :: proc(path: string, mask: int) -> (bool, Errno) {
+access :: proc(path: string, mask: int) -> (bool, Error) {
 	runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD()
 
 	cstr := strings.clone_to_cstring(path, context.temp_allocator)
 	result := _unix_access(cstr, c.int(mask))
 	if result == -1 {
-		return false, Errno(get_last_error())
+		return false, get_last_error()
 	}
-	return true, ERROR_NONE
+	return true, nil
 }
 
+@(require_results)
 lookup_env :: proc(key: string, allocator := context.allocator) -> (value: string, found: bool) {
 	runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD(ignore = context.temp_allocator == allocator)
 
@@ -676,11 +807,13 @@ lookup_env :: proc(key: string, allocator := context.allocator) -> (value: strin
 	return strings.clone(string(cstr), allocator), true
 }
 
+@(require_results)
 get_env :: proc(key: string, allocator := context.allocator) -> (value: string) {
 	value, _ = lookup_env(key, allocator)
 	return
 }
 
+@(require_results)
 get_current_directory :: proc() -> string {
 	// NOTE(tetra): I would use PATH_MAX here, but I was not able to find
 	// an authoritative value for it across all systems.
@@ -692,7 +825,7 @@ get_current_directory :: proc() -> string {
 		if cwd != nil {
 			return string(cwd)
 		}
-		if Errno(get_last_error()) != ERANGE {
+		if get_last_error() != ERANGE {
 			delete(buf)
 			return ""
 		}
@@ -701,14 +834,14 @@ get_current_directory :: proc() -> string {
 	unreachable()
 }
 
-set_current_directory :: proc(path: string) -> (err: Errno) {
+set_current_directory :: proc(path: string) -> (err: Error) {
 	runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD()
 	cstr := strings.clone_to_cstring(path, context.temp_allocator)
 	res := _unix_chdir(cstr)
 	if res == -1 {
-		return Errno(get_last_error())
+		return get_last_error()
 	}
-	return ERROR_NONE
+	return nil
 }
 
 exit :: proc "contextless" (code: int) -> ! {
@@ -716,16 +849,19 @@ exit :: proc "contextless" (code: int) -> ! {
 	_unix_exit(c.int(code))
 }
 
+@(require_results)
 current_thread_id :: proc "contextless" () -> int {
 	return cast(int) pthread_getthreadid_np()
 }
 
+@(require_results)
 dlopen :: proc(filename: string, flags: int) -> rawptr {
 	runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD()
 	cstr := strings.clone_to_cstring(filename, context.temp_allocator)
 	handle := _unix_dlopen(cstr, c.int(flags))
 	return handle
 }
+@(require_results)
 dlsym :: proc(handle: rawptr, symbol: string) -> rawptr {
 	assert(handle != nil)
 	runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD()
@@ -741,6 +877,7 @@ dlerror :: proc() -> string {
 	return string(_unix_dlerror())
 }
 
+@(require_results)
 get_page_size :: proc() -> int {
 	// NOTE(tetra): The page size never changes, so why do anything complicated
 	// if we don't have to.
@@ -753,7 +890,7 @@ get_page_size :: proc() -> int {
 	return page_size
 }
 
-@(private)
+@(private, require_results)
 _processor_core_count :: proc() -> int {
 	count : int = 0
 	count_size := size_of(count)
@@ -767,6 +904,7 @@ _processor_core_count :: proc() -> int {
 }
 
 
+@(require_results)
 _alloc_command_line_arguments :: proc() -> []string {
 	res := make([]string, len(runtime.args__))
 	for arg, i in runtime.args__ {

+ 101 - 69
core/os/os_haiku.odin

@@ -10,16 +10,14 @@ import "core:sys/haiku"
 Handle    :: i32
 Pid       :: i32
 File_Time :: i64
-Errno     :: i32
+_Platform_Error :: haiku.Errno
 
 MAX_PATH :: haiku.PATH_MAX
 
-ENOSYS :: int(haiku.Errno.POSIX_ERROR_BASE) + 9
+ENOSYS :: _Platform_Error(i32(haiku.Errno.POSIX_ERROR_BASE) + 9)
 
 INVALID_HANDLE :: ~Handle(0)
 
-ERROR_NONE: Errno: 0
-
 stdin:  Handle = 0
 stdout: Handle = 1
 stderr: Handle = 2
@@ -121,7 +119,7 @@ S_ISSOCK :: #force_inline proc(m: u32) -> bool { return (m & S_IFMT) == S_IFSOCK
 
 
 foreign libc {
-	@(link_name="_errnop")	__error		:: proc() -> ^c.int ---
+	@(link_name="_errorp")	__error		:: proc() -> ^c.int ---
 
 	@(link_name="fork")	_unix_fork	:: proc() -> pid_t ---
 	@(link_name="getthrid")	_unix_getthrid	:: proc() -> int ---
@@ -179,38 +177,47 @@ Dirent :: struct {
 
 Dir :: distinct rawptr // DIR*
 
+@(require_results)
 is_path_separator :: proc(r: rune) -> bool {
 	return r == '/'
 }
 
-get_last_error :: proc "contextless" () -> int {
-	return int(__error()^)
+@(require_results, no_instrumentation)
+get_last_error :: proc "contextless" () -> Error {
+	return Platform_Error(__error()^)
 }
 
-fork :: proc() -> (Pid, Errno) {
+@(require_results)
+fork :: proc() -> (Pid, Error) {
 	pid := _unix_fork()
 	if pid == -1 {
-		return Pid(-1), Errno(get_last_error())
+		return Pid(-1), get_last_error()
 	}
-	return Pid(pid), ERROR_NONE
+	return Pid(pid), nil
 }
 
-open :: proc(path: string, flags: int = O_RDONLY, mode: int = 0) -> (Handle, Errno) {
+@(require_results)
+open :: proc(path: string, flags: int = O_RDONLY, mode: int = 0) -> (Handle, Error) {
 	runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD()
 	cstr := strings.clone_to_cstring(path, context.temp_allocator)
 	handle := _unix_open(cstr, c.int(flags), c.int(mode))
 	if handle == -1 {
-		return INVALID_HANDLE, Errno(get_last_error())
+		return INVALID_HANDLE, get_last_error()
 	}
-	return handle, ERROR_NONE
+	return handle, nil
 }
 
-close :: proc(fd: Handle) -> Errno {
+close :: proc(fd: Handle) -> Error {
 	result := _unix_close(fd)
 	if result == -1 {
-		return Errno(get_last_error())
+		return get_last_error()
 	}
-	return ERROR_NONE
+	return nil
+}
+
+flush :: proc(fd: Handle) -> Error {
+	// do nothing
+	return nil
 }
 
 // In practice a read/write call would probably never read/write these big buffers all at once,
@@ -220,47 +227,69 @@ close :: proc(fd: Handle) -> Errno {
 @(private)
 MAX_RW :: 1 << 30
 
-read :: proc(fd: Handle, data: []byte) -> (int, Errno) {
+read :: proc(fd: Handle, data: []byte) -> (int, Error) {
 	to_read    := min(c.size_t(len(data)), MAX_RW)
 	bytes_read := _unix_read(fd, &data[0], to_read)
 	if bytes_read == -1 {
-		return -1, Errno(get_last_error())
+		return -1, get_last_error()
 	}
-	return int(bytes_read), ERROR_NONE
+	return int(bytes_read), nil
 }
 
-write :: proc(fd: Handle, data: []byte) -> (int, Errno) {
+write :: proc(fd: Handle, data: []byte) -> (int, Error) {
 	if len(data) == 0 {
-		return 0, ERROR_NONE
+		return 0, nil
 	}
 
 	to_write      := min(c.size_t(len(data)), MAX_RW)
 	bytes_written := _unix_write(fd, &data[0], to_write)
 	if bytes_written == -1 {
-		return -1, Errno(get_last_error())
+		return -1, get_last_error()
+	}
+	return int(bytes_written), nil
+}
+
+read_at :: proc(fd: Handle, data: []byte, offset: i64) -> (n: int, err: Error) {
+	curr := seek(fd, offset, SEEK_CUR) or_return
+	n, err = read(fd, data)
+	_, err1 := seek(fd, curr, SEEK_SET)
+	if err1 != nil && err == nil {
+		err = err1
 	}
-	return int(bytes_written), ERROR_NONE
+	return
 }
 
-seek :: proc(fd: Handle, offset: i64, whence: int) -> (i64, Errno) {
+write_at :: proc(fd: Handle, data: []byte, offset: i64) -> (n: int, err: Error) {
+	curr := seek(fd, offset, SEEK_CUR) or_return
+	n, err = write(fd, data)
+	_, err1 := seek(fd, curr, SEEK_SET)
+	if err1 != nil && err == nil {
+		err = err1
+	}
+	return
+}
+
+seek :: proc(fd: Handle, offset: i64, whence: int) -> (i64, Error) {
 	res := _unix_seek(fd, offset, c.int(whence))
 	if res == -1 {
-		return -1, Errno(get_last_error())
+		return -1, get_last_error()
 	}
-	return res, ERROR_NONE
+	return res, nil
 }
 
-file_size :: proc(fd: Handle) -> (i64, Errno) {
+@(require_results)
+file_size :: proc(fd: Handle) -> (i64, Error) {
 	s, err := _fstat(fd)
-	if err != ERROR_NONE {
+	if err != nil {
 		return -1, err
 	}
-	return s.size, ERROR_NONE
+	return s.size, nil
 }
 
 // "Argv" arguments converted to Odin strings
 args := _alloc_command_line_arguments()
 
+@(require_results)
 _alloc_command_line_arguments :: proc() -> []string {
 	res := make([]string, len(runtime.args__))
 	for arg, i in runtime.args__ {
@@ -269,8 +298,8 @@ _alloc_command_line_arguments :: proc() -> []string {
 	return res
 }
 
-@private
-_stat :: proc(path: string) -> (OS_Stat, Errno) {
+@(private, require_results)
+_stat :: proc(path: string) -> (OS_Stat, Error) {
 	runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD()
 	cstr := strings.clone_to_cstring(path, context.temp_allocator)
 
@@ -278,13 +307,13 @@ _stat :: proc(path: string) -> (OS_Stat, Errno) {
 	s: OS_Stat = ---
 	res := _unix_stat(cstr, &s)
 	if res == -1 {
-		return s, Errno(get_last_error())
+		return s, get_last_error()
 	}
-	return s, ERROR_NONE
+	return s, nil
 }
 
-@private
-_lstat :: proc(path: string) -> (OS_Stat, Errno) {
+@(private, require_results)
+_lstat :: proc(path: string) -> (OS_Stat, Error) {
 	runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD()
 	cstr := strings.clone_to_cstring(path, context.temp_allocator)
 
@@ -292,55 +321,54 @@ _lstat :: proc(path: string) -> (OS_Stat, Errno) {
 	s: OS_Stat = ---
 	res := _unix_lstat(cstr, &s)
 	if res == -1 {
-		return s, Errno(get_last_error())
+		return s, get_last_error()
 	}
-	return s, ERROR_NONE
+	return s, nil
 }
 
-@private
-_fstat :: proc(fd: Handle) -> (OS_Stat, Errno) {
+@(private, require_results)
+_fstat :: proc(fd: Handle) -> (OS_Stat, Error) {
 	// deliberately uninitialized
 	s: OS_Stat = ---
 	res := _unix_fstat(fd, &s)
 	if res == -1 {
-		return s, Errno(get_last_error())
+		return s, get_last_error()
 	}
-	return s, ERROR_NONE
+	return s, nil
 }
 
-@private
-_fdopendir :: proc(fd: Handle) -> (Dir, Errno) {
+@(private)
+_fdopendir :: proc(fd: Handle) -> (Dir, Error) {
 	dirp := _unix_fdopendir(fd)
 	if dirp == cast(Dir)nil {
-		return nil, Errno(get_last_error())
+		return nil, get_last_error()
 	}
-	return dirp, ERROR_NONE
+	return dirp, nil
 }
 
-@private
-_closedir :: proc(dirp: Dir) -> Errno {
+@(private)
+_closedir :: proc(dirp: Dir) -> Error {
 	rc := _unix_closedir(dirp)
 	if rc != 0 {
-		return Errno(get_last_error())
+		return get_last_error()
 	}
-	return ERROR_NONE
+	return nil
 }
 
-@private
+@(private)
 _rewinddir :: proc(dirp: Dir) {
 	_unix_rewinddir(dirp)
 }
 
-@private
-_readdir :: proc(dirp: Dir) -> (entry: Dirent, err: Errno, end_of_stream: bool) {
+@(private, require_results)
+_readdir :: proc(dirp: Dir) -> (entry: Dirent, err: Error, end_of_stream: bool) {
 	result: ^Dirent
 	rc := _unix_readdir_r(dirp, &entry, &result)
 
 	if rc != 0 {
-		err = Errno(get_last_error())
+		err = get_last_error()
 		return
 	}
-	err = ERROR_NONE
 
 	if result == nil {
 		end_of_stream = true
@@ -350,8 +378,8 @@ _readdir :: proc(dirp: Dir) -> (entry: Dirent, err: Errno, end_of_stream: bool)
 	return
 }
 
-@private
-_readlink :: proc(path: string) -> (string, Errno) {
+@(private, require_results)
+_readlink :: proc(path: string) -> (string, Error) {
 	runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD(ignore = context.temp_allocator == context.allocator)
 	path_cstr := strings.clone_to_cstring(path, context.temp_allocator)
 
@@ -361,22 +389,24 @@ _readlink :: proc(path: string) -> (string, Errno) {
 		rc := _unix_readlink(path_cstr, &(buf[0]), bufsz)
 		if rc == -1 {
 			delete(buf)
-			return "", Errno(get_last_error())
+			return "", get_last_error()
 		} else if rc == int(bufsz) {
 			bufsz += MAX_PATH
 			delete(buf)
 			buf = make([]byte, bufsz)
 		} else {
-			return strings.string_from_ptr(&buf[0], rc), ERROR_NONE
+			return strings.string_from_ptr(&buf[0], rc), nil
 		}	
 	}
 }
 
-absolute_path_from_handle :: proc(fd: Handle) -> (string, Errno) {
-	return "", Errno(ENOSYS)
+@(require_results)
+absolute_path_from_handle :: proc(fd: Handle) -> (string, Error) {
+	return "", Error(ENOSYS)
 }
 
-absolute_path_from_relative :: proc(rel: string) -> (path: string, err: Errno) {
+@(require_results)
+absolute_path_from_relative :: proc(rel: string) -> (path: string, err: Error) {
 	rel := rel
 	if rel == "" {
 		rel = "."
@@ -387,26 +417,27 @@ absolute_path_from_relative :: proc(rel: string) -> (path: string, err: Errno) {
 
 	path_ptr := _unix_realpath(rel_cstr, nil)
 	if path_ptr == nil {
-		return "", Errno(get_last_error())
+		return "", get_last_error()
 	}
 	defer _unix_free(path_ptr)
 
-	path_cstr := transmute(cstring)path_ptr
-	path = strings.clone( string(path_cstr) )
+	path_cstr := cstring(path_ptr)
+	path = strings.clone(string(path_cstr))
 
-	return path, ERROR_NONE
+	return path, nil
 }
 
-access :: proc(path: string, mask: int) -> (bool, Errno) {
+access :: proc(path: string, mask: int) -> (bool, Error) {
 	runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD()
 	cstr := strings.clone_to_cstring(path, context.temp_allocator)
 	res := _unix_access(cstr, c.int(mask))
 	if res == -1 {
-		return false, Errno(get_last_error())
+		return false, get_last_error()
 	}
-	return true, ERROR_NONE
+	return true, nil
 }
 
+@(require_results)
 lookup_env :: proc(key: string, allocator := context.allocator) -> (value: string, found: bool) {
 	runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD(ignore = context.temp_allocator == allocator)
 	path_str := strings.clone_to_cstring(key, context.temp_allocator)
@@ -417,12 +448,13 @@ lookup_env :: proc(key: string, allocator := context.allocator) -> (value: strin
 	return strings.clone(string(cstr), allocator), true
 }
 
+@(require_results)
 get_env :: proc(key: string, allocator := context.allocator) -> (value: string) {
 	value, _ = lookup_env(key, allocator)
 	return
 }
 
-@(private)
+@(private, require_results)
 _processor_core_count :: proc() -> int {
 	info: haiku.system_info
 	haiku.get_system_info(&info)

+ 106 - 61
core/os/os_js.odin

@@ -3,42 +3,45 @@ package os
 
 import "base:runtime"
 
+@(require_results)
 is_path_separator :: proc(c: byte) -> bool {
 	return c == '/' || c == '\\'
 }
 
-open :: proc(path: string, mode: int = O_RDONLY, perm: int = 0) -> (Handle, Errno) {
+@(require_results)
+open :: proc(path: string, mode: int = O_RDONLY, perm: int = 0) -> (Handle, Error) {
 	unimplemented("core:os procedure not supported on JS target")
 }
 
-close :: proc(fd: Handle) -> Errno {
+close :: proc(fd: Handle) -> Error {
 	unimplemented("core:os procedure not supported on JS target")
 }
 
-flush :: proc(fd: Handle) -> (err: Errno) {
+flush :: proc(fd: Handle) -> (err: Error) {
 	unimplemented("core:os procedure not supported on JS target")
 }
 
 
 
-write :: proc(fd: Handle, data: []byte) -> (int, Errno) {
+write :: proc(fd: Handle, data: []byte) -> (int, Error) {
 	unimplemented("core:os procedure not supported on JS target")
 }
 
 @(private="file")
-read_console :: proc(handle: Handle, b: []byte) -> (n: int, err: Errno) {
+read_console :: proc(handle: Handle, b: []byte) -> (n: int, err: Error) {
 	unimplemented("core:os procedure not supported on JS target")
 }
 
-read :: proc(fd: Handle, data: []byte) -> (int, Errno) {
+read :: proc(fd: Handle, data: []byte) -> (int, Error) {
 	unimplemented("core:os procedure not supported on JS target")
 }
 
-seek :: proc(fd: Handle, offset: i64, whence: int) -> (i64, Errno) {
+seek :: proc(fd: Handle, offset: i64, whence: int) -> (i64, Error) {
 	unimplemented("core:os procedure not supported on JS target")
 }
 
-file_size :: proc(fd: Handle) -> (i64, Errno) {
+@(require_results)
+file_size :: proc(fd: Handle) -> (i64, Error) {
 	unimplemented("core:os procedure not supported on JS target")
 }
 
@@ -47,38 +50,42 @@ file_size :: proc(fd: Handle) -> (i64, Errno) {
 MAX_RW :: 1<<30
 
 @(private)
-pread :: proc(fd: Handle, data: []byte, offset: i64) -> (int, Errno) {
+pread :: proc(fd: Handle, data: []byte, offset: i64) -> (int, Error) {
 	unimplemented("core:os procedure not supported on JS target")
 }
 @(private)
-pwrite :: proc(fd: Handle, data: []byte, offset: i64) -> (int, Errno) {
+pwrite :: proc(fd: Handle, data: []byte, offset: i64) -> (int, Error) {
 	unimplemented("core:os procedure not supported on JS target")
 }
 
-read_at :: proc(fd: Handle, data: []byte, offset: i64) -> (n: int, err: Errno) {
+read_at :: proc(fd: Handle, data: []byte, offset: i64) -> (n: int, err: Error) {
 	unimplemented("core:os procedure not supported on JS target")
 }
-write_at :: proc(fd: Handle, data: []byte, offset: i64) -> (n: int, err: Errno) {
+write_at :: proc(fd: Handle, data: []byte, offset: i64) -> (n: int, err: Error) {
 	unimplemented("core:os procedure not supported on JS target")
 }
 
 stdout: Handle = 1
 stderr: Handle = 2
 
+@(require_results)
 get_std_handle :: proc "contextless" (h: uint) -> Handle {
 	context = runtime.default_context()
 	unimplemented("core:os procedure not supported on JS target")
 }
 
 
+@(require_results)
 exists :: proc(path: string) -> bool {
 	unimplemented("core:os procedure not supported on JS target")
 }
 
+@(require_results)
 is_file :: proc(path: string) -> bool {
 	unimplemented("core:os procedure not supported on JS target")
 }
 
+@(require_results)
 is_dir :: proc(path: string) -> bool {
 	unimplemented("core:os procedure not supported on JS target")
 }
@@ -86,82 +93,118 @@ is_dir :: proc(path: string) -> bool {
 // NOTE(tetra): GetCurrentDirectory is not thread safe with SetCurrentDirectory and GetFullPathName
 //@private cwd_lock := win32.SRWLOCK{} // zero is initialized
 
+@(require_results)
 get_current_directory :: proc(allocator := context.allocator) -> string {
 	unimplemented("core:os procedure not supported on JS target")
 }
 
-set_current_directory :: proc(path: string) -> (err: Errno) {
+set_current_directory :: proc(path: string) -> (err: Error) {
 	unimplemented("core:os procedure not supported on JS target")
 }
 
 
 
-change_directory :: proc(path: string) -> (err: Errno) {
+change_directory :: proc(path: string) -> (err: Error) {
 	unimplemented("core:os procedure not supported on JS target")
 }
 
-make_directory :: proc(path: string, mode: u32 = 0) -> (err: Errno) {
+make_directory :: proc(path: string, mode: u32 = 0) -> (err: Error) {
 	unimplemented("core:os procedure not supported on JS target")
 }
 
 
-remove_directory :: proc(path: string) -> (err: Errno) {
+remove_directory :: proc(path: string) -> (err: Error) {
 	unimplemented("core:os procedure not supported on JS target")
 }
 
 
 
-@(private)
+@(private, require_results)
 is_abs :: proc(path: string) -> bool {
 	unimplemented("core:os procedure not supported on JS target")
 }
 
-@(private)
+@(private, require_results)
 fix_long_path :: proc(path: string) -> string {
 	unimplemented("core:os procedure not supported on JS target")
 }
 
 
-link :: proc(old_name, new_name: string) -> (err: Errno) {
+link :: proc(old_name, new_name: string) -> (err: Error) {
 	unimplemented("core:os procedure not supported on JS target")
 }
 
-unlink :: proc(path: string) -> (err: Errno) {
+unlink :: proc(path: string) -> (err: Error) {
 	unimplemented("core:os procedure not supported on JS target")
 }
 
 
 
-rename :: proc(old_path, new_path: string) -> (err: Errno) {
+rename :: proc(old_path, new_path: string) -> (err: Error) {
 	unimplemented("core:os procedure not supported on JS target")
 }
 
 
-ftruncate :: proc(fd: Handle, length: i64) -> (err: Errno) {
+ftruncate :: proc(fd: Handle, length: i64) -> (err: Error) {
 	unimplemented("core:os procedure not supported on JS target")
 }
 
-truncate :: proc(path: string, length: i64) -> (err: Errno) {
+truncate :: proc(path: string, length: i64) -> (err: Error) {
 	unimplemented("core:os procedure not supported on JS target")
 }
 
 
-remove :: proc(name: string) -> Errno {
+remove :: proc(name: string) -> Error {
 	unimplemented("core:os procedure not supported on JS target")
 }
 
 
-pipe :: proc() -> (r, w: Handle, err: Errno) {
+@(require_results)
+pipe :: proc() -> (r, w: Handle, err: Error) {
 	unimplemented("core:os procedure not supported on JS target")
 }
 
-read_dir :: proc(fd: Handle, n: int, allocator := context.allocator) -> (fi: []File_Info, err: Errno) {
+@(require_results)
+read_dir :: proc(fd: Handle, n: int, allocator := context.allocator) -> (fi: []File_Info, err: Error) {
 	unimplemented("core:os procedure not supported on JS target")
 }
 
 Handle    :: distinct uintptr
 File_Time :: distinct u64
-Errno     :: distinct int
+
+_Platform_Error :: enum i32 {
+	NONE = 0,
+	FILE_NOT_FOUND      = 2,
+	PATH_NOT_FOUND      = 3,
+	ACCESS_DENIED       = 5,
+	INVALID_HANDLE      = 6,
+	NOT_ENOUGH_MEMORY   = 8,
+	NO_MORE_FILES       = 18,
+	HANDLE_EOF          = 38,
+	NETNAME_DELETED     = 64,
+	FILE_EXISTS         = 80,
+	INVALID_PARAMETER   = 87,
+	BROKEN_PIPE         = 109,
+	BUFFER_OVERFLOW     = 111,
+	INSUFFICIENT_BUFFER = 122,
+	MOD_NOT_FOUND       = 126,
+	PROC_NOT_FOUND      = 127,
+	DIR_NOT_EMPTY       = 145,
+	ALREADY_EXISTS      = 183,
+	ENVVAR_NOT_FOUND    = 203,
+	MORE_DATA           = 234,
+	OPERATION_ABORTED   = 995,
+	IO_PENDING          = 997,
+	NOT_FOUND           = 1168,
+	PRIVILEGE_NOT_HELD  = 1314,
+	WSAEACCES             = 10013,
+	WSAECONNRESET         = 10054,
+
+	// Windows reserves errors >= 1<<29 for application use
+	FILE_IS_PIPE    = 1<<29 + 0,
+	FILE_IS_NOT_DIR = 1<<29 + 1,
+	NEGATIVE_OFFSET = 1<<29 + 2,
+}
 
 
 INVALID_HANDLE :: ~Handle(0)
@@ -182,37 +225,34 @@ O_ASYNC    :: 0x02000
 O_CLOEXEC  :: 0x80000
 
 
-ERROR_NONE:                   Errno : 0
-ERROR_FILE_NOT_FOUND:         Errno : 2
-ERROR_PATH_NOT_FOUND:         Errno : 3
-ERROR_ACCESS_DENIED:          Errno : 5
-ERROR_INVALID_HANDLE:         Errno : 6
-ERROR_NOT_ENOUGH_MEMORY:      Errno : 8
-ERROR_NO_MORE_FILES:          Errno : 18
-ERROR_HANDLE_EOF:             Errno : 38
-ERROR_NETNAME_DELETED:        Errno : 64
-ERROR_FILE_EXISTS:            Errno : 80
-ERROR_INVALID_PARAMETER:      Errno : 87
-ERROR_BROKEN_PIPE:            Errno : 109
-ERROR_BUFFER_OVERFLOW:        Errno : 111
-ERROR_INSUFFICIENT_BUFFER:    Errno : 122
-ERROR_MOD_NOT_FOUND:          Errno : 126
-ERROR_PROC_NOT_FOUND:         Errno : 127
-ERROR_DIR_NOT_EMPTY:          Errno : 145
-ERROR_ALREADY_EXISTS:         Errno : 183
-ERROR_ENVVAR_NOT_FOUND:       Errno : 203
-ERROR_MORE_DATA:              Errno : 234
-ERROR_OPERATION_ABORTED:      Errno : 995
-ERROR_IO_PENDING:             Errno : 997
-ERROR_NOT_FOUND:              Errno : 1168
-ERROR_PRIVILEGE_NOT_HELD:     Errno : 1314
-WSAEACCES:                    Errno : 10013
-WSAECONNRESET:                Errno : 10054
-
-// Windows reserves errors >= 1<<29 for application use
-ERROR_FILE_IS_PIPE:           Errno : 1<<29 + 0
-ERROR_FILE_IS_NOT_DIR:        Errno : 1<<29 + 1
-ERROR_NEGATIVE_OFFSET:        Errno : 1<<29 + 2
+ERROR_FILE_NOT_FOUND      :: Platform_Error.FILE_NOT_FOUND
+ERROR_PATH_NOT_FOUND      :: Platform_Error.PATH_NOT_FOUND
+ERROR_ACCESS_DENIED       :: Platform_Error.ACCESS_DENIED
+ERROR_INVALID_HANDLE      :: Platform_Error.INVALID_HANDLE
+ERROR_NOT_ENOUGH_MEMORY   :: Platform_Error.NOT_ENOUGH_MEMORY
+ERROR_NO_MORE_FILES       :: Platform_Error.NO_MORE_FILES
+ERROR_HANDLE_EOF          :: Platform_Error.HANDLE_EOF
+ERROR_NETNAME_DELETED     :: Platform_Error.NETNAME_DELETED
+ERROR_FILE_EXISTS         :: Platform_Error.FILE_EXISTS
+ERROR_INVALID_PARAMETER   :: Platform_Error.INVALID_PARAMETER
+ERROR_BROKEN_PIPE         :: Platform_Error.BROKEN_PIPE
+ERROR_BUFFER_OVERFLOW     :: Platform_Error.BUFFER_OVERFLOW
+ERROR_INSUFFICIENT_BUFFER :: Platform_Error.INSUFFICIENT_BUFFER
+ERROR_MOD_NOT_FOUND       :: Platform_Error.MOD_NOT_FOUND
+ERROR_PROC_NOT_FOUND      :: Platform_Error.PROC_NOT_FOUND
+ERROR_DIR_NOT_EMPTY       :: Platform_Error.DIR_NOT_EMPTY
+ERROR_ALREADY_EXISTS      :: Platform_Error.ALREADY_EXISTS
+ERROR_ENVVAR_NOT_FOUND    :: Platform_Error.ENVVAR_NOT_FOUND
+ERROR_MORE_DATA           :: Platform_Error.MORE_DATA
+ERROR_OPERATION_ABORTED   :: Platform_Error.OPERATION_ABORTED
+ERROR_IO_PENDING          :: Platform_Error.IO_PENDING
+ERROR_NOT_FOUND           :: Platform_Error.NOT_FOUND
+ERROR_PRIVILEGE_NOT_HELD  :: Platform_Error.PRIVILEGE_NOT_HELD
+WSAEACCES                 :: Platform_Error.WSAEACCES
+WSAECONNRESET             :: Platform_Error.WSAECONNRESET
+
+ERROR_FILE_IS_PIPE        :: General_Error.File_Is_Pipe
+ERROR_FILE_IS_NOT_DIR     :: General_Error.Not_Dir
 
 // "Argv" arguments converted to Odin strings
 args := _alloc_command_line_arguments()
@@ -221,20 +261,23 @@ args := _alloc_command_line_arguments()
 
 
 
-last_write_time :: proc(fd: Handle) -> (File_Time, Errno) {
+@(require_results)
+last_write_time :: proc(fd: Handle) -> (File_Time, Error) {
 	unimplemented("core:os procedure not supported on JS target")
 }
 
-last_write_time_by_name :: proc(name: string) -> (File_Time, Errno) {
+@(require_results)
+last_write_time_by_name :: proc(name: string) -> (File_Time, Error) {
 	unimplemented("core:os procedure not supported on JS target")
 }
 
 
+@(require_results)
 get_page_size :: proc() -> int {
 	unimplemented("core:os procedure not supported on JS target")
 }
 
-@(private)
+@(private, require_results)
 _processor_core_count :: proc() -> int {
 	unimplemented("core:os procedure not supported on JS target")
 }
@@ -246,6 +289,7 @@ exit :: proc "contextless" (code: int) -> ! {
 
 
 
+@(require_results)
 current_thread_id :: proc "contextless" () -> int {
 	context = runtime.default_context()
 	unimplemented("core:os procedure not supported on JS target")
@@ -253,6 +297,7 @@ current_thread_id :: proc "contextless" () -> int {
 
 
 
+@(require_results)
 _alloc_command_line_arguments :: proc() -> []string {
 	return nil
 }

+ 293 - 258
core/os/os_linux.odin

@@ -20,148 +20,148 @@ import "base:intrinsics"
 // all that about compatibility. But we don't want to push experimental changes
 // and have people's code break while it's still work in progress.
 import unix "core:sys/unix"
+import linux "core:sys/linux"
 
 Handle    :: distinct i32
 Pid       :: distinct i32
 File_Time :: distinct u64
-Errno     :: distinct i32
 Socket    :: distinct int
 
 INVALID_HANDLE :: ~Handle(0)
 
-ERROR_NONE:     Errno : 0
-EPERM:          Errno : 1
-ENOENT:         Errno : 2
-ESRCH:          Errno : 3
-EINTR:          Errno : 4
-EIO:            Errno : 5
-ENXIO:          Errno : 6
-EBADF:          Errno : 9
-EAGAIN:         Errno : 11
-ENOMEM:         Errno : 12
-EACCES:         Errno : 13
-EFAULT:         Errno : 14
-EEXIST:         Errno : 17
-ENODEV:         Errno : 19
-ENOTDIR:        Errno : 20
-EISDIR:         Errno : 21
-EINVAL:         Errno : 22
-ENFILE:         Errno : 23
-EMFILE:         Errno : 24
-ETXTBSY:        Errno : 26
-EFBIG:          Errno : 27
-ENOSPC:         Errno : 28
-ESPIPE:         Errno : 29
-EROFS:          Errno : 30
-EPIPE:          Errno : 32
-
-ERANGE:         Errno : 34 /* Result too large */
-EDEADLK:        Errno : 35 /* Resource deadlock would occur */
-ENAMETOOLONG:   Errno : 36 /* File name too long */
-ENOLCK:         Errno : 37 /* No record locks available */
-
-ENOSYS:         Errno : 38	/* Invalid system call number */
-
-ENOTEMPTY:      Errno : 39	/* Directory not empty */
-ELOOP:          Errno : 40	/* Too many symbolic links encountered */
-EWOULDBLOCK:    Errno : EAGAIN /* Operation would block */
-ENOMSG:         Errno : 42	/* No message of desired type */
-EIDRM:          Errno : 43	/* Identifier removed */
-ECHRNG:         Errno : 44	/* Channel number out of range */
-EL2NSYNC:       Errno : 45	/* Level 2 not synchronized */
-EL3HLT:         Errno : 46	/* Level 3 halted */
-EL3RST:         Errno : 47	/* Level 3 reset */
-ELNRNG:         Errno : 48	/* Link number out of range */
-EUNATCH:        Errno : 49	/* Protocol driver not attached */
-ENOCSI:         Errno : 50	/* No CSI structure available */
-EL2HLT:         Errno : 51	/* Level 2 halted */
-EBADE:          Errno : 52	/* Invalid exchange */
-EBADR:          Errno : 53	/* Invalid request descriptor */
-EXFULL:         Errno : 54	/* Exchange full */
-ENOANO:         Errno : 55	/* No anode */
-EBADRQC:        Errno : 56	/* Invalid request code */
-EBADSLT:        Errno : 57	/* Invalid slot */
-EDEADLOCK:      Errno : EDEADLK
-EBFONT:         Errno : 59	/* Bad font file format */
-ENOSTR:         Errno : 60	/* Device not a stream */
-ENODATA:        Errno : 61	/* No data available */
-ETIME:          Errno : 62	/* Timer expired */
-ENOSR:          Errno : 63	/* Out of streams resources */
-ENONET:         Errno : 64	/* Machine is not on the network */
-ENOPKG:         Errno : 65	/* Package not installed */
-EREMOTE:        Errno : 66	/* Object is remote */
-ENOLINK:        Errno : 67	/* Link has been severed */
-EADV:           Errno : 68	/* Advertise error */
-ESRMNT:         Errno : 69	/* Srmount error */
-ECOMM:          Errno : 70	/* Communication error on send */
-EPROTO:         Errno : 71	/* Protocol error */
-EMULTIHOP:      Errno : 72	/* Multihop attempted */
-EDOTDOT:        Errno : 73	/* RFS specific error */
-EBADMSG:        Errno : 74	/* Not a data message */
-EOVERFLOW:      Errno : 75	/* Value too large for defined data type */
-ENOTUNIQ:       Errno : 76	/* Name not unique on network */
-EBADFD:         Errno : 77	/* File descriptor in bad state */
-EREMCHG:        Errno : 78	/* Remote address changed */
-ELIBACC:        Errno : 79	/* Can not access a needed shared library */
-ELIBBAD:        Errno : 80	/* Accessing a corrupted shared library */
-ELIBSCN:        Errno : 81	/* .lib section in a.out corrupted */
-ELIBMAX:        Errno : 82	/* Attempting to link in too many shared libraries */
-ELIBEXEC:       Errno : 83	/* Cannot exec a shared library directly */
-EILSEQ:         Errno : 84	/* Illegal byte sequence */
-ERESTART:       Errno : 85	/* Interrupted system call should be restarted */
-ESTRPIPE:       Errno : 86	/* Streams pipe error */
-EUSERS:         Errno : 87	/* Too many users */
-ENOTSOCK:       Errno : 88	/* Socket operation on non-socket */
-EDESTADDRREQ:   Errno : 89	/* Destination address required */
-EMSGSIZE:       Errno : 90	/* Message too long */
-EPROTOTYPE:     Errno : 91	/* Protocol wrong type for socket */
-ENOPROTOOPT:    Errno : 92	/* Protocol not available */
-EPROTONOSUPPORT:Errno : 93	/* Protocol not supported */
-ESOCKTNOSUPPORT:Errno : 94	/* Socket type not supported */
-EOPNOTSUPP: 	Errno : 95	/* Operation not supported on transport endpoint */
-EPFNOSUPPORT: 	Errno : 96	/* Protocol family not supported */
-EAFNOSUPPORT: 	Errno : 97	/* Address family not supported by protocol */
-EADDRINUSE: 	Errno : 98	/* Address already in use */
-EADDRNOTAVAIL: 	Errno : 99	/* Cannot assign requested address */
-ENETDOWN: 		Errno : 100	/* Network is down */
-ENETUNREACH: 	Errno : 101	/* Network is unreachable */
-ENETRESET: 		Errno : 102	/* Network dropped connection because of reset */
-ECONNABORTED: 	Errno : 103	/* Software caused connection abort */
-ECONNRESET: 	Errno : 104	/* Connection reset by peer */
-ENOBUFS: 		Errno : 105	/* No buffer space available */
-EISCONN: 		Errno : 106	/* Transport endpoint is already connected */
-ENOTCONN: 		Errno : 107	/* Transport endpoint is not connected */
-ESHUTDOWN: 		Errno : 108	/* Cannot send after transport endpoint shutdown */
-ETOOMANYREFS: 	Errno : 109	/* Too many references: cannot splice */
-ETIMEDOUT: 		Errno : 110	/* Connection timed out */
-ECONNREFUSED: 	Errno : 111	/* Connection refused */
-EHOSTDOWN: 		Errno : 112	/* Host is down */
-EHOSTUNREACH: 	Errno : 113	/* No route to host */
-EALREADY: 		Errno : 114	/* Operation already in progress */
-EINPROGRESS: 	Errno : 115	/* Operation now in progress */
-ESTALE: 		Errno : 116	/* Stale file handle */
-EUCLEAN: 		Errno : 117	/* Structure needs cleaning */
-ENOTNAM: 		Errno : 118	/* Not a XENIX named type file */
-ENAVAIL: 		Errno : 119	/* No XENIX semaphores available */
-EISNAM: 		Errno : 120	/* Is a named type file */
-EREMOTEIO: 		Errno : 121	/* Remote I/O error */
-EDQUOT: 		Errno : 122	/* Quota exceeded */
-
-ENOMEDIUM: 		Errno : 123	/* No medium found */
-EMEDIUMTYPE: 	Errno : 124	/* Wrong medium type */
-ECANCELED: 		Errno : 125	/* Operation Canceled */
-ENOKEY: 		Errno : 126	/* Required key not available */
-EKEYEXPIRED: 	Errno : 127	/* Key has expired */
-EKEYREVOKED: 	Errno : 128	/* Key has been revoked */
-EKEYREJECTED: 	Errno : 129	/* Key was rejected by service */
+_Platform_Error :: linux.Errno
+EPERM           :: Platform_Error.EPERM
+ENOENT          :: Platform_Error.ENOENT
+ESRCH           :: Platform_Error.ESRCH
+EINTR           :: Platform_Error.EINTR
+EIO             :: Platform_Error.EIO
+ENXIO           :: Platform_Error.ENXIO
+EBADF           :: Platform_Error.EBADF
+EAGAIN          :: Platform_Error.EAGAIN
+ENOMEM          :: Platform_Error.ENOMEM
+EACCES          :: Platform_Error.EACCES
+EFAULT          :: Platform_Error.EFAULT
+EEXIST          :: Platform_Error.EEXIST
+ENODEV          :: Platform_Error.ENODEV
+ENOTDIR         :: Platform_Error.ENOTDIR
+EISDIR          :: Platform_Error.EISDIR
+EINVAL          :: Platform_Error.EINVAL
+ENFILE          :: Platform_Error.ENFILE
+EMFILE          :: Platform_Error.EMFILE
+ETXTBSY         :: Platform_Error.ETXTBSY
+EFBIG           :: Platform_Error.EFBIG
+ENOSPC          :: Platform_Error.ENOSPC
+ESPIPE          :: Platform_Error.ESPIPE
+EROFS           :: Platform_Error.EROFS
+EPIPE           :: Platform_Error.EPIPE
+
+ERANGE          :: Platform_Error.ERANGE          /* Result too large */
+EDEADLK         :: Platform_Error.EDEADLK         /* Resource deadlock would occur */
+ENAMETOOLONG    :: Platform_Error.ENAMETOOLONG    /* File name too long */
+ENOLCK          :: Platform_Error.ENOLCK          /* No record locks available */
+
+ENOSYS          :: Platform_Error.ENOSYS          /* Invalid system call number */
+
+ENOTEMPTY       :: Platform_Error.ENOTEMPTY       /* Directory not empty */
+ELOOP           :: Platform_Error.ELOOP           /* Too many symbolic links encountered */
+EWOULDBLOCK     :: Platform_Error.EWOULDBLOCK     /* Operation would block */
+ENOMSG          :: Platform_Error.ENOMSG          /* No message of desired type */
+EIDRM           :: Platform_Error.EIDRM           /* Identifier removed */
+ECHRNG          :: Platform_Error.ECHRNG          /* Channel number out of range */
+EL2NSYNC        :: Platform_Error.EL2NSYNC        /* Level 2 not synchronized */
+EL3HLT          :: Platform_Error.EL3HLT          /* Level 3 halted */
+EL3RST          :: Platform_Error.EL3RST          /* Level 3 reset */
+ELNRNG          :: Platform_Error.ELNRNG          /* Link number out of range */
+EUNATCH         :: Platform_Error.EUNATCH         /* Protocol driver not attached */
+ENOCSI          :: Platform_Error.ENOCSI          /* No CSI structure available */
+EL2HLT          :: Platform_Error.EL2HLT          /* Level 2 halted */
+EBADE           :: Platform_Error.EBADE           /* Invalid exchange */
+EBADR           :: Platform_Error.EBADR           /* Invalid request descriptor */
+EXFULL          :: Platform_Error.EXFULL          /* Exchange full */
+ENOANO          :: Platform_Error.ENOANO          /* No anode */
+EBADRQC         :: Platform_Error.EBADRQC         /* Invalid request code */
+EBADSLT         :: Platform_Error.EBADSLT         /* Invalid slot */
+EDEADLOCK       :: Platform_Error.EDEADLOCK
+EBFONT          :: Platform_Error.EBFONT          /* Bad font file format */
+ENOSTR          :: Platform_Error.ENOSTR          /* Device not a stream */
+ENODATA         :: Platform_Error.ENODATA         /* No data available */
+ETIME           :: Platform_Error.ETIME           /* Timer expired */
+ENOSR           :: Platform_Error.ENOSR           /* Out of streams resources */
+ENONET          :: Platform_Error.ENONET          /* Machine is not on the network */
+ENOPKG          :: Platform_Error.ENOPKG          /* Package not installed */
+EREMOTE         :: Platform_Error.EREMOTE         /* Object is remote */
+ENOLINK         :: Platform_Error.ENOLINK         /* Link has been severed */
+EADV            :: Platform_Error.EADV            /* Advertise error */
+ESRMNT          :: Platform_Error.ESRMNT          /* Srmount error */
+ECOMM           :: Platform_Error.ECOMM           /* Communication error on send */
+EPROTO          :: Platform_Error.EPROTO          /* Protocol error */
+EMULTIHOP       :: Platform_Error.EMULTIHOP       /* Multihop attempted */
+EDOTDOT         :: Platform_Error.EDOTDOT         /* RFS specific error */
+EBADMSG         :: Platform_Error.EBADMSG         /* Not a data message */
+EOVERFLOW       :: Platform_Error.EOVERFLOW       /* Value too large for defined data type */
+ENOTUNIQ        :: Platform_Error.ENOTUNIQ        /* Name not unique on network */
+EBADFD          :: Platform_Error.EBADFD          /* File descriptor in bad state */
+EREMCHG         :: Platform_Error.EREMCHG         /* Remote address changed */
+ELIBACC         :: Platform_Error.ELIBACC         /* Can not access a needed shared library */
+ELIBBAD         :: Platform_Error.ELIBBAD         /* Accessing a corrupted shared library */
+ELIBSCN         :: Platform_Error.ELIBSCN         /* .lib section in a.out corrupted */
+ELIBMAX         :: Platform_Error.ELIBMAX         /* Attempting to link in too many shared libraries */
+ELIBEXEC        :: Platform_Error.ELIBEXEC        /* Cannot exec a shared library directly */
+EILSEQ          :: Platform_Error.EILSEQ          /* Illegal byte sequence */
+ERESTART        :: Platform_Error.ERESTART        /* Interrupted system call should be restarted */
+ESTRPIPE        :: Platform_Error.ESTRPIPE        /* Streams pipe error */
+EUSERS          :: Platform_Error.EUSERS          /* Too many users */
+ENOTSOCK        :: Platform_Error.ENOTSOCK        /* Socket operation on non-socket */
+EDESTADDRREQ    :: Platform_Error.EDESTADDRREQ    /* Destination address required */
+EMSGSIZE        :: Platform_Error.EMSGSIZE        /* Message too long */
+EPROTOTYPE      :: Platform_Error.EPROTOTYPE      /* Protocol wrong type for socket */
+ENOPROTOOPT     :: Platform_Error.ENOPROTOOPT     /* Protocol not available */
+EPROTONOSUPPOR  :: Platform_Error.EPROTONOSUPPORT /* Protocol not supported */
+ESOCKTNOSUPPOR  :: Platform_Error.ESOCKTNOSUPPORT /* Socket type not supported */
+EOPNOTSUPP      :: Platform_Error.EOPNOTSUPP      /* Operation not supported on transport endpoint */
+EPFNOSUPPORT    :: Platform_Error.EPFNOSUPPORT    /* Protocol family not supported */
+EAFNOSUPPORT    :: Platform_Error.EAFNOSUPPORT    /* Address family not supported by protocol */
+EADDRINUSE      :: Platform_Error.EADDRINUSE      /* Address already in use */
+EADDRNOTAVAIL   :: Platform_Error.EADDRNOTAVAIL   /* Cannot assign requested address */
+ENETDOWN        :: Platform_Error.ENETDOWN        /* Network is down */
+ENETUNREACH     :: Platform_Error.ENETUNREACH     /* Network is unreachable */
+ENETRESET       :: Platform_Error.ENETRESET       /* Network dropped connection because of reset */
+ECONNABORTED    :: Platform_Error.ECONNABORTED    /* Software caused connection abort */
+ECONNRESET      :: Platform_Error.ECONNRESET      /* Connection reset by peer */
+ENOBUFS         :: Platform_Error.ENOBUFS         /* No buffer space available */
+EISCONN         :: Platform_Error.EISCONN         /* Transport endpoint is already connected */
+ENOTCONN        :: Platform_Error.ENOTCONN        /* Transport endpoint is not connected */
+ESHUTDOWN       :: Platform_Error.ESHUTDOWN       /* Cannot send after transport endpoint shutdown */
+ETOOMANYREFS    :: Platform_Error.ETOOMANYREFS    /* Too many references: cannot splice */
+ETIMEDOUT       :: Platform_Error.ETIMEDOUT       /* Connection timed out */
+ECONNREFUSED    :: Platform_Error.ECONNREFUSED    /* Connection refused */
+EHOSTDOWN       :: Platform_Error.EHOSTDOWN       /* Host is down */
+EHOSTUNREACH    :: Platform_Error.EHOSTUNREACH    /* No route to host */
+EALREADY        :: Platform_Error.EALREADY        /* Operation already in progress */
+EINPROGRESS     :: Platform_Error.EINPROGRESS     /* Operation now in progress */
+ESTALE          :: Platform_Error.ESTALE          /* Stale file handle */
+EUCLEAN         :: Platform_Error.EUCLEAN         /* Structure needs cleaning */
+ENOTNAM         :: Platform_Error.ENOTNAM         /* Not a XENIX named type file */
+ENAVAIL         :: Platform_Error.ENAVAIL         /* No XENIX semaphores available */
+EISNAM          :: Platform_Error.EISNAM          /* Is a named type file */
+EREMOTEIO       :: Platform_Error.EREMOTEIO       /* Remote I/O error */
+EDQUOT          :: Platform_Error.EDQUOT          /* Quota exceeded */
+
+ENOMEDIUM       :: Platform_Error.ENOMEDIUM       /* No medium found */
+EMEDIUMTYPE     :: Platform_Error.EMEDIUMTYPE     /* Wrong medium type */
+ECANCELED       :: Platform_Error.ECANCELED       /* Operation Canceled */
+ENOKEY          :: Platform_Error.ENOKEY          /* Required key not available */
+EKEYEXPIRED     :: Platform_Error.EKEYEXPIRED     /* Key has expired */
+EKEYREVOKED     :: Platform_Error.EKEYREVOKED     /* Key has been revoked */
+EKEYREJECTED    :: Platform_Error.EKEYREJECTED    /* Key was rejected by service */
 
 /* for robust mutexes */
-EOWNERDEAD: 	Errno : 130	/* Owner died */
-ENOTRECOVERABLE: Errno : 131	/* State not recoverable */
+EOWNERDEAD      :: Platform_Error.EOWNERDEAD      /* Owner died */
+ENOTRECOVERABLE :: Platform_Error.ENOTRECOVERABLE /* State not recoverable */
 
-ERFKILL: 		Errno : 132	/* Operation not possible due to RF-kill */
+ERFKILL   :: Platform_Error.ERFKILL               /* Operation not possible due to RF-kill */
 
-EHWPOISON: 		Errno : 133	/* Memory page has hardware error */
+EHWPOISON :: Platform_Error.EHWPOISON             /* Memory page has hardware error */
 
 ADDR_NO_RANDOMIZE :: 0x40000
 
@@ -448,13 +448,13 @@ S_ISGID :: 0o2000 // Set group id on execution
 S_ISVTX :: 0o1000 // Directory restrcted delete
 
 
-S_ISLNK  :: #force_inline proc(m: u32) -> bool { return (m & S_IFMT) == S_IFLNK  }
-S_ISREG  :: #force_inline proc(m: u32) -> bool { return (m & S_IFMT) == S_IFREG  }
-S_ISDIR  :: #force_inline proc(m: u32) -> bool { return (m & S_IFMT) == S_IFDIR  }
-S_ISCHR  :: #force_inline proc(m: u32) -> bool { return (m & S_IFMT) == S_IFCHR  }
-S_ISBLK  :: #force_inline proc(m: u32) -> bool { return (m & S_IFMT) == S_IFBLK  }
-S_ISFIFO :: #force_inline proc(m: u32) -> bool { return (m & S_IFMT) == S_IFIFO  }
-S_ISSOCK :: #force_inline proc(m: u32) -> bool { return (m & S_IFMT) == S_IFSOCK }
+@(require_results) S_ISLNK  :: #force_inline proc(m: u32) -> bool { return (m & S_IFMT) == S_IFLNK  }
+@(require_results) S_ISREG  :: #force_inline proc(m: u32) -> bool { return (m & S_IFMT) == S_IFREG  }
+@(require_results) S_ISDIR  :: #force_inline proc(m: u32) -> bool { return (m & S_IFMT) == S_IFDIR  }
+@(require_results) S_ISCHR  :: #force_inline proc(m: u32) -> bool { return (m & S_IFMT) == S_IFCHR  }
+@(require_results) S_ISBLK  :: #force_inline proc(m: u32) -> bool { return (m & S_IFMT) == S_IFBLK  }
+@(require_results) S_ISFIFO :: #force_inline proc(m: u32) -> bool { return (m & S_IFMT) == S_IFIFO  }
+@(require_results) S_ISSOCK :: #force_inline proc(m: u32) -> bool { return (m & S_IFMT) == S_IFSOCK }
 
 F_OK :: 0 // Test for file existance
 X_OK :: 1 // Test for execute permission
@@ -506,41 +506,55 @@ foreign dl {
 	@(link_name="freeifaddrs")      _freeifaddrs        :: proc(ifa: ^ifaddrs) ---
 }
 
+@(require_results)
 is_path_separator :: proc(r: rune) -> bool {
 	return r == '/'
 }
 
 // determine errno from syscall return value
-@private
-_get_errno :: proc(res: int) -> Errno {
+@(private, require_results)
+_get_errno :: proc(res: int) -> Error {
 	if res < 0 && res > -4096 {
-		return Errno(-res)
+		return Platform_Error(-res)
 	}
-	return 0
+	return nil
 }
 
 // get errno from libc
-get_last_error :: proc "contextless" () -> int {
-	return int(__errno_location()^)
+@(require_results, no_instrumentation)
+get_last_error :: proc "contextless" () -> Error {
+	err := Platform_Error(__errno_location()^)
+	#partial switch err {
+	case .NONE:
+		return nil
+	case .EPERM:
+		return .Permission_Denied
+	case .EEXIST:
+		return .Exist
+	case .ENOENT:
+		return .Not_Exist
+	}
+	return err
 }
 
-personality :: proc(persona: u64) -> (Errno) {
+personality :: proc(persona: u64) -> Error {
 	res := unix.sys_personality(persona)
 	if res == -1 {
 		return _get_errno(res)
 	}
-	return ERROR_NONE
+	return nil
 }
 
-fork :: proc() -> (Pid, Errno) {
+@(require_results)
+fork :: proc() -> (Pid, Error) {
 	pid := unix.sys_fork()
 	if pid == -1 {
 		return -1, _get_errno(pid)
 	}
-	return Pid(pid), ERROR_NONE
+	return Pid(pid), nil
 }
 
-execvp :: proc(path: string, args: []string) -> Errno {
+execvp :: proc(path: string, args: []string) -> Error {
 	runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD()
 	path_cstr := strings.clone_to_cstring(path, context.temp_allocator)
 
@@ -551,24 +565,30 @@ execvp :: proc(path: string, args: []string) -> Errno {
 	}
 
 	_unix_execvp(path_cstr, raw_data(args_cstrs))
-	return Errno(get_last_error())
+	return get_last_error()
 }
 
 
-open :: proc(path: string, flags: int = O_RDONLY, mode: int = 0o000) -> (Handle, Errno) {
+@(require_results)
+open :: proc(path: string, flags: int = O_RDONLY, mode: int = 0o000) -> (Handle, Error) {
 	runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD()
 	cstr := strings.clone_to_cstring(path, context.temp_allocator)
 	handle := unix.sys_open(cstr, flags, uint(mode))
 	if handle < 0 {
 		return INVALID_HANDLE, _get_errno(handle)
 	}
-	return Handle(handle), ERROR_NONE
+	return Handle(handle), nil
 }
 
-close :: proc(fd: Handle) -> Errno {
+close :: proc(fd: Handle) -> Error {
 	return _get_errno(unix.sys_close(int(fd)))
 }
 
+flush :: proc(fd: Handle) -> Error {
+	// do nothing
+	return nil
+}
+
 // If you read or write more than `SSIZE_MAX` bytes, result is implementation defined (probably an error).
 // `SSIZE_MAX` is also implementation defined but usually the max of a `ssize_t` which is `max(int)` in Odin.
 // In practice a read/write call would probably never read/write these big buffers all at once,
@@ -578,9 +598,9 @@ close :: proc(fd: Handle) -> Errno {
 @(private)
 MAX_RW :: 1 << 30
 
-read :: proc(fd: Handle, data: []byte) -> (int, Errno) {
+read :: proc(fd: Handle, data: []byte) -> (int, Error) {
 	if len(data) == 0 {
-		return 0, ERROR_NONE
+		return 0, nil
 	}
 
 	to_read := min(uint(len(data)), MAX_RW)
@@ -589,12 +609,12 @@ read :: proc(fd: Handle, data: []byte) -> (int, Errno) {
 	if bytes_read < 0 {
 		return -1, _get_errno(bytes_read)
 	}
-	return bytes_read, ERROR_NONE
+	return bytes_read, nil
 }
 
-write :: proc(fd: Handle, data: []byte) -> (int, Errno) {
+write :: proc(fd: Handle, data: []byte) -> (int, Error) {
 	if len(data) == 0 {
-		return 0, ERROR_NONE
+		return 0, nil
 	}
 
 	to_write := min(uint(len(data)), MAX_RW)
@@ -603,12 +623,12 @@ write :: proc(fd: Handle, data: []byte) -> (int, Errno) {
 	if bytes_written < 0 {
 		return -1, _get_errno(bytes_written)
 	}
-	return bytes_written, ERROR_NONE
+	return bytes_written, nil
 }
 
-read_at :: proc(fd: Handle, data: []byte, offset: i64) -> (int, Errno) {
+read_at :: proc(fd: Handle, data: []byte, offset: i64) -> (int, Error) {
 	if len(data) == 0 {
-		return 0, ERROR_NONE
+		return 0, nil
 	}
 
 	to_read := min(uint(len(data)), MAX_RW)
@@ -617,12 +637,12 @@ read_at :: proc(fd: Handle, data: []byte, offset: i64) -> (int, Errno) {
 	if bytes_read < 0 {
 		return -1, _get_errno(bytes_read)
 	}
-	return bytes_read, ERROR_NONE
+	return bytes_read, nil
 }
 
-write_at :: proc(fd: Handle, data: []byte, offset: i64) -> (int, Errno) {
+write_at :: proc(fd: Handle, data: []byte, offset: i64) -> (int, Error) {
 	if len(data) == 0 {
-		return 0, ERROR_NONE
+		return 0, nil
 	}
 
 	to_write := min(uint(len(data)), MAX_RW)
@@ -631,92 +651,97 @@ write_at :: proc(fd: Handle, data: []byte, offset: i64) -> (int, Errno) {
 	if bytes_written < 0 {
 		return -1, _get_errno(bytes_written)
 	}
-	return bytes_written, ERROR_NONE
+	return bytes_written, nil
 }
 
-seek :: proc(fd: Handle, offset: i64, whence: int) -> (i64, Errno) {
+seek :: proc(fd: Handle, offset: i64, whence: int) -> (i64, Error) {
 	res := unix.sys_lseek(int(fd), offset, whence)
 	if res < 0 {
 		return -1, _get_errno(int(res))
 	}
-	return i64(res), ERROR_NONE
+	return i64(res), nil
 }
 
-file_size :: proc(fd: Handle) -> (i64, Errno) {
+@(require_results)
+file_size :: proc(fd: Handle) -> (i64, Error) {
 	// deliberately uninitialized; the syscall fills this buffer for us
 	s: OS_Stat = ---
 	result := unix.sys_fstat(int(fd), rawptr(&s))
 	if result < 0 {
 		return 0, _get_errno(result)
 	}
-	return max(s.size, 0), ERROR_NONE
+	return max(s.size, 0), nil
 }
 
-rename :: proc(old_path, new_path: string) -> Errno {
+rename :: proc(old_path, new_path: string) -> Error {
 	runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD()
 	old_path_cstr := strings.clone_to_cstring(old_path, context.temp_allocator)
 	new_path_cstr := strings.clone_to_cstring(new_path, context.temp_allocator)
 	return _get_errno(unix.sys_rename(old_path_cstr, new_path_cstr))
 }
 
-remove :: proc(path: string) -> Errno {
+remove :: proc(path: string) -> Error {
 	runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD()
 	path_cstr := strings.clone_to_cstring(path, context.temp_allocator)
 	return _get_errno(unix.sys_unlink(path_cstr))
 }
 
-make_directory :: proc(path: string, mode: u32 = 0o775) -> Errno {
+make_directory :: proc(path: string, mode: u32 = 0o775) -> Error {
 	runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD()
 	path_cstr := strings.clone_to_cstring(path, context.temp_allocator)
 	return _get_errno(unix.sys_mkdir(path_cstr, uint(mode)))
 }
 
-remove_directory :: proc(path: string) -> Errno {
+remove_directory :: proc(path: string) -> Error {
 	runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD()
 	path_cstr := strings.clone_to_cstring(path, context.temp_allocator)
 	return _get_errno(unix.sys_rmdir(path_cstr))
 }
 
+@(require_results)
 is_file_handle :: proc(fd: Handle) -> bool {
 	s, err := _fstat(fd)
-	if err != ERROR_NONE {
+	if err != nil {
 		return false
 	}
 	return S_ISREG(s.mode)
 }
 
+@(require_results)
 is_file_path :: proc(path: string, follow_links: bool = true) -> bool {
 	s: OS_Stat
-	err: Errno
+	err: Error
 	if follow_links {
 		s, err = _stat(path)
 	} else {
 		s, err = _lstat(path)
 	}
-	if err != ERROR_NONE {
+	if err != nil {
 		return false
 	}
 	return S_ISREG(s.mode)
 }
 
 
+@(require_results)
 is_dir_handle :: proc(fd: Handle) -> bool {
 	s, err := _fstat(fd)
-	if err != ERROR_NONE {
+	if err != nil {
 		return false
 	}
 	return S_ISDIR(s.mode)
 }
 
+@(require_results)
 is_dir_path :: proc(path: string, follow_links: bool = true) -> bool {
 	s: OS_Stat
-	err: Errno
+	err: Error
 	if follow_links {
 		s, err = _stat(path)
 	} else {
 		s, err = _lstat(path)
 	}
-	if err != ERROR_NONE {
+	if err != nil {
 		return false
 	}
 	return S_ISDIR(s.mode)
@@ -725,6 +750,7 @@ is_dir_path :: proc(path: string, follow_links: bool = true) -> bool {
 is_file :: proc {is_file_path, is_file_handle}
 is_dir :: proc {is_dir_path, is_dir_handle}
 
+@(require_results)
 exists :: proc(path: string) -> bool {
 	runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD()
 	cpath := strings.clone_to_cstring(path, context.temp_allocator)
@@ -742,26 +768,22 @@ stderr: Handle = 2
 last_write_time :: proc(fd: Handle) -> File_Time {}
 last_write_time_by_name :: proc(name: string) -> File_Time {}
 */
-last_write_time :: proc(fd: Handle) -> (File_Time, Errno) {
-	s, err := _fstat(fd)
-	if err != ERROR_NONE {
-		return 0, err
-	}
+@(require_results)
+last_write_time :: proc(fd: Handle) -> (time: File_Time, err: Error) {
+	s := _fstat(fd) or_return
 	modified := s.modified.seconds * 1_000_000_000 + s.modified.nanoseconds
-	return File_Time(modified), ERROR_NONE
+	return File_Time(modified), nil
 }
 
-last_write_time_by_name :: proc(name: string) -> (File_Time, Errno) {
-	s, err := _stat(name)
-	if err != ERROR_NONE {
-		return 0, err
-	}
+@(require_results)
+last_write_time_by_name :: proc(name: string) -> (time: File_Time, err: Error) {
+	s := _stat(name) or_return
 	modified := s.modified.seconds * 1_000_000_000 + s.modified.nanoseconds
-	return File_Time(modified), ERROR_NONE
+	return File_Time(modified), nil
 }
 
-@private
-_stat :: proc(path: string) -> (OS_Stat, Errno) {
+@(private, require_results)
+_stat :: proc(path: string) -> (OS_Stat, Error) {
 	runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD()
 	cstr := strings.clone_to_cstring(path, context.temp_allocator)
 
@@ -771,11 +793,11 @@ _stat :: proc(path: string) -> (OS_Stat, Errno) {
 	if result < 0 {
 		return s, _get_errno(result)
 	}
-	return s, ERROR_NONE
+	return s, nil
 }
 
-@private
-_lstat :: proc(path: string) -> (OS_Stat, Errno) {
+@(private, require_results)
+_lstat :: proc(path: string) -> (OS_Stat, Error) {
 	runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD()
 	cstr := strings.clone_to_cstring(path, context.temp_allocator)
 
@@ -785,53 +807,53 @@ _lstat :: proc(path: string) -> (OS_Stat, Errno) {
 	if result < 0 {
 		return s, _get_errno(result)
 	}
-	return s, ERROR_NONE
+	return s, nil
 }
 
-@private
-_fstat :: proc(fd: Handle) -> (OS_Stat, Errno) {
+@(private, require_results)
+_fstat :: proc(fd: Handle) -> (OS_Stat, Error) {
 	// deliberately uninitialized; the syscall fills this buffer for us
 	s: OS_Stat = ---
 	result := unix.sys_fstat(int(fd), rawptr(&s))
 	if result < 0 {
 		return s, _get_errno(result)
 	}
-	return s, ERROR_NONE
+	return s, nil
 }
 
-@private
-_fdopendir :: proc(fd: Handle) -> (Dir, Errno) {
+@(private, require_results)
+_fdopendir :: proc(fd: Handle) -> (Dir, Error) {
 	dirp := _unix_fdopendir(fd)
 	if dirp == cast(Dir)nil {
-		return nil, Errno(get_last_error())
+		return nil, get_last_error()
 	}
-	return dirp, ERROR_NONE
+	return dirp, nil
 }
 
-@private
-_closedir :: proc(dirp: Dir) -> Errno {
+@(private)
+_closedir :: proc(dirp: Dir) -> Error {
 	rc := _unix_closedir(dirp)
 	if rc != 0 {
-		return Errno(get_last_error())
+		return get_last_error()
 	}
-	return ERROR_NONE
+	return nil
 }
 
-@private
+@(private)
 _rewinddir :: proc(dirp: Dir) {
 	_unix_rewinddir(dirp)
 }
 
-@private
-_readdir :: proc(dirp: Dir) -> (entry: Dirent, err: Errno, end_of_stream: bool) {
+@(private, require_results)
+_readdir :: proc(dirp: Dir) -> (entry: Dirent, err: Error, end_of_stream: bool) {
 	result: ^Dirent
 	rc := _unix_readdir_r(dirp, &entry, &result)
 
 	if rc != 0 {
-		err = Errno(get_last_error())
+		err = get_last_error()
 		return
 	}
-	err = ERROR_NONE
+	err = nil
 
 	if result == nil {
 		end_of_stream = true
@@ -842,8 +864,8 @@ _readdir :: proc(dirp: Dir) -> (entry: Dirent, err: Errno, end_of_stream: bool)
 	return
 }
 
-@private
-_readlink :: proc(path: string) -> (string, Errno) {
+@(private, require_results)
+_readlink :: proc(path: string) -> (string, Error) {
 	runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD(ignore = context.temp_allocator == context.allocator)
 	path_cstr := strings.clone_to_cstring(path, context.temp_allocator)
 
@@ -860,12 +882,13 @@ _readlink :: proc(path: string) -> (string, Errno) {
 			delete(buf)
 			buf = make([]byte, bufsz)
 		} else {
-			return strings.string_from_ptr(&buf[0], rc), ERROR_NONE
+			return strings.string_from_ptr(&buf[0], rc), nil
 		}
 	}
 }
 
-absolute_path_from_handle :: proc(fd: Handle) -> (string, Errno) {
+@(require_results)
+absolute_path_from_handle :: proc(fd: Handle) -> (string, Error) {
 	buf : [256]byte
 	fd_str := strconv.itoa( buf[:], cast(int)fd )
 
@@ -875,7 +898,8 @@ absolute_path_from_handle :: proc(fd: Handle) -> (string, Errno) {
 	return _readlink(procfs_path)
 }
 
-absolute_path_from_relative :: proc(rel: string) -> (path: string, err: Errno) {
+@(require_results)
+absolute_path_from_relative :: proc(rel: string) -> (path: string, err: Error) {
 	rel := rel
 	if rel == "" {
 		rel = "."
@@ -886,25 +910,26 @@ absolute_path_from_relative :: proc(rel: string) -> (path: string, err: Errno) {
 
 	path_ptr := _unix_realpath(rel_cstr, nil)
 	if path_ptr == nil {
-		return "", Errno(get_last_error())
+		return "", get_last_error()
 	}
 	defer _unix_free(path_ptr)
 
 	path = strings.clone(string(cstring(path_ptr)))
 
-	return path, ERROR_NONE
+	return path, nil
 }
 
-access :: proc(path: string, mask: int) -> (bool, Errno) {
+access :: proc(path: string, mask: int) -> (bool, Error) {
 	runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD()
 	cstr := strings.clone_to_cstring(path, context.temp_allocator)
 	result := unix.sys_access(cstr, mask)
 	if result < 0 {
 		return false, _get_errno(result)
 	}
-	return true, ERROR_NONE
+	return true, nil
 }
 
+@(require_results)
 lookup_env :: proc(key: string, allocator := context.allocator) -> (value: string, found: bool) {
 	runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD(ignore = context.temp_allocator == allocator)
 	path_str := strings.clone_to_cstring(key, context.temp_allocator)
@@ -916,33 +941,35 @@ lookup_env :: proc(key: string, allocator := context.allocator) -> (value: strin
 	return strings.clone(string(cstr), allocator), true
 }
 
+@(require_results)
 get_env :: proc(key: string, allocator := context.allocator) -> (value: string) {
 	value, _ = lookup_env(key, allocator)
 	return
 }
 
-set_env :: proc(key, value: string) -> Errno {
+set_env :: proc(key, value: string) -> Error {
 	runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD()
 	key_cstring := strings.clone_to_cstring(key, context.temp_allocator)
 	value_cstring := strings.clone_to_cstring(value, context.temp_allocator)
 	// NOTE(GoNZooo): `setenv` instead of `putenv` because it copies both key and value more commonly
 	res := _unix_setenv(key_cstring, value_cstring, 1)
 	if res < 0 {
-		return Errno(get_last_error())
+		return get_last_error()
 	}
-	return ERROR_NONE
+	return nil
 }
 
-unset_env :: proc(key: string) -> Errno {
+unset_env :: proc(key: string) -> Error {
 	runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD()
 	s := strings.clone_to_cstring(key, context.temp_allocator)
 	res := _unix_putenv(s)
 	if res < 0 {
-		return Errno(get_last_error())
+		return get_last_error()
 	}
-	return ERROR_NONE
+	return nil
 }
 
+@(require_results)
 get_current_directory :: proc() -> string {
 	// NOTE(tetra): I would use PATH_MAX here, but I was not able to find
 	// an authoritative value for it across all systems.
@@ -964,14 +991,14 @@ get_current_directory :: proc() -> string {
 	unreachable()
 }
 
-set_current_directory :: proc(path: string) -> (err: Errno) {
+set_current_directory :: proc(path: string) -> (err: Error) {
 	runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD()
 	cstr := strings.clone_to_cstring(path, context.temp_allocator)
 	res := unix.sys_chdir(cstr)
 	if res < 0 {
 		return _get_errno(res)
 	}
-	return ERROR_NONE
+	return nil
 }
 
 exit :: proc "contextless" (code: int) -> ! {
@@ -979,16 +1006,19 @@ exit :: proc "contextless" (code: int) -> ! {
 	_unix_exit(c.int(code))
 }
 
+@(require_results)
 current_thread_id :: proc "contextless" () -> int {
 	return unix.sys_gettid()
 }
 
+@(require_results)
 dlopen :: proc(filename: string, flags: int) -> rawptr {
 	runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD()
 	cstr := strings.clone_to_cstring(filename, context.temp_allocator)
 	handle := _unix_dlopen(cstr, c.int(flags))
 	return handle
 }
+@(require_results)
 dlsym :: proc(handle: rawptr, symbol: string) -> rawptr {
 	assert(handle != nil)
 	runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD()
@@ -1004,6 +1034,7 @@ dlerror :: proc() -> string {
 	return string(_unix_dlerror())
 }
 
+@(require_results)
 get_page_size :: proc() -> int {
 	// NOTE(tetra): The page size never changes, so why do anything complicated
 	// if we don't have to.
@@ -1016,11 +1047,12 @@ get_page_size :: proc() -> int {
 	return page_size
 }
 
-@(private)
+@(private, require_results)
 _processor_core_count :: proc() -> int {
 	return int(_unix_get_nprocs())
 }
 
+@(require_results)
 _alloc_command_line_arguments :: proc() -> []string {
 	res := make([]string, len(runtime.args__))
 	for arg, i in runtime.args__ {
@@ -1029,117 +1061,120 @@ _alloc_command_line_arguments :: proc() -> []string {
 	return res
 }
 
-socket :: proc(domain: int, type: int, protocol: int) -> (Socket, Errno) {
+@(require_results)
+socket :: proc(domain: int, type: int, protocol: int) -> (Socket, Error) {
 	result := unix.sys_socket(domain, type, protocol)
 	if result < 0 {
 		return 0, _get_errno(result)
 	}
-	return Socket(result), ERROR_NONE
+	return Socket(result), nil
 }
 
-bind :: proc(sd: Socket, addr: ^SOCKADDR, len: socklen_t) -> (Errno) {
+bind :: proc(sd: Socket, addr: ^SOCKADDR, len: socklen_t) -> Error {
 	result := unix.sys_bind(int(sd), addr, len)
 	if result < 0 {
 		return _get_errno(result)
 	}
-	return ERROR_NONE
+	return nil
 }
 
 
-connect :: proc(sd: Socket, addr: ^SOCKADDR, len: socklen_t) -> (Errno) {
+connect :: proc(sd: Socket, addr: ^SOCKADDR, len: socklen_t) -> Error {
 	result := unix.sys_connect(int(sd), addr, len)
 	if result < 0 {
 		return _get_errno(result)
 	}
-	return ERROR_NONE
+	return nil
 }
 
-accept :: proc(sd: Socket, addr: ^SOCKADDR, len: rawptr) -> (Socket, Errno) {
+accept :: proc(sd: Socket, addr: ^SOCKADDR, len: rawptr) -> (Socket, Error) {
 	result := unix.sys_accept(int(sd), rawptr(addr), len)
 	if result < 0 {
 		return 0, _get_errno(result)
 	}
-	return Socket(result), ERROR_NONE
+	return Socket(result), nil
 }
 
-listen :: proc(sd: Socket, backlog: int) -> (Errno) {
+listen :: proc(sd: Socket, backlog: int) -> Error {
 	result := unix.sys_listen(int(sd), backlog)
 	if result < 0 {
 		return _get_errno(result)
 	}
-	return ERROR_NONE
+	return nil
 }
 
-setsockopt :: proc(sd: Socket, level: int, optname: int, optval: rawptr, optlen: socklen_t) -> (Errno) {
+setsockopt :: proc(sd: Socket, level: int, optname: int, optval: rawptr, optlen: socklen_t) -> Error {
 	result := unix.sys_setsockopt(int(sd), level, optname, optval, optlen)
 	if result < 0 {
 		return _get_errno(result)
 	}
-	return ERROR_NONE
+	return nil
 }
 
 
-recvfrom :: proc(sd: Socket, data: []byte, flags: int, addr: ^SOCKADDR, addr_size: ^socklen_t) -> (u32, Errno) {
+recvfrom :: proc(sd: Socket, data: []byte, flags: int, addr: ^SOCKADDR, addr_size: ^socklen_t) -> (u32, Error) {
 	result := unix.sys_recvfrom(int(sd), raw_data(data), len(data), flags, addr, uintptr(addr_size))
 	if result < 0 {
 		return 0, _get_errno(int(result))
 	}
-	return u32(result), ERROR_NONE
+	return u32(result), nil
 }
 
-recv :: proc(sd: Socket, data: []byte, flags: int) -> (u32, Errno) {
+recv :: proc(sd: Socket, data: []byte, flags: int) -> (u32, Error) {
 	result := unix.sys_recvfrom(int(sd), raw_data(data), len(data), flags, nil, 0)
 	if result < 0 {
 		return 0, _get_errno(int(result))
 	}
-	return u32(result), ERROR_NONE
+	return u32(result), nil
 }
 
 
-sendto :: proc(sd: Socket, data: []u8, flags: int, addr: ^SOCKADDR, addrlen: socklen_t) -> (u32, Errno) {
+sendto :: proc(sd: Socket, data: []u8, flags: int, addr: ^SOCKADDR, addrlen: socklen_t) -> (u32, Error) {
 	result := unix.sys_sendto(int(sd), raw_data(data), len(data), flags, addr, addrlen)
 	if result < 0 {
 		return 0, _get_errno(int(result))
 	}
-	return u32(result), ERROR_NONE
+	return u32(result), nil
 }
 
-send :: proc(sd: Socket, data: []byte, flags: int) -> (u32, Errno) {
+send :: proc(sd: Socket, data: []byte, flags: int) -> (u32, Error) {
 	result := unix.sys_sendto(int(sd), raw_data(data), len(data), 0, nil, 0)
 	if result < 0 {
 		return 0, _get_errno(int(result))
 	}
-	return u32(result), ERROR_NONE
+	return u32(result), nil
 }
 
-shutdown :: proc(sd: Socket, how: int) -> (Errno) {
+shutdown :: proc(sd: Socket, how: int) -> Error {
 	result := unix.sys_shutdown(int(sd), how)
 	if result < 0 {
 		return _get_errno(result)
 	}
-	return ERROR_NONE
+	return nil
 }
 
-fcntl :: proc(fd: int, cmd: int, arg: int) -> (int, Errno) {
+fcntl :: proc(fd: int, cmd: int, arg: int) -> (int, Error) {
 	result := unix.sys_fcntl(fd, cmd, arg)
 	if result < 0 {
 		return 0, _get_errno(result)
 	}
-	return result, ERROR_NONE
+	return result, nil
 }
 
-poll :: proc(fds: []pollfd, timeout: int) -> (int, Errno) {
+@(require_results)
+poll :: proc(fds: []pollfd, timeout: int) -> (int, Error) {
 	result := unix.sys_poll(raw_data(fds), uint(len(fds)), timeout)
 	if result < 0 {
 		return 0, _get_errno(result)
 	}
-	return result, ERROR_NONE
+	return result, nil
 }
 
-ppoll :: proc(fds: []pollfd, timeout: ^unix.timespec, sigmask: ^sigset_t) -> (int, Errno) {
+@(require_results)
+ppoll :: proc(fds: []pollfd, timeout: ^unix.timespec, sigmask: ^sigset_t) -> (int, Error) {
 	result := unix.sys_ppoll(raw_data(fds), uint(len(fds)), timeout, sigmask, size_of(sigset_t))
 	if result < 0 {
 		return 0, _get_errno(result)
 	}
-	return result, ERROR_NONE
+	return result, nil
 }

+ 393 - 218
core/os/os_netbsd.odin

@@ -9,150 +9,289 @@ import "core:c"
 
 Handle :: distinct i32
 File_Time :: distinct u64
-Errno :: distinct i32
 
 INVALID_HANDLE :: ~Handle(0)
 
-ERROR_NONE:     Errno : 0 		/* No error */
-EPERM:          Errno : 1		/* Operation not permitted */
-ENOENT:         Errno : 2		/* No such file or directory */
-EINTR:          Errno : 4		/* Interrupted system call */
-ESRCH:          Errno : 3		/* No such process */
-EIO:            Errno : 5		/* Input/output error */
-ENXIO:          Errno : 6		/* Device not configured */
-E2BIG:          Errno : 7		/* Argument list too long */
-ENOEXEC:        Errno : 8		/* Exec format error */
-EBADF:          Errno : 9		/* Bad file descriptor */
-ECHILD:         Errno : 10		/* No child processes */
-EDEADLK:        Errno : 11		/* Resource deadlock avoided. 11 was EAGAIN */
-ENOMEM:         Errno : 12		/* Cannot allocate memory */
-EACCES:         Errno : 13		/* Permission denied */
-EFAULT:         Errno : 14		/* Bad address */
-ENOTBLK:        Errno : 15		/* Block device required */
-EBUSY:          Errno : 16		/* Device busy */
-EEXIST:         Errno : 17		/* File exists */
-EXDEV:          Errno : 18		/* Cross-device link */
-ENODEV:         Errno : 19		/* Operation not supported by device */
-ENOTDIR:        Errno : 20		/* Not a directory */
-EISDIR:         Errno : 21		/* Is a directory */
-EINVAL:         Errno : 22		/* Invalid argument */
-ENFILE:         Errno : 23		/* Too many open files in system */
-EMFILE:         Errno : 24		/* Too many open files */
-ENOTTY:         Errno : 25		/* Inappropriate ioctl for device */
-ETXTBSY:        Errno : 26		/* Text file busy */
-EFBIG:          Errno : 27		/* File too large */
-ENOSPC:         Errno : 28		/* No space left on device */
-ESPIPE:         Errno : 29		/* Illegal seek */
-EROFS:          Errno : 30		/* Read-only file system */
-EMLINK:         Errno : 31		/* Too many links */
-EPIPE:          Errno : 32		/* Broken pipe */
+_Platform_Error :: enum i32 {
+	NONE            = 0,
+	EPERM           = 1,          /* Operation not permitted */
+	ENOENT          = 2,          /* No such file or directory */
+	EINTR           = 4,          /* Interrupted system call */
+	ESRCH           = 3,          /* No such process */
+	EIO             = 5,          /* Input/output error */
+	ENXIO           = 6,          /* Device not configured */
+	E2BIG           = 7,          /* Argument list too long */
+	ENOEXEC         = 8,          /* Exec format error */
+	EBADF           = 9,          /* Bad file descriptor */
+	ECHILD          = 10,         /* No child processes */
+	EDEADLK         = 11,         /* Resource deadlock avoided. 11 was EAGAIN */
+	ENOMEM          = 12,         /* Cannot allocate memory */
+	EACCES          = 13,         /* Permission denied */
+	EFAULT          = 14,         /* Bad address */
+	ENOTBLK         = 15,         /* Block device required */
+	EBUSY           = 16,         /* Device busy */
+	EEXIST          = 17,         /* File exists */
+	EXDEV           = 18,         /* Cross-device link */
+	ENODEV          = 19,         /* Operation not supported by device */
+	ENOTDIR         = 20,         /* Not a directory */
+	EISDIR          = 21,         /* Is a directory */
+	EINVAL          = 22,         /* Invalid argument */
+	ENFILE          = 23,         /* Too many open files in system */
+	EMFILE          = 24,         /* Too many open files */
+	ENOTTY          = 25,         /* Inappropriate ioctl for device */
+	ETXTBSY         = 26,         /* Text file busy */
+	EFBIG           = 27,         /* File too large */
+	ENOSPC          = 28,         /* No space left on device */
+	ESPIPE          = 29,         /* Illegal seek */
+	EROFS           = 30,         /* Read-only file system */
+	EMLINK          = 31,         /* Too many links */
+	EPIPE           = 32,         /* Broken pipe */
+
+	/* math software */
+	EDOM            = 33,         /* Numerical argument out of domain */
+	ERANGE          = 34,         /* Result too large or too small */
+
+	/* non-blocking and interrupt i/o */
+	EAGAIN          = 35,         /* Resource temporarily unavailable */
+	EWOULDBLOCK     = EAGAIN,     /*  Operation would block */
+	EINPROGRESS     = 36,         /* Operation now in progress */
+	EALREADY        = 37,         /* Operation already in progress */
+
+	/* ipc/network software -- argument errors */
+	ENOTSOCK        = 38,         /* Socket operation on non-socket */
+	EDESTADDRREQ    = 39,         /* Destination address required */
+	EMSGSIZE        = 40,         /* Message too long */
+	EPROTOTYPE      = 41,         /* Protocol wrong type for socket */
+	ENOPROTOOPT     = 42,         /* Protocol option not available */
+	EPROTONOSUPPORT = 43,         /* Protocol not supported */
+	ESOCKTNOSUPPORT = 44,         /* Socket type not supported */
+	EOPNOTSUPP      = 45,         /* Operation not supported */
+	EPFNOSUPPORT    = 46,         /* Protocol family not supported */
+	EAFNOSUPPORT    = 47,         /* Address family not supported by protocol family */
+	EADDRINUSE      = 48,         /* Address already in use */
+	EADDRNOTAVAIL   = 49,         /* Can't assign requested address */
+
+	/* ipc/network software -- operational errors */
+	ENETDOWN        = 50,         /* Network is down */
+	ENETUNREACH     = 51,         /* Network is unreachable */
+	ENETRESET       = 52,         /* Network dropped connection on reset */
+	ECONNABORTED    = 53,         /* Software caused connection abort */
+	ECONNRESET      = 54,         /* Connection reset by peer */
+	ENOBUFS         = 55,         /* No buffer space available */
+	EISCONN         = 56,         /* Socket is already connected */
+	ENOTCONN        = 57,         /* Socket is not connected */
+	ESHUTDOWN       = 58,         /* Can't send after socket shutdown */
+	ETOOMANYREFS    = 59,         /* Too many references: can't splice */
+	ETIMEDOUT       = 60,         /* Operation timed out */
+	ECONNREFUSED    = 61,         /* Connection refused */
+
+	ELOOP           = 62,         /* Too many levels of symbolic links */
+	ENAMETOOLONG    = 63,         /* File name too long */
+
+	/* should be rearranged */
+	EHOSTDOWN       = 64,         /* Host is down */
+	EHOSTUNREACH    = 65,         /* No route to host */
+	ENOTEMPTY       = 66,         /* Directory not empty */
+
+	/* quotas & mush */
+	EPROCLIM        = 67,         /* Too many processes */
+	EUSERS          = 68,         /* Too many users */
+	EDQUOT          = 69,         /* Disc quota exceeded */
+
+	/* Network File System */
+	ESTALE          = 70,         /* Stale NFS file handle */
+	EREMOTE         = 71,         /* Too many levels of remote in path */
+	EBADRPC         = 72,         /* RPC struct is bad */
+	ERPCMISMATCH    = 73,         /* RPC version wrong */
+	EPROGUNAVAIL    = 74,         /* RPC prog. not avail */
+	EPROGMISMATCH   = 75,         /* Program version wrong */
+	EPROCUNAVAIL    = 76,         /* Bad procedure for program */
+
+	ENOLCK          = 77,         /* No locks available */
+	ENOSYS          = 78,         /* Function not implemented */
+
+	EFTYPE          = 79,         /* Inappropriate file type or format */
+	EAUTH           = 80,         /* Authentication error */
+	ENEEDAUTH       = 81,         /* Need authenticator */
+
+	/* SystemV IPC */
+	EIDRM           = 82,         /* Identifier removed */
+	ENOMSG          = 83,         /* No message of desired type */
+	EOVERFLOW       = 84,         /* Value too large to be stored in data type */
+
+	/* Wide/multibyte-character handling, ISO/IEC 9899/AMD1:1995 */
+	EILSEQ          = 85,         /* Illegal byte sequence */
+
+	/* From IEEE Std 1003.1-2001 */
+	/* Base, Realtime, Threads or Thread Priority Scheduling option errors */
+	ENOTSUP         = 86,         /* Not supported */
+
+	/* Realtime option errors */
+	ECANCELED       = 87,         /* Operation canceled */
+
+	/* Realtime, XSI STREAMS option errors */
+	EBADMSG         = 88,         /* Bad or Corrupt message */
+
+	/* XSI STREAMS option errors  */
+	ENODATA         = 89,         /* No message available */
+	ENOSR           = 90,         /* No STREAM resources */
+	ENOSTR          = 91,         /* Not a STREAM */
+	ETIME           = 92,         /* STREAM ioctl timeout */
+
+	/* File system extended attribute errors */
+	ENOATTR         = 93,         /* Attribute not found */
+
+	/* Realtime, XSI STREAMS option errors */
+	EMULTIHOP       = 94,         /* Multihop attempted */
+	ENOLINK         = 95,         /* Link has been severed */
+	EPROTO          = 96,         /* Protocol error */
+
+	/* Robust mutexes */
+	EOWNERDEAD      = 97,         /* Previous owner died */
+	ENOTRECOVERABLE = 98,         /* State not recoverable */
+
+	ELAST           = 98,         /* Must equal largest Error */
+}
+
+EPERM           :: Platform_Error.EPERM           /* Operation not permitted */
+ENOENT          :: Platform_Error.ENOENT          /* No such file or directory */
+EINTR           :: Platform_Error.EINTR           /* Interrupted system call */
+ESRCH           :: Platform_Error.ESRCH           /* No such process */
+EIO             :: Platform_Error.EIO             /* Input/output error */
+ENXIO           :: Platform_Error.ENXIO           /* Device not configured */
+E2BIG           :: Platform_Error.E2BIG           /* Argument list too long */
+ENOEXEC         :: Platform_Error.ENOEXEC         /* Exec format error */
+EBADF           :: Platform_Error.EBADF           /* Bad file descriptor */
+ECHILD          :: Platform_Error.ECHILD          /* No child processes */
+EDEADLK         :: Platform_Error.EDEADLK         /* Resource deadlock avoided. 11 was EAGAIN */
+ENOMEM          :: Platform_Error.ENOMEM          /* Cannot allocate memory */
+EACCES          :: Platform_Error.EACCES          /* Permission denied */
+EFAULT          :: Platform_Error.EFAULT          /* Bad address */
+ENOTBLK         :: Platform_Error.ENOTBLK         /* Block device required */
+EBUSY           :: Platform_Error.EBUSY           /* Device busy */
+EEXIST          :: Platform_Error.EEXIST          /* File exists */
+EXDEV           :: Platform_Error.EXDEV           /* Cross-device link */
+ENODEV          :: Platform_Error.ENODEV          /* Operation not supported by device */
+ENOTDIR         :: Platform_Error.ENOTDIR         /* Not a directory */
+EISDIR          :: Platform_Error.EISDIR          /* Is a directory */
+EINVAL          :: Platform_Error.EINVAL          /* Invalid argument */
+ENFILE          :: Platform_Error.ENFILE          /* Too many open files in system */
+EMFILE          :: Platform_Error.EMFILE          /* Too many open files */
+ENOTTY          :: Platform_Error.ENOTTY          /* Inappropriate ioctl for device */
+ETXTBSY         :: Platform_Error.ETXTBSY         /* Text file busy */
+EFBIG           :: Platform_Error.EFBIG           /* File too large */
+ENOSPC          :: Platform_Error.ENOSPC          /* No space left on device */
+ESPIPE          :: Platform_Error.ESPIPE          /* Illegal seek */
+EROFS           :: Platform_Error.EROFS           /* Read-only file system */
+EMLINK          :: Platform_Error.EMLINK          /* Too many links */
+EPIPE           :: Platform_Error.EPIPE           /* Broken pipe */
 
 /* math software */
-EDOM:           Errno : 33		/* Numerical argument out of domain */
-ERANGE:         Errno : 34		/* Result too large or too small */
+EDOM            :: Platform_Error.EDOM            /* Numerical argument out of domain */
+ERANGE          :: Platform_Error.ERANGE          /* Result too large or too small */
 
 /* non-blocking and interrupt i/o */
-EAGAIN:         Errno : 35		/* Resource temporarily unavailable */
-EWOULDBLOCK:    Errno : EAGAIN	/* Operation would block */
-EINPROGRESS:    Errno : 36		/* Operation now in progress */
-EALREADY:       Errno : 37		/* Operation already in progress */
+EAGAIN          :: Platform_Error.EAGAIN          /* Resource temporarily unavailable */
+EWOULDBLOCK     :: EAGAIN        /*  Operation would block */
+EINPROGRESS     :: Platform_Error.EINPROGRESS     /* Operation now in progress */
+EALREADY        :: Platform_Error.EALREADY        /* Operation already in progress */
 
 /* ipc/network software -- argument errors */
-ENOTSOCK:       Errno : 38		/* Socket operation on non-socket */
-EDESTADDRREQ:   Errno : 39		/* Destination address required */
-EMSGSIZE:       Errno : 40		/* Message too long */
-EPROTOTYPE:     Errno : 41		/* Protocol wrong type for socket */
-ENOPROTOOPT:    Errno : 42		/* Protocol option not available */
-EPROTONOSUPPORT:    Errno : 43		/* Protocol not supported */
-ESOCKTNOSUPPORT:    Errno : 44		/* Socket type not supported */
-EOPNOTSUPP:     Errno : 45		/* Operation not supported */
-EPFNOSUPPORT:   Errno : 46		/* Protocol family not supported */
-EAFNOSUPPORT:   Errno : 47		/* Address family not supported by protocol family */
-EADDRINUSE:     Errno : 48		/* Address already in use */
-EADDRNOTAVAIL:  Errno : 49		/* Can't assign requested address */
+ENOTSOCK        :: Platform_Error.ENOTSOCK        /* Socket operation on non-socket */
+EDESTADDRREQ    :: Platform_Error.EDESTADDRREQ    /* Destination address required */
+EMSGSIZE        :: Platform_Error.EMSGSIZE        /* Message too long */
+EPROTOTYPE      :: Platform_Error.EPROTOTYPE      /* Protocol wrong type for socket */
+ENOPROTOOPT     :: Platform_Error.ENOPROTOOPT     /* Protocol option not available */
+EPROTONOSUPPORT :: Platform_Error.EPROTONOSUPPORT /* Protocol not supported */
+ESOCKTNOSUPPORT :: Platform_Error.ESOCKTNOSUPPORT /* Socket type not supported */
+EOPNOTSUPP      :: Platform_Error.EOPNOTSUPP      /* Operation not supported */
+EPFNOSUPPORT    :: Platform_Error.EPFNOSUPPORT    /* Protocol family not supported */
+EAFNOSUPPORT    :: Platform_Error.EAFNOSUPPORT    /* Address family not supported by protocol family */
+EADDRINUSE      :: Platform_Error.EADDRINUSE      /* Address already in use */
+EADDRNOTAVAIL   :: Platform_Error.EADDRNOTAVAIL   /* Can't assign requested address */
 
 /* ipc/network software -- operational errors */
-ENETDOWN:       Errno : 50		/* Network is down */
-ENETUNREACH:    Errno : 51		/* Network is unreachable */
-ENETRESET:      Errno : 52		/* Network dropped connection on reset */
-ECONNABORTED:   Errno : 53		/* Software caused connection abort */
-ECONNRESET:     Errno : 54		/* Connection reset by peer */
-ENOBUFS:        Errno : 55		/* No buffer space available */
-EISCONN:        Errno : 56		/* Socket is already connected */
-ENOTCONN:       Errno : 57		/* Socket is not connected */
-ESHUTDOWN:      Errno : 58		/* Can't send after socket shutdown */
-ETOOMANYREFS:   Errno : 59		/* Too many references: can't splice */
-ETIMEDOUT:      Errno : 60		/* Operation timed out */
-ECONNREFUSED:   Errno : 61		/* Connection refused */
-
-ELOOP:          Errno : 62		/* Too many levels of symbolic links */
-ENAMETOOLONG:   Errno : 63		/* File name too long */
+ENETDOWN        :: Platform_Error.ENETDOWN        /* Network is down */
+ENETUNREACH     :: Platform_Error.ENETUNREACH     /* Network is unreachable */
+ENETRESET       :: Platform_Error.ENETRESET       /* Network dropped connection on reset */
+ECONNABORTED    :: Platform_Error.ECONNABORTED    /* Software caused connection abort */
+ECONNRESET      :: Platform_Error.ECONNRESET      /* Connection reset by peer */
+ENOBUFS         :: Platform_Error.ENOBUFS         /* No buffer space available */
+EISCONN         :: Platform_Error.EISCONN         /* Socket is already connected */
+ENOTCONN        :: Platform_Error.ENOTCONN        /* Socket is not connected */
+ESHUTDOWN       :: Platform_Error.ESHUTDOWN       /* Can't send after socket shutdown */
+ETOOMANYREFS    :: Platform_Error.ETOOMANYREFS    /* Too many references: can't splice */
+ETIMEDOUT       :: Platform_Error.ETIMEDOUT       /* Operation timed out */
+ECONNREFUSED    :: Platform_Error.ECONNREFUSED    /* Connection refused */
+
+ELOOP           :: Platform_Error.ELOOP           /* Too many levels of symbolic links */
+ENAMETOOLONG    :: Platform_Error.ENAMETOOLONG    /* File name too long */
 
 /* should be rearranged */
-EHOSTDOWN:      Errno : 64		/* Host is down */
-EHOSTUNREACH:   Errno : 65		/* No route to host */
-ENOTEMPTY:      Errno : 66		/* Directory not empty */
+EHOSTDOWN       :: Platform_Error.EHOSTDOWN       /* Host is down */
+EHOSTUNREACH    :: Platform_Error.EHOSTUNREACH    /* No route to host */
+ENOTEMPTY       :: Platform_Error.ENOTEMPTY       /* Directory not empty */
 
 /* quotas & mush */
-EPROCLIM:       Errno : 67		/* Too many processes */
-EUSERS:         Errno : 68		/* Too many users */
-EDQUOT:         Errno : 69		/* Disc quota exceeded */
+EPROCLIM        :: Platform_Error.EPROCLIM        /* Too many processes */
+EUSERS          :: Platform_Error.EUSERS          /* Too many users */
+EDQUOT          :: Platform_Error.EDQUOT          /* Disc quota exceeded */
 
 /* Network File System */
-ESTALE:         Errno : 70		/* Stale NFS file handle */
-EREMOTE:        Errno : 71		/* Too many levels of remote in path */
-EBADRPC:        Errno : 72		/* RPC struct is bad */
-ERPCMISMATCH:   Errno : 73		/* RPC version wrong */
-EPROGUNAVAIL:   Errno : 74		/* RPC prog. not avail */
-EPROGMISMATCH:  Errno : 75		/* Program version wrong */
-EPROCUNAVAIL:   Errno : 76		/* Bad procedure for program */
+ESTALE          :: Platform_Error.ESTALE          /* Stale NFS file handle */
+EREMOTE         :: Platform_Error.EREMOTE         /* Too many levels of remote in path */
+EBADRPC         :: Platform_Error.EBADRPC         /* RPC struct is bad */
+ERPCMISMATCH    :: Platform_Error.ERPCMISMATCH    /* RPC version wrong */
+EPROGUNAVAIL    :: Platform_Error.EPROGUNAVAIL    /* RPC prog. not avail */
+EPROGMISMATCH   :: Platform_Error.EPROGMISMATCH   /* Program version wrong */
+EPROCUNAVAIL    :: Platform_Error.EPROCUNAVAIL    /* Bad procedure for program */
 
-ENOLCK:         Errno : 77		/* No locks available */
-ENOSYS:         Errno : 78		/* Function not implemented */
+ENOLCK          :: Platform_Error.ENOLCK          /* No locks available */
+ENOSYS          :: Platform_Error.ENOSYS          /* Function not implemented */
 
-EFTYPE:         Errno : 79		/* Inappropriate file type or format */
-EAUTH:          Errno : 80		/* Authentication error */
-ENEEDAUTH:      Errno : 81		/* Need authenticator */
+EFTYPE          :: Platform_Error.EFTYPE          /* Inappropriate file type or format */
+EAUTH           :: Platform_Error.EAUTH           /* Authentication error */
+ENEEDAUTH       :: Platform_Error.ENEEDAUTH       /* Need authenticator */
 
 /* SystemV IPC */
-EIDRM:          Errno : 82		/* Identifier removed */
-ENOMSG:         Errno : 83		/* No message of desired type */
-EOVERFLOW:      Errno : 84		/* Value too large to be stored in data type */
+EIDRM           :: Platform_Error.EIDRM           /* Identifier removed */
+ENOMSG          :: Platform_Error.ENOMSG          /* No message of desired type */
+EOVERFLOW       :: Platform_Error.EOVERFLOW       /* Value too large to be stored in data type */
 
 /* Wide/multibyte-character handling, ISO/IEC 9899/AMD1:1995 */
-EILSEQ:         Errno : 85		/* Illegal byte sequence */
+EILSEQ          :: Platform_Error.EILSEQ          /* Illegal byte sequence */
 
 /* From IEEE Std 1003.1-2001 */
 /* Base, Realtime, Threads or Thread Priority Scheduling option errors */
-ENOTSUP:        Errno : 86		/* Not supported */
+ENOTSUP         :: Platform_Error.ENOTSUP         /* Not supported */
 
 /* Realtime option errors */
-ECANCELED:      Errno : 87		/* Operation canceled */
+ECANCELED       :: Platform_Error.ECANCELED       /* Operation canceled */
 
 /* Realtime, XSI STREAMS option errors */
-EBADMSG:        Errno : 88		/* Bad or Corrupt message */
+EBADMSG         :: Platform_Error.EBADMSG         /* Bad or Corrupt message */
 
 /* XSI STREAMS option errors  */
-ENODATA:        Errno : 89		/* No message available */
-ENOSR:          Errno : 90		/* No STREAM resources */
-ENOSTR:         Errno : 91		/* Not a STREAM */
-ETIME:          Errno : 92		/* STREAM ioctl timeout */
+ENODATA         :: Platform_Error.ENODATA         /* No message available */
+ENOSR           :: Platform_Error.ENOSR           /* No STREAM resources */
+ENOSTR          :: Platform_Error.ENOSTR          /* Not a STREAM */
+ETIME           :: Platform_Error.ETIME           /* STREAM ioctl timeout */
 
 /* File system extended attribute errors */
-ENOATTR:        Errno : 93		/* Attribute not found */
+ENOATTR         :: Platform_Error.ENOATTR         /* Attribute not found */
 
 /* Realtime, XSI STREAMS option errors */
-EMULTIHOP:      Errno : 94		/* Multihop attempted */
-ENOLINK:        Errno : 95		/* Link has been severed */
-EPROTO:         Errno : 96		/* Protocol error */
+EMULTIHOP       :: Platform_Error.EMULTIHOP       /* Multihop attempted */
+ENOLINK         :: Platform_Error.ENOLINK         /* Link has been severed */
+EPROTO          :: Platform_Error.EPROTO          /* Protocol error */
 
 /* Robust mutexes */
-EOWNERDEAD:     Errno : 97		/* Previous owner died */
-ENOTRECOVERABLE:    Errno : 98		/* State not recoverable */
+EOWNERDEAD      :: Platform_Error.EOWNERDEAD      /* Previous owner died */
+ENOTRECOVERABLE :: Platform_Error.ENOTRECOVERABLE /* State not recoverable */
 
-ELAST:          Errno : 98		/* Must equal largest errno */
+ELAST           :: Platform_Error.ELAST           /* Must equal largest Error */
 
-/* end of errno */
+/* end of Error */
 
 O_RDONLY   :: 0x000000000
 O_WRONLY   :: 0x000000001
@@ -268,13 +407,13 @@ S_ISUID :: 0o4000 // Set user id on execution
 S_ISGID :: 0o2000 // Set group id on execution
 S_ISVTX :: 0o1000 // Directory restrcted delete
 
-S_ISLNK  :: #force_inline proc "contextless" (m: mode_t) -> bool { return (m & S_IFMT) == S_IFLNK  }
-S_ISREG  :: #force_inline proc "contextless" (m: mode_t) -> bool { return (m & S_IFMT) == S_IFREG  }
-S_ISDIR  :: #force_inline proc "contextless" (m: mode_t) -> bool { return (m & S_IFMT) == S_IFDIR  }
-S_ISCHR  :: #force_inline proc "contextless" (m: mode_t) -> bool { return (m & S_IFMT) == S_IFCHR  }
-S_ISBLK  :: #force_inline proc "contextless" (m: mode_t) -> bool { return (m & S_IFMT) == S_IFBLK  }
-S_ISFIFO :: #force_inline proc "contextless" (m: mode_t) -> bool { return (m & S_IFMT) == S_IFIFO  }
-S_ISSOCK :: #force_inline proc "contextless" (m: mode_t) -> bool { return (m & S_IFMT) == S_IFSOCK }
+@(require_results) S_ISLNK  :: #force_inline proc "contextless" (m: mode_t) -> bool { return (m & S_IFMT) == S_IFLNK  }
+@(require_results) S_ISREG  :: #force_inline proc "contextless" (m: mode_t) -> bool { return (m & S_IFMT) == S_IFREG  }
+@(require_results) S_ISDIR  :: #force_inline proc "contextless" (m: mode_t) -> bool { return (m & S_IFMT) == S_IFDIR  }
+@(require_results) S_ISCHR  :: #force_inline proc "contextless" (m: mode_t) -> bool { return (m & S_IFMT) == S_IFCHR  }
+@(require_results) S_ISBLK  :: #force_inline proc "contextless" (m: mode_t) -> bool { return (m & S_IFMT) == S_IFBLK  }
+@(require_results) S_ISFIFO :: #force_inline proc "contextless" (m: mode_t) -> bool { return (m & S_IFMT) == S_IFIFO  }
+@(require_results) S_ISSOCK :: #force_inline proc "contextless" (m: mode_t) -> bool { return (m & S_IFMT) == S_IFSOCK }
 
 F_OK :: 0 // Test for file existance
 X_OK :: 1 // Test for execute permission
@@ -306,7 +445,7 @@ foreign libc {
 	@(link_name="fdopendir")        _unix_fdopendir     :: proc(fd: Handle) -> Dir ---
 	@(link_name="closedir")         _unix_closedir      :: proc(dirp: Dir) -> c.int ---
 	@(link_name="rewinddir")        _unix_rewinddir     :: proc(dirp: Dir) ---
-	@(link_name="readdir_r")        _unix_readdir_r     :: proc(dirp: Dir, entry: ^Dirent, result: ^^Dirent) -> c.int ---
+	@(link_name="__readdir_r30")    _unix_readdir_r     :: proc(dirp: Dir, entry: ^Dirent, result: ^^Dirent) -> c.int ---
 
 	@(link_name="malloc")           _unix_malloc        :: proc(size: c.size_t) -> rawptr ---
 	@(link_name="calloc")           _unix_calloc        :: proc(num, size: c.size_t) -> rawptr ---
@@ -334,154 +473,186 @@ foreign libc {
 
 // NOTE(phix): Perhaps share the following functions with FreeBSD if they turn out to be the same in the end.
 
+@(require_results)
 is_path_separator :: proc(r: rune) -> bool {
 	return r == '/'
 }
 
-get_last_error :: proc "contextless" () -> int {
-	return int(__errno_location()^)
+@(require_results, no_instrumentation)
+get_last_error :: proc "contextless" () -> Error {
+	return Platform_Error(__errno_location()^)
 }
 
-open :: proc(path: string, flags: int = O_RDONLY, mode: int = 0) -> (Handle, Errno) {
+@(require_results)
+open :: proc(path: string, flags: int = O_RDONLY, mode: int = 0) -> (Handle, Error) {
 	runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD()
 	cstr := strings.clone_to_cstring(path, context.temp_allocator)
 	handle := _unix_open(cstr, c.int(flags), c.int(mode))
 	if handle == -1 {
-		return INVALID_HANDLE, Errno(get_last_error())
+		return INVALID_HANDLE, get_last_error()
 	}
-	return handle, ERROR_NONE
+	return handle, nil
 }
 
-close :: proc(fd: Handle) -> Errno {
+close :: proc(fd: Handle) -> Error {
 	result := _unix_close(fd)
 	if result == -1 {
-		return Errno(get_last_error())
+		return get_last_error()
 	}
-	return ERROR_NONE
+	return nil
+}
+
+flush :: proc(fd: Handle) -> Error {
+	// do nothing
+	return nil
 }
 
 // We set a max of 1GB to keep alignment and to be safe.
 @(private)
 MAX_RW :: 1 << 30
 
-read :: proc(fd: Handle, data: []byte) -> (int, Errno) {
+read :: proc(fd: Handle, data: []byte) -> (int, Error) {
 	to_read    := min(c.size_t(len(data)), MAX_RW)
 	bytes_read := _unix_read(fd, &data[0], to_read)
 	if bytes_read == -1 {
-		return -1, Errno(get_last_error())
+		return -1, get_last_error()
 	}
-	return int(bytes_read), ERROR_NONE
+	return int(bytes_read), nil
 }
 
-write :: proc(fd: Handle, data: []byte) -> (int, Errno) {
+write :: proc(fd: Handle, data: []byte) -> (int, Error) {
 	if len(data) == 0 {
-		return 0, ERROR_NONE
+		return 0, nil
 	}
 
 	to_write      := min(c.size_t(len(data)), MAX_RW)
 	bytes_written := _unix_write(fd, &data[0], to_write)
 	if bytes_written == -1 {
-		return -1, Errno(get_last_error())
+		return -1, get_last_error()
+	}
+	return int(bytes_written), nil
+}
+
+read_at :: proc(fd: Handle, data: []byte, offset: i64) -> (n: int, err: Error) {
+	curr := seek(fd, offset, SEEK_CUR) or_return
+	n, err = read(fd, data)
+	_, err1 := seek(fd, curr, SEEK_SET)
+	if err1 != nil && err == nil {
+		err = err1
 	}
-	return int(bytes_written), ERROR_NONE
+	return
 }
 
-seek :: proc(fd: Handle, offset: i64, whence: int) -> (i64, Errno) {
+write_at :: proc(fd: Handle, data: []byte, offset: i64) -> (n: int, err: Error) {
+	curr := seek(fd, offset, SEEK_CUR) or_return
+	n, err = write(fd, data)
+	_, err1 := seek(fd, curr, SEEK_SET)
+	if err1 != nil && err == nil {
+		err = err1
+	}
+	return
+}
+
+seek :: proc(fd: Handle, offset: i64, whence: int) -> (i64, Error) {
 	res := _unix_seek(fd, offset, c.int(whence))
 	if res == -1 {
-		return -1, Errno(get_last_error())
+		return -1, get_last_error()
 	}
-	return res, ERROR_NONE
+	return res, nil
 }
 
-file_size :: proc(fd: Handle) -> (i64, Errno) {
-	s, err := _fstat(fd)
-	if err != ERROR_NONE {
-		return -1, err
-	}
-	return s.size, ERROR_NONE
+@(require_results)
+file_size :: proc(fd: Handle) -> (size: i64, err: Error) {
+	size = -1
+	s := _fstat(fd) or_return
+	size = s.size
+	return
 }
 
-rename :: proc(old_path, new_path: string) -> Errno {
+rename :: proc(old_path, new_path: string) -> Error {
 	runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD()
 	old_path_cstr := strings.clone_to_cstring(old_path, context.temp_allocator)
 	new_path_cstr := strings.clone_to_cstring(new_path, context.temp_allocator)
 	res := _unix_rename(old_path_cstr, new_path_cstr)
 	if res == -1 {
-		return Errno(get_last_error())
+		return get_last_error()
 	}
-	return ERROR_NONE
+	return nil
 }
 
-remove :: proc(path: string) -> Errno {
+remove :: proc(path: string) -> Error {
 	runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD()
 	path_cstr := strings.clone_to_cstring(path, context.temp_allocator)
 	res := _unix_unlink(path_cstr)
 	if res == -1 {
-		return Errno(get_last_error())
+		return get_last_error()
 	}
-	return ERROR_NONE
+	return nil
 }
 
-make_directory :: proc(path: string, mode: mode_t = 0o775) -> Errno {
+make_directory :: proc(path: string, mode: mode_t = 0o775) -> Error {
 	runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD()
 	path_cstr := strings.clone_to_cstring(path, context.temp_allocator)
 	res := _unix_mkdir(path_cstr, mode)
 	if res == -1 {
-		return Errno(get_last_error())
+		return get_last_error()
 	}
-	return ERROR_NONE
+	return nil
 }
 
-remove_directory :: proc(path: string) -> Errno {
+remove_directory :: proc(path: string) -> Error {
 	runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD()
 	path_cstr := strings.clone_to_cstring(path, context.temp_allocator)
 	res := _unix_rmdir(path_cstr)
 	if res == -1 {
-		return Errno(get_last_error())
+		return get_last_error()
 	}
-	return ERROR_NONE
+	return nil
 }
 
+@(require_results)
 is_file_handle :: proc(fd: Handle) -> bool {
 	s, err := _fstat(fd)
-	if err != ERROR_NONE {
+	if err != nil {
 		return false
 	}
 	return S_ISREG(s.mode)
 }
 
+@(require_results)
 is_file_path :: proc(path: string, follow_links: bool = true) -> bool {
 	s: OS_Stat
-	err: Errno
+	err: Error
 	if follow_links {
 		s, err = _stat(path)
 	} else {
 		s, err = _lstat(path)
 	}
-	if err != ERROR_NONE {
+	if err != nil {
 		return false
 	}
 	return S_ISREG(s.mode)
 }
 
+@(require_results)
 is_dir_handle :: proc(fd: Handle) -> bool {
 	s, err := _fstat(fd)
-	if err != ERROR_NONE {
+	if err != nil {
 		return false
 	}
 	return S_ISDIR(s.mode)
 }
 
+@(require_results)
 is_dir_path :: proc(path: string, follow_links: bool = true) -> bool {
 	s: OS_Stat
-	err: Errno
+	err: Error
 	if follow_links {
 		s, err = _stat(path)
 	} else {
 		s, err = _lstat(path)
 	}
-	if err != ERROR_NONE {
+	if err != nil {
 		return false
 	}
 	return S_ISDIR(s.mode)
@@ -490,6 +661,7 @@ is_dir_path :: proc(path: string, follow_links: bool = true) -> bool {
 is_file :: proc {is_file_path, is_file_handle}
 is_dir :: proc {is_dir_path, is_dir_handle}
 
+@(require_results)
 exists :: proc(path: string) -> bool {
 	runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD()
 	cpath := strings.clone_to_cstring(path, context.temp_allocator)
@@ -497,12 +669,13 @@ exists :: proc(path: string) -> bool {
 	return res == 0
 }
 
-fcntl :: proc(fd: int, cmd: int, arg: int) -> (int, Errno) {
+@(require_results)
+fcntl :: proc(fd: int, cmd: int, arg: int) -> (int, Error) {
 	result := _unix_fcntl(Handle(fd), c.int(cmd), uintptr(arg))
 	if result < 0 {
-		return 0, Errno(get_last_error())
+		return 0, get_last_error()
 	}
-	return int(result), ERROR_NONE
+	return int(result), nil
 }
 
 // NOTE(bill): Uses startup to initialize it
@@ -511,38 +684,34 @@ stdin: Handle  = 0
 stdout: Handle = 1
 stderr: Handle = 2
 
-last_write_time :: proc(fd: Handle) -> (File_Time, Errno) {
-	s, err := _fstat(fd)
-	if err != ERROR_NONE {
-		return 0, err
-	}
+@(require_results)
+last_write_time :: proc(fd: Handle) -> (time: File_Time, err: Error) {
+	s := _fstat(fd) or_return
 	modified := s.modified.seconds * 1_000_000_000 + s.modified.nanoseconds
-	return File_Time(modified), ERROR_NONE
+	return File_Time(modified), nil
 }
 
-last_write_time_by_name :: proc(name: string) -> (File_Time, Errno) {
-	s, err := _stat(name)
-	if err != ERROR_NONE {
-		return 0, err
-	}
+@(require_results)
+last_write_time_by_name :: proc(name: string) -> (time: File_Time, err: Error) {
+	s := _stat(name) or_return
 	modified := s.modified.seconds * 1_000_000_000 + s.modified.nanoseconds
-	return File_Time(modified), ERROR_NONE
+	return File_Time(modified), nil
 }
 
-@private
-_stat :: proc(path: string) -> (OS_Stat, Errno) {
+@(private, require_results)
+_stat :: proc(path: string) -> (OS_Stat, Error) {
 	runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD()
 	cstr := strings.clone_to_cstring(path, context.temp_allocator)
 	s: OS_Stat = ---
 	result := _unix_lstat(cstr, &s)
 	if result == -1 {
-		return s, Errno(get_last_error())
+		return s, get_last_error()
 	}
-	return s, ERROR_NONE
+	return s, nil
 }
 
-@private
-_lstat :: proc(path: string) -> (OS_Stat, Errno) {
+@(private, require_results)
+_lstat :: proc(path: string) -> (OS_Stat, Error) {
 	runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD()
 	cstr := strings.clone_to_cstring(path, context.temp_allocator)
 	
@@ -550,54 +719,54 @@ _lstat :: proc(path: string) -> (OS_Stat, Errno) {
 	s: OS_Stat = ---
 	res := _unix_lstat(cstr, &s)
 	if res == -1 {
-		return s, Errno(get_last_error())
+		return s, get_last_error()
 	}
-	return s, ERROR_NONE
+	return s, nil
 }
 
-@private
-_fstat :: proc(fd: Handle) -> (OS_Stat, Errno) {
+@(private, require_results)
+_fstat :: proc(fd: Handle) -> (OS_Stat, Error) {
 	s: OS_Stat = ---
 	result := _unix_fstat(fd, &s)
 	if result == -1 {
-		return s, Errno(get_last_error())
+		return s, get_last_error()
 	}
-	return s, ERROR_NONE
+	return s, nil
 }
 
-@private
-_fdopendir :: proc(fd: Handle) -> (Dir, Errno) {
+@(private, require_results)
+_fdopendir :: proc(fd: Handle) -> (Dir, Error) {
 	dirp := _unix_fdopendir(fd)
 	if dirp == cast(Dir)nil {
-		return nil, Errno(get_last_error())
+		return nil, get_last_error()
 	}
-	return dirp, ERROR_NONE
+	return dirp, nil
 }
 
-@private
-_closedir :: proc(dirp: Dir) -> Errno {
+@(private)
+_closedir :: proc(dirp: Dir) -> Error {
 	rc := _unix_closedir(dirp)
 	if rc != 0 {
-		return Errno(get_last_error())
+		return get_last_error()
 	}
-	return ERROR_NONE
+	return nil
 }
 
-@private
+@(private)
 _rewinddir :: proc(dirp: Dir) {
 	_unix_rewinddir(dirp)
 }
 
-@private
-_readdir :: proc(dirp: Dir) -> (entry: Dirent, err: Errno, end_of_stream: bool) {
+@(private, require_results)
+_readdir :: proc(dirp: Dir) -> (entry: Dirent, err: Error, end_of_stream: bool) {
 	result: ^Dirent
 	rc := _unix_readdir_r(dirp, &entry, &result)
 
 	if rc != 0 {
-		err = Errno(get_last_error())
+		err = get_last_error()
 		return
 	}
-	err = ERROR_NONE
+	err = nil
 
 	if result == nil {
 		end_of_stream = true
@@ -607,8 +776,8 @@ _readdir :: proc(dirp: Dir) -> (entry: Dirent, err: Errno, end_of_stream: bool)
 	return
 }
 
-@private
-_readlink :: proc(path: string) -> (string, Errno) {
+@(private, require_results)
+_readlink :: proc(path: string) -> (string, Error) {
 	runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD(ignore = context.temp_allocator == context.allocator)
 
 	path_cstr := strings.clone_to_cstring(path, context.temp_allocator)
@@ -619,31 +788,28 @@ _readlink :: proc(path: string) -> (string, Errno) {
 		rc := _unix_readlink(path_cstr, &(buf[0]), bufsz)
 		if rc == -1 {
 			delete(buf)
-			return "", Errno(get_last_error())
+			return "", get_last_error()
 		} else if rc == int(bufsz) {
 			bufsz += MAX_PATH
 			delete(buf)
 			buf = make([]byte, bufsz)
 		} else {
-			return strings.string_from_ptr(&buf[0], rc), ERROR_NONE
+			return strings.string_from_ptr(&buf[0], rc), nil
 		}	
 	}
 
-	return "", Errno{}
+	return "", Error{}
 }
 
-absolute_path_from_handle :: proc(fd: Handle) -> (string, Errno) {
+@(require_results)
+absolute_path_from_handle :: proc(fd: Handle) -> (path: string, err: Error) {
 	buf: [MAX_PATH]byte
-	_, err := fcntl(int(fd), F_GETPATH, int(uintptr(&buf[0])))
-	if err != ERROR_NONE {
-		return "", err
-	}
-
-	path := strings.clone_from_cstring(cstring(&buf[0]))
-	return path, err
+	_ = fcntl(int(fd), F_GETPATH, int(uintptr(&buf[0]))) or_return
+	return strings.clone_from_cstring(cstring(&buf[0]))
 }
 
-absolute_path_from_relative :: proc(rel: string) -> (path: string, err: Errno) {
+@(require_results)
+absolute_path_from_relative :: proc(rel: string) -> (path: string, err: Error) {
 	rel := rel
 	if rel == "" {
 		rel = "."
@@ -654,26 +820,27 @@ absolute_path_from_relative :: proc(rel: string) -> (path: string, err: Errno) {
 
 	path_ptr := _unix_realpath(rel_cstr, nil)
 	if path_ptr == nil {
-		return "", Errno(get_last_error())
+		return "", get_last_error()
 	}
 	defer _unix_free(path_ptr)
 
 	path = strings.clone(string(cstring(path_ptr)))
 
-	return path, ERROR_NONE
+	return path, nil
 }
 
-access :: proc(path: string, mask: int) -> (bool, Errno) {
+access :: proc(path: string, mask: int) -> (bool, Error) {
 	runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD()
 
 	cstr := strings.clone_to_cstring(path, context.temp_allocator)
 	result := _unix_access(cstr, c.int(mask))
 	if result == -1 {
-		return false, Errno(get_last_error())
+		return false, get_last_error()
 	}
-	return true, ERROR_NONE
+	return true, nil
 }
 
+@(require_results)
 lookup_env :: proc(key: string, allocator := context.allocator) -> (value: string, found: bool) {
 	runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD(ignore = context.temp_allocator == allocator)
 
@@ -685,11 +852,13 @@ lookup_env :: proc(key: string, allocator := context.allocator) -> (value: strin
 	return strings.clone(string(cstr), allocator), true
 }
 
+@(require_results)
 get_env :: proc(key: string, allocator := context.allocator) -> (value: string) {
 	value, _ = lookup_env(key, allocator)
 	return
 }
 
+@(require_results)
 get_current_directory :: proc() -> string {
 	// NOTE(tetra): I would use PATH_MAX here, but I was not able to find
 	// an authoritative value for it across all systems.
@@ -701,7 +870,7 @@ get_current_directory :: proc() -> string {
 		if cwd != nil {
 			return string(cwd)
 		}
-		if Errno(get_last_error()) != ERANGE {
+		if get_last_error() != ERANGE {
 			delete(buf)
 			return ""
 		}
@@ -710,14 +879,14 @@ get_current_directory :: proc() -> string {
 	unreachable()
 }
 
-set_current_directory :: proc(path: string) -> (err: Errno) {
+set_current_directory :: proc(path: string) -> (err: Error) {
 	runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD()
 	cstr := strings.clone_to_cstring(path, context.temp_allocator)
 	res := _unix_chdir(cstr)
 	if res == -1 {
-		return Errno(get_last_error())
+		return get_last_error()
 	}
-	return ERROR_NONE
+	return nil
 }
 
 exit :: proc "contextless" (code: int) -> ! {
@@ -725,10 +894,12 @@ exit :: proc "contextless" (code: int) -> ! {
 	_unix_exit(c.int(code))
 }
 
+@(require_results)
 current_thread_id :: proc "contextless" () -> int {
 	return int(_lwp_self())
 }
 
+@(require_results)
 dlopen :: proc(filename: string, flags: int) -> rawptr {
 	runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD()
 	cstr := strings.clone_to_cstring(filename, context.temp_allocator)
@@ -736,6 +907,7 @@ dlopen :: proc(filename: string, flags: int) -> rawptr {
 	return handle
 }
 
+@(require_results)
 dlsym :: proc(handle: rawptr, symbol: string) -> rawptr {
 	assert(handle != nil)
 	runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD()
@@ -749,10 +921,12 @@ dlclose :: proc(handle: rawptr) -> bool {
 	return _unix_dlclose(handle) == 0
 }
 
+@(require_results)
 dlerror :: proc() -> string {
 	return string(_unix_dlerror())
 }
 
+@(require_results)
 get_page_size :: proc() -> int {
 	// NOTE(tetra): The page size never changes, so why do anything complicated
 	// if we don't have to.
@@ -765,7 +939,7 @@ get_page_size :: proc() -> int {
 	return page_size
 }
 
-@(private)
+@(private, require_results)
 _processor_core_count :: proc() -> int {
 	count : int = 0
 	count_size := size_of(count)
@@ -778,6 +952,7 @@ _processor_core_count :: proc() -> int {
 	return 1
 }
 
+@(require_results)
 _alloc_command_line_arguments :: proc() -> []string {
 	res := make([]string, len(runtime.args__))
 	for arg, i in runtime.args__ {

+ 342 - 205
core/os/os_openbsd.odin

@@ -9,108 +9,205 @@ import "base:runtime"
 Handle    :: distinct i32
 Pid       :: distinct i32
 File_Time :: distinct u64
-Errno     :: distinct i32
 
 INVALID_HANDLE :: ~Handle(0)
 
-ERROR_NONE:	Errno: 0
-
-EPERM:		Errno: 1
-ENOENT:		Errno: 2
-ESRCH:		Errno: 3
-EINTR:		Errno: 4
-EIO:		Errno: 5
-ENXIO:		Errno: 6
-E2BIG:		Errno: 7
-ENOEXEC:	Errno: 8
-EBADF:		Errno: 9
-ECHILD:		Errno: 10
-EDEADLK:	Errno: 11
-ENOMEM:		Errno: 12
-EACCES:		Errno: 13
-EFAULT:		Errno: 14
-ENOTBLK:	Errno: 15
-EBUSY:		Errno: 16
-EEXIST:		Errno: 17
-EXDEV:		Errno: 18
-ENODEV:		Errno: 19
-ENOTDIR:	Errno: 20
-EISDIR:		Errno: 21
-EINVAL:		Errno: 22
-ENFILE:		Errno: 23
-EMFILE:		Errno: 24
-ENOTTY:		Errno: 25
-ETXTBSY:	Errno: 26
-EFBIG:		Errno: 27
-ENOSPC:		Errno: 28
-ESPIPE:		Errno: 29
-EROFS:		Errno: 30
-EMLINK:		Errno: 31
-EPIPE:		Errno: 32
-EDOM:		Errno: 33
-ERANGE:		Errno: 34
-EAGAIN:		Errno: 35
-EWOULDBLOCK:	Errno: EAGAIN
-EINPROGRESS:	Errno: 36
-EALREADY:	Errno: 37
-ENOTSOCK:	Errno: 38
-EDESTADDRREQ:	Errno: 39
-EMSGSIZE:	Errno: 40
-EPROTOTYPE:	Errno: 41
-ENOPROTOOPT:	Errno: 42
-EPROTONOSUPPORT: Errno: 43
-ESOCKTNOSUPPORT: Errno: 44
-EOPNOTSUPP:	Errno: 45
-EPFNOSUPPORT:	Errno: 46
-EAFNOSUPPORT:	Errno: 47
-EADDRINUSE:	Errno: 48
-EADDRNOTAVAIL:	Errno: 49
-ENETDOWN:	Errno: 50
-ENETUNREACH:	Errno: 51
-ENETRESET:	Errno: 52
-ECONNABORTED:	Errno: 53
-ECONNRESET:	Errno: 54
-ENOBUFS:	Errno: 55
-EISCONN:	Errno: 56
-ENOTCONN:	Errno: 57
-ESHUTDOWN:	Errno: 58
-ETOOMANYREFS:	Errno: 59
-ETIMEDOUT:	Errno: 60
-ECONNREFUSED:	Errno: 61
-ELOOP:		Errno: 62
-ENAMETOOLONG:	Errno: 63
-EHOSTDOWN:	Errno: 64
-EHOSTUNREACH:	Errno: 65
-ENOTEMPTY:	Errno: 66
-EPROCLIM:	Errno: 67
-EUSERS:		Errno: 68
-EDQUOT:		Errno: 69
-ESTALE:		Errno: 70
-EREMOTE:	Errno: 71
-EBADRPC:	Errno: 72
-ERPCMISMATCH:	Errno: 73
-EPROGUNAVAIL:	Errno: 74
-EPROGMISMATCH:	Errno: 75
-EPROCUNAVAIL:	Errno: 76
-ENOLCK:		Errno: 77
-ENOSYS:		Errno: 78
-EFTYPE:		Errno: 79
-EAUTH:		Errno: 80
-ENEEDAUTH:	Errno: 81
-EIPSEC:		Errno: 82
-ENOATTR:	Errno: 83
-EILSEQ:		Errno: 84
-ENOMEDIUM:	Errno: 85
-EMEDIUMTYPE:	Errno: 86
-EOVERFLOW:	Errno: 87
-ECANCELED:	Errno: 88
-EIDRM:		Errno: 89
-ENOMSG:		Errno: 90
-ENOTSUP:	Errno: 91
-EBADMSG:	Errno: 92
-ENOTRECOVERABLE: Errno: 93
-EOWNERDEAD:	Errno: 94
-EPROTO:		Errno: 95
+_Platform_Error :: enum i32 {
+	NONE = 0,
+	EPERM           = 1,
+	ENOENT          = 2,
+	ESRCH           = 3,
+	EINTR           = 4,
+	EIO             = 5,
+	ENXIO           = 6,
+	E2BIG           = 7,
+	ENOEXEC         = 8,
+	EBADF           = 9,
+	ECHILD          = 10,
+	EDEADLK         = 11,
+	ENOMEM          = 12,
+	EACCES          = 13,
+	EFAULT          = 14,
+	ENOTBLK         = 15,
+	EBUSY           = 16,
+	EEXIST          = 17,
+	EXDEV           = 18,
+	ENODEV          = 19,
+	ENOTDIR         = 20,
+	EISDIR          = 21,
+	EINVAL          = 22,
+	ENFILE          = 23,
+	EMFILE          = 24,
+	ENOTTY          = 25,
+	ETXTBSY         = 26,
+	EFBIG           = 27,
+	ENOSPC          = 28,
+	ESPIPE          = 29,
+	EROFS           = 30,
+	EMLINK          = 31,
+	EPIPE           = 32,
+	EDOM            = 33,
+	ERANGE          = 34,
+	EAGAIN          = 35,
+	EWOULDBLOCK     = EAGAIN,
+	EINPROGRESS     = 36,
+	EALREADY        = 37,
+	ENOTSOCK        = 38,
+	EDESTADDRREQ    = 39,
+	EMSGSIZE        = 40,
+	EPROTOTYPE      = 41,
+	ENOPROTOOPT     = 42,
+	EPROTONOSUPPORT = 43,
+	ESOCKTNOSUPPORT = 44,
+	EOPNOTSUPP      = 45,
+	EPFNOSUPPORT    = 46,
+	EAFNOSUPPORT    = 47,
+	EADDRINUSE      = 48,
+	EADDRNOTAVAIL   = 49,
+	ENETDOWN        = 50,
+	ENETUNREACH     = 51,
+	ENETRESET       = 52,
+	ECONNABORTED    = 53,
+	ECONNRESET      = 54,
+	ENOBUFS         = 55,
+	EISCONN         = 56,
+	ENOTCONN        = 57,
+	ESHUTDOWN       = 58,
+	ETOOMANYREFS    = 59,
+	ETIMEDOUT       = 60,
+	ECONNREFUSED    = 61,
+	ELOOP           = 62,
+	ENAMETOOLONG    = 63,
+	EHOSTDOWN       = 64,
+	EHOSTUNREACH    = 65,
+	ENOTEMPTY       = 66,
+	EPROCLIM        = 67,
+	EUSERS          = 68,
+	EDQUOT          = 69,
+	ESTALE          = 70,
+	EREMOTE         = 71,
+	EBADRPC         = 72,
+	ERPCMISMATCH    = 73,
+	EPROGUNAVAIL    = 74,
+	EPROGMISMATCH   = 75,
+	EPROCUNAVAIL    = 76,
+	ENOLCK          = 77,
+	ENOSYS          = 78,
+	EFTYPE          = 79,
+	EAUTH           = 80,
+	ENEEDAUTH       = 81,
+	EIPSEC          = 82,
+	ENOATTR         = 83,
+	EILSEQ          = 84,
+	ENOMEDIUM       = 85,
+	EMEDIUMTYPE     = 86,
+	EOVERFLOW       = 87,
+	ECANCELED       = 88,
+	EIDRM           = 89,
+	ENOMSG          = 90,
+	ENOTSUP         = 91,
+	EBADMSG         = 92,
+	ENOTRECOVERABLE = 93,
+	EOWNERDEAD      = 94,
+	EPROTO          = 95,
+}
+
+EPERM           :: Platform_Error.EPERM
+ENOENT          :: Platform_Error.ENOENT
+ESRCH           :: Platform_Error.ESRCH
+EINTR           :: Platform_Error.EINTR
+EIO             :: Platform_Error.EIO
+ENXIO           :: Platform_Error.ENXIO
+E2BIG           :: Platform_Error.E2BIG
+ENOEXEC         :: Platform_Error.ENOEXEC
+EBADF           :: Platform_Error.EBADF
+ECHILD          :: Platform_Error.ECHILD
+EDEADLK         :: Platform_Error.EDEADLK
+ENOMEM          :: Platform_Error.ENOMEM
+EACCES          :: Platform_Error.EACCES
+EFAULT          :: Platform_Error.EFAULT
+ENOTBLK         :: Platform_Error.ENOTBLK
+EBUSY           :: Platform_Error.EBUSY
+EEXIST          :: Platform_Error.EEXIST
+EXDEV           :: Platform_Error.EXDEV
+ENODEV          :: Platform_Error.ENODEV
+ENOTDIR         :: Platform_Error.ENOTDIR
+EISDIR          :: Platform_Error.EISDIR
+EINVAL          :: Platform_Error.EINVAL
+ENFILE          :: Platform_Error.ENFILE
+EMFILE          :: Platform_Error.EMFILE
+ENOTTY          :: Platform_Error.ENOTTY
+ETXTBSY         :: Platform_Error.ETXTBSY
+EFBIG           :: Platform_Error.EFBIG
+ENOSPC          :: Platform_Error.ENOSPC
+ESPIPE          :: Platform_Error.ESPIPE
+EROFS           :: Platform_Error.EROFS
+EMLINK          :: Platform_Error.EMLINK
+EPIPE           :: Platform_Error.EPIPE
+EDOM            :: Platform_Error.EDOM
+ERANGE          :: Platform_Error.ERANGE
+EAGAIN          :: Platform_Error.EAGAIN
+EWOULDBLOCK     :: Platform_Error.EWOULDBLOCK
+EINPROGRESS     :: Platform_Error.EINPROGRESS
+EALREADY        :: Platform_Error.EALREADY
+ENOTSOCK        :: Platform_Error.ENOTSOCK
+EDESTADDRREQ    :: Platform_Error.EDESTADDRREQ
+EMSGSIZE        :: Platform_Error.EMSGSIZE
+EPROTOTYPE      :: Platform_Error.EPROTOTYPE
+ENOPROTOOPT     :: Platform_Error.ENOPROTOOPT
+EPROTONOSUPPORT :: Platform_Error.EPROTONOSUPPORT
+ESOCKTNOSUPPORT :: Platform_Error.ESOCKTNOSUPPORT
+EOPNOTSUPP      :: Platform_Error.EOPNOTSUPP
+EPFNOSUPPORT    :: Platform_Error.EPFNOSUPPORT
+EAFNOSUPPORT    :: Platform_Error.EAFNOSUPPORT
+EADDRINUSE      :: Platform_Error.EADDRINUSE
+EADDRNOTAVAIL   :: Platform_Error.EADDRNOTAVAIL
+ENETDOWN        :: Platform_Error.ENETDOWN
+ENETUNREACH     :: Platform_Error.ENETUNREACH
+ENETRESET       :: Platform_Error.ENETRESET
+ECONNABORTED    :: Platform_Error.ECONNABORTED
+ECONNRESET      :: Platform_Error.ECONNRESET
+ENOBUFS         :: Platform_Error.ENOBUFS
+EISCONN         :: Platform_Error.EISCONN
+ENOTCONN        :: Platform_Error.ENOTCONN
+ESHUTDOWN       :: Platform_Error.ESHUTDOWN
+ETOOMANYREFS    :: Platform_Error.ETOOMANYREFS
+ETIMEDOUT       :: Platform_Error.ETIMEDOUT
+ECONNREFUSED    :: Platform_Error.ECONNREFUSED
+ELOOP           :: Platform_Error.ELOOP
+ENAMETOOLONG    :: Platform_Error.ENAMETOOLONG
+EHOSTDOWN       :: Platform_Error.EHOSTDOWN
+EHOSTUNREACH    :: Platform_Error.EHOSTUNREACH
+ENOTEMPTY       :: Platform_Error.ENOTEMPTY
+EPROCLIM        :: Platform_Error.EPROCLIM
+EUSERS          :: Platform_Error.EUSERS
+EDQUOT          :: Platform_Error.EDQUOT
+ESTALE          :: Platform_Error.ESTALE
+EREMOTE         :: Platform_Error.EREMOTE
+EBADRPC         :: Platform_Error.EBADRPC
+ERPCMISMATCH    :: Platform_Error.ERPCMISMATCH
+EPROGUNAVAIL    :: Platform_Error.EPROGUNAVAIL
+EPROGMISMATCH   :: Platform_Error.EPROGMISMATCH
+EPROCUNAVAIL    :: Platform_Error.EPROCUNAVAIL
+ENOLCK          :: Platform_Error.ENOLCK
+ENOSYS          :: Platform_Error.ENOSYS
+EFTYPE          :: Platform_Error.EFTYPE
+EAUTH           :: Platform_Error.EAUTH
+ENEEDAUTH       :: Platform_Error.ENEEDAUTH
+EIPSEC          :: Platform_Error.EIPSEC
+ENOATTR         :: Platform_Error.ENOATTR
+EILSEQ          :: Platform_Error.EILSEQ
+ENOMEDIUM       :: Platform_Error.ENOMEDIUM
+EMEDIUMTYPE     :: Platform_Error.EMEDIUMTYPE
+EOVERFLOW       :: Platform_Error.EOVERFLOW
+ECANCELED       :: Platform_Error.ECANCELED
+EIDRM           :: Platform_Error.EIDRM
+ENOMSG          :: Platform_Error.ENOMSG
+ENOTSUP         :: Platform_Error.ENOTSUP
+EBADMSG         :: Platform_Error.EBADMSG
+ENOTRECOVERABLE :: Platform_Error.ENOTRECOVERABLE
+EOWNERDEAD      :: Platform_Error.EOWNERDEAD
+EPROTO          :: Platform_Error.EPROTO
 
 O_RDONLY   :: 0x00000
 O_WRONLY   :: 0x00001
@@ -225,13 +322,13 @@ S_ISUID :: 0o4000 // Set user id on execution
 S_ISGID :: 0o2000 // Set group id on execution
 S_ISTXT :: 0o1000 // Sticky bit
 
-S_ISLNK  :: #force_inline proc(m: u32) -> bool { return (m & S_IFMT) == S_IFLNK  }
-S_ISREG  :: #force_inline proc(m: u32) -> bool { return (m & S_IFMT) == S_IFREG  }
-S_ISDIR  :: #force_inline proc(m: u32) -> bool { return (m & S_IFMT) == S_IFDIR  }
-S_ISCHR  :: #force_inline proc(m: u32) -> bool { return (m & S_IFMT) == S_IFCHR  }
-S_ISBLK  :: #force_inline proc(m: u32) -> bool { return (m & S_IFMT) == S_IFBLK  }
-S_ISFIFO :: #force_inline proc(m: u32) -> bool { return (m & S_IFMT) == S_IFIFO  }
-S_ISSOCK :: #force_inline proc(m: u32) -> bool { return (m & S_IFMT) == S_IFSOCK }
+@(require_results) S_ISLNK  :: #force_inline proc(m: u32) -> bool { return (m & S_IFMT) == S_IFLNK  }
+@(require_results) S_ISREG  :: #force_inline proc(m: u32) -> bool { return (m & S_IFMT) == S_IFREG  }
+@(require_results) S_ISDIR  :: #force_inline proc(m: u32) -> bool { return (m & S_IFMT) == S_IFDIR  }
+@(require_results) S_ISCHR  :: #force_inline proc(m: u32) -> bool { return (m & S_IFMT) == S_IFCHR  }
+@(require_results) S_ISBLK  :: #force_inline proc(m: u32) -> bool { return (m & S_IFMT) == S_IFBLK  }
+@(require_results) S_ISFIFO :: #force_inline proc(m: u32) -> bool { return (m & S_IFMT) == S_IFIFO  }
+@(require_results) S_ISSOCK :: #force_inline proc(m: u32) -> bool { return (m & S_IFMT) == S_IFSOCK }
 
 F_OK :: 0x00 // Test for file existance
 X_OK :: 0x01 // Test for execute permission
@@ -291,38 +388,47 @@ foreign libc {
 	@(link_name="dlerror")	_unix_dlerror	:: proc() -> cstring ---
 }
 
+@(require_results)
 is_path_separator :: proc(r: rune) -> bool {
 	return r == '/'
 }
 
-get_last_error :: proc "contextless" () -> int {
-	return int(__error()^)
+@(require_results, no_instrumentation)
+get_last_error :: proc "contextless" () -> Error {
+	return Platform_Error(__error()^)
 }
 
-fork :: proc() -> (Pid, Errno) {
+@(require_results)
+fork :: proc() -> (Pid, Error) {
 	pid := _unix_fork()
 	if pid == -1 {
-		return Pid(-1), Errno(get_last_error())
+		return Pid(-1), get_last_error()
 	}
-	return Pid(pid), ERROR_NONE
+	return Pid(pid), nil
 }
 
-open :: proc(path: string, flags: int = O_RDONLY, mode: int = 0) -> (Handle, Errno) {
+@(require_results)
+open :: proc(path: string, flags: int = O_RDONLY, mode: int = 0) -> (Handle, Error) {
 	runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD()
 	cstr := strings.clone_to_cstring(path, context.temp_allocator)
 	handle := _unix_open(cstr, c.int(flags), c.int(mode))
 	if handle == -1 {
-		return INVALID_HANDLE, Errno(get_last_error())
+		return INVALID_HANDLE, get_last_error()
 	}
-	return handle, ERROR_NONE
+	return handle, nil
 }
 
-close :: proc(fd: Handle) -> Errno {
+close :: proc(fd: Handle) -> Error {
 	result := _unix_close(fd)
 	if result == -1 {
-		return Errno(get_last_error())
+		return get_last_error()
 	}
-	return ERROR_NONE
+	return nil
+}
+
+flush :: proc(fd: Handle) -> Error {
+	// do nothing
+	return nil
 }
 
 // If you read or write more than `SSIZE_MAX` bytes, OpenBSD returns `EINVAL`.
@@ -333,124 +439,148 @@ close :: proc(fd: Handle) -> Errno {
 @(private)
 MAX_RW :: 1 << 30
 
-read :: proc(fd: Handle, data: []byte) -> (int, Errno) {
+read :: proc(fd: Handle, data: []byte) -> (int, Error) {
 	to_read    := min(c.size_t(len(data)), MAX_RW)
 	bytes_read := _unix_read(fd, &data[0], to_read)
 	if bytes_read == -1 {
-		return -1, Errno(get_last_error())
+		return -1, get_last_error()
 	}
-	return int(bytes_read), ERROR_NONE
+	return int(bytes_read), nil
 }
 
-write :: proc(fd: Handle, data: []byte) -> (int, Errno) {
+write :: proc(fd: Handle, data: []byte) -> (int, Error) {
 	if len(data) == 0 {
-		return 0, ERROR_NONE
+		return 0, nil
 	}
 
 	to_write      := min(c.size_t(len(data)), MAX_RW)
 	bytes_written := _unix_write(fd, &data[0], to_write)
 	if bytes_written == -1 {
-		return -1, Errno(get_last_error())
+		return -1, get_last_error()
+	}
+	return int(bytes_written), nil
+}
+
+read_at :: proc(fd: Handle, data: []byte, offset: i64) -> (n: int, err: Error) {
+	curr := seek(fd, offset, SEEK_CUR) or_return
+	n, err = read(fd, data)
+	_, err1 := seek(fd, curr, SEEK_SET)
+	if err1 != nil && err == nil {
+		err = err1
 	}
-	return int(bytes_written), ERROR_NONE
+	return
+}
+
+write_at :: proc(fd: Handle, data: []byte, offset: i64) -> (n: int, err: Error) {
+	curr := seek(fd, offset, SEEK_CUR) or_return
+	n, err = write(fd, data)
+	_, err1 := seek(fd, curr, SEEK_SET)
+	if err1 != nil && err == nil {
+		err = err1
+	}
+	return
 }
 
-seek :: proc(fd: Handle, offset: i64, whence: int) -> (i64, Errno) {
+seek :: proc(fd: Handle, offset: i64, whence: int) -> (i64, Error) {
 	res := _unix_seek(fd, offset, c.int(whence))
 	if res == -1 {
-		return -1, Errno(get_last_error())
+		return -1, get_last_error()
 	}
-	return res, ERROR_NONE
+	return res, nil
 }
 
-file_size :: proc(fd: Handle) -> (i64, Errno) {
-	s, err := _fstat(fd)
-	if err != ERROR_NONE {
-		return -1, err
-	}
-	return s.size, ERROR_NONE
+@(require_results)
+file_size :: proc(fd: Handle) -> (size: i64, err: Error) {
+	size = -1
+	s :=  _fstat(fd) or_return
+	size = s.size
+	return
 }
 
-rename :: proc(old_path, new_path: string) -> Errno {
+rename :: proc(old_path, new_path: string) -> Error {
 	runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD()
 	old_path_cstr := strings.clone_to_cstring(old_path, context.temp_allocator)
 	new_path_cstr := strings.clone_to_cstring(new_path, context.temp_allocator)
 	res := _unix_rename(old_path_cstr, new_path_cstr)
 	if res == -1 {
-		return Errno(get_last_error())
+		return get_last_error()
 	}
-	return ERROR_NONE
+	return nil
 }
 
-remove :: proc(path: string) -> Errno {
+remove :: proc(path: string) -> Error {
 	runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD()
 	path_cstr := strings.clone_to_cstring(path, context.temp_allocator)
 	res := _unix_unlink(path_cstr)
 	if res == -1 {
-		return Errno(get_last_error())
+		return get_last_error()
 	}
-	return ERROR_NONE
+	return nil
 }
 
-make_directory :: proc(path: string, mode: mode_t = 0o775) -> Errno {
+make_directory :: proc(path: string, mode: mode_t = 0o775) -> Error {
 	runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD()
 	path_cstr := strings.clone_to_cstring(path, context.temp_allocator)
 	res := _unix_mkdir(path_cstr, mode)
 	if res == -1 {
-		return Errno(get_last_error())
+		return get_last_error()
 	}
-	return ERROR_NONE
+	return nil
 }
 
-remove_directory :: proc(path: string) -> Errno {
+remove_directory :: proc(path: string) -> Error {
 	runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD()
 	path_cstr := strings.clone_to_cstring(path, context.temp_allocator)
 	res := _unix_rmdir(path_cstr)
 	if res == -1 {
-		return Errno(get_last_error())
+		return get_last_error()
 	}
-	return ERROR_NONE
+	return nil
 }
 
+@(require_results)
 is_file_handle :: proc(fd: Handle) -> bool {
 	s, err := _fstat(fd)
-	if err != ERROR_NONE {
+	if err != nil {
 		return false
 	}
 	return S_ISREG(s.mode)
 }
 
+@(require_results)
 is_file_path :: proc(path: string, follow_links: bool = true) -> bool {
 	s: OS_Stat
-	err: Errno
+	err: Error
 	if follow_links {
 		s, err = _stat(path)
 	} else {
 		s, err = _lstat(path)
 	}
-	if err != ERROR_NONE {
+	if err != nil {
 		return false
 	}
 	return S_ISREG(s.mode)
 }
 
+@(require_results)
 is_dir_handle :: proc(fd: Handle) -> bool {
 	s, err := _fstat(fd)
-	if err != ERROR_NONE {
+	if err != nil {
 		return false
 	}
 	return S_ISDIR(s.mode)
 }
 
+@(require_results)
 is_dir_path :: proc(path: string, follow_links: bool = true) -> bool {
 	s: OS_Stat
-	err: Errno
+	err: Error
 	if follow_links {
 		s, err = _stat(path)
 	} else {
 		s, err = _lstat(path)
 	}
-	if err != ERROR_NONE {
+	if err != nil {
 		return false
 	}
 	return S_ISDIR(s.mode)
@@ -469,26 +599,22 @@ stderr: Handle = 2
 last_write_time :: proc(fd: Handle) -> File_Time {}                                                               
 last_write_time_by_name :: proc(name: string) -> File_Time {}                                                     
 */
-last_write_time :: proc(fd: Handle) -> (File_Time, Errno) {
-	s, err := _fstat(fd)
-	if err != ERROR_NONE {
-		return 0, err
-	}
+@(require_results)
+last_write_time :: proc(fd: Handle) -> (time: File_Time, err: Error) {
+	s := _fstat(fd) or_return
 	modified := s.modified.seconds * 1_000_000_000 + s.modified.nanoseconds
-	return File_Time(modified), ERROR_NONE
+	return File_Time(modified), nil
 }
 
-last_write_time_by_name :: proc(name: string) -> (File_Time, Errno) {
-	s, err := _stat(name)
-	if err != ERROR_NONE {
-		return 0, err
-	}
+@(require_results)
+last_write_time_by_name :: proc(name: string) -> (time: File_Time, err: Error) {
+	s := _stat(name) or_return
 	modified := s.modified.seconds * 1_000_000_000 + s.modified.nanoseconds
-	return File_Time(modified), ERROR_NONE
+	return File_Time(modified), nil
 }
 
-@private
-_stat :: proc(path: string) -> (OS_Stat, Errno) {
+@(private, require_results)
+_stat :: proc(path: string) -> (OS_Stat, Error) {
 	runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD()
 	cstr := strings.clone_to_cstring(path, context.temp_allocator)
 
@@ -496,13 +622,13 @@ _stat :: proc(path: string) -> (OS_Stat, Errno) {
 	s: OS_Stat = ---
 	res := _unix_stat(cstr, &s)
 	if res == -1 {
-		return s, Errno(get_last_error())
+		return s, get_last_error()
 	}
-	return s, ERROR_NONE
+	return s, nil
 }
 
-@private
-_lstat :: proc(path: string) -> (OS_Stat, Errno) {
+@(private, require_results)
+_lstat :: proc(path: string) -> (OS_Stat, Error) {
 	runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD()
 	cstr := strings.clone_to_cstring(path, context.temp_allocator)
 
@@ -510,55 +636,55 @@ _lstat :: proc(path: string) -> (OS_Stat, Errno) {
 	s: OS_Stat = ---
 	res := _unix_lstat(cstr, &s)
 	if res == -1 {
-		return s, Errno(get_last_error())
+		return s, get_last_error()
 	}
-	return s, ERROR_NONE
+	return s, nil
 }
 
-@private
-_fstat :: proc(fd: Handle) -> (OS_Stat, Errno) {
+@(private, require_results)
+_fstat :: proc(fd: Handle) -> (OS_Stat, Error) {
 	// deliberately uninitialized
 	s: OS_Stat = ---
 	res := _unix_fstat(fd, &s)
 	if res == -1 {
-		return s, Errno(get_last_error())
+		return s, get_last_error()
 	}
-	return s, ERROR_NONE
+	return s, nil
 }
 
-@private
-_fdopendir :: proc(fd: Handle) -> (Dir, Errno) {
+@(private, require_results)
+_fdopendir :: proc(fd: Handle) -> (Dir, Error) {
 	dirp := _unix_fdopendir(fd)
 	if dirp == cast(Dir)nil {
-		return nil, Errno(get_last_error())
+		return nil, get_last_error()
 	}
-	return dirp, ERROR_NONE
+	return dirp, nil
 }
 
-@private
-_closedir :: proc(dirp: Dir) -> Errno {
+@(private)
+_closedir :: proc(dirp: Dir) -> Error {
 	rc := _unix_closedir(dirp)
 	if rc != 0 {
-		return Errno(get_last_error())
+		return get_last_error()
 	}
-	return ERROR_NONE
+	return nil
 }
 
-@private
+@(private)
 _rewinddir :: proc(dirp: Dir) {
 	_unix_rewinddir(dirp)
 }
 
-@private
-_readdir :: proc(dirp: Dir) -> (entry: Dirent, err: Errno, end_of_stream: bool) {
+@(private, require_results)
+_readdir :: proc(dirp: Dir) -> (entry: Dirent, err: Error, end_of_stream: bool) {
 	result: ^Dirent
 	rc := _unix_readdir_r(dirp, &entry, &result)
 
 	if rc != 0 {
-		err = Errno(get_last_error())
+		err = get_last_error()
 		return
 	}
-	err = ERROR_NONE
+	err = nil
 
 	if result == nil {
 		end_of_stream = true
@@ -568,8 +694,8 @@ _readdir :: proc(dirp: Dir) -> (entry: Dirent, err: Errno, end_of_stream: bool)
 	return
 }
 
-@private
-_readlink :: proc(path: string) -> (string, Errno) {
+@(private, require_results)
+_readlink :: proc(path: string) -> (string, Error) {
 	runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD(ignore = context.temp_allocator == context.allocator)
 	path_cstr := strings.clone_to_cstring(path, context.temp_allocator)
 
@@ -579,23 +705,25 @@ _readlink :: proc(path: string) -> (string, Errno) {
 		rc := _unix_readlink(path_cstr, &(buf[0]), bufsz)
 		if rc == -1 {
 			delete(buf)
-			return "", Errno(get_last_error())
+			return "", get_last_error()
 		} else if rc == int(bufsz) {
 			bufsz += MAX_PATH
 			delete(buf)
 			buf = make([]byte, bufsz)
 		} else {
-			return strings.string_from_ptr(&buf[0], rc), ERROR_NONE
+			return strings.string_from_ptr(&buf[0], rc), nil
 		}	
 	}
 }
 
 // XXX OpenBSD
-absolute_path_from_handle :: proc(fd: Handle) -> (string, Errno) {
-	return "", Errno(ENOSYS)
+@(require_results)
+absolute_path_from_handle :: proc(fd: Handle) -> (string, Error) {
+	return "", Error(ENOSYS)
 }
 
-absolute_path_from_relative :: proc(rel: string) -> (path: string, err: Errno) {
+@(require_results)
+absolute_path_from_relative :: proc(rel: string) -> (path: string, err: Error) {
 	rel := rel
 	if rel == "" {
 		rel = "."
@@ -606,25 +734,26 @@ absolute_path_from_relative :: proc(rel: string) -> (path: string, err: Errno) {
 
 	path_ptr := _unix_realpath(rel_cstr, nil)
 	if path_ptr == nil {
-		return "", Errno(get_last_error())
+		return "", get_last_error()
 	}
 	defer _unix_free(path_ptr)
 
 	path = strings.clone(string(cstring(path_ptr)))
 
-	return path, ERROR_NONE
+	return path, nil
 }
 
-access :: proc(path: string, mask: int) -> (bool, Errno) {
+access :: proc(path: string, mask: int) -> (bool, Error) {
 	runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD()
 	cstr := strings.clone_to_cstring(path, context.temp_allocator)
 	res := _unix_access(cstr, c.int(mask))
 	if res == -1 {
-		return false, Errno(get_last_error())
+		return false, get_last_error()
 	}
-	return true, ERROR_NONE
+	return true, nil
 }
 
+@(require_results)
 lookup_env :: proc(key: string, allocator := context.allocator) -> (value: string, found: bool) {
 	runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD(ignore = context.temp_allocator == allocator)
 	path_str := strings.clone_to_cstring(key, context.temp_allocator)
@@ -635,11 +764,13 @@ lookup_env :: proc(key: string, allocator := context.allocator) -> (value: strin
 	return strings.clone(string(cstr), allocator), true
 }
 
+@(require_results)
 get_env :: proc(key: string, allocator := context.allocator) -> (value: string) {
 	value, _ = lookup_env(key, allocator)
 	return
 }
 
+@(require_results)
 get_current_directory :: proc() -> string {
 	buf := make([dynamic]u8, MAX_PATH)
 	for {
@@ -647,7 +778,7 @@ get_current_directory :: proc() -> string {
 		if cwd != nil {
 			return string(cwd)
 		}
-		if Errno(get_last_error()) != ERANGE {
+		if get_last_error() != ERANGE {
 			delete(buf)
 			return ""
 		}
@@ -656,14 +787,14 @@ get_current_directory :: proc() -> string {
 	unreachable()
 }
 
-set_current_directory :: proc(path: string) -> (err: Errno) {
+set_current_directory :: proc(path: string) -> (err: Error) {
 	runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD()
 	cstr := strings.clone_to_cstring(path, context.temp_allocator)
 	res := _unix_chdir(cstr)
 	if res == -1 {
-		return Errno(get_last_error())
+		return get_last_error()
 	}
-	return ERROR_NONE
+	return nil
 }
 
 exit :: proc "contextless" (code: int) -> ! {
@@ -671,16 +802,19 @@ exit :: proc "contextless" (code: int) -> ! {
 	_unix_exit(c.int(code))
 }
 
+@(require_results)
 current_thread_id :: proc "contextless" () -> int {
 	return _unix_getthrid()
 }
 
+@(require_results)
 dlopen :: proc(filename: string, flags: int) -> rawptr {
 	runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD()
 	cstr := strings.clone_to_cstring(filename, context.temp_allocator)
 	handle := _unix_dlopen(cstr, c.int(flags))
 	return handle
 }
+@(require_results)
 dlsym :: proc(handle: rawptr, symbol: string) -> rawptr {
 	assert(handle != nil)
 	runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD()
@@ -692,10 +826,12 @@ dlclose :: proc(handle: rawptr) -> bool {
 	assert(handle != nil)
 	return _unix_dlclose(handle) == 0
 }
+@(require_results)
 dlerror :: proc() -> string {
 	return string(_unix_dlerror())
 }
 
+@(require_results)
 get_page_size :: proc() -> int {
 	// NOTE(tetra): The page size never changes, so why do anything complicated
 	// if we don't have to.
@@ -710,11 +846,12 @@ get_page_size :: proc() -> int {
 
 _SC_NPROCESSORS_ONLN :: 503
 
-@(private)
+@(private, require_results)
 _processor_core_count :: proc() -> int {
 	return int(_sysconf(_SC_NPROCESSORS_ONLN))
 }
 
+@(require_results)
 _alloc_command_line_arguments :: proc() -> []string {
 	res := make([]string, len(runtime.args__))
 	for arg, i in runtime.args__ {

+ 25 - 18
core/os/os_wasi.odin

@@ -4,12 +4,10 @@ import "core:sys/wasm/wasi"
 import "base:runtime"
 
 Handle :: distinct i32
-Errno :: distinct i32
+_Platform_Error :: wasi.errno_t
 
 INVALID_HANDLE :: -1
 
-ERROR_NONE :: Errno(wasi.errno_t.SUCCESS)
-
 O_RDONLY   :: 0x00000
 O_WRONLY   :: 0x00001
 O_RDWR     :: 0x00002
@@ -29,6 +27,7 @@ stderr: Handle = 2
 
 args := _alloc_command_line_arguments()
 
+@(require_results)
 _alloc_command_line_arguments :: proc() -> (args: []string) {
 	args = make([]string, len(runtime.args__))
 	for &arg, i in args {
@@ -93,8 +92,9 @@ init_preopens :: proc() {
 	preopens = dyn_preopens[:]
 }
 
+@(require_results)
 wasi_match_preopen :: proc(path: string) -> (wasi.fd_t, string, bool) {
-
+	@(require_results)
 	prefix_matches :: proc(prefix, path: string) -> bool {
 		// Empty is valid for any relative path.
 		if len(prefix) == 0 && len(path) > 0 && path[0] != '/' {
@@ -148,23 +148,24 @@ wasi_match_preopen :: proc(path: string) -> (wasi.fd_t, string, bool) {
 write :: proc(fd: Handle, data: []byte) -> (int, Errno) {
 	iovs := wasi.ciovec_t(data)
 	n, err := wasi.fd_write(wasi.fd_t(fd), {iovs})
-	return int(n), Errno(err)
+	return int(n), Platform_Error(err)
 }
 read :: proc(fd: Handle, data: []byte) -> (int, Errno) {
 	iovs := wasi.iovec_t(data)
 	n, err := wasi.fd_read(wasi.fd_t(fd), {iovs})
-	return int(n), Errno(err)
+	return int(n), Platform_Error(err)
 }
 write_at :: proc(fd: Handle, data: []byte, offset: i64) -> (int, Errno) {
 	iovs := wasi.ciovec_t(data)
 	n, err := wasi.fd_pwrite(wasi.fd_t(fd), {iovs}, wasi.filesize_t(offset))
-	return int(n), Errno(err)
+	return int(n), Platform_Error(err)
 }
 read_at :: proc(fd: Handle, data: []byte, offset: i64) -> (int, Errno) {
 	iovs := wasi.iovec_t(data)
 	n, err := wasi.fd_pread(wasi.fd_t(fd), {iovs}, wasi.filesize_t(offset))
-	return int(n), Errno(err)
+	return int(n), Platform_Error(err)
 }
+@(require_results)
 open :: proc(path: string, mode: int = O_RDONLY, perm: int = 0) -> (Handle, Errno) {
 	oflags: wasi.oflags_t
 	if mode & O_CREATE == O_CREATE {
@@ -201,30 +202,36 @@ open :: proc(path: string, mode: int = O_RDONLY, perm: int = 0) -> (Handle, Errn
 	}
 
 	fd, err := wasi.path_open(dir_fd, {.SYMLINK_FOLLOW}, relative, oflags, rights, {}, fdflags)
-	return Handle(fd), Errno(err)
+	return Handle(fd), Platform_Error(err)
 }
 close :: proc(fd: Handle) -> Errno {
 	err := wasi.fd_close(wasi.fd_t(fd))
-	return Errno(err)
+	return Platform_Error(err)
+}
+
+flush :: proc(fd: Handle) -> Error {
+	// do nothing
+	return nil
 }
+
 seek :: proc(fd: Handle, offset: i64, whence: int) -> (i64, Errno) {
 	n, err := wasi.fd_seek(wasi.fd_t(fd), wasi.filedelta_t(offset), wasi.whence_t(whence))
-	return i64(n), Errno(err)
+	return i64(n), Platform_Error(err)
 }
+@(require_results)
 current_thread_id :: proc "contextless" () -> int {
 	return 0
 }
-@(private)
+@(private, require_results)
 _processor_core_count :: proc() -> int {
 	return 1
 }
 
-file_size :: proc(fd: Handle) -> (i64, Errno) {
-	stat, err := wasi.fd_filestat_get(wasi.fd_t(fd))
-	if err != nil {
-		return 0, Errno(err)
-	}
-	return i64(stat.size), 0
+@(require_results)
+file_size :: proc(fd: Handle) -> (size: i64, err: Errno) {
+	stat := wasi.fd_filestat_get(wasi.fd_t(fd)) or_return
+	size = i64(stat.size)
+	return
 }
 
 

+ 108 - 50
core/os/os_windows.odin

@@ -7,7 +7,6 @@ import "base:intrinsics"
 
 Handle    :: distinct uintptr
 File_Time :: distinct u64
-Errno     :: distinct int
 
 
 INVALID_HANDLE :: ~Handle(0)
@@ -27,70 +26,119 @@ O_SYNC     :: 0x01000
 O_ASYNC    :: 0x02000
 O_CLOEXEC  :: 0x80000
 
-
-ERROR_NONE:                   Errno : 0
-ERROR_FILE_NOT_FOUND:         Errno : 2
-ERROR_PATH_NOT_FOUND:         Errno : 3
-ERROR_ACCESS_DENIED:          Errno : 5
-ERROR_INVALID_HANDLE:         Errno : 6
-ERROR_NOT_ENOUGH_MEMORY:      Errno : 8
-ERROR_NO_MORE_FILES:          Errno : 18
-ERROR_HANDLE_EOF:             Errno : 38
-ERROR_NETNAME_DELETED:        Errno : 64
-ERROR_FILE_EXISTS:            Errno : 80
-ERROR_INVALID_PARAMETER:      Errno : 87
-ERROR_BROKEN_PIPE:            Errno : 109
-ERROR_BUFFER_OVERFLOW:        Errno : 111
-ERROR_INSUFFICIENT_BUFFER:    Errno : 122
-ERROR_MOD_NOT_FOUND:          Errno : 126
-ERROR_PROC_NOT_FOUND:         Errno : 127
-ERROR_DIR_NOT_EMPTY:          Errno : 145
-ERROR_ALREADY_EXISTS:         Errno : 183
-ERROR_ENVVAR_NOT_FOUND:       Errno : 203
-ERROR_MORE_DATA:              Errno : 234
-ERROR_OPERATION_ABORTED:      Errno : 995
-ERROR_IO_PENDING:             Errno : 997
-ERROR_NOT_FOUND:              Errno : 1168
-ERROR_PRIVILEGE_NOT_HELD:     Errno : 1314
-WSAEACCES:                    Errno : 10013
-WSAECONNRESET:                Errno : 10054
-
-// Windows reserves errors >= 1<<29 for application use
-ERROR_FILE_IS_PIPE:           Errno : 1<<29 + 0
-ERROR_FILE_IS_NOT_DIR:        Errno : 1<<29 + 1
-ERROR_NEGATIVE_OFFSET:        Errno : 1<<29 + 2
+_Platform_Error :: win32.System_Error
+
+ERROR_FILE_NOT_FOUND      :: _Platform_Error(2)
+ERROR_PATH_NOT_FOUND      :: _Platform_Error(3)
+ERROR_ACCESS_DENIED       :: _Platform_Error(5)
+ERROR_INVALID_HANDLE      :: _Platform_Error(6)
+ERROR_NOT_ENOUGH_MEMORY   :: _Platform_Error(8)
+ERROR_NO_MORE_FILES       :: _Platform_Error(18)
+ERROR_HANDLE_EOF          :: _Platform_Error(38)
+ERROR_NETNAME_DELETED     :: _Platform_Error(64)
+ERROR_FILE_EXISTS         :: _Platform_Error(80)
+ERROR_INVALID_PARAMETER   :: _Platform_Error(87)
+ERROR_BROKEN_PIPE         :: _Platform_Error(109)
+ERROR_BUFFER_OVERFLOW     :: _Platform_Error(111)
+ERROR_INSUFFICIENT_BUFFER :: _Platform_Error(122)
+ERROR_MOD_NOT_FOUND       :: _Platform_Error(126)
+ERROR_PROC_NOT_FOUND      :: _Platform_Error(127)
+ERROR_DIR_NOT_EMPTY       :: _Platform_Error(145)
+ERROR_ALREADY_EXISTS      :: _Platform_Error(183)
+ERROR_ENVVAR_NOT_FOUND    :: _Platform_Error(203)
+ERROR_MORE_DATA           :: _Platform_Error(234)
+ERROR_OPERATION_ABORTED   :: _Platform_Error(995)
+ERROR_IO_PENDING          :: _Platform_Error(997)
+ERROR_NOT_FOUND           :: _Platform_Error(1168)
+ERROR_PRIVILEGE_NOT_HELD  :: _Platform_Error(1314)
+WSAEACCES                 :: _Platform_Error(10013)
+WSAECONNRESET             :: _Platform_Error(10054)
+
+ERROR_FILE_IS_PIPE        :: General_Error.File_Is_Pipe
+ERROR_FILE_IS_NOT_DIR     :: General_Error.Not_Dir
 
 // "Argv" arguments converted to Odin strings
 args := _alloc_command_line_arguments()
 
+@(require_results, no_instrumentation)
+get_last_error :: proc "contextless" () -> Error {
+	err := win32.GetLastError()
+	if err == 0 {
+		return nil
+	}
+	switch err {
+	case win32.ERROR_ACCESS_DENIED, win32.ERROR_SHARING_VIOLATION:
+		return .Permission_Denied
+
+	case win32.ERROR_FILE_EXISTS, win32.ERROR_ALREADY_EXISTS:
+		return .Exist
+
+	case win32.ERROR_FILE_NOT_FOUND, win32.ERROR_PATH_NOT_FOUND:
+		return .Not_Exist
+
+	case win32.ERROR_NO_DATA:
+		return .Closed
+
+	case win32.ERROR_TIMEOUT, win32.WAIT_TIMEOUT:
+		return .Timeout
+
+	case win32.ERROR_NOT_SUPPORTED:
+		return .Unsupported
+
+	case win32.ERROR_HANDLE_EOF:
+		return .EOF
+
+	case win32.ERROR_INVALID_HANDLE:
+		return .Invalid_File
+
+	case
+		win32.ERROR_BAD_ARGUMENTS,
+		win32.ERROR_INVALID_PARAMETER,
+		win32.ERROR_NOT_ENOUGH_MEMORY,
+		win32.ERROR_NO_MORE_FILES,
+		win32.ERROR_LOCK_VIOLATION,
+		win32.ERROR_BROKEN_PIPE,
+		win32.ERROR_CALL_NOT_IMPLEMENTED,
+		win32.ERROR_INSUFFICIENT_BUFFER,
+		win32.ERROR_INVALID_NAME,
+		win32.ERROR_LOCK_FAILED,
+		win32.ERROR_ENVVAR_NOT_FOUND,
+		win32.ERROR_OPERATION_ABORTED,
+		win32.ERROR_IO_PENDING,
+		win32.ERROR_NO_UNICODE_TRANSLATION:
+		// fallthrough
+	}
+	return Platform_Error(err)
+}
 
 
-
-
-last_write_time :: proc(fd: Handle) -> (File_Time, Errno) {
+@(require_results)
+last_write_time :: proc(fd: Handle) -> (File_Time, Error) {
 	file_info: win32.BY_HANDLE_FILE_INFORMATION
 	if !win32.GetFileInformationByHandle(win32.HANDLE(fd), &file_info) {
-		return 0, Errno(win32.GetLastError())
+		return 0, get_last_error()
 	}
 	lo := File_Time(file_info.ftLastWriteTime.dwLowDateTime)
 	hi := File_Time(file_info.ftLastWriteTime.dwHighDateTime)
-	return lo | hi << 32, ERROR_NONE
+	return lo | hi << 32, nil
 }
 
-last_write_time_by_name :: proc(name: string) -> (File_Time, Errno) {
+@(require_results)
+last_write_time_by_name :: proc(name: string) -> (File_Time, Error) {
 	data: win32.WIN32_FILE_ATTRIBUTE_DATA
 
 	wide_path := win32.utf8_to_wstring(name)
 	if !win32.GetFileAttributesExW(wide_path, win32.GetFileExInfoStandard, &data) {
-		return 0, Errno(win32.GetLastError())
+		return 0, get_last_error()
 	}
 
 	l := File_Time(data.ftLastWriteTime.dwLowDateTime)
 	h := File_Time(data.ftLastWriteTime.dwHighDateTime)
-	return l | h << 32, ERROR_NONE
+	return l | h << 32, nil
 }
 
 
+@(require_results)
 get_page_size :: proc() -> int {
 	// NOTE(tetra): The page size never changes, so why do anything complicated
 	// if we don't have to.
@@ -105,7 +153,7 @@ get_page_size :: proc() -> int {
 	return page_size
 }
 
-@(private)
+@(private, require_results)
 _processor_core_count :: proc() -> int {
 	length : win32.DWORD = 0
 	result := win32.GetLogicalProcessorInformation(nil, &length)
@@ -136,12 +184,14 @@ exit :: proc "contextless" (code: int) -> ! {
 
 
 
+@(require_results)
 current_thread_id :: proc "contextless" () -> int {
 	return int(win32.GetCurrentThreadId())
 }
 
 
 
+@(require_results)
 _alloc_command_line_arguments :: proc() -> []string {
 	arg_count: i32
 	arg_list_ptr := win32.CommandLineToArgvW(win32.GetCommandLineW(), &arg_count)
@@ -175,44 +225,52 @@ _alloc_command_line_arguments :: proc() -> []string {
 */
 WINDOWS_11_BUILD_CUTOFF :: 22_000
 
-get_windows_version_w :: proc() -> win32.OSVERSIONINFOEXW {
+@(require_results)
+get_windows_version_w :: proc "contextless" () -> win32.OSVERSIONINFOEXW {
 	osvi : win32.OSVERSIONINFOEXW
 	osvi.dwOSVersionInfoSize = size_of(win32.OSVERSIONINFOEXW)
 	win32.RtlGetVersion(&osvi)
 	return osvi
 }
 
-is_windows_xp :: proc() -> bool {
+@(require_results)
+is_windows_xp :: proc "contextless" () -> bool {
 	osvi := get_windows_version_w()
 	return (osvi.dwMajorVersion == 5 && osvi.dwMinorVersion == 1)
 }
 
-is_windows_vista :: proc() -> bool {
+@(require_results)
+is_windows_vista :: proc "contextless" () -> bool {
 	osvi := get_windows_version_w()
 	return (osvi.dwMajorVersion == 6 && osvi.dwMinorVersion == 0)
 }
 
-is_windows_7 :: proc() -> bool {
+@(require_results)
+is_windows_7 :: proc "contextless" () -> bool {
 	osvi := get_windows_version_w()
 	return (osvi.dwMajorVersion == 6 && osvi.dwMinorVersion == 1)
 }
 
-is_windows_8 :: proc() -> bool {
+@(require_results)
+is_windows_8 :: proc "contextless" () -> bool {
 	osvi := get_windows_version_w()
 	return (osvi.dwMajorVersion == 6 && osvi.dwMinorVersion == 2)
 }
 
-is_windows_8_1 :: proc() -> bool {
+@(require_results)
+is_windows_8_1 :: proc "contextless" () -> bool {
 	osvi := get_windows_version_w()
 	return (osvi.dwMajorVersion == 6 && osvi.dwMinorVersion == 3)
 }
 
-is_windows_10 :: proc() -> bool {
+@(require_results)
+is_windows_10 :: proc "contextless" () -> bool {
 	osvi := get_windows_version_w()
 	return (osvi.dwMajorVersion == 10 && osvi.dwMinorVersion == 0 && osvi.dwBuildNumber <  WINDOWS_11_BUILD_CUTOFF)
 }
 
-is_windows_11 :: proc() -> bool {
+@(require_results)
+is_windows_11 :: proc "contextless" () -> bool {
 	osvi := get_windows_version_w()
 	return (osvi.dwMajorVersion == 10 && osvi.dwMinorVersion == 0 && osvi.dwBuildNumber >= WINDOWS_11_BUILD_CUTOFF)
 }

+ 18 - 38
core/os/stat_unix.odin

@@ -50,14 +50,14 @@ File_Info :: struct {
 }
 */
 
-@private
+@(private, require_results)
 _make_time_from_unix_file_time :: proc(uft: Unix_File_Time) -> time.Time {
 	return time.Time{
 		_nsec = uft.nanoseconds + uft.seconds * 1_000_000_000,
 	}
 }
 
-@private
+@(private)
 _fill_file_info_from_stat :: proc(fi: ^File_Info, s: OS_Stat) {
 	fi.size = s.size
 	fi.mode = cast(File_Mode)s.mode
@@ -71,7 +71,7 @@ _fill_file_info_from_stat :: proc(fi: ^File_Info, s: OS_Stat) {
 }
 
 
-@private
+@(private, require_results)
 path_base :: proc(path: string) -> string {
 	is_separator :: proc(c: byte) -> bool {
 		return c == '/'
@@ -100,55 +100,35 @@ path_base :: proc(path: string) -> string {
 }
 
 
-lstat :: proc(name: string, allocator := context.allocator) -> (fi: File_Info, err: Errno) {
-
+@(require_results)
+lstat :: proc(name: string, allocator := context.allocator) -> (fi: File_Info, err: Error) {
 	context.allocator = allocator
 
-	s: OS_Stat
-	s, err = _lstat(name)
-	if err != ERROR_NONE {
-		return fi, err
-	}
+	s := _lstat(name) or_return
 	_fill_file_info_from_stat(&fi, s)
-	fi.fullpath, err = absolute_path_from_relative(name)
-	if err != ERROR_NONE {
-		return
-	}
+	fi.fullpath = absolute_path_from_relative(name) or_return
 	fi.name = path_base(fi.fullpath)
-	return fi, ERROR_NONE
+	return
 }
 
-stat :: proc(name: string, allocator := context.allocator) -> (fi: File_Info, err: Errno) {
+@(require_results)
+stat :: proc(name: string, allocator := context.allocator) -> (fi: File_Info, err: Error) {
 	context.allocator = allocator
 
-	s: OS_Stat
-	s, err = _stat(name)
-	if err != ERROR_NONE {
-		return fi, err
-	}
+	s := _stat(name) or_return
 	_fill_file_info_from_stat(&fi, s)
-	fi.fullpath, err = absolute_path_from_relative(name)
-	if err != ERROR_NONE {
-		return
-	}
+	fi.fullpath = absolute_path_from_relative(name) or_return
 	fi.name = path_base(fi.fullpath)
-	return fi, ERROR_NONE
+	return
 }
 
-fstat :: proc(fd: Handle, allocator := context.allocator) -> (fi: File_Info, err: Errno) {
-
+@(require_results)
+fstat :: proc(fd: Handle, allocator := context.allocator) -> (fi: File_Info, err: Error) {
 	context.allocator = allocator
 
-	s: OS_Stat
-	s, err = _fstat(fd)
-	if err != ERROR_NONE {
-		return fi, err
-	}
+	s := _fstat(fd) or_return
 	_fill_file_info_from_stat(&fi, s)
-	fi.fullpath, err = absolute_path_from_handle(fd)
-	if err != ERROR_NONE {
-		return
-	}
+	fi.fullpath = absolute_path_from_handle(fd) or_return
 	fi.name = path_base(fi.fullpath)
-	return fi, ERROR_NONE
+	return
 }

+ 36 - 36
core/os/stat_windows.odin

@@ -4,7 +4,7 @@ import "core:time"
 import "base:runtime"
 import win32 "core:sys/windows"
 
-@(private)
+@(private, require_results)
 full_path_from_name :: proc(name: string, allocator := context.allocator) -> (path: string, err: Errno) {
 	context.allocator = allocator
 	
@@ -19,10 +19,10 @@ full_path_from_name :: proc(name: string, allocator := context.allocator) -> (pa
 	for {
 		n := win32.GetFullPathNameW(raw_data(p), u32(len(buf)), raw_data(buf), nil)
 		if n == 0 {
-			return "", Errno(win32.GetLastError())
+			return "", get_last_error()
 		}
 		if n <= u32(len(buf)) {
-			return win32.utf16_to_utf8(buf[:n], allocator) or_else "", ERROR_NONE
+			return win32.utf16_to_utf8(buf[:n], allocator) or_else "", nil
 		}
 		resize(&buf, len(buf)*2)
 	}
@@ -30,7 +30,7 @@ full_path_from_name :: proc(name: string, allocator := context.allocator) -> (pa
 	return
 }
 
-@(private)
+@(private, require_results)
 _stat :: proc(name: string, create_file_attributes: u32, allocator := context.allocator) -> (fi: File_Info, e: Errno) {
 	if len(name) == 0 {
 		return {}, ERROR_PATH_NOT_FOUND
@@ -54,7 +54,7 @@ _stat :: proc(name: string, create_file_attributes: u32, allocator := context.al
 		fd: win32.WIN32_FIND_DATAW
 		sh := win32.FindFirstFileW(wname, &fd)
 		if sh == win32.INVALID_HANDLE_VALUE {
-			e = Errno(win32.GetLastError())
+			e = get_last_error()
 			return
 		}
 		win32.FindClose(sh)
@@ -64,7 +64,7 @@ _stat :: proc(name: string, create_file_attributes: u32, allocator := context.al
 
 	h := win32.CreateFileW(wname, 0, 0, nil, win32.OPEN_EXISTING, create_file_attributes, nil)
 	if h == win32.INVALID_HANDLE_VALUE {
-		e = Errno(win32.GetLastError())
+		e = get_last_error()
 		return
 	}
 	defer win32.CloseHandle(h)
@@ -72,26 +72,29 @@ _stat :: proc(name: string, create_file_attributes: u32, allocator := context.al
 }
 
 
+@(require_results)
 lstat :: proc(name: string, allocator := context.allocator) -> (File_Info, Errno) {
 	attrs := win32.FILE_FLAG_BACKUP_SEMANTICS
 	attrs |= win32.FILE_FLAG_OPEN_REPARSE_POINT
 	return _stat(name, attrs, allocator)
 }
 
+@(require_results)
 stat :: proc(name: string, allocator := context.allocator) -> (File_Info, Errno) {
 	attrs := win32.FILE_FLAG_BACKUP_SEMANTICS
 	return _stat(name, attrs, allocator)
 }
 
-fstat :: proc(fd: Handle, allocator := context.allocator) -> (fi: File_Info, errno: Errno) {
+@(require_results)
+fstat :: proc(fd: Handle, allocator := context.allocator) -> (fi: File_Info, err: Errno) {
 	if fd == 0 {
-		return {}, ERROR_INVALID_HANDLE
+		err = ERROR_INVALID_HANDLE
 	}
 	context.allocator = allocator
 
-	path, err := cleanpath_from_handle(fd)
-	if err != ERROR_NONE {
-		return {}, err
+	path := cleanpath_from_handle(fd) or_return
+	defer if err != nil {
+		delete(path)
 	}
 
 	h := win32.HANDLE(fd)
@@ -99,16 +102,16 @@ fstat :: proc(fd: Handle, allocator := context.allocator) -> (fi: File_Info, err
 	case win32.FILE_TYPE_PIPE, win32.FILE_TYPE_CHAR:
 		fi.name = basename(path)
 		fi.mode |= file_type_mode(h)
-		errno = ERROR_NONE
+		err = nil
 	case:
-		fi, errno = file_info_from_get_file_information_by_handle(path, h)
+		fi  = file_info_from_get_file_information_by_handle(path, h) or_return
 	}
 	fi.fullpath = path
 	return
 }
 
 
-@(private)
+@(private, require_results)
 cleanpath_strip_prefix :: proc(buf: []u16) -> []u16 {
 	buf := buf
 	N := 0
@@ -133,16 +136,13 @@ cleanpath_strip_prefix :: proc(buf: []u16) -> []u16 {
 	return buf
 }
 
-@(private)
-cleanpath_from_handle :: proc(fd: Handle) -> (string, Errno) {
+@(private, require_results)
+cleanpath_from_handle :: proc(fd: Handle) -> (s: string, err: Errno) {
 	runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD(ignore = context.temp_allocator == context.allocator)
-	buf, err := cleanpath_from_handle_u16(fd, context.temp_allocator)
-	if err != 0 {
-		return "", err
-	}
-	return win32.utf16_to_utf8(buf, context.allocator) or_else "", err
+	buf := cleanpath_from_handle_u16(fd, context.temp_allocator) or_return
+	return win32.utf16_to_utf8(buf, context.allocator)
 }
-@(private)
+@(private, require_results)
 cleanpath_from_handle_u16 :: proc(fd: Handle, allocator: runtime.Allocator) -> ([]u16, Errno) {
 	if fd == 0 {
 		return nil, ERROR_INVALID_HANDLE
@@ -151,20 +151,20 @@ cleanpath_from_handle_u16 :: proc(fd: Handle, allocator: runtime.Allocator) -> (
 
 	n := win32.GetFinalPathNameByHandleW(h, nil, 0, 0)
 	if n == 0 {
-		return nil, Errno(win32.GetLastError())
+		return nil, get_last_error()
 	}
 	buf := make([]u16, max(n, win32.DWORD(260))+1, allocator)
 	buf_len := win32.GetFinalPathNameByHandleW(h, raw_data(buf), n, 0)
-	return buf[:buf_len], ERROR_NONE
+	return buf[:buf_len], nil
 }
-@(private)
+@(private, require_results)
 cleanpath_from_buf :: proc(buf: []u16) -> string {
 	buf := buf
 	buf = cleanpath_strip_prefix(buf)
 	return win32.utf16_to_utf8(buf, context.allocator) or_else ""
 }
 
-@(private)
+@(private, require_results)
 basename :: proc(name: string) -> (base: string) {
 	name := name
 	if len(name) > 3 && name[:3] == `\\?` {
@@ -190,7 +190,7 @@ basename :: proc(name: string) -> (base: string) {
 	return name
 }
 
-@(private)
+@(private, require_results)
 file_type_mode :: proc(h: win32.HANDLE) -> File_Mode {
 	switch win32.GetFileType(h) {
 	case win32.FILE_TYPE_PIPE:
@@ -202,7 +202,7 @@ file_type_mode :: proc(h: win32.HANDLE) -> File_Mode {
 }
 
 
-@(private)
+@(private, require_results)
 file_mode_from_file_attributes :: proc(FileAttributes: win32.DWORD, h: win32.HANDLE, ReparseTag: win32.DWORD) -> (mode: File_Mode) {
 	if FileAttributes & win32.FILE_ATTRIBUTE_READONLY != 0 {
 		mode |= 0o444
@@ -239,7 +239,7 @@ windows_set_file_info_times :: proc(fi: ^File_Info, d: ^$T) {
 	fi.access_time       = time.unix(0, win32.FILETIME_as_unix_nanoseconds(d.ftLastAccessTime))
 }
 
-@(private)
+@(private, require_results)
 file_info_from_win32_file_attribute_data :: proc(d: ^win32.WIN32_FILE_ATTRIBUTE_DATA, name: string) -> (fi: File_Info, e: Errno) {
 	fi.size = i64(d.nFileSizeHigh)<<32 + i64(d.nFileSizeLow)
 
@@ -254,7 +254,7 @@ file_info_from_win32_file_attribute_data :: proc(d: ^win32.WIN32_FILE_ATTRIBUTE_
 	return
 }
 
-@(private)
+@(private, require_results)
 file_info_from_win32_find_data :: proc(d: ^win32.WIN32_FIND_DATAW, name: string) -> (fi: File_Info, e: Errno) {
 	fi.size = i64(d.nFileSizeHigh)<<32 + i64(d.nFileSizeLow)
 
@@ -269,20 +269,20 @@ file_info_from_win32_find_data :: proc(d: ^win32.WIN32_FIND_DATAW, name: string)
 	return
 }
 
-@(private)
+@(private, require_results)
 file_info_from_get_file_information_by_handle :: proc(path: string, h: win32.HANDLE) -> (File_Info, Errno) {
 	d: win32.BY_HANDLE_FILE_INFORMATION
 	if !win32.GetFileInformationByHandle(h, &d) {
-		err := Errno(win32.GetLastError())
+		err := get_last_error()
 		return {}, err
 
 	}
 
 	ti: win32.FILE_ATTRIBUTE_TAG_INFO
 	if !win32.GetFileInformationByHandleEx(h, .FileAttributeTagInfo, &ti, size_of(ti)) {
-		err := win32.GetLastError()
-		if err != u32(ERROR_INVALID_PARAMETER) {
-			return {}, Errno(err)
+		err := get_last_error()
+		if err != ERROR_INVALID_PARAMETER {
+			return {}, err
 		}
 		// Indicate this is a symlink on FAT file systems
 		ti.ReparseTag = 0
@@ -299,5 +299,5 @@ file_info_from_get_file_information_by_handle :: proc(path: string, h: win32.HAN
 
 	windows_set_file_info_times(&fi, &d)
 
-	return fi, ERROR_NONE
+	return fi, nil
 }

+ 16 - 33
core/os/stream.odin

@@ -14,44 +14,36 @@ stream_from_handle :: proc(fd: Handle) -> io.Stream {
 _file_stream_proc :: proc(stream_data: rawptr, mode: io.Stream_Mode, p: []byte, offset: i64, whence: io.Seek_From) -> (n: i64, err: io.Error) {
 	fd := Handle(uintptr(stream_data))
 	n_int: int
-	os_err: Errno
+	os_err: Error
 	switch mode {
 	case .Close:
-		close(fd)
+		os_err = close(fd)
 	case .Flush:
-		when ODIN_OS == .Windows {
-			flush(fd)
-		} else {
-			// TOOD(bill): other operating systems
-		}
+		os_err = flush(fd)
 	case .Read:
 		n_int, os_err = read(fd, p)
 		n = i64(n_int)
-		if n == 0 && os_err == 0 {
+		if n == 0 && os_err == nil {
 			err = .EOF
 		}
 
 	case .Read_At:
-		when !(ODIN_OS == .FreeBSD || ODIN_OS == .OpenBSD || ODIN_OS == .NetBSD || ODIN_OS == .Haiku) {
-			n_int, os_err = read_at(fd, p, offset)
-			n = i64(n_int)
-			if n == 0 && os_err == 0 {
-				err = .EOF
-			}
+		n_int, os_err = read_at(fd, p, offset)
+		n = i64(n_int)
+		if n == 0 && os_err == nil {
+			err = .EOF
 		}
 	case .Write:
 		n_int, os_err = write(fd, p)
 		n = i64(n_int)
-		if n == 0 && os_err == 0 {
+		if n == 0 && os_err == nil {
 			err = .EOF
 		}
 	case .Write_At:
-		when !(ODIN_OS == .FreeBSD || ODIN_OS == .OpenBSD || ODIN_OS == .NetBSD || ODIN_OS == .Haiku) {
-			n_int, os_err = write_at(fd, p, offset)
-			n = i64(n_int)
-			if n == 0 && os_err == 0 {
-				err = .EOF
-			}
+		n_int, os_err = write_at(fd, p, offset)
+		n = i64(n_int)
+		if n == 0 && os_err == nil {
+			err = .EOF
 		}
 	case .Seek:
 		n, os_err = seek(fd, offset, int(whence))
@@ -60,20 +52,11 @@ _file_stream_proc :: proc(stream_data: rawptr, mode: io.Stream_Mode, p: []byte,
 	case .Destroy:
 		err = .Empty
 	case .Query:
-		when ODIN_OS == .FreeBSD || ODIN_OS == .OpenBSD || ODIN_OS == .NetBSD || ODIN_OS == .Haiku {
-			return io.query_utility({.Close, .Flush, .Read, .Write, .Seek, .Size, .Query})
-		} else {
-			return io.query_utility({.Close, .Flush, .Read, .Read_At, .Write, .Write_At, .Seek, .Size, .Query})
-		}
+		return io.query_utility({.Close, .Flush, .Read, .Read_At, .Write, .Write_At, .Seek, .Size, .Query})
 	}
 
-	if err == nil && os_err != 0 {
-		when ODIN_OS == .Windows {
-			if os_err == ERROR_HANDLE_EOF {
-				return n, .EOF
-			}
-		}
-		err = .Unknown
+	if err == nil && os_err != nil {
+		err = error_to_io_error(os_err)
 	}
 	return
 }

+ 2 - 2
core/path/filepath/match.odin

@@ -271,7 +271,7 @@ _glob :: proc(dir, pattern: string, matches: ^[dynamic]string, allocator := cont
 
 
 	d, derr := os.open(dir, os.O_RDONLY)
-	if derr != 0 {
+	if derr != nil {
 		return
 	}
 	defer os.close(d)
@@ -280,7 +280,7 @@ _glob :: proc(dir, pattern: string, matches: ^[dynamic]string, allocator := cont
 		file_info, ferr := os.fstat(d)
 		defer os.file_info_delete(file_info)
 
-		if ferr != 0 {
+		if ferr != nil {
 			return
 		}
 		if !file_info.is_dir {

+ 5 - 5
core/path/filepath/path_windows.odin

@@ -52,7 +52,7 @@ is_abs :: proc(path: string) -> bool {
 
 
 @(private)
-temp_full_path :: proc(name: string) -> (path: string, err: os.Errno) {
+temp_full_path :: proc(name: string) -> (path: string, err: os.Error) {
 	ta := context.temp_allocator
 
 	name := name
@@ -63,17 +63,17 @@ temp_full_path :: proc(name: string) -> (path: string, err: os.Errno) {
 	p := win32.utf8_to_utf16(name, ta)
 	n := win32.GetFullPathNameW(raw_data(p), 0, nil, nil)
 	if n == 0 {
-		return "", os.Errno(win32.GetLastError())
+		return "", os.get_last_error()
 	}
 
 	buf := make([]u16, n, ta)
 	n = win32.GetFullPathNameW(raw_data(p), u32(len(buf)), raw_data(buf), nil)
 	if n == 0 {
 		delete(buf)
-		return "", os.Errno(win32.GetLastError())
+		return "", os.get_last_error()
 	}
 
-	return win32.utf16_to_utf8(buf[:n], ta) or_else "", os.ERROR_NONE
+	return win32.utf16_to_utf8(buf[:n], ta)
 }
 
 
@@ -81,7 +81,7 @@ temp_full_path :: proc(name: string) -> (path: string, err: os.Errno) {
 abs :: proc(path: string, allocator := context.allocator) -> (string, bool) {
 	runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD(ignore = allocator == context.temp_allocator)
 	full_path, err := temp_full_path(path)
-	if err != 0 {
+	if err != nil {
 		return "", false
 	}
 	p := clean(full_path, allocator)

+ 14 - 21
core/path/filepath/walk.odin

@@ -14,7 +14,7 @@ import "core:slice"
 // The sole exception is if 'skip_dir' is returned as true:
 // 	when 'skip_dir' is invoked on a directory. 'walk' skips directory contents
 // 	when 'skip_dir' is invoked on a non-directory. 'walk' skips the remaining files in the containing directory
-Walk_Proc :: #type proc(info: os.File_Info, in_err: os.Errno, user_data: rawptr) -> (err: os.Errno, skip_dir: bool)
+Walk_Proc :: #type proc(info: os.File_Info, in_err: os.Error, user_data: rawptr) -> (err: os.Error, skip_dir: bool)
 
 // walk walks the file tree rooted at 'root', calling 'walk_proc' for each file or directory in the tree, including 'root'
 // All errors that happen visiting files and directories are filtered by walk_proc
@@ -22,44 +22,44 @@ Walk_Proc :: #type proc(info: os.File_Info, in_err: os.Errno, user_data: rawptr)
 // NOTE: Walking large directories can be inefficient due to the lexical sort
 // NOTE: walk does not follow symbolic links
 // NOTE: os.File_Info uses the 'context.temp_allocator' to allocate, and will delete when it is done
-walk :: proc(root: string, walk_proc: Walk_Proc, user_data: rawptr) -> os.Errno {
+walk :: proc(root: string, walk_proc: Walk_Proc, user_data: rawptr) -> os.Error {
 	info, err := os.lstat(root, context.temp_allocator)
 	defer os.file_info_delete(info, context.temp_allocator)
 
 	skip_dir: bool
-	if err != 0 {
+	if err != nil {
 		err, skip_dir = walk_proc(info, err, user_data)
 	} else {
 		err, skip_dir = _walk(info, walk_proc, user_data)
 	}
-	return 0 if skip_dir else err
+	return nil if skip_dir else err
 }
 
 
 @(private)
-_walk :: proc(info: os.File_Info, walk_proc: Walk_Proc, user_data: rawptr) -> (err: os.Errno, skip_dir: bool) {
+_walk :: proc(info: os.File_Info, walk_proc: Walk_Proc, user_data: rawptr) -> (err: os.Error, skip_dir: bool) {
 	if !info.is_dir {
 		if info.fullpath == "" && info.name == "" {
 			// ignore empty things
 			return
 		}
-		return walk_proc(info, 0, user_data)
+		return walk_proc(info, nil, user_data)
 	}
 
 	fis: []os.File_Info
-	err1: os.Errno
+	err1: os.Error
 	fis, err = read_dir(info.fullpath, context.temp_allocator)
 	defer os.file_info_slice_delete(fis, context.temp_allocator)
 
 	err1, skip_dir = walk_proc(info, err, user_data)
-	if err != 0 || err1 != 0 || skip_dir {
+	if err != nil || err1 != nil || skip_dir {
 		err = err1
 		return
 	}
 
 	for fi in fis {
 		err, skip_dir = _walk(fi, walk_proc, user_data)
-		if err != 0 || skip_dir {
+		if err != nil || skip_dir {
 			if !fi.is_dir || !skip_dir {
 				return
 			}
@@ -70,19 +70,12 @@ _walk :: proc(info: os.File_Info, walk_proc: Walk_Proc, user_data: rawptr) -> (e
 }
 
 @(private)
-read_dir :: proc(dir_name: string, allocator := context.temp_allocator) -> ([]os.File_Info, os.Errno) {
-	f, err := os.open(dir_name, os.O_RDONLY)
-	if err != 0 {
-		return nil, err
-	}
-	fis: []os.File_Info
-	fis, err = os.read_dir(f, -1, allocator)
-	os.close(f)
-	if err != 0 {
-		return nil, err
-	}
+read_dir :: proc(dir_name: string, allocator := context.temp_allocator) -> (fis: []os.File_Info, err: os.Error) {
+	f := os.open(dir_name, os.O_RDONLY) or_return
+	defer os.close(f)
+	fis = os.read_dir(f, -1, allocator) or_return
 	slice.sort_by(fis, proc(a, b: os.File_Info) -> bool {
 		return a.name < b.name
 	})
-	return fis, 0
+	return
 }

+ 4 - 2
core/prof/spall/doc.odin

@@ -1,8 +1,10 @@
 /*
+	import "base:runtime"
 	import "core:prof/spall"
+	import "core:sync"
 
 	spall_ctx: spall.Context
-	spall_buffer: spall.Buffer
+	@(thread_local) spall_buffer: spall.Buffer
 
 	foo :: proc() {
 		spall.SCOPED_EVENT(&spall_ctx, &spall_buffer, #procedure)
@@ -13,7 +15,7 @@
 		defer spall.context_destroy(&spall_ctx)
 
 		buffer_backing := make([]u8, spall.BUFFER_DEFAULT_SIZE)
-		spall_buffer = spall.buffer_create(buffer_backing)
+		spall_buffer = spall.buffer_create(buffer_backing, u32(sync.current_thread_id()))
 		defer spall.buffer_destroy(&spall_ctx, &spall_buffer)
 
 		spall.SCOPED_EVENT(&spall_ctx, &spall_buffer, #procedure)

+ 2 - 2
core/prof/spall/spall.odin

@@ -68,7 +68,7 @@ BUFFER_DEFAULT_SIZE :: 0x10_0000
 
 context_create_with_scale :: proc(filename: string, precise_time: bool, timestamp_scale: f64) -> (ctx: Context, ok: bool) #optional_ok {
 	fd, err := os.open(filename, os.O_WRONLY | os.O_APPEND | os.O_CREATE | os.O_TRUNC, 0o600)
-	if err != os.ERROR_NONE {
+	if err != nil {
 		return
 	}
 
@@ -227,7 +227,7 @@ _buffer_end :: proc "contextless" (ctx: ^Context, buffer: ^Buffer) #no_bounds_ch
 }
 
 @(no_instrumentation)
-write :: proc "contextless" (fd: os.Handle, buf: []byte) -> (n: int, err: os.Errno) {
+write :: proc "contextless" (fd: os.Handle, buf: []byte) -> (n: int, err: os.Error) {
 	return _write(fd, buf)
 }
 

+ 3 - 12
core/prof/spall/spall_linux.odin

@@ -10,21 +10,12 @@ import "core:sys/linux"
 MAX_RW :: 0x7fffffff
 
 @(no_instrumentation)
-_write :: proc "contextless" (fd: os.Handle, data: []byte) -> (n: int, err: os.Errno) #no_bounds_check /* bounds check would segfault instrumentation */ {
-	if len(data) == 0 {
-		return 0, os.ERROR_NONE
-	}
-	
+_write :: proc "contextless" (fd: os.Handle, data: []byte) -> (n: int, err: os.Error) #no_bounds_check /* bounds check would segfault instrumentation */ {
 	for n < len(data) {
 		chunk := data[:min(len(data), MAX_RW)]
-		written, errno := linux.write(linux.Fd(fd), chunk)
-		if errno != .NONE {
-			return n, os.Errno(errno)
-		}
-		n += written
+		n += linux.write(linux.Fd(fd), chunk) or_return
 	}
-
-	return n, os.ERROR_NONE
+	return
 }
 
 CLOCK_MONOTONIC_RAW :: 4 // NOTE(tetra): "RAW" means: Not adjusted by NTP.

+ 4 - 9
core/prof/spall/spall_unix.odin

@@ -22,29 +22,24 @@ foreign libc {
 	@(link_name="clock_gettime") _unix_clock_gettime :: proc(clock_id: u64, timespec: ^timespec) -> i32 ---
 }
 
-@(no_instrumentation)
-get_last_error :: proc "contextless" () -> int {
-	return int(__error()^)
-}
-
 MAX_RW :: 0x7fffffff
 
 @(no_instrumentation)
-_write :: proc "contextless" (fd: os.Handle, data: []byte) -> (n: int, err: os.Errno) #no_bounds_check /* bounds check would segfault instrumentation */ {
+_write :: proc "contextless" (fd: os.Handle, data: []byte) -> (n: int, err: os.Error) #no_bounds_check /* bounds check would segfault instrumentation */ {
 	if len(data) == 0 {
-		return 0, os.ERROR_NONE
+		return 0, nil
 	}
 
 	for n < len(data) {
 		chunk := data[:min(len(data), MAX_RW)]
 		written := _unix_write(fd, raw_data(chunk), len(chunk))
 		if written < 0 {
-			return n, os.Errno(get_last_error())
+			return n, os.get_last_error()
 		}
 		n += written
 	}
 
-	return n, os.ERROR_NONE
+	return n, nil
 }
 
 CLOCK_MONOTONIC_RAW :: 4 // NOTE(tetra): "RAW" means: Not adjusted by NTP.

+ 4 - 5
core/prof/spall/spall_windows.odin

@@ -10,9 +10,9 @@ import win32 "core:sys/windows"
 MAX_RW :: 1<<30
 
 @(no_instrumentation)
-_write :: proc "contextless" (fd: os.Handle, data: []byte) -> (int, os.Errno) #no_bounds_check /* bounds check would segfault instrumentation */ {
+_write :: proc "contextless" (fd: os.Handle, data: []byte) -> (int, os.Error) #no_bounds_check /* bounds check would segfault instrumentation */ {
 	if len(data) == 0 {
-		return 0, os.ERROR_NONE
+		return 0, nil
 	}
 
 	single_write_length: win32.DWORD
@@ -25,12 +25,11 @@ _write :: proc "contextless" (fd: os.Handle, data: []byte) -> (int, os.Errno) #n
 
 		e := win32.WriteFile(win32.HANDLE(fd), &data[total_write], to_write, &single_write_length, nil)
 		if single_write_length <= 0 || !e {
-			err := os.Errno(win32.GetLastError())
-			return int(total_write), err
+			return int(total_write), os.get_last_error()
 		}
 		total_write += i64(single_write_length)
 	}
-	return int(total_write), os.ERROR_NONE
+	return int(total_write), nil
 }
 
 @(no_instrumentation)

+ 58 - 0
core/reflect/reflect.odin

@@ -496,6 +496,64 @@ struct_field_offsets :: proc(T: typeid) -> []uintptr {
 	return nil
 }
 
+Struct_Field_Count_Method :: enum {
+	Top_Level,
+	Using,
+	Recursive,
+}
+
+/*
+Counts the number of fields in a struct
+
+This procedure returns the number of fields in a struct, counting in one of three ways:
+- .Top_Level: Only counts the top-level fields
+- .Using:     Same count as .Top_Level, and adds the field count of any `using s: Struct` it encounters (in addition to itself)
+- .Recursive: The count of all top-level fields, plus the count of any child struct's fields, recursively
+
+Inputs:
+- T:      The struct type
+- method: The counting method
+
+Returns:
+- The `count`, enumerated using the `method`, which will be `0` if the type is not a struct
+
+Example:
+	symbols_loaded, ok := dynlib.initialize_symbols(&game_api, "game.dll")
+	symbols_expected   := reflect.struct_field_count(Game_Api) - API_PRIVATE_COUNT
+
+	if symbols_loaded != symbols_expected {
+		fmt.eprintf("Expected %v symbols, got %v", symbols_expected, symbols_loaded)
+		return
+	}
+*/
+@(require_results)
+struct_field_count :: proc(T: typeid, method := Struct_Field_Count_Method.Top_Level) -> (count: int) {
+	ti := runtime.type_info_base(type_info_of(T))
+	if s, ok := ti.variant.(runtime.Type_Info_Struct); ok {
+		switch method {
+		case .Top_Level:
+			return int(s.field_count)
+
+		case .Using:
+			count = int(s.field_count)
+			for type, i in s.types[:s.field_count] {
+				if s.usings[i] {
+					count += struct_field_count(type.id)
+				}
+			}
+
+		case .Recursive:
+			count = int(s.field_count)
+			for type in s.types[:s.field_count] {
+				count += struct_field_count(type.id)
+			}
+
+		case: return 0
+		}
+	}
+	return
+}
+
 @(require_results)
 struct_fields_zipped :: proc(T: typeid) -> (fields: #soa[]Struct_Field) {
 	ti := runtime.type_info_base(type_info_of(T))

+ 14 - 2
core/simd/simd.odin

@@ -74,8 +74,8 @@ shl_masked :: intrinsics.simd_shl_masked
 shr_masked :: intrinsics.simd_shr_masked
 
 // Saturation Arithmetic
-add_sat :: intrinsics.simd_add_sat
-sub_sat :: intrinsics.simd_sub_sat
+saturating_add :: intrinsics.simd_saturating_add
+saturating_sub :: intrinsics.simd_saturating_sub
 
 bit_and     :: intrinsics.simd_bit_and
 bit_or      :: intrinsics.simd_bit_or
@@ -102,6 +102,15 @@ lanes_le :: intrinsics.simd_lanes_le
 lanes_gt :: intrinsics.simd_lanes_gt
 lanes_ge :: intrinsics.simd_lanes_ge
 
+
+// Gather and Scatter intrinsics
+gather  :: intrinsics.simd_gather
+scatter :: intrinsics.simd_scatter
+masked_load  :: intrinsics.simd_masked_load
+masked_store :: intrinsics.simd_masked_store
+masked_expand_load    :: intrinsics.simd_masked_expand_load
+masked_compress_store :: intrinsics.simd_masked_compress_store
+
 // extract :: proc(a: #simd[N]T, idx: uint) -> T
 extract :: intrinsics.simd_extract
 // replace :: proc(a: #simd[N]T, idx: uint, elem: T) -> #simd[N]T
@@ -115,6 +124,9 @@ reduce_and         :: intrinsics.simd_reduce_and
 reduce_or          :: intrinsics.simd_reduce_or
 reduce_xor         :: intrinsics.simd_reduce_xor
 
+reduce_any         :: intrinsics.simd_reduce_any
+reduce_all         :: intrinsics.simd_reduce_all
+
 // swizzle :: proc(a: #simd[N]T, indices: ..int) -> #simd[len(indices)]T
 swizzle :: builtin.swizzle
 

+ 8 - 8
core/simd/x86/sse2.odin

@@ -39,19 +39,19 @@ _mm_add_epi64 :: #force_inline proc "c" (a, b: __m128i)  -> __m128i {
 }
 @(require_results, enable_target_feature="sse2")
 _mm_adds_epi8 :: #force_inline proc "c" (a, b: __m128i)  -> __m128i {
-	return transmute(__m128i)simd.add_sat(transmute(i8x16)a, transmute(i8x16)b)
+	return transmute(__m128i)simd.saturating_add(transmute(i8x16)a, transmute(i8x16)b)
 }
 @(require_results, enable_target_feature="sse2")
 _mm_adds_epi16 :: #force_inline proc "c" (a, b: __m128i)  -> __m128i {
-	return transmute(__m128i)simd.add_sat(transmute(i16x8)a, transmute(i16x8)b)
+	return transmute(__m128i)simd.saturating_add(transmute(i16x8)a, transmute(i16x8)b)
 }
 @(require_results, enable_target_feature="sse2")
 _mm_adds_epu8 :: #force_inline proc "c" (a, b: __m128i)  -> __m128i {
-	return transmute(__m128i)simd.add_sat(transmute(u8x16)a, transmute(u8x16)b)
+	return transmute(__m128i)simd.saturating_add(transmute(u8x16)a, transmute(u8x16)b)
 }
 @(require_results, enable_target_feature="sse2")
 _mm_adds_epu16 :: #force_inline proc "c" (a, b: __m128i)  -> __m128i {
-	return transmute(__m128i)simd.add_sat(transmute(u16x8)a, transmute(u16x8)b)
+	return transmute(__m128i)simd.saturating_add(transmute(u16x8)a, transmute(u16x8)b)
 }
 @(require_results, enable_target_feature="sse2")
 _mm_avg_epu8 :: #force_inline proc "c" (a, b: __m128i)  -> __m128i {
@@ -122,19 +122,19 @@ _mm_sub_epi64 :: #force_inline proc "c" (a, b: __m128i) -> __m128i {
 }
 @(require_results, enable_target_feature="sse2")
 _mm_subs_epi8 :: #force_inline proc "c" (a, b: __m128i) -> __m128i {
-	return transmute(__m128i)simd.sub_sat(transmute(i8x16)a, transmute(i8x16)b)
+	return transmute(__m128i)simd.saturating_sub(transmute(i8x16)a, transmute(i8x16)b)
 }
 @(require_results, enable_target_feature="sse2")
 _mm_subs_epi16 :: #force_inline proc "c" (a, b: __m128i) -> __m128i {
-	return transmute(__m128i)simd.sub_sat(transmute(i16x8)a, transmute(i16x8)b)
+	return transmute(__m128i)simd.saturating_sub(transmute(i16x8)a, transmute(i16x8)b)
 }
 @(require_results, enable_target_feature="sse2")
 _mm_subs_epu8 :: #force_inline proc "c" (a, b: __m128i) -> __m128i {
-	return transmute(__m128i)simd.sub_sat(transmute(u8x16)a, transmute(u8x16)b)
+	return transmute(__m128i)simd.saturating_sub(transmute(u8x16)a, transmute(u8x16)b)
 }
 @(require_results, enable_target_feature="sse2")
 _mm_subs_epu16 :: #force_inline proc "c" (a, b: __m128i) -> __m128i {
-	return transmute(__m128i)simd.sub_sat(transmute(u16x8)a, transmute(u16x8)b)
+	return transmute(__m128i)simd.saturating_sub(transmute(u16x8)a, transmute(u16x8)b)
 }
 
 

+ 441 - 33
core/sync/atomic.odin

@@ -2,44 +2,452 @@ package sync
 
 import "base:intrinsics"
 
+/*
+This procedure may lower CPU consumption or yield to a hyperthreaded twin
+processor. It's exact function is architecture specific, but the intent is to
+say that you're not doing much on a CPU.
+*/
 cpu_relax :: intrinsics.cpu_relax
 
 /*
-Atomic_Memory_Order :: enum {
-	Relaxed = 0, // Unordered
-	Consume = 1, // Monotonic
-	Acquire = 2,
-	Release = 3,
-	Acq_Rel = 4,
-	Seq_Cst = 5,
-}
+Describes memory ordering for an atomic operation.
+
+Modern CPU's contain multiple cores and caches specific to those cores. When a
+core performs a write to memory, the value is written to cache first. The issue
+is that a core doesn't typically see what's inside the caches of other cores.
+In order to make operations consistent CPU's implement mechanisms that
+synchronize memory operations across cores by asking other cores or by
+pushing data about writes to other cores.
+
+Due to how these algorithms are implemented, the stores and loads performed by
+one core may seem to happen in a different order to another core. It also may
+happen that a core reorders stores and loads (independent of how compiler put
+them into the machine code). This can cause issues when trying to synchronize
+multiple memory locations between two cores. Which is why CPU's allow for
+stronger memory ordering guarantees if certain instructions or instruction
+variants are used.
+
+In Odin there are 5 different memory ordering guaranties that can be provided
+to an atomic operation:
+
+- `Relaxed`: The memory access (load or store) is unordered with respect to
+  other memory accesses. This can be used to implement an atomic counter.
+  Multiple threads access a single variable, but it doesn't matter when
+  exactly it gets incremented, because it will become eventually consistent.
+- `Consume`: No loads or stores dependent on a memory location can be
+  reordered before a load with consume memory order. If other threads released
+  the same memory, it becomes visible.
+- `Acquire`: No loads or stores on a memory location can be reordered before a
+  load of that memory location with acquire memory ordering. If other threads
+  release the same memory, it becomes visible.
+- `Release`: No loads or stores on a memory location can be reordered after a
+  store of that memory location with release memory ordering. All threads that
+  acquire the same memory location will see all writes done by the current
+  thread.
+- `Acq_Rel`: Acquire-release memory ordering: combines acquire and release
+  memory orderings in the same operation.
+- `Seq_Cst`: Sequential consistency. The strongest memory ordering. A load will
+  always be an acquire operation, a store will always be a release operation,
+  and in addition to that all threads observe the same order of writes.
+
+Non-explicit atomics will always be sequentially consistent.
+
+	Atomic_Memory_Order :: enum {
+		Relaxed = 0, // Unordered
+		Consume = 1, // Monotonic
+		Acquire = 2,
+		Release = 3,
+		Acq_Rel = 4,
+		Seq_Cst = 5,
+	}
+
+**Note(i386, x64)**: x86 has a very strong memory model by default. It
+guarantees that all writes are ordered, stores and loads aren't reordered. In
+a sense, all operations are at least acquire and release operations. If `lock`
+prefix is used, all operations are sequentially consistent. If you use explicit
+atomics, make sure you have the correct atomic memory order, because bugs likely
+will not show up in x86, but may show up on e.g. arm. More on x86 memory
+ordering can be found
+[[here; https://www.cs.cmu.edu/~410-f10/doc/Intel_Reordering_318147.pdf]]
 */
 Atomic_Memory_Order :: intrinsics.Atomic_Memory_Order
 
+/*
+Establish memory ordering.
+
+This procedure establishes memory ordering, without an associated atomic
+operation.
+*/
+atomic_thread_fence :: intrinsics.atomic_thread_fence
+
+/*
+Establish memory ordering between a current thread and a signal handler.
+
+This procedure establishes memory ordering between a thread and a signal
+handler, that run on the same thread, without an associated atomic operation.
+This procedure is equivalent to `atomic_thread_fence`, except it doesn't
+issue any CPU instructions for memory ordering.
+*/
+atomic_signal_fence :: intrinsics.atomic_signal_fence
+
+/*
+Atomically store a value into memory.
+
+This procedure stores a value to a memory location in such a way that no other
+thread is able to see partial reads. This operation is sequentially-consistent.
+*/
+atomic_store :: intrinsics.atomic_store
+
+/*
+Atomically store a value into memory with explicit memory ordering.
+
+This procedure stores a value to a memory location in such a way that no other
+thread is able to see partial reads. The memory ordering of this operation is
+as specified by the `order` parameter.
+*/
+atomic_store_explicit :: intrinsics.atomic_store_explicit
+
+/*
+Atomically load a value from memory.
+
+This procedure loads a value from a memory location in such a way that the
+received value is not a partial read. The memory ordering of this operation is
+sequentially-consistent.
+*/
+atomic_load :: intrinsics.atomic_load
+
+/*
+Atomically load a value from memory with explicit memory ordering.
+
+This procedure loads a value from a memory location in such a way that the
+received value is not a partial read. The memory ordering of this operation
+is as specified by the `order` parameter.
+*/
+atomic_load_explicit :: intrinsics.atomic_load_explicit
+
+/*
+Atomically add a value to the value stored in memory.
+
+This procedure loads a value from memory, adds the specified value to it, and
+stores it back as an atomic operation. This operation is an atomic equivalent
+of the following:
+
+	dst^ += val
+
+The memory ordering of this operation is sequentially-consistent.
+*/
+atomic_add :: intrinsics.atomic_add
+
+/*
+Atomically add a value to the value stored in memory.
+
+This procedure loads a value from memory, adds the specified value to it, and
+stores it back as an atomic operation. This operation is an atomic equivalent
+of the following:
+
+	dst^ += val
+
+The memory ordering of this operation is as specified by the `order` parameter.
+*/
+atomic_add_explicit :: intrinsics.atomic_add_explicit
+
+/*
+Atomically subtract a value from the value stored in memory.
+
+This procedure loads a value from memory, subtracts the specified value from it,
+and stores the result back as an atomic operation. This operation is an atomic
+equivalent of the following:
+
+	dst^ -= val
+
+The memory ordering of this operation is sequentially-consistent.
+*/
+atomic_sub :: intrinsics.atomic_sub
+
+/*
+Atomically subtract a value from the value stored in memory.
+
+This procedure loads a value from memory, subtracts the specified value from it,
+and stores the result back as an atomic operation. This operation is an atomic
+equivalent of the following:
+
+	dst^ -= val
+
+The memory ordering of this operation is as specified by the `order` parameter.
+*/
+atomic_sub_explicit :: intrinsics.atomic_sub_explicit
+
+/*
+Atomically replace the memory location with the result of AND operation with
+the specified value.
+
+This procedure loads a value from memory, calculates the result of AND operation
+between the loaded value and the specified value, and stores it back into the
+same memory location as an atomic operation. This operation is an atomic
+equivalent of the following:
+
+	dst^ &= val
+
+The memory ordering of this operation is sequentially-consistent.
+*/
+atomic_and :: intrinsics.atomic_and
+
+/*
+Atomically replace the memory location with the result of AND operation with
+the specified value.
+
+This procedure loads a value from memory, calculates the result of AND operation
+between the loaded value and the specified value, and stores it back into the
+same memory location as an atomic operation. This operation is an atomic
+equivalent of the following:
+
+	dst^ &= val
+
+The memory ordering of this operation is as specified by the `order` parameter.
+*/
+atomic_and_explicit :: intrinsics.atomic_and_explicit
+
+/*
+Atomically replace the memory location with the result of NAND operation with
+the specified value.
+
+This procedure loads a value from memory, calculates the result of NAND operation
+between the loaded value and the specified value, and stores it back into the
+same memory location as an atomic operation. This operation is an atomic
+equivalent of the following:
+
+	dst^ = ~(dst^ & val)
+
+The memory ordering of this operation is sequentially-consistent.
+*/
+atomic_nand :: intrinsics.atomic_nand
 
-atomic_thread_fence                     :: intrinsics.atomic_thread_fence
-atomic_signal_fence                     :: intrinsics.atomic_signal_fence
-atomic_store                            :: intrinsics.atomic_store
-atomic_store_explicit                   :: intrinsics.atomic_store_explicit
-atomic_load                             :: intrinsics.atomic_load
-atomic_load_explicit                    :: intrinsics.atomic_load_explicit
-atomic_add                              :: intrinsics.atomic_add
-atomic_add_explicit                     :: intrinsics.atomic_add_explicit
-atomic_sub                              :: intrinsics.atomic_sub
-atomic_sub_explicit                     :: intrinsics.atomic_sub_explicit
-atomic_and                              :: intrinsics.atomic_and
-atomic_and_explicit                     :: intrinsics.atomic_and_explicit
-atomic_nand                             :: intrinsics.atomic_nand
-atomic_nand_explicit                    :: intrinsics.atomic_nand_explicit
-atomic_or                               :: intrinsics.atomic_or
-atomic_or_explicit                      :: intrinsics.atomic_or_explicit
-atomic_xor                              :: intrinsics.atomic_xor
-atomic_xor_explicit                     :: intrinsics.atomic_xor_explicit
-atomic_exchange                         :: intrinsics.atomic_exchange
-atomic_exchange_explicit                :: intrinsics.atomic_exchange_explicit
-
-// Returns value and optional ok boolean
-atomic_compare_exchange_strong          :: intrinsics.atomic_compare_exchange_strong
+/*
+Atomically replace the memory location with the result of NAND operation with
+the specified value.
+
+This procedure loads a value from memory, calculates the result of NAND operation
+between the loaded value and the specified value, and stores it back into the
+same memory location as an atomic operation. This operation is an atomic
+equivalent of the following:
+
+	dst^ = ~(dst^ & val)
+
+The memory ordering of this operation is as specified by the `order` parameter.
+*/
+atomic_nand_explicit :: intrinsics.atomic_nand_explicit
+
+/*
+Atomically replace the memory location with the result of OR operation with
+the specified value.
+
+This procedure loads a value from memory, calculates the result of OR operation
+between the loaded value and the specified value, and stores it back into the
+same memory location as an atomic operation. This operation is an atomic
+equivalent of the following:
+
+	dst^ |= val
+
+The memory ordering of this operation is sequentially-consistent.
+*/
+atomic_or :: intrinsics.atomic_or
+
+/*
+Atomically replace the memory location with the result of OR operation with
+the specified value.
+
+This procedure loads a value from memory, calculates the result of OR operation
+between the loaded value and the specified value, and stores it back into the
+same memory location as an atomic operation. This operation is an atomic
+equivalent of the following:
+
+	dst^ |= val
+
+The memory ordering of this operation is as specified by the `order` parameter.
+*/
+atomic_or_explicit :: intrinsics.atomic_or_explicit
+
+/*
+Atomically replace the memory location with the result of XOR operation with
+the specified value.
+
+This procedure loads a value from memory, calculates the result of XOR operation
+between the loaded value and the specified value, and stores it back into the
+same memory location as an atomic operation. This operation is an atomic
+equivalent of the following:
+
+	dst^ ~= val
+
+The memory ordering of this operation is sequentially-consistent.
+*/
+atomic_xor :: intrinsics.atomic_xor
+
+/*
+Atomically replace the memory location with the result of XOR operation with
+the specified value.
+
+This procedure loads a value from memory, calculates the result of XOR operation
+between the loaded value and the specified value, and stores it back into the
+same memory location as an atomic operation. This operation is an atomic
+equivalent of the following:
+
+	dst^ ~= val
+
+The memory ordering of this operation is as specified by the `order` parameter.
+*/
+atomic_xor_explicit :: intrinsics.atomic_xor_explicit
+
+/*
+Atomically exchange the value in a memory location, with the specified value.
+
+This procedure loads a value from the specified memory location, and stores the
+specified value into that memory location. Then the loaded value is returned,
+all done in a single atomic operation. This operation is an atomic equivalent
+of the following:
+
+	tmp := dst^
+	dst^ = val
+	return tmp
+
+The memory ordering of this operation is sequentially-consistent.
+*/
+atomic_exchange :: intrinsics.atomic_exchange
+
+/*
+Atomically exchange the value in a memory location, with the specified value.
+
+This procedure loads a value from the specified memory location, and stores the
+specified value into that memory location. Then the loaded value is returned,
+all done in a single atomic operation. This operation is an atomic equivalent
+of the following:
+
+	tmp := dst^
+	dst^ = val
+	return tmp
+
+The memory ordering of this operation is as specified by the `order` parameter.
+*/
+atomic_exchange_explicit :: intrinsics.atomic_exchange_explicit
+
+/*
+Atomically compare and exchange the value with a memory location.
+
+This procedure checks if the value pointed to by the `dst` parameter is equal
+to `old`, and if they are, it stores the value `new` into the memory location,
+all done in a single atomic operation. This procedure returns the old value
+stored in a memory location and a boolean value signifying whether `old` was
+equal to `new`.
+
+This procedure is an atomic equivalent of the following operation:
+
+	old_dst := dst^
+	if old_dst == old {
+		dst^ = new
+		return old_dst, true
+	} else {
+		return old_dst, false
+	}
+
+The strong version of compare exchange always returns true, when the returned
+old value stored in location pointed to by `dst` and the `old` parameter are
+equal.
+
+Atomic compare exchange has two memory orderings: One is for the
+read-modify-write operation, if the comparison succeeds, and the other is for
+the load operation, if the comparison fails. The memory ordering for both of
+of these operations is sequentially-consistent.
+*/
+atomic_compare_exchange_strong :: intrinsics.atomic_compare_exchange_strong
+
+/*
+Atomically compare and exchange the value with a memory location.
+
+This procedure checks if the value pointed to by the `dst` parameter is equal
+to `old`, and if they are, it stores the value `new` into the memory location,
+all done in a single atomic operation. This procedure returns the old value
+stored in a memory location and a boolean value signifying whether `old` was
+equal to `new`.
+
+This procedure is an atomic equivalent of the following operation:
+
+	old_dst := dst^
+	if old_dst == old {
+		dst^ = new
+		return old_dst, true
+	} else {
+		return old_dst, false
+	}
+
+The strong version of compare exchange always returns true, when the returned
+old value stored in location pointed to by `dst` and the `old` parameter are
+equal.
+
+Atomic compare exchange has two memory orderings: One is for the
+read-modify-write operation, if the comparison succeeds, and the other is for
+the load operation, if the comparison fails. The memory ordering for these
+operations is as specified by `success` and `failure` parameters respectively.
+*/
 atomic_compare_exchange_strong_explicit :: intrinsics.atomic_compare_exchange_strong_explicit
-atomic_compare_exchange_weak            :: intrinsics.atomic_compare_exchange_weak
-atomic_compare_exchange_weak_explicit   :: intrinsics.atomic_compare_exchange_weak_explicit
+
+/*
+Atomically compare and exchange the value with a memory location.
+
+This procedure checks if the value pointed to by the `dst` parameter is equal
+to `old`, and if they are, it stores the value `new` into the memory location,
+all done in a single atomic operation. This procedure returns the old value
+stored in a memory location and a boolean value signifying whether `old` was
+equal to `new`.
+
+This procedure is an atomic equivalent of the following operation:
+
+	old_dst := dst^
+	if old_dst == old {
+		// may return false here
+		dst^ = new
+		return old_dst, true
+	} else {
+		return old_dst, false
+	}
+
+The weak version of compare exchange may return false, even if `dst^ == old`.
+On some platforms running weak compare exchange in a loop is faster than a
+strong version.
+
+Atomic compare exchange has two memory orderings: One is for the
+read-modify-write operation, if the comparison succeeds, and the other is for
+the load operation, if the comparison fails. The memory ordering for both
+of these operations is sequentially-consistent.
+*/
+atomic_compare_exchange_weak :: intrinsics.atomic_compare_exchange_weak
+
+/*
+Atomically compare and exchange the value with a memory location.
+
+This procedure checks if the value pointed to by the `dst` parameter is equal
+to `old`, and if they are, it stores the value `new` into the memory location,
+all done in a single atomic operation. This procedure returns the old value
+stored in a memory location and a boolean value signifying whether `old` was
+equal to `new`.
+
+This procedure is an atomic equivalent of the following operation:
+
+	old_dst := dst^
+	if old_dst == old {
+		// may return false here
+		dst^ = new
+		return old_dst, true
+	} else {
+		return old_dst, false
+	}
+
+The weak version of compare exchange may return false, even if `dst^ == old`.
+On some platforms running weak compare exchange in a loop is faster than a
+strong version.
+
+Atomic compare exchange has two memory orderings: One is for the
+read-modify-write operation, if the comparison succeeds, and the other is for
+the load operation, if the comparison fails. The memory ordering for these
+operations is as specified by the `success` and `failure` parameters
+respectively.
+*/
+atomic_compare_exchange_weak_explicit :: intrinsics.atomic_compare_exchange_weak_explicit

+ 21 - 0
core/sync/doc.odin

@@ -0,0 +1,21 @@
+/*
+Synchronization primitives
+
+This package implements various synchronization primitives that can be used to
+synchronize threads' access to shared memory.
+
+To limit or control the threads' access to shared memory typically the
+following approaches are used:
+
+* Locks
+* Lock-free
+
+When using locks, sections of the code that access shared memory (also known as
+**critical sections**) are guarded by locks, allowing limited access to threads
+and blocking the execution of any other threads.
+
+In lock-free programming the data itself is organized in such a way that threads
+don't intervene much. It can be done via segmenting the data between threads,
+and/or by using atomic operations.
+*/
+package sync

+ 391 - 92
core/sync/extended.odin

@@ -4,15 +4,41 @@ import "core:time"
 import vg "core:sys/valgrind"
 _ :: vg
 
-// A Wait_Group waits for a collection of threads to finish
-//
-// A Wait_Group must not be copied after first use
+/*
+Wait group.
+
+Wait group is a synchronization primitive used by the waiting thread to wait,
+until a all working threads finish work.
+
+The waiting thread first sets the number of working threads it will expect to
+wait for using `wait_group_add` call, and start waiting using `wait_group_wait`
+call. When worker threads complete their work, each of them will call
+`wait_group_done`, and after all working threads have called this procedure,
+the waiting thread will resume execution.
+
+For the purpose of keeping track whether all working threads have finished their
+work, the wait group keeps an internal atomic counter. Initially, the waiting
+thread might set it to a certain non-zero amount. When each working thread
+completes the work, the internal counter is atomically decremented until it
+reaches zero. When it reaches zero, the waiting thread is unblocked. The counter
+is not allowed to become negative.
+
+**Note**: Just like any synchronization primitives, a wait group cannot be
+copied after first use. See documentation for `Mutex` or `Cond`.
+*/
 Wait_Group :: struct #no_copy {
 	counter: int,
 	mutex:   Mutex,
 	cond:    Cond,
 }
 
+/*
+Increment an internal counter of a wait group.
+
+This procedure atomicaly increments a number to the specified wait group's
+internal counter by a specified amount. This operation can be done on any
+thread.
+*/
 wait_group_add :: proc "contextless" (wg: ^Wait_Group, delta: int) {
 	if delta == 0 {
 		return
@@ -32,10 +58,23 @@ wait_group_add :: proc "contextless" (wg: ^Wait_Group, delta: int) {
 	}
 }
 
+/*
+Signal work done by a thread in a wait group.
+
+This procedure decrements the internal counter of the specified wait group and
+wakes up the waiting thread. Once the internal counter reaches zero, the waiting
+thread resumes execution.
+*/
 wait_group_done :: proc "contextless" (wg: ^Wait_Group) {
 	wait_group_add(wg, -1)
 }
 
+/*
+Wait for all worker threads in the wait group.
+
+This procedure blocks the execution of the current thread, until the specified
+wait group's internal counter reaches zero.
+*/
 wait_group_wait :: proc "contextless" (wg: ^Wait_Group) {
 	guard(&wg.mutex)
 
@@ -47,6 +86,14 @@ wait_group_wait :: proc "contextless" (wg: ^Wait_Group) {
 	}
 }
 
+/*
+Wait for all worker threads in the wait group, or until timeout is reached.
+
+This procedure blocks the execution of the current thread, until the specified
+wait group's internal counter reaches zero, or until the timeout is reached.
+
+This procedure returns `false`, if the timeout was reached, `true` otherwise.
+*/
 wait_group_wait_with_timeout :: proc "contextless" (wg: ^Wait_Group, duration: time.Duration) -> bool {
 	if duration <= 0 {
 		return false
@@ -64,41 +111,43 @@ wait_group_wait_with_timeout :: proc "contextless" (wg: ^Wait_Group, duration: t
 	return true
 }
 
-
-
 /*
-A barrier enabling multiple threads to synchronize the beginning of some computation
-
-Example:
-	package example
-
-	import "core:fmt"
-	import "core:sync"
-	import "core:thread"
-
-	barrier := &sync.Barrier{}
-
-	main :: proc "contextless" () {
-		fmt.println("Start")
-
-		THREAD_COUNT :: 4
-		threads: [THREAD_COUNT]^thread.Thread
-
-		sync.barrier_init(barrier, THREAD_COUNT)
-
-		for _, i in threads {
-			threads[i] = thread.create_and_start(proc(t: ^thread.Thread) {
-				// Same messages will be printed together but without any interleaving
-				fmt.println("Getting ready!")
-				sync.barrier_wait(barrier)
-				fmt.println("Off their marks they go!")
-			})
-		}
-
-		for t in threads {
-			thread.destroy(t) // join and free thread
-		}
-		fmt.println("Finished")
+Barrier.
+
+A barrier is a synchronization primitive enabling multiple threads to
+synchronize the beginning of some computation.
+
+When `barrier_wait` procedure is called by any thread, that thread will block
+the execution, until all threads associated with the barrier reach the same
+point of execution and also call `barrier_wait`.
+
+when barrier is initialized, a `thread_count` parameter is passed, signifying
+the amount of participant threads of the barrier. The barrier also keeps track
+of an internal atomic counter. When a thread calls `barrier_wait`, the internal
+counter is incremented. When the internal counter reaches `thread_count`, it is
+reset and all threads waiting on the barrier are unblocked.
+
+This type of synchronization primitive can be used to synchronize "staged"
+workloads, where the workload is split into stages, and until all threads have
+completed the previous threads, no thread is allowed to start work on the next
+stage. In this case, after each stage, a `barrier_wait` shall be inserted in the
+thread procedure.
+
+**Example**:
+
+	THREAD_COUNT :: 4
+	threads: [THREAD_COUNT]^thread.Thread
+	sync.barrier_init(barrier, THREAD_COUNT)
+	for _, i in threads {
+		threads[i] = thread.create_and_start(proc(t: ^thread.Thread) {
+			// Same messages will be printed together but without any interleaving
+			fmt.println("Getting ready!")
+			sync.barrier_wait(barrier)
+			fmt.println("Off their marks they go!")
+		})
+	}
+	for t in threads {
+		thread.destroy(t)
 	}
 */
 Barrier :: struct #no_copy {
@@ -109,6 +158,13 @@ Barrier :: struct #no_copy {
 	thread_count:  int,
 }
 
+/*
+Initialize a barrier.
+
+
+This procedure initializes the barrier for the specified amount of participant
+threads.
+*/
 barrier_init :: proc "contextless" (b: ^Barrier, thread_count: int) {
 	when ODIN_VALGRIND_SUPPORT {
 		vg.helgrind_barrier_resize_pre(b, uint(thread_count))
@@ -118,8 +174,13 @@ barrier_init :: proc "contextless" (b: ^Barrier, thread_count: int) {
 	b.thread_count = thread_count
 }
 
-// Block the current thread until all threads have rendezvoused
-// Barrier can be reused after all threads rendezvoused once, and can be used continuously
+/*
+Block the current thread until all threads have rendezvoused.
+
+This procedure blocks the execution of the current thread, until all threads
+have reached the same point in the execution of the thread proc. Multiple calls
+to `barrier_wait` are allowed within the thread procedure.
+*/
 barrier_wait :: proc "contextless" (b: ^Barrier) -> (is_leader: bool) {
 	when ODIN_VALGRIND_SUPPORT {
 		vg.helgrind_barrier_wait_pre(b)
@@ -140,15 +201,31 @@ barrier_wait :: proc "contextless" (b: ^Barrier) -> (is_leader: bool) {
 	return true
 }
 
+/*
+Auto-reset event.
 
+Represents a thread synchronization primitive that, when signalled, releases one
+single waiting thread and then resets automatically to a state where it can be
+signalled again.
+
+When a thread calls `auto_reset_event_wait`, it's execution will be blocked,
+until the event is signalled by another thread. The call to
+`auto_reset_event_signal` wakes up exactly one thread waiting for the event.
+*/
 Auto_Reset_Event :: struct #no_copy {
 	// status ==  0: Event is reset and no threads are waiting
-	// status ==  1: Event is signaled
+	// status ==  1: Event is signalled
 	// status == -N: Event is reset and N threads are waiting
 	status: i32,
 	sema:   Sema,
 }
 
+/*
+Signal an auto-reset event.
+
+This procedure signals an auto-reset event, waking up exactly one waiting
+thread.
+*/
 auto_reset_event_signal :: proc "contextless" (e: ^Auto_Reset_Event) {
 	old_status := atomic_load_explicit(&e.status, .Relaxed)
 	for {
@@ -163,6 +240,12 @@ auto_reset_event_signal :: proc "contextless" (e: ^Auto_Reset_Event) {
 	}
 }
 
+/*
+Wait on an auto-reset event.
+
+This procedure blocks the execution of the current thread, until the event is
+signalled by another thread.
+*/
 auto_reset_event_wait :: proc "contextless" (e: ^Auto_Reset_Event) {
 	old_status := atomic_sub_explicit(&e.status, 1, .Acquire)
 	if old_status < 1 {
@@ -170,13 +253,35 @@ auto_reset_event_wait :: proc "contextless" (e: ^Auto_Reset_Event) {
 	}
 }
 
+/*
+Ticket lock.
+
+A ticket lock is a mutual exclusion lock that uses "tickets" to control which
+thread is allowed into a critical section.
 
+This synchronization primitive works just like spinlock, except that it implements
+a "fairness" guarantee, making sure that each thread gets a roughly equal amount
+of entries into the critical section.
 
+This type of synchronization primitive is applicable for short critical sections
+in low-contention systems, as it uses a spinlock under the hood.
+*/
 Ticket_Mutex :: struct #no_copy {
 	ticket:  uint,
 	serving: uint,
 }
 
+/*
+Acquire a lock on a ticket mutex.
+
+This procedure acquires a lock on a ticket mutex. If the ticket mutex is held
+by another thread, this procedure also blocks the execution until the lock
+can be acquired.
+
+Once the lock is acquired, any thread calling `ticket_mutex_lock` will be
+blocked from entering any critical sections associated with the same ticket
+mutex, until the lock is released.
+*/
 ticket_mutex_lock :: #force_inline proc "contextless" (m: ^Ticket_Mutex) {
 	ticket := atomic_add_explicit(&m.ticket, 1, .Relaxed)
 	for ticket != atomic_load_explicit(&m.serving, .Acquire) {
@@ -184,44 +289,147 @@ ticket_mutex_lock :: #force_inline proc "contextless" (m: ^Ticket_Mutex) {
 	}
 }
 
+/*
+Release a lock on a ticket mutex.
+
+This procedure releases the lock on a ticket mutex. If any of the threads are
+waiting to acquire the lock, exactly one of those threads is unblocked and
+allowed into the critical section.
+*/
 ticket_mutex_unlock :: #force_inline proc "contextless" (m: ^Ticket_Mutex) {
 	atomic_add_explicit(&m.serving, 1, .Relaxed)
 }
+
+/*
+Guard the current scope with a lock on a ticket mutex.
+
+This procedure acquires a lock on a ticket mutex. The lock is automatically
+released at the end of callee's scope. If the mutex was already locked, this
+procedure also blocks until the lock can be acquired.
+
+When a lock has been acquired, all threads attempting to acquire a lock will be
+blocked from entering any critical sections associated with the ticket mutex,
+until the lock is released.
+
+This procedure always returns `true`. This makes it easy to define a critical
+section by putting the function inside the `if` statement.
+
+**Example**:
+
+	if ticket_mutex_guard(&m) {
+		...
+	}
+*/
 @(deferred_in=ticket_mutex_unlock)
 ticket_mutex_guard :: proc "contextless" (m: ^Ticket_Mutex) -> bool {
 	ticket_mutex_lock(m)
 	return true
 }
 
+/*
+Benaphore.
+
+A benaphore is a combination of an atomic variable and a semaphore that can
+improve locking efficiency in a no-contention system. Acquiring a benaphore
+lock doesn't call into an internal semaphore, if no other thread in a middle of
+a critical section.
 
+Once a lock on a benaphore is acquired by a thread, no other thread is allowed
+into any critical sections, associted with the same benaphore, until the lock
+is released.
+*/
 Benaphore :: struct #no_copy {
 	counter: i32,
 	sema:    Sema,
 }
 
+/*
+Acquire a lock on a benaphore.
+
+This procedure acquires a lock on the specified benaphore. If the lock on a
+benaphore is already held, this procedure also blocks the execution of the
+current thread, until the lock could be acquired.
+
+Once a lock is acquired, all threads attempting to take a lock will be blocked
+from entering any critical sections associated with the same benaphore, until
+until the lock is released.
+*/
 benaphore_lock :: proc "contextless" (b: ^Benaphore) {
 	if atomic_add_explicit(&b.counter, 1, .Acquire) > 1 {
 		sema_wait(&b.sema)
 	}
 }
 
+/*
+Try to acquire a lock on a benaphore.
+
+This procedure tries to acquire a lock on the specified benaphore. If it was
+already locked, then the returned value is `false`, otherwise the lock is
+acquired and the procedure returns `true`.
+
+If the lock is acquired, all threads that attempt to acquire a lock will be
+blocked from entering any critical sections associated with the same benaphore,
+until the lock is released.
+*/
 benaphore_try_lock :: proc "contextless" (b: ^Benaphore) -> bool {
 	v, _ := atomic_compare_exchange_strong_explicit(&b.counter, 0, 1, .Acquire, .Acquire)
 	return v == 0
 }
 
+/*
+Release a lock on a benaphore.
+
+This procedure releases a lock on the specified benaphore. If any of the threads
+are waiting on the lock, exactly one thread is allowed into a critical section
+associated with the same banaphore.
+*/
 benaphore_unlock :: proc "contextless" (b: ^Benaphore) {
 	if atomic_sub_explicit(&b.counter, 1, .Release) > 0 {
 		sema_post(&b.sema)
 	}
 }
 
+/*
+Guard the current scope with a lock on a benaphore.
+
+This procedure acquires a lock on a benaphore. The lock is automatically
+released at the end of callee's scope. If the benaphore was already locked, this
+procedure also blocks until the lock can be acquired.
+
+When a lock has been acquired, all threads attempting to acquire a lock will be
+blocked from entering any critical sections associated with the same benaphore,
+until the lock is released.
+
+This procedure always returns `true`. This makes it easy to define a critical
+section by putting the function inside the `if` statement.
+
+**Example**:
+
+	if benaphore_guard(&m) {
+		...
+	}
+*/
 @(deferred_in=benaphore_unlock)
 benaphore_guard :: proc "contextless" (m: ^Benaphore) -> bool {
 	benaphore_lock(m)
 	return true
 }
 
+/*
+Recursive benaphore.
+
+Recurisve benaphore is just like a plain benaphore, except it allows reentrancy
+into the critical section.
+
+When a lock is acquired on a benaphore, all other threads attempting to
+acquire a lock on the same benaphore will be blocked from any critical sections,
+associated with the same benaphore.
+
+When a lock is acquired on a benaphore by a thread, that thread is allowed
+to acquire another lock on the same benaphore. When a thread has acquired the
+lock on a benaphore, the benaphore will stay locked until the thread releases
+the lock as many times as it has been locked by the thread.
+*/
 Recursive_Benaphore :: struct #no_copy {
 	counter:   int,
 	owner:     int,
@@ -229,6 +437,16 @@ Recursive_Benaphore :: struct #no_copy {
 	sema:      Sema,
 }
 
+/*
+Acquire a lock on a recursive benaphore.
+
+This procedure acquires a lock on a recursive benaphore. If the benaphore is
+held by another thread, this function blocks until the lock can be acquired.
+
+Once a lock is acquired, all other threads attempting to acquire a lock will
+be blocked from entering any critical sections associated with the same
+recursive benaphore, until the lock is released.
+*/
 recursive_benaphore_lock :: proc "contextless" (b: ^Recursive_Benaphore) {
 	tid := current_thread_id()
 	if atomic_add_explicit(&b.counter, 1, .Acquire) > 1 {
@@ -241,6 +459,17 @@ recursive_benaphore_lock :: proc "contextless" (b: ^Recursive_Benaphore) {
 	b.recursion += 1
 }
 
+/*
+Try to acquire a lock on a recursive benaphore.
+
+This procedure attempts to acquire a lock on recursive benaphore. If the
+benaphore is already held by a different thread, this procedure returns `false`.
+Otherwise the lock is acquired and the procedure returns `true`.
+
+If the lock is acquired, all other threads attempting to acquire a lock will
+be blocked from entering any critical sections assciated with the same recursive
+benaphore, until the lock is released.
+*/
 recursive_benaphore_try_lock :: proc "contextless" (b: ^Recursive_Benaphore) -> bool {
 	tid := current_thread_id()
 	if b.owner == tid {
@@ -256,6 +485,13 @@ recursive_benaphore_try_lock :: proc "contextless" (b: ^Recursive_Benaphore) ->
 	return true
 }
 
+/*
+Release a lock on a recursive benaphore.
+
+This procedure releases a lock on the specified recursive benaphore. It also
+causes the critical sections associated with the same benaphore, to become open
+for other threads for entering.
+*/
 recursive_benaphore_unlock :: proc "contextless" (b: ^Recursive_Benaphore) {
 	tid := current_thread_id()
 	_assert(tid == b.owner, "tid != b.owner")
@@ -272,24 +508,50 @@ recursive_benaphore_unlock :: proc "contextless" (b: ^Recursive_Benaphore) {
 	// outside the lock
 }
 
+/*
+Guard the current scope with a recursive benaphore.
+
+This procedure acquires a lock on the specified recursive benaphores and
+automatically releases it at the end of the callee's scope. If the recursive
+benaphore was already held by a another thread, this procedure also blocks until
+the lock can be acquired.
+
+When the lock is acquired all other threads attempting to take a lock will be
+blocked from entering any critical sections associated with the same benaphore,
+until the lock is released.
+
+This procedure always returns `true`, which makes it easy to define a critical
+section by calling this procedure inside an `if` statement.
+
+**Example**:
+
+	if recursive_benaphore_guard(&m) {
+		...
+	}
+*/
 @(deferred_in=recursive_benaphore_unlock)
 recursive_benaphore_guard :: proc "contextless" (m: ^Recursive_Benaphore) -> bool {
 	recursive_benaphore_lock(m)
 	return true
 }
 
+/*
+Once action.
 
-
-
-// Once is a data value that will perform exactly on action.
-// 
-// A Once must not be copied after first use.
+`Once` a synchronization primitive, that only allows a single entry into a
+critical section from a single thread.
+*/
 Once :: struct #no_copy {
 	m:    Mutex,
 	done: bool,
 }
 
-// once_do calls the procedure fn if and only if once_do is being called for the first for this instance of Once.
+/*
+Call a function once.
+
+The `once_do` procedure group calls a specified function, if it wasn't already
+called from the perspective of a specific `Once` struct.
+*/
 once_do :: proc{
 	once_do_without_data,
 	once_do_without_data_contextless,
@@ -297,7 +559,9 @@ once_do :: proc{
 	once_do_with_data_contextless,
 }
 
-// once_do_without_data calls the procedure fn if and only if once_do_without_data is being called for the first for this instance of Once.
+/*
+Call a function with no data once.
+*/
 once_do_without_data :: proc(o: ^Once, fn: proc()) {
 	@(cold)
 	do_slow :: proc(o: ^Once, fn: proc()) {
@@ -313,7 +577,9 @@ once_do_without_data :: proc(o: ^Once, fn: proc()) {
 	}
 }
 
-// once_do_without_data calls the procedure fn if and only if once_do_without_data is being called for the first for this instance of Once.
+/*
+Call a contextless function with no data once.
+*/
 once_do_without_data_contextless :: proc(o: ^Once, fn: proc "contextless" ()) {
 	@(cold)
 	do_slow :: proc(o: ^Once, fn: proc "contextless" ()) {
@@ -329,7 +595,9 @@ once_do_without_data_contextless :: proc(o: ^Once, fn: proc "contextless" ()) {
 	}
 }
 
-// once_do_with_data calls the procedure fn if and only if once_do_with_data is being called for the first for this instance of Once.
+/*
+Call a function with data once.
+*/
 once_do_with_data :: proc(o: ^Once, fn: proc(data: rawptr), data: rawptr) {
 	@(cold)
 	do_slow :: proc(o: ^Once, fn: proc(data: rawptr), data: rawptr) {
@@ -345,7 +613,9 @@ once_do_with_data :: proc(o: ^Once, fn: proc(data: rawptr), data: rawptr) {
 	}
 }
 
-// once_do_with_data_contextless calls the procedure fn if and only if once_do_with_data_contextless is being called for the first for this instance of Once.
+/*
+Call a contextless function with data once.
+*/
 once_do_with_data_contextless :: proc "contextless" (o: ^Once, fn: proc "contextless" (data: rawptr), data: rawptr) {
 	@(cold)
 	do_slow :: proc "contextless" (o: ^Once, fn: proc "contextless" (data: rawptr), data: rawptr) {
@@ -361,83 +631,112 @@ once_do_with_data_contextless :: proc "contextless" (o: ^Once, fn: proc "context
 	}
 }
 
-
-
-
-
-// A Parker is an associated token which is initially not present:
-//     * The `park` procedure blocks the current thread unless or until the token
-//       is available, at which point the token is consumed.
-//     * The `park_with_timeout` procedures works the same as `park` but only
-//       blocks for the specified duration.
-//     * The `unpark` procedure automatically makes the token available if it
-//       was not already.
+/*
+A Parker is an associated token which is initially not present:
+
+* The `park` procedure blocks the current thread unless or until the token
+  is available, at which point the token is consumed.
+* The `park_with_timeout` procedures works the same as `park` but only
+  blocks for the specified duration.
+* The `unpark` procedure automatically makes the token available if it
+  was not already.
+*/
 Parker :: struct #no_copy {
 	state: Futex,
 }
 
-// Blocks the current thread until the token is made available.
-//
-// Assumes this is only called by the thread that owns the Parker.
+@(private="file") PARKER_EMPTY    :: 0
+@(private="file") PARKER_NOTIFIED :: 1
+@(private="file") PARKER_PARKED   :: max(u32)
+
+/*
+Blocks until the token is available.
+
+This procedure blocks the execution of the current thread, until a token is
+made available.
+
+**Note**: This procedure assumes this is only called by the thread that owns
+the Parker.
+*/
 park :: proc "contextless" (p: ^Parker) {
-	EMPTY    :: 0
-	NOTIFIED :: 1
-	PARKED   :: max(u32)
-	if atomic_sub_explicit(&p.state, 1, .Acquire) == NOTIFIED {
+	if atomic_sub_explicit(&p.state, 1, .Acquire) == PARKER_NOTIFIED {
 		return
 	}
 	for {
-		futex_wait(&p.state, PARKED)
-		if _, ok := atomic_compare_exchange_strong_explicit(&p.state, NOTIFIED, EMPTY, .Acquire, .Acquire); ok {
+		futex_wait(&p.state, PARKER_PARKED)
+		if _, ok := atomic_compare_exchange_strong_explicit(&p.state, PARKER_NOTIFIED, PARKER_EMPTY, .Acquire, .Acquire); ok {
 			return
 		}
 	}
 }
 
-// Blocks the current thread until the token is made available, but only
-// for a limited duration.
-//
-// Assumes this is only called by the thread that owns the Parker
+/*
+Blocks until the token is available with timeout.
+
+This procedure blocks the execution of the current thread until a token is made
+available, or until the timeout has expired, whatever happens first.
+
+**Note**: This procedure assumes this is only called by the thread that owns
+the Parker.
+*/
 park_with_timeout :: proc "contextless" (p: ^Parker, duration: time.Duration) {
-	EMPTY    :: 0
-	NOTIFIED :: 1
-	PARKED   :: max(u32)
-	if atomic_sub_explicit(&p.state, 1, .Acquire) == NOTIFIED {
+	start_tick := time.tick_now()
+	remaining_duration := duration
+	if atomic_sub_explicit(&p.state, 1, .Acquire) == PARKER_NOTIFIED {
 		return
 	}
-	futex_wait_with_timeout(&p.state, PARKED, duration)
-	atomic_exchange_explicit(&p.state, EMPTY, .Acquire)
+	for {
+		if !futex_wait_with_timeout(&p.state, PARKER_PARKED, remaining_duration) {
+			return
+		}
+		old, ok := atomic_compare_exchange_weak_explicit((^u32)(&p.state), PARKER_PARKED, PARKER_EMPTY, .Acquire, .Relaxed)
+		if ok || old == PARKER_PARKED {
+			return
+		}
+		end_tick := time.tick_now()
+		remaining_duration -= time.tick_diff(start_tick, end_tick)
+		start_tick = end_tick
+	}
 }
 
-// Automatically makes thee token available if it was not already.
+/*
+Make the token available.
+*/
 unpark :: proc "contextless" (p: ^Parker)  {
-	EMPTY    :: 0
-	NOTIFIED :: 1
-	PARKED   :: max(Futex)
-	if atomic_exchange_explicit(&p.state, NOTIFIED, .Release) == PARKED {
+	if atomic_exchange_explicit((^u32)(&p.state), PARKER_NOTIFIED, .Release) == PARKER_PARKED {
 		futex_signal(&p.state)
 	}
 }
 
+/*
+One-shot event.
 
+A one-shot event is an associated token which is initially not present:
 
-// A One_Shot_Event is an associated token which is initially not present:
-//     * The `one_shot_event_wait` blocks the current thread until the event
-//       is made available
-//     * The `one_shot_event_signal` procedure automatically makes the token
-//       available if its was not already.
+* The `one_shot_event_wait` blocks the current thread until the event
+  is made available
+* The `one_shot_event_signal` procedure automatically makes the token
+  available if its was not already.
+*/
 One_Shot_Event :: struct #no_copy {
 	state: Futex,
 }
 
-// Blocks the current thread until the event is made available with `one_shot_event_signal`.
+/*
+Block until the event is made available.
+
+This procedure blocks the execution of the current thread, until the event is
+made available.
+*/
 one_shot_event_wait :: proc "contextless" (e: ^One_Shot_Event) {
 	for atomic_load_explicit(&e.state, .Acquire) == 0 {
 		futex_wait(&e.state, 0)
 	}
 }
 
-// Releases any threads that are currently blocked by this event with `one_shot_event_wait`.
+/*
+Make event available.
+*/
 one_shot_event_signal :: proc "contextless" (e: ^One_Shot_Event) {
 	atomic_store_explicit(&e.state, 1, .Release)
 	futex_broadcast(&e.state)

+ 5 - 6
core/sync/futex_haiku.odin

@@ -2,7 +2,6 @@
 package sync
 
 import "core:c"
-import "base:runtime"
 import "core:sys/haiku"
 import "core:sys/unix"
 import "core:time"
@@ -86,10 +85,10 @@ _futex_wait :: proc "contextless" (f: ^Futex, expect: u32) -> (ok: bool) {
 	waiter.prev.next = waiter.next
 	waiter.next.prev = waiter.prev
 
- 	unix.pthread_sigmask(haiku.SIG_SETMASK, &old_mask, nil)
+	_ = unix.pthread_sigmask(haiku.SIG_SETMASK, &old_mask, nil)
 
  	// FIXME: Add error handling!
- 	return
+	return
 }
 
 _futex_wait_with_timeout :: proc "contextless" (f: ^Futex, expect: u32, duration: time.Duration) -> (ok: bool) {
@@ -133,10 +132,10 @@ _futex_wait_with_timeout :: proc "contextless" (f: ^Futex, expect: u32, duration
 	waiter.prev.next = waiter.next
 	waiter.next.prev = waiter.prev
 
- 	unix.pthread_sigmask(haiku.SIG_SETMASK, &old_mask, nil)
+	unix.pthread_sigmask(haiku.SIG_SETMASK, &old_mask, nil)
 
- 	// FIXME: Add error handling!
- 	return 
+	// FIXME: Add error handling!
+	return
 }
 
 _futex_signal :: proc "contextless" (f: ^Futex) {

+ 420 - 56
core/sync/primitives.odin

@@ -3,46 +3,108 @@ package sync
 import "base:runtime"
 import "core:time"
 
+/*
+Obtain the current thread ID.
+*/
 current_thread_id :: proc "contextless" () -> int {
 	return _current_thread_id()
 }
 
-// A Mutex is a [[mutual exclusion lock; https://en.wikipedia.org/wiki/Mutual_exclusion]]
-// It can be used to prevent more than one thread from executing the same piece of code,
-// and thus prevent access to same piece of memory by multiple threads, at the same time.
-//
-// A Mutex's zero value represents an initial, *unlocked* state.
-//
-// If another thread tries to take the lock while another thread holds it, it will pause
-// until the lock is released. Code or memory that is "surrounded" by a mutex lock is said
-// to be "guarded by a mutex".
-//
-// A Mutex must not be copied after first use (e.g., after locking it the first time).
-// This is because, in order to coordinate with other threads, all threads must watch
-// the same memory address to know when the lock has been released. Trying to use a
-// copy of the lock at a different memory address will result in broken and unsafe
-// behavior. For this reason, Mutexes are marked as `#no_copy`.
+/*
+Mutual exclusion lock.
+
+A Mutex is a [[mutual exclusion lock; https://en.wikipedia.org/wiki/Mutual_exclusion]]
+It can be used to prevent more than one thread from entering the critical
+section, and thus prevent access to same piece of memory by multiple threads, at
+the same time.
+
+Mutex's zero-initializzed value represents an initial, *unlocked* state.
+ 
+If another thread tries to acquire the lock, while it's already held (typically
+by another thread), the thread's execution will be blocked, until the lock is
+released. Code or memory that is "surrounded" by a mutex lock and unlock
+operations is said to be "guarded by a mutex".
+
+**Note**: A Mutex must not be copied after first use (e.g., after locking it the
+first time). This is because, in order to coordinate with other threads, all
+threads must watch the same memory address to know when the lock has been
+released. Trying to use a copy of the lock at a different memory address will
+result in broken and unsafe behavior. For this reason, Mutexes are marked as
+`#no_copy`.
+
+**Note**: If the current thread attempts to lock a mutex, while it's already
+holding another lock, that will cause a trivial case of deadlock. Do not use
+`Mutex` in recursive functions. In case multiple locks by the same thread are
+desired, use `Recursive_Mutex`.
+*/
 Mutex :: struct #no_copy {
 	impl: _Mutex,
 }
 
-// mutex_lock locks m
+/*
+Acquire a lock on a mutex.
+
+This procedure acquires a lock with the specified mutex. If the mutex has been
+already locked by any thread, this procedure also blocks until the lock can be
+acquired.
+
+Once the lock is acquired, all other threads that attempt to acquire a lock will
+be blocked from entering any critical sections associated with the same mutex,
+until the the lock is released.
+
+**Note**: If the mutex is already locked by the current thread, a call to this
+procedure will block indefinately. Do not use this in recursive procedures.
+*/
 mutex_lock :: proc "contextless" (m: ^Mutex) {
 	_mutex_lock(m)
 }
 
-// mutex_unlock unlocks m
+/*
+Release a lock on a mutex.
+
+This procedure releases the lock associated with the specified mutex. If the
+mutex was not locked, this operation is a no-op.
+
+When the current thread, that holds a lock to the mutex calls `mutex_unlock`,
+this allows one other thread waiting on the mutex to enter any critical sections
+associated with the mutex. If there are no threads waiting on the mutex, the
+critical sections will remain open.
+*/
 mutex_unlock :: proc "contextless" (m: ^Mutex) {
 	_mutex_unlock(m)
 }
 
-// mutex_try_lock tries to lock m, will return true on success, and false on failure
+/*
+Try to acquire a lock on a mutex.
+
+This procedure tries to acquire a lock on the specified mutex. If it was already
+locked, then the returned value is `false`, otherwise the lock is acquired and
+the procedure returns `true`.
+
+If the lock is acquired, all threads that attempt to acquire a lock will be
+blocked from entering any critical sections associated with the same mutex,
+until the lock is released.
+*/
 mutex_try_lock :: proc "contextless" (m: ^Mutex) -> bool {
 	return _mutex_try_lock(m)
 }
 
 /*
-Example:
+Guard the current scope with a lock on a mutex.
+
+This procedure acquires a mutex lock. The lock is automatically released
+at the end of callee's scope. If the mutex was already locked, this procedure
+also blocks until the lock can be acquired.
+
+When a lock has been acquired, all threads attempting to acquire a lock will be
+blocked from entering any critical sections associated with the mutex, until
+the lock is released.
+
+This procedure always returns `true`. This makes it easy to define a critical
+section by putting the function inside the `if` statement.
+
+**Example**:
+
 	if mutex_guard(&m) {
 		...
 	}
@@ -53,47 +115,145 @@ mutex_guard :: proc "contextless" (m: ^Mutex) -> bool {
 	return true
 }
 
-// A RW_Mutex is a reader/writer mutual exclusion lock
-// The lock can be held by any arbitrary number of readers or a single writer
-// The zero value for a RW_Mutex is an unlocked mutex
-//
-// A RW_Mutex must not be copied after first use
+/*
+Read-write mutual exclusion lock.
+
+An `RW_Mutex` is a reader/writer mutual exclusion lock. The lock can be held by
+any number of readers or a single writer.
+
+This type of synchronization primitive supports two kinds of lock operations:
+
+- Exclusive lock (write lock)
+- Shared lock (read lock)
+
+When an exclusive lock is acquired by any thread, all other threads, attempting
+to acquire either an exclusive or shared lock, will be blocked from entering the
+critical sections associated with the read-write mutex, until the exclusive
+owner of the lock releases the lock.
+
+When a shared lock is acquired by any thread, any other thread attempting to
+acquire a shared lock will also be able to enter all the critical sections
+associated with the read-write mutex. However threads attempting to acquire
+an exclusive lock will be blocked from entering those critical sections, until
+all shared locks are released.
+
+**Note**: A read-write mutex must not be copied after first use (e.g., after
+acquiring a lock). This is because, in order to coordinate with other threads,
+all threads must watch the same memory address to know when the lock has been
+released. Trying to use a copy of the lock at a different memory address will
+result in broken and unsafe behavior. For this reason, mutexes are marked as
+`#no_copy`.
+
+**Note**: A read-write mutex is not recursive. Do not attempt to acquire an
+exclusive lock more than once from the same thread, or an exclusive and shared
+lock on the same thread. Taking a shared lock multiple times is acceptable.
+*/
 RW_Mutex :: struct #no_copy {
 	impl: _RW_Mutex,
 }
 
-// rw_mutex_lock locks rw for writing (with a single writer)
-// If the mutex is already locked for reading or writing, the mutex blocks until the mutex is available.
+/*
+Acquire an exclusive lock.
+
+This procedure acquires an exclusive lock on the specified read-write mutex. If
+the lock is already held by any thread, this procedure also blocks until the
+lock can be acquired.
+
+After a lock has been acquired, any thread attempting to acquire any lock
+will be blocked from entering any critical sections associated with the same
+read-write mutex, until the exclusive lock is released.
+*/
 rw_mutex_lock :: proc "contextless" (rw: ^RW_Mutex) {
 	_rw_mutex_lock(rw)
 }
 
-// rw_mutex_unlock unlocks rw for writing (with a single writer)
+/*
+Release an exclusive lock.
+
+This procedure releases an exclusive lock associated with the specified
+read-write mutex.
+
+When the exclusive lock is released, all critical sections, associated with the
+same read-write mutex, become open to other threads.
+*/
 rw_mutex_unlock :: proc "contextless" (rw: ^RW_Mutex) {
 	_rw_mutex_unlock(rw)
 }
 
-// rw_mutex_try_lock tries to lock rw for writing (with a single writer)
+/*
+Try to acquire an exclusive lock on a read-write mutex.
+
+This procedure tries to acquire an exclusive lock on the specified read-write
+mutex. If the mutex was already locked, the procedure returns `false`. Otherwise
+it acquires the exclusive lock and returns `true`.
+
+If the lock has been acquired, all threads attempting to acquire any lock
+will be blocked from entering any critical sections associated with the same
+read-write mutex, until the exclusive locked is released.
+*/
 rw_mutex_try_lock :: proc "contextless" (rw: ^RW_Mutex) -> bool {
 	return _rw_mutex_try_lock(rw)
 }
 
-// rw_mutex_shared_lock locks rw for reading (with arbitrary number of readers)
+/*
+Acquire a shared lock on a read-write mutex.
+
+This procedure acquires a shared lock on the specified read-write mutex. If the
+mutex already has an exclusive lock held, this procedure also blocks until the
+lock can be acquired.
+
+After the shared lock is obtained, all threads attempting to acquire an
+exclusive lock will be blocked from entering any critical sections associated
+with the same read-write mutex, until all shared locks associated with the
+specified read-write mutex are released.
+*/
 rw_mutex_shared_lock :: proc "contextless" (rw: ^RW_Mutex) {
 	_rw_mutex_shared_lock(rw)
 }
 
-// rw_mutex_shared_unlock unlocks rw for reading (with arbitrary number of readers)
+/*
+Release the shared lock on a read-write mutex.
+
+This procedure releases shared lock on the specified read-write mutex. When all
+shared locks are released, all critical sections associated with the same
+read-write mutex become open to other threads.
+*/
 rw_mutex_shared_unlock :: proc "contextless" (rw: ^RW_Mutex) {
 	_rw_mutex_shared_unlock(rw)
 }
 
-// rw_mutex_try_shared_lock tries to lock rw for reading (with arbitrary number of readers)
+/*
+Try to acquire a shared lock on a read-write mutex.
+
+This procedure attempts to acquire a lock on the specified read-write mutex. If
+the mutex already has an exclusive lock held, this procedure returns `false`.
+Otherwise, it acquires the lock on the mutex and returns `true`.
+
+If the shared lock has been acquired, it causes all threads attempting to
+acquire the exclusive lock to be blocked from entering any critical sections
+associated with the same read-write mutex, until all shared locks are released.
+*/
 rw_mutex_try_shared_lock :: proc "contextless" (rw: ^RW_Mutex) -> bool {
 	return _rw_mutex_try_shared_lock(rw)
 }
+
 /*
-Example:
+Guard the current scope with an exclusive lock on a read-write mutex.
+
+This procedure acquires an exclusive lock on the specified read-write mutex.
+This procedure automatically releases the lock at the end of the callee's scope.
+If the mutex was already locked by readers or a writer, this procedure blocks,
+until a lock can be acquired.
+
+When an exclusive lock is acquired, all other threads attempting to acquire an
+exclusive lock will be blocked from entering any critical sections associated
+with the same read-write mutex, until the exclusive lock is released.
+
+This procedure always returns `true`, which makes it easy to define a critical
+section by running this procedure inside an `if` statement.
+
+**Example**:
+
 	if rw_mutex_guard(&m) {
 		...
 	}
@@ -105,8 +265,23 @@ rw_mutex_guard :: proc "contextless" (m: ^RW_Mutex) -> bool {
 }
 
 /*
-Example:
-	if rw_mutex_shared_guard(&m) {
+Guard the current scope with a shared lock on a read-write mutex.
+
+This procedure acquires a shared lock on the specified read-write mutex. This
+procedure automatically releases the lock at the end of the callee's scope. If
+the mutex already has an associated exclusive lock, this procedure blocks, until
+a lock can be acquired.
+
+When a shared lock is obtained, all other threads attempting to obtain an
+exclusive lock will be blocked from any critical sections, associated with the
+same read-write mutex, until all shared locks are released.
+
+This procedure always returns `true`, which makes it easy to define a critical
+section by running this procedure inside an `if` statement.
+
+**Example**:
+
+	if rw_mutex_guard(&m) {
 		...
 	}
 */
@@ -116,30 +291,91 @@ rw_mutex_shared_guard :: proc "contextless" (m: ^RW_Mutex) -> bool {
 	return true
 }
 
-
-
-// A Recursive_Mutex is a recursive mutual exclusion lock
-// The zero value for a Recursive_Mutex is an unlocked mutex
-//
-// A Recursive_Mutex must not be copied after first use
+/*
+Recursive mutual exclusion lock.
+
+Recurisve mutex is just like a plain mutex, except it allows reentrancy. In
+order for a thread to release the mutex for other threads, the mutex needs to
+be unlocked as many times, as it was locked.
+
+When a lock is acquired on a recursive mutex, all other threads attempting to
+acquire a lock on the same mutex will be blocked from any critical sections,
+associated with the same recrusive mutex.
+
+When a lock is acquired on a recursive mutex by a thread, that thread is allowed
+to acquire another lock on the same mutex. When a thread has acquired the lock
+on a recursive mutex, the recursive mutex will stay locked until the thread
+releases the lock as many times as it has been locked by the thread.
+
+**Note**: A recursive mutex must not be copied after first use (e.g., after
+acquiring a lock). This is because, in order to coordinate with other threads,
+all threads must watch the same memory address to know when the lock has been
+released. Trying to use a copy of the lock at a different memory address will
+result in broken and unsafe behavior. For this reason, mutexes are marked as
+`#no_copy`.
+*/
 Recursive_Mutex :: struct #no_copy {
 	impl: _Recursive_Mutex,
 }
 
+/*
+Acquire a lock on a recursive mutex.
+
+This procedure acquires a lock on the specified recursive mutex. If the lock is
+acquired by a different thread, this procedure also blocks until the lock can be
+acquired.
+
+When the lock is acquired, all other threads attempting to acquire a lock will
+be blocked from entering any critical sections associated with the same mutex,
+until the lock is released.
+*/
 recursive_mutex_lock :: proc "contextless" (m: ^Recursive_Mutex) {
 	_recursive_mutex_lock(m)
 }
 
+/*
+Release a lock on a recursive mutex.
+
+This procedure releases a lock on the specified recursive mutex. It also causes
+the critical sections associated with the same mutex, to become open for other
+threads for entering.
+*/
 recursive_mutex_unlock :: proc "contextless" (m: ^Recursive_Mutex) {
 	_recursive_mutex_unlock(m)
 }
 
+/*
+Try to acquire a lock on a recursive mutex.
+
+This procedure attempts to acquire a lock on the specified recursive mutex. If
+the recursive mutex is locked by other threads, this procedure returns `false`.
+Otherwise it locks the mutex and returns `true`.
+
+If the lock is acquired, all other threads attempting to obtain a lock will be
+blocked from entering any critical sections associated with the same mutex,
+until the lock is released.
+*/
 recursive_mutex_try_lock :: proc "contextless" (m: ^Recursive_Mutex) -> bool {
 	return _recursive_mutex_try_lock(m)
 }
 
 /*
-Example:
+Guard the scope with a recursive mutex lock.
+
+This procedure acquires a lock on the specified recursive mutex and
+automatically releases it at the end of the callee's scope. If the recursive
+mutex was already held by a another thread, this procedure also blocks until the
+lock can be acquired.
+
+When the lock is acquired all other threads attempting to take a lock will be
+blocked from entering any critical sections associated with the same mutex,
+until the lock is released.
+
+This procedure always returns `true`, which makes it easy to define a critical
+section by calling this procedure inside an `if` statement.
+
+**Example**:
+
 	if recursive_mutex_guard(&m) {
 		...
 	}
@@ -150,19 +386,69 @@ recursive_mutex_guard :: proc "contextless" (m: ^Recursive_Mutex) -> bool {
 	return true
 }
 
+/*
+A condition variable.
+
+`Cond` implements a condition variable, a rendezvous point for threads waiting
+for signalling the occurence of an event. Condition variables are used on
+conjuction with mutexes to provide a shared access to one or more shared
+variable.
+
+A typical usage of condition variable is as follows. A thread that intends to
+modify a shared variable shall:
+
+1. Acquire a lock on a mutex.
+2. Modify the shared memory.
+3. Release the lock.
+3. Call `cond_signal` or `cond_broadcast`.
 
-// Cond implements a condition variable, a rendezvous point for threads
-// waiting for signalling the occurence of an event
-//
-// A Cond must not be copied after first use
+A thread that intends to wait on a shared variable shall:
+
+1. Acquire a lock on a mutex.
+2. Call `cond_wait` or `cond_wait_with_timeout` (will release the mutex).
+3. Check the condition and keep waiting in a loop if not satisfied with result.
+
+**Note**: A condition variable must not be copied after first use (e.g., after
+waiting on it the first time). This is because, in order to coordinate with
+other threads, all threads must watch the same memory address to know when the
+lock has been released. Trying to use a copy of the lock at a different memory
+address will result in broken and unsafe behavior. For this reason, condition
+variables are marked as `#no_copy`.
+*/
 Cond :: struct #no_copy {
 	impl: _Cond,
 }
 
+/*
+Wait until the condition variable is signalled and release the associated mutex.
+
+This procedure blocks the current thread until the specified condition variable
+is signalled, or until a spurious wakeup occurs. In addition, if the condition
+has been signalled, this procedure releases the lock on the specified mutex.
+
+The mutex must be held by the calling thread, before calling the procedure.
+
+**Note**: This procedure can return on a spurious wake-up, even if the condition
+variable was not signalled by a thread.
+*/
 cond_wait :: proc "contextless" (c: ^Cond, m: ^Mutex) {
 	_cond_wait(c, m)
 }
 
+/*
+Wait until the condition variable is signalled or timeout is reached and release
+the associated mutex.
+
+This procedure blocks the current thread until the specified condition variable
+is signalled, a timeout is reached, or until a spurious wakeup occurs. In
+addition, if the condition has been signalled, this procedure releases the
+lock on the specified mutex.
+
+If the timeout was reached, this procedure returns `false`. Otherwise it returns
+`true`.
+
+Before this procedure is called the mutex must be held by the calling thread.
+*/
 cond_wait_with_timeout :: proc "contextless" (c: ^Cond, m: ^Mutex, duration: time.Duration) -> bool {
 	if duration <= 0 {
 		return false
@@ -170,51 +456,123 @@ cond_wait_with_timeout :: proc "contextless" (c: ^Cond, m: ^Mutex, duration: tim
 	return _cond_wait_with_timeout(c, m, duration)
 }
 
+/*
+Wake up one thread that waits on a condition variable.
+
+This procedure causes exactly one thread waiting on the condition variable to
+wake up.
+*/
 cond_signal :: proc "contextless" (c: ^Cond) {
 	_cond_signal(c)
 }
 
+/*
+Wake up all threads that wait on a condition variable.
+
+This procedure causes all threads waiting on the condition variable to wake up.
+*/
 cond_broadcast :: proc "contextless" (c: ^Cond) {
 	_cond_broadcast(c)
 }
 
-
-// When waited upon, blocks until the internal count is greater than zero, then subtracts one.
-// Posting to the semaphore increases the count by one, or the provided amount.
-//
-// A Sema must not be copied after first use
+/*
+Semaphore.
+
+When waited upon, semaphore blocks until the internal count is greater than
+zero, then decrements the internal counter by one. Posting to the semaphore
+increases the count by one, or the provided amount.
+
+This type of synchronization primitives can be useful for implementing queues.
+The internal counter of the semaphore can be thought of as the amount of items
+in the queue. After a data has been pushed to the queue, the thread shall call
+`sema_post()` procedure, increasing the counter. When a thread takes an item
+from the queue to do the job, it shall call `sema_wait()`, waiting on the
+semaphore counter to become non-zero and decreasing it, if necessary.
+
+**Note**: A semaphore must not be copied after first use (e.g., after posting
+to it). This is because, in order to coordinate with other threads, all threads
+must watch the same memory address to know when the lock has been released.
+Trying to use a copy of the lock at a different memory address will result in
+broken and unsafe behavior. For this reason, semaphores are marked as `#no_copy`.
+*/
 Sema :: struct #no_copy {
 	impl: _Sema,
 }
 
+/*
+Increment the internal counter on a semaphore by the specified amount.
+
+This procedure increments the internal counter of the semaphore. If any of the
+threads were waiting on the semaphore, up to `count` of threads will continue
+the execution and enter the critical section.
+*/
 sema_post :: proc "contextless" (s: ^Sema, count := 1) {
 	_sema_post(s, count)
 }
 
+/*
+Wait on a semaphore until the internal counter is non-zero.
+
+This procedure blocks the execution of the current thread, until the semaphore
+counter is non-zero, and atomically decrements it by one, once the wait has
+ended.
+*/
 sema_wait :: proc "contextless" (s: ^Sema) {
 	_sema_wait(s)
 }
 
+/*
+Wait on a semaphore until the internal counter is non-zero or a timeout is reached.
+
+This procedure blocks the execution of the current thread, until the semaphore
+counter is non-zero, and if so atomically decrements it by one, once the wait
+has ended. If the specified timeout is reached, the function returns `false`,
+otherwise it returns `true`.
+*/
 sema_wait_with_timeout :: proc "contextless" (s: ^Sema, duration: time.Duration) -> bool {
 	return _sema_wait_with_timeout(s, duration)
 }
 
+/*
+Fast userspace mutual exclusion lock.
 
+Futex is a fast userspace mutual exclusion lock, that uses a pointer to a 32-bit
+value as an identifier of the queue of waiting threads. The value pointed to
+by that pointer can be used to store extra data.
 
-// Futex is a fast userspace mutual exclusion lock, using a 32-bit memory address as a hint
-// 
-// An Futex must not be copied after first use
+**IMPORTANT**: A futex must not be copied after first use (e.g., after waiting
+on it the first time, or signalling it). This is because, in order to coordinate
+with other threads, all threads must watch the same memory address. Trying to
+use a copy of the lock at a different memory address will result in broken and
+unsafe behavior.
+*/
 Futex :: distinct u32
 
+/*
+Sleep if the futex contains the expected value until it's signalled.
+
+If the value of the futex is `expected`, this procedure blocks the execution of
+the current thread, until the futex is woken up, or until a spurious wakeup
+occurs.
+*/
 futex_wait :: proc "contextless" (f: ^Futex, expected: u32) {
 	if u32(atomic_load_explicit(f, .Acquire)) != expected {
 		return
 	}
-	
-	_assert(_futex_wait(f, expected), "futex_wait failure")
+	ok := _futex_wait(f, expected)
+	_assert(ok, "futex_wait failure")
 }
 
-// returns true if the wait happened within the duration, false if it exceeded the time duration
+/*
+Sleep if the futex contains the expected value until it's signalled or the
+timeout is reached.
+
+If the value of the futex is `expected`, this procedure blocks the execution of
+the current thread, until the futex is signalled, a timeout is reached, or
+until a spurious wakeup occurs.
+
+This procedure returns `false` if the timeout was reached, `true` otherwise.
+*/
 futex_wait_with_timeout :: proc "contextless" (f: ^Futex, expected: u32, duration: time.Duration) -> bool {
 	if u32(atomic_load_explicit(f, .Acquire)) != expected {
 		return true
@@ -226,10 +584,16 @@ futex_wait_with_timeout :: proc "contextless" (f: ^Futex, expected: u32, duratio
 	return _futex_wait_with_timeout(f, expected, duration)
 }
 
+/*
+Wake up a single thread waiting on a futex.
+*/
 futex_signal :: proc "contextless" (f: ^Futex) {
 	_futex_signal(f)
 }
 
+/*
+Wake up multiple threads waiting on a futex.
+*/
 futex_broadcast :: proc "contextless" (f: ^Futex) {
 	_futex_broadcast(f)
 }

+ 4 - 0
core/sys/darwin/Foundation/NSOpenPanel.odin

@@ -29,3 +29,7 @@ OpenPanel_setResolvesAliases :: proc "c" (self: ^OpenPanel, setting: BOOL) {
 OpenPanel_setAllowsMultipleSelection :: proc "c" (self: ^OpenPanel, setting: BOOL) {
 	msgSend(nil, self, "setAllowsMultipleSelection:", setting)
 }
+@(objc_type=OpenPanel, objc_name="setAllowedFileTypes")
+OpenPanel_setAllowedFileTypes :: proc "c" (self: ^OpenPanel, types: ^Array) {
+	msgSend(nil, self, "setAllowedFileTypes:", types)
+}

+ 1 - 1
core/sys/haiku/os.odin

@@ -399,7 +399,7 @@ cpu_topology_node_info :: struct {
 		},
 		_package: struct {
 			vendor:          cpu_vendor,
-			cache_line_size: u32
+			cache_line_size: u32,
 		},
 		_core: struct {
 			model:             u32,

+ 1 - 0
core/sys/wasm/wasi/wasi_api.odin

@@ -16,6 +16,7 @@ CLOCK_REALTIME           :: clockid_t(2)
 CLOCK_THREAD_CPUTIME_ID  :: clockid_t(3)
 
 errno_t :: enum u16 {
+	NONE = 0,
 	// No error occurred. System call completed successfully.
 	SUCCESS = 0,
 	// Argument list too long.

+ 37 - 0
core/sys/windows/advapi32.odin

@@ -152,6 +152,43 @@ foreign advapi32 {
 		cbData: DWORD,
 	) -> LSTATUS ---
 
+	RegQueryInfoKeyW :: proc(
+		hKey: HKEY,
+		lpClass: LPWSTR,
+		lpcchClass: LPDWORD,
+		lpReserved: LPDWORD,
+		lpcSubKeys: LPDWORD,
+		lpcbMaxSubKeyLen: LPDWORD,
+		lpcbMaxClassLen: LPDWORD,
+		lpcValues: LPDWORD,
+		lpcbMaxValueNameLen: LPDWORD,
+		lpcbMaxValueLen: LPDWORD,
+		lpcbSecurityDescriptor: LPDWORD,
+		lpftLastWriteTime: ^FILETIME,
+	) -> LSTATUS ---
+
+	RegEnumKeyExW :: proc(
+		hKey: HKEY,
+		dwIndex: DWORD,
+		lpName: LPWSTR,
+		lpcchName: LPDWORD,
+		lpReserved: LPDWORD,
+		lpClass: LPWSTR,
+		lpcchClass: LPDWORD,
+		lpftLastWriteTime: ^FILETIME,
+	  ) -> LSTATUS ---
+
+	RegEnumValueW :: proc(
+		hKey: HKEY,
+		dwIndex: DWORD,
+		lpValueName: LPWSTR,
+		lpcchValueName: LPDWORD,
+		lpReserved: LPDWORD,
+		lpType: LPDWORD,
+		lpData: LPBYTE,
+		lpcbData: LPDWORD,
+	) -> LSTATUS ---
+
 	GetFileSecurityW :: proc(
 		lpFileName: LPCWSTR,
 		RequestedInformation: SECURITY_INFORMATION,

+ 298 - 0
core/sys/windows/codepage.odin

@@ -0,0 +1,298 @@
+// +build windows
+package sys_windows
+
+// https://learn.microsoft.com/en-us/windows/win32/intl/code-page-identifiers
+CODEPAGE :: enum UINT {
+	// Default to ANSI code page
+	ACP                     = CP_ACP,
+	// Default to OEM  code page
+	OEMCP                   = CP_OEMCP,
+	// Default to MAC  code page
+	MACCP                   = CP_MACCP,
+	// Current thread's ANSI code page
+	THREAD_ACP              = CP_THREAD_ACP,
+	// Symbol translations
+	SYMBOL                  = CP_SYMBOL,
+
+	// IBM EBCDIC US-Canada
+	IBM037                  = 037,
+	// OEM United States
+	IBM437                  = 437,
+	// IBM EBCDIC International
+	IBM500                  = 500,
+	// Arabic (ASMO 708)
+	ASMO_708                = 708,
+	// Arabic (Transparent ASMO); Arabic (DOS)
+	DOS_720                 = 720,
+	// OEM Greek (formerly 437G); Greek (DOS)
+	IBM737                  = 737,
+	// OEM Baltic; Baltic (DOS)
+	IBM775                  = 775,
+	// OEM Multilingual Latin 1; Western European (DOS)
+	IBM850                  = 850,
+	// OEM Latin 2; Central European (DOS)
+	IBM852                  = 852,
+	// OEM Cyrillic (primarily Russian)
+	IBM855                  = 855,
+	// OEM Turkish; Turkish (DOS)
+	IBM857                  = 857,
+	// OEM Multilingual Latin 1 + Euro symbol
+	IBM00858                = 858,
+	// OEM Portuguese; Portuguese (DOS)
+	IBM860                  = 860,
+	// OEM Icelandic; Icelandic (DOS)
+	IBM861                  = 861,
+	// OEM Hebrew; Hebrew (DOS)
+	DOS_862                 = 862,
+	// OEM French Canadian; French Canadian (DOS)
+	IBM863                  = 863,
+	// OEM Arabic; Arabic (864)
+	IBM864                  = 864,
+	// OEM Nordic; Nordic (DOS)
+	IBM865                  = 865,
+	// OEM Russian; Cyrillic (DOS)
+	CP866                   = 866,
+	// OEM Modern Greek; Greek, Modern (DOS)
+	IBM869                  = 869,
+	// IBM EBCDIC Multilingual/ROECE (Latin 2); IBM EBCDIC Multilingual Latin 2
+	IBM870                  = 870,
+	// Thai (Windows)
+	WINDOWS_874             = 874,
+	// IBM EBCDIC Greek Modern
+	CP875                   = 875,
+	// ANSI/OEM Japanese; Japanese (Shift-JIS)
+	SHIFT_JIS               = 932,
+	// ANSI/OEM Simplified Chinese (PRC, Singapore); Chinese Simplified (GB2312)
+	GB2312                  = 936,
+	// ANSI/OEM Korean (Unified Hangul Code)
+	KS_C_5601_1987          = 949,
+	// ANSI/OEM Traditional Chinese (Taiwan; Hong Kong SAR, PRC); Chinese Traditional (Big5)
+	BIG5                    = 950,
+	// IBM EBCDIC Turkish (Latin 5)
+	IBM1026                 = 1026,
+	// IBM EBCDIC Latin 1/Open System
+	IBM01047                = 1047,
+	// IBM EBCDIC US-Canada (037 + Euro symbol); IBM EBCDIC (US-Canada-Euro)
+	IBM01140                = 1140,
+	// IBM EBCDIC Germany (20273 + Euro symbol); IBM EBCDIC (Germany-Euro)
+	IBM01141                = 1141,
+	// IBM EBCDIC Denmark-Norway (20277 + Euro symbol); IBM EBCDIC (Denmark-Norway-Euro)
+	IBM01142                = 1142,
+	// IBM EBCDIC Finland-Sweden (20278 + Euro symbol); IBM EBCDIC (Finland-Sweden-Euro)
+	IBM01143                = 1143,
+	// IBM EBCDIC Italy (20280 + Euro symbol); IBM EBCDIC (Italy-Euro)
+	IBM01144                = 1144,
+	// IBM EBCDIC Latin America-Spain (20284 + Euro symbol); IBM EBCDIC (Spain-Euro)
+	IBM01145                = 1145,
+	// IBM EBCDIC United Kingdom (20285 + Euro symbol); IBM EBCDIC (UK-Euro)
+	IBM01146                = 1146,
+	// IBM EBCDIC France (20297 + Euro symbol); IBM EBCDIC (France-Euro)
+	IBM01147                = 1147,
+	// IBM EBCDIC International (500 + Euro symbol); IBM EBCDIC (International-Euro)
+	IBM01148                = 1148,
+	// IBM EBCDIC Icelandic (20871 + Euro symbol); IBM EBCDIC (Icelandic-Euro)
+	IBM01149                = 1149,
+	// Unicode UTF-16, little endian byte order (BMP of ISO 10646); available only to managed applications
+	UTF16                   = 1200,
+	// Unicode UTF-16, big endian byte order; available only to managed applications
+	UNICODEFFFE             = 1201,
+	// ANSI Central European; Central European (Windows)
+	WINDOWS_1250            = 1250,
+	// ANSI Cyrillic; Cyrillic (Windows)
+	WINDOWS_1251            = 1251,
+	// ANSI Latin 1; Western European (Windows)
+	WINDOWS_1252            = 1252,
+	// ANSI Greek; Greek (Windows)
+	WINDOWS_1253            = 1253,
+	// ANSI Turkish; Turkish (Windows)
+	WINDOWS_1254            = 1254,
+	// ANSI Hebrew; Hebrew (Windows)
+	WINDOWS_1255            = 1255,
+	// ANSI Arabic; Arabic (Windows)
+	WINDOWS_1256            = 1256,
+	// ANSI Baltic; Baltic (Windows)
+	WINDOWS_1257            = 1257,
+	// ANSI/OEM Vietnamese; Vietnamese (Windows)
+	WINDOWS_1258            = 1258,
+	// Korean (Johab)
+	JOHAB                   = 1361,
+	// MAC Roman; Western European (Mac)
+	MACINTOSH               = 10000,
+	// Japanese (Mac)
+	X_MAC_JAPANESE          = 10001,
+	// MAC Traditional Chinese (Big5); Chinese Traditional (Mac)
+	X_MAC_CHINESETRAD       = 10002,
+	// Korean (Mac)
+	X_MAC_KOREAN            = 10003,
+	// Arabic (Mac)
+	X_MAC_ARABIC            = 10004,
+	// Hebrew (Mac)
+	X_MAC_HEBREW            = 10005,
+	// Greek (Mac)
+	X_MAC_GREEK             = 10006,
+	// Cyrillic (Mac)
+	X_MAC_CYRILLIC          = 10007,
+	// MAC Simplified Chinese (GB 2312); Chinese Simplified (Mac)
+	X_MAC_CHINESESIMP       = 10008,
+	// Romanian (Mac)
+	X_MAC_ROMANIAN          = 10010,
+	// Ukrainian (Mac)
+	X_MAC_UKRAINIAN         = 10017,
+	// Thai (Mac)
+	X_MAC_THAI              = 10021,
+	// MAC Latin 2; Central European (Mac)
+	X_MAC_CE                = 10029,
+	// Icelandic (Mac)
+	X_MAC_ICELANDIC         = 10079,
+	// Turkish (Mac)
+	X_MAC_TURKISH           = 10081,
+	// Croatian (Mac)
+	X_MAC_CROATIAN          = 10082,
+	// Unicode UTF-32, little endian byte order; available only to managed applications
+	UTF32                   = 12000,
+	// Unicode UTF-32, big endian byte order; available only to managed applications
+	UTF32BE                 = 12001,
+	// CNS Taiwan; Chinese Traditional (CNS)
+	X_CHINESE_CNS           = 20000,
+	// TCA Taiwan
+	X_CP20001               = 20001,
+	// Eten Taiwan; Chinese Traditional (Eten)
+	X_CHINESE_ETEN          = 20002,
+	// IBM5550 Taiwan
+	X_CP20003               = 20003,
+	// TeleText Taiwan
+	X_CP20004               = 20004,
+	// Wang Taiwan
+	X_CP20005               = 20005,
+	// IA5 (IRV International Alphabet No. 5, 7-bit); Western European (IA5)
+	X_IA5                   = 20105,
+	// IA5 German (7-bit)
+	X_IA5_GERMAN            = 20106,
+	// IA5 Swedish (7-bit)
+	X_IA5_SWEDISH           = 20107,
+	// IA5 Norwegian (7-bit)
+	X_IA5_NORWEGIAN         = 20108,
+	// US-ASCII (7-bit)
+	US_ASCII                = 20127,
+	// T.61
+	X_CP20261               = 20261,
+	// ISO 6937 Non-Spacing Accent
+	X_CP20269               = 20269,
+	// IBM EBCDIC Germany
+	IBM273                  = 20273,
+	// IBM EBCDIC Denmark-Norway
+	IBM277                  = 20277,
+	// IBM EBCDIC Finland-Sweden
+	IBM278                  = 20278,
+	// IBM EBCDIC Italy
+	IBM280                  = 20280,
+	// IBM EBCDIC Latin America-Spain
+	IBM284                  = 20284,
+	// IBM EBCDIC United Kingdom
+	IBM285                  = 20285,
+	// IBM EBCDIC Japanese Katakana Extended
+	IBM290                  = 20290,
+	// IBM EBCDIC France
+	IBM297                  = 20297,
+	// IBM EBCDIC Arabic
+	IBM420                  = 20420,
+	// IBM EBCDIC Greek
+	IBM423                  = 20423,
+	// IBM EBCDIC Hebrew
+	IBM424                  = 20424,
+	// IBM EBCDIC Korean Extended
+	X_EBCDIC_KOREANEXTENDED = 20833,
+	// IBM EBCDIC Thai
+	IBM_THAI                = 20838,
+	// Russian (KOI8-R); Cyrillic (KOI8-R)
+	KOI8_R                  = 20866,
+	// IBM EBCDIC Icelandic
+	IBM871                  = 20871,
+	// IBM EBCDIC Cyrillic Russian
+	IBM880                  = 20880,
+	// IBM EBCDIC Turkish
+	IBM905                  = 20905,
+	// IBM EBCDIC Latin 1/Open System (1047 + Euro symbol)
+	IBM00924                = 20924,
+	// Japanese (JIS 0208-1990 and 0212-1990)
+	EUC_JP                  = 20932,
+	// Simplified Chinese (GB2312); Chinese Simplified (GB2312-80)
+	X_CP20936               = 20936,
+	// Korean Wansung
+	X_CP20949               = 20949,
+	// IBM EBCDIC Cyrillic Serbian-Bulgarian
+	CP1025                  = 21025,
+	// Ukrainian (KOI8-U); Cyrillic (KOI8-U)
+	KOI8_U                  = 21866,
+	// ISO 8859-1 Latin 1; Western European (ISO)
+	ISO_8859_1              = 28591,
+	// ISO 8859-2 Central European; Central European (ISO)
+	ISO_8859_2              = 28592,
+	// ISO 8859-3 Latin 3
+	ISO_8859_3              = 28593,
+	// ISO 8859-4 Baltic
+	ISO_8859_4              = 28594,
+	// ISO 8859-5 Cyrillic
+	ISO_8859_5              = 28595,
+	// ISO 8859-6 Arabic
+	ISO_8859_6              = 28596,
+	// ISO 8859-7 Greek
+	ISO_8859_7              = 28597,
+	// ISO 8859-8 Hebrew; Hebrew (ISO-Visual)
+	ISO_8859_8              = 28598,
+	// ISO 8859-9 Turkish
+	ISO_8859_9              = 28599,
+	// ISO 8859-13 Estonian
+	ISO_8859_13             = 28603,
+	// ISO 8859-15 Latin 9
+	ISO_8859_15             = 28605,
+	// Europa 3
+	X_EUROPA                = 29001,
+	// ISO 8859-8 Hebrew; Hebrew (ISO-Logical)
+	ISO_8859_8_I            = 38598,
+	// ISO 2022 Japanese with no halfwidth Katakana; Japanese (JIS)
+	ISO_2022_JP             = 50220,
+	// ISO 2022 Japanese with halfwidth Katakana; Japanese (JIS-Allow 1 byte Kana)
+	CSISO2022JP             = 50221,
+	// ISO 2022 Japanese JIS X 0201-1989; Japanese (JIS-Allow 1 byte Kana - SO/SI)
+	ISO_2022_2_JP           = 50222,
+	// ISO 2022 Korean
+	ISO_2022_KR             = 50225,
+	// ISO 2022 Simplified Chinese; Chinese Simplified (ISO 2022)
+	X_CP50227               = 50227,
+	// EUC Japanese
+	EUC_JP_2                = 51932,
+	// EUC Simplified Chinese; Chinese Simplified (EUC)
+	EUC_CN                  = 51936,
+	// EUC Korean
+	EUC_KR                  = 51949,
+	// HZ-GB2312 Simplified Chinese; Chinese Simplified (HZ)
+	HZ_GB_2312              = 52936,
+	// **Windows XP and later:** GB18030 Simplified Chinese (4 byte); Chinese Simplified (GB18030)
+	GB18030                 = 54936,
+	// ISCII Devanagari
+	X_ISCII_DE              = 57002,
+	// ISCII Bangla
+	X_ISCII_BE              = 57003,
+	// ISCII Tamil
+	X_ISCII_TA              = 57004,
+	// ISCII Telugu
+	X_ISCII_TE              = 57005,
+	// ISCII Assamese
+	X_ISCII_AS              = 57006,
+	// ISCII Odia
+	X_ISCII_OR              = 57007,
+	// ISCII Kannada
+	X_ISCII_KA              = 57008,
+	// ISCII Malayalam
+	X_ISCII_MA              = 57009,
+	// ISCII Gujarati
+	X_ISCII_GU              = 57010,
+	// ISCII Punjabi
+	X_ISCII_PA              = 57011,
+
+	// Unicode (UTF-7)
+	UTF7                    = CP_UTF7, /*65000*/
+	// Unicode (UTF-8)
+	UTF8                    = CP_UTF8, /*65001*/
+}

+ 289 - 76
core/sys/windows/gdi32.odin

@@ -7,108 +7,97 @@ foreign import gdi32 "system:Gdi32.lib"
 
 @(default_calling_convention="system")
 foreign gdi32 {
-	GetStockObject :: proc(i: c_int) -> HGDIOBJ ---
+	GetDeviceCaps :: proc(hdc: HDC, index: INT) -> INT ---
+	GetStockObject :: proc(i: INT) -> HGDIOBJ ---
 	SelectObject :: proc(hdc: HDC, h: HGDIOBJ) -> HGDIOBJ ---
 	DeleteObject :: proc(ho: HGDIOBJ) -> BOOL ---
 	SetBkColor :: proc(hdc: HDC, color: COLORREF) -> COLORREF ---
+	SetBkMode :: proc(hdc: HDC, mode: BKMODE) -> INT ---
 
 	CreateCompatibleDC :: proc(hdc: HDC) -> HDC ---
 	DeleteDC :: proc(hdc: HDC) -> BOOL ---
+	CancelDC :: proc(hdc: HDC) -> BOOL ---
+	SaveDC :: proc(hdc: HDC) -> INT ---
+	RestoreDC :: proc(hdc: HDC, nSavedDC: INT) -> BOOL ---
 
 	CreateDIBPatternBrush :: proc(h: HGLOBAL, iUsage: UINT) -> HBRUSH ---
+	CreateDIBitmap :: proc(hdc: HDC, pbmih: ^BITMAPINFOHEADER, flInit: DWORD, pjBits: VOID, pbmi: ^BITMAPINFO, iUsage: UINT) -> HBITMAP ---
+	CreateDIBSection :: proc(hdc: HDC, pbmi: ^BITMAPINFO, usage: UINT, ppvBits: VOID, hSection: HANDLE, offset: DWORD) -> HBITMAP ---
+	StretchDIBits :: proc(hdc: HDC, xDest, yDest, DestWidth, DestHeight, xSrc, ySrc, SrcWidth, SrcHeight: INT, lpBits: VOID, lpbmi: ^BITMAPINFO, iUsage: UINT, rop: DWORD) -> INT ---
+	StretchBlt :: proc(hdcDest: HDC, xDest, yDest, wDest, hDest: INT, hdcSrc: HDC, xSrc, ySrc, wSrc, hSrc: INT, rop: DWORD) -> BOOL ---
 
-	CreateDIBitmap :: proc(
-		hdc: HDC,
-		pbmih: ^BITMAPINFOHEADER,
-		flInit: DWORD,
-		pjBits: VOID,
-		pbmi: ^BITMAPINFO,
-		iUsage: UINT,
-	) -> HBITMAP ---
-
-	CreateDIBSection :: proc(
-		hdc: HDC,
-		pbmi: ^BITMAPINFO,
-		usage: UINT,
-		ppvBits: VOID,
-		hSection: HANDLE,
-		offset: DWORD,
-	) -> HBITMAP ---
-
-	StretchDIBits :: proc(
-		hdc: HDC,
-		xDest: c_int,
-		yDest: c_int,
-		DestWidth: c_int,
-		DestHeight: c_int,
-		xSrc: c_int,
-		ySrc: c_int,
-		SrcWidth: c_int,
-		SrcHeight: c_int,
-		lpBits: VOID,
-		lpbmi: ^BITMAPINFO,
-		iUsage: UINT,
-		rop: DWORD,
-	) -> c_int ---
-
-	StretchBlt :: proc(
-		hdcDest: HDC,
-		xDest: c_int,
-		yDest: c_int,
-		wDest: c_int,
-		hDest: c_int,
-		hdcSrc: HDC,
-		xSrc: c_int,
-		ySrc: c_int,
-		wSrc: c_int,
-		hSrc: c_int,
-		rop: DWORD,
-	) -> BOOL ---
-
-	SetPixelFormat :: proc(hdc: HDC, format: c_int, ppfd: ^PIXELFORMATDESCRIPTOR) -> BOOL ---
-	ChoosePixelFormat :: proc(hdc: HDC, ppfd: ^PIXELFORMATDESCRIPTOR) -> c_int ---
-	DescribePixelFormat :: proc(hdc: HDC, iPixelFormat: c_int, nBytes: UINT, ppfd: ^PIXELFORMATDESCRIPTOR) -> c_int ---
-	SwapBuffers :: proc(HDC) -> BOOL ---
+	SetPixelFormat :: proc(hdc: HDC, format: INT, ppfd: ^PIXELFORMATDESCRIPTOR) -> BOOL ---
+	ChoosePixelFormat :: proc(hdc: HDC, ppfd: ^PIXELFORMATDESCRIPTOR) -> INT ---
+	DescribePixelFormat :: proc(hdc: HDC, iPixelFormat: INT, nBytes: UINT, ppfd: ^PIXELFORMATDESCRIPTOR) -> INT ---	
+	SwapBuffers :: proc(hdc: HDC) -> BOOL ---
 
 	SetDCBrushColor :: proc(hdc: HDC, color: COLORREF) -> COLORREF ---
 	GetDCBrushColor :: proc(hdc: HDC) -> COLORREF ---
-	PatBlt :: proc(hdc: HDC, x, y, w, h: c_int, rop: DWORD) -> BOOL ---
-	Rectangle :: proc(hdc: HDC, left, top, right, bottom: c_int) -> BOOL ---
-
-	CreateFontW :: proc(
-		cHeight, cWidth, cEscapement, cOrientation, cWeight: c_int,
-		bItalic, bUnderline, bStrikeOut, iCharSet, iOutPrecision: DWORD,
-		iClipPrecision, iQuality, iPitchAndFamily: DWORD,
-		pszFaceName: LPCWSTR,
-	) -> HFONT ---
-	TextOutW :: proc(hdc: HDC, x, y: c_int, lpString: LPCWSTR, c: c_int) -> BOOL ---
-	GetTextExtentPoint32W :: proc(hdc: HDC, lpString: LPCWSTR, c: c_int, psizl: LPSIZE) -> BOOL ---
+	PatBlt :: proc(hdc: HDC, x, y, w, h: INT, rop: DWORD) -> BOOL ---
+	Rectangle :: proc(hdc: HDC, left, top, right, bottom: INT) -> BOOL ---
+
+	CreateFontW :: proc(cHeight, cWidth, cEscapement, cOrientation, cWeight: INT, bItalic, bUnderline, bStrikeOut, iCharSet, iOutPrecision: DWORD, iClipPrecision, iQuality, iPitchAndFamily: DWORD, pszFaceName: LPCWSTR) -> HFONT ---
+	CreateFontIndirectW :: proc(lplf: ^LOGFONTW) -> HFONT ---
+	CreateFontIndirectExW :: proc(unnamedParam1: ^ENUMLOGFONTEXDVW) -> HFONT ---
+	AddFontResourceW :: proc(unnamedParam1: LPCWSTR) -> INT ---
+	AddFontResourceExW :: proc(name: LPCWSTR, fl: DWORD, res: PVOID) -> INT ---
+	AddFontMemResourceEx :: proc(pFileView: PVOID, cjSize: DWORD, pvResrved: PVOID, pNumFonts: ^DWORD) -> HANDLE ---
+	EnumFontsW :: proc(hdc: HDC, lpLogfont: LPCWSTR, lpProc: FONTENUMPROCW, lParam: LPARAM) -> INT ---
+	EnumFontFamiliesW :: proc(hdc: HDC, lpLogfont: LPCWSTR, lpProc: FONTENUMPROCW, lParam: LPARAM) -> INT ---
+	EnumFontFamiliesExW :: proc(hdc: HDC, lpLogfont: LPLOGFONTW, lpProc: FONTENUMPROCW, lParam: LPARAM, dwFlags: DWORD) -> INT ---
+
+	TextOutW :: proc(hdc: HDC, x, y: INT, lpString: LPCWSTR, c: INT) -> BOOL ---
+	GetTextExtentPoint32W :: proc(hdc: HDC, lpString: LPCWSTR, c: INT, psizl: LPSIZE) -> BOOL ---
 	GetTextMetricsW :: proc(hdc: HDC, lptm: LPTEXTMETRICW) -> BOOL ---
 
 	CreateSolidBrush :: proc(color: COLORREF) -> HBRUSH ---
 
-	GetObjectW :: proc(h: HANDLE, c: c_int, pv: LPVOID) -> int ---
-	CreateCompatibleBitmap :: proc(hdc: HDC, cx, cy: c_int) -> HBITMAP ---
-	BitBlt :: proc(hdc: HDC, x, y, cx, cy: c_int, hdcSrc: HDC, x1, y1: c_int, rop: DWORD) -> BOOL ---
-	GetDIBits :: proc(hdc: HDC, hbm: HBITMAP, start, cLines: UINT, lpvBits: LPVOID, lpbmi: ^BITMAPINFO, usage: UINT) -> int ---
+	GetObjectW :: proc(h: HANDLE, c: INT, pv: LPVOID) -> int ---
+	CreateCompatibleBitmap :: proc(hdc: HDC, cx, cy: INT) -> HBITMAP ---
+	BitBlt :: proc(hdc: HDC, x, y, cx, cy: INT, hdcSrc: HDC, x1, y1: INT, rop: DWORD) -> BOOL ---
+	GetDIBits :: proc(hdc: HDC, hbm: HBITMAP, start, cLines: UINT, lpvBits: LPVOID, lpbmi: ^BITMAPINFO, usage: UINT) -> INT ---
+	SetDIBits :: proc(hdc: HDC, hbm: HBITMAP, start: UINT, cLines: UINT, lpBits: VOID, lpbmi: ^BITMAPINFO, ColorUse: UINT) -> INT ---
+	SetDIBColorTable :: proc(hdc: HDC, iStart: UINT, cEntries: UINT, prgbq: ^RGBQUAD) -> UINT ---
+	GetDIBColorTable :: proc(hdc: HDC, iStart: UINT, cEntries: UINT, prgbq: ^RGBQUAD) -> UINT ---
+
+	CreatePen :: proc(iStyle, cWidth: INT, color: COLORREF) -> HPEN ---
+	ExtCreatePen :: proc(iPenStyle, cWidth: DWORD, plbrush: ^LOGBRUSH, cStyle: DWORD, pstyle: ^DWORD) -> HPEN ---
+	SetDCPenColor :: proc(hdc: HDC, color: COLORREF) -> COLORREF ---
+	GetDCPenColor :: proc(hdc: HDC) -> COLORREF ---
+
+	CreatePalette :: proc(plpal: ^LOGPALETTE) -> HPALETTE ---
+	SelectPalette :: proc(hdc: HDC, hPal: HPALETTE, bForceBkgd: BOOL) -> HPALETTE ---
+	RealizePalette :: proc(hdc: HDC) -> UINT ---
+
+	SetTextColor :: proc(hdc: HDC, color: COLORREF) -> COLORREF ---
+	RoundRect :: proc(hdc: HDC, left: INT, top: INT, right: INT, bottom: INT, width: INT, height: INT) -> BOOL ---
+	SetPixel :: proc(hdc: HDC, x: INT, y: INT, color: COLORREF) -> COLORREF ---
+
+	GdiTransparentBlt :: proc(hdcDest: HDC, xoriginDest, yoriginDest, wDest, hDest: INT, hdcSrc: HDC, xoriginSrc, yoriginSrc, wSrc, hSrc: INT, crTransparent: UINT) -> BOOL ---
+	GdiGradientFill :: proc(hdc: HDC, pVertex: PTRIVERTEX, nVertex: ULONG, pMesh: PVOID, nCount: ULONG, ulMode: ULONG) -> BOOL ---
+	GdiAlphaBlend :: proc(hdcDest: HDC, xoriginDest, yoriginDest, wDest, hDest: INT, hdcSrc: HDC, xoriginSrc, yoriginSrc, wSrc, hSrc: INT, ftn: BLENDFUNCTION) -> BOOL ---
+}
+
+RGB :: #force_inline proc "contextless" (#any_int r, g, b: int) -> COLORREF {
+	return COLORREF(DWORD(BYTE(r)) | (DWORD(BYTE(g)) << 8) | (DWORD(BYTE(b)) << 16))
+}
+
+PALETTERGB :: #force_inline proc "contextless" (#any_int r, g, b: int) -> COLORREF {
+	return 0x02000000 | RGB(r, g, b)
 }
 
-RGB :: #force_inline proc "contextless" (r, g, b: u8) -> COLORREF {
-	return transmute(COLORREF)[4]u8{r, g, b, 0}
+PALETTEINDEX :: #force_inline proc "contextless" (#any_int i: int) -> COLORREF {
+	return COLORREF(DWORD(0x01000000) | DWORD(WORD(i)))
 }
 
 FXPT2DOT30 :: distinct fixed.Fixed(i32, 30)
 
 CIEXYZ :: struct {
-	ciexyzX: FXPT2DOT30,
-	ciexyzY: FXPT2DOT30,
-	ciexyzZ: FXPT2DOT30,
+	ciexyzX, ciexyzY, ciexyzZ: FXPT2DOT30,
 }
 
 CIEXYZTRIPLE :: struct {
-	ciexyzRed:   CIEXYZ,
-	ciexyzGreen: CIEXYZ,
-	ciexyzBlue:  CIEXYZ,
+	ciexyzRed, ciexyzGreen, ciexyzBlue: CIEXYZ,
 }
 
 // https://learn.microsoft.com/en-us/windows/win32/api/wingdi/ns-wingdi-bitmapv5header
@@ -138,3 +127,227 @@ BITMAPV5HEADER :: struct {
 	bV5ProfileSize:   DWORD,
 	bV5Reserved:      DWORD,
 }
+
+PALETTEENTRY :: struct {
+	peRed, peGreen, peBlue, peFlags: BYTE,
+}
+
+LOGPALETTE :: struct {
+	palVersion:    WORD,
+	palNumEntries: WORD,
+	palPalEntry:   []PALETTEENTRY,
+}
+
+BKMODE :: enum {
+	TRANSPARENT = 1,
+	OPAQUE      = 2,
+}
+
+ICONINFOEXW :: struct {
+	cbSize:             DWORD,
+	fIcon:              BOOL,
+	xHotspot, yHotspot: DWORD,
+	hbmMask, hbmColor:  HBITMAP,
+	wResID:             WORD,
+	szModName:          [MAX_PATH]WCHAR,
+	szResName:          [MAX_PATH]WCHAR,
+}
+PICONINFOEXW :: ^ICONINFOEXW
+
+AC_SRC_OVER :: 0x00
+AC_SRC_ALPHA :: 0x01
+
+TransparentBlt :: GdiTransparentBlt
+GradientFill :: GdiGradientFill
+AlphaBlend :: GdiAlphaBlend
+
+COLOR16 :: USHORT
+TRIVERTEX :: struct {
+	x, y:                    LONG,
+	Red, Green, Blue, Alpha: COLOR16,
+}
+PTRIVERTEX :: ^TRIVERTEX
+
+GRADIENT_TRIANGLE :: struct {
+	Vertex1, Vertex2, Vertex3: ULONG,
+}
+PGRADIENT_TRIANGLE :: ^GRADIENT_TRIANGLE
+
+GRADIENT_RECT :: struct {
+	UpperLeft, LowerRight: ULONG,
+}
+PGRADIENT_RECT :: ^GRADIENT_RECT
+
+BLENDFUNCTION :: struct {
+	BlendOp, BlendFlags, SourceConstantAlpha, AlphaFormat: BYTE,
+}
+
+GRADIENT_FILL_RECT_H    : ULONG : 0x00000000
+GRADIENT_FILL_RECT_V    : ULONG : 0x00000001
+GRADIENT_FILL_TRIANGLE  : ULONG : 0x00000002
+GRADIENT_FILL_OP_FLAG   : ULONG : 0x000000ff
+
+/* Brush Styles */
+BS_SOLID         :: 0
+BS_NULL          :: 1
+BS_HOLLOW        :: BS_NULL
+BS_HATCHED       :: 2
+BS_PATTERN       :: 3
+BS_INDEXED       :: 4
+BS_DIBPATTERN    :: 5
+BS_DIBPATTERNPT  :: 6
+BS_PATTERN8X8    :: 7
+BS_DIBPATTERN8X8 :: 8
+BS_MONOPATTERN   :: 9
+
+/* Hatch Styles */
+HS_HORIZONTAL    :: 0       /* ----- */
+HS_VERTICAL      :: 1       /* ||||| */
+HS_FDIAGONAL     :: 2       /* \\\\\ */
+HS_BDIAGONAL     :: 3       /* ///// */
+HS_CROSS         :: 4       /* +++++ */
+HS_DIAGCROSS     :: 5       /* xxxxx */
+HS_API_MAX       :: 12
+
+/* Pen Styles */
+PS_SOLID         ::  0
+PS_DASH          ::  1      /* ------- */
+PS_DOT           ::  2      /* ....... */
+PS_DASHDOT       ::  3      /* _._._._ */
+PS_DASHDOTDOT    ::  4      /* _.._.._ */
+PS_NULL          ::  5
+PS_INSIDEFRAME   ::  6
+PS_USERSTYLE     ::  7
+PS_ALTERNATE     ::  8
+PS_STYLE_MASK    ::  0x0000000F
+PS_ENDCAP_ROUND  ::  0x00000000
+PS_ENDCAP_SQUARE ::  0x00000100
+PS_ENDCAP_FLAT   ::  0x00000200
+PS_ENDCAP_MASK   ::  0x00000F00
+PS_JOIN_ROUND    ::  0x00000000
+PS_JOIN_BEVEL    ::  0x00001000
+PS_JOIN_MITER    ::  0x00002000
+PS_JOIN_MASK     ::  0x0000F000
+PS_COSMETIC      ::  0x00000000
+PS_GEOMETRIC     ::  0x00010000
+PS_TYPE_MASK     ::  0x000F0000
+
+LOGBRUSH :: struct {
+	lbStyle: UINT,
+	lbColor: COLORREF,
+	lbHatch: ULONG_PTR,
+}
+PLOGBRUSH :: ^LOGBRUSH
+
+/* CombineRgn() Styles */
+RGN_AND  :: 1
+RGN_OR   :: 2
+RGN_XOR  :: 3
+RGN_DIFF :: 4
+RGN_COPY :: 5
+
+/* StretchBlt() Modes */
+// BLACKONWHITE :: 1
+// WHITEONBLACK :: 2
+// COLORONCOLOR :: 3
+// HALFTONE     :: 4
+
+/* PolyFill() Modes */
+ALTERNATE :: 1
+WINDING   :: 2
+
+/* Layout Orientation Options */
+LAYOUT_RTL             :: 0x00000001 // Right to left
+LAYOUT_BTT             :: 0x00000002 // Bottom to top
+LAYOUT_VBH             :: 0x00000004 // Vertical before horizontal
+LAYOUT_ORIENTATIONMASK :: (LAYOUT_RTL | LAYOUT_BTT | LAYOUT_VBH)
+
+/* Text Alignment Options */
+TA_NOUPDATECP :: 0
+TA_UPDATECP   :: 1
+
+TA_LEFT       :: 0
+TA_RIGHT      :: 2
+TA_CENTER     :: 6
+
+TA_TOP        :: 0
+TA_BOTTOM     :: 8
+TA_BASELINE   :: 24
+TA_RTLREADING :: 256
+TA_MASK       :: (TA_BASELINE+TA_CENTER+TA_UPDATECP+TA_RTLREADING)
+
+MM_MAX_NUMAXES :: 16
+DESIGNVECTOR :: struct {
+	dvReserved: DWORD,
+	dvNumAxes:  DWORD,
+	dvValues:   [MM_MAX_NUMAXES]LONG,
+}
+
+LF_FACESIZE :: 32
+LF_FULLFACESIZE :: 64
+
+LOGFONTW :: struct {
+	lfHeight:         LONG,
+	lfWidth:          LONG,
+	lfEscapement:     LONG,
+	lfOrientation:    LONG,
+	lfWeight:         LONG,
+	lfItalic:         BYTE,
+	lfUnderline:      BYTE,
+	lfStrikeOut:      BYTE,
+	lfCharSet:        BYTE,
+	lfOutPrecision:   BYTE,
+	lfClipPrecision:  BYTE,
+	lfQuality:        BYTE,
+	lfPitchAndFamily: BYTE,
+	lfFaceName:       [LF_FACESIZE]WCHAR,
+}
+LPLOGFONTW :: ^LOGFONTW
+
+ENUMLOGFONTW :: struct {
+	elfLogFont:  LOGFONTW,
+	elfFullName: [LF_FULLFACESIZE]WCHAR,
+	elfStyle:    [LF_FACESIZE]WCHAR,
+}
+LPENUMLOGFONTW :: ^ENUMLOGFONTW
+
+ENUMLOGFONTEXW :: struct {
+	elfLogFont:  LOGFONTW,
+	elfFullName: [LF_FULLFACESIZE]WCHAR,
+	elfStyle:    [LF_FACESIZE]WCHAR,
+	elfScript:   [LF_FACESIZE]WCHAR,
+}
+
+ENUMLOGFONTEXDVW :: struct {
+	elfEnumLogfontEx: ENUMLOGFONTEXW,
+	elfDesignVector:  DESIGNVECTOR,
+}
+
+NEWTEXTMETRICW :: struct {
+	tmHeight:           LONG,
+	tmAscent:           LONG,
+	tmDescent:          LONG,
+	tmInternalLeading:  LONG,
+	tmExternalLeading:  LONG,
+	tmAveCharWidth:     LONG,
+	tmMaxCharWidth:     LONG,
+	tmWeight:           LONG,
+	tmOverhang:         LONG,
+	tmDigitizedAspectX: LONG,
+	tmDigitizedAspectY: LONG,
+	tmFirstChar:        WCHAR,
+	tmLastChar:         WCHAR,
+	tmDefaultChar:      WCHAR,
+	tmBreakChar:        WCHAR,
+	tmItalic:           BYTE,
+	tmUnderlined:       BYTE,
+	tmStruckOut:        BYTE,
+	tmPitchAndFamily:   BYTE,
+	tmCharSet:          BYTE,
+	ntmFlags:           DWORD,
+	ntmSizeEM:          UINT,
+	ntmCellHeight:      UINT,
+	ntmAvgWidth:        UINT,
+}
+
+FONTENUMPROCW :: #type proc(lpelf: ^ENUMLOGFONTW, lpntm: ^NEWTEXTMETRICW, FontType: DWORD, lParam: LPARAM) -> INT

+ 33 - 14
core/sys/windows/kernel32.odin

@@ -51,18 +51,14 @@ foreign kernel32 {
 	// https://learn.microsoft.com/en-us/windows/console/getnumberofconsoleinputevents
 	GetNumberOfConsoleInputEvents :: proc(hConsoleInput: HANDLE, lpcNumberOfEvents: LPDWORD) -> BOOL ---
 
-	GetConsoleMode :: proc(hConsoleHandle: HANDLE,
-	                       lpMode: LPDWORD) -> BOOL ---
-	SetConsoleMode :: proc(hConsoleHandle: HANDLE,
-	                       dwMode: DWORD) -> BOOL ---
-	SetConsoleCursorPosition :: proc(hConsoleHandle: HANDLE,
-	                                 dwCursorPosition: COORD) -> BOOL ---
-	SetConsoleTextAttribute :: proc(hConsoleOutput: HANDLE,
-	                                wAttributes: WORD) -> BOOL ---
-	GetConsoleCP :: proc() -> UINT ---
-	SetConsoleCP :: proc(wCodePageID: UINT) -> BOOL ---
-	GetConsoleOutputCP :: proc() -> UINT ---
-	SetConsoleOutputCP :: proc(wCodePageID: UINT) -> BOOL ---
+	GetConsoleMode :: proc(hConsoleHandle: HANDLE, lpMode: LPDWORD) -> BOOL ---
+	SetConsoleMode :: proc(hConsoleHandle: HANDLE, dwMode: DWORD) -> BOOL ---
+	SetConsoleCursorPosition :: proc(hConsoleHandle: HANDLE, dwCursorPosition: COORD) -> BOOL ---
+	SetConsoleTextAttribute :: proc(hConsoleOutput: HANDLE, wAttributes: WORD) -> BOOL ---
+	GetConsoleCP :: proc() -> CODEPAGE ---
+	SetConsoleCP :: proc(wCodePageID: CODEPAGE) -> BOOL ---
+	GetConsoleOutputCP :: proc() -> CODEPAGE ---
+	SetConsoleOutputCP :: proc(wCodePageID: CODEPAGE) -> BOOL ---
 	FlushConsoleInputBuffer :: proc(hConsoleInput: HANDLE) -> BOOL ---
 
 	GetFileInformationByHandle :: proc(hFile: HANDLE, lpFileInformation: LPBY_HANDLE_FILE_INFORMATION) -> BOOL ---
@@ -256,6 +252,7 @@ foreign kernel32 {
 	SetEnvironmentVariableW :: proc(n: LPCWSTR, v: LPCWSTR) -> BOOL ---
 	GetEnvironmentStringsW :: proc() -> LPWCH ---
 	FreeEnvironmentStringsW :: proc(env_ptr: LPWCH) -> BOOL ---
+	ExpandEnvironmentStringsW :: proc(lpSrc: LPCWSTR, lpDst: LPWSTR, nSize: DWORD) -> DWORD ---
 	GetModuleFileNameW :: proc(hModule: HMODULE, lpFilename: LPWSTR, nSize: DWORD) -> DWORD ---
 	CreateDirectoryW :: proc(
 		lpPathName: LPCWSTR,
@@ -440,12 +437,34 @@ foreign kernel32 {
 	GetFileAttributesExW :: proc(lpFileName: LPCWSTR, fInfoLevelId: GET_FILEEX_INFO_LEVELS, lpFileInformation: LPVOID) -> BOOL ---
 	GetSystemInfo :: proc(system_info: ^SYSTEM_INFO) ---
 	GetVersionExW :: proc(osvi: ^OSVERSIONINFOEXW) ---
-
+	GetSystemDirectoryW :: proc(lpBuffer: LPWSTR, uSize: UINT) -> UINT ---
+	GetWindowsDirectoryW :: proc(lpBuffer: LPWSTR, uSize: UINT) -> UINT ---
+	GetSystemDefaultLangID :: proc() -> LANGID ---
+	GetSystemDefaultLCID :: proc() -> LCID ---
+	GetSystemDefaultLocaleName :: proc(lpLocaleName: LPWSTR, cchLocaleName: INT) -> INT ---
+	LCIDToLocaleName :: proc(Locale: LCID, lpName: LPWSTR, cchName: INT, dwFlags: DWORD) -> INT ---
+	LocaleNameToLCID :: proc(lpName: LPCWSTR, dwFlags: DWORD) -> LCID ---
+	SetDllDirectoryW :: proc(lpPathName: LPCWSTR) -> BOOL ---
+	AddDllDirectory :: proc(NewDirectory: PCWSTR) -> rawptr ---
+	RemoveDllDirectory :: proc(Cookie: rawptr) -> BOOL ---
 	LoadLibraryW :: proc(c_str: LPCWSTR) -> HMODULE ---
+	LoadLibraryExW :: proc(c_str: LPCWSTR, hFile: HANDLE, dwFlags: LoadLibraryEx_Flags) -> HMODULE ---
 	FreeLibrary :: proc(h: HMODULE) -> BOOL ---
 	GetProcAddress :: proc(h: HMODULE, c_str: LPCSTR) -> rawptr ---
-	LoadLibraryExW :: proc(c_str: LPCWSTR, file: HANDLE, flags: LoadLibraryEx_Flags) -> HMODULE ---
 
+	LoadResource :: proc(hModule: HMODULE, hResInfo: HRSRC) -> HGLOBAL ---
+	FreeResource :: proc(hResData: HGLOBAL) -> BOOL ---
+	LockResource :: proc(hResData: HGLOBAL) -> LPVOID ---
+	SizeofResource :: proc(hModule: HMODULE, hResInfo: HRSRC) -> DWORD ---
+	FindResourceW :: proc(hModule: HMODULE, lpName: LPCWSTR, lpType: LPCWSTR) -> HRSRC ---
+	FindResourceExW :: proc(hModule: HMODULE, lpType: LPCWSTR, lpName: LPCWSTR, wLanguage: LANGID) -> HRSRC ---
+	EnumResourceNamesW :: proc(hModule: HMODULE, lpType: LPCWSTR, lpEnumFunc: ENUMRESNAMEPROCW, lParam: LONG_PTR) -> BOOL ---
+	EnumResourceNamesExW :: proc(hModule: HMODULE, lpType: LPCWSTR, lpEnumFunc: ENUMRESNAMEPROCW, lParam: LONG_PTR, dwFlags: DWORD, LangId: LANGID) -> BOOL ---
+	EnumResourceTypesExW :: proc(hModule: HMODULE, lpEnumFunc: ENUMRESTYPEPROCW, lParam: LONG_PTR, dwFlags: DWORD, LangId: LANGID) -> BOOL ---
+	EnumResourceLanguagesExW :: proc(hModule: HMODULE, lpType: LPCWSTR, lpName: LPCWSTR, lpEnumFunc: ENUMRESLANGPROCW, lParam: LONG_PTR, dwFlags: DWORD, LangId: LANGID) -> BOOL ---
+	LookupIconIdFromDirectory :: proc(presbits: PBYTE, fIcon: BOOL) -> INT ---
+	LookupIconIdFromDirectoryEx :: proc(presbits: PBYTE, fIcon: BOOL, cxDesired: INT, cyDesired: INT, Flags: UINT) -> INT ---
+	CreateIconFromResourceEx :: proc(presbits: PBYTE, dwResSize: DWORD, fIcon: BOOL, dwVer: DWORD, cxDesired: INT, cyDesired: INT, Flags: UINT) -> HICON ---
 
 	GetFullPathNameW  :: proc(filename: LPCWSTR, buffer_length: DWORD, buffer: LPCWSTR, file_part: ^LPCWSTR) -> DWORD ---
 	GetLongPathNameW  :: proc(short, long: LPCWSTR, len: DWORD) -> DWORD ---

+ 38 - 0
core/sys/windows/ntdll.odin

@@ -23,8 +23,24 @@ foreign ntdll_lib {
 		Length:               ULONG,
 		FileInformationClass: FILE_INFORMATION_CLASS,
 	) -> NTSTATUS ---
+
+	NtQueryDirectoryFileEx :: proc(
+		FileHandle:           HANDLE,
+		Event:                HANDLE,
+		ApcRoutine:           PIO_APC_ROUTINE,
+		ApcContext:           PVOID,
+		IoStatusBlock:        PIO_STATUS_BLOCK,
+		FileInformation:      PVOID,
+		Length:               ULONG,
+		FileInformationClass: FILE_INFORMATION_CLASS,
+		QueryFlags:           ULONG,
+		FileName   :          PUNICODE_STRING,
+	) -> NTSTATUS ---
 }
 
+
+PIO_APC_ROUTINE :: #type proc "system" (ApcContext: rawptr, IoStatusBlock: PIO_STATUS_BLOCK, Reserved: ULONG)
+
 PIO_STATUS_BLOCK :: ^IO_STATUS_BLOCK
 IO_STATUS_BLOCK :: struct {
 	using _: struct #raw_union {
@@ -45,6 +61,12 @@ PROCESS_INFO_CLASS :: enum c_int {
 	ProcessSubsystemInformation   = 75,
 }
 
+SL_RESTART_SCAN                :: 0x00000001 // The scan will start at the first entry in the directory. If this flag is not set, the scan will resume from where the last query ended.
+SL_RETURN_SINGLE_ENTRY         :: 0x00000002 // Normally the return buffer is packed with as many matching directory entries that fit. If this flag is set, the file system will return only one directory entry at a time. This does make the operation less efficient.
+SL_INDEX_SPECIFIED             :: 0x00000004 // The scan should start at a specified indexed position in the directory. This flag can only be set if you generate your own IRP_MJ_DIRECTORY_CONTROL IRP; the index is specified in the IRP. How the position is specified varies from file system to file system.
+SL_RETURN_ON_DISK_ENTRIES_ONLY :: 0x00000008 // Any file system filters that perform directory virtualization or just-in-time expansion should simply pass the request through to the file system and return entries that are currently on disk. Not all file systems support this flag.
+SL_NO_CURSOR_UPDATE_QUERY      :: 0x00000010 // File systems maintain per-FileObject directory cursor information. When multiple threads do queries using the same FileObject, access to the per-FileObject structure is single threaded to prevent corruption of the cursor state. This flag tells the file system to not update per-FileObject cursor state information thus allowing multiple threads to query in parallel using the same handle. It behaves as if SL_RESTART_SCAN is specified on each call. If a wild card pattern is given on the next call, the operation will not pick up where the last query ended. This allows for true asynchronous directory query support. If this flag is used inside a TxF transaction the operation will be failed. Not all file systems support this flag.
+
 
 PFILE_INFORMATION_CLASS :: ^FILE_INFORMATION_CLASS
 FILE_INFORMATION_CLASS :: enum c_int {
@@ -134,6 +156,22 @@ FILE_INFORMATION_CLASS :: enum c_int {
 	FileMaximumInformation,
 }
 
+PFILE_ID_FULL_DIR_INFORMATION :: ^FILE_ID_FULL_DIR_INFORMATION
+FILE_ID_FULL_DIR_INFORMATION :: struct {
+	NextEntryOffset: ULONG,
+	FileIndex:       ULONG,
+	CreationTime:    LARGE_INTEGER,
+	LastAccessTime:  LARGE_INTEGER,
+	LastWriteTime:   LARGE_INTEGER,
+	ChangeTime:      LARGE_INTEGER,
+	EndOfFile:       LARGE_INTEGER,
+	AllocationSize:  LARGE_INTEGER,
+	FileAttributes:  ULONG,
+	FileNameLength:  ULONG,
+	EaSize:          ULONG,
+	FileId:          LARGE_INTEGER,
+	FileName:        [1]WCHAR,
+}
 
 
 PROCESS_BASIC_INFORMATION :: struct {

+ 15 - 1
core/sys/windows/ole32.odin

@@ -40,7 +40,8 @@ LPUNKNOWN :: ^IUnknown
 
 @(default_calling_convention="system")
 foreign Ole32 {
-	CoInitializeEx :: proc(reserved: rawptr, co_init: COINIT) -> HRESULT ---
+	CoInitialize :: proc(reserved: rawptr = nil) -> HRESULT ---
+	CoInitializeEx :: proc(reserved: rawptr = nil, co_init: COINIT = .APARTMENTTHREADED) -> HRESULT ---
 	CoUninitialize :: proc() ---
 
 	CoCreateInstance :: proc(
@@ -52,4 +53,17 @@ foreign Ole32 {
 	) -> HRESULT ---
 
 	CoTaskMemFree :: proc(pv: rawptr) ---
+
+	CLSIDFromProgID :: proc(lpszProgID: LPCOLESTR, lpclsid: LPCLSID) -> HRESULT ---
+	CLSIDFromProgIDEx :: proc(lpszProgID, LPCOLESTR, lpclsid: LPCLSID) -> HRESULT ---
+	CLSIDFromString :: proc(lpsz: LPOLESTR, pclsid: LPCLSID) -> HRESULT ---
+	IIDFromString :: proc(lpsz: LPOLESTR, lpiid: LPIID) -> HRESULT ---
+	ProgIDFromCLSID :: proc(clsid: REFCLSID, lplpszProgID: ^LPOLESTR) -> HRESULT ---
+	StringFromCLSID :: proc(rclsid: REFCLSID, lplpsz: ^LPOLESTR) -> HRESULT ---
+	StringFromGUID2 :: proc(rclsid: REFCLSID, lplpsz: LPOLESTR, cchMax: INT) -> INT ---
+	StringFromIID :: proc(rclsid: REFIID, lplpsz: ^LPOLESTR) -> HRESULT ---
+
+	PropVariantClear :: proc(pvar: ^PROPVARIANT) -> HRESULT ---
+	PropVariantCopy :: proc(pvarDest: ^PROPVARIANT, pvarSrc: ^PROPVARIANT) -> HRESULT ---
+	FreePropVariantArray :: proc(cVariants: ULONG, rgvars: ^PROPVARIANT) -> HRESULT ---
 }

+ 25 - 0
core/sys/windows/shcore.odin

@@ -0,0 +1,25 @@
+// +build windows
+
+package sys_windows
+
+foreign import shcore "system:Shcore.lib"
+
+@(default_calling_convention="system")
+foreign shcore {
+	GetProcessDpiAwareness :: proc(hprocess: HANDLE, value: ^PROCESS_DPI_AWARENESS) -> HRESULT ---
+	SetProcessDpiAwareness :: proc(value: PROCESS_DPI_AWARENESS) -> HRESULT ---
+	GetDpiForMonitor :: proc(hmonitor: HMONITOR, dpiType: MONITOR_DPI_TYPE, dpiX: ^UINT, dpiY: ^UINT) -> HRESULT ---
+}
+
+PROCESS_DPI_AWARENESS :: enum DWORD {
+	PROCESS_DPI_UNAWARE = 0,
+	PROCESS_SYSTEM_DPI_AWARE = 1,
+	PROCESS_PER_MONITOR_DPI_AWARE = 2,
+}
+
+MONITOR_DPI_TYPE :: enum DWORD {
+	MDT_EFFECTIVE_DPI = 0,
+	MDT_ANGULAR_DPI = 1,
+	MDT_RAW_DPI = 2,
+	MDT_DEFAULT,
+}

+ 1 - 0
core/sys/windows/shell32.odin

@@ -31,6 +31,7 @@ foreign shell32 {
 	SHSetKnownFolderPath :: proc(rfid: REFKNOWNFOLDERID, dwFlags: /* KNOWN_FOLDER_FLAG */ DWORD, hToken: HANDLE, pszPath: PCWSTR ) -> HRESULT ---
 	SHGetKnownFolderPath :: proc(rfid: REFKNOWNFOLDERID, dwFlags: /* KNOWN_FOLDER_FLAG */ DWORD, hToken: HANDLE, ppszPath: ^LPWSTR) -> HRESULT ---
 
+	ExtractIconExW :: proc(pszFile: LPCWSTR, nIconIndex: INT, phiconLarge: ^HICON, phiconSmall: ^HICON, nIcons: UINT) -> UINT ---
 	DragAcceptFiles :: proc(hWnd: HWND, fAccept: BOOL) ---
 	DragQueryPoint :: proc(hDrop: HDROP, ppt: ^POINT) -> BOOL ---
 	DragQueryFileW :: proc(hDrop: HDROP, iFile: UINT, lpszFile: LPWSTR, cch: UINT) -> UINT ---

+ 121 - 4
core/sys/windows/types.odin

@@ -30,8 +30,10 @@ HICON :: distinct HANDLE
 HCURSOR :: distinct HANDLE
 HMENU :: distinct HANDLE
 HBRUSH :: distinct HANDLE
+HPEN :: distinct HANDLE
 HGDIOBJ :: distinct HANDLE
 HBITMAP :: distinct HANDLE
+HPALETTE :: distinct HANDLE
 HGLOBAL :: distinct HANDLE
 HHOOK :: distinct HANDLE
 HWINEVENTHOOK :: distinct HANDLE
@@ -39,6 +41,9 @@ HKEY :: distinct HANDLE
 HDESK :: distinct HANDLE
 HFONT :: distinct HANDLE
 HRGN :: distinct HANDLE
+HRSRC :: distinct HANDLE
+HWINSTA :: distinct HANDLE
+HACCEL :: distinct HANDLE
 BOOL :: distinct b32
 BYTE :: distinct u8
 BOOLEAN :: distinct b8
@@ -139,11 +144,14 @@ LPSTR :: ^CHAR
 LPWSTR :: ^WCHAR
 OLECHAR :: WCHAR
 LPOLESTR :: ^OLECHAR
+LPCOLESTR :: LPCSTR
 LPFILETIME :: ^FILETIME
 LPWSABUF :: ^WSABUF
 LPWSAOVERLAPPED :: distinct rawptr
 LPWSAOVERLAPPED_COMPLETION_ROUTINE :: distinct rawptr
 LPCVOID :: rawptr
+SCODE :: LONG
+PSCODE :: ^SCODE
 
 PACCESS_TOKEN :: PVOID
 PSECURITY_DESCRIPTOR :: PVOID
@@ -699,6 +707,92 @@ FW_BLACK      :: FW_HEAVY
 
 PTIMERAPCROUTINE :: #type proc "system" (lpArgToCompletionRoutine: LPVOID, dwTimerLowValue, dwTimerHighValue: DWORD)
 
+// Character Sets
+ANSI_CHARSET        :: 0
+DEFAULT_CHARSET     :: 1
+SYMBOL_CHARSET      :: 2
+SHIFTJIS_CHARSET    :: 128
+HANGEUL_CHARSET     :: 129
+HANGUL_CHARSET      :: 129
+GB2312_CHARSET      :: 134
+CHINESEBIG5_CHARSET :: 136
+OEM_CHARSET         :: 255
+JOHAB_CHARSET       :: 130
+HEBREW_CHARSET      :: 177
+ARABIC_CHARSET      :: 178
+GREEK_CHARSET       :: 161
+TURKISH_CHARSET     :: 162
+VIETNAMESE_CHARSET  :: 163
+THAI_CHARSET        :: 222
+EASTEUROPE_CHARSET  :: 238
+RUSSIAN_CHARSET     :: 204
+MAC_CHARSET         :: 77
+BALTIC_CHARSET      :: 186
+
+// Font Signature Bitmaps
+FS_LATIN1      :: 0x00000001
+FS_LATIN2      :: 0x00000002
+FS_CYRILLIC    :: 0x00000004
+FS_GREEK       :: 0x00000008
+FS_TURKISH     :: 0x00000010
+FS_HEBREW      :: 0x00000020
+FS_ARABIC      :: 0x00000040
+FS_BALTIC      :: 0x00000080
+FS_VIETNAMESE  :: 0x00000100
+FS_THAI        :: 0x00010000
+FS_JISJAPAN    :: 0x00020000
+FS_CHINESESIMP :: 0x00040000
+FS_WANSUNG     :: 0x00080000
+FS_CHINESETRAD :: 0x00100000
+FS_JOHAB       :: 0x00200000
+FS_SYMBOL      :: 0x80000000
+
+// Output Precisions
+OUT_DEFAULT_PRECIS        :: 0
+OUT_STRING_PRECIS         :: 1
+OUT_CHARACTER_PRECIS      :: 2
+OUT_STROKE_PRECIS         :: 3
+OUT_TT_PRECIS             :: 4
+OUT_DEVICE_PRECIS         :: 5
+OUT_RASTER_PRECIS         :: 6
+OUT_TT_ONLY_PRECIS        :: 7
+OUT_OUTLINE_PRECIS        :: 8
+OUT_SCREEN_OUTLINE_PRECIS :: 9
+OUT_PS_ONLY_PRECIS        :: 10
+
+// Clipping Precisions
+CLIP_DEFAULT_PRECIS   :: 0
+CLIP_CHARACTER_PRECIS :: 1
+CLIP_STROKE_PRECIS    :: 2
+CLIP_MASK             :: 0xf
+CLIP_LH_ANGLES        :: 1 << 4
+CLIP_TT_ALWAYS        :: 2 << 4
+CLIP_DFA_DISABLE      :: 4 << 4
+CLIP_EMBEDDED         :: 8 << 4
+
+// Output Qualities
+DEFAULT_QUALITY           :: 0
+DRAFT_QUALITY             :: 1
+PROOF_QUALITY             :: 2
+NONANTIALIASED_QUALITY    :: 3
+ANTIALIASED_QUALITY       :: 4
+CLEARTYPE_QUALITY         :: 5
+CLEARTYPE_NATURAL_QUALITY :: 6
+
+// Font Pitches
+DEFAULT_PITCH  :: 0
+FIXED_PITCH    :: 1
+VARIABLE_PITCH :: 2
+MONO_FONT      :: 8
+
+// Font Families
+FF_DONTCARE   :: 0 << 4
+FF_ROMAN      :: 1 << 4
+FF_SWISS      :: 2 << 4
+FF_MODERN     :: 3 << 4
+FF_SCRIPT     :: 4 << 4
+FF_DECORATIVE :: 5 << 4
+
 TIMERPROC :: #type proc "system" (HWND, UINT, UINT_PTR, DWORD)
 
 WNDPROC :: #type proc "system" (HWND, UINT, WPARAM, LPARAM) -> LRESULT
@@ -932,6 +1026,16 @@ MF_RIGHTJUSTIFY :: 0x00004000
 MF_MOUSESELECT :: 0x00008000
 MF_END :: 0x00000080  // Obsolete -- only used by old RES files
 
+// Menu flags for Add/Check/EnableMenuItem()
+MFS_GRAYED    :: 0x00000003
+MFS_DISABLED  :: MFS_GRAYED
+MFS_CHECKED   :: MF_CHECKED
+MFS_HILITE    :: MF_HILITE
+MFS_ENABLED   :: MF_ENABLED
+MFS_UNCHECKED :: MF_UNCHECKED
+MFS_UNHILITE  :: MF_UNHILITE
+MFS_DEFAULT   :: MF_DEFAULT
+
 // Flags for TrackPopupMenu
 TPM_LEFTBUTTON :: 0x0000
 TPM_RIGHTBUTTON :: 0x0002
@@ -1037,9 +1141,6 @@ WIN32_FIND_DATAW :: struct {
 	dwReserved1:        DWORD,
 	cFileName:          [MAX_PATH]WCHAR,
 	cAlternateFileName: [14]WCHAR,
-	_OBSOLETE_dwFileType:    DWORD, // Obsolete. Do not use.
-	_OBSOLETE_dwCreatorType: DWORD, // Obsolete. Do not use
-	_OBSOLETE_wFinderFlags:  WORD,  // Obsolete. Do not use
 }
 
 FILE_ID_128 :: struct {
@@ -2207,6 +2308,14 @@ CP_SYMBOL     :: 42    // SYMBOL translations
 CP_UTF7       :: 65000 // UTF-7 translation
 CP_UTF8       :: 65001 // UTF-8 translation
 
+LCID :: DWORD
+LANGID :: WORD
+
+LANG_NEUTRAL :: 0x00
+LANG_INVARIANT :: 0x7f
+SUBLANG_NEUTRAL :: 0x00 // language neutral
+SUBLANG_DEFAULT :: 0x01 // user default
+
 MB_ERR_INVALID_CHARS :: 8
 WC_ERR_INVALID_CHARS :: 128
 
@@ -2422,8 +2531,10 @@ REFIID  :: ^GUID
 
 REFGUID :: GUID
 IID :: GUID
+LPIID :: ^IID
 CLSID :: GUID
 REFCLSID :: ^CLSID
+LPCLSID :: ^CLSID
 
 CLSCTX_INPROC_SERVER                  :: 0x1
 CLSCTX_INPROC_HANDLER                 :: 0x2
@@ -2453,6 +2564,7 @@ CLSCTX_RESERVED6                      :: 0x1000000
 CLSCTX_ACTIVATE_ARM32_SERVER          :: 0x2000000
 CLSCTX_ALLOW_LOWER_TRUST_REGISTRATION :: 0x4000000
 CLSCTX_PS_DLL                         :: 0x80000000
+CLSCTX_ALL                            :: CLSCTX_INPROC_SERVER | CLSCTX_INPROC_HANDLER | CLSCTX_LOCAL_SERVER | CLSCTX_REMOTE_SERVER
 
 WSAPROTOCOLCHAIN :: struct {
 	ChainLen: c_int,
@@ -2512,6 +2624,7 @@ OBJECT_ATTRIBUTES :: struct {
 	SecurityQualityOfService: rawptr,
 }
 
+PUNICODE_STRING :: ^UNICODE_STRING
 UNICODE_STRING :: struct {
 	Length:        u16    `fmt:"-"`,
 	MaximumLength: u16    `fmt:"-"`,
@@ -4389,7 +4502,7 @@ DNS_STATUS :: distinct DWORD // zero is success
 DNS_INFO_NO_RECORDS :: 9501
 DNS_QUERY_NO_RECURSION :: 0x00000004
 
-DNS_RECORD :: struct {
+DNS_RECORD :: struct { // aka DNS_RECORDA
     pNext: ^DNS_RECORD,
     pName: cstring,
     wType: WORD,
@@ -4433,6 +4546,10 @@ SOCKADDR :: struct {
 	sa_data:   [14]CHAR,
 }
 
+ENUMRESNAMEPROCW :: #type proc (hModule: HMODULE, lpType: LPCWSTR, lpName: LPWSTR, lParam: LONG_PTR)-> BOOL
+ENUMRESTYPEPROCW :: #type proc (hModule: HMODULE, lpType: LPCWSTR, lParam: LONG_PTR)-> BOOL
+ENUMRESLANGPROCW :: #type proc (hModule: HMODULE, lpType: LPCWSTR, lpName: LPWSTR, wIDLanguage: LANGID, lParam: LONG_PTR)-> BOOL
+
 DTR_Control :: enum byte {
 	Disable = 0,
 	Enable = 1,

+ 216 - 70
core/sys/windows/user32.odin

@@ -6,19 +6,18 @@ foreign import user32 "system:User32.lib"
 
 @(default_calling_convention="system")
 foreign user32 {
-	GetClassInfoW :: proc(hInstance: HINSTANCE, lpClassNAme: LPCWSTR, lpWndClass: ^WNDCLASSW) -> BOOL ---
-	GetClassInfoExW :: proc(hInsatnce: HINSTANCE, lpszClass: LPCWSTR, lpwcx: ^WNDCLASSEXW) -> BOOL ---
+	GetClassInfoW :: proc(hInstance: HINSTANCE, lpClassName: LPCWSTR, lpWndClass: ^WNDCLASSW) -> BOOL ---
+	GetClassInfoExW :: proc(hInstance: HINSTANCE, lpszClass: LPCWSTR, lpwcx: ^WNDCLASSEXW) -> BOOL ---
 
-	GetClassLongW :: proc(hWnd: HWND, nIndex: c_int) -> DWORD ---
-	SetClassLongW :: proc(hWnd: HWND, nIndex: c_int, dwNewLong: LONG) -> DWORD ---
+	GetClassLongW :: proc(hWnd: HWND, nIndex: INT) -> DWORD ---
+	SetClassLongW :: proc(hWnd: HWND, nIndex: INT, dwNewLong: LONG) -> DWORD ---
 
-	GetWindowLongW :: proc(hWnd: HWND, nIndex: c_int) -> LONG ---
-	SetWindowLongW :: proc(hWnd: HWND, nIndex: c_int, dwNewLong: LONG) -> LONG ---
+	GetWindowLongW :: proc(hWnd: HWND, nIndex: INT) -> LONG ---
+	SetWindowLongW :: proc(hWnd: HWND, nIndex: INT, dwNewLong: LONG) -> LONG ---
 
-	GetClassNameW :: proc(hWnd: HWND, lpClassName: LPWSTR, nMaxCount: c_int) -> c_int ---
+	GetClassNameW :: proc(hWnd: HWND, lpClassName: LPWSTR, nMaxCount: INT) -> INT ---
 
 	GetParent :: proc(hWnd: HWND) -> HWND ---
-	IsWindowVisible :: proc(hWnd: HWND) -> BOOL ---
 	SetWinEventHook :: proc(
 		eventMin, eventMax: DWORD,
 		hmodWinEventProc: HMODULE,
@@ -38,10 +37,7 @@ foreign user32 {
 		lpClassName: LPCWSTR,
 		lpWindowName: LPCWSTR,
 		dwStyle: DWORD,
-		X: c_int,
-		Y: c_int,
-		nWidth: c_int,
-		nHeight: c_int,
+		X, Y, nWidth, nHeight: INT,
 		hWndParent: HWND,
 		hMenu: HMENU,
 		hInstance: HINSTANCE,
@@ -50,24 +46,33 @@ foreign user32 {
 
 	DestroyWindow :: proc(hWnd: HWND) -> BOOL ---
 
-	ShowWindow :: proc(hWnd: HWND, nCmdShow: c_int) -> BOOL ---
+	ShowWindow :: proc(hWnd: HWND, nCmdShow: INT) -> BOOL ---
 	IsWindow :: proc(hWnd: HWND) -> BOOL ---
+	IsWindowVisible :: proc(hwnd: HWND) -> BOOL ---
+	IsWindowEnabled :: proc(hwnd: HWND) -> BOOL ---
+	IsIconic :: proc(hwnd: HWND) -> BOOL ---
 	BringWindowToTop :: proc(hWnd: HWND) -> BOOL ---
 	GetTopWindow :: proc(hWnd: HWND) -> HWND ---
 	SetForegroundWindow :: proc(hWnd: HWND) -> BOOL ---
 	GetForegroundWindow :: proc() -> HWND ---
+	GetDesktopWindow :: proc() -> HWND ---
 	UpdateWindow :: proc(hWnd: HWND) -> BOOL ---
 	SetActiveWindow :: proc(hWnd: HWND) -> HWND ---
 	GetActiveWindow :: proc() -> HWND ---
 	RedrawWindow :: proc(hwnd: HWND, lprcUpdate: LPRECT, hrgnUpdate: HRGN, flags: RedrawWindowFlags) -> BOOL ---
-
+	SetParent :: proc(hWndChild: HWND, hWndNewParent: HWND) -> HWND ---
+	SetPropW :: proc(hWnd: HWND, lpString: LPCWSTR, hData: HANDLE) -> BOOL ---
+	GetPropW :: proc(hWnd: HWND, lpString: LPCWSTR) -> HANDLE ---
+	RemovePropW :: proc(hWnd: HWND, lpString: LPCWSTR) -> HANDLE ---
+	EnumPropsW :: proc(hWnd: HWND, lpEnumFunc: PROPENUMPROCW) -> INT ---
+	EnumPropsExW :: proc(hWnd: HWND, lpEnumFunc: PROPENUMPROCW, lParam: LPARAM) -> INT ---
 	GetMessageW :: proc(lpMsg: ^MSG, hWnd: HWND, wMsgFilterMin: UINT, wMsgFilterMax: UINT) -> BOOL ---
 
 	TranslateMessage :: proc(lpMsg: ^MSG) -> BOOL ---
 	DispatchMessageW :: proc(lpMsg: ^MSG) -> LRESULT ---
 
 	WaitMessage :: proc() -> BOOL ---
-	MsgWaitForMultipleObjects :: proc(nCount: DWORD, pHandles: ^HANDLE, fWaitAll: bool, dwMilliseconds: DWORD, dwWakeMask: DWORD) -> DWORD ---
+	MsgWaitForMultipleObjects :: proc(nCount: DWORD, pHandles: ^HANDLE, fWaitAll: BOOL, dwMilliseconds: DWORD, dwWakeMask: DWORD) -> DWORD ---
 
 	PeekMessageA :: proc(lpMsg: ^MSG, hWnd: HWND, wMsgFilterMin: UINT, wMsgFilterMax: UINT, wRemoveMsg: UINT) -> BOOL ---
 	PeekMessageW :: proc(lpMsg: ^MSG, hWnd: HWND, wMsgFilterMin: UINT, wMsgFilterMax: UINT, wRemoveMsg: UINT) -> BOOL ---
@@ -80,7 +85,7 @@ foreign user32 {
 	PostThreadMessageA :: proc(idThread: DWORD, Msg: UINT, wParam: WPARAM, lParam: LPARAM) -> BOOL ---
 	PostThreadMessageW :: proc(idThread: DWORD, Msg: UINT, wParam: WPARAM, lParam: LPARAM) -> BOOL ---
 
-	PostQuitMessage :: proc(nExitCode: c_int) ---
+	PostQuitMessage :: proc(nExitCode: INT) ---
 
 	GetQueueStatus :: proc(flags: UINT) -> DWORD ---
 
@@ -94,33 +99,26 @@ foreign user32 {
 
 	LoadIconA :: proc(hInstance: HINSTANCE, lpIconName: LPCSTR) -> HICON ---
 	LoadIconW :: proc(hInstance: HINSTANCE, lpIconName: LPCWSTR) -> HICON ---
+	GetIconInfoExW :: proc(hIcon: HICON, piconinfo: PICONINFOEXW) -> BOOL ---
 	LoadCursorA :: proc(hInstance: HINSTANCE, lpCursorName: LPCSTR) -> HCURSOR ---
 	LoadCursorW :: proc(hInstance: HINSTANCE, lpCursorName: LPCWSTR) -> HCURSOR ---
-	LoadImageW :: proc(hInst: HINSTANCE, name: LPCWSTR, type: UINT, cx: c_int, cy: c_int, fuLoad: UINT) -> HANDLE ---
+	LoadImageW :: proc(hInst: HINSTANCE, name: LPCWSTR, type: UINT, cx, cy: INT, fuLoad: UINT) -> HANDLE ---
 
-	CreateIcon :: proc(hInstance: HINSTANCE, nWidth: c_int, nHeight: c_int, cPlanes: BYTE, cBitsPixel: BYTE, lpbANDbits: PBYTE, lpbXORbits: PBYTE) -> HICON ---
+	CreateIcon :: proc(hInstance: HINSTANCE, nWidth, nHeight: INT, cPlanes: BYTE, cBitsPixel: BYTE, lpbANDbits: PBYTE, lpbXORbits: PBYTE) -> HICON ---
 	CreateIconFromResource :: proc(presbits: PBYTE, dwResSize: DWORD, fIcon: BOOL, dwVer: DWORD) -> HICON ---
 	DestroyIcon :: proc(hIcon: HICON) -> BOOL ---
-	DrawIcon :: proc(hDC: HDC, X: c_int, Y: c_int, hIcon: HICON) -> BOOL ---
+	DrawIcon :: proc(hDC: HDC, X, Y: INT, hIcon: HICON) -> BOOL ---
 
-	CreateCursor :: proc(hInst: HINSTANCE, xHotSpot: c_int, yHotSpot: c_int, nWidth: c_int, nHeight: c_int, pvANDPlane: PVOID, pvXORPlane: PVOID) -> HCURSOR ---
+	CreateCursor :: proc(hInst: HINSTANCE, xHotSpot, yHotSpot, nWidth, nHeight: INT, pvANDPlane: PVOID, pvXORPlane: PVOID) -> HCURSOR ---
 	DestroyCursor :: proc(hCursor: HCURSOR) -> BOOL ---
 
 	GetWindowRect :: proc(hWnd: HWND, lpRect: LPRECT) -> BOOL ---
 	GetClientRect :: proc(hWnd: HWND, lpRect: LPRECT) -> BOOL ---
 	ClientToScreen :: proc(hWnd: HWND, lpPoint: LPPOINT) -> BOOL ---
 	ScreenToClient :: proc(hWnd: HWND, lpPoint: LPPOINT) -> BOOL ---
-	SetWindowPos :: proc(
-		hWnd: HWND,
-		hWndInsertAfter: HWND,
-		X: c_int,
-		Y: c_int,
-		cx: c_int,
-		cy: c_int,
-		uFlags: UINT,
-	) -> BOOL ---
-	MoveWindow :: proc(hWnd: HWND, X, Y, hWidth, hHeight: c_int, bRepaint: BOOL) -> BOOL ---
-	GetSystemMetrics :: proc(nIndex: c_int) -> c_int ---
+	SetWindowPos :: proc(hWnd: HWND, hWndInsertAfter: HWND, X, Y, cx, cy: INT, uFlags: UINT) -> BOOL ---
+	MoveWindow :: proc(hWnd: HWND, X, Y, hWidth, hHeight: INT, bRepaint: BOOL) -> BOOL ---
+	GetSystemMetrics :: proc(nIndex: INT) -> INT ---
 	AdjustWindowRect :: proc(lpRect: LPRECT, dwStyle: DWORD, bMenu: BOOL) -> BOOL ---
 	AdjustWindowRectEx :: proc(lpRect: LPRECT, dwStyle: DWORD, bMenu: BOOL, dwExStyle: DWORD) -> BOOL ---
 	AdjustWindowRectExForDpi :: proc(lpRect: LPRECT, dwStyle: DWORD, bMenu: BOOL, dwExStyle: DWORD, dpi: UINT) -> BOOL ---
@@ -130,18 +128,36 @@ foreign user32 {
 
 	GetWindowDC :: proc(hWnd: HWND) -> HDC ---
 	GetDC :: proc(hWnd: HWND) -> HDC ---
-	ReleaseDC :: proc(hWnd: HWND, hDC: HDC) -> c_int ---
+	GetDCEx :: proc(hWnd: HWND, hrgnClip: HRGN, flags: DWORD) -> HDC ---
+	ReleaseDC :: proc(hWnd: HWND, hDC: HDC) -> INT ---
 
-	GetDlgCtrlID :: proc(hWnd: HWND) -> c_int ---
-	GetDlgItem :: proc(hDlg: HWND, nIDDlgItem: c_int) -> HWND ---
+	GetDlgCtrlID :: proc(hWnd: HWND) -> INT ---
+	GetDlgItem :: proc(hDlg: HWND, nIDDlgItem: INT) -> HWND ---
 
+	CreateMenu :: proc() -> HMENU ---
 	CreatePopupMenu :: proc() -> HMENU ---
+	DeleteMenu :: proc(hMenu: HMENU, uPosition: UINT, uFlags: UINT) -> BOOL ---
 	DestroyMenu :: proc(hMenu: HMENU) -> BOOL ---
+	InsertMenuW :: proc(hMenu: HMENU, uPosition: UINT, uFlags: UINT, uIDNewItem: UINT_PTR, lpNewItem: LPCWSTR) -> BOOL ---
 	AppendMenuW :: proc(hMenu: HMENU, uFlags: UINT, uIDNewItem: UINT_PTR, lpNewItem: LPCWSTR) -> BOOL ---
+	GetMenu :: proc(hWnd: HWND) -> HMENU ---
 	SetMenu :: proc(hWnd: HWND, hMenu: HMENU) -> BOOL ---
-	TrackPopupMenu :: proc(hMenu: HMENU, uFlags: UINT, x: int, y: int, nReserved: int, hWnd: HWND, prcRect: ^RECT) -> i32 ---
+	TrackPopupMenu :: proc(hMenu: HMENU, uFlags: UINT, x, y: INT, nReserved: INT, hWnd: HWND, prcRect: ^RECT) -> BOOL ---
 	RegisterWindowMessageW :: proc(lpString: LPCWSTR) -> UINT ---
 
+	CreateAcceleratorTableW :: proc(paccel: LPACCEL, cAccel: INT) -> HACCEL ---
+	DestroyAcceleratorTable :: proc(hAccel: HACCEL) -> BOOL ---
+	LoadAcceleratorsW :: proc(hInstance: HINSTANCE, lpTableName: LPCWSTR) -> HACCEL ---
+	TranslateAcceleratorW :: proc(hWnd: HWND, hAccTable: HACCEL, lpMsg: LPMSG) -> INT ---
+	CopyAcceleratorTableW :: proc(hAccelSrc: HACCEL, lpAccelDst: LPACCEL, cAccelEntries: INT) -> INT ---
+
+	InsertMenuItemW :: proc(hmenu: HMENU, item: UINT, fByPosition: BOOL, lpmi: LPMENUITEMINFOW) -> BOOL ---
+	GetMenuItemInfoW :: proc(hmenu: HMENU, item: UINT, fByPosition: BOOL, lpmii: LPMENUITEMINFOW) -> BOOL ---
+	SetMenuItemInfoW :: proc(hmenu: HMENU, item: UINT, fByPositon: BOOL, lpmii: LPMENUITEMINFOW) -> BOOL ---
+	GetMenuDefaultItem :: proc(hMenu: HMENU, fByPos: UINT, gmdiFlags: UINT) -> UINT ---
+	SetMenuDefaultItem :: proc(hMenu: HMENU, uItem: UINT, fByPos: UINT) -> BOOL ---
+	GetMenuItemRect :: proc(hWnd: HWND, hMenu: HMENU, uItem: UINT, lprcItem: LPRECT) -> c_int ---
+
 	GetUpdateRect :: proc(hWnd: HWND, lpRect: LPRECT, bErase: BOOL) -> BOOL ---
 	ValidateRect :: proc(hWnd: HWND, lpRect: ^RECT) -> BOOL ---
 	InvalidateRect :: proc(hWnd: HWND, lpRect: ^RECT, bErase: BOOL) -> BOOL ---
@@ -154,34 +170,35 @@ foreign user32 {
 	ReleaseCapture :: proc() -> BOOL ---
 	TrackMouseEvent :: proc(lpEventTrack: LPTRACKMOUSEEVENT) -> BOOL ---
 
-	GetKeyState :: proc(nVirtKey: c_int) -> SHORT ---
-	GetAsyncKeyState :: proc(vKey: c_int) -> SHORT ---
+	GetKeyState :: proc(nVirtKey: INT) -> SHORT ---
+	GetAsyncKeyState :: proc(vKey: INT) -> SHORT ---
 
 	GetKeyboardState :: proc(lpKeyState: PBYTE) -> BOOL ---
 
 	MapVirtualKeyW :: proc(uCode: UINT, uMapType: UINT) -> UINT ---
-	ToUnicode :: proc(nVirtKey: UINT, wScanCode: UINT, lpKeyState: ^BYTE, pwszBuff: LPWSTR, cchBuff: c_int, wFlags: UINT) -> c_int ---
+	ToUnicode :: proc(nVirtKey: UINT, wScanCode: UINT, lpKeyState: ^BYTE, pwszBuff: LPWSTR, cchBuff: INT, wFlags: UINT) -> INT ---
 
-	SetWindowsHookExW :: proc(idHook: c_int, lpfn: HOOKPROC, hmod: HINSTANCE, dwThreadId: DWORD) -> HHOOK ---
+	SetWindowsHookExW :: proc(idHook: INT, lpfn: HOOKPROC, hmod: HINSTANCE, dwThreadId: DWORD) -> HHOOK ---
 	UnhookWindowsHookEx :: proc(hhk: HHOOK) -> BOOL ---
-	CallNextHookEx :: proc(hhk: HHOOK, nCode: c_int, wParam: WPARAM, lParam: LPARAM) -> LRESULT ---
+	CallNextHookEx :: proc(hhk: HHOOK, nCode: INT, wParam: WPARAM, lParam: LPARAM) -> LRESULT ---
 
 	SetTimer :: proc(hWnd: HWND, nIDEvent: UINT_PTR, uElapse: UINT, lpTimerFunc: TIMERPROC) -> UINT_PTR ---
 	KillTimer :: proc(hWnd: HWND, uIDEvent: UINT_PTR) -> BOOL ---
 
-	// MessageBoxA :: proc(hWnd: HWND, lpText: LPCSTR, lpCaption: LPCSTR, uType: UINT) -> c_int ---
-	MessageBoxW :: proc(hWnd: HWND, lpText: LPCWSTR, lpCaption: LPCWSTR, uType: UINT) -> c_int ---
-	// MessageBoxExA :: proc(hWnd: HWND, lpText: LPCSTR, lpCaption: LPCSTR, uType: UINT, wLanguageId: WORD) -> c_int ---
-	MessageBoxExW :: proc(hWnd: HWND, lpText: LPCWSTR, lpCaption: LPCWSTR, uType: UINT, wLanguageId: WORD) -> c_int ---
+	// MessageBoxA :: proc(hWnd: HWND, lpText: LPCSTR, lpCaption: LPCSTR, uType: UINT) -> INT ---
+	MessageBoxW :: proc(hWnd: HWND, lpText: LPCWSTR, lpCaption: LPCWSTR, uType: UINT) -> INT ---
+	// MessageBoxExA :: proc(hWnd: HWND, lpText: LPCSTR, lpCaption: LPCSTR, uType: UINT, wLanguageId: WORD) -> INT ---
+	MessageBoxExW :: proc(hWnd: HWND, lpText: LPCWSTR, lpCaption: LPCWSTR, uType: UINT, wLanguageId: WORD) -> INT ---
 
 	ClipCursor :: proc(lpRect: LPRECT) -> BOOL ---
 	GetCursorPos :: proc(lpPoint: LPPOINT) -> BOOL ---
-	SetCursorPos :: proc(X: c_int, Y: c_int) -> BOOL ---
+	SetCursorPos :: proc(X, Y: INT) -> BOOL ---
 	SetCursor :: proc(hCursor: HCURSOR) -> HCURSOR ---
 	when !intrinsics.is_package_imported("raylib") {
 		ShowCursor :: proc(bShow: BOOL) -> INT ---
 	}
 
+	EnumDisplayDevicesW :: proc (lpDevice: LPCWSTR, iDevNum: DWORD, lpDisplayDevice: PDISPLAY_DEVICEW, dwFlags: DWORD) -> BOOL ---
 	EnumDisplaySettingsW :: proc(lpszDeviceName: LPCWSTR, iModeNum: DWORD, lpDevMode: ^DEVMODEW) -> BOOL ---
 
 	MonitorFromPoint  :: proc(pt: POINT, dwFlags: Monitor_From_Flags) -> HMONITOR ---
@@ -191,6 +208,9 @@ foreign user32 {
 
 	EnumWindows :: proc(lpEnumFunc: Window_Enum_Proc, lParam: LPARAM) -> BOOL ---
 
+	IsProcessDPIAware :: proc() -> BOOL ---
+	SetProcessDPIAware :: proc() -> BOOL ---
+
 	SetThreadDpiAwarenessContext :: proc(dpiContext: DPI_AWARENESS_CONTEXT) -> DPI_AWARENESS_CONTEXT ---
 	GetThreadDpiAwarenessContext :: proc() -> DPI_AWARENESS_CONTEXT ---
 	GetWindowDpiAwarenessContext :: proc(hwnd: HWND) -> DPI_AWARENESS_CONTEXT ---
@@ -225,14 +245,14 @@ foreign user32 {
 		lpdwResult: PDWORD_PTR,
 	) -> LRESULT ---
 
-	GetSysColor :: proc(nIndex: c_int) -> DWORD ---
-	GetSysColorBrush :: proc(nIndex: c_int) -> HBRUSH ---
-	SetSysColors :: proc(cElements: c_int, lpaElements: ^INT, lpaRgbValues: ^COLORREF) -> BOOL ---
+	GetSysColor :: proc(nIndex: INT) -> DWORD ---
+	GetSysColorBrush :: proc(nIndex: INT) -> HBRUSH ---
+	SetSysColors :: proc(cElements: INT, lpaElements: ^INT, lpaRgbValues: ^COLORREF) -> BOOL ---
 	MessageBeep :: proc(uType: UINT) -> BOOL ---
 
 	IsDialogMessageW :: proc(hDlg: HWND, lpMsg: LPMSG) -> BOOL ---
-	GetWindowTextLengthW :: proc(hWnd: HWND) -> c_int ---
-	GetWindowTextW :: proc(hWnd: HWND, lpString: LPWSTR, nMaxCount: c_int) -> c_int ---
+	GetWindowTextLengthW :: proc(hWnd: HWND) -> INT ---
+	GetWindowTextW :: proc(hWnd: HWND, lpString: LPWSTR, nMaxCount: INT) -> INT ---
 	SetWindowTextW :: proc(hWnd: HWND, lpString: LPCWSTR) -> BOOL ---
 	CallWindowProcW :: proc(lpPrevWndFunc: WNDPROC, hWnd: HWND, Msg: UINT, wParam: WPARAM, lParam: LPARAM) -> LRESULT ---
 	EnableWindow :: proc(hWnd: HWND, bEnable: BOOL) -> BOOL ---
@@ -245,12 +265,20 @@ foreign user32 {
 	GetRegisteredRawInputDevices :: proc(pRawInputDevices: PRAWINPUTDEVICE, puiNumDevices: PUINT, cbSize: UINT) -> UINT ---
 	RegisterRawInputDevices :: proc(pRawInputDevices: PCRAWINPUTDEVICE, uiNumDevices: UINT, cbSize: UINT) -> BOOL ---
 
-	SendInput :: proc(cInputs: UINT, pInputs: [^]INPUT, cbSize: c_int) -> UINT ---
+	SendInput :: proc(cInputs: UINT, pInputs: [^]INPUT, cbSize: INT) -> UINT ---
 
 	SetLayeredWindowAttributes  :: proc(hWnd: HWND, crKey: COLORREF, bAlpha: BYTE, dwFlags: DWORD) -> BOOL ---
 
 	FillRect :: proc(hDC: HDC, lprc: ^RECT, hbr: HBRUSH) -> int ---
-	EqualRect :: proc(lprc1: ^RECT, lprc2: ^RECT) -> BOOL ---
+	EqualRect :: proc(lprc1, lprc2: ^RECT) -> BOOL ---
+	OffsetRect :: proc(lprc1: ^RECT, dx, dy: INT) -> BOOL ---
+	InflateRect :: proc(lprc1: ^RECT, dx, dy: INT) -> BOOL ---
+	IntersectRect :: proc(lprcDst, lprcSrc1, lprcSrc2: ^RECT) -> BOOL ---
+	SubtractRect :: proc(lprcDst, lprcSrc1, lprcSrc2: ^RECT) -> BOOL ---
+	UnionRect :: proc(lprcDst, lprcSrc1, lprcSrc2: ^RECT) -> BOOL ---
+	IsRectEmpty :: proc(lprc: ^RECT) -> BOOL ---
+	SetRectEmpty :: proc(lprc: ^RECT) -> BOOL ---
+	CopyRect :: proc(lprcDst, lprcSrc: ^RECT) -> BOOL ---
 
 	GetWindowInfo :: proc(hwnd: HWND, pwi: PWINDOWINFO) -> BOOL ---
 	GetWindowPlacement :: proc(hWnd: HWND, lpwndpl: ^WINDOWPLACEMENT) -> BOOL ---
@@ -259,21 +287,34 @@ foreign user32 {
 	CreateRectRgnIndirect :: proc(lprect: ^RECT) -> HRGN ---
 	GetSystemMetricsForDpi :: proc(nIndex: int, dpi: UINT) -> int ---
 
+	GetCursorInfo :: proc(pci: PCURSORINFO) -> BOOL ---
+
 	GetSystemMenu :: proc(hWnd: HWND, bRevert: BOOL) -> HMENU ---
 	EnableMenuItem :: proc(hMenu: HMENU, uIDEnableItem: UINT, uEnable: UINT) -> BOOL ---
+	MenuItemFromPoint :: proc(hWnd: HWND, hMenu: HMENU, ptScreen: POINT) -> INT ---
 
 	DrawTextW :: proc(hdc: HDC, lpchText: LPCWSTR, cchText: INT, lprc: LPRECT, format: DrawTextFormat) -> INT ---
 	DrawTextExW :: proc(hdc: HDC, lpchText: LPCWSTR, cchText: INT, lprc: LPRECT, format: DrawTextFormat, lpdtp: PDRAWTEXTPARAMS) -> INT ---
+
+	GetLocaleInfoEx :: proc(lpLocaleName: LPCWSTR, LCType: LCTYPE, lpLCData: LPWSTR, cchData: INT) -> INT ---
+	IsValidLocaleName :: proc(lpLocaleName: LPCWSTR) -> BOOL ---
+	ResolveLocaleName :: proc(lpNameToResolve: LPCWSTR, lpLocaleName: LPWSTR, cchLocaleName: INT) -> INT ---
+	IsValidCodePage :: proc(CodePage: UINT) -> BOOL ---
+	GetACP :: proc() -> CODEPAGE ---
+	GetCPInfoExW :: proc(CodePage: CODEPAGE, dwFlags: DWORD, lpCPInfoEx: LPCPINFOEXW) -> BOOL ---
+
+	GetProcessWindowStation :: proc() -> HWINSTA ---
+	GetUserObjectInformationW :: proc(hObj: HANDLE, nIndex: GetUserObjectInformationFlags, pvInfo: PVOID, nLength: DWORD, lpnLengthNeeded: LPDWORD) -> BOOL ---
 }
 
 CreateWindowW :: #force_inline proc "system" (
 	lpClassName: LPCTSTR,
 	lpWindowName: LPCTSTR,
 	dwStyle: DWORD,
-	X: c_int,
-	Y: c_int,
-	nWidth: c_int,
-	nHeight: c_int,
+	X: INT,
+	Y: INT,
+	nWidth: INT,
+	nHeight: INT,
 	hWndParent: HWND,
 	hMenu: HMENU,
 	hInstance: HINSTANCE,
@@ -298,11 +339,11 @@ CreateWindowW :: #force_inline proc "system" (
 when ODIN_ARCH == .amd64 {
 	@(default_calling_convention="system")
 	foreign user32 {
-		GetClassLongPtrW :: proc(hWnd: HWND, nIndex: c_int) -> ULONG_PTR ---
-		SetClassLongPtrW :: proc(hWnd: HWND, nIndex: c_int, dwNewLong: LONG_PTR) -> ULONG_PTR ---
+		GetClassLongPtrW :: proc(hWnd: HWND, nIndex: INT) -> ULONG_PTR ---
+		SetClassLongPtrW :: proc(hWnd: HWND, nIndex: INT, dwNewLong: LONG_PTR) -> ULONG_PTR ---
 
-		GetWindowLongPtrW :: proc(hWnd: HWND, nIndex: c_int) -> LONG_PTR ---
-		SetWindowLongPtrW :: proc(hWnd: HWND, nIndex: c_int, dwNewLong: LONG_PTR) -> LONG_PTR ---
+		GetWindowLongPtrW :: proc(hWnd: HWND, nIndex: INT) -> LONG_PTR ---
+		SetWindowLongPtrW :: proc(hWnd: HWND, nIndex: INT, dwNewLong: LONG_PTR) -> LONG_PTR ---
 	}
 } else when ODIN_ARCH == .i386 {
 	GetClassLongPtrW :: GetClassLongW
@@ -312,8 +353,8 @@ when ODIN_ARCH == .amd64 {
 	SetWindowLongPtrW :: SetWindowLongW
 }
 
-GET_SC_WPARAM :: #force_inline proc "contextless" (wParam: WPARAM) -> c_int {
-	return c_int(wParam) & 0xFFF0
+GET_SC_WPARAM :: #force_inline proc "contextless" (wParam: WPARAM) -> INT {
+	return INT(wParam) & 0xFFF0
 }
 
 GET_WHEEL_DELTA_WPARAM :: #force_inline proc "contextless" (wParam: WPARAM) -> c_short {
@@ -332,6 +373,10 @@ GET_XBUTTON_WPARAM :: #force_inline proc "contextless" (wParam: WPARAM) -> WORD
 	return HIWORD(cast(DWORD)wParam)
 }
 
+GET_RAWINPUT_CODE_WPARAM :: #force_inline proc "contextless" (wParam: WPARAM) -> BYTE {
+	return BYTE(wParam) & 0xFF
+}
+
 MAKEINTRESOURCEW :: #force_inline proc "contextless" (#any_int i: int) -> LPWSTR {
 	return cast(LPWSTR)uintptr(WORD(i))
 }
@@ -512,8 +557,8 @@ WINDOWPLACEMENT :: struct {
 	flags: UINT,
 	showCmd: UINT,
 	ptMinPosition: POINT,
-  	ptMaxPosition: POINT,
-  	rcNormalPosition: RECT,
+	ptMaxPosition: POINT,
+	rcNormalPosition: RECT,
 }
 
 WINDOWINFO :: struct {
@@ -530,11 +575,20 @@ WINDOWINFO :: struct {
 }
 PWINDOWINFO :: ^WINDOWINFO
 
+CURSORINFO :: struct {
+	cbSize: DWORD,
+	flags: DWORD,
+	hCursor: HCURSOR,
+	ptScreenPos: POINT,
+}
+PCURSORINFO :: ^CURSORINFO
+
+
 DRAWTEXTPARAMS :: struct {
 	cbSize: UINT,
-	iTabLength: int,
-	iLeftMargin: int,
-	iRightMargin: int,
+	iTabLength: INT,
+	iLeftMargin: INT,
+	iRightMargin: INT,
 	uiLengthDrawn: UINT,
 }
 PDRAWTEXTPARAMS :: ^DRAWTEXTPARAMS
@@ -581,11 +635,103 @@ RedrawWindowFlags :: enum UINT {
 	RDW_NOFRAME         = 0x0800,
 }
 
+GetUserObjectInformationFlags :: enum INT {
+	UOI_FLAGS      = 1,
+	UOI_NAME       = 2,
+	UOI_TYPE       = 3,
+	UOI_USER_SID   = 4,
+	UOI_HEAPSIZE   = 5,
+	UOI_IO         = 6,
+	UOI_TIMERPROC_EXCEPTION_SUPPRESSION = 7,
+}
+
+USEROBJECTFLAGS :: struct  {
+	fInherit: BOOL,
+	fReserved: BOOL,
+	dwFlags: DWORD,
+}
+
+PROPENUMPROCW :: #type proc(unnamedParam1: HWND, unnamedParam2: LPCWSTR, unnamedParam3: HANDLE) -> BOOL
+PROPENUMPROCEXW :: #type proc(unnamedParam1: HWND, unnamedParam2: LPCWSTR, unnamedParam3: HANDLE, unnamedParam4: ULONG_PTR) -> BOOL
+
+RT_CURSOR       :: LPWSTR(uintptr(0x00000001))
+RT_BITMAP       :: LPWSTR(uintptr(0x00000002))
+RT_ICON         :: LPWSTR(uintptr(0x00000003))
+RT_MENU         :: LPWSTR(uintptr(0x00000004))
+RT_DIALOG       :: LPWSTR(uintptr(0x00000005))
+RT_STRING       :: LPWSTR(uintptr(0x00000006))
+RT_FONTDIR      :: LPWSTR(uintptr(0x00000007))
+RT_FONT         :: LPWSTR(uintptr(0x00000008))
+RT_ACCELERATOR  :: LPWSTR(uintptr(0x00000009))
+RT_RCDATA       :: LPWSTR(uintptr(0x0000000A))
+RT_MESSAGETABLE :: LPWSTR(uintptr(0x0000000B))
+RT_GROUP_CURSOR :: LPWSTR(uintptr(0x0000000C))
+RT_GROUP_ICON   :: LPWSTR(uintptr(0x0000000E))
+RT_VERSION      :: LPWSTR(uintptr(0x00000010))
+RT_DLGINCLUDE   :: LPWSTR(uintptr(0x00000011))
+RT_PLUGPLAY     :: LPWSTR(uintptr(0x00000013))
+RT_VXD          :: LPWSTR(uintptr(0x00000014))
+RT_ANICURSOR    :: LPWSTR(uintptr(0x00000015))
+RT_ANIICON      :: LPWSTR(uintptr(0x00000016))
+RT_MANIFEST     :: LPWSTR(uintptr(0x00000018))
+
+CREATEPROCESS_MANIFEST_RESOURCE_ID                 :: LPWSTR(uintptr(0x00000001))
+ISOLATIONAWARE_MANIFEST_RESOURCE_ID                :: LPWSTR(uintptr(0x00000002))
+ISOLATIONAWARE_NOSTATICIMPORT_MANIFEST_RESOURCE_ID :: LPWSTR(uintptr(0x00000003))
+ISOLATIONPOLICY_MANIFEST_RESOURCE_ID               :: LPWSTR(uintptr(0x00000004))
+ISOLATIONPOLICY_BROWSER_MANIFEST_RESOURCE_ID       :: LPWSTR(uintptr(0x00000005))
+MINIMUM_RESERVED_MANIFEST_RESOURCE_ID              :: LPWSTR(uintptr(0x00000001))
+MAXIMUM_RESERVED_MANIFEST_RESOURCE_ID              :: LPWSTR(uintptr(0x00000010))
+
+ACCEL :: struct {
+	/* Also called the flags field */
+	fVirt: BYTE,
+	key: WORD,
+	cmd: WORD,
+}
+LPACCEL :: ^ACCEL
+
+MIIM_STATE      :: 0x00000001
+MIIM_ID         :: 0x00000002
+MIIM_SUBMENU    :: 0x00000004
+MIIM_CHECKMARKS :: 0x00000008
+MIIM_TYPE       :: 0x00000010
+MIIM_DATA       :: 0x00000020
+
+MIIM_STRING :: 0x00000040
+MIIM_BITMAP :: 0x00000080
+MIIM_FTYPE  :: 0x00000100
+
+MENUITEMINFOW :: struct {
+	cbSize: UINT,
+	fMask: UINT,
+	fType: UINT,         // used if MIIM_TYPE (4.0) or MIIM_FTYPE (>4.0)
+	fState: UINT,        // used if MIIM_STATE
+	wID: UINT,           // used if MIIM_ID
+	hSubMenu: HMENU,      // used if MIIM_SUBMENU
+	hbmpChecked: HBITMAP,   // used if MIIM_CHECKMARKS
+	hbmpUnchecked: HBITMAP, // used if MIIM_CHECKMARKS
+	dwItemData: ULONG_PTR,   // used if MIIM_DATA
+	dwTypeData: LPWSTR,    // used if MIIM_TYPE (4.0) or MIIM_STRING (>4.0)
+	cch: UINT,           // used if MIIM_TYPE (4.0) or MIIM_STRING (>4.0)
+	hbmpItem: HBITMAP,      // used if MIIM_BITMAP
+}
+LPMENUITEMINFOW :: ^MENUITEMINFOW
+DISPLAY_DEVICEW :: struct {
+	cb: DWORD,
+	DeviceName: [32]WCHAR,
+	DeviceString: [128]WCHAR,
+	StateFlags: DWORD,
+	DeviceID: [128]WCHAR,
+	DeviceKey: [128]WCHAR,
+}
+PDISPLAY_DEVICEW :: ^DISPLAY_DEVICEW
+
 // OUTOFCONTEXT is the zero value, use {}
 WinEventFlags :: bit_set[WinEventFlag; DWORD]
 
 WinEventFlag :: enum DWORD {
-    SKIPOWNTHREAD  = 0,
-    SKIPOWNPROCESS = 1,
-    INCONTEXT      = 2,
+	SKIPOWNTHREAD  = 0,
+	SKIPOWNPROCESS = 1,
+	INCONTEXT      = 2,
 }

+ 59 - 2
core/sys/windows/util.odin

@@ -6,22 +6,79 @@ import "base:intrinsics"
 
 L :: intrinsics.constant_utf16_cstring
 
-LOWORD :: #force_inline proc "contextless" (x: DWORD) -> WORD {
+// https://learn.microsoft.com/en-us/windows/win32/winmsg/makeword
+MAKEWORD :: #force_inline proc "contextless" (#any_int a, b: int) -> WORD {
+	return WORD(BYTE(DWORD_PTR(a) & 0xff)) | (WORD(BYTE(DWORD_PTR(b) & 0xff)) << 8)
+}
+
+// https://learn.microsoft.com/en-us/windows/win32/winmsg/makelong
+MAKELONG :: #force_inline proc "contextless" (#any_int a, b: int) -> LONG {
+	return LONG(WORD(DWORD_PTR(a) & 0xffff)) | (LONG(WORD(DWORD_PTR(b) & 0xffff)) << 16)
+}
+
+// https://learn.microsoft.com/en-us/windows/win32/winmsg/loword
+LOWORD :: #force_inline proc "contextless" (#any_int x: int) -> WORD {
 	return WORD(x & 0xffff)
 }
 
-HIWORD :: #force_inline proc "contextless" (x: DWORD) -> WORD {
+// https://learn.microsoft.com/en-us/windows/win32/winmsg/hiword
+HIWORD :: #force_inline proc "contextless" (#any_int x: int) -> WORD {
 	return WORD(x >> 16)
 }
 
+// https://learn.microsoft.com/en-us/windows/win32/winmsg/lobyte
+LOBYTE :: #force_inline proc "contextless" (w: WORD) -> BYTE {
+	return BYTE((DWORD_PTR(w)) & 0xff)
+}
+
+// https://learn.microsoft.com/en-us/windows/win32/winmsg/hibyte
+HIBYTE :: #force_inline proc "contextless" (w: WORD) -> BYTE {
+	return BYTE(((DWORD_PTR(w)) >> 8) & 0xff)
+}
+
+// https://learn.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-makewparam
+MAKEWPARAM :: #force_inline proc "contextless" (#any_int l, h: int) -> WPARAM {
+	return WPARAM(MAKELONG(l, h))
+}
+
+// https://learn.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-makelparam
+MAKELPARAM :: #force_inline proc "contextless" (#any_int l, h: int) -> LPARAM {
+	return LPARAM(MAKELONG(l, h))
+}
+
+// https://learn.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-makelresult
+MAKELRESULT :: #force_inline proc "contextless" (#any_int l, h: int) -> LRESULT {
+	return LRESULT(MAKELONG(l, h))
+}
+
+// https://learn.microsoft.com/en-us/windows/win32/api/windowsx/nf-windowsx-get_x_lparam
 GET_X_LPARAM :: #force_inline proc "contextless" (lp: LPARAM) -> c_int {
 	return cast(c_int)cast(c_short)LOWORD(cast(DWORD)lp)
 }
 
+// https://learn.microsoft.com/en-us/windows/win32/api/windowsx/nf-windowsx-get_y_lparam
 GET_Y_LPARAM :: #force_inline proc "contextless" (lp: LPARAM) -> c_int {
 	return cast(c_int)cast(c_short)HIWORD(cast(DWORD)lp)
 }
 
+// https://learn.microsoft.com/en-us/windows/win32/api/winnt/nf-winnt-makelcid
+MAKELCID :: #force_inline proc "contextless" (lgid, srtid: WORD) -> LCID {
+	return (DWORD(WORD(srtid)) << 16) | DWORD(WORD(lgid))
+}
+
+// https://learn.microsoft.com/en-us/windows/win32/api/winnt/nf-winnt-makelangid
+MAKELANGID :: #force_inline proc "contextless" (p, s: WORD) -> DWORD {
+	return DWORD(WORD(s)) << 10 | DWORD(WORD(p))
+}
+
+LANGIDFROMLCID :: #force_inline proc "contextless" (lcid: LCID) -> LANGID {
+	return LANGID(lcid)
+}
+
+// this one gave me trouble as it do not mask the values.
+// the _ in the name is also off comparing to the c code
+// i can't find any usage in the odin repo
+@(deprecated = "use MAKEWORD")
 MAKE_WORD :: #force_inline proc "contextless" (x, y: WORD) -> WORD {
 	return x << 8 | y
 }

+ 227 - 3
core/sys/windows/winerror.odin

@@ -1,6 +1,189 @@
 // +build windows
 package sys_windows
 
+// https://learn.microsoft.com/en-us/windows/win32/api/winerror/
+
+//  Values are 32 bit values laid out as follows:
+//
+//   3 3 2 2 2 2 2 2 2 2 2 2 1 1 1 1 1 1 1 1 1 1
+//   1 0 9 8 7 6 5 4 3 2 1 0 9 8 7 6 5 4 3 2 1 0 9 8 7 6 5 4 3 2 1 0
+//  +---+-+-+-----------------------+-------------------------------+
+//  |Sev|C|R|     Facility          |               Code            |
+//  +---+-+-+-----------------------+-------------------------------+
+//
+//  where
+//
+//      Sev - is the severity code
+//
+//          00 - Success
+//          01 - Informational
+//          10 - Warning
+//          11 - Error
+//
+//      C - is the Customer code flag
+//
+//      R - is a reserved bit
+//
+//      Facility - is the facility code
+//
+//      Code - is the facility's status code
+
+// Define the facility codes
+FACILITY :: enum DWORD {
+	NULL                                     = 0,
+	RPC                                      = 1,
+	DISPATCH                                 = 2,
+	STORAGE                                  = 3,
+	ITF                                      = 4,
+	WIN32                                    = 7,
+	WINDOWS                                  = 8,
+	SSPI                                     = 9,
+	SECURITY                                 = 9,
+	CONTROL                                  = 10,
+	CERT                                     = 11,
+	INTERNET                                 = 12,
+	MEDIASERVER                              = 13,
+	MSMQ                                     = 14,
+	SETUPAPI                                 = 15,
+	SCARD                                    = 16,
+	COMPLUS                                  = 17,
+	AAF                                      = 18,
+	URT                                      = 19,
+	ACS                                      = 20,
+	DPLAY                                    = 21,
+	UMI                                      = 22,
+	SXS                                      = 23,
+	WINDOWS_CE                               = 24,
+	HTTP                                     = 25,
+	USERMODE_COMMONLOG                       = 26,
+	WER                                      = 27,
+	USERMODE_FILTER_MANAGER                  = 31,
+	BACKGROUNDCOPY                           = 32,
+	CONFIGURATION                            = 33,
+	WIA                                      = 33,
+	STATE_MANAGEMENT                         = 34,
+	METADIRECTORY                            = 35,
+	WINDOWSUPDATE                            = 36,
+	DIRECTORYSERVICE                         = 37,
+	GRAPHICS                                 = 38,
+	SHELL                                    = 39,
+	NAP                                      = 39,
+	TPM_SERVICES                             = 40,
+	TPM_SOFTWARE                             = 41,
+	UI                                       = 42,
+	XAML                                     = 43,
+	ACTION_QUEUE                             = 44,
+	PLA                                      = 48,
+	WINDOWS_SETUP                            = 48,
+	FVE                                      = 49,
+	FWP                                      = 50,
+	WINRM                                    = 51,
+	NDIS                                     = 52,
+	USERMODE_HYPERVISOR                      = 53,
+	CMI                                      = 54,
+	USERMODE_VIRTUALIZATION                  = 55,
+	USERMODE_VOLMGR                          = 56,
+	BCD                                      = 57,
+	USERMODE_VHD                             = 58,
+	USERMODE_HNS                             = 59,
+	SDIAG                                    = 60,
+	WEBSERVICES                              = 61,
+	WINPE                                    = 61,
+	WPN                                      = 62,
+	WINDOWS_STORE                            = 63,
+	INPUT                                    = 64,
+	QUIC                                     = 65,
+	EAP                                      = 66,
+	IORING                                   = 70,
+	WINDOWS_DEFENDER                         = 80,
+	OPC                                      = 81,
+	XPS                                      = 82,
+	MBN                                      = 84,
+	POWERSHELL                               = 84,
+	RAS                                      = 83,
+	P2P_INT                                  = 98,
+	P2P                                      = 99,
+	DAF                                      = 100,
+	BLUETOOTH_ATT                            = 101,
+	AUDIO                                    = 102,
+	STATEREPOSITORY                          = 103,
+	VISUALCPP                                = 109,
+	SCRIPT                                   = 112,
+	PARSE                                    = 113,
+	BLB                                      = 120,
+	BLB_CLI                                  = 121,
+	WSBAPP                                   = 122,
+	BLBUI                                    = 128,
+	USN                                      = 129,
+	USERMODE_VOLSNAP                         = 130,
+	TIERING                                  = 131,
+	WSB_ONLINE                               = 133,
+	ONLINE_ID                                = 134,
+	DEVICE_UPDATE_AGENT                      = 135,
+	DRVSERVICING                             = 136,
+	DLS                                      = 153,
+	DELIVERY_OPTIMIZATION                    = 208,
+	USERMODE_SPACES                          = 231,
+	USER_MODE_SECURITY_CORE                  = 232,
+	USERMODE_LICENSING                       = 234,
+	SOS                                      = 160,
+	OCP_UPDATE_AGENT                         = 173,
+	DEBUGGERS                                = 176,
+	SPP                                      = 256,
+	RESTORE                                  = 256,
+	DMSERVER                                 = 256,
+	DEPLOYMENT_SERVICES_SERVER               = 257,
+	DEPLOYMENT_SERVICES_IMAGING              = 258,
+	DEPLOYMENT_SERVICES_MANAGEMENT           = 259,
+	DEPLOYMENT_SERVICES_UTIL                 = 260,
+	DEPLOYMENT_SERVICES_BINLSVC              = 261,
+	DEPLOYMENT_SERVICES_PXE                  = 263,
+	DEPLOYMENT_SERVICES_TFTP                 = 264,
+	DEPLOYMENT_SERVICES_TRANSPORT_MANAGEMENT = 272,
+	DEPLOYMENT_SERVICES_DRIVER_PROVISIONING  = 278,
+	DEPLOYMENT_SERVICES_MULTICAST_SERVER     = 289,
+	DEPLOYMENT_SERVICES_MULTICAST_CLIENT     = 290,
+	DEPLOYMENT_SERVICES_CONTENT_PROVIDER     = 293,
+	HSP_SERVICES                             = 296,
+	HSP_SOFTWARE                             = 297,
+	LINGUISTIC_SERVICES                      = 305,
+	AUDIOSTREAMING                           = 1094,
+	TTD                                      = 1490,
+	ACCELERATOR                              = 1536,
+	WMAAECMA                                 = 1996,
+	DIRECTMUSIC                              = 2168,
+	DIRECT3D10                               = 2169,
+	DXGI                                     = 2170,
+	DXGI_DDI                                 = 2171,
+	DIRECT3D11                               = 2172,
+	DIRECT3D11_DEBUG                         = 2173,
+	DIRECT3D12                               = 2174,
+	DIRECT3D12_DEBUG                         = 2175,
+	DXCORE                                   = 2176,
+	PRESENTATION                             = 2177,
+	LEAP                                     = 2184,
+	AUDCLNT                                  = 2185,
+	WINCODEC_DWRITE_DWM                      = 2200,
+	WINML                                    = 2192,
+	DIRECT2D                                 = 2201,
+	DEFRAG                                   = 2304,
+	USERMODE_SDBUS                           = 2305,
+	JSCRIPT                                  = 2306,
+	PIDGENX                                  = 2561,
+	EAS                                      = 85,
+	WEB                                      = 885,
+	WEB_SOCKET                               = 886,
+	MOBILE                                   = 1793,
+	SQLITE                                   = 1967,
+	SERVICE_FABRIC                           = 1968,
+	UTC                                      = 1989,
+	WEP                                      = 2049,
+	SYNCENGINE                               = 2050,
+	XBOX                                     = 2339,
+	GAME                                     = 2340,
+	PIX                                      = 2748,
+}
+
 ERROR_SUCCESS : DWORD : 0
 NO_ERROR :: 0
 SEC_E_OK : HRESULT : 0x00000000
@@ -42,14 +225,55 @@ ERROR_TIMEOUT                : DWORD : 1460
 ERROR_DATATYPE_MISMATCH      : DWORD : 1629
 ERROR_UNSUPPORTED_TYPE       : DWORD : 1630
 ERROR_NOT_SAME_OBJECT        : DWORD : 1656
-ERROR_PIPE_CONNECTED         : DWORD : 0x80070217
+ERROR_PIPE_CONNECTED         : DWORD : 535
 ERROR_PIPE_BUSY              : DWORD : 231
 
-E_NOTIMPL :: HRESULT(-0x7fff_bfff) // 0x8000_4001
+// https://learn.microsoft.com/en-us/windows/win32/seccrypto/common-hresult-values
+S_OK           :: 0x00000000 // Operation successful
+E_NOTIMPL      :: 0x80004001 // Not implemented
+E_NOINTERFACE  :: 0x80004002 // No such interface supported
+E_POINTER      :: 0x80004003 // Pointer that is not valid
+E_ABORT        :: 0x80004004 // Operation aborted
+E_FAIL         :: 0x80004005 // Unspecified failure
+E_UNEXPECTED   :: 0x8000FFFF // Unexpected failure
+E_ACCESSDENIED :: 0x80070005 // General access denied error
+E_HANDLE       :: 0x80070006 // Handle that is not valid
+E_OUTOFMEMORY  :: 0x8007000E // Failed to allocate necessary memory
+E_INVALIDARG   :: 0x80070057 // One or more arguments are not valid
+
+// Severity values
+SEVERITY :: enum DWORD {
+	SUCCESS = 0,
+	ERROR   = 1,
+}
+
+// Generic test for success on any status value (non-negative numbers indicate success).
+SUCCEEDED :: #force_inline proc "contextless" (#any_int result: int) -> bool { return result >= S_OK }
+// and the inverse
+FAILED :: #force_inline proc(#any_int result: int) -> bool { return result < S_OK }
 
-SUCCEEDED :: #force_inline proc "contextless" (#any_int result: int) -> bool { return result >= 0 }
+// Generic test for error on any status value.
+IS_ERROR :: #force_inline proc(#any_int status: int) -> bool { return u32(status) >> 31 == u32(SEVERITY.ERROR) }
 
+// Return the code
+HRESULT_CODE :: #force_inline proc(#any_int hr: int) -> int { return int(u32(hr) & 0xFFFF) }
+
+//  Return the facility
+HRESULT_FACILITY :: #force_inline proc(#any_int hr: int) -> FACILITY { return FACILITY((u32(hr) >> 16) & 0x1FFF) }
+
+//  Return the severity
+HRESULT_SEVERITY :: #force_inline proc(#any_int hr: int) -> SEVERITY { return SEVERITY((u32(hr) >> 31) & 0x1) }
+
+// Create an HRESULT value from component pieces
+MAKE_HRESULT :: #force_inline proc(#any_int sev: int, #any_int fac: int, #any_int code: int) -> HRESULT {
+	return HRESULT((uint(sev)<<31) | (uint(fac)<<16) | (uint(code)))
+}
+
+DECODE_HRESULT :: #force_inline proc(#any_int hr: int) -> (SEVERITY, FACILITY, int) {
+	return HRESULT_SEVERITY(hr), HRESULT_FACILITY(hr), HRESULT_CODE(hr)
+}
 
+// aka ERROR or WIN32_ERROR to hint the WIN32 facility
 System_Error :: enum DWORD {
 	// The operation completed successfully.
 	SUCCESS = 0x0,

+ 16 - 1
core/sys/windows/winmm.odin

@@ -270,7 +270,7 @@ LPHWAVEOUT :: ^HWAVEOUT
 
 // https://learn.microsoft.com/en-us/windows/win32/multimedia/multimedia-timer-structures
 MMTIME :: struct {
-	wType: UINT,
+	wType: MMTIME_TYPE,
 	u: struct #raw_union {
 		ms: DWORD,
 		sample: DWORD,
@@ -292,6 +292,21 @@ MMTIME :: struct {
 }
 LPMMTIME :: ^MMTIME
 
+MMTIME_TYPE :: enum UINT {
+	/* time in milliseconds */
+	TIME_MS      = 0x0001,
+	/* number of wave samples */
+	TIME_SAMPLES = 0x0002,
+	/* current byte offset */
+	TIME_BYTES   = 0x0004,
+	/* SMPTE time */
+	TIME_SMPTE   = 0x0008,
+	/* MIDI time */
+	TIME_MIDI    = 0x0010,
+	/* Ticks within MIDI stream */
+	TIME_TICKS   = 0x0020,
+}
+
 MAXPNAMELEN :: 32
 MAXERRORLENGTH :: 256
 MMVERSION :: UINT

+ 31 - 0
core/sys/windows/winnls.odin

@@ -0,0 +1,31 @@
+// +build windows
+package sys_windows
+
+LCTYPE :: distinct DWORD
+
+LOCALE_NAME_MAX_LENGTH     :: 85
+LOCALE_NAME_USER_DEFAULT   :: 0
+LOCALE_NAME_INVARIANT      : wstring = L("")
+LOCALE_NAME_SYSTEM_DEFAULT : wstring = L("!x-sys-default-locale")
+
+// String Length Maximums.
+// 5 ranges, 2 bytes ea., 0 term.
+MAX_LEADBYTES   :: 12
+// single or double byte
+MAX_DEFAULTCHAR :: 2
+
+CPINFOEXW :: struct{
+	// Maximum length, in bytes, of a character in the code page.
+	MaxCharSize: UINT,
+	// The default is usually the "?" character for the code page.
+	DefaultChar: [MAX_DEFAULTCHAR]BYTE,
+	// A fixed-length array of lead byte ranges, for which the number of lead byte ranges is variable.
+	LeadByte: [MAX_LEADBYTES]BYTE,
+	// The default is usually the "?" character or the katakana middle dot character.
+	UnicodeDefaultChar: WCHAR,
+	// Code page value. This value reflects the code page passed to the GetCPInfoEx function.
+	CodePage: CODEPAGE,
+	// Full name of the code page.
+	CodePageName: [MAX_PATH]WCHAR,
+}
+LPCPINFOEXW :: ^CPINFOEXW

+ 92 - 0
core/sys/windows/winver.odin

@@ -0,0 +1,92 @@
+// +build windows
+package sys_windows
+
+foreign import version "system:version.lib"
+
+@(default_calling_convention = "system")
+foreign version {
+	GetFileVersionInfoSizeW :: proc(lpwstrFilename: LPCWSTR, lpdwHandle: LPDWORD) -> DWORD ---
+	GetFileVersionInfoW :: proc(lptstrFilename: LPCWSTR, dwHandle: DWORD, dwLen: DWORD, lpData: LPVOID) -> BOOL ---
+
+	GetFileVersionInfoSizeExW :: proc(dwFlags: FILE_VER_GET_FLAGS, lpwstrFilename: LPCWSTR, lpdwHandle: LPDWORD) -> DWORD ---
+	GetFileVersionInfoExW :: proc(dwFlags: FILE_VER_GET_FLAGS, lpwstrFilename: LPCWSTR, dwHandle, dwLen: DWORD, lpData: LPVOID) -> DWORD ---
+
+	VerLanguageNameW :: proc(wLang: DWORD, szLang: LPWSTR, cchLang: DWORD) -> DWORD ---
+	VerQueryValueW :: proc(pBlock: LPCVOID, lpSubBlock: LPCWSTR, lplpBuffer: ^LPVOID, puLen: PUINT) -> BOOL ---
+}
+
+FILE_VER_GET :: enum DWORD {LOCALISED, NEUTRAL, PREFETCHED}
+FILE_VER_GET_FLAGS :: bit_set[FILE_VER_GET; DWORD]
+
+/* ----- Symbols ----- */
+VS_FILE_INFO            :: RT_VERSION
+VS_VERSION_INFO         :: 1
+VS_USER_DEFINED         :: 100
+
+VS_FFI_SIGNATURE : DWORD : 0xFEEF04BD
+
+VS_FFI_STRUCVERSION     :: 0x00010000
+VS_FFI_FILEFLAGSMASK    :: 0x0000003F
+
+/* ----- VS_VERSION.dwFileFlags ----- */
+VS_FILEFLAG :: enum DWORD {
+	DEBUG,
+	PRERELEASE,
+	PATCHED,
+	PRIVATEBUILD,
+	INFOINFERRED,
+	SPECIALBUILD,
+}
+VS_FILEFLAGS :: bit_set[VS_FILEFLAG;DWORD]
+
+/* ----- VS_VERSION.dwFileOS ----- */
+VOS :: enum WORD {
+	UNKNOWN = 0x0000,
+	DOS     = 0x0001,
+	OS216   = 0x0002,
+	OS232   = 0x0003,
+	NT      = 0x0004,
+	WINCE   = 0x0005,
+}
+VOS2 :: enum WORD {
+	BASE      = 0x0000,
+	WINDOWS16 = 0x0001,
+	PM16      = 0x0002,
+	PM32      = 0x0003,
+	WINDOWS32 = 0x0004,
+}
+
+/* ----- VS_VERSION.dwFileType ----- */
+VFT :: enum DWORD {
+	UNKNOWN    = 0x00000000,
+	APP        = 0x00000001,
+	DLL        = 0x00000002,
+	DRV        = 0x00000003,
+	FONT       = 0x00000004,
+	VXD        = 0x00000005,
+	STATIC_LIB = 0x00000007,
+}
+
+/* ----- VS_VERSION.dwFileSubtype for VFT_WINDOWS_DRV ----- */
+VFT2_WINDOWS_DRV :: enum DWORD {
+	UNKNOWN               = 0x00000000,
+	DRV_PRINTER           = 0x00000001,
+	DRV_KEYBOARD          = 0x00000002,
+	DRV_LANGUAGE          = 0x00000003,
+	DRV_DISPLAY           = 0x00000004,
+	DRV_MOUSE             = 0x00000005,
+	DRV_NETWORK           = 0x00000006,
+	DRV_SYSTEM            = 0x00000007,
+	DRV_INSTALLABLE       = 0x00000008,
+	DRV_SOUND             = 0x00000009,
+	DRV_COMM              = 0x0000000A,
+	DRV_INPUTMETHOD       = 0x0000000B,
+	DRV_VERSIONED_PRINTER = 0x0000000C,
+}
+
+/* ----- VS_VERSION.dwFileSubtype for VFT_WINDOWS_FONT ----- */
+VFT2_WINDOWS_FONT :: enum DWORD {
+	FONT_RASTER   = 0x00000001,
+	FONT_VECTOR   = 0x00000002,
+	FONT_TRUETYPE = 0x00000003,
+}

+ 2 - 2
core/testing/runner.odin

@@ -868,8 +868,8 @@ To partly mitigate this, redirect STDERR to a file or use the -define:ODIN_TEST_
 		when ODIN_OS != .Windows {
 			mode = os.S_IRUSR|os.S_IWUSR|os.S_IRGRP|os.S_IROTH
 		}
-		json_fd, errno := os.open(JSON_REPORT, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, mode)
-		fmt.assertf(errno == os.ERROR_NONE, "unable to open file %q for writing of JSON report, error: %v", JSON_REPORT, errno)
+		json_fd, err := os.open(JSON_REPORT, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, mode)
+		fmt.assertf(err == nil, "unable to open file %q for writing of JSON report, error: %v", JSON_REPORT, err)
 		defer os.close(json_fd)
 
 		for test, i in report.all_tests {

+ 1 - 1
core/text/edit/text_edit.odin

@@ -137,7 +137,7 @@ clear_all :: proc(s: ^State) -> (cleared: bool) {
 
 // push current text state to the wanted undo|redo stack
 undo_state_push :: proc(s: ^State, undo: ^[dynamic]^Undo_State) -> mem.Allocator_Error {
-	if s.builder != nil {
+	if s.builder == nil {
 		return nil
 	}
 	text := string(s.builder.buf[:])

+ 319 - 30
core/time/time.odin

@@ -232,27 +232,21 @@ duration_hours :: proc "contextless" (d: Duration) -> f64 {
 }
 
 /*
-Round a duration to a specific unit.
+Round a duration to a specific unit
 
-This procedure rounds the duration to a specific unit.
+This procedure rounds the duration to a specific unit
 
-**Inputs**:
-- `d`: The duration to round.
-- `m`: The unit to round to.
-
-**Returns**:
-- The duration `d`, rounded to the unit specified by `m`.
-
-**Example**:
+**Note**: Any duration can be supplied as a unit.
 
-In order to obtain the rough amount of seconds in a duration, the following call
-can be used:
+Inputs:
+- d: The duration to round
+- m: The unit to round to
 
-```
-time.duration_round(my_duration, time.Second)
-```
+Returns:
+- The duration `d`, rounded to the unit specified by `m`
 
-**Note**: Any duration can be supplied as a unit.
+Example:
+	time.duration_round(my_duration, time.Second)
 */
 duration_round :: proc "contextless" (d, m: Duration) -> Duration {
 	_less_than_half :: #force_inline proc "contextless" (x, y: Duration) -> bool {
@@ -288,23 +282,17 @@ Truncate the duration to the specified unit.
 
 This procedure truncates the duration `d` to the unit specified by `m`.
 
-**Inputs**:
-- `d`: The duration to truncate.
-- `m`: The unit to truncate to.
-
-**Returns**:
-- The duration `d`, truncated to the unit specified by `m`.
-
-**Example**:
+**Note**: Any duration can be supplied as a unit.
 
-In order to obtain the amount of whole seconds in a duration, the following call
-can be used:
+Inputs:
+- d: The duration to truncate.
+- m: The unit to truncate to.
 
-```
-time.duration_round(my_duration, time.Second)
-```
+Returns:
+- The duration `d`, truncated to the unit specified by `m`.
 
-**Note**: Any duration can be supplied as a unit.
+Example:
+	time.duration_round(my_duration, time.Second)
 */
 duration_truncate :: proc "contextless" (d, m: Duration) -> Duration {
 	return d if m <= 0 else d - d%m
@@ -389,6 +377,307 @@ clock_from_seconds :: proc "contextless" (nsec: u64) -> (hour, min, sec: int) {
 	return
 }
 
+MIN_HMS_LEN       :: 8
+MIN_HMS_12_LEN    :: 11
+MIN_YYYY_DATE_LEN :: 10
+MIN_YY_DATE_LEN   :: 8
+
+/*
+Formats a `Time` as a 24-hour `hh:mm:ss` string.
+
+**Does not allocate**
+
+Inputs:
+- t:   The Time to format.
+- buf: The backing buffer to use.
+
+Returns:
+- res: The formatted string, backed by buf
+
+Example:
+	buf: [MIN_HMS_LEN]u8
+	now := time.now()
+	fmt.println(time.to_string_hms(now, buf[:]))
+*/
+time_to_string_hms :: proc(t: Time, buf: []u8) -> (res: string) #no_bounds_check {
+	assert(len(buf) >= MIN_HMS_LEN)
+	h, m, s := clock(t)
+
+	buf[7] = '0' + u8(s % 10); s /= 10
+	buf[6] = '0' + u8(s)
+	buf[5] = ':'
+	buf[4] = '0' + u8(m % 10); m /= 10
+	buf[3] = '0' + u8(m)
+	buf[2] = ':'
+	buf[1] = '0' + u8(h % 10); h /= 10
+	buf[0] = '0' + u8(h)
+
+	return string(buf[:MIN_HMS_LEN])
+}
+
+/*
+Formats a `Duration` as a 24-hour `hh:mm:ss` string.
+
+**Does not allocate**
+
+Inputs:
+- d:   The Duration to format.
+- buf: The backing buffer to use.
+
+Returns:
+- res: The formatted string, backed by buf
+
+Example:
+	buf: [MIN_HMS_LEN]u8
+	d   := time.since(earlier)
+	fmt.println(time.to_string_hms(now, buf[:]))
+*/
+duration_to_string_hms :: proc(d: Duration, buf: []u8) -> (res: string) #no_bounds_check {
+	return time_to_string_hms(Time{_nsec=i64(d)}, buf)
+}
+
+to_string_hms :: proc{time_to_string_hms, duration_to_string_hms}
+
+/*
+Formats a `Time` as a 12-hour `hh:mm:ss pm` string
+
+**Does not allocate**
+
+Inputs:
+- t:    The Time to format
+- buf:  The backing buffer to use
+- ampm: An optional pair of am/pm strings to use in place of the default
+
+Returns:
+- res: The formatted string, backed by buf
+
+Example:
+	buf: [64]u8
+	now := time.now()
+	fmt.println(time.to_string_hms_12(now, buf[:]))
+	fmt.println(time.to_string_hms_12(now, buf[:], {"㏂", "㏘"}))
+*/
+to_string_hms_12 :: proc(t: Time, buf: []u8, ampm: [2]string = {" am", " pm"}) -> (res: string) #no_bounds_check {
+	assert(len(buf) >= MIN_HMS_LEN + max(len(ampm[0]), len(ampm[1])))
+	h, m, s := clock(t)
+
+	_h := h % 12
+	buf[7] = '0' + u8(s % 10); s /= 10
+	buf[6] = '0' + u8(s)
+	buf[5] = ':'
+	buf[4] = '0' + u8(m % 10); m /= 10
+	buf[3] = '0' + u8(m)
+	buf[2] = ':'
+	buf[1] = '0' + u8(_h% 10); _h /= 10
+	buf[0] = '0' + u8(_h)
+
+	if h < 13 {
+		copy(buf[8:], ampm[0])
+		return string(buf[:MIN_HMS_LEN+len(ampm[0])])
+	} else {
+		copy(buf[8:], ampm[1])
+		return string(buf[:MIN_HMS_LEN+len(ampm[1])])
+	}
+}
+
+/*
+Formats a Time as a yyyy-mm-dd date string.
+
+Inputs:
+- t:    The Time to format.
+- buf:  The backing buffer to use.
+
+Returns:
+- res: The formatted string, backed by `buf`.
+
+Example:
+	buf: [MIN_YYYY_DATE_LEN]u8
+	now := time.now()
+	fmt.println(time.to_string_yyyy_mm_dd(now, buf[:]))
+*/
+to_string_yyyy_mm_dd :: proc(t: Time, buf: []u8) -> (res: string) #no_bounds_check {
+	assert(len(buf) >= MIN_YYYY_DATE_LEN)
+	y, _m, d := date(t)
+	m := u8(_m)
+
+	buf[9] = '0' + u8(d % 10); d /= 10
+	buf[8] = '0' + u8(d % 10)
+	buf[7] = '-'
+	buf[6] = '0' + u8(m % 10); m /= 10
+	buf[5] = '0' + u8(m % 10)
+	buf[4] = '-'
+	buf[3] = '0' + u8(y % 10); y /= 10
+	buf[2] = '0' + u8(y % 10); y /= 10
+	buf[1] = '0' + u8(y % 10); y /= 10
+	buf[0] = '0' + u8(y)
+
+	return string(buf[:MIN_YYYY_DATE_LEN])
+}
+
+/*
+Formats a Time as a yy-mm-dd date string.
+
+Inputs:
+- t:    The Time to format.
+- buf:  The backing buffer to use.
+
+Returns:
+- res: The formatted string, backed by `buf`.
+
+Example:
+	buf: [MIN_YY_DATE_LEN]u8
+	now := time.now()
+	fmt.println(time.to_string_yy_mm_dd(now, buf[:]))
+*/
+to_string_yy_mm_dd :: proc(t: Time, buf: []u8) -> (res: string) #no_bounds_check {
+	assert(len(buf) >= MIN_YY_DATE_LEN)
+	y, _m, d := date(t)
+	y %= 100; m := u8(_m)
+
+	buf[7] = '0' + u8(d % 10); d /= 10
+	buf[6] = '0' + u8(d % 10)
+	buf[5] = '-'
+	buf[4] = '0' + u8(m % 10); m /= 10
+	buf[3] = '0' + u8(m % 10)
+	buf[2] = '-'
+	buf[1] = '0' + u8(y % 10); y /= 10
+	buf[0] = '0' + u8(y)
+
+	return string(buf[:MIN_YY_DATE_LEN])
+}
+
+/*
+Formats a Time as a dd-mm-yyyy date string.
+
+Inputs:
+- t:    The Time to format.
+- buf:  The backing buffer to use.
+
+Returns:
+- res: The formatted string, backed by `buf`.
+
+Example:
+	buf: [MIN_YYYY_DATE_LEN]u8
+	now := time.now()
+	fmt.println(time.to_string_dd_mm_yyyy(now, buf[:]))
+*/
+to_string_dd_mm_yyyy :: proc(t: Time, buf: []u8) -> (res: string) #no_bounds_check {
+	assert(len(buf) >= MIN_YYYY_DATE_LEN)
+	y, _m, d := date(t)
+	m := u8(_m)
+
+	buf[9] = '0' + u8(y % 10); y /= 10
+	buf[8] = '0' + u8(y % 10); y /= 10
+	buf[7] = '0' + u8(y % 10); y /= 10
+	buf[6] = '0' + u8(y)
+	buf[5] = '-'
+	buf[4] = '0' + u8(m % 10); m /= 10
+	buf[3] = '0' + u8(m % 10)
+	buf[2] = '-'
+	buf[1] = '0' + u8(d % 10); d /= 10
+	buf[0] = '0' + u8(d % 10)
+
+	return string(buf[:MIN_YYYY_DATE_LEN])
+}
+
+/*
+Formats a Time as a dd-mm-yy date string.
+
+Inputs:
+- t:    The Time to format.
+- buf:  The backing buffer to use.
+
+Returns:
+- res: The formatted string, backed by `buf`.
+
+Example:
+	buf: [MIN_YY_DATE_LEN]u8
+	now := time.now()
+	fmt.println(time.to_string_dd_mm_yy(now, buf[:]))
+*/
+to_string_dd_mm_yy :: proc(t: Time, buf: []u8) -> (res: string) #no_bounds_check {
+	assert(len(buf) >= MIN_YY_DATE_LEN)
+	y, _m, d := date(t)
+	y %= 100; m := u8(_m)
+
+	buf[7] = '0' + u8(y % 10); y /= 10
+	buf[6] = '0' + u8(y)
+	buf[5] = '-'
+	buf[4] = '0' + u8(m % 10); m /= 10
+	buf[3] = '0' + u8(m % 10)
+	buf[2] = '-'
+	buf[1] = '0' + u8(d % 10); d /= 10
+	buf[0] = '0' + u8(d % 10)
+
+	return string(buf[:MIN_YY_DATE_LEN])
+}
+
+/*
+Formats a Time as a mm-dd-yyyy date string.
+
+Inputs:
+- t:    The Time to format.
+- buf:  The backing buffer to use.
+
+Returns:
+- res: The formatted string, backed by `buf`.
+
+Example:
+	buf: [MIN_YYYY_DATE_LEN]u8
+	now := time.now()
+	fmt.println(time.to_string_mm_dd_yyyy(now, buf[:]))
+*/
+to_string_mm_dd_yyyy :: proc(t: Time, buf: []u8) -> (res: string) #no_bounds_check {
+	assert(len(buf) >= MIN_YYYY_DATE_LEN)
+	y, _m, d := date(t)
+	m := u8(_m)
+
+	buf[9] = '0' + u8(y % 10); y /= 10
+	buf[8] = '0' + u8(y % 10); y /= 10
+	buf[7] = '0' + u8(y % 10); y /= 10
+	buf[6] = '0' + u8(y)
+	buf[5] = '-'
+	buf[4] = '0' + u8(d % 10); d /= 10
+	buf[3] = '0' + u8(d % 10)
+	buf[2] = '-'
+	buf[1] = '0' + u8(m % 10); m /= 10
+	buf[0] = '0' + u8(m % 10)
+
+	return string(buf[:MIN_YYYY_DATE_LEN])
+}
+
+/*
+Formats a Time as a mm-dd-yy date string.
+
+Inputs:
+- t:    The Time to format.
+- buf:  The backing buffer to use.
+
+Returns:
+- res: The formatted string, backed by `buf`.
+
+Example:
+	buf: [MIN_YY_DATE_LEN]u8
+	now := time.now()
+	fmt.println(time.to_string_mm_dd_yy(now, buf[:]))
+*/
+to_string_mm_dd_yy :: proc(t: Time, buf: []u8) -> (res: string) #no_bounds_check {
+	assert(len(buf) >= MIN_YY_DATE_LEN)
+	y, _m, d := date(t)
+	y %= 100; m := u8(_m)
+
+	buf[7] = '0' + u8(y % 10); y /= 10
+	buf[6] = '0' + u8(y)
+	buf[5] = '-'
+	buf[4] = '0' + u8(d % 10); d /= 10
+	buf[3] = '0' + u8(d % 10)
+	buf[2] = '-'
+	buf[1] = '0' + u8(m % 10); m /= 10
+	buf[0] = '0' + u8(m % 10)
+
+	return string(buf[:MIN_YY_DATE_LEN])
+}
+
 /*
 Read the timestamp counter of the CPU.
 */

+ 1 - 1
examples/demo/demo.odin

@@ -359,7 +359,7 @@ control_flow :: proc() {
 
 		if false {
 			f, err := os.open("my_file.txt")
-			if err != os.ERROR_NONE {
+			if err != nil {
 				// handle error
 			}
 			defer os.close(f)

+ 11 - 3
src/build_settings.cpp

@@ -1822,6 +1822,7 @@ gb_internal bool init_build_paths(String init_filename) {
 		if (bc->resource_filepath.len > 0) {
 			bc->build_paths[BuildPath_RES] = path_from_string(ha, bc->resource_filepath);
 			if (!string_ends_with(bc->resource_filepath, str_lit(".res"))) {
+				bc->build_paths[BuildPath_RES].ext = copy_string(ha, STR_LIT("res"));
 				bc->build_paths[BuildPath_RC]      = path_from_string(ha, bc->resource_filepath);
 				bc->build_paths[BuildPath_RC].ext  = copy_string(ha, STR_LIT("rc"));
 			}
@@ -2010,15 +2011,22 @@ gb_internal bool init_build_paths(String init_filename) {
 		}
 	}
 
+	String output_file = path_to_string(ha, bc->build_paths[BuildPath_Output]);
+	defer (gb_free(ha, output_file.text));
+
 	// Check if output path is a directory.
 	if (path_is_directory(bc->build_paths[BuildPath_Output])) {
-		String output_file = path_to_string(ha, bc->build_paths[BuildPath_Output]);
-		defer (gb_free(ha, output_file.text));
 		gb_printf_err("Output path %.*s is a directory.\n", LIT(output_file));
 		return false;
 	}
 
-	if (!write_directory(bc->build_paths[BuildPath_Output].basename)) {
+	gbFile      output_file_test;
+	const char* output_file_name = (const char*)output_file.text;
+	gbFileError output_test_err = gb_file_open_mode(&output_file_test, gbFileMode_Append | gbFileMode_Rw, output_file_name);
+	gb_file_close(&output_file_test);
+	gb_file_remove(output_file_name);
+
+	if (output_test_err != 0) {
 		String output_file = path_to_string(ha, bc->build_paths[BuildPath_Output]);
 		defer (gb_free(ha, output_file.text));
 		gb_printf_err("No write permissions for output path: %.*s\n", LIT(output_file));

+ 115 - 7
src/check_builtin.cpp

@@ -470,8 +470,8 @@ gb_internal bool check_builtin_simd_operation(CheckerContext *c, Operand *operan
 		}
 
 	// Integer only
-	case BuiltinProc_simd_add_sat:
-	case BuiltinProc_simd_sub_sat:
+	case BuiltinProc_simd_saturating_add:
+	case BuiltinProc_simd_saturating_sub:
 	case BuiltinProc_simd_bit_and:
 	case BuiltinProc_simd_bit_or:
 	case BuiltinProc_simd_bit_xor:
@@ -501,8 +501,8 @@ gb_internal bool check_builtin_simd_operation(CheckerContext *c, Operand *operan
 			Type *elem = base_array_type(x.type);
 
 			switch (id) {
-			case BuiltinProc_simd_add_sat:
-			case BuiltinProc_simd_sub_sat:
+			case BuiltinProc_simd_saturating_add:
+			case BuiltinProc_simd_saturating_sub:
 				if (!is_type_integer(elem)) {
 					gbString xs = type_to_string(x.type);
 					error(x.expr, "'%.*s' expected a #simd type with an integer element, got '%s'", LIT(builtin_name), xs);
@@ -663,6 +663,91 @@ gb_internal bool check_builtin_simd_operation(CheckerContext *c, Operand *operan
 			return true;
 		}
 
+	case BuiltinProc_simd_gather:
+	case BuiltinProc_simd_scatter:
+	case BuiltinProc_simd_masked_load:
+	case BuiltinProc_simd_masked_store:
+	case BuiltinProc_simd_masked_expand_load:
+	case BuiltinProc_simd_masked_compress_store:
+		{
+			// gather (ptr: #simd[N]rawptr, values: #simd[N]T, mask: #simd[N]int_or_bool) -> #simd[N]T
+			// scatter(ptr: #simd[N]rawptr, values: #simd[N]T, mask: #simd[N]int_or_bool)
+
+			// masked_load (ptr: rawptr, values: #simd[N]T, mask: #simd[N]int_or_bool) -> #simd[N]T
+			// masked_store(ptr: rawptr, values: #simd[N]T, mask: #simd[N]int_or_bool)
+			// masked_expand_load (ptr: rawptr, values: #simd[N]T, mask: #simd[N]int_or_bool) -> #simd[N]T
+			// masked_compress_store(ptr: rawptr, values: #simd[N]T, mask: #simd[N]int_or_bool)
+
+			Operand ptr    = {};
+			Operand values = {};
+			Operand mask   = {};
+			check_expr(c, &ptr,    ce->args[0]); if (ptr.mode    == Addressing_Invalid) return false;
+			check_expr(c, &values, ce->args[1]); if (values.mode == Addressing_Invalid) return false;
+			check_expr(c, &mask,   ce->args[2]); if (mask.mode   == Addressing_Invalid) return false;
+			if (!is_type_simd_vector(values.type)) { error(values.expr, "'%.*s' expected a simd vector type", LIT(builtin_name)); return false; }
+			if (!is_type_simd_vector(mask.type))   { error(mask.expr,   "'%.*s' expected a simd vector type", LIT(builtin_name)); return false; }
+
+			if (id == BuiltinProc_simd_gather || id == BuiltinProc_simd_scatter) {
+				if (!is_type_simd_vector(ptr.type))    { error(ptr.expr,    "'%.*s' expected a simd vector type", LIT(builtin_name)); return false; }
+				Type *ptr_elem = base_array_type(ptr.type);
+				if (!is_type_rawptr(ptr_elem)) {
+					gbString s = type_to_string(ptr.type);
+					error(ptr.expr, "Expected a simd vector of 'rawptr' for the addresses, got %s", s);
+					gb_string_free(s);
+					return false;
+				}
+			} else {
+				if (!is_type_pointer(ptr.type)) {
+					gbString s = type_to_string(ptr.type);
+					error(ptr.expr, "Expected a pointer type for the address, got %s", s);
+					gb_string_free(s);
+					return false;
+				}
+			}
+			Type *mask_elem = base_array_type(mask.type);
+
+			if (!is_type_integer(mask_elem) && !is_type_boolean(mask_elem)) {
+				gbString s = type_to_string(mask.type);
+				error(mask.expr, "Expected a simd vector of integers or booleans for the mask, got %s", s);
+				gb_string_free(s);
+				return false;
+			}
+
+			if (id == BuiltinProc_simd_gather || id == BuiltinProc_simd_scatter) {
+				i64 ptr_count    = get_array_type_count(ptr.type);
+				i64 values_count = get_array_type_count(values.type);
+				i64 mask_count   = get_array_type_count(mask.type);
+				if (ptr_count != values_count ||
+				    values_count != mask_count ||
+				    mask_count != ptr_count) {
+					gbString s = type_to_string(mask.type);
+					error(mask.expr, "All simd vectors must be of the same length, got %lld vs %lld vs %lld", cast(long long)ptr_count, cast(long long)values_count, cast(long long)mask_count);
+					gb_string_free(s);
+					return false;
+				}
+			} else {
+				i64 values_count = get_array_type_count(values.type);
+				i64 mask_count   = get_array_type_count(mask.type);
+				if (values_count != mask_count) {
+					gbString s = type_to_string(mask.type);
+					error(mask.expr, "All simd vectors must be of the same length, got %lld vs %lld", cast(long long)values_count, cast(long long)mask_count);
+					gb_string_free(s);
+					return false;
+				}
+			}
+
+			if (id == BuiltinProc_simd_gather ||
+			    id == BuiltinProc_simd_masked_load ||
+			    id == BuiltinProc_simd_masked_expand_load) {
+				operand->mode = Addressing_Value;
+				operand->type = values.type;
+			} else {
+				operand->mode = Addressing_NoValue;
+				operand->type = nullptr;
+			}
+			return true;
+		}
+
 	case BuiltinProc_simd_extract:
 		{
 			Operand x = {};
@@ -775,6 +860,29 @@ gb_internal bool check_builtin_simd_operation(CheckerContext *c, Operand *operan
 			return true;
 		}
 
+	case BuiltinProc_simd_reduce_any:
+	case BuiltinProc_simd_reduce_all:
+		{
+			Operand x = {};
+			check_expr(c, &x, ce->args[0]); if (x.mode == Addressing_Invalid) return false;
+
+			if (!is_type_simd_vector(x.type)) {
+				error(x.expr, "'%.*s' expected a simd vector type", LIT(builtin_name));
+				return false;
+			}
+			Type *elem = base_array_type(x.type);
+			if (!is_type_boolean(elem)) {
+				gbString xs = type_to_string(x.type);
+				error(x.expr, "'%.*s' expected a #simd type with a boolean element, got '%s'", LIT(builtin_name), xs);
+				gb_string_free(xs);
+				return false;
+			}
+
+			operand->mode = Addressing_Value;
+			operand->type = t_untyped_bool;
+			return true;
+		}
+
 
 	case BuiltinProc_simd_shuffle:
 		{
@@ -2460,7 +2568,7 @@ gb_internal bool check_builtin_procedure(CheckerContext *c, Operand *operand, As
 			arg_count++;
 		}
 
-		if (arg_count > max_count) {
+		if (false && arg_count > max_count) {
 			error(call, "Too many 'swizzle' indices, %td > %td", arg_count, max_count);
 			return false;
 		} else if (arg_count < 2) {
@@ -4302,8 +4410,8 @@ gb_internal bool check_builtin_procedure(CheckerContext *c, Operand *operand, As
 		}
 		break;
 
-	case BuiltinProc_add_sat:
-	case BuiltinProc_sub_sat:
+	case BuiltinProc_saturating_add:
+	case BuiltinProc_saturating_sub:
 		{
 			Operand x = {};
 			Operand y = {};

+ 31 - 0
src/check_expr.cpp

@@ -127,6 +127,8 @@ gb_internal bool complete_soa_type(Checker *checker, Type *t, bool wait_to_finis
 
 gb_internal bool check_is_castable_to(CheckerContext *c, Operand *operand, Type *y);
 
+gb_internal bool is_exact_value_zero(ExactValue const &v);
+
 enum LoadDirectiveResult {
 	LoadDirective_Success  = 0,
 	LoadDirective_Error    = 1,
@@ -4457,6 +4459,27 @@ gb_internal void convert_to_typed(CheckerContext *c, Operand *operand, Type *tar
 		
 
 	case Type_Union:
+		// IMPORTANT NOTE HACK(bill): This is just to allow for comparisons against `0` with the `os.Error` type
+		// as a kind of transition period
+		if (!build_context.strict_style &&
+		    operand->mode == Addressing_Constant &&
+		    target_type->kind == Type_Named &&
+		    (c->pkg == nullptr || c->pkg->name != "os") &&
+		    target_type->Named.name == "Error") {
+			Entity *e = target_type->Named.type_name;
+			if (e->pkg && e->pkg->name == "os") {
+				if (is_exact_value_zero(operand->value) &&
+				    (operand->value.kind == ExactValue_Integer ||
+				     operand->value.kind == ExactValue_Float)) {
+					operand->mode = Addressing_Value;
+					target_type = t_untyped_nil;
+				     	operand->value = empty_exact_value;
+					update_untyped_expr_value(c, operand->expr, operand->value);
+					break;
+				}
+			}
+		}
+		// "fallthrough"
 		if (!is_operand_nil(*operand) && !is_operand_uninit(*operand)) {
 			TEMPORARY_ALLOCATOR_GUARD();
 
@@ -5135,6 +5158,14 @@ gb_internal Entity *check_selector(CheckerContext *c, Operand *operand, Ast *nod
 			Scope *import_scope = e->ImportName.scope;
 			String entity_name = selector->Ident.token.string;
 
+			if (import_scope == nullptr) {
+				ERROR_BLOCK();
+				error(node, "'%.*s' is not imported in this file, '%.*s' is unavailable", LIT(import_name), LIT(entity_name));
+				operand->mode = Addressing_Invalid;
+				operand->expr = node;
+				return nullptr;
+			}
+
 			check_op_expr = false;
 			entity = scope_lookup_current(import_scope, entity_name);
 			bool allow_builtin = false;

+ 1 - 1
src/check_type.cpp

@@ -3171,7 +3171,7 @@ gb_internal void check_array_type_internal(CheckerContext *ctx, Ast *e, Type **t
 			} else if (name == "simd") {
 				if (!is_type_valid_vector_elem(elem) && !is_type_polymorphic(elem)) {
 					gbString str = type_to_string(elem);
-					error(at->elem, "Invalid element type for #simd, expected an integer, float, or boolean with no specific endianness, got '%s'", str);
+					error(at->elem, "Invalid element type for #simd, expected an integer, float, boolean, or 'rawptr' with no specific endianness, got '%s'", str);
 					gb_string_free(str);
 					*type = alloc_type_array(elem, count, generic_type);
 					return;

+ 2 - 2
src/checker.cpp

@@ -1651,9 +1651,9 @@ gb_internal void add_type_and_value(CheckerContext *ctx, Ast *expr, AddressingMo
 
 		if (mode == Addressing_Constant || mode == Addressing_Invalid) {
 			expr->tav.value = value;
-		} else if (mode == Addressing_Value && is_type_typeid(type)) {
+		} else if (mode == Addressing_Value && type != nullptr && is_type_typeid(type)) {
 			expr->tav.value = value;
-		} else if (mode == Addressing_Value && is_type_proc(type)) {
+		} else if (mode == Addressing_Value && type != nullptr && is_type_proc(type)) {
 			expr->tav.value = value;
 		}
 

+ 28 - 8
src/checker_builtin_procs.hpp

@@ -70,8 +70,8 @@ enum BuiltinProcId {
 	BuiltinProc_overflow_sub,
 	BuiltinProc_overflow_mul,
 
-	BuiltinProc_add_sat,
-	BuiltinProc_sub_sat,
+	BuiltinProc_saturating_add,
+	BuiltinProc_saturating_sub,
 
 	BuiltinProc_sqrt,
 	BuiltinProc_fused_mul_add,
@@ -141,8 +141,8 @@ BuiltinProc__simd_begin,
 	BuiltinProc_simd_shl_masked, // C logic
 	BuiltinProc_simd_shr_masked, // C logic
 
-	BuiltinProc_simd_add_sat, // saturation arithmetic
-	BuiltinProc_simd_sub_sat, // saturation arithmetic
+	BuiltinProc_simd_saturating_add, // saturation arithmetic
+	BuiltinProc_simd_saturating_sub, // saturation arithmetic
 
 	BuiltinProc_simd_bit_and,
 	BuiltinProc_simd_bit_or,
@@ -174,6 +174,9 @@ BuiltinProc__simd_begin,
 	BuiltinProc_simd_reduce_or,
 	BuiltinProc_simd_reduce_xor,
 
+	BuiltinProc_simd_reduce_any,
+	BuiltinProc_simd_reduce_all,
+
 	BuiltinProc_simd_shuffle,
 	BuiltinProc_simd_select,
 
@@ -188,6 +191,12 @@ BuiltinProc__simd_begin,
 	BuiltinProc_simd_lanes_rotate_left,
 	BuiltinProc_simd_lanes_rotate_right,
 
+	BuiltinProc_simd_gather,
+	BuiltinProc_simd_scatter,
+	BuiltinProc_simd_masked_load,
+	BuiltinProc_simd_masked_store,
+	BuiltinProc_simd_masked_expand_load,
+	BuiltinProc_simd_masked_compress_store,
 
 	// Platform specific SIMD intrinsics
 	BuiltinProc_simd_x86__MM_SHUFFLE,
@@ -396,8 +405,8 @@ gb_global BuiltinProc builtin_procs[BuiltinProc_COUNT] = {
 	{STR_LIT("overflow_sub"), 2, false, Expr_Expr, BuiltinProcPkg_intrinsics},
 	{STR_LIT("overflow_mul"), 2, false, Expr_Expr, BuiltinProcPkg_intrinsics},
 
-	{STR_LIT("add_sat"), 2, false, Expr_Expr, BuiltinProcPkg_intrinsics},
-	{STR_LIT("sub_sat"), 2, false, Expr_Expr, BuiltinProcPkg_intrinsics},
+	{STR_LIT("saturating_add"), 2, false, Expr_Expr, BuiltinProcPkg_intrinsics},
+	{STR_LIT("saturating_sub"), 2, false, Expr_Expr, BuiltinProcPkg_intrinsics},
 
 	{STR_LIT("sqrt"), 1, false, Expr_Expr, BuiltinProcPkg_intrinsics},
 	{STR_LIT("fused_mul_add"), 3, false, Expr_Expr, BuiltinProcPkg_intrinsics},
@@ -467,8 +476,8 @@ gb_global BuiltinProc builtin_procs[BuiltinProc_COUNT] = {
 	{STR_LIT("simd_shl_masked"), 2, false, Expr_Expr, BuiltinProcPkg_intrinsics},
 	{STR_LIT("simd_shr_masked"), 2, false, Expr_Expr, BuiltinProcPkg_intrinsics},
 
-	{STR_LIT("simd_add_sat"), 2, false, Expr_Expr, BuiltinProcPkg_intrinsics},
-	{STR_LIT("simd_sub_sat"), 2, false, Expr_Expr, BuiltinProcPkg_intrinsics},
+	{STR_LIT("simd_saturating_add"), 2, false, Expr_Expr, BuiltinProcPkg_intrinsics},
+	{STR_LIT("simd_saturating_sub"), 2, false, Expr_Expr, BuiltinProcPkg_intrinsics},
 
 	{STR_LIT("simd_bit_and"), 2, false, Expr_Expr, BuiltinProcPkg_intrinsics},
 	{STR_LIT("simd_bit_or"),  2, false, Expr_Expr, BuiltinProcPkg_intrinsics},
@@ -501,6 +510,10 @@ gb_global BuiltinProc builtin_procs[BuiltinProc_COUNT] = {
 	{STR_LIT("simd_reduce_or"),          1, false, Expr_Expr, BuiltinProcPkg_intrinsics},
 	{STR_LIT("simd_reduce_xor"),         1, false, Expr_Expr, BuiltinProcPkg_intrinsics},
 
+	{STR_LIT("simd_reduce_any"),          1, false, Expr_Expr, BuiltinProcPkg_intrinsics},
+	{STR_LIT("simd_reduce_all"),         1, false, Expr_Expr, BuiltinProcPkg_intrinsics},
+
+
 	{STR_LIT("simd_shuffle"), 2, true,  Expr_Expr, BuiltinProcPkg_intrinsics},
 	{STR_LIT("simd_select"),  3, false, Expr_Expr, BuiltinProcPkg_intrinsics},
 
@@ -515,6 +528,13 @@ gb_global BuiltinProc builtin_procs[BuiltinProc_COUNT] = {
 	{STR_LIT("simd_lanes_rotate_left"), 2, false, Expr_Expr, BuiltinProcPkg_intrinsics},
 	{STR_LIT("simd_lanes_rotate_right"), 2, false, Expr_Expr, BuiltinProcPkg_intrinsics},
 
+	{STR_LIT("simd_gather"),       3, false, Expr_Expr, BuiltinProcPkg_intrinsics},
+	{STR_LIT("simd_scatter"),      3, false, Expr_Stmt, BuiltinProcPkg_intrinsics},
+	{STR_LIT("simd_masked_load"),  3, false, Expr_Expr, BuiltinProcPkg_intrinsics},
+	{STR_LIT("simd_masked_store"), 3, false, Expr_Stmt, BuiltinProcPkg_intrinsics},
+	{STR_LIT("simd_masked_expand_load"),    3, false, Expr_Expr, BuiltinProcPkg_intrinsics},
+	{STR_LIT("simd_masked_compress_store"), 3, false, Expr_Stmt, BuiltinProcPkg_intrinsics},
+
 	{STR_LIT("simd_x86__MM_SHUFFLE"), 4, false, Expr_Expr, BuiltinProcPkg_intrinsics},
 
 	{STR_LIT(""), 0, false, Expr_Stmt, BuiltinProcPkg_intrinsics},

Энэ ялгаанд хэт олон файл өөрчлөгдсөн тул зарим файлыг харуулаагүй болно