Browse Source

Merge branch 'haiku' of https://github.com/avanspector/Odin into haiku

avanspector 1 year ago
parent
commit
0a6673220b
67 changed files with 3919 additions and 1796 deletions
  1. 10 0
      base/runtime/core.odin
  2. 22 0
      base/runtime/internal.odin
  3. 14 0
      base/runtime/print.odin
  4. 3 0
      core/encoding/json/marshal.odin
  5. 97 27
      core/fmt/fmt.odin
  6. 4 2
      core/net/errors_darwin.odin
  7. 8 1
      core/net/socket_darwin.odin
  8. 7 2
      core/odin/doc-format/doc_format.odin
  9. 2 0
      core/odin/tokenizer/token.odin
  10. 4 0
      core/os/os2/env_windows.odin
  11. 41 3
      core/os/os2/file_windows.odin
  12. 4 0
      core/os/os2/path_windows.odin
  13. 4 0
      core/os/os2/stat_windows.odin
  14. 2 0
      core/os/os2/temp_file_windows.odin
  15. 47 0
      core/reflect/reflect.odin
  16. 31 0
      core/reflect/types.odin
  17. 489 0
      core/sync/chan/chan.odin
  18. 24 0
      core/sync/extended.odin
  19. 2 2
      core/sys/linux/bits.odin
  20. 9 9
      core/sys/unix/pthread_darwin.odin
  21. 1067 1061
      src/bug_report.cpp
  22. 20 0
      src/check_builtin.cpp
  23. 1 0
      src/check_decl.cpp
  24. 164 31
      src/check_expr.cpp
  25. 10 0
      src/check_stmt.cpp
  26. 212 0
      src/check_type.cpp
  27. 18 0
      src/checker.cpp
  28. 1 0
      src/checker.hpp
  29. 4 0
      src/checker_builtin_procs.hpp
  30. 6 3
      src/docs_format.cpp
  31. 24 1
      src/docs_writer.cpp
  32. 2 0
      src/entity.cpp
  33. 7 1
      src/llvm_backend.cpp
  34. 8 0
      src/llvm_backend.hpp
  35. 36 0
      src/llvm_backend_debug.cpp
  36. 74 0
      src/llvm_backend_expr.cpp
  37. 52 3
      src/llvm_backend_general.cpp
  38. 17 4
      src/llvm_backend_stmt.cpp
  39. 71 0
      src/llvm_backend_type.cpp
  40. 1 1
      src/llvm_backend_utility.cpp
  41. 97 0
      src/parser.cpp
  42. 17 0
      src/parser.hpp
  43. 3 0
      src/parser_pos.cpp
  44. 1 0
      src/tokenizer.cpp
  45. 131 5
      src/types.cpp
  46. 84 132
      tests/core/net/test_core_net.odin
  47. 53 20
      vendor/miniaudio/common.odin
  48. 56 26
      vendor/miniaudio/data_conversion.odin
  49. 11 8
      vendor/miniaudio/device_io_procs.odin
  50. 137 107
      vendor/miniaudio/device_io_types.odin
  51. 203 120
      vendor/miniaudio/doc.odin
  52. 26 13
      vendor/miniaudio/effects.odin
  53. 1 1
      vendor/miniaudio/encoding.odin
  54. 102 30
      vendor/miniaudio/engine.odin
  55. 1 1
      vendor/miniaudio/filtering.odin
  56. 1 1
      vendor/miniaudio/generation.odin
  57. 1 1
      vendor/miniaudio/job_queue.odin
  58. BIN
      vendor/miniaudio/lib/miniaudio.lib
  59. 2 7
      vendor/miniaudio/logging.odin
  60. 6 5
      vendor/miniaudio/node_graph.odin
  61. 1 0
      vendor/miniaudio/resource_manager.odin
  62. 315 152
      vendor/miniaudio/src/miniaudio.h
  63. 36 1
      vendor/miniaudio/utilities.odin
  64. BIN
      vendor/raylib/macos-arm64/libraylib.5.0.0.dylib
  65. BIN
      vendor/raylib/macos/libraylib.5.0.0.dylib
  66. 1 1
      vendor/vulkan/_gen/create_vulkan_odin_wrapper.py
  67. 14 14
      vendor/vulkan/structs.odin

+ 10 - 0
base/runtime/core.odin

@@ -181,6 +181,14 @@ Type_Info_Matrix :: struct {
 Type_Info_Soa_Pointer :: struct {
 	elem: ^Type_Info,
 }
+Type_Info_Bit_Field :: struct {
+	backing_type: ^Type_Info,
+	names:        []string,
+	types:        []^Type_Info,
+	bit_sizes:    []uintptr,
+	bit_offsets:  []uintptr,
+	tags:         []string,
+}
 
 Type_Info_Flag :: enum u8 {
 	Comparable     = 0,
@@ -223,6 +231,7 @@ Type_Info :: struct {
 		Type_Info_Relative_Multi_Pointer,
 		Type_Info_Matrix,
 		Type_Info_Soa_Pointer,
+		Type_Info_Bit_Field,
 	},
 }
 
@@ -256,6 +265,7 @@ Typeid_Kind :: enum u8 {
 	Relative_Multi_Pointer,
 	Matrix,
 	Soa_Pointer,
+	Bit_Field,
 }
 #assert(len(Typeid_Kind) < 32)
 

+ 22 - 0
base/runtime/internal.odin

@@ -1034,3 +1034,25 @@ fixdfti :: proc(a: u64) -> i128 {
 	}
 
 }
+
+
+
+__write_bits :: proc "contextless" (dst, src: [^]byte, offset: uintptr, size: uintptr) {
+	for i in 0..<size {
+		j := offset+i
+		the_bit := byte((src[i/8]) & (1<<(i&7)) != 0)
+		b := the_bit<<(j&7)
+		dst[j/8] &~= b
+		dst[j/8] |=  b
+	}
+}
+
+__read_bits :: proc "contextless" (dst, src: [^]byte, offset: uintptr, size: uintptr) {
+	for j in 0..<size {
+		i := offset+j
+		the_bit := byte((src[i/8]) & (1<<(i&7)) != 0)
+		b := the_bit<<(j&7)
+		dst[j/8] &~= b
+		dst[j/8] |=  b
+	}
+}

+ 14 - 0
base/runtime/print.odin

@@ -459,6 +459,20 @@ print_type :: proc "contextless" (ti: ^Type_Info) {
 		}
 		print_byte(']')
 
+	case Type_Info_Bit_Field:
+		print_string("bit_field ")
+		print_type(info.backing_type)
+		print_string(" {")
+		for name, i in info.names {
+			if i > 0 { print_string(", ") }
+			print_string(name)
+			print_string(": ")
+			print_type(info.types[i])
+			print_string(" | ")
+			print_u64(u64(info.bit_sizes[i]))
+		}
+		print_byte('}')
+
 
 	case Type_Info_Simd_Vector:
 		print_string("#simd[")

+ 3 - 0
core/encoding/json/marshal.odin

@@ -228,6 +228,9 @@ marshal_to_writer :: proc(w: io.Writer, v: any, opt: ^Marshal_Options) -> (err:
 	case runtime.Type_Info_Matrix:
 		return .Unsupported_Type
 
+	case runtime.Type_Info_Bit_Field:
+		return .Unsupported_Type
+
 	case runtime.Type_Info_Array:
 		opt_write_start(w, opt, '[') or_return
 		for i in 0..<info.count {

+ 97 - 27
core/fmt/fmt.odin

@@ -1408,34 +1408,9 @@ fmt_soa_pointer :: proc(fi: ^Info, p: runtime.Raw_Soa_Pointer, verb: rune) {
 //
 // Returns: The string representation of the enum value and a boolean indicating success.
 //
+@(require_results)
 enum_value_to_string :: proc(val: any) -> (string, bool) {
-	v := val
-	v.id = runtime.typeid_base(v.id)
-	type_info := type_info_of(v.id)
-
-	#partial switch e in type_info.variant {
-	case: return "", false
-	case runtime.Type_Info_Enum:
-		Enum_Value :: runtime.Type_Info_Enum_Value
-
-		ev_, ok := reflect.as_i64(val)
-		ev := Enum_Value(ev_)
-
-		if ok {
-			if len(e.values) == 0 {
-				return "", true
-			} else {
-				for val, idx in e.values {
-					if val == ev {
-						return e.names[idx], true
-					}
-				}
-			}
-			return "", false
-		}
-	}
-
-	return "", false
+	return reflect.enum_name_from_value_any(val)
 }
 // Returns the enum value of a string representation.
 //
@@ -2198,6 +2173,8 @@ fmt_named :: proc(fi: ^Info, v: any, verb: rune, info: runtime.Type_Info_Named)
 	#partial switch b in info.base.variant {
 	case runtime.Type_Info_Struct:
 		fmt_struct(fi, v, verb, b, info.name)
+	case runtime.Type_Info_Bit_Field:
+		fmt_bit_field(fi, v, verb, b, info.name)
 	case runtime.Type_Info_Bit_Set:
 		fmt_bit_set(fi, v, verb = verb)
 	case:
@@ -2308,6 +2285,96 @@ fmt_matrix :: proc(fi: ^Info, v: any, verb: rune, info: runtime.Type_Info_Matrix
 		fmt_write_indent(fi)
 	}
 }
+
+fmt_bit_field :: proc(fi: ^Info, v: any, verb: rune, info: runtime.Type_Info_Bit_Field, type_name: string) {
+	read_bits :: proc(ptr: [^]byte, offset, size: uintptr) -> (res: u64) {
+		for i in 0..<size {
+			j := i+offset
+			B := ptr[j/8]
+			k := j&7
+			if B & (u8(1)<<k) != 0 {
+				res |= u64(1)<<u64(i)
+			}
+		}
+		return
+	}
+
+	handle_bit_field_tag :: proc(data: rawptr, info: reflect.Type_Info_Bit_Field, idx: int, verb: ^rune) -> (do_continue: bool) {
+		tag := info.tags[idx]
+		if vt, ok := reflect.struct_tag_lookup(reflect.Struct_Tag(tag), "fmt"); ok {
+			value := strings.trim_space(string(vt))
+			switch value {
+			case "": return false
+			case "-": return true
+			}
+			r, w := utf8.decode_rune_in_string(value)
+			value = value[w:]
+			if value == "" || value[0] == ',' {
+				verb^ = r
+			}
+		}
+		return false
+	}
+
+	io.write_string(fi.writer, type_name if len(type_name) != 0 else "bit_field", &fi.n)
+	io.write_string(fi.writer, "{", &fi.n)
+
+	hash   := fi.hash;   defer fi.hash = hash
+	indent := fi.indent; defer fi.indent -= 1
+	do_trailing_comma := hash
+
+	fi.indent += 1
+
+	if hash	{
+		io.write_byte(fi.writer, '\n', &fi.n)
+	}
+	defer {
+		if hash {
+			for _ in 0..<indent { io.write_byte(fi.writer, '\t', &fi.n) }
+		}
+		io.write_byte(fi.writer, '}', &fi.n)
+	}
+
+
+	field_count := -1
+	for name, i in info.names {
+		field_verb := verb
+		if handle_bit_field_tag(v.data, info, i, &field_verb) {
+			continue
+		}
+
+		field_count += 1
+
+		if !do_trailing_comma && field_count > 0 {
+			io.write_string(fi.writer, ", ")
+		}
+		if hash {
+			fmt_write_indent(fi)
+		}
+
+		io.write_string(fi.writer, name, &fi.n)
+		io.write_string(fi.writer, " = ", &fi.n)
+
+		bit_offset := info.bit_offsets[i]
+		bit_size := info.bit_sizes[i]
+
+		value := read_bits(([^]byte)(v.data), bit_offset, bit_size)
+		type := info.types[i]
+
+		if !reflect.is_unsigned(runtime.type_info_core(type)) {
+			// Sign Extension
+			m := u64(1<<(bit_size-1))
+			value = (value ~ m) - m
+		}
+
+		fmt_value(fi, any{&value, type.id}, field_verb)
+		if do_trailing_comma { io.write_string(fi.writer, ",\n", &fi.n) }
+
+	}
+}
+
+
+
 // Formats a value based on its type and formatting verb
 //
 // Inputs:
@@ -2636,6 +2703,9 @@ fmt_value :: proc(fi: ^Info, v: any, verb: rune) {
 
 	case runtime.Type_Info_Matrix:
 		fmt_matrix(fi, v, verb, info)
+
+	case runtime.Type_Info_Bit_Field:
+		fmt_bit_field(fi, v, verb, info, "")
 	}
 }
 // Formats a complex number based on the given formatting verb

+ 4 - 2
core/net/errors_darwin.odin

@@ -34,7 +34,7 @@ Create_Socket_Error :: enum c.int {
 
 Dial_Error :: enum c.int {
 	None                      = 0,
-	Port_Required             = -1,
+	Port_Required             = -1, // Attempted to dial an endpointing without a port being set.
 
 	Address_In_Use            = c.int(os.EADDRINUSE),
 	In_Progress               = c.int(os.EINPROGRESS),
@@ -54,7 +54,9 @@ Dial_Error :: enum c.int {
 }
 
 Bind_Error :: enum c.int {
-	None                    = 0,
+	None                         = 0,
+	Privileged_Port_Without_Root = -1, // Attempted to bind to a port less than 1024 without root access.
+
 	Address_In_Use          = c.int(os.EADDRINUSE),    // Another application is currently bound to this endpoint.
 	Given_Nonlocal_Address  = c.int(os.EADDRNOTAVAIL), // The address is not a local address on this machine.
 	Broadcast_Disabled      = c.int(os.EACCES),        // To bind a UDP socket to the broadcast address, the appropriate socket option must be set.

+ 8 - 1
core/net/socket_darwin.odin

@@ -92,13 +92,20 @@ _dial_tcp_from_endpoint :: proc(endpoint: Endpoint, options := default_tcp_optio
 	return
 }
 
+// On Darwin, any port below 1024 is 'privileged' - which means that you need root access in order to use it.
+MAX_PRIVILEGED_PORT :: 1023
+
 @(private)
 _bind :: proc(skt: Any_Socket, ep: Endpoint) -> (err: Network_Error) {
 	sockaddr := _endpoint_to_sockaddr(ep)
 	s := any_socket_to_socket(skt)
 	res := os.bind(os.Socket(s), (^os.SOCKADDR)(&sockaddr), i32(sockaddr.len))
 	if res != os.ERROR_NONE {
-		err = Bind_Error(res)
+		if res == os.EACCES && ep.port <= MAX_PRIVILEGED_PORT {
+			err = .Privileged_Port_Without_Root
+		} else {
+			err = Bind_Error(res)
+		}
 	}
 	return
 }

+ 7 - 2
core/odin/doc-format/doc_format.odin

@@ -10,8 +10,8 @@ Array :: struct($T: typeid) {
 String :: distinct Array(byte)
 
 Version_Type_Major :: 0
-Version_Type_Minor :: 2
-Version_Type_Patch :: 4
+Version_Type_Minor :: 3
+Version_Type_Patch :: 0
 
 Version_Type :: struct {
 	major, minor, patch: u8,
@@ -110,6 +110,8 @@ Entity_Flag :: enum u32le {
 	Param_No_Alias  = 7, // #no_alias
 	Param_Any_Int   = 8, // #any_int
 
+	Bit_Field_Field = 19,
+
 	Type_Alias = 20,
 
 	Builtin_Pkg_Builtin    = 30,
@@ -137,6 +139,7 @@ Entity :: struct {
 	// May be used by (Struct fields and procedure fields):
 	// .Variable
 	// .Constant
+	// This is equal to the negative of the "bit size" it this is a `bit_field`s field
 	field_group_index: i32le,
 
 	// May used by:
@@ -187,6 +190,7 @@ Type_Kind :: enum u32le {
 	Multi_Pointer          = 22,
 	Matrix                 = 23,
 	Soa_Pointer            = 24,
+	Bit_Field              = 25,
 }
 
 Type_Elems_Cap :: 4
@@ -247,6 +251,7 @@ Type :: struct {
 	// .Multi_Pointer      - 1 type:    0=element
 	// .Matrix             - 1 type:    0=element
 	// .Soa_Pointer        - 1 type:    0=element
+	// .Bit_Field          - 1 type:    0=backing type
 	types: Array(Type_Index),
 
 	// Used by:

+ 2 - 0
core/odin/tokenizer/token.odin

@@ -137,6 +137,7 @@ Token_Kind :: enum u32 {
 		Union,       // union
 		Enum,        // enum
 		Bit_Set,     // bit_set
+		Bit_Field,   // bit_field
 		Map,         // map
 		Dynamic,     // dynamic
 		Auto_Cast,   // auto_cast
@@ -270,6 +271,7 @@ tokens := [Token_Kind.COUNT]string {
 	"union",
 	"enum",
 	"bit_set",
+	"bit_field",
 	"map",
 	"dynamic",
 	"auto_cast",

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

@@ -18,6 +18,9 @@ _lookup_env :: proc(key: string, allocator: runtime.Allocator) -> (value: string
 		}
 		return "", true
 	}
+
+	_TEMP_ALLOCATOR_GUARD()
+
 	b := make([]u16, n+1, _temp_allocator())
 
 	n = win32.GetEnvironmentVariableW(wkey, raw_data(b), u32(len(b)))
@@ -47,6 +50,7 @@ _unset_env :: proc(key: string) -> bool {
 }
 
 _clear_env :: proc() {
+	_TEMP_ALLOCATOR_GUARD()
 	envs := environ(_temp_allocator())
 	for env in envs {
 		for j in 1..<len(env) {

+ 41 - 3
core/os/os2/file_windows.odin

@@ -1,10 +1,11 @@
 //+private
 package os2
 
+import "base:runtime"
+
 import "core:io"
 import "core:mem"
 import "core:sync"
-import "base:runtime"
 import "core:strings"
 import "core:time"
 import "core:unicode/utf16"
@@ -20,11 +21,45 @@ _file_allocator :: proc() -> runtime.Allocator {
 	return heap_allocator()
 }
 
+_temp_allocator_proc :: runtime.arena_allocator_proc
+
+@(private="file", thread_local)
+_global_default_temp_allocator_arena: runtime.Arena
+
 _temp_allocator :: proc() -> runtime.Allocator {
-	// TODO(bill): make this not depend on the context allocator
-	return context.temp_allocator
+	return runtime.Allocator{
+		procedure = _temp_allocator_proc,
+		data      = &_global_default_temp_allocator_arena,
+	}
 }
 
+@(require_results)
+_temp_allocator_temp_begin :: proc(loc := #caller_location) -> (temp: runtime.Arena_Temp) {
+	temp = runtime.arena_temp_begin(&_global_default_temp_allocator_arena, loc)
+	return
+}
+
+_temp_allocator_temp_end :: proc(temp: runtime.Arena_Temp, loc := #caller_location) {
+	runtime.arena_temp_end(temp, loc)
+}
+
+@(fini, private)
+_destroy_temp_allocator_fini :: proc() {
+	runtime.arena_destroy(&_global_default_temp_allocator_arena)
+	_global_default_temp_allocator_arena = {}
+}
+
+@(deferred_out=_temp_allocator_temp_end)
+_TEMP_ALLOCATOR_GUARD :: #force_inline proc(ignore := false, loc := #caller_location) -> (runtime.Arena_Temp, runtime.Source_Code_Location) {
+	if ignore {
+		return {}, loc
+	} else {
+		return _temp_allocator_temp_begin(loc), loc
+	}
+}
+
+
+
 
 _File_Kind :: enum u8 {
 	File,
@@ -546,6 +581,9 @@ _normalize_link_path :: proc(p: []u16, allocator: runtime.Allocator) -> (str: st
 	if n == 0 {
 		return "", _get_platform_error()
 	}
+
+	_TEMP_ALLOCATOR_GUARD()
+
 	buf := make([]u16, n+1, _temp_allocator())
 	n = win32.GetFinalPathNameByHandleW(handle, raw_data(buf), u32(len(buf)), win32.VOLUME_NAME_DOS)
 	if n == 0 {

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

@@ -31,6 +31,8 @@ _mkdir_all :: proc(path: string, perm: File_Mode) -> Error {
 		return p, false, nil
 	}
 
+	_TEMP_ALLOCATOR_GUARD()
+
 	dir, err := stat(path, _temp_allocator())
 	if err == nil {
 		if dir.is_directory {
@@ -125,6 +127,8 @@ _fix_long_path_internal :: proc(path: string) -> string {
 		return path
 	}
 
+	_TEMP_ALLOCATOR_GUARD()
+
 	PREFIX :: `\\?`
 	path_buf := make([]byte, len(PREFIX)+len(path)+1, _temp_allocator())
 	copy(path_buf, PREFIX)

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

@@ -46,6 +46,8 @@ full_path_from_name :: proc(name: string, allocator: runtime.Allocator) -> (path
 	if name == "" {
 		name = "."
 	}
+	_TEMP_ALLOCATOR_GUARD()
+
 	p := win32.utf8_to_utf16(name, _temp_allocator())
 
 	n := win32.GetFullPathNameW(raw_data(p), 0, nil, nil)
@@ -129,6 +131,7 @@ _cleanpath_from_handle :: proc(f: ^File, allocator: runtime.Allocator) -> (strin
 	if n == 0 {
 		return "", _get_platform_error()
 	}
+	_TEMP_ALLOCATOR_GUARD()
 	buf := make([]u16, max(n, 260)+1, _temp_allocator())
 	n = win32.GetFinalPathNameByHandleW(h, raw_data(buf), u32(len(buf)), 0)
 	return _cleanpath_from_buf(buf[:n], allocator)
@@ -144,6 +147,7 @@ _cleanpath_from_handle_u16 :: proc(f: ^File) -> ([]u16, Error) {
 	if n == 0 {
 		return nil, _get_platform_error()
 	}
+	_TEMP_ALLOCATOR_GUARD()
 	buf := make([]u16, max(n, 260)+1, _temp_allocator())
 	n = win32.GetFinalPathNameByHandleW(h, raw_data(buf), u32(len(buf)), 0)
 	return _cleanpath_strip_prefix(buf[:n]), nil

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

@@ -17,6 +17,8 @@ _temp_dir :: proc(allocator: runtime.Allocator) -> (string, runtime.Allocator_Er
 	if n == 0 {
 		return "", nil
 	}
+	_TEMP_ALLOCATOR_GUARD()
+
 	b := make([]u16, max(win32.MAX_PATH, n), _temp_allocator())
 	n = win32.GetTempPathW(u32(len(b)), raw_data(b))
 

+ 47 - 0
core/reflect/reflect.odin

@@ -35,6 +35,7 @@ Type_Info_Relative_Pointer       :: runtime.Type_Info_Relative_Pointer
 Type_Info_Relative_Multi_Pointer :: runtime.Type_Info_Relative_Multi_Pointer
 Type_Info_Matrix                 :: runtime.Type_Info_Matrix
 Type_Info_Soa_Pointer            :: runtime.Type_Info_Soa_Pointer
+Type_Info_Bit_Field              :: runtime.Type_Info_Bit_Field
 
 Type_Info_Enum_Value :: runtime.Type_Info_Enum_Value
 
@@ -70,6 +71,7 @@ Type_Kind :: enum {
 	Relative_Multi_Pointer,
 	Matrix,
 	Soa_Pointer,
+	Bit_Field,
 }
 
 
@@ -106,6 +108,7 @@ type_kind :: proc(T: typeid) -> Type_Kind {
 		case Type_Info_Relative_Multi_Pointer: return .Relative_Multi_Pointer
 		case Type_Info_Matrix:                 return .Matrix
 		case Type_Info_Soa_Pointer:            return .Soa_Pointer
+		case Type_Info_Bit_Field:              return .Bit_Field
 		}
 
 	}
@@ -627,6 +630,43 @@ enum_from_name_any :: proc(Enum_Type: typeid, name: string) -> (value: Type_Info
 	return
 }
 
+@(require_results)
+enum_name_from_value :: proc(value: $Enum_Type) -> (name: string, ok: bool) where intrinsics.type_is_enum(Enum_Type) {
+	ti := type_info_base(type_info_of(Enum_Type))
+	e := ti.variant.(runtime.Type_Info_Enum) or_return
+	if len(e.values) == 0 {
+		return
+	}
+	ev := Type_Info_Enum_Value(value)
+	for val, idx in e.values {
+		if val == ev {
+			return e.names[idx], true
+		}
+	}
+	return
+}
+
+@(require_results)
+enum_name_from_value_any :: proc(value: any) -> (name: string, ok: bool) {
+	if value.id == nil {
+		return
+	}
+	ti := type_info_base(type_info_of(value.id))
+	e := ti.variant.(runtime.Type_Info_Enum) or_return
+	if len(e.values) == 0 {
+		return
+	}
+	ev := Type_Info_Enum_Value(as_i64(value) or_return)
+	for val, idx in e.values {
+		if val == ev {
+			return e.names[idx], true
+		}
+	}
+	return
+}
+
+
+
 
 @(require_results)
 enum_field_names :: proc(Enum_Type: typeid) -> []string {
@@ -1567,6 +1607,13 @@ equal :: proc(a, b: any, including_indirect_array_recursion := false, recursion_
 			}	
 		}
 		return true
+
+	case Type_Info_Bit_Field:
+		x, y := a, b
+		x.id = v.backing_type.id
+		y.id = v.backing_type.id
+		return equal(x, y, including_indirect_array_recursion, recursion_level+0)
+
 	}
 	
 	runtime.print_typeid(a.id)

+ 31 - 0
core/reflect/types.odin

@@ -174,6 +174,23 @@ are_types_identical :: proc(a, b: ^Type_Info) -> bool {
 		if x.row_count != y.row_count { return false }
 		if x.column_count != y.column_count { return false }
 		return are_types_identical(x.elem, y.elem)
+
+	case Type_Info_Bit_Field:
+		y := b.variant.(Type_Info_Bit_Field) or_return
+		if !are_types_identical(x.backing_type, y.backing_type) { return false }
+		if len(x.names) != len(y.names) { return false }
+		for _, i in x.names {
+			if x.names[i] != y.names[i] {
+				return false
+			}
+			if !are_types_identical(x.types[i], y.types[i]) {
+				return false
+			}
+			if x.bit_sizes[i] != y.bit_sizes[i] {
+				return false
+			}
+		}
+		return true
 	}
 
 	return false
@@ -639,6 +656,20 @@ write_type_writer :: proc(w: io.Writer, ti: ^Type_Info, n_written: ^int = nil) -
 		}
 		io.write_byte(w, ']', &n) or_return
 
+	case Type_Info_Bit_Field:
+		io.write_string(w, "bit_field ", &n) or_return
+		write_type(w, info.backing_type, &n) or_return
+		io.write_string(w, " {",         &n) or_return
+		for name, i in info.names {
+			if i > 0 { io.write_string(w, ", ", &n) or_return }
+			io.write_string(w, name,     &n) or_return
+			io.write_string(w, ": ",     &n) or_return
+			write_type(w, info.types[i], &n) or_return
+			io.write_string(w, " | ",    &n) or_return
+			io.write_u64(w, u64(info.bit_sizes[i]), 10, &n) or_return
+		}
+		io.write_string(w, "}", &n) or_return
+
 	case Type_Info_Simd_Vector:
 		io.write_string(w, "#simd[",         &n) or_return
 		io.write_i64(w, i64(info.count), 10, &n) or_return

+ 489 - 0
core/sync/chan/chan.odin

@@ -0,0 +1,489 @@
+package sync_chan
+
+import "base:builtin"
+import "base:intrinsics"
+import "base:runtime"
+import "core:mem"
+import "core:sync"
+import "core:math/rand"
+
+Direction :: enum {
+	Send = -1,
+	Both =  0,
+	Recv = +1,
+}
+
+Chan :: struct($T: typeid, $D: Direction = Direction.Both) {
+	#subtype impl: ^Raw_Chan `fmt:"-"`,
+}
+
+Raw_Chan :: struct {
+	// Shared
+	allocator:       runtime.Allocator,
+	allocation_size: int,
+	msg_size:        u16,
+	closed:          b16, // atomic
+	mutex:           sync.Mutex,
+	r_cond:          sync.Cond,
+	w_cond:          sync.Cond,
+	r_waiting:       int,  // atomic
+	w_waiting:       int,  // atomic
+
+	// Buffered
+	queue: ^Raw_Queue,
+
+	// Unbuffered
+	r_mutex:         sync.Mutex,
+	w_mutex:         sync.Mutex,
+	unbuffered_data: rawptr,
+}
+
+
+create :: proc{
+	create_unbuffered,
+	create_buffered,
+}
+
+@(require_results)
+create_unbuffered :: proc($C: typeid/Chan($T), allocator: runtime.Allocator) -> (c: C, err: runtime.Allocator_Error)
+	where size_of(T) <= int(max(u16)) {
+	c.impl, err = create_raw_unbuffered(size_of(T), align_of(T), allocator)
+	return
+}
+
+@(require_results)
+create_buffered :: proc($C: typeid/Chan($T), #any_int cap: int, allocator: runtime.Allocator) -> (c: C, err: runtime.Allocator_Error)
+	where size_of(T) <= int(max(u16)) {
+	c.impl, err = create_raw_buffered(size_of(T), align_of(T), cap, allocator)
+	return
+}
+
+create_raw :: proc{
+	create_raw_unbuffered,
+	create_raw_buffered,
+}
+
+@(require_results)
+create_raw_unbuffered :: proc(#any_int msg_size, msg_alignment: int, allocator: runtime.Allocator) -> (c: ^Raw_Chan, err: runtime.Allocator_Error) {
+	assert(msg_size <= int(max(u16)))
+	align := max(align_of(Raw_Chan), msg_alignment)
+
+	size := mem.align_forward_int(size_of(Raw_Chan), align)
+	offset := size
+	size += msg_size
+	size = mem.align_forward_int(size, align)
+
+	ptr := mem.alloc(size, align, allocator) or_return
+	c = (^Raw_Chan)(ptr)
+	c.allocation_size = size
+	c.unbuffered_data = ([^]byte)(ptr)[offset:]
+	c.msg_size = u16(msg_size)
+	return
+}
+
+@(require_results)
+create_raw_buffered :: proc(#any_int msg_size, msg_alignment: int, #any_int cap: int, allocator: runtime.Allocator) -> (c: ^Raw_Chan, err: runtime.Allocator_Error) {
+	assert(msg_size <= int(max(u16)))
+	if cap <= 0 {
+		return create_raw_unbuffered(msg_size, msg_alignment, allocator)
+	}
+
+	align := max(align_of(Raw_Chan), msg_alignment, align_of(Raw_Queue))
+
+	size := mem.align_forward_int(size_of(Raw_Chan), align)
+	q_offset := size
+	size = mem.align_forward_int(q_offset + size_of(Raw_Queue), msg_alignment)
+	offset := size
+	size += msg_size * cap
+	size = mem.align_forward_int(size, align)
+
+	ptr := mem.alloc(size, align, allocator) or_return
+	c = (^Raw_Chan)(ptr)
+	c.allocation_size = size
+
+	bptr := ([^]byte)(ptr)
+
+	c.queue = (^Raw_Queue)(bptr[q_offset:])
+	c.msg_size = u16(msg_size)
+
+	raw_queue_init(c.queue, ([^]byte)(bptr[offset:]), cap, msg_size)
+	return
+}
+
+destroy :: proc(c: ^Raw_Chan) -> (err: runtime.Allocator_Error) {
+	if c != nil {
+		allocator := c.allocator
+		err = mem.free_with_size(c, c.allocation_size, allocator)
+	}
+	return
+}
+
+@(require_results)
+as_send :: #force_inline proc "contextless" (c: $C/Chan($T, $D)) -> (s: Chan(T, .Send)) where C.D <= .Both {
+	return transmute(type_of(s))c
+}
+@(require_results)
+as_recv :: #force_inline proc "contextless" (c: $C/Chan($T, $D)) -> (r: Chan(T, .Recv)) where C.D >= .Both {
+	return transmute(type_of(r))c
+}
+
+
+send :: proc "contextless" (c: $C/Chan($T, $D), data: T) -> (ok: bool) where C.D <= .Both {
+	data := data
+	ok = send_raw(c, &data)
+	return
+}
+
+@(require_results)
+try_send :: proc "contextless" (c: $C/Chan($T, $D), data: T) -> (ok: bool) where C.D <= .Both {
+	data := data
+	ok = try_send_raw(c, &data)
+	return
+}
+
+@(require_results)
+recv :: proc "contextless" (c: $C/Chan($T)) -> (data: T, ok: bool) where C.D >= .Both {
+	ok = recv_raw(c, &data)
+	return
+}
+
+
+@(require_results)
+try_recv :: proc "contextless" (c: $C/Chan($T)) -> (data: T, ok: bool) where C.D >= .Both {
+	ok = try_recv_raw(c, &data)
+	return
+}
+
+
+@(require_results)
+send_raw :: proc "contextless" (c: ^Raw_Chan, msg_in: rawptr) -> (ok: bool) {
+	if c == nil {
+		return
+	}
+	if c.queue != nil { // buffered
+		sync.guard(&c.mutex)
+		for c.queue.len == c.queue.cap {
+			sync.atomic_add(&c.w_waiting, 1)
+			sync.wait(&c.w_cond, &c.mutex)
+			sync.atomic_sub(&c.w_waiting, 1)
+		}
+
+		ok = raw_queue_push(c.queue, msg_in)
+		if sync.atomic_load(&c.r_waiting) > 0 {
+			sync.signal(&c.r_cond)
+		}
+	} else if c.unbuffered_data != nil { // unbuffered
+		sync.guard(&c.w_mutex)
+		sync.guard(&c.mutex)
+
+		if sync.atomic_load(&c.closed) {
+			return false
+		}
+
+		mem.copy(c.unbuffered_data, msg_in, int(c.msg_size))
+		sync.atomic_add(&c.w_waiting, 1)
+		if sync.atomic_load(&c.r_waiting) > 0 {
+			sync.signal(&c.r_cond)
+		}
+		sync.wait(&c.w_cond, &c.mutex)
+		ok = true
+	}
+	return
+}
+
+@(require_results)
+recv_raw :: proc "contextless" (c: ^Raw_Chan, msg_out: rawptr) -> (ok: bool) {
+	if c == nil {
+		return
+	}
+	if c.queue != nil { // buffered
+		sync.guard(&c.mutex)
+		for c.queue.len == 0 {
+			if sync.atomic_load(&c.closed) {
+				return
+			}
+
+			sync.atomic_add(&c.r_waiting, 1)
+			sync.wait(&c.r_cond, &c.mutex)
+			sync.atomic_sub(&c.r_waiting, 1)
+		}
+
+		msg := raw_queue_pop(c.queue)
+		if msg != nil {
+			mem.copy(msg_out, msg, int(c.msg_size))
+		}
+
+		if sync.atomic_load(&c.w_waiting) > 0 {
+			sync.signal(&c.w_cond)
+		}
+		ok = true
+	} else if c.unbuffered_data != nil { // unbuffered
+		sync.guard(&c.r_mutex)
+		sync.guard(&c.mutex)
+
+		for !sync.atomic_load(&c.closed) &&
+		    sync.atomic_load(&c.w_waiting) == 0 {
+			sync.atomic_add(&c.r_waiting, 1)
+			sync.wait(&c.r_cond, &c.mutex)
+			sync.atomic_sub(&c.r_waiting, 1)
+		}
+
+		if sync.atomic_load(&c.closed) {
+			return
+		}
+
+		mem.copy(msg_out, c.unbuffered_data, int(c.msg_size))
+		sync.atomic_sub(&c.w_waiting, 1)
+
+		sync.signal(&c.w_cond)
+		ok = true
+	}
+	return
+}
+
+
+@(require_results)
+try_send_raw :: proc "contextless" (c: ^Raw_Chan, msg_in: rawptr) -> (ok: bool) {
+	if c == nil {
+		return false
+	}
+	if c.queue != nil { // buffered
+		sync.guard(&c.mutex)
+		if c.queue.len == c.queue.cap {
+			return false
+		}
+
+		ok = raw_queue_push(c.queue, msg_in)
+		if sync.atomic_load(&c.r_waiting) > 0 {
+			sync.signal(&c.r_cond)
+		}
+	} else if c.unbuffered_data != nil { // unbuffered
+		sync.guard(&c.w_mutex)
+		sync.guard(&c.mutex)
+
+		if sync.atomic_load(&c.closed) {
+			return false
+		}
+
+		mem.copy(c.unbuffered_data, msg_in, int(c.msg_size))
+		sync.atomic_add(&c.w_waiting, 1)
+		if sync.atomic_load(&c.r_waiting) > 0 {
+			sync.signal(&c.r_cond)
+		}
+		sync.wait(&c.w_cond, &c.mutex)
+		ok = true
+	}
+	return
+}
+
+@(require_results)
+try_recv_raw :: proc "contextless" (c: ^Raw_Chan, msg_out: rawptr) -> bool {
+	if c == nil {
+		return false
+	}
+	if c.queue != nil { // buffered
+		sync.guard(&c.mutex)
+		if c.queue.len == 0 {
+			return false
+		}
+
+		msg := raw_queue_pop(c.queue)
+		if msg != nil {
+			mem.copy(msg_out, msg, int(c.msg_size))
+		}
+
+		if sync.atomic_load(&c.w_waiting) > 0 {
+			sync.signal(&c.w_cond)
+		}
+		return true
+	} else if c.unbuffered_data != nil { // unbuffered
+		sync.guard(&c.r_mutex)
+		sync.guard(&c.mutex)
+
+		if sync.atomic_load(&c.closed) ||
+		   sync.atomic_load(&c.w_waiting) == 0 {
+		   	return false
+		}
+
+		mem.copy(msg_out, c.unbuffered_data, int(c.msg_size))
+		sync.atomic_sub(&c.w_waiting, 1)
+
+		sync.signal(&c.w_cond)
+		return true
+	}
+	return false
+}
+
+
+
+@(require_results)
+is_buffered :: proc "contextless" (c: ^Raw_Chan) -> bool {
+	return c != nil && c.queue != nil
+}
+
+@(require_results)
+is_unbuffered :: proc "contextless" (c: ^Raw_Chan) -> bool {
+	return c != nil && c.unbuffered_data != nil
+}
+
+@(require_results)
+len :: proc "contextless" (c: ^Raw_Chan) -> int {
+	if c != nil && c.queue != nil {
+		sync.guard(&c.mutex)
+		return c.queue.len
+	}
+	return 0
+}
+
+@(require_results)
+cap :: proc "contextless" (c: ^Raw_Chan) -> int {
+	if c != nil && c.queue != nil {
+		sync.guard(&c.mutex)
+		return c.queue.cap
+	}
+	return 0
+}
+
+close :: proc "contextless" (c: ^Raw_Chan) -> bool {
+	if c == nil {
+		return false
+	}
+	sync.guard(&c.mutex)
+	if sync.atomic_load(&c.closed) {
+		return false
+	}
+	sync.atomic_store(&c.closed, true)
+	sync.broadcast(&c.r_cond)
+	sync.broadcast(&c.w_cond)
+	return true
+}
+
+@(require_results)
+is_closed :: proc "contextless" (c: ^Raw_Chan) -> bool {
+	if c == nil {
+		return true
+	}
+	sync.guard(&c.mutex)
+	return bool(sync.atomic_load(&c.closed))
+}
+
+
+
+
+Raw_Queue :: struct {
+	data: [^]byte,
+	len:  int,
+	cap:  int,
+	next: int,
+	size: int, // element size
+}
+
+raw_queue_init :: proc "contextless" (q: ^Raw_Queue, data: rawptr, cap: int, size: int) {
+	q.data = ([^]byte)(data)
+	q.len  = 0
+	q.cap  = cap
+	q.next = 0
+	q.size = size
+}
+
+
+@(require_results)
+raw_queue_push :: proc "contextless" (q: ^Raw_Queue, data: rawptr) -> bool {
+	if q.len == q.cap {
+		return false
+	}
+	pos := q.next + q.len
+	if pos >= q.cap {
+		pos -= q.cap
+	}
+
+	val_ptr := q.data[pos*q.size:]
+	mem.copy(val_ptr, data, q.size)
+	q.len += 1
+	return true
+}
+
+@(require_results)
+raw_queue_pop :: proc "contextless" (q: ^Raw_Queue) -> (data: rawptr) {
+	if q.len > 0 {
+		data = q.data[q.next*q.size:]
+		q.next += 1
+		q.len -= 1
+		if q.next >= q.cap {
+			q.next -= q.cap
+		}
+	}
+	return
+}
+
+
+@(require_results)
+can_recv :: proc "contextless" (c: ^Raw_Chan) -> bool {
+	if is_buffered(c) {
+		return len(c) > 0
+	}
+	sync.guard(&c.mutex)
+	return sync.atomic_load(&c.w_waiting) > 0
+}
+
+
+@(require_results)
+can_send :: proc "contextless" (c: ^Raw_Chan) -> bool {
+	if is_buffered(c) {
+		sync.guard(&c.mutex)
+		return len(c) < cap(c)
+	}
+	sync.guard(&c.mutex)
+	return sync.atomic_load(&c.r_waiting) > 0
+}
+
+
+
+@(require_results)
+select_raw :: proc "odin" (recvs: []^Raw_Chan, sends: []^Raw_Chan, send_msgs: []rawptr, recv_out: rawptr) -> (select_idx: int, ok: bool) #no_bounds_check {
+	Select_Op :: struct {
+		idx:     int, // local to the slice that was given
+		is_recv: bool,
+	}
+
+	candidate_count := builtin.len(recvs)+builtin.len(sends)
+	candidates := ([^]Select_Op)(intrinsics.alloca(candidate_count*size_of(Select_Op), align_of(Select_Op)))
+	count := 0
+
+	for c, i in recvs {
+		if can_recv(c) {
+			candidates[count] = {
+				is_recv = true,
+				idx     = i,
+			}
+			count += 1
+		}
+	}
+
+	for c, i in sends {
+		if can_send(c) {
+			candidates[count] = {
+				is_recv = false,
+				idx     = i,
+			}
+			count += 1
+		}
+	}
+
+	if count == 0 {
+		return
+	}
+
+	r: ^rand.Rand = nil
+
+
+	select_idx = rand.int_max(count, r) if count > 0 else 0
+
+	sel := candidates[select_idx]
+	if sel.is_recv {
+		ok = recv_raw(recvs[sel.idx], recv_out)
+	} else {
+		ok = send_raw(sends[sel.idx], send_msgs[sel.idx])
+	}
+	return
+}

+ 24 - 0
core/sync/extended.odin

@@ -417,4 +417,28 @@ unpark :: proc "contextless" (p: ^Parker)  {
 	if atomic_exchange_explicit(&p.state, NOTIFIED, .Release) == PARKED {
 		futex_signal(&p.state)
 	}
+}
+
+
+
+// A One_Shot_Event is an associated token which is initially not present:
+//     * The `one_shot_event_wait` blocks the current thread until the event
+//       is made available
+//     * The `one_shot_event_signal` procedure automatically makes the token
+//       available if its was not already.
+One_Shot_Event :: struct #no_copy {
+	state: Futex,
+}
+
+// Blocks the current thread until the event is made available with `one_shot_event_signal`.
+one_shot_event_wait :: proc "contextless" (e: ^One_Shot_Event) {
+	for atomic_load_explicit(&e.state, .Acquire) == 0 {
+		futex_wait(&e.state, 1)
+	}
+}
+
+// Releases any threads that are currently blocked by this event with `one_shot_event_wait`.
+one_shot_event_signal :: proc "contextless" (e: ^One_Shot_Event) {
+	atomic_store_explicit(&e.state, 1, .Release)
+	futex_broadcast(&e.state)
 }

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

@@ -944,8 +944,8 @@ Socket_Type :: enum {
 	Bits for Socket_FD_Flags
 */
 Socket_FD_Flags_Bits :: enum {
-	NONBLOCK  = 14,
-	CLOEXEC   = 25,
+	NONBLOCK  = 11,
+	CLOEXEC   = 19,
 }
 
 /*

+ 9 - 9
core/sys/unix/pthread_darwin.odin

@@ -17,42 +17,42 @@ PTHREAD_RWLOCKATTR_SIZE :: 16
 
 pthread_t :: distinct u64
 
-pthread_attr_t :: struct #align(16) {
+pthread_attr_t :: struct {
 	sig: c.long,
 	_: [PTHREAD_ATTR_SIZE] c.char,
 }
 
-pthread_cond_t :: struct #align(16) {
+pthread_cond_t :: struct {
 	sig: c.long,
 	_: [PTHREAD_COND_SIZE] c.char,
 }
 
-pthread_condattr_t :: struct #align(16) {
+pthread_condattr_t :: struct {
 	sig: c.long,
 	_: [PTHREAD_CONDATTR_SIZE] c.char,
 }
 
-pthread_mutex_t :: struct #align(16) {
+pthread_mutex_t :: struct {
 	sig: c.long,
 	_: [PTHREAD_MUTEX_SIZE] c.char,
 }
 
-pthread_mutexattr_t :: struct #align(16) {
+pthread_mutexattr_t :: struct {
 	sig: c.long,
 	_: [PTHREAD_MUTEXATTR_SIZE] c.char,
 }
 
-pthread_once_t :: struct #align(16) {
+pthread_once_t :: struct {
 	sig: c.long,
 	_: [PTHREAD_ONCE_SIZE] c.char,
 }
 
-pthread_rwlock_t :: struct #align(16) {
+pthread_rwlock_t :: struct {
 	sig: c.long,
 	_: [PTHREAD_RWLOCK_SIZE] c.char,
 }
 
-pthread_rwlockattr_t :: struct #align(16) {
+pthread_rwlockattr_t :: struct {
 	sig: c.long,
 	_: [PTHREAD_RWLOCKATTR_SIZE] c.char,
 }
@@ -93,4 +93,4 @@ foreign pthread {
 	pthread_setcancelstate :: proc (state: c.int, old_state: ^c.int) -> c.int ---
 	pthread_setcanceltype  :: proc (type:  c.int, old_type:  ^c.int) -> c.int ---
 	pthread_cancel         :: proc (thread: pthread_t) -> c.int ---
-}
+}

+ 1067 - 1061
src/bug_report.cpp

@@ -1,1061 +1,1067 @@
-/*
-	Gather and print platform and version info to help with reporting Odin bugs.
-*/
-
-#if !defined(GB_COMPILER_MSVC)
-	#if defined(GB_CPU_X86)
-		#include <cpuid.h>
-	#endif
-#endif
-
-#if defined(GB_SYSTEM_LINUX)
-	#include <sys/utsname.h>
-	#include <sys/sysinfo.h>
-#endif
-
-#if defined(GB_SYSTEM_OSX)
-	#include <sys/sysctl.h>
-#endif
-
-#if defined(GB_SYSTEM_OPENBSD)
-	#include <sys/sysctl.h>
-	#include <sys/utsname.h>
-#endif
-
-#if defined(GB_SYSTEM_FREEBSD)
-	#include <sys/sysctl.h>
-#endif
-
-/*
-	NOTE(Jeroen): This prints the Windows product edition only, to be called from `print_platform_details`.
-*/
-#if defined(GB_SYSTEM_WINDOWS)
-gb_internal void report_windows_product_type(DWORD ProductType) {
-	switch (ProductType) {
-	case PRODUCT_ULTIMATE:
-		gb_printf("Ultimate");
-		break;
-
-	case PRODUCT_HOME_BASIC:
-		gb_printf("Home Basic");
-		break;
-
-	case PRODUCT_HOME_PREMIUM:
-		gb_printf("Home Premium");
-		break;
-
-	case PRODUCT_ENTERPRISE:
-		gb_printf("Enterprise");
-		break;
-
-	case PRODUCT_CORE:
-		gb_printf("Home Basic");
-		break;
-
-	case PRODUCT_HOME_BASIC_N:
-		gb_printf("Home Basic N");
-		break;
-
-	case PRODUCT_EDUCATION:
-		gb_printf("Education");
-		break;
-
-	case PRODUCT_EDUCATION_N:
-		gb_printf("Education N");
-		break;
-
-	case PRODUCT_BUSINESS:
-		gb_printf("Business");
-		break;
-
-	case PRODUCT_STANDARD_SERVER:
-		gb_printf("Standard Server");
-		break;
-
-	case PRODUCT_DATACENTER_SERVER:
-		gb_printf("Datacenter");
-		break;
-
-	case PRODUCT_SMALLBUSINESS_SERVER:
-		gb_printf("Windows Small Business Server");
-		break;
-
-	case PRODUCT_ENTERPRISE_SERVER:
-		gb_printf("Enterprise Server");
-		break;
-
-	case PRODUCT_STARTER:
-		gb_printf("Starter");
-		break;
-
-	case PRODUCT_DATACENTER_SERVER_CORE:
-		gb_printf("Datacenter Server Core");
-		break;
-
-	case PRODUCT_STANDARD_SERVER_CORE:
-		gb_printf("Server Standard Core");
-		break;
-
-	case PRODUCT_ENTERPRISE_SERVER_CORE:
-		gb_printf("Enterprise Server Core");
-		break;
-
-	case PRODUCT_BUSINESS_N:
-		gb_printf("Business N");
-		break;
-
-	case PRODUCT_HOME_SERVER:
-		gb_printf("Home Server");
-		break;
-
-	case PRODUCT_SERVER_FOR_SMALLBUSINESS:
-		gb_printf("Windows Server 2008 for Windows Essential Server Solutions");
-		break;
-
-	case PRODUCT_SMALLBUSINESS_SERVER_PREMIUM:
-		gb_printf("Small Business Server Premium");
-		break;
-
-	case PRODUCT_HOME_PREMIUM_N:
-		gb_printf("Home Premium N");
-		break;
-
-	case PRODUCT_ENTERPRISE_N:
-		gb_printf("Enterprise N");
-		break;
-
-	case PRODUCT_ULTIMATE_N:
-		gb_printf("Ultimate N");
-		break;
-
-	case PRODUCT_HYPERV:
-		gb_printf("HyperV");
-		break;
-
-	case PRODUCT_STARTER_N:
-		gb_printf("Starter N");
-		break;
-
-	case PRODUCT_PROFESSIONAL:
-		gb_printf("Professional");
-		break;
-
-	case PRODUCT_PROFESSIONAL_N:
-		gb_printf("Professional N");
-		break;
-
-	case PRODUCT_UNLICENSED:
-		gb_printf("Unlicensed");
-		break;
-
-	default:
-		gb_printf("Unknown Edition (%08x)", cast(unsigned)ProductType);
-	}
-}
-#endif
-
-gb_internal void odin_cpuid(int leaf, int result[]) {
-	#if defined(GB_CPU_ARM)
-		return;
-
-	#elif defined(GB_CPU_X86)
-	
-		#if defined(GB_COMPILER_MSVC)
-			__cpuid(result, leaf);
-		#else
-			__get_cpuid(leaf, (unsigned int*)&result[0], (unsigned int*)&result[1], (unsigned int*)&result[2], (unsigned int*)&result[3]);
-		#endif
-
-	#endif
-}
-
-gb_internal void report_cpu_info() {
-	gb_printf("\tCPU:  ");
-
-	#if defined(GB_CPU_X86)
-
-	/*
-		Get extended leaf info
-	*/
-	int cpu[4];
-
-	odin_cpuid(0x80000000, &cpu[0]);
-	int number_of_extended_ids = cpu[0];
-
-	int brand[0x12] = {};
-
-	/*
-		Read CPU brand if supported.
-	*/
-	if (number_of_extended_ids >= 0x80000004) {
-		odin_cpuid(0x80000002, &brand[0]);
-		odin_cpuid(0x80000003, &brand[4]);
-		odin_cpuid(0x80000004, &brand[8]);
-
-		/*
-			Some CPUs like `      Intel(R) Xeon(R) CPU E5-1650 v2 @ 3.50GHz` may include leading spaces. Trim them.
-		*/
-		char * brand_name = (char *)&brand[0];
-		for (; brand_name[0] == ' '; brand_name++) {}
-
-		gb_printf("%s\n", brand_name);
-	} else {
-		gb_printf("Unable to retrieve.\n");
-	}
-
-	#elif defined(GB_CPU_ARM)
-		/*
-			TODO(Jeroen): On *nix, perhaps query `/proc/cpuinfo`.
-		*/
-		#if defined(GB_ARCH_64_BIT)
-			gb_printf("ARM64\n");
-		#else
-			gb_printf("ARM\n");
-		#endif
-	#else
-		gb_printf("Unknown\n");
-	#endif
-}
-
-/*
-	Report the amount of installed RAM.
-*/
-gb_internal void report_ram_info() {
-	gb_printf("\tRAM:  ");
-
-	#if defined(GB_SYSTEM_WINDOWS)
-		MEMORYSTATUSEX statex;
-		statex.dwLength = sizeof(statex);
-		GlobalMemoryStatusEx (&statex);
-
-		gb_printf("%lld MiB\n", statex.ullTotalPhys / gb_megabytes(1));
-
-	#elif defined(GB_SYSTEM_LINUX)
-		/*
-			Retrieve RAM info using `sysinfo()`, 
-		*/
-		struct sysinfo info;
-		int result = sysinfo(&info);
-
-		if (result == 0x0) {
-			gb_printf("%lu MiB\n", info.totalram * info.mem_unit / gb_megabytes(1));
-		} else {
-			gb_printf("Unknown.\n");
-		}
-	#elif defined(GB_SYSTEM_OSX)
-		uint64_t ram_amount;
-		size_t   val_size = sizeof(ram_amount);
-
-		int mibs[] = { CTL_HW, HW_MEMSIZE };
-		if (sysctl(mibs, 2, &ram_amount, &val_size, NULL, 0) != -1) {
-			gb_printf("%lld MiB\n", ram_amount / gb_megabytes(1));
-		}
-	#elif defined(GB_SYSTEM_OPENBSD)
-		uint64_t ram_amount;
-		size_t   val_size = sizeof(ram_amount);
-
-		int mibs[] = { CTL_HW, HW_PHYSMEM64 };
-		if (sysctl(mibs, 2, &ram_amount, &val_size, NULL, 0) != -1) {
-			gb_printf("%lld MiB\n", ram_amount / gb_megabytes(1));
-		}
-	#elif defined(GB_SYSTEM_FREEBSD)
-		uint64_t ram_amount;
-		size_t   val_size = sizeof(ram_amount);
-
-		int mibs[] = { CTL_HW, HW_PHYSMEM };
-		if (sysctl(mibs, 2, &ram_amount, &val_size, NULL, 0) != -1) {
-			gb_printf("%lu MiB\n", ram_amount / gb_megabytes(1));
-		}
-	#else
-		gb_printf("Unknown.\n");
-	#endif
-}
-
-gb_internal void report_os_info() {
-	gb_printf("\tOS:   ");
-
-	#if defined(GB_SYSTEM_WINDOWS)
-	/*
-		NOTE(Jeroen): 
-			`GetVersionEx`  will return 6.2 for Windows 10 unless the program is manifested for Windows 10.
-			`RtlGetVersion` will return the true version.
-
-			Rather than include the WinDDK, we ask the kernel directly.
-
-			`HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion` is for the minor build version (Update Build Release)
-
-	*/
-	OSVERSIONINFOEXW osvi;
-	ZeroMemory(&osvi, sizeof(OSVERSIONINFOEXW));
-	osvi.dwOSVersionInfoSize = sizeof(OSVERSIONINFOEXW);
-
-	typedef NTSTATUS (WINAPI* RtlGetVersionPtr)(OSVERSIONINFOW*);
-	typedef BOOL (WINAPI* GetProductInfoPtr)(DWORD dwOSMajorVersion, DWORD dwOSMinorVersion, DWORD dwSpMajorVersion, DWORD dwSpMinorVersion, PDWORD pdwReturnedProductType);
-
-	// https://docs.microsoft.com/en-us/windows-hardware/drivers/ddi/wdm/nf-wdm-rtlgetversion
-	RtlGetVersionPtr  RtlGetVersion  =  (RtlGetVersionPtr)GetProcAddress(GetModuleHandle(TEXT("ntdll.dll")), "RtlGetVersion");
-	// https://docs.microsoft.com/en-us/windows/win32/api/sysinfoapi/nf-sysinfoapi-getproductinfo
-	GetProductInfoPtr GetProductInfo = (GetProductInfoPtr)GetProcAddress(GetModuleHandle(TEXT("kernel32.dll")), "GetProductInfo");
-
-	NTSTATUS status  = {};
-	DWORD ProductType = {};
-	if (RtlGetVersion != nullptr) {
-		status = RtlGetVersion((OSVERSIONINFOW*)&osvi);
-	}
-
-	if (RtlGetVersion == nullptr || status != 0x0) {
-		gb_printf("Windows (Unknown Version)");
-	} else {
-		if (GetProductInfo != nullptr) {
-			GetProductInfo(osvi.dwMajorVersion, osvi.dwMinorVersion, osvi.wServicePackMajor, osvi.wServicePackMinor, &ProductType);
-		}
-
-		if (false) {
-			gb_printf("dwMajorVersion:    %u\n", cast(unsigned)osvi.dwMajorVersion);
-			gb_printf("dwMinorVersion:    %u\n", cast(unsigned)osvi.dwMinorVersion);
-			gb_printf("dwBuildNumber:     %u\n", cast(unsigned)osvi.dwBuildNumber);
-			gb_printf("dwPlatformId:      %u\n", cast(unsigned)osvi.dwPlatformId);
-			gb_printf("wServicePackMajor: %u\n", cast(unsigned)osvi.wServicePackMajor);
-			gb_printf("wServicePackMinor: %u\n", cast(unsigned)osvi.wServicePackMinor);
-			gb_printf("wSuiteMask:        %u\n", cast(unsigned)osvi.wSuiteMask);
-			gb_printf("wProductType:      %u\n", cast(unsigned)osvi.wProductType);
-		}
-
-		gb_printf("Windows ");
-
-		switch (osvi.dwMajorVersion) {
-		case 10:
-			/*
-				Windows 10 (Pro), Windows 2016 Server, Windows 2019 Server, Windows 2022 Server
-			*/
-			switch (osvi.wProductType) {
-			case VER_NT_WORKSTATION: // Workstation
-				if (osvi.dwBuildNumber < 22000) {
-					gb_printf("10 ");
-				} else {
-					gb_printf("11 ");
-				}
-				
-				report_windows_product_type(ProductType);
-
-				break;
-			default: // Server or Domain Controller
-				switch(osvi.dwBuildNumber) {
-				case 14393:
-					gb_printf("2016 Server");
-					break;
-				case 17763:
-					gb_printf("2019 Server");
-					break;
-				case 20348:
-					gb_printf("2022 Server");
-					break;
-				default:
-					gb_printf("Unknown Server");
-					break;
-				}
-			}
-			break;
-		case 6:
-			switch (osvi.dwMinorVersion) {
-				case 0:
-					switch (osvi.wProductType) {
-						case VER_NT_WORKSTATION:
-							gb_printf("Windows Vista ");
-							report_windows_product_type(ProductType);
-							break;
-						case 3:
-							gb_printf("Windows Server 2008");
-							break;
-					}
-					break;
-
-				case 1:
-					switch (osvi.wProductType) {
-						case VER_NT_WORKSTATION:
-							gb_printf("Windows 7 ");
-							report_windows_product_type(ProductType);
-							break;
-						case 3:
-							gb_printf("Windows Server 2008 R2");
-							break;
-					}
-					break;
-				case 2:
-					switch (osvi.wProductType) {
-						case VER_NT_WORKSTATION:
-							gb_printf("Windows 8 ");
-							report_windows_product_type(ProductType);
-							break;
-						case 3:
-							gb_printf("Windows Server 2012");
-							break;
-					}
-					break;
-				case 3:
-					switch (osvi.wProductType) {
-						case VER_NT_WORKSTATION:
-							gb_printf("Windows 8.1 ");
-							report_windows_product_type(ProductType);
-							break;
-						case 3:
-							gb_printf("Windows Server 2012 R2");
-							break;
-					}
-					break;
-			}
-			break;
-		case 5:
-			switch (osvi.dwMinorVersion) {
-				case 0:
-					gb_printf("Windows 2000");
-					break;
-				case 1:
-					gb_printf("Windows XP");
-					break;
-				case 2:
-					gb_printf("Windows Server 2003");
-					break;
-			}
-			break;
-		default:
-			break;
-		}
-
-		/*
-			Grab Windows DisplayVersion (like 20H02)
-		*/
-		LPDWORD ValueType = {};
-		DWORD   UBR;
-		char    DisplayVersion[256];
-		DWORD   ValueSize = 256;
-
-		status = RegGetValue(
-			HKEY_LOCAL_MACHINE,
-			TEXT("SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion"),
-			TEXT("DisplayVersion"),
-			RRF_RT_REG_SZ,
-			ValueType,
-			DisplayVersion,
-			&ValueSize
-		);
-
-		if (status == 0x0) {
-			gb_printf(" (version: %s)", DisplayVersion);
-		}
-
-		/*
-			Now print build number.
-		*/
-		gb_printf(", build %u", cast(unsigned)osvi.dwBuildNumber);
-
-		ValueSize = sizeof(UBR);
-		status = RegGetValue(
-			HKEY_LOCAL_MACHINE,
-			TEXT("SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion"),
-			TEXT("UBR"),
-			RRF_RT_REG_DWORD,
-			ValueType,
-			&UBR,
-			&ValueSize
-		);
-
-		if (status == 0x0) {
-			gb_printf(".%u", cast(unsigned)UBR);
-		}
-		gb_printf("\n");
-	}
-	#elif defined(GB_SYSTEM_LINUX)
-		/*
-			Try to parse `/etc/os-release` for `PRETTY_NAME="Ubuntu 20.04.3 LTS`
-		*/
-		gbAllocator a = heap_allocator();
-
-		gbFileContents release = gb_file_read_contents(a, 1, "/etc/os-release");
-		defer (gb_file_free_contents(&release));
-
-		b32 found = 0;
-		if (release.size) {
-			char *start        = (char *)release.data;
-			char *end          = (char *)release.data + release.size;
-			const char *needle = "PRETTY_NAME=\"";
-			isize needle_len   = gb_strlen((needle));
-		
-			char *c = start;
-			for (; c < end; c++) {
-				if (gb_strncmp(c, needle, needle_len) == 0) {
-					found = 1;
-					start = c + needle_len;
-					break;
-				}
-			}
-
-			if (found) {
-				for (c = start; c < end; c++) {
-					if (*c == '"') {
-						// Found the closing quote. Replace it with \0
-						*c = 0;
-						gb_printf("%s", (char *)start);
-						break;
-					} else if (*c == '\n') {
-						found = 0;
-					}
-				}
-			}
-		}
-
-		if (!found) {
-			gb_printf("Unknown Linux Distro");
-		}
-
-		/*
-			Print kernel info using `uname()` syscall, https://linux.die.net/man/2/uname
-		*/
-		char buffer[1024];
-		uname((struct utsname *)&buffer[0]);
-
-		struct utsname *info;
-		info = (struct utsname *)&buffer[0];
-
-		gb_printf(", %s %s\n", info->sysname, info->release);
-
-	#elif defined(GB_SYSTEM_OSX)
-		struct Darwin_To_Release {
-			const char* build;    // 21G83
-			int   darwin[3];      // Darwin kernel triplet
-			const char* os_name;  // OS X, MacOS
-			struct {
-				const char* name; // Monterey, Mojave, etc.
-				int version[3];   // 12.4, etc.
-			} release;
-		};
-
-		Darwin_To_Release macos_release_map[] = {
-			{"8A428",    { 8,  0,  0}, "macOS", {"Tiger",         {10,  4,  0}}},
-			{"8A432",    { 8,  0,  0}, "macOS", {"Tiger",         {10,  4,  0}}},
-			{"8B15",     { 8,  1,  0}, "macOS", {"Tiger",         {10,  4,  1}}},
-			{"8B17",     { 8,  1,  0}, "macOS", {"Tiger",         {10,  4,  1}}},
-			{"8C46",     { 8,  2,  0}, "macOS", {"Tiger",         {10,  4,  2}}},
-			{"8C47",     { 8,  2,  0}, "macOS", {"Tiger",         {10,  4,  2}}},
-			{"8E102",    { 8,  2,  0}, "macOS", {"Tiger",         {10,  4,  2}}},
-			{"8E45",     { 8,  2,  0}, "macOS", {"Tiger",         {10,  4,  2}}},
-			{"8E90",     { 8,  2,  0}, "macOS", {"Tiger",         {10,  4,  2}}},
-			{"8F46",     { 8,  3,  0}, "macOS", {"Tiger",         {10,  4,  3}}},
-			{"8G32",     { 8,  4,  0}, "macOS", {"Tiger",         {10,  4,  4}}},
-			{"8G1165",   { 8,  4,  0}, "macOS", {"Tiger",         {10,  4,  4}}},
-			{"8H14",     { 8,  5,  0}, "macOS", {"Tiger",         {10,  4,  5}}},
-			{"8G1454",   { 8,  5,  0}, "macOS", {"Tiger",         {10,  4,  5}}},
-			{"8I127",    { 8,  6,  0}, "macOS", {"Tiger",         {10,  4,  6}}},
-			{"8I1119",   { 8,  6,  0}, "macOS", {"Tiger",         {10,  4,  6}}},
-			{"8J135",    { 8,  7,  0}, "macOS", {"Tiger",         {10,  4,  7}}},
-			{"8J2135a",  { 8,  7,  0}, "macOS", {"Tiger",         {10,  4,  7}}},
-			{"8K1079",   { 8,  7,  0}, "macOS", {"Tiger",         {10,  4,  7}}},
-			{"8N5107",   { 8,  7,  0}, "macOS", {"Tiger",         {10,  4,  7}}},
-			{"8L127",    { 8,  8,  0}, "macOS", {"Tiger",         {10,  4,  8}}},
-			{"8L2127",   { 8,  8,  0}, "macOS", {"Tiger",         {10,  4,  8}}},
-			{"8P135",    { 8,  9,  0}, "macOS", {"Tiger",         {10,  4,  9}}},
-			{"8P2137",   { 8,  9,  0}, "macOS", {"Tiger",         {10,  4,  9}}},
-			{"8R218",    { 8, 10,  0}, "macOS", {"Tiger",         {10,  4, 10}}},
-			{"8R2218",   { 8, 10,  0}, "macOS", {"Tiger",         {10,  4, 10}}},
-			{"8R2232",   { 8, 10,  0}, "macOS", {"Tiger",         {10,  4, 10}}},
-			{"8S165",    { 8, 11,  0}, "macOS", {"Tiger",         {10,  4, 11}}},
-			{"8S2167",   { 8, 11,  0}, "macOS", {"Tiger",         {10,  4, 11}}},
-			{"9A581",    { 9,  0,  0}, "macOS", {"Leopard",       {10,  5,  0}}},
-			{"9B18",     { 9,  1,  0}, "macOS", {"Leopard",       {10,  5,  1}}},
-			{"9B2117",   { 9,  1,  1}, "macOS", {"Leopard",       {10,  5,  1}}},
-			{"9C31",     { 9,  2,  0}, "macOS", {"Leopard",       {10,  5,  2}}},
-			{"9C7010",   { 9,  2,  0}, "macOS", {"Leopard",       {10,  5,  2}}},
-			{"9D34",     { 9,  3,  0}, "macOS", {"Leopard",       {10,  5,  3}}},
-			{"9E17",     { 9,  4,  0}, "macOS", {"Leopard",       {10,  5,  4}}},
-			{"9F33",     { 9,  5,  0}, "macOS", {"Leopard",       {10,  5,  5}}},
-			{"9G55",     { 9,  6,  0}, "macOS", {"Leopard",       {10,  5,  6}}},
-			{"9G66",     { 9,  6,  0}, "macOS", {"Leopard",       {10,  5,  6}}},
-			{"9G71",     { 9,  6,  0}, "macOS", {"Leopard",       {10,  5,  6}}},
-			{"9J61",     { 9,  7,  0}, "macOS", {"Leopard",       {10,  5,  7}}},
-			{"9L30",     { 9,  8,  0}, "macOS", {"Leopard",       {10,  5,  8}}},
-			{"9L34",     { 9,  8,  0}, "macOS", {"Leopard",       {10,  5,  8}}},
-			{"10A432",   {10,  0,  0}, "macOS", {"Snow Leopard",  {10,  6,  0}}},
-			{"10A433",   {10,  0,  0}, "macOS", {"Snow Leopard",  {10,  6,  0}}},
-			{"10B504",   {10,  1,  0}, "macOS", {"Snow Leopard",  {10,  6,  1}}},
-			{"10C540",   {10,  2,  0}, "macOS", {"Snow Leopard",  {10,  6,  2}}},
-			{"10D573",   {10,  3,  0}, "macOS", {"Snow Leopard",  {10,  6,  3}}},
-			{"10D575",   {10,  3,  0}, "macOS", {"Snow Leopard",  {10,  6,  3}}},
-			{"10D578",   {10,  3,  0}, "macOS", {"Snow Leopard",  {10,  6,  3}}},
-			{"10F569",   {10,  4,  0}, "macOS", {"Snow Leopard",  {10,  6,  4}}},
-			{"10H574",   {10,  5,  0}, "macOS", {"Snow Leopard",  {10,  6,  5}}},
-			{"10J567",   {10,  6,  0}, "macOS", {"Snow Leopard",  {10,  6,  6}}},
-			{"10J869",   {10,  7,  0}, "macOS", {"Snow Leopard",  {10,  6,  7}}},
-			{"10J3250",  {10,  7,  0}, "macOS", {"Snow Leopard",  {10,  6,  7}}},
-			{"10J4138",  {10,  7,  0}, "macOS", {"Snow Leopard",  {10,  6,  7}}},
-			{"10K540",   {10,  8,  0}, "macOS", {"Snow Leopard",  {10,  6,  8}}},
-			{"10K549",   {10,  8,  0}, "macOS", {"Snow Leopard",  {10,  6,  8}}},
-			{"11A511",   {11,  0,  0}, "macOS", {"Lion",          {10,  7,  0}}},
-			{"11A511s",  {11,  0,  0}, "macOS", {"Lion",          {10,  7,  0}}},
-			{"11A2061",  {11,  0,  2}, "macOS", {"Lion",          {10,  7,  0}}},
-			{"11A2063",  {11,  0,  2}, "macOS", {"Lion",          {10,  7,  0}}},
-			{"11B26",    {11,  1,  0}, "macOS", {"Lion",          {10,  7,  1}}},
-			{"11B2118",  {11,  1,  0}, "macOS", {"Lion",          {10,  7,  1}}},
-			{"11C74",    {11,  2,  0}, "macOS", {"Lion",          {10,  7,  2}}},
-			{"11D50",    {11,  3,  0}, "macOS", {"Lion",          {10,  7,  3}}},
-			{"11E53",    {11,  4,  0}, "macOS", {"Lion",          {10,  7,  4}}},
-			{"11G56",    {11,  4,  2}, "macOS", {"Lion",          {10,  7,  5}}},
-			{"11G63",    {11,  4,  2}, "macOS", {"Lion",          {10,  7,  5}}},
-			{"12A269",   {12,  0,  0}, "macOS", {"Mountain Lion", {10,  8,  0}}},
-			{"12B19",    {12,  1,  0}, "macOS", {"Mountain Lion", {10,  8,  1}}},
-			{"12C54",    {12,  2,  0}, "macOS", {"Mountain Lion", {10,  8,  2}}},
-			{"12C60",    {12,  2,  0}, "macOS", {"Mountain Lion", {10,  8,  2}}},
-			{"12C2034",  {12,  2,  0}, "macOS", {"Mountain Lion", {10,  8,  2}}},
-			{"12C3104",  {12,  2,  0}, "macOS", {"Mountain Lion", {10,  8,  2}}},
-			{"12D78",    {12,  3,  0}, "macOS", {"Mountain Lion", {10,  8,  3}}},
-			{"12E55",    {12,  4,  0}, "macOS", {"Mountain Lion", {10,  8,  4}}},
-			{"12E3067",  {12,  4,  0}, "macOS", {"Mountain Lion", {10,  8,  4}}},
-			{"12E4022",  {12,  4,  0}, "macOS", {"Mountain Lion", {10,  8,  4}}},
-			{"12F37",    {12,  5,  0}, "macOS", {"Mountain Lion", {10,  8,  5}}},
-			{"12F45",    {12,  5,  0}, "macOS", {"Mountain Lion", {10,  8,  5}}},
-			{"12F2501",  {12,  5,  0}, "macOS", {"Mountain Lion", {10,  8,  5}}},
-			{"12F2518",  {12,  5,  0}, "macOS", {"Mountain Lion", {10,  8,  5}}},
-			{"12F2542",  {12,  5,  0}, "macOS", {"Mountain Lion", {10,  8,  5}}},
-			{"12F2560",  {12,  5,  0}, "macOS", {"Mountain Lion", {10,  8,  5}}},
-			{"13A603",   {13,  0,  0}, "macOS", {"Mavericks",     {10,  9,  0}}},
-			{"13B42",    {13,  0,  0}, "macOS", {"Mavericks",     {10,  9,  1}}},
-			{"13C64",    {13,  1,  0}, "macOS", {"Mavericks",     {10,  9,  2}}},
-			{"13C1021",  {13,  1,  0}, "macOS", {"Mavericks",     {10,  9,  2}}},
-			{"13D65",    {13,  2,  0}, "macOS", {"Mavericks",     {10,  9,  3}}},
-			{"13E28",    {13,  3,  0}, "macOS", {"Mavericks",     {10,  9,  4}}},
-			{"13F34",    {13,  4,  0}, "macOS", {"Mavericks",     {10,  9,  5}}},
-			{"13F1066",  {13,  4,  0}, "macOS", {"Mavericks",     {10,  9,  5}}},
-			{"13F1077",  {13,  4,  0}, "macOS", {"Mavericks",     {10,  9,  5}}},
-			{"13F1096",  {13,  4,  0}, "macOS", {"Mavericks",     {10,  9,  5}}},
-			{"13F1112",  {13,  4,  0}, "macOS", {"Mavericks",     {10,  9,  5}}},
-			{"13F1134",  {13,  4,  0}, "macOS", {"Mavericks",     {10,  9,  5}}},
-			{"13F1507",  {13,  4,  0}, "macOS", {"Mavericks",     {10,  9,  5}}},
-			{"13F1603",  {13,  4,  0}, "macOS", {"Mavericks",     {10,  9,  5}}},
-			{"13F1712",  {13,  4,  0}, "macOS", {"Mavericks",     {10,  9,  5}}},
-			{"13F1808",  {13,  4,  0}, "macOS", {"Mavericks",     {10,  9,  5}}},
-			{"13F1911",  {13,  4,  0}, "macOS", {"Mavericks",     {10,  9,  5}}},
-			{"14A389",   {14,  0,  0}, "macOS", {"Yosemite",      {10, 10,  0}}},
-			{"14B25",    {14,  0,  0}, "macOS", {"Yosemite",      {10, 10,  1}}},
-			{"14C109",   {14,  1,  0}, "macOS", {"Yosemite",      {10, 10,  2}}},
-			{"14C1510",  {14,  1,  0}, "macOS", {"Yosemite",      {10, 10,  2}}},
-			{"14C2043",  {14,  1,  0}, "macOS", {"Yosemite",      {10, 10,  2}}},
-			{"14C1514",  {14,  1,  0}, "macOS", {"Yosemite",      {10, 10,  2}}},
-			{"14C2513",  {14,  1,  0}, "macOS", {"Yosemite",      {10, 10,  2}}},
-			{"14D131",   {14,  3,  0}, "macOS", {"Yosemite",      {10, 10,  3}}},
-			{"14D136",   {14,  3,  0}, "macOS", {"Yosemite",      {10, 10,  3}}},
-			{"14E46",    {14,  4,  0}, "macOS", {"Yosemite",      {10, 10,  4}}},
-			{"14F27",    {14,  5,  0}, "macOS", {"Yosemite",      {10, 10,  5}}},
-			{"14F1021",  {14,  5,  0}, "macOS", {"Yosemite",      {10, 10,  5}}},
-			{"14F1505",  {14,  5,  0}, "macOS", {"Yosemite",      {10, 10,  5}}},
-			{"14F1509",  {14,  5,  0}, "macOS", {"Yosemite",      {10, 10,  5}}},
-			{"14F1605",  {14,  5,  0}, "macOS", {"Yosemite",      {10, 10,  5}}},
-			{"14F1713",  {14,  5,  0}, "macOS", {"Yosemite",      {10, 10,  5}}},
-			{"14F1808",  {14,  5,  0}, "macOS", {"Yosemite",      {10, 10,  5}}},
-			{"14F1909",  {14,  5,  0}, "macOS", {"Yosemite",      {10, 10,  5}}},
-			{"14F1912",  {14,  5,  0}, "macOS", {"Yosemite",      {10, 10,  5}}},
-			{"14F2009",  {14,  5,  0}, "macOS", {"Yosemite",      {10, 10,  5}}},
-			{"14F2109",  {14,  5,  0}, "macOS", {"Yosemite",      {10, 10,  5}}},
-			{"14F2315",  {14,  5,  0}, "macOS", {"Yosemite",      {10, 10,  5}}},
-			{"14F2411",  {14,  5,  0}, "macOS", {"Yosemite",      {10, 10,  5}}},
-			{"14F2511",  {14,  5,  0}, "macOS", {"Yosemite",      {10, 10,  5}}},
-			{"15A284",   {15,  0,  0}, "macOS", {"El Capitan",    {10, 11,  0}}},
-			{"15B42",    {15,  0,  0}, "macOS", {"El Capitan",    {10, 11,  1}}},
-			{"15C50",    {15,  2,  0}, "macOS", {"El Capitan",    {10, 11,  2}}},
-			{"15D21",    {15,  3,  0}, "macOS", {"El Capitan",    {10, 11,  3}}},
-			{"15E65",    {15,  4,  0}, "macOS", {"El Capitan",    {10, 11,  4}}},
-			{"15F34",    {15,  5,  0}, "macOS", {"El Capitan",    {10, 11,  5}}},
-			{"15G31",    {15,  6,  0}, "macOS", {"El Capitan",    {10, 11,  6}}},
-			{"15G1004",  {15,  6,  0}, "macOS", {"El Capitan",    {10, 11,  6}}},
-			{"15G1011",  {15,  6,  0}, "macOS", {"El Capitan",    {10, 11,  6}}},
-			{"15G1108",  {15,  6,  0}, "macOS", {"El Capitan",    {10, 11,  6}}},
-			{"15G1212",  {15,  6,  0}, "macOS", {"El Capitan",    {10, 11,  6}}},
-			{"15G1217",  {15,  6,  0}, "macOS", {"El Capitan",    {10, 11,  6}}},
-			{"15G1421",  {15,  6,  0}, "macOS", {"El Capitan",    {10, 11,  6}}},
-			{"15G1510",  {15,  6,  0}, "macOS", {"El Capitan",    {10, 11,  6}}},
-			{"15G1611",  {15,  6,  0}, "macOS", {"El Capitan",    {10, 11,  6}}},
-			{"15G17023", {15,  6,  0}, "macOS", {"El Capitan",    {10, 11,  6}}},
-			{"15G18013", {15,  6,  0}, "macOS", {"El Capitan",    {10, 11,  6}}},
-			{"15G19009", {15,  6,  0}, "macOS", {"El Capitan",    {10, 11,  6}}},
-			{"15G20015", {15,  6,  0}, "macOS", {"El Capitan",    {10, 11,  6}}},
-			{"15G21013", {15,  6,  0}, "macOS", {"El Capitan",    {10, 11,  6}}},
-			{"15G22010", {15,  6,  0}, "macOS", {"El Capitan",    {10, 11,  6}}},
-			{"16A323",   {16,  0,  0}, "macOS", {"Sierra",        {10, 12,  0}}},
-			{"16B2555",  {16,  1,  0}, "macOS", {"Sierra",        {10, 12,  1}}},
-			{"16B2657",  {16,  1,  0}, "macOS", {"Sierra",        {10, 12,  1}}},
-			{"16C67",    {16,  3,  0}, "macOS", {"Sierra",        {10, 12,  2}}},
-			{"16C68",    {16,  3,  0}, "macOS", {"Sierra",        {10, 12,  2}}},
-			{"16D32",    {16,  4,  0}, "macOS", {"Sierra",        {10, 12,  3}}},
-			{"16E195",   {16,  5,  0}, "macOS", {"Sierra",        {10, 12,  4}}},
-			{"16F73",    {16,  6,  0}, "macOS", {"Sierra",        {10, 12,  5}}},
-			{"16F2073",  {16,  6,  0}, "macOS", {"Sierra",        {10, 12,  5}}},
-			{"16G29",    {16,  7,  0}, "macOS", {"Sierra",        {10, 12,  6}}},
-			{"16G1036",  {16,  7,  0}, "macOS", {"Sierra",        {10, 12,  6}}},
-			{"16G1114",  {16,  7,  0}, "macOS", {"Sierra",        {10, 12,  6}}},
-			{"16G1212",  {16,  7,  0}, "macOS", {"Sierra",        {10, 12,  6}}},
-			{"16G1314",  {16,  7,  0}, "macOS", {"Sierra",        {10, 12,  6}}},
-			{"16G1408",  {16,  7,  0}, "macOS", {"Sierra",        {10, 12,  6}}},
-			{"16G1510",  {16,  7,  0}, "macOS", {"Sierra",        {10, 12,  6}}},
-			{"16G1618",  {16,  7,  0}, "macOS", {"Sierra",        {10, 12,  6}}},
-			{"16G1710",  {16,  7,  0}, "macOS", {"Sierra",        {10, 12,  6}}},
-			{"16G1815",  {16,  7,  0}, "macOS", {"Sierra",        {10, 12,  6}}},
-			{"16G1917",  {16,  7,  0}, "macOS", {"Sierra",        {10, 12,  6}}},
-			{"16G1918",  {16,  7,  0}, "macOS", {"Sierra",        {10, 12,  6}}},
-			{"16G2016",  {16,  7,  0}, "macOS", {"Sierra",        {10, 12,  6}}},
-			{"16G2127",  {16,  7,  0}, "macOS", {"Sierra",        {10, 12,  6}}},
-			{"16G2128",  {16,  7,  0}, "macOS", {"Sierra",        {10, 12,  6}}},
-			{"16G2136",  {16,  7,  0}, "macOS", {"Sierra",        {10, 12,  6}}},
-			{"17A365",   {17,  0,  0}, "macOS", {"High Sierra",   {10, 13,  0}}},
-			{"17A405",   {17,  0,  0}, "macOS", {"High Sierra",   {10, 13,  0}}},
-			{"17B48",    {17,  2,  0}, "macOS", {"High Sierra",   {10, 13,  1}}},
-			{"17B1002",  {17,  2,  0}, "macOS", {"High Sierra",   {10, 13,  1}}},
-			{"17B1003",  {17,  2,  0}, "macOS", {"High Sierra",   {10, 13,  1}}},
-			{"17C88",    {17,  3,  0}, "macOS", {"High Sierra",   {10, 13,  2}}},
-			{"17C89",    {17,  3,  0}, "macOS", {"High Sierra",   {10, 13,  2}}},
-			{"17C205",   {17,  3,  0}, "macOS", {"High Sierra",   {10, 13,  2}}},
-			{"17C2205",  {17,  3,  0}, "macOS", {"High Sierra",   {10, 13,  2}}},
-			{"17D47",    {17,  4,  0}, "macOS", {"High Sierra",   {10, 13,  3}}},
-			{"17D2047",  {17,  4,  0}, "macOS", {"High Sierra",   {10, 13,  3}}},
-			{"17D102",   {17,  4,  0}, "macOS", {"High Sierra",   {10, 13,  3}}},
-			{"17D2102",  {17,  4,  0}, "macOS", {"High Sierra",   {10, 13,  3}}},
-			{"17E199",   {17,  5,  0}, "macOS", {"High Sierra",   {10, 13,  4}}},
-			{"17E202",   {17,  5,  0}, "macOS", {"High Sierra",   {10, 13,  4}}},
-			{"17F77",    {17,  6,  0}, "macOS", {"High Sierra",   {10, 13,  5}}},
-			{"17G65",    {17,  7,  0}, "macOS", {"High Sierra",   {10, 13,  6}}},
-			{"17G2208",  {17,  7,  0}, "macOS", {"High Sierra",   {10, 13,  6}}},
-			{"17G2307",  {17,  7,  0}, "macOS", {"High Sierra",   {10, 13,  6}}},
-			{"17G3025",  {17,  7,  0}, "macOS", {"High Sierra",   {10, 13,  6}}},
-			{"17G4015",  {17,  7,  0}, "macOS", {"High Sierra",   {10, 13,  6}}},
-			{"17G5019",  {17,  7,  0}, "macOS", {"High Sierra",   {10, 13,  6}}},
-			{"17G6029",  {17,  7,  0}, "macOS", {"High Sierra",   {10, 13,  6}}},
-			{"17G6030",  {17,  7,  0}, "macOS", {"High Sierra",   {10, 13,  6}}},
-			{"17G7024",  {17,  7,  0}, "macOS", {"High Sierra",   {10, 13,  6}}},
-			{"17G8029",  {17,  7,  0}, "macOS", {"High Sierra",   {10, 13,  6}}},
-			{"17G8030",  {17,  7,  0}, "macOS", {"High Sierra",   {10, 13,  6}}},
-			{"17G8037",  {17,  7,  0}, "macOS", {"High Sierra",   {10, 13,  6}}},
-			{"17G9016",  {17,  7,  0}, "macOS", {"High Sierra",   {10, 13,  6}}},
-			{"17G10021", {17,  7,  0}, "macOS", {"High Sierra",   {10, 13,  6}}},
-			{"17G11023", {17,  7,  0}, "macOS", {"High Sierra",   {10, 13,  6}}},
-			{"17G12034", {17,  7,  0}, "macOS", {"High Sierra",   {10, 13,  6}}},
-			{"17G13033", {17,  7,  0}, "macOS", {"High Sierra",   {10, 13,  6}}},
-			{"17G13035", {17,  7,  0}, "macOS", {"High Sierra",   {10, 13,  6}}},
-			{"17G14019", {17,  7,  0}, "macOS", {"High Sierra",   {10, 13,  6}}},
-			{"17G14033", {17,  7,  0}, "macOS", {"High Sierra",   {10, 13,  6}}},
-			{"17G14042", {17,  7,  0}, "macOS", {"High Sierra",   {10, 13,  6}}},
-			{"18A391",   {18,  0,  0}, "macOS", {"Mojave",        {10, 14,  0}}},
-			{"18B75",    {18,  2,  0}, "macOS", {"Mojave",        {10, 14,  1}}},
-			{"18B2107",  {18,  2,  0}, "macOS", {"Mojave",        {10, 14,  1}}},
-			{"18B3094",  {18,  2,  0}, "macOS", {"Mojave",        {10, 14,  1}}},
-			{"18C54",    {18,  2,  0}, "macOS", {"Mojave",        {10, 14,  2}}},
-			{"18D42",    {18,  2,  0}, "macOS", {"Mojave",        {10, 14,  3}}},
-			{"18D43",    {18,  2,  0}, "macOS", {"Mojave",        {10, 14,  3}}},
-			{"18D109",   {18,  2,  0}, "macOS", {"Mojave",        {10, 14,  3}}},
-			{"18E226",   {18,  5,  0}, "macOS", {"Mojave",        {10, 14,  4}}},
-			{"18E227",   {18,  5,  0}, "macOS", {"Mojave",        {10, 14,  4}}},
-			{"18F132",   {18,  6,  0}, "macOS", {"Mojave",        {10, 14,  5}}},
-			{"18G84",    {18,  7,  0}, "macOS", {"Mojave",        {10, 14,  6}}},
-			{"18G87",    {18,  7,  0}, "macOS", {"Mojave",        {10, 14,  6}}},
-			{"18G95",    {18,  7,  0}, "macOS", {"Mojave",        {10, 14,  6}}},
-			{"18G103",   {18,  7,  0}, "macOS", {"Mojave",        {10, 14,  6}}},
-			{"18G1012",  {18,  7,  0}, "macOS", {"Mojave",        {10, 14,  6}}},
-			{"18G2022",  {18,  7,  0}, "macOS", {"Mojave",        {10, 14,  6}}},
-			{"18G3020",  {18,  7,  0}, "macOS", {"Mojave",        {10, 14,  6}}},
-			{"18G4032",  {18,  7,  0}, "macOS", {"Mojave",        {10, 14,  6}}},
-			{"18G5033",  {18,  7,  0}, "macOS", {"Mojave",        {10, 14,  6}}},
-			{"18G6020",  {18,  7,  0}, "macOS", {"Mojave",        {10, 14,  6}}},
-			{"18G6032",  {18,  7,  0}, "macOS", {"Mojave",        {10, 14,  6}}},
-			{"18G6042",  {18,  7,  0}, "macOS", {"Mojave",        {10, 14,  6}}},
-			{"18G7016",  {18,  7,  0}, "macOS", {"Mojave",        {10, 14,  6}}},
-			{"18G8012",  {18,  7,  0}, "macOS", {"Mojave",        {10, 14,  6}}},
-			{"18G8022",  {18,  7,  0}, "macOS", {"Mojave",        {10, 14,  6}}},
-			{"18G9028",  {18,  7,  0}, "macOS", {"Mojave",        {10, 14,  6}}},
-			{"18G9216",  {18,  7,  0}, "macOS", {"Mojave",        {10, 14,  6}}},
-			{"18G9323",  {18,  7,  0}, "macOS", {"Mojave",        {10, 14,  6}}},
-			{"19A583",   {19,  0,  0}, "macOS", {"Catalina",      {10, 15,  0}}},
-			{"19A602",   {19,  0,  0}, "macOS", {"Catalina",      {10, 15,  0}}},
-			{"19A603",   {19,  0,  0}, "macOS", {"Catalina",      {10, 15,  0}}},
-			{"19B88",    {19,  0,  0}, "macOS", {"Catalina",      {10, 15,  1}}},
-			{"19C57",    {19,  2,  0}, "macOS", {"Catalina",      {10, 15,  2}}},
-			{"19C58",    {19,  2,  0}, "macOS", {"Catalina",      {10, 15,  2}}},
-			{"19D76",    {19,  3,  0}, "macOS", {"Catalina",      {10, 15,  3}}},
-			{"19E266",   {19,  4,  0}, "macOS", {"Catalina",      {10, 15,  4}}},
-			{"19E287",   {19,  4,  0}, "macOS", {"Catalina",      {10, 15,  4}}},
-			{"19F96",    {19,  5,  0}, "macOS", {"Catalina",      {10, 15,  5}}},
-			{"19F101",   {19,  5,  0}, "macOS", {"Catalina",      {10, 15,  5}}},
-			{"19G73",    {19,  6,  0}, "macOS", {"Catalina",      {10, 15,  6}}},
-			{"19G2021",  {19,  6,  0}, "macOS", {"Catalina",      {10, 15,  6}}},
-			{"19H2",     {19,  6,  0}, "macOS", {"Catalina",      {10, 15,  7}}},
-			{"19H4",     {19,  6,  0}, "macOS", {"Catalina",      {10, 15,  7}}},
-			{"19H15",    {19,  6,  0}, "macOS", {"Catalina",      {10, 15,  7}}},
-			{"19H114",   {19,  6,  0}, "macOS", {"Catalina",      {10, 15,  7}}},
-			{"19H512",   {19,  6,  0}, "macOS", {"Catalina",      {10, 15,  7}}},
-			{"19H524",   {19,  6,  0}, "macOS", {"Catalina",      {10, 15,  7}}},
-			{"19H1030",  {19,  6,  0}, "macOS", {"Catalina",      {10, 15,  7}}},
-			{"19H1217",  {19,  6,  0}, "macOS", {"Catalina",      {10, 15,  7}}},
-			{"19H1323",  {19,  6,  0}, "macOS", {"Catalina",      {10, 15,  7}}},
-			{"19H1417",  {19,  6,  0}, "macOS", {"Catalina",      {10, 15,  7}}},
-			{"19H1419",  {19,  6,  0}, "macOS", {"Catalina",      {10, 15,  7}}},
-			{"19H1519",  {19,  6,  0}, "macOS", {"Catalina",      {10, 15,  7}}},
-			{"19H1615",  {19,  6,  0}, "macOS", {"Catalina",      {10, 15,  7}}},
-			{"19H1713",  {19,  6,  0}, "macOS", {"Catalina",      {10, 15,  7}}},
-			{"19H1715",  {19,  6,  0}, "macOS", {"Catalina",      {10, 15,  7}}},
-			{"19H1824",  {19,  6,  0}, "macOS", {"Catalina",      {10, 15,  7}}},
-			{"19H1922",  {19,  6,  0}, "macOS", {"Catalina",      {10, 15,  7}}},
-			{"19H2026",  {19,  6,  0}, "macOS", {"Catalina",      {10, 15,  7}}},
-			{"20A2411",  {20,  1,  0}, "macOS", {"Big Sur",       {11,  0,  0}}},
-			{"20B29",    {20,  1,  0}, "macOS", {"Big Sur",       {11,  0,  1}}},
-			{"20B50",    {20,  1,  0}, "macOS", {"Big Sur",       {11,  0,  1}}},
-			{"20C69",    {20,  2,  0}, "macOS", {"Big Sur",       {11,  1,  0}}},
-			{"20D64",    {20,  3,  0}, "macOS", {"Big Sur",       {11,  2,  0}}},
-			{"20D74",    {20,  3,  0}, "macOS", {"Big Sur",       {11,  2,  1}}},
-			{"20D75",    {20,  3,  0}, "macOS", {"Big Sur",       {11,  2,  1}}},
-			{"20D80",    {20,  3,  0}, "macOS", {"Big Sur",       {11,  2,  2}}},
-			{"20D91",    {20,  3,  0}, "macOS", {"Big Sur",       {11,  2,  3}}},
-			{"20E232",   {20,  4,  0}, "macOS", {"Big Sur",       {11,  3,  0}}},
-			{"20E241",   {20,  4,  0}, "macOS", {"Big Sur",       {11,  3,  1}}},
-			{"20F71",    {20,  5,  0}, "macOS", {"Big Sur",       {11,  4,  0}}},
-			{"20G71",    {20,  6,  0}, "macOS", {"Big Sur",       {11,  5,  0}}},
-			{"20G80",    {20,  6,  0}, "macOS", {"Big Sur",       {11,  5,  1}}},
-			{"20G95",    {20,  6,  0}, "macOS", {"Big Sur",       {11,  5,  2}}},
-			{"20G165",   {20,  6,  0}, "macOS", {"Big Sur",       {11,  6,  0}}},
-			{"20G224",   {20,  6,  0}, "macOS", {"Big Sur",       {11,  6,  1}}},
-			{"20G314",   {20,  6,  0}, "macOS", {"Big Sur",       {11,  6,  2}}},
-			{"20G415",   {20,  6,  0}, "macOS", {"Big Sur",       {11,  6,  3}}},
-			{"20G417",   {20,  6,  0}, "macOS", {"Big Sur",       {11,  6,  4}}},
-			{"20G527",   {20,  6,  0}, "macOS", {"Big Sur",       {11,  6,  5}}},
-			{"20G624",   {20,  6,  0}, "macOS", {"Big Sur",       {11,  6,  6}}},
-			{"20G630",   {20,  6,  0}, "macOS", {"Big Sur",       {11,  6,  7}}},
-			{"20G730",   {20,  6,  0}, "macOS", {"Big Sur",       {11,  6,  8}}},
-			{"20G817",   {20,  6,  0}, "macOS", {"Big Sur",       {11, 7,   0}}},
-			{"20G918",   {20,  6,  0}, "macOS", {"Big Sur",       {11, 7,   1}}},
-			{"20G1020",  {20,  6,  0}, "macOS", {"Big Sur",       {11, 7,   2}}},
-			{"20G1116",  {20,  6,  0}, "macOS", {"Big Sur",       {11, 7,   3}}},
-			{"20G1120",  {20,  6,  0}, "macOS", {"Big Sur",       {11, 7,   4}}},
-			{"20G1225",  {20,  6,  0}, "macOS", {"Big Sur",       {11, 7,   5}}},
-			{"20G1231",  {20,  6,  0}, "macOS", {"Big Sur",       {11, 7,   6}}},
-			{"20G1345",  {20,  6,  0}, "macOS", {"Big Sur",       {11, 7,   7}}},
-			{"20G1351",  {20,  6,  0}, "macOS", {"Big Sur",       {11, 7,   8}}},
-			{"20G1426",  {20,  6,  0}, "macOS", {"Big Sur",       {11, 7,   9}}},
-			{"20G1427",  {20,  6,  0}, "macOS", {"Big Sur",       {11, 7,  10}}},
-			{"21A344",   {21,  0,  1}, "macOS", {"Monterey",      {12,  0,  0}}},
-			{"21A559",   {21,  1,  0}, "macOS", {"Monterey",      {12,  0,  1}}},
-			{"21C52",    {21,  2,  0}, "macOS", {"Monterey",      {12,  1,  0}}},
-			{"21D49",    {21,  3,  0}, "macOS", {"Monterey",      {12,  2,  0}}},
-			{"21D62",    {21,  3,  0}, "macOS", {"Monterey",      {12,  2,  1}}},
-			{"21E230",   {21,  4,  0}, "macOS", {"Monterey",      {12,  3,  0}}},
-			{"21E258",   {21,  4,  0}, "macOS", {"Monterey",      {12,  3,  1}}},
-			{"21F79",    {21,  5,  0}, "macOS", {"Monterey",      {12,  4,  0}}},
-			{"21F2081",  {21,  5,  0}, "macOS", {"Monterey",      {12,  4,  0}}},
-			{"21F2092",  {21,  5,  0}, "macOS", {"Monterey",      {12,  4,  0}}},
-			{"21G72",    {21,  6,  0}, "macOS", {"Monterey",      {12,  5,  0}}},
-			{"21G83",    {21,  6,  0}, "macOS", {"Monterey",      {12,  5,  1}}},
-			{"21G115",   {21,  6,  0}, "macOS", {"Monterey",      {12,  6,  0}}},
-			{"21G217",   {21,  6,  0}, "macOS", {"Monterey",      {12,  6,  1}}},
-			{"21G320",   {21,  6,  0}, "macOS", {"Monterey",      {12,  6,  2}}},
-			{"21G419",   {21,  6,  0}, "macOS", {"Monterey",      {12,  6,  3}}},
-			{"21G526",   {21,  6,  0}, "macOS", {"Monterey",      {12,  6,  4}}},
-			{"21G531",   {21,  6,  0}, "macOS", {"Monterey",      {12,  6,  5}}},
-			{"21G646",   {21,  6,  0}, "macOS", {"Monterey",      {12,  6,  6}}},
-			{"21G651",   {21,  6,  0}, "macOS", {"Monterey",      {12,  6,  7}}},
-			{"21G725",   {21,  6,  0}, "macOS", {"Monterey",      {12,  6,  8}}},
-			{"21G726",   {21,  6,  0}, "macOS", {"Monterey",      {12,  6,  9}}},
-			{"21G816",   {21,  6,  0}, "macOS", {"Monterey",      {12,  7,  0}}},
-			{"21G920",   {21,  6,  0}, "macOS", {"Monterey",      {12,  7,  1}}},
-			{"21G1974",  {21,  6,  0}, "macOS", {"Monterey",      {12,  7,  2}}},
-			{"22A380",   {13,  0,  0}, "macOS", {"Ventura",       {22,  1,  0}}},
-			{"22A400",   {13,  0,  1}, "macOS", {"Ventura",       {22,  1,  0}}},
-			{"22C65",    {13,  1,  0}, "macOS", {"Ventura",       {22,  2,  0}}},
-			{"22D49",    {13,  2,  0}, "macOS", {"Ventura",       {22,  3,  0}}},
-			{"22D68",    {13,  2,  1}, "macOS", {"Ventura",       {22,  3,  0}}},
-			{"22E252",   {13,  3,  0}, "macOS", {"Ventura",       {22,  4,  0}}},
-			{"22E261",   {13,  3,  1}, "macOS", {"Ventura",       {22,  4,  0}}},
-			{"22F66",    {13,  4,  0}, "macOS", {"Ventura",       {22,  5,  0}}},
-			{"22F82",    {13,  4,  1}, "macOS", {"Ventura",       {22,  5,  0}}},
-			{"22E772610a", {13, 4, 1}, "macOS", {"Ventura",       {22,  5,  0}}},
-			{"22F770820d", {13, 4, 1}, "macOS", {"Ventura",       {22,  5,  0}}},
-			{"22G74",    {13,  5,  0}, "macOS", {"Ventura",       {22,  6,  0}}},
-			{"22G90",    {13,  5,  1}, "macOS", {"Ventura",       {22,  6,  0}}},
-			{"22G91",    {13,  5,  2}, "macOS", {"Ventura",       {22,  6,  0}}},
-			{"22G120",   {13,  6,  0}, "macOS", {"Ventura",       {22,  6,  0}}},
-			{"22G313",   {13,  6,  1}, "macOS", {"Ventura",       {22,  6,  0}}},
-			{"22G320",   {13,  6,  2}, "macOS", {"Ventura",       {22,  6,  0}}},
-			{"23A344",   {23,  0,  0}, "macOS", {"Sonoma",        {14,  0,  0}}},
-			{"23B74",    {23,  1,  0}, "macOS", {"Sonoma",        {14,  1,  0}}},
-			{"23B81",    {23,  1,  0}, "macOS", {"Sonoma",        {14,  1,  1}}},
-			{"23B92",    {23,  1,  0}, "macOS", {"Sonoma",        {14,  1,  2}}},
-			{"23C64",    {23,  2,  0}, "macOS", {"Sonoma",        {14,  2,  0}}},
-			{"23C71",    {23,  2,  0}, "macOS", {"Sonoma",        {14,  2,  1}}},
-		};
-
-
-		b32 build_found  = 1;
-		b32 darwin_found = 1;
-		uint32_t major, minor, patch;
-
-		#define MACOS_VERSION_BUFFER_SIZE 100
-		char build_buffer[MACOS_VERSION_BUFFER_SIZE];
-		char darwin_buffer[MACOS_VERSION_BUFFER_SIZE];
-		size_t build_buffer_size  = MACOS_VERSION_BUFFER_SIZE - 1;
-		size_t darwin_buffer_size = MACOS_VERSION_BUFFER_SIZE - 1;
-		#undef MACOS_VERSION_BUFFER_SIZE
-
-		int build_mibs[] = { CTL_KERN, KERN_OSVERSION };
-		if (sysctl(build_mibs, 2, build_buffer, &build_buffer_size, NULL, 0) == -1) {
-			build_found = 0;
-		}
-
-		int darwin_mibs[] = { CTL_KERN, KERN_OSRELEASE };
-		if (sysctl(darwin_mibs, 2, darwin_buffer, &darwin_buffer_size, NULL, 0) == -1) {
-			gb_printf("macOS Unknown\n");
-			return;
-		} else {
-			if (sscanf(darwin_buffer, "%u.%u.%u", &major, &minor, &patch) != 3) {
-				darwin_found = 0;
-			}
-		}
-
-		// Scan table for match on BUILD
-		int macos_release_count = sizeof(macos_release_map) / sizeof(macos_release_map[0]);
-		Darwin_To_Release build_match = {};
-		Darwin_To_Release kernel_match = {};
-	
-		for (int build = 0; build < macos_release_count; build++) {
-			Darwin_To_Release rel = macos_release_map[build];
-			
-			// Do we have an exact match on the BUILD?
-			if (gb_strcmp(rel.build, (const char *)build_buffer) == 0) {
-				build_match = rel;
-				break;
-			}
-			
-			// Do we have an exact Darwin match?
-			if (rel.darwin[0] == major && rel.darwin[1] == minor && rel.darwin[2] == patch) {
-				kernel_match = rel;
-			}
-	
-			// Major kernel version needs to match exactly,
-			if (rel.darwin[0] == major) {
-				// No major version match yet.
-				if (!kernel_match.os_name) {
-					kernel_match = rel;
-				}
-				if (minor >= rel.darwin[1]) {
-					kernel_match = rel;
-					if (patch >= rel.darwin[2]) {
-						kernel_match = rel;
-					}
-				}
-			}
-		}
-	
-		Darwin_To_Release match = {};
-		if(!build_match.build) {
-			match = kernel_match;
-		} else {
-			match = build_match;
-		}
-
-		if (match.os_name) {
-			gb_printf("%s %s %d", match.os_name, match.release.name, match.release.version[0]);
-			if (match.release.version[1] > 0 || match.release.version[2] > 0) {
-				gb_printf(".%d", match.release.version[1]);
-			}
-			if (match.release.version[2] > 0) {
-				gb_printf(".%d", match.release.version[2]);
-			}
-			if (build_found) {
-				gb_printf(" (build: %s, kernel: %d.%d.%d)\n", build_buffer, match.darwin[0], match.darwin[1], match.darwin[2]);
-			} else {
-				gb_printf(" (build: %s?, kernel: %d.%d.%d)\n", match.build, match.darwin[0], match.darwin[1], match.darwin[2]);				
-			}
-			return;
-		}
-
-		if (build_found && darwin_found) {
-			gb_printf("macOS Unknown (build: %s, kernel: %d.%d.%d)\n", build_buffer, major, minor, patch);
-			return;
-		} else if (build_found) {
-			gb_printf("macOS Unknown (build: %s)\n", build_buffer);
-			return;
-		} else if (darwin_found) {
-			gb_printf("macOS Unknown (kernel: %d.%d.%d)\n", major, minor, patch);
-			return;
-		}
-	#elif defined(GB_SYSTEM_OPENBSD)
-		struct utsname un;
-		
-		if (uname(&un) != -1) {
-			gb_printf("%s %s %s %s\n", un.sysname, un.release, un.version, un.machine);
-		} else {
-			gb_printf("OpenBSD: Unknown\n");    
-		}
-	#elif defined(GB_SYSTEM_FREEBSD)
-		#define freebsd_version_buffer 129
-		char buffer[freebsd_version_buffer];
-		size_t buffer_size = freebsd_version_buffer - 1;
-		#undef freebsd_version_buffer
-
-		int mibs[] = { CTL_KERN, KERN_VERSION };
-		if (sysctl(mibs, 2, buffer, &buffer_size, NULL, 0) == -1) {
-			gb_printf("FreeBSD: Unknown\n");
-		} else {
-			// KERN_VERSION can end in a \n, replace it with a \0
-			for (int i = 0; i < buffer_size; i += 1) {
-				if (buffer[i] == '\n') buffer[i] = 0;
-			}
-			gb_printf("%s", &buffer[0]);
-
-			// Retrieve kernel revision using `sysctl`, e.g. 199506
-			mibs[1] = KERN_OSREV;
-			uint64_t revision;
-			size_t revision_size = sizeof(revision);
-
-			if (sysctl(mibs, 2, &revision, &revision_size, NULL, 0) == -1) {
-				gb_printf("\n");
-			} else {
-				gb_printf(", revision %ld\n", revision);
-			}
-		}
-	#else
-		gb_printf("Unknown");
-	#endif
-}
-
-// NOTE(Jeroen): `odin report` prints some system information for easier bug reporting.
-gb_internal void print_bug_report_help() {
-	gb_printf("Where to find more information and get into contact when you encounter a bug:\n\n");
-	gb_printf("\tWebsite: https://odin-lang.org\n");
-	gb_printf("\tGitHub:  https://github.com/odin-lang/Odin/issues\n");
-	/*
-		Uncomment and update URL once we have a Discord vanity URL. For now people can get here from the site.
-		gb_printf("\tDiscord: https://discord.com/invite/sVBPHEv\n");
-	*/
-	gb_printf("\n\n");
-
-	gb_printf("Useful information to add to a bug report:\n\n");
-
-	gb_printf("\tOdin: %.*s", LIT(ODIN_VERSION));
-
-	#ifdef NIGHTLY
-	gb_printf("-nightly");
-	#endif
-
-	#ifdef GIT_SHA
-	gb_printf(":%s", GIT_SHA);
-	#endif
-
-	gb_printf("\n");
-
-	/*
-		Print OS information.
-	*/
-	report_os_info();
-
-	/*
-		Now print CPU info.
-	*/
-	report_cpu_info();
-
-	/*
-		And RAM info.
-	*/
-	report_ram_info();
-}
+/*
+	Gather and print platform and version info to help with reporting Odin bugs.
+*/
+
+#if !defined(GB_COMPILER_MSVC)
+	#if defined(GB_CPU_X86)
+		#include <cpuid.h>
+	#endif
+#endif
+
+#if defined(GB_SYSTEM_LINUX)
+	#include <sys/utsname.h>
+	#include <sys/sysinfo.h>
+#endif
+
+#if defined(GB_SYSTEM_OSX)
+	#include <sys/sysctl.h>
+#endif
+
+#if defined(GB_SYSTEM_OPENBSD)
+	#include <sys/sysctl.h>
+	#include <sys/utsname.h>
+#endif
+
+#if defined(GB_SYSTEM_FREEBSD)
+	#include <sys/sysctl.h>
+#endif
+
+/*
+	NOTE(Jeroen): This prints the Windows product edition only, to be called from `print_platform_details`.
+*/
+#if defined(GB_SYSTEM_WINDOWS)
+gb_internal void report_windows_product_type(DWORD ProductType) {
+	switch (ProductType) {
+	case PRODUCT_ULTIMATE:
+		gb_printf("Ultimate");
+		break;
+
+	case PRODUCT_HOME_BASIC:
+		gb_printf("Home Basic");
+		break;
+
+	case PRODUCT_HOME_PREMIUM:
+		gb_printf("Home Premium");
+		break;
+
+	case PRODUCT_ENTERPRISE:
+		gb_printf("Enterprise");
+		break;
+
+	case PRODUCT_CORE:
+		gb_printf("Home Basic");
+		break;
+
+	case PRODUCT_HOME_BASIC_N:
+		gb_printf("Home Basic N");
+		break;
+
+	case PRODUCT_EDUCATION:
+		gb_printf("Education");
+		break;
+
+	case PRODUCT_EDUCATION_N:
+		gb_printf("Education N");
+		break;
+
+	case PRODUCT_BUSINESS:
+		gb_printf("Business");
+		break;
+
+	case PRODUCT_STANDARD_SERVER:
+		gb_printf("Standard Server");
+		break;
+
+	case PRODUCT_DATACENTER_SERVER:
+		gb_printf("Datacenter");
+		break;
+
+	case PRODUCT_SMALLBUSINESS_SERVER:
+		gb_printf("Windows Small Business Server");
+		break;
+
+	case PRODUCT_ENTERPRISE_SERVER:
+		gb_printf("Enterprise Server");
+		break;
+
+	case PRODUCT_STARTER:
+		gb_printf("Starter");
+		break;
+
+	case PRODUCT_DATACENTER_SERVER_CORE:
+		gb_printf("Datacenter Server Core");
+		break;
+
+	case PRODUCT_STANDARD_SERVER_CORE:
+		gb_printf("Server Standard Core");
+		break;
+
+	case PRODUCT_ENTERPRISE_SERVER_CORE:
+		gb_printf("Enterprise Server Core");
+		break;
+
+	case PRODUCT_BUSINESS_N:
+		gb_printf("Business N");
+		break;
+
+	case PRODUCT_HOME_SERVER:
+		gb_printf("Home Server");
+		break;
+
+	case PRODUCT_SERVER_FOR_SMALLBUSINESS:
+		gb_printf("Windows Server 2008 for Windows Essential Server Solutions");
+		break;
+
+	case PRODUCT_SMALLBUSINESS_SERVER_PREMIUM:
+		gb_printf("Small Business Server Premium");
+		break;
+
+	case PRODUCT_HOME_PREMIUM_N:
+		gb_printf("Home Premium N");
+		break;
+
+	case PRODUCT_ENTERPRISE_N:
+		gb_printf("Enterprise N");
+		break;
+
+	case PRODUCT_ULTIMATE_N:
+		gb_printf("Ultimate N");
+		break;
+
+	case PRODUCT_HYPERV:
+		gb_printf("HyperV");
+		break;
+
+	case PRODUCT_STARTER_N:
+		gb_printf("Starter N");
+		break;
+
+	case PRODUCT_PROFESSIONAL:
+		gb_printf("Professional");
+		break;
+
+	case PRODUCT_PROFESSIONAL_N:
+		gb_printf("Professional N");
+		break;
+
+	case PRODUCT_UNLICENSED:
+		gb_printf("Unlicensed");
+		break;
+
+	default:
+		gb_printf("Unknown Edition (%08x)", cast(unsigned)ProductType);
+	}
+}
+#endif
+
+gb_internal void odin_cpuid(int leaf, int result[]) {
+	#if defined(GB_CPU_ARM)
+		return;
+
+	#elif defined(GB_CPU_X86)
+	
+		#if defined(GB_COMPILER_MSVC)
+			__cpuid(result, leaf);
+		#else
+			__get_cpuid(leaf, (unsigned int*)&result[0], (unsigned int*)&result[1], (unsigned int*)&result[2], (unsigned int*)&result[3]);
+		#endif
+
+	#endif
+}
+
+gb_internal void report_cpu_info() {
+	gb_printf("\tCPU:     ");
+
+	#if defined(GB_CPU_X86)
+
+	/*
+		Get extended leaf info
+	*/
+	int cpu[4];
+
+	odin_cpuid(0x80000000, &cpu[0]);
+	int number_of_extended_ids = cpu[0];
+
+	int brand[0x12] = {};
+
+	/*
+		Read CPU brand if supported.
+	*/
+	if (number_of_extended_ids >= 0x80000004) {
+		odin_cpuid(0x80000002, &brand[0]);
+		odin_cpuid(0x80000003, &brand[4]);
+		odin_cpuid(0x80000004, &brand[8]);
+
+		/*
+			Some CPUs like `      Intel(R) Xeon(R) CPU E5-1650 v2 @ 3.50GHz` may include leading spaces. Trim them.
+		*/
+		char * brand_name = (char *)&brand[0];
+		for (; brand_name[0] == ' '; brand_name++) {}
+
+		gb_printf("%s\n", brand_name);
+	} else {
+		gb_printf("Unable to retrieve.\n");
+	}
+
+	#elif defined(GB_CPU_ARM)
+		/*
+			TODO(Jeroen): On *nix, perhaps query `/proc/cpuinfo`.
+		*/
+		#if defined(GB_ARCH_64_BIT)
+			gb_printf("ARM64\n");
+		#else
+			gb_printf("ARM\n");
+		#endif
+	#else
+		gb_printf("Unknown\n");
+	#endif
+}
+
+/*
+	Report the amount of installed RAM.
+*/
+gb_internal void report_ram_info() {
+	gb_printf("\tRAM:     ");
+
+	#if defined(GB_SYSTEM_WINDOWS)
+		MEMORYSTATUSEX statex;
+		statex.dwLength = sizeof(statex);
+		GlobalMemoryStatusEx (&statex);
+
+		gb_printf("%lld MiB\n", statex.ullTotalPhys / gb_megabytes(1));
+
+	#elif defined(GB_SYSTEM_LINUX)
+		/*
+			Retrieve RAM info using `sysinfo()`, 
+		*/
+		struct sysinfo info;
+		int result = sysinfo(&info);
+
+		if (result == 0x0) {
+			gb_printf("%lu MiB\n", info.totalram * info.mem_unit / gb_megabytes(1));
+		} else {
+			gb_printf("Unknown.\n");
+		}
+	#elif defined(GB_SYSTEM_OSX)
+		uint64_t ram_amount;
+		size_t   val_size = sizeof(ram_amount);
+
+		int mibs[] = { CTL_HW, HW_MEMSIZE };
+		if (sysctl(mibs, 2, &ram_amount, &val_size, NULL, 0) != -1) {
+			gb_printf("%lld MiB\n", ram_amount / gb_megabytes(1));
+		}
+	#elif defined(GB_SYSTEM_OPENBSD)
+		uint64_t ram_amount;
+		size_t   val_size = sizeof(ram_amount);
+
+		int mibs[] = { CTL_HW, HW_PHYSMEM64 };
+		if (sysctl(mibs, 2, &ram_amount, &val_size, NULL, 0) != -1) {
+			gb_printf("%lld MiB\n", ram_amount / gb_megabytes(1));
+		}
+	#elif defined(GB_SYSTEM_FREEBSD)
+		uint64_t ram_amount;
+		size_t   val_size = sizeof(ram_amount);
+
+		int mibs[] = { CTL_HW, HW_PHYSMEM };
+		if (sysctl(mibs, 2, &ram_amount, &val_size, NULL, 0) != -1) {
+			gb_printf("%lu MiB\n", ram_amount / gb_megabytes(1));
+		}
+	#else
+		gb_printf("Unknown.\n");
+	#endif
+}
+
+gb_internal void report_os_info() {
+	gb_printf("\tOS:      ");
+
+	#if defined(GB_SYSTEM_WINDOWS)
+	/*
+		NOTE(Jeroen): 
+			`GetVersionEx`  will return 6.2 for Windows 10 unless the program is manifested for Windows 10.
+			`RtlGetVersion` will return the true version.
+
+			Rather than include the WinDDK, we ask the kernel directly.
+
+			`HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion` is for the minor build version (Update Build Release)
+
+	*/
+	OSVERSIONINFOEXW osvi;
+	ZeroMemory(&osvi, sizeof(OSVERSIONINFOEXW));
+	osvi.dwOSVersionInfoSize = sizeof(OSVERSIONINFOEXW);
+
+	typedef NTSTATUS (WINAPI* RtlGetVersionPtr)(OSVERSIONINFOW*);
+	typedef BOOL (WINAPI* GetProductInfoPtr)(DWORD dwOSMajorVersion, DWORD dwOSMinorVersion, DWORD dwSpMajorVersion, DWORD dwSpMinorVersion, PDWORD pdwReturnedProductType);
+
+	// https://docs.microsoft.com/en-us/windows-hardware/drivers/ddi/wdm/nf-wdm-rtlgetversion
+	RtlGetVersionPtr  RtlGetVersion  =  (RtlGetVersionPtr)GetProcAddress(GetModuleHandle(TEXT("ntdll.dll")), "RtlGetVersion");
+	// https://docs.microsoft.com/en-us/windows/win32/api/sysinfoapi/nf-sysinfoapi-getproductinfo
+	GetProductInfoPtr GetProductInfo = (GetProductInfoPtr)GetProcAddress(GetModuleHandle(TEXT("kernel32.dll")), "GetProductInfo");
+
+	NTSTATUS status  = {};
+	DWORD ProductType = {};
+	if (RtlGetVersion != nullptr) {
+		status = RtlGetVersion((OSVERSIONINFOW*)&osvi);
+	}
+
+	if (RtlGetVersion == nullptr || status != 0x0) {
+		gb_printf("Windows (Unknown Version)");
+	} else {
+		if (GetProductInfo != nullptr) {
+			GetProductInfo(osvi.dwMajorVersion, osvi.dwMinorVersion, osvi.wServicePackMajor, osvi.wServicePackMinor, &ProductType);
+		}
+
+		if (false) {
+			gb_printf("dwMajorVersion:    %u\n", cast(unsigned)osvi.dwMajorVersion);
+			gb_printf("dwMinorVersion:    %u\n", cast(unsigned)osvi.dwMinorVersion);
+			gb_printf("dwBuildNumber:     %u\n", cast(unsigned)osvi.dwBuildNumber);
+			gb_printf("dwPlatformId:      %u\n", cast(unsigned)osvi.dwPlatformId);
+			gb_printf("wServicePackMajor: %u\n", cast(unsigned)osvi.wServicePackMajor);
+			gb_printf("wServicePackMinor: %u\n", cast(unsigned)osvi.wServicePackMinor);
+			gb_printf("wSuiteMask:        %u\n", cast(unsigned)osvi.wSuiteMask);
+			gb_printf("wProductType:      %u\n", cast(unsigned)osvi.wProductType);
+		}
+
+		gb_printf("Windows ");
+
+		switch (osvi.dwMajorVersion) {
+		case 10:
+			/*
+				Windows 10 (Pro), Windows 2016 Server, Windows 2019 Server, Windows 2022 Server
+			*/
+			switch (osvi.wProductType) {
+			case VER_NT_WORKSTATION: // Workstation
+				if (osvi.dwBuildNumber < 22000) {
+					gb_printf("10 ");
+				} else {
+					gb_printf("11 ");
+				}
+				
+				report_windows_product_type(ProductType);
+
+				break;
+			default: // Server or Domain Controller
+				switch(osvi.dwBuildNumber) {
+				case 14393:
+					gb_printf("2016 Server");
+					break;
+				case 17763:
+					gb_printf("2019 Server");
+					break;
+				case 20348:
+					gb_printf("2022 Server");
+					break;
+				default:
+					gb_printf("Unknown Server");
+					break;
+				}
+			}
+			break;
+		case 6:
+			switch (osvi.dwMinorVersion) {
+				case 0:
+					switch (osvi.wProductType) {
+						case VER_NT_WORKSTATION:
+							gb_printf("Windows Vista ");
+							report_windows_product_type(ProductType);
+							break;
+						case 3:
+							gb_printf("Windows Server 2008");
+							break;
+					}
+					break;
+
+				case 1:
+					switch (osvi.wProductType) {
+						case VER_NT_WORKSTATION:
+							gb_printf("Windows 7 ");
+							report_windows_product_type(ProductType);
+							break;
+						case 3:
+							gb_printf("Windows Server 2008 R2");
+							break;
+					}
+					break;
+				case 2:
+					switch (osvi.wProductType) {
+						case VER_NT_WORKSTATION:
+							gb_printf("Windows 8 ");
+							report_windows_product_type(ProductType);
+							break;
+						case 3:
+							gb_printf("Windows Server 2012");
+							break;
+					}
+					break;
+				case 3:
+					switch (osvi.wProductType) {
+						case VER_NT_WORKSTATION:
+							gb_printf("Windows 8.1 ");
+							report_windows_product_type(ProductType);
+							break;
+						case 3:
+							gb_printf("Windows Server 2012 R2");
+							break;
+					}
+					break;
+			}
+			break;
+		case 5:
+			switch (osvi.dwMinorVersion) {
+				case 0:
+					gb_printf("Windows 2000");
+					break;
+				case 1:
+					gb_printf("Windows XP");
+					break;
+				case 2:
+					gb_printf("Windows Server 2003");
+					break;
+			}
+			break;
+		default:
+			break;
+		}
+
+		/*
+			Grab Windows DisplayVersion (like 20H02)
+		*/
+		LPDWORD ValueType = {};
+		DWORD   UBR;
+		char    DisplayVersion[256];
+		DWORD   ValueSize = 256;
+
+		status = RegGetValue(
+			HKEY_LOCAL_MACHINE,
+			TEXT("SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion"),
+			TEXT("DisplayVersion"),
+			RRF_RT_REG_SZ,
+			ValueType,
+			DisplayVersion,
+			&ValueSize
+		);
+
+		if (status == 0x0) {
+			gb_printf(" (version: %s)", DisplayVersion);
+		}
+
+		/*
+			Now print build number.
+		*/
+		gb_printf(", build %u", cast(unsigned)osvi.dwBuildNumber);
+
+		ValueSize = sizeof(UBR);
+		status = RegGetValue(
+			HKEY_LOCAL_MACHINE,
+			TEXT("SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion"),
+			TEXT("UBR"),
+			RRF_RT_REG_DWORD,
+			ValueType,
+			&UBR,
+			&ValueSize
+		);
+
+		if (status == 0x0) {
+			gb_printf(".%u", cast(unsigned)UBR);
+		}
+		gb_printf("\n");
+	}
+	#elif defined(GB_SYSTEM_LINUX)
+		/*
+			Try to parse `/etc/os-release` for `PRETTY_NAME="Ubuntu 20.04.3 LTS`
+		*/
+		gbAllocator a = heap_allocator();
+
+		gbFileContents release = gb_file_read_contents(a, 1, "/etc/os-release");
+		defer (gb_file_free_contents(&release));
+
+		b32 found = 0;
+		if (release.size) {
+			char *start        = (char *)release.data;
+			char *end          = (char *)release.data + release.size;
+			const char *needle = "PRETTY_NAME=\"";
+			isize needle_len   = gb_strlen((needle));
+		
+			char *c = start;
+			for (; c < end; c++) {
+				if (gb_strncmp(c, needle, needle_len) == 0) {
+					found = 1;
+					start = c + needle_len;
+					break;
+				}
+			}
+
+			if (found) {
+				for (c = start; c < end; c++) {
+					if (*c == '"') {
+						// Found the closing quote. Replace it with \0
+						*c = 0;
+						gb_printf("%s", (char *)start);
+						break;
+					} else if (*c == '\n') {
+						found = 0;
+					}
+				}
+			}
+		}
+
+		if (!found) {
+			gb_printf("Unknown Linux Distro");
+		}
+
+		/*
+			Print kernel info using `uname()` syscall, https://linux.die.net/man/2/uname
+		*/
+		char buffer[1024];
+		uname((struct utsname *)&buffer[0]);
+
+		struct utsname *info;
+		info = (struct utsname *)&buffer[0];
+
+		gb_printf(", %s %s\n", info->sysname, info->release);
+
+	#elif defined(GB_SYSTEM_OSX)
+		struct Darwin_To_Release {
+			const char* build;    // 21G83
+			int   darwin[3];      // Darwin kernel triplet
+			const char* os_name;  // OS X, MacOS
+			struct {
+				const char* name; // Monterey, Mojave, etc.
+				int version[3];   // 12.4, etc.
+			} release;
+		};
+
+		Darwin_To_Release macos_release_map[] = {
+			{"8A428",    { 8,  0,  0}, "macOS", {"Tiger",         {10,  4,  0}}},
+			{"8A432",    { 8,  0,  0}, "macOS", {"Tiger",         {10,  4,  0}}},
+			{"8B15",     { 8,  1,  0}, "macOS", {"Tiger",         {10,  4,  1}}},
+			{"8B17",     { 8,  1,  0}, "macOS", {"Tiger",         {10,  4,  1}}},
+			{"8C46",     { 8,  2,  0}, "macOS", {"Tiger",         {10,  4,  2}}},
+			{"8C47",     { 8,  2,  0}, "macOS", {"Tiger",         {10,  4,  2}}},
+			{"8E102",    { 8,  2,  0}, "macOS", {"Tiger",         {10,  4,  2}}},
+			{"8E45",     { 8,  2,  0}, "macOS", {"Tiger",         {10,  4,  2}}},
+			{"8E90",     { 8,  2,  0}, "macOS", {"Tiger",         {10,  4,  2}}},
+			{"8F46",     { 8,  3,  0}, "macOS", {"Tiger",         {10,  4,  3}}},
+			{"8G32",     { 8,  4,  0}, "macOS", {"Tiger",         {10,  4,  4}}},
+			{"8G1165",   { 8,  4,  0}, "macOS", {"Tiger",         {10,  4,  4}}},
+			{"8H14",     { 8,  5,  0}, "macOS", {"Tiger",         {10,  4,  5}}},
+			{"8G1454",   { 8,  5,  0}, "macOS", {"Tiger",         {10,  4,  5}}},
+			{"8I127",    { 8,  6,  0}, "macOS", {"Tiger",         {10,  4,  6}}},
+			{"8I1119",   { 8,  6,  0}, "macOS", {"Tiger",         {10,  4,  6}}},
+			{"8J135",    { 8,  7,  0}, "macOS", {"Tiger",         {10,  4,  7}}},
+			{"8J2135a",  { 8,  7,  0}, "macOS", {"Tiger",         {10,  4,  7}}},
+			{"8K1079",   { 8,  7,  0}, "macOS", {"Tiger",         {10,  4,  7}}},
+			{"8N5107",   { 8,  7,  0}, "macOS", {"Tiger",         {10,  4,  7}}},
+			{"8L127",    { 8,  8,  0}, "macOS", {"Tiger",         {10,  4,  8}}},
+			{"8L2127",   { 8,  8,  0}, "macOS", {"Tiger",         {10,  4,  8}}},
+			{"8P135",    { 8,  9,  0}, "macOS", {"Tiger",         {10,  4,  9}}},
+			{"8P2137",   { 8,  9,  0}, "macOS", {"Tiger",         {10,  4,  9}}},
+			{"8R218",    { 8, 10,  0}, "macOS", {"Tiger",         {10,  4, 10}}},
+			{"8R2218",   { 8, 10,  0}, "macOS", {"Tiger",         {10,  4, 10}}},
+			{"8R2232",   { 8, 10,  0}, "macOS", {"Tiger",         {10,  4, 10}}},
+			{"8S165",    { 8, 11,  0}, "macOS", {"Tiger",         {10,  4, 11}}},
+			{"8S2167",   { 8, 11,  0}, "macOS", {"Tiger",         {10,  4, 11}}},
+			{"9A581",    { 9,  0,  0}, "macOS", {"Leopard",       {10,  5,  0}}},
+			{"9B18",     { 9,  1,  0}, "macOS", {"Leopard",       {10,  5,  1}}},
+			{"9B2117",   { 9,  1,  1}, "macOS", {"Leopard",       {10,  5,  1}}},
+			{"9C31",     { 9,  2,  0}, "macOS", {"Leopard",       {10,  5,  2}}},
+			{"9C7010",   { 9,  2,  0}, "macOS", {"Leopard",       {10,  5,  2}}},
+			{"9D34",     { 9,  3,  0}, "macOS", {"Leopard",       {10,  5,  3}}},
+			{"9E17",     { 9,  4,  0}, "macOS", {"Leopard",       {10,  5,  4}}},
+			{"9F33",     { 9,  5,  0}, "macOS", {"Leopard",       {10,  5,  5}}},
+			{"9G55",     { 9,  6,  0}, "macOS", {"Leopard",       {10,  5,  6}}},
+			{"9G66",     { 9,  6,  0}, "macOS", {"Leopard",       {10,  5,  6}}},
+			{"9G71",     { 9,  6,  0}, "macOS", {"Leopard",       {10,  5,  6}}},
+			{"9J61",     { 9,  7,  0}, "macOS", {"Leopard",       {10,  5,  7}}},
+			{"9L30",     { 9,  8,  0}, "macOS", {"Leopard",       {10,  5,  8}}},
+			{"9L34",     { 9,  8,  0}, "macOS", {"Leopard",       {10,  5,  8}}},
+			{"10A432",   {10,  0,  0}, "macOS", {"Snow Leopard",  {10,  6,  0}}},
+			{"10A433",   {10,  0,  0}, "macOS", {"Snow Leopard",  {10,  6,  0}}},
+			{"10B504",   {10,  1,  0}, "macOS", {"Snow Leopard",  {10,  6,  1}}},
+			{"10C540",   {10,  2,  0}, "macOS", {"Snow Leopard",  {10,  6,  2}}},
+			{"10D573",   {10,  3,  0}, "macOS", {"Snow Leopard",  {10,  6,  3}}},
+			{"10D575",   {10,  3,  0}, "macOS", {"Snow Leopard",  {10,  6,  3}}},
+			{"10D578",   {10,  3,  0}, "macOS", {"Snow Leopard",  {10,  6,  3}}},
+			{"10F569",   {10,  4,  0}, "macOS", {"Snow Leopard",  {10,  6,  4}}},
+			{"10H574",   {10,  5,  0}, "macOS", {"Snow Leopard",  {10,  6,  5}}},
+			{"10J567",   {10,  6,  0}, "macOS", {"Snow Leopard",  {10,  6,  6}}},
+			{"10J869",   {10,  7,  0}, "macOS", {"Snow Leopard",  {10,  6,  7}}},
+			{"10J3250",  {10,  7,  0}, "macOS", {"Snow Leopard",  {10,  6,  7}}},
+			{"10J4138",  {10,  7,  0}, "macOS", {"Snow Leopard",  {10,  6,  7}}},
+			{"10K540",   {10,  8,  0}, "macOS", {"Snow Leopard",  {10,  6,  8}}},
+			{"10K549",   {10,  8,  0}, "macOS", {"Snow Leopard",  {10,  6,  8}}},
+			{"11A511",   {11,  0,  0}, "macOS", {"Lion",          {10,  7,  0}}},
+			{"11A511s",  {11,  0,  0}, "macOS", {"Lion",          {10,  7,  0}}},
+			{"11A2061",  {11,  0,  2}, "macOS", {"Lion",          {10,  7,  0}}},
+			{"11A2063",  {11,  0,  2}, "macOS", {"Lion",          {10,  7,  0}}},
+			{"11B26",    {11,  1,  0}, "macOS", {"Lion",          {10,  7,  1}}},
+			{"11B2118",  {11,  1,  0}, "macOS", {"Lion",          {10,  7,  1}}},
+			{"11C74",    {11,  2,  0}, "macOS", {"Lion",          {10,  7,  2}}},
+			{"11D50",    {11,  3,  0}, "macOS", {"Lion",          {10,  7,  3}}},
+			{"11E53",    {11,  4,  0}, "macOS", {"Lion",          {10,  7,  4}}},
+			{"11G56",    {11,  4,  2}, "macOS", {"Lion",          {10,  7,  5}}},
+			{"11G63",    {11,  4,  2}, "macOS", {"Lion",          {10,  7,  5}}},
+			{"12A269",   {12,  0,  0}, "macOS", {"Mountain Lion", {10,  8,  0}}},
+			{"12B19",    {12,  1,  0}, "macOS", {"Mountain Lion", {10,  8,  1}}},
+			{"12C54",    {12,  2,  0}, "macOS", {"Mountain Lion", {10,  8,  2}}},
+			{"12C60",    {12,  2,  0}, "macOS", {"Mountain Lion", {10,  8,  2}}},
+			{"12C2034",  {12,  2,  0}, "macOS", {"Mountain Lion", {10,  8,  2}}},
+			{"12C3104",  {12,  2,  0}, "macOS", {"Mountain Lion", {10,  8,  2}}},
+			{"12D78",    {12,  3,  0}, "macOS", {"Mountain Lion", {10,  8,  3}}},
+			{"12E55",    {12,  4,  0}, "macOS", {"Mountain Lion", {10,  8,  4}}},
+			{"12E3067",  {12,  4,  0}, "macOS", {"Mountain Lion", {10,  8,  4}}},
+			{"12E4022",  {12,  4,  0}, "macOS", {"Mountain Lion", {10,  8,  4}}},
+			{"12F37",    {12,  5,  0}, "macOS", {"Mountain Lion", {10,  8,  5}}},
+			{"12F45",    {12,  5,  0}, "macOS", {"Mountain Lion", {10,  8,  5}}},
+			{"12F2501",  {12,  5,  0}, "macOS", {"Mountain Lion", {10,  8,  5}}},
+			{"12F2518",  {12,  5,  0}, "macOS", {"Mountain Lion", {10,  8,  5}}},
+			{"12F2542",  {12,  5,  0}, "macOS", {"Mountain Lion", {10,  8,  5}}},
+			{"12F2560",  {12,  5,  0}, "macOS", {"Mountain Lion", {10,  8,  5}}},
+			{"13A603",   {13,  0,  0}, "macOS", {"Mavericks",     {10,  9,  0}}},
+			{"13B42",    {13,  0,  0}, "macOS", {"Mavericks",     {10,  9,  1}}},
+			{"13C64",    {13,  1,  0}, "macOS", {"Mavericks",     {10,  9,  2}}},
+			{"13C1021",  {13,  1,  0}, "macOS", {"Mavericks",     {10,  9,  2}}},
+			{"13D65",    {13,  2,  0}, "macOS", {"Mavericks",     {10,  9,  3}}},
+			{"13E28",    {13,  3,  0}, "macOS", {"Mavericks",     {10,  9,  4}}},
+			{"13F34",    {13,  4,  0}, "macOS", {"Mavericks",     {10,  9,  5}}},
+			{"13F1066",  {13,  4,  0}, "macOS", {"Mavericks",     {10,  9,  5}}},
+			{"13F1077",  {13,  4,  0}, "macOS", {"Mavericks",     {10,  9,  5}}},
+			{"13F1096",  {13,  4,  0}, "macOS", {"Mavericks",     {10,  9,  5}}},
+			{"13F1112",  {13,  4,  0}, "macOS", {"Mavericks",     {10,  9,  5}}},
+			{"13F1134",  {13,  4,  0}, "macOS", {"Mavericks",     {10,  9,  5}}},
+			{"13F1507",  {13,  4,  0}, "macOS", {"Mavericks",     {10,  9,  5}}},
+			{"13F1603",  {13,  4,  0}, "macOS", {"Mavericks",     {10,  9,  5}}},
+			{"13F1712",  {13,  4,  0}, "macOS", {"Mavericks",     {10,  9,  5}}},
+			{"13F1808",  {13,  4,  0}, "macOS", {"Mavericks",     {10,  9,  5}}},
+			{"13F1911",  {13,  4,  0}, "macOS", {"Mavericks",     {10,  9,  5}}},
+			{"14A389",   {14,  0,  0}, "macOS", {"Yosemite",      {10, 10,  0}}},
+			{"14B25",    {14,  0,  0}, "macOS", {"Yosemite",      {10, 10,  1}}},
+			{"14C109",   {14,  1,  0}, "macOS", {"Yosemite",      {10, 10,  2}}},
+			{"14C1510",  {14,  1,  0}, "macOS", {"Yosemite",      {10, 10,  2}}},
+			{"14C2043",  {14,  1,  0}, "macOS", {"Yosemite",      {10, 10,  2}}},
+			{"14C1514",  {14,  1,  0}, "macOS", {"Yosemite",      {10, 10,  2}}},
+			{"14C2513",  {14,  1,  0}, "macOS", {"Yosemite",      {10, 10,  2}}},
+			{"14D131",   {14,  3,  0}, "macOS", {"Yosemite",      {10, 10,  3}}},
+			{"14D136",   {14,  3,  0}, "macOS", {"Yosemite",      {10, 10,  3}}},
+			{"14E46",    {14,  4,  0}, "macOS", {"Yosemite",      {10, 10,  4}}},
+			{"14F27",    {14,  5,  0}, "macOS", {"Yosemite",      {10, 10,  5}}},
+			{"14F1021",  {14,  5,  0}, "macOS", {"Yosemite",      {10, 10,  5}}},
+			{"14F1505",  {14,  5,  0}, "macOS", {"Yosemite",      {10, 10,  5}}},
+			{"14F1509",  {14,  5,  0}, "macOS", {"Yosemite",      {10, 10,  5}}},
+			{"14F1605",  {14,  5,  0}, "macOS", {"Yosemite",      {10, 10,  5}}},
+			{"14F1713",  {14,  5,  0}, "macOS", {"Yosemite",      {10, 10,  5}}},
+			{"14F1808",  {14,  5,  0}, "macOS", {"Yosemite",      {10, 10,  5}}},
+			{"14F1909",  {14,  5,  0}, "macOS", {"Yosemite",      {10, 10,  5}}},
+			{"14F1912",  {14,  5,  0}, "macOS", {"Yosemite",      {10, 10,  5}}},
+			{"14F2009",  {14,  5,  0}, "macOS", {"Yosemite",      {10, 10,  5}}},
+			{"14F2109",  {14,  5,  0}, "macOS", {"Yosemite",      {10, 10,  5}}},
+			{"14F2315",  {14,  5,  0}, "macOS", {"Yosemite",      {10, 10,  5}}},
+			{"14F2411",  {14,  5,  0}, "macOS", {"Yosemite",      {10, 10,  5}}},
+			{"14F2511",  {14,  5,  0}, "macOS", {"Yosemite",      {10, 10,  5}}},
+			{"15A284",   {15,  0,  0}, "macOS", {"El Capitan",    {10, 11,  0}}},
+			{"15B42",    {15,  0,  0}, "macOS", {"El Capitan",    {10, 11,  1}}},
+			{"15C50",    {15,  2,  0}, "macOS", {"El Capitan",    {10, 11,  2}}},
+			{"15D21",    {15,  3,  0}, "macOS", {"El Capitan",    {10, 11,  3}}},
+			{"15E65",    {15,  4,  0}, "macOS", {"El Capitan",    {10, 11,  4}}},
+			{"15F34",    {15,  5,  0}, "macOS", {"El Capitan",    {10, 11,  5}}},
+			{"15G31",    {15,  6,  0}, "macOS", {"El Capitan",    {10, 11,  6}}},
+			{"15G1004",  {15,  6,  0}, "macOS", {"El Capitan",    {10, 11,  6}}},
+			{"15G1011",  {15,  6,  0}, "macOS", {"El Capitan",    {10, 11,  6}}},
+			{"15G1108",  {15,  6,  0}, "macOS", {"El Capitan",    {10, 11,  6}}},
+			{"15G1212",  {15,  6,  0}, "macOS", {"El Capitan",    {10, 11,  6}}},
+			{"15G1217",  {15,  6,  0}, "macOS", {"El Capitan",    {10, 11,  6}}},
+			{"15G1421",  {15,  6,  0}, "macOS", {"El Capitan",    {10, 11,  6}}},
+			{"15G1510",  {15,  6,  0}, "macOS", {"El Capitan",    {10, 11,  6}}},
+			{"15G1611",  {15,  6,  0}, "macOS", {"El Capitan",    {10, 11,  6}}},
+			{"15G17023", {15,  6,  0}, "macOS", {"El Capitan",    {10, 11,  6}}},
+			{"15G18013", {15,  6,  0}, "macOS", {"El Capitan",    {10, 11,  6}}},
+			{"15G19009", {15,  6,  0}, "macOS", {"El Capitan",    {10, 11,  6}}},
+			{"15G20015", {15,  6,  0}, "macOS", {"El Capitan",    {10, 11,  6}}},
+			{"15G21013", {15,  6,  0}, "macOS", {"El Capitan",    {10, 11,  6}}},
+			{"15G22010", {15,  6,  0}, "macOS", {"El Capitan",    {10, 11,  6}}},
+			{"16A323",   {16,  0,  0}, "macOS", {"Sierra",        {10, 12,  0}}},
+			{"16B2555",  {16,  1,  0}, "macOS", {"Sierra",        {10, 12,  1}}},
+			{"16B2657",  {16,  1,  0}, "macOS", {"Sierra",        {10, 12,  1}}},
+			{"16C67",    {16,  3,  0}, "macOS", {"Sierra",        {10, 12,  2}}},
+			{"16C68",    {16,  3,  0}, "macOS", {"Sierra",        {10, 12,  2}}},
+			{"16D32",    {16,  4,  0}, "macOS", {"Sierra",        {10, 12,  3}}},
+			{"16E195",   {16,  5,  0}, "macOS", {"Sierra",        {10, 12,  4}}},
+			{"16F73",    {16,  6,  0}, "macOS", {"Sierra",        {10, 12,  5}}},
+			{"16F2073",  {16,  6,  0}, "macOS", {"Sierra",        {10, 12,  5}}},
+			{"16G29",    {16,  7,  0}, "macOS", {"Sierra",        {10, 12,  6}}},
+			{"16G1036",  {16,  7,  0}, "macOS", {"Sierra",        {10, 12,  6}}},
+			{"16G1114",  {16,  7,  0}, "macOS", {"Sierra",        {10, 12,  6}}},
+			{"16G1212",  {16,  7,  0}, "macOS", {"Sierra",        {10, 12,  6}}},
+			{"16G1314",  {16,  7,  0}, "macOS", {"Sierra",        {10, 12,  6}}},
+			{"16G1408",  {16,  7,  0}, "macOS", {"Sierra",        {10, 12,  6}}},
+			{"16G1510",  {16,  7,  0}, "macOS", {"Sierra",        {10, 12,  6}}},
+			{"16G1618",  {16,  7,  0}, "macOS", {"Sierra",        {10, 12,  6}}},
+			{"16G1710",  {16,  7,  0}, "macOS", {"Sierra",        {10, 12,  6}}},
+			{"16G1815",  {16,  7,  0}, "macOS", {"Sierra",        {10, 12,  6}}},
+			{"16G1917",  {16,  7,  0}, "macOS", {"Sierra",        {10, 12,  6}}},
+			{"16G1918",  {16,  7,  0}, "macOS", {"Sierra",        {10, 12,  6}}},
+			{"16G2016",  {16,  7,  0}, "macOS", {"Sierra",        {10, 12,  6}}},
+			{"16G2127",  {16,  7,  0}, "macOS", {"Sierra",        {10, 12,  6}}},
+			{"16G2128",  {16,  7,  0}, "macOS", {"Sierra",        {10, 12,  6}}},
+			{"16G2136",  {16,  7,  0}, "macOS", {"Sierra",        {10, 12,  6}}},
+			{"17A365",   {17,  0,  0}, "macOS", {"High Sierra",   {10, 13,  0}}},
+			{"17A405",   {17,  0,  0}, "macOS", {"High Sierra",   {10, 13,  0}}},
+			{"17B48",    {17,  2,  0}, "macOS", {"High Sierra",   {10, 13,  1}}},
+			{"17B1002",  {17,  2,  0}, "macOS", {"High Sierra",   {10, 13,  1}}},
+			{"17B1003",  {17,  2,  0}, "macOS", {"High Sierra",   {10, 13,  1}}},
+			{"17C88",    {17,  3,  0}, "macOS", {"High Sierra",   {10, 13,  2}}},
+			{"17C89",    {17,  3,  0}, "macOS", {"High Sierra",   {10, 13,  2}}},
+			{"17C205",   {17,  3,  0}, "macOS", {"High Sierra",   {10, 13,  2}}},
+			{"17C2205",  {17,  3,  0}, "macOS", {"High Sierra",   {10, 13,  2}}},
+			{"17D47",    {17,  4,  0}, "macOS", {"High Sierra",   {10, 13,  3}}},
+			{"17D2047",  {17,  4,  0}, "macOS", {"High Sierra",   {10, 13,  3}}},
+			{"17D102",   {17,  4,  0}, "macOS", {"High Sierra",   {10, 13,  3}}},
+			{"17D2102",  {17,  4,  0}, "macOS", {"High Sierra",   {10, 13,  3}}},
+			{"17E199",   {17,  5,  0}, "macOS", {"High Sierra",   {10, 13,  4}}},
+			{"17E202",   {17,  5,  0}, "macOS", {"High Sierra",   {10, 13,  4}}},
+			{"17F77",    {17,  6,  0}, "macOS", {"High Sierra",   {10, 13,  5}}},
+			{"17G65",    {17,  7,  0}, "macOS", {"High Sierra",   {10, 13,  6}}},
+			{"17G2208",  {17,  7,  0}, "macOS", {"High Sierra",   {10, 13,  6}}},
+			{"17G2307",  {17,  7,  0}, "macOS", {"High Sierra",   {10, 13,  6}}},
+			{"17G3025",  {17,  7,  0}, "macOS", {"High Sierra",   {10, 13,  6}}},
+			{"17G4015",  {17,  7,  0}, "macOS", {"High Sierra",   {10, 13,  6}}},
+			{"17G5019",  {17,  7,  0}, "macOS", {"High Sierra",   {10, 13,  6}}},
+			{"17G6029",  {17,  7,  0}, "macOS", {"High Sierra",   {10, 13,  6}}},
+			{"17G6030",  {17,  7,  0}, "macOS", {"High Sierra",   {10, 13,  6}}},
+			{"17G7024",  {17,  7,  0}, "macOS", {"High Sierra",   {10, 13,  6}}},
+			{"17G8029",  {17,  7,  0}, "macOS", {"High Sierra",   {10, 13,  6}}},
+			{"17G8030",  {17,  7,  0}, "macOS", {"High Sierra",   {10, 13,  6}}},
+			{"17G8037",  {17,  7,  0}, "macOS", {"High Sierra",   {10, 13,  6}}},
+			{"17G9016",  {17,  7,  0}, "macOS", {"High Sierra",   {10, 13,  6}}},
+			{"17G10021", {17,  7,  0}, "macOS", {"High Sierra",   {10, 13,  6}}},
+			{"17G11023", {17,  7,  0}, "macOS", {"High Sierra",   {10, 13,  6}}},
+			{"17G12034", {17,  7,  0}, "macOS", {"High Sierra",   {10, 13,  6}}},
+			{"17G13033", {17,  7,  0}, "macOS", {"High Sierra",   {10, 13,  6}}},
+			{"17G13035", {17,  7,  0}, "macOS", {"High Sierra",   {10, 13,  6}}},
+			{"17G14019", {17,  7,  0}, "macOS", {"High Sierra",   {10, 13,  6}}},
+			{"17G14033", {17,  7,  0}, "macOS", {"High Sierra",   {10, 13,  6}}},
+			{"17G14042", {17,  7,  0}, "macOS", {"High Sierra",   {10, 13,  6}}},
+			{"18A391",   {18,  0,  0}, "macOS", {"Mojave",        {10, 14,  0}}},
+			{"18B75",    {18,  2,  0}, "macOS", {"Mojave",        {10, 14,  1}}},
+			{"18B2107",  {18,  2,  0}, "macOS", {"Mojave",        {10, 14,  1}}},
+			{"18B3094",  {18,  2,  0}, "macOS", {"Mojave",        {10, 14,  1}}},
+			{"18C54",    {18,  2,  0}, "macOS", {"Mojave",        {10, 14,  2}}},
+			{"18D42",    {18,  2,  0}, "macOS", {"Mojave",        {10, 14,  3}}},
+			{"18D43",    {18,  2,  0}, "macOS", {"Mojave",        {10, 14,  3}}},
+			{"18D109",   {18,  2,  0}, "macOS", {"Mojave",        {10, 14,  3}}},
+			{"18E226",   {18,  5,  0}, "macOS", {"Mojave",        {10, 14,  4}}},
+			{"18E227",   {18,  5,  0}, "macOS", {"Mojave",        {10, 14,  4}}},
+			{"18F132",   {18,  6,  0}, "macOS", {"Mojave",        {10, 14,  5}}},
+			{"18G84",    {18,  7,  0}, "macOS", {"Mojave",        {10, 14,  6}}},
+			{"18G87",    {18,  7,  0}, "macOS", {"Mojave",        {10, 14,  6}}},
+			{"18G95",    {18,  7,  0}, "macOS", {"Mojave",        {10, 14,  6}}},
+			{"18G103",   {18,  7,  0}, "macOS", {"Mojave",        {10, 14,  6}}},
+			{"18G1012",  {18,  7,  0}, "macOS", {"Mojave",        {10, 14,  6}}},
+			{"18G2022",  {18,  7,  0}, "macOS", {"Mojave",        {10, 14,  6}}},
+			{"18G3020",  {18,  7,  0}, "macOS", {"Mojave",        {10, 14,  6}}},
+			{"18G4032",  {18,  7,  0}, "macOS", {"Mojave",        {10, 14,  6}}},
+			{"18G5033",  {18,  7,  0}, "macOS", {"Mojave",        {10, 14,  6}}},
+			{"18G6020",  {18,  7,  0}, "macOS", {"Mojave",        {10, 14,  6}}},
+			{"18G6032",  {18,  7,  0}, "macOS", {"Mojave",        {10, 14,  6}}},
+			{"18G6042",  {18,  7,  0}, "macOS", {"Mojave",        {10, 14,  6}}},
+			{"18G7016",  {18,  7,  0}, "macOS", {"Mojave",        {10, 14,  6}}},
+			{"18G8012",  {18,  7,  0}, "macOS", {"Mojave",        {10, 14,  6}}},
+			{"18G8022",  {18,  7,  0}, "macOS", {"Mojave",        {10, 14,  6}}},
+			{"18G9028",  {18,  7,  0}, "macOS", {"Mojave",        {10, 14,  6}}},
+			{"18G9216",  {18,  7,  0}, "macOS", {"Mojave",        {10, 14,  6}}},
+			{"18G9323",  {18,  7,  0}, "macOS", {"Mojave",        {10, 14,  6}}},
+			{"19A583",   {19,  0,  0}, "macOS", {"Catalina",      {10, 15,  0}}},
+			{"19A602",   {19,  0,  0}, "macOS", {"Catalina",      {10, 15,  0}}},
+			{"19A603",   {19,  0,  0}, "macOS", {"Catalina",      {10, 15,  0}}},
+			{"19B88",    {19,  0,  0}, "macOS", {"Catalina",      {10, 15,  1}}},
+			{"19C57",    {19,  2,  0}, "macOS", {"Catalina",      {10, 15,  2}}},
+			{"19C58",    {19,  2,  0}, "macOS", {"Catalina",      {10, 15,  2}}},
+			{"19D76",    {19,  3,  0}, "macOS", {"Catalina",      {10, 15,  3}}},
+			{"19E266",   {19,  4,  0}, "macOS", {"Catalina",      {10, 15,  4}}},
+			{"19E287",   {19,  4,  0}, "macOS", {"Catalina",      {10, 15,  4}}},
+			{"19F96",    {19,  5,  0}, "macOS", {"Catalina",      {10, 15,  5}}},
+			{"19F101",   {19,  5,  0}, "macOS", {"Catalina",      {10, 15,  5}}},
+			{"19G73",    {19,  6,  0}, "macOS", {"Catalina",      {10, 15,  6}}},
+			{"19G2021",  {19,  6,  0}, "macOS", {"Catalina",      {10, 15,  6}}},
+			{"19H2",     {19,  6,  0}, "macOS", {"Catalina",      {10, 15,  7}}},
+			{"19H4",     {19,  6,  0}, "macOS", {"Catalina",      {10, 15,  7}}},
+			{"19H15",    {19,  6,  0}, "macOS", {"Catalina",      {10, 15,  7}}},
+			{"19H114",   {19,  6,  0}, "macOS", {"Catalina",      {10, 15,  7}}},
+			{"19H512",   {19,  6,  0}, "macOS", {"Catalina",      {10, 15,  7}}},
+			{"19H524",   {19,  6,  0}, "macOS", {"Catalina",      {10, 15,  7}}},
+			{"19H1030",  {19,  6,  0}, "macOS", {"Catalina",      {10, 15,  7}}},
+			{"19H1217",  {19,  6,  0}, "macOS", {"Catalina",      {10, 15,  7}}},
+			{"19H1323",  {19,  6,  0}, "macOS", {"Catalina",      {10, 15,  7}}},
+			{"19H1417",  {19,  6,  0}, "macOS", {"Catalina",      {10, 15,  7}}},
+			{"19H1419",  {19,  6,  0}, "macOS", {"Catalina",      {10, 15,  7}}},
+			{"19H1519",  {19,  6,  0}, "macOS", {"Catalina",      {10, 15,  7}}},
+			{"19H1615",  {19,  6,  0}, "macOS", {"Catalina",      {10, 15,  7}}},
+			{"19H1713",  {19,  6,  0}, "macOS", {"Catalina",      {10, 15,  7}}},
+			{"19H1715",  {19,  6,  0}, "macOS", {"Catalina",      {10, 15,  7}}},
+			{"19H1824",  {19,  6,  0}, "macOS", {"Catalina",      {10, 15,  7}}},
+			{"19H1922",  {19,  6,  0}, "macOS", {"Catalina",      {10, 15,  7}}},
+			{"19H2026",  {19,  6,  0}, "macOS", {"Catalina",      {10, 15,  7}}},
+			{"20A2411",  {20,  1,  0}, "macOS", {"Big Sur",       {11,  0,  0}}},
+			{"20B29",    {20,  1,  0}, "macOS", {"Big Sur",       {11,  0,  1}}},
+			{"20B50",    {20,  1,  0}, "macOS", {"Big Sur",       {11,  0,  1}}},
+			{"20C69",    {20,  2,  0}, "macOS", {"Big Sur",       {11,  1,  0}}},
+			{"20D64",    {20,  3,  0}, "macOS", {"Big Sur",       {11,  2,  0}}},
+			{"20D74",    {20,  3,  0}, "macOS", {"Big Sur",       {11,  2,  1}}},
+			{"20D75",    {20,  3,  0}, "macOS", {"Big Sur",       {11,  2,  1}}},
+			{"20D80",    {20,  3,  0}, "macOS", {"Big Sur",       {11,  2,  2}}},
+			{"20D91",    {20,  3,  0}, "macOS", {"Big Sur",       {11,  2,  3}}},
+			{"20E232",   {20,  4,  0}, "macOS", {"Big Sur",       {11,  3,  0}}},
+			{"20E241",   {20,  4,  0}, "macOS", {"Big Sur",       {11,  3,  1}}},
+			{"20F71",    {20,  5,  0}, "macOS", {"Big Sur",       {11,  4,  0}}},
+			{"20G71",    {20,  6,  0}, "macOS", {"Big Sur",       {11,  5,  0}}},
+			{"20G80",    {20,  6,  0}, "macOS", {"Big Sur",       {11,  5,  1}}},
+			{"20G95",    {20,  6,  0}, "macOS", {"Big Sur",       {11,  5,  2}}},
+			{"20G165",   {20,  6,  0}, "macOS", {"Big Sur",       {11,  6,  0}}},
+			{"20G224",   {20,  6,  0}, "macOS", {"Big Sur",       {11,  6,  1}}},
+			{"20G314",   {20,  6,  0}, "macOS", {"Big Sur",       {11,  6,  2}}},
+			{"20G415",   {20,  6,  0}, "macOS", {"Big Sur",       {11,  6,  3}}},
+			{"20G417",   {20,  6,  0}, "macOS", {"Big Sur",       {11,  6,  4}}},
+			{"20G527",   {20,  6,  0}, "macOS", {"Big Sur",       {11,  6,  5}}},
+			{"20G624",   {20,  6,  0}, "macOS", {"Big Sur",       {11,  6,  6}}},
+			{"20G630",   {20,  6,  0}, "macOS", {"Big Sur",       {11,  6,  7}}},
+			{"20G730",   {20,  6,  0}, "macOS", {"Big Sur",       {11,  6,  8}}},
+			{"20G817",   {20,  6,  0}, "macOS", {"Big Sur",       {11, 7,   0}}},
+			{"20G918",   {20,  6,  0}, "macOS", {"Big Sur",       {11, 7,   1}}},
+			{"20G1020",  {20,  6,  0}, "macOS", {"Big Sur",       {11, 7,   2}}},
+			{"20G1116",  {20,  6,  0}, "macOS", {"Big Sur",       {11, 7,   3}}},
+			{"20G1120",  {20,  6,  0}, "macOS", {"Big Sur",       {11, 7,   4}}},
+			{"20G1225",  {20,  6,  0}, "macOS", {"Big Sur",       {11, 7,   5}}},
+			{"20G1231",  {20,  6,  0}, "macOS", {"Big Sur",       {11, 7,   6}}},
+			{"20G1345",  {20,  6,  0}, "macOS", {"Big Sur",       {11, 7,   7}}},
+			{"20G1351",  {20,  6,  0}, "macOS", {"Big Sur",       {11, 7,   8}}},
+			{"20G1426",  {20,  6,  0}, "macOS", {"Big Sur",       {11, 7,   9}}},
+			{"20G1427",  {20,  6,  0}, "macOS", {"Big Sur",       {11, 7,  10}}},
+			{"21A344",   {21,  0,  1}, "macOS", {"Monterey",      {12,  0,  0}}},
+			{"21A559",   {21,  1,  0}, "macOS", {"Monterey",      {12,  0,  1}}},
+			{"21C52",    {21,  2,  0}, "macOS", {"Monterey",      {12,  1,  0}}},
+			{"21D49",    {21,  3,  0}, "macOS", {"Monterey",      {12,  2,  0}}},
+			{"21D62",    {21,  3,  0}, "macOS", {"Monterey",      {12,  2,  1}}},
+			{"21E230",   {21,  4,  0}, "macOS", {"Monterey",      {12,  3,  0}}},
+			{"21E258",   {21,  4,  0}, "macOS", {"Monterey",      {12,  3,  1}}},
+			{"21F79",    {21,  5,  0}, "macOS", {"Monterey",      {12,  4,  0}}},
+			{"21F2081",  {21,  5,  0}, "macOS", {"Monterey",      {12,  4,  0}}},
+			{"21F2092",  {21,  5,  0}, "macOS", {"Monterey",      {12,  4,  0}}},
+			{"21G72",    {21,  6,  0}, "macOS", {"Monterey",      {12,  5,  0}}},
+			{"21G83",    {21,  6,  0}, "macOS", {"Monterey",      {12,  5,  1}}},
+			{"21G115",   {21,  6,  0}, "macOS", {"Monterey",      {12,  6,  0}}},
+			{"21G217",   {21,  6,  0}, "macOS", {"Monterey",      {12,  6,  1}}},
+			{"21G320",   {21,  6,  0}, "macOS", {"Monterey",      {12,  6,  2}}},
+			{"21G419",   {21,  6,  0}, "macOS", {"Monterey",      {12,  6,  3}}},
+			{"21G526",   {21,  6,  0}, "macOS", {"Monterey",      {12,  6,  4}}},
+			{"21G531",   {21,  6,  0}, "macOS", {"Monterey",      {12,  6,  5}}},
+			{"21G646",   {21,  6,  0}, "macOS", {"Monterey",      {12,  6,  6}}},
+			{"21G651",   {21,  6,  0}, "macOS", {"Monterey",      {12,  6,  7}}},
+			{"21G725",   {21,  6,  0}, "macOS", {"Monterey",      {12,  6,  8}}},
+			{"21G726",   {21,  6,  0}, "macOS", {"Monterey",      {12,  6,  9}}},
+			{"21G816",   {21,  6,  0}, "macOS", {"Monterey",      {12,  7,  0}}},
+			{"21G920",   {21,  6,  0}, "macOS", {"Monterey",      {12,  7,  1}}},
+			{"21G1974",  {21,  6,  0}, "macOS", {"Monterey",      {12,  7,  2}}},
+			{"22A380",   {13,  0,  0}, "macOS", {"Ventura",       {22,  1,  0}}},
+			{"22A400",   {13,  0,  1}, "macOS", {"Ventura",       {22,  1,  0}}},
+			{"22C65",    {13,  1,  0}, "macOS", {"Ventura",       {22,  2,  0}}},
+			{"22D49",    {13,  2,  0}, "macOS", {"Ventura",       {22,  3,  0}}},
+			{"22D68",    {13,  2,  1}, "macOS", {"Ventura",       {22,  3,  0}}},
+			{"22E252",   {13,  3,  0}, "macOS", {"Ventura",       {22,  4,  0}}},
+			{"22E261",   {13,  3,  1}, "macOS", {"Ventura",       {22,  4,  0}}},
+			{"22F66",    {13,  4,  0}, "macOS", {"Ventura",       {22,  5,  0}}},
+			{"22F82",    {13,  4,  1}, "macOS", {"Ventura",       {22,  5,  0}}},
+			{"22E772610a", {13, 4, 1}, "macOS", {"Ventura",       {22,  5,  0}}},
+			{"22F770820d", {13, 4, 1}, "macOS", {"Ventura",       {22,  5,  0}}},
+			{"22G74",    {13,  5,  0}, "macOS", {"Ventura",       {22,  6,  0}}},
+			{"22G90",    {13,  5,  1}, "macOS", {"Ventura",       {22,  6,  0}}},
+			{"22G91",    {13,  5,  2}, "macOS", {"Ventura",       {22,  6,  0}}},
+			{"22G120",   {13,  6,  0}, "macOS", {"Ventura",       {22,  6,  0}}},
+			{"22G313",   {13,  6,  1}, "macOS", {"Ventura",       {22,  6,  0}}},
+			{"22G320",   {13,  6,  2}, "macOS", {"Ventura",       {22,  6,  0}}},
+			{"23A344",   {23,  0,  0}, "macOS", {"Sonoma",        {14,  0,  0}}},
+			{"23B74",    {23,  1,  0}, "macOS", {"Sonoma",        {14,  1,  0}}},
+			{"23B81",    {23,  1,  0}, "macOS", {"Sonoma",        {14,  1,  1}}},
+			{"23B92",    {23,  1,  0}, "macOS", {"Sonoma",        {14,  1,  2}}},
+			{"23C64",    {23,  2,  0}, "macOS", {"Sonoma",        {14,  2,  0}}},
+			{"23C71",    {23,  2,  0}, "macOS", {"Sonoma",        {14,  2,  1}}},
+		};
+
+
+		b32 build_found  = 1;
+		b32 darwin_found = 1;
+		uint32_t major, minor, patch;
+
+		#define MACOS_VERSION_BUFFER_SIZE 100
+		char build_buffer[MACOS_VERSION_BUFFER_SIZE];
+		char darwin_buffer[MACOS_VERSION_BUFFER_SIZE];
+		size_t build_buffer_size  = MACOS_VERSION_BUFFER_SIZE - 1;
+		size_t darwin_buffer_size = MACOS_VERSION_BUFFER_SIZE - 1;
+		#undef MACOS_VERSION_BUFFER_SIZE
+
+		int build_mibs[] = { CTL_KERN, KERN_OSVERSION };
+		if (sysctl(build_mibs, 2, build_buffer, &build_buffer_size, NULL, 0) == -1) {
+			build_found = 0;
+		}
+
+		int darwin_mibs[] = { CTL_KERN, KERN_OSRELEASE };
+		if (sysctl(darwin_mibs, 2, darwin_buffer, &darwin_buffer_size, NULL, 0) == -1) {
+			gb_printf("macOS Unknown\n");
+			return;
+		} else {
+			if (sscanf(darwin_buffer, "%u.%u.%u", &major, &minor, &patch) != 3) {
+				darwin_found = 0;
+			}
+		}
+
+		// Scan table for match on BUILD
+		int macos_release_count = sizeof(macos_release_map) / sizeof(macos_release_map[0]);
+		Darwin_To_Release build_match = {};
+		Darwin_To_Release kernel_match = {};
+	
+		for (int build = 0; build < macos_release_count; build++) {
+			Darwin_To_Release rel = macos_release_map[build];
+			
+			// Do we have an exact match on the BUILD?
+			if (gb_strcmp(rel.build, (const char *)build_buffer) == 0) {
+				build_match = rel;
+				break;
+			}
+			
+			// Do we have an exact Darwin match?
+			if (rel.darwin[0] == major && rel.darwin[1] == minor && rel.darwin[2] == patch) {
+				kernel_match = rel;
+			}
+	
+			// Major kernel version needs to match exactly,
+			if (rel.darwin[0] == major) {
+				// No major version match yet.
+				if (!kernel_match.os_name) {
+					kernel_match = rel;
+				}
+				if (minor >= rel.darwin[1]) {
+					kernel_match = rel;
+					if (patch >= rel.darwin[2]) {
+						kernel_match = rel;
+					}
+				}
+			}
+		}
+	
+		Darwin_To_Release match = {};
+		if(!build_match.build) {
+			match = kernel_match;
+		} else {
+			match = build_match;
+		}
+
+		if (match.os_name) {
+			gb_printf("%s %s %d", match.os_name, match.release.name, match.release.version[0]);
+			if (match.release.version[1] > 0 || match.release.version[2] > 0) {
+				gb_printf(".%d", match.release.version[1]);
+			}
+			if (match.release.version[2] > 0) {
+				gb_printf(".%d", match.release.version[2]);
+			}
+			if (build_found) {
+				gb_printf(" (build: %s, kernel: %d.%d.%d)\n", build_buffer, match.darwin[0], match.darwin[1], match.darwin[2]);
+			} else {
+				gb_printf(" (build: %s?, kernel: %d.%d.%d)\n", match.build, match.darwin[0], match.darwin[1], match.darwin[2]);				
+			}
+			return;
+		}
+
+		if (build_found && darwin_found) {
+			gb_printf("macOS Unknown (build: %s, kernel: %d.%d.%d)\n", build_buffer, major, minor, patch);
+			return;
+		} else if (build_found) {
+			gb_printf("macOS Unknown (build: %s)\n", build_buffer);
+			return;
+		} else if (darwin_found) {
+			gb_printf("macOS Unknown (kernel: %d.%d.%d)\n", major, minor, patch);
+			return;
+		}
+	#elif defined(GB_SYSTEM_OPENBSD)
+		struct utsname un;
+		
+		if (uname(&un) != -1) {
+			gb_printf("%s %s %s %s\n", un.sysname, un.release, un.version, un.machine);
+		} else {
+			gb_printf("OpenBSD: Unknown\n");    
+		}
+	#elif defined(GB_SYSTEM_FREEBSD)
+		#define freebsd_version_buffer 129
+		char buffer[freebsd_version_buffer];
+		size_t buffer_size = freebsd_version_buffer - 1;
+		#undef freebsd_version_buffer
+
+		int mibs[] = { CTL_KERN, KERN_VERSION };
+		if (sysctl(mibs, 2, buffer, &buffer_size, NULL, 0) == -1) {
+			gb_printf("FreeBSD: Unknown\n");
+		} else {
+			// KERN_VERSION can end in a \n, replace it with a \0
+			for (int i = 0; i < buffer_size; i += 1) {
+				if (buffer[i] == '\n') buffer[i] = 0;
+			}
+			gb_printf("%s", &buffer[0]);
+
+			// Retrieve kernel revision using `sysctl`, e.g. 199506
+			mibs[1] = KERN_OSREV;
+			uint64_t revision;
+			size_t revision_size = sizeof(revision);
+
+			if (sysctl(mibs, 2, &revision, &revision_size, NULL, 0) == -1) {
+				gb_printf("\n");
+			} else {
+				gb_printf(", revision %ld\n", revision);
+			}
+		}
+	#else
+		gb_printf("Unknown");
+	#endif
+}
+
+gb_internal void report_backend_info() {
+	gb_printf("\tBackend: LLVM %s\n", LLVM_VERSION_STRING);
+}
+
+// NOTE(Jeroen): `odin report` prints some system information for easier bug reporting.
+gb_internal void print_bug_report_help() {
+	gb_printf("Where to find more information and get into contact when you encounter a bug:\n\n");
+	gb_printf("\tWebsite: https://odin-lang.org\n");
+	gb_printf("\tGitHub:  https://github.com/odin-lang/Odin/issues\n");
+	/*
+		Uncomment and update URL once we have a Discord vanity URL. For now people can get here from the site.
+		gb_printf("\tDiscord: https://discord.com/invite/sVBPHEv\n");
+	*/
+	gb_printf("\n\n");
+
+	gb_printf("Useful information to add to a bug report:\n\n");
+
+	gb_printf("\tOdin:    %.*s", LIT(ODIN_VERSION));
+
+	#ifdef NIGHTLY
+	gb_printf("-nightly");
+	#endif
+
+	#ifdef GIT_SHA
+	gb_printf(":%s", GIT_SHA);
+	#endif
+
+	gb_printf("\n");
+
+	/*
+		Print OS information.
+	*/
+	report_os_info();
+
+	/*
+		Now print CPU info.
+	*/
+	report_cpu_info();
+
+	/*
+		And RAM info.
+	*/
+	report_ram_info();
+
+	report_backend_info();
+}

+ 20 - 0
src/check_builtin.cpp

@@ -5821,6 +5821,26 @@ gb_internal bool check_builtin_procedure(CheckerContext *c, Operand *operand, As
 		}
 		break;
 
+	case BuiltinProc_type_bit_set_backing_type:
+		{
+			Operand op = {};
+			Type *type = check_type(c, ce->args[0]);
+			Type *bt = base_type(type);
+			if (bt == nullptr || bt == t_invalid) {
+				error(ce->args[0], "Expected a type for '%.*s'", LIT(builtin_name));
+				return false;
+			}
+			if (bt->kind != Type_BitSet) {
+				gbString s = type_to_string(type);
+				error(ce->args[0], "Expected a bit_set type for '%.*s', got %s", LIT(builtin_name), s);
+				return false;
+			}
+
+			operand->mode = Addressing_Type;
+			operand->type = bit_set_to_int(bt);
+			break;
+		}
+
 	case BuiltinProc_type_equal_proc:
 		{
 			Operand op = {};

+ 1 - 0
src/check_decl.cpp

@@ -210,6 +210,7 @@ gb_internal bool is_type_distinct(Ast *node) {
 	case Ast_UnionType:
 	case Ast_EnumType:
 	case Ast_ProcType:
+	case Ast_BitFieldType:
 		return true;
 
 	case Ast_PointerType:

+ 164 - 31
src/check_expr.cpp

@@ -100,7 +100,7 @@ gb_internal void     check_union_type               (CheckerContext *c, Type *un
 gb_internal Type *   check_init_variable            (CheckerContext *c, Entity *e, Operand *operand, String context_name);
 
 
-gb_internal void check_assignment_error_suggestion(CheckerContext *c, Operand *o, Type *type);
+gb_internal void check_assignment_error_suggestion(CheckerContext *c, Operand *o, Type *type, i64 max_bit_size=0);
 gb_internal void add_map_key_type_dependencies(CheckerContext *ctx, Type *key);
 
 gb_internal Type *make_soa_struct_slice(CheckerContext *ctx, Ast *array_typ_expr, Ast *elem_expr, Type *elem);
@@ -1241,7 +1241,7 @@ gb_internal bool is_polymorphic_type_assignable(CheckerContext *c, Type *poly, T
 	}
 	case Type_Pointer:
 		if (source->kind == Type_Pointer) {
-			isize level = check_is_assignable_to_using_subtype(source->Pointer.elem, poly->Pointer.elem);
+			isize level = check_is_assignable_to_using_subtype(source->Pointer.elem, poly->Pointer.elem, /*level*/0, /*src_is_ptr*/false, /*allow_polymorphic*/true);
 			if (level > 0) {
 				return true;
 			}
@@ -1413,7 +1413,9 @@ gb_internal bool is_polymorphic_type_assignable(CheckerContext *c, Type *poly, T
 				return ok;
 
 			}
-			// return check_is_assignable_to(c, &o, poly);
+
+			// NOTE(bill): Check for subtypes of
+			// return check_is_assignable_to(c, &o, poly); // && is_type_subtype_of_and_allow_polymorphic(o.type, poly);
 		}
 		return false;
 	case Type_Tuple:
@@ -1884,33 +1886,55 @@ gb_internal bool check_representable_as_constant(CheckerContext *c, ExactValue i
 
 		BigInt i = v.value_integer;
 
-		i64 bit_size = type_size_of(type);
+		i64 byte_size = type_size_of(type);
 		BigInt umax = {};
 		BigInt imin = {};
 		BigInt imax = {};
 
-		if (bit_size < 16) {
-			big_int_from_u64(&umax, unsigned_integer_maxs[bit_size]);
-			big_int_from_i64(&imin, signed_integer_mins[bit_size]);
-			big_int_from_i64(&imax, signed_integer_maxs[bit_size]);
-		} else {
+		if (c->bit_field_bit_size > 0) {
+			i64 bit_size = gb_min(cast(i64)(8*byte_size), cast(i64)c->bit_field_bit_size);
+
 			big_int_from_u64(&umax, 1);
 			big_int_from_i64(&imin, 1);
 			big_int_from_i64(&imax, 1);
 
-			BigInt bi128 = {};
-			BigInt bi127 = {};
-			big_int_from_i64(&bi128, 128);
-			big_int_from_i64(&bi127, 127);
+			BigInt bu = {};
+			BigInt bi = {};
+			big_int_from_i64(&bu, bit_size);
+			big_int_from_i64(&bi, bit_size-1);
 
-			big_int_shl_eq(&umax, &bi128);
+			big_int_shl_eq(&umax, &bu);
 			mp_decr(&umax);
 
-			big_int_shl_eq(&imin, &bi127);
+			big_int_shl_eq(&imin, &bi);
 			big_int_neg(&imin, &imin);
 
-			big_int_shl_eq(&imax, &bi127);
+			big_int_shl_eq(&imax, &bi);
 			mp_decr(&imax);
+		} else {
+			if (byte_size < 16) {
+				big_int_from_u64(&umax, unsigned_integer_maxs[byte_size]);
+				big_int_from_i64(&imin, signed_integer_mins[byte_size]);
+				big_int_from_i64(&imax, signed_integer_maxs[byte_size]);
+			} else {
+				big_int_from_u64(&umax, 1);
+				big_int_from_i64(&imin, 1);
+				big_int_from_i64(&imax, 1);
+
+				BigInt bi128 = {};
+				BigInt bi127 = {};
+				big_int_from_i64(&bi128, 128);
+				big_int_from_i64(&bi127, 127);
+
+				big_int_shl_eq(&umax, &bi128);
+				mp_decr(&umax);
+
+				big_int_shl_eq(&imin, &bi127);
+				big_int_neg(&imin, &imin);
+
+				big_int_shl_eq(&imax, &bi127);
+				mp_decr(&imax);
+			}
 		}
 
 		switch (type->Basic.kind) {
@@ -2069,11 +2093,17 @@ gb_internal bool check_representable_as_constant(CheckerContext *c, ExactValue i
 }
 
 
-gb_internal bool check_integer_exceed_suggestion(CheckerContext *c, Operand *o, Type *type) {
+gb_internal bool check_integer_exceed_suggestion(CheckerContext *c, Operand *o, Type *type, i64 max_bit_size=0) {
 	if (is_type_integer(type) && o->value.kind == ExactValue_Integer) {
 		gbString b = type_to_string(type);
 
 		i64 sz = type_size_of(type);
+		i64 bit_size = 8*sz;
+		bool size_changed = false;
+		if (max_bit_size > 0) {
+			size_changed = (bit_size != max_bit_size);
+			bit_size = gb_min(bit_size, max_bit_size);
+		}
 		BigInt *bi = &o->value.value_integer;
 		if (is_type_unsigned(type)) {
 			if (big_int_is_neg(bi)) {
@@ -2081,25 +2111,36 @@ gb_internal bool check_integer_exceed_suggestion(CheckerContext *c, Operand *o,
 			} else {
 				BigInt one = big_int_make_u64(1);
 				BigInt max_size = big_int_make_u64(1);
-				BigInt bits = big_int_make_i64(8*sz);
+				BigInt bits = big_int_make_i64(bit_size);
 				big_int_shl_eq(&max_size, &bits);
 				big_int_sub_eq(&max_size, &one);
 				String max_size_str = big_int_to_string(temporary_allocator(), &max_size);
-				error_line("\tThe maximum value that can be represented by '%s' is '%.*s'\n", b, LIT(max_size_str));
+
+				if (size_changed) {
+					error_line("\tThe maximum value that can be represented with that bit_field's field of '%s | %u' is '%.*s'\n", b, bit_size, LIT(max_size_str));
+				} else {
+					error_line("\tThe maximum value that can be represented by '%s' is '%.*s'\n", b, LIT(max_size_str));
+				}
 			}
 		} else {
 			BigInt zero = big_int_make_u64(0);
 			BigInt one = big_int_make_u64(1);
 			BigInt max_size = big_int_make_u64(1);
-			BigInt bits = big_int_make_i64(8*sz - 1);
+			BigInt bits = big_int_make_i64(bit_size - 1);
 			big_int_shl_eq(&max_size, &bits);
+
+			String max_size_str = {};
 			if (big_int_is_neg(bi)) {
 				big_int_neg(&max_size, &max_size);
-				String max_size_str = big_int_to_string(temporary_allocator(), &max_size);
-				error_line("\tThe minimum value that can be represented by '%s' is '%.*s'\n", b, LIT(max_size_str));
+				max_size_str = big_int_to_string(temporary_allocator(), &max_size);
 			} else {
 				big_int_sub_eq(&max_size, &one);
-				String max_size_str = big_int_to_string(temporary_allocator(), &max_size);
+				max_size_str = big_int_to_string(temporary_allocator(), &max_size);
+			}
+
+			if (size_changed) {
+				error_line("\tThe maximum value that can be represented with that bit_field's field of '%s | %u' is '%.*s'\n", b, bit_size, LIT(max_size_str));
+			} else {
 				error_line("\tThe maximum value that can be represented by '%s' is '%.*s'\n", b, LIT(max_size_str));
 			}
 		}
@@ -2110,7 +2151,7 @@ gb_internal bool check_integer_exceed_suggestion(CheckerContext *c, Operand *o,
 	}
 	return false;
 }
-gb_internal void check_assignment_error_suggestion(CheckerContext *c, Operand *o, Type *type) {
+gb_internal void check_assignment_error_suggestion(CheckerContext *c, Operand *o, Type *type, i64 max_bit_size) {
 	gbString a = expr_to_string(o->expr);
 	gbString b = type_to_string(type);
 	defer(
@@ -2141,7 +2182,7 @@ gb_internal void check_assignment_error_suggestion(CheckerContext *c, Operand *o
 		error_line("\t            whereas slices in general are assumed to be mutable.\n");
 	} else if (is_type_u8_slice(src) && are_types_identical(dst, t_string) && o->mode != Addressing_Constant) {
 		error_line("\tSuggestion: the expression may be casted to %s\n", b);
-	} else if (check_integer_exceed_suggestion(c, o, type)) {
+	} else if (check_integer_exceed_suggestion(c, o, type, max_bit_size)) {
 		return;
 	}
 }
@@ -2215,13 +2256,18 @@ gb_internal bool check_is_expressible(CheckerContext *ctx, Operand *o, Type *typ
 			if (!is_type_integer(o->type) && is_type_integer(type)) {
 				error(o->expr, "'%s' truncated to '%s', got %s", a, b, s);
 			} else {
+				i64 max_bit_size = 0;
+				if (ctx->bit_field_bit_size) {
+					max_bit_size = ctx->bit_field_bit_size;
+				}
+
 				if (are_types_identical(o->type, type)) {
 					error(o->expr, "Numeric value '%s' from '%s' cannot be represented by '%s'", s, a, b);
 				} else {
 					error(o->expr, "Cannot convert numeric value '%s' from '%s' to '%s' from '%s'", s, a, b, c);
 				}
 
-				check_assignment_error_suggestion(ctx, o, type);
+				check_assignment_error_suggestion(ctx, o, type, max_bit_size);
 			}
 		} else {
 			error(o->expr, "Cannot convert '%s' to '%s' from '%s', got %s", a, b, c, s);
@@ -2232,6 +2278,11 @@ gb_internal bool check_is_expressible(CheckerContext *ctx, Operand *o, Type *typ
 }
 
 gb_internal bool check_is_not_addressable(CheckerContext *c, Operand *o) {
+	if (o->expr && o->expr->kind == Ast_SelectorExpr) {
+		if (o->expr->SelectorExpr.is_bit_field) {
+			return true;
+		}
+	}
 	if (o->mode == Addressing_OptionalOk) {
 		Ast *expr = unselector_expr(o->expr);
 		if (expr->kind != Ast_TypeAssertion) {
@@ -2304,6 +2355,8 @@ gb_internal void check_unary_expr(CheckerContext *c, Operand *o, Token op, Ast *
 				Entity *e = entity_of_node(ue->expr);
 				if (e != nullptr && (e->flags & EntityFlag_Param) != 0) {
 					error(op, "Cannot take the pointer address of '%s' which is a procedure parameter", str);
+				} else if (e != nullptr && (e->flags & EntityFlag_BitFieldField) != 0) {
+					error(op, "Cannot take the pointer address of '%s' which is a bit_field's field", str);
 				} else {
 					switch (o->mode) {
 					case Addressing_Constant:
@@ -2877,6 +2930,13 @@ gb_internal bool check_is_castable_to(CheckerContext *c, Operand *operand, Type
 		}
 	}
 
+	if (is_type_bit_field(src)) {
+		return are_types_identical(core_type(src->BitField.backing_type), dst);
+	}
+	if (is_type_bit_field(dst)) {
+		return are_types_identical(src, core_type(dst->BitField.backing_type));
+	}
+
 	if (is_type_integer(src) && is_type_rune(dst)) {
 		return true;
 	}
@@ -5065,6 +5125,11 @@ gb_internal Entity *check_selector(CheckerContext *c, Operand *operand, Ast *nod
 	operand->type = entity->type;
 	operand->expr = node;
 
+	if (entity->flags & EntityFlag_BitFieldField) {
+		add_package_dependency(c, "runtime", "__write_bits");
+		add_package_dependency(c, "runtime", "__read_bits");
+	}
+
 	switch (entity->kind) {
 	case Entity_Constant:
 	operand->value = entity->Constant.value;
@@ -5078,6 +5143,9 @@ gb_internal Entity *check_selector(CheckerContext *c, Operand *operand, Ast *nod
 		}
 		break;
 	case Entity_Variable:
+		if (sel.is_bit_field) {
+			se->is_bit_field = true;
+		}
 		if (sel.indirect) {
 			operand->mode = Addressing_Variable;
 		} else if (operand->mode == Addressing_Context) {
@@ -8394,6 +8462,11 @@ gb_internal void check_compound_literal_field_values(CheckerContext *c, Slice<As
 	StringMap<String> fields_visited_through_raw_union = {};
 	defer (string_map_destroy(&fields_visited_through_raw_union));
 
+	String assignment_str = str_lit("structure literal");
+	if (bt->kind == Type_BitField) {
+		assignment_str = str_lit("bit_field literal");
+	}
+
 	for (Ast *elem : elems) {
 		if (elem->kind != Ast_FieldValue) {
 			error(elem, "Mixture of 'field = value' and value elements in a literal is not allowed");
@@ -8415,17 +8488,26 @@ gb_internal void check_compound_literal_field_values(CheckerContext *c, Slice<As
 			continue;
 		}
 
-		Entity *field = bt->Struct.fields[sel.index[0]];
+		Entity *field = nullptr;
+		if (bt->kind == Type_Struct) {
+			field = bt->Struct.fields[sel.index[0]];
+		} else if (bt->kind == Type_BitField) {
+			field = bt->BitField.fields[sel.index[0]];
+		} else {
+			GB_PANIC("Unknown type");
+		}
+
+
 		add_entity_use(c, fv->field, field);
 		if (string_set_update(&fields_visited, name)) {
 			if (sel.index.count > 1) {
 				if (String *found = string_map_get(&fields_visited_through_raw_union, sel.entity->token.string)) {
 					error(fv->field, "Field '%.*s' is already initialized due to a previously assigned struct #raw_union field '%.*s'", LIT(sel.entity->token.string), LIT(*found));
 				} else {
-					error(fv->field, "Duplicate or reused field '%.*s' in structure literal", LIT(sel.entity->token.string));
+					error(fv->field, "Duplicate or reused field '%.*s' in %.*s", LIT(sel.entity->token.string), LIT(assignment_str));
 				}
 			} else {
-				error(fv->field, "Duplicate field '%.*s' in structure literal", LIT(field->token.string));
+				error(fv->field, "Duplicate field '%.*s' in %.*s", LIT(field->token.string), LIT(assignment_str));
 			}
 			continue;
 		} else if (String *found = string_map_get(&fields_visited_through_raw_union, sel.entity->token.string)) {
@@ -8433,11 +8515,13 @@ gb_internal void check_compound_literal_field_values(CheckerContext *c, Slice<As
 			continue;
 		}
 		if (sel.indirect) {
-			error(fv->field, "Cannot assign to the %d-nested anonymous indirect field '%.*s' in a structure literal", cast(int)sel.index.count-1, LIT(name));
+			error(fv->field, "Cannot assign to the %d-nested anonymous indirect field '%.*s' in a %.*s", cast(int)sel.index.count-1, LIT(name), LIT(assignment_str));
 			continue;
 		}
 
 		if (sel.index.count > 1) {
+			GB_ASSERT(bt->kind == Type_Struct);
+
 			if (is_constant) {
 				Type *ft = type;
 				for (i32 index : sel.index) {
@@ -8498,7 +8582,15 @@ gb_internal void check_compound_literal_field_values(CheckerContext *c, Slice<As
 			is_constant = check_is_operand_compound_lit_constant(c, &o);
 		}
 
-		check_assignment(c, &o, field->type, str_lit("structure literal"));
+		u8 prev_bit_field_bit_size = c->bit_field_bit_size;
+		if (field->kind == Entity_Variable && field->Variable.bit_field_bit_size) {
+			// HACK NOTE(bill): This is a bit of a hack, but it will work fine for this use case
+			c->bit_field_bit_size = field->Variable.bit_field_bit_size;
+		}
+
+		check_assignment(c, &o, field->type, assignment_str);
+
+		c->bit_field_bit_size = prev_bit_field_bit_size;
 	}
 }
 
@@ -9300,6 +9392,21 @@ gb_internal ExprKind check_compound_literal(CheckerContext *c, Operand *o, Ast *
 		}
 		break;
 	}
+	case Type_BitField: {
+		if (cl->elems.count == 0) {
+			break; // NOTE(bill): No need to init
+		}
+		is_constant = false;
+		if (cl->elems[0]->kind != Ast_FieldValue) {
+			gbString type_str = type_to_string(type);
+			error(node, "%s ('bit_field') compound literals are only allowed to contain 'field = value' elements", type_str);
+			gb_string_free(type_str);
+		} else {
+			check_compound_literal_field_values(c, cl->elems, o, type, is_constant);
+		}
+		break;
+	}
+
 
 	default: {
 		if (cl->elems.count == 0) {
@@ -11113,6 +11220,32 @@ gb_internal gbString write_expr_to_string(gbString str, Ast *node, bool shorthan
 	case_end;
 
 
+	case_ast_node(f, BitFieldField, node);
+		str = write_expr_to_string(str, f->name, shorthand);
+		str = gb_string_appendc(str, ": ");
+		str = write_expr_to_string(str, f->type, shorthand);
+		str = gb_string_appendc(str, " | ");
+		str = write_expr_to_string(str, f->bit_size, shorthand);
+	case_end;
+	case_ast_node(bf, BitFieldType, node);
+		str = gb_string_appendc(str, "bit_field ");
+		if (!shorthand) {
+			str = write_expr_to_string(str, bf->backing_type, shorthand);
+		}
+		str = gb_string_appendc(str, " {");
+		if (shorthand) {
+			str = gb_string_appendc(str, "...");
+		} else {
+			for_array(i, bf->fields) {
+				if (i > 0) {
+					str = gb_string_appendc(str, ", ");
+				}
+				str = write_expr_to_string(str, bf->fields[i], false);
+			}
+		}
+		str = gb_string_appendc(str, "}");
+	case_end;
+
 	case_ast_node(ia, InlineAsmExpr, node);
 		str = gb_string_appendc(str, "asm(");
 		for_array(i, ia->param_types) {

+ 10 - 0
src/check_stmt.cpp

@@ -485,7 +485,17 @@ gb_internal Type *check_assignment_variable(CheckerContext *ctx, Operand *lhs, O
 	}
 	}
 
+	Entity *lhs_e = entity_of_node(lhs->expr);
+	u8 prev_bit_field_bit_size = ctx->bit_field_bit_size;
+	if (lhs_e && lhs_e->kind == Entity_Variable && lhs_e->Variable.bit_field_bit_size) {
+		// HACK NOTE(bill): This is a bit of a hack, but it will work fine for this use case
+		ctx->bit_field_bit_size = lhs_e->Variable.bit_field_bit_size;
+	}
+
 	check_assignment(ctx, rhs, assignment_type, str_lit("assignment"));
+
+	ctx->bit_field_bit_size = prev_bit_field_bit_size;
+
 	if (rhs->mode == Addressing_Invalid) {
 		return nullptr;
 	}

+ 212 - 0
src/check_type.cpp

@@ -89,6 +89,8 @@ gb_internal bool does_field_type_allow_using(Type *t) {
 		return true;
 	} else if (is_type_array(t)) {
 		return t->Array.count <= 4;
+	} else if (is_type_bit_field(t)) {
+		return true;
 	}
 	return false;
 }
@@ -925,6 +927,202 @@ gb_internal void check_enum_type(CheckerContext *ctx, Type *enum_type, Type *nam
 	enum_type->Enum.max_value_index = max_value_index;
 }
 
+gb_internal bool is_valid_bit_field_backing_type(Type *type) {
+	if (type == nullptr) {
+		return false;
+	}
+	type = base_type(type);
+	if (is_type_untyped(type)) {
+		return false;
+	}
+	if (is_type_integer(type)) {
+		return true;
+	}
+	if (type->kind == Type_Array) {
+		return is_type_integer(type->Array.elem);
+	}
+	return false;
+}
+
+gb_internal void check_bit_field_type(CheckerContext *ctx, Type *bit_field_type, Type *named_type, Ast *node) {
+	ast_node(bf, BitFieldType, node);
+	GB_ASSERT(is_type_bit_field(bit_field_type));
+
+	Type *backing_type = check_type(ctx, bf->backing_type);
+	if (backing_type == nullptr || !is_valid_bit_field_backing_type(backing_type)) {
+		error(node, "Backing type for a bit_field must be an integer or an array of an integer");
+		return;
+	}
+
+	bit_field_type->BitField.backing_type = backing_type;
+	bit_field_type->BitField.scope = ctx->scope;
+
+	auto fields    = array_make<Entity *>(permanent_allocator(), 0, bf->fields.count);
+	auto bit_sizes = array_make<u8>      (permanent_allocator(), 0, bf->fields.count);
+	auto tags      = array_make<String>  (permanent_allocator(), 0, bf->fields.count);
+
+	u64 maximum_bit_size = 8 * type_size_of(backing_type);
+	u64 total_bit_size = 0;
+
+	for_array(i, bf->fields) {
+		i32 field_src_index = cast(i32)i;
+		Ast *field = bf->fields[i];
+		if (field->kind != Ast_BitFieldField) {
+			error(field, "Invalid AST for a bit_field");
+			continue;
+		}
+		ast_node(f, BitFieldField, field);
+		if (f->name == nullptr || f->name->kind != Ast_Ident) {
+			error(field, "A bit_field's field name must be an identifier");
+			continue;
+		}
+		CommentGroup *docs    = f->docs;
+		CommentGroup *comment = f->comment;
+
+		String name = f->name->Ident.token.string;
+
+		if (f->type == nullptr) {
+			error(field, "A bit_field's field must have a type");
+			continue;
+		}
+
+		Type *type = check_type(ctx, f->type);
+		if (type_size_of(type) > 8) {
+			error(f->type, "The type of a bit_field's field must be <= 8 bytes, got %lld", cast(long long)type_size_of(type));
+		}
+
+		if (is_type_untyped(type)) {
+			gbString s = type_to_string(type);
+			error(f->type, "The type of a bit_field's field must be a typed integer, enum, or boolean, got %s", s);
+			gb_string_free(s);
+		} else if (!(is_type_integer(type) || is_type_enum(type) || is_type_boolean(type))) {
+			gbString s = type_to_string(type);
+			error(f->type, "The type of a bit_field's field must be an integer, enum, or boolean, got %s", s);
+			gb_string_free(s);
+		}
+
+		if (f->bit_size == nullptr) {
+			error(field, "A bit_field's field must have a specified bit size");
+			continue;
+		}
+
+
+		Operand o = {};
+		check_expr(ctx, &o, f->bit_size);
+		if (o.mode != Addressing_Constant) {
+			error(f->bit_size, "A bit_field's specified bit size must be a constant");
+			o.mode = Addressing_Invalid;
+		}
+		if (o.value.kind == ExactValue_Float) {
+			o.value = exact_value_to_integer(o.value);
+		}
+		if (f->bit_size->kind == Ast_BinaryExpr && f->bit_size->BinaryExpr.op.kind == Token_Or) {
+			gbString s = expr_to_string(f->bit_size);
+			error(f->bit_size, "Wrap the expression in parentheses, e.g. (%s)", s);
+			gb_string_free(s);
+		}
+
+		ExactValue bit_size = o.value;
+
+		if (bit_size.kind != ExactValue_Integer) {
+			gbString s = expr_to_string(f->bit_size);
+			error(f->bit_size, "Expected an integer constant value for the specified bit size, got %s", s);
+			gb_string_free(s);
+		}
+
+		if (scope_lookup_current(ctx->scope, name) != nullptr) {
+			error(f->name, "'%.*s' is already declared in this bit_field", LIT(name));
+		} else {
+			i64 bit_size_i64 = exact_value_to_i64(bit_size);
+			u8 bit_size_u8 = 0;
+			if (bit_size_i64 <= 0) {
+				error(f->bit_size, "A bit_field's specified bit size cannot be <= 0, got %lld", cast(long long)bit_size_i64);
+				bit_size_i64 = 1;
+			}
+			if (bit_size_i64 > 64) {
+				error(f->bit_size, "A bit_field's specified bit size cannot exceed 64 bits, got %lld", cast(long long)bit_size_i64);
+				bit_size_i64 = 64;
+			}
+			i64 sz = 8*type_size_of(type);
+			if (bit_size_i64 > sz) {
+				error(f->bit_size, "A bit_field's specified bit size cannot exceed its type, got %lld, expect <=%lld", cast(long long)bit_size_i64, cast(long long)sz);
+				bit_size_i64 = sz;
+			}
+
+			bit_size_u8 = cast(u8)bit_size_i64;
+
+			Entity *e = alloc_entity_field(ctx->scope, f->name->Ident.token, type, false, field_src_index);
+			e->Variable.docs    = docs;
+			e->Variable.comment = comment;
+			e->Variable.bit_field_bit_size = bit_size_u8;
+			e->flags |= EntityFlag_BitFieldField;
+
+			add_entity(ctx, ctx->scope, nullptr, e);
+			array_add(&fields, e);
+			array_add(&bit_sizes, bit_size_u8);
+
+			String tag = f->tag.string;
+			if (tag.len != 0 && !unquote_string(permanent_allocator(), &tag, 0, tag.text[0] == '`')) {
+				error(f->tag, "Invalid string literal");
+				tag = {};
+			}
+			array_add(&tags, tag);
+
+			add_entity_use(ctx, field, e);
+		}
+	}
+
+	GB_ASSERT(fields.count <= bf->fields.count);
+
+	auto bit_offsets = slice_make<i64>(permanent_allocator(), fields.count);
+	i64 curr_offset = 0;
+	for_array(i, bit_sizes) {
+		bit_offsets[i] = curr_offset;
+		curr_offset += cast(i64)bit_sizes[i];
+	}
+
+	if (total_bit_size > maximum_bit_size) {
+		gbString s = type_to_string(backing_type);
+		error(node, "The numbers required %llu exceeds the backing type's (%s) bit size %llu",
+		      cast(unsigned long long)total_bit_size,
+		      s,
+		      cast(unsigned long long)maximum_bit_size);
+		gb_string_free(s);
+	}
+
+	if (bit_sizes.count > 0 && is_type_integer(backing_type)) {
+		bool all_booleans = is_type_boolean(fields[0]->type);
+		bool all_ones = bit_sizes[0] == 1;
+		if (all_ones && all_booleans) {
+			for_array(i, bit_sizes) {
+				all_ones = bit_sizes[i] == 1;
+				if (!all_ones) {
+					break;
+				}
+				all_booleans = is_type_boolean(fields[i]->type);
+				if (!all_booleans) {
+					break;
+				}
+			}
+			if (all_ones && all_booleans) {
+				if (build_context.vet_flags & VetFlag_Style) {
+					char const *msg = "This 'bit_field' is better expressed as a 'bit_set' since all of the fields are booleans, of 1-bit in size, and the backing type is an integer (-vet-style)";
+					error(node, msg);
+				} else {
+					char const *msg = "This 'bit_field' might be better expressed as a 'bit_set' since all of the fields are booleans, of 1-bit in size, and the backing type is an integer";
+					warning(node, msg);
+				}
+			}
+		}
+	}
+
+
+	bit_field_type->BitField.fields      = slice_from_array(fields);
+	bit_field_type->BitField.bit_sizes   = slice_from_array(bit_sizes);
+	bit_field_type->BitField.bit_offsets = bit_offsets;
+	bit_field_type->BitField.tags        = tags.data;
+}
+
 gb_internal bool is_type_valid_bit_set_range(Type *t) {
 	if (is_type_integer(t)) {
 		return true;
@@ -3051,6 +3249,20 @@ gb_internal bool check_type_internal(CheckerContext *ctx, Ast *e, Type **type, T
 		return true;
 	case_end;
 
+	case_ast_node(bf, BitFieldType, e);
+		bool ips = ctx->in_polymorphic_specialization;
+		defer (ctx->in_polymorphic_specialization = ips);
+		ctx->in_polymorphic_specialization = false;
+
+		*type = alloc_type_bit_field();
+		set_base_type(named_type, *type);
+		check_open_scope(ctx, e);
+		check_bit_field_type(ctx, *type, named_type, e);
+		check_close_scope(ctx);
+		(*type)->BitField.node = e;
+		return true;
+	case_end;
+
 
 	case_ast_node(pt, ProcType, e);
 		bool ips = ctx->in_polymorphic_specialization;

+ 18 - 0
src/checker.cpp

@@ -313,6 +313,7 @@ gb_internal void add_scope(CheckerContext *c, Ast *node, Scope *scope) {
 	case Ast_StructType:      node->StructType.scope      = scope; break;
 	case Ast_UnionType:       node->UnionType.scope       = scope; break;
 	case Ast_EnumType:        node->EnumType.scope        = scope; break;
+	case Ast_BitFieldType:    node->BitFieldType.scope    = scope; break;
 	default: GB_PANIC("Invalid node for add_scope: %.*s", LIT(ast_strings[node->kind]));
 	}
 }
@@ -334,6 +335,7 @@ gb_internal Scope *scope_of_node(Ast *node) {
 	case Ast_StructType:      return node->StructType.scope;
 	case Ast_UnionType:       return node->UnionType.scope;
 	case Ast_EnumType:        return node->EnumType.scope;
+	case Ast_BitFieldType:    return node->BitFieldType.scope;
 	}
 	GB_PANIC("Invalid node for add_scope: %.*s", LIT(ast_strings[node->kind]));
 	return nullptr;
@@ -355,6 +357,7 @@ gb_internal void check_open_scope(CheckerContext *c, Ast *node) {
 	case Ast_EnumType:
 	case Ast_UnionType:
 	case Ast_BitSetType:
+	case Ast_BitFieldType:
 		scope->flags |= ScopeFlag_Type;
 		break;
 	}
@@ -2061,6 +2064,12 @@ gb_internal void add_type_info_type_internal(CheckerContext *c, Type *t) {
 		add_type_info_type_internal(c, bt->SoaPointer.elem);
 		break;
 
+	case Type_BitField:
+		add_type_info_type_internal(c, bt->BitField.backing_type);
+		for (Entity *f : bt->BitField.fields) {
+			add_type_info_type_internal(c, f->type);
+		}
+		break;
 
 	case Type_Generic:
 		break;
@@ -2310,6 +2319,13 @@ gb_internal void add_min_dep_type_info(Checker *c, Type *t) {
 		add_min_dep_type_info(c, bt->SoaPointer.elem);
 		break;
 
+	case Type_BitField:
+		add_min_dep_type_info(c, bt->BitField.backing_type);
+		for (Entity *f : bt->BitField.fields) {
+			add_min_dep_type_info(c, f->type);
+		}
+		break;
+
 	default:
 		GB_PANIC("Unhandled type: %*.s", LIT(type_strings[bt->kind]));
 		break;
@@ -2908,6 +2924,7 @@ gb_internal void init_core_type_info(Checker *c) {
 	t_type_info_relative_multi_pointer = find_core_type(c, str_lit("Type_Info_Relative_Multi_Pointer"));
 	t_type_info_matrix           = find_core_type(c, str_lit("Type_Info_Matrix"));
 	t_type_info_soa_pointer      = find_core_type(c, str_lit("Type_Info_Soa_Pointer"));
+	t_type_info_bit_field        = find_core_type(c, str_lit("Type_Info_Bit_Field"));
 
 	t_type_info_named_ptr            = alloc_type_pointer(t_type_info_named);
 	t_type_info_integer_ptr          = alloc_type_pointer(t_type_info_integer);
@@ -2937,6 +2954,7 @@ gb_internal void init_core_type_info(Checker *c) {
 	t_type_info_relative_multi_pointer_ptr = alloc_type_pointer(t_type_info_relative_multi_pointer);
 	t_type_info_matrix_ptr           = alloc_type_pointer(t_type_info_matrix);
 	t_type_info_soa_pointer_ptr      = alloc_type_pointer(t_type_info_soa_pointer);
+	t_type_info_bit_field_ptr        = alloc_type_pointer(t_type_info_bit_field);
 }
 
 gb_internal void init_mem_allocator(Checker *c) {

+ 1 - 0
src/checker.hpp

@@ -475,6 +475,7 @@ struct CheckerContext {
 	bool       hide_polymorphic_errors;
 	bool       in_polymorphic_specialization;
 	bool       allow_arrow_right_selector_expr;
+	u8         bit_field_bit_size;
 	Scope *    polymorphic_scope;
 
 	Ast *assignment_lhs_hint;

+ 4 - 0
src/checker_builtin_procs.hpp

@@ -282,6 +282,8 @@ BuiltinProc__type_simple_boolean_end,
 
 	BuiltinProc_type_field_index_of,
 
+	BuiltinProc_type_bit_set_backing_type,
+
 	BuiltinProc_type_equal_proc,
 	BuiltinProc_type_hasher_proc,
 	BuiltinProc_type_map_info,
@@ -586,6 +588,8 @@ gb_global BuiltinProc builtin_procs[BuiltinProc_COUNT] = {
 
 	{STR_LIT("type_field_index_of"), 2, false, Expr_Expr, BuiltinProcPkg_intrinsics},
 
+	{STR_LIT("type_bit_set_backing_type"), 1, false, Expr_Expr, BuiltinProcPkg_intrinsics},
+
 	{STR_LIT("type_equal_proc"),    1, false, Expr_Expr, BuiltinProcPkg_intrinsics},
 	{STR_LIT("type_hasher_proc"),   1, false, Expr_Expr, BuiltinProcPkg_intrinsics},
 	{STR_LIT("type_map_info"),      1, false, Expr_Expr, BuiltinProcPkg_intrinsics},

+ 6 - 3
src/docs_format.cpp

@@ -14,8 +14,8 @@ struct OdinDocVersionType {
 };
 
 #define OdinDocVersionType_Major 0
-#define OdinDocVersionType_Minor 2
-#define OdinDocVersionType_Patch 4
+#define OdinDocVersionType_Minor 3
+#define OdinDocVersionType_Patch 0
 
 struct OdinDocHeaderBase {
 	u8                 magic[8];
@@ -84,6 +84,7 @@ enum OdinDocTypeKind : u32 {
 	OdinDocType_MultiPointer         = 22,
 	OdinDocType_Matrix               = 23,
 	OdinDocType_SoaPointer           = 24,
+	OdinDocType_BitField             = 25,
 };
 
 enum OdinDocTypeFlag_Basic : u32 {
@@ -170,6 +171,8 @@ enum OdinDocEntityFlag : u64 {
 	OdinDocEntityFlag_Param_NoAlias  = 1ull<<7,
 	OdinDocEntityFlag_Param_AnyInt   = 1ull<<8,
 
+	OdinDocEntityFlag_BitField_Field = 1ull<<19,
+
 	OdinDocEntityFlag_Type_Alias = 1ull<<20,
 
 	OdinDocEntityFlag_Builtin_Pkg_Builtin    = 1ull<<30,
@@ -192,7 +195,7 @@ struct OdinDocEntity {
 	u32                reserved_for_init;
 	OdinDocString      comment; // line comment
 	OdinDocString      docs; // preceding comment
-	i32                field_group_index;
+	i32                field_group_index; // For `bit_field`s this is the "bit_size"
 	OdinDocEntityIndex foreign_library;
 	OdinDocString      link_name;
 	OdinDocArray<OdinDocAttribute> attributes;

+ 24 - 1
src/docs_writer.cpp

@@ -615,6 +615,20 @@ gb_internal OdinDocTypeIndex odin_doc_type(OdinDocWriter *w, Type *type) {
 			doc_type.types = odin_write_slice(w, types, gb_count_of(types));
 		}
 		break;
+	case Type_BitField:
+		doc_type.kind = OdinDocType_BitField;
+		{
+			auto fields = array_make<OdinDocEntityIndex>(heap_allocator(), type->BitField.fields.count);
+			defer (array_free(&fields));
+
+			for_array(i, type->BitField.fields) {
+				fields[i] = odin_doc_add_entity(w, type->BitField.fields[i]);
+			}
+			doc_type.entities = odin_write_slice(w, fields.data, fields.count);
+			doc_type.types = odin_doc_type_as_slice(w, type->BitField.backing_type);
+		}
+		break;
+
 	case Type_Struct:
 		doc_type.kind = OdinDocType_Struct;
 		if (type->Struct.soa_kind != StructSoa_None) {
@@ -863,6 +877,10 @@ gb_internal OdinDocEntityIndex odin_doc_add_entity(OdinDocWriter *w, Entity *e)
 		}
 		break;
 	case Entity_Variable:
+		if (e->flags & EntityFlag_BitFieldField) {
+			flags |= OdinDocEntityFlag_BitField_Field;
+		}
+
 		if (e->Variable.is_foreign) { flags |= OdinDocEntityFlag_Foreign; }
 		if (e->Variable.is_export)  { flags |= OdinDocEntityFlag_Export;  }
 		if (e->Variable.thread_local_model != "") {
@@ -873,7 +891,12 @@ gb_internal OdinDocEntityIndex odin_doc_add_entity(OdinDocWriter *w, Entity *e)
 		if (init_expr == nullptr) {
 			init_expr = e->Variable.init_expr;
 		}
-		field_group_index = e->Variable.field_group_index;
+
+		if (e->flags & EntityFlag_BitFieldField) {
+			field_group_index = -cast(i32)e->Variable.bit_field_bit_size;
+		} else {
+			field_group_index = e->Variable.field_group_index;
+		}
 		break;
 	case Entity_Constant:
 		field_group_index = e->Constant.field_group_index;

+ 2 - 0
src/entity.cpp

@@ -43,6 +43,7 @@ enum EntityFlag : u64 {
 	EntityFlag_NoAlias       = 1ull<<9,
 	EntityFlag_TypeField     = 1ull<<10,
 	EntityFlag_Value         = 1ull<<11,
+	EntityFlag_BitFieldField = 1ull<<12,
 
 
 
@@ -212,6 +213,7 @@ struct Entity {
 			Ast *init_expr; // only used for some variables within procedure bodies
 			i32  field_index;
 			i32  field_group_index;
+			u8   bit_field_bit_size;
 
 			ParameterValue param_value;
 

+ 7 - 1
src/llvm_backend.cpp

@@ -2719,6 +2719,7 @@ gb_internal bool lb_generate_code(lbGenerator *gen) {
 		{ // Type info member buffer
 			// NOTE(bill): Removes need for heap allocation by making it global memory
 			isize count = 0;
+			isize offsets_extra = 0;
 
 			for (Type *t : m->info->type_info_types) {
 				isize index = lb_type_info_index(m->info, t, false);
@@ -2736,6 +2737,11 @@ gb_internal bool lb_generate_code(lbGenerator *gen) {
 				case Type_Tuple:
 					count += t->Tuple.variables.count;
 					break;
+				case Type_BitField:
+					count += t->BitField.fields.count;
+					// Twice is needed for the bit_offsets
+					offsets_extra += t->BitField.fields.count;
+					break;
 				}
 			}
 
@@ -2752,7 +2758,7 @@ gb_internal bool lb_generate_code(lbGenerator *gen) {
 
 			lb_global_type_info_member_types   = global_type_info_make(m, LB_TYPE_INFO_TYPES_NAME,   t_type_info_ptr, count);
 			lb_global_type_info_member_names   = global_type_info_make(m, LB_TYPE_INFO_NAMES_NAME,   t_string,        count);
-			lb_global_type_info_member_offsets = global_type_info_make(m, LB_TYPE_INFO_OFFSETS_NAME, t_uintptr,       count);
+			lb_global_type_info_member_offsets = global_type_info_make(m, LB_TYPE_INFO_OFFSETS_NAME, t_uintptr,       count+offsets_extra);
 			lb_global_type_info_member_usings  = global_type_info_make(m, LB_TYPE_INFO_USINGS_NAME,  t_bool,          count);
 			lb_global_type_info_member_tags    = global_type_info_make(m, LB_TYPE_INFO_TAGS_NAME,    t_string,        count);
 		}

+ 8 - 0
src/llvm_backend.hpp

@@ -84,6 +84,8 @@ enum lbAddrKind {
 
 	lbAddr_Swizzle,
 	lbAddr_SwizzleLarge,
+
+	lbAddr_BitField,
 };
 
 struct lbAddr {
@@ -118,6 +120,12 @@ struct lbAddr {
 			Type *type;
 			Slice<i32> indices;
 		} swizzle_large;
+		struct {
+			Type *type;
+			i64 index;
+			i64 bit_offset;
+			i64 bit_size;
+		} bitfield;
 	};
 };
 

+ 36 - 0
src/llvm_backend_debug.cpp

@@ -461,6 +461,42 @@ gb_internal LLVMMetadataRef lb_debug_type_internal(lbModule *m, Type *type) {
 			lb_debug_type(m, type->Matrix.elem),
 			subscripts, gb_count_of(subscripts));
 	}
+
+	case Type_BitField: {
+		LLVMMetadataRef parent_scope = nullptr;
+		LLVMMetadataRef scope = nullptr;
+		LLVMMetadataRef file = nullptr;
+		unsigned line = 0;
+		u64 size_in_bits = 8*cast(u64)type_size_of(type);
+		u32 align_in_bits = 8*cast(u32)type_align_of(type);
+		LLVMDIFlags flags = LLVMDIFlagZero;
+
+		unsigned element_count = cast(unsigned)type->BitField.fields.count;
+		LLVMMetadataRef *elements = gb_alloc_array(permanent_allocator(), LLVMMetadataRef, element_count);
+
+		u64 offset_in_bits = 0;
+		for (unsigned i = 0; i < element_count; i++) {
+			Entity *f = type->BitField.fields[i];
+			u8 bit_size = type->BitField.bit_sizes[i];
+			GB_ASSERT(f->kind == Entity_Variable);
+			String name = f->token.string;
+			unsigned field_line = 0;
+			LLVMDIFlags field_flags = LLVMDIFlagZero;
+			elements[i] = LLVMDIBuilderCreateBitFieldMemberType(m->debug_builder, scope, cast(char const *)name.text, name.len, file, field_line,
+				bit_size, offset_in_bits, offset_in_bits,
+				field_flags, lb_debug_type(m, f->type)
+			);
+
+			offset_in_bits += bit_size;
+		}
+
+
+		return LLVMDIBuilderCreateStructType(m->debug_builder, parent_scope, "", 0, file, line,
+			size_in_bits, align_in_bits, flags,
+			nullptr, elements, element_count, 0, nullptr,
+			"", 0
+		);
+	}
 	}
 
 	GB_PANIC("Invalid type %s", type_to_string(type));

+ 74 - 0
src/llvm_backend_expr.cpp

@@ -1946,6 +1946,24 @@ gb_internal lbValue lb_emit_conv(lbProcedure *p, lbValue value, Type *t) {
 		}
 	}
 
+	// bit_field <-> backing type
+	if (is_type_bit_field(src)) {
+		if (are_types_identical(src->BitField.backing_type, dst)) {
+			lbValue res = {};
+			res.type = t;
+			res.value = value.value;
+			return res;
+		}
+	}
+	if (is_type_bit_field(dst)) {
+		if (are_types_identical(src, dst->BitField.backing_type)) {
+			lbValue res = {};
+			res.type = t;
+			res.value = value.value;
+			return res;
+		}
+	}
+
 
 	// Pointer <-> uintptr
 	if (is_type_pointer(src) && is_type_uintptr(dst)) {
@@ -4217,6 +4235,38 @@ gb_internal lbAddr lb_build_addr_compound_lit(lbProcedure *p, Ast *expr) {
 	switch (bt->kind) {
 	default: GB_PANIC("Unknown CompoundLit type: %s", type_to_string(type)); break;
 
+	case Type_BitField:
+		for (Ast *elem : cl->elems) {
+			ast_node(fv, FieldValue, elem);
+			String name = fv->field->Ident.token.string;
+			Selection sel = lookup_field(bt, name, false);
+			GB_ASSERT(sel.is_bit_field);
+			GB_ASSERT(!sel.indirect);
+			GB_ASSERT(sel.index.count == 1);
+			GB_ASSERT(sel.entity != nullptr);
+
+			i64 index = sel.index[0];
+			i64 bit_offset = 0;
+			i64 bit_size = -1;
+			for_array(i, bt->BitField.fields) {
+				Entity *f = bt->BitField.fields[i];
+				if (f == sel.entity) {
+					bit_offset = bt->BitField.bit_offsets[i];
+					bit_size   = bt->BitField.bit_sizes[i];
+					break;
+				}
+			}
+			GB_ASSERT(bit_size > 0);
+
+			Type *field_type = sel.entity->type;
+			lbValue field_expr = lb_build_expr(p, fv->value);
+			field_expr = lb_emit_conv(p, field_expr, field_type);
+
+			lbAddr field_addr = lb_addr_bit_field(v.addr, field_type, index, bit_offset, bit_size);
+			lb_addr_store(p, field_addr, field_expr);
+		}
+		return v;
+
 	case Type_Struct: {
 		// TODO(bill): "constant" '#raw_union's are not initialized constantly at the moment.
 		// NOTE(bill): This is due to the layout of the unions when printed to LLVM-IR
@@ -4634,6 +4684,30 @@ gb_internal lbAddr lb_build_addr_internal(lbProcedure *p, Ast *expr) {
 				return lb_addr(lb_find_value_from_entity(p->module, e));
 			}
 
+			if (sel.is_bit_field) {
+				lbAddr addr = lb_build_addr(p, se->expr);
+
+				Selection sub_sel = sel;
+				sub_sel.index.count -= 1;
+
+				lbValue ptr = lb_addr_get_ptr(p, addr);
+				if (sub_sel.index.count > 0) {
+					ptr = lb_emit_deep_field_gep(p, ptr, sub_sel);
+				}
+
+				Type *bf_type = type_deref(ptr.type);
+				bf_type = base_type(type_deref(bf_type));
+				GB_ASSERT(bf_type->kind == Type_BitField);
+
+				i32 index = sel.index[sel.index.count-1];
+
+				Entity *f = bf_type->BitField.fields[index];
+				u8 bit_size = bf_type->BitField.bit_sizes[index];
+				i64 bit_offset = bf_type->BitField.bit_offsets[index];
+
+				return lb_addr_bit_field(ptr, f->type, index, bit_offset, bit_size);
+			}
+
 			{
 				lbAddr addr = lb_build_addr(p, se->expr);
 				if (addr.kind == lbAddr_Map) {

+ 52 - 3
src/llvm_backend_general.cpp

@@ -451,6 +451,20 @@ gb_internal lbAddr lb_addr_swizzle_large(lbValue addr, Type *array_type, Slice<i
 	return v;
 }
 
+gb_internal lbAddr lb_addr_bit_field(lbValue addr, Type *type, i64 index, i64 bit_offset, i64 bit_size) {
+	GB_ASSERT(is_type_pointer(addr.type));
+	Type *mt = type_deref(addr.type);
+	GB_ASSERT(is_type_bit_field(mt));
+
+	lbAddr v = {lbAddr_BitField, addr};
+	v.bitfield.type       = type;
+	v.bitfield.index      = index;
+	v.bitfield.bit_offset = bit_offset;
+	v.bitfield.bit_size   = bit_size;
+	return v;
+}
+
+
 gb_internal Type *lb_addr_type(lbAddr const &addr) {
 	if (addr.addr.value == nullptr) {
 		return nullptr;
@@ -759,7 +773,17 @@ gb_internal void lb_addr_store(lbProcedure *p, lbAddr addr, lbValue value) {
 		addr = lb_addr(lb_address_from_load(p, lb_addr_load(p, addr)));
 	}
 
-	if (addr.kind == lbAddr_RelativePointer) {
+	if (addr.kind == lbAddr_BitField) {
+		lbValue dst = addr.addr;
+
+		auto args = array_make<lbValue>(temporary_allocator(), 4);
+		args[0] = dst;
+		args[1] = lb_address_from_load_or_generate_local(p, value);
+		args[2] = lb_const_int(p->module, t_uintptr, addr.bitfield.bit_offset);
+		args[3] = lb_const_int(p->module, t_uintptr, addr.bitfield.bit_size);
+		lb_emit_runtime_call(p, "__write_bits", args);
+		return;
+	} else if (addr.kind == lbAddr_RelativePointer) {
 		Type *rel_ptr = base_type(lb_addr_type(addr));
 		GB_ASSERT(rel_ptr->kind == Type_RelativePointer ||
 		          rel_ptr->kind == Type_RelativeMultiPointer);
@@ -1074,8 +1098,31 @@ gb_internal lbValue lb_emit_load(lbProcedure *p, lbValue value) {
 gb_internal lbValue lb_addr_load(lbProcedure *p, lbAddr const &addr) {
 	GB_ASSERT(addr.addr.value != nullptr);
 
+	if (addr.kind == lbAddr_BitField) {
+		lbAddr dst = lb_add_local_generated(p, addr.bitfield.type, true);
+		lbValue src = addr.addr;
 
-	if (addr.kind == lbAddr_RelativePointer) {
+		auto args = array_make<lbValue>(temporary_allocator(), 4);
+		args[0] = dst.addr;
+		args[1] = src;
+		args[2] = lb_const_int(p->module, t_uintptr, addr.bitfield.bit_offset);
+		args[3] = lb_const_int(p->module, t_uintptr, addr.bitfield.bit_size);
+		lb_emit_runtime_call(p, "__read_bits", args);
+
+		lbValue r = lb_addr_load(p, dst);
+
+		if (!is_type_unsigned(core_type(addr.bitfield.type))) {
+			// Sign extension
+			// m := 1<<(bit_size-1)
+			// r = (r XOR m) - m
+			Type *t = addr.bitfield.type;
+			lbValue m = lb_const_int(p->module, t, 1ull<<(addr.bitfield.bit_size-1));
+			r = lb_emit_arith(p, Token_Xor, r, m, t);
+			r = lb_emit_arith(p, Token_Sub, r, m, t);
+		}
+
+		return r;
+	} else if (addr.kind == lbAddr_RelativePointer) {
 		Type *rel_ptr = base_type(lb_addr_type(addr));
 		Type *base_integer = nullptr;
 		Type *pointer_type = nullptr;
@@ -2216,7 +2263,9 @@ gb_internal LLVMTypeRef lb_type_internal(lbModule *m, Type *type) {
 			}
 			return LLVMStructTypeInContext(ctx, fields, field_count, false);
 		}
-	
+
+	case Type_BitField:
+		return lb_type_internal(m, type->BitField.backing_type);
 	}
 
 	GB_PANIC("Invalid type %s", type_to_string(type));

+ 17 - 4
src/llvm_backend_stmt.cpp

@@ -1843,7 +1843,11 @@ gb_internal void lb_build_return_stmt_internal(lbProcedure *p, lbValue res) {
 
 		lb_emit_defer_stmts(p, lbDeferExit_Return, nullptr);
 
-		LLVMBuildRetVoid(p->builder);
+		// Check for terminator in the defer stmts
+		LLVMValueRef instr = LLVMGetLastInstruction(p->curr_block->block);
+		if (!lb_is_instr_terminating(instr)) {
+			LLVMBuildRetVoid(p->builder);
+		}
 	} else {
 		LLVMValueRef ret_val = res.value;
 		LLVMTypeRef ret_type = p->abi_function_type->ret.type;
@@ -1868,7 +1872,12 @@ gb_internal void lb_build_return_stmt_internal(lbProcedure *p, lbValue res) {
 		}
 
 		lb_emit_defer_stmts(p, lbDeferExit_Return, nullptr);
-		LLVMBuildRet(p->builder, ret_val);
+
+		// Check for terminator in the defer stmts
+		LLVMValueRef instr = LLVMGetLastInstruction(p->curr_block->block);
+		if (!lb_is_instr_terminating(instr)) {
+			LLVMBuildRet(p->builder, ret_val);
+		}
 	}
 }
 gb_internal void lb_build_return_stmt(lbProcedure *p, Slice<Ast *> const &return_results) {
@@ -1887,8 +1896,12 @@ gb_internal void lb_build_return_stmt(lbProcedure *p, Slice<Ast *> const &return
 		// No return values
 
 		lb_emit_defer_stmts(p, lbDeferExit_Return, nullptr);
-
-		LLVMBuildRetVoid(p->builder);
+		
+		// Check for terminator in the defer stmts
+		LLVMValueRef instr = LLVMGetLastInstruction(p->curr_block->block);
+		if (!lb_is_instr_terminating(instr)) {
+			LLVMBuildRetVoid(p->builder);
+		}
 		return;
 	} else if (return_count == 1) {
 		Entity *e = tuple->variables[0];

+ 71 - 0
src/llvm_backend_type.cpp

@@ -1788,6 +1788,77 @@ gb_internal void lb_setup_type_info_data(lbProcedure *p) { // NOTE(bill): Setup
 				lb_emit_store(p, tag, res);
 			}
 			break;
+
+		case Type_BitField:
+			{
+				tag = lb_const_ptr_cast(m, variant_ptr, t_type_info_bit_field_ptr);
+				LLVMValueRef vals[6] = {};
+
+				vals[0] = lb_type_info(m, t->BitField.backing_type).value;
+				isize count = t->BitField.fields.count;
+				if (count > 0) {
+					i64 names_offset       = 0;
+					i64 types_offset       = 0;
+					i64 bit_sizes_offset   = 0;
+					i64 bit_offsets_offset = 0;
+					i64 tags_offset        = 0;
+					lbValue memory_names       = lb_type_info_member_names_offset  (m, count, &names_offset);
+					lbValue memory_types       = lb_type_info_member_types_offset  (m, count, &types_offset);
+					lbValue memory_bit_sizes   = lb_type_info_member_offsets_offset(m, count, &bit_sizes_offset);
+					lbValue memory_bit_offsets = lb_type_info_member_offsets_offset(m, count, &bit_offsets_offset);
+					lbValue memory_tags        = lb_type_info_member_tags_offset   (m, count, &tags_offset);
+
+					u64 bit_offset = 0;
+					for (isize source_index = 0; source_index < count; source_index++) {
+						Entity *f = t->BitField.fields[source_index];
+						u64 bit_size = cast(u64)t->BitField.bit_sizes[source_index];
+
+						lbValue index = lb_const_int(m, t_int, source_index);
+						if (f->token.string.len > 0) {
+							lbValue name_ptr = lb_emit_ptr_offset(p, memory_names, index);
+							lb_emit_store(p, name_ptr, lb_const_string(m, f->token.string));
+						}
+						lbValue type_ptr       = lb_emit_ptr_offset(p, memory_types, index);
+						lbValue bit_size_ptr   = lb_emit_ptr_offset(p, memory_bit_sizes, index);
+						lbValue bit_offset_ptr = lb_emit_ptr_offset(p, memory_bit_offsets, index);
+
+						lb_emit_store(p, type_ptr,       lb_type_info(m, f->type));
+						lb_emit_store(p, bit_size_ptr,   lb_const_int(m, t_uintptr, bit_size));
+						lb_emit_store(p, bit_offset_ptr, lb_const_int(m, t_uintptr, bit_offset));
+
+						if (t->BitField.tags) {
+							String tag = t->BitField.tags[source_index];
+							if (tag.len > 0) {
+								lbValue tag_ptr = lb_emit_ptr_offset(p, memory_tags, index);
+								lb_emit_store(p, tag_ptr, lb_const_string(m, tag));
+							}
+						}
+
+						bit_offset += bit_size;
+					}
+
+					lbValue cv = lb_const_int(m, t_int, count);
+					vals[1] = llvm_const_slice(m, memory_names,       cv);
+					vals[2] = llvm_const_slice(m, memory_types,       cv);
+					vals[3] = llvm_const_slice(m, memory_bit_sizes,   cv);
+					vals[4] = llvm_const_slice(m, memory_bit_offsets, cv);
+					vals[5] = llvm_const_slice(m, memory_tags,        cv);
+				}
+
+				for (isize i = 0; i < gb_count_of(vals); i++) {
+					if (vals[i] == nullptr) {
+						vals[i]  = LLVMConstNull(lb_type(m, get_struct_field_type(tag.type, i)));
+					}
+				}
+
+				lbValue res = {};
+				res.type = type_deref(tag.type);
+				res.value = llvm_const_named_struct(m, res.type, vals, gb_count_of(vals));
+				lb_emit_store(p, tag, res);
+
+				break;
+			}
+
 		}
 
 

+ 1 - 1
src/llvm_backend_utility.cpp

@@ -1332,7 +1332,7 @@ gb_internal lbValue lb_emit_deep_field_gep(lbProcedure *p, lbValue e, Selection
 				if (index == 0) {
 					type = t_rawptr;
 				} else if (index == 1) {
-					type = t_type_info_ptr;
+					type = t_typeid;
 				}
 				e = lb_emit_struct_ep(p, e, index);
 				break;

+ 97 - 0
src/parser.cpp

@@ -350,6 +350,11 @@ gb_internal Ast *clone_ast(Ast *node, AstFile *f) {
 		n->Field.names = clone_ast_array(n->Field.names, f);
 		n->Field.type  = clone_ast(n->Field.type, f);
 		break;
+	case Ast_BitFieldField:
+		n->BitFieldField.name     = clone_ast(n->BitFieldField.name, f);
+		n->BitFieldField.type     = clone_ast(n->BitFieldField.type, f);
+		n->BitFieldField.bit_size = clone_ast(n->BitFieldField.bit_size, f);
+		break;
 	case Ast_FieldList:
 		n->FieldList.list = clone_ast_array(n->FieldList.list, f);
 		break;
@@ -406,6 +411,10 @@ gb_internal Ast *clone_ast(Ast *node, AstFile *f) {
 		n->BitSetType.elem       = clone_ast(n->BitSetType.elem, f);
 		n->BitSetType.underlying = clone_ast(n->BitSetType.underlying, f);
 		break;
+	case Ast_BitFieldType:
+		n->BitFieldType.backing_type = clone_ast(n->BitFieldType.backing_type, f);
+		n->BitFieldType.fields = clone_ast_array(n->BitFieldType.fields, f);
+		break;
 	case Ast_MapType:
 		n->MapType.count = clone_ast(n->MapType.count, f);
 		n->MapType.key   = clone_ast(n->MapType.key, f);
@@ -1045,6 +1054,18 @@ gb_internal Ast *ast_field(AstFile *f, Array<Ast *> const &names, Ast *type, Ast
 	return result;
 }
 
+gb_internal Ast *ast_bit_field_field(AstFile *f, Ast *name, Ast *type, Ast *bit_size, Token tag,
+                                     CommentGroup *docs, CommentGroup *comment) {
+	Ast *result = alloc_ast_node(f, Ast_BitFieldField);
+	result->BitFieldField.name     = name;
+	result->BitFieldField.type     = type;
+	result->BitFieldField.bit_size = bit_size;
+	result->BitFieldField.tag      = tag;
+	result->BitFieldField.docs     = docs;
+	result->BitFieldField.comment  = comment;
+	return result;
+}
+
 gb_internal Ast *ast_field_list(AstFile *f, Token token, Array<Ast *> const &list) {
 	Ast *result = alloc_ast_node(f, Ast_FieldList);
 	result->FieldList.token = token;
@@ -1178,6 +1199,17 @@ gb_internal Ast *ast_bit_set_type(AstFile *f, Token token, Ast *elem, Ast *under
 	return result;
 }
 
+gb_internal Ast *ast_bit_field_type(AstFile *f, Token token, Ast *backing_type, Token open, Array<Ast *> const &fields, Token close) {
+	Ast *result = alloc_ast_node(f, Ast_BitFieldType);
+	result->BitFieldType.token        = token;
+	result->BitFieldType.backing_type = backing_type;
+	result->BitFieldType.open         = open;
+	result->BitFieldType.fields       = slice_from_array(fields);
+	result->BitFieldType.close        = close;
+	return result;
+}
+
+
 gb_internal Ast *ast_map_type(AstFile *f, Token token, Ast *key, Ast *value) {
 	Ast *result = alloc_ast_node(f, Ast_MapType);
 	result->MapType.token = token;
@@ -2549,6 +2581,66 @@ gb_internal Ast *parse_operand(AstFile *f, bool lhs) {
 		return ast_matrix_type(f, token, row_count, column_count, type);
 	} break;
 
+	case Token_bit_field: {
+		Token token = expect_token(f, Token_bit_field);
+		isize prev_level;
+
+		prev_level = f->expr_level;
+		f->expr_level = -1;
+
+		Ast *backing_type = parse_type_or_ident(f);
+		if (backing_type == nullptr) {
+			Token token = advance_token(f);
+			syntax_error(token, "Expected a backing type for a 'bit_field'");
+			backing_type = ast_bad_expr(f, token, f->curr_token);
+		}
+
+		skip_possible_newline_for_literal(f);
+		Token open = expect_token_after(f, Token_OpenBrace, "bit_field");
+
+
+		auto fields = array_make<Ast *>(ast_allocator(f), 0, 0);
+
+		while (f->curr_token.kind != Token_CloseBrace &&
+		       f->curr_token.kind != Token_EOF) {
+			CommentGroup *docs = nullptr;
+			CommentGroup *comment = nullptr;
+
+			Ast *name = parse_ident(f);
+			bool err_once = false;
+			while (allow_token(f, Token_Comma)) {
+				Ast *dummy_name = parse_ident(f);
+				if (!err_once) {
+					error(dummy_name, "'bit_field' fields do not support multiple names per field");
+					err_once = true;
+				}
+			}
+			expect_token(f, Token_Colon);
+			Ast *type = parse_type(f);
+			expect_token(f, Token_Or);
+			Ast *bit_size = parse_expr(f, true);
+
+			Token tag = {};
+			if (f->curr_token.kind == Token_String) {
+				tag = expect_token(f, Token_String);
+			}
+
+			Ast *bf_field = ast_bit_field_field(f, name, type, bit_size, tag, docs, comment);
+			array_add(&fields, bf_field);
+
+			if (!allow_field_separator(f)) {
+				break;
+			}
+		}
+
+		Token close = expect_closing_brace_of_field_list(f);
+
+		f->expr_level = prev_level;
+
+		return ast_bit_field_type(f, token, backing_type, open, fields, close);
+	}
+
+
 	case Token_struct: {
 		Token    token = expect_token(f, Token_struct);
 		Ast *polymorphic_params = nullptr;
@@ -3923,6 +4015,10 @@ gb_internal Array<Ast *> convert_to_ident_list(AstFile *f, Array<AstAndFlags> li
 		case Ast_Ident:
 		case Ast_BadExpr:
 			break;
+		case Ast_Implicit:
+			syntax_error(ident, "Expected an identifier, '%.*s' which is a keyword", LIT(ident->Implicit.string));
+			ident = ast_ident(f, blank_token);
+			break;
 
 		case Ast_PolyType:
 			if (allow_poly_names) {
@@ -3936,6 +4032,7 @@ gb_internal Array<Ast *> convert_to_ident_list(AstFile *f, Array<AstAndFlags> li
 			}
 			/*fallthrough*/
 
+
 		default:
 			syntax_error(ident, "Expected an identifier");
 			ident = ast_ident(f, blank_token);

+ 17 - 0
src/parser.hpp

@@ -429,6 +429,7 @@ AST_KIND(_ExprBegin,  "",  bool) \
 		Ast *expr, *selector; \
 		u8 swizzle_count; /*maximum of 4 components, if set, count >= 2*/ \
 		u8 swizzle_indices; /*2 bits per component*/ \
+		bool is_bit_field; \
 	}) \
 	AST_KIND(ImplicitSelectorExpr, "implicit selector expression",    struct { Token token; Ast *selector; }) \
 	AST_KIND(SelectorCallExpr, "selector call expression", struct { \
@@ -650,6 +651,14 @@ AST_KIND(_DeclEnd,   "", bool) \
 		CommentGroup *   docs;      \
 		CommentGroup *   comment;   \
 	}) \
+	AST_KIND(BitFieldField, "bit field field", struct { \
+		Ast *         name;     \
+		Ast *         type;     \
+		Ast *         bit_size; \
+		Token         tag;      \
+		CommentGroup *docs;     \
+		CommentGroup *comment;  \
+	}) \
 	AST_KIND(FieldList, "field list", struct { \
 		Token token;       \
 		Slice<Ast *> list; \
@@ -742,6 +751,14 @@ AST_KIND(_TypeBegin, "", bool) \
 		Ast * elem;  \
 		Ast * underlying; \
 	}) \
+	AST_KIND(BitFieldType, "bit field type", struct { \
+		Scope *scope; \
+		Token token; \
+		Ast * backing_type;  \
+		Token open; \
+		Slice<Ast *> fields; /* BitFieldField */ \
+		Token close; \
+	}) \
 	AST_KIND(MapType, "map type", struct { \
 		Token token; \
 		Ast *count; \

+ 3 - 0
src/parser_pos.cpp

@@ -111,6 +111,7 @@ gb_internal Token ast_token(Ast *node) {
 	case Ast_UnionType:        return node->UnionType.token;
 	case Ast_EnumType:         return node->EnumType.token;
 	case Ast_BitSetType:       return node->BitSetType.token;
+	case Ast_BitFieldType:     return node->BitFieldType.token;
 	case Ast_MapType:          return node->MapType.token;
 	case Ast_MatrixType:       return node->MatrixType.token;
 	}
@@ -364,6 +365,8 @@ Token ast_end_token(Ast *node) {
 			return ast_end_token(node->BitSetType.underlying);
 		}
 		return ast_end_token(node->BitSetType.elem);
+	case Ast_BitFieldType:
+		return node->BitFieldType.close;
 	case Ast_MapType:          return ast_end_token(node->MapType.value);
 	case Ast_MatrixType:       return ast_end_token(node->MatrixType.elem);
 	}

+ 1 - 0
src/tokenizer.cpp

@@ -106,6 +106,7 @@ TOKEN_KIND(Token__KeywordBegin, ""), \
 	TOKEN_KIND(Token_union,       "union"),       \
 	TOKEN_KIND(Token_enum,        "enum"),        \
 	TOKEN_KIND(Token_bit_set,     "bit_set"),     \
+	TOKEN_KIND(Token_bit_field,   "bit_field"),   \
 	TOKEN_KIND(Token_map,         "map"),         \
 	TOKEN_KIND(Token_dynamic,     "dynamic"),     \
 	TOKEN_KIND(Token_auto_cast,   "auto_cast"),   \

+ 131 - 5
src/types.cpp

@@ -282,6 +282,15 @@ struct TypeProc {
 		Type *generic_column_count;                       \
 		i64   stride_in_bytes;                            \
 	})                                                        \
+	TYPE_KIND(BitField, struct {                              \
+		Scope *         scope;                            \
+		Type *          backing_type;                     \
+		Slice<Entity *> fields;                           \
+		String *        tags; /*count == fields.count*/   \
+		Slice<u8>       bit_sizes;                        \
+		Slice<i64>      bit_offsets;                      \
+		Ast *           node;                             \
+	})                                                        \
 	TYPE_KIND(SoaPointer, struct { Type *elem; })
 
 
@@ -355,6 +364,7 @@ enum Typeid_Kind : u8 {
 	Typeid_Relative_Multi_Pointer,
 	Typeid_Matrix,
 	Typeid_SoaPointer,
+	Typeid_Bit_Field,
 };
 
 // IMPORTANT NOTE(bill): This must match the same as the in core.odin
@@ -376,6 +386,9 @@ enum : int {
 
 gb_internal bool is_type_comparable(Type *t);
 gb_internal bool is_type_simple_compare(Type *t);
+gb_internal Type *type_deref(Type *t, bool allow_multi_pointer=false);
+gb_internal Type *base_type(Type *t);
+gb_internal Type *alloc_type_multi_pointer(Type *elem);
 
 gb_internal u32 type_info_flags_of_type(Type *type) {
 	if (type == nullptr) {
@@ -400,6 +413,7 @@ struct Selection {
 	bool       indirect; // Set if there was a pointer deref anywhere down the line
 	u8 swizzle_count;    // maximum components = 4
 	u8 swizzle_indices;  // 2 bits per component, representing which swizzle index
+	bool is_bit_field;
 	bool pseudo_field;
 };
 gb_global Selection const empty_selection = {0};
@@ -641,6 +655,7 @@ gb_global Type *t_type_info_relative_pointer     = nullptr;
 gb_global Type *t_type_info_relative_multi_pointer = nullptr;
 gb_global Type *t_type_info_matrix               = nullptr;
 gb_global Type *t_type_info_soa_pointer          = nullptr;
+gb_global Type *t_type_info_bit_field            = nullptr;
 
 gb_global Type *t_type_info_named_ptr            = nullptr;
 gb_global Type *t_type_info_integer_ptr          = nullptr;
@@ -670,6 +685,7 @@ gb_global Type *t_type_info_relative_pointer_ptr = nullptr;
 gb_global Type *t_type_info_relative_multi_pointer_ptr = nullptr;
 gb_global Type *t_type_info_matrix_ptr           = nullptr;
 gb_global Type *t_type_info_soa_pointer_ptr      = nullptr;
+gb_global Type *t_type_info_bit_field_ptr        = nullptr;
 
 gb_global Type *t_allocator                      = nullptr;
 gb_global Type *t_allocator_ptr                  = nullptr;
@@ -750,7 +766,6 @@ gb_internal bool  is_type_proc(Type *t);
 gb_internal bool  is_type_slice(Type *t);
 gb_internal bool  is_type_integer(Type *t);
 gb_internal bool  type_set_offsets(Type *t);
-gb_internal Type *base_type(Type *t);
 
 gb_internal i64 type_size_of_internal(Type *t, TypePath *path);
 gb_internal i64 type_align_of_internal(Type *t, TypePath *path);
@@ -1040,6 +1055,11 @@ gb_internal Type *alloc_type_enum() {
 	return t;
 }
 
+gb_internal Type *alloc_type_bit_field() {
+	Type *t = alloc_type(Type_BitField);
+	return t;
+}
+
 gb_internal Type *alloc_type_relative_pointer(Type *pointer_type, Type *base_integer) {
 	GB_ASSERT(is_type_pointer(pointer_type));
 	GB_ASSERT(is_type_integer(base_integer));
@@ -1140,7 +1160,7 @@ gb_internal Type *alloc_type_simd_vector(i64 count, Type *elem, Type *generic_co
 ////////////////////////////////////////////////////////////////
 
 
-gb_internal Type *type_deref(Type *t, bool allow_multi_pointer=false) {
+gb_internal Type *type_deref(Type *t, bool allow_multi_pointer) {
 	if (t != nullptr) {
 		Type *bt = base_type(t);
 		if (bt == nullptr) {
@@ -1707,6 +1727,10 @@ gb_internal bool is_type_bit_set(Type *t) {
 	t = base_type(t);
 	return (t->kind == Type_BitSet);
 }
+gb_internal bool is_type_bit_field(Type *t) {
+	t = base_type(t);
+	return (t->kind == Type_BitField);
+}
 gb_internal bool is_type_map(Type *t) {
 	t = base_type(t);
 	return t->kind == Type_Map;
@@ -3168,6 +3192,21 @@ gb_internal Selection lookup_field_with_selection(Type *type_, String field_name
 			else if (field_name == "a") mapped_field_name = str_lit("w");
 			return lookup_field_with_selection(type, mapped_field_name, is_type, sel, allow_blank_ident);
 		}
+	} else if (type->kind == Type_BitField) {
+		for_array(i, type->BitField.fields) {
+			Entity *f = type->BitField.fields[i];
+			if (f->kind != Entity_Variable || (f->flags & EntityFlag_Field) == 0) {
+				continue;
+			}
+			String str = f->token.string;
+			if (field_name == str) {
+				selection_add_index(&sel, i);  // HACK(bill): Leaky memory
+				sel.entity = f;
+				sel.is_bit_field = true;
+				return sel;
+			}
+		}
+
 	} else if (type->kind == Type_Basic) {
 		switch (type->Basic.kind) {
 		case Basic_any: {
@@ -3568,6 +3607,8 @@ gb_internal i64 type_align_of_internal(Type *t, TypePath *path) {
 	case Type_Slice:
 		return build_context.int_size;
 
+	case Type_BitField:
+		return type_align_of_internal(t->BitField.backing_type, path);
 
 	case Type_Tuple: {
 		i64 max = 1;
@@ -3943,6 +3984,9 @@ gb_internal i64 type_size_of_internal(Type *t, TypePath *path) {
 		return stride_in_bytes * t->Matrix.column_count;
 	}
 
+	case Type_BitField:
+		return type_size_of_internal(t->BitField.backing_type, path);
+
 	case Type_RelativePointer:
 		return type_size_of_internal(t->RelativePointer.base_integer, path);
 	case Type_RelativeMultiPointer:
@@ -4114,8 +4158,10 @@ gb_internal isize check_is_assignable_to_using_subtype(Type *src, Type *dst, isi
 		}
 		if (allow_polymorphic && dst_is_polymorphic) {
 			Type *fb = base_type(type_deref(f->type));
-			if (fb->kind == Type_Struct && fb->Struct.polymorphic_parent == dst) {
-				return true;
+			if (fb->kind == Type_Struct) {
+				if (fb->Struct.polymorphic_parent == dst) {
+					return true;
+				}
 			}
 		}
 
@@ -4217,7 +4263,70 @@ gb_internal Type *alloc_type_proc_from_types(Type **param_types, unsigned param_
 	return t;
 }
 
-
+// gb_internal Type *type_from_selection(Type *type, Selection const &sel) {
+// 	for (i32 index : sel.index) {
+// 		Type *bt = base_type(type_deref(type));
+// 		switch (bt->kind) {
+// 		case Type_Struct:
+// 			type = bt->Struct.fields[index]->type;
+// 			break;
+// 		case Type_Tuple:
+// 			type = bt->Tuple.variables[index]->type;
+// 			break;
+// 		case Type_BitField:
+// 			type = bt->BitField.fields[index]->type;
+// 			break;
+// 		case Type_Array:
+// 			type = bt->Array.elem;
+// 			break;
+// 		case Type_EnumeratedArray:
+// 			type = bt->Array.elem;
+// 			break;
+// 		case Type_Slice:
+// 			switch (index) {
+// 			case 0: type = alloc_type_multi_pointer(bt->Slice.elem); break;
+// 			case 1: type = t_int;                                    break;
+// 			}
+// 			break;
+// 		case Type_DynamicArray:
+// 			switch (index) {
+// 			case 0: type = alloc_type_multi_pointer(bt->DynamicArray.elem); break;
+// 			case 1: type = t_int;                                           break;
+// 			case 2: type = t_int;                                           break;
+// 			case 3: type = t_allocator;                                     break;
+// 			}
+// 			break;
+// 		case Type_Map:
+// 			switch (index) {
+// 			case 0: type = t_uintptr;   break;
+// 			case 1: type = t_int;       break;
+// 			case 2: type = t_allocator; break;
+// 			}
+// 			break;
+// 		case Type_Basic:
+// 			if (is_type_complex_or_quaternion(bt)) {
+// 				type = base_complex_elem_type(bt);
+// 			} else {
+// 				switch (type->Basic.kind) {
+// 				case Basic_any:
+// 					switch (index) {
+// 					case 0: type = t_rawptr; break;
+// 					case 1: type = t_typeid; break;
+// 					}
+// 					break;
+// 				case Basic_string:
+// 					switch (index) {
+// 					case 0: type = t_u8_multi_ptr; break;
+// 					case 1: type = t_int;          break;
+// 					}
+// 					break;
+// 				}
+// 			}
+// 			break;
+// 		}
+// 	}
+// 	return type;
+// }
 
 gb_internal gbString write_type_to_string(gbString str, Type *type, bool shorthand=false) {
 	if (type == nullptr) {
@@ -4525,6 +4634,23 @@ gb_internal gbString write_type_to_string(gbString str, Type *type, bool shortha
 		str = gb_string_appendc(str, gb_bprintf("matrix[%d, %d]", cast(int)type->Matrix.row_count, cast(int)type->Matrix.column_count));
 		str = write_type_to_string(str, type->Matrix.elem);
 		break;
+
+	case Type_BitField:
+		str = gb_string_appendc(str, "bit_field ");
+		str = write_type_to_string(str, type->BitField.backing_type);
+		str = gb_string_appendc(str, " {");
+		for (isize i = 0; i < type->BitField.fields.count; i++) {
+			Entity *f = type->BitField.fields[i];
+			if (i > 0) {
+				str = gb_string_appendc(str, ", ");
+			}
+			str = gb_string_append_length(str, f->token.string.text, f->token.string.len);
+			str = gb_string_appendc(str, ": ");
+			str = write_type_to_string(str, f->type);
+			str = gb_string_append_fmt(str, " | %u", type->BitField.bit_sizes[i]);
+		}
+		str = gb_string_appendc(str, " }");
+		break;
 	}
 
 	return str;

+ 84 - 132
tests/core/net/test_core_net.odin

@@ -15,6 +15,7 @@ import "core:mem"
 import "core:fmt"
 import "core:net"
 import "core:strconv"
+import "core:sync"
 import "core:time"
 import "core:thread"
 import "core:os"
@@ -62,11 +63,7 @@ main :: proc() {
 
 	address_parsing_test(t)
 
-	when ODIN_OS != .Windows {
-		fmt.printf("IMPORTANT: `core:thread` seems to still be a bit wonky on Linux and MacOS, so we can't run tests relying on them.\n", ODIN_OS)
-	} else {
-		tcp_tests(t)
-	}
+	tcp_tests(t)
 
 	split_url_test(t)
 	join_url_test(t)
@@ -338,174 +335,129 @@ IP_Address_Parsing_Test_Vectors :: []IP_Address_Parsing_Test_Vector{
 	{ .IP6, "c0a8",                    "", ""},
 }
 
-
-ENDPOINT := net.Endpoint{
-	net.IP4_Address{127, 0, 0, 1},
-	9999,
-}
-
-CONTENT := "Hellope!"
-
-SEND_TIMEOUT :: time.Duration(1 * time.Second)
-RECV_TIMEOUT :: time.Duration(1 * time.Second)
-
-Thread_Data :: struct {
-	skt: net.Any_Socket,
-	err: net.Network_Error,
-	tid: ^thread.Thread,
-
-	no_accept: bool,  // Tell the server proc not to accept.
-
-	data:   [1024]u8, // Received data and its length
-	length: int,
-}
-
-thread_data := [3]Thread_Data{}
-
-/*
-	This runs a bunch of socket tests using threads:
-	- two servers trying to bind the same endpoint
-	- client trying to connect to closed port
-	- client trying to connect to an open port with a non-accepting server
-	- client sending server data and server sending client data
-	- etc.
-*/
 tcp_tests :: proc(t: ^testing.T) {
 	fmt.println("Testing two servers trying to bind to the same endpoint...")
 	two_servers_binding_same_endpoint(t)
 	fmt.println("Testing client connecting to a closed port...")
 	client_connects_to_closed_port(t)
-	fmt.println("Testing client connecting to port that doesn't accept...")
-	client_connects_to_open_but_non_accepting_port(t)
 	fmt.println("Testing client sending server data...")
 	client_sends_server_data(t)
 }
 
-tcp_client :: proc(retval: rawptr) {
-	send :: proc(content: []u8) -> (err: net.Network_Error) {
-		skt := net.dial_tcp(ENDPOINT) or_return
-		defer net.close(skt)
-
-		net.set_option(skt, .Send_Timeout,    SEND_TIMEOUT)
-		net.set_option(skt, .Receive_Timeout, RECV_TIMEOUT)
-
-		_, err = net.send(skt, content)
-		return
-	}
-
-	r := transmute(^Thread_Data)retval
-	r.err = send(transmute([]u8)CONTENT)
-	return
+ENDPOINT := net.Endpoint{
+	net.IP4_Address{127, 0, 0, 1},
+	9999,
 }
 
-tcp_server :: proc(retval: rawptr) {
-	r := transmute(^Thread_Data)retval
-
-	if r.skt, r.err = net.listen_tcp(ENDPOINT); r.err != nil {
-		return
-	}
-	defer net.close(r.skt)
-
-	if r.no_accept {
-		// Don't accept any connections, just listen.
-		return
-	}
-
-	client: net.TCP_Socket
-	if client, _, r.err = net.accept_tcp(r.skt.(net.TCP_Socket)); r.err != nil {
-		return
-	}
-	defer net.close(client)
-
+@(test)
+two_servers_binding_same_endpoint :: proc(t: ^testing.T) {
+	skt1, err1 := net.listen_tcp(ENDPOINT)
+	defer net.close(skt1)
+	skt2, err2 := net.listen_tcp(ENDPOINT)
+	defer net.close(skt2)
 
-	r.length, r.err = net.recv_tcp(client, r.data[:])
-	return
+	expect(t, err1 == nil, "expected first server binding to endpoint to do so without error")
+	expect(t, err2 == net.Bind_Error.Address_In_Use, "expected second server to bind to an endpoint to return .Address_In_Use")
 }
 
-cleanup_thread :: proc(data: Thread_Data) {
-	net.close(data.skt)
-
-	thread.terminate(data.tid, 1)
-	thread.destroy(data.tid)
+@(test)
+client_connects_to_closed_port :: proc(t: ^testing.T) {
+	skt, err := net.dial_tcp(ENDPOINT)
+	defer net.close(skt)
+	expect(t, err == net.Dial_Error.Refused, "expected dial of a closed endpoint to return .Refused")
 }
 
-two_servers_binding_same_endpoint :: proc(t: ^testing.T) {
-	thread_data = {}
+@(test)
+client_sends_server_data :: proc(t: ^testing.T) {
+	CONTENT: string: "Hellope!"
 
-	thread_data[0].tid = thread.create_and_start_with_data(&thread_data[0], tcp_server, context)
-	thread_data[1].tid = thread.create_and_start_with_data(&thread_data[1], tcp_server, context)
+	SEND_TIMEOUT :: time.Duration(1 * time.Second)
+	RECV_TIMEOUT :: time.Duration(1 * time.Second)
 
-	defer {
-		cleanup_thread(thread_data[0])
-		cleanup_thread(thread_data[1])
-	}
+	Thread_Data :: struct {
+		t: ^testing.T,
+		skt: net.Any_Socket,
+		err: net.Network_Error,
+		tid: ^thread.Thread,
 
-	// Give the two servers enough time to try and bind the same endpoint
-	time.sleep(1 * time.Second)
+		data:   [1024]u8, // Received data and its length
+		length: int,
+		wg:     ^sync.Wait_Group,
+	}
 
-	first_won  := thread_data[0].err == nil && thread_data[1].err == net.Bind_Error.Address_In_Use
-	second_won := thread_data[1].err == nil && thread_data[0].err == net.Bind_Error.Address_In_Use
+	tcp_client :: proc(thread_data: rawptr) {
+		r := transmute(^Thread_Data)thread_data
 
-	okay := first_won || second_won
-	msg  := fmt.tprintf("Expected servers to return `nil` and `Address_In_Use`, got %v and %v", thread_data[0].err, thread_data[1].err)
-	expect(t, okay, msg)
-}
+		defer sync.wait_group_done(r.wg)
 
-client_connects_to_closed_port :: proc(t: ^testing.T) {
-	thread_data = {}
+		if r.skt, r.err = net.dial_tcp(ENDPOINT); r.err != nil {
+			log(r.t, r.err)
+			return
+		}
 
-	thread_data[0].tid = thread.create_and_start_with_data(&thread_data[0], tcp_client, context)
+		net.set_option(r.skt, .Send_Timeout, SEND_TIMEOUT)
 
-	defer {
-		cleanup_thread(thread_data[0])
+		_, r.err = net.send(r.skt, transmute([]byte)CONTENT)
 	}
 
-	// Give the socket enough time to return `Refused`
-	time.sleep(4 * time.Second)
+	tcp_server :: proc(thread_data: rawptr) {
+		r := transmute(^Thread_Data)thread_data
 
-	okay := thread_data[0].err == net.Dial_Error.Refused
-	msg  := fmt.tprintf("Expected client to return `Refused` connecting to closed port, got %v", thread_data[0].err)
-	expect(t, okay, msg)
-}
+		defer sync.wait_group_done(r.wg)
 
-client_connects_to_open_but_non_accepting_port :: proc(t: ^testing.T) {
-	thread_data = {}
+		log(r.t, "tcp_server listen")
+		if r.skt, r.err = net.listen_tcp(ENDPOINT); r.err != nil {
+			sync.wait_group_done(r.wg)
+			log(r.t, r.err)
+			return
+		}
 
-	// Tell server proc not to accept
-	thread_data[0].no_accept = true
+		sync.wait_group_done(r.wg)
 
-	thread_data[0].tid = thread.create_and_start_with_data(&thread_data[0], tcp_server, context)
-	thread_data[1].tid = thread.create_and_start_with_data(&thread_data[1], tcp_client, context)
+		log(r.t, "tcp_server accept")
+		client: net.TCP_Socket
+		if client, _, r.err = net.accept_tcp(r.skt.(net.TCP_Socket)); r.err != nil {
+			log(r.t, r.err)
+			return
+		}
+		defer net.close(client)
 
-	defer {
-		cleanup_thread(thread_data[0])
-		cleanup_thread(thread_data[1])
-	}
+		net.set_option(client, .Receive_Timeout, RECV_TIMEOUT)
 
-	// Give the two servers enough time to try and bind the same endpoint
-	time.sleep(4 * time.Second)
+		r.length, r.err = net.recv_tcp(client, r.data[:])
+		return
+	}
+	
+	thread_data := [2]Thread_Data{}
 
-	okay := thread_data[0].err == nil && thread_data[1].err == net.Dial_Error.Refused
-	msg  := fmt.tprintf("Expected server and client to return `nil` and `Refused`, got %v and %v", thread_data[0].err, thread_data[1].err)
-	expect(t, okay, msg)
-}
+	wg: sync.Wait_Group
+	sync.wait_group_add(&wg, 1)
 
-client_sends_server_data :: proc(t: ^testing.T) {
-	thread_data = {}
+	thread_data[0].t = t
+	thread_data[0].wg = &wg
+	thread_data[0].tid = thread.create_and_start_with_data(&thread_data[0], tcp_server, context)
+	
+	log(t, "waiting for server to start listening")
+	sync.wait_group_wait(&wg)
+	log(t, "starting up client")
 
-	// Tell server proc not to accept
-	// thread_data[0].no_accept = true
+	sync.wait_group_add(&wg, 2)
 
-	thread_data[0].tid = thread.create_and_start_with_data(&thread_data[0], tcp_server, context)
+	thread_data[1].t = t
+	thread_data[1].wg = &wg
 	thread_data[1].tid = thread.create_and_start_with_data(&thread_data[1], tcp_client, context)
 
 	defer {
-		cleanup_thread(thread_data[0])
-		cleanup_thread(thread_data[1])
+		net.close(thread_data[0].skt)
+		thread.destroy(thread_data[0].tid)
+
+		net.close(thread_data[1].skt)
+		thread.destroy(thread_data[1].tid)
 	}
 
-	// Give the two servers enough time to try and bind the same endpoint
-	time.sleep(1 * time.Second)
+	log(t, "waiting for threads to finish")
+	sync.wait_group_wait(&wg)
+	log(t, "threads finished")
 
 	okay := thread_data[0].err == nil && thread_data[1].err == nil
 	msg  := fmt.tprintf("Expected client and server to return `nil`, got %v and %v", thread_data[0].err, thread_data[1].err)

+ 53 - 20
vendor/miniaudio/common.odin

@@ -14,8 +14,37 @@ when ODIN_OS == .Windows {
 	foreign import lib "lib/miniaudio.a"
 }
 
-handle :: distinct rawptr
+BINDINGS_VERSION_MAJOR    :: 0
+BINDINGS_VERSION_MINOR    :: 11
+BINDINGS_VERSION_REVISION :: 21 
+BINDINGS_VERSION          :: [3]u32{BINDINGS_VERSION_MAJOR, BINDINGS_VERSION_MINOR, BINDINGS_VERSION_REVISION}
+BINDINGS_VERSION_STRING   :: "0.11.21"
+
+@(init)
+version_check :: proc() {
+	v: [3]u32
+	version(&v.x, &v.y, &v.z)
+	if v != BINDINGS_VERSION {
+		buf: [1024]byte
+		n := copy(buf[:],  "miniaudio version mismatch: ")
+		n += copy(buf[n:], "bindings are for version ")
+		n += copy(buf[n:], BINDINGS_VERSION_STRING)
+		n += copy(buf[n:], ", but version ")
+		n += copy(buf[n:], string(version_string()))
+		n += copy(buf[n:], " is linked, make sure to compile the correct miniaudio version by going to `vendor/miniaudio/src` ")
+
+		when ODIN_OS == .Windows {
+			n += copy(buf[n:], "and executing `build.bat`")
+		} else {
+			n += copy(buf[n:], "and executing `make`")
+		}
+
+		panic(string(buf[:n]))
+	}
+}
+
 
+handle :: distinct rawptr
 
 /* SIMD alignment in bytes. Currently set to 32 bytes in preparation for future AVX optimizations. */
 SIMD_ALIGNMENT :: 32
@@ -141,28 +170,32 @@ result :: enum c.int {
 	CANCELLED                                   = -51,
 	MEMORY_ALREADY_MAPPED                       = -52,
 
+	/* General non-standard errors. */
+	CRC_MISMATCH                                = -100,
+
 	/* General miniaudio-specific errors. */
-	FORMAT_NOT_SUPPORTED                        = -100,
-	DEVICE_TYPE_NOT_SUPPORTED                   = -101,
-	SHARE_MODE_NOT_SUPPORTED                    = -102,
-	NO_BACKEND                                  = -103,
-	NO_DEVICE                                   = -104,
-	API_NOT_FOUND                               = -105,
-	INVALID_DEVICE_CONFIG                       = -106,
-	LOOP                                        = -107,
+	FORMAT_NOT_SUPPORTED                        = -200,
+	DEVICE_TYPE_NOT_SUPPORTED                   = -201,
+	SHARE_MODE_NOT_SUPPORTED                    = -202,
+	NO_BACKEND                                  = -203,
+	NO_DEVICE                                   = -204,
+	API_NOT_FOUND                               = -205,
+	INVALID_DEVICE_CONFIG                       = -206,
+	LOOP                                        = -207,
+	BACKEND_NOT_ENABLED                         = -208,
 
 	/* State errors. */
-	DEVICE_NOT_INITIALIZED                      = -200,
-	DEVICE_ALREADY_INITIALIZED                  = -201,
-	DEVICE_NOT_STARTED                          = -202,
-	DEVICE_NOT_STOPPED                          = -203,
+	DEVICE_NOT_INITIALIZED                      = -300,
+	DEVICE_ALREADY_INITIALIZED                  = -301,
+	DEVICE_NOT_STARTED                          = -302,
+	DEVICE_NOT_STOPPED                          = -303,
 
 	/* Operation errors. */
-	FAILED_TO_INIT_BACKEND                      = -300,
-	FAILED_TO_OPEN_BACKEND_DEVICE               = -301,
-	FAILED_TO_START_BACKEND_DEVICE              = -302,
-	FAILED_TO_STOP_BACKEND_DEVICE               = -303,
-} 
+	FAILED_TO_INIT_BACKEND                      = -400,
+	FAILED_TO_OPEN_BACKEND_DEVICE               = -401,
+	FAILED_TO_START_BACKEND_DEVICE              = -402,
+	FAILED_TO_STOP_BACKEND_DEVICE               = -403,
+}
 
 
 MIN_CHANNELS :: 1
@@ -214,7 +247,7 @@ standard_sample_rate :: enum u32 {
 	rate_192000 = 192000,
 
 	rate_16000  = 16000,     /* Extreme lows */
-	rate_11025  = 11250,
+	rate_11025  = 11025,
 	rate_8000   = 8000,
 
 	rate_352800 = 352800,    /* Extreme highs */
@@ -229,7 +262,7 @@ standard_sample_rate :: enum u32 {
 channel_mix_mode :: enum c.int {
 	rectangular = 0,   /* Simple averaging based on the plane(s) the channel is sitting on. */
 	simple,            /* Drop excess channels; zeroed out extra channels. */
-	custom_weights,    /* Use custom weights specified in ma_channel_router_config. */
+	custom_weights,    /* Use custom weights specified in ma_channel_converter_config. */
 	default = rectangular,
 }
 

+ 56 - 26
vendor/miniaudio/data_conversion.odin

@@ -68,7 +68,7 @@ resampling_backend_vtable :: struct {
 	onReset:                       proc "c" (pUserData: rawptr, pBackend: ^resampling_backend) -> result,
 }
 
-resample_algorithm :: enum {
+resample_algorithm :: enum c.int {
 	linear = 0,   /* Fastest, lowest quality. Optional low-pass filtering. Default. */
 	custom,
 }
@@ -138,7 +138,7 @@ foreign lib {
 	/*
 	Converts the given input data.
 
-	Both the input and output frames must be in the format specified in the config when the resampler was initilized.
+	Both the input and output frames must be in the format specified in the config when the resampler was initialized.
 
 	On input, [pFrameCountOut] contains the number of output frames to process. On output it contains the number of output frames that
 	were actually processed, which may be less than the requested amount which will happen if there's not enough input data. You can use
@@ -161,7 +161,7 @@ foreign lib {
 
 
 	/*
-	Sets the input and output sample sample rate.
+	Sets the input and output sample rate.
 	*/
 	resampler_set_rate :: proc(pResampler: ^resampler, sampleRateIn, sampleRateOut: u32) -> result ---
 
@@ -226,13 +226,14 @@ mono_expansion_mode :: enum c.int {
 }
 
 channel_converter_config :: struct {
-	format:         format,
-	channelsIn:     u32,
-	channelsOut:    u32,
-	pChannelMapIn:  [^]channel,
-	pChannelMapOut: [^]channel,
-	mixingMode:     channel_mix_mode,
-	ppWeights:      ^[^]f32,  /* [in][out]. Only used when mixingMode is set to ma_channel_mix_mode_custom_weights. */
+	format:                          format,
+	channelsIn:                      u32,
+	channelsOut:                     u32,
+	pChannelMapIn:                   [^]channel,
+	pChannelMapOut:                  [^]channel,
+	mixingMode:                      channel_mix_mode,
+	calculateLFEFromSpatialChannels: b32, /* When an output LFE channel is present, but no input LFE, set to true to set the output LFE to the average of all spatial channels (LR, FR, etc.). Ignored when an input LFE is present. */
+	ppWeights:                       ^[^]f32, /* [in][out]. Only used when mixingMode is set to ma_channel_mix_mode_custom_weights. */
 }
 
 channel_converter :: struct {
@@ -275,19 +276,20 @@ Data Conversion
 
 **************************************************************************************************************************************************************/
 data_converter_config :: struct {
-	formatIn:               format,
-	formatOut:              format,
-	channelsIn:             u32,
-	channelsOut:            u32,
-	sampleRateIn:           u32,
-	sampleRateOut:          u32,
-	pChannelMapIn:          [^]channel,
-	pChannelMapOut:         [^]channel,
-	ditherMode:             dither_mode,
-	channelMixMode:         channel_mix_mode,
-	ppChannelWeights:       ^[^]f32, /* [in][out]. Only used when channelMixMode is set to ma_channel_mix_mode_custom_weights. */
-	allowDynamicSampleRate: b32,
-	resampling:             resampler_config,
+	formatIn:                        format,
+	formatOut:                       format,
+	channelsIn:                      u32,
+	channelsOut:                     u32,
+	sampleRateIn:                    u32,
+	sampleRateOut:                   u32,
+	pChannelMapIn:                   [^]channel,
+	pChannelMapOut:                  [^]channel,
+	ditherMode:                      dither_mode,
+	channelMixMode:                  channel_mix_mode,
+	calculateLFEFromSpatialChannels: b32, /* When an output LFE channel is present, but no input LFE, set to true to set the output LFE to the average of all spatial channels (LR, FR, etc.). Ignored when an input LFE is present. */
+	ppChannelWeights:                ^[^]f32, /* [in][out]. Only used when channelMixMode is set to ma_channel_mix_mode_custom_weights. */
+	allowDynamicSampleRate:          b32,
+	resampling:                      resampler_config,
 }
 
 data_converter_execution_path :: enum c.int {
@@ -471,6 +473,28 @@ foreign lib {
 	The channel map buffer must have a capacity of at least `channels`.
 	*/
 	channel_map_contains_channel_position :: proc(channels: u32, pChannelMap: [^]channel, channelPosition: channel) -> b32 ---
+
+	/*
+	Find a channel position in the given channel map. Returns MA_TRUE if the channel is found; MA_FALSE otherwise. The
+	index of the channel is output to `pChannelIndex`.
+	
+	The channel map buffer must have a capacity of at least `channels`.
+	*/
+	channel_map_find_channel_position :: proc(channels: u32, pChannelMap: [^]channel, channelPosition: channel, pChannelIndex: ^u32) -> b32 ---
+
+	/*
+	Generates a string representing the given channel map.
+	
+	This is for printing and debugging purposes, not serialization/deserialization.
+	
+	Returns the length of the string, not including the null terminator.
+	*/
+	channel_map_to_string :: proc(pChannelMap: [^]channel, channels: u32, pBufferOut: [^]u8, bufferCap: uint) -> uint ---
+	
+	/*
+	Retrieves a human readable version of a channel position.
+	*/
+	channel_position_to_string :: proc(channel: channel) -> cstring ---
 }
 
 
@@ -514,9 +538,11 @@ rb :: struct {
 }
 
 pcm_rb :: struct {
-	rb:       rb,
-	format:   format,
-	channels: u32,
+	ds:         data_source_base,
+	rb:         rb,
+	format:     format,
+	channels:   u32,
+	sampleRate: u32, /* Not required for the ring buffer itself, but useful for associating the data with some sample rate, particularly for data sources. */
 }
 
 @(default_calling_convention="c", link_prefix="ma_")
@@ -556,6 +582,10 @@ foreign lib {
 	pcm_rb_get_subbuffer_stride :: proc(pRB: ^pcm_rb) -> u32 ---
 	pcm_rb_get_subbuffer_offset :: proc(pRB: ^pcm_rb, subbufferIndex: u32) -> u32 ---
 	pcm_rb_get_subbuffer_ptr    :: proc(pRB: ^pcm_rb, subbufferIndex: u32, pBuffer: rawptr) -> rawptr ---
+	pcm_rb_get_format           :: proc(pRB: ^pcm_rb) -> format ---
+	pcm_rb_get_channels         :: proc(pRB: ^pcm_rb) -> u32 ---
+	pcm_rb_get_sample_rate      :: proc(pRB: ^pcm_rb) -> u32 ---
+	pcm_rb_set_sample_rate      :: proc(pRB: ^pcm_rb, sampleRate: u32) ---
 }
 
 /*

+ 11 - 8
vendor/miniaudio/device_io_procs.odin

@@ -636,17 +636,17 @@ foreign lib {
 					callback will write to every sample in the output buffer, or if you are doing your own clearing.
 
 			noClip
-					When set to true, the contents of the output buffer passed into the data callback will be clipped after returning. When set to false (default), the
-					contents of the output buffer are left alone after returning and it will be left up to the backend itself to decide whether or not the clip. This only
+        			When set to true, the contents of the output buffer are left alone after returning and it will be left up to the backend itself to decide whether or
+        			not to clip. When set to false (default), the contents of the output buffer passed into the data callback will be clipped after returning. This only
 					applies when the playback sample format is f32.
 
 			noDisableDenormals
 					By default, miniaudio will disable denormals when the data callback is called. Setting this to true will prevent the disabling of denormals.
 
 			noFixedSizedCallback
-					Allows miniaudio to fire the data callback with any frame count. When this is set to true, the data callback will be fired with a consistent frame
-					count as specified by `periodSizeInFrames` or `periodSizeInMilliseconds`. When set to false, miniaudio will fire the callback with whatever the
-					backend requests, which could be anything.
+        			Allows miniaudio to fire the data callback with any frame count. When this is set to false (the default), the data callback will be fired with a
+        			consistent frame count as specified by `periodSizeInFrames` or `periodSizeInMilliseconds`. When set to true, miniaudio will fire the callback with
+        			whatever the backend requests, which could be anything.
 
 			dataCallback
 					The callback to fire whenever data is ready to be delivered to or from the device.
@@ -668,7 +668,7 @@ foreign lib {
 					A pointer that will passed to callbacks in pBackendVTable.
 
 			resampling.linear.lpfOrder
-					The linear resampler applies a low-pass filter as part of it's procesing for anti-aliasing. This setting controls the order of the filter. The higher
+					The linear resampler applies a low-pass filter as part of it's processing for anti-aliasing. This setting controls the order of the filter. The higher
 					the value, the better the quality, in general. Setting this to 0 will disable low-pass filtering altogether. The maximum value is
 					`MA_MAX_FILTER_ORDER`. The default value is `min(4, MA_MAX_FILTER_ORDER)`.
 
@@ -1150,8 +1150,6 @@ foreign lib {
 
 	Do not call this in any callback.
 
-	This will be called implicitly by `ma_device_uninit()`.
-
 
 	See Also
 	--------
@@ -1586,6 +1584,11 @@ foreign lib {
 	*/
 	get_backend_name :: proc(backend: backend) -> cstring ---
 
+	/*
+	Retrieves the backend enum from the given name.
+	*/
+	get_backend_from_name :: proc(pBackendName: cstring, pBackend: ^backend) -> result ---
+
 	/*
 	Determines whether or not the given backend is available by the compilation environment.
 	*/

+ 137 - 107
vendor/miniaudio/device_io_types.odin

@@ -84,6 +84,7 @@ device_notification_type :: enum c.int {
 	rerouted,
 	interruption_began,
 	interruption_ended,
+	unlocked,
 }
 
 device_notification :: struct {
@@ -195,7 +196,7 @@ DEPRECATED. Use ma_device_notification_proc instead.
 The callback for when the device has been stopped.
 
 This will be called when the device is stopped explicitly with `ma_device_stop()` and also called implicitly when the device is stopped through external forces
-such as being unplugged or an internal error occuring.
+such as being unplugged or an internal error occurring.
 
 
 Parameters
@@ -225,7 +226,7 @@ share_mode :: enum c.int {
 
 /* iOS/tvOS/watchOS session categories. */
 ios_session_category :: enum c.int {
-	default = 0,        /* AVAudioSessionCategoryPlayAndRecord with AVAudioSessionCategoryOptionDefaultToSpeaker. */
+	default = 0,        /* AVAudioSessionCategoryPlayAndRecord. */
 	none,               /* Leave the session category unchanged. */
 	ambient,            /* AVAudioSessionCategoryAmbient */
 	solo_ambient,       /* AVAudioSessionCategorySoloAmbient */
@@ -267,34 +268,41 @@ opensl_recording_preset :: enum c.int {
 	voice_unprocessed,   /* SL_ANDROID_RECORDING_PRESET_UNPROCESSED */
 }
 
+/* WASAPI audio thread priority characteristics. */
+wasapi_usage :: enum c.int {
+	default = 0,
+	games,
+	pro_audio,
+}
+
 /* AAudio usage types. */
 aaudio_usage :: enum c.int {
 	default = 0,                    /* Leaves the usage type unset. */
-	announcement,                   /* AAUDIO_SYSTEM_USAGE_ANNOUNCEMENT */
-	emergency,                      /* AAUDIO_SYSTEM_USAGE_EMERGENCY */
-	safety,                         /* AAUDIO_SYSTEM_USAGE_SAFETY */
-	vehicle_status,                 /* AAUDIO_SYSTEM_USAGE_VEHICLE_STATUS */
+	media,                          /* AAUDIO_USAGE_MEDIA */
+	voice_communication,            /* AAUDIO_USAGE_VOICE_COMMUNICATION */
+	voice_communication_signalling, /* AAUDIO_USAGE_VOICE_COMMUNICATION_SIGNALLING */
 	alarm,                          /* AAUDIO_USAGE_ALARM */
+	notification,                   /* AAUDIO_USAGE_NOTIFICATION */
+	notification_ringtone,          /* AAUDIO_USAGE_NOTIFICATION_RINGTONE */
+	notification_event,             /* AAUDIO_USAGE_NOTIFICATION_EVENT */
 	assistance_accessibility,       /* AAUDIO_USAGE_ASSISTANCE_ACCESSIBILITY */
 	assistance_navigation_guidance, /* AAUDIO_USAGE_ASSISTANCE_NAVIGATION_GUIDANCE */
 	assistance_sonification,        /* AAUDIO_USAGE_ASSISTANCE_SONIFICATION */
-	assitant,                       /* AAUDIO_USAGE_ASSISTANT */
 	game,                           /* AAUDIO_USAGE_GAME */
-	media,                          /* AAUDIO_USAGE_MEDIA */
-	notification,                   /* AAUDIO_USAGE_NOTIFICATION */
-	notification_event,             /* AAUDIO_USAGE_NOTIFICATION_EVENT */
-	notification_ringtone,          /* AAUDIO_USAGE_NOTIFICATION_RINGTONE */
-	voice_communication,            /* AAUDIO_USAGE_VOICE_COMMUNICATION */
-	voice_communication_signalling, /* AAUDIO_USAGE_VOICE_COMMUNICATION_SIGNALLING */
+	assitant,                       /* AAUDIO_USAGE_ASSISTANT */
+	emergency,                      /* AAUDIO_SYSTEM_USAGE_EMERGENCY */
+	safety,                         /* AAUDIO_SYSTEM_USAGE_SAFETY */
+	vehicle_status,                 /* AAUDIO_SYSTEM_USAGE_VEHICLE_STATUS */
+	announcement,                   /* AAUDIO_SYSTEM_USAGE_ANNOUNCEMENT */
 }
 
 /* AAudio content types. */
 aaudio_content_type :: enum c.int {
 	default = 0,             /* Leaves the content type unset. */
-	movie,                   /* AAUDIO_CONTENT_TYPE_MOVIE */
+	speech,                  /* AAUDIO_CONTENT_TYPE_SPEECH */
 	music,                   /* AAUDIO_CONTENT_TYPE_MUSIC */
+	movie,                   /* AAUDIO_CONTENT_TYPE_MOVIE */
 	sonification,            /* AAUDIO_CONTENT_TYPE_SONIFICATION */
-	speech,                  /* AAUDIO_CONTENT_TYPE_SPEECH */
 }
 
 /* AAudio input presets. */
@@ -302,12 +310,19 @@ aaudio_input_preset :: enum c.int {
 	default = 0,             /* Leaves the input preset unset. */
 	generic,                 /* AAUDIO_INPUT_PRESET_GENERIC */
 	camcorder,               /* AAUDIO_INPUT_PRESET_CAMCORDER */
-	unprocessed,             /* AAUDIO_INPUT_PRESET_UNPROCESSED */
 	voice_recognition,       /* AAUDIO_INPUT_PRESET_VOICE_RECOGNITION */
 	voice_communication,     /* AAUDIO_INPUT_PRESET_VOICE_COMMUNICATION */
+	unprocessed,             /* AAUDIO_INPUT_PRESET_UNPROCESSED */
 	voice_performance,       /* AAUDIO_INPUT_PRESET_VOICE_PERFORMANCE */
 }
 
+aaudio_allowed_capture_policy :: enum c.int {
+	default = 0, /* Leaves the allowed capture policy unset. */
+	all,         /* AAUDIO_ALLOW_CAPTURE_BY_ALL */
+	system,      /* AAUDIO_ALLOW_CAPTURE_BY_SYSTEM */
+	none,        /* AAUDIO_ALLOW_CAPTURE_BY_NONE */
+}
+
 
 timer :: struct #raw_union {
 	counter:  i64,
@@ -364,36 +379,41 @@ device_config :: struct {
 	periods:                    u32,
 	performanceProfile:         performance_profile,
 	noPreSilencedOutputBuffer:  b8,   /* When set to true, the contents of the output buffer passed into the data callback will be left undefined rather than initialized to zero. */
-	noClip:                     b8,   /* When set to true, the contents of the output buffer passed into the data callback will be clipped after returning. Only applies when the playback sample format is f32. */
+	noClip:                     b8,   /* When set to true, the contents of the output buffer passed into the data callback will not be clipped after returning. Only applies when the playback sample format is f32. */
 	noDisableDenormals:         b8,   /* Do not disable denormals when firing the data callback. */
-  noFixedSizedCallback:       b8,   /* Disables strict fixed-sized data callbacks. Setting this to true will result in the period size being treated only as a hint to the backend. This is an optimization for those who don't need fixed sized callbacks. */
+  	noFixedSizedCallback:       b8,   /* Disables strict fixed-sized data callbacks. Setting this to true will result in the period size being treated only as a hint to the backend. This is an optimization for those who don't need fixed sized callbacks. */
 	dataCallback:               device_data_proc,
 	notificationCallback:       device_notification_proc,
 	stopCallback:               stop_proc,
 	pUserData:                  rawptr,
 	resampling:                 resampler_config,
 	playback: struct {
-		pDeviceID:      ^device_id,
-		format:         format,
-		channels:       u32,
-		channelMap:     [^]channel,
-		channelMixMode: channel_mix_mode,
-		shareMode:      share_mode,
+		pDeviceID:                       ^device_id,
+		format:                          format,
+		channels:                        u32,
+		channelMap:                      [^]channel,
+		channelMixMode:                  channel_mix_mode,
+		calculateLFEFromSpatialChannels: b32, /* When an output LFE channel is present, but no input LFE, set to true to set the output LFE to the average of all spatial channels (LR, FR, etc.). Ignored when an input LFE is present. */
+		shareMode:                       share_mode,
 	},
 	capture: struct {
-		pDeviceID:      ^device_id,
-		format:         format,
-		channels:       u32,
-		channelMap:     [^]channel,
-		channelMixMode: channel_mix_mode,
-		shareMode:      share_mode,
+		pDeviceID:                       ^device_id,
+		format:                          format,
+		channels:                        u32,
+		channelMap:                      [^]channel,
+		channelMixMode:                  channel_mix_mode,
+		calculateLFEFromSpatialChannels: b32, /* When an output LFE channel is present, but no input LFE, set to true to set the output LFE to the average of all spatial channels (LR, FR, etc.). Ignored when an input LFE is present. */
+		shareMode:                       share_mode,
 	},
 
 	wasapi: struct {
-		noAutoConvertSRC:     b8, /* When set to true, disables the use of AUDCLNT_STREAMFLAGS_AUTOCONVERTPCM. */
-		noDefaultQualitySRC:  b8, /* When set to true, disables the use of AUDCLNT_STREAMFLAGS_SRC_DEFAULT_QUALITY. */
-		noAutoStreamRouting:  b8, /* Disables automatic stream routing. */
-		noHardwareOffloading: b8, /* Disables WASAPI's hardware offloading feature. */
+		usage:                  wasapi_usage, /* When configured, uses Avrt APIs to set the thread characteristics. */
+		noAutoConvertSRC:       b8, /* When set to true, disables the use of AUDCLNT_STREAMFLAGS_AUTOCONVERTPCM. */
+		noDefaultQualitySRC:    b8, /* When set to true, disables the use of AUDCLNT_STREAMFLAGS_SRC_DEFAULT_QUALITY. */
+		noAutoStreamRouting:    b8, /* Disables automatic stream routing. */
+		noHardwareOffloading:   b8, /* Disables WASAPI's hardware offloading feature. */
+		loopbackProcessID:      u32, /* The process ID to include or exclude for loopback mode. Set to 0 to capture audio from all processes. Ignored when an explicit device ID is specified. */
+		loopbackProcessExclude: b8, /* When set to true, excludes the process specified by loopbackProcessID. By default, the process will be included. */
 	},
 	alsa: struct {
 		noMMap:         b32, /* Disables MMap mode. */
@@ -409,20 +429,23 @@ device_config :: struct {
 		allowNominalSampleRateChange: b32, /* Desktop only. When enabled, allows changing of the sample rate at the operating system level. */
 	},
 	opensl: struct {
-		streamType:      opensl_stream_type,
-		recordingPreset: opensl_recording_preset,
+		streamType:                     opensl_stream_type,
+		recordingPreset:                opensl_recording_preset,
+		enableCompatibilityWorkarounds: b32,
 	},
 	aaudio: struct {
-		usage:                   aaudio_usage,
-		contentType:             aaudio_content_type,
-		inputPreset:             aaudio_input_preset,
-		noAutoStartAfterReroute: b32,
+		usage:                          aaudio_usage,
+		contentType:                    aaudio_content_type,
+		inputPreset:                    aaudio_input_preset,
+		allowedCapturePolicy:           aaudio_allowed_capture_policy,
+		noAutoStartAfterReroute:        b32,
+		enableCompatibilityWorkarounds: b32,
 	},
 }
 
 
 /*
-The callback for handling device enumeration. This is fired from `ma_context_enumerated_devices()`.
+The callback for handling device enumeration. This is fired from `ma_context_enumerate_devices()`.
 
 
 Parameters
@@ -500,7 +523,7 @@ sample rate. For the channel map, the default should be used when `ma_channel_ma
 `MA_CHANNEL_NONE`). On input, the `periodSizeInFrames` or `periodSizeInMilliseconds` option should always be set. The backend should
 inspect both of these variables. If `periodSizeInFrames` is set, it should take priority, otherwise it needs to be derived from the period
 size in milliseconds (`periodSizeInMilliseconds`) and the sample rate, keeping in mind that the sample rate may be 0, in which case the
-sample rate will need to be determined before calculating the period size in frames. On output, all members of the `ma_device_data_format`
+sample rate will need to be determined before calculating the period size in frames. On output, all members of the `ma_device_descriptor`
 object should be set to a valid value, except for `periodSizeInMilliseconds` which is optional (`periodSizeInFrames` *must* be set).
 
 Starting and stopping of the device is done with `onDeviceStart()` and `onDeviceStop()` and should be self-explanatory. If the backend uses
@@ -516,7 +539,7 @@ If the backend requires absolute flexibility with it's data delivery, it can opt
 which will allow it to implement the logic that will run on the audio thread. This is much more advanced and is completely optional.
 
 The audio thread should run data delivery logic in a loop while `ma_device_get_state() == ma_device_state_started` and no errors have been
-encounted. Do not start or stop the device here. That will be handled from outside the `onDeviceDataLoop()` callback.
+encountered. Do not start or stop the device here. That will be handled from outside the `onDeviceDataLoop()` callback.
 
 The invocation of the `onDeviceDataLoop()` callback will be handled by miniaudio. When you start the device, miniaudio will fire this
 callback. When the device is stopped, the `ma_device_get_state() == ma_device_state_started` condition will fail and the loop will be terminated
@@ -609,12 +632,17 @@ context_type :: struct {
 
 	using _: struct #raw_union {
 		wasapi: (struct {
-			commandThread: thread,
-			commandLock:   mutex,
-			commandSem:    semaphore,
-			commandIndex:  u32,
-			commandCount:  u32,
-			commands:      [4]context_command__wasapi,
+			commandThread:                   thread,
+			commandLock:                     mutex,
+			commandSem:                      semaphore,
+			commandIndex:                    u32,
+			commandCount:                    u32,
+			commands:                        [4]context_command__wasapi,
+			hAvrt:                           handle,
+			AvSetMmThreadCharacteristicsA:   proc "system" (),
+			AvRevertMmThreadCharacteristics: proc "system" (),
+			hMMDevapi:                       handle,
+			ActivateAudioInterfaceAsync:     proc "system" (),
 		} when SUPPORT_WASAPI else struct {}),
 		
 		dsound: (struct {
@@ -888,6 +916,7 @@ context_type :: struct {
 			AAudioStreamBuilder_setUsage:                  proc "system" (),
 			AAudioStreamBuilder_setContentType:            proc "system" (),
 			AAudioStreamBuilder_setInputPreset:            proc "system" (),
+			AAudioStreamBuilder_setAllowedCapturePolicy:   proc "system" (),
 			AAudioStreamBuilder_openStream:                proc "system" (),
 			AAudioStream_close:                            proc "system" (),
 			AAudioStream_getState:                         proc "system" (),
@@ -926,6 +955,7 @@ context_type :: struct {
 	using _: struct #raw_union {
 		win32: (struct {
 			/*HMODULE*/ hOle32DLL:       handle,
+			CoInitialize:                proc "system" (),
 			CoInitializeEx:              proc "system" (),
 			CoUninitialize:              proc "system" (),
 			CoCreateInstance:            proc "system" (),
@@ -941,25 +971,12 @@ context_type :: struct {
 			RegOpenKeyExA:               proc "system" (),
 			RegCloseKey:                 proc "system" (),
 			RegQueryValueExA:            proc "system" (),
+
+			/*HRESULT*/ CoInitializeResult: c.long,
 		} when ODIN_OS == .Windows else struct {}),
 		
 		posix: (struct {
-			pthreadSO:                   handle,
-			pthread_create:              proc "system" (),
-			pthread_join:                proc "system" (),
-			pthread_mutex_init:          proc "system" (),
-			pthread_mutex_destroy:       proc "system" (),
-			pthread_mutex_lock:          proc "system" (),
-			pthread_mutex_unlock:        proc "system" (),
-			pthread_cond_init:           proc "system" (),
-			pthread_cond_destroy:        proc "system" (),
-			pthread_cond_wait:           proc "system" (),
-			pthread_cond_signal:         proc "system" (),
-			pthread_attr_init:           proc "system" (),
-			pthread_attr_destroy:        proc "system" (),
-			pthread_attr_setschedpolicy: proc "system" (),
-			pthread_attr_getschedparam:  proc "system" (),
-			pthread_attr_setschedparam:  proc "system" (),
+			_unused: c.int,
 		} when ODIN_OS != .Windows else struct {}),
 		
 		_unused: c.int,
@@ -997,48 +1014,50 @@ device :: struct {
 		},
 	},
 	playback: struct {
-		pID:                        ^device_id,                           /* Set to NULL if using default ID, otherwise set to the address of "id". */
-		id:                         device_id,                            /* If using an explicit device, will be set to a copy of the ID used for initialization. Otherwise cleared to 0. */
-		name:                       [MAX_DEVICE_NAME_LENGTH + 1]c.char,   /* Maybe temporary. Likely to be replaced with a query API. */
-		shareMode:                  share_mode,                           /* Set to whatever was passed in when the device was initialized. */
-		playback_format:            format,
-		channels:                   u32,
-		channelMap:                 [MAX_CHANNELS]channel,
-		internalFormat:             format,
-		internalChannels:           u32,
-		internalSampleRate:         u32,
-		internalChannelMap:         [MAX_CHANNELS]channel,
-		internalPeriodSizeInFrames: u32,
-		internalPeriods:            u32,
-		channelMixMode:             channel_mix_mode,
-		converter:                  data_converter,
-		pIntermediaryBuffer:        rawptr,  /* For implementing fixed sized buffer callbacks. Will be null if using variable sized callbacks. */
-		intermediaryBufferCap:      u32,
-		intermediaryBufferLen:      u32,     /* How many valid frames are sitting in the intermediary buffer. */
-		pInputCache:                rawptr,  /* In external format. Can be null. */
-		inputCacheCap:              u64,
-		inputCacheConsumed:         u64,
-		inputCacheRemaining:        u64,
+		pID:                             ^device_id,                           /* Set to NULL if using default ID, otherwise set to the address of "id". */
+		id:                              device_id,                            /* If using an explicit device, will be set to a copy of the ID used for initialization. Otherwise cleared to 0. */
+		name:                            [MAX_DEVICE_NAME_LENGTH + 1]c.char,   /* Maybe temporary. Likely to be replaced with a query API. */
+		shareMode:                       share_mode,                           /* Set to whatever was passed in when the device was initialized. */
+		playback_format:                 format,
+		channels:                        u32,
+		channelMap:                      [MAX_CHANNELS]channel,
+		internalFormat:                  format,
+		internalChannels:                u32,
+		internalSampleRate:              u32,
+		internalChannelMap:              [MAX_CHANNELS]channel,
+		internalPeriodSizeInFrames:      u32,
+		internalPeriods:                 u32,
+		channelMixMode:                  channel_mix_mode,
+		calculateLFEFromSpatialChannels: b32,
+		converter:                       data_converter,
+		pIntermediaryBuffer:             rawptr,  /* For implementing fixed sized buffer callbacks. Will be null if using variable sized callbacks. */
+		intermediaryBufferCap:           u32,
+		intermediaryBufferLen:           u32,     /* How many valid frames are sitting in the intermediary buffer. */
+		pInputCache:                     rawptr,  /* In external format. Can be null. */
+		inputCacheCap:                   u64,
+		inputCacheConsumed:              u64,
+		inputCacheRemaining:             u64,
 	},
 	capture: struct {
-		pID:                        ^device_id,                           /* Set to NULL if using default ID, otherwise set to the address of "id". */
-		id:                         device_id,                            /* If using an explicit device, will be set to a copy of the ID used for initialization. Otherwise cleared to 0. */
-		name:                       [MAX_DEVICE_NAME_LENGTH + 1]c.char,   /* Maybe temporary. Likely to be replaced with a query API. */
-		shareMode:                  share_mode,                           /* Set to whatever was passed in when the device was initialized. */
-		capture_format:             format,
-		channels:                   u32,
-		channelMap:                 [MAX_CHANNELS]channel,
-		internalFormat:             format,
-		internalChannels:           u32,
-		internalSampleRate:         u32,
-		internalChannelMap:         [MAX_CHANNELS]channel,
-		internalPeriodSizeInFrames: u32,
-		internalPeriods:            u32,
-		channelMixMode:             channel_mix_mode,
-		converter:                  data_converter,
-		pIntermediaryBuffer:        rawptr,  /* For implementing fixed sized buffer callbacks. Will be null if using variable sized callbacks. */
-		intermediaryBufferCap:      u32,
-		intermediaryBufferLen:      u32,     /* How many valid frames are sitting in the intermediary buffer. */
+		pID:                             ^device_id,                           /* Set to NULL if using default ID, otherwise set to the address of "id". */
+		id:                              device_id,                            /* If using an explicit device, will be set to a copy of the ID used for initialization. Otherwise cleared to 0. */
+		name:                            [MAX_DEVICE_NAME_LENGTH + 1]c.char,   /* Maybe temporary. Likely to be replaced with a query API. */
+		shareMode:                       share_mode,                           /* Set to whatever was passed in when the device was initialized. */
+		capture_format:                  format,
+		channels:                        u32,
+		channelMap:                      [MAX_CHANNELS]channel,
+		internalFormat:                  format,
+		internalChannels:                u32,
+		internalSampleRate:              u32,
+		internalChannelMap:              [MAX_CHANNELS]channel,
+		internalPeriodSizeInFrames:      u32,
+		internalPeriods:                 u32,
+		channelMixMode:                  channel_mix_mode,
+		calculateLFEFromSpatialChannels: b32,
+		converter:                       data_converter,
+		pIntermediaryBuffer:             rawptr,  /* For implementing fixed sized buffer callbacks. Will be null if using variable sized callbacks. */
+		intermediaryBufferCap:           u32,
+		intermediaryBufferLen:           u32,     /* How many valid frames are sitting in the intermediary buffer. */
 	},
 
 	using _: struct #raw_union {
@@ -1067,6 +1086,8 @@ device :: struct {
 			mappedBufferPlaybackLen: u32,
 			isStartedCapture: b32, /*atomic*/                  /* Can be read and written simultaneously across different threads. Must be used atomically, and must be 32-bit. */
 			isStartedPlayback: b32, /*atomic*/                 /* Can be read and written simultaneously across different threads. Must be used atomically, and must be 32-bit. */
+			loopbackProcessID: u32,
+			loopbackProcessExclude: b8,
 			noAutoConvertSRC: b8,                              /* When set to true, disables the use of AUDCLNT_STREAMFLAGS_AUTOCONVERTPCM. */
 			noDefaultQualitySRC: b8,                           /* When set to true, disables the use of AUDCLNT_STREAMFLAGS_SRC_DEFAULT_QUALITY. */
 			noHardwareOffloading: b8,
@@ -1074,6 +1095,9 @@ device :: struct {
 			allowPlaybackAutoStreamRouting: b8,
 			isDetachedPlayback: b8,
 			isDetachedCapture: b8,
+			usage: wasapi_usage,
+			hAvrtHandle: rawptr,
+			rerouteLock: mutex,
 		} when SUPPORT_WASAPI else struct {}),
 		
 		dsound: (struct {
@@ -1171,6 +1195,7 @@ device :: struct {
 			usage: aaudio_usage,
 			contentType: aaudio_content_type,
 			inputPreset: aaudio_input_preset,
+			allowedCapturePolicy: aaudio_allowed_capture_policy,
 			noAutoStartAfterReroute: b32,
 		} when SUPPORT_AAUDIO else struct {}),
 
@@ -1192,8 +1217,13 @@ device :: struct {
 		} when SUPPORT_OPENSL else struct {}),
 
 		webaudio: (struct {
-			indexPlayback: c.int,              /* We use a factory on the JavaScript side to manage devices and use an index for JS/C interop. */
-			indexCapture: c.int,
+			/* audioWorklets path. */
+			/* EMSCRIPTEN_WEBAUDIO_T */ audioContext: c.int,
+			/* EMSCRIPTEN_WEBAUDIO_T */ audioWorklet: c.int,
+			pIntermediaryBuffer: ^f32,
+			pStackBuffer: rawptr,
+			initResult: result, /* Set to MA_BUSY while initializing is in progress. */
+			deviceIndex: c.int, /* We store the device in a list on the JavaScript side. This is used to map our C object to the JS object. */
 		} when SUPPORT_WEBAUDIO else struct {}),
 
 		null_device: (struct {

+ 203 - 120
vendor/miniaudio/doc.odin

@@ -2,7 +2,7 @@ package miniaudio
 
 /*
 Audio playback and capture library. Choice of public domain or MIT-0. See license statements at the end of this file.
-miniaudio - v0.11.9 - 2022-04-20
+miniaudio - v0.11.21 - 2023-11-15
 
 David Reid - [email protected]
 
@@ -40,7 +40,7 @@ A config/init pattern is used throughout the entire library. The idea is that yo
 object and pass that into the initialization routine. The advantage to this system is that the
 config object can be initialized with logical defaults and new properties added to it without
 breaking the API. The config object can be allocated on the stack and does not need to be
-maintained after initialization of the corresponding object. 
+maintained after initialization of the corresponding object.
 
 
 1.1. Low Level API
@@ -89,7 +89,7 @@ device on the stack, but you could allocate it on the heap if that suits your si
 
         // Do something here. Probably your program's main loop.
 
-        ma_device_uninit(&device);    // This will stop the device so no need to do that manually.
+        ma_device_uninit(&device);
         return 0;
     }
     ```
@@ -365,7 +365,7 @@ initialized. The easiest but least flexible way of playing a sound is like so:
 This plays what miniaudio calls an "inline" sound. It plays the sound once, and then puts the
 internal sound up for recycling. The last parameter is used to specify which sound group the sound
 should be associated with which will be explained later. This particular way of playing a sound is
-simple, but lacks flexibility and features. A more flexible way of playing a sound is to first 
+simple, but lacks flexibility and features. A more flexible way of playing a sound is to first
 initialize a sound:
 
     ```c
@@ -388,7 +388,7 @@ Sounds should be uninitialized with `ma_sound_uninit()`.
 
 Sounds are not started by default. Start a sound with `ma_sound_start()` and stop it with
 `ma_sound_stop()`. When a sound is stopped, it is not rewound to the start. Use
-`ma_sound_seek_to_pcm_frames(&sound, 0)` to seek back to the start of a sound. By default, starting
+`ma_sound_seek_to_pcm_frame(&sound, 0)` to seek back to the start of a sound. By default, starting
 and stopping sounds happens immediately, but sometimes it might be convenient to schedule the sound
 the be started and/or stopped at a specific time. This can be done with the following functions:
 
@@ -400,13 +400,13 @@ the be started and/or stopped at a specific time. This can be done with the foll
     ```
 
 The start/stop time needs to be specified based on the absolute timer which is controlled by the
-engine. The current global time time in PCM frames can be retrieved with `ma_engine_get_time()`.
-The engine's global time can be changed with `ma_engine_set_time()` for synchronization purposes if
-required. Note that scheduling a start time still requires an explicit call to `ma_sound_start()`
-before anything will play:
+engine. The current global time time in PCM frames can be retrieved with
+`ma_engine_get_time_in_pcm_frames()`. The engine's global time can be changed with
+`ma_engine_set_time_in_pcm_frames()` for synchronization purposes if required. Note that scheduling
+a start time still requires an explicit call to `ma_sound_start()` before anything will play:
 
     ```c
-    ma_sound_set_start_time_in_pcm_frames(&sound, ma_engine_get_time(&engine) + (ma_engine_get_sample_rate(&engine) * 2);
+    ma_sound_set_start_time_in_pcm_frames(&sound, ma_engine_get_time_in_pcm_frames(&engine) + (ma_engine_get_sample_rate(&engine) * 2);
     ma_sound_start(&sound);
     ```
 
@@ -462,6 +462,11 @@ is at the end, use `ma_sound_at_end()`. Looping of a sound can be controlled wit
 miniaudio should work cleanly out of the box without the need to download or install any
 dependencies. See below for platform-specific details.
 
+Note that GCC and Clang require `-msse2`, `-mavx2`, etc. for SIMD optimizations.
+
+If you get errors about undefined references to `__sync_val_compare_and_swap_8`, `__atomic_load_8`,
+etc. you need to link with `-latomic`.
+
 
 2.1. Windows
 ------------
@@ -491,9 +496,10 @@ notarization process. To fix this there are two options. The first is to use the
     #include "miniaudio.h"
     ```
 
-This will require linking with `-framework CoreFoundation -framework CoreAudio -framework AudioUnit`.
-Alternatively, if you would rather keep using runtime linking you can add the following to your
-entitlements.xcent file:
+This will require linking with `-framework CoreFoundation -framework CoreAudio -framework AudioToolbox`.
+If you get errors about AudioToolbox, try with `-framework AudioUnit` instead. You may get this when
+using older versions of iOS. Alternatively, if you would rather keep using runtime linking you can
+add the following to your entitlements.xcent file:
 
     ```
     <key>com.apple.security.cs.allow-dyld-environment-variables</key>
@@ -534,6 +540,20 @@ you'll need to disable run-time linking with `MA_NO_RUNTIME_LINKING` and link wi
 The Emscripten build emits Web Audio JavaScript directly and should compile cleanly out of the box.
 You cannot use `-std=c*` compiler flags, nor `-ansi`.
 
+You can enable the use of AudioWorkets by defining `MA_ENABLE_AUDIO_WORKLETS` and then compiling
+with the following options:
+
+    -sAUDIO_WORKLET=1 -sWASM_WORKERS=1 -sASYNCIFY
+
+An example for compiling with AudioWorklet support might look like this:
+
+    emcc program.c -o bin/program.html -DMA_ENABLE_AUDIO_WORKLETS -sAUDIO_WORKLET=1 -sWASM_WORKERS=1 -sASYNCIFY
+
+To run locally, you'll need to use emrun:
+
+    emrun bin/program.html
+
+
 
 2.7. Build Options
 ------------------
@@ -629,10 +649,29 @@ You cannot use `-std=c*` compiler flags, nor `-ansi`.
     |                                  | and `ma_device` APIs. This is useful if you only want to use       |
     |                                  | miniaudio's data conversion and/or decoding APIs.                  |
     +----------------------------------+--------------------------------------------------------------------+
+    | MA_NO_RESOURCE_MANAGER           | Disables the resource manager. When using the engine this will     |
+    |                                  | also disable the following functions:                              |
+    |                                  |                                                                    |
+    |                                  | ```                                                                |
+    |                                  | ma_sound_init_from_file()                                          |
+    |                                  | ma_sound_init_from_file_w()                                        |
+    |                                  | ma_sound_init_copy()                                               |
+    |                                  | ma_engine_play_sound_ex()                                          |
+    |                                  | ma_engine_play_sound()                                             |
+    |                                  | ```                                                                |
+    |                                  |                                                                    |
+    |                                  | The only way to initialize a `ma_sound` object is to initialize it |
+    |                                  | from a data source.                                                |
+    +----------------------------------+--------------------------------------------------------------------+
+    | MA_NO_NODE_GRAPH                 | Disables the node graph API. This will also disable the engine API |
+    |                                  | because it depends on the node graph.                              |
+    +----------------------------------+--------------------------------------------------------------------+
+    | MA_NO_ENGINE                     | Disables the engine API.                                           |
+    +----------------------------------+--------------------------------------------------------------------+
     | MA_NO_THREADING                  | Disables the `ma_thread`, `ma_mutex`, `ma_semaphore` and           |
     |                                  | `ma_event` APIs. This option is useful if you only need to use     |
     |                                  | miniaudio for data conversion, decoding and/or encoding. Some      |
-    |                                  | families of APIsrequire threading which means the following        |
+    |                                  | families of APIs require threading which means the following       |
     |                                  | options must also be set:                                          |
     |                                  |                                                                    |
     |                                  |     ```                                                            |
@@ -731,7 +770,7 @@ To read data from a data source:
     ma_result result;
     ma_uint64 framesRead;
 
-    result = ma_data_source_read_pcm_frames(pDataSource, pFramesOut, frameCount, &framesRead, loop);
+    result = ma_data_source_read_pcm_frames(pDataSource, pFramesOut, frameCount, &framesRead);
     if (result != MA_SUCCESS) {
         return result;  // Failed to read data from the data source.
     }
@@ -751,7 +790,7 @@ you could plug in a decoder like so:
     ma_uint64 framesRead;
     ma_decoder decoder;   // <-- This would be initialized with `ma_decoder_init_*()`.
 
-    result = ma_data_source_read_pcm_frames(&decoder, pFramesOut, frameCount, &framesRead, loop);
+    result = ma_data_source_read_pcm_frames(&decoder, pFramesOut, frameCount, &framesRead);
     if (result != MA_SUCCESS) {
         return result;  // Failed to read data from the decoder.
     }
@@ -805,7 +844,7 @@ retrieved like so:
     ma_uint32 channels;
     ma_uint32 sampleRate;
     ma_channel channelMap[MA_MAX_CHANNELS];
-    
+
     result = ma_data_source_get_data_format(pDataSource, &format, &channels, &sampleRate, channelMap, MA_MAX_CHANNELS);
     if (result != MA_SUCCESS) {
         return result;  // Failed to retrieve data format.
@@ -825,7 +864,9 @@ read data within a certain range of the underlying data. To do this you can use
     ```
 
 This is useful if you have a sound bank where many sounds are stored in the same file and you want
-the data source to only play one of those sub-sounds.
+the data source to only play one of those sub-sounds. Note that once the range is set, everything
+that takes a position, such as cursors and loop points, should always be relatvie to the start of
+the range. When the range is set, any previously defined loop point will be reset.
 
 Custom loop points can also be used with data sources. By default, data sources will loop after
 they reach the end of the data source, but if you need to loop at a specific location, you can do
@@ -854,19 +895,19 @@ To do this, you can use chaining:
         return result;  // Failed to set the next data source.
     }
 
-    result = ma_data_source_read_pcm_frames(&decoder1, pFramesOut, frameCount, pFramesRead, MA_FALSE);
+    result = ma_data_source_read_pcm_frames(&decoder1, pFramesOut, frameCount, pFramesRead);
     if (result != MA_SUCCESS) {
         return result;  // Failed to read from the decoder.
     }
     ```
 
 In the example above we're using decoders. When reading from a chain, you always want to read from
-the top level data source in the chain. In the example above, `decoder1` is the top level data 
+the top level data source in the chain. In the example above, `decoder1` is the top level data
 source in the chain. When `decoder1` reaches the end, `decoder2` will start seamlessly without any
 gaps.
 
-Note that the `loop` parameter is set to false in the example above. When this is set to true, only
-the current data source will be looped. You can loop the entire chain by linking in a loop like so:
+Note that when looping is enabled, only the current data source will be looped. You can loop the
+entire chain by linking in a loop like so:
 
     ```c
     ma_data_source_set_next(&decoder1, &decoder2);  // decoder1 -> decoder2
@@ -877,9 +918,9 @@ Note that setting up chaining is not thread safe, so care needs to be taken if y
 changing links while the audio thread is in the middle of reading.
 
 Do not use `ma_decoder_seek_to_pcm_frame()` as a means to reuse a data source to play multiple
-instances of the same sound simultaneously. Instead, initialize multiple data sources for each
-instance. This can be extremely inefficient depending on the data source and can result in
-glitching due to subtle changes to the state of internal filters.
+instances of the same sound simultaneously. This can be extremely inefficient depending on the type
+of data source and can result in glitching due to subtle changes to the state of internal filters.
+Instead, initialize multiple data sources for each instance.
 
 
 4.1. Custom Data Sources
@@ -924,7 +965,7 @@ base object (`ma_data_source_base`):
         // Retrieve the length in PCM frames here. Return MA_NOT_IMPLEMENTED and set *pLength to 0 if there is no notion of a length or if the length is unknown.
     }
 
-    static g_my_data_source_vtable =
+    static ma_data_source_vtable g_my_data_source_vtable =
     {
         my_data_source_read,
         my_data_source_seek,
@@ -954,7 +995,7 @@ base object (`ma_data_source_base`):
     void my_data_source_uninit(my_data_source* pMyDataSource)
     {
         // ... do the uninitialization of your custom data source here ...
-        
+
         // You must uninitialize the base data source.
         ma_data_source_uninit(&pMyDataSource->base);
     }
@@ -1003,7 +1044,7 @@ configure the engine with an engine config:
     ma_engine_config engineConfig;
 
     engineConfig = ma_engine_config_init();
-    engineConfig.pPlaybackDevice = &myDevice;
+    engineConfig.pDevice = &myDevice;
 
     result = ma_engine_init(&engineConfig, &engine);
     if (result != MA_SUCCESS) {
@@ -1044,7 +1085,7 @@ Note that when you're not using a device, you must set the channel count and sam
 config or else miniaudio won't know what to use (miniaudio will use the device to determine this
 normally). When not using a device, you need to use `ma_engine_read_pcm_frames()` to process audio
 data from the engine. This kind of setup is useful if you want to do something like offline
-processing.
+processing or want to use a different audio system for playback such as SDL.
 
 When a sound is loaded it goes through a resource manager. By default the engine will initialize a
 resource manager internally, but you can also specify a pre-initialized resource manager:
@@ -1209,7 +1250,7 @@ might be beneficial to pre-decode the sound. You can do this with the `MA_SOUND_
 
 By default, sounds will be loaded synchronously, meaning `ma_sound_init_*()` will not return until
 the sound has been fully loaded. If this is prohibitive you can instead load sounds asynchronously
-by specificying the `MA_SOUND_FLAG_ASYNC` flag:
+by specifying the `MA_SOUND_FLAG_ASYNC` flag:
 
     ```c
     ma_sound_init_from_file(&engine, "my_sound.wav", MA_SOUND_FLAG_DECODE | MA_SOUND_FLAG_ASYNC, pGroup, NULL, &sound);
@@ -1230,7 +1271,7 @@ counter hit's zero. You can specify a fence like so:
     ma_sound sounds[4];
 
     result = ma_fence_init(&fence);
-    if (result != MA_SUCCES) {
+    if (result != MA_SUCCESS) {
         return result;
     }
 
@@ -1256,6 +1297,18 @@ When streaming sounds, 2 seconds worth of audio data is stored in memory. Althou
 fine, it's inefficient to use streaming for short sounds. Streaming is useful for things like music
 tracks in games.
 
+When loading a sound from a file path, the engine will reference count the file to prevent it from
+being loaded if it's already in memory. When you uninitialize a sound, the reference count will be
+decremented, and if it hits zero, the sound will be unloaded from memory. This reference counting
+system is not used for streams. The engine will use a 64-bit hash of the file name when comparing
+file paths which means there's a small chance you might encounter a name collision. If this is an
+issue, you'll need to use a different name for one of the colliding file paths, or just not load
+from files and instead load from a data source.
+
+You can use `ma_sound_init_copy()` to initialize a copy of another sound. Note, however, that this
+only works for sounds that were initialized with `ma_sound_init_from_file()` and without the
+`MA_SOUND_FLAG_STREAM` flag.
+
 When you initialize a sound, if you specify a sound group the sound will be attached to that group
 automatically. If you set it to NULL, it will be automatically attached to the engine's endpoint.
 If you would instead rather leave the sound unattached by default, you can can specify the
@@ -1395,19 +1448,19 @@ can be useful to schedule a sound to start or stop:
 
     ```c
     // Start the sound in 1 second from now.
-    ma_sound_set_start_time_in_pcm_frames(&sound, ma_engine_get_time(&engine) + (ma_engine_get_sample_rate(&engine) * 1));
+    ma_sound_set_start_time_in_pcm_frames(&sound, ma_engine_get_time_in_pcm_frames(&engine) + (ma_engine_get_sample_rate(&engine) * 1));
 
     // Stop the sound in 2 seconds from now.
-    ma_sound_set_stop_time_in_pcm_frames(&sound, ma_engine_get_time(&engine) + (ma_engine_get_sample_rate(&engine) * 2));
+    ma_sound_set_stop_time_in_pcm_frames(&sound, ma_engine_get_time_in_pcm_frames(&engine) + (ma_engine_get_sample_rate(&engine) * 2));
     ```
 
 Note that scheduling a start time still requires an explicit call to `ma_sound_start()` before
 anything will play.
 
 The time is specified in global time which is controlled by the engine. You can get the engine's
-current time with `ma_engine_get_time()`. The engine's global time is incremented automatically as
-audio data is read, but it can be reset with `ma_engine_set_time()` in case it needs to be
-resynchronized for some reason.
+current time with `ma_engine_get_time_in_pcm_frames()`. The engine's global time is incremented
+automatically as audio data is read, but it can be reset with `ma_engine_set_time_in_pcm_frames()`
+in case it needs to be resynchronized for some reason.
 
 To determine whether or not a sound is currently playing, use `ma_sound_is_playing()`. This will
 take the scheduled start and stop times into account.
@@ -1416,7 +1469,25 @@ Whether or not a sound should loop can be controlled with `ma_sound_set_looping(
 be looping by default. Use `ma_sound_is_looping()` to determine whether or not a sound is looping.
 
 Use `ma_sound_at_end()` to determine whether or not a sound is currently at the end. For a looping
-sound this should never return true.
+sound this should never return true. Alternatively, you can configure a callback that will be fired
+when the sound reaches the end. Note that the callback is fired from the audio thread which means
+you cannot be uninitializing sound from the callback. To set the callback you can use
+`ma_sound_set_end_callback()`. Alternatively, if you're using `ma_sound_init_ex()`, you can pass it
+into the config like so:
+
+    ```c
+    soundConfig.endCallback = my_end_callback;
+    soundConfig.pEndCallbackUserData = pMyEndCallbackUserData;
+    ```
+
+The end callback is declared like so:
+
+    ```c
+    void my_end_callback(void* pUserData, ma_sound* pSound)
+    {
+        ...
+    }
+    ```
 
 Internally a sound wraps around a data source. Some APIs exist to control the underlying data
 source, mainly for convenience:
@@ -1431,7 +1502,7 @@ source, mainly for convenience:
 Sound groups have the same API as sounds, only they are called `ma_sound_group`, and since they do
 not have any notion of a data source, anything relating to a data source is unavailable.
 
-Internally, sound data is loaded via the `ma_decoder` API which means by default in only supports
+Internally, sound data is loaded via the `ma_decoder` API which means by default it only supports
 file formats that have built-in support in miniaudio. You can extend this to support any kind of
 file format through the use of custom decoders. To do this you'll need to use a self-managed
 resource manager and configure it appropriately. See the "Resource Management" section below for
@@ -1446,7 +1517,7 @@ streaming. This is supported by miniaudio via the `ma_resource_manager` API.
 The resource manager is mainly responsible for the following:
 
   * Loading of sound files into memory with reference counting.
-  * Streaming of sound data
+  * Streaming of sound data.
 
 When loading a sound file, the resource manager will give you back a `ma_data_source` compatible
 object called `ma_resource_manager_data_source`. This object can be passed into any
@@ -1541,7 +1612,7 @@ need to retrieve a job using `ma_resource_manager_next_job()` and then process i
             ma_job job;
             ma_result result = ma_resource_manager_next_job(pMyResourceManager, &job);
             if (result != MA_SUCCESS) {
-                if (result == MA_NOT_DATA_AVAILABLE) {
+                if (result == MA_NO_DATA_AVAILABLE) {
                     // No jobs are available. Keep going. Will only get this if the resource manager was initialized
                     // with MA_RESOURCE_MANAGER_FLAG_NON_BLOCKING.
                     continue;
@@ -1580,7 +1651,7 @@ default. This can be done by setting `pVFS` member of the resource manager's con
 
 This is particularly useful in programs like games where you want to read straight from an archive
 rather than the normal file system. If you do not specify a custom VFS, the resource manager will
-use the operating system's normal file operations. This is default.
+use the operating system's normal file operations.
 
 To load a sound file and create a data source, call `ma_resource_manager_data_source_init()`. When
 loading a sound you need to specify the file path and options for how the sounds should be loaded.
@@ -1606,7 +1677,7 @@ an example for initializing a data source:
 
     // ...
 
-    ma_resource_manager_data_source_uninit(pResourceManager, &dataSource);
+    ma_resource_manager_data_source_uninit(&dataSource);
     ```
 
 The `flags` parameter specifies how you want to perform loading of the sound file. It can be a
@@ -1843,19 +1914,21 @@ once after the other:
 
     ```c
     ma_resource_manager_data_source_init(pResourceManager, "my_file", ..., &myDataBuffer0); // Refcount = 1. Initial load.
-    ma_resource_manager_data_source_uninit(pResourceManager, &myDataBuffer0);               // Refcount = 0. Unloaded.
+    ma_resource_manager_data_source_uninit(&myDataBuffer0);                                 // Refcount = 0. Unloaded.
 
     ma_resource_manager_data_source_init(pResourceManager, "my_file", ..., &myDataBuffer1); // Refcount = 1. Reloaded because previous uninit() unloaded it.
-    ma_resource_manager_data_source_uninit(pResourceManager, &myDataBuffer1);               // Refcount = 0. Unloaded.
+    ma_resource_manager_data_source_uninit(&myDataBuffer1);                                 // Refcount = 0. Unloaded.
     ```
 
 A binary search tree (BST) is used for storing data buffers as it has good balance between
 efficiency and simplicity. The key of the BST is a 64-bit hash of the file path that was passed
 into `ma_resource_manager_data_source_init()`. The advantage of using a hash is that it saves
 memory over storing the entire path, has faster comparisons, and results in a mostly balanced BST
-due to the random nature of the hash. The disadvantage is that file names are case-sensitive. If
-this is an issue, you should normalize your file names to upper- or lower-case before initializing
-your data sources.
+due to the random nature of the hash. The disadvantages are that file names are case-sensitive and
+there's a small chance of name collisions. If case-sensitivity is an issue, you should normalize
+your file names to upper- or lower-case before initializing your data sources. If name collisions
+become an issue, you'll need to change the name of one of the colliding names or just not use the
+resource manager.
 
 When a sound file has not already been loaded and the `MA_RESOURCE_MANAGER_DATA_SOURCE_FLAG_ASYNC`
 flag is excluded, the file will be decoded synchronously by the calling thread. There are two
@@ -1935,7 +2008,7 @@ miniaudio's routing infrastructure follows a node graph paradigm. The idea is th
 node whose outputs are attached to inputs of another node, thereby creating a graph. There are
 different types of nodes, with each node in the graph processing input data to produce output,
 which is then fed through the chain. Each node in the graph can apply their own custom effects. At
-the start of the graph will usually be one or more data source nodes which have no inputs, but
+the start of the graph will usually be one or more data source nodes which have no inputs and
 instead pull their data from a data source. At the end of the graph is an endpoint which represents
 the end of the chain and is where the final output is ultimately extracted from.
 
@@ -1961,7 +2034,7 @@ splitter node. It's at this point that the two data sources are mixed. After mix
 performs it's processing routine and produces two outputs which is simply a duplication of the
 input stream. One output is attached to a low pass filter, whereas the other output is attached to
 a echo/delay. The outputs of the the low pass filter and the echo are attached to the endpoint, and
-since they're both connected to the same input but, they'll be mixed.
+since they're both connected to the same input bus, they'll be mixed.
 
 Each input bus must be configured to accept the same number of channels, but the number of channels
 used by input buses can be different to the number of channels for output buses in which case
@@ -2001,14 +2074,14 @@ data from the graph:
     ```
 
 When you read audio data, miniaudio starts at the node graph's endpoint node which then pulls in
-data from it's input attachments, which in turn recusively pull in data from their inputs, and so
+data from it's input attachments, which in turn recursively pull in data from their inputs, and so
 on. At the start of the graph there will be some kind of data source node which will have zero
 inputs and will instead read directly from a data source. The base nodes don't literally need to
 read from a `ma_data_source` object, but they will always have some kind of underlying object that
 sources some kind of audio. The `ma_data_source_node` node can be used to read from a
 `ma_data_source`. Data is always in floating-point format and in the number of channels you
 specified when the graph was initialized. The sample rate is defined by the underlying data sources.
-It's up to you to ensure they use a consistent and appropraite sample rate.
+It's up to you to ensure they use a consistent and appropriate sample rate.
 
 The `ma_node` API is designed to allow custom nodes to be implemented with relative ease, but
 miniaudio includes a few stock nodes for common functionality. This is how you would initialize a
@@ -2049,7 +2122,7 @@ another, you do not need to detach first. You can just call `ma_node_attach_outp
 deal with it for you.
 
 Less frequently you may want to create a specialized node. This will be a node where you implement
-your own processing callback to apply a custom effect of some kind. This is similar to initalizing
+your own processing callback to apply a custom effect of some kind. This is similar to initializing
 one of the stock node types, only this time you need to specify a pointer to a vtable containing a
 pointer to the processing function and the number of input and output buses. Example:
 
@@ -2076,7 +2149,7 @@ pointer to the processing function and the number of input and output buses. Exa
 
     static ma_node_vtable my_custom_node_vtable =
     {
-        my_custom_node_process_pcm_frames, // The function that will be called process your custom node. This is where you'd implement your effect processing.
+        my_custom_node_process_pcm_frames, // The function that will be called to process your custom node. This is where you'd implement your effect processing.
         NULL,   // Optional. A callback for calculating the number of input frames that are required to process a specified number of output frames.
         2,      // 2 input buses.
         1,      // 1 output bus.
@@ -2088,7 +2161,7 @@ pointer to the processing function and the number of input and output buses. Exa
     // Each bus needs to have a channel count specified. To do this you need to specify the channel
     // counts in an array and then pass that into the node config.
     ma_uint32 inputChannels[2];     // Equal in size to the number of input channels specified in the vtable.
-    ma_uint32 outputChannels[1];    // Equal in size to the number of output channels specicied in the vtable.
+    ma_uint32 outputChannels[1];    // Equal in size to the number of output channels specified in the vtable.
 
     inputChannels[0]  = channelsIn;
     inputChannels[1]  = channelsIn;
@@ -2172,10 +2245,19 @@ and include the following:
     +-----------------------------------------+---------------------------------------------------+
     | MA_NODE_FLAG_CONTINUOUS_PROCESSING      | Causes the processing callback to be called even  |
     |                                         | when no data is available to be read from input   |
-    |                                         | attachments. This is useful for effects like      |
+    |                                         | attachments. When a node has at least one input   |
+    |                                         | bus, but there are no inputs attached or the      |
+    |                                         | inputs do not deliver any data, the node's        |
+    |                                         | processing callback will not get fired. This flag |
+    |                                         | will make it so the callback is always fired      |
+    |                                         | regardless of whether or not any input data is    |
+    |                                         | received. This is useful for effects like         |
     |                                         | echos where there will be a tail of audio data    |
     |                                         | that still needs to be processed even when the    |
-    |                                         | original data sources have reached their ends.    |
+    |                                         | original data sources have reached their ends. It |
+    |                                         | may also be useful for nodes that must always     |
+    |                                         | have their processing callback fired when there   |
+    |                                         | are no inputs attached.                           |
     +-----------------------------------------+---------------------------------------------------+
     | MA_NODE_FLAG_ALLOW_NULL_INPUT           | Used in conjunction with                          |
     |                                         | `MA_NODE_FLAG_CONTINUOUS_PROCESSING`. When this   |
@@ -2206,7 +2288,7 @@ called `ma_splitter_node`. This takes has 1 input bus and splits the stream into
 You can use it like this:
 
     ```c
-    ma_splitter_node_config splitterNodeConfig = ma_splitter_node_config_init(channelsIn, channelsOut);
+    ma_splitter_node_config splitterNodeConfig = ma_splitter_node_config_init(channels);
 
     ma_splitter_node splitterNode;
     result = ma_splitter_node_init(&nodeGraph, &splitterNodeConfig, NULL, &splitterNode);
@@ -2366,7 +2448,7 @@ bus and input bus is locked. This locking is specifically for attaching and deta
 different threads and does not affect `ma_node_graph_read_pcm_frames()` in any way. The locking and
 unlocking is mostly self-explanatory, but a slightly less intuitive aspect comes into it when
 considering that iterating over attachments must not break as a result of attaching or detaching a
-node while iteration is occuring.
+node while iteration is occurring.
 
 Attaching and detaching are both quite simple. When an output bus of a node is attached to an input
 bus of another node, it's added to a linked list. Basically, an input bus is a linked list, where
@@ -2394,37 +2476,18 @@ used. The same general process applies to detachment. See `ma_node_attach_output
 8. Decoding
 ===========
 The `ma_decoder` API is used for reading audio files. Decoders are completely decoupled from
-devices and can be used independently. The following formats are supported:
+devices and can be used independently. Built-in support is included for the following formats:
 
-    +---------+------------------+----------+
-    | Format  | Decoding Backend | Built-In |
-    +---------+------------------+----------+
-    | WAV     | dr_wav           | Yes      |
-    | MP3     | dr_mp3           | Yes      |
-    | FLAC    | dr_flac          | Yes      |
-    | Vorbis  | stb_vorbis       | No       |
-    +---------+------------------+----------+
+    +---------+
+    | Format  |
+    +---------+
+    | WAV     |
+    | MP3     |
+    | FLAC    |
+    +---------+
 
-Vorbis is supported via stb_vorbis which can be enabled by including the header section before the
-implementation of miniaudio, like the following:
-
-    ```c
-    #define STB_VORBIS_HEADER_ONLY
-    #include "extras/stb_vorbis.c"    // Enables Vorbis decoding.
-
-    #define MINIAUDIO_IMPLEMENTATION
-    #include "miniaudio.h"
-
-    // The stb_vorbis implementation must come after the implementation of miniaudio.
-    #undef STB_VORBIS_HEADER_ONLY
-    #include "extras/stb_vorbis.c"
-    ```
-
-A copy of stb_vorbis is included in the "extras" folder in the miniaudio repository (https://github.com/mackron/miniaudio).
-
-Built-in decoders are amalgamated into the implementation section of miniaudio. You can disable the
-built-in decoders by specifying one or more of the following options before the miniaudio
-implementation:
+You can disable the built-in decoders by specifying one or more of the following options before the
+miniaudio implementation:
 
     ```c
     #define MA_NO_WAV
@@ -2432,8 +2495,8 @@ implementation:
     #define MA_NO_FLAC
     ```
 
-Disabling built-in decoding libraries is useful if you use these libraries independantly of the
-`ma_decoder` API.
+miniaudio supports the ability to plug in custom decoders. See the section below for details on how
+to use custom decoders.
 
 A decoder can be initialized from a file with `ma_decoder_init_file()`, a block of memory with
 `ma_decoder_init_memory()`, or from data delivered via callbacks with `ma_decoder_init()`. Here is
@@ -2534,7 +2597,7 @@ The `ma_decoding_backend_vtable` vtable has the following functions:
 
     ```
     onInit
-    onInitFile 
+    onInitFile
     onInitFileW
     onInitMemory
     onUninit
@@ -2546,11 +2609,11 @@ these are not specified, miniaudio will deal with it for you via a generic imple
 
 When you initialize a custom data source (by implementing the `onInit` function in the vtable) you
 will need to output a pointer to a `ma_data_source` which implements your custom decoder. See the
-section about data sources for details on how to implemen this. Alternatively, see the
+section about data sources for details on how to implement this. Alternatively, see the
 "custom_decoders" example in the miniaudio repository.
 
 The `onInit` function takes a pointer to some callbacks for the purpose of reading raw audio data
-from some abitrary source. You'll use these functions to read from the raw data and perform the
+from some arbitrary source. You'll use these functions to read from the raw data and perform the
 decoding. When you call them, you will pass in the `pReadSeekTellUserData` pointer to the relevant
 parameter.
 
@@ -2574,8 +2637,7 @@ opportunity to clean up and internal data.
 
 9. Encoding
 ===========
-The `ma_encoding` API is used for writing audio files. The only supported output format is WAV
-which is achieved via dr_wav which is amalgamated into the implementation section of miniaudio.
+The `ma_encoding` API is used for writing audio files. The only supported output format is WAV.
 This can be disabled by specifying the following option before the implementation of miniaudio:
 
     ```c
@@ -2615,9 +2677,16 @@ outputting any audio data. To output audio data, use `ma_encoder_write_pcm_frame
 example below:
 
     ```c
-    framesWritten = ma_encoder_write_pcm_frames(&encoder, pPCMFramesToWrite, framesToWrite);
+    ma_uint64 framesWritten;
+    result = ma_encoder_write_pcm_frames(&encoder, pPCMFramesToWrite, framesToWrite, &framesWritten);
+    if (result != MA_SUCCESS) {
+        ... handle error ...
+    }
     ```
 
+The `framesWritten` variable will contain the number of PCM frames that were actually written. This
+is optionally and you can pass in `NULL` if you need this.
+
 Encoders must be uninitialized with `ma_encoder_uninit()`.
 
 
@@ -2701,7 +2770,7 @@ To perform the conversion simply call `ma_channel_converter_process_pcm_frames()
     }
     ```
 
-It is up to the caller to ensure the output buffer is large enough to accomodate the new PCM
+It is up to the caller to ensure the output buffer is large enough to accommodate the new PCM
 frames.
 
 Input and output PCM frames are always interleaved. Deinterleaved layouts are not supported.
@@ -3147,7 +3216,7 @@ you can chain first and second order filters together.
 
 If you need to change the configuration of the filter, but need to maintain the state of internal
 registers you can do so with `ma_lpf_reinit()`. This may be useful if you need to change the sample
-rate and/or cutoff frequency dynamically while maintaing smooth transitions. Note that changing the
+rate and/or cutoff frequency dynamically while maintaining smooth transitions. Note that changing the
 format or channel count after initialization is invalid and will result in an error.
 
 The `ma_lpf` object supports a configurable order, but if you only need a first order filter you
@@ -3320,8 +3389,8 @@ The noise API uses simple LCG random number generation. It supports a custom see
 for things like automated testing requiring reproducibility. Setting the seed to zero will default
 to `MA_DEFAULT_LCG_SEED`.
 
-The amplitude, seed, and type can be changed dynamically with `ma_noise_set_amplitude()`,
-`ma_noise_set_seed()`, and `ma_noise_set_type()` respectively.
+The amplitude and seed can be changed dynamically with `ma_noise_set_amplitude()` and
+`ma_noise_set_seed()` respectively.
 
 By default, the noise API will use different values for different channels. So, for example, the
 left side in a stereo stream will be different to the right side. To instead have each channel use
@@ -3349,7 +3418,7 @@ miniaudio supports reading from a buffer of raw audio data via the `ma_audio_buf
 read from memory that's managed by the application, but can also handle the memory management for
 you internally. Memory management is flexible and should support most use cases.
 
-Audio buffers are initialised using the standard configuration system used everywhere in miniaudio:
+Audio buffers are initialized using the standard configuration system used everywhere in miniaudio:
 
     ```c
     ma_audio_buffer_config config = ma_audio_buffer_config_init(
@@ -3469,7 +3538,7 @@ you will want to use. To initialize a ring buffer, do something like the followi
     ```
 
 The `ma_pcm_rb_init()` function takes the sample format and channel count as parameters because
-it's the PCM varient of the ring buffer API. For the regular ring buffer that operates on bytes you
+it's the PCM variant of the ring buffer API. For the regular ring buffer that operates on bytes you
 would call `ma_rb_init()` which leaves these out and just takes the size of the buffer in bytes
 instead of frames. The fourth parameter is an optional pre-allocated buffer and the fifth parameter
 is a pointer to a `ma_allocation_callbacks` structure for custom memory allocation routines.
@@ -3516,21 +3585,26 @@ producer thread.
 
 15. Backends
 ============
-The following backends are supported by miniaudio.
+The following backends are supported by miniaudio. These are listed in order of default priority.
+When no backend is specified when initializing a context or device, miniaudio will attempt to use
+each of these backends in the order listed in the table below.
+
+Note that backends that are not usable by the build target will not be included in the build. For
+example, ALSA, which is specific to Linux, will not be included in the Windows build.
 
     +-------------+-----------------------+--------------------------------------------------------+
     | Name        | Enum Name             | Supported Operating Systems                            |
     +-------------+-----------------------+--------------------------------------------------------+
     | WASAPI      | ma_backend_wasapi     | Windows Vista+                                         |
     | DirectSound | ma_backend_dsound     | Windows XP+                                            |
-    | WinMM       | ma_backend_winmm      | Windows XP+ (may work on older versions, but untested) |
+    | WinMM       | ma_backend_winmm      | Windows 95+                                            |
     | Core Audio  | ma_backend_coreaudio  | macOS, iOS                                             |
-    | ALSA        | ma_backend_alsa       | Linux                                                  |
-    | PulseAudio  | ma_backend_pulseaudio | Cross Platform (disabled on Windows, BSD and Android)  |
-    | JACK        | ma_backend_jack       | Cross Platform (disabled on BSD and Android)           |
     | sndio       | ma_backend_sndio      | OpenBSD                                                |
     | audio(4)    | ma_backend_audio4     | NetBSD, OpenBSD                                        |
     | OSS         | ma_backend_oss        | FreeBSD                                                |
+    | PulseAudio  | ma_backend_pulseaudio | Cross Platform (disabled on Windows, BSD and Android)  |
+    | ALSA        | ma_backend_alsa       | Linux                                                  |
+    | JACK        | ma_backend_jack       | Cross Platform (disabled on BSD and Android)           |
     | AAudio      | ma_backend_aaudio     | Android 8+                                             |
     | OpenSL ES   | ma_backend_opensl     | Android (API level 16+)                                |
     | Web Audio   | ma_backend_webaudio   | Web (via Emscripten)                                   |
@@ -3569,6 +3643,12 @@ Some backends have some nuance details you may want to be aware of.
   miniaudio's built-in resampler is to take advantage of any potential device-specific
   optimizations the driver may implement.
 
+BSD
+---
+- The sndio backend is currently only enabled on OpenBSD builds.
+- The audio(4) backend is supported on OpenBSD, but you may need to disable sndiod before you can
+  use it.
+
 15.4. UWP
 ---------
 - UWP only supports default playback and capture devices.
@@ -3599,14 +3679,28 @@ Some backends have some nuance details you may want to be aware of.
 
 16. Optimization Tips
 =====================
+See below for some tips on improving performance.
 
-16.1. High Level API
+16.1. Low Level API
+-------------------
+- In the data callback, if your data is already clipped prior to copying it into the output buffer,
+  set the `noClip` config option in the device config to true. This will disable miniaudio's built
+  in clipping function.
+- By default, miniaudio will pre-silence the data callback's output buffer. If you know that you
+  will always write valid data to the output buffer you can disable pre-silencing by setting the
+  `noPreSilence` config option in the device config to true.
+
+16.2. High Level API
 --------------------
 - If a sound does not require doppler or pitch shifting, consider disabling pitching by
   initializing the sound with the `MA_SOUND_FLAG_NO_PITCH` flag.
-- If a sound does not require spatialization, disable it by initialzing the sound with the
-  `MA_SOUND_FLAG_NO_SPATIALIZATION` flag. It can be renabled again post-initialization with
+- If a sound does not require spatialization, disable it by initializing the sound with the
+  `MA_SOUND_FLAG_NO_SPATIALIZATION` flag. It can be re-enabled again post-initialization with
   `ma_sound_set_spatialization_enabled()`.
+- If you know all of your sounds will always be the same sample rate, set the engine's sample
+  rate to match that of the sounds. Likewise, if you're using a self-managed resource manager,
+  consider setting the decoded sample rate to match your sounds. By configuring everything to
+  use a consistent sample rate, sample rate conversion can be avoided.
 
 
 
@@ -3615,17 +3709,6 @@ Some backends have some nuance details you may want to be aware of.
 - Automatic stream routing is enabled on a per-backend basis. Support is explicitly enabled for
   WASAPI and Core Audio, however other backends such as PulseAudio may naturally support it, though
   not all have been tested.
-- The contents of the output buffer passed into the data callback will always be pre-initialized to
-  silence unless the `noPreSilencedOutputBuffer` config variable in `ma_device_config` is set to
-  true, in which case it'll be undefined which will require you to write something to the entire
-  buffer.
-- By default miniaudio will automatically clip samples. This only applies when the playback sample
-  format is configured as `ma_format_f32`. If you are doing clipping yourself, you can disable this
-  overhead by setting `noClip` to true in the device config.
-- Note that GCC and Clang requires `-msse2`, `-mavx2`, etc. for SIMD optimizations.
-- The sndio backend is currently only enabled on OpenBSD builds.
-- The audio(4) backend is supported on OpenBSD, but you may need to disable sndiod before you can
-  use it.
 - When compiling with VC6 and earlier, decoding is restricted to files less than 2GB in size. This
   is due to 64-bit file APIs not being available.
 */

+ 26 - 13
vendor/miniaudio/effects.odin

@@ -1,6 +1,6 @@
 package miniaudio
 
-import c "core:c/libc"
+import "core:c"
 
 when ODIN_OS == .Windows {
 	foreign import lib "lib/miniaudio.lib"
@@ -24,7 +24,7 @@ delay_config :: struct {
 delay :: struct {
 	config: delay_config,
 	cursor: u32,               /* Feedback is written to this cursor. Always equal or in front of the read cursor. */
-	bufferSizeInFrames: u32,   /* The maximum of config.startDelayInFrames and config.feedbackDelayInFrames. */
+	bufferSizeInFrames: u32,
 	pBuffer: [^]f32,
 }
 
@@ -51,10 +51,11 @@ gainer_config :: struct {
 }
 
 gainer :: struct {
-	config:    gainer_config,
-	t:         u32,
-	pOldGains: [^]f32,
-	pNewGains: [^]f32,
+	config:       gainer_config,
+	t:            u32,
+	masterVolume: f32,
+	pOldGains:    [^]f32,
+	pNewGains:    [^]f32,
 
 	/* Memory management. */
 	_pHeap:    rawptr,
@@ -72,6 +73,8 @@ foreign lib {
 	gainer_process_pcm_frames :: proc(pGainer: ^gainer, pFramesOut: rawptr, pFramesIn: rawptr, frameCount: u64) -> result ---
 	gainer_set_gain           :: proc(pGainer: ^gainer, newGain: f32) -> result ---
 	gainer_set_gains          :: proc(pGainer: ^gainer, pNewGains: [^]f32) -> result ---
+	gainer_set_master_volume  :: proc(pGainer: ^gainer, volume: f32) -> result ---
+	gainer_get_master_volume  :: proc(pGainer: ^gainer, volume: ^f32) -> result --- 
 }
 
 
@@ -120,7 +123,7 @@ fader :: struct {
 	volumeBeg:      f32,    /* If volumeBeg and volumeEnd is equal to 1, no fading happens (ma_fader_process_pcm_frames() will run as a passthrough). */
 	volumeEnd:      f32,
 	lengthInFrames: u64,    /* The total length of the fade. */
-	cursorInFrames: u64,    /* The current time in frames. Incremented by ma_fader_process_pcm_frames(). */
+	cursorInFrames: i64,    /* The current time in frames. Incremented by ma_fader_process_pcm_frames(). Signed because it'll be offset by startOffsetInFrames in set_fade_ex(). */
 }
 
 @(default_calling_convention="c", link_prefix="ma_")
@@ -131,6 +134,7 @@ foreign lib {
 	fader_process_pcm_frames :: proc(pFader: ^fader, pFramesOut, pFramesIn: rawptr, frameCount: u64) -> result ---
 	fader_get_data_format    :: proc(pFader: ^fader, pFormat: ^format, pChannels, pSampleRate: ^u32) ---
 	fader_set_fade           :: proc(pFader: ^fader, volumeBeg, volumeEnd: f32, lengthInFrames: u64) ---
+	fader_set_fade_ex        :: proc(pFader: ^fader, volumeBeg, volumeEnd: f32, lengthInFrames: u64, startOffsetInFrames: i64) ---
 	fader_get_current_volume :: proc(pFader: ^fader) -> f32 ---
 }
 
@@ -142,6 +146,11 @@ vec3f :: struct {
 	z: f32,
 }
 
+atomic_vec3f :: struct {
+	v:    vec3f,
+	lock: spinlock,
+}
+
 attenuation_model :: enum c.int {
 	none,          /* No distance attenuation and no spatialization. */
 	inverse,       /* Equivalent to OpenAL's AL_INVERSE_DISTANCE_CLAMPED. */
@@ -172,9 +181,9 @@ spatializer_listener_config :: struct {
 
 spatializer_listener :: struct {
 		config:    spatializer_listener_config,
-		position:  vec3f,  /* The absolute position of the listener. */
-		direction: vec3f,  /* The direction the listener is facing. The world up vector is config.worldUp. */
-		velocity:  vec3f,
+		position:  atomic_vec3f,  /* The absolute position of the listener. */
+		direction: atomic_vec3f,  /* The direction the listener is facing. The world up vector is config.worldUp. */
+		velocity:  atomic_vec3f,
 		isEnabled: b32,
 
 		/* Memory management. */
@@ -224,6 +233,7 @@ spatializer_config :: struct {
 	coneOuterGain:                f32,
 	dopplerFactor:                f32,    /* Set to 0 to disable doppler effect. */
 	directionalAttenuationFactor: f32,    /* Set to 0 to disable directional attenuation. */
+	minSpatializationChannelGain: f32,    /* The minimal scaling factor to apply to channel gains when accounting for the direction of the sound relative to the listener. Must be in the range of 0..1. Smaller values means more aggressive directional panning, larger values means more subtle directional panning. */
 	gainSmoothTimeInFrames:       u32,    /* When the gain of a channel changes during spatialization, the transition will be linearly interpolated over this number of frames. */
 }
 
@@ -245,10 +255,11 @@ spatializer :: struct {
 		dopplerFactor:                f32,      /* Set to 0 to disable doppler effect. */
 		directionalAttenuationFactor: f32,      /* Set to 0 to disable directional attenuation. */
 		gainSmoothTimeInFrames:       u32,      /* When the gain of a channel changes during spatialization, the transition will be linearly interpolated over this number of frames. */
-		position:                     vec3f,
-		direction:                    vec3f,
-		velocity:                     vec3f,    /* For doppler effect. */
+		position:                     atomic_vec3f,
+		direction:                    atomic_vec3f,
+		velocity:                     atomic_vec3f,    /* For doppler effect. */
 		dopplerPitch:                 f32,      /* Will be updated by ma_spatializer_process_pcm_frames() and can be used by higher level functions to apply a pitch shift for doppler effect. */
+		minSpatializationChannelGain: f32,
 		gainer:                       gainer,   /* For smooth gain transitions. */
 		pNewChannelGainsOut:          [^]f32,     /* An offset of _pHeap. Used by ma_spatializer_process_pcm_frames() to store new channel gains. The number of elements in this array is equal to config.channelsOut. */
 
@@ -266,6 +277,8 @@ foreign lib {
 	spatializer_init                                :: proc(pConfig: ^spatializer_config, pAllocationCallbacks: ^allocation_callbacks, pSpatializer: ^spatializer) -> result ---
 	spatializer_uninit                              :: proc(pSpatializer: ^spatializer, pAllocationCallbacks: ^allocation_callbacks) ---
 	spatializer_process_pcm_frames                  :: proc(pSpatializer: ^spatializer, pListener: ^spatializer_listener, pFramesOut, pFramesIn: rawptr, frameCount: u64) -> result ---
+	spatializer_set_master_volume                   :: proc(pSpatializer: ^spatializer, volume: f32) -> result ---
+	spatializer_get_master_volume                   :: proc(pSpatializer: ^spatializer, pVolume: ^f32) -> result ---
 	spatializer_get_input_channels                  :: proc(pSpatializer: ^spatializer) -> u32 ---
 	spatializer_get_output_channels                 :: proc(pSpatializer: ^spatializer) -> u32 ---
 	spatializer_set_attenuation_model               :: proc(pSpatializer: ^spatializer, attenuationModel: attenuation_model) ---

+ 1 - 1
vendor/miniaudio/encoding.odin

@@ -39,7 +39,7 @@ encoder :: struct {
 	onUninit:         encoder_uninit_proc,
 	onWritePCMFrames: encoder_write_pcm_frames_proc,
 	pUserData:        rawptr,
-	pInternalEncoder: rawptr, /* <-- The drwav/drflac/stb_vorbis/etc. objects. */
+	pInternalEncoder: rawptr,
 	data: struct #raw_union {
 		vfs: struct {
 			pVFS: ^vfs,

+ 102 - 30
vendor/miniaudio/engine.odin

@@ -16,13 +16,17 @@ Engine
 
 /* Sound flags. */
 sound_flags :: enum c.int {
+	/* Resource manager flags. */
 	STREAM                = 0x00000001,   /* MA_RESOURCE_MANAGER_DATA_SOURCE_FLAG_STREAM */
 	DECODE                = 0x00000002,   /* MA_RESOURCE_MANAGER_DATA_SOURCE_FLAG_DECODE */
 	ASYNC                 = 0x00000004,   /* MA_RESOURCE_MANAGER_DATA_SOURCE_FLAG_ASYNC */
 	WAIT_INIT             = 0x00000008,   /* MA_RESOURCE_MANAGER_DATA_SOURCE_FLAG_WAIT_INIT */
-	NO_DEFAULT_ATTACHMENT = 0x00000010,   /* Do not attach to the endpoint by default. Useful for when setting up nodes in a complex graph system. */
-	NO_PITCH              = 0x00000020,   /* Disable pitch shifting with ma_sound_set_pitch() and ma_sound_group_set_pitch(). This is an optimization. */
-	NO_SPATIALIZATION     = 0x00000040,   /* Disable spatialization. */
+	UNKNOWN_LENGTH        = 0x00000010,   /* MA_RESOURCE_MANAGER_DATA_SOURCE_FLAG_UNKNOWN_LENGTH */
+	
+	/* ma_sound specific flags. */
+	NO_DEFAULT_ATTACHMENT = 0x00001000,   /* Do not attach to the endpoint by default. Useful for when setting up nodes in a complex graph system. */
+	NO_PITCH              = 0x00002000,   /* Disable pitch shifting with ma_sound_set_pitch() and ma_sound_group_set_pitch(). This is an optimization. */
+	NO_SPATIALIZATION     = 0x00004000,   /* Disable spatialization. */
 }
 
 ENGINE_MAX_LISTENERS :: 4
@@ -35,31 +39,44 @@ engine_node_type :: enum c.int {
 }
 
 engine_node_config :: struct {
-	pEngine:                  ^engine,
-	type:                     engine_node_type,
-	channelsIn:               u32,
-	channelsOut:              u32,
-	sampleRate:               u32,     /* Only used when the type is set to ma_engine_node_type_sound. */
-	isPitchDisabled:          b8,      /* Pitching can be explicitly disable with MA_SOUND_FLAG_NO_PITCH to optimize processing. */
-	isSpatializationDisabled: b8,      /* Spatialization can be explicitly disabled with MA_SOUND_FLAG_NO_SPATIALIZATION. */
-	pinnedListenerIndex:      u8,      /* The index of the listener this node should always use for spatialization. If set to MA_LISTENER_INDEX_CLOSEST the engine will use the closest listener. */
+	pEngine:                     ^engine,
+	type:                        engine_node_type,
+	channelsIn:                  u32,
+	channelsOut:                 u32,
+	sampleRate:                  u32,     /* Only used when the type is set to ma_engine_node_type_sound. */
+	volumeSmoothTimeInPCMFrames: u32,
+	monoExpansionMode:           mono_expansion_mode,
+	isPitchDisabled:             b8,      /* Pitching can be explicitly disable with MA_SOUND_FLAG_NO_PITCH to optimize processing. */
+	isSpatializationDisabled:    b8,      /* Spatialization can be explicitly disabled with MA_SOUND_FLAG_NO_SPATIALIZATION. */
+	pinnedListenerIndex:         u8,      /* The index of the listener this node should always use for spatialization. If set to MA_LISTENER_INDEX_CLOSEST the engine will use the closest listener. */
 }
 
 /* Base node object for both ma_sound and ma_sound_group. */
 engine_node :: struct {
-	baseNode:                 node_base,           /* Must be the first member for compatiblity with the ma_node API. */
-	pEngine:                  ^engine,             /* A pointer to the engine. Set based on the value from the config. */
-	sampleRate:               u32,                 /* The sample rate of the input data. For sounds backed by a data source, this will be the data source's sample rate. Otherwise it'll be the engine's sample rate. */
-	fader:                    fader,
-	resampler:                linear_resampler,    /* For pitch shift. */
-	spatializer:              spatializer,
-	panner:                   panner,
-	pitch:                    f32, /*atomic*/
-	oldPitch:                 f32,                 /* For determining whether or not the resampler needs to be updated to reflect the new pitch. The resampler will be updated on the mixing thread. */
-	oldDopplerPitch:          f32,                 /* For determining whether or not the resampler needs to be updated to take a new doppler pitch into account. */
-	isPitchDisabled:          b32, /*atomic*/      /* When set to true, pitching will be disabled which will allow the resampler to be bypassed to save some computation. */
-	isSpatializationDisabled: b32, /*atomic*/      /* Set to false by default. When set to false, will not have spatialisation applied. */
-	pinnedListenerIndex:      u32, /*atomic*/      /* The index of the listener this node should always use for spatialization. If set to MA_LISTENER_INDEX_CLOSEST the engine will use the closest listener. */
+	baseNode:                    node_base,           /* Must be the first member for compatiblity with the ma_node API. */
+	pEngine:                     ^engine,             /* A pointer to the engine. Set based on the value from the config. */
+	sampleRate:                  u32,                 /* The sample rate of the input data. For sounds backed by a data source, this will be the data source's sample rate. Otherwise it'll be the engine's sample rate. */
+	volumeSmoothTimeInPCMFrames: u32,
+	monoExpansionMode:           mono_expansion_mode,
+	fader:                       fader,
+	resampler:                   linear_resampler,    /* For pitch shift. */
+	spatializer:                 spatializer,
+	panner:                      panner,
+	volumeGainer:                gainer,              /* This will only be used if volumeSmoothTimeInPCMFrames is > 0. */
+	volume:                      f32, /*atomic*/      /* Defaults to 1. */
+	pitch:                       f32, /*atomic*/
+	oldPitch:                    f32,                 /* For determining whether or not the resampler needs to be updated to reflect the new pitch. The resampler will be updated on the mixing thread. */
+	oldDopplerPitch:             f32,                 /* For determining whether or not the resampler needs to be updated to take a new doppler pitch into account. */
+	isPitchDisabled:             b32, /*atomic*/      /* When set to true, pitching will be disabled which will allow the resampler to be bypassed to save some computation. */
+	isSpatializationDisabled:    b32, /*atomic*/      /* Set to false by default. When set to false, will not have spatialisation applied. */
+	pinnedListenerIndex:         u32, /*atomic*/      /* The index of the listener this node should always use for spatialization. If set to MA_LISTENER_INDEX_CLOSEST the engine will use the closest listener. */
+
+	fadeSettings: struct {
+		volumeBeg:                  f32, /*atomic*/
+		volumeEnd:                  f32, /*atomic*/
+		fadeLengthInFrames:         u64, /*atomic*/ /* <-- Defaults to (~(ma_uint64)0) which is used to indicate that no fade should be applied. */
+		absoluteGlobalTimeInFrames: u64, /*atomic*/ /* <-- The time to start the fade. */
+	},
 
 	/* Memory management. */
 	_ownsHeap: b8,
@@ -79,6 +96,9 @@ foreign lib {
 
 SOUND_SOURCE_CHANNEL_COUNT :: 0xFFFFFFFF
 
+/* Callback for when a sound reaches the end. */
+sound_end_proc :: #type proc "c" (pUserData: rawptr, pSound: ^sound)
+
 sound_config :: struct {
 	pFilePath:                      cstring,          /* Set this to load from the resource manager. */
 	pFilePathW:                     [^]c.wchar_t,     /* Set this to load from the resource manager. */
@@ -87,14 +107,22 @@ sound_config :: struct {
 	initialAttachmentInputBusIndex: u32,              /* The index of the input bus of pInitialAttachment to attach the sound to. */
 	channelsIn:                     u32,              /* Ignored if using a data source as input (the data source's channel count will be used always). Otherwise, setting to 0 will cause the engine's channel count to be used. */
 	channelsOut:                    u32,              /* Set this to 0 (default) to use the engine's channel count. Set to MA_SOUND_SOURCE_CHANNEL_COUNT to use the data source's channel count (only used if using a data source as input). */
+	monoExpansionMode:              mono_expansion_mode, /* Controls how the mono channel should be expanded to other channels when spatialization is disabled on a sound. */
 	flags:                          u32,              /* A combination of MA_SOUND_FLAG_* flags. */
+	volumeSmoothTimeInPCMFrames:    u32,              /* The number of frames to smooth over volume changes. Defaults to 0 in which case no smoothing is used. */
 	initialSeekPointInPCMFrames:    u64,              /* Initializes the sound such that it's seeked to this location by default. */
 	rangeBegInPCMFrames:            u64,
 	rangeEndInPCMFrames:            u64,
 	loopPointBegInPCMFrames:        u64,
 	loopPointEndInPCMFrames:        u64,
 	isLooping:                      b32,
-	pDoneFence:                     ^fence,           /* Released when the resource manager has finished decoding the entire sound. Not used with streams. */
+
+	endCallback:          sound_end_proc, /* Fired when the sound reaches the end. Will be fired from the audio thread. Do not restart, uninitialize or otherwise change the state of the sound from here. Instead fire an event or set a variable to indicate to a different thread to change the start of the sound. Will not be fired in response to a scheduled stop with ma_sound_set_stop_time_*(). */
+	pEndCallbackUserData: rawptr,
+	
+	initNotifications: resource_manager_pipeline_notifications,
+
+	pDoneFence: ^fence, /* Deprecated. Use initNotifications instead. Released when the resource manager has finished decoding the entire sound. Not used with streams. */
 }
 
 sound :: struct {
@@ -102,6 +130,10 @@ sound :: struct {
 	pDataSource:    ^data_source,
 	seekTarget:     u64, /*atomic*/    /* The PCM frame index to seek to in the mixing thread. Set to (~(ma_uint64)0) to not perform any seeking. */
 	atEnd:          b32, /*atomic*/
+
+	endCallback:          sound_end_proc,
+	pEndCallbackUserData: rawptr,
+
 	ownsDataSource: b8,
 
 	/*
@@ -120,7 +152,9 @@ sound_inlined :: struct {
 
 @(default_calling_convention="c", link_prefix="ma_")
 foreign lib {
-	sound_config_init :: proc() -> sound_config ---
+	@(deprecated="Will be removed in 0.12. Use sound_config_init2() instead.")
+	sound_config_init  :: proc() -> sound_config ---
+	sound_config_init2 :: proc(pEngine: ^engine) -> sound_config --- /* Will be renamed to sound_config_init() in version 0.12. */
 
 	sound_init_from_file                     :: proc(pEngine: ^engine, pFilePath: cstring, flags: u32, pGroup: ^sound_group, pDoneFence: ^fence, pSound: ^sound) -> result ---
 	sound_init_from_file_w                   :: proc(pEngine: ^engine, pFilePath: [^]c.wchar_t, flags: u32, pGroup: ^sound_group, pDoneFence: ^fence, pSound: ^sound) -> result ---
@@ -132,6 +166,8 @@ foreign lib {
 	sound_get_data_source                    :: proc(pSound: ^sound) -> ^data_source ---
 	sound_start                              :: proc(pSound: ^sound) -> result ---
 	sound_stop                               :: proc(pSound: ^sound) -> result ---
+	sound_stop_with_fade_in_pcm_frames       :: proc(pSound: ^sound, fadeLengthInFrames: u64) --- /* Will overwrite any scheduled stop and fade. */
+	sound_stop_with_fade_in_milliseconds     :: proc(pSound: ^sound, fadeLengthInFrames: u64) --- /* Will overwrite any scheduled stop and fade. */
 	sound_set_volume                         :: proc(pSound: ^sound, volume: f32) ---
 	sound_get_volume                         :: proc(pSound: ^sound) -> f32 ---
 	sound_set_pan                            :: proc(pSound: ^sound, pan: f32) ---
@@ -174,13 +210,20 @@ foreign lib {
 	sound_get_directional_attenuation_factor :: proc(pSound: ^sound) -> f32 ---
 	sound_set_fade_in_pcm_frames             :: proc(pSound: ^sound, volumeBeg, volumeEnd: f32, fadeLengthInFrames: u64) ---
 	sound_set_fade_in_milliseconds           :: proc(pSound: ^sound, volumeBeg, volumeEnd: f32, fadeLengthInMilliseconds: u64) ---
+	sound_set_fade_start_in_pcm_frames       :: proc(pSound: ^sound, volumeBeg, volumeEnd: f32, fadeLengthInFrames, absoluteGlobalTimeInFrames: u64) ---
+	sound_set_fade_start_in_milliseconds     :: proc(pSound: ^sound, volumeBeg, volumeEnd: f32, fadeLengthInMilliseconds, absoluteGlobalTimeInMilliseconds: u64) ---
 	sound_get_current_fade_volume            :: proc(pSound: ^sound) -> f32 ---
 	sound_set_start_time_in_pcm_frames       :: proc(pSound: ^sound, absoluteGlobalTimeInFrames: u64) ---
 	sound_set_start_time_in_milliseconds     :: proc(pSound: ^sound, absoluteGlobalTimeInMilliseconds: u64) ---
 	sound_set_stop_time_in_pcm_frames        :: proc(pSound: ^sound, absoluteGlobalTimeInFrames: u64) ---
 	sound_set_stop_time_in_milliseconds      :: proc(pSound: ^sound, absoluteGlobalTimeInMilliseconds: u64) ---
+
+	sound_set_stop_time_with_fade_in_pcm_frames   :: proc(pSound: ^sound, stopAbsoluteGlobalTimeInFrames, fadeLengthInFrames: u64) ---
+	sound_set_stop_time_with_fade_in_milliseconds :: proc(pSound: ^sound, fadeAbsoluteGlobalTimeInMilliseconds, fadeLengthInMilliseconds: u64) ---
+
 	sound_is_playing                         :: proc(pSound: ^sound) -> b32 ---
 	sound_get_time_in_pcm_frames             :: proc(pSound: ^sound) -> u64 ---
+	sound_get_time_in_milliseconds           :: proc(pSound: ^sound) -> u64 ---
 	sound_set_looping                        :: proc(pSound: ^sound, isLooping: b32) ---
 	sound_is_looping                         :: proc(pSound: ^sound) -> b32 ---
 	sound_at_end                             :: proc(pSound: ^sound) -> b32 ---
@@ -190,6 +233,7 @@ foreign lib {
 	sound_get_length_in_pcm_frames           :: proc(pSound: ^sound, pLength: ^u64) -> result ---
 	sound_get_cursor_in_seconds              :: proc(pSound: ^sound, pCursor: ^f32) -> result ---
 	sound_get_length_in_seconds              :: proc(pSound: ^sound, pLength: ^f32) -> result ---
+	sound_set_end_callback                   :: proc(pSound: ^sound, callback: sound_end_proc, pUserData: rawptr) ---
 }
 
 
@@ -199,7 +243,9 @@ sound_group        :: distinct sound
 
 @(default_calling_convention="c", link_prefix="ma_")
 foreign lib {
-	sound_group_config_init :: proc() -> sound_group_config ---
+	@(deprecated="Will be removed in 0.12. Use sound_config_init2() instead.")
+	sound_group_config_init  :: proc() -> sound_group_config ---
+	sound_group_config_init2 :: proc(pEngine: ^engine) -> sound_group_config ---
 
 	sound_group_init                               :: proc(pEngine: ^engine, flags: u32, pParentGroup, pGroup: ^sound_group) -> result ---
 	sound_group_init_ex                            :: proc(pEngine: ^engine, pConfig: ^sound_group_config, pGroup: ^sound_group) -> result ---
@@ -258,12 +304,17 @@ foreign lib {
 	sound_group_get_time_in_pcm_frames             :: proc(pGroup: ^sound_group) -> u64 ---
 }
 
+engine_process_proc :: #type proc "c" (pUserData: rawptr, pFramesOut: [^]f32, frameCount: u64)
 
 engine_config :: struct {
 	pResourceManager:             ^resource_manager,      /* Can be null in which case a resource manager will be created for you. */
 	pContext:                     ^context_type,
 	pDevice:                      ^device,                /* If set, the caller is responsible for calling ma_engine_data_callback() in the device's data callback. */
 	pPlaybackDeviceID:            ^device_id,             /* The ID of the playback device to use with the default listener. */
+
+	dataCallback:         device_data_proc,               /* Can be null. Can be used to provide a custom device data callback. */
+	notificationCallback: device_notification_proc,
+
 	pLog:                         ^log,                   /* When set to NULL, will use the context's log. */
 	listenerCount:                u32,                    /* Must be between 1 and MA_ENGINE_MAX_LISTENERS. */
 	channels:                     u32,                    /* The number of channels to use when mixing and spatializing. When set to 0, will use the native channel count of the device. */
@@ -272,11 +323,16 @@ engine_config :: struct {
 	periodSizeInMilliseconds:     u32,                    /* Used if periodSizeInFrames is unset. */
 	gainSmoothTimeInFrames:       u32,                    /* The number of frames to interpolate the gain of spatialized sounds across. If set to 0, will use gainSmoothTimeInMilliseconds. */
 	gainSmoothTimeInMilliseconds: u32,                    /* When set to 0, gainSmoothTimeInFrames will be used. If both are set to 0, a default value will be used. */
+
+	defaultVolumeSmoothTimeInPCMFrames: u32,              /* Defaults to 0. Controls the default amount of smoothing to apply to volume changes to sounds. High values means more smoothing at the expense of high latency (will take longer to reach the new volume). */
+
 	allocationCallbacks:          allocation_callbacks,
 	noAutoStart:                  b32,                    /* When set to true, requires an explicit call to ma_engine_start(). This is false by default, meaning the engine will be started automatically in ma_engine_init(). */
 	noDevice:                     b32,                    /* When set to true, don't create a default device. ma_engine_read_pcm_frames() can be called manually to read data. */
 	monoExpansionMode:            mono_expansion_mode,    /* Controls how the mono channel should be expanded to other channels when spatialization is disabled on a sound. */
 	pResourceManagerVFS:          ^vfs,                   /* A pointer to a pre-allocated VFS object to use with the resource manager. This is ignored if pResourceManager is not NULL. */
+	onProcess:                    engine_process_proc,    /* Fired at the end of each call to ma_engine_read_pcm_frames(). For engine's that manage their own internal device (the default configuration), this will be fired from the audio thread, and you do not need to call ma_engine_read_pcm_frames() manually in order to trigger this. */
+	pProcessUserData:             rawptr,                 /* User data that's passed into onProcess. */
 }
 
 engine :: struct {
@@ -294,7 +350,12 @@ engine :: struct {
 	pInlinedSoundHead:      ^sound_inlined,               /* The first inlined sound. Inlined sounds are tracked in a linked list. */
 	inlinedSoundCount:      u32, /*atomic*/               /* The total number of allocated inlined sound objects. Used for debugging. */
 	gainSmoothTimeInFrames: u32,                          /* The number of frames to interpolate the gain of spatialized sounds across. */
-	monoExpansionMode:      mono_expansion_mode,
+
+	defaultVolumeSmoothTimeInPCMFrames: u32,
+
+	monoExpansionMode: mono_expansion_mode,
+	onProcess:         engine_process_proc,
+	pProcessUserData:  rawptr,
 }
 
 @(default_calling_convention="c", link_prefix="ma_")
@@ -309,15 +370,26 @@ foreign lib {
 	engine_get_device           :: proc(pEngine: ^engine) -> ^device ---
 	engine_get_log              :: proc(pEngine: ^engine) -> ^log ---
 	engine_get_endpoint         :: proc(pEngine: ^engine) -> ^node ---
-	engine_get_time             :: proc(pEngine: ^engine) -> u64 ---
-	engine_set_time             :: proc(pEngine: ^engine, globalTime: u64) -> result ---
+
+	engine_get_time_in_pcm_frames   :: proc(pEngine: ^engine) -> u64 ---
+	engine_get_time_in_milliseconds :: proc(pEngine: ^engine) -> u64 ---
+	engine_set_time_in_pcm_frames   :: proc(pEngine: ^engine, globalTime: u64) -> result --- 
+	engine_set_time_in_milliseconds :: proc(pEngine: ^engine, globalTime: u64) -> result --- 
+	
+	@(deprecated="Use engine_get_time_in_pcm_frames(). Will be removed in 0.12.")
+	engine_get_time :: proc(pEngine: ^engine) -> u64 ---
+	@(deprecated="Use engine_set_time_in_pcm_frames(). Will be removed in 0.12.")
+	engine_set_time :: proc(pEngine: ^engine, globalTime: u64) -> result ---
+
 	engine_get_channels         :: proc(pEngine: ^engine) -> u32 ---
 	engine_get_sample_rate      :: proc(pEngine: ^engine) -> u32 ---
 	
 	engine_start       :: proc(pEngine: ^engine) -> result ---
 	engine_stop        :: proc(pEngine: ^engine) -> result ---
 	engine_set_volume  :: proc(pEngine: ^engine, volume: f32) -> result ---
+	engine_get_volume  :: proc(pEngine: ^engine) -> f32 ---
 	engine_set_gain_db :: proc(pEngine: ^engine, gainDB: f32) -> result ---
+	engine_get_gain_db :: proc(pEngine: ^engine) -> f32 ---
 	
 	engine_get_listener_count     :: proc(pEngine: ^engine) -> u32 ---
 	engine_find_closest_listener  :: proc(pEngine: ^engine, absolutePosX, absolutePosY, absolutePosZ: f32) -> u32 ---

+ 1 - 1
vendor/miniaudio/filtering.odin

@@ -1,6 +1,6 @@
 package miniaudio
 
-import c "core:c/libc"
+import "core:c"
 
 when ODIN_OS == .Windows {
 	foreign import lib "lib/miniaudio.lib"

+ 1 - 1
vendor/miniaudio/generation.odin

@@ -49,7 +49,7 @@ noise_config :: struct {
 }
 
 noise :: struct {
-	ds:     data_source_vtable,
+	ds:     data_source_base,
 	config: noise_config,
 	lcg:    lcg,
 	state: struct #raw_union {

+ 1 - 1
vendor/miniaudio/job_queue.odin

@@ -1,6 +1,6 @@
 package miniaudio
 
-import c "core:c/libc"
+import "core:c"
 
 when ODIN_OS == .Windows {
 	foreign import lib "lib/miniaudio.lib"

BIN
vendor/miniaudio/lib/miniaudio.lib


+ 2 - 7
vendor/miniaudio/logging.odin

@@ -1,6 +1,6 @@
 package miniaudio
 
-import c "core:c/libc"
+import "core:c/libc"
 
 when ODIN_OS == .Windows {
 	foreign import lib "lib/miniaudio.lib"
@@ -34,11 +34,6 @@ logLevel (in)
 
 pMessage (in)
     The log message.
-
-
-Remarks
--------
-Do not modify the state of the device from inside the callback.
 */
 log_callback_proc :: proc "c" (pUserData: rawptr, level: u32, pMessage: cstring)
 
@@ -63,6 +58,6 @@ foreign lib {
 	log_register_callback   :: proc(pLog: ^log, callback: log_callback) -> result ---
 	log_unregister_callback :: proc(pLog: ^log, callback: log_callback) -> result ---
 	log_post                :: proc(pLog: ^log, level: u32, pMessage: cstring) -> result ---
-	log_postv               :: proc(pLog: ^log, level: u32, pFormat: cstring, args: c.va_list) -> result ---
+	log_postv               :: proc(pLog: ^log, level: u32, pFormat: cstring, args: libc.va_list) -> result ---
 	log_postf               :: proc(pLog: ^log, level: u32, pFormat: cstring, #c_vararg args: ..any) -> result ---
 }

+ 6 - 5
vendor/miniaudio/node_graph.odin

@@ -44,7 +44,7 @@ node_vtable :: struct {
 	/*
 	Extended processing callback. This callback is used for effects that process input and output
 	at different rates (i.e. they perform resampling). This is similar to the simple version, only
-	they take two seperate frame counts: one for input, and one for output.
+	they take two separate frame counts: one for input, and one for output.
 
 	On input, `pFrameCountOut` is equal to the capacity of the output buffer for each bus, whereas
 	`pFrameCountIn` will be equal to the number of PCM frames in each of the buffers in `ppFramesIn`.
@@ -102,7 +102,7 @@ node_output_bus :: struct {
 	channels:       u8,                     /* The number of channels in the audio stream for this bus. */
 
 	/* Mutable via multiple threads. Must be used atomically. The weird ordering here is for packing reasons. */
-	inputNodeInputBusIndex: u8, /*atomic*/                  /* The index of the input bus on the input. Required for detaching. */
+	inputNodeInputBusIndex: u8,                             /* The index of the input bus on the input. Required for detaching. Will only be used in the spinlock so does not need to be atomic. */
 	flags:                  u32, /*atomic*/                 /* Some state flags for tracking the read state of the output buffer. A combination of MA_NODE_OUTPUT_BUS_FLAG_*. */
 	refCount:               u32, /*atomic*/                 /* Reference count for some thread-safety when detaching. */
 	isAttached:             b32, /*atomic*/                 /* This is used to prevent iteration of nodes that are in the middle of being detached. Used for thread safety. */
@@ -236,10 +236,11 @@ foreign lib {
 }
 
 
-/* Splitter Node. 1 input, 2 outputs. Used for splitting/copying a stream so it can be as input into two separate output nodes. */
+/* Splitter Node. 1 input, many outputs. Used for splitting/copying a stream so it can be as input into two separate output nodes. */
 splitter_node_config :: struct {
-	nodeConfig: node_config,
-	channels:   u32,
+	nodeConfig:     node_config,
+	channels:       u32,
+	outputBusCount: u32,
 }
 
 splitter_node :: struct {

+ 1 - 0
vendor/miniaudio/resource_manager.odin

@@ -190,6 +190,7 @@ resource_manager_config :: struct {
 	decodedChannels:                u32,       /* The decoded channel count to use. Set to 0 (default) to use the file's native channel count. */
 	decodedSampleRate:              u32,       /* the decoded sample rate to use. Set to 0 (default) to use the file's native sample rate. */
 	jobThreadCount:                 u32,       /* Set to 0 if you want to self-manage your job threads. Defaults to 1. */
+	jobThreadStackSize:             uint,
 	jobQueueCapacity:               u32,       /* The maximum number of jobs that can fit in the queue at a time. Defaults to MA_JOB_TYPE_RESOURCE_MANAGER_QUEUE_CAPACITY. Cannot be zero. */
 	flags:                          u32,
 	pVFS:                           ^vfs,      /* Can be NULL in which case defaults will be used. */

File diff suppressed because it is too large
+ 315 - 152
vendor/miniaudio/src/miniaudio.h


+ 36 - 1
vendor/miniaudio/utilities.odin

@@ -1,6 +1,6 @@
 package miniaudio
 
-import c "core:c/libc"
+import "core:c"
 
 when ODIN_OS == .Windows {
 	foreign import lib "lib/miniaudio.lib"
@@ -104,6 +104,13 @@ foreign lib {
 	Helper for converting gain in decibels to a linear factor.
 	*/
 	volume_db_to_linear :: proc(gain: f32) -> f32 ---
+
+	/*
+	Mixes the specified number of frames in floating point format with a volume factor.
+
+	This will run on an optimized path when the volume is equal to 1.
+	*/
+	ma_mix_pcm_frames_f32 :: proc(pDst: ^f32, pSrc: ^f32, frameCount: u64, channels: u32, volume: f32) -> result ---
 }
 
 offset_pcm_frames_ptr_f32 :: #force_inline proc "c" (p: [^]f32, offsetInFrames: u64, channels: u32) -> [^]f32 {
@@ -297,3 +304,31 @@ foreign lib {
 	paged_audio_buffer_get_cursor_in_pcm_frames :: proc(pPagedAudioBuffer: ^paged_audio_buffer, pCursor: ^u64) -> result ---
 	paged_audio_buffer_get_length_in_pcm_frames :: proc(pPagedAudioBuffer: ^paged_audio_buffer, pLength: ^u64) -> result ---
 }
+
+pulsewave_config :: struct {
+	format:     format,
+	channels:   u32,
+	sampleRate: u32,
+	dutyCycle:  f64,
+	amplitude:  f64,
+	frequency:  f64,
+}
+
+pulsewave :: struct {
+	waveform: waveform,
+	config:   pulsewave_config,
+}
+
+@(default_calling_convention="c", link_prefix="ma_")
+foreign lib {
+	pulsewave_config_init :: proc(format: format, channels: u32, sampleRate: u32, dutyCycle: f64, amplitude: f64, frequency: f64) -> pulsewave_config ---
+
+	pulsewave_init              :: proc(pConfig: ^pulsewave_config, pWaveForm: ^pulsewave) -> result ---
+	pulsewave_uninit            :: proc(pWaveForm: ^pulsewave) ---
+	pulsewave_read_pcm_frames   :: proc(pWaveForm: ^pulsewave, pFramesOut: rawptr, frameCount: u64, pFramesRead: ^u64) -> result ---
+	pulsewave_seek_to_pcm_frame :: proc(pWaveForm: ^pulsewave, frameIndex: u64) -> result ---
+	pulsewave_set_amplitude     :: proc(pWaveForm: ^pulsewave, amplitude: f64) -> result ---
+	pulsewave_set_frequency     :: proc(pWaveForm: ^pulsewave, frequency: f64) -> result ---
+	pulsewave_set_sample_rate   :: proc(pWaveForm: ^pulsewave, sampleRate: u32) -> result ---
+	pulsewave_set_duty_cycle    :: proc(pWaveForm: ^pulsewave, dutyCycle: f64) -> result ---
+}

BIN
vendor/raylib/macos-arm64/libraylib.5.0.0.dylib


BIN
vendor/raylib/macos/libraylib.5.0.0.dylib


+ 1 - 1
vendor/vulkan/_gen/create_vulkan_odin_wrapper.py

@@ -565,7 +565,7 @@ def parse_structs(f):
                 # The second way has many fields that are each 1 bit
                 elif int(fname) == 1:
                     bit_field_type = do_type(bit_field[0], prev_name, fname)
-                    ffields.append(tuple(["bit_field", bit_field_type, comment]))
+                    ffields.append(tuple(["bitfield", bit_field_type, comment]))
                     break
                     
 

+ 14 - 14
vendor/vulkan/structs.odin

@@ -7032,7 +7032,7 @@ WaylandSurfaceCreateInfoKHR :: struct {
 }
 
 VideoH264SpsVuiFlags :: struct {
-	bit_field: u32,
+	bitfield: u32,
 }
 
 VideoH264HrdParameters :: struct {
@@ -7069,7 +7069,7 @@ VideoH264SequenceParameterSetVui :: struct {
 }
 
 VideoH264SpsFlags :: struct {
-	bit_field: u32,
+	bitfield: u32,
 }
 
 VideoH264ScalingLists :: struct {
@@ -7108,7 +7108,7 @@ VideoH264SequenceParameterSet :: struct {
 }
 
 VideoH264PpsFlags :: struct {
-	bit_field: u32,
+	bitfield: u32,
 }
 
 VideoH264PictureParameterSet :: struct {
@@ -7140,7 +7140,7 @@ VideoH265SubLayerHrdParameters :: struct {
 }
 
 VideoH265HrdFlags :: struct {
-	bit_field: u32,
+	bitfield: u32,
 }
 
 VideoH265HrdParameters :: struct {
@@ -7162,11 +7162,11 @@ VideoH265HrdParameters :: struct {
 }
 
 VideoH265VpsFlags :: struct {
-	bit_field: u32,
+	bitfield: u32,
 }
 
 VideoH265ProfileTierLevelFlags :: struct {
-	bit_field: u32,
+	bitfield: u32,
 }
 
 VideoH265ProfileTierLevel :: struct {
@@ -7200,7 +7200,7 @@ VideoH265ScalingLists :: struct {
 }
 
 VideoH265SpsVuiFlags :: struct {
-	bit_field: u32,
+	bitfield: u32,
 }
 
 VideoH265SequenceParameterSetVui :: struct {
@@ -7237,11 +7237,11 @@ VideoH265PredictorPaletteEntries :: struct {
 }
 
 VideoH265SpsFlags :: struct {
-	bit_field: u32,
+	bitfield: u32,
 }
 
 VideoH265ShortTermRefPicSetFlags :: struct {
-	bit_field: u32,
+	bitfield: u32,
 }
 
 VideoH265ShortTermRefPicSet :: struct {
@@ -7309,7 +7309,7 @@ VideoH265SequenceParameterSet :: struct {
 }
 
 VideoH265PpsFlags :: struct {
-	bit_field: u32,
+	bitfield: u32,
 }
 
 VideoH265PictureParameterSet :: struct {
@@ -7352,7 +7352,7 @@ VideoH265PictureParameterSet :: struct {
 }
 
 VideoDecodeH264PictureInfoFlags :: struct {
-	bit_field: u32,
+	bitfield: u32,
 }
 
 VideoDecodeH264PictureInfo :: struct {
@@ -7367,7 +7367,7 @@ VideoDecodeH264PictureInfo :: struct {
 }
 
 VideoDecodeH264ReferenceInfoFlags :: struct {
-	bit_field: u32,
+	bitfield: u32,
 }
 
 VideoDecodeH264ReferenceInfo :: struct {
@@ -7378,7 +7378,7 @@ VideoDecodeH264ReferenceInfo :: struct {
 }
 
 VideoDecodeH265PictureInfoFlags :: struct {
-	bit_field: u32,
+	bitfield: u32,
 }
 
 VideoDecodeH265PictureInfo :: struct {
@@ -7396,7 +7396,7 @@ VideoDecodeH265PictureInfo :: struct {
 }
 
 VideoDecodeH265ReferenceInfoFlags :: struct {
-	bit_field: u32,
+	bitfield: u32,
 }
 
 VideoDecodeH265ReferenceInfo :: struct {

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