Sfoglia il codice sorgente

Merge branch 'master' into windows-llvm-13.0.0

gingerBill 2 anni fa
parent
commit
b4666c7e23

+ 1 - 0
core/net/common.odin

@@ -64,6 +64,7 @@ Network_Error :: union #shared_nil {
 	UDP_Recv_Error,
 	UDP_Recv_Error,
 	Shutdown_Error,
 	Shutdown_Error,
 	Socket_Option_Error,
 	Socket_Option_Error,
+	Set_Blocking_Error,
 	Parse_Endpoint_Error,
 	Parse_Endpoint_Error,
 	Resolve_Error,
 	Resolve_Error,
 	DNS_Error,
 	DNS_Error,

+ 6 - 0
core/net/errors_darwin.odin

@@ -197,4 +197,10 @@ Socket_Option_Error :: enum c.int {
 	Invalid_Option_For_Socket  = c.int(os.ENOPROTOOPT),
 	Invalid_Option_For_Socket  = c.int(os.ENOPROTOOPT),
 	Reset_When_Keepalive_Set   = c.int(os.ENOTCONN),
 	Reset_When_Keepalive_Set   = c.int(os.ENOTCONN),
 	Not_Socket                 = c.int(os.ENOTSOCK),
 	Not_Socket                 = c.int(os.ENOTSOCK),
+}
+
+Set_Blocking_Error :: enum c.int {
+	None = 0,
+
+	// TODO: Add errors for `set_blocking`
 }
 }

+ 8 - 0
core/net/errors_linux.odin

@@ -190,4 +190,12 @@ Socket_Option_Error :: enum c.int {
 	Invalid_Option_For_Socket  = c.int(os.ENOPROTOOPT),
 	Invalid_Option_For_Socket  = c.int(os.ENOPROTOOPT),
 	Reset_When_Keepalive_Set   = c.int(os.ENOTCONN),
 	Reset_When_Keepalive_Set   = c.int(os.ENOTCONN),
 	Not_Socket                 = c.int(os.ENOTSOCK),
 	Not_Socket                 = c.int(os.ENOTSOCK),
+}
+
+Set_Blocking_Error :: enum c.int {
+	None = 0,
+
+	// TODO: add errors occuring on followig calls:
+	// flags, _ := os.fcntl(sd, os.F_GETFL, 0)
+	// os.fcntl(sd, os.F_SETFL, flags | int(os.O_NONBLOCK))
 }
 }

+ 12 - 0
core/net/errors_windows.odin

@@ -259,3 +259,15 @@ Socket_Option_Error :: enum c.int {
 	Reset_When_Keepalive_Set           = win.WSAENOTCONN,
 	Reset_When_Keepalive_Set           = win.WSAENOTCONN,
 	Not_Socket                         = win.WSAENOTSOCK,
 	Not_Socket                         = win.WSAENOTSOCK,
 }
 }
+
+Set_Blocking_Error :: enum c.int {
+	None = 0,
+
+	Network_Subsystem_Failure          = win.WSAENETDOWN,
+	Blocking_Call_In_Progress          = win.WSAEINPROGRESS,
+	Not_Socket                         = win.WSAENOTSOCK,
+
+	// TODO: are those errors possible?
+	Network_Subsystem_Not_Initialized  = win.WSAENOTINITIALISED,
+	Invalid_Argument_Pointer           = win.WSAEFAULT,
+}

+ 7 - 0
core/net/socket.odin

@@ -118,6 +118,9 @@ make_unbound_udp_socket :: proc(family: Address_Family) -> (socket: UDP_Socket,
 	The `bound_address` is the address of the network interface that you want to use, or a loopback address if you don't care which to use.
 	The `bound_address` is the address of the network interface that you want to use, or a loopback address if you don't care which to use.
 */
 */
 make_bound_udp_socket :: proc(bound_address: Address, port: int) -> (socket: UDP_Socket, err: Network_Error) {
 make_bound_udp_socket :: proc(bound_address: Address, port: int) -> (socket: UDP_Socket, err: Network_Error) {
+	if bound_address == nil {
+		return {}, .Bad_Address
+	}
 	socket = make_unbound_udp_socket(family_from_address(bound_address)) or_return
 	socket = make_unbound_udp_socket(family_from_address(bound_address)) or_return
 	bind(socket, {bound_address, port}) or_return
 	bind(socket, {bound_address, port}) or_return
 	return
 	return
@@ -173,4 +176,8 @@ shutdown :: proc(socket: Any_Socket, manner: Shutdown_Manner) -> (err: Network_E
 
 
 set_option :: proc(socket: Any_Socket, option: Socket_Option, value: any, loc := #caller_location) -> Network_Error {
 set_option :: proc(socket: Any_Socket, option: Socket_Option, value: any, loc := #caller_location) -> Network_Error {
 	return _set_option(socket, option, value, loc)
 	return _set_option(socket, option, value, loc)
+}
+
+set_blocking :: proc(socket: Any_Socket, should_block: bool) -> (err: Network_Error) {
+	return _set_blocking(socket, should_block)
 }
 }

+ 6 - 0
core/net/socket_darwin.odin

@@ -301,6 +301,12 @@ _set_option :: proc(s: Any_Socket, option: Socket_Option, value: any, loc := #ca
 	return nil
 	return nil
 }
 }
 
 
+@(private)
+_set_blocking :: proc(socket: Any_Socket, should_block: bool) -> (err: Network_Error) {
+	// TODO: Implement
+	unimplemented()
+}
+
 @private
 @private
 _endpoint_to_sockaddr :: proc(ep: Endpoint) -> (sockaddr: os.SOCKADDR_STORAGE_LH) {
 _endpoint_to_sockaddr :: proc(ep: Endpoint) -> (sockaddr: os.SOCKADDR_STORAGE_LH) {
 	switch a in ep.address {
 	switch a in ep.address {

+ 23 - 0
core/net/socket_linux.odin

@@ -316,6 +316,29 @@ _set_option :: proc(s: Any_Socket, option: Socket_Option, value: any, loc := #ca
 	return nil
 	return nil
 }
 }
 
 
+@(private)
+_set_blocking :: proc(socket: Any_Socket, should_block: bool) -> (err: Network_Error) {
+	socket := any_socket_to_socket(socket)
+
+	flags, getfl_err := os.fcntl(int(socket), os.F_GETFL, 0)
+	if getfl_err != os.ERROR_NONE {
+		return Set_Blocking_Error(getfl_err)
+	}
+
+	if should_block {
+		flags &= ~int(os.O_NONBLOCK)
+	} else {
+		flags |= int(os.O_NONBLOCK)
+	}
+
+	_, setfl_err := os.fcntl(int(socket), os.F_SETFL, flags)
+	if setfl_err != os.ERROR_NONE {
+		return Set_Blocking_Error(setfl_err)
+	}
+
+	return nil
+}
+
 @(private)
 @(private)
 _endpoint_to_sockaddr :: proc(ep: Endpoint) -> (sockaddr: os.SOCKADDR_STORAGE_LH) {
 _endpoint_to_sockaddr :: proc(ep: Endpoint) -> (sockaddr: os.SOCKADDR_STORAGE_LH) {
 	switch a in ep.address {
 	switch a in ep.address {

+ 12 - 0
core/net/socket_windows.odin

@@ -310,6 +310,18 @@ _set_option :: proc(s: Any_Socket, option: Socket_Option, value: any, loc := #ca
 	return nil
 	return nil
 }
 }
 
 
+@(private)
+_set_blocking :: proc(socket: Any_Socket, should_block: bool) -> (err: Network_Error) {
+	socket := any_socket_to_socket(socket)
+	arg: win.DWORD = 0 if should_block else 1
+	res := win.ioctlsocket(win.SOCKET(socket), transmute(win.c_long)win.FIONBIO, &arg)
+	if res == win.SOCKET_ERROR {
+		return Set_Blocking_Error(win.WSAGetLastError())
+	}
+
+	return nil
+}
+
 @(private)
 @(private)
 _endpoint_to_sockaddr :: proc(ep: Endpoint) -> (sockaddr: win.SOCKADDR_STORAGE_LH) {
 _endpoint_to_sockaddr :: proc(ep: Endpoint) -> (sockaddr: win.SOCKADDR_STORAGE_LH) {
 	switch a in ep.address {
 	switch a in ep.address {

+ 13 - 0
core/os/os_linux.odin

@@ -225,6 +225,11 @@ TCP_CORK:    int : 3
 
 
 MSG_TRUNC : int : 0x20
 MSG_TRUNC : int : 0x20
 
 
+// TODO: add remaining fcntl commands
+// reference: https://github.com/torvalds/linux/blob/master/include/uapi/asm-generic/fcntl.h
+F_GETFL: int : 3 /* Get file flags */
+F_SETFL: int : 4 /* Set file flags */
+
 // NOTE(zangent): These are OS specific!
 // NOTE(zangent): These are OS specific!
 // Do not mix these up!
 // Do not mix these up!
 RTLD_LAZY         :: 0x001
 RTLD_LAZY         :: 0x001
@@ -1074,3 +1079,11 @@ shutdown :: proc(sd: Socket, how: int) -> (Errno) {
 	}
 	}
 	return ERROR_NONE
 	return ERROR_NONE
 }
 }
+
+fcntl :: proc(fd: int, cmd: int, arg: int) -> (int, Errno) {
+	result := unix.sys_fcntl(fd, cmd, arg)
+	if result < 0 {
+		return 0, _get_errno(result)
+	}
+	return result, ERROR_NONE
+}

+ 26 - 0
core/prof/spall/doc.odin

@@ -0,0 +1,26 @@
+/*
+import "core:prof/spall"
+
+spall_ctx: spall.Context
+spall_buffer: spall.Buffer
+
+foo :: proc() {
+	spall.SCOPED_EVENT(&spall_ctx, &spall_buffer, #procedure)
+}
+
+main :: proc() {
+    spall_ctx = spall.context_create("trace_test.spall")
+    defer spall.context_destroy(&spall_ctx)
+
+    buffer_backing := make([]u8, spall.BUFFER_DEFAULT_SIZE)
+    spall_buffer = spall.buffer_create(buffer_backing)
+    defer spall.buffer_destroy(&spall_ctx, &spall_buffer)
+
+    spall.SCOPED_EVENT(&spall_ctx, &spall_buffer, #procedure)
+
+    for i := 0; i < 9001; i += 1 {
+		foo()
+    }
+}
+*/
+package spall

+ 10 - 3
core/prof/spall/spall.odin

@@ -1,4 +1,4 @@
-package prof_spall
+package spall
 
 
 import "core:os"
 import "core:os"
 import "core:time"
 import "core:time"
@@ -95,7 +95,7 @@ context_destroy :: proc(ctx: ^Context) {
 }
 }
 
 
 buffer_create :: proc(data: []byte, tid: u32 = 0, pid: u32 = 0) -> (buffer: Buffer, ok: bool) #optional_ok {
 buffer_create :: proc(data: []byte, tid: u32 = 0, pid: u32 = 0) -> (buffer: Buffer, ok: bool) #optional_ok {
-	assert(len(data) > 0)
+	assert(len(data) >= 1024)
 	buffer.data = data
 	buffer.data = data
 	buffer.tid  = tid
 	buffer.tid  = tid
 	buffer.pid  = pid
 	buffer.pid  = pid
@@ -105,8 +105,13 @@ buffer_create :: proc(data: []byte, tid: u32 = 0, pid: u32 = 0) -> (buffer: Buff
 }
 }
 
 
 buffer_flush :: proc(ctx: ^Context, buffer: ^Buffer) {
 buffer_flush :: proc(ctx: ^Context, buffer: ^Buffer) {
+	start := _trace_now(ctx)
 	os.write(ctx.fd, buffer.data[:buffer.head])
 	os.write(ctx.fd, buffer.data[:buffer.head])
 	buffer.head = 0
 	buffer.head = 0
+	end := _trace_now(ctx)
+
+	buffer.head += _build_begin(buffer.data[buffer.head:], "Spall Trace Buffer Flush", "", start, buffer.tid, buffer.pid)
+	buffer.head += _build_end(buffer.data[buffer.head:], end, buffer.tid, buffer.pid)
 }
 }
 
 
 buffer_destroy :: proc(ctx: ^Context, buffer: ^Buffer) {
 buffer_destroy :: proc(ctx: ^Context, buffer: ^Buffer) {
@@ -171,10 +176,11 @@ _build_begin :: proc "contextless" (buffer: []u8, name: string, args: string, ts
 	mem.copy(raw_data(buffer[size_of(Begin_Event):]), raw_data(name), name_len)
 	mem.copy(raw_data(buffer[size_of(Begin_Event):]), raw_data(name), name_len)
 	mem.copy(raw_data(buffer[size_of(Begin_Event)+name_len:]), raw_data(args), args_len)
 	mem.copy(raw_data(buffer[size_of(Begin_Event)+name_len:]), raw_data(args), args_len)
 	ok = true
 	ok = true
+
 	return
 	return
 }
 }
 
 
-_build_end :: proc(buffer: []u8, ts: f64, tid: u32, pid: u32) -> (event_size: int, ok: bool) #optional_ok {
+_build_end :: proc "contextless" (buffer: []u8, ts: f64, tid: u32, pid: u32) -> (event_size: int, ok: bool) #optional_ok {
 	ev := (^End_Event)(raw_data(buffer))
 	ev := (^End_Event)(raw_data(buffer))
 	event_size = size_of(End_Event)
 	event_size = size_of(End_Event)
 	if event_size > len(buffer) {
 	if event_size > len(buffer) {
@@ -186,6 +192,7 @@ _build_end :: proc(buffer: []u8, ts: f64, tid: u32, pid: u32) -> (event_size: in
 	ev.tid  = u32le(tid)
 	ev.tid  = u32le(tid)
 	ev.ts   = f64le(ts)
 	ev.ts   = f64le(ts)
 	ok = true
 	ok = true
+
 	return
 	return
 }
 }
 
 

+ 8 - 2
core/runtime/print.odin

@@ -6,7 +6,7 @@ _INTEGER_DIGITS :: "0123456789abcdefghijklmnopqrstuvwxyz"
 _INTEGER_DIGITS_VAR := _INTEGER_DIGITS
 _INTEGER_DIGITS_VAR := _INTEGER_DIGITS
 
 
 when !ODIN_DISALLOW_RTTI {
 when !ODIN_DISALLOW_RTTI {
-	print_any_single :: proc(arg: any) {
+	print_any_single :: proc "contextless" (arg: any) {
 		x := arg
 		x := arg
 		if loc, ok := x.(Source_Code_Location); ok {
 		if loc, ok := x.(Source_Code_Location); ok {
 			print_caller_location(loc)
 			print_caller_location(loc)
@@ -49,6 +49,12 @@ when !ODIN_DISALLOW_RTTI {
 		case uint:    print_uint(v)
 		case uint:    print_uint(v)
 		case uintptr: print_uintptr(v)
 		case uintptr: print_uintptr(v)
 
 
+		case bool: print_string("true" if v else "false")
+		case b8:   print_string("true" if v else "false")
+		case b16:  print_string("true" if v else "false")
+		case b32:  print_string("true" if v else "false")
+		case b64:  print_string("true" if v else "false")
+
 		case:
 		case:
 			ti := type_info_of(x.id)
 			ti := type_info_of(x.id)
 			#partial switch v in ti.variant {
 			#partial switch v in ti.variant {
@@ -60,7 +66,7 @@ when !ODIN_DISALLOW_RTTI {
 			print_string("<invalid-value>")
 			print_string("<invalid-value>")
 		}
 		}
 	}
 	}
-	println_any :: proc(args: ..any) {
+	println_any :: proc "contextless" (args: ..any) {
 		loop: for arg, i in args {
 		loop: for arg, i in args {
 			if i != 0 {
 			if i != 0 {
 				print_string(" ")
 				print_string(" ")

+ 2 - 2
core/slice/sort.odin

@@ -181,7 +181,7 @@ reverse_sort :: proc(data: $T/[]$E) where ORD(E) {
 }
 }
 
 
 
 
-reverse_sort_by :: proc(data: $T/[]$E, less: proc(i, j: E) -> bool) where ORD(E) {
+reverse_sort_by :: proc(data: $T/[]$E, less: proc(i, j: E) -> bool) {
 	context._internal = rawptr(less)
 	context._internal = rawptr(less)
 	sort_by(data, proc(i, j: E) -> bool {
 	sort_by(data, proc(i, j: E) -> bool {
 		k := (proc(i, j: E) -> bool)(context._internal)
 		k := (proc(i, j: E) -> bool)(context._internal)
@@ -189,7 +189,7 @@ reverse_sort_by :: proc(data: $T/[]$E, less: proc(i, j: E) -> bool) where ORD(E)
 	})
 	})
 }
 }
 
 
-reverse_sort_by_cmp :: proc(data: $T/[]$E, cmp: proc(i, j: E) -> Ordering) where ORD(E) {
+reverse_sort_by_cmp :: proc(data: $T/[]$E, cmp: proc(i, j: E) -> Ordering) {
 	context._internal = rawptr(cmp)
 	context._internal = rawptr(cmp)
 	sort_by_cmp(data, proc(i, j: E) -> Ordering {
 	sort_by_cmp(data, proc(i, j: E) -> Ordering {
 		k := (proc(i, j: E) -> Ordering)(context._internal)
 		k := (proc(i, j: E) -> Ordering)(context._internal)

+ 44 - 15
core/strconv/strconv.odin

@@ -556,19 +556,51 @@ parse_f32 :: proc(s: string, n: ^int = nil) -> (value: f32, ok: bool) {
 	return f32(v), ok
 	return f32(v), ok
 }
 }
 
 
+
+parse_f64 :: proc(str: string, n: ^int = nil) -> (value: f64, ok: bool) {
+	nr: int
+	value, nr, ok = parse_f64_prefix(str)
+	if ok && len(str) != nr {
+		ok = false
+	}
+	if n != nil { n^ = nr }
+	return
+}
+
+
+// Parses a 32-bit floating point number from a string.
+//
+// Returns ok=false if a base 10 float could not be found,
+// or if the input string contained more than just the number.
+//
+// ```
+// n, _, ok := strconv.parse_f32("12.34eee");
+// assert(n == 12.34 && ok);
+//
+// n, _, ok = strconv.parse_f32("12.34");
+// assert(n == 12.34 && ok);
+// ```
+parse_f32_prefix :: proc(str: string) -> (value: f32, nr: int, ok: bool) {
+	f: f64
+	f, nr, ok = parse_f64_prefix(str)
+	value = f32(f)
+	return
+}
+
+
 // Parses a 64-bit floating point number from a string.
 // Parses a 64-bit floating point number from a string.
 //
 //
 // Returns ok=false if a base 10 float could not be found,
 // Returns ok=false if a base 10 float could not be found,
 // or if the input string contained more than just the number.
 // or if the input string contained more than just the number.
 //
 //
 // ```
 // ```
-// n, ok := strconv.parse_f32("12.34eee");
+// n, _, ok := strconv.parse_f32("12.34eee");
 // assert(n == 12.34 && ok);
 // assert(n == 12.34 && ok);
 //
 //
-// n, ok = strconv.parse_f32("12.34");
+// n, _, ok = strconv.parse_f32("12.34");
 // assert(n == 12.34 && ok);
 // assert(n == 12.34 && ok);
 // ```
 // ```
-parse_f64 :: proc(str: string, n: ^int = nil) -> (value: f64, ok: bool) {
+parse_f64_prefix :: proc(str: string) -> (value: f64, nr: int, ok: bool) {
 	common_prefix_len_ignore_case :: proc "contextless" (s, prefix: string) -> int {
 	common_prefix_len_ignore_case :: proc "contextless" (s, prefix: string) -> int {
 		n := len(prefix)
 		n := len(prefix)
 		if n > len(s) {
 		if n > len(s) {
@@ -678,8 +710,8 @@ parse_f64 :: proc(str: string, n: ^int = nil) -> (value: f64, ok: bool) {
 				saw_digits = true
 				saw_digits = true
 				nd += 1
 				nd += 1
 				if nd_mant < MAX_MANT_DIGITS {
 				if nd_mant < MAX_MANT_DIGITS {
-					MAX_MANT_DIGITS *= 16
-					MAX_MANT_DIGITS += int(lower(c) - 'a' + 10)
+					mantissa *= 16
+					mantissa += u64(lower(c) - 'a' + 10)
 					nd_mant += 1
 					nd_mant += 1
 				} else {
 				} else {
 					trunc = true
 					trunc = true
@@ -729,12 +761,11 @@ parse_f64 :: proc(str: string, n: ^int = nil) -> (value: f64, ok: bool) {
 		if mantissa != 0 {
 		if mantissa != 0 {
 			exp = decimal_point - nd_mant
 			exp = decimal_point - nd_mant
 		}
 		}
-		// TODO(bill): check underscore correctness
 		ok = true
 		ok = true
 		return
 		return
 	}
 	}
 
 
-	parse_hex :: proc(s: string, mantissa: u64, exp: int, neg, trunc: bool) -> (f64, bool) {
+	parse_hex :: proc "contextless" (s: string, mantissa: u64, exp: int, neg, trunc: bool) -> (f64, bool) {
 		info := &_f64_info
 		info := &_f64_info
 
 
 		mantissa, exp := mantissa, exp
 		mantissa, exp := mantissa, exp
@@ -751,7 +782,7 @@ parse_f64 :: proc(str: string, n: ^int = nil) -> (value: f64, ok: bool) {
 			mantissa |= 1
 			mantissa |= 1
 		}
 		}
 
 
-		for mantissa >> (info.mantbits+2) == 0 {
+		for mantissa != 0 && mantissa >> (info.mantbits+2) == 0 {
 			mantissa = mantissa>>1 | mantissa&1
 			mantissa = mantissa>>1 | mantissa&1
 			exp += 1
 			exp += 1
 		}
 		}
@@ -795,9 +826,6 @@ parse_f64 :: proc(str: string, n: ^int = nil) -> (value: f64, ok: bool) {
 	}
 	}
 
 
 
 
-	nr: int
-	defer if n != nil { n^ = nr }
-
 	if value, nr, ok = check_special(str); ok {
 	if value, nr, ok = check_special(str); ok {
 		return
 		return
 	}
 	}
@@ -808,7 +836,8 @@ parse_f64 :: proc(str: string, n: ^int = nil) -> (value: f64, ok: bool) {
 	mantissa, exp, neg, trunc, hex, nr = parse_components(str) or_return
 	mantissa, exp, neg, trunc, hex, nr = parse_components(str) or_return
 
 
 	if hex {
 	if hex {
-		return parse_hex(str, mantissa, exp, neg, trunc)
+		value, ok = parse_hex(str, mantissa, exp, neg, trunc)
+		return
 	}
 	}
 
 
 	trunc_block: if !trunc {
 	trunc_block: if !trunc {
@@ -827,7 +856,7 @@ parse_f64 :: proc(str: string, n: ^int = nil) -> (value: f64, ok: bool) {
 		}
 		}
 		switch {
 		switch {
 		case exp == 0:
 		case exp == 0:
-			return f, true
+			return f, nr, true
 		case exp > 0 && exp <= 15+22:
 		case exp > 0 && exp <= 15+22:
 			if exp > 22 {
 			if exp > 22 {
 				f *= pow10[exp-22]
 				f *= pow10[exp-22]
@@ -836,9 +865,9 @@ parse_f64 :: proc(str: string, n: ^int = nil) -> (value: f64, ok: bool) {
 			if f > 1e15 || f < 1e-15 {
 			if f > 1e15 || f < 1e-15 {
 				break trunc_block
 				break trunc_block
 			}
 			}
-			return f * pow10[exp], true
+			return f * pow10[exp], nr, true
 		case -22 <= exp && exp < 0:
 		case -22 <= exp && exp < 0:
-			return f / pow10[-exp], true
+			return f / pow10[-exp], nr, true
 		}
 		}
 	}
 	}
 	d: decimal.Decimal
 	d: decimal.Decimal

+ 2 - 2
core/strings/conversion.odin

@@ -78,8 +78,8 @@ to_lower :: proc(s: string, allocator := context.allocator) -> string {
 	returns the input string `s` with all runes set to upper case
 	returns the input string `s` with all runes set to upper case
 	always allocates using the `allocator`
 	always allocates using the `allocator`
 
 
-	strings.to_lower("test") -> TEST
-	strings.to_lower("Test") -> TEST
+	strings.to_upper("test") -> TEST
+	strings.to_upper("Test") -> TEST
 */
 */
 to_upper :: proc(s: string, allocator := context.allocator) -> string {
 to_upper :: proc(s: string, allocator := context.allocator) -> string {
 	b: Builder
 	b: Builder

+ 1 - 1
core/strings/strings.odin

@@ -1428,7 +1428,7 @@ split_multi :: proc(s: string, substrs: []string, allocator := context.allocator
 	// sort substrings by string size, largest to smallest
 	// sort substrings by string size, largest to smallest
 	runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD()
 	runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD()
 	temp_substrs := slice.clone(substrs, context.temp_allocator)
 	temp_substrs := slice.clone(substrs, context.temp_allocator)
-	defer delete(temp_substrs)
+
 	slice.sort_by(temp_substrs, proc(a, b: string) -> bool {
 	slice.sort_by(temp_substrs, proc(a, b: string) -> bool {
 		return len(a) > len(b)	
 		return len(a) > len(b)	
 	})
 	})

+ 81 - 61
core/sys/info/platform_windows.odin

@@ -223,11 +223,10 @@ init_os_version :: proc () {
 
 
 	// Grab Windows DisplayVersion (like 20H02)
 	// Grab Windows DisplayVersion (like 20H02)
 	format_display_version :: proc (b: ^strings.Builder) -> (version: string) {
 	format_display_version :: proc (b: ^strings.Builder) -> (version: string) {
-		dv, ok := read_reg(
+		dv, ok := read_reg_string(
 			sys.HKEY_LOCAL_MACHINE,
 			sys.HKEY_LOCAL_MACHINE,
 			"SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion",
 			"SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion",
 			"DisplayVersion",
 			"DisplayVersion",
-			string,
 		)
 		)
 		defer delete(dv) // It'll be interned into `version_string_buf`
 		defer delete(dv) // It'll be interned into `version_string_buf`
 
 
@@ -243,11 +242,10 @@ init_os_version :: proc () {
 
 
 	// Grab build number and UBR
 	// Grab build number and UBR
 	format_build_number :: proc (b: ^strings.Builder, major_build: int) -> (ubr: int) {
 	format_build_number :: proc (b: ^strings.Builder, major_build: int) -> (ubr: int) {
-		res, ok := read_reg(
+		res, ok := read_reg_i32(
 			sys.HKEY_LOCAL_MACHINE,
 			sys.HKEY_LOCAL_MACHINE,
 			"SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion",
 			"SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion",
 			"UBR",
 			"UBR",
-			i32,
 		)
 		)
 
 
 		if ok {
 		if ok {
@@ -289,17 +287,17 @@ init_gpu_info :: proc() {
 	for {
 	for {
 		key := fmt.tprintf("%v\\%04d", GPU_INFO_BASE, gpu_index)
 		key := fmt.tprintf("%v\\%04d", GPU_INFO_BASE, gpu_index)
 
 
-		if vendor, ok := read_reg(sys.HKEY_LOCAL_MACHINE, key, "ProviderName", string); ok {
+		if vendor, ok := read_reg_string(sys.HKEY_LOCAL_MACHINE, key, "ProviderName"); ok {
 			append(&gpu_list, GPU{vendor_name = vendor})
 			append(&gpu_list, GPU{vendor_name = vendor})
 		} else {
 		} else {
 			break
 			break
 		}
 		}
 
 
-		if desc, ok := read_reg(sys.HKEY_LOCAL_MACHINE, key, "DriverDesc", string); ok {
+		if desc, ok := read_reg_string(sys.HKEY_LOCAL_MACHINE, key, "DriverDesc"); ok {
 			gpu_list[gpu_index].model_name = desc
 			gpu_list[gpu_index].model_name = desc
 		}
 		}
 
 
-		if vram, ok := read_reg(sys.HKEY_LOCAL_MACHINE, key, "HardwareInformation.qwMemorySize", i64); ok {
+		if vram, ok := read_reg_i64(sys.HKEY_LOCAL_MACHINE, key, "HardwareInformation.qwMemorySize"); ok {
 			gpu_list[gpu_index].total_ram = int(vram)
 			gpu_list[gpu_index].total_ram = int(vram)
 		}
 		}
 		gpu_index += 1
 		gpu_index += 1
@@ -308,71 +306,93 @@ init_gpu_info :: proc() {
 }
 }
 
 
 @(private)
 @(private)
-read_reg :: proc(hkey: sys.HKEY, subkey, val: string, $T: typeid) -> (res: T, ok: bool) {
+read_reg_string :: proc(hkey: sys.HKEY, subkey, val: string) -> (res: string, ok: bool) {
+	if len(subkey) == 0 || len(val) == 0 {
+		return
+	}
+
+	runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD()
+
 	BUF_SIZE :: 1024
 	BUF_SIZE :: 1024
+	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)
 
 
+	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
+}
+@(private)
+read_reg_i32 :: proc(hkey: sys.HKEY, subkey, val: string) -> (res: i32, ok: bool) {
 	if len(subkey) == 0 || len(val) == 0 {
 	if len(subkey) == 0 || len(val) == 0 {
-		return {}, false
+		return
 	}
 	}
 
 
 	runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD()
 	runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD()
 
 
+	BUF_SIZE :: 1024
 	key_name_wide := make([]u16, BUF_SIZE, context.temp_allocator)
 	key_name_wide := make([]u16, BUF_SIZE, context.temp_allocator)
 	val_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(key_name_wide, subkey)
 	utf16.encode_string(val_name_wide, val)
 	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")
+	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
+}
+@(private)
+read_reg_i64 :: proc(hkey: sys.HKEY, subkey, val: string) -> (res: i64, ok: bool) {
+	if len(subkey) == 0 || len(val) == 0 {
+		return
 	}
 	}
-	return
-}
+
+	runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD()
+
+	BUF_SIZE :: 1024
+	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)
+
+	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
+}

+ 4 - 0
core/sys/unix/syscalls_linux.odin

@@ -2053,6 +2053,10 @@ sys_personality :: proc(persona: u64) -> int {
 	return int(intrinsics.syscall(SYS_personality, uintptr(persona)))
 	return int(intrinsics.syscall(SYS_personality, uintptr(persona)))
 }
 }
 
 
+sys_fcntl :: proc "contextless" (fd: int, cmd: int, arg: int) -> int {
+	return int(intrinsics.syscall(SYS_fcntl, uintptr(fd), uintptr(cmd), uintptr(arg)))
+}
+
 get_errno :: proc "contextless" (res: int) -> i32 {
 get_errno :: proc "contextless" (res: int) -> i32 {
 	if res < 0 && res > -4096 {
 	if res < 0 && res > -4096 {
 		return i32(-res)
 		return i32(-res)

+ 2 - 1
core/sys/windows/kernel32.odin

@@ -45,7 +45,8 @@ foreign kernel32 {
 						   dwCursorPosition: COORD) -> BOOL ---
 						   dwCursorPosition: COORD) -> BOOL ---
 	SetConsoleTextAttribute :: proc(hConsoleOutput: HANDLE,
 	SetConsoleTextAttribute :: proc(hConsoleOutput: HANDLE,
 									wAttributes: WORD) -> BOOL ---
 									wAttributes: WORD) -> BOOL ---
-
+	SetConsoleOutputCP :: proc(wCodePageID: UINT) -> BOOL ---
+	
 	GetFileInformationByHandle :: proc(hFile: HANDLE, lpFileInformation: LPBY_HANDLE_FILE_INFORMATION) -> BOOL ---
 	GetFileInformationByHandle :: proc(hFile: HANDLE, lpFileInformation: LPBY_HANDLE_FILE_INFORMATION) -> BOOL ---
 	SetHandleInformation :: proc(hObject: HANDLE,
 	SetHandleInformation :: proc(hObject: HANDLE,
 	                             dwMask: DWORD,
 	                             dwMask: DWORD,

+ 3 - 1
src/build_settings.cpp

@@ -275,6 +275,7 @@ struct BuildContext {
 	bool   no_output_files;
 	bool   no_output_files;
 	bool   no_crt;
 	bool   no_crt;
 	bool   no_entry_point;
 	bool   no_entry_point;
+	bool   no_thread_local;
 	bool   use_lld;
 	bool   use_lld;
 	bool   vet;
 	bool   vet;
 	bool   vet_extra;
 	bool   vet_extra;
@@ -1255,7 +1256,7 @@ gb_internal void init_build_context(TargetMetrics *cross_target) {
 		gb_exit(1);
 		gb_exit(1);
 	}
 	}
 
 
-	bc->optimization_level = gb_clamp(bc->optimization_level, 0, 3);
+	bc->optimization_level = gb_clamp(bc->optimization_level, -1, 2);
 
 
 	// ENFORCE DYNAMIC MAP CALLS
 	// ENFORCE DYNAMIC MAP CALLS
 	bc->dynamic_map_calls = true;
 	bc->dynamic_map_calls = true;
@@ -1369,6 +1370,7 @@ gb_internal char const *target_features_set_to_cstring(gbAllocator allocator, bo
 		gb_memmove(features + len, feature.text, feature.len);
 		gb_memmove(features + len, feature.text, feature.len);
 		len += feature.len;
 		len += feature.len;
 		if (with_quotes) features[len++] = '"';
 		if (with_quotes) features[len++] = '"';
+		i += 1;
 	}
 	}
 	features[len++] = 0;
 	features[len++] = 0;
 
 

+ 4 - 1
src/check_decl.cpp

@@ -1143,9 +1143,12 @@ gb_internal void check_global_variable_decl(CheckerContext *ctx, Entity *&e, Ast
 
 
 	if (is_arch_wasm() && e->Variable.thread_local_model.len != 0) {
 	if (is_arch_wasm() && e->Variable.thread_local_model.len != 0) {
 		e->Variable.thread_local_model.len = 0;
 		e->Variable.thread_local_model.len = 0;
-		// NOTE(bill): ignore this message for the time begin
+		// NOTE(bill): ignore this message for the time being
 		// error(e->token, "@(thread_local) is not supported for this target platform");
 		// error(e->token, "@(thread_local) is not supported for this target platform");
 	}
 	}
+	if(build_context.no_thread_local) {
+		e->Variable.thread_local_model.len = 0;
+	}
 
 
 	String context_name = str_lit("variable declaration");
 	String context_name = str_lit("variable declaration");
 
 

+ 26 - 2
src/check_stmt.cpp

@@ -1184,6 +1184,8 @@ gb_internal void check_type_switch_stmt(CheckerContext *ctx, Ast *node, u32 mod_
 		return;
 		return;
 	}
 	}
 
 
+
+	Ast *nil_seen = nullptr;
 	PtrSet<Type *> seen = {};
 	PtrSet<Type *> seen = {};
 	defer (ptr_set_destroy(&seen));
 	defer (ptr_set_destroy(&seen));
 
 
@@ -1194,6 +1196,7 @@ gb_internal void check_type_switch_stmt(CheckerContext *ctx, Ast *node, u32 mod_
 		}
 		}
 		ast_node(cc, CaseClause, stmt);
 		ast_node(cc, CaseClause, stmt);
 
 
+		bool saw_nil = false;
 		// TODO(bill): Make robust
 		// TODO(bill): Make robust
 		Type *bt = base_type(type_deref(x.type));
 		Type *bt = base_type(type_deref(x.type));
 
 
@@ -1202,6 +1205,25 @@ gb_internal void check_type_switch_stmt(CheckerContext *ctx, Ast *node, u32 mod_
 			if (type_expr != nullptr) { // Otherwise it's a default expression
 			if (type_expr != nullptr) { // Otherwise it's a default expression
 				Operand y = {};
 				Operand y = {};
 				check_expr_or_type(ctx, &y, type_expr);
 				check_expr_or_type(ctx, &y, type_expr);
+
+				if (is_operand_nil(y)) {
+					if (!type_has_nil(type_deref(x.type))) {
+						error(type_expr, "'nil' case is not allowed for the type '%s'", type_to_string(type_deref(x.type)));
+						continue;
+					}
+					saw_nil = true;
+
+					if (nil_seen) {
+						ERROR_BLOCK();
+						error(type_expr, "'nil' case has already been handled previously");
+						error_line("\t 'nil' was already previously seen at %s", token_pos_to_string(ast_token(nil_seen).pos));
+					} else {
+						nil_seen = type_expr;
+					}
+					case_type = y.type;
+					continue;
+				}
+
 				if (y.mode != Addressing_Type) {
 				if (y.mode != Addressing_Type) {
 					gbString str = expr_to_string(type_expr);
 					gbString str = expr_to_string(type_expr);
 					error(type_expr, "Expected a type as a case, got %s", str);
 					error(type_expr, "Expected a type as a case, got %s", str);
@@ -1255,14 +1277,16 @@ gb_internal void check_type_switch_stmt(CheckerContext *ctx, Ast *node, u32 mod_
 			is_reference = true;
 			is_reference = true;
 		}
 		}
 
 
-		if (cc->list.count > 1) {
+		if (cc->list.count > 1 || saw_nil) {
 			case_type = nullptr;
 			case_type = nullptr;
 		}
 		}
 		if (case_type == nullptr) {
 		if (case_type == nullptr) {
 			case_type = x.type;
 			case_type = x.type;
 		}
 		}
 		if (switch_kind == TypeSwitch_Any) {
 		if (switch_kind == TypeSwitch_Any) {
-			add_type_info_type(ctx, case_type);
+			if (!is_type_untyped(case_type)) {
+				add_type_info_type(ctx, case_type);
+			}
 		}
 		}
 
 
 		check_open_scope(ctx, stmt);
 		check_open_scope(ctx, stmt);

+ 8 - 0
src/check_type.cpp

@@ -674,6 +674,10 @@ gb_internal void check_union_type(CheckerContext *ctx, Type *union_type, Ast *no
 	for_array(i, ut->variants) {
 	for_array(i, ut->variants) {
 		Ast *node = ut->variants[i];
 		Ast *node = ut->variants[i];
 		Type *t = check_type_expr(ctx, node, nullptr);
 		Type *t = check_type_expr(ctx, node, nullptr);
+		if (union_type->Union.is_polymorphic && poly_operands == nullptr) {
+			// NOTE(bill): don't add any variants if this is this is an unspecialized polymorphic record
+			continue;
+		}
 		if (t != nullptr && t != t_invalid) {
 		if (t != nullptr && t != t_invalid) {
 			bool ok = true;
 			bool ok = true;
 			t = default_type(t);
 			t = default_type(t);
@@ -686,8 +690,12 @@ gb_internal void check_union_type(CheckerContext *ctx, Type *union_type, Ast *no
 				for_array(j, variants) {
 				for_array(j, variants) {
 					if (are_types_identical(t, variants[j])) {
 					if (are_types_identical(t, variants[j])) {
 						ok = false;
 						ok = false;
+						ERROR_BLOCK();
 						gbString str = type_to_string(t);
 						gbString str = type_to_string(t);
 						error(node, "Duplicate variant type '%s'", str);
 						error(node, "Duplicate variant type '%s'", str);
+						if (j < ut->variants.count) {
+							error_line("\tPrevious found at %s\n", token_pos_to_string(ast_token(ut->variants[j]).pos));
+						}
 						gb_string_free(str);
 						gb_string_free(str);
 						break;
 						break;
 					}
 					}

+ 32 - 1
src/exact_value.cpp

@@ -1,4 +1,5 @@
 #include <math.h>
 #include <math.h>
+#include <stdlib.h>
 
 
 gb_global BlockingMutex hash_exact_value_mutex;
 gb_global BlockingMutex hash_exact_value_mutex;
 
 
@@ -174,7 +175,36 @@ gb_internal ExactValue exact_value_integer_from_string(String const &string) {
 
 
 
 
 
 
-gb_internal f64 float_from_string(String string) {
+gb_internal f64 float_from_string(String const &string) {
+	if (string.len < 128) {
+		char buf[128] = {};
+		isize n = 0;
+		for (isize i = 0; i < string.len; i++) {
+			u8 c = string.text[i];
+			if (c == '_') {
+				continue;
+			}
+			if (c == 'E') { c = 'e'; }
+			buf[n++] = cast(char)c;
+		}
+		buf[n] = 0;
+		return atof(buf);
+	} else {
+		TEMPORARY_ALLOCATOR_GUARD();
+		char *buf = gb_alloc_array(temporary_allocator(), char, string.len+1);
+		isize n = 0;
+		for (isize i = 0; i < string.len; i++) {
+			u8 c = string.text[i];
+			if (c == '_') {
+				continue;
+			}
+			if (c == 'E') { c = 'e'; }
+			buf[n++] = cast(char)c;
+		}
+		buf[n] = 0;
+		return atof(buf);
+	}
+/*
 	isize i = 0;
 	isize i = 0;
 	u8 *str = string.text;
 	u8 *str = string.text;
 	isize len = string.len;
 	isize len = string.len;
@@ -250,6 +280,7 @@ gb_internal f64 float_from_string(String string) {
 	}
 	}
 
 
 	return sign * (frac ? (value / scale) : (value * scale));
 	return sign * (frac ? (value / scale) : (value * scale));
+*/
 }
 }
 
 
 gb_internal ExactValue exact_value_float_from_string(String string) {
 gb_internal ExactValue exact_value_float_from_string(String string) {

+ 2 - 0
src/llvm_backend.hpp

@@ -337,6 +337,8 @@ struct lbProcedure {
 
 
 	LLVMMetadataRef debug_info;
 	LLVMMetadataRef debug_info;
 
 
+	lbAddr current_elision_hint;
+
 	PtrMap<Ast *, lbValue> selector_values;
 	PtrMap<Ast *, lbValue> selector_values;
 	PtrMap<Ast *, lbAddr>  selector_addr;
 	PtrMap<Ast *, lbAddr>  selector_addr;
 	PtrMap<LLVMValueRef, lbTupleFix> tuple_fix_map;
 	PtrMap<LLVMValueRef, lbTupleFix> tuple_fix_map;

+ 8 - 1
src/llvm_backend_const.cpp

@@ -484,7 +484,14 @@ gb_internal lbValue lb_const_value(lbModule *m, Type *type, ExactValue value, bo
 					LLVMValueRef indices[2] = {llvm_zero(m), llvm_zero(m)};
 					LLVMValueRef indices[2] = {llvm_zero(m), llvm_zero(m)};
 					LLVMValueRef ptr = LLVMBuildInBoundsGEP2(p->builder, llvm_type, array_data, indices, 2, "");
 					LLVMValueRef ptr = LLVMBuildInBoundsGEP2(p->builder, llvm_type, array_data, indices, 2, "");
 					LLVMValueRef len = LLVMConstInt(lb_type(m, t_int), count, true);
 					LLVMValueRef len = LLVMConstInt(lb_type(m, t_int), count, true);
-					lbAddr slice = lb_add_local_generated(p, type, false);
+
+					lbAddr slice = {};
+					if (p->current_elision_hint.addr.value && are_types_identical(lb_addr_type(p->current_elision_hint), type)) {
+						slice = p->current_elision_hint;
+						p->current_elision_hint = {};
+					} else {
+						slice = lb_add_local_generated(p, type, false);
+					}
 					map_set(&m->exact_value_compound_literal_addr_map, value.value_compound, slice);
 					map_set(&m->exact_value_compound_literal_addr_map, value.value_compound, slice);
 
 
 					lb_fill_slice(p, slice, {ptr, alloc_type_pointer(elem)}, {len, t_int});
 					lb_fill_slice(p, slice, {ptr, alloc_type_pointer(elem)}, {len, t_int});

+ 0 - 1
src/llvm_backend_debug.cpp

@@ -989,7 +989,6 @@ gb_internal void lb_add_debug_local_variable(lbProcedure *p, LLVMValueRef ptr, T
 		return;
 		return;
 	}
 	}
 
 
-
 	AstFile *file = p->body->file();
 	AstFile *file = p->body->file();
 
 
 	LLVMMetadataRef llvm_scope = lb_get_current_debug_scope(p);
 	LLVMMetadataRef llvm_scope = lb_get_current_debug_scope(p);

+ 7 - 2
src/llvm_backend_general.cpp

@@ -63,7 +63,6 @@ gb_internal void lb_init_module(lbModule *m, Checker *c) {
 	map_init(&m->values);
 	map_init(&m->values);
 	map_init(&m->soa_values);
 	map_init(&m->soa_values);
 	string_map_init(&m->members);
 	string_map_init(&m->members);
-	map_init(&m->procedure_values);
 	string_map_init(&m->procedures);
 	string_map_init(&m->procedures);
 	string_map_init(&m->const_strings);
 	string_map_init(&m->const_strings);
 	map_init(&m->function_type_map);
 	map_init(&m->function_type_map);
@@ -71,7 +70,13 @@ gb_internal void lb_init_module(lbModule *m, Checker *c) {
 	map_init(&m->hasher_procs);
 	map_init(&m->hasher_procs);
 	map_init(&m->map_get_procs);
 	map_init(&m->map_get_procs);
 	map_init(&m->map_set_procs);
 	map_init(&m->map_set_procs);
-	array_init(&m->procedures_to_generate, a, 0, 1024);
+	if (build_context.use_separate_modules) {
+		array_init(&m->procedures_to_generate, a, 0, 1<<10);
+		map_init(&m->procedure_values,               1<<11);
+	} else {
+		array_init(&m->procedures_to_generate, a, 0, c->info.all_procedures.count);
+		map_init(&m->procedure_values,               c->info.all_procedures.count*2);
+	}
 	array_init(&m->global_procedures_and_types_to_create, a, 0, 1024);
 	array_init(&m->global_procedures_and_types_to_create, a, 0, 1024);
 	array_init(&m->missing_procedures_to_check, a, 0, 16);
 	array_init(&m->missing_procedures_to_check, a, 0, 16);
 	map_init(&m->debug_values);
 	map_init(&m->debug_values);

+ 25 - 13
src/llvm_backend_opt.cpp

@@ -55,8 +55,17 @@ gb_internal void lb_populate_function_pass_manager_specific(lbModule *m, LLVMPas
 #define LLVM_ADD_CONSTANT_VALUE_PASS(fpm) 
 #define LLVM_ADD_CONSTANT_VALUE_PASS(fpm) 
 #endif
 #endif
 
 
+gb_internal bool lb_opt_ignore(i32 optimization_level) {
+	optimization_level = gb_clamp(optimization_level, -1, 2);
+	return optimization_level == -1;
+}
+
 gb_internal void lb_basic_populate_function_pass_manager(LLVMPassManagerRef fpm, i32 optimization_level) {
 gb_internal void lb_basic_populate_function_pass_manager(LLVMPassManagerRef fpm, i32 optimization_level) {
-	if (false && optimization_level == 0 && build_context.ODIN_DEBUG) {
+	if (lb_opt_ignore(optimization_level)) {
+		return;
+	}
+
+	if (false && optimization_level <= 0 && build_context.ODIN_DEBUG) {
 		LLVMAddMergedLoadStoreMotionPass(fpm);
 		LLVMAddMergedLoadStoreMotionPass(fpm);
 	} else {
 	} else {
 		LLVMAddPromoteMemoryToRegisterPass(fpm);
 		LLVMAddPromoteMemoryToRegisterPass(fpm);
@@ -69,14 +78,14 @@ gb_internal void lb_basic_populate_function_pass_manager(LLVMPassManagerRef fpm,
 }
 }
 
 
 gb_internal void lb_populate_function_pass_manager(lbModule *m, LLVMPassManagerRef fpm, bool ignore_memcpy_pass, i32 optimization_level) {
 gb_internal void lb_populate_function_pass_manager(lbModule *m, LLVMPassManagerRef fpm, bool ignore_memcpy_pass, i32 optimization_level) {
-	// NOTE(bill): Treat -opt:3 as if it was -opt:2
-	// TODO(bill): Determine which opt definitions should exist in the first place
-	optimization_level = gb_clamp(optimization_level, 0, 2);
+	if (lb_opt_ignore(optimization_level)) {
+		return;
+	}
 
 
 	if (ignore_memcpy_pass) {
 	if (ignore_memcpy_pass) {
 		lb_basic_populate_function_pass_manager(fpm, optimization_level);
 		lb_basic_populate_function_pass_manager(fpm, optimization_level);
 		return;
 		return;
-	} else if (optimization_level == 0) {
+	} else if (optimization_level <= 0) {
 		LLVMAddMemCpyOptPass(fpm);
 		LLVMAddMemCpyOptPass(fpm);
 		lb_basic_populate_function_pass_manager(fpm, optimization_level);
 		lb_basic_populate_function_pass_manager(fpm, optimization_level);
 		return;
 		return;
@@ -103,11 +112,11 @@ gb_internal void lb_populate_function_pass_manager(lbModule *m, LLVMPassManagerR
 }
 }
 
 
 gb_internal void lb_populate_function_pass_manager_specific(lbModule *m, LLVMPassManagerRef fpm, i32 optimization_level) {
 gb_internal void lb_populate_function_pass_manager_specific(lbModule *m, LLVMPassManagerRef fpm, i32 optimization_level) {
-	// NOTE(bill): Treat -opt:3 as if it was -opt:2
-	// TODO(bill): Determine which opt definitions should exist in the first place
-	optimization_level = gb_clamp(optimization_level, 0, 2);
+	if (lb_opt_ignore(optimization_level)) {
+		return;
+	}
 
 
-	if (optimization_level == 0) {
+	if (optimization_level <= 0) {
 		LLVMAddMemCpyOptPass(fpm);
 		LLVMAddMemCpyOptPass(fpm);
 		lb_basic_populate_function_pass_manager(fpm, optimization_level);
 		lb_basic_populate_function_pass_manager(fpm, optimization_level);
 		return;
 		return;
@@ -181,8 +190,7 @@ gb_internal void lb_populate_module_pass_manager(LLVMTargetMachineRef target_mac
 
 
 	// NOTE(bill): Treat -opt:3 as if it was -opt:2
 	// NOTE(bill): Treat -opt:3 as if it was -opt:2
 	// TODO(bill): Determine which opt definitions should exist in the first place
 	// TODO(bill): Determine which opt definitions should exist in the first place
-	optimization_level = gb_clamp(optimization_level, 0, 2);
-	if (optimization_level == 0 && build_context.ODIN_DEBUG) {
+	if (optimization_level <= 0 && build_context.ODIN_DEBUG) {
 		return;
 		return;
 	}
 	}
 
 
@@ -190,7 +198,7 @@ gb_internal void lb_populate_module_pass_manager(LLVMTargetMachineRef target_mac
 	LLVMAddStripDeadPrototypesPass(mpm);
 	LLVMAddStripDeadPrototypesPass(mpm);
 	LLVMAddAnalysisPasses(target_machine, mpm);
 	LLVMAddAnalysisPasses(target_machine, mpm);
 	LLVMAddPruneEHPass(mpm);
 	LLVMAddPruneEHPass(mpm);
-	if (optimization_level == 0) {
+	if (optimization_level <= 0) {
 		return;
 		return;
 	}
 	}
 
 
@@ -267,6 +275,9 @@ gb_internal void lb_populate_module_pass_manager(LLVMTargetMachineRef target_mac
 **************************************************************************/
 **************************************************************************/
 
 
 gb_internal void lb_run_remove_dead_instruction_pass(lbProcedure *p) {
 gb_internal void lb_run_remove_dead_instruction_pass(lbProcedure *p) {
+	unsigned debug_declare_id = LLVMLookupIntrinsicID("llvm.dbg.declare", 16);
+	GB_ASSERT(debug_declare_id != 0);
+
 	isize removal_count = 0;
 	isize removal_count = 0;
 	isize pass_count = 0;
 	isize pass_count = 0;
 	isize const max_pass_count = 10;
 	isize const max_pass_count = 10;
@@ -302,6 +313,8 @@ gb_internal void lb_run_remove_dead_instruction_pass(lbProcedure *p) {
 
 
 				// NOTE(bill): Explicit instructions are set here because some instructions could have side effects
 				// NOTE(bill): Explicit instructions are set here because some instructions could have side effects
 				switch (LLVMGetInstructionOpcode(curr_instr)) {
 				switch (LLVMGetInstructionOpcode(curr_instr)) {
+				// case LLVMAlloca:
+
 				case LLVMFNeg:
 				case LLVMFNeg:
 				case LLVMAdd:
 				case LLVMAdd:
 				case LLVMFAdd:
 				case LLVMFAdd:
@@ -321,7 +334,6 @@ gb_internal void lb_run_remove_dead_instruction_pass(lbProcedure *p) {
 				case LLVMAnd:
 				case LLVMAnd:
 				case LLVMOr:
 				case LLVMOr:
 				case LLVMXor:
 				case LLVMXor:
-				case LLVMAlloca:
 				case LLVMLoad:
 				case LLVMLoad:
 				case LLVMGetElementPtr:
 				case LLVMGetElementPtr:
 				case LLVMTrunc:
 				case LLVMTrunc:

+ 33 - 23
src/llvm_backend_stmt.cpp

@@ -791,15 +791,6 @@ gb_internal void lb_build_range_stmt(lbProcedure *p, AstRangeStmt *rs, Scope *sc
 		val1_type = type_of_expr(rs->vals[1]);
 		val1_type = type_of_expr(rs->vals[1]);
 	}
 	}
 
 
-	if (val0_type != nullptr) {
-		Entity *e = entity_of_node(rs->vals[0]);
-		lb_add_local(p, e->type, e, true);
-	}
-	if (val1_type != nullptr) {
-		Entity *e = entity_of_node(rs->vals[1]);
-		lb_add_local(p, e->type, e, true);
-	}
-
 	lbValue val = {};
 	lbValue val = {};
 	lbValue key = {};
 	lbValue key = {};
 	lbBlock *loop = nullptr;
 	lbBlock *loop = nullptr;
@@ -1308,6 +1299,7 @@ gb_internal lbAddr lb_store_range_stmt_val(lbProcedure *p, Ast *stmt_val, lbValu
 		if (LLVMIsALoadInst(value.value)) {
 		if (LLVMIsALoadInst(value.value)) {
 			lbValue ptr = lb_address_from_load_or_generate_local(p, value);
 			lbValue ptr = lb_address_from_load_or_generate_local(p, value);
 			lb_add_entity(p->module, e, ptr);
 			lb_add_entity(p->module, e, ptr);
+			lb_add_debug_local_variable(p, ptr.value, e->type, e->token);
 			return lb_addr(ptr);
 			return lb_addr(ptr);
 		}
 		}
 	}
 	}
@@ -1431,9 +1423,11 @@ gb_internal void lb_build_type_switch_stmt(lbProcedure *p, AstTypeSwitchStmt *ss
 				continue;
 				continue;
 			}
 			}
 			Entity *case_entity = implicit_entity_of_node(clause);
 			Entity *case_entity = implicit_entity_of_node(clause);
-			max_size = gb_max(max_size, type_size_of(case_entity->type));
-			max_align = gb_max(max_align, type_align_of(case_entity->type));
-			variants_found = true;
+			if (!is_type_untyped_nil(case_entity->type)) {
+				max_size = gb_max(max_size, type_size_of(case_entity->type));
+				max_align = gb_max(max_align, type_align_of(case_entity->type));
+				variants_found = true;
+			}
 		}
 		}
 		if (variants_found) {
 		if (variants_found) {
 			Type *t = alloc_type_array(t_u8, max_size);
 			Type *t = alloc_type_array(t_u8, max_size);
@@ -1457,6 +1451,8 @@ gb_internal void lb_build_type_switch_stmt(lbProcedure *p, AstTypeSwitchStmt *ss
 		if (p->debug_info != nullptr) {
 		if (p->debug_info != nullptr) {
 			LLVMSetCurrentDebugLocation2(p->builder, lb_debug_location_from_ast(p, clause));
 			LLVMSetCurrentDebugLocation2(p->builder, lb_debug_location_from_ast(p, clause));
 		}
 		}
+
+		bool saw_nil = false;
 		for (Ast *type_expr : cc->list) {
 		for (Ast *type_expr : cc->list) {
 			Type *case_type = type_of_expr(type_expr);
 			Type *case_type = type_of_expr(type_expr);
 			lbValue on_val = {};
 			lbValue on_val = {};
@@ -1465,7 +1461,12 @@ gb_internal void lb_build_type_switch_stmt(lbProcedure *p, AstTypeSwitchStmt *ss
 				on_val = lb_const_union_tag(m, ut, case_type);
 				on_val = lb_const_union_tag(m, ut, case_type);
 
 
 			} else if (switch_kind == TypeSwitch_Any) {
 			} else if (switch_kind == TypeSwitch_Any) {
-				on_val = lb_typeid(m, case_type);
+				if (is_type_untyped_nil(case_type)) {
+					saw_nil = true;
+					on_val = lb_const_nil(m, t_typeid);
+				} else {
+					on_val = lb_typeid(m, case_type);
+				}
 			}
 			}
 			GB_ASSERT(on_val.value != nullptr);
 			GB_ASSERT(on_val.value != nullptr);
 			LLVMAddCase(switch_instr, on_val.value, body->block);
 			LLVMAddCase(switch_instr, on_val.value, body->block);
@@ -1477,7 +1478,7 @@ gb_internal void lb_build_type_switch_stmt(lbProcedure *p, AstTypeSwitchStmt *ss
 
 
 		bool by_reference = (case_entity->flags & EntityFlag_Value) == 0;
 		bool by_reference = (case_entity->flags & EntityFlag_Value) == 0;
 
 
-		if (cc->list.count == 1) {
+		if (cc->list.count == 1 && !saw_nil) {
 			lbValue data = {};
 			lbValue data = {};
 			if (switch_kind == TypeSwitch_Union) {
 			if (switch_kind == TypeSwitch_Union) {
 				data = union_data;
 				data = union_data;
@@ -2287,18 +2288,25 @@ gb_internal void lb_build_stmt(lbProcedure *p, Ast *node) {
 
 
 			isize lval_index = 0;
 			isize lval_index = 0;
 			for (Ast *rhs : values) {
 			for (Ast *rhs : values) {
+				p->current_elision_hint = lvals[lval_index];
+
 				rhs = unparen_expr(rhs);
 				rhs = unparen_expr(rhs);
 				lbValue init = lb_build_expr(p, rhs);
 				lbValue init = lb_build_expr(p, rhs);
 			#if 1
 			#if 1
-				// NOTE(bill, 2023-02-17): lb_const_value might produce a stack local variable for the
-				// compound literal, so reusing that variable should minimize the stack wastage
-				if (rhs->kind == Ast_CompoundLit) {
-					lbAddr *comp_lit_addr = map_get(&p->module->exact_value_compound_literal_addr_map, rhs);
-					if (comp_lit_addr) {
-						Entity *e = entity_of_node(vd->names[lval_index]);
-						if (e) {
-							lb_add_entity(p->module, e, comp_lit_addr->addr);
-							lvals[lval_index] = {}; // do nothing so that nothing will assign to it
+				if (p->current_elision_hint.addr.value != lvals[lval_index].addr.value) {
+					lvals[lval_index] = {}; // do nothing so that nothing will assign to it
+				} else {
+					// NOTE(bill, 2023-02-17): lb_const_value might produce a stack local variable for the
+					// compound literal, so reusing that variable should minimize the stack wastage
+					if (rhs->kind == Ast_CompoundLit) {
+						lbAddr *comp_lit_addr = map_get(&p->module->exact_value_compound_literal_addr_map, rhs);
+						if (comp_lit_addr) {
+							Entity *e = entity_of_node(vd->names[lval_index]);
+							if (e) {
+								GB_ASSERT(p->current_elision_hint.addr.value == nullptr);
+								GB_ASSERT(p->current_elision_hint.addr.value != lvals[lval_index].addr.value);
+								lvals[lval_index] = {}; // do nothing so that nothing will assign to it
+							}
 						}
 						}
 					}
 					}
 				}
 				}
@@ -2308,6 +2316,8 @@ gb_internal void lb_build_stmt(lbProcedure *p, Ast *node) {
 			}
 			}
 			GB_ASSERT(lval_index == lvals.count);
 			GB_ASSERT(lval_index == lvals.count);
 
 
+			p->current_elision_hint = {};
+
 			GB_ASSERT(lvals.count == inits.count);
 			GB_ASSERT(lvals.count == inits.count);
 			for_array(i, inits) {
 			for_array(i, inits) {
 				lbAddr lval = lvals[i];
 				lbAddr lval = lvals[i];

+ 14 - 2
src/main.cpp

@@ -634,6 +634,7 @@ enum BuildFlagKind {
 	BuildFlag_Microarch,
 	BuildFlag_Microarch,
 	BuildFlag_TargetFeatures,
 	BuildFlag_TargetFeatures,
 	BuildFlag_MinimumOSVersion,
 	BuildFlag_MinimumOSVersion,
+	BuildFlag_NoThreadLocal,
 
 
 	BuildFlag_RelocMode,
 	BuildFlag_RelocMode,
 	BuildFlag_DisableRedZone,
 	BuildFlag_DisableRedZone,
@@ -794,6 +795,7 @@ gb_internal bool parse_build_flags(Array<String> args) {
 	add_flag(&build_flags, BuildFlag_Debug,                   str_lit("debug"),                     BuildFlagParam_None,    Command__does_check);
 	add_flag(&build_flags, BuildFlag_Debug,                   str_lit("debug"),                     BuildFlagParam_None,    Command__does_check);
 	add_flag(&build_flags, BuildFlag_DisableAssert,           str_lit("disable-assert"),            BuildFlagParam_None,    Command__does_check);
 	add_flag(&build_flags, BuildFlag_DisableAssert,           str_lit("disable-assert"),            BuildFlagParam_None,    Command__does_check);
 	add_flag(&build_flags, BuildFlag_NoBoundsCheck,           str_lit("no-bounds-check"),           BuildFlagParam_None,    Command__does_check);
 	add_flag(&build_flags, BuildFlag_NoBoundsCheck,           str_lit("no-bounds-check"),           BuildFlagParam_None,    Command__does_check);
+	add_flag(&build_flags, BuildFlag_NoThreadLocal,           str_lit("no-thread-local"),           BuildFlagParam_None,    Command__does_check);
 	add_flag(&build_flags, BuildFlag_NoDynamicLiterals,       str_lit("no-dynamic-literals"),       BuildFlagParam_None,    Command__does_check);
 	add_flag(&build_flags, BuildFlag_NoDynamicLiterals,       str_lit("no-dynamic-literals"),       BuildFlagParam_None,    Command__does_check);
 	add_flag(&build_flags, BuildFlag_NoCRT,                   str_lit("no-crt"),                    BuildFlagParam_None,    Command__does_build);
 	add_flag(&build_flags, BuildFlag_NoCRT,                   str_lit("no-crt"),                    BuildFlagParam_None,    Command__does_build);
 	add_flag(&build_flags, BuildFlag_NoEntryPoint,            str_lit("no-entry-point"),            BuildFlagParam_None,    Command__does_check &~ Command_test);
 	add_flag(&build_flags, BuildFlag_NoEntryPoint,            str_lit("no-entry-point"),            BuildFlagParam_None,    Command__does_check &~ Command_test);
@@ -1002,7 +1004,9 @@ gb_internal bool parse_build_flags(Array<String> args) {
 						}
 						}
 						case BuildFlag_OptimizationMode: {
 						case BuildFlag_OptimizationMode: {
 							GB_ASSERT(value.kind == ExactValue_String);
 							GB_ASSERT(value.kind == ExactValue_String);
-							if (value.value_string == "minimal") {
+							if (value.value_string == "none") {
+								build_context.optimization_level = -1;
+							} else if (value.value_string == "minimal") {
 								build_context.optimization_level = 0;
 								build_context.optimization_level = 0;
 							} else if (value.value_string == "size") {
 							} else if (value.value_string == "size") {
 								build_context.optimization_level = 1;
 								build_context.optimization_level = 1;
@@ -1014,6 +1018,7 @@ gb_internal bool parse_build_flags(Array<String> args) {
 								gb_printf_err("\tminimal\n");
 								gb_printf_err("\tminimal\n");
 								gb_printf_err("\tsize\n");
 								gb_printf_err("\tsize\n");
 								gb_printf_err("\tspeed\n");
 								gb_printf_err("\tspeed\n");
+								gb_printf_err("\tnone (useful for -debug builds)\n");
 								bad_flags = true;
 								bad_flags = true;
 							}
 							}
 							break;
 							break;
@@ -1309,6 +1314,9 @@ gb_internal bool parse_build_flags(Array<String> args) {
 						case BuildFlag_NoEntryPoint:
 						case BuildFlag_NoEntryPoint:
 							build_context.no_entry_point = true;
 							build_context.no_entry_point = true;
 							break;
 							break;
+						case BuildFlag_NoThreadLocal:
+							build_context.no_thread_local = true;
+							break;
 						case BuildFlag_UseLLD:
 						case BuildFlag_UseLLD:
 							build_context.use_lld = true;
 							build_context.use_lld = true;
 							break;
 							break;
@@ -1955,7 +1963,7 @@ gb_internal void print_show_help(String const arg0, String const &command) {
 
 
 		print_usage_line(1, "-o:<string>");
 		print_usage_line(1, "-o:<string>");
 		print_usage_line(2, "Set the optimization mode for compilation");
 		print_usage_line(2, "Set the optimization mode for compilation");
-		print_usage_line(2, "Accepted values: minimal, size, speed");
+		print_usage_line(2, "Accepted values: minimal, size, speed, none");
 		print_usage_line(2, "Example: -o:speed");
 		print_usage_line(2, "Example: -o:speed");
 		print_usage_line(0, "");
 		print_usage_line(0, "");
 	}
 	}
@@ -2061,6 +2069,10 @@ gb_internal void print_show_help(String const arg0, String const &command) {
 		print_usage_line(2, "Disables automatic linking with the C Run Time");
 		print_usage_line(2, "Disables automatic linking with the C Run Time");
 		print_usage_line(0, "");
 		print_usage_line(0, "");
 
 
+		print_usage_line(1, "-no-thread-local");
+		print_usage_line(2, "Ignore @thread_local attribute, effectively treating the program as if it is single-threaded");
+		print_usage_line(0, "");
+
 		print_usage_line(1, "-lld");
 		print_usage_line(1, "-lld");
 		print_usage_line(2, "Use the LLD linker rather than the default");
 		print_usage_line(2, "Use the LLD linker rather than the default");
 		print_usage_line(0, "");
 		print_usage_line(0, "");

+ 19 - 0
vendor/directx/d3d12/d3d12.odin

@@ -4870,6 +4870,25 @@ IInfoQueue_VTable :: struct {
 	GetMuteDebugOutput:                           proc "stdcall" (this: ^IInfoQueue) -> BOOL,
 	GetMuteDebugOutput:                           proc "stdcall" (this: ^IInfoQueue) -> BOOL,
 }
 }
 
 
+MESSAGE_CALLBACK_FLAGS :: distinct bit_set[MESSAGE_CALLBACK_FLAG; u32]
+MESSAGE_CALLBACK_FLAG :: enum {
+	IGNORE_FILTERS = 0,
+}
+
+PFN_MESSAGE_CALLBACK :: #type proc "c" (Category: MESSAGE_CATEGORY, Severity: MESSAGE_SEVERITY, ID: MESSAGE_ID, pDescription: cstring, pContext: rawptr)
+
+IInfoQueue1_UUID_STRING :: "2852dd88-b484-4c0c-b6b1-67168500e600"
+IInfoQueue1_UUID := &IID{0x2852dd88, 0xb484, 0x4c0c, {0xb6, 0xb1, 0x67, 0x16, 0x85, 0x00, 0xe6, 0x00}}
+IInfoQueue1 :: struct #raw_union {
+	#subtype iinfo_queue: IInfoQueue,
+	using idxgiinfoqueue1_vtable: ^IInfoQueue1_VTable,
+}
+IInfoQueue1_VTable :: struct {
+	using idxgiinfoqueue_vtable: IInfoQueue_VTable,
+	RegisterMessageCallback: proc "stdcall" (this: ^IInfoQueue1, CallbackFunc: PFN_MESSAGE_CALLBACK, CallbackFilterFlags: MESSAGE_CALLBACK_FLAGS, pContext: rawptr, pCallbackCookie: ^u32) -> HRESULT,
+	UnregisterMessageCallback: proc "stdcall" (this: ^IInfoQueue1, pCallbackCookie: u32) -> HRESULT,
+}
+
 PFN_CREATE_DEVICE :: #type proc "c" (a0: ^IUnknown, a1: FEATURE_LEVEL, a2: ^IID, a3: ^rawptr) -> HRESULT
 PFN_CREATE_DEVICE :: #type proc "c" (a0: ^IUnknown, a1: FEATURE_LEVEL, a2: ^IID, a3: ^rawptr) -> HRESULT
 PFN_GET_DEBUG_INTERFACE :: #type proc "c" (a0: ^IID, a1: ^rawptr) -> HRESULT
 PFN_GET_DEBUG_INTERFACE :: #type proc "c" (a0: ^IID, a1: ^rawptr) -> HRESULT
 
 

+ 1 - 1
vendor/directx/dxc/dxcapi.odin

@@ -102,7 +102,7 @@ IBlobEncoding :: struct #raw_union {
 }
 }
 IBlobEncoding_VTable :: struct {
 IBlobEncoding_VTable :: struct {
 	using idxcblob_vtable: IBlob_VTable,
 	using idxcblob_vtable: IBlob_VTable,
-	GetEncoding: proc "stdcall" (pKnown: ^BOOL, pCodePage: ^u32) -> HRESULT,
+	GetEncoding: proc "stdcall" (this: ^IBlobEncoding, pKnown: ^BOOL, pCodePage: ^u32) -> HRESULT,
 }
 }
 
 
 IBlobUtf16_UUID_STRING :: "A3F84EAB-0FAA-497E-A39C-EE6ED60B2D84"
 IBlobUtf16_UUID_STRING :: "A3F84EAB-0FAA-497E-A39C-EE6ED60B2D84"