Browse Source

Merged with master

Tyler Erickson 5 years ago
parent
commit
bb026c99a9

+ 1 - 0
.gitattributes

@@ -0,0 +1 @@
+*.odin linguist-language=Odin

+ 1 - 1
README.md

@@ -29,7 +29,7 @@ The Odin programming language is fast, concise, readable, pragmatic and open sou
 
 Website: [https://odin-lang.org/](https://odin-lang.org/)
 
-```go
+```odin
 package main
 
 import "core:fmt"

+ 5 - 5
core/encoding/base64/base64.odin

@@ -49,7 +49,7 @@ encode :: proc(data: []byte, ENC_TBL := ENC_TABLE, allocator := context.allocato
     c0, c1, c2, block: int;
 
     for i, d := 0, 0; i < length; i, d = i + 3, d + 4 {
-        c0, c1, c2 = int(data[i]), 0, 0;
+        c0, c1, c2 = int(data[i]), -1, -1;
 
         if i + 1 < length do c1 = int(data[i + 1]);
         if i + 2 < length do c2 = int(data[i + 2]);
@@ -58,13 +58,13 @@ encode :: proc(data: []byte, ENC_TBL := ENC_TABLE, allocator := context.allocato
 
         out[d]     = ENC_TBL[block >> 18 & 63];
         out[d + 1] = ENC_TBL[block >> 12 & 63];
-        out[d + 2] = c1 == 0 ? PADDING : ENC_TBL[block >> 6 & 63];
-        out[d + 3] = c2 == 0 ? PADDING : ENC_TBL[block & 63];
+        out[d + 2] = c1 == -1 ? PADDING : ENC_TBL[block >> 6 & 63];
+        out[d + 3] = c2 == -1 ? PADDING : ENC_TBL[block & 63];
     }
     return string(out);
 }
 
-decode :: proc(data: string, DEC_TBL := DEC_TABLE, allocator := context.allocator) -> []byte #no_bounds_check{
+decode :: proc(data: string, DEC_TBL := DEC_TABLE, allocator := context.allocator) -> []byte #no_bounds_check {
     length := len(data);
     if length == 0 do return []byte{};
 
@@ -90,4 +90,4 @@ decode :: proc(data: string, DEC_TBL := DEC_TABLE, allocator := context.allocato
         out[j + 2] = byte(b2);
     }
     return out;
-}
+}

+ 12 - 1
core/fmt/fmt.odin

@@ -1525,10 +1525,21 @@ fmt_value :: proc(fi: ^Info, v: any, verb: rune) {
 			return;
 		}
 
+
+		if info.maybe && len(info.variants) == 1 && reflect.is_pointer(info.variants[0]) {
+			if v.data == nil {
+				strings.write_string(fi.buf, "nil");
+			} else {
+				id := info.variants[0].id;
+				fmt_arg(fi, any{v.data, id}, verb);
+			}
+			return;
+		}
+
+		tag: i64 = -1;
 		tag_ptr := uintptr(v.data) + info.tag_offset;
 		tag_any := any{rawptr(tag_ptr), info.tag_type.id};
 
-		tag: i64 = -1;
 		switch i in tag_any {
 		case u8:   tag = i64(i);
 		case i8:   tag = i64(i);

+ 2 - 7
core/log/file_console_logger.odin

@@ -30,17 +30,15 @@ Default_File_Logger_Opts :: Options{
 
 
 File_Console_Logger_Data :: struct {
-    lowest_level: Level,
     file_handle:  os.Handle,
     ident : string,
 }
 
 create_file_logger :: proc(h: os.Handle, lowest := Level.Debug, opt := Default_File_Logger_Opts, ident := "") -> Logger {
     data := new(File_Console_Logger_Data);
-    data.lowest_level = lowest;
     data.file_handle = h;
     data.ident = ident;
-    return Logger{file_console_logger_proc, data, opt};
+    return Logger{file_console_logger_proc, data, lowest, opt};
 }
 
 destroy_file_logger ::proc(log : ^Logger) {
@@ -52,10 +50,9 @@ destroy_file_logger ::proc(log : ^Logger) {
 
 create_console_logger :: proc(lowest := Level.Debug, opt := Default_Console_Logger_Opts, ident := "") -> Logger {
     data := new(File_Console_Logger_Data);
-    data.lowest_level = lowest;
     data.file_handle = os.INVALID_HANDLE;
     data.ident = ident;
-    return Logger{file_console_logger_proc, data, opt};
+    return Logger{file_console_logger_proc, data, lowest, opt};
 }
 
 destroy_console_logger ::proc(log : ^Logger) {
@@ -65,8 +62,6 @@ destroy_console_logger ::proc(log : ^Logger) {
 
 file_console_logger_proc :: proc(logger_data: rawptr, level: Level, text: string, options: Options, location := #caller_location) {
     data := cast(^File_Console_Logger_Data)logger_data;
-    if level < data.lowest_level do return;
-
     h : os.Handle;
     if(data.file_handle != os.INVALID_HANDLE) do h = data.file_handle;
     else                                      do h = level <= Level.Error ? context.stdout : context.stderr;

+ 25 - 10
core/log/log.odin

@@ -60,9 +60,10 @@ Logger_Proc :: #type proc(data: rawptr, level: Level, text: string, options: Opt
 Logger :: runtime.Logger;
 /*
 Logger :: struct {
-	procedure: Logger_Proc,
-	data:      rawptr,
-	options:   Options,
+	procedure:    Logger_Proc,
+	data:      	  rawptr,
+	lowest_level: Level,
+	options:   	  Logger_Options,
 }
 */
 
@@ -74,7 +75,7 @@ create_multi_logger :: proc(logs: ..Logger) -> Logger {
 	data := new(Multi_Logger_Data);
 	data.loggers = make([]Logger, len(logs));
 	copy(data.loggers, logs);
-	return Logger{multi_logger_proc, data, nil};
+	return Logger{multi_logger_proc, data, Level.Debug, nil};
 }
 
 destroy_multi_logger :: proc(log : ^Logger) {
@@ -98,18 +99,32 @@ nil_logger_proc :: proc(data: rawptr, level: Level, text: string, options: Optio
 }
 
 nil_logger :: proc() -> Logger {
-	return Logger{nil_logger_proc, nil, nil};
+	return Logger{nil_logger_proc, nil, Level.Debug, nil};
 }
 
 // TODO(bill): Should these be redesigned so that they are do not rely upon `package fmt`?
-debug :: proc(fmt_str : string, args : ..any, location := #caller_location) do logf(level=Level.Debug,   fmt_str=fmt_str, args=args, location=location);
-info  :: proc(fmt_str : string, args : ..any, location := #caller_location) do logf(level=Level.Info,    fmt_str=fmt_str, args=args, location=location);
-warn  :: proc(fmt_str : string, args : ..any, location := #caller_location) do logf(level=Level.Warning, fmt_str=fmt_str, args=args, location=location);
-error :: proc(fmt_str : string, args : ..any, location := #caller_location) do logf(level=Level.Error,   fmt_str=fmt_str, args=args, location=location);
-fatal :: proc(fmt_str : string, args : ..any, location := #caller_location) do logf(level=Level.Fatal,   fmt_str=fmt_str, args=args, location=location);
+debugf :: proc(fmt_str : string, args : ..any, location := #caller_location) do logf(level=Level.Debug,   fmt_str=fmt_str, args=args, location=location);
+infof  :: proc(fmt_str : string, args : ..any, location := #caller_location) do logf(level=Level.Info,    fmt_str=fmt_str, args=args, location=location);
+warnf  :: proc(fmt_str : string, args : ..any, location := #caller_location) do logf(level=Level.Warning, fmt_str=fmt_str, args=args, location=location);
+errorf :: proc(fmt_str : string, args : ..any, location := #caller_location) do logf(level=Level.Error,   fmt_str=fmt_str, args=args, location=location);
+fatalf :: proc(fmt_str : string, args : ..any, location := #caller_location) do logf(level=Level.Fatal,   fmt_str=fmt_str, args=args, location=location);
+
+debug :: proc(args : ..any, location := #caller_location) do log(level=Level.Debug,   args=args, location=location);
+info  :: proc(args : ..any, location := #caller_location) do log(level=Level.Info,    args=args, location=location);
+warn  :: proc(args : ..any, location := #caller_location) do log(level=Level.Warning, args=args, location=location);
+error :: proc(args : ..any, location := #caller_location) do log(level=Level.Error,   args=args, location=location);
+fatal :: proc(args : ..any, location := #caller_location) do log(level=Level.Fatal,   args=args, location=location);
+
+log :: proc(level : Level, args : ..any, location := #caller_location) {
+	logger := context.logger;
+	if level < logger.lowest_level do return;
+	str := fmt.tprint(..args); //NOTE(Hoej): While tprint isn't thread-safe, no logging is.
+	logger.procedure(logger.data, level, str, logger.options, location);
+}
 
 logf :: proc(level : Level, fmt_str : string, args : ..any, location := #caller_location) {
 	logger := context.logger;
+	if level < logger.lowest_level do return;
 	str := len(args) > 0 ? fmt.tprintf(fmt_str, ..args) : fmt.tprint(fmt_str); //NOTE(Hoej): While tprint isn't thread-safe, no logging is.
 	logger.procedure(logger.data, level, str, logger.options, location);
 }

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

@@ -357,7 +357,7 @@ matrix_mul_vector :: proc(a: $A/[$I][$J]$E, b: $B/[I]E) -> (c: B)
 		  IS_NUMERIC(E) {
 	for i in 0..<I {
 		for j in 0..<J {
-			c[i] += a[i][j] * b[i];
+			c[j] += a[i][j] * b[i];
 		}
 	}
 	return;

+ 11 - 9
core/math/math.odin

@@ -742,26 +742,28 @@ atan2_f64 :: proc(y, x: f64) -> f64 {
 atan2 :: proc{atan2_f32, atan2_f64};
 
 atan_f32 :: proc(x: f32) -> f32 {
-	return atan2_f32(1.0, x);
+	return atan2_f32(1, x);
 }
-atan :: proc{atan_f32};
-
+atan_f64 :: proc(x: f64) -> f64 {
+	return atan2_f64(1, x);
+}
+atan :: proc{atan_f32, atan_f64};
 
 asin_f32 :: proc(x: f32) -> f32 {
-	return atan2_f32(x, sqrt_f32(1 - x*x));
+	return atan2_f32(x, 1 + sqrt_f32(1 - x*x));
 }
 asin_f64 :: proc(x: f64) -> f64 {
-	return atan2_f64(x, sqrt_f64(1 - x*x));
+	return atan2_f64(x, 1 + sqrt_f64(1 - x*x));
 }
-asin :: proc{asin_f32};
+asin :: proc{asin_f32, asin_f64};
 
 acos_f32 :: proc(x: f32) -> f32 {
-	return atan2_f32(sqrt_f32(1 - x), sqrt_f32(1 + x));
+	return 2 * atan2_f32(sqrt_f32(1 - x), sqrt_f32(1 + x));
 }
 acos_f64 :: proc(x: f64) -> f64 {
-	return atan2_f64(sqrt_f64(1 - x), sqrt_f64(1 + x));
+	return 2 * atan2_f64(sqrt_f64(1 - x), sqrt_f64(1 + x));
 }
-acos :: proc{acos_f32};
+acos :: proc{acos_f32, acos_f64};
 
 
 sinh_f32 :: proc(x: f32) -> f32 {

+ 11 - 1
core/mem/mem.odin

@@ -45,8 +45,18 @@ copy_non_overlapping :: proc "contextless" (dst, src: rawptr, len: int) -> rawpt
 	return runtime.mem_copy_non_overlapping(dst, src, len);
 }
 compare :: inline proc "contextless" (a, b: []byte) -> int {
-	return compare_byte_ptrs(&a[0], &b[0], min(len(a), len(b)));
+	// NOTE(tetra): no-abc is okay here because if the slices are empty, `&a[0]` is just nil+0 == nil, which
+	// compare_byte_ptrs handles fine when the passed length is also zero.
+	res := #no_bounds_check compare_byte_ptrs(&a[0], &b[0], min(len(a), len(b)));
+	if res == 0 && len(a) != len(b) {
+		return len(a) <= len(b) ? -1 : +1;
+	} else if len(a) == 0 && len(b) == 0 {
+		return 0;
+	} else {
+		return res;
+	}
 }
+
 compare_byte_ptrs :: proc "contextless" (a, b: ^byte, n: int) -> int #no_bounds_check {
 	x := slice_ptr(a, n);
 	y := slice_ptr(b, n);

+ 41 - 60
core/os/os.odin

@@ -129,85 +129,66 @@ read_ptr :: proc(fd: Handle, data: rawptr, len: int) -> (int, Errno) {
 heap_allocator_proc :: proc(allocator_data: rawptr, mode: mem.Allocator_Mode,
                             size, alignment: int,
                             old_memory: rawptr, old_size: int, flags: u64 = 0, loc := #caller_location) -> rawptr {
-/*
+
 	//
-	// NOTE(tetra, 2019-11-10): The heap doesn't respect alignment.
-	// HACK: Overallocate, align forwards, and then use the two bytes immediately before
-	// the address we return, to store the padding we inserted.
-	// This allows us to pass the original pointer we got back from the heap to `free` later.
+	// NOTE(tetra, 2020-01-14): The heap doesn't respect alignment.
+	// Instead, we overallocate by `alignment + size_of(rawptr) - 1`, and insert
+	// padding. We also store the original pointer returned by heap_alloc right before
+	// the pointer we return to the user.
 	//
 
-	align_and_store_padding :: proc(ptr: rawptr, alignment: int) -> rawptr {
-		ptr := mem.ptr_offset(cast(^u8) ptr, 2);
-		new_ptr := cast(^u8) mem.align_forward(ptr, uintptr(alignment));
-		offset := mem.ptr_sub(new_ptr, cast(^u8) ptr) + 2;
-		assert(offset < int(max(u16)));
-		(^[2]u8)(mem.ptr_offset(new_ptr, -2))^ = transmute([2]u8) u16(offset);
-		return new_ptr;
-	}
-
-	recover_original_pointer :: proc(ptr: rawptr) -> rawptr {
-		ptr := cast(^u8) ptr;
-		offset := transmute(u16) (^[2]u8)(mem.ptr_offset(ptr, -2))^;
-		ptr = mem.ptr_offset(ptr, -int(offset));
-		return ptr;
-	}
-
-	aligned_heap_alloc :: proc(size: int, alignment: int) -> rawptr {
-		// NOTE(tetra): Alignment 1 will mean we only have one extra byte.
-		// This is not enough for a u16 - so we ensure there is at least two bytes extra.
-		// This also means that the pointer is always aligned to at least 2.
-		extra := alignment;
-		if extra <= 1 do extra = 2;
-
-		orig := cast(^u8) heap_alloc(size + extra);
-		if orig == nil do return nil;
-		ptr := align_and_store_padding(orig, alignment);
-		assert(recover_original_pointer(ptr) == orig);
-		return ptr;
-	}
+	aligned_alloc :: proc(size, alignment: int, old_ptr: rawptr = nil) -> rawptr {
+		a := max(alignment, align_of(rawptr));
+		space := size + a - 1;
 
-	switch mode {
-	case .Alloc:
-		return aligned_heap_alloc(size, alignment);
+		allocated_mem: rawptr;
+		if old_ptr != nil {
+			original_old_ptr := mem.ptr_offset((^rawptr)(old_ptr), -1)^;
+			allocated_mem = heap_resize(original_old_ptr, space+size_of(rawptr));
+		} else {
+			allocated_mem = heap_alloc(space+size_of(rawptr));
+		}
+		aligned_mem := rawptr(mem.ptr_offset((^u8)(allocated_mem), size_of(rawptr)));
 
-	case .Free:
-		if old_memory != nil {
-			ptr := recover_original_pointer(old_memory);
-			heap_free(ptr);
+		ptr := uintptr(aligned_mem);
+		aligned_ptr := (ptr - 1 + uintptr(a)) & -uintptr(a);
+		diff := int(aligned_ptr - ptr);
+		if (size + diff) > space {
+			return nil;
 		}
-		return nil;
 
-	case .Free_All:
-		// NOTE(bill): Does nothing
+		aligned_mem = rawptr(aligned_ptr);
+		mem.ptr_offset((^rawptr)(aligned_mem), -1)^ = allocated_mem;
 
-	case .Resize:
-		if old_memory == nil {
-			return aligned_heap_alloc(size, alignment);
+		return aligned_mem;
+	}
+
+	aligned_free :: proc(p: rawptr) {
+		if p != nil {
+			heap_free(mem.ptr_offset((^rawptr)(p), -1)^);
 		}
-		ptr := recover_original_pointer(old_memory);
-		ptr = heap_resize(ptr, size);
-		assert(ptr != nil);
-		return align_and_store_padding(ptr, alignment);
 	}
 
-	return nil;
-*/
+	aligned_resize :: proc(p: rawptr, old_size: int, new_size: int, new_alignment: int) -> rawptr {
+		if p == nil do return nil;
+		return aligned_alloc(new_size, new_alignment, p);
+	}
+
 	switch mode {
 	case .Alloc:
-		return heap_alloc(size);
+		return aligned_alloc(size, alignment);
 
 	case .Free:
-		if old_memory != nil {
-			heap_free(old_memory);
-		}
-		return nil;
+		aligned_free(old_memory);
 
 	case .Free_All:
-		// NOTE(bill): Does nothing
+		// NOTE(tetra): Do nothing.
 
 	case .Resize:
-		return heap_resize(old_memory, size);
+		if old_memory == nil {
+			return aligned_alloc(size, alignment);
+		}
+		return aligned_resize(old_memory, old_size, size, alignment);
 	}
 
 	return nil;

+ 33 - 0
core/os/os_darwin.odin

@@ -5,6 +5,7 @@ foreign import libc "system:c"
 
 import "core:runtime"
 import "core:strings"
+import "core:c"
 
 OS :: "darwin";
 
@@ -263,6 +264,8 @@ X_OK :: 1; // Test for execute permission
 F_OK :: 0; // Test for file existance
 
 foreign libc {
+	@(link_name="__error") __error :: proc() -> ^int ---;
+
 	@(link_name="open")    _unix_open    :: proc(path: cstring, flags: int, #c_vararg mode: ..any) -> Handle ---;
 	@(link_name="close")   _unix_close   :: proc(handle: Handle) ---;
 	@(link_name="read")    _unix_read    :: proc(handle: Handle, buffer: rawptr, count: int) -> int ---;
@@ -278,6 +281,8 @@ foreign libc {
 	@(link_name="free")    _unix_free    :: proc(ptr: rawptr) ---;
 	@(link_name="realloc") _unix_realloc :: proc(ptr: rawptr, size: int) -> rawptr ---;
 	@(link_name="getenv")  _unix_getenv  :: proc(cstring) -> cstring ---;
+	@(link_name="getcwd")  _unix_getcwd  :: proc(buf: cstring, len: c.size_t) -> cstring ---;
+	@(link_name="chdir")   _unix_chdir   :: proc(buf: cstring) -> c.int ---;
 
 	@(link_name="exit")    _unix_exit    :: proc(status: int) ---;
 }
@@ -289,6 +294,10 @@ foreign dl {
 	@(link_name="dlerror") _unix_dlerror :: proc() -> cstring ---;
 }
 
+get_last_error :: proc() -> int {
+	return __error()^;
+}
+
 open :: proc(path: string, flags: int = O_RDONLY, mode: int = 0) -> (Handle, Errno) {
 	cstr := strings.clone_to_cstring(path);
 	handle := _unix_open(cstr, flags, mode);
@@ -394,6 +403,30 @@ getenv :: proc(name: string) -> (string, bool) {
 	return string(cstr), true;
 }
 
+get_current_directory :: proc() -> string {
+	page_size := get_page_size(); // NOTE(tetra): See note in os_linux.odin/get_current_directory.
+	buf := make([dynamic]u8, page_size);
+	for {
+		cwd := _unix_getcwd(cstring(#no_bounds_check &buf[0]), c.size_t(len(buf)));
+		if cwd != nil {
+			return string(cwd);
+		}
+		if Errno(get_last_error()) != ERANGE {
+			return "";
+		}
+		resize(&buf, len(buf)+page_size);
+	}
+	unreachable();
+	return "";
+}
+
+set_current_directory :: proc(path: string) -> (err: Errno) {
+	cstr := strings.clone_to_cstring(path, context.temp_allocator);
+	res := _unix_chdir(cstr);
+	if res == -1 do return Errno(get_last_error());
+	return ERROR_NONE;
+}
+
 exit :: inline proc(code: int) -> ! {
 	_unix_exit(code);
 }

+ 139 - 108
core/os/os_linux.odin

@@ -5,6 +5,7 @@ foreign import libc "system:c"
 
 import "core:runtime"
 import "core:strings"
+import "core:c"
 
 OS :: "linux";
 
@@ -15,94 +16,95 @@ Syscall   :: distinct int;
 
 INVALID_HANDLE :: ~Handle(0);
 
-ERROR_NONE:    	Errno : 0;
-EPERM:         	Errno : 1;
-ENOENT:        	Errno : 2;
-ESRCH:         	Errno : 3;
-EINTR:         	Errno : 4;
-EIO:           	Errno : 5;
-ENXIO:         	Errno : 6;
-EBADF:         	Errno : 9;
-EAGAIN:        	Errno : 11;
-ENOMEM:        	Errno : 12;
-EACCES:        	Errno : 13;
-EFAULT:        	Errno : 14;
-EEXIST:        	Errno : 17;
-ENODEV:        	Errno : 19;
-ENOTDIR:       	Errno : 20;
-EISDIR:        	Errno : 21;
-EINVAL:        	Errno : 22;
-ENFILE:        	Errno : 23;
-EMFILE:        	Errno : 24;
-ETXTBSY:       	Errno : 26;
-EFBIG:         	Errno : 27;
-ENOSPC:        	Errno : 28;
-ESPIPE:        	Errno : 29;
-EROFS:         	Errno : 30;
-EPIPE:         	Errno : 32;
-
-EDEADLK: 		Errno :	35;	/* Resource deadlock would occur */
-ENAMETOOLONG: 	Errno : 36;	/* File name too long */
-ENOLCK: 		Errno : 37;	/* No record locks available */
-
-ENOSYS: Errno : 38;	/* Invalid system call number */
-
-ENOTEMPTY: 	Errno : 39;	/* Directory not empty */
-ELOOP: 		Errno : 40;	/* Too many symbolic links encountered */
-EWOULDBLOCK: Errno : EAGAIN;	/* Operation would block */
-ENOMSG: 	Errno : 42;	/* No message of desired type */
-EIDRM: 		Errno : 43;	/* Identifier removed */
-ECHRNG: 	Errno : 44;	/* Channel number out of range */
-EL2NSYNC: 	Errno : 45;	/* Level 2 not synchronized */
-EL3HLT: 	Errno : 46;	/* Level 3 halted */
-EL3RST: 	Errno : 47;	/* Level 3 reset */
-ELNRNG: 	Errno : 48;	/* Link number out of range */
-EUNATCH: 	Errno : 49;	/* Protocol driver not attached */
-ENOCSI: 	Errno : 50;	/* No CSI structure available */
-EL2HLT: 	Errno : 51;	/* Level 2 halted */
-EBADE: 		Errno : 52;	/* Invalid exchange */
-EBADR: 		Errno : 53;	/* Invalid request descriptor */
-EXFULL: 	Errno : 54;	/* Exchange full */
-ENOANO: 	Errno : 55;	/* No anode */
-EBADRQC: 	Errno : 56;	/* Invalid request code */
-EBADSLT: 	Errno : 57;	/* Invalid slot */
-EDEADLOCK:  Errno : EDEADLK;
-EBFONT: 	Errno : 59;	/* Bad font file format */
-ENOSTR: 	Errno : 60;	/* Device not a stream */
-ENODATA: 	Errno : 61;	/* No data available */
-ETIME: 		Errno : 62;	/* Timer expired */
-ENOSR: 		Errno : 63;	/* Out of streams resources */
-ENONET: 	Errno : 64;	/* Machine is not on the network */
-ENOPKG: 	Errno : 65;	/* Package not installed */
-EREMOTE: 	Errno : 66;	/* Object is remote */
-ENOLINK: 	Errno : 67;	/* Link has been severed */
-EADV: 		Errno : 68;	/* Advertise error */
-ESRMNT: 	Errno : 69;	/* Srmount error */
-ECOMM: 		Errno : 70;	/* Communication error on send */
-EPROTO: 	Errno : 71;	/* Protocol error */
-EMULTIHOP: 	Errno : 72;	/* Multihop attempted */
-EDOTDOT: 	Errno : 73;	/* RFS specific error */
-EBADMSG: 	Errno : 74;	/* Not a data message */
-EOVERFLOW: 	Errno : 75;	/* Value too large for defined data type */
-ENOTUNIQ: 	Errno : 76;	/* Name not unique on network */
-EBADFD: 	Errno : 77;	/* File descriptor in bad state */
-EREMCHG: 	Errno : 78;	/* Remote address changed */
-ELIBACC: 	Errno : 79;	/* Can not access a needed shared library */
-ELIBBAD: 	Errno : 80;	/* Accessing a corrupted shared library */
-ELIBSCN: 	Errno : 81;	/* .lib section in a.out corrupted */
-ELIBMAX: 	Errno : 82;	/* Attempting to link in too many shared libraries */
-ELIBEXEC: 	Errno : 83;	/* Cannot exec a shared library directly */
-EILSEQ: 	Errno : 84;	/* Illegal byte sequence */
-ERESTART: 	Errno : 85;	/* Interrupted system call should be restarted */
-ESTRPIPE: 	Errno : 86;	/* Streams pipe error */
-EUSERS: 	Errno : 87;	/* Too many users */
-ENOTSOCK: 	Errno : 88;	/* Socket operation on non-socket */
-EDESTADDRREQ: Errno : 89;	/* Destination address required */
-EMSGSIZE: 	Errno : 90;	/* Message too long */
-EPROTOTYPE: Errno : 91;	/* Protocol wrong type for socket */
-ENOPROTOOPT: 	Errno : 92;	/* Protocol not available */
-EPROTONOSUPPORT: Errno : 93;	/* Protocol not supported */
-ESOCKTNOSUPPORT: Errno : 94;	/* Socket type not supported */
+ERROR_NONE:     Errno : 0;
+EPERM:          Errno : 1;
+ENOENT:         Errno : 2;
+ESRCH:          Errno : 3;
+EINTR:          Errno : 4;
+EIO:            Errno : 5;
+ENXIO:          Errno : 6;
+EBADF:          Errno : 9;
+EAGAIN:         Errno : 11;
+ENOMEM:         Errno : 12;
+EACCES:         Errno : 13;
+EFAULT:         Errno : 14;
+EEXIST:         Errno : 17;
+ENODEV:         Errno : 19;
+ENOTDIR:        Errno : 20;
+EISDIR:         Errno : 21;
+EINVAL:         Errno : 22;
+ENFILE:         Errno : 23;
+EMFILE:         Errno : 24;
+ETXTBSY:        Errno : 26;
+EFBIG:          Errno : 27;
+ENOSPC:         Errno : 28;
+ESPIPE:         Errno : 29;
+EROFS:          Errno : 30;
+EPIPE:          Errno : 32;
+
+ERANGE:         Errno : 34; /* Result too large */
+EDEADLK:        Errno : 35; /* Resource deadlock would occur */
+ENAMETOOLONG:   Errno : 36; /* File name too long */
+ENOLCK:         Errno : 37; /* No record locks available */
+
+ENOSYS:         Errno : 38;	/* Invalid system call number */
+
+ENOTEMPTY:      Errno : 39;	/* Directory not empty */
+ELOOP:          Errno : 40;	/* Too many symbolic links encountered */
+EWOULDBLOCK:    Errno : EAGAIN; /* Operation would block */
+ENOMSG:         Errno : 42;	/* No message of desired type */
+EIDRM:          Errno : 43;	/* Identifier removed */
+ECHRNG:         Errno : 44;	/* Channel number out of range */
+EL2NSYNC:       Errno : 45;	/* Level 2 not synchronized */
+EL3HLT:         Errno : 46;	/* Level 3 halted */
+EL3RST:         Errno : 47;	/* Level 3 reset */
+ELNRNG:         Errno : 48;	/* Link number out of range */
+EUNATCH:        Errno : 49;	/* Protocol driver not attached */
+ENOCSI:         Errno : 50;	/* No CSI structure available */
+EL2HLT:         Errno : 51;	/* Level 2 halted */
+EBADE:          Errno : 52;	/* Invalid exchange */
+EBADR:          Errno : 53;	/* Invalid request descriptor */
+EXFULL:         Errno : 54;	/* Exchange full */
+ENOANO:         Errno : 55;	/* No anode */
+EBADRQC:        Errno : 56;	/* Invalid request code */
+EBADSLT:        Errno : 57;	/* Invalid slot */
+EDEADLOCK:      Errno : EDEADLK;
+EBFONT:         Errno : 59;	/* Bad font file format */
+ENOSTR:         Errno : 60;	/* Device not a stream */
+ENODATA:        Errno : 61;	/* No data available */
+ETIME:          Errno : 62;	/* Timer expired */
+ENOSR:          Errno : 63;	/* Out of streams resources */
+ENONET:         Errno : 64;	/* Machine is not on the network */
+ENOPKG:         Errno : 65;	/* Package not installed */
+EREMOTE:        Errno : 66;	/* Object is remote */
+ENOLINK:        Errno : 67;	/* Link has been severed */
+EADV:           Errno : 68;	/* Advertise error */
+ESRMNT:         Errno : 69;	/* Srmount error */
+ECOMM:          Errno : 70;	/* Communication error on send */
+EPROTO:         Errno : 71;	/* Protocol error */
+EMULTIHOP:      Errno : 72;	/* Multihop attempted */
+EDOTDOT:        Errno : 73;	/* RFS specific error */
+EBADMSG:        Errno : 74;	/* Not a data message */
+EOVERFLOW:      Errno : 75;	/* Value too large for defined data type */
+ENOTUNIQ:       Errno : 76;	/* Name not unique on network */
+EBADFD:         Errno : 77;	/* File descriptor in bad state */
+EREMCHG:        Errno : 78;	/* Remote address changed */
+ELIBACC:        Errno : 79;	/* Can not access a needed shared library */
+ELIBBAD:        Errno : 80;	/* Accessing a corrupted shared library */
+ELIBSCN:        Errno : 81;	/* .lib section in a.out corrupted */
+ELIBMAX:        Errno : 82;	/* Attempting to link in too many shared libraries */
+ELIBEXEC:       Errno : 83;	/* Cannot exec a shared library directly */
+EILSEQ:         Errno : 84;	/* Illegal byte sequence */
+ERESTART:       Errno : 85;	/* Interrupted system call should be restarted */
+ESTRPIPE:       Errno : 86;	/* Streams pipe error */
+EUSERS:         Errno : 87;	/* Too many users */
+ENOTSOCK:       Errno : 88;	/* Socket operation on non-socket */
+EDESTADDRREQ:   Errno : 89;	/* Destination address required */
+EMSGSIZE:       Errno : 90;	/* Message too long */
+EPROTOTYPE:     Errno : 91;	/* Protocol wrong type for socket */
+ENOPROTOOPT:    Errno : 92;	/* Protocol not available */
+EPROTONOSUPPORT:Errno : 93;	/* Protocol not supported */
+ESOCKTNOSUPPORT:Errno : 94;	/* Socket type not supported */
 EOPNOTSUPP: 	Errno : 95;	/* Operation not supported on transport endpoint */
 EPFNOSUPPORT: 	Errno : 96;	/* Protocol family not supported */
 EAFNOSUPPORT: 	Errno : 97;	/* Address family not supported by protocol */
@@ -259,11 +261,11 @@ foreign libc {
 	@(link_name="__errno_location") __errno_location    :: proc() -> ^int ---;
 	@(link_name="syscall")          syscall             :: proc(number: Syscall, #c_vararg args: ..any) -> int ---;
 
-	@(link_name="open")             _unix_open          :: proc(path: cstring, flags: int, mode: int) -> Handle ---;
-	@(link_name="close")            _unix_close         :: proc(fd: Handle) -> int ---;
-	@(link_name="read")             _unix_read          :: proc(fd: Handle, buf: rawptr, size: int) -> int ---;
-	@(link_name="write")            _unix_write         :: proc(fd: Handle, buf: rawptr, size: int) -> int ---;
-	@(link_name="lseek64")          _unix_seek          :: proc(fd: Handle, offset: i64, whence: i32) -> i64 ---;
+	@(link_name="open")             _unix_open          :: proc(path: cstring, flags: c.int, mode: c.int) -> Handle ---;
+	@(link_name="close")            _unix_close         :: proc(fd: Handle) -> c.int ---;
+	@(link_name="read")             _unix_read          :: proc(fd: Handle, buf: rawptr, size: c.size_t) -> c.ssize_t ---;
+	@(link_name="write")            _unix_write         :: proc(fd: Handle, buf: rawptr, size: c.size_t) -> c.ssize_t ---;
+	@(link_name="lseek64")          _unix_seek          :: proc(fd: Handle, offset: i64, whence: c.int) -> i64 ---;
 	@(link_name="gettid")           _unix_gettid        :: proc() -> u64 ---;
 	@(link_name="getpagesize")      _unix_getpagesize   :: proc() -> i32 ---;
 	@(link_name="stat64")           _unix_stat          :: proc(path: cstring, stat: ^Stat) -> int ---;
@@ -273,15 +275,17 @@ foreign libc {
 	@(link_name="malloc")           _unix_malloc        :: proc(size: int) -> rawptr ---;
 	@(link_name="calloc")           _unix_calloc        :: proc(num, size: int) -> rawptr ---;
 	@(link_name="free")             _unix_free          :: proc(ptr: rawptr) ---;
-	@(link_name="realloc")          _unix_realloc       :: proc(ptr: rawptr, size: int) -> rawptr ---;
+	@(link_name="realloc")          _unix_realloc       :: proc(ptr: rawptr, size: c.size_t) -> rawptr ---;
 	@(link_name="getenv")           _unix_getenv        :: proc(cstring) -> cstring ---;
+	@(link_name="getcwd")           _unix_getcwd        :: proc(buf: cstring, len: c.size_t) -> cstring ---;
+	@(link_name="chdir")            _unix_chdir         :: proc(buf: cstring) -> c.int ---;
 
-	@(link_name="exit")             _unix_exit          :: proc(status: int) -> ! ---;
+	@(link_name="exit")             _unix_exit          :: proc(status: c.int) -> ! ---;
 }
 foreign dl {
-	@(link_name="dlopen")           _unix_dlopen        :: proc(filename: cstring, flags: int) -> rawptr ---;
+	@(link_name="dlopen")           _unix_dlopen        :: proc(filename: cstring, flags: c.int) -> rawptr ---;
 	@(link_name="dlsym")            _unix_dlsym         :: proc(handle: rawptr, symbol: cstring) -> rawptr ---;
-	@(link_name="dlclose")          _unix_dlclose       :: proc(handle: rawptr) -> int ---;
+	@(link_name="dlclose")          _unix_dlclose       :: proc(handle: rawptr) -> c.int ---;
 	@(link_name="dlerror")          _unix_dlerror       :: proc() -> cstring ---;
 }
 
@@ -295,7 +299,7 @@ get_last_error :: proc() -> int {
 
 open :: proc(path: string, flags: int = O_RDONLY, mode: int = 0) -> (Handle, Errno) {
 	cstr := strings.clone_to_cstring(path);
-	handle := _unix_open(cstr, flags, mode);
+	handle := _unix_open(cstr, c.int(flags), c.int(mode));
 	delete(cstr);
 	if handle == -1 {
 		return INVALID_HANDLE, Errno(get_last_error());
@@ -312,26 +316,26 @@ close :: proc(fd: Handle) -> Errno {
 }
 
 read :: proc(fd: Handle, data: []byte) -> (int, Errno) {
-	bytes_read := _unix_read(fd, &data[0], len(data));
+	bytes_read := _unix_read(fd, &data[0], c.size_t(len(data)));
 	if bytes_read == -1 {
 		return -1, Errno(get_last_error());
 	}
-	return bytes_read, ERROR_NONE;
+	return int(bytes_read), ERROR_NONE;
 }
 
 write :: proc(fd: Handle, data: []byte) -> (int, Errno) {
 	if len(data) == 0 {
 		return 0, ERROR_NONE;
 	}
-	bytes_written := _unix_write(fd, &data[0], len(data));
+	bytes_written := _unix_write(fd, &data[0], c.size_t(len(data)));
 	if bytes_written == -1 {
 		return -1, Errno(get_last_error());
 	}
-	return bytes_written, ERROR_NONE;
+	return int(bytes_written), ERROR_NONE;
 }
 
 seek :: proc(fd: Handle, offset: i64, whence: int) -> (i64, Errno) {
-	res := _unix_seek(fd, offset, i32(whence));
+	res := _unix_seek(fd, offset, c.int(whence));
 	if res == -1 {
 		return -1, Errno(get_last_error());
 	}
@@ -397,7 +401,7 @@ fstat :: inline proc(fd: Handle) -> (Stat, Errno) {
 access :: inline proc(path: string, mask: int) -> (bool, Errno) {
 	cstr := strings.clone_to_cstring(path);
 	defer delete(cstr);
-	result := _unix_access(cstr, mask);
+	result := _unix_access(cstr, c.int(mask));
 	if result == -1 {
 		return false, Errno(get_last_error());
 	}
@@ -406,11 +410,11 @@ access :: inline proc(path: string, mask: int) -> (bool, Errno) {
 
 heap_alloc :: proc(size: int) -> rawptr {
 	assert(size >= 0);
-	return _unix_calloc(1, size);
+	return _unix_calloc(1, c.size_t(size));
 }
 
 heap_resize :: proc(ptr: rawptr, new_size: int) -> rawptr {
-	return _unix_realloc(ptr, new_size);
+	return _unix_realloc(ptr, c.size_t(new_size));
 }
 
 heap_free :: proc(ptr: rawptr) {
@@ -427,8 +431,35 @@ getenv :: proc(name: string) -> (string, bool) {
 	return string(cstr), true;
 }
 
+get_current_directory :: proc() -> string {
+	// NOTE(tetra): I would use PATH_MAX here, but I was not able to find
+	// an authoritative value for it across all systems.
+	// The largest value I could find was 4096, so might as well use the page size.
+	page_size := get_page_size();
+	buf := make([dynamic]u8, page_size);
+	for {
+		cwd := _unix_getcwd(cstring(#no_bounds_check &buf[0]), c.size_t(len(buf)));
+		if cwd != nil {
+			return string(cwd);
+		}
+		if Errno(get_last_error()) != ERANGE {
+			return "";
+		}
+		resize(&buf, len(buf)+page_size);
+	}
+	unreachable();
+	return "";
+}
+
+set_current_directory :: proc(path: string) -> (err: Errno) {
+	cstr := strings.clone_to_cstring(path, context.temp_allocator);
+	res := _unix_chdir(cstr);
+	if res == -1 do return Errno(get_last_error());
+	return ERROR_NONE;
+}
+
 exit :: proc(code: int) -> ! {
-	_unix_exit(code);
+	_unix_exit(c.int(code));
 }
 
 current_thread_id :: proc "contextless" () -> int {
@@ -438,7 +469,7 @@ current_thread_id :: proc "contextless" () -> int {
 dlopen :: inline proc(filename: string, flags: int) -> rawptr {
 	cstr := strings.clone_to_cstring(filename);
 	defer delete(cstr);
-	handle := _unix_dlopen(cstr, flags);
+	handle := _unix_dlopen(cstr, c.int(flags));
 	return handle;
 }
 dlsym :: inline proc(handle: rawptr, symbol: string) -> rawptr {

+ 35 - 0
core/os/os_windows.odin

@@ -2,6 +2,7 @@
 package os
 
 import "core:sys/win32"
+import "core:intrinsics"
 
 OS :: "windows";
 
@@ -265,6 +266,40 @@ get_page_size :: proc() -> int {
 }
 
 
+
+// NOTE(tetra): GetCurrentDirectory is not thread safe with SetCurrentDirectory and GetFullPathName;
+// The current directory is stored as a global variable in the process.
+@private cwd_gate := false;
+
+get_current_directory :: proc() -> string {
+	for intrinsics.atomic_xchg(&cwd_gate, true) {}
+
+	sz_utf16 := win32.get_current_directory_w(0, nil);
+	dir_buf_wstr := make([]u16, sz_utf16, context.temp_allocator); // the first time, it _includes_ the NUL.
+
+	sz_utf16 = win32.get_current_directory_w(u32(len(dir_buf_wstr)), cast(win32.Wstring) &dir_buf_wstr[0]);
+	assert(int(sz_utf16)+1 == len(dir_buf_wstr)); // the second time, it _excludes_ the NUL.
+
+	intrinsics.atomic_store(&cwd_gate, false);
+
+	dir_utf8 := win32.utf16_to_utf8(dir_buf_wstr);
+	return dir_utf8[:len(dir_utf8)-1]; // NOTE(tetra): Remove the NUL.
+}
+
+set_current_directory :: proc(path: string) -> (err: Errno) {
+	wstr := win32.utf8_to_wstring(path);
+
+	for intrinsics.atomic_xchg(&cwd_gate, true) {}
+	defer intrinsics.atomic_store(&cwd_gate, false);
+
+	res := win32.set_current_directory_w(wstr);
+	if res == 0 do return Errno(win32.get_last_error());
+
+	return;
+}
+
+
+
 exit :: proc(code: int) -> ! {
 	win32.exit_process(u32(code));
 }

+ 13 - 10
core/runtime/core.odin

@@ -111,6 +111,7 @@ Type_Info_Union :: struct {
 	tag_type:     ^Type_Info,
 	custom_align: bool,
 	no_nil:       bool,
+	maybe:        bool,
 };
 Type_Info_Enum :: struct {
 	base:      ^Type_Info,
@@ -275,9 +276,10 @@ Logger_Options :: bit_set[Logger_Option];
 Logger_Proc :: #type proc(data: rawptr, level: Logger_Level, text: string, options: Logger_Options, location := #caller_location);
 
 Logger :: struct {
-	procedure: Logger_Proc,
-	data:      rawptr,
-	options:   Logger_Options,
+	procedure:    Logger_Proc,
+	data:         rawptr,
+	lowest_level: Logger_Level,
+	options:      Logger_Options,
 }
 
 Context :: struct {
@@ -433,7 +435,7 @@ default_logger_proc :: proc(data: rawptr, level: Logger_Level, text: string, opt
 }
 
 default_logger :: proc() -> Logger {
-	return Logger{default_logger_proc, nil, nil};
+	return Logger{default_logger_proc, nil, Logger_Level.Debug, nil};
 }
 
 
@@ -1131,7 +1133,7 @@ __dynamic_array_reserve :: proc(array_: rawptr, elem_size, elem_align: int, cap:
 		array.allocator = context.allocator;
 	}
 	assert(array.allocator.procedure != nil);
-	
+
 	if cap <= array.cap do return true;
 
 	old_size  := array.cap * elem_size;
@@ -1139,11 +1141,12 @@ __dynamic_array_reserve :: proc(array_: rawptr, elem_size, elem_align: int, cap:
 	allocator := array.allocator;
 
 	new_data := allocator.procedure(allocator.data, .Resize, new_size, elem_align, array.data, old_size, 0, loc);
-	if new_data == nil do return false;
-
-	array.data = new_data;
-	array.cap = cap;
-	return true;
+	if new_data != nil || elem_size == 0 {
+		array.data = new_data;
+		array.cap = cap;
+		return true;
+	}
+	return false;
 }
 
 __dynamic_array_resize :: proc(array_: rawptr, elem_size, elem_align: int, len: int, loc := #caller_location) -> bool {

+ 1 - 1
core/sort/sort.odin

@@ -103,7 +103,7 @@ _log2 :: proc(x: int) -> int {
 	return res;
 }
 
-merge_sort_proc :: proc(array: $A/[]$T, f: proc(T, T) -> int) where intrinsics.type_is_ordered(T) {
+merge_sort_proc :: proc(array: $A/[]$T, f: proc(T, T) -> int) {
 	merge :: proc(a: A, start, mid, end: int, f: proc(T, T) -> int) {
 		s, m := start, mid;
 

+ 2 - 0
core/strings/strings.odin

@@ -52,6 +52,8 @@ unsafe_string_to_cstring :: proc(str: string) -> cstring {
 	return cstring(d.data);
 }
 
+// Compares two strings, returning a value representing which one comes first lexiographically.
+// -1 for `a`; 1 for `b`, or 0 if they are equal.
 compare :: proc(lhs, rhs: string) -> int {
 	return mem.compare(transmute([]byte)lhs, transmute([]byte)rhs);
 }

+ 5 - 0
core/sys/win32/kernel32.odin

@@ -35,6 +35,11 @@ foreign kernel32 {
 	@(link_name="GetVersionExA")             get_version                  :: proc(osvi: ^OS_Version_Info_Ex_A) ---;
 	@(link_name="GetCurrentThreadId")        get_current_thread_id        :: proc() -> u32 ---;
 
+	// NOTE(tetra): Not thread safe with SetCurrentDirectory and GetFullPathName;
+	// The current directory is stored as a global variable in the process.
+	@(link_name="GetCurrentDirectoryW")       get_current_directory_w     :: proc(len: u32, buf: Wstring) -> u32 ---;
+	@(link_name="SetCurrentDirectoryW")       set_current_directory_w     :: proc(buf: Wstring) -> u32 ---;
+
 	@(link_name="GetSystemTimeAsFileTime")   get_system_time_as_file_time :: proc(system_time_as_file_time: ^Filetime) ---;
 	@(link_name="FileTimeToLocalFileTime")   file_time_to_local_file_time :: proc(file_time: ^Filetime, local_file_time: ^Filetime) -> Bool ---;
 	@(link_name="FileTimeToSystemTime")      file_time_to_system_time     :: proc(file_time: ^Filetime, system_time: ^Systemtime) -> Bool ---;

+ 23 - 0
examples/demo/demo.odin

@@ -1907,6 +1907,28 @@ constant_literal_expressions :: proc() {
 	fmt.println(STRING_CONST[3:][:4]);
 }
 
+union_maybe :: proc() {
+	fmt.println("\n#union #maybe");
+
+	Maybe :: union(T: typeid) #maybe {T};
+
+	i: Maybe(u8);
+	p: Maybe(^u8); // No tag is stored for pointers, nil is the sentinel value
+
+	#assert(size_of(i) == size_of(u8) + size_of(u8));
+	#assert(size_of(p) == size_of(^u8));
+
+	i = 123;
+	x := i.?;
+	y, y_ok := p.?;
+	p = &x;
+	z, z_ok := p.?;
+
+	fmt.println(i, p);
+	fmt.println(x, &x);
+	fmt.println(y, y_ok);
+	fmt.println(z, z_ok);
+}
 
 main :: proc() {
 	when true {
@@ -1937,5 +1959,6 @@ main :: proc() {
 		threading_example();
 		soa_struct_layout();
 		constant_literal_expressions();
+		union_maybe();
 	}
 }

+ 1 - 0
src/build_settings.cpp

@@ -126,6 +126,7 @@ struct BuildContext {
 	bool   use_lld;
 	bool   vet;
 	bool   cross_compiling;
+	bool   use_subsystem_windows;
 
 	QueryDataSetSettings query_data_set_settings;
 

+ 1 - 1
src/check_decl.cpp

@@ -1207,7 +1207,7 @@ void check_proc_body(CheckerContext *ctx_, Token token, DeclInfo *decl, Type *ty
 	}
 
 
-	bool where_clause_ok = evaluate_where_clauses(ctx, decl->scope, &decl->proc_lit->ProcLit.where_clauses, true);
+	bool where_clause_ok = evaluate_where_clauses(ctx, nullptr, decl->scope, &decl->proc_lit->ProcLit.where_clauses, true);
 	if (!where_clause_ok) {
 		// NOTE(bill, 2019-08-31): Don't check the body as the where clauses failed
 		return;

+ 93 - 40
src/check_expr.cpp

@@ -2260,6 +2260,8 @@ bool check_cast_internal(CheckerContext *c, Operand *x, Type *type) {
 			x->mode = Addressing_Value;
 		} else if (is_type_slice(type) && is_type_string(x->type)) {
 			x->mode = Addressing_Value;
+		} else if (is_type_union(type)) {
+			x->mode = Addressing_Value;
 		}
 		return true;
 	}
@@ -5278,6 +5280,10 @@ bool check_builtin_procedure(CheckerContext *c, Operand *operand, Ast *call, i32
 		break;
 	}
 
+	case BuiltinProc_cpu_relax:
+		operand->mode = Addressing_NoValue;
+		break;
+
 	case BuiltinProc_atomic_fence:
 	case BuiltinProc_atomic_fence_acq:
 	case BuiltinProc_atomic_fence_rel:
@@ -5942,7 +5948,9 @@ CALL_ARGUMENT_CHECKER(check_call_arguments_internal) {
 				Entity *e = sig_params[operand_index];
 				Type *t = e->type;
 				Operand o = operands[operand_index];
-				call->viral_state_flags |= o.expr->viral_state_flags;
+				if (o.expr != nullptr) {
+					call->viral_state_flags |= o.expr->viral_state_flags;
+				}
 
 				if (e->kind == Entity_TypeName) {
 					// GB_ASSERT(!variadic);
@@ -5983,6 +5991,15 @@ CALL_ARGUMENT_CHECKER(check_call_arguments_internal) {
 				}
 				score += s;
 
+				if (e->flags & EntityFlag_ConstInput) {
+					if (o.mode != Addressing_Constant) {
+						if (show_error) {
+							error(o.expr, "Expected a constant value for the argument '%.*s'", LIT(e->token.string));
+						}
+						err = CallArgumentError_NoneConstantParameter;
+					}
+				}
+
 				if (o.mode == Addressing_Type && is_type_typeid(e->type)) {
 					add_type_info_type(c, o.type);
 					add_type_and_value(c->info, o.expr, Addressing_Value, e->type, exact_value_typeid(o.type));
@@ -6238,6 +6255,15 @@ CALL_ARGUMENT_CHECKER(check_named_call_arguments) {
 					}
 					err = CallArgumentError_WrongTypes;
 				}
+
+				if (e->flags & EntityFlag_ConstInput) {
+					if (o->mode != Addressing_Constant) {
+						if (show_error) {
+							error(o->expr, "Expected a constant value for the argument '%.*s'", LIT(e->token.string));
+						}
+						err = CallArgumentError_NoneConstantParameter;
+					}
+				}
 			}
 			score += s;
 		}
@@ -6296,7 +6322,7 @@ Entity **populate_proc_parameter_list(CheckerContext *c, Type *proc_type, isize
 }
 
 
-bool evaluate_where_clauses(CheckerContext *ctx, Scope *scope, Array<Ast *> *clauses, bool print_err) {
+bool evaluate_where_clauses(CheckerContext *ctx, Ast *call_expr, Scope *scope, Array<Ast *> *clauses, bool print_err) {
 	if (clauses != nullptr) {
 		for_array(i, *clauses) {
 			Ast *clause = (*clauses)[i];
@@ -6304,9 +6330,11 @@ bool evaluate_where_clauses(CheckerContext *ctx, Scope *scope, Array<Ast *> *cla
 			check_expr(ctx, &o, clause);
 			if (o.mode != Addressing_Constant) {
 				if (print_err) error(clause, "'where' clauses expect a constant boolean evaluation");
+				if (print_err && call_expr) error(call_expr, "at caller location");
 				return false;
 			} else if (o.value.kind != ExactValue_Bool) {
 				if (print_err) error(clause, "'where' clauses expect a constant boolean evaluation");
+				if (print_err && call_expr) error(call_expr, "at caller location");
 				return false;
 			} else if (!o.value.value_bool) {
 				if (print_err) {
@@ -6348,6 +6376,7 @@ bool evaluate_where_clauses(CheckerContext *ctx, Scope *scope, Array<Ast *> *cla
 						}
 					}
 
+					if (call_expr) error(call_expr, "at caller location");
 				}
 				return false;
 			}
@@ -6613,7 +6642,7 @@ CallArgumentData check_call_arguments(CheckerContext *c, Operand *operand, Type
 					ctx.curr_proc_sig  = e->type;
 
 					GB_ASSERT(decl->proc_lit->kind == Ast_ProcLit);
-					if (!evaluate_where_clauses(&ctx, decl->scope, &decl->proc_lit->ProcLit.where_clauses, false)) {
+					if (!evaluate_where_clauses(&ctx, operand->expr, decl->scope, &decl->proc_lit->ProcLit.where_clauses, false)) {
 						continue;
 					}
 				}
@@ -8572,8 +8601,6 @@ ExprKind check_expr_base_internal(CheckerContext *c, Operand *o, Ast *node, Type
 			o->expr = node;
 			return kind;
 		}
-		Type *t = check_type(c, ta->type);
-
 		if (o->mode == Addressing_Constant) {
 			gbString expr_str = expr_to_string(o->expr);
 			error(o->expr, "A type assertion cannot be applied to a constant expression: '%s'", expr_str);
@@ -8594,54 +8621,80 @@ ExprKind check_expr_base_internal(CheckerContext *c, Operand *o, Ast *node, Type
 
 		bool src_is_ptr = is_type_pointer(o->type);
 		Type *src = type_deref(o->type);
-		Type *dst = t;
 		Type *bsrc = base_type(src);
-		Type *bdst = base_type(dst);
 
 
-		if (is_type_union(src)) {
-			bool ok = false;
-			for_array(i, bsrc->Union.variants) {
-				Type *vt = bsrc->Union.variants[i];
-				if (are_types_identical(vt, dst)) {
-					ok = true;
-					break;
-				}
+		if (ta->type != nullptr && ta->type->kind == Ast_UnaryExpr && ta->type->UnaryExpr.op.kind == Token_Question) {
+			if (!is_type_union(src)) {
+				gbString str = type_to_string(o->type);
+				error(o->expr, "Type assertions with .? can only operate on unions with 1 variant, got %s", str);
+				gb_string_free(str);
+				o->mode = Addressing_Invalid;
+				o->expr = node;
+				return kind;
 			}
-
-			if (!ok) {
-				gbString expr_str = expr_to_string(o->expr);
-				gbString dst_type_str = type_to_string(t);
-				defer (gb_string_free(expr_str));
-				defer (gb_string_free(dst_type_str));
-				if (bsrc->Union.variants.count == 0) {
-					error(o->expr, "Cannot type assert '%s' to '%s' as this is an empty union", expr_str, dst_type_str);
-				} else {
-					error(o->expr, "Cannot type assert '%s' to '%s' as it is not a variant of that union", expr_str, dst_type_str);
-				}
+			if (bsrc->Union.variants.count != 1) {
+				error(o->expr, "Type assertions with .? can only operate on unions with 1 variant, got %lld", cast(long long)bsrc->Union.variants.count);
 				o->mode = Addressing_Invalid;
 				o->expr = node;
 				return kind;
 			}
 
 			add_type_info_type(c, o->type);
-			add_type_info_type(c, t);
+			add_type_info_type(c, bsrc->Union.variants[0]);
 
-			o->type = t;
+			o->type = bsrc->Union.variants[0];
 			o->mode = Addressing_OptionalOk;
-		} else if (is_type_any(src)) {
-			o->type = t;
-			o->mode = Addressing_OptionalOk;
-
-			add_type_info_type(c, o->type);
-			add_type_info_type(c, t);
 		} else {
-			gbString str = type_to_string(o->type);
-			error(o->expr, "Type assertions can only operate on unions and 'any', got %s", str);
-			gb_string_free(str);
-			o->mode = Addressing_Invalid;
-			o->expr = node;
-			return kind;
+			Type *t = check_type(c, ta->type);
+			Type *dst = t;
+			Type *bdst = base_type(dst);
+
+
+			if (is_type_union(src)) {
+				bool ok = false;
+				for_array(i, bsrc->Union.variants) {
+					Type *vt = bsrc->Union.variants[i];
+					if (are_types_identical(vt, dst)) {
+						ok = true;
+						break;
+					}
+				}
+
+				if (!ok) {
+					gbString expr_str = expr_to_string(o->expr);
+					gbString dst_type_str = type_to_string(t);
+					defer (gb_string_free(expr_str));
+					defer (gb_string_free(dst_type_str));
+					if (bsrc->Union.variants.count == 0) {
+						error(o->expr, "Cannot type assert '%s' to '%s' as this is an empty union", expr_str, dst_type_str);
+					} else {
+						error(o->expr, "Cannot type assert '%s' to '%s' as it is not a variant of that union", expr_str, dst_type_str);
+					}
+					o->mode = Addressing_Invalid;
+					o->expr = node;
+					return kind;
+				}
+
+				add_type_info_type(c, o->type);
+				add_type_info_type(c, t);
+
+				o->type = t;
+				o->mode = Addressing_OptionalOk;
+			} else if (is_type_any(src)) {
+				o->type = t;
+				o->mode = Addressing_OptionalOk;
+
+				add_type_info_type(c, o->type);
+				add_type_info_type(c, t);
+			} else {
+				gbString str = type_to_string(o->type);
+				error(o->expr, "Type assertions can only operate on unions and 'any', got %s", str);
+				gb_string_free(str);
+				o->mode = Addressing_Invalid;
+				o->expr = node;
+				return kind;
+			}
 		}
 
 		add_package_dependency(c, "runtime", "type_assertion_check");

+ 13 - 1
src/check_stmt.cpp

@@ -314,7 +314,11 @@ Type *check_assignment_variable(CheckerContext *ctx, Operand *lhs, Operand *rhs)
 
 		gbString str = expr_to_string(lhs->expr);
 		if (e != nullptr && e->flags & EntityFlag_Param) {
-			error(lhs->expr, "Cannot assign to '%s' which is a procedure parameter", str);
+			if (e->flags & EntityFlag_Using) {
+				error(lhs->expr, "Cannot assign to '%s' which is from a 'using' procedure parameter", str);
+			} else {
+				error(lhs->expr, "Cannot assign to '%s' which is a procedure parameter", str);
+			}
 		} else {
 			error(lhs->expr, "Cannot assign to '%s'", str);
 		}
@@ -497,6 +501,8 @@ bool check_using_stmt_entity(CheckerContext *ctx, AstUsingStmt *us, Ast *expr, b
 				Entity *f = found->elements.entries[i].value;
 				if (f->kind == Entity_Variable) {
 					Entity *uvar = alloc_entity_using_variable(e, f->token, f->type, expr);
+					if (e->flags & EntityFlag_Value) uvar->flags |= EntityFlag_Value;
+					if (e->flags & EntityFlag_Param) uvar->flags |= EntityFlag_Param;
 					Entity *prev = scope_insert(ctx->scope, uvar);
 					if (prev != nullptr) {
 						gbString expr_str = expr_to_string(expr);
@@ -1102,6 +1108,12 @@ void check_type_switch_stmt(CheckerContext *ctx, Ast *node, u32 mod_flags) {
 			if (type_expr != nullptr) { // Otherwise it's a default expression
 				Operand y = {};
 				check_expr_or_type(ctx, &y, type_expr);
+				if (y.mode != Addressing_Type) {
+					gbString str = expr_to_string(type_expr);
+					error(type_expr, "Expected a type as a case, got %s", str);
+					gb_string_free(str);
+					continue;
+				}
 
 				if (switch_kind == TypeSwitch_Union) {
 					GB_ASSERT(is_type_union(bt));

+ 12 - 3
src/check_type.cpp

@@ -527,7 +527,7 @@ void check_struct_type(CheckerContext *ctx, Type *struct_type, Ast *node, Array<
 		if (st->where_clauses.count > 0 && st->polymorphic_params == nullptr) {
 			error(st->where_clauses[0], "'where' clauses can only be used on structures with polymorphic parameters");
 		} else {
-			bool where_clause_ok = evaluate_where_clauses(ctx, ctx->scope, &st->where_clauses, true);
+			bool where_clause_ok = evaluate_where_clauses(ctx, node, ctx->scope, &st->where_clauses, true);
 		}
 		check_struct_fields(ctx, node, &struct_type->Struct.fields, &struct_type->Struct.tags, st->fields, min_field_count, struct_type, context);
 	}
@@ -714,7 +714,7 @@ void check_union_type(CheckerContext *ctx, Type *union_type, Ast *node, Array<Op
 	if (ut->where_clauses.count > 0 && ut->polymorphic_params == nullptr) {
 		error(ut->where_clauses[0], "'where' clauses can only be used on unions with polymorphic parameters");
 	} else {
-		bool where_clause_ok = evaluate_where_clauses(ctx, ctx->scope, &ut->where_clauses, true);
+		bool where_clause_ok = evaluate_where_clauses(ctx, node, ctx->scope, &ut->where_clauses, true);
 	}
 
 
@@ -748,11 +748,17 @@ void check_union_type(CheckerContext *ctx, Type *union_type, Ast *node, Array<Op
 
 	union_type->Union.variants = variants;
 	union_type->Union.no_nil = ut->no_nil;
+	union_type->Union.maybe = ut->maybe;
 	if (union_type->Union.no_nil) {
 		if (variants.count < 2) {
 			error(ut->align, "A union with #no_nil must have at least 2 variants");
 		}
 	}
+	if (union_type->Union.maybe) {
+		if (variants.count != 1) {
+			error(ut->align, "A union with #maybe must have at 1 variant, got %lld", cast(long long)variants.count);
+		}
+	}
 
 	if (ut->align != nullptr) {
 		i64 custom_align = 1;
@@ -1716,8 +1722,11 @@ Type *check_get_params(CheckerContext *ctx, Scope *scope, Ast *_params, bool *is
 			if (p->flags&FieldFlag_auto_cast) {
 				param->flags |= EntityFlag_AutoCast;
 			}
-			param->state = EntityState_Resolved; // NOTE(bill): This should have be resolved whilst determining it
+			if (p->flags&FieldFlag_const) {
+				param->flags |= EntityFlag_ConstInput;
+			}
 
+			param->state = EntityState_Resolved; // NOTE(bill): This should have be resolved whilst determining it
 			add_entity(ctx->checker, scope, name, param);
 			if (is_using) {
 				add_entity_use(ctx, name, param);

+ 4 - 0
src/checker_builtin_procs.hpp

@@ -36,6 +36,8 @@ enum BuiltinProcId {
 	BuiltinProc_simd_vector,
 	BuiltinProc_soa_struct,
 
+	BuiltinProc_cpu_relax,
+
 	BuiltinProc_atomic_fence,
 	BuiltinProc_atomic_fence_acq,
 	BuiltinProc_atomic_fence_rel,
@@ -214,6 +216,8 @@ gb_global BuiltinProc builtin_procs[BuiltinProc_COUNT] = {
 	{STR_LIT("simd_vector"), 2, false, Expr_Expr, BuiltinProcPkg_intrinsics}, // Type
 	{STR_LIT("soa_struct"),  2, false, Expr_Expr, BuiltinProcPkg_intrinsics}, // Type
 
+	{STR_LIT("cpu_relax"),  0, false, Expr_Stmt, BuiltinProcPkg_intrinsics},
+
 	{STR_LIT("atomic_fence"),        0, false, Expr_Stmt, BuiltinProcPkg_intrinsics},
 	{STR_LIT("atomic_fence_acq"),    0, false, Expr_Stmt, BuiltinProcPkg_intrinsics},
 	{STR_LIT("atomic_fence_rel"),    0, false, Expr_Stmt, BuiltinProcPkg_intrinsics},

+ 1 - 0
src/entity.cpp

@@ -46,6 +46,7 @@ enum EntityFlag {
 	EntityFlag_BitFieldValue = 1<<12,
 	EntityFlag_PolyConst     = 1<<13,
 	EntityFlag_NotExported   = 1<<14,
+	EntityFlag_ConstInput    = 1<<15,
 
 	EntityFlag_Static        = 1<<16,
 

+ 66 - 13
src/ir.cpp

@@ -197,6 +197,7 @@ gbAllocator ir_allocator(void) {
 	IR_INSTR_KIND(ZeroInit, struct { irValue *address; })             \
 	IR_INSTR_KIND(Store,    struct { irValue *address, *value; bool is_volatile; }) \
 	IR_INSTR_KIND(Load,     struct { Type *type; irValue *address; i64 custom_align; }) \
+	IR_INSTR_KIND(InlineCode, struct { BuiltinProcId id; Array<irValue *> operands; }) \
 	IR_INSTR_KIND(AtomicFence, struct { BuiltinProcId id; })          \
 	IR_INSTR_KIND(AtomicStore, struct {                               \
 		irValue *address, *value;                                     \
@@ -1063,6 +1064,14 @@ irValue *ir_instr_load(irProcedure *p, irValue *address) {
 	return v;
 }
 
+irValue *ir_instr_inline_code(irProcedure *p, BuiltinProcId id, Array<irValue *> operands) {
+	irValue *v = ir_alloc_instr(p, irInstr_InlineCode);
+	irInstr *i = &v->Instr;
+	i->InlineCode.id = id;
+	i->InlineCode.operands = operands;
+	return v;
+}
+
 irValue *ir_instr_atomic_fence(irProcedure *p, BuiltinProcId id) {
 	irValue *v = ir_alloc_instr(p, irInstr_AtomicFence);
 	irInstr *i = &v->Instr;
@@ -1222,7 +1231,11 @@ irValue *ir_instr_union_tag_ptr(irProcedure *p, irValue *address) {
 
 	// i->UnionTagPtr.type = alloc_type_pointer(t_type_info_ptr);
 	Type *u = type_deref(ir_type(address));
+	if (is_type_union_maybe_pointer(u)) {
+		GB_PANIC("union #maybe UnionTagPtr");
+	}
 	i->UnionTagPtr.type = alloc_type_pointer(union_tag_type(u));
+
 	return v;
 }
 
@@ -1234,6 +1247,9 @@ irValue *ir_instr_union_tag_value(irProcedure *p, irValue *address) {
 	if (address) address->uses += 1;
 
 	Type *u = type_deref(ir_type(address));
+	if (is_type_union_maybe_pointer(u)) {
+		GB_PANIC("union #maybe UnionTagValue");
+	}
 	i->UnionTagPtr.type = union_tag_type(u);
 	return v;
 }
@@ -4351,7 +4367,9 @@ irValue *ir_emit_union_tag_value(irProcedure *proc, irValue *u) {
 
 irValue *ir_emit_comp_against_nil(irProcedure *proc, TokenKind op_kind, irValue *x) {
 	Type *t = ir_type(x);
-	if (is_type_cstring(t)) {
+	if (is_type_pointer(t)) {
+		return ir_emit_comp(proc, op_kind, x, v_raw_nil);
+	} else if (is_type_cstring(t)) {
 		irValue *ptr = ir_emit_conv(proc, x, t_u8_ptr);
 		return ir_emit_comp(proc, op_kind, ptr, v_raw_nil);
 	} else if (is_type_any(t)) {
@@ -4396,6 +4414,12 @@ irValue *ir_emit_comp_against_nil(irProcedure *proc, TokenKind op_kind, irValue
 	} else if (is_type_union(t)) {
 		if (type_size_of(t) == 0) {
 			return ir_emit_comp(proc, op_kind, v_zero, v_zero);
+		} else if (is_type_union_maybe_pointer(t)) {
+			Type *bt = base_type(t);
+			irValue *ptr = ir_address_from_load_or_generate_local(proc, x);
+			ptr = ir_emit_bitcast(proc, ptr, alloc_type_pointer(bt->Union.variants[0]));
+			irValue *data = ir_emit_load(proc, ptr);
+			return ir_emit_comp_against_nil(proc, op_kind, data);
 		} else {
 			irValue *tag = ir_emit_union_tag_value(proc, x);
 			return ir_emit_comp(proc, op_kind, tag, v_zero);
@@ -5232,8 +5256,12 @@ void ir_emit_store_union_variant(irProcedure *proc, irValue *parent, irValue *va
 
 	Type *t = type_deref(ir_type(parent));
 
-	irValue *tag_ptr = ir_emit_union_tag_ptr(proc, parent);
-	ir_emit_store(proc, tag_ptr, ir_const_union_tag(t, variant_type));
+	if (is_type_union_maybe_pointer(t)) {
+		// No tag needed!
+	} else {
+		irValue *tag_ptr = ir_emit_union_tag_ptr(proc, parent);
+		ir_emit_store(proc, tag_ptr, ir_const_union_tag(t, variant_type));
+	}
 }
 
 irValue *ir_emit_conv(irProcedure *proc, irValue *value, Type *t) {
@@ -5701,7 +5729,7 @@ irValue *ir_emit_transmute(irProcedure *proc, irValue *value, Type *t) {
 	}
 
 	// TODO(bill): Actually figure out what the conversion needs to be correctly 'cause LLVM
-	return ir_emit_bitcast(proc, value, dst);
+	return ir_emit_bitcast(proc, value, t);
 }
 
 
@@ -5728,22 +5756,41 @@ irValue *ir_emit_union_cast(irProcedure *proc, irValue *value, Type *type, Token
 	GB_ASSERT_MSG(is_type_union(src), "%s", type_to_string(src_type));
 	Type *dst = tuple->Tuple.variables[0]->type;
 
-
 	irValue *value_  = ir_address_from_load_or_generate_local(proc, value);
-	irValue *tag     = ir_emit_load(proc, ir_emit_union_tag_ptr(proc, value_));
-	irValue *dst_tag = ir_const_union_tag(src, dst);
 
+	irValue *tag = nullptr;
+	irValue *dst_tag = nullptr;
+	irValue *cond = nullptr;
+	irValue *data = nullptr;
+
+	irValue *gep0 = ir_emit_struct_ep(proc, v, 0);
+	irValue *gep1 = ir_emit_struct_ep(proc, v, 1);
+
+	if (is_type_union_maybe_pointer(src)) {
+		data = ir_emit_load(proc, ir_emit_conv(proc, value_, ir_type(gep0)));
+	} else {
+		tag     = ir_emit_load(proc, ir_emit_union_tag_ptr(proc, value_));
+		dst_tag = ir_const_union_tag(src, dst);
+	}
 
 	irBlock *ok_block = ir_new_block(proc, nullptr, "union_cast.ok");
 	irBlock *end_block = ir_new_block(proc, nullptr, "union_cast.end");
-	irValue *cond = ir_emit_comp(proc, Token_CmpEq, tag, dst_tag);
+
+	if (data != nullptr) {
+		GB_ASSERT(is_type_union_maybe_pointer(src));
+		cond = ir_emit_comp_against_nil(proc, Token_NotEq, data);
+	} else {
+		cond = ir_emit_comp(proc, Token_CmpEq, tag, dst_tag);
+	}
+
 	ir_emit_if(proc, cond, ok_block, end_block);
 	ir_start_block(proc, ok_block);
 
-	irValue *gep0 = ir_emit_struct_ep(proc, v, 0);
-	irValue *gep1 = ir_emit_struct_ep(proc, v, 1);
 
-	irValue *data = ir_emit_load(proc, ir_emit_conv(proc, value_, ir_type(gep0)));
+
+	if (data == nullptr) {
+		data = ir_emit_load(proc, ir_emit_conv(proc, value_, ir_type(gep0)));
+	}
 	ir_emit_store(proc, gep0, data);
 	ir_emit_store(proc, gep1, v_true);
 
@@ -6876,6 +6923,9 @@ irValue *ir_build_builtin_proc(irProcedure *proc, Ast *expr, TypeAndValue tv, Bu
 
 
 	// "Intrinsics"
+	case BuiltinProc_cpu_relax:
+		return ir_emit(proc, ir_instr_inline_code(proc, id, {}));
+
 	case BuiltinProc_atomic_fence:
 	case BuiltinProc_atomic_fence_acq:
 	case BuiltinProc_atomic_fence_rel:
@@ -7344,10 +7394,11 @@ irValue *ir_build_expr_internal(irProcedure *proc, Ast *expr) {
 					GB_ASSERT(are_types_identical(ir_type(left), key_type));
 
 					Type *it = bit_set_to_int(rt);
+					left = ir_emit_conv(proc, left, it);
 
 					irValue *lower = ir_value_constant(it, exact_value_i64(rt->BitSet.lower));
-					irValue *key = ir_emit_arith(proc, Token_Sub, left, lower, ir_type(left));
-					irValue *bit = ir_emit_arith(proc, Token_Shl, v_one, key, ir_type(left));
+					irValue *key = ir_emit_arith(proc, Token_Sub, left, lower, it);
+					irValue *bit = ir_emit_arith(proc, Token_Shl, v_one, key, it);
 					bit = ir_emit_conv(proc, bit, it);
 
 					irValue *old_value = ir_emit_bitcast(proc, right, it);
@@ -11465,6 +11516,7 @@ void ir_setup_type_info_data(irProcedure *proc) { // NOTE(bill): Setup type_info
 				irValue *tag_type_ptr     = ir_emit_struct_ep(proc, tag, 2);
 				irValue *custom_align_ptr = ir_emit_struct_ep(proc, tag, 3);
 				irValue *no_nil_ptr       = ir_emit_struct_ep(proc, tag, 4);
+				irValue *maybe_ptr        = ir_emit_struct_ep(proc, tag, 5);
 
 				isize variant_count = gb_max(0, t->Union.variants.count);
 				irValue *memory_types = ir_type_info_member_types_offset(proc, variant_count);
@@ -11494,6 +11546,7 @@ void ir_setup_type_info_data(irProcedure *proc) { // NOTE(bill): Setup type_info
 				ir_emit_store(proc, custom_align_ptr, is_custom_align);
 
 				ir_emit_store(proc, no_nil_ptr, ir_const_bool(t->Union.no_nil));
+				ir_emit_store(proc, maybe_ptr, ir_const_bool(t->Union.maybe));
 			}
 
 			break;

+ 40 - 4
src/ir_print.cpp

@@ -508,13 +508,25 @@ void ir_print_type(irFileBuffer *f, irModule *m, Type *t, bool in_struct) {
 			// NOTE(bill): The zero size array is used to fix the alignment used in a structure as
 			// LLVM takes the first element's alignment as the entire alignment (like C)
 			i64 align = type_align_of(t);
+
+			if (is_type_union_maybe_pointer_original_alignment(t)) {
+				ir_write_byte(f, '{');
+				ir_print_type(f, m, t->Union.variants[0]);
+				ir_write_byte(f, '}');
+				return;
+			}
+
 			i64 block_size =  t->Union.variant_block_size;
 
 			ir_write_byte(f, '{');
 			ir_print_alignment_prefix_hack(f, align);
-			ir_fprintf(f, ", [%lld x i8], ", block_size);
-			// ir_print_type(f, m, t_type_info_ptr);
-			ir_print_type(f, m, union_tag_type(t));
+			if (is_type_union_maybe_pointer(t)) {
+				ir_fprintf(f, ", ");
+				ir_print_type(f, m, t->Union.variants[0]);
+			} else {
+				ir_fprintf(f, ", [%lld x i8], ", block_size);
+				ir_print_type(f, m, union_tag_type(t));
+			}
 			ir_write_byte(f, '}');
 		}
 		return;
@@ -1471,6 +1483,18 @@ void ir_print_instr(irFileBuffer *f, irModule *m, irValue *value) {
 		break;
 	}
 
+	case irInstr_InlineCode:
+		{
+			switch (instr->InlineCode.id) {
+			case BuiltinProc_cpu_relax:
+				ir_write_str_lit(f, "call void asm sideeffect \"pause\", \"\"()");
+				break;
+			default: GB_PANIC("Unknown inline code %d", instr->InlineCode.id); break;
+			}
+		}
+		break;
+
+
 	case irInstr_AtomicFence:
 		ir_write_str_lit(f, "fence ");
 		switch (instr->AtomicFence.id) {
@@ -1850,6 +1874,12 @@ void ir_print_instr(irFileBuffer *f, irModule *m, irValue *value) {
 
 	case irInstr_UnionTagPtr: {
 		Type *et = ir_type(instr->UnionTagPtr.address);
+
+		Type *ut = type_deref(et);
+		if (is_type_union_maybe_pointer(ut)) {
+			GB_PANIC("union #maybe UnionTagPtr");
+		}
+
 		ir_fprintf(f, "%%%d = getelementptr inbounds ", value->index);
 		Type *t = base_type(type_deref(et));
 		GB_ASSERT(is_type_union(t));
@@ -1869,10 +1899,16 @@ void ir_print_instr(irFileBuffer *f, irModule *m, irValue *value) {
 
 	case irInstr_UnionTagValue: {
 		Type *et = ir_type(instr->UnionTagValue.address);
-		ir_fprintf(f, "%%%d = extractvalue ", value->index);
 		Type *t = base_type(et);
+
+		if (is_type_union_maybe_pointer(t)) {
+			GB_PANIC("union #maybe UnionTagValue");
+		}
+
+		ir_fprintf(f, "%%%d = extractvalue ", value->index);
 		GB_ASSERT(is_type_union(t));
 
+
 		ir_print_type(f, m, et);
 		ir_write_byte(f, ' ');
 		ir_print_value(f, m, instr->UnionTagValue.address, et);

+ 35 - 11
src/main.cpp

@@ -243,6 +243,7 @@ enum BuildFlagKind {
 #if defined(GB_SYSTEM_WINDOWS)
 	BuildFlag_ResourceFile,
 	BuildFlag_WindowsPdbName,
+	BuildFlag_Subsystem,
 #endif
 
 	BuildFlag_COUNT,
@@ -331,8 +332,9 @@ bool parse_build_flags(Array<String> args) {
 	add_flag(&build_flags, BuildFlag_GoToDefinitions, str_lit("go-to-definitions"), BuildFlagParam_None);
 
 #if defined(GB_SYSTEM_WINDOWS)
-	add_flag(&build_flags, BuildFlag_ResourceFile,   str_lit("resource"), BuildFlagParam_String);
-	add_flag(&build_flags, BuildFlag_WindowsPdbName, str_lit("pdb-name"), BuildFlagParam_String);
+	add_flag(&build_flags, BuildFlag_ResourceFile,   str_lit("resource"),  BuildFlagParam_String);
+	add_flag(&build_flags, BuildFlag_WindowsPdbName, str_lit("pdb-name"),  BuildFlagParam_String);
+	add_flag(&build_flags, BuildFlag_Subsystem,      str_lit("subsystem"), BuildFlagParam_String);
 #endif
 
 	GB_ASSERT(args.count >= 3);
@@ -764,7 +766,19 @@ bool parse_build_flags(Array<String> args) {
 								bad_flags = true;
 							}
 							break;
+						}
 
+						case BuildFlag_Subsystem: {
+							GB_ASSERT(value.kind == ExactValue_String);
+							String subsystem = value.value_string;
+							if (str_eq_ignore_case(subsystem, str_lit("console"))) {
+								build_context.use_subsystem_windows = false;
+							} else  if (str_eq_ignore_case(subsystem, str_lit("windows"))) {
+								build_context.use_subsystem_windows = true;
+							} else {
+								gb_printf_err("Invalid -subsystem string, got %.*s, expected either 'console' or 'windows'\n", LIT(subsystem));
+								bad_flags = true;
+							}
 							break;
 						}
 					#endif
@@ -1180,6 +1194,10 @@ int main(int arg_count, char const **arg_ptr) {
 		return 1;
 	}
 
+	if (init_filename == "-help") {
+		build_context.show_help = true;
+	}
+
 	build_context.command = command;
 
 	if (!parse_build_flags(args)) {
@@ -1372,8 +1390,8 @@ int main(int arg_count, char const **arg_ptr) {
 		}
 
 
+		char const *subsystem_str = build_context.use_subsystem_windows ? "WINDOWS" : "CONSOLE";
 		if (!build_context.use_lld) { // msvc
-
 			if (build_context.has_resource) {
 				exit_code = system_exec_command_line_app("msvc-link",
 					"\"%.*src.exe\" /nologo /fo \"%.*s.res\" \"%.*s.rc\"",
@@ -1388,36 +1406,42 @@ int main(int arg_count, char const **arg_ptr) {
 
 				exit_code = system_exec_command_line_app("msvc-link",
 					"\"%.*slink.exe\" \"%.*s.obj\" \"%.*s.res\" -OUT:\"%.*s.%s\" %s "
-					"/nologo /incremental:no /opt:ref /subsystem:CONSOLE "
+					"/nologo /incremental:no /opt:ref /subsystem:%s "
 					" %.*s "
 					" %s "
 					"",
 					LIT(find_result.vs_exe_path), LIT(output_base), LIT(output_base), LIT(output_base), output_ext,
-					link_settings, LIT(build_context.link_flags),
+					link_settings,
+					subsystem_str,
+					LIT(build_context.link_flags),
 					lib_str
 				);
 			} else {
 				exit_code = system_exec_command_line_app("msvc-link",
 					"\"%.*slink.exe\" \"%.*s.obj\" -OUT:\"%.*s.%s\" %s "
-					"/nologo /incremental:no /opt:ref /subsystem:CONSOLE "
+					"/nologo /incremental:no /opt:ref /subsystem:%s "
 					" %.*s "
 					" %s "
 					"",
 					LIT(find_result.vs_exe_path), LIT(output_base), LIT(output_base), output_ext,
-					link_settings, LIT(build_context.link_flags),
+					link_settings,
+					subsystem_str,
+					LIT(build_context.link_flags),
 					lib_str
 				);
 			}
 		} else { // lld
 			exit_code = system_exec_command_line_app("msvc-link",
 				"\"%.*s\\bin\\lld-link\" \"%.*s.obj\" -OUT:\"%.*s.%s\" %s "
-				"/nologo /incremental:no /opt:ref /subsystem:CONSOLE "
+				"/nologo /incremental:no /opt:ref /subsystem:%s "
 				" %.*s "
 				" %s "
 				"",
 				LIT(build_context.ODIN_ROOT),
 				LIT(output_base), LIT(output_base), output_ext,
-				link_settings, LIT(build_context.link_flags),
+				link_settings,
+				subsystem_str,
+				LIT(build_context.link_flags),
 				lib_str
 			);
 		}
@@ -1500,11 +1524,11 @@ int main(int arg_count, char const **arg_ptr) {
 			// Shared libraries are .dylib on MacOS and .so on Linux.
 			#if defined(GB_SYSTEM_OSX)
 				output_ext = STR_LIT(".dylib");
+				link_settings = "-dylib -dynamic";
 			#else
 				output_ext = STR_LIT(".so");
+				link_settings = "-shared";
 			#endif
-
-			link_settings = "-shared";
 		} else {
 			// TODO: Do I need anything here?
 			link_settings = "";

+ 28 - 3
src/parser.cpp

@@ -929,7 +929,7 @@ Ast *ast_struct_type(AstFile *f, Token token, Array<Ast *> fields, isize field_c
 }
 
 
-Ast *ast_union_type(AstFile *f, Token token, Array<Ast *> variants, Ast *polymorphic_params, Ast *align, bool no_nil,
+Ast *ast_union_type(AstFile *f, Token token, Array<Ast *> variants, Ast *polymorphic_params, Ast *align, bool no_nil, bool maybe,
                     Token where_token, Array<Ast *> const &where_clauses) {
 	Ast *result = alloc_ast_node(f, Ast_UnionType);
 	result->UnionType.token              = token;
@@ -937,6 +937,7 @@ Ast *ast_union_type(AstFile *f, Token token, Array<Ast *> variants, Ast *polymor
 	result->UnionType.polymorphic_params = polymorphic_params;
 	result->UnionType.align              = align;
 	result->UnionType.no_nil             = no_nil;
+	result->UnionType.maybe             = maybe;
 	result->UnionType.where_token        = where_token;
 	result->UnionType.where_clauses      = where_clauses;
 	return result;
@@ -2091,6 +2092,7 @@ Ast *parse_operand(AstFile *f, bool lhs) {
 		Ast *polymorphic_params = nullptr;
 		Ast *align = nullptr;
 		bool no_nil = false;
+		bool maybe = false;
 
 		CommentGroup *docs = f->lead_comment;
 		Token start_token = f->curr_token;
@@ -2118,10 +2120,19 @@ Ast *parse_operand(AstFile *f, bool lhs) {
 					syntax_error(tag, "Duplicate union tag '#%.*s'", LIT(tag.string));
 				}
 				no_nil = true;
-			} else {
+			} else if (tag.string == "maybe") {
+				if (maybe) {
+					syntax_error(tag, "Duplicate union tag '#%.*s'", LIT(tag.string));
+				}
+				maybe = true;
+			}else {
 				syntax_error(tag, "Invalid union tag '#%.*s'", LIT(tag.string));
 			}
 		}
+		if (no_nil && maybe) {
+			syntax_error(f->curr_token, "#maybe and #no_nil cannot be applied together");
+		}
+
 
 		Token where_token = {};
 		Array<Ast *> where_clauses = {};
@@ -2150,7 +2161,7 @@ Ast *parse_operand(AstFile *f, bool lhs) {
 
 		Token close = expect_token(f, Token_CloseBrace);
 
-		return ast_union_type(f, token, variants, polymorphic_params, align, no_nil, where_token, where_clauses);
+		return ast_union_type(f, token, variants, polymorphic_params, align, no_nil, maybe, where_token, where_clauses);
 	} break;
 
 	case Token_enum: {
@@ -2350,6 +2361,12 @@ Ast *parse_atom_expr(AstFile *f, Ast *operand, bool lhs) {
 				operand = ast_type_assertion(f, operand, token, type);
 			} break;
 
+			case Token_Question: {
+				Token question = expect_token(f, Token_Question);
+				Ast *type = ast_unary_expr(f, question, nullptr);
+				operand = ast_type_assertion(f, operand, token, type);
+			} break;
+
 			default:
 				syntax_error(f->curr_token, "Expected a selector");
 				advance_token(f);
@@ -2981,6 +2998,7 @@ enum FieldPrefixKind {
 	FieldPrefix_Invalid = 0,
 
 	FieldPrefix_using,
+	FieldPrefix_const,
 	FieldPrefix_no_alias,
 	FieldPrefix_c_var_arg,
 	FieldPrefix_auto_cast,
@@ -3007,6 +3025,9 @@ FieldPrefixKind is_token_field_prefix(AstFile *f) {
 				return FieldPrefix_c_var_arg;
 			}
 			break;
+
+		case Token_const:
+			return FieldPrefix_const;
 		}
 		return FieldPrefix_Unknown;
 	}
@@ -3019,6 +3040,7 @@ u32 parse_field_prefixes(AstFile *f) {
 	i32 no_alias_count  = 0;
 	i32 c_vararg_count  = 0;
 	i32 auto_cast_count = 0;
+	i32 const_count     = 0;
 
 	for (;;) {
 		FieldPrefixKind kind = is_token_field_prefix(f);
@@ -3036,12 +3058,14 @@ u32 parse_field_prefixes(AstFile *f) {
 		case FieldPrefix_no_alias:  no_alias_count  += 1; advance_token(f); break;
 		case FieldPrefix_c_var_arg: c_vararg_count  += 1; advance_token(f); break;
 		case FieldPrefix_auto_cast: auto_cast_count += 1; advance_token(f); break;
+		case FieldPrefix_const:     const_count     += 1; advance_token(f); break;
 		}
 	}
 	if (using_count     > 1) syntax_error(f->curr_token, "Multiple 'using' in this field list");
 	if (no_alias_count  > 1) syntax_error(f->curr_token, "Multiple '#no_alias' in this field list");
 	if (c_vararg_count  > 1) syntax_error(f->curr_token, "Multiple '#c_vararg' in this field list");
 	if (auto_cast_count > 1) syntax_error(f->curr_token, "Multiple 'auto_cast' in this field list");
+	if (const_count     > 1) syntax_error(f->curr_token, "Multiple '#const' in this field list");
 
 
 	u32 field_flags = 0;
@@ -3049,6 +3073,7 @@ u32 parse_field_prefixes(AstFile *f) {
 	if (no_alias_count  > 0) field_flags |= FieldFlag_no_alias;
 	if (c_vararg_count  > 0) field_flags |= FieldFlag_c_vararg;
 	if (auto_cast_count > 0) field_flags |= FieldFlag_auto_cast;
+	if (const_count     > 0) field_flags |= FieldFlag_const;
 	return field_flags;
 }
 

+ 3 - 1
src/parser.hpp

@@ -203,12 +203,13 @@ enum FieldFlag {
 	FieldFlag_no_alias  = 1<<2,
 	FieldFlag_c_vararg  = 1<<3,
 	FieldFlag_auto_cast = 1<<4,
+	FieldFlag_const     = 1<<5,
 
 	FieldFlag_Tags = 1<<10,
 
 	FieldFlag_Results   = 1<<16,
 
-	FieldFlag_Signature = FieldFlag_ellipsis|FieldFlag_using|FieldFlag_no_alias|FieldFlag_c_vararg|FieldFlag_auto_cast,
+	FieldFlag_Signature = FieldFlag_ellipsis|FieldFlag_using|FieldFlag_no_alias|FieldFlag_c_vararg|FieldFlag_auto_cast|FieldFlag_const,
 	FieldFlag_Struct    = FieldFlag_using|FieldFlag_Tags,
 };
 
@@ -515,6 +516,7 @@ AST_KIND(_TypeBegin, "", bool) \
 		Array<Ast *> variants;      \
 		Ast *polymorphic_params;    \
 		Ast *        align;         \
+		bool         maybe;         \
 		bool         no_nil;        \
 		Token where_token;          \
 		Array<Ast *> where_clauses; \

+ 44 - 7
src/types.cpp

@@ -152,6 +152,7 @@ struct TypeUnion {
 	Type *        polymorphic_params; // Type_Tuple
 	Type *        polymorphic_parent;
 	bool          no_nil;
+	bool          maybe;
 	bool          is_polymorphic;
 	bool          is_poly_specialized;
 };
@@ -1219,6 +1220,32 @@ bool is_type_map(Type *t) {
 	return t->kind == Type_Map;
 }
 
+bool is_type_union_maybe_pointer(Type *t) {
+	t = base_type(t);
+	if (t->kind == Type_Union && t->Union.maybe) {
+		if (t->Union.variants.count == 1) {
+			return is_type_pointer(t->Union.variants[0]);
+		}
+	}
+	return false;
+}
+
+
+bool is_type_union_maybe_pointer_original_alignment(Type *t) {
+	t = base_type(t);
+	if (t->kind == Type_Union && t->Union.maybe) {
+		if (t->Union.variants.count == 1) {
+			Type *v = t->Union.variants[0];
+			if (is_type_pointer(v)) {
+				return type_align_of(v) == type_align_of(t);
+			}
+		}
+	}
+	return false;
+}
+
+
+
 
 bool is_type_integer_endian_big(Type *t) {
 	t = core_type(t);
@@ -2024,6 +2051,7 @@ i64 union_tag_size(Type *u) {
 Type *union_tag_type(Type *u) {
 	i64 s = union_tag_size(u);
 	switch (s) {
+	case  0: return  t_u8;
 	case  1: return  t_u8;
 	case  2: return  t_u16;
 	case  4: return  t_u32;
@@ -2934,14 +2962,23 @@ i64 type_size_of_internal(Type *t, TypePath *path) {
 			}
 		}
 
-		// NOTE(bill): Align to tag
-		i64 tag_size = union_tag_size(t);
-		i64 size = align_formula(max, tag_size);
-		// NOTE(bill): Calculate the padding between the common fields and the tag
-		t->Union.tag_size = tag_size;
-		t->Union.variant_block_size = size - field_size;
+		i64 size = 0;
+
+		if (is_type_union_maybe_pointer(t)) {
+			size = max;
+			t->Union.tag_size = 0;
+			t->Union.variant_block_size = size;
+		} else {
+			// NOTE(bill): Align to tag
+			i64 tag_size = union_tag_size(t);
+			size = align_formula(max, tag_size);
+			// NOTE(bill): Calculate the padding between the common fields and the tag
+			t->Union.tag_size = tag_size;
+			t->Union.variant_block_size = size - field_size;
 
-		return align_formula(size + tag_size, align);
+			size += tag_size;
+		}
+		return align_formula(size, align);
 	} break;