瀏覽代碼

Merge branch 'master' into windows-llvm-13.0.0

gingerBill 3 年之前
父節點
當前提交
660aa7d78e
共有 87 個文件被更改,包括 5007 次插入656 次删除
  1. 5 3
      build.bat
  2. 13 1
      build_odin.sh
  3. 56 1
      core/compress/common.odin
  4. 19 0
      core/container/queue/queue.odin
  5. 1 0
      core/encoding/json/marshal.odin
  6. 18 3
      core/fmt/fmt.odin
  7. 60 2
      core/image/common.odin
  8. 341 2
      core/image/tga/tga.odin
  9. 2 0
      core/intrinsics/intrinsics.odin
  10. 1 1
      core/math/ease/ease.odin
  11. 6 0
      core/math/math.odin
  12. 1 42
      core/mem/mem.odin
  13. 3 3
      core/mem/virtual/growing_arena.odin
  14. 1 1
      core/mem/virtual/virtual_linux.odin
  15. 1 1
      core/os/os_darwin.odin
  16. 1 1
      core/os/os_linux.odin
  17. 1 1
      core/os/os_openbsd.odin
  18. 45 24
      core/reflect/reflect.odin
  19. 21 13
      core/runtime/core_builtin.odin
  20. 2 2
      core/slice/slice.odin
  21. 62 0
      core/slice/sort.odin
  22. 177 0
      core/slice/sort_private.odin
  23. 31 18
      core/strconv/strconv.odin
  24. 5 5
      core/strings/strings.odin
  25. 4 3
      core/sync/futex_darwin.odin
  26. 26 0
      core/sys/info/cpu_arm.odin
  27. 37 2
      core/sys/info/cpu_intel.odin
  28. 78 0
      core/sys/info/doc.odin
  29. 510 0
      core/sys/info/platform_darwin.odin
  30. 76 0
      core/sys/info/platform_freebsd.odin
  31. 136 0
      core/sys/info/platform_linux.odin
  32. 69 0
      core/sys/info/platform_openbsd.odin
  33. 375 0
      core/sys/info/platform_windows.odin
  34. 45 0
      core/sys/info/sysinfo.odin
  35. 7 0
      core/sys/unix/syscalls_freebsd.odin
  36. 3 3
      core/sys/unix/syscalls_linux.odin
  37. 6 0
      core/sys/unix/syscalls_openbsd.odin
  38. 45 0
      core/sys/unix/sysctl_darwin.odin
  39. 44 0
      core/sys/unix/sysctl_freebsd.odin
  40. 49 0
      core/sys/unix/sysctl_openbsd.odin
  41. 2 2
      core/sys/valgrind/callgrind.odin
  42. 178 0
      core/sys/valgrind/helgrind.odin
  43. 2 2
      core/sys/valgrind/memcheck.odin
  44. 4 4
      core/sys/valgrind/valgrind.odin
  45. 36 10
      core/sys/windows/kernel32.odin
  46. 171 0
      core/sys/windows/types.odin
  47. 151 0
      core/sys/windows/user32.odin
  48. 3 3
      core/sys/windows/util.odin
  49. 2 0
      core/sys/windows/winerror.odin
  50. 3 0
      examples/all/all_main.odin
  51. 639 313
      src/bug_report.cpp
  52. 112 63
      src/build_settings.cpp
  53. 50 5
      src/check_builtin.cpp
  54. 18 2
      src/check_expr.cpp
  55. 49 6
      src/check_stmt.cpp
  56. 4 2
      src/check_type.cpp
  57. 1 0
      src/checker.cpp
  58. 3 0
      src/checker_builtin_procs.hpp
  59. 4 0
      src/common.cpp
  60. 1 1
      src/llvm_abi.cpp
  61. 2 1
      src/llvm_backend.cpp
  62. 4 0
      src/llvm_backend_const.cpp
  63. 2 1
      src/llvm_backend_debug.cpp
  64. 22 12
      src/llvm_backend_expr.cpp
  65. 11 23
      src/llvm_backend_general.cpp
  66. 5 3
      src/llvm_backend_proc.cpp
  67. 13 3
      src/llvm_backend_stmt.cpp
  68. 14 0
      src/llvm_backend_utility.cpp
  69. 43 43
      src/main.cpp
  70. 50 1
      src/parser.cpp
  71. 3 4
      src/types.cpp
  72. 6 1
      tests/core/build.bat
  73. 182 0
      tests/core/slice/test_core_slice.odin
  74. 1 1
      tools/odinfmt/flag/flag.odin
  75. 1 1
      tools/odinfmt/main.odin
  76. 2 0
      vendor/OpenGL/constants.odin
  77. 1 1
      vendor/OpenGL/helpers.odin
  78. 1 1
      vendor/OpenGL/wrappers.odin
  79. 8 1
      vendor/README.md
  80. 170 0
      vendor/commonmark/LICENSE
  81. 4 0
      vendor/commonmark/build.bat
  82. 526 0
      vendor/commonmark/cmark.odin
  83. 二進制
      vendor/commonmark/cmark_static.lib
  84. 117 0
      vendor/commonmark/doc.odin
  85. 1 12
      vendor/darwin/Foundation/NSWindow.odin
  86. 1 1
      vendor/sdl2/sdl2.odin
  87. 1 1
      vendor/wasm/js/runtime.js

+ 5 - 3
build.bat

@@ -72,10 +72,12 @@ del *.ilk > NUL 2> NUL
 cl %compiler_settings% "src\main.cpp" "src\libtommath.cpp" /link %linker_settings% -OUT:%exe_name%
 if %errorlevel% neq 0 goto end_of_build
 
-call build_vendor.bat
-if %errorlevel% neq 0 goto end_of_build
+rem call build_vendor.bat
+rem if %errorlevel% neq 0 goto end_of_build
 
-if %release_mode% EQU 0 odin run examples/demo
+odin run examples/demo -vet -strict-style
+rem odin check examples/all
+rem odin run examples/bug
 
 del *.obj > NUL 2> NUL
 

+ 13 - 1
build_odin.sh

@@ -50,7 +50,19 @@ config_darwin() {
 }
 
 config_freebsd() {
-	: ${LLVM_CONFIG=/usr/local/bin/llvm-config11}
+	: ${LLVM_CONFIG=}
+
+	if [ ! "$LLVM_CONFIG" ]; then
+		if which llvm-config11 > /dev/null 2>&1; then
+			LLVM_CONFIG=llvm-config11
+		elif which llvm-config12 > /dev/null 2>&1; then
+			LLVM_CONFIG=llvm-config12
+		elif which llvm-config13 > /dev/null 2>&1; then
+			LLVM_CONFIG=llvm-config13
+		else
+			panic "Unable to find LLVM-config"
+		fi
+	fi
 
 	CXXFLAGS="$CXXFLAGS $($LLVM_CONFIG --cxxflags --ldflags)"
 	LDFLAGS="$LDFLAGS $($LLVM_CONFIG --libs core native --system-libs)"

+ 56 - 1
core/compress/common.odin

@@ -294,6 +294,24 @@ peek_data_from_memory :: #force_inline proc(z: ^Context_Memory_Input, $T: typeid
 	}
 }
 
+@(optimization_mode="speed")
+peek_data_at_offset_from_memory :: #force_inline proc(z: ^Context_Memory_Input, $T: typeid, #any_int offset: int) -> (res: T, err: io.Error) {
+	size :: size_of(T)
+
+	#no_bounds_check {
+		if len(z.input_data) >= size + offset {
+			buf := z.input_data[offset:][:size]
+			return (^T)(&buf[0])^, .None
+		}
+	}
+
+	if len(z.input_data) == 0 {
+		return T{}, .EOF
+	} else {
+		return T{}, .Short_Buffer
+	}
+}
+
 @(optimization_mode="speed")
 peek_data_from_stream :: #force_inline proc(z: ^Context_Stream_Input, $T: typeid) -> (res: T, err: io.Error) {
 	size :: size_of(T)
@@ -321,7 +339,44 @@ peek_data_from_stream :: #force_inline proc(z: ^Context_Stream_Input, $T: typeid
 	return res, .None
 }
 
-peek_data :: proc{peek_data_from_memory, peek_data_from_stream}
+@(optimization_mode="speed")
+peek_data_at_offset_from_stream :: #force_inline proc(z: ^Context_Stream_Input, $T: typeid, #any_int offset: int) -> (res: T, err: io.Error) {
+	size :: size_of(T)
+
+	// Get current position to return to.
+	cur_pos, e1 := z.input->impl_seek(0, .Current)
+	if e1 != .None {
+		return T{}, e1
+	}
+
+	// Seek to offset.
+	pos, e2 := z.input->impl_seek(offset, .Start)
+	if e2 != .None {
+		return T{}, e2
+	}
+
+	r, e3 := io.to_reader_at(z.input)
+	if !e3 {
+		return T{}, .Empty
+	}
+	when size <= 128 {
+		b: [size]u8
+	} else {
+		b := make([]u8, size, context.temp_allocator)
+	}
+	_, e4 := io.read_at(r, b[:], pos)
+	if e4 != .None {
+		return T{}, .Empty
+	}
+
+	// Return read head to original position.
+	z.input->impl_seek(cur_pos, .Start)
+
+	res = (^T)(&b[0])^
+	return res, .None
+}
+
+peek_data :: proc{peek_data_from_memory, peek_data_from_stream, peek_data_at_offset_from_memory, peek_data_at_offset_from_stream}
 
 
 

+ 19 - 0
core/container/queue/queue.odin

@@ -73,11 +73,18 @@ get :: proc(q: ^$Q/Queue($T), #any_int i: int, loc := #caller_location) -> T {
 front :: proc(q: ^$Q/Queue($T)) -> T {
 	return q.data[q.offset]
 }
+front_ptr :: proc(q: ^$Q/Queue($T)) -> ^T {
+	return &q.data[q.offset]
+}
 
 back :: proc(q: ^$Q/Queue($T)) -> T {
 	idx := (q.offset+uint(q.len))%builtin.len(q.data)
 	return q.data[idx]
 }
+back_ptr :: proc(q: ^$Q/Queue($T)) -> ^T {
+	idx := (q.offset+uint(q.len))%builtin.len(q.data)
+	return &q.data[idx]
+}
 
 set :: proc(q: ^$Q/Queue($T), #any_int i: int, val: T, loc := #caller_location) {
 	runtime.bounds_check_error_loc(loc, i, builtin.len(q.data))
@@ -92,6 +99,18 @@ get_ptr :: proc(q: ^$Q/Queue($T), #any_int i: int, loc := #caller_location) -> ^
 	return &q.data[idx]
 }
 
+peek_front :: proc(q: ^$Q/Queue($T), loc := #caller_location) -> ^T {
+	runtime.bounds_check_error_loc(loc, 0, builtin.len(q.data))
+	idx := q.offset%builtin.len(q.data)
+	return &q.data[idx]
+}
+
+peek_back :: proc(q: ^$Q/Queue($T), loc := #caller_location) -> ^T {
+	runtime.bounds_check_error_loc(loc, int(q.len - 1), builtin.len(q.data))
+	idx := (uint(q.len - 1)+q.offset)%builtin.len(q.data)
+	return &q.data[idx]
+}
+
 // Push an element to the back of the queue
 push_back :: proc(q: ^$Q/Queue($T), elem: T) -> bool {
 	if space(q^) == 0 {

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

@@ -85,6 +85,7 @@ marshal_to_writer :: proc(w: io.Writer, v: any, opt: ^Marshal_Options) -> (err:
 		case i16:     u = u128(i)
 		case i32:     u = u128(i)
 		case i64:     u = u128(i)
+		case i128:    u = u128(i)
 		case int:     u = u128(i)
 		case u8:      u = u128(i)
 		case u16:     u = u128(i)

+ 18 - 3
core/fmt/fmt.odin

@@ -2059,18 +2059,33 @@ fmt_value :: proc(fi: ^Info, v: any, verb: rune) {
 			ed         := runtime.type_info_base(gs.types[1]).variant.(runtime.Type_Info_Dynamic_Array)
 			entry_type := ed.elem.variant.(runtime.Type_Info_Struct)
 			entry_size := ed.elem_size
-
+			/*
+				NOTE: The layout of a `map` is as follows:
+
+					map[Key]Value
+
+				## Internal Layout
+				struct {
+					hashes: []int,
+					entries: [dynamic]struct{
+						hash:  uintptr,
+						next:  int,
+						key:   Key,
+						value: Value,
+					},
+				}
+			*/
 			for i in 0..<entries.len {
 				if i > 0 { io.write_string(fi.writer, ", ", &fi.n) }
 
 				data := uintptr(entries.data) + uintptr(i*entry_size)
 
-				key := data + entry_type.offsets[2]
+				key := data + entry_type.offsets[2] // key: Key
 				fmt_arg(&Info{writer = fi.writer}, any{rawptr(key), info.key.id}, 'v')
 
 				io.write_string(fi.writer, "=", &fi.n)
 
-				value := data + entry_type.offsets[3]
+				value := data + entry_type.offsets[3] // value: Value
 				fmt_arg(fi, any{rawptr(value), info.value.id}, 'v')
 			}
 		}

+ 60 - 2
core/image/common.odin

@@ -46,7 +46,7 @@ Image :: struct {
 	height:        int,
 	channels:      int,
 	depth:         int, // Channel depth in bits, typically 8 or 16
-	pixels:        bytes.Buffer,
+	pixels:        bytes.Buffer `fmt:"-"`,
 	/*
 		Some image loaders/writers can return/take an optional background color.
 		For convenience, we return them as u16 so we don't need to switch on the type
@@ -61,6 +61,7 @@ Image_Metadata :: union #shared_nil {
 	^Netpbm_Info,
 	^PNG_Info,
 	^QOI_Info,
+	^TGA_Info,
 }
 
 
@@ -168,6 +169,7 @@ Error :: union #shared_nil {
 
 General_Image_Error :: enum {
 	None = 0,
+	Unsupported_Option,
 	// File I/O
 	Unable_To_Read_File,
 	Unable_To_Write_File,
@@ -376,10 +378,20 @@ QOI_Info :: struct {
 	header: QOI_Header,
 }
 
+TGA_Data_Type :: enum u8  {
+	No_Image_Data             = 0,
+	Uncompressed_Color_Mapped = 1,
+	Uncompressed_RGB          = 2,
+	Uncompressed_Black_White  = 3,
+	Compressed_Color_Mapped   = 9,
+	Compressed_RGB            = 10,
+	Compressed_Black_White    = 11,
+}
+
 TGA_Header :: struct #packed {
 	id_length:        u8,
 	color_map_type:   u8,
-	data_type_code:   u8,
+	data_type_code:   TGA_Data_Type,
 	color_map_origin: u16le,
 	color_map_length: u16le,
 	color_map_depth:  u8,
@@ -390,6 +402,52 @@ TGA_Header :: struct #packed {
 }
 #assert(size_of(TGA_Header) == 18)
 
+New_TGA_Signature :: "TRUEVISION-XFILE.\x00"
+
+TGA_Footer :: struct #packed {
+	extension_area_offset:      u32le,
+	developer_directory_offset: u32le,
+	signature:                  [18]u8 `fmt:"s,0"`, // Should match signature if New TGA.
+}
+#assert(size_of(TGA_Footer) == 26)
+
+TGA_Extension :: struct #packed {
+	extension_size:          u16le,               // Size of this struct. If not 495 bytes it means it's an unsupported version.
+	author_name:             [41]u8  `fmt:"s,0"`, // Author name, ASCII. Zero-terminated
+	author_comments:         [324]u8 `fmt:"s,0"`, // Author comments, formatted as 4 lines of 80 character lines, each zero terminated.
+	datetime:                struct {month, day, year, hour, minute, second: u16le},
+	job_name:                [41]u8  `fmt:"s,0"`, // Author name, ASCII. Zero-terminated
+	job_time:                struct {hour, minute, second: u16le},
+	software_id:             [41]u8  `fmt:"s,0"`, // Software ID name, ASCII. Zero-terminated
+	software_version: struct #packed {
+		number: u16le, // Version number * 100
+		letter: u8 `fmt:"r"`,   // " " if not used
+	},
+	key_color:               [4]u8,    // ARGB key color used at time of production
+	aspect_ratio:            [2]u16le, // Numerator / Denominator
+	gamma:                   [2]u16le, // Numerator / Denominator, range should be 0.0..10.0
+	color_correction_offset: u32le,    // 0 if no color correction information
+	postage_stamp_offset:    u32le,    // 0 if no thumbnail
+	scanline_offset:         u32le,    // 0 if no scanline table
+	attributes:              TGA_Alpha_Kind,
+}
+#assert(size_of(TGA_Extension) == 495)
+
+TGA_Alpha_Kind :: enum u8 {
+	None,
+	Undefined_Ignore,
+	Undefined_Retain,
+	Useful,
+	Premultiplied,
+}
+
+TGA_Info :: struct {
+	header:    TGA_Header,
+	image_id:  string,
+	footer:    Maybe(TGA_Footer),
+	extension: Maybe(TGA_Extension),
+}
+
 // Function to help with image buffer calculations
 compute_buffer_size :: proc(width, height, channels, depth: int, extra_row_bytes := int(0)) -> (size: int) {
 	size = ((((channels * width * depth) + 7) >> 3) + extra_row_bytes) * height

+ 341 - 2
core/image/tga/tga.odin

@@ -4,6 +4,7 @@
 
 	List of contributors:
 		Jeroen van Rijn: Initial implementation.
+		Benoit Jacquier: tga loader
 */
 
 
@@ -14,11 +15,16 @@ import "core:mem"
 import "core:image"
 import "core:bytes"
 import "core:os"
+import "core:compress"
+import "core:strings"
+
+// TODO: alpha_premultiply support
 
 Error   :: image.Error
 Image   :: image.Image
 Options :: image.Options
 
+GA_Pixel   :: image.GA_Pixel
 RGB_Pixel  :: image.RGB_Pixel
 RGBA_Pixel :: image.RGBA_Pixel
 
@@ -57,7 +63,7 @@ save_to_memory  :: proc(output: ^bytes.Buffer, img: ^Image, options := Options{}
 	}
 
 	header := image.TGA_Header{
-		data_type_code   = 0x02, // Color, uncompressed.
+		data_type_code   = .Uncompressed_RGB,
 		dimensions       = {u16le(img.width), u16le(img.height)},
 		bits_per_pixel   = u8(img.depth * img.channels),
 		image_descriptor = 1 << 5, // Origin is top left.
@@ -98,4 +104,337 @@ save_to_file :: proc(output: string, img: ^Image, options := Options{}, allocato
 	return nil if write_ok else .Unable_To_Write_File
 }
 
-save :: proc{save_to_memory, save_to_file}
+save :: proc{save_to_memory, save_to_file}
+
+load_from_context :: proc(ctx: ^$C, options := Options{}, allocator := context.allocator) -> (img: ^Image, err: Error) {
+	context.allocator = allocator
+	options := options
+
+	if .alpha_premultiply in options {
+		return nil, .Unsupported_Option
+	}
+
+	if .info in options {
+		options |= {.return_metadata, .do_not_decompress_image}
+		options -= {.info}
+	}
+
+	if .return_header in options && .return_metadata in options {
+		options -= {.return_header}
+	}
+
+	// First check for a footer.
+	filesize := compress.input_size(ctx) or_return
+
+	footer: image.TGA_Footer
+	have_valid_footer := false
+
+	extension: image.TGA_Extension
+	have_valid_extension := false
+
+	if filesize >= size_of(image.TGA_Header) + size_of(image.TGA_Footer) {
+		if f, f_err := compress.peek_data(ctx, image.TGA_Footer, filesize - i64(size_of(image.TGA_Footer))); f_err == .None {
+			if string(f.signature[:]) == image.New_TGA_Signature {
+				have_valid_footer = true
+				footer = f
+
+				if i64(footer.extension_area_offset) + i64(size_of(image.TGA_Extension)) < filesize {
+					if e, e_err := compress.peek_data(ctx, image.TGA_Extension, footer.extension_area_offset); e_err == .None {
+						if e.extension_size == size_of(image.TGA_Extension) {
+							have_valid_extension = true
+							extension = e
+						}
+					}
+				}
+			}
+		}
+	}
+
+	header := image.read_data(ctx, image.TGA_Header) or_return
+
+	// Header checks
+	rle_encoding  := false
+	color_mapped  := false
+	black_white   := false
+	src_channels  := 0
+	dest_depth    := header.bits_per_pixel
+	dest_channels := 0
+
+	#partial switch header.data_type_code {
+	// Supported formats: RGB(A), RGB(A) RLE
+	case .Compressed_RGB:
+		rle_encoding = true
+	case .Uncompressed_RGB:
+		// Intentionally blank
+	case .Uncompressed_Black_White:
+		black_white  = true
+		dest_depth   = 24
+	case .Uncompressed_Color_Mapped:
+		color_mapped = true
+	case .Compressed_Color_Mapped:
+		color_mapped = true
+		rle_encoding = true
+	case .Compressed_Black_White:
+		black_white  = true
+		rle_encoding = true
+		dest_depth   = 24
+
+	case:
+		return nil, .Unsupported_Format
+	}
+
+	if color_mapped {
+		if header.color_map_type != 1 {
+			return nil, .Unsupported_Format
+		}
+		dest_depth = header.color_map_depth
+
+		// Expect LUT entry index to be 8 bits
+		if header.bits_per_pixel != 8 || header.color_map_origin != 0 || header.color_map_length > 256 {
+			return nil, .Unsupported_Format
+		}
+	}
+
+	switch dest_depth {
+	case 15: // B5G5R5
+		src_channels  = 2
+		dest_channels = 3
+		if color_mapped {
+			src_channels = 1
+		}
+	case 16: // B5G5R5A1
+		src_channels  = 2
+		dest_channels = 3 // Alpha bit is dodgy in TGA, so we ignore it.
+		if color_mapped {
+			src_channels = 1
+		}
+	case 24: // RGB8
+		src_channels  = 1 if (color_mapped || black_white) else 3
+		dest_channels = 3
+	case 32: // RGBA8
+		src_channels  = 4 if !color_mapped else 1
+		dest_channels = 4
+
+	case:
+		return nil, .Unsupported_Format
+	}
+
+	if header.image_descriptor & IMAGE_DESCRIPTOR_INTERLEAVING_MASK != 0 {
+		return nil, .Unsupported_Format
+	}
+
+	if int(header.dimensions[0]) * int(header.dimensions[1]) > image.MAX_DIMENSIONS {
+		return nil, .Image_Dimensions_Too_Large
+	}
+
+	if img == nil {
+		img = new(Image)
+	}
+
+	defer if err != nil {
+		destroy(img)
+	}
+
+	img.which = .TGA
+	img.channels = 4 if .alpha_add_if_missing  in options else dest_channels
+	img.channels = 3 if .alpha_drop_if_present in options else img.channels
+
+	img.depth  = 8
+	img.width  = int(header.dimensions[0])
+	img.height = int(header.dimensions[1])
+
+	// Read Image ID if present
+	image_id := ""
+	if _id, e := compress.read_slice(ctx, int(header.id_length)); e != .None {
+		return img, .Corrupt
+	} else {
+		if .return_metadata in options {
+			id := strings.trim_right_null(string(_id))
+			image_id = strings.clone(id)
+		}
+	}
+
+	color_map := make([]RGBA_Pixel, header.color_map_length)
+	defer delete(color_map)
+
+	if color_mapped {
+		switch header.color_map_depth {
+		case 16:
+			for i in 0..<header.color_map_length {
+				if lut, lut_err := compress.read_data(ctx, GA_Pixel); lut_err != .None {
+					return img, .Corrupt
+				} else {
+					color_map[i].rg = lut
+					color_map[i].ba = 255
+				}
+			}
+
+		case 24:
+			for i in 0..<header.color_map_length {
+				if lut, lut_err := compress.read_data(ctx, RGB_Pixel); lut_err != .None {
+					return img, .Corrupt
+				} else {
+					color_map[i].rgb = lut
+					color_map[i].a   = 255
+				}
+			}
+
+		case 32:
+			for i in 0..<header.color_map_length {
+				if lut, lut_err := compress.read_data(ctx, RGBA_Pixel); lut_err != .None {
+					return img, .Corrupt
+				} else {
+					color_map[i] = lut
+				}
+			}
+		}
+	}
+
+	if .return_metadata in options {
+		info := new(image.TGA_Info)
+		info.header   = header
+		info.image_id = image_id
+		if have_valid_footer {
+			info.footer = footer
+		}
+		if have_valid_extension {
+			info.extension = extension
+		}
+		img.metadata = info
+	}
+
+	if .do_not_decompress_image in options {
+		return img, nil
+	}
+
+	if !resize(&img.pixels.buf, dest_channels * img.width * img.height) {
+		return img, .Unable_To_Allocate_Or_Resize
+	}
+
+	origin_is_top        := header.image_descriptor & IMAGE_DESCRIPTOR_TOP_MASK   != 0
+	origin_is_left       := header.image_descriptor & IMAGE_DESCRIPTOR_RIGHT_MASK == 0
+	rle_repetition_count := 0
+	read_pixel           := true
+	is_packet_rle        := false
+
+	pixel: RGBA_Pixel
+
+	stride := img.width * dest_channels
+	line   := 0 if origin_is_top else img.height - 1
+
+	for _ in 0..<img.height {
+		offset := line * stride + (0 if origin_is_left else (stride - dest_channels))
+		for _ in 0..<img.width {
+			// handle RLE decoding
+			if rle_encoding {
+				if rle_repetition_count == 0 {
+					rle_cmd, err := compress.read_u8(ctx)
+					if err != .None {
+						return img, .Corrupt
+					}
+					is_packet_rle = (rle_cmd >> 7) != 0
+					rle_repetition_count = 1 + int(rle_cmd & 0x7F)
+					read_pixel = true
+				} else if !is_packet_rle {
+					read_pixel = rle_repetition_count > 0
+				} else {
+					read_pixel = false
+				}
+			}
+			// Read pixel
+			if read_pixel {
+				src, src_err := compress.read_slice(ctx, src_channels)
+				if src_err != .None {
+					return img, .Corrupt
+				}
+				switch src_channels {
+				case 1:
+					// Color-mapped or Black & White
+					if black_white {
+						pixel = {src[0], src[0], src[0], 255}
+					} else if header.color_map_depth == 24 {
+						pixel = color_map[src[0]].bgra
+					} else if header.color_map_depth == 16 {
+						lut := color_map[src[0]]
+						v := u16(lut.r) | u16(lut.g) << 8
+						b := u8( v        & 31) << 3
+						g := u8((v >>  5) & 31) << 3
+						r := u8((v >> 10) & 31) << 3
+						pixel = {r, g, b, 255}
+					}
+
+				case 2:
+					v := u16(src[0]) | u16(src[1]) << 8
+					b := u8( v        & 31) << 3
+					g := u8((v >>  5) & 31) << 3
+					r := u8((v >> 10) & 31) << 3
+					pixel = {r, g, b, 255}
+
+				case 3:
+					pixel = {src[2], src[1], src[0], 255}
+				case 4:
+					pixel = {src[2], src[1], src[0], src[3]}
+				case:
+					return img, .Corrupt
+				}
+			}
+
+			// Write pixel
+			copy(img.pixels.buf[offset:], pixel[:dest_channels])
+			offset += dest_channels if origin_is_left else -dest_channels
+			rle_repetition_count -= 1
+		}
+		line += 1 if origin_is_top else -1
+	}
+	return img, nil
+}
+
+load_from_bytes :: proc(data: []byte, options := Options{}, allocator := context.allocator) -> (img: ^Image, err: Error) {
+	ctx := &compress.Context_Memory_Input{
+		input_data = data,
+	}
+
+	img, err = load_from_context(ctx, options, allocator)
+	return img, err
+}
+
+load_from_file :: proc(filename: string, options := Options{}, allocator := context.allocator) -> (img: ^Image, err: Error) {
+	context.allocator = allocator
+
+	data, ok := os.read_entire_file(filename)
+	defer delete(data)
+
+	if ok {
+		return load_from_bytes(data, options)
+	} else {
+		return nil, .Unable_To_Read_File
+	}
+}
+
+load :: proc{load_from_file, load_from_bytes, load_from_context}
+
+destroy :: proc(img: ^Image) {
+	if img == nil || img.width == 0 || img.height == 0 {
+		return
+	}
+
+	bytes.buffer_destroy(&img.pixels)
+	if v, ok := img.metadata.(^image.TGA_Info); ok {
+		delete(v.image_id)
+		free(v)
+	}
+
+	// Make destroy idempotent
+	img.width  = 0
+	img.height = 0
+	free(img)
+}
+
+IMAGE_DESCRIPTOR_INTERLEAVING_MASK :: (1<<6) | (1<<7)
+IMAGE_DESCRIPTOR_RIGHT_MASK :: 1<<4
+IMAGE_DESCRIPTOR_TOP_MASK   :: 1<<5
+
+@(init, private)
+_register :: proc() {
+	image.register(.TGA, load_from_bytes, destroy)
+}

+ 2 - 0
core/intrinsics/intrinsics.odin

@@ -188,6 +188,8 @@ type_field_index_of :: proc($T: typeid, $name: string) -> uintptr ---
 type_equal_proc  :: proc($T: typeid) -> (equal:  proc "contextless" (rawptr, rawptr) -> bool)                 where type_is_comparable(T) ---
 type_hasher_proc :: proc($T: typeid) -> (hasher: proc "contextless" (data: rawptr, seed: uintptr) -> uintptr) where type_is_comparable(T) ---
 
+type_convert_variants_to_pointers :: proc($T: typeid) -> typeid where type_is_union(T) ---
+
 constant_utf16_cstring :: proc($literal: string) -> [^]u16 ---
 
 // SIMD related

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

@@ -356,7 +356,7 @@ Flux_Tween :: struct($T: typeid) {
 flux_init :: proc($T: typeid, value_capacity := 8) -> Flux_Map(T) where intrinsics.type_is_float(T) {
 	return {
 		values = make(map[^T]Flux_Tween(T), value_capacity),
-		keys_to_be_deleted = make([dynamic]^T, 0, value_capacity)
+		keys_to_be_deleted = make([dynamic]^T, 0, value_capacity),
 	}
 }
 

+ 6 - 0
core/math/math.odin

@@ -1,6 +1,7 @@
 package math
 
 import "core:intrinsics"
+import "core:builtin"
 _ :: intrinsics
 
 Float_Class :: enum {
@@ -36,6 +37,11 @@ MAX_F16_PRECISION ::  4 // Maximum number of meaningful digits after the decimal
 RAD_PER_DEG :: TAU/360.0
 DEG_PER_RAD :: 360.0/TAU
 
+abs :: builtin.abs
+min :: builtin.min
+max :: builtin.max
+clamp :: builtin.clamp
+
 sqrt_f16le :: proc "contextless" (x: f16le) -> f16le { return #force_inline f16le(sqrt_f16(f16(x))) }
 sqrt_f16be :: proc "contextless" (x: f16be) -> f16be { return #force_inline f16be(sqrt_f16(f16(x))) }
 sqrt_f32le :: proc "contextless" (x: f32le) -> f32le { return #force_inline f32le(sqrt_f32(f32(x))) }

+ 1 - 42
core/mem/mem.odin

@@ -54,48 +54,7 @@ compare :: proc "contextless" (a, b: []byte) -> int {
 }
 
 compare_byte_ptrs :: proc "contextless" (a, b: ^byte, n: int) -> int #no_bounds_check {
-	switch {
-	case a == b:
-		return 0
-	case a == nil:
-		return -1
-	case b == nil:
-		return -1
-	case n == 0:
-		return 0
-	}
-
-	x := slice_ptr(a, n)
-	y := slice_ptr(b, n)
-
-	SU :: size_of(uintptr)
-	fast := n/SU + 1
-	offset := (fast-1)*SU
-	curr_block := 0
-	if n < SU {
-		fast = 0
-	}
-
-	la := slice_ptr((^uintptr)(a), fast)
-	lb := slice_ptr((^uintptr)(b), fast)
-
-	for /**/; curr_block < fast; curr_block += 1 {
-		if la[curr_block] ~ lb[curr_block] != 0 {
-			for pos := curr_block*SU; pos < n; pos += 1 {
-				if x[pos] ~ y[pos] != 0 {
-					return (int(x[pos]) - int(y[pos])) < 0 ? -1 : +1
-				}
-			}
-		}
-	}
-
-	for /**/; offset < n; offset += 1 {
-		if x[offset] ~ y[offset] != 0 {
-			return (int(x[offset]) - int(y[offset])) < 0 ? -1 : +1
-		}
-	}
-
-	return 0
+	return runtime.memory_compare(a, b, n)
 }
 
 check_zero :: proc(data: []byte) -> bool {

+ 3 - 3
core/mem/virtual/growing_arena.odin

@@ -13,10 +13,10 @@ Growing_Arena :: struct {
 
 DEFAULT_MINIMUM_BLOCK_SIZE :: 1<<20 // 1 MiB should be enough
 
-growing_arena_init :: proc(arena: ^Static_Arena, reserved: uint = DEFAULT_MINIMUM_BLOCK_SIZE) -> (err: Allocator_Error) {
-	arena.block = memory_block_alloc(0, reserved, {}) or_return
+growing_arena_init :: proc(arena: ^Growing_Arena, reserved: uint = DEFAULT_MINIMUM_BLOCK_SIZE) -> (err: Allocator_Error) {
+	arena.curr_block = memory_block_alloc(0, reserved, {}) or_return
 	arena.total_used = 0
-	arena.total_reserved = arena.block.reserved
+	arena.total_reserved = arena.curr_block.reserved
 	return
 }
 

+ 1 - 1
core/mem/virtual/virtual_linux.odin

@@ -48,7 +48,7 @@ munmap :: proc "contextless" (addr: rawptr, length: uint) -> c.int {
 }
 
 mprotect :: proc "contextless" (addr: rawptr, length: uint, prot: c.int) -> c.int {
-	res := intrinsics.syscall(unix.SYS_mprotect, uintptr(addr), uintptr(length), uint(prot))
+	res := intrinsics.syscall(unix.SYS_mprotect, uintptr(addr), uintptr(length), uintptr(prot))
 	return c.int(res)
 }
 

+ 1 - 1
core/os/os_darwin.odin

@@ -596,7 +596,7 @@ absolute_path_from_handle :: proc(fd: Handle) -> (string, Errno) {
 		return "", Errno(get_last_error())
 	}
 
-	path := strings.clone_from_cstring(cstring(&buf[0]), context.temp_allocator)
+	path := strings.clone_from_cstring(cstring(&buf[0]))
 	return path, ERROR_NONE
 }
 

+ 1 - 1
core/os/os_linux.odin

@@ -311,7 +311,7 @@ _unix_seek :: proc(fd: Handle, offset: i64, whence: int) -> i64 {
 		low := uintptr(offset & 0xFFFFFFFF)
 		high := uintptr(offset >> 32)
 		result: i64
-		res := i64(intrinsics.syscall(unix.SYS__llseek, uintptr(fd), high, low, &result, uintptr(whence)))
+		res := i64(intrinsics.syscall(unix.SYS__llseek, uintptr(fd), high, low, uintptr(&result), uintptr(whence)))
 		return -1 if res < 0 else result
 	}
 }

+ 1 - 1
core/os/os_openbsd.odin

@@ -705,4 +705,4 @@ _alloc_command_line_arguments :: proc() -> []string {
 		res[i] = string(arg)
 	}
 	return res
-}
+}

+ 45 - 24
core/reflect/reflect.odin

@@ -1,8 +1,9 @@
 package reflect
 
 import "core:runtime"
-import "core:mem"
 import "core:intrinsics"
+import "core:mem"
+_ :: mem
 _ :: intrinsics
 
 Type_Info :: runtime.Type_Info
@@ -224,7 +225,7 @@ align_of_typeid :: proc(T: typeid) -> int {
 as_bytes :: proc(v: any) -> []byte {
 	if v != nil {
 		sz := size_of_typeid(v.id)
-		return mem.slice_ptr((^byte)(v.data), sz)
+		return ([^]byte)(v.data)[:sz]
 	}
 	return nil
 }
@@ -266,19 +267,19 @@ length :: proc(val: any) -> int {
 		return a.count
 
 	case Type_Info_Slice:
-		return (^mem.Raw_Slice)(val.data).len
+		return (^runtime.Raw_Slice)(val.data).len
 
 	case Type_Info_Dynamic_Array:
-		return (^mem.Raw_Dynamic_Array)(val.data).len
+		return (^runtime.Raw_Dynamic_Array)(val.data).len
 
 	case Type_Info_Map:
-		return (^mem.Raw_Map)(val.data).entries.len
+		return (^runtime.Raw_Map)(val.data).entries.len
 
 	case Type_Info_String:
 		if a.is_cstring {
 			return len((^cstring)(val.data)^)
 		} else {
-			return (^mem.Raw_String)(val.data).len
+			return (^runtime.Raw_String)(val.data).len
 		}
 	}
 	return 0
@@ -301,10 +302,10 @@ capacity :: proc(val: any) -> int {
 		return a.count
 
 	case Type_Info_Dynamic_Array:
-		return (^mem.Raw_Dynamic_Array)(val.data).cap
+		return (^runtime.Raw_Dynamic_Array)(val.data).cap
 
 	case Type_Info_Map:
-		return (^mem.Raw_Map)(val.data).entries.cap
+		return (^runtime.Raw_Map)(val.data).entries.cap
 	}
 	return 0
 }
@@ -344,14 +345,14 @@ index :: proc(val: any, i: int, loc := #caller_location) -> any {
 		return any{data, a.elem.id}
 
 	case Type_Info_Slice:
-		raw := (^mem.Raw_Slice)(val.data)
+		raw := (^runtime.Raw_Slice)(val.data)
 		runtime.bounds_check_error_loc(loc, i, raw.len)
 		offset := uintptr(a.elem.size * i)
 		data := rawptr(uintptr(raw.data) + offset)
 		return any{data, a.elem.id}
 
 	case Type_Info_Dynamic_Array:
-		raw := (^mem.Raw_Dynamic_Array)(val.data)
+		raw := (^runtime.Raw_Dynamic_Array)(val.data)
 		runtime.bounds_check_error_loc(loc, i, raw.len)
 		offset := uintptr(a.elem.size * i)
 		data := rawptr(uintptr(raw.data) + offset)
@@ -360,7 +361,7 @@ index :: proc(val: any, i: int, loc := #caller_location) -> any {
 	case Type_Info_String:
 		if a.is_cstring { return nil }
 
-		raw := (^mem.Raw_String)(val.data)
+		raw := (^runtime.Raw_String)(val.data)
 		runtime.bounds_check_error_loc(loc, i, raw.len)
 		offset := uintptr(size_of(u8) * i)
 		data := rawptr(uintptr(raw.data) + offset)
@@ -729,6 +730,26 @@ get_union_variant_raw_tag :: proc(a: any) -> i64 {
 	panic("expected a union to reflect.get_union_variant_raw_tag")
 }
 
+get_union_variant :: proc(a: any) -> any {
+	if a == nil {
+		return nil
+	}
+	id := union_variant_typeid(a)
+	if id == nil {
+		return nil
+	}
+	return any{a.data, id}
+}
+
+get_union_as_ptr_variants :: proc(val: ^$T) -> (res: intrinsics.type_convert_variants_to_pointers(T)) where intrinsics.type_is_union(T) {
+	ptr := rawptr(val)
+	tag := get_union_variant_raw_tag(val^)
+	mem.copy(&res, &ptr, size_of(ptr))
+	set_union_variant_raw_tag(res, tag)
+	return
+}
+
+
 
 set_union_variant_raw_tag :: proc(a: any, tag: i64) {
 	if a == nil { return }
@@ -826,17 +847,17 @@ set_union_value :: proc(dst: any, value: any) -> bool {
 	ti := runtime.type_info_base(type_info_of(dst.id))
 	if info, ok := ti.variant.(runtime.Type_Info_Union); ok {
 		if value.id == nil {
-			mem.zero(dst.data, ti.size)
+			intrinsics.mem_zero(dst.data, ti.size)
 			return true
 		}
 		if ti.id == runtime.typeid_base(value.id) {
-			mem.copy(dst.data, value.data, ti.size)
+			intrinsics.mem_copy(dst.data, value.data, ti.size)
 			return true
 		}
 		
 		if type_info_union_is_pure_maybe(info) {
 			if variant := info.variants[0]; variant.id == value.id {
-				mem.copy(dst.data, value.data, variant.size)
+				intrinsics.mem_copy(dst.data, value.data, variant.size)
 				return true
 			}
 			return false
@@ -848,7 +869,7 @@ set_union_value :: proc(dst: any, value: any) -> bool {
 				if !info.no_nil {
 					tag += 1
 				}
-				mem.copy(dst.data, value.data, variant.size)
+				intrinsics.mem_copy(dst.data, value.data, variant.size)
 				set_union_variant_raw_tag(dst, tag)
 				return true
 			}
@@ -1341,11 +1362,11 @@ as_raw_data :: proc(a: any) -> (value: rawptr, valid: bool) {
 
 	case Type_Info_Slice:
 		valid = true
-		value = (^mem.Raw_Slice)(a.data).data
+		value = (^runtime.Raw_Slice)(a.data).data
 
 	case Type_Info_Dynamic_Array:
 		valid = true
-		value = (^mem.Raw_Dynamic_Array)(a.data).data
+		value = (^runtime.Raw_Dynamic_Array)(a.data).data
 	}
 
 	return
@@ -1387,7 +1408,7 @@ equal :: proc(a, b: any, including_indirect_array_recursion := false, recursion_
 	}
 
 	if .Simple_Compare in t.flags {
-		return mem.compare_byte_ptrs((^byte)(a.data), (^byte)(b.data), t.size) == 0
+		return runtime.memory_compare(a.data, b.data, t.size) == 0
 	}
 	
 	t = runtime.type_info_core(t)
@@ -1425,7 +1446,7 @@ equal :: proc(a, b: any, including_indirect_array_recursion := false, recursion_
 		Type_Info_Relative_Pointer,
 		Type_Info_Soa_Pointer,
 		Type_Info_Matrix:
-		return mem.compare_byte_ptrs((^byte)(a.data), (^byte)(b.data), t.size) == 0
+		return runtime.memory_compare(a.data, b.data, t.size) == 0
 		
 	case Type_Info_String:
 		if v.is_cstring {
@@ -1479,8 +1500,8 @@ equal :: proc(a, b: any, including_indirect_array_recursion := false, recursion_
 		if !including_indirect_array_recursion {
 			return false
 		}
-		array_a := (^mem.Raw_Slice)(a.data)
-		array_b := (^mem.Raw_Slice)(b.data)
+		array_a := (^runtime.Raw_Slice)(a.data)
+		array_b := (^runtime.Raw_Slice)(b.data)
 		if array_a.len != array_b.len {
 			return false
 		}
@@ -1499,8 +1520,8 @@ equal :: proc(a, b: any, including_indirect_array_recursion := false, recursion_
 		if !including_indirect_array_recursion {
 			return false
 		}
-		array_a := (^mem.Raw_Dynamic_Array)(a.data)
-		array_b := (^mem.Raw_Dynamic_Array)(b.data)
+		array_a := (^runtime.Raw_Dynamic_Array)(a.data)
+		array_b := (^runtime.Raw_Dynamic_Array)(b.data)
 		if array_a.len != array_b.len {
 			return false
 		}
@@ -1508,7 +1529,7 @@ equal :: proc(a, b: any, including_indirect_array_recursion := false, recursion_
 			return true
 		}
 		if .Simple_Compare in v.elem.flags {
-			return mem.compare_byte_ptrs((^byte)(array_a.data), (^byte)(array_b.data), array_a.len * v.elem.size) == 0
+			return runtime.memory_compare((^byte)(array_a.data), (^byte)(array_b.data), array_a.len * v.elem.size) == 0
 		}
 		
 		for i in 0..<array_a.len {

+ 21 - 13
core/runtime/core_builtin.odin

@@ -342,12 +342,13 @@ delete_key :: proc(m: ^$T/map[$K]$V, key: K) -> (deleted_key: K, deleted_value:
 
 
 @builtin
-append_elem :: proc(array: ^$T/[dynamic]$E, arg: E, loc := #caller_location)  {
+append_elem :: proc(array: ^$T/[dynamic]$E, arg: E, loc := #caller_location) -> int {
 	if array == nil {
-		return
+		return 0
 	}
 	when size_of(E) == 0 {
-		a.len += 1
+		array.len += 1
+		return 1
 	} else {
 		if cap(array) < len(array)+1 {
 			cap := 2 * cap(array) + max(8, 1)
@@ -361,23 +362,26 @@ append_elem :: proc(array: ^$T/[dynamic]$E, arg: E, loc := #caller_location)  {
 				data[a.len] = arg
 			}
 			a.len += 1
+			return 1
 		}
+		return 0
 	}
 }
 
 @builtin
-append_elems :: proc(array: ^$T/[dynamic]$E, args: ..E, loc := #caller_location)  {
+append_elems :: proc(array: ^$T/[dynamic]$E, args: ..E, loc := #caller_location) -> int {
 	if array == nil {
-		return
+		return 0
 	}
 
 	arg_len := len(args)
 	if arg_len <= 0 {
-		return
+		return 0
 	}
 
 	when size_of(E) == 0 {
-		a.len += arg_len
+		array.len += arg_len
+		return arg_len
 	} else {
 		if cap(array) < len(array)+arg_len {
 			cap := 2 * cap(array) + max(8, arg_len)
@@ -393,23 +397,25 @@ append_elems :: proc(array: ^$T/[dynamic]$E, args: ..E, loc := #caller_location)
 			}
 			a.len += arg_len
 		}
+		return arg_len
 	}
 }
 
 // The append_string built-in procedure appends a string to the end of a [dynamic]u8 like type
 @builtin
-append_elem_string :: proc(array: ^$T/[dynamic]$E/u8, arg: $A/string, loc := #caller_location) {
+append_elem_string :: proc(array: ^$T/[dynamic]$E/u8, arg: $A/string, loc := #caller_location) -> int {
 	args := transmute([]E)arg
-	append_elems(array=array, args=args, loc=loc)
+	return append_elems(array=array, args=args, loc=loc)
 }
 
 
 // The append_string built-in procedure appends multiple strings to the end of a [dynamic]u8 like type
 @builtin
-append_string :: proc(array: ^$T/[dynamic]$E/u8, args: ..string, loc := #caller_location) {
+append_string :: proc(array: ^$T/[dynamic]$E/u8, args: ..string, loc := #caller_location) -> (n: int) {
 	for arg in args {
-		append(array = array, args = transmute([]E)(arg), loc = loc)
+		n += append(array = array, args = transmute([]E)(arg), loc = loc)
 	}
+	return
 }
 
 // The append built-in procedure appends elements to the end of a dynamic array
@@ -417,11 +423,13 @@ append_string :: proc(array: ^$T/[dynamic]$E/u8, args: ..string, loc := #caller_
 
 
 @builtin
-append_nothing :: proc(array: ^$T/[dynamic]$E, loc := #caller_location) {
+append_nothing :: proc(array: ^$T/[dynamic]$E, loc := #caller_location) -> int {
 	if array == nil {
-		return
+		return 0
 	}
+	prev_len := len(array)
 	resize(array, len(array)+1)
+	return len(array)-prev_len
 }
 
 

+ 2 - 2
core/slice/slice.odin

@@ -14,14 +14,14 @@ _ :: mem
 	Turn a pointer and a length into a slice.
 */
 from_ptr :: proc "contextless" (ptr: ^$T, count: int) -> []T {
-    return ([^]T)(ptr)[:count]
+	return ([^]T)(ptr)[:count]
 }
 
 /*
 	Turn a pointer and a length into a byte slice.
 */
 bytes_from_ptr :: proc "contextless" (ptr: rawptr, byte_count: int) -> []byte {
-    return ([^]byte)(ptr)[:byte_count]
+	return ([^]byte)(ptr)[:byte_count]
 }
 
 /*

+ 62 - 0
core/slice/sort.odin

@@ -38,6 +38,52 @@ sort :: proc(data: $T/[]$E) where ORD(E) {
 	}
 }
 
+
+sort_by_indices :: proc{ sort_by_indices_allocate, _sort_by_indices}
+
+sort_by_indices_allocate :: proc(data: $T/[]$E, indices: []int, allocator := context.allocator) -> (sorted: T) {
+	assert(len(data) == len(indices))
+	sorted = make(T, len(data), allocator)
+	for v, i in indices {
+		sorted[i] = data[v]
+	}
+	return
+}
+
+_sort_by_indices :: proc(data, sorted: $T/[]$E, indices: []int) {
+	assert(len(data) == len(indices))
+	assert(len(data) == len(sorted))
+	for v, i in indices {
+		sorted[i] = data[v]
+	}
+}
+
+sort_by_indices_overwrite :: proc(data: $T/[]$E, indices: []int) {
+	assert(len(data) == len(indices))
+	temp := make([]int, len(data), context.allocator)
+	defer delete(temp)
+	for v, i in indices {
+		temp[i] = data[v]
+	}
+	swap_with_slice(data, temp)
+}
+
+// sort sorts a slice and returns a slice of the original indices
+// This sort is not guaranteed to be stable
+sort_with_indices :: proc(data: $T/[]$E, allocator := context.allocator) -> (indices: []int) where ORD(E) {
+	indices = make([]int, len(data), allocator)
+	when size_of(E) != 0 {
+		if n := len(data); n > 1 {
+			for _, idx in indices {
+				indices[idx] = idx
+			}
+			_quick_sort_general_with_indices(data, indices, 0, n, _max_depth(n), struct{}{}, .Ordered)
+		}
+		return indices
+	}
+	return indices
+}
+
 // sort_by sorts a slice with a given procedure to test whether two values are ordered "i < j"
 // This sort is not guaranteed to be stable
 sort_by :: proc(data: $T/[]$E, less: proc(i, j: E) -> bool) {
@@ -48,6 +94,22 @@ sort_by :: proc(data: $T/[]$E, less: proc(i, j: E) -> bool) {
 	}
 }
 
+// sort_by sorts a slice with a given procedure to test whether two values are ordered "i < j"
+// This sort is not guaranteed to be stable
+sort_by_with_indices :: proc(data: $T/[]$E, less: proc(i, j: E) -> bool, allocator := context.allocator) -> (indices : []int) {
+	indices = make([]int, len(data), allocator)
+	when size_of(E) != 0 {
+		if n := len(data); n > 1 {
+			for _, idx in indices {
+				indices[idx] = idx
+			}
+			_quick_sort_general_with_indices(data, indices, 0, n, _max_depth(n), less, .Less)
+			return indices
+		}
+	}
+	return indices
+}
+
 sort_by_cmp :: proc(data: $T/[]$E, cmp: proc(i, j: E) -> Ordering) {
 	when size_of(E) != 0 {
 		if n := len(data); n > 1 {

+ 177 - 0
core/slice/sort_private.odin

@@ -198,3 +198,180 @@ _stable_sort_general :: proc(data: $T/[]$E, call: $P, $KIND: Sort_Kind) where (O
 		}
 	}
 }
+
+_quick_sort_general_with_indices :: proc(data: $T/[]$E, indices: []int, a, b, max_depth: int, call: $P, $KIND: Sort_Kind) where (ORD(E) && KIND == .Ordered) || (KIND != .Ordered) #no_bounds_check {
+	less :: #force_inline proc(a, b: E, call: P) -> bool {
+		when KIND == .Ordered {
+			return a < b
+		} else when KIND == .Less {
+			return call(a, b)
+		} else when KIND == .Cmp {
+			return call(a, b) == .Less
+		} else {
+			#panic("unhandled Sort_Kind")
+		}
+	}
+
+	insertion_sort :: proc(data: $T/[]$E, indices: []int, a, b: int, call: P) #no_bounds_check {
+		for i in a+1..<b {
+			for j := i; j > a && less(data[j], data[j-1], call); j -= 1 {
+				swap(data, j, j-1)
+				swap(indices, j, j-1)
+			}
+		}
+	}
+
+	heap_sort :: proc(data: $T/[]$E, indices: []int, a, b: int, call: P) #no_bounds_check {
+		sift_down :: proc(data: T, indices: []int, lo, hi, first: int, call: P) #no_bounds_check {
+			root := lo
+			for {
+				child := 2*root + 1
+				if child >= hi {
+					break
+				}
+				if child+1 < hi && less(data[first+child], data[first+child+1], call) {
+					child += 1
+				}
+				if !less(data[first+root], data[first+child], call) {
+					return
+				}
+				swap(data, first+root, first+child)
+				swap(indices, first+root, first+child)
+				root = child
+			}
+		}
+
+
+		first, lo, hi := a, 0, b-a
+
+		for i := (hi-1)/2; i >= 0; i -= 1 {
+			sift_down(data, indices, i, hi, first, call)
+		}
+
+		for i := hi-1; i >= 0; i -= 1 {
+			swap(data, first, first+i)
+			swap(indices, first, first+i)
+			sift_down(data, indices, lo, i, first, call)
+		}
+	}
+
+	median3 :: proc(data: T, indices: []int, m1, m0, m2: int, call: P) #no_bounds_check {
+		if less(data[m1], data[m0], call) {
+			swap(data, m1, m0)
+			swap(indices, m1, m0)
+		}
+		if less(data[m2], data[m1], call) {
+			swap(data, m2, m1)
+			swap(indices, m2, m1)
+			if less(data[m1], data[m0], call) {
+				swap(data, m1, m0)
+				swap(indices, m1, m0)
+			}
+		}
+	}
+
+	do_pivot :: proc(data: T, indices: []int, lo, hi: int, call: P) -> (midlo, midhi: int) #no_bounds_check {
+		m := int(uint(lo+hi)>>1)
+		if hi-lo > 40 {
+			s := (hi-lo)/8
+			median3(data, indices, lo, lo+s, lo+s*2, call)
+			median3(data, indices, m, m-s, m+s, call)
+			median3(data, indices, hi-1, hi-1-s, hi-1-s*2, call)
+		}
+		median3(data, indices, lo, m, hi-1, call)
+
+		pivot := lo
+		a, c := lo+1, hi-1
+
+
+		for ; a < c && less(data[a], data[pivot], call); a += 1 {
+		}
+		b := a
+
+		for {
+			for ; b < c && !less(data[pivot], data[b], call); b += 1 { // data[b] <= pivot
+			}
+			for ; b < c && less(data[pivot], data[c-1], call); c -=1 { // data[c-1] > pivot
+			}
+			if b >= c {
+				break
+			}
+
+			swap(data, b, c-1)
+			swap(indices, b, c-1)
+			b += 1
+			c -= 1
+		}
+
+		protect := hi-c < 5
+		if !protect && hi-c < (hi-lo)/4 {
+			dups := 0
+			if !less(data[pivot], data[hi-1], call) {
+				swap(data, c, hi-1)
+				swap(indices, c, hi-1)
+				c += 1
+				dups += 1
+			}
+			if !less(data[b-1], data[pivot], call) {
+				b -= 1
+				dups += 1
+			}
+
+			if !less(data[m], data[pivot], call) {
+				swap(data, m, b-1)
+				swap(indices, m, b-1)
+				b -= 1
+				dups += 1
+			}
+			protect = dups > 1
+		}
+		if protect {
+			for {
+				for ; a < b && !less(data[b-1], data[pivot], call); b -= 1 {
+				}
+				for ; a < b && less(data[a], data[pivot], call); a += 1 {
+				}
+				if a >= b {
+					break
+				}
+				swap(data, a, b-1)
+				swap(indices, a, b-1)
+				a += 1
+				b -= 1
+			}
+		}
+		swap(data, pivot, b-1)
+		swap(indices, pivot, b-1)
+		return b-1, c
+	}
+
+	assert(len(data) == len(indices))
+
+	a, b, max_depth := a, b, max_depth
+
+	for b-a > 12 { // only use shell sort for lengths <= 12
+		if max_depth == 0 {
+			heap_sort(data, indices, a, b, call)
+			return
+		}
+		max_depth -= 1
+		mlo, mhi := do_pivot(data, indices, a, b, call)
+		if mlo-a < b-mhi {
+			_quick_sort_general_with_indices(data, indices, a, mlo, max_depth, call, KIND)
+			a = mhi
+		} else {
+			_quick_sort_general_with_indices(data, indices, mhi, b, max_depth, call, KIND)
+			b = mlo
+		}
+	}
+	if b-a > 1 {
+		// Shell short with gap 6
+		for i in a+6..<b {
+			if less(data[i], data[i-6], call) {
+				swap(data, i, i-6)
+				swap(indices, i, i-6)
+			}
+		}
+		insertion_sort(data, indices, a, b, call)
+	}
+}

+ 31 - 18
core/strconv/strconv.odin

@@ -2,11 +2,13 @@ package strconv
 
 import "core:unicode/utf8"
 
-parse_bool :: proc(s: string) -> (result: bool = false, ok: bool) {
+parse_bool :: proc(s: string, n: ^int = nil) -> (result: bool = false, ok: bool) {
 	switch s {
 	case "1", "t", "T", "true", "TRUE", "True":
+		if n != nil { n^ = len(s) }
 		return true, true
 	case "0", "f", "F", "false", "FALSE", "False":
+		if n != nil { n^ = len(s) }
 		return false, true
 	}
 	return
@@ -32,10 +34,13 @@ _digit_value :: proc(r: rune) -> int {
 // n, ok := strconv.parse_i64_of_base("-1234eeee", 10);
 // assert(n == -1234 && ok);
 // ```
-parse_i64_of_base :: proc(str: string, base: int) -> (value: i64, ok: bool) {
+parse_i64_of_base :: proc(str: string, base: int, n: ^int = nil) -> (value: i64, ok: bool) {
 	assert(base <= 16, "base must be 1-16")
 
 	s := str
+
+	defer if n != nil { n^ = len(str)-len(s) }
+
 	if s == "" {
 		return
 	}
@@ -87,8 +92,9 @@ parse_i64_of_base :: proc(str: string, base: int) -> (value: i64, ok: bool) {
 // n, ok = strconv.parse_i64_maybe_prefixed("0xeeee");
 // assert(n == 0xeeee && ok);
 // ```
-parse_i64_maybe_prefixed :: proc(str: string) -> (value: i64, ok: bool) {
+parse_i64_maybe_prefixed :: proc(str: string, n: ^int = nil) -> (value: i64, ok: bool) {
 	s := str
+	defer if n != nil { n^ = len(str)-len(s) }
 	if s == "" {
 		return
 	}
@@ -155,9 +161,10 @@ parse_i64 :: proc{parse_i64_maybe_prefixed, parse_i64_of_base}
 // n, ok = strconv.parse_u64_of_base("5678eeee", 16);
 // assert(n == 0x5678eeee && ok);
 // ```
-parse_u64_of_base :: proc(str: string, base: int) -> (value: u64, ok: bool) {
+parse_u64_of_base :: proc(str: string, base: int, n: ^int = nil) -> (value: u64, ok: bool) {
 	assert(base <= 16, "base must be 1-16")
 	s := str
+	defer if n != nil { n^ = len(str)-len(s) }
 	if s == "" {
 		return
 	}
@@ -198,8 +205,9 @@ parse_u64_of_base :: proc(str: string, base: int) -> (value: u64, ok: bool) {
 // n, ok = strconv.parse_u64_maybe_prefixed("0xeeee");
 // assert(n == 0xeeee && ok);
 // ```
-parse_u64_maybe_prefixed :: proc(str: string) -> (value: u64, ok: bool) {
+parse_u64_maybe_prefixed :: proc(str: string, n: ^int = nil) -> (value: u64, ok: bool) {
 	s := str
+	defer if n != nil { n^ = len(str)-len(s) }
 	if s == "" {
 		return
 	}
@@ -259,11 +267,11 @@ parse_u64 :: proc{parse_u64_maybe_prefixed, parse_u64_of_base}
 // n, ok = strconv.parse_int("0xffff"); // with prefix and inferred base
 // assert(n == 0xffff && ok);
 // ```
-parse_int :: proc(s: string, base := 0) -> (value: int, ok: bool) {
+parse_int :: proc(s: string, base := 0, n: ^int = nil) -> (value: int, ok: bool) {
 	v: i64 = ---
 	switch base {
-	case 0:  v, ok = parse_i64_maybe_prefixed(s)
-	case:    v, ok = parse_i64_of_base(s, base)
+	case 0:  v, ok = parse_i64_maybe_prefixed(s, n)
+	case:    v, ok = parse_i64_of_base(s, base, n)
 	}
 	value = int(v)
 	return
@@ -289,11 +297,11 @@ parse_int :: proc(s: string, base := 0) -> (value: int, ok: bool) {
 // n, ok = strconv.parse_uint("0xffff"); // with prefix and inferred base
 // assert(n == 0xffff && ok);
 // ```
-parse_uint :: proc(s: string, base := 0) -> (value: uint, ok: bool) {
+parse_uint :: proc(s: string, base := 0, n: ^int = nil) -> (value: uint, ok: bool) {
 	v: u64 = ---
 	switch base {
-	case 0:  v, ok = parse_u64_maybe_prefixed(s)
-	case:    v, ok = parse_u64_of_base(s, base)
+	case 0:  v, ok = parse_u64_maybe_prefixed(s, n)
+	case:    v, ok = parse_u64_of_base(s, base, n)
 	}
 	value = uint(v)
 	return
@@ -309,10 +317,11 @@ parse_uint :: proc(s: string, base := 0) -> (value: uint, ok: bool) {
 // n, ok := strconv.parse_i128_of_base("-1234eeee", 10);
 // assert(n == -1234 && ok);
 // ```
-parse_i128_of_base :: proc(str: string, base: int) -> (value: i128, ok: bool) {
+parse_i128_of_base :: proc(str: string, base: int, n: ^int = nil) -> (value: i128, ok: bool) {
 	assert(base <= 16, "base must be 1-16")
 
 	s := str
+	defer if n != nil { n^ = len(str)-len(s) }
 	if s == "" {
 		return
 	}
@@ -364,8 +373,9 @@ parse_i128_of_base :: proc(str: string, base: int) -> (value: i128, ok: bool) {
 // n, ok = strconv.parse_i128_maybe_prefixed("0xeeee");
 // assert(n == 0xeeee && ok);
 // ```
-parse_i128_maybe_prefixed :: proc(str: string) -> (value: i128, ok: bool) {
+parse_i128_maybe_prefixed :: proc(str: string, n: ^int = nil) -> (value: i128, ok: bool) {
 	s := str
+	defer if n != nil { n^ = len(str)-len(s) }
 	if s == "" {
 		return
 	}
@@ -432,9 +442,10 @@ parse_i128 :: proc{parse_i128_maybe_prefixed, parse_i128_of_base}
 // n, ok = strconv.parse_u128_of_base("5678eeee", 16);
 // assert(n == 0x5678eeee && ok);
 // ```
-parse_u128_of_base :: proc(str: string, base: int) -> (value: u128, ok: bool) {
+parse_u128_of_base :: proc(str: string, base: int, n: ^int = nil) -> (value: u128, ok: bool) {
 	assert(base <= 16, "base must be 1-16")
 	s := str
+	defer if n != nil { n^ = len(str)-len(s) }
 	if s == "" {
 		return
 	}
@@ -475,8 +486,9 @@ parse_u128_of_base :: proc(str: string, base: int) -> (value: u128, ok: bool) {
 // n, ok = strconv.parse_u128_maybe_prefixed("0xeeee");
 // assert(n == 0xeeee && ok);
 // ```
-parse_u128_maybe_prefixed :: proc(str: string) -> (value: u128, ok: bool) {
+parse_u128_maybe_prefixed :: proc(str: string, n: ^int = nil) -> (value: u128, ok: bool) {
 	s := str
+	defer if n != nil { n^ = len(str)-len(s) }
 	if s == "" {
 		return
 	}
@@ -535,9 +547,9 @@ parse_u128 :: proc{parse_u128_maybe_prefixed, parse_u128_of_base}
 // n, ok = strconv.parse_f32("12.34");
 // assert(n == 12.34 && ok);
 // ```
-parse_f32 :: proc(s: string) -> (value: f32, ok: bool) {
+parse_f32 :: proc(s: string, n: ^int = nil) -> (value: f32, ok: bool) {
 	v: f64 = ---
-	v, ok = parse_f64(s)
+	v, ok = parse_f64(s, n)
 	return f32(v), ok
 }
 
@@ -553,8 +565,9 @@ parse_f32 :: proc(s: string) -> (value: f32, ok: bool) {
 // n, ok = strconv.parse_f32("12.34");
 // assert(n == 12.34 && ok);
 // ```
-parse_f64 :: proc(str: string) -> (value: f64, ok: bool) {
+parse_f64 :: proc(str: string, n: ^int = nil) -> (value: f64, ok: bool) {
 	s := str
+	defer if n != nil { n^ = len(str)-len(s) }
 	if s == "" {
 		return
 	}

+ 5 - 5
core/strings/strings.odin

@@ -1686,7 +1686,7 @@ centre_justify :: proc(str: string, length: int, pad: string, allocator := conte
 		return clone(str, allocator)
 	}
 
-	remains := length-1
+	remains := length-n
 	pad_len := rune_count(pad)
 
 	b: Builder
@@ -1702,14 +1702,14 @@ centre_justify :: proc(str: string, length: int, pad: string, allocator := conte
 	return to_string(b)
 }
 
-// left_justify returns a string with a pad string at left side if the str's rune length is smaller than length
+// left_justify returns a string with a pad string at right side if the str's rune length is smaller than length
 left_justify :: proc(str: string, length: int, pad: string, allocator := context.allocator) -> string {
 	n := rune_count(str)
 	if n >= length || pad == "" {
 		return clone(str, allocator)
 	}
 
-	remains := length-1
+	remains := length-n
 	pad_len := rune_count(pad)
 
 	b: Builder
@@ -1724,14 +1724,14 @@ left_justify :: proc(str: string, length: int, pad: string, allocator := context
 	return to_string(b)
 }
 
-// right_justify returns a string with a pad string at right side if the str's rune length is smaller than length
+// right_justify returns a string with a pad string at left side if the str's rune length is smaller than length
 right_justify :: proc(str: string, length: int, pad: string, allocator := context.allocator) -> string {
 	n := rune_count(str)
 	if n >= length || pad == "" {
 		return clone(str, allocator)
 	}
 
-	remains := length-1
+	remains := length-n
 	pad_len := rune_count(pad)
 
 	b: Builder

+ 4 - 3
core/sync/futex_darwin.odin

@@ -8,8 +8,9 @@ import "core:time"
 foreign import System "System.framework"
 
 foreign System {
+	// __ulock_wait is not available on 10.15
+	// See https://github.com/odin-lang/Odin/issues/1959
 	__ulock_wait  :: proc "c" (operation: u32, addr: rawptr, value: u64, timeout_us: u32) -> c.int ---
-	__ulock_wait2 :: proc "c" (operation: u32, addr: rawptr, value: u64, timeout_ns: u64, value2: u64) -> c.int ---
 	__ulock_wake  :: proc "c" (operation: u32, addr: rawptr, wake_value: u64) -> c.int ---
 }
 
@@ -28,9 +29,9 @@ _futex_wait :: proc(f: ^Futex, expected: u32) -> bool {
 }
 
 _futex_wait_with_timeout :: proc(f: ^Futex, expected: u32, duration: time.Duration) -> bool {
-	timeout_ns := u64(duration)
+	timeout_ns := u32(duration) * 1000
 	
-	s := __ulock_wait2(UL_COMPARE_AND_WAIT | ULF_NO_ERRNO, f, u64(expected), timeout_ns, 0)
+	s := __ulock_wait(UL_COMPARE_AND_WAIT | ULF_NO_ERRNO, f, u64(expected), timeout_ns)
 	if s >= 0 {
 		return true
 	}

+ 26 - 0
core/sys/info/cpu_arm.odin

@@ -0,0 +1,26 @@
+//+build arm32, arm64
+package sysinfo
+
+// TODO: Set up an enum with the ARM equivalent of the above.
+CPU_Feature :: enum u64 {}
+
+cpu_features: Maybe(CPU_Feature)
+cpu_name:     Maybe(string)
+
+@(init, private)
+init_cpu_features :: proc "c" () {
+}
+
+@(private)
+_cpu_name_buf: [72]u8
+
+@(init, private)
+init_cpu_name :: proc "c" () {
+	when ODIN_ARCH == .arm32 {
+		copy(_cpu_name_buf[:], "ARM")
+		cpu_name = string(_cpu_name_buf[:3])
+	} else {
+		copy(_cpu_name_buf[:], "ARM64")
+		cpu_name = string(_cpu_name_buf[:5])
+	}
+}

+ 37 - 2
core/simd/x86/cpu.odin → core/sys/info/cpu_intel.odin

@@ -1,5 +1,5 @@
 //+build i386, amd64
-package simd_x86
+package sysinfo
 
 import "core:intrinsics"
 
@@ -9,7 +9,6 @@ cpuid :: intrinsics.x86_cpuid
 // xgetbv :: proc(cx: u32) -> (eax, edx: u32) ---
 xgetbv :: intrinsics.x86_xgetbv
 
-
 CPU_Feature :: enum u64 {
 	aes,       // AES hardware implementation (AES NI)
 	adx,       // Multi-precision add-carry instruction extensions
@@ -34,6 +33,7 @@ CPU_Feature :: enum u64 {
 CPU_Features :: distinct bit_set[CPU_Feature; u64]
 
 cpu_features: Maybe(CPU_Features)
+cpu_name:     Maybe(string)
 
 @(init, private)
 init_cpu_features :: proc "c" () {
@@ -67,6 +67,13 @@ init_cpu_features :: proc "c" () {
 	try_set(&set, .os_xsave,  27, ecx1)
 	try_set(&set, .rdrand,    30, ecx1)
 
+	when ODIN_OS == .FreeBSD || ODIN_OS == .OpenBSD {
+		// xgetbv is an illegal instruction under FreeBSD 13 & OpenBSD 7.1
+		// return before probing further
+		cpu_features = set
+		return
+	}
+
 	os_supports_avx := false
 	if .os_xsave in set {
 		eax, _ := xgetbv(0)
@@ -92,3 +99,31 @@ init_cpu_features :: proc "c" () {
 
 	cpu_features = set
 }
+
+@(private)
+_cpu_name_buf: [72]u8
+
+@(init, private)
+init_cpu_name :: proc "c" () {
+	number_of_extended_ids, _, _, _ := cpuid(0x8000_0000, 0)
+	if number_of_extended_ids < 0x8000_0004 {
+		return
+	}
+
+	_buf := transmute(^[0x12]u32)&_cpu_name_buf
+	_buf[ 0], _buf[ 1], _buf[ 2], _buf[ 3] = cpuid(0x8000_0002, 0)
+	_buf[ 4], _buf[ 5], _buf[ 6], _buf[ 7] = cpuid(0x8000_0003, 0)
+	_buf[ 8], _buf[ 9], _buf[10], _buf[11] = cpuid(0x8000_0004, 0)
+
+	// Some CPUs like may include leading or trailing spaces. Trim them.
+	// e.g. `      Intel(R) Xeon(R) CPU E5-1650 v2 @ 3.50GHz`
+
+	brand := string(_cpu_name_buf[:])
+	for len(brand) > 0 && brand[0] == 0 || brand[0] == ' ' {
+		brand = brand[1:]
+	}
+	for len(brand) > 0 && brand[len(brand) - 1] == 0 || brand[len(brand) - 1] == ' ' {
+		brand = brand[:len(brand) - 1]
+	}
+	cpu_name = brand
+}

+ 78 - 0
core/sys/info/doc.odin

@@ -0,0 +1,78 @@
+/*
+	Copyright 2022 Jeroen van Rijn <[email protected]>.
+	Made available under Odin's BSD-3 license.
+
+	Package `core:sys/info` gathers system information on:
+	Windows, Linux, macOS, FreeBSD & OpenBSD.
+
+	Simply import the package and you'll have access to the OS version, RAM amount
+	and CPU information.
+
+	On Windows, GPUs will also be enumerated using the registry.
+
+	CPU feature flags can be tested against `cpu_features`, where applicable, e.g.
+	`if .aes in si.aes { ... }`
+*/
+// +ignore
+package sysinfo
+
+import "core:fmt"
+import si "core:sys/info"
+
+main :: proc() {
+	fmt.printf("Odin:  %v\n",     ODIN_VERSION)
+	fmt.printf("OS:    %v\n",     si.os_version.as_string)
+	fmt.printf("OS:    %#v\n",    si.os_version)
+	fmt.printf("CPU:   %v\n",     si.cpu_name)
+	fmt.printf("RAM:   %v MiB\n", si.ram.total_ram / 1024 / 1024)
+
+	fmt.println()
+	for gpu, i in si.gpus {
+		fmt.printf("GPU #%v:\n", i)
+		fmt.printf("\tVendor: %v\n",     gpu.vendor_name)
+		fmt.printf("\tModel:  %v\n",     gpu.model_name)
+		fmt.printf("\tVRAM:   %v MiB\n", gpu.total_ram / 1024 / 1024)
+	}
+}
+
+/*
+	Example Windows output:
+		Odin:  dev-2022-09
+		OS:    Windows 10 Professional (version: 20H2), build: 19042.1466
+		OS:    OS_Version{
+			platform = "Windows",
+			major = 10,
+			minor = 0,
+			patch = 0,
+			build = [
+				19042,
+				1466,
+			],
+			version = "20H2",
+			as_string = "Windows 10 Professional (version: 20H2), build: 19042.1466",
+		}
+		CPU:   AMD Ryzen 7 1800X Eight-Core Processor
+		RAM:   65469 MiB
+
+		GPU #0:
+			Vendor: Advanced Micro Devices, Inc.
+			Model:  Radeon RX Vega
+			VRAM:   8176 MiB
+
+	Example macOS output:
+		ODIN: dev-2022-09
+		OS:   OS_Version{
+		        platform = "MacOS",
+		        major = 21,
+		        minor = 5,
+		        patch = 0,
+		        build = [
+		                0,
+		                0,
+		        ],
+		        version = "21F79",
+		        as_string = "macOS Monterey 12.4 (build 21F79, kernel 21.5.0)",
+		}
+		CPU:  Intel(R) Core(TM) i5-7360U CPU @ 2.30GHz
+		RAM:  8192 MiB
+*/

+ 510 - 0
core/sys/info/platform_darwin.odin

@@ -0,0 +1,510 @@
+// +build darwin
+package sysinfo
+
+import sys "core:sys/unix"
+import "core:strconv"
+import "core:strings"
+
+@(private)
+version_string_buf: [1024]u8
+
+@(init, private)
+init_os_version :: proc () {
+	os_version.platform = .MacOS
+
+	// Start building display version
+	b := strings.builder_from_bytes(version_string_buf[:])
+
+	mib := []i32{sys.CTL_KERN, sys.KERN_OSVERSION}
+	build_buf: [12]u8
+
+	ok := sys.sysctl(mib, &build_buf)
+	if !ok {
+		strings.write_string(&b, "macOS Unknown")
+		os_version.as_string = strings.to_string(b)
+		return
+	}
+
+	build := string(cstring(&build_buf[0]))
+
+	// Do we have an exact match?
+	match: Darwin_Match
+	rel, exact := macos_release_map[build]
+
+	if exact {
+		match = .Exact
+	} else {
+		// Match on XNU kernel version
+		mib = []i32{sys.CTL_KERN, sys.KERN_OSRELEASE}
+		version_bits: [12]u8 // enough for 999.999.999\x00
+		have_kernel_version := sys.sysctl(mib, &version_bits)
+
+		major_ok, minor_ok, patch_ok: bool
+
+		triplet := strings.split(string(cstring(&version_bits[0])), ".", context.temp_allocator)
+		if len(triplet) != 3 {
+			have_kernel_version = false
+		} else {
+			rel.darwin.x, major_ok = strconv.parse_int(triplet[0])
+			rel.darwin.y, minor_ok = strconv.parse_int(triplet[1])
+			rel.darwin.z, patch_ok = strconv.parse_int(triplet[2])
+
+			if !(major_ok && minor_ok && patch_ok) {
+				have_kernel_version = false
+			}
+		}
+
+		if !have_kernel_version {
+			// We don't know the kernel version, but we do know the build
+			strings.write_string(&b, "macOS Unknown (build ")
+			l := strings.builder_len(b)
+			strings.write_string(&b, build)
+			os_version.version = strings.to_string(b)[l:]
+			strings.write_rune(&b, ')')
+			os_version.as_string = strings.to_string(b)
+			return
+		}
+		rel, match = map_darwin_kernel_version_to_macos_release(build, rel.darwin)
+	}
+
+	os_version.major = rel.darwin.x
+	os_version.minor = rel.darwin.y
+	os_version.patch = rel.darwin.z
+
+	strings.write_string(&b, rel.os_name)
+	if match == .Exact || match == .Nearest {
+		strings.write_rune(&b, ' ')
+		strings.write_string(&b, rel.release.name)
+		strings.write_rune(&b, ' ')
+		strings.write_int(&b, rel.release.version.x)
+		if rel.release.version.y > 0 || rel.release.version.z > 0 {
+			strings.write_rune(&b, '.')
+			strings.write_int(&b, rel.release.version.y)
+		}
+		if rel.release.version.z > 0 {
+			strings.write_rune(&b, '.')
+			strings.write_int(&b, rel.release.version.z)
+		}
+		if match == .Nearest {
+			strings.write_rune(&b, '?')
+		}
+	} else {
+		strings.write_string(&b, " Unknown")
+	}
+
+	strings.write_string(&b, " (build ")
+	l := strings.builder_len(b)
+	strings.write_string(&b, build)
+	os_version.version = strings.to_string(b)[l:]
+
+	strings.write_string(&b, ", kernel ")
+	strings.write_int(&b, rel.darwin.x)
+	strings.write_rune(&b, '.')
+	strings.write_int(&b, rel.darwin.y)
+	strings.write_rune(&b, '.')
+	strings.write_int(&b, rel.darwin.z)
+	strings.write_rune(&b, ')')
+
+	os_version.as_string = strings.to_string(b)
+}
+
+@(init)
+init_ram :: proc() {
+	// Retrieve RAM info using `sysctl`
+
+	mib := []i32{sys.CTL_HW, sys.HW_MEMSIZE}
+	mem_size: u64
+	if sys.sysctl(mib, &mem_size) {
+		ram.total_ram = int(mem_size)
+	}
+}
+
+@(private)
+Darwin_To_Release :: struct {
+	darwin:      [3]int, // Darwin kernel triplet
+	os_name:     string, // OS X, MacOS
+	release:     struct {
+		name:    string, // Monterey, Mojave, etc.
+		version: [3]int, // 12.4, etc.
+	},
+}
+
+// Important: Order from lowest to highest kernel version
+@(private)
+macos_release_map: map[string]Darwin_To_Release = {
+	// MacOS Tiger
+	"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}}},
+
+	// MacOS Leopard
+	"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}}},
+
+	// MacOS Snow Leopard
+	"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}}},
+
+	// MacOS Lion
+	"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}}},
+
+	// MacOS Mountain Lion
+	"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}}},
+
+	// MacOS Mavericks
+	"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}}},
+
+	// MacOS Yosemite
+	"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}}},
+
+	// MacOS El Capitan
+	"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}}},
+
+	// MacOS Sierra
+	"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}}},
+
+	// MacOS High Sierra
+	"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}}},
+
+	// MacOS Mojave
+	"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}}},
+
+	// MacOS Catalina
+	"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}}},
+
+	// MacOS Big Sur
+	"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}}},
+
+	// MacOS Monterey
+	"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}}},
+}
+
+@(private)
+Darwin_Match :: enum {
+	Unknown,
+	Exact,
+	Nearest,
+}
+
+@(private)
+map_darwin_kernel_version_to_macos_release :: proc(build: string, darwin: [3]int) -> (res: Darwin_To_Release, match: Darwin_Match) {
+	// Find exact release match if possible.
+	if v, v_ok := macos_release_map[build]; v_ok {
+		return v, .Exact
+	}
+
+	nearest: Darwin_To_Release
+	for _, v in macos_release_map {
+		// Try an exact match on XNU version first.
+		if darwin == v.darwin {
+			return v, .Exact
+		}
+
+		// Major kernel version needs to match exactly,
+		// otherwise the release is considered .Unknown
+		if darwin.x == v.darwin.x {
+			if nearest == {} {
+				nearest = v
+			}
+			if darwin.y >= v.darwin.y && v.darwin != nearest.darwin {
+				nearest = v
+				if darwin.z >= v.darwin.z && v.darwin != nearest.darwin {
+					nearest = v
+				}
+			}
+		}
+	}
+
+	if nearest == {} {
+		return {darwin, "macOS", {"Unknown", {}}}, .Unknown
+	} else {
+		return nearest, .Nearest
+	}
+}

+ 76 - 0
core/sys/info/platform_freebsd.odin

@@ -0,0 +1,76 @@
+// +build freebsd
+package sysinfo
+
+import sys "core:sys/unix"
+import "core:strings"
+import "core:strconv"
+
+@(private)
+version_string_buf: [1024]u8
+
+@(init, private)
+init_os_version :: proc () {
+	os_version.platform = .FreeBSD
+
+	kernel_version_buf: [129]u8
+
+	b := strings.builder_from_bytes(version_string_buf[:])
+	// Retrieve kernel info using `sysctl`, e.g. FreeBSD 13.1-RELEASE-p2 GENERIC
+	mib := []i32{sys.CTL_KERN, sys.KERN_VERSION}
+	if !sys.sysctl(mib, &kernel_version_buf) {
+		return
+	}
+
+	pretty_name := string(cstring(raw_data(kernel_version_buf[:])))
+	pretty_name  = strings.trim(pretty_name, "\n")
+	strings.write_string(&b, pretty_name)
+
+	// l := strings.builder_len(b)
+
+	// Retrieve kernel revision using `sysctl`, e.g. 199506
+	mib = []i32{sys.CTL_KERN, sys.KERN_OSREV}
+	revision: int
+	if !sys.sysctl(mib, &revision) {
+		return
+	}
+	os_version.patch = revision
+
+	strings.write_string(&b, ", revision ")
+	strings.write_int(&b, revision)
+
+	// Finalize pretty name.
+	os_version.as_string = strings.to_string(b)
+
+	// Retrieve kernel release using `sysctl`, e.g. 13.1-RELEASE-p2
+	mib = []i32{sys.CTL_KERN, sys.KERN_OSRELEASE}
+	if !sys.sysctl(mib, &kernel_version_buf) {
+		return
+	}
+
+	// Parse kernel version
+	release := string(cstring(raw_data(kernel_version_buf[:])))
+	version_bits := strings.split_n(release, "-", 2, context.temp_allocator)
+	if len(version_bits) > 1 {
+		// Parse major, minor from KERN_OSRELEASE
+		triplet := strings.split(version_bits[0], ".", context.temp_allocator)
+		if len(triplet) == 2 {
+			major, major_ok := strconv.parse_int(triplet[0])
+			minor, minor_ok := strconv.parse_int(triplet[1])
+
+			if major_ok && minor_ok {
+				os_version.major = major
+				os_version.minor = minor
+			}
+		}
+	}
+}
+
+@(init)
+init_ram :: proc() {
+	// Retrieve RAM info using `sysctl`
+	mib := []i32{sys.CTL_HW, sys.HW_PHYSMEM}
+	mem_size: u64
+	if sys.sysctl(mib, &mem_size) {
+		ram.total_ram = int(mem_size)
+	}
+}

+ 136 - 0
core/sys/info/platform_linux.odin

@@ -0,0 +1,136 @@
+// +build linux
+package sysinfo
+
+import "core:c"
+import sys "core:sys/unix"
+import "core:intrinsics"
+import "core:os"
+import "core:strings"
+import "core:strconv"
+
+@(private)
+version_string_buf: [1024]u8
+
+@(init, private)
+init_os_version :: proc () {
+	os_version.platform = .Linux
+
+	// Try to parse `/etc/os-release` for `PRETTY_NAME="Ubuntu 20.04.3 LTS`
+	fd, err := os.open("/etc/os-release", os.O_RDONLY, 0)
+	if err != 0 {
+		return
+	}
+	defer os.close(fd)
+
+	os_release_buf: [2048]u8
+	n, read_err := os.read(fd, os_release_buf[:])
+	if read_err != 0 {
+		return
+	}
+	release := string(os_release_buf[:n])
+
+	NEEDLE :: "PRETTY_NAME=\""
+	pretty_start := strings.index(release, NEEDLE)
+
+	b := strings.builder_from_bytes(version_string_buf[:])
+
+	if pretty_start > 0 {
+		for r, i in release[pretty_start + len(NEEDLE):] {
+			if r == '"' {
+				strings.write_string(&b, release[pretty_start + len(NEEDLE):][:i])
+				break
+			} else if r == '\r' || r == '\n' {
+				strings.write_string(&b, "Unknown Linux Distro")
+				break
+			}
+		}
+	}
+
+	NEW_UTS_LEN :: 64
+	UTS_Name :: struct {
+		sys_name:    [NEW_UTS_LEN + 1]u8 `fmt:"s,0"`,
+		node_name:   [NEW_UTS_LEN + 1]u8 `fmt:"s,0"`,
+		release:     [NEW_UTS_LEN + 1]u8 `fmt:"s,0"`,
+		version:     [NEW_UTS_LEN + 1]u8 `fmt:"s,0"`,
+		machine:     [NEW_UTS_LEN + 1]u8 `fmt:"s,0"`,
+		domain_name: [NEW_UTS_LEN + 1]u8 `fmt:"s,0"`,
+	}
+	uts: UTS_Name
+
+	// Grab kernel info using `uname()` syscall, https://linux.die.net/man/2/uname
+	if intrinsics.syscall(sys.SYS_uname, uintptr(&uts)) != 0 {
+		return
+	}
+
+	strings.write_string(&b, ", ")
+	strings.write_string(&b, string(cstring(&uts.sys_name[0])))
+	strings.write_rune(&b, ' ')
+
+	l := strings.builder_len(b)
+	strings.write_string(&b, string(cstring(&uts.release[0])))
+
+	// Parse kernel version, as substrings of the version info in `version_string_buf`
+	version_bits := strings.split_n(strings.to_string(b)[l:], "-", 2, context.temp_allocator)
+	if len(version_bits) > 1 {
+		os_version.version = version_bits[1]
+	}
+
+	// Parse major, minor, patch from release info
+	triplet := strings.split(version_bits[0], ".", context.temp_allocator)
+	if len(triplet) == 3 {
+		major, major_ok := strconv.parse_int(triplet[0])
+		minor, minor_ok := strconv.parse_int(triplet[1])
+		patch, patch_ok := strconv.parse_int(triplet[2])
+
+		if major_ok && minor_ok && patch_ok {
+			os_version.major = major
+			os_version.minor = minor
+			os_version.patch = patch
+		}
+	}
+
+	// Finish the string
+	os_version.as_string = strings.to_string(b)
+}
+
+Sys_Info :: struct {
+	uptime:    c.long,     // Seconds since boot
+	loads:     [3]c.long,  // 1, 5, 15 minute load averages
+	totalram:  c.ulong,    // Total usable main memory size
+	freeram:   c.ulong,    // Available memory size
+	sharedram: c.ulong,    // Amount of shared memory
+	bufferram: c.ulong,    // Memory used by buffers
+	totalswap: c.ulong,    // Total swap space size
+	freeswap:  c.ulong,    // Swap space still available
+	procs:     c.ushort,   // Number of current processes
+	totalhigh: c.ulong,    // Total high memory size
+	freehigh:  c.ulong,    // Available high memory size
+	mem_unit:  c.int,      // Memory unit size in bytes
+	_padding:  [20 - (2 * size_of(c.long)) - size_of(c.int)]u8,
+}
+
+get_sysinfo :: proc "c" () -> (res: Sys_Info, ok: bool) {
+	si: Sys_Info
+	err := intrinsics.syscall(sys.SYS_sysinfo, uintptr(rawptr(&si)))
+	if err != 0 {
+		// Unable to retrieve sysinfo
+		return {}, false
+	}
+	return si, true
+}
+
+@(init)
+init_ram :: proc() {
+	// Retrieve RAM info using `sysinfo`
+	si, ok := get_sysinfo()
+	if !ok {
+		return
+	}
+
+	ram = RAM{
+		total_ram  = int(si.totalram)  * int(si.mem_unit),
+		free_ram   = int(si.freeram)   * int(si.mem_unit),
+		total_swap = int(si.totalswap) * int(si.mem_unit),
+		free_swap  = int(si.freeswap)  * int(si.mem_unit),
+	}
+}

+ 69 - 0
core/sys/info/platform_openbsd.odin

@@ -0,0 +1,69 @@
+// +build openbsd
+package sysinfo
+
+import sys "core:sys/unix"
+import "core:strings"
+import "core:strconv"
+
+@(private)
+version_string_buf: [1024]u8
+
+@(init, private)
+init_os_version :: proc () {
+	os_version.platform = .OpenBSD
+
+	kernel_version_buf: [1024]u8
+
+	b := strings.builder_from_bytes(version_string_buf[:])
+	// Retrieve kernel info using `sysctl`, e.g. OpenBSD
+	mib := []i32{sys.CTL_KERN, sys.KERN_OSTYPE}
+	if !sys.sysctl(mib, &kernel_version_buf) {
+		return
+	}
+	os_type := string(cstring(raw_data(kernel_version_buf[:])))
+	strings.write_string(&b, os_type)
+
+	mib = []i32{sys.CTL_KERN, sys.KERN_OSRELEASE}
+	if !sys.sysctl(mib, &kernel_version_buf) {
+		return
+	}
+
+	strings.write_rune(&b, ' ')
+	version := string(cstring(raw_data(kernel_version_buf[:])))
+	strings.write_string(&b, version)
+
+	// // Parse kernel version
+	triplet := strings.split(version, ".", context.temp_allocator)
+	if len(triplet) == 2 {
+		major, major_ok := strconv.parse_int(triplet[0])
+		minor, minor_ok := strconv.parse_int(triplet[1])
+
+		if major_ok && minor_ok {
+			os_version.major = major
+			os_version.minor = minor
+		}
+	}
+
+	// Retrieve kernel revision using `sysctl`, e.g. 199506
+	mib = []i32{sys.CTL_KERN, sys.KERN_OSREV}
+	revision: int
+	if !sys.sysctl(mib, &revision) {
+		return
+	}
+	os_version.patch = revision
+	strings.write_string(&b, ", build ")
+	strings.write_int(&b, revision)
+
+	// Finalize pretty name.
+	os_version.as_string = strings.to_string(b)
+}
+
+@(init)
+init_ram :: proc() {
+	// Retrieve RAM info using `sysctl`
+	mib := []i32{sys.CTL_HW, sys.HW_PHYSMEM64}
+	mem_size: u64
+	if sys.sysctl(mib, &mem_size) {
+		ram.total_ram = int(mem_size)
+	}
+}

+ 375 - 0
core/sys/info/platform_windows.odin

@@ -0,0 +1,375 @@
+// +build windows
+package sysinfo
+
+import sys "core:sys/windows"
+import "core:intrinsics"
+import "core:strings"
+import "core:unicode/utf16"
+
+import "core:fmt"
+
+@(private)
+version_string_buf: [1024]u8
+
+@(init, private)
+init_os_version :: proc () {
+	/*
+		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)
+
+	*/
+	os_version.platform = .Windows
+
+	osvi: sys.OSVERSIONINFOEXW
+	osvi.dwOSVersionInfoSize = size_of(osvi)
+	status := sys.RtlGetVersion(&osvi)
+
+	if status != 0 {
+		return
+	}
+
+	product_type: sys.Windows_Product_Type
+	sys.GetProductInfo(
+		osvi.dwMajorVersion,         osvi.dwMinorVersion,
+		u32(osvi.wServicePackMajor), u32(osvi.wServicePackMinor),
+		&product_type,
+	)
+
+	os_version.major    = int(osvi.dwMajorVersion)
+	os_version.minor    = int(osvi.dwMinorVersion)
+	os_version.build[0] = int(osvi.dwBuildNumber)
+
+	b := strings.builder_from_bytes(version_string_buf[:])
+	strings.write_string(&b, "Windows ")
+
+	switch osvi.dwMajorVersion {
+	case 10:
+		switch osvi.wProductType {
+		case 1: // VER_NT_WORKSTATION:
+			if osvi.dwBuildNumber < 22000 {
+				strings.write_string(&b, "10 ")
+			} else {
+				strings.write_string(&b, "11 ")
+			}
+			format_windows_product_type(&b, product_type)
+
+		case: // Server or Domain Controller
+			switch osvi.dwBuildNumber {
+			case 14393:
+				strings.write_string(&b, "2016 Server")
+			case 17763:
+				strings.write_string(&b, "2019 Server")
+			case 20348:
+				strings.write_string(&b, "2022 Server")
+			case:
+				strings.write_string(&b, "Unknown Server")
+			}
+		}
+
+	case 6:
+		switch osvi.dwMinorVersion {
+		case 0:
+			switch osvi.wProductType {
+			case 1: // VER_NT_WORKSTATION
+				strings.write_string(&b, "Windows Vista ")
+				format_windows_product_type(&b, product_type)
+			case 3:
+				strings.write_string(&b, "Windows Server 2008")
+			}
+
+		case 1:
+			switch osvi.wProductType {
+			case 1: // VER_NT_WORKSTATION:
+				strings.write_string(&b, "Windows 7 ")
+				format_windows_product_type(&b, product_type)
+			case 3:
+				strings.write_string(&b, "Windows Server 2008 R2")
+			}
+
+		case 2:
+			switch osvi.wProductType {
+			case 1: // VER_NT_WORKSTATION:
+				strings.write_string(&b, "Windows 8 ")
+				format_windows_product_type(&b, product_type)
+			case 3:
+				strings.write_string(&b, "Windows Server 2012")
+			}
+
+		case 3:
+			switch osvi.wProductType {
+			case 1: // VER_NT_WORKSTATION:
+				strings.write_string(&b, "Windows 8.1 ")
+				format_windows_product_type(&b, product_type)
+			case 3:
+				strings.write_string(&b, "Windows Server 2012 R2")
+			}
+		}
+
+	case 5:
+		switch osvi.dwMinorVersion {
+		case 0:
+			strings.write_string(&b, "Windows 2000")
+		case 1:
+			strings.write_string(&b, "Windows XP")
+		case 2:
+			strings.write_string(&b, "Windows Server 2003")
+		}
+	}
+
+	// Grab DisplayVersion
+	os_version.version = format_display_version(&b)
+
+	// Grab build number and UBR
+	os_version.build[1]  = format_build_number(&b, int(osvi.dwBuildNumber))
+
+	// Finish the string
+	os_version.as_string = strings.to_string(b)
+
+	format_windows_product_type :: proc (b: ^strings.Builder, prod_type: sys.Windows_Product_Type) {
+		#partial switch prod_type {
+		case .ULTIMATE:
+			strings.write_string(b, "Ultimate")
+
+		case .HOME_BASIC:
+			strings.write_string(b, "Home Basic")
+
+		case .HOME_PREMIUM:
+			strings.write_string(b, "Home Premium")
+
+		case .ENTERPRISE:
+			strings.write_string(b, "Enterprise")
+
+		case .CORE:
+			strings.write_string(b, "Home Basic")
+
+		case .HOME_BASIC_N:
+			strings.write_string(b, "Home Basic N")
+
+		case .EDUCATION:
+			strings.write_string(b, "Education")
+
+		case .EDUCATION_N:
+			strings.write_string(b, "Education N")
+
+		case .BUSINESS:
+			strings.write_string(b, "Business")
+
+		case .STANDARD_SERVER:
+			strings.write_string(b, "Standard Server")
+
+		case .DATACENTER_SERVER:
+			strings.write_string(b, "Datacenter")
+
+		case .SMALLBUSINESS_SERVER:
+			strings.write_string(b, "Windows Small Business Server")
+
+		case .ENTERPRISE_SERVER:
+			strings.write_string(b, "Enterprise Server")
+
+		case .STARTER:
+			strings.write_string(b, "Starter")
+
+		case .DATACENTER_SERVER_CORE:
+			strings.write_string(b, "Datacenter Server Core")
+
+		case .STANDARD_SERVER_CORE:
+			strings.write_string(b, "Server Standard Core")
+
+		case .ENTERPRISE_SERVER_CORE:
+			strings.write_string(b, "Enterprise Server Core")
+
+		case .BUSINESS_N:
+			strings.write_string(b, "Business N")
+
+		case .HOME_SERVER:
+			strings.write_string(b, "Home Server")
+
+		case .SERVER_FOR_SMALLBUSINESS:
+			strings.write_string(b, "Windows Server 2008 for Windows Essential Server Solutions")
+
+		case .SMALLBUSINESS_SERVER_PREMIUM:
+			strings.write_string(b, "Small Business Server Premium")
+
+		case .HOME_PREMIUM_N:
+			strings.write_string(b, "Home Premium N")
+
+		case .ENTERPRISE_N:
+			strings.write_string(b, "Enterprise N")
+
+		case .ULTIMATE_N:
+			strings.write_string(b, "Ultimate N")
+
+		case .HYPERV:
+			strings.write_string(b, "HyperV")
+
+		case .STARTER_N:
+			strings.write_string(b, "Starter N")
+
+		case .PROFESSIONAL:
+			strings.write_string(b, "Professional")
+
+		case .PROFESSIONAL_N:
+			strings.write_string(b, "Professional N")
+
+		case:
+			strings.write_string(b, "Unknown Edition")
+		}
+	}
+
+	// Grab Windows DisplayVersion (like 20H02)
+	format_display_version :: proc (b: ^strings.Builder) -> (version: string) {
+		dv, ok := read_reg(
+			sys.HKEY_LOCAL_MACHINE,
+			"SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion",
+			"DisplayVersion",
+			string,
+		)
+		defer delete(dv) // It'll be interned into `version_string_buf`
+
+		if ok {
+			strings.write_string(b, " (version: ")
+			l := strings.builder_len(b^)
+			strings.write_string(b, dv)
+			version = strings.to_string(b^)[l:][:len(dv)]
+			strings.write_rune(b, ')')
+		}
+		return
+	}
+
+	// Grab build number and UBR
+	format_build_number :: proc (b: ^strings.Builder, major_build: int) -> (ubr: int) {
+		res, ok := read_reg(
+			sys.HKEY_LOCAL_MACHINE,
+			"SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion",
+			"UBR",
+			i32,
+		)
+
+		if ok {
+			ubr = int(res)
+			strings.write_string(b, ", build: ")
+			strings.write_int(b, major_build)
+			strings.write_rune(b, '.')
+			strings.write_int(b, ubr)
+		}
+		return
+	}
+}
+
+@(init)
+init_ram :: proc() {
+	state: sys.MEMORYSTATUSEX
+
+	state.dwLength = size_of(state)
+	ok := sys.GlobalMemoryStatusEx(&state)
+	if !ok {
+		return
+	}
+	ram = RAM{
+		total_ram  = int(state.ullTotalPhys),
+		free_ram   = int(state.ullAvailPhys),
+		total_swap = int(state.ullTotalPageFil),
+		free_swap  = int(state.ullAvailPageFil),
+	}
+}
+
+@(init, private)
+init_gpu_info :: proc() {
+
+	GPU_INFO_BASE :: "SYSTEM\\ControlSet001\\Control\\Class\\{4d36e968-e325-11ce-bfc1-08002be10318}\\"
+
+	gpu_list: [dynamic]GPU
+	gpu_index: int
+
+	for {
+		key := fmt.tprintf("%v\\%04d", GPU_INFO_BASE, gpu_index)
+
+		if vendor, ok := read_reg(sys.HKEY_LOCAL_MACHINE, key, "ProviderName", string); ok {
+			append(&gpu_list, GPU{vendor_name = vendor})
+		} else {
+			break
+		}
+
+		if desc, ok := read_reg(sys.HKEY_LOCAL_MACHINE, key, "DriverDesc", string); ok {
+			gpu_list[gpu_index].model_name = desc
+		}
+
+		if vram, ok := read_reg(sys.HKEY_LOCAL_MACHINE, key, "HardwareInformation.qwMemorySize", i64); ok {
+			gpu_list[gpu_index].total_ram = int(vram)
+		}
+		gpu_index += 1
+	}
+	gpus = gpu_list[:]
+}
+
+@(private)
+read_reg :: proc(hkey: sys.HKEY, subkey, val: string, $T: typeid) -> (res: T, ok: bool) {
+	BUF_SIZE :: 1024
+
+	if len(subkey) == 0 || len(val) == 0 {
+		return {}, false
+	}
+
+	key_name_wide := make([]u16, BUF_SIZE, context.temp_allocator)
+	val_name_wide := make([]u16, BUF_SIZE, context.temp_allocator)
+
+	utf16.encode_string(key_name_wide, subkey)
+	utf16.encode_string(val_name_wide, val)
+
+	when T == string {
+		result_wide := make([]u16, BUF_SIZE, context.temp_allocator)
+		result_size := sys.DWORD(BUF_SIZE * size_of(u16))
+
+		status := sys.RegGetValueW(
+			hkey,
+			&key_name_wide[0],
+			&val_name_wide[0],
+			sys.RRF_RT_REG_SZ,
+			nil,
+			raw_data(result_wide[:]),
+			&result_size,
+		)
+		if status != 0 {
+			// Couldn't retrieve string
+			return
+		}
+
+		// Result string will be allocated for the caller.
+		result_utf8 := make([]u8, BUF_SIZE * 4, context.temp_allocator)
+		utf16.decode_to_utf8(result_utf8, result_wide[:result_size])
+		return strings.clone_from_cstring(cstring(raw_data(result_utf8))), true
+
+	} else when T == i32 {
+		result_size := sys.DWORD(size_of(i32))
+		status := sys.RegGetValueW(
+			hkey,
+			&key_name_wide[0],
+			&val_name_wide[0],
+			sys.RRF_RT_REG_DWORD,
+			nil,
+			&res,
+			&result_size,
+		)
+		return res, status == 0
+
+	} else when T == i64 {
+		result_size := sys.DWORD(size_of(i64))
+		status := sys.RegGetValueW(
+			hkey,
+			&key_name_wide[0],
+			&val_name_wide[0],
+			sys.RRF_RT_REG_QWORD,
+			nil,
+			&res,
+			&result_size,
+		)
+		return res, status == 0
+	} else {
+		#assert(false, "Unhandled type for read_reg")
+	}
+	return
+}

+ 45 - 0
core/sys/info/sysinfo.odin

@@ -0,0 +1,45 @@
+package sysinfo
+
+when !(ODIN_ARCH == .amd64 || ODIN_ARCH == .i386 || ODIN_ARCH == .arm32 || ODIN_ARCH == .arm64) {
+	#assert(false, "This package is unsupported on this architecture.")
+}
+
+os_version: OS_Version
+ram:        RAM
+gpus:       []GPU
+
+OS_Version_Platform :: enum {
+	Unknown,
+	Windows,
+	Linux,
+	MacOS,
+	iOS,
+	FreeBSD,
+	OpenBSD,
+	NetBSD,
+}
+
+OS_Version :: struct {
+	platform: OS_Version_Platform,
+
+	major:     int,
+	minor:     int,
+	patch:     int,
+	build:     [2]int,
+	version:   string,
+
+	as_string: string,
+}
+
+RAM :: struct {
+	total_ram:  int,
+	free_ram:   int,
+	total_swap: int,
+	free_swap:  int,
+}
+
+GPU :: struct {
+	vendor_name: string,
+	model_name:  string,
+	total_ram:   int,
+}

+ 7 - 0
core/sys/unix/syscalls_freebsd.odin

@@ -0,0 +1,7 @@
+package unix
+
+// FreeBSD 13 syscall numbers
+// See: https://alfonsosiciliano.gitlab.io/posts/2021-01-02-freebsd-system-calls-table.html
+
+SYS_uname  : uintptr : 164
+SYS_sysctl : uintptr : 202

+ 3 - 3
core/sys/unix/syscalls_linux.odin

@@ -1568,7 +1568,7 @@ sys_gettid :: proc "contextless" () -> int {
 }
 
 sys_getrandom :: proc "contextless" (buf: [^]byte, buflen: int, flags: uint) -> int {
-	return cast(int)intrinsics.syscall(SYS_getrandom, buf, cast(uintptr)(buflen), cast(uintptr)(flags))
+	return cast(int)intrinsics.syscall(SYS_getrandom, uintptr(buf), uintptr(buflen), uintptr(flags))
 }
 
 sys_open :: proc "contextless" (path: cstring, flags: int, mode: int = 0o000) -> int {
@@ -1622,7 +1622,7 @@ sys_lseek :: proc "contextless" (fd: int, offset: i64, whence: int) -> i64 {
 		low := uintptr(offset & 0xFFFFFFFF)
 		high := uintptr(offset >> 32)
 		result: i64
-		res := i64(intrinsics.syscall(SYS__llseek, uintptr(fd), high, low, &result, uintptr(whence)))
+		res := i64(intrinsics.syscall(SYS__llseek, uintptr(fd), high, low, uintptr(&result), uintptr(whence)))
 		return res if res < 0 else result
 	}
 }
@@ -1748,7 +1748,7 @@ sys_unlink :: proc "contextless" (path: cstring) -> int {
 }
 
 sys_unlinkat :: proc "contextless" (dfd: int, path: cstring, flag: int = 0) -> int {
-	return int(intrinsics.syscall(SYS_unlinkat, uintptr(dfd), uintptr(rawptr(path)), flag))
+	return int(intrinsics.syscall(SYS_unlinkat, uintptr(dfd), uintptr(rawptr(path)), uintptr(flag)))
 }
 
 sys_rmdir :: proc "contextless" (path: cstring) -> int {

+ 6 - 0
core/sys/unix/syscalls_openbsd.odin

@@ -0,0 +1,6 @@
+package unix
+
+// OpenBSD 7.1 syscall numbers
+// See: /usr/include/sys/syscall.h
+
+SYS_sysctl : uintptr : 202

+ 45 - 0
core/sys/unix/sysctl_darwin.odin

@@ -0,0 +1,45 @@
+//+build darwin
+package unix
+
+import "core:sys/darwin"
+import "core:intrinsics"
+
+_ :: darwin
+
+sysctl :: proc(mib: []i32, val: ^$T) -> (ok: bool) {
+	mib := mib
+	result_size := i64(size_of(T))
+
+	res := intrinsics.syscall(
+		darwin.unix_offset_syscall(.sysctl),
+		uintptr(raw_data(mib)), uintptr(len(mib)),
+		uintptr(val), uintptr(&result_size),
+		uintptr(0), uintptr(0),
+	)
+	return res == 0
+}
+
+// See sysctl.h for darwin for details
+CTL_KERN    :: 1
+	KERN_OSTYPE    :: 1  // Darwin
+	KERN_OSRELEASE :: 2  // 21.5.0 for 12.4 Monterey
+	KERN_OSREV     :: 3  // i32: system revision
+	KERN_VERSION   :: 4  // Darwin Kernel Version 21.5.0: Tue Apr 26 21:08:22 PDT 2022; root:darwin-8020.121.3~4/RELEASE_X86_64
+	KERN_OSRELDATE :: 26 // i32: OS release date
+	KERN_OSVERSION :: 65 // Build number, e.g. 21F79
+CTL_VM      :: 2
+CTL_VFS     :: 3
+CTL_NET     :: 4
+CTL_DEBUG   :: 5
+CTL_HW      :: 6
+	HW_MACHINE      :: 1  // x86_64
+	HW_MODEL        :: 2  // MacbookPro14,1
+	HW_NCPU         :: 3  /* int: number of cpus */
+	HW_BYTEORDER    :: 4  /* int: machine byte order */
+	HW_MACHINE_ARCH :: 12 /* string: machine architecture */
+	HW_VECTORUNIT   :: 13 /* int: has HW vector unit? */
+	HW_MEMSIZE      :: 24 // u64
+	HW_AVAILCPU     :: 25 /* int: number of available CPUs */
+
+CTL_MACHDEP :: 7
+CTL_USER    :: 8

+ 44 - 0
core/sys/unix/sysctl_freebsd.odin

@@ -0,0 +1,44 @@
+//+build freebsd
+package unix
+
+import "core:intrinsics"
+
+sysctl :: proc(mib: []i32, val: ^$T) -> (ok: bool) {
+	mib := mib
+	result_size := i64(size_of(T))
+
+	res := intrinsics.syscall(SYS_sysctl,
+		uintptr(raw_data(mib)), uintptr(len(mib)),
+		uintptr(val), uintptr(&result_size),
+		uintptr(0), uintptr(0),
+	)
+	return res == 0
+}
+
+// See /usr/include/sys/sysctl.h for details
+CTL_SYSCTL :: 0
+CTL_KERN   :: 1
+	KERN_OSTYPE    :: 1
+	KERN_OSRELEASE :: 2
+	KERN_OSREV     :: 3
+	KERN_VERSION   :: 4
+CTL_VM     :: 2
+CTL_VFS    :: 3
+CTL_NET    :: 4
+CTL_DEBUG  :: 5
+CTL_HW     :: 6
+	HW_MACHINE      ::  1
+	HW_MODEL        ::  2
+	HW_NCPU         ::  3
+	HW_BYTEORDER    ::  4
+	HW_PHYSMEM      ::  5
+	HW_USERMEM      ::  6
+	HW_PAGESIZE     ::  7
+	HW_DISKNAMES    ::  8
+	HW_DISKSTATS    ::  9
+	HW_FLOATINGPT   :: 10
+	HW_MACHINE_ARCH :: 11
+	HW_REALMEM      :: 12
+CTL_MACHDEP  :: 7
+CTL_USER     :: 8
+CTL_P1003_1B :: 9

+ 49 - 0
core/sys/unix/sysctl_openbsd.odin

@@ -0,0 +1,49 @@
+//+build openbsd
+package unix
+
+import "core:c"
+foreign import libc "system:c"
+
+@(default_calling_convention="c")
+foreign libc {
+	@(link_name="sysctl")	_unix_sysctl    :: proc(name: [^]i32, namelen: u32, oldp: rawptr, oldlenp: ^c.size_t, newp: rawptr, newlen: c.size_t) -> i32 ---
+}
+
+sysctl :: proc(mib: []i32, val: ^$T) -> (ok: bool) {
+	mib := mib
+	result_size := c.size_t(size_of(T))
+	res := _unix_sysctl(raw_data(mib), u32(len(mib)), val, &result_size, nil, 0)
+	return res == 0
+}
+
+// See /usr/include/sys/sysctl.h for details
+CTL_SYSCTL :: 0
+CTL_KERN   :: 1
+	KERN_OSTYPE    :: 1
+	KERN_OSRELEASE :: 2
+	KERN_OSREV     :: 3
+	KERN_VERSION   :: 4
+CTL_VM     :: 2
+CTL_FS     :: 3
+CTL_NET    :: 4
+CTL_DEBUG  :: 5
+CTL_HW     :: 6
+	HW_MACHINE   ::  1
+	HW_MODEL     ::  2
+	HW_NCPU      ::  3
+	HW_BYTEORDER ::  4
+	HW_PHYSMEM   ::  5
+	HW_USERMEM   ::  6
+	HW_PAGESIZE  ::  7
+	HW_DISKNAMES ::  8
+	HW_DISKSTATS ::  9
+	HW_DISKCOUNT :: 10
+	HW_SENSORS   :: 11
+	HW_CPUSPEED  :: 12
+	HW_SETPERF   :: 13
+	HW_VENDOR    :: 14
+	HW_PRODUCT   :: 15
+	HW_VERSION   :: 16
+	HW_SERIALNO  :: 17
+	HW_UUID      :: 18
+	HW_PHYSMEM64 :: 19

+ 2 - 2
core/sys/valgrind/callgrind.odin

@@ -13,10 +13,10 @@ Callgrind_Client_Request :: enum uintptr {
 }
 
 @(require_results)
-callgrind_client_request_expr :: proc "c" (default: uintptr, request: Callgrind_Client_Request, a0, a1, a2, a3, a4: uintptr) -> uintptr {
+callgrind_client_request_expr :: #force_inline proc "c" (default: uintptr, request: Callgrind_Client_Request, a0, a1, a2, a3, a4: uintptr) -> uintptr {
 	return intrinsics.valgrind_client_request(default, uintptr(request), a0, a1, a2, a3, a4)
 }
-callgrind_client_request_stmt :: proc "c" (request: Callgrind_Client_Request, a0, a1, a2, a3, a4: uintptr) {
+callgrind_client_request_stmt :: #force_inline proc "c" (request: Callgrind_Client_Request, a0, a1, a2, a3, a4: uintptr) {
 	_ = intrinsics.valgrind_client_request(0, uintptr(request), a0, a1, a2, a3, a4)
 }
 

+ 178 - 0
core/sys/valgrind/helgrind.odin

@@ -0,0 +1,178 @@
+//+build amd64
+package sys_valgrind
+
+import "core:intrinsics"
+
+Helgrind_Client_Request :: enum uintptr {
+	Clean_Memory = 'H'<<24 | 'G'<<16,
+	Set_My_pthread_t = ('H'<<25 | 'G'<<16)+256,
+	Pthread_Join_Post,
+	Pthread_Mutex_Init_Post,
+	Pthread_Mutex_Destroy_Pre,
+	Pthread_Mutex_Unlock_Pre,
+	Pthread_Mutex_Unlock_Post,
+	Pthread_Mutex_Lock_Pre,
+	Pthread_Mutex_Lock_Post,
+	Pthread_Cond_Signal_Pre,
+	Pthread_Cond_Broadcast_Pre,
+	Pthread_Cond_Wait_Pre,
+	Pthread_Cond_Wait_Post,
+	Pthread_Cond_Destroy_Pre,
+	Pthread_Rwlock_Init_Post,
+	Pthread_Rwlock_Destroy_Pre,
+	Pthread_Rwlock_Lock_Pre,
+	Pthread_Rwlock_Lock_Post,
+	Pthread_Rwlock_Unlock_Pre,
+	Pthread_Rwlock_Unlock_Post,
+	Posix_Sem_Init_Post,
+	Posix_Sem_Destroy_Pre,
+	Posix_Sem_Post_Pre,
+	Posix_Sem_Wait_Post,
+	Pthread_Barrier_Init_Pre,
+	Pthread_Barrier_Wait_Pre,
+	Pthread_Barrier_Destroy_Pre,
+	Pthread_Spin_Init_Or_Unlock_Pre,
+	Pthread_Spin_Init_Or_Unlock_Post,
+	Pthread_Spin_Lock_Pre,
+	Pthread_Spin_Lock_Post,
+	Pthread_Spin_Destroy_Pre,
+	Clientreq_Unimp,
+	Userso_Send_Pre,
+	Userso_Recv_Post,
+	Userso_Forget_All,
+	Reserved2,
+	Reserved3,
+	Reserved4,
+	Arange_Make_Untracked,
+	Arange_Make_Tracked,
+	Pthread_Barrier_Resize_Pre,
+	Clean_Memory_Heapblock,
+	Pthread_Cond_Init_Post,
+}
+
+@(require_results)
+helgrind_client_request_expr :: #force_inline proc "c" (default: uintptr, request: Helgrind_Client_Request, a0, a1, a2, a3, a4: uintptr) -> uintptr {
+	return intrinsics.valgrind_client_request(default, uintptr(request), a0, a1, a2, a3, a4)
+}
+helgrind_client_request_stmt :: #force_inline proc "c" (request: Helgrind_Client_Request, a0, a1, a2, a3, a4: uintptr) {
+	_ = intrinsics.valgrind_client_request(0, uintptr(request), a0, a1, a2, a3, a4)
+}
+
+helgrind_mutex_init_post :: proc "c" (mutex: rawptr, mb_rec: uint) {
+	helgrind_client_request_stmt(.Pthread_Mutex_Init_Post, uintptr(mutex), uintptr(mb_rec), 0, 0, 0)
+}
+helgrind_mutex_destroy_pre :: proc "c" (mutex: rawptr) {
+	helgrind_client_request_stmt(.Pthread_Mutex_Destroy_Pre, uintptr(mutex), 0, 0, 0, 0)
+}
+helgrind_mutex_lock_pre :: proc "c" (mutex: rawptr, is_try_lock: bool) {
+	helgrind_client_request_stmt(.Pthread_Mutex_Lock_Pre, uintptr(mutex), uintptr(is_try_lock), 0, 0, 0)
+}
+helgrind_mutex_lock_post :: proc "c" (mutex: rawptr) {
+	helgrind_client_request_stmt(.Pthread_Mutex_Lock_Post, uintptr(mutex), 0, 0, 0, 0)
+}
+helgrind_mutex_unlock_pre :: proc "c" (mutex: rawptr) {
+	helgrind_client_request_stmt(.Pthread_Mutex_Unlock_Pre, uintptr(mutex), 0, 0, 0, 0)
+}
+helgrind_mutex_unlock_post :: proc "c" (mutex: rawptr) {
+	helgrind_client_request_stmt(.Pthread_Mutex_Unlock_Post, uintptr(mutex), 0, 0, 0, 0)
+}
+
+helgrind_rwlock_init_post :: proc "c" (lock: rawptr) {
+	helgrind_client_request_stmt(.Pthread_Rwlock_Init_Post, uintptr(lock), 0, 0, 0, 0)
+}
+helgrind_rwlock_destroy_pre :: proc "c" (lock: rawptr) {
+	helgrind_client_request_stmt(.Pthread_Rwlock_Destroy_Pre, uintptr(lock), 0, 0, 0, 0)
+}
+helgrind_rwlock_lock_pre :: proc "c" (lock: rawptr, is_w: bool) {
+	helgrind_client_request_stmt(.Pthread_Rwlock_Lock_Pre, uintptr(lock), uintptr(is_w), 0, 0, 0)
+}
+helgrind_rwlock_unlock_post :: proc "c" (lock: rawptr, is_w: bool) {
+	helgrind_client_request_stmt(.Pthread_Rwlock_Unlock_Pre, uintptr(lock), uintptr(is_w), 0, 0, 0)
+}
+
+
+helgrind_sem_init_post :: proc "c" (sem: rawptr, value: uint) {
+	helgrind_client_request_stmt(.Posix_Sem_Init_Post, uintptr(sem), uintptr(value), 0, 0, 0)
+}
+helgrind_sem_wait_post :: proc "c" (sem: rawptr) {
+	helgrind_client_request_stmt(.Posix_Sem_Wait_Post, uintptr(sem), 0, 0, 0, 0)
+}
+helgrind_sem_post_pre :: proc "c" (sem: rawptr) {
+	helgrind_client_request_stmt(.Posix_Sem_Post_Pre, uintptr(sem), 0, 0, 0, 0)
+}
+helgrind_sem_destroy_pre :: proc "c" (sem: rawptr) {
+	helgrind_client_request_stmt(.Posix_Sem_Destroy_Pre, uintptr(sem), 0, 0, 0, 0)
+}
+
+
+helgrind_barrier_init_pre :: proc "c" (bar: rawptr, count: uint, resizable: bool) {
+	helgrind_client_request_stmt(.Pthread_Barrier_Init_Pre, uintptr(bar), uintptr(count), uintptr(resizable), 0, 0)
+}
+helgrind_barrier_wait_pre :: proc "c" (bar: rawptr) {
+	helgrind_client_request_stmt(.Pthread_Barrier_Wait_Pre, uintptr(bar), 0, 0, 0, 0)
+}
+helgrind_barrier_resize_pre :: proc "c" (bar: rawptr, new_count: uint) {
+	helgrind_client_request_stmt(.Pthread_Barrier_Resize_Pre, uintptr(bar), uintptr(new_count), 0, 0, 0)
+}
+helgrind_barrier_destroy_pre :: proc "c" (bar: rawptr) {
+	helgrind_client_request_stmt(.Pthread_Barrier_Destroy_Pre, uintptr(bar), 0, 0, 0, 0)
+}
+
+
+
+helgrind_clean_memory :: proc "c" (qzz_start: rawptr, qzz_len: uint) {
+	helgrind_client_request_stmt(.Clean_Memory, uintptr(qzz_start), uintptr(qzz_len), 0, 0, 0)
+}
+helgrind_clean_memory_slice :: proc "c" (qzz: []byte) {
+	helgrind_client_request_stmt(.Clean_Memory, uintptr(raw_data(qzz)), uintptr(len(qzz)), 0, 0, 0)
+}
+helgrind_clean_memory_heap_block :: proc "c" (qzz_blockstart: rawptr) -> int {
+	return int(helgrind_client_request_expr(~uintptr(1), .Clean_Memory_Heapblock, uintptr(qzz_blockstart), 0, 0, 0, 0))
+}
+
+
+helgrind_disable_checking :: proc "c" (qzz_start: rawptr, qzz_len: uint) {
+	helgrind_client_request_stmt(.Arange_Make_Untracked, uintptr(qzz_start), uintptr(qzz_len), 0, 0, 0)
+}
+helgrind_enable_checking :: proc "c" (qzz_start: rawptr, qzz_len: uint) {
+	helgrind_client_request_stmt(.Arange_Make_Tracked, uintptr(qzz_start), uintptr(qzz_len), 0, 0, 0)
+}
+
+
+helgrind_cond_init_post :: proc "c" (cond: rawptr) {
+	helgrind_client_request_stmt(.Pthread_Cond_Init_Post, uintptr(cond), 0, 0, 0, 0)
+}
+helgrind_cond_destroy_pre :: proc "c" (cond: rawptr) {
+	helgrind_client_request_stmt(.Pthread_Cond_Destroy_Pre, uintptr(cond), 0, 0, 0, 0)
+}
+helgrind_cond_signal_pre :: proc "c" (cond: rawptr) {
+	helgrind_client_request_stmt(.Pthread_Cond_Signal_Pre, uintptr(cond), 0, 0, 0, 0)
+}
+helgrind_cond_broadcast_pre :: proc "c" (cond: rawptr) {
+	helgrind_client_request_stmt(.Pthread_Cond_Broadcast_Pre, uintptr(cond), 0, 0, 0, 0)
+}
+helgrind_cond_wait_pre :: proc "c" (cond: rawptr, lock: rawptr) -> bool {
+	return 0 != helgrind_client_request_expr(0, .Pthread_Cond_Wait_Pre, uintptr(cond), uintptr(lock), 0, 0, 0)
+}
+helgrind_cond_wait_post :: proc "c" (cond: rawptr, lock: rawptr) -> bool {
+	return 0 != helgrind_client_request_expr(0, .Pthread_Cond_Wait_Post, uintptr(cond), uintptr(lock), 0, 0, 0)
+}
+
+
+helgrind_client_request_unimp :: #force_inline proc "c" (msg: cstring) {
+	helgrind_client_request_stmt(.Clientreq_Unimp, uintptr(rawptr(msg)), 0, 0, 0, 0)
+}
+
+
+helgrind_annotate_condvar_lock_wait :: #force_inline proc "c" (cv: rawptr, lock: rawptr) {
+	helgrind_client_request_unimp("ANNOTATE_CONDVAR_LOCK_WAIT")
+}
+helgrind_annotate_condvar_wait :: proc "c" (cv: rawptr) {
+	helgrind_client_request_unimp("ANNOTATE_CONDVAR_WAIT")
+}
+helgrind_annotate_condvar_signal :: proc "c" (cv: rawptr) {
+	helgrind_client_request_unimp("ANNOTATE_CONDVAR_SIGNAL")
+}
+helgrind_annotate_condvar_signal_all :: proc "c" (cv: rawptr) {
+	helgrind_client_request_unimp("ANNOTATE_CONDVAR_SIGNAL_ALL")
+}

+ 2 - 2
core/sys/valgrind/memcheck.odin

@@ -22,10 +22,10 @@ Mem_Check_Client_Request :: enum uintptr {
 }
 
 @(require_results)
-mem_check_client_request_expr :: proc "c" (default: uintptr, request: Mem_Check_Client_Request, a0, a1, a2, a3, a4: uintptr) -> uintptr {
+mem_check_client_request_expr :: #force_inline proc "c" (default: uintptr, request: Mem_Check_Client_Request, a0, a1, a2, a3, a4: uintptr) -> uintptr {
 	return intrinsics.valgrind_client_request(default, uintptr(request), a0, a1, a2, a3, a4)
 }
-mem_check_client_request_stmt :: proc "c" (request: Mem_Check_Client_Request, a0, a1, a2, a3, a4: uintptr) {
+mem_check_client_request_stmt :: #force_inline proc "c" (request: Mem_Check_Client_Request, a0, a1, a2, a3, a4: uintptr) {
 	_ = intrinsics.valgrind_client_request(0, uintptr(request), a0, a1, a2, a3, a4)
 }
 

+ 4 - 4
core/sys/valgrind/valgrind.odin

@@ -38,10 +38,10 @@ Client_Request :: enum uintptr {
 }
 
 @(require_results)
-client_request_expr :: proc "c" (default: uintptr, request: Client_Request, a0, a1, a2, a3, a4: uintptr) -> uintptr {
+client_request_expr :: #force_inline proc "c" (default: uintptr, request: Client_Request, a0, a1, a2, a3, a4: uintptr) -> uintptr {
 	return intrinsics.valgrind_client_request(default, uintptr(request), a0, a1, a2, a3, a4)
 }
-client_request_stmt :: proc "c" (request: Client_Request, a0, a1, a2, a3, a4: uintptr) {
+client_request_stmt :: #force_inline proc "c" (request: Client_Request, a0, a1, a2, a3, a4: uintptr) {
 	_ = intrinsics.valgrind_client_request(0, uintptr(request), a0, a1, a2, a3, a4)
 }
 
@@ -49,8 +49,8 @@ client_request_stmt :: proc "c" (request: Client_Request, a0, a1, a2, a3, a4: ui
 //     0 - running natively
 //     1 - running under Valgrind
 //     2 - running under Valgrind which is running under another Valgrind
-running_on_valgrind :: proc "c" () -> uintptr {
-	return client_request_expr(0, .Running_On_Valgrind, 0, 0, 0, 0, 0)
+running_on_valgrind :: proc "c" () -> uint {
+	return uint(client_request_expr(0, .Running_On_Valgrind, 0, 0, 0, 0, 0))
 }
 
 // Discard translation of code in the slice qzz. Useful if you are debugging a JIT-er or some such,

+ 36 - 10
core/sys/windows/kernel32.odin

@@ -24,6 +24,8 @@ foreign kernel32 {
 	                       lpMode: LPDWORD) -> BOOL ---
 	SetConsoleMode :: proc(hConsoleHandle: HANDLE,
 	                       dwMode: DWORD) -> BOOL ---
+	SetConsoleCursorPosition :: proc(hConsoleHandle: HANDLE,
+						   dwCursorPosition: COORD) -> BOOL ---
 
 	GetFileInformationByHandle :: proc(hFile: HANDLE, lpFileInformation: LPBY_HANDLE_FILE_INFORMATION) -> BOOL ---
 	SetHandleInformation :: proc(hObject: HANDLE,
@@ -94,6 +96,15 @@ foreign kernel32 {
 		dwCreationFlags: DWORD,
 		lpThreadId: LPDWORD,
 	) -> HANDLE ---
+	CreateRemoteThread :: proc(
+		hProcess: HANDLE,
+		lpThreadAttributes: LPSECURITY_ATTRIBUTES,
+		dwStackSize: SIZE_T,
+		lpStartAddress: proc "stdcall" (rawptr) -> DWORD,
+		lpParameter: LPVOID,
+		dwCreationFlags: DWORD,
+		lpThreadId: LPDWORD,
+	) -> HANDLE ---
 	SwitchToThread :: proc() -> BOOL ---
 	ResumeThread :: proc(thread: HANDLE) -> DWORD ---
 	GetThreadPriority :: proc(thread: HANDLE) -> c_int ---
@@ -326,6 +337,15 @@ foreign kernel32 {
 	SetEndOfFile :: proc(hFile: HANDLE) -> BOOL ---
 
 	CreatePipe :: proc(hReadPipe, hWritePipe: ^HANDLE, lpPipeAttributes: LPSECURITY_ATTRIBUTES, nSize: DWORD) -> BOOL ---
+
+	ConnectNamedPipe :: proc(hNamedPipe: HANDLE, lpOverlapped: LPOVERLAPPED,) -> BOOL ---
+	DisconnectNamedPipe :: proc(hNamedPipe: HANDLE,) -> BOOL ---
+	WaitNamedPipeW :: proc(lpNamedPipeName: LPCWSTR, nTimeOut: DWORD,) -> BOOL ---
+
+	SetConsoleCtrlHandler :: proc(HandlerRoutine: PHANDLER_ROUTINE, Add: BOOL) -> BOOL ---
+	GenerateConsoleCtrlEvent :: proc(dwCtrlEvent: DWORD, dwProcessGroupId: DWORD) -> BOOL ---
+	FreeConsole :: proc() -> BOOL ---
+	GetConsoleWindow :: proc() -> HWND ---
 }
 
 
@@ -637,6 +657,13 @@ foreign kernel32 {
 	) -> BOOL ---
 }
 
+@(default_calling_convention="stdcall")
+foreign kernel32 {
+	GlobalMemoryStatusEx :: proc(
+		lpBuffer: ^MEMORYSTATUSEX,
+	) -> BOOL ---
+}
+
 PBAD_MEMORY_CALLBACK_ROUTINE :: #type proc "stdcall" ()
 
 @(default_calling_convention="stdcall")
@@ -782,15 +809,14 @@ foreign kernel32 {
 
 @(default_calling_convention="stdcall")
 foreign kernel32 {
-	@(link_name="SetConsoleCtrlHandler") set_console_ctrl_handler :: proc(handler: Handler_Routine, add: BOOL) -> BOOL ---
+	GetProductInfo :: proc(
+		OSMajorVersion: DWORD,
+		OSMinorVersion: DWORD,
+		SpMajorVersion: DWORD,
+		SpMinorVersion: DWORD,
+		product_type: ^Windows_Product_Type,
+	) -> BOOL ---
 }
 
-Handler_Routine :: proc(dwCtrlType: Control_Event) -> BOOL
-
-Control_Event :: enum DWORD {
-	control_c = 0,
-	_break    = 1,
-	close     = 2,
-	logoff    = 5,
-	shutdown  = 6,
-}
+HandlerRoutine :: proc "stdcall" (dwCtrlType: DWORD) -> BOOL
+PHANDLER_ROUTINE :: HandlerRoutine

+ 171 - 0
core/sys/windows/types.odin

@@ -17,6 +17,7 @@ size_t      :: c.size_t
 wchar_t     :: c.wchar_t
 
 DWORD :: c_ulong
+DWORDLONG :: c.ulonglong
 QWORD :: c.ulonglong
 HANDLE :: distinct LPVOID
 HINSTANCE :: HANDLE
@@ -978,6 +979,35 @@ WS_TILEDWINDOW      : UINT : WS_OVERLAPPED | WS_CAPTION | WS_SYSMENU | WS_THICKF
 WS_VISIBLE          : UINT : 0x1000_0000
 WS_VSCROLL          : UINT : 0x0020_0000
 
+WS_EX_ACCEPTFILES           : UINT : 0x0000_0010
+WS_EX_APPWINDOW             : UINT : 0x0004_0000
+WS_EX_CLIENTEDGE            : UINT : 0x0000_0200
+WS_EX_COMPOSITED            : UINT : 0x0200_0000
+WS_EX_CONTEXTHELP           : UINT : 0x0000_0400
+WS_EX_CONTROLPARENT         : UINT : 0x0001_0000
+WS_EX_DLGMODALFRAME         : UINT : 0x0000_0001
+WS_EX_DRAGDETECT            : UINT : 0x0000_0002 // undocumented
+WS_EX_LAYERED               : UINT : 0x0008_0000
+WS_EX_LAYOUTRTL             : UINT : 0x0040_0000
+WS_EX_LEFT                  : UINT : 0x0000_0000
+WS_EX_LEFTSCROLLBAR         : UINT : 0x0000_4000
+WS_EX_LTRREADING            : UINT : 0x0000_0000
+WS_EX_MDICHILD              : UINT : 0x0000_0040
+WS_EX_NOACTIVATE            : UINT : 0x0800_0000
+WS_EX_NOINHERITLAYOUT       : UINT : 0x0010_0000
+WS_EX_NOPARENTNOTIFY        : UINT : 0x0000_0004
+WS_EX_NOREDIRECTIONBITMAP   : UINT : 0x0020_0000
+WS_EX_OVERLAPPEDWINDOW      : UINT : WS_EX_WINDOWEDGE | WS_EX_CLIENTEDGE
+WS_EX_PALETTEWINDOW         : UINT : WS_EX_WINDOWEDGE | WS_EX_TOOLWINDOW | WS_EX_TOPMOST
+WS_EX_RIGHT                 : UINT : 0x0000_1000
+WS_EX_RIGHTSCROLLBAR        : UINT : 0x0000_0000
+WS_EX_RTLREADING            : UINT : 0x0000_2000
+WS_EX_STATICEDGE            : UINT : 0x0002_0000
+WS_EX_TOOLWINDOW            : UINT : 0x0000_0080
+WS_EX_TOPMOST               : UINT : 0x0000_0008
+WS_EX_TRANSPARENT           : UINT : 0x0000_0020
+WS_EX_WINDOWEDGE            : UINT : 0x0000_0100
+
 PBS_SMOOTH   :: 0x01
 PBS_VERTICAL :: 0x04
 
@@ -1628,6 +1658,8 @@ CONDITION_VARIABLE_INIT :: CONDITION_VARIABLE{}
 SRWLOCK_INIT :: SRWLOCK{}
 
 DETACHED_PROCESS: DWORD : 0x00000008
+CREATE_NEW_CONSOLE: DWORD : 0x00000010
+CREATE_NO_WINDOW: DWORD : 0x08000000
 CREATE_NEW_PROCESS_GROUP: DWORD : 0x00000200
 CREATE_UNICODE_ENVIRONMENT: DWORD : 0x00000400
 STARTF_USESTDHANDLES: DWORD : 0x00000100
@@ -1689,6 +1721,7 @@ PIPE_WAIT: DWORD : 0x00000000
 PIPE_TYPE_BYTE: DWORD : 0x00000000
 PIPE_REJECT_REMOTE_CLIENTS: DWORD : 0x00000008
 PIPE_READMODE_BYTE: DWORD : 0x00000000
+PIPE_ACCEPT_REMOTE_CLIENTS: DWORD : 0x00000000
 
 FD_SETSIZE :: 64
 
@@ -3265,3 +3298,141 @@ IFileSaveDialogVtbl :: struct {
 	GetProperties:          proc "stdcall" (this: ^IFileSaveDialog, ppStore: ^^IPropertyStore) -> HRESULT,
 	ApplyProperties:        proc "stdcall" (this: ^IFileSaveDialog, psi: ^IShellItem, pStore: ^IPropertyStore, hwnd: HWND, pSink: ^IFileOperationProgressSink) -> HRESULT,
 }
+
+MEMORYSTATUSEX :: struct {
+	dwLength:                DWORD,
+	dwMemoryLoad:            DWORD,
+	ullTotalPhys:            DWORDLONG,
+	ullAvailPhys:            DWORDLONG,
+	ullTotalPageFil:         DWORDLONG,
+	ullAvailPageFil:         DWORDLONG,
+	ullTotalVirtual:         DWORDLONG,
+	ullAvailVirtual:         DWORDLONG,
+	ullAvailExtendedVirtual: DWORDLONG,
+}
+
+Windows_Product_Type :: enum DWORD {
+	BUSINESS                            = 0x00000006, // Business
+	BUSINESS_N                          = 0x00000010, // Business N
+	CLUSTER_SERVER                      = 0x00000012, // HPC Edition
+	CLUSTER_SERVER_V                    = 0x00000040, // Server Hyper Core V
+	CORE                                = 0x00000065, // Windows 10 Home
+	CORE_COUNTRYSPECIFIC                = 0x00000063, // Windows 10 Home China
+	CORE_N                              = 0x00000062, // Windows 10 Home N
+	CORE_SINGLELANGUAGE                 = 0x00000064, // Windows 10 Home Single Language
+	DATACENTER_EVALUATION_SERVER        = 0x00000050, // Server Datacenter (evaluation installation)
+	DATACENTER_A_SERVER_CORE            = 0x00000091, // Server Datacenter, Semi-Annual Channel (core installation)
+	STANDARD_A_SERVER_CORE              = 0x00000092, // Server Standard, Semi-Annual Channel (core installation)
+	DATACENTER_SERVER                   = 0x00000008, // Server Datacenter (full installation. For Server Core installations of Windows Server 2012 and later, use the method, Determining whether Server Core is running.)
+	DATACENTER_SERVER_CORE              = 0x0000000C, // Server Datacenter (core installation, Windows Server 2008 R2 and earlier)
+	DATACENTER_SERVER_CORE_V            = 0x00000027, // Server Datacenter without Hyper-V (core installation)
+	DATACENTER_SERVER_V                 = 0x00000025, // Server Datacenter without Hyper-V (full installation)
+	EDUCATION                           = 0x00000079, // Windows 10 Education
+	EDUCATION_N                         = 0x0000007A, // Windows 10 Education N
+	ENTERPRISE                          = 0x00000004, // Windows 10 Enterprise
+	ENTERPRISE_E                        = 0x00000046, // Windows 10 Enterprise E
+	ENTERPRISE_EVALUATION               = 0x00000048, // Windows 10 Enterprise Evaluation
+	ENTERPRISE_N                        = 0x0000001B, // Windows 10 Enterprise N
+	ENTERPRISE_N_EVALUATION             = 0x00000054, // Windows 10 Enterprise N Evaluation
+	ENTERPRISE_S                        = 0x0000007D, // Windows 10 Enterprise 2015 LTSB
+	ENTERPRISE_S_EVALUATION             = 0x00000081, // Windows 10 Enterprise 2015 LTSB Evaluation
+	ENTERPRISE_S_N                      = 0x0000007E, // Windows 10 Enterprise 2015 LTSB N
+	ENTERPRISE_S_N_EVALUATION           = 0x00000082, // Windows 10 Enterprise 2015 LTSB N Evaluation
+	ENTERPRISE_SERVER                   = 0x0000000A, // Server Enterprise (full installation)
+	ENTERPRISE_SERVER_CORE              = 0x0000000E, // Server Enterprise (core installation)
+	ENTERPRISE_SERVER_CORE_V            = 0x00000029, // Server Enterprise without Hyper-V (core installation)
+	ENTERPRISE_SERVER_IA64              = 0x0000000F, // Server Enterprise for Itanium-based Systems
+	ENTERPRISE_SERVER_V                 = 0x00000026, // Server Enterprise without Hyper-V (full installation)
+	ESSENTIALBUSINESS_SERVER_ADDL       = 0x0000003C, // Windows Essential Server Solution Additional
+	ESSENTIALBUSINESS_SERVER_ADDLSVC    = 0x0000003E, // Windows Essential Server Solution Additional SVC
+	ESSENTIALBUSINESS_SERVER_MGMT       = 0x0000003B, // Windows Essential Server Solution Management
+	ESSENTIALBUSINESS_SERVER_MGMTSVC    = 0x0000003D, // Windows Essential Server Solution Management SVC
+	HOME_BASIC                          = 0x00000002, // Home Basic
+	HOME_BASIC_E                        = 0x00000043, // Not supported
+	HOME_BASIC_N                        = 0x00000005, // Home Basic N
+	HOME_PREMIUM                        = 0x00000003, // Home Premium
+	HOME_PREMIUM_E                      = 0x00000044, // Not supported
+	HOME_PREMIUM_N                      = 0x0000001A, // Home Premium N
+	HOME_PREMIUM_SERVER                 = 0x00000022, // Windows Home Server 2011
+	HOME_SERVER                         = 0x00000013, // Windows Storage Server 2008 R2 Essentials
+	HYPERV                              = 0x0000002A, // Microsoft Hyper-V Server
+	IOTENTERPRISE                       = 0x000000BC, // Windows IoT Enterprise
+	IOTENTERPRISE_S                     = 0x000000BF, // Windows IoT Enterprise LTSC
+	IOTUAP                              = 0x0000007B, // Windows 10 IoT Core
+	IOTUAPCOMMERCIAL                    = 0x00000083, // Windows 10 IoT Core Commercial
+	MEDIUMBUSINESS_SERVER_MANAGEMENT    = 0x0000001E, // Windows Essential Business Server Management Server
+	MEDIUMBUSINESS_SERVER_MESSAGING     = 0x00000020, // Windows Essential Business Server Messaging Server
+	MEDIUMBUSINESS_SERVER_SECURITY      = 0x0000001F, // Windows Essential Business Server Security Server
+	MOBILE_CORE                         = 0x00000068, // Windows 10 Mobile
+	MOBILE_ENTERPRISE                   = 0x00000085, // Windows 10 Mobile Enterprise
+	MULTIPOINT_PREMIUM_SERVER           = 0x0000004D, // Windows MultiPoint Server Premium (full installation)
+	MULTIPOINT_STANDARD_SERVER          = 0x0000004C, // Windows MultiPoint Server Standard (full installation)
+	PRO_WORKSTATION                     = 0x000000A1, // Windows 10 Pro for Workstations
+	PRO_WORKSTATION_N                   = 0x000000A2, // Windows 10 Pro for Workstations N
+	PROFESSIONAL                        = 0x00000030, // Windows 10 Pro
+	PROFESSIONAL_E                      = 0x00000045, // Not supported
+	PROFESSIONAL_N                      = 0x00000031, // Windows 10 Pro N
+	PROFESSIONAL_WMC                    = 0x00000067, // Professional with Media Center
+	SB_SOLUTION_SERVER                  = 0x00000032, // Windows Small Business Server 2011 Essentials
+	SB_SOLUTION_SERVER_EM               = 0x00000036, // Server For SB Solutions EM
+	SERVER_FOR_SB_SOLUTIONS             = 0x00000033, // Server For SB Solutions
+	SERVER_FOR_SB_SOLUTIONS_EM          = 0x00000037, // Server For SB Solutions EM
+	SERVER_FOR_SMALLBUSINESS            = 0x00000018, // Windows Server 2008 for Windows Essential Server Solutions
+	SERVER_FOR_SMALLBUSINESS_V          = 0x00000023, // Windows Server 2008 without Hyper-V for Windows Essential Server Solutions
+	SERVER_FOUNDATION                   = 0x00000021, // Server Foundation
+	SMALLBUSINESS_SERVER                = 0x00000009, // Windows Small Business Server
+	SMALLBUSINESS_SERVER_PREMIUM        = 0x00000019, // Small Business Server Premium
+	SMALLBUSINESS_SERVER_PREMIUM_CORE   = 0x0000003F, // Small Business Server Premium (core installation)
+	SOLUTION_EMBEDDEDSERVER             = 0x00000038, // Windows MultiPoint Server
+	STANDARD_EVALUATION_SERVER          = 0x0000004F, // Server Standard (evaluation installation)
+	STANDARD_SERVER                     = 0x00000007, // Server Standard (full installation. For Server Core installations of Windows Server 2012 and later, use the method, Determining whether Server Core is running.)
+	STANDARD_SERVER_CORE                = 0x0000000D, // Server Standard (core installation, Windows Server 2008 R2 and earlier)
+	STANDARD_SERVER_CORE_V              = 0x00000028, // Server Standard without Hyper-V (core installation)
+	STANDARD_SERVER_V                   = 0x00000024, // Server Standard without Hyper-V
+	STANDARD_SERVER_SOLUTIONS           = 0x00000034, // Server Solutions Premium
+	STANDARD_SERVER_SOLUTIONS_CORE      = 0x00000035, // Server Solutions Premium (core installation)
+	STARTER                             = 0x0000000B, // Starter
+	STARTER_E                           = 0x00000042, // Not supported
+	STARTER_N                           = 0x0000002F, // Starter N
+	STORAGE_ENTERPRISE_SERVER           = 0x00000017, // Storage Server Enterprise
+	STORAGE_ENTERPRISE_SERVER_CORE      = 0x0000002E, // Storage Server Enterprise (core installation)
+	STORAGE_EXPRESS_SERVER              = 0x00000014, // Storage Server Express
+	STORAGE_EXPRESS_SERVER_CORE         = 0x0000002B, // Storage Server Express (core installation)
+	STORAGE_STANDARD_EVALUATION_SERVER  = 0x00000060, // Storage Server Standard (evaluation installation)
+	STORAGE_STANDARD_SERVER             = 0x00000015, // Storage Server Standard
+	STORAGE_STANDARD_SERVER_CORE        = 0x0000002C, // Storage Server Standard (core installation)
+	STORAGE_WORKGROUP_EVALUATION_SERVER = 0x0000005F, // Storage Server Workgroup (evaluation installation)
+	STORAGE_WORKGROUP_SERVER            = 0x00000016, // Storage Server Workgroup
+	STORAGE_WORKGROUP_SERVER_CORE       = 0x0000002D, // Storage Server Workgroup (core installation)
+	ULTIMATE                            = 0x00000001, // Ultimate
+	ULTIMATE_E                          = 0x00000047, // Not supported
+	ULTIMATE_N                          = 0x0000001C, // Ultimate N
+	UNDEFINED                           = 0x00000000, // An unknown product
+	WEB_SERVER                          = 0x00000011, // Web Server (full installation)
+	WEB_SERVER_CORE                     = 0x0000001D, // Web Server (core installation)
+}
+
+ENABLE_ECHO_INPUT : DWORD : 0x0004
+ENABLE_INSERT_MODE : DWORD : 0x0020
+ENABLE_LINE_INPUT : DWORD : 0x0002
+ENABLE_MOUSE_INPUT : DWORD : 0x0010
+ENABLE_PROCESSED_INPUT : DWORD : 0x0001
+ENABLE_QUICK_EDIT_MODE : DWORD : 0x0040
+ENABLE_WINDOW_INPUT : DWORD : 0x0008
+ENABLE_VIRTUAL_TERMINAL_INPUT : DWORD : 0x0200
+ENABLE_PROCESSED_OUTPUT : DWORD : 0x0001
+ENABLE_WRAP_AT_EOL_OUTPUT : DWORD : 0x0002
+ENABLE_VIRTUAL_TERMINAL_PROCESSING : DWORD : 0x0004
+DISABLE_NEWLINE_AUTO_RETURN : DWORD : 0x0008
+ENABLE_LVB_GRID_WORLDWIDE : DWORD : 0x0010
+
+CTRL_C_EVENT : DWORD : 0
+CTRL_BREAK_EVENT : DWORD : 1
+CTRL_CLOSE_EVENT : DWORD : 2
+CTRL_LOGOFF_EVENT : DWORD : 5
+CTRL_SHUTDOWN_EVENT : DWORD : 6
+
+COORD :: struct {
+	X: SHORT,
+	Y: SHORT,
+}

+ 151 - 0
core/sys/windows/user32.odin

@@ -190,6 +190,14 @@ foreign user32 {
 	SetWindowTextW :: proc(hWnd: HWND, lpString: LPCWSTR) -> BOOL ---
 	CallWindowProcW :: proc(lpPrevWndFunc: WNDPROC, hWnd: HWND, Msg: UINT, wParam: WPARAM, lParam: LPARAM) -> LRESULT ---
 	EnableWindow :: proc(hWnd: HWND, bEnable: BOOL) -> BOOL ---
+
+	DefRawInputProc :: proc(paRawInput: ^PRAWINPUT, nInput: INT, cbSizeHeader: UINT) -> LRESULT ---
+	GetRawInputBuffer :: proc(pRawInput: PRAWINPUT, pcbSize: PUINT, cbSizeHeader: UINT) -> UINT ---
+	GetRawInputData :: proc(hRawInput: HRAWINPUT, uiCommand: UINT, pData: LPVOID, pcbSize: PUINT, cbSizeHeader: UINT) -> UINT ---
+	GetRawInputDeviceInfoW :: proc(hDevice: HANDLE, uiCommand: UINT, pData: LPVOID, pcbSize: PUINT) -> UINT ---
+	GetRawInputDeviceList :: proc(pRawInputDeviceList: PRAWINPUTDEVICELIST, puiNumDevices: PUINT, cbSize: UINT) -> UINT ---
+	GetRegisteredRawInputDevices :: proc(pRawInputDevices: PRAWINPUTDEVICE, puiNumDevices: PUINT, cbSize: UINT) -> UINT ---
+	RegisterRawInputDevices :: proc(pRawInputDevices: PCRAWINPUTDEVICE, uiNumDevices: UINT, cbSize: UINT) -> BOOL ---
 }
 
 CreateWindowW :: #force_inline proc "stdcall" (
@@ -277,3 +285,146 @@ DPI_AWARENESS_CONTEXT_SYSTEM_AWARE         :: DPI_AWARENESS_CONTEXT(~uintptr(1))
 DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE    :: DPI_AWARENESS_CONTEXT(~uintptr(2)) // -3
 DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2 :: DPI_AWARENESS_CONTEXT(~uintptr(3)) // -4
 DPI_AWARENESS_CONTEXT_UNAWARE_GDISCALED    :: DPI_AWARENESS_CONTEXT(~uintptr(4)) // -5
+
+RAWINPUTHEADER :: struct {
+	dwType: DWORD,
+	dwSize: DWORD,
+	hDevice: HANDLE,
+	wParam: WPARAM,
+}
+
+RAWHID :: struct {
+	dwSizeHid: DWORD,
+	dwCount: DWORD,
+	bRawData: [1]BYTE,
+}
+
+RAWMOUSE :: struct {
+	usFlags: USHORT,
+	DUMMYUNIONNAME: struct #raw_union {
+		ulButtons: ULONG,
+		DUMMYSTRUCTNAME: struct {
+			usButtonFlags: USHORT,
+			usButtonData: USHORT,
+		},
+	},
+	ulRawButtons: ULONG,
+	lLastX: LONG,
+	lLastY: LONG,
+	ulExtraInformation: ULONG,
+}
+
+RAWKEYBOARD :: struct {
+	MakeCode: USHORT,
+	Flags: USHORT,
+	Rserved: USHORT,
+	VKey: USHORT,
+	Message: UINT,
+	ExtraInformation: ULONG,
+}
+
+RAWINPUT :: struct {
+	header: RAWINPUTHEADER,
+	data: struct #raw_union {
+		mouse: RAWMOUSE,
+		keyboard: RAWKEYBOARD,
+		hid: RAWHID,
+	},
+}
+
+PRAWINPUT :: ^RAWINPUT
+HRAWINPUT :: distinct LPARAM
+
+RAWINPUTDEVICE :: struct {
+	usUsagePage: USHORT,
+	usUsage: USHORT,
+	dwFlags: DWORD,
+	hwndTarget: HWND,
+}
+
+PRAWINPUTDEVICE :: ^RAWINPUTDEVICE
+PCRAWINPUTDEVICE :: PRAWINPUTDEVICE
+
+RAWINPUTDEVICELIST :: struct {
+	hDevice: HANDLE,
+	dwType: DWORD,
+}
+
+PRAWINPUTDEVICELIST :: ^RAWINPUTDEVICELIST
+
+RID_DEVICE_INFO_HID :: struct {
+	dwVendorId: DWORD,
+	dwProductId: DWORD,
+	dwVersionNumber: DWORD,
+	usUsagePage: USHORT,
+	usUsage: USHORT,
+}
+
+RID_DEVICE_INFO_KEYBOARD :: struct {
+	dwType: DWORD,
+	dwSubType: DWORD,
+	dwKeyboardMode: DWORD,
+	dwNumberOfFunctionKeys: DWORD,
+	dwNumberOfIndicators: DWORD,
+	dwNumberOfKeysTotal: DWORD,
+}
+
+RID_DEVICE_INFO_MOUSE :: struct {
+	dwId: DWORD,
+	dwNumberOfButtons: DWORD,
+	dwSampleRate: DWORD,
+	fHasHorizontalWheel: BOOL,
+}
+
+RID_DEVICE_INFO :: struct {
+	cbSize: DWORD,
+	dwType: DWORD,
+	DUMMYUNIONNAME: struct #raw_union {
+		mouse: RID_DEVICE_INFO_MOUSE,
+		keyboard: RID_DEVICE_INFO_KEYBOARD,
+		hid: RID_DEVICE_INFO_HID,
+	},
+}
+
+RIDEV_REMOVE :: 0x00000001
+RIDEV_EXCLUDE :: 0x00000010
+RIDEV_PAGEONLY :: 0x00000020
+RIDEV_NOLEGACY :: 0x00000030
+RIDEV_INPUTSINK :: 0x00000100
+RIDEV_CAPTUREMOUSE :: 0x00000200
+RIDEV_NOHOTKEYS :: 0x00000200
+RIDEV_APPKEYS :: 0x00000400
+RIDEV_EXINPUTSINK :: 0x00001000
+RIDEV_DEVNOTIFY :: 0x00002000
+
+RID_HEADER :: 0x10000005
+RID_INPUT :: 0x10000003
+
+RIM_TYPEMOUSE :: 0
+RIM_TYPEKEYBOARD :: 1
+RIM_TYPEHID :: 2
+
+MOUSE_MOVE_RELATIVE :: 0x00
+MOUSE_MOVE_ABSOLUTE :: 0x01
+MOUSE_VIRTUAL_DESKTOP :: 0x02
+MOUSE_ATTRIUBTTES_CHANGED :: 0x04
+MOUSE_MOVE_NOCOALESCE :: 0x08
+
+RI_MOUSE_BUTTON_1_DOWN :: 0x0001
+RI_MOUSE_LEFT_BUTTON_DOWNS :: RI_MOUSE_BUTTON_1_DOWN
+RI_MOUSE_BUTTON_1_UP :: 0x0002
+RI_MOUSE_LEFT_BUTTON_UP :: RI_MOUSE_BUTTON_1_UP
+RI_MOUSE_BUTTON_2_DOWN :: 0x0004
+RI_MOUSE_RIGHT_BUTTON_DOWN :: RI_MOUSE_BUTTON_2_DOWN
+RI_MOUSE_BUTTON_2_UP :: 0x0008
+RI_MOUSE_RIGHT_BUTTON_UP :: RI_MOUSE_BUTTON_2_UP
+RI_MOUSE_BUTTON_3_DOWN :: 0x0010
+RI_MOUSE_MIDDLE_BUTTON_DOWN :: RI_MOUSE_BUTTON_3_DOWN
+RI_MOUSE_BUTTON_3_UP :: 0x0020
+RI_MOUSE_MIDDLE_BUTTON_UP :: RI_MOUSE_BUTTON_3_UP
+RI_MOUSE_BUTTON_4_DOWN :: 0x0040
+RI_MOUSE_BUTTON_4_UP :: 0x0080
+RI_MOUSE_BUTTON_5_DOWN :: 0x0100
+RI_MOUSE_BUTTON_5_UP :: 0x0200
+RI_MOUSE_WHEEL :: 0x0400
+RI_MOUSE_HWHEEL :: 0x0800

+ 3 - 3
core/sys/windows/util.odin

@@ -73,10 +73,10 @@ wstring_to_utf8 :: proc(s: wstring, N: int, allocator := context.temp_allocator)
 
 	// If N == -1 the call to WideCharToMultiByte assume the wide string is null terminated
 	// and will scan it to find the first null terminated character. The resulting string will
-	// also null terminated.
+	// also be null terminated.
 	// If N != -1 it assumes the wide string is not null terminated and the resulting string
-	// will not be null terminated, we therefore have to force it to be null terminated manually.
-	text := make([]byte, n+1 if N != -1 else n) or_return
+	// will not be null terminated.
+	text := make([]byte, n) or_return
 
 	n1 := WideCharToMultiByte(CP_UTF8, WC_ERR_INVALID_CHARS, s, i32(N), raw_data(text), n, nil, nil)
 	if n1 == 0 {

+ 2 - 0
core/sys/windows/winerror.odin

@@ -42,6 +42,8 @@ ERROR_TIMEOUT                : DWORD : 1460
 ERROR_DATATYPE_MISMATCH      : DWORD : 1629
 ERROR_UNSUPPORTED_TYPE       : DWORD : 1630
 ERROR_NOT_SAME_OBJECT        : DWORD : 1656
+ERROR_PIPE_CONNECTED         : DWORD : 0x80070217
+ERROR_PIPE_BUSY              : DWORD : 231
 
 E_NOTIMPL :: HRESULT(-0x7fff_bfff) // 0x8000_4001
 

+ 3 - 0
examples/all/all_main.odin

@@ -109,6 +109,8 @@ import i18n           "core:text/i18n"
 import thread         "core:thread"
 import time           "core:time"
 
+import sysinfo        "core:sys/info"
+
 import unicode        "core:unicode"
 import utf8           "core:unicode/utf8"
 import utf8string     "core:unicode/utf8/utf8string"
@@ -206,6 +208,7 @@ _ :: scanner
 _ :: i18n
 _ :: thread
 _ :: time
+_ :: sysinfo
 _ :: unicode
 _ :: utf8
 _ :: utf8string

文件差異過大導致無法顯示
+ 639 - 313
src/bug_report.cpp


+ 112 - 63
src/build_settings.cpp

@@ -116,6 +116,7 @@ struct TargetMetrics {
 	TargetArchKind arch;
 	isize          word_size;
 	isize          max_align;
+	isize          max_simd_align;
 	String         target_triplet;
 	String         target_data_layout;
 	TargetABIKind  abi;
@@ -224,6 +225,7 @@ struct BuildContext {
 	String ODIN_VENDOR;  // compiler vendor
 	String ODIN_VERSION; // compiler version
 	String ODIN_ROOT;    // Odin ROOT
+	String ODIN_BUILD_PROJECT_NAME; // Odin main/initial package's directory name
 	bool   ODIN_DEBUG;   // Odin in debug mode
 	bool   ODIN_DISABLE_ASSERT; // Whether the default 'assert' et al is disabled in code or not
 	bool   ODIN_DEFAULT_TO_NIL_ALLOCATOR; // Whether the default allocator is a "nil" allocator or not (i.e. it does nothing)
@@ -235,8 +237,9 @@ struct BuildContext {
 	TargetEndianKind endian_kind;
 
 	// In bytes
-	i64    word_size; // Size of a pointer, must be >= 4
-	i64    max_align; // max alignment, must be >= 1 (and typically >= word_size)
+	i64    word_size;      // Size of a pointer, must be >= 4
+	i64    max_align;      // max alignment, must be >= 1 (and typically >= word_size)
+	i64    max_simd_align; // max alignment, must be >= 1 (and typically >= word_size)
 
 	CommandKind command_kind;
 	String command;
@@ -339,15 +342,13 @@ bool global_ignore_warnings(void) {
 gb_global TargetMetrics target_windows_i386 = {
 	TargetOs_windows,
 	TargetArch_i386,
-	4,
-	8,
+	4, 4, 8,
 	str_lit("i386-pc-windows-msvc"),
 };
 gb_global TargetMetrics target_windows_amd64 = {
 	TargetOs_windows,
 	TargetArch_amd64,
-	8,
-	16,
+	8, 8, 16,
 	str_lit("x86_64-pc-windows-msvc"),
 	str_lit("e-m:w-i64:64-f80:128-n8:16:32:64-S128"),
 };
@@ -355,24 +356,21 @@ gb_global TargetMetrics target_windows_amd64 = {
 gb_global TargetMetrics target_linux_i386 = {
 	TargetOs_linux,
 	TargetArch_i386,
-	4,
-	8,
+	4, 4, 8,
 	str_lit("i386-pc-linux-gnu"),
 
 };
 gb_global TargetMetrics target_linux_amd64 = {
 	TargetOs_linux,
 	TargetArch_amd64,
-	8,
-	16,
+	8, 8, 16,
 	str_lit("x86_64-pc-linux-gnu"),
 	str_lit("e-m:w-i64:64-f80:128-n8:16:32:64-S128"),
 };
 gb_global TargetMetrics target_linux_arm64 = {
 	TargetOs_linux,
 	TargetArch_arm64,
-	8,
-	16,
+	8, 8, 16,
 	str_lit("aarch64-linux-elf"),
 	str_lit("e-m:o-p:32:32-Fi8-i64:64-v128:64:128-a:0:32-n32-S64"),
 };
@@ -380,8 +378,7 @@ gb_global TargetMetrics target_linux_arm64 = {
 gb_global TargetMetrics target_linux_arm32 = {
 	TargetOs_linux,
 	TargetArch_arm32,
-	4,
-	8,
+	4, 4, 8,
 	str_lit("arm-linux-gnu"),
 	str_lit("e-m:o-p:32:32-Fi8-i64:64-v128:64:128-a:0:32-n32-S64"),
 };
@@ -389,8 +386,7 @@ gb_global TargetMetrics target_linux_arm32 = {
 gb_global TargetMetrics target_darwin_amd64 = {
 	TargetOs_darwin,
 	TargetArch_amd64,
-	8,
-	16,
+	8, 8, 16,
 	str_lit("x86_64-apple-darwin"),
 	str_lit("e-m:o-i64:64-f80:128-n8:16:32:64-S128"),
 };
@@ -398,8 +394,7 @@ gb_global TargetMetrics target_darwin_amd64 = {
 gb_global TargetMetrics target_darwin_arm64 = {
 	TargetOs_darwin,
 	TargetArch_arm64,
-	8,
-	16,
+	8, 8, 16,
 	str_lit("arm64-apple-macosx11.0.0"),
 	str_lit("e-m:o-i64:64-i128:128-n32:64-S128"), // TODO(bill): Is this correct?
 };
@@ -407,16 +402,14 @@ gb_global TargetMetrics target_darwin_arm64 = {
 gb_global TargetMetrics target_freebsd_i386 = {
 	TargetOs_freebsd,
 	TargetArch_i386,
-	4,
-	8,
+	4, 4, 8,
 	str_lit("i386-unknown-freebsd-elf"),
 };
 
 gb_global TargetMetrics target_freebsd_amd64 = {
 	TargetOs_freebsd,
 	TargetArch_amd64,
-	8,
-	16,
+	8, 8, 16,
 	str_lit("x86_64-unknown-freebsd-elf"),
 	str_lit("e-m:w-i64:64-f80:128-n8:16:32:64-S128"),
 };
@@ -424,8 +417,7 @@ gb_global TargetMetrics target_freebsd_amd64 = {
 gb_global TargetMetrics target_openbsd_amd64 = {
 	TargetOs_openbsd,
 	TargetArch_amd64,
-	8,
-	16,
+	8, 8, 16,
 	str_lit("x86_64-unknown-openbsd-elf"),
 	str_lit("e-m:e-p270:32:32-p271:32:32-p272:64:64-i64:64-f80:128-n8:16:32:64-S128"),
 };
@@ -433,62 +425,48 @@ gb_global TargetMetrics target_openbsd_amd64 = {
 gb_global TargetMetrics target_essence_amd64 = {
 	TargetOs_essence,
 	TargetArch_amd64,
-	8,
-	16,
+	8, 8, 16,
 	str_lit("x86_64-pc-none-elf"),
 };
 
+
 gb_global TargetMetrics target_freestanding_wasm32 = {
 	TargetOs_freestanding,
 	TargetArch_wasm32,
-	4,
-	8,
+	4, 8, 16,
 	str_lit("wasm32-freestanding-js"),
-	str_lit(""),
+	str_lit("e-m:e-p:32:32-i64:64-n32:64-S128"),
 };
 
 gb_global TargetMetrics target_js_wasm32 = {
 	TargetOs_js,
 	TargetArch_wasm32,
-	4,
-	8,
+	4, 8, 16,
 	str_lit("wasm32-js-js"),
-	str_lit(""),
-};
-
-gb_global TargetMetrics target_js_wasm64 = {
-	TargetOs_js,
-	TargetArch_wasm64,
-	8,
-	16,
-	str_lit("wasm64-js-js"),
-	str_lit(""),
+	str_lit("e-m:e-p:32:32-i64:64-n32:64-S128"),
 };
 
 gb_global TargetMetrics target_wasi_wasm32 = {
 	TargetOs_wasi,
 	TargetArch_wasm32,
-	4,
-	8,
+	4, 8, 16,
 	str_lit("wasm32-wasi-js"),
-	str_lit(""),
+	str_lit("e-m:e-p:32:32-i64:64-n32:64-S128"),
 };
 
 
-// gb_global TargetMetrics target_freestanding_wasm64 = {
-// 	TargetOs_freestanding,
-// 	TargetArch_wasm64,
-// 	8,
-// 	16,
-// 	str_lit("wasm64-freestanding-js"),
-// 	str_lit(""),
-// };
+gb_global TargetMetrics target_js_wasm64 = {
+	TargetOs_js,
+	TargetArch_wasm64,
+	8, 8, 16,
+	str_lit("wasm64-js-js"),
+	str_lit(""),
+};
 
 gb_global TargetMetrics target_freestanding_amd64_sysv = {
 	TargetOs_freestanding,
 	TargetArch_amd64,
-	8,
-	16,
+	8, 8, 16,
 	str_lit("x86_64-pc-none-gnu"),
 	str_lit("e-m:w-i64:64-f80:128-n8:16:32:64-S128"),
 	TargetABI_SysV,
@@ -517,7 +495,7 @@ gb_global NamedTargetMetrics named_targets[] = {
 	{ str_lit("freestanding_wasm32"), &target_freestanding_wasm32 },
 	{ str_lit("wasi_wasm32"),         &target_wasi_wasm32 },
 	{ str_lit("js_wasm32"),           &target_js_wasm32 },
-	{ str_lit("js_wasm64"),           &target_js_wasm64 },
+	// { str_lit("js_wasm64"),           &target_js_wasm64 },
 
 	{ str_lit("freestanding_amd64_sysv"), &target_freestanding_amd64_sysv },
 };
@@ -853,10 +831,73 @@ String internal_odin_root_dir(void) {
 			gb_mfree(argv);
 			return make_string(nullptr, 0);
 		}
-		// copy argv[0] to path_buf
+
+		// NOTE(Jeroen):
+		// On OpenBSD, if `odin` is on the path, `argv[0]` will contain just `odin`,
+		// even though that isn't then the relative path.
+		// When run from Odin's directory, it returns `./odin`.
+		// Check argv[0] for lack of / to see if it's a reference to PATH.
+		// If so, walk PATH to find the executable.
+
 		len = gb_strlen(argv[0]);
-		if(len < path_buf.count) {
-			gb_memmove(&path_buf[0], argv[0], len);
+
+		bool slash_found = false;
+		bool odin_found  = false;
+
+		for (int i = 0; i < len; i += 1) {
+			if (argv[0][i] == '/') {
+				slash_found = true;
+				break;
+			}
+		}
+
+		if (slash_found) {
+			// copy argv[0] to path_buf
+			if(len < path_buf.count) {
+				gb_memmove(&path_buf[0], argv[0], len);
+				odin_found = true;
+			}
+		} else {
+			gbAllocator a = heap_allocator();
+			char const *path_env = gb_get_env("PATH", a);
+			defer (gb_free(a, cast(void *)path_env));
+
+			if (path_env) {
+				int path_len = gb_strlen(path_env);
+				int path_start = 0;
+				int path_end   = 0;
+
+				for (; path_start < path_len; ) {
+					for (; path_end <= path_len; path_end++) {
+						if (path_env[path_end] == ':' || path_end == path_len) {
+							break;
+						}
+					}
+					String path_needle    = (const String)make_string((const u8 *)(path_env + path_start), path_end - path_start);
+					String argv0          = (const String)make_string((const u8 *)argv[0], len);
+					String odin_candidate = concatenate3_strings(a, path_needle, STR_LIT("/"), argv0);
+					defer (gb_free(a, odin_candidate.text));
+
+					if (gb_file_exists((const char *)odin_candidate.text)) {
+						len = odin_candidate.len;
+						if(len < path_buf.count) {
+							gb_memmove(&path_buf[0], odin_candidate.text, len);
+						}
+						odin_found = true;
+						break;
+					}
+
+					path_start = path_end + 1;
+					path_end   = path_start;
+					if (path_start > path_len) {
+						break;
+					}
+				}
+			}
+
+			if (!odin_found) {
+				gb_printf_err("Odin could not locate itself in PATH, and ODIN_ROOT wasn't set either.\n");
+			}
 		}
 		gb_mfree(argv);
 #endif
@@ -1084,14 +1125,16 @@ void init_build_context(TargetMetrics *cross_target) {
 	GB_ASSERT(metrics->arch != TargetArch_Invalid);
 	GB_ASSERT(metrics->word_size > 1);
 	GB_ASSERT(metrics->max_align > 1);
+	GB_ASSERT(metrics->max_simd_align > 1);
 
 
 	bc->metrics = *metrics;
-	bc->ODIN_OS     = target_os_names[metrics->os];
-	bc->ODIN_ARCH   = target_arch_names[metrics->arch];
-	bc->endian_kind = target_endians[metrics->arch];
-	bc->word_size   = metrics->word_size;
-	bc->max_align   = metrics->max_align;
+	bc->ODIN_OS        = target_os_names[metrics->os];
+	bc->ODIN_ARCH      = target_arch_names[metrics->arch];
+	bc->endian_kind    = target_endians[metrics->arch];
+	bc->word_size      = metrics->word_size;
+	bc->max_align      = metrics->max_align;
+	bc->max_simd_align = metrics->max_simd_align;
 	bc->link_flags  = str_lit(" ");
 
 	#if defined(DEFAULT_TO_THREADED_CHECKER)
@@ -1495,6 +1538,12 @@ bool init_build_paths(String init_filename) {
 		enable_target_feature({}, bc->target_features_string);
 	}
 
+	{
+		String build_project_name  = last_path_element(bc->build_paths[BuildPath_Main_Package].basename);
+		GB_ASSERT(build_project_name.len > 0);
+		bc->ODIN_BUILD_PROJECT_NAME = build_project_name;
+	}
+
 	return true;
 }
 

+ 50 - 5
src/check_builtin.cpp

@@ -341,6 +341,13 @@ bool check_builtin_objc_procedure(CheckerContext *c, Operand *operand, Ast *call
 		for (isize i = 2+arg_offset; i < ce->args.count; i++) {
 			Operand x = {};
 			check_expr(c, &x, ce->args[i]);
+			if (is_type_untyped(x.type)) {
+				gbString e = expr_to_string(x.expr);
+				gbString t = type_to_string(x.type);
+				error(x.expr, "'%.*s' expects typed parameters, got %s of type %s", LIT(builtin_name), e, t);
+				gb_string_free(t);
+				gb_string_free(e);
+			}
 			param_types[i-arg_offset] = x.type;
 		}
 
@@ -4523,7 +4530,8 @@ bool check_builtin_procedure(CheckerContext *c, Operand *operand, Ast *call, i32
 				if (x.mode != Addressing_Invalid) {
 					convert_to_typed(c, &x, t_uintptr);	
 				}
-				if (!is_type_uintptr(operand->type)) {
+				convert_to_typed(c, &x, t_uintptr);
+				if (!is_type_uintptr(x.type)) {
 					gbString t = type_to_string(x.type);
 					error(x.expr, "Argument %td must be of type 'uintptr', got %s", i, t);
 					gb_string_free(t);
@@ -4605,6 +4613,46 @@ bool check_builtin_procedure(CheckerContext *c, Operand *operand, Ast *call, i32
 		operand->mode = Addressing_Type;
 		break;
 
+	case BuiltinProc_type_convert_variants_to_pointers:
+		if (operand->mode != Addressing_Type) {
+			error(operand->expr, "Expected a type for '%.*s'", LIT(builtin_name));
+		} else {
+			Type *bt = base_type(operand->type);
+			if (is_type_polymorphic(bt)) {
+				// IGNORE polymorphic types
+				return true;
+			} else if (bt->kind != Type_Union) {
+				gbString t = type_to_string(operand->type);
+				error(operand->expr, "Expected a union type for '%.*s', got %s", LIT(builtin_name), t);
+				gb_string_free(t);
+
+				operand->mode = Addressing_Invalid;
+				operand->type = t_invalid;
+				return false;
+			} else if (bt->Union.is_polymorphic) {
+				gbString t = type_to_string(operand->type);
+				error(operand->expr, "Expected a non-polymorphic union type for '%.*s', got %s", LIT(builtin_name), t);
+				gb_string_free(t);
+
+				operand->mode = Addressing_Invalid;
+				operand->type = t_invalid;
+				return false;
+			}
+
+			Type *new_type = alloc_type_union();
+			auto variants = slice_make<Type *>(permanent_allocator(), bt->Union.variants.count);
+			for_array(i, bt->Union.variants) {
+				variants[i] = alloc_type_pointer(bt->Union.variants[i]);
+			}
+			new_type->Union.variants = variants;
+
+			// NOTE(bill): Is this even correct?
+			new_type->Union.scope = bt->Union.scope;
+
+			operand->type = new_type;
+		}
+		operand->mode = Addressing_Type;
+		break;
 
 	case BuiltinProc_type_is_boolean:
 	case BuiltinProc_type_is_integer:
@@ -5390,10 +5438,7 @@ bool check_builtin_procedure(CheckerContext *c, Operand *operand, Ast *call, i32
 
 	case BuiltinProc_valgrind_client_request:
 		{
-			if (!is_arch_x86()) {
-				error(call, "'%.*s' is only allowed on x86 targets (i386, amd64)", LIT(builtin_name));
-				return false;
-			}
+			// NOTE(bill): Check it but make it a no-op for non x86 (i386, amd64) targets
 
 			enum {ARG_COUNT = 7};
 			GB_ASSERT(builtin_procs[BuiltinProc_valgrind_client_request].arg_count == ARG_COUNT);

+ 18 - 2
src/check_expr.cpp

@@ -3039,8 +3039,8 @@ void check_binary_matrix(CheckerContext *c, Token const &op, Operand *x, Operand
 		x->type = xt;
 		goto matrix_success;
 	} else {
-		GB_ASSERT(is_type_matrix(yt));
 		GB_ASSERT(!is_type_matrix(xt));
+		GB_ASSERT(is_type_matrix(yt));
 		
 		if (op.kind == Token_Mul) {
 			// NOTE(bill): no need to handle the matrix case here since it should be handled above
@@ -3061,6 +3061,9 @@ void check_binary_matrix(CheckerContext *c, Token const &op, Operand *x, Operand
 					x->type = alloc_type_matrix(yt->Matrix.elem, 1, yt->Matrix.column_count);
 				}
 				goto matrix_success;
+			} else if (are_types_identical(yt->Matrix.elem, xt)) {
+				x->type = check_matrix_type_hint(y->type, type_hint);
+				return;
 			}
 		}
 		if (!are_types_identical(xt, yt)) {
@@ -7359,6 +7362,14 @@ ExprKind check_ternary_if_expr(CheckerContext *c, Operand *o, Ast *node, Type *t
 		return kind;
 	}
 
+	if (x.mode == Addressing_Type || y.mode == Addressing_Type) {
+		Ast *type_expr = (x.mode == Addressing_Type) ? x.expr : y.expr;
+		gbString type_string = expr_to_string(type_expr);
+		error(node, "Type %s is invalid operand for ternary if expression", type_string);
+		gb_string_free(type_string);
+		return kind;
+	}
+
 	if (x.type == nullptr || x.type == t_invalid ||
 	    y.type == nullptr || y.type == t_invalid) {
 		return kind;
@@ -7395,6 +7406,7 @@ ExprKind check_ternary_if_expr(CheckerContext *c, Operand *o, Ast *node, Type *t
 		    check_cast_internal(c, &y, type_hint)) {
 			convert_to_typed(c, o, type_hint);
 			update_untyped_expr_type(c, node, type_hint, !is_type_untyped(type_hint));
+			o->type = type_hint;
 		}
 	}
 	return kind;
@@ -8769,7 +8781,10 @@ ExprKind check_selector_call_expr(CheckerContext *c, Operand *o, Ast *node, Type
 	Ast *first_arg = x.expr->SelectorExpr.expr;
 	GB_ASSERT(first_arg != nullptr);
 
-	first_arg->state_flags |= StateFlag_SelectorCallExpr;
+	Entity *e = entity_of_node(se->expr);
+	if (!(e != nullptr && (e->kind == Entity_Procedure || e->kind == Entity_ProcGroup))) {
+		first_arg->state_flags |= StateFlag_SelectorCallExpr;
+	}
 
 	Type *pt = base_type(x.type);
 	GB_ASSERT(pt->kind == Type_Proc);
@@ -9571,6 +9586,7 @@ ExprKind check_expr_base_internal(CheckerContext *c, Operand *o, Ast *node, Type
 	case Ast_MapType:
 	case Ast_BitSetType:
 	case Ast_MatrixType:
+	case Ast_RelativeType:
 		o->mode = Addressing_Type;
 		o->type = check_type(c, node);
 		break;

+ 49 - 6
src/check_stmt.cpp

@@ -1396,6 +1396,23 @@ bool check_stmt_internal_builtin_proc_id(Ast *expr, BuiltinProcId *id_) {
 	return id != BuiltinProc_Invalid;
 }
 
+bool check_expr_is_stack_variable(Ast *expr) {
+	expr = unparen_expr(expr);
+	Entity *e = entity_of_node(expr);
+	if (e && e->kind == Entity_Variable) {
+		if (e->flags & (EntityFlag_Static|EntityFlag_Using|EntityFlag_ImplicitReference|EntityFlag_ForValue)) {
+			// okay
+		} else if (e->Variable.thread_local_model.len != 0) {
+			// okay
+		} else if (e->scope) {
+			if ((e->scope->flags & (ScopeFlag_Global|ScopeFlag_File|ScopeFlag_Type)) == 0) {
+				return true;
+			}
+		}
+	}
+	return false;
+}
+
 void check_stmt_internal(CheckerContext *ctx, Ast *node, u32 flags) {
 	u32 mod_flags = flags & (~Stmt_FallthroughAllowed);
 	switch (node->kind) {
@@ -1447,6 +1464,12 @@ void check_stmt_internal(CheckerContext *ctx, Ast *node, u32 flags) {
 				AstSelectorCallExpr *se = &expr->SelectorCallExpr;
 				ast_node(ce, CallExpr, se->call);
 				Type *t = base_type(type_of_expr(ce->proc));
+				if (t == nullptr) {
+					gbString expr_str = expr_to_string(ce->proc);
+					error(node, "'%s' is not a value field nor procedure", expr_str);
+					gb_string_free(expr_str);
+					return;
+				}
 				if (t->kind == Type_Proc) {
 					do_require = t->Proc.require_results;
 				} else if (check_stmt_internal_builtin_proc_id(ce->proc, &builtin_id)) {
@@ -1678,6 +1701,29 @@ void check_stmt_internal(CheckerContext *ctx, Ast *node, u32 flags) {
 				if (is_type_untyped(o->type)) {
 					update_untyped_expr_type(ctx, o->expr, e->type, true);
 				}
+
+
+				// NOTE(bill): This is very basic escape analysis
+				// This needs to be improved tremendously, and a lot of it done during the
+				// middle-end (or LLVM side) to improve checks and error messages
+				Ast *expr = unparen_expr(o->expr);
+				if (expr->kind == Ast_UnaryExpr && expr->UnaryExpr.op.kind == Token_And) {
+					Ast *x = unparen_expr(expr->UnaryExpr.expr);
+					if (x->kind == Ast_CompoundLit) {
+						error(expr, "Cannot return the address to a stack value from a procedure");
+					} else if (x->kind == Ast_IndexExpr) {
+						Ast *array = x->IndexExpr.expr;
+						if (is_type_array_like(type_of_expr(array)) && check_expr_is_stack_variable(array)) {
+							gbString t = type_to_string(type_of_expr(array));
+							error(expr, "Cannot return the address to an element of stack variable from a procedure, of type %s", t);
+							gb_string_free(t);
+						}
+					} else {
+						if (check_expr_is_stack_variable(x)) {
+							error(expr, "Cannot return the address to a stack variable from a procedure");
+						}
+					}
+				}
 			}
 		}
 	case_end;
@@ -1895,13 +1941,10 @@ void check_stmt_internal(CheckerContext *ctx, Ast *node, u32 flags) {
 				}
 				if (found == nullptr) {
 					entity = alloc_entity_variable(ctx->scope, token, type, EntityState_Resolved);
+					entity->flags |= EntityFlag_ForValue;
 					entity->flags |= EntityFlag_Value;
-					if (i == addressable_index) {
-						if (use_by_reference_for_value) {
-							entity->flags &= ~EntityFlag_Value;
-						} else {
-							entity->flags |= EntityFlag_ForValue;
-						}
+					if (i == addressable_index && use_by_reference_for_value) {
+						entity->flags &= ~EntityFlag_Value;
 					}
 					if (is_soa) {
 						if (i == 0) {

+ 4 - 2
src/check_type.cpp

@@ -1643,8 +1643,10 @@ Type *check_get_params(CheckerContext *ctx, Scope *scope, Ast *_params, bool *is
 						bool valid = false;
 						if (is_type_proc(op.type)) {
 							Entity *proc_entity = entity_from_expr(op.expr);
-							valid = proc_entity != nullptr;
-							poly_const = exact_value_procedure(proc_entity->identifier.load() ? proc_entity->identifier.load() : op.expr);
+							valid = (proc_entity != nullptr) && (op.value.kind == ExactValue_Procedure);
+							if (valid) {
+								poly_const = exact_value_procedure(proc_entity->identifier.load() ? proc_entity->identifier.load() : op.expr);
+							}
 						}
 						if (!valid) {
 							if (op.mode == Addressing_Constant) {

+ 1 - 0
src/checker.cpp

@@ -938,6 +938,7 @@ void init_universal(void) {
 	add_global_string_constant("ODIN_VENDOR",  bc->ODIN_VENDOR);
 	add_global_string_constant("ODIN_VERSION", bc->ODIN_VERSION);
 	add_global_string_constant("ODIN_ROOT",    bc->ODIN_ROOT);
+	add_global_string_constant("ODIN_BUILD_PROJECT_NAME", bc->ODIN_BUILD_PROJECT_NAME);
 
 	{
 		GlobalEnumValue values[TargetOs_COUNT] = {

+ 3 - 0
src/checker_builtin_procs.hpp

@@ -200,6 +200,8 @@ BuiltinProc__type_begin,
 	BuiltinProc_type_core_type,
 	BuiltinProc_type_elem_type,
 
+	BuiltinProc_type_convert_variants_to_pointers,
+
 BuiltinProc__type_simple_boolean_begin,
 	BuiltinProc_type_is_boolean,
 	BuiltinProc_type_is_integer,
@@ -492,6 +494,7 @@ gb_global BuiltinProc builtin_procs[BuiltinProc_COUNT] = {
 	{STR_LIT("type_base_type"),            1, false, Expr_Expr, BuiltinProcPkg_intrinsics},
 	{STR_LIT("type_core_type"),            1, false, Expr_Expr, BuiltinProcPkg_intrinsics},
 	{STR_LIT("type_elem_type"),            1, false, Expr_Expr, BuiltinProcPkg_intrinsics},
+	{STR_LIT("type_convert_variants_to_pointers"), 1, false, Expr_Expr, BuiltinProcPkg_intrinsics},
 
 	{STR_LIT(""), 0, false, Expr_Stmt, BuiltinProcPkg_intrinsics},
 	{STR_LIT("type_is_boolean"),           1, false, Expr_Expr, BuiltinProcPkg_intrinsics},

+ 4 - 0
src/common.cpp

@@ -38,6 +38,10 @@ i64 next_pow2(i64 n);
 isize next_pow2_isize(isize n);
 void debugf(char const *fmt, ...);
 
+#if defined(GB_SYSTEM_WINDOWS) && defined(GB_ARCH_32_BIT)
+#error Odin on Windows requires a 64-bit build-system. The 'Developer Command Prompt' for VS still defaults to 32-bit shell. The 64-bit shell can be found under the name 'x64 Native Tools Command Prompt' for VS. For more information, please see https://odin-lang.org/docs/install/#for-windows
+#endif
+
 #include "threading.cpp"
 #include "unicode.cpp"
 #include "array.cpp"

+ 1 - 1
src/llvm_abi.cpp

@@ -294,7 +294,7 @@ i64 lb_alignof(LLVMTypeRef type) {
 			i64 elem_size = lb_sizeof(elem);
 			i64 count = LLVMGetVectorSize(type);
 			i64 size = count * elem_size;
-			return gb_clamp(next_pow2(size), 1, build_context.max_align);
+			return gb_clamp(next_pow2(size), 1, build_context.max_simd_align);
 		}
 
 	}

+ 2 - 1
src/llvm_backend.cpp

@@ -1420,7 +1420,7 @@ void lb_generate_code(lbGenerator *gen) {
 				}
 			}
 
-			LLVMBool split_debug_inlining = false;
+			LLVMBool split_debug_inlining = build_context.build_mode == BuildMode_Assembly;
 			LLVMBool debug_info_for_profiling = false;
 
 			m->debug_compile_unit = LLVMDIBuilderCreateCompileUnit(m->debug_builder, LLVMDWARFSourceLanguageC99,
@@ -1615,6 +1615,7 @@ void lb_generate_code(lbGenerator *gen) {
 		}
 		if (is_foreign) {
 			LLVMSetLinkage(g.value, LLVMExternalLinkage);
+			LLVMSetDLLStorageClass(g.value, LLVMDLLImportStorageClass);
 			LLVMSetExternallyInitialized(g.value, true);
 			lb_add_foreign_library_path(m, e->Variable.foreign_library);
 			

+ 4 - 0
src/llvm_backend_const.cpp

@@ -297,12 +297,16 @@ LLVMValueRef lb_build_constant_array_values(lbModule *m, Type *type, Type *elem_
 	}
 
 	if (!is_const) {
+		LLVMTypeRef llvm_elem_type = lb_type(m, elem_type);
 		lbProcedure *p = m->curr_procedure;
 		GB_ASSERT(p != nullptr);
 		lbAddr v = lb_add_local_generated(p, type, false);
 		lbValue ptr = lb_addr_get_ptr(p, v);
 		for (isize i = 0; i < count; i++) {
 			lbValue elem = lb_emit_array_epi(p, ptr, i);
+			if (is_type_proc(elem_type)) {
+				values[i] = LLVMConstPointerCast(values[i], llvm_elem_type);
+			}
 			LLVMBuildStore(p->builder, values[i], elem.value);
 		}
 		return lb_addr_load(p, v).value;

+ 2 - 1
src/llvm_backend_debug.cpp

@@ -1042,7 +1042,8 @@ void lb_add_debug_param_variable(lbProcedure *p, LLVMValueRef ptr, Type *type, T
 	// NOTE(bill, 2022-02-01): For parameter values, you must insert them at the end of the decl block
 	// The reason is that if the parameter is at index 0 and a pointer, there is not such things as an
 	// instruction "before" it.
-	LLVMDIBuilderInsertDbgValueAtEnd(m->debug_builder, storage, var_info, llvm_expr, llvm_debug_loc, block->block);
+	LLVMDIBuilderInsertDeclareAtEnd(m->debug_builder, storage, var_info, llvm_expr, llvm_debug_loc, block->block);
+	// LLVMDIBuilderInsertDbgValueAtEnd(m->debug_builder, storage, var_info, llvm_expr, llvm_debug_loc, block->block);
 }
 
 

+ 22 - 12
src/llvm_backend_expr.cpp

@@ -1,4 +1,4 @@
-lbValue lb_emit_arith_matrix(lbProcedure *p, TokenKind op, lbValue lhs, lbValue rhs, Type *type, bool component_wise=false);
+lbValue lb_emit_arith_matrix(lbProcedure *p, TokenKind op, lbValue lhs, lbValue rhs, Type *type, bool component_wise);
 
 lbValue lb_emit_logical_binary_expr(lbProcedure *p, TokenKind op, Ast *left, Ast *right, Type *type) {
 	lbModule *m = p->module;
@@ -137,7 +137,7 @@ lbValue lb_emit_unary_arith(lbProcedure *p, TokenKind op, lbValue x, Type *type)
 		lbAddr res_addr = lb_add_local(p, type, nullptr, false, 0, true);
 		lbValue res = lb_addr_get_ptr(p, res_addr);
 
-		bool inline_array_arith = type_size_of(type) <= build_context.max_align;
+		bool inline_array_arith = lb_can_try_to_inline_array_arith(type);
 
 		i32 count = cast(i32)get_array_type_count(tl);
 
@@ -436,7 +436,7 @@ lbValue lb_emit_arith_array(lbProcedure *p, TokenKind op, lbValue lhs, lbValue r
 		return direct_vector_res;
 	}
 
-	bool inline_array_arith = type_size_of(type) <= build_context.max_align;
+	bool inline_array_arith = lb_can_try_to_inline_array_arith(type);
 	if (inline_array_arith) {
 
 		auto dst_ptrs = slice_make<lbValue>(temporary_allocator(), n);
@@ -987,7 +987,6 @@ lbValue lb_emit_vector_mul_matrix(lbProcedure *p, lbValue lhs, lbValue rhs, Type
 lbValue lb_emit_arith_matrix(lbProcedure *p, TokenKind op, lbValue lhs, lbValue rhs, Type *type, bool component_wise) {
 	GB_ASSERT(is_type_matrix(lhs.type) || is_type_matrix(rhs.type));
 
-
 	if (op == Token_Mul && !component_wise) {
 		Type *xt = base_type(lhs.type);
 		Type *yt = base_type(rhs.type);
@@ -1001,8 +1000,22 @@ lbValue lb_emit_arith_matrix(lbProcedure *p, TokenKind op, lbValue lhs, lbValue
 		} else if (is_type_array_like(xt)) {
 			GB_ASSERT(yt->kind == Type_Matrix);
 			return lb_emit_vector_mul_matrix(p, lhs, rhs, type);
-		}
+		} else {
+			GB_ASSERT(xt->kind == Type_Basic);
+			GB_ASSERT(yt->kind == Type_Matrix);
+			GB_ASSERT(is_type_matrix(type));
+
+			Type *array_type = alloc_type_array(yt->Matrix.elem, matrix_type_total_internal_elems(yt));
+			GB_ASSERT(type_size_of(array_type) == type_size_of(yt));
+
+			lbValue array_lhs = lb_emit_conv(p, lhs, array_type);
+			lbValue array_rhs = rhs;
+			array_rhs.type = array_type;
 
+			lbValue array = lb_emit_arith(p, op, array_lhs, array_rhs, array_type);
+			array.type = type;
+			return array;
+		}
 	} else {
 		if (is_type_matrix(lhs.type)) {
 			rhs = lb_emit_conv(p, rhs, lhs.type);
@@ -1047,7 +1060,7 @@ lbValue lb_emit_arith(lbProcedure *p, TokenKind op, lbValue lhs, lbValue rhs, Ty
 	if (is_type_array_like(lhs.type) || is_type_array_like(rhs.type)) {
 		return lb_emit_arith_array(p, op, lhs, rhs, type);
 	} else if (is_type_matrix(lhs.type) || is_type_matrix(rhs.type)) {
-		return lb_emit_arith_matrix(p, op, lhs, rhs, type);
+		return lb_emit_arith_matrix(p, op, lhs, rhs, type, false);
 	} else if (is_type_complex(type)) {
 		lhs = lb_emit_conv(p, lhs, type);
 		rhs = lb_emit_conv(p, rhs, type);
@@ -1320,7 +1333,7 @@ lbValue lb_build_binary_expr(lbProcedure *p, Ast *expr) {
 	if (is_type_matrix(be->left->tav.type) || is_type_matrix(be->right->tav.type)) {
 		lbValue left = lb_build_expr(p, be->left);
 		lbValue right = lb_build_expr(p, be->right);
-		return lb_emit_arith_matrix(p, be->op.kind, left, right, default_type(tv.type));
+		return lb_emit_arith_matrix(p, be->op.kind, left, right, default_type(tv.type), false);
 	}
 
 
@@ -1777,9 +1790,6 @@ lbValue lb_emit_conv(lbProcedure *p, lbValue value, Type *t) {
 			lbValue res = {};
 			res = lb_emit_conv(p, value, platform_src_type);
 			res = lb_emit_conv(p, res, platform_dst_type);
-			if (is_type_different_to_arch_endianness(dst)) {
-				res = lb_emit_byte_swap(p, res, platform_dst_type);
-			}
 			return lb_emit_conv(p, res, t);
 		}
 
@@ -1927,7 +1937,7 @@ lbValue lb_emit_conv(lbProcedure *p, lbValue value, Type *t) {
 		}
 		if (dst->Union.variants.count == 1) {
 			Type *vt = dst->Union.variants[0];
-			if (internal_check_is_assignable_to(src, vt)) {
+			if (internal_check_is_assignable_to(src_type, vt)) {
 				value = lb_emit_conv(p, value, vt);
 				lbAddr parent = lb_add_local_generated(p, t, true);
 				lb_emit_store_union_variant(p, parent.addr, value, vt);
@@ -2306,7 +2316,7 @@ lbValue lb_emit_comp(lbProcedure *p, TokenKind op_kind, lbValue left, lbValue ri
 			cmp_op = Token_And;
 		}
 
-		bool inline_array_arith = type_size_of(tl) <= build_context.max_align;
+		bool inline_array_arith = lb_can_try_to_inline_array_arith(tl);
 		i32 count = 0;
 		switch (tl->kind) {
 		case Type_Array:           count = cast(i32)tl->Array.count;           break;

+ 11 - 23
src/llvm_backend_general.cpp

@@ -591,6 +591,9 @@ bool lb_try_update_alignment(lbValue ptr, unsigned alignment) {
 	return lb_try_update_alignment(ptr.value, alignment);
 }
 
+bool lb_can_try_to_inline_array_arith(Type *t) {
+	return type_size_of(t) <= build_context.max_simd_align;
+}
 
 bool lb_try_vector_cast(lbModule *m, lbValue ptr, LLVMTypeRef *vector_type_) {
 	Type *array_type = base_type(type_deref(ptr.type));
@@ -599,7 +602,7 @@ bool lb_try_vector_cast(lbModule *m, lbValue ptr, LLVMTypeRef *vector_type_) {
 	Type *elem_type = base_array_type(array_type);
 
 	// TODO(bill): Determine what is the correct limit for doing vector arithmetic
-	if (type_size_of(array_type) <= build_context.max_align &&
+	if (lb_can_try_to_inline_array_arith(array_type) &&
 	    is_type_valid_vector_elem(elem_type)) {
 		// Try to treat it like a vector if possible
 		bool possible = false;
@@ -874,18 +877,6 @@ bool lb_is_type_proc_recursive(Type *t) {
 		case Type_Pointer:
 			t = t->Pointer.elem;
 			break;
-		case Type_Array:
-			t = t->Array.elem;
-			break;
-		case Type_EnumeratedArray:
-			t = t->EnumeratedArray.elem;
-			break;
-		case Type_Slice:
-			t = t->Slice.elem;
-			break;
-		case Type_DynamicArray:
-			t = t->DynamicArray.elem;
-			break;
 		case Type_Proc:
 			return true;
 		default:
@@ -1887,16 +1878,16 @@ LLVMTypeRef lb_type_internal(lbModule *m, Type *type) {
 		return LLVMPointerType(lb_type(m, type->Pointer.elem), 0);
 
 	case Type_Array: {
-		m->internal_type_level -= 1;
-		LLVMTypeRef t = LLVMArrayType(lb_type(m, type->Array.elem), cast(unsigned)type->Array.count);
 		m->internal_type_level += 1;
+		LLVMTypeRef t = LLVMArrayType(lb_type(m, type->Array.elem), cast(unsigned)type->Array.count);
+		m->internal_type_level -= 1;
 		return t;
 	}
 
 	case Type_EnumeratedArray: {
-		m->internal_type_level -= 1;
-		LLVMTypeRef t = LLVMArrayType(lb_type(m, type->EnumeratedArray.elem), cast(unsigned)type->EnumeratedArray.count);
 		m->internal_type_level += 1;
+		LLVMTypeRef t = LLVMArrayType(lb_type(m, type->EnumeratedArray.elem), cast(unsigned)type->EnumeratedArray.count);
+		m->internal_type_level -= 1;
 		return t;
 	}
 
@@ -2098,14 +2089,11 @@ LLVMTypeRef lb_type_internal(lbModule *m, Type *type) {
 		}
 
 	case Type_Proc:
-		// if (m->internal_type_level > 256) { // TODO HACK(bill): is this really enough?
-		if (m->internal_type_level > 1) { // TODO HACK(bill): is this really enough?
-			return LLVMPointerType(LLVMIntTypeInContext(m->ctx, 8), 0);
-		} else {
+		{
 			LLVMTypeRef proc_raw_type = lb_type_internal_for_procedures_raw(m, type);
-			return LLVMPointerType(proc_raw_type, 0);
+			gb_unused(proc_raw_type);
+			return LLVMPointerType(LLVMIntTypeInContext(m->ctx, 8), 0);
 		}
-
 		break;
 	case Type_BitSet:
 		{

+ 5 - 3
src/llvm_backend_proc.cpp

@@ -736,6 +736,9 @@ lbValue lb_emit_call_internal(lbProcedure *p, lbValue value, lbValue return_ptr,
 	}
 	for_array(i, processed_args) {
 		lbValue arg = processed_args[i];
+		if (is_type_proc(arg.type)) {
+			arg.value = LLVMBuildPointerCast(p->builder, arg.value, lb_type(p->module, arg.type), "");
+		}
 		args[arg_index++] = arg.value;
 	}
 	if (context_ptr.addr.value != nullptr) {
@@ -2867,9 +2870,9 @@ lbValue lb_build_call_expr_internal(lbProcedure *p, Ast *expr) {
 		return y;
 	}
 
-	Ast *pexpr = unparen_expr(ce->proc);
+	Ast *proc_expr = unparen_expr(ce->proc);
 	if (proc_mode == Addressing_Builtin) {
-		Entity *e = entity_of_node(pexpr);
+		Entity *e = entity_of_node(proc_expr);
 		BuiltinProcId id = BuiltinProc_Invalid;
 		if (e != nullptr) {
 			id = cast(BuiltinProcId)e->Builtin.id;
@@ -2881,7 +2884,6 @@ lbValue lb_build_call_expr_internal(lbProcedure *p, Ast *expr) {
 
 	// NOTE(bill): Regular call
 	lbValue value = {};
-	Ast *proc_expr = unparen_expr(ce->proc);
 
 	Entity *proc_entity = entity_of_node(proc_expr);
 	if (proc_entity != nullptr) {

+ 13 - 3
src/llvm_backend_stmt.cpp

@@ -646,7 +646,7 @@ void lb_build_range_stmt_struct_soa(lbProcedure *p, AstRangeStmt *rs, Scope *sco
 
 
 	lbAddr array = lb_build_addr(p, expr);
-	if (is_type_pointer(type_deref(lb_addr_type(array)))) {
+	if (is_type_pointer(lb_addr_type(array))) {
 		array = lb_addr(lb_addr_load(p, array));
 	}
 	lbValue count = lb_soa_struct_len(p, lb_addr_load(p, array));
@@ -1796,7 +1796,7 @@ void lb_build_assign_stmt_array(lbProcedure *p, TokenKind op, lbAddr const &lhs,
 
 	lbValue rhs = lb_emit_conv(p, value, lhs_type);
 
-	bool inline_array_arith = type_size_of(array_type) <= build_context.max_align;
+	bool inline_array_arith = lb_can_try_to_inline_array_arith(array_type);
 
 
 	if (lhs.kind == lbAddr_Swizzle) {
@@ -1959,8 +1959,18 @@ void lb_build_assign_stmt(lbProcedure *p, AstAssignStmt *as) {
 	} else {
 		lbAddr lhs = lb_build_addr(p, as->lhs[0]);
 		lbValue value = lb_build_expr(p, as->rhs[0]);
-
 		Type *lhs_type = lb_addr_type(lhs);
+
+		// NOTE(bill): Allow for the weird edge case of:
+		// array *= matrix
+		if (op == Token_Mul && is_type_matrix(value.type) && is_type_array(lhs_type)) {
+			lbValue old_value = lb_addr_load(p, lhs);
+			Type *type = old_value.type;
+			lbValue new_value = lb_emit_vector_mul_matrix(p, old_value, value, type);
+			lb_addr_store(p, lhs, new_value);
+			return;
+		}
+
 		if (is_type_array(lhs_type)) {
 			lb_build_assign_stmt_array(p, op, lhs, value);
 			return;

+ 14 - 0
src/llvm_backend_utility.cpp

@@ -225,6 +225,20 @@ lbValue lb_emit_transmute(lbProcedure *p, lbValue value, Type *t) {
 	if (is_type_simd_vector(src) && is_type_simd_vector(dst)) {
 		res.value = LLVMBuildBitCast(p->builder, value.value, lb_type(p->module, t), "");
 		return res;
+	} else if (is_type_array_like(src) && is_type_simd_vector(dst)) {
+		unsigned align = cast(unsigned)gb_max(type_align_of(src), type_align_of(dst));
+		lbValue ptr = lb_address_from_load_or_generate_local(p, value);
+		if (lb_try_update_alignment(ptr, align)) {
+			LLVMTypeRef result_type = lb_type(p->module, t);
+			res.value = LLVMBuildPointerCast(p->builder, ptr.value, LLVMPointerType(result_type, 0), "");
+			res.value = LLVMBuildLoad2(p->builder, result_type, res.value, "");
+			return res;
+		}
+		lbAddr addr = lb_add_local_generated(p, t, false);
+		lbValue ap = lb_addr_get_ptr(p, addr);
+		ap = lb_emit_conv(p, ap, alloc_type_pointer(value.type));
+		lb_emit_store(p, ap, value);
+		return lb_addr_load(p, addr);
 	}
 
 	if (lb_is_type_aggregate(src) || lb_is_type_aggregate(dst)) {

+ 43 - 43
src/main.cpp

@@ -288,11 +288,15 @@ i32 linker_stage(lbGenerator *gen) {
 
 			char const *subsystem_str = build_context.use_subsystem_windows ? "WINDOWS" : "CONSOLE";
 			if (!build_context.use_lld) { // msvc
+				String res_path = {};
+				defer (gb_free(heap_allocator(), res_path.text));
 				if (build_context.has_resource) {
+					String temp_res_path = path_to_string(heap_allocator(), build_context.build_paths[BuildPath_RES]);
+					res_path = concatenate3_strings(heap_allocator(), str_lit("\""), temp_res_path, str_lit("\""));
+					gb_free(heap_allocator(), temp_res_path.text);
+
 					String rc_path  = path_to_string(heap_allocator(), build_context.build_paths[BuildPath_RC]);
-					String res_path = path_to_string(heap_allocator(), build_context.build_paths[BuildPath_RES]);
 					defer (gb_free(heap_allocator(), rc_path.text));
-					defer (gb_free(heap_allocator(), res_path.text));
 
 					result = system_exec_command_line_app("msvc-link",
 						"\"%.*src.exe\" /nologo /fo \"%.*s\" \"%.*s\"",
@@ -304,42 +308,25 @@ i32 linker_stage(lbGenerator *gen) {
 					if (result) {
 						return result;
 					}
-
-					result = system_exec_command_line_app("msvc-link",
-						"\"%.*slink.exe\" %s \"%.*s\" -OUT:\"%.*s\" %s "
-						"/nologo /incremental:no /opt:ref /subsystem:%s "
-						" %.*s "
-						" %.*s "
-						" %s "
-						"",
-						LIT(vs_exe_path), object_files, LIT(res_path), LIT(output_filename),
-						link_settings,
-						subsystem_str,
-						LIT(build_context.link_flags),
-						LIT(build_context.extra_linker_flags),
-						lib_str
-					  );
-				} else {
-					result = system_exec_command_line_app("msvc-link",
-						"\"%.*slink.exe\" %s -OUT:\"%.*s\" %s "
-						"/nologo /incremental:no /opt:ref /subsystem:%s "
-						" %.*s "
-						" %.*s "
-						" %s "
-						"",
-						LIT(vs_exe_path), object_files, LIT(output_filename),
-						link_settings,
-						subsystem_str,
-						LIT(build_context.link_flags),
-						LIT(build_context.extra_linker_flags),
-						lib_str
-					);
 				}
 
+				result = system_exec_command_line_app("msvc-link",
+					"\"%.*slink.exe\" %s %.*s -OUT:\"%.*s\" %s "
+					"/nologo /incremental:no /opt:ref /subsystem:%s "
+					" %.*s "
+					" %.*s "
+					" %s "
+					"",
+					LIT(vs_exe_path), object_files, LIT(res_path), LIT(output_filename),
+					link_settings,
+					subsystem_str,
+					LIT(build_context.link_flags),
+					LIT(build_context.extra_linker_flags),
+					lib_str
+				);
 				if (result) {
 					return result;
 				}
-
 			} else { // lld
 				result = system_exec_command_line_app("msvc-lld-link",
 					"\"%.*s\\bin\\lld-link\" %s -OUT:\"%.*s\" %s "
@@ -590,8 +577,8 @@ void usage(String argv0) {
 	print_usage_line(1, "version           print version");
 	print_usage_line(1, "report            print information useful to reporting a bug");
 	print_usage_line(0, "");
-	print_usage_line(0, "For further details on a command, use -help after the command name");
-	print_usage_line(1, "e.g. odin build -help");
+	print_usage_line(0, "For further details on a command, invoke command help:");
+	print_usage_line(1, "e.g. `odin build -help` or `odin help build`");
 }
 
 enum BuildFlagKind {
@@ -1963,13 +1950,13 @@ void print_show_help(String const arg0, String const &command) {
 		print_usage_line(2, "Parse and type check .odin file(s) and then remove unneeded semicolons from the entire project");
 	}
 
-	bool doc = command == "doc";
-	bool build = command == "build";
-	bool run_or_build = command == "run" || command == "build" || command == "test";
-	bool test_only = command == "test";
+	bool doc             = command == "doc";
+	bool build           = command == "build";
+	bool run_or_build    = command == "run" || command == "build" || command == "test";
+	bool test_only       = command == "test";
 	bool strip_semicolon = command == "strip-semicolon";
-	bool check_only = command == "check" || strip_semicolon;
-	bool check = run_or_build || check_only;
+	bool check_only      = command == "check" || strip_semicolon;
+	bool check           = run_or_build || check_only;
 
 	print_usage_line(0, "");
 	print_usage_line(1, "Flags");
@@ -2085,7 +2072,7 @@ void print_show_help(String const arg0, String const &command) {
 		print_usage_line(3, "-build-mode:shared    Build as a dynamically linked library");
 		print_usage_line(3, "-build-mode:obj       Build as an object file");
 		print_usage_line(3, "-build-mode:object    Build as an object file");
-		print_usage_line(3, "-build-mode:assembly  Build as an object file");
+		print_usage_line(3, "-build-mode:assembly  Build as an assembly file");
 		print_usage_line(3, "-build-mode:assembler Build as an assembly file");
 		print_usage_line(3, "-build-mode:asm       Build as an assembly file");
 		print_usage_line(3, "-build-mode:llvm-ir   Build as an LLVM IR file");
@@ -2701,6 +2688,14 @@ int main(int arg_count, char const **arg_ptr) {
 		build_context.command_kind = Command_bug_report;
 		print_bug_report_help();
 		return 0;
+	} else if (command == "help") {
+		if (args.count <= 2) {
+			usage(args[0]);
+			return 1;
+		} else {
+			print_show_help(args[0], args[2]);
+			return 0;
+		}
 	} else {
 		usage(args[0]);
 		return 1;
@@ -2727,7 +2722,12 @@ int main(int arg_count, char const **arg_ptr) {
 
 			if (!single_file_package) {
 				gb_printf_err("ERROR: `%.*s %.*s` takes a package as its first argument.\n", LIT(args[0]), LIT(command));
-				gb_printf_err("Did you mean `%.*s %.*s %.*s -file`?\n", LIT(args[0]), LIT(command), LIT(init_filename));
+				if (init_filename == "-file") {
+					gb_printf_err("Did you mean `%.*s %.*s <filename.odin> -file`?\n", LIT(args[0]), LIT(command));
+				} else {
+					gb_printf_err("Did you mean `%.*s %.*s %.*s -file`?\n", LIT(args[0]), LIT(command), LIT(init_filename));
+				}
+
 				gb_printf_err("The `-file` flag tells it to treat a file as a self-contained package.\n");
 				return 1;
 			} else {

+ 50 - 1
src/parser.cpp

@@ -5506,6 +5506,51 @@ isize calc_decl_count(Ast *decl) {
 	return count;
 }
 
+bool parse_build_project_directory_tag(Token token_for_pos, String s) {
+	String const prefix = str_lit("+build-project-name");
+	GB_ASSERT(string_starts_with(s, prefix));
+	s = string_trim_whitespace(substring(s, prefix.len, s.len));
+	if (s.len == 0) {
+		return true;
+	}
+
+	bool any_correct = false;
+
+	while (s.len > 0) {
+		bool this_kind_correct = true;
+
+		do {
+			String p = string_trim_whitespace(build_tag_get_token(s, &s));
+			if (p.len == 0) break;
+			if (p == ",") break;
+
+			bool is_notted = false;
+			if (p[0] == '!') {
+				is_notted = true;
+				p = substring(p, 1, p.len);
+				if (p.len == 0) {
+					syntax_error(token_for_pos, "Expected a build-project-name after '!'");
+					break;
+				}
+			}
+
+			if (p.len == 0) {
+				continue;
+			}
+
+			if (is_notted) {
+				this_kind_correct = this_kind_correct && (p != build_context.ODIN_BUILD_PROJECT_NAME);
+			} else {
+				this_kind_correct = this_kind_correct && (p == build_context.ODIN_BUILD_PROJECT_NAME);
+			}
+		} while (s.len > 0);
+
+		any_correct = any_correct || this_kind_correct;
+	}
+
+	return any_correct;
+}
+
 bool parse_file(Parser *p, AstFile *f) {
 	if (f->tokens.count == 0) {
 		return true;
@@ -5561,7 +5606,11 @@ bool parse_file(Parser *p, AstFile *f) {
 			if (string_starts_with(str, str_lit("//"))) {
 				String lc = string_trim_whitespace(substring(str, 2, str.len));
 				if (lc.len > 0 && lc[0] == '+') {
-					if (string_starts_with(lc, str_lit("+build"))) {
+					 if (string_starts_with(lc, str_lit("+build-project-name"))) {
+						if (!parse_build_project_directory_tag(tok, lc)) {
+							return false;
+						}
+					} else if (string_starts_with(lc, str_lit("+build"))) {
 						if (!parse_build_tag(tok, lc)) {
 							return false;
 						}

+ 3 - 4
src/types.cpp

@@ -1428,7 +1428,7 @@ i64 matrix_align_of(Type *t, struct TypePath *tp) {
 	}
 	GB_ASSERT(min_alignment >= elem_align);
 	
-	i64 align = gb_min(min_alignment, build_context.max_align);
+	i64 align = gb_min(min_alignment, build_context.max_simd_align);
 	return align;
 }
 
@@ -3556,7 +3556,7 @@ i64 type_align_of_internal(Type *t, TypePath *path) {
 
 	case Type_SimdVector: {
 		// IMPORTANT TODO(bill): Figure out the alignment of vector types
-		return gb_clamp(next_pow2(type_size_of_internal(t, path)), 1, build_context.max_align*2);
+		return gb_clamp(next_pow2(type_size_of_internal(t, path)), 1, build_context.max_simd_align*2);
 	}
 	
 	case Type_Matrix: 
@@ -3571,10 +3571,9 @@ i64 type_align_of_internal(Type *t, TypePath *path) {
 		return build_context.word_size;
 	}
 
-	// return gb_clamp(next_pow2(type_size_of(t)), 1, build_context.max_align);
 	// NOTE(bill): Things that are bigger than build_context.word_size, are actually comprised of smaller types
 	// TODO(bill): Is this correct for 128-bit types (integers)?
-	return gb_clamp(next_pow2(type_size_of_internal(t, path)), 1, build_context.word_size);
+	return gb_clamp(next_pow2(type_size_of_internal(t, path)), 1, build_context.max_align);
 }
 
 i64 *type_set_offsets_of(Slice<Entity *> const &fields, bool is_packed, bool is_raw_union) {

+ 6 - 1
tests/core/build.bat

@@ -69,4 +69,9 @@ echo ---
 echo ---
 echo Running core:text/i18n tests
 echo ---
-%PATH_TO_ODIN% run text\i18n %COMMON% -out:test_core_i18n.exe
+%PATH_TO_ODIN% run text\i18n %COMMON% -out:test_core_i18n.exe
+
+echo ---
+echo Running core:slice tests
+echo ---
+%PATH_TO_ODIN% run slice %COMMON% -out:test_core_slice.exe

+ 182 - 0
tests/core/slice/test_core_slice.odin

@@ -0,0 +1,182 @@
+package test_core_slice
+
+import "core:slice"
+import "core:testing"
+import "core:fmt"
+import "core:os"
+import "core:math/rand"
+
+TEST_count := 0
+TEST_fail  := 0
+
+when ODIN_TEST {
+	expect  :: testing.expect
+	log     :: testing.log
+} else {
+	expect  :: proc(t: ^testing.T, condition: bool, message: string, loc := #caller_location) {
+		TEST_count += 1
+		if !condition {
+			TEST_fail += 1
+			fmt.printf("[%v] %v\n", loc, message)
+			return
+		}
+	}
+	log     :: proc(t: ^testing.T, v: any, loc := #caller_location) {
+		fmt.printf("[%v] ", loc)
+		fmt.printf("log: %v\n", v)
+	}
+}
+
+main :: proc() {
+	t := testing.T{}
+	test_sort_with_indices(&t)
+
+	fmt.printf("%v/%v tests successful.\n", TEST_count - TEST_fail, TEST_count)
+	if TEST_fail > 0 {
+		os.exit(1)
+	}
+}
+
+@test
+test_sort_with_indices :: proc(t: ^testing.T) {
+	seed := rand.uint64()
+	fmt.printf("Random seed: %v\n", seed)
+
+	// Test sizes are all prime.
+	test_sizes :: []int{7, 13, 347, 1031, 10111, 100003}
+
+	for test_size in test_sizes {
+		fmt.printf("Sorting %v random u64 values along with index.\n", test_size)
+
+		r := rand.create(seed)
+
+		vals  := make([]u64, test_size)
+		r_idx := make([]int, test_size) // Reverse index
+		defer {
+			delete(vals)
+			delete(r_idx)
+		}
+
+		// Set up test values
+		for _, i in vals {
+			vals[i]     = rand.uint64(&r)
+		}
+
+		// Sort
+		f_idx := slice.sort_with_indices(vals)
+		defer delete(f_idx)
+
+		// Verify sorted test values
+		rand.init(&r, seed)
+
+		for v, i in f_idx {
+			r_idx[v] = i
+		}
+
+		last: u64
+		for v, i in vals {
+			if i > 0 {
+				val_pass := v >= last
+				expect(t, val_pass, "Expected values to have been sorted.")
+				if !val_pass {
+					break
+				}
+			}
+
+			idx_pass := vals[r_idx[i]] == rand.uint64(&r)
+			expect(t, idx_pass, "Expected index to have been sorted.")
+			if !idx_pass {
+				break
+			}
+			last = v
+		}
+	}
+}
+
+@test
+test_sort_by_indices :: proc(t: ^testing.T) {
+	seed := rand.uint64()
+	fmt.printf("Random seed: %v\n", seed)
+
+	// Test sizes are all prime.
+	test_sizes :: []int{7, 13, 347, 1031, 10111, 100003}
+
+	for test_size in test_sizes {
+		fmt.printf("Sorting %v random u64 values along with index.\n", test_size)
+
+		r := rand.create(seed)
+
+		vals  := make([]u64, test_size)
+		r_idx := make([]int, test_size) // Reverse index
+		defer {
+			delete(vals)
+			delete(r_idx)
+		}
+
+		// Set up test values
+		for _, i in vals {
+			vals[i]     = rand.uint64(&r)
+		}
+
+		// Sort
+		f_idx := slice.sort_with_indices(vals)
+		defer delete(f_idx)
+
+		// Verify sorted test values
+		rand.init(&r, seed)
+
+		{
+			indices := make([]int, test_size)
+			defer delete(indices)
+			for _, i in indices {
+				indices[i] = i
+			}
+
+			sorted_indices := slice.sort_by_indices(indices, f_idx)
+			defer delete(sorted_indices)
+			for v, i in sorted_indices {
+				idx_pass := v == f_idx[i]
+				expect(t, idx_pass, "Expected the sorted index to be the same as the result from sort_with_indices")
+				if !idx_pass {
+					break
+				}
+			}
+		}
+		{
+			indices := make([]int, test_size)
+			defer delete(indices)
+			for _, i in indices {
+				indices[i] = i
+			}
+
+			slice.sort_by_indices_overwrite(indices, f_idx)
+			for v, i in indices {
+				idx_pass := v == f_idx[i]
+				expect(t, idx_pass, "Expected the sorted index to be the same as the result from sort_with_indices")
+				if !idx_pass {
+					break
+				}
+			}
+		}
+		{
+			indices := make([]int, test_size)
+			swap := make([]int, test_size)
+			defer {
+				delete(indices)
+				delete(swap)
+			}
+			for _, i in indices {
+				indices[i] = i
+			}
+
+			slice.sort_by_indices(indices, swap, f_idx)
+			for v, i in swap {
+				idx_pass := v == f_idx[i]
+				expect(t, idx_pass, "Expected the sorted index to be the same as the result from sort_with_indices")
+				if !idx_pass {
+					break
+				}
+			}
+		}
+	}
+}

+ 1 - 1
tools/odinfmt/flag/flag.odin

@@ -146,7 +146,7 @@ reflect_args_structure :: proc(ctx: ^Flag_Context, v: any) -> Flag_Error {
 		type := types[i];
 
 		if named_type, ok := type.variant.(Type_Info_Named); ok {
-			if union_type, ok := named_type.base.variant.(Type_Info_Union); ok && union_type.maybe && len(union_type.variants) == 1 {
+			if union_type, ok := named_type.base.variant.(Type_Info_Union); ok && len(union_type.variants) == 1 {
 				flag.optional = true;
 				flag.tag_ptr = rawptr(uintptr(union_type.tag_offset) + uintptr(v.data) + uintptr(offsets[i]));
 				type = union_type.variants[0];

+ 1 - 1
tools/odinfmt/main.odin

@@ -70,7 +70,7 @@ walk_files :: proc(info: os.File_Info, in_err: os.Errno) -> (err: os.Errno, skip
 }
 
 main :: proc() {
-	init_global_temporary_allocator(mem.megabytes(100));
+	init_global_temporary_allocator(mem.Megabyte * 100)
 
 	args: Args;
 

+ 2 - 0
vendor/OpenGL/constants.odin

@@ -1,5 +1,7 @@
 package odin_gl
 
+GL_DEBUG :: #config(GL_DEBUG, ODIN_DEBUG)
+
 FALSE                          :: false
 TRUE                           :: true
 

+ 1 - 1
vendor/OpenGL/helpers.odin

@@ -46,7 +46,7 @@ get_last_error_message :: proc() -> (compile_message: string, compile_type: Shad
 // Shader checking and linking checking are identical
 // except for calling differently named GL functions
 // it's a bit ugly looking, but meh
-when ODIN_DEBUG {
+when GL_DEBUG {
 	import "core:runtime"
 	
 	@private

+ 1 - 1
vendor/OpenGL/wrappers.odin

@@ -2,7 +2,7 @@ package odin_gl
 
 #assert(size_of(bool) == size_of(u8))
 
-when !ODIN_DEBUG {
+when !GL_DEBUG {
 	// VERSION_1_0
 	CullFace               :: proc "c" (mode: u32)                                                                                         {        impl_CullFace(mode)                                                                         }
 	FrontFace              :: proc "c" (mode: u32)                                                                                         {        impl_FrontFace(mode)                                                                        }

+ 8 - 1
vendor/README.md

@@ -134,4 +134,11 @@ See also LICENSE in the `GGPO` directory itself.
 `botan.lib` is available under Botan's [BSD](https://botan.randombit.net/license.txt) license.
 
 See also LICENSE in the `botan` directory itself.
-Includes full bindings as well as wrappers to match the `core:crypto` API.
+Includes full bindings as well as wrappers to match the `core:crypto` API.
+
+## CommonMark
+
+[CMark](https://github.com/commonmark/cmark) CommonMark parsing library.
+
+See also LICENSE in the `commonmark` directory itself.
+Includes full bindings and Windows `.lib` and `.dll`.

+ 170 - 0
vendor/commonmark/LICENSE

@@ -0,0 +1,170 @@
+Copyright (c) 2014, John MacFarlane
+
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+
+    * Redistributions of source code must retain the above copyright
+      notice, this list of conditions and the following disclaimer.
+
+    * Redistributions in binary form must reproduce the above
+      copyright notice, this list of conditions and the following
+      disclaimer in the documentation and/or other materials provided
+      with the distribution.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+-----
+
+houdini.h, houdini_href_e.c, houdini_html_e.c, houdini_html_u.c
+
+derive from https://github.com/vmg/houdini (with some modifications)
+
+Copyright (C) 2012 Vicent Martí
+
+Permission is hereby granted, free of charge, to any person obtaining a copy of
+this software and associated documentation files (the "Software"), to deal in
+the Software without restriction, including without limitation the rights to
+use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
+of the Software, and to permit persons to whom the Software is furnished to do
+so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
+
+-----
+
+buffer.h, buffer.c, chunk.h
+
+are derived from code (C) 2012 Github, Inc.
+
+Permission is hereby granted, free of charge, to any person obtaining a copy of
+this software and associated documentation files (the "Software"), to deal in
+the Software without restriction, including without limitation the rights to
+use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
+of the Software, and to permit persons to whom the Software is furnished to do
+so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
+
+-----
+
+utf8.c and utf8.c
+
+are derived from utf8proc
+(<http://www.public-software-group.org/utf8proc>),
+(C) 2009 Public Software Group e. V., Berlin, Germany.
+
+Permission is hereby granted, free of charge, to any person obtaining a
+copy of this software and associated documentation files (the "Software"),
+to deal in the Software without restriction, including without limitation
+the rights to use, copy, modify, merge, publish, distribute, sublicense,
+and/or sell copies of the Software, and to permit persons to whom the
+Software is furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+DEALINGS IN THE SOFTWARE.
+
+-----
+
+The normalization code in normalize.py was derived from the
+markdowntest project, Copyright 2013 Karl Dubost:
+
+The MIT License (MIT)
+
+Copyright (c) 2013 Karl Dubost
+
+Permission is hereby granted, free of charge, to any person obtaining
+a copy of this software and associated documentation files (the
+"Software"), to deal in the Software without restriction, including
+without limitation the rights to use, copy, modify, merge, publish,
+distribute, sublicense, and/or sell copies of the Software, and to
+permit persons to whom the Software is furnished to do so, subject to
+the following conditions:
+
+The above copyright notice and this permission notice shall be
+included in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+-----
+
+The CommonMark spec (test/spec.txt) is
+
+Copyright (C) 2014-15 John MacFarlane
+
+Released under the Creative Commons CC-BY-SA 4.0 license:
+<http://creativecommons.org/licenses/by-sa/4.0/>.
+
+-----
+
+The test software in test/ is
+
+Copyright (c) 2014, John MacFarlane
+
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+
+    * Redistributions of source code must retain the above copyright
+      notice, this list of conditions and the following disclaimer.
+
+    * Redistributions in binary form must reproduce the above
+      copyright notice, this list of conditions and the following
+      disclaimer in the documentation and/or other materials provided
+      with the distribution.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

+ 4 - 0
vendor/commonmark/build.bat

@@ -0,0 +1,4 @@
+@echo off
+pushd W:\Odin-other\Odin-test
+call build.bat
+popd

+ 526 - 0
vendor/commonmark/cmark.odin

@@ -0,0 +1,526 @@
+/*
+	Bindings against CMark (https://github.com/commonmark/cmark)
+
+	Original authors: John MacFarlane, Vicent Marti, Kārlis Gaņģis, Nick Wellnhofer.
+	See LICENSE for license details.
+*/
+package commonmark
+
+import "core:c"
+import "core:c/libc"
+import "core:runtime"
+
+BINDING_VERSION :: Version_Info{major = 0, minor = 30, patch = 2}
+
+when ODIN_OS == .Windows {
+	foreign import lib {
+		"cmark_static.lib",
+	}
+} else when ODIN_OS == .Linux {
+	foreign import lib {
+		"libcmark.a",
+	}
+}
+
+Option :: enum c.int {
+	Source_Position =  1, // Include a `data-sourcepos` attribute on all block elements.
+	Hard_Breaks     =  2, // Render `softbreak` as hard line breaks.
+	Safe            =  3, // Defined for API compatibility, now enabled by default.
+	Unsafe          = 17, // Render raw HTML and unsafe links (`javascript:`, `vbscript:`,
+						  // `file:`, and `data:`, except for `image/png`, `image/gif`,
+						  // `image/jpeg`, or `image/webp` mime types).  By default,
+						  // raw HTML is replaced by a placeholder HTML comment. Unsafe
+						  // links are replaced by empty strings.
+	No_Breaks       =  4, // Render `softbreak` elements as spaces.
+	Normalize       =  8, // Legacy option, no effect.
+	Validate_UTF8   =  9, // Validate UTF-8 input before parsing, replacing illegal
+						  // sequences with the replacement character U+FFFD.
+	Smart           = 10, // Convert straight quotes to curly, --- to em dashes, -- to en dashes.
+}
+Options :: bit_set[Option; c.int]
+
+DEFAULT_OPTIONS :: Options{}
+
+Node_Type :: enum u16 {
+	// Error status
+	None = 0,
+
+	/* Block */
+	Document,
+	Block_Quote,
+	List,
+	Item,
+	Code_Block,
+	HTML_Block,
+	Custom_Block,
+	Paragraph,
+	Heading,
+	Thematic_Break,
+
+	/* Inline */
+	Text,
+	Soft_Break,
+	Line_Break,
+	Code,
+	HTML_Inline,
+	Custom_Inline,
+	Emph,
+	Strong,
+	Link,
+	Image,
+
+	First_Block  = Document,
+	Last_Block   = Thematic_Break,
+
+	First_Inline = Text,
+	Last_Inline  = Image,
+}
+
+List_Type :: enum c.int {
+	None,
+	Bullet,
+	Ordered,
+}
+
+Delim_Type :: enum c.int {
+	None,
+	Period,
+	Paren,
+}
+
+// Version information
+Version_Info :: struct {
+	patch: u8,
+	minor: u8,
+	major: u8,
+	_:     u8,
+}
+
+@(default_calling_convention="c", link_prefix="cmark_")
+foreign lib {
+	version        :: proc() -> (res: Version_Info) ---
+	version_string :: proc() -> (res: cstring) ---
+}
+
+// Simple API
+@(default_calling_convention="c", link_prefix="cmark_")
+foreign lib {
+	// Convert 'text' (assumed to be a UTF-8 encoded string with length `len`) from CommonMark Markdown to HTML
+	// returning a null-terminated, UTF-8-encoded string. It is the caller's responsibility
+	// to free the returned buffer.
+	markdown_to_html :: proc(text: cstring, length: c.size_t, options: Options) -> (html: cstring) ---
+}
+
+markdown_to_html_from_string :: proc(text: string, options: Options) -> (html: string) {
+	return string(markdown_to_html(cstring(raw_data(text)), len(text), options))
+}
+
+// Custom allocator - Defines the memory allocation functions to be used by CMark
+// when parsing and allocating a document tree
+Allocator :: struct {
+	calloc:  proc "c" (num: c.size_t, size: c.size_t)  -> rawptr,
+	realloc: proc "c" (ptr: rawptr, new_size: c.size_t) -> rawptr,
+	free:    proc "c" (ptr: rawptr),
+}
+
+@(default_calling_convention="c", link_prefix="cmark_")
+foreign lib {
+	// Returns a pointer to the default memory allocator.
+	get_default_mem_allocator :: proc() -> (mem: ^Allocator) ---
+}
+
+bufsize_t :: distinct i32
+
+// Node creation, destruction, and tree traversal
+Node :: struct {
+	mem:          ^Allocator,
+
+	next:         ^Node,
+	prev:         ^Node,
+	parent:       ^Node,
+	first_child:  ^Node,
+	last_child:   ^Node,
+
+	user_data:    rawptr,
+	data:         [^]u8,
+	len:          bufsize_t,
+
+	start_line:   c.int,
+	start_column: c.int,
+	end_line:     c.int,
+	end_column:   c.int,
+
+	type:         Node_Type,
+	flags:        Node_Flags,
+
+	as: struct #raw_union {
+		list:    List,
+		code:    Code,
+		heading: Heading,
+		link:    Link,
+		custom:  Custom,
+		html_block_type: c.int,
+	},
+}
+
+Node_Flag :: enum u16 {
+	Open              = 0,
+	Last_Line_Blank   = 1,
+	Last_Line_Checked = 2,
+}
+Node_Flags :: bit_set[Node_Flag; u16]
+
+List :: struct {
+	marker_offset: c.int,
+	padding:       c.int,
+	start:         c.int,
+	list_type:     u8,
+	delimiter:     u8,
+	bullet_char:   u8,
+	tight:         c.bool,
+}
+
+Code :: struct {
+	info:         cstring,
+	fence_length: u8,
+	fence_offset: u8,
+	fence_char:   u8,
+	fenced:       b8,
+}
+
+Heading :: struct {
+	internal_offset: c.int,
+	level:           i8,
+	setext:          c.bool,
+}
+
+
+Link :: struct {
+	url:   cstring,
+	title: cstring,
+}
+
+Custom :: struct {
+	on_enter: cstring,
+	on_exit:  cstring,
+}
+
+@(default_calling_convention="c", link_prefix="cmark_")
+foreign lib {
+	// Creates a new node of type 'type'.
+	// Note that the node may have other required properties, which it is the caller's responsibility
+	// to assign.
+	node_new :: proc(type: Node_Type) -> (node: ^Node) ---
+
+	// Same as `node_new`, but explicitly listing the memory allocator used to allocate the node.
+	// Note: be sure to use the same allocator for every node in a tree, or bad things can happen.
+	node_new_with_mem :: proc(type: Node_Type, mem: ^Allocator) -> (node: ^Node) ---
+
+	// Frees the memory allocated for a node and any children.
+	node_free :: proc(node: ^Node) ---
+
+	/*
+		Tree Traversal
+	*/
+	// Returns the next node in the sequence after `node`, or nil if there is none.
+	node_next :: proc(node: ^Node) -> (next: ^Node) ---
+
+	// Returns the previous node in the sequence after `node`, or nil if there is none.
+	node_previous :: proc(node: ^Node) -> (prev: ^Node) ---
+
+	// Returns the parent of `node`, or nil if there is none.
+	node_parent :: proc(node: ^Node) -> (parent: ^Node) ---
+
+	// Returns the first child of `node`, or nil if `node` has no children.
+	node_first_child :: proc(node: ^Node) -> (child: ^Node) ---
+
+	// Returns the last child of `node`, or nil if `node` has no children.
+	node_last_child :: proc(node: ^Node) -> (child: ^Node) ---
+
+}
+
+Iter   :: distinct rawptr
+
+Event_Type :: enum c.int {
+	None,
+	Done,
+	Enter,
+	Exit,
+}
+
+@(default_calling_convention="c", link_prefix="cmark_")
+foreign lib {
+	// Creates a new iterator starting at 'root'.  The current node and event
+	// type are undefined until `iter_next` is called for the first time.
+	// The memory allocated for the iterator should be released using
+	// 'iter_free' when it is no longer needed.
+	iter_new :: proc(root: ^Node) -> (iter: ^Iter) ---
+
+	// Frees the memory allocated for an iterator.
+	iter_free :: proc(iter: ^Iter) ---
+
+	// Advances to the next node and returns the event type (`.Enter`, `.Exit`, `.Done`)
+	iter_next :: proc(iter: ^Iter) -> (event_type: Event_Type) ---
+
+	// Returns the current node.
+	iter_get_node :: proc(iter: ^Iter) -> (node: ^Node) ---
+
+	// Returns the current event type.
+	iter_get_event_type :: proc(iter: ^Iter) -> (event_type: Event_Type) ---
+
+	// Returns the root node.
+	iter_get_root :: proc(iter: ^Iter) -> (root: ^Node) ---
+
+	// Resets the iterator so that the current node is `current` and
+	// the event type is `event_type`. The new current node must be a
+	// descendant of the root node or the root node itself.
+	iter_reset :: proc(iter: ^Iter, current: ^Node, event_type: Event_Type) ---
+}
+
+// Accessors
+@(default_calling_convention="c", link_prefix="cmark_")
+foreign lib {
+	// Returns the user data of `node`.
+	node_get_user_data :: proc(node: ^Node) -> (user_data: rawptr) ---
+
+	// Sets arbitrary user data for `node`. Returns `true` on success, `false` on failure.
+	node_set_user_data :: proc(node: ^Node, user_data: rawptr) -> (success: b32) ---
+
+	// Returns the type of `node`, or `.None` on error.
+	node_get_type :: proc(node: ^Node) -> (node_type: Node_Type) ---
+
+	// Like `node_get_type`, but returns a string representation of the type, or "<unknown>".
+	node_get_type_string :: proc(node: ^Node) -> (node_type: cstring) ---
+
+	// Returns the string contents of `node`, or an empty string if none is set.
+	// Returns `nil` if called on a node that does not have string content.
+	node_get_literal :: proc(node: ^Node) -> (content: cstring) ---
+
+	// Sets the string contents of `node`. Returns `true` on success, `false` on failure.
+	node_set_literal :: proc(node: ^Node, content: cstring) -> (success: b32) ---
+
+	// Returns the heading level of `node`, or 0 if `node` is not a heading.
+	node_get_heading_level :: proc(node: ^Node) -> (level: c.int) ---
+
+	// Sets the heading level of `node`. Returns `true` on success, `false` on failure.
+	node_set_heading_level :: proc(node: ^Node, level: c.int) -> (success: b32) ---
+
+	// Returns the list type of `node`, or `.No_List` if not a list.
+	node_get_list_type :: proc(node: ^Node) -> (list_type: List_Type) ---
+
+	// Sets the list type of `node`. Returns `true` on success, `false` on failure.
+	node_set_list_type :: proc(node: ^Node, list_type: List_Type) -> (success: b32) ---
+
+	// Returns the list delimiter type of `node`, or `.No_Delim` if not a list.
+	node_get_list_delim :: proc(node: ^Node) -> (delim_type: Delim_Type) ---
+
+	// Sets the delimiter type of `node`. Returns `true` on success, `false` on failure.
+	node_set_list_delim :: proc(node: ^Node, delim_type: Delim_Type) -> (success: b32) ---
+
+	// Returns starting number of `node`, if it is an ordered list, otherwise 0.
+	node_get_list_start :: proc(node: ^Node) -> (start: c.int) ---
+
+	// Sets starting number of `node`, if it is an ordered list.
+	// Returns `true` on success, `false` on failure.
+	node_set_list_start :: proc(node: ^Node, start: c.int) -> (success: b32) ---
+
+	// Returns `true` if `node` is a tight list, `false` otherwise.
+	node_get_list_tight :: proc(node: ^Node) -> (tight: b32) ---
+
+	// Sets the "tightness" of a list. Returns `true` on success, `false` on failure.
+	node_set_list_tight :: proc(node: ^Node, tight: b32) -> (success: b32) ---
+
+	// Returns the info string from a fenced code block.
+	get_fence_info :: proc(node: ^Node) -> (fence_info: cstring) ---
+
+	// Sets the info string in a fenced code block, returning `true` on success and `false` on failure.
+	node_set_fence_info :: proc(node: ^Node, fence_info: cstring) -> (success: b32) ---
+
+	// Returns the URL of a link or image `node`, or an empty string if no URL is set.
+	// Returns nil if called on a node that is not a link or image.
+	node_get_url :: proc(node: ^Node) -> (url: cstring) ---
+
+	// Sets the URL of a link or image `node`. Returns `true` on success, `false` on failure.
+	node_set_url :: proc(node: ^Node, url: cstring) -> (success: b32) ---
+
+	// Returns the title of a link or image `node`, or an empty string if no title is set.
+	// Returns nil if called on a node that is not a link or image.
+	node_get_title :: proc(node: ^Node) -> (title: cstring) ---
+
+	// Sets the title of a link or image `node`. Returns `true` on success, `false` on failure.
+	node_set_title :: proc(node: ^Node, title: cstring) -> (success: b32) ---
+
+	// Returns the literal "on enter" text for a custom `node`, or an empty string if no on_enter is set.
+	// Returns nil if called on a non-custom node.
+	node_get_on_enter :: proc(node: ^Node) -> (on_enter: cstring) ---
+
+	// Sets the literal text to render "on enter" for a custom `node`.
+	// Any children of the node will be rendered after this text.
+	// Returns `true` on success, `false`on failure.
+	node_set_on_enter :: proc(node: ^Node, on_enter: cstring) -> (success: b32) ---
+
+	// Returns the line on which `node` begins.
+	node_get_start_line :: proc(node: ^Node) -> (line: c.int) ---
+
+	// Returns the column at which `node` begins.
+	node_get_start_column :: proc(node: ^Node) -> (column: c.int) ---
+
+	// Returns the line on which `node` ends.
+	node_get_end_line :: proc(node: ^Node) -> (line: c.int) ---
+
+	// Returns the column at which `node` ends.
+	node_get_end_column :: proc(node: ^Node) -> (column: c.int) ---
+}
+
+// Tree Manipulation
+@(default_calling_convention="c", link_prefix="cmark_")
+foreign lib {
+	// Unlinks a `node`, removing it from the tree, but not freeing its memory.
+	// (Use `node_free` for that.)
+	node_unlink :: proc(node: ^Node) ---
+
+	// Inserts 'sibling' before `node`.  Returns `true` on success, `false` on failure.
+	node_insert_before :: proc(node: ^Node, sibling: ^Node) -> (success: b32) ---
+
+	// Inserts 'sibling' after `node`. Returns `true` on success, `false` on failure.
+	node_insert_after :: proc(node: ^Node, sibling: ^Node) -> (success: b32) ---
+
+	// Replaces 'oldnode' with 'newnode' and unlinks 'oldnode'
+	// (but does not free its memory).
+	// Returns `true` on success, `false` on failure.
+	node_replace :: proc(old_node: ^Node, new_node: ^Node) -> (success: b32) ---
+
+	// Adds 'child' to the beginning of the children of `node`.
+	// Returns `true` on success, `false` on failure.
+	node_prepend_child :: proc(node: ^Node, child: ^Node) -> (success: b32) ---
+
+	// Adds 'child' to the end of the children of `node`.
+	// Returns `true` on success, `false` on failure.
+	node_append_child :: proc(node: ^Node, child: ^Node) -> (success: b32) ---
+
+	// Consolidates adjacent text nodes.
+	consolidate_text_nodes :: proc(root: ^Node) ---
+}
+
+Parser :: distinct rawptr
+
+@(default_calling_convention="c", link_prefix="cmark_")
+foreign lib {
+	// Creates a new parser object.
+	parser_new :: proc(options: Options) -> (parser: ^Parser) ---
+
+	// Creates a new parser object with the given memory allocator.
+	parser_new_with_mem :: proc(options: Options, mem: ^Allocator) -> (parser: ^Parser) ---
+
+	// Frees memory allocated for a parser object.
+	parser_free :: proc(parser: ^Parser) ---
+
+	// Feeds a string of length 'len' to 'parser'.
+	parser_feed :: proc(parser: ^Parser, buffer: [^]byte, len: c.size_t) ---
+
+	// Finish parsing and return a pointer to a tree of nodes.
+	parser_finish :: proc(parser: ^Parser) -> (root: ^Node) ---
+
+	// Parse a CommonMark document in 'buffer' of length 'len'.
+	// Returns a pointer to a tree of nodes. The memory allocated for
+	// the node tree should be released using 'node_free' when it is no longer needed.
+	parse_document :: proc(buffer: [^]byte, len: c.size_t, options: Options) -> (root: ^Node) ---
+
+	// Parse a CommonMark document in file 'f', returning a pointer to a tree of nodes.
+	// The memory allocated for the node tree should be released using 'node_free'
+	// when it is no longer needed.
+	//
+	// Called `parse_from_libc_file` so as not to confuse with Odin's file handling.
+
+	@(link_name = "parse_from_file")
+	parse_from_libc_file :: proc(file: ^libc.FILE, options: Options) -> (root: ^Node) ---
+}
+
+parser_feed_from_string :: proc "c" (parser: ^Parser, s: string) {
+	parser_feed(parser, raw_data(s), len(s))
+}
+parse_document_from_string :: proc "c" (s: string, options: Options) -> (root: ^Node) {
+	return parse_document(raw_data(s), len(s), options)
+}
+
+// Rendering
+@(default_calling_convention="c", link_prefix="cmark_")
+foreign lib {
+	// Render a `node` tree as XML.
+	// It is the caller's responsibilityto free the returned buffer.
+	render_xml :: proc(root: ^Node, options: Options) -> (xml: cstring) ---
+
+	// Render a `node` tree as an HTML fragment.
+	// It is up to the user to add an appropriate header and footer.
+	// It is the caller's responsibility to free the returned buffer.
+	render_html :: proc(root: ^Node, options: Options) -> (html: cstring) ---
+
+	// Render a `node` tree as a groff man page, without the header.
+	// It is the caller's responsibility to free the returned buffer.
+	render_man :: proc(root: ^Node, options: Options, width: c.int) -> (groff: cstring) ---
+
+	// Render a `node` tree as a commonmark document.
+	// It is the caller's responsibility to free the returned buffer.
+	render_commonmark :: proc(root: ^Node, options: Options, width: c.int) -> (commonmark: cstring) ---
+
+	// Render a `node` tree as a LaTeX document.
+	// It is the caller's responsibility to free the returned buffer.
+	render_latex :: proc(root: ^Node, options: Options, width: c.int) -> (latex: cstring) ---
+}
+
+// Helpers to free results from `render_*`.
+free_rawptr :: proc "c" (ptr: rawptr) {
+	cmm := get_default_mem_allocator()
+	cmm.free(ptr)
+}
+free_cstring :: proc "c" (str: cstring) {
+	free_rawptr(rawptr(str))
+}
+free_string :: proc "c" (s: string) {
+    free_rawptr(raw_data(s))
+}
+free :: proc{free_rawptr, free_cstring}
+
+// Wrap CMark allocator as Odin allocator
+@(private)
+cmark_allocator_proc :: proc(allocator_data: rawptr, mode: runtime.Allocator_Mode,
+                           size, alignment: int,
+                           old_memory: rawptr, old_size: int, loc := #caller_location) -> (res: []byte, err: runtime.Allocator_Error) {
+
+	cmark_alloc := cast(^Allocator)allocator_data
+	switch mode {
+	case .Alloc:
+		ptr := cmark_alloc.calloc(1, c.size_t(size))
+		res  = transmute([]byte)runtime.Raw_Slice{ptr, size}
+		return res, nil
+
+	case .Free:
+		cmark_alloc.free(old_memory)
+		return nil, nil
+
+	case .Free_All:
+		return nil, .Mode_Not_Implemented
+
+	case .Resize:
+		new_ptr := cmark_alloc.realloc(old_memory, c.size_t(size))
+		res = transmute([]byte)runtime.Raw_Slice{new_ptr, size}
+		if size > old_size {
+			runtime.mem_zero(raw_data(res[old_size:]), size - old_size)
+		}
+		return res, nil
+
+	case .Query_Features:
+		return nil, nil
+
+	case .Query_Info:
+		return nil, .Mode_Not_Implemented
+	}
+	return nil, nil
+}
+
+get_default_mem_allocator_as_odin :: proc() -> runtime.Allocator {
+	return runtime.Allocator{
+		procedure = cmark_allocator_proc,
+		data = rawptr(get_default_mem_allocator()),
+	}
+}

二進制
vendor/commonmark/cmark_static.lib


+ 117 - 0
vendor/commonmark/doc.odin

@@ -0,0 +1,117 @@
+// +ignore
+/*
+	Bindings against CMark (https://github.com/commonmark/cmark)
+
+	Original authors: John MacFarlane, Vicent Marti, Kārlis Gaņģis, Nick Wellnhofer.
+	See LICENSE for license details.
+*/
+package commonmark
+
+/*
+	Parsing - Simple interface:
+
+	```odin
+	import cm "vendor:commonmark"
+
+	hellope_world :: proc() {
+		fmt.printf("CMark version: %v\n", cm.version_string())
+
+		str := "Hellope *world*!"
+		root := cm.parse_document(raw_data(str), len(str), cm.DEFAULT_OPTIONS)
+		defer cm.node_free(root)
+
+		html := cm.render_html(root, cm.DEFAULT_OPTIONS)
+		defer cm.free(html)
+
+		fmt.println(html)
+	}
+	```
+
+	Parsing - Streaming interface:
+
+	```odin
+	import cm "vendor:commonmark"
+
+	streaming :: proc() {
+		using cm
+
+		STR :: "Hellope *world*!\n\n"
+		N   :: 50
+		STREAM_SIZE :: 42
+
+		str_buf: [len(STR) * N]u8
+		for i in 0..<N {
+			copy(str_buf[i*len(STR):], STR)
+		}
+
+		parser := parser_new(DEFAULT_OPTIONS)
+		defer parser_free(parser)
+
+		buf := str_buf[:]
+		for len(buf) > STREAM_SIZE {
+			parser_feed(parser, raw_data(buf), STREAM_SIZE)
+			buf = buf[STREAM_SIZE:]
+		}
+
+		if len(buf) > 0 {
+			parser_feed(parser, raw_data(buf), len(buf))
+			buf = buf[len(buf):]
+		}
+
+		root := parser_finish(parser)
+		defer cm.node_free(root)
+
+		html := cm.render_html(root, cm.DEFAULT_OPTIONS)
+		defer cm.free(html)
+
+		fmt.println(html)
+	}
+
+	```
+
+	An iterator will walk through a tree of nodes, starting from a root
+	node, returning one node at a time, together with information about
+	whether the node is being entered or exited.
+
+	The iterator will first descend to a child node, if there is one.
+	When there is no child, the iterator will go to the next sibling.
+	When there is no next sibling, the iterator will return to the parent
+	(but with an `Event_Type.Exit`).
+
+	The iterator will return `.Done` when it reaches the root node again.
+
+	One natural application is an HTML renderer, where an `.Enter` event
+	outputs an open tag and an `.Exit` event outputs a close tag.
+
+	An iterator might also be used to transform an AST in some systematic
+	way, for example, turning all level-3 headings into regular paragraphs.
+
+	```odin
+    usage_example(root: ^Node) {
+        ev_type: Event_Type
+        iter := iter_new(root)
+        defer iter_free(iter)
+        for {
+            ev_type = iter_next(iter)
+            if ev_type == .Done do break
+            cur := iter_get_node(iter)
+            // Do something with `cur` and `ev_type`
+        }
+    }
+    ```
+
+	Iterators will never return `.Exit` events for leaf nodes,
+	which are nodes of type:
+
+	* HTML_Block
+	* Thematic_Break
+	* Code_Block
+	* Text
+	* Soft_Break
+	* Line_Break
+	* Code
+	* HTML_Inline
+
+	Nodes must only be modified after an `.Exit` event, or an `.Enter` event for
+	leaf nodes.
+*/

+ 1 - 12
vendor/darwin/Foundation/NSWindow.odin

@@ -103,18 +103,7 @@ Window_alloc :: proc() -> ^Window {
 
 @(objc_type=Window, objc_name="initWithContentRect")
 Window_initWithContentRect :: proc (self: ^Window, contentRect: Rect, styleMask: WindowStyleMask, backing: BackingStoreType, doDefer: bool) -> ^Window {
-	self := self
-	// HACK: due to a compiler bug, the generated calling code does not
-	// currently work for this message. Has to do with passing a struct along
-	// with other parameters, so we don't send the rect here.
-	// Omiting the rect argument here actually works, because of how the C
-	// calling conventions are defined.
-	self = msgSend(^Window, self, "initWithContentRect:styleMask:backing:defer:", styleMask, backing, doDefer)
-
-	// apply the contentRect now, since we did not pass it to the init call
-	msgSend(nil, self, "setContentSize:", contentRect.size)
-	msgSend(nil, self, "setFrameOrigin:", contentRect.origin)
-	return self
+	return msgSend(^Window, self, "initWithContentRect:styleMask:backing:defer:", contentRect, styleMask, backing, doDefer)
 }
 @(objc_type=Window, objc_name="contentView")
 Window_contentView :: proc(self: ^Window) -> ^View {

+ 1 - 1
vendor/sdl2/sdl2.odin

@@ -200,7 +200,7 @@ Locale :: struct {
 
 @(default_calling_convention="c", link_prefix="SDL_")
 foreign lib {
-	GetPreferredLocales :: proc() -> ^Locale ---
+	GetPreferredLocales :: proc() -> [^]Locale ---
 }
 
 // misc

+ 1 - 1
vendor/wasm/js/runtime.js

@@ -1340,7 +1340,7 @@ function odinSetupDefaultImports(wasmMemoryInterface, consoleElement) {
 			sqrt:    (x) => Math.sqrt(x),
 			sin:     (x) => Math.sin(x),
 			cos:     (x) => Math.cos(x),
-			pow:     (x) => Math.pow(x),
+			pow:     (x, power) => Math.pow(x, power),
 			fmuladd: (x, y, z) => x*y + z,
 			ln:      (x) => Math.log(x),
 			exp:     (x) => Math.exp(x),

部分文件因文件數量過多而無法顯示