Browse Source

Merge remote-tracking branch 'upstream/master' into sys-windows-2

# Conflicts:
#	core/sys/windows/shell32.odin
Thomas la Cour 1 year ago
parent
commit
9d67d12d22
100 changed files with 5390 additions and 1148 deletions
  1. 5 7
      .github/workflows/ci.yml
  2. 1 32
      .gitignore
  3. 6 3
      base/intrinsics/intrinsics.odin
  4. 30 20
      base/runtime/core.odin
  5. 104 99
      base/runtime/core_builtin.odin
  6. 1 1
      base/runtime/core_builtin_soa.odin
  7. 6 6
      base/runtime/dynamic_map_internal.odin
  8. 6 5
      base/runtime/print.odin
  9. 2 1
      base/runtime/wasm_allocator.odin
  10. 25 0
      core/bytes/bytes.odin
  11. 2 2
      core/compress/zlib/zlib.odin
  12. 46 0
      core/container/intrusive/list/doc.odin
  13. 169 2
      core/container/intrusive/list/intrusive_list.odin
  14. 2 2
      core/container/queue/queue.odin
  15. 2 12
      core/crypto/_aes/ct64/api.odin
  16. 43 0
      core/crypto/_aes/hw_intel/api.odin
  17. 281 0
      core/crypto/_aes/hw_intel/ghash.odin
  18. 178 0
      core/crypto/_aes/hw_intel/hw_intel_keysched.odin
  19. 0 1
      core/crypto/aes/aes.odin
  20. 17 15
      core/crypto/aes/aes_ctr.odin
  21. 151 0
      core/crypto/aes/aes_ctr_hw_intel.odin
  22. 58 0
      core/crypto/aes/aes_ecb_hw_intel.odin
  23. 45 29
      core/crypto/aes/aes_gcm.odin
  24. 243 0
      core/crypto/aes/aes_gcm_hw_intel.odin
  25. 1 0
      core/crypto/aes/aes_impl_hw_gen.odin
  26. 18 0
      core/crypto/aes/aes_impl_hw_intel.odin
  27. 5 3
      core/crypto/chacha20/chacha20.odin
  28. 5 1
      core/crypto/crypto.odin
  29. 5 4
      core/encoding/cbor/marshal.odin
  30. 3 2
      core/encoding/cbor/unmarshal.odin
  31. 12 9
      core/encoding/ini/ini.odin
  32. 51 34
      core/encoding/json/marshal.odin
  33. 24 11
      core/encoding/json/unmarshal.odin
  34. 44 25
      core/fmt/fmt.odin
  35. 6 56
      core/math/cmplx/cmplx.odin
  36. 3 13
      core/math/cmplx/cmplx_invtrig.odin
  37. 7 1
      core/math/rand/rand.odin
  38. 3 3
      core/net/socket_linux.odin
  39. 3 0
      core/odin/ast/ast.odin
  40. 20 17
      core/odin/parser/parser.odin
  41. 80 0
      core/os/os2/dir.odin
  42. 20 0
      core/os/os2/dir_linux.odin
  43. 141 0
      core/os/os2/dir_windows.odin
  44. 9 6
      core/os/os2/env_windows.odin
  45. 4 0
      core/os/os2/errors.odin
  46. 10 3
      core/os/os2/errors_windows.odin
  47. 90 22
      core/os/os2/file.odin
  48. 82 103
      core/os/os2/file_linux.odin
  49. 13 1
      core/os/os2/file_util.odin
  50. 199 116
      core/os/os2/file_windows.odin
  51. 0 4
      core/os/os2/heap.odin
  52. 2 0
      core/os/os2/internal_util.odin
  53. 8 5
      core/os/os2/path.odin
  54. 13 32
      core/os/os2/path_linux.odin
  55. 97 24
      core/os/os2/path_windows.odin
  56. 3 3
      core/os/os2/pipe_linux.odin
  57. 5 1
      core/os/os2/pipe_windows.odin
  58. 350 46
      core/os/os2/process.odin
  59. 95 0
      core/os/os2/process_linux.odin
  60. 695 0
      core/os/os2/process_windows.odin
  61. 41 7
      core/os/os2/stat.odin
  62. 24 12
      core/os/os2/stat_linux.odin
  63. 37 61
      core/os/os2/stat_windows.odin
  64. 1 1
      core/os/os2/temp_file.odin
  65. 1 1
      core/os/os2/temp_file_windows.odin
  66. 2 2
      core/os/os_freestanding.odin
  67. 15 15
      core/reflect/reflect.odin
  68. 14 15
      core/reflect/types.odin
  69. 8 8
      core/simd/x86/aes.odin
  70. 19 14
      core/simd/x86/sse2.odin
  71. 9 1
      core/sys/linux/bits.odin
  72. 3 3
      core/sys/linux/constants.odin
  73. 31 28
      core/sys/linux/helpers.odin
  74. 3 7
      core/sys/linux/sys.odin
  75. 6 1
      core/sys/linux/types.odin
  76. 250 0
      core/sys/windows/ntdll.odin
  77. 6 0
      core/sys/windows/shell32.odin
  78. 50 30
      core/sys/windows/types.odin
  79. 47 3
      core/testing/runner.odin
  80. 242 39
      core/thread/thread.odin
  81. 5 3
      core/thread/thread_unix.odin
  82. 61 5
      core/time/datetime/constants.odin
  83. 168 4
      core/time/datetime/datetime.odin
  84. 1 0
      core/time/datetime/internal.odin
  85. 43 1
      core/time/datetime/validation.odin
  86. 75 13
      core/time/iso8601.odin
  87. 80 10
      core/time/perf.odin
  88. 80 12
      core/time/rfc3339.odin
  89. 277 8
      core/time/time.odin
  90. 9 0
      examples/README.md
  91. 2 0
      examples/all/all_main.odin
  92. 16 1
      src/build_settings.cpp
  93. 8 8
      src/cached.cpp
  94. 50 3
      src/check_builtin.cpp
  95. 10 2
      src/check_decl.cpp
  96. 33 8
      src/check_expr.cpp
  97. 13 2
      src/check_stmt.cpp
  98. 69 20
      src/check_type.cpp
  99. 23 18
      src/checker.cpp
  100. 9 0
      src/checker.hpp

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

@@ -6,7 +6,7 @@ jobs:
     name: NetBSD Build, Check, and Test
     name: NetBSD Build, Check, and Test
     runs-on: ubuntu-latest
     runs-on: ubuntu-latest
     env:
     env:
-      PKGSRC_BRANCH: 2024Q1
+      PKGSRC_BRANCH: 2024Q2
     steps:
     steps:
     - uses: actions/checkout@v4
     - uses: actions/checkout@v4
     - name: Build, Check, and Test
     - name: Build, Check, and Test
@@ -19,10 +19,7 @@ jobs:
         copyback: false
         copyback: false
         prepare: |
         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)/$(uname -r | cut -d_ -f1)_${PKGSRC_BRANCH}/All" /usr/sbin/pkg_add pkgin
-          pkgin -y in gmake git bash python311
-          pkgin -y in libxml2 perl zstd
-          /usr/sbin/pkg_add https://github.com/andreas-jonsson/llvm17-netbsd-bin/releases/download/pkgsrc-current/llvm-17.0.6.tgz
-          /usr/sbin/pkg_add https://github.com/andreas-jonsson/llvm17-netbsd-bin/releases/download/pkgsrc-current/clang-17.0.6.tgz
+          pkgin -y in gmake git bash python311 llvm clang
           ln -s /usr/pkg/bin/python3.11 /usr/bin/python3
           ln -s /usr/pkg/bin/python3.11 /usr/bin/python3
         run: |
         run: |
           git config --global --add safe.directory $(pwd)
           git config --global --add safe.directory $(pwd)
@@ -91,13 +88,13 @@ jobs:
       - name: Download LLVM (MacOS Intel)
       - name: Download LLVM (MacOS Intel)
         if: matrix.os == 'macos-13'
         if: matrix.os == 'macos-13'
         run: |
         run: |
-          brew install llvm@17
+          brew install llvm@17 [email protected]
           echo "/usr/local/opt/llvm@17/bin" >> $GITHUB_PATH
           echo "/usr/local/opt/llvm@17/bin" >> $GITHUB_PATH
 
 
       - name: Download LLVM (MacOS ARM)
       - name: Download LLVM (MacOS ARM)
         if: matrix.os == 'macos-14'
         if: matrix.os == 'macos-14'
         run: |
         run: |
-          brew install llvm@17 wasmtime
+          brew install llvm@17 wasmtime [email protected]
           echo "/opt/homebrew/opt/llvm@17/bin" >> $GITHUB_PATH
           echo "/opt/homebrew/opt/llvm@17/bin" >> $GITHUB_PATH
 
 
       - name: Build Odin
       - name: Build Odin
@@ -207,6 +204,7 @@ jobs:
         shell: cmd
         shell: cmd
         run: |
         run: |
           call "C:\Program Files\Microsoft Visual Studio\2022\Enterprise\VC\Auxiliary\Build\vcvars64.bat
           call "C:\Program Files\Microsoft Visual Studio\2022\Enterprise\VC\Auxiliary\Build\vcvars64.bat
+          copy vendor\lua\5.4\windows\*.dll .
           odin test tests/vendor -all-packages -define:ODIN_TEST_FANCY=false
           odin test tests/vendor -all-packages -define:ODIN_TEST_FANCY=false
       - name: Odin internals tests
       - name: Odin internals tests
         shell: cmd
         shell: cmd

+ 1 - 32
.gitignore

@@ -24,38 +24,6 @@ bld/
 ![Cc]ore/[Ll]og/
 ![Cc]ore/[Ll]og/
 tests/documentation/verify/
 tests/documentation/verify/
 tests/documentation/all.odin-doc
 tests/documentation/all.odin-doc
-tests/internal/test_map
-tests/internal/test_pow
-tests/internal/test_rtti
-tests/core/test_base64
-tests/core/test_cbor
-tests/core/test_core_compress
-tests/core/test_core_container
-tests/core/test_core_filepath
-tests/core/test_core_fmt
-tests/core/test_core_i18n
-tests/core/test_core_image
-tests/core/test_core_libc
-tests/core/test_core_match
-tests/core/test_core_math
-tests/core/test_core_net
-tests/core/test_core_os_exit
-tests/core/test_core_reflect
-tests/core/test_core_strings
-tests/core/test_core_time
-tests/core/test_crypto
-tests/core/test_hash
-tests/core/test_hex
-tests/core/test_hxa
-tests/core/test_json
-tests/core/test_linalg_glsl_math
-tests/core/test_noise
-tests/core/test_varint
-tests/core/test_xml
-tests/core/test_core_slice
-tests/core/test_core_thread
-tests/core/test_core_runtime
-tests/vendor/vendor_botan
 # Visual Studio 2015 cache/options directory
 # Visual Studio 2015 cache/options directory
 .vs/
 .vs/
 # Visual Studio Code options directory
 # Visual Studio Code options directory
@@ -63,6 +31,7 @@ tests/vendor/vendor_botan
 # Uncomment if you have tasks that create the project's static files in wwwroot
 # Uncomment if you have tasks that create the project's static files in wwwroot
 #wwwroot/
 #wwwroot/
 demo
 demo
+benchmark
 
 
 # MSTest test Results
 # MSTest test Results
 [Tt]est[Rr]esult*/
 [Tt]est[Rr]esult*/

+ 6 - 3
base/intrinsics/intrinsics.odin

@@ -38,9 +38,12 @@ count_leading_zeros  :: proc(x: $T) -> T where type_is_integer(T) || type_is_sim
 reverse_bits         :: proc(x: $T) -> T where type_is_integer(T) || type_is_simd_vector(T) ---
 reverse_bits         :: proc(x: $T) -> T where type_is_integer(T) || type_is_simd_vector(T) ---
 byte_swap            :: proc(x: $T) -> T where type_is_integer(T) || type_is_float(T) ---
 byte_swap            :: proc(x: $T) -> T where type_is_integer(T) || type_is_float(T) ---
 
 
-overflow_add :: proc(lhs, rhs: $T) -> (T, bool) ---
-overflow_sub :: proc(lhs, rhs: $T) -> (T, bool) ---
-overflow_mul :: proc(lhs, rhs: $T) -> (T, bool) ---
+overflow_add :: proc(lhs, rhs: $T) -> (T, bool) where type_is_integer(T) #optional_ok ---
+overflow_sub :: proc(lhs, rhs: $T) -> (T, bool) where type_is_integer(T) #optional_ok ---
+overflow_mul :: proc(lhs, rhs: $T) -> (T, bool) where type_is_integer(T) #optional_ok ---
+
+add_sat :: proc(lhs, rhs: $T) -> T where type_is_integer(T) ---
+sub_sat :: proc(lhs, rhs: $T) -> T where type_is_integer(T) ---
 
 
 sqrt :: proc(x: $T) -> T where type_is_float(T) || (type_is_simd_vector(T) && type_is_float(type_elem_type(T))) ---
 sqrt :: proc(x: $T) -> T where type_is_float(T) || (type_is_simd_vector(T) && type_is_float(type_elem_type(T))) ---
 
 

+ 30 - 20
base/runtime/core.odin

@@ -66,7 +66,7 @@ Type_Info_Named :: struct {
 	name: string,
 	name: string,
 	base: ^Type_Info,
 	base: ^Type_Info,
 	pkg:  string,
 	pkg:  string,
-	loc:  Source_Code_Location,
+	loc:  ^Source_Code_Location,
 }
 }
 Type_Info_Integer    :: struct {signed: bool, endianness: Platform_Endianness}
 Type_Info_Integer    :: struct {signed: bool, endianness: Platform_Endianness}
 Type_Info_Rune       :: struct {}
 Type_Info_Rune       :: struct {}
@@ -112,23 +112,32 @@ Type_Info_Parameters :: struct { // Only used for procedures parameters and resu
 }
 }
 Type_Info_Tuple :: Type_Info_Parameters // Will be removed eventually
 Type_Info_Tuple :: Type_Info_Parameters // Will be removed eventually
 
 
+Type_Info_Struct_Flags :: distinct bit_set[Type_Info_Struct_Flag; u8]
+Type_Info_Struct_Flag :: enum u8 {
+	packed    = 0,
+	raw_union = 1,
+	no_copy   = 2,
+	align     = 3,
+}
+
 Type_Info_Struct :: struct {
 Type_Info_Struct :: struct {
-	types:        []^Type_Info,
-	names:        []string,
-	offsets:      []uintptr,
-	usings:       []bool,
-	tags:         []string,
-	is_packed:    bool,
-	is_raw_union: bool,
-	is_no_copy:   bool,
-	custom_align: bool,
+	// Slice these with `field_count`
+	types:   [^]^Type_Info `fmt:"v,field_count"`,
+	names:   [^]string     `fmt:"v,field_count"`,
+	offsets: [^]uintptr    `fmt:"v,field_count"`,
+	usings:  [^]bool       `fmt:"v,field_count"`,
+	tags:    [^]string     `fmt:"v,field_count"`,
 
 
-	equal: Equal_Proc, // set only when the struct has .Comparable set but does not have .Simple_Compare set
+	field_count: i32,
+
+	flags: Type_Info_Struct_Flags,
 
 
 	// These are only set iff this structure is an SOA structure
 	// These are only set iff this structure is an SOA structure
 	soa_kind:      Type_Info_Struct_Soa_Kind,
 	soa_kind:      Type_Info_Struct_Soa_Kind,
+	soa_len:       i32,
 	soa_base_type: ^Type_Info,
 	soa_base_type: ^Type_Info,
-	soa_len:       int,
+
+	equal: Equal_Proc, // set only when the struct has .Comparable set but does not have .Simple_Compare set
 }
 }
 Type_Info_Union :: struct {
 Type_Info_Union :: struct {
 	variants:     []^Type_Info,
 	variants:     []^Type_Info,
@@ -142,9 +151,9 @@ Type_Info_Union :: struct {
 	shared_nil:   bool,
 	shared_nil:   bool,
 }
 }
 Type_Info_Enum :: struct {
 Type_Info_Enum :: struct {
-	base:      ^Type_Info,
-	names:     []string,
-	values:    []Type_Info_Enum_Value,
+	base:   ^Type_Info,
+	names:  []string,
+	values: []Type_Info_Enum_Value,
 }
 }
 Type_Info_Map :: struct {
 Type_Info_Map :: struct {
 	key:      ^Type_Info,
 	key:      ^Type_Info,
@@ -187,11 +196,12 @@ Type_Info_Soa_Pointer :: struct {
 }
 }
 Type_Info_Bit_Field :: struct {
 Type_Info_Bit_Field :: struct {
 	backing_type: ^Type_Info,
 	backing_type: ^Type_Info,
-	names:        []string,
-	types:        []^Type_Info,
-	bit_sizes:    []uintptr,
-	bit_offsets:  []uintptr,
-	tags:         []string,
+	names:        [^]string     `fmt:"v,field_count"`,
+	types:        [^]^Type_Info `fmt:"v,field_count"`,
+	bit_sizes:    [^]uintptr    `fmt:"v,field_count"`,
+	bit_offsets:  [^]uintptr    `fmt:"v,field_count"`,
+	tags:         [^]string     `fmt:"v,field_count"`,
+	field_count:  int,
 }
 }
 
 
 Type_Info_Flag :: enum u8 {
 Type_Info_Flag :: enum u8 {

+ 104 - 99
base/runtime/core_builtin.odin

@@ -333,16 +333,23 @@ make_dynamic_array_len :: proc($T: typeid/[dynamic]$E, #any_int len: int, alloca
 // Note: Prefer using the procedure group `make`.
 // Note: Prefer using the procedure group `make`.
 @(builtin, require_results)
 @(builtin, require_results)
 make_dynamic_array_len_cap :: proc($T: typeid/[dynamic]$E, #any_int len: int, #any_int cap: int, allocator := context.allocator, loc := #caller_location) -> (array: T, err: Allocator_Error) #optional_allocator_error {
 make_dynamic_array_len_cap :: proc($T: typeid/[dynamic]$E, #any_int len: int, #any_int cap: int, allocator := context.allocator, loc := #caller_location) -> (array: T, err: Allocator_Error) #optional_allocator_error {
+	err = _make_dynamic_array_len_cap((^Raw_Dynamic_Array)(&array), size_of(E), align_of(E), len, cap, allocator, loc)
+	return
+}
+
+@(require_results)
+_make_dynamic_array_len_cap :: proc(array: ^Raw_Dynamic_Array, size_of_elem, align_of_elem: int, #any_int len: int, #any_int cap: int, allocator := context.allocator, loc := #caller_location) -> (err: Allocator_Error) {
 	make_dynamic_array_error_loc(loc, len, cap)
 	make_dynamic_array_error_loc(loc, len, cap)
 	array.allocator = allocator // initialize allocator before just in case it fails to allocate any memory
 	array.allocator = allocator // initialize allocator before just in case it fails to allocate any memory
-	data := mem_alloc_bytes(size_of(E)*cap, align_of(E), allocator, loc) or_return
-	s := Raw_Dynamic_Array{raw_data(data), len, cap, allocator}
-	if data == nil && size_of(E) != 0 {
-		s.len, s.cap = 0, 0
-	}
-	array = transmute(T)s
+	data := mem_alloc_bytes(size_of_elem*cap, align_of_elem, allocator, loc) or_return
+	use_zero := data == nil && size_of_elem != 0
+	array.data = raw_data(data)
+	array.len = 0 if use_zero else len
+	array.cap = 0 if use_zero else cap
+	array.allocator = allocator
 	return
 	return
 }
 }
+
 // `make_map` allocates and initializes a dynamic array. Like `new`, the first argument is a type, not a value.
 // `make_map` allocates and initializes a dynamic array. Like `new`, the first argument is a type, not a value.
 // Unlike `new`, `make`'s return value is the same as the type of its argument, not a pointer to it.
 // Unlike `new`, `make`'s return value is the same as the type of its argument, not a pointer to it.
 //
 //
@@ -440,107 +447,103 @@ delete_key :: proc(m: ^$T/map[$K]$V, key: K) -> (deleted_key: K, deleted_value:
 	return
 	return
 }
 }
 
 
-_append_elem :: #force_inline proc(array: ^$T/[dynamic]$E, arg: E, should_zero: bool, loc := #caller_location) -> (n: int, err: Allocator_Error) #optional_allocator_error {
+_append_elem :: #force_inline proc(array: ^Raw_Dynamic_Array, size_of_elem, align_of_elem: int, arg_ptr: rawptr, should_zero: bool, loc := #caller_location) -> (n: int, err: Allocator_Error) #optional_allocator_error {
 	if array == nil {
 	if array == nil {
-		return 0, nil
+		return
 	}
 	}
-	when size_of(E) == 0 {
-		array := (^Raw_Dynamic_Array)(array)
+
+	if array.cap < array.len+1 {
+		// Same behavior as _append_elems but there's only one arg, so we always just add DEFAULT_DYNAMIC_ARRAY_CAPACITY.
+		cap := 2 * array.cap + DEFAULT_DYNAMIC_ARRAY_CAPACITY
+
+		// do not 'or_return' here as it could be a partial success
+		err = _reserve_dynamic_array(array, size_of_elem, align_of_elem, cap, should_zero, loc)
+	}
+	if array.cap-array.len > 0 {
+		data := ([^]byte)(array.data)
+		assert(data != nil, loc=loc)
+		data = data[array.len*size_of_elem:]
+		intrinsics.mem_copy_non_overlapping(data, arg_ptr, size_of_elem)
 		array.len += 1
 		array.len += 1
-		return 1, nil
-	} else {
-		if cap(array) < len(array)+1 {
-			// Same behavior as _append_elems but there's only one arg, so we always just add DEFAULT_DYNAMIC_ARRAY_CAPACITY.
-			cap := 2 * cap(array) + DEFAULT_DYNAMIC_ARRAY_CAPACITY
-
-			// do not 'or_return' here as it could be a partial success
-			if should_zero {
-				err = reserve(array, cap, loc)
-			} else {
-				err = non_zero_reserve(array, cap, loc) 
-			}
-		}
-		if cap(array)-len(array) > 0 {
-			a := (^Raw_Dynamic_Array)(array)
-			when size_of(E) != 0 {
-				data := ([^]E)(a.data)
-				assert(data != nil, loc=loc)
-				data[a.len] = arg
-			}
-			a.len += 1
-			return 1, err
-		}
-		return 0, err
+		n = 1
 	}
 	}
+	return
 }
 }
 
 
 @builtin
 @builtin
 append_elem :: proc(array: ^$T/[dynamic]$E, #no_broadcast arg: E, loc := #caller_location) -> (n: int, err: Allocator_Error) #optional_allocator_error {
 append_elem :: proc(array: ^$T/[dynamic]$E, #no_broadcast arg: E, loc := #caller_location) -> (n: int, err: Allocator_Error) #optional_allocator_error {
-	return _append_elem(array, arg, true, loc=loc)
+	when size_of(E) == 0 {
+		(^Raw_Dynamic_Array)(array).len += 1
+		return 1, nil
+	} else {
+		arg := arg
+		return _append_elem((^Raw_Dynamic_Array)(array), size_of(E), align_of(E), &arg, true, loc=loc)
+	}
 }
 }
 
 
 @builtin
 @builtin
 non_zero_append_elem :: proc(array: ^$T/[dynamic]$E, #no_broadcast arg: E, loc := #caller_location) -> (n: int, err: Allocator_Error) #optional_allocator_error {
 non_zero_append_elem :: proc(array: ^$T/[dynamic]$E, #no_broadcast arg: E, loc := #caller_location) -> (n: int, err: Allocator_Error) #optional_allocator_error {
-	return _append_elem(array, arg, false, loc=loc)
+	when size_of(E) == 0 {
+		(^Raw_Dynamic_Array)(array).len += 1
+		return 1, nil
+	} else {
+		arg := arg
+		return _append_elem((^Raw_Dynamic_Array)(array), size_of(E), align_of(E), &arg, false, loc=loc)
+	}
 }
 }
 
 
-_append_elems :: #force_inline proc(array: ^$T/[dynamic]$E, should_zero: bool, loc := #caller_location, args: ..E) -> (n: int, err: Allocator_Error) #optional_allocator_error {
+_append_elems :: #force_inline proc(array: ^Raw_Dynamic_Array, size_of_elem, align_of_elem: int, should_zero: bool, loc := #caller_location, args: rawptr, arg_len: int) -> (n: int, err: Allocator_Error) #optional_allocator_error {
 	if array == nil {
 	if array == nil {
 		return 0, nil
 		return 0, nil
 	}
 	}
 
 
-	arg_len := len(args)
 	if arg_len <= 0 {
 	if arg_len <= 0 {
 		return 0, nil
 		return 0, nil
 	}
 	}
 
 
-	when size_of(E) == 0 {
-		array := (^Raw_Dynamic_Array)(array)
+	if array.cap < array.len+arg_len {
+		cap := 2 * array.cap + max(DEFAULT_DYNAMIC_ARRAY_CAPACITY, arg_len)
+
+		// do not 'or_return' here as it could be a partial success
+		err = _reserve_dynamic_array(array, size_of_elem, align_of_elem, cap, should_zero, loc)
+	}
+	arg_len := arg_len
+	arg_len = min(array.cap-array.len, arg_len)
+	if arg_len > 0 {
+		data := ([^]byte)(array.data)
+		assert(data != nil, loc=loc)
+		data = data[array.len*size_of_elem:]
+		intrinsics.mem_copy(data, args, size_of_elem * arg_len) // must be mem_copy (overlapping)
 		array.len += arg_len
 		array.len += arg_len
-		return arg_len, nil
-	} else {
-		if cap(array) < len(array)+arg_len {
-			cap := 2 * cap(array) + max(DEFAULT_DYNAMIC_ARRAY_CAPACITY, arg_len)
-
-			// do not 'or_return' here as it could be a partial success
-			if should_zero {
-				err = reserve(array, cap, loc)
-			} else {
-				err = non_zero_reserve(array, cap, loc)
-			}
-		}
-		arg_len = min(cap(array)-len(array), arg_len)
-		if arg_len > 0 {
-			a := (^Raw_Dynamic_Array)(array)
-			when size_of(E) != 0 {
-				data := ([^]E)(a.data)
-				assert(data != nil, loc=loc)
-				intrinsics.mem_copy(&data[a.len], raw_data(args), size_of(E) * arg_len)
-			}
-			a.len += arg_len
-		}
-		return arg_len, err
 	}
 	}
+	return arg_len, err
 }
 }
 
 
 @builtin
 @builtin
 append_elems :: proc(array: ^$T/[dynamic]$E, #no_broadcast args: ..E, loc := #caller_location) -> (n: int, err: Allocator_Error) #optional_allocator_error {
 append_elems :: proc(array: ^$T/[dynamic]$E, #no_broadcast args: ..E, loc := #caller_location) -> (n: int, err: Allocator_Error) #optional_allocator_error {
-	return _append_elems(array, true, loc, ..args)
+	when size_of(E) == 0 {
+		a := (^Raw_Dynamic_Array)(array)
+		a.len += len(args)
+		return len(args), nil
+	} else {
+		return _append_elems((^Raw_Dynamic_Array)(array), size_of(E), align_of(E), true, loc, raw_data(args), len(args))
+	}
 }
 }
 
 
 @builtin
 @builtin
 non_zero_append_elems :: proc(array: ^$T/[dynamic]$E, #no_broadcast args: ..E, loc := #caller_location) -> (n: int, err: Allocator_Error) #optional_allocator_error {
 non_zero_append_elems :: proc(array: ^$T/[dynamic]$E, #no_broadcast args: ..E, loc := #caller_location) -> (n: int, err: Allocator_Error) #optional_allocator_error {
-	return _append_elems(array, false, loc, ..args)
+	when size_of(E) == 0 {
+		a := (^Raw_Dynamic_Array)(array)
+		a.len += len(args)
+		return len(args), nil
+	} else {
+		return _append_elems((^Raw_Dynamic_Array)(array), size_of(E), align_of(E), false, loc, raw_data(args), len(args))
+	}
 }
 }
 
 
 // The append_string built-in procedure appends a string to the end of a [dynamic]u8 like type
 // The append_string built-in procedure appends a string to the end of a [dynamic]u8 like type
 _append_elem_string :: proc(array: ^$T/[dynamic]$E/u8, arg: $A/string, should_zero: bool, loc := #caller_location) -> (n: int, err: Allocator_Error) #optional_allocator_error {
 _append_elem_string :: proc(array: ^$T/[dynamic]$E/u8, arg: $A/string, should_zero: bool, loc := #caller_location) -> (n: int, err: Allocator_Error) #optional_allocator_error {
-	args := transmute([]E)arg
-	if should_zero { 
-		return append_elems(array, ..args, loc=loc)
-	} else {
-		return non_zero_append_elems(array, ..args, loc=loc)
-	}
+	return _append_elems((^Raw_Dynamic_Array)(array), 1, 1, should_zero, loc, raw_data(arg), len(arg))
 }
 }
 
 
 @builtin
 @builtin
@@ -679,7 +682,7 @@ assign_at_elem :: proc(array: ^$T/[dynamic]$E, index: int, arg: E, loc := #calle
 
 
 
 
 @builtin
 @builtin
-assign_at_elems :: proc(array: ^$T/[dynamic]$E, index: int, args: ..E, loc := #caller_location) -> (ok: bool, err: Allocator_Error) #no_bounds_check #optional_allocator_error {
+assign_at_elems :: proc(array: ^$T/[dynamic]$E, index: int, #no_broadcast args: ..E, loc := #caller_location) -> (ok: bool, err: Allocator_Error) #no_bounds_check #optional_allocator_error {
 	new_size := index + len(args)
 	new_size := index + len(args)
 	if len(args) == 0 {
 	if len(args) == 0 {
 		ok = true
 		ok = true
@@ -729,11 +732,10 @@ clear_dynamic_array :: proc "contextless" (array: ^$T/[dynamic]$E) {
 // `reserve_dynamic_array` will try to reserve memory of a passed dynamic array or map to the requested element count (setting the `cap`).
 // `reserve_dynamic_array` will try to reserve memory of a passed dynamic array or map to the requested element count (setting the `cap`).
 //
 //
 // Note: Prefer the procedure group `reserve`.
 // Note: Prefer the procedure group `reserve`.
-_reserve_dynamic_array :: #force_inline proc(array: ^$T/[dynamic]$E, capacity: int, should_zero: bool, loc := #caller_location) -> Allocator_Error {
-	if array == nil {
+_reserve_dynamic_array :: #force_inline proc(a: ^Raw_Dynamic_Array, size_of_elem, align_of_elem: int, capacity: int, should_zero: bool, loc := #caller_location) -> Allocator_Error {
+	if a == nil {
 		return nil
 		return nil
 	}
 	}
-	a := (^Raw_Dynamic_Array)(array)
 
 
 	if capacity <= a.cap {
 	if capacity <= a.cap {
 		return nil
 		return nil
@@ -744,15 +746,15 @@ _reserve_dynamic_array :: #force_inline proc(array: ^$T/[dynamic]$E, capacity: i
 	}
 	}
 	assert(a.allocator.procedure != nil)
 	assert(a.allocator.procedure != nil)
 
 
-	old_size  := a.cap * size_of(E)
-	new_size  := capacity * size_of(E)
+	old_size  := a.cap * size_of_elem
+	new_size  := capacity * size_of_elem
 	allocator := a.allocator
 	allocator := a.allocator
 
 
 	new_data: []byte
 	new_data: []byte
 	if should_zero {
 	if should_zero {
-		new_data = mem_resize(a.data, old_size, new_size, align_of(E), allocator, loc) or_return
+		new_data = mem_resize(a.data, old_size, new_size, align_of_elem, allocator, loc) or_return
 	} else {
 	} else {
-		new_data = non_zero_mem_resize(a.data, old_size, new_size, align_of(E), allocator, loc) or_return
+		new_data = non_zero_mem_resize(a.data, old_size, new_size, align_of_elem, allocator, loc) or_return
 	}
 	}
 	if new_data == nil && new_size > 0 {
 	if new_data == nil && new_size > 0 {
 		return .Out_Of_Memory
 		return .Out_Of_Memory
@@ -765,26 +767,23 @@ _reserve_dynamic_array :: #force_inline proc(array: ^$T/[dynamic]$E, capacity: i
 
 
 @builtin
 @builtin
 reserve_dynamic_array :: proc(array: ^$T/[dynamic]$E, #any_int capacity: int, loc := #caller_location) -> Allocator_Error {
 reserve_dynamic_array :: proc(array: ^$T/[dynamic]$E, #any_int capacity: int, loc := #caller_location) -> Allocator_Error {
-	return _reserve_dynamic_array(array, capacity, true, loc)
+	return _reserve_dynamic_array((^Raw_Dynamic_Array)(array), size_of(E), align_of(E), capacity, true, loc)
 }
 }
 
 
 @builtin
 @builtin
 non_zero_reserve_dynamic_array :: proc(array: ^$T/[dynamic]$E, #any_int capacity: int, loc := #caller_location) -> Allocator_Error {
 non_zero_reserve_dynamic_array :: proc(array: ^$T/[dynamic]$E, #any_int capacity: int, loc := #caller_location) -> Allocator_Error {
-	return _reserve_dynamic_array(array, capacity, false, loc)
+	return _reserve_dynamic_array((^Raw_Dynamic_Array)(array), size_of(E), align_of(E), capacity, false, loc)
 }
 }
 
 
-// `resize_dynamic_array` will try to resize memory of a passed dynamic array or map to the requested element count (setting the `len`, and possibly `cap`).
-//
-// Note: Prefer the procedure group `resize`
-_resize_dynamic_array :: #force_inline proc(array: ^$T/[dynamic]$E, length: int, should_zero: bool, loc := #caller_location) -> Allocator_Error {
-	if array == nil {
+
+_resize_dynamic_array :: #force_inline proc(a: ^Raw_Dynamic_Array, size_of_elem, align_of_elem: int, length: int, should_zero: bool, loc := #caller_location) -> Allocator_Error {
+	if a == nil {
 		return nil
 		return nil
 	}
 	}
-	a := (^Raw_Dynamic_Array)(array)
 
 
 	if length <= a.cap {
 	if length <= a.cap {
 		if should_zero && a.len < length {
 		if should_zero && a.len < length {
-			intrinsics.mem_zero(([^]E)(a.data)[a.len:], (length-a.len)*size_of(E))
+			intrinsics.mem_zero(([^]byte)(a.data)[a.len*size_of_elem:], (length-a.len)*size_of_elem)
 		}
 		}
 		a.len = max(length, 0)
 		a.len = max(length, 0)
 		return nil
 		return nil
@@ -795,15 +794,15 @@ _resize_dynamic_array :: #force_inline proc(array: ^$T/[dynamic]$E, length: int,
 	}
 	}
 	assert(a.allocator.procedure != nil)
 	assert(a.allocator.procedure != nil)
 
 
-	old_size  := a.cap * size_of(E)
-	new_size  := length * size_of(E)
+	old_size  := a.cap  * size_of_elem
+	new_size  := length * size_of_elem
 	allocator := a.allocator
 	allocator := a.allocator
 
 
 	new_data : []byte
 	new_data : []byte
 	if should_zero {
 	if should_zero {
-		new_data = mem_resize(a.data, old_size, new_size, align_of(E), allocator, loc) or_return
+		new_data = mem_resize(a.data, old_size, new_size, align_of_elem, allocator, loc) or_return
 	} else {
 	} else {
-		new_data = non_zero_mem_resize(a.data, old_size, new_size, align_of(E), allocator, loc) or_return
+		new_data = non_zero_mem_resize(a.data, old_size, new_size, align_of_elem, allocator, loc) or_return
 	}
 	}
 	if new_data == nil && new_size > 0 {
 	if new_data == nil && new_size > 0 {
 		return .Out_Of_Memory
 		return .Out_Of_Memory
@@ -815,14 +814,17 @@ _resize_dynamic_array :: #force_inline proc(array: ^$T/[dynamic]$E, length: int,
 	return nil
 	return nil
 }
 }
 
 
+// `resize_dynamic_array` will try to resize memory of a passed dynamic array or map to the requested element count (setting the `len`, and possibly `cap`).
+//
+// Note: Prefer the procedure group `resize`
 @builtin
 @builtin
 resize_dynamic_array :: proc(array: ^$T/[dynamic]$E, #any_int length: int, loc := #caller_location) -> Allocator_Error {
 resize_dynamic_array :: proc(array: ^$T/[dynamic]$E, #any_int length: int, loc := #caller_location) -> Allocator_Error {
-	return _resize_dynamic_array(array, length, true, loc=loc)
+	return _resize_dynamic_array((^Raw_Dynamic_Array)(array), size_of(E), align_of(E), length, true, loc=loc)
 }
 }
 
 
 @builtin
 @builtin
 non_zero_resize_dynamic_array :: proc(array: ^$T/[dynamic]$E, #any_int length: int, loc := #caller_location) -> Allocator_Error {
 non_zero_resize_dynamic_array :: proc(array: ^$T/[dynamic]$E, #any_int length: int, loc := #caller_location) -> Allocator_Error {
-	return _resize_dynamic_array(array, length, false, loc=loc)
+	return _resize_dynamic_array((^Raw_Dynamic_Array)(array), size_of(E), align_of(E), length, false, loc=loc)
 }
 }
 
 
 /*
 /*
@@ -837,10 +839,13 @@ non_zero_resize_dynamic_array :: proc(array: ^$T/[dynamic]$E, #any_int length: i
 	Note: Prefer the procedure group `shrink`
 	Note: Prefer the procedure group `shrink`
 */
 */
 shrink_dynamic_array :: proc(array: ^$T/[dynamic]$E, new_cap := -1, loc := #caller_location) -> (did_shrink: bool, err: Allocator_Error) {
 shrink_dynamic_array :: proc(array: ^$T/[dynamic]$E, new_cap := -1, loc := #caller_location) -> (did_shrink: bool, err: Allocator_Error) {
-	if array == nil {
+	return _shrink_dynamic_array((^Raw_Dynamic_Array)(array), size_of(E), align_of(E), new_cap, loc)
+}
+
+_shrink_dynamic_array :: proc(a: ^Raw_Dynamic_Array, size_of_elem, align_of_elem: int, new_cap := -1, loc := #caller_location) -> (did_shrink: bool, err: Allocator_Error) {
+	if a == nil {
 		return
 		return
 	}
 	}
-	a := (^Raw_Dynamic_Array)(array)
 
 
 	new_cap := new_cap if new_cap >= 0 else a.len
 	new_cap := new_cap if new_cap >= 0 else a.len
 
 
@@ -853,10 +858,10 @@ shrink_dynamic_array :: proc(array: ^$T/[dynamic]$E, new_cap := -1, loc := #call
 	}
 	}
 	assert(a.allocator.procedure != nil)
 	assert(a.allocator.procedure != nil)
 
 
-	old_size := a.cap * size_of(E)
-	new_size := new_cap * size_of(E)
+	old_size := a.cap * size_of_elem
+	new_size := new_cap * size_of_elem
 
 
-	new_data := mem_resize(a.data, old_size, new_size, align_of(E), a.allocator, loc) or_return
+	new_data := mem_resize(a.data, old_size, new_size, align_of_elem, a.allocator, loc) or_return
 
 
 	a.data = raw_data(new_data)
 	a.data = raw_data(new_data)
 	a.len = min(new_cap, a.len)
 	a.len = min(new_cap, a.len)

+ 1 - 1
base/runtime/core_builtin_soa.odin

@@ -352,7 +352,7 @@ non_zero_append_soa_elems :: proc(array: ^$T/#soa[dynamic]$E, #no_broadcast args
 }
 }
 
 
 
 
-_append_soa_elems :: proc(array: ^$T/#soa[dynamic]$E, zero_memory: bool, #no_broadcast args: ..E, loc := #caller_location) -> (n: int, err: Allocator_Error) #optional_allocator_error {
+_append_soa_elems :: proc(array: ^$T/#soa[dynamic]$E, zero_memory: bool, #no_broadcast args: []E, loc := #caller_location) -> (n: int, err: Allocator_Error) #optional_allocator_error {
 	if array == nil {
 	if array == nil {
 		return
 		return
 	}
 	}

+ 6 - 6
base/runtime/dynamic_map_internal.odin

@@ -577,7 +577,7 @@ map_grow_dynamic :: proc "odin" (#no_alias m: ^Raw_Map, #no_alias info: ^Map_Inf
 
 
 
 
 @(require_results)
 @(require_results)
-map_reserve_dynamic :: proc "odin" (#no_alias m: ^Raw_Map, #no_alias info: ^Map_Info, new_capacity: uintptr, loc := #caller_location) -> Allocator_Error {
+map_reserve_dynamic :: #force_no_inline proc "odin" (#no_alias m: ^Raw_Map, #no_alias info: ^Map_Info, new_capacity: uintptr, loc := #caller_location) -> Allocator_Error {
 	@(require_results)
 	@(require_results)
 	ceil_log2 :: #force_inline proc "contextless" (x: uintptr) -> uintptr {
 	ceil_log2 :: #force_inline proc "contextless" (x: uintptr) -> uintptr {
 		z := intrinsics.count_leading_zeros(x)
 		z := intrinsics.count_leading_zeros(x)
@@ -641,7 +641,7 @@ map_reserve_dynamic :: proc "odin" (#no_alias m: ^Raw_Map, #no_alias info: ^Map_
 
 
 
 
 @(require_results)
 @(require_results)
-map_shrink_dynamic :: proc "odin" (#no_alias m: ^Raw_Map, #no_alias info: ^Map_Info, loc := #caller_location) -> (did_shrink: bool, err: Allocator_Error) {
+map_shrink_dynamic :: #force_no_inline proc "odin" (#no_alias m: ^Raw_Map, #no_alias info: ^Map_Info, loc := #caller_location) -> (did_shrink: bool, err: Allocator_Error) {
 	if m.allocator.procedure == nil {
 	if m.allocator.procedure == nil {
 		m.allocator = context.allocator
 		m.allocator = context.allocator
 	}
 	}
@@ -688,7 +688,7 @@ map_shrink_dynamic :: proc "odin" (#no_alias m: ^Raw_Map, #no_alias info: ^Map_I
 }
 }
 
 
 @(require_results)
 @(require_results)
-map_free_dynamic :: proc "odin" (m: Raw_Map, info: ^Map_Info, loc := #caller_location) -> Allocator_Error {
+map_free_dynamic :: #force_no_inline proc "odin" (m: Raw_Map, info: ^Map_Info, loc := #caller_location) -> Allocator_Error {
 	ptr := rawptr(map_data(m))
 	ptr := rawptr(map_data(m))
 	size := int(map_total_allocation_size(uintptr(map_cap(m)), info))
 	size := int(map_total_allocation_size(uintptr(map_cap(m)), info))
 	err := mem_free_with_size(ptr, size, m.allocator, loc)
 	err := mem_free_with_size(ptr, size, m.allocator, loc)
@@ -700,7 +700,7 @@ map_free_dynamic :: proc "odin" (m: Raw_Map, info: ^Map_Info, loc := #caller_loc
 }
 }
 
 
 @(require_results)
 @(require_results)
-map_lookup_dynamic :: proc "contextless" (m: Raw_Map, #no_alias info: ^Map_Info, k: uintptr) -> (index: uintptr, ok: bool) {
+map_lookup_dynamic :: #force_no_inline proc "contextless" (m: Raw_Map, #no_alias info: ^Map_Info, k: uintptr) -> (index: uintptr, ok: bool) {
 	if map_len(m) == 0 {
 	if map_len(m) == 0 {
 		return 0, false
 		return 0, false
 	}
 	}
@@ -723,7 +723,7 @@ map_lookup_dynamic :: proc "contextless" (m: Raw_Map, #no_alias info: ^Map_Info,
 	}
 	}
 }
 }
 @(require_results)
 @(require_results)
-map_exists_dynamic :: proc "contextless" (m: Raw_Map, #no_alias info: ^Map_Info, k: uintptr) -> (ok: bool) {
+map_exists_dynamic :: #force_no_inline proc "contextless" (m: Raw_Map, #no_alias info: ^Map_Info, k: uintptr) -> (ok: bool) {
 	if map_len(m) == 0 {
 	if map_len(m) == 0 {
 		return false
 		return false
 	}
 	}
@@ -749,7 +749,7 @@ map_exists_dynamic :: proc "contextless" (m: Raw_Map, #no_alias info: ^Map_Info,
 
 
 
 
 @(require_results)
 @(require_results)
-map_erase_dynamic :: #force_inline proc "contextless" (#no_alias m: ^Raw_Map, #no_alias info: ^Map_Info, k: uintptr) -> (old_k, old_v: uintptr, ok: bool) {
+map_erase_dynamic :: #force_no_inline proc "contextless" (#no_alias m: ^Raw_Map, #no_alias info: ^Map_Info, k: uintptr) -> (old_k, old_v: uintptr, ok: bool) {
 	index := map_lookup_dynamic(m^, info, k) or_return
 	index := map_lookup_dynamic(m^, info, k) or_return
 	ks, vs, hs, _, _ := map_kvh_data_dynamic(m^, info)
 	ks, vs, hs, _, _ := map_kvh_data_dynamic(m^, info)
 	hs[index] |= TOMBSTONE_MASK
 	hs[index] |= TOMBSTONE_MASK

+ 6 - 5
base/runtime/print.odin

@@ -401,15 +401,16 @@ print_type :: #force_no_inline proc "contextless" (ti: ^Type_Info) {
 		}
 		}
 
 
 		print_string("struct ")
 		print_string("struct ")
-		if info.is_packed    { print_string("#packed ") }
-		if info.is_raw_union { print_string("#raw_union ") }
-		if info.custom_align {
+		if .packed    in info.flags { print_string("#packed ") }
+		if .raw_union in info.flags { print_string("#raw_union ") }
+		if .no_copy   in info.flags { print_string("#no_copy ") }
+		if .align in info.flags {
 			print_string("#align(")
 			print_string("#align(")
 			print_u64(u64(ti.align))
 			print_u64(u64(ti.align))
 			print_string(") ")
 			print_string(") ")
 		}
 		}
 		print_byte('{')
 		print_byte('{')
-		for name, i in info.names {
+		for name, i in info.names[:info.field_count] {
 			if i > 0 { print_string(", ") }
 			if i > 0 { print_string(", ") }
 			print_string(name)
 			print_string(name)
 			print_string(": ")
 			print_string(": ")
@@ -469,7 +470,7 @@ print_type :: #force_no_inline proc "contextless" (ti: ^Type_Info) {
 		print_string("bit_field ")
 		print_string("bit_field ")
 		print_type(info.backing_type)
 		print_type(info.backing_type)
 		print_string(" {")
 		print_string(" {")
-		for name, i in info.names {
+		for name, i in info.names[:info.field_count] {
 			if i > 0 { print_string(", ") }
 			if i > 0 { print_string(", ") }
 			print_string(name)
 			print_string(name)
 			print_string(": ")
 			print_string(": ")

+ 2 - 1
base/runtime/wasm_allocator.odin

@@ -297,7 +297,8 @@ lock :: proc(a: ^WASM_Allocator) {
 					return
 					return
 				}
 				}
 
 
-				assert(intrinsics.wasm_memory_atomic_wait32((^u32)(&a.mu), u32(new_state), -1) != 0)
+				ret := intrinsics.wasm_memory_atomic_wait32((^u32)(&a.mu), u32(new_state), -1)
+				assert(ret != 0)
 				intrinsics.cpu_relax()
 				intrinsics.cpu_relax()
 			}
 			}
 		}
 		}

+ 25 - 0
core/bytes/bytes.odin

@@ -1167,3 +1167,28 @@ fields_proc :: proc(s: []byte, f: proc(rune) -> bool, allocator := context.alloc
 
 
 	return subslices[:]
 	return subslices[:]
 }
 }
+
+// alias returns true iff a and b have a non-zero length, and any part of
+// a overlaps with b.
+alias :: proc "contextless" (a, b: []byte) -> bool {
+	a_len, b_len := len(a), len(b)
+	if a_len == 0 || b_len == 0 {
+		return false
+	}
+
+	a_start, b_start := uintptr(raw_data(a)), uintptr(raw_data(b))
+	a_end, b_end := a_start + uintptr(a_len-1), b_start + uintptr(b_len-1)
+
+	return a_start <= b_end && b_start <= a_end
+}
+
+// alias_inexactly returns true iff a and b have a non-zero length,
+// the base pointer of a and b are NOT equal, and any part of a overlaps
+// with b (ie: `alias(a, b)` with an exception that returns false for
+// `a == b`, `b = a[:len(a)-69]` and similar conditions).
+alias_inexactly :: proc "contextless" (a, b: []byte) -> bool {
+	if raw_data(a) == raw_data(b) {
+		return false
+	}
+	return alias(a, b)
+}

+ 2 - 2
core/compress/zlib/zlib.odin

@@ -235,7 +235,7 @@ allocate_huffman_table :: proc(allocator := context.allocator) -> (z: ^Huffman_T
 }
 }
 
 
 @(optimization_mode="favor_size")
 @(optimization_mode="favor_size")
-build_huffman :: proc(z: ^Huffman_Table, code_lengths: []u8) -> (err: Error) {
+build_huffman :: #force_no_inline proc(z: ^Huffman_Table, code_lengths: []u8) -> (err: Error) {
 	sizes:     [HUFFMAN_MAX_BITS+1]int
 	sizes:     [HUFFMAN_MAX_BITS+1]int
 	next_code: [HUFFMAN_MAX_BITS+1]int
 	next_code: [HUFFMAN_MAX_BITS+1]int
 
 
@@ -670,4 +670,4 @@ inflate_from_byte_array_raw :: proc(input: []u8, buf: ^bytes.Buffer, raw := fals
 	return inflate_raw(&ctx, expected_output_size=expected_output_size)
 	return inflate_raw(&ctx, expected_output_size=expected_output_size)
 }
 }
 
 
-inflate :: proc{inflate_from_context, inflate_from_byte_array}
+inflate :: proc{inflate_from_context, inflate_from_byte_array}

+ 46 - 0
core/container/intrusive/list/doc.odin

@@ -0,0 +1,46 @@
+/*
+Package list implements an intrusive doubly-linked list.
+
+An intrusive container requires a `Node` to be embedded in your own structure, like this:
+
+	My_String :: struct {
+		node:  list.Node,
+		value: string,
+	}
+
+Embedding the members of a `list.Node` in your structure with the `using` keyword is also allowed:
+
+	My_String :: struct {
+		using node: list.Node,
+		value: string,
+	}
+
+Here is a full example:
+
+	package test
+	
+	import "core:fmt"
+	import "core:container/intrusive/list"
+	
+	main :: proc() {
+	    l: list.List
+	
+	    one := My_String{value="Hello"}
+	    two := My_String{value="World"}
+	
+	    list.push_back(&l, &one.node)
+	    list.push_back(&l, &two.node)
+	
+	    iter := list.iterator_head(l, My_String, "node")
+	    for s in list.iterate_next(&iter) {
+	        fmt.println(s.value)
+	    }
+	}
+	
+	My_String :: struct {
+	    node:  list.Node,
+	    value: string,
+	}
+
+*/
+package container_intrusive_list

+ 169 - 2
core/container/intrusive/list/intrusive_list.odin

@@ -18,11 +18,18 @@ List :: struct {
 	tail: ^Node,
 	tail: ^Node,
 }
 }
 
 
-
+// The list link you must include in your own structure.
 Node :: struct {
 Node :: struct {
 	prev, next: ^Node,
 	prev, next: ^Node,
 }
 }
 
 
+/*
+Inserts a new element at the front of the list with O(1) time complexity.
+
+**Inputs**
+- list: The container list
+- node: The node member of the user-defined element structure
+*/
 push_front :: proc "contextless" (list: ^List, node: ^Node) {
 push_front :: proc "contextless" (list: ^List, node: ^Node) {
 	if list.head != nil {
 	if list.head != nil {
 		list.head.prev = node
 		list.head.prev = node
@@ -33,7 +40,13 @@ push_front :: proc "contextless" (list: ^List, node: ^Node) {
 		node.prev, node.next = nil, nil
 		node.prev, node.next = nil, nil
 	}
 	}
 }
 }
+/*
+Inserts a new element at the back of the list with O(1) time complexity.
 
 
+**Inputs**
+- list: The container list
+- node: The node member of the user-defined element structure
+*/
 push_back :: proc "contextless" (list: ^List, node: ^Node) {
 push_back :: proc "contextless" (list: ^List, node: ^Node) {
 	if list.tail != nil {
 	if list.tail != nil {
 		list.tail.next = node
 		list.tail.next = node
@@ -45,6 +58,13 @@ push_back :: proc "contextless" (list: ^List, node: ^Node) {
 	}
 	}
 }
 }
 
 
+/*
+Removes an element from a list with O(1) time complexity.
+
+**Inputs**
+- list: The container list
+- node: The node member of the user-defined element structure to be removed
+*/
 remove :: proc "contextless" (list: ^List, node: ^Node) {
 remove :: proc "contextless" (list: ^List, node: ^Node) {
 	if node != nil {
 	if node != nil {
 		if node.next != nil {
 		if node.next != nil {
@@ -61,7 +81,13 @@ remove :: proc "contextless" (list: ^List, node: ^Node) {
 		}
 		}
 	}
 	}
 }
 }
+/*
+Removes from the given list all elements that satisfy a condition with O(N) time complexity.
 
 
+**Inputs**
+- list: The container list
+- to_erase: The condition procedure. It should return `true` if a node should be removed, `false` otherwise
+*/
 remove_by_proc :: proc(list: ^List, to_erase: proc(^Node) -> bool) {
 remove_by_proc :: proc(list: ^List, to_erase: proc(^Node) -> bool) {
 	for node := list.head; node != nil; {
 	for node := list.head; node != nil; {
 		next := node.next
 		next := node.next
@@ -82,7 +108,13 @@ remove_by_proc :: proc(list: ^List, to_erase: proc(^Node) -> bool) {
 		node = next
 		node = next
 	}
 	}
 }
 }
+/*
+Removes from the given list all elements that satisfy a condition with O(N) time complexity.
 
 
+**Inputs**
+- list: The container list
+- to_erase: The _contextless_ condition procedure. It should return `true` if a node should be removed, `false` otherwise
+*/
 remove_by_proc_contextless :: proc(list: ^List, to_erase: proc "contextless" (^Node) -> bool) {
 remove_by_proc_contextless :: proc(list: ^List, to_erase: proc "contextless" (^Node) -> bool) {
 	for node := list.head; node != nil; {
 	for node := list.head; node != nil; {
 		next := node.next
 		next := node.next
@@ -104,12 +136,26 @@ remove_by_proc_contextless :: proc(list: ^List, to_erase: proc "contextless" (^N
 	}
 	}
 }
 }
 
 
+/*
+Checks whether the given list does not contain any element.
 
 
+**Inputs**
+- list: The container list
 
 
+**Returns** `true` if `list` is empty, `false` otherwise
+*/
 is_empty :: proc "contextless" (list: ^List) -> bool {
 is_empty :: proc "contextless" (list: ^List) -> bool {
 	return list.head == nil
 	return list.head == nil
 }
 }
 
 
+/*
+Removes and returns the element at the front of the list with O(1) time complexity.
+
+**Inputs**
+- list: The container list
+
+**Returns** The node member of the user-defined element structure, or `nil` if the list is empty
+*/
 pop_front :: proc "contextless" (list: ^List) -> ^Node {
 pop_front :: proc "contextless" (list: ^List) -> ^Node {
 	link := list.head
 	link := list.head
 	if link == nil {
 	if link == nil {
@@ -130,6 +176,14 @@ pop_front :: proc "contextless" (list: ^List) -> ^Node {
 	return link
 	return link
 
 
 }
 }
+/*
+Removes and returns the element at the back of the list with O(1) time complexity.
+
+**Inputs**
+- list: The container list
+
+**Returns** The node member of the user-defined element structure, or `nil` if the list is empty
+*/
 pop_back :: proc "contextless" (list: ^List) -> ^Node {
 pop_back :: proc "contextless" (list: ^List) -> ^Node {
 	link := list.tail
 	link := list.tail
 	if link == nil {
 	if link == nil {
@@ -151,29 +205,102 @@ pop_back :: proc "contextless" (list: ^List) -> ^Node {
 }
 }
 
 
 
 
+
 Iterator :: struct($T: typeid) {
 Iterator :: struct($T: typeid) {
 	curr:   ^Node,
 	curr:   ^Node,
 	offset: uintptr,
 	offset: uintptr,
 }
 }
 
 
+/*
+Creates an iterator pointing at the head of the given list. For an example, see `iterate_next`.
+
+**Inputs**
+- list: The container list
+- T: The type of the list's elements
+- field_name: The name of the node field in the `T` structure
+
+**Returns** An iterator pointing at the head of `list`
+
+*/
 iterator_head :: proc "contextless" (list: List, $T: typeid, $field_name: string) -> Iterator(T)
 iterator_head :: proc "contextless" (list: List, $T: typeid, $field_name: string) -> Iterator(T)
 	where intrinsics.type_has_field(T, field_name),
 	where intrinsics.type_has_field(T, field_name),
 	      intrinsics.type_field_type(T, field_name) == Node {
 	      intrinsics.type_field_type(T, field_name) == Node {
 	return {list.head, offset_of_by_string(T, field_name)}
 	return {list.head, offset_of_by_string(T, field_name)}
 }
 }
+/*
+Creates an iterator pointing at the tail of the given list. For an example, see `iterate_prev`.
+
+**Inputs**
+- list: The container list
+- T: The type of the list's elements
+- field_name: The name of the node field in the `T` structure
 
 
+**Returns** An iterator pointing at the tail of `list`
+
+*/
 iterator_tail :: proc "contextless" (list: List, $T: typeid, $field_name: string) -> Iterator(T)
 iterator_tail :: proc "contextless" (list: List, $T: typeid, $field_name: string) -> Iterator(T)
 	where intrinsics.type_has_field(T, field_name),
 	where intrinsics.type_has_field(T, field_name),
 	      intrinsics.type_field_type(T, field_name) == Node {
 	      intrinsics.type_field_type(T, field_name) == Node {
 	return {list.tail, offset_of_by_string(T, field_name)}
 	return {list.tail, offset_of_by_string(T, field_name)}
 }
 }
+/*
+Creates an iterator pointing at the specified node of a list.
+
+**Inputs**
+- node: a list node
+- T: The type of the list's elements
+- field_name: The name of the node field in the `T` structure
+
+**Returns** An iterator pointing at `node`
 
 
+*/
 iterator_from_node :: proc "contextless" (node: ^Node, $T: typeid, $field_name: string) -> Iterator(T)
 iterator_from_node :: proc "contextless" (node: ^Node, $T: typeid, $field_name: string) -> Iterator(T)
 	where intrinsics.type_has_field(T, field_name),
 	where intrinsics.type_has_field(T, field_name),
 	      intrinsics.type_field_type(T, field_name) == Node {
 	      intrinsics.type_field_type(T, field_name) == Node {
 	return {node, offset_of_by_string(T, field_name)}
 	return {node, offset_of_by_string(T, field_name)}
 }
 }
 
 
+/*
+Retrieves the next element in a list and advances the iterator.
+
+**Inputs**  
+- it: The iterator
+
+**Returns**
+- ptr: The next list element
+- ok: `true` if the element is valid (the iterator could advance), `false` otherwise
+
+Example:
+
+	import "core:fmt"
+	import "core:container/intrusive/list"
+
+	iterate_next_example :: proc() {
+		l: list.List
+
+		one := My_Struct{value=1}
+		two := My_Struct{value=2}
+
+		list.push_back(&l, &one.node)
+		list.push_back(&l, &two.node)
+
+		it := list.iterator_head(l, My_Struct, "node")
+		for num in list.iterate_next(&it) {
+			fmt.println(num.value)
+		}
+	}
+
+	My_Struct :: struct {
+		node : list.Node,
+		value: int,
+	}
+
+Output:
+
+	1
+	2
+
+*/
 iterate_next :: proc "contextless" (it: ^Iterator($T)) -> (ptr: ^T, ok: bool) {
 iterate_next :: proc "contextless" (it: ^Iterator($T)) -> (ptr: ^T, ok: bool) {
 	node := it.curr
 	node := it.curr
 	if node == nil {
 	if node == nil {
@@ -183,7 +310,47 @@ iterate_next :: proc "contextless" (it: ^Iterator($T)) -> (ptr: ^T, ok: bool) {
 
 
 	return (^T)(uintptr(node) - it.offset), true
 	return (^T)(uintptr(node) - it.offset), true
 }
 }
+/*
+Retrieves the previous element in a list and recede the iterator.
+
+**Inputs**  
+- it: The iterator
+
+**Returns**
+- ptr: The previous list element
+- ok: `true` if the element is valid (the iterator could recede), `false` otherwise
+
+Example:
+
+	import "core:fmt"
+	import "core:container/intrusive/list"
 
 
+	iterate_next_example :: proc() {
+		l: list.List
+
+		one := My_Struct{value=1}
+		two := My_Struct{value=2}
+
+		list.push_back(&l, &one.node)
+		list.push_back(&l, &two.node)
+
+		it := list.iterator_tail(l, My_Struct, "node")
+		for num in list.iterate_prev(&it) {
+			fmt.println(num.value)
+		}
+	}
+
+	My_Struct :: struct {
+		node : list.Node,
+		value: int,
+	}
+
+Output:
+
+	2
+	1
+
+*/
 iterate_prev :: proc "contextless" (it: ^Iterator($T)) -> (ptr: ^T, ok: bool) {
 iterate_prev :: proc "contextless" (it: ^Iterator($T)) -> (ptr: ^T, ok: bool) {
 	node := it.curr
 	node := it.curr
 	if node == nil {
 	if node == nil {
@@ -192,4 +359,4 @@ iterate_prev :: proc "contextless" (it: ^Iterator($T)) -> (ptr: ^T, ok: bool) {
 	it.curr = node.prev
 	it.curr = node.prev
 
 
 	return (^T)(uintptr(node) - it.offset), true
 	return (^T)(uintptr(node) - it.offset), true
-}
+}

+ 2 - 2
core/container/queue/queue.odin

@@ -95,11 +95,11 @@ front_ptr :: proc(q: ^$Q/Queue($T)) -> ^T {
 }
 }
 
 
 back :: proc(q: ^$Q/Queue($T)) -> T {
 back :: proc(q: ^$Q/Queue($T)) -> T {
-	idx := (q.offset+uint(q.len))%builtin.len(q.data)
+	idx := (q.offset+uint(q.len - 1))%builtin.len(q.data)
 	return q.data[idx]
 	return q.data[idx]
 }
 }
 back_ptr :: proc(q: ^$Q/Queue($T)) -> ^T {
 back_ptr :: proc(q: ^$Q/Queue($T)) -> ^T {
-	idx := (q.offset+uint(q.len))%builtin.len(q.data)
+	idx := (q.offset+uint(q.len - 1))%builtin.len(q.data)
 	return &q.data[idx]
 	return &q.data[idx]
 }
 }
 
 

+ 2 - 12
core/crypto/_aes/ct64/api.odin

@@ -7,9 +7,8 @@ STRIDE :: 4
 
 
 // Context is a keyed AES (ECB) instance.
 // Context is a keyed AES (ECB) instance.
 Context :: struct {
 Context :: struct {
-	_sk_exp:         [120]u64,
-	_num_rounds:     int,
-	_is_initialized: bool,
+	_sk_exp:     [120]u64,
+	_num_rounds: int,
 }
 }
 
 
 // init initializes a context for AES with the provided key.
 // init initializes a context for AES with the provided key.
@@ -18,13 +17,10 @@ init :: proc(ctx: ^Context, key: []byte) {
 
 
 	ctx._num_rounds = keysched(skey[:], key)
 	ctx._num_rounds = keysched(skey[:], key)
 	skey_expand(ctx._sk_exp[:], skey[:], ctx._num_rounds)
 	skey_expand(ctx._sk_exp[:], skey[:], ctx._num_rounds)
-	ctx._is_initialized = true
 }
 }
 
 
 // encrypt_block sets `dst` to `AES-ECB-Encrypt(src)`.
 // encrypt_block sets `dst` to `AES-ECB-Encrypt(src)`.
 encrypt_block :: proc(ctx: ^Context, dst, src: []byte) {
 encrypt_block :: proc(ctx: ^Context, dst, src: []byte) {
-	assert(ctx._is_initialized)
-
 	q: [8]u64
 	q: [8]u64
 	load_blockx1(&q, src)
 	load_blockx1(&q, src)
 	_encrypt(&q, ctx._sk_exp[:], ctx._num_rounds)
 	_encrypt(&q, ctx._sk_exp[:], ctx._num_rounds)
@@ -33,8 +29,6 @@ encrypt_block :: proc(ctx: ^Context, dst, src: []byte) {
 
 
 // encrypt_block sets `dst` to `AES-ECB-Decrypt(src)`.
 // encrypt_block sets `dst` to `AES-ECB-Decrypt(src)`.
 decrypt_block :: proc(ctx: ^Context, dst, src: []byte) {
 decrypt_block :: proc(ctx: ^Context, dst, src: []byte) {
-	assert(ctx._is_initialized)
-
 	q: [8]u64
 	q: [8]u64
 	load_blockx1(&q, src)
 	load_blockx1(&q, src)
 	_decrypt(&q, ctx._sk_exp[:], ctx._num_rounds)
 	_decrypt(&q, ctx._sk_exp[:], ctx._num_rounds)
@@ -43,8 +37,6 @@ decrypt_block :: proc(ctx: ^Context, dst, src: []byte) {
 
 
 // encrypt_blocks sets `dst` to `AES-ECB-Encrypt(src[0], .. src[n])`.
 // encrypt_blocks sets `dst` to `AES-ECB-Encrypt(src[0], .. src[n])`.
 encrypt_blocks :: proc(ctx: ^Context, dst, src: [][]byte) {
 encrypt_blocks :: proc(ctx: ^Context, dst, src: [][]byte) {
-	assert(ctx._is_initialized)
-
 	q: [8]u64 = ---
 	q: [8]u64 = ---
 	src, dst := src, dst
 	src, dst := src, dst
 
 
@@ -67,8 +59,6 @@ encrypt_blocks :: proc(ctx: ^Context, dst, src: [][]byte) {
 
 
 // decrypt_blocks sets dst to `AES-ECB-Decrypt(src[0], .. src[n])`.
 // decrypt_blocks sets dst to `AES-ECB-Decrypt(src[0], .. src[n])`.
 decrypt_blocks :: proc(ctx: ^Context, dst, src: [][]byte) {
 decrypt_blocks :: proc(ctx: ^Context, dst, src: [][]byte) {
-	assert(ctx._is_initialized)
-
 	q: [8]u64 = ---
 	q: [8]u64 = ---
 	src, dst := src, dst
 	src, dst := src, dst
 
 

+ 43 - 0
core/crypto/_aes/hw_intel/api.odin

@@ -0,0 +1,43 @@
+//+build amd64
+package aes_hw_intel
+
+import "core:sys/info"
+
+// is_supporte returns true iff hardware accelerated AES
+// is supported.
+is_supported :: proc "contextless" () -> bool {
+	features, ok := info.cpu_features.?
+	if !ok {
+		return false
+	}
+
+	// Note: Everything with AES-NI and PCLMULQDQ has support for
+	// the required SSE extxtensions.
+	req_features :: info.CPU_Features{
+		.sse2,
+		.ssse3,
+		.sse41,
+		.aes,
+		.pclmulqdq,
+	}
+	return features >= req_features
+}
+
+// Context is a keyed AES (ECB) instance.
+Context :: struct {
+	// Note: The ideal thing to do is for the expanded round keys to be
+	// arrays of `__m128i`, however that implies alignment (or using AVX).
+	//
+	// All the people using e-waste processors that don't support an
+	// insturction set that has been around for over 10 years are why
+	// we can't have nice things.
+	_sk_exp_enc: [15][16]byte,
+	_sk_exp_dec: [15][16]byte,
+	_num_rounds: int,
+}
+
+// init initializes a context for AES with the provided key.
+init :: proc(ctx: ^Context, key: []byte) {
+	keysched(ctx, key)
+}
+

+ 281 - 0
core/crypto/_aes/hw_intel/ghash.odin

@@ -0,0 +1,281 @@
+// Copyright (c) 2017 Thomas Pornin <[email protected]>
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions
+// are met:
+//
+//   1. Redistributions of source code must retain the above copyright
+//      notice, this list of conditions and the following disclaimer.
+//
+// THIS SOFTWARE IS PROVIDED BY THE AUTHORS “AS IS” AND ANY EXPRESS OR
+// IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+// ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY
+// DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE
+// GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+// WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+// THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+//+build amd64
+package aes_hw_intel
+
+import "base:intrinsics"
+import "core:crypto/_aes"
+import "core:simd"
+import "core:simd/x86"
+
+@(private = "file")
+GHASH_STRIDE_HW :: 4
+@(private = "file")
+GHASH_STRIDE_BYTES_HW :: GHASH_STRIDE_HW * _aes.GHASH_BLOCK_SIZE
+
+// GHASH is defined over elements of GF(2^128) with "full little-endian"
+// representation: leftmost byte is least significant, and, within each
+// byte, leftmost _bit_ is least significant. The natural ordering in
+// x86 is "mixed little-endian": bytes are ordered from least to most
+// significant, but bits within a byte are in most-to-least significant
+// order. Going to full little-endian representation would require
+// reversing bits within each byte, which is doable but expensive.
+//
+// Instead, we go to full big-endian representation, by swapping bytes
+// around, which is done with a single _mm_shuffle_epi8() opcode (it
+// comes with SSSE3; all CPU that offer pclmulqdq also have SSSE3). We
+// can use a full big-endian representation because in a carryless
+// multiplication, we have a nice bit reversal property:
+//
+// rev_128(x) * rev_128(y) = rev_255(x * y)
+//
+// So by using full big-endian, we still get the right result, except
+// that it is right-shifted by 1 bit. The left-shift is relatively
+// inexpensive, and it can be mutualised.
+//
+// Since SSE2 opcodes do not have facilities for shitfting full 128-bit
+// values with bit precision, we have to break down values into 64-bit
+// chunks. We number chunks from 0 to 3 in left to right order.
+
+@(private = "file")
+byteswap_index := transmute(x86.__m128i)simd.i8x16{
+	// Note: simd.i8x16 is reverse order from x86._mm_set_epi8.
+	15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0,
+}
+
+@(private = "file", require_results, enable_target_feature = "sse2,ssse3")
+byteswap :: #force_inline proc "contextless" (x: x86.__m128i) -> x86.__m128i {
+	return x86._mm_shuffle_epi8(x, byteswap_index)
+}
+
+// From a 128-bit value kw, compute kx as the XOR of the two 64-bit
+// halves of kw (into the right half of kx; left half is unspecified),
+// and return kx.
+@(private = "file", require_results, enable_target_feature = "sse2")
+bk :: #force_inline proc "contextless" (kw: x86.__m128i) -> x86.__m128i {
+	return x86._mm_xor_si128(kw, x86._mm_shuffle_epi32(kw, 0x0e))
+}
+
+// Combine two 64-bit values (k0:k1) into a 128-bit (kw) value and
+// the XOR of the two values (kx), and return (kw, kx).
+@(private = "file", enable_target_feature = "sse2")
+pbk :: #force_inline proc "contextless" (k0, k1: x86.__m128i) -> (x86.__m128i, x86.__m128i) {
+	kw := x86._mm_unpacklo_epi64(k1, k0)
+	kx := x86._mm_xor_si128(k0, k1)
+	return kw, kx
+}
+
+// Left-shift by 1 bit a 256-bit value (in four 64-bit words).
+@(private = "file", require_results, enable_target_feature = "sse2")
+sl_256 :: #force_inline proc "contextless" (x0, x1, x2, x3: x86.__m128i) -> (x86.__m128i, x86.__m128i, x86.__m128i, x86.__m128i) {
+	x0, x1, x2, x3 := x0, x1, x2, x3
+
+	x0 = x86._mm_or_si128(x86._mm_slli_epi64(x0, 1), x86._mm_srli_epi64(x1, 63))
+	x1 = x86._mm_or_si128(x86._mm_slli_epi64(x1, 1), x86._mm_srli_epi64(x2, 63))
+	x2 = x86._mm_or_si128(x86._mm_slli_epi64(x2, 1), x86._mm_srli_epi64(x3, 63))
+	x3 = x86._mm_slli_epi64(x3, 1)
+
+	return x0, x1, x2, x3
+}
+
+// Perform reduction in GF(2^128).
+@(private = "file", require_results, enable_target_feature = "sse2")
+reduce_f128 :: #force_inline proc "contextless" (x0, x1, x2, x3: x86.__m128i) -> (x86.__m128i, x86.__m128i) {
+	x0, x1, x2 := x0, x1, x2
+
+	x1 = x86._mm_xor_si128(
+		x1,
+		x86._mm_xor_si128(
+			x86._mm_xor_si128(
+				x3,
+				x86._mm_srli_epi64(x3, 1)),
+			x86._mm_xor_si128(
+				x86._mm_srli_epi64(x3, 2),
+				x86._mm_srli_epi64(x3, 7))))
+	x2 = x86._mm_xor_si128(
+		x86._mm_xor_si128(
+			x2,
+			x86._mm_slli_epi64(x3, 63)),
+		x86._mm_xor_si128(
+			x86._mm_slli_epi64(x3, 62),
+			x86._mm_slli_epi64(x3, 57)))
+	x0 = x86._mm_xor_si128(
+		x0,
+		x86._mm_xor_si128(
+			x86._mm_xor_si128(
+				x2,
+				x86._mm_srli_epi64(x2, 1)),
+			x86._mm_xor_si128(
+				x86._mm_srli_epi64(x2, 2),
+				x86._mm_srli_epi64(x2, 7))))
+	x1 = x86._mm_xor_si128(
+		x86._mm_xor_si128(
+			x1,
+			x86._mm_slli_epi64(x2, 63)),
+		x86._mm_xor_si128(
+			x86._mm_slli_epi64(x2, 62),
+			x86._mm_slli_epi64(x2, 57)))
+
+	return x0, x1
+}
+
+// Square value kw in GF(2^128) into (dw,dx).
+@(private = "file", require_results, enable_target_feature = "sse2,pclmul")
+square_f128 :: #force_inline proc "contextless" (kw: x86.__m128i) -> (x86.__m128i, x86.__m128i) {
+	z1 := x86._mm_clmulepi64_si128(kw, kw, 0x11)
+	z3 := x86._mm_clmulepi64_si128(kw, kw, 0x00)
+	z0 := x86._mm_shuffle_epi32(z1, 0x0E)
+	z2 := x86._mm_shuffle_epi32(z3, 0x0E)
+	z0, z1, z2, z3 = sl_256(z0, z1, z2, z3)
+	z0, z1 = reduce_f128(z0, z1, z2, z3)
+	return pbk(z0, z1)
+}
+
+// ghash calculates the GHASH of data, with the key `key`, and input `dst`
+// and `data`, and stores the resulting digest in `dst`.
+//
+// Note: `dst` is both an input and an output, to support easy implementation
+// of GCM.
+@(enable_target_feature = "sse2,ssse3,pclmul")
+ghash :: proc "contextless" (dst, key, data: []byte) #no_bounds_check {
+	if len(dst) != _aes.GHASH_BLOCK_SIZE || len(key) != _aes.GHASH_BLOCK_SIZE {
+		intrinsics.trap()
+	}
+
+	// Note: BearSSL opts to copy the remainder into a zero-filled
+	// 64-byte buffer.  We do something slightly more simple.
+
+	// Load key and dst (h and y).
+	yw := intrinsics.unaligned_load((^x86.__m128i)(raw_data(dst)))
+	h1w := intrinsics.unaligned_load((^x86.__m128i)(raw_data(key)))
+	yw = byteswap(yw)
+	h1w = byteswap(h1w)
+	h1x := bk(h1w)
+
+	// Process 4 blocks at a time
+	buf := data
+	l := len(buf)
+	if l >= GHASH_STRIDE_BYTES_HW {
+		// Compute h2 = h^2
+		h2w, h2x := square_f128(h1w)
+
+		// Compute h3 = h^3 = h*(h^2)
+		t1 := x86._mm_clmulepi64_si128(h1w, h2w, 0x11)
+		t3 := x86._mm_clmulepi64_si128(h1w, h2w, 0x00)
+		t2 := x86._mm_xor_si128(
+			x86._mm_clmulepi64_si128(h1x, h2x, 0x00),
+			x86._mm_xor_si128(t1, t3))
+		t0 := x86._mm_shuffle_epi32(t1, 0x0E)
+		t1 = x86._mm_xor_si128(t1, x86._mm_shuffle_epi32(t2, 0x0E))
+		t2 = x86._mm_xor_si128(t2, x86._mm_shuffle_epi32(t3, 0x0E))
+		t0, t1, t2, t3 = sl_256(t0, t1, t2, t3)
+		t0, t1 = reduce_f128(t0, t1, t2, t3)
+		h3w, h3x := pbk(t0, t1)
+
+		// Compute h4 = h^4 = (h^2)^2
+		h4w, h4x := square_f128(h2w)
+
+		for l >= GHASH_STRIDE_BYTES_HW {
+			aw0 := intrinsics.unaligned_load((^x86.__m128i)(raw_data(buf)))
+			aw1 := intrinsics.unaligned_load((^x86.__m128i)(raw_data(buf[16:])))
+			aw2 := intrinsics.unaligned_load((^x86.__m128i)(raw_data(buf[32:])))
+			aw3 := intrinsics.unaligned_load((^x86.__m128i)(raw_data(buf[48:])))
+			aw0 = byteswap(aw0)
+			aw1 = byteswap(aw1)
+			aw2 = byteswap(aw2)
+			aw3 = byteswap(aw3)
+			buf, l = buf[GHASH_STRIDE_BYTES_HW:], l - GHASH_STRIDE_BYTES_HW
+
+			aw0 = x86._mm_xor_si128(aw0, yw)
+			ax1 := bk(aw1)
+			ax2 := bk(aw2)
+			ax3 := bk(aw3)
+			ax0 := bk(aw0)
+
+			t1 = x86._mm_xor_si128(
+				x86._mm_xor_si128(
+					x86._mm_clmulepi64_si128(aw0, h4w, 0x11),
+					x86._mm_clmulepi64_si128(aw1, h3w, 0x11)),
+				x86._mm_xor_si128(
+					x86._mm_clmulepi64_si128(aw2, h2w, 0x11),
+					x86._mm_clmulepi64_si128(aw3, h1w, 0x11)))
+			t3 = x86._mm_xor_si128(
+				x86._mm_xor_si128(
+					x86._mm_clmulepi64_si128(aw0, h4w, 0x00),
+					x86._mm_clmulepi64_si128(aw1, h3w, 0x00)),
+				x86._mm_xor_si128(
+					x86._mm_clmulepi64_si128(aw2, h2w, 0x00),
+					x86._mm_clmulepi64_si128(aw3, h1w, 0x00)))
+			t2 = x86._mm_xor_si128(
+				x86._mm_xor_si128(
+					x86._mm_clmulepi64_si128(ax0, h4x, 0x00),
+					x86._mm_clmulepi64_si128(ax1, h3x, 0x00)),
+				x86._mm_xor_si128(
+					x86._mm_clmulepi64_si128(ax2, h2x, 0x00),
+					x86._mm_clmulepi64_si128(ax3, h1x, 0x00)))
+			t2 = x86._mm_xor_si128(t2, x86._mm_xor_si128(t1, t3))
+			t0 = x86._mm_shuffle_epi32(t1, 0x0E)
+			t1 = x86._mm_xor_si128(t1, x86._mm_shuffle_epi32(t2, 0x0E))
+			t2 = x86._mm_xor_si128(t2, x86._mm_shuffle_epi32(t3, 0x0E))
+			t0, t1, t2, t3 = sl_256(t0, t1, t2, t3)
+			t0, t1 = reduce_f128(t0, t1, t2, t3)
+			yw = x86._mm_unpacklo_epi64(t1, t0)
+		}
+	}
+
+	// Process 1 block at a time
+	src: []byte
+	for l > 0 {
+		if l >= _aes.GHASH_BLOCK_SIZE {
+			src = buf
+			buf = buf[_aes.GHASH_BLOCK_SIZE:]
+			l -= _aes.GHASH_BLOCK_SIZE
+		} else {
+			tmp: [_aes.GHASH_BLOCK_SIZE]byte
+			copy(tmp[:], buf)
+			src = tmp[:]
+			l = 0
+		}
+
+		aw := intrinsics.unaligned_load((^x86.__m128i)(raw_data(src)))
+		aw = byteswap(aw)
+
+		aw = x86._mm_xor_si128(aw, yw)
+		ax := bk(aw)
+
+		t1 := x86._mm_clmulepi64_si128(aw, h1w, 0x11)
+		t3 := x86._mm_clmulepi64_si128(aw, h1w, 0x00)
+		t2 := x86._mm_clmulepi64_si128(ax, h1x, 0x00)
+		t2 = x86._mm_xor_si128(t2, x86._mm_xor_si128(t1, t3))
+		t0 := x86._mm_shuffle_epi32(t1, 0x0E)
+		t1 = x86._mm_xor_si128(t1, x86._mm_shuffle_epi32(t2, 0x0E))
+		t2 = x86._mm_xor_si128(t2, x86._mm_shuffle_epi32(t3, 0x0E))
+		t0, t1, t2, t3 = sl_256(t0, t1, t2, t3)
+		t0, t1 = reduce_f128(t0, t1, t2, t3)
+		yw = x86._mm_unpacklo_epi64(t1, t0)
+	}
+
+	// Write back the hash (dst, aka y)
+	yw = byteswap(yw)
+	intrinsics.unaligned_store((^x86.__m128i)(raw_data(dst)), yw)
+}

+ 178 - 0
core/crypto/_aes/hw_intel/hw_intel_keysched.odin

@@ -0,0 +1,178 @@
+// Copyright (c) 2017 Thomas Pornin <[email protected]>
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions
+// are met:
+//
+//   1. Redistributions of source code must retain the above copyright
+//      notice, this list of conditions and the following disclaimer.
+//
+// THIS SOFTWARE IS PROVIDED BY THE AUTHORS “AS IS” AND ANY EXPRESS OR
+// IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+// ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY
+// DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE
+// GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+// WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+// THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+//+build amd64
+package aes_hw_intel
+
+import "base:intrinsics"
+import "core:crypto/_aes"
+import "core:mem"
+import "core:simd/x86"
+
+// Intel AES-NI based implementation.  Inspiration taken from BearSSL.
+//
+// Note: This assumes that the SROA optimization pass is enabled to be
+// anything resembling performat otherwise, LLVM will not elide a massive
+// number of redundant loads/stores it generates for every intrinsic call.
+
+@(private = "file", require_results, enable_target_feature = "sse2")
+expand_step128 :: #force_inline proc(k1, k2: x86.__m128i) -> x86.__m128i {
+	k1, k2 := k1, k2
+
+	k2 = x86._mm_shuffle_epi32(k2, 0xff)
+	k1 = x86._mm_xor_si128(k1, x86._mm_slli_si128(k1, 0x04))
+	k1 = x86._mm_xor_si128(k1, x86._mm_slli_si128(k1, 0x04))
+	k1 = x86._mm_xor_si128(k1, x86._mm_slli_si128(k1, 0x04))
+	return x86._mm_xor_si128(k1, k2)
+}
+
+@(private = "file", require_results, enable_target_feature = "sse,sse2")
+expand_step192a :: #force_inline proc (k1_, k2_: ^x86.__m128i, k3: x86.__m128i) -> (x86.__m128i, x86.__m128i) {
+	k1, k2, k3 := k1_^, k2_^, k3
+
+	k3 = x86._mm_shuffle_epi32(k3, 0x55)
+	k1 = x86._mm_xor_si128(k1, x86._mm_slli_si128(k1, 0x04))
+	k1 = x86._mm_xor_si128(k1, x86._mm_slli_si128(k1, 0x04))
+	k1 = x86._mm_xor_si128(k1, x86._mm_slli_si128(k1, 0x04))
+	k1 = x86._mm_xor_si128(k1, k3)
+
+	tmp := k2
+	k2 = x86._mm_xor_si128(k2, x86._mm_slli_si128(k2, 0x04))
+	k2 = x86._mm_xor_si128(k2, x86._mm_shuffle_epi32(k1, 0xff))
+
+	k1_, k2_ := k1_, k2_
+	k1_^, k2_^ = k1, k2
+
+	r1 := transmute(x86.__m128i)(x86._mm_shuffle_ps(transmute(x86.__m128)(tmp), transmute(x86.__m128)(k1), 0x44))
+	r2 := transmute(x86.__m128i)(x86._mm_shuffle_ps(transmute(x86.__m128)(k1), transmute(x86.__m128)(k2), 0x4e))
+
+	return r1, r2
+}
+
+@(private = "file", require_results, enable_target_feature = "sse2")
+expand_step192b :: #force_inline proc (k1_, k2_: ^x86.__m128i, k3: x86.__m128i) -> x86.__m128i {
+	k1, k2, k3 := k1_^, k2_^, k3
+
+	k3 = x86._mm_shuffle_epi32(k3, 0x55)
+	k1 = x86._mm_xor_si128(k1, x86._mm_slli_si128(k1, 0x04))
+	k1 = x86._mm_xor_si128(k1, x86._mm_slli_si128(k1, 0x04))
+	k1 = x86._mm_xor_si128(k1, x86._mm_slli_si128(k1, 0x04))
+	k1 = x86._mm_xor_si128(k1, k3)
+
+	k2 = x86._mm_xor_si128(k2, x86._mm_slli_si128(k2, 0x04))
+	k2 = x86._mm_xor_si128(k2, x86._mm_shuffle_epi32(k1, 0xff))
+
+	k1_, k2_ := k1_, k2_
+	k1_^, k2_^ = k1, k2
+
+	return k1
+}
+
+@(private = "file", require_results, enable_target_feature = "sse2")
+expand_step256b :: #force_inline proc(k1, k2: x86.__m128i) -> x86.__m128i {
+	k1, k2 := k1, k2
+
+	k2 = x86._mm_shuffle_epi32(k2, 0xaa)
+	k1 = x86._mm_xor_si128(k1, x86._mm_slli_si128(k1, 0x04))
+	k1 = x86._mm_xor_si128(k1, x86._mm_slli_si128(k1, 0x04))
+	k1 = x86._mm_xor_si128(k1, x86._mm_slli_si128(k1, 0x04))
+	return x86._mm_xor_si128(k1, k2)
+}
+
+@(private = "file", enable_target_feature = "aes")
+derive_dec_keys :: proc(ctx: ^Context, sks: ^[15]x86.__m128i, num_rounds: int) {
+	intrinsics.unaligned_store((^x86.__m128i)(&ctx._sk_exp_dec[0]), sks[num_rounds])
+	for i in 1 ..< num_rounds {
+		tmp := x86._mm_aesimc_si128(sks[i])
+		intrinsics.unaligned_store((^x86.__m128i)(&ctx._sk_exp_dec[num_rounds - i]), tmp)
+	}
+	intrinsics.unaligned_store((^x86.__m128i)(&ctx._sk_exp_dec[num_rounds]), sks[0])
+}
+
+@(private, enable_target_feature = "sse,sse2,aes")
+keysched :: proc(ctx: ^Context, key: []byte) {
+	sks: [15]x86.__m128i = ---
+
+	// Compute the encryption keys.
+	num_rounds, key_len := 0, len(key)
+	switch key_len {
+	case _aes.KEY_SIZE_128:
+		sks[0] = intrinsics.unaligned_load((^x86.__m128i)(raw_data(key)))
+		sks[1] = expand_step128(sks[0], x86._mm_aeskeygenassist_si128(sks[0], 0x01))
+		sks[2] = expand_step128(sks[1], x86._mm_aeskeygenassist_si128(sks[1], 0x02))
+		sks[3] = expand_step128(sks[2], x86._mm_aeskeygenassist_si128(sks[2], 0x04))
+		sks[4] = expand_step128(sks[3], x86._mm_aeskeygenassist_si128(sks[3], 0x08))
+		sks[5] = expand_step128(sks[4], x86._mm_aeskeygenassist_si128(sks[4], 0x10))
+		sks[6] = expand_step128(sks[5], x86._mm_aeskeygenassist_si128(sks[5], 0x20))
+		sks[7] = expand_step128(sks[6], x86._mm_aeskeygenassist_si128(sks[6], 0x40))
+		sks[8] = expand_step128(sks[7], x86._mm_aeskeygenassist_si128(sks[7], 0x80))
+		sks[9] = expand_step128(sks[8], x86._mm_aeskeygenassist_si128(sks[8], 0x1b))
+		sks[10] = expand_step128(sks[9], x86._mm_aeskeygenassist_si128(sks[9], 0x36))
+		num_rounds = _aes.ROUNDS_128
+	case _aes.KEY_SIZE_192:
+		k0 := intrinsics.unaligned_load((^x86.__m128i)(raw_data(key)))
+		k1 := x86.__m128i{
+			intrinsics.unaligned_load((^i64)(raw_data(key[16:]))),
+			0,
+		}
+		sks[0] = k0
+		sks[1], sks[2] = expand_step192a(&k0, &k1, x86._mm_aeskeygenassist_si128(k1, 0x01))
+		sks[3] = expand_step192b(&k0, &k1, x86._mm_aeskeygenassist_si128(k1, 0x02))
+		sks[4], sks[5] = expand_step192a(&k0, &k1, x86._mm_aeskeygenassist_si128(k1, 0x04))
+		sks[6] = expand_step192b(&k0, &k1, x86._mm_aeskeygenassist_si128(k1, 0x08))
+		sks[7], sks[8] = expand_step192a(&k0, &k1, x86._mm_aeskeygenassist_si128(k1, 0x10))
+		sks[9] = expand_step192b(&k0, &k1, x86._mm_aeskeygenassist_si128(k1, 0x20))
+		sks[10], sks[11] = expand_step192a(&k0, &k1, x86._mm_aeskeygenassist_si128(k1, 0x40))
+		sks[12] = expand_step192b(&k0, &k1, x86._mm_aeskeygenassist_si128(k1, 0x80))
+		num_rounds = _aes.ROUNDS_192
+	case _aes.KEY_SIZE_256:
+		sks[0] = intrinsics.unaligned_load((^x86.__m128i)(raw_data(key)))
+		sks[1] = intrinsics.unaligned_load((^x86.__m128i)(raw_data(key[16:])))
+		sks[2] = expand_step128(sks[0], x86._mm_aeskeygenassist_si128(sks[1], 0x01))
+		sks[3] = expand_step256b(sks[1], x86._mm_aeskeygenassist_si128(sks[2], 0x01))
+		sks[4] = expand_step128(sks[2], x86._mm_aeskeygenassist_si128(sks[3], 0x02))
+		sks[5] = expand_step256b(sks[3], x86._mm_aeskeygenassist_si128(sks[4], 0x02))
+		sks[6] = expand_step128(sks[4], x86._mm_aeskeygenassist_si128(sks[5], 0x04))
+		sks[7] = expand_step256b(sks[5], x86._mm_aeskeygenassist_si128(sks[6], 0x04))
+		sks[8] = expand_step128(sks[6], x86._mm_aeskeygenassist_si128(sks[7], 0x08))
+		sks[9] = expand_step256b(sks[7], x86._mm_aeskeygenassist_si128(sks[8], 0x08))
+		sks[10] = expand_step128(sks[8], x86._mm_aeskeygenassist_si128(sks[9], 0x10))
+		sks[11] = expand_step256b(sks[9], x86._mm_aeskeygenassist_si128(sks[10], 0x10))
+		sks[12] = expand_step128(sks[10], x86._mm_aeskeygenassist_si128(sks[11], 0x20))
+		sks[13] = expand_step256b(sks[11], x86._mm_aeskeygenassist_si128(sks[12], 0x20))
+		sks[14] = expand_step128(sks[12], x86._mm_aeskeygenassist_si128(sks[13], 0x40))
+		num_rounds = _aes.ROUNDS_256
+	case:
+		panic("crypto/aes: invalid AES key size")
+	}
+	for i in 0 ..= num_rounds {
+		intrinsics.unaligned_store((^x86.__m128i)(&ctx._sk_exp_enc[i]), sks[i])
+	}
+
+	// Compute the decryption keys.  GCM and CTR do not need this, however
+	// ECB, CBC, OCB3, etc do.
+	derive_dec_keys(ctx, &sks, num_rounds)
+
+	ctx._num_rounds = num_rounds
+
+	mem.zero_explicit(&sks, size_of(sks))
+}

+ 0 - 1
core/crypto/aes/aes.odin

@@ -6,7 +6,6 @@ See:
 - https://nvlpubs.nist.gov/nistpubs/Legacy/SP/nistspecialpublication800-38a.pdf
 - https://nvlpubs.nist.gov/nistpubs/Legacy/SP/nistspecialpublication800-38a.pdf
 - https://nvlpubs.nist.gov/nistpubs/Legacy/SP/nistspecialpublication800-38d.pdf
 - https://nvlpubs.nist.gov/nistpubs/Legacy/SP/nistspecialpublication800-38d.pdf
 */
 */
-
 package aes
 package aes
 
 
 import "core:crypto/_aes"
 import "core:crypto/_aes"

+ 17 - 15
core/crypto/aes/aes_ctr.odin

@@ -1,5 +1,6 @@
 package aes
 package aes
 
 
+import "core:bytes"
 import "core:crypto/_aes/ct64"
 import "core:crypto/_aes/ct64"
 import "core:encoding/endian"
 import "core:encoding/endian"
 import "core:math/bits"
 import "core:math/bits"
@@ -37,14 +38,15 @@ init_ctr :: proc(ctx: ^Context_CTR, key, iv: []byte, impl := Implementation.Hard
 xor_bytes_ctr :: proc(ctx: ^Context_CTR, dst, src: []byte) {
 xor_bytes_ctr :: proc(ctx: ^Context_CTR, dst, src: []byte) {
 	assert(ctx._is_initialized)
 	assert(ctx._is_initialized)
 
 
-	// TODO: Enforcing that dst and src alias exactly or not at all
-	// is a good idea, though odd aliasing should be extremely uncommon.
-
 	src, dst := src, dst
 	src, dst := src, dst
 	if dst_len := len(dst); dst_len < len(src) {
 	if dst_len := len(dst); dst_len < len(src) {
 		src = src[:dst_len]
 		src = src[:dst_len]
 	}
 	}
 
 
+	if bytes.alias_inexactly(dst, src) {
+		panic("crypto/aes: dst and src alias inexactly")
+	}
+
 	for remaining := len(src); remaining > 0; {
 	for remaining := len(src); remaining > 0; {
 		// Process multiple blocks at once
 		// Process multiple blocks at once
 		if ctx._off == BLOCK_SIZE {
 		if ctx._off == BLOCK_SIZE {
@@ -123,8 +125,8 @@ reset_ctr :: proc "contextless" (ctx: ^Context_CTR) {
 	ctx._is_initialized = false
 	ctx._is_initialized = false
 }
 }
 
 
-@(private)
-ctr_blocks :: proc(ctx: ^Context_CTR, dst, src: []byte, nr_blocks: int) {
+@(private = "file")
+ctr_blocks :: proc(ctx: ^Context_CTR, dst, src: []byte, nr_blocks: int) #no_bounds_check {
 	// Use the optimized hardware implementation if available.
 	// Use the optimized hardware implementation if available.
 	if _, is_hw := ctx._impl.(Context_Impl_Hardware); is_hw {
 	if _, is_hw := ctx._impl.(Context_Impl_Hardware); is_hw {
 		ctr_blocks_hw(ctx, dst, src, nr_blocks)
 		ctr_blocks_hw(ctx, dst, src, nr_blocks)
@@ -183,17 +185,17 @@ xor_blocks :: #force_inline proc "contextless" (dst, src: []byte, blocks: [][]by
 	// performance of this implementation matters to where that
 	// performance of this implementation matters to where that
 	// optimization would be worth it, use chacha20poly1305, or a
 	// optimization would be worth it, use chacha20poly1305, or a
 	// CPU that isn't e-waste.
 	// CPU that isn't e-waste.
-	if src != nil {
-		#no_bounds_check {
-			for i in 0 ..< len(blocks) {
-				off := i * BLOCK_SIZE
-				for j in 0 ..< BLOCK_SIZE {
-					blocks[i][j] ~= src[off + j]
+	#no_bounds_check {
+		if src != nil {
+				for i in 0 ..< len(blocks) {
+					off := i * BLOCK_SIZE
+					for j in 0 ..< BLOCK_SIZE {
+						blocks[i][j] ~= src[off + j]
+					}
 				}
 				}
-			}
 		}
 		}
-	}
-	for i in 0 ..< len(blocks) {
-		copy(dst[i * BLOCK_SIZE:], blocks[i])
+		for i in 0 ..< len(blocks) {
+			copy(dst[i * BLOCK_SIZE:], blocks[i])
+		}
 	}
 	}
 }
 }

+ 151 - 0
core/crypto/aes/aes_ctr_hw_intel.odin

@@ -0,0 +1,151 @@
+//+build amd64
+package aes
+
+import "base:intrinsics"
+import "core:crypto/_aes"
+import "core:math/bits"
+import "core:mem"
+import "core:simd/x86"
+
+@(private)
+CTR_STRIDE_HW :: 4
+@(private)
+CTR_STRIDE_BYTES_HW :: CTR_STRIDE_HW * BLOCK_SIZE
+
+@(private, enable_target_feature = "sse2,aes")
+ctr_blocks_hw :: proc(ctx: ^Context_CTR, dst, src: []byte, nr_blocks: int) #no_bounds_check {
+	hw_ctx := ctx._impl.(Context_Impl_Hardware)
+
+	sks: [15]x86.__m128i = ---
+	for i in 0 ..= hw_ctx._num_rounds {
+		sks[i] = intrinsics.unaligned_load((^x86.__m128i)(&hw_ctx._sk_exp_enc[i]))
+	}
+
+	hw_inc_ctr := #force_inline proc "contextless" (hi, lo: u64) -> (x86.__m128i, u64, u64) {
+		ret := x86.__m128i{
+			i64(intrinsics.byte_swap(hi)),
+			i64(intrinsics.byte_swap(lo)),
+		}
+
+		hi, lo := hi, lo
+		carry: u64
+
+		lo, carry = bits.add_u64(lo, 1, 0)
+		hi, _ = bits.add_u64(hi, 0, carry)
+		return ret, hi, lo
+	}
+
+	// The latency of AESENC depends on mfg and microarchitecture:
+	// - 7 -> up to Broadwell
+	// - 4 -> AMD and Skylake - Cascade Lake
+	// - 3 -> Ice Lake and newer
+	//
+	// This implementation does 4 blocks at once, since performance
+	// should be "adequate" across most CPUs.
+
+	src, dst := src, dst
+	nr_blocks := nr_blocks
+	ctr_hi, ctr_lo := ctx._ctr_hi, ctx._ctr_lo
+
+	blks: [CTR_STRIDE_HW]x86.__m128i = ---
+	for nr_blocks >= CTR_STRIDE_HW {
+		#unroll for i in 0..< CTR_STRIDE_HW {
+			blks[i], ctr_hi, ctr_lo = hw_inc_ctr(ctr_hi, ctr_lo)
+		}
+
+		#unroll for i in 0 ..< CTR_STRIDE_HW {
+			blks[i] = x86._mm_xor_si128(blks[i], sks[0])
+		}
+		#unroll for i in 1 ..= 9 {
+			#unroll for j in 0 ..< CTR_STRIDE_HW {
+				blks[j] = x86._mm_aesenc_si128(blks[j], sks[i])
+			}
+		}
+		switch hw_ctx._num_rounds {
+		case _aes.ROUNDS_128:
+			#unroll for i in 0 ..< CTR_STRIDE_HW {
+				blks[i] = x86._mm_aesenclast_si128(blks[i], sks[10])
+			}
+		case _aes.ROUNDS_192:
+			#unroll for i in 10 ..= 11 {
+				#unroll for j in 0 ..< CTR_STRIDE_HW {
+					blks[j] = x86._mm_aesenc_si128(blks[j], sks[i])
+				}
+			}
+			#unroll for i in 0 ..< CTR_STRIDE_HW {
+				blks[i] = x86._mm_aesenclast_si128(blks[i], sks[12])
+			}
+		case _aes.ROUNDS_256:
+			#unroll for i in 10 ..= 13 {
+				#unroll for j in 0 ..< CTR_STRIDE_HW {
+					blks[j] = x86._mm_aesenc_si128(blks[j], sks[i])
+				}
+			}
+			#unroll for i in 0 ..< CTR_STRIDE_HW {
+				blks[i] = x86._mm_aesenclast_si128(blks[i], sks[14])
+			}
+		}
+
+		xor_blocks_hw(dst, src, blks[:])
+
+		if src != nil {
+			src = src[CTR_STRIDE_BYTES_HW:]
+		}
+		dst = dst[CTR_STRIDE_BYTES_HW:]
+		nr_blocks -= CTR_STRIDE_HW
+	}
+
+	// Handle the remainder.
+	for nr_blocks > 0 {
+		blks[0], ctr_hi, ctr_lo = hw_inc_ctr(ctr_hi, ctr_lo)
+
+		blks[0] = x86._mm_xor_si128(blks[0], sks[0])
+		#unroll for i in 1 ..= 9 {
+			blks[0] = x86._mm_aesenc_si128(blks[0], sks[i])
+		}
+		switch hw_ctx._num_rounds {
+		case _aes.ROUNDS_128:
+			blks[0] = x86._mm_aesenclast_si128(blks[0], sks[10])
+		case _aes.ROUNDS_192:
+			#unroll for i in 10 ..= 11 {
+				blks[0] = x86._mm_aesenc_si128(blks[0], sks[i])
+			}
+			blks[0] = x86._mm_aesenclast_si128(blks[0], sks[12])
+		case _aes.ROUNDS_256:
+			#unroll for i in 10 ..= 13 {
+				blks[0] = x86._mm_aesenc_si128(blks[0], sks[i])
+			}
+			blks[0] = x86._mm_aesenclast_si128(blks[0], sks[14])
+		}
+
+		xor_blocks_hw(dst, src, blks[:1])
+
+		if src != nil {
+			src = src[BLOCK_SIZE:]
+		}
+		dst = dst[BLOCK_SIZE:]
+		nr_blocks -= 1
+	}
+
+	// Write back the counter.
+	ctx._ctr_hi, ctx._ctr_lo = ctr_hi, ctr_lo
+
+	mem.zero_explicit(&blks, size_of(blks))
+	mem.zero_explicit(&sks, size_of(sks))
+}
+
+@(private, enable_target_feature = "sse2")
+xor_blocks_hw :: proc(dst, src: []byte, blocks: []x86.__m128i) {
+	#no_bounds_check {
+		if src != nil {
+				for i in 0 ..< len(blocks) {
+					off := i * BLOCK_SIZE
+					tmp := intrinsics.unaligned_load((^x86.__m128i)(raw_data(src[off:])))
+					blocks[i] = x86._mm_xor_si128(blocks[i], tmp)
+				}
+		}
+		for i in 0 ..< len(blocks) {
+			intrinsics.unaligned_store((^x86.__m128i)(raw_data(dst[i * BLOCK_SIZE:])), blocks[i])
+		}
+	}
+}

+ 58 - 0
core/crypto/aes/aes_ecb_hw_intel.odin

@@ -0,0 +1,58 @@
+//+build amd64
+package aes
+
+import "base:intrinsics"
+import "core:crypto/_aes"
+import "core:simd/x86"
+
+@(private, enable_target_feature = "sse2,aes")
+encrypt_block_hw :: proc(ctx: ^Context_Impl_Hardware, dst, src: []byte) {
+	blk := intrinsics.unaligned_load((^x86.__m128i)(raw_data(src)))
+
+	blk = x86._mm_xor_si128(blk, intrinsics.unaligned_load((^x86.__m128i)(&ctx._sk_exp_enc[0])))
+	#unroll for i in 1 ..= 9 {
+		blk = x86._mm_aesenc_si128(blk, intrinsics.unaligned_load((^x86.__m128i)(&ctx._sk_exp_enc[i])))
+	}
+	switch ctx._num_rounds {
+	case _aes.ROUNDS_128:
+		blk = x86._mm_aesenclast_si128(blk, intrinsics.unaligned_load((^x86.__m128i)(&ctx._sk_exp_enc[10])))
+	case _aes.ROUNDS_192:
+		#unroll for i in 10 ..= 11 {
+			blk = x86._mm_aesenc_si128(blk, intrinsics.unaligned_load((^x86.__m128i)(&ctx._sk_exp_enc[i])))
+		}
+		blk = x86._mm_aesenclast_si128(blk, intrinsics.unaligned_load((^x86.__m128i)(&ctx._sk_exp_enc[12])))
+	case _aes.ROUNDS_256:
+		#unroll for i in 10 ..= 13 {
+			blk = x86._mm_aesenc_si128(blk, intrinsics.unaligned_load((^x86.__m128i)(&ctx._sk_exp_enc[i])))
+		}
+		blk = x86._mm_aesenclast_si128(blk, intrinsics.unaligned_load((^x86.__m128i)(&ctx._sk_exp_enc[14])))
+	}
+
+	intrinsics.unaligned_store((^x86.__m128i)(raw_data(dst)), blk)
+}
+
+@(private, enable_target_feature = "sse2,aes")
+decrypt_block_hw :: proc(ctx: ^Context_Impl_Hardware, dst, src: []byte) {
+	blk := intrinsics.unaligned_load((^x86.__m128i)(raw_data(src)))
+
+	blk = x86._mm_xor_si128(blk, intrinsics.unaligned_load((^x86.__m128i)(&ctx._sk_exp_dec[0])))
+	#unroll for i in 1 ..= 9 {
+		blk = x86._mm_aesdec_si128(blk, intrinsics.unaligned_load((^x86.__m128i)(&ctx._sk_exp_dec[i])))
+	}
+	switch ctx._num_rounds {
+	case _aes.ROUNDS_128:
+		blk = x86._mm_aesdeclast_si128(blk, intrinsics.unaligned_load((^x86.__m128i)(&ctx._sk_exp_dec[10])))
+	case _aes.ROUNDS_192:
+		#unroll for i in 10 ..= 11 {
+			blk = x86._mm_aesdec_si128(blk, intrinsics.unaligned_load((^x86.__m128i)(&ctx._sk_exp_dec[i])))
+		}
+		blk = x86._mm_aesdeclast_si128(blk, intrinsics.unaligned_load((^x86.__m128i)(&ctx._sk_exp_dec[12])))
+	case _aes.ROUNDS_256:
+		#unroll for i in 10 ..= 13 {
+			blk = x86._mm_aesdec_si128(blk, intrinsics.unaligned_load((^x86.__m128i)(&ctx._sk_exp_dec[i])))
+		}
+		blk = x86._mm_aesdeclast_si128(blk, intrinsics.unaligned_load((^x86.__m128i)(&ctx._sk_exp_dec[14])))
+	}
+
+	intrinsics.unaligned_store((^x86.__m128i)(raw_data(dst)), blk)
+}

+ 45 - 29
core/crypto/aes/aes_gcm.odin

@@ -1,13 +1,16 @@
 package aes
 package aes
 
 
+import "core:bytes"
 import "core:crypto"
 import "core:crypto"
 import "core:crypto/_aes"
 import "core:crypto/_aes"
 import "core:crypto/_aes/ct64"
 import "core:crypto/_aes/ct64"
 import "core:encoding/endian"
 import "core:encoding/endian"
 import "core:mem"
 import "core:mem"
 
 
-// GCM_NONCE_SIZE is the size of the GCM nonce in bytes.
+// GCM_NONCE_SIZE is the default size of the GCM nonce in bytes.
 GCM_NONCE_SIZE :: 12
 GCM_NONCE_SIZE :: 12
+// GCM_NONCE_SIZE_MAX is the maximum size of the GCM nonce in bytes.
+GCM_NONCE_SIZE_MAX :: 0x2000000000000000 // floor((2^64 - 1) / 8) bits
 // GCM_TAG_SIZE is the size of a GCM tag in bytes.
 // GCM_TAG_SIZE is the size of a GCM tag in bytes.
 GCM_TAG_SIZE :: _aes.GHASH_TAG_SIZE
 GCM_TAG_SIZE :: _aes.GHASH_TAG_SIZE
 
 
@@ -39,6 +42,9 @@ seal_gcm :: proc(ctx: ^Context_GCM, dst, tag, nonce, aad, plaintext: []byte) {
 	if len(dst) != len(plaintext) {
 	if len(dst) != len(plaintext) {
 		panic("crypto/aes: invalid destination ciphertext size")
 		panic("crypto/aes: invalid destination ciphertext size")
 	}
 	}
+	if bytes.alias_inexactly(dst, plaintext) {
+		panic("crypto/aes: dst and plaintext alias inexactly")
+	}
 
 
 	if impl, is_hw := ctx._impl.(Context_Impl_Hardware); is_hw {
 	if impl, is_hw := ctx._impl.(Context_Impl_Hardware); is_hw {
 		gcm_seal_hw(&impl, dst, tag, nonce, aad, plaintext)
 		gcm_seal_hw(&impl, dst, tag, nonce, aad, plaintext)
@@ -47,17 +53,19 @@ seal_gcm :: proc(ctx: ^Context_GCM, dst, tag, nonce, aad, plaintext: []byte) {
 
 
 	h: [_aes.GHASH_KEY_SIZE]byte
 	h: [_aes.GHASH_KEY_SIZE]byte
 	j0: [_aes.GHASH_BLOCK_SIZE]byte
 	j0: [_aes.GHASH_BLOCK_SIZE]byte
+	j0_enc: [_aes.GHASH_BLOCK_SIZE]byte
 	s: [_aes.GHASH_TAG_SIZE]byte
 	s: [_aes.GHASH_TAG_SIZE]byte
-	init_ghash_ct64(ctx, &h, &j0, nonce)
+	init_ghash_ct64(ctx, &h, &j0, &j0_enc, nonce)
 
 
 	// Note: Our GHASH implementation handles appending padding.
 	// Note: Our GHASH implementation handles appending padding.
 	ct64.ghash(s[:], h[:], aad)
 	ct64.ghash(s[:], h[:], aad)
-	gctr_ct64(ctx, dst, &s, plaintext, &h, nonce, true)
-	final_ghash_ct64(&s, &h, &j0, len(aad), len(plaintext))
+	gctr_ct64(ctx, dst, &s, plaintext, &h, &j0, true)
+	final_ghash_ct64(&s, &h, &j0_enc, len(aad), len(plaintext))
 	copy(tag, s[:])
 	copy(tag, s[:])
 
 
 	mem.zero_explicit(&h, len(h))
 	mem.zero_explicit(&h, len(h))
 	mem.zero_explicit(&j0, len(j0))
 	mem.zero_explicit(&j0, len(j0))
+	mem.zero_explicit(&j0_enc, len(j0_enc))
 }
 }
 
 
 // open_gcm authenticates the aad and ciphertext, and decrypts the ciphertext,
 // open_gcm authenticates the aad and ciphertext, and decrypts the ciphertext,
@@ -73,6 +81,9 @@ open_gcm :: proc(ctx: ^Context_GCM, dst, nonce, aad, ciphertext, tag: []byte) ->
 	if len(dst) != len(ciphertext) {
 	if len(dst) != len(ciphertext) {
 		panic("crypto/aes: invalid destination plaintext size")
 		panic("crypto/aes: invalid destination plaintext size")
 	}
 	}
+	if bytes.alias_inexactly(dst, ciphertext) {
+		panic("crypto/aes: dst and ciphertext alias inexactly")
+	}
 
 
 	if impl, is_hw := ctx._impl.(Context_Impl_Hardware); is_hw {
 	if impl, is_hw := ctx._impl.(Context_Impl_Hardware); is_hw {
 		return gcm_open_hw(&impl, dst, nonce, aad, ciphertext, tag)
 		return gcm_open_hw(&impl, dst, nonce, aad, ciphertext, tag)
@@ -80,12 +91,13 @@ open_gcm :: proc(ctx: ^Context_GCM, dst, nonce, aad, ciphertext, tag: []byte) ->
 
 
 	h: [_aes.GHASH_KEY_SIZE]byte
 	h: [_aes.GHASH_KEY_SIZE]byte
 	j0: [_aes.GHASH_BLOCK_SIZE]byte
 	j0: [_aes.GHASH_BLOCK_SIZE]byte
+	j0_enc: [_aes.GHASH_BLOCK_SIZE]byte
 	s: [_aes.GHASH_TAG_SIZE]byte
 	s: [_aes.GHASH_TAG_SIZE]byte
-	init_ghash_ct64(ctx, &h, &j0, nonce)
+	init_ghash_ct64(ctx, &h, &j0, &j0_enc, nonce)
 
 
 	ct64.ghash(s[:], h[:], aad)
 	ct64.ghash(s[:], h[:], aad)
-	gctr_ct64(ctx, dst, &s, ciphertext, &h, nonce, false)
-	final_ghash_ct64(&s, &h, &j0, len(aad), len(ciphertext))
+	gctr_ct64(ctx, dst, &s, ciphertext, &h, &j0, false)
+	final_ghash_ct64(&s, &h, &j0_enc, len(aad), len(ciphertext))
 
 
 	ok := crypto.compare_constant_time(s[:], tag) == 1
 	ok := crypto.compare_constant_time(s[:], tag) == 1
 	if !ok {
 	if !ok {
@@ -94,6 +106,7 @@ open_gcm :: proc(ctx: ^Context_GCM, dst, nonce, aad, ciphertext, tag: []byte) ->
 
 
 	mem.zero_explicit(&h, len(h))
 	mem.zero_explicit(&h, len(h))
 	mem.zero_explicit(&j0, len(j0))
 	mem.zero_explicit(&j0, len(j0))
+	mem.zero_explicit(&j0_enc, len(j0_enc))
 	mem.zero_explicit(&s, len(s))
 	mem.zero_explicit(&s, len(s))
 
 
 	return ok
 	return ok
@@ -106,19 +119,14 @@ reset_gcm :: proc "contextless" (ctx: ^Context_GCM) {
 	ctx._is_initialized = false
 	ctx._is_initialized = false
 }
 }
 
 
-@(private)
+@(private = "file")
 gcm_validate_common_slice_sizes :: proc(tag, nonce, aad, text: []byte) {
 gcm_validate_common_slice_sizes :: proc(tag, nonce, aad, text: []byte) {
 	if len(tag) != GCM_TAG_SIZE {
 	if len(tag) != GCM_TAG_SIZE {
 		panic("crypto/aes: invalid GCM tag size")
 		panic("crypto/aes: invalid GCM tag size")
 	}
 	}
 
 
-	// The specification supports nonces in the range [1, 2^64) bits
-	// however per NIST SP 800-38D 5.2.1.1:
-	//
-	// > For IVs, it is recommended that implementations restrict support
-	// > to the length of 96 bits, to promote interoperability, efficiency,
-	// > and simplicity of design.
-	if len(nonce) != GCM_NONCE_SIZE {
+	// The specification supports nonces in the range [1, 2^64) bits.
+	if l := len(nonce); l == 0 || u64(l) >= GCM_NONCE_SIZE_MAX {
 		panic("crypto/aes: invalid GCM nonce size")
 		panic("crypto/aes: invalid GCM nonce size")
 	}
 	}
 
 
@@ -135,6 +143,7 @@ init_ghash_ct64 :: proc(
 	ctx: ^Context_GCM,
 	ctx: ^Context_GCM,
 	h: ^[_aes.GHASH_KEY_SIZE]byte,
 	h: ^[_aes.GHASH_KEY_SIZE]byte,
 	j0: ^[_aes.GHASH_BLOCK_SIZE]byte,
 	j0: ^[_aes.GHASH_BLOCK_SIZE]byte,
+	j0_enc: ^[_aes.GHASH_BLOCK_SIZE]byte,
 	nonce: []byte,
 	nonce: []byte,
 ) {
 ) {
 	impl := &ctx._impl.(ct64.Context)
 	impl := &ctx._impl.(ct64.Context)
@@ -142,12 +151,25 @@ init_ghash_ct64 :: proc(
 	// 1. Let H = CIPH(k, 0^128)
 	// 1. Let H = CIPH(k, 0^128)
 	ct64.encrypt_block(impl, h[:], h[:])
 	ct64.encrypt_block(impl, h[:], h[:])
 
 
+	// Define a block, J0, as follows:
+	if l := len(nonce); l == GCM_NONCE_SIZE {
+		// if len(IV) = 96, then let J0 = IV || 0^31 || 1
+		copy(j0[:], nonce)
+		j0[_aes.GHASH_BLOCK_SIZE - 1] = 1
+	} else {
+		// If len(IV) != 96, then let s = 128 ceil(len(IV)/128) - len(IV),
+		// and let J0 = GHASHH(IV || 0^(s+64) || ceil(len(IV))^64).
+		ct64.ghash(j0[:], h[:], nonce)
+
+		tmp: [_aes.GHASH_BLOCK_SIZE]byte
+		endian.unchecked_put_u64be(tmp[8:], u64(l) * 8)
+		ct64.ghash(j0[:], h[:], tmp[:])
+	}
+
 	// ECB encrypt j0, so that we can just XOR with the tag.  In theory
 	// ECB encrypt j0, so that we can just XOR with the tag.  In theory
 	// this could be processed along with the final GCTR block, to
 	// this could be processed along with the final GCTR block, to
 	// potentially save a call to AES-ECB, but... just use AES-NI.
 	// potentially save a call to AES-ECB, but... just use AES-NI.
-	copy(j0[:], nonce)
-	j0[_aes.GHASH_BLOCK_SIZE - 1] = 1
-	ct64.encrypt_block(impl, j0[:], j0[:])
+	ct64.encrypt_block(impl, j0_enc[:], j0[:])
 }
 }
 
 
 @(private = "file")
 @(private = "file")
@@ -175,33 +197,27 @@ gctr_ct64 :: proc(
 	s: ^[_aes.GHASH_BLOCK_SIZE]byte,
 	s: ^[_aes.GHASH_BLOCK_SIZE]byte,
 	src: []byte,
 	src: []byte,
 	h: ^[_aes.GHASH_KEY_SIZE]byte,
 	h: ^[_aes.GHASH_KEY_SIZE]byte,
-	nonce: []byte,
+	nonce: ^[_aes.GHASH_BLOCK_SIZE]byte,
 	is_seal: bool,
 	is_seal: bool,
-) {
+) #no_bounds_check {
 	ct64_inc_ctr32 := #force_inline proc "contextless" (dst: []byte, ctr: u32) -> u32 {
 	ct64_inc_ctr32 := #force_inline proc "contextless" (dst: []byte, ctr: u32) -> u32 {
 		endian.unchecked_put_u32be(dst[12:], ctr)
 		endian.unchecked_put_u32be(dst[12:], ctr)
 		return ctr + 1
 		return ctr + 1
 	}
 	}
 
 
-	// 2. Define a block J_0 as follows:
-	//    if len(IV) = 96, then let J0 = IV || 0^31 || 1
-	//
-	// Note: We only support 96 bit IVs.
+	// Setup the counter blocks.
 	tmp, tmp2: [ct64.STRIDE][BLOCK_SIZE]byte = ---, ---
 	tmp, tmp2: [ct64.STRIDE][BLOCK_SIZE]byte = ---, ---
 	ctrs, blks: [ct64.STRIDE][]byte = ---, ---
 	ctrs, blks: [ct64.STRIDE][]byte = ---, ---
-	ctr: u32 = 2
+	ctr := endian.unchecked_get_u32be(nonce[GCM_NONCE_SIZE:]) + 1
 	for i in 0 ..< ct64.STRIDE {
 	for i in 0 ..< ct64.STRIDE {
 		// Setup scratch space for the keystream.
 		// Setup scratch space for the keystream.
 		blks[i] = tmp2[i][:]
 		blks[i] = tmp2[i][:]
 
 
 		// Pre-copy the IV to all the counter blocks.
 		// Pre-copy the IV to all the counter blocks.
 		ctrs[i] = tmp[i][:]
 		ctrs[i] = tmp[i][:]
-		copy(ctrs[i], nonce)
+		copy(ctrs[i], nonce[:GCM_NONCE_SIZE])
 	}
 	}
 
 
-	// We stitch the GCTR and GHASH operations together, so that only
-	// one pass over the ciphertext is required.
-
 	impl := &ctx._impl.(ct64.Context)
 	impl := &ctx._impl.(ct64.Context)
 	src, dst := src, dst
 	src, dst := src, dst
 
 

+ 243 - 0
core/crypto/aes/aes_gcm_hw_intel.odin

@@ -0,0 +1,243 @@
+//+build amd64
+package aes
+
+import "base:intrinsics"
+import "core:crypto"
+import "core:crypto/_aes"
+import "core:crypto/_aes/hw_intel"
+import "core:encoding/endian"
+import "core:mem"
+import "core:simd/x86"
+
+@(private)
+gcm_seal_hw :: proc(ctx: ^Context_Impl_Hardware, dst, tag, nonce, aad, plaintext: []byte) {
+	h: [_aes.GHASH_KEY_SIZE]byte
+	j0: [_aes.GHASH_BLOCK_SIZE]byte
+	j0_enc: [_aes.GHASH_BLOCK_SIZE]byte
+	s: [_aes.GHASH_TAG_SIZE]byte
+	init_ghash_hw(ctx, &h, &j0, &j0_enc, nonce)
+
+	// Note: Our GHASH implementation handles appending padding.
+	hw_intel.ghash(s[:], h[:], aad)
+	gctr_hw(ctx, dst, &s, plaintext, &h, &j0, true)
+	final_ghash_hw(&s, &h, &j0_enc, len(aad), len(plaintext))
+	copy(tag, s[:])
+
+	mem.zero_explicit(&h, len(h))
+	mem.zero_explicit(&j0, len(j0))
+	mem.zero_explicit(&j0_enc, len(j0_enc))
+}
+
+@(private)
+gcm_open_hw :: proc(ctx: ^Context_Impl_Hardware, dst, nonce, aad, ciphertext, tag: []byte) -> bool {
+	h: [_aes.GHASH_KEY_SIZE]byte
+	j0: [_aes.GHASH_BLOCK_SIZE]byte
+	j0_enc: [_aes.GHASH_BLOCK_SIZE]byte
+	s: [_aes.GHASH_TAG_SIZE]byte
+	init_ghash_hw(ctx, &h, &j0, &j0_enc, nonce)
+
+	hw_intel.ghash(s[:], h[:], aad)
+	gctr_hw(ctx, dst, &s, ciphertext, &h, &j0, false)
+	final_ghash_hw(&s, &h, &j0_enc, len(aad), len(ciphertext))
+
+	ok := crypto.compare_constant_time(s[:], tag) == 1
+	if !ok {
+		mem.zero_explicit(raw_data(dst), len(dst))
+	}
+
+	mem.zero_explicit(&h, len(h))
+	mem.zero_explicit(&j0, len(j0))
+	mem.zero_explicit(&j0_enc, len(j0_enc))
+	mem.zero_explicit(&s, len(s))
+
+	return ok
+}
+
+@(private = "file")
+init_ghash_hw :: proc(
+	ctx: ^Context_Impl_Hardware,
+	h: ^[_aes.GHASH_KEY_SIZE]byte,
+	j0: ^[_aes.GHASH_BLOCK_SIZE]byte,
+	j0_enc: ^[_aes.GHASH_BLOCK_SIZE]byte,
+	nonce: []byte,
+) {
+	// 1. Let H = CIPH(k, 0^128)
+	encrypt_block_hw(ctx, h[:], h[:])
+
+	// Define a block, J0, as follows:
+	if l := len(nonce); l == GCM_NONCE_SIZE {
+		// if len(IV) = 96, then let J0 = IV || 0^31 || 1
+		copy(j0[:], nonce)
+		j0[_aes.GHASH_BLOCK_SIZE - 1] = 1
+	} else {
+		// If len(IV) != 96, then let s = 128 ceil(len(IV)/128) - len(IV),
+		// and let J0 = GHASHH(IV || 0^(s+64) || ceil(len(IV))^64).
+		hw_intel.ghash(j0[:], h[:], nonce)
+
+		tmp: [_aes.GHASH_BLOCK_SIZE]byte
+		endian.unchecked_put_u64be(tmp[8:], u64(l) * 8)
+		hw_intel.ghash(j0[:], h[:], tmp[:])
+	}
+
+	// ECB encrypt j0, so that we can just XOR with the tag.
+	encrypt_block_hw(ctx, j0_enc[:], j0[:])
+}
+
+@(private = "file", enable_target_feature = "sse2")
+final_ghash_hw :: proc(
+	s: ^[_aes.GHASH_BLOCK_SIZE]byte,
+	h: ^[_aes.GHASH_KEY_SIZE]byte,
+	j0: ^[_aes.GHASH_BLOCK_SIZE]byte,
+	a_len: int,
+	t_len: int,
+) {
+	blk: [_aes.GHASH_BLOCK_SIZE]byte
+	endian.unchecked_put_u64be(blk[0:], u64(a_len) * 8)
+	endian.unchecked_put_u64be(blk[8:], u64(t_len) * 8)
+
+	hw_intel.ghash(s[:], h[:], blk[:])
+	j0_vec := intrinsics.unaligned_load((^x86.__m128i)(j0))
+	s_vec := intrinsics.unaligned_load((^x86.__m128i)(s))
+	s_vec = x86._mm_xor_si128(s_vec, j0_vec)
+	intrinsics.unaligned_store((^x86.__m128i)(s), s_vec)
+}
+
+@(private = "file", enable_target_feature = "sse2,sse4.1,aes")
+gctr_hw :: proc(
+	ctx: ^Context_Impl_Hardware,
+	dst: []byte,
+	s: ^[_aes.GHASH_BLOCK_SIZE]byte,
+	src: []byte,
+	h: ^[_aes.GHASH_KEY_SIZE]byte,
+	nonce: ^[_aes.GHASH_BLOCK_SIZE]byte,
+	is_seal: bool,
+) #no_bounds_check {
+	sks: [15]x86.__m128i = ---
+	for i in 0 ..= ctx._num_rounds {
+		sks[i] = intrinsics.unaligned_load((^x86.__m128i)(&ctx._sk_exp_enc[i]))
+	}
+
+	// Setup the counter block
+	ctr_blk := intrinsics.unaligned_load((^x86.__m128i)(nonce))
+	ctr := endian.unchecked_get_u32be(nonce[GCM_NONCE_SIZE:]) + 1
+
+	src, dst := src, dst
+
+	// Note: Instead of doing GHASH and CTR separately, it is more
+	// performant to interleave (stitch) the two operations together.
+	// This results in an unreadable mess, so we opt for simplicity
+	// as performance is adequate.
+
+	blks: [CTR_STRIDE_HW]x86.__m128i = ---
+	nr_blocks := len(src) / BLOCK_SIZE
+	for nr_blocks >= CTR_STRIDE_HW {
+		if !is_seal {
+			hw_intel.ghash(s[:], h[:], src[:CTR_STRIDE_BYTES_HW])
+		}
+
+		#unroll for i in 0 ..< CTR_STRIDE_HW {
+			blks[i], ctr = hw_inc_ctr32(&ctr_blk, ctr)
+		}
+
+		#unroll for i in 0 ..< CTR_STRIDE_HW {
+			blks[i] = x86._mm_xor_si128(blks[i], sks[0])
+		}
+		#unroll for i in 1 ..= 9 {
+			#unroll for j in 0 ..< CTR_STRIDE_HW {
+				blks[j] = x86._mm_aesenc_si128(blks[j], sks[i])
+			}
+		}
+		switch ctx._num_rounds {
+		case _aes.ROUNDS_128:
+			#unroll for i in 0 ..< CTR_STRIDE_HW {
+				blks[i] = x86._mm_aesenclast_si128(blks[i], sks[10])
+			}
+		case _aes.ROUNDS_192:
+			#unroll for i in 10 ..= 11 {
+				#unroll for j in 0 ..< CTR_STRIDE_HW {
+					blks[j] = x86._mm_aesenc_si128(blks[j], sks[i])
+				}
+			}
+			#unroll for i in 0 ..< CTR_STRIDE_HW {
+				blks[i] = x86._mm_aesenclast_si128(blks[i], sks[12])
+			}
+		case _aes.ROUNDS_256:
+			#unroll for i in 10 ..= 13 {
+				#unroll for j in 0 ..< CTR_STRIDE_HW {
+					blks[j] = x86._mm_aesenc_si128(blks[j], sks[i])
+				}
+			}
+			#unroll for i in 0 ..< CTR_STRIDE_HW {
+				blks[i] = x86._mm_aesenclast_si128(blks[i], sks[14])
+			}
+		}
+
+		xor_blocks_hw(dst, src, blks[:])
+
+		if is_seal {
+			hw_intel.ghash(s[:], h[:], dst[:CTR_STRIDE_BYTES_HW])
+		}
+
+		src = src[CTR_STRIDE_BYTES_HW:]
+		dst = dst[CTR_STRIDE_BYTES_HW:]
+		nr_blocks -= CTR_STRIDE_HW
+	}
+
+	// Handle the remainder.
+	for n := len(src); n > 0; {
+		l := min(n, BLOCK_SIZE)
+		if !is_seal {
+			hw_intel.ghash(s[:], h[:], src[:l])
+		}
+
+		blks[0], ctr = hw_inc_ctr32(&ctr_blk, ctr)
+
+		blks[0] = x86._mm_xor_si128(blks[0], sks[0])
+		#unroll for i in 1 ..= 9 {
+			blks[0] = x86._mm_aesenc_si128(blks[0], sks[i])
+		}
+		switch ctx._num_rounds {
+		case _aes.ROUNDS_128:
+			blks[0] = x86._mm_aesenclast_si128(blks[0], sks[10])
+		case _aes.ROUNDS_192:
+			#unroll for i in 10 ..= 11 {
+				blks[0] = x86._mm_aesenc_si128(blks[0], sks[i])
+			}
+			blks[0] = x86._mm_aesenclast_si128(blks[0], sks[12])
+		case _aes.ROUNDS_256:
+			#unroll for i in 10 ..= 13 {
+				blks[0] = x86._mm_aesenc_si128(blks[0], sks[i])
+			}
+			blks[0] = x86._mm_aesenclast_si128(blks[0], sks[14])
+		}
+
+		if l == BLOCK_SIZE {
+			xor_blocks_hw(dst, src, blks[:1])
+		} else {
+			blk: [BLOCK_SIZE]byte
+			copy(blk[:], src)
+			xor_blocks_hw(blk[:], blk[:], blks[:1])
+			copy(dst, blk[:l])
+		}
+		if is_seal {
+			hw_intel.ghash(s[:], h[:], dst[:l])
+		}
+
+		dst = dst[l:]
+		src = src[l:]
+		n -= l
+	}
+
+	mem.zero_explicit(&blks, size_of(blks))
+	mem.zero_explicit(&sks, size_of(sks))
+}
+
+// BUG: Sticking this in gctr_hw (like the other implementations) crashes
+// the compiler.
+//
+// src/check_expr.cpp(7892): Assertion Failure: `c->curr_proc_decl->entity`
+@(private = "file", enable_target_feature = "sse4.1")
+hw_inc_ctr32 :: #force_inline proc "contextless" (src: ^x86.__m128i, ctr: u32) -> (x86.__m128i, u32) {
+	ret := x86._mm_insert_epi32(src^, i32(intrinsics.byte_swap(ctr)), 3)
+	return ret, ctr + 1
+}

+ 1 - 0
core/crypto/aes/aes_impl_hw_gen.odin

@@ -1,3 +1,4 @@
+//+build !amd64
 package aes
 package aes
 
 
 @(private = "file")
 @(private = "file")

+ 18 - 0
core/crypto/aes/aes_impl_hw_intel.odin

@@ -0,0 +1,18 @@
+//+build amd64
+package aes
+
+import "core:crypto/_aes/hw_intel"
+
+// is_hardware_accelerated returns true iff hardware accelerated AES
+// is supported.
+is_hardware_accelerated :: proc "contextless" () -> bool {
+	return hw_intel.is_supported()
+}
+
+@(private)
+Context_Impl_Hardware :: hw_intel.Context
+
+@(private, enable_target_feature = "sse2,aes")
+init_impl_hw :: proc(ctx: ^Context_Impl_Hardware, key: []byte) {
+	hw_intel.init(ctx, key)
+}

+ 5 - 3
core/crypto/chacha20/chacha20.odin

@@ -7,6 +7,7 @@ See:
 */
 */
 package chacha20
 package chacha20
 
 
+import "core:bytes"
 import "core:encoding/endian"
 import "core:encoding/endian"
 import "core:math/bits"
 import "core:math/bits"
 import "core:mem"
 import "core:mem"
@@ -121,14 +122,15 @@ seek :: proc(ctx: ^Context, block_nr: u64) {
 xor_bytes :: proc(ctx: ^Context, dst, src: []byte) {
 xor_bytes :: proc(ctx: ^Context, dst, src: []byte) {
 	assert(ctx._is_initialized)
 	assert(ctx._is_initialized)
 
 
-	// TODO: Enforcing that dst and src alias exactly or not at all
-	// is a good idea, though odd aliasing should be extremely uncommon.
-
 	src, dst := src, dst
 	src, dst := src, dst
 	if dst_len := len(dst); dst_len < len(src) {
 	if dst_len := len(dst); dst_len < len(src) {
 		src = src[:dst_len]
 		src = src[:dst_len]
 	}
 	}
 
 
+	if bytes.alias_inexactly(dst, src) {
+		panic("crypto/chacha20: dst and src alias inexactly")
+	}
+
 	for remaining := len(src); remaining > 0; {
 	for remaining := len(src); remaining > 0; {
 		// Process multiple blocks at once
 		// Process multiple blocks at once
 		if ctx._off == _BLOCK_SIZE {
 		if ctx._off == _BLOCK_SIZE {

+ 5 - 1
core/crypto/crypto.odin

@@ -60,7 +60,11 @@ rand_bytes :: proc (dst: []byte) {
 	_rand_bytes(dst)
 	_rand_bytes(dst)
 }
 }
 
 
-
+// random_generator returns a `runtime.Random_Generator` backed by the
+// system entropy source.
+//
+// Support for the system entropy source can be checked with the
+// `HAS_RAND_BYTES` boolean constant.
 random_generator :: proc() -> runtime.Random_Generator {
 random_generator :: proc() -> runtime.Random_Generator {
 	return {
 	return {
 		procedure = proc(data: rawptr, mode: runtime.Random_Generator_Mode, p: []byte) {
 		procedure = proc(data: rawptr, mode: runtime.Random_Generator_Mode, p: []byte) {

+ 5 - 4
core/encoding/cbor/marshal.odin

@@ -351,7 +351,8 @@ _marshal_into_encoder :: proc(e: Encoder, v: any, ti: ^runtime.Type_Info) -> (er
 				builder := strings.builder_from_slice(res[:])
 				builder := strings.builder_from_slice(res[:])
 				e.writer = strings.to_stream(&builder)
 				e.writer = strings.to_stream(&builder)
 
 
-				assert(_encode_u64(e, u64(len(str)), .Text) == nil)
+				err := _encode_u64(e, u64(len(str)), .Text)
+				assert(err == nil)
 				res[9] = u8(len(builder.buf))
 				res[9] = u8(len(builder.buf))
 				assert(res[9] < 10)
 				assert(res[9] < 10)
 				return
 				return
@@ -506,7 +507,7 @@ _marshal_into_encoder :: proc(e: Encoder, v: any, ti: ^runtime.Type_Info) -> (er
 		}
 		}
 		
 		
 		n: u64; {
 		n: u64; {
-			for _, i in info.names {
+			for _, i in info.names[:info.field_count] {
 				if field_name(info, i) != "-" {
 				if field_name(info, i) != "-" {
 					n += 1
 					n += 1
 				}
 				}
@@ -522,7 +523,7 @@ _marshal_into_encoder :: proc(e: Encoder, v: any, ti: ^runtime.Type_Info) -> (er
 			entries := make([dynamic]Name, 0, n, e.temp_allocator) or_return
 			entries := make([dynamic]Name, 0, n, e.temp_allocator) or_return
 			defer delete(entries)
 			defer delete(entries)
 
 
-			for _, i in info.names {
+			for _, i in info.names[:info.field_count] {
 				fname := field_name(info, i)
 				fname := field_name(info, i)
 				if fname == "-" {
 				if fname == "-" {
 					continue
 					continue
@@ -540,7 +541,7 @@ _marshal_into_encoder :: proc(e: Encoder, v: any, ti: ^runtime.Type_Info) -> (er
 				marshal_entry(e, info, v, entry.name, entry.field) or_return
 				marshal_entry(e, info, v, entry.name, entry.field) or_return
 			}
 			}
 		} else {
 		} else {
-			for _, i in info.names {
+			for _, i in info.names[:info.field_count] {
 				fname := field_name(info, i)
 				fname := field_name(info, i)
 				if fname == "-" {
 				if fname == "-" {
 					continue
 					continue

+ 3 - 2
core/encoding/cbor/unmarshal.odin

@@ -96,7 +96,8 @@ _unmarshal_value :: proc(d: Decoder, v: any, hdr: Header, allocator := context.a
 			ti = reflect.type_info_base(variant)
 			ti = reflect.type_info_base(variant)
 			if !reflect.is_pointer_internally(variant) {
 			if !reflect.is_pointer_internally(variant) {
 				tag := any{rawptr(uintptr(v.data) + u.tag_offset), u.tag_type.id}
 				tag := any{rawptr(uintptr(v.data) + u.tag_offset), u.tag_type.id}
-				assert(_assign_int(tag, 1))
+				assigned := _assign_int(tag, 1)
+				assert(assigned)
 			}
 			}
 		}
 		}
 	}
 	}
@@ -618,7 +619,7 @@ _unmarshal_map :: proc(d: Decoder, v: any, ti: ^reflect.Type_Info, hdr: Header,
 
 
 	#partial switch t in ti.variant {
 	#partial switch t in ti.variant {
 	case reflect.Type_Info_Struct:
 	case reflect.Type_Info_Struct:
-		if t.is_raw_union {
+		if .raw_union in t.flags {
 			return _unsupported(v, hdr)
 			return _unsupported(v, hdr)
 		}
 		}
 
 

+ 12 - 9
core/encoding/ini/ini.odin

@@ -82,15 +82,17 @@ Map :: distinct map[string]map[string]string
 
 
 load_map_from_string :: proc(src: string, allocator: runtime.Allocator, options := DEFAULT_OPTIONS) -> (m: Map, err: runtime.Allocator_Error) {
 load_map_from_string :: proc(src: string, allocator: runtime.Allocator, options := DEFAULT_OPTIONS) -> (m: Map, err: runtime.Allocator_Error) {
 	unquote :: proc(val: string) -> (string, runtime.Allocator_Error) {
 	unquote :: proc(val: string) -> (string, runtime.Allocator_Error) {
-		v, allocated, ok := strconv.unquote_string(val)
-		if !ok {
-			return strings.clone(val)
-		}
-		if allocated {
-			return v, nil
+		if len(val) > 0 && (val[0] == '"' || val[0] == '\'') {
+			v, allocated, ok := strconv.unquote_string(val)
+			if !ok {
+				return strings.clone(val)
+			}
+			if allocated {
+				return v, nil
+			}
+			return strings.clone(v), nil
 		}
 		}
-		return strings.clone(v)
-
+		return strings.clone(val)
 	}
 	}
 
 
 	context.allocator = allocator
 	context.allocator = allocator
@@ -121,7 +123,7 @@ load_map_from_path :: proc(path: string, allocator: runtime.Allocator, options :
 	data := os.read_entire_file(path, allocator) or_return
 	data := os.read_entire_file(path, allocator) or_return
 	defer delete(data, allocator)
 	defer delete(data, allocator)
 	m, err = load_map_from_string(string(data), allocator, options)
 	m, err = load_map_from_string(string(data), allocator, options)
-	ok = err != nil
+	ok = err == nil
 	defer if !ok {
 	defer if !ok {
 		delete_map(m)
 		delete_map(m)
 	}
 	}
@@ -142,6 +144,7 @@ delete_map :: proc(m: Map) {
 			delete(value, allocator)
 			delete(value, allocator)
 		}
 		}
 		delete(section)
 		delete(section)
+		delete(pairs)
 	}
 	}
 	delete(m)
 	delete(m)
 }
 }

+ 51 - 34
core/encoding/json/marshal.odin

@@ -100,38 +100,7 @@ marshal_to_writer :: proc(w: io.Writer, v: any, opt: ^Marshal_Options) -> (err:
 
 
 	case runtime.Type_Info_Integer:
 	case runtime.Type_Info_Integer:
 		buf: [40]byte
 		buf: [40]byte
-		u: u128
-		switch i in a {
-		case i8:      u = u128(i)
-		case i16:     u = u128(i)
-		case i32:     u = u128(i)
-		case i64:     u = u128(i)
-		case i128:    u = u128(i)
-		case int:     u = u128(i)
-		case u8:      u = u128(i)
-		case u16:     u = u128(i)
-		case u32:     u = u128(i)
-		case u64:     u = u128(i)
-		case u128:    u = u128(i)
-		case uint:    u = u128(i)
-		case uintptr: u = u128(i)
-
-		case i16le:  u = u128(i)
-		case i32le:  u = u128(i)
-		case i64le:  u = u128(i)
-		case u16le:  u = u128(i)
-		case u32le:  u = u128(i)
-		case u64le:  u = u128(i)
-		case u128le: u = u128(i)
-
-		case i16be:  u = u128(i)
-		case i32be:  u = u128(i)
-		case i64be:  u = u128(i)
-		case u16be:  u = u128(i)
-		case u32be:  u = u128(i)
-		case u64be:  u = u128(i)
-		case u128be: u = u128(i)
-		}
+		u := cast_any_int_to_u128(a)
 
 
 		s: string
 		s: string
 
 
@@ -310,7 +279,12 @@ marshal_to_writer :: proc(w: io.Writer, v: any, opt: ^Marshal_Options) -> (err:
 							case cstring: name = string(s)
 							case cstring: name = string(s)
 							}
 							}
 							opt_write_key(w, opt, name) or_return
 							opt_write_key(w, opt, name) or_return
-
+						case runtime.Type_Info_Integer:
+							buf: [40]byte
+							u := cast_any_int_to_u128(ka)
+							name = strconv.append_bits_128(buf[:], u, 10, info.signed, 8*kti.size, "0123456789", nil)
+							
+							opt_write_key(w, opt, name) or_return
 						case: return .Unsupported_Type
 						case: return .Unsupported_Type
 						}
 						}
 					}
 					}
@@ -406,10 +380,15 @@ marshal_to_writer :: proc(w: io.Writer, v: any, opt: ^Marshal_Options) -> (err:
 			ti := runtime.type_info_base(type_info_of(v.id))
 			ti := runtime.type_info_base(type_info_of(v.id))
 			info := ti.variant.(runtime.Type_Info_Struct)
 			info := ti.variant.(runtime.Type_Info_Struct)
 			first_iteration := true
 			first_iteration := true
-			for name, i in info.names {
+			for name, i in info.names[:info.field_count] {
 				omitempty := false
 				omitempty := false
 
 
 				json_name, extra := json_name_from_tag_value(reflect.struct_tag_get(reflect.Struct_Tag(info.tags[i]), "json"))
 				json_name, extra := json_name_from_tag_value(reflect.struct_tag_get(reflect.Struct_Tag(info.tags[i]), "json"))
+
+				if json_name == "-" {
+					continue
+				}
+
 				for flag in strings.split_iterator(&extra, ",") {
 				for flag in strings.split_iterator(&extra, ",") {
 					switch flag {
 					switch flag {
 					case "omitempty":
 					case "omitempty":
@@ -657,3 +636,41 @@ opt_write_indentation :: proc(w: io.Writer, opt: ^Marshal_Options) -> (err: io.E
 
 
 	return
 	return
 }
 }
+
+@(private)
+cast_any_int_to_u128 :: proc(any_int_value: any) -> u128 {
+	u: u128 = 0
+	switch i in any_int_value {
+	case i8:      u = u128(i)
+	case i16:     u = u128(i)
+	case i32:     u = u128(i)
+	case i64:     u = u128(i)
+	case i128:    u = u128(i)
+	case int:     u = u128(i)
+	case u8:      u = u128(i)
+	case u16:     u = u128(i)
+	case u32:     u = u128(i)
+	case u64:     u = u128(i)
+	case u128:    u = u128(i)
+	case uint:    u = u128(i)
+	case uintptr: u = u128(i)
+
+	case i16le:  u = u128(i)
+	case i32le:  u = u128(i)
+	case i64le:  u = u128(i)
+	case u16le:  u = u128(i)
+	case u32le:  u = u128(i)
+	case u64le:  u = u128(i)
+	case u128le: u = u128(i)
+
+	case i16be:  u = u128(i)
+	case i32be:  u = u128(i)
+	case i64be:  u = u128(i)
+	case u16be:  u = u128(i)
+	case u32be:  u = u128(i)
+	case u64be:  u = u128(i)
+	case u128be: u = u128(i)
+	}
+
+	return u
+}

+ 24 - 11
core/encoding/json/unmarshal.odin

@@ -363,12 +363,11 @@ unmarshal_object :: proc(p: ^Parser, v: any, end_token: Token_Kind) -> (err: Unm
 	}
 	}
 
 
 	v := v
 	v := v
-	v = reflect.any_base(v)
-	ti := type_info_of(v.id)
+	ti := reflect.type_info_base(type_info_of(v.id))
 	
 	
 	#partial switch t in ti.variant {
 	#partial switch t in ti.variant {
 	case reflect.Type_Info_Struct:
 	case reflect.Type_Info_Struct:
-		if t.is_raw_union {
+		if .raw_union in t.flags {
 			return UNSUPPORTED_TYPE
 			return UNSUPPORTED_TYPE
 		}
 		}
 	
 	
@@ -475,7 +474,7 @@ unmarshal_object :: proc(p: ^Parser, v: any, end_token: Token_Kind) -> (err: Unm
 		}
 		}
 		
 		
 	case reflect.Type_Info_Map:
 	case reflect.Type_Info_Map:
-		if !reflect.is_string(t.key) {
+		if !reflect.is_string(t.key) && !reflect.is_integer(t.key) {
 			return UNSUPPORTED_TYPE
 			return UNSUPPORTED_TYPE
 		}
 		}
 		raw_map := (^mem.Raw_Map)(v.data)
 		raw_map := (^mem.Raw_Map)(v.data)
@@ -492,25 +491,39 @@ unmarshal_object :: proc(p: ^Parser, v: any, end_token: Token_Kind) -> (err: Unm
 			key, _ := parse_object_key(p, p.allocator)
 			key, _ := parse_object_key(p, p.allocator)
 			unmarshal_expect_token(p, .Colon)
 			unmarshal_expect_token(p, .Colon)
 			
 			
-			
+
 			mem.zero_slice(elem_backing)
 			mem.zero_slice(elem_backing)
 			if uerr := unmarshal_value(p, map_backing_value); uerr != nil {
 			if uerr := unmarshal_value(p, map_backing_value); uerr != nil {
 				delete(key, p.allocator)
 				delete(key, p.allocator)
 				return uerr
 				return uerr
 			}
 			}
 
 
-			key_ptr := rawptr(&key)
+			key_ptr: rawptr
 
 
-			key_cstr: cstring
-			if reflect.is_cstring(t.key) {
-				key_cstr = cstring(raw_data(key))
-				key_ptr = &key_cstr
+			#partial switch tk in t.key.variant {
+				case runtime.Type_Info_String:			
+					key_ptr = rawptr(&key)
+					key_cstr: cstring
+					if reflect.is_cstring(t.key) {
+						key_cstr = cstring(raw_data(key))
+						key_ptr = &key_cstr
+					}
+				case runtime.Type_Info_Integer:
+					i, ok := strconv.parse_i128(key)
+					if !ok	{ return UNSUPPORTED_TYPE }
+					key_ptr = rawptr(&i)
+				case: return UNSUPPORTED_TYPE
 			}
 			}
-			
+
 			set_ptr := runtime.__dynamic_map_set_without_hash(raw_map, t.map_info, key_ptr, map_backing_value.data)
 			set_ptr := runtime.__dynamic_map_set_without_hash(raw_map, t.map_info, key_ptr, map_backing_value.data)
 			if set_ptr == nil {
 			if set_ptr == nil {
 				delete(key, p.allocator)
 				delete(key, p.allocator)
 			} 
 			} 
+
+			// there's no need to keep string value on the heap, since it was copied into map 
+			if reflect.is_integer(t.key) {
+				delete(key, p.allocator)
+			}
 			
 			
 			if parse_comma(p) {
 			if parse_comma(p) {
 				break map_loop
 				break map_loop

+ 44 - 25
core/fmt/fmt.odin

@@ -334,6 +334,27 @@ panicf :: proc(fmt: string, args: ..any, loc := #caller_location) -> ! {
 	message := tprintf(fmt, ..args)
 	message := tprintf(fmt, ..args)
 	p("Panic", message, loc)
 	p("Panic", message, loc)
 }
 }
+
+// 	Creates a formatted C string
+//
+// 	*Allocates Using Context's Allocator*
+//
+// 	Inputs:
+// 	- args: A variadic list of arguments to be formatted.
+// 	- sep: An optional separator string (default is a single space).
+//
+// 	Returns: A formatted C string.
+//
+@(require_results)
+caprint :: proc(args: ..any, sep := " ", allocator := context.allocator) -> cstring {
+	str: strings.Builder
+	strings.builder_init(&str, allocator)
+	sbprint(&str, ..args, sep=sep)
+	strings.write_byte(&str, 0)
+	s := strings.to_string(str)
+	return cstring(raw_data(s))
+}
+
 // Creates a formatted C string
 // Creates a formatted C string
 //
 //
 // *Allocates Using Context's Allocator*
 // *Allocates Using Context's Allocator*
@@ -346,9 +367,9 @@ panicf :: proc(fmt: string, args: ..any, loc := #caller_location) -> ! {
 // Returns: A formatted C string
 // Returns: A formatted C string
 //
 //
 @(require_results)
 @(require_results)
-caprintf :: proc(format: string, args: ..any, newline := false) -> cstring {
+caprintf :: proc(format: string, args: ..any, allocator := context.allocator, newline := false) -> cstring {
 	str: strings.Builder
 	str: strings.Builder
-	strings.builder_init(&str)
+	strings.builder_init(&str, allocator)
 	sbprintf(&str, format, ..args, newline=newline)
 	sbprintf(&str, format, ..args, newline=newline)
 	strings.write_byte(&str, 0)
 	strings.write_byte(&str, 0)
 	s := strings.to_string(str)
 	s := strings.to_string(str)
@@ -365,8 +386,8 @@ caprintf :: proc(format: string, args: ..any, newline := false) -> cstring {
 // Returns: A formatted C string
 // Returns: A formatted C string
 //
 //
 @(require_results)
 @(require_results)
-caprintfln :: proc(format: string, args: ..any) -> cstring {
-	return caprintf(format, ..args, newline=true)
+caprintfln :: proc(format: string, args: ..any, allocator := context.allocator) -> cstring {
+	return caprintf(format, ..args, allocator=allocator, newline=true)
 }
 }
 // 	Creates a formatted C string
 // 	Creates a formatted C string
 //
 //
@@ -380,12 +401,7 @@ caprintfln :: proc(format: string, args: ..any) -> cstring {
 //
 //
 @(require_results)
 @(require_results)
 ctprint :: proc(args: ..any, sep := " ") -> cstring {
 ctprint :: proc(args: ..any, sep := " ") -> cstring {
-	str: strings.Builder
-	strings.builder_init(&str, context.temp_allocator)
-	sbprint(&str, ..args, sep=sep)
-	strings.write_byte(&str, 0)
-	s := strings.to_string(str)
-	return cstring(raw_data(s))
+	return caprint(args=args, sep=sep, allocator=context.temp_allocator)
 }
 }
 // Creates a formatted C string
 // Creates a formatted C string
 //
 //
@@ -400,12 +416,7 @@ ctprint :: proc(args: ..any, sep := " ") -> cstring {
 //
 //
 @(require_results)
 @(require_results)
 ctprintf :: proc(format: string, args: ..any, newline := false) -> cstring {
 ctprintf :: proc(format: string, args: ..any, newline := false) -> cstring {
-	str: strings.Builder
-	strings.builder_init(&str, context.temp_allocator)
-	sbprintf(&str, format, ..args, newline=newline)
-	strings.write_byte(&str, 0)
-	s := strings.to_string(str)
-	return cstring(raw_data(s))
+	return caprintf(format=format, args=args, allocator=context.temp_allocator, newline=newline)
 }
 }
 // Creates a formatted C string, followed by a newline.
 // Creates a formatted C string, followed by a newline.
 //
 //
@@ -419,7 +430,7 @@ ctprintf :: proc(format: string, args: ..any, newline := false) -> cstring {
 //
 //
 @(require_results)
 @(require_results)
 ctprintfln :: proc(format: string, args: ..any) -> cstring {
 ctprintfln :: proc(format: string, args: ..any) -> cstring {
-	return ctprintf(format, ..args, newline=true)
+	return caprintf(format=format, args=args, allocator=context.temp_allocator, newline=true)
 }
 }
 // Formats using the default print settings and writes to the given strings.Builder
 // Formats using the default print settings and writes to the given strings.Builder
 //
 //
@@ -1861,7 +1872,7 @@ handle_tag :: proc(state: ^Info_State, data: rawptr, info: reflect.Type_Info_Str
 		if optional_len == nil {
 		if optional_len == nil {
 			return
 			return
 		}
 		}
-		for f, i in info.names {
+		for f, i in info.names[:info.field_count] {
 			if f != field_name {
 			if f != field_name {
 				continue
 				continue
 			}
 			}
@@ -1965,7 +1976,7 @@ fmt_struct :: proc(fi: ^Info, v: any, the_verb: rune, info: runtime.Type_Info_St
 		fmt_bad_verb(fi, the_verb)
 		fmt_bad_verb(fi, the_verb)
 		return
 		return
 	}
 	}
-	if info.is_raw_union {
+	if .raw_union in info.flags {
 		if type_name == "" {
 		if type_name == "" {
 			io.write_string(fi.writer, "(raw union)", &fi.n)
 			io.write_string(fi.writer, "(raw union)", &fi.n)
 		} else {
 		} else {
@@ -1989,7 +2000,7 @@ fmt_struct :: proc(fi: ^Info, v: any, the_verb: rune, info: runtime.Type_Info_St
 	// fi.hash = false;
 	// fi.hash = false;
 	fi.indent += 1
 	fi.indent += 1
 
 
-	is_empty := len(info.names) == 0
+	is_empty := info.field_count == 0
 
 
 	if !is_soa && hash && !is_empty {
 	if !is_soa && hash && !is_empty {
 		io.write_byte(fi.writer, '\n', &fi.n)
 		io.write_byte(fi.writer, '\n', &fi.n)
@@ -2010,17 +2021,17 @@ fmt_struct :: proc(fi: ^Info, v: any, the_verb: rune, info: runtime.Type_Info_St
 			base_type_name = v.name
 			base_type_name = v.name
 		}
 		}
 
 
-		actual_field_count := len(info.names)
+		actual_field_count := info.field_count
 
 
 		n := uintptr(info.soa_len)
 		n := uintptr(info.soa_len)
 
 
 		if info.soa_kind == .Slice {
 		if info.soa_kind == .Slice {
-			actual_field_count = len(info.names)-1 // len
+			actual_field_count = info.field_count-1 // len
 
 
 			n = uintptr((^int)(uintptr(v.data) + info.offsets[actual_field_count])^)
 			n = uintptr((^int)(uintptr(v.data) + info.offsets[actual_field_count])^)
 
 
 		} else if info.soa_kind == .Dynamic {
 		} else if info.soa_kind == .Dynamic {
-			actual_field_count = len(info.names)-3 // len, cap, allocator
+			actual_field_count = info.field_count-3 // len, cap, allocator
 
 
 			n = uintptr((^int)(uintptr(v.data) + info.offsets[actual_field_count])^)
 			n = uintptr((^int)(uintptr(v.data) + info.offsets[actual_field_count])^)
 		}
 		}
@@ -2099,7 +2110,7 @@ fmt_struct :: proc(fi: ^Info, v: any, the_verb: rune, info: runtime.Type_Info_St
 		}
 		}
 	} else {
 	} else {
 		field_count := -1
 		field_count := -1
-		for name, i in info.names {
+		for name, i in info.names[:info.field_count] {
 			optional_len: int = -1
 			optional_len: int = -1
 			use_nul_termination: bool = false
 			use_nul_termination: bool = false
 			verb := the_verb if the_verb == 'w' else 'v'
 			verb := the_verb if the_verb == 'w' else 'v'
@@ -2605,7 +2616,7 @@ fmt_bit_field :: proc(fi: ^Info, v: any, verb: rune, info: runtime.Type_Info_Bit
 
 
 
 
 	field_count := -1
 	field_count := -1
-	for name, i in info.names {
+	for name, i in info.names[:info.field_count] {
 		field_verb := verb
 		field_verb := verb
 		if handle_bit_field_tag(v.data, info, i, &field_verb) {
 		if handle_bit_field_tag(v.data, info, i, &field_verb) {
 			continue
 			continue
@@ -2751,9 +2762,11 @@ fmt_value :: proc(fi: ^Info, v: any, verb: rune) {
 			elem := runtime.type_info_base(info.elem)
 			elem := runtime.type_info_base(info.elem)
 			if elem != nil {
 			if elem != nil {
 				if n, ok := fi.optional_len.?; ok {
 				if n, ok := fi.optional_len.?; ok {
+					fi.optional_len = nil
 					fmt_array(fi, ptr, n, elem.size, elem, verb)
 					fmt_array(fi, ptr, n, elem.size, elem, verb)
 					return
 					return
 				} else if fi.use_nul_termination {
 				} else if fi.use_nul_termination {
+					fi.use_nul_termination = false
 					fmt_array_nul_terminated(fi, ptr, -1, elem.size, elem, verb)
 					fmt_array_nul_terminated(fi, ptr, -1, elem.size, elem, verb)
 					return
 					return
 				}
 				}
@@ -2855,8 +2868,10 @@ fmt_value :: proc(fi: ^Info, v: any, verb: rune) {
 		n := info.count
 		n := info.count
 		ptr := v.data
 		ptr := v.data
 		if ol, ok := fi.optional_len.?; ok {
 		if ol, ok := fi.optional_len.?; ok {
+			fi.optional_len = nil
 			n = min(n, ol)
 			n = min(n, ol)
 		} else if fi.use_nul_termination {
 		} else if fi.use_nul_termination {
+			fi.use_nul_termination = false
 			fmt_array_nul_terminated(fi, ptr, n, info.elem_size, info.elem, verb)
 			fmt_array_nul_terminated(fi, ptr, n, info.elem_size, info.elem, verb)
 			return
 			return
 		}
 		}
@@ -2867,8 +2882,10 @@ fmt_value :: proc(fi: ^Info, v: any, verb: rune) {
 		n := slice.len
 		n := slice.len
 		ptr := slice.data
 		ptr := slice.data
 		if ol, ok := fi.optional_len.?; ok {
 		if ol, ok := fi.optional_len.?; ok {
+			fi.optional_len = nil
 			n = min(n, ol)
 			n = min(n, ol)
 		} else if fi.use_nul_termination {
 		} else if fi.use_nul_termination {
+			fi.use_nul_termination = false
 			fmt_array_nul_terminated(fi, ptr, n, info.elem_size, info.elem, verb)
 			fmt_array_nul_terminated(fi, ptr, n, info.elem_size, info.elem, verb)
 			return
 			return
 		}
 		}
@@ -2879,8 +2896,10 @@ fmt_value :: proc(fi: ^Info, v: any, verb: rune) {
 		n := array.len
 		n := array.len
 		ptr := array.data
 		ptr := array.data
 		if ol, ok := fi.optional_len.?; ok {
 		if ol, ok := fi.optional_len.?; ok {
+			fi.optional_len = nil
 			n = min(n, ol)
 			n = min(n, ol)
 		} else if fi.use_nul_termination {
 		} else if fi.use_nul_termination {
+			fi.use_nul_termination = false
 			fmt_array_nul_terminated(fi, ptr, n, info.elem_size, info.elem, verb)
 			fmt_array_nul_terminated(fi, ptr, n, info.elem_size, info.elem, verb)
 			return
 			return
 		}
 		}

+ 6 - 56
core/math/cmplx/cmplx.odin

@@ -229,7 +229,7 @@ sqrt_complex128 :: proc "contextless" (x: complex128) -> complex128 {
 }
 }
 
 
 ln_complex32 :: proc "contextless" (x: complex32) -> complex32 {
 ln_complex32 :: proc "contextless" (x: complex32) -> complex32 {
-	return complex(math.ln(abs(x)), phase(x))
+	return complex32(ln_complex64(complex64(x)))
 }
 }
 ln_complex64 :: proc "contextless" (x: complex64) -> complex64 {
 ln_complex64 :: proc "contextless" (x: complex64) -> complex64 {
 	return complex(math.ln(abs(x)), phase(x))
 	return complex(math.ln(abs(x)), phase(x))
@@ -240,26 +240,7 @@ ln_complex128 :: proc "contextless" (x: complex128) -> complex128 {
 
 
 
 
 exp_complex32 :: proc "contextless" (x: complex32) -> complex32 {
 exp_complex32 :: proc "contextless" (x: complex32) -> complex32 {
-	switch re, im := real(x), imag(x); {
-	case math.is_inf(re, 0):
-		switch {
-		case re > 0 && im == 0:
-			return x
-		case math.is_inf(im, 0) || math.is_nan(im):
-			if re < 0 {
-				return complex(0, math.copy_sign(0, im))
-			} else {
-				return complex(math.inf_f64(1.0), math.nan_f64())
-			}
-		}
-	case math.is_nan(re):
-		if im == 0 {
-			return complex(math.nan_f16(), im)
-		}
-	}
-	r := math.exp(real(x))
-	s, c := math.sincos(imag(x))
-	return complex(r*c, r*s)
+	return complex32(exp_complex64(complex64(x)))
 }
 }
 exp_complex64 :: proc "contextless" (x: complex64) -> complex64 {
 exp_complex64 :: proc "contextless" (x: complex64) -> complex64 {
 	switch re, im := real(x), imag(x); {
 	switch re, im := real(x), imag(x); {
@@ -308,37 +289,7 @@ exp_complex128 :: proc "contextless" (x: complex128) -> complex128 {
 
 
 
 
 pow_complex32 :: proc "contextless" (x, y: complex32) -> complex32 {
 pow_complex32 :: proc "contextless" (x, y: complex32) -> complex32 {
-	if x == 0 { // Guaranteed also true for x == -0.
-		if is_nan(y) {
-			return nan_complex32()
-		}
-		r, i := real(y), imag(y)
-		switch {
-		case r == 0:
-			return 1
-		case r < 0:
-			if i == 0 {
-				return complex(math.inf_f16(1), 0)
-			}
-			return inf_complex32()
-		case r > 0:
-			return 0
-		}
-		unreachable()
-	}
-	modulus := abs(x)
-	if modulus == 0 {
-		return complex(0, 0)
-	}
-	r := math.pow(modulus, real(y))
-	arg := phase(x)
-	theta := real(y) * arg
-	if imag(y) != 0 {
-		r *= math.exp(-imag(y) * arg)
-		theta += imag(y) * math.ln(modulus)
-	}
-	s, c := math.sincos(theta)
-	return complex(r*c, r*s)
+	return complex32(pow_complex64(complex64(x), complex64(y)))
 }
 }
 pow_complex64 :: proc "contextless" (x, y: complex64) -> complex64 {
 pow_complex64 :: proc "contextless" (x, y: complex64) -> complex64 {
 	if x == 0 { // Guaranteed also true for x == -0.
 	if x == 0 { // Guaranteed also true for x == -0.
@@ -410,7 +361,7 @@ pow_complex128 :: proc "contextless" (x, y: complex128) -> complex128 {
 
 
 
 
 log10_complex32 :: proc "contextless" (x: complex32) -> complex32 {
 log10_complex32 :: proc "contextless" (x: complex32) -> complex32 {
-	return math.LN10*ln(x)
+	return complex32(log10_complex64(complex64(x)))
 }
 }
 log10_complex64 :: proc "contextless" (x: complex64) -> complex64 {
 log10_complex64 :: proc "contextless" (x: complex64) -> complex64 {
 	return math.LN10*ln(x)
 	return math.LN10*ln(x)
@@ -421,7 +372,7 @@ log10_complex128 :: proc "contextless" (x: complex128) -> complex128 {
 
 
 
 
 phase_complex32 :: proc "contextless" (x:  complex32) -> f16 {
 phase_complex32 :: proc "contextless" (x:  complex32) -> f16 {
-	return math.atan2(imag(x), real(x))
+	return f16(phase_complex64(complex64(x)))
 }
 }
 phase_complex64 :: proc "contextless" (x:  complex64) -> f32 {
 phase_complex64 :: proc "contextless" (x:  complex64) -> f32 {
 	return math.atan2(imag(x), real(x))
 	return math.atan2(imag(x), real(x))
@@ -432,8 +383,7 @@ phase_complex128 :: proc "contextless" (x:  complex128) -> f64 {
 
 
 
 
 rect_complex32 :: proc "contextless" (r, θ: f16) -> complex32 {
 rect_complex32 :: proc "contextless" (r, θ: f16) -> complex32 {
-	s, c := math.sincos(θ)
-	return complex(r*c, r*s)
+	return complex32(rect_complex64(f32(r), f32(θ)))
 }
 }
 rect_complex64 :: proc "contextless" (r, θ: f32) -> complex64 {
 rect_complex64 :: proc "contextless" (r, θ: f32) -> complex64 {
 	s, c := math.sincos(θ)
 	s, c := math.sincos(θ)

+ 3 - 13
core/math/cmplx/cmplx_invtrig.odin

@@ -61,8 +61,7 @@ atanh :: proc{
 
 
 
 
 acos_complex32 :: proc "contextless" (x: complex32) -> complex32 {
 acos_complex32 :: proc "contextless" (x: complex32) -> complex32 {
-	w := asin(x)
-	return complex(math.PI/2 - real(w), -imag(w))
+	return complex32(acos_complex64(complex64(x)))
 }
 }
 acos_complex64 :: proc "contextless" (x: complex64) -> complex64 {
 acos_complex64 :: proc "contextless" (x: complex64) -> complex64 {
 	w := asin(x)
 	w := asin(x)
@@ -75,14 +74,7 @@ acos_complex128 :: proc "contextless" (x: complex128) -> complex128 {
 
 
 
 
 acosh_complex32 :: proc "contextless" (x: complex32) -> complex32 {
 acosh_complex32 :: proc "contextless" (x: complex32) -> complex32 {
-	if x == 0 {
-		return complex(0, math.copy_sign(math.PI/2, imag(x)))
-	}
-	w := acos(x)
-	if imag(w) <= 0 {
-		return complex(-imag(w), real(w))
-	}
-	return complex(imag(w), -real(w))
+	return complex32(acosh_complex64(complex64(x)))
 }
 }
 acosh_complex64 :: proc "contextless" (x: complex64) -> complex64 {
 acosh_complex64 :: proc "contextless" (x: complex64) -> complex64 {
 	if x == 0 {
 	if x == 0 {
@@ -257,9 +249,7 @@ atan_complex128 :: proc "contextless" (x: complex128) -> complex128 {
 }
 }
 
 
 atanh_complex32 :: proc "contextless" (x: complex32) -> complex32 {
 atanh_complex32 :: proc "contextless" (x: complex32) -> complex32 {
-	z := complex(-imag(x), real(x)) // z = i * x
-	z = atan(z)
-	return complex(imag(z), -real(z)) // z = -i * z
+	return complex32(atanh_complex64(complex64(x)))
 }
 }
 atanh_complex64 :: proc "contextless" (x: complex64) -> complex64 {
 atanh_complex64 :: proc "contextless" (x: complex64) -> complex64 {
 	z := complex(-imag(x), real(x)) // z = i * x
 	z := complex(-imag(x), real(x)) // z = i * x

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

@@ -618,10 +618,16 @@ shuffle :: proc(array: $T/[]$E, gen := context.random_generator) {
 		return
 		return
 	}
 	}
 
 
-	for i := i64(n - 1); i > 0; i -= 1 {
+	i := n - 1
+	for ; i > (1<<31 - 2); i -= 1 {
 		j := int63_max(i + 1, gen)
 		j := int63_max(i + 1, gen)
 		array[i], array[j] = array[j], array[i]
 		array[i], array[j] = array[j], array[i]
 	}
 	}
+
+	for ; i > 0; i -= 1 {
+		j := int31_max(i32(i + 1), gen)
+		array[i], array[j] = array[j], array[i]
+	}
 }
 }
 
 
 /*
 /*

+ 3 - 3
core/net/socket_linux.odin

@@ -117,7 +117,7 @@ _wrap_os_addr :: proc "contextless" (addr: linux.Sock_Addr_Any)->(Endpoint) {
 _create_socket :: proc(family: Address_Family, protocol: Socket_Protocol) -> (Any_Socket, Network_Error) {
 _create_socket :: proc(family: Address_Family, protocol: Socket_Protocol) -> (Any_Socket, Network_Error) {
 	family := _unwrap_os_family(family)
 	family := _unwrap_os_family(family)
 	proto, socktype := _unwrap_os_proto_socktype(protocol)
 	proto, socktype := _unwrap_os_proto_socktype(protocol)
-	sock, errno := linux.socket(family, socktype, {}, proto)
+	sock, errno := linux.socket(family, socktype, {.CLOEXEC}, proto)
 	if errno != .NONE {
 	if errno != .NONE {
 		return {}, Create_Socket_Error(errno)
 		return {}, Create_Socket_Error(errno)
 	}
 	}
@@ -132,7 +132,7 @@ _dial_tcp_from_endpoint :: proc(endpoint: Endpoint, options := default_tcp_optio
 	}
 	}
 	// Create new TCP socket
 	// Create new TCP socket
 	os_sock: linux.Fd
 	os_sock: linux.Fd
-	os_sock, errno = linux.socket(_unwrap_os_family(family_from_endpoint(endpoint)), .STREAM, {}, .TCP)
+	os_sock, errno = linux.socket(_unwrap_os_family(family_from_endpoint(endpoint)), .STREAM, {.CLOEXEC}, .TCP)
 	if errno != .NONE {
 	if errno != .NONE {
 		// TODO(flysand): should return invalid file descriptor here casted as TCP_Socket
 		// TODO(flysand): should return invalid file descriptor here casted as TCP_Socket
 		return {}, Create_Socket_Error(errno)
 		return {}, Create_Socket_Error(errno)
@@ -172,7 +172,7 @@ _listen_tcp :: proc(endpoint: Endpoint, backlog := 1000) -> (TCP_Socket, Network
 	ep_address := _unwrap_os_addr(endpoint)
 	ep_address := _unwrap_os_addr(endpoint)
 	// Create TCP socket
 	// Create TCP socket
 	os_sock: linux.Fd
 	os_sock: linux.Fd
-	os_sock, errno = linux.socket(ep_family, .STREAM, {}, .TCP)
+	os_sock, errno = linux.socket(ep_family, .STREAM, {.CLOEXEC}, .TCP)
 	if errno != .NONE {
 	if errno != .NONE {
 		// TODO(flysand): should return invalid file descriptor here casted as TCP_Socket
 		// TODO(flysand): should return invalid file descriptor here casted as TCP_Socket
 		return {}, Create_Socket_Error(errno)
 		return {}, Create_Socket_Error(errno)

+ 3 - 0
core/odin/ast/ast.odin

@@ -599,6 +599,7 @@ Field_Flag :: enum {
 	Subtype,
 	Subtype,
 	By_Ptr,
 	By_Ptr,
 	No_Broadcast,
 	No_Broadcast,
+	No_Capture,
 
 
 	Results,
 	Results,
 	Tags,
 	Tags,
@@ -619,6 +620,7 @@ field_flag_strings := [Field_Flag]string{
 	.Subtype            = "#subtype",
 	.Subtype            = "#subtype",
 	.By_Ptr             = "#by_ptr",
 	.By_Ptr             = "#by_ptr",
 	.No_Broadcast       = "#no_broadcast",
 	.No_Broadcast       = "#no_broadcast",
+	.No_Capture         = "#no_capture",
 
 
 	.Results            = "results",
 	.Results            = "results",
 	.Tags               = "field tag",
 	.Tags               = "field tag",
@@ -634,6 +636,7 @@ field_hash_flag_strings := []struct{key: string, flag: Field_Flag}{
 	{"subtype",      .Subtype},
 	{"subtype",      .Subtype},
 	{"by_ptr",       .By_Ptr},
 	{"by_ptr",       .By_Ptr},
 	{"no_broadcast", .No_Broadcast},
 	{"no_broadcast", .No_Broadcast},
+	{"no_capture",   .No_Capture},
 }
 }
 
 
 
 

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

@@ -2179,22 +2179,25 @@ parse_inlining_operand :: proc(p: ^Parser, lhs: bool, tok: tokenizer.Token) -> ^
 		}
 		}
 	}
 	}
 
 
-	#partial switch e in ast.strip_or_return_expr(expr).derived_expr {
-	case ^ast.Proc_Lit:
-		if e.inlining != .None && e.inlining != pi {
-			error(p, expr.pos, "both 'inline' and 'no_inline' cannot be applied to a procedure literal")
-		}
-		e.inlining = pi
-	case ^ast.Call_Expr:
-		if e.inlining != .None && e.inlining != pi {
-			error(p, expr.pos, "both 'inline' and 'no_inline' cannot be applied to a procedure call")
+	if expr != nil {
+		#partial switch e in ast.strip_or_return_expr(expr).derived_expr {
+		case ^ast.Proc_Lit:
+			if e.inlining != .None && e.inlining != pi {
+				error(p, expr.pos, "both 'inline' and 'no_inline' cannot be applied to a procedure literal")
+			}
+			e.inlining = pi
+			return expr
+		case ^ast.Call_Expr:
+			if e.inlining != .None && e.inlining != pi {
+				error(p, expr.pos, "both 'inline' and 'no_inline' cannot be applied to a procedure call")
+			}
+			e.inlining = pi
+			return expr
 		}
 		}
-		e.inlining = pi
-	case:
-		error(p, tok.pos, "'%s' must be followed by a procedure literal or call", tok.text)
-		return ast.new(ast.Bad_Expr, tok.pos, expr)
 	}
 	}
-	return expr
+
+	error(p, tok.pos, "'%s' must be followed by a procedure literal or call", tok.text)
+	return ast.new(ast.Bad_Expr, tok.pos, expr)
 }
 }
 
 
 parse_operand :: proc(p: ^Parser, lhs: bool) -> ^ast.Expr {
 parse_operand :: proc(p: ^Parser, lhs: bool) -> ^ast.Expr {
@@ -2258,18 +2261,18 @@ parse_operand :: proc(p: ^Parser, lhs: bool) -> ^ast.Expr {
 			hp.type = type
 			hp.type = type
 			return hp
 			return hp
 
 
-		case "file", "line", "procedure", "caller_location":
+		case "file", "directory", "line", "procedure", "caller_location":
 			bd := ast.new(ast.Basic_Directive, tok.pos, end_pos(name))
 			bd := ast.new(ast.Basic_Directive, tok.pos, end_pos(name))
 			bd.tok  = tok
 			bd.tok  = tok
 			bd.name = name.text
 			bd.name = name.text
 			return bd
 			return bd
-		case "location", "load", "assert", "defined", "config":
+
+		case "location", "exists", "load", "load_directory", "load_hash", "hash", "assert", "panic", "defined", "config":
 			bd := ast.new(ast.Basic_Directive, tok.pos, end_pos(name))
 			bd := ast.new(ast.Basic_Directive, tok.pos, end_pos(name))
 			bd.tok  = tok
 			bd.tok  = tok
 			bd.name = name.text
 			bd.name = name.text
 			return parse_call_expr(p, bd)
 			return parse_call_expr(p, bd)
 
 
-
 		case "soa":
 		case "soa":
 			bd := ast.new(ast.Basic_Directive, tok.pos, end_pos(name))
 			bd := ast.new(ast.Basic_Directive, tok.pos, end_pos(name))
 			bd.tok  = tok
 			bd.tok  = tok

+ 80 - 0
core/os/os2/dir.odin

@@ -0,0 +1,80 @@
+package os2
+
+import "base:runtime"
+import "core:slice"
+
+@(require_results)
+read_directory :: proc(f: ^File, n: int, allocator: runtime.Allocator) -> (files: []File_Info, err: Error) {
+	if f == nil {
+		return nil, .Invalid_File
+	}
+
+	n := n
+	size := n
+	if n <= 0 {
+		n = -1
+		size = 100
+	}
+
+	TEMP_ALLOCATOR_GUARD()
+
+	it := read_directory_iterator_create(f) or_return
+	defer _read_directory_iterator_destroy(&it)
+
+	dfi := make([dynamic]File_Info, 0, size, temp_allocator())
+	defer if err != nil {
+		for fi in dfi {
+			file_info_delete(fi, allocator)
+		}
+	}
+
+	for fi, index in read_directory_iterator(&it) {
+		if n > 0 && index == n {
+			break
+		}
+		append(&dfi, file_info_clone(fi, allocator) or_return)
+	}
+
+	return slice.clone(dfi[:], allocator)
+}
+
+
+@(require_results)
+read_all_directory :: proc(f: ^File, allocator: runtime.Allocator) -> (fi: []File_Info, err: Error) {
+	return read_directory(f, -1, allocator)
+}
+
+@(require_results)
+read_directory_by_path :: proc(path: string, n: int, allocator: runtime.Allocator) -> (fi: []File_Info, err: Error) {
+	f := open(path) or_return
+	defer close(f)
+	return read_directory(f, n, allocator)
+}
+
+@(require_results)
+read_all_directory_by_path :: proc(path: string, allocator: runtime.Allocator) -> (fi: []File_Info, err: Error) {
+	return read_directory_by_path(path, -1, allocator)
+}
+
+
+Read_Directory_Iterator :: struct {
+	f:    ^File,
+	impl: Read_Directory_Iterator_Impl,
+}
+
+
+@(require_results)
+read_directory_iterator_create :: proc(f: ^File) -> (Read_Directory_Iterator, Error) {
+	return _read_directory_iterator_create(f)
+}
+
+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) {
+	return _read_directory_iterator(it)
+}

+ 20 - 0
core/os/os2/dir_linux.odin

@@ -0,0 +1,20 @@
+//+private
+package os2
+
+Read_Directory_Iterator_Impl :: struct {
+
+}
+
+
+@(require_results)
+_read_directory_iterator :: proc(it: ^Read_Directory_Iterator) -> (fi: File_Info, index: int, ok: bool) {
+	return
+}
+
+@(require_results)
+_read_directory_iterator_create :: proc(f: ^File) -> (Read_Directory_Iterator, Error) {
+	return {}, nil
+}
+
+_read_directory_iterator_destroy :: proc(it: ^Read_Directory_Iterator) {
+}

+ 141 - 0
core/os/os2/dir_windows.odin

@@ -0,0 +1,141 @@
+//+private
+package os2
+
+import "base:runtime"
+import "core:time"
+import win32 "core:sys/windows"
+
+@(private="file")
+find_data_to_file_info :: proc(base_path: string, d: ^win32.WIN32_FIND_DATAW, allocator: runtime.Allocator) -> (fi: File_Info, err: Error) {
+	// Ignore "." and ".."
+	if d.cFileName[0] == '.' && d.cFileName[1] == 0 {
+		return
+	}
+	if d.cFileName[0] == '.' && d.cFileName[1] == '.' && d.cFileName[2] == 0 {
+		return
+	}
+	path := concatenate({base_path, `\`, win32_utf16_to_utf8(d.cFileName[:], temp_allocator()) or_else ""}, allocator) or_return
+
+
+	fi.fullpath = path
+	fi.name = basename(path)
+	fi.size = i64(d.nFileSizeHigh)<<32 + i64(d.nFileSizeLow)
+
+	fi.type, fi.mode = _file_type_mode_from_file_attributes(d.dwFileAttributes, nil, d.dwReserved0)
+
+	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
+}
+
+Read_Directory_Iterator_Impl :: struct {
+	find_data:     win32.WIN32_FIND_DATAW,
+	find_handle:   win32.HANDLE,
+	path:          string,
+	prev_fi:       File_Info,
+	no_more_files: bool,
+	index:         int,
+}
+
+
+@(require_results)
+_read_directory_iterator :: proc(it: ^Read_Directory_Iterator) -> (fi: File_Info, index: int, ok: bool) {
+	if it.f == nil {
+		return
+	}
+
+	TEMP_ALLOCATOR_GUARD()
+
+	for !it.impl.no_more_files {
+		err: Error
+		file_info_delete(it.impl.prev_fi, file_allocator())
+		it.impl.prev_fi = {}
+
+		fi, err = find_data_to_file_info(it.impl.path, &it.impl.find_data, file_allocator())
+		if err != nil {
+			return
+		}
+		if fi.name != "" {
+			it.impl.prev_fi = fi
+			ok = true
+			index = it.impl.index
+			it.impl.index += 1
+		}
+
+		if !win32.FindNextFileW(it.impl.find_handle, &it.impl.find_data) {
+			e := _get_platform_error()
+			if pe, _ := is_platform_error(e); pe == i32(win32.ERROR_NO_MORE_FILES) {
+				it.impl.no_more_files = true
+			}
+			it.impl.no_more_files = true
+		}
+		if ok {
+			return
+		}
+	}
+	return
+}
+
+@(require_results)
+_read_directory_iterator_create :: proc(f: ^File) -> (it: Read_Directory_Iterator, err: Error) {
+	if f == nil {
+		return
+	}
+	it.f = f
+	impl := (^File_Impl)(f.impl)
+
+	if !is_directory(impl.name) {
+		err = .Invalid_Dir
+		return
+	}
+
+	wpath: []u16
+	{
+		i := 0
+		for impl.wname[i] != 0 {
+			i += 1
+		}
+		wpath = impl.wname[:i]
+	}
+
+	TEMP_ALLOCATOR_GUARD()
+
+	wpath_search := make([]u16, len(wpath)+3, temp_allocator())
+	copy(wpath_search, wpath)
+	wpath_search[len(wpath)+0] = '\\'
+	wpath_search[len(wpath)+1] = '*'
+	wpath_search[len(wpath)+2] = 0
+
+	it.impl.find_handle = win32.FindFirstFileW(raw_data(wpath_search), &it.impl.find_data)
+	if it.impl.find_handle == win32.INVALID_HANDLE_VALUE {
+		err = _get_platform_error()
+		return
+	}
+	defer if err != nil {
+		win32.FindClose(it.impl.find_handle)
+	}
+
+	it.impl.path = _cleanpath_from_buf(wpath, file_allocator()) or_return
+	return
+}
+
+_read_directory_iterator_destroy :: proc(it: ^Read_Directory_Iterator) {
+	if it.f == nil {
+		return
+	}
+	file_info_delete(it.impl.prev_fi, file_allocator())
+	win32.FindClose(it.impl.find_handle)
+}

+ 9 - 6
core/os/os2/env_windows.odin

@@ -8,7 +8,8 @@ _lookup_env :: proc(key: string, allocator: runtime.Allocator) -> (value: string
 	if key == "" {
 	if key == "" {
 		return
 		return
 	}
 	}
-	wkey := win32.utf8_to_wstring(key)
+	TEMP_ALLOCATOR_GUARD()
+	wkey, _ := win32_utf8_to_wstring(key, temp_allocator())
 
 
 	n := win32.GetEnvironmentVariableW(wkey, nil, 0)
 	n := win32.GetEnvironmentVariableW(wkey, nil, 0)
 	if n == 0 {
 	if n == 0 {
@@ -32,20 +33,22 @@ _lookup_env :: proc(key: string, allocator: runtime.Allocator) -> (value: string
 		return "", false
 		return "", false
 	}
 	}
 
 
-	value = win32.utf16_to_utf8(b[:n], allocator) or_else ""
+	value = win32_utf16_to_utf8(b[:n], allocator) or_else ""
 	found = true
 	found = true
 	return
 	return
 }
 }
 
 
 _set_env :: proc(key, value: string) -> bool {
 _set_env :: proc(key, value: string) -> bool {
-	k := win32.utf8_to_wstring(key)
-	v := win32.utf8_to_wstring(value)
+	TEMP_ALLOCATOR_GUARD()
+	k, _ := win32_utf8_to_wstring(key,   temp_allocator())
+	v, _ := win32_utf8_to_wstring(value, temp_allocator())
 
 
 	return bool(win32.SetEnvironmentVariableW(k, v))
 	return bool(win32.SetEnvironmentVariableW(k, v))
 }
 }
 
 
 _unset_env :: proc(key: string) -> bool {
 _unset_env :: proc(key: string) -> bool {
-	k := win32.utf8_to_wstring(key)
+	TEMP_ALLOCATOR_GUARD()
+	k, _ := win32_utf8_to_wstring(key, temp_allocator())
 	return bool(win32.SetEnvironmentVariableW(k, nil))
 	return bool(win32.SetEnvironmentVariableW(k, nil))
 }
 }
 
 
@@ -89,7 +92,7 @@ _environ :: proc(allocator: runtime.Allocator) -> []string {
 				break
 				break
 			}
 			}
 			w := ([^]u16)(p)[from:i]
 			w := ([^]u16)(p)[from:i]
-			append(&r, win32.utf16_to_utf8(w, allocator) or_else "")
+			append(&r, win32_utf16_to_utf8(w, allocator) or_else "")
 			from = i + 1
 			from = i + 1
 		}
 		}
 	}
 	}

+ 4 - 0
core/os/os2/errors.odin

@@ -22,6 +22,7 @@ General_Error :: enum u32 {
 	Invalid_File,
 	Invalid_File,
 	Invalid_Dir,
 	Invalid_Dir,
 	Invalid_Path,
 	Invalid_Path,
+	Invalid_Callback,
 
 
 	Pattern_Has_Separator,
 	Pattern_Has_Separator,
 
 
@@ -38,6 +39,8 @@ Error :: union #shared_nil {
 }
 }
 #assert(size_of(Error) == size_of(u64))
 #assert(size_of(Error) == size_of(u64))
 
 
+ERROR_NONE :: Error{}
+
 
 
 
 
 is_platform_error :: proc(ferr: Error) -> (err: i32, ok: bool) {
 is_platform_error :: proc(ferr: Error) -> (err: i32, ok: bool) {
@@ -64,6 +67,7 @@ error_string :: proc(ferr: Error) -> string {
 		case .Invalid_File:      return "invalid file"
 		case .Invalid_File:      return "invalid file"
 		case .Invalid_Dir:       return "invalid directory"
 		case .Invalid_Dir:       return "invalid directory"
 		case .Invalid_Path:      return "invalid path"
 		case .Invalid_Path:      return "invalid path"
+		case .Invalid_Callback:  return "invalid callback"
 		case .Unsupported:       return "unsupported"
 		case .Unsupported:       return "unsupported"
 		case .Pattern_Has_Separator: return "pattern has separator"
 		case .Pattern_Has_Separator: return "pattern has separator"
 		}
 		}

+ 10 - 3
core/os/os2/errors_windows.odin

@@ -1,6 +1,8 @@
 //+private
 //+private
 package os2
 package os2
 
 
+import "base:runtime"
+import "core:slice"
 import win32 "core:sys/windows"
 import win32 "core:sys/windows"
 
 
 _error_string :: proc(errno: i32) -> string {
 _error_string :: proc(errno: i32) -> string {
@@ -8,9 +10,14 @@ _error_string :: proc(errno: i32) -> string {
 	if e == 0 {
 	if e == 0 {
 		return ""
 		return ""
 	}
 	}
-	// TODO(bill): _error_string for windows
-	// FormatMessageW
-	return ""
+
+	err := runtime.Type_Info_Enum_Value(e)
+
+	ti := &runtime.type_info_base(type_info_of(win32.System_Error)).variant.(runtime.Type_Info_Enum)
+	if idx, ok := slice.binary_search(ti.values, err); ok {
+		return ti.names[idx]
+	}
+	return "<unknown platform error>"
 }
 }
 
 
 _get_platform_error :: proc() -> Error {
 _get_platform_error :: proc() -> Error {

+ 90 - 22
core/os/os2/file.odin

@@ -4,21 +4,58 @@ import "core:io"
 import "core:time"
 import "core:time"
 import "base:runtime"
 import "base:runtime"
 
 
+/*
+	Type representing a file handle.
+
+	This struct represents an OS-specific file-handle, which can be one of
+	the following:
+	- File
+	- Directory
+	- Pipe
+	- Named pipe
+	- Block Device
+	- Character device
+	- Symlink
+	- Socket
+
+	See `File_Type` enum for more information on file types.
+*/
 File :: struct {
 File :: struct {
-	impl:   _File,
+	impl:   rawptr,
 	stream: io.Stream,
 	stream: io.Stream,
-	user_fstat: Fstat_Callback,
+	fstat:  Fstat_Callback,
+}
+
+/*
+	Type representing the type of a file handle.
+
+	**Note(windows)**: Socket handles can not be distinguished from
+	files, as they are just a normal file handle that is being treated by
+	a special driver. Windows also makes no distinction between block and
+	character devices.
+*/
+File_Type :: enum {
+	// The type of a file could not be determined for the current platform.
+	Undetermined,
+	// Represents a regular file.
+	Regular,
+	// Represents a directory.
+	Directory,
+	// Represents a symbolic link.
+	Symlink,
+	// Represents a named pipe (FIFO).
+	Named_Pipe,
+	// Represents a socket.
+	// **Note(windows)**: Not returned on windows
+	Socket,
+	// Represents a block device.
+	// **Note(windows)**: On windows represents all devices.
+	Block_Device,
+	// Represents a character device.
+	// **Note(windows)**: Not returned on windows
+	Character_Device,
 }
 }
 
 
-File_Mode :: distinct u32
-File_Mode_Dir         :: File_Mode(1<<16)
-File_Mode_Named_Pipe  :: File_Mode(1<<17)
-File_Mode_Device      :: File_Mode(1<<18)
-File_Mode_Char_Device :: File_Mode(1<<19)
-File_Mode_Sym_Link    :: File_Mode(1<<20)
-
-File_Mode_Perm :: File_Mode(0o777) // Unix permision bits
-
 File_Flags :: distinct bit_set[File_Flag; uint]
 File_Flags :: distinct bit_set[File_Flag; uint]
 File_Flag :: enum {
 File_Flag :: enum {
 	Read,
 	Read,
@@ -29,7 +66,7 @@ File_Flag :: enum {
 	Sync,
 	Sync,
 	Trunc,
 	Trunc,
 	Sparse,
 	Sparse,
-	Close_On_Exec,
+	Inheritable,
 
 
 	Unbuffered_IO,
 	Unbuffered_IO,
 }
 }
@@ -43,7 +80,15 @@ O_EXCL    :: File_Flags{.Excl}
 O_SYNC    :: File_Flags{.Sync}
 O_SYNC    :: File_Flags{.Sync}
 O_TRUNC   :: File_Flags{.Trunc}
 O_TRUNC   :: File_Flags{.Trunc}
 O_SPARSE  :: File_Flags{.Sparse}
 O_SPARSE  :: File_Flags{.Sparse}
-O_CLOEXEC :: File_Flags{.Close_On_Exec}
+
+/*
+	If specified, the file handle is inherited upon the creation of a child
+	process. By default all handles are created non-inheritable.
+
+	**Note**: The standard file handles (stderr, stdout and stdin) are always
+	initialized as inheritable.
+*/
+O_INHERITABLE :: File_Flags{.Inheritable}
 
 
 stdin:  ^File = nil // OS-Specific
 stdin:  ^File = nil // OS-Specific
 stdout: ^File = nil // OS-Specific
 stdout: ^File = nil // OS-Specific
@@ -51,17 +96,17 @@ stderr: ^File = nil // OS-Specific
 
 
 @(require_results)
 @(require_results)
 create :: proc(name: string) -> (^File, Error) {
 create :: proc(name: string) -> (^File, Error) {
-	return open(name, {.Read, .Write, .Create}, File_Mode(0o777))
+	return open(name, {.Read, .Write, .Create}, 0o777)
 }
 }
 
 
 @(require_results)
 @(require_results)
-open :: proc(name: string, flags := File_Flags{.Read}, perm := File_Mode(0o777)) -> (^File, Error) {
+open :: proc(name: string, flags := File_Flags{.Read}, perm := 0o777) -> (^File, Error) {
 	return _open(name, flags, perm)
 	return _open(name, flags, perm)
 }
 }
 
 
 @(require_results)
 @(require_results)
 new_file :: proc(handle: uintptr, name: string) -> ^File {
 new_file :: proc(handle: uintptr, name: string) -> ^File {
-	return _new_file(handle, name)
+	return _new_file(handle, name) or_else panic("Out of memory")
 }
 }
 
 
 @(require_results)
 @(require_results)
@@ -161,44 +206,56 @@ read_link :: proc(name: string, allocator: runtime.Allocator) -> (string, Error)
 
 
 
 
 chdir :: change_directory
 chdir :: change_directory
+
 change_directory :: proc(name: string) -> Error {
 change_directory :: proc(name: string) -> Error {
 	return _chdir(name)
 	return _chdir(name)
 }
 }
 
 
 chmod :: change_mode
 chmod :: change_mode
-change_mode :: proc(name: string, mode: File_Mode) -> Error {
+
+change_mode :: proc(name: string, mode: int) -> Error {
 	return _chmod(name, mode)
 	return _chmod(name, mode)
 }
 }
+
 chown :: change_owner
 chown :: change_owner
+
 change_owner :: proc(name: string, uid, gid: int) -> Error {
 change_owner :: proc(name: string, uid, gid: int) -> Error {
 	return _chown(name, uid, gid)
 	return _chown(name, uid, gid)
 }
 }
 
 
 fchdir :: fchange_directory
 fchdir :: fchange_directory
+
 fchange_directory :: proc(f: ^File) -> Error {
 fchange_directory :: proc(f: ^File) -> Error {
 	return _fchdir(f)
 	return _fchdir(f)
 }
 }
+
 fchmod :: fchange_mode
 fchmod :: fchange_mode
-fchange_mode :: proc(f: ^File, mode: File_Mode) -> Error {
+
+fchange_mode :: proc(f: ^File, mode: int) -> Error {
 	return _fchmod(f, mode)
 	return _fchmod(f, mode)
 }
 }
 
 
 fchown :: fchange_owner
 fchown :: fchange_owner
+
 fchange_owner :: proc(f: ^File, uid, gid: int) -> Error {
 fchange_owner :: proc(f: ^File, uid, gid: int) -> Error {
 	return _fchown(f, uid, gid)
 	return _fchown(f, uid, gid)
 }
 }
 
 
 
 
 lchown :: change_owner_do_not_follow_links
 lchown :: change_owner_do_not_follow_links
+
 change_owner_do_not_follow_links :: proc(name: string, uid, gid: int) -> Error {
 change_owner_do_not_follow_links :: proc(name: string, uid, gid: int) -> Error {
 	return _lchown(name, uid, gid)
 	return _lchown(name, uid, gid)
 }
 }
 
 
 chtimes :: change_times
 chtimes :: change_times
+
 change_times :: proc(name: string, atime, mtime: time.Time) -> Error {
 change_times :: proc(name: string, atime, mtime: time.Time) -> Error {
 	return _chtimes(name, atime, mtime)
 	return _chtimes(name, atime, mtime)
 }
 }
+
 fchtimes :: fchange_times
 fchtimes :: fchange_times
+
 fchange_times :: proc(f: ^File, atime, mtime: time.Time) -> Error {
 fchange_times :: proc(f: ^File, atime, mtime: time.Time) -> Error {
 	return _fchtimes(f, atime, mtime)
 	return _fchtimes(f, atime, mtime)
 }
 }
@@ -210,13 +267,24 @@ exists :: proc(path: string) -> bool {
 
 
 @(require_results)
 @(require_results)
 is_file :: proc(path: string) -> bool {
 is_file :: proc(path: string) -> bool {
-	return _is_file(path)
+	TEMP_ALLOCATOR_GUARD()
+	fi, err := stat(path, temp_allocator())
+	if err != nil {
+		return false
+	}
+	return fi.type == .Regular
 }
 }
 
 
 is_dir :: is_directory
 is_dir :: is_directory
+
 @(require_results)
 @(require_results)
 is_directory :: proc(path: string) -> bool {
 is_directory :: proc(path: string) -> bool {
-	return _is_dir(path)
+	TEMP_ALLOCATOR_GUARD()
+	fi, err := stat(path, temp_allocator())
+	if err != nil {
+		return false
+	}
+	return fi.type == .Directory
 }
 }
 
 
 
 
@@ -226,11 +294,11 @@ copy_file :: proc(dst_path, src_path: string) -> Error {
 
 
 	info := fstat(src, file_allocator()) or_return
 	info := fstat(src, file_allocator()) or_return
 	defer file_info_delete(info, file_allocator())
 	defer file_info_delete(info, file_allocator())
-	if info.is_directory {
+	if info.type == .Directory {
 		return .Invalid_File
 		return .Invalid_File
 	}
 	}
 
 
-	dst := open(dst_path, {.Read, .Write, .Create, .Trunc}, info.mode & File_Mode_Perm) or_return
+	dst := open(dst_path, {.Read, .Write, .Create, .Trunc}, info.mode & 0o777) or_return
 	defer close(dst)
 	defer close(dst)
 
 
 	_, err := io.copy(to_writer(dst), to_reader(src))
 	_, err := io.copy(to_writer(dst), to_reader(src))

+ 82 - 103
core/os/os2/file_linux.odin

@@ -6,14 +6,15 @@ import "core:time"
 import "base:runtime"
 import "base:runtime"
 import "core:sys/linux"
 import "core:sys/linux"
 
 
-_File :: struct {
+File_Impl :: struct {
+	file: File,
 	name: string,
 	name: string,
 	fd: linux.Fd,
 	fd: linux.Fd,
 	allocator: runtime.Allocator,
 	allocator: runtime.Allocator,
 }
 }
 
 
-_stdin : File = {
-	impl = {
+_stdin := File{
+	impl = &File_Impl{
 		name = "/proc/self/fd/0",
 		name = "/proc/self/fd/0",
 		fd = 0,
 		fd = 0,
 		allocator = _file_allocator(),
 		allocator = _file_allocator(),
@@ -21,9 +22,10 @@ _stdin : File = {
 	stream = {
 	stream = {
 		procedure = _file_stream_proc,
 		procedure = _file_stream_proc,
 	},
 	},
+	fstat = _fstat,
 }
 }
-_stdout : File = {
-	impl = {
+_stdout := File{
+	impl = &File_Impl{
 		name = "/proc/self/fd/1",
 		name = "/proc/self/fd/1",
 		fd = 1,
 		fd = 1,
 		allocator = _file_allocator(),
 		allocator = _file_allocator(),
@@ -31,9 +33,10 @@ _stdout : File = {
 	stream = {
 	stream = {
 		procedure = _file_stream_proc,
 		procedure = _file_stream_proc,
 	},
 	},
+	fstat = _fstat,
 }
 }
-_stderr : File = {
-	impl = {
+_stderr := File{
+	impl = &File_Impl{
 		name = "/proc/self/fd/2",
 		name = "/proc/self/fd/2",
 		fd = 2,
 		fd = 2,
 		allocator = _file_allocator(),
 		allocator = _file_allocator(),
@@ -41,6 +44,7 @@ _stderr : File = {
 	stream = {
 	stream = {
 		procedure = _file_stream_proc,
 		procedure = _file_stream_proc,
 	},
 	},
+	fstat = _fstat,
 }
 }
 
 
 @init
 @init
@@ -59,70 +63,67 @@ _file_allocator :: proc() -> runtime.Allocator {
 	return heap_allocator()
 	return heap_allocator()
 }
 }
 
 
-_open :: proc(name: string, flags: File_Flags, perm: File_Mode) -> (f: ^File, err: Error) {
+_open :: proc(name: string, flags: File_Flags, perm: int) -> (f: ^File, err: Error) {
 	TEMP_ALLOCATOR_GUARD()
 	TEMP_ALLOCATOR_GUARD()
 	name_cstr := temp_cstring(name) or_return
 	name_cstr := temp_cstring(name) or_return
 
 
 	// Just default to using O_NOCTTY because needing to open a controlling
 	// Just default to using O_NOCTTY because needing to open a controlling
 	// terminal would be incredibly rare. This has no effect on files while
 	// terminal would be incredibly rare. This has no effect on files while
 	// allowing us to open serial devices.
 	// allowing us to open serial devices.
-	sys_flags: linux.Open_Flags = {.NOCTTY}
+	sys_flags: linux.Open_Flags = {.NOCTTY, .CLOEXEC}
 	switch flags & O_RDONLY|O_WRONLY|O_RDWR {
 	switch flags & O_RDONLY|O_WRONLY|O_RDWR {
 	case O_RDONLY:
 	case O_RDONLY:
 	case O_WRONLY: sys_flags += {.WRONLY}
 	case O_WRONLY: sys_flags += {.WRONLY}
 	case O_RDWR:   sys_flags += {.RDWR}
 	case O_RDWR:   sys_flags += {.RDWR}
 	}
 	}
-
 	if .Append in flags        { sys_flags += {.APPEND} }
 	if .Append in flags        { sys_flags += {.APPEND} }
 	if .Create in flags        { sys_flags += {.CREAT} }
 	if .Create in flags        { sys_flags += {.CREAT} }
 	if .Excl in flags          { sys_flags += {.EXCL} }
 	if .Excl in flags          { sys_flags += {.EXCL} }
 	if .Sync in flags          { sys_flags += {.DSYNC} }
 	if .Sync in flags          { sys_flags += {.DSYNC} }
 	if .Trunc in flags         { sys_flags += {.TRUNC} }
 	if .Trunc in flags         { sys_flags += {.TRUNC} }
-	if .Close_On_Exec in flags { sys_flags += {.CLOEXEC} }
+	if .Inheritable in flags   { sys_flags -= {.CLOEXEC} }
 
 
-	fd, errno := linux.open(name_cstr, sys_flags, transmute(linux.Mode)(u32(perm)))
+	fd, errno := linux.open(name_cstr, sys_flags, transmute(linux.Mode)u32(perm))
 	if errno != .NONE {
 	if errno != .NONE {
 		return nil, _get_platform_error(errno)
 		return nil, _get_platform_error(errno)
 	}
 	}
 
 
-	return _new_file(uintptr(fd), name), nil
-}
-
-_new_file :: proc(fd: uintptr, _: string = "") -> ^File {
-	file := new(File, file_allocator())
-	_construct_file(file, fd, "")
-	return file
+	return _new_file(uintptr(fd), name)
 }
 }
 
 
-_construct_file :: proc(file: ^File, fd: uintptr, _: string = "") {
-	file^ = {
-		impl = {
-			fd = linux.Fd(fd),
-			allocator = file_allocator(),
-			name = _get_full_path(file.impl.fd, file.impl.allocator),
-		},
-		stream = {
-			data = file,
-			procedure = _file_stream_proc,
-		},
+_new_file :: proc(fd: uintptr, _: string = "") -> (f: ^File, err: Error) {
+	impl := new(File_Impl, file_allocator()) or_return
+	defer if err != nil {
+		free(impl, file_allocator())
+	}
+	impl.file.impl = impl
+	impl.fd = linux.Fd(fd)
+	impl.allocator = file_allocator()
+	impl.name = _get_full_path(impl.fd, file_allocator()) or_return
+	impl.file.stream = {
+		data = impl,
+		procedure = _file_stream_proc,
 	}
 	}
+	impl.file.fstat = _fstat
+	return &impl.file, nil
 }
 }
 
 
-_destroy :: proc(f: ^File) -> Error {
+_destroy :: proc(f: ^File_Impl) -> Error {
 	if f == nil {
 	if f == nil {
 		return nil
 		return nil
 	}
 	}
-	delete(f.impl.name, f.impl.allocator)
-	free(f, f.impl.allocator)
+	a := f.allocator
+	delete(f.name, a)
+	free(f, a)
 	return nil
 	return nil
 }
 }
 
 
 
 
-_close :: proc(f: ^File) -> Error {
-	if f == nil {
+_close :: proc(f: ^File_Impl) -> Error {
+	if f == nil{
 		return nil
 		return nil
 	}
 	}
-	errno := linux.close(f.impl.fd)
+	errno := linux.close(f.fd)
 	if errno == .EBADF { // avoid possible double free
 	if errno == .EBADF { // avoid possible double free
 		return _get_platform_error(errno)
 		return _get_platform_error(errno)
 	}
 	}
@@ -131,41 +132,41 @@ _close :: proc(f: ^File) -> Error {
 }
 }
 
 
 _fd :: proc(f: ^File) -> uintptr {
 _fd :: proc(f: ^File) -> uintptr {
-	if f == nil {
+	if f == nil || f.impl == nil {
 		return ~uintptr(0)
 		return ~uintptr(0)
 	}
 	}
-	return uintptr(f.impl.fd)
+	impl := (^File_Impl)(f.impl)
+	return uintptr(impl.fd)
 }
 }
 
 
 _name :: proc(f: ^File) -> string {
 _name :: proc(f: ^File) -> string {
-	return f.impl.name if f != nil else ""
+	return (^File_Impl)(f.impl).name if f != nil && f.impl != nil else ""
 }
 }
 
 
-_seek :: proc(f: ^File, offset: i64, whence: io.Seek_From) -> (ret: i64, err: Error) {
-	n, errno := linux.lseek(f.impl.fd, offset, linux.Seek_Whence(whence))
+_seek :: proc(f: ^File_Impl, offset: i64, whence: io.Seek_From) -> (ret: i64, err: Error) {
+	n, errno := linux.lseek(f.fd, offset, linux.Seek_Whence(whence))
 	if errno != .NONE {
 	if errno != .NONE {
 		return -1, _get_platform_error(errno)
 		return -1, _get_platform_error(errno)
 	}
 	}
 	return n, nil
 	return n, nil
 }
 }
 
 
-_read :: proc(f: ^File, p: []byte) -> (i64, Error) {
+_read :: proc(f: ^File_Impl, p: []byte) -> (i64, Error) {
 	if len(p) == 0 {
 	if len(p) == 0 {
 		return 0, nil
 		return 0, nil
 	}
 	}
-	n, errno := linux.read(f.impl.fd, p[:])
+	n, errno := linux.read(f.fd, p[:])
 	if errno != .NONE {
 	if errno != .NONE {
 		return -1, _get_platform_error(errno)
 		return -1, _get_platform_error(errno)
 	}
 	}
-	return i64(n), n == 0 ? io.Error.EOF : nil
+	return i64(n), io.Error.EOF if n == 0 else nil
 }
 }
 
 
-_read_at :: proc(f: ^File, p: []byte, offset: i64) -> (i64, Error) {
+_read_at :: proc(f: ^File_Impl, p: []byte, offset: i64) -> (i64, Error) {
 	if offset < 0 {
 	if offset < 0 {
 		return 0, .Invalid_Offset
 		return 0, .Invalid_Offset
 	}
 	}
-
-	n, errno := linux.pread(f.impl.fd, p[:], offset)
+	n, errno := linux.pread(f.fd, p[:], offset)
 	if errno != .NONE {
 	if errno != .NONE {
 		return -1, _get_platform_error(errno)
 		return -1, _get_platform_error(errno)
 	}
 	}
@@ -175,32 +176,31 @@ _read_at :: proc(f: ^File, p: []byte, offset: i64) -> (i64, Error) {
 	return i64(n), nil
 	return i64(n), nil
 }
 }
 
 
-_write :: proc(f: ^File, p: []byte) -> (i64, Error) {
+_write :: proc(f: ^File_Impl, p: []byte) -> (i64, Error) {
 	if len(p) == 0 {
 	if len(p) == 0 {
 		return 0, nil
 		return 0, nil
 	}
 	}
-	n, errno := linux.write(f.impl.fd, p[:])
+	n, errno := linux.write(f.fd, p[:])
 	if errno != .NONE {
 	if errno != .NONE {
 		return -1, _get_platform_error(errno)
 		return -1, _get_platform_error(errno)
 	}
 	}
 	return i64(n), nil
 	return i64(n), nil
 }
 }
 
 
-_write_at :: proc(f: ^File, p: []byte, offset: i64) -> (i64, Error) {
+_write_at :: proc(f: ^File_Impl, p: []byte, offset: i64) -> (i64, Error) {
 	if offset < 0 {
 	if offset < 0 {
 		return 0, .Invalid_Offset
 		return 0, .Invalid_Offset
 	}
 	}
-
-	n, errno := linux.pwrite(f.impl.fd, p[:], offset)
+	n, errno := linux.pwrite(f.fd, p[:], offset)
 	if errno != .NONE {
 	if errno != .NONE {
 		return -1, _get_platform_error(errno)
 		return -1, _get_platform_error(errno)
 	}
 	}
 	return i64(n), nil
 	return i64(n), nil
 }
 }
 
 
-_file_size :: proc(f: ^File) -> (n: i64, err: Error) {
+_file_size :: proc(f: ^File_Impl) -> (n: i64, err: Error) {
 	s: linux.Stat = ---
 	s: linux.Stat = ---
-	errno := linux.fstat(f.impl.fd, &s)
+	errno := linux.fstat(f.fd, &s)
 	if errno != .NONE {
 	if errno != .NONE {
 		return -1, _get_platform_error(errno)
 		return -1, _get_platform_error(errno)
 	}
 	}
@@ -208,27 +208,38 @@ _file_size :: proc(f: ^File) -> (n: i64, err: Error) {
 }
 }
 
 
 _sync :: proc(f: ^File) -> Error {
 _sync :: proc(f: ^File) -> Error {
-	return _get_platform_error(linux.fsync(f.impl.fd))
+	impl := (^File_Impl)(f.impl)
+	return _get_platform_error(linux.fsync(impl.fd))
 }
 }
 
 
-_flush :: proc(f: ^File) -> Error {
-	return _get_platform_error(linux.fsync(f.impl.fd))
+_flush :: proc(f: ^File_Impl) -> Error {
+	return _get_platform_error(linux.fsync(f.fd))
 }
 }
 
 
 _truncate :: proc(f: ^File, size: i64) -> Error {
 _truncate :: proc(f: ^File, size: i64) -> Error {
-	return _get_platform_error(linux.ftruncate(f.impl.fd, size))
+	impl := (^File_Impl)(f.impl)
+	return _get_platform_error(linux.ftruncate(impl.fd, size))
 }
 }
 
 
 _remove :: proc(name: string) -> Error {
 _remove :: proc(name: string) -> Error {
+	is_dir_fd :: proc(fd: linux.Fd) -> bool {
+		s: linux.Stat
+		if linux.fstat(fd, &s) != .NONE {
+			return false
+		}
+		return linux.S_ISDIR(s.mode)
+	}
+
 	TEMP_ALLOCATOR_GUARD()
 	TEMP_ALLOCATOR_GUARD()
 	name_cstr := temp_cstring(name) or_return
 	name_cstr := temp_cstring(name) or_return
 
 
 	fd, errno := linux.open(name_cstr, {.NOFOLLOW})
 	fd, errno := linux.open(name_cstr, {.NOFOLLOW})
 	#partial switch (errno) {
 	#partial switch (errno) {
-	case .ELOOP: /* symlink */
+	case .ELOOP:
+		/* symlink */
 	case .NONE:
 	case .NONE:
 		defer linux.close(fd)
 		defer linux.close(fd)
-		if _is_dir_fd(fd) {
+		if is_dir_fd(fd) {
 			return _get_platform_error(linux.rmdir(name_cstr))
 			return _get_platform_error(linux.rmdir(name_cstr))
 		}
 		}
 	case:
 	case:
@@ -292,17 +303,19 @@ _chdir :: proc(name: string) -> Error {
 }
 }
 
 
 _fchdir :: proc(f: ^File) -> Error {
 _fchdir :: proc(f: ^File) -> Error {
-	return _get_platform_error(linux.fchdir(f.impl.fd))
+	impl := (^File_Impl)(f.impl)
+	return _get_platform_error(linux.fchdir(impl.fd))
 }
 }
 
 
-_chmod :: proc(name: string, mode: File_Mode) -> Error {
+_chmod :: proc(name: string, mode: int) -> Error {
 	TEMP_ALLOCATOR_GUARD()
 	TEMP_ALLOCATOR_GUARD()
 	name_cstr := temp_cstring(name) or_return
 	name_cstr := temp_cstring(name) or_return
 	return _get_platform_error(linux.chmod(name_cstr, transmute(linux.Mode)(u32(mode))))
 	return _get_platform_error(linux.chmod(name_cstr, transmute(linux.Mode)(u32(mode))))
 }
 }
 
 
-_fchmod :: proc(f: ^File, mode: File_Mode) -> Error {
-	return _get_platform_error(linux.fchmod(f.impl.fd, transmute(linux.Mode)(u32(mode))))
+_fchmod :: proc(f: ^File, mode: int) -> Error {
+	impl := (^File_Impl)(f.impl)
+	return _get_platform_error(linux.fchmod(impl.fd, transmute(linux.Mode)(u32(mode))))
 }
 }
 
 
 // NOTE: will throw error without super user priviledges
 // NOTE: will throw error without super user priviledges
@@ -321,7 +334,8 @@ _lchown :: proc(name: string, uid, gid: int) -> Error {
 
 
 // NOTE: will throw error without super user priviledges
 // NOTE: will throw error without super user priviledges
 _fchown :: proc(f: ^File, uid, gid: int) -> Error {
 _fchown :: proc(f: ^File, uid, gid: int) -> Error {
-	return _get_platform_error(linux.fchown(f.impl.fd, linux.Uid(uid), linux.Gid(gid)))
+	impl := (^File_Impl)(f.impl)
+	return _get_platform_error(linux.fchown(impl.fd, linux.Uid(uid), linux.Gid(gid)))
 }
 }
 
 
 _chtimes :: proc(name: string, atime, mtime: time.Time) -> Error {
 _chtimes :: proc(name: string, atime, mtime: time.Time) -> Error {
@@ -351,7 +365,8 @@ _fchtimes :: proc(f: ^File, atime, mtime: time.Time) -> Error {
 			uint(mtime._nsec) % uint(time.Second),
 			uint(mtime._nsec) % uint(time.Second),
 		},
 		},
 	}
 	}
-	return _get_platform_error(linux.utimensat(f.impl.fd, nil, &times[0], nil))
+	impl := (^File_Impl)(f.impl)
+	return _get_platform_error(linux.utimensat(impl.fd, nil, &times[0], nil))
 }
 }
 
 
 _exists :: proc(name: string) -> bool {
 _exists :: proc(name: string) -> bool {
@@ -361,42 +376,6 @@ _exists :: proc(name: string) -> bool {
 	return !res && errno == .NONE
 	return !res && errno == .NONE
 }
 }
 
 
-_is_file :: proc(name: string) -> bool {
-	TEMP_ALLOCATOR_GUARD()
-	name_cstr, _ := temp_cstring(name)
-	s: linux.Stat
-	if linux.stat(name_cstr, &s) != .NONE {
-		return false
-	}
-	return linux.S_ISREG(s.mode)
-}
-
-_is_file_fd :: proc(fd: linux.Fd) -> bool {
-	s: linux.Stat
-	if linux.fstat(fd, &s) != .NONE {
-		return false
-	}
-	return linux.S_ISREG(s.mode)
-}
-
-_is_dir :: proc(name: string) -> bool {
-	TEMP_ALLOCATOR_GUARD()
-	name_cstr, _ := temp_cstring(name)
-	s: linux.Stat
-	if linux.stat(name_cstr, &s) != .NONE {
-		return false
-	}
-	return linux.S_ISDIR(s.mode)
-}
-
-_is_dir_fd :: proc(fd: linux.Fd) -> bool {
-	s: linux.Stat
-	if linux.fstat(fd, &s) != .NONE {
-		return false
-	}
-	return linux.S_ISDIR(s.mode)
-}
-
 /* Certain files in the Linux file system are not actual
 /* Certain files in the Linux file system are not actual
  * files (e.g. everything in /proc/). Therefore, the
  * files (e.g. everything in /proc/). Therefore, the
  * read_entire_file procs fail to actually read anything
  * read_entire_file procs fail to actually read anything
@@ -443,7 +422,7 @@ _read_entire_pseudo_file_cstring :: proc(name: cstring, allocator: runtime.Alloc
 
 
 @(private="package")
 @(private="package")
 _file_stream_proc :: proc(stream_data: rawptr, mode: io.Stream_Mode, p: []byte, offset: i64, whence: io.Seek_From) -> (n: i64, err: io.Error) {
 _file_stream_proc :: proc(stream_data: rawptr, mode: io.Stream_Mode, p: []byte, offset: i64, whence: io.Seek_From) -> (n: i64, err: io.Error) {
-	f := (^File)(stream_data)
+	f := (^File_Impl)(stream_data)
 	ferr: Error
 	ferr: Error
 	switch mode {
 	switch mode {
 	case .Read:
 	case .Read:

+ 13 - 1
core/os/os2/file_util.odin

@@ -8,6 +8,18 @@ write_string :: proc(f: ^File, s: string) -> (n: int, err: Error) {
 	return write(f, transmute([]byte)s)
 	return write(f, transmute([]byte)s)
 }
 }
 
 
+write_strings :: proc(f: ^File, strings: ..string) -> (n: int, err: Error) {
+	for s in strings {
+		m: int
+		m, err = write_string(f, s)
+		n += m
+		if err != nil {
+			return
+		}
+	}
+	return
+}
+
 write_byte :: proc(f: ^File, b: byte) -> (n: int, err: Error) {
 write_byte :: proc(f: ^File, b: byte) -> (n: int, err: Error) {
 	return write(f, []byte{b})
 	return write(f, []byte{b})
 }
 }
@@ -138,7 +150,7 @@ read_entire_file_from_file :: proc(f: ^File, allocator: runtime.Allocator) -> (d
 }
 }
 
 
 @(require_results)
 @(require_results)
-write_entire_file :: proc(name: string, data: []byte, perm: File_Mode, truncate := true) -> Error {
+write_entire_file :: proc(name: string, data: []byte, perm: int, truncate := true) -> Error {
 	flags := O_WRONLY|O_CREATE
 	flags := O_WRONLY|O_CREATE
 	if truncate {
 	if truncate {
 		flags |= O_TRUNC
 		flags |= O_TRUNC

+ 199 - 116
core/os/os2/file_windows.odin

@@ -17,17 +17,19 @@ _ERROR_BAD_NETPATH :: 53
 MAX_RW :: 1<<30
 MAX_RW :: 1<<30
 
 
 
 
-_File_Kind :: enum u8 {
+File_Impl_Kind :: enum u8 {
 	File,
 	File,
 	Console,
 	Console,
 	Pipe,
 	Pipe,
 }
 }
 
 
-_File :: struct {
+File_Impl :: struct {
+	file: File,
+
 	fd:   rawptr,
 	fd:   rawptr,
 	name: string,
 	name: string,
 	wname: win32.wstring,
 	wname: win32.wstring,
-	kind: _File_Kind,
+	kind: File_Impl_Kind,
 
 
 	allocator: runtime.Allocator,
 	allocator: runtime.Allocator,
 
 
@@ -53,13 +55,14 @@ _handle :: proc(f: ^File) -> win32.HANDLE {
 	return win32.HANDLE(_fd(f))
 	return win32.HANDLE(_fd(f))
 }
 }
 
 
-_open_internal :: proc(name: string, flags: File_Flags, perm: File_Mode) -> (handle: uintptr, err: Error) {
+_open_internal :: proc(name: string, flags: File_Flags, perm: int) -> (handle: uintptr, err: Error) {
 	if len(name) == 0 {
 	if len(name) == 0 {
 		err = .Not_Exist
 		err = .Not_Exist
 		return
 		return
 	}
 	}
+	TEMP_ALLOCATOR_GUARD()
 
 
-	path := _fix_long_path(name)
+	path := _fix_long_path(name, temp_allocator()) or_return
 	access: u32
 	access: u32
 	switch flags & {.Read, .Write} {
 	switch flags & {.Read, .Write} {
 	case {.Read}:         access = win32.FILE_GENERIC_READ
 	case {.Read}:         access = win32.FILE_GENERIC_READ
@@ -75,11 +78,9 @@ _open_internal :: proc(name: string, flags: File_Flags, perm: File_Mode) -> (han
 		access |= win32.FILE_APPEND_DATA
 		access |= win32.FILE_APPEND_DATA
 	}
 	}
 	share_mode := u32(win32.FILE_SHARE_READ | win32.FILE_SHARE_WRITE)
 	share_mode := u32(win32.FILE_SHARE_READ | win32.FILE_SHARE_WRITE)
-	sa: ^win32.SECURITY_ATTRIBUTES
-	if .Close_On_Exec not_in flags {
-		sa = &win32.SECURITY_ATTRIBUTES{}
-		sa.nLength = size_of(win32.SECURITY_ATTRIBUTES)
-		sa.bInheritHandle = true
+	sa := win32.SECURITY_ATTRIBUTES {
+		nLength = size_of(win32.SECURITY_ATTRIBUTES),
+		bInheritHandle = .Inheritable in flags,
 	}
 	}
 
 
 	create_mode: u32 = win32.OPEN_EXISTING
 	create_mode: u32 = win32.OPEN_EXISTING
@@ -94,14 +95,14 @@ _open_internal :: proc(name: string, flags: File_Flags, perm: File_Mode) -> (han
 		create_mode = win32.TRUNCATE_EXISTING
 		create_mode = win32.TRUNCATE_EXISTING
 	}
 	}
 
 
-	attrs: u32 = win32.FILE_ATTRIBUTE_NORMAL
+	attrs: u32 = win32.FILE_ATTRIBUTE_NORMAL|win32.FILE_FLAG_BACKUP_SEMANTICS
 	if perm & S_IWRITE == 0 {
 	if perm & S_IWRITE == 0 {
 		attrs = win32.FILE_ATTRIBUTE_READONLY
 		attrs = win32.FILE_ATTRIBUTE_READONLY
 		if create_mode == win32.CREATE_ALWAYS {
 		if create_mode == win32.CREATE_ALWAYS {
 			// NOTE(bill): Open has just asked to create a file in read-only mode.
 			// NOTE(bill): Open has just asked to create a file in read-only mode.
 			// If the file already exists, to make it akin to a *nix open call,
 			// If the file already exists, to make it akin to a *nix open call,
 			// the call preserves the existing permissions.
 			// the call preserves the existing permissions.
-			h := win32.CreateFileW(path, access, share_mode, sa, win32.TRUNCATE_EXISTING, win32.FILE_ATTRIBUTE_NORMAL, nil)
+			h := win32.CreateFileW(path, access, share_mode, &sa, win32.TRUNCATE_EXISTING, win32.FILE_ATTRIBUTE_NORMAL, nil)
 			if h == win32.INVALID_HANDLE {
 			if h == win32.INVALID_HANDLE {
 				switch e := win32.GetLastError(); e {
 				switch e := win32.GetLastError(); e {
 				case win32.ERROR_FILE_NOT_FOUND, _ERROR_BAD_NETPATH, win32.ERROR_PATH_NOT_FOUND:
 				case win32.ERROR_FILE_NOT_FOUND, _ERROR_BAD_NETPATH, win32.ERROR_PATH_NOT_FOUND:
@@ -109,12 +110,13 @@ _open_internal :: proc(name: string, flags: File_Flags, perm: File_Mode) -> (han
 				case 0:
 				case 0:
 					return uintptr(h), nil
 					return uintptr(h), nil
 				case:
 				case:
-					return 0, Platform_Error(e)
+					return 0, _get_platform_error()
 				}
 				}
 			}
 			}
 		}
 		}
 	}
 	}
-	h := win32.CreateFileW(path, access, share_mode, sa, create_mode, attrs, nil)
+
+	h := win32.CreateFileW(path, access, share_mode, &sa, create_mode, attrs, nil)
 	if h == win32.INVALID_HANDLE {
 	if h == win32.INVALID_HANDLE {
 		return 0, _get_platform_error()
 		return 0, _get_platform_error()
 	}
 	}
@@ -122,85 +124,95 @@ _open_internal :: proc(name: string, flags: File_Flags, perm: File_Mode) -> (han
 }
 }
 
 
 
 
-_open :: proc(name: string, flags: File_Flags, perm: File_Mode) -> (f: ^File, err: Error) {
+_open :: proc(name: string, flags: File_Flags, perm: int) -> (f: ^File, err: Error) {
 	flags := flags if flags != nil else {.Read}
 	flags := flags if flags != nil else {.Read}
-	handle := _open_internal(name, flags + {.Close_On_Exec}, perm) or_return
-	return _new_file(handle, name), nil
+	handle := _open_internal(name, flags, perm) or_return
+	return _new_file(handle, name)
 }
 }
 
 
-_new_file :: proc(handle: uintptr, name: string) -> ^File {
+_new_file :: proc(handle: uintptr, name: string) -> (f: ^File, err: Error) {
 	if handle == INVALID_HANDLE {
 	if handle == INVALID_HANDLE {
-		return nil
+		return
+	}
+	impl := new(File_Impl, file_allocator()) or_return
+	defer if err != nil {
+		free(impl, file_allocator())
 	}
 	}
-	f := new(File, file_allocator())
 
 
-	f.impl.allocator = file_allocator()
-	f.impl.fd = rawptr(handle)
-	f.impl.name, _ = clone_string(name, f.impl.allocator)
-	f.impl.wname = win32.utf8_to_wstring(name, f.impl.allocator)
+	impl.file.impl = impl
 
 
-	handle := _handle(f)
-	kind := _File_Kind.File
+	impl.allocator = file_allocator()
+	impl.fd = rawptr(handle)
+	impl.name = clone_string(name, impl.allocator) or_return
+	impl.wname = win32_utf8_to_wstring(name, impl.allocator) or_return
+
+	handle := _handle(&impl.file)
+	kind := File_Impl_Kind.File
 	if m: u32; win32.GetConsoleMode(handle, &m) {
 	if m: u32; win32.GetConsoleMode(handle, &m) {
 		kind = .Console
 		kind = .Console
 	}
 	}
 	if win32.GetFileType(handle) == win32.FILE_TYPE_PIPE {
 	if win32.GetFileType(handle) == win32.FILE_TYPE_PIPE {
 		kind = .Pipe
 		kind = .Pipe
 	}
 	}
-	f.impl.kind = kind
+	impl.kind = kind
 
 
-	f.stream = {
-		data = f,
+	impl.file.stream = {
+		data = impl,
 		procedure = _file_stream_proc,
 		procedure = _file_stream_proc,
 	}
 	}
+	impl.file.fstat = _fstat
 
 
-	return f
+	return &impl.file, nil
 }
 }
 
 
 _fd :: proc(f: ^File) -> uintptr {
 _fd :: proc(f: ^File) -> uintptr {
-	if f == nil {
+	if f == nil || f.impl == nil {
 		return INVALID_HANDLE
 		return INVALID_HANDLE
 	}
 	}
-	return uintptr(f.impl.fd)
+	return uintptr((^File_Impl)(f.impl).fd)
 }
 }
 
 
-_destroy :: proc(f: ^File) -> Error {
+_destroy :: proc(f: ^File_Impl) -> Error {
 	if f == nil {
 	if f == nil {
 		return nil
 		return nil
 	}
 	}
 
 
-	a := f.impl.allocator
-	free(f.impl.wname, a)
-	delete(f.impl.name, a)
-	free(f, a)
+	a := f.allocator
+	err0 := free(f.wname, a)
+	err1 := delete(f.name, a)
+	err2 := free(f, a)
+	err0 or_return
+	err1 or_return
+	err2 or_return
 	return nil
 	return nil
 }
 }
 
 
 
 
-_close :: proc(f: ^File) -> Error {
-	if f == nil {
+_close :: proc(f: ^File_Impl) -> Error {
+	if f == nil  {
 		return nil
 		return nil
 	}
 	}
-	if !win32.CloseHandle(win32.HANDLE(f.impl.fd)) {
+	if !win32.CloseHandle(win32.HANDLE(f.fd)) {
 		return .Closed
 		return .Closed
 	}
 	}
 	return _destroy(f)
 	return _destroy(f)
 }
 }
 
 
 _name :: proc(f: ^File) -> string {
 _name :: proc(f: ^File) -> string {
-	return f.impl.name if f != nil else ""
+	return (^File_Impl)(f.impl).name if f != nil && f.impl != nil else ""
 }
 }
 
 
-_seek :: proc(f: ^File, offset: i64, whence: io.Seek_From) -> (ret: i64, err: Error) {
-	handle := _handle(f)
+_seek :: proc(f: ^File_Impl, offset: i64, whence: io.Seek_From) -> (ret: i64, err: Error) {
+	handle := _handle(&f.file)
 	if handle == win32.INVALID_HANDLE {
 	if handle == win32.INVALID_HANDLE {
 		return 0, .Invalid_File
 		return 0, .Invalid_File
 	}
 	}
-	if f.impl.kind == .Pipe {
+
+	if f.kind == .Pipe {
 		return 0, .Invalid_File
 		return 0, .Invalid_File
 	}
 	}
 
 
-	sync.guard(&f.impl.rw_mutex)
+	sync.guard(&f.rw_mutex)
 
 
 	w: u32
 	w: u32
 	switch whence {
 	switch whence {
@@ -218,13 +230,13 @@ _seek :: proc(f: ^File, offset: i64, whence: io.Seek_From) -> (ret: i64, err: Er
 	return i64(hi)<<32 + i64(dw_ptr), nil
 	return i64(hi)<<32 + i64(dw_ptr), nil
 }
 }
 
 
-_read :: proc(f: ^File, p: []byte) -> (n: i64, err: Error) {
+_read :: proc(f: ^File_Impl, p: []byte) -> (n: i64, err: Error) {
 	read_console :: proc(handle: win32.HANDLE, b: []byte) -> (n: int, err: Error) {
 	read_console :: proc(handle: win32.HANDLE, b: []byte) -> (n: int, err: Error) {
 		if len(b) == 0 {
 		if len(b) == 0 {
 			return 0, nil
 			return 0, nil
 		}
 		}
 
 
-		// TODO(bill): should this be moved to `_File` instead?
+		// TODO(bill): should this be moved to `File_Impl` instead?
 		BUF_SIZE :: 386
 		BUF_SIZE :: 386
 		buf16: [BUF_SIZE]u16
 		buf16: [BUF_SIZE]u16
 		buf8: [4*BUF_SIZE]u8
 		buf8: [4*BUF_SIZE]u8
@@ -269,18 +281,18 @@ _read :: proc(f: ^File, p: []byte) -> (n: i64, err: Error) {
 		return
 		return
 	}
 	}
 
 
-	handle := _handle(f)
+	handle := _handle(&f.file)
 
 
 	single_read_length: win32.DWORD
 	single_read_length: win32.DWORD
 	total_read: int
 	total_read: int
 	length := len(p)
 	length := len(p)
 
 
-	sync.shared_guard(&f.impl.rw_mutex) // multiple readers
+	sync.shared_guard(&f.rw_mutex) // multiple readers
 
 
-	if sync.guard(&f.impl.p_mutex) {
+	if sync.guard(&f.p_mutex) {
 		to_read := min(win32.DWORD(length), MAX_RW)
 		to_read := min(win32.DWORD(length), MAX_RW)
 		ok: win32.BOOL
 		ok: win32.BOOL
-		if f.impl.kind == .Console {
+		if f.kind == .Console {
 			n, cerr := read_console(handle, p[total_read:][:to_read])
 			n, cerr := read_console(handle, p[total_read:][:to_read])
 			total_read += n
 			total_read += n
 			if cerr != nil {
 			if cerr != nil {
@@ -300,15 +312,15 @@ _read :: proc(f: ^File, p: []byte) -> (n: i64, err: Error) {
 	return i64(total_read), err
 	return i64(total_read), err
 }
 }
 
 
-_read_at :: proc(f: ^File, p: []byte, offset: i64) -> (n: i64, err: Error) {
-	pread :: proc(f: ^File, data: []byte, offset: i64) -> (n: i64, err: Error) {
+_read_at :: proc(f: ^File_Impl, p: []byte, offset: i64) -> (n: i64, err: Error) {
+	pread :: proc(f: ^File_Impl, data: []byte, offset: i64) -> (n: i64, err: Error) {
 		buf := data
 		buf := data
 		if len(buf) > MAX_RW {
 		if len(buf) > MAX_RW {
 			buf = buf[:MAX_RW]
 			buf = buf[:MAX_RW]
 
 
 		}
 		}
-		curr_offset := seek(f, offset, .Current) or_return
-		defer seek(f, curr_offset, .Start)
+		curr_offset := _seek(f, offset, .Current) or_return
+		defer _seek(f, curr_offset, .Start)
 
 
 		o := win32.OVERLAPPED{
 		o := win32.OVERLAPPED{
 			OffsetHigh = u32(offset>>32),
 			OffsetHigh = u32(offset>>32),
@@ -317,7 +329,7 @@ _read_at :: proc(f: ^File, p: []byte, offset: i64) -> (n: i64, err: Error) {
 
 
 		// TODO(bill): Determine the correct behaviour for consoles
 		// TODO(bill): Determine the correct behaviour for consoles
 
 
-		h := _handle(f)
+		h := _handle(&f.file)
 		done: win32.DWORD
 		done: win32.DWORD
 		if !win32.ReadFile(h, raw_data(buf), u32(len(buf)), &done, &o) {
 		if !win32.ReadFile(h, raw_data(buf), u32(len(buf)), &done, &o) {
 			err = _get_platform_error()
 			err = _get_platform_error()
@@ -327,7 +339,7 @@ _read_at :: proc(f: ^File, p: []byte, offset: i64) -> (n: i64, err: Error) {
 		return
 		return
 	}
 	}
 
 
-	sync.guard(&f.impl.p_mutex)
+	sync.guard(&f.p_mutex)
 
 
 	p, offset := p, offset
 	p, offset := p, offset
 	for len(p) > 0 {
 	for len(p) > 0 {
@@ -339,7 +351,7 @@ _read_at :: proc(f: ^File, p: []byte, offset: i64) -> (n: i64, err: Error) {
 	return
 	return
 }
 }
 
 
-_write :: proc(f: ^File, p: []byte) -> (n: i64, err: Error) {
+_write :: proc(f: ^File_Impl, p: []byte) -> (n: i64, err: Error) {
 	if len(p) == 0 {
 	if len(p) == 0 {
 		return
 		return
 	}
 	}
@@ -348,9 +360,9 @@ _write :: proc(f: ^File, p: []byte) -> (n: i64, err: Error) {
 	total_write: i64
 	total_write: i64
 	length := i64(len(p))
 	length := i64(len(p))
 
 
-	handle := _handle(f)
+	handle := _handle(&f.file)
 
 
-	sync.guard(&f.impl.rw_mutex)
+	sync.guard(&f.rw_mutex)
 	for total_write < length {
 	for total_write < length {
 		remaining := length - total_write
 		remaining := length - total_write
 		to_write := win32.DWORD(min(i32(remaining), MAX_RW))
 		to_write := win32.DWORD(min(i32(remaining), MAX_RW))
@@ -366,22 +378,22 @@ _write :: proc(f: ^File, p: []byte) -> (n: i64, err: Error) {
 	return i64(total_write), nil
 	return i64(total_write), nil
 }
 }
 
 
-_write_at :: proc(f: ^File, p: []byte, offset: i64) -> (n: i64, err: Error) {
-	pwrite :: proc(f: ^File, data: []byte, offset: i64) -> (n: i64, err: Error) {
+_write_at :: proc(f: ^File_Impl, p: []byte, offset: i64) -> (n: i64, err: Error) {
+	pwrite :: proc(f: ^File_Impl, data: []byte, offset: i64) -> (n: i64, err: Error) {
 		buf := data
 		buf := data
 		if len(buf) > MAX_RW {
 		if len(buf) > MAX_RW {
 			buf = buf[:MAX_RW]
 			buf = buf[:MAX_RW]
 
 
 		}
 		}
-		curr_offset := seek(f, offset, .Current) or_return
-		defer seek(f, curr_offset, .Start)
+		curr_offset := _seek(f, offset, .Current) or_return
+		defer _seek(f, curr_offset, .Start)
 
 
 		o := win32.OVERLAPPED{
 		o := win32.OVERLAPPED{
 			OffsetHigh = u32(offset>>32),
 			OffsetHigh = u32(offset>>32),
 			Offset = u32(offset),
 			Offset = u32(offset),
 		}
 		}
 
 
-		h := _handle(f)
+		h := _handle(&f.file)
 		done: win32.DWORD
 		done: win32.DWORD
 		if !win32.WriteFile(h, raw_data(buf), u32(len(buf)), &done, &o) {
 		if !win32.WriteFile(h, raw_data(buf), u32(len(buf)), &done, &o) {
 			err = _get_platform_error()
 			err = _get_platform_error()
@@ -391,7 +403,7 @@ _write_at :: proc(f: ^File, p: []byte, offset: i64) -> (n: i64, err: Error) {
 		return
 		return
 	}
 	}
 
 
-	sync.guard(&f.impl.p_mutex)
+	sync.guard(&f.p_mutex)
 	p, offset := p, offset
 	p, offset := p, offset
 	for len(p) > 0 {
 	for len(p) > 0 {
 		m := pwrite(f, p, offset) or_return
 		m := pwrite(f, p, offset) or_return
@@ -402,12 +414,12 @@ _write_at :: proc(f: ^File, p: []byte, offset: i64) -> (n: i64, err: Error) {
 	return
 	return
 }
 }
 
 
-_file_size :: proc(f: ^File) -> (n: i64, err: Error) {
+_file_size :: proc(f: ^File_Impl) -> (n: i64, err: Error) {
 	length: win32.LARGE_INTEGER
 	length: win32.LARGE_INTEGER
-	if f.impl.kind == .Pipe {
+	if f.kind == .Pipe {
 		return 0, .No_Size
 		return 0, .No_Size
 	}
 	}
-	handle := _handle(f)
+	handle := _handle(&f.file)
 	if !win32.GetFileSizeEx(handle, &length) {
 	if !win32.GetFileSizeEx(handle, &length) {
 		err = _get_platform_error()
 		err = _get_platform_error()
 	}
 	}
@@ -417,11 +429,14 @@ _file_size :: proc(f: ^File) -> (n: i64, err: Error) {
 
 
 
 
 _sync :: proc(f: ^File) -> Error {
 _sync :: proc(f: ^File) -> Error {
-	return _flush(f)
+	if f != nil && f.impl != nil {
+		return _flush((^File_Impl)(f.impl))
+	}
+	return nil
 }
 }
 
 
-_flush :: proc(f: ^File) -> Error {
-	handle := _handle(f)
+_flush :: proc(f: ^File_Impl) -> Error {
+	handle := _handle(&f.file)
 	if !win32.FlushFileBuffers(handle) {
 	if !win32.FlushFileBuffers(handle) {
 		return _get_platform_error()
 		return _get_platform_error()
 	}
 	}
@@ -429,7 +444,7 @@ _flush :: proc(f: ^File) -> Error {
 }
 }
 
 
 _truncate :: proc(f: ^File, size: i64) -> Error {
 _truncate :: proc(f: ^File, size: i64) -> Error {
-	if f == nil {
+	if f == nil || f.impl == nil {
 		return nil
 		return nil
 	}
 	}
 	curr_off := seek(f, 0, .Current) or_return
 	curr_off := seek(f, 0, .Current) or_return
@@ -443,7 +458,8 @@ _truncate :: proc(f: ^File, size: i64) -> Error {
 }
 }
 
 
 _remove :: proc(name: string) -> Error {
 _remove :: proc(name: string) -> Error {
-	p := _fix_long_path(name)
+	TEMP_ALLOCATOR_GUARD()
+	p := _fix_long_path(name, temp_allocator()) or_return
 	err, err1: Error
 	err, err1: Error
 	if !win32.DeleteFileW(p) {
 	if !win32.DeleteFileW(p) {
 		err = _get_platform_error()
 		err = _get_platform_error()
@@ -480,8 +496,9 @@ _remove :: proc(name: string) -> Error {
 }
 }
 
 
 _rename :: proc(old_path, new_path: string) -> Error {
 _rename :: proc(old_path, new_path: string) -> Error {
-	from := _fix_long_path(old_path)
-	to := _fix_long_path(new_path)
+	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) {
 	if win32.MoveFileExW(from, to, win32.MOVEFILE_REPLACE_EXISTING) {
 		return nil
 		return nil
 	}
 	}
@@ -489,10 +506,10 @@ _rename :: proc(old_path, new_path: string) -> Error {
 
 
 }
 }
 
 
-
 _link :: proc(old_name, new_name: string) -> Error {
 _link :: proc(old_name, new_name: string) -> Error {
-	o := _fix_long_path(old_name)
-	n := _fix_long_path(new_name)
+	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) {
 	if win32.CreateHardLinkW(n, o, nil) {
 		return nil
 		return nil
 	}
 	}
@@ -532,16 +549,16 @@ _normalize_link_path :: proc(p: []u16, allocator: runtime.Allocator) -> (str: st
 	}
 	}
 
 
 	if !has_unc_prefix(p) {
 	if !has_unc_prefix(p) {
-		return win32.utf16_to_utf8(p, allocator)
+		return win32_utf16_to_utf8(p, allocator)
 	}
 	}
 
 
 	ws := p[4:]
 	ws := p[4:]
 	switch {
 	switch {
 	case len(ws) >= 2 && ws[1] == ':':
 	case len(ws) >= 2 && ws[1] == ':':
-		return win32.utf16_to_utf8(ws, allocator)
+		return win32_utf16_to_utf8(ws, allocator)
 	case has_prefix(ws, `UNC\`):
 	case has_prefix(ws, `UNC\`):
 		ws[3] = '\\' // override data in buffer
 		ws[3] = '\\' // override data in buffer
-		return win32.utf16_to_utf8(ws[3:], allocator)
+		return win32_utf16_to_utf8(ws[3:], allocator)
 	}
 	}
 
 
 
 
@@ -566,9 +583,9 @@ _normalize_link_path :: proc(p: []u16, allocator: runtime.Allocator) -> (str: st
 		ws = ws[4:]
 		ws = ws[4:]
 		if len(ws) > 3 && has_prefix(ws, `UNC`) {
 		if len(ws) > 3 && has_prefix(ws, `UNC`) {
 			ws[2] = '\\'
 			ws[2] = '\\'
-			return win32.utf16_to_utf8(ws[2:], allocator)
+			return win32_utf16_to_utf8(ws[2:], allocator)
 		}
 		}
-		return win32.utf16_to_utf8(ws, allocator)
+		return win32_utf16_to_utf8(ws, allocator)
 	}
 	}
 	return "", .Invalid_Path
 	return "", .Invalid_Path
 }
 }
@@ -579,7 +596,9 @@ _read_link :: proc(name: string, allocator: runtime.Allocator) -> (s: string, er
 	@thread_local
 	@thread_local
 	rdb_buf: [MAXIMUM_REPARSE_DATA_BUFFER_SIZE]byte
 	rdb_buf: [MAXIMUM_REPARSE_DATA_BUFFER_SIZE]byte
 
 
-	p := _fix_long_path(name)
+	TEMP_ALLOCATOR_GUARD()
+
+	p      := _fix_long_path(name, temp_allocator()) or_return
 	handle := _open_sym_link(p) or_return
 	handle := _open_sym_link(p) or_return
 	defer win32.CloseHandle(handle)
 	defer win32.CloseHandle(handle)
 
 
@@ -599,7 +618,7 @@ _read_link :: proc(name: string, allocator: runtime.Allocator) -> (s: string, er
 		pb[rb.SubstituteNameOffset+rb.SubstituteNameLength] = 0
 		pb[rb.SubstituteNameOffset+rb.SubstituteNameLength] = 0
 		p := pb[rb.SubstituteNameOffset:][:rb.SubstituteNameLength]
 		p := pb[rb.SubstituteNameOffset:][:rb.SubstituteNameLength]
 		if rb.Flags & win32.SYMLINK_FLAG_RELATIVE != 0 {
 		if rb.Flags & win32.SYMLINK_FLAG_RELATIVE != 0 {
-			return win32.utf16_to_utf8(p, allocator)
+			return win32_utf16_to_utf8(p, allocator)
 		}
 		}
 		return _normalize_link_path(p, allocator)
 		return _normalize_link_path(p, allocator)
 
 
@@ -616,17 +635,18 @@ _read_link :: proc(name: string, allocator: runtime.Allocator) -> (s: string, er
 
 
 
 
 _fchdir :: proc(f: ^File) -> Error {
 _fchdir :: proc(f: ^File) -> Error {
-	if f == nil {
+	if f == nil || f.impl == nil {
 		return nil
 		return nil
 	}
 	}
-	if !win32.SetCurrentDirectoryW(f.impl.wname) {
+	impl := (^File_Impl)(f.impl)
+	if !win32.SetCurrentDirectoryW(impl.wname) {
 		return _get_platform_error()
 		return _get_platform_error()
 	}
 	}
 	return nil
 	return nil
 }
 }
 
 
-_fchmod :: proc(f: ^File, mode: File_Mode) -> Error {
-	if f == nil {
+_fchmod :: proc(f: ^File, mode: int) -> Error {
+	if f == nil || f.impl == nil {
 		return nil
 		return nil
 	}
 	}
 	d: win32.BY_HANDLE_FILE_INFORMATION
 	d: win32.BY_HANDLE_FILE_INFORMATION
@@ -653,14 +673,15 @@ _fchown :: proc(f: ^File, uid, gid: int) -> Error {
 }
 }
 
 
 _chdir :: proc(name: string) -> Error {
 _chdir :: proc(name: string) -> Error {
-	p := _fix_long_path(name)
+	TEMP_ALLOCATOR_GUARD()
+	p := _fix_long_path(name, temp_allocator()) or_return
 	if !win32.SetCurrentDirectoryW(p) {
 	if !win32.SetCurrentDirectoryW(p) {
 		return _get_platform_error()
 		return _get_platform_error()
 	}
 	}
 	return nil
 	return nil
 }
 }
 
 
-_chmod :: proc(name: string, mode: File_Mode) -> Error {
+_chmod :: proc(name: string, mode: int) -> Error {
 	f := open(name, {.Write}) or_return
 	f := open(name, {.Write}) or_return
 	defer close(f)
 	defer close(f)
 	return _fchmod(f, mode)
 	return _fchmod(f, mode)
@@ -681,7 +702,7 @@ _chtimes :: proc(name: string, atime, mtime: time.Time) -> Error {
 	return _fchtimes(f, atime, mtime)
 	return _fchtimes(f, atime, mtime)
 }
 }
 _fchtimes :: proc(f: ^File, atime, mtime: time.Time) -> Error {
 _fchtimes :: proc(f: ^File, atime, mtime: time.Time) -> Error {
-	if f == nil {
+	if f == nil || f.impl == nil {
 		return nil
 		return nil
 	}
 	}
 	d: win32.BY_HANDLE_FILE_INFORMATION
 	d: win32.BY_HANDLE_FILE_INFORMATION
@@ -708,36 +729,16 @@ _fchtimes :: proc(f: ^File, atime, mtime: time.Time) -> Error {
 	return nil
 	return nil
 }
 }
 
 
-
-
 _exists :: proc(path: string) -> bool {
 _exists :: proc(path: string) -> bool {
-	wpath := _fix_long_path(path)
+	TEMP_ALLOCATOR_GUARD()
+	wpath, _ := _fix_long_path(path, temp_allocator())
 	attribs := win32.GetFileAttributesW(wpath)
 	attribs := win32.GetFileAttributesW(wpath)
 	return attribs != win32.INVALID_FILE_ATTRIBUTES
 	return attribs != win32.INVALID_FILE_ATTRIBUTES
 }
 }
 
 
-_is_file :: proc(path: string) -> bool {
-	wpath := _fix_long_path(path)
-	attribs := win32.GetFileAttributesW(wpath)
-	if attribs != win32.INVALID_FILE_ATTRIBUTES {
-		return attribs & win32.FILE_ATTRIBUTE_DIRECTORY == 0
-	}
-	return false
-}
-
-_is_dir :: proc(path: string) -> bool {
-	wpath := _fix_long_path(path)
-	attribs := win32.GetFileAttributesW(wpath)
-	if attribs != win32.INVALID_FILE_ATTRIBUTES {
-		return attribs & win32.FILE_ATTRIBUTE_DIRECTORY != 0
-	}
-	return false
-}
-
-
 @(private="package")
 @(private="package")
 _file_stream_proc :: proc(stream_data: rawptr, mode: io.Stream_Mode, p: []byte, offset: i64, whence: io.Seek_From) -> (n: i64, err: io.Error) {
 _file_stream_proc :: proc(stream_data: rawptr, mode: io.Stream_Mode, p: []byte, offset: i64, whence: io.Seek_From) -> (n: i64, err: io.Error) {
-	f := (^File)(stream_data)
+	f := (^File_Impl)(stream_data)
 	ferr: Error
 	ferr: Error
 	switch mode {
 	switch mode {
 	case .Read:
 	case .Read:
@@ -778,3 +779,85 @@ _file_stream_proc :: proc(stream_data: rawptr, mode: io.Stream_Mode, p: []byte,
 	return 0, .Empty
 	return 0, .Empty
 }
 }
 
 
+
+
+@(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)
+	return
+}
+
+@(private="package", require_results)
+win32_utf8_to_utf16 :: proc(s: string, allocator: runtime.Allocator) -> (ws: []u16, err: runtime.Allocator_Error) {
+	if len(s) < 1 {
+		return
+	}
+
+	b := transmute([]byte)s
+	cstr := raw_data(b)
+	n := win32.MultiByteToWideChar(win32.CP_UTF8, win32.MB_ERR_INVALID_CHARS, cstr, i32(len(s)), nil, 0)
+	if n == 0 {
+		return nil, nil
+	}
+
+	text := make([]u16, n+1, allocator) or_return
+
+	n1 := win32.MultiByteToWideChar(win32.CP_UTF8, win32.MB_ERR_INVALID_CHARS, cstr, i32(len(s)), raw_data(text), n)
+	if n1 == 0 {
+		delete(text, allocator)
+		return
+	}
+
+	text[n] = 0
+	for n >= 1 && text[n-1] == 0 {
+		n -= 1
+	}
+	ws = text[:n]
+	return
+}
+
+@(private="package", require_results)
+win32_wstring_to_utf8 :: proc(s: [^]u16, allocator: runtime.Allocator) -> (res: string, err: runtime.Allocator_Error) {
+	if s == nil || s[0] == 0 {
+		return "", nil
+	}
+	n := 0
+	for s[n] != 0 {
+		n += 1
+	}
+	return win32_utf16_to_utf8(s[:n], allocator)
+}
+
+@(private="package", require_results)
+win32_utf16_to_utf8 :: proc(s: []u16, allocator: runtime.Allocator) -> (res: string, err: runtime.Allocator_Error) {
+	if len(s) == 0 {
+		return
+	}
+
+	n := win32.WideCharToMultiByte(win32.CP_UTF8, win32.WC_ERR_INVALID_CHARS, raw_data(s), i32(len(s)), nil, 0, nil, nil)
+	if n == 0 {
+		return
+	}
+
+	// If N < 0 the call to WideCharToMultiByte assume the wide string is null terminated
+	// and will scan it to find the first null terminated character. The resulting string will
+	// also be null terminated.
+	// If N > 0 it assumes the wide string is not null terminated and the resulting string
+	// will not be null terminated.
+	text := make([]byte, n, allocator) or_return
+
+	n1 := win32.WideCharToMultiByte(win32.CP_UTF8, win32.WC_ERR_INVALID_CHARS, raw_data(s), i32(len(s)), raw_data(text), n, nil, nil)
+	if n1 == 0 {
+		delete(text, allocator)
+		return
+	}
+
+	for i in 0..<n {
+		if text[i] == 0 {
+			n = i
+			break
+		}
+	}
+	res = string(text[:n])
+	return
+}

+ 0 - 4
core/os/os2/heap.odin

@@ -17,7 +17,3 @@ heap_allocator_proc :: proc(allocator_data: rawptr, mode: runtime.Allocator_Mode
                             old_memory: rawptr, old_size: int, loc := #caller_location) -> ([]byte, runtime.Allocator_Error) {
                             old_memory: rawptr, old_size: int, loc := #caller_location) -> ([]byte, runtime.Allocator_Error) {
 	return _heap_allocator_proc(allocator_data, mode, size, alignment, old_memory, old_size, loc)
 	return _heap_allocator_proc(allocator_data, mode, size, alignment, old_memory, old_size, loc)
 }
 }
-
-
-@(private)
-error_allocator := heap_allocator

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

@@ -126,3 +126,5 @@ random_string :: proc(buf: []byte) -> string {
 	buf[i] = digits[u % b]
 	buf[i] = digits[u % b]
 	return string(buf[i:])
 	return string(buf[i:])
 }
 }
+
+

+ 8 - 5
core/os/os2/path.odin

@@ -12,12 +12,14 @@ is_path_separator :: proc(c: byte) -> bool {
 }
 }
 
 
 mkdir :: make_directory
 mkdir :: make_directory
-make_directory :: proc(name: string, perm: File_Mode) -> Error {
+
+make_directory :: proc(name: string, perm: int) -> Error {
 	return _mkdir(name, perm)
 	return _mkdir(name, perm)
 }
 }
 
 
 mkdir_all :: make_directory_all
 mkdir_all :: make_directory_all
-make_directory_all :: proc(path: string, perm: File_Mode) -> Error {
+
+make_directory_all :: proc(path: string, perm: int) -> Error {
 	return _mkdir_all(path, perm)
 	return _mkdir_all(path, perm)
 }
 }
 
 
@@ -25,14 +27,15 @@ remove_all :: proc(path: string) -> Error {
 	return _remove_all(path)
 	return _remove_all(path)
 }
 }
 
 
-
 getwd :: get_working_directory
 getwd :: get_working_directory
+
 @(require_results)
 @(require_results)
 get_working_directory :: proc(allocator: runtime.Allocator) -> (dir: string, err: Error) {
 get_working_directory :: proc(allocator: runtime.Allocator) -> (dir: string, err: Error) {
-	return _getwd(allocator)
+	return _get_working_directory(allocator)
 }
 }
 
 
 setwd :: set_working_directory
 setwd :: set_working_directory
+
 set_working_directory :: proc(dir: string) -> (err: Error) {
 set_working_directory :: proc(dir: string) -> (err: Error) {
-	return _setwd(dir)
+	return _set_working_directory(dir)
 }
 }

+ 13 - 32
core/os/os2/path_linux.odin

@@ -15,19 +15,13 @@ _is_path_separator :: proc(c: byte) -> bool {
 	return c == '/'
 	return c == '/'
 }
 }
 
 
-_mkdir :: proc(path: string, perm: File_Mode) -> Error {
-	// TODO: These modes would require mknod, however, that would also
-	//       require additional arguments to this function..
-	if perm & (File_Mode_Named_Pipe | File_Mode_Device | File_Mode_Char_Device | File_Mode_Sym_Link) != 0 {
-		return .Invalid_Argument
-	}
-
+_mkdir :: proc(path: string, perm: int) -> Error {
 	TEMP_ALLOCATOR_GUARD()
 	TEMP_ALLOCATOR_GUARD()
 	path_cstr := temp_cstring(path) or_return
 	path_cstr := temp_cstring(path) or_return
-	return _get_platform_error(linux.mkdir(path_cstr, transmute(linux.Mode)(u32(perm) & 0o777)))
+	return _get_platform_error(linux.mkdir(path_cstr, transmute(linux.Mode)u32(perm)))
 }
 }
 
 
-_mkdir_all :: proc(path: string, perm: File_Mode) -> Error {
+_mkdir_all :: proc(path: string, perm: int) -> Error {
 	mkdirat :: proc(dfd: linux.Fd, path: []u8, perm: int, has_created: ^bool) -> Error {
 	mkdirat :: proc(dfd: linux.Fd, path: []u8, perm: int, has_created: ^bool) -> Error {
 		i: int
 		i: int
 		for ; i < len(path) - 1 && path[i] != '/'; i += 1 {}
 		for ; i < len(path) - 1 && path[i] != '/'; i += 1 {}
@@ -38,7 +32,7 @@ _mkdir_all :: proc(path: string, perm: File_Mode) -> Error {
 		new_dfd, errno := linux.openat(dfd, cstring(&path[0]), _OPENDIR_FLAGS)
 		new_dfd, errno := linux.openat(dfd, cstring(&path[0]), _OPENDIR_FLAGS)
 		#partial switch errno {
 		#partial switch errno {
 		case .ENOENT:
 		case .ENOENT:
-			if errno = linux.mkdirat(dfd, cstring(&path[0]), transmute(linux.Mode)(u32(perm))); errno != .NONE {
+			if errno = linux.mkdirat(dfd, cstring(&path[0]), transmute(linux.Mode)u32(perm)); errno != .NONE {
 				return _get_platform_error(errno)
 				return _get_platform_error(errno)
 			}
 			}
 			has_created^ = true
 			has_created^ = true
@@ -53,17 +47,9 @@ _mkdir_all :: proc(path: string, perm: File_Mode) -> Error {
 			// skip consecutive '/'
 			// skip consecutive '/'
 			for i += 1; i < len(path) && path[i] == '/'; i += 1 {}
 			for i += 1; i < len(path) && path[i] == '/'; i += 1 {}
 			return mkdirat(new_dfd, path[i:], perm, has_created)
 			return mkdirat(new_dfd, path[i:], perm, has_created)
-		case:
-			return _get_platform_error(errno)
 		}
 		}
-		unreachable()
-	}
-
-	// TODO
-	if perm & (File_Mode_Named_Pipe | File_Mode_Device | File_Mode_Char_Device | File_Mode_Sym_Link) != 0 {
-		return .Invalid_Argument
+		return _get_platform_error(errno)
 	}
 	}
-
 	TEMP_ALLOCATOR_GUARD()
 	TEMP_ALLOCATOR_GUARD()
 	// need something we can edit, and use to generate cstrings
 	// need something we can edit, and use to generate cstrings
 	path_bytes := make([]u8, len(path) + 1, temp_allocator())
 	path_bytes := make([]u8, len(path) + 1, temp_allocator())
@@ -85,12 +71,8 @@ _mkdir_all :: proc(path: string, perm: File_Mode) -> Error {
 	}
 	}
 	
 	
 	has_created: bool
 	has_created: bool
-	mkdirat(dfd, path_bytes, int(perm & 0o777), &has_created) or_return
-	if has_created {
-		return nil
-	}
-	return .Exist
-	//return has_created ? nil : .Exist
+	mkdirat(dfd, path_bytes, perm, &has_created) or_return
+	return nil if has_created else .Exist
 }
 }
 
 
 dirent64 :: struct {
 dirent64 :: struct {
@@ -181,7 +163,7 @@ _remove_all :: proc(path: string) -> Error {
 	return _get_platform_error(linux.rmdir(path_cstr))
 	return _get_platform_error(linux.rmdir(path_cstr))
 }
 }
 
 
-_getwd :: proc(allocator: runtime.Allocator) -> (string, Error) {
+_get_working_directory :: proc(allocator: runtime.Allocator) -> (string, Error) {
 	// NOTE(tetra): I would use PATH_MAX here, but I was not able to find
 	// NOTE(tetra): I would use PATH_MAX here, but I was not able to find
 	// an authoritative value for it across all systems.
 	// an authoritative value for it across all systems.
 	// The largest value I could find was 4096, so might as well use the page size.
 	// The largest value I could find was 4096, so might as well use the page size.
@@ -201,12 +183,12 @@ _getwd :: proc(allocator: runtime.Allocator) -> (string, Error) {
 	unreachable()
 	unreachable()
 }
 }
 
 
-_setwd :: proc(dir: string) -> Error {
+_set_working_directory :: proc(dir: string) -> Error {
 	dir_cstr := temp_cstring(dir) or_return
 	dir_cstr := temp_cstring(dir) or_return
 	return _get_platform_error(linux.chdir(dir_cstr))
 	return _get_platform_error(linux.chdir(dir_cstr))
 }
 }
 
 
-_get_full_path :: proc(fd: linux.Fd, allocator: runtime.Allocator) -> string {
+_get_full_path :: proc(fd: linux.Fd, allocator: runtime.Allocator) -> (fullpath: string, err: Error) {
 	PROC_FD_PATH :: "/proc/self/fd/"
 	PROC_FD_PATH :: "/proc/self/fd/"
 
 
 	buf: [32]u8
 	buf: [32]u8
@@ -214,10 +196,9 @@ _get_full_path :: proc(fd: linux.Fd, allocator: runtime.Allocator) -> string {
 
 
 	strconv.itoa(buf[len(PROC_FD_PATH):], int(fd))
 	strconv.itoa(buf[len(PROC_FD_PATH):], int(fd))
 
 
-	fullpath: string
-	err: Error
 	if fullpath, err = _read_link_cstr(cstring(&buf[0]), allocator); err != nil || fullpath[0] != '/' {
 	if fullpath, err = _read_link_cstr(cstring(&buf[0]), allocator); err != nil || fullpath[0] != '/' {
-		return ""
+		delete(fullpath, allocator)
+		fullpath = ""
 	}
 	}
-	return fullpath
+	return
 }
 }

+ 97 - 24
core/os/os2/path_windows.odin

@@ -12,14 +12,15 @@ _is_path_separator :: proc(c: byte) -> bool {
 	return c == '\\' || c == '/'
 	return c == '\\' || c == '/'
 }
 }
 
 
-_mkdir :: proc(name: string, perm: File_Mode) -> Error {
-	if !win32.CreateDirectoryW(_fix_long_path(name), nil) {
+_mkdir :: proc(name: string, perm: int) -> Error {
+	TEMP_ALLOCATOR_GUARD()
+	if !win32.CreateDirectoryW(_fix_long_path(name, temp_allocator()) or_return, nil) {
 		return _get_platform_error()
 		return _get_platform_error()
 	}
 	}
 	return nil
 	return nil
 }
 }
 
 
-_mkdir_all :: proc(path: string, perm: File_Mode) -> Error {
+_mkdir_all :: proc(path: string, perm: int) -> Error {
 	fix_root_directory :: proc(p: string) -> (s: string, allocated: bool, err: runtime.Allocator_Error) {
 	fix_root_directory :: proc(p: string) -> (s: string, allocated: bool, err: runtime.Allocator_Error) {
 		if len(p) == len(`\\?\c:`) {
 		if len(p) == len(`\\?\c:`) {
 			if is_path_separator(p[0]) && is_path_separator(p[1]) && p[2] == '?' && is_path_separator(p[3]) && p[5] == ':' {
 			if is_path_separator(p[0]) && is_path_separator(p[1]) && p[2] == '?' && is_path_separator(p[3]) && p[5] == ':' {
@@ -33,9 +34,9 @@ _mkdir_all :: proc(path: string, perm: File_Mode) -> Error {
 
 
 	TEMP_ALLOCATOR_GUARD()
 	TEMP_ALLOCATOR_GUARD()
 
 
-	dir, err := stat(path, temp_allocator())
+	dir_stat, err := stat(path, temp_allocator())
 	if err == nil {
 	if err == nil {
-		if dir.is_directory {
+		if dir_stat.type == .Directory {
 			return nil
 			return nil
 		}
 		}
 		return .Exist
 		return .Exist
@@ -61,8 +62,8 @@ _mkdir_all :: proc(path: string, perm: File_Mode) -> Error {
 
 
 	err = mkdir(path, perm)
 	err = mkdir(path, perm)
 	if err != nil {
 	if err != nil {
-		dir1, err1 := lstat(path, temp_allocator())
-		if err1 == nil && dir1.is_directory {
+		new_dir_stat, err1 := lstat(path, temp_allocator())
+		if err1 == nil && new_dir_stat.type == .Directory {
 			return nil
 			return nil
 		}
 		}
 		return err
 		return err
@@ -71,41 +72,114 @@ _mkdir_all :: proc(path: string, perm: File_Mode) -> Error {
 }
 }
 
 
 _remove_all :: proc(path: string) -> Error {
 _remove_all :: proc(path: string) -> Error {
-	// TODO(bill): _remove_all for windows
+	if path == "" {
+		return nil
+	}
+
+	err := remove(path)
+	if err == nil || err == .Not_Exist {
+		return nil
+	}
+
+	TEMP_ALLOCATOR_GUARD()
+	dir := win32_utf8_to_wstring(path, temp_allocator()) or_return
+
+	empty: [1]u16
+
+	file_op := win32.SHFILEOPSTRUCTW {
+		nil,
+		win32.FO_DELETE,
+		dir,
+		&empty[0],
+		win32.FOF_NOCONFIRMATION | win32.FOF_NOERRORUI | win32.FOF_SILENT,
+		false,
+		nil,
+		&empty[0],
+	}
+	res := win32.SHFileOperationW(&file_op)
+	if res != 0 {
+		return _get_platform_error()
+	}
 	return nil
 	return nil
 }
 }
 
 
-_getwd :: proc(allocator: runtime.Allocator) -> (dir: string, err: Error) {
-	// TODO(bill)
-	return "", nil
-}
+@private cwd_lock: win32.SRWLOCK // zero is initialized
 
 
-_setwd :: proc(dir: string) -> (err: Error) {
-	// TODO(bill)
-	return nil
+_get_working_directory :: proc(allocator: runtime.Allocator) -> (dir: string, err: Error) {
+	win32.AcquireSRWLockExclusive(&cwd_lock)
+
+	TEMP_ALLOCATOR_GUARD()
+
+	sz_utf16 := win32.GetCurrentDirectoryW(0, nil)
+	dir_buf_wstr := make([]u16, sz_utf16, temp_allocator()) or_return
+
+	sz_utf16 = win32.GetCurrentDirectoryW(win32.DWORD(len(dir_buf_wstr)), raw_data(dir_buf_wstr))
+	assert(int(sz_utf16)+1 == len(dir_buf_wstr)) // the second time, it _excludes_ the NUL.
+
+	win32.ReleaseSRWLockExclusive(&cwd_lock)
+
+	return win32_utf16_to_utf8(dir_buf_wstr, allocator)
 }
 }
 
 
+_set_working_directory :: proc(dir: string) -> (err: Error) {
+	TEMP_ALLOCATOR_GUARD()
+	wstr := win32_utf8_to_wstring(dir, temp_allocator()) or_return
+
+	win32.AcquireSRWLockExclusive(&cwd_lock)
+
+	if !win32.SetCurrentDirectoryW(wstr) {
+		err = _get_platform_error()
+	}
+
+	win32.ReleaseSRWLockExclusive(&cwd_lock)
+
+	return
+}
 
 
 can_use_long_paths: bool
 can_use_long_paths: bool
 
 
 @(init)
 @(init)
 init_long_path_support :: proc() {
 init_long_path_support :: proc() {
-	// TODO(bill): init_long_path_support
-	// ADD THIS SHIT
-	// registry_path := win32.L(`Computer\HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\FileSystem\LongPathsEnabled`)
 	can_use_long_paths = false
 	can_use_long_paths = false
-}
 
 
+	key: win32.HKEY
+	res := win32.RegOpenKeyExW(win32.HKEY_LOCAL_MACHINE, win32.L(`SYSTEM\CurrentControlSet\Control\FileSystem`), 0, win32.KEY_READ, &key)
+	defer win32.RegCloseKey(key)
+	if res != 0 {
+		return
+	}
+
+	value: u32
+	size := u32(size_of(value))
+	res = win32.RegGetValueW(
+		key,
+		nil,
+		win32.L("LongPathsEnabled"),
+		win32.RRF_RT_ANY,
+		nil,
+		&value,
+		&size,
+	)
+	if res != 0 {
+		return
+	}
+	if value == 1 {
+		can_use_long_paths = true
+	}
 
 
-_fix_long_path_slice :: proc(path: string) -> []u16 {
-	return win32.utf8_to_utf16(_fix_long_path_internal(path))
 }
 }
 
 
-_fix_long_path :: proc(path: string) -> win32.wstring {
-	return win32.utf8_to_wstring(_fix_long_path_internal(path))
+@(require_results)
+_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, allocator: runtime.Allocator) -> (win32.wstring, runtime.Allocator_Error) {
+	return win32_utf8_to_wstring(_fix_long_path_internal(path), allocator)
+}
 
 
+@(require_results)
 _fix_long_path_internal :: proc(path: string) -> string {
 _fix_long_path_internal :: proc(path: string) -> string {
 	if can_use_long_paths {
 	if can_use_long_paths {
 		return path
 		return path
@@ -162,5 +236,4 @@ _fix_long_path_internal :: proc(path: string) -> string {
 	}
 	}
 
 
 	return string(path_buf[:w])
 	return string(path_buf[:w])
-
 }
 }

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

@@ -5,13 +5,13 @@ import "core:sys/linux"
 
 
 _pipe :: proc() -> (r, w: ^File, err: Error) {
 _pipe :: proc() -> (r, w: ^File, err: Error) {
 	fds: [2]linux.Fd
 	fds: [2]linux.Fd
-	errno := linux.pipe2(&fds, {.CLOEXEC})
+	errno := linux.pipe2(&fds, {})
 	if errno != .NONE {
 	if errno != .NONE {
 		return nil, nil,_get_platform_error(errno)
 		return nil, nil,_get_platform_error(errno)
 	}
 	}
 
 
-	r = _new_file(uintptr(fds[0]))
-	w = _new_file(uintptr(fds[1]))
+	r = _new_file(uintptr(fds[0])) or_return
+	w = _new_file(uintptr(fds[1])) or_return
 	return
 	return
 }
 }
 
 

+ 5 - 1
core/os/os2/pipe_windows.odin

@@ -5,7 +5,11 @@ import win32 "core:sys/windows"
 
 
 _pipe :: proc() -> (r, w: ^File, err: Error) {
 _pipe :: proc() -> (r, w: ^File, err: Error) {
 	p: [2]win32.HANDLE
 	p: [2]win32.HANDLE
-	if !win32.CreatePipe(&p[0], &p[1], nil, 0) {
+	sa := win32.SECURITY_ATTRIBUTES {
+		nLength = size_of(win32.SECURITY_ATTRIBUTES),
+		bInheritHandle = true,
+	}
+	if !win32.CreatePipe(&p[0], &p[1], &sa, 0) {
 		return nil, nil, _get_platform_error()
 		return nil, nil, _get_platform_error()
 	}
 	}
 	return new_file(uintptr(p[0]), ""), new_file(uintptr(p[1]), ""), nil
 	return new_file(uintptr(p[0]), ""), new_file(uintptr(p[1]), ""), nil

+ 350 - 46
core/os/os2/process.odin

@@ -1,102 +1,406 @@
 package os2
 package os2
 
 
-import "core:sync"
-import "core:time"
 import "base:runtime"
 import "base:runtime"
+import "core:time"
+
+/*
+	In procedures that explicitly state this as one of the allowed values,
+	specifies an infinite timeout.
+*/
+TIMEOUT_INFINITE :: time.MIN_DURATION // Note(flysand): Any negative duration will be treated as infinity
 
 
-args: []string
+/*
+	Arguments to the current process.
+*/
+args := get_args()
 
 
+@(private="file", require_results)
+get_args :: proc() -> []string {
+	result := make([]string, len(runtime.args__), heap_allocator())
+	for rt_arg, i in runtime.args__ {
+		result[i] = string(rt_arg)
+	}
+	return result
+}
+
+/*
+	Exit the current process.
+*/
 exit :: proc "contextless" (code: int) -> ! {
 exit :: proc "contextless" (code: int) -> ! {
-	runtime.trap()
+	_exit(code)
 }
 }
 
 
+/*
+	Obtain the UID of the current process.
+
+	**Note(windows)**: Windows doesn't follow the posix permissions model, so
+	the function simply returns -1.
+*/
+@(require_results)
 get_uid :: proc() -> int {
 get_uid :: proc() -> int {
-	return -1
+	return _get_uid()
 }
 }
 
 
+/*
+	Obtain the effective UID of the current process.
+
+	The effective UID is typically the same as the UID of the process. In case
+	the process was run by a user with elevated permissions, the process may
+	lower the privilege to perform some tasks without privilege. In these cases
+	the real UID of the process and the effective UID are different.
+	
+	**Note(windows)**: Windows doesn't follow the posix permissions model, so
+	the function simply returns -1.
+*/
+@(require_results)
 get_euid :: proc() -> int {
 get_euid :: proc() -> int {
-	return -1
+	return _get_euid()
 }
 }
 
 
+/*
+	Obtain the GID of the current process.
+	
+	**Note(windows)**: Windows doesn't follow the posix permissions model, so
+	the function simply returns -1.
+*/
+@(require_results)
 get_gid :: proc() -> int {
 get_gid :: proc() -> int {
-	return -1
+	return _get_gid()
 }
 }
 
 
+/*
+	Obtain the effective GID of the current process.
+	
+	The effective GID is typically the same as the GID of the process. In case
+	the process was run by a user with elevated permissions, the process may
+	lower the privilege to perform some tasks without privilege. In these cases
+	the real GID of the process and the effective GID are different.
+
+	**Note(windows)**: Windows doesn't follow the posix permissions model, so
+	the function simply returns -1.
+*/
+@(require_results)
 get_egid :: proc() -> int {
 get_egid :: proc() -> int {
-	return -1
+	return _get_egid()
 }
 }
 
 
+/*
+	Obtain the ID of the current process.
+*/
+@(require_results)
 get_pid :: proc() -> int {
 get_pid :: proc() -> int {
-	return -1
+	return _get_pid()
 }
 }
 
 
+/*
+	Obtain the ID of the parent process.
+
+	**Note(windows)**: Windows does not mantain strong relationships between
+	parent and child processes. This function returns the ID of the process
+	that has created the current process. In case the parent has died, the ID
+	returned by this function can identify a non-existent or a different
+	process.
+*/
+@(require_results)
 get_ppid :: proc() -> int {
 get_ppid :: proc() -> int {
-	return -1
+	return _get_ppid()
 }
 }
 
 
+/*
+	Obtain ID's of all processes running in the system.
+*/
+@(require_results)
+process_list :: proc(allocator: runtime.Allocator) -> ([]int, Error) {
+	return _process_list(allocator)
+}
 
 
-Process :: struct {
-	pid:          int,
-	handle:       uintptr,
-	is_done:      b32,
-	signal_mutex: sync.RW_Mutex,
+/*
+	Bit set specifying which fields of the `Process_Info` struct need to be
+	obtained by the `process_info()` procedure. Each bit corresponds to a
+	field in the `Process_Info` struct.
+*/
+Process_Info_Fields :: bit_set[Process_Info_Field]
+Process_Info_Field :: enum {
+	Executable_Path,
+	PPid,
+	Priority,
+	Command_Line,
+	Command_Args,
+	Environment,
+	Username,
+	Working_Dir,
 }
 }
 
 
+/*
+	Contains information about the process as obtained by the `process_info()`
+	procedure.
+*/
+Process_Info :: struct {
+	// The information about a process the struct contains. `pid` is always
+	// stored, no matter what.
+	fields: Process_Info_Fields,
+	// The ID of the process.
+	pid: int,
+	// The ID of the parent process.
+	ppid: int,
+	// The process priority.
+	priority: int,
+	// The path to the executable, which the process runs.
+	executable_path: string,
+	// The command line supplied to the process.
+	command_line: string,
+	// The arguments supplied to the process.
+	command_args: []string,
+	// The environment of the process.
+	environment: []string,
+	// The username of the user who started the process.
+	username: string,
+	// The current working directory of the process.
+	working_dir: string,
+}
 
 
-Process_Attributes :: struct {
-	dir: string,
-	env: []string,
-	files: []^File,
-	sys: ^Process_Attributes_OS_Specific,
+/*
+	Obtain information about a process.
+
+	This procedure obtains an information, specified by `selection` parameter of
+	a process given by `pid`.
+	
+	Use `free_process_info` to free the memory allocated by this procedure. In
+	case the function returns an error all temporary allocations would be freed
+	and as such, calling `free_process_info()` is not needed.
+
+	**Note**: The resulting information may or may contain the fields specified
+	by the `selection` parameter. Always check whether the returned
+	`Process_Info` struct has the required fields before checking the error code
+	returned by this function.
+*/
+@(require_results)
+process_info_by_pid :: proc(pid: int, selection: Process_Info_Fields, allocator: runtime.Allocator) -> (Process_Info, Error) {
+	return _process_info_by_pid(pid, selection, allocator)
 }
 }
 
 
-Process_Attributes_OS_Specific :: struct{}
+/*
+	Obtain information about a process.
+
+	This procedure obtains information, specified by `selection` parameter
+	about a process that has been opened by the application, specified in
+	the `process` parameter.
 
 
-Process_Error :: enum {
-	None,
+	Use `free_process_info` to free the memory allocated by this procedure. In
+	case the function returns an error, all temporary allocations would be freed
+	and as such, calling `free_process_info` is not needed.
+
+	**Note**: The resulting information may or may contain the fields specified
+	by the `selection` parameter. Always check whether the returned
+	`Process_Info` struct has the required fields before checking the error code
+	returned by this function.
+*/
+@(require_results)
+process_info_by_handle :: proc(process: Process, selection: Process_Info_Fields, allocator: runtime.Allocator) -> (Process_Info, Error) {
+	return _process_info_by_handle(process, selection, allocator)
 }
 }
 
 
-Process_State :: struct {
-	pid:         int,
-	exit_code:   int,
-	exited:      bool,
-	success:     bool,
-	system_time: time.Duration,
-	user_time:   time.Duration,
-	sys:         rawptr,
+/*
+	Obtain information about the current process.
+
+	This procedure obtains the information, specified by `selection` parameter
+	about the currently running process.
+
+	Use `free_process_info` to free the memory allocated by this function. In
+	case this function returns an error, all temporary allocations would be
+	freed and as such calling `free_process_info()` is not needed.
+
+	**Note**: The resulting information may or may contain the fields specified
+	by the `selection` parameter. Always check whether the returned
+	`Process_Info` struct has the required fields before checking the error code
+	returned by this function.
+*/
+@(require_results)
+current_process_info :: proc(selection: Process_Info_Fields, allocator: runtime.Allocator) -> (Process_Info, Error) {
+	return _current_process_info(selection, allocator)
+}
+
+/*
+	Obtain information about the specified process.
+*/
+process_info :: proc {
+	process_info_by_pid,
+	process_info_by_handle,
+	current_process_info,
 }
 }
 
 
-Signal :: #type proc()
+/*
+	Free the information about the process.
 
 
-Kill:      Signal = nil
-Interrupt: Signal = nil
+	This procedure frees the memory occupied by process info using the provided
+	allocator. The allocator needs to be the same allocator that was supplied
+	to the `process_info` function.
+*/
+free_process_info :: proc(pi: Process_Info, allocator: runtime.Allocator) {
+	delete(pi.executable_path, allocator)
+	delete(pi.command_line, allocator)
+	delete(pi.command_args, allocator)
+	for s in pi.environment {
+		delete(s, allocator)
+	}
+	delete(pi.environment, allocator)
+	delete(pi.working_dir, allocator)
+}
+
+/*
+	Represents a process handle.
 
 
+	When a process dies, the OS is free to re-use the pid of that process. The
+	`Process` struct represents a handle to the process that will refer to a
+	specific process, even after it has died.
 
 
-find_process :: proc(pid: int) -> (^Process, Process_Error) {
-	return nil, .None
+	**Note(linux)**: The `handle` will be referring to pidfd.
+*/
+Process :: struct {
+	pid: int,
+	handle: uintptr,
 }
 }
 
 
+Process_Open_Flags :: bit_set[Process_Open_Flag]
+Process_Open_Flag :: enum {
+	// Request for reading from the virtual memory of another process.
+	Mem_Read,
+	// Request for writing to the virtual memory of another process.
+	Mem_Write,
+}
+
+/*
+	Open a process handle using it's pid.
+
+	This procedure obtains a process handle of a process specified by `pid`.
+	This procedure can be subject to race conditions. See the description of
+	`Process`.
 
 
-process_start :: proc(name: string, argv: []string, attr: ^Process_Attributes) -> (^Process, Process_Error) {
-	return nil, .None
+	Use `process_close()` function to close the process handle.
+*/
+@(require_results)
+process_open :: proc(pid: int, flags := Process_Open_Flags {}) -> (Process, Error) {
+	return _process_open(pid, flags)
 }
 }
 
 
-process_release :: proc(p: ^Process) -> Process_Error {
-	return .None
+/*
+	The description of how a process should be created.
+*/
+Process_Desc :: struct {
+	// OS-specific attributes.
+	sys_attr: _Sys_Process_Attributes,
+	// The working directory of the process. If the string has length 0, the
+	// working directory is assumed to be the current working directory of the
+	// current process.
+	working_dir: string,
+	// The command to run. Each element of the slice is a separate argument to
+	// the process. The first element of the slice would be the executable.
+	command: []string,
+	// A slice of strings, each having the format `KEY=VALUE` representing the
+	// full environment that the child process will receive.
+	// In case this slice is `nil`, the current process' environment is used.
+	env: []string,
+	// The `stderr` handle to give to the child process. It can be either a file
+	// or a writeable end of a pipe. Passing `nil` will shut down the process'
+	// stderr output.
+	stderr: ^File,
+	// The `stdout` handle to give to the child process. It can be either a file
+	// or a writeabe end of a pipe. Passing a `nil` will shut down the process'
+	// stdout output.
+	stdout: ^File,
+	// The `stdin` handle to give to the child process. It can either be a file
+	// or a readable end of a pipe. Passing a `nil` will shut down the process'
+	// input.
+	stdin: ^File,
 }
 }
 
 
-process_kill :: proc(p: ^Process) -> Process_Error {
-	return .None
+/*
+	Create a new process and obtain its handle.
+
+	This procedure creates a new process, with a given command and environment
+	strings as parameters. Use `environ()` to inherit the environment of the
+	current process.
+
+	The `desc` parameter specifies the description of how the process should
+	be created. It contains information such as the command line, the
+	environment of the process, the starting directory and many other options.
+	Most of the fields in the struct can be set to `nil` or an empty value.
+	
+	Use `process_close` to close the handle to the process. Note, that this
+	is not the same as terminating the process. One can terminate the process
+	and not close the handle, in which case the handle would be leaked. In case
+	the function returns an error, an invalid handle is returned.
+
+	This procedure is not thread-safe. It may alter the inheritance properties
+	of file handles in an unpredictable manner. In case multiple threads change
+	handle inheritance properties, make sure to serialize all those calls.
+*/
+@(require_results)
+process_start :: proc(desc := Process_Desc {}) -> (Process, Error) {
+	return _process_start(desc)
 }
 }
 
 
-process_signal :: proc(p: ^Process, sig: Signal) -> Process_Error {
-	return .None
+/*
+	The state of the process after it has finished execution.
+*/
+Process_State :: struct {
+	// The ID of the process.
+	pid: int,
+	// Specifies whether the process has terminated or is still running.
+	exited: bool,
+	// The exit code of the process, if it has exited.
+	// Will also store the number of the exception or signal that has crashed the
+	// process.
+	exit_code: int,
+	// Specifies whether the termination of the process was successfull or not,
+	// i.e. whether it has crashed or not.
+	// **Note(windows)**: On windows `true` is always returned, as there is no
+	// reliable way to obtain information about whether the process has crashed.
+	success: bool,
+	// The time the process has spend executing in kernel time.
+	system_time: time.Duration,
+	// The time the process has spend executing in userspace.
+	user_time: time.Duration,
 }
 }
 
 
-process_wait :: proc(p: ^Process) -> (Process_State, Process_Error) {
-	return {}, .None
+/*
+	Wait for a process event.
+
+	This procedure blocks the execution until the process has exited or the
+	timeout (if specified) has reached zero. If the timeout is `TIMEOUT_INFINITE`,
+	no timeout restriction is imposed and the procedure can block indefinately.
+
+	If the timeout has expired, the `General_Error.Timeout` is returned as
+	the error.
+
+	If an error is returned for any other reason, other than timeout, the
+	process state is considered undetermined.
+*/
+@(require_results)
+process_wait :: proc(process: Process, timeout := TIMEOUT_INFINITE) -> (Process_State, Error) {
+	return _process_wait(process, timeout)
 }
 }
 
 
+/*
+	Close the handle to a process.
 
 
+	This procedure closes the handle associated with a process. It **does not**
+	terminate a process, in case it was running. In case a termination is
+	desired, kill the process first, wait for the process to finish,
+	then close the handle.
+*/
+@(require_results)
+process_close :: proc(process: Process) -> (Error) {
+	return _process_close(process)
+}
 
 
+/*
+	Terminate a process.
 
 
+	This procedure terminates a process, specified by it's handle, `process`.
+
+*/
+@(require_results)
+process_kill :: proc(process: Process) -> (Error) {
+	return _process_kill(process)
+}

+ 95 - 0
core/os/os2/process_linux.odin

@@ -0,0 +1,95 @@
+//+private file
+package os2
+
+import "base:runtime"
+import "core:time"
+import "core:sys/linux"
+
+@(private="package")
+_exit :: proc "contextless" (code: int) -> ! {
+	linux.exit(i32(code))
+}
+
+
+@(private="package")
+_get_uid :: proc() -> int {
+	return -1
+}
+
+@(private="package")
+_get_euid :: proc() -> int {
+	return -1
+}
+
+@(private="package")
+_get_gid :: proc() -> int {
+	return -1
+}
+
+@(private="package")
+_get_egid :: proc() -> int {
+	return -1
+}
+
+@(private="package")
+_get_pid :: proc() -> int {
+	return -1
+}
+
+@(private="package")
+_get_ppid :: proc() -> int {
+	return -1
+}
+
+@(private="package")
+_process_list :: proc(allocator: runtime.Allocator) -> (list: []int, err: Error) {
+	return
+}
+
+@(private="package")
+_process_info_by_pid :: proc(pid: int, selection: Process_Info_Fields, allocator: runtime.Allocator) -> (info: Process_Info, err: Error) {
+	return
+}
+
+@(private="package")
+_process_info_by_handle :: proc(process: Process, selection: Process_Info_Fields, allocator: runtime.Allocator) -> (info: Process_Info, err: Error) {
+	return
+}
+
+@(private="package")
+_current_process_info :: proc(selection: Process_Info_Fields, allocator: runtime.Allocator) -> (info: Process_Info, err: Error) {
+	return
+}
+
+@(private="package")
+_process_open :: proc(pid: int, flags: Process_Open_Flags) -> (process: Process, err: Error) {
+	return
+}
+
+@(private="package")
+_Sys_Process_Attributes :: struct {}
+
+@(private="package")
+_process_start :: proc(desc: Process_Desc) -> (process: Process, err: Error) {
+	return
+}
+
+@(private="package")
+_process_wait :: proc(process: Process, timeout: time.Duration) -> (process_state: Process_State, err: Error) {
+	return
+}
+
+@(private="package")
+_process_close :: proc(process: Process) -> Error {
+	return nil
+}
+
+@(private="package")
+_process_kill :: proc(process: Process) -> Error {
+	return nil
+}
+
+@(private="package")
+_process_exe_by_pid :: proc(pid: int, allocator: runtime.Allocator) -> (exe_path: string, err: Error) {
+	return
+}

+ 695 - 0
core/os/os2/process_windows.odin

@@ -0,0 +1,695 @@
+//+private file
+package os2
+
+import "base:runtime"
+
+import "core:strings"
+import win32 "core:sys/windows"
+import "core:time"
+
+@(private="package")
+_exit :: proc "contextless" (code: int) -> ! {
+	win32.ExitProcess(u32(code))
+}
+
+@(private="package")
+_get_uid :: proc() -> int {
+	return -1
+}
+
+@(private="package")
+_get_euid :: proc() -> int {
+	return -1
+}
+
+@(private="package")
+_get_gid :: proc() -> int {
+	return -1
+}
+
+@(private="package")
+_get_egid :: proc() -> int {
+	return -1
+}
+
+@(private="package")
+_get_pid :: proc() -> int {
+	return int(win32.GetCurrentProcessId())
+}
+
+@(private="package")
+_get_ppid :: proc() -> int {
+	our_pid := win32.GetCurrentProcessId()
+	snap := win32.CreateToolhelp32Snapshot(win32.TH32CS_SNAPPROCESS, 0)
+	if snap == win32.INVALID_HANDLE_VALUE {
+		return -1
+	}
+	defer win32.CloseHandle(snap)
+	entry := win32.PROCESSENTRY32W { dwSize = size_of(win32.PROCESSENTRY32W) }
+	for status := win32.Process32FirstW(snap, &entry); status; /**/ {
+		if entry.th32ProcessID == our_pid {
+			return int(entry.th32ParentProcessID)
+		}
+		status = win32.Process32NextW(snap, &entry)
+	}
+	return -1
+}
+
+@(private="package")
+_process_list :: proc(allocator: runtime.Allocator) -> (list: []int, err: Error) {
+	snap := win32.CreateToolhelp32Snapshot(win32.TH32CS_SNAPPROCESS, 0)
+	if snap == win32.INVALID_HANDLE_VALUE {
+		err = _get_platform_error()
+		return
+	}
+
+	list_d := make([dynamic]int, allocator) or_return
+
+	entry := win32.PROCESSENTRY32W{dwSize = size_of(win32.PROCESSENTRY32W)}
+	status := win32.Process32FirstW(snap, &entry)
+	for status {
+		append(&list_d, int(entry.th32ProcessID))
+		status = win32.Process32NextW(snap, &entry)
+	}
+	list = list_d[:]
+	return
+}
+
+@(require_results)
+read_memory_as_struct :: proc(h: win32.HANDLE, addr: rawptr, dest: ^$T) -> (bytes_read: uint, err: Error) {
+	if !win32.ReadProcessMemory(h, addr, dest, size_of(T), &bytes_read) {
+		err = _get_platform_error()
+	}
+	return
+}
+@(require_results)
+read_memory_as_slice :: proc(h: win32.HANDLE, addr: rawptr, dest: []$T) -> (bytes_read: uint, err: Error) {
+	if !win32.ReadProcessMemory(h, addr, raw_data(dest), len(dest)*size_of(T), &bytes_read) {
+		err = _get_platform_error()
+	}
+	return
+}
+
+@(private="package")
+_process_info_by_pid :: proc(pid: int, selection: Process_Info_Fields, allocator: runtime.Allocator) -> (info: Process_Info, err: Error) {
+	info.pid = pid
+	defer if err != nil {
+		free_process_info(info, allocator)
+	}
+
+	// Data obtained from process snapshots
+	if selection >= {.PPid, .Priority} {
+		entry, entry_err := _process_entry_by_pid(info.pid)
+		if entry_err != nil {
+			err = General_Error.Not_Exist
+			return
+		}
+		if .PPid in selection {
+			info.fields += {.PPid}
+			info.ppid = int(entry.th32ParentProcessID)
+		}
+		if .Priority in selection {
+			info.fields += {.Priority}
+			info.priority = int(entry.pcPriClassBase)
+		}
+	}
+	if .Executable_Path in selection { // snap module
+		info.executable_path = _process_exe_by_pid(pid, allocator) or_return
+		info.fields += {.Executable_Path}
+	}
+
+	ph := win32.INVALID_HANDLE_VALUE
+
+	if selection >= {.Command_Line, .Environment, .Working_Dir, .Username} { // need process handle
+		ph = win32.OpenProcess(
+			win32.PROCESS_QUERY_LIMITED_INFORMATION | win32.PROCESS_VM_READ,
+			false,
+			u32(pid),
+		)
+		if ph == win32.INVALID_HANDLE_VALUE {
+			err = _get_platform_error()
+			return
+		}
+	}
+	defer if ph != win32.INVALID_HANDLE_VALUE {
+		win32.CloseHandle(ph)
+	}
+
+	if selection >= {.Command_Line, .Environment, .Working_Dir} { // need peb
+		process_info_size: u32
+		process_info: win32.PROCESS_BASIC_INFORMATION
+		status := win32.NtQueryInformationProcess(ph, .ProcessBasicInformation, &process_info, size_of(process_info), &process_info_size)
+		if status != 0 {
+			// TODO(flysand): There's probably a mismatch between NTSTATUS and
+			// windows userland error codes, I haven't checked.
+			err = Platform_Error(status)
+			return
+		}
+		if process_info.PebBaseAddress == nil {
+			// Not sure what the error is
+			err = General_Error.Unsupported
+			return
+		}
+		process_peb: win32.PEB
+
+		_ = read_memory_as_struct(ph, process_info.PebBaseAddress, &process_peb) or_return
+
+		process_params: win32.RTL_USER_PROCESS_PARAMETERS
+		_ = read_memory_as_struct(ph, process_peb.ProcessParameters, &process_params) or_return
+
+		if selection >= {.Command_Line, .Command_Args} {
+			TEMP_ALLOCATOR_GUARD()
+			cmdline_w := make([]u16, process_params.CommandLine.Length, temp_allocator()) or_return
+			_ = read_memory_as_slice(ph, process_params.CommandLine.Buffer, cmdline_w) or_return
+
+			if .Command_Line in selection {
+				info.command_line = win32_utf16_to_utf8(cmdline_w, allocator) or_return
+				info.fields += {.Command_Line}
+			}
+			if .Command_Args in selection {
+				info.command_args = _parse_command_line(raw_data(cmdline_w), allocator) or_return
+				info.fields += {.Command_Args}
+			}
+		}
+		if .Environment in selection {
+			TEMP_ALLOCATOR_GUARD()
+			env_len := process_params.EnvironmentSize / 2
+			envs_w := make([]u16, env_len, temp_allocator()) or_return
+			_ = read_memory_as_slice(ph, process_params.Environment, envs_w) or_return
+
+			info.environment = _parse_environment_block(raw_data(envs_w), allocator) or_return
+			info.fields += {.Environment}
+		}
+		if .Working_Dir in selection {
+			TEMP_ALLOCATOR_GUARD()
+			cwd_w := make([]u16, process_params.CurrentDirectoryPath.Length, temp_allocator()) or_return
+			_ = read_memory_as_slice(ph, process_params.CurrentDirectoryPath.Buffer, cwd_w) or_return
+
+			info.working_dir = win32_utf16_to_utf8(cwd_w, allocator) or_return
+			info.fields += {.Working_Dir}
+		}
+	}
+
+	if .Username in selection {
+		info.username = _get_process_user(ph, allocator) or_return
+		info.fields += {.Username}
+	}
+	err = nil
+	return
+}
+
+@(private="package")
+_process_info_by_handle :: proc(process: Process, selection: Process_Info_Fields, allocator: runtime.Allocator) -> (info: Process_Info, err: Error) {
+	pid := process.pid
+	info.pid = pid
+	defer if err != nil {
+		free_process_info(info, allocator)
+	}
+
+	// Data obtained from process snapshots
+	if selection >= {.PPid, .Priority} { // snap process
+		entry, entry_err := _process_entry_by_pid(info.pid)
+		if entry_err != nil {
+			err = General_Error.Not_Exist
+			return
+		}
+		if .PPid in selection {
+			info.fields += {.PPid}
+			info.ppid = int(entry.th32ParentProcessID)
+		}
+		if .Priority in selection {
+			info.fields += {.Priority}
+			info.priority = int(entry.pcPriClassBase)
+		}
+	}
+	if .Executable_Path in selection { // snap module
+		info.executable_path = _process_exe_by_pid(pid, allocator) or_return
+		info.fields += {.Executable_Path}
+	}
+	ph := win32.HANDLE(process.handle)
+	if selection >= {.Command_Line, .Environment, .Working_Dir} { // need peb
+		process_info_size: u32
+		process_info: win32.PROCESS_BASIC_INFORMATION
+		status := win32.NtQueryInformationProcess(ph, .ProcessBasicInformation, &process_info, size_of(process_info), &process_info_size)
+		if status != 0 {
+			// TODO(flysand): There's probably a mismatch between NTSTATUS and
+			// windows userland error codes, I haven't checked.
+			err = Platform_Error(status)
+			return
+		}
+		if process_info.PebBaseAddress == nil {
+			// Not sure what the error is
+			err = General_Error.Unsupported
+			return
+		}
+
+		process_peb: win32.PEB
+		_ = read_memory_as_struct(ph, process_info.PebBaseAddress, &process_peb) or_return
+
+		process_params: win32.RTL_USER_PROCESS_PARAMETERS
+		_ = read_memory_as_struct(ph, process_peb.ProcessParameters, &process_params) or_return
+
+		if selection >= {.Command_Line, .Command_Args} {
+			TEMP_ALLOCATOR_GUARD()
+			cmdline_w := make([]u16, process_params.CommandLine.Length, temp_allocator()) or_return
+			_ = read_memory_as_slice(ph, process_params.CommandLine.Buffer, cmdline_w) or_return
+
+			if .Command_Line in selection {
+				info.command_line = win32_utf16_to_utf8(cmdline_w, allocator) or_return
+				info.fields += {.Command_Line}
+			}
+			if .Command_Args in selection {
+				info.command_args = _parse_command_line(raw_data(cmdline_w), allocator) or_return
+				info.fields += {.Command_Args}
+			}
+		}
+
+		if .Environment in selection {
+			TEMP_ALLOCATOR_GUARD()
+			env_len := process_params.EnvironmentSize / 2
+			envs_w := make([]u16, env_len, temp_allocator()) or_return
+			_ = read_memory_as_slice(ph, process_params.Environment, envs_w) or_return
+
+			info.environment =  _parse_environment_block(raw_data(envs_w), allocator) or_return
+			info.fields += {.Environment}
+		}
+
+		if .Working_Dir in selection {
+			TEMP_ALLOCATOR_GUARD()
+			cwd_w := make([]u16, process_params.CurrentDirectoryPath.Length, temp_allocator()) or_return
+			_ = read_memory_as_slice(ph, process_params.CurrentDirectoryPath.Buffer, cwd_w) or_return
+
+			info.working_dir = win32_utf16_to_utf8(cwd_w, allocator) or_return
+			info.fields += {.Working_Dir}
+		}
+	}
+	if .Username in selection {
+		info.username = _get_process_user(ph, allocator) or_return
+		info.fields += {.Username}
+	}
+	err = nil
+	return
+}
+
+@(private="package")
+_current_process_info :: proc(selection: Process_Info_Fields, allocator: runtime.Allocator) -> (info: Process_Info, err: Error) {
+	info.pid = get_pid()
+	defer if err != nil {
+		free_process_info(info, allocator)
+	}
+
+	if selection >= {.PPid, .Priority} { // snap process
+		entry, entry_err := _process_entry_by_pid(info.pid)
+		if entry_err != nil {
+			err = General_Error.Not_Exist
+			return
+		}
+		if .PPid in selection {
+			info.fields += {.PPid}
+			info.ppid = int(entry.th32ProcessID)
+		}
+		if .Priority in selection {
+			info.fields += {.Priority}
+			info.priority = int(entry.pcPriClassBase)
+		}
+	}
+	if .Executable_Path in selection {
+		exe_filename_w: [256]u16
+		path_len := win32.GetModuleFileNameW(nil, raw_data(exe_filename_w[:]), len(exe_filename_w))
+		info.executable_path = win32_utf16_to_utf8(exe_filename_w[:path_len], allocator) or_return
+		info.fields += {.Executable_Path}
+	}
+	if selection >= {.Command_Line,  .Command_Args} {
+		command_line_w := win32.GetCommandLineW()
+		if .Command_Line in selection {
+			info.command_line = win32_wstring_to_utf8(command_line_w, allocator) or_return
+			info.fields += {.Command_Line}
+		}
+		if .Command_Args in selection {
+			info.command_args = _parse_command_line(command_line_w, allocator) or_return
+			info.fields += {.Command_Args}
+		}
+	}
+	if .Environment in selection {
+		env_block := win32.GetEnvironmentStringsW()
+		info.environment = _parse_environment_block(env_block, allocator) or_return
+		info.fields += {.Environment}
+	}
+	if .Username in selection {
+		process_handle := win32.GetCurrentProcess()
+		info.username = _get_process_user(process_handle, allocator) or_return
+		info.fields += {.Username}
+	}
+	if .Working_Dir in selection {
+		// TODO(flysand): Implement this by reading PEB
+		err = .Mode_Not_Implemented
+		return
+	}
+	err = nil
+	return
+}
+
+@(private="package")
+_process_open :: proc(pid: int, flags: Process_Open_Flags) -> (process: Process, err: Error) {
+	// Note(flysand): The handle will be used for querying information so we
+	// take the necessary permissions right away.
+	dwDesiredAccess := win32.PROCESS_QUERY_LIMITED_INFORMATION | win32.SYNCHRONIZE
+	if .Mem_Read in flags {
+		dwDesiredAccess |= win32.PROCESS_VM_READ
+	}
+	if .Mem_Write in flags {
+		dwDesiredAccess |= win32.PROCESS_VM_WRITE
+	}
+	handle := win32.OpenProcess(
+		dwDesiredAccess,
+		false,
+		u32(pid),
+	)
+	if handle == win32.INVALID_HANDLE_VALUE {
+		err = _get_platform_error()
+	} else {
+		process = {pid = pid, handle = uintptr(handle)}
+	}
+	return
+}
+
+@(private="package")
+_Sys_Process_Attributes :: struct {}
+
+@(private="package")
+_process_start :: proc(desc: Process_Desc) -> (process: Process, err: Error) {
+	TEMP_ALLOCATOR_GUARD()
+	command_line   := _build_command_line(desc.command, temp_allocator())
+	command_line_w := win32_utf8_to_wstring(command_line, temp_allocator()) or_return
+	environment := desc.env
+	if desc.env == nil {
+		environment = environ(temp_allocator())
+	}
+	environment_block   := _build_environment_block(environment, temp_allocator())
+	environment_block_w := win32_utf8_to_utf16(environment_block, temp_allocator()) or_return
+	stderr_handle       := win32.GetStdHandle(win32.STD_ERROR_HANDLE)
+	stdout_handle       := win32.GetStdHandle(win32.STD_OUTPUT_HANDLE)
+	stdin_handle        := win32.GetStdHandle(win32.STD_INPUT_HANDLE)
+
+	if desc.stdout != nil {
+		stdout_handle = win32.HANDLE((^File_Impl)(desc.stdout.impl).fd)
+	}
+	if desc.stderr != nil {
+		stderr_handle = win32.HANDLE((^File_Impl)(desc.stderr.impl).fd)
+	}
+	if desc.stdin != nil {
+		stdin_handle = win32.HANDLE((^File_Impl)(desc.stderr.impl).fd)
+	}
+
+	working_dir_w := (win32_utf8_to_wstring(desc.working_dir, temp_allocator()) or_else nil) if len(desc.working_dir) > 0 else nil
+	process_info: win32.PROCESS_INFORMATION
+	ok := win32.CreateProcessW(
+		nil,
+		command_line_w,
+		nil,
+		nil,
+		true,
+		win32.CREATE_UNICODE_ENVIRONMENT|win32.NORMAL_PRIORITY_CLASS,
+		raw_data(environment_block_w),
+		working_dir_w,
+		&win32.STARTUPINFOW{
+			cb = size_of(win32.STARTUPINFOW),
+			hStdError  = stderr_handle,
+			hStdOutput = stdout_handle,
+			hStdInput  = stdin_handle,
+			dwFlags = win32.STARTF_USESTDHANDLES,
+		},
+		&process_info,
+	)
+	if !ok {
+		err = _get_platform_error()
+		return
+	}
+	process = {pid = int(process_info.dwProcessId), handle = uintptr(process_info.hProcess)}
+	return
+}
+
+@(private="package")
+_process_wait :: proc(process: Process, timeout: time.Duration) -> (process_state: Process_State, err: Error) {
+	handle := win32.HANDLE(process.handle)
+	timeout_ms := u32(timeout / time.Millisecond) if timeout >= 0 else win32.INFINITE
+
+	switch win32.WaitForSingleObject(handle, timeout_ms) {
+	case win32.WAIT_OBJECT_0:
+		exit_code: u32
+		if !win32.GetExitCodeProcess(handle, &exit_code) {
+			err =_get_platform_error()
+			return
+		}
+		time_created: win32.FILETIME
+		time_exited: win32.FILETIME
+		time_kernel: win32.FILETIME
+		time_user: win32.FILETIME
+		if !win32.GetProcessTimes(handle, &time_created, &time_exited, &time_kernel, &time_user) {
+			err = _get_platform_error()
+			return
+		}
+		process_state = {
+			exit_code   = int(exit_code),
+			exited      = true,
+			pid         = process.pid,
+			success     = true,
+			system_time = _filetime_to_duration(time_kernel),
+			user_time   = _filetime_to_duration(time_user),
+		}
+		return
+	case win32.WAIT_TIMEOUT:
+		err = General_Error.Timeout
+		return
+	case:
+		err = _get_platform_error()
+		return
+	}
+}
+
+@(private="package")
+_process_close :: proc(process: Process) -> Error {
+	if !win32.CloseHandle(win32.HANDLE(process.handle)) {
+		return _get_platform_error()
+	}
+	return nil
+}
+
+@(private="package")
+_process_kill :: proc(process: Process) -> Error {
+	// Note(flysand): This is different than what the task manager's "kill process"
+	// functionality does, as we don't try to send WM_CLOSE message first. This
+	// is quite a rough way to kill the process, which should be consistent with
+	// linux. The error code 9 is to mimic SIGKILL event.
+	if !win32.TerminateProcess(win32.HANDLE(process.handle), 9) {
+		return _get_platform_error()
+	}
+	return nil
+}
+
+_filetime_to_duration :: proc(filetime: win32.FILETIME) -> time.Duration {
+	ticks := u64(filetime.dwHighDateTime)<<32 | u64(filetime.dwLowDateTime)
+	return time.Duration(ticks * 100)
+}
+
+_process_entry_by_pid :: proc(pid: int) -> (entry: win32.PROCESSENTRY32W, err: Error) {
+	snap := win32.CreateToolhelp32Snapshot(win32.TH32CS_SNAPPROCESS, 0)
+	if snap == win32.INVALID_HANDLE_VALUE {
+		err = _get_platform_error()
+		return
+	}
+	defer win32.CloseHandle(snap)
+
+	entry = win32.PROCESSENTRY32W{dwSize = size_of(win32.PROCESSENTRY32W)}
+	status := win32.Process32FirstW(snap, &entry)
+	for status {
+		if u32(pid) == entry.th32ProcessID {
+			return
+		}
+		status = win32.Process32NextW(snap, &entry)
+	}
+	err = General_Error.Not_Exist
+	return
+}
+
+// Note(flysand): Not sure which way it's better to get the executable path:
+// via toolhelp snapshots or by reading other process' PEB memory. I have
+// a slight suspicion that if both exe path and command line are desired,
+// it's faster to just read both from PEB, but maybe the toolhelp snapshots
+// are just better...?
+@(private="package")
+_process_exe_by_pid :: proc(pid: int, allocator: runtime.Allocator) -> (exe_path: string, err: Error) {
+	snap := win32.CreateToolhelp32Snapshot(
+		win32.TH32CS_SNAPMODULE|win32.TH32CS_SNAPMODULE32,
+		u32(pid),
+	)
+	if snap == win32.INVALID_HANDLE_VALUE {
+		err =_get_platform_error()
+		return
+	}
+	defer win32.CloseHandle(snap)
+
+	entry := win32.MODULEENTRY32W { dwSize = size_of(win32.MODULEENTRY32W) }
+	status := win32.Module32FirstW(snap, &entry)
+	if !status {
+		err =_get_platform_error()
+		return
+	}
+	return win32_wstring_to_utf8(raw_data(entry.szExePath[:]), allocator)
+}
+
+_get_process_user :: proc(process_handle: win32.HANDLE, allocator: runtime.Allocator) -> (full_username: string, err: Error) {
+	TEMP_ALLOCATOR_GUARD()
+	token_handle: win32.HANDLE
+	if !win32.OpenProcessToken(process_handle, win32.TOKEN_QUERY, &token_handle) {
+		err = _get_platform_error()
+		return
+	}
+	token_user_size: u32
+	if !win32.GetTokenInformation(token_handle, .TokenUser, nil, 0, &token_user_size) {
+		// Note(flysand): Make sure the buffer too small error comes out, and not any other error
+		err = _get_platform_error()
+		if v, ok := is_platform_error(err); !ok || v != i32(win32.ERROR_INSUFFICIENT_BUFFER) {
+			return
+		}
+		err = nil
+	}
+	token_user := (^win32.TOKEN_USER)(raw_data(make([]u8, token_user_size, temp_allocator()) or_return))
+	if !win32.GetTokenInformation(token_handle, .TokenUser, token_user, token_user_size, &token_user_size) {
+		err = _get_platform_error()
+		return
+	}
+
+	sid_type: win32.SID_NAME_USE
+	username_w: [256]u16
+	domain_w:   [256]u16
+	username_chrs := u32(256)
+	domain_chrs   := u32(256)
+
+	if !win32.LookupAccountSidW(nil, token_user.User.Sid, &username_w[0], &username_chrs, &domain_w[0], &domain_chrs, &sid_type) {
+		err = _get_platform_error()
+		return
+	}
+	username := win32_utf16_to_utf8(username_w[:username_chrs], temp_allocator()) or_return
+	domain   := win32_utf16_to_utf8(domain_w[:domain_chrs], temp_allocator()) or_return
+	return strings.concatenate({domain, "\\", username}, allocator)
+}
+
+_parse_command_line :: proc(cmd_line_w: [^]u16, allocator: runtime.Allocator) -> (argv: []string, err: Error) {
+	argc: i32
+	argv_w := win32.CommandLineToArgvW(cmd_line_w, &argc)
+	if argv_w == nil {
+		return nil, _get_platform_error()
+	}
+	argv = make([]string, argc, allocator) or_return
+	defer if err != nil {
+		for arg in argv {
+			delete(arg, allocator)
+		}
+		delete(argv, allocator)
+	}
+	for arg_w, i in argv_w[:argc] {
+		argv[i] = win32_wstring_to_utf8(arg_w, allocator) or_return
+	}
+	return
+}
+
+_build_command_line :: proc(command: []string, allocator: runtime.Allocator) -> string {
+	_write_byte_n_times :: #force_inline proc(builder: ^strings.Builder, b: byte, n: int) {
+		for _ in 0 ..< n {
+			strings.write_byte(builder, b)
+		}
+	}
+	builder := strings.builder_make(allocator)
+	for arg, i in command {
+		if i != 0 {
+			strings.write_byte(&builder, ' ')
+		}
+		j := 0
+		strings.write_byte(&builder, '"')
+		for j < len(arg) {
+			backslashes := 0
+			for j < len(arg) && arg[j] == '\\' {
+				backslashes += 1
+				j += 1
+			}
+			if j == len(arg) {
+				_write_byte_n_times(&builder, '\\', 2*backslashes)
+				break
+			} else if arg[j] == '"' {
+				_write_byte_n_times(&builder, '\\', 2*backslashes+1)
+				strings.write_byte(&builder, '"')
+			} else {
+				_write_byte_n_times(&builder, '\\', backslashes)
+				strings.write_byte(&builder, arg[j])
+			}
+			j += 1
+		}
+		strings.write_byte(&builder, '"')
+	}
+	return strings.to_string(builder)
+}
+
+_parse_environment_block :: proc(block: [^]u16, allocator: runtime.Allocator) -> (envs: []string, err: Error) {
+	zt_count := 0
+	for idx := 0; true; {
+		if block[idx] == 0x0000 {
+			zt_count += 1
+			if block[idx+1] == 0x0000 {
+				zt_count += 1
+				break
+			}
+		}
+		idx += 1
+	}
+
+	// Note(flysand): Each string in the environment block is terminated
+	// by a NUL character. In addition, the environment block itself is
+	// terminated by a NUL character. So the number of strings in the
+	// environment block is the number of NUL character minus the
+	// block terminator.
+	env_count := zt_count - 1
+	envs = make([]string, env_count, allocator) or_return
+	defer if err != nil {
+		for env in envs {
+			delete(env, allocator)
+		}
+		delete(envs, allocator)
+	}
+
+	env_idx := 0
+	last_idx := 0
+	idx := 0
+	for block[idx] != 0x0000 {
+		for block[idx] != 0x0000 {
+			idx += 1
+		}
+		env_w := block[last_idx:idx]
+		envs[env_idx] = win32_utf16_to_utf8(env_w, allocator) or_return
+		env_idx += 1
+		idx += 1
+		last_idx = idx
+	}
+	return
+}
+
+_build_environment_block :: proc(environment: []string, allocator: runtime.Allocator) -> string {
+	builder := strings.builder_make(allocator)
+	loop: #reverse for kv, cur_idx in environment {
+		eq_idx := strings.index_byte(kv, '=')
+		assert(eq_idx >= 0, "Malformed environment string. Expected '=' to separate keys and values")
+		key := kv[:eq_idx]
+		for old_kv in environment[cur_idx+1:] {
+			old_key := old_kv[:strings.index_byte(old_kv, '=')]
+			if key == old_key {
+				continue loop
+			}
+		}
+		strings.write_string(&builder, kv)
+		strings.write_byte(&builder, 0)
+	}
+	// Note(flysand): In addition to the NUL-terminator for each string, the
+	// environment block itself is NUL-terminated.
+	strings.write_byte(&builder, 0)
+	return strings.to_string(builder)
+}

+ 41 - 7
core/os/os2/stat.odin

@@ -1,21 +1,34 @@
 package os2
 package os2
 
 
-import "core:time"
 import "base:runtime"
 import "base:runtime"
+import "core:path/filepath"
+import "core:strings"
+import "core:time"
 
 
 Fstat_Callback :: proc(f: ^File, allocator: runtime.Allocator) -> (File_Info, Error)
 Fstat_Callback :: proc(f: ^File, allocator: runtime.Allocator) -> (File_Info, Error)
 
 
 File_Info :: struct {
 File_Info :: struct {
 	fullpath:          string,
 	fullpath:          string,
 	name:              string,
 	name:              string,
-	size:              i64,
-	mode:              File_Mode,
-	is_directory:      bool,
+
+	inode:             u128, // might be zero if cannot be determined
+	size:              i64 `fmt:"M"`,
+	mode:              int `fmt:"o"`,
+	type:              File_Type,
+
 	creation_time:     time.Time,
 	creation_time:     time.Time,
 	modification_time: time.Time,
 	modification_time: time.Time,
 	access_time:       time.Time,
 	access_time:       time.Time,
 }
 }
 
 
+@(require_results)
+file_info_clone :: proc(fi: File_Info, allocator: runtime.Allocator) -> (cloned: File_Info, err: runtime.Allocator_Error) {
+	cloned = fi
+	cloned.fullpath = strings.clone(fi.fullpath) or_return
+	cloned.name = filepath.base(cloned.fullpath)
+	return
+}
+
 file_info_slice_delete :: proc(infos: []File_Info, allocator: runtime.Allocator) {
 file_info_slice_delete :: proc(infos: []File_Info, allocator: runtime.Allocator) {
 	for i := len(infos)-1; i >= 0; i -= 1 {
 	for i := len(infos)-1; i >= 0; i -= 1 {
 		file_info_delete(infos[i], allocator)
 		file_info_delete(infos[i], allocator)
@@ -29,10 +42,12 @@ file_info_delete :: proc(fi: File_Info, allocator: runtime.Allocator) {
 
 
 @(require_results)
 @(require_results)
 fstat :: proc(f: ^File, allocator: runtime.Allocator) -> (File_Info, Error) {
 fstat :: proc(f: ^File, allocator: runtime.Allocator) -> (File_Info, Error) {
-	if f != nil && f.user_fstat != nil {
-		return f->user_fstat(allocator)
+	if f == nil {
+		return {}, nil
+	} else if f.fstat != nil {
+		return f->fstat(allocator)
 	}
 	}
-	return _fstat(f, allocator)
+	return {}, .Invalid_Callback
 }
 }
 
 
 @(require_results)
 @(require_results)
@@ -41,6 +56,7 @@ stat :: proc(name: string, allocator: runtime.Allocator) -> (File_Info, Error) {
 }
 }
 
 
 lstat :: stat_do_not_follow_links
 lstat :: stat_do_not_follow_links
+
 @(require_results)
 @(require_results)
 stat_do_not_follow_links :: proc(name: string, allocator: runtime.Allocator) -> (File_Info, Error) {
 stat_do_not_follow_links :: proc(name: string, allocator: runtime.Allocator) -> (File_Info, Error) {
 	return _lstat(name, allocator)
 	return _lstat(name, allocator)
@@ -51,3 +67,21 @@ stat_do_not_follow_links :: proc(name: string, allocator: runtime.Allocator) ->
 same_file :: proc(fi1, fi2: File_Info) -> bool {
 same_file :: proc(fi1, fi2: File_Info) -> bool {
 	return _same_file(fi1, fi2)
 	return _same_file(fi1, fi2)
 }
 }
+
+
+last_write_time         :: modification_time
+last_write_time_by_name :: modification_time_by_path
+
+@(require_results)
+modification_time :: proc(f: ^File) -> (time.Time, Error) {
+	TEMP_ALLOCATOR_GUARD()
+	fi, err := fstat(f, temp_allocator())
+	return fi.modification_time, err
+}
+
+@(require_results)
+modification_time_by_path :: proc(path: string) -> (time.Time, Error) {
+	TEMP_ALLOCATOR_GUARD()
+	fi, err := stat(path, temp_allocator())
+	return fi.modification_time, err
+}

+ 24 - 12
core/os/os2/stat_linux.odin

@@ -7,31 +7,43 @@ import "core:sys/linux"
 import "core:path/filepath"
 import "core:path/filepath"
 
 
 _fstat :: proc(f: ^File, allocator: runtime.Allocator) -> (File_Info, Error) {
 _fstat :: proc(f: ^File, allocator: runtime.Allocator) -> (File_Info, Error) {
-	return _fstat_internal(f.impl.fd, allocator)
+	impl := (^File_Impl)(f.impl)
+	return _fstat_internal(impl.fd, allocator)
 }
 }
 
 
-_fstat_internal :: proc(fd: linux.Fd, allocator: runtime.Allocator) -> (File_Info, Error) {
+_fstat_internal :: proc(fd: linux.Fd, allocator: runtime.Allocator) -> (fi: File_Info, err: Error) {
 	s: linux.Stat
 	s: linux.Stat
 	errno := linux.fstat(fd, &s)
 	errno := linux.fstat(fd, &s)
 	if errno != .NONE {
 	if errno != .NONE {
 		return {}, _get_platform_error(errno)
 		return {}, _get_platform_error(errno)
 	}
 	}
+	type := File_Type.Regular
+	switch s.mode & linux.S_IFMT {
+	case linux.S_IFBLK:  type = .Block_Device
+	case linux.S_IFCHR:  type = .Character_Device
+	case linux.S_IFDIR:  type = .Directory
+	case linux.S_IFIFO:  type = .Named_Pipe
+	case linux.S_IFLNK:  type = .Symlink
+	case linux.S_IFREG:  type = .Regular
+	case linux.S_IFSOCK: type = .Socket
+	}
+	mode := int(0o7777 & transmute(u32)s.mode)
 
 
 	// TODO: As of Linux 4.11, the new statx syscall can retrieve creation_time
 	// TODO: As of Linux 4.11, the new statx syscall can retrieve creation_time
-	fi := File_Info {
-		fullpath = _get_full_path(fd, allocator),
-		name = "",
-		size = i64(s.size),
-		mode = 0,
-		is_directory = linux.S_ISDIR(s.mode),
+	fi = File_Info {
+		fullpath          = _get_full_path(fd, allocator) or_return,
+		name              = "",
+		inode             = u128(u64(s.ino)),
+		size              = i64(s.size),
+		mode              = mode,
+		type              = type,
 		modification_time = time.Time {i64(s.mtime.time_sec) * i64(time.Second) + i64(s.mtime.time_nsec)},
 		modification_time = time.Time {i64(s.mtime.time_sec) * i64(time.Second) + i64(s.mtime.time_nsec)},
-		access_time = time.Time {i64(s.atime.time_sec) * i64(time.Second) + i64(s.atime.time_nsec)},
-		creation_time = time.Time{i64(s.ctime.time_sec) * i64(time.Second) + i64(s.ctime.time_nsec)}, // regular stat does not provide this
+		access_time       = time.Time {i64(s.atime.time_sec) * i64(time.Second) + i64(s.atime.time_nsec)},
+		creation_time     = time.Time{i64(s.ctime.time_sec) * i64(time.Second) + i64(s.ctime.time_nsec)}, // regular stat does not provide this
 	}
 	}
 	fi.creation_time = fi.modification_time
 	fi.creation_time = fi.modification_time
-
 	fi.name = filepath.base(fi.fullpath)
 	fi.name = filepath.base(fi.fullpath)
-	return fi, nil
+	return
 }
 }
 
 
 // NOTE: _stat and _lstat are using _fstat to avoid a race condition when populating fullpath
 // NOTE: _stat and _lstat are using _fstat to avoid a race condition when populating fullpath

+ 37 - 61
core/os/os2/stat_windows.odin

@@ -7,7 +7,7 @@ import "core:strings"
 import win32 "core:sys/windows"
 import win32 "core:sys/windows"
 
 
 _fstat :: proc(f: ^File, allocator: runtime.Allocator) -> (File_Info, Error) {
 _fstat :: proc(f: ^File, allocator: runtime.Allocator) -> (File_Info, Error) {
-	if f == nil || f.impl.fd == nil {
+	if f == nil || (^File_Impl)(f.impl).fd == nil {
 		return {}, nil
 		return {}, nil
 	}
 	}
 
 
@@ -19,28 +19,29 @@ _fstat :: proc(f: ^File, allocator: runtime.Allocator) -> (File_Info, Error) {
 	h := _handle(f)
 	h := _handle(f)
 	switch win32.GetFileType(h) {
 	switch win32.GetFileType(h) {
 	case win32.FILE_TYPE_PIPE, win32.FILE_TYPE_CHAR:
 	case win32.FILE_TYPE_PIPE, win32.FILE_TYPE_CHAR:
-		fi: File_Info
-		fi.fullpath = path
-		fi.name = basename(path)
-		fi.mode |= file_type_mode(h)
+		fi := File_Info {
+			fullpath = path,
+			name = basename(path),
+			type = file_type(h),
+		}
 		return fi, nil
 		return fi, nil
 	}
 	}
 
 
 	return _file_info_from_get_file_information_by_handle(path, h, allocator)
 	return _file_info_from_get_file_information_by_handle(path, h, allocator)
 }
 }
+
 _stat :: proc(name: string, allocator: runtime.Allocator) -> (File_Info, Error) {
 _stat :: proc(name: string, allocator: runtime.Allocator) -> (File_Info, Error) {
 	return internal_stat(name, win32.FILE_FLAG_BACKUP_SEMANTICS, allocator)
 	return internal_stat(name, win32.FILE_FLAG_BACKUP_SEMANTICS, allocator)
 }
 }
+
 _lstat :: proc(name: string, allocator: runtime.Allocator) -> (File_Info, Error) {
 _lstat :: proc(name: string, allocator: runtime.Allocator) -> (File_Info, Error) {
 	return internal_stat(name, win32.FILE_FLAG_BACKUP_SEMANTICS|win32.FILE_FLAG_OPEN_REPARSE_POINT, allocator)
 	return internal_stat(name, win32.FILE_FLAG_BACKUP_SEMANTICS|win32.FILE_FLAG_OPEN_REPARSE_POINT, allocator)
 }
 }
+
 _same_file :: proc(fi1, fi2: File_Info) -> bool {
 _same_file :: proc(fi1, fi2: File_Info) -> bool {
 	return fi1.fullpath == fi2.fullpath
 	return fi1.fullpath == fi2.fullpath
 }
 }
 
 
-
-
-
 full_path_from_name :: proc(name: string, allocator: runtime.Allocator) -> (path: string, err: Error) {
 full_path_from_name :: proc(name: string, allocator: runtime.Allocator) -> (path: string, err: Error) {
 	name := name
 	name := name
 	if name == "" {
 	if name == "" {
@@ -48,7 +49,7 @@ full_path_from_name :: proc(name: string, allocator: runtime.Allocator) -> (path
 	}
 	}
 	TEMP_ALLOCATOR_GUARD()
 	TEMP_ALLOCATOR_GUARD()
 
 
-	p := win32.utf8_to_utf16(name, temp_allocator())
+	p := win32_utf8_to_utf16(name, temp_allocator()) or_return
 
 
 	n := win32.GetFullPathNameW(raw_data(p), 0, nil, nil)
 	n := win32.GetFullPathNameW(raw_data(p), 0, nil, nil)
 	if n == 0 {
 	if n == 0 {
@@ -59,16 +60,16 @@ full_path_from_name :: proc(name: string, allocator: runtime.Allocator) -> (path
 	if n == 0 {
 	if n == 0 {
 		return "", _get_platform_error()
 		return "", _get_platform_error()
 	}
 	}
-	return win32.utf16_to_utf8(buf[:n], allocator)
+	return win32_utf16_to_utf8(buf[:n], allocator)
 }
 }
 
 
-
 internal_stat :: proc(name: string, create_file_attributes: u32, allocator: runtime.Allocator) -> (fi: File_Info, e: Error) {
 internal_stat :: proc(name: string, create_file_attributes: u32, allocator: runtime.Allocator) -> (fi: File_Info, e: Error) {
 	if len(name) == 0 {
 	if len(name) == 0 {
 		return {}, .Not_Exist
 		return {}, .Not_Exist
 	}
 	}
+	TEMP_ALLOCATOR_GUARD()
 
 
-	wname := _fix_long_path(name)
+	wname := _fix_long_path(name, temp_allocator()) or_return
 	fa: win32.WIN32_FILE_ATTRIBUTE_DATA
 	fa: win32.WIN32_FILE_ATTRIBUTE_DATA
 	ok := win32.GetFileAttributesExW(wname, win32.GetFileExInfoStandard, &fa)
 	ok := win32.GetFileAttributesExW(wname, win32.GetFileExInfoStandard, &fa)
 	if ok && fa.dwFileAttributes & win32.FILE_ATTRIBUTE_REPARSE_POINT == 0 {
 	if ok && fa.dwFileAttributes & win32.FILE_ATTRIBUTE_REPARSE_POINT == 0 {
@@ -99,7 +100,6 @@ internal_stat :: proc(name: string, create_file_attributes: u32, allocator: runt
 	return _file_info_from_get_file_information_by_handle(name, h, allocator)
 	return _file_info_from_get_file_information_by_handle(name, h, allocator)
 }
 }
 
 
-
 _cleanpath_strip_prefix :: proc(buf: []u16) -> []u16 {
 _cleanpath_strip_prefix :: proc(buf: []u16) -> []u16 {
 	buf := buf
 	buf := buf
 	N := 0
 	N := 0
@@ -120,9 +120,8 @@ _cleanpath_strip_prefix :: proc(buf: []u16) -> []u16 {
 	return buf
 	return buf
 }
 }
 
 
-
 _cleanpath_from_handle :: proc(f: ^File, allocator: runtime.Allocator) -> (string, Error) {
 _cleanpath_from_handle :: proc(f: ^File, allocator: runtime.Allocator) -> (string, Error) {
-	if f == nil || f.impl.fd == nil {
+	if f == nil {
 		return "", nil
 		return "", nil
 	}
 	}
 	h := _handle(f)
 	h := _handle(f)
@@ -138,7 +137,7 @@ _cleanpath_from_handle :: proc(f: ^File, allocator: runtime.Allocator) -> (strin
 }
 }
 
 
 _cleanpath_from_handle_u16 :: proc(f: ^File) -> ([]u16, Error) {
 _cleanpath_from_handle_u16 :: proc(f: ^File) -> ([]u16, Error) {
-	if f == nil || f.impl.fd == nil {
+	if f  == nil {
 		return nil, nil
 		return nil, nil
 	}
 	}
 	h := _handle(f)
 	h := _handle(f)
@@ -156,10 +155,9 @@ _cleanpath_from_handle_u16 :: proc(f: ^File) -> ([]u16, Error) {
 _cleanpath_from_buf :: proc(buf: []u16, allocator: runtime.Allocator) -> (string, runtime.Allocator_Error) {
 _cleanpath_from_buf :: proc(buf: []u16, allocator: runtime.Allocator) -> (string, runtime.Allocator_Error) {
 	buf := buf
 	buf := buf
 	buf = _cleanpath_strip_prefix(buf)
 	buf = _cleanpath_strip_prefix(buf)
-	return win32.utf16_to_utf8(buf, allocator)
+	return win32_utf16_to_utf8(buf, allocator)
 }
 }
 
 
-
 basename :: proc(name: string) -> (base: string) {
 basename :: proc(name: string) -> (base: string) {
 	name := name
 	name := name
 	if len(name) > 3 && name[:3] == `\\?` {
 	if len(name) > 3 && name[:3] == `\\?` {
@@ -185,83 +183,67 @@ basename :: proc(name: string) -> (base: string) {
 	return name
 	return name
 }
 }
 
 
-
-file_type_mode :: proc(h: win32.HANDLE) -> File_Mode {
+file_type :: proc(h: win32.HANDLE) -> File_Type {
 	switch win32.GetFileType(h) {
 	switch win32.GetFileType(h) {
-	case win32.FILE_TYPE_PIPE:
-		return File_Mode_Named_Pipe
-	case win32.FILE_TYPE_CHAR:
-		return File_Mode_Device | File_Mode_Char_Device
+	case win32.FILE_TYPE_PIPE: return .Named_Pipe
+	case win32.FILE_TYPE_CHAR: return .Character_Device
+	case win32.FILE_TYPE_DISK: return .Regular
 	}
 	}
-	return 0
+	return .Undetermined
 }
 }
 
 
-
-
-_file_mode_from_file_attributes :: proc(file_attributes: win32.DWORD, h: win32.HANDLE, ReparseTag: win32.DWORD) -> (mode: File_Mode) {
+_file_type_mode_from_file_attributes :: proc(file_attributes: win32.DWORD, h: win32.HANDLE, ReparseTag: win32.DWORD) -> (type: File_Type, mode: int) {
 	if file_attributes & win32.FILE_ATTRIBUTE_READONLY != 0 {
 	if file_attributes & win32.FILE_ATTRIBUTE_READONLY != 0 {
 		mode |= 0o444
 		mode |= 0o444
 	} else {
 	} else {
 		mode |= 0o666
 		mode |= 0o666
 	}
 	}
-
 	is_sym := false
 	is_sym := false
 	if file_attributes & win32.FILE_ATTRIBUTE_REPARSE_POINT == 0 {
 	if file_attributes & win32.FILE_ATTRIBUTE_REPARSE_POINT == 0 {
 		is_sym = false
 		is_sym = false
 	} else {
 	} else {
 		is_sym = ReparseTag == win32.IO_REPARSE_TAG_SYMLINK || ReparseTag == win32.IO_REPARSE_TAG_MOUNT_POINT
 		is_sym = ReparseTag == win32.IO_REPARSE_TAG_SYMLINK || ReparseTag == win32.IO_REPARSE_TAG_MOUNT_POINT
 	}
 	}
-
 	if is_sym {
 	if is_sym {
-		mode |= File_Mode_Sym_Link
+		type = .Symlink
 	} else {
 	} else {
 		if file_attributes & win32.FILE_ATTRIBUTE_DIRECTORY != 0 {
 		if file_attributes & win32.FILE_ATTRIBUTE_DIRECTORY != 0 {
-			mode |= 0o111 | File_Mode_Dir
+			type = .Directory
+			mode |= 0o111
 		}
 		}
-
 		if h != nil {
 		if h != nil {
-			mode |= file_type_mode(h)
+			type = file_type(h)
 		}
 		}
 	}
 	}
-
 	return
 	return
 }
 }
 
 
-
 _file_info_from_win32_file_attribute_data :: proc(d: ^win32.WIN32_FILE_ATTRIBUTE_DATA, name: string, allocator: runtime.Allocator) -> (fi: File_Info, e: Error) {
 _file_info_from_win32_file_attribute_data :: proc(d: ^win32.WIN32_FILE_ATTRIBUTE_DATA, name: string, allocator: runtime.Allocator) -> (fi: File_Info, e: Error) {
 	fi.size = i64(d.nFileSizeHigh)<<32 + i64(d.nFileSizeLow)
 	fi.size = i64(d.nFileSizeHigh)<<32 + i64(d.nFileSizeLow)
-
-	fi.mode |= _file_mode_from_file_attributes(d.dwFileAttributes, nil, 0)
-	fi.is_directory = fi.mode & File_Mode_Dir != 0
-
+	type, mode := _file_type_mode_from_file_attributes(d.dwFileAttributes, nil, 0)
+	fi.type = type
+	fi.mode |= mode
 	fi.creation_time     = time.unix(0, win32.FILETIME_as_unix_nanoseconds(d.ftCreationTime))
 	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.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))
 	fi.access_time       = time.unix(0, win32.FILETIME_as_unix_nanoseconds(d.ftLastAccessTime))
-
 	fi.fullpath, e = full_path_from_name(name, allocator)
 	fi.fullpath, e = full_path_from_name(name, allocator)
 	fi.name = basename(fi.fullpath)
 	fi.name = basename(fi.fullpath)
-
 	return
 	return
 }
 }
 
 
-
 _file_info_from_win32_find_data :: proc(d: ^win32.WIN32_FIND_DATAW, name: string, allocator: runtime.Allocator) -> (fi: File_Info, e: Error) {
 _file_info_from_win32_find_data :: proc(d: ^win32.WIN32_FIND_DATAW, name: string, allocator: runtime.Allocator) -> (fi: File_Info, e: Error) {
 	fi.size = i64(d.nFileSizeHigh)<<32 + i64(d.nFileSizeLow)
 	fi.size = i64(d.nFileSizeHigh)<<32 + i64(d.nFileSizeLow)
-
-	fi.mode |= _file_mode_from_file_attributes(d.dwFileAttributes, nil, 0)
-	fi.is_directory = fi.mode & File_Mode_Dir != 0
-
+	type, mode := _file_type_mode_from_file_attributes(d.dwFileAttributes, nil, 0)
+	fi.type = type
+	fi.mode |= mode
 	fi.creation_time     = time.unix(0, win32.FILETIME_as_unix_nanoseconds(d.ftCreationTime))
 	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.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))
 	fi.access_time       = time.unix(0, win32.FILETIME_as_unix_nanoseconds(d.ftLastAccessTime))
-
 	fi.fullpath, e = full_path_from_name(name, allocator)
 	fi.fullpath, e = full_path_from_name(name, allocator)
 	fi.name = basename(fi.fullpath)
 	fi.name = basename(fi.fullpath)
-
 	return
 	return
 }
 }
 
 
-
 _file_info_from_get_file_information_by_handle :: proc(path: string, h: win32.HANDLE, allocator: runtime.Allocator) -> (File_Info, Error) {
 _file_info_from_get_file_information_by_handle :: proc(path: string, h: win32.HANDLE, allocator: runtime.Allocator) -> (File_Info, Error) {
 	d: win32.BY_HANDLE_FILE_INFORMATION
 	d: win32.BY_HANDLE_FILE_INFORMATION
 	if !win32.GetFileInformationByHandle(h, &d) {
 	if !win32.GetFileInformationByHandle(h, &d) {
@@ -278,25 +260,20 @@ _file_info_from_get_file_information_by_handle :: proc(path: string, h: win32.HA
 		// Indicate this is a symlink on FAT file systems
 		// Indicate this is a symlink on FAT file systems
 		ti.ReparseTag = 0
 		ti.ReparseTag = 0
 	}
 	}
-
 	fi: File_Info
 	fi: File_Info
-
 	fi.fullpath = path
 	fi.fullpath = path
 	fi.name = basename(path)
 	fi.name = basename(path)
-	fi.size = i64(d.nFileSizeHigh)<<32 + i64(d.nFileSizeLow)
-
-	fi.mode |= _file_mode_from_file_attributes(ti.FileAttributes, h, ti.ReparseTag)
-	fi.is_directory = fi.mode & File_Mode_Dir != 0
-
+	fi.inode = u128(u64(d.nFileIndexHigh)<<32 + u64(d.nFileIndexLow))
+	fi.size  = i64(d.nFileSizeHigh)<<32  + i64(d.nFileSizeLow)
+	type, mode := _file_type_mode_from_file_attributes(d.dwFileAttributes, nil, 0)
+	fi.type = type
+	fi.mode |= mode
 	fi.creation_time     = time.unix(0, win32.FILETIME_as_unix_nanoseconds(d.ftCreationTime))
 	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.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))
 	fi.access_time       = time.unix(0, win32.FILETIME_as_unix_nanoseconds(d.ftLastAccessTime))
-
 	return fi, nil
 	return fi, nil
 }
 }
 
 
-
-
 reserved_names := [?]string{
 reserved_names := [?]string{
 	"CON", "PRN", "AUX", "NUL",
 	"CON", "PRN", "AUX", "NUL",
 	"COM1", "COM2", "COM3", "COM4", "COM5", "COM6", "COM7", "COM8", "COM9",
 	"COM1", "COM2", "COM3", "COM4", "COM5", "COM6", "COM7", "COM8", "COM9",
@@ -357,7 +334,6 @@ _volume_name_len :: proc(path: string) -> int {
 	return 0
 	return 0
 }
 }
 
 
-
 _is_abs :: proc(path: string) -> bool {
 _is_abs :: proc(path: string) -> bool {
 	if _is_reserved_name(path) {
 	if _is_reserved_name(path) {
 		return true
 		return true

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

@@ -26,7 +26,7 @@ create_temp_file :: proc(dir, pattern: string) -> (f: ^File, err: Error) {
 	attempts := 0
 	attempts := 0
 	for {
 	for {
 		name := concatenate_strings_from_buffer(name_buf[:], prefix, random_string(rand_buf[:]), suffix)
 		name := concatenate_strings_from_buffer(name_buf[:], prefix, random_string(rand_buf[:]), suffix)
-		f, err = open(name, {.Read, .Write, .Create, .Excl}, File_Mode(0o666))
+		f, err = open(name, {.Read, .Write, .Create, .Excl}, 0o666)
 		if err == .Exist {
 		if err == .Exist {
 			close(f)
 			close(f)
 			attempts += 1
 			attempts += 1

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

@@ -19,5 +19,5 @@ _temp_dir :: proc(allocator: runtime.Allocator) -> (string, runtime.Allocator_Er
 	} else if n > 0 && b[n-1] == '\\' {
 	} else if n > 0 && b[n-1] == '\\' {
 		n -= 1
 		n -= 1
 	}
 	}
-	return win32.utf16_to_utf8(b[:n], allocator)
+	return win32_utf16_to_utf8(b[:n], allocator)
 }
 }

+ 2 - 2
core/os/os_freestanding.odin

@@ -1,4 +1,4 @@
-//+freestanding
+//+build freestanding
 package os
 package os
 
 
-#panic("package os does not support a freestanding target")
+#panic("package os does not support a freestanding target")

+ 15 - 15
core/reflect/reflect.odin

@@ -143,7 +143,7 @@ when !ODIN_NO_RTTI {
 @(require_results)
 @(require_results)
 any_base :: proc(v: any) -> any {
 any_base :: proc(v: any) -> any {
 	v := v
 	v := v
-	if v != nil {
+	if v.id != nil {
 		v.id = typeid_base(v.id)
 		v.id = typeid_base(v.id)
 	}
 	}
 	return v
 	return v
@@ -151,7 +151,7 @@ any_base :: proc(v: any) -> any {
 @(require_results)
 @(require_results)
 any_core :: proc(v: any) -> any {
 any_core :: proc(v: any) -> any {
 	v := v
 	v := v
-	if v != nil {
+	if v.id != nil {
 		v.id = typeid_core(v.id)
 		v.id = typeid_core(v.id)
 	}
 	}
 	return v
 	return v
@@ -391,7 +391,7 @@ Struct_Field :: struct {
 struct_field_at :: proc(T: typeid, i: int) -> (field: Struct_Field) {
 struct_field_at :: proc(T: typeid, i: int) -> (field: Struct_Field) {
 	ti := runtime.type_info_base(type_info_of(T))
 	ti := runtime.type_info_base(type_info_of(T))
 	if s, ok := ti.variant.(runtime.Type_Info_Struct); ok {
 	if s, ok := ti.variant.(runtime.Type_Info_Struct); ok {
-		if 0 <= i && i < len(s.names) {
+		if 0 <= i && i < int(s.field_count) {
 			field.name     = s.names[i]
 			field.name     = s.names[i]
 			field.type     = s.types[i]
 			field.type     = s.types[i]
 			field.tag      = Struct_Tag(s.tags[i])
 			field.tag      = Struct_Tag(s.tags[i])
@@ -406,7 +406,7 @@ struct_field_at :: proc(T: typeid, i: int) -> (field: Struct_Field) {
 struct_field_by_name :: proc(T: typeid, name: string) -> (field: Struct_Field) {
 struct_field_by_name :: proc(T: typeid, name: string) -> (field: Struct_Field) {
 	ti := runtime.type_info_base(type_info_of(T))
 	ti := runtime.type_info_base(type_info_of(T))
 	if s, ok := ti.variant.(runtime.Type_Info_Struct); ok {
 	if s, ok := ti.variant.(runtime.Type_Info_Struct); ok {
-		for fname, i in s.names {
+		for fname, i in s.names[:s.field_count] {
 			if fname == name {
 			if fname == name {
 				field.name     = s.names[i]
 				field.name     = s.names[i]
 				field.type     = s.types[i]
 				field.type     = s.types[i]
@@ -427,7 +427,7 @@ struct_field_value_by_name :: proc(a: any, field: string, allow_using := false)
 	ti := runtime.type_info_base(type_info_of(a.id))
 	ti := runtime.type_info_base(type_info_of(a.id))
 
 
 	if s, ok := ti.variant.(runtime.Type_Info_Struct); ok {
 	if s, ok := ti.variant.(runtime.Type_Info_Struct); ok {
-		for name, i in s.names {
+		for name, i in s.names[:s.field_count] {
 			if name == field {
 			if name == field {
 				return any{
 				return any{
 					rawptr(uintptr(a.data) + s.offsets[i]),
 					rawptr(uintptr(a.data) + s.offsets[i]),
@@ -463,7 +463,7 @@ struct_field_value :: proc(a: any, field: Struct_Field) -> any {
 struct_field_names :: proc(T: typeid) -> []string {
 struct_field_names :: proc(T: typeid) -> []string {
 	ti := runtime.type_info_base(type_info_of(T))
 	ti := runtime.type_info_base(type_info_of(T))
 	if s, ok := ti.variant.(runtime.Type_Info_Struct); ok {
 	if s, ok := ti.variant.(runtime.Type_Info_Struct); ok {
-		return s.names
+		return s.names[:s.field_count]
 	}
 	}
 	return nil
 	return nil
 }
 }
@@ -472,7 +472,7 @@ struct_field_names :: proc(T: typeid) -> []string {
 struct_field_types :: proc(T: typeid) -> []^Type_Info {
 struct_field_types :: proc(T: typeid) -> []^Type_Info {
 	ti := runtime.type_info_base(type_info_of(T))
 	ti := runtime.type_info_base(type_info_of(T))
 	if s, ok := ti.variant.(runtime.Type_Info_Struct); ok {
 	if s, ok := ti.variant.(runtime.Type_Info_Struct); ok {
-		return s.types
+		return s.types[:s.field_count]
 	}
 	}
 	return nil
 	return nil
 }
 }
@@ -482,7 +482,7 @@ struct_field_types :: proc(T: typeid) -> []^Type_Info {
 struct_field_tags :: proc(T: typeid) -> []Struct_Tag {
 struct_field_tags :: proc(T: typeid) -> []Struct_Tag {
 	ti := runtime.type_info_base(type_info_of(T))
 	ti := runtime.type_info_base(type_info_of(T))
 	if s, ok := ti.variant.(runtime.Type_Info_Struct); ok {
 	if s, ok := ti.variant.(runtime.Type_Info_Struct); ok {
-		return transmute([]Struct_Tag)s.tags
+		return transmute([]Struct_Tag)s.tags[:s.field_count]
 	}
 	}
 	return nil
 	return nil
 }
 }
@@ -491,7 +491,7 @@ struct_field_tags :: proc(T: typeid) -> []Struct_Tag {
 struct_field_offsets :: proc(T: typeid) -> []uintptr {
 struct_field_offsets :: proc(T: typeid) -> []uintptr {
 	ti := runtime.type_info_base(type_info_of(T))
 	ti := runtime.type_info_base(type_info_of(T))
 	if s, ok := ti.variant.(runtime.Type_Info_Struct); ok {
 	if s, ok := ti.variant.(runtime.Type_Info_Struct); ok {
-		return s.offsets
+		return s.offsets[:s.field_count]
 	}
 	}
 	return nil
 	return nil
 }
 }
@@ -501,11 +501,11 @@ struct_fields_zipped :: proc(T: typeid) -> (fields: #soa[]Struct_Field) {
 	ti := runtime.type_info_base(type_info_of(T))
 	ti := runtime.type_info_base(type_info_of(T))
 	if s, ok := ti.variant.(runtime.Type_Info_Struct); ok {
 	if s, ok := ti.variant.(runtime.Type_Info_Struct); ok {
 		return soa_zip(
 		return soa_zip(
-			name     = s.names,
-			type     = s.types,
-			tag      = transmute([]Struct_Tag)s.tags,
-			offset   = s.offsets,
-			is_using = s.usings,
+			name     = s.names[:s.field_count],
+			type     = s.types[:s.field_count],
+			tag      = ([^]Struct_Tag)(s.tags)[:s.field_count],
+			offset   = s.offsets[:s.field_count],
+			is_using = s.usings[:s.field_count],
 		)
 		)
 	}
 	}
 	return nil
 	return nil
@@ -1569,7 +1569,7 @@ equal :: proc(a, b: any, including_indirect_array_recursion := false, recursion_
 		if v.equal != nil {
 		if v.equal != nil {
 			return v.equal(a.data, b.data)
 			return v.equal(a.data, b.data)
 		} else {
 		} else {
-			for offset, i in v.offsets {
+			for offset, i in v.offsets[:v.field_count] {
 				x := rawptr(uintptr(a.data) + offset)
 				x := rawptr(uintptr(a.data) + offset)
 				y := rawptr(uintptr(b.data) + offset)
 				y := rawptr(uintptr(b.data) + offset)
 				id := v.types[i].id
 				id := v.types[i].id

+ 14 - 15
core/reflect/types.odin

@@ -115,16 +115,14 @@ are_types_identical :: proc(a, b: ^Type_Info) -> bool {
 	case Type_Info_Struct:
 	case Type_Info_Struct:
 		y := b.variant.(Type_Info_Struct) or_return
 		y := b.variant.(Type_Info_Struct) or_return
 		switch {
 		switch {
-		case len(x.types)    != len(y.types),
-		     x.is_packed     != y.is_packed,
-		     x.is_raw_union  != y.is_raw_union,
-		     x.custom_align  != y.custom_align,
+		case x.field_count   != y.field_count,
+		     x.flags         != y.flags,
 		     x.soa_kind      != y.soa_kind,
 		     x.soa_kind      != y.soa_kind,
 		     x.soa_base_type != y.soa_base_type,
 		     x.soa_base_type != y.soa_base_type,
 		     x.soa_len       != y.soa_len:
 		     x.soa_len       != y.soa_len:
 			return false
 			return false
 		}
 		}
-		for _, i in x.types {
+		for i in 0..<x.field_count {
 			xn, yn := x.names[i], y.names[i]
 			xn, yn := x.names[i], y.names[i]
 			xt, yt := x.types[i], y.types[i]
 			xt, yt := x.types[i], y.types[i]
 			xl, yl := x.tags[i],  y.tags[i]
 			xl, yl := x.tags[i],  y.tags[i]
@@ -179,8 +177,8 @@ are_types_identical :: proc(a, b: ^Type_Info) -> bool {
 	case Type_Info_Bit_Field:
 	case Type_Info_Bit_Field:
 		y := b.variant.(Type_Info_Bit_Field) or_return
 		y := b.variant.(Type_Info_Bit_Field) or_return
 		if !are_types_identical(x.backing_type, y.backing_type) { return false }
 		if !are_types_identical(x.backing_type, y.backing_type) { return false }
-		if len(x.names) != len(y.names) { return false }
-		for _, i in x.names {
+		if x.field_count != y.field_count { return false }
+		for _, i in x.names[:x.field_count] {
 			if x.names[i] != y.names[i] {
 			if x.names[i] != y.names[i] {
 				return false
 				return false
 			}
 			}
@@ -368,13 +366,13 @@ is_tuple :: proc(info: ^Type_Info) -> bool {
 is_struct :: proc(info: ^Type_Info) -> bool {
 is_struct :: proc(info: ^Type_Info) -> bool {
 	if info == nil { return false }
 	if info == nil { return false }
 	s, ok := type_info_base(info).variant.(Type_Info_Struct)
 	s, ok := type_info_base(info).variant.(Type_Info_Struct)
-	return ok && !s.is_raw_union
+	return ok && .raw_union not_in s.flags
 }
 }
 @(require_results)
 @(require_results)
 is_raw_union :: proc(info: ^Type_Info) -> bool {
 is_raw_union :: proc(info: ^Type_Info) -> bool {
 	if info == nil { return false }
 	if info == nil { return false }
 	s, ok := type_info_base(info).variant.(Type_Info_Struct)
 	s, ok := type_info_base(info).variant.(Type_Info_Struct)
-	return ok && s.is_raw_union
+	return ok && .raw_union in s.flags
 }
 }
 @(require_results)
 @(require_results)
 is_union :: proc(info: ^Type_Info) -> bool {
 is_union :: proc(info: ^Type_Info) -> bool {
@@ -495,7 +493,7 @@ write_type_builder :: proc(buf: ^strings.Builder, ti: ^Type_Info) -> int {
 	n, _ := write_type_writer(strings.to_writer(buf), ti)
 	n, _ := write_type_writer(strings.to_writer(buf), ti)
 	return n
 	return n
 }
 }
-write_type_writer :: proc(w: io.Writer, ti: ^Type_Info, n_written: ^int = nil) -> (n: int, err: io.Error) {
+write_type_writer :: #force_no_inline proc(w: io.Writer, ti: ^Type_Info, n_written: ^int = nil) -> (n: int, err: io.Error) {
 	defer if n_written != nil {
 	defer if n_written != nil {
 		n_written^ += n
 		n_written^ += n
 	}
 	}
@@ -656,15 +654,16 @@ write_type_writer :: proc(w: io.Writer, ti: ^Type_Info, n_written: ^int = nil) -
 		}
 		}
 
 
 		io.write_string(w, "struct ", &n) or_return
 		io.write_string(w, "struct ", &n) or_return
-		if info.is_packed    { io.write_string(w, "#packed ",    &n) or_return }
-		if info.is_raw_union { io.write_string(w, "#raw_union ", &n) or_return }
-		if info.custom_align {
+		if .packed    in info.flags { io.write_string(w, "#packed ",    &n) or_return }
+		if .raw_union in info.flags { io.write_string(w, "#raw_union ", &n) or_return }
+		if .no_copy   in info.flags { io.write_string(w, "#no_copy ", &n) or_return }
+		if .align in info.flags {
 			io.write_string(w, "#align(",      &n) or_return
 			io.write_string(w, "#align(",      &n) or_return
 			io.write_i64(w, i64(ti.align), 10, &n) or_return
 			io.write_i64(w, i64(ti.align), 10, &n) or_return
 			io.write_string(w, ") ",           &n) or_return
 			io.write_string(w, ") ",           &n) or_return
 		}
 		}
 		io.write_byte(w, '{', &n) or_return
 		io.write_byte(w, '{', &n) or_return
-		for name, i in info.names {
+		for name, i in info.names[:info.field_count] {
 			if i > 0 { io.write_string(w, ", ", &n) or_return }
 			if i > 0 { io.write_string(w, ", ", &n) or_return }
 			io.write_string(w, name,     &n) or_return
 			io.write_string(w, name,     &n) or_return
 			io.write_string(w, ": ",     &n) or_return
 			io.write_string(w, ": ",     &n) or_return
@@ -722,7 +721,7 @@ write_type_writer :: proc(w: io.Writer, ti: ^Type_Info, n_written: ^int = nil) -
 		io.write_string(w, "bit_field ", &n) or_return
 		io.write_string(w, "bit_field ", &n) or_return
 		write_type(w, info.backing_type, &n) or_return
 		write_type(w, info.backing_type, &n) or_return
 		io.write_string(w, " {",         &n) or_return
 		io.write_string(w, " {",         &n) or_return
-		for name, i in info.names {
+		for name, i in info.names[:info.field_count] {
 			if i > 0 { io.write_string(w, ", ", &n) or_return }
 			if i > 0 { io.write_string(w, ", ", &n) or_return }
 			io.write_string(w, name,     &n) or_return
 			io.write_string(w, name,     &n) or_return
 			io.write_string(w, ": ",     &n) or_return
 			io.write_string(w, ": ",     &n) or_return

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

@@ -2,33 +2,33 @@
 package simd_x86
 package simd_x86
 
 
 @(require_results, enable_target_feature = "aes")
 @(require_results, enable_target_feature = "aes")
-_mm_aesdec :: #force_inline proc "c" (a, b: __m128i) -> __m128i {
+_mm_aesdec_si128 :: #force_inline proc "c" (a, b: __m128i) -> __m128i {
 	return aesdec(a, b)
 	return aesdec(a, b)
 }
 }
 
 
 @(require_results, enable_target_feature = "aes")
 @(require_results, enable_target_feature = "aes")
-_mm_aesdeclast :: #force_inline proc "c" (a, b: __m128i) -> __m128i {
+_mm_aesdeclast_si128 :: #force_inline proc "c" (a, b: __m128i) -> __m128i {
 	return aesdeclast(a, b)
 	return aesdeclast(a, b)
 }
 }
 
 
 @(require_results, enable_target_feature = "aes")
 @(require_results, enable_target_feature = "aes")
-_mm_aesenc :: #force_inline proc "c" (a, b: __m128i) -> __m128i {
+_mm_aesenc_si128 :: #force_inline proc "c" (a, b: __m128i) -> __m128i {
 	return aesenc(a, b)
 	return aesenc(a, b)
 }
 }
 
 
 @(require_results, enable_target_feature = "aes")
 @(require_results, enable_target_feature = "aes")
-_mm_aesenclast :: #force_inline proc "c" (a, b: __m128i) -> __m128i {
+_mm_aesenclast_si128 :: #force_inline proc "c" (a, b: __m128i) -> __m128i {
 	return aesenclast(a, b)
 	return aesenclast(a, b)
 }
 }
 
 
 @(require_results, enable_target_feature = "aes")
 @(require_results, enable_target_feature = "aes")
-_mm_aesimc :: #force_inline proc "c" (a: __m128i) -> __m128i {
+_mm_aesimc_si128 :: #force_inline proc "c" (a: __m128i) -> __m128i {
 	return aesimc(a)
 	return aesimc(a)
 }
 }
 
 
 @(require_results, enable_target_feature = "aes")
 @(require_results, enable_target_feature = "aes")
-_mm_aeskeygenassist :: #force_inline proc "c" (a: __m128i, $IMM8: u8) -> __m128i {
-	return aeskeygenassist(a, u8(IMM8))
+_mm_aeskeygenassist_si128 :: #force_inline proc "c" (a: __m128i, $IMM8: u8) -> __m128i {
+	return aeskeygenassist(a, IMM8)
 }
 }
 
 
 
 
@@ -45,5 +45,5 @@ foreign _ {
 	@(link_name = "llvm.x86.aesni.aesimc")
 	@(link_name = "llvm.x86.aesni.aesimc")
 	aesimc :: proc(a: __m128i) -> __m128i ---
 	aesimc :: proc(a: __m128i) -> __m128i ---
 	@(link_name = "llvm.x86.aesni.aeskeygenassist")
 	@(link_name = "llvm.x86.aesni.aeskeygenassist")
-	aeskeygenassist :: proc(a: __m128i, imm8: u8) -> __m128i ---
+	aeskeygenassist :: proc(a: __m128i, #const imm8: u8) -> __m128i ---
 }
 }

+ 19 - 14
core/simd/x86/sse2.odin

@@ -144,19 +144,26 @@ _mm_subs_epu16 :: #force_inline proc "c" (a, b: __m128i) -> __m128i {
 _mm_slli_si128_impl :: #force_inline proc "c" (a: __m128i, $IMM8: u32) -> __m128i {
 _mm_slli_si128_impl :: #force_inline proc "c" (a: __m128i, $IMM8: u32) -> __m128i {
 	shift :: IMM8 & 0xff
 	shift :: IMM8 & 0xff
 
 
+	// This needs to emit behavior identical to PSLLDQ which is as follows:
+	//
+	// TEMP := COUNT
+	// IF (TEMP > 15) THEN TEMP := 16; FI
+	// DEST := DEST << (TEMP * 8)
+	// DEST[MAXVL-1:128] (Unmodified)
+
 	return transmute(__m128i)simd.shuffle(
 	return transmute(__m128i)simd.shuffle(
-		transmute(i8x16)a,
 		i8x16(0),
 		i8x16(0),
-		0  when shift > 15 else (16 - shift + 0),
-		1  when shift > 15 else (16 - shift + 1),
-		2  when shift > 15 else (16 - shift + 2),
-		3  when shift > 15 else (16 - shift + 3),
-		4  when shift > 15 else (16 - shift + 4),
-		5  when shift > 15 else (16 - shift + 5),
-		6  when shift > 15 else (16 - shift + 6),
-		7  when shift > 15 else (16 - shift + 7),
-		8  when shift > 15 else (16 - shift + 8),
-		9  when shift > 15 else (16 - shift + 9),
+		transmute(i8x16)a,
+		0 when shift > 15 else (16 - shift + 0),
+		1 when shift > 15 else (16 - shift + 1),
+		2 when shift > 15 else (16 - shift + 2),
+		3 when shift > 15 else (16 - shift + 3),
+		4 when shift > 15 else (16 - shift + 4),
+		5 when shift > 15 else (16 - shift + 5),
+		6 when shift > 15 else (16 - shift + 6),
+		7 when shift > 15 else (16 - shift + 7),
+		8 when shift > 15 else (16 - shift + 8),
+		9 when shift > 15 else (16 - shift + 9),
 		10 when shift > 15 else (16 - shift + 10),
 		10 when shift > 15 else (16 - shift + 10),
 		11 when shift > 15 else (16 - shift + 11),
 		11 when shift > 15 else (16 - shift + 11),
 		12 when shift > 15 else (16 - shift + 12),
 		12 when shift > 15 else (16 - shift + 12),
@@ -435,7 +442,7 @@ _mm_store_si128 :: #force_inline proc "c" (mem_addr: ^__m128i, a: __m128i) {
 }
 }
 @(enable_target_feature="sse2")
 @(enable_target_feature="sse2")
 _mm_storeu_si128 :: #force_inline proc "c" (mem_addr: ^__m128i, a: __m128i) {
 _mm_storeu_si128 :: #force_inline proc "c" (mem_addr: ^__m128i, a: __m128i) {
-	storeudq(mem_addr, a)
+	intrinsics.unaligned_store(mem_addr, a)
 }
 }
 @(enable_target_feature="sse2")
 @(enable_target_feature="sse2")
 _mm_storel_epi64 :: #force_inline proc "c" (mem_addr: ^__m128i, a: __m128i) {
 _mm_storel_epi64 :: #force_inline proc "c" (mem_addr: ^__m128i, a: __m128i) {
@@ -1178,8 +1185,6 @@ foreign _ {
 	cvttsd2si  :: proc(a: __m128d) -> i32 ---
 	cvttsd2si  :: proc(a: __m128d) -> i32 ---
 	@(link_name="llvm.x86.sse2.cvttps2dq")
 	@(link_name="llvm.x86.sse2.cvttps2dq")
 	cvttps2dq  :: proc(a: __m128) -> i32x4 ---
 	cvttps2dq  :: proc(a: __m128) -> i32x4 ---
-	@(link_name="llvm.x86.sse2.storeu.dq")
-	storeudq   :: proc(mem_addr: rawptr, a: __m128i) ---
 	@(link_name="llvm.x86.sse2.storeu.pd")
 	@(link_name="llvm.x86.sse2.storeu.pd")
 	storeupd   :: proc(mem_addr: rawptr, a: __m128d) ---
 	storeupd   :: proc(mem_addr: rawptr, a: __m128d) ---
 
 

+ 9 - 1
core/sys/linux/bits.odin

@@ -244,7 +244,7 @@ Mode_Bits :: enum {
 	ISVTX  = 9,  // 0o0001000
 	ISVTX  = 9,  // 0o0001000
 	ISGID  = 10, // 0o0002000
 	ISGID  = 10, // 0o0002000
 	ISUID  = 11, // 0o0004000
 	ISUID  = 11, // 0o0004000
-	IFFIFO = 12, // 0o0010000
+	IFIFO = 12, // 0o0010000
 	IFCHR  = 13, // 0o0020000
 	IFCHR  = 13, // 0o0020000
 	IFDIR  = 14, // 0o0040000
 	IFDIR  = 14, // 0o0040000
 	IFREG  = 15, // 0o0100000
 	IFREG  = 15, // 0o0100000
@@ -1815,3 +1815,11 @@ EPoll_Ctl_Opcode :: enum i32 {
 	DEL = 2,
 	DEL = 2,
 	MOD = 3,
 	MOD = 3,
 }
 }
+
+/*
+	Bits for execveat(2) flags.
+*/
+Execveat_Flags_Bits :: enum {
+	AT_SYMLINK_NOFOLLOW = 8,
+	AT_EMPTY_PATH       = 12,
+}

+ 3 - 3
core/sys/linux/constants.odin

@@ -39,11 +39,11 @@ PRIO_MIN :: -20
 SIGRTMIN :: Signal(32)
 SIGRTMIN :: Signal(32)
 SIGRTMAX :: Signal(64)
 SIGRTMAX :: Signal(64)
 
 
-S_IFMT   :: Mode{.IFREG, .IFDIR, .IFCHR, .IFFIFO}
+S_IFMT   :: Mode{.IFREG, .IFDIR, .IFCHR, .IFIFO}
 S_IFSOCK :: Mode{.IFREG, .IFDIR}
 S_IFSOCK :: Mode{.IFREG, .IFDIR}
 S_IFLNK  :: Mode{.IFREG, .IFCHR}
 S_IFLNK  :: Mode{.IFREG, .IFCHR}
 S_IFBLK  :: Mode{.IFDIR, .IFCHR}
 S_IFBLK  :: Mode{.IFDIR, .IFCHR}
-S_IFFIFO :: Mode{.IFFIFO}
+S_IFIFO  :: Mode{.IFIFO}
 S_IFCHR  :: Mode{.IFCHR}
 S_IFCHR  :: Mode{.IFCHR}
 S_IFDIR  :: Mode{.IFDIR}
 S_IFDIR  :: Mode{.IFDIR}
 S_IFREG  :: Mode{.IFREG}
 S_IFREG  :: Mode{.IFREG}
@@ -51,7 +51,7 @@ S_IFREG  :: Mode{.IFREG}
 /*
 /*
 	Checks the Mode bits to see if the file is a named pipe (FIFO).
 	Checks the Mode bits to see if the file is a named pipe (FIFO).
 */
 */
-S_ISFIFO :: #force_inline proc "contextless" (m: Mode) -> bool {return (S_IFFIFO  == (m & S_IFMT))}
+S_ISFIFO :: #force_inline proc "contextless" (m: Mode) -> bool {return (S_IFIFO  == (m & S_IFMT))}
 
 
 /*
 /*
 	Check the Mode bits to see if the file is a character device.
 	Check the Mode bits to see if the file is a character device.

+ 31 - 28
core/sys/linux/helpers.odin

@@ -12,7 +12,7 @@ import "base:intrinsics"
 
 
 @(private)
 @(private)
 syscall0 :: #force_inline proc "contextless" (nr: uintptr) -> int {
 syscall0 :: #force_inline proc "contextless" (nr: uintptr) -> int {
-	return cast(int) intrinsics.syscall(nr)
+	return int(intrinsics.syscall(nr))
 }
 }
 
 
 @(private)
 @(private)
@@ -20,7 +20,7 @@ syscall1 :: #force_inline proc "contextless" (nr: uintptr, p1: $T) -> int
 where
 where
 	size_of(p1) <= size_of(uintptr)
 	size_of(p1) <= size_of(uintptr)
 {
 {
-	return cast(int) intrinsics.syscall(nr, cast(uintptr) p1)
+	return int(intrinsics.syscall(nr, uintptr(p1)))
 }
 }
 
 
 @(private)
 @(private)
@@ -29,8 +29,7 @@ where
 	size_of(p1) <= size_of(uintptr),
 	size_of(p1) <= size_of(uintptr),
 	size_of(p2) <= size_of(uintptr) 
 	size_of(p2) <= size_of(uintptr) 
 {
 {
-	return cast(int) intrinsics.syscall(nr,
-		cast(uintptr) p1, cast(uintptr) p2)
+	return int(intrinsics.syscall(nr, uintptr(p1), uintptr(p2)))
 }
 }
 
 
 @(private)
 @(private)
@@ -40,10 +39,11 @@ where
 	size_of(p2) <= size_of(uintptr),
 	size_of(p2) <= size_of(uintptr),
 	size_of(p3) <= size_of(uintptr)
 	size_of(p3) <= size_of(uintptr)
 {
 {
-	return cast(int) intrinsics.syscall(nr,
-		cast(uintptr) p1,
-		cast(uintptr) p2,
-		cast(uintptr) p3)
+	return int(intrinsics.syscall(nr,
+		uintptr(p1),
+		uintptr(p2),
+		uintptr(p3),
+	))
 }
 }
 
 
 @(private)
 @(private)
@@ -54,11 +54,12 @@ where
 	size_of(p3) <= size_of(uintptr),
 	size_of(p3) <= size_of(uintptr),
 	size_of(p4) <= size_of(uintptr)
 	size_of(p4) <= size_of(uintptr)
 {
 {
-	return cast(int) intrinsics.syscall(nr,
-		cast(uintptr) p1,
-		cast(uintptr) p2,
-		cast(uintptr) p3,
-		cast(uintptr) p4)
+	return int(intrinsics.syscall(nr,
+		uintptr(p1),
+		uintptr(p2),
+		uintptr(p3),
+		uintptr(p4),
+	))
 }
 }
 
 
 @(private)
 @(private)
@@ -70,12 +71,13 @@ where
 	size_of(p4) <= size_of(uintptr),
 	size_of(p4) <= size_of(uintptr),
 	size_of(p5) <= size_of(uintptr)
 	size_of(p5) <= size_of(uintptr)
 {
 {
-	return cast(int) intrinsics.syscall(nr,
-		cast(uintptr) p1,
-		cast(uintptr) p2,
-		cast(uintptr) p3,
-		cast(uintptr) p4,
-		cast(uintptr) p5)
+	return int(intrinsics.syscall(nr,
+		uintptr(p1),
+		uintptr(p2),
+		uintptr(p3),
+		uintptr(p4),
+		uintptr(p5),
+	))
 }
 }
 
 
 @(private)
 @(private)
@@ -88,13 +90,14 @@ where
 	size_of(p5) <= size_of(uintptr),
 	size_of(p5) <= size_of(uintptr),
 	size_of(p6) <= size_of(uintptr)
 	size_of(p6) <= size_of(uintptr)
 {
 {
-	return cast(int) intrinsics.syscall(nr,
-		cast(uintptr) p1,
-		cast(uintptr) p2,
-		cast(uintptr) p3,
-		cast(uintptr) p4,
-		cast(uintptr) p5,
-		cast(uintptr) p6)
+	return int(intrinsics.syscall(nr,
+		uintptr(p1),
+		uintptr(p2),
+		uintptr(p3),
+		uintptr(p4),
+		uintptr(p5),
+		uintptr(p6),
+	))
 }
 }
 
 
 syscall :: proc {syscall0, syscall1, syscall2, syscall3, syscall4, syscall5, syscall6}
 syscall :: proc {syscall0, syscall1, syscall2, syscall3, syscall4, syscall5, syscall6}
@@ -113,7 +116,7 @@ where
 		default_value: T
 		default_value: T
 		return default_value, Errno(-ret)
 		return default_value, Errno(-ret)
 	} else {
 	} else {
-		return cast(T) transmute(U) ret, Errno(.NONE)
+		return T(transmute(U)ret), Errno(.NONE)
 	}
 	}
 }
 }
 
 
@@ -123,7 +126,7 @@ errno_unwrap2 :: #force_inline proc "contextless" (ret: $P, $T: typeid) -> (T, E
 		default_value: T
 		default_value: T
 		return default_value, Errno(-ret)
 		return default_value, Errno(-ret)
 	} else {
 	} else {
-		return cast(T) ret, Errno(.NONE)
+		return T(ret), Errno(.NONE)
 	}
 	}
 }
 }
 
 

+ 3 - 7
core/sys/linux/sys.odin

@@ -749,17 +749,13 @@ getsockopt :: proc {
 	getsockopt_base,
 	getsockopt_base,
 }
 }
 
 
-// TODO(flysand): clone (probably not in this PR, maybe not ever)
-
 /*
 /*
 	Creates a copy of the running process.
 	Creates a copy of the running process.
 	Available since Linux 1.0.
 	Available since Linux 1.0.
 */
 */
 fork :: proc "contextless" () -> (Pid, Errno) {
 fork :: proc "contextless" () -> (Pid, Errno) {
 	when ODIN_ARCH == .arm64 {
 	when ODIN_ARCH == .arm64 {
-		// Note(flysand): this syscall is not documented, but the bottom 8 bits of flags
-		// are for exit signal
-		ret := syscall(SYS_clone, Signal.SIGCHLD)
+		ret := syscall(SYS_clone, u64(Signal.SIGCHLD), cast(rawptr) nil, cast(rawptr) nil, cast(rawptr) nil, u64(0))
 		return errno_unwrap(ret, Pid)
 		return errno_unwrap(ret, Pid)
 	} else {
 	} else {
 		ret := syscall(SYS_fork)
 		ret := syscall(SYS_fork)
@@ -789,7 +785,7 @@ execve :: proc "contextless" (name: cstring, argv: [^]cstring, envp: [^]cstring)
 		ret := syscall(SYS_execve, cast(rawptr) name, cast(rawptr) argv, cast(rawptr) envp)
 		ret := syscall(SYS_execve, cast(rawptr) name, cast(rawptr) argv, cast(rawptr) envp)
 		return Errno(-ret)
 		return Errno(-ret)
 	} else {
 	} else {
-		ret := syscall(SYS_execveat, AT_FDCWD, cast(rawptr) name, cast(rawptr) argv, cast(rawptr) envp)
+		ret := syscall(SYS_execveat, AT_FDCWD, cast(rawptr) name, cast(rawptr) argv, cast(rawptr) envp, i32(0))
 		return Errno(-ret)
 		return Errno(-ret)
 	}
 	}
 }
 }
@@ -2818,7 +2814,7 @@ getrandom :: proc "contextless" (buf: []u8, flags: Get_Random_Flags) -> (int, Er
 	Execute program relative to a directory file descriptor.
 	Execute program relative to a directory file descriptor.
 	Available since Linux 3.19.
 	Available since Linux 3.19.
 */
 */
-execveat :: proc "contextless" (dirfd: Fd, name: cstring, argv: [^]cstring, envp: [^]cstring, flags: FD_Flags = {}) -> (Errno) {
+execveat :: proc "contextless" (dirfd: Fd, name: cstring, argv: [^]cstring, envp: [^]cstring, flags: Execveat_Flags = {}) -> (Errno) {
 	ret := syscall(SYS_execveat, dirfd, cast(rawptr) name, cast(rawptr) argv, cast(rawptr) envp, transmute(i32) flags)
 	ret := syscall(SYS_execveat, dirfd, cast(rawptr) name, cast(rawptr) argv, cast(rawptr) envp, transmute(i32) flags)
 	return Errno(-ret)
 	return Errno(-ret)
 }
 }

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

@@ -688,7 +688,7 @@ Sock_Addr_In6 :: struct #packed {
 }
 }
 
 
 /*
 /*
-  Struct representing Unix Domain Socket address
+	Struct representing Unix Domain Socket address
 */
 */
 Sock_Addr_Un :: struct #packed {
 Sock_Addr_Un :: struct #packed {
 	sun_family: Address_Family,
 	sun_family: Address_Family,
@@ -1303,3 +1303,8 @@ EPoll_Event :: struct #packed {
 	events: EPoll_Event_Kind,
 	events: EPoll_Event_Kind,
 	data:   EPoll_Data,
 	data:   EPoll_Data,
 }
 }
+
+/*
+	Flags for execveat(2) syscall.
+*/
+Execveat_Flags :: bit_set[Execveat_Flags_Bits; i32]

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

@@ -6,4 +6,254 @@ foreign import ntdll_lib "system:ntdll.lib"
 @(default_calling_convention="system")
 @(default_calling_convention="system")
 foreign ntdll_lib {
 foreign ntdll_lib {
 	RtlGetVersion :: proc(lpVersionInformation: ^OSVERSIONINFOEXW) -> NTSTATUS ---
 	RtlGetVersion :: proc(lpVersionInformation: ^OSVERSIONINFOEXW) -> NTSTATUS ---
+
+
+	NtQueryInformationProcess :: proc(
+		ProcessHandle:            HANDLE,
+		ProcessInformationClass:  PROCESS_INFO_CLASS,
+		ProcessInformation:       rawptr,
+		ProcessInformationLength: u32,
+		ReturnLength:             ^u32,
+	) -> u32 ---
+
+	NtQueryInformationFile :: proc(
+		FileHandle:           HANDLE,
+		IoStatusBlock:        PIO_STATUS_BLOCK,
+		FileInformation:      rawptr,
+		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 {
+		Status:  NTSTATUS,
+		Pointer: rawptr,
+	},
+	Information: ULONG_PTR,
+}
+
+
+PROCESS_INFO_CLASS :: enum c_int {
+	ProcessBasicInformation       = 0,
+	ProcessDebugPort              = 7,
+	ProcessWow64Information       = 26,
+	ProcessImageFileName          = 27,
+	ProcessBreakOnTermination     = 29,
+	ProcessTelemetryIdInformation = 64,
+	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 {
+	FileDirectoryInformation                     = 1,
+	FileFullDirectoryInformation                 = 2,
+	FileBothDirectoryInformation                 = 3,
+	FileBasicInformation                         = 4,
+	FileStandardInformation                      = 5,
+	FileInternalInformation                      = 6,
+	FileEaInformation                            = 7,
+	FileAccessInformation                        = 8,
+	FileNameInformation                          = 9,
+	FileRenameInformation                        = 10,
+	FileLinkInformation                          = 11,
+	FileNamesInformation                         = 12,
+	FileDispositionInformation                   = 13,
+	FilePositionInformation                      = 14,
+	FileFullEaInformation                        = 15,
+	FileModeInformation                          = 16,
+	FileAlignmentInformation                     = 17,
+	FileAllInformation                           = 18,
+	FileAllocationInformation                    = 19,
+	FileEndOfFileInformation                     = 20,
+	FileAlternateNameInformation                 = 21,
+	FileStreamInformation                        = 22,
+	FilePipeInformation                          = 23,
+	FilePipeLocalInformation                     = 24,
+	FilePipeRemoteInformation                    = 25,
+	FileMailslotQueryInformation                 = 26,
+	FileMailslotSetInformation                   = 27,
+	FileCompressionInformation                   = 28,
+	FileObjectIdInformation                      = 29,
+	FileCompletionInformation                    = 30,
+	FileMoveClusterInformation                   = 31,
+	FileQuotaInformation                         = 32,
+	FileReparsePointInformation                  = 33,
+	FileNetworkOpenInformation                   = 34,
+	FileAttributeTagInformation                  = 35,
+	FileTrackingInformation                      = 36,
+	FileIdBothDirectoryInformation               = 37,
+	FileIdFullDirectoryInformation               = 38,
+	FileValidDataLengthInformation               = 39,
+	FileShortNameInformation                     = 40,
+	FileIoCompletionNotificationInformation      = 41,
+	FileIoStatusBlockRangeInformation            = 42,
+	FileIoPriorityHintInformation                = 43,
+	FileSfioReserveInformation                   = 44,
+	FileSfioVolumeInformation                    = 45,
+	FileHardLinkInformation                      = 46,
+	FileProcessIdsUsingFileInformation           = 47,
+	FileNormalizedNameInformation                = 48,
+	FileNetworkPhysicalNameInformation           = 49,
+	FileIdGlobalTxDirectoryInformation           = 50,
+	FileIsRemoteDeviceInformation                = 51,
+	FileUnusedInformation                        = 52,
+	FileNumaNodeInformation                      = 53,
+	FileStandardLinkInformation                  = 54,
+	FileRemoteProtocolInformation                = 55,
+	FileRenameInformationBypassAccessCheck       = 56,
+	FileLinkInformationBypassAccessCheck         = 57,
+	FileVolumeNameInformation                    = 58,
+	FileIdInformation                            = 59,
+	FileIdExtdDirectoryInformation               = 60,
+	FileReplaceCompletionInformation             = 61,
+	FileHardLinkFullIdInformation                = 62,
+	FileIdExtdBothDirectoryInformation           = 63,
+	FileDispositionInformationEx                 = 64,
+	FileRenameInformationEx                      = 65,
+	FileRenameInformationExBypassAccessCheck     = 66,
+	FileDesiredStorageClassInformation           = 67,
+	FileStatInformation                          = 68,
+	FileMemoryPartitionInformation               = 69,
+	FileStatLxInformation                        = 70,
+	FileCaseSensitiveInformation                 = 71,
+	FileLinkInformationEx                        = 72,
+	FileLinkInformationExBypassAccessCheck       = 73,
+	FileStorageReserveIdInformation              = 74,
+	FileCaseSensitiveInformationForceAccessCheck = 75,
+	FileKnownFolderInformation                   = 76,
+	FileStatBasicInformation                     = 77,
+	FileId64ExtdDirectoryInformation             = 78,
+	FileId64ExtdBothDirectoryInformation         = 79,
+	FileIdAllExtdDirectoryInformation            = 80,
+	FileIdAllExtdBothDirectoryInformation        = 81,
+	FileStreamReservationInformation,
+	FileMupProviderInfo,
+	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 {
+	ExitStatus:                   NTSTATUS,
+	PebBaseAddress:               ^PEB,
+	AffinityMask:                 ULONG_PTR,
+	BasePriority:                 KPRIORITY,
+	UniqueProcessId:              ULONG_PTR,
+	InheritedFromUniqueProcessId: ULONG_PTR,
+}
+
+KPRIORITY :: rawptr
+
+PPS_POST_PROCESS_INIT_ROUTINE :: proc "system" ()
+
+
+PEB :: struct {
+	_:                      [2]u8,
+	BeingDebugged:          u8,
+	_:                      [1]u8,
+	_:                      [2]rawptr,
+	Ldr:                    ^PEB_LDR_DATA,
+	ProcessParameters:      ^RTL_USER_PROCESS_PARAMETERS,
+	_:                      [104]u8,
+	_:                      [52]rawptr,
+	PostProcessInitRoutine: PPS_POST_PROCESS_INIT_ROUTINE,
+	_:                      [128]u8,
+	_:                      [1]rawptr,
+	SessionId:              u32,
+}
+
+
+
+
+PEB_LDR_DATA :: struct {
+	_: [8]u8,
+	_: [3]rawptr,
+	InMemoryOrderModuleList: LIST_ENTRY,
+}
+
+RTL_USER_PROCESS_PARAMETERS :: struct {
+	MaximumLength:          u32,
+	Length:                 u32,
+	Flags:                  u32,
+	DebugFlags:             u32,
+	ConsoleHandle:          rawptr,
+	ConsoleFlags:           u32,
+	StdInputHandle:         rawptr,
+	StdOutputHandle:        rawptr,
+	StdErrorHandle:         rawptr,
+	CurrentDirectoryPath:   UNICODE_STRING,
+	CurrentDirectoryHandle: rawptr,
+	DllPath:                UNICODE_STRING,
+	ImagePathName:          UNICODE_STRING,
+	CommandLine:            UNICODE_STRING,
+	Environment:            rawptr,
+	StartingPositionLeft:   u32,
+	StartingPositionTop:    u32,
+	Width:                  u32,
+	Height:                 u32,
+	CharWidth:              u32,
+	CharHeight:             u32,
+	ConsoleTextAttributes:  u32,
+	WindowFlags:            u32,
+	ShowWindowFlags:        u32,
+	WindowTitle:            UNICODE_STRING,
+	DesktopName:            UNICODE_STRING,
+	ShellInfo:              UNICODE_STRING,
+	RuntimeData:            UNICODE_STRING,
+	DLCurrentDirectory:     [32]RTL_DRIVE_LETTER_CURDIR,
+	EnvironmentSize:        u32,
+}
+
+RTL_DRIVE_LETTER_CURDIR :: struct {
+	Flags:     u16,
+	Length:    u16,
+	TimeStamp: u32,
+	DosPath:   UNICODE_STRING,
+}
+
+
+LIST_ENTRY :: struct {
+	Flink: ^LIST_ENTRY,
+	Blink: ^LIST_ENTRY,
 }
 }

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

@@ -32,6 +32,10 @@ foreign shell32 {
 	SHGetKnownFolderPath :: proc(rfid: REFKNOWNFOLDERID, dwFlags: /* KNOWN_FOLDER_FLAG */ DWORD, hToken: HANDLE, ppszPath: ^LPWSTR) -> 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 ---
 	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 ---
+	DragFinish :: proc(hDrop: HDROP) --- // @New
 }
 }
 
 
 APPBARDATA :: struct {
 APPBARDATA :: struct {
@@ -69,6 +73,8 @@ ABE_BOTTOM           :: 3
 KNOWNFOLDERID :: GUID
 KNOWNFOLDERID :: GUID
 REFKNOWNFOLDERID :: ^KNOWNFOLDERID
 REFKNOWNFOLDERID :: ^KNOWNFOLDERID
 
 
+HDROP :: HANDLE
+
 KNOWN_FOLDER_FLAG :: enum u32 {
 KNOWN_FOLDER_FLAG :: enum u32 {
 	DEFAULT                          = 0x00000000,
 	DEFAULT                          = 0x00000000,
 
 

+ 50 - 30
core/sys/windows/types.odin

@@ -1131,16 +1131,28 @@ TRACKMOUSEEVENT :: struct {
 }
 }
 
 
 WIN32_FIND_DATAW :: struct {
 WIN32_FIND_DATAW :: struct {
-	dwFileAttributes: DWORD,
-	ftCreationTime: FILETIME,
-	ftLastAccessTime: FILETIME,
-	ftLastWriteTime: FILETIME,
-	nFileSizeHigh: DWORD,
-	nFileSizeLow: DWORD,
-	dwReserved0: DWORD,
-	dwReserved1: DWORD,
-	cFileName: [260]wchar_t, // #define MAX_PATH 260
-	cAlternateFileName: [14]wchar_t,
+	dwFileAttributes:   DWORD,
+	ftCreationTime:     FILETIME,
+	ftLastAccessTime:   FILETIME,
+	ftLastWriteTime:    FILETIME,
+	nFileSizeHigh:      DWORD,
+	nFileSizeLow:       DWORD,
+	dwReserved0:        DWORD,
+	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 {
+	Identifier: [16]BYTE,
+}
+
+FILE_ID_INFO :: struct {
+	VolumeSerialNumber: ULONGLONG,
+	FileId:             FILE_ID_128,
 }
 }
 
 
 CREATESTRUCTA :: struct {
 CREATESTRUCTA :: struct {
@@ -1196,6 +1208,11 @@ NMHDR :: struct {
 	code:     UINT,      // NM_ code
 	code:     UINT,      // NM_ code
 }
 }
 
 
+NCCALCSIZE_PARAMS :: struct {
+	rgrc: [3]RECT,
+	lppos: PWINDOWPOS,
+}
+
 // Generic WM_NOTIFY notification codes
 // Generic WM_NOTIFY notification codes
 NM_OUTOFMEMORY          :: ~uintptr(0) // -1
 NM_OUTOFMEMORY          :: ~uintptr(0) // -1
 NM_CLICK                :: NM_OUTOFMEMORY-1  // uses NMCLICK struct
 NM_CLICK                :: NM_OUTOFMEMORY-1  // uses NMCLICK struct
@@ -2318,6 +2335,7 @@ FILE_TYPE_PIPE :: 0x0003
 RECT  :: struct {left, top, right, bottom: LONG}
 RECT  :: struct {left, top, right, bottom: LONG}
 POINT :: struct {x, y: LONG}
 POINT :: struct {x, y: LONG}
 
 
+PWINDOWPOS :: ^WINDOWPOS
 WINDOWPOS :: struct {
 WINDOWPOS :: struct {
 	hwnd: HWND,
 	hwnd: HWND,
 	hwndInsertAfter: HWND,
 	hwndInsertAfter: HWND,
@@ -2549,6 +2567,7 @@ CLSCTX_RESERVED6                      :: 0x1000000
 CLSCTX_ACTIVATE_ARM32_SERVER          :: 0x2000000
 CLSCTX_ACTIVATE_ARM32_SERVER          :: 0x2000000
 CLSCTX_ALLOW_LOWER_TRUST_REGISTRATION :: 0x4000000
 CLSCTX_ALLOW_LOWER_TRUST_REGISTRATION :: 0x4000000
 CLSCTX_PS_DLL                         :: 0x80000000
 CLSCTX_PS_DLL                         :: 0x80000000
+CLSCTX_ALL                            :: CLSCTX_INPROC_SERVER | CLSCTX_INPROC_HANDLER | CLSCTX_LOCAL_SERVER | CLSCTX_REMOTE_SERVER
 
 
 WSAPROTOCOLCHAIN :: struct {
 WSAPROTOCOLCHAIN :: struct {
 	ChainLen: c_int,
 	ChainLen: c_int,
@@ -2608,10 +2627,11 @@ OBJECT_ATTRIBUTES :: struct {
 	SecurityQualityOfService: rawptr,
 	SecurityQualityOfService: rawptr,
 }
 }
 
 
+PUNICODE_STRING :: ^UNICODE_STRING
 UNICODE_STRING :: struct {
 UNICODE_STRING :: struct {
-	Length:        u16,
-	MaximumLength: u16,
-	Buffer:        ^u16,
+	Length:        u16    `fmt:"-"`,
+	MaximumLength: u16    `fmt:"-"`,
+	Buffer:        [^]u16 `fmt:"s,Length"`,
 }
 }
 
 
 OVERLAPPED :: struct {
 OVERLAPPED :: struct {
@@ -2822,41 +2842,41 @@ NEON128 :: struct {
 
 
 EXCEPTION_POINTERS :: struct {
 EXCEPTION_POINTERS :: struct {
 	ExceptionRecord: ^EXCEPTION_RECORD,
 	ExceptionRecord: ^EXCEPTION_RECORD,
-	ContextRecord: ^CONTEXT,
+	ContextRecord:   ^CONTEXT,
 }
 }
 
 
 PVECTORED_EXCEPTION_HANDLER :: #type proc "system" (ExceptionInfo: ^EXCEPTION_POINTERS) -> LONG
 PVECTORED_EXCEPTION_HANDLER :: #type proc "system" (ExceptionInfo: ^EXCEPTION_POINTERS) -> LONG
 
 
 CONSOLE_READCONSOLE_CONTROL :: struct {
 CONSOLE_READCONSOLE_CONTROL :: struct {
-	nLength: ULONG,
-	nInitialChars: ULONG,
-	dwCtrlWakeupMask: ULONG,
+	nLength:           ULONG,
+	nInitialChars:     ULONG,
+	dwCtrlWakeupMask:  ULONG,
 	dwControlKeyState: ULONG,
 	dwControlKeyState: ULONG,
 }
 }
 
 
 PCONSOLE_READCONSOLE_CONTROL :: ^CONSOLE_READCONSOLE_CONTROL
 PCONSOLE_READCONSOLE_CONTROL :: ^CONSOLE_READCONSOLE_CONTROL
 
 
 BY_HANDLE_FILE_INFORMATION :: struct {
 BY_HANDLE_FILE_INFORMATION :: struct {
-	dwFileAttributes: DWORD,
-	ftCreationTime: FILETIME,
-	ftLastAccessTime: FILETIME,
-	ftLastWriteTime: FILETIME,
+	dwFileAttributes:     DWORD,
+	ftCreationTime:       FILETIME,
+	ftLastAccessTime:     FILETIME,
+	ftLastWriteTime:      FILETIME,
 	dwVolumeSerialNumber: DWORD,
 	dwVolumeSerialNumber: DWORD,
-	nFileSizeHigh: DWORD,
-	nFileSizeLow: DWORD,
-	nNumberOfLinks: DWORD,
-	nFileIndexHigh: DWORD,
-	nFileIndexLow: DWORD,
+	nFileSizeHigh:        DWORD,
+	nFileSizeLow:         DWORD,
+	nNumberOfLinks:       DWORD,
+	nFileIndexHigh:       DWORD,
+	nFileIndexLow:        DWORD,
 }
 }
 
 
 LPBY_HANDLE_FILE_INFORMATION :: ^BY_HANDLE_FILE_INFORMATION
 LPBY_HANDLE_FILE_INFORMATION :: ^BY_HANDLE_FILE_INFORMATION
 
 
 FILE_STANDARD_INFO :: struct {
 FILE_STANDARD_INFO :: struct {
 	AllocationSize: LARGE_INTEGER,
 	AllocationSize: LARGE_INTEGER,
-	EndOfFile: LARGE_INTEGER,
-	NumberOfLinks: DWORD,
-	DeletePending: BOOLEAN,
-	Directory: BOOLEAN,
+	EndOfFile:      LARGE_INTEGER,
+	NumberOfLinks:  DWORD,
+	DeletePending:  BOOLEAN,
+	Directory:      BOOLEAN,
 }
 }
 
 
 FILE_ATTRIBUTE_TAG_INFO :: struct {
 FILE_ATTRIBUTE_TAG_INFO :: struct {

+ 47 - 3
core/testing/runner.odin

@@ -6,6 +6,7 @@ import "base:runtime"
 import "core:bytes"
 import "core:bytes"
 import "core:encoding/ansi"
 import "core:encoding/ansi"
 @require import "core:encoding/base64"
 @require import "core:encoding/base64"
+@require import "core:encoding/json"
 import "core:fmt"
 import "core:fmt"
 import "core:io"
 import "core:io"
 @require import pkg_log "core:log"
 @require import pkg_log "core:log"
@@ -44,7 +45,8 @@ SHARED_RANDOM_SEED    : u64    : #config(ODIN_TEST_RANDOM_SEED, 0)
 LOG_LEVEL             : string : #config(ODIN_TEST_LOG_LEVEL, "info")
 LOG_LEVEL             : string : #config(ODIN_TEST_LOG_LEVEL, "info")
 // Show only the most necessary logging information.
 // Show only the most necessary logging information.
 USING_SHORT_LOGS      : bool   : #config(ODIN_TEST_SHORT_LOGS, false)
 USING_SHORT_LOGS      : bool   : #config(ODIN_TEST_SHORT_LOGS, false)
-
+// Output a report of the tests to the given path.
+JSON_REPORT           : string : #config(ODIN_TEST_JSON_REPORT, "")
 
 
 get_log_level :: #force_inline proc() -> runtime.Logger_Level {
 get_log_level :: #force_inline proc() -> runtime.Logger_Level {
 	when ODIN_DEBUG {
 	when ODIN_DEBUG {
@@ -61,6 +63,18 @@ get_log_level :: #force_inline proc() -> runtime.Logger_Level {
 	}
 	}
 }
 }
 
 
+JSON :: struct {
+	total:    int,
+	success:  int,
+	duration: time.Duration,
+	packages: map[string][dynamic]JSON_Test,
+}
+
+JSON_Test :: struct {
+	success: bool,
+	name:    string,
+}
+
 end_t :: proc(t: ^T) {
 end_t :: proc(t: ^T) {
 	for i := len(t.cleanups)-1; i >= 0; i -= 1 {
 	for i := len(t.cleanups)-1; i >= 0; i -= 1 {
 		#no_bounds_check c := t.cleanups[i]
 		#no_bounds_check c := t.cleanups[i]
@@ -654,8 +668,8 @@ runner :: proc(internal_tests: []Internal_Test) -> bool {
 			#no_bounds_check pkg := report.packages_by_name[it.pkg]
 			#no_bounds_check pkg := report.packages_by_name[it.pkg]
 			pkg.frame_ready = false
 			pkg.frame_ready = false
 
 
-			fmt.assertf(thread.pool_stop_task(&pool, test_index),
-				"A signal (%v) was raised to stop test #%i %s.%s, but it was unable to be found.",
+			found := thread.pool_stop_task(&pool, test_index)
+			fmt.assertf(found, "A signal (%v) was raised to stop test #%i %s.%s, but it was unable to be found.",
 				reason, test_index, it.pkg, it.name)
 				reason, test_index, it.pkg, it.name)
 
 
 			// The order this is handled in is a little particular.
 			// The order this is handled in is a little particular.
@@ -847,5 +861,35 @@ To partly mitigate this, redirect STDERR to a file or use the -define:ODIN_TEST_
 
 
 	fmt.wprintln(stderr, bytes.buffer_to_string(&batch_buffer))
 	fmt.wprintln(stderr, bytes.buffer_to_string(&batch_buffer))
 
 
+	when JSON_REPORT != "" {
+		json_report: JSON
+
+		mode: int
+		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)
+		defer os.close(json_fd)
+
+		for test, i in report.all_tests {
+			#no_bounds_check state := report.all_test_states[i]
+
+			if test.pkg not_in json_report.packages {
+				json_report.packages[test.pkg] = {}
+			}
+
+			tests := &json_report.packages[test.pkg]
+			append(tests, JSON_Test{name = test.name, success = state == .Successful})
+		}
+
+		json_report.total    = len(internal_tests)
+		json_report.success  = total_success_count
+		json_report.duration = finished_in
+
+		err := json.marshal_to_writer(os.stream_from_handle(json_fd), json_report, &{ pretty = true })
+		fmt.assertf(err == nil, "Error writing JSON report: %v", err)
+	}
+
 	return total_success_count == total_test_count
 	return total_success_count == total_test_count
 }
 }

+ 242 - 39
core/thread/thread.odin

@@ -6,12 +6,26 @@ import "base:intrinsics"
 
 
 _ :: intrinsics
 _ :: intrinsics
 
 
+/*
+Value, specifying whether `core:thread` functionality is available on the
+current platform.
+*/
 IS_SUPPORTED :: _IS_SUPPORTED
 IS_SUPPORTED :: _IS_SUPPORTED
 
 
+/*
+Type for a procedure that will be run in a thread, after that thread has been
+started.
+*/
 Thread_Proc :: #type proc(^Thread)
 Thread_Proc :: #type proc(^Thread)
 
 
+/*
+Maximum number of user arguments for polymorphic thread procedures.
+*/
 MAX_USER_ARGUMENTS :: 8
 MAX_USER_ARGUMENTS :: 8
 
 
+/*
+Type representing the state/flags of the thread.
+*/
 Thread_State :: enum u8 {
 Thread_State :: enum u8 {
 	Started,
 	Started,
 	Joined,
 	Joined,
@@ -19,44 +33,48 @@ Thread_State :: enum u8 {
 	Self_Cleanup,
 	Self_Cleanup,
 }
 }
 
 
+/*
+Type representing a thread handle and the associated with that thread data.
+*/
 Thread :: struct {
 Thread :: struct {
 	using specific: Thread_Os_Specific,
 	using specific: Thread_Os_Specific,
 	flags: bit_set[Thread_State; u8],
 	flags: bit_set[Thread_State; u8],
-	id:             int,
-	procedure:      Thread_Proc,
-
-	/*
-		These are values that the user can set as they wish, after the thread has been created.
-		This data is easily available to the thread proc.
-
-		These fields can be assigned to directly.
-
-		Should be set after the thread is created, but before it is started.
-	*/
-	data:           rawptr,
-	user_index:     int,
-	user_args:      [MAX_USER_ARGUMENTS]rawptr,
-
-	/*
-		The context to be used as 'context' in the thread proc.
-
-		This field can be assigned to directly, after the thread has been created, but __before__ the thread has been started.
-		This field must not be changed after the thread has started.
-
-		NOTE: If you __don't__ set this, the temp allocator will be managed for you;
-		      If you __do__ set this, then you're expected to handle whatever allocators you set, yourself.
-
-		IMPORTANT:
-		By default, the thread proc will get the same context as `main()` gets.
-		In this situation, the thread will get a new temporary allocator which will be cleaned up when the thread dies.
-		***This does NOT happen when you set `init_context`.***
-		This means that if you set `init_context`, but still have the `temp_allocator` field set to the default temp allocator,
-		then you'll need to call `runtime.default_temp_allocator_destroy(auto_cast the_thread.init_context.temp_allocator.data)` manually,
-		in order to prevent any memory leaks.
-		This call ***must*** be done ***in the thread proc*** because the default temporary allocator uses thread local state!
-	*/
+	// Thread ID.
+	id: int,
+	// The thread procedure.
+	procedure: Thread_Proc,
+	// User-supplied pointer, that will be available to the thread once it is
+	// started. Should be set after the thread has been created, but before
+	// it is started.
+	data: rawptr,
+	// User-supplied integer, that will be available to the thread once it is
+	// started. Should be set after the thread has been created, but before
+	// it is started.
+	user_index: int,
+	// User-supplied array of arguments, that will be available to the thread,
+	// once it is started. Should be set after the thread has been created,
+	// but before it is started.
+	user_args: [MAX_USER_ARGUMENTS]rawptr,
+	// The thread context.
+	// This field can be assigned to directly, after the thread has been
+	// created, but __before__ the thread has been started. This field must
+	// not be changed after the thread has started.
+	//
+	// **Note**: If this field is **not** set, the temp allocator will be managed
+	// automatically. If it is set, the allocators must be handled manually.
+	//
+	// **IMPORTANT**:
+	// By default, the thread proc will get the same context as `main()` gets.
+	// In this situation, the thread will get a new temporary allocator which
+	// will be cleaned up when the thread dies. ***This does NOT happen when
+	// `init_context` field is initialized***.
+	//
+	// If `init_context` is initialized, and `temp_allocator` field is set to
+	// the default temp allocator, then `runtime.default_temp_allocator_destroy()`
+	// procedure needs to be called from the thread procedure, in order to prevent
+	// any memory leaks.
 	init_context: Maybe(runtime.Context),
 	init_context: Maybe(runtime.Context),
-
+	// The allocator used to allocate data for the thread.
 	creation_allocator: mem.Allocator,
 	creation_allocator: mem.Allocator,
 }
 }
 
 
@@ -64,6 +82,9 @@ when IS_SUPPORTED {
 	#assert(size_of(Thread{}.user_index) == size_of(uintptr))
 	#assert(size_of(Thread{}.user_index) == size_of(uintptr))
 }
 }
 
 
+/*
+Type representing priority of a thread.
+*/
 Thread_Priority :: enum {
 Thread_Priority :: enum {
 	Normal,
 	Normal,
 	Low,
 	Low,
@@ -71,74 +92,178 @@ Thread_Priority :: enum {
 }
 }
 
 
 /*
 /*
-	Creates a thread in a suspended state with the given priority.
-	To start the thread, call `thread.start()`.
+Create a thread in a suspended state with the given priority.
 
 
-	See `thread.create_and_start()`.
+This procedure creates a thread that will be set to run the procedure
+specified by `procedure` parameter with a specified priority. The returned
+thread will be in a suspended state, until `start()` procedure is called.
+
+To start the thread, call `start()`. Also the `create_and_start()`
+procedure can be called to create and start the thread immediately.
 */
 */
 create :: proc(procedure: Thread_Proc, priority := Thread_Priority.Normal) -> ^Thread {
 create :: proc(procedure: Thread_Proc, priority := Thread_Priority.Normal) -> ^Thread {
 	return _create(procedure, priority)
 	return _create(procedure, priority)
 }
 }
+
+/*
+Wait for the thread to finish and free all data associated with it.
+*/
 destroy :: proc(thread: ^Thread) {
 destroy :: proc(thread: ^Thread) {
 	_destroy(thread)
 	_destroy(thread)
 }
 }
 
 
+/*
+Start a suspended thread.
+*/
 start :: proc(thread: ^Thread) {
 start :: proc(thread: ^Thread) {
 	_start(thread)
 	_start(thread)
 }
 }
 
 
+/*
+Check if the thread has finished work.
+*/
 is_done :: proc(thread: ^Thread) -> bool {
 is_done :: proc(thread: ^Thread) -> bool {
 	return _is_done(thread)
 	return _is_done(thread)
 }
 }
 
 
-
+/*
+Wait for the thread to finish work.
+*/
 join :: proc(thread: ^Thread) {
 join :: proc(thread: ^Thread) {
 	_join(thread)
 	_join(thread)
 }
 }
 
 
-
+/*
+Wait for all threads to finish work.
+*/
 join_multiple :: proc(threads: ..^Thread) {
 join_multiple :: proc(threads: ..^Thread) {
 	_join_multiple(..threads)
 	_join_multiple(..threads)
 }
 }
 
 
+/*
+Forcibly terminate a running thread.
+*/
 terminate :: proc(thread: ^Thread, exit_code: int) {
 terminate :: proc(thread: ^Thread, exit_code: int) {
 	_terminate(thread, exit_code)
 	_terminate(thread, exit_code)
 }
 }
 
 
+/*
+Yield the execution of the current thread to another OS thread or process.
+*/
 yield :: proc() {
 yield :: proc() {
 	_yield()
 	_yield()
 }
 }
 
 
+/*
+Run a procedure on a different thread.
 
 
+This procedure runs the given procedure on another thread. The context
+specified by `init_context` will be used as the context in which `fn` is going
+to execute. The thread will have priority specified by the `priority` parameter.
 
 
+**IMPORTANT**: If `init_context` is specified and the default temporary allocator
+is used, the thread procedure needs to call `runtime.default_temp_allocator_destroy()`
+in order to free the resources associated with the temporary allocations.
+*/
 run :: proc(fn: proc(), init_context: Maybe(runtime.Context) = nil, priority := Thread_Priority.Normal) {
 run :: proc(fn: proc(), init_context: Maybe(runtime.Context) = nil, priority := Thread_Priority.Normal) {
 	create_and_start(fn, init_context, priority, true)
 	create_and_start(fn, init_context, priority, true)
 }
 }
 
 
+/*
+Run a procedure with one pointer parameter on a different thread.
+
+This procedure runs the given procedure on another thread. The context
+specified by `init_context` will be used as the context in which `fn` is going
+to execute. The thread will have priority specified by the `priority` parameter.
+
+**IMPORTANT**: If `init_context` is specified and the default temporary allocator
+is used, the thread procedure needs to call `runtime.default_temp_allocator_destroy()`
+in order to free the resources associated with the temporary allocations.
+*/
 run_with_data :: proc(data: rawptr, fn: proc(data: rawptr), init_context: Maybe(runtime.Context) = nil, priority := Thread_Priority.Normal) {
 run_with_data :: proc(data: rawptr, fn: proc(data: rawptr), init_context: Maybe(runtime.Context) = nil, priority := Thread_Priority.Normal) {
 	create_and_start_with_data(data, fn, init_context, priority, true)
 	create_and_start_with_data(data, fn, init_context, priority, true)
 }
 }
 
 
+/*
+Run a procedure with one polymorphic parameter on a different thread.
+
+This procedure runs the given procedure on another thread. The context
+specified by `init_context` will be used as the context in which `fn` is going
+to execute. The thread will have priority specified by the `priority` parameter.
+
+**IMPORTANT**: If `init_context` is specified and the default temporary allocator
+is used, the thread procedure needs to call `runtime.default_temp_allocator_destroy()`
+in order to free the resources associated with the temporary allocations.
+*/
 run_with_poly_data :: proc(data: $T, fn: proc(data: T), init_context: Maybe(runtime.Context) = nil, priority := Thread_Priority.Normal)
 run_with_poly_data :: proc(data: $T, fn: proc(data: T), init_context: Maybe(runtime.Context) = nil, priority := Thread_Priority.Normal)
 	where size_of(T) <= size_of(rawptr) * MAX_USER_ARGUMENTS {
 	where size_of(T) <= size_of(rawptr) * MAX_USER_ARGUMENTS {
 	create_and_start_with_poly_data(data, fn, init_context, priority, true)
 	create_and_start_with_poly_data(data, fn, init_context, priority, true)
 }
 }
 
 
+/*
+Run a procedure with two polymorphic parameters on a different thread.
+
+This procedure runs the given procedure on another thread. The context
+specified by `init_context` will be used as the context in which `fn` is going
+to execute. The thread will have priority specified by the `priority` parameter.
+
+**IMPORTANT**: If `init_context` is specified and the default temporary allocator
+is used, the thread procedure needs to call `runtime.default_temp_allocator_destroy()`
+in order to free the resources associated with the temporary allocations.
+*/
 run_with_poly_data2 :: proc(arg1: $T1, arg2: $T2, fn: proc(T1, T2), init_context: Maybe(runtime.Context) = nil, priority := Thread_Priority.Normal)
 run_with_poly_data2 :: proc(arg1: $T1, arg2: $T2, fn: proc(T1, T2), init_context: Maybe(runtime.Context) = nil, priority := Thread_Priority.Normal)
 	where size_of(T1) + size_of(T2) <= size_of(rawptr) * MAX_USER_ARGUMENTS {
 	where size_of(T1) + size_of(T2) <= size_of(rawptr) * MAX_USER_ARGUMENTS {
 	create_and_start_with_poly_data2(arg1, arg2, fn, init_context, priority, true)
 	create_and_start_with_poly_data2(arg1, arg2, fn, init_context, priority, true)
 }
 }
 
 
+/*
+Run a procedure with three polymorphic parameters on a different thread.
+
+This procedure runs the given procedure on another thread. The context
+specified by `init_context` will be used as the context in which `fn` is going
+to execute. The thread will have priority specified by the `priority` parameter.
+
+**IMPORTANT**: If `init_context` is specified and the default temporary allocator
+is used, the thread procedure needs to call `runtime.default_temp_allocator_destroy()`
+in order to free the resources associated with the temporary allocations.
+*/
 run_with_poly_data3 :: proc(arg1: $T1, arg2: $T2, arg3: $T3, fn: proc(arg1: T1, arg2: T2, arg3: T3), init_context: Maybe(runtime.Context) = nil, priority := Thread_Priority.Normal)
 run_with_poly_data3 :: proc(arg1: $T1, arg2: $T2, arg3: $T3, fn: proc(arg1: T1, arg2: T2, arg3: T3), init_context: Maybe(runtime.Context) = nil, priority := Thread_Priority.Normal)
 	where size_of(T1) + size_of(T2) + size_of(T3) <= size_of(rawptr) * MAX_USER_ARGUMENTS {
 	where size_of(T1) + size_of(T2) + size_of(T3) <= size_of(rawptr) * MAX_USER_ARGUMENTS {
 	create_and_start_with_poly_data3(arg1, arg2, arg3, fn, init_context, priority, true)
 	create_and_start_with_poly_data3(arg1, arg2, arg3, fn, init_context, priority, true)
 }
 }
+
+/*
+Run a procedure with four polymorphic parameters on a different thread.
+
+This procedure runs the given procedure on another thread. The context
+specified by `init_context` will be used as the context in which `fn` is going
+to execute. The thread will have priority specified by the `priority` parameter.
+
+**IMPORTANT**: If `init_context` is specified and the default temporary allocator
+is used, the thread procedure needs to call `runtime.default_temp_allocator_destroy()`
+in order to free the resources associated with the temporary allocations.
+*/
 run_with_poly_data4 :: proc(arg1: $T1, arg2: $T2, arg3: $T3, arg4: $T4, fn: proc(arg1: T1, arg2: T2, arg3: T3, arg4: T4), init_context: Maybe(runtime.Context) = nil, priority := Thread_Priority.Normal)
 run_with_poly_data4 :: proc(arg1: $T1, arg2: $T2, arg3: $T3, arg4: $T4, fn: proc(arg1: T1, arg2: T2, arg3: T3, arg4: T4), init_context: Maybe(runtime.Context) = nil, priority := Thread_Priority.Normal)
 	where size_of(T1) + size_of(T2) + size_of(T3) + size_of(T4) <= size_of(rawptr) * MAX_USER_ARGUMENTS {
 	where size_of(T1) + size_of(T2) + size_of(T3) + size_of(T4) <= size_of(rawptr) * MAX_USER_ARGUMENTS {
 	create_and_start_with_poly_data4(arg1, arg2, arg3, arg4, fn, init_context, priority, true)
 	create_and_start_with_poly_data4(arg1, arg2, arg3, arg4, fn, init_context, priority, true)
 }
 }
 
 
+/*
+Run a procedure on a different thread.
+
+This procedure runs the given procedure on another thread. The context
+specified by `init_context` will be used as the context in which `fn` is going
+to execute. The thread will have priority specified by the `priority` parameter.
+
+If `self_cleanup` is specified, after the thread finishes the execution of the
+`fn` procedure, the resources associated with the thread are going to be
+automatically freed. **Do not** dereference the `^Thread` pointer, if this
+flag is specified.
 
 
+**IMPORTANT**: If `init_context` is specified and the default temporary allocator
+is used, the thread procedure needs to call `runtime.default_temp_allocator_destroy()`
+in order to free the resources associated with the temporary allocations.
+*/
 create_and_start :: proc(fn: proc(), init_context: Maybe(runtime.Context) = nil, priority := Thread_Priority.Normal, self_cleanup := false) -> ^Thread {
 create_and_start :: proc(fn: proc(), init_context: Maybe(runtime.Context) = nil, priority := Thread_Priority.Normal, self_cleanup := false) -> ^Thread {
 	thread_proc :: proc(t: ^Thread) {
 	thread_proc :: proc(t: ^Thread) {
 		fn := cast(proc())t.data
 		fn := cast(proc())t.data
@@ -154,9 +279,22 @@ create_and_start :: proc(fn: proc(), init_context: Maybe(runtime.Context) = nil,
 	return t
 	return t
 }
 }
 
 
+/*
+Run a procedure with one pointer parameter on a different thread.
 
 
+This procedure runs the given procedure on another thread. The context
+specified by `init_context` will be used as the context in which `fn` is going
+to execute. The thread will have priority specified by the `priority` parameter.
 
 
+If `self_cleanup` is specified, after the thread finishes the execution of the
+`fn` procedure, the resources associated with the thread are going to be
+automatically freed. **Do not** dereference the `^Thread` pointer, if this
+flag is specified.
 
 
+**IMPORTANT**: If `init_context` is specified and the default temporary allocator
+is used, the thread procedure needs to call `runtime.default_temp_allocator_destroy()`
+in order to free the resources associated with the temporary allocations.
+*/
 create_and_start_with_data :: proc(data: rawptr, fn: proc(data: rawptr), init_context: Maybe(runtime.Context) = nil, priority := Thread_Priority.Normal, self_cleanup := false) -> ^Thread {
 create_and_start_with_data :: proc(data: rawptr, fn: proc(data: rawptr), init_context: Maybe(runtime.Context) = nil, priority := Thread_Priority.Normal, self_cleanup := false) -> ^Thread {
 	thread_proc :: proc(t: ^Thread) {
 	thread_proc :: proc(t: ^Thread) {
 		fn := cast(proc(rawptr))t.data
 		fn := cast(proc(rawptr))t.data
@@ -176,6 +314,22 @@ create_and_start_with_data :: proc(data: rawptr, fn: proc(data: rawptr), init_co
 	return t
 	return t
 }
 }
 
 
+/*
+Run a procedure with one polymorphic parameter on a different thread.
+
+This procedure runs the given procedure on another thread. The context
+specified by `init_context` will be used as the context in which `fn` is going
+to execute. The thread will have priority specified by the `priority` parameter.
+
+If `self_cleanup` is specified, after the thread finishes the execution of the
+`fn` procedure, the resources associated with the thread are going to be
+automatically freed. **Do not** dereference the `^Thread` pointer, if this
+flag is specified.
+
+**IMPORTANT**: If `init_context` is specified and the default temporary allocator
+is used, the thread procedure needs to call `runtime.default_temp_allocator_destroy()`
+in order to free the resources associated with the temporary allocations.
+*/
 create_and_start_with_poly_data :: proc(data: $T, fn: proc(data: T), init_context: Maybe(runtime.Context) = nil, priority := Thread_Priority.Normal, self_cleanup := false) -> ^Thread
 create_and_start_with_poly_data :: proc(data: $T, fn: proc(data: T), init_context: Maybe(runtime.Context) = nil, priority := Thread_Priority.Normal, self_cleanup := false) -> ^Thread
 	where size_of(T) <= size_of(rawptr) * MAX_USER_ARGUMENTS {
 	where size_of(T) <= size_of(rawptr) * MAX_USER_ARGUMENTS {
 	thread_proc :: proc(t: ^Thread) {
 	thread_proc :: proc(t: ^Thread) {
@@ -201,6 +355,22 @@ create_and_start_with_poly_data :: proc(data: $T, fn: proc(data: T), init_contex
 	return t
 	return t
 }
 }
 
 
+/*
+Run a procedure with two polymorphic parameters on a different thread.
+
+This procedure runs the given procedure on another thread. The context
+specified by `init_context` will be used as the context in which `fn` is going
+to execute. The thread will have priority specified by the `priority` parameter.
+
+If `self_cleanup` is specified, after the thread finishes the execution of the
+`fn` procedure, the resources associated with the thread are going to be
+automatically freed. **Do not** dereference the `^Thread` pointer, if this
+flag is specified.
+
+**IMPORTANT**: If `init_context` is specified and the default temporary allocator
+is used, the thread procedure needs to call `runtime.default_temp_allocator_destroy()`
+in order to free the resources associated with the temporary allocations.
+*/
 create_and_start_with_poly_data2 :: proc(arg1: $T1, arg2: $T2, fn: proc(T1, T2), init_context: Maybe(runtime.Context) = nil, priority := Thread_Priority.Normal, self_cleanup := false) -> ^Thread
 create_and_start_with_poly_data2 :: proc(arg1: $T1, arg2: $T2, fn: proc(T1, T2), init_context: Maybe(runtime.Context) = nil, priority := Thread_Priority.Normal, self_cleanup := false) -> ^Thread
 	where size_of(T1) + size_of(T2) <= size_of(rawptr) * MAX_USER_ARGUMENTS {
 	where size_of(T1) + size_of(T2) <= size_of(rawptr) * MAX_USER_ARGUMENTS {
 	thread_proc :: proc(t: ^Thread) {
 	thread_proc :: proc(t: ^Thread) {
@@ -232,6 +402,22 @@ create_and_start_with_poly_data2 :: proc(arg1: $T1, arg2: $T2, fn: proc(T1, T2),
 	return t
 	return t
 }
 }
 
 
+/*
+Run a procedure with three polymorphic parameters on a different thread.
+
+This procedure runs the given procedure on another thread. The context
+specified by `init_context` will be used as the context in which `fn` is going
+to execute. The thread will have priority specified by the `priority` parameter.
+
+If `self_cleanup` is specified, after the thread finishes the execution of the
+`fn` procedure, the resources associated with the thread are going to be
+automatically freed. **Do not** dereference the `^Thread` pointer, if this
+flag is specified.
+
+**IMPORTANT**: If `init_context` is specified and the default temporary allocator
+is used, the thread procedure needs to call `runtime.default_temp_allocator_destroy()`
+in order to free the resources associated with the temporary allocations.
+*/
 create_and_start_with_poly_data3 :: proc(arg1: $T1, arg2: $T2, arg3: $T3, fn: proc(arg1: T1, arg2: T2, arg3: T3), init_context: Maybe(runtime.Context) = nil, priority := Thread_Priority.Normal, self_cleanup := false) -> ^Thread
 create_and_start_with_poly_data3 :: proc(arg1: $T1, arg2: $T2, arg3: $T3, fn: proc(arg1: T1, arg2: T2, arg3: T3), init_context: Maybe(runtime.Context) = nil, priority := Thread_Priority.Normal, self_cleanup := false) -> ^Thread
 	where size_of(T1) + size_of(T2) + size_of(T3) <= size_of(rawptr) * MAX_USER_ARGUMENTS {
 	where size_of(T1) + size_of(T2) + size_of(T3) <= size_of(rawptr) * MAX_USER_ARGUMENTS {
 	thread_proc :: proc(t: ^Thread) {
 	thread_proc :: proc(t: ^Thread) {
@@ -264,6 +450,23 @@ create_and_start_with_poly_data3 :: proc(arg1: $T1, arg2: $T2, arg3: $T3, fn: pr
 	start(t)
 	start(t)
 	return t
 	return t
 }
 }
+
+/*
+Run a procedure with four polymorphic parameters on a different thread.
+
+This procedure runs the given procedure on another thread. The context
+specified by `init_context` will be used as the context in which `fn` is going
+to execute. The thread will have priority specified by the `priority` parameter.
+
+If `self_cleanup` is specified, after the thread finishes the execution of the
+`fn` procedure, the resources associated with the thread are going to be
+automatically freed. **Do not** dereference the `^Thread` pointer, if this
+flag is specified.
+
+**IMPORTANT**: If `init_context` is specified and the default temporary allocator
+is used, the thread procedure needs to call `runtime.default_temp_allocator_destroy()`
+in order to free the resources associated with the temporary allocations.
+*/
 create_and_start_with_poly_data4 :: proc(arg1: $T1, arg2: $T2, arg3: $T3, arg4: $T4, fn: proc(arg1: T1, arg2: T2, arg3: T3, arg4: T4), init_context: Maybe(runtime.Context) = nil, priority := Thread_Priority.Normal, self_cleanup := false) -> ^Thread
 create_and_start_with_poly_data4 :: proc(arg1: $T1, arg2: $T2, arg3: $T3, arg4: $T4, fn: proc(arg1: T1, arg2: T2, arg3: T3, arg4: T4), init_context: Maybe(runtime.Context) = nil, priority := Thread_Priority.Normal, self_cleanup := false) -> ^Thread
 	where size_of(T1) + size_of(T2) + size_of(T3) + size_of(T4) <= size_of(rawptr) * MAX_USER_ARGUMENTS {
 	where size_of(T1) + size_of(T2) + size_of(T3) + size_of(T4) <= size_of(rawptr) * MAX_USER_ARGUMENTS {
 	thread_proc :: proc(t: ^Thread) {
 	thread_proc :: proc(t: ^Thread) {

+ 5 - 3
core/thread/thread_unix.odin

@@ -81,9 +81,12 @@ _create :: proc(procedure: Thread_Proc, priority: Thread_Priority) -> ^Thread {
 	defer unix.pthread_attr_destroy(&attrs)
 	defer unix.pthread_attr_destroy(&attrs)
 
 
 	// NOTE(tetra, 2019-11-01): These only fail if their argument is invalid.
 	// NOTE(tetra, 2019-11-01): These only fail if their argument is invalid.
-	assert(unix.pthread_attr_setdetachstate(&attrs, unix.PTHREAD_CREATE_JOINABLE) == 0)
+	res: i32
+	res = unix.pthread_attr_setdetachstate(&attrs, unix.PTHREAD_CREATE_JOINABLE)
+	assert(res == 0)
 	when ODIN_OS != .Haiku && ODIN_OS != .NetBSD {
 	when ODIN_OS != .Haiku && ODIN_OS != .NetBSD {
-		assert(unix.pthread_attr_setinheritsched(&attrs, unix.PTHREAD_EXPLICIT_SCHED) == 0)
+		res = unix.pthread_attr_setinheritsched(&attrs, unix.PTHREAD_EXPLICIT_SCHED)
+		assert(res == 0)
 	}
 	}
 
 
 	thread := new(Thread)
 	thread := new(Thread)
@@ -94,7 +97,6 @@ _create :: proc(procedure: Thread_Proc, priority: Thread_Priority) -> ^Thread {
 
 
 	// Set thread priority.
 	// Set thread priority.
 	policy: i32
 	policy: i32
-	res: i32
 	when ODIN_OS != .Haiku && ODIN_OS != .NetBSD {
 	when ODIN_OS != .Haiku && ODIN_OS != .NetBSD {
 		res = unix.pthread_attr_getschedpolicy(&attrs, &policy)
 		res = unix.pthread_attr_getschedpolicy(&attrs, &policy)
 		assert(res == 0)
 		assert(res == 0)

+ 61 - 5
core/time/datetime/constants.odin

@@ -1,16 +1,46 @@
 package datetime
 package datetime
 
 
-// Ordinal 1 = Midnight Monday, January 1, 1 A.D. (Gregorian)
-//         |   Midnight Monday, January 3, 1 A.D. (Julian)
+/*
+Type representing a mononotic day number corresponding to a date.
+
+	Ordinal 1 = Midnight Monday, January 1, 1 A.D. (Gregorian)
+	        |   Midnight Monday, January 3, 1 A.D. (Julian)
+*/
 Ordinal :: i64
 Ordinal :: i64
+
+/*
+*/
 EPOCH   :: Ordinal(1)
 EPOCH   :: Ordinal(1)
 
 
-// Minimum and maximum dates and ordinals. Chosen for safe roundtripping.
+/*
+Minimum valid value for date.
+
+The value is chosen such that a conversion `date -> ordinal -> date` is always
+safe.
+*/
 MIN_DATE :: Date{year = -25_252_734_927_766_552, month =  1, day =  1}
 MIN_DATE :: Date{year = -25_252_734_927_766_552, month =  1, day =  1}
+
+/*
+Maximum valid value for date
+
+The value is chosen such that a conversion `date -> ordinal -> date` is always
+safe.
+*/
 MAX_DATE :: Date{year =  25_252_734_927_766_552, month = 12, day = 31}
 MAX_DATE :: Date{year =  25_252_734_927_766_552, month = 12, day = 31}
+
+/*
+Minimum value for an ordinal
+*/
 MIN_ORD  :: Ordinal(-9_223_372_036_854_775_234)
 MIN_ORD  :: Ordinal(-9_223_372_036_854_775_234)
+
+/*
+Maximum value for an ordinal
+*/
 MAX_ORD  :: Ordinal( 9_223_372_036_854_774_869)
 MAX_ORD  :: Ordinal( 9_223_372_036_854_774_869)
 
 
+/*
+Possible errors returned by datetime functions.
+*/
 Error :: enum {
 Error :: enum {
 	None,
 	None,
 	Invalid_Year,
 	Invalid_Year,
@@ -24,12 +54,22 @@ Error :: enum {
 	Invalid_Delta,
 	Invalid_Delta,
 }
 }
 
 
+/*
+A type representing a date.
+
+The minimum and maximum values for a year can be found in `MIN_DATE` and
+`MAX_DATE` constants. The `month` field can range from 1 to 12, and the day
+ranges from 1 to however many days there are in the specified month.
+*/
 Date :: struct {
 Date :: struct {
 	year:   i64,
 	year:   i64,
 	month:  i8,
 	month:  i8,
 	day:    i8,
 	day:    i8,
 }
 }
 
 
+/*
+A type representing a time within a single day within a nanosecond precision.
+*/
 Time :: struct {
 Time :: struct {
 	hour:   i8,
 	hour:   i8,
 	minute: i8,
 	minute: i8,
@@ -37,17 +77,30 @@ Time :: struct {
 	nano:   i32,
 	nano:   i32,
 }
 }
 
 
+/*
+A type representing datetime.
+*/
 DateTime :: struct {
 DateTime :: struct {
 	using date: Date,
 	using date: Date,
 	using time: Time,
 	using time: Time,
 }
 }
 
 
+/*
+A type representing a difference between two instances of datetime.
+
+**Note**: All fields are i64 because we can also use it to add a number of
+seconds or nanos to a moment, that are then normalized within their respective
+ranges.
+*/
 Delta :: struct {
 Delta :: struct {
-	days:    i64, // These are all i64 because we can also use it to add a number of seconds or nanos to a moment,
-	seconds: i64, // that are then normalized within their respective ranges.
+	days:    i64, 
+	seconds: i64, 
 	nanos:   i64,
 	nanos:   i64,
 }
 }
 
 
+/*
+Type representing one of the months.
+*/
 Month :: enum i8 {
 Month :: enum i8 {
 	January = 1,
 	January = 1,
 	February,
 	February,
@@ -63,6 +116,9 @@ Month :: enum i8 {
 	December,
 	December,
 }
 }
 
 
+/*
+Type representing one of the weekdays.
+*/
 Weekday :: enum i8 {
 Weekday :: enum i8 {
 	Sunday = 0,
 	Sunday = 0,
 	Monday,
 	Monday,

+ 168 - 4
core/time/datetime/datetime.odin

@@ -1,56 +1,113 @@
 /*
 /*
-	Calendrical conversions using a proleptic Gregorian calendar.
+Calendrical conversions using a proleptic Gregorian calendar.
 
 
-	Implemented using formulas from: Calendrical Calculations Ultimate Edition, Reingold & Dershowitz
+Implemented using formulas from: Calendrical Calculations Ultimate Edition,
+Reingold & Dershowitz
 */
 */
 package datetime
 package datetime
 
 
 import "base:intrinsics"
 import "base:intrinsics"
 
 
-// Procedures that return an Ordinal
+/*
+Obtain an ordinal from a date.
 
 
+This procedure converts the specified date into an ordinal. If the specified
+date is not a valid date, an error is returned.
+*/
 date_to_ordinal :: proc "contextless" (date: Date) -> (ordinal: Ordinal, err: Error) {
 date_to_ordinal :: proc "contextless" (date: Date) -> (ordinal: Ordinal, err: Error) {
 	validate(date) or_return
 	validate(date) or_return
 	return unsafe_date_to_ordinal(date), .None
 	return unsafe_date_to_ordinal(date), .None
 }
 }
 
 
+/*
+Obtain an ordinal from date components.
+
+This procedure converts the specified date, provided by its individual
+components, into an ordinal. If the specified date is not a valid date, an error
+is returned.
+*/
 components_to_ordinal :: proc "contextless" (#any_int year, #any_int month, #any_int day: i64) -> (ordinal: Ordinal, err: Error) {
 components_to_ordinal :: proc "contextless" (#any_int year, #any_int month, #any_int day: i64) -> (ordinal: Ordinal, err: Error) {
 	validate(year, month, day) or_return
 	validate(year, month, day) or_return
 	return unsafe_date_to_ordinal({year, i8(month), i8(day)}), .None
 	return unsafe_date_to_ordinal({year, i8(month), i8(day)}), .None
 }
 }
 
 
-// Procedures that return a Date
+/*
+Obtain date using an Ordinal.
 
 
+This provedure converts the specified ordinal into a date. If the ordinal is not
+a valid ordinal, an error is returned.
+*/
 ordinal_to_date :: proc "contextless" (ordinal: Ordinal) -> (date: Date, err: Error) {
 ordinal_to_date :: proc "contextless" (ordinal: Ordinal) -> (date: Date, err: Error) {
 	validate(ordinal) or_return
 	validate(ordinal) or_return
 	return unsafe_ordinal_to_date(ordinal), .None
 	return unsafe_ordinal_to_date(ordinal), .None
 }
 }
 
 
+/*
+Obtain a date from date components.
+
+This procedure converts date components, specified by a year, a month and a day,
+into a date object. If the provided date components don't represent a valid
+date, an error is returned.
+*/
 components_to_date :: proc "contextless" (#any_int year, #any_int month, #any_int day: i64) -> (date: Date, err: Error) {
 components_to_date :: proc "contextless" (#any_int year, #any_int month, #any_int day: i64) -> (date: Date, err: Error) {
 	validate(year, month, day) or_return
 	validate(year, month, day) or_return
 	return Date{i64(year), i8(month), i8(day)}, .None
 	return Date{i64(year), i8(month), i8(day)}, .None
 }
 }
 
 
+/*
+Obtain time from time components.
+
+This procedure converts time components, specified by an hour, a minute, a second
+and nanoseconds, into a time object. If the provided time components don't
+represent a valid time, an error is returned.
+*/
 components_to_time :: proc "contextless" (#any_int hour, #any_int minute, #any_int second: i64, #any_int nanos := i64(0)) -> (time: Time, err: Error) {
 components_to_time :: proc "contextless" (#any_int hour, #any_int minute, #any_int second: i64, #any_int nanos := i64(0)) -> (time: Time, err: Error) {
 	validate(hour, minute, second, nanos) or_return
 	validate(hour, minute, second, nanos) or_return
 	return Time{i8(hour), i8(minute), i8(second), i32(nanos)}, .None
 	return Time{i8(hour), i8(minute), i8(second), i32(nanos)}, .None
 }
 }
 
 
+/*
+Obtain datetime from components.
+
+This procedure converts date components and time components into a datetime object.
+If the provided date components or time components don't represent a valid
+datetime, an error is returned.
+*/
 components_to_datetime :: proc "contextless" (#any_int year, #any_int month, #any_int day, #any_int hour, #any_int minute, #any_int second: i64, #any_int nanos := i64(0)) -> (datetime: DateTime, err: Error) {
 components_to_datetime :: proc "contextless" (#any_int year, #any_int month, #any_int day, #any_int hour, #any_int minute, #any_int second: i64, #any_int nanos := i64(0)) -> (datetime: DateTime, err: Error) {
 	date := components_to_date(year, month, day)            or_return
 	date := components_to_date(year, month, day)            or_return
 	time := components_to_time(hour, minute, second, nanos) or_return
 	time := components_to_time(hour, minute, second, nanos) or_return
 	return {date, time}, .None
 	return {date, time}, .None
 }
 }
 
 
+/*
+Obtain an datetime from an ordinal.
+
+This procedure converts the value of an ordinal into a datetime. Since the
+ordinal only has the amount of days, the resulting time in the datetime
+object will always have the time equal to `00:00:00.000`.
+*/
 ordinal_to_datetime :: proc "contextless" (ordinal: Ordinal) -> (datetime: DateTime, err: Error) {
 ordinal_to_datetime :: proc "contextless" (ordinal: Ordinal) -> (datetime: DateTime, err: Error) {
 	d := ordinal_to_date(ordinal) or_return
 	d := ordinal_to_date(ordinal) or_return
 	return {Date(d), {}}, .None
 	return {Date(d), {}}, .None
 }
 }
 
 
+/*
+Calculate the weekday from an ordinal.
+
+This procedure takes the value of an ordinal and returns the day of week for
+that ordinal.
+*/
 day_of_week :: proc "contextless" (ordinal: Ordinal) -> (day: Weekday) {
 day_of_week :: proc "contextless" (ordinal: Ordinal) -> (day: Weekday) {
 	return Weekday((ordinal - EPOCH + 1) %% 7)
 	return Weekday((ordinal - EPOCH + 1) %% 7)
 }
 }
 
 
+/*
+Calculate the difference between two dates.
+
+This procedure calculates the difference between two dates `a - b`, and returns
+a delta between the two dates in `days`. If either `a` or `b` is not a valid
+date, an error is returned.
+*/
 subtract_dates :: proc "contextless" (a, b: Date) -> (delta: Delta, err: Error) {
 subtract_dates :: proc "contextless" (a, b: Date) -> (delta: Delta, err: Error) {
 	ord_a := date_to_ordinal(a) or_return
 	ord_a := date_to_ordinal(a) or_return
 	ord_b := date_to_ordinal(b) or_return
 	ord_b := date_to_ordinal(b) or_return
@@ -59,6 +116,16 @@ subtract_dates :: proc "contextless" (a, b: Date) -> (delta: Delta, err: Error)
 	return
 	return
 }
 }
 
 
+/*
+Calculate the difference between two datetimes.
+
+This procedure calculates the difference between two datetimes, `a - b`, and
+returns a delta between the two dates. The difference is returned in all three
+fields of the `Delta` struct: the difference in days, the difference in seconds
+and the difference in nanoseconds.
+
+If either `a` or `b` is not a valid datetime, an error is returned.
+*/
 subtract_datetimes :: proc "contextless" (a, b: DateTime) -> (delta: Delta, err: Error) {
 subtract_datetimes :: proc "contextless" (a, b: DateTime) -> (delta: Delta, err: Error) {
 	ord_a := date_to_ordinal(a) or_return
 	ord_a := date_to_ordinal(a) or_return
 	ord_b := date_to_ordinal(b) or_return
 	ord_b := date_to_ordinal(b) or_return
@@ -73,19 +140,42 @@ subtract_datetimes :: proc "contextless" (a, b: DateTime) -> (delta: Delta, err:
 	return
 	return
 }
 }
 
 
+/*
+Calculate a difference between two deltas.
+*/
 subtract_deltas :: proc "contextless" (a, b: Delta) -> (delta: Delta, err: Error) {
 subtract_deltas :: proc "contextless" (a, b: Delta) -> (delta: Delta, err: Error) {
 	delta = Delta{a.days - b.days, a.seconds - b.seconds, a.nanos - b.nanos}
 	delta = Delta{a.days - b.days, a.seconds - b.seconds, a.nanos - b.nanos}
 	delta = normalize_delta(delta) or_return
 	delta = normalize_delta(delta) or_return
 	return
 	return
 }
 }
+
+/*
+Calculate a difference between two datetimes, dates or deltas.
+*/
 sub :: proc{subtract_datetimes, subtract_dates, subtract_deltas}
 sub :: proc{subtract_datetimes, subtract_dates, subtract_deltas}
 
 
+/*
+Add certain amount of days to a date.
+
+This procedure adds the specified amount of days to a date and returns a new
+date. The new date would have happened the specified amount of days after the
+specified date.
+*/
 add_days_to_date :: proc "contextless" (a: Date, days: i64) -> (date: Date, err: Error) {
 add_days_to_date :: proc "contextless" (a: Date, days: i64) -> (date: Date, err: Error) {
 	ord := date_to_ordinal(a) or_return
 	ord := date_to_ordinal(a) or_return
 	ord += days
 	ord += days
 	return ordinal_to_date(ord)
 	return ordinal_to_date(ord)
 }
 }
 
 
+/*
+Add delta to a date.
+
+This procedure adds a delta to a date, and returns a new date. The new date
+would have happened the time specified by `delta` after the specified date.
+
+**Note**: The delta is assumed to be normalized. That is, if it contains seconds
+or milliseconds, regardless of the amount only the days will be added.
+*/
 add_delta_to_date :: proc "contextless" (a: Date, delta: Delta) -> (date: Date, err: Error) {
 add_delta_to_date :: proc "contextless" (a: Date, delta: Delta) -> (date: Date, err: Error) {
 	ord := date_to_ordinal(a) or_return
 	ord := date_to_ordinal(a) or_return
 	// Because the input is a Date, we add only the days from the Delta.
 	// Because the input is a Date, we add only the days from the Delta.
@@ -93,6 +183,13 @@ add_delta_to_date :: proc "contextless" (a: Date, delta: Delta) -> (date: Date,
 	return ordinal_to_date(ord)
 	return ordinal_to_date(ord)
 }
 }
 
 
+/*
+Add delta to datetime.
+
+This procedure adds a delta to a datetime, and returns a new datetime. The new
+datetime would have happened the time specified by `delta` after the specified
+datetime. 
+*/
 add_delta_to_datetime :: proc "contextless" (a: DateTime, delta: Delta) -> (datetime: DateTime, err: Error) {
 add_delta_to_datetime :: proc "contextless" (a: DateTime, delta: Delta) -> (datetime: DateTime, err: Error) {
 	days   := date_to_ordinal(a) or_return
 	days   := date_to_ordinal(a) or_return
 
 
@@ -110,8 +207,18 @@ add_delta_to_datetime :: proc "contextless" (a: DateTime, delta: Delta) -> (date
 	datetime.time = components_to_time(hour, minute, second, sum_delta.nanos) or_return
 	datetime.time = components_to_time(hour, minute, second, sum_delta.nanos) or_return
 	return
 	return
 }
 }
+
+/*
+Add days to a date, delta to a date or delta to datetime.
+*/
 add :: proc{add_days_to_date, add_delta_to_date, add_delta_to_datetime}
 add :: proc{add_days_to_date, add_delta_to_date, add_delta_to_datetime}
 
 
+/*
+Obtain the day number in a year
+
+This procedure returns the number of the day in a year, starting from 1. If
+the date is not a valid date, an error is returned.
+*/
 day_number :: proc "contextless" (date: Date) -> (day_number: i64, err: Error) {
 day_number :: proc "contextless" (date: Date) -> (day_number: i64, err: Error) {
 	validate(date) or_return
 	validate(date) or_return
 
 
@@ -120,6 +227,13 @@ day_number :: proc "contextless" (date: Date) -> (day_number: i64, err: Error) {
 	return
 	return
 }
 }
 
 
+/*
+Obtain the remaining number of days in a year.
+
+This procedure returns the number of days between the specified date and
+December 31 of the same year. If the date is not a valid date, an error is
+returned.
+*/
 days_remaining :: proc "contextless" (date: Date) -> (days_remaining: i64, err: Error) {
 days_remaining :: proc "contextless" (date: Date) -> (days_remaining: i64, err: Error) {
 	// Alternative formulation `day_number` subtracted from 365 or 366 depending on leap year
 	// Alternative formulation `day_number` subtracted from 365 or 366 depending on leap year
 	validate(date) or_return
 	validate(date) or_return
@@ -127,6 +241,12 @@ days_remaining :: proc "contextless" (date: Date) -> (days_remaining: i64, err:
 	return delta.days, .None
 	return delta.days, .None
 }
 }
 
 
+/*
+Obtain the last day of a given month on a given year.
+
+This procedure returns the amount of days in a specified month on a specified
+date. If the specified year or month is not valid, an error is returned.
+*/
 last_day_of_month :: proc "contextless" (#any_int year: i64, #any_int month: i8) -> (day: i8, err: Error) {
 last_day_of_month :: proc "contextless" (#any_int year: i64, #any_int month: i8) -> (day: i8, err: Error) {
 	// Not using formula 2.27 from the book. This is far simpler and gives the same answer.
 	// Not using formula 2.27 from the book. This is far simpler and gives the same answer.
 
 
@@ -140,16 +260,33 @@ last_day_of_month :: proc "contextless" (#any_int year: i64, #any_int month: i8)
 	return
 	return
 }
 }
 
 
+/*
+Obtain the new year date of a given year.
+
+This procedure returns the January 1st date of the specified year. If the year
+is not valid, an error is returned.
+*/
 new_year :: proc "contextless" (#any_int year: i64) -> (new_year: Date, err: Error) {
 new_year :: proc "contextless" (#any_int year: i64) -> (new_year: Date, err: Error) {
 	validate(year, 1, 1) or_return
 	validate(year, 1, 1) or_return
 	return {year, 1, 1}, .None
 	return {year, 1, 1}, .None
 }
 }
 
 
+/*
+Obtain the end year of a given date.
+
+This procedure returns the December 31st date of the specified year. If the year
+is not valid, an error is returned.
+*/
 year_end :: proc "contextless" (#any_int year: i64) -> (year_end: Date, err: Error) {
 year_end :: proc "contextless" (#any_int year: i64) -> (year_end: Date, err: Error) {
 	validate(year, 12, 31) or_return
 	validate(year, 12, 31) or_return
 	return {year, 12, 31}, .None
 	return {year, 12, 31}, .None
 }
 }
 
 
+/*
+Obtain the range of dates for a given year.
+
+This procedure returns dates, for every day of a given year in a slice.
+*/
 year_range :: proc (#any_int year: i64, allocator := context.allocator) -> (range: []Date) {
 year_range :: proc (#any_int year: i64, allocator := context.allocator) -> (range: []Date) {
 	is_leap := is_leap_year(year)
 	is_leap := is_leap_year(year)
 
 
@@ -171,6 +308,15 @@ year_range :: proc (#any_int year: i64, allocator := context.allocator) -> (rang
 	return
 	return
 }
 }
 
 
+/*
+Normalize the delta.
+
+This procedure normalizes the delta in such a way that the number of seconds
+is between 0 and the number of seconds in the day and nanoseconds is between
+0 and 10^9.
+
+If the value for `days` overflows during this operation, an error is returned.
+*/
 normalize_delta :: proc "contextless" (delta: Delta) -> (normalized: Delta, err: Error) {
 normalize_delta :: proc "contextless" (delta: Delta) -> (normalized: Delta, err: Error) {
 	// Distribute nanos into seconds and remainder
 	// Distribute nanos into seconds and remainder
 	seconds, nanos := divmod(delta.nanos, 1e9)
 	seconds, nanos := divmod(delta.nanos, 1e9)
@@ -194,6 +340,12 @@ normalize_delta :: proc "contextless" (delta: Delta) -> (normalized: Delta, err:
 // The following procedures don't check whether their inputs are in a valid range.
 // The following procedures don't check whether their inputs are in a valid range.
 // They're still exported for those who know their inputs have been validated.
 // They're still exported for those who know their inputs have been validated.
 
 
+/*
+Obtain an ordinal from a date.
+
+This procedure converts a date into an ordinal. If the date is not a valid date,
+the result is unspecified.
+*/
 unsafe_date_to_ordinal :: proc "contextless" (date: Date) -> (ordinal: Ordinal) {
 unsafe_date_to_ordinal :: proc "contextless" (date: Date) -> (ordinal: Ordinal) {
 	year_minus_one := date.year - 1
 	year_minus_one := date.year - 1
 
 
@@ -223,6 +375,12 @@ unsafe_date_to_ordinal :: proc "contextless" (date: Date) -> (ordinal: Ordinal)
 	return
 	return
 }
 }
 
 
+/*
+Obtain a year and a day of the year from an ordinal.
+
+This procedure returns the year and the day of the year of a given ordinal.
+Of the ordinal is outside of its valid range, the result is unspecified.
+*/
 unsafe_ordinal_to_year :: proc "contextless" (ordinal: Ordinal) -> (year: i64, day_ordinal: i64) {
 unsafe_ordinal_to_year :: proc "contextless" (ordinal: Ordinal) -> (year: i64, day_ordinal: i64) {
 	// Days after epoch
 	// Days after epoch
 	d0   := ordinal - EPOCH
 	d0   := ordinal - EPOCH
@@ -253,6 +411,12 @@ unsafe_ordinal_to_year :: proc "contextless" (ordinal: Ordinal) -> (year: i64, d
 	return year + 1, day_ordinal
 	return year + 1, day_ordinal
 }
 }
 
 
+/*
+Obtain a date from an ordinal.
+
+This procedure converts an ordinal into a date. If the ordinal is outside of
+its valid range, the result is unspecified.
+*/
 unsafe_ordinal_to_date :: proc "contextless" (ordinal: Ordinal) -> (date: Date) {
 unsafe_ordinal_to_date :: proc "contextless" (ordinal: Ordinal) -> (date: Date) {
 	year, _ := unsafe_ordinal_to_year(ordinal)
 	year, _ := unsafe_ordinal_to_year(ordinal)
 
 

+ 1 - 0
core/time/datetime/internal.odin

@@ -1,3 +1,4 @@
+//+private
 package datetime
 package datetime
 
 
 // Internal helper functions for calendrical conversions
 // Internal helper functions for calendrical conversions

+ 43 - 1
core/time/datetime/validation.odin

@@ -1,14 +1,29 @@
 package datetime
 package datetime
-
 // Validation helpers
 // Validation helpers
+
+/*
+Check if a year is a leap year.
+*/
 is_leap_year :: proc "contextless" (#any_int year: i64) -> (leap: bool) {
 is_leap_year :: proc "contextless" (#any_int year: i64) -> (leap: bool) {
 	return year % 4 == 0 && (year % 100 != 0 || year % 400 == 0)
 	return year % 4 == 0 && (year % 100 != 0 || year % 400 == 0)
 }
 }
 
 
+/*
+Check for errors in date formation.
+
+This procedure validates all fields of a date, and if any of the fields is
+outside of allowed range, an error is returned.
+*/
 validate_date :: proc "contextless" (date: Date) -> (err: Error) {
 validate_date :: proc "contextless" (date: Date) -> (err: Error) {
 	return validate(date.year, date.month, date.day)
 	return validate(date.year, date.month, date.day)
 }
 }
 
 
+/*
+Check for errors in date formation given date components.
+
+This procedure checks whether a date formed by the specified year month and a
+day is a valid date. If not, an error is returned.
+*/
 validate_year_month_day :: proc "contextless" (#any_int year, #any_int month, #any_int day: i64) -> (err: Error) {
 validate_year_month_day :: proc "contextless" (#any_int year, #any_int month, #any_int day: i64) -> (err: Error) {
 	if year < MIN_DATE.year || year > MAX_DATE.year {
 	if year < MIN_DATE.year || year > MAX_DATE.year {
 		return .Invalid_Year
 		return .Invalid_Year
@@ -29,6 +44,12 @@ validate_year_month_day :: proc "contextless" (#any_int year, #any_int month, #a
 	return .None
 	return .None
 }
 }
 
 
+/*
+Check for errors in Ordinal
+
+This procedure checks if the ordinal is in a valid range for roundtrip
+conversions with the dates. If not, an error is returned.
+*/
 validate_ordinal :: proc "contextless" (ordinal: Ordinal) -> (err: Error) {
 validate_ordinal :: proc "contextless" (ordinal: Ordinal) -> (err: Error) {
 	if ordinal < MIN_ORD || ordinal > MAX_ORD {
 	if ordinal < MIN_ORD || ordinal > MAX_ORD {
 		return .Invalid_Ordinal
 		return .Invalid_Ordinal
@@ -36,10 +57,22 @@ validate_ordinal :: proc "contextless" (ordinal: Ordinal) -> (err: Error) {
 	return
 	return
 }
 }
 
 
+/*
+Check for errors in time formation
+
+This procedure checks whether time has all fields in valid ranges, and if not
+an error is returned.
+*/
 validate_time :: proc "contextless" (time: Time) -> (err: Error) {
 validate_time :: proc "contextless" (time: Time) -> (err: Error) {
 	return validate(time.hour, time.minute, time.second, time.nano)
 	return validate(time.hour, time.minute, time.second, time.nano)
 }
 }
 
 
+/*
+Check for errors in time formed by its components.
+
+This procedure checks whether the time formed by its components is valid, and
+if not an error is returned.
+*/
 validate_hour_minute_second :: proc "contextless" (#any_int hour, #any_int minute, #any_int second, #any_int nano: i64) -> (err: Error) {
 validate_hour_minute_second :: proc "contextless" (#any_int hour, #any_int minute, #any_int second, #any_int nano: i64) -> (err: Error) {
 	if hour < 0 || hour > 23 {
 	if hour < 0 || hour > 23 {
 		return .Invalid_Hour
 		return .Invalid_Hour
@@ -56,12 +89,21 @@ validate_hour_minute_second :: proc "contextless" (#any_int hour, #any_int minut
 	return .None
 	return .None
 }
 }
 
 
+/*
+Check for errors in datetime formation.
+
+This procedure checks whether all fields of date and time in the specified
+datetime are valid, and if not, an error is returned.
+*/
 validate_datetime :: proc "contextless" (datetime: DateTime) -> (err: Error) {
 validate_datetime :: proc "contextless" (datetime: DateTime) -> (err: Error) {
 	validate(datetime.date) or_return
 	validate(datetime.date) or_return
 	validate(datetime.time) or_return
 	validate(datetime.time) or_return
 	return .None
 	return .None
 }
 }
 
 
+/*
+Check for errors in date, time or datetime.
+*/
 validate :: proc{
 validate :: proc{
 	validate_date,
 	validate_date,
 	validate_year_month_day,
 	validate_year_month_day,

+ 75 - 13
core/time/iso8601.odin

@@ -3,23 +3,62 @@ package time
 
 
 import dt "core:time/datetime"
 import dt "core:time/datetime"
 
 
-// Parses an ISO 8601 string and returns Time in UTC, with any UTC offset applied to it.
-// Only 4-digit years are accepted.
-// Optional pointer to boolean `is_leap` will return `true` if the moment was a leap second.
-// Leap seconds are smeared into 23:59:59.
+/*
+Parse an ISO 8601 string into a time with UTC offset applied to it.
+
+This procedure parses an ISO 8601 string of roughly the following format:
+
+```text
+YYYY-MM-DD[Tt]HH:mm:ss[.nn][Zz][+-]HH:mm
+```
+
+And returns time, in UTC represented by that string. In case the timezone offset
+is specified in the string, that timezone is applied to time.
+
+**Inputs**:
+- `iso_datetime`: The string to be parsed.
+- `is_leap`: Optional output parameter, specifying if the moment was a leap second.
+
+**Returns**:
+- `res`: The time represented by `iso_datetime`, with UTC offset applied.
+- `consumed`: Number of bytes consumed by parsing the string.
+
+**Notes**:
+- Only 4-digit years are accepted.
+- Leap seconds are smeared into 23:59:59.
+*/
 iso8601_to_time_utc :: proc(iso_datetime: string, is_leap: ^bool = nil) -> (res: Time, consumed: int) {
 iso8601_to_time_utc :: proc(iso_datetime: string, is_leap: ^bool = nil) -> (res: Time, consumed: int) {
 	offset: int
 	offset: int
-
 	res, offset, consumed = iso8601_to_time_and_offset(iso_datetime, is_leap)
 	res, offset, consumed = iso8601_to_time_and_offset(iso_datetime, is_leap)
 	res._nsec += (i64(-offset) * i64(Minute))
 	res._nsec += (i64(-offset) * i64(Minute))
 	return res, consumed
 	return res, consumed
 }
 }
 
 
-// Parses an ISO 8601 string and returns Time and a UTC offset in minutes.
-// e.g. 1985-04-12T23:20:50.52Z
-// Note: Only 4-digit years are accepted.
-// Optional pointer to boolean `is_leap` will return `true` if the moment was a leap second.
-// Leap seconds are smeared into 23:59:59.
+/*
+Parse an ISO 8601 string into a time and a UTC offset in minutes.
+
+This procedure parses an ISO 8601 string of roughly the following format:
+
+```text
+YYYY-MM-DD[Tt]HH:mm:ss[.nn][Zz][+-]HH:mm
+```
+
+And returns time, in UTC represented by that string, and the UTC offset, in
+minutes.
+
+**Inputs**:
+- `iso_datetime`: The string to be parsed.
+- `is_leap`: Optional output parameter, specifying if the moment was a leap second.
+
+**Returns**:
+- `res`: The time in UTC.
+- `utc_offset`: The UTC offset of the time, in minutes.
+- `consumed`: Number of bytes consumed by parsing the string.
+
+**Notes**:
+- Only 4-digit years are accepted.
+- Leap seconds are smeared into 23:59:59.
+*/
 iso8601_to_time_and_offset :: proc(iso_datetime: string, is_leap: ^bool = nil) -> (res: Time, utc_offset: int, consumed: int) {
 iso8601_to_time_and_offset :: proc(iso_datetime: string, is_leap: ^bool = nil) -> (res: Time, utc_offset: int, consumed: int) {
 	moment, offset, leap_second, count := iso8601_to_components(iso_datetime)
 	moment, offset, leap_second, count := iso8601_to_components(iso_datetime)
 	if count == 0 {
 	if count == 0 {
@@ -37,9 +76,32 @@ iso8601_to_time_and_offset :: proc(iso_datetime: string, is_leap: ^bool = nil) -
 	}
 	}
 }
 }
 
 
-// Parses an ISO 8601 string and returns Time and a UTC offset in minutes.
-// e.g. 1985-04-12T23:20:50.52Z
-// Performs no validation on whether components are valid, e.g. it'll return hour = 25 if that's what it's given
+/*
+Parse an ISO 8601 string into a datetime and a UTC offset in minutes.
+
+This procedure parses an ISO 8601 string of roughly the following format:
+
+```text
+YYYY-MM-DD[Tt]HH:mm:ss[.nn][Zz][+-]HH:mm
+```
+
+And returns datetime, in UTC represented by that string, and the UTC offset, in
+minutes.
+
+**Inputs**:
+- `iso_datetime`: The string to be parsed
+
+**Returns**:
+- `res`: The parsed datetime, in UTC.
+- `utc_offset`: The UTC offset, in minutes.
+- `is_leap`: Specifies whether the moment was a leap second.
+- `consumed`: The number of bytes consumed by parsing the string.
+
+**Notes**:
+- This procedure performs no validation on whether components are valid,
+  e.g. it'll return hour = 25 if that's what it's given in the specified
+  string.
+*/
 iso8601_to_components :: proc(iso_datetime: string) -> (res: dt.DateTime, utc_offset: int, is_leap: bool, consumed: int) {
 iso8601_to_components :: proc(iso_datetime: string) -> (res: dt.DateTime, utc_offset: int, is_leap: bool, consumed: int) {
 	moment, offset, count, leap_second, ok := _iso8601_to_components(iso_datetime)
 	moment, offset, count, leap_second, ok := _iso8601_to_components(iso_datetime)
 	if !ok {
 	if !ok {

+ 80 - 10
core/time/perf.odin

@@ -3,18 +3,39 @@ package time
 import "base:runtime"
 import "base:runtime"
 import "base:intrinsics"
 import "base:intrinsics"
 
 
+/*
+Type representing monotonic time, useful for measuring durations.
+*/
 Tick :: struct {
 Tick :: struct {
 	_nsec: i64, // relative amount
 	_nsec: i64, // relative amount
 }
 }
+
+/*
+Obtain the current tick.
+*/
 tick_now :: proc "contextless" () -> Tick {
 tick_now :: proc "contextless" () -> Tick {
 	return _tick_now()
 	return _tick_now()
 }
 }
 
 
+/*
+Obtain the difference between ticks.
+*/
 tick_diff :: proc "contextless" (start, end: Tick) -> Duration {
 tick_diff :: proc "contextless" (start, end: Tick) -> Duration {
 	d := end._nsec - start._nsec
 	d := end._nsec - start._nsec
 	return Duration(d)
 	return Duration(d)
 }
 }
 
 
+/*
+Incrementally obtain durations since last tick.
+
+This procedure returns the duration between the current tick and the tick
+stored in `prev` pointer, and then stores the current tick in location,
+specified by `prev`. If the prev pointer contains an zero-initialized tick,
+then the returned duration is 0.
+
+This procedure is meant to be used in a loop, or in other scenarios, where one
+might want to obtain time between multiple ticks at specific points.
+*/
 tick_lap_time :: proc "contextless" (prev: ^Tick) -> Duration {
 tick_lap_time :: proc "contextless" (prev: ^Tick) -> Duration {
 	d: Duration
 	d: Duration
 	t := tick_now()
 	t := tick_now()
@@ -25,17 +46,21 @@ tick_lap_time :: proc "contextless" (prev: ^Tick) -> Duration {
 	return d
 	return d
 }
 }
 
 
+/*
+Obtain the duration since last tick.
+*/
 tick_since :: proc "contextless" (start: Tick) -> Duration {
 tick_since :: proc "contextless" (start: Tick) -> Duration {
 	return tick_diff(start, tick_now())
 	return tick_diff(start, tick_now())
 }
 }
 
 
-
+/*
+Capture the duration the code in the current scope takes to execute.
+*/
 @(deferred_in_out=_tick_duration_end)
 @(deferred_in_out=_tick_duration_end)
 SCOPED_TICK_DURATION :: proc "contextless" (d: ^Duration) -> Tick {
 SCOPED_TICK_DURATION :: proc "contextless" (d: ^Duration) -> Tick {
 	return tick_now()
 	return tick_now()
 }
 }
 
 
-
 _tick_duration_end :: proc "contextless" (d: ^Duration, t: Tick) {
 _tick_duration_end :: proc "contextless" (d: ^Duration, t: Tick) {
 	d^ = tick_since(t)
 	d^ = tick_since(t)
 }
 }
@@ -62,6 +87,13 @@ when ODIN_OS != .Darwin && ODIN_OS != .Linux && ODIN_OS != .FreeBSD {
 	}
 	}
 }
 }
 
 
+/*
+Check if the CPU has invariant TSC.
+
+This procedure checks if the CPU contains an invariant TSC (Time stamp counter).
+Invariant TSC is a feature of modern processors that allows them to run their
+TSC at a fixed frequency, independent of ACPI state, and CPU frequency.
+*/
 has_invariant_tsc :: proc "contextless" () -> bool {
 has_invariant_tsc :: proc "contextless" () -> bool {
 	when ODIN_ARCH == .amd64 {
 	when ODIN_ARCH == .amd64 {
 		return x86_has_invariant_tsc()
 		return x86_has_invariant_tsc()
@@ -70,6 +102,17 @@ has_invariant_tsc :: proc "contextless" () -> bool {
 	return false
 	return false
 }
 }
 
 
+/*
+Obtain the CPU's TSC frequency, in hertz.
+
+This procedure tries to obtain the CPU's TSC frequency in hertz. If the CPU
+doesn't have an invariant TSC, this procedure returns with an error. Otherwise
+an attempt is made to fetch the TSC frequency from the OS. If this fails,
+the frequency is obtained by sleeping for the specified amount of time and
+dividing the readings from TSC by the duration of the sleep.
+
+The duration of sleep can be controlled by `fallback_sleep` parameter.
+*/
 tsc_frequency :: proc "contextless" (fallback_sleep := 2 * Second) -> (u64, bool) {
 tsc_frequency :: proc "contextless" (fallback_sleep := 2 * Second) -> (u64, bool) {
 	if !has_invariant_tsc() {
 	if !has_invariant_tsc() {
 		return 0, false
 		return 0, false
@@ -93,37 +136,64 @@ tsc_frequency :: proc "contextless" (fallback_sleep := 2 * Second) -> (u64, bool
 	return hz, true
 	return hz, true
 }
 }
 
 
+// Benchmark helpers
+
 /*
 /*
-	Benchmark helpers
+Errors returned by the `benchmark()` procedure.
 */
 */
-
 Benchmark_Error :: enum {
 Benchmark_Error :: enum {
 	Okay = 0,
 	Okay = 0,
 	Allocation_Error,
 	Allocation_Error,
 }
 }
 
 
+/*
+Options for benchmarking.
+*/
 Benchmark_Options :: struct {
 Benchmark_Options :: struct {
+	// The initialization procedure. `benchmark()` will call this before taking measurements.
 	setup:     #type proc(options: ^Benchmark_Options, allocator: runtime.Allocator) -> (err: Benchmark_Error),
 	setup:     #type proc(options: ^Benchmark_Options, allocator: runtime.Allocator) -> (err: Benchmark_Error),
+	// The procedure to benchmark.
 	bench:     #type proc(options: ^Benchmark_Options, allocator: runtime.Allocator) -> (err: Benchmark_Error),
 	bench:     #type proc(options: ^Benchmark_Options, allocator: runtime.Allocator) -> (err: Benchmark_Error),
+	// The deinitialization procedure.
 	teardown:  #type proc(options: ^Benchmark_Options, allocator: runtime.Allocator) -> (err: Benchmark_Error),
 	teardown:  #type proc(options: ^Benchmark_Options, allocator: runtime.Allocator) -> (err: Benchmark_Error),
-
+	// Field to be used by `bench()` procedure for any purpose.
 	rounds:    int,
 	rounds:    int,
+	// Field to be used by `bench()` procedure for any purpose.
 	bytes:     int,
 	bytes:     int,
+	// Field to be used by `bench()` procedure for any purpose.
 	input:     []u8,
 	input:     []u8,
-
+	// `bench()` writes to specify the count of elements processed.
 	count:     int,
 	count:     int,
+	// `bench()` writes to specify the number of bytes processed.
 	processed: int,
 	processed: int,
+	// `bench()` can write the output slice here.
 	output:    []u8, // Unused for hash benchmarks
 	output:    []u8, // Unused for hash benchmarks
+	// `bench()` can write the output hash here.
 	hash:      u128,
 	hash:      u128,
-
-	/*
-		Performance
-	*/
+	// `benchmark()` procedure will output the duration of benchmark
 	duration:             Duration,
 	duration:             Duration,
+	// `benchmark()` procedure will output the average count of elements
+	// processed per second, using the `count` field of this struct.
 	rounds_per_second:    f64,
 	rounds_per_second:    f64,
+	// `benchmark()` procedure will output the average number of megabytes
+	// processed per second, using the `processed` field of this struct.
 	megabytes_per_second: f64,
 	megabytes_per_second: f64,
 }
 }
 
 
+/*
+Benchmark a procedure.
+
+This procedure produces a benchmark. The procedure specified in the `bench`
+field of the `options` parameter will be benchmarked. The following metrics
+can be obtained:
+
+- Run time of the procedure
+- Number of elements per second processed on average
+- Number of bytes per second this processed on average
+
+In order to obtain these metrics, the `bench()` procedure writes to `options`
+struct the number of elements or bytes it has processed.
+*/
 benchmark :: proc(options: ^Benchmark_Options, allocator := context.allocator) -> (err: Benchmark_Error) {
 benchmark :: proc(options: ^Benchmark_Options, allocator := context.allocator) -> (err: Benchmark_Error) {
 	assert(options != nil)
 	assert(options != nil)
 	assert(options.bench != nil)
 	assert(options.bench != nil)

+ 80 - 12
core/time/rfc3339.odin

@@ -4,10 +4,33 @@ package time
 
 
 import dt "core:time/datetime"
 import dt "core:time/datetime"
 
 
-// Parses an RFC 3339 string and returns Time in UTC, with any UTC offset applied to it.
-// Only 4-digit years are accepted.
-// Optional pointer to boolean `is_leap` will return `true` if the moment was a leap second.
-// Leap seconds are smeared into 23:59:59.
+/*
+Parse an RFC 3339 string into time with a UTC offset applied to it.
+
+This procedure parses the specified RFC 3339 strings of roughly the following
+format:
+
+```text
+YYYY-MM-DD[Tt]HH:mm:ss[.nn][Zz][+-]HH:mm
+```
+
+And returns the time that was represented by the RFC 3339 string, with the UTC
+offset applied to it.
+
+**Inputs**:
+- `rfc_datetime`: An RFC 3339 string to parse.
+- `is_leap`: Optional output parameter specifying whether the moment was a leap
+  second.
+
+**Returns**:
+- `res`: The time, with UTC offset applied, that was parsed from the RFC 3339
+  string.
+- `consumed`: The number of bytes consumed by parsing the RFC 3339 string.
+
+**Notes**:
+- Only 4-digit years are accepted.
+- Leap seconds are smeared into 23:59:59.
+*/
 rfc3339_to_time_utc :: proc(rfc_datetime: string, is_leap: ^bool = nil) -> (res: Time, consumed: int) {
 rfc3339_to_time_utc :: proc(rfc_datetime: string, is_leap: ^bool = nil) -> (res: Time, consumed: int) {
 	offset: int
 	offset: int
 
 
@@ -16,11 +39,34 @@ rfc3339_to_time_utc :: proc(rfc_datetime: string, is_leap: ^bool = nil) -> (res:
 	return res, consumed
 	return res, consumed
 }
 }
 
 
-// Parses an RFC 3339 string and returns Time and a UTC offset in minutes.
-// e.g. 1985-04-12T23:20:50.52Z
-// Note: Only 4-digit years are accepted.
-// Optional pointer to boolean `is_leap` will return `true` if the moment was a leap second.
-// Leap seconds are smeared into 23:59:59.
+/*
+Parse an RFC 3339 string into a time and a UTC offset in minutes.
+
+This procedure parses the specified RFC 3339 strings of roughly the following
+format:
+
+```text
+YYYY-MM-DD[Tt]HH:mm:ss[.nn][Zz][+-]HH:mm
+```
+
+And returns the time, in UTC and a UTC offset, in minutes, that were represented
+by the RFC 3339 string.
+
+**Inputs**:
+- `rfc_datetime`: The RFC 3339 string to be parsed.
+- `is_leap`: Optional output parameter specifying whether the moment was a
+  leap second.
+
+**Returns**:
+- `res`: The time, in UTC, that was parsed from the RFC 3339 string.
+- `utc_offset`: The UTC offset, in minutes, that was parsed from the RFC 3339
+  string.
+- `consumed`: The number of bytes consumed by parsing the string.
+
+**Notes**:
+- Only 4-digit years are accepted.
+- Leap seconds are smeared into 23:59:59.
+*/
 rfc3339_to_time_and_offset :: proc(rfc_datetime: string, is_leap: ^bool = nil) -> (res: Time, utc_offset: int, consumed: int) {
 rfc3339_to_time_and_offset :: proc(rfc_datetime: string, is_leap: ^bool = nil) -> (res: Time, utc_offset: int, consumed: int) {
 	moment, offset, leap_second, count := rfc3339_to_components(rfc_datetime)
 	moment, offset, leap_second, count := rfc3339_to_components(rfc_datetime)
 	if count == 0 {
 	if count == 0 {
@@ -38,9 +84,31 @@ rfc3339_to_time_and_offset :: proc(rfc_datetime: string, is_leap: ^bool = nil) -
 	}
 	}
 }
 }
 
 
-// Parses an RFC 3339 string and returns Time and a UTC offset in minutes.
-// e.g. 1985-04-12T23:20:50.52Z
-// Performs no validation on whether components are valid, e.g. it'll return hour = 25 if that's what it's given
+/*
+Parse an RFC 3339 string into a datetime and a UTC offset in minutes.
+
+This procedure parses the specified RFC 3339 strings of roughly the following
+format:
+
+```text
+YYYY-MM-DD[Tt]HH:mm:ss[.nn][Zz][+-]HH:mm
+```
+
+And returns the datetime, in UTC and the UTC offset, in minutes, that were
+represented by the RFC 3339 string.
+
+**Inputs**:
+- `rfc_datetime`: The RFC 3339 string to parse.
+
+**Returns**:
+- `res`: The datetime, in UTC, that was parsed from the RFC 3339 string.
+- `utc_offset`: The UTC offset, in minutes, that was parsed from the RFC 3339
+  string.
+- `is_leap`: Specifies whether the moment was a leap second.
+- `consumed`: Number of bytes consumed by parsing the string.
+
+Performs no validation on whether components are valid, e.g. it'll return hour = 25 if that's what it's given
+*/
 rfc3339_to_components :: proc(rfc_datetime: string) -> (res: dt.DateTime, utc_offset: int, is_leap: bool, consumed: int) {
 rfc3339_to_components :: proc(rfc_datetime: string) -> (res: dt.DateTime, utc_offset: int, is_leap: bool, consumed: int) {
 	moment, offset, count, leap_second, ok := _rfc3339_to_components(rfc_datetime)
 	moment, offset, count, leap_second, ok := _rfc3339_to_components(rfc_datetime)
 	if !ok {
 	if !ok {

+ 277 - 8
core/time/time.odin

@@ -3,24 +3,73 @@ package time
 import    "base:intrinsics"
 import    "base:intrinsics"
 import dt "core:time/datetime"
 import dt "core:time/datetime"
 
 
+/*
+Type representing duration, with nanosecond precision.
+This is the regular Unix timestamp, scaled to nanosecond precision.
+*/
 Duration :: distinct i64
 Duration :: distinct i64
 
 
+/*
+The duration equal to one nanosecond (1e-9 seconds).
+*/
 Nanosecond  :: Duration(1)
 Nanosecond  :: Duration(1)
+
+/*
+The duration equal to one microsecond (1e-6 seconds).
+*/
 Microsecond :: 1000 * Nanosecond
 Microsecond :: 1000 * Nanosecond
+
+/*
+The duration equal to one millisecond (1e-3 seconds).
+*/
 Millisecond :: 1000 * Microsecond
 Millisecond :: 1000 * Microsecond
+
+/*
+The duration equal to one second.
+*/
 Second      :: 1000 * Millisecond
 Second      :: 1000 * Millisecond
+
+/*
+The duration equal to one minute (60 seconds).
+*/
 Minute      :: 60 * Second
 Minute      :: 60 * Second
+
+/*
+The duration equal to one hour (3600 seconds).
+*/
 Hour        :: 60 * Minute
 Hour        :: 60 * Minute
 
 
+/*
+Minimum representable duration.
+*/
 MIN_DURATION :: Duration(-1 << 63)
 MIN_DURATION :: Duration(-1 << 63)
+
+/*
+Maximum representable duration.
+*/
 MAX_DURATION :: Duration(1<<63 - 1)
 MAX_DURATION :: Duration(1<<63 - 1)
 
 
+/*
+Value specifying whether the time procedures are supported by the current
+platform.
+*/
 IS_SUPPORTED :: _IS_SUPPORTED
 IS_SUPPORTED :: _IS_SUPPORTED
 
 
+/*
+Specifies time since the UNIX epoch, with nanosecond precision.
+
+Capable of representing any time within the following range:
+
+- `min: 1677-09-21 00:12:44.145224192 +0000 UTC`
+- `max: 2262-04-11 23:47:16.854775807 +0000 UTC`
+*/
 Time :: struct {
 Time :: struct {
 	_nsec: i64, // Measured in UNIX nanonseconds
 	_nsec: i64, // Measured in UNIX nanonseconds
 }
 }
 
 
+/*
+Type representing a month.
+*/
 Month :: enum int {
 Month :: enum int {
 	January = 1,
 	January = 1,
 	February,
 	February,
@@ -36,6 +85,9 @@ Month :: enum int {
 	December,
 	December,
 }
 }
 
 
+/*
+Type representing a weekday.
+*/
 Weekday :: enum int {
 Weekday :: enum int {
 	Sunday = 0,
 	Sunday = 0,
 	Monday,
 	Monday,
@@ -46,20 +98,37 @@ Weekday :: enum int {
 	Saturday,
 	Saturday,
 }
 }
 
 
+/*
+Type representing a stopwatch.
+
+The stopwatch is used for measuring the total time in multiple "runs". When the
+stopwatch is started, it starts counting time. When the stopwatch is stopped,
+the difference in time between the last start and the stop is added to the
+total. When the stopwatch resets, the total is reset.
+*/
 Stopwatch :: struct {
 Stopwatch :: struct {
 	running: bool,
 	running: bool,
 	_start_time: Tick,
 	_start_time: Tick,
 	_accumulation: Duration,
 	_accumulation: Duration,
 }
 }
 
 
+/*
+Obtain the current time.
+*/
 now :: proc "contextless" () -> Time {
 now :: proc "contextless" () -> Time {
 	return _now()
 	return _now()
 }
 }
 
 
+/*
+Sleep for the specified duration.
+*/
 sleep :: proc "contextless" (d: Duration) {
 sleep :: proc "contextless" (d: Duration) {
 	_sleep(d)
 	_sleep(d)
 }
 }
 
 
+/*
+Start the stopwatch.
+*/
 stopwatch_start :: proc "contextless" (stopwatch: ^Stopwatch) {
 stopwatch_start :: proc "contextless" (stopwatch: ^Stopwatch) {
 	if !stopwatch.running {
 	if !stopwatch.running {
 		stopwatch._start_time = tick_now()
 		stopwatch._start_time = tick_now()
@@ -67,6 +136,9 @@ stopwatch_start :: proc "contextless" (stopwatch: ^Stopwatch) {
 	}
 	}
 }
 }
 
 
+/*
+Stop the stopwatch.
+*/
 stopwatch_stop :: proc "contextless" (stopwatch: ^Stopwatch) {
 stopwatch_stop :: proc "contextless" (stopwatch: ^Stopwatch) {
 	if stopwatch.running {
 	if stopwatch.running {
 		stopwatch._accumulation += tick_diff(stopwatch._start_time, tick_now())
 		stopwatch._accumulation += tick_diff(stopwatch._start_time, tick_now())
@@ -74,11 +146,21 @@ stopwatch_stop :: proc "contextless" (stopwatch: ^Stopwatch) {
 	}
 	}
 }
 }
 
 
+/*
+Reset the stopwatch.
+*/
 stopwatch_reset :: proc "contextless" (stopwatch: ^Stopwatch) {
 stopwatch_reset :: proc "contextless" (stopwatch: ^Stopwatch) {
 	stopwatch._accumulation = {}
 	stopwatch._accumulation = {}
 	stopwatch.running = false
 	stopwatch.running = false
 }
 }
 
 
+/*
+Obtain the total time, counted by the stopwatch.
+
+This procedure obtains the total time, counted by the stopwatch. If the stopwatch
+isn't stopped at the time of calling this procedure, the time between the last
+start and the current time is also accounted for.
+*/
 stopwatch_duration :: proc "contextless" (stopwatch: Stopwatch) -> Duration {
 stopwatch_duration :: proc "contextless" (stopwatch: Stopwatch) -> Duration {
 	if !stopwatch.running {
 	if !stopwatch.running {
 		return stopwatch._accumulation
 		return stopwatch._accumulation
@@ -86,40 +168,92 @@ stopwatch_duration :: proc "contextless" (stopwatch: Stopwatch) -> Duration {
 	return stopwatch._accumulation + tick_diff(stopwatch._start_time, tick_now())
 	return stopwatch._accumulation + tick_diff(stopwatch._start_time, tick_now())
 }
 }
 
 
+/*
+Calculate the duration elapsed between two times.
+*/
 diff :: proc "contextless" (start, end: Time) -> Duration {
 diff :: proc "contextless" (start, end: Time) -> Duration {
 	d := end._nsec - start._nsec
 	d := end._nsec - start._nsec
 	return Duration(d)
 	return Duration(d)
 }
 }
 
 
+/*
+Calculate the duration elapsed since a specific time.
+*/
 since :: proc "contextless" (start: Time) -> Duration {
 since :: proc "contextless" (start: Time) -> Duration {
 	return diff(start, now())
 	return diff(start, now())
 }
 }
 
 
+/*
+Obtain the number of nanoseconds in a duration.
+*/
 duration_nanoseconds :: proc "contextless" (d: Duration) -> i64 {
 duration_nanoseconds :: proc "contextless" (d: Duration) -> i64 {
 	return i64(d)
 	return i64(d)
 }
 }
+
+/*
+Obtain the number of microseconds in a duration.
+*/
 duration_microseconds :: proc "contextless" (d: Duration) -> f64 {
 duration_microseconds :: proc "contextless" (d: Duration) -> f64 {
 	return duration_seconds(d) * 1e6
 	return duration_seconds(d) * 1e6
 }
 }
+
+/*
+Obtain the number of milliseconds in a duration.
+*/
 duration_milliseconds :: proc "contextless" (d: Duration) -> f64 {
 duration_milliseconds :: proc "contextless" (d: Duration) -> f64 {
 	return duration_seconds(d) * 1e3
 	return duration_seconds(d) * 1e3
 }
 }
+
+/*
+Obtain the number of seconds in a duration.
+*/
 duration_seconds :: proc "contextless" (d: Duration) -> f64 {
 duration_seconds :: proc "contextless" (d: Duration) -> f64 {
 	sec := d / Second
 	sec := d / Second
 	nsec := d % Second
 	nsec := d % Second
 	return f64(sec) + f64(nsec)/1e9
 	return f64(sec) + f64(nsec)/1e9
 }
 }
+
+/*
+Obtain the number of minutes in a duration.
+*/
 duration_minutes :: proc "contextless" (d: Duration) -> f64 {
 duration_minutes :: proc "contextless" (d: Duration) -> f64 {
 	min := d / Minute
 	min := d / Minute
 	nsec := d % Minute
 	nsec := d % Minute
 	return f64(min) + f64(nsec)/(60*1e9)
 	return f64(min) + f64(nsec)/(60*1e9)
 }
 }
+
+/*
+Obtain the number of hours in a duration.
+*/
 duration_hours :: proc "contextless" (d: Duration) -> f64 {
 duration_hours :: proc "contextless" (d: Duration) -> f64 {
 	hour := d / Hour
 	hour := d / Hour
 	nsec := d % Hour
 	nsec := d % Hour
 	return f64(hour) + f64(nsec)/(60*60*1e9)
 	return f64(hour) + f64(nsec)/(60*60*1e9)
 }
 }
 
 
+/*
+Round a 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**:
+
+In order to obtain the rough amount of seconds in a duration, the following call
+can be used:
+
+```
+time.duration_round(my_duration, time.Second)
+```
+
+**Note**: Any duration can be supplied as a unit.
+*/
 duration_round :: proc "contextless" (d, m: Duration) -> Duration {
 duration_round :: proc "contextless" (d, m: Duration) -> Duration {
 	_less_than_half :: #force_inline proc "contextless" (x, y: Duration) -> bool {
 	_less_than_half :: #force_inline proc "contextless" (x, y: Duration) -> bool {
 		return u64(x)+u64(x) < u64(y)
 		return u64(x)+u64(x) < u64(y)
@@ -149,50 +283,103 @@ duration_round :: proc "contextless" (d, m: Duration) -> Duration {
 	return MAX_DURATION
 	return MAX_DURATION
 }
 }
 
 
+/*
+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**:
+
+In order to obtain the amount of whole seconds in a duration, the following call
+can be used:
+
+```
+time.duration_round(my_duration, time.Second)
+```
+
+**Note**: Any duration can be supplied as a unit.
+*/
 duration_truncate :: proc "contextless" (d, m: Duration) -> Duration {
 duration_truncate :: proc "contextless" (d, m: Duration) -> Duration {
 	return d if m <= 0 else d - d%m
 	return d if m <= 0 else d - d%m
 }
 }
 
 
+/*
+Parse time into date components.
+*/
 date :: proc "contextless" (t: Time) -> (year: int, month: Month, day: int) {
 date :: proc "contextless" (t: Time) -> (year: int, month: Month, day: int) {
 	year, month, day, _ = _abs_date(_time_abs(t), true)
 	year, month, day, _ = _abs_date(_time_abs(t), true)
 	return
 	return
 }
 }
 
 
+/*
+Obtain the year of the date specified by time.
+*/
 year :: proc "contextless" (t: Time) -> (year: int) {
 year :: proc "contextless" (t: Time) -> (year: int) {
 	year, _, _, _ = _date(t, true)
 	year, _, _, _ = _date(t, true)
 	return
 	return
 }
 }
 
 
+/*
+Obtain the month of the date specified by time.
+*/
 month :: proc "contextless" (t: Time) -> (month: Month) {
 month :: proc "contextless" (t: Time) -> (month: Month) {
 	_, month, _, _ = _date(t, true)
 	_, month, _, _ = _date(t, true)
 	return
 	return
 }
 }
 
 
+/*
+Obtain the day of the date specified by time.
+*/
 day :: proc "contextless" (t: Time) -> (day: int) {
 day :: proc "contextless" (t: Time) -> (day: int) {
 	_, _, day, _ = _date(t, true)
 	_, _, day, _ = _date(t, true)
 	return
 	return
 }
 }
 
 
+/*
+Obtain the week day of the date specified by time.
+*/
 weekday :: proc "contextless" (t: Time) -> (weekday: Weekday) {
 weekday :: proc "contextless" (t: Time) -> (weekday: Weekday) {
 	abs := _time_abs(t)
 	abs := _time_abs(t)
 	sec := (abs + u64(Weekday.Monday) * SECONDS_PER_DAY) % SECONDS_PER_WEEK
 	sec := (abs + u64(Weekday.Monday) * SECONDS_PER_DAY) % SECONDS_PER_WEEK
 	return Weekday(int(sec) / SECONDS_PER_DAY)
 	return Weekday(int(sec) / SECONDS_PER_DAY)
 }
 }
 
 
+/*
+Obtain the time components from a time, a duration or a stopwatch's total.
+*/
 clock :: proc { clock_from_time, clock_from_duration, clock_from_stopwatch }
 clock :: proc { clock_from_time, clock_from_duration, clock_from_stopwatch }
 
 
+/*
+Obtain the time components from a time.
+*/
 clock_from_time :: proc "contextless" (t: Time) -> (hour, min, sec: int) {
 clock_from_time :: proc "contextless" (t: Time) -> (hour, min, sec: int) {
 	return clock_from_seconds(_time_abs(t))
 	return clock_from_seconds(_time_abs(t))
 }
 }
 
 
+/*
+Obtain the time components from a duration.
+*/
 clock_from_duration :: proc "contextless" (d: Duration) -> (hour, min, sec: int) {
 clock_from_duration :: proc "contextless" (d: Duration) -> (hour, min, sec: int) {
 	return clock_from_seconds(u64(d/1e9))
 	return clock_from_seconds(u64(d/1e9))
 }
 }
 
 
+/*
+Obtain the time components from a stopwatch's total.
+*/
 clock_from_stopwatch :: proc "contextless" (s: Stopwatch) -> (hour, min, sec: int) {
 clock_from_stopwatch :: proc "contextless" (s: Stopwatch) -> (hour, min, sec: int) {
 	return clock_from_duration(stopwatch_duration(s))
 	return clock_from_duration(stopwatch_duration(s))
 }
 }
 
 
+/*
+Obtain the time components from the number of seconds.
+*/
 clock_from_seconds :: proc "contextless" (nsec: u64) -> (hour, min, sec: int) {
 clock_from_seconds :: proc "contextless" (nsec: u64) -> (hour, min, sec: int) {
 	sec = int(nsec % SECONDS_PER_DAY)
 	sec = int(nsec % SECONDS_PER_DAY)
 	hour = sec / SECONDS_PER_HOUR
 	hour = sec / SECONDS_PER_HOUR
@@ -202,10 +389,16 @@ clock_from_seconds :: proc "contextless" (nsec: u64) -> (hour, min, sec: int) {
 	return
 	return
 }
 }
 
 
+/*
+Read the timestamp counter of the CPU.
+*/
 read_cycle_counter :: proc "contextless" () -> u64 {
 read_cycle_counter :: proc "contextless" () -> u64 {
 	return u64(intrinsics.read_cycle_counter())
 	return u64(intrinsics.read_cycle_counter())
 }
 }
 
 
+/*
+Obtain time from unix seconds and unix nanoseconds.
+*/
 unix :: proc "contextless" (sec: i64, nsec: i64) -> Time {
 unix :: proc "contextless" (sec: i64, nsec: i64) -> Time {
 	sec, nsec := sec, nsec
 	sec, nsec := sec, nsec
 	if nsec < 0 || nsec >= 1e9 {
 	if nsec < 0 || nsec >= 1e9 {
@@ -220,31 +413,59 @@ unix :: proc "contextless" (sec: i64, nsec: i64) -> Time {
 	return Time{(sec*1e9 + nsec)}
 	return Time{(sec*1e9 + nsec)}
 }
 }
 
 
+/*
+Obtain time from unix nanoseconds.
+*/
 from_nanoseconds :: #force_inline proc "contextless" (nsec: i64) -> Time {
 from_nanoseconds :: #force_inline proc "contextless" (nsec: i64) -> Time {
 	return Time{nsec}
 	return Time{nsec}
 }
 }
 
 
+/*
+Alias for `time_to_unix`.
+*/
 to_unix_seconds :: time_to_unix
 to_unix_seconds :: time_to_unix
+
+/*
+Obtain the Unix timestamp in seconds from a Time.
+*/
 time_to_unix :: proc "contextless" (t: Time) -> i64 {
 time_to_unix :: proc "contextless" (t: Time) -> i64 {
 	return t._nsec/1e9
 	return t._nsec/1e9
 }
 }
 
 
+/*
+Alias for `time_to_unix_nano`.
+*/
 to_unix_nanoseconds :: time_to_unix_nano
 to_unix_nanoseconds :: time_to_unix_nano
+
+/*
+Obtain the Unix timestamp in nanoseconds from a Time.
+*/
 time_to_unix_nano :: proc "contextless" (t: Time) -> i64 {
 time_to_unix_nano :: proc "contextless" (t: Time) -> i64 {
 	return t._nsec
 	return t._nsec
 }
 }
 
 
+/*
+Add duration to a time.
+*/
 time_add :: proc "contextless" (t: Time, d: Duration) -> Time {
 time_add :: proc "contextless" (t: Time, d: Duration) -> Time {
 	return Time{t._nsec + i64(d)}
 	return Time{t._nsec + i64(d)}
 }
 }
 
 
-// Accurate sleep borrowed from: https://blat-blatnik.github.io/computerBear/making-accurate-sleep-function/
-//
-// Accuracy seems to be pretty good out of the box on Linux, to within around 4µs worst case.
-// On Windows it depends but is comparable with regular sleep in the worst case.
-// To get the same kind of accuracy as on Linux, have your program call `windows.timeBeginPeriod(1)` to
-// tell Windows to use a more accurate timer for your process.
-// Additionally your program should call `windows.timeEndPeriod(1)` once you're done with `accurate_sleep`. 
+/*
+Accurate sleep
+
+This procedure sleeps for the duration specified by `d`, very accurately.
+
+**Note**: Implementation borrowed from: [this source](https://blat-blatnik.github.io/computerBear/making-accurate-sleep-function/)
+
+**Note(linux)**: The accuracy is within around 4µs (microseconds), in the worst case.
+
+**Note(windows)**: The accuracy depends but is comparable with regular sleep in
+the worst case. To get the same kind of accuracy as on Linux, have your program
+call `windows.timeBeginPeriod(1)` to tell Windows to use a more accurate timer
+for your process. Additionally your program should call `windows.timeEndPeriod(1)`
+once you're done with `accurate_sleep`. 
+*/
 accurate_sleep :: proc "contextless" (d: Duration) {
 accurate_sleep :: proc "contextless" (d: Duration) {
 	to_sleep, estimate, mean, m2, count: Duration
 	to_sleep, estimate, mean, m2, count: Duration
 
 
@@ -362,6 +583,13 @@ _abs_date :: proc "contextless" (abs: u64, full: bool) -> (year: int, month: Mon
 	return
 	return
 }
 }
 
 
+/*
+Convert datetime components into time.
+
+This procedure calculates the time from datetime components supplied in the
+arguments to this procedure. If the datetime components don't represent a valid
+datetime, the function returns `false` in the second argument.
+*/
 components_to_time :: proc "contextless" (#any_int year, #any_int month, #any_int day, #any_int hour, #any_int minute, #any_int second: i64, #any_int nsec := i64(0)) -> (t: Time, ok: bool) {
 components_to_time :: proc "contextless" (#any_int year, #any_int month, #any_int day, #any_int hour, #any_int minute, #any_int second: i64, #any_int nsec := i64(0)) -> (t: Time, ok: bool) {
 	this_date, err := dt.components_to_datetime(year, month, day, hour, minute, second, nsec)
 	this_date, err := dt.components_to_datetime(year, month, day, hour, minute, second, nsec)
 	if err != .None {
 	if err != .None {
@@ -370,6 +598,12 @@ components_to_time :: proc "contextless" (#any_int year, #any_int month, #any_in
 	return compound_to_time(this_date)
 	return compound_to_time(this_date)
 }
 }
 
 
+/*
+Convert datetime into time.
+
+If the datetime represents a time outside of a valid range, `false` is returned
+as the second return value. See `Time` for the representable range.
+*/
 compound_to_time :: proc "contextless" (datetime: dt.DateTime) -> (t: Time, ok: bool) {
 compound_to_time :: proc "contextless" (datetime: dt.DateTime) -> (t: Time, ok: bool) {
 	unix_epoch := dt.DateTime{{1970, 1, 1}, {0, 0, 0, 0}}
 	unix_epoch := dt.DateTime{{1970, 1, 1}, {0, 0, 0, 0}}
 	delta, err := dt.sub(datetime, unix_epoch)
 	delta, err := dt.sub(datetime, unix_epoch)
@@ -387,12 +621,21 @@ compound_to_time :: proc "contextless" (datetime: dt.DateTime) -> (t: Time, ok:
 	return Time{_nsec=i64(nanoseconds)}, true
 	return Time{_nsec=i64(nanoseconds)}, true
 }
 }
 
 
+/*
+Convert datetime components into time.
+*/
 datetime_to_time :: proc{components_to_time, compound_to_time}
 datetime_to_time :: proc{components_to_time, compound_to_time}
 
 
+/*
+Check if a year is a leap year.
+*/
 is_leap_year :: proc "contextless" (year: int) -> (leap: bool) {
 is_leap_year :: proc "contextless" (year: int) -> (leap: bool) {
 	return year % 4 == 0 && (year % 100 != 0 || year % 400 == 0)
 	return year % 4 == 0 && (year % 100 != 0 || year % 400 == 0)
 }
 }
 
 
+/*
+Days before each month in a year, not counting the leap day on february 29th.
+*/
 @(rodata)
 @(rodata)
 days_before := [?]i32{
 days_before := [?]i32{
 	0,
 	0,
@@ -410,11 +653,37 @@ days_before := [?]i32{
 	31 + 28 + 31 + 30 + 31 + 30 + 31 + 31 + 30 + 31 + 30 + 31,
 	31 + 28 + 31 + 30 + 31 + 30 + 31 + 31 + 30 + 31 + 30 + 31,
 }
 }
 
 
-
+/*
+Number of seconds in a minute (without leap seconds).
+*/
 SECONDS_PER_MINUTE :: 60
 SECONDS_PER_MINUTE :: 60
+
+/*
+Number of seconds in an hour (without leap seconds).
+*/
 SECONDS_PER_HOUR   :: 60 * SECONDS_PER_MINUTE
 SECONDS_PER_HOUR   :: 60 * SECONDS_PER_MINUTE
+
+/*
+Number of seconds in a day (without leap seconds).
+*/
 SECONDS_PER_DAY    :: 24 * SECONDS_PER_HOUR
 SECONDS_PER_DAY    :: 24 * SECONDS_PER_HOUR
+
+/*
+Number of seconds in a week (without leap seconds).
+*/
 SECONDS_PER_WEEK   ::  7 * SECONDS_PER_DAY
 SECONDS_PER_WEEK   ::  7 * SECONDS_PER_DAY
+
+/*
+Days in 400 years, with leap days.
+*/
 DAYS_PER_400_YEARS :: 365*400 + 97
 DAYS_PER_400_YEARS :: 365*400 + 97
+
+/*
+Days in 100 years, with leap days.
+*/
 DAYS_PER_100_YEARS :: 365*100 + 24
 DAYS_PER_100_YEARS :: 365*100 + 24
+
+/*
+Days in 4 years, with leap days.
+*/
 DAYS_PER_4_YEARS   :: 365*4   + 1
 DAYS_PER_4_YEARS   :: 365*4   + 1

+ 9 - 0
examples/README.md

@@ -0,0 +1,9 @@
+# Examples
+
+The `example` directory contains two packages:
+
+A [demo](examples/demo) illustrating the basics of Odin.
+
+It further contains [all](examples/all), which imports all [core](core) and [vendor](vendor) packages so we can conveniently run `odin check` on everything at once.
+
+For additional example code, see the [examples](https://github.com/odin-lang/examples) repository.

+ 2 - 0
examples/all/all_main.odin

@@ -61,6 +61,7 @@ import cbor             "core:encoding/cbor"
 import csv              "core:encoding/csv"
 import csv              "core:encoding/csv"
 import endian           "core:encoding/endian"
 import endian           "core:encoding/endian"
 import hxa              "core:encoding/hxa"
 import hxa              "core:encoding/hxa"
+import ini              "core:encoding/ini"
 import json             "core:encoding/json"
 import json             "core:encoding/json"
 import varint           "core:encoding/varint"
 import varint           "core:encoding/varint"
 import xml              "core:encoding/xml"
 import xml              "core:encoding/xml"
@@ -193,6 +194,7 @@ _ :: base32
 _ :: base64
 _ :: base64
 _ :: csv
 _ :: csv
 _ :: hxa
 _ :: hxa
+_ :: ini
 _ :: json
 _ :: json
 _ :: varint
 _ :: varint
 _ :: xml
 _ :: xml

+ 16 - 1
src/build_settings.cpp

@@ -440,6 +440,8 @@ struct BuildContext {
 	bool   cached;
 	bool   cached;
 	BuildCacheData build_cache_data;
 	BuildCacheData build_cache_data;
 
 
+	bool internal_no_inline;
+
 	bool   no_threaded_checker;
 	bool   no_threaded_checker;
 
 
 	bool   show_debug_messages;
 	bool   show_debug_messages;
@@ -1649,11 +1651,24 @@ gb_internal void init_build_context(TargetMetrics *cross_target, Subtarget subta
 	if (!bc->custom_optimization_level) {
 	if (!bc->custom_optimization_level) {
 		// NOTE(bill): when building with `-debug` but not specifying an optimization level
 		// NOTE(bill): when building with `-debug` but not specifying an optimization level
 		// default to `-o:none` to improve the debug symbol generation by default
 		// default to `-o:none` to improve the debug symbol generation by default
-		bc->optimization_level = -1; // -o:none
+		if (bc->ODIN_DEBUG) {
+			bc->optimization_level = -1; // -o:none
+		} else {
+			bc->optimization_level = 0; // -o:minimal
+		}
 	}
 	}
 
 
 	bc->optimization_level = gb_clamp(bc->optimization_level, -1, 3);
 	bc->optimization_level = gb_clamp(bc->optimization_level, -1, 3);
 
 
+#if defined(GB_SYSTEM_WINDOWS)
+	if (bc->optimization_level <= 0) {
+		if (!is_arch_wasm()) {
+			bc->use_separate_modules = true;
+		}
+	}
+#endif
+
+
 	// TODO: Static map calls are bugged on `amd64sysv` abi.
 	// TODO: Static map calls are bugged on `amd64sysv` abi.
 	if (bc->metrics.os != TargetOs_windows && bc->metrics.arch == TargetArch_amd64) {
 	if (bc->metrics.os != TargetOs_windows && bc->metrics.arch == TargetArch_amd64) {
 		// ENFORCE DYNAMIC MAP CALLS
 		// ENFORCE DYNAMIC MAP CALLS

+ 8 - 8
src/cached.cpp

@@ -17,11 +17,11 @@ gb_internal bool recursively_delete_directory(wchar_t *wpath_c) {
 
 
 	wchar_t dir_path[MAX_PATH] = {};
 	wchar_t dir_path[MAX_PATH] = {};
 	wchar_t filename[MAX_PATH] = {};
 	wchar_t filename[MAX_PATH] = {};
-	wcscpy(dir_path, wpath_c);
-	wcscat(dir_path, L"\\*");
+	wcscpy_s(dir_path, wpath_c);
+	wcscat_s(dir_path, L"\\*");
 
 
-	wcscpy(filename, wpath_c);
-	wcscat(filename, L"\\");
+	wcscpy_s(filename, wpath_c);
+	wcscat_s(filename, L"\\");
 
 
 
 
 	WIN32_FIND_DATAW find_file_data = {};
 	WIN32_FIND_DATAW find_file_data = {};
@@ -31,21 +31,21 @@ gb_internal bool recursively_delete_directory(wchar_t *wpath_c) {
 	}
 	}
 	defer (FindClose(hfind));
 	defer (FindClose(hfind));
 
 
-	wcscpy(dir_path, filename);
+	wcscpy_s(dir_path, filename);
 
 
 	for (;;) {
 	for (;;) {
 		if (FindNextFileW(hfind, &find_file_data)) {
 		if (FindNextFileW(hfind, &find_file_data)) {
 			if (is_dots_w(find_file_data.cFileName)) {
 			if (is_dots_w(find_file_data.cFileName)) {
 				continue;
 				continue;
 			}
 			}
-			wcscat(filename, find_file_data.cFileName);
+			wcscat_s(filename, find_file_data.cFileName);
 
 
 			if (find_file_data.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) {
 			if (find_file_data.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) {
 				if (!recursively_delete_directory(filename)) {
 				if (!recursively_delete_directory(filename)) {
 					return false;
 					return false;
 				}
 				}
 				RemoveDirectoryW(filename);
 				RemoveDirectoryW(filename);
-				wcscpy(filename, dir_path);
+				wcscpy_s(filename, dir_path);
 			} else {
 			} else {
 				if (find_file_data.dwFileAttributes & FILE_ATTRIBUTE_READONLY) {
 				if (find_file_data.dwFileAttributes & FILE_ATTRIBUTE_READONLY) {
 					_wchmod(filename, _S_IWRITE);
 					_wchmod(filename, _S_IWRITE);
@@ -53,7 +53,7 @@ gb_internal bool recursively_delete_directory(wchar_t *wpath_c) {
 				if (!DeleteFileW(filename)) {
 				if (!DeleteFileW(filename)) {
 					return false;
 					return false;
 				}
 				}
-				wcscpy(filename, dir_path);
+				wcscpy_s(filename, dir_path);
 			}
 			}
 		} else {
 		} else {
 			if (GetLastError() == ERROR_NO_MORE_FILES) {
 			if (GetLastError() == ERROR_NO_MORE_FILES) {

+ 50 - 3
src/check_builtin.cpp

@@ -1079,7 +1079,7 @@ gb_internal bool check_builtin_simd_operation(CheckerContext *c, Operand *operan
 	return false;
 	return false;
 }
 }
 
 
-gb_internal bool cache_load_file_directive(CheckerContext *c, Ast *call, String const &original_string, bool err_on_not_found, LoadFileCache **cache_, LoadFileTier tier) {
+gb_internal bool cache_load_file_directive(CheckerContext *c, Ast *call, String const &original_string, bool err_on_not_found, LoadFileCache **cache_, LoadFileTier tier, bool use_mutex=true) {
 	ast_node(ce, CallExpr, call);
 	ast_node(ce, CallExpr, call);
 	ast_node(bd, BasicDirective, ce->proc);
 	ast_node(bd, BasicDirective, ce->proc);
 	String builtin_name = bd->name.string;
 	String builtin_name = bd->name.string;
@@ -1101,7 +1101,8 @@ gb_internal bool cache_load_file_directive(CheckerContext *c, Ast *call, String
 		}
 		}
 	}
 	}
 
 
-	MUTEX_GUARD(&c->info->load_file_mutex);
+	if (use_mutex) mutex_lock(&c->info->load_file_mutex);
+	defer (if (use_mutex) mutex_unlock(&c->info->load_file_mutex));
 
 
 	gbFileError file_error = gbFileError_None;
 	gbFileError file_error = gbFileError_None;
 	String data = {};
 	String data = {};
@@ -1414,9 +1415,12 @@ gb_internal LoadDirectiveResult check_load_directory_directive(CheckerContext *c
 
 
 		file_caches = array_make<LoadFileCache *>(heap_allocator(), 0, files_to_reserve);
 		file_caches = array_make<LoadFileCache *>(heap_allocator(), 0, files_to_reserve);
 
 
+		mutex_lock(&c->info->load_file_mutex);
+		defer (mutex_unlock(&c->info->load_file_mutex));
+
 		for (FileInfo fi : list) {
 		for (FileInfo fi : list) {
 			LoadFileCache *cache = nullptr;
 			LoadFileCache *cache = nullptr;
-			if (cache_load_file_directive(c, call, fi.fullpath, err_on_not_found, &cache, LoadFileTier_Contents)) {
+			if (cache_load_file_directive(c, call, fi.fullpath, err_on_not_found, &cache, LoadFileTier_Contents, /*use_mutex*/false)) {
 				array_add(&file_caches, cache);
 				array_add(&file_caches, cache);
 			} else {
 			} else {
 				result = LoadDirective_Error;
 				result = LoadDirective_Error;
@@ -4298,6 +4302,49 @@ gb_internal bool check_builtin_procedure(CheckerContext *c, Operand *operand, As
 		}
 		}
 		break;
 		break;
 
 
+	case BuiltinProc_add_sat:
+	case BuiltinProc_sub_sat:
+		{
+			Operand x = {};
+			Operand y = {};
+			check_expr(c, &x, ce->args[0]);
+			check_expr(c, &y, ce->args[1]);
+			if (x.mode == Addressing_Invalid) {
+				return false;
+			}
+			if (y.mode == Addressing_Invalid) {
+				return false;
+			}
+			convert_to_typed(c, &y, x.type); if (y.mode == Addressing_Invalid) return false;
+			convert_to_typed(c, &x, y.type);
+			if (is_type_untyped(x.type)) {
+				gbString xts = type_to_string(x.type);
+				error(x.expr, "Expected a typed integer for '%.*s', got %s", LIT(builtin_name), xts);
+				gb_string_free(xts);
+				return false;
+			}
+			if (!is_type_integer(x.type)) {
+				gbString xts = type_to_string(x.type);
+				error(x.expr, "Expected an integer for '%.*s', got %s", LIT(builtin_name), xts);
+				gb_string_free(xts);
+				return false;
+			}
+			Type *ct = core_type(x.type);
+			if (is_type_different_to_arch_endianness(ct)) {
+				GB_ASSERT(ct->kind == Type_Basic);
+				if (ct->Basic.flags & (BasicFlag_EndianLittle|BasicFlag_EndianBig)) {
+					gbString xts = type_to_string(x.type);
+					error(x.expr, "Expected an integer which does not specify the explicit endianness for '%.*s', got %s", LIT(builtin_name), xts);
+					gb_string_free(xts);
+					return false;
+				}
+			}
+
+			operand->mode = Addressing_Value;
+			operand->type = default_type(x.type);
+		}
+		break;
+
 	case BuiltinProc_sqrt:
 	case BuiltinProc_sqrt:
 		{
 		{
 			Operand x = {};
 			Operand x = {};

+ 10 - 2
src/check_decl.cpp

@@ -182,8 +182,7 @@ gb_internal void override_entity_in_scope(Entity *original_entity, Entity *new_e
 	original_entity->type = new_entity->type;
 	original_entity->type = new_entity->type;
 	original_entity->aliased_of = new_entity;
 	original_entity->aliased_of = new_entity;
 
 
-	Ast *empty_ident = nullptr;
-	original_entity->identifier.compare_exchange_strong(empty_ident, new_entity->identifier);
+	original_entity->identifier.store(new_entity->identifier);
 
 
 	if (original_entity->identifier.load() != nullptr &&
 	if (original_entity->identifier.load() != nullptr &&
 	    original_entity->identifier.load()->kind == Ast_Ident) {
 	    original_entity->identifier.load()->kind == Ast_Ident) {
@@ -1869,5 +1868,14 @@ gb_internal bool check_proc_body(CheckerContext *ctx_, Token token, DeclInfo *de
 
 
 	add_deps_from_child_to_parent(decl);
 	add_deps_from_child_to_parent(decl);
 
 
+	for (VariadicReuseData const &vr : decl->variadic_reuses) {
+		GB_ASSERT(vr.slice_type->kind == Type_Slice);
+		Type *elem = vr.slice_type->Slice.elem;
+		i64 size = type_size_of(elem);
+		i64 align = type_align_of(elem);
+		decl->variadic_reuse_max_bytes = gb_max(decl->variadic_reuse_max_bytes, size*vr.max_count);
+		decl->variadic_reuse_max_align = gb_max(decl->variadic_reuse_max_align, align);
+	}
+
 	return true;
 	return true;
 }
 }

+ 33 - 8
src/check_expr.cpp

@@ -500,7 +500,9 @@ gb_internal bool find_or_generate_polymorphic_procedure(CheckerContext *old_c, E
 		nctx.no_polymorphic_errors = false;
 		nctx.no_polymorphic_errors = false;
 
 
 		// NOTE(bill): Reset scope from the failed procedure type
 		// NOTE(bill): Reset scope from the failed procedure type
-		scope_reset(scope);
+		scope->head_child.store(nullptr, std::memory_order_relaxed);
+		string_map_clear(&scope->elements);
+		ptr_set_clear(&scope->imported);
 
 
 		// LEAK NOTE(bill): Cloning this AST may be leaky but this is not really an issue due to arena-based allocation
 		// LEAK NOTE(bill): Cloning this AST may be leaky but this is not really an issue due to arena-based allocation
 		Ast *cloned_proc_type_node = clone_ast(pt->node);
 		Ast *cloned_proc_type_node = clone_ast(pt->node);
@@ -6033,6 +6035,22 @@ gb_internal CallArgumentError check_call_arguments_internal(CheckerContext *c, A
 
 
 					Entity *vt = pt->params->Tuple.variables[pt->variadic_index];
 					Entity *vt = pt->params->Tuple.variables[pt->variadic_index];
 					o.type = vt->type;
 					o.type = vt->type;
+
+					// NOTE(bill, 2024-07-14): minimize the stack usage for variadic parameters with the backing array
+					if (c->decl) {
+						bool found = false;
+						for (auto &vr : c->decl->variadic_reuses) {
+							if (are_types_identical(vt->type, vr.slice_type)) {
+								vr.max_count = gb_max(vr.max_count, variadic_operands.count);
+								found = true;
+								break;
+							}
+						}
+						if (!found) {
+							array_add(&c->decl->variadic_reuses, VariadicReuseData{vt->type, variadic_operands.count});
+						}
+					}
+
 				} else {
 				} else {
 					dummy_argument_count += 1;
 					dummy_argument_count += 1;
 					o.type = t_untyped_nil;
 					o.type = t_untyped_nil;
@@ -7888,12 +7906,15 @@ gb_internal ExprKind check_call_expr(CheckerContext *c, Operand *operand, Ast *c
 
 
 			// NOTE: Due to restrictions in LLVM you can not inline calls with a superset of features.
 			// NOTE: Due to restrictions in LLVM you can not inline calls with a superset of features.
 			if (is_call_inlined) {
 			if (is_call_inlined) {
-				GB_ASSERT(c->curr_proc_decl);
-				GB_ASSERT(c->curr_proc_decl->entity);
-				GB_ASSERT(c->curr_proc_decl->entity->type->kind == Type_Proc);
-				String scope_features = c->curr_proc_decl->entity->type->Proc.enable_target_feature;
-				if (!check_target_feature_is_superset_of(scope_features, pt->Proc.enable_target_feature, &invalid)) {
-					error(call, "Inlined procedure enables target feature '%.*s', this requires the calling procedure to at least enable the same feature", LIT(invalid));
+				if (c->curr_proc_decl == nullptr) {
+					error(call, "Calling a '#force_inline' procedure that enables target features is not allowed at file scope");
+				} else {
+					GB_ASSERT(c->curr_proc_decl->entity);
+					GB_ASSERT(c->curr_proc_decl->entity->type->kind == Type_Proc);
+					String scope_features = c->curr_proc_decl->entity->type->Proc.enable_target_feature;
+					if (!check_target_feature_is_superset_of(scope_features, pt->Proc.enable_target_feature, &invalid)) {
+						error(call, "Inlined procedure enables target feature '%.*s', this requires the calling procedure to at least enable the same feature", LIT(invalid));
+					}
 				}
 				}
 			}
 			}
 		}
 		}
@@ -9926,10 +9947,14 @@ gb_internal ExprKind check_compound_literal(CheckerContext *c, Operand *o, Ast *
 		}
 		}
 		Type *et = base_type(t->BitSet.elem);
 		Type *et = base_type(t->BitSet.elem);
 		isize field_count = 0;
 		isize field_count = 0;
-		if (et->kind == Type_Enum) {
+		if (et != nullptr && et->kind == Type_Enum) {
 			field_count = et->Enum.fields.count;
 			field_count = et->Enum.fields.count;
 		}
 		}
 
 
+		if (is_type_array(bit_set_to_int(t))) {
+			is_constant = false;
+		}
+
 		if (cl->elems[0]->kind == Ast_FieldValue) {
 		if (cl->elems[0]->kind == Ast_FieldValue) {
 			error(cl->elems[0], "'field = value' in a bit_set a literal is not allowed");
 			error(cl->elems[0], "'field = value' in a bit_set a literal is not allowed");
 			is_constant = false;
 			is_constant = false;

+ 13 - 2
src/check_stmt.cpp

@@ -1060,6 +1060,9 @@ gb_internal void check_switch_stmt(CheckerContext *ctx, Ast *node, u32 mod_flags
 	if (ss->tag != nullptr) {
 	if (ss->tag != nullptr) {
 		check_expr(ctx, &x, ss->tag);
 		check_expr(ctx, &x, ss->tag);
 		check_assignment(ctx, &x, nullptr, str_lit("switch expression"));
 		check_assignment(ctx, &x, nullptr, str_lit("switch expression"));
+		if (x.type == nullptr) {
+			return;
+		}
 	} else {
 	} else {
 		x.mode  = Addressing_Constant;
 		x.mode  = Addressing_Constant;
 		x.type  = t_bool;
 		x.type  = t_bool;
@@ -1834,7 +1837,7 @@ gb_internal void check_range_stmt(CheckerContext *ctx, Ast *node, u32 mod_flags)
 
 
 			if (rs->vals.count == 1) {
 			if (rs->vals.count == 1) {
 				Type *t = type_deref(operand.type);
 				Type *t = type_deref(operand.type);
-				if (is_type_map(t) || is_type_bit_set(t)) {
+				if (t != NULL && (is_type_map(t) || is_type_bit_set(t))) {
 					gbString v = expr_to_string(rs->vals[0]);
 					gbString v = expr_to_string(rs->vals[0]);
 					defer (gb_string_free(v));
 					defer (gb_string_free(v));
 					error_line("\tSuggestion: place parentheses around the expression\n");
 					error_line("\tSuggestion: place parentheses around the expression\n");
@@ -2514,7 +2517,7 @@ gb_internal void check_return_stmt(CheckerContext *ctx, Ast *node) {
 			Entity *e = entity_of_node(x);
 			Entity *e = entity_of_node(x);
 			if (is_entity_local_variable(e)) {
 			if (is_entity_local_variable(e)) {
 				unsafe_return_error(o, "the address of a local variable");
 				unsafe_return_error(o, "the address of a local variable");
-			} else if(x->kind == Ast_CompoundLit) {
+			} else if (x->kind == Ast_CompoundLit) {
 				unsafe_return_error(o, "the address of a compound literal");
 				unsafe_return_error(o, "the address of a compound literal");
 			} else if (x->kind == Ast_IndexExpr) {
 			} else if (x->kind == Ast_IndexExpr) {
 				Entity *f = entity_of_node(x->IndexExpr.expr);
 				Entity *f = entity_of_node(x->IndexExpr.expr);
@@ -2529,6 +2532,14 @@ gb_internal void check_return_stmt(CheckerContext *ctx, Ast *node) {
 					unsafe_return_error(o, "the address of an indexed variable", f->type);
 					unsafe_return_error(o, "the address of an indexed variable", f->type);
 				}
 				}
 			}
 			}
+		} else if (expr->kind == Ast_SliceExpr) {
+			Ast *x = unparen_expr(expr->SliceExpr.expr);
+			Entity *e = entity_of_node(x);
+			if (is_entity_local_variable(e) && is_type_array(e->type)) {
+				unsafe_return_error(o, "a slice of a local variable");
+			} else if (x->kind == Ast_CompoundLit) {
+				unsafe_return_error(o, "a slice of a compound literal");
+			}
 		} else if (o.mode == Addressing_Constant && is_type_slice(o.type)) {
 		} else if (o.mode == Addressing_Constant && is_type_slice(o.type)) {
 			ERROR_BLOCK();
 			ERROR_BLOCK();
 			unsafe_return_error(o, "a compound literal of a slice");
 			unsafe_return_error(o, "a compound literal of a slice");

+ 69 - 20
src/check_type.cpp

@@ -939,22 +939,6 @@ gb_internal void check_enum_type(CheckerContext *ctx, Type *enum_type, Type *nam
 	enum_type->Enum.max_value_index = max_value_index;
 	enum_type->Enum.max_value_index = max_value_index;
 }
 }
 
 
-gb_internal bool is_valid_bit_field_backing_type(Type *type) {
-	if (type == nullptr) {
-		return false;
-	}
-	type = base_type(type);
-	if (is_type_untyped(type)) {
-		return false;
-	}
-	if (is_type_integer(type)) {
-		return true;
-	}
-	if (type->kind == Type_Array) {
-		return is_type_integer(type->Array.elem);
-	}
-	return false;
-}
 
 
 gb_internal void check_bit_field_type(CheckerContext *ctx, Type *bit_field_type, Type *named_type, Ast *node) {
 gb_internal void check_bit_field_type(CheckerContext *ctx, Type *bit_field_type, Type *named_type, Ast *node) {
 	ast_node(bf, BitFieldType, node);
 	ast_node(bf, BitFieldType, node);
@@ -1268,11 +1252,14 @@ gb_internal void check_bit_set_type(CheckerContext *c, Type *type, Type *named_t
 		Type *t = default_type(lhs.type);
 		Type *t = default_type(lhs.type);
 		if (bs->underlying != nullptr) {
 		if (bs->underlying != nullptr) {
 			Type *u = check_type(c, bs->underlying);
 			Type *u = check_type(c, bs->underlying);
+			// if (!is_valid_bit_field_backing_type(u)) {
 			if (!is_type_integer(u)) {
 			if (!is_type_integer(u)) {
 				gbString ts = type_to_string(u);
 				gbString ts = type_to_string(u);
 				error(bs->underlying, "Expected an underlying integer for the bit set, got %s", ts);
 				error(bs->underlying, "Expected an underlying integer for the bit set, got %s", ts);
 				gb_string_free(ts);
 				gb_string_free(ts);
-				return;
+				if (!is_valid_bit_field_backing_type(u)) {
+					return;
+				}
 			}
 			}
 			type->BitSet.underlying = u;
 			type->BitSet.underlying = u;
 		}
 		}
@@ -1572,11 +1559,30 @@ gb_internal Type *determine_type_from_polymorphic(CheckerContext *ctx, Type *pol
 		return poly_type;
 		return poly_type;
 	}
 	}
 	if (show_error) {
 	if (show_error) {
+		ERROR_BLOCK();
 		gbString pts = type_to_string(poly_type);
 		gbString pts = type_to_string(poly_type);
 		gbString ots = type_to_string(operand.type, true);
 		gbString ots = type_to_string(operand.type, true);
 		defer (gb_string_free(pts));
 		defer (gb_string_free(pts));
 		defer (gb_string_free(ots));
 		defer (gb_string_free(ots));
 		error(operand.expr, "Cannot determine polymorphic type from parameter: '%s' to '%s'", ots, pts);
 		error(operand.expr, "Cannot determine polymorphic type from parameter: '%s' to '%s'", ots, pts);
+
+		Type *pt = poly_type;
+		while (pt && pt->kind == Type_Generic && pt->Generic.specialized) {
+			pt = pt->Generic.specialized;
+		}
+		if (is_type_slice(pt) &&
+		    (is_type_dynamic_array(operand.type) || is_type_array(operand.type))) {
+			Ast *expr = unparen_expr(operand.expr);
+			if (expr->kind == Ast_CompoundLit) {
+				gbString es = type_to_string(base_any_array_type(operand.type));
+				error_line("\tSuggestion: Try using a slice compound literal instead '[]%s{...}'\n", es);
+				gb_string_free(es);
+			} else {
+				gbString os = expr_to_string(operand.expr);
+				error_line("\tSuggestion: Try slicing the value with '%s[:]'\n", os);
+				gb_string_free(os);
+			}
+		}
 	}
 	}
 	return t_invalid;
 	return t_invalid;
 }
 }
@@ -1953,6 +1959,10 @@ gb_internal Type *check_get_params(CheckerContext *ctx, Scope *scope, Ast *_para
 					error(name, "'#by_ptr' can only be applied to variable fields");
 					error(name, "'#by_ptr' can only be applied to variable fields");
 					p->flags &= ~FieldFlag_by_ptr;
 					p->flags &= ~FieldFlag_by_ptr;
 				}
 				}
+				if (p->flags&FieldFlag_no_capture) {
+					error(name, "'#no_capture' can only be applied to variable fields");
+					p->flags &= ~FieldFlag_no_capture;
+				}
 
 
 				param = alloc_entity_type_name(scope, name->Ident.token, type, EntityState_Resolved);
 				param = alloc_entity_type_name(scope, name->Ident.token, type, EntityState_Resolved);
 				param->TypeName.is_type_alias = true;
 				param->TypeName.is_type_alias = true;
@@ -2054,6 +2064,28 @@ gb_internal Type *check_get_params(CheckerContext *ctx, Scope *scope, Ast *_para
 						p->flags &= ~FieldFlag_by_ptr; // Remove the flag
 						p->flags &= ~FieldFlag_by_ptr; // Remove the flag
 					}
 					}
 				}
 				}
+				if (p->flags&FieldFlag_no_capture) {
+					if (is_variadic && variadic_index == variables.count) {
+						if (p->flags & FieldFlag_c_vararg) {
+							error(name, "'#no_capture' cannot be applied to a #c_vararg parameter");
+							p->flags &= ~FieldFlag_no_capture;
+						} else {
+							error(name, "'#no_capture' is already implied on all variadic parameter");
+						}
+					} else if (is_type_polymorphic(type)) {
+						// ignore
+					} else {
+						if (is_type_internally_pointer_like(type)) {
+							error(name, "'#no_capture' is currently reserved for future use");
+						} else {
+							ERROR_BLOCK();
+							error(name, "'#no_capture' can only be applied to pointer-like types");
+							error_line("\t'#no_capture' does not currently do anything useful\n");
+							p->flags &= ~FieldFlag_no_capture;
+						}
+					}
+				}
+
 
 
 				if (is_poly_name) {
 				if (is_poly_name) {
 					if (p->flags&FieldFlag_no_alias) {
 					if (p->flags&FieldFlag_no_alias) {
@@ -2072,6 +2104,11 @@ gb_internal Type *check_get_params(CheckerContext *ctx, Scope *scope, Ast *_para
 						error(name, "'#by_ptr' can only be applied to variable fields");
 						error(name, "'#by_ptr' can only be applied to variable fields");
 						p->flags &= ~FieldFlag_by_ptr;
 						p->flags &= ~FieldFlag_by_ptr;
 					}
 					}
+					if (p->flags&FieldFlag_no_capture) {
+						error(name, "'#no_capture' can only be applied to variable fields");
+						p->flags &= ~FieldFlag_no_capture;
+					}
+
 
 
 					if (!is_type_polymorphic(type) && check_constant_parameter_value(type, params[i])) {
 					if (!is_type_polymorphic(type) && check_constant_parameter_value(type, params[i])) {
 						// failed
 						// failed
@@ -2091,6 +2128,8 @@ gb_internal Type *check_get_params(CheckerContext *ctx, Scope *scope, Ast *_para
 				param->flags |= EntityFlag_Ellipsis;
 				param->flags |= EntityFlag_Ellipsis;
 				if (is_c_vararg) {
 				if (is_c_vararg) {
 					param->flags |= EntityFlag_CVarArg;
 					param->flags |= EntityFlag_CVarArg;
+				} else {
+					param->flags |= EntityFlag_NoCapture;
 				}
 				}
 			}
 			}
 
 
@@ -2115,6 +2154,10 @@ gb_internal Type *check_get_params(CheckerContext *ctx, Scope *scope, Ast *_para
 			if (p->flags&FieldFlag_by_ptr) {
 			if (p->flags&FieldFlag_by_ptr) {
 				param->flags |= EntityFlag_ByPtr;
 				param->flags |= EntityFlag_ByPtr;
 			}
 			}
+			if (p->flags&FieldFlag_no_capture) {
+				param->flags |= EntityFlag_NoCapture;
+			}
+
 
 
 			param->state = EntityState_Resolved; // NOTE(bill): This should have be resolved whilst determining it
 			param->state = EntityState_Resolved; // NOTE(bill): This should have be resolved whilst determining it
 			add_entity(ctx, scope, name, param);
 			add_entity(ctx, scope, name, param);
@@ -2430,9 +2473,15 @@ gb_internal i64 check_array_count(CheckerContext *ctx, Operand *o, Ast *e) {
 	if (e == nullptr) {
 	if (e == nullptr) {
 		return 0;
 		return 0;
 	}
 	}
-	if (e->kind == Ast_UnaryExpr &&
-	    e->UnaryExpr.op.kind == Token_Question) {
-		return -1;
+	if (e->kind == Ast_UnaryExpr) {
+		Token op = e->UnaryExpr.op;
+		if (op.kind == Token_Question) {
+			return -1;
+		}
+		if (e->UnaryExpr.expr == nullptr) {
+			error(op, "Invalid array count '[%.*s]'", LIT(op.string));
+			return 0;
+		}
 	}
 	}
 
 
 	check_expr_or_type(ctx, o, e);
 	check_expr_or_type(ctx, o, e);

+ 23 - 18
src/checker.cpp

@@ -50,15 +50,6 @@ gb_internal bool check_rtti_type_disallowed(Ast *expr, Type *type, char const *f
 	return check_rtti_type_disallowed(ast_token(expr), type, format);
 	return check_rtti_type_disallowed(ast_token(expr), type, format);
 }
 }
 
 
-gb_internal void scope_reset(Scope *scope) {
-	if (scope == nullptr) return;
-
-	rw_mutex_lock(&scope->mutex);
-	scope->head_child.store(nullptr, std::memory_order_relaxed);
-	string_map_clear(&scope->elements);
-	ptr_set_clear(&scope->imported);
-	rw_mutex_unlock(&scope->mutex);
-}
 
 
 gb_internal void scope_reserve(Scope *scope, isize count) {
 gb_internal void scope_reserve(Scope *scope, isize count) {
 	string_map_reserve(&scope->elements, 2*count);
 	string_map_reserve(&scope->elements, 2*count);
@@ -168,9 +159,6 @@ gb_internal void import_graph_node_swap(ImportGraphNode **data, isize i, isize j
 }
 }
 
 
 
 
-
-
-
 gb_internal void init_decl_info(DeclInfo *d, Scope *scope, DeclInfo *parent) {
 gb_internal void init_decl_info(DeclInfo *d, Scope *scope, DeclInfo *parent) {
 	gb_zero_item(d);
 	gb_zero_item(d);
 	if (parent) {
 	if (parent) {
@@ -184,6 +172,9 @@ gb_internal void init_decl_info(DeclInfo *d, Scope *scope, DeclInfo *parent) {
 	ptr_set_init(&d->deps, 0);
 	ptr_set_init(&d->deps, 0);
 	ptr_set_init(&d->type_info_deps, 0);
 	ptr_set_init(&d->type_info_deps, 0);
 	d->labels.allocator = heap_allocator();
 	d->labels.allocator = heap_allocator();
+	d->variadic_reuses.allocator = heap_allocator();
+	d->variadic_reuse_max_bytes = 0;
+	d->variadic_reuse_max_align = 1;
 }
 }
 
 
 gb_internal DeclInfo *make_decl_info(Scope *scope, DeclInfo *parent) {
 gb_internal DeclInfo *make_decl_info(Scope *scope, DeclInfo *parent) {
@@ -381,6 +372,7 @@ gb_internal Entity *scope_lookup_current(Scope *s, String const &name) {
 	return nullptr;
 	return nullptr;
 }
 }
 
 
+
 gb_internal void scope_lookup_parent(Scope *scope, String const &name, Scope **scope_, Entity **entity_) {
 gb_internal void scope_lookup_parent(Scope *scope, String const &name, Scope **scope_, Entity **entity_) {
 	if (scope != nullptr) {
 	if (scope != nullptr) {
 		bool gone_thru_proc = false;
 		bool gone_thru_proc = false;
@@ -508,9 +500,15 @@ end:;
 	return result;
 	return result;
 }
 }
 
 
+gb_global bool in_single_threaded_checker_stage = false;
+
 gb_internal Entity *scope_insert(Scope *s, Entity *entity) {
 gb_internal Entity *scope_insert(Scope *s, Entity *entity) {
 	String name = entity->token.string;
 	String name = entity->token.string;
-	return scope_insert_with_name(s, name, entity);
+	if (in_single_threaded_checker_stage) {
+		return scope_insert_with_name_no_mutex(s, name, entity);
+	} else {
+		return scope_insert_with_name(s, name, entity);
+	}
 }
 }
 
 
 gb_internal Entity *scope_insert_no_mutex(Scope *s, Entity *entity) {
 gb_internal Entity *scope_insert_no_mutex(Scope *s, Entity *entity) {
@@ -655,7 +653,7 @@ gb_internal bool check_vet_shadowing(Checker *c, Entity *e, VettedEntity *ve) {
 		}
 		}
 	}
 	}
 
 
-	zero_item(ve);
+	gb_zero_item(ve);
 	ve->kind = VettedEntity_Shadowed;
 	ve->kind = VettedEntity_Shadowed;
 	ve->entity = e;
 	ve->entity = e;
 	ve->other = shadowed;
 	ve->other = shadowed;
@@ -674,7 +672,7 @@ gb_internal bool check_vet_unused(Checker *c, Entity *e, VettedEntity *ve) {
 			}
 			}
 		case Entity_ImportName:
 		case Entity_ImportName:
 		case Entity_LibraryName:
 		case Entity_LibraryName:
-			zero_item(ve);
+			gb_zero_item(ve);
 			ve->kind = VettedEntity_Unused;
 			ve->kind = VettedEntity_Unused;
 			ve->entity = e;
 			ve->entity = e;
 			return true;
 			return true;
@@ -1114,7 +1112,11 @@ gb_internal void init_universal(void) {
 		int minimum_os_version = 0;
 		int minimum_os_version = 0;
 		if (build_context.minimum_os_version_string != "") {
 		if (build_context.minimum_os_version_string != "") {
 			int major, minor, revision = 0;
 			int major, minor, revision = 0;
+		#if defined(GB_SYSTEM_WINDOWS)
+			sscanf_s(cast(const char *)(build_context.minimum_os_version_string.text), "%d.%d.%d", &major, &minor, &revision);
+		#else
 			sscanf(cast(const char *)(build_context.minimum_os_version_string.text), "%d.%d.%d", &major, &minor, &revision);
 			sscanf(cast(const char *)(build_context.minimum_os_version_string.text), "%d.%d.%d", &major, &minor, &revision);
+		#endif
 			minimum_os_version = (major*10000)+(minor*100)+revision;
 			minimum_os_version = (major*10000)+(minor*100)+revision;
 		}
 		}
 		add_global_constant("ODIN_MINIMUM_OS_VERSION", t_untyped_integer, exact_value_i64(minimum_os_version));
 		add_global_constant("ODIN_MINIMUM_OS_VERSION", t_untyped_integer, exact_value_i64(minimum_os_version));
@@ -1386,7 +1388,7 @@ gb_internal void reset_checker_context(CheckerContext *ctx, AstFile *file, Untyp
 	auto type_path = ctx->type_path;
 	auto type_path = ctx->type_path;
 	array_clear(type_path);
 	array_clear(type_path);
 
 
-	zero_size(&ctx->pkg, gb_size_of(CheckerContext) - gb_offset_of(CheckerContext, pkg));
+	gb_zero_size(&ctx->pkg, gb_size_of(CheckerContext) - gb_offset_of(CheckerContext, pkg));
 
 
 	ctx->file = nullptr;
 	ctx->file = nullptr;
 	ctx->scope = builtin_pkg->scope;
 	ctx->scope = builtin_pkg->scope;
@@ -1788,8 +1790,7 @@ gb_internal void add_entity_use(CheckerContext *c, Ast *identifier, Entity *enti
 	if (identifier == nullptr || identifier->kind != Ast_Ident) {
 	if (identifier == nullptr || identifier->kind != Ast_Ident) {
 		return;
 		return;
 	}
 	}
-	Ast *empty_ident = nullptr;
-	entity->identifier.compare_exchange_strong(empty_ident, identifier);
+	entity->identifier.store(identifier);
 
 
 	identifier->Ident.entity = entity;
 	identifier->Ident.entity = entity;
 
 
@@ -4584,6 +4585,8 @@ gb_internal void check_single_global_entity(Checker *c, Entity *e, DeclInfo *d)
 }
 }
 
 
 gb_internal void check_all_global_entities(Checker *c) {
 gb_internal void check_all_global_entities(Checker *c) {
+	in_single_threaded_checker_stage = true;
+
 	// NOTE(bill): This must be single threaded
 	// NOTE(bill): This must be single threaded
 	// Don't bother trying
 	// Don't bother trying
 	for_array(i, c->info.entities) {
 	for_array(i, c->info.entities) {
@@ -4603,6 +4606,8 @@ gb_internal void check_all_global_entities(Checker *c) {
 			(void)type_align_of(e->type);
 			(void)type_align_of(e->type);
 		}
 		}
 	}
 	}
+
+	in_single_threaded_checker_stage = false;
 }
 }
 
 
 
 

+ 9 - 0
src/checker.hpp

@@ -181,6 +181,11 @@ char const *ProcCheckedState_strings[ProcCheckedState_COUNT] {
 	"Checked",
 	"Checked",
 };
 };
 
 
+struct VariadicReuseData {
+	Type *slice_type; // ..elem_type
+	i64 max_count;
+};
+
 // DeclInfo is used to store information of certain declarations to allow for "any order" usage
 // DeclInfo is used to store information of certain declarations to allow for "any order" usage
 struct DeclInfo {
 struct DeclInfo {
 	DeclInfo *    parent; // NOTE(bill): only used for procedure literals at the moment
 	DeclInfo *    parent; // NOTE(bill): only used for procedure literals at the moment
@@ -219,6 +224,10 @@ struct DeclInfo {
 
 
 	Array<BlockLabel> labels;
 	Array<BlockLabel> labels;
 
 
+	Array<VariadicReuseData> variadic_reuses;
+	i64 variadic_reuse_max_bytes;
+	i64 variadic_reuse_max_align;
+
 	// NOTE(bill): this is to prevent a race condition since these procedure literals can be created anywhere at any time
 	// NOTE(bill): this is to prevent a race condition since these procedure literals can be created anywhere at any time
 	struct lbModule *code_gen_module;
 	struct lbModule *code_gen_module;
 };
 };

Some files were not shown because too many files changed in this diff