瀏覽代碼

Merge remote-tracking branch 'upstream/master' into prototype-fmt

Daniel Gavin 4 年之前
父節點
當前提交
c708f649ec
共有 64 個文件被更改,包括 6555 次插入266 次删除
  1. 42 0
      core/bytes/buffer.odin
  2. 19 9
      core/io/io.odin
  3. 4 2
      core/math/linalg/specific.odin
  4. 261 0
      core/odin/doc-format/doc_format.odin
  5. 11 0
      core/os/os2/doc.odin
  6. 43 0
      core/os/os2/env.odin
  7. 80 0
      core/os/os2/env_windows.odin
  8. 126 0
      core/os/os2/errors.odin
  9. 14 0
      core/os/os2/errors_windows.odin
  10. 158 0
      core/os/os2/file.odin
  11. 98 0
      core/os/os2/file_stream.odin
  12. 122 0
      core/os/os2/file_util.odin
  13. 136 0
      core/os/os2/file_windows.odin
  14. 21 0
      core/os/os2/heap.odin
  15. 107 0
      core/os/os2/heap_windows.odin
  16. 29 0
      core/os/os2/path.odin
  17. 31 0
      core/os/os2/path_windows.odin
  18. 5 0
      core/os/os2/pipe.odin
  19. 13 0
      core/os/os2/pipe_windows.odin
  20. 101 0
      core/os/os2/process.odin
  21. 42 0
      core/os/os2/stat.odin
  22. 373 0
      core/os/os2/stat_windows.odin
  23. 14 0
      core/os/os2/temp_file.odin
  24. 29 0
      core/os/os2/temp_file_windows.odin
  25. 68 0
      core/os/os2/user.odin
  26. 33 4
      core/os/stat_unix.odin
  27. 38 6
      core/path/filepath/path_unix.odin
  28. 7 7
      core/runtime/internal_linux.odin
  29. 92 92
      core/runtime/internal_windows.odin
  30. 4 4
      core/slice/slice.odin
  31. 79 0
      core/sync/sync2/atomic.odin
  32. 886 0
      core/sync/sync2/channel.odin
  33. 17 0
      core/sync/sync2/channel_unix.odin
  34. 34 0
      core/sync/sync2/channel_windows.odin
  35. 238 0
      core/sync/sync2/extended.odin
  36. 185 0
      core/sync/sync2/primitives.odin
  37. 244 0
      core/sync/sync2/primitives_atomic.odin
  38. 154 0
      core/sync/sync2/primitives_pthreads.odin
  39. 73 0
      core/sync/sync2/primitives_windows.odin
  40. 54 0
      core/sys/windows/advapi32.odin
  41. 37 0
      core/sys/windows/netapi32.odin
  42. 449 2
      core/sys/windows/types.odin
  43. 28 0
      core/sys/windows/userenv.odin
  44. 374 0
      core/sys/windows/util.odin
  45. 40 30
      core/thread/thread.odin
  46. 28 27
      core/thread/thread_unix.odin
  47. 16 21
      core/thread/thread_windows.odin
  48. 2 0
      src/build_settings.cpp
  49. 4 0
      src/check_decl.cpp
  50. 16 16
      src/check_expr.cpp
  51. 3 1
      src/check_type.cpp
  52. 12 0
      src/checker.cpp
  53. 1 0
      src/checker.hpp
  54. 62 15
      src/docs.cpp
  55. 216 0
      src/docs_format.cpp
  56. 1092 0
      src/docs_writer.cpp
  57. 9 5
      src/entity.cpp
  58. 7 1
      src/ir.cpp
  59. 43 3
      src/llvm_backend.cpp
  60. 7 0
      src/llvm_backend.hpp
  61. 5 6
      src/llvm_backend_opt.cpp
  62. 18 13
      src/main.cpp
  63. 1 1
      src/tokenizer.cpp
  64. 0 1
      src/types.cpp

+ 42 - 0
core/bytes/buffer.odin

@@ -135,6 +135,22 @@ buffer_grow :: proc(b: ^Buffer, n: int) {
 	resize(&b.buf, m);
 }
 
+buffer_write_at :: proc(b: ^Buffer, p: []byte, offset: int) -> (n: int, err: io.Error) {
+	b.last_read = .Invalid;
+	if offset < 0 {
+		err = .Invalid_Offset;
+		return;
+	}
+	_, ok := _buffer_try_grow(b, offset+len(p));
+	if !ok {
+		_ = _buffer_grow(b, offset+len(p));
+	}
+	if len(b.buf) <= offset {
+		return 0, .Short_Write;
+	}
+	return copy(b.buf[offset:], p), nil;
+}
+
 
 buffer_write :: proc(b: ^Buffer, p: []byte) -> (n: int, err: io.Error) {
 	b.last_read = .Invalid;
@@ -213,6 +229,24 @@ buffer_read :: proc(b: ^Buffer, p: []byte) -> (n: int, err: io.Error) {
 	return;
 }
 
+buffer_read_at :: proc(b: ^Buffer, p: []byte, offset: int) -> (n: int, err: io.Error) {
+	b.last_read = .Invalid;
+
+	if offset < 0 || offset >= len(b.buf) {
+		err = .Invalid_Offset;
+		return;
+	}
+
+	if 0 <= offset && offset < len(b.buf) {
+		n = copy(p, b.buf[offset:]);
+	}
+	if n > 0 {
+		b.last_read = .Read;
+	}
+	return;
+}
+
+
 buffer_read_byte :: proc(b: ^Buffer) -> (byte, io.Error) {
 	if buffer_is_empty(b) {
 		buffer_reset(b);
@@ -346,6 +380,10 @@ _buffer_vtable := &io.Stream_VTable{
 		b := (^Buffer)(s.stream_data);
 		return buffer_read(b, p);
 	},
+	impl_read_at = proc(s: io.Stream, p: []byte, offset: i64) -> (n: int, err: io.Error) {
+		b := (^Buffer)(s.stream_data);
+		return buffer_read_at(b, p, int(offset));
+	},
 	impl_read_byte = proc(s: io.Stream) -> (byte, io.Error) {
 		b := (^Buffer)(s.stream_data);
 		return buffer_read_byte(b);
@@ -358,6 +396,10 @@ _buffer_vtable := &io.Stream_VTable{
 		b := (^Buffer)(s.stream_data);
 		return buffer_write(b, p);
 	},
+	impl_write_at = proc(s: io.Stream, p: []byte, offset: i64) -> (n: int, err: io.Error) {
+		b := (^Buffer)(s.stream_data);
+		return buffer_write_at(b, p, int(offset));
+	},
 	impl_write_byte = proc(s: io.Stream, c: byte) -> io.Error {
 		b := (^Buffer)(s.stream_data);
 		return buffer_write_byte(b, c);

+ 19 - 9
core/io/io.odin

@@ -23,6 +23,9 @@ Error :: enum i32 {
 	// Short_Write means that a write accepted fewer bytes than requested but failed to return an explicit error
 	Short_Write,
 
+	// Invalid_Write means that a write returned an impossible count
+	Invalid_Write,
+
 	// Short_Buffer means that a read required a longer buffer than was provided
 	Short_Buffer,
 
@@ -40,6 +43,9 @@ Error :: enum i32 {
 	Negative_Count,
 	Buffer_Full,
 
+	// Unknown means that an error has occurred but cannot be categorized
+	Unknown,
+
 	// Empty is returned when a procedure has not been implemented for an io.Stream
 	Empty = -1,
 }
@@ -212,17 +218,17 @@ read_at :: proc(r: Reader_At, p: []byte, offset: i64) -> (n: int, err: Error) {
 		return 0, .Empty;
 	}
 
-	current_offset: i64;
-	current_offset, err = r->impl_seek(offset, .Current);
+	curr_offset: i64;
+	curr_offset, err = r->impl_seek(offset, .Current);
 	if err != nil {
 		return 0, err;
 	}
 
 	n, err = r->impl_read(p);
-	if err != nil {
-		return;
+	_, err1 := r->impl_seek(curr_offset, .Start);
+	if err1 != nil && err == nil {
+		err = err1;
 	}
-	_, err = r->impl_seek(current_offset, .Start);
 	return;
 
 }
@@ -238,14 +244,18 @@ write_at :: proc(w: Writer_At, p: []byte, offset: i64) -> (n: int, err: Error) {
 		return 0, .Empty;
 	}
 
-	current_offset: i64;
-	current_offset, err = w->impl_seek(offset, .Current);
+	curr_offset: i64;
+	curr_offset, err = w->impl_seek(offset, .Current);
 	if err != nil {
 		return 0, err;
 	}
-	defer w->impl_seek(current_offset, .Start);
 
-	return w->impl_write(p);
+	n, err = w->impl_write(p);
+	_, err1 := w->impl_seek(curr_offset, .Start);
+	if err1 != nil && err == nil {
+		err = err1;
+	}
+	return;
 }
 
 write_to :: proc(r: Writer_To, w: Writer) -> (n: i64, err: Error) {

+ 4 - 2
core/math/linalg/specific.odin

@@ -711,7 +711,8 @@ quaternion_nlerp_f16 :: proc(a, b: Quaternionf16, t: f16) -> (c: Quaternionf16)
 	c.z = a.z + (b.z-a.z)*t;
 	c.w = a.w + (b.w-a.w)*t;
 	return normalize(c);
-}quaternion_nlerp_f32 :: proc(a, b: Quaternionf32, t: f32) -> (c: Quaternionf32) {
+}
+quaternion_nlerp_f32 :: proc(a, b: Quaternionf32, t: f32) -> (c: Quaternionf32) {
 	c.x = a.x + (b.x-a.x)*t;
 	c.y = a.y + (b.y-a.y)*t;
 	c.z = a.z + (b.z-a.z)*t;
@@ -758,7 +759,8 @@ quaternion_slerp_f16 :: proc(x, y: Quaternionf16, t: f16) -> (q: Quaternionf16)
 	q.z = factor_a * a.z + factor_b * b.z;
 	q.w = factor_a * a.w + factor_b * b.w;
 	return;
-}quaternion_slerp_f32 :: proc(x, y: Quaternionf32, t: f32) -> (q: Quaternionf32) {
+}
+quaternion_slerp_f32 :: proc(x, y: Quaternionf32, t: f32) -> (q: Quaternionf32) {
 	a, b := x, y;
 	cos_angle := dot(a, b);
 	if cos_angle < 0 {

+ 261 - 0
core/odin/doc-format/doc_format.odin

@@ -0,0 +1,261 @@
+package odin_doc_format
+
+import "core:mem"
+
+Array :: struct($T: typeid) {
+	offset: u32le,
+	length: u32le,
+}
+
+String :: distinct Array(byte);
+
+Version_Type_Major :: 0;
+Version_Type_Minor :: 1;
+Version_Type_Patch :: 0;
+
+Version_Type :: struct {
+	major, minor, patch: u8,
+	_: u8,
+};
+
+Version_Type_Default :: Version_Type{
+	major=Version_Type_Major,
+	minor=Version_Type_Minor,
+	patch=Version_Type_Patch,
+};
+
+Magic_String :: "odindoc\x00";
+
+Header_Base :: struct {
+	magic: [8]byte,
+	_: u32le,
+	version:     Version_Type,
+	total_size:  u32le,
+	header_size: u32le,
+	hash:        u32le,
+}
+
+Header :: struct {
+	using base: Header_Base,
+
+	// NOTE: These arrays reserve the zero element as a sentinel value
+	files:    Array(File),
+	pkgs:     Array(Pkg),
+	entities: Array(Entity),
+	types:    Array(Type),
+}
+
+File_Index   :: distinct u32le;
+Pkg_Index    :: distinct u32le;
+Entity_Index :: distinct u32le;
+Type_Index   :: distinct u32le;
+
+
+Position :: struct {
+	file:   File_Index,
+	line:   u32le,
+	column: u32le,
+	offset: u32le,
+};
+
+File :: struct {
+	pkg:  Pkg_Index,
+	name: String,
+}
+
+Pkg_Flag :: enum u32le {
+	Builtin = 0,
+	Runtime = 1,
+	Init    = 2,
+}
+
+Pkg_Flags :: distinct bit_set[Pkg_Flag; u32le];
+
+Pkg :: struct {
+	fullpath: String,
+	name:     String,
+	flags:    Pkg_Flags,
+	docs:     String,
+	files:    Array(File_Index),
+	entities: Array(Entity_Index),
+}
+
+Entity_Kind :: enum u32le {
+	Invalid      = 0,
+	Constant     = 1,
+	Variable     = 2,
+	Type_Name    = 3,
+	Procedure    = 4,
+	Proc_Group   = 5,
+	Import_Name  = 6,
+	Library_Name = 7,
+}
+
+Entity_Flag :: enum u32le {
+	Foreign = 0,
+	Export  = 1,
+
+	Param_Using     = 2,
+	Param_Const     = 3,
+	Param_Auto_Cast = 4,
+	Param_Ellipsis  = 5,
+	Param_CVararg   = 6,
+	Param_No_Alias  = 7,
+
+	Type_Alias = 8,
+
+	Var_Thread_Local = 9,
+}
+
+Entity_Flags :: distinct bit_set[Entity_Flag; u32le];
+
+Entity :: struct {
+	kind:             Entity_Kind,
+	flags:            Entity_Flags,
+	pos:              Position,
+	name:             String,
+	type:             Type_Index,
+	init_string:      String,
+	_:                u32le,
+	comment:          String,
+	docs:             String,
+	foreign_library:  Entity_Index,
+	link_name:        String,
+	attributes:       Array(Attribute),
+	grouped_entities: Array(Entity_Index), // Procedure Groups
+	where_clauses:    Array(String), // Procedures
+}
+
+Attribute :: struct {
+	name:  String,
+	value: String,
+}
+
+Type_Kind :: enum u32le {
+	Invalid            = 0,
+	Basic              = 1,
+	Named              = 2,
+	Generic            = 3,
+	Pointer            = 4,
+	Array              = 5,
+	Enumerated_Array   = 6,
+	Slice              = 7,
+	Dynamic_Array      = 8,
+	Map                = 9,
+	Struct             = 10,
+	Union              = 11,
+	Enum               = 12,
+	Tuple              = 13,
+	Proc               = 14,
+	Bit_Set            = 15,
+	Simd_Vector        = 16,
+	SOA_Struct_Fixed   = 17,
+	SOA_Struct_Slice   = 18,
+	SOA_Struct_Dynamic = 19,
+	Relative_Pointer   = 20,
+	Relative_Slice     = 21,
+}
+
+Type_Elems_Cap :: 4;
+
+Type :: struct {
+	kind:         Type_Kind,
+	flags:        u32le, // Type_Kind specific
+	name:         String,
+	custom_align: String,
+
+	// Used by some types
+	elem_count_len: u32le,
+	elem_counts:    [Type_Elems_Cap]i64le,
+
+	// Each of these is esed by some types, not all
+	calling_convention: String, // Procedures
+	types:              Array(Type_Index),
+	entities:           Array(Entity_Index),
+	polymorphic_params: Type_Index, // Struct, Union
+	where_clauses:      Array(String), // Struct, Union
+}
+
+Type_Flags_Basic :: distinct bit_set[Type_Flag_Basic; u32le];
+Type_Flag_Basic :: enum u32le {
+	Untyped = 1,
+}
+
+Type_Flags_Struct :: distinct bit_set[Type_Flag_Struct; u32le];
+Type_Flag_Struct :: enum u32le {
+	Polymorphic = 0,
+	Packed      = 1,
+	Raw_Union   = 2,
+}
+
+Type_Flags_Union :: distinct bit_set[Type_Flag_Union; u32le];
+Type_Flag_Union :: enum u32le {
+	Polymorphic = 0,
+	No_Nil      = 1,
+	Maybe       = 2,
+}
+
+Type_Flags_Proc :: distinct bit_set[Type_Flag_Proc; u32le];
+Type_Flag_Proc :: enum u32le {
+	Polymorphic = 0,
+	Diverging   = 1,
+	Optional_Ok = 2,
+	Variadic    = 3,
+	C_Vararg    = 4,
+}
+
+Type_Flags_Bit_Set :: distinct bit_set[Type_Flag_Bit_Set; u32le];
+Type_Flag_Bit_Set :: enum u32le {
+	Range            = 1,
+	Op_Lt            = 2,
+	Op_Lt_Eq         = 3,
+	Underlying_Type  = 4,
+}
+
+Type_Flags_SimdVector :: distinct bit_set[Type_Flag_SimdVector; u32le];
+Type_Flag_SimdVector :: enum u32le {
+	x86_mmx = 1,
+}
+
+from_array :: proc(base: ^Header_Base, a: $A/Array($T)) -> []T {
+	s: mem.Raw_Slice;
+	s.data = rawptr(uintptr(base) + uintptr(a.offset));
+	s.len = int(a.length);
+	return transmute([]T)s;
+}
+from_string :: proc(base: ^Header_Base, s: String) -> string {
+	return string(from_array(base, s));
+}
+
+
+
+
+Reader_Error :: enum {
+	None,
+	Header_Too_Small,
+	Invalid_Magic,
+	Data_Too_Small,
+	Invalid_Version,
+}
+
+read_from_bytes :: proc(data: []byte) -> (h: ^Header, err: Reader_Error) {
+	if len(data) < size_of(Header_Base) {
+		err = .Header_Too_Small;
+		return;
+	}
+	header_base := (^Header_Base)(raw_data(data));
+	if header_base.magic != Magic_String {
+		err = .Invalid_Magic;
+		return;
+	}
+	if len(data) < int(header_base.total_size) {
+		err = .Data_Too_Small;
+		return;
+	}
+	if header_base.version != Version_Type_Default {
+		err = .Invalid_Version;
+		return;
+	}
+	h = (^Header)(header_base);
+	return;
+}

+ 11 - 0
core/os/os2/doc.odin

@@ -0,0 +1,11 @@
+// Package os provides a platform-independent interface to operating system functionality.
+// The design is UNIX-like but with Odin-like error handling. Failing calls return values with a specific error type rather than error number.
+//
+// The package os interface is intended to be uniform across all operating systems.
+// Features not generally available appear in the system-specific packages under core:sys/*.
+//
+//
+// IMPORTANT NOTE from Bill: this is purely a mockup of what I want the new package os to be, and NON-FUNCTIONING.
+// It is not complete but should give designers a better idea of the general interface and how to write things.
+// This entire interface is subject to change.
+package os2

+ 43 - 0
core/os/os2/env.odin

@@ -0,0 +1,43 @@
+package os2
+
+// get_env retrieves the value of the environment variable named by the key
+// It returns the value, which will be empty if the variable is not present
+// To distinguish between an empty value and an unset value, use lookup_env
+// NOTE: the value will be allocated with the supplied allocator
+get_env :: proc(key: string, allocator := context.allocator) -> string {
+	value, _ := lookup_env(key, allocator);
+	return value;
+}
+
+// lookup_env gets the value of the environment variable named by the key
+// If the variable is found in the environment the value (which can be empty) is returned and the boolean is true
+// Otherwise the returned value will be empty and the boolean will be false
+// NOTE: the value will be allocated with the supplied allocator
+lookup_env :: proc(key: string, allocator := context.allocator) -> (value: string, found: bool) {
+	return _lookup_env(key, allocator);
+}
+
+// set_env sets the value of the environment variable named by the key
+// Returns true on success, false on failure
+set_env :: proc(key, value: string) -> bool {
+	return _set_env(key, value);
+}
+
+// unset_env unsets a single environment variable
+// Returns true on success, false on failure
+unset_env :: proc(key: string) -> bool {
+	return _unset_env(key);
+}
+
+clear_env :: proc() {
+	_clear_env();
+}
+
+
+// environ returns a copy of strings representing the environment, in the form "key=value"
+// NOTE: the slice of strings and the strings with be allocated using the supplied allocator
+environ :: proc(allocator := context.allocator) -> []string {
+	return _environ(allocator);
+}
+
+

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

@@ -0,0 +1,80 @@
+//+private
+package os2
+
+import "core:mem"
+import win32 "core:sys/windows"
+
+_lookup_env :: proc(key: string, allocator := context.allocator) -> (value: string, found: bool) {
+	if key == "" {
+		return;
+	}
+	wkey := win32.utf8_to_wstring(key);
+	b := make([dynamic]u16, 100, context.temp_allocator);
+	for {
+		n := win32.GetEnvironmentVariableW(wkey, raw_data(b), u32(len(b)));
+		if n == 0 {
+			err := win32.GetLastError();
+			if err == win32.ERROR_ENVVAR_NOT_FOUND {
+				return "", false;
+			}
+		}
+
+		if n <= u32(len(b)) {
+			value = win32.utf16_to_utf8(b[:n], allocator);
+			found = true;
+			return;
+		}
+
+		resize(&b, len(b)*2);
+	}
+}
+
+_set_env :: proc(key, value: string) -> bool {
+	k := win32.utf8_to_wstring(key);
+	v := win32.utf8_to_wstring(value);
+
+	return bool(win32.SetEnvironmentVariableW(k, v));
+}
+
+_unset_env :: proc(key: string) -> bool {
+	k := win32.utf8_to_wstring(key);
+	return bool(win32.SetEnvironmentVariableW(k, nil));
+}
+
+_clear_env :: proc() {
+	envs := environ(context.temp_allocator);
+	for env in envs {
+		for j in 1..<len(env) {
+			if env[j] == '=' {
+				unset_env(env[0:j]);
+				break;
+			}
+		}
+	}
+}
+
+_environ :: proc(allocator := context.allocator) -> []string {
+	envs := win32.GetEnvironmentStringsW();
+	if envs == nil {
+		return nil;
+	}
+	defer win32.FreeEnvironmentStringsW(envs);
+
+	r := make([dynamic]string, 0, 50, allocator);
+	for from, i, p := 0, 0, envs; true; i += 1 {
+		c := (^u16)(uintptr(p) + uintptr(i*2))^;
+		if c == 0 {
+			if i <= from {
+				break;
+			}
+			w := mem.slice_ptr(mem.ptr_offset(p, from), i-from);
+
+			append(&r, win32.utf16_to_utf8(w, allocator));
+			from = i + 1;
+		}
+	}
+
+	return r[:];
+}
+
+

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

@@ -0,0 +1,126 @@
+package os2
+
+Platform_Error_Min_Bits :: 32;
+
+Error :: enum u64 {
+	None = 0,
+
+	// General Errors
+	Invalid_Argument,
+
+	Permission_Denied,
+	Exist,
+	Not_Exist,
+	Closed,
+
+	// Timeout Errors
+	Timeout,
+
+	// I/O Errors
+	// EOF is the error returned by `read` when no more input is available
+	EOF,
+
+	// Unexpected_EOF means that EOF was encountered in the middle of reading a fixed-sized block of data
+	Unexpected_EOF,
+
+	// Short_Write means that a write accepted fewer bytes than requested but failed to return an explicit error
+	Short_Write,
+
+	// Invalid_Write means that a write returned an impossible count
+	Invalid_Write,
+
+	// Short_Buffer means that a read required a longer buffer than was provided
+	Short_Buffer,
+
+	// No_Progress is returned by some implementations of `io.Reader` when many calls
+	// to `read` have failed to return any data or error.
+	// This is usually a signed of a broken `io.Reader` implementation
+	No_Progress,
+
+	Invalid_Whence,
+	Invalid_Offset,
+	Invalid_Unread,
+
+	Negative_Read,
+	Negative_Write,
+	Negative_Count,
+	Buffer_Full,
+
+	// Platform Specific Errors
+	Platform_Minimum = 1<<Platform_Error_Min_Bits,
+}
+
+Path_Error :: struct {
+	op:   string,
+	path: string,
+	err:  Error,
+}
+
+Link_Error :: struct {
+	op:  string,
+	old: string,
+	new: string,
+	err: Error,
+}
+
+path_error_delete :: proc(perr: Maybe(Path_Error)) {
+	if err, ok := perr.?; ok {
+		context.allocator = error_allocator();
+		delete(err.op);
+		delete(err.path);
+	}
+}
+
+link_error_delete :: proc(lerr: Maybe(Link_Error)) {
+	if err, ok := lerr.?; ok {
+		context.allocator = error_allocator();
+		delete(err.op);
+		delete(err.old);
+		delete(err.new);
+	}
+}
+
+
+
+is_platform_error :: proc(ferr: Error) -> (err: i32, ok: bool) {
+	if ferr >= .Platform_Minimum {
+		err = i32(u64(ferr)>>Platform_Error_Min_Bits);
+		ok = true;
+	}
+	return;
+}
+
+error_from_platform_error :: proc(errno: i32) -> Error {
+	return Error(u64(errno) << Platform_Error_Min_Bits);
+}
+
+error_string :: proc(ferr: Error) -> string {
+	#partial switch ferr {
+	case .None:              return "";
+	case .Invalid_Argument:  return "invalid argument";
+	case .Permission_Denied: return "permission denied";
+	case .Exist:             return "file already exists";
+	case .Not_Exist:         return "file does not exist";
+	case .Closed:            return "file already closed";
+	case .Timeout:           return "i/o timeout";
+	case .EOF:               return "eof";
+	case .Unexpected_EOF:    return "unexpected eof";
+	case .Short_Write:       return "short write";
+	case .Invalid_Write:     return "invalid write result";
+	case .Short_Buffer:      return "short buffer";
+	case .No_Progress:       return "multiple read calls return no data or error";
+	case .Invalid_Whence:    return "invalid whence";
+	case .Invalid_Offset:    return "invalid offset";
+	case .Invalid_Unread:    return "invalid unread";
+	case .Negative_Read:     return "negative read";
+	case .Negative_Write:    return "negative write";
+	case .Negative_Count:    return "negative count";
+	case .Buffer_Full:       return "buffer full";
+	}
+
+	if errno, ok := is_platform_error(ferr); ok {
+		return _error_string(errno);
+	}
+
+	return "unknown error";
+}

+ 14 - 0
core/os/os2/errors_windows.odin

@@ -0,0 +1,14 @@
+//+private
+package os2
+
+import win32 "core:sys/windows"
+
+_error_string :: proc(errno: i32) -> string {
+	e := win32.DWORD(errno);
+	if e == 0 {
+		return "";
+	}
+	// TODO(bill): _error_string for windows
+	// FormatMessageW
+	return "";
+}

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

@@ -0,0 +1,158 @@
+package os2
+
+import "core:io"
+import "core:time"
+
+Handle :: distinct uintptr;
+
+Seek_From :: enum {
+	Start   = 0, // seek relative to the origin of the file
+	Current = 1, // seek relative to the current offset
+	End     = 2, // seek relative to the end
+}
+
+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);
+
+
+O_RDONLY :: int( 0);
+O_WRONLY :: int( 1);
+O_RDWR   :: int( 2);
+O_APPEND :: int( 4);
+O_CREATE :: int( 8);
+O_EXCL   :: int(16);
+O_SYNC   :: int(32);
+O_TRUNC  :: int(64);
+
+
+
+stdin:  Handle = 0; // OS-Specific
+stdout: Handle = 1; // OS-Specific
+stderr: Handle = 2; // OS-Specific
+
+
+create :: proc(name: string) -> (Handle, Error) {
+	return _create(name);
+}
+
+open :: proc(name: string) -> (Handle, Error) {
+	return _open(name);
+}
+
+open_file :: proc(name: string, flag: int, perm: File_Mode) -> (Handle, Error) {
+	return _open_file(name, flag, perm);
+}
+
+close :: proc(fd: Handle) -> Error {
+	return _close(fd);
+}
+
+name :: proc(fd: Handle, allocator := context.allocator) -> string {
+	return _name(fd);
+}
+
+seek :: proc(fd: Handle, offset: i64, whence: Seek_From) -> (ret: i64, err: Error) {
+	return _seek(fd, offset, whence);
+}
+
+read :: proc(fd: Handle, p: []byte) -> (n: int, err: Error) {
+	return _read(fd, p);
+}
+
+read_at :: proc(fd: Handle, p: []byte, offset: i64) -> (n: int, err: Error) {
+	return _read_at(fd, p, offset);
+}
+
+read_from :: proc(fd: Handle, r: io.Reader) -> (n: i64, err: Error) {
+	return _read_from(fd, r);
+}
+
+write :: proc(fd: Handle, p: []byte) -> (n: int, err: Error) {
+	return _write(fd, p);
+}
+
+write_at :: proc(fd: Handle, p: []byte, offset: i64) -> (n: int, err: Error) {
+	return _write_at(fd, p, offset);
+}
+
+write_to :: proc(fd: Handle, w: io.Writer) -> (n: i64, err: Error) {
+	return _write_to(fd, w);
+}
+
+file_size :: proc(fd: Handle) -> (n: i64, err: Error) {
+	return _file_size(fd);
+}
+
+
+sync :: proc(fd: Handle) -> Error {
+	return _sync(fd);
+}
+
+flush :: proc(fd: Handle) -> Error {
+	return _flush(fd);
+}
+
+truncate :: proc(fd: Handle, size: i64) -> Maybe(Path_Error) {
+	return _truncate(fd, size);
+}
+
+remove :: proc(name: string) -> Maybe(Path_Error) {
+	return _remove(name);
+}
+
+rename :: proc(old_path, new_path: string) -> Maybe(Path_Error) {
+	return _rename(old_path, new_path);
+}
+
+
+link :: proc(old_name, new_name: string) -> Maybe(Link_Error) {
+	return _link(old_name, new_name);
+}
+
+symlink :: proc(old_name, new_name: string) -> Maybe(Link_Error) {
+	return _symlink(old_name, new_name);
+}
+
+read_link :: proc(name: string) -> (string, Maybe(Path_Error)) {
+	return _read_link(name);
+}
+
+
+chdir :: proc(fd: Handle) -> Error {
+	return _chdir(fd);
+}
+
+chmod :: proc(fd: Handle, mode: File_Mode) -> Error {
+	return _chmod(fd, mode);
+}
+
+chown :: proc(fd: Handle, uid, gid: int) -> Error {
+	return _chown(fd, uid, gid);
+}
+
+
+lchown :: proc(name: string, uid, gid: int) -> Error {
+	return _lchown(name, uid, gid);
+}
+
+
+chtimes :: proc(name: string, atime, mtime: time.Time) -> Maybe(Path_Error) {
+	return _chtimes(name, atime, mtime);
+}
+
+exists :: proc(path: string) -> bool {
+	return _exists(path);
+}
+
+is_file :: proc(path: string) -> bool {
+	return _is_file(path);
+}
+
+is_dir :: proc(path: string) -> bool {
+	return _is_dir(path);
+}
+

+ 98 - 0
core/os/os2/file_stream.odin

@@ -0,0 +1,98 @@
+package os2
+
+import "core:io"
+
+file_to_stream :: proc(fd: Handle) -> (s: io.Stream) {
+	s.stream_data = rawptr(uintptr(fd));
+	s.stream_vtable = _file_stream_vtable;
+	return;
+}
+
+@(private)
+error_to_io_error :: proc(ferr: Error) -> io.Error {
+	#partial switch ferr {
+	case .None:           return .None;
+	case .EOF:            return .EOF;
+	case .Unexpected_EOF: return .Unexpected_EOF;
+	case .Short_Write:    return .Short_Write;
+	case .Invalid_Write:  return .Invalid_Write;
+	case .Short_Buffer:   return .Short_Buffer;
+	case .No_Progress:    return .No_Progress;
+	case .Invalid_Whence: return .Invalid_Whence;
+	case .Invalid_Offset: return .Invalid_Offset;
+	case .Invalid_Unread: return .Invalid_Unread;
+	case .Negative_Read:  return .Negative_Read;
+	case .Negative_Write: return .Negative_Write;
+	case .Negative_Count: return .Negative_Count;
+	case .Buffer_Full:    return .Buffer_Full;
+	}
+	return .Unknown;
+}
+
+
+@(private)
+_file_stream_vtable := &io.Stream_VTable{
+	impl_read = proc(s: io.Stream, p: []byte) -> (n: int, err: io.Error) {
+		fd := Handle(uintptr(s.stream_data));
+		ferr: Error;
+		n, ferr = read(fd, p);
+		err = error_to_io_error(ferr);
+		return;
+	},
+	impl_read_at = proc(s: io.Stream, p: []byte, offset: i64) -> (n: int, err: io.Error) {
+		fd := Handle(uintptr(s.stream_data));
+		ferr: Error;
+		n, ferr = read_at(fd, p, offset);
+		err = error_to_io_error(ferr);
+		return;
+	},
+	impl_write_to = proc(s: io.Stream, w: io.Writer) -> (n: i64, err: io.Error) {
+		fd := Handle(uintptr(s.stream_data));
+		ferr: Error;
+		n, ferr = write_to(fd, w);
+		err = error_to_io_error(ferr);
+		return;
+	},
+	impl_write = proc(s: io.Stream, p: []byte) -> (n: int, err: io.Error) {
+		fd := Handle(uintptr(s.stream_data));
+		ferr: Error;
+		n, ferr = write(fd, p);
+		err = error_to_io_error(ferr);
+		return;
+	},
+	impl_write_at = proc(s: io.Stream, p: []byte, offset: i64) -> (n: int, err: io.Error) {
+		fd := Handle(uintptr(s.stream_data));
+		ferr: Error;
+		n, ferr = write_at(fd, p, offset);
+		err = error_to_io_error(ferr);
+		return;
+	},
+	impl_read_from = proc(s: io.Stream, r: io.Reader) -> (n: i64, err: io.Error) {
+		fd := Handle(uintptr(s.stream_data));
+		ferr: Error;
+		n, ferr = read_from(fd, r);
+		err = error_to_io_error(ferr);
+		return;
+	},
+	impl_seek = proc(s: io.Stream, offset: i64, whence: io.Seek_From) -> (i64, io.Error) {
+		fd := Handle(uintptr(s.stream_data));
+		n, ferr := seek(fd, offset, Seek_From(whence));
+		err := error_to_io_error(ferr);
+		return n, err;
+	},
+	impl_size = proc(s: io.Stream) -> i64 {
+		fd := Handle(uintptr(s.stream_data));
+		sz, _ := file_size(fd);
+		return sz;
+	},
+	impl_flush = proc(s: io.Stream) -> io.Error {
+		fd := Handle(uintptr(s.stream_data));
+		ferr := flush(fd);
+		return error_to_io_error(ferr);
+	},
+	impl_close = proc(s: io.Stream) -> io.Error {
+		fd := Handle(uintptr(s.stream_data));
+		ferr := close(fd);
+		return error_to_io_error(ferr);
+	},
+};

+ 122 - 0
core/os/os2/file_util.odin

@@ -0,0 +1,122 @@
+package os2
+
+import "core:mem"
+import "core:strconv"
+import "core:unicode/utf8"
+
+write_string :: proc(fd: Handle, s: string) -> (n: int, err: Error) {
+	return write(fd, transmute([]byte)s);
+}
+
+write_byte :: proc(fd: Handle, b: byte) -> (n: int, err: Error) {
+	return write(fd, []byte{b});
+}
+
+write_rune :: proc(fd: Handle, r: rune) -> (n: int, err: Error) {
+	if r < utf8.RUNE_SELF {
+		return write_byte(fd, byte(r));
+	}
+
+	b: [4]byte;
+	b, n = utf8.encode_rune(r);
+	return write(fd, b[:n]);
+}
+
+write_encoded_rune :: proc(fd: Handle, r: rune) -> (n: int, err: Error) {
+	wrap :: proc(m: int, merr: Error, n: ^int, err: ^Error) -> bool {
+		n^ += m;
+		if merr != nil {
+			err^ = merr;
+			return true;
+		}
+		return false;
+	}
+
+	if wrap(write_byte(fd, '\''), &n, &err) { return; }
+
+	switch r {
+	case '\a': if wrap(write_string(fd, "\\a"), &n, &err) { return; }
+	case '\b': if wrap(write_string(fd, "\\b"), &n, &err) { return; }
+	case '\e': if wrap(write_string(fd, "\\e"), &n, &err) { return; }
+	case '\f': if wrap(write_string(fd, "\\f"), &n, &err) { return; }
+	case '\n': if wrap(write_string(fd, "\\n"), &n, &err) { return; }
+	case '\r': if wrap(write_string(fd, "\\r"), &n, &err) { return; }
+	case '\t': if wrap(write_string(fd, "\\t"), &n, &err) { return; }
+	case '\v': if wrap(write_string(fd, "\\v"), &n, &err) { return; }
+	case:
+		if r < 32 {
+			if wrap(write_string(fd, "\\x"), &n, &err) { return; }
+			b: [2]byte;
+			s := strconv.append_bits(b[:], u64(r), 16, true, 64, strconv.digits, nil);
+			switch len(s) {
+			case 0: if wrap(write_string(fd, "00"), &n, &err) { return; }
+			case 1: if wrap(write_rune(fd, '0'), &n, &err)    { return; }
+			case 2: if wrap(write_string(fd, s), &n, &err)    { return; }
+			}
+		} else {
+			if wrap(write_rune(fd, r), &n, &err) { return; }
+		}
+	}
+	_ = wrap(write_byte(fd, '\''), &n, &err);
+	return;
+}
+
+
+write_ptr :: proc(fd: Handle, data: rawptr, len: int) -> (n: int, err: Error) {
+	s := transmute([]byte)mem.Raw_Slice{data, len};
+	return write(fd, s);
+}
+
+read_ptr :: proc(fd: Handle, data: rawptr, len: int) -> (n: int, err: Error) {
+	s := transmute([]byte)mem.Raw_Slice{data, len};
+	return read(fd, s);
+}
+
+
+
+read_entire_file :: proc(name: string, allocator := context.allocator) -> ([]byte, Error) {
+	f, ferr := open(name);
+	if ferr != nil {
+		return nil, ferr;
+	}
+	defer close(f);
+
+	size: int;
+	if size64, err := file_size(f); err == nil {
+		if i64(int(size64)) != size64 {
+			size = int(size64);
+		}
+	}
+	size += 1; // for EOF
+
+	// TODO(bill): Is this correct logic?
+	total: int;
+	data := make([]byte, size, allocator);
+	for {
+		n, err := read(f, data[total:]);
+		total += n;
+		if err != nil {
+			if err == .EOF {
+				err = nil;
+			}
+			return data[:total], err;
+		}
+	}
+}
+
+write_entire_file :: proc(name: string, data: []byte, perm: File_Mode, truncate := true) -> Error {
+	flags := O_WRONLY|O_CREATE;
+	if truncate {
+		flags |= O_TRUNC;
+	}
+	f, err := open_file(name, flags, perm);
+	if err != nil {
+		return err;
+	}
+	_, err = write(f, data);
+	if cerr := close(f); cerr != nil && err == nil {
+		err = cerr;
+	}
+	return err;
+}
+

+ 136 - 0
core/os/os2/file_windows.odin

@@ -0,0 +1,136 @@
+//+private
+package os2
+
+import "core:io"
+import "core:time"
+
+_create :: proc(name: string) -> (Handle, Error) {
+	return 0, .None;
+}
+
+_open :: proc(name: string) -> (Handle, Error) {
+	return 0, .None;
+}
+
+_open_file :: proc(name: string, flag: int, perm: File_Mode) -> (Handle, Error) {
+	return 0, .None;
+}
+
+_close :: proc(fd: Handle) -> Error {
+	return .None;
+}
+
+_name :: proc(fd: Handle, allocator := context.allocator) -> string {
+	return "";
+}
+
+_seek :: proc(fd: Handle, offset: i64, whence: Seek_From) -> (ret: i64, err: Error) {
+	return;
+}
+
+_read :: proc(fd: Handle, p: []byte) -> (n: int, err: Error) {
+	return;
+}
+
+_read_at :: proc(fd: Handle, p: []byte, offset: i64) -> (n: int, err: Error) {
+	return;
+}
+
+_read_from :: proc(fd: Handle, r: io.Reader) -> (n: i64, err: Error) {
+	return;
+}
+
+_write :: proc(fd: Handle, p: []byte) -> (n: int, err: Error) {
+	return;
+}
+
+_write_at :: proc(fd: Handle, p: []byte, offset: i64) -> (n: int, err: Error) {
+	return;
+}
+
+_write_to :: proc(fd: Handle, w: io.Writer) -> (n: i64, err: Error) {
+	return;
+}
+
+_file_size :: proc(fd: Handle) -> (n: i64, err: Error) {
+	return;
+}
+
+
+_sync :: proc(fd: Handle) -> Error {
+	return .None;
+}
+
+_flush :: proc(fd: Handle) -> Error {
+	return .None;
+}
+
+_truncate :: proc(fd: Handle, size: i64) -> Maybe(Path_Error) {
+	return nil;
+}
+
+_remove :: proc(name: string) -> Maybe(Path_Error) {
+	return nil;
+}
+
+_rename :: proc(old_path, new_path: string) -> Maybe(Path_Error) {
+	return nil;
+}
+
+
+_link :: proc(old_name, new_name: string) -> Maybe(Link_Error) {
+	return nil;
+}
+
+_symlink :: proc(old_name, new_name: string) -> Maybe(Link_Error) {
+	return nil;
+}
+
+_read_link :: proc(name: string) -> (string, Maybe(Path_Error)) {
+	return "", nil;
+}
+
+
+_chdir :: proc(fd: Handle) -> Error {
+	return .None;
+}
+
+_chmod :: proc(fd: Handle, mode: File_Mode) -> Error {
+	return .None;
+}
+
+_chown :: proc(fd: Handle, uid, gid: int) -> Error {
+	return .None;
+}
+
+
+_lchown :: proc(name: string, uid, gid: int) -> Error {
+	return .None;
+}
+
+
+_chtimes :: proc(name: string, atime, mtime: time.Time) -> Maybe(Path_Error) {
+	return nil;
+}
+
+
+_exists :: proc(path: string) -> bool {
+	return false;
+}
+
+_is_file :: proc(path: string) -> bool {
+	return false;
+}
+
+_is_dir :: proc(path: string) -> bool {
+	return false;
+}
+
+
+_path_error_delete :: proc(perr: Maybe(Path_Error)) {
+
+}
+
+_link_error_delete :: proc(lerr: Maybe(Link_Error)) {
+
+}

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

@@ -0,0 +1,21 @@
+package os2
+
+import "core:runtime"
+
+heap_allocator :: proc() -> runtime.Allocator {
+	return runtime.Allocator{
+		procedure = heap_allocator_proc,
+		data = nil,
+	};
+}
+
+
+heap_allocator_proc :: proc(allocator_data: rawptr, mode: runtime.Allocator_Mode,
+                            size, alignment: int,
+                            old_memory: rawptr, old_size: int, flags: u64 = 0, loc := #caller_location) -> rawptr {
+	return _heap_allocator_proc(allocator_data, mode, size, alignment, old_memory, old_size, flags, loc);
+}
+
+
+@(private)
+error_allocator := heap_allocator;

+ 107 - 0
core/os/os2/heap_windows.odin

@@ -0,0 +1,107 @@
+//+private
+package os2
+
+import "core:runtime"
+import "core:mem"
+import win32 "core:sys/windows"
+
+heap_alloc :: proc(size: int) -> rawptr {
+	return win32.HeapAlloc(win32.GetProcessHeap(), win32.HEAP_ZERO_MEMORY, uint(size));
+}
+
+heap_resize :: proc(ptr: rawptr, new_size: int) -> rawptr {
+	if new_size == 0 {
+		heap_free(ptr);
+		return nil;
+	}
+	if ptr == nil {
+		return heap_alloc(new_size);
+	}
+
+	return win32.HeapReAlloc(win32.GetProcessHeap(), win32.HEAP_ZERO_MEMORY, ptr, uint(new_size));
+}
+heap_free :: proc(ptr: rawptr) {
+	if ptr == nil {
+		return;
+	}
+	win32.HeapFree(win32.GetProcessHeap(), 0, ptr);
+}
+
+_heap_allocator_proc :: proc(allocator_data: rawptr, mode: runtime.Allocator_Mode,
+                            size, alignment: int,
+                            old_memory: rawptr, old_size: int, flags: u64 = 0, loc := #caller_location) -> rawptr {
+	//
+	// NOTE(tetra, 2020-01-14): The heap doesn't respect alignment.
+	// Instead, we overallocate by `alignment + size_of(rawptr) - 1`, and insert
+	// padding. We also store the original pointer returned by heap_alloc right before
+	// the pointer we return to the user.
+	//
+
+	aligned_alloc :: proc(size, alignment: int, old_ptr: rawptr = nil) -> rawptr {
+		a := max(alignment, align_of(rawptr));
+		space := size + a - 1;
+
+		allocated_mem: rawptr;
+		if old_ptr != nil {
+			original_old_ptr := mem.ptr_offset((^rawptr)(old_ptr), -1)^;
+			allocated_mem = heap_resize(original_old_ptr, space+size_of(rawptr));
+		} else {
+			allocated_mem = heap_alloc(space+size_of(rawptr));
+		}
+		aligned_mem := rawptr(mem.ptr_offset((^u8)(allocated_mem), size_of(rawptr)));
+
+		ptr := uintptr(aligned_mem);
+		aligned_ptr := (ptr - 1 + uintptr(a)) & -uintptr(a);
+		diff := int(aligned_ptr - ptr);
+		if (size + diff) > space {
+			return nil;
+		}
+
+		aligned_mem = rawptr(aligned_ptr);
+		mem.ptr_offset((^rawptr)(aligned_mem), -1)^ = allocated_mem;
+
+		return aligned_mem;
+	}
+
+	aligned_free :: proc(p: rawptr) {
+		if p != nil {
+			heap_free(mem.ptr_offset((^rawptr)(p), -1)^);
+		}
+	}
+
+	aligned_resize :: proc(p: rawptr, old_size: int, new_size: int, new_alignment: int) -> rawptr {
+		if p == nil {
+			return nil;
+		}
+		return aligned_alloc(new_size, new_alignment, p);
+	}
+
+	switch mode {
+	case .Alloc:
+		return aligned_alloc(size, alignment);
+
+	case .Free:
+		aligned_free(old_memory);
+
+	case .Free_All:
+		// NOTE(tetra): Do nothing.
+
+	case .Resize:
+		if old_memory == nil {
+			return aligned_alloc(size, alignment);
+		}
+		return aligned_resize(old_memory, old_size, size, alignment);
+
+	case .Query_Features:
+		set := (^runtime.Allocator_Mode_Set)(old_memory);
+		if set != nil {
+			set^ = {.Alloc, .Free, .Resize, .Query_Features};
+		}
+		return set;
+
+	case .Query_Info:
+		return nil;
+	}
+
+	return nil;
+}

+ 29 - 0
core/os/os2/path.odin

@@ -0,0 +1,29 @@
+package os2
+
+Path_Separator      :: _Path_Separator;      // OS-Specific
+Path_List_Separator :: _Path_List_Separator; // OS-Specific
+
+is_path_separator :: proc(c: byte) -> bool {
+	return _is_path_separator(c);
+}
+
+mkdir :: proc(name: string, perm: File_Mode) -> Maybe(Path_Error) {
+	return _mkdir(name, perm);
+}
+
+mkdir_all :: proc(path: string, perm: File_Mode) -> Maybe(Path_Error) {
+	return _mkdir_all(path, perm);
+}
+
+remove_all :: proc(path: string) -> Maybe(Path_Error) {
+	return _remove_all(path);
+}
+
+
+
+getwd :: proc(allocator := context.allocator) -> (dir: string, err: Error) {
+	return _getwd(allocator);
+}
+setwd :: proc(dir: string) -> (err: Error) {
+	return _setwd(dir);
+}

+ 31 - 0
core/os/os2/path_windows.odin

@@ -0,0 +1,31 @@
+//+private
+package os2
+
+_Path_Separator      :: '\\';
+_Path_List_Separator :: ';';
+
+_is_path_separator :: proc(c: byte) -> bool {
+	return c == '\\' || c == '/';
+}
+
+_mkdir :: proc(name: string, perm: File_Mode) -> Maybe(Path_Error) {
+	return nil;
+}
+
+_mkdir_all :: proc(path: string, perm: File_Mode) -> Maybe(Path_Error) {
+	// TODO(bill): _mkdir_all for windows
+	return nil;
+}
+
+_remove_all :: proc(path: string) -> Maybe(Path_Error) {
+	// TODO(bill): _remove_all for windows
+	return nil;
+}
+
+_getwd :: proc(allocator := context.allocator) -> (dir: string, err: Error) {
+	return "", nil;
+}
+
+_setwd :: proc(dir: string) -> (err: Error) {
+	return nil;
+}

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

@@ -0,0 +1,5 @@
+package os2
+
+pipe :: proc() -> (r, w: Handle, err: Error) {
+	return _pipe();
+}

+ 13 - 0
core/os/os2/pipe_windows.odin

@@ -0,0 +1,13 @@
+//+private
+package os2
+
+import win32 "core:sys/windows"
+
+_pipe :: proc() -> (r, w: Handle, err: Error) {
+	p: [2]win32.HANDLE;
+	if !win32.CreatePipe(&p[0], &p[1], nil, 0) {
+		return 0, 0, error_from_platform_error(i32(win32.GetLastError()));
+	}
+	return Handle(p[0]), Handle(p[1]), nil;
+}
+

+ 101 - 0
core/os/os2/process.odin

@@ -0,0 +1,101 @@
+package os2
+
+import sync "core:sync/sync2"
+import "core:time"
+
+args: []string;
+
+exit :: proc "contextless" (code: int) -> ! {
+	//
+}
+
+get_uid :: proc() -> int {
+	return -1;
+}
+
+get_euid :: proc() -> int {
+	return -1;
+}
+
+get_gid :: proc() -> int {
+	return -1;
+}
+
+get_egid :: proc() -> int {
+	return -1;
+}
+
+get_pid :: proc() -> int {
+	return -1;
+}
+
+get_ppid :: proc() -> int {
+	return -1;
+}
+
+
+Process :: struct {
+	pid:          int,
+	handle:       uintptr,
+	is_done:      b32,
+	signal_mutex: sync.RW_Mutex,
+}
+
+
+Process_Attributes :: struct {
+	dir: string,
+	env: []string,
+	files: []Handle,
+	sys: ^Process_Attributes_OS_Specific,
+}
+
+Process_Attributes_OS_Specific :: struct{};
+
+Process_Error :: enum {
+	None,
+}
+
+Process_State :: struct {
+	pid:         int,
+	exit_code:   int,
+	exited:      bool,
+	success:     bool,
+	system_time: time.Duration,
+	user_time:   time.Duration,
+	sys:         rawptr,
+}
+
+Signal :: #type proc();
+
+Kill:      Signal = nil;
+Interrupt: Signal = nil;
+
+
+find_process :: proc(pid: int) -> (^Process, Process_Error) {
+	return nil, .None;
+}
+
+
+process_start :: proc(name: string, argv: []string, attr: ^Process_Attributes) -> (^Process, Process_Error) {
+	return nil, .None;
+}
+
+process_release :: proc(p: ^Process) -> Process_Error {
+	return .None;
+}
+
+process_kill :: proc(p: ^Process) -> Process_Error {
+	return .None;
+}
+
+process_signal :: proc(p: ^Process, sig: Signal) -> Process_Error {
+	return .None;
+}
+
+process_wait :: proc(p: ^Process) -> (Process_State, Process_Error) {
+	return {}, .None;
+}
+
+
+
+

+ 42 - 0
core/os/os2/stat.odin

@@ -0,0 +1,42 @@
+package os2
+
+import "core:time"
+
+File_Info :: struct {
+	fullpath: string,
+	name:     string,
+	size:     i64,
+	mode:     File_Mode,
+	is_dir:   bool,
+	creation_time:     time.Time,
+	modification_time: time.Time,
+	access_time:       time.Time,
+}
+
+file_info_slice_delete :: proc(infos: []File_Info, allocator := context.allocator) {
+	for i := len(infos)-1; i >= 0; i -= 1 {
+		file_info_delete(infos[i], allocator);
+	}
+	delete(infos, allocator);
+}
+
+file_info_delete :: proc(fi: File_Info, allocator := context.allocator) {
+	delete(fi.fullpath, allocator);
+}
+
+fstat :: proc(fd: Handle, allocator := context.allocator) -> (File_Info, Maybe(Path_Error)) {
+	return _fstat(fd, allocator);
+}
+
+stat :: proc(name: string, allocator := context.allocator) -> (File_Info, Maybe(Path_Error)) {
+	return _stat(name, allocator);
+}
+
+lstat :: proc(name: string, allocator := context.allocator) -> (File_Info, Maybe(Path_Error)) {
+	return _lstat(name, allocator);
+}
+
+
+same_file :: proc(fi1, fi2: File_Info) -> bool {
+	return _same_file(fi1, fi2);
+}

+ 373 - 0
core/os/os2/stat_windows.odin

@@ -0,0 +1,373 @@
+//+private
+package os2
+
+import "core:time"
+import win32 "core:sys/windows"
+
+_fstat :: proc(fd: Handle, allocator := context.allocator) -> (File_Info, Maybe(Path_Error)) {
+	if fd == 0 {
+		return {}, Path_Error{err = .Invalid_Argument};
+	}
+	context.allocator = allocator;
+
+	path, err := _cleanpath_from_handle(fd);
+	if err != nil {
+		return {}, err;
+	}
+
+	h := win32.HANDLE(fd);
+	switch win32.GetFileType(h) {
+	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);
+		return fi, nil;
+	}
+
+	return _file_info_from_get_file_information_by_handle(path, h);
+}
+_stat :: proc(name: string, allocator := context.allocator) -> (File_Info, Maybe(Path_Error)) {
+	return internal_stat(name, win32.FILE_FLAG_BACKUP_SEMANTICS);
+}
+_lstat :: proc(name: string, allocator := context.allocator) -> (File_Info, Maybe(Path_Error)) {
+	return internal_stat(name, win32.FILE_FLAG_BACKUP_SEMANTICS|win32.FILE_FLAG_OPEN_REPARSE_POINT);
+}
+_same_file :: proc(fi1, fi2: File_Info) -> bool {
+	return fi1.fullpath == fi2.fullpath;
+}
+
+
+
+_stat_errno :: proc(errno: win32.DWORD) -> Path_Error {
+	return Path_Error{err = error_from_platform_error(i32(errno))};
+}
+
+
+full_path_from_name :: proc(name: string, allocator := context.allocator) -> (path: string, err: Maybe(Path_Error)) {
+	name := name;
+	if name == "" {
+		name = ".";
+	}
+	p := win32.utf8_to_utf16(name, context.temp_allocator);
+	buf := make([dynamic]u16, 100, allocator);
+	for {
+		n := win32.GetFullPathNameW(raw_data(p), u32(len(buf)), raw_data(buf), nil);
+		if n == 0 {
+			delete(buf);
+			return "", _stat_errno(win32.GetLastError());
+		}
+		if n <= u32(len(buf)) {
+			return win32.utf16_to_utf8(buf[:n]), nil;
+		}
+		resize(&buf, len(buf)*2);
+	}
+
+	return;
+}
+
+
+internal_stat :: proc(name: string, create_file_attributes: u32, allocator := context.allocator) -> (fi: File_Info, e: Maybe(Path_Error)) {
+	if len(name) == 0 {
+		return {}, Path_Error{err = .Not_Exist};
+	}
+
+	context.allocator = allocator;
+
+
+	wname := win32.utf8_to_wstring(_fix_long_path(name), context.temp_allocator);
+	fa: win32.WIN32_FILE_ATTRIBUTE_DATA;
+	ok := win32.GetFileAttributesExW(wname, win32.GetFileExInfoStandard, &fa);
+	if ok && fa.dwFileAttributes & win32.FILE_ATTRIBUTE_REPARSE_POINT == 0 {
+		// Not a symlink
+		return _file_info_from_win32_file_attribute_data(&fa, name);
+	}
+
+	err := 0 if ok else win32.GetLastError();
+
+	if err == win32.ERROR_SHARING_VIOLATION {
+		fd: win32.WIN32_FIND_DATAW;
+		sh := win32.FindFirstFileW(wname, &fd);
+		if sh == win32.INVALID_HANDLE_VALUE {
+			e = Path_Error{err = error_from_platform_error(i32(win32.GetLastError()))};
+			return;
+		}
+		win32.FindClose(sh);
+
+		return _file_info_from_win32_find_data(&fd, name);
+	}
+
+	h := win32.CreateFileW(wname, 0, 0, nil, win32.OPEN_EXISTING, create_file_attributes, nil);
+	if h == win32.INVALID_HANDLE_VALUE {
+		e = Path_Error{err = error_from_platform_error(i32(win32.GetLastError()))};
+		return;
+	}
+	defer win32.CloseHandle(h);
+	return _file_info_from_get_file_information_by_handle(name, h);
+}
+
+
+_cleanpath_strip_prefix :: proc(buf: []u16) -> []u16 {
+	buf := buf;
+	N := 0;
+	for c, i in buf {
+		if c == 0 { break; }
+		N = i+1;
+	}
+	buf = buf[:N];
+
+	if len(buf) >= 4 {
+		if buf[0] == '\\' &&
+		   buf[1] == '\\' &&
+		   buf[2] == '?'  &&
+		   buf[3] == '\\' {
+			buf = buf[4:];
+		}
+	}
+	return buf;
+}
+
+
+_cleanpath_from_handle :: proc(fd: Handle) -> (string, Maybe(Path_Error)) {
+	if fd == 0 {
+		return "", Path_Error{err = .Invalid_Argument};
+	}
+	h := win32.HANDLE(fd);
+
+	MAX_PATH := win32.DWORD(260) + 1;
+	buf: []u16;
+	for {
+		buf = make([]u16, MAX_PATH, context.temp_allocator);
+		err := win32.GetFinalPathNameByHandleW(h, raw_data(buf), MAX_PATH, 0);
+		switch err {
+		case win32.ERROR_PATH_NOT_FOUND, win32.ERROR_INVALID_PARAMETER:
+			return "", _stat_errno(err);
+		case win32.ERROR_NOT_ENOUGH_MEMORY:
+			MAX_PATH = MAX_PATH*2 + 1;
+			continue;
+		}
+		break;
+	}
+	return _cleanpath_from_buf(buf), nil;
+}
+
+_cleanpath_from_handle_u16 :: proc(fd: Handle) -> ([]u16, Maybe(Path_Error)) {
+	if fd == 0 {
+		return nil, Path_Error{err = .Invalid_Argument};
+	}
+	h := win32.HANDLE(fd);
+
+	MAX_PATH := win32.DWORD(260) + 1;
+	buf: []u16;
+	for {
+		buf = make([]u16, MAX_PATH, context.temp_allocator);
+		err := win32.GetFinalPathNameByHandleW(h, raw_data(buf), MAX_PATH, 0);
+		switch err {
+		case win32.ERROR_PATH_NOT_FOUND, win32.ERROR_INVALID_PARAMETER:
+			return nil, _stat_errno(err);
+		case win32.ERROR_NOT_ENOUGH_MEMORY:
+			MAX_PATH = MAX_PATH*2 + 1;
+			continue;
+		}
+		break;
+	}
+	return _cleanpath_strip_prefix(buf), nil;
+}
+
+_cleanpath_from_buf :: proc(buf: []u16) -> string {
+	buf := buf;
+	buf = _cleanpath_strip_prefix(buf);
+	return win32.utf16_to_utf8(buf, context.allocator);
+}
+
+
+basename :: proc(name: string) -> (base: string) {
+	name := name;
+	if len(name) > 3 && name[:3] == `\\?` {
+		name = name[3:];
+	}
+
+	if len(name) == 2 && name[1] == ':' {
+		return ".";
+	} else if len(name) > 2 && name[1] == ':' {
+		name = name[2:];
+	}
+	i := len(name)-1;
+
+	for ; i > 0 && (name[i] == '/' || name[i] == '\\'); i -= 1 {
+		name = name[:i];
+	}
+	for i -= 1; i >= 0; i -= 1 {
+		if name[i] == '/' || name[i] == '\\' {
+			name = name[i+1:];
+			break;
+		}
+	}
+	return name;
+}
+
+
+file_type_mode :: proc(h: win32.HANDLE) -> File_Mode {
+	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;
+	}
+	return 0;
+}
+
+
+
+_file_mode_from_file_attributes :: proc(FileAttributes: win32.DWORD, h: win32.HANDLE, ReparseTag: win32.DWORD) -> (mode: File_Mode) {
+	if FileAttributes & win32.FILE_ATTRIBUTE_READONLY != 0 {
+		mode |= 0o444;
+	} else {
+		mode |= 0o666;
+	}
+
+	is_sym := false;
+	if FileAttributes & win32.FILE_ATTRIBUTE_REPARSE_POINT == 0 {
+		is_sym = false;
+	} else {
+		is_sym = ReparseTag == win32.IO_REPARSE_TAG_SYMLINK || ReparseTag == win32.IO_REPARSE_TAG_MOUNT_POINT;
+	}
+
+	if is_sym {
+		mode |= File_Mode_Sym_Link;
+	} else {
+		if FileAttributes & win32.FILE_ATTRIBUTE_DIRECTORY != 0 {
+			mode |= 0o111 | File_Mode_Dir;
+		}
+
+		if h != nil {
+			mode |= file_type_mode(h);
+		}
+	}
+
+	return;
+}
+
+
+_file_info_from_win32_file_attribute_data :: proc(d: ^win32.WIN32_FILE_ATTRIBUTE_DATA, name: string) -> (fi: File_Info, e: Maybe(Path_Error)) {
+	fi.size = i64(d.nFileSizeHigh)<<32 + i64(d.nFileSizeLow);
+
+	fi.mode |= _file_mode_from_file_attributes(d.dwFileAttributes, nil, 0);
+	fi.is_dir = fi.mode & File_Mode_Dir != 0;
+
+	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));
+
+	fi.fullpath, e = full_path_from_name(name);
+	fi.name = basename(fi.fullpath);
+
+	return;
+}
+
+
+_file_info_from_win32_find_data :: proc(d: ^win32.WIN32_FIND_DATAW, name: string) -> (fi: File_Info, e: Maybe(Path_Error)) {
+	fi.size = i64(d.nFileSizeHigh)<<32 + i64(d.nFileSizeLow);
+
+	fi.mode |= _file_mode_from_file_attributes(d.dwFileAttributes, nil, 0);
+	fi.is_dir = fi.mode & File_Mode_Dir != 0;
+
+	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));
+
+	fi.fullpath, e = full_path_from_name(name);
+	fi.name = basename(fi.fullpath);
+
+	return;
+}
+
+
+_file_info_from_get_file_information_by_handle :: proc(path: string, h: win32.HANDLE) -> (File_Info, Maybe(Path_Error)) {
+	d: win32.BY_HANDLE_FILE_INFORMATION;
+	if !win32.GetFileInformationByHandle(h, &d) {
+		return {}, _stat_errno(win32.GetLastError());
+
+	}
+
+	ti: win32.FILE_ATTRIBUTE_TAG_INFO;
+	if !win32.GetFileInformationByHandleEx(h, .FileAttributeTagInfo, &ti, size_of(ti)) {
+		err := win32.GetLastError();
+		if err != win32.ERROR_INVALID_PARAMETER {
+			return {}, _stat_errno(err);
+		}
+		// Indicate this is a symlink on FAT file systems
+		ti.ReparseTag = 0;
+	}
+
+	fi: File_Info;
+
+	fi.fullpath = 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_dir = fi.mode & File_Mode_Dir != 0;
+
+	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));
+
+	return fi, nil;
+}
+
+_is_abs :: proc(path: string) -> bool {
+	if len(path) > 0 && path[0] == '/' {
+		return true;
+	}
+	if len(path) > 2 {
+		switch path[0] {
+		case 'A'..'Z', 'a'..'z':
+			return path[1] == ':' && is_path_separator(path[2]);
+		}
+	}
+	return false;
+}
+
+_fix_long_path :: proc(path: string) -> string {
+	if len(path) < 248 {
+		return path;
+	}
+
+	if len(path) >= 2 && path[:2] == `\\` {
+		return path;
+	}
+	if !_is_abs(path) {
+		return path;
+	}
+
+	prefix :: `\\?`;
+
+	path_buf := make([]byte, len(prefix)+len(path)+len(`\`), context.temp_allocator);
+	copy(path_buf, prefix);
+	n := len(path);
+	r, w := 0, len(prefix);
+	for r < n {
+		switch {
+		case is_path_separator(path[r]):
+			r += 1;
+		case path[r] == '.' && (r+1 == n || is_path_separator(path[r+1])):
+			r += 1;
+		case r+1 < n && path[r] == '.' && path[r+1] == '.' && (r+2 == n || is_path_separator(path[r+2])):
+			return path;
+		case:
+			path_buf[w] = '\\';
+			w += 1;
+			for ; r < n && !is_path_separator(path[r]); r += 1 {
+				path_buf[w] = path[r];
+				w += 1;
+			}
+		}
+	}
+
+	if w == len(`\\?\c:`) {
+		path_buf[w] = '\\';
+		w += 1;
+	}
+	return string(path_buf[:w]);
+}

+ 14 - 0
core/os/os2/temp_file.odin

@@ -0,0 +1,14 @@
+package os2
+
+
+create_temp :: proc(dir, pattern: string) -> (Handle, Error) {
+	return _create_temp(dir, pattern);
+}
+
+mkdir_temp :: proc(dir, pattern: string, allocator := context.allocator) -> (string, Error) {
+	return _mkdir_temp(dir, pattern);
+}
+
+temp_dir :: proc(allocator := context.allocator) -> string {
+	return _temp_dir(allocator);
+}

+ 29 - 0
core/os/os2/temp_file_windows.odin

@@ -0,0 +1,29 @@
+//+private
+package os2
+
+import win32 "core:sys/windows"
+
+_create_temp :: proc(dir, pattern: string) -> (Handle, Error) {
+	return 0, .None;
+}
+
+_mkdir_temp :: proc(dir, pattern: string, allocator := context.allocator) -> (string, Error) {
+	return "", .None;
+}
+
+_temp_dir :: proc(allocator := context.allocator) -> string {
+	b := make([dynamic]u16, u32(win32.MAX_PATH), context.temp_allocator);
+	for {
+		n := win32.GetTempPathW(u32(len(b)), raw_data(b));
+		if n > u32(len(b)) {
+			resize(&b, int(n));
+			continue;
+		}
+		if n == 3 && b[1] == ':' && b[2] == '\\' {
+
+		} else if n > 0 && b[n-1] == '\\' {
+			n -= 1;
+		}
+		return win32.utf16_to_utf8(b[:n], allocator);
+	}
+}

+ 68 - 0
core/os/os2/user.odin

@@ -0,0 +1,68 @@
+package os2
+
+import "core:strings"
+
+user_cache_dir :: proc(allocator := context.allocator) -> (dir: string, is_defined: bool) {
+	switch ODIN_OS {
+	case "windows":
+		dir = get_env("LocalAppData");
+		if dir != "" {
+			dir = strings.clone(dir, allocator);
+		}
+	case "darwin":
+		dir = get_env("HOME");
+		if dir != "" {
+			dir = strings.concatenate({dir, "/Library/Caches"}, allocator);
+		}
+	case: // All other UNIX systems
+		dir = get_env("XDG_CACHE_HOME");
+		if dir == "" {
+			dir = get_env("HOME");
+			if dir == "" {
+				return;
+			}
+			dir = strings.concatenate({dir, "/.cache"}, allocator);
+		}
+	}
+	is_defined = dir != "";
+	return;
+}
+
+user_config_dir :: proc(allocator := context.allocator) -> (dir: string, is_defined: bool) {
+	switch ODIN_OS {
+	case "windows":
+		dir = get_env("AppData");
+		if dir != "" {
+			dir = strings.clone(dir, allocator);
+		}
+	case "darwin":
+		dir = get_env("HOME");
+		if dir != "" {
+			dir = strings.concatenate({dir, "/Library/Application Support"}, allocator);
+		}
+	case: // All other UNIX systems
+		dir = get_env("XDG_CACHE_HOME");
+		if dir == "" {
+			dir = get_env("HOME");
+			if dir == "" {
+				return;
+			}
+			dir = strings.concatenate({dir, "/.config"}, allocator);
+		}
+	}
+	is_defined = dir != "";
+	return;
+}
+
+user_home_dir :: proc() -> (dir: string, is_defined: bool) {
+	env := "HOME";
+	switch ODIN_OS {
+	case "windows":
+		env = "USERPROFILE";
+	}
+	if v := get_env(env); v != "" {
+		return v, true;
+	}
+	return "", false;
+}
+

+ 33 - 4
core/os/stat_unix.odin

@@ -2,7 +2,6 @@
 package os
 
 import "core:time"
-import "core:path"
 
 /*
 For reference
@@ -71,6 +70,36 @@ _fill_file_info_from_stat :: proc(fi: ^File_Info, s: OS_Stat) {
 	fi.access_time = _make_time_from_unix_file_time(s.last_access);
 }
 
+
+@private
+path_base :: proc(path: string) -> string {
+	is_separator :: proc(c: byte) -> bool {
+		return c == '/';
+	}
+
+	if path == "" {
+		return ".";
+	}
+
+	path := path;
+	for len(path) > 0 && is_separator(path[len(path)-1]) {
+		path = path[:len(path)-1];
+	}
+
+	i := len(path)-1;
+	for i >= 0 && !is_separator(path[i]) {
+		i -= 1;
+	}
+	if i >= 0 {
+		path = path[i+1:];
+	}
+	if path == "" {
+		return "/";
+	}
+	return path;
+}
+
+
 lstat :: proc(name: string, allocator := context.allocator) -> (fi: File_Info, err: Errno) {
 
 	context.allocator = allocator;
@@ -85,7 +114,7 @@ lstat :: proc(name: string, allocator := context.allocator) -> (fi: File_Info, e
 	if err != ERROR_NONE {
 		return;
 	}
-	fi.name = path.base(fi.fullpath);
+	fi.name = path_base(fi.fullpath);
 	return fi, ERROR_NONE;
 }
 
@@ -103,7 +132,7 @@ stat :: proc(name: string, allocator := context.allocator) -> (fi: File_Info, er
 	if err != ERROR_NONE {
 		return;
 	}
-	fi.name = path.base(fi.fullpath);
+	fi.name = path_base(fi.fullpath);
 	return fi, ERROR_NONE;
 }
 
@@ -121,6 +150,6 @@ fstat :: proc(fd: Handle, allocator := context.allocator) -> (fi: File_Info, err
 	if err != ERROR_NONE {
 		return;
 	}
-	fi.name = path.base(fi.fullpath);
+	fi.name = path_base(fi.fullpath);
 	return fi, ERROR_NONE;
 }

+ 38 - 6
core/path/filepath/path_unix.odin

@@ -1,8 +1,13 @@
 //+build linux, darwin, freebsd
 package filepath
 
+when ODIN_OS == "darwin" {
+	foreign import libc "System.framework"
+} else {
+	foreign import libc "system:c"
+}
+
 import "core:strings"
-import "core:os"
 
 SEPARATOR :: '/';
 SEPARATOR_STRING :: `/`;
@@ -17,11 +22,20 @@ is_abs :: proc(path: string) -> bool {
 }
 
 abs :: proc(path: string, allocator := context.allocator) -> (string, bool) {
-	full_path, err := os.absolute_path_from_relative(path);
-	if err != os.ERROR_NONE {
-		return "", false;
+	rel := path;
+	if rel == "" {
+		rel = ".";
+	}
+	rel_cstr := strings.clone_to_cstring(rel, context.temp_allocator);
+	path_ptr := realpath(rel_cstr, nil);
+	if path_ptr == nil {
+		return "", __error()^ == 0;
 	}
-	return full_path, true;
+	defer _unix_free(path_ptr);
+
+	path_cstr := cstring(path_ptr);
+	path = strings.clone(string(path_cstr), allocator);
+	return path, true;
 }
 
 join :: proc(elems: ..string, allocator := context.allocator) -> string {
@@ -32,4 +46,22 @@ join :: proc(elems: ..string, allocator := context.allocator) -> string {
 		}
 	}
 	return "";
-}
+}
+
+@(private)
+foreign libc {
+	realpath :: proc(path: cstring, resolved_path: rawptr) -> rawptr ---
+	@(link_name="free") _unix_free :: proc(ptr: rawptr) ---
+
+}
+when ODIN_OS == "darwin" {
+	@(private)
+	foreign libc {
+		@(link_name="__error")          __error :: proc() -> ^i32 ---
+	}
+} else {
+	@(private)
+	foreign libc {
+		@(link_name="__errno_location") __error :: proc() -> ^i32 ---
+	}
+}

+ 7 - 7
core/runtime/internal_linux.odin

@@ -103,7 +103,7 @@ floattidf :: proc(a: i128) -> f64 {
 	s := a >> (N-1);
 	a = (a ~ s) - s;
 	sd: = N - _clz_i128(a);  // number of significant digits
-	e := u32(sd - 1);        // exponent 
+	e := u32(sd - 1);        // exponent
 	if sd > DBL_MANT_DIG {
 		switch sd {
 		case DBL_MANT_DIG + 1:
@@ -115,8 +115,8 @@ floattidf :: proc(a: i128) -> f64 {
 				i128(u128(a) & (~u128(0) >> u128(N + DBL_MANT_DIG+2 - sd)) != 0);
 		};
 
-		a |= i128((a & 4) != 0);  
-		a += 1; 
+		a |= i128((a & 4) != 0);
+		a += 1;
 		a >>= 2;
 
 		if a & (1 << DBL_MANT_DIG) != 0 {
@@ -127,9 +127,9 @@ floattidf :: proc(a: i128) -> f64 {
 		a <<= u128(DBL_MANT_DIG - sd);
 	}
 	fb: [2]u32;
-	fb[1] = (u32(s) & 0x80000000) |        // sign
-	        ((e + 1023) << 20)      |      // exponent
-	        ((u32(a) >> 32) & 0x000FFFFF); // mantissa-high
-	fb[1] = u32(a);                        // mantissa-low
+	fb[1] = (u32(s) & 0x80000000) |           // sign
+	        ((e + 1023) << 20)    |           // exponent
+	        u32((u64(a) >> 32) & 0x000FFFFF); // mantissa-high
+	fb[1] = u32(a);                           // mantissa-low
 	return transmute(f64)fb;
 }

+ 92 - 92
core/runtime/internal_windows.odin

@@ -2,134 +2,134 @@ package runtime
 
 @(link_name="__umodti3")
 umodti3 :: proc "c" (a, b: u128) -> u128 {
-    r: u128 = ---;
-    _ = udivmod128(a, b, &r);
-    return r;
+	r: u128 = ---;
+	_ = udivmod128(a, b, &r);
+	return r;
 }
 
 
 @(link_name="__udivmodti4")
 udivmodti4 :: proc "c" (a, b: u128, rem: ^u128) -> u128 {
-    return udivmod128(a, b, rem);
+	return udivmod128(a, b, rem);
 }
 
 @(link_name="__udivti3")
 udivti3 :: proc "c" (a, b: u128) -> u128 {
-    return udivmodti4(a, b, nil);
+	return udivmodti4(a, b, nil);
 }
 
 
 @(link_name="__modti3")
 modti3 :: proc "c" (a, b: i128) -> i128 {
-    s_a := a >> (128 - 1);
-    s_b := b >> (128 - 1);
-    an := (a ~ s_a) - s_a;
-    bn := (b ~ s_b) - s_b;
-
-    r: u128 = ---;
-    _ = udivmod128(transmute(u128)an, transmute(u128)bn, &r);
-    return (transmute(i128)r ~ s_a) - s_a;
+	s_a := a >> (128 - 1);
+	s_b := b >> (128 - 1);
+	an := (a ~ s_a) - s_a;
+	bn := (b ~ s_b) - s_b;
+
+	r: u128 = ---;
+	_ = udivmod128(transmute(u128)an, transmute(u128)bn, &r);
+	return (transmute(i128)r ~ s_a) - s_a;
 }
 
 
 @(link_name="__divmodti4")
 divmodti4 :: proc "c" (a, b: i128, rem: ^i128) -> i128 {
-    u := udivmod128(transmute(u128)a, transmute(u128)b, cast(^u128)rem);
-    return transmute(i128)u;
+	u := udivmod128(transmute(u128)a, transmute(u128)b, cast(^u128)rem);
+	return transmute(i128)u;
 }
 
 @(link_name="__divti3")
 divti3 :: proc "c" (a, b: i128) -> i128 {
-    u := udivmodti4(transmute(u128)a, transmute(u128)b, nil);
-    return transmute(i128)u;
+	u := udivmodti4(transmute(u128)a, transmute(u128)b, nil);
+	return transmute(i128)u;
 }
 
 
 @(link_name="__fixdfti")
 fixdfti :: proc(a: u64) -> i128 {
-    significandBits :: 52;
-    typeWidth       :: (size_of(u64)*8);
-    exponentBits    :: (typeWidth - significandBits - 1);
-    maxExponent     :: ((1 << exponentBits) - 1);
-    exponentBias    :: (maxExponent >> 1);
-
-    implicitBit     :: (u64(1) << significandBits);
-    significandMask :: (implicitBit - 1);
-    signBit         :: (u64(1) << (significandBits + exponentBits));
-    absMask         :: (signBit - 1);
-    exponentMask    :: (absMask ~ significandMask);
-
-    // Break a into sign, exponent, significand
-    aRep := a;
-    aAbs := aRep & absMask;
-    sign := i128(-1 if aRep & signBit != 0 else 1);
-    exponent := (aAbs >> significandBits) - exponentBias;
-    significand := (aAbs & significandMask) | implicitBit;
-
-    // If exponent is negative, the result is zero.
-    if exponent < 0 {
-        return 0;
-    }
-
-    // If the value is too large for the integer type, saturate.
-    if exponent >= size_of(i128) * 8 {
-        return max(i128) if sign == 1 else min(i128);
-    }
-
-    // If 0 <= exponent < significandBits, right shift to get the result.
-    // Otherwise, shift left.
-    if exponent < significandBits {
-        return sign * i128(significand >> (significandBits - exponent));
-    } else {
-        return sign * (i128(significand) << (exponent - significandBits));
-    }
+	significandBits :: 52;
+	typeWidth       :: (size_of(u64)*8);
+	exponentBits    :: (typeWidth - significandBits - 1);
+	maxExponent     :: ((1 << exponentBits) - 1);
+	exponentBias    :: (maxExponent >> 1);
+
+	implicitBit     :: (u64(1) << significandBits);
+	significandMask :: (implicitBit - 1);
+	signBit         :: (u64(1) << (significandBits + exponentBits));
+	absMask         :: (signBit - 1);
+	exponentMask    :: (absMask ~ significandMask);
+
+	// Break a into sign, exponent, significand
+	aRep := a;
+	aAbs := aRep & absMask;
+	sign := i128(-1 if aRep & signBit != 0 else 1);
+	exponent := (aAbs >> significandBits) - exponentBias;
+	significand := (aAbs & significandMask) | implicitBit;
+
+	// If exponent is negative, the result is zero.
+	if exponent < 0 {
+		return 0;
+	}
+
+	// If the value is too large for the integer type, saturate.
+	if exponent >= size_of(i128) * 8 {
+		return max(i128) if sign == 1 else min(i128);
+	}
+
+	// If 0 <= exponent < significandBits, right shift to get the result.
+	// Otherwise, shift left.
+	if exponent < significandBits {
+		return sign * i128(significand >> (significandBits - exponent));
+	} else {
+		return sign * (i128(significand) << (exponent - significandBits));
+	}
 
 }
 
 @(default_calling_convention = "none")
 foreign {
-    @(link_name="llvm.ctlz.i128") _clz_i128 :: proc(x: i128, is_zero_undef := false) -> i128 ---
+	@(link_name="llvm.ctlz.i128") _clz_i128 :: proc(x: i128, is_zero_undef := false) -> i128 ---
 }
 
 
 @(link_name="__floattidf")
 floattidf :: proc(a: i128) -> f64 {
-    DBL_MANT_DIG :: 53;
-    if a == 0 {
-        return 0.0;
-    }
-    a := a;
-    N :: size_of(i128) * 8;
-    s := a >> (N-1);
-    a = (a ~ s) - s;
-    sd: = N - _clz_i128(a);  // number of significant digits
-    e := u32(sd - 1);        // exponent
-    if sd > DBL_MANT_DIG {
-        switch sd {
-        case DBL_MANT_DIG + 1:
-            a <<= 1;
-        case DBL_MANT_DIG + 2:
-            // okay
-        case:
-            a = i128(u128(a) >> u128(sd - (DBL_MANT_DIG+2))) |
-                i128(u128(a) & (~u128(0) >> u128(N + DBL_MANT_DIG+2 - sd)) != 0);
-        };
-
-        a |= i128((a & 4) != 0);
-        a += 1;
-        a >>= 2;
-
-        if a & (1 << DBL_MANT_DIG) != 0 {
-            a >>= 1;
-            e += 1;
-        }
-    } else {
-        a <<= u128(DBL_MANT_DIG - sd);
-    }
-    fb: [2]u32;
-    fb[1] = (u32(s) & 0x80000000) |        // sign
-            ((e + 1023) << 20)      |      // exponent
-            ((u32(a) >> 32) & 0x000FFFFF); // mantissa-high
-    fb[1] = u32(a);                        // mantissa-low
-    return transmute(f64)fb;
+	DBL_MANT_DIG :: 53;
+	if a == 0 {
+		return 0.0;
+	}
+	a := a;
+	N :: size_of(i128) * 8;
+	s := a >> (N-1);
+	a = (a ~ s) - s;
+	sd: = N - _clz_i128(a);  // number of significant digits
+	e := u32(sd - 1);        // exponent
+	if sd > DBL_MANT_DIG {
+		switch sd {
+		case DBL_MANT_DIG + 1:
+			a <<= 1;
+		case DBL_MANT_DIG + 2:
+			// okay
+		case:
+			a = i128(u128(a) >> u128(sd - (DBL_MANT_DIG+2))) |
+				i128(u128(a) & (~u128(0) >> u128(N + DBL_MANT_DIG+2 - sd)) != 0);
+		};
+
+		a |= i128((a & 4) != 0);
+		a += 1;
+		a >>= 2;
+
+		if a & (1 << DBL_MANT_DIG) != 0 {
+			a >>= 1;
+			e += 1;
+		}
+	} else {
+		a <<= u128(DBL_MANT_DIG - sd);
+	}
+	fb: [2]u32;
+	fb[1] = (u32(s) & 0x80000000) |           // sign
+	        ((e + 1023) << 20)    |           // exponent
+	        u32((u64(a) >> 32) & 0x000FFFFF); // mantissa-high
+	fb[1] = u32(a);                           // mantissa-low
+	return transmute(f64)fb;
 }

+ 4 - 4
core/slice/slice.odin

@@ -115,7 +115,7 @@ simple_equal :: proc(a, b: $T/[]$E) -> bool where intrinsics.type_is_simple_comp
 }
 
 
-has_prefix :: proc(array: $T/[]$E, needle: T) -> bool where intrinsics.type_is_comparable(E) {
+has_prefix :: proc(array: $T/[]$E, needle: E) -> bool where intrinsics.type_is_comparable(E) {
 	n := len(needle);
 	if len(array) >= n {
 		return equal(array[:n], needle);
@@ -124,7 +124,7 @@ has_prefix :: proc(array: $T/[]$E, needle: T) -> bool where intrinsics.type_is_c
 }
 
 
-has_suffix :: proc(array: $T/[]$E, needle: T) -> bool where intrinsics.type_is_comparable(E) {
+has_suffix :: proc(array: $T/[]$E, needle: E) -> bool where intrinsics.type_is_comparable(E) {
 	array := array;
 	m, n := len(array), len(needle);
 	if m >= n {
@@ -133,7 +133,7 @@ has_suffix :: proc(array: $T/[]$E, needle: T) -> bool where intrinsics.type_is_c
 	return false;
 }
 
-fill :: proc(array: $T/[]$E, value: T) {
+fill :: proc(array: $T/[]$E, value: E) {
 	for _, i in array {
 		array[i] = value;
 	}
@@ -281,7 +281,7 @@ reduce :: proc(s: $S/[]$U, initializer: $V, f: proc(V, U) -> V) -> V {
 }
 
 filter :: proc(s: $S/[]$U, f: proc(U) -> bool, allocator := context.allocator) -> S {
-	r := make([dynamic]S, 0, 0, allocator);
+	r := make([dynamic]U, 0, 0, allocator);
 	for v in s {
 		if f(v) {
 			append(&r, v);

+ 79 - 0
core/sync/sync2/atomic.odin

@@ -0,0 +1,79 @@
+package sync2
+
+import "intrinsics"
+
+// TODO(bill): Is this even a good design? The intrinsics seem to be more than good enough and just as clean
+
+cpu_relax :: intrinsics.cpu_relax;
+
+atomic_fence        :: intrinsics.atomic_fence;
+atomic_fence_acq    :: intrinsics.atomic_fence_acq;
+atomic_fence_rel    :: intrinsics.atomic_fence_rel;
+atomic_fence_acqrel :: intrinsics.atomic_fence_acqrel;
+
+atomic_store           :: intrinsics.atomic_store;
+atomic_store_rel       :: intrinsics.atomic_store_rel;
+atomic_store_relaxed   :: intrinsics.atomic_store_relaxed;
+atomic_store_unordered :: intrinsics.atomic_store_unordered;
+
+atomic_load           :: intrinsics.atomic_load;
+atomic_load_acq       :: intrinsics.atomic_load_acq;
+atomic_load_relaxed   :: intrinsics.atomic_load_relaxed;
+atomic_load_unordered :: intrinsics.atomic_load_unordered;
+
+atomic_add          :: intrinsics.atomic_add;
+atomic_add_acq      :: intrinsics.atomic_add_acq;
+atomic_add_rel      :: intrinsics.atomic_add_rel;
+atomic_add_acqrel   :: intrinsics.atomic_add_acqrel;
+atomic_add_relaxed  :: intrinsics.atomic_add_relaxed;
+atomic_sub          :: intrinsics.atomic_sub;
+atomic_sub_acq      :: intrinsics.atomic_sub_acq;
+atomic_sub_rel      :: intrinsics.atomic_sub_rel;
+atomic_sub_acqrel   :: intrinsics.atomic_sub_acqrel;
+atomic_sub_relaxed  :: intrinsics.atomic_sub_relaxed;
+atomic_and          :: intrinsics.atomic_and;
+atomic_and_acq      :: intrinsics.atomic_and_acq;
+atomic_and_rel      :: intrinsics.atomic_and_rel;
+atomic_and_acqrel   :: intrinsics.atomic_and_acqrel;
+atomic_and_relaxed  :: intrinsics.atomic_and_relaxed;
+atomic_nand         :: intrinsics.atomic_nand;
+atomic_nand_acq     :: intrinsics.atomic_nand_acq;
+atomic_nand_rel     :: intrinsics.atomic_nand_rel;
+atomic_nand_acqrel  :: intrinsics.atomic_nand_acqrel;
+atomic_nand_relaxed :: intrinsics.atomic_nand_relaxed;
+atomic_or           :: intrinsics.atomic_or;
+atomic_or_acq       :: intrinsics.atomic_or_acq;
+atomic_or_rel       :: intrinsics.atomic_or_rel;
+atomic_or_acqrel    :: intrinsics.atomic_or_acqrel;
+atomic_or_relaxed   :: intrinsics.atomic_or_relaxed;
+atomic_xor          :: intrinsics.atomic_xor;
+atomic_xor_acq      :: intrinsics.atomic_xor_acq;
+atomic_xor_rel      :: intrinsics.atomic_xor_rel;
+atomic_xor_acqrel   :: intrinsics.atomic_xor_acqrel;
+atomic_xor_relaxed  :: intrinsics.atomic_xor_relaxed;
+
+atomic_xchg         :: intrinsics.atomic_xchg;
+atomic_xchg_acq     :: intrinsics.atomic_xchg_acq;
+atomic_xchg_rel     :: intrinsics.atomic_xchg_rel;
+atomic_xchg_acqrel  :: intrinsics.atomic_xchg_acqrel;
+atomic_xchg_relaxed :: intrinsics.atomic_xchg_relaxed;
+
+atomic_cxchg                    :: intrinsics.atomic_cxchg;
+atomic_cxchg_acq                :: intrinsics.atomic_cxchg_acq;
+atomic_cxchg_rel                :: intrinsics.atomic_cxchg_rel;
+atomic_cxchg_acqrel             :: intrinsics.atomic_cxchg_acqrel;
+atomic_cxchg_relaxed            :: intrinsics.atomic_cxchg_relaxed;
+atomic_cxchg_failrelaxed        :: intrinsics.atomic_cxchg_failrelaxed;
+atomic_cxchg_failacq            :: intrinsics.atomic_cxchg_failacq;
+atomic_cxchg_acq_failrelaxed    :: intrinsics.atomic_cxchg_acq_failrelaxed;
+atomic_cxchg_acqrel_failrelaxed :: intrinsics.atomic_cxchg_acqrel_failrelaxed;
+
+atomic_cxchgweak                    :: intrinsics.atomic_cxchgweak;
+atomic_cxchgweak_acq                :: intrinsics.atomic_cxchgweak_acq;
+atomic_cxchgweak_rel                :: intrinsics.atomic_cxchgweak_rel;
+atomic_cxchgweak_acqrel             :: intrinsics.atomic_cxchgweak_acqrel;
+atomic_cxchgweak_relaxed            :: intrinsics.atomic_cxchgweak_relaxed;
+atomic_cxchgweak_failrelaxed        :: intrinsics.atomic_cxchgweak_failrelaxed;
+atomic_cxchgweak_failacq            :: intrinsics.atomic_cxchgweak_failacq;
+atomic_cxchgweak_acq_failrelaxed    :: intrinsics.atomic_cxchgweak_acq_failrelaxed;
+atomic_cxchgweak_acqrel_failrelaxed :: intrinsics.atomic_cxchgweak_acqrel_failrelaxed;

+ 886 - 0
core/sync/sync2/channel.odin

@@ -0,0 +1,886 @@
+package sync2
+
+// TODO(bill): The Channel implementation needs a complete rewrite for this new package sync design
+// Especially how the `select` things work
+
+import "core:mem"
+import "core:time"
+import "core:math/rand"
+
+_, _ :: time, rand;
+
+Channel_Direction :: enum i8 {
+	Both =  0,
+	Send = +1,
+	Recv = -1,
+}
+
+Channel :: struct(T: typeid, Direction := Channel_Direction.Both) {
+	using _internal: ^Raw_Channel,
+}
+
+channel_init :: proc(ch: ^$C/Channel($T, $D), cap := 0, allocator := context.allocator) {
+	context.allocator = allocator;
+	ch._internal = raw_channel_create(size_of(T), align_of(T), cap);
+	return;
+}
+
+channel_make :: proc($T: typeid, cap := 0, allocator := context.allocator) -> (ch: Channel(T, .Both)) {
+	context.allocator = allocator;
+	ch._internal = raw_channel_create(size_of(T), align_of(T), cap);
+	return;
+}
+
+channel_make_send :: proc($T: typeid, cap := 0, allocator := context.allocator) -> (ch: Channel(T, .Send)) {
+	context.allocator = allocator;
+	ch._internal = raw_channel_create(size_of(T), align_of(T), cap);
+	return;
+}
+channel_make_recv :: proc($T: typeid, cap := 0, allocator := context.allocator) -> (ch: Channel(T, .Recv)) {
+	context.allocator = allocator;
+	ch._internal = raw_channel_create(size_of(T), align_of(T), cap);
+	return;
+}
+
+channel_destroy :: proc(ch: $C/Channel($T, $D)) {
+	raw_channel_destroy(ch._internal);
+}
+
+channel_as_send :: proc(ch: $C/Channel($T, .Both)) -> (res: Channel(T, .Send)) {
+	res._internal = ch._internal;
+	return;
+}
+
+channel_as_recv :: proc(ch: $C/Channel($T, .Both)) -> (res: Channel(T, .Recv)) {
+	res._internal = ch._internal;
+	return;
+}
+
+
+channel_len :: proc(ch: $C/Channel($T, $D)) -> int {
+	return ch._internal.len if ch._internal != nil else 0;
+}
+channel_cap :: proc(ch: $C/Channel($T, $D)) -> int {
+	return ch._internal.cap if ch._internal != nil else 0;
+}
+
+
+channel_send :: proc(ch: $C/Channel($T, $D), msg: T, loc := #caller_location) where D >= .Both {
+	msg := msg;
+	_ = raw_channel_send_impl(ch._internal, &msg, /*block*/true, loc);
+}
+channel_try_send :: proc(ch: $C/Channel($T, $D), msg: T, loc := #caller_location) -> bool where D >= .Both {
+	msg := msg;
+	return raw_channel_send_impl(ch._internal, &msg, /*block*/false, loc);
+}
+
+channel_recv :: proc(ch: $C/Channel($T, $D), loc := #caller_location) -> (msg: T) where D <= .Both {
+	c := ch._internal;
+	if c == nil {
+		panic(message="cannot recv message; channel is nil", loc=loc);
+	}
+	mutex_lock(&c.mutex);
+	raw_channel_recv_impl(c, &msg, loc);
+	mutex_unlock(&c.mutex);
+	return;
+}
+channel_try_recv :: proc(ch: $C/Channel($T, $D), loc := #caller_location) -> (msg: T, ok: bool) where D <= .Both {
+	c := ch._internal;
+	if c != nil && mutex_try_lock(&c.mutex) {
+		if c.len > 0 {
+			raw_channel_recv_impl(c, &msg, loc);
+			ok = true;
+		}
+		mutex_unlock(&c.mutex);
+	}
+	return;
+}
+channel_try_recv_ptr :: proc(ch: $C/Channel($T, $D), msg: ^T, loc := #caller_location) -> (ok: bool) where D <= .Both {
+	res: T;
+	res, ok = channel_try_recv(ch, loc);
+	if ok && msg != nil {
+		msg^ = res;
+	}
+	return;
+}
+
+
+channel_is_nil :: proc(ch: $C/Channel($T, $D)) -> bool {
+	return ch._internal == nil;
+}
+channel_is_open :: proc(ch: $C/Channel($T, $D)) -> bool {
+	c := ch._internal;
+	return c != nil && !c.closed;
+}
+
+
+channel_eq :: proc(a, b: $C/Channel($T, $D)) -> bool {
+	return a._internal == b._internal;
+}
+channel_ne :: proc(a, b: $C/Channel($T, $D)) -> bool {
+	return a._internal != b._internal;
+}
+
+
+channel_can_send :: proc(ch: $C/Channel($T, $D)) -> (ok: bool) where D >= .Both {
+	return raw_channel_can_send(ch._internal);
+}
+channel_can_recv :: proc(ch: $C/Channel($T, $D)) -> (ok: bool) where D <= .Both {
+	return raw_channel_can_recv(ch._internal);
+}
+
+
+channel_peek :: proc(ch: $C/Channel($T, $D)) -> int {
+	c := ch._internal;
+	if c == nil {
+		return -1;
+	}
+	if atomic_load(&c.closed) {
+		return -1;
+	}
+	return atomic_load(&c.len);
+}
+
+
+channel_close :: proc(ch: $C/Channel($T, $D), loc := #caller_location) {
+	raw_channel_close(ch._internal, loc);
+}
+
+
+channel_iterator :: proc(ch: $C/Channel($T, $D)) -> (msg: T, ok: bool) where D <= .Both {
+	c := ch._internal;
+	if c == nil {
+		return;
+	}
+
+	if !c.closed || c.len > 0 {
+		msg, ok = channel_recv(ch), true;
+	}
+	return;
+}
+channel_drain :: proc(ch: $C/Channel($T, $D)) where D >= .Both {
+	raw_channel_drain(ch._internal);
+}
+
+
+channel_move :: proc(dst: $C1/Channel($T, $D1) src: $C2/Channel(T, $D2)) where D1 <= .Both, D2 >= .Both {
+	for msg in channel_iterator(src) {
+		channel_send(dst, msg);
+	}
+}
+
+
+Raw_Channel_Wait_Queue :: struct {
+	next: ^Raw_Channel_Wait_Queue,
+	state: ^uintptr,
+}
+
+
+Raw_Channel :: struct {
+	closed:      bool,
+	ready:       bool, // ready to recv
+	data_offset: u16,  // data is stored at the end of this data structure
+	elem_size:   u32,
+	len, cap:    int,
+	read, write: int,
+	mutex:       Mutex,
+	cond:        Cond,
+	allocator:   mem.Allocator,
+
+	sendq: ^Raw_Channel_Wait_Queue,
+	recvq: ^Raw_Channel_Wait_Queue,
+}
+
+raw_channel_wait_queue_insert :: proc(head: ^^Raw_Channel_Wait_Queue, val: ^Raw_Channel_Wait_Queue) {
+	val.next = head^;
+	head^ = val;
+}
+raw_channel_wait_queue_remove :: proc(head: ^^Raw_Channel_Wait_Queue, val: ^Raw_Channel_Wait_Queue) {
+	p := head;
+	for p^ != nil && p^ != val {
+		p = &p^.next;
+	}
+	if p != nil {
+		p^ = p^.next;
+	}
+}
+
+
+raw_channel_create :: proc(elem_size, elem_align: int, cap := 0) -> ^Raw_Channel {
+	assert(int(u32(elem_size)) == elem_size);
+
+	s := size_of(Raw_Channel);
+	s = mem.align_forward_int(s, elem_align);
+	data_offset := uintptr(s);
+	s += elem_size * max(cap, 1);
+
+	a := max(elem_align, align_of(Raw_Channel));
+
+	c := (^Raw_Channel)(mem.alloc(s, a));
+	if c == nil {
+		return nil;
+	}
+
+	c.data_offset = u16(data_offset);
+	c.elem_size = u32(elem_size);
+	c.len, c.cap = 0, max(cap, 0);
+	c.read, c.write = 0, 0;
+	c.allocator = context.allocator;
+	c.closed = false;
+
+	return c;
+}
+
+
+raw_channel_destroy :: proc(c: ^Raw_Channel) {
+	if c == nil {
+		return;
+	}
+	context.allocator = c.allocator;
+	atomic_store(&c.closed, true);
+	free(c);
+}
+
+raw_channel_close :: proc(c: ^Raw_Channel, loc := #caller_location) {
+	if c == nil {
+		panic(message="cannot close nil channel", loc=loc);
+	}
+	mutex_lock(&c.mutex);
+	defer mutex_unlock(&c.mutex);
+	atomic_store(&c.closed, true);
+
+	// Release readers and writers
+	raw_channel_wait_queue_broadcast(c.recvq);
+	raw_channel_wait_queue_broadcast(c.sendq);
+	cond_broadcast(&c.cond);
+}
+
+
+
+raw_channel_send_impl :: proc(c: ^Raw_Channel, msg: rawptr, block: bool, loc := #caller_location) -> bool {
+	send :: proc(c: ^Raw_Channel, src: rawptr) {
+		data := uintptr(c) + uintptr(c.data_offset);
+		dst := data + uintptr(c.write * int(c.elem_size));
+		mem.copy(rawptr(dst), src, int(c.elem_size));
+		c.len += 1;
+		c.write = (c.write + 1) % max(c.cap, 1);
+	}
+
+	switch {
+	case c == nil:
+		panic(message="cannot send message; channel is nil", loc=loc);
+	case c.closed:
+		panic(message="cannot send message; channel is closed", loc=loc);
+	}
+
+	mutex_lock(&c.mutex);
+	defer mutex_unlock(&c.mutex);
+
+	if c.cap > 0 {
+		if !block && c.len >= c.cap {
+			return false;
+		}
+
+		for c.len >= c.cap {
+			cond_wait(&c.cond, &c.mutex);
+		}
+	} else if c.len > 0 { // TODO(bill): determine correct behaviour
+		if !block {
+			return false;
+		}
+		cond_wait(&c.cond, &c.mutex);
+	} else if c.len == 0 && !block {
+		return false;
+	}
+
+	send(c, msg);
+	cond_signal(&c.cond);
+	raw_channel_wait_queue_signal(c.recvq);
+
+	return true;
+}
+
+raw_channel_recv_impl :: proc(c: ^Raw_Channel, res: rawptr, loc := #caller_location) {
+	recv :: proc(c: ^Raw_Channel, dst: rawptr, loc := #caller_location) {
+		if c.len < 1 {
+			panic(message="cannot recv message; channel is empty", loc=loc);
+		}
+		c.len -= 1;
+
+		data := uintptr(c) + uintptr(c.data_offset);
+		src := data + uintptr(c.read * int(c.elem_size));
+		mem.copy(dst, rawptr(src), int(c.elem_size));
+		c.read = (c.read + 1) % max(c.cap, 1);
+	}
+
+	if c == nil {
+		panic(message="cannot recv message; channel is nil", loc=loc);
+	}
+	atomic_store(&c.ready, true);
+	for c.len < 1 {
+		raw_channel_wait_queue_signal(c.sendq);
+		cond_wait(&c.cond, &c.mutex);
+	}
+	atomic_store(&c.ready, false);
+	recv(c, res, loc);
+	if c.cap > 0 {
+		if c.len == c.cap - 1 {
+			// NOTE(bill): Only signal on the last one
+			cond_signal(&c.cond);
+		}
+	} else {
+		cond_signal(&c.cond);
+	}
+}
+
+
+raw_channel_can_send :: proc(c: ^Raw_Channel) -> (ok: bool) {
+	if c == nil {
+		return false;
+	}
+	mutex_lock(&c.mutex);
+	switch {
+	case c.closed:
+		ok = false;
+	case c.cap > 0:
+		ok = c.ready && c.len < c.cap;
+	case:
+		ok = c.ready && c.len == 0;
+	}
+	mutex_unlock(&c.mutex);
+	return;
+}
+raw_channel_can_recv :: proc(c: ^Raw_Channel) -> (ok: bool) {
+	if c == nil {
+		return false;
+	}
+	mutex_lock(&c.mutex);
+	ok = c.len > 0;
+	mutex_unlock(&c.mutex);
+	return;
+}
+
+
+raw_channel_drain :: proc(c: ^Raw_Channel) {
+	if c == nil {
+		return;
+	}
+	mutex_lock(&c.mutex);
+	c.len   = 0;
+	c.read  = 0;
+	c.write = 0;
+	mutex_unlock(&c.mutex);
+}
+
+
+
+MAX_SELECT_CHANNELS :: 64;
+SELECT_MAX_TIMEOUT :: max(time.Duration);
+
+Select_Command :: enum {
+	Recv,
+	Send,
+}
+
+Select_Channel :: struct {
+	channel: ^Raw_Channel,
+	command: Select_Command,
+}
+
+
+
+select :: proc(channels: ..Select_Channel) -> (index: int) {
+	return select_timeout(SELECT_MAX_TIMEOUT, ..channels);
+}
+select_timeout :: proc(timeout: time.Duration, channels: ..Select_Channel) -> (index: int) {
+	switch len(channels) {
+	case 0:
+		panic("sync: select with no channels");
+	}
+
+	assert(len(channels) <= MAX_SELECT_CHANNELS);
+
+	backing: [MAX_SELECT_CHANNELS]int;
+	queues:  [MAX_SELECT_CHANNELS]Raw_Channel_Wait_Queue;
+	candidates := backing[:];
+	cap := len(channels);
+	candidates = candidates[:cap];
+
+	count := u32(0);
+	for c, i in channels {
+		if c.channel == nil {
+			continue;
+		}
+		switch c.command {
+		case .Recv:
+			if raw_channel_can_recv(c.channel) {
+				candidates[count] = i;
+				count += 1;
+			}
+		case .Send:
+			if raw_channel_can_send(c.channel) {
+				candidates[count] = i;
+				count += 1;
+			}
+		}
+	}
+
+	if count == 0 {
+		wait_state: uintptr = 0;
+		for _, i in channels {
+			q := &queues[i];
+			q.state = &wait_state;
+		}
+
+		for c, i in channels {
+			if c.channel == nil {
+				continue;
+			}
+			q := &queues[i];
+			switch c.command {
+			case .Recv: raw_channel_wait_queue_insert(&c.channel.recvq, q);
+			case .Send: raw_channel_wait_queue_insert(&c.channel.sendq, q);
+			}
+		}
+		raw_channel_wait_queue_wait_on(&wait_state, timeout);
+		for c, i in channels {
+			if c.channel == nil {
+				continue;
+			}
+			q := &queues[i];
+			switch c.command {
+			case .Recv: raw_channel_wait_queue_remove(&c.channel.recvq, q);
+			case .Send: raw_channel_wait_queue_remove(&c.channel.sendq, q);
+			}
+		}
+
+		for c, i in channels {
+			switch c.command {
+			case .Recv:
+				if raw_channel_can_recv(c.channel) {
+					candidates[count] = i;
+					count += 1;
+				}
+			case .Send:
+				if raw_channel_can_send(c.channel) {
+					candidates[count] = i;
+					count += 1;
+				}
+			}
+		}
+		if count == 0 && timeout == SELECT_MAX_TIMEOUT {
+			index = -1;
+			return;
+		}
+
+		assert(count != 0);
+	}
+
+	t := time.now();
+	r := rand.create(transmute(u64)t);
+	i := rand.uint32(&r);
+
+	index = candidates[i % count];
+	return;
+}
+
+select_recv :: proc(channels: ..^Raw_Channel) -> (index: int) {
+	switch len(channels) {
+	case 0:
+		panic("sync: select with no channels");
+	}
+
+	assert(len(channels) <= MAX_SELECT_CHANNELS);
+
+	backing: [MAX_SELECT_CHANNELS]int;
+	queues:  [MAX_SELECT_CHANNELS]Raw_Channel_Wait_Queue;
+	candidates := backing[:];
+	cap := len(channels);
+	candidates = candidates[:cap];
+
+	count := u32(0);
+	for c, i in channels {
+		if raw_channel_can_recv(c) {
+			candidates[count] = i;
+			count += 1;
+		}
+	}
+
+	if count == 0 {
+		state: uintptr;
+		for c, i in channels {
+			q := &queues[i];
+			q.state = &state;
+			raw_channel_wait_queue_insert(&c.recvq, q);
+		}
+		raw_channel_wait_queue_wait_on(&state, SELECT_MAX_TIMEOUT);
+		for c, i in channels {
+			q := &queues[i];
+			raw_channel_wait_queue_remove(&c.recvq, q);
+		}
+
+		for c, i in channels {
+			if raw_channel_can_recv(c) {
+				candidates[count] = i;
+				count += 1;
+			}
+		}
+		assert(count != 0);
+	}
+
+	t := time.now();
+	r := rand.create(transmute(u64)t);
+	i := rand.uint32(&r);
+
+	index = candidates[i % count];
+	return;
+}
+
+select_recv_msg :: proc(channels: ..$C/Channel($T, $D)) -> (msg: T, index: int) {
+	switch len(channels) {
+	case 0:
+		panic("sync: select with no channels");
+	}
+
+	assert(len(channels) <= MAX_SELECT_CHANNELS);
+
+	queues:  [MAX_SELECT_CHANNELS]Raw_Channel_Wait_Queue;
+	candidates: [MAX_SELECT_CHANNELS]int;
+
+	count := u32(0);
+	for c, i in channels {
+		if raw_channel_can_recv(c) {
+			candidates[count] = i;
+			count += 1;
+		}
+	}
+
+	if count == 0 {
+		state: uintptr;
+		for c, i in channels {
+			q := &queues[i];
+			q.state = &state;
+			raw_channel_wait_queue_insert(&c.recvq, q);
+		}
+		raw_channel_wait_queue_wait_on(&state, SELECT_MAX_TIMEOUT);
+		for c, i in channels {
+			q := &queues[i];
+			raw_channel_wait_queue_remove(&c.recvq, q);
+		}
+
+		for c, i in channels {
+			if raw_channel_can_recv(c) {
+				candidates[count] = i;
+				count += 1;
+			}
+		}
+		assert(count != 0);
+	}
+
+	t := time.now();
+	r := rand.create(transmute(u64)t);
+	i := rand.uint32(&r);
+
+	index = candidates[i % count];
+	msg = channel_recv(channels[index]);
+
+	return;
+}
+
+select_send_msg :: proc(msg: $T, channels: ..$C/Channel(T, $D)) -> (index: int) {
+	switch len(channels) {
+	case 0:
+		panic("sync: select with no channels");
+	}
+
+	assert(len(channels) <= MAX_SELECT_CHANNELS);
+
+	backing: [MAX_SELECT_CHANNELS]int;
+	queues:  [MAX_SELECT_CHANNELS]Raw_Channel_Wait_Queue;
+	candidates := backing[:];
+	cap := len(channels);
+	candidates = candidates[:cap];
+
+	count := u32(0);
+	for c, i in channels {
+		if raw_channel_can_recv(c) {
+			candidates[count] = i;
+			count += 1;
+		}
+	}
+
+	if count == 0 {
+		state: uintptr;
+		for c, i in channels {
+			q := &queues[i];
+			q.state = &state;
+			raw_channel_wait_queue_insert(&c.recvq, q);
+		}
+		raw_channel_wait_queue_wait_on(&state, SELECT_MAX_TIMEOUT);
+		for c, i in channels {
+			q := &queues[i];
+			raw_channel_wait_queue_remove(&c.recvq, q);
+		}
+
+		for c, i in channels {
+			if raw_channel_can_recv(c) {
+				candidates[count] = i;
+				count += 1;
+			}
+		}
+		assert(count != 0);
+	}
+
+	t := time.now();
+	r := rand.create(transmute(u64)t);
+	i := rand.uint32(&r);
+
+	index = candidates[i % count];
+
+	if msg != nil {
+		channel_send(channels[index], msg);
+	}
+
+	return;
+}
+
+select_send :: proc(channels: ..^Raw_Channel) -> (index: int) {
+	switch len(channels) {
+	case 0:
+		panic("sync: select with no channels");
+	}
+
+	assert(len(channels) <= MAX_SELECT_CHANNELS);
+	candidates: [MAX_SELECT_CHANNELS]int;
+	queues: [MAX_SELECT_CHANNELS]Raw_Channel_Wait_Queue;
+
+	count := u32(0);
+	for c, i in channels {
+		if raw_channel_can_send(c) {
+			candidates[count] = i;
+			count += 1;
+		}
+	}
+
+	if count == 0 {
+		state: uintptr;
+		for c, i in channels {
+			q := &queues[i];
+			q.state = &state;
+			raw_channel_wait_queue_insert(&c.sendq, q);
+		}
+		raw_channel_wait_queue_wait_on(&state, SELECT_MAX_TIMEOUT);
+		for c, i in channels {
+			q := &queues[i];
+			raw_channel_wait_queue_remove(&c.sendq, q);
+		}
+
+		for c, i in channels {
+			if raw_channel_can_send(c) {
+				candidates[count] = i;
+				count += 1;
+			}
+		}
+		assert(count != 0);
+	}
+
+	t := time.now();
+	r := rand.create(transmute(u64)t);
+	i := rand.uint32(&r);
+
+	index = candidates[i % count];
+	return;
+}
+
+select_try :: proc(channels: ..Select_Channel) -> (index: int) {
+	switch len(channels) {
+	case 0:
+		panic("sync: select with no channels");
+	}
+
+	assert(len(channels) <= MAX_SELECT_CHANNELS);
+
+	backing: [MAX_SELECT_CHANNELS]int;
+	candidates := backing[:];
+	cap := len(channels);
+	candidates = candidates[:cap];
+
+	count := u32(0);
+	for c, i in channels {
+		switch c.command {
+		case .Recv:
+			if raw_channel_can_recv(c.channel) {
+				candidates[count] = i;
+				count += 1;
+			}
+		case .Send:
+			if raw_channel_can_send(c.channel) {
+				candidates[count] = i;
+				count += 1;
+			}
+		}
+	}
+
+	if count == 0 {
+		index = -1;
+		return;
+	}
+
+	t := time.now();
+	r := rand.create(transmute(u64)t);
+	i := rand.uint32(&r);
+
+	index = candidates[i % count];
+	return;
+}
+
+
+select_try_recv :: proc(channels: ..^Raw_Channel) -> (index: int) {
+	switch len(channels) {
+	case 0:
+		index = -1;
+		return;
+	case 1:
+		index = -1;
+		if raw_channel_can_recv(channels[0]) {
+			index = 0;
+		}
+		return;
+	}
+
+	assert(len(channels) <= MAX_SELECT_CHANNELS);
+	candidates: [MAX_SELECT_CHANNELS]int;
+
+	count := u32(0);
+	for c, i in channels {
+		if raw_channel_can_recv(c) {
+			candidates[count] = i;
+			count += 1;
+		}
+	}
+
+	if count == 0 {
+		index = -1;
+		return;
+	}
+
+	t := time.now();
+	r := rand.create(transmute(u64)t);
+	i := rand.uint32(&r);
+
+	index = candidates[i % count];
+	return;
+}
+
+
+select_try_send :: proc(channels: ..^Raw_Channel) -> (index: int) #no_bounds_check {
+	switch len(channels) {
+	case 0:
+		return -1;
+	case 1:
+		if raw_channel_can_send(channels[0]) {
+			return 0;
+		}
+		return -1;
+	}
+
+	assert(len(channels) <= MAX_SELECT_CHANNELS);
+	candidates: [MAX_SELECT_CHANNELS]int;
+
+	count := u32(0);
+	for c, i in channels {
+		if raw_channel_can_send(c) {
+			candidates[count] = i;
+			count += 1;
+		}
+	}
+
+	if count == 0 {
+		index = -1;
+		return;
+	}
+
+	t := time.now();
+	r := rand.create(transmute(u64)t);
+	i := rand.uint32(&r);
+
+	index = candidates[i % count];
+	return;
+}
+
+select_try_recv_msg :: proc(channels: ..$C/Channel($T, $D)) -> (msg: T, index: int) {
+	switch len(channels) {
+	case 0:
+		index = -1;
+		return;
+	case 1:
+		ok: bool;
+		if msg, ok = channel_try_recv(channels[0]); ok {
+			index = 0;
+		}
+		return;
+	}
+
+	assert(len(channels) <= MAX_SELECT_CHANNELS);
+	candidates: [MAX_SELECT_CHANNELS]int;
+
+	count := u32(0);
+	for c, i in channels {
+		if channel_can_recv(c) {
+			candidates[count] = i;
+			count += 1;
+		}
+	}
+
+	if count == 0 {
+		index = -1;
+		return;
+	}
+
+	t := time.now();
+	r := rand.create(transmute(u64)t);
+	i := rand.uint32(&r);
+
+	index = candidates[i % count];
+	msg = channel_recv(channels[index]);
+	return;
+}
+
+select_try_send_msg :: proc(msg: $T, channels: ..$C/Channel(T, $D)) -> (index: int) {
+	index = -1;
+	switch len(channels) {
+	case 0:
+		return;
+	case 1:
+		if channel_try_send(channels[0], msg) {
+			index = 0;
+		}
+		return;
+	}
+
+
+	assert(len(channels) <= MAX_SELECT_CHANNELS);
+	candidates: [MAX_SELECT_CHANNELS]int;
+
+	count := u32(0);
+	for c, i in channels {
+		if raw_channel_can_send(c) {
+			candidates[count] = i;
+			count += 1;
+		}
+	}
+
+	if count == 0 {
+		index = -1;
+		return;
+	}
+
+	t := time.now();
+	r := rand.create(transmute(u64)t);
+	i := rand.uint32(&r);
+
+	index = candidates[i % count];
+	channel_send(channels[index], msg);
+	return;
+}
+

+ 17 - 0
core/sync/sync2/channel_unix.odin

@@ -0,0 +1,17 @@
+//+build linux, darwin, freebsd
+//+private
+package sync2
+
+import "core:time"
+
+raw_channel_wait_queue_wait_on :: proc(state: ^uintptr, timeout: time.Duration) {
+	// stub
+}
+
+raw_channel_wait_queue_signal :: proc(q: ^Raw_Channel_Wait_Queue) {
+	// stub
+}
+
+raw_channel_wait_queue_broadcast :: proc(q: ^Raw_Channel_Wait_Queue) {
+	// stub
+}

+ 34 - 0
core/sync/sync2/channel_windows.odin

@@ -0,0 +1,34 @@
+//+build windows
+//+private
+package sync2
+
+import win32 "core:sys/windows"
+import "core:time"
+
+raw_channel_wait_queue_wait_on :: proc(state: ^uintptr, timeout: time.Duration) {
+	ms: win32.DWORD = win32.INFINITE;
+	if max(time.Duration) != SELECT_MAX_TIMEOUT {
+		ms = win32.DWORD((max(time.duration_nanoseconds(timeout), 0) + 999999)/1000000);
+	}
+
+	v := atomic_load(state);
+	for v == 0 {
+		win32.WaitOnAddress(state, &v, size_of(state^), ms);
+		v = atomic_load(state);
+	}
+	atomic_store(state, 0);
+}
+
+raw_channel_wait_queue_signal :: proc(q: ^Raw_Channel_Wait_Queue) {
+	for x := q; x != nil; x = x.next {
+		atomic_add(x.state, 1);
+		win32.WakeByAddressSingle(x.state);
+	}
+}
+
+raw_channel_wait_queue_broadcast :: proc(q: ^Raw_Channel_Wait_Queue) {
+	for x := q; x != nil; x = x.next {
+		atomic_add(x.state, 1);
+		win32.WakeByAddressAll(x.state);
+	}
+}

+ 238 - 0
core/sync/sync2/extended.odin

@@ -0,0 +1,238 @@
+package sync2
+
+import "core:runtime"
+
+// A Wait_Group waits for a collection of threads to finish
+//
+// A Wait_Group must not be copied after first use
+Wait_Group :: struct {
+	counter: int,
+	mutex:   Mutex,
+	cond:    Cond,
+}
+
+wait_group_add :: proc(wg: ^Wait_Group, delta: int) {
+	if delta == 0 {
+		return;
+	}
+
+	mutex_lock(&wg.mutex);
+	defer mutex_unlock(&wg.mutex);
+
+	atomic_add(&wg.counter, delta);
+	if wg.counter < 0 {
+		panic("sync.Wait_Group negative counter");
+	}
+	if wg.counter == 0 {
+		cond_broadcast(&wg.cond);
+		if wg.counter != 0 {
+			panic("sync.Wait_Group misuse: sync.wait_group_add called concurrently with sync.wait_group_wait");
+		}
+	}
+}
+
+wait_group_done :: proc(wg: ^Wait_Group) {
+	wait_group_add(wg, -1);
+}
+
+wait_group_wait :: proc(wg: ^Wait_Group) {
+	mutex_lock(&wg.mutex);
+	defer mutex_unlock(&wg.mutex);
+
+	if wg.counter != 0 {
+		cond_wait(&wg.cond, &wg.mutex);
+		if wg.counter != 0 {
+			panic("sync.Wait_Group misuse: sync.wait_group_add called concurrently with sync.wait_group_wait");
+		}
+	}
+}
+
+
+
+// A barrier enabling multiple threads to synchronize the beginning of some computation
+/*
+ * Example:
+ *
+ * 	package example
+ *
+ * 	import "core:fmt"
+ * 	import "core:sync"
+ * 	import "core:thread"
+ *
+ * 	barrier := &sync.Barrier{};
+ *
+ * 	main :: proc() {
+ * 		fmt.println("Start");
+ *
+ * 		THREAD_COUNT :: 4;
+ * 		threads: [THREAD_COUNT]^thread.Thread;
+ *
+ * 		sync.barrier_init(barrier, THREAD_COUNT);
+ * 		defer sync.barrier_destroy(barrier);
+ *
+ *
+ * 		for _, i in threads {
+ * 			threads[i] = thread.create_and_start(proc(t: ^thread.Thread) {
+ * 				// Same messages will be printed together but without any interleaving
+ * 				fmt.println("Getting ready!");
+ * 				sync.barrier_wait(barrier);
+ * 				fmt.println("Off their marks they go!");
+ * 			});
+ * 		}
+ *
+ * 		for t in threads {
+ * 			thread.destroy(t); // join and free thread
+ * 		}
+ * 		fmt.println("Finished");
+ * 	}
+ *
+ */
+Barrier :: struct {
+	mutex: Mutex,
+	cond:  Cond,
+	index:         int,
+	generation_id: int,
+	thread_count:  int,
+}
+
+barrier_init :: proc(b: ^Barrier, thread_count: int) {
+	b.index = 0;
+	b.generation_id = 0;
+	b.thread_count = thread_count;
+}
+
+// Block the current thread until all threads have rendezvoused
+// Barrier can be reused after all threads rendezvoused once, and can be used continuously
+barrier_wait :: proc(b: ^Barrier) -> (is_leader: bool) {
+	mutex_lock(&b.mutex);
+	defer mutex_unlock(&b.mutex);
+	local_gen := b.generation_id;
+	b.index += 1;
+	if b.index < b.thread_count {
+		for local_gen == b.generation_id && b.index < b.thread_count {
+			cond_wait(&b.cond, &b.mutex);
+		}
+		return false;
+	}
+
+	b.index = 0;
+	b.generation_id += 1;
+	cond_broadcast(&b.cond);
+	return true;
+}
+
+
+
+Ticket_Mutex :: struct {
+	ticket:  uint,
+	serving: uint,
+}
+
+ticket_mutex_lock :: #force_inline proc(m: ^Ticket_Mutex) {
+	ticket := atomic_add_relaxed(&m.ticket, 1);
+	for ticket != atomic_load_acq(&m.serving) {
+		cpu_relax();
+	}
+}
+
+ticket_mutex_unlock :: #force_inline proc(m: ^Ticket_Mutex) {
+	atomic_add_relaxed(&m.serving, 1);
+}
+
+
+
+Benaphore :: struct {
+	counter: int,
+	sema:    Sema,
+}
+
+benaphore_lock :: proc(b: ^Benaphore) {
+	if atomic_add_acq(&b.counter, 1) > 1 {
+		sema_wait(&b.sema);
+	}
+}
+
+benaphore_try_lock :: proc(b: ^Benaphore) -> bool {
+	v, _ := atomic_cxchg_acq(&b.counter, 1, 0);
+	return v == 0;
+}
+
+benaphore_unlock :: proc(b: ^Benaphore) {
+	if atomic_sub_rel(&b.counter, 1) > 0 {
+		sema_post(&b.sema);
+	}
+}
+
+Recursive_Benaphore :: struct {
+	counter:   int,
+	owner:     int,
+	recursion: int,
+	sema:      Sema,
+}
+
+recursive_benaphore_lock :: proc(b: ^Recursive_Benaphore) {
+	tid := runtime.current_thread_id();
+	if atomic_add_acq(&b.counter, 1) > 1 {
+		if tid != b.owner {
+			sema_wait(&b.sema);
+		}
+	}
+	// inside the lock
+	b.owner = tid;
+	b.recursion += 1;
+}
+
+recursive_benaphore_try_lock :: proc(b: ^Recursive_Benaphore) -> bool {
+	tid := runtime.current_thread_id();
+	if b.owner == tid {
+		atomic_add_acq(&b.counter, 1);
+	}
+
+	if v, _ := atomic_cxchg_acq(&b.counter, 1, 0); v != 0 {
+		return false;
+	}
+	// inside the lock
+	b.owner = tid;
+	b.recursion += 1;
+	return true;
+}
+
+recursive_benaphore_unlock :: proc(b: ^Recursive_Benaphore) {
+	tid := runtime.current_thread_id();
+	assert(tid == b.owner);
+	b.recursion -= 1;
+	recursion := b.recursion;
+	if recursion == 0 {
+		b.owner = 0;
+	}
+	if atomic_sub_rel(&b.counter, 1) > 0 {
+		if recursion == 0 {
+			sema_post(&b.sema);
+		}
+	}
+	// outside the lock
+}
+
+
+
+
+
+Once :: struct {
+	m:    Mutex,
+	done: bool,
+}
+
+once_do :: proc(o: ^Once, fn: proc()) {
+	if atomic_load_acq(&o.done) == false {
+		_once_do_slow(o, fn);
+	}
+}
+
+_once_do_slow :: proc(o: ^Once, fn: proc()) {
+	mutex_lock(&o.m);
+	defer mutex_unlock(&o.m);
+	if !o.done {
+		fn();
+		atomic_store_rel(&o.done, true);
+	}
+}

+ 185 - 0
core/sync/sync2/primitives.odin

@@ -0,0 +1,185 @@
+package sync2
+
+import "core:time"
+import "core:runtime"
+
+// A Mutex is a mutual exclusion lock
+// The zero value for a Mutex is an unlocked mutex
+//
+// A Mutex must not be copied after first use
+Mutex :: struct {
+	impl: _Mutex,
+}
+
+// mutex_lock locks m
+mutex_lock :: proc(m: ^Mutex) {
+	_mutex_lock(m);
+}
+
+// mutex_lock unlocks m
+mutex_unlock :: proc(m: ^Mutex) {
+	_mutex_unlock(m);
+}
+
+// mutex_lock tries to lock m, will return true on success, and false on failure
+mutex_try_lock :: proc(m: ^Mutex) -> bool {
+	return _mutex_try_lock(m);
+}
+
+// A RW_Mutex is a reader/writer mutual exclusion lock
+// The lock can be held by any arbitrary number of readers or a single writer
+// The zero value for a RW_Mutex is an unlocked mutex
+//
+// A RW_Mutex must not be copied after first use
+RW_Mutex :: struct {
+	impl: _RW_Mutex,
+}
+
+// rw_mutex_lock locks rw for writing (with a single writer)
+// If the mutex is already locked for reading or writing, the mutex blocks until the mutex is available.
+rw_mutex_lock :: proc(rw: ^RW_Mutex) {
+	_rw_mutex_lock(rw);
+}
+
+// rw_mutex_unlock unlocks rw for writing (with a single writer)
+rw_mutex_unlock :: proc(rw: ^RW_Mutex) {
+	_rw_mutex_unlock(rw);
+}
+
+// rw_mutex_try_lock tries to lock rw for writing (with a single writer)
+rw_mutex_try_lock :: proc(rw: ^RW_Mutex) -> bool {
+	return _rw_mutex_try_lock(rw);
+}
+
+// rw_mutex_shared_lock locks rw for reading (with arbitrary number of readers)
+rw_mutex_shared_lock :: proc(rw: ^RW_Mutex) {
+	_rw_mutex_shared_lock(rw);
+}
+
+// rw_mutex_shared_unlock unlocks rw for reading (with arbitrary number of readers)
+rw_mutex_shared_unlock :: proc(rw: ^RW_Mutex) {
+	_rw_mutex_shared_unlock(rw);
+}
+
+// rw_mutex_try_shared_lock tries to lock rw for reading (with arbitrary number of readers)
+rw_mutex_try_shared_lock :: proc(rw: ^RW_Mutex) -> bool {
+	return _rw_mutex_try_shared_lock(rw);
+}
+
+
+// A Recusrive_Mutex is a recursive mutual exclusion lock
+// The zero value for a Recursive_Mutex is an unlocked mutex
+//
+// A Recursive_Mutex must not be copied after first use
+Recursive_Mutex :: struct {
+	// TODO(bill): Is this implementation too lazy?
+	// Can this be made to work on all OSes without construction and destruction, i.e. Zero is Initialized
+	// CRITICAL_SECTION would be a perfect candidate for this on Windows but that cannot be "dumb"
+
+	owner:     int,
+	recursion: int,
+	mutex: Mutex,
+}
+
+recursive_mutex_lock :: proc(m: ^Recursive_Mutex) {
+	tid := runtime.current_thread_id();
+	if tid != m.owner {
+		mutex_lock(&m.mutex);
+	}
+	// inside the lock
+	m.owner = tid;
+	m.recursion += 1;
+}
+
+recursive_mutex_unlock :: proc(m: ^Recursive_Mutex) {
+	tid := runtime.current_thread_id();
+	assert(tid == m.owner);
+	m.recursion -= 1;
+	recursion := m.recursion;
+	if recursion == 0 {
+		m.owner = 0;
+	}
+	if recursion == 0 {
+		mutex_unlock(&m.mutex);
+	}
+	// outside the lock
+
+}
+
+recursive_mutex_try_lock :: proc(m: ^Recursive_Mutex) -> bool {
+	tid := runtime.current_thread_id();
+	if m.owner == tid {
+		return mutex_try_lock(&m.mutex);
+	}
+	if !mutex_try_lock(&m.mutex) {
+		return false;
+	}
+	// inside the lock
+	m.owner = tid;
+	m.recursion += 1;
+	return true;
+}
+
+
+
+// Cond implements a condition variable, a rendezvous point for threads
+// waiting for signalling the occurence of an event
+//
+// A Cond must not be copied after first use
+Cond :: struct {
+	impl: _Cond,
+}
+
+cond_wait :: proc(c: ^Cond, m: ^Mutex) {
+	_cond_wait(c, m);
+}
+
+cond_wait_with_timeout :: proc(c: ^Cond, m: ^Mutex, timeout: time.Duration) -> bool {
+	return _cond_wait_with_timeout(c, m, timeout);
+}
+
+cond_signal :: proc(c: ^Cond) {
+	_cond_signal(c);
+}
+
+cond_broadcast :: proc(c: ^Cond) {
+	_cond_broadcast(c);
+}
+
+
+
+// When waited upon, blocks until the internal count is greater than zero, then subtracts one.
+// Posting to the semaphore increases the count by one, or the provided amount.
+//
+// A Sema must not be copied after first use
+Sema :: struct {
+	// TODO(bill): Is this implementation too lazy?
+	// Can this be made to work on all OSes without construction and destruction, i.e. Zero is Initialized
+
+	mutex: Mutex,
+	cond:  Cond,
+	count: int,
+}
+
+
+sema_wait :: proc(s: ^Sema) {
+	mutex_lock(&s.mutex);
+	defer mutex_unlock(&s.mutex);
+
+	for s.count == 0 {
+		cond_wait(&s.cond, &s.mutex);
+	}
+
+	s.count -= 1;
+	if s.count > 0 {
+		cond_signal(&s.cond);
+	}
+}
+
+sema_post :: proc(s: ^Sema, count := 1) {
+	mutex_lock(&s.mutex);
+	defer mutex_unlock(&s.mutex);
+
+	s.count += count;
+	cond_signal(&s.cond);
+}

+ 244 - 0
core/sync/sync2/primitives_atomic.odin

@@ -0,0 +1,244 @@
+//+build linux, darwin, freebsd
+//+private
+package sync2
+
+when !#config(ODIN_SYNC_USE_PTHREADS, true) {
+
+import "core:time"
+
+_Mutex_State :: enum i32 {
+	Unlocked = 0,
+	Locked   = 1,
+	Waiting  = 2,
+}
+_Mutex :: struct {
+	state: _Mutex_State,
+}
+
+_mutex_lock :: proc(m: ^Mutex) {
+	if atomic_xchg_rel(&m.impl.state, .Unlocked) != .Unlocked {
+		_mutex_unlock_slow(m);
+	}
+}
+
+_mutex_unlock :: proc(m: ^Mutex) {
+	switch atomic_xchg_rel(&m.impl.state, .Unlocked) {
+	case .Unlocked:
+		unreachable();
+	case .Locked:
+		// Okay
+	case .Waiting:
+		_mutex_unlock_slow(m);
+	}
+}
+
+_mutex_try_lock :: proc(m: ^Mutex) -> bool {
+	_, ok := atomic_cxchg_acq(&m.impl.state, .Unlocked, .Locked);
+	return ok;
+}
+
+
+@(cold)
+_mutex_lock_slow :: proc(m: ^Mutex, curr_state: _Mutex_State) {
+	new_state := curr_state; // Make a copy of it
+
+	spin_lock: for spin in 0..<i32(100) {
+		state, ok := atomic_cxchgweak_acq(&m.impl.state, .Unlocked, new_state);
+		if ok {
+			return;
+		}
+
+		if state == .Waiting {
+			break spin_lock;
+		}
+
+		for i := min(spin+1, 32); i > 0; i -= 1 {
+			cpu_relax();
+		}
+	}
+
+	for {
+		if atomic_xchg_acq(&m.impl.state, .Waiting) == .Unlocked {
+			return;
+		}
+
+		// TODO(bill): Use a Futex here for Linux to improve performance and error handling
+		cpu_relax();
+	}
+}
+
+
+@(cold)
+_mutex_unlock_slow :: proc(m: ^Mutex) {
+	// TODO(bill): Use a Futex here for Linux to improve performance and error handling
+}
+
+
+RW_Mutex_State :: distinct uint;
+RW_Mutex_State_Half_Width :: size_of(RW_Mutex_State)*8/2;
+RW_Mutex_State_Is_Writing :: RW_Mutex_State(1);
+RW_Mutex_State_Writer     :: RW_Mutex_State(1)<<1;
+RW_Mutex_State_Reader     :: RW_Mutex_State(1)<<RW_Mutex_State_Half_Width;
+
+RW_Mutex_State_Writer_Mask :: RW_Mutex_State(1<<(RW_Mutex_State_Half_Width-1) - 1) << 1;
+RW_Mutex_State_Reader_Mask :: RW_Mutex_State(1<<(RW_Mutex_State_Half_Width-1) - 1) << RW_Mutex_State_Half_Width;
+
+
+_RW_Mutex :: struct {
+	state: RW_Mutex_State,
+	mutex: Mutex,
+	sema:  Sema,
+}
+
+_rw_mutex_lock :: proc(rw: ^RW_Mutex) {
+	_ = atomic_add(&rw.impl.state, RW_Mutex_State_Writer);
+	mutex_lock(&rw.impl.mutex);
+
+	state := atomic_or(&rw.impl.state, RW_Mutex_State_Writer);
+	if state & RW_Mutex_State_Reader_Mask != 0 {
+		sema_wait(&rw.impl.sema);
+	}
+}
+
+_rw_mutex_unlock :: proc(rw: ^RW_Mutex) {
+	_ = atomic_and(&rw.impl.state, ~RW_Mutex_State_Is_Writing);
+	mutex_unlock(&rw.impl.mutex);
+}
+
+_rw_mutex_try_lock :: proc(rw: ^RW_Mutex) -> bool {
+	if mutex_try_lock(&rw.impl.mutex) {
+		state := atomic_load(&rw.impl.state);
+		if state & RW_Mutex_State_Reader_Mask == 0 {
+			_ = atomic_or(&rw.impl.state, RW_Mutex_State_Is_Writing);
+			return true;
+		}
+
+		mutex_unlock(&rw.impl.mutex);
+	}
+	return false;
+}
+
+_rw_mutex_shared_lock :: proc(rw: ^RW_Mutex) {
+	state := atomic_load(&rw.impl.state);
+	for state & (RW_Mutex_State_Is_Writing|RW_Mutex_State_Writer_Mask) == 0 {
+		ok: bool;
+		state, ok = atomic_cxchgweak(&rw.impl.state, state, state + RW_Mutex_State_Reader);
+		if ok {
+			return;
+		}
+	}
+
+	mutex_lock(&rw.impl.mutex);
+	_ = atomic_add(&rw.impl.state, RW_Mutex_State_Reader);
+	mutex_unlock(&rw.impl.mutex);
+}
+
+_rw_mutex_shared_unlock :: proc(rw: ^RW_Mutex) {
+	state := atomic_sub(&rw.impl.state, RW_Mutex_State_Reader);
+
+	if (state & RW_Mutex_State_Reader_Mask == RW_Mutex_State_Reader) &&
+	   (state & RW_Mutex_State_Is_Writing != 0) {
+	   	sema_post(&rw.impl.sema);
+	}
+}
+
+_rw_mutex_try_shared_lock :: proc(rw: ^RW_Mutex) -> bool {
+	state := atomic_load(&rw.impl.state);
+	if state & (RW_Mutex_State_Is_Writing|RW_Mutex_State_Writer_Mask) == 0 {
+		_, ok := atomic_cxchg(&rw.impl.state, state, state + RW_Mutex_State_Reader);
+		if ok {
+			return true;
+		}
+	}
+	if mutex_try_lock(&rw.impl.mutex) {
+		_ = atomic_add(&rw.impl.state, RW_Mutex_State_Reader);
+		mutex_unlock(&rw.impl.mutex);
+		return true;
+	}
+
+	return false;
+}
+
+
+
+Queue_Item :: struct {
+	next: ^Queue_Item,
+	futex: i32,
+}
+
+queue_item_wait :: proc(item: ^Queue_Item) {
+	for atomic_load_acq(&item.futex) == 0 {
+		// TODO(bill): Use a Futex here for Linux to improve performance and error handling
+		cpu_relax();
+	}
+}
+queue_item_signal :: proc(item: ^Queue_Item) {
+	atomic_store_rel(&item.futex, 1);
+	// TODO(bill): Use a Futex here for Linux to improve performance and error handling
+}
+
+
+_Cond :: struct {
+	queue_mutex: Mutex,
+	queue_head:  ^Queue_Item,
+	pending:     bool,
+}
+
+_cond_wait :: proc(c: ^Cond, m: ^Mutex) {
+	waiter := &Queue_Item{};
+
+	mutex_lock(&c.impl.queue_mutex);
+	waiter.next = c.impl.queue_head;
+	c.impl.queue_head = waiter;
+
+	atomic_store(&c.impl.pending, true);
+	mutex_unlock(&c.impl.queue_mutex);
+
+	mutex_unlock(m);
+	queue_item_wait(waiter);
+	mutex_lock(m);
+}
+
+_cond_wait_with_timeout :: proc(c: ^Cond, m: ^Mutex, timeout: time.Duration) -> bool {
+	// TODO(bill): _cond_wait_with_timeout for unix
+	return false;
+}
+
+_cond_signal :: proc(c: ^Cond) {
+	if !atomic_load(&c.impl.pending) {
+		return;
+	}
+
+	mutex_lock(&c.impl.queue_mutex);
+	waiter := c.impl.queue_head;
+	if c.impl.queue_head != nil {
+		c.impl.queue_head = c.impl.queue_head.next;
+	}
+	atomic_store(&c.impl.pending, c.impl.queue_head != nil);
+	mutex_unlock(&c.impl.queue_mutex);
+
+	if waiter != nil {
+		queue_item_signal(waiter);
+	}
+}
+
+_cond_broadcast :: proc(c: ^Cond) {
+	if !atomic_load(&c.impl.pending) {
+		return;
+	}
+
+	atomic_store(&c.impl.pending, false);
+
+	mutex_lock(&c.impl.queue_mutex);
+	waiters := c.impl.queue_head;
+	c.impl.queue_head = nil;
+	mutex_unlock(&c.impl.queue_mutex);
+
+	for waiters != nil {
+		queue_item_signal(waiters);
+		waiters = waiters.next;
+	}
+}
+
+
+} // !ODIN_SYNC_USE_PTHREADS

+ 154 - 0
core/sync/sync2/primitives_pthreads.odin

@@ -0,0 +1,154 @@
+//+build linux, darwin, freebsd
+//+private
+package sync2
+
+when #config(ODIN_SYNC_USE_PTHREADS, true) {
+
+import "core:time"
+import "core:sys/unix"
+
+_Mutex_State :: enum i32 {
+	Unlocked = 0,
+	Locked   = 1,
+	Waiting  = 2,
+}
+_Mutex :: struct {
+	pthread_mutex: unix.pthread_mutex_t,
+}
+
+_mutex_lock :: proc(m: ^Mutex) {
+	err := unix.pthread_mutex_lock(&m.impl.pthread_mutex);
+	assert(err == 0);
+}
+
+_mutex_unlock :: proc(m: ^Mutex) {
+	err := unix.pthread_mutex_unlock(&m.impl.pthread_mutex);
+	assert(err == 0);
+}
+
+_mutex_try_lock :: proc(m: ^Mutex) -> bool {
+	err := unix.pthread_mutex_trylock(&m.impl.pthread_mutex);
+	return err == 0;
+}
+
+
+
+RW_Mutex_State :: distinct uint;
+RW_Mutex_State_Half_Width :: size_of(RW_Mutex_State)*8/2;
+RW_Mutex_State_Is_Writing :: RW_Mutex_State(1);
+RW_Mutex_State_Writer     :: RW_Mutex_State(1)<<1;
+RW_Mutex_State_Reader     :: RW_Mutex_State(1)<<RW_Mutex_State_Half_Width;
+
+RW_Mutex_State_Writer_Mask :: RW_Mutex_State(1<<(RW_Mutex_State_Half_Width-1) - 1) << 1;
+RW_Mutex_State_Reader_Mask :: RW_Mutex_State(1<<(RW_Mutex_State_Half_Width-1) - 1) << RW_Mutex_State_Half_Width;
+
+
+_RW_Mutex :: struct {
+	// NOTE(bill): pthread_rwlock_t cannot be used since pthread_rwlock_destroy is required on some platforms
+	// TODO(bill): Can we determine which platforms exactly?
+	state: RW_Mutex_State,
+	mutex: Mutex,
+	sema:  Sema,
+}
+
+_rw_mutex_lock :: proc(rw: ^RW_Mutex) {
+	_ = atomic_add(&rw.impl.state, RW_Mutex_State_Writer);
+	mutex_lock(&rw.impl.mutex);
+
+	state := atomic_or(&rw.impl.state, RW_Mutex_State_Writer);
+	if state & RW_Mutex_State_Reader_Mask != 0 {
+		sema_wait(&rw.impl.sema);
+	}
+}
+
+_rw_mutex_unlock :: proc(rw: ^RW_Mutex) {
+	_ = atomic_and(&rw.impl.state, ~RW_Mutex_State_Is_Writing);
+	mutex_unlock(&rw.impl.mutex);
+}
+
+_rw_mutex_try_lock :: proc(rw: ^RW_Mutex) -> bool {
+	if mutex_try_lock(&rw.impl.mutex) {
+		state := atomic_load(&rw.impl.state);
+		if state & RW_Mutex_State_Reader_Mask == 0 {
+			_ = atomic_or(&rw.impl.state, RW_Mutex_State_Is_Writing);
+			return true;
+		}
+
+		mutex_unlock(&rw.impl.mutex);
+	}
+	return false;
+}
+
+_rw_mutex_shared_lock :: proc(rw: ^RW_Mutex) {
+	state := atomic_load(&rw.impl.state);
+	for state & (RW_Mutex_State_Is_Writing|RW_Mutex_State_Writer_Mask) == 0 {
+		ok: bool;
+		state, ok = atomic_cxchgweak(&rw.impl.state, state, state + RW_Mutex_State_Reader);
+		if ok {
+			return;
+		}
+	}
+
+	mutex_lock(&rw.impl.mutex);
+	_ = atomic_add(&rw.impl.state, RW_Mutex_State_Reader);
+	mutex_unlock(&rw.impl.mutex);
+}
+
+_rw_mutex_shared_unlock :: proc(rw: ^RW_Mutex) {
+	state := atomic_sub(&rw.impl.state, RW_Mutex_State_Reader);
+
+	if (state & RW_Mutex_State_Reader_Mask == RW_Mutex_State_Reader) &&
+	   (state & RW_Mutex_State_Is_Writing != 0) {
+	   	sema_post(&rw.impl.sema);
+	}
+}
+
+_rw_mutex_try_shared_lock :: proc(rw: ^RW_Mutex) -> bool {
+	state := atomic_load(&rw.impl.state);
+	if state & (RW_Mutex_State_Is_Writing|RW_Mutex_State_Writer_Mask) == 0 {
+		_, ok := atomic_cxchg(&rw.impl.state, state, state + RW_Mutex_State_Reader);
+		if ok {
+			return true;
+		}
+	}
+	if mutex_try_lock(&rw.impl.mutex) {
+		_ = atomic_add(&rw.impl.state, RW_Mutex_State_Reader);
+		mutex_unlock(&rw.impl.mutex);
+		return true;
+	}
+
+	return false;
+}
+
+_Cond :: struct {
+	pthread_cond: unix.pthread_cond_t,
+}
+
+_cond_wait :: proc(c: ^Cond, m: ^Mutex) {
+	err := unix.pthread_cond_wait(&c.impl.pthread_cond, &m.impl.pthread_mutex);
+	assert(err == 0);
+}
+
+_cond_wait_with_timeout :: proc(c: ^Cond, m: ^Mutex, timeout: time.Duration) -> bool {
+	ns := time.duration_nanoseconds(timeout);
+	timeout_timespec := &time.TimeSpec{
+		tv_sec  = ns / 1e9,
+		tv_nsec = ns % 1e9,
+	};
+	err := unix.pthread_cond_timedwait(&c.impl.pthread_cond, &m.impl.pthread_mutex, timeout_timespec);
+	// TODO(bill):
+	return err == 0;
+}
+
+_cond_signal :: proc(c: ^Cond) {
+	err := unix.pthread_cond_signal(&c.impl.pthread_cond);
+	assert(err == 0);
+}
+
+_cond_broadcast :: proc(c: ^Cond) {
+	err := unix.pthread_cond_broadcast(&c.impl.pthread_cond);
+	assert(err == 0);
+}
+
+
+} // ODIN_SYNC_USE_PTHREADS

+ 73 - 0
core/sync/sync2/primitives_windows.odin

@@ -0,0 +1,73 @@
+//+build windows
+//+private
+package sync2
+
+import "core:time"
+import win32 "core:sys/windows"
+
+_Mutex :: struct {
+	srwlock: win32.SRWLOCK,
+}
+
+_mutex_lock :: proc(m: ^Mutex) {
+	win32.AcquireSRWLockExclusive(&m.impl.srwlock);
+}
+
+_mutex_unlock :: proc(m: ^Mutex) {
+	win32.ReleaseSRWLockExclusive(&m.impl.srwlock);
+}
+
+_mutex_try_lock :: proc(m: ^Mutex) -> bool {
+	return bool(win32.TryAcquireSRWLockExclusive(&m.impl.srwlock));
+}
+
+_RW_Mutex :: struct {
+	srwlock: win32.SRWLOCK,
+}
+
+_rw_mutex_lock :: proc(rw: ^RW_Mutex) {
+	win32.AcquireSRWLockExclusive(&rw.impl.srwlock);
+}
+
+_rw_mutex_unlock :: proc(rw: ^RW_Mutex) {
+	win32.ReleaseSRWLockExclusive(&rw.impl.srwlock);
+}
+
+_rw_mutex_try_lock :: proc(rw: ^RW_Mutex) -> bool {
+	return bool(win32.TryAcquireSRWLockExclusive(&rw.impl.srwlock));
+}
+
+_rw_mutex_shared_lock :: proc(rw: ^RW_Mutex) {
+	win32.AcquireSRWLockShared(&rw.impl.srwlock);
+}
+
+_rw_mutex_shared_unlock :: proc(rw: ^RW_Mutex) {
+	win32.ReleaseSRWLockShared(&rw.impl.srwlock);
+}
+
+_rw_mutex_try_shared_lock :: proc(rw: ^RW_Mutex) -> bool {
+	return bool(win32.TryAcquireSRWLockShared(&rw.impl.srwlock));
+}
+
+
+
+_Cond :: struct {
+	cond: win32.CONDITION_VARIABLE,
+}
+
+_cond_wait :: proc(c: ^Cond, m: ^Mutex) {
+	_ = win32.SleepConditionVariableSRW(&c.impl.cond, &m.impl.srwlock, win32.INFINITE, 0);
+}
+
+_cond_wait_with_timeout :: proc(c: ^Cond, m: ^Mutex, timeout: time.Duration) -> bool {
+	ms := win32.DWORD((max(time.duration_nanoseconds(timeout), 0) + 999999)/1000000);
+	return cast(bool)win32.SleepConditionVariableSRW(&c.impl.cond, &m.impl.srwlock, ms, 0);
+}
+
+_cond_signal :: proc(c: ^Cond) {
+	win32.WakeConditionVariable(&c.impl.cond);
+}
+
+_cond_broadcast :: proc(c: ^Cond) {
+	win32.WakeAllConditionVariable(&c.impl.cond);
+}

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

@@ -10,3 +10,57 @@ foreign advapi32 {
 	                         DesiredAccess: DWORD,
 	                         TokenHandle: ^HANDLE) -> BOOL ---
 }
+
+// Necessary to create a token to impersonate a user with for CreateProcessAsUser
+@(default_calling_convention="stdcall")
+foreign advapi32 {
+	LogonUserW :: proc(
+		lpszUsername: LPCWSTR,
+		lpszDomain: LPCWSTR,
+		lpszPassword: LPCWSTR,
+		dwLogonType: Logon32_Type,
+		dwLogonProvider: Logon32_Provider,
+		phToken: ^HANDLE,
+	) -> BOOL ---
+
+	// https://docs.microsoft.com/en-us/windows/win32/api/winbase/nf-winbase-lookupaccountnamew
+	// To look up the SID to use with DeleteProfileW.
+	LookupAccountNameW :: proc(
+		lpSystemName: wstring,
+		lpAccountName: wstring,
+		Sid: ^SID,
+		cbSid: ^DWORD,
+		ReferencedDomainName: wstring,
+		cchReferencedDomainName: ^DWORD,
+		peUse: ^SID_TYPE,
+	) -> BOOL ---
+
+	CreateProcessWithLogonW :: proc(
+		lpUsername: wstring,
+		lpDomain: wstring,
+		lpPassword: wstring,
+		dwLogonFlags: DWORD,
+		lpApplicationName: wstring,
+		lpCommandLine: wstring,
+		dwCreationFlags: DWORD,
+		lpEnvironment: LPVOID,
+		lpCurrentDirectory: wstring,
+		lpStartupInfo: LPSTARTUPINFO,
+		lpProcessInformation: LPPROCESS_INFORMATION,
+	) -> BOOL ---
+
+	// https://docs.microsoft.com/en-us/windows/win32/api/processthreadsapi/nf-processthreadsapi-createprocessasuserw
+	CreateProcessAsUserW :: proc(
+		hToken: HANDLE,
+		lpApplicationName: wstring,
+		lpCommandLine: wstring,
+		lpProcessAttributes: LPSECURITY_ATTRIBUTES,
+		lpThreadAttributes: LPSECURITY_ATTRIBUTES,
+		bInheritHandles: BOOL,
+		dwCreationFlags: DWORD,
+		lpEnvironment: LPVOID,
+		lpCurrentDirectory: wstring,
+		lpStartupInfo: LPSTARTUPINFO,
+		lpProcessInformation: LPPROCESS_INFORMATION,
+	) -> BOOL ---;
+}

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

@@ -0,0 +1,37 @@
+package sys_windows
+
+foreign import netapi32 "system:Netapi32.lib"
+
+@(default_calling_convention="stdcall")
+foreign netapi32 {
+	NetUserAdd :: proc(
+		servername: wstring,
+		level: DWORD,
+		user_info: ^USER_INFO_1, // Perhaps make this a #raw_union with USER_INFO1..4 when we need the other levels.
+		parm_err: ^DWORD,
+	) -> NET_API_STATUS ---;
+	NetUserDel :: proc(
+		servername: wstring,
+		username: wstring,
+	) -> NET_API_STATUS ---;
+	NetUserGetInfo :: proc(
+		servername: wstring,
+		username: wstring,
+		level: DWORD,
+		user_info: ^USER_INFO_1,
+	) -> NET_API_STATUS ---;
+	NetLocalGroupAddMembers :: proc(
+		servername: wstring,
+		groupname: wstring,
+		level: DWORD,
+		group_members_info: ^LOCALGROUP_MEMBERS_INFO_0, // Actually a variably sized array of these.
+		totalentries: DWORD,
+	) -> NET_API_STATUS ---;
+	NetLocalGroupDelMembers :: proc(
+		servername: wstring,
+		groupname: wstring,
+		level: DWORD,
+		group_members_info: ^LOCALGROUP_MEMBERS_INFO_0, // Actually a variably sized array of these.
+		totalentries: DWORD,
+	) -> NET_API_STATUS ---;
+}

+ 449 - 2
core/sys/windows/types.odin

@@ -232,6 +232,7 @@ PROGRESS_CONTINUE: DWORD : 0;
 ERROR_FILE_NOT_FOUND: DWORD : 2;
 ERROR_PATH_NOT_FOUND: DWORD : 3;
 ERROR_ACCESS_DENIED: DWORD : 5;
+ERROR_NOT_ENOUGH_MEMORY: DWORD : 8;
 ERROR_INVALID_HANDLE: DWORD : 6;
 ERROR_NO_MORE_FILES: DWORD : 18;
 ERROR_SHARING_VIOLATION: DWORD : 32;
@@ -570,7 +571,8 @@ PROCESS_INFORMATION :: struct {
 	dwThreadId: DWORD,
 }
 
-STARTUPINFO :: struct {
+// FYI: This is STARTUPINFOW, not STARTUPINFOA
+STARTUPINFO :: struct #packed {
 	cb: DWORD,
 	lpReserved: LPWSTR,
 	lpDesktop: LPWSTR,
@@ -580,7 +582,7 @@ STARTUPINFO :: struct {
 	dwXSize: DWORD,
 	dwYSize: DWORD,
 	dwXCountChars: DWORD,
-	dwYCountCharts: DWORD,
+	dwYCountChars: DWORD,
 	dwFillAttribute: DWORD,
 	dwFlags: DWORD,
 	wShowWindow: WORD,
@@ -788,3 +790,448 @@ OSVERSIONINFOEXW :: struct {
     wProductType:        UCHAR,
     wReserved:           UCHAR,
 };
+
+// https://docs.microsoft.com/en-us/windows/win32/api/winnt/ns-winnt-quota_limits
+// Used in LogonUserExW
+PQUOTA_LIMITS :: struct {
+	PagedPoolLimit: SIZE_T,
+	NonPagedPoolLimit: SIZE_T,
+	MinimumWorkingSetSize: SIZE_T,
+	MaximumWorkingSetSize: SIZE_T,
+	PagefileLimit: SIZE_T,
+	TimeLimit: LARGE_INTEGER,
+};
+
+Logon32_Type :: enum DWORD {
+	INTERACTIVE       = 2,
+	NETWORK           = 3,
+	BATCH             = 4,
+	SERVICE           = 5,
+	UNLOCK            = 7,
+	NETWORK_CLEARTEXT = 8,
+	NEW_CREDENTIALS   = 9,
+}
+
+Logon32_Provider :: enum DWORD {
+	DEFAULT = 0,
+	WINNT35 = 1,
+	WINNT40 = 2,
+	WINNT50 = 3,
+	VIRTUAL = 4,
+}
+
+// https://docs.microsoft.com/en-us/windows/win32/api/profinfo/ns-profinfo-profileinfow
+// Used in LoadUserProfileW
+
+PROFILEINFOW :: struct {
+	dwSize: DWORD,
+	dwFlags: DWORD,
+	lpUserName: LPWSTR,
+	lpProfilePath: LPWSTR,
+	lpDefaultPath: LPWSTR,
+	lpServerName: LPWSTR,
+	lpPolicyPath: LPWSTR,
+  	hProfile: HANDLE,
+};
+
+// Used in LookupAccountNameW
+SID_NAME_USE :: distinct DWORD;
+
+SID_TYPE :: enum SID_NAME_USE {
+  User = 1,
+  Group,
+  Domain,
+  Alias,
+  WellKnownGroup,
+  DeletedAccount,
+  Invalid,
+  Unknown,
+  Computer,
+  Label,
+  LogonSession
+}
+
+SECURITY_MAX_SID_SIZE :: 68;
+
+// https://docs.microsoft.com/en-us/windows/win32/api/winnt/ns-winnt-sid
+SID :: struct #packed {
+	Revision: byte,
+	SubAuthorityCount: byte,
+	IdentifierAuthority: SID_IDENTIFIER_AUTHORITY,
+	SubAuthority: [15]DWORD, // Array of DWORDs
+};
+#assert(size_of(SID) == SECURITY_MAX_SID_SIZE);
+
+SID_IDENTIFIER_AUTHORITY :: struct #packed {
+    Value: [6]u8,
+};
+
+// For NetAPI32
+// https://github.com/tpn/winsdk-10/blob/master/Include/10.0.14393.0/shared/lmerr.h
+// https://github.com/tpn/winsdk-10/blob/master/Include/10.0.14393.0/shared/LMaccess.h
+
+UNLEN      :: 256;        // Maximum user name length
+LM20_UNLEN ::  20;        // LM 2.0 Maximum user name length
+
+GNLEN      :: UNLEN;      // Group name
+LM20_GNLEN :: LM20_UNLEN; // LM 2.0 Group name
+
+PWLEN      :: 256;        // Maximum password length
+LM20_PWLEN ::  14;        // LM 2.0 Maximum password length
+
+USER_PRIV :: enum DWORD {
+	Guest = 0,
+	User  = 1,
+	Admin = 2,
+	Mask  = 0x3,
+}
+
+USER_INFO_FLAG :: enum DWORD {
+	Script                          = 0,  // 1 <<  0: 0x0001,
+	AccountDisable                  = 1,  // 1 <<  1: 0x0002,
+	HomeDir_Required                = 3,  // 1 <<  3: 0x0008,
+	Lockout                         = 4,  // 1 <<  4: 0x0010,
+	Passwd_NotReqd                  = 5,  // 1 <<  5: 0x0020,
+	Passwd_Cant_Change              = 6,  // 1 <<  6: 0x0040,
+	Encrypted_Text_Password_Allowed = 7,  // 1 <<  7: 0x0080,
+
+    Temp_Duplicate_Account          = 8,  // 1 <<  8: 0x0100,
+    Normal_Account                  = 9,  // 1 <<  9: 0x0200,
+    InterDomain_Trust_Account       = 11, // 1 << 11: 0x0800,
+    Workstation_Trust_Account       = 12, // 1 << 12: 0x1000,
+    Server_Trust_Account            = 13, // 1 << 13: 0x2000,
+}
+USER_INFO_FLAGS :: distinct bit_set[USER_INFO_FLAG];
+
+USER_INFO_1 :: struct #packed {
+	name: LPWSTR,
+	password: LPWSTR,     // Max password length is defined in LM20_PWLEN.
+	password_age: DWORD,
+	priv: USER_PRIV,
+	home_dir: LPWSTR,
+	comment: LPWSTR,
+	flags: USER_INFO_FLAGS,
+	script_path: LPWSTR,
+};
+#assert(size_of(USER_INFO_1) == 50);
+
+LOCALGROUP_MEMBERS_INFO_0 :: struct #packed {
+	sid: ^SID,
+};
+
+NET_API_STATUS :: enum DWORD {
+	Success = 0,
+	ERROR_ACCESS_DENIED = 5,
+	MemberInAlias = 1378,
+	NetNotStarted = 2102,
+	UnknownServer = 2103,
+	ShareMem = 2104,
+	NoNetworkResource = 2105,
+	RemoteOnly = 2106,
+	DevNotRedirected = 2107,
+	ServerNotStarted = 2114,
+	ItemNotFound = 2115,
+	UnknownDevDir = 2116,
+	RedirectedPath = 2117,
+	DuplicateShare = 2118,
+	NoRoom = 2119,
+	TooManyItems = 2121,
+	InvalidMaxUsers = 2122,
+	BufTooSmall = 2123,
+	RemoteErr = 2127,
+	LanmanIniError = 2131,
+	NetworkError = 2136,
+	WkstaInconsistentState = 2137,
+	WkstaNotStarted = 2138,
+	BrowserNotStarted = 2139,
+	InternalError = 2140,
+	BadTransactConfig = 2141,
+	InvalidAPI = 2142,
+	BadEventName = 2143,
+	DupNameReboot = 2144,
+	CfgCompNotFound = 2146,
+	CfgParamNotFound = 2147,
+	LineTooLong = 2149,
+	QNotFound = 2150,
+	JobNotFound = 2151,
+	DestNotFound = 2152,
+	DestExists = 2153,
+	QExists = 2154,
+	QNoRoom = 2155,
+	JobNoRoom = 2156,
+	DestNoRoom = 2157,
+	DestIdle = 2158,
+	DestInvalidOp = 2159,
+	ProcNoRespond = 2160,
+	SpoolerNotLoaded = 2161,
+	DestInvalidState = 2162,
+	QInvalidState = 2163,
+	JobInvalidState = 2164,
+	SpoolNoMemory = 2165,
+	DriverNotFound = 2166,
+	DataTypeInvalid = 2167,
+	ProcNotFound = 2168,
+	ServiceTableLocked = 2180,
+	ServiceTableFull = 2181,
+	ServiceInstalled = 2182,
+	ServiceEntryLocked = 2183,
+	ServiceNotInstalled = 2184,
+	BadServiceName = 2185,
+	ServiceCtlTimeout = 2186,
+	ServiceCtlBusy = 2187,
+	BadServiceProgName = 2188,
+	ServiceNotCtrl = 2189,
+	ServiceKillProc = 2190,
+	ServiceCtlNotValid = 2191,
+	NotInDispatchTbl = 2192,
+	BadControlRecv = 2193,
+	ServiceNotStarting = 2194,
+	AlreadyLoggedOn = 2200,
+	NotLoggedOn = 2201,
+	BadUsername = 2202,
+	BadPassword = 2203,
+	UnableToAddName_W = 2204,
+	UnableToAddName_F = 2205,
+	UnableToDelName_W = 2206,
+	UnableToDelName_F = 2207,
+	LogonsPaused = 2209,
+	LogonServerConflict = 2210,
+	LogonNoUserPath = 2211,
+	LogonScriptError = 2212,
+	StandaloneLogon = 2214,
+	LogonServerNotFound = 2215,
+	LogonDomainExists = 2216,
+	NonValidatedLogon = 2217,
+	ACFNotFound = 2219,
+	GroupNotFound = 2220,
+	UserNotFound = 2221,
+	ResourceNotFound = 2222,
+	GroupExists = 2223,
+	UserExists = 2224,
+	ResourceExists = 2225,
+	NotPrimary = 2226,
+	ACFNotLoaded = 2227,
+	ACFNoRoom = 2228,
+	ACFFileIOFail = 2229,
+	ACFTooManyLists = 2230,
+	UserLogon = 2231,
+	ACFNoParent = 2232,
+	CanNotGrowSegment = 2233,
+	SpeGroupOp = 2234,
+	NotInCache = 2235,
+	UserInGroup = 2236,
+	UserNotInGroup = 2237,
+	AccountUndefined = 2238,
+	AccountExpired = 2239,
+	InvalidWorkstation = 2240,
+	InvalidLogonHours = 2241,
+	PasswordExpired = 2242,
+	PasswordCantChange = 2243,
+	PasswordHistConflict = 2244,
+	PasswordTooShort = 2245,
+	PasswordTooRecent = 2246,
+	InvalidDatabase = 2247,
+	DatabaseUpToDate = 2248,
+	SyncRequired = 2249,
+	UseNotFound = 2250,
+	BadAsgType = 2251,
+	DeviceIsShared = 2252,
+	SameAsComputerName = 2253,
+	NoComputerName = 2270,
+	MsgAlreadyStarted = 2271,
+	MsgInitFailed = 2272,
+	NameNotFound = 2273,
+	AlreadyForwarded = 2274,
+	AddForwarded = 2275,
+	AlreadyExists = 2276,
+	TooManyNames = 2277,
+	DelComputerName = 2278,
+	LocalForward = 2279,
+	GrpMsgProcessor = 2280,
+	PausedRemote = 2281,
+	BadReceive = 2282,
+	NameInUse = 2283,
+	MsgNotStarted = 2284,
+	NotLocalName = 2285,
+	NoForwardName = 2286,
+	RemoteFull = 2287,
+	NameNotForwarded = 2288,
+	TruncatedBroadcast = 2289,
+	InvalidDevice = 2294,
+	WriteFault = 2295,
+	DuplicateName = 2297,
+	DeleteLater = 2298,
+	IncompleteDel = 2299,
+	MultipleNets = 2300,
+	NetNameNotFound = 2310,
+	DeviceNotShared = 2311,
+	ClientNameNotFound = 2312,
+	FileIdNotFound = 2314,
+	ExecFailure = 2315,
+	TmpFile = 2316,
+	TooMuchData = 2317,
+	DeviceShareConflict = 2318,
+	BrowserTableIncomplete = 2319,
+	NotLocalDomain = 2320,
+	IsDfsShare = 2321,
+	DevInvalidOpCode = 2331,
+	DevNotFound = 2332,
+	DevNotOpen = 2333,
+	BadQueueDevString = 2334,
+	BadQueuePriority = 2335,
+	NoCommDevs = 2337,
+	QueueNotFound = 2338,
+	BadDevString = 2340,
+	BadDev = 2341,
+	InUseBySpooler = 2342,
+	CommDevInUse = 2343,
+	InvalidComputer = 2351,
+	MaxLenExceeded = 2354,
+	BadComponent = 2356,
+	CantType = 2357,
+	TooManyEntries = 2362,
+	ProfileFileTooBig = 2370,
+	ProfileOffset = 2371,
+	ProfileCleanup = 2372,
+	ProfileUnknownCmd = 2373,
+	ProfileLoadErr = 2374,
+	ProfileSaveErr = 2375,
+	LogOverflow = 2377,
+	LogFileChanged = 2378,
+	LogFileCorrupt = 2379,
+	SourceIsDir = 2380,
+	BadSource = 2381,
+	BadDest = 2382,
+	DifferentServers = 2383,
+	RunSrvPaused = 2385,
+	ErrCommRunSrv = 2389,
+	ErrorExecingGhost = 2391,
+	ShareNotFound = 2392,
+	InvalidLana = 2400,
+	OpenFiles = 2401,
+	ActiveConns = 2402,
+	BadPasswordCore = 2403,
+	DevInUse = 2404,
+	LocalDrive = 2405,
+	AlertExists = 2430,
+	TooManyAlerts = 2431,
+	NoSuchAlert = 2432,
+	BadRecipient = 2433,
+	AcctLimitExceeded = 2434,
+	InvalidLogSeek = 2440,
+	BadUasConfig = 2450,
+	InvalidUASOp = 2451,
+	LastAdmin = 2452,
+	DCNotFound = 2453,
+	LogonTrackingError = 2454,
+	NetlogonNotStarted = 2455,
+	CanNotGrowUASFile = 2456,
+	TimeDiffAtDC = 2457,
+	PasswordMismatch = 2458,
+	NoSuchServer = 2460,
+	NoSuchSession = 2461,
+	NoSuchConnection = 2462,
+	TooManyServers = 2463,
+	TooManySessions = 2464,
+	TooManyConnections = 2465,
+	TooManyFiles = 2466,
+	NoAlternateServers = 2467,
+	TryDownLevel = 2470,
+	UPSDriverNotStarted = 2480,
+	UPSInvalidConfig = 2481,
+	UPSInvalidCommPort = 2482,
+	UPSSignalAsserted = 2483,
+	UPSShutdownFailed = 2484,
+	BadDosRetCode = 2500,
+	ProgNeedsExtraMem = 2501,
+	BadDosFunction = 2502,
+	RemoteBootFailed = 2503,
+	BadFileCheckSum = 2504,
+	NoRplBootSystem = 2505,
+	RplLoadrNetBiosErr = 2506,
+	RplLoadrDiskErr = 2507,
+	ImageParamErr = 2508,
+	TooManyImageParams = 2509,
+	NonDosFloppyUsed = 2510,
+	RplBootRestart = 2511,
+	RplSrvrCallFailed = 2512,
+	CantConnectRplSrvr = 2513,
+	CantOpenImageFile = 2514,
+	CallingRplSrvr = 2515,
+	StartingRplBoot = 2516,
+	RplBootServiceTerm = 2517,
+	RplBootStartFailed = 2518,
+	RPL_CONNECTED = 2519,
+	BrowserConfiguredToNotRun = 2550,
+	RplNoAdaptersStarted = 2610,
+	RplBadRegistry = 2611,
+	RplBadDatabase = 2612,
+	RplRplfilesShare = 2613,
+	RplNotRplServer = 2614,
+	RplCannotEnum = 2615,
+	RplWkstaInfoCorrupted = 2616,
+	RplWkstaNotFound = 2617,
+	RplWkstaNameUnavailable = 2618,
+	RplProfileInfoCorrupted = 2619,
+	RplProfileNotFound = 2620,
+	RplProfileNameUnavailable = 2621,
+	RplProfileNotEmpty = 2622,
+	RplConfigInfoCorrupted = 2623,
+	RplConfigNotFound = 2624,
+	RplAdapterInfoCorrupted = 2625,
+	RplInternal = 2626,
+	RplVendorInfoCorrupted = 2627,
+	RplBootInfoCorrupted = 2628,
+	RplWkstaNeedsUserAcct = 2629,
+	RplNeedsRPLUSERAcct = 2630,
+	RplBootNotFound = 2631,
+	RplIncompatibleProfile = 2632,
+	RplAdapterNameUnavailable = 2633,
+	RplConfigNotEmpty = 2634,
+	RplBootInUse = 2635,
+	RplBackupDatabase = 2636,
+	RplAdapterNotFound = 2637,
+	RplVendorNotFound = 2638,
+	RplVendorNameUnavailable = 2639,
+	RplBootNameUnavailable = 2640,
+	RplConfigNameUnavailable = 2641,
+	DfsInternalCorruption = 2660,
+	DfsVolumeDataCorrupt = 2661,
+	DfsNoSuchVolume = 2662,
+	DfsVolumeAlreadyExists = 2663,
+	DfsAlreadyShared = 2664,
+	DfsNoSuchShare = 2665,
+	DfsNotALeafVolume = 2666,
+	DfsLeafVolume = 2667,
+	DfsVolumeHasMultipleServers = 2668,
+	DfsCantCreateJunctionPoint = 2669,
+	DfsServerNotDfsAware = 2670,
+	DfsBadRenamePath = 2671,
+	DfsVolumeIsOffline = 2672,
+	DfsNoSuchServer = 2673,
+	DfsCyclicalName = 2674,
+	DfsNotSupportedInServerDfs = 2675,
+	DfsDuplicateService = 2676,
+	DfsCantRemoveLastServerShare = 2677,
+	DfsVolumeIsInterDfs = 2678,
+	DfsInconsistent = 2679,
+	DfsServerUpgraded = 2680,
+	DfsDataIsIdentical = 2681,
+	DfsCantRemoveDfsRoot = 2682,
+	DfsChildOrParentInDfs = 2683,
+	DfsInternalError = 2690,
+	SetupAlreadyJoined = 2691,
+	SetupNotJoined = 2692,
+	SetupDomainController = 2693,
+	DefaultJoinRequired = 2694,
+	InvalidWorkgroupName = 2695,
+	NameUsesIncompatibleCodePage = 2696,
+	ComputerAccountNotFound = 2697,
+	PersonalSku = 2698,
+	SetupCheckDNSConfig = 2699,
+	PasswordMustChange = 2701,
+	AccountLockedOut = 2702,
+	PasswordTooLong = 2703,
+	PasswordNotComplexEnough = 2704,
+	PasswordFilterError = 2705,
+}

+ 28 - 0
core/sys/windows/userenv.odin

@@ -7,4 +7,32 @@ foreign userenv {
 	GetUserProfileDirectoryW :: proc(hToken: HANDLE,
 	                                 lpProfileDir: LPWSTR,
 	                                 lpcchSize: ^DWORD) -> BOOL ---
+	LoadUserProfileW :: proc(
+		hToken: HANDLE,
+		lpProfileInfo: ^PROFILEINFOW,
+	) -> BOOL ---
+
+	// https://docs.microsoft.com/en-us/windows/win32/api/userenv/nf-userenv-createprofile
+	// The caller must have administrator privileges to call this function.
+	CreateProfile :: proc(
+		pszUserSid: LPCWSTR,
+		pszUserName: LPCWSTR,
+		pszProfilePath: wstring,
+		cchProfilePath: DWORD,
+	) -> u32 ---
+
+	// https://docs.microsoft.com/en-us/windows/win32/api/userenv/nf-userenv-deleteprofilew
+	// The caller must have administrative privileges to delete a user's profile.
+	DeleteProfileW :: proc(
+		lpSidString: LPCWSTR,
+		lpProfilePath: LPCWSTR,
+		lpComputerName: LPCWSTR,
+	) -> BOOL ---
+
+	// https://docs.microsoft.com/en-us/windows/win32/api/sddl/nf-sddl-convertsidtostringsida
+	// To turn a SID into a string SID to use with CreateProfile & DeleteProfileW.
+	ConvertSidToStringSidW :: proc(
+		Sid: ^SID,
+	  	StringSid: ^LPCWSTR,
+	) -> BOOL ---
 }

+ 374 - 0
core/sys/windows/util.odin

@@ -1,5 +1,8 @@
 package sys_windows
 
+import "core:strings"
+import "core:sys/win32"
+
 LOWORD :: #force_inline proc "contextless" (x: DWORD) -> WORD {
 	return WORD(x & 0xffff);
 }
@@ -81,3 +84,374 @@ utf16_to_utf8 :: proc(s: []u16, allocator := context.temp_allocator) -> string {
 	return wstring_to_utf8(raw_data(s), len(s), allocator);
 }
 
+// AdvAPI32, NetAPI32 and UserENV helpers.
+
+allowed_username :: proc(username: string) -> bool {
+/*
+	User account names are limited to 20 characters and group names are limited to 256 characters.
+	In addition, account names cannot be terminated by a period and they cannot include commas or any of the following printable characters:
+	", /, , [, ], :, |, <, >, +, =, ;, ?, *. Names also cannot include characters in the range 1-31, which are nonprintable.
+*/
+
+	_DISALLOWED :: "\"/ []:|<>+=;?*,";
+
+	if len(username) > LM20_UNLEN || len(username) == 0 {
+		return false;
+	}
+	if username[len(username)-1] == '.' {
+		return false;
+	}
+
+	for r in username {
+		if r > 0 && r < 32 {
+			return false;
+		}
+	}
+	if strings.contains_any(username, _DISALLOWED) {
+		return false;
+	}
+
+	return true;
+}
+
+// Returns .Success on success.
+_add_user :: proc(servername: string, username: string, password: string) -> (ok: NET_API_STATUS) {
+
+	servername_w: wstring;
+	username_w:   []u16;
+	password_w:   []u16;
+
+	if len(servername) == 0 {
+		// Create account on this computer
+		servername_w = nil;
+	} else {
+		server := utf8_to_utf16(servername, context.temp_allocator);
+		servername_w = &server[0];
+	}
+
+	if len(username) == 0 || len(username) > LM20_UNLEN {
+		return .BadUsername;
+	}
+	if !allowed_username(username) {
+		return .BadUsername;
+	}
+	if len(password) == 0 || len(password) > LM20_PWLEN {
+		return .BadPassword;
+	}
+
+	username_w = utf8_to_utf16(username, context.temp_allocator);
+	password_w = utf8_to_utf16(password, context.temp_allocator);
+
+
+	level  := DWORD(1);
+	parm_err: DWORD;
+
+	user_info := USER_INFO_1{
+		name         = &username_w[0],
+		password     = &password_w[0], // Max password length is defined in LM20_PWLEN.
+		password_age = 0,              // Ignored
+		priv         = .User,
+		home_dir     = nil,            // We'll set it later
+		comment      = nil,
+		flags        = {.Script, .Normal_Account},
+		script_path  = nil,
+	};
+
+	ok = NetUserAdd(
+		servername_w,
+		level,
+		&user_info,
+		&parm_err,
+	);
+
+	return;
+}
+
+get_computer_name_and_account_sid :: proc(username: string) -> (computer_name: string, sid := SID{}, ok: bool) {
+
+	username_w := utf8_to_utf16(username, context.temp_allocator);
+	cbsid: DWORD;
+	computer_name_size: DWORD;
+	pe_use := SID_TYPE.User;
+
+	res := LookupAccountNameW(
+		nil, // Look on this computer first
+		&username_w[0],
+		&sid,
+		&cbsid,
+		nil,
+		&computer_name_size,
+		&pe_use,
+	);
+	if computer_name_size == 0 {
+		// User didn't exist, or we'd have a size here.
+		return "", {}, false;
+	}
+
+	cname_w := make([]u16, min(computer_name_size, 1), context.temp_allocator);
+
+	res = LookupAccountNameW(
+		nil,
+		&username_w[0],
+		&sid,
+		&cbsid,
+		&cname_w[0],
+		&computer_name_size,
+		&pe_use,
+	);
+
+	if !res {
+		return "", {}, false;
+	}
+	computer_name = utf16_to_utf8(cname_w, context.temp_allocator);
+
+	ok = true;
+	return;
+}
+
+get_sid :: proc(username: string, sid: ^SID) -> (ok: bool) {
+
+	username_w := utf8_to_utf16(username, context.temp_allocator);
+	cbsid: DWORD;
+	computer_name_size: DWORD;
+	pe_use := SID_TYPE.User;
+
+	res := LookupAccountNameW(
+		nil, // Look on this computer first
+		&username_w[0],
+		sid,
+		&cbsid,
+		nil,
+		&computer_name_size,
+		&pe_use,
+	);
+	if computer_name_size == 0 {
+		// User didn't exist, or we'd have a size here.
+		return false;
+	}
+
+	cname_w := make([]u16, min(computer_name_size, 1), context.temp_allocator);
+
+	res = LookupAccountNameW(
+		nil,
+		&username_w[0],
+		sid,
+		&cbsid,
+		&cname_w[0],
+		&computer_name_size,
+		&pe_use,
+	);
+
+	if !res {
+		return false;
+	}
+	ok = true;
+	return;
+}
+
+add_user_to_group :: proc(sid: ^SID, group: string) -> (ok: NET_API_STATUS) {
+	group_member := LOCALGROUP_MEMBERS_INFO_0{
+		sid = sid,
+	};
+	group_name := utf8_to_utf16(group, context.temp_allocator);
+	ok = NetLocalGroupAddMembers(
+		nil,
+		&group_name[0],
+		0,
+		&group_member,
+		1,
+	);
+	return;
+}
+
+add_del_from_group :: proc(sid: ^SID, group: string) -> (ok: NET_API_STATUS) {
+	group_member := LOCALGROUP_MEMBERS_INFO_0{
+		sid = sid,
+	};
+	group_name := utf8_to_utf16(group, context.temp_allocator);
+	ok = NetLocalGroupDelMembers(
+		nil,
+		&group_name[0],
+		0,
+		&group_member,
+		1,
+	);
+	return;
+}
+
+add_user_profile :: proc(username: string) -> (ok: bool, profile_path: string) {
+	username_w := utf8_to_utf16(username, context.temp_allocator);
+
+	sid := SID{};
+	ok = get_sid(username, &sid);
+	if ok == false {
+		return false, "";
+	}
+
+	sb: wstring;
+	res := ConvertSidToStringSidW(&sid, &sb);
+	if res == false {
+		return false, "";
+	}
+	defer win32.local_free(sb);
+
+	pszProfilePath := make([]u16, 257, context.temp_allocator);
+	res2 := CreateProfile(
+		sb,
+		&username_w[0],
+		&pszProfilePath[0],
+		257,
+	);
+	if res2 != 0 {
+		return false, "";
+	}
+	profile_path = wstring_to_utf8(&pszProfilePath[0], 257);
+
+	return true, profile_path;
+}
+
+
+delete_user_profile :: proc(username: string) -> (ok: bool) {
+	sid := SID{};
+	ok = get_sid(username, &sid);
+	if ok == false {
+		return false;
+	}
+
+	sb: wstring;
+	res := ConvertSidToStringSidW(&sid, &sb);
+	if res == false {
+		return false;
+	}
+	defer win32.local_free(sb);
+
+	res2 := DeleteProfileW(
+		sb,
+		nil,
+		nil,
+	);
+	return bool(res2);
+}
+
+add_user :: proc(servername: string, username: string, password: string) -> (ok: bool) {
+	/*
+		Convenience function that creates a new user, adds it to the group Users and creates a profile directory for it.
+		Requires elevated privileges (run as administrator).
+
+		TODO: Add a bool that governs whether to delete the user if adding to group and/or creating profile fail?
+		TODO: SecureZeroMemory the password after use.
+	*/
+
+	res := _add_user(servername, username, password);
+	if res != .Success {
+		return false;
+	}
+
+	// Grab the SID to add the user to the Users group.
+	sid: SID;
+	ok2 := get_sid(username, &sid);
+	if ok2 == false {
+		return false;
+	}
+
+	ok3 := add_user_to_group(&sid, "Users");
+	if ok3 != .Success {
+		return false;
+	}
+
+	return true;
+}
+
+delete_user :: proc(servername: string, username: string) -> (ok: bool) {
+	/*
+		Convenience function that deletes a user.
+		Requires elevated privileges (run as administrator).
+
+		TODO: Add a bool that governs whether to delete the profile from this wrapper?
+	*/
+
+	servername_w: wstring;
+	if len(servername) == 0 {
+		// Delete account on this computer
+		servername_w = nil;
+	} else {
+		server := utf8_to_utf16(servername, context.temp_allocator);
+		servername_w = &server[0];
+	}
+	username_w := utf8_to_utf16(username);
+
+	res := NetUserDel(
+		servername_w,
+		&username_w[0],
+	);
+	if res != .Success {
+		return false;
+	}
+	return true;
+}
+
+run_as_user :: proc(username, password, application, commandline: string, pi: ^PROCESS_INFORMATION, wait := true) -> (ok: bool) {
+	/*
+		Needs to be run as an account which has the "Replace a process level token" privilege.
+		This can be added to an account from: Control Panel -> Administrative Tools -> Local Security Policy.
+		The path to this policy is as follows: Local Policies -> User Rights Assignment -> Replace a process level token.
+		A reboot may be required for this change to take effect and impersonating a user to work.
+
+		TODO: SecureZeroMemory the password after use.
+
+	*/
+
+	username_w    := utf8_to_utf16(username);
+	domain_w      := utf8_to_utf16(".");
+	password_w    := utf8_to_utf16(password);
+	app_w         := utf8_to_utf16(application);
+
+	commandline_w: []u16 = {0};
+	if len(commandline) > 0 {
+		commandline_w = utf8_to_utf16(commandline);
+	}
+
+	user_token: HANDLE;
+
+	ok = bool(LogonUserW(
+		lpszUsername    = &username_w[0],
+		lpszDomain      = &domain_w[0],
+		lpszPassword    = &password_w[0],
+		dwLogonType     = .NEW_CREDENTIALS,
+		dwLogonProvider = .WINNT50,
+		phToken         = &user_token,
+	));
+
+	if !ok {
+		return false;
+		// err := GetLastError();
+		// fmt.printf("GetLastError: %v\n", err);
+	}
+	si := STARTUPINFO{};
+	si.cb = size_of(STARTUPINFO);
+	pi := pi;
+
+	ok = bool(CreateProcessAsUserW(
+		user_token,
+		&app_w[0],
+		&commandline_w[0],
+		nil,	// lpProcessAttributes,
+		nil,	// lpThreadAttributes,
+		false,	// bInheritHandles,
+    	0,		// creation flags
+    	nil,	// environment,
+    	nil,	// current directory: inherit from parent if nil
+    	&si,
+    	pi,
+    ));
+    if ok {
+    	if wait {
+	    	WaitForSingleObject(pi.hProcess, INFINITE);
+    		CloseHandle(pi.hProcess);
+    		CloseHandle(pi.hThread);
+    	}
+    	return true;
+    } else {
+    	return false;
+    }
+}

+ 40 - 30
core/thread/thread.odin

@@ -1,7 +1,6 @@
 package thread
 
 import "core:runtime"
-import "core:sync"
 import "core:mem"
 import "intrinsics"
 
@@ -26,6 +25,46 @@ Thread :: struct {
 
 #assert(size_of(Thread{}.user_index) == size_of(uintptr));
 
+Thread_Priority :: enum {
+	Normal,
+	Low,
+	High,
+}
+
+create :: proc(procedure: Thread_Proc, priority := Thread_Priority.Normal) -> ^Thread {
+	return _create(procedure, priority);
+}
+destroy :: proc(thread: ^Thread) {
+	_destroy(thread);
+}
+
+start :: proc(thread: ^Thread) {
+	_start(thread);
+}
+
+is_done :: proc(thread: ^Thread) -> bool {
+	return _is_done(thread);
+}
+
+
+join :: proc(thread: ^Thread) {
+	_join(thread);
+}
+
+
+join_mulitple :: proc(threads: ..^Thread) {
+	_join_multiple(..threads);
+}
+
+terminate :: proc(thread: ^Thread, exit_code: int) {
+	_terminate(thread, exit_code);
+}
+
+yield :: proc() {
+	_yield();
+}
+
+
 
 run :: proc(fn: proc(), init_context: Maybe(runtime.Context) = nil, priority := Thread_Priority.Normal) {
 	thread_proc :: proc(t: ^Thread) {
@@ -39,7 +78,6 @@ run :: proc(fn: proc(), init_context: Maybe(runtime.Context) = nil, priority :=
 	start(t);
 }
 
-
 run_with_data :: proc(data: rawptr, fn: proc(data: rawptr), init_context: Maybe(runtime.Context) = nil, priority := Thread_Priority.Normal) {
 	thread_proc :: proc(t: ^Thread) {
 		fn := cast(proc(rawptr))t.data;
@@ -152,31 +190,3 @@ create_and_start :: proc(fn: Thread_Proc, init_context: Maybe(runtime.Context) =
 	start(t);
 	return t;
 }
-
-
-Once :: struct {
-	m:    sync.Blocking_Mutex,
-	done: bool,
-}
-once_init :: proc(o: ^Once) {
-	sync.blocking_mutex_init(&o.m);
-	intrinsics.atomic_store_rel(&o.done, false);
-}
-once_destroy :: proc(o: ^Once) {
-	sync.blocking_mutex_destroy(&o.m);
-}
-
-once_do :: proc(o: ^Once, fn: proc()) {
-	if intrinsics.atomic_load(&o.done) == false {
-		_once_do_slow(o, fn);
-	}
-}
-
-_once_do_slow :: proc(o: ^Once, fn: proc()) {
-	sync.blocking_mutex_lock(&o.m);
-	defer sync.blocking_mutex_unlock(&o.m);
-	if !o.done {
-		fn();
-		intrinsics.atomic_store_rel(&o.done, true);
-	}
-}

+ 28 - 27
core/thread/thread_unix.odin

@@ -1,5 +1,6 @@
 // +build linux, darwin, freebsd
-package thread;
+// +private
+package thread
 
 import "core:runtime"
 import "core:intrinsics"
@@ -19,7 +20,7 @@ Thread_Os_Specific :: struct #align 16 {
 	// in a suspended state, we have it wait on this gate, which we
 	// signal to start it.
 	// destroyed after thread is started.
-	start_gate: sync.Condition,
+	start_gate:  sync.Condition,
 	start_mutex: sync.Mutex,
 
 	// if true, the thread has been started and the start_gate has been destroyed.
@@ -31,18 +32,11 @@ Thread_Os_Specific :: struct #align 16 {
 	// See the comment in `join`.
 	already_joined: bool,
 }
-
-Thread_Priority :: enum {
-	Normal,
-	Low,
-	High,
-}
-
 //
 // Creates a thread which will run the given procedure.
 // It then waits for `start` to be called.
 //
-create :: proc(procedure: Thread_Proc, priority := Thread_Priority.Normal) -> ^Thread {
+_create :: proc(procedure: Thread_Proc, priority := Thread_Priority.Normal) -> ^Thread {
 	__linux_thread_entry_proc :: proc "c" (t: rawptr) -> rawptr {
 		context = runtime.default_context();
 
@@ -67,7 +61,7 @@ create :: proc(procedure: Thread_Proc, priority := Thread_Priority.Normal) -> ^T
 			}
 		}
 
-		sync.atomic_store(&t.done, true, .Sequentially_Consistent);
+		intrinsics.atomic_store(&t.done, true);
 		return nil;
 	}
 
@@ -104,29 +98,30 @@ create :: proc(procedure: Thread_Proc, priority := Thread_Priority.Normal) -> ^T
 	res = unix.pthread_attr_setschedparam(&attrs, &params);
 	assert(res == 0);
 
-	sync.mutex_init(&thread.start_mutex);
-	sync.condition_init(&thread.start_gate, &thread.start_mutex);
 	if unix.pthread_create(&thread.unix_thread, &attrs, __linux_thread_entry_proc, thread) != 0 {
 		free(thread, thread.creation_allocator);
 		return nil;
 	}
 	thread.procedure = procedure;
 
+	sync.mutex_init(&thread.start_mutex);
+	sync.condition_init(&thread.start_gate, &thread.start_mutex);
+
 	return thread;
 }
 
-start :: proc(t: ^Thread) {
-	if sync.atomic_swap(&t.started, true, .Sequentially_Consistent) {
+_start :: proc(t: ^Thread) {
+	if intrinsics.atomic_xchg(&t.started, true) {
 		return;
 	}
 	sync.condition_signal(&t.start_gate);
 }
 
-is_done :: proc(t: ^Thread) -> bool {
-	return sync.atomic_load(&t.done, .Sequentially_Consistent);
+_is_done :: proc(t: ^Thread) -> bool {
+	return intrinsics.atomic_load(&t.done);
 }
 
-join :: proc(t: ^Thread) {
+_join :: proc(t: ^Thread) {
 	if unix.pthread_equal(unix.pthread_self(), t.unix_thread) {
 		return;
 	}
@@ -138,9 +133,9 @@ join :: proc(t: ^Thread) {
 	// See note on `already_joined` field.
 	// TODO(tetra): I'm not sure if we should do this, or panic, since I'm not
 	// sure it makes sense to need to join from multiple threads?
-	if sync.atomic_swap(&t.already_joined, true, .Sequentially_Consistent) {
+	if intrinsics.atomic_xchg(&t.already_joined, true) {
 		for {
-			if sync.atomic_load(&t.done, .Sequentially_Consistent) {
+			if intrinsics.atomic_load(&t.done) {
 				return;
 			}
 			intrinsics.cpu_relax();
@@ -152,31 +147,37 @@ join :: proc(t: ^Thread) {
 	// We do this instead because I don't know if there is a danger
 	// that you may join a different thread from the one you called join on,
 	// if the thread handle is reused.
-	if sync.atomic_load(&t.done, .Sequentially_Consistent) {
+	if intrinsics.atomic_load(&t.done) {
 		return;
 	}
 
 	ret_val: rawptr;
 	_ = unix.pthread_join(t.unix_thread, &ret_val);
-	if !sync.atomic_load(&t.done, .Sequentially_Consistent) {
+	if !intrinsics.atomic_load(&t.done) {
 		panic("thread not done after join");
 	}
 }
 
-join_multiple :: proc(threads: ..^Thread) {
+_join_multiple :: proc(threads: ..^Thread) {
 	for t in threads {
-		join(t);
+		_join(t);
 	}
 }
 
 
-destroy :: proc(t: ^Thread) {
-	join(t);
+_destroy :: proc(t: ^Thread) {
+	_join(t);
+	sync.condition_destroy(&t.start_gate);
+	sync.mutex_destroy(&t.start_mutex);
 	t.unix_thread = {};
 	free(t, t.creation_allocator);
 }
 
 
-yield :: proc() {
+_terminate :: proc(t: ^Thread, exit_code: int) {
+	// TODO(bill)
+}
+
+_yield :: proc() {
 	unix.sched_yield();
 }

+ 16 - 21
core/thread/thread_windows.odin

@@ -1,7 +1,9 @@
+//+build windows
+//+private
 package thread
 
 import "core:runtime"
-import "core:sync"
+import sync "core:sync/sync2"
 import win32 "core:sys/windows"
 
 Thread_Os_Specific :: struct {
@@ -10,20 +12,13 @@ Thread_Os_Specific :: struct {
 	done: bool, // see note in `is_done`
 }
 
-
-Thread_Priority :: enum {
-	Normal,
-	Low,
-	High,
-}
-
 _thread_priority_map := [Thread_Priority]i32{
 	.Normal = 0,
 	.Low = -2,
 	.High = +2,
 };
 
-create :: proc(procedure: Thread_Proc, priority := Thread_Priority.Normal) -> ^Thread {
+_create :: proc(procedure: Thread_Proc, priority := Thread_Priority.Normal) -> ^Thread {
 	win32_thread_id: win32.DWORD;
 
 	__windows_thread_entry_proc :: proc "stdcall" (t_: rawptr) -> win32.DWORD {
@@ -43,7 +38,7 @@ create :: proc(procedure: Thread_Proc, priority := Thread_Priority.Normal) -> ^T
 			}
 		}
 
-		sync.atomic_store(&t.done, true, .Sequentially_Consistent);
+		sync.atomic_store(&t.done, true);
 		return 0;
 	}
 
@@ -70,18 +65,18 @@ create :: proc(procedure: Thread_Proc, priority := Thread_Priority.Normal) -> ^T
 	return thread;
 }
 
-start :: proc(using thread: ^Thread) {
-	win32.ResumeThread(win32_thread);
+_start :: proc(thread: ^Thread) {
+	win32.ResumeThread(thread.win32_thread);
 }
 
-is_done :: proc(using thread: ^Thread) -> bool {
+_is_done :: proc(using thread: ^Thread) -> bool {
 	// NOTE(tetra, 2019-10-31): Apparently using wait_for_single_object and
 	// checking if it didn't time out immediately, is not good enough,
 	// so we do it this way instead.
-	return sync.atomic_load(&done, .Sequentially_Consistent);
+	return sync.atomic_load(&done);
 }
 
-join :: proc(using thread: ^Thread) {
+_join :: proc(using thread: ^Thread) {
 	if win32_thread != win32.INVALID_HANDLE {
 		win32.WaitForSingleObject(win32_thread, win32.INFINITE);
 		win32.CloseHandle(win32_thread);
@@ -89,7 +84,7 @@ join :: proc(using thread: ^Thread) {
 	}
 }
 
-join_multiple :: proc(threads: ..^Thread) {
+_join_multiple :: proc(threads: ..^Thread) {
 	MAXIMUM_WAIT_OBJECTS :: 64;
 
 	handles: [MAXIMUM_WAIT_OBJECTS]win32.HANDLE;
@@ -113,16 +108,16 @@ join_multiple :: proc(threads: ..^Thread) {
 	}
 }
 
-destroy :: proc(thread: ^Thread) {
-	join(thread);
+_destroy :: proc(thread: ^Thread) {
+	_join(thread);
 	free(thread, thread.creation_allocator);
 }
 
-terminate :: proc(using thread : ^Thread, exit_code: u32) {
-	win32.TerminateThread(win32_thread, exit_code);
+_terminate :: proc(using thread : ^Thread, exit_code: int) {
+	win32.TerminateThread(win32_thread, u32(exit_code));
 }
 
-yield :: proc() {
+_yield :: proc() {
 	win32.SwitchToThread();
 }
 

+ 2 - 0
src/build_settings.cpp

@@ -127,6 +127,7 @@ char const *odin_command_strings[32] = {
 	"query",
 	"doc",
 	"version",
+	"test",
 };
 
 
@@ -134,6 +135,7 @@ char const *odin_command_strings[32] = {
 enum CmdDocFlag : u32 {
 	CmdDocFlag_Short       = 1<<0,
 	CmdDocFlag_AllPackages = 1<<1,
+	CmdDocFlag_DocFormat   = 1<<2,
 };
 
 

+ 4 - 0
src/check_decl.cpp

@@ -690,6 +690,10 @@ void check_proc_decl(CheckerContext *ctx, Entity *e, DeclInfo *d) {
 	if (ac.test) {
 		e->flags |= EntityFlag_Test;
 	}
+	if (ac.set_cold) {
+		e->flags |= EntityFlag_Cold;
+	}
+
 	e->Procedure.is_export = ac.is_export;
 	e->deprecated_message = ac.deprecated_message;
 	ac.link_name = handle_link_name(ctx, e->token, ac.link_name, ac.link_prefix);

+ 16 - 16
src/check_expr.cpp

@@ -3515,7 +3515,7 @@ Entity *check_selector(CheckerContext *c, Operand *operand, Ast *node, Type *typ
 				if (entity->kind == Entity_Builtin) {
 					// NOTE(bill): Builtin's are in the universal scope which is part of every scopes hierarchy
 					// This means that we should just ignore the found result through it
-					allow_builtin = entity->scope == import_scope;
+					allow_builtin = entity->scope == import_scope || entity->scope != builtin_pkg->scope;
 				} else if ((entity->scope->flags&ScopeFlag_Global) == ScopeFlag_Global && (import_scope->flags&ScopeFlag_Global) == 0) {
 					is_declared = false;
 				}
@@ -8175,24 +8175,24 @@ ExprKind check_call_expr(CheckerContext *c, Operand *operand, Ast *call, Ast *pr
 	}
 
 	switch (inlining) {
-		case ProcInlining_inline: {
-			if (proc != nullptr) {
-				Entity *e = entity_from_expr(proc);
-				if (e != nullptr && e->kind == Entity_Procedure) {
-					DeclInfo *decl = e->decl_info;
-					if (decl->proc_lit) {
-						ast_node(pl, ProcLit, decl->proc_lit);
-						if (pl->inlining == ProcInlining_no_inline) {
-							error(call, "'inline' cannot be applied to a procedure that has be marked as 'no_inline'");
-						}
+	case ProcInlining_inline: {
+		if (proc != nullptr) {
+			Entity *e = entity_from_expr(proc);
+			if (e != nullptr && e->kind == Entity_Procedure) {
+				DeclInfo *decl = e->decl_info;
+				if (decl->proc_lit) {
+					ast_node(pl, ProcLit, decl->proc_lit);
+					if (pl->inlining == ProcInlining_no_inline) {
+						error(call, "'inline' cannot be applied to a procedure that has be marked as 'no_inline'");
 					}
 				}
 			}
-			break;
 		}
+		break;
+	}
 
-		case ProcInlining_no_inline:
-			break;
+	case ProcInlining_no_inline:
+		break;
 	}
 
 	operand->expr = call;
@@ -11019,10 +11019,10 @@ gbString write_expr_to_string(gbString str, Ast *node, bool shorthand) {
 	case_ast_node(ce, CallExpr, node);
 		switch (ce->inlining) {
 		case ProcInlining_inline:
-			str = gb_string_appendc(str, "inline ");
+			str = gb_string_appendc(str, "#force_inline ");
 			break;
 		case ProcInlining_no_inline:
-			str = gb_string_appendc(str, "no_inline ");
+			str = gb_string_appendc(str, "#force_no_inline ");
 			break;
 		}
 

+ 3 - 1
src/check_type.cpp

@@ -731,6 +731,7 @@ void check_enum_type(CheckerContext *ctx, Type *enum_type, Type *named_type, Ast
 		Ast *field = et->fields[i];
 		Ast *ident = nullptr;
 		Ast *init = nullptr;
+		u32 entity_flags = 0;
 		if (field->kind == Ast_FieldValue) {
 			ast_node(fv, FieldValue, field);
 			if (fv->field == nullptr || fv->field->kind != Ast_Ident) {
@@ -764,6 +765,7 @@ void check_enum_type(CheckerContext *ctx, Type *enum_type, Type *named_type, Ast
 			}
 		} else {
 			iota = exact_binary_operator_value(Token_Add, iota, exact_value_i64(1));
+			entity_flags |= EntityConstantFlag_ImplicitEnumValue;
 		}
 
 
@@ -800,6 +802,7 @@ void check_enum_type(CheckerContext *ctx, Type *enum_type, Type *named_type, Ast
 		e->identifier = ident;
 		e->flags |= EntityFlag_Visited;
 		e->state = EntityState_Resolved;
+		e->Constant.flags |= entity_flags;
 
 		if (scope_lookup_current(ctx->scope, name) != nullptr) {
 			error(ident, "'%.*s' is already declared in this enumeration", LIT(name));
@@ -2461,7 +2464,6 @@ bool check_procedure_type(CheckerContext *ctx, Type *type, Ast *proc_type_node,
 	type->Proc.specialization_count = specialization_count;
 	type->Proc.diverging            = pt->diverging;
 	type->Proc.optional_ok          = optional_ok;
-	type->Proc.tags                 = pt->tags;
 
 	if (param_count > 0) {
 		Entity *end = params->Tuple.variables[param_count-1];

+ 12 - 0
src/checker.cpp

@@ -2562,6 +2562,18 @@ DECL_ATTRIBUTE_PROC(proc_decl_attribute) {
 			error(elem, "Expected a boolean value for '%.*s'", LIT(name));
 		}
 		return true;
+	} else if (name == "cold") {
+		if (value == nullptr) {
+			ac->set_cold = true;
+		} else {
+			ExactValue ev = check_decl_attribute_value(c, value);
+			if (ev.kind == ExactValue_Bool) {
+				ac->set_cold = ev.value_bool;
+			} else {
+				error(elem, "Expected a boolean value for '%.*s' or no value whatsoever", LIT(name));
+			}
+		}
+		return true;
 	}
 	return false;
 }

+ 1 - 0
src/checker.hpp

@@ -105,6 +105,7 @@ struct AttributeContext {
 	bool    has_disabled_proc;
 	bool    disabled_proc;
 	bool    test;
+	bool    set_cold;
 	String  link_name;
 	String  link_prefix;
 	isize   init_expr_list_count;

+ 62 - 15
src/docs.cpp

@@ -34,9 +34,17 @@ GB_COMPARE_PROC(cmp_entities_for_printing) {
 	Entity *x = *cast(Entity **)a;
 	Entity *y = *cast(Entity **)b;
 	int res = 0;
-	res = string_compare(x->pkg->name, y->pkg->name);
-	if (res != 0) {
-		return res;
+	if (x->pkg != y->pkg) {
+		if (x->pkg == nullptr) {
+			return -1;
+		}
+		if (y->pkg == nullptr) {
+			return +1;
+		}
+		res = string_compare(x->pkg->name, y->pkg->name);
+		if (res != 0) {
+			return res;
+		}
 	}
 	int ox = print_entity_kind_ordering[x->kind];
 	int oy = print_entity_kind_ordering[y->kind];
@@ -56,6 +64,9 @@ GB_COMPARE_PROC(cmp_ast_package_by_name) {
 	return string_compare(x->name, y->name);
 }
 
+#include "docs_format.cpp"
+#include "docs_writer.cpp"
+
 void print_doc_line(i32 indent, char const *fmt, ...) {
 	while (indent --> 0) {
 		gb_printf("\t");
@@ -297,23 +308,59 @@ void print_doc_package(CheckerInfo *info, AstPackage *pkg) {
 void generate_documentation(Checker *c) {
 	CheckerInfo *info = &c->info;
 
-	auto pkgs = array_make<AstPackage *>(permanent_allocator(), 0, info->packages.entries.count);
-	for_array(i, info->packages.entries) {
-		AstPackage *pkg = info->packages.entries[i].value;
-		if (build_context.cmd_doc_flags & CmdDocFlag_AllPackages) {
-			array_add(&pkgs, pkg);
+	if (build_context.cmd_doc_flags & CmdDocFlag_DocFormat) {
+		String init_fullpath = c->parser->init_fullpath;
+		String output_name = {};
+		String output_base = {};
+
+		if (build_context.out_filepath.len == 0) {
+			output_name = remove_directory_from_path(init_fullpath);
+			output_name = remove_extension_from_path(output_name);
+			output_name = string_trim_whitespace(output_name);
+			if (output_name.len == 0) {
+				output_name = info->init_scope->pkg->name;
+			}
+			output_base = output_name;
 		} else {
-			if (pkg->kind == Package_Init) {
-				array_add(&pkgs, pkg);
-			} else if (pkg->is_extra) {
+			output_name = build_context.out_filepath;
+			output_name = string_trim_whitespace(output_name);
+			if (output_name.len == 0) {
+				output_name = info->init_scope->pkg->name;
+			}
+			isize pos = string_extension_position(output_name);
+			if (pos < 0) {
+				output_base = output_name;
+			} else {
+				output_base = substring(output_name, 0, pos);
+			}
+		}
+
+		output_base = path_to_full_path(permanent_allocator(), output_base);
+
+		gbString output_file_path = gb_string_make_length(heap_allocator(), output_base.text, output_base.len);
+		output_file_path = gb_string_appendc(output_file_path, ".odin-doc");
+		defer (gb_string_free(output_file_path));
+
+		odin_doc_write(info, output_file_path);
+	} else {
+		auto pkgs = array_make<AstPackage *>(permanent_allocator(), 0, info->packages.entries.count);
+		for_array(i, info->packages.entries) {
+			AstPackage *pkg = info->packages.entries[i].value;
+			if (build_context.cmd_doc_flags & CmdDocFlag_AllPackages) {
 				array_add(&pkgs, pkg);
+			} else {
+				if (pkg->kind == Package_Init) {
+					array_add(&pkgs, pkg);
+				} else if (pkg->is_extra) {
+					array_add(&pkgs, pkg);
+				}
 			}
 		}
-	}
 
-	gb_sort_array(pkgs.data, pkgs.count, cmp_ast_package_by_name);
+		gb_sort_array(pkgs.data, pkgs.count, cmp_ast_package_by_name);
 
-	for_array(i, pkgs) {
-		print_doc_package(info, pkgs[i]);
+		for_array(i, pkgs) {
+			print_doc_package(info, pkgs[i]);
+		}
 	}
 }

+ 216 - 0
src/docs_format.cpp

@@ -0,0 +1,216 @@
+#define OdinDocHeader_MagicString "odindoc\0"
+
+template <typename T>
+struct OdinDocArray {
+	u32 offset;
+	u32 length;
+};
+
+using OdinDocString = OdinDocArray<u8>;
+
+struct OdinDocVersionType {
+	u8 major, minor, patch;
+	u8 pad0;
+};
+
+#define OdinDocVersionType_Major 0
+#define OdinDocVersionType_Minor 1
+#define OdinDocVersionType_Patch 0
+
+struct OdinDocHeaderBase {
+	u8                 magic[8];
+	u32                padding0;
+	OdinDocVersionType version;
+	u32                total_size;
+	u32                header_size;
+	u32                hash; // after header
+};
+
+template <typename T>
+Slice<T> from_array(OdinDocHeaderBase *base, OdinDocArray<T> const &a) {
+	Slice<T> s = {};
+	s.data  = cast(T *)(cast(uintptr)base + cast(uintptr)a.offset);
+	s.count = cast(isize)a.length;
+	return s;
+}
+
+String from_string(OdinDocHeaderBase *base, OdinDocString const &s) {
+	String str = {};
+	str.text = cast(u8 *)(cast(uintptr)base + cast(uintptr)s.offset);
+	str.len  = cast(isize)s.length;
+	return str;
+}
+
+typedef u32 OdinDocFileIndex;
+typedef u32 OdinDocPkgIndex;
+typedef u32 OdinDocEntityIndex;
+typedef u32 OdinDocTypeIndex;
+
+struct OdinDocFile {
+	OdinDocPkgIndex pkg;
+	OdinDocString   name;
+};
+
+struct OdinDocPosition {
+	OdinDocFileIndex file;
+	u32              line;
+	u32              column;
+	u32              offset;
+};
+
+enum OdinDocTypeKind : u32 {
+	OdinDocType_Invalid          = 0,
+	OdinDocType_Basic            = 1,
+	OdinDocType_Named            = 2,
+	OdinDocType_Generic          = 3,
+	OdinDocType_Pointer          = 4,
+	OdinDocType_Array            = 5,
+	OdinDocType_EnumeratedArray  = 6,
+	OdinDocType_Slice            = 7,
+	OdinDocType_DynamicArray     = 8,
+	OdinDocType_Map              = 9,
+	OdinDocType_Struct           = 10,
+	OdinDocType_Union            = 11,
+	OdinDocType_Enum             = 12,
+	OdinDocType_Tuple            = 13,
+	OdinDocType_Proc             = 14,
+	OdinDocType_BitSet           = 15,
+	OdinDocType_SimdVector       = 16,
+	OdinDocType_SOAStructFixed   = 17,
+	OdinDocType_SOAStructSlice   = 18,
+	OdinDocType_SOAStructDynamic = 19,
+	OdinDocType_RelativePointer  = 20,
+	OdinDocType_RelativeSlice    = 21,
+};
+
+enum OdinDocTypeFlag_Basic : u32 {
+	OdinDocTypeFlag_Basic_untyped = 1<<1,
+};
+
+enum OdinDocTypeFlag_Struct : u32 {
+	OdinDocTypeFlag_Struct_polymorphic = 1<<0,
+	OdinDocTypeFlag_Struct_packed      = 1<<1,
+	OdinDocTypeFlag_Struct_raw_union   = 1<<2,
+};
+
+enum OdinDocTypeFlag_Union : u32 {
+	OdinDocTypeFlag_Union_polymorphic = 1<<0,
+	OdinDocTypeFlag_Union_no_nil      = 1<<1,
+	OdinDocTypeFlag_Union_maybe       = 1<<2,
+};
+
+enum OdinDocTypeFlag_Proc : u32 {
+	OdinDocTypeFlag_Proc_polymorphic = 1<<0,
+	OdinDocTypeFlag_Proc_diverging   = 1<<1,
+	OdinDocTypeFlag_Proc_optional_ok = 1<<2,
+	OdinDocTypeFlag_Proc_variadic    = 1<<3,
+	OdinDocTypeFlag_Proc_c_vararg    = 1<<4,
+};
+
+enum OdinDocTypeFlag_BitSet : u32 {
+	OdinDocTypeFlag_BitSet_Range          = 1<<1,
+	OdinDocTypeFlag_BitSet_OpLt           = 1<<2,
+	OdinDocTypeFlag_BitSet_OpLtEq         = 1<<3,
+	OdinDocTypeFlag_BitSet_UnderlyingType = 1<<4,
+};
+
+enum OdinDocTypeFlag_SimdVector : u32 {
+	OdinDocTypeFlag_BitSet_x86_mmx = 1<<1,
+};
+
+enum {
+	// constants
+	OdinDocType_ElemsCap = 4,
+};
+
+struct OdinDocType {
+	OdinDocTypeKind kind;
+	u32             flags;
+	OdinDocString   name;
+	OdinDocString   custom_align;
+
+	// Used by some types
+	u32 elem_count_len;
+	i64 elem_counts[OdinDocType_ElemsCap];
+
+	// Each of these is esed by some types, not all
+	OdinDocString calling_convention;
+	OdinDocArray<OdinDocTypeIndex> types;
+	OdinDocArray<OdinDocEntityIndex> entities;
+	OdinDocTypeIndex polmorphic_params;
+	OdinDocArray<OdinDocString> where_clauses;
+};
+
+struct OdinDocAttribute {
+	OdinDocString name;
+	OdinDocString value;
+};
+
+enum OdinDocEntityKind : u32 {
+	OdinDocEntity_Invalid     = 0,
+	OdinDocEntity_Constant    = 1,
+	OdinDocEntity_Variable    = 2,
+	OdinDocEntity_TypeName    = 3,
+	OdinDocEntity_Procedure   = 4,
+	OdinDocEntity_ProcGroup   = 5,
+	OdinDocEntity_ImportName  = 6,
+	OdinDocEntity_LibraryName = 7,
+};
+
+enum OdinDocEntityFlag : u32 {
+	OdinDocEntityFlag_Foreign = 1<<0,
+	OdinDocEntityFlag_Export  = 1<<1,
+
+	OdinDocEntityFlag_Param_Using    = 1<<2,
+	OdinDocEntityFlag_Param_Const    = 1<<3,
+	OdinDocEntityFlag_Param_AutoCast = 1<<4,
+	OdinDocEntityFlag_Param_Ellipsis = 1<<5,
+	OdinDocEntityFlag_Param_CVararg  = 1<<6,
+	OdinDocEntityFlag_Param_NoAlias  = 1<<7,
+
+	OdinDocEntityFlag_Type_Alias = 1<<8,
+
+	OdinDocEntityFlag_Var_Thread_Local = 1<<9,
+};
+
+struct OdinDocEntity {
+	OdinDocEntityKind  kind;
+	u32                flags;
+	OdinDocPosition    pos;
+	OdinDocString      name;
+	OdinDocTypeIndex   type;
+	OdinDocString      init_string;
+	u32                reserved_for_init;
+	OdinDocString      comment;
+	OdinDocString      docs;
+	OdinDocEntityIndex foreign_library;
+	OdinDocString      link_name;
+	OdinDocArray<OdinDocAttribute> attributes;
+	OdinDocArray<OdinDocEntityIndex> grouped_entities; // Procedure Groups
+	OdinDocArray<OdinDocString>      where_clauses; // Procedures
+};
+
+enum OdinDocPkgFlags : u32 {
+	OdinDocPkgFlag_Builtin = 1<<0,
+	OdinDocPkgFlag_Runtime = 1<<1,
+	OdinDocPkgFlag_Init    = 1<<2,
+};
+
+struct OdinDocPkg {
+	OdinDocString fullpath;
+	OdinDocString name;
+	u32           flags;
+	OdinDocString docs;
+	OdinDocArray<OdinDocFileIndex>   files;
+	OdinDocArray<OdinDocEntityIndex> entities;
+};
+
+struct OdinDocHeader {
+	OdinDocHeaderBase base;
+
+	OdinDocArray<OdinDocFile>   files;
+	OdinDocArray<OdinDocPkg>    pkgs;
+	OdinDocArray<OdinDocEntity> entities;
+	OdinDocArray<OdinDocType>   types;
+};
+

+ 1092 - 0
src/docs_writer.cpp

@@ -0,0 +1,1092 @@
+
+template <typename T>
+struct OdinDocWriterItemTracker {
+	isize len;
+	isize cap;
+	isize offset;
+};
+
+enum OdinDocWriterState {
+	OdinDocWriterState_Preparing,
+	OdinDocWriterState_Writing,
+};
+
+char const* OdinDocWriterState_strings[] {
+	"preparing",
+	"writing  ",
+};
+
+struct OdinDocWriter {
+	CheckerInfo *info;
+	OdinDocWriterState state;
+
+	void *data;
+	isize data_len;
+	OdinDocHeader *header;
+
+	StringMap<OdinDocString> string_cache;
+
+	Map<OdinDocFileIndex>   file_cache;      // Key: AstFile *
+	Map<OdinDocPkgIndex>    pkg_cache;       // Key: AstPackage *
+	Map<OdinDocEntityIndex> entity_cache;    // Key: Entity *
+	Map<Entity *>           entity_id_cache; // Key: OdinDocEntityIndex
+	Map<OdinDocTypeIndex>   type_cache;      // Key: Type *
+	Map<Type *>             type_id_cache;   // Key: OdinDocTypeIndex
+
+	OdinDocWriterItemTracker<OdinDocFile>   files;
+	OdinDocWriterItemTracker<OdinDocPkg>    pkgs;
+	OdinDocWriterItemTracker<OdinDocEntity> entities;
+	OdinDocWriterItemTracker<OdinDocType>   types;
+
+	OdinDocWriterItemTracker<u8> strings;
+	OdinDocWriterItemTracker<u8> blob;
+};
+
+OdinDocEntityIndex odin_doc_add_entity(OdinDocWriter *w, Entity *e);
+OdinDocTypeIndex odin_doc_type(OdinDocWriter *w, Type *type);
+
+template <typename T>
+void odin_doc_writer_item_tracker_init(OdinDocWriterItemTracker<T> *t, isize size) {
+	t->len = size;
+	t->cap = size;
+}
+
+
+void odin_doc_writer_prepare(OdinDocWriter *w) {
+	w->state = OdinDocWriterState_Preparing;
+
+	gbAllocator a = heap_allocator();
+	string_map_init(&w->string_cache, a);
+
+	map_init(&w->file_cache, a);
+	map_init(&w->pkg_cache, a);
+	map_init(&w->entity_cache, a);
+	map_init(&w->entity_id_cache, a);
+	map_init(&w->type_cache, a);
+	map_init(&w->type_id_cache, a);
+
+	odin_doc_writer_item_tracker_init(&w->files,    1);
+	odin_doc_writer_item_tracker_init(&w->pkgs,     1);
+	odin_doc_writer_item_tracker_init(&w->entities, 1);
+	odin_doc_writer_item_tracker_init(&w->types,    1);
+	odin_doc_writer_item_tracker_init(&w->strings, 16);
+	odin_doc_writer_item_tracker_init(&w->blob,    16);
+}
+
+
+void odin_doc_writer_destroy(OdinDocWriter *w) {
+	gb_free(heap_allocator(), w->data);
+
+	string_map_destroy(&w->string_cache);
+	map_destroy(&w->file_cache);
+	map_destroy(&w->pkg_cache);
+	map_destroy(&w->entity_cache);
+	map_destroy(&w->entity_id_cache);
+	map_destroy(&w->type_cache);
+	map_destroy(&w->type_id_cache);
+}
+
+
+
+template <typename T>
+void odin_doc_writer_tracker_size(isize *offset, OdinDocWriterItemTracker<T> *t, isize alignment=1) {
+	isize size = t->cap*gb_size_of(T);
+	isize align = gb_max(gb_align_of(T), alignment);
+	*offset = align_formula_isize(*offset, align);
+	t->offset = *offset;
+	*offset += size;
+}
+
+isize odin_doc_writer_calc_total_size(OdinDocWriter *w) {
+	isize total_size = gb_size_of(OdinDocHeader);
+	odin_doc_writer_tracker_size(&total_size, &w->files);
+	odin_doc_writer_tracker_size(&total_size, &w->pkgs);
+	odin_doc_writer_tracker_size(&total_size, &w->entities);
+	odin_doc_writer_tracker_size(&total_size, &w->types);
+	odin_doc_writer_tracker_size(&total_size, &w->strings, 16);
+	odin_doc_writer_tracker_size(&total_size, &w->blob, 16);
+	return total_size;
+}
+
+void odin_doc_writer_start_writing(OdinDocWriter *w) {
+	w->state = OdinDocWriterState_Writing;
+
+	string_map_clear(&w->string_cache);
+	map_clear(&w->file_cache);
+	map_clear(&w->pkg_cache);
+	map_clear(&w->entity_cache);
+	map_clear(&w->entity_id_cache);
+	map_clear(&w->type_cache);
+	map_clear(&w->type_id_cache);
+
+	isize total_size = odin_doc_writer_calc_total_size(w);
+	total_size = align_formula_isize(total_size, 8);
+	w->data = gb_alloc_align(heap_allocator(), total_size, 8);
+	w->data_len = total_size;
+	w->header = cast(OdinDocHeader *)w->data;
+}
+
+u32 hash_data_after_header(OdinDocHeaderBase *base, void *data, isize data_len) {
+	u8 *start = cast(u8 *)data;
+	u8 *end = start + base->total_size;
+	start += base->header_size;
+
+	u32 h = 0x811c9dc5;
+	for (u8 *b = start; b != end; b++) {
+		h = (h ^ cast(u32)*b) * 0x01000193;
+	}
+	return h;
+}
+
+
+template <typename T>
+void odin_doc_writer_assign_tracker(OdinDocArray<T> *array, OdinDocWriterItemTracker<T> const &t) {
+	array->offset = cast(u32)t.offset;
+	array->length = cast(u32)t.len;
+}
+
+
+void odin_doc_writer_end_writing(OdinDocWriter *w) {
+	OdinDocHeader *h = w->header;
+
+	gb_memmove(h->base.magic, OdinDocHeader_MagicString, gb_strlen(OdinDocHeader_MagicString));
+	h->base.version.major = OdinDocVersionType_Major;
+	h->base.version.minor = OdinDocVersionType_Minor;
+	h->base.version.patch = OdinDocVersionType_Patch;
+	h->base.total_size    = cast(u32)w->data_len;
+	h->base.header_size   = gb_size_of(*h);
+	h->base.hash = hash_data_after_header(&h->base, w->data, w->data_len);
+
+	odin_doc_writer_assign_tracker(&h->files,    w->files);
+	odin_doc_writer_assign_tracker(&h->pkgs,     w->pkgs);
+	odin_doc_writer_assign_tracker(&h->entities, w->entities);
+	odin_doc_writer_assign_tracker(&h->types,    w->types);
+}
+
+template <typename T>
+u32 odin_doc_write_item(OdinDocWriter *w, OdinDocWriterItemTracker<T> *t, T const *item, T **dst=nullptr) {
+	if (w->state == OdinDocWriterState_Preparing) {
+		t->cap += 1;
+		if (dst) *dst = nullptr;
+		return 0;
+	} else {
+		GB_ASSERT_MSG(t->len < t->cap, "%td < %td", t->len, t->cap);
+		isize item_index = t->len++;
+		uintptr data = cast(uintptr)w->data + cast(uintptr)(t->offset + gb_size_of(T)*item_index);
+		if (item) {
+			gb_memmove(cast(T *)data, item, gb_size_of(T));
+		}
+		if (dst) *dst = cast(T *)data;
+
+		return cast(u32)item_index;
+	}
+}
+
+template <typename T>
+T *odin_doc_get_item(OdinDocWriter *w, OdinDocWriterItemTracker<T> *t, u32 index) {
+	if (w->state != OdinDocWriterState_Writing) {
+		return nullptr;
+	}
+	GB_ASSERT(index < t->len);
+	uintptr data = cast(uintptr)w->data + cast(uintptr)(t->offset + gb_size_of(T)*index);
+	return cast(T *)data;
+}
+
+OdinDocString odin_doc_write_string_without_cache(OdinDocWriter *w, String const &str) {
+	OdinDocString res = {};
+
+	if (w->state == OdinDocWriterState_Preparing) {
+		w->strings.cap += str.len+1;
+	} else {
+		GB_ASSERT_MSG(w->strings.len+str.len+1 <= w->strings.cap, "%td <= %td", w->strings.len+str.len, w->strings.cap);
+
+		isize offset = w->strings.offset + w->strings.len;
+		u8 *data = cast(u8 *)w->data + offset;
+		gb_memmove(data, str.text, str.len);
+		data[str.len] = 0;
+		w->strings.len += str.len+1;
+		res.offset = cast(u32)offset;
+		res.length = cast(u32)str.len;
+	}
+
+	return res;
+}
+
+OdinDocString odin_doc_write_string(OdinDocWriter *w, String const &str) {
+	OdinDocString *c = string_map_get(&w->string_cache, str);
+	if (c != nullptr) {
+		if (w->state == OdinDocWriterState_Writing) {
+			GB_ASSERT(from_string(&w->header->base, *c) == str);
+		}
+		return *c;
+	}
+
+	OdinDocString res = odin_doc_write_string_without_cache(w, str);
+
+	string_map_set(&w->string_cache, str, res);
+
+	return res;
+}
+
+
+
+template <typename T>
+OdinDocArray<T> odin_write_slice(OdinDocWriter *w, T *data, isize len) {
+	GB_ASSERT(gb_align_of(T) <= 4);
+	if (len <= 0) {
+		return {0, 0};
+	}
+	isize alignment = 4;
+
+	if (w->state == OdinDocWriterState_Preparing) {
+		w->blob.cap = align_formula_isize(w->blob.cap, alignment);
+		w->blob.cap += len * gb_size_of(T);
+		return {0, 0};
+	}
+
+	w->blob.len = align_formula_isize(w->blob.len, alignment);
+
+	isize offset = w->blob.offset + w->blob.len;
+	u8 *dst = cast(u8 *)w->data + offset;
+	gb_memmove(dst, data, len*gb_size_of(T));
+
+	w->blob.len += len * gb_size_of(T);
+
+	return {cast(u32)offset, cast(u32)len};
+}
+
+
+template <typename T>
+OdinDocArray<T> odin_write_item_as_slice(OdinDocWriter *w, T data) {
+	return odin_write_slice(w, &data, 1);
+}
+
+
+OdinDocPosition odin_doc_token_pos_cast(OdinDocWriter *w, TokenPos const &pos) {
+	OdinDocFileIndex file_index = 0;
+	if (pos.file_id != 0) {
+		String file_path = get_file_path_string(pos.file_id);
+		if (file_path != "") {
+			AstFile **found = string_map_get(&w->info->files, file_path);
+			GB_ASSERT(found != nullptr);
+			AstFile *file = *found;
+			OdinDocFileIndex *file_index_found = map_get(&w->file_cache, hash_pointer(file));
+			GB_ASSERT(file_index_found != nullptr);
+			file_index = *file_index_found;
+		}
+	}
+
+	OdinDocPosition doc_pos = {};
+	doc_pos.file   = file_index;
+	doc_pos.line   = cast(u32)pos.line;
+	doc_pos.column = cast(u32)pos.column;
+	doc_pos.offset = cast(u32)pos.offset;
+	return doc_pos;
+}
+
+bool odin_doc_append_comment_group_string(Array<u8> *buf, CommentGroup *g) {
+	if (g == nullptr) {
+		return false;
+	}
+	isize len = 0;
+	for_array(i, g->list) {
+		String comment = g->list[i].string;
+		len += comment.len;
+		len += 1; // for \n
+	}
+	if (len <= g->list.count) {
+		return false;
+	}
+
+	isize count = 0;
+	for_array(i, g->list) {
+		String comment = g->list[i].string;
+		String original_comment = comment;
+
+		bool slash_slash = comment[1] == '/';
+		bool slash_star = comment[1] == '*';
+		if (comment[1] == '/') {
+			comment.text += 2;
+			comment.len  -= 2;
+		} else if (comment[1] == '*') {
+			comment.text += 2;
+			comment.len  -= 4;
+		}
+
+		// Ignore the first space
+		if (comment.len > 0 && comment[0] == ' ') {
+			comment.text += 1;
+			comment.len  -= 1;
+		}
+
+		if (slash_slash) {
+			if (string_starts_with(comment, str_lit("+"))) {
+				continue;
+			}
+			if (string_starts_with(comment, str_lit("@("))) {
+				continue;
+			}
+		}
+
+		if (slash_slash) {
+			array_add_elems(buf, comment.text, comment.len);
+			array_add(buf, cast(u8)'\n');
+			count += 1;
+		} else {
+			isize pos = 0;
+			for (; pos < comment.len; pos++) {
+				isize end = pos;
+				for (; end < comment.len; end++) {
+					if (comment[end] == '\n') {
+						break;
+					}
+				}
+				String line = substring(comment, pos, end);
+				pos = end+1;
+				String trimmed_line = string_trim_whitespace(line);
+				if (trimmed_line.len == 0) {
+					if (count == 0) {
+						continue;
+					}
+				}
+				/*
+				 * Remove comments with
+				 * styles
+				 * like this
+				 */
+				if (string_starts_with(line, str_lit("* "))) {
+					line = substring(line, 2, line.len);
+				}
+
+				array_add_elems(buf, line.text, line.len);
+				array_add(buf, cast(u8)'\n');
+				count += 1;
+			}
+		}
+	}
+
+	if (count > 0) {
+		array_add(buf, cast(u8)'\n');
+		return true;
+	}
+	return false;
+}
+
+OdinDocString odin_doc_pkg_doc_string(OdinDocWriter *w, AstPackage *pkg) {
+	if (pkg == nullptr) {
+		return {};
+	}
+	auto buf = array_make<u8>(permanent_allocator(), 0, 0); // Minor leak
+
+	for_array(i, pkg->files) {
+		AstFile *f = pkg->files[i];
+		if (f->pkg_decl) {
+			GB_ASSERT(f->pkg_decl->kind == Ast_PackageDecl);
+			odin_doc_append_comment_group_string(&buf, f->pkg_decl->PackageDecl.docs);
+		}
+	}
+
+	return odin_doc_write_string_without_cache(w, make_string(buf.data, buf.count));
+}
+
+OdinDocString odin_doc_comment_group_string(OdinDocWriter *w, CommentGroup *g) {
+	if (g == nullptr) {
+		return {};
+	}
+	auto buf = array_make<u8>(permanent_allocator(), 0, 0); // Minor leak
+
+	odin_doc_append_comment_group_string(&buf, g);
+
+	return odin_doc_write_string_without_cache(w, make_string(buf.data, buf.count));
+}
+
+OdinDocString odin_doc_expr_string(OdinDocWriter *w, Ast *expr) {
+	if (expr == nullptr) {
+		return {};
+	}
+	gbString s = write_expr_to_string( // Minor leak
+		gb_string_make(permanent_allocator(), ""),
+		expr,
+		build_context.cmd_doc_flags & CmdDocFlag_Short
+	);
+
+	return odin_doc_write_string(w, make_string(cast(u8 *)s, gb_string_length(s)));
+}
+
+OdinDocArray<OdinDocAttribute> odin_doc_attributes(OdinDocWriter *w, Array<Ast *> const &attributes) {
+	isize count = 0;
+	for_array(i, attributes) {
+		Ast *attr = attributes[i];
+		if (attr->kind != Ast_Attribute) continue;
+		count += attr->Attribute.elems.count;
+	};
+
+	auto attribs = array_make<OdinDocAttribute>(heap_allocator(), 0, count);
+	defer (array_free(&attribs));
+
+	for_array(i, attributes) {
+		Ast *attr = attributes[i];
+		if (attr->kind != Ast_Attribute) continue;
+		for_array(j, attr->Attribute.elems) {
+			Ast *elem = attr->Attribute.elems[j];
+			String name = {};
+			Ast *value = nullptr;
+			switch (elem->kind) {
+			case_ast_node(i, Ident, elem);
+				name = i->token.string;
+			case_end;
+			case_ast_node(i, Implicit, elem);
+				name = i->string;
+			case_end;
+			case_ast_node(fv, FieldValue, elem);
+				if (fv->field->kind == Ast_Ident) {
+					name = fv->field->Ident.token.string;
+				} else if (fv->field->kind == Ast_Implicit) {
+					name = fv->field->Implicit.string;
+				}
+				value = fv->value;
+			case_end;
+			default:
+				continue;
+			}
+
+			OdinDocAttribute doc_attrib = {};
+			doc_attrib.name = odin_doc_write_string(w, name);
+			doc_attrib.value = odin_doc_expr_string(w, value);
+			array_add(&attribs, doc_attrib);
+		}
+	}
+	return odin_write_slice(w, attribs.data, attribs.count);
+}
+
+OdinDocArray<OdinDocString> odin_doc_where_clauses(OdinDocWriter *w, Slice<Ast *> const &where_clauses) {
+	if (where_clauses.count == 0) {
+		return {};
+	}
+	auto clauses = array_make<OdinDocString>(heap_allocator(), where_clauses.count);
+	defer (array_free(&clauses));
+
+	for_array(i, where_clauses) {
+		clauses[i] = odin_doc_expr_string(w, where_clauses[i]);
+	}
+
+	return odin_write_slice(w, clauses.data, clauses.count);
+}
+
+OdinDocArray<OdinDocTypeIndex> odin_doc_type_as_slice(OdinDocWriter *w, Type *type) {
+	OdinDocTypeIndex index = odin_doc_type(w, type);
+	return odin_write_item_as_slice(w, index);
+}
+
+OdinDocArray<OdinDocEntityIndex> odin_doc_add_entity_as_slice(OdinDocWriter *w, Entity *e) {
+	OdinDocEntityIndex index = odin_doc_add_entity(w, e);
+	return odin_write_item_as_slice(w, index);
+}
+
+OdinDocTypeIndex odin_doc_type(OdinDocWriter *w, Type *type) {
+	if (type == nullptr) {
+		return 0;
+	}
+	OdinDocTypeIndex *found = map_get(&w->type_cache, hash_pointer(type));
+	if (found) {
+		return *found;
+	}
+	for_array(i, w->type_cache.entries) {
+		// NOTE(bill): THIS IS SLOW
+		Type *other = cast(Type *)cast(uintptr)w->type_cache.entries[i].key.key;
+		if (are_types_identical(type, other)) {
+			OdinDocTypeIndex index = w->type_cache.entries[i].value;
+			map_set(&w->type_cache, hash_pointer(type), index);
+			return index;
+		}
+	}
+
+
+	OdinDocType *dst = nullptr;
+	OdinDocType doc_type = {};
+	OdinDocTypeIndex type_index = 0;
+	type_index = odin_doc_write_item(w, &w->types, &doc_type, &dst);
+	map_set(&w->type_cache, hash_pointer(type), type_index);
+	map_set(&w->type_id_cache, hash_integer(type_index), type);
+
+	switch (type->kind) {
+	case Type_Basic:
+		doc_type.kind = OdinDocType_Basic;
+		doc_type.name = odin_doc_write_string(w, type->Basic.name);
+		if (is_type_untyped(type)) {
+			doc_type.flags |= OdinDocTypeFlag_Basic_untyped;
+		}
+		break;
+	case Type_Named:
+		doc_type.kind = OdinDocType_Named;
+		doc_type.name = odin_doc_write_string(w, type->Named.name);
+		doc_type.types = odin_doc_type_as_slice(w, base_type(type));
+		doc_type.entities = odin_doc_add_entity_as_slice(w, type->Named.type_name);
+		break;
+	case Type_Generic:
+		doc_type.kind = OdinDocType_Generic;
+		doc_type.name = odin_doc_write_string(w, type->Generic.name);
+		if (type->Generic.specialized) {
+			doc_type.types = odin_doc_type_as_slice(w, type->Generic.specialized);
+		}
+		break;
+	case Type_Pointer:
+		doc_type.kind = OdinDocType_Pointer;
+		doc_type.types = odin_doc_type_as_slice(w, type->Pointer.elem);
+		break;
+	case Type_Array:
+		doc_type.kind = OdinDocType_Array;
+		doc_type.elem_count_len = 1;
+		doc_type.elem_counts[0] = type->Array.count;
+		doc_type.types = odin_doc_type_as_slice(w, type->Array.elem);
+		break;
+	case Type_EnumeratedArray:
+		doc_type.kind = OdinDocType_EnumeratedArray;
+		doc_type.elem_count_len = 1;
+		doc_type.elem_counts[0] = type->EnumeratedArray.count;
+		{
+			OdinDocTypeIndex types[2] = {};
+			types[0] = odin_doc_type(w, type->EnumeratedArray.index);
+			types[1] = odin_doc_type(w, type->EnumeratedArray.elem);
+			doc_type.types = odin_write_slice(w, types, gb_count_of(types));
+		}
+		break;
+	case Type_Slice:
+		doc_type.kind = OdinDocType_Slice;
+		doc_type.types = odin_doc_type_as_slice(w, type->Slice.elem);
+		break;
+	case Type_DynamicArray:
+		doc_type.kind = OdinDocType_DynamicArray;
+		doc_type.types = odin_doc_type_as_slice(w, type->DynamicArray.elem);
+		break;
+	case Type_Map:
+		doc_type.kind = OdinDocType_Map;
+		{
+			OdinDocTypeIndex types[2] = {};
+			types[0] = odin_doc_type(w, type->Map.key);
+			types[1] = odin_doc_type(w, type->Map.value);
+			doc_type.types = odin_write_slice(w, types, gb_count_of(types));
+		}
+		break;
+	case Type_Struct:
+		doc_type.kind = OdinDocType_Struct;
+		if (type->Struct.soa_kind != StructSoa_None) {
+			switch (type->Struct.soa_kind) {
+			case StructSoa_Fixed:
+				doc_type.kind = OdinDocType_SOAStructFixed;
+				doc_type.elem_count_len = 1;
+				doc_type.elem_counts[0] = type->Struct.soa_count;
+				break;
+			case StructSoa_Slice:
+				doc_type.kind = OdinDocType_SOAStructSlice;
+				break;
+			case StructSoa_Dynamic:
+				doc_type.kind = OdinDocType_SOAStructDynamic;
+				break;
+			}
+			doc_type.types = odin_doc_type_as_slice(w, type->Struct.soa_elem);
+		} else {
+			if (type->Struct.is_polymorphic) { doc_type.flags |= OdinDocTypeFlag_Struct_polymorphic; }
+			if (type->Struct.is_packed)      { doc_type.flags |= OdinDocTypeFlag_Struct_packed; }
+			if (type->Struct.is_raw_union)   { doc_type.flags |= OdinDocTypeFlag_Struct_raw_union; }
+
+			auto fields = array_make<OdinDocEntityIndex>(heap_allocator(), type->Struct.fields.count);
+			defer (array_free(&fields));
+
+			for_array(i, type->Struct.fields) {
+				fields[i] = odin_doc_add_entity(w, type->Struct.fields[i]);
+			}
+
+			doc_type.entities = odin_write_slice(w, fields.data, fields.count);
+			doc_type.polmorphic_params = odin_doc_type(w, type->Struct.polymorphic_params);
+
+			if (type->Struct.node) {
+				ast_node(st, StructType, type->Struct.node);
+				if (st->align) {
+					doc_type.custom_align = odin_doc_expr_string(w, st->align);
+				}
+				doc_type.where_clauses = odin_doc_where_clauses(w, st->where_clauses);
+			}
+		}
+		break;
+	case Type_Union:
+		doc_type.kind = OdinDocType_Union;
+		if (type->Union.is_polymorphic) { doc_type.flags |= OdinDocTypeFlag_Union_polymorphic; }
+		if (type->Union.no_nil)         { doc_type.flags |= OdinDocTypeFlag_Union_no_nil; }
+		if (type->Union.maybe)          { doc_type.flags |= OdinDocTypeFlag_Union_maybe; }
+
+		{
+			auto variants = array_make<OdinDocTypeIndex>(heap_allocator(), type->Union.variants.count);
+			defer (array_free(&variants));
+
+			for_array(i, type->Union.variants) {
+				variants[i] = odin_doc_type(w, type->Union.variants[i]);
+			}
+
+			doc_type.types = odin_write_slice(w, variants.data, variants.count);
+			doc_type.polmorphic_params = odin_doc_type(w, type->Union.polymorphic_params);
+		}
+
+		if (type->Union.node) {
+			ast_node(ut, UnionType, type->Union.node);
+			if (ut->align) {
+				doc_type.custom_align = odin_doc_expr_string(w, ut->align);
+			}
+			doc_type.where_clauses = odin_doc_where_clauses(w, ut->where_clauses);
+		}
+		break;
+	case Type_Enum:
+		doc_type.kind = OdinDocType_Enum;
+		{
+			auto fields = array_make<OdinDocEntityIndex>(heap_allocator(), type->Enum.fields.count);
+			defer (array_free(&fields));
+
+			for_array(i, type->Enum.fields) {
+				fields[i] = odin_doc_add_entity(w, type->Enum.fields[i]);
+			}
+			doc_type.entities = odin_write_slice(w, fields.data, fields.count);
+			if (type->Enum.base_type) {
+				doc_type.types = odin_doc_type_as_slice(w, type->Enum.base_type);
+			}
+		}
+		break;
+	case Type_Tuple:
+		doc_type.kind = OdinDocType_Tuple;
+		{
+			auto variables = array_make<OdinDocEntityIndex>(heap_allocator(), type->Tuple.variables.count);
+			defer (array_free(&variables));
+
+			for_array(i, type->Tuple.variables) {
+				variables[i] = odin_doc_add_entity(w, type->Tuple.variables[i]);
+			}
+
+			doc_type.entities = odin_write_slice(w, variables.data, variables.count);
+		}
+		break;
+	case Type_Proc:
+		doc_type.kind = OdinDocType_Proc;
+		if (type->Proc.is_polymorphic) { doc_type.flags |= OdinDocTypeFlag_Proc_polymorphic; }
+		if (type->Proc.diverging)      { doc_type.flags |= OdinDocTypeFlag_Proc_diverging; }
+		if (type->Proc.optional_ok)    { doc_type.flags |= OdinDocTypeFlag_Proc_optional_ok; }
+		if (type->Proc.variadic)       { doc_type.flags |= OdinDocTypeFlag_Proc_variadic; }
+		if (type->Proc.c_vararg)       { doc_type.flags |= OdinDocTypeFlag_Proc_c_vararg; }
+		{
+			OdinDocTypeIndex types[2];
+			types[0] = odin_doc_type(w, type->Proc.params);
+			types[1] = odin_doc_type(w, type->Proc.results);
+			doc_type.types = odin_write_slice(w, types, gb_count_of(types));
+
+			String calling_convention = {};
+			switch (type->Proc.calling_convention) {
+			case ProcCC_Invalid:
+			case ProcCC_Odin:
+				// no need
+				break;
+			case ProcCC_Contextless:
+				calling_convention = str_lit("contextless");
+				break;
+			case ProcCC_CDecl:
+				calling_convention = str_lit("cdecl");
+				break;
+			case ProcCC_StdCall:
+				calling_convention = str_lit("stdcall");
+				break;
+			case ProcCC_FastCall:
+				calling_convention = str_lit("fastcall");
+				break;
+			case ProcCC_None:
+				calling_convention = str_lit("none");
+				break;
+			case ProcCC_InlineAsm:
+				calling_convention = str_lit("inline-assembly");
+				break;
+			}
+			doc_type.calling_convention = odin_doc_write_string(w, calling_convention);
+		}
+		break;
+	case Type_BitSet:
+		doc_type.kind = OdinDocType_BitSet;
+		{
+			isize type_count = 0;
+			OdinDocTypeIndex types[2] = {};
+			if (type->BitSet.elem) {
+				types[type_count++] = odin_doc_type(w, type->BitSet.elem);
+			}
+			if (type->BitSet.underlying) {
+				types[type_count++] = odin_doc_type(w, type->BitSet.underlying);
+				doc_type.flags |= OdinDocTypeFlag_BitSet_UnderlyingType;
+			}
+			doc_type.types = odin_write_slice(w, types, type_count);
+			doc_type.elem_count_len = 2;
+			doc_type.elem_counts[0] = type->BitSet.lower;
+			doc_type.elem_counts[1] = type->BitSet.upper;
+		}
+		break;
+	case Type_SimdVector:
+		doc_type.kind = OdinDocType_SimdVector;
+		if (type->SimdVector.is_x86_mmx) {
+			doc_type.flags |= OdinDocTypeFlag_BitSet_x86_mmx;
+		} else {
+			doc_type.elem_count_len = 1;
+			doc_type.elem_counts[0] = type->SimdVector.count;
+			doc_type.types = odin_doc_type_as_slice(w, type->SimdVector.elem);
+		}
+		// TODO(bill):
+		break;
+	case Type_RelativePointer:
+		doc_type.kind = OdinDocType_RelativePointer;
+		{
+			OdinDocTypeIndex types[2] = {};
+			types[0] = odin_doc_type(w, type->RelativePointer.pointer_type);
+			types[1] = odin_doc_type(w, type->RelativePointer.base_integer);
+			doc_type.types = odin_write_slice(w, types, gb_count_of(types));
+		}
+		break;
+	case Type_RelativeSlice:
+		doc_type.kind = OdinDocType_RelativeSlice;
+		{
+			OdinDocTypeIndex types[2] = {};
+			types[0] = odin_doc_type(w, type->RelativeSlice.slice_type);
+			types[1] = odin_doc_type(w, type->RelativeSlice.base_integer);
+			doc_type.types = odin_write_slice(w, types, gb_count_of(types));
+		}
+		break;
+	}
+
+	if (dst) {
+		*dst = doc_type;
+	}
+	return type_index;
+}
+OdinDocEntityIndex odin_doc_add_entity(OdinDocWriter *w, Entity *e) {
+	if (e == nullptr) {
+		return 0;
+	}
+
+	OdinDocEntityIndex *prev_index = map_get(&w->entity_cache, hash_pointer(e));
+	if (prev_index) {
+		return *prev_index;
+	}
+
+	if (e->pkg != nullptr && map_get(&w->pkg_cache, hash_pointer(e->pkg)) == nullptr) {
+		return 0;
+	}
+
+	OdinDocEntity doc_entity = {};
+	OdinDocEntity* dst = nullptr;
+
+	OdinDocEntityIndex doc_entity_index = odin_doc_write_item(w, &w->entities, &doc_entity, &dst);
+	map_set(&w->entity_cache, hash_pointer(e), doc_entity_index);
+	map_set(&w->entity_id_cache, hash_integer(doc_entity_index), e);
+
+
+	Ast *type_expr = nullptr;
+	Ast *init_expr = nullptr;
+	Ast *decl_node = nullptr;
+	CommentGroup *comment = nullptr;
+	CommentGroup *docs = nullptr;
+	if (e->decl_info != nullptr) {
+		type_expr = e->decl_info->type_expr;
+		init_expr = e->decl_info->init_expr;
+		decl_node = e->decl_info->decl_node;
+		comment = e->decl_info->comment;
+		docs = e->decl_info->docs;
+	}
+
+	String link_name = {};
+
+	OdinDocEntityKind kind = OdinDocEntity_Invalid;
+	u32 flags = 0;
+
+	switch (e->kind) {
+	case Entity_Invalid:     kind = OdinDocEntity_Invalid;     break;
+	case Entity_Constant:    kind = OdinDocEntity_Constant;    break;
+	case Entity_Variable:    kind = OdinDocEntity_Variable;    break;
+	case Entity_TypeName:    kind = OdinDocEntity_TypeName;    break;
+	case Entity_Procedure:   kind = OdinDocEntity_Procedure;   break;
+	case Entity_ProcGroup:   kind = OdinDocEntity_ProcGroup;   break;
+	case Entity_ImportName:  kind = OdinDocEntity_ImportName;  break;
+	case Entity_LibraryName: kind = OdinDocEntity_LibraryName; break;
+	}
+
+	switch (e->kind) {
+	case Entity_TypeName:
+		if (e->TypeName.is_type_alias) {
+			flags |= OdinDocEntityFlag_Type_Alias;
+		}
+		break;
+	case Entity_Variable:
+		if (e->Variable.is_foreign) { flags |= OdinDocEntityFlag_Foreign; }
+		if (e->Variable.is_export)  { flags |= OdinDocEntityFlag_Export; }
+		if (e->Variable.thread_local_model != "") {
+			flags |= OdinDocEntityFlag_Var_Thread_Local;
+		}
+		link_name = e->Variable.link_name;
+		break;
+	case Entity_Procedure:
+		if (e->Procedure.is_foreign) { flags |= OdinDocEntityFlag_Foreign; }
+		if (e->Procedure.is_export)  { flags |= OdinDocEntityFlag_Export; }
+		link_name = e->Procedure.link_name;
+		break;
+	}
+
+	OdinDocString init_string = {};
+	if (init_expr) {
+		init_string = odin_doc_expr_string(w, init_expr);
+	} else {
+		if (e->kind == Entity_Constant) {
+			if (e->Constant.flags & EntityConstantFlag_ImplicitEnumValue) {
+				init_string = {}; // Blank
+			} else if (e->Constant.param_value.original_ast_expr) {
+				init_string = odin_doc_expr_string(w, e->Constant.param_value.original_ast_expr);
+			} else {
+				init_string = odin_doc_write_string(w, make_string_c(exact_value_to_string(e->Constant.value)));
+			}
+		} else if (e->kind == Entity_Variable) {
+			if (e->Variable.param_expr) {
+				init_string = odin_doc_expr_string(w, e->Variable.param_expr);
+			}
+		}
+	}
+
+	doc_entity.kind = kind;
+	doc_entity.flags = flags;
+	doc_entity.pos = odin_doc_token_pos_cast(w, e->token.pos);
+	doc_entity.name = odin_doc_write_string(w, e->token.string);
+	doc_entity.type = 0; // Set later
+	doc_entity.init_string = init_string;
+	doc_entity.comment = odin_doc_comment_group_string(w, comment);
+	doc_entity.docs = odin_doc_comment_group_string(w, docs);
+	doc_entity.foreign_library = 0; // Set later
+	doc_entity.link_name = odin_doc_write_string(w, link_name);
+	if (e->decl_info != nullptr) {
+		doc_entity.attributes = odin_doc_attributes(w, e->decl_info->attributes);
+	}
+	doc_entity.grouped_entities = {}; // Set later
+
+	if (dst) {
+		*dst = doc_entity;
+	}
+
+	return doc_entity_index;
+}
+
+void odin_doc_update_entities(OdinDocWriter *w) {
+	{
+		// NOTE(bill): Double pass, just in case entities are created on odin_doc_type
+		auto entities = array_make<Entity *>(heap_allocator(), w->entity_cache.entries.count);
+		defer (array_free(&entities));
+
+		for_array(i, w->entity_cache.entries) {
+			Entity *e = cast(Entity *)cast(uintptr)w->entity_cache.entries[i].key.key;
+			entities[i] = e;
+		}
+		for_array(i, entities) {
+			Entity *e = entities[i];
+			OdinDocTypeIndex type_index = odin_doc_type(w, e->type);
+		}
+	}
+
+	for_array(i, w->entity_cache.entries) {
+		Entity *e = cast(Entity *)cast(uintptr)w->entity_cache.entries[i].key.key;
+		OdinDocEntityIndex entity_index = w->entity_cache.entries[i].value;
+		OdinDocTypeIndex type_index = odin_doc_type(w, e->type);
+
+		OdinDocEntityIndex foreign_library = 0;
+		OdinDocArray<OdinDocEntityIndex> grouped_entities = {};
+
+		switch (e->kind) {
+		case Entity_Variable:
+			foreign_library = odin_doc_add_entity(w, e->Variable.foreign_library);
+			break;
+		case Entity_Procedure:
+			foreign_library = odin_doc_add_entity(w, e->Procedure.foreign_library);
+			break;
+		case Entity_ProcGroup:
+			{
+				auto pges = array_make<OdinDocEntityIndex>(heap_allocator(), 0, e->ProcGroup.entities.count);
+				defer (array_free(&pges));
+
+				for_array(j, e->ProcGroup.entities) {
+					OdinDocEntityIndex index = odin_doc_add_entity(w, e->ProcGroup.entities[j]);
+					array_add(&pges, index);
+				}
+				grouped_entities = odin_write_slice(w, pges.data, pges.count);
+			}
+			break;
+		}
+
+		OdinDocEntity *dst = odin_doc_get_item(w, &w->entities, entity_index);
+		if (dst) {
+			dst->type = type_index;
+			dst->foreign_library = foreign_library;
+			dst->grouped_entities = grouped_entities;
+		}
+	}
+}
+
+
+
+OdinDocArray<OdinDocEntityIndex> odin_doc_add_pkg_entities(OdinDocWriter *w, AstPackage *pkg) {
+	if (pkg->scope == nullptr) {
+		return {};
+	}
+	if (map_get(&w->pkg_cache, hash_pointer(pkg)) == nullptr) {
+		return {};
+	}
+
+	auto entities = array_make<Entity *>(heap_allocator(), 0, pkg->scope->elements.entries.count);
+	defer (array_free(&entities));
+
+	for_array(i, pkg->scope->elements.entries) {
+		Entity *e = pkg->scope->elements.entries[i].value;
+		switch (e->kind) {
+		case Entity_Invalid:
+		case Entity_Builtin:
+		case Entity_Nil:
+		case Entity_Label:
+			continue;
+		case Entity_Constant:
+		case Entity_Variable:
+		case Entity_TypeName:
+		case Entity_Procedure:
+		case Entity_ProcGroup:
+		case Entity_ImportName:
+		case Entity_LibraryName:
+			// Fine
+			break;
+		}
+		array_add(&entities, e);
+	}
+	gb_sort_array(entities.data, entities.count, cmp_entities_for_printing);
+
+	auto entity_indices = array_make<OdinDocEntityIndex>(heap_allocator(), 0, w->entity_cache.entries.count);
+	defer (array_free(&entity_indices));
+
+	EntityKind curr_entity_kind = Entity_Invalid;
+	for_array(i, entities) {
+		Entity *e = entities[i];
+		if (e->pkg != pkg) {
+			continue;
+		}
+		if (!is_entity_exported(e)) {
+			continue;
+		}
+		if (e->token.string.len == 0) {
+			continue;
+		}
+
+		OdinDocEntityIndex doc_entity_index = 0;
+		doc_entity_index = odin_doc_add_entity(w, e);
+		array_add(&entity_indices, doc_entity_index);
+	}
+
+	return odin_write_slice(w, entity_indices.data, entity_indices.count);
+}
+
+
+void odin_doc_write_docs(OdinDocWriter *w) {
+	auto pkgs = array_make<AstPackage *>(heap_allocator(), 0, w->info->packages.entries.count);
+	defer (array_free(&pkgs));
+	for_array(i, w->info->packages.entries) {
+		AstPackage *pkg = w->info->packages.entries[i].value;
+		if (build_context.cmd_doc_flags & CmdDocFlag_AllPackages) {
+			array_add(&pkgs, pkg);
+		} else {
+			if (pkg->kind == Package_Init) {
+				array_add(&pkgs, pkg);
+			} else if (pkg->is_extra) {
+				array_add(&pkgs, pkg);
+			}
+		}
+	}
+
+	gb_sort_array(pkgs.data, pkgs.count, cmp_ast_package_by_name);
+
+	for_array(i, pkgs) {
+		gbAllocator allocator = heap_allocator();
+
+		AstPackage *pkg = pkgs[i];
+
+		u32 pkg_flags = 0;
+		switch (pkg->kind) {
+		case Package_Normal:
+			break;
+		case Package_Runtime:
+			pkg_flags |= OdinDocPkgFlag_Runtime;
+			break;
+		case Package_Init:
+			pkg_flags |= OdinDocPkgFlag_Init;
+			break;
+		}
+		if (pkg->name == "builtin") {
+			pkg_flags |= OdinDocPkgFlag_Builtin;
+		} else if (pkg->name == "intrinsics") {
+			pkg_flags |= OdinDocPkgFlag_Builtin;
+		}
+
+		OdinDocPkg doc_pkg = {};
+		doc_pkg.fullpath = odin_doc_write_string(w, pkg->fullpath);
+		doc_pkg.name     = odin_doc_write_string(w, pkg->name);
+		doc_pkg.flags    = pkg_flags;
+		doc_pkg.docs     = odin_doc_pkg_doc_string(w, pkg);
+
+		OdinDocPkg *dst = nullptr;
+		OdinDocPkgIndex pkg_index = odin_doc_write_item(w, &w->pkgs, &doc_pkg, &dst);
+		map_set(&w->pkg_cache, hash_pointer(pkg), pkg_index);
+
+		auto file_indices = array_make<OdinDocFileIndex>(heap_allocator(), 0, pkg->files.count);
+		defer (array_free(&file_indices));
+
+		for_array(j, pkg->files) {
+			AstFile *file = pkg->files[j];
+			OdinDocFile doc_file = {};
+			doc_file.pkg = pkg_index;
+			doc_file.name = odin_doc_write_string(w, file->fullpath);
+			OdinDocFileIndex file_index = odin_doc_write_item(w, &w->files, &doc_file);
+			map_set(&w->file_cache, hash_pointer(file), file_index);
+			array_add(&file_indices, file_index);
+		}
+
+		doc_pkg.files = odin_write_slice(w, file_indices.data, file_indices.count);
+		doc_pkg.entities = odin_doc_add_pkg_entities(w, pkg);
+
+		if (dst) {
+			*dst = doc_pkg;
+		}
+	}
+
+	odin_doc_update_entities(w);
+}
+
+
+void odin_doc_write_to_file(OdinDocWriter *w, char const *filename) {
+	gbFile f = {};
+	gbFileError err = gb_file_open_mode(&f, gbFileMode_Write, filename);
+	if (err != gbFileError_None) {
+		gb_printf_err("Failed to write .odin-doc to: %s\n", filename);
+		gb_exit(1);
+		return;
+	}
+	defer (gb_file_close(&f));
+	if (gb_file_write(&f, w->data, w->data_len)) {
+		err = gb_file_truncate(&f, w->data_len);
+		gb_printf("Wrote .odin-doc file to: %s\n", filename);
+	}
+}
+
+void odin_doc_write(CheckerInfo *info, char const *filename) {
+	OdinDocWriter w_ = {};
+	OdinDocWriter *w = &w_;
+	defer (odin_doc_writer_destroy(w));
+	w->info = info;
+
+	odin_doc_writer_prepare(w);
+	odin_doc_write_docs(w);
+
+	odin_doc_writer_start_writing(w);
+	odin_doc_write_docs(w);
+	odin_doc_writer_end_writing(w);
+
+	odin_doc_write_to_file(w, filename);
+}

+ 9 - 5
src/entity.cpp

@@ -32,7 +32,7 @@ String const entity_strings[] = {
 #undef ENTITY_KIND
 };
 
-enum EntityFlag : u32 {
+enum EntityFlag : u64 {
 	EntityFlag_Visited       = 1<<0,
 	EntityFlag_Used          = 1<<1,
 	EntityFlag_Using         = 1<<2,
@@ -63,12 +63,13 @@ enum EntityFlag : u32 {
 	EntityFlag_AutoCast      = 1<<22,
 
 	EntityFlag_Disabled      = 1<<24,
+	EntityFlag_Cold          = 1<<25, // procedure is rarely called
 
-	EntityFlag_Test          = 1<<25,
+	EntityFlag_Test          = 1<<30,
 
 };
 
-enum EntityState {
+enum EntityState : u32 {
 	EntityState_Unresolved = 0,
 	EntityState_InProgress = 1,
 	EntityState_Resolved   = 2,
@@ -92,13 +93,15 @@ struct ParameterValue {
 	};
 };
 
-
+enum EntityConstantFlags : u32 {
+	EntityConstantFlag_ImplicitEnumValue = 1<<0,
+};
 
 // An Entity is a named "thing" in the language
 struct Entity {
 	EntityKind  kind;
 	u64         id;
-	u32         flags;
+	u64         flags;
 	EntityState state;
 	Token       token;
 	Scope *     scope;
@@ -125,6 +128,7 @@ struct Entity {
 		struct {
 			ExactValue value;
 			ParameterValue param_value;
+			u32 flags;
 		} Constant;
 		struct {
 			Ast *init_expr; // only used for some variables within procedure bodies

+ 7 - 1
src/ir.cpp

@@ -132,6 +132,7 @@ struct irProcedure {
 	irModule *            module;
 	String                name;
 	Type *                type;
+	Ast *                 proc_lit; // only for actual anonymous procedure literals
 	Ast *                 type_expr;
 	Ast *                 body;
 	u64                   tags;
@@ -6894,7 +6895,7 @@ irTargetList *ir_push_target_list(irProcedure *proc, Ast *label, irBlock *break_
 			}
 		}
 
-		GB_PANIC("ir_set_label_blocks: Unreachable");
+		GB_PANIC("ir_set_label_blocks: Unreachable; Unable to find label: %s", expr_to_string(label->Label.name));
 	}
 
 	return tl;
@@ -6927,6 +6928,7 @@ irValue *ir_gen_anonymous_proc_lit(irModule *m, String prefix_name, Ast *expr, i
 	set_procedure_abi_types(type);
 	irValue *value = ir_value_procedure(m, nullptr, type, pl->type, pl->body, name);
 
+	value->Proc.proc_lit = expr;
 	value->Proc.tags = pl->tags;
 	value->Proc.inlining = pl->inlining;
 	value->Proc.parent = proc;
@@ -11390,6 +11392,10 @@ void ir_begin_procedure_body(irProcedure *proc) {
 	array_init(&proc->context_stack,    heap_allocator());
 
 	DeclInfo *decl = decl_info_of_entity(proc->entity);
+	if (decl == nullptr && proc->proc_lit != nullptr) {
+		GB_ASSERT(proc->proc_lit->kind == Ast_ProcLit);
+		decl = proc->proc_lit->ProcLit.decl;
+	}
 	if (decl != nullptr) {
 		for_array(i, decl->labels) {
 			BlockLabel bl = decl->labels[i];

+ 43 - 3
src/llvm_backend.cpp

@@ -2464,9 +2464,12 @@ void lb_add_proc_attribute_at_index(lbProcedure *p, isize index, char const *nam
 }
 
 void lb_add_proc_attribute_at_index(lbProcedure *p, isize index, char const *name) {
-	lb_add_proc_attribute_at_index(p, index, name, cast(u64)true);
+	lb_add_proc_attribute_at_index(p, index, name, 0);
 }
 
+void lb_add_attribute_to_proc(lbModule *m, LLVMValueRef proc_value, char const *name, u64 value=0) {
+	LLVMAddAttributeAtIndex(proc_value, LLVMAttributeIndex_FunctionIndex, lb_create_enum_attribute(m->ctx, name, value));
+}
 
 
 void lb_ensure_abi_function_type(lbModule *m, lbProcedure *p) {
@@ -2520,7 +2523,6 @@ lbProcedure *lb_create_procedure(lbModule *m, Entity *entity) {
 	p->type           = entity->type;
 	p->type_expr      = decl->type_expr;
 	p->body           = pl->body;
-	p->tags           = pt->Proc.tags;
 	p->inlining       = ProcInlining_none;
 	p->is_foreign     = entity->Procedure.is_foreign;
 	p->is_export      = entity->Procedure.is_export;
@@ -2556,6 +2558,23 @@ lbProcedure *lb_create_procedure(lbModule *m, Entity *entity) {
 		LLVMSetFunctionCallConv(p->value, cc_kind);
 	}
 
+	if (entity->flags & EntityFlag_Cold) {
+		lb_add_attribute_to_proc(m, p->value, "cold");
+	}
+
+	if (pt->Proc.diverging) {
+		lb_add_attribute_to_proc(m, p->value, "noreturn");
+	}
+
+	switch (p->inlining) {
+	case ProcInlining_inline:
+		lb_add_attribute_to_proc(m, p->value, "alwaysinline");
+		break;
+	case ProcInlining_no_inline:
+		lb_add_attribute_to_proc(m, p->value, "noinline");
+		break;
+	}
+
 	// lbCallingConventionKind cc_kind = lbCallingConvention_C;
 	// // TODO(bill): Clean up this logic
 	// if (build_context.metrics.os != TargetOs_js)  {
@@ -8073,7 +8092,18 @@ lbValue lb_emit_call_internal(lbProcedure *p, lbValue value, lbValue return_ptr,
 		}
 
 		LLVMValueRef ret = LLVMBuildCall2(p->builder, fnp, fn, args, arg_count, "");
-		// LLVMValueRef ret = LLVMBuildCall(p->builder, fn, args, arg_count, "");
+
+		switch (inlining) {
+		case ProcInlining_none:
+			break;
+		case ProcInlining_inline:
+			LLVMAddCallSiteAttribute(ret, LLVMAttributeIndex_FunctionIndex, lb_create_enum_attribute(p->module->ctx, "alwaysinline"));
+			break;
+		case ProcInlining_no_inline:
+			LLVMAddCallSiteAttribute(ret, LLVMAttributeIndex_FunctionIndex, lb_create_enum_attribute(p->module->ctx, "noinline"));
+			break;
+		}
+
 		lbValue res = {};
 		res.value = ret;
 		res.type = abi_rt;
@@ -8988,6 +9018,16 @@ lbValue lb_build_builtin_proc(lbProcedure *p, Ast *expr, TypeAndValue const &tv,
 			);
 			GB_ASSERT(the_asm != nullptr);
 			LLVMBuildCall2(p->builder, func_type, the_asm, nullptr, 0, "");
+		} else if (build_context.metrics.arch == TargetArch_arm64) {
+			LLVMTypeRef func_type = LLVMFunctionType(LLVMVoidTypeInContext(p->module->ctx), nullptr, 0, false);
+			LLVMValueRef the_asm = LLVMGetInlineAsm(func_type,
+				cast(char *)"yield", 5,
+				cast(char *)"", 0,
+				/*HasSideEffects*/true, /*IsAlignStack*/false,
+				LLVMInlineAsmDialectATT
+			);
+			GB_ASSERT(the_asm != nullptr);
+			LLVMBuildCall2(p->builder, func_type, the_asm, nullptr, 0, "");
 		}
 		return {};
 

+ 7 - 0
src/llvm_backend.hpp

@@ -499,3 +499,10 @@ enum {
 	DW_TAG_subroutine_type  = 21,
 	DW_TAG_inheritance      = 28,
 };
+
+
+enum : LLVMAttributeIndex {
+	LLVMAttributeIndex_ReturnIndex = 0u,
+	LLVMAttributeIndex_FunctionIndex = ~0u,
+	LLVMAttributeIndex_FirstArgIndex = 1,
+};

+ 5 - 6
src/llvm_backend_opt.cpp

@@ -107,12 +107,11 @@ void lb_populate_module_pass_manager(LLVMTargetMachineRef target_machine, LLVMPa
 
 	if (optimization_level >= 2) {
 		// NOTE(bill, 2021-03-29: use this causes invalid code generation)
-		LLVMPassManagerBuilderRef pmb = LLVMPassManagerBuilderCreate();
-		LLVMPassManagerBuilderSetOptLevel(pmb, optimization_level);
-		LLVMPassManagerBuilderPopulateModulePassManager(pmb, mpm);
-		LLVMPassManagerBuilderPopulateLTOPassManager(pmb, mpm, false, true);
-		// LLVMPassManagerBuilderSetSizeLevel(pmb, optimization_level);
-		return;
+		// LLVMPassManagerBuilderRef pmb = LLVMPassManagerBuilderCreate();
+		// LLVMPassManagerBuilderSetOptLevel(pmb, optimization_level);
+		// LLVMPassManagerBuilderPopulateModulePassManager(pmb, mpm);
+		// LLVMPassManagerBuilderPopulateLTOPassManager(pmb, mpm, false, true);
+		// return;
 	}
 
 	LLVMAddIPSCCPPass(mpm);

+ 18 - 13
src/main.cpp

@@ -273,7 +273,7 @@ i32 linker_stage(lbGenerator *gen) {
 					LIT(output_base),
 					LIT(build_context.resource_filepath)
 				);
-        
+
         if(result == 0) {
           result = system_exec_command_line_app("msvc-link",
             "\"%.*slink.exe\" %s \"%.*s.res\" -OUT:\"%.*s.%s\" %s "
@@ -425,7 +425,7 @@ i32 linker_stage(lbGenerator *gen) {
 				output_ext = substring(build_context.out_filepath, pos, build_context.out_filepath.len);
 			}
 		}
-    
+
 		result = system_exec_command_line_app("ld-link",
 			"%s %s -o \"%.*s%.*s\" %s "
 			" %s "
@@ -467,7 +467,7 @@ i32 linker_stage(lbGenerator *gen) {
 
 	#endif
 	}
-  
+
 	return result;
 }
 #endif
@@ -607,6 +607,7 @@ enum BuildFlagKind {
 
 	BuildFlag_Short,
 	BuildFlag_AllPackages,
+	BuildFlag_DocFormat,
 
 	BuildFlag_IgnoreWarnings,
 	BuildFlag_WarningsAsErrors,
@@ -696,7 +697,7 @@ bool parse_build_flags(Array<String> args) {
 	add_flag(&build_flags, BuildFlag_Collection,        str_lit("collection"),          BuildFlagParam_String, Command__does_check);
 	add_flag(&build_flags, BuildFlag_Define,            str_lit("define"),              BuildFlagParam_String, Command__does_check, true);
 	add_flag(&build_flags, BuildFlag_BuildMode,         str_lit("build-mode"),          BuildFlagParam_String, Command__does_build); // Commands_build is not used to allow for a better error message
-	add_flag(&build_flags, BuildFlag_Target,            str_lit("target"),              BuildFlagParam_String, Command__does_build);
+	add_flag(&build_flags, BuildFlag_Target,            str_lit("target"),              BuildFlagParam_String, Command__does_check);
 	add_flag(&build_flags, BuildFlag_Debug,             str_lit("debug"),               BuildFlagParam_None, Command__does_check);
 	add_flag(&build_flags, BuildFlag_DisableAssert,     str_lit("disable-assert"),      BuildFlagParam_None, Command__does_check);
 	add_flag(&build_flags, BuildFlag_NoBoundsCheck,     str_lit("no-bounds-check"),     BuildFlagParam_None, Command__does_check);
@@ -721,6 +722,7 @@ bool parse_build_flags(Array<String> args) {
 
 	add_flag(&build_flags, BuildFlag_Short,         str_lit("short"),        BuildFlagParam_None, Command_doc);
 	add_flag(&build_flags, BuildFlag_AllPackages,   str_lit("all-packages"), BuildFlagParam_None, Command_doc);
+	add_flag(&build_flags, BuildFlag_DocFormat,     str_lit("doc-format"),   BuildFlagParam_None, Command_doc);
 
 	add_flag(&build_flags, BuildFlag_IgnoreWarnings,   str_lit("ignore-warnings"),    BuildFlagParam_None, Command_all);
 	add_flag(&build_flags, BuildFlag_WarningsAsErrors, str_lit("warnings-as-errors"), BuildFlagParam_None, Command_all);
@@ -1227,6 +1229,9 @@ bool parse_build_flags(Array<String> args) {
 						case BuildFlag_AllPackages:
 							build_context.cmd_doc_flags |= CmdDocFlag_AllPackages;
 							break;
+						case BuildFlag_DocFormat:
+							build_context.cmd_doc_flags |= CmdDocFlag_DocFormat;
+							break;
 						case BuildFlag_IgnoreWarnings:
 							if (build_context.warnings_as_errors) {
 								gb_printf_err("-ignore-warnings cannot be used with -warnings-as-errors\n");
@@ -2349,11 +2354,11 @@ int main(int arg_count, char const **arg_ptr) {
 						LIT(output_base),
 						LIT(build_context.resource_filepath)
 					);
-          
+
           if(result != 0) {
             return 1;
           }
-          
+
           result = system_exec_command_line_app("msvc-link",
 						"\"%.*slink.exe\" \"%.*s.obj\" \"%.*s.res\" -OUT:\"%.*s.%s\" %s "
 						"/nologo /incremental:no /opt:ref /subsystem:%s "
@@ -2368,11 +2373,11 @@ int main(int arg_count, char const **arg_ptr) {
 						LIT(build_context.extra_linker_flags),
 						lib_str
 					);
-          
+
           if(result != 0) {
             return 1;
           }
-          
+
 				} else {
           i32 result =  system_exec_command_line_app("msvc-link",
 						"\"%.*slink.exe\" \"%.*s.obj\" -OUT:\"%.*s.%s\" %s "
@@ -2388,7 +2393,7 @@ int main(int arg_count, char const **arg_ptr) {
 						LIT(build_context.extra_linker_flags),
 						lib_str
 					);
-          
+
           if(result != 0) {
             return 1;
           }
@@ -2409,7 +2414,7 @@ int main(int arg_count, char const **arg_ptr) {
 					LIT(build_context.extra_linker_flags),
 					lib_str
 				);
-        
+
         if(result != 0) {
           return 1;
         }
@@ -2547,11 +2552,11 @@ int main(int arg_count, char const **arg_ptr) {
 				LIT(build_context.link_flags),
 				LIT(build_context.extra_linker_flags),
 				link_settings);
-      
+
       if(result != 0) {
         return 1;
       }
-      
+
 		#if defined(GB_SYSTEM_OSX)
 			if (build_context.ODIN_DEBUG) {
 				// NOTE: macOS links DWARF symbols dynamically. Dsymutil will map the stubs in the exe
@@ -2578,6 +2583,6 @@ int main(int arg_count, char const **arg_ptr) {
 		#endif
 		}
 	}
-  
+
 	return 0;
 }

+ 1 - 1
src/tokenizer.cpp

@@ -478,7 +478,7 @@ void syntax_warning_va(Token token, char const *fmt, va_list va) {
 		// NOTE(bill): Duplicate error, skip it
 		if (global_error_collector.prev != token.pos) {
 			global_error_collector.prev = token.pos;
-			error_out("%S Syntax Warning: %s\n",
+			error_out("%s Syntax Warning: %s\n",
 			          token_pos_to_string(token.pos),
 			          gb_bprintf_va(fmt, va));
 		} else if (token.pos.line == 0) {

+ 0 - 1
src/types.cpp

@@ -195,7 +195,6 @@ struct TypeProc {
 	Type *   results; // Type_Tuple
 	i32      param_count;
 	i32      result_count;
-	u64      tags;
 	isize    specialization_count;
 	ProcCallingConvention calling_convention;
 	i32      variadic_index;