Quellcode durchsuchen

Merge branch 'master' into stdlib-parser-fixes

gingerBill vor 2 Jahren
Ursprung
Commit
77e5854a16
100 geänderte Dateien mit 16329 neuen und 2041 gelöschten Zeilen
  1. 14 1
      build.bat
  2. 7 4
      build_odin.sh
  3. 1 0
      core/compress/zlib/zlib.odin
  4. 17 20
      core/encoding/entity/entity.odin
  5. 1 1
      core/encoding/json/marshal.odin
  6. 21 23
      core/encoding/xml/debug_print.odin
  7. 3 3
      core/encoding/xml/example/xml_example.odin
  8. 17 12
      core/encoding/xml/helpers.odin
  9. 16 16
      core/encoding/xml/tokenizer.odin
  10. 15 27
      core/encoding/xml/xml_reader.odin
  11. 14 35
      core/fmt/fmt.odin
  12. 8 7
      core/image/netpbm/helpers.odin
  13. 1 0
      core/image/netpbm/netpbm.odin
  14. 3 4
      core/image/png/helpers.odin
  15. 8 8
      core/image/png/png.odin
  16. 7 5
      core/math/fixed/fixed.odin
  17. 12 12
      core/math/math.odin
  18. 52 52
      core/mem/allocators.odin
  19. 35 35
      core/net/url.odin
  20. 25 25
      core/odin/doc-format/doc_format.odin
  21. 1 1
      core/odin/parser/parser.odin
  22. 86 92
      core/odin/printer/visit.odin
  23. 16 16
      core/odin/tokenizer/tokenizer.odin
  24. 59 60
      core/reflect/reflect.odin
  25. 7 8
      core/reflect/types.odin
  26. 5 5
      core/runtime/core.odin
  27. 5 2
      core/runtime/default_allocators_general.odin
  28. 118 98
      core/runtime/dynamic_map_internal.odin
  29. 9 9
      core/runtime/error_checks.odin
  30. 11 2
      core/runtime/internal.odin
  31. 6 9
      core/runtime/os_specific_windows.odin
  32. 17 9
      core/runtime/print.odin
  33. 2 2
      core/runtime/procs.odin
  34. 6 0
      core/sys/windows/types.odin
  35. 3 0
      core/sys/windows/user32.odin
  36. 2 0
      core/text/i18n/i18n.odin
  37. 34 8
      core/text/i18n/qt_linguist.odin
  38. 2 2
      core/thread/thread_windows.odin
  39. 19 17
      core/time/time.odin
  40. 28 6
      examples/demo/demo.odin
  41. 11 0
      src/array.cpp
  42. 41 3
      src/build_settings.cpp
  43. 5 2
      src/check_builtin.cpp
  44. 77 90
      src/check_decl.cpp
  45. 184 150
      src/check_expr.cpp
  46. 11 6
      src/check_stmt.cpp
  47. 10 4
      src/check_type.cpp
  48. 123 44
      src/checker.cpp
  49. 3 2
      src/checker.hpp
  50. 25 25
      src/docs_format.cpp
  51. 4 4
      src/docs_writer.cpp
  52. 11 8
      src/entity.cpp
  53. 2 2
      src/error.cpp
  54. 2 5
      src/exact_value.cpp
  55. 460 0
      src/linker.cpp
  56. 3 10
      src/llvm_backend.hpp
  57. 67 69
      src/llvm_backend_const.cpp
  58. 5 12
      src/llvm_backend_debug.cpp
  59. 27 17
      src/llvm_backend_expr.cpp
  60. 87 155
      src/llvm_backend_general.cpp
  61. 12 9
      src/llvm_backend_proc.cpp
  62. 1 4
      src/llvm_backend_stmt.cpp
  63. 7 6
      src/llvm_backend_type.cpp
  64. 1 6
      src/llvm_backend_utility.cpp
  65. 139 460
      src/main.cpp
  66. 139 34
      src/parser.cpp
  67. 3 0
      src/parser.hpp
  68. 813 0
      src/tilde.cpp
  69. 373 0
      src/tilde.hpp
  70. 1101 0
      src/tilde/tb.h
  71. BIN
      src/tilde/tb.lib
  72. 76 0
      src/tilde/tb_arena.h
  73. 330 0
      src/tilde/tb_coff.h
  74. 170 0
      src/tilde/tb_elf.h
  75. 132 0
      src/tilde/tb_formats.h
  76. 90 0
      src/tilde/tb_x64.h
  77. 443 0
      src/tilde_builtin.cpp
  78. 1040 0
      src/tilde_const.cpp
  79. 482 0
      src/tilde_debug.cpp
  80. 3871 0
      src/tilde_expr.cpp
  81. 1307 0
      src/tilde_proc.cpp
  82. 2614 0
      src/tilde_stmt.cpp
  83. 983 0
      src/tilde_type_info.cpp
  84. 2 2
      src/tokenizer.cpp
  85. 127 70
      src/types.cpp
  86. 14 20
      tests/core/encoding/hxa/test_core_hxa.odin
  87. 33 36
      tests/core/encoding/xml/test_core_xml.odin
  88. 4 10
      tests/core/math/linalg/glsl/test_linalg_glsl_math.odin
  89. 73 94
      tests/core/math/test_core_math.odin
  90. 6 12
      tests/core/path/filepath/test_core_filepath.odin
  91. 16 20
      tests/core/reflect/test_core_reflect.odin
  92. 2 6
      tests/core/text/i18n/test_core_text_i18n.odin
  93. 1 0
      tests/issues/run.bat
  94. 1 0
      tests/issues/run.sh
  95. 26 0
      tests/issues/test_issue_2666.odin
  96. 2 2
      vendor/OpenGL/helpers.odin
  97. 5 3
      vendor/OpenGL/wrappers.odin
  98. 2 3
      vendor/darwin/Foundation/NSNumber.odin
  99. 14 0
      vendor/directx/d3d11/d3d11.odin
  100. 3 0
      vendor/directx/d3d_compiler/d3d_compiler.odin

+ 14 - 1
build.bat

@@ -51,7 +51,10 @@ set compiler_flags= -nologo -Oi -TP -fp:precise -Gm- -MP -FC -EHsc- -GR- -GF
 set compiler_defines= -DODIN_VERSION_RAW=\"%odin_version_raw%\"
 
 if not exist .git\ goto skip_git_hash
-for /f %%i in ('git rev-parse --short HEAD') do set GIT_SHA=%%i
+for /f "tokens=1,2" %%i IN ('git show "--pretty=%%cd %%h" "--date=format:%%Y-%%m" --no-patch --no-notes HEAD') do (
+	set odin_version_raw=dev-%%i
+	set GIT_SHA=%%j
+)
 if %ERRORLEVEL% equ 0 set compiler_defines=%compiler_defines% -DGIT_SHA=\"%GIT_SHA%\"
 :skip_git_hash
 
@@ -77,6 +80,16 @@ set libs= ^
 	Synchronization.lib ^
 	bin\llvm\windows\LLVM-C.lib
 
+rem DO NOT TOUCH!
+rem THIS TILDE STUFF IS FOR DEVELOPMENT ONLY!
+set tilde_backend=0
+if %tilde_backend% EQU 1 (
+	set libs=%libs% src\tilde\tb.lib
+	set compiler_defines=%compiler_defines% -DODIN_TILDE_BACKEND
+)
+rem DO NOT TOUCH!
+
+
 set linker_flags= -incremental:no -opt:ref -subsystem:console
 
 if %release_mode% EQU 0 ( rem Debug

+ 7 - 4
build_odin.sh

@@ -8,17 +8,20 @@ set -eu
 : ${ODIN_VERSION=dev-$(date +"%Y-%m")}
 : ${GIT_SHA=}
 
-CPPFLAGS="$CPPFLAGS -DODIN_VERSION_RAW=\"$ODIN_VERSION\""
 CXXFLAGS="$CXXFLAGS -std=c++14"
 LDFLAGS="$LDFLAGS -pthread -lm -lstdc++"
 
-if [ -d ".git" ]; then
-	GIT_SHA=$(git rev-parse --short HEAD || :)
-	if [ "$GIT_SHA" ]; then
+if [ -d ".git" ] && [ $(which git) ]; then
+	versionTag=( $(git show --pretty='%cd %h' --date=format:%Y-%m --no-patch --no-notes HEAD) )
+	if [ $? -eq 0 ]; then
+		ODIN_VERSION="dev-${versionTag[0]}"
+		GIT_SHA="${versionTag[1]}"
 		CPPFLAGS="$CPPFLAGS -DGIT_SHA=\"$GIT_SHA\""
 	fi
 fi
 
+CPPFLAGS="$CPPFLAGS -DODIN_VERSION_RAW=\"$ODIN_VERSION\""
+
 DISABLED_WARNINGS="-Wno-switch -Wno-macro-redefined -Wno-unused-value"
 OS=$(uname)
 

+ 1 - 0
core/compress/zlib/zlib.odin

@@ -1,3 +1,4 @@
+//+vet !using-param
 package zlib
 
 /*

+ 17 - 20
core/encoding/entity/entity.odin

@@ -184,28 +184,26 @@ decode_xml :: proc(input: string, options := XML_Decode_Options{}, allocator :=
 
 advance :: proc(t: ^Tokenizer) -> (err: Error) {
 	if t == nil { return .Tokenizer_Is_Nil }
-	using t
-
 	#no_bounds_check {
-		if read_offset < len(src) {
-			offset = read_offset
-			r, w   = rune(src[read_offset]), 1
+		if t.read_offset < len(t.src) {
+			t.offset = t.read_offset
+			t.r, t.w   = rune(t.src[t.read_offset]), 1
 			switch {
-			case r == 0:
+			case t.r == 0:
 				return .Illegal_NUL_Character
-			case r >= utf8.RUNE_SELF:
-				r, w = utf8.decode_rune_in_string(src[read_offset:])
-				if r == utf8.RUNE_ERROR && w == 1 {
+			case t.r >= utf8.RUNE_SELF:
+				t.r, t.w = utf8.decode_rune_in_string(t.src[t.read_offset:])
+				if t.r == utf8.RUNE_ERROR && t.w == 1 {
 					return .Illegal_UTF_Encoding
-				} else if r == utf8.RUNE_BOM && offset > 0 {
+				} else if t.r == utf8.RUNE_BOM && t.offset > 0 {
 					return .Illegal_BOM
 				}
 			}
-			read_offset += w
+			t.read_offset += t.w
 			return .None
 		} else {
-			offset = len(src)
-			r = -1
+			t.offset = len(t.src)
+			t.r = -1
 			return
 		}
 	}
@@ -273,26 +271,25 @@ _extract_xml_entity :: proc(t: ^Tokenizer) -> (entity: string, err: Error) {
 		All of these would be in the ASCII range.
 		Even if one is not, it doesn't matter. All characters we need to compare to extract are.
 	*/
-	using t
 
 	length := len(t.src)
 	found  := false
 
 	#no_bounds_check {
-		for read_offset < length {
-			if src[read_offset] == ';' {
+		for t.read_offset < length {
+			if t.src[t.read_offset] == ';' {
+				t.read_offset += 1
 				found = true
-				read_offset += 1
 				break
 			}
-			read_offset += 1
+			t.read_offset += 1
 		}
 	}
 
 	if found {
-		return string(src[offset + 1 : read_offset - 1]), .None
+		return string(t.src[t.offset + 1 : t.read_offset - 1]), .None
 	}
-	return string(src[offset : read_offset]), .Invalid_Entity_Encoding
+	return string(t.src[t.offset : t.read_offset]), .Invalid_Entity_Encoding
 }
 
 /*

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

@@ -207,7 +207,7 @@ marshal_to_writer :: proc(w: io.Writer, v: any, opt: ^Marshal_Options) -> (err:
 	case runtime.Type_Info_Relative_Pointer:
 		return .Unsupported_Type
 
-	case runtime.Type_Info_Relative_Slice:
+	case runtime.Type_Info_Relative_Multi_Pointer:
 		return .Unsupported_Type
 		
 	case runtime.Type_Info_Matrix:

+ 21 - 23
core/encoding/xml/debug_print.odin

@@ -19,43 +19,39 @@ import "core:fmt"
 */
 print :: proc(writer: io.Writer, doc: ^Document) -> (written: int, err: io.Error) {
 	if doc == nil { return }
-	using fmt
-
-	written += wprintf(writer, "[XML Prolog]\n")
+	written += fmt.wprintf(writer, "[XML Prolog]\n")
 
 	for attr in doc.prologue {
-		written += wprintf(writer, "\t%v: %v\n", attr.key, attr.val)
+		written += fmt.wprintf(writer, "\t%v: %v\n", attr.key, attr.val)
 	}
 
-	written += wprintf(writer, "[Encoding] %v\n", doc.encoding)
+	written += fmt.wprintf(writer, "[Encoding] %v\n", doc.encoding)
 
 	if len(doc.doctype.ident) > 0 {
-		written += wprintf(writer, "[DOCTYPE]  %v\n", doc.doctype.ident)
+		written += fmt.wprintf(writer, "[DOCTYPE]  %v\n", doc.doctype.ident)
 
 		if len(doc.doctype.rest) > 0 {
-		 	wprintf(writer, "\t%v\n", doc.doctype.rest)
+		 	fmt.wprintf(writer, "\t%v\n", doc.doctype.rest)
 		}
 	}
 
 	for comment in doc.comments {
-		written += wprintf(writer, "[Pre-root comment]  %v\n", comment)
+		written += fmt.wprintf(writer, "[Pre-root comment]  %v\n", comment)
 	}
 
 	if len(doc.elements) > 0 {
-	 	wprintln(writer, " --- ")
+	 	fmt.wprintln(writer, " --- ")
 	 	print_element(writer, doc, 0)
-	 	wprintln(writer, " --- ")
+	 	fmt.wprintln(writer, " --- ")
 	 }
 
 	return written, .None
 }
 
 print_element :: proc(writer: io.Writer, doc: ^Document, element_id: Element_ID, indent := 0) -> (written: int, err: io.Error) {
-	using fmt
-
 	tab :: proc(writer: io.Writer, indent: int) {
 		for _ in 0..=indent {
-			wprintf(writer, "\t")
+			fmt.wprintf(writer, "\t")
 		}
 	}
 
@@ -64,22 +60,24 @@ print_element :: proc(writer: io.Writer, doc: ^Document, element_id: Element_ID,
 	element := doc.elements[element_id]
 
 	if element.kind == .Element {
-		wprintf(writer, "<%v>\n", element.ident)
-		if len(element.value) > 0 {
-			tab(writer, indent + 1)
-			wprintf(writer, "[Value] %v\n", element.value)
+		fmt.wprintf(writer, "<%v>\n", element.ident)
+
+		for value in element.value {
+			switch v in value {
+			case string:
+				tab(writer, indent + 1)
+				fmt.wprintf(writer, "[Value] %v\n", v)
+			case Element_ID:
+				print_element(writer, doc, v, indent + 1)
+			}
 		}
 
 		for attr in element.attribs {
 			tab(writer, indent + 1)
-			wprintf(writer, "[Attr] %v: %v\n", attr.key, attr.val)
-		}
-
-		for child in element.children {
-			print_element(writer, doc, child, indent + 1)
+			fmt.wprintf(writer, "[Attr] %v: %v\n", attr.key, attr.val)
 		}
 	} else if element.kind == .Comment {
-		wprintf(writer, "[COMMENT] %v\n", element.value)
+		fmt.wprintf(writer, "[COMMENT] %v\n", element.value)
 	}
 
 	return written, .None

+ 3 - 3
core/encoding/xml/example/xml_example.odin

@@ -72,10 +72,10 @@ example :: proc() {
 	 	return
 	}
 
-	printf("Found `<charlist>` with %v children, %v elements total\n", len(docs[0].elements[charlist].children), docs[0].element_count)
+	printf("Found `<charlist>` with %v children, %v elements total\n", len(docs[0].elements[charlist].value), docs[0].element_count)
 
-	crc32 := doc_hash(docs[0])
-	printf("[%v] CRC32: 0x%08x\n", "🎉" if crc32 == 0xcaa042b9 else "🤬", crc32)
+	crc32 := doc_hash(docs[0], false)
+	printf("[%v] CRC32: 0x%08x\n", "🎉" if crc32 == 0x420dbac5 else "🤬", crc32)
 
 	for round in 0..<N {
 		defer xml.destroy(docs[round])

+ 17 - 12
core/encoding/xml/helpers.odin

@@ -13,20 +13,25 @@ find_child_by_ident :: proc(doc: ^Document, parent_id: Element_ID, ident: string
 	tag := doc.elements[parent_id]
 
 	count := 0
-	for child_id in tag.children {
-		child := doc.elements[child_id]
-		/*
-			Skip commments. They have no name.
-		*/
-		if child.kind  != .Element                { continue }
+	for v in tag.value {
+		switch child_id in v {
+		case string: continue
+		case Element_ID:
+			child := doc.elements[child_id]
+			/*
+				Skip commments. They have no name.
+			*/
+			if child.kind  != .Element                { continue }
 
-		/*
-			If the ident matches and it's the nth such child, return it.
-		*/
-		if child.ident == ident {
-			if count == nth                       { return child_id, true }
-			count += 1
+			/*
+				If the ident matches and it's the nth such child, return it.
+			*/
+			if child.ident == ident {
+				if count == nth                       { return child_id, true }
+				count += 1
+			}
 		}
+
 	}
 	return 0, false
 }

+ 16 - 16
core/encoding/xml/tokenizer.odin

@@ -125,38 +125,38 @@ error :: proc(t: ^Tokenizer, offset: int, msg: string, args: ..any) {
 }
 
 @(optimization_mode="speed")
-advance_rune :: proc(using t: ^Tokenizer) {
+advance_rune :: proc(t: ^Tokenizer) {
 	#no_bounds_check {
 		/*
 			Already bounds-checked here.
 		*/
-		if read_offset < len(src) {
-			offset = read_offset
-			if ch == '\n' {
-				line_offset = offset
-				line_count += 1
+		if t.read_offset < len(t.src) {
+			t.offset = t.read_offset
+			if t.ch == '\n' {
+				t.line_offset = t.offset
+				t.line_count += 1
 			}
-			r, w := rune(src[read_offset]), 1
+			r, w := rune(t.src[t.read_offset]), 1
 			switch {
 			case r == 0:
 				error(t, t.offset, "illegal character NUL")
 			case r >= utf8.RUNE_SELF:
-				r, w = #force_inline utf8.decode_rune_in_string(src[read_offset:])
+				r, w = #force_inline utf8.decode_rune_in_string(t.src[t.read_offset:])
 				if r == utf8.RUNE_ERROR && w == 1 {
 					error(t, t.offset, "illegal UTF-8 encoding")
-				} else if r == utf8.RUNE_BOM && offset > 0 {
+				} else if r == utf8.RUNE_BOM && t.offset > 0 {
 					error(t, t.offset, "illegal byte order mark")
 				}
 			}
-			read_offset += w
-			ch = r
+			t.read_offset += w
+			t.ch = r
 		} else {
-			offset = len(src)
-			if ch == '\n' {
-				line_offset = offset
-				line_count += 1
+			t.offset = len(t.src)
+			if t.ch == '\n' {
+				t.line_offset = t.offset
+				t.line_count += 1
 			}
-			ch = -1
+			t.ch = -1
 		}
 	}
 }

+ 15 - 27
core/encoding/xml/xml_reader.odin

@@ -125,16 +125,19 @@ Document :: struct {
 
 Element :: struct {
 	ident:   string,
-	value:   string,
+	value:   [dynamic]Value,
 	attribs: Attributes,
 
 	kind: enum {
 		Element = 0,
 		Comment,
 	},
-
 	parent:   Element_ID,
-	children: [dynamic]Element_ID,
+}
+
+Value :: union {
+	string,
+	Element_ID,
 }
 
 Attribute :: struct {
@@ -247,9 +250,6 @@ parse_bytes :: proc(data: []u8, options := DEFAULT_OPTIONS, path := "", error_ha
 
 	err =            .Unexpected_Token
 	element, parent: Element_ID
-
-	tag_is_open   := false
-	first_element := true
 	open: Token
 
 	/*
@@ -275,16 +275,10 @@ parse_bytes :: proc(data: []u8, options := DEFAULT_OPTIONS, path := "", error_ha
 					e.g. <odin - Start of new element.
 				*/
 				element = new_element(doc)
-				tag_is_open = true
-
-				if first_element {
-					/*
-						First element.
-					*/
-					parent   = element
-					first_element = false
+				if element == 0 { // First Element
+					parent = element
 				} else {
-					append(&doc.elements[parent].children, element)
+					append(&doc.elements[parent].value, element)
 				}
 
 				doc.elements[element].parent = parent
@@ -324,7 +318,6 @@ parse_bytes :: proc(data: []u8, options := DEFAULT_OPTIONS, path := "", error_ha
 					expect(t, .Gt) or_return
 					parent      = doc.elements[element].parent
 					element     = parent
-					tag_is_open = false
 
 				case:
 					error(t, t.offset, "Expected close tag, got: %#v\n", end_token)
@@ -344,7 +337,6 @@ parse_bytes :: proc(data: []u8, options := DEFAULT_OPTIONS, path := "", error_ha
 				}
 				parent      = doc.elements[element].parent
 				element     = parent
-				tag_is_open = false
 
 			} else if open.kind == .Exclaim {
 				/*
@@ -392,8 +384,8 @@ parse_bytes :: proc(data: []u8, options := DEFAULT_OPTIONS, path := "", error_ha
 							el := new_element(doc)
 							doc.elements[el].parent = element
 							doc.elements[el].kind   = .Comment
-							doc.elements[el].value  = comment
-							append(&doc.elements[element].children, el)
+							append(&doc.elements[el].value, comment)
+							append(&doc.elements[element].value, el)
 						}
 					}
 
@@ -436,9 +428,6 @@ parse_bytes :: proc(data: []u8, options := DEFAULT_OPTIONS, path := "", error_ha
 			/*
 				End of file.
 			*/
-			if tag_is_open {
-				return doc, .Premature_EOF
-			}
 			break loop
 
 		case:
@@ -450,7 +439,7 @@ parse_bytes :: proc(data: []u8, options := DEFAULT_OPTIONS, path := "", error_ha
 			needs_processing |= .Decode_SGML_Entities in opts.flags
 
 			if !needs_processing {
-				doc.elements[element].value = body_text
+				append(&doc.elements[element].value, body_text)
 				continue
 			}
 
@@ -472,10 +461,10 @@ parse_bytes :: proc(data: []u8, options := DEFAULT_OPTIONS, path := "", error_ha
 
 			decoded, decode_err := entity.decode_xml(body_text, decode_opts)
 			if decode_err == .None {
-				doc.elements[element].value = decoded
+				append(&doc.elements[element].value, decoded)
 				append(&doc.strings_to_free, decoded)
 			} else {
-				doc.elements[element].value = body_text
+				append(&doc.elements[element].value, body_text)
 			}
 		}
 	}
@@ -518,7 +507,7 @@ destroy :: proc(doc: ^Document) {
 
 	for el in doc.elements {
 		delete(el.attribs)
-		delete(el.children)
+		delete(el.value)
 	}
 	delete(doc.elements)
 
@@ -710,6 +699,5 @@ new_element :: proc(doc: ^Document) -> (id: Element_ID) {
 
 	cur := doc.element_count
 	doc.element_count += 1
-
 	return cur
 }

+ 14 - 35
core/fmt/fmt.odin

@@ -835,22 +835,22 @@ int_from_arg :: proc(args: []any, arg_index: int) -> (int, int, bool) {
 // - fi: A pointer to an Info structure
 // - verb: The invalid format verb
 //
-fmt_bad_verb :: proc(using fi: ^Info, verb: rune) {
+fmt_bad_verb :: proc(fi: ^Info, verb: rune) {
 	prev_in_bad := fi.in_bad
 	defer fi.in_bad = prev_in_bad
 	fi.in_bad = true
 
-	io.write_string(writer, "%!", &fi.n)
-	io.write_rune(writer, verb, &fi.n)
-	io.write_byte(writer, '(', &fi.n)
-	if arg.id != nil {
-		reflect.write_typeid(writer, arg.id, &fi.n)
-		io.write_byte(writer, '=', &fi.n)
-		fmt_value(fi, arg, 'v')
+	io.write_string(fi.writer, "%!", &fi.n)
+	io.write_rune(fi.writer, verb, &fi.n)
+	io.write_byte(fi.writer, '(', &fi.n)
+	if fi.arg.id != nil {
+		reflect.write_typeid(fi.writer, fi.arg.id, &fi.n)
+		io.write_byte(fi.writer, '=', &fi.n)
+		fmt_value(fi, fi.arg, 'v')
 	} else {
-		io.write_string(writer, "<nil>", &fi.n)
+		io.write_string(fi.writer, "<nil>", &fi.n)
 	}
-	io.write_byte(writer, ')', &fi.n)
+	io.write_byte(fi.writer, ')', &fi.n)
 }
 // Formats a boolean value according to the specified format verb
 //
@@ -859,7 +859,7 @@ fmt_bad_verb :: proc(using fi: ^Info, verb: rune) {
 // - b: The boolean value to format
 // - verb: The format verb
 //
-fmt_bool :: proc(using fi: ^Info, b: bool, verb: rune) {
+fmt_bool :: proc(fi: ^Info, b: bool, verb: rune) {
 	switch verb {
 	case 't', 'v':
 		fmt_string(fi, b ? "true" : "false", 's')
@@ -2535,32 +2535,11 @@ fmt_value :: proc(fi: ^Info, v: any, verb: rune) {
 
 		fmt_value(fi, absolute_ptr, verb)
 
-	case runtime.Type_Info_Relative_Slice:
+	case runtime.Type_Info_Relative_Multi_Pointer:
 		ptr := reflect.relative_pointer_to_absolute_raw(v.data, info.base_integer.id)
+		absolute_ptr := any{ptr, info.pointer.id}
 
-		if verb == 'p' {
-			fmt_pointer(fi, ptr, 'p')
-		} else if ptr == nil {
-			io.write_string(fi.writer, "[]", &fi.n)
-		} else {
-			len_ptr := uintptr(v.data) + uintptr(info.base_integer.size)
-			len_any := any{rawptr(len_ptr), info.base_integer.id}
-			len, _ := reflect.as_int(len_any)
-			slice_type := reflect.type_info_base(info.slice).variant.(runtime.Type_Info_Slice)
-
-			fi.record_level += 1
-			defer fi.record_level -= 1
-
-			io.write_byte(fi.writer, '[', &fi.n)
-			defer io.write_byte(fi.writer, ']', &fi.n)
-
-			for i in 0..<len {
-				if i > 0 { io.write_string(fi.writer, ", ", &fi.n) }
-
-				data := uintptr(ptr) + uintptr(i*slice_type.elem_size)
-				fmt_arg(fi, any{rawptr(data), slice_type.elem.id}, verb)
-			}
-		}
+		fmt_value(fi, absolute_ptr, verb)
 
 	case runtime.Type_Info_Matrix:
 		fmt_matrix(fi, v, verb, info)

+ 8 - 7
core/image/netpbm/helpers.odin

@@ -4,13 +4,14 @@ import "core:bytes"
 import "core:image"
 
 destroy :: proc(img: ^image.Image) -> bool {
-	if img == nil do return false
+	if img == nil {
+		return false
+	}
 
 	defer free(img)
 	bytes.buffer_destroy(&img.pixels)
 
-	info, ok := img.metadata.(^image.Netpbm_Info)
-	if !ok do return false
+	info := img.metadata.(^image.Netpbm_Info) or_return
 
 	header_destroy(&info.header)
 	free(info)
@@ -19,9 +20,9 @@ destroy :: proc(img: ^image.Image) -> bool {
 	return true
 }
 
-header_destroy :: proc(using header: ^Header) {
-	if format == .P7 && tupltype != "" {
-		delete(tupltype)
-		tupltype = ""
+header_destroy :: proc(header: ^Header) {
+	if header.format == .P7 && header.tupltype != "" {
+		delete(header.tupltype)
+		header.tupltype = ""
 	}
 }

+ 1 - 0
core/image/netpbm/netpbm.odin

@@ -1,3 +1,4 @@
+//+vet !using-stmt
 package netpbm
 
 import "core:bytes"

+ 3 - 4
core/image/png/helpers.odin

@@ -80,11 +80,10 @@ time :: proc(c: image.PNG_Chunk) -> (res: tIME, ok: bool) {
 }
 
 core_time :: proc(c: image.PNG_Chunk) -> (t: coretime.Time, ok: bool) {
-	if png_time, png_ok := time(c); png_ok {
-		using png_time
+	if t, png_ok := time(c); png_ok {
 		return coretime.datetime_to_time(
-			int(year), int(month), int(day),
-			int(hour), int(minute), int(second),
+			int(t.year), int(t.month),  int(t.day),
+			int(t.hour), int(t.minute), int(t.second),
 		)
 	} else {
 		return {}, false

+ 8 - 8
core/image/png/png.odin

@@ -11,6 +11,7 @@
 // package png implements a PNG image reader
 //
 // The PNG specification is at https://www.w3.org/TR/PNG/.
+//+vet !using-stmt
 package png
 
 import "core:compress"
@@ -444,15 +445,14 @@ load_from_context :: proc(ctx: ^$C, options := Options{}, allocator := context.a
 			img.width  = int(header.width)
 			img.height = int(header.height)
 
-			using header
 			h := image.PNG_IHDR{
-				width              = width,
-				height             = height,
-				bit_depth          = bit_depth,
-				color_type         = color_type,
-				compression_method = compression_method,
-				filter_method      = filter_method,
-				interlace_method   = interlace_method,
+				width              = header.width,
+				height             = header.height,
+				bit_depth          = header.bit_depth,
+				color_type         = header.color_type,
+				compression_method = header.compression_method,
+				filter_method      = header.filter_method,
+				interlace_method   = header.interlace_method,
 			}
 			info.header = h
 

+ 7 - 5
core/math/fixed/fixed.odin

@@ -29,13 +29,13 @@ Fixed52_12 :: distinct Fixed(i64, 12)
 
 
 init_from_f64 :: proc(x: ^$T/Fixed($Backing, $Fraction_Width), val: f64) {
-	i, f := math.modf(val)
+	i, f := math.modf(math.abs(val))
 	x.i  = Backing(f * (1<<Fraction_Width))
 	x.i &= 1<<Fraction_Width - 1
 	x.i |= Backing(i) << Fraction_Width
+	if val < 0 do x.i *= -1
 }
 
-
 init_from_parts :: proc(x: ^$T/Fixed($Backing, $Fraction_Width), integer, fraction: Backing) {
 	i, f := math.modf(val)
 	x.i  = fraction
@@ -44,9 +44,11 @@ init_from_parts :: proc(x: ^$T/Fixed($Backing, $Fraction_Width), integer, fracti
 }
 
 to_f64 :: proc(x: $T/Fixed($Backing, $Fraction_Width)) -> f64 {
-	res := f64(x.i >> Fraction_Width)
-	res += f64(x.i & (1<<Fraction_Width-1)) / f64(1<<Fraction_Width)
-	return res
+	sign := -1.0 if x.i < 0 else 1.0
+	num := math.abs(x.i)
+	res := f64(num >> Fraction_Width)
+	res += f64(num & (1<<Fraction_Width-1)) / f64(1<<Fraction_Width)
+	return res * sign
 }
 
 

+ 12 - 12
core/math/math.odin

@@ -2286,20 +2286,20 @@ F64_MASK  :: 0x7ff
 F64_SHIFT :: 64 - 12
 F64_BIAS  :: 0x3ff
 
-INF_F16     :f16: 0h7C00
-NEG_INF_F16 :f16: 0hFC00
+INF_F16     :: f16(0h7C00)
+NEG_INF_F16 :: f16(0hFC00)
 
-SNAN_F16    :f16: 0h7C01
-QNAN_F16    :f16: 0h7E01
+SNAN_F16    :: f16(0h7C01)
+QNAN_F16    :: f16(0h7E01)
 
-INF_F32     :f32: 0h7F80_0000
-NEG_INF_F32 :f32: 0hFF80_0000
+INF_F32     :: f32(0h7F80_0000)
+NEG_INF_F32 :: f32(0hFF80_0000)
 
-SNAN_F32    :f32: 0hFF80_0001
-QNAN_F32    :f32: 0hFFC0_0001
+SNAN_F32    :: f32(0hFF80_0001)
+QNAN_F32    :: f32(0hFFC0_0001)
 
-INF_F64     :f64: 0h7FF0_0000_0000_0000
-NEG_INF_F64 :f64: 0hFFF0_0000_0000_0000
+INF_F64     :: f64(0h7FF0_0000_0000_0000)
+NEG_INF_F64 :: f64(0hFFF0_0000_0000_0000)
 
-SNAN_F64    :f64: 0h7FF0_0000_0000_0001
-QNAN_F64    :f64: 0h7FF8_0000_0000_0001
+SNAN_F64    :: f64(0h7FF0_0000_0000_0001)
+QNAN_F64    :: f64(0h7FF8_0000_0000_0001)

+ 52 - 52
core/mem/allocators.odin

@@ -111,11 +111,11 @@ begin_arena_temp_memory :: proc(a: ^Arena) -> Arena_Temp_Memory {
 	return tmp
 }
 
-end_arena_temp_memory :: proc(using tmp: Arena_Temp_Memory) {
-	assert(arena.offset >= prev_offset)
-	assert(arena.temp_count > 0)
-	arena.offset = prev_offset
-	arena.temp_count -= 1
+end_arena_temp_memory :: proc(tmp: Arena_Temp_Memory) {
+	assert(tmp.arena.offset >= tmp.prev_offset)
+	assert(tmp.arena.temp_count > 0)
+	tmp.arena.offset = tmp.prev_offset
+	tmp.arena.temp_count -= 1
 }
 
 
@@ -702,11 +702,11 @@ dynamic_pool_init :: proc(pool: ^Dynamic_Pool,
 	pool.         used_blocks.allocator = array_allocator
 }
 
-dynamic_pool_destroy :: proc(using pool: ^Dynamic_Pool) {
+dynamic_pool_destroy :: proc(pool: ^Dynamic_Pool) {
 	dynamic_pool_free_all(pool)
-	delete(unused_blocks)
-	delete(used_blocks)
-	delete(out_band_allocations)
+	delete(pool.unused_blocks)
+	delete(pool.used_blocks)
+	delete(pool.out_band_allocations)
 
 	zero(pool, size_of(pool^))
 }
@@ -719,90 +719,90 @@ dynamic_pool_alloc :: proc(pool: ^Dynamic_Pool, bytes: int) -> (rawptr, Allocato
 }
 
 @(require_results)
-dynamic_pool_alloc_bytes :: proc(using pool: ^Dynamic_Pool, bytes: int) -> ([]byte, Allocator_Error) {
-	cycle_new_block :: proc(using pool: ^Dynamic_Pool) -> (err: Allocator_Error) {
-		if block_allocator.procedure == nil {
+dynamic_pool_alloc_bytes :: proc(p: ^Dynamic_Pool, bytes: int) -> ([]byte, Allocator_Error) {
+	cycle_new_block :: proc(p: ^Dynamic_Pool) -> (err: Allocator_Error) {
+		if p.block_allocator.procedure == nil {
 			panic("You must call pool_init on a Pool before using it")
 		}
 
-		if current_block != nil {
-			append(&used_blocks, current_block)
+		if p.current_block != nil {
+			append(&p.used_blocks, p.current_block)
 		}
 
 		new_block: rawptr
-		if len(unused_blocks) > 0 {
-			new_block = pop(&unused_blocks)
+		if len(p.unused_blocks) > 0 {
+			new_block = pop(&p.unused_blocks)
 		} else {
 			data: []byte
-			data, err = block_allocator.procedure(block_allocator.data, Allocator_Mode.Alloc,
-			                                           block_size, alignment,
-			                                           nil, 0)
+			data, err = p.block_allocator.procedure(p.block_allocator.data, Allocator_Mode.Alloc,
+			                                        p.block_size, p.alignment,
+			                                        nil, 0)
 			new_block = raw_data(data)
 		}
 
-		bytes_left = block_size
-		current_pos = new_block
-		current_block = new_block
+		p.bytes_left    = p.block_size
+		p.current_pos   = new_block
+		p.current_block = new_block
 		return
 	}
 
 	n := bytes
-	extra := alignment - (n % alignment)
+	extra := p.alignment - (n % p.alignment)
 	n += extra
-	if n >= out_band_size {
-		assert(block_allocator.procedure != nil)
-		memory, err := block_allocator.procedure(block_allocator.data, Allocator_Mode.Alloc,
-			                                block_size, alignment,
-			                                nil, 0)
+	if n >= p.out_band_size {
+		assert(p.block_allocator.procedure != nil)
+		memory, err := p.block_allocator.procedure(p.block_allocator.data, Allocator_Mode.Alloc,
+		                                           p.block_size, p.alignment,
+		                                           nil, 0)
 		if memory != nil {
-			append(&out_band_allocations, raw_data(memory))
+			append(&p.out_band_allocations, raw_data(memory))
 		}
 		return memory, err
 	}
 
-	if bytes_left < n {
-		err := cycle_new_block(pool)
+	if p.bytes_left < n {
+		err := cycle_new_block(p)
 		if err != nil {
 			return nil, err
 		}
-		if current_block == nil {
+		if p.current_block == nil {
 			return nil, .Out_Of_Memory
 		}
 	}
 
-	memory := current_pos
-	current_pos = ptr_offset((^byte)(current_pos), n)
-	bytes_left -= n
-	return byte_slice(memory, bytes), nil
+	memory := p.current_pos
+	p.current_pos = ([^]byte)(p.current_pos)[n:]
+	p.bytes_left -= n
+	return ([^]byte)(memory)[:bytes], nil
 }
 
 
-dynamic_pool_reset :: proc(using pool: ^Dynamic_Pool) {
-	if current_block != nil {
-		append(&unused_blocks, current_block)
-		current_block = nil
+dynamic_pool_reset :: proc(p: ^Dynamic_Pool) {
+	if p.current_block != nil {
+		append(&p.unused_blocks, p.current_block)
+		p.current_block = nil
 	}
 
-	for block in used_blocks {
-		append(&unused_blocks, block)
+	for block in p.used_blocks {
+		append(&p.unused_blocks, block)
 	}
-	clear(&used_blocks)
+	clear(&p.used_blocks)
 
-	for a in out_band_allocations {
-		free(a, block_allocator)
+	for a in p.out_band_allocations {
+		free(a, p.block_allocator)
 	}
-	clear(&out_band_allocations)
+	clear(&p.out_band_allocations)
 
-	bytes_left = 0 // Make new allocations call `cycle_new_block` again.
+	p.bytes_left = 0 // Make new allocations call `cycle_new_block` again.
 }
 
-dynamic_pool_free_all :: proc(using pool: ^Dynamic_Pool) {
-	dynamic_pool_reset(pool)
+dynamic_pool_free_all :: proc(p: ^Dynamic_Pool) {
+	dynamic_pool_reset(p)
 
-	for block in unused_blocks {
-		free(block, block_allocator)
+	for block in p.unused_blocks {
+		free(block, p.block_allocator)
 	}
-	clear(&unused_blocks)
+	clear(&p.unused_blocks)
 }
 
 

+ 35 - 35
core/net/url.odin

@@ -63,100 +63,100 @@ split_url :: proc(url: string, allocator := context.allocator) -> (scheme, host,
 }
 
 join_url :: proc(scheme, host, path: string, queries: map[string]string, allocator := context.allocator) -> string {
-	using strings
+	b := strings.builder_make(allocator)
+	strings.builder_grow(&b, len(scheme) + 3 + len(host) + 1 + len(path))
 
-	b := builder_make(allocator)
-	builder_grow(&b, len(scheme) + 3 + len(host) + 1 + len(path))
-
-	write_string(&b, scheme)
-	write_string(&b, "://")
-	write_string(&b, trim_space(host))
+	strings.write_string(&b, scheme)
+	strings.write_string(&b, "://")
+	strings.write_string(&b, strings.trim_space(host))
 
 	if path != "" {
-		if path[0] != '/' do write_string(&b, "/")
-		write_string(&b, trim_space(path))
+		if path[0] != '/' {
+			strings.write_string(&b, "/")
+		}
+		strings.write_string(&b, strings.trim_space(path))
 	}
 
 
 	query_length := len(queries)
-	if query_length > 0 do write_string(&b, "?")
+	if query_length > 0 {
+		strings.write_string(&b, "?")
+	}
 	i := 0
 	for query_name, query_value in queries {
-		write_string(&b, query_name)
+		strings.write_string(&b, query_name)
 		if query_value != "" {
-			write_string(&b, "=")
-			write_string(&b, query_value)
+			strings.write_string(&b, "=")
+			strings.write_string(&b, query_value)
 		}
 		if i < query_length - 1 {
-			write_string(&b, "&")
+			strings.write_string(&b, "&")
 		}
 		i += 1
 	}
 
-	return to_string(b)
+	return strings.to_string(b)
 }
 
 percent_encode :: proc(s: string, allocator := context.allocator) -> string {
-	using strings
-
-	b := builder_make(allocator)
-	builder_grow(&b, len(s) + 16) // NOTE(tetra): A reasonable number to allow for the number of things we need to escape.
+	b := strings.builder_make(allocator)
+	strings.builder_grow(&b, len(s) + 16) // NOTE(tetra): A reasonable number to allow for the number of things we need to escape.
 
 	for ch in s {
 		switch ch {
 		case 'A'..='Z', 'a'..='z', '0'..='9', '-', '_', '.', '~':
-			write_rune(&b, ch)
+			strings.write_rune(&b, ch)
 		case:
 			bytes, n := utf8.encode_rune(ch)
 			for byte in bytes[:n] {
 				buf: [2]u8 = ---
 				t := strconv.append_int(buf[:], i64(byte), 16)
-				write_rune(&b, '%')
-				write_string(&b, t)
+				strings.write_rune(&b, '%')
+				strings.write_string(&b, t)
 			}
 		}
 	}
 
-	return to_string(b)
+	return strings.to_string(b)
 }
 
 percent_decode :: proc(encoded_string: string, allocator := context.allocator) -> (decoded_string: string, ok: bool) {
-	using strings
-
-	b := builder_make(allocator)
-	builder_grow(&b, len(encoded_string))
-	defer if !ok do builder_destroy(&b)
+	b := strings.builder_make(allocator)
+	strings.builder_grow(&b, len(encoded_string))
+	defer if !ok do strings.builder_destroy(&b)
 
 	s := encoded_string
 
 	for len(s) > 0 {
-		i := index_byte(s, '%')
+		i := strings.index_byte(s, '%')
 		if i == -1 {
-			write_string(&b, s) // no '%'s; the string is already decoded
+			strings.write_string(&b, s) // no '%'s; the string is already decoded
 			break
 		}
 
-		write_string(&b, s[:i])
+		strings.write_string(&b, s[:i])
 		s = s[i:]
 
 		if len(s) == 0 do return // percent without anything after it
 		s = s[1:]
 
 		if s[0] == '%' {
-			write_byte(&b, '%')
+			strings.write_byte(&b, '%')
 			s = s[1:]
 			continue
 		}
 
-		if len(s) < 2 do return // percent without encoded value
+		if len(s) < 2 {
+			return // percent without encoded value
+		}
 
 		val := hex.decode_sequence(s[:2]) or_return
-		write_byte(&b, val)
+		strings.write_byte(&b, val)
 		s = s[2:]
 	}
 
 	ok = true
-	decoded_string = to_string(b)
+	decoded_string = strings.to_string(b)
 	return
 }
 

+ 25 - 25
core/odin/doc-format/doc_format.odin

@@ -162,31 +162,31 @@ Attribute :: struct {
 }
 
 Type_Kind :: enum u32le {
-	Invalid            = 0,
-	Basic              = 1,
-	Named              = 2,
-	Generic            = 3,
-	Pointer            = 4,
-	Array              = 5,
-	Enumerated_Array   = 6,
-	Slice              = 7,
-	Dynamic_Array      = 8,
-	Map                = 9,
-	Struct             = 10,
-	Union              = 11,
-	Enum               = 12,
-	Tuple              = 13,
-	Proc               = 14,
-	Bit_Set            = 15,
-	Simd_Vector        = 16,
-	SOA_Struct_Fixed   = 17,
-	SOA_Struct_Slice   = 18,
-	SOA_Struct_Dynamic = 19,
-	Relative_Pointer   = 20,
-	Relative_Slice     = 21,
-	Multi_Pointer      = 22,
-	Matrix             = 23,
-	Soa_Pointer        = 24,
+	Invalid                = 0,
+	Basic                  = 1,
+	Named                  = 2,
+	Generic                = 3,
+	Pointer                = 4,
+	Array                  = 5,
+	Enumerated_Array       = 6,
+	Slice                  = 7,
+	Dynamic_Array          = 8,
+	Map                    = 9,
+	Struct                 = 10,
+	Union                  = 11,
+	Enum                   = 12,
+	Tuple                  = 13,
+	Proc                   = 14,
+	Bit_Set                = 15,
+	Simd_Vector            = 16,
+	SOA_Struct_Fixed       = 17,
+	SOA_Struct_Slice       = 18,
+	SOA_Struct_Dynamic     = 19,
+	Relative_Pointer       = 20,
+	Relative_Multi_Pointer = 21,
+	Multi_Pointer          = 22,
+	Matrix                 = 23,
+	Soa_Pointer            = 24,
 }
 
 Type_Elems_Cap :: 4

+ 1 - 1
core/odin/parser/parser.odin

@@ -2943,7 +2943,7 @@ parse_call_expr :: proc(p: ^Parser, operand: ^ast.Expr) -> ^ast.Expr {
 
 	seen_ellipsis := false
 	for p.curr_tok.kind != .Close_Paren &&
-	    p.curr_tok.kind != .EOF {
+		p.curr_tok.kind != .EOF {
 
 		if p.curr_tok.kind == .Comma {
 			error(p, p.curr_tok.pos, "expected an expression not ,")

+ 86 - 92
core/odin/printer/visit.odin

@@ -336,22 +336,20 @@ hint_current_line :: proc(p: ^Printer, hint: Line_Type) {
 
 @(private)
 visit_decl :: proc(p: ^Printer, decl: ^ast.Decl, called_in_stmt := false) {
-	using ast
-
 	if decl == nil {
 		return
 	}
 
 	#partial switch v in decl.derived_stmt {
-	case ^Expr_Stmt:
+	case ^ast.Expr_Stmt:
 		move_line(p, decl.pos)
 		visit_expr(p, v.expr)
 		if p.config.semicolons {
 			push_generic_token(p, .Semicolon, 0)
 		}
-	case ^When_Stmt:
-		visit_stmt(p, cast(^Stmt)decl)
-	case ^Foreign_Import_Decl:
+	case ^ast.When_Stmt:
+		visit_stmt(p, cast(^ast.Stmt)decl)
+	case ^ast.Foreign_Import_Decl:
 		if len(v.attributes) > 0 {
 			sort.sort(sort_attribute(&v.attributes))
 			move_line(p, v.attributes[0].pos)
@@ -370,7 +368,7 @@ visit_decl :: proc(p: ^Printer, decl: ^ast.Decl, called_in_stmt := false) {
 		for path in v.fullpaths {
 			push_ident_token(p, path, 0)
 		}
-	case ^Foreign_Block_Decl:
+	case ^ast.Foreign_Block_Decl:
 		if len(v.attributes) > 0 {
 			sort.sort(sort_attribute(&v.attributes))
 			move_line(p, v.attributes[0].pos)
@@ -383,7 +381,7 @@ visit_decl :: proc(p: ^Printer, decl: ^ast.Decl, called_in_stmt := false) {
 
 		visit_expr(p, v.foreign_library)
 		visit_stmt(p, v.body)
-	case ^Import_Decl:
+	case ^ast.Import_Decl:
 		move_line(p, decl.pos)
 
 		if v.name.text != "" {
@@ -395,7 +393,7 @@ visit_decl :: proc(p: ^Printer, decl: ^ast.Decl, called_in_stmt := false) {
 			push_ident_token(p, v.fullpath, 1)
 		}
 
-	case ^Value_Decl:
+	case ^ast.Value_Decl:
 		if len(v.attributes) > 0 {
 			sort.sort(sort_attribute(&v.attributes))
 			move_line(p, v.attributes[0].pos)
@@ -447,9 +445,9 @@ visit_decl :: proc(p: ^Printer, decl: ^ast.Decl, called_in_stmt := false) {
 
 		for value in v.values {
 			#partial switch a in value.derived {
-			case ^Union_Type, ^Enum_Type, ^Struct_Type:
+			case ^ast.Union_Type, ^ast.Enum_Type, ^ast.Struct_Type:
 				add_semicolon = false || called_in_stmt
-			case ^Proc_Lit:
+			case ^ast.Proc_Lit:
 				add_semicolon = false
 			}
 		}
@@ -510,40 +508,38 @@ visit_attributes :: proc(p: ^Printer, attributes: [dynamic]^ast.Attribute) {
 
 @(private)
 visit_stmt :: proc(p: ^Printer, stmt: ^ast.Stmt, block_type: Block_Type = .Generic, empty_block := false, block_stmt := false) {
-	using ast
-
 	if stmt == nil {
 		return
 	}
 
 
 	switch v in stmt.derived_stmt {
-	case ^Bad_Stmt:
-	case ^Bad_Decl:
-	case ^Package_Decl:
+	case ^ast.Bad_Stmt:
+	case ^ast.Bad_Decl:
+	case ^ast.Package_Decl:
 
-	case ^Empty_Stmt:
+	case ^ast.Empty_Stmt:
 		push_generic_token(p, .Semicolon, 0)
-	case ^Tag_Stmt:
+	case ^ast.Tag_Stmt:
 		push_generic_token(p, .Hash, 1)
 		push_generic_token(p, v.op.kind, 1, v.op.text)
 		visit_stmt(p, v.stmt)
 
 
-	case ^Import_Decl:
-		visit_decl(p, cast(^Decl)stmt, true)
+	case ^ast.Import_Decl:
+		visit_decl(p, cast(^ast.Decl)stmt, true)
 		return
-	case ^Value_Decl:
-		visit_decl(p, cast(^Decl)stmt, true)
+	case ^ast.Value_Decl:
+		visit_decl(p, cast(^ast.Decl)stmt, true)
 		return
-	case ^Foreign_Import_Decl:
-		visit_decl(p, cast(^Decl)stmt, true)
+	case ^ast.Foreign_Import_Decl:
+		visit_decl(p, cast(^ast.Decl)stmt, true)
 		return
-	case ^Foreign_Block_Decl:
-		visit_decl(p, cast(^Decl)stmt, true)
+	case ^ast.Foreign_Block_Decl:
+		visit_decl(p, cast(^ast.Decl)stmt, true)
 		return
 
-	case ^Using_Stmt:
+	case ^ast.Using_Stmt:
 		move_line(p, v.pos)
 
 		push_generic_token(p, .Using, 1)
@@ -553,7 +549,7 @@ visit_stmt :: proc(p: ^Printer, stmt: ^ast.Stmt, block_type: Block_Type = .Gener
 		if p.config.semicolons {
 			push_generic_token(p, .Semicolon, 0)
 		}
-	case ^Block_Stmt:
+	case ^ast.Block_Stmt:
 		move_line(p, v.pos)
 
 		if v.pos.line == v.end.line {
@@ -583,7 +579,7 @@ visit_stmt :: proc(p: ^Printer, stmt: ^ast.Stmt, block_type: Block_Type = .Gener
 				visit_end_brace(p, v.end)
 			}
 		}
-	case ^If_Stmt:
+	case ^ast.If_Stmt:
 		move_line(p, v.pos)
 
 		if v.label != nil {
@@ -606,7 +602,7 @@ visit_stmt :: proc(p: ^Printer, stmt: ^ast.Stmt, block_type: Block_Type = .Gener
 
 		uses_do := false
 
-		if check_stmt, ok := v.body.derived.(^Block_Stmt); ok && check_stmt.uses_do {
+		if check_stmt, ok := v.body.derived.(^ast.Block_Stmt); ok && check_stmt.uses_do {
 			uses_do = true
 		}
 
@@ -637,7 +633,7 @@ visit_stmt :: proc(p: ^Printer, stmt: ^ast.Stmt, block_type: Block_Type = .Gener
 
 			visit_stmt(p, v.else_stmt)
 		}
-	case ^Switch_Stmt:
+	case ^ast.Switch_Stmt:
 		move_line(p, v.pos)
 
 		if v.label != nil {
@@ -665,7 +661,7 @@ visit_stmt :: proc(p: ^Printer, stmt: ^ast.Stmt, block_type: Block_Type = .Gener
 
 		visit_expr(p, v.cond)
 		visit_stmt(p, v.body)
-	case ^Case_Clause:
+	case ^ast.Case_Clause:
 		move_line(p, v.pos)
 
 		if !p.config.indent_cases {
@@ -689,7 +685,7 @@ visit_stmt :: proc(p: ^Printer, stmt: ^ast.Stmt, block_type: Block_Type = .Gener
 		if !p.config.indent_cases {
 			indent(p)
 		}
-	case ^Type_Switch_Stmt:
+	case ^ast.Type_Switch_Stmt:
 		move_line(p, v.pos)
 
 		hint_current_line(p, {.Switch_Stmt})
@@ -707,7 +703,7 @@ visit_stmt :: proc(p: ^Printer, stmt: ^ast.Stmt, block_type: Block_Type = .Gener
 
 		visit_stmt(p, v.tag)
 		visit_stmt(p, v.body)
-	case ^Assign_Stmt:
+	case ^ast.Assign_Stmt:
 		move_line(p, v.pos)
 
 		hint_current_line(p, {.Assign})
@@ -721,13 +717,13 @@ visit_stmt :: proc(p: ^Printer, stmt: ^ast.Stmt, block_type: Block_Type = .Gener
 		if block_stmt && p.config.semicolons {
 			push_generic_token(p, .Semicolon, 0)
 		}
-	case ^Expr_Stmt:
+	case ^ast.Expr_Stmt:
 		move_line(p, v.pos)
 		visit_expr(p, v.expr)
 		if block_stmt && p.config.semicolons {
 			push_generic_token(p, .Semicolon, 0)
 		}
-	case ^For_Stmt:
+	case ^ast.For_Stmt:
 		// this should be simplified
 		move_line(p, v.pos)
 
@@ -764,7 +760,7 @@ visit_stmt :: proc(p: ^Printer, stmt: ^ast.Stmt, block_type: Block_Type = .Gener
 
 		visit_stmt(p, v.body)
 
-	case ^Inline_Range_Stmt:
+	case ^ast.Inline_Range_Stmt:
 		move_line(p, v.pos)
 
 		if v.label != nil {
@@ -790,7 +786,7 @@ visit_stmt :: proc(p: ^Printer, stmt: ^ast.Stmt, block_type: Block_Type = .Gener
 		visit_expr(p, v.expr)
 		visit_stmt(p, v.body)
 
-	case ^Range_Stmt:
+	case ^ast.Range_Stmt:
 		move_line(p, v.pos)
 
 		if v.label != nil {
@@ -816,7 +812,7 @@ visit_stmt :: proc(p: ^Printer, stmt: ^ast.Stmt, block_type: Block_Type = .Gener
 		visit_expr(p, v.expr)
 
 		visit_stmt(p, v.body)
-	case ^Return_Stmt:
+	case ^ast.Return_Stmt:
 		move_line(p, v.pos)
 
 		push_generic_token(p, .Return, 1)
@@ -828,7 +824,7 @@ visit_stmt :: proc(p: ^Printer, stmt: ^ast.Stmt, block_type: Block_Type = .Gener
 		if block_stmt && p.config.semicolons {
 			push_generic_token(p, .Semicolon, 0)
 		}
-	case ^Defer_Stmt:
+	case ^ast.Defer_Stmt:
 		move_line(p, v.pos)
 		push_generic_token(p, .Defer, 0)
 
@@ -837,7 +833,7 @@ visit_stmt :: proc(p: ^Printer, stmt: ^ast.Stmt, block_type: Block_Type = .Gener
 		if p.config.semicolons {
 			push_generic_token(p, .Semicolon, 0)
 		}
-	case ^When_Stmt:
+	case ^ast.When_Stmt:
 		move_line(p, v.pos)
 		push_generic_token(p, .When, 1)
 		visit_expr(p, v.cond)
@@ -857,7 +853,7 @@ visit_stmt :: proc(p: ^Printer, stmt: ^ast.Stmt, block_type: Block_Type = .Gener
 			visit_stmt(p, v.else_stmt)
 		}
 
-	case ^Branch_Stmt:
+	case ^ast.Branch_Stmt:
 		move_line(p, v.pos)
 
 		push_generic_token(p, v.tok.kind, 0)
@@ -921,8 +917,6 @@ push_poly_params :: proc(p: ^Printer, poly_params: ^ast.Field_List) {
 
 @(private)
 visit_expr :: proc(p: ^Printer, expr: ^ast.Expr, options := List_Options{}) {
-	using ast
-
 	if expr == nil {
 		return
 	}
@@ -930,14 +924,14 @@ visit_expr :: proc(p: ^Printer, expr: ^ast.Expr, options := List_Options{}) {
 	set_source_position(p, expr.pos)
 
 	switch v in expr.derived_expr {
-	case ^Bad_Expr:
+	case ^ast.Bad_Expr:
 
-	case ^Tag_Expr:
+	case ^ast.Tag_Expr:
 		push_generic_token(p, .Hash, 1)
 		push_generic_token(p, v.op.kind, 1, v.op.text)
 		visit_expr(p, v.expr)
 
-	case ^Inline_Asm_Expr:
+	case ^ast.Inline_Asm_Expr:
 		push_generic_token(p, v.tok.kind, 1, v.tok.text)
 
 		push_generic_token(p, .Open_Paren, 1)
@@ -954,42 +948,42 @@ visit_expr :: proc(p: ^Printer, expr: ^ast.Expr, options := List_Options{}) {
 		push_generic_token(p, .Comma, 0)
 		visit_expr(p, v.constraints_string)
 		push_generic_token(p, .Close_Brace, 0)
-	case ^Undef:
+	case ^ast.Undef:
 		push_generic_token(p, .Undef, 1)
-	case ^Auto_Cast:
+	case ^ast.Auto_Cast:
 		push_generic_token(p, v.op.kind, 1)
 		visit_expr(p, v.expr)
-	case ^Ternary_If_Expr:
+	case ^ast.Ternary_If_Expr:
 		visit_expr(p, v.x)
 		push_generic_token(p, v.op1.kind, 1)
 		visit_expr(p, v.cond)
 		push_generic_token(p, v.op2.kind, 1)
 		visit_expr(p, v.y)
-	case ^Ternary_When_Expr:
+	case ^ast.Ternary_When_Expr:
 		visit_expr(p, v.x)
 		push_generic_token(p, v.op1.kind, 1)
 		visit_expr(p, v.cond)
 		push_generic_token(p, v.op2.kind, 1)
 		visit_expr(p, v.y)
-	case ^Or_Else_Expr:
+	case ^ast.Or_Else_Expr:
 		visit_expr(p, v.x)
 		push_generic_token(p, v.token.kind, 1)
 		visit_expr(p, v.y)
-	case ^Or_Return_Expr:
+	case ^ast.Or_Return_Expr:
 		visit_expr(p, v.expr)
 		push_generic_token(p, v.token.kind, 1)
-	case ^Selector_Call_Expr:
+	case ^ast.Selector_Call_Expr:
 		visit_expr(p, v.call.expr)
 		push_generic_token(p, .Open_Paren, 1)
 		visit_exprs(p, v.call.args, {.Add_Comma})
 		push_generic_token(p, .Close_Paren, 0)
-	case ^Ellipsis:
+	case ^ast.Ellipsis:
 		push_generic_token(p, .Ellipsis, 1)
 		visit_expr(p, v.expr)
-	case ^Relative_Type:
+	case ^ast.Relative_Type:
 		visit_expr(p, v.tag)
 		visit_expr(p, v.type)
-	case ^Slice_Expr:
+	case ^ast.Slice_Expr:
 		visit_expr(p, v.expr)
 		push_generic_token(p, .Open_Bracket, 0)
 		visit_expr(p, v.low)
@@ -999,37 +993,37 @@ visit_expr :: proc(p: ^Printer, expr: ^ast.Expr, options := List_Options{}) {
 			visit_expr(p, v.high)
 		}
 		push_generic_token(p, .Close_Bracket, 0)
-	case ^Ident:
+	case ^ast.Ident:
 		if .Enforce_Poly_Names in options {
 			push_generic_token(p, .Dollar, 1)
 			push_ident_token(p, v.name, 0)
 		} else {
 			push_ident_token(p, v.name, 1)
 		}
-	case ^Deref_Expr:
+	case ^ast.Deref_Expr:
 		visit_expr(p, v.expr)
 		push_generic_token(p, v.op.kind, 0)
-	case ^Type_Cast:
+	case ^ast.Type_Cast:
 		push_generic_token(p, v.tok.kind, 1)
 		push_generic_token(p, .Open_Paren, 0)
 		visit_expr(p, v.type)
 		push_generic_token(p, .Close_Paren, 0)
 		merge_next_token(p)
 		visit_expr(p, v.expr)
-	case ^Basic_Directive:
+	case ^ast.Basic_Directive:
 		push_generic_token(p, v.tok.kind, 1)
 		push_ident_token(p, v.name, 0)
-	case ^Distinct_Type:
+	case ^ast.Distinct_Type:
 		push_generic_token(p, .Distinct, 1)
 		visit_expr(p, v.type)
-	case ^Dynamic_Array_Type:
+	case ^ast.Dynamic_Array_Type:
 		visit_expr(p, v.tag)
 		push_generic_token(p, .Open_Bracket, 1)
 		push_generic_token(p, .Dynamic, 0)
 		push_generic_token(p, .Close_Bracket, 0)
 		merge_next_token(p)
 		visit_expr(p, v.elem)
-	case ^Bit_Set_Type:
+	case ^ast.Bit_Set_Type:
 		push_generic_token(p, .Bit_Set, 1)
 		push_generic_token(p, .Open_Bracket, 0)
 
@@ -1041,7 +1035,7 @@ visit_expr :: proc(p: ^Printer, expr: ^ast.Expr, options := List_Options{}) {
 		}
 
 		push_generic_token(p, .Close_Bracket, 0)
-	case ^Union_Type:
+	case ^ast.Union_Type:
 		push_generic_token(p, .Union, 1)
 
 		push_poly_params(p, v.poly_params)
@@ -1066,7 +1060,7 @@ visit_expr :: proc(p: ^Printer, expr: ^ast.Expr, options := List_Options{}) {
 			visit_exprs(p, v.variants, {.Add_Comma, .Trailing})
 			visit_end_brace(p, v.end)
 		}
-	case ^Enum_Type:
+	case ^ast.Enum_Type:
 		push_generic_token(p, .Enum, 1)
 
 		hint_current_line(p, {.Enum})
@@ -1089,7 +1083,7 @@ visit_expr :: proc(p: ^Printer, expr: ^ast.Expr, options := List_Options{}) {
 		}
 
 		set_source_position(p, v.end)
-	case ^Struct_Type:
+	case ^ast.Struct_Type:
 		push_generic_token(p, .Struct, 1)
 
 		hint_current_line(p, {.Struct})
@@ -1124,7 +1118,7 @@ visit_expr :: proc(p: ^Printer, expr: ^ast.Expr, options := List_Options{}) {
 		}
 
 		set_source_position(p, v.end)
-	case ^Proc_Lit:
+	case ^ast.Proc_Lit:
 		switch v.inlining {
 		case .None:
 		case .Inline:
@@ -1143,16 +1137,16 @@ visit_expr :: proc(p: ^Printer, expr: ^ast.Expr, options := List_Options{}) {
 		} else {
 			push_generic_token(p, .Undef, 1)
 		}
-	case ^Proc_Type:
+	case ^ast.Proc_Type:
 		visit_proc_type(p, v)
-	case ^Basic_Lit:
+	case ^ast.Basic_Lit:
 		push_generic_token(p, v.tok.kind, 1, v.tok.text)
-	case ^Binary_Expr:
+	case ^ast.Binary_Expr:
 		visit_binary_expr(p, v)
-	case ^Implicit_Selector_Expr:
+	case ^ast.Implicit_Selector_Expr:
 		push_generic_token(p, .Period, 1)
 		push_ident_token(p, v.field.name, 0)
-	case ^Call_Expr:
+	case ^ast.Call_Expr:
 		visit_expr(p, v.expr)
 
 		push_format_token(p,
@@ -1167,34 +1161,34 @@ visit_expr :: proc(p: ^Printer, expr: ^ast.Expr, options := List_Options{}) {
 
 		visit_call_exprs(p, v.args, v.ellipsis.kind == .Ellipsis)
 		push_generic_token(p, .Close_Paren, 0)
-	case ^Typeid_Type:
+	case ^ast.Typeid_Type:
 		push_generic_token(p, .Typeid, 1)
 
 		if v.specialization != nil {
 			push_generic_token(p, .Quo, 0)
 			visit_expr(p, v.specialization)
 		}
-	case ^Selector_Expr:
+	case ^ast.Selector_Expr:
 		visit_expr(p, v.expr)
 		push_generic_token(p, v.op.kind, 0)
 		visit_expr(p, v.field)
-	case ^Paren_Expr:
+	case ^ast.Paren_Expr:
 		push_generic_token(p, .Open_Paren, 1)
 		visit_expr(p, v.expr)
 		push_generic_token(p, .Close_Paren, 0)
-	case ^Index_Expr:
+	case ^ast.Index_Expr:
 		visit_expr(p, v.expr)
 		push_generic_token(p, .Open_Bracket, 0)
 		visit_expr(p, v.index)
 		push_generic_token(p, .Close_Bracket, 0)
-	case ^Matrix_Index_Expr:
+	case ^ast.Matrix_Index_Expr:
 		visit_expr(p, v.expr)
 		push_generic_token(p, .Open_Bracket, 0)
 		visit_expr(p, v.row_index)
 		push_generic_token(p, .Comma, 0)
 		visit_expr(p, v.column_index)
 		push_generic_token(p, .Close_Bracket, 0)
-	case ^Proc_Group:
+	case ^ast.Proc_Group:
 		push_generic_token(p, v.tok.kind, 1)
 
 		if len(v.args) != 0 && v.pos.line != v.args[len(v.args) - 1].pos.line {
@@ -1209,7 +1203,7 @@ visit_expr :: proc(p: ^Printer, expr: ^ast.Expr, options := List_Options{}) {
 			push_generic_token(p, .Close_Brace, 0)
 		}
 
-	case ^Comp_Lit:
+	case ^ast.Comp_Lit:
 		if v.type != nil {
 			visit_expr(p, v.type)
 		}
@@ -1226,18 +1220,18 @@ visit_expr :: proc(p: ^Printer, expr: ^ast.Expr, options := List_Options{}) {
 			push_generic_token(p, .Close_Brace, 0)
 		}
 
-	case ^Unary_Expr:
+	case ^ast.Unary_Expr:
 		push_generic_token(p, v.op.kind, 1)
 		merge_next_token(p)
 		visit_expr(p, v.expr)
-	case ^Field_Value:
+	case ^ast.Field_Value:
 		visit_expr(p, v.field)
 		push_generic_token(p, .Eq, 1)
 		visit_expr(p, v.value)
-	case ^Type_Assertion:
+	case ^ast.Type_Assertion:
 		visit_expr(p, v.expr)
 
-		if unary, ok := v.type.derived.(^Unary_Expr); ok && unary.op.text == "?" {
+		if unary, ok := v.type.derived.(^ast.Unary_Expr); ok && unary.op.text == "?" {
 			push_generic_token(p, .Period, 0)
 			visit_expr(p, v.type)
 		} else {
@@ -1247,13 +1241,13 @@ visit_expr :: proc(p: ^Printer, expr: ^ast.Expr, options := List_Options{}) {
 			push_generic_token(p, .Close_Paren, 0)
 		}
 
-	case ^Pointer_Type:
+	case ^ast.Pointer_Type:
 		push_generic_token(p, .Pointer, 1)
 		merge_next_token(p)
 		visit_expr(p, v.elem)
-	case ^Implicit:
+	case ^ast.Implicit:
 		push_generic_token(p, v.tok.kind, 1)
-	case ^Poly_Type:
+	case ^ast.Poly_Type:
 		push_generic_token(p, .Dollar, 1)
 		merge_next_token(p)
 		visit_expr(p, v.type)
@@ -1263,28 +1257,28 @@ visit_expr :: proc(p: ^Printer, expr: ^ast.Expr, options := List_Options{}) {
 			merge_next_token(p)
 			visit_expr(p, v.specialization)
 		}
-	case ^Array_Type:
+	case ^ast.Array_Type:
 		visit_expr(p, v.tag)
 		push_generic_token(p, .Open_Bracket, 1)
 		visit_expr(p, v.len)
 		push_generic_token(p, .Close_Bracket, 0)
 		merge_next_token(p)
 		visit_expr(p, v.elem)
-	case ^Map_Type:
+	case ^ast.Map_Type:
 		push_generic_token(p, .Map, 1)
 		push_generic_token(p, .Open_Bracket, 0)
 		visit_expr(p, v.key)
 		push_generic_token(p, .Close_Bracket, 0)
 		merge_next_token(p)
 		visit_expr(p, v.value)
-	case ^Helper_Type:
+	case ^ast.Helper_Type:
 		visit_expr(p, v.type)
-	case ^Multi_Pointer_Type:
+	case ^ast.Multi_Pointer_Type:
 		push_generic_token(p, .Open_Bracket, 1)
 		push_generic_token(p, .Pointer, 0)
 		push_generic_token(p, .Close_Bracket, 0)
 		visit_expr(p, v.elem)
-	case ^Matrix_Type:
+	case ^ast.Matrix_Type:
 		push_generic_token(p, .Matrix, 1)
 		push_generic_token(p, .Open_Bracket, 0)
 		visit_expr(p, v.row_count)

+ 16 - 16
core/odin/tokenizer/tokenizer.odin

@@ -75,34 +75,34 @@ error :: proc(t: ^Tokenizer, offset: int, msg: string, args: ..any) {
 	t.error_count += 1
 }
 
-advance_rune :: proc(using t: ^Tokenizer) {
-	if read_offset < len(src) {
-		offset = read_offset
-		if ch == '\n' {
-			line_offset = offset
-			line_count += 1
+advance_rune :: proc(t: ^Tokenizer) {
+	if t.read_offset < len(t.src) {
+		t.offset = t.read_offset
+		if t.ch == '\n' {
+			t.line_offset = t.offset
+			t.line_count += 1
 		}
-		r, w := rune(src[read_offset]), 1
+		r, w := rune(t.src[t.read_offset]), 1
 		switch {
 		case r == 0:
 			error(t, t.offset, "illegal character NUL")
 		case r >= utf8.RUNE_SELF:
-			r, w = utf8.decode_rune_in_string(src[read_offset:])
+			r, w = utf8.decode_rune_in_string(t.src[t.read_offset:])
 			if r == utf8.RUNE_ERROR && w == 1 {
 				error(t, t.offset, "illegal UTF-8 encoding")
-			} else if r == utf8.RUNE_BOM && offset > 0 {
+			} else if r == utf8.RUNE_BOM && t.offset > 0 {
 				error(t, t.offset, "illegal byte order mark")
 			}
 		}
-		read_offset += w
-		ch = r
+		t.read_offset += w
+		t.ch = r
 	} else {
-		offset = len(src)
-		if ch == '\n' {
-			line_offset = offset
-			line_count += 1
+		t.offset = len(t.src)
+		if t.ch == '\n' {
+			t.line_offset = t.offset
+			t.line_count += 1
 		}
-		ch = -1
+		t.ch = -1
 	}
 }
 

+ 59 - 60
core/reflect/reflect.odin

@@ -8,35 +8,35 @@ _ :: intrinsics
 
 Type_Info :: runtime.Type_Info
 
-Type_Info_Named            :: runtime.Type_Info_Named
-Type_Info_Integer          :: runtime.Type_Info_Integer
-Type_Info_Rune             :: runtime.Type_Info_Rune
-Type_Info_Float            :: runtime.Type_Info_Float
-Type_Info_Complex          :: runtime.Type_Info_Complex
-Type_Info_Quaternion       :: runtime.Type_Info_Quaternion
-Type_Info_String           :: runtime.Type_Info_String
-Type_Info_Boolean          :: runtime.Type_Info_Boolean
-Type_Info_Any              :: runtime.Type_Info_Any
-Type_Info_Type_Id          :: runtime.Type_Info_Type_Id
-Type_Info_Pointer          :: runtime.Type_Info_Pointer
-Type_Info_Multi_Pointer    :: runtime.Type_Info_Multi_Pointer
-Type_Info_Procedure        :: runtime.Type_Info_Procedure
-Type_Info_Array            :: runtime.Type_Info_Array
-Type_Info_Enumerated_Array :: runtime.Type_Info_Enumerated_Array
-Type_Info_Dynamic_Array    :: runtime.Type_Info_Dynamic_Array
-Type_Info_Slice            :: runtime.Type_Info_Slice
-Type_Info_Parameters       :: runtime.Type_Info_Parameters
-Type_Info_Tuple            :: runtime.Type_Info_Parameters
-Type_Info_Struct           :: runtime.Type_Info_Struct
-Type_Info_Union            :: runtime.Type_Info_Union
-Type_Info_Enum             :: runtime.Type_Info_Enum
-Type_Info_Map              :: runtime.Type_Info_Map
-Type_Info_Bit_Set          :: runtime.Type_Info_Bit_Set
-Type_Info_Simd_Vector      :: runtime.Type_Info_Simd_Vector
-Type_Info_Relative_Pointer :: runtime.Type_Info_Relative_Pointer
-Type_Info_Relative_Slice   :: runtime.Type_Info_Relative_Slice
-Type_Info_Matrix           :: runtime.Type_Info_Matrix
-Type_Info_Soa_Pointer      :: runtime.Type_Info_Soa_Pointer
+Type_Info_Named                  :: runtime.Type_Info_Named
+Type_Info_Integer                :: runtime.Type_Info_Integer
+Type_Info_Rune                   :: runtime.Type_Info_Rune
+Type_Info_Float                  :: runtime.Type_Info_Float
+Type_Info_Complex                :: runtime.Type_Info_Complex
+Type_Info_Quaternion             :: runtime.Type_Info_Quaternion
+Type_Info_String                 :: runtime.Type_Info_String
+Type_Info_Boolean                :: runtime.Type_Info_Boolean
+Type_Info_Any                    :: runtime.Type_Info_Any
+Type_Info_Type_Id                :: runtime.Type_Info_Type_Id
+Type_Info_Pointer                :: runtime.Type_Info_Pointer
+Type_Info_Multi_Pointer          :: runtime.Type_Info_Multi_Pointer
+Type_Info_Procedure              :: runtime.Type_Info_Procedure
+Type_Info_Array                  :: runtime.Type_Info_Array
+Type_Info_Enumerated_Array       :: runtime.Type_Info_Enumerated_Array
+Type_Info_Dynamic_Array          :: runtime.Type_Info_Dynamic_Array
+Type_Info_Slice                  :: runtime.Type_Info_Slice
+Type_Info_Parameters             :: runtime.Type_Info_Parameters
+Type_Info_Tuple                  :: runtime.Type_Info_Parameters
+Type_Info_Struct                 :: runtime.Type_Info_Struct
+Type_Info_Union                  :: runtime.Type_Info_Union
+Type_Info_Enum                   :: runtime.Type_Info_Enum
+Type_Info_Map                    :: runtime.Type_Info_Map
+Type_Info_Bit_Set                :: runtime.Type_Info_Bit_Set
+Type_Info_Simd_Vector            :: runtime.Type_Info_Simd_Vector
+Type_Info_Relative_Pointer       :: runtime.Type_Info_Relative_Pointer
+Type_Info_Relative_Multi_Pointer :: runtime.Type_Info_Relative_Multi_Pointer
+Type_Info_Matrix                 :: runtime.Type_Info_Matrix
+Type_Info_Soa_Pointer            :: runtime.Type_Info_Soa_Pointer
 
 Type_Info_Enum_Value :: runtime.Type_Info_Enum_Value
 
@@ -69,7 +69,7 @@ Type_Kind :: enum {
 	Bit_Set,
 	Simd_Vector,
 	Relative_Pointer,
-	Relative_Slice,
+	Relative_Multi_Pointer,
 	Matrix,
 	Soa_Pointer,
 }
@@ -80,34 +80,34 @@ type_kind :: proc(T: typeid) -> Type_Kind {
 	ti := type_info_of(T)
 	if ti != nil {
 		switch _ in ti.variant {
-		case Type_Info_Named:            return .Named
-		case Type_Info_Integer:          return .Integer
-		case Type_Info_Rune:             return .Rune
-		case Type_Info_Float:            return .Float
-		case Type_Info_Complex:          return .Complex
-		case Type_Info_Quaternion:       return .Quaternion
-		case Type_Info_String:           return .String
-		case Type_Info_Boolean:          return .Boolean
-		case Type_Info_Any:              return .Any
-		case Type_Info_Type_Id:          return .Type_Id
-		case Type_Info_Pointer:          return .Pointer
-		case Type_Info_Multi_Pointer:    return .Multi_Pointer
-		case Type_Info_Procedure:        return .Procedure
-		case Type_Info_Array:            return .Array
-		case Type_Info_Enumerated_Array: return .Enumerated_Array
-		case Type_Info_Dynamic_Array:    return .Dynamic_Array
-		case Type_Info_Slice:            return .Slice
-		case Type_Info_Parameters:       return .Tuple
-		case Type_Info_Struct:           return .Struct
-		case Type_Info_Union:            return .Union
-		case Type_Info_Enum:             return .Enum
-		case Type_Info_Map:              return .Map
-		case Type_Info_Bit_Set:          return .Bit_Set
-		case Type_Info_Simd_Vector:      return .Simd_Vector
-		case Type_Info_Relative_Pointer: return .Relative_Pointer
-		case Type_Info_Relative_Slice:   return .Relative_Slice
-		case Type_Info_Matrix:           return .Matrix
-		case Type_Info_Soa_Pointer:      return .Soa_Pointer
+		case Type_Info_Named:                  return .Named
+		case Type_Info_Integer:                return .Integer
+		case Type_Info_Rune:                   return .Rune
+		case Type_Info_Float:                  return .Float
+		case Type_Info_Complex:                return .Complex
+		case Type_Info_Quaternion:             return .Quaternion
+		case Type_Info_String:                 return .String
+		case Type_Info_Boolean:                return .Boolean
+		case Type_Info_Any:                    return .Any
+		case Type_Info_Type_Id:                return .Type_Id
+		case Type_Info_Pointer:                return .Pointer
+		case Type_Info_Multi_Pointer:          return .Multi_Pointer
+		case Type_Info_Procedure:              return .Procedure
+		case Type_Info_Array:                  return .Array
+		case Type_Info_Enumerated_Array:       return .Enumerated_Array
+		case Type_Info_Dynamic_Array:          return .Dynamic_Array
+		case Type_Info_Slice:                  return .Slice
+		case Type_Info_Parameters:             return .Tuple
+		case Type_Info_Struct:                 return .Struct
+		case Type_Info_Union:                  return .Union
+		case Type_Info_Enum:                   return .Enum
+		case Type_Info_Map:                    return .Map
+		case Type_Info_Bit_Set:                return .Bit_Set
+		case Type_Info_Simd_Vector:            return .Simd_Vector
+		case Type_Info_Relative_Pointer:       return .Relative_Pointer
+		case Type_Info_Relative_Multi_Pointer: return .Relative_Multi_Pointer
+		case Type_Info_Matrix:                 return .Matrix
+		case Type_Info_Soa_Pointer:            return .Soa_Pointer
 		}
 
 	}
@@ -1457,8 +1457,6 @@ equal :: proc(a, b: any, including_indirect_array_recursion := false, recursion_
 		return equal(va, vb, including_indirect_array_recursion, recursion_level+1) 
 	case Type_Info_Map:
 		return false
-	case Type_Info_Relative_Slice:
-		return false
 	case 
 		Type_Info_Boolean,
 		Type_Info_Integer, 
@@ -1474,6 +1472,7 @@ equal :: proc(a, b: any, including_indirect_array_recursion := false, recursion_
 		Type_Info_Enum,
 		Type_Info_Simd_Vector,
 		Type_Info_Relative_Pointer,
+		Type_Info_Relative_Multi_Pointer,
 		Type_Info_Soa_Pointer,
 		Type_Info_Matrix:
 		return runtime.memory_compare(a.data, b.data, t.size) == 0

+ 7 - 8
core/reflect/types.odin

@@ -165,9 +165,9 @@ are_types_identical :: proc(a, b: ^Type_Info) -> bool {
 		y := b.variant.(Type_Info_Relative_Pointer) or_return
 		return x.base_integer == y.base_integer && x.pointer == y.pointer
 
-	case Type_Info_Relative_Slice:
-		y := b.variant.(Type_Info_Relative_Slice) or_return
-		return x.base_integer == y.base_integer && x.slice == y.slice
+	case Type_Info_Relative_Multi_Pointer:
+		y := b.variant.(Type_Info_Relative_Multi_Pointer) or_return
+		return x.base_integer == y.base_integer && x.pointer == y.pointer
 		
 	case Type_Info_Matrix:
 		y := b.variant.(Type_Info_Matrix) or_return
@@ -383,9 +383,9 @@ is_relative_pointer :: proc(info: ^Type_Info) -> bool {
 	return ok
 }
 @(require_results)
-is_relative_slice :: proc(info: ^Type_Info) -> bool {
+is_relative_multi_pointer :: proc(info: ^Type_Info) -> bool {
 	if info == nil { return false }
-	_, ok := type_info_base(info).variant.(Type_Info_Relative_Slice)
+	_, ok := type_info_base(info).variant.(Type_Info_Relative_Multi_Pointer)
 	return ok
 }
 
@@ -395,7 +395,6 @@ is_relative_slice :: proc(info: ^Type_Info) -> bool {
 
 
 
-
 write_typeid_builder :: proc(buf: ^strings.Builder, id: typeid, n_written: ^int = nil) -> (n: int, err: io.Error) {
 	return write_type_writer(strings.to_writer(buf), type_info_of(id))
 }
@@ -652,11 +651,11 @@ write_type_writer :: proc(w: io.Writer, ti: ^Type_Info, n_written: ^int = nil) -
 		io.write_string(w, ") ",         &n) or_return
 		write_type(w, info.pointer,      &n) or_return
 
-	case Type_Info_Relative_Slice:
+	case Type_Info_Relative_Multi_Pointer:
 		io.write_string(w, "#relative(", &n) or_return
 		write_type(w, info.base_integer, &n) or_return
 		io.write_string(w, ") ",         &n) or_return
-		write_type(w, info.slice,        &n) or_return
+		write_type(w, info.pointer,      &n) or_return
 		
 	case Type_Info_Matrix:
 		io.write_string(w, "matrix[",               &n) or_return

+ 5 - 5
core/runtime/core.odin

@@ -162,11 +162,11 @@ Type_Info_Simd_Vector :: struct {
 	count:      int,
 }
 Type_Info_Relative_Pointer :: struct {
-	pointer:      ^Type_Info,
+	pointer:      ^Type_Info, // ^T
 	base_integer: ^Type_Info,
 }
-Type_Info_Relative_Slice :: struct {
-	slice:        ^Type_Info,
+Type_Info_Relative_Multi_Pointer :: struct {
+	pointer:      ^Type_Info, // [^]T
 	base_integer: ^Type_Info,
 }
 Type_Info_Matrix :: struct {
@@ -219,7 +219,7 @@ Type_Info :: struct {
 		Type_Info_Bit_Set,
 		Type_Info_Simd_Vector,
 		Type_Info_Relative_Pointer,
-		Type_Info_Relative_Slice,
+		Type_Info_Relative_Multi_Pointer,
 		Type_Info_Matrix,
 		Type_Info_Soa_Pointer,
 	},
@@ -252,7 +252,7 @@ Typeid_Kind :: enum u8 {
 	Bit_Set,
 	Simd_Vector,
 	Relative_Pointer,
-	Relative_Slice,
+	Relative_Multi_Pointer,
 	Matrix,
 }
 #assert(len(Typeid_Kind) < 32)

+ 5 - 2
core/runtime/default_allocators_general.odin

@@ -4,13 +4,16 @@
 //+build !js
 package runtime
 
+// TODO(bill): reimplement these procedures in the os_specific stuff
+import "core:os"
+
 when ODIN_DEFAULT_TO_NIL_ALLOCATOR {
+	_ :: os
+
 	// mem.nil_allocator reimplementation
 	default_allocator_proc :: nil_allocator_proc
 	default_allocator :: nil_allocator
 } else {
-	// TODO(bill): reimplement these procedures in the os_specific stuff
-	import "core:os"
 
 	default_allocator_proc :: os.heap_allocator_proc
 

+ 118 - 98
core/runtime/dynamic_map_internal.odin

@@ -414,68 +414,21 @@ map_insert_hash_dynamic :: proc "odin" (#no_alias m: ^Raw_Map, #no_alias info: ^
 	tk := map_cell_index_dynamic(sk, info.ks, 1)
 	tv := map_cell_index_dynamic(sv, info.vs, 1)
 
-	for {
-		hp := &hs[pos]
-		element_hash := hp^
+	swap_loop: for {
+		element_hash := hs[pos]
 
 		if map_hash_is_empty(element_hash) {
-			kp := map_cell_index_dynamic(ks, info.ks, pos)
-			vp := map_cell_index_dynamic(vs, info.vs, pos)
-			intrinsics.mem_copy_non_overlapping(rawptr(kp), rawptr(k), size_of_k)
-			intrinsics.mem_copy_non_overlapping(rawptr(vp), rawptr(v), size_of_v)
-			hp^ = h
+			k_dst := map_cell_index_dynamic(ks, info.ks, pos)
+			v_dst := map_cell_index_dynamic(vs, info.vs, pos)
+			intrinsics.mem_copy_non_overlapping(rawptr(k_dst), rawptr(k), size_of_k)
+			intrinsics.mem_copy_non_overlapping(rawptr(v_dst), rawptr(v), size_of_v)
+			hs[pos] = h
 
-			return result if result != 0 else vp
+			return result if result != 0 else v_dst
 		}
 
 		if map_hash_is_deleted(element_hash) {
-			next_pos := (pos + 1) & mask
-
-			// backward shift
-			for !map_hash_is_empty(hs[next_pos]) {
-				probe_distance := map_probe_distance(m^, hs[next_pos], next_pos)
-				if probe_distance == 0 {
-					break
-				}
-				probe_distance -= 1
-
-				kp := map_cell_index_dynamic(ks, info.ks, pos)
-				vp := map_cell_index_dynamic(vs, info.vs, pos)
-				kn := map_cell_index_dynamic(ks, info.ks, next_pos)
-				vn := map_cell_index_dynamic(vs, info.vs, next_pos)
-
-				if distance > probe_distance {
-					if result == 0 {
-						result = vp
-					}
-					// move stored into pos; store next
-					intrinsics.mem_copy_non_overlapping(rawptr(kp), rawptr(k), size_of_k)
-					intrinsics.mem_copy_non_overlapping(rawptr(vp), rawptr(v), size_of_v)
-					hs[pos] = h
-
-					intrinsics.mem_copy_non_overlapping(rawptr(k), rawptr(kn), size_of_k)
-					intrinsics.mem_copy_non_overlapping(rawptr(v), rawptr(vn), size_of_v)
-					h = hs[next_pos]
-				} else {
-					// move next back 1
-					intrinsics.mem_copy_non_overlapping(rawptr(kp), rawptr(kn), size_of_k)
-					intrinsics.mem_copy_non_overlapping(rawptr(vp), rawptr(vn), size_of_v)
-					hs[pos] = hs[next_pos]
-					distance = probe_distance
-				}
-				hs[next_pos] = 0
-				pos = (pos + 1) & mask
-				next_pos = (next_pos + 1) & mask
-				distance += 1
-			}
-
-			kp := map_cell_index_dynamic(ks, info.ks, pos)
-			vp := map_cell_index_dynamic(vs, info.vs, pos)
-			intrinsics.mem_copy_non_overlapping(rawptr(kp), rawptr(k), size_of_k)
-			intrinsics.mem_copy_non_overlapping(rawptr(vp), rawptr(v), size_of_v)
-			hs[pos] = h
-
-			return result if result != 0 else vp
+			break swap_loop
 		}
 
 		if probe_distance := map_probe_distance(m^, element_hash, pos); distance > probe_distance {
@@ -495,8 +448,8 @@ map_insert_hash_dynamic :: proc "odin" (#no_alias m: ^Raw_Map, #no_alias info: ^
 			intrinsics.mem_copy_non_overlapping(rawptr(vp), rawptr(tv), size_of_v)
 
 			th := h
-			h = hp^
-			hp^ = th
+			h = hs[pos]
+			hs[pos] = th
 
 			distance = probe_distance
 		}
@@ -504,6 +457,103 @@ map_insert_hash_dynamic :: proc "odin" (#no_alias m: ^Raw_Map, #no_alias info: ^
 		pos = (pos + 1) & mask
 		distance += 1
 	}
+
+	// backward shift loop
+	hs[pos] = 0
+	look_ahead: uintptr = 1
+	for {
+		la_pos := (pos + look_ahead) & mask
+		element_hash := hs[la_pos]
+
+		if map_hash_is_deleted(element_hash) {
+			look_ahead += 1
+			hs[la_pos] = 0
+			continue
+		}
+
+		k_dst := map_cell_index_dynamic(ks, info.ks, pos)
+		v_dst := map_cell_index_dynamic(vs, info.vs, pos)
+
+		if map_hash_is_empty(element_hash) {
+			intrinsics.mem_copy_non_overlapping(rawptr(k_dst), rawptr(k), size_of_k)
+			intrinsics.mem_copy_non_overlapping(rawptr(v_dst), rawptr(v), size_of_v)
+			hs[pos] = h
+
+			return result if result != 0 else v_dst
+		}
+
+		k_src := map_cell_index_dynamic(ks, info.ks, la_pos)
+		v_src := map_cell_index_dynamic(vs, info.vs, la_pos)
+		probe_distance := map_probe_distance(m^, element_hash, la_pos)
+
+		if probe_distance < look_ahead {
+			// probed can be made ideal while placing saved (ending condition)
+			if result == 0 {
+				result = v_dst
+			}
+			intrinsics.mem_copy_non_overlapping(rawptr(k_dst), rawptr(k), size_of_k)
+			intrinsics.mem_copy_non_overlapping(rawptr(v_dst), rawptr(v), size_of_v)
+			hs[pos] = h
+
+			// This will be an ideal move
+			pos = (la_pos - probe_distance) & mask
+			look_ahead -= probe_distance
+
+			// shift until we hit ideal/empty
+			for probe_distance != 0 {
+				k_dst = map_cell_index_dynamic(ks, info.ks, pos)
+				v_dst = map_cell_index_dynamic(vs, info.vs, pos)
+
+				intrinsics.mem_copy_non_overlapping(rawptr(k_dst), rawptr(k_src), size_of_k)
+				intrinsics.mem_copy_non_overlapping(rawptr(v_dst), rawptr(v_src), size_of_v)
+				hs[pos] = element_hash
+				hs[la_pos] = 0
+
+				pos = (pos + 1) & mask
+				la_pos = (la_pos + 1) & mask
+				look_ahead = (la_pos - pos) & mask
+				element_hash = hs[la_pos]
+				if map_hash_is_empty(element_hash) {
+					return
+				}
+
+				probe_distance = map_probe_distance(m^, element_hash, la_pos)
+				if probe_distance == 0 {
+					return
+				}
+				// can be ideal?
+				if probe_distance < look_ahead {
+					pos = (la_pos - probe_distance) & mask
+				}
+				k_src = map_cell_index_dynamic(ks, info.ks, la_pos)
+				v_src = map_cell_index_dynamic(vs, info.vs, la_pos)
+			}
+			return
+		} else if distance < probe_distance - look_ahead {
+			// shift back probed
+			intrinsics.mem_copy_non_overlapping(rawptr(k_dst), rawptr(k_src), size_of_k)
+			intrinsics.mem_copy_non_overlapping(rawptr(v_dst), rawptr(v_src), size_of_v)
+			hs[pos] = element_hash
+			hs[la_pos] = 0
+		} else {
+			// place saved, save probed
+			if result == 0 {
+				result = v_dst
+			}
+			intrinsics.mem_copy_non_overlapping(rawptr(k_dst), rawptr(k), size_of_k)
+			intrinsics.mem_copy_non_overlapping(rawptr(v_dst), rawptr(v), size_of_v)
+			hs[pos] = h
+
+			intrinsics.mem_copy_non_overlapping(rawptr(k), rawptr(k_src), size_of_k)
+			intrinsics.mem_copy_non_overlapping(rawptr(v), rawptr(v_src), size_of_v)
+			h = hs[la_pos]
+			hs[la_pos] = 0
+			distance = probe_distance - look_ahead
+		}
+
+		pos = (pos + 1) & mask
+		distance += 1
+	}
 }
 
 @(require_results)
@@ -696,49 +746,19 @@ map_erase_dynamic :: #force_inline proc "contextless" (#no_alias m: ^Raw_Map, #n
 	m.len -= 1
 	ok = true
 
-	{ // coalesce tombstones
-		// HACK NOTE(bill): This is an ugly bodge but it is coalescing the tombstone slots
-		mask := (uintptr(1)<<map_log2_cap(m^)) - 1
-		curr_index := uintptr(index)
-
-		// TODO(bill): determine a good value for this empirically
-		// if we do not implement backward shift deletion
-		PROBE_COUNT :: 8
-		for _ in 0..<PROBE_COUNT {
-			next_index := (curr_index + 1) & mask
-			if next_index == index {
-				// looped around
-				break
-			}
-
-			// if the next element is empty or has zero probe distance, then any lookup
-			// will always fail on the next, so we can clear both of them
-			hash := hs[next_index]
-			if map_hash_is_empty(hash) || map_probe_distance(m^, hash, next_index) == 0 {
-				hs[curr_index] = 0
-				return
-			}
-
-			// now the next element will have a probe count of at least one,
-			// so it can use the delete slot instead
-			hs[curr_index] = hs[next_index]
-
-			mem_copy_non_overlapping(
-				rawptr(map_cell_index_dynamic(ks, info.ks, curr_index)),
-				rawptr(map_cell_index_dynamic(ks, info.ks, next_index)),
-				int(info.ks.size_of_type),
-			)
-			mem_copy_non_overlapping(
-				rawptr(map_cell_index_dynamic(vs, info.vs, curr_index)),
-				rawptr(map_cell_index_dynamic(vs, info.vs, next_index)),
-				int(info.vs.size_of_type),
-			)
-
-			curr_index = next_index
-		}
+	mask := (uintptr(1)<<map_log2_cap(m^)) - 1
+	curr_index := uintptr(index)
+	next_index := (curr_index + 1) & mask
 
+	// if the next element is empty or has zero probe distance, then any lookup
+	// will always fail on the next, so we can clear both of them
+	hash := hs[next_index]
+	if map_hash_is_empty(hash) || map_probe_distance(m^, hash, next_index) == 0 {
+		hs[curr_index] = 0
+	} else {
 		hs[curr_index] |= TOMBSTONE_MASK
 	}
+
 	return
 }
 

+ 9 - 9
core/runtime/error_checks.odin

@@ -235,7 +235,7 @@ make_slice_error_loc :: #force_inline proc "contextless" (loc := #caller_locatio
 	handle_error(loc, len)
 }
 
-make_dynamic_array_error_loc :: #force_inline proc "contextless" (using loc := #caller_location, len, cap: int) {
+make_dynamic_array_error_loc :: #force_inline proc "contextless" (loc := #caller_location, len, cap: int) {
 	if 0 <= len && len <= cap {
 		return
 	}
@@ -271,18 +271,18 @@ make_map_expr_error_loc :: #force_inline proc "contextless" (loc := #caller_loca
 
 
 
-bounds_check_error_loc :: #force_inline proc "contextless" (using loc := #caller_location, index, count: int) {
-	bounds_check_error(file_path, line, column, index, count)
+bounds_check_error_loc :: #force_inline proc "contextless" (loc := #caller_location, index, count: int) {
+	bounds_check_error(loc.file_path, loc.line, loc.column, index, count)
 }
 
-slice_expr_error_hi_loc :: #force_inline proc "contextless" (using loc := #caller_location, hi: int, len: int) {
-	slice_expr_error_hi(file_path, line, column, hi, len)
+slice_expr_error_hi_loc :: #force_inline proc "contextless" (loc := #caller_location, hi: int, len: int) {
+	slice_expr_error_hi(loc.file_path, loc.line, loc.column, hi, len)
 }
 
-slice_expr_error_lo_hi_loc :: #force_inline proc "contextless" (using loc := #caller_location, lo, hi: int, len: int) {
-	slice_expr_error_lo_hi(file_path, line, column, lo, hi, len)
+slice_expr_error_lo_hi_loc :: #force_inline proc "contextless" (loc := #caller_location, lo, hi: int, len: int) {
+	slice_expr_error_lo_hi(loc.file_path, loc.line, loc.column, lo, hi, len)
 }
 
-dynamic_array_expr_error_loc :: #force_inline proc "contextless" (using loc := #caller_location, low, high, max: int) {
-	dynamic_array_expr_error(file_path, line, column, low, high, max)
+dynamic_array_expr_error_loc :: #force_inline proc "contextless" (loc := #caller_location, low, high, max: int) {
+	dynamic_array_expr_error(loc.file_path, loc.line, loc.column, low, high, max)
 }

+ 11 - 2
core/runtime/internal.odin

@@ -11,7 +11,7 @@ RUNTIME_LINKAGE :: "strong" when (
 	ODIN_BUILD_MODE == .Dynamic ||
 	!ODIN_NO_CRT) &&
 	!IS_WASM) else "internal"
-RUNTIME_REQUIRE :: true
+RUNTIME_REQUIRE :: !ODIN_TILDE
 
 
 @(private)
@@ -218,10 +218,18 @@ memory_equal :: proc "contextless" (x, y: rawptr, n: int) -> bool {
 	case n == 0: return true
 	case x == y: return true
 	}
-	
 	a, b := ([^]byte)(x), ([^]byte)(y)
 	length := uint(n)
+
+	for i := uint(0); i < length; i += 1 {
+		if a[i] != b[i] {
+			return false
+		}
+	}
+	return true
 	
+/*
+
 	when size_of(uint) == 8 {
 		if word_length := length >> 3; word_length != 0 {
 			for _ in 0..<word_length {
@@ -276,6 +284,7 @@ memory_equal :: proc "contextless" (x, y: rawptr, n: int) -> bool {
 
 		return true
 	}
+*/
 
 }
 memory_compare :: proc "contextless" (a, b: rawptr, n: int) -> int #no_bounds_check {

+ 6 - 9
core/runtime/os_specific_windows.odin

@@ -1,9 +1,6 @@
-//+private
 //+build windows
 package runtime
 
-import "core:intrinsics"
-
 foreign import kernel32 "system:Kernel32.lib"
 
 @(private="file")
@@ -102,12 +99,12 @@ _windows_default_alloc_or_resize :: proc "contextless" (size, alignment: int, ol
 
 	allocated_mem: rawptr
 	if old_ptr != nil {
-		original_old_ptr := intrinsics.ptr_offset((^rawptr)(old_ptr), -1)^
+		original_old_ptr := ([^]rawptr)(old_ptr)[-1]
 		allocated_mem = heap_resize(original_old_ptr, space+size_of(rawptr))
 	} else {
 		allocated_mem = heap_alloc(space+size_of(rawptr), zero_memory)
 	}
-	aligned_mem := rawptr(intrinsics.ptr_offset((^u8)(allocated_mem), size_of(rawptr)))
+	aligned_mem := ([^]u8)(allocated_mem)[size_of(rawptr):]
 
 	ptr := uintptr(aligned_mem)
 	aligned_ptr := (ptr - 1 + uintptr(a)) & -uintptr(a)
@@ -116,10 +113,10 @@ _windows_default_alloc_or_resize :: proc "contextless" (size, alignment: int, ol
 		return nil, .Out_Of_Memory
 	}
 
-	aligned_mem = rawptr(aligned_ptr)
-	intrinsics.ptr_offset((^rawptr)(aligned_mem), -1)^ = allocated_mem
+	aligned_mem = ([^]byte)(aligned_ptr)
+	([^]rawptr)(aligned_mem)[-1] = allocated_mem
 
-	return byte_slice(aligned_mem, size), nil
+	return aligned_mem[:size], nil
 }
 
 _windows_default_alloc :: proc "contextless" (size, alignment: int, zero_memory := true) -> ([]byte, Allocator_Error) {
@@ -129,7 +126,7 @@ _windows_default_alloc :: proc "contextless" (size, alignment: int, zero_memory
 
 _windows_default_free :: proc "contextless" (ptr: rawptr) {
 	if ptr != nil {
-		heap_free(intrinsics.ptr_offset((^rawptr)(ptr), -1)^)
+		heap_free(([^]rawptr)(ptr)[-1])
 	}
 }
 

+ 17 - 9
core/runtime/print.odin

@@ -8,6 +8,11 @@ _INTEGER_DIGITS_VAR := _INTEGER_DIGITS
 when !ODIN_NO_RTTI {
 	print_any_single :: proc "contextless" (arg: any) {
 		x := arg
+		if x.data == nil {
+			print_string("nil")
+			return
+		}
+
 		if loc, ok := x.(Source_Code_Location); ok {
 			print_caller_location(loc)
 			return
@@ -48,6 +53,7 @@ when !ODIN_NO_RTTI {
 		case int:     print_int(v)
 		case uint:    print_uint(v)
 		case uintptr: print_uintptr(v)
+		case rawptr:  print_uintptr(uintptr(v))
 
 		case bool: print_string("true" if v else "false")
 		case b8:   print_string("true" if v else "false")
@@ -58,7 +64,7 @@ when !ODIN_NO_RTTI {
 		case:
 			ti := type_info_of(x.id)
 			#partial switch v in ti.variant {
-			case Type_Info_Pointer:
+			case Type_Info_Pointer, Type_Info_Multi_Pointer:
 				print_uintptr((^uintptr)(x.data)^)
 				return
 			}
@@ -67,7 +73,9 @@ when !ODIN_NO_RTTI {
 		}
 	}
 	println_any :: proc "contextless" (args: ..any) {
+		context = default_context()
 		loop: for arg, i in args {
+			assert(arg.id != nil)
 			if i != 0 {
 				print_string(" ")
 			}
@@ -215,19 +223,19 @@ print_uint    :: proc "contextless" (x: uint)    { print_u64(u64(x)) }
 print_uintptr :: proc "contextless" (x: uintptr) { print_u64(u64(x)) }
 print_int     :: proc "contextless" (x: int)     { print_i64(i64(x)) }
 
-print_caller_location :: proc "contextless" (using loc: Source_Code_Location) {
-	print_string(file_path)
+print_caller_location :: proc "contextless" (loc: Source_Code_Location) {
+	print_string(loc.file_path)
 	when ODIN_ERROR_POS_STYLE == .Default {
 		print_byte('(')
-		print_u64(u64(line))
+		print_u64(u64(loc.line))
 		print_byte(':')
-		print_u64(u64(column))
+		print_u64(u64(loc.column))
 		print_byte(')')
 	} else when ODIN_ERROR_POS_STYLE == .Unix {
 		print_byte(':')
-		print_u64(u64(line))
+		print_u64(u64(loc.line))
 		print_byte(':')
-		print_u64(u64(column))
+		print_u64(u64(loc.column))
 		print_byte(':')
 	} else {
 		#panic("unhandled ODIN_ERROR_POS_STYLE")
@@ -463,11 +471,11 @@ print_type :: proc "contextless" (ti: ^Type_Info) {
 		print_string(") ")
 		print_type(info.pointer)
 
-	case Type_Info_Relative_Slice:
+	case Type_Info_Relative_Multi_Pointer:
 		print_string("#relative(")
 		print_type(info.base_integer)
 		print_string(") ")
-		print_type(info.slice)
+		print_type(info.pointer)
 		
 	case Type_Info_Matrix:
 		print_string("matrix[")

+ 2 - 2
core/runtime/procs.odin

@@ -31,7 +31,7 @@ when ODIN_NO_CRT && ODIN_OS == .Windows {
 		if ptr != nil && len != 0 {
 			b := byte(val)
 			p := ([^]byte)(ptr)
-			for i in 0..<len {
+			for i := 0; i < len; i += 1 {
 				p[i] = b
 			}
 		}
@@ -75,7 +75,7 @@ when ODIN_NO_CRT && ODIN_OS == .Windows {
 		if ptr != nil && len != 0 {
 			b := byte(val)
 			p := ([^]byte)(ptr)
-			for i in 0..<len {
+			for i := 0; i < len; i += 1 {
 				p[i] = b
 			}
 		}

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

@@ -1709,6 +1709,12 @@ MONITORINFO :: struct {
 }
 LPMONITORINFO :: ^MONITORINFO
 
+CCHDEVICENAME :: 32
+MONITORINFOEXW :: struct {
+	using _: MONITORINFO,
+	szDevice: [CCHDEVICENAME]WCHAR,
+}
+
 // SetWindowsHook() codes
 WH_MIN             :: -1
 WH_MSGFILTER       :: -1

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

@@ -228,6 +228,9 @@ foreign user32 {
 	SetWindowRgn :: proc(hWnd: HWND, hRgn: HRGN, bRedraw: BOOL) -> int ---
 	CreateRectRgnIndirect :: proc(lprect: ^RECT) -> HRGN ---
 	GetSystemMetricsForDpi :: proc(nIndex: int, dpi: UINT) -> int ---
+
+	GetSystemMenu :: proc(hWnd: HWND, bRevert: BOOL) -> HMENU ---
+	EnableMenuItem :: proc(hMenu: HMENU, uIDEnableItem: UINT, uEnable: UINT) -> BOOL ---
 }
 
 CreateWindowW :: #force_inline proc "stdcall" (

+ 2 - 0
core/text/i18n/i18n.odin

@@ -71,6 +71,8 @@ Error :: enum {
 	TS_File_Expected_Source,
 	TS_File_Expected_Translation,
 	TS_File_Expected_NumerusForm,
+	Bad_Str,
+	Bad_Id,
 
 }
 

+ 34 - 8
core/text/i18n/qt_linguist.odin

@@ -30,10 +30,26 @@ TS_XML_Options := xml.Options{
 parse_qt_linguist_from_bytes :: proc(data: []byte, options := DEFAULT_PARSE_OPTIONS, pluralizer: proc(int) -> int = nil, allocator := context.allocator) -> (translation: ^Translation, err: Error) {
 	context.allocator = allocator
 
+	get_str :: proc(val: xml.Value) -> (str: string, err: Error) {
+		v, ok := val.(string)
+		if ok {
+			return v, .None
+		}
+		return "", .Bad_Str
+	}
+
+	get_id :: proc(val: xml.Value) -> (str: xml.Element_ID, err: Error) {
+		v, ok := val.(xml.Element_ID)
+		if ok {
+			return v, .None
+		}
+		return 0, .Bad_Id
+	}
+
 	ts, xml_err := xml.parse(data, TS_XML_Options)
 	defer xml.destroy(ts)
 
-	if xml_err != .None || ts.element_count < 1 || ts.elements[0].ident != "TS" || len(ts.elements[0].children) == 0 {
+	if xml_err != .None || ts.element_count < 1 || ts.elements[0].ident != "TS" || len(ts.elements[0].value) == 0 {
 		return nil, .TS_File_Parse_Error
 	}
 
@@ -46,10 +62,12 @@ parse_qt_linguist_from_bytes :: proc(data: []byte, options := DEFAULT_PARSE_OPTI
 
 	section: ^Section
 
-	for child_id in ts.elements[0].children {
+	for value in ts.elements[0].value {
+		child_id := get_id(value) or_return
+
 		// These should be <context>s.
-		child := ts.elements[child_id]
-		if child.ident != "context" {
+
+		if ts.elements[child_id].ident != "context" {
 			return translation, .TS_File_Expected_Context
 		}
 
@@ -61,7 +79,8 @@ parse_qt_linguist_from_bytes :: proc(data: []byte, options := DEFAULT_PARSE_OPTI
 
 		section_name, _ := strings.intern_get(&translation.intern, "")
 		if !options.merge_sections {
-			section_name, _ = strings.intern_get(&translation.intern, ts.elements[section_name_id].value)
+			value_text := get_str(ts.elements[section_name_id].value[0]) or_return
+			section_name, _ = strings.intern_get(&translation.intern, value_text)
 		}
 
 		if section_name not_in translation.k_v {
@@ -92,8 +111,14 @@ parse_qt_linguist_from_bytes :: proc(data: []byte, options := DEFAULT_PARSE_OPTI
 				return translation, .TS_File_Expected_Translation
 			}
 
-			source, _ := strings.intern_get(&translation.intern, ts.elements[source_id].value)
-			xlat,   _ := strings.intern_get(&translation.intern, ts.elements[translation_id].value)
+			source    := get_str(ts.elements[source_id].value[0]) or_return
+			source, _  = strings.intern_get(&translation.intern, source)
+
+			xlat := ""
+			if !has_plurals {
+				xlat    = get_str(ts.elements[translation_id].value[0]) or_return
+				xlat, _ = strings.intern_get(&translation.intern, xlat)
+			}
 
 			if source in section {
 				return translation, .Duplicate_Key
@@ -124,7 +149,8 @@ parse_qt_linguist_from_bytes :: proc(data: []byte, options := DEFAULT_PARSE_OPTI
 					if !numerus_found {
 						break
 					}
-					numerus, _ := strings.intern_get(&translation.intern, ts.elements[numerus_id].value)
+					numerus := get_str(ts.elements[numerus_id].value[0]) or_return
+					numerus, _ = strings.intern_get(&translation.intern, numerus)
 					section[source][num_plurals] = numerus
 
 					num_plurals += 1

+ 2 - 2
core/thread/thread_windows.odin

@@ -129,8 +129,8 @@ _destroy :: proc(thread: ^Thread) {
 	free(thread, thread.creation_allocator)
 }
 
-_terminate :: proc(using thread : ^Thread, exit_code: int) {
-	win32.TerminateThread(win32_thread, u32(exit_code))
+_terminate :: proc(thread: ^Thread, exit_code: int) {
+	win32.TerminateThread(thread.win32_thread, u32(exit_code))
 }
 
 _yield :: proc() {

+ 19 - 17
core/time/time.odin

@@ -59,28 +59,30 @@ sleep :: proc "contextless" (d: Duration) {
 	_sleep(d)
 }
 
-stopwatch_start :: proc "contextless" (using stopwatch: ^Stopwatch) {
-	if !running {
-		_start_time = tick_now()
-		running = true
+stopwatch_start :: proc "contextless" (stopwatch: ^Stopwatch) {
+	if !stopwatch.running {
+		stopwatch._start_time = tick_now()
+		stopwatch.running = true
 	}
 }
 
-stopwatch_stop :: proc "contextless" (using stopwatch: ^Stopwatch) {
-	if running {
-		_accumulation += tick_diff(_start_time, tick_now())
-		running = false
+stopwatch_stop :: proc "contextless" (stopwatch: ^Stopwatch) {
+	if stopwatch.running {
+		stopwatch._accumulation += tick_diff(stopwatch._start_time, tick_now())
+		stopwatch.running = false
 	}
 }
 
-stopwatch_reset :: proc "contextless" (using stopwatch: ^Stopwatch) {
-	_accumulation = {}
-	running = false
+stopwatch_reset :: proc "contextless" (stopwatch: ^Stopwatch) {
+	stopwatch._accumulation = {}
+	stopwatch.running = false
 }
 
-stopwatch_duration :: proc "contextless" (using stopwatch: Stopwatch) -> Duration {
-	if !running { return _accumulation }
-	return _accumulation + tick_diff(_start_time, tick_now())
+stopwatch_duration :: proc "contextless" (stopwatch: Stopwatch) -> Duration {
+	if !stopwatch.running {
+		return stopwatch._accumulation
+	}
+	return stopwatch._accumulation + tick_diff(stopwatch._start_time, tick_now())
 }
 
 diff :: proc "contextless" (start, end: Time) -> Duration {
@@ -171,9 +173,9 @@ day :: proc "contextless" (t: Time) -> (day: int) {
 }
 
 weekday :: proc "contextless" (t: Time) -> (weekday: Weekday) {
-    abs := _time_abs(t)
-    sec := (abs + u64(Weekday.Monday) * SECONDS_PER_DAY) % SECONDS_PER_WEEK
-    return Weekday(int(sec) / SECONDS_PER_DAY)
+	abs := _time_abs(t)
+	sec := (abs + u64(Weekday.Monday) * SECONDS_PER_DAY) % SECONDS_PER_WEEK
+	return Weekday(int(sec) / SECONDS_PER_DAY)
 }
 
 clock :: proc { clock_from_time, clock_from_duration, clock_from_stopwatch }

+ 28 - 6
examples/demo/demo.odin

@@ -1,3 +1,4 @@
+//+vet !using-stmt !using-param
 package main
 
 import "core:fmt"
@@ -458,6 +459,27 @@ named_proc_return_parameters :: proc() {
 	fmt.println("foo2 =", foo2()) // 567 321
 }
 
+variadic_procedures :: proc() {
+	fmt.println("\n# variadic procedures")
+	sum :: proc(nums: ..int, init_value:= 0) -> (result: int) {
+		result = init_value
+		for n in nums {
+			result += n
+		}
+		return
+	}
+	fmt.println("sum(()) =", sum())
+	fmt.println("sum(1, 2) =", sum(1, 2))
+	fmt.println("sum(1, 2, 3, 4, 5) =", sum(1, 2, 3, 4, 5))
+	fmt.println("sum(1, 2, 3, 4, 5, init_value = 5) =", sum(1, 2, 3, 4, 5, init_value = 5))
+
+	// pass a slice as varargs
+	odds := []int{1, 3, 5}
+	fmt.println("odds =", odds)
+	fmt.println("sum(..odds) =", sum(..odds))
+	fmt.println("sum(..odds, init_value = 5) =", sum(..odds, init_value = 5))
+}
+
 
 explicit_procedure_overloading :: proc() {
 	fmt.println("\n# explicit procedure overloading")
@@ -2023,12 +2045,11 @@ relative_data_types :: proc() {
 	fmt.println(ptr^)
 
 	arr := [3]int{1, 2, 3}
-	s := arr[:]
-	rel_slice: #relative(i16) []int
-	rel_slice = s
-	fmt.println(rel_slice)
-	fmt.println(rel_slice[:])
-	fmt.println(rel_slice[1])
+	multi_ptr: #relative(i16) [^]int
+	multi_ptr = &arr[0]
+	fmt.println(multi_ptr)
+	fmt.println(multi_ptr[:3])
+	fmt.println(multi_ptr[1])
 }
 
 or_else_operator :: proc() {
@@ -2462,6 +2483,7 @@ main :: proc() {
 		the_basics()
 		control_flow()
 		named_proc_return_parameters()
+		variadic_procedures()
 		explicit_procedure_overloading()
 		struct_type()
 		union_type()

+ 11 - 0
src/array.cpp

@@ -168,6 +168,17 @@ gb_internal gb_inline Slice<T> slice(Slice<T> const &array, isize lo, isize hi)
 	}
 	return out;
 }
+template <typename T>
+gb_internal gb_inline Slice<T> slice(Array<T> const &array, isize lo, isize hi) {
+	GB_ASSERT(0 <= lo && lo <= hi && hi <= array.count);
+	Slice<T> out = {};
+	isize len = hi-lo;
+	if (len > 0) {
+		out.data = array.data+lo;
+		out.count = len;
+	}
+	return out;
+}
 
 
 template <typename T>

+ 41 - 3
src/build_settings.cpp

@@ -216,6 +216,43 @@ enum BuildPath : u8 {
 	BuildPathCOUNT,
 };
 
+enum VetFlags : u64 {
+	VetFlag_NONE       = 0,
+	VetFlag_Unused     = 1u<<0, // 1
+	VetFlag_Shadowing  = 1u<<1, // 2
+	VetFlag_UsingStmt  = 1u<<2, // 4
+	VetFlag_UsingParam = 1u<<3, // 8
+	VetFlag_Style      = 1u<<4, // 16
+	VetFlag_Semicolon  = 1u<<5, // 32
+
+	VetFlag_Extra     = 1u<<16,
+
+	VetFlag_All = VetFlag_Unused|VetFlag_Shadowing|VetFlag_UsingStmt, // excluding extra
+
+	VetFlag_Using = VetFlag_UsingStmt|VetFlag_UsingParam,
+};
+
+u64 get_vet_flag_from_name(String const &name) {
+	if (name == "unused") {
+		return VetFlag_Unused;
+	} else if (name == "shadowing") {
+		return VetFlag_Shadowing;
+	} else if (name == "using-stmt") {
+		return VetFlag_UsingStmt;
+	} else if (name == "using-param") {
+		return VetFlag_UsingParam;
+	} else if (name == "style") {
+		return VetFlag_Style;
+	} else if (name == "semicolon") {
+		return VetFlag_Semicolon;
+	} else if (name == "extra") {
+		return VetFlag_Extra;
+	}
+	return VetFlag_NONE;
+}
+
+
+
 // This stores the information for the specify architecture of this build
 struct BuildContext {
 	// Constants
@@ -255,6 +292,8 @@ struct BuildContext {
 	String resource_filepath;
 	String pdb_filepath;
 
+	u64 vet_flags;
+
 	bool   has_resource;
 	String link_flags;
 	String extra_linker_flags;
@@ -280,15 +319,12 @@ struct BuildContext {
 	bool   no_entry_point;
 	bool   no_thread_local;
 	bool   use_lld;
-	bool   vet;
-	bool   vet_extra;
 	bool   cross_compiling;
 	bool   different_os;
 	bool   keep_object_files;
 	bool   disallow_do;
 
 	bool   strict_style;
-	bool   strict_style_init_only;
 
 	bool   ignore_warnings;
 	bool   warnings_as_errors;
@@ -318,6 +354,8 @@ struct BuildContext {
 
 	isize max_error_count;
 
+	bool tilde_backend;
+
 
 	u32 cmd_doc_flags;
 	Array<String> extra_packages;

+ 5 - 2
src/check_builtin.cpp

@@ -1406,7 +1406,7 @@ gb_internal bool check_builtin_procedure_directive(CheckerContext *c, Operand *o
 		}
 		return false;
 	} else if (name == "load_or") {
-		warning(call, "'#load_or' is deprecated in favour of '#load(path) or_else default'");
+		error(call, "'#load_or' has now been removed in favour of '#load(path) or_else default'");
 
 		if (ce->args.count != 2) {
 			if (ce->args.count == 0) {
@@ -1748,7 +1748,7 @@ gb_internal bool check_builtin_procedure(CheckerContext *c, Operand *operand, As
 			mode = Addressing_Constant;
 			value = exact_value_i64(at->EnumeratedArray.count);
 			type = t_untyped_integer;
-		} else if ((is_type_slice(op_type) || is_type_relative_slice(op_type)) && id == BuiltinProc_len) {
+		} else if (is_type_slice(op_type) && id == BuiltinProc_len) {
 			mode = Addressing_Value;
 		} else if (is_type_dynamic_array(op_type)) {
 			mode = Addressing_Value;
@@ -3692,6 +3692,9 @@ gb_internal bool check_builtin_procedure(CheckerContext *c, Operand *operand, As
 					case Type_SimdVector:
 						operand->type = alloc_type_multi_pointer(base_array_type(base));
 						break;
+					case Type_Matrix:
+						operand->type = alloc_type_multi_pointer(base->Matrix.elem);
+						break;
 					}
 				}
 				break;

+ 77 - 90
src/check_decl.cpp

@@ -7,13 +7,15 @@ gb_internal Type *check_init_variable(CheckerContext *ctx, Entity *e, Operand *o
 		e->type == t_invalid) {
 
 		if (operand->mode == Addressing_Builtin) {
+			ERROR_BLOCK();
 			gbString expr_str = expr_to_string(operand->expr);
 
-			// TODO(bill): is this a good enough error message?
 			error(operand->expr,
-				  "Cannot assign built-in procedure '%s' in %.*s",
-				  expr_str,
-				  LIT(context_name));
+			      "Cannot assign built-in procedure '%s' in %.*s",
+			      expr_str,
+			      LIT(context_name));
+
+			error_line("\tBuilt-in procedures are implemented by the compiler and might not be actually instantiated procedure\n");
 
 			operand->mode = Addressing_Invalid;
 
@@ -159,9 +161,8 @@ gb_internal void check_init_constant(CheckerContext *ctx, Entity *e, Operand *op
 	}
 
 	if (operand->mode != Addressing_Constant) {
-		// TODO(bill): better error
 		gbString str = expr_to_string(operand->expr);
-		error(operand->expr, "'%s' is not a constant", str);
+		error(operand->expr, "'%s' is not a compile-time known constant", str);
 		gb_string_free(str);
 		if (e->type == nullptr) {
 			e->type = t_invalid;
@@ -354,31 +355,7 @@ gb_internal void check_type_decl(CheckerContext *ctx, Entity *e, Ast *init_expr,
 
 	// using decl
 	if (decl->is_using) {
-		warning(init_expr, "'using' an enum declaration is not allowed, prefer using implicit selector expressions e.g. '.A'");
-		#if 1
-		// NOTE(bill): Must be an enum declaration
-		if (te->kind == Ast_EnumType) {
-			Scope *parent = e->scope;
-			if (parent->flags&ScopeFlag_File) {
-				// NOTE(bill): Use package scope
-				parent = parent->parent;
-			}
-
-			Type *t = base_type(e->type);
-			if (t->kind == Type_Enum) {
-				for (Entity *f : t->Enum.fields) {
-					if (f->kind != Entity_Constant) {
-						continue;
-					}
-					String name = f->token.string;
-					if (is_blank_ident(name)) {
-						continue;
-					}
-					add_entity(ctx, parent, nullptr, f);
-				}
-			}
-		}
-		#endif
+		error(init_expr, "'using' an enum declaration is not allowed, prefer using implicit selector expressions e.g. '.A'");
 	}
 }
 
@@ -757,6 +734,66 @@ gb_internal String handle_link_name(CheckerContext *ctx, Token token, String lin
 	return link_name;
 }
 
+gb_internal void check_objc_methods(CheckerContext *ctx, Entity *e, AttributeContext const &ac) {
+	if (!(ac.objc_name.len || ac.objc_is_class_method || ac.objc_type)) {
+		return;
+	}
+	if (ac.objc_name.len == 0 && ac.objc_is_class_method) {
+		error(e->token, "@(objc_name) is required with @(objc_is_class_method)");
+	} else if (ac.objc_type == nullptr) {
+		error(e->token, "@(objc_name) requires that @(objc_type) to be set");
+	} else if (ac.objc_name.len == 0 && ac.objc_type) {
+		error(e->token, "@(objc_name) is required with @(objc_type)");
+	} else {
+		Type *t = ac.objc_type;
+		if (t->kind == Type_Named) {
+			Entity *tn = t->Named.type_name;
+
+			GB_ASSERT(tn->kind == Entity_TypeName);
+
+			if (tn->scope != e->scope) {
+				error(e->token, "@(objc_name) attribute may only be applied to procedures and types within the same scope");
+			} else {
+				mutex_lock(&global_type_name_objc_metadata_mutex);
+				defer (mutex_unlock(&global_type_name_objc_metadata_mutex));
+
+				if (!tn->TypeName.objc_metadata) {
+					tn->TypeName.objc_metadata = create_type_name_obj_c_metadata();
+				}
+				auto *md = tn->TypeName.objc_metadata;
+				mutex_lock(md->mutex);
+				defer (mutex_unlock(md->mutex));
+
+				if (!ac.objc_is_class_method) {
+					bool ok = true;
+					for (TypeNameObjCMetadataEntry const &entry : md->value_entries) {
+						if (entry.name == ac.objc_name) {
+							error(e->token, "Previous declaration of @(objc_name=\"%.*s\")", LIT(ac.objc_name));
+							ok = false;
+							break;
+						}
+					}
+					if (ok) {
+						array_add(&md->value_entries, TypeNameObjCMetadataEntry{ac.objc_name, e});
+					}
+				} else {
+					bool ok = true;
+					for (TypeNameObjCMetadataEntry const &entry : md->type_entries) {
+						if (entry.name == ac.objc_name) {
+							error(e->token, "Previous declaration of @(objc_name=\"%.*s\")", LIT(ac.objc_name));
+							ok = false;
+							break;
+						}
+					}
+					if (ok) {
+						array_add(&md->type_entries, TypeNameObjCMetadataEntry{ac.objc_name, e});
+					}
+				}
+			}
+		}
+	}
+}
+
 gb_internal void check_proc_decl(CheckerContext *ctx, Entity *e, DeclInfo *d) {
 	GB_ASSERT(e->type == nullptr);
 	if (d->proc_lit->kind != Ast_ProcLit) {
@@ -840,62 +877,7 @@ gb_internal void check_proc_decl(CheckerContext *ctx, Entity *e, DeclInfo *d) {
 	}
 	e->Procedure.optimization_mode = cast(ProcedureOptimizationMode)ac.optimization_mode;
 
-	if (ac.objc_name.len || ac.objc_is_class_method || ac.objc_type) {
-		if (ac.objc_name.len == 0 && ac.objc_is_class_method) {
-			error(e->token, "@(objc_name) is required with @(objc_is_class_method)");
-		} else if (ac.objc_type == nullptr) {
-			error(e->token, "@(objc_name) requires that @(objc_type) to be set");
-		} else if (ac.objc_name.len == 0 && ac.objc_type) {
-			error(e->token, "@(objc_name) is required with @(objc_type)");
-		} else {
-			Type *t = ac.objc_type;
-			if (t->kind == Type_Named) {
-				Entity *tn = t->Named.type_name;
-
-				GB_ASSERT(tn->kind == Entity_TypeName);
-
-				if (tn->scope != e->scope) {
-					error(e->token, "@(objc_name) attribute may only be applied to procedures and types within the same scope");
-				} else {
-					mutex_lock(&global_type_name_objc_metadata_mutex);
-					defer (mutex_unlock(&global_type_name_objc_metadata_mutex));
-
-					if (!tn->TypeName.objc_metadata) {
-						tn->TypeName.objc_metadata = create_type_name_obj_c_metadata();
-					}
-					auto *md = tn->TypeName.objc_metadata;
-					mutex_lock(md->mutex);
-					defer (mutex_unlock(md->mutex));
-
-					if (!ac.objc_is_class_method) {
-						bool ok = true;
-						for (TypeNameObjCMetadataEntry const &entry : md->value_entries) {
-							if (entry.name == ac.objc_name) {
-								error(e->token, "Previous declaration of @(objc_name=\"%.*s\")", LIT(ac.objc_name));
-								ok = false;
-								break;
-							}
-						}
-						if (ok) {
-							array_add(&md->value_entries, TypeNameObjCMetadataEntry{ac.objc_name, e});
-						}
-					} else {
-						bool ok = true;
-						for (TypeNameObjCMetadataEntry const &entry : md->type_entries) {
-							if (entry.name == ac.objc_name) {
-								error(e->token, "Previous declaration of @(objc_name=\"%.*s\")", LIT(ac.objc_name));
-								ok = false;
-								break;
-							}
-						}
-						if (ok) {
-							array_add(&md->type_entries, TypeNameObjCMetadataEntry{ac.objc_name, e});
-						}
-					}
-				}
-			}
-		}
-	}
+	check_objc_methods(ctx, e, ac);
 
 	if (ac.require_target_feature.len != 0 && ac.enable_target_feature.len != 0) {
 		error(e->token, "Attributes @(require_target_feature=...) and @(enable_target_feature=...) cannot be used together");
@@ -1059,7 +1041,7 @@ gb_internal void check_proc_decl(CheckerContext *ctx, Entity *e, DeclInfo *d) {
 		auto *fp = &ctx->info->foreigns;
 		StringHashKey key = string_hash_string(name);
 		Entity **found = string_map_get(fp, key);
-		if (found) {
+		if (found && e != *found) {
 			Entity *f = *found;
 			TokenPos pos = f->token.pos;
 			Type *this_type = base_type(e->type);
@@ -1241,7 +1223,7 @@ gb_internal void check_global_variable_decl(CheckerContext *ctx, Entity *&e, Ast
 	check_rtti_type_disallowed(e->token, e->type, "A variable declaration is using a type, %s, which has been disallowed");
 }
 
-gb_internal void check_proc_group_decl(CheckerContext *ctx, Entity *&pg_entity, DeclInfo *d) {
+gb_internal void check_proc_group_decl(CheckerContext *ctx, Entity *pg_entity, DeclInfo *d) {
 	GB_ASSERT(pg_entity->kind == Entity_ProcGroup);
 	auto *pge = &pg_entity->ProcGroup;
 	String proc_group_name = pg_entity->token.string;
@@ -1366,6 +1348,11 @@ gb_internal void check_proc_group_decl(CheckerContext *ctx, Entity *&pg_entity,
 		}
 	}
 
+	AttributeContext ac = {};
+	check_decl_attributes(ctx, d->attributes, proc_group_attribute, &ac);
+	check_objc_methods(ctx, pg_entity, ac);
+
+
 }
 
 gb_internal void check_entity_decl(CheckerContext *ctx, Entity *e, DeclInfo *d, Type *named_type) {
@@ -1626,7 +1613,7 @@ gb_internal bool check_proc_body(CheckerContext *ctx_, Token token, DeclInfo *de
 	}
 	check_close_scope(ctx);
 
-	check_scope_usage(ctx->checker, ctx->scope);
+	check_scope_usage(ctx->checker, ctx->scope, check_vet_flags(body));
 
 	add_deps_from_child_to_parent(decl);
 

+ 184 - 150
src/check_expr.cpp

@@ -349,6 +349,10 @@ gb_internal bool find_or_generate_polymorphic_procedure(CheckerContext *old_c, E
 		return false;
 	}
 
+	if (base_entity->flags & EntityFlag_Disabled) {
+		return false;
+	}
+
 	String name = base_entity->token.string;
 
 	Type *src = base_type(base_entity->type);
@@ -462,7 +466,7 @@ gb_internal bool find_or_generate_polymorphic_procedure(CheckerContext *old_c, E
 
 
 	{
-		// LEAK TODO(bill): This is technically a memory leak as it has to generate the type twice
+		// LEAK NOTE(bill): This is technically a memory leak as it has to generate the type twice
 		bool prev_no_polymorphic_errors = nctx.no_polymorphic_errors;
 		defer (nctx.no_polymorphic_errors = prev_no_polymorphic_errors);
 		nctx.no_polymorphic_errors = false;
@@ -470,7 +474,7 @@ gb_internal bool find_or_generate_polymorphic_procedure(CheckerContext *old_c, E
 		// NOTE(bill): Reset scope from the failed procedure type
 		scope_reset(scope);
 
-		// LEAK TODO(bill): Cloning this AST may be leaky
+		// LEAK NOTE(bill): Cloning this AST may be leaky but this is not really an issue due to arena-based allocation
 		Ast *cloned_proc_type_node = clone_ast(pt->node);
 		success = check_procedure_type(&nctx, final_proc_type, cloned_proc_type_node, &operands);
 		if (!success) {
@@ -778,16 +782,6 @@ gb_internal i64 check_distance_between_types(CheckerContext *c, Operand *operand
 		}
 	}
 
-	// ^T <- rawptr
-#if 0
-	// TODO(bill): Should C-style (not C++) pointer cast be allowed?
-	if (is_type_pointer(dst) && is_type_rawptr(src)) {
-	    return true;
-	}
-#endif
-#if 1
-
-
 	// rawptr <- ^T
 	if (are_types_identical(type, t_rawptr) && is_type_pointer(src)) {
 		return 5;
@@ -808,7 +802,6 @@ gb_internal i64 check_distance_between_types(CheckerContext *c, Operand *operand
 			return 4;
 		}
 	}
-#endif
 
 	if (is_type_polymorphic(dst) && !is_type_polymorphic(src)) {
 		bool modify_type = !c->no_polymorphic_errors;
@@ -824,7 +817,6 @@ gb_internal i64 check_distance_between_types(CheckerContext *c, Operand *operand
 			}
 		}
 
-		// TODO(bill): Determine which rule is a better on in practice
 		if (dst->Union.variants.count == 1) {
 			Type *vt = dst->Union.variants[0];
 			i64 score = check_distance_between_types(c, operand, vt);
@@ -864,8 +856,8 @@ gb_internal i64 check_distance_between_types(CheckerContext *c, Operand *operand
 		}
 	}
 
-	if (is_type_relative_slice(dst)) {
-		i64 score = check_distance_between_types(c, operand, dst->RelativeSlice.slice_type);
+	if (is_type_relative_multi_pointer(dst)) {
+		i64 score = check_distance_between_types(c, operand, dst->RelativeMultiPointer.pointer_type);
 		if (score >= 0) {
 			return score+2;
 		}
@@ -1013,8 +1005,8 @@ gb_internal AstPackage *get_package_of_type(Type *type) {
 		case Type_RelativePointer:
 			type = type->RelativePointer.pointer_type;
 			continue;
-		case Type_RelativeSlice:
-			type = type->RelativeSlice.slice_type;
+		case Type_RelativeMultiPointer:
+			type = type->RelativeMultiPointer.pointer_type;
 			continue;
 		}
 		return nullptr;
@@ -1093,7 +1085,7 @@ gb_internal void check_assignment(CheckerContext *c, Operand *operand, Type *typ
 
 			// TODO(bill): is this a good enough error message?
 			error(operand->expr,
-			      "Cannot assign overloaded procedure '%s' to '%s' in %.*s",
+			      "Cannot assign overloaded procedure group '%s' to '%s' in %.*s",
 			      expr_str,
 			      op_type_str,
 			      LIT(context_name));
@@ -1120,7 +1112,6 @@ gb_internal void check_assignment(CheckerContext *c, Operand *operand, Type *typ
 
 		switch (operand->mode) {
 		case Addressing_Builtin:
-			// TODO(bill): Actually allow built in procedures to be passed around and thus be created on use
 			error(operand->expr,
 			      "Cannot assign built-in procedure '%s' in %.*s",
 			      expr_str,
@@ -1412,9 +1403,6 @@ gb_internal bool is_polymorphic_type_assignable(CheckerContext *c, Type *poly, T
 		return false;
 	case Type_Proc:
 		if (source->kind == Type_Proc) {
-			// return check_is_assignable_to(c, &o, poly);
-			// TODO(bill): Polymorphic type assignment
-			#if 1
 			TypeProc *x = &poly->Proc;
 			TypeProc *y = &source->Proc;
 			if (x->calling_convention != y->calling_convention) {
@@ -1447,7 +1435,6 @@ gb_internal bool is_polymorphic_type_assignable(CheckerContext *c, Type *poly, T
 			}
 
 			return true;
-			#endif
 		}
 		return false;
 	case Type_Map:
@@ -1699,7 +1686,6 @@ gb_internal bool check_unary_op(CheckerContext *c, Operand *o, Token op) {
 		gb_string_free(str);
 		return false;
 	}
-	// TODO(bill): Handle errors correctly
 	Type *type = base_type(core_array_type(o->type));
 	gbString str = nullptr;
 	switch (op.kind) {
@@ -1743,7 +1729,6 @@ gb_internal bool check_unary_op(CheckerContext *c, Operand *o, Token op) {
 gb_internal bool check_binary_op(CheckerContext *c, Operand *o, Token op) {
 	Type *main_type = o->type;
 
-	// TODO(bill): Handle errors correctly
 	Type *type = base_type(core_array_type(main_type));
 	Type *ct = core_type(type);
 
@@ -2261,7 +2246,7 @@ gb_internal bool check_is_not_addressable(CheckerContext *c, Operand *o) {
 }
 
 gb_internal void check_old_for_or_switch_value_usage(Ast *expr) {
-	if (!build_context.strict_style) {
+	if (!(build_context.strict_style || (check_vet_flags(expr) & VetFlag_Style))) {
 		return;
 	}
 
@@ -2351,7 +2336,7 @@ gb_internal void check_unary_expr(CheckerContext *c, Operand *o, Token op, Ast *
 				o->type = alloc_type_pointer(o->type);
 			}
 		} else {
-			if (build_context.strict_style && ast_node_expect(node, Ast_UnaryExpr)) {
+			if (ast_node_expect(node, Ast_UnaryExpr)) {
 				ast_node(ue, UnaryExpr, node);
 				check_old_for_or_switch_value_usage(ue->expr);
 			}
@@ -2775,8 +2760,6 @@ gb_internal void check_shift(CheckerContext *c, Operand *x, Operand *y, Ast *nod
 		gb_string_free(err_str);
 	}
 
-	// TODO(bill): Should we support shifts for fixed arrays and #simd vectors?
-
 	if (!is_type_integer(x->type)) {
 		gbString err_str = expr_to_string(x->expr);
 		error(node, "Shift operand '%s' must be an integer", err_str);
@@ -3099,7 +3082,7 @@ gb_internal void check_cast(CheckerContext *c, Operand *x, Type *type) {
 		update_untyped_expr_type(c, x->expr, final_type, true);
 	}
 
-	if (build_context.vet_extra) {
+	if (check_vet_flags(x->expr) & VetFlag_Extra) {
 		if (are_types_identical(x->type, type)) {
 			gbString str = type_to_string(type);
 			warning(x->expr, "Unneeded cast to the same type '%s'", str);
@@ -3171,7 +3154,7 @@ gb_internal bool check_transmute(CheckerContext *c, Ast *node, Operand *o, Type
 		return false;
 	}
 
-	if (build_context.vet_extra) {
+	if (check_vet_flags(node) & VetFlag_Extra) {
 		if (are_types_identical(o->type, dst_t)) {
 			gbString str = type_to_string(dst_t);
 			warning(o->expr, "Unneeded transmute to the same type '%s'", str);
@@ -4437,7 +4420,6 @@ gb_internal ExactValue get_constant_field_single(CheckerContext *c, ExactValue v
 	case_end;
 
 	default:
-		// TODO(bill): Should this be a general fallback?
 		if (success_) *success_ = true;
 		if (finish_) *finish_ = true;
 		return empty_exact_value;
@@ -4793,8 +4775,6 @@ gb_internal Entity *check_selector(CheckerContext *c, Operand *operand, Ast *nod
 	}
 
 	if (entity == nullptr && selector->kind == Ast_Ident && is_type_array(type_deref(operand->type))) {
-		// TODO(bill): Simd_Vector swizzling
-
 		String field_name = selector->Ident.token.string;
 		if (1 < field_name.len && field_name.len <= 4) {
 			u8 swizzles_xyzw[4] = {'x', 'y', 'z', 'w'};
@@ -5112,27 +5092,6 @@ gb_internal bool check_identifier_exists(Scope *s, Ast *node, bool nested = fals
 	return false;
 }
 
-gb_internal isize add_dependencies_from_unpacking(CheckerContext *c, Entity **lhs, isize lhs_count, isize tuple_index, isize tuple_count) {
-	if (lhs != nullptr && c->decl != nullptr) {
-		for (isize j = 0; (tuple_index + j) < lhs_count && j < tuple_count; j++) {
-			Entity *e = lhs[tuple_index + j];
-			if (e != nullptr) {
-				DeclInfo *decl = decl_info_of_entity(e);
-				if (decl != nullptr) {
-					rw_mutex_shared_lock(&decl->deps_mutex);
-					rw_mutex_lock(&c->decl->deps_mutex);
-					for (Entity *dep : decl->deps) {
-						ptr_set_add(&c->decl->deps, dep);
-					}
-					rw_mutex_unlock(&c->decl->deps_mutex);
-					rw_mutex_shared_unlock(&decl->deps_mutex);
-				}
-			}
-		}
-	}
-	return tuple_count;
-}
-
 gb_internal bool check_no_copy_assignment(Operand const &o, String const &context) {
 	if (o.type && is_type_no_copy(o.type)) {
 		Ast *expr = unparen_expr(o.expr);
@@ -5240,6 +5199,31 @@ enum UnpackFlag : u32 {
 
 
 gb_internal bool check_unpack_arguments(CheckerContext *ctx, Entity **lhs, isize lhs_count, Array<Operand> *operands, Slice<Ast *> const &rhs_arguments, UnpackFlags flags) {
+	auto const &add_dependencies_from_unpacking = [](CheckerContext *c, Entity **lhs, isize lhs_count, isize tuple_index, isize tuple_count) -> isize {
+		if (lhs == nullptr || c->decl == nullptr) {
+			return tuple_count;
+		}
+		for (isize j = 0; (tuple_index + j) < lhs_count && j < tuple_count; j++) {
+			Entity *e = lhs[tuple_index + j];
+			if (e == nullptr) {
+				continue;
+			}
+			DeclInfo *decl = decl_info_of_entity(e);
+			if (decl == nullptr) {
+				continue;
+			}
+			rw_mutex_shared_lock(&decl->deps_mutex);
+			rw_mutex_lock(&c->decl->deps_mutex);
+			for (Entity *dep : decl->deps) {
+				ptr_set_add(&c->decl->deps, dep);
+			}
+			rw_mutex_unlock(&c->decl->deps_mutex);
+			rw_mutex_shared_unlock(&decl->deps_mutex);
+		}
+		return tuple_count;
+	};
+
+
 	bool allow_ok    = (flags & UnpackFlag_AllowOk) != 0;
 	bool is_variadic = (flags & UnpackFlag_IsVariadic) != 0;
 	bool allow_undef = (flags & UnpackFlag_AllowUndef) != 0;
@@ -5494,6 +5478,8 @@ gb_internal CallArgumentError check_call_arguments_internal(CheckerContext *c, A
 
 	auto variadic_operands = slice(slice_from_array(positional_operands), positional_operand_count, positional_operands.count);
 
+	bool named_variadic_param = false;
+
 	if (named_operands.count != 0) {
 		GB_ASSERT(ce->split_args->named.count == named_operands.count);
 		for_array(i, ce->split_args->named) {
@@ -5519,6 +5505,9 @@ gb_internal CallArgumentError check_call_arguments_internal(CheckerContext *c, A
 				err = CallArgumentError_ParameterNotFound;
 				continue;
 			}
+			if (pt->variadic && param_index == pt->variadic_index) {
+				named_variadic_param = true;
+			}
 			if (visited[param_index]) {
 				if (show_error) {
 					error(arg, "Duplicate parameter '%.*s' in procedure call", LIT(name));
@@ -5720,11 +5709,6 @@ gb_internal CallArgumentError check_call_arguments_internal(CheckerContext *c, A
 				}
 				continue;
 			}
-
-			if (param_is_variadic) {
-				continue;
-			}
-
 			score += eval_param_and_score(c, o, e->type, err, param_is_variadic, e, show_error);
 		}
 	}
@@ -6136,7 +6120,6 @@ gb_internal CallArgumentData check_call_arguments_proc_group(CheckerContext *c,
 	{
 		// NOTE(bill, 2019-07-13): This code is used to improve the type inference for procedure groups
 		// where the same positional parameter has the same type value (and ellipsis)
-		bool proc_arg_count_all_equal = true;
 		isize proc_arg_count = -1;
 		for (Entity *p : procs) {
 			Type *pt = base_type(p->type);
@@ -6144,15 +6127,12 @@ gb_internal CallArgumentData check_call_arguments_proc_group(CheckerContext *c,
 				if (proc_arg_count < 0) {
 					proc_arg_count = pt->Proc.param_count;
 				} else {
-					if (proc_arg_count != pt->Proc.param_count) {
-						proc_arg_count_all_equal = false;
-						break;
-					}
+					proc_arg_count = gb_min(proc_arg_count, pt->Proc.param_count);
 				}
 			}
 		}
 
-		if (proc_arg_count >= 0 && proc_arg_count_all_equal) {
+		if (proc_arg_count >= 0) {
 			lhs_count = proc_arg_count;
 			if (lhs_count > 0)  {
 				lhs = gb_alloc_array(heap_allocator(), Entity *, lhs_count);
@@ -6258,14 +6238,18 @@ gb_internal CallArgumentData check_call_arguments_proc_group(CheckerContext *c,
 			}
 			isize index = i;
 
+			ValidIndexAndScore item = {};
+			item.score = data.score;
+
 			if (data.gen_entity != nullptr) {
 				array_add(&proc_entities, data.gen_entity);
 				index = proc_entities.count-1;
+
+				// prefer non-polymorphic procedures over polymorphic
+				item.score += assign_score_function(1);
 			}
 
-			ValidIndexAndScore item = {};
 			item.index = index;
-			item.score = data.score;
 			array_add(&valids, item);
 		}
 	}
@@ -6328,9 +6312,44 @@ gb_internal CallArgumentData check_call_arguments_proc_group(CheckerContext *c,
 			print_argument_types();
 		}
 
+		if (procs.count == 0) {
+			procs = proc_group_entities_cloned(c, *operand);
+		}
 		if (procs.count > 0) {
 			error_line("Did you mean to use one of the following:\n");
 		}
+		isize max_name_length = 0;
+		isize max_type_length = 0;
+		for (Entity *proc : procs) {
+			Type *t = base_type(proc->type);
+			if (t == t_invalid) continue;
+			String prefix = {};
+			String prefix_sep = {};
+			if (proc->pkg) {
+				prefix = proc->pkg->name;
+				prefix_sep = str_lit(".");
+			}
+			String name = proc->token.string;
+			max_name_length = gb_max(max_name_length, prefix.len + prefix_sep.len + name.len);
+
+			gbString pt;
+			if (t->Proc.node != nullptr) {
+				pt = expr_to_string(t->Proc.node);
+			} else {
+				pt = type_to_string(t);
+			}
+
+			max_type_length = gb_max(max_type_length, gb_string_length(pt));
+			gb_string_free(pt);
+		}
+
+		isize max_spaces = gb_max(max_name_length, max_type_length);
+		char *spaces = gb_alloc_array(temporary_allocator(), char, max_spaces+1);
+		for (isize i = 0; i < max_spaces; i++) {
+			spaces[i] = ' ';
+		}
+		spaces[max_spaces] = 0;
+
 		for (Entity *proc : procs) {
 			TokenPos pos = proc->token.pos;
 			Type *t = base_type(proc->type);
@@ -6350,12 +6369,23 @@ gb_internal CallArgumentData check_call_arguments_proc_group(CheckerContext *c,
 				prefix_sep = str_lit(".");
 			}
 			String name = proc->token.string;
+			isize len = prefix.len + prefix_sep.len + name.len;
+
+			int name_padding = cast(int)gb_max(max_name_length - len, 0);
+			int type_padding = cast(int)gb_max(max_type_length - gb_string_length(pt), 0);
 
 			char const *sep = "::";
 			if (proc->kind == Entity_Variable) {
 				sep = ":=";
 			}
-			error_line("\t%.*s%.*s%.*s %s %s at %s\n", LIT(prefix), LIT(prefix_sep), LIT(name), sep, pt, token_pos_to_string(pos));
+			error_line("\t%.*s%.*s%.*s %.*s%s %s %.*sat %s\n",
+			           LIT(prefix), LIT(prefix_sep), LIT(name),
+			           name_padding, spaces,
+			           sep,
+			           pt,
+			           type_padding, spaces,
+			           token_pos_to_string(pos)
+			);
 		}
 		if (procs.count > 0) {
 			error_line("\n");
@@ -6369,8 +6399,8 @@ gb_internal CallArgumentData check_call_arguments_proc_group(CheckerContext *c,
 		error(operand->expr, "Ambiguous procedure group call '%s' that match with the given arguments", expr_name);
 		print_argument_types();
 
-		for (isize i = 0; i < valids.count; i++) {
-			Entity *proc = proc_entities[valids[i].index];
+		for (auto const &valid : valids) {
+			Entity *proc = proc_entities[valid.index];
 			GB_ASSERT(proc != nullptr);
 			TokenPos pos = proc->token.pos;
 			Type *t = base_type(proc->type); GB_ASSERT(t->kind == Type_Proc);
@@ -7107,7 +7137,7 @@ gb_internal ExprKind check_call_expr(CheckerContext *c, Operand *operand, Ast *c
 		i32 id = operand->builtin_id;
 		Entity *e = entity_of_node(operand->expr);
 		if (e != nullptr && e->token.string == "expand_to_tuple") {
-			warning(operand->expr, "'expand_to_tuple' has been replaced with 'expand_values'");
+			error(operand->expr, "'expand_to_tuple' has been replaced with 'expand_values'");
 		}
 		if (!check_builtin_procedure(c, operand, call, id, type_hint)) {
 			operand->mode = Addressing_Invalid;
@@ -7128,6 +7158,7 @@ gb_internal ExprKind check_call_expr(CheckerContext *c, Operand *operand, Ast *c
 				c->decl->defer_used += 1;
 			}
 		}
+		add_entity_use(c, operand->expr, initial_entity);
 	}
 
 	if (operand->mode != Addressing_ProcGroup) {
@@ -7335,11 +7366,11 @@ gb_internal bool check_set_index_data(Operand *o, Type *t, bool indirection, i64
 		}
 		return true;
 
-	case Type_RelativeSlice:
+	case Type_RelativeMultiPointer:
 		{
-			Type *slice_type = base_type(t->RelativeSlice.slice_type);
-			GB_ASSERT(slice_type->kind == Type_Slice);
-			o->type = slice_type->Slice.elem;
+			Type *pointer_type = base_type(t->RelativeMultiPointer.pointer_type);
+			GB_ASSERT(pointer_type->kind == Type_MultiPointer);
+			o->type = pointer_type->MultiPointer.elem;
 			if (o->mode != Addressing_Constant) {
 				o->mode = Addressing_Variable;
 			}
@@ -9315,13 +9346,13 @@ gb_internal ExprKind check_selector_call_expr(CheckerContext *c, Operand *o, Ast
 	ExprKind kind = check_expr_base(c, &x, se->expr, nullptr);
 	c->allow_arrow_right_selector_expr = allow_arrow_right_selector_expr;
 
-	if (x.mode == Addressing_Invalid || x.type == t_invalid) {
+	if (x.mode == Addressing_Invalid || (x.type == t_invalid && x.mode != Addressing_ProcGroup)) {
 		o->mode = Addressing_Invalid;
 		o->type = t_invalid;
 		o->expr = node;
 		return kind;
 	}
-	if (!is_type_proc(x.type)) {
+	if (!is_type_proc(x.type) && x.mode != Addressing_ProcGroup) {
 		gbString type_str = type_to_string(x.type);
 		error(se->call, "Selector call expressions expect a procedure type for the call, got '%s'", type_str);
 		gb_string_free(type_str);
@@ -9344,76 +9375,76 @@ gb_internal ExprKind check_selector_call_expr(CheckerContext *c, Operand *o, Ast
 		first_arg->state_flags |= StateFlag_SelectorCallExpr;
 	}
 
-	Type *pt = base_type(x.type);
-	GB_ASSERT(pt->kind == Type_Proc);
-	Type *first_type = nullptr;
-	String first_arg_name = {};
-	if (pt->Proc.param_count > 0) {
-		Entity *f = pt->Proc.params->Tuple.variables[0];
-		first_type = f->type;
-		first_arg_name = f->token.string;
-	}
-	if (first_arg_name.len == 0) {
-		first_arg_name = str_lit("_");
-	}
+	if (e->kind != Entity_ProcGroup) {
+		Type *pt = base_type(x.type);
+		GB_ASSERT_MSG(pt->kind == Type_Proc, "%.*s %.*s %s", LIT(e->token.string), LIT(entity_strings[e->kind]), type_to_string(x.type));
+		Type *first_type = nullptr;
+		String first_arg_name = {};
+		if (pt->Proc.param_count > 0) {
+			Entity *f = pt->Proc.params->Tuple.variables[0];
+			first_type = f->type;
+			first_arg_name = f->token.string;
+		}
+		if (first_arg_name.len == 0) {
+			first_arg_name = str_lit("_");
+		}
 
-	if (first_type == nullptr) {
-		error(se->call, "Selector call expressions expect a procedure type for the call with at least 1 parameter");
-		o->mode = Addressing_Invalid;
-		o->type = t_invalid;
-		o->expr = node;
-		return Expr_Stmt;
-	}
+		if (first_type == nullptr) {
+			error(se->call, "Selector call expressions expect a procedure type for the call with at least 1 parameter");
+			o->mode = Addressing_Invalid;
+			o->type = t_invalid;
+			o->expr = node;
+			return Expr_Stmt;
+		}
 
-	Operand y = {};
-	y.mode = first_arg->tav.mode;
-	y.type = first_arg->tav.type;
-	y.value = first_arg->tav.value;
+		Operand y = {};
+		y.mode = first_arg->tav.mode;
+		y.type = first_arg->tav.type;
+		y.value = first_arg->tav.value;
 
-	if (check_is_assignable_to(c, &y, first_type)) {
-		// Do nothing, it's valid
-	} else {
-		Operand z = y;
-		z.type = type_deref(y.type);
-		if (check_is_assignable_to(c, &z, first_type)) {
-			// NOTE(bill): AST GENERATION HACK!
-			Token op = {Token_Pointer};
-			first_arg = ast_deref_expr(first_arg->file(), first_arg, op);
-		} else if (y.mode == Addressing_Variable) {
-			Operand w = y;
-			w.type = alloc_type_pointer(y.type);
-			if (check_is_assignable_to(c, &w, first_type)) {
+		if (check_is_assignable_to(c, &y, first_type)) {
+			// Do nothing, it's valid
+		} else {
+			Operand z = y;
+			z.type = type_deref(y.type);
+			if (check_is_assignable_to(c, &z, first_type)) {
 				// NOTE(bill): AST GENERATION HACK!
-				Token op = {Token_And};
-				first_arg = ast_unary_expr(first_arg->file(), op, first_arg);
+				Token op = {Token_Pointer};
+				first_arg = ast_deref_expr(first_arg->file(), first_arg, op);
+			} else if (y.mode == Addressing_Variable) {
+				Operand w = y;
+				w.type = alloc_type_pointer(y.type);
+				if (check_is_assignable_to(c, &w, first_type)) {
+					// NOTE(bill): AST GENERATION HACK!
+					Token op = {Token_And};
+					first_arg = ast_unary_expr(first_arg->file(), op, first_arg);
+				}
 			}
 		}
-	}
 
-	if (ce->args.count > 0) {
-		bool fail = false;
-		bool first_is_field_value = (ce->args[0]->kind == Ast_FieldValue);
-		for (Ast *arg : ce->args) {
-			bool mix = false;
-			if (first_is_field_value) {
-				mix = arg->kind != Ast_FieldValue;
-			} else {
-				mix = arg->kind == Ast_FieldValue;
+		if (ce->args.count > 0) {
+			bool fail = false;
+			bool first_is_field_value = (ce->args[0]->kind == Ast_FieldValue);
+			for (Ast *arg : ce->args) {
+				bool mix = false;
+				if (first_is_field_value) {
+					mix = arg->kind != Ast_FieldValue;
+				} else {
+					mix = arg->kind == Ast_FieldValue;
+				}
+				if (mix) {
+					fail = true;
+					break;
+				}
 			}
-			if (mix) {
-				fail = true;
-				break;
+			if (!fail && first_is_field_value) {
+				Token op = {Token_Eq};
+				AstFile *f = first_arg->file();
+				first_arg = ast_field_value(f, ast_ident(f, make_token_ident(first_arg_name)), first_arg, op);
 			}
 		}
-		if (!fail && first_is_field_value) {
-			Token op = {Token_Eq};
-			AstFile *f = first_arg->file();
-			first_arg = ast_field_value(f, ast_ident(f, make_token_ident(first_arg_name)), first_arg, op);
-		}
 	}
 
-
-
 	auto modified_args = slice_make<Ast *>(heap_allocator(), ce->args.count+1);
 	modified_args[0] = first_arg;
 	slice_copy(&modified_args, ce->args, 1);
@@ -9471,14 +9502,14 @@ gb_internal ExprKind check_index_expr(CheckerContext *c, Operand *o, Ast *node,
 
 	if (is_const) {
 		if (is_type_array(t)) {
-			// OKay
+			// Okay
 		} else if (is_type_slice(t)) {
 			// Okay
 		} else if (is_type_enumerated_array(t)) {
 			// Okay
 		} else if (is_type_string(t)) {
 			// Okay
-		} else if (is_type_relative_slice(t)) {
+		} else if (is_type_relative_multi_pointer(t)) {
 			// Okay
 		} else if (is_type_matrix(t)) {
 			// Okay
@@ -9616,17 +9647,9 @@ gb_internal ExprKind check_slice_expr(CheckerContext *c, Operand *o, Ast *node,
 		}
 		break;
 
-	case Type_RelativeSlice:
+	case Type_RelativeMultiPointer:
 		valid = true;
-		o->type = t->RelativeSlice.slice_type;
-		if (o->mode != Addressing_Variable) {
-			gbString str = expr_to_string(node);
-			error(node, "Cannot relative slice '%s', as value is not addressable", str);
-			gb_string_free(str);
-			o->mode = Addressing_Invalid;
-			o->expr = node;
-			return kind;
-		}
+		o->type = type_deref(o->type);
 		break;
 
 	case Type_EnumeratedArray:
@@ -9705,8 +9728,19 @@ gb_internal ExprKind check_slice_expr(CheckerContext *c, Operand *o, Ast *node,
 			x[i:n] -> []T
 		*/
 		o->type = alloc_type_slice(t->MultiPointer.elem);
+	} else if (t->kind == Type_RelativeMultiPointer && se->high != nullptr) {
+		/*
+			x[:]   -> [^]T
+			x[i:]  -> [^]T
+			x[:n]  -> []T
+			x[i:n] -> []T
+		*/
+		Type *pointer_type = base_type(t->RelativeMultiPointer.pointer_type);
+		GB_ASSERT(pointer_type->kind == Type_MultiPointer);
+		o->type = alloc_type_slice(pointer_type->MultiPointer.elem);
 	}
 
+
 	o->mode = Addressing_Value;
 
 	if (is_type_string(t) && max_count >= 0) {
@@ -9982,7 +10016,7 @@ gb_internal ExprKind check_expr_base_internal(CheckerContext *c, Operand *o, Ast
 			Type *type = type_of_expr(ac->expr);
 			check_cast(c, o, type_hint);
 			if (is_type_typed(type) && are_types_identical(type, type_hint)) {
-				if (build_context.vet_extra) {
+				if (check_vet_flags(node) & VetFlag_Extra) {
 					error(node, "Redundant 'auto_cast' applied to expression");
 				}
 			}

+ 11 - 6
src/check_stmt.cpp

@@ -384,7 +384,6 @@ gb_internal Type *check_assignment_variable(CheckerContext *ctx, Operand *lhs, O
 		}
 
 		if (e != nullptr) {
-			// HACK TODO(bill): Should the entities be freed as it's technically a leak
 			rhs->mode = Addressing_Value;
 			rhs->type = e->type;
 			rhs->proc_group = nullptr;
@@ -394,7 +393,7 @@ gb_internal Type *check_assignment_variable(CheckerContext *ctx, Operand *lhs, O
 			ast_node(i, Ident, node);
 			e = scope_lookup(ctx->scope, i->token.string);
 			if (e != nullptr && e->kind == Entity_Variable) {
-				used = (e->flags & EntityFlag_Used) != 0; // TODO(bill): Make backup just in case
+				used = (e->flags & EntityFlag_Used) != 0; // NOTE(bill): Make backup just in case
 			}
 		}
 
@@ -888,7 +887,7 @@ gb_internal void check_switch_stmt(CheckerContext *ctx, Ast *node, u32 mod_flags
 	check_open_scope(ctx, node);
 	defer (check_close_scope(ctx));
 
-	check_label(ctx, ss->label, node); // TODO(bill): What should the label's "scope" be?
+	check_label(ctx, ss->label, node);
 
 	if (ss->init != nullptr) {
 		check_stmt(ctx, ss->init, 0);
@@ -1125,7 +1124,7 @@ gb_internal void check_type_switch_stmt(CheckerContext *ctx, Ast *node, u32 mod_
 	check_open_scope(ctx, node);
 	defer (check_close_scope(ctx));
 
-	check_label(ctx, ss->label, node); // TODO(bill): What should the label's "scope" be?
+	check_label(ctx, ss->label, node);
 
 	if (ss->tag->kind != Ast_AssignStmt) {
 		error(ss->tag, "Expected an 'in' assignment for this type switch statement");
@@ -1960,7 +1959,7 @@ gb_internal void check_value_decl_stmt(CheckerContext *ctx, Ast *node, u32 mod_f
 		Token token = ast_token(node);
 		if (vd->type != nullptr && entity_count > 1) {
 			error(token, "'using' can only be applied to one variable of the same type");
-			// TODO(bill): Should a 'continue' happen here?
+			// NOTE(bill): `using` will only be applied to a single declaration
 		}
 
 		for (isize entity_index = 0; entity_index < 1; entity_index++) {
@@ -2294,7 +2293,7 @@ gb_internal void check_for_stmt(CheckerContext *ctx, Ast *node, u32 mod_flags) {
 	mod_flags |= Stmt_BreakAllowed | Stmt_ContinueAllowed;
 
 	check_open_scope(ctx, node);
-	check_label(ctx, fs->label, node); // TODO(bill): What should the label's "scope" be?
+	check_label(ctx, fs->label, node);
 
 	if (fs->init != nullptr) {
 		check_stmt(ctx, fs->init, 0);
@@ -2464,6 +2463,12 @@ gb_internal void check_stmt_internal(CheckerContext *ctx, Ast *node, u32 flags)
 			error(us->token, "Empty 'using' list");
 			return;
 		}
+		if (check_vet_flags(node) & VetFlag_UsingStmt) {
+			ERROR_BLOCK();
+			error(node, "'using' as a statement is not allowed when '-vet' or '-vet-using' is applied");
+			error_line("\t'using' is considered bad practice to use as a statement outside of immediate refactoring\n");
+		}
+
 		for (Ast *expr : us->list) {
 			expr = unparen_expr(expr);
 			Entity *e = nullptr;

+ 10 - 4
src/check_type.cpp

@@ -1474,6 +1474,12 @@ gb_internal Type *check_get_params(CheckerContext *ctx, Scope *scope, Ast *_para
 		Type *specialization = nullptr;
 
 		bool is_using = (p->flags&FieldFlag_using) != 0;
+		if ((check_vet_flags(param) & VetFlag_UsingParam) && is_using) {
+			ERROR_BLOCK();
+			error(param, "'using' on a procedure parameter is now allowed when '-vet' or '-vet-using-param' is applied");
+			error_line("\t'using' is considered bad practice to use as a statement/procedure parameter outside of immediate refactoring\n");
+
+		}
 
 		if (type_expr == nullptr) {
 			param_value = handle_parameter_value(ctx, nullptr, &type, default_value, true);
@@ -2772,16 +2778,16 @@ gb_internal bool check_type_internal(CheckerContext *ctx, Ast *e, Type **type, T
 
 		Type *relative_type = nullptr;
 		Type *base_type = check_type(ctx, rt->type);
-		if (!is_type_pointer(base_type) && !is_type_slice(base_type)) {
-			error(rt->type, "#relative types can only be a pointer or slice");
+		if (!is_type_pointer(base_type) && !is_type_multi_pointer(base_type)) {
+			error(rt->type, "#relative types can only be a pointer or multi-pointer");
 			relative_type = base_type;
 		} else if (base_integer == nullptr) {
 			relative_type = base_type;
 		} else {
 			if (is_type_pointer(base_type)) {
 				relative_type = alloc_type_relative_pointer(base_type, base_integer);
-			} else if (is_type_slice(base_type)) {
-				relative_type = alloc_type_relative_slice(base_type, base_integer);
+			} else if (is_type_multi_pointer(base_type)) {
+				relative_type = alloc_type_relative_multi_pointer(base_type, base_integer);
 			}
 		}
 		GB_ASSERT(relative_type != nullptr);

+ 123 - 44
src/checker.cpp

@@ -521,6 +521,28 @@ GB_COMPARE_PROC(entity_variable_pos_cmp) {
 }
 
 
+
+gb_internal u64 check_vet_flags(CheckerContext *c) {
+	AstFile *file = c->file;
+	if (file == nullptr &&
+	    c->curr_proc_decl &&
+	    c->curr_proc_decl->proc_lit) {
+		file = c->curr_proc_decl->proc_lit->file();
+	}
+	if (file && file->vet_flags_set) {
+		return file->vet_flags;
+	}
+	return build_context.vet_flags;
+}
+
+gb_internal u64 check_vet_flags(Ast *node) {
+	AstFile *file = node->file();
+	if (file && file->vet_flags_set) {
+		return file->vet_flags;
+	}
+	return build_context.vet_flags;
+}
+
 enum VettedEntityKind {
 	VettedEntity_Invalid,
 
@@ -655,9 +677,9 @@ gb_internal bool check_vet_unused(Checker *c, Entity *e, VettedEntity *ve) {
 	return false;
 }
 
-gb_internal void check_scope_usage(Checker *c, Scope *scope) {
-	bool vet_unused = true;
-	bool vet_shadowing = true;
+gb_internal void check_scope_usage(Checker *c, Scope *scope, u64 vet_flags) {
+	bool vet_unused = (vet_flags & VetFlag_Unused) != 0;
+	bool vet_shadowing = (vet_flags & (VetFlag_Shadowing|VetFlag_Using)) != 0;
 
 	Array<VettedEntity> vetted_entities = {};
 	array_init(&vetted_entities, heap_allocator());
@@ -691,15 +713,17 @@ gb_internal void check_scope_usage(Checker *c, Scope *scope) {
 
 		if (ve.kind == VettedEntity_Shadowed_And_Unused) {
 			error(e->token, "'%.*s' declared but not used, possibly shadows declaration at line %d", LIT(name), other->token.pos.line);
-		} else if (build_context.vet) {
+		} else if (vet_flags) {
 			switch (ve.kind) {
 			case VettedEntity_Unused:
-				error(e->token, "'%.*s' declared but not used", LIT(name));
+				if (vet_flags & VetFlag_Unused) {
+					error(e->token, "'%.*s' declared but not used", LIT(name));
+				}
 				break;
 			case VettedEntity_Shadowed:
-				if (e->flags&EntityFlag_Using) {
+				if ((vet_flags & (VetFlag_Shadowing|VetFlag_Using)) != 0 && e->flags&EntityFlag_Using) {
 					error(e->token, "Declaration of '%.*s' from 'using' shadows declaration at line %d", LIT(name), other->token.pos.line);
-				} else {
+				} else if ((vet_flags & (VetFlag_Shadowing)) != 0) {
 					error(e->token, "Declaration of '%.*s' shadows declaration at line %d", LIT(name), other->token.pos.line);
 				}
 				break;
@@ -726,7 +750,7 @@ gb_internal void check_scope_usage(Checker *c, Scope *scope) {
 		if (child->flags & (ScopeFlag_Proc|ScopeFlag_Type|ScopeFlag_File)) {
 			// Ignore these
 		} else {
-			check_scope_usage(c, child);
+			check_scope_usage(c, child, vet_flags);
 		}
 	}
 }
@@ -943,7 +967,6 @@ gb_internal void init_universal(void) {
 	add_global_bool_constant("true",  true);
 	add_global_bool_constant("false", false);
 
-	// TODO(bill): Set through flags in the compiler
 	add_global_string_constant("ODIN_VENDOR",  bc->ODIN_VENDOR);
 	add_global_string_constant("ODIN_VERSION", bc->ODIN_VERSION);
 	add_global_string_constant("ODIN_ROOT",    bc->ODIN_ROOT);
@@ -1046,6 +1069,7 @@ gb_internal void init_universal(void) {
 	add_global_bool_constant("ODIN_NO_RTTI",            bc->no_rtti);
 
 	add_global_bool_constant("ODIN_VALGRIND_SUPPORT",         bc->ODIN_VALGRIND_SUPPORT);
+	add_global_bool_constant("ODIN_TILDE",                    bc->tilde_backend);
 
 	add_global_constant("ODIN_COMPILE_TIMESTAMP", t_untyped_integer, exact_value_i64(odin_compile_timestamp()));
 
@@ -1114,6 +1138,7 @@ gb_internal void init_universal(void) {
 
 
 	t_u8_ptr       = alloc_type_pointer(t_u8);
+	t_u8_multi_ptr = alloc_type_multi_pointer(t_u8);
 	t_int_ptr      = alloc_type_pointer(t_int);
 	t_i64_ptr      = alloc_type_pointer(t_i64);
 	t_f64_ptr      = alloc_type_pointer(t_f64);
@@ -1453,7 +1478,6 @@ gb_internal void add_type_and_value(CheckerContext *ctx, Ast *expr, AddressingMo
 	if (ctx->decl) {
 		mutex = &ctx->decl->type_and_value_mutex;
 	} else if (ctx->pkg) {
-		// TODO(bill): is a per package mutex is a good idea here?
 		mutex = &ctx->pkg->type_and_value_mutex;
 	}
 
@@ -1581,30 +1605,28 @@ gb_internal void add_entity_use(CheckerContext *c, Ast *identifier, Entity *enti
 	if (entity == nullptr) {
 		return;
 	}
-	if (identifier != nullptr) {
-		if (identifier->kind != Ast_Ident) {
-			return;
-		}
-		Ast *empty_ident = nullptr;
-		entity->identifier.compare_exchange_strong(empty_ident, identifier);
-
-		identifier->Ident.entity = entity;
-
-		String dmsg = entity->deprecated_message;
-		if (dmsg.len > 0) {
-			warning(identifier, "%.*s is deprecated: %.*s", LIT(entity->token.string), LIT(dmsg));
-		}
-		String wmsg = entity->warning_message;
-		if (wmsg.len > 0) {
-			warning(identifier, "%.*s: %.*s", LIT(entity->token.string), LIT(wmsg));
-		}
-	}
-	entity->flags |= EntityFlag_Used;
 	add_declaration_dependency(c, entity);
+	entity->flags |= EntityFlag_Used;
 	if (entity_has_deferred_procedure(entity)) {
 		Entity *deferred = entity->Procedure.deferred_procedure.entity;
 		add_entity_use(c, nullptr, deferred);
 	}
+	if (identifier == nullptr || identifier->kind != Ast_Ident) {
+		return;
+	}
+	Ast *empty_ident = nullptr;
+	entity->identifier.compare_exchange_strong(empty_ident, identifier);
+
+	identifier->Ident.entity = entity;
+
+	String dmsg = entity->deprecated_message;
+	if (dmsg.len > 0) {
+		warning(identifier, "%.*s is deprecated: %.*s", LIT(entity->token.string), LIT(dmsg));
+	}
+	String wmsg = entity->warning_message;
+	if (wmsg.len > 0) {
+		warning(identifier, "%.*s: %.*s", LIT(entity->token.string), LIT(wmsg));
+	}
 }
 
 
@@ -1947,9 +1969,9 @@ gb_internal void add_type_info_type_internal(CheckerContext *c, Type *t) {
 		add_type_info_type_internal(c, bt->RelativePointer.base_integer);
 		break;
 
-	case Type_RelativeSlice:
-		add_type_info_type_internal(c, bt->RelativeSlice.slice_type);
-		add_type_info_type_internal(c, bt->RelativeSlice.base_integer);
+	case Type_RelativeMultiPointer:
+		add_type_info_type_internal(c, bt->RelativeMultiPointer.pointer_type);
+		add_type_info_type_internal(c, bt->RelativeMultiPointer.base_integer);
 		break;
 
 	case Type_Matrix:
@@ -2188,9 +2210,9 @@ gb_internal void add_min_dep_type_info(Checker *c, Type *t) {
 		add_min_dep_type_info(c, bt->RelativePointer.base_integer);
 		break;
 
-	case Type_RelativeSlice:
-		add_min_dep_type_info(c, bt->RelativeSlice.slice_type);
-		add_min_dep_type_info(c, bt->RelativeSlice.base_integer);
+	case Type_RelativeMultiPointer:
+		add_min_dep_type_info(c, bt->RelativeMultiPointer.pointer_type);
+		add_min_dep_type_info(c, bt->RelativeMultiPointer.base_integer);
 		break;
 
 	case Type_Matrix:
@@ -2310,7 +2332,9 @@ gb_internal void generate_minimum_dependency_set(Checker *c, Entity *start) {
 		str_lit("memory_equal"),
 		str_lit("memory_compare"),
 		str_lit("memory_compare_zero"),
+	);
 
+	FORCE_ADD_RUNTIME_ENTITIES(!build_context.tilde_backend,
 		// Extended data type internal procedures
 		str_lit("umodti3"),
 		str_lit("udivti3"),
@@ -2556,9 +2580,6 @@ gb_internal Array<EntityGraphNode *> generate_entity_dependency_graph(CheckerInf
 		}
 	}
 
-	// TODO(bill): This could be multithreaded to improve performance
-	// This means that the entity graph node set will have to be thread safe
-
 	TIME_SECTION("generate_entity_dependency_graph: Calculate edges for graph M - Part 2");
 	auto G = array_make<EntityGraphNode *>(allocator, 0, M.count);
 
@@ -2779,7 +2800,7 @@ gb_internal void init_core_type_info(Checker *c) {
 	t_type_info_bit_set          = find_core_type(c, str_lit("Type_Info_Bit_Set"));
 	t_type_info_simd_vector      = find_core_type(c, str_lit("Type_Info_Simd_Vector"));
 	t_type_info_relative_pointer = find_core_type(c, str_lit("Type_Info_Relative_Pointer"));
-	t_type_info_relative_slice   = find_core_type(c, str_lit("Type_Info_Relative_Slice"));
+	t_type_info_relative_multi_pointer = find_core_type(c, str_lit("Type_Info_Relative_Multi_Pointer"));
 	t_type_info_matrix           = find_core_type(c, str_lit("Type_Info_Matrix"));
 	t_type_info_soa_pointer      = find_core_type(c, str_lit("Type_Info_Soa_Pointer"));
 
@@ -2808,7 +2829,7 @@ gb_internal void init_core_type_info(Checker *c) {
 	t_type_info_bit_set_ptr          = alloc_type_pointer(t_type_info_bit_set);
 	t_type_info_simd_vector_ptr      = alloc_type_pointer(t_type_info_simd_vector);
 	t_type_info_relative_pointer_ptr = alloc_type_pointer(t_type_info_relative_pointer);
-	t_type_info_relative_slice_ptr   = alloc_type_pointer(t_type_info_relative_slice);
+	t_type_info_relative_multi_pointer_ptr = alloc_type_pointer(t_type_info_relative_multi_pointer);
 	t_type_info_matrix_ptr           = alloc_type_pointer(t_type_info_matrix);
 	t_type_info_soa_pointer_ptr      = alloc_type_pointer(t_type_info_soa_pointer);
 }
@@ -2935,6 +2956,60 @@ gb_internal DECL_ATTRIBUTE_PROC(foreign_block_decl_attribute) {
 	return false;
 }
 
+gb_internal DECL_ATTRIBUTE_PROC(proc_group_attribute) {
+	if (name == ATTRIBUTE_USER_TAG_NAME) {
+		ExactValue ev = check_decl_attribute_value(c, value);
+		if (ev.kind != ExactValue_String) {
+			error(elem, "Expected a string value for '%.*s'", LIT(name));
+		}
+		return true;
+	} else if (name == "objc_name") {
+		ExactValue ev = check_decl_attribute_value(c, value);
+		if (ev.kind == ExactValue_String) {
+			if (string_is_valid_identifier(ev.value_string)) {
+				ac->objc_name = ev.value_string;
+			} else {
+				error(elem, "Invalid identifier for '%.*s', got '%.*s'", LIT(name), LIT(ev.value_string));
+			}
+		} else {
+			error(elem, "Expected a string value for '%.*s'", LIT(name));
+		}
+		return true;
+	} else if (name == "objc_is_class_method") {
+		ExactValue ev = check_decl_attribute_value(c, value);
+		if (ev.kind == ExactValue_Bool) {
+			ac->objc_is_class_method = ev.value_bool;
+		} else {
+			error(elem, "Expected a boolean value for '%.*s'", LIT(name));
+		}
+		return true;
+	} else if (name == "objc_type") {
+		if (value == nullptr) {
+			error(elem, "Expected a type for '%.*s'", LIT(name));
+		} else {
+			Type *objc_type = check_type(c, value);
+			if (objc_type != nullptr) {
+				if (!has_type_got_objc_class_attribute(objc_type)) {
+					gbString t = type_to_string(objc_type);
+					error(value, "'%.*s' expected a named type with the attribute @(obj_class=<string>), got type %s", LIT(name), t);
+					gb_string_free(t);
+				} else {
+					ac->objc_type = objc_type;
+				}
+			}
+		}
+		return true;
+	} else if (name == "require_results") {
+		if (value != nullptr) {
+			error(elem, "Expected no value for '%.*s'", LIT(name));
+		}
+		ac->require_results = true;
+		return true;
+	}
+	return false;
+}
+
+
 gb_internal DECL_ATTRIBUTE_PROC(proc_decl_attribute) {
 	if (name == ATTRIBUTE_USER_TAG_NAME) {
 		ExactValue ev = check_decl_attribute_value(c, value);
@@ -3007,7 +3082,7 @@ gb_internal DECL_ATTRIBUTE_PROC(proc_decl_attribute) {
 			check_expr(c, &o, value);
 			Entity *e = entity_of_node(o.expr);
 			if (e != nullptr && e->kind == Entity_Procedure) {
-				warning(elem, "'%.*s' is deprecated, please use one of the following instead: 'deferred_none', 'deferred_in', 'deferred_out'", LIT(name));
+				error(elem, "'%.*s' is not allowed any more, please use one of the following instead: 'deferred_none', 'deferred_in', 'deferred_out'", LIT(name));
 				if (ac->deferred_procedure.entity != nullptr) {
 					error(elem, "Previous usage of a 'deferred_*' attribute");
 				}
@@ -4506,7 +4581,7 @@ gb_internal DECL_ATTRIBUTE_PROC(foreign_import_decl_attribute) {
 		if (value != nullptr) {
 			error(elem, "Expected no parameter for '%.*s'", LIT(name));
 		} else if (name == "force") {
-			warning(elem, "'force' is deprecated and is identical to 'require'");
+			error(elem, "'force' was replaced with 'require'");
 		}
 		ac->require_declaration = true;
 		return true;
@@ -5904,7 +5979,11 @@ gb_internal void check_parsed_files(Checker *c) {
 	TIME_SECTION("check scope usage");
 	for (auto const &entry : c->info.files) {
 		AstFile *f = entry.value;
-		check_scope_usage(c, f->scope);
+		u64 vet_flags = build_context.vet_flags;
+		if (f->vet_flags_set) {
+			vet_flags = f->vet_flags;
+		}
+		check_scope_usage(c, f->scope, vet_flags);
 	}
 
 	TIME_SECTION("add basic type information");
@@ -6022,7 +6101,7 @@ gb_internal void check_parsed_files(Checker *c) {
 		while (mpsc_dequeue(&c->info.intrinsics_entry_point_usage, &node)) {
 			if (c->info.entry_point == nullptr && node != nullptr) {
 				if (node->file()->pkg->kind != Package_Runtime) {
-					warning(node, "usage of intrinsics.__entry_point will be a no-op");
+					error(node, "usage of intrinsics.__entry_point will be a no-op");
 				}
 			}
 		}

+ 3 - 2
src/checker.hpp

@@ -387,8 +387,6 @@ struct CheckerInfo {
 	BlockingMutex foreign_mutex; // NOT recursive
 	StringMap<Entity *> foreigns;
 
-	// NOTE(bill): These are actually MPSC queues
-	// TODO(bill): Convert them to be MPSC queues
 	MPSCQueue<Entity *> definition_queue;
 	MPSCQueue<Entity *> entity_queue;
 	MPSCQueue<Entity *> required_global_variable_queue;
@@ -449,6 +447,9 @@ struct CheckerContext {
 	Ast *assignment_lhs_hint;
 };
 
+gb_internal u64 check_vet_flags(CheckerContext *c);
+gb_internal u64 check_vet_flags(Ast *node);
+
 
 struct Checker {
 	Parser *    parser;

+ 25 - 25
src/docs_format.cpp

@@ -59,31 +59,31 @@ struct OdinDocPosition {
 };
 
 enum OdinDocTypeKind : u32 {
-	OdinDocType_Invalid          = 0,
-	OdinDocType_Basic            = 1,
-	OdinDocType_Named            = 2,
-	OdinDocType_Generic          = 3,
-	OdinDocType_Pointer          = 4,
-	OdinDocType_Array            = 5,
-	OdinDocType_EnumeratedArray  = 6,
-	OdinDocType_Slice            = 7,
-	OdinDocType_DynamicArray     = 8,
-	OdinDocType_Map              = 9,
-	OdinDocType_Struct           = 10,
-	OdinDocType_Union            = 11,
-	OdinDocType_Enum             = 12,
-	OdinDocType_Tuple            = 13,
-	OdinDocType_Proc             = 14,
-	OdinDocType_BitSet           = 15,
-	OdinDocType_SimdVector       = 16,
-	OdinDocType_SOAStructFixed   = 17,
-	OdinDocType_SOAStructSlice   = 18,
-	OdinDocType_SOAStructDynamic = 19,
-	OdinDocType_RelativePointer  = 20,
-	OdinDocType_RelativeSlice    = 21,
-	OdinDocType_MultiPointer     = 22,
-	OdinDocType_Matrix           = 23,
-	OdinDocType_SoaPointer       = 24,
+	OdinDocType_Invalid              = 0,
+	OdinDocType_Basic                = 1,
+	OdinDocType_Named                = 2,
+	OdinDocType_Generic              = 3,
+	OdinDocType_Pointer              = 4,
+	OdinDocType_Array                = 5,
+	OdinDocType_EnumeratedArray      = 6,
+	OdinDocType_Slice                = 7,
+	OdinDocType_DynamicArray         = 8,
+	OdinDocType_Map                  = 9,
+	OdinDocType_Struct               = 10,
+	OdinDocType_Union                = 11,
+	OdinDocType_Enum                 = 12,
+	OdinDocType_Tuple                = 13,
+	OdinDocType_Proc                 = 14,
+	OdinDocType_BitSet               = 15,
+	OdinDocType_SimdVector           = 16,
+	OdinDocType_SOAStructFixed       = 17,
+	OdinDocType_SOAStructSlice       = 18,
+	OdinDocType_SOAStructDynamic     = 19,
+	OdinDocType_RelativePointer      = 20,
+	OdinDocType_RelativeMultiPointer = 21,
+	OdinDocType_MultiPointer         = 22,
+	OdinDocType_Matrix               = 23,
+	OdinDocType_SoaPointer           = 24,
 };
 
 enum OdinDocTypeFlag_Basic : u32 {

+ 4 - 4
src/docs_writer.cpp

@@ -771,12 +771,12 @@ gb_internal OdinDocTypeIndex odin_doc_type(OdinDocWriter *w, Type *type) {
 			doc_type.types = odin_write_slice(w, types, gb_count_of(types));
 		}
 		break;
-	case Type_RelativeSlice:
-		doc_type.kind = OdinDocType_RelativeSlice;
+	case Type_RelativeMultiPointer:
+		doc_type.kind = OdinDocType_RelativeMultiPointer;
 		{
 			OdinDocTypeIndex types[2] = {};
-			types[0] = odin_doc_type(w, type->RelativeSlice.slice_type);
-			types[1] = odin_doc_type(w, type->RelativeSlice.base_integer);
+			types[0] = odin_doc_type(w, type->RelativeMultiPointer.pointer_type);
+			types[1] = odin_doc_type(w, type->RelativeMultiPointer.base_integer);
 			doc_type.types = odin_write_slice(w, types, gb_count_of(types));
 		}
 		break;

+ 11 - 8
src/entity.cpp

@@ -2,8 +2,6 @@ struct Scope;
 struct Checker;
 struct Type;
 struct DeclInfo;
-struct lbModule;
-struct lbProcedure;
 
 
 #define ENTITY_KINDS \
@@ -183,8 +181,14 @@ struct Entity {
 
 	Entity *    aliased_of;
 
-	lbModule *   code_gen_module;
-	lbProcedure *code_gen_procedure;
+	union {
+		struct lbModule *code_gen_module;
+		struct cgModule *cg_module;
+	};
+	union {
+		struct lbProcedure *code_gen_procedure;
+		struct cgProcedure *cg_procedure;
+	};
 
 	u64         order_in_src;
 	String      deprecated_message;
@@ -287,7 +291,6 @@ gb_internal bool is_entity_kind_exported(EntityKind kind, bool allow_builtin = f
 }
 
 gb_internal bool is_entity_exported(Entity *e, bool allow_builtin = false) {
-	// TODO(bill): Determine the actual exportation rules for imports of entities
 	GB_ASSERT(e != nullptr);
 	if (!is_entity_kind_exported(e->kind, allow_builtin)) {
 		return false;
@@ -401,7 +404,7 @@ gb_internal Entity *alloc_entity_array_elem(Scope *scope, Token token, Type *typ
 	return entity;
 }
 
-gb_internal Entity *alloc_entity_procedure(Scope *scope, Token token, Type *signature_type, u64 tags) {
+gb_internal Entity *alloc_entity_procedure(Scope *scope, Token token, Type *signature_type, u64 tags=0) {
 	Entity *entity = alloc_entity(Entity_Procedure, scope, token, signature_type);
 	entity->Procedure.tags = tags;
 	return entity;
@@ -418,7 +421,7 @@ gb_internal Entity *alloc_entity_import_name(Scope *scope, Token token, Type *ty
 	entity->ImportName.path = path;
 	entity->ImportName.name = name;
 	entity->ImportName.scope = import_scope;
-	entity->state = EntityState_Resolved; // TODO(bill): Is this correct?
+	entity->state = EntityState_Resolved;
 	return entity;
 }
 
@@ -427,7 +430,7 @@ gb_internal Entity *alloc_entity_library_name(Scope *scope, Token token, Type *t
 	Entity *entity = alloc_entity(Entity_LibraryName, scope, token, type);
 	entity->LibraryName.paths = paths;
 	entity->LibraryName.name = name;
-	entity->state = EntityState_Resolved; // TODO(bill): Is this correct?
+	entity->state = EntityState_Resolved;
 	return entity;
 }
 

+ 2 - 2
src/error.cpp

@@ -411,7 +411,7 @@ gb_internal void error_line_va(char const *fmt, va_list va) {
 
 gb_internal void error_no_newline_va(TokenPos const &pos, char const *fmt, va_list va) {
 	mutex_lock(&global_error_collector.mutex);
-	global_error_collector.count++;
+	global_error_collector.count.fetch_add(1);
 	// NOTE(bill): Duplicate error, skip it
 	if (pos.line == 0) {
 		error_out_coloured("Error: ", TerminalStyle_Normal, TerminalColour_Red);
@@ -425,7 +425,7 @@ gb_internal void error_no_newline_va(TokenPos const &pos, char const *fmt, va_li
 		error_out_va(fmt, va);
 	}
 	mutex_unlock(&global_error_collector.mutex);
-	if (global_error_collector.count > MAX_ERROR_COLLECTOR_COUNT()) {
+	if (global_error_collector.count.load() > MAX_ERROR_COLLECTOR_COUNT()) {
 		gb_exit(1);
 	}
 }

+ 2 - 5
src/exact_value.cpp

@@ -26,8 +26,8 @@ enum ExactValueKind {
 	ExactValue_Complex    = 5,
 	ExactValue_Quaternion = 6,
 	ExactValue_Pointer    = 7,
-	ExactValue_Compound   = 8,  // TODO(bill): Is this good enough?
-	ExactValue_Procedure  = 9, // TODO(bill): Is this good enough?
+	ExactValue_Compound   = 8,
+	ExactValue_Procedure  = 9,
 	ExactValue_Typeid     = 10,
 
 	ExactValue_Count,
@@ -101,7 +101,6 @@ gb_internal ExactValue exact_value_bool(bool b) {
 }
 
 gb_internal ExactValue exact_value_string(String string) {
-	// TODO(bill): Allow for numbers with underscores in them
 	ExactValue result = {ExactValue_String};
 	result.value_string = string;
 	return result;
@@ -702,7 +701,6 @@ gb_internal void match_exact_values(ExactValue *x, ExactValue *y) {
 	compiler_error("match_exact_values: How'd you get here? Invalid ExactValueKind %d", x->kind);
 }
 
-// TODO(bill): Allow for pointer arithmetic? Or are pointer slices good enough?
 gb_internal ExactValue exact_binary_operator_value(TokenKind op, ExactValue x, ExactValue y) {
 	match_exact_values(&x, &y);
 
@@ -943,7 +941,6 @@ gb_internal bool compare_exact_values(TokenKind op, ExactValue x, ExactValue y)
 	case ExactValue_String: {
 		String a = x.value_string;
 		String b = y.value_string;
-		// TODO(bill): gb_memcompare is used because the strings are UTF-8
 		switch (op) {
 		case Token_CmpEq: return a == b;
 		case Token_NotEq: return a != b;

+ 460 - 0
src/linker.cpp

@@ -0,0 +1,460 @@
+struct LinkerData {
+	BlockingMutex foreign_mutex;
+	PtrSet<Entity *> foreign_libraries_set;
+	Array<Entity *>  foreign_libraries;
+
+	Array<String> output_object_paths;
+	Array<String> output_temp_paths;
+	String   output_base;
+	String   output_name;
+};
+
+gb_internal i32 system_exec_command_line_app(char const *name, char const *fmt, ...);
+
+gb_internal void linker_data_init(LinkerData *ld, CheckerInfo *info, String const &init_fullpath) {
+	gbAllocator ha = heap_allocator();
+	array_init(&ld->output_object_paths, ha);
+	array_init(&ld->output_temp_paths,   ha);
+	array_init(&ld->foreign_libraries,   ha, 0, 1024);
+	ptr_set_init(&ld->foreign_libraries_set, 1024);
+
+	if (build_context.out_filepath.len == 0) {
+		ld->output_name = remove_directory_from_path(init_fullpath);
+		ld->output_name = remove_extension_from_path(ld->output_name);
+		ld->output_name = string_trim_whitespace(ld->output_name);
+		if (ld->output_name.len == 0) {
+			ld->output_name = info->init_scope->pkg->name;
+		}
+		ld->output_base = ld->output_name;
+	} else {
+		ld->output_name = build_context.out_filepath;
+		ld->output_name = string_trim_whitespace(ld->output_name);
+		if (ld->output_name.len == 0) {
+			ld->output_name = info->init_scope->pkg->name;
+		}
+		isize pos = string_extension_position(ld->output_name);
+		if (pos < 0) {
+			ld->output_base = ld->output_name;
+		} else {
+			ld->output_base = substring(ld->output_name, 0, pos);
+		}
+	}
+
+	ld->output_base = path_to_full_path(ha, ld->output_base);
+
+}
+
+gb_internal i32 linker_stage(LinkerData *gen) {
+	i32 result = 0;
+	Timings *timings = &global_timings;
+
+	String output_filename = path_to_string(heap_allocator(), build_context.build_paths[BuildPath_Output]);
+	debugf("Linking %.*s\n", LIT(output_filename));
+
+	// TOOD(Jeroen): Make a `build_paths[BuildPath_Object] to avoid `%.*s.o`.
+
+	if (is_arch_wasm()) {
+		timings_start_section(timings, str_lit("wasm-ld"));
+
+	#if defined(GB_SYSTEM_WINDOWS)
+		result = system_exec_command_line_app("wasm-ld",
+			"\"%.*s\\bin\\wasm-ld\" \"%.*s.o\" -o \"%.*s\" %.*s %.*s",
+			LIT(build_context.ODIN_ROOT),
+			LIT(output_filename), LIT(output_filename), LIT(build_context.link_flags), LIT(build_context.extra_linker_flags));
+	#else
+		result = system_exec_command_line_app("wasm-ld",
+			"wasm-ld \"%.*s.o\" -o \"%.*s\" %.*s %.*s",
+			LIT(output_filename), LIT(output_filename), LIT(build_context.link_flags), LIT(build_context.extra_linker_flags));
+	#endif
+		return result;
+	}
+
+	if (build_context.cross_compiling && selected_target_metrics->metrics == &target_essence_amd64) {
+#if defined(GB_SYSTEM_UNIX)
+		result = system_exec_command_line_app("linker", "x86_64-essence-gcc \"%.*s.o\" -o \"%.*s\" %.*s %.*s",
+			LIT(output_filename), LIT(output_filename), LIT(build_context.link_flags), LIT(build_context.extra_linker_flags));
+#else
+		gb_printf_err("Linking for cross compilation for this platform is not yet supported (%.*s %.*s)\n",
+			LIT(target_os_names[build_context.metrics.os]),
+			LIT(target_arch_names[build_context.metrics.arch])
+		);
+#endif
+	} else if (build_context.cross_compiling && build_context.different_os) {
+		gb_printf_err("Linking for cross compilation for this platform is not yet supported (%.*s %.*s)\n",
+			LIT(target_os_names[build_context.metrics.os]),
+			LIT(target_arch_names[build_context.metrics.arch])
+		);
+		build_context.keep_object_files = true;
+	} else {
+	#if defined(GB_SYSTEM_WINDOWS)
+		bool is_windows = true;
+	#else
+		bool is_windows = false;
+	#endif
+	#if defined(GB_SYSTEM_OSX)
+		bool is_osx = true;
+	#else
+		bool is_osx = false;
+	#endif
+
+
+		if (is_windows) {
+			String section_name = str_lit("msvc-link");
+			if (build_context.use_lld) {
+				section_name = str_lit("lld-link");
+			}
+			timings_start_section(timings, section_name);
+
+			gbString lib_str = gb_string_make(heap_allocator(), "");
+			defer (gb_string_free(lib_str));
+
+			gbString link_settings = gb_string_make_reserve(heap_allocator(), 256);
+			defer (gb_string_free(link_settings));
+
+			// Add library search paths.
+			if (build_context.build_paths[BuildPath_VS_LIB].basename.len > 0) {
+				String path = {};
+				auto add_path = [&](String path) {
+					if (path[path.len-1] == '\\') {
+						path.len -= 1;
+					}
+					link_settings = gb_string_append_fmt(link_settings, " /LIBPATH:\"%.*s\"", LIT(path));
+				};
+				add_path(build_context.build_paths[BuildPath_Win_SDK_UM_Lib].basename);
+				add_path(build_context.build_paths[BuildPath_Win_SDK_UCRT_Lib].basename);
+				add_path(build_context.build_paths[BuildPath_VS_LIB].basename);
+			}
+
+
+			StringSet libs = {};
+			string_set_init(&libs, 64);
+			defer (string_set_destroy(&libs));
+
+			StringSet asm_files = {};
+			string_set_init(&asm_files, 64);
+			defer (string_set_destroy(&asm_files));
+
+			for (Entity *e : gen->foreign_libraries) {
+				GB_ASSERT(e->kind == Entity_LibraryName);
+				for_array(i, e->LibraryName.paths) {
+					String lib = string_trim_whitespace(e->LibraryName.paths[i]);
+					// IMPORTANT NOTE(bill): calling `string_to_lower` here is not an issue because
+					// we will never uses these strings afterwards
+					string_to_lower(&lib);
+					if (lib.len == 0) {
+						continue;
+					}
+
+					if (has_asm_extension(lib)) {
+						if (!string_set_update(&asm_files, lib)) {
+							String asm_file = asm_files.entries[i].value;
+							String obj_file = concatenate_strings(permanent_allocator(), asm_file, str_lit(".obj"));
+
+							result = system_exec_command_line_app("nasm",
+								"\"%.*s\\bin\\nasm\\windows\\nasm.exe\" \"%.*s\" "
+								"-f win64 "
+								"-o \"%.*s\" "
+								"%.*s "
+								"",
+								LIT(build_context.ODIN_ROOT), LIT(asm_file),
+								LIT(obj_file),
+								LIT(build_context.extra_assembler_flags)
+							);
+
+							if (result) {
+								return result;
+							}
+							array_add(&gen->output_object_paths, obj_file);
+						}
+					} else {
+						if (!string_set_update(&libs, lib)) {
+							lib_str = gb_string_append_fmt(lib_str, " \"%.*s\"", LIT(lib));
+						}
+					}
+				}
+			}
+
+			for (Entity *e : gen->foreign_libraries) {
+				GB_ASSERT(e->kind == Entity_LibraryName);
+				if (e->LibraryName.extra_linker_flags.len != 0) {
+					lib_str = gb_string_append_fmt(lib_str, " %.*s", LIT(e->LibraryName.extra_linker_flags));
+				}
+			}
+
+			if (build_context.build_mode == BuildMode_DynamicLibrary) {
+				link_settings = gb_string_append_fmt(link_settings, " /DLL");
+			} else {
+				link_settings = gb_string_append_fmt(link_settings, " /ENTRY:mainCRTStartup");
+			}
+
+			if (build_context.pdb_filepath != "") {
+				String pdb_path = path_to_string(heap_allocator(), build_context.build_paths[BuildPath_PDB]);
+				link_settings = gb_string_append_fmt(link_settings, " /PDB:%.*s", LIT(pdb_path));
+			}
+
+			if (build_context.no_crt) {
+				link_settings = gb_string_append_fmt(link_settings, " /nodefaultlib");
+			} else {
+				link_settings = gb_string_append_fmt(link_settings, " /defaultlib:libcmt");
+			}
+
+			if (build_context.ODIN_DEBUG) {
+				link_settings = gb_string_append_fmt(link_settings, " /DEBUG");
+			}
+
+			gbString object_files = gb_string_make(heap_allocator(), "");
+			defer (gb_string_free(object_files));
+			for (String const &object_path : gen->output_object_paths) {
+				object_files = gb_string_append_fmt(object_files, "\"%.*s\" ", LIT(object_path));
+			}
+
+			String vs_exe_path = path_to_string(heap_allocator(), build_context.build_paths[BuildPath_VS_EXE]);
+			defer (gb_free(heap_allocator(), vs_exe_path.text));
+
+			String windows_sdk_bin_path = path_to_string(heap_allocator(), build_context.build_paths[BuildPath_Win_SDK_Bin_Path]);
+			defer (gb_free(heap_allocator(), windows_sdk_bin_path.text));
+
+			char const *subsystem_str = build_context.use_subsystem_windows ? "WINDOWS" : "CONSOLE";
+			if (!build_context.use_lld) { // msvc
+				String res_path = {};
+				defer (gb_free(heap_allocator(), res_path.text));
+				if (build_context.has_resource) {
+					String temp_res_path = path_to_string(heap_allocator(), build_context.build_paths[BuildPath_RES]);
+					res_path = concatenate3_strings(heap_allocator(), str_lit("\""), temp_res_path, str_lit("\""));
+					gb_free(heap_allocator(), temp_res_path.text);
+
+					String rc_path  = path_to_string(heap_allocator(), build_context.build_paths[BuildPath_RC]);
+					defer (gb_free(heap_allocator(), rc_path.text));
+
+					result = system_exec_command_line_app("msvc-link",
+						"\"%.*src.exe\" /nologo /fo \"%.*s\" \"%.*s\"",
+						LIT(windows_sdk_bin_path),
+						LIT(res_path),
+						LIT(rc_path)
+					);
+
+					if (result) {
+						return result;
+					}
+				}
+
+				switch (build_context.build_mode) {
+				case BuildMode_Executable:
+					link_settings = gb_string_append_fmt(link_settings, " /NOIMPLIB /NOEXP");
+					break;
+				}
+
+				result = system_exec_command_line_app("msvc-link",
+					"\"%.*slink.exe\" %s %.*s -OUT:\"%.*s\" %s "
+					"/nologo /incremental:no /opt:ref /subsystem:%s "
+					"%.*s "
+					"%.*s "
+					"%s "
+					"",
+					LIT(vs_exe_path), object_files, LIT(res_path), LIT(output_filename),
+					link_settings,
+					subsystem_str,
+					LIT(build_context.link_flags),
+					LIT(build_context.extra_linker_flags),
+					lib_str
+				);
+				if (result) {
+					return result;
+				}
+			} else { // lld
+				result = system_exec_command_line_app("msvc-lld-link",
+					"\"%.*s\\bin\\lld-link\" %s -OUT:\"%.*s\" %s "
+					"/nologo /incremental:no /opt:ref /subsystem:%s "
+					"%.*s "
+					"%.*s "
+					"%s "
+					"",
+					LIT(build_context.ODIN_ROOT), object_files, LIT(output_filename),
+					link_settings,
+					subsystem_str,
+					LIT(build_context.link_flags),
+					LIT(build_context.extra_linker_flags),
+					lib_str
+				);
+
+				if (result) {
+					return result;
+				}
+			}
+		} else {
+			timings_start_section(timings, str_lit("ld-link"));
+
+			// NOTE(vassvik): get cwd, for used for local shared libs linking, since those have to be relative to the exe
+			char cwd[256];
+			#if !defined(GB_SYSTEM_WINDOWS)
+			getcwd(&cwd[0], 256);
+			#endif
+			//printf("%s\n", cwd);
+
+			// NOTE(vassvik): needs to add the root to the library search paths, so that the full filenames of the library
+			//                files can be passed with -l:
+			gbString lib_str = gb_string_make(heap_allocator(), "-L/");
+			defer (gb_string_free(lib_str));
+
+			StringSet libs = {};
+			string_set_init(&libs, 64);
+			defer (string_set_destroy(&libs));
+
+			for (Entity *e : gen->foreign_libraries) {
+				GB_ASSERT(e->kind == Entity_LibraryName);
+				for (String lib : e->LibraryName.paths) {
+					lib = string_trim_whitespace(lib);
+					if (lib.len == 0) {
+						continue;
+					}
+					if (string_set_update(&libs, lib)) {
+						continue;
+					}
+
+					// NOTE(zangent): Sometimes, you have to use -framework on MacOS.
+					//   This allows you to specify '-f' in a #foreign_system_library,
+					//   without having to implement any new syntax specifically for MacOS.
+					if (build_context.metrics.os == TargetOs_darwin) {
+						if (string_ends_with(lib, str_lit(".framework"))) {
+							// framework thingie
+							String lib_name = lib;
+							lib_name = remove_extension_from_path(lib_name);
+							lib_str = gb_string_append_fmt(lib_str, " -framework %.*s ", LIT(lib_name));
+						} else if (string_ends_with(lib, str_lit(".a")) || string_ends_with(lib, str_lit(".o")) || string_ends_with(lib, str_lit(".dylib"))) {
+							// For:
+							// object
+							// dynamic lib
+							// static libs, absolute full path relative to the file in which the lib was imported from
+							lib_str = gb_string_append_fmt(lib_str, " %.*s ", LIT(lib));
+						} else {
+							// dynamic or static system lib, just link regularly searching system library paths
+							lib_str = gb_string_append_fmt(lib_str, " -l%.*s ", LIT(lib));
+						}
+					} else {
+						// NOTE(vassvik): static libraries (.a files) in linux can be linked to directly using the full path,
+						//                since those are statically linked to at link time. shared libraries (.so) has to be
+						//                available at runtime wherever the executable is run, so we make require those to be
+						//                local to the executable (unless the system collection is used, in which case we search
+						//                the system library paths for the library file).
+						if (string_ends_with(lib, str_lit(".a")) || string_ends_with(lib, str_lit(".o"))) {
+							// static libs and object files, absolute full path relative to the file in which the lib was imported from
+							lib_str = gb_string_append_fmt(lib_str, " -l:\"%.*s\" ", LIT(lib));
+						} else if (string_ends_with(lib, str_lit(".so"))) {
+							// dynamic lib, relative path to executable
+							// NOTE(vassvik): it is the user's responsibility to make sure the shared library files are visible
+							//                at runtime to the executable
+							lib_str = gb_string_append_fmt(lib_str, " -l:\"%s/%.*s\" ", cwd, LIT(lib));
+						} else {
+							// dynamic or static system lib, just link regularly searching system library paths
+							lib_str = gb_string_append_fmt(lib_str, " -l%.*s ", LIT(lib));
+						}
+					}
+				}
+			}
+
+			for (Entity *e : gen->foreign_libraries) {
+				GB_ASSERT(e->kind == Entity_LibraryName);
+				if (e->LibraryName.extra_linker_flags.len != 0) {
+					lib_str = gb_string_append_fmt(lib_str, " %.*s", LIT(e->LibraryName.extra_linker_flags));
+				}
+			}
+
+			gbString object_files = gb_string_make(heap_allocator(), "");
+			defer (gb_string_free(object_files));
+			for (String object_path : gen->output_object_paths) {
+				object_files = gb_string_append_fmt(object_files, "\"%.*s\" ", LIT(object_path));
+			}
+
+			gbString link_settings = gb_string_make_reserve(heap_allocator(), 32);
+
+			if (build_context.no_crt) {
+				link_settings = gb_string_append_fmt(link_settings, "-nostdlib ");
+			}
+
+			// NOTE(dweiler): We use clang as a frontend for the linker as there are
+			// other runtime and compiler support libraries that need to be linked in
+			// very specific orders such as libgcc_s, ld-linux-so, unwind, etc.
+			// These are not always typically inside /lib, /lib64, or /usr versions
+			// of that, e.g libgcc.a is in /usr/lib/gcc/{version}, and can vary on
+			// the distribution of Linux even. The gcc or clang specs is the only
+			// reliable way to query this information to call ld directly.
+			if (build_context.build_mode == BuildMode_DynamicLibrary) {
+				// NOTE(dweiler): Let the frontend know we're building a shared library
+				// so it doesn't generate symbols which cannot be relocated.
+				link_settings = gb_string_appendc(link_settings, "-shared ");
+
+				// NOTE(dweiler): _odin_entry_point must be called at initialization
+				// time of the shared object, similarly, _odin_exit_point must be called
+				// at deinitialization. We can pass both -init and -fini to the linker by
+				// using a comma separated list of arguments to -Wl.
+				//
+				// This previously used ld but ld cannot actually build a shared library
+				// correctly this way since all the other dependencies provided implicitly
+				// by the compiler frontend are still needed and most of the command
+				// line arguments prepared previously are incompatible with ld.
+				if (build_context.metrics.os == TargetOs_darwin) {
+					link_settings = gb_string_appendc(link_settings, "-Wl,-init,'__odin_entry_point' ");
+					// NOTE(weshardee): __odin_exit_point should also be added, but -fini
+					// does not exist on MacOS
+				} else {
+					link_settings = gb_string_appendc(link_settings, "-Wl,-init,'_odin_entry_point' ");
+					link_settings = gb_string_appendc(link_settings, "-Wl,-fini,'_odin_exit_point' ");
+				}
+
+			} else if (build_context.metrics.os != TargetOs_openbsd) {
+				// OpenBSD defaults to PIE executable. do not pass -no-pie for it.
+				link_settings = gb_string_appendc(link_settings, "-no-pie ");
+			}
+
+			gbString platform_lib_str = gb_string_make(heap_allocator(), "");
+			defer (gb_string_free(platform_lib_str));
+			if (build_context.metrics.os == TargetOs_darwin) {
+				platform_lib_str = gb_string_appendc(platform_lib_str, "-lSystem -lm -Wl,-syslibroot /Library/Developer/CommandLineTools/SDKs/MacOSX.sdk -L/usr/local/lib");
+			} else {
+				platform_lib_str = gb_string_appendc(platform_lib_str, "-lc -lm");
+			}
+
+			if (build_context.metrics.os == TargetOs_darwin) {
+				// This sets a requirement of Mountain Lion and up, but the compiler doesn't work without this limit.
+				if (build_context.minimum_os_version_string.len) {
+					link_settings = gb_string_append_fmt(link_settings, " -mmacosx-version-min=%.*s ", LIT(build_context.minimum_os_version_string));
+				} else if (build_context.metrics.arch == TargetArch_arm64) {
+					link_settings = gb_string_appendc(link_settings, " -mmacosx-version-min=12.0.0  ");
+				} else {
+					link_settings = gb_string_appendc(link_settings, " -mmacosx-version-min=10.12.0 ");
+				}
+				// This points the linker to where the entry point is
+				link_settings = gb_string_appendc(link_settings, " -e _main ");
+			}
+
+			gbString link_command_line = gb_string_make(heap_allocator(), "clang -Wno-unused-command-line-argument ");
+			defer (gb_string_free(link_command_line));
+
+			link_command_line = gb_string_appendc(link_command_line, object_files);
+			link_command_line = gb_string_append_fmt(link_command_line, " -o \"%.*s\" ", LIT(output_filename));
+			link_command_line = gb_string_append_fmt(link_command_line, " %s ", platform_lib_str);
+			link_command_line = gb_string_append_fmt(link_command_line, " %s ", lib_str);
+			link_command_line = gb_string_append_fmt(link_command_line, " %.*s ", LIT(build_context.link_flags));
+			link_command_line = gb_string_append_fmt(link_command_line, " %.*s ", LIT(build_context.extra_linker_flags));
+			link_command_line = gb_string_append_fmt(link_command_line, " %s ", link_settings);
+
+			result = system_exec_command_line_app("ld-link", link_command_line);
+
+			if (result) {
+				return result;
+			}
+
+			if (is_osx && build_context.ODIN_DEBUG) {
+				// NOTE: macOS links DWARF symbols dynamically. Dsymutil will map the stubs in the exe
+				// to the symbols in the object file
+				result = system_exec_command_line_app("dsymutil", "dsymutil %.*s", LIT(output_filename));
+
+				if (result) {
+					return result;
+				}
+			}
+		}
+	}
+
+	return result;
+}

+ 3 - 10
src/llvm_backend.hpp

@@ -69,7 +69,6 @@ enum lbAddrKind {
 	lbAddr_SoaVariable,
 
 	lbAddr_RelativePointer,
-	lbAddr_RelativeSlice,
 
 	lbAddr_Swizzle,
 	lbAddr_SwizzleLarge,
@@ -190,13 +189,9 @@ struct lbModule {
 	LLVMPassManagerRef function_pass_managers[lbFunctionPassManager_COUNT];
 };
 
-struct lbGenerator {
+struct lbGenerator : LinkerData {
 	CheckerInfo *info;
 
-	Array<String> output_object_paths;
-	Array<String> output_temp_paths;
-	String   output_base;
-	String   output_name;
 	PtrMap<void *, lbModule *> modules; // key is `AstPackage *` (`void *` is used for future use)
 	PtrMap<LLVMContextRef, lbModule *> modules_through_ctx; 
 	lbModule default_module;
@@ -204,10 +199,6 @@ struct lbGenerator {
 	RecursiveMutex anonymous_proc_lits_mutex;
 	PtrMap<Ast *, lbProcedure *> anonymous_proc_lits; 
 
-	BlockingMutex foreign_mutex;
-	PtrSet<Entity *> foreign_libraries_set;
-	Array<Entity *>  foreign_libraries;
-
 	std::atomic<u32> global_array_index;
 	std::atomic<u32> global_generated_index;
 
@@ -346,7 +337,9 @@ struct lbProcedure {
 };
 
 
+#ifndef ABI_PKG_NAME_SEPARATOR
 #define ABI_PKG_NAME_SEPARATOR "."
+#endif
 
 
 #if !ODIN_LLVM_MINIMUM_VERSION_14

+ 67 - 69
src/llvm_backend_const.cpp

@@ -567,7 +567,7 @@ gb_internal lbValue lb_const_value(lbModule *m, Type *type, ExactValue value, bo
 
 		}
 	} else if (is_type_array(type) && value.kind == ExactValue_String && !is_type_u8(core_array_type(type))) {
-		if (is_type_rune_array(type) && value.kind == ExactValue_String) {
+		if (is_type_rune_array(type)) {
 			i64 count  = type->Array.count;
 			Type *elem = type->Array.elem;
 			LLVMTypeRef et = lb_type(m, elem);
@@ -1036,86 +1036,84 @@ gb_internal lbValue lb_const_value(lbModule *m, Type *type, ExactValue value, bo
 			LLVMValueRef *values = gb_alloc_array(temporary_allocator(), LLVMValueRef, value_count);
 			bool *visited = gb_alloc_array(temporary_allocator(), bool, value_count);
 
-			if (cl->elems.count > 0) {
-				if (cl->elems[0]->kind == Ast_FieldValue) {
-					isize elem_count = cl->elems.count;
-					for (isize i = 0; i < elem_count; i++) {
-						ast_node(fv, FieldValue, cl->elems[i]);
-						String name = fv->field->Ident.token.string;
+			if (cl->elems[0]->kind == Ast_FieldValue) {
+				isize elem_count = cl->elems.count;
+				for (isize i = 0; i < elem_count; i++) {
+					ast_node(fv, FieldValue, cl->elems[i]);
+					String name = fv->field->Ident.token.string;
 
-						TypeAndValue tav = fv->value->tav;
-						GB_ASSERT(tav.mode != Addressing_Invalid);
+					TypeAndValue tav = fv->value->tav;
+					GB_ASSERT(tav.mode != Addressing_Invalid);
 
-						Selection sel = lookup_field(type, name, false);
-						GB_ASSERT(!sel.indirect);
+					Selection sel = lookup_field(type, name, false);
+					GB_ASSERT(!sel.indirect);
 
-						Entity *f = type->Struct.fields[sel.index[0]];
-						i32 index = field_remapping[f->Variable.field_index];
-						if (elem_type_can_be_constant(f->type)) {
-							if (sel.index.count == 1) {
-								values[index] = lb_const_value(m, f->type, tav.value, allow_local).value;
+					Entity *f = type->Struct.fields[sel.index[0]];
+					i32 index = field_remapping[f->Variable.field_index];
+					if (elem_type_can_be_constant(f->type)) {
+						if (sel.index.count == 1) {
+							values[index] = lb_const_value(m, f->type, tav.value, allow_local).value;
+							visited[index] = true;
+						} else {
+							if (!visited[index]) {
+								values[index] = lb_const_value(m, f->type, {}, false).value;
 								visited[index] = true;
-							} else {
-								if (!visited[index]) {
-									values[index] = lb_const_value(m, f->type, {}, false).value;
-									visited[index] = true;
-								}
-								unsigned idx_list_len = cast(unsigned)sel.index.count-1;
-								unsigned *idx_list = gb_alloc_array(temporary_allocator(), unsigned, idx_list_len);
-
-								if (lb_is_nested_possibly_constant(type, sel, fv->value)) {
-									bool is_constant = true;
-									Type *cv_type = f->type;
-									for (isize j = 1; j < sel.index.count; j++) {
-										i32 index = sel.index[j];
-										Type *cvt = base_type(cv_type);
-
-										if (cvt->kind == Type_Struct) {
-											if (cvt->Struct.is_raw_union) {
-												// sanity check which should have been caught by `lb_is_nested_possibly_constant`
-												is_constant = false;
-												break;
-											}
-											cv_type = cvt->Struct.fields[index]->type;
-
-											if (is_type_struct(cvt)) {
-												auto cv_field_remapping = lb_get_struct_remapping(m, cvt);
-												unsigned remapped_index = cast(unsigned)cv_field_remapping[index];
-												idx_list[j-1] = remapped_index;
-											} else {
-												idx_list[j-1] = cast(unsigned)index;
-											}
-										} else if (cvt->kind == Type_Array) {
-											cv_type = cvt->Array.elem;
+							}
+							unsigned idx_list_len = cast(unsigned)sel.index.count-1;
+							unsigned *idx_list = gb_alloc_array(temporary_allocator(), unsigned, idx_list_len);
+
+							if (lb_is_nested_possibly_constant(type, sel, fv->value)) {
+								bool is_constant = true;
+								Type *cv_type = f->type;
+								for (isize j = 1; j < sel.index.count; j++) {
+									i32 index = sel.index[j];
+									Type *cvt = base_type(cv_type);
+
+									if (cvt->kind == Type_Struct) {
+										if (cvt->Struct.is_raw_union) {
+											// sanity check which should have been caught by `lb_is_nested_possibly_constant`
+											is_constant = false;
+											break;
+										}
+										cv_type = cvt->Struct.fields[index]->type;
 
-											idx_list[j-1] = cast(unsigned)index;
+										if (is_type_struct(cvt)) {
+											auto cv_field_remapping = lb_get_struct_remapping(m, cvt);
+											unsigned remapped_index = cast(unsigned)cv_field_remapping[index];
+											idx_list[j-1] = remapped_index;
 										} else {
-											GB_PANIC("UNKNOWN TYPE: %s", type_to_string(cv_type));
+											idx_list[j-1] = cast(unsigned)index;
 										}
+									} else if (cvt->kind == Type_Array) {
+										cv_type = cvt->Array.elem;
+
+										idx_list[j-1] = cast(unsigned)index;
+									} else {
+										GB_PANIC("UNKNOWN TYPE: %s", type_to_string(cv_type));
 									}
-									if (is_constant) {
-										LLVMValueRef elem_value = lb_const_value(m, tav.type, tav.value, allow_local).value;
-										GB_ASSERT(LLVMIsConstant(elem_value));
-										values[index] = LLVMConstInsertValue(values[index], elem_value, idx_list, idx_list_len);
-									}
+								}
+								if (is_constant) {
+									LLVMValueRef elem_value = lb_const_value(m, tav.type, tav.value, allow_local).value;
+									GB_ASSERT(LLVMIsConstant(elem_value));
+									values[index] = LLVMConstInsertValue(values[index], elem_value, idx_list, idx_list_len);
 								}
 							}
 						}
 					}
-				} else {
-					for_array(i, cl->elems) {
-						Entity *f = type->Struct.fields[i];
-						TypeAndValue tav = cl->elems[i]->tav;
-						ExactValue val = {};
-						if (tav.mode != Addressing_Invalid) {
-							val = tav.value;
-						}
-						
-						i32 index = field_remapping[f->Variable.field_index];
-						if (elem_type_can_be_constant(f->type)) {
-							values[index]  = lb_const_value(m, f->type, val, allow_local).value;
-							visited[index] = true;
-						}
+				}
+			} else {
+				for_array(i, cl->elems) {
+					Entity *f = type->Struct.fields[i];
+					TypeAndValue tav = cl->elems[i]->tav;
+					ExactValue val = {};
+					if (tav.mode != Addressing_Invalid) {
+						val = tav.value;
+					}
+
+					i32 index = field_remapping[f->Variable.field_index];
+					if (elem_type_can_be_constant(f->type)) {
+						values[index]  = lb_const_value(m, f->type, val, allow_local).value;
+						visited[index] = true;
 					}
 				}
 			}

+ 5 - 12
src/llvm_backend_debug.cpp

@@ -442,19 +442,12 @@ gb_internal LLVMMetadataRef lb_debug_type_internal(lbModule *m, Type *type) {
 		gbString name = type_to_string(type, temporary_allocator());
 		return LLVMDIBuilderCreateTypedef(m->debug_builder, base_integer, name, gb_string_length(name), nullptr, 0, nullptr, cast(u32)(8*type_align_of(type)));
 	}
+	case Type_RelativeMultiPointer: {
+		LLVMMetadataRef base_integer = lb_debug_type(m, type->RelativeMultiPointer.base_integer);
+		gbString name = type_to_string(type, temporary_allocator());
+		return LLVMDIBuilderCreateTypedef(m->debug_builder, base_integer, name, gb_string_length(name), nullptr, 0, nullptr, cast(u32)(8*type_align_of(type)));
+	}
 
-	case Type_RelativeSlice:
-		{
-			unsigned element_count = 0;
-			LLVMMetadataRef elements[2] = {};
-			Type *base_integer = type->RelativeSlice.base_integer;
-			unsigned base_bits = cast(unsigned)(8*type_size_of(base_integer));
-			elements[0] = lb_debug_struct_field(m, str_lit("data_offset"), base_integer, 0);
-			elements[1] = lb_debug_struct_field(m, str_lit("len"), base_integer, base_bits);
-			gbString name = type_to_string(type, temporary_allocator());
-			return LLVMDIBuilderCreateStructType(m->debug_builder, nullptr, name, gb_string_length(name), nullptr, 0, 2*base_bits, base_bits, LLVMDIFlagZero, nullptr, elements, element_count, 0, nullptr, "", 0);
-		}
-		
 	case Type_Matrix: {
 		LLVMMetadataRef subscripts[1] = {};
 		subscripts[0] = LLVMDIBuilderGetOrCreateSubrange(m->debug_builder,

+ 27 - 17
src/llvm_backend_expr.cpp

@@ -2863,7 +2863,6 @@ gb_internal lbValue lb_build_unary_and(lbProcedure *p, Ast *expr) {
 	ast_node(ue, UnaryExpr, expr);
 	auto tv = type_and_value_of_expr(expr);
 
-
 	Ast *ue_expr = unparen_expr(ue->expr);
 	if (ue_expr->kind == Ast_IndexExpr && tv.mode == Addressing_OptionalOkPtr && is_type_tuple(tv.type)) {
 		Type *tuple = tv.type;
@@ -3803,25 +3802,32 @@ gb_internal lbAddr lb_build_addr_index_expr(lbProcedure *p, Ast *expr) {
 		lbValue v = {};
 
 		LLVMValueRef indices[1] = {index.value};
-		v.value = LLVMBuildGEP2(p->builder, lb_type(p->module, t->MultiPointer.elem), multi_ptr.value, indices, 1, "foo");
+		v.value = LLVMBuildGEP2(p->builder, lb_type(p->module, t->MultiPointer.elem), multi_ptr.value, indices, 1, "");
 		v.type = alloc_type_pointer(t->MultiPointer.elem);
 		return lb_addr(v);
 	}
 
-	case Type_RelativeSlice: {
-		lbAddr slice_addr = {};
+	case Type_RelativeMultiPointer: {
+		lbAddr rel_ptr_addr = {};
 		if (deref) {
-			slice_addr = lb_addr(lb_build_expr(p, ie->expr));
+			lbValue rel_ptr_ptr = lb_build_expr(p, ie->expr);
+			rel_ptr_addr = lb_addr(rel_ptr_ptr);
 		} else {
-			slice_addr = lb_build_addr(p, ie->expr);
+			rel_ptr_addr = lb_build_addr(p, ie->expr);
 		}
-		lbValue slice = lb_addr_load(p, slice_addr);
+		lbValue rel_ptr = lb_relative_pointer_to_pointer(p, rel_ptr_addr);
 
-		lbValue elem = lb_slice_elem(p, slice);
-		lbValue index = lb_emit_conv(p, lb_build_expr(p, ie->index), t_int);
-		lbValue len = lb_slice_len(p, slice);
-		lb_emit_bounds_check(p, ast_token(ie->index), index, len);
-		lbValue v = lb_emit_ptr_offset(p, elem, index);
+		lbValue index = lb_build_expr(p, ie->index);
+		index = lb_emit_conv(p, index, t_int);
+		lbValue v = {};
+
+		Type *pointer_type = base_type(t->RelativeMultiPointer.pointer_type);
+		GB_ASSERT(pointer_type->kind == Type_MultiPointer);
+		Type *elem = pointer_type->MultiPointer.elem;
+
+		LLVMValueRef indices[1] = {index.value};
+		v.value = LLVMBuildGEP2(p->builder, lb_type(p->module, elem), rel_ptr.value, indices, 1, "");
+		v.type = alloc_type_pointer(elem);
 		return lb_addr(v);
 	}
 
@@ -3925,8 +3931,11 @@ gb_internal lbAddr lb_build_addr_slice_expr(lbProcedure *p, Ast *expr) {
 		return slice;
 	}
 
-	case Type_RelativeSlice:
-		GB_PANIC("TODO(bill): Type_RelativeSlice should be handled above already on the lb_addr_load");
+	case Type_RelativePointer:
+		GB_PANIC("TODO(bill): Type_RelativePointer should be handled above already on the lb_addr_load");
+		break;
+	case Type_RelativeMultiPointer:
+		GB_PANIC("TODO(bill): Type_RelativeMultiPointer should be handled above already on the lb_addr_load");
 		break;
 
 	case Type_DynamicArray: {
@@ -3996,7 +4005,7 @@ gb_internal lbAddr lb_build_addr_slice_expr(lbProcedure *p, Ast *expr) {
 	}
 
 	case Type_Basic: {
-		GB_ASSERT(type == t_string);
+		GB_ASSERT_MSG(type == t_string, "got %s", type_to_string(type));
 		lbValue len = lb_string_len(p, base);
 		if (high.value == nullptr) high = len;
 
@@ -4171,7 +4180,7 @@ gb_internal lbAddr lb_build_addr_compound_lit(lbProcedure *p, Ast *expr) {
 
 				// HACK TODO(bill): THIS IS A MASSIVE HACK!!!!
 				if (is_type_union(ft) && !are_types_identical(fet, ft) && !is_type_untyped(fet)) {
-					GB_ASSERT_MSG(union_variant_index(ft, fet) > 0, "%s", type_to_string(fet));
+					GB_ASSERT_MSG(union_variant_index(ft, fet) >= 0, "%s", type_to_string(fet));
 
 					lb_emit_store_union_variant(p, gep, field_expr, fet);
 				} else {
@@ -4519,8 +4528,9 @@ gb_internal lbAddr lb_build_addr_internal(lbProcedure *p, Ast *expr) {
 			Selection sel = lookup_field(type, selector, false);
 			GB_ASSERT(sel.entity != nullptr);
 			if (sel.pseudo_field) {
-				GB_ASSERT(sel.entity->kind == Entity_Procedure);
+				GB_ASSERT(sel.entity->kind == Entity_Procedure || sel.entity->kind == Entity_ProcGroup);
 				Entity *e = entity_of_node(sel_node);
+				GB_ASSERT(e->kind == Entity_Procedure);
 				return lb_addr(lb_find_value_from_entity(p->module, e));
 			}
 

+ 87 - 155
src/llvm_backend_general.cpp

@@ -103,37 +103,7 @@ gb_internal bool lb_init_generator(lbGenerator *gen, Checker *c) {
 	}
 
 	String init_fullpath = c->parser->init_fullpath;
-
-	if (build_context.out_filepath.len == 0) {
-		gen->output_name = remove_directory_from_path(init_fullpath);
-		gen->output_name = remove_extension_from_path(gen->output_name);
-		gen->output_name = string_trim_whitespace(gen->output_name);
-		if (gen->output_name.len == 0) {
-			gen->output_name = c->info.init_scope->pkg->name;
-		}
-		gen->output_base = gen->output_name;
-	} else {
-		gen->output_name = build_context.out_filepath;
-		gen->output_name = string_trim_whitespace(gen->output_name);
-		if (gen->output_name.len == 0) {
-			gen->output_name = c->info.init_scope->pkg->name;
-		}
-		isize pos = string_extension_position(gen->output_name);
-		if (pos < 0) {
-			gen->output_base = gen->output_name;
-		} else {
-			gen->output_base = substring(gen->output_name, 0, pos);
-		}
-	}
-	gbAllocator ha = heap_allocator();
-	array_init(&gen->output_object_paths, ha);
-	array_init(&gen->output_temp_paths, ha);
-
-	gen->output_base = path_to_full_path(ha, gen->output_base);
-
-	gbString output_file_path = gb_string_make_length(ha, gen->output_base.text, gen->output_base.len);
-	output_file_path = gb_string_appendc(output_file_path, ".obj");
-	defer (gb_string_free(output_file_path));
+	linker_data_init(gen, &c->info, init_fullpath);
 
 	gen->info = &c->info;
 
@@ -141,10 +111,6 @@ gb_internal bool lb_init_generator(lbGenerator *gen, Checker *c) {
 	map_init(&gen->modules_through_ctx, gen->info->packages.count*2);
 	map_init(&gen->anonymous_proc_lits, 1024);
 
-
-	array_init(&gen->foreign_libraries,       heap_allocator(), 0, 1024);
-	ptr_set_init(&gen->foreign_libraries_set, 1024);
-
 	if (USE_SEPARATE_MODULES) {
 		for (auto const &entry : gen->info->packages) {
 			AstPackage *pkg = entry.value;
@@ -383,9 +349,10 @@ gb_internal lbAddr lb_addr(lbValue addr) {
 	if (addr.type != nullptr && is_type_relative_pointer(type_deref(addr.type))) {
 		GB_ASSERT(is_type_pointer(addr.type));
 		v.kind = lbAddr_RelativePointer;
-	} else if (addr.type != nullptr && is_type_relative_slice(type_deref(addr.type))) {
-		GB_ASSERT(is_type_pointer(addr.type));
-		v.kind = lbAddr_RelativeSlice;
+	} else if (addr.type != nullptr && is_type_relative_multi_pointer(type_deref(addr.type))) {
+		GB_ASSERT(is_type_pointer(addr.type) ||
+		          is_type_multi_pointer(addr.type));
+		v.kind = lbAddr_RelativePointer;
 	}
 	return v;
 }
@@ -458,6 +425,43 @@ gb_internal Type *lb_addr_type(lbAddr const &addr) {
 	return type_deref(addr.addr.type);
 }
 
+
+gb_internal lbValue lb_relative_pointer_to_pointer(lbProcedure *p, lbAddr const &addr) {
+	GB_ASSERT(addr.kind == lbAddr_RelativePointer);
+
+	Type *t = base_type(lb_addr_type(addr));
+	GB_ASSERT(is_type_relative_pointer(t) || is_type_relative_multi_pointer(t));
+
+	Type *pointer_type = nullptr;
+	Type *base_integer = nullptr;
+	if (t->kind == Type_RelativePointer) {
+		pointer_type = t->RelativePointer.pointer_type;
+		base_integer = t->RelativePointer.base_integer;
+	} else if (t->kind == Type_RelativeMultiPointer) {
+		pointer_type = t->RelativeMultiPointer.pointer_type;
+		base_integer = t->RelativeMultiPointer.base_integer;
+	}
+
+	lbValue ptr = lb_emit_conv(p, addr.addr, t_uintptr);
+	lbValue offset = lb_emit_conv(p, ptr, alloc_type_pointer(base_integer));
+	offset = lb_emit_load(p, offset);
+
+	if (!is_type_unsigned(base_integer)) {
+		offset = lb_emit_conv(p, offset, t_i64);
+	}
+	offset = lb_emit_conv(p, offset, t_uintptr);
+	lbValue absolute_ptr = lb_emit_arith(p, Token_Add, ptr, offset, t_uintptr);
+	absolute_ptr = lb_emit_conv(p, absolute_ptr, pointer_type);
+
+	lbValue cond = lb_emit_comp(p, Token_CmpEq, offset, lb_const_nil(p->module, base_integer));
+
+	// NOTE(bill): nil check
+	lbValue nil_ptr = lb_const_nil(p->module, pointer_type);
+	lbValue final_ptr = lb_emit_select(p, cond, nil_ptr, absolute_ptr);
+	return final_ptr;
+}
+
+
 gb_internal lbValue lb_addr_get_ptr(lbProcedure *p, lbAddr const &addr) {
 	if (addr.addr.value == nullptr) {
 		GB_PANIC("Illegal addr -> nullptr");
@@ -468,28 +472,8 @@ gb_internal lbValue lb_addr_get_ptr(lbProcedure *p, lbAddr const &addr) {
 	case lbAddr_Map:
 		return lb_internal_dynamic_map_get_ptr(p, addr.addr, addr.map.key);
 
-	case lbAddr_RelativePointer: {
-		Type *rel_ptr = base_type(lb_addr_type(addr));
-		GB_ASSERT(rel_ptr->kind == Type_RelativePointer);
-
-		lbValue ptr = lb_emit_conv(p, addr.addr, t_uintptr);
-		lbValue offset = lb_emit_conv(p, ptr, alloc_type_pointer(rel_ptr->RelativePointer.base_integer));
-		offset = lb_emit_load(p, offset);
-
-		if (!is_type_unsigned(rel_ptr->RelativePointer.base_integer)) {
-			offset = lb_emit_conv(p, offset, t_i64);
-		}
-		offset = lb_emit_conv(p, offset, t_uintptr);
-		lbValue absolute_ptr = lb_emit_arith(p, Token_Add, ptr, offset, t_uintptr);
-		absolute_ptr = lb_emit_conv(p, absolute_ptr, rel_ptr->RelativePointer.pointer_type);
-
-		lbValue cond = lb_emit_comp(p, Token_CmpEq, offset, lb_const_nil(p->module, rel_ptr->RelativePointer.base_integer));
-
-		// NOTE(bill): nil check
-		lbValue nil_ptr = lb_const_nil(p->module, rel_ptr->RelativePointer.pointer_type);
-		lbValue final_ptr = lb_emit_select(p, cond, nil_ptr, absolute_ptr);
-		return final_ptr;
-	}
+	case lbAddr_RelativePointer:
+		return lb_relative_pointer_to_pointer(p, addr);
 
 	case lbAddr_SoaVariable:
 		// TODO(bill): FIX THIS HACK
@@ -511,6 +495,9 @@ gb_internal lbValue lb_addr_get_ptr(lbProcedure *p, lbAddr const &addr) {
 
 gb_internal lbValue lb_build_addr_ptr(lbProcedure *p, Ast *expr) {
 	lbAddr addr = lb_build_addr(p, expr);
+	if (addr.kind == lbAddr_RelativePointer) {
+		return addr.addr;
+	}
 	return lb_addr_get_ptr(p, addr);
 }
 
@@ -719,9 +706,20 @@ gb_internal void lb_addr_store(lbProcedure *p, lbAddr addr, lbValue value) {
 
 	if (addr.kind == lbAddr_RelativePointer) {
 		Type *rel_ptr = base_type(lb_addr_type(addr));
-		GB_ASSERT(rel_ptr->kind == Type_RelativePointer);
+		GB_ASSERT(rel_ptr->kind == Type_RelativePointer ||
+		          rel_ptr->kind == Type_RelativeMultiPointer);
+		Type *pointer_type = nullptr;
+		Type *base_integer = nullptr;
+
+		if (rel_ptr->kind == Type_RelativePointer) {
+			pointer_type = rel_ptr->RelativePointer.pointer_type;
+			base_integer = rel_ptr->RelativePointer.base_integer;
+		} else if (rel_ptr->kind == Type_RelativeMultiPointer) {
+			pointer_type = rel_ptr->RelativeMultiPointer.pointer_type;
+			base_integer = rel_ptr->RelativeMultiPointer.base_integer;
+		}
 
-		value = lb_emit_conv(p, value, rel_ptr->RelativePointer.pointer_type);
+		value = lb_emit_conv(p, value, pointer_type);
 
 		GB_ASSERT(is_type_pointer(addr.addr.type));
 		lbValue ptr = lb_emit_conv(p, addr.addr, t_uintptr);
@@ -730,54 +728,20 @@ gb_internal void lb_addr_store(lbProcedure *p, lbAddr addr, lbValue value) {
 		offset.value = LLVMBuildSub(p->builder, val_ptr.value, ptr.value, "");
 		offset.type = t_uintptr;
 
-		if (!is_type_unsigned(rel_ptr->RelativePointer.base_integer)) {
+		if (!is_type_unsigned(base_integer)) {
 			offset = lb_emit_conv(p, offset, t_i64);
 		}
-		offset = lb_emit_conv(p, offset, rel_ptr->RelativePointer.base_integer);
+		offset = lb_emit_conv(p, offset, base_integer);
 
-		lbValue offset_ptr = lb_emit_conv(p, addr.addr, alloc_type_pointer(rel_ptr->RelativePointer.base_integer));
+		lbValue offset_ptr = lb_emit_conv(p, addr.addr, alloc_type_pointer(base_integer));
 		offset = lb_emit_select(p,
 			lb_emit_comp(p, Token_CmpEq, val_ptr, lb_const_nil(p->module, t_uintptr)),
-			lb_const_nil(p->module, rel_ptr->RelativePointer.base_integer),
+			lb_const_nil(p->module, base_integer),
 			offset
 		);
 		LLVMBuildStore(p->builder, offset.value, offset_ptr.value);
 		return;
 
-	} else if (addr.kind == lbAddr_RelativeSlice) {
-		Type *rel_ptr = base_type(lb_addr_type(addr));
-		GB_ASSERT(rel_ptr->kind == Type_RelativeSlice);
-
-		value = lb_emit_conv(p, value, rel_ptr->RelativeSlice.slice_type);
-
-		GB_ASSERT(is_type_pointer(addr.addr.type));
-		lbValue ptr = lb_emit_conv(p, lb_emit_struct_ep(p, addr.addr, 0), t_uintptr);
-		lbValue val_ptr = lb_emit_conv(p, lb_slice_elem(p, value), t_uintptr);
-		lbValue offset = {};
-		offset.value = LLVMBuildSub(p->builder, val_ptr.value, ptr.value, "");
-		offset.type = t_uintptr;
-
-		if (!is_type_unsigned(rel_ptr->RelativePointer.base_integer)) {
-			offset = lb_emit_conv(p, offset, t_i64);
-		}
-		offset = lb_emit_conv(p, offset, rel_ptr->RelativePointer.base_integer);
-
-
-		lbValue offset_ptr = lb_emit_conv(p, addr.addr, alloc_type_pointer(rel_ptr->RelativePointer.base_integer));
-		offset = lb_emit_select(p,
-			lb_emit_comp(p, Token_CmpEq, val_ptr, lb_const_nil(p->module, t_uintptr)),
-			lb_const_nil(p->module, rel_ptr->RelativePointer.base_integer),
-			offset
-		);
-		LLVMBuildStore(p->builder, offset.value, offset_ptr.value);
-
-		lbValue len = lb_slice_len(p, value);
-		len = lb_emit_conv(p, len, rel_ptr->RelativePointer.base_integer);
-
-		lbValue len_ptr = lb_emit_struct_ep(p, addr.addr, 1);
-		LLVMBuildStore(p->builder, len.value, len_ptr.value);
-
-		return;
 	} else if (addr.kind == lbAddr_Map) {
 		lb_internal_dynamic_map_set(p, addr.addr, addr.map.type, addr.map.key, value, p->curr_stmt);
 		return;
@@ -1054,67 +1018,43 @@ gb_internal lbValue lb_addr_load(lbProcedure *p, lbAddr const &addr) {
 
 	if (addr.kind == lbAddr_RelativePointer) {
 		Type *rel_ptr = base_type(lb_addr_type(addr));
-		GB_ASSERT(rel_ptr->kind == Type_RelativePointer);
+		Type *base_integer = nullptr;
+		Type *pointer_type = nullptr;
+		GB_ASSERT(rel_ptr->kind == Type_RelativePointer ||
+		          rel_ptr->kind == Type_RelativeMultiPointer);
+
+		if (rel_ptr->kind == Type_RelativePointer) {
+			base_integer = rel_ptr->RelativePointer.base_integer;
+			pointer_type = rel_ptr->RelativePointer.pointer_type;
+		} else if (rel_ptr->kind == Type_RelativeMultiPointer) {
+			base_integer = rel_ptr->RelativeMultiPointer.base_integer;
+			pointer_type = rel_ptr->RelativeMultiPointer.pointer_type;
+		}
 
 		lbValue ptr = lb_emit_conv(p, addr.addr, t_uintptr);
-		lbValue offset = lb_emit_conv(p, ptr, alloc_type_pointer(rel_ptr->RelativePointer.base_integer));
+		lbValue offset = lb_emit_conv(p, ptr, alloc_type_pointer(base_integer));
 		offset = lb_emit_load(p, offset);
 
 
-		if (!is_type_unsigned(rel_ptr->RelativePointer.base_integer)) {
+		if (!is_type_unsigned(base_integer)) {
 			offset = lb_emit_conv(p, offset, t_i64);
 		}
 		offset = lb_emit_conv(p, offset, t_uintptr);
 		lbValue absolute_ptr = lb_emit_arith(p, Token_Add, ptr, offset, t_uintptr);
-		absolute_ptr = lb_emit_conv(p, absolute_ptr, rel_ptr->RelativePointer.pointer_type);
+		absolute_ptr = lb_emit_conv(p, absolute_ptr, pointer_type);
 
-		lbValue cond = lb_emit_comp(p, Token_CmpEq, offset, lb_const_nil(p->module, rel_ptr->RelativePointer.base_integer));
+		lbValue cond = lb_emit_comp(p, Token_CmpEq, offset, lb_const_nil(p->module, base_integer));
 
 		// NOTE(bill): nil check
-		lbValue nil_ptr = lb_const_nil(p->module, rel_ptr->RelativePointer.pointer_type);
+		lbValue nil_ptr = lb_const_nil(p->module, pointer_type);
 		lbValue final_ptr = {};
 		final_ptr.type = absolute_ptr.type;
 		final_ptr.value = LLVMBuildSelect(p->builder, cond.value, nil_ptr.value, absolute_ptr.value, "");
 
-		return lb_emit_load(p, final_ptr);
-
-	} else if (addr.kind == lbAddr_RelativeSlice) {
-		Type *rel_ptr = base_type(lb_addr_type(addr));
-		GB_ASSERT(rel_ptr->kind == Type_RelativeSlice);
-
-		lbValue offset_ptr = lb_emit_struct_ep(p, addr.addr, 0);
-		lbValue ptr = lb_emit_conv(p, offset_ptr, t_uintptr);
-		lbValue offset = lb_emit_load(p, offset_ptr);
-
-
-		if (!is_type_unsigned(rel_ptr->RelativeSlice.base_integer)) {
-			offset = lb_emit_conv(p, offset, t_i64);
+		if (rel_ptr->kind == Type_RelativeMultiPointer) {
+			return final_ptr;
 		}
-		offset = lb_emit_conv(p, offset, t_uintptr);
-		lbValue absolute_ptr = lb_emit_arith(p, Token_Add, ptr, offset, t_uintptr);
-
-		Type *slice_type = base_type(rel_ptr->RelativeSlice.slice_type);
-		GB_ASSERT(rel_ptr->RelativeSlice.slice_type->kind == Type_Slice);
-		Type *slice_elem = slice_type->Slice.elem;
-		Type *slice_elem_ptr = alloc_type_pointer(slice_elem);
-
-		absolute_ptr = lb_emit_conv(p, absolute_ptr, slice_elem_ptr);
-
-		lbValue cond = lb_emit_comp(p, Token_CmpEq, offset, lb_const_nil(p->module, rel_ptr->RelativeSlice.base_integer));
-
-		// NOTE(bill): nil check
-		lbValue nil_ptr = lb_const_nil(p->module, slice_elem_ptr);
-		lbValue data = {};
-		data.type = absolute_ptr.type;
-		data.value = LLVMBuildSelect(p->builder, cond.value, nil_ptr.value, absolute_ptr.value, "");
-
-		lbValue len = lb_emit_load(p, lb_emit_struct_ep(p, addr.addr, 1));
-		len = lb_emit_conv(p, len, t_int);
-
-		lbAddr slice = lb_add_local_generated(p, slice_type, false);
-		lb_fill_slice(p, slice, data, len);
-		return lb_addr_load(p, slice);
-
+		return lb_emit_load(p, final_ptr);
 
 	} else if (addr.kind == lbAddr_Map) {
 		Type *map_type = base_type(type_deref(addr.addr.type));
@@ -1895,8 +1835,8 @@ gb_internal LLVMTypeRef lb_type_internal(lbModule *m, Type *type) {
 			case Type_SimdVector:
 				return lb_type_internal(m, base);
 
-			// TODO(bill): Deal with this correctly. Can this be named?
 			case Type_Proc:
+				// TODO(bill): Deal with this correctly. Can this be named?
 				return lb_type_internal(m, base);
 
 			case Type_Tuple:
@@ -2173,17 +2113,10 @@ gb_internal LLVMTypeRef lb_type_internal(lbModule *m, Type *type) {
 
 	case Type_RelativePointer:
 		return lb_type_internal(m, type->RelativePointer.base_integer);
+	case Type_RelativeMultiPointer:
+		return lb_type_internal(m, type->RelativeMultiPointer.base_integer);
 
-	case Type_RelativeSlice:
-		{
-			LLVMTypeRef base_integer = lb_type_internal(m, type->RelativeSlice.base_integer);
 
-			unsigned field_count = 2;
-			LLVMTypeRef *fields = gb_alloc_array(permanent_allocator(), LLVMTypeRef, field_count);
-			fields[0] = base_integer;
-			fields[1] = base_integer;
-			return LLVMStructTypeInContext(ctx, fields, field_count, false);
-		}
 		
 	case Type_Matrix:
 		{
@@ -2869,7 +2802,6 @@ gb_internal lbValue lb_find_value_from_entity(lbModule *m, Entity *e) {
 	if (USE_SEPARATE_MODULES) {
 		lbModule *other_module = lb_module_of_entity(m->gen, e);
 
-		// TODO(bill): correct this logic
 		bool is_external = other_module != m;
 		if (!is_external) {
 			if (e->code_gen_module != nullptr) {

+ 12 - 9
src/llvm_backend_proc.cpp

@@ -362,7 +362,6 @@ gb_internal lbProcedure *lb_create_dummy_procedure(lbModule *m, String link_name
 
 	Type *pt = p->type;
 	lbCallingConventionKind cc_kind = lbCallingConvention_C;
-	// TODO(bill): Clean up this logic
 	if (!is_arch_wasm()) {
 		cc_kind = lb_calling_convention_map[pt->Proc.calling_convention];
 	}
@@ -1702,7 +1701,6 @@ gb_internal lbValue lb_build_builtin_proc(lbProcedure *p, Ast *expr, TypeAndValu
 		lbValue v = lb_build_expr(p, ce->args[0]);
 		Type *t = base_type(v.type);
 		if (is_type_pointer(t)) {
-			// IMPORTANT TODO(bill): Should there be a nil pointer check?
 			v = lb_emit_load(p, v);
 			t = type_deref(t);
 		}
@@ -1712,7 +1710,7 @@ gb_internal lbValue lb_build_builtin_proc(lbProcedure *p, Ast *expr, TypeAndValu
 			return lb_string_len(p, v);
 		} else if (is_type_array(t)) {
 			GB_PANIC("Array lengths are constant");
-		} else if (is_type_slice(t) || is_type_relative_slice(t)) {
+		} else if (is_type_slice(t)) {
 			return lb_slice_len(p, v);
 		} else if (is_type_dynamic_array(t)) {
 			return lb_dynamic_array_len(p, v);
@@ -1730,7 +1728,6 @@ gb_internal lbValue lb_build_builtin_proc(lbProcedure *p, Ast *expr, TypeAndValu
 		lbValue v = lb_build_expr(p, ce->args[0]);
 		Type *t = base_type(v.type);
 		if (is_type_pointer(t)) {
-			// IMPORTANT TODO(bill): Should there be a nil pointer check?
 			v = lb_emit_load(p, v);
 			t = type_deref(t);
 		}
@@ -1738,7 +1735,7 @@ gb_internal lbValue lb_build_builtin_proc(lbProcedure *p, Ast *expr, TypeAndValu
 			GB_PANIC("Unreachable");
 		} else if (is_type_array(t)) {
 			GB_PANIC("Array lengths are constant");
-		} else if (is_type_slice(t) || is_type_relative_slice(t)) {
+		} else if (is_type_slice(t)) {
 			return lb_slice_len(p, v);
 		} else if (is_type_dynamic_array(t)) {
 			return lb_dynamic_array_cap(p, v);
@@ -3144,7 +3141,7 @@ gb_internal lbValue lb_build_call_expr(lbProcedure *p, Ast *expr) {
 
 	lbValue res = lb_build_call_expr_internal(p, expr);
 
-	if (ce->optional_ok_one) { // TODO(bill): Minor hack for #optional_ok procedures
+	if (ce->optional_ok_one) {
 		GB_ASSERT(is_type_tuple(res.type));
 		GB_ASSERT(res.type->Tuple.variables.count == 2);
 		return lb_emit_struct_ev(p, res, 0);
@@ -3332,9 +3329,15 @@ gb_internal lbValue lb_build_call_expr_internal(lbProcedure *p, Ast *expr) {
 		isize param_index = lookup_procedure_parameter(pt, name);
 		GB_ASSERT(param_index >= 0);
 
-		lbValue value = lb_build_expr(p, fv->value);
-		GB_ASSERT(!is_type_tuple(value.type));
-		args[param_index] = value;
+		Entity *e = pt->params->Tuple.variables[param_index];
+		if (e->kind == Entity_TypeName) {
+			lbValue value = lb_const_nil(p->module, e->type);
+			args[param_index] = value;
+		} else {
+			lbValue value = lb_build_expr(p, fv->value);
+			GB_ASSERT(!is_type_tuple(value.type));
+			args[param_index] = value;
+		}
 	}
 
 	TokenPos pos = ast_token(ce->proc).pos;

+ 1 - 4
src/llvm_backend_stmt.cpp

@@ -1688,7 +1688,6 @@ gb_internal void lb_build_type_switch_stmt(lbProcedure *p, AstTypeSwitchStmt *ss
 			lb_add_entity(p->module, case_entity, ptr);
 			lb_add_debug_local_variable(p, ptr.value, case_entity->type, case_entity->token);
 		} else {
-			// TODO(bill): is the correct expected behaviour?
 			lb_store_type_case_implicit(p, clause, parent_value);
 		}
 
@@ -2014,12 +2013,10 @@ gb_internal void lb_build_if_stmt(lbProcedure *p, Ast *node) {
 	defer (lb_close_scope(p, lbDeferExit_Default, nullptr));
 
 	if (is->init != nullptr) {
-		// TODO(bill): Should this have a separate block to begin with?
-	#if 1
 		lbBlock *init = lb_create_block(p, "if.init");
 		lb_emit_jump(p, init);
 		lb_start_block(p, init);
-	#endif
+
 		lb_build_stmt(p, is->init);
 	}
 	lbBlock *then = lb_create_block(p, "if.then");

+ 7 - 6
src/llvm_backend_type.cpp

@@ -57,7 +57,7 @@ gb_internal lbValue lb_typeid(lbModule *m, Type *type) {
 	case Type_BitSet:          kind = Typeid_Bit_Set;          break;
 	case Type_SimdVector:      kind = Typeid_Simd_Vector;      break;
 	case Type_RelativePointer: kind = Typeid_Relative_Pointer; break;
-	case Type_RelativeSlice:   kind = Typeid_Relative_Slice;   break;
+	case Type_RelativeMultiPointer: kind = Typeid_Relative_Multi_Pointer; break;
 	case Type_SoaPointer:      kind = Typeid_SoaPointer;       break;
 	}
 
@@ -731,7 +731,6 @@ gb_internal void lb_setup_type_info_data(lbProcedure *p) { // NOTE(bill): Setup
 
 				type_set_offsets(t); // NOTE(bill): Just incase the offsets have not been set yet
 				for (isize source_index = 0; source_index < count; source_index++) {
-					// TODO(bill): Order fields in source order not layout order
 					Entity *f = t->Struct.fields[source_index];
 					lbValue tip = lb_type_info(m, f->type);
 					i64 foffset = 0;
@@ -858,12 +857,13 @@ gb_internal void lb_setup_type_info_data(lbProcedure *p) { // NOTE(bill): Setup
 				lb_emit_store(p, tag, res);
 			}
 			break;
-		case Type_RelativeSlice:
+
+		case Type_RelativeMultiPointer:
 			{
-				tag = lb_const_ptr_cast(m, variant_ptr, t_type_info_relative_slice_ptr);
+				tag = lb_const_ptr_cast(m, variant_ptr, t_type_info_relative_multi_pointer_ptr);
 				LLVMValueRef vals[2] = {
-					lb_type_info(m, t->RelativeSlice.slice_type).value,
-					lb_type_info(m, t->RelativeSlice.base_integer).value,
+					lb_type_info(m, t->RelativeMultiPointer.pointer_type).value,
+					lb_type_info(m, t->RelativeMultiPointer.base_integer).value,
 				};
 
 				lbValue res = {};
@@ -872,6 +872,7 @@ gb_internal void lb_setup_type_info_data(lbProcedure *p) { // NOTE(bill): Setup
 				lb_emit_store(p, tag, res);
 			}
 			break;
+
 		case Type_Matrix: 
 			{
 				tag = lb_const_ptr_cast(m, variant_ptr, t_type_info_matrix_ptr);

+ 1 - 6
src/llvm_backend_utility.cpp

@@ -1124,11 +1124,6 @@ gb_internal lbValue lb_emit_struct_ep(lbProcedure *p, lbValue s, i32 index) {
 		}
 	} else if (is_type_array(t)) {
 		return lb_emit_array_epi(p, s, index);
-	} else if (is_type_relative_slice(t)) {
-		switch (index) {
-		case 0: result_type = t->RelativeSlice.base_integer; break;
-		case 1: result_type = t->RelativeSlice.base_integer; break;
-		}
 	} else if (is_type_soa_pointer(t)) {
 		switch (index) {
 		case 0: result_type = alloc_type_pointer(t->SoaPointer.elem); break;
@@ -1547,7 +1542,7 @@ gb_internal lbValue lb_slice_elem(lbProcedure *p, lbValue slice) {
 	return lb_emit_struct_ev(p, slice, 0);
 }
 gb_internal lbValue lb_slice_len(lbProcedure *p, lbValue slice) {
-	GB_ASSERT(is_type_slice(slice.type) || is_type_relative_slice(slice.type));
+	GB_ASSERT(is_type_slice(slice.type));
 	return lb_emit_struct_ev(p, slice, 1);
 }
 gb_internal lbValue lb_dynamic_array_elem(lbProcedure *p, lbValue da) {

+ 139 - 460
src/main.cpp

@@ -1,5 +1,4 @@
 // #define NO_ARRAY_BOUNDS_CHECK
-
 #include "common.cpp"
 #include "timings.cpp"
 #include "tokenizer.cpp"
@@ -71,6 +70,18 @@ gb_global Timings global_timings = {0};
 #include "checker.cpp"
 #include "docs.cpp"
 
+#include "linker.cpp"
+
+#if defined(GB_SYSTEM_WINDOWS) && defined(ODIN_TILDE_BACKEND)
+#define ALLOW_TILDE 1
+#else
+#define ALLOW_TILDE 0
+#endif
+
+#if ALLOW_TILDE
+#include "tilde.cpp"
+#endif
+
 #include "llvm_backend.cpp"
 
 #if defined(GB_SYSTEM_OSX)
@@ -147,422 +158,6 @@ gb_internal i32 system_exec_command_line_app(char const *name, char const *fmt,
 	return exit_code;
 }
 
-
-gb_internal i32 linker_stage(lbGenerator *gen) {
-	i32 result = 0;
-	Timings *timings = &global_timings;
-
-	String output_filename = path_to_string(heap_allocator(), build_context.build_paths[BuildPath_Output]);
-	debugf("Linking %.*s\n", LIT(output_filename));
-
-	// TOOD(Jeroen): Make a `build_paths[BuildPath_Object] to avoid `%.*s.o`.
-
-	if (is_arch_wasm()) {
-		timings_start_section(timings, str_lit("wasm-ld"));
-
-	#if defined(GB_SYSTEM_WINDOWS)
-		result = system_exec_command_line_app("wasm-ld",
-			"\"%.*s\\bin\\wasm-ld\" \"%.*s.o\" -o \"%.*s\" %.*s %.*s",
-			LIT(build_context.ODIN_ROOT),
-			LIT(output_filename), LIT(output_filename), LIT(build_context.link_flags), LIT(build_context.extra_linker_flags));
-	#else
-		result = system_exec_command_line_app("wasm-ld",
-			"wasm-ld \"%.*s.o\" -o \"%.*s\" %.*s %.*s",
-			LIT(output_filename), LIT(output_filename), LIT(build_context.link_flags), LIT(build_context.extra_linker_flags));
-	#endif
-		return result;
-	}
-
-	if (build_context.cross_compiling && selected_target_metrics->metrics == &target_essence_amd64) {
-#if defined(GB_SYSTEM_UNIX) 
-		result = system_exec_command_line_app("linker", "x86_64-essence-gcc \"%.*s.o\" -o \"%.*s\" %.*s %.*s",
-			LIT(output_filename), LIT(output_filename), LIT(build_context.link_flags), LIT(build_context.extra_linker_flags));
-#else
-		gb_printf_err("Linking for cross compilation for this platform is not yet supported (%.*s %.*s)\n",
-			LIT(target_os_names[build_context.metrics.os]),
-			LIT(target_arch_names[build_context.metrics.arch])
-		);
-#endif
-	} else if (build_context.cross_compiling && build_context.different_os) {
-		gb_printf_err("Linking for cross compilation for this platform is not yet supported (%.*s %.*s)\n",
-			LIT(target_os_names[build_context.metrics.os]),
-			LIT(target_arch_names[build_context.metrics.arch])
-		);
-		build_context.keep_object_files = true;
-	} else {
-	#if defined(GB_SYSTEM_WINDOWS)
-		bool is_windows = true;
-	#else
-		bool is_windows = false;
-	#endif
-	#if defined(GB_SYSTEM_OSX)
-		bool is_osx = true;
-	#else
-		bool is_osx = false;
-	#endif
-
-
-		if (is_windows) {
-			String section_name = str_lit("msvc-link");
-			if (build_context.use_lld) {
-				section_name = str_lit("lld-link");
-			}
-			timings_start_section(timings, section_name);
-
-			gbString lib_str = gb_string_make(heap_allocator(), "");
-			defer (gb_string_free(lib_str));
-
-			gbString link_settings = gb_string_make_reserve(heap_allocator(), 256);
-			defer (gb_string_free(link_settings));
-
-			// Add library search paths.
-			if (build_context.build_paths[BuildPath_VS_LIB].basename.len > 0) {
-				String path = {};
-				auto add_path = [&](String path) {
-					if (path[path.len-1] == '\\') {
-						path.len -= 1;
-					}
-					link_settings = gb_string_append_fmt(link_settings, " /LIBPATH:\"%.*s\"", LIT(path));
-				};
-				add_path(build_context.build_paths[BuildPath_Win_SDK_UM_Lib].basename);
-				add_path(build_context.build_paths[BuildPath_Win_SDK_UCRT_Lib].basename);
-				add_path(build_context.build_paths[BuildPath_VS_LIB].basename);
-			}
-
-
-			StringSet libs = {};
-			string_set_init(&libs, 64);
-			defer (string_set_destroy(&libs));
-
-			StringSet asm_files = {};
-			string_set_init(&asm_files, 64);
-			defer (string_set_destroy(&asm_files));
-
-			for (Entity *e : gen->foreign_libraries) {
-				GB_ASSERT(e->kind == Entity_LibraryName);
-				for_array(i, e->LibraryName.paths) {
-					String lib = string_trim_whitespace(e->LibraryName.paths[i]);
-					// IMPORTANT NOTE(bill): calling `string_to_lower` here is not an issue because
-					// we will never uses these strings afterwards
-					string_to_lower(&lib);
-					if (lib.len == 0) {
-						continue;
-					}
-
-					if (has_asm_extension(lib)) {
-						if (!string_set_update(&asm_files, lib)) {
-							String asm_file = asm_files.entries[i].value;
-							String obj_file = concatenate_strings(permanent_allocator(), asm_file, str_lit(".obj"));
-
-							result = system_exec_command_line_app("nasm",
-								"\"%.*s\\bin\\nasm\\windows\\nasm.exe\" \"%.*s\" "
-								"-f win64 "
-								"-o \"%.*s\" "
-								"%.*s "
-								"",
-								LIT(build_context.ODIN_ROOT), LIT(asm_file),
-								LIT(obj_file),
-								LIT(build_context.extra_assembler_flags)
-							);
-
-							if (result) {
-								return result;
-							}
-							array_add(&gen->output_object_paths, obj_file);
-						}
-					} else {
-						if (!string_set_update(&libs, lib)) {
-							lib_str = gb_string_append_fmt(lib_str, " \"%.*s\"", LIT(lib));
-						}
-					}
-				}
-			}
-
-			for (Entity *e : gen->foreign_libraries) {
-				GB_ASSERT(e->kind == Entity_LibraryName);
-				if (e->LibraryName.extra_linker_flags.len != 0) {
-					lib_str = gb_string_append_fmt(lib_str, " %.*s", LIT(e->LibraryName.extra_linker_flags));
-				}
-			}
-
-			if (build_context.build_mode == BuildMode_DynamicLibrary) {
-				link_settings = gb_string_append_fmt(link_settings, " /DLL");
-			} else {
-				link_settings = gb_string_append_fmt(link_settings, " /ENTRY:mainCRTStartup");
-			}
-
-			if (build_context.pdb_filepath != "") {
-				String pdb_path = path_to_string(heap_allocator(), build_context.build_paths[BuildPath_PDB]);
-				link_settings = gb_string_append_fmt(link_settings, " /PDB:%.*s", LIT(pdb_path));
-			}
-
-			if (build_context.no_crt) {
-				link_settings = gb_string_append_fmt(link_settings, " /nodefaultlib");
-			} else {
-				link_settings = gb_string_append_fmt(link_settings, " /defaultlib:libcmt");
-			}
-
-			if (build_context.ODIN_DEBUG) {
-				link_settings = gb_string_append_fmt(link_settings, " /DEBUG");
-			}
-
-			gbString object_files = gb_string_make(heap_allocator(), "");
-			defer (gb_string_free(object_files));
-			for (String const &object_path : gen->output_object_paths) {
-				object_files = gb_string_append_fmt(object_files, "\"%.*s\" ", LIT(object_path));
-			}
-
-			String vs_exe_path = path_to_string(heap_allocator(), build_context.build_paths[BuildPath_VS_EXE]);
-			defer (gb_free(heap_allocator(), vs_exe_path.text));
-
-			String windows_sdk_bin_path = path_to_string(heap_allocator(), build_context.build_paths[BuildPath_Win_SDK_Bin_Path]);
-			defer (gb_free(heap_allocator(), windows_sdk_bin_path.text));
-
-			char const *subsystem_str = build_context.use_subsystem_windows ? "WINDOWS" : "CONSOLE";
-			if (!build_context.use_lld) { // msvc
-				String res_path = {};
-				defer (gb_free(heap_allocator(), res_path.text));
-				if (build_context.has_resource) {
-					String temp_res_path = path_to_string(heap_allocator(), build_context.build_paths[BuildPath_RES]);
-					res_path = concatenate3_strings(heap_allocator(), str_lit("\""), temp_res_path, str_lit("\""));
-					gb_free(heap_allocator(), temp_res_path.text);
-
-					String rc_path  = path_to_string(heap_allocator(), build_context.build_paths[BuildPath_RC]);
-					defer (gb_free(heap_allocator(), rc_path.text));
-
-					result = system_exec_command_line_app("msvc-link",
-						"\"%.*src.exe\" /nologo /fo \"%.*s\" \"%.*s\"",
-						LIT(windows_sdk_bin_path),
-						LIT(res_path),
-						LIT(rc_path)
-					);
-
-					if (result) {
-						return result;
-					}
-				}
-
-				switch (build_context.build_mode) {
-				case BuildMode_Executable:
-					link_settings = gb_string_append_fmt(link_settings, " /NOIMPLIB /NOEXP");
-					break;
-				}
-
-				result = system_exec_command_line_app("msvc-link",
-					"\"%.*slink.exe\" %s %.*s -OUT:\"%.*s\" %s "
-					"/nologo /incremental:no /opt:ref /subsystem:%s "
-					"%.*s "
-					"%.*s "
-					"%s "
-					"",
-					LIT(vs_exe_path), object_files, LIT(res_path), LIT(output_filename),
-					link_settings,
-					subsystem_str,
-					LIT(build_context.link_flags),
-					LIT(build_context.extra_linker_flags),
-					lib_str
-				);
-				if (result) {
-					return result;
-				}
-			} else { // lld
-				result = system_exec_command_line_app("msvc-lld-link",
-					"\"%.*s\\bin\\lld-link\" %s -OUT:\"%.*s\" %s "
-					"/nologo /incremental:no /opt:ref /subsystem:%s "
-					"%.*s "
-					"%.*s "
-					"%s "
-					"",
-					LIT(build_context.ODIN_ROOT), object_files, LIT(output_filename),
-					link_settings,
-					subsystem_str,
-					LIT(build_context.link_flags),
-					LIT(build_context.extra_linker_flags),
-					lib_str
-				);
-
-				if (result) {
-					return result;
-				}
-			}
-		} else {
-			timings_start_section(timings, str_lit("ld-link"));
-
-			// NOTE(vassvik): get cwd, for used for local shared libs linking, since those have to be relative to the exe
-			char cwd[256];
-			#if !defined(GB_SYSTEM_WINDOWS)
-			getcwd(&cwd[0], 256);
-			#endif
-			//printf("%s\n", cwd);
-
-			// NOTE(vassvik): needs to add the root to the library search paths, so that the full filenames of the library
-			//                files can be passed with -l:
-			gbString lib_str = gb_string_make(heap_allocator(), "-L/");
-			defer (gb_string_free(lib_str));
-
-			StringSet libs = {};
-			string_set_init(&libs, 64);
-			defer (string_set_destroy(&libs));
-
-			for (Entity *e : gen->foreign_libraries) {
-				GB_ASSERT(e->kind == Entity_LibraryName);
-				for (String lib : e->LibraryName.paths) {
-					lib = string_trim_whitespace(lib);
-					if (lib.len == 0) {
-						continue;
-					}
-					if (string_set_update(&libs, lib)) {
-						continue;
-					}
-
-					// NOTE(zangent): Sometimes, you have to use -framework on MacOS.
-					//   This allows you to specify '-f' in a #foreign_system_library,
-					//   without having to implement any new syntax specifically for MacOS.
-					if (build_context.metrics.os == TargetOs_darwin) {
-						if (string_ends_with(lib, str_lit(".framework"))) {
-							// framework thingie
-							String lib_name = lib;
-							lib_name = remove_extension_from_path(lib_name);
-							lib_str = gb_string_append_fmt(lib_str, " -framework %.*s ", LIT(lib_name));
-						} else if (string_ends_with(lib, str_lit(".a")) || string_ends_with(lib, str_lit(".o")) || string_ends_with(lib, str_lit(".dylib"))) {
-							// For:
-							// object
-							// dynamic lib
-							// static libs, absolute full path relative to the file in which the lib was imported from
-							lib_str = gb_string_append_fmt(lib_str, " %.*s ", LIT(lib));
-						} else {
-							// dynamic or static system lib, just link regularly searching system library paths
-							lib_str = gb_string_append_fmt(lib_str, " -l%.*s ", LIT(lib));
-						}
-					} else {
-						// NOTE(vassvik): static libraries (.a files) in linux can be linked to directly using the full path,
-						//                since those are statically linked to at link time. shared libraries (.so) has to be
-						//                available at runtime wherever the executable is run, so we make require those to be
-						//                local to the executable (unless the system collection is used, in which case we search
-						//                the system library paths for the library file).
-						if (string_ends_with(lib, str_lit(".a")) || string_ends_with(lib, str_lit(".o"))) {
-							// static libs and object files, absolute full path relative to the file in which the lib was imported from
-							lib_str = gb_string_append_fmt(lib_str, " -l:\"%.*s\" ", LIT(lib));
-						} else if (string_ends_with(lib, str_lit(".so"))) {
-							// dynamic lib, relative path to executable
-							// NOTE(vassvik): it is the user's responsibility to make sure the shared library files are visible
-							//                at runtime to the executable
-							lib_str = gb_string_append_fmt(lib_str, " -l:\"%s/%.*s\" ", cwd, LIT(lib));
-						} else {
-							// dynamic or static system lib, just link regularly searching system library paths
-							lib_str = gb_string_append_fmt(lib_str, " -l%.*s ", LIT(lib));
-						}
-					}
-				}
-			}
-
-			for (Entity *e : gen->foreign_libraries) {
-				GB_ASSERT(e->kind == Entity_LibraryName);
-				if (e->LibraryName.extra_linker_flags.len != 0) {
-					lib_str = gb_string_append_fmt(lib_str, " %.*s", LIT(e->LibraryName.extra_linker_flags));
-				}
-			}
-
-			gbString object_files = gb_string_make(heap_allocator(), "");
-			defer (gb_string_free(object_files));
-			for (String object_path : gen->output_object_paths) {
-				object_files = gb_string_append_fmt(object_files, "\"%.*s\" ", LIT(object_path));
-			}
-
-			gbString link_settings = gb_string_make_reserve(heap_allocator(), 32);
-
-			if (build_context.no_crt) {
-				link_settings = gb_string_append_fmt(link_settings, "-nostdlib ");
-			}
-
-			// NOTE(dweiler): We use clang as a frontend for the linker as there are
-			// other runtime and compiler support libraries that need to be linked in
-			// very specific orders such as libgcc_s, ld-linux-so, unwind, etc.
-			// These are not always typically inside /lib, /lib64, or /usr versions
-			// of that, e.g libgcc.a is in /usr/lib/gcc/{version}, and can vary on
-			// the distribution of Linux even. The gcc or clang specs is the only
-			// reliable way to query this information to call ld directly.
-			if (build_context.build_mode == BuildMode_DynamicLibrary) {
-				// NOTE(dweiler): Let the frontend know we're building a shared library
-				// so it doesn't generate symbols which cannot be relocated.
-				link_settings = gb_string_appendc(link_settings, "-shared ");
-
-				// NOTE(dweiler): _odin_entry_point must be called at initialization
-				// time of the shared object, similarly, _odin_exit_point must be called
-				// at deinitialization. We can pass both -init and -fini to the linker by
-				// using a comma separated list of arguments to -Wl.
-				//
-				// This previously used ld but ld cannot actually build a shared library
-				// correctly this way since all the other dependencies provided implicitly
-				// by the compiler frontend are still needed and most of the command
-				// line arguments prepared previously are incompatible with ld.
-				if (build_context.metrics.os == TargetOs_darwin) {
-					link_settings = gb_string_appendc(link_settings, "-Wl,-init,'__odin_entry_point' ");
-					// NOTE(weshardee): __odin_exit_point should also be added, but -fini 
-					// does not exist on MacOS
-				} else {
-					link_settings = gb_string_appendc(link_settings, "-Wl,-init,'_odin_entry_point' ");
-					link_settings = gb_string_appendc(link_settings, "-Wl,-fini,'_odin_exit_point' ");
-				}
-				
-			} else if (build_context.metrics.os != TargetOs_openbsd) {
-				// OpenBSD defaults to PIE executable. do not pass -no-pie for it.
-				link_settings = gb_string_appendc(link_settings, "-no-pie ");
-			}
-
-			gbString platform_lib_str = gb_string_make(heap_allocator(), "");
-			defer (gb_string_free(platform_lib_str));
-			if (build_context.metrics.os == TargetOs_darwin) {
-				platform_lib_str = gb_string_appendc(platform_lib_str, "-lSystem -lm -Wl,-syslibroot /Library/Developer/CommandLineTools/SDKs/MacOSX.sdk -L/usr/local/lib");
-			} else {
-				platform_lib_str = gb_string_appendc(platform_lib_str, "-lc -lm");
-			}
-
-			if (build_context.metrics.os == TargetOs_darwin) {
-				// This sets a requirement of Mountain Lion and up, but the compiler doesn't work without this limit.
-				if (build_context.minimum_os_version_string.len) {
-					link_settings = gb_string_append_fmt(link_settings, " -mmacosx-version-min=%.*s ", LIT(build_context.minimum_os_version_string));
-				} else if (build_context.metrics.arch == TargetArch_arm64) {
-					link_settings = gb_string_appendc(link_settings, " -mmacosx-version-min=12.0.0  ");
-				} else {
-					link_settings = gb_string_appendc(link_settings, " -mmacosx-version-min=10.12.0 ");
-				}
-				// This points the linker to where the entry point is
-				link_settings = gb_string_appendc(link_settings, " -e _main ");
-			}
-
-			gbString link_command_line = gb_string_make(heap_allocator(), "clang -Wno-unused-command-line-argument ");
-			defer (gb_string_free(link_command_line));
-
-			link_command_line = gb_string_appendc(link_command_line, object_files);
-			link_command_line = gb_string_append_fmt(link_command_line, " -o \"%.*s\" ", LIT(output_filename));
-			link_command_line = gb_string_append_fmt(link_command_line, " %s ", platform_lib_str);
-			link_command_line = gb_string_append_fmt(link_command_line, " %s ", lib_str);
-			link_command_line = gb_string_append_fmt(link_command_line, " %.*s ", LIT(build_context.link_flags));
-			link_command_line = gb_string_append_fmt(link_command_line, " %.*s ", LIT(build_context.extra_linker_flags));
-			link_command_line = gb_string_append_fmt(link_command_line, " %s ", link_settings);
-
-			result = system_exec_command_line_app("ld-link", link_command_line);
-
-			if (result) {
-				return result;
-			}
-
-			if (is_osx && build_context.ODIN_DEBUG) {
-				// NOTE: macOS links DWARF symbols dynamically. Dsymutil will map the stubs in the exe
-				// to the symbols in the object file
-				result = system_exec_command_line_app("dsymutil", "dsymutil %.*s", LIT(output_filename));
-
-				if (result) {
-					return result;
-				}
-			}
-		}
-	}
-
-	return result;
-}
-
 gb_internal Array<String> setup_args(int argc, char const **argv) {
 	gbAllocator a = heap_allocator();
 
@@ -653,8 +248,16 @@ enum BuildFlagKind {
 	BuildFlag_UseSeparateModules,
 	BuildFlag_NoThreadedChecker,
 	BuildFlag_ShowDebugMessages,
+
 	BuildFlag_Vet,
+	BuildFlag_VetShadowing,
+	BuildFlag_VetUnused,
+	BuildFlag_VetUsingStmt,
+	BuildFlag_VetUsingParam,
+	BuildFlag_VetStyle,
+	BuildFlag_VetSemicolon,
 	BuildFlag_VetExtra,
+
 	BuildFlag_IgnoreUnknownAttributes,
 	BuildFlag_ExtraLinkerFlags,
 	BuildFlag_ExtraAssemblerFlags,
@@ -671,7 +274,6 @@ enum BuildFlagKind {
 	BuildFlag_DisallowDo,
 	BuildFlag_DefaultToNilAllocator,
 	BuildFlag_StrictStyle,
-	BuildFlag_StrictStyleInitOnly,
 	BuildFlag_ForeignErrorProcedures,
 	BuildFlag_NoRTTI,
 	BuildFlag_DynamicMapCalls,
@@ -695,6 +297,8 @@ enum BuildFlagKind {
 	BuildFlag_InternalIgnoreLazy,
 	BuildFlag_InternalIgnoreLLVMBuild,
 
+	BuildFlag_Tilde,
+
 #if defined(GB_SYSTEM_WINDOWS)
 	BuildFlag_IgnoreVsSearch,
 	BuildFlag_ResourceFile,
@@ -830,8 +434,16 @@ gb_internal bool parse_build_flags(Array<String> args) {
 	add_flag(&build_flags, BuildFlag_UseSeparateModules,      str_lit("use-separate-modules"),      BuildFlagParam_None,    Command__does_build);
 	add_flag(&build_flags, BuildFlag_NoThreadedChecker,       str_lit("no-threaded-checker"),       BuildFlagParam_None,    Command__does_check);
 	add_flag(&build_flags, BuildFlag_ShowDebugMessages,       str_lit("show-debug-messages"),       BuildFlagParam_None,    Command_all);
+
 	add_flag(&build_flags, BuildFlag_Vet,                     str_lit("vet"),                       BuildFlagParam_None,    Command__does_check);
+	add_flag(&build_flags, BuildFlag_VetUnused,               str_lit("vet-unused"),                BuildFlagParam_None,    Command__does_check);
+	add_flag(&build_flags, BuildFlag_VetShadowing,            str_lit("vet-shadowing"),             BuildFlagParam_None,    Command__does_check);
+	add_flag(&build_flags, BuildFlag_VetUsingStmt,            str_lit("vet-using-stmt"),            BuildFlagParam_None,    Command__does_check);
+	add_flag(&build_flags, BuildFlag_VetUsingParam,           str_lit("vet-using-param"),           BuildFlagParam_None,    Command__does_check);
+	add_flag(&build_flags, BuildFlag_VetStyle,                str_lit("vet-style"),                 BuildFlagParam_None,    Command__does_check);
+	add_flag(&build_flags, BuildFlag_VetSemicolon,            str_lit("vet-semicolon"),             BuildFlagParam_None,    Command__does_check);
 	add_flag(&build_flags, BuildFlag_VetExtra,                str_lit("vet-extra"),                 BuildFlagParam_None,    Command__does_check);
+
 	add_flag(&build_flags, BuildFlag_IgnoreUnknownAttributes, str_lit("ignore-unknown-attributes"), BuildFlagParam_None,    Command__does_check);
 	add_flag(&build_flags, BuildFlag_ExtraLinkerFlags,        str_lit("extra-linker-flags"),        BuildFlagParam_String,  Command__does_build);
 	add_flag(&build_flags, BuildFlag_ExtraAssemblerFlags,     str_lit("extra-assembler-flags"),     BuildFlagParam_String,  Command__does_build);
@@ -847,7 +459,6 @@ gb_internal bool parse_build_flags(Array<String> args) {
 	add_flag(&build_flags, BuildFlag_DisallowDo,              str_lit("disallow-do"),               BuildFlagParam_None,    Command__does_check);
 	add_flag(&build_flags, BuildFlag_DefaultToNilAllocator,   str_lit("default-to-nil-allocator"),  BuildFlagParam_None,    Command__does_check);
 	add_flag(&build_flags, BuildFlag_StrictStyle,             str_lit("strict-style"),              BuildFlagParam_None,    Command__does_check);
-	add_flag(&build_flags, BuildFlag_StrictStyleInitOnly,     str_lit("strict-style-init-only"),    BuildFlagParam_None,    Command__does_check);
 	add_flag(&build_flags, BuildFlag_ForeignErrorProcedures,  str_lit("foreign-error-procedures"),  BuildFlagParam_None,    Command__does_check);
 
 	add_flag(&build_flags, BuildFlag_NoRTTI,                  str_lit("no-rtti"),                   BuildFlagParam_None,    Command__does_check);
@@ -869,6 +480,10 @@ gb_internal bool parse_build_flags(Array<String> args) {
 	add_flag(&build_flags, BuildFlag_InternalIgnoreLazy,      str_lit("internal-ignore-lazy"),      BuildFlagParam_None,    Command_all);
 	add_flag(&build_flags, BuildFlag_InternalIgnoreLLVMBuild, str_lit("internal-ignore-llvm-build"),BuildFlagParam_None,    Command_all);
 
+#if ALLOW_TILDE
+	add_flag(&build_flags, BuildFlag_Tilde,                   str_lit("tilde"),                     BuildFlagParam_None,    Command__does_build);
+#endif
+
 #if defined(GB_SYSTEM_WINDOWS)
 	add_flag(&build_flags, BuildFlag_IgnoreVsSearch,          str_lit("ignore-vs-search"),          BuildFlagParam_None,    Command__does_build);
 	add_flag(&build_flags, BuildFlag_ResourceFile,            str_lit("resource"),                  BuildFlagParam_String,  Command__does_build);
@@ -1362,13 +977,25 @@ gb_internal bool parse_build_flags(Array<String> args) {
 							build_context.show_debug_messages = true;
 							break;
 						case BuildFlag_Vet:
-							build_context.vet = true;
+							if (build_context.vet_flags & VetFlag_Extra) {
+								build_context.vet_flags |= VetFlag_All;
+							} else {
+								build_context.vet_flags &= ~VetFlag_Extra;
+								build_context.vet_flags |= VetFlag_All;
+							}
 							break;
-						case BuildFlag_VetExtra: {
-							build_context.vet = true;
-							build_context.vet_extra = true;
+
+						case BuildFlag_VetUnused:     build_context.vet_flags |= VetFlag_Unused;     break;
+						case BuildFlag_VetShadowing:  build_context.vet_flags |= VetFlag_Shadowing;  break;
+						case BuildFlag_VetUsingStmt:  build_context.vet_flags |= VetFlag_UsingStmt;  break;
+						case BuildFlag_VetUsingParam: build_context.vet_flags |= VetFlag_UsingParam; break;
+						case BuildFlag_VetStyle:      build_context.vet_flags |= VetFlag_Style;      break;
+						case BuildFlag_VetSemicolon:  build_context.vet_flags |= VetFlag_Semicolon;  break;
+
+						case BuildFlag_VetExtra:
+							build_context.vet_flags = VetFlag_All | VetFlag_Extra;
 							break;
-						}
+
 						case BuildFlag_IgnoreUnknownAttributes:
 							build_context.ignore_unknown_attributes = true;
 							break;
@@ -1456,20 +1083,9 @@ gb_internal bool parse_build_flags(Array<String> args) {
 						case BuildFlag_ForeignErrorProcedures:
 							build_context.ODIN_FOREIGN_ERROR_PROCEDURES = true;
 							break;
-						case BuildFlag_StrictStyle: {
-							if (build_context.strict_style_init_only) {
-								gb_printf_err("-strict-style and -strict-style-init-only cannot be used together\n");
-							}
+						case BuildFlag_StrictStyle:
 							build_context.strict_style = true;
 							break;
-						}
-						case BuildFlag_StrictStyleInitOnly: {
-							if (build_context.strict_style) {
-								gb_printf_err("-strict-style and -strict-style-init-only cannot be used together\n");
-							}
-							build_context.strict_style_init_only = true;
-							break;
-						}
 						case BuildFlag_Short:
 							build_context.cmd_doc_flags |= CmdDocFlag_Short;
 							break;
@@ -1536,6 +1152,10 @@ gb_internal bool parse_build_flags(Array<String> args) {
 						case BuildFlag_InternalIgnoreLLVMBuild:
 							build_context.ignore_llvm_build = true;
 							break;
+						case BuildFlag_Tilde:
+							build_context.tilde_backend = true;
+							break;
+
 					#if defined(GB_SYSTEM_WINDOWS)
 						case BuildFlag_IgnoreVsSearch: {
 							GB_ASSERT(value.kind == ExactValue_Invalid);
@@ -1572,7 +1192,7 @@ gb_internal bool parse_build_flags(Array<String> args) {
 								if (path_is_directory(path)) {
 									gb_printf_err("Invalid -pdb-name path. %.*s, is a directory.\n", LIT(path));
 									bad_flags = true;
-									break;									
+									break;
 								}
 								// #if defined(GB_SYSTEM_WINDOWS)
 								// 	String ext = path_extension(path);
@@ -2005,6 +1625,10 @@ gb_internal void print_show_help(String const arg0, String const &command) {
 		print_usage_line(2, "Shows an advanced overview of the timings of different stages within the compiler in milliseconds");
 		print_usage_line(0, "");
 
+		print_usage_line(1, "-show-system-calls");
+		print_usage_line(2, "Prints the whole command and arguments for calls to external tools like linker and assembler");
+		print_usage_line(0, "");
+
 		print_usage_line(1, "-export-timings:<format>");
 		print_usage_line(2, "Export timings to one of a few formats. Requires `-show-timings` or `-show-more-timings`");
 		print_usage_line(2, "Available options:");
@@ -2114,29 +1738,55 @@ gb_internal void print_show_help(String const arg0, String const &command) {
 	}
 
 	if (check) {
-		#if defined(GB_SYSTEM_WINDOWS)
 		print_usage_line(1, "-no-threaded-checker");
 		print_usage_line(2, "Disabled multithreading in the semantic checker stage");
 		print_usage_line(0, "");
-		#else
-		print_usage_line(1, "-threaded-checker");
-		print_usage_line(1, "[EXPERIMENTAL]");
-		print_usage_line(2, "Multithread the semantic checker stage");
-		print_usage_line(0, "");
-		#endif
+	}
 
+	if (check) {
 		print_usage_line(1, "-vet");
 		print_usage_line(2, "Do extra checks on the code");
 		print_usage_line(2, "Extra checks include:");
-		print_usage_line(3, "Variable shadowing within procedures");
-		print_usage_line(3, "Unused declarations");
+		print_usage_line(2, "-vet-unused");
+		print_usage_line(2, "-vet-shadowing");
+		print_usage_line(2, "-vet-using-stmt");
+		print_usage_line(0, "");
+
+		print_usage_line(1, "-vet-unused");
+		print_usage_line(2, "Checks for unused declarations");
+		print_usage_line(0, "");
+
+		print_usage_line(1, "-vet-shadowing");
+		print_usage_line(2, "Checks for variable shadowing within procedures");
+		print_usage_line(0, "");
+
+		print_usage_line(1, "-vet-using-stmt");
+		print_usage_line(2, "Checks for the use of 'using' as a statement");
+		print_usage_line(2, "'using' is considered bad practice outside of immediate refactoring");
+		print_usage_line(0, "");
+
+		print_usage_line(1, "-vet-using-param");
+		print_usage_line(2, "Checks for the use of 'using' on procedure parameters");
+		print_usage_line(2, "'using' is considered bad practice outside of immediate refactoring");
+		print_usage_line(0, "");
+
+		print_usage_line(1, "-vet-style");
+		print_usage_line(2, "Errs on missing trailing commas followed by a newline");
+		print_usage_line(2, "Errs on deprecated syntax");
+		print_usage_line(2, "Does not err on unneeded tokens (unlike -strict-style)");
+		print_usage_line(0, "");
+
+		print_usage_line(1, "-vet-semicolon");
+		print_usage_line(2, "Errs on unneeded semicolons");
 		print_usage_line(0, "");
 
 		print_usage_line(1, "-vet-extra");
 		print_usage_line(2, "Do even more checks than standard vet on the code");
 		print_usage_line(2, "To treat the extra warnings as errors, use -warnings-as-errors");
 		print_usage_line(0, "");
+	}
 
+	if (check) {
 		print_usage_line(1, "-ignore-unknown-attributes");
 		print_usage_line(2, "Ignores unknown attributes");
 		print_usage_line(2, "This can be used with metaprogramming tools");
@@ -2206,10 +1856,8 @@ gb_internal void print_show_help(String const arg0, String const &command) {
 
 		print_usage_line(1, "-strict-style");
 		print_usage_line(2, "Errs on unneeded tokens, such as unneeded semicolons");
-		print_usage_line(0, "");
-
-		print_usage_line(1, "-strict-style-init-only");
-		print_usage_line(2, "Errs on unneeded tokens, such as unneeded semicolons, only on the initial project");
+		print_usage_line(2, "Errs on missing trailing commas followed by a newline");
+		print_usage_line(2, "Errs on deprecated syntax");
 		print_usage_line(0, "");
 
 		print_usage_line(1, "-ignore-warnings");
@@ -2347,6 +1995,8 @@ gb_internal void print_show_unused(Checker *c) {
 }
 
 gb_internal bool check_env(void) {
+	TIME_SECTION("init check env");
+
 	gbAllocator a = heap_allocator();
 	char const *odin_root = gb_get_env("ODIN_ROOT", a);
 	defer (gb_free(a, cast(void *)odin_root));
@@ -2548,6 +2198,7 @@ gb_internal int strip_semicolons(Parser *parser) {
 }
 
 gb_internal void init_terminal(void) {
+	TIME_SECTION("init terminal");
 	build_context.has_ansi_terminal_colours = false;
 
 	gbAllocator a = heap_allocator();
@@ -2614,11 +2265,13 @@ int main(int arg_count, char const **arg_ptr) {
 		return 1;
 	}
 
+	TIME_SECTION("init default library collections");
 	array_init(&library_collections, heap_allocator());
 	// NOTE(bill): 'core' cannot be (re)defined by the user
 	add_library_collection(str_lit("core"), get_fullpath_relative(heap_allocator(), odin_root_dir(), str_lit("core")));
 	add_library_collection(str_lit("vendor"), get_fullpath_relative(heap_allocator(), odin_root_dir(), str_lit("vendor")));
 
+	TIME_SECTION("init args");
 	map_init(&build_context.defined_values);
 	build_context.extra_packages.allocator = heap_allocator();
 	string_set_init(&build_context.test_names);
@@ -2814,12 +2467,14 @@ int main(int arg_count, char const **arg_ptr) {
 		for_array(i, build_context.build_paths) {
 			String build_path = path_to_string(heap_allocator(), build_context.build_paths[i]);
 			debugf("build_paths[%ld]: %.*s\n", i, LIT(build_path));
-		}		
+		}
 	}
 
+	TIME_SECTION("init thread pool");
 	init_global_thread_pool();
 	defer (thread_pool_destroy(&global_thread_pool));
 
+	TIME_SECTION("init universal");
 	init_universal();
 	// TODO(bill): prevent compiling without a linker
 
@@ -2882,16 +2537,18 @@ int main(int arg_count, char const **arg_ptr) {
 		return 0;
 	}
 
-	MAIN_TIME_SECTION("LLVM API Code Gen");
-	lbGenerator *gen = gb_alloc_item(permanent_allocator(), lbGenerator);
-	if (!lb_init_generator(gen, checker)) {
-		return 1;
-	}
-	if (lb_generate_code(gen)) {
+#if ALLOW_TILDE
+	if (build_context.tilde_backend) {
+		LinkerData linker_data = {};
+		MAIN_TIME_SECTION("Tilde Code Gen");
+		if (!cg_generate_code(checker, &linker_data)) {
+			return 1;
+		}
+
 		switch (build_context.build_mode) {
 		case BuildMode_Executable:
 		case BuildMode_DynamicLibrary:
-			i32 result = linker_stage(gen);
+			i32 result = linker_stage(&linker_data);
 			if (result) {
 				if (build_context.show_timings) {
 					show_timings(checker, &global_timings);
@@ -2900,9 +2557,31 @@ int main(int arg_count, char const **arg_ptr) {
 			}
 			break;
 		}
-	}
+	} else
+#endif
+	{
+		MAIN_TIME_SECTION("LLVM API Code Gen");
+		lbGenerator *gen = gb_alloc_item(permanent_allocator(), lbGenerator);
+		if (!lb_init_generator(gen, checker)) {
+			return 1;
+		}
+		if (lb_generate_code(gen)) {
+			switch (build_context.build_mode) {
+			case BuildMode_Executable:
+			case BuildMode_DynamicLibrary:
+				i32 result = linker_stage(gen);
+				if (result) {
+					if (build_context.show_timings) {
+						show_timings(checker, &global_timings);
+					}
+					return result;
+				}
+				break;
+			}
+		}
 
-	remove_temp_files(gen);
+		remove_temp_files(gen);
+	}
 
 	if (build_context.show_timings) {
 		show_timings(checker, &global_timings);

+ 139 - 34
src/parser.cpp

@@ -1,7 +1,21 @@
 #include "parser_pos.cpp"
 
-// #undef at the bottom of this file
-#define ALLOW_NEWLINE (!build_context.strict_style)
+gb_internal u64 ast_file_vet_flags(AstFile *f) {
+	if (f->vet_flags_set) {
+		return f->vet_flags;
+	}
+	return build_context.vet_flags;
+}
+
+gb_internal bool ast_file_vet_style(AstFile *f) {
+	return (ast_file_vet_flags(f) & VetFlag_Style) != 0;
+}
+
+
+gb_internal bool file_allow_newline(AstFile *f) {
+	bool is_strict = build_context.strict_style || ast_file_vet_style(f);
+	return !is_strict;
+}
 
 gb_internal Token token_end_of_line(AstFile *f, Token tok) {
 	u8 const *start = f->tokenizer.start + tok.pos.offset;
@@ -1567,29 +1581,29 @@ gb_internal void assign_removal_flag_to_semicolon(AstFile *f) {
 	Token *prev_token = &f->tokens[f->prev_token_index];
 	Token *curr_token = &f->tokens[f->curr_token_index];
 	GB_ASSERT(prev_token->kind == Token_Semicolon);
-	if (prev_token->string == ";") {
-		bool ok = false;
-		if (curr_token->pos.line > prev_token->pos.line) {
+	if (prev_token->string != ";") {
+		return;
+	}
+	bool ok = false;
+	if (curr_token->pos.line > prev_token->pos.line) {
+		ok = true;
+	} else if (curr_token->pos.line == prev_token->pos.line) {
+		switch (curr_token->kind) {
+		case Token_CloseBrace:
+		case Token_CloseParen:
+		case Token_EOF:
 			ok = true;
-		} else if (curr_token->pos.line == prev_token->pos.line) {
-			switch (curr_token->kind) {
-			case Token_CloseBrace:
-			case Token_CloseParen:
-			case Token_EOF:
-				ok = true;
-				break;
-			}
-		}
-			
-		if (ok) {
-			if (build_context.strict_style) {
-				syntax_error(*prev_token, "Found unneeded semicolon");
-			} else if (build_context.strict_style_init_only && f->pkg->kind == Package_Init) {
-				syntax_error(*prev_token, "Found unneeded semicolon");
-			}
-			prev_token->flags |= TokenFlag_Remove;
+			break;
 		}
 	}
+	if (!ok) {
+		return;
+	}
+
+	if (build_context.strict_style || (ast_file_vet_flags(f) & VetFlag_Semicolon)) {
+		syntax_error(*prev_token, "Found unneeded semicolon");
+	}
+	prev_token->flags |= TokenFlag_Remove;
 }
 
 gb_internal void expect_semicolon(AstFile *f) {
@@ -2221,7 +2235,11 @@ gb_internal Ast *parse_operand(AstFile *f, bool lhs) {
 			return parse_check_directive_for_statement(operand, name, StateFlag_no_type_assert);
 		} else if (name.string == "relative") {
 			Ast *tag = ast_basic_directive(f, token, name);
-			tag = parse_call_expr(f, tag);
+			if (f->curr_token.kind != Token_OpenParen) {
+				syntax_error(tag, "expected #relative(<integer type>) <type>");
+			} else {
+				tag = parse_call_expr(f, tag);
+			}
 			Ast *type = parse_type(f);
 			return ast_relative_type(f, tag, type);
 		} else if (name.string == "force_inline" ||
@@ -2748,7 +2766,7 @@ gb_internal Ast *parse_call_expr(AstFile *f, Ast *operand) {
 	isize prev_expr_level = f->expr_level;
 	bool prev_allow_newline = f->allow_newline;
 	f->expr_level = 0;
-	f->allow_newline = ALLOW_NEWLINE;
+	f->allow_newline = file_allow_newline(f);
 
 	open_paren = expect_token(f, Token_OpenParen);
 
@@ -3147,7 +3165,7 @@ gb_internal Ast *parse_expr(AstFile *f, bool lhs) {
 
 gb_internal Array<Ast *> parse_expr_list(AstFile *f, bool lhs) {
 	bool allow_newline = f->allow_newline;
-	f->allow_newline = ALLOW_NEWLINE;
+	f->allow_newline = file_allow_newline(f);
 
 	auto list = array_make<Ast *>(heap_allocator());
 	for (;;) {
@@ -3472,7 +3490,7 @@ gb_internal Ast *parse_results(AstFile *f, bool *diverging) {
 	Ast *list = nullptr;
 	expect_token(f, Token_OpenParen);
 	list = parse_field_list(f, nullptr, FieldFlag_Results, Token_CloseParen, true, false);
-	if (ALLOW_NEWLINE) {
+	if (file_allow_newline(f)) {
 		skip_possible_newline(f);
 	}
 	expect_token_after(f, Token_CloseParen, "parameter list");
@@ -3532,7 +3550,7 @@ gb_internal Ast *parse_proc_type(AstFile *f, Token proc_token) {
 
 	expect_token(f, Token_OpenParen);
 	params = parse_field_list(f, nullptr, FieldFlag_Signature, Token_CloseParen, true, true);
-	if (ALLOW_NEWLINE) {
+	if (file_allow_newline(f)) {
 		skip_possible_newline(f);
 	}
 	expect_token_after(f, Token_CloseParen, "parameter list");
@@ -3754,7 +3772,7 @@ gb_internal bool allow_field_separator(AstFile *f) {
 	}
 	if (token.kind == Token_Semicolon) {
 		bool ok = false;
-		if (ALLOW_NEWLINE && token_is_newline(token)) {
+		if (file_allow_newline(f) && token_is_newline(token)) {
 			TokenKind next = peek_token(f).kind;
 			switch (next) {
 			case Token_CloseBrace:
@@ -3818,7 +3836,7 @@ gb_internal bool check_procedure_name_list(Array<Ast *> const &names) {
 gb_internal Ast *parse_field_list(AstFile *f, isize *name_count_, u32 allowed_flags, TokenKind follow, bool allow_default_parameters, bool allow_typeid_token) {
 	bool prev_allow_newline = f->allow_newline;
 	defer (f->allow_newline = prev_allow_newline);
-	f->allow_newline = ALLOW_NEWLINE;
+	f->allow_newline = file_allow_newline(f);
 
 	Token start_token = f->curr_token;
 
@@ -4169,6 +4187,8 @@ gb_internal Ast *parse_when_stmt(AstFile *f) {
 		syntax_error(f->curr_token, "Expected condition for when statement");
 	}
 
+	bool was_in_when_statement = f->in_when_statement;
+	f->in_when_statement = true;
 	if (allow_token(f, Token_do)) {
 		body = parse_do_body(f, cond ? ast_token(cond) : token, "then when statement");
 	} else {
@@ -4195,6 +4215,7 @@ gb_internal Ast *parse_when_stmt(AstFile *f) {
 			break;
 		}
 	}
+	f->in_when_statement = was_in_when_statement;
 
 	return ast_when_stmt(f, token, cond, body, else_stmt);
 }
@@ -4456,6 +4477,10 @@ gb_internal Ast *parse_import_decl(AstFile *f, ImportDeclKind kind) {
 		array_add(&f->imports, s);
 	}
 
+	if (f->in_when_statement) {
+		syntax_error(import_name, "Cannot use 'import' within a 'when' statement. Prefer using the file suffixes (e.g. foo_windows.odin) or '//+build' tags");
+	}
+
 	if (kind != ImportDecl_Standard) {
 		syntax_error(import_name, "'using import' is not allowed, please use the import name explicitly");
 	}
@@ -4954,7 +4979,6 @@ gb_internal bool init_parser(Parser *p) {
 
 gb_internal void destroy_parser(Parser *p) {
 	GB_ASSERT(p != nullptr);
-	// TODO(bill): Fix memory leak
 	for (AstPackage *pkg : p->packages) {
 		for (AstFile *file : pkg->files) {
 			destroy_ast_file(file);
@@ -4998,7 +5022,6 @@ gb_internal WORKER_TASK_PROC(parser_worker_proc) {
 
 
 gb_internal void parser_add_file_to_process(Parser *p, AstPackage *pkg, FileInfo fi, TokenPos pos) {
-	// TODO(bill): Use a better allocator
 	ImportedFile f = {pkg, fi, pos, p->file_to_process_count++};
 	auto wd = gb_alloc_item(permanent_allocator(), ParserWorkerData);
 	wd->parser = p;
@@ -5528,6 +5551,88 @@ gb_internal bool parse_build_tag(Token token_for_pos, String s) {
 	return any_correct;
 }
 
+gb_internal String vet_tag_get_token(String s, String *out) {
+	s = string_trim_whitespace(s);
+	isize n = 0;
+	while (n < s.len) {
+		Rune rune = 0;
+		isize width = utf8_decode(&s[n], s.len-n, &rune);
+		if (n == 0 && rune == '!') {
+
+		} else if (!rune_is_letter(rune) && !rune_is_digit(rune) && rune != '-') {
+			isize k = gb_max(gb_max(n, width), 1);
+			*out = substring(s, k, s.len);
+			return substring(s, 0, k);
+		}
+		n += width;
+	}
+	out->len = 0;
+	return s;
+}
+
+
+gb_internal u64 parse_vet_tag(Token token_for_pos, String s) {
+	String const prefix = str_lit("+vet");
+	GB_ASSERT(string_starts_with(s, prefix));
+	s = string_trim_whitespace(substring(s, prefix.len, s.len));
+
+	if (s.len == 0) {
+		return VetFlag_All;
+	}
+
+
+	u64 vet_flags = 0;
+	u64 vet_not_flags = 0;
+
+	while (s.len > 0) {
+		String p = string_trim_whitespace(vet_tag_get_token(s, &s));
+		if (p.len == 0) {
+			break;
+		}
+
+		bool is_notted = false;
+		if (p[0] == '!') {
+			is_notted = true;
+			p = substring(p, 1, p.len);
+			if (p.len == 0) {
+				syntax_error(token_for_pos, "Expected a vet flag name after '!'");
+				return build_context.vet_flags;
+			}
+		}
+
+		u64 flag = get_vet_flag_from_name(p);
+		if (flag != VetFlag_NONE) {
+			if (is_notted) {
+				vet_not_flags |= flag;
+			} else {
+				vet_flags     |= flag;
+			}
+		} else {
+			ERROR_BLOCK();
+			syntax_error(token_for_pos, "Invalid vet flag name: %.*s", LIT(p));
+			error_line("\tExpected one of the following\n");
+			error_line("\tunused\n");
+			error_line("\tshadowing\n");
+			error_line("\tusing-stmt\n");
+			error_line("\tusing-param\n");
+			error_line("\textra\n");
+			return build_context.vet_flags;
+		}
+	}
+
+	if (vet_flags == 0 && vet_not_flags == 0) {
+		return build_context.vet_flags;
+	}
+	if (vet_flags == 0 && vet_not_flags != 0) {
+		return build_context.vet_flags &~ vet_not_flags;
+	}
+	if (vet_flags != 0 && vet_not_flags == 0) {
+		return vet_flags;
+	}
+	GB_ASSERT(vet_flags != 0 && vet_not_flags != 0);
+	return vet_flags &~ vet_not_flags;
+}
+
 gb_internal String dir_from_path(String path) {
 	String base_dir = path;
 	for (isize i = path.len-1; i >= 0; i--) {
@@ -5679,6 +5784,9 @@ gb_internal bool parse_file(Parser *p, AstFile *f) {
 						if (!parse_build_tag(tok, lc)) {
 							return false;
 						}
+					} else if (string_starts_with(lc, str_lit("+vet"))) {
+						f->vet_flags = parse_vet_tag(tok, lc);
+						f->vet_flags_set = true;
 					} else if (string_starts_with(lc, str_lit("+ignore"))) {
 							return false;
 					} else if (string_starts_with(lc, str_lit("+private"))) {
@@ -5920,6 +6028,3 @@ gb_internal ParseFileError parse_packages(Parser *p, String init_filename) {
 	return ParseFile_None;
 }
 
-
-
-#undef ALLOW_NEWLINE

+ 3 - 0
src/parser.hpp

@@ -104,6 +104,8 @@ struct AstFile {
 	Token        package_token;
 	String       package_name;
 
+	u64          vet_flags;
+	bool         vet_flags_set;
 
 	// >= 0: In Expression
 	// <  0: In Control Clause
@@ -114,6 +116,7 @@ struct AstFile {
 	bool         allow_in_expr; // NOTE(bill): in expression are only allowed in certain cases
 	bool         in_foreign_block;
 	bool         allow_type;
+	bool         in_when_statement;
 
 	isize total_file_decl_count;
 	isize delayed_decl_count;

+ 813 - 0
src/tilde.cpp

@@ -0,0 +1,813 @@
+#include "tilde.hpp"
+
+
+gb_global Slice<TB_Arena> global_tb_arenas;
+
+gb_internal TB_Arena *cg_arena(void) {
+	return &global_tb_arenas[current_thread_index()];
+}
+
+gb_internal void cg_global_arena_init(void) {
+	global_tb_arenas = slice_make<TB_Arena>(permanent_allocator(), global_thread_pool.threads.count);
+	for_array(i, global_tb_arenas) {
+		tb_arena_create(&global_tb_arenas[i], 2ull<<20);
+	}
+}
+
+// returns TB_TYPE_VOID if not trivially possible
+gb_internal TB_DataType cg_data_type(Type *t) {
+	GB_ASSERT(t != nullptr);
+	t = core_type(t);
+	i64 sz = type_size_of(t);
+	switch (t->kind) {
+	case Type_Basic:
+		switch (t->Basic.kind) {
+		case Basic_bool:
+		case Basic_b8:
+		case Basic_b16:
+		case Basic_b32:
+		case Basic_b64:
+
+		case Basic_i8:
+		case Basic_u8:
+		case Basic_i16:
+		case Basic_u16:
+		case Basic_i32:
+		case Basic_u32:
+		case Basic_i64:
+		case Basic_u64:
+		case Basic_i128:
+		case Basic_u128:
+
+		case Basic_rune:
+
+		case Basic_int:
+		case Basic_uint:
+		case Basic_uintptr:
+		case Basic_typeid:
+			return TB_TYPE_INTN(cast(u16)gb_min(8*sz, 64));
+
+		case Basic_f16: return TB_TYPE_F16;
+		case Basic_f32: return TB_TYPE_F32;
+		case Basic_f64: return TB_TYPE_F64;
+
+		case Basic_rawptr:  return TB_TYPE_PTR;
+		case Basic_cstring: return TB_TYPE_PTR;
+
+
+		// Endian Specific Types
+		case Basic_i16le:
+		case Basic_u16le:
+		case Basic_i32le:
+		case Basic_u32le:
+		case Basic_i64le:
+		case Basic_u64le:
+		case Basic_i128le:
+		case Basic_u128le:
+		case Basic_i16be:
+		case Basic_u16be:
+		case Basic_i32be:
+		case Basic_u32be:
+		case Basic_i64be:
+		case Basic_u64be:
+		case Basic_i128be:
+		case Basic_u128be:
+			return TB_TYPE_INTN(cast(u16)gb_min(8*sz, 64));
+
+		case Basic_f16le: return TB_TYPE_F16;
+		case Basic_f32le: return TB_TYPE_F32;
+		case Basic_f64le: return TB_TYPE_F64;
+
+		case Basic_f16be: return TB_TYPE_F16;
+		case Basic_f32be: return TB_TYPE_F32;
+		case Basic_f64be: return TB_TYPE_F64;
+		}
+		break;
+
+	case Type_Pointer:
+	case Type_MultiPointer:
+	case Type_Proc:
+		return TB_TYPE_PTR;
+
+	case Type_BitSet:
+		return cg_data_type(bit_set_to_int(t));
+
+	case Type_RelativePointer:
+		return cg_data_type(t->RelativePointer.base_integer);
+	}
+
+	// unknown
+	return {};
+}
+
+
+gb_internal cgValue cg_value(TB_Global *g, Type *type) {
+	return cg_value((TB_Symbol *)g, type);
+}
+gb_internal cgValue cg_value(TB_External *e, Type *type) {
+	return cg_value((TB_Symbol *)e, type);
+}
+gb_internal cgValue cg_value(TB_Function *f, Type *type) {
+	return cg_value((TB_Symbol *)f, type);
+}
+gb_internal cgValue cg_value(TB_Symbol *s, Type *type) {
+	cgValue v = {};
+	v.kind = cgValue_Symbol;
+	v.type = type;
+	v.symbol = s;
+	return v;
+}
+gb_internal cgValue cg_value(TB_Node *node, Type *type) {
+	cgValue v = {};
+	v.kind = cgValue_Value;
+	v.type = type;
+	v.node = node;
+	return v;
+}
+gb_internal cgValue cg_lvalue_addr(TB_Node *node, Type *type) {
+	GB_ASSERT(node->dt.type == TB_PTR);
+	cgValue v = {};
+	v.kind = cgValue_Addr;
+	v.type = type;
+	v.node = node;
+	return v;
+}
+
+gb_internal cgValue cg_lvalue_addr_to_value(cgValue v) {
+	if (v.kind == cgValue_Value) {
+		GB_ASSERT(is_type_pointer(v.type));
+		GB_ASSERT(v.node->dt.type == TB_PTR);
+	} else {
+		GB_ASSERT(v.kind == cgValue_Addr);
+		GB_ASSERT(v.node->dt.type == TB_PTR);
+		v.kind = cgValue_Value;
+		v.type = alloc_type_pointer(v.type);
+	}
+	return v;
+}
+
+gb_internal cgValue cg_value_multi(cgValueMulti *multi, Type *type) {
+	GB_ASSERT(type->kind == Type_Tuple);
+	GB_ASSERT(multi != nullptr);
+	GB_ASSERT(type->Tuple.variables.count > 1);
+	GB_ASSERT(multi->values.count == type->Tuple.variables.count);
+	cgValue v = {};
+	v.kind = cgValue_Multi;
+	v.type = type;
+	v.multi = multi;
+	return v;
+}
+
+gb_internal cgValue cg_value_multi(Slice<cgValue> const &values, Type *type) {
+	cgValueMulti *multi = gb_alloc_item(permanent_allocator(), cgValueMulti);
+	multi->values = values;
+	return cg_value_multi(multi, type);
+}
+
+
+gb_internal cgValue cg_value_multi2(cgValue const &x, cgValue const &y, Type *type) {
+	GB_ASSERT(type->kind == Type_Tuple);
+	GB_ASSERT(type->Tuple.variables.count == 2);
+	cgValueMulti *multi = gb_alloc_item(permanent_allocator(), cgValueMulti);
+	multi->values = slice_make<cgValue>(permanent_allocator(), 2);
+	multi->values[0] = x;
+	multi->values[1] = y;
+	return cg_value_multi(multi, type);
+}
+
+
+gb_internal cgAddr cg_addr(cgValue const &value) {
+	GB_ASSERT(value.kind != cgValue_Multi);
+	cgAddr addr = {};
+	addr.kind = cgAddr_Default;
+	addr.addr = value;
+	if (addr.addr.kind == cgValue_Addr) {
+		GB_ASSERT(addr.addr.node != nullptr);
+		addr.addr.kind = cgValue_Value;
+		addr.addr.type = alloc_type_pointer(addr.addr.type);
+	}
+	return addr;
+}
+
+gb_internal void cg_set_debug_pos_from_node(cgProcedure *p, Ast *node) {
+	if (node) {
+		TokenPos pos = ast_token(node).pos;
+		TB_FileID *file_id = map_get(&p->module->file_id_map, cast(uintptr)pos.file_id);
+		if (file_id) {
+			tb_inst_set_location(p->func, *file_id, pos.line);
+		}
+	}
+}
+
+gb_internal void cg_add_symbol(cgModule *m, Entity *e, TB_Symbol *symbol) {
+	if (e) {
+		rw_mutex_lock(&m->values_mutex);
+		map_set(&m->symbols, e, symbol);
+		rw_mutex_unlock(&m->values_mutex);
+	}
+}
+
+gb_internal void cg_add_entity(cgModule *m, Entity *e, cgValue const &val) {
+	if (e) {
+		rw_mutex_lock(&m->values_mutex);
+		GB_ASSERT(val.node != nullptr);
+		map_set(&m->values, e, val);
+		rw_mutex_unlock(&m->values_mutex);
+	}
+}
+
+gb_internal void cg_add_member(cgModule *m, String const &name, cgValue const &val) {
+	if (name.len > 0) {
+		rw_mutex_lock(&m->values_mutex);
+		string_map_set(&m->members, name, val);
+		rw_mutex_unlock(&m->values_mutex);
+	}
+}
+
+gb_internal void cg_add_procedure_value(cgModule *m, cgProcedure *p) {
+	rw_mutex_lock(&m->values_mutex);
+	if (p->entity != nullptr) {
+		map_set(&m->procedure_values, p->func, p->entity);
+		if (p->symbol != nullptr) {
+			map_set(&m->symbols, p->entity, p->symbol);
+		}
+	}
+	string_map_set(&m->procedures, p->name, p);
+	rw_mutex_unlock(&m->values_mutex);
+
+}
+
+gb_internal TB_Symbol *cg_find_symbol_from_entity(cgModule *m, Entity *e) {
+	GB_ASSERT(e != nullptr);
+
+	rw_mutex_lock(&m->values_mutex);
+	TB_Symbol **found = map_get(&m->symbols, e);
+	if (found) {
+		rw_mutex_unlock(&m->values_mutex);
+		return *found;
+	}
+
+	String link_name = cg_get_entity_name(m, e);
+	cgProcedure **proc_found = string_map_get(&m->procedures, link_name);
+	if (proc_found) {
+		TB_Symbol *symbol = (*proc_found)->symbol;
+		map_set(&m->symbols, e, symbol);
+		rw_mutex_unlock(&m->values_mutex);
+		return symbol;
+	}
+	rw_mutex_unlock(&m->values_mutex);
+
+	if (e->kind == Entity_Procedure) {
+		debugf("[Tilde] try to generate procedure %.*s as it was not in the minimum_dependency_set", LIT(e->token.string));
+		// IMPORTANT TODO(bill): This is an utter bodge, try and fix this shit
+		cgProcedure *p = cg_procedure_create(m, e);
+		if (p != nullptr) {
+			GB_ASSERT(p->symbol != nullptr);
+			cg_add_procedure_to_queue(p);
+			return p->symbol;
+		}
+	}
+
+
+	GB_PANIC("could not find entity's symbol %.*s", LIT(e->token.string));
+	return nullptr;
+}
+
+
+struct cgGlobalVariable {
+	cgValue var;
+	cgValue init;
+	DeclInfo *decl;
+	bool is_initialized;
+};
+
+// Returns already_has_entry_point
+gb_internal bool cg_global_variables_create(cgModule *m, Array<cgGlobalVariable> *global_variables) {
+	isize global_variable_max_count = 0;
+	bool already_has_entry_point = false;
+
+	for (Entity *e : m->info->entities) {
+		String name = e->token.string;
+
+		if (e->kind == Entity_Variable) {
+			global_variable_max_count++;
+		} else if (e->kind == Entity_Procedure) {
+			if ((e->scope->flags&ScopeFlag_Init) && name == "main") {
+				GB_ASSERT(e == m->info->entry_point);
+			}
+			if (build_context.command_kind == Command_test &&
+			    (e->Procedure.is_export || e->Procedure.link_name.len > 0)) {
+				String link_name = e->Procedure.link_name;
+				if (e->pkg->kind == Package_Runtime) {
+					if (link_name == "main"           ||
+					    link_name == "DllMain"        ||
+					    link_name == "WinMain"        ||
+					    link_name == "wWinMain"       ||
+					    link_name == "mainCRTStartup" ||
+					    link_name == "_start") {
+						already_has_entry_point = true;
+					}
+				}
+			}
+		}
+	}
+	*global_variables = array_make<cgGlobalVariable>(permanent_allocator(), 0, global_variable_max_count);
+
+	auto *min_dep_set = &m->info->minimum_dependency_set;
+
+	for (DeclInfo *d : m->info->variable_init_order) {
+		Entity *e = d->entity;
+
+		if ((e->scope->flags & ScopeFlag_File) == 0) {
+			continue;
+		}
+
+		if (!ptr_set_exists(min_dep_set, e)) {
+			continue;
+		}
+
+		DeclInfo *decl = decl_info_of_entity(e);
+		if (decl == nullptr) {
+			continue;
+		}
+		GB_ASSERT(e->kind == Entity_Variable);
+
+		bool is_foreign = e->Variable.is_foreign;
+		bool is_export  = e->Variable.is_export;
+
+		String name = cg_get_entity_name(m, e);
+
+		TB_Linkage linkage = TB_LINKAGE_PRIVATE;
+
+		if (is_foreign) {
+			linkage = TB_LINKAGE_PUBLIC;
+			// lb_add_foreign_library_path(m, e->Variable.foreign_library);
+			// lb_set_wasm_import_attributes(g.value, e, name);
+		} else if (is_export) {
+			linkage = TB_LINKAGE_PUBLIC;
+		}
+		// lb_set_linkage_from_entity_flags(m, g.value, e->flags);
+
+		TB_DebugType *debug_type = cg_debug_type(m, e->type);
+		TB_Global *global = tb_global_create(m->mod, name.len, cast(char const *)name.text, debug_type, linkage);
+		cgValue g = cg_value(global, alloc_type_pointer(e->type));
+
+		TB_ModuleSection *section = tb_module_get_data(m->mod);
+
+		if (e->Variable.thread_local_model != "") {
+			section = tb_module_get_tls(m->mod);
+		}
+		if (e->Variable.link_section.len > 0) {
+			// TODO(bill): custom module sections
+			// LLVMSetSection(g.value, alloc_cstring(permanent_allocator(), e->Variable.link_section));
+		}
+
+
+		cgGlobalVariable var = {};
+		var.var = g;
+		var.decl = decl;
+
+		if (decl->init_expr != nullptr) {
+			TypeAndValue tav = type_and_value_of_expr(decl->init_expr);
+
+			isize max_regions = cg_global_const_calculate_region_count(tav.value, e->type);
+			tb_global_set_storage(m->mod, section, global, type_size_of(e->type), type_align_of(e->type), max_regions);
+
+			if (tav.mode == Addressing_Constant &&
+			    tav.value.kind != ExactValue_Invalid) {
+				cg_global_const_add_region(m, tav.value, e->type, global, 0);
+				var.is_initialized = true;
+			}
+			if (!var.is_initialized && is_type_untyped_nil(tav.type)) {
+				var.is_initialized = true;
+			}
+		} else {
+			var.is_initialized = true;
+			// TODO(bill): is this even needed;
+			i64 max_regions = cg_global_const_calculate_region_count_from_basic_type(e->type);
+			tb_global_set_storage(m->mod, section, global, type_size_of(e->type), type_align_of(e->type), max_regions);
+		}
+
+		array_add(global_variables, var);
+
+		cg_add_symbol(m, e, cast(TB_Symbol *)global);
+		cg_add_entity(m, e, g);
+		cg_add_member(m, name, g);
+	}
+
+	cg_setup_type_info_data(m);
+
+	return already_has_entry_point;
+}
+
+gb_internal void cg_global_variables_initialize(cgProcedure *p, Array<cgGlobalVariable> *global_variables) {
+	for (cgGlobalVariable &var : *global_variables) {
+		if (var.is_initialized) {
+			continue;
+		}
+		cgValue src = cg_build_expr(p, var.decl->init_expr);
+		cgValue dst = cg_flatten_value(p, var.var);
+		cg_emit_store(p, dst, src);
+	}
+}
+
+
+gb_internal cgModule *cg_module_create(Checker *c) {
+	cgModule *m = gb_alloc_item(permanent_allocator(), cgModule);
+
+	m->checker = c;
+	m->info = &c->info;
+
+
+	TB_FeatureSet feature_set = {};
+	bool is_jit = false;
+	m->mod = tb_module_create(TB_ARCH_X86_64, TB_SYSTEM_WINDOWS, &feature_set, is_jit);
+	tb_module_set_tls_index(m->mod, 10, "_tls_index");
+
+	map_init(&m->values);
+	map_init(&m->symbols);
+	map_init(&m->file_id_map);
+	map_init(&m->debug_type_map);
+	map_init(&m->proc_debug_type_map);
+	map_init(&m->proc_proto_map);
+	map_init(&m->anonymous_proc_lits_map);
+	map_init(&m->equal_procs);
+	map_init(&m->hasher_procs);
+	map_init(&m->map_get_procs);
+	map_init(&m->map_set_procs);
+
+	array_init(&m->single_threaded_procedure_queue, heap_allocator());
+
+
+	for_array(id, global_files) {
+		if (AstFile *f = global_files[id]) {
+			char const *path = alloc_cstring(permanent_allocator(), f->fullpath);
+			map_set(&m->file_id_map, cast(uintptr)id, tb_file_create(m->mod, path));
+		}
+	}
+
+	return m;
+}
+
+gb_internal void cg_module_destroy(cgModule *m) {
+	map_destroy(&m->values);
+	map_destroy(&m->symbols);
+	map_destroy(&m->file_id_map);
+	map_destroy(&m->debug_type_map);
+	map_destroy(&m->proc_debug_type_map);
+	map_destroy(&m->proc_proto_map);
+	map_destroy(&m->anonymous_proc_lits_map);
+	map_destroy(&m->equal_procs);
+	map_destroy(&m->hasher_procs);
+	map_destroy(&m->map_get_procs);
+	map_destroy(&m->map_set_procs);
+
+	array_free(&m->single_threaded_procedure_queue);
+
+	tb_module_destroy(m->mod);
+}
+
+gb_internal String cg_set_nested_type_name_ir_mangled_name(Entity *e, cgProcedure *p) {
+	// NOTE(bill, 2020-03-08): A polymorphic procedure may take a nested type declaration
+	// and as a result, the declaration does not have time to determine what it should be
+
+	GB_ASSERT(e != nullptr && e->kind == Entity_TypeName);
+	if (e->TypeName.ir_mangled_name.len != 0)  {
+		return e->TypeName.ir_mangled_name;
+	}
+	GB_ASSERT((e->scope->flags & ScopeFlag_File) == 0);
+
+	if (p == nullptr) {
+		Entity *proc = nullptr;
+		if (e->parent_proc_decl != nullptr) {
+			proc = e->parent_proc_decl->entity;
+		} else {
+			Scope *scope = e->scope;
+			while (scope != nullptr && (scope->flags & ScopeFlag_Proc) == 0) {
+				scope = scope->parent;
+			}
+			GB_ASSERT(scope != nullptr);
+			GB_ASSERT(scope->flags & ScopeFlag_Proc);
+			proc = scope->procedure_entity;
+		}
+		GB_ASSERT(proc->kind == Entity_Procedure);
+		if (proc->cg_procedure != nullptr) {
+			p = proc->cg_procedure;
+		}
+	}
+
+	// NOTE(bill): Generate a new name
+	// parent_proc.name-guid
+	String ts_name = e->token.string;
+
+	if (p != nullptr) {
+		isize name_len = p->name.len + 1 + ts_name.len + 1 + 10 + 1;
+		char *name_text = gb_alloc_array(permanent_allocator(), char, name_len);
+		u32 guid = 1+p->module->nested_type_name_guid.fetch_add(1);
+		name_len = gb_snprintf(name_text, name_len, "%.*s" ABI_PKG_NAME_SEPARATOR "%.*s-%u", LIT(p->name), LIT(ts_name), guid);
+
+		String name = make_string(cast(u8 *)name_text, name_len-1);
+		e->TypeName.ir_mangled_name = name;
+		return name;
+	} else {
+		// NOTE(bill): a nested type be required before its parameter procedure exists. Just give it a temp name for now
+		isize name_len = 9 + 1 + ts_name.len + 1 + 10 + 1;
+		char *name_text = gb_alloc_array(permanent_allocator(), char, name_len);
+		static std::atomic<u32> guid;
+		name_len = gb_snprintf(name_text, name_len, "_internal" ABI_PKG_NAME_SEPARATOR "%.*s-%u", LIT(ts_name), 1+guid.fetch_add(1));
+
+		String name = make_string(cast(u8 *)name_text, name_len-1);
+		e->TypeName.ir_mangled_name = name;
+		return name;
+	}
+}
+
+gb_internal String cg_mangle_name(cgModule *m, Entity *e) {
+	String name = e->token.string;
+
+	AstPackage *pkg = e->pkg;
+	GB_ASSERT_MSG(pkg != nullptr, "Missing package for '%.*s'", LIT(name));
+	String pkgn = pkg->name;
+	GB_ASSERT(!rune_is_digit(pkgn[0]));
+	if (pkgn == "llvm") {
+		GB_PANIC("llvm. entities are not allowed with the tilde backend");
+	}
+
+	isize max_len = pkgn.len + 1 + name.len + 1;
+	bool require_suffix_id = is_type_polymorphic(e->type, true);
+
+	if ((e->scope->flags & (ScopeFlag_File | ScopeFlag_Pkg)) == 0) {
+		require_suffix_id = true;
+	} else if (is_blank_ident(e->token)) {
+		require_suffix_id = true;
+	}if (e->flags & EntityFlag_NotExported) {
+		require_suffix_id = true;
+	}
+
+	if (require_suffix_id) {
+		max_len += 21;
+	}
+
+	char *new_name = gb_alloc_array(permanent_allocator(), char, max_len);
+	isize new_name_len = gb_snprintf(
+		new_name, max_len,
+		"%.*s" ABI_PKG_NAME_SEPARATOR "%.*s", LIT(pkgn), LIT(name)
+	);
+	if (require_suffix_id) {
+		char *str = new_name + new_name_len-1;
+		isize len = max_len-new_name_len;
+		isize extra = gb_snprintf(str, len, "-%llu", cast(unsigned long long)e->id);
+		new_name_len += extra-1;
+	}
+
+	String mangled_name = make_string((u8 const *)new_name, new_name_len-1);
+	return mangled_name;
+}
+
+gb_internal String cg_get_entity_name(cgModule *m, Entity *e) {
+	if (e != nullptr && e->kind == Entity_TypeName && e->TypeName.ir_mangled_name.len != 0) {
+		return e->TypeName.ir_mangled_name;
+	}
+	GB_ASSERT(e != nullptr);
+
+	if (e->pkg == nullptr) {
+		return e->token.string;
+	}
+
+	if (e->kind == Entity_TypeName && (e->scope->flags & ScopeFlag_File) == 0) {
+		return cg_set_nested_type_name_ir_mangled_name(e, nullptr);
+	}
+
+	String name = {};
+
+	bool no_name_mangle = false;
+
+	if (e->kind == Entity_Variable) {
+		bool is_foreign = e->Variable.is_foreign;
+		bool is_export  = e->Variable.is_export;
+		no_name_mangle = e->Variable.link_name.len > 0 || is_foreign || is_export;
+		if (e->Variable.link_name.len > 0) {
+			return e->Variable.link_name;
+		}
+	} else if (e->kind == Entity_Procedure && e->Procedure.link_name.len > 0) {
+		return e->Procedure.link_name;
+	} else if (e->kind == Entity_Procedure && e->Procedure.is_export) {
+		no_name_mangle = true;
+	}
+
+	if (!no_name_mangle) {
+		name = cg_mangle_name(m, e);
+	}
+	if (name.len == 0) {
+		name = e->token.string;
+	}
+
+	if (e->kind == Entity_TypeName) {
+		e->TypeName.ir_mangled_name = name;
+	} else if (e->kind == Entity_Procedure) {
+		e->Procedure.link_name = name;
+	}
+
+	return name;
+}
+
+#include "tilde_const.cpp"
+#include "tilde_debug.cpp"
+#include "tilde_expr.cpp"
+#include "tilde_builtin.cpp"
+#include "tilde_type_info.cpp"
+#include "tilde_proc.cpp"
+#include "tilde_stmt.cpp"
+
+
+gb_internal String cg_filepath_obj_for_module(cgModule *m) {
+	String path = concatenate3_strings(permanent_allocator(),
+		build_context.build_paths[BuildPath_Output].basename,
+		STR_LIT("/"),
+		build_context.build_paths[BuildPath_Output].name
+	);
+
+	// if (m->file) {
+	// 	char buf[32] = {};
+	// 	isize n = gb_snprintf(buf, gb_size_of(buf), "-%u", m->file->id);
+	// 	String suffix = make_string((u8 *)buf, n-1);
+	// 	path = concatenate_strings(permanent_allocator(), path, suffix);
+	// } else if (m->pkg) {
+	// 	path = concatenate3_strings(permanent_allocator(), path, STR_LIT("-"), m->pkg->name);
+	// }
+
+	String ext = {};
+
+	if (build_context.build_mode == BuildMode_Assembly) {
+		ext = STR_LIT(".S");
+	} else {
+		if (is_arch_wasm()) {
+			ext = STR_LIT(".wasm.o");
+		} else {
+			switch (build_context.metrics.os) {
+			case TargetOs_windows:
+				ext = STR_LIT(".obj");
+				break;
+			default:
+			case TargetOs_darwin:
+			case TargetOs_linux:
+			case TargetOs_essence:
+				ext = STR_LIT(".o");
+				break;
+
+			case TargetOs_freestanding:
+				switch (build_context.metrics.abi) {
+				default:
+				case TargetABI_Default:
+				case TargetABI_SysV:
+					ext = STR_LIT(".o");
+					break;
+				case TargetABI_Win64:
+					ext = STR_LIT(".obj");
+					break;
+				}
+				break;
+			}
+		}
+	}
+
+	return concatenate_strings(permanent_allocator(), path, ext);
+}
+
+
+gb_internal WORKER_TASK_PROC(cg_procedure_generate_worker_proc) {
+	cgProcedure *p = cast(cgProcedure *)data;
+	cg_procedure_generate(p);
+	return 0;
+}
+
+gb_internal void cg_add_procedure_to_queue(cgProcedure *p) {
+	if (p == nullptr) {
+		return;
+	}
+	cgModule *m = p->module;
+	if (m->do_threading) {
+		thread_pool_add_task(cg_procedure_generate_worker_proc, p);
+	} else {
+		array_add(&m->single_threaded_procedure_queue, p);
+	}
+}
+
+gb_internal bool cg_generate_code(Checker *c, LinkerData *linker_data) {
+	TIME_SECTION("Tilde Module Initializtion");
+
+	CheckerInfo *info = &c->info;
+
+	linker_data_init(linker_data, info, c->parser->init_fullpath);
+
+	cg_global_arena_init();
+
+	cgModule *m = cg_module_create(c);
+	defer (cg_module_destroy(m));
+
+	m->do_threading = false;
+
+	TIME_SECTION("Tilde Global Variables");
+
+	Array<cgGlobalVariable> global_variables = {};
+	bool already_has_entry_point = cg_global_variables_create(m, &global_variables);
+	gb_unused(already_has_entry_point);
+
+	if (true) {
+		Type *proc_type = alloc_type_proc(nullptr, nullptr, 0, nullptr, 0, false, ProcCC_Odin);
+		cgProcedure *p = cg_procedure_create_dummy(m, str_lit(CG_STARTUP_RUNTIME_PROC_NAME), proc_type);
+		p->is_startup = true;
+		cg_startup_runtime_proc = p;
+	}
+
+	if (true) {
+		Type *proc_type = alloc_type_proc(nullptr, nullptr, 0, nullptr, 0, false, ProcCC_Odin);
+		cgProcedure *p = cg_procedure_create_dummy(m, str_lit(CG_CLEANUP_RUNTIME_PROC_NAME), proc_type);
+		p->is_startup = true;
+		cg_cleanup_runtime_proc = p;
+	}
+
+	auto *min_dep_set = &info->minimum_dependency_set;
+
+	Array<cgProcedure *> procedures_to_generate = {};
+	array_init(&procedures_to_generate, heap_allocator());
+	defer (array_free(&procedures_to_generate));
+
+	for (Entity *e : info->entities) {
+		String name  = e->token.string;
+		Scope *scope = e->scope;
+
+		if ((scope->flags & ScopeFlag_File) == 0) {
+			continue;
+		}
+
+		Scope *package_scope = scope->parent;
+		GB_ASSERT(package_scope->flags & ScopeFlag_Pkg);
+
+		if (e->kind != Entity_Procedure) {
+			continue;
+		}
+
+		if (!ptr_set_exists(min_dep_set, e)) {
+			// NOTE(bill): Nothing depends upon it so doesn't need to be built
+			continue;
+		}
+		if (cgProcedure *p = cg_procedure_create(m, e)) {
+			array_add(&procedures_to_generate, p);
+		}
+	}
+	for (cgProcedure *p : procedures_to_generate) {
+		cg_add_procedure_to_queue(p);
+	}
+
+	if (!m->do_threading) {
+		for (isize i = 0; i < m->single_threaded_procedure_queue.count; i++) {
+			cgProcedure *p = m->single_threaded_procedure_queue[i];
+			cg_procedure_generate(p);
+		}
+	}
+
+	thread_pool_wait();
+
+	{
+		cgProcedure *p = cg_startup_runtime_proc;
+		cg_procedure_begin(p);
+		cg_global_variables_initialize(p, &global_variables);
+		tb_inst_ret(p->func, 0, nullptr);
+		cg_procedure_end(p);
+	}
+	{
+		cgProcedure *p = cg_cleanup_runtime_proc;
+		cg_procedure_begin(p);
+		tb_inst_ret(p->func, 0, nullptr);
+		cg_procedure_end(p);
+	}
+
+
+
+	TB_DebugFormat debug_format = TB_DEBUGFMT_NONE;
+	if (build_context.ODIN_DEBUG) {
+		switch (build_context.metrics.os) {
+		case TargetOs_windows:
+			debug_format = TB_DEBUGFMT_CODEVIEW;
+			break;
+		case TargetOs_darwin:
+		case TargetOs_linux:
+		case TargetOs_essence:
+		case TargetOs_freebsd:
+		case TargetOs_openbsd:
+			debug_format = TB_DEBUGFMT_DWARF;
+			break;
+		}
+	}
+	TB_ExportBuffer export_buffer = tb_module_object_export(m->mod, debug_format);
+	defer (tb_export_buffer_free(export_buffer));
+
+	String filepath_obj = cg_filepath_obj_for_module(m);
+	array_add(&linker_data->output_object_paths, filepath_obj);
+	GB_ASSERT(tb_export_buffer_to_file(export_buffer, cast(char const *)filepath_obj.text));
+
+	return true;
+}
+
+#undef ABI_PKG_NAME_SEPARATOR

+ 373 - 0
src/tilde.hpp

@@ -0,0 +1,373 @@
+#if defined(GB_SYSTEM_WINDOWS)
+	#pragma warning(push)
+	#pragma warning(disable: 4200)
+	#pragma warning(disable: 4201)
+	#define restrict gb_restrict
+#endif
+
+#include "tilde/tb.h"
+#include "tilde/tb_arena.h"
+
+#define TB_TYPE_F16    TB_DataType{ { TB_INT, 0, 16 } }
+#define TB_TYPE_I128   TB_DataType{ { TB_INT, 0, 128 } }
+#define TB_TYPE_INT    TB_TYPE_INTN(cast(u16)(8*build_context.int_size))
+#define TB_TYPE_INTPTR TB_TYPE_INTN(cast(u16)(8*build_context.ptr_size))
+
+#if defined(GB_SYSTEM_WINDOWS)
+	#pragma warning(pop)
+#endif
+
+#define CG_STARTUP_RUNTIME_PROC_NAME   "__$startup_runtime"
+#define CG_CLEANUP_RUNTIME_PROC_NAME   "__$cleanup_runtime"
+#define CG_STARTUP_TYPE_INFO_PROC_NAME "__$startup_type_info"
+#define CG_TYPE_INFO_DATA_NAME         "__$type_info_data"
+#define CG_TYPE_INFO_TYPES_NAME        "__$type_info_types_data"
+#define CG_TYPE_INFO_NAMES_NAME        "__$type_info_names_data"
+#define CG_TYPE_INFO_OFFSETS_NAME      "__$type_info_offsets_data"
+#define CG_TYPE_INFO_USINGS_NAME       "__$type_info_usings_data"
+#define CG_TYPE_INFO_TAGS_NAME         "__$type_info_tags_data"
+#define CG_TYPE_INFO_ENUM_VALUES_NAME  "__$type_info_enum_values_data"
+
+struct cgModule;
+
+
+enum cgValueKind : u32 {
+	cgValue_Value,  // rvalue
+	cgValue_Addr,   // lvalue
+	cgValue_Symbol, // global
+	cgValue_Multi,  // multiple values
+};
+
+struct cgValueMulti;
+
+struct cgValue {
+	cgValueKind kind;
+	Type *      type;
+	union {
+		// NOTE: any value in this union must be a pointer
+		TB_Symbol *   symbol;
+		TB_Node *     node;
+		cgValueMulti *multi;
+	};
+};
+
+struct cgValueMulti {
+	Slice<cgValue> values;
+};
+
+
+enum cgAddrKind {
+	cgAddr_Default,
+	cgAddr_Map,
+	cgAddr_Context,
+	cgAddr_SoaVariable,
+
+	cgAddr_RelativePointer,
+	cgAddr_RelativeSlice,
+
+	cgAddr_Swizzle,
+	cgAddr_SwizzleLarge,
+};
+
+struct cgAddr {
+	cgAddrKind kind;
+	cgValue addr;
+	union {
+		struct {
+			cgValue key;
+			Type *type;
+			Type *result;
+		} map;
+		struct {
+			Selection sel;
+		} ctx;
+		struct {
+			cgValue index;
+			Ast *index_expr;
+		} soa;
+		struct {
+			cgValue index;
+			Ast *node;
+		} index_set;
+		struct {
+			bool deref;
+		} relative;
+		struct {
+			Type *type;
+			u8 count;      // 2, 3, or 4 components
+			u8 indices[4];
+		} swizzle;
+		struct {
+			Type *type;
+			Slice<i32> indices;
+		} swizzle_large;
+	};
+};
+
+
+struct cgTargetList {
+	cgTargetList *prev;
+	bool          is_block;
+	// control regions
+	TB_Node *     break_;
+	TB_Node *     continue_;
+	TB_Node *     fallthrough_;
+};
+
+struct cgBranchRegions {
+	Ast *    label;
+	TB_Node *break_;
+	TB_Node *continue_;
+};
+
+enum cgDeferExitKind {
+	cgDeferExit_Default,
+	cgDeferExit_Return,
+	cgDeferExit_Branch,
+};
+
+enum cgDeferKind {
+	cgDefer_Node,
+	cgDefer_Proc,
+};
+
+struct cgDefer {
+	cgDeferKind kind;
+	isize       scope_index;
+	isize       context_stack_count;
+	TB_Node *   control_region;
+	union {
+		Ast *stmt;
+		struct {
+			cgValue deferred;
+			Slice<cgValue> result_as_args;
+		} proc;
+	};
+};
+
+
+struct cgContextData {
+	cgAddr ctx;
+	isize scope_index;
+	isize uses;
+};
+
+struct cgControlRegion {
+	TB_Node *control_region;
+	isize    scope_index;
+};
+
+struct cgProcedure {
+	u32 flags;
+	u16 state_flags;
+
+	cgProcedure *parent;
+	Array<cgProcedure *> children;
+
+	TB_Function *func;
+	TB_FunctionPrototype *proto;
+	TB_Symbol *symbol;
+
+	Entity *  entity;
+	cgModule *module;
+	String    name;
+	Type *    type;
+	Ast *     type_expr;
+	Ast *     body;
+	u64       tags;
+	ProcInlining inlining;
+	bool         is_foreign;
+	bool         is_export;
+	bool         is_entry_point;
+	bool         is_startup;
+
+	TB_DebugType *debug_type;
+
+	cgValue value;
+
+	Ast *curr_stmt;
+
+	cgTargetList *         target_list;
+	Array<cgDefer>         defer_stack;
+	Array<Scope *>         scope_stack;
+	Array<cgContextData>   context_stack;
+
+	Array<cgControlRegion> control_regions;
+	Array<cgBranchRegions> branch_regions;
+
+	Scope *curr_scope;
+	i32    scope_index;
+	bool   in_multi_assignment;
+	isize  split_returns_index;
+	bool   return_by_ptr;
+
+	PtrMap<Entity *, cgAddr> variable_map;
+};
+
+
+struct cgModule {
+	TB_Module *  mod;
+	Checker *    checker;
+	CheckerInfo *info;
+	LinkerData * linker_data;
+
+	bool do_threading;
+	Array<cgProcedure *> single_threaded_procedure_queue;
+
+	RwMutex values_mutex;
+	PtrMap<Entity *, cgValue>       values;
+	PtrMap<Entity *, TB_Symbol *>   symbols;
+	StringMap<cgValue>              members;
+	StringMap<cgProcedure *>        procedures;
+	PtrMap<TB_Function *, Entity *> procedure_values;
+
+	RecursiveMutex debug_type_mutex;
+	PtrMap<Type *, TB_DebugType *> debug_type_map;
+	PtrMap<Type *, TB_DebugType *> proc_debug_type_map; // not pointer to
+
+	RecursiveMutex proc_proto_mutex;
+	PtrMap<Type *, TB_FunctionPrototype *> proc_proto_map;
+
+	BlockingMutex anonymous_proc_lits_mutex;
+	PtrMap<Ast *, cgProcedure *> anonymous_proc_lits_map;
+
+	RecursiveMutex generated_procs_mutex;
+	PtrMap<Type *, cgProcedure *> equal_procs;
+	PtrMap<Type *, cgProcedure *> hasher_procs;
+	PtrMap<Type *, cgProcedure *> map_get_procs;
+	PtrMap<Type *, cgProcedure *> map_set_procs;
+
+
+	// NOTE(bill): no need to protect this with a mutex
+	PtrMap<uintptr, TB_FileID> file_id_map; // Key: AstFile.id (i32 cast to uintptr)
+
+	std::atomic<u32> nested_type_name_guid;
+	std::atomic<u32> const_nil_guid;
+};
+
+#ifndef ABI_PKG_NAME_SEPARATOR
+#define ABI_PKG_NAME_SEPARATOR "@"
+#endif
+
+struct GlobalTypeInfoData {
+	TB_Global *global;
+	Type *     array_type;
+	Type *     elem_type;
+	isize      index;
+};
+
+gb_global Entity *cg_global_type_info_data_entity   = {};
+gb_global GlobalTypeInfoData cg_global_type_info_member_types       = {};
+gb_global GlobalTypeInfoData cg_global_type_info_member_names       = {};
+gb_global GlobalTypeInfoData cg_global_type_info_member_offsets     = {};
+gb_global GlobalTypeInfoData cg_global_type_info_member_usings      = {};
+gb_global GlobalTypeInfoData cg_global_type_info_member_tags        = {};
+gb_global GlobalTypeInfoData cg_global_type_info_member_enum_values = {};
+
+gb_global cgProcedure *cg_startup_runtime_proc = nullptr;
+gb_global cgProcedure *cg_cleanup_runtime_proc = nullptr;
+
+
+
+gb_internal TB_Arena *cg_arena(void);
+
+gb_internal cgProcedure *cg_procedure_create(cgModule *m, Entity *entity, bool ignore_body=false);
+gb_internal void cg_add_procedure_to_queue(cgProcedure *p);
+gb_internal void cg_setup_type_info_data(cgModule *m);
+gb_internal cgProcedure *cg_procedure_generate_anonymous(cgModule *m, Ast *expr, cgProcedure *parent);
+
+gb_internal isize cg_global_const_calculate_region_count(ExactValue const &value, Type *type);
+gb_internal i64   cg_global_const_calculate_region_count_from_basic_type(Type *type);
+gb_internal bool  cg_global_const_add_region(cgModule *m, ExactValue const &value, Type *type, TB_Global *global, i64 offset);
+
+gb_internal String cg_get_entity_name(cgModule *m, Entity *e);
+
+gb_internal cgValue cg_value(TB_Global *  g,    Type *type);
+gb_internal cgValue cg_value(TB_External *e,    Type *type);
+gb_internal cgValue cg_value(TB_Function *f,    Type *type);
+gb_internal cgValue cg_value(TB_Symbol *  s,    Type *type);
+gb_internal cgValue cg_value(TB_Node *    node, Type *type);
+
+gb_internal cgAddr cg_addr(cgValue const &value);
+
+gb_internal u64 cg_typeid_as_u64(cgModule *m, Type *type);
+gb_internal cgValue cg_type_info(cgProcedure *p, Type *type);
+gb_internal isize cg_type_info_index(CheckerInfo *info, Type *type, bool err_on_not_found=true);
+
+gb_internal cgValue cg_const_value(cgProcedure *p, Type *type, ExactValue const &value);
+gb_internal cgValue cg_const_nil(cgProcedure *p, Type *type);
+
+gb_internal cgValue cg_flatten_value(cgProcedure *p, cgValue value);
+
+gb_internal void cg_build_stmt(cgProcedure *p, Ast *stmt);
+gb_internal void cg_build_stmt_list(cgProcedure *p, Slice<Ast *> const &stmts);
+gb_internal void cg_build_when_stmt(cgProcedure *p, AstWhenStmt *ws);
+
+
+gb_internal cgValue cg_build_expr(cgProcedure *p, Ast *expr);
+gb_internal cgAddr  cg_build_addr(cgProcedure *p, Ast *expr);
+gb_internal cgValue cg_build_addr_ptr(cgProcedure *p, Ast *expr);
+gb_internal cgValue cg_build_cond(cgProcedure *p, Ast *cond, TB_Node *true_block, TB_Node *false_block);
+
+gb_internal Type *  cg_addr_type(cgAddr const &addr);
+gb_internal cgValue cg_addr_load(cgProcedure *p, cgAddr addr);
+gb_internal void    cg_addr_store(cgProcedure *p, cgAddr addr, cgValue value);
+gb_internal cgValue cg_addr_get_ptr(cgProcedure *p, cgAddr const &addr);
+
+gb_internal cgValue cg_emit_load(cgProcedure *p, cgValue const &ptr, bool is_volatile=false);
+gb_internal void    cg_emit_store(cgProcedure *p, cgValue dst, cgValue src, bool is_volatile=false);
+
+gb_internal cgAddr  cg_add_local (cgProcedure *p, Type *type, Entity *e, bool zero_init);
+gb_internal cgAddr  cg_add_global(cgProcedure *p, Type *type, Entity *e);
+gb_internal cgValue cg_address_from_load_or_generate_local(cgProcedure *p, cgValue value);
+gb_internal cgValue cg_copy_value_to_ptr(cgProcedure *p, cgValue value, Type *original_type, isize min_alignment);
+
+gb_internal cgValue cg_build_call_expr(cgProcedure *p, Ast *expr);
+
+gb_internal void cg_build_return_stmt(cgProcedure *p, Slice<Ast *> const &return_results);
+gb_internal void cg_build_return_stmt_internal(cgProcedure *p, Slice<cgValue> const &results);
+gb_internal void cg_build_return_stmt_internal_single(cgProcedure *p, cgValue result);
+gb_internal void cg_build_range_stmt(cgProcedure *p, Ast *node);
+
+gb_internal cgValue cg_find_value_from_entity(cgModule *m, Entity *e);
+gb_internal cgValue cg_find_procedure_value_from_entity(cgModule *m, Entity *e);
+
+gb_internal TB_DebugType *cg_debug_type(cgModule *m, Type *type);
+
+gb_internal String cg_get_entity_name(cgModule *m, Entity *e);
+
+gb_internal cgValue cg_typeid(cgProcedure *m, Type *t);
+
+gb_internal cgValue cg_emit_ptr_offset(cgProcedure *p, cgValue ptr, cgValue index);
+gb_internal cgValue cg_emit_array_ep(cgProcedure *p, cgValue s, cgValue index);
+gb_internal cgValue cg_emit_array_epi(cgProcedure *p, cgValue s, i64 index);
+gb_internal cgValue cg_emit_struct_ep(cgProcedure *p, cgValue s, i64 index);
+gb_internal cgValue cg_emit_deep_field_gep(cgProcedure *p, cgValue e, Selection const &sel);
+gb_internal cgValue cg_emit_struct_ev(cgProcedure *p, cgValue s, i64 index);
+
+gb_internal cgValue cg_emit_conv(cgProcedure *p, cgValue value, Type *t);
+gb_internal cgValue cg_emit_comp_against_nil(cgProcedure *p, TokenKind op_kind, cgValue x);
+gb_internal cgValue cg_emit_comp(cgProcedure *p, TokenKind op_kind, cgValue left, cgValue right);
+gb_internal cgValue cg_emit_arith(cgProcedure *p, TokenKind op, cgValue lhs, cgValue rhs, Type *type);
+gb_internal cgValue cg_emit_unary_arith(cgProcedure *p, TokenKind op, cgValue x, Type *type);
+gb_internal void    cg_emit_increment(cgProcedure *p, cgValue addr);
+
+gb_internal cgProcedure *cg_equal_proc_for_type (cgModule *m, Type *type);
+gb_internal cgProcedure *cg_hasher_proc_for_type(cgModule *m, Type *type);
+gb_internal cgValue     cg_hasher_proc_value_for_type(cgProcedure *p, Type *type);
+gb_internal cgValue     cg_equal_proc_value_for_type(cgProcedure *p, Type *type);
+
+gb_internal cgValue cg_emit_call(cgProcedure * p, cgValue value, Slice<cgValue> const &args);
+gb_internal cgValue cg_emit_runtime_call(cgProcedure *p, char const *name, Slice<cgValue> const &args);
+
+gb_internal bool    cg_emit_goto(cgProcedure *p, TB_Node *control_region);
+
+gb_internal TB_Node *cg_control_region(cgProcedure *p, char const *name);
+
+gb_internal isize cg_append_tuple_values(cgProcedure *p, Array<cgValue> *dst_values, cgValue src_value);
+
+gb_internal cgValue cg_handle_param_value(cgProcedure *p, Type *parameter_type, ParameterValue const &param_value, TokenPos const &pos);
+
+gb_internal cgValue cg_builtin_len(cgProcedure *p, cgValue value);
+gb_internal cgValue cg_builtin_raw_data(cgProcedure *p, cgValue const &x);
+

+ 1101 - 0
src/tilde/tb.h

@@ -0,0 +1,1101 @@
+#ifndef TB_CORE_H
+#define TB_CORE_H
+
+#include <assert.h>
+#include <inttypes.h>
+#include <stdbool.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stddef.h>
+#include <stdlib.h>
+#include <string.h>
+
+// https://semver.org/
+#define TB_VERSION_MAJOR 0
+#define TB_VERSION_MINOR 2
+#define TB_VERSION_PATCH 0
+
+#ifndef TB_API
+#  ifdef __cplusplus
+#    define TB_EXTERN extern "C"
+#  else
+#    define TB_EXTERN
+#  endif
+#  ifdef TB_DLL
+#    ifdef TB_IMPORT_DLL
+#      define TB_API TB_EXTERN __declspec(dllimport)
+#    else
+#      define TB_API TB_EXTERN __declspec(dllexport)
+#    endif
+#  else
+#    define TB_API TB_EXTERN
+#  endif
+#endif
+
+// These are flags
+typedef enum TB_ArithmeticBehavior {
+    TB_ARITHMATIC_NONE = 0,
+    TB_ARITHMATIC_NSW  = 1,
+    TB_ARITHMATIC_NUW  = 2,
+} TB_ArithmeticBehavior;
+
+typedef enum TB_DebugFormat {
+    TB_DEBUGFMT_NONE,
+
+    TB_DEBUGFMT_DWARF,
+    TB_DEBUGFMT_CODEVIEW,
+
+    TB_DEBUGFMT_COLINPILLED
+} TB_DebugFormat;
+
+typedef enum TB_Arch {
+    TB_ARCH_UNKNOWN,
+
+    TB_ARCH_X86_64,
+    TB_ARCH_AARCH64, // unsupported but planned
+    TB_ARCH_WASM32,
+} TB_Arch;
+
+typedef enum TB_System {
+    TB_SYSTEM_WINDOWS,
+    TB_SYSTEM_LINUX,
+    TB_SYSTEM_MACOS,
+    TB_SYSTEM_ANDROID, // Not supported yet
+    TB_SYSTEM_WEB,
+
+    TB_SYSTEM_MAX,
+} TB_System;
+
+typedef enum TB_WindowsSubsystem {
+    TB_WIN_SUBSYSTEM_UNKNOWN,
+
+    TB_WIN_SUBSYSTEM_WINDOWS,
+    TB_WIN_SUBSYSTEM_CONSOLE,
+    TB_WIN_SUBSYSTEM_EFI_APP,
+} TB_WindowsSubsystem;
+
+typedef enum TB_ABI {
+    // Used on 64bit Windows platforms
+    TB_ABI_WIN64,
+
+    // Used on Mac, BSD and Linux platforms
+    TB_ABI_SYSTEMV,
+} TB_ABI;
+
+typedef enum TB_OutputFlavor {
+    TB_FLAVOR_OBJECT,     // .o  .obj
+    TB_FLAVOR_SHARED,     // .so .dll
+    TB_FLAVOR_STATIC,     // .a  .lib
+    TB_FLAVOR_EXECUTABLE, //     .exe
+} TB_OutputFlavor;
+
+typedef enum TB_CallingConv {
+    TB_CDECL,
+    TB_STDCALL
+} TB_CallingConv;
+
+typedef enum TB_FeatureSet_X64 {
+    TB_FEATURE_X64_SSE3   = (1u << 0u),
+    TB_FEATURE_X64_SSE41  = (1u << 1u),
+    TB_FEATURE_X64_SSE42  = (1u << 2u),
+
+    TB_FEATURE_X64_POPCNT = (1u << 3u),
+    TB_FEATURE_X64_LZCNT  = (1u << 4u),
+
+    TB_FEATURE_X64_CLMUL  = (1u << 5u),
+    TB_FEATURE_X64_F16C   = (1u << 6u),
+
+    TB_FEATURE_X64_BMI1   = (1u << 7u),
+    TB_FEATURE_X64_BMI2   = (1u << 8u),
+
+    TB_FEATURE_X64_AVX    = (1u << 9u),
+    TB_FEATURE_X64_AVX2   = (1u << 10u),
+} TB_FeatureSet_X64;
+
+typedef struct TB_FeatureSet {
+    TB_FeatureSet_X64 x64;
+} TB_FeatureSet;
+
+typedef enum TB_BranchHint {
+    TB_BRANCH_HINT_NONE,
+    TB_BRANCH_HINT_LIKELY,
+    TB_BRANCH_HINT_UNLIKELY
+} TB_BranchHint;
+
+typedef enum TB_Linkage {
+    TB_LINKAGE_PUBLIC,
+    TB_LINKAGE_PRIVATE
+} TB_Linkage;
+
+typedef enum {
+    TB_COMDAT_NONE,
+
+    TB_COMDAT_MATCH_ANY,
+} TB_ComdatType;
+
+typedef enum TB_MemoryOrder {
+    TB_MEM_ORDER_RELAXED,
+    TB_MEM_ORDER_CONSUME,
+    TB_MEM_ORDER_ACQUIRE,
+    TB_MEM_ORDER_RELEASE,
+    TB_MEM_ORDER_ACQ_REL,
+    TB_MEM_ORDER_SEQ_CST,
+} TB_MemoryOrder;
+
+typedef enum TB_ISelMode {
+    // FastISel
+    TB_ISEL_FAST,
+    TB_ISEL_COMPLEX
+} TB_ISelMode;
+
+typedef enum TB_DataTypeEnum {
+    // Integers, note void is an i0 and bool is an i1
+    //   i(0-2047)
+    TB_INT,
+    // Floating point numbers
+    //   f{32,64}
+    TB_FLOAT,
+    // Pointers
+    //   ptr(0-2047)
+    TB_PTR,
+    // Tuples, these cannot be used in memory ops, just accessed via projections
+    TB_TUPLE,
+    // represents control flow as a kind of data
+    TB_CONTROL,
+} TB_DataTypeEnum;
+
+typedef enum TB_FloatFormat {
+    // IEEE 754 floats
+    TB_FLT_32, TB_FLT_64
+} TB_FloatFormat;
+
+typedef union TB_DataType {
+    struct {
+        uint8_t type;
+        // Only integers and floats can be wide.
+        uint8_t width;
+        // for integers it's the bitwidth
+        uint16_t data;
+    };
+    uint32_t raw;
+} TB_DataType;
+
+// classify data types
+#define TB_IS_VOID_TYPE(x)     ((x).type == TB_INT && (x).data == 0)
+#define TB_IS_BOOL_TYPE(x)     ((x).type == TB_INT && (x).data == 1)
+#define TB_IS_INTEGER_TYPE(x)  ((x).type == TB_INT)
+#define TB_IS_FLOAT_TYPE(x)    ((x).type == TB_FLOAT)
+#define TB_IS_POINTER_TYPE(x)  ((x).type == TB_PTR)
+
+// accessors
+#define TB_GET_INT_BITWIDTH(x) ((x).data)
+#define TB_GET_FLOAT_FORMAT(x) ((x).data)
+#define TB_GET_PTR_ADDRSPACE(x) ((x).data)
+
+typedef enum TB_NodeTypeEnum {
+    TB_NULL = 0,
+
+    // Immediates
+    TB_INTEGER_CONST,
+    TB_FLOAT32_CONST,
+    TB_FLOAT64_CONST,
+
+    // only one per function
+    TB_START, // fn()
+
+    // regions represent the begining of BBs
+    TB_REGION, // fn(preds: []region)
+
+    // projection
+    TB_PROJ,
+
+    TB_CALL,  // normal call
+    TB_SCALL, // system call
+
+    // Managed ops
+    TB_SAFEPOINT,
+
+    // Memory operations
+    TB_STORE, // fn(r: control, addr: data, src: data)
+    TB_MEMCPY,
+    TB_MEMSET,
+
+    // Atomics
+    TB_ATOMIC_TEST_AND_SET,
+    TB_ATOMIC_CLEAR,
+
+    TB_ATOMIC_LOAD,
+    TB_ATOMIC_XCHG,
+    TB_ATOMIC_ADD,
+    TB_ATOMIC_SUB,
+    TB_ATOMIC_AND,
+    TB_ATOMIC_XOR,
+    TB_ATOMIC_OR,
+
+    TB_ATOMIC_CMPXCHG,
+    TB_DEBUGBREAK,
+
+    // Terminators
+    TB_BRANCH,
+    TB_RET,
+    TB_UNREACHABLE,
+    TB_TRAP,
+
+    TB_POISON,
+
+    // Load
+    TB_LOAD,
+
+    // Pointers
+    TB_LOCAL,
+
+    TB_GET_SYMBOL_ADDRESS,
+
+    TB_MEMBER_ACCESS,
+    TB_ARRAY_ACCESS,
+
+    // Conversions
+    TB_TRUNCATE,
+    TB_FLOAT_EXT,
+    TB_SIGN_EXT,
+    TB_ZERO_EXT,
+    TB_INT2PTR,
+    TB_PTR2INT,
+    TB_UINT2FLOAT,
+    TB_FLOAT2UINT,
+    TB_INT2FLOAT,
+    TB_FLOAT2INT,
+    TB_BITCAST,
+
+    // Select
+    TB_SELECT,
+
+    // Bitmagic
+    TB_BSWAP,
+    TB_CLZ,
+    TB_CTZ,
+    TB_POPCNT,
+
+    // Unary operations
+    TB_NOT,
+    TB_NEG,
+
+    // Integer arithmatic
+    TB_AND,
+    TB_OR,
+    TB_XOR,
+    TB_ADD,
+    TB_SUB,
+    TB_MUL,
+
+    TB_SHL,
+    TB_SHR,
+    TB_SAR,
+    TB_ROL,
+    TB_ROR,
+    TB_UDIV,
+    TB_SDIV,
+    TB_UMOD,
+    TB_SMOD,
+
+    // Float arithmatic
+    TB_FADD,
+    TB_FSUB,
+    TB_FMUL,
+    TB_FDIV,
+
+    // Comparisons
+    TB_CMP_EQ,
+    TB_CMP_NE,
+    TB_CMP_ULT,
+    TB_CMP_ULE,
+    TB_CMP_SLT,
+    TB_CMP_SLE,
+    TB_CMP_FLT,
+    TB_CMP_FLE,
+
+    // Special ops
+    // does full multiplication (64x64=128 and so on) returning
+    // the low and high values in separate projections
+    TB_MULPAIR,
+
+    // PHI
+    TB_PHI, // fn(r: region, x: []data)
+
+    // variadic
+    TB_VA_START,
+
+    // x86 intrinsics
+    TB_X86INTRIN_RDTSC,
+    TB_X86INTRIN_LDMXCSR,
+    TB_X86INTRIN_STMXCSR,
+    TB_X86INTRIN_SQRT,
+    TB_X86INTRIN_RSQRT,
+} TB_NodeTypeEnum;
+typedef uint8_t TB_NodeType;
+
+typedef int TB_Label;
+
+// just represents some region of bytes, usually in file parsing crap
+typedef struct {
+    size_t length;
+    const uint8_t* data;
+} TB_Slice;
+
+// represents byte counts
+typedef uint32_t TB_CharUnits;
+
+typedef unsigned int TB_FileID;
+
+// SO refers to shared objects which mean either shared libraries (.so or .dll)
+// or executables (.exe or ELF executables)
+typedef enum {
+    // exports to the rest of the shared object
+    TB_EXTERNAL_SO_LOCAL,
+
+    // exports outside of the shared object
+    TB_EXTERNAL_SO_EXPORT,
+} TB_ExternalType;
+
+typedef struct TB_Global            TB_Global;
+typedef struct TB_External          TB_External;
+typedef struct TB_Function          TB_Function;
+
+typedef struct TB_Module            TB_Module;
+typedef struct TB_Attrib            TB_Attrib;
+typedef struct TB_DebugType         TB_DebugType;
+typedef struct TB_ModuleSection     TB_ModuleSection;
+typedef struct TB_FunctionPrototype TB_FunctionPrototype;
+
+// Refers generically to objects within a module
+//
+// TB_Function, TB_Global, and TB_External are all subtypes of TB_Symbol
+// and thus are safely allowed to cast into a symbol for operations.
+typedef struct TB_Symbol {
+    enum TB_SymbolTag {
+        TB_SYMBOL_NONE,
+
+        // symbol is dead now
+        TB_SYMBOL_TOMBSTONE,
+
+        TB_SYMBOL_EXTERNAL,
+        TB_SYMBOL_GLOBAL,
+        TB_SYMBOL_FUNCTION,
+
+        TB_SYMBOL_MAX,
+    } tag;
+
+    // refers to the prev or next symbol with the same tag
+    struct TB_Symbol* next;
+    char* name;
+
+    // It's kinda a weird circular reference but yea
+    TB_Module* module;
+
+    // helpful for sorting and getting consistent builds
+    uint64_t ordinal;
+
+    union {
+        // if we're JITing then this maps to the address of the symbol
+        void* address;
+        size_t symbol_id;
+    };
+
+    // after this point it's tag-specific storage
+} TB_Symbol;
+
+typedef int TB_Reg;
+
+typedef struct TB_Node TB_Node;
+struct TB_Node {
+    TB_NodeType type;
+    TB_DataType dt;
+    uint16_t input_count; // number of node inputs
+    uint16_t extra_count; // number of bytes for extra operand data
+
+    TB_Attrib* first_attrib;
+    TB_Node** inputs;
+
+    char extra[];
+};
+
+#define TB_KILL_NODE(n) ((n)->type = TB_NULL)
+
+// These are the extra data in specific nodes
+#define TB_NODE_GET_EXTRA(n)         ((void*) n->extra)
+#define TB_NODE_GET_EXTRA_T(n, T)    ((T*) (n)->extra)
+#define TB_NODE_SET_EXTRA(n, T, ...) (*((T*) (n)->extra) = (T){ __VA_ARGS__ })
+
+// this represents switch (many targets), if (one target) and goto (only default) logic.
+typedef struct { // TB_BRANCH
+    // avoid empty structs with flexible members
+    int64_t _;
+    int64_t keys[];
+} TB_NodeBranch;
+
+typedef struct { // TB_PROJ
+    int index;
+} TB_NodeProj;
+
+typedef struct { // TB_INT
+    uint64_t num_words;
+    uint64_t words[];
+} TB_NodeInt;
+
+typedef struct { // any compare operator
+    TB_DataType cmp_dt;
+} TB_NodeCompare;
+
+typedef struct { // any integer binary operator
+    TB_ArithmeticBehavior ab;
+} TB_NodeBinopInt;
+
+typedef struct { // TB_MULPAIR
+    TB_Node *lo, *hi;
+} TB_NodeMulPair;
+
+typedef struct {
+    TB_CharUnits align;
+    bool is_volatile;
+} TB_NodeMemAccess;
+
+typedef struct {
+    TB_CharUnits size, align;
+} TB_NodeLocal;
+
+typedef struct {
+    TB_FileID file;
+    int line;
+} TB_NodeLine;
+
+typedef struct {
+    float value;
+} TB_NodeFloat32;
+
+typedef struct {
+    double value;
+} TB_NodeFloat64;
+
+typedef struct {
+    int64_t stride;
+} TB_NodeArray;
+
+typedef struct {
+    int64_t offset;
+} TB_NodeMember;
+
+typedef struct {
+    TB_Symbol* sym;
+} TB_NodeSymbol;
+
+typedef struct {
+    TB_MemoryOrder order;
+    TB_MemoryOrder order2;
+} TB_NodeAtomic;
+
+typedef struct {
+    TB_FunctionPrototype* proto;
+    TB_Node* projs[];
+} TB_NodeCall;
+
+typedef struct {
+    uint32_t id;
+} TB_NodeSafepoint;
+
+typedef struct {
+    TB_Node* end;
+    const char* tag;
+
+    size_t succ_count;
+    TB_Node** succ;
+
+    size_t proj_count;
+    TB_Node** projs;
+} TB_NodeRegion;
+
+typedef struct TB_MultiOutput {
+    size_t count;
+    union {
+        // count = 1
+        TB_Node* single;
+        // count > 1
+        TB_Node** multiple;
+    };
+} TB_MultiOutput;
+#define TB_MULTI_OUTPUT(o) ((o).count > 1 ? (o).multiple : &(o).single)
+
+typedef struct {
+    int64_t key;
+    TB_Node* value;
+} TB_SwitchEntry;
+
+typedef enum {
+    TB_EXECUTABLE_UNKNOWN,
+    TB_EXECUTABLE_PE,
+    TB_EXECUTABLE_ELF,
+} TB_ExecutableType;
+
+typedef struct {
+    TB_Node* node; // type == TB_SAFEPOINT
+    void* userdata;
+
+    uint32_t ip;    // relative to the function body.
+    uint32_t count; // same as node->input_count
+    int32_t values[];
+} TB_Safepoint;
+
+// *******************************
+// Public macros
+// *******************************
+#ifdef __cplusplus
+
+#define TB_TYPE_TUPLE   TB_DataType{ { TB_TUPLE } }
+#define TB_TYPE_CONTROL TB_DataType{ { TB_CONTROL } }
+#define TB_TYPE_VOID    TB_DataType{ { TB_INT,   0, 0 } }
+#define TB_TYPE_I8      TB_DataType{ { TB_INT,   0, 8 } }
+#define TB_TYPE_I16     TB_DataType{ { TB_INT,   0, 16 } }
+#define TB_TYPE_I32     TB_DataType{ { TB_INT,   0, 32 } }
+#define TB_TYPE_I64     TB_DataType{ { TB_INT,   0, 64 } }
+#define TB_TYPE_F32     TB_DataType{ { TB_FLOAT, 0, TB_FLT_32 } }
+#define TB_TYPE_F64     TB_DataType{ { TB_FLOAT, 0, TB_FLT_64 } }
+#define TB_TYPE_BOOL    TB_DataType{ { TB_INT,   0, 1 } }
+#define TB_TYPE_PTR     TB_DataType{ { TB_PTR,   0, 0 } }
+
+#define TB_TYPE_INTN(N) TB_DataType{ { TB_INT,   0, (N) } }
+#define TB_TYPE_PTRN(N) TB_DataType{ { TB_PTR,   0, (N) } }
+
+#else
+
+#define TB_TYPE_TUPLE   (TB_DataType){ { TB_TUPLE } }
+#define TB_TYPE_CONTROL (TB_DataType){ { TB_CONTROL } }
+#define TB_TYPE_VOID    (TB_DataType){ { TB_INT,   0, 0 } }
+#define TB_TYPE_I8      (TB_DataType){ { TB_INT,   0, 8 } }
+#define TB_TYPE_I16     (TB_DataType){ { TB_INT,   0, 16 } }
+#define TB_TYPE_I32     (TB_DataType){ { TB_INT,   0, 32 } }
+#define TB_TYPE_I64     (TB_DataType){ { TB_INT,   0, 64 } }
+#define TB_TYPE_F32     (TB_DataType){ { TB_FLOAT, 0, TB_FLT_32 } }
+#define TB_TYPE_F64     (TB_DataType){ { TB_FLOAT, 0, TB_FLT_64 } }
+#define TB_TYPE_BOOL    (TB_DataType){ { TB_INT,   0, 1 } }
+#define TB_TYPE_PTR     (TB_DataType){ { TB_PTR,   0, 0 } }
+#define TB_TYPE_INTN(N) (TB_DataType){ { TB_INT,  0, (N) } }
+#define TB_TYPE_PTRN(N) (TB_DataType){ { TB_PTR,  0, (N) } }
+
+#endif
+
+typedef void (*TB_PrintCallback)(void* user_data, const char* fmt, ...);
+
+// defined in common/arena.h
+typedef struct TB_Arena TB_Arena;
+
+// 0 for default
+TB_API void tb_arena_create(TB_Arena* restrict arena, size_t chunk_size);
+TB_API void tb_arena_destroy(TB_Arena* restrict arena);
+TB_API bool tb_arena_is_empty(TB_Arena* arena);
+
+////////////////////////////////
+// Module management
+////////////////////////////////
+// Creates a module with the correct target and settings
+TB_API TB_Module* tb_module_create(TB_Arch arch, TB_System sys, const TB_FeatureSet* features, bool is_jit);
+
+// Creates a module but defaults on the architecture and system based on the host machine
+TB_API TB_Module* tb_module_create_for_host(const TB_FeatureSet* features, bool is_jit);
+
+TB_API size_t tb_module_get_function_count(TB_Module* m);
+
+// Frees all resources for the TB_Module and it's functions, globals and
+// compiled code.
+TB_API void tb_module_destroy(TB_Module* m);
+
+// When targetting windows & thread local storage, you'll need to bind a tls index
+// which is usually just a global that the runtime support has initialized, if you
+// dont and the tls_index is used, it'll crash
+TB_API void tb_module_set_tls_index(TB_Module* m, ptrdiff_t len, const char* name);
+
+// You don't need to manually call this unless you want to resolve locations before
+// exporting.
+TB_API void tb_module_layout_sections(TB_Module* m);
+
+////////////////////////////////
+// Compiled code introspection
+////////////////////////////////
+enum { TB_ASSEMBLY_CHUNK_CAP = 4*1024 - sizeof(size_t[2]) };
+
+typedef struct TB_Assembly TB_Assembly;
+struct TB_Assembly {
+    TB_Assembly* next;
+
+    // nice chunk of text here
+    size_t length;
+    char data[];
+};
+
+// this is where the machine code and other relevant pieces go.
+typedef struct TB_FunctionOutput TB_FunctionOutput;
+
+TB_API void tb_output_print_asm(TB_FunctionOutput* out, FILE* fp);
+
+TB_API uint8_t* tb_output_get_code(TB_FunctionOutput* out, size_t* out_length);
+
+// returns NULL if no assembly was generated
+TB_API TB_Assembly* tb_output_get_asm(TB_FunctionOutput* out);
+
+// this is relative to the start of the function (the start of the prologue)
+TB_API TB_Safepoint* tb_safepoint_get(TB_Function* f, uint32_t relative_ip);
+
+////////////////////////////////
+// Exporter
+////////////////////////////////
+// Export buffers are generated in chunks because it's easier, usually the
+// chunks are "massive" (representing some connected piece of the buffer)
+// but they don't have to be.
+typedef struct TB_ExportChunk TB_ExportChunk;
+struct TB_ExportChunk {
+    TB_ExportChunk* next;
+    size_t pos, size;
+    uint8_t data[];
+};
+
+typedef struct {
+    size_t total;
+    TB_ExportChunk *head, *tail;
+} TB_ExportBuffer;
+
+TB_API TB_ExportBuffer tb_module_object_export(TB_Module* m, TB_DebugFormat debug_fmt);
+TB_API bool tb_export_buffer_to_file(TB_ExportBuffer buffer, const char* path);
+TB_API void tb_export_buffer_free(TB_ExportBuffer buffer);
+
+////////////////////////////////
+// Linker exporter
+////////////////////////////////
+// This is used to export shared objects or executables
+typedef struct TB_Linker TB_Linker;
+typedef struct TB_LinkerSection TB_LinkerSection;
+typedef struct TB_LinkerSectionPiece TB_LinkerSectionPiece;
+
+typedef struct {
+    enum {
+        TB_LINKER_MSG_NULL,
+
+        // pragma comment(lib, "blah")
+        TB_LINKER_MSG_IMPORT,
+    } tag;
+    union {
+        // pragma lib request
+        TB_Slice import_path;
+    };
+} TB_LinkerMsg;
+
+TB_API TB_ExecutableType tb_system_executable_format(TB_System s);
+
+TB_API TB_Linker* tb_linker_create(TB_ExecutableType type, TB_Arch arch);
+TB_API TB_ExportBuffer tb_linker_export(TB_Linker* l);
+TB_API void tb_linker_destroy(TB_Linker* l);
+
+TB_API bool tb_linker_get_msg(TB_Linker* l, TB_LinkerMsg* msg);
+
+// windows only
+TB_API void tb_linker_set_subsystem(TB_Linker* l, TB_WindowsSubsystem subsystem);
+
+TB_API void tb_linker_set_entrypoint(TB_Linker* l, const char* name);
+
+// Links compiled module into output
+TB_API void tb_linker_append_module(TB_Linker* l, TB_Module* m);
+
+// Adds object file to output
+TB_API void tb_linker_append_object(TB_Linker* l, TB_Slice obj_name, TB_Slice content);
+
+// Adds static library to output
+//   this can include imports (wrappers for DLL symbols) along with
+//   normal sections.
+TB_API void tb_linker_append_library(TB_Linker* l, TB_Slice ar_name, TB_Slice content);
+
+////////////////////////////////
+// JIT compilation
+////////////////////////////////
+typedef struct TB_JITContext TB_JITContext;
+
+// passing 0 to jit_heap_capacity will default to 4MiB
+TB_API TB_JITContext* tb_module_begin_jit(TB_Module* m, size_t jit_heap_capacity);
+TB_API void* tb_module_apply_function(TB_JITContext* jit, TB_Function* f);
+TB_API void* tb_module_apply_global(TB_JITContext* jit, TB_Global* g);
+// fixes page permissions, applies missing relocations
+TB_API void tb_module_ready_jit(TB_JITContext* jit);
+TB_API void tb_module_end_jit(TB_JITContext* jit);
+
+#define TB_FOR_FUNCTIONS(it, module) for (TB_Function* it = tb_first_function(module); it != NULL; it = tb_next_function(it))
+TB_API TB_Function* tb_first_function(TB_Module* m);
+TB_API TB_Function* tb_next_function(TB_Function* f);
+
+#define TB_FOR_EXTERNALS(it, module) for (TB_External* it = tb_first_external(module); it != NULL; it = tb_next_external(it))
+TB_API TB_External* tb_first_external(TB_Module* m);
+TB_API TB_External* tb_next_external(TB_External* e);
+
+// this is used JIT scenarios to tell the compiler what externals map to
+TB_API TB_ExternalType tb_extern_get_type(TB_External* e);
+TB_Global* tb_extern_transmute(TB_External* e, TB_DebugType* dbg_type, TB_Linkage linkage);
+
+TB_API TB_External* tb_extern_create(TB_Module* m, ptrdiff_t len, const char* name, TB_ExternalType type);
+TB_API TB_FileID tb_file_create(TB_Module* m, const char* path);
+
+// Called once you're done with TB operations on a thread (or i guess when it's
+// about to be killed :p), not calling it can only result in leaks on that thread
+// and calling it too early will result in TB potentially reallocating it but there's
+// should be no crashes from this, just potential slowdown or higher than expected memory
+// usage.
+TB_API void tb_free_thread_resources(void);
+
+////////////////////////////////
+// Function Prototypes
+////////////////////////////////
+typedef struct TB_PrototypeParam {
+    TB_DataType dt;
+    TB_DebugType* debug_type;
+
+    // does not apply for returns
+    const char* name;
+} TB_PrototypeParam;
+
+struct TB_FunctionPrototype {
+    // header
+    TB_CallingConv call_conv;
+    uint16_t return_count, param_count;
+    bool has_varargs;
+
+    // params are directly followed by returns
+    TB_PrototypeParam params[];
+};
+#define TB_PROTOTYPE_RETURNS(p) ((p)->params + (p)->param_count)
+
+// creates a function prototype used to define a function's parameters and returns.
+//
+// function prototypes do not get freed individually and last for the entire run
+// of the backend, they can also be reused for multiple functions which have
+// matching signatures.
+TB_API TB_FunctionPrototype* tb_prototype_create(TB_Module* m, TB_CallingConv cc, size_t param_count, const TB_PrototypeParam* params, size_t return_count, const TB_PrototypeParam* returns, bool has_varargs);
+
+// same as tb_function_set_prototype except it will handle lowering from types like the TB_DebugType
+// into the correct ABI and exposing sane looking nodes to the parameters.
+//
+// returns the parameters
+TB_API TB_Node** tb_function_set_prototype_from_dbg(TB_Function* f, TB_DebugType* dbg, TB_Arena* arena, size_t* out_param_count);
+TB_API TB_FunctionPrototype* tb_prototype_from_dbg(TB_Module* m, TB_DebugType* dbg);
+
+// used for ABI parameter passing
+typedef enum {
+    // needs a direct value
+    TB_PASSING_DIRECT,
+
+    // needs an address to the value
+    TB_PASSING_INDIRECT,
+
+    // doesn't use this parameter
+    TB_PASSING_IGNORE,
+} TB_PassingRule;
+
+TB_API TB_PassingRule tb_get_passing_rule_from_dbg(TB_Module* mod, TB_DebugType* param_type, bool is_return);
+
+////////////////////////////////
+// Globals
+////////////////////////////////
+TB_API TB_Global* tb_global_create(TB_Module* m, ptrdiff_t len, const char* name, TB_DebugType* dbg_type, TB_Linkage linkage);
+
+// allocate space for the global
+TB_API void tb_global_set_storage(TB_Module* m, TB_ModuleSection* section, TB_Global* global, size_t size, size_t align, size_t max_objects);
+
+// returns a buffer which the user can fill to then have represented in the initializer
+TB_API void* tb_global_add_region(TB_Module* m, TB_Global* global, size_t offset, size_t size);
+
+// places a relocation for a global at offset, the size of the relocation
+// depends on the pointer size
+TB_API void tb_global_add_symbol_reloc(TB_Module* m, TB_Global* global, size_t offset, const TB_Symbol* symbol);
+
+TB_API TB_ModuleSection* tb_module_get_text(TB_Module* m);
+TB_API TB_ModuleSection* tb_module_get_rdata(TB_Module* m);
+TB_API TB_ModuleSection* tb_module_get_data(TB_Module* m);
+TB_API TB_ModuleSection* tb_module_get_tls(TB_Module* m);
+
+////////////////////////////////
+// Function Attributes
+////////////////////////////////
+TB_API void tb_node_append_attrib(TB_Node* n, TB_Attrib* a);
+
+// These are parts of a function that describe metadata for instructions
+TB_API TB_Attrib* tb_function_attrib_variable(TB_Function* f, ptrdiff_t len, const char* name, TB_DebugType* type);
+TB_API TB_Attrib* tb_function_attrib_scope(TB_Function* f, TB_Attrib* parent_scope);
+
+////////////////////////////////
+// Debug info Generation
+////////////////////////////////
+TB_API TB_DebugType* tb_debug_get_void(TB_Module* m);
+TB_API TB_DebugType* tb_debug_get_bool(TB_Module* m);
+TB_API TB_DebugType* tb_debug_get_integer(TB_Module* m, bool is_signed, int bits);
+TB_API TB_DebugType* tb_debug_get_float(TB_Module* m, TB_FloatFormat fmt);
+TB_API TB_DebugType* tb_debug_create_ptr(TB_Module* m, TB_DebugType* base);
+TB_API TB_DebugType* tb_debug_create_array(TB_Module* m, TB_DebugType* base, size_t count);
+TB_API TB_DebugType* tb_debug_create_alias(TB_Module* m, TB_DebugType* base, ptrdiff_t len, const char* tag);
+TB_API TB_DebugType* tb_debug_create_struct(TB_Module* m, ptrdiff_t len, const char* tag);
+TB_API TB_DebugType* tb_debug_create_union(TB_Module* m, ptrdiff_t len, const char* tag);
+TB_API TB_DebugType* tb_debug_create_field(TB_Module* m, TB_DebugType* type, ptrdiff_t len, const char* name, TB_CharUnits offset);
+
+// returns the array you need to fill with fields
+TB_API TB_DebugType** tb_debug_record_begin(TB_DebugType* type, size_t count);
+TB_API void tb_debug_record_end(TB_DebugType* type, TB_CharUnits size, TB_CharUnits align);
+
+TB_API TB_DebugType* tb_debug_create_func(TB_Module* m, TB_CallingConv cc, size_t param_count, size_t return_count, bool has_varargs);
+
+TB_API TB_DebugType* tb_debug_field_type(TB_DebugType* type);
+
+TB_API size_t tb_debug_func_return_count(TB_DebugType* type);
+TB_API size_t tb_debug_func_param_count(TB_DebugType* type);
+
+// you'll need to fill these if you make a function
+TB_API TB_DebugType** tb_debug_func_params(TB_DebugType* type);
+TB_API TB_DebugType** tb_debug_func_returns(TB_DebugType* type);
+
+////////////////////////////////
+// IR access
+////////////////////////////////
+// it is an index to the input
+#define TB_FOR_INPUT_IN_NODE(it, parent) for (TB_Node **it = parent->inputs, **__end = it + (parent)->input_count; it != __end; it++)
+
+////////////////////////////////
+// Symbols
+////////////////////////////////
+TB_API bool tb_symbol_is_comdat(const TB_Symbol* s);
+
+// returns NULL if the tag doesn't match
+TB_API TB_Function* tb_symbol_as_function(TB_Symbol* s);
+TB_API TB_External* tb_symbol_as_external(TB_Symbol* s);
+TB_API TB_Global* tb_symbol_as_global(TB_Symbol* s);
+
+////////////////////////////////
+// Function IR Generation
+////////////////////////////////
+TB_API void tb_get_data_type_size(TB_Module* mod, TB_DataType dt, size_t* size, size_t* align);
+
+// the user_data is expected to be a valid FILE*
+TB_API void tb_default_print_callback(void* user_data, const char* fmt, ...);
+
+TB_API void tb_inst_set_location(TB_Function* f, TB_FileID file, int line);
+
+// if section is NULL, default to .text
+TB_API TB_Function* tb_function_create(TB_Module* m, ptrdiff_t len, const char* name, TB_Linkage linkage, TB_ComdatType comdat);
+
+TB_API void* tb_function_get_jit_pos(TB_Function* f);
+
+// if len is -1, it's null terminated
+TB_API void tb_symbol_set_name(TB_Symbol* s, ptrdiff_t len, const char* name);
+
+TB_API void tb_symbol_bind_ptr(TB_Symbol* s, void* ptr);
+TB_API const char* tb_symbol_get_name(TB_Symbol* s);
+
+// if arena is NULL, defaults to module arena which is freed on tb_free_thread_resources
+TB_API void tb_function_set_prototype(TB_Function* f, TB_FunctionPrototype* p, TB_Arena* arena);
+TB_API TB_FunctionPrototype* tb_function_get_prototype(TB_Function* f);
+
+TB_API void tb_function_print(TB_Function* f, TB_PrintCallback callback, void* user_data);
+
+TB_API void tb_inst_set_control(TB_Function* f, TB_Node* control);
+TB_API TB_Node* tb_inst_get_control(TB_Function* f);
+
+TB_API TB_Node* tb_inst_region(TB_Function* f);
+
+// if len is -1, it's null terminated
+TB_API void tb_inst_set_region_name(TB_Node* n, ptrdiff_t len, const char* name);
+
+TB_API void tb_inst_unreachable(TB_Function* f);
+TB_API void tb_inst_debugbreak(TB_Function* f);
+TB_API void tb_inst_trap(TB_Function* f);
+TB_API TB_Node* tb_inst_poison(TB_Function* f);
+
+TB_API TB_Node* tb_inst_param(TB_Function* f, int param_id);
+
+TB_API TB_Node* tb_inst_fpxt(TB_Function* f, TB_Node* src, TB_DataType dt);
+TB_API TB_Node* tb_inst_sxt(TB_Function* f, TB_Node* src, TB_DataType dt);
+TB_API TB_Node* tb_inst_zxt(TB_Function* f, TB_Node* src, TB_DataType dt);
+TB_API TB_Node* tb_inst_trunc(TB_Function* f, TB_Node* src, TB_DataType dt);
+TB_API TB_Node* tb_inst_int2ptr(TB_Function* f, TB_Node* src);
+TB_API TB_Node* tb_inst_ptr2int(TB_Function* f, TB_Node* src, TB_DataType dt);
+TB_API TB_Node* tb_inst_int2float(TB_Function* f, TB_Node* src, TB_DataType dt, bool is_signed);
+TB_API TB_Node* tb_inst_float2int(TB_Function* f, TB_Node* src, TB_DataType dt, bool is_signed);
+TB_API TB_Node* tb_inst_bitcast(TB_Function* f, TB_Node* src, TB_DataType dt);
+
+TB_API TB_Node* tb_inst_local(TB_Function* f, TB_CharUnits size, TB_CharUnits align);
+TB_API TB_Node* tb_inst_load(TB_Function* f, TB_DataType dt, TB_Node* addr, TB_CharUnits align, bool is_volatile);
+TB_API void tb_inst_store(TB_Function* f, TB_DataType dt, TB_Node* addr, TB_Node* val, TB_CharUnits align, bool is_volatile);
+
+TB_API TB_Node* tb_inst_bool(TB_Function* f, bool imm);
+TB_API TB_Node* tb_inst_sint(TB_Function* f, TB_DataType dt, int64_t imm);
+TB_API TB_Node* tb_inst_uint(TB_Function* f, TB_DataType dt, uint64_t imm);
+TB_API TB_Node* tb_inst_float32(TB_Function* f, float imm);
+TB_API TB_Node* tb_inst_float64(TB_Function* f, double imm);
+TB_API TB_Node* tb_inst_cstring(TB_Function* f, const char* str);
+TB_API TB_Node* tb_inst_string(TB_Function* f, size_t len, const char* str);
+
+// write 'val' over 'count' bytes on 'dst'
+TB_API void tb_inst_memset(TB_Function* f, TB_Node* dst, TB_Node* val, TB_Node* count, TB_CharUnits align, bool is_volatile);
+
+// zero 'count' bytes on 'dst'
+TB_API void tb_inst_memzero(TB_Function* f, TB_Node* dst, TB_Node* count, TB_CharUnits align, bool is_volatile);
+
+// performs a copy of 'count' elements from one memory location to another
+// both locations cannot overlap.
+TB_API void tb_inst_memcpy(TB_Function* f, TB_Node* dst, TB_Node* src, TB_Node* count, TB_CharUnits align, bool is_volatile);
+
+// result = base + (index * stride)
+TB_API TB_Node* tb_inst_array_access(TB_Function* f, TB_Node* base, TB_Node* index, int64_t stride);
+
+// result = base + offset
+// where base is a pointer
+TB_API TB_Node* tb_inst_member_access(TB_Function* f, TB_Node* base, int64_t offset);
+
+TB_API TB_Node* tb_inst_get_symbol_address(TB_Function* f, TB_Symbol* target);
+
+// Performs a conditional select between two values, if the operation is
+// performed wide then the cond is expected to be the same type as a and b where
+// the condition is resolved as true if the MSB (per component) is 1.
+//
+// result = cond ? a : b
+// a, b must match in type
+TB_API TB_Node* tb_inst_select(TB_Function* f, TB_Node* cond, TB_Node* a, TB_Node* b);
+
+// Integer arithmatic
+TB_API TB_Node* tb_inst_add(TB_Function* f, TB_Node* a, TB_Node* b, TB_ArithmeticBehavior arith_behavior);
+TB_API TB_Node* tb_inst_sub(TB_Function* f, TB_Node* a, TB_Node* b, TB_ArithmeticBehavior arith_behavior);
+TB_API TB_Node* tb_inst_mul(TB_Function* f, TB_Node* a, TB_Node* b, TB_ArithmeticBehavior arith_behavior);
+TB_API TB_Node* tb_inst_div(TB_Function* f, TB_Node* a, TB_Node* b, bool signedness);
+TB_API TB_Node* tb_inst_mod(TB_Function* f, TB_Node* a, TB_Node* b, bool signedness);
+
+// Bitmagic operations
+TB_API TB_Node* tb_inst_bswap(TB_Function* f, TB_Node* n);
+TB_API TB_Node* tb_inst_clz(TB_Function* f, TB_Node* n);
+TB_API TB_Node* tb_inst_ctz(TB_Function* f, TB_Node* n);
+TB_API TB_Node* tb_inst_popcount(TB_Function* f, TB_Node* n);
+
+// Bitwise operations
+TB_API TB_Node* tb_inst_not(TB_Function* f, TB_Node* n);
+TB_API TB_Node* tb_inst_neg(TB_Function* f, TB_Node* n);
+TB_API TB_Node* tb_inst_and(TB_Function* f, TB_Node* a, TB_Node* b);
+TB_API TB_Node* tb_inst_or(TB_Function* f, TB_Node* a, TB_Node* b);
+TB_API TB_Node* tb_inst_xor(TB_Function* f, TB_Node* a, TB_Node* b);
+TB_API TB_Node* tb_inst_sar(TB_Function* f, TB_Node* a, TB_Node* b);
+TB_API TB_Node* tb_inst_shl(TB_Function* f, TB_Node* a, TB_Node* b, TB_ArithmeticBehavior arith_behavior);
+TB_API TB_Node* tb_inst_shr(TB_Function* f, TB_Node* a, TB_Node* b);
+TB_API TB_Node* tb_inst_rol(TB_Function* f, TB_Node* a, TB_Node* b);
+TB_API TB_Node* tb_inst_ror(TB_Function* f, TB_Node* a, TB_Node* b);
+
+// Atomics
+// By default you can use TB_MEM_ORDER_SEQ_CST for the memory order to get
+// correct but possibly slower results on certain platforms (those with relaxed
+// memory models).
+
+// Must be aligned to the natural alignment of dt
+TB_API TB_Node* tb_inst_atomic_load(TB_Function* f, TB_Node* addr, TB_DataType dt, TB_MemoryOrder order);
+
+// All atomic operations here return the old value and the operations are
+// performed in the same data type as 'src' with alignment of 'addr' being
+// the natural alignment of 'src'
+TB_API TB_Node* tb_inst_atomic_xchg(TB_Function* f, TB_Node* addr, TB_Node* src, TB_MemoryOrder order);
+TB_API TB_Node* tb_inst_atomic_add(TB_Function* f, TB_Node* addr, TB_Node* src, TB_MemoryOrder order);
+TB_API TB_Node* tb_inst_atomic_sub(TB_Function* f, TB_Node* addr, TB_Node* src, TB_MemoryOrder order);
+TB_API TB_Node* tb_inst_atomic_and(TB_Function* f, TB_Node* addr, TB_Node* src, TB_MemoryOrder order);
+TB_API TB_Node* tb_inst_atomic_xor(TB_Function* f, TB_Node* addr, TB_Node* src, TB_MemoryOrder order);
+TB_API TB_Node* tb_inst_atomic_or(TB_Function* f, TB_Node* addr, TB_Node* src, TB_MemoryOrder order);
+
+// returns old_value from *addr
+TB_API TB_Node* tb_inst_atomic_cmpxchg(TB_Function* f, TB_Node* addr, TB_Node* expected, TB_Node* desired, TB_MemoryOrder succ, TB_MemoryOrder fail);
+
+// Float math
+TB_API TB_Node* tb_inst_fadd(TB_Function* f, TB_Node* a, TB_Node* b);
+TB_API TB_Node* tb_inst_fsub(TB_Function* f, TB_Node* a, TB_Node* b);
+TB_API TB_Node* tb_inst_fmul(TB_Function* f, TB_Node* a, TB_Node* b);
+TB_API TB_Node* tb_inst_fdiv(TB_Function* f, TB_Node* a, TB_Node* b);
+
+// Comparisons
+TB_API TB_Node* tb_inst_cmp_eq(TB_Function* f, TB_Node* a, TB_Node* b);
+TB_API TB_Node* tb_inst_cmp_ne(TB_Function* f, TB_Node* a, TB_Node* b);
+
+TB_API TB_Node* tb_inst_cmp_ilt(TB_Function* f, TB_Node* a, TB_Node* b, bool signedness);
+TB_API TB_Node* tb_inst_cmp_ile(TB_Function* f, TB_Node* a, TB_Node* b, bool signedness);
+TB_API TB_Node* tb_inst_cmp_igt(TB_Function* f, TB_Node* a, TB_Node* b, bool signedness);
+TB_API TB_Node* tb_inst_cmp_ige(TB_Function* f, TB_Node* a, TB_Node* b, bool signedness);
+
+TB_API TB_Node* tb_inst_cmp_flt(TB_Function* f, TB_Node* a, TB_Node* b);
+TB_API TB_Node* tb_inst_cmp_fle(TB_Function* f, TB_Node* a, TB_Node* b);
+TB_API TB_Node* tb_inst_cmp_fgt(TB_Function* f, TB_Node* a, TB_Node* b);
+TB_API TB_Node* tb_inst_cmp_fge(TB_Function* f, TB_Node* a, TB_Node* b);
+
+// General intrinsics
+TB_API TB_Node* tb_inst_va_start(TB_Function* f, TB_Node* a);
+
+// x86 Intrinsics
+TB_API TB_Node* tb_inst_x86_rdtsc(TB_Function* f);
+TB_API TB_Node* tb_inst_x86_ldmxcsr(TB_Function* f, TB_Node* a);
+TB_API TB_Node* tb_inst_x86_stmxcsr(TB_Function* f);
+TB_API TB_Node* tb_inst_x86_sqrt(TB_Function* f, TB_Node* a);
+TB_API TB_Node* tb_inst_x86_rsqrt(TB_Function* f, TB_Node* a);
+
+// Control flow
+TB_API TB_Node* tb_inst_syscall(TB_Function* f, TB_DataType dt, TB_Node* syscall_num, size_t param_count, TB_Node** params);
+TB_API TB_MultiOutput tb_inst_call(TB_Function* f, TB_FunctionPrototype* proto, TB_Node* target, size_t param_count, TB_Node** params);
+
+// Managed
+TB_API TB_Node* tb_inst_safepoint(TB_Function* f, size_t param_count, TB_Node** params);
+
+TB_API TB_Node* tb_inst_incomplete_phi(TB_Function* f, TB_DataType dt, TB_Node* region, size_t preds);
+TB_API bool tb_inst_add_phi_operand(TB_Function* f, TB_Node* phi, TB_Node* region, TB_Node* val);
+
+TB_API TB_Node* tb_inst_phi2(TB_Function* f, TB_Node* region, TB_Node* a, TB_Node* b);
+TB_API void tb_inst_goto(TB_Function* f, TB_Node* target);
+TB_API void tb_inst_if(TB_Function* f, TB_Node* cond, TB_Node* true_case, TB_Node* false_case);
+TB_API void tb_inst_branch(TB_Function* f, TB_DataType dt, TB_Node* key, TB_Node* default_case, size_t entry_count, const TB_SwitchEntry* keys);
+
+TB_API void tb_inst_ret(TB_Function* f, size_t count, TB_Node** values);
+
+////////////////////////////////
+// Passes
+////////////////////////////////
+// Function analysis, optimizations, and codegen are all part of this
+typedef struct TB_Passes TB_Passes;
+
+// the arena is used to allocate the nodes while passes are being done.
+TB_API TB_Passes* tb_pass_enter(TB_Function* f, TB_Arena* arena);
+TB_API void tb_pass_exit(TB_Passes* opt);
+
+// transformation passes:
+//   peephole: runs most simple reductions on the code,
+//     should be run after any bigger passes (it's incremental
+//     so it's not that bad)
+//
+//   mem2reg: lowers TB_LOCALs into SSA values, this makes more
+//     data flow analysis possible on the code and allows to codegen
+//     to place variables into registers.
+//
+//   loop: NOT READY
+//
+TB_API bool tb_pass_peephole(TB_Passes* opt);
+TB_API bool tb_pass_mem2reg(TB_Passes* opt);
+TB_API bool tb_pass_loop(TB_Passes* opt);
+
+// analysis
+//   print: prints IR in a flattened text form.
+TB_API bool tb_pass_print(TB_Passes* opt);
+
+// codegen
+TB_API TB_FunctionOutput* tb_pass_codegen(TB_Passes* opt, bool emit_asm);
+
+TB_API void tb_pass_kill_node(TB_Passes* opt, TB_Node* n);
+TB_API bool tb_pass_mark(TB_Passes* opt, TB_Node* n);
+TB_API void tb_pass_mark_users(TB_Passes* opt, TB_Node* n);
+
+////////////////////////////////
+// IR access
+////////////////////////////////
+TB_API const char* tb_node_get_name(TB_Node* n);
+
+TB_API TB_Node* tb_get_parent_region(TB_Node* n);
+TB_API bool tb_node_is_constant_non_zero(TB_Node* n);
+TB_API bool tb_node_is_constant_zero(TB_Node* n);
+
+#endif /* TB_CORE_H */

BIN
src/tilde/tb.lib


+ 76 - 0
src/tilde/tb_arena.h

@@ -0,0 +1,76 @@
+#pragma once
+#include <stddef.h>
+#include <stdbool.h>
+
+#ifndef TB_API
+#  ifdef __cplusplus
+#    define TB_EXTERN extern "C"
+#  else
+#    define TB_EXTERN
+#  endif
+#  ifdef TB_DLL
+#    ifdef TB_IMPORT_DLL
+#      define TB_API TB_EXTERN __declspec(dllimport)
+#    else
+#      define TB_API TB_EXTERN __declspec(dllexport)
+#    endif
+#  else
+#    define TB_API TB_EXTERN
+#  endif
+#endif
+
+enum {
+    TB_ARENA_SMALL_CHUNK_SIZE  =        4 * 1024,
+    TB_ARENA_MEDIUM_CHUNK_SIZE =      512 * 1024,
+    TB_ARENA_LARGE_CHUNK_SIZE  = 2 * 1024 * 1024,
+
+    TB_ARENA_ALIGNMENT = 16,
+};
+
+typedef struct TB_ArenaChunk TB_ArenaChunk;
+struct TB_ArenaChunk {
+    TB_ArenaChunk* next;
+    size_t pad;
+    char data[];
+};
+
+typedef struct TB_Arena {
+    size_t chunk_size;
+    TB_ArenaChunk* base;
+    TB_ArenaChunk* top;
+
+    // top of the allocation space
+    char* watermark;
+    char* high_point; // &top->data[chunk_size]
+} TB_Arena;
+
+typedef struct TB_ArenaSavepoint {
+    TB_ArenaChunk* top;
+    char* watermark;
+} TB_ArenaSavepoint;
+
+#define TB_ARENA_FOR(it, arena) for (TB_ArenaChunk* it = (arena)->base; it != NULL; it = it->next)
+
+#define TB_ARENA_ALLOC(arena, T) tb_arena_alloc(arena, sizeof(T))
+#define TB_ARENA_ARR_ALLOC(arena, count, T) tb_arena_alloc(arena, (count) * sizeof(T))
+
+TB_API void tb_arena_create(TB_Arena* restrict arena, size_t chunk_size);
+TB_API void tb_arena_destroy(TB_Arena* restrict arena);
+
+TB_API void* tb_arena_unaligned_alloc(TB_Arena* restrict arena, size_t size);
+TB_API void* tb_arena_alloc(TB_Arena* restrict arena, size_t size);
+
+// asserts if ptr+size != watermark
+TB_API void tb_arena_pop(TB_Arena* restrict arena, void* ptr, size_t size);
+
+// in case you wanna mix unaligned and aligned arenas
+TB_API void tb_arena_realign(TB_Arena* restrict arena);
+
+TB_API bool tb_arena_is_empty(TB_Arena* arena);
+
+// savepoints
+TB_API TB_ArenaSavepoint tb_arena_save(TB_Arena* arena);
+TB_API void tb_arena_restore(TB_Arena* arena, TB_ArenaSavepoint sp);
+
+// resets to only having one chunk
+TB_API void tb_arena_clear(TB_Arena* arena);

+ 330 - 0
src/tilde/tb_coff.h

@@ -0,0 +1,330 @@
+// PE/COFF is the executable/object format used by Microsoft.
+#ifndef TB_COFF_H
+#define TB_COFF_H
+
+#include "tb_formats.h"
+
+#define IMAGE_SCN_LNK_NRELOC_OVFL 0x01000000
+
+#define IMAGE_SYM_CLASS_EXTERNAL      0x0002
+#define IMAGE_SYM_CLASS_STATIC        0x0003
+#define IMAGE_SYM_CLASS_LABEL         0x0006
+#define IMAGE_SYM_CLASS_FILE          0x0067
+#define IMAGE_SYM_CLASS_SECTION       0x0068
+#define IMAGE_SYM_CLASS_WEAK_EXTERNAL 0x0069
+
+#define IMAGE_FILE_LINE_NUMS_STRIPPED 0x0004
+
+#define IMAGE_REL_AMD64_ADDR64   0x0001
+#define IMAGE_REL_AMD64_ADDR32   0x0002
+#define IMAGE_REL_AMD64_ADDR32NB 0x0003
+#define IMAGE_REL_AMD64_REL32    0x0004
+#define IMAGE_REL_AMD64_REL32_1  0x0005
+#define IMAGE_REL_AMD64_REL32_2  0x0006
+#define IMAGE_REL_AMD64_REL32_3  0x0007
+#define IMAGE_REL_AMD64_REL32_4  0x0008
+#define IMAGE_REL_AMD64_REL32_5  0x0009
+#define IMAGE_REL_AMD64_SECTION  0x000A
+#define IMAGE_REL_AMD64_SECREL   0x000B
+
+#define IMAGE_SCN_LNK_REMOVE      0x00000800
+#define IMAGE_SCN_LNK_COMDAT      0x00001000
+#define IMAGE_SCN_MEM_DISCARDABLE 0x02000000
+#define IMAGE_SCN_MEM_EXECUTE     0x20000000
+#define IMAGE_SCN_MEM_READ        0x40000000
+#define IMAGE_SCN_MEM_WRITE       0x80000000
+
+#define IMAGE_SCN_CNT_CODE                   0x00000020  /* Section contains code. */
+#define IMAGE_SCN_CNT_INITIALIZED_DATA       0x00000040  /* Section contains initialized data. */
+#define IMAGE_SCN_CNT_UNINITIALIZED_DATA     0x00000080  /* Section contains uninitialized data. */
+
+#define IMAGE_DIRECTORY_ENTRY_EXPORT          0   // Export Directory
+#define IMAGE_DIRECTORY_ENTRY_IMPORT          1   // Import Directory
+#define IMAGE_DIRECTORY_ENTRY_RESOURCE        2   // Resource Directory
+#define IMAGE_DIRECTORY_ENTRY_EXCEPTION       3   // Exception Directory
+#define IMAGE_DIRECTORY_ENTRY_SECURITY        4   // Security Directory
+#define IMAGE_DIRECTORY_ENTRY_BASERELOC       5   // Base Relocation Table
+#define IMAGE_DIRECTORY_ENTRY_DEBUG           6   // Debug Directory
+#define IMAGE_DIRECTORY_ENTRY_ARCHITECTURE    7   // Architecture Specific Data
+#define IMAGE_DIRECTORY_ENTRY_GLOBALPTR       8   // RVA of GP
+#define IMAGE_DIRECTORY_ENTRY_TLS             9   // TLS Directory
+#define IMAGE_DIRECTORY_ENTRY_LOAD_CONFIG    10   // Load Configuration Directory
+#define IMAGE_DIRECTORY_ENTRY_BOUND_IMPORT   11   // Bound Import Directory in headers
+#define IMAGE_DIRECTORY_ENTRY_IAT            12   // Import Address Table
+#define IMAGE_DIRECTORY_ENTRY_DELAY_IMPORT   13   // Delay Load Import Descriptors
+#define IMAGE_DIRECTORY_ENTRY_COM_DESCRIPTOR 14   // COM Runtime descriptor
+
+#define IMAGE_SUBSYSTEM_WINDOWS_GUI 2
+#define IMAGE_SUBSYSTEM_WINDOWS_CUI 3
+#define IMAGE_SUBSYSTEM_EFI_APPLICATION 10
+
+typedef enum {
+    TB_COFF_SECTION_NO_PAD      = 0x00000008,
+    TB_COFF_SECTION_CODE        = 0x00000020,
+    TB_COFF_SECTION_INIT        = 0x00000040,
+    TB_COFF_SECTION_UNINIT      = 0x00000080,
+    TB_COFF_SECTION_OTHER       = 0x00000100,
+    TB_COFF_SECTION_INFO        = 0x00000200,
+    TB_COFF_SECTION_REMOVE      = 0x00000800,
+    TB_COFF_SECTION_COMDAT      = 0x00001000,
+
+    // this is actually a 4bit field
+    TB_COFF_SECTION_ALIGN       = 0x00F00000,
+
+    // if we have more than 65535 relocations we do this
+    TB_COFF_SECTION_RELOC_OVR   = 0x00F00000,
+
+    // memory flags
+    TB_COFF_SECTION_DISCARDABLE = 0x02000000,
+    TB_COFF_SECTION_NOT_CACHED  = 0x04000000,
+    TB_COFF_SECTION_NOT_PAGED   = 0x08000000,
+    TB_COFF_SECTION_SHARED      = 0x10000000,
+    TB_COFF_SECTION_EXECUTE     = 0x20000000,
+    TB_COFF_SECTION_READ        = 0x40000000,
+    TB_COFF_SECTION_WRITE       = 0x80000000,
+} TB_COFF_SectionFlags;
+
+typedef struct TB_COFF_Parser {
+    // inputs
+    TB_Slice name, file;
+
+    // results
+    size_t section_count;
+    size_t symbol_table, symbol_count;
+
+    // private
+    TB_Slice string_table;
+} TB_COFF_Parser;
+
+// fills the parser with results from the COFF header
+bool tb_coff_parse_init(TB_COFF_Parser* restrict parser);
+bool tb_coff_parse_section(TB_COFF_Parser* restrict parser, size_t i, TB_ObjectSection* out_sec);
+
+// how many symbols does this one symbol take up (basically 1 + aux symbols).
+// returns 0 if error.
+size_t tb_coff_parse_symbol(TB_COFF_Parser* restrict parser, size_t i, TB_ObjectSymbol* restrict out_sym);
+
+#endif // TB_COFF_H
+
+#ifdef TB_COFF_IMPL
+#include <common.h>
+
+#pragma pack(push, 2)
+typedef struct COFF_SectionHeader {
+    char name[8];
+    union {
+        uint32_t physical_address;
+        uint32_t virtual_size;
+    } misc;
+    uint32_t virtual_address;
+    uint32_t raw_data_size;
+    uint32_t raw_data_pos;
+    uint32_t pointer_to_reloc;
+    uint32_t pointer_to_lineno;
+    uint16_t num_reloc;
+    uint16_t num_lineno;
+    uint32_t characteristics;
+} COFF_SectionHeader;
+
+typedef struct COFF_FileHeader {
+    uint16_t machine;
+    uint16_t section_count;
+    uint32_t timestamp;
+    uint32_t symbol_table;
+    uint32_t symbol_count;
+    uint16_t optional_header_size;
+    uint16_t flags;
+} COFF_FileHeader;
+
+typedef struct COFF_Symbol {
+    union {
+        uint8_t  short_name[8];
+        uint32_t long_name[2];
+    };
+    uint32_t value;
+    int16_t  section_number;
+    uint16_t type;
+    uint8_t  storage_class;
+    uint8_t  aux_symbols_count;
+} COFF_Symbol;
+
+typedef struct COFF_ImageReloc {
+    union {
+        uint32_t VirtualAddress;
+        uint32_t RelocCount;
+    };
+    uint32_t SymbolTableIndex;
+    uint16_t Type;
+} COFF_ImageReloc;
+#pragma pack(pop)
+
+// sanity checks
+static_assert(sizeof(COFF_SectionHeader) == 40, "COFF Section header size != 40 bytes");
+static_assert(sizeof(COFF_ImageReloc) == 10,    "COFF Image Relocation size != 10 bytes");
+static_assert(sizeof(COFF_FileHeader) == 20,    "COFF File header size != 20 bytes");
+static_assert(sizeof(COFF_Symbol) == 18,        "COFF Symbol size != 18 bytes");
+
+bool tb_coff_parse_init(TB_COFF_Parser* restrict parser) {
+    TB_Slice file = parser->file;
+
+    if (file.length < sizeof(COFF_FileHeader)) return false;
+    COFF_FileHeader* header = (COFF_FileHeader*) &parser->file.data[0];
+
+    // locate string table (it spans until the end of the file)
+    size_t string_table_pos = header->symbol_table + (header->symbol_count * sizeof(COFF_Symbol));
+    if (file.length < string_table_pos) return false;
+
+    parser->symbol_count = header->symbol_count;
+    parser->symbol_table = header->symbol_table;
+    parser->section_count = header->section_count;
+    parser->string_table = (TB_Slice){
+        .length = file.length - string_table_pos,
+        .data   = &file.data[string_table_pos]
+    };
+
+    return true;
+}
+
+static long long tb__parse_decimal_int(size_t n, const char* str) {
+    const char* end = &str[n];
+
+    int result = 0;
+    while (str != end) {
+        if (*str < '0' || *str > '9') break;
+
+        result *= 10;
+        result += *str - '0';
+        str++;
+    }
+
+    return result;
+}
+
+bool tb_coff_parse_section(TB_COFF_Parser* restrict parser, size_t i, TB_ObjectSection* restrict out_sec) {
+    TB_Slice file = parser->file;
+    size_t section_offset = sizeof(COFF_FileHeader) + (i * sizeof(COFF_SectionHeader));
+
+    if (file.length < section_offset + sizeof(COFF_SectionHeader)) {
+        return false;
+    }
+
+    COFF_SectionHeader* sec = (COFF_SectionHeader*) &file.data[section_offset];
+    *out_sec = (TB_ObjectSection) { .flags = sec->characteristics };
+
+    // Parse string table name stuff
+    if (sec->name[0] == '/') {
+        // string table access
+        int offset = tb__parse_decimal_int(7, &sec->name[1]);
+        if (file.length > offset) {
+            return false;
+        }
+
+        const uint8_t* data = &parser->string_table.data[offset];
+        out_sec->name = (TB_Slice){ strlen((const char*) data), data };
+    } else {
+        // normal inplace string
+        size_t len = strlen(sec->name);
+        out_sec->name = (TB_Slice){ len, (uint8_t*) sec->name };
+    }
+
+    // Parse relocations
+    if (sec->num_reloc > 0) {
+        out_sec->relocation_count = sec->num_reloc;
+        COFF_ImageReloc* src_relocs = (COFF_ImageReloc*) &file.data[sec->pointer_to_reloc];
+
+        TB_ObjectReloc* dst_relocs = tb_platform_heap_alloc(sec->num_reloc * sizeof(TB_ObjectReloc));
+        FOREACH_N(j, 0, sec->num_reloc) {
+            dst_relocs[j] = (TB_ObjectReloc){ 0 };
+            switch (src_relocs[j].Type) {
+                case IMAGE_REL_AMD64_ADDR32NB: dst_relocs[j].type = TB_OBJECT_RELOC_ADDR32NB; break;
+                case IMAGE_REL_AMD64_ADDR32:   dst_relocs[j].type = TB_OBJECT_RELOC_ADDR32; break;
+                case IMAGE_REL_AMD64_ADDR64:   dst_relocs[j].type = TB_OBJECT_RELOC_ADDR64; break;
+                case IMAGE_REL_AMD64_SECREL:   dst_relocs[j].type = TB_OBJECT_RELOC_SECREL; break;
+                case IMAGE_REL_AMD64_SECTION:  dst_relocs[j].type = TB_OBJECT_RELOC_SECTION; break;
+
+                case IMAGE_REL_AMD64_REL32:
+                case IMAGE_REL_AMD64_REL32_1:
+                case IMAGE_REL_AMD64_REL32_2:
+                case IMAGE_REL_AMD64_REL32_3:
+                case IMAGE_REL_AMD64_REL32_4:
+                case IMAGE_REL_AMD64_REL32_5:
+                dst_relocs[j].type = TB_OBJECT_RELOC_REL32;
+                break;
+
+                default: tb_todo();
+            }
+
+            if (src_relocs[j].Type >= IMAGE_REL_AMD64_REL32 && src_relocs[j].Type <= IMAGE_REL_AMD64_REL32_5) {
+                dst_relocs[j].addend = 4 + (src_relocs[j].Type - IMAGE_REL_AMD64_REL32);
+            }
+
+            dst_relocs[j].symbol_index = src_relocs[j].SymbolTableIndex;
+            dst_relocs[j].virtual_address = src_relocs[j].VirtualAddress;
+        }
+
+        out_sec->relocations = dst_relocs;
+    }
+
+    // Parse virtual region
+    out_sec->virtual_address = sec->virtual_address;
+    out_sec->virtual_size = sec->misc.virtual_size;
+
+    // Read raw data (if applies)
+    if (sec->raw_data_size) {
+        assert(sec->raw_data_pos + sec->raw_data_size < file.length);
+        out_sec->raw_data = (TB_Slice){ sec->raw_data_size, &file.data[sec->raw_data_pos] };
+    }
+
+    return true;
+}
+
+TB_ObjectSymbolType classify_symbol_type(uint16_t st_class) {
+    switch (st_class) {
+        case 2:    return TB_OBJECT_SYMBOL_EXTERN;
+        case 3:    return TB_OBJECT_SYMBOL_STATIC;
+        case 6:    return TB_OBJECT_SYMBOL_STATIC;
+        case 0x68: return TB_OBJECT_SYMBOL_SECTION;
+        case 0x69: return TB_OBJECT_SYMBOL_WEAK_EXTERN;
+        default: return TB_OBJECT_SYMBOL_UNKNOWN;
+    }
+}
+
+size_t tb_coff_parse_symbol(TB_COFF_Parser* restrict parser, size_t i, TB_ObjectSymbol* restrict out_sym) {
+    TB_Slice file = parser->file;
+    size_t symbol_offset = parser->symbol_table + (i * sizeof(COFF_Symbol));
+
+    if (file.length < symbol_offset + sizeof(COFF_Symbol)) {
+        return 0;
+    }
+
+    COFF_Symbol* sym = (COFF_Symbol*) &file.data[symbol_offset];
+    *out_sym = (TB_ObjectSymbol) {
+        .ordinal = i,
+        .type = classify_symbol_type(sym->storage_class),
+        .section_num = sym->section_number,
+        .value = sym->value
+    };
+
+    // Parse string table name stuff
+    if (sym->long_name[0] == 0) {
+        // string table access (read a cstring)
+        // TODO(NeGate): bounds check this
+        const uint8_t* data = &parser->string_table.data[sym->long_name[1]];
+        out_sym->name = (TB_Slice){ strlen((const char*) data), data };
+    } else {
+        // normal inplace string
+        size_t len = strlen((const char*) sym->short_name);
+        out_sym->name = (TB_Slice){ len, sym->short_name };
+    }
+
+    // TODO(NeGate): Process aux symbols
+    if (sym->aux_symbols_count) {
+        out_sym->extra = &sym[1];
+
+        // FOREACH_N(j, 0, sym->aux_symbols_count) {}
+    }
+
+    return sym->aux_symbols_count + 1;
+}
+
+#endif // TB_COFF_IMPL

+ 170 - 0
src/tilde/tb_elf.h

@@ -0,0 +1,170 @@
+#ifndef TB_ELF_H
+#define TB_ELF_H
+
+#include <stdint.h>
+
+#define TB_EI_MAG0       0
+#define TB_EI_MAG1       1
+#define TB_EI_MAG2       2
+#define TB_EI_MAG3       3
+#define TB_EI_CLASS      4  /* Class of machine. */
+#define TB_EI_DATA       5  /* Data format. */
+#define TB_EI_VERSION    6  /* ELF format version. */
+#define TB_EI_OSABI      7  /* Operating system / ABI identification */
+#define TB_EI_ABIVERSION 8  /* ABI version */
+#define TB_OLD_EI_BRAND  8  /* Start of architecture identification. */
+#define TB_EI_PAD        9  /* Start of padding (per SVR4 ABI). */
+#define TB_EI_NIDENT     16 /* Size of e_ident array. */
+
+/* Values for e_type. */
+#define TB_ET_NONE   0      /* Unknown type. */
+#define TB_ET_REL    1      /* Relocatable. */
+#define TB_ET_EXEC   2      /* Executable. */
+#define TB_ET_DYN    3      /* Shared object. */
+#define TB_ET_CORE   4      /* Core file. */
+#define TB_ET_LOOS   0xfe00 /* First operating system specific. */
+#define TB_ET_HIOS   0xfeff /* Last operating system-specific. */
+#define TB_ET_LOPROC 0xff00 /* First processor-specific. */
+#define TB_ET_HIPROC 0xffff /* Last processor-specific. */
+
+/* Values for e_machine. */
+#define TB_EM_NONE    0   /* Unknown machine. */
+#define TB_EM_X86_64  62  /* Advanced Micro Devices x86-64 */
+#define TB_EM_AARCH64 183 /* AArch64 (64-bit ARM) */
+
+/* sh_type */
+#define TB_SHT_NULL     0 /* inactive */
+#define TB_SHT_PROGBITS 1 /* program defined information */
+#define TB_SHT_SYMTAB   2 /* symbol table section */
+#define TB_SHT_STRTAB   3 /* string table section */
+#define TB_SHT_RELA     4 /* relocation section with addends */
+#define TB_SHT_NOBITS   8 /* no space section */
+
+/* Flags for sh_flags. */
+#define TB_SHF_WRITE            0x1        /* Section contains writable data. */
+#define TB_SHF_ALLOC            0x2        /* Section occupies memory. */
+#define TB_SHF_EXECINSTR        0x4        /* Section contains instructions. */
+#define TB_SHF_MERGE            0x10       /* Section may be merged. */
+#define TB_SHF_STRINGS          0x20       /* Section contains strings. */
+#define TB_SHF_INFO_LINK        0x40       /* sh_info holds section index. */
+#define TB_SHF_LINK_ORDER       0x80       /* Special ordering requirements. */
+#define TB_SHF_OS_NONCONFORMING 0x100      /* OS-specific processing required. */
+#define TB_SHF_GROUP            0x200      /* Member of section group. */
+#define TB_SHF_TLS              0x400      /* Section contains TLS data. */
+#define TB_SHF_MASKOS           0x0ff00000 /* OS-specific semantics. */
+#define TB_SHF_MASKPROC         0xf0000000 /* Processor-specific semantics. */
+
+/* Values for p_flags. */
+#define TB_PF_X		0x1        /* Executable. */
+#define TB_PF_W		0x2        /* Writable. */
+#define TB_PF_R		0x4        /* Readable. */
+#define TB_PF_MASKOS   0x0ff00000 /* Operating system-specific. */
+#define TB_PF_MASKPROC 0xf0000000 /* Processor-specific. */
+
+/* Values for p_type. */
+#define TB_PT_NULL      0	/* Unused entry. */
+#define TB_PT_LOAD      1	/* Loadable segment. */
+#define TB_PT_DYNAMIC   2	/* Dynamic linking information segment. */
+#define TB_PT_INTERP    3	/* Pathname of interpreter. */
+#define TB_PT_NOTE      4	/* Auxiliary information. */
+#define TB_PT_SHLIB     5	/* Reserved (not used). */
+#define TB_PT_PHDR      6	/* Location of program header itself. */
+#define TB_PT_TLS       7	/* Thread local storage segment */
+
+/* Values for relocation */
+typedef enum {
+    TB_ELF_X86_64_NONE     = 0,
+    TB_ELF_X86_64_64       = 1,
+    TB_ELF_X86_64_PC32     = 2,
+    TB_ELF_X86_64_GOT32    = 3,
+    TB_ELF_X86_64_PLT32    = 4,
+    TB_ELF_X86_64_GOTPCREL = 9,
+} TB_ELF_RelocType;
+
+// ST_TYPE
+#define TB_ELF64_STT_NOTYPE  0
+#define TB_ELF64_STT_OBJECT  1
+#define TB_ELF64_STT_FUNC    2
+#define TB_ELF64_STT_SECTION 3
+
+// ST_INFO
+#define TB_ELF64_STB_LOCAL  0
+#define TB_ELF64_STB_GLOBAL 1
+#define TB_ELF64_STB_WEAK   2
+
+/* Macros for accessing the fields of st_info. */
+#define TB_ELF64_ST_BIND(info) ((info) >> 4)
+#define TB_ELF64_ST_TYPE(info) ((info) & 0xf)
+
+#define TB_ELF64_ST_INFO(b, t) (((b) << 4) | ((t) & 0xF))
+
+#define TB_ELF64_R_SYM(i)     ((i) >> 32u)
+#define TB_ELF64_R_TYPE(i)    ((i)&0xffffffffULL)
+#define TB_ELF64_R_INFO(s, t) (((uint64_t)(s) << 32ULL) + ((uint64_t)(t) & 0xffffffffULL))
+
+// http://web.mit.edu/freebsd/head/sys/sys/elf64.h
+// https://cirosantilli.com/elf-hello-world#minimal-elf-file
+// https://en.wikipedia.org/wiki/Executable_and_Linkable_Format
+typedef struct {
+    uint8_t  ident[16];
+    uint16_t type;
+    uint16_t machine;
+    uint32_t version;
+    uint64_t entry;
+    uint64_t phoff;
+    uint64_t shoff;
+    uint32_t flags;
+    uint16_t ehsize;
+    uint16_t phentsize;
+    uint16_t phnum;
+    uint16_t shentsize;
+    uint16_t shnum;
+    uint16_t shstrndx;
+} TB_Elf64_Ehdr;
+
+typedef struct {
+    uint32_t name;
+    uint32_t type;
+    uint64_t flags;
+    uint64_t addr;
+    uint64_t offset;
+    uint64_t size;
+    uint32_t link;
+    uint32_t info;
+    uint64_t addralign;
+    uint64_t entsize;
+} TB_Elf64_Shdr;
+
+// Segment header for ELF64.
+typedef struct {
+    uint32_t type;   // Type of segment
+    uint32_t flags;  // Segment flags
+    uint64_t offset; // File offset where segment is located, in bytes
+    uint64_t vaddr;  // Virtual address of beginning of segment
+    uint64_t paddr;  // Physical addr of beginning of segment (OS-specific)
+    uint64_t filesz; // Num. of bytes in file image of segment (may be zero)
+    uint64_t memsz;  // Num. of bytes in mem image of segment (may be zero)
+    uint64_t align;  // Segment alignment constraint
+} TB_Elf64_Phdr;
+
+typedef struct {
+    uint32_t name;
+    uint8_t  info;
+    uint8_t  other;
+    uint16_t shndx;
+    uint64_t value;
+    uint64_t size;
+} TB_Elf64_Sym;
+
+typedef struct {
+    uint64_t offset;
+    uint64_t info;
+    int64_t  addend;
+} TB_Elf64_Rela;
+
+typedef struct {
+    uint64_t offset;
+    uint64_t info;
+} TB_Elf64_Rel;
+
+#endif /* TB_ELF_H */

+ 132 - 0
src/tilde/tb_formats.h

@@ -0,0 +1,132 @@
+// This handles the generalized executable/object format parsing stuff
+#ifndef TB_OBJECT_H
+#define TB_OBJECT_H
+
+#include <stdint.h>
+#include <stddef.h>
+
+typedef enum {
+    TB_OBJECT_RELOC_NONE, // how?
+
+    // Target independent
+    TB_OBJECT_RELOC_ADDR32,
+    TB_OBJECT_RELOC_ADDR64, // unsupported on 32bit platforms
+    TB_OBJECT_RELOC_SECREL,
+    TB_OBJECT_RELOC_SECTION,
+
+    // COFF only
+    TB_OBJECT_RELOC_ADDR32NB, // Relative virtual address
+
+    // x64 only
+    TB_OBJECT_RELOC_REL32,    // relative 32bit displacement
+
+    // Aarch64 only
+    TB_OBJECT_RELOC_BRANCH26, // 26bit displacement for B and BL instructions
+    TB_OBJECT_RELOC_REL21,    // for ADR instructions
+
+    // TODO(NeGate): fill in the rest of this later
+} TB_ObjectRelocType;
+
+typedef struct {
+    TB_ObjectRelocType type;
+    uint32_t symbol_index;
+    size_t virtual_address;
+    size_t addend;
+} TB_ObjectReloc;
+
+typedef enum {
+    TB_OBJECT_SYMBOL_UNKNOWN,
+    TB_OBJECT_SYMBOL_EXTERN,      // exported
+    TB_OBJECT_SYMBOL_WEAK_EXTERN, // weak
+    TB_OBJECT_SYMBOL_IMPORT,      // forward decl
+    TB_OBJECT_SYMBOL_STATIC,      // local
+    TB_OBJECT_SYMBOL_SECTION,     // local
+} TB_ObjectSymbolType;
+
+typedef struct {
+    TB_ObjectSymbolType type;
+    int section_num;
+
+    uint32_t ordinal;
+    uint32_t value;
+
+    TB_Slice name;
+
+    // for COFF, this is the auxillary
+    void* extra;
+
+    // this is zeroed out by the loader and left for the user to do crap with
+    void* user_data;
+} TB_ObjectSymbol;
+
+typedef struct {
+    TB_Slice name;
+    uint32_t flags;
+
+    size_t virtual_address;
+    size_t virtual_size;
+
+    // You can have a virtual size without having a raw
+    // data size, that's how the BSS section works
+    TB_Slice raw_data;
+
+    size_t relocation_count;
+    TB_ObjectReloc* relocations;
+
+    // this is zeroed out by the loader and left for the user to do crap with
+    void* user_data;
+} TB_ObjectSection;
+
+typedef enum {
+    TB_OBJECT_FILE_UNKNOWN,
+
+    TB_OBJECT_FILE_COFF,
+    TB_OBJECT_FILE_ELF64
+} TB_ObjectFileType;
+
+typedef struct {
+    TB_ObjectFileType type;
+    TB_Arch           arch;
+
+    TB_Slice          name;
+    TB_Slice          ar_name;
+
+    size_t           symbol_count;
+    TB_ObjectSymbol* symbols;
+
+    size_t           section_count;
+    TB_ObjectSection sections[];
+} TB_ObjectFile;
+
+////////////////////////////////
+// Archive parser
+////////////////////////////////
+typedef struct {
+    TB_Slice name;
+
+    // if import_name is empty, we're dealing with an object file
+    TB_Slice import_name;
+    uint16_t ordinal;
+
+    TB_Slice content;
+} TB_ArchiveEntry;
+
+typedef struct {
+    TB_Slice file;
+    size_t pos;
+
+    size_t member_count;
+    uint32_t* members;
+
+    size_t symbol_count;
+    uint16_t* symbols;
+
+    TB_Slice strtbl;
+} TB_ArchiveFileParser;
+
+// We do this to parse the header
+bool tb_archive_parse(TB_Slice file, TB_ArchiveFileParser* restrict out_parser);
+// After that we can enumerate any symbol entries to resolve imports
+size_t tb_archive_parse_entries(TB_ArchiveFileParser* restrict parser, size_t i, size_t count, TB_ArchiveEntry* out_entry);
+
+#endif // TB_OBJECT_H

+ 90 - 0
src/tilde/tb_x64.h

@@ -0,0 +1,90 @@
+#ifndef TB_X64_H
+#define TB_X64_H
+
+#include <stdint.h>
+#include <stdbool.h>
+
+typedef enum {
+    // uses xmm registers for the reg array
+    TB_X86_INSTR_XMMREG = (1u << 0u),
+
+    // r/m is a memory operand
+    TB_X86_INSTR_USE_MEMOP = (1u << 1u),
+
+    // r/m is a rip-relative address (TB_X86_INSTR_USE_MEMOP is always set when this is set)
+    TB_X86_INSTR_USE_RIPMEM = (1u << 2u),
+
+    // LOCK prefix is present
+    TB_X86_INSTR_LOCK = (1u << 3u),
+
+    // uses a signed immediate
+    TB_X86_INSTR_IMMEDIATE = (1u << 4u),
+
+    // absolute means it's using the 64bit immediate (cannot be applied while a memory operand is active)
+    TB_X86_INSTR_ABSOLUTE = (1u << 5u),
+
+    // set if the r/m can be found on the right hand side
+    TB_X86_INSTR_DIRECTION = (1u << 6u),
+
+    // uses the second data type because the instruction is weird like MOVSX or MOVZX
+    TB_X86_INSTR_TWO_DATA_TYPES = (1u << 7u)
+} TB_X86_InstFlags;
+
+typedef enum {
+    TB_X86_SEGMENT_DEFAULT = 0,
+
+    TB_X86_SEGMENT_ES, TB_X86_SEGMENT_CS,
+    TB_X86_SEGMENT_SS, TB_X86_SEGMENT_DS,
+    TB_X86_SEGMENT_GS, TB_X86_SEGMENT_FS,
+} TB_X86_Segment;
+
+typedef enum {
+    TB_X86_TYPE_NONE = 0,
+
+    TB_X86_TYPE_BYTE,    // 1
+    TB_X86_TYPE_WORD,    // 2
+    TB_X86_TYPE_DWORD,   // 4
+    TB_X86_TYPE_QWORD,   // 8
+
+    TB_X86_TYPE_PBYTE,   // int8 x 16 = 16
+    TB_X86_TYPE_PWORD,   // int16 x 8 = 16
+    TB_X86_TYPE_PDWORD,  // int32 x 4 = 16
+    TB_X86_TYPE_PQWORD,  // int64 x 2 = 16
+
+    TB_X86_TYPE_SSE_SS,  // float32 x 1 = 4
+    TB_X86_TYPE_SSE_SD,  // float64 x 1 = 8
+    TB_X86_TYPE_SSE_PS,  // float32 x 4 = 16
+    TB_X86_TYPE_SSE_PD,  // float64 x 2 = 16
+
+    TB_X86_TYPE_XMMWORD, // the generic idea of them
+} TB_X86_DataType;
+
+typedef struct {
+    int16_t type;
+
+    // registers (there's 4 max taking up 4bit slots each)
+    uint16_t regs;
+    uint8_t flags;
+
+    // bitpacking amirite
+    TB_X86_DataType data_type  : 4;
+    TB_X86_DataType data_type2 : 4;
+    TB_X86_Segment segment     : 4;
+    uint8_t length             : 4;
+
+    // memory operand
+    //   X86_INSTR_USE_MEMOP
+    int32_t disp;
+
+    // immediate operand
+    //   imm for INSTR_IMMEDIATE
+    //   abs for INSTR_ABSOLUTE
+    union {
+        int32_t  imm;
+        uint64_t abs;
+    };
+} TB_X86_Inst;
+
+TB_X86_Inst tb_x86_disasm(size_t length, const uint8_t data[length]);
+
+#endif /* TB_X64_H */

+ 443 - 0
src/tilde_builtin.cpp

@@ -0,0 +1,443 @@
+gb_internal cgValue cg_builtin_len(cgProcedure *p, cgValue value) {
+	Type *t = base_type(value.type);
+
+	switch (t->kind) {
+	case Type_Basic:
+		switch (t->Basic.kind) {
+		case Basic_string:
+			{
+				GB_ASSERT(value.kind == cgValue_Addr);
+				cgValue ptr = cg_value(value.node, alloc_type_pointer(value.type));
+				cgValue len_ptr = cg_emit_struct_ep(p, ptr, 1);
+				return cg_emit_load(p, len_ptr);
+			}
+		case Basic_cstring:
+			GB_PANIC("TODO(bill): len(cstring)");
+			break;
+		}
+		break;
+	case Type_Array:
+		return cg_const_int(p, t_int, t->Array.count);
+	case Type_EnumeratedArray:
+		return cg_const_int(p, t_int, t->EnumeratedArray.count);
+	case Type_Slice:
+		{
+			GB_ASSERT(value.kind == cgValue_Addr);
+			cgValue ptr = cg_value(value.node, alloc_type_pointer(value.type));
+			cgValue len_ptr = cg_emit_struct_ep(p, ptr, 1);
+			return cg_emit_load(p, len_ptr);
+		}
+	case Type_DynamicArray:
+		{
+			GB_ASSERT(value.kind == cgValue_Addr);
+			cgValue ptr = cg_value(value.node, alloc_type_pointer(value.type));
+			cgValue len_ptr = cg_emit_struct_ep(p, ptr, 1);
+			return cg_emit_load(p, len_ptr);
+		}
+	case Type_Map:
+		{
+			GB_ASSERT(value.kind == cgValue_Addr);
+			cgValue ptr = cg_value(value.node, alloc_type_pointer(value.type));
+			cgValue len_ptr = cg_emit_struct_ep(p, ptr, 1);
+			return cg_emit_conv(p, cg_emit_load(p, len_ptr), t_int);
+		}
+	case Type_Struct:
+		GB_ASSERT(is_type_soa_struct(t));
+		break;
+	}
+
+	GB_PANIC("TODO(bill): cg_builtin_len %s", type_to_string(t));
+	return {};
+}
+
+gb_internal cgValue cg_builtin_cap(cgProcedure *p, cgValue value) {
+	Type *t = base_type(value.type);
+
+	switch (t->kind) {
+	case Type_Basic:
+		switch (t->Basic.kind) {
+		case Basic_string:
+			{
+				GB_ASSERT(value.kind == cgValue_Addr);
+				cgValue ptr = cg_value(value.node, alloc_type_pointer(value.type));
+				cgValue len_ptr = cg_emit_struct_ep(p, ptr, 1);
+				return cg_emit_load(p, len_ptr);
+			}
+		case Basic_cstring:
+			GB_PANIC("TODO(bill): cap(cstring)");
+			break;
+		}
+		break;
+	case Type_Array:
+		return cg_const_int(p, t_int, t->Array.count);
+	case Type_EnumeratedArray:
+		return cg_const_int(p, t_int, t->EnumeratedArray.count);
+	case Type_Slice:
+		{
+			GB_ASSERT(value.kind == cgValue_Addr);
+			cgValue ptr = cg_value(value.node, alloc_type_pointer(value.type));
+			cgValue len_ptr = cg_emit_struct_ep(p, ptr, 1);
+			return cg_emit_load(p, len_ptr);
+		}
+	case Type_DynamicArray:
+		{
+			GB_ASSERT(value.kind == cgValue_Addr);
+			cgValue ptr = cg_value(value.node, alloc_type_pointer(value.type));
+			cgValue len_ptr = cg_emit_struct_ep(p, ptr, 2);
+			return cg_emit_load(p, len_ptr);
+		}
+	case Type_Map:
+		{
+			TB_DataType dt_uintptr = cg_data_type(t_uintptr);
+			TB_Node *zero = tb_inst_uint(p->func, dt_uintptr, 0);
+			TB_Node *one  = tb_inst_uint(p->func, dt_uintptr, 0);
+			TB_Node *mask = tb_inst_uint(p->func, dt_uintptr, MAP_CACHE_LINE_SIZE-1);
+
+			TB_Node *data = cg_emit_struct_ev(p, value, 0).node;
+			TB_Node *log2_cap = tb_inst_and(p->func, data, mask);
+			TB_Node *cap = tb_inst_shl(p->func, one, log2_cap, cast(TB_ArithmeticBehavior)0);
+			TB_Node *cmp = tb_inst_cmp_eq(p->func, data, zero);
+
+			cgValue res = cg_value(tb_inst_select(p->func, cmp, zero, cap), t_uintptr);
+			return cg_emit_conv(p, res, t_int);
+		}
+	case Type_Struct:
+		GB_ASSERT(is_type_soa_struct(t));
+		break;
+	}
+
+	GB_PANIC("TODO(bill): cg_builtin_cap %s", type_to_string(t));
+	return {};
+}
+
+
+gb_internal cgValue cg_builtin_raw_data(cgProcedure *p, cgValue const &value) {
+	Type *t = base_type(value.type);
+	cgValue res = {};
+	switch (t->kind) {
+	case Type_Slice:
+		{
+			GB_ASSERT(value.kind == cgValue_Addr);
+			cgValue ptr = cg_value(value.node, alloc_type_pointer(value.type));
+			cgValue data_ptr = cg_emit_struct_ep(p, ptr, 0);
+			res = cg_emit_load(p, data_ptr);
+			GB_ASSERT(is_type_multi_pointer(res.type));
+		}
+		break;
+	case Type_DynamicArray:
+		{
+			GB_ASSERT(value.kind == cgValue_Addr);
+			cgValue ptr = cg_value(value.node, alloc_type_pointer(value.type));
+			cgValue data_ptr = cg_emit_struct_ep(p, ptr, 0);
+			res = cg_emit_load(p, data_ptr);
+		}
+		break;
+	case Type_Basic:
+		if (t->Basic.kind == Basic_string) {
+			GB_ASSERT(value.kind == cgValue_Addr);
+			cgValue ptr = cg_value(value.node, alloc_type_pointer(value.type));
+			cgValue data_ptr = cg_emit_struct_ep(p, ptr, 0);
+			res = cg_emit_load(p, data_ptr);
+		} else if (t->Basic.kind == Basic_cstring) {
+			res = cg_emit_conv(p, value, t_u8_multi_ptr);
+		}
+		break;
+	case Type_Pointer:
+		GB_ASSERT(is_type_array_like(t->Pointer.elem));
+		GB_ASSERT(value.kind == cgValue_Value);
+		res = cg_value(value.node, alloc_type_multi_pointer(base_array_type(t->Pointer.elem)));
+		break;
+	case Type_MultiPointer:
+
+		GB_PANIC("TODO(bill) %s", type_to_string(value.type));
+		// res = cg_emit_conv(p, value, tv.type);
+		break;
+	}
+	GB_ASSERT(res.node != nullptr);
+	return res;
+}
+
+gb_internal cgValue cg_builtin_min(cgProcedure *p, Type *t, cgValue x, cgValue y) {
+	x = cg_emit_conv(p, x, t);
+	y = cg_emit_conv(p, y, t);
+	return cg_emit_select(p, cg_emit_comp(p, Token_Lt, x, y), x, y);
+}
+gb_internal cgValue cg_builtin_max(cgProcedure *p, Type *t, cgValue x, cgValue y) {
+	x = cg_emit_conv(p, x, t);
+	y = cg_emit_conv(p, y, t);
+	return cg_emit_select(p, cg_emit_comp(p, Token_Gt, x, y), x, y);
+}
+
+gb_internal cgValue cg_builtin_abs(cgProcedure *p, cgValue const &x) {
+	if (is_type_unsigned(x.type)) {
+		return x;
+	}
+
+	if (is_type_quaternion(x.type)) {
+		GB_PANIC("TODO(bill): abs quaternion");
+	} else if (is_type_complex(x.type)) {
+		GB_PANIC("TODO(bill): abs complex");
+	}
+
+	TB_DataType dt = cg_data_type(x.type);
+	GB_ASSERT(!TB_IS_VOID_TYPE(dt));
+	TB_Node *zero = nullptr;
+	if (dt.type == TB_FLOAT) {
+		if (dt.data == 32) {
+			zero = tb_inst_float32(p->func, 0);
+		} else if (dt.data == 64) {
+			zero = tb_inst_float64(p->func, 0);
+		}
+	} else {
+		zero = tb_inst_uint(p->func, dt, 0);
+	}
+	GB_ASSERT(zero != nullptr);
+
+	cgValue cond = cg_emit_comp(p, Token_Lt, x, cg_value(zero, x.type));
+	cgValue neg = cg_emit_unary_arith(p, Token_Sub, x, x.type);
+	return cg_emit_select(p, cond, neg, x);
+}
+
+gb_internal cgValue cg_builtin_clamp(cgProcedure *p, Type *t, cgValue const &x, cgValue const &min, cgValue const &max) {
+	cgValue z = x;
+	z = cg_builtin_max(p, t, z, min);
+	z = cg_builtin_min(p, t, z, max);
+	return z;
+}
+
+
+
+gb_internal cgValue cg_builtin_mem_zero(cgProcedure *p, cgValue const &ptr, cgValue const &len) {
+	GB_ASSERT(ptr.kind == cgValue_Value);
+	GB_ASSERT(len.kind == cgValue_Value);
+	tb_inst_memzero(p->func, ptr.node, len.node, 1, false);
+	return ptr;
+}
+
+gb_internal cgValue cg_builtin_mem_copy(cgProcedure *p, cgValue const &dst, cgValue const &src, cgValue const &len) {
+	GB_ASSERT(dst.kind == cgValue_Value);
+	GB_ASSERT(src.kind == cgValue_Value);
+	GB_ASSERT(len.kind == cgValue_Value);
+	// TODO(bill): This needs to be memmove
+	tb_inst_memcpy(p->func, dst.node, src.node, len.node, 1, false);
+	return dst;
+}
+
+gb_internal cgValue cg_builtin_mem_copy_non_overlapping(cgProcedure *p, cgValue const &dst, cgValue const &src, cgValue const &len) {
+	GB_ASSERT(dst.kind == cgValue_Value);
+	GB_ASSERT(src.kind == cgValue_Value);
+	GB_ASSERT(len.kind == cgValue_Value);
+	tb_inst_memcpy(p->func, dst.node, src.node, len.node, 1, false);
+	return dst;
+}
+
+
+gb_internal cgValue cg_build_builtin(cgProcedure *p, BuiltinProcId id, Ast *expr) {
+	ast_node(ce, CallExpr, expr);
+
+	if (BuiltinProc__simd_begin < id && id < BuiltinProc__simd_end) {
+		GB_PANIC("TODO(bill): cg_build_builtin_simd_proc");
+		// return cg_build_builtin_simd_proc(p, expr, tv, id);
+	}
+
+	String builtin_name = builtin_procs[id].name;
+
+	switch (id) {
+	case BuiltinProc_DIRECTIVE: {
+		ast_node(bd, BasicDirective, ce->proc);
+		String name = bd->name.string;
+		GB_ASSERT(name == "location");
+		String procedure = p->entity->token.string;
+		TokenPos pos = ast_token(ce->proc).pos;
+		if (ce->args.count > 0) {
+			Ast *ident = unselector_expr(ce->args[0]);
+			GB_ASSERT(ident->kind == Ast_Ident);
+			Entity *e = entity_of_node(ident);
+			GB_ASSERT(e != nullptr);
+
+			if (e->parent_proc_decl != nullptr && e->parent_proc_decl->entity != nullptr) {
+				procedure = e->parent_proc_decl->entity->token.string;
+			} else {
+				procedure = str_lit("");
+			}
+			pos = e->token.pos;
+
+		}
+		return cg_emit_source_code_location_as_global(p, procedure, pos);
+	} break;
+
+	case BuiltinProc_len: {
+		cgValue v = cg_build_expr(p, ce->args[0]);
+		Type *t = base_type(v.type);
+		if (is_type_pointer(t)) {
+			// IMPORTANT TODO(bill): Should there be a nil pointer check?
+			v = cg_emit_load(p, v);
+			t = type_deref(t);
+		}
+		return cg_builtin_len(p, v);
+	}
+
+	case BuiltinProc_cap: {
+		cgValue v = cg_build_expr(p, ce->args[0]);
+		Type *t = base_type(v.type);
+		if (is_type_pointer(t)) {
+			// IMPORTANT TODO(bill): Should there be a nil pointer check?
+			v = cg_emit_load(p, v);
+			t = type_deref(t);
+		}
+		return cg_builtin_cap(p, v);
+	}
+
+	case BuiltinProc_raw_data:
+		{
+			cgValue v = cg_build_expr(p, ce->args[0]);
+			return cg_builtin_raw_data(p, v);
+		}
+
+	case BuiltinProc_min:
+		if (ce->args.count == 2) {
+			Type *t = type_of_expr(expr);
+			cgValue x = cg_build_expr(p, ce->args[0]);
+			cgValue y = cg_build_expr(p, ce->args[1]);
+			return cg_builtin_min(p, t, x, y);
+		} else {
+			Type *t = type_of_expr(expr);
+			cgValue x = cg_build_expr(p, ce->args[0]);
+			for (isize i = 1; i < ce->args.count; i++) {
+				cgValue y = cg_build_expr(p, ce->args[i]);
+				x = cg_builtin_min(p, t, x, y);
+			}
+			return x;
+		}
+		break;
+	case BuiltinProc_max:
+		if (ce->args.count == 2) {
+			Type *t = type_of_expr(expr);
+			cgValue x = cg_build_expr(p, ce->args[0]);
+			cgValue y = cg_build_expr(p, ce->args[1]);
+			return cg_builtin_max(p, t, x, y);
+		} else {
+			Type *t = type_of_expr(expr);
+			cgValue x = cg_build_expr(p, ce->args[0]);
+			for (isize i = 1; i < ce->args.count; i++) {
+				cgValue y = cg_build_expr(p, ce->args[i]);
+				x = cg_builtin_max(p, t, x, y);
+			}
+			return x;
+		}
+		break;
+
+	case BuiltinProc_abs:
+		{
+			cgValue x = cg_build_expr(p, ce->args[0]);
+			return cg_builtin_abs(p, x);
+		}
+
+	case BuiltinProc_clamp:
+		{
+			cgValue x   = cg_build_expr(p, ce->args[0]);
+			cgValue min = cg_build_expr(p, ce->args[1]);
+			cgValue max = cg_build_expr(p, ce->args[2]);
+			return cg_builtin_clamp(p, type_of_expr(expr), x, min, max);
+		}
+
+	case BuiltinProc_debug_trap:
+		tb_inst_debugbreak(p->func);
+		return {};
+	case BuiltinProc_trap:
+		tb_inst_trap(p->func);
+		return {};
+
+	case BuiltinProc_mem_zero:
+		{
+			cgValue ptr = cg_build_expr(p, ce->args[0]);
+			cgValue len = cg_build_expr(p, ce->args[1]);
+			return cg_builtin_mem_zero(p, ptr, len);
+		}
+
+	case BuiltinProc_mem_copy:
+		{
+			cgValue dst = cg_build_expr(p, ce->args[0]);
+			cgValue src = cg_build_expr(p, ce->args[1]);
+			cgValue len = cg_build_expr(p, ce->args[2]);
+			return cg_builtin_mem_copy(p, dst, src, len);
+		}
+
+	case BuiltinProc_mem_copy_non_overlapping:
+		{
+			cgValue dst = cg_build_expr(p, ce->args[0]);
+			cgValue src = cg_build_expr(p, ce->args[1]);
+			cgValue len = cg_build_expr(p, ce->args[2]);
+			return cg_builtin_mem_copy_non_overlapping(p, dst, src, len);
+		}
+
+
+	case BuiltinProc_overflow_add:
+		{
+			Type *res_type = type_of_expr(expr);
+			GB_ASSERT(res_type->kind == Type_Tuple);
+			GB_ASSERT(res_type->Tuple.variables.count == 2);
+			// TODO(bill): do a proper overflow add
+			Type *type = res_type->Tuple.variables[0]->type;
+			Type *ok_type = res_type->Tuple.variables[1]->type;
+			cgValue x = cg_build_expr(p, ce->args[0]);
+			cgValue y = cg_build_expr(p, ce->args[1]);
+			x = cg_emit_conv(p, x, type);
+			y = cg_emit_conv(p, y, type);
+			cgValue res = cg_emit_arith(p, Token_Add, x, y, type);
+			cgValue ok  = cg_const_int(p, ok_type, false);
+
+			return cg_value_multi2(res, ok, res_type);
+		}
+
+
+	case BuiltinProc_ptr_offset:
+		{
+			cgValue ptr = cg_build_expr(p, ce->args[0]);
+			cgValue len = cg_build_expr(p, ce->args[1]);
+			len = cg_emit_conv(p, len, t_int);
+			return cg_emit_ptr_offset(p, ptr, len);
+		}
+	case BuiltinProc_ptr_sub:
+		{
+			Type *elem0 = type_deref(type_of_expr(ce->args[0]));
+			Type *elem1 = type_deref(type_of_expr(ce->args[1]));
+			GB_ASSERT(are_types_identical(elem0, elem1));
+			Type *elem = elem0;
+
+			cgValue ptr0 = cg_emit_conv(p, cg_build_expr(p, ce->args[0]), t_uintptr);
+			cgValue ptr1 = cg_emit_conv(p, cg_build_expr(p, ce->args[1]), t_uintptr);
+
+			cgValue diff = cg_emit_arith(p, Token_Sub, ptr0, ptr1, t_uintptr);
+			diff = cg_emit_conv(p, diff, t_int);
+			return cg_emit_arith(p, Token_Quo, diff, cg_const_int(p, t_int, type_size_of(elem)), t_int);
+		}
+
+	case BuiltinProc_type_info_of:
+		{
+			Ast *arg = ce->args[0];
+			TypeAndValue tav = type_and_value_of_expr(arg);
+			if (tav.mode == Addressing_Type) {
+				Type *t = default_type(type_of_expr(arg));
+				return cg_type_info(p, t);
+			}
+			GB_ASSERT(is_type_typeid(tav.type));
+
+			auto args = slice_make<cgValue>(permanent_allocator(), 1);
+			args[0] = cg_build_expr(p, arg);
+			return cg_emit_runtime_call(p, "__type_info_of", args);
+		}
+
+
+	case BuiltinProc_type_equal_proc:
+		return cg_equal_proc_value_for_type(p, ce->args[0]->tav.type);
+
+	case BuiltinProc_type_hasher_proc:
+		return cg_hasher_proc_value_for_type(p, ce->args[0]->tav.type);
+	}
+
+
+	GB_PANIC("TODO(bill): builtin procs %d %.*s", id, LIT(builtin_name));
+	return {};
+}
+

+ 1040 - 0
src/tilde_const.cpp

@@ -0,0 +1,1040 @@
+gb_internal bool cg_is_expr_constant_zero(Ast *expr) {
+	GB_ASSERT(expr != nullptr);
+	auto v = exact_value_to_integer(expr->tav.value);
+	if (v.kind == ExactValue_Integer) {
+		return big_int_cmp_zero(&v.value_integer) == 0;
+	}
+	return false;
+}
+
+gb_internal cgValue cg_const_nil(cgModule *m, cgProcedure *p, Type *type) {
+	GB_ASSERT(m != nullptr);
+	Type *original_type = type;
+	type = core_type(type);
+	i64 size = type_size_of(type);
+	i64 align = type_align_of(type);
+	TB_DataType dt = cg_data_type(type);
+	if (TB_IS_VOID_TYPE(dt)) {
+		char name[32] = {};
+		gb_snprintf(name, 31, "cnil$%u", 1+m->const_nil_guid.fetch_add(1));
+		TB_Global *global = tb_global_create(m->mod, -1, name, cg_debug_type(m, type), TB_LINKAGE_PRIVATE);
+		tb_global_set_storage(m->mod, tb_module_get_rdata(m->mod), global, size, align, 0);
+
+		TB_Symbol *symbol = cast(TB_Symbol *)global;
+		if (p) {
+			TB_Node *node = tb_inst_get_symbol_address(p->func, symbol);
+			return cg_lvalue_addr(node, type);
+		} else {
+			return cg_value(symbol, type);
+		}
+	}
+
+	if (is_type_internally_pointer_like(type)) {
+		return cg_value(tb_inst_uint(p->func, dt, 0), type);
+	} else if (is_type_integer(type) || is_type_boolean(type) || is_type_bit_set(type) || is_type_typeid(type)) {
+		return cg_value(tb_inst_uint(p->func, dt, 0), type);
+	} else if (is_type_float(type)) {
+		switch (size) {
+		case 2:
+			return cg_value(tb_inst_uint(p->func, dt, 0), type);
+		case 4:
+			return cg_value(tb_inst_float32(p->func, 0), type);
+		case 8:
+			return cg_value(tb_inst_float64(p->func, 0), type);
+		}
+	}
+	GB_PANIC("TODO(bill): cg_const_nil %s", type_to_string(original_type));
+	return {};
+}
+
+gb_internal cgValue cg_const_nil(cgProcedure *p, Type *type) {
+	return cg_const_nil(p->module, p, type);
+}
+
+gb_internal TB_Global *cg_global_const_string(cgModule *m, String const &str, Type *type, TB_Global *global, i64 offset);
+gb_internal void cg_write_int_at_ptr(void *dst, i64 i, Type *original_type);
+
+gb_internal void cg_global_source_code_location_const(cgModule *m, String const &proc_name, TokenPos pos, TB_Global *global, i64 offset) {
+	// Source_Code_Location :: struct {
+	// 	file_path:    string,
+	// 	line, column: i32,
+	// 	procedure:    string,
+	// }
+
+	i64 file_path_offset = type_offset_of(t_source_code_location, 0);
+	i64 line_offset      = type_offset_of(t_source_code_location, 1);
+	i64 column_offset    = type_offset_of(t_source_code_location, 2);
+	i64 procedure_offset = type_offset_of(t_source_code_location, 3);
+
+	String file_path = get_file_path_string(pos.file_id);
+	if (file_path.len != 0) {
+		cg_global_const_string(m, file_path, t_string, global, offset+file_path_offset);
+	}
+
+	void *line_ptr   = tb_global_add_region(m->mod, global, offset+line_offset,   4);
+	void *column_ptr = tb_global_add_region(m->mod, global, offset+column_offset, 4);
+	cg_write_int_at_ptr(line_ptr,   pos.line,   t_i32);
+	cg_write_int_at_ptr(column_ptr, pos.column, t_i32);
+
+	if (proc_name.len != 0) {
+		cg_global_const_string(m, proc_name, t_string, global, offset+procedure_offset);
+	}
+}
+
+
+gb_internal cgValue cg_emit_source_code_location_as_global(cgProcedure *p, String const &proc_name, TokenPos pos) {
+	cgModule *m = p->module;
+	char name[32] = {};
+	gb_snprintf(name, 31, "scl$%u", 1+m->const_nil_guid.fetch_add(1));
+
+	TB_Global *global = tb_global_create(m->mod, -1, name, cg_debug_type(m, t_source_code_location), TB_LINKAGE_PRIVATE);
+	tb_global_set_storage(m->mod, tb_module_get_rdata(m->mod), global, type_size_of(t_source_code_location), type_align_of(t_source_code_location), 6);
+
+	cg_global_source_code_location_const(m, proc_name, pos, global, 0);
+
+	TB_Node *ptr = tb_inst_get_symbol_address(p->func, cast(TB_Symbol *)global);
+	return cg_lvalue_addr(ptr, t_source_code_location);
+}
+
+
+
+gb_internal void cg_write_big_int_at_ptr(void *dst, BigInt const *a, Type *original_type) {
+	GB_ASSERT(build_context.endian_kind == TargetEndian_Little);
+	size_t sz = cast(size_t)type_size_of(original_type);
+	if (big_int_is_zero(a)) {
+		gb_memset(dst, 0, sz);
+		return;
+	}
+	u64 rop64[4] = {}; // 2 u64 is the maximum we will ever need, so doubling it will be fine :P
+	u8 *rop = cast(u8 *)rop64;
+
+	size_t max_count = 0;
+	size_t written = 0;
+	size_t size = 1;
+	size_t nails = 0;
+	mp_endian endian = MP_LITTLE_ENDIAN;
+
+	max_count = mp_pack_count(a, nails, size);
+	if (sz < max_count) {
+		debug_print_big_int(a);
+		gb_printf_err("%s -> %tu\n", type_to_string(original_type), sz);;
+	}
+	GB_ASSERT_MSG(sz >= max_count, "max_count: %tu, sz: %tu, written: %tu, type %s", max_count, sz, written, type_to_string(original_type));
+	GB_ASSERT(gb_size_of(rop64) >= sz);
+
+	mp_err err = mp_pack(rop, sz, &written,
+	                     MP_LSB_FIRST,
+	                     size, endian, nails,
+	                     a);
+	GB_ASSERT(err == MP_OKAY);
+
+	if (!is_type_endian_little(original_type)) {
+		for (size_t i = 0; i < sz/2; i++) {
+			u8 tmp = rop[i];
+			rop[i] = rop[sz-1-i];
+			rop[sz-1-i] = tmp;
+		}
+	}
+
+	gb_memcopy(dst, rop, sz);
+	return;
+}
+
+
+gb_internal void cg_write_int_at_ptr(void *dst, i64 i, Type *original_type) {
+	ExactValue v = exact_value_i64(i);
+	cg_write_big_int_at_ptr(dst, &v.value_integer, original_type);
+}
+gb_internal void cg_write_uint_at_ptr(void *dst, u64 i, Type *original_type) {
+	ExactValue v = exact_value_u64(i);
+	cg_write_big_int_at_ptr(dst, &v.value_integer, original_type);
+}
+
+gb_internal TB_Global *cg_global_const_string(cgModule *m, String const &str, Type *type, TB_Global *global, i64 offset) {
+	GB_ASSERT(is_type_string(type));
+
+	char name[32] = {};
+	gb_snprintf(name, 31, "csb$%u", 1+m->const_nil_guid.fetch_add(1));
+	TB_Global *str_global = tb_global_create(m->mod, -1, name, nullptr, TB_LINKAGE_PRIVATE);
+	i64 size = str.len+1;
+	tb_global_set_storage(m->mod, tb_module_get_rdata(m->mod), str_global, size, 1, 1);
+	u8 *data = cast(u8 *)tb_global_add_region(m->mod, str_global, 0, size);
+	gb_memcopy(data, str.text, str.len);
+	data[str.len] = 0;
+
+	if (is_type_cstring(type)) {
+		if (global) {
+			tb_global_add_symbol_reloc(m->mod, global, offset+0, cast(TB_Symbol *)str_global);
+		}
+		return str_global;
+	}
+
+	if (global == nullptr) {
+		gb_snprintf(name, 31, "cstr$%u", 1+m->const_nil_guid.fetch_add(1));
+		global = tb_global_create(m->mod, -1, name, cg_debug_type(m, type), TB_LINKAGE_PRIVATE);
+		tb_global_set_storage(m->mod, tb_module_get_rdata(m->mod), global, type_size_of(type), type_align_of(type), 2);
+	}
+
+	tb_global_add_symbol_reloc(m->mod, global, offset+0, cast(TB_Symbol *)str_global);
+	void *len_ptr = tb_global_add_region(m->mod, global, offset+build_context.int_size, build_context.int_size);
+	cg_write_int_at_ptr(len_ptr, str.len, t_int);
+
+	return global;
+}
+
+gb_internal bool cg_elem_type_can_be_constant(Type *t) {
+	t = base_type(t);
+	if (t == t_invalid) {
+		return false;
+	}
+	if (is_type_dynamic_array(t) || is_type_map(t)) {
+		return false;
+	}
+	return true;
+}
+
+
+gb_internal bool cg_is_elem_const(Ast *elem, Type *elem_type) {
+	if (!cg_elem_type_can_be_constant(elem_type)) {
+		return false;
+	}
+	if (elem->kind == Ast_FieldValue) {
+		elem = elem->FieldValue.value;
+	}
+	TypeAndValue tav = type_and_value_of_expr(elem);
+	GB_ASSERT_MSG(tav.mode != Addressing_Invalid, "%s %s", expr_to_string(elem), type_to_string(tav.type));
+	return tav.value.kind != ExactValue_Invalid;
+}
+
+gb_internal bool cg_is_nested_possibly_constant(Type *ft, Selection const &sel, Ast *elem) {
+	GB_ASSERT(!sel.indirect);
+	for (i32 index : sel.index) {
+		Type *bt = base_type(ft);
+		switch (bt->kind) {
+		case Type_Struct:
+			// if (bt->Struct.is_raw_union) {
+				// return false;
+			// }
+			ft = bt->Struct.fields[index]->type;
+			break;
+		case Type_Array:
+			ft = bt->Array.elem;
+			break;
+		default:
+			return false;
+		}
+	}
+	return cg_is_elem_const(elem, ft);
+}
+
+gb_internal i64 cg_global_const_calculate_region_count_from_basic_type(Type *type) {
+	type = core_type(type);
+
+	switch (type->kind) {
+	case Type_Basic:
+		switch (type->Basic.kind) {
+		case Basic_string:  // ^u8 + int
+		case Basic_any:     // rawptr + typeid
+			return 2;
+		}
+		return 1;
+	case Type_Pointer:
+	case Type_MultiPointer:
+		return 2; // allows for offsets
+	case Type_Proc:
+		return 1;
+	case Type_Slice:
+		return 3; // alows for offsets
+	case Type_DynamicArray:
+		return 5;
+	case Type_Map:
+		return 4;
+
+	case Type_Enum:
+	case Type_BitSet:
+		return 1;
+
+	case Type_RelativePointer:
+	case Type_RelativeMultiPointer:
+		return 2; // allows for offsets
+
+	case Type_Matrix:
+		return 1;
+
+	case Type_Array:
+		{
+			Type *elem = type->Array.elem;
+			i64 count = cg_global_const_calculate_region_count_from_basic_type(elem);
+			return count*type->Array.count;
+		}
+	case Type_EnumeratedArray:
+		{
+			Type *elem = type->EnumeratedArray.elem;
+			i64 count = cg_global_const_calculate_region_count_from_basic_type(elem);
+			return count*type->EnumeratedArray.count;
+		}
+
+	case Type_Struct:
+		if (type->Struct.is_raw_union) {
+			i64 max_count = 0;
+			for (Entity *f : type->Struct.fields) {
+				i64 count = cg_global_const_calculate_region_count_from_basic_type(f->type);
+				max_count = gb_max(count, max_count);
+			}
+			return max_count;
+		} else {
+			i64 max_count = 0;
+			for (Entity *f : type->Struct.fields) {
+				max_count += cg_global_const_calculate_region_count_from_basic_type(f->type);
+			}
+			return max_count;
+		}
+		break;
+	case Type_Union:
+		{
+			i64 max_count = 0;
+			for (Type *t : type->Union.variants) {
+				i64 count = cg_global_const_calculate_region_count_from_basic_type(t);
+				max_count = gb_max(count, max_count);
+			}
+			return max_count+1;
+		}
+		break;
+
+	default:
+		GB_PANIC("TODO(bill): %s", type_to_string(type));
+		break;
+	}
+	return -1;
+}
+gb_internal isize cg_global_const_calculate_region_count(ExactValue const &value, Type *type) {
+	Type *bt = base_type(type);
+	if (is_type_array(type) && value.kind == ExactValue_String && !is_type_u8(core_array_type(type))) {
+		if (is_type_rune_array(type)) {
+			return 1;
+		}
+
+		Type *et = base_array_type(type);
+		i64 base_count = 2;
+		if (is_type_cstring(et)) {
+			base_count = 1;
+		}
+		return base_count * bt->Array.count;
+	} else if (is_type_u8_array(type) && value.kind == ExactValue_String) {
+		return 1;
+	} else if (is_type_array(type) &&
+		value.kind != ExactValue_Invalid &&
+		value.kind != ExactValue_String &&
+		value.kind != ExactValue_Compound) {
+		Type *elem = type->Array.elem;
+
+		i64 base_count = cg_global_const_calculate_region_count(value, elem);
+		return base_count * type->Array.count;
+	} else if (is_type_matrix(type) &&
+		value.kind != ExactValue_Invalid &&
+		value.kind != ExactValue_Compound) {
+		return 1;
+	} else if (is_type_simd_vector(type) &&
+		value.kind != ExactValue_Invalid &&
+		value.kind != ExactValue_Compound) {
+		return 1;
+	}
+
+	isize count = 0;
+	switch (value.kind) {
+	case ExactValue_Invalid:
+		return 0;
+	case ExactValue_Bool:
+	case ExactValue_Integer:
+	case ExactValue_Float:
+	case ExactValue_Typeid:
+	case ExactValue_Complex:
+	case ExactValue_Quaternion:
+		return 1;
+	case ExactValue_Pointer:
+		return 2;
+
+	case ExactValue_Procedure:
+		return 1;
+
+	case ExactValue_String:
+		if (is_type_string(type)) {
+			return 3;
+		} else if (is_type_cstring(type) || is_type_array_like(type)) {
+			return 2;
+		}
+		return 3;
+
+	case ExactValue_Compound: {
+		ast_node(cl, CompoundLit, value.value_compound);
+		Type *bt = base_type(type);
+		switch (bt->kind) {
+		case Type_Struct:
+			if (cl->elems[0]->kind == Ast_FieldValue) {
+				for (isize i = 0; i < cl->elems.count; i++) {
+					ast_node(fv, FieldValue, cl->elems[i]);
+					String name = fv->field->Ident.token.string;
+
+					Selection sel = lookup_field(type, name, false);
+					GB_ASSERT(!sel.indirect);
+
+					Entity *f = bt->Struct.fields[sel.index[0]];
+
+					if (!cg_elem_type_can_be_constant(f->type)) {
+						continue;
+					}
+
+					if (sel.index.count == 1) {
+						count += cg_global_const_calculate_region_count(fv->value->tav.value, f->type);
+					} else {
+						count += 1; // just in case
+						if (cg_is_nested_possibly_constant(type, sel, fv->value)) {
+							Type *cv_type = sel.entity->type;
+							count += cg_global_const_calculate_region_count(fv->value->tav.value, cv_type);
+						}
+					}
+				}
+			} else {
+				for_array(i, cl->elems) {
+					i64 field_index = i;
+					Ast *elem = cl->elems[i];
+					TypeAndValue tav = elem->tav;
+					Entity *f = bt->Struct.fields[field_index];
+					if (!cg_elem_type_can_be_constant(f->type)) {
+						continue;
+					}
+
+					ExactValue value = {};
+					if (tav.mode != Addressing_Invalid) {
+						value = tav.value;
+					}
+					count += cg_global_const_calculate_region_count(value, type);
+				}
+			}
+			break;
+		case Type_Array:
+		case Type_EnumeratedArray:
+		case Type_SimdVector: {
+			Type *et = base_array_type(bt);
+			if (!cg_elem_type_can_be_constant(et)) {
+				break;
+			}
+			for (Ast *elem : cl->elems) {
+				if (elem->kind == Ast_FieldValue) {
+					ast_node(fv, FieldValue, elem);
+					ExactValue const &value = elem->FieldValue.value->tav.value;
+					if (is_ast_range(fv->field)) {
+						ast_node(ie, BinaryExpr, fv->field);
+						TypeAndValue lo_tav = ie->left->tav;
+						TypeAndValue hi_tav = ie->right->tav;
+						GB_ASSERT(lo_tav.mode == Addressing_Constant);
+						GB_ASSERT(hi_tav.mode == Addressing_Constant);
+
+						TokenKind op = ie->op.kind;
+						i64 lo = exact_value_to_i64(lo_tav.value);
+						i64 hi = exact_value_to_i64(hi_tav.value);
+						if (op != Token_RangeHalf) {
+							hi += 1;
+						}
+
+						for (i64 i = lo; i < hi; i++) {
+							count += cg_global_const_calculate_region_count(value, et);
+						}
+					} else {
+						count += cg_global_const_calculate_region_count(value, et);
+					}
+				} else {
+					ExactValue const &value = elem->tav.value;
+					count += cg_global_const_calculate_region_count(value, et);
+				}
+			}
+		} break;
+
+		case Type_BitSet:
+			return 1;
+		case Type_Matrix:
+			return 1;
+
+		case Type_Slice:
+			return 3;
+
+		default:
+			GB_PANIC("TODO(bill): %s", type_to_string(type));
+			break;
+		}
+	}break;
+	}
+	return count;
+}
+
+gb_internal TB_Global *cg_global_const_comp_literal(cgModule *m, Type *type, ExactValue const &value, TB_Global *global, i64 base_offset);
+
+gb_internal bool cg_global_const_add_region(cgModule *m, ExactValue const &value, Type *type, TB_Global *global, i64 offset) {
+	GB_ASSERT(is_type_endian_little(type));
+	GB_ASSERT(!is_type_different_to_arch_endianness(type));
+
+	GB_ASSERT(global != nullptr);
+
+	Type *bt = base_type(type);
+	i64 size = type_size_of(type);
+	if (value.kind == ExactValue_Invalid) {
+		return false;
+	}
+	if (is_type_array(type) && value.kind == ExactValue_String && !is_type_u8(core_array_type(type))) {
+		if (is_type_rune_array(type)) {
+			i64 count = type->Array.count;
+			Rune rune;
+			isize rune_offset = 0;
+			isize width = 1;
+			String s = value.value_string;
+
+			Rune *runes = cast(Rune *)tb_global_add_region(m->mod, global, offset, count*4);
+
+			for (i64 i = 0; i < count && rune_offset < s.len; i++) {
+				width = utf8_decode(s.text+rune_offset, s.len-rune_offset, &rune);
+				runes[i] = rune;
+				rune_offset += width;
+
+			}
+			GB_ASSERT(offset == s.len);
+			return true;
+		}
+		Type *et = bt->Array.elem;
+		i64 elem_size = type_size_of(et);
+
+		for (i64 i = 0; i < bt->Array.count; i++) {
+			cg_global_const_add_region(m, value, et, global, offset+(i * elem_size));
+		}
+		return true;
+	} else if (is_type_u8_array(type) && value.kind == ExactValue_String) {
+		u8 *dst = cast(u8 *)tb_global_add_region(m->mod, global, offset, size);
+		gb_memcopy(dst, value.value_string.text, gb_min(value.value_string.len, size));
+		return true;
+	} else if (is_type_array(type) &&
+		value.kind != ExactValue_Invalid &&
+		value.kind != ExactValue_String &&
+		value.kind != ExactValue_Compound) {
+
+		Type *et = bt->Array.elem;
+		i64 elem_size = type_size_of(et);
+
+		for (i64 i = 0; i < bt->Array.count; i++) {
+			cg_global_const_add_region(m, value, et, global, offset+(i * elem_size));
+		}
+
+		return true;
+	} else if (is_type_matrix(type) &&
+		value.kind != ExactValue_Invalid &&
+		value.kind != ExactValue_Compound) {
+		GB_PANIC("TODO(bill): matrices");
+
+		i64 row = bt->Matrix.row_count;
+		i64 column = bt->Matrix.column_count;
+		GB_ASSERT(row == column);
+
+		Type *elem = bt->Matrix.elem;
+
+		i64 elem_size = type_size_of(elem);
+		gb_unused(elem_size);
+
+		// 1 region in memory, not many
+
+		return true;
+	} else if (is_type_simd_vector(type) &&
+		value.kind != ExactValue_Invalid &&
+		value.kind != ExactValue_Compound) {
+
+		GB_PANIC("TODO(bill): #simd vectors");
+
+		Type *et = type->SimdVector.elem;
+		i64 elem_size = type_size_of(et);
+		gb_unused(elem_size);
+
+		// 1 region in memory, not many
+
+		return true;
+	}
+
+
+	switch (value.kind) {
+	case ExactValue_Bool:
+		{
+			GB_ASSERT_MSG(!is_type_array_like(bt), "%s", type_to_string(type));
+			bool *res = cast(bool *)tb_global_add_region(m->mod, global, offset, size);
+			*res = !!value.value_bool;
+		}
+		break;
+
+	case ExactValue_Integer:
+		{
+			GB_ASSERT_MSG(!is_type_array_like(bt), "%s", type_to_string(type));
+			void *res = tb_global_add_region(m->mod, global, offset, size);
+			cg_write_big_int_at_ptr(res, &value.value_integer, type);
+		}
+		break;
+
+	case ExactValue_Float:
+		{
+			GB_ASSERT_MSG(!is_type_array_like(bt), "%s", type_to_string(type));
+			f64 f = exact_value_to_f64(value);
+			void *res = tb_global_add_region(m->mod, global, offset, size);
+			switch (size) {
+			case 2: *(u16 *)res = f32_to_f16(cast(f32)f); break;
+			case 4: *(f32 *)res = cast(f32)f;             break;
+			case 8: *(f64 *)res = cast(f64)f;             break;
+			}
+		}
+		break;
+
+	case ExactValue_Pointer:
+		{
+			GB_ASSERT_MSG(!is_type_array_like(bt), "%s", type_to_string(type));
+			void *res = tb_global_add_region(m->mod, global, offset, size);
+			*(u64 *)res = exact_value_to_u64(value);
+		}
+		break;
+
+	case ExactValue_String:
+		if (is_type_array_like(type)) {
+			GB_ASSERT(global != nullptr);
+			void *data = tb_global_add_region(m->mod, global, offset, size);
+			gb_memcopy(data, value.value_string.text, gb_min(value.value_string.len, size));
+		} else {
+			cg_global_const_string(m, value.value_string, type, global, offset);
+		}
+		break;
+
+	case ExactValue_Typeid:
+		{
+			GB_ASSERT_MSG(!is_type_array_like(bt), "%s", type_to_string(type));
+			void *dst = tb_global_add_region(m->mod, global, offset, size);
+			u64 id = cg_typeid_as_u64(m, value.value_typeid);
+			cg_write_uint_at_ptr(dst, id, t_typeid);
+		}
+		break;
+
+	case ExactValue_Compound:
+		{
+			TB_Global *out_global = cg_global_const_comp_literal(m, type, value, global, offset);
+			GB_ASSERT(out_global == global);
+		}
+		break;
+
+	case ExactValue_Procedure:
+		GB_PANIC("TODO(bill): nested procedure values/literals\n");
+		break;
+	case ExactValue_Complex:
+		{
+			GB_ASSERT_MSG(!is_type_array_like(bt), "%s", type_to_string(type));
+			Complex128 c = {};
+			if (value.value_complex) {
+				c = *value.value_complex;
+			}
+			void *res = tb_global_add_region(m->mod, global, offset, size);
+			switch (size) {
+			case 4:
+				((u16 *)res)[0] = f32_to_f16(cast(f32)c.real);
+				((u16 *)res)[1] = f32_to_f16(cast(f32)c.imag);
+				break;
+			case 8:
+				((f32 *)res)[0] = cast(f32)c.real;
+				((f32 *)res)[1] = cast(f32)c.imag;
+				break;
+			case 16:
+				((f64 *)res)[0] = cast(f64)c.real;
+				((f64 *)res)[1] = cast(f64)c.imag;
+				break;
+			}
+		}
+		break;
+	case ExactValue_Quaternion:
+		{
+			GB_ASSERT_MSG(!is_type_array_like(bt), "%s", type_to_string(type));
+			// @QuaternionLayout
+			Quaternion256 q = {};
+			if (value.value_quaternion) {
+				q = *value.value_quaternion;
+			}
+			void *res = tb_global_add_region(m->mod, global, offset, size);
+			switch (size) {
+			case 8:
+				((u16 *)res)[0] = f32_to_f16(cast(f32)q.imag);
+				((u16 *)res)[1] = f32_to_f16(cast(f32)q.jmag);
+				((u16 *)res)[2] = f32_to_f16(cast(f32)q.kmag);
+				((u16 *)res)[3] = f32_to_f16(cast(f32)q.real);
+				break;
+			case 16:
+				((f32 *)res)[0] = cast(f32)q.imag;
+				((f32 *)res)[1] = cast(f32)q.jmag;
+				((f32 *)res)[2] = cast(f32)q.kmag;
+				((f32 *)res)[3] = cast(f32)q.real;
+				break;
+			case 32:
+				((f64 *)res)[0] = cast(f64)q.imag;
+				((f64 *)res)[1] = cast(f64)q.jmag;
+				((f64 *)res)[2] = cast(f64)q.kmag;
+				((f64 *)res)[3] = cast(f64)q.real;
+				break;
+			}
+		}
+		break;
+	default:
+		GB_PANIC("%s", type_to_string(type));
+		break;
+	}
+	return true;
+}
+
+
+gb_internal TB_Global *cg_global_const_comp_literal(cgModule *m, Type *original_type, ExactValue const &value, TB_Global *global, i64 base_offset) {
+	GB_ASSERT(value.kind == ExactValue_Compound);
+	Ast *value_compound = value.value_compound;
+	ast_node(cl, CompoundLit, value_compound);
+
+	TEMPORARY_ALLOCATOR_GUARD();
+
+	if (global == nullptr) {
+		char name[32] = {};
+		gb_snprintf(name, 31, "complit$%u", 1+m->const_nil_guid.fetch_add(1));
+		global = tb_global_create(m->mod, -1, name, cg_debug_type(m, original_type), TB_LINKAGE_PRIVATE);
+		i64 size = type_size_of(original_type);
+		i64 align = type_align_of(original_type);
+
+		// READ ONLY?
+		TB_ModuleSection *section = nullptr;
+		if (is_type_string(original_type) || is_type_cstring(original_type)) {
+			section = tb_module_get_rdata(m->mod);
+		} else {
+			section = tb_module_get_data(m->mod);
+		}
+
+		if (cl->elems.count == 0) {
+			tb_global_set_storage(m->mod, section, global, size, align, 0);
+			return global;
+		}
+
+
+		isize global_region_count = cg_global_const_calculate_region_count(value, original_type);
+		tb_global_set_storage(m->mod, section, global, size, align, global_region_count);
+	}
+
+	if (cl->elems.count == 0) {
+		return global;
+	}
+
+
+	Type *bt = base_type(original_type);
+	i64 bt_size = type_size_of(bt);
+
+	switch (bt->kind) {
+	case Type_Struct:
+		if (cl->elems[0]->kind == Ast_FieldValue) {
+			isize elem_count = cl->elems.count;
+			for (isize i = 0; i < elem_count; i++) {
+				ast_node(fv, FieldValue, cl->elems[i]);
+				String name = fv->field->Ident.token.string;
+
+				TypeAndValue tav = fv->value->tav;
+				GB_ASSERT(tav.mode != Addressing_Invalid);
+				ExactValue value = tav.value;
+
+				Selection sel = lookup_field(bt, name, false);
+				GB_ASSERT(!sel.indirect);
+
+				if (!cg_is_nested_possibly_constant(bt, sel, fv->value)) {
+					continue;
+				}
+
+				i64 offset = type_offset_of_from_selection(bt, sel);
+				cg_global_const_add_region(m, value, sel.entity->type, global, base_offset+offset);
+			}
+		} else {
+			for_array(i, cl->elems) {
+				i64 field_index = i;
+				Ast *elem = cl->elems[i];
+				TypeAndValue tav = elem->tav;
+				Entity *f = bt->Struct.fields[field_index];
+				if (!cg_elem_type_can_be_constant(f->type)) {
+					continue;
+				}
+
+				i64 offset = bt->Struct.offsets[field_index];
+
+				ExactValue value = {};
+				if (tav.mode != Addressing_Invalid) {
+					value = tav.value;
+				}
+				cg_global_const_add_region(m, value, f->type, global, base_offset+offset);
+			}
+		}
+		return global;
+
+	case Type_Array:
+	case Type_EnumeratedArray:
+	case Type_SimdVector:
+		if (cl->elems[0]->kind == Ast_FieldValue) {
+			Type *et = base_array_type(bt);
+			i64 elem_size = type_size_of(et);
+			for (Ast *elem : cl->elems) {
+				ast_node(fv, FieldValue, elem);
+
+				ExactValue const &value = fv->value->tav.value;
+
+				if (is_ast_range(fv->field)) {
+					ast_node(ie, BinaryExpr, fv->field);
+					TypeAndValue lo_tav = ie->left->tav;
+					TypeAndValue hi_tav = ie->right->tav;
+					GB_ASSERT(lo_tav.mode == Addressing_Constant);
+					GB_ASSERT(hi_tav.mode == Addressing_Constant);
+
+					TokenKind op = ie->op.kind;
+					i64 lo = exact_value_to_i64(lo_tav.value);
+					i64 hi = exact_value_to_i64(hi_tav.value);
+					if (op != Token_RangeHalf) {
+						hi += 1;
+					}
+
+					for (i64 i = lo; i < hi; i++) {
+						i64 offset = i * elem_size;
+						cg_global_const_add_region(m, value, et, global, base_offset+offset);
+					}
+				} else {
+					TypeAndValue index_tav = fv->field->tav;
+					GB_ASSERT(index_tav.mode == Addressing_Constant);
+					i64 i = exact_value_to_i64(index_tav.value);
+					i64 offset = i * elem_size;
+					cg_global_const_add_region(m, value, et, global, base_offset+offset);
+				}
+			}
+		} else {
+			Type *et = base_array_type(bt);
+			i64 elem_size = type_size_of(et);
+			i64 offset = 0;
+			for (Ast *elem : cl->elems) {
+				ExactValue const &value = elem->tav.value;
+				cg_global_const_add_region(m, value, et, global, base_offset+offset);
+				offset += elem_size;
+			}
+		}
+
+		return global;
+
+	case Type_BitSet:
+		if (bt_size > 0) {
+			BigInt bits = {};
+			BigInt one = {};
+			big_int_from_u64(&one, 1);
+
+			for_array(i, cl->elems) {
+				Ast *e = cl->elems[i];
+				GB_ASSERT(e->kind != Ast_FieldValue);
+
+				TypeAndValue tav = e->tav;
+				if (tav.mode != Addressing_Constant) {
+					continue;
+				}
+				GB_ASSERT(tav.value.kind == ExactValue_Integer);
+				i64 v = big_int_to_i64(&tav.value.value_integer);
+				i64 lower = bt->BitSet.lower;
+				u64 index = cast(u64)(v-lower);
+				BigInt bit = {};
+				big_int_from_u64(&bit, index);
+				big_int_shl(&bit, &one, &bit);
+				big_int_or(&bits, &bits, &bit);
+			}
+
+			void *dst = tb_global_add_region(m->mod, global, base_offset, bt_size);
+			cg_write_big_int_at_ptr(dst, &bits, original_type);
+		}
+		return global;
+
+	case Type_Matrix:
+		GB_PANIC("TODO(bill): constant compound literal for %s", type_to_string(original_type));
+		break;
+
+	case Type_Slice:
+		{
+			i64 count = gb_max(cl->elems.count, cl->max_count);
+			Type *elem = bt->Slice.elem;
+			Type *t = alloc_type_array(elem, count);
+			TB_Global *backing_array = cg_global_const_comp_literal(m, t, value, nullptr, 0);
+
+			tb_global_add_symbol_reloc(m->mod, global, base_offset+0, cast(TB_Symbol *)backing_array);
+
+			void *len_ptr = tb_global_add_region(m->mod, global, base_offset+build_context.int_size, build_context.int_size);
+			cg_write_int_at_ptr(len_ptr, count, t_int);
+		}
+		return global;
+	}
+
+	GB_PANIC("TODO(bill): constant compound literal for %s", type_to_string(original_type));
+	return nullptr;
+}
+
+
+gb_internal cgValue cg_const_value(cgProcedure *p, Type *type, ExactValue const &value) {
+	GB_ASSERT(p != nullptr);
+	TB_Node *node = nullptr;
+
+	if (is_type_untyped(type)) {
+		// TODO(bill): THIS IS A COMPLETE HACK, WHY DOES THIS NOT A TYPE?
+		GB_ASSERT(type->kind == Type_Basic);
+		switch (type->Basic.kind) {
+		case Basic_UntypedBool:
+			type = t_bool;
+			break;
+		case Basic_UntypedInteger:
+			type = t_i64;
+			break;
+		case Basic_UntypedFloat:
+			type = t_f64;
+			break;
+		case Basic_UntypedComplex:
+			type = t_complex128;
+			break;
+		case Basic_UntypedQuaternion:
+			type = t_quaternion256;
+			break;
+		case Basic_UntypedString:
+			type = t_string;
+			break;
+		case Basic_UntypedRune:
+			type = t_rune;
+			break;
+		case Basic_UntypedNil:
+		case Basic_UntypedUninit:
+			return cg_value(cast(TB_Node *)nullptr, type);
+		}
+	}
+	TB_DataType dt = cg_data_type(type);
+
+	switch (value.kind) {
+	case ExactValue_Invalid:
+		return cg_const_nil(p, type);
+
+	case ExactValue_Typeid:
+		return cg_typeid(p, value.value_typeid);
+
+	case ExactValue_Procedure:
+		{
+			Ast *expr = unparen_expr(value.value_procedure);
+			if (expr->kind == Ast_ProcLit) {
+				cgProcedure *anon = cg_procedure_generate_anonymous(p->module, expr, p);
+				TB_Node *ptr = tb_inst_get_symbol_address(p->func, anon->symbol);
+				GB_ASSERT(are_types_identical(type, anon->type));
+				return cg_value(ptr, type);
+			}
+
+			Entity *e = entity_of_node(expr);
+			if (e != nullptr) {
+				TB_Symbol *found = cg_find_symbol_from_entity(p->module, e);
+				GB_ASSERT_MSG(found != nullptr, "could not find '%.*s'", LIT(e->token.string));
+				TB_Node *ptr = tb_inst_get_symbol_address(p->func, found);
+				GB_ASSERT(type != nullptr);
+				GB_ASSERT(are_types_identical(type, e->type));
+				return cg_value(ptr, type);
+			}
+
+			GB_PANIC("TODO(bill): cg_const_value ExactValue_Procedure %s", expr_to_string(expr));
+		}
+		break;
+	}
+
+	switch (value.kind) {
+	case ExactValue_Bool:
+		GB_ASSERT(!TB_IS_VOID_TYPE(dt));
+		return cg_value(tb_inst_uint(p->func, dt, value.value_bool), type);
+
+	case ExactValue_Integer:
+		GB_ASSERT(!TB_IS_VOID_TYPE(dt));
+		// GB_ASSERT(dt.raw != TB_TYPE_I128.raw);
+		if (is_type_unsigned(type)) {
+			u64 i = exact_value_to_u64(value);
+			return cg_value(tb_inst_uint(p->func, dt, i), type);
+		} else {
+			i64 i = exact_value_to_i64(value);
+			return cg_value(tb_inst_sint(p->func, dt, i), type);
+		}
+		break;
+
+	case ExactValue_Float:
+		GB_ASSERT(!TB_IS_VOID_TYPE(dt));
+		GB_ASSERT(dt.raw != TB_TYPE_F16.raw);
+		GB_ASSERT(!is_type_different_to_arch_endianness(type));
+		{
+			f64 f = exact_value_to_f64(value);
+			if (type_size_of(type) == 8) {
+				return cg_value(tb_inst_float64(p->func, f), type);
+			} else {
+				return cg_value(tb_inst_float32(p->func, cast(f32)f), type);
+			}
+		}
+		break;
+
+	case ExactValue_String:
+		{
+			GB_ASSERT(is_type_string(type));
+			cgModule *m = p->module;
+
+			String str = value.value_string;
+
+			char name[32] = {};
+			gb_snprintf(name, 31, "csb$%u", 1+m->const_nil_guid.fetch_add(1));
+			TB_Global *cstr_global = tb_global_create(m->mod, -1, name, nullptr, TB_LINKAGE_PRIVATE);
+
+			i64 size = str.len+1;
+			tb_global_set_storage(m->mod, tb_module_get_rdata(m->mod), cstr_global, size, 1, 1);
+			u8 *data = cast(u8 *)tb_global_add_region(m->mod, cstr_global, 0, size);
+			gb_memcopy(data, str.text, str.len);
+			data[str.len] = 0;
+
+			if (is_type_cstring(type)) {
+				cgValue s = cg_value(cstr_global, type);
+				return cg_flatten_value(p, s);
+			}
+
+			gb_snprintf(name, 31, "str$%u", 1+m->const_nil_guid.fetch_add(1));
+			TB_Global *str_global = tb_global_create(m->mod, -1, name, cg_debug_type(m, type), TB_LINKAGE_PRIVATE);
+			tb_global_set_storage(m->mod, tb_module_get_rdata(m->mod), str_global, type_size_of(type), type_align_of(type), 2);
+
+			tb_global_add_symbol_reloc(m->mod, str_global, 0, cast(TB_Symbol *)cstr_global);
+			void *len_ptr = tb_global_add_region(m->mod, str_global, build_context.int_size, build_context.int_size);
+			cg_write_int_at_ptr(len_ptr, str.len, t_int);
+
+			TB_Node *s = tb_inst_get_symbol_address(p->func, cast(TB_Symbol *)str_global);
+			return cg_lvalue_addr(s, type);
+
+		}
+
+	case ExactValue_Pointer:
+		return cg_value(tb_inst_uint(p->func, dt, exact_value_to_u64(value)), type);
+
+	case ExactValue_Compound:
+		{
+			TB_Symbol *symbol = cast(TB_Symbol *)cg_global_const_comp_literal(p->module, type, value, nullptr, 0);
+			TB_Node *node = tb_inst_get_symbol_address(p->func, symbol);
+			return cg_lvalue_addr(node, type);
+		}
+		break;
+	}
+
+
+	GB_ASSERT(node != nullptr);
+	return cg_value(node, type);
+}
+
+gb_internal cgValue cg_const_int(cgProcedure *p, Type *type, i64 i) {
+	return cg_const_value(p, type, exact_value_i64(i));
+}
+gb_internal cgValue cg_const_bool(cgProcedure *p, Type *type, bool v) {
+	return cg_value(tb_inst_bool(p->func, v), type);
+}
+
+gb_internal cgValue cg_const_string(cgProcedure *p, Type *type, String const &str) {
+	return cg_const_value(p, type, exact_value_string(str));
+}
+
+gb_internal cgValue cg_const_union_tag(cgProcedure *p, Type *u, Type *v) {
+	return cg_const_value(p, union_tag_type(u), exact_value_i64(union_variant_index(u, v)));
+}
+

+ 482 - 0
src/tilde_debug.cpp

@@ -0,0 +1,482 @@
+gb_internal TB_DebugType *cg_debug_type_internal(cgModule *m, Type *type);
+gb_internal TB_DebugType *cg_debug_type(cgModule *m, Type *type) {
+	type = reduce_tuple_to_single_type(type);
+
+	mutex_lock(&m->debug_type_mutex);
+	defer (mutex_unlock(&m->debug_type_mutex));
+	TB_DebugType **found = map_get(&m->debug_type_map, type);
+	if (found) {
+		return *found;
+	}
+
+	TB_DebugType *res = cg_debug_type_internal(m, type);
+	map_set(&m->debug_type_map, type, res);
+	return res;
+}
+
+gb_internal TB_DebugType *cg_debug_type_for_proc(cgModule *m, Type *type) {
+	GB_ASSERT(is_type_proc(type));
+	TB_DebugType **func_found = nullptr;
+	TB_DebugType *func_ptr = cg_debug_type(m, type);
+	GB_ASSERT(func_ptr != nullptr);
+
+	mutex_lock(&m->debug_type_mutex);
+	func_found = map_get(&m->proc_debug_type_map, type);
+	mutex_unlock(&m->debug_type_mutex);
+	GB_ASSERT(func_found != nullptr);
+	return *func_found;
+}
+
+
+gb_internal TB_DebugType *cg_debug_type_internal_record(cgModule *m, Type *type, String const &record_name) {
+	Type *bt = base_type(type);
+	switch (bt->kind) {
+	case Type_Struct:
+		{
+			type_set_offsets(bt);
+
+			TB_DebugType *record = nullptr;
+			if (bt->Struct.is_raw_union) {
+				record = tb_debug_create_union(m->mod, record_name.len, cast(char const *)record_name.text);
+			} else {
+				record = tb_debug_create_struct(m->mod, record_name.len, cast(char const *)record_name.text);
+			}
+			if (record_name.len != 0) {
+				map_set(&m->debug_type_map, type, record);
+			}
+
+			TB_DebugType **fields = tb_debug_record_begin(record, bt->Struct.fields.count);
+			for_array(i, bt->Struct.fields) {
+				Entity *e = bt->Struct.fields[i];
+				Type *type = e->type;
+				if (is_type_proc(type)) {
+					type = t_rawptr;
+				}
+				TB_DebugType *field_type = cg_debug_type(m, type);
+				String        name       = e->token.string;
+				TB_CharUnits  offset     = cast(TB_CharUnits)bt->Struct.offsets[i];
+				if (name.len == 0) {
+					name = str_lit("_");
+				}
+
+				fields[i] = tb_debug_create_field(m->mod, field_type, name.len, cast(char const *)name.text, offset);
+			}
+			tb_debug_record_end(
+				record,
+				cast(TB_CharUnits)type_size_of(type),
+				cast(TB_CharUnits)type_align_of(type)
+			);
+			return record;
+		}
+		break;
+
+	case Type_Tuple:
+		{
+			GB_ASSERT(record_name.len == 0);
+			type_set_offsets(bt);
+
+			TB_DebugType *record = tb_debug_create_struct(m->mod, 0, "");
+			isize record_count = 0;
+			for (Entity *e : bt->Tuple.variables) {
+				if (e->kind == Entity_Variable) {
+					record_count += 1;
+				}
+			}
+			TB_DebugType **fields = tb_debug_record_begin(record, record_count);
+			for_array(i, bt->Tuple.variables) {
+				Entity *e = bt->Tuple.variables[i];
+				if (e->kind != Entity_Variable) {
+					continue;
+				}
+				Type *type = e->type;
+				if (is_type_proc(type)) {
+					type = t_rawptr;
+				}
+				TB_DebugType *field_type = cg_debug_type(m, type);
+				String        name       = e->token.string;
+				TB_CharUnits  offset     = cast(TB_CharUnits)bt->Tuple.offsets[i];
+				if (name.len == 0) {
+					name = str_lit("_");
+				}
+
+				fields[i] = tb_debug_create_field(m->mod, field_type, name.len, cast(char const *)name.text, offset);
+			}
+			tb_debug_record_end(
+				record,
+				cast(TB_CharUnits)type_size_of(type),
+				cast(TB_CharUnits)type_align_of(type)
+			);
+			return record;
+		}
+		break;
+	case Type_Union:
+		{
+			TB_DebugType *record = tb_debug_create_struct(m->mod, record_name.len, cast(char const *)record_name.text);
+			if (record_name.len != 0) {
+				map_set(&m->debug_type_map, type, record);
+			}
+
+			i64 variant_count = bt->Union.variants.count;
+			if (is_type_union_maybe_pointer(bt)) {
+				// NO TAG
+				GB_ASSERT(variant_count == 1);
+				TB_DebugType **fields = tb_debug_record_begin(record, variant_count);
+				TB_DebugType *variant_type = cg_debug_type(m, bt->Union.variants[0]);
+				fields[0] = tb_debug_create_field(m->mod, variant_type, -1, "v0", 0);
+				tb_debug_record_end(
+					record,
+					cast(TB_CharUnits)type_size_of(type),
+					cast(TB_CharUnits)type_align_of(type)
+				);
+			} else {
+				TB_DebugType **fields = tb_debug_record_begin(record, variant_count+1);
+				for_array(i, bt->Union.variants) {
+					Type *v = bt->Union.variants[i];
+					TB_DebugType *variant_type = cg_debug_type(m, v);
+					char name[32] = {};
+					u32 v_index = cast(u32)i;
+					if (bt->Union.kind != UnionType_no_nil) {
+						v_index += 1;
+					}
+					gb_snprintf(name, 31, "v%u", v_index);
+					fields[i] = tb_debug_create_field(m->mod, variant_type, -1, name, 0);
+				}
+
+				TB_DebugType *tag_type = cg_debug_type(m, union_tag_type(bt));
+				fields[variant_count] = tb_debug_create_field(m->mod, tag_type, -1, "tag", cast(TB_CharUnits)bt->Union.variant_block_size);
+
+			}
+			tb_debug_record_end(
+				record,
+				cast(TB_CharUnits)type_size_of(type),
+				cast(TB_CharUnits)type_align_of(type)
+			);
+			return record;
+		}
+		break;
+	}
+	return nullptr;
+}
+
+
+gb_internal TB_DebugType *cg_debug_type_internal(cgModule *m, Type *type) {
+	if (type == nullptr) {
+		return tb_debug_get_void(m->mod);
+	}
+	Type *original_type = type;
+	if (type->kind == Type_Named) {
+		String name = type->Named.name;
+		TB_DebugType *res = cg_debug_type_internal_record(m, type, name);
+		if (res) {
+			return res;
+		}
+		type = base_type(type->Named.base);
+	}
+
+	TB_CharUnits int_size = cast(TB_CharUnits)build_context.int_size;
+	TB_CharUnits ptr_size = cast(TB_CharUnits)build_context.ptr_size;
+	TB_CharUnits size  = cast(TB_CharUnits)type_size_of(type);
+	TB_CharUnits align = cast(TB_CharUnits)type_align_of(type);
+	int bits = cast(int)(8*size);
+	bool is_signed = is_type_integer(core_type(type)) && !is_type_unsigned(core_type(type));
+
+	switch (type->kind) {
+	case Type_Basic:
+		switch (type->Basic.kind) {
+		case Basic_bool:          return tb_debug_get_bool(m->mod);
+		case Basic_b8:            return tb_debug_get_bool(m->mod);
+		case Basic_b16:           return tb_debug_get_integer(m->mod, is_signed, bits);
+		case Basic_b32:           return tb_debug_get_integer(m->mod, is_signed, bits);
+		case Basic_b64:           return tb_debug_get_integer(m->mod, is_signed, bits);
+		case Basic_i8:            return tb_debug_get_integer(m->mod, is_signed, bits);
+		case Basic_u8:            return tb_debug_get_integer(m->mod, is_signed, bits);
+		case Basic_i16:           return tb_debug_get_integer(m->mod, is_signed, bits);
+		case Basic_u16:           return tb_debug_get_integer(m->mod, is_signed, bits);
+		case Basic_i32:           return tb_debug_get_integer(m->mod, is_signed, bits);
+		case Basic_u32:           return tb_debug_get_integer(m->mod, is_signed, bits);
+		case Basic_i64:           return tb_debug_get_integer(m->mod, is_signed, bits);
+		case Basic_u64:           return tb_debug_get_integer(m->mod, is_signed, bits);
+		case Basic_i128:          return tb_debug_get_integer(m->mod, is_signed, bits);
+		case Basic_u128:          return tb_debug_get_integer(m->mod, is_signed, bits);
+		case Basic_rune:          return tb_debug_get_integer(m->mod, is_signed, bits);
+
+		case Basic_f16:           return tb_debug_get_integer(m->mod, false, bits);
+		case Basic_f32:           return tb_debug_get_float(m->mod,   TB_FLT_32);
+		case Basic_f64:           return tb_debug_get_float(m->mod,   TB_FLT_64);
+
+		case Basic_complex32:
+		case Basic_complex64:
+		case Basic_complex128:
+			{
+				String name = basic_types[type->Basic.kind].Basic.name;
+				TB_DebugType *record = tb_debug_create_struct(m->mod, name.len, cast(char const *)name.text);
+				Type *et = base_complex_elem_type(type);
+				TB_CharUnits elem_size = cast(TB_CharUnits)type_size_of(et);
+				TB_DebugType *elem = cg_debug_type(m, et);
+
+				TB_DebugType **fields = tb_debug_record_begin(record, 2);
+				fields[0] = tb_debug_create_field(m->mod, elem, -1, "real", 0*elem_size);
+				fields[1] = tb_debug_create_field(m->mod, elem, -1, "imag", 1*elem_size);
+
+				tb_debug_record_end(record, size, align);
+				return record;
+			}
+		case Basic_quaternion64:
+		case Basic_quaternion128:
+		case Basic_quaternion256:
+			{
+				String name = basic_types[type->Basic.kind].Basic.name;
+				TB_DebugType *record = tb_debug_create_struct(m->mod, name.len, cast(char const *)name.text);
+				Type *et = base_complex_elem_type(type);
+				TB_CharUnits elem_size = cast(TB_CharUnits)type_size_of(et);
+				TB_DebugType *elem = cg_debug_type(m, et);
+
+				// @QuaternionLayout
+				TB_DebugType **fields = tb_debug_record_begin(record, 4);
+				fields[0] = tb_debug_create_field(m->mod, elem, -1, "imag", 0*elem_size);
+				fields[1] = tb_debug_create_field(m->mod, elem, -1, "jmag", 1*elem_size);
+				fields[2] = tb_debug_create_field(m->mod, elem, -1, "kmag", 2*elem_size);
+				fields[3] = tb_debug_create_field(m->mod, elem, -1, "real", 3*elem_size);
+
+				tb_debug_record_end(record, size, align);
+				return record;
+			}
+
+		case Basic_int:           return tb_debug_get_integer(m->mod, is_signed, bits);
+		case Basic_uint:          return tb_debug_get_integer(m->mod, is_signed, bits);
+		case Basic_uintptr:       return tb_debug_get_integer(m->mod, is_signed, bits);
+
+		case Basic_rawptr:
+			return tb_debug_create_ptr(m->mod, tb_debug_get_void(m->mod));
+		case Basic_string:
+			{
+				String name = basic_types[type->Basic.kind].Basic.name;
+				TB_DebugType *record = tb_debug_create_struct(m->mod, name.len, cast(char const *)name.text);
+				// @QuaternionLayout
+				TB_DebugType **fields = tb_debug_record_begin(record, 2);
+				fields[0] = tb_debug_create_field(m->mod, cg_debug_type(m, t_u8_ptr), -1, "data", 0*int_size);
+				fields[1] = tb_debug_create_field(m->mod, cg_debug_type(m, t_int),    -1, "len",  1*int_size);
+
+				tb_debug_record_end(record, size, align);
+				return record;
+			}
+		case Basic_cstring:
+			return tb_debug_create_ptr(m->mod, tb_debug_get_integer(m->mod, false, 8));
+
+		case Basic_any:
+			{
+				String name = basic_types[type->Basic.kind].Basic.name;
+				TB_DebugType *record = tb_debug_create_struct(m->mod, name.len, cast(char const *)name.text);
+				// @QuaternionLayout
+				TB_DebugType **fields = tb_debug_record_begin(record, 2);
+				fields[0] = tb_debug_create_field(m->mod, cg_debug_type(m, t_rawptr), -1, "data", 0*ptr_size);
+				fields[1] = tb_debug_create_field(m->mod, cg_debug_type(m, t_typeid), -1, "id",   1*ptr_size);
+
+				tb_debug_record_end(record, size, align);
+				return record;
+			}
+		case Basic_typeid: return tb_debug_get_integer(m->mod, false, bits);
+
+		case Basic_i16le:         return tb_debug_get_integer(m->mod, is_signed, bits);
+		case Basic_u16le:         return tb_debug_get_integer(m->mod, is_signed, bits);
+		case Basic_i32le:         return tb_debug_get_integer(m->mod, is_signed, bits);
+		case Basic_u32le:         return tb_debug_get_integer(m->mod, is_signed, bits);
+		case Basic_i64le:         return tb_debug_get_integer(m->mod, is_signed, bits);
+		case Basic_u64le:         return tb_debug_get_integer(m->mod, is_signed, bits);
+		case Basic_i128le:        return tb_debug_get_integer(m->mod, is_signed, bits);
+		case Basic_u128le:        return tb_debug_get_integer(m->mod, is_signed, bits);
+		case Basic_i16be:         return tb_debug_get_integer(m->mod, is_signed, bits);
+		case Basic_u16be:         return tb_debug_get_integer(m->mod, is_signed, bits);
+		case Basic_i32be:         return tb_debug_get_integer(m->mod, is_signed, bits);
+		case Basic_u32be:         return tb_debug_get_integer(m->mod, is_signed, bits);
+		case Basic_i64be:         return tb_debug_get_integer(m->mod, is_signed, bits);
+		case Basic_u64be:         return tb_debug_get_integer(m->mod, is_signed, bits);
+		case Basic_i128be:        return tb_debug_get_integer(m->mod, is_signed, bits);
+		case Basic_u128be:        return tb_debug_get_integer(m->mod, is_signed, bits);
+
+		case Basic_f16le:         return tb_debug_get_integer(m->mod, false, bits);
+		case Basic_f32le:         return tb_debug_get_float(m->mod,   TB_FLT_32);
+		case Basic_f64le:         return tb_debug_get_float(m->mod,   TB_FLT_64);
+		case Basic_f16be:         return tb_debug_get_integer(m->mod, false, bits);
+		case Basic_f32be:         return tb_debug_get_float(m->mod,   TB_FLT_32);
+		case Basic_f64be:         return tb_debug_get_float(m->mod,   TB_FLT_64);
+		}
+		break;
+	case Type_Generic:
+		GB_PANIC("SHOULD NEVER HIT");
+		break;
+	case Type_Pointer:
+		return tb_debug_create_ptr(m->mod, cg_debug_type(m, type->Pointer.elem));
+	case Type_MultiPointer:
+		return tb_debug_create_ptr(m->mod, cg_debug_type(m, type->MultiPointer.elem));
+	case Type_Array:
+		return tb_debug_create_array(m->mod, cg_debug_type(m, type->Array.elem), type->Array.count);
+	case Type_EnumeratedArray:
+		return tb_debug_create_array(m->mod, cg_debug_type(m, type->EnumeratedArray.elem), type->EnumeratedArray.count);
+	case Type_Slice:
+		{
+			String name = {};
+			TB_DebugType *record = tb_debug_create_struct(m->mod, name.len, cast(char const *)name.text);
+			TB_DebugType **fields = tb_debug_record_begin(record, 2);
+			fields[0] = tb_debug_create_field(m->mod, cg_debug_type(m, alloc_type_pointer(type->Slice.elem)), -1, "data", 0*int_size);
+			fields[1] = tb_debug_create_field(m->mod, cg_debug_type(m, t_int),    -1, "len",  1*int_size);
+
+			tb_debug_record_end(record, size, align);
+			return record;
+		}
+	case Type_DynamicArray:
+		{
+			String name = {};
+			TB_DebugType *record = tb_debug_create_struct(m->mod, name.len, cast(char const *)name.text);
+			TB_DebugType **fields = tb_debug_record_begin(record, 4);
+			fields[0] = tb_debug_create_field(m->mod, cg_debug_type(m, alloc_type_pointer(type->Slice.elem)), -1, "data", 0*int_size);
+			fields[1] = tb_debug_create_field(m->mod, cg_debug_type(m, t_int),       -1, "len",        1*int_size);
+			fields[2] = tb_debug_create_field(m->mod, cg_debug_type(m, t_int),       -1, "cap",        2*int_size);
+			fields[3] = tb_debug_create_field(m->mod, cg_debug_type(m, t_allocator), -1, "allocator",  3*int_size);
+
+			tb_debug_record_end(record, size, align);
+			return record;
+		}
+	case Type_Map:
+		return cg_debug_type(m, t_raw_map);
+
+	case Type_Struct:
+	case Type_Tuple:
+	case Type_Union:
+		return cg_debug_type_internal_record(m, type, {});
+
+	case Type_Enum:
+		return tb_debug_get_integer(m->mod, is_signed, bits);
+
+	case Type_Proc:
+		{
+			TypeProc *pt = &type->Proc;
+			isize param_count  = 0;
+			isize return_count = 0;
+
+			bool is_odin_cc = is_calling_convention_odin(pt->calling_convention);
+
+			if (pt->params) for (Entity *e : pt->params->Tuple.variables) {
+				if (e->kind == Entity_Variable) {
+					param_count += 1;
+				}
+			}
+
+			if (pt->result_count > 0) {
+				if (is_odin_cc) {
+					// Split returns
+					param_count += pt->result_count-1;
+					return_count = 1;
+				} else {
+					return_count = 1;
+				}
+			}
+
+			if (pt->calling_convention == ProcCC_Odin) {
+				// `context` ptr
+				param_count += 1;
+			}
+
+			TB_CallingConv tb_cc = TB_CDECL;
+			if (pt->calling_convention == ProcCC_StdCall) {
+				tb_cc = TB_STDCALL;
+			}
+			TB_DebugType *func = tb_debug_create_func(m->mod, tb_cc, param_count, return_count, pt->c_vararg);
+
+			map_set(&m->proc_debug_type_map, original_type, func);
+			map_set(&m->proc_debug_type_map, type, func);
+
+			TB_DebugType *func_ptr = tb_debug_create_ptr(m->mod, func);
+			map_set(&m->debug_type_map, original_type, func_ptr);
+			map_set(&m->debug_type_map, type, func_ptr);
+
+			TB_DebugType **params = tb_debug_func_params(func);
+			TB_DebugType **returns = tb_debug_func_returns(func);
+
+			isize param_index = 0;
+			isize return_index = 0;
+			if (pt->params) for (Entity *e : pt->params->Tuple.variables) {
+				if (e->kind == Entity_Variable) {
+					Type *type = e->type;
+					if (is_type_proc(type)) {
+						type = t_rawptr;
+					}
+					String name = e->token.string;
+					if (name.len == 0) {
+						name = str_lit("_");
+					}
+					params[param_index++] = tb_debug_create_field(m->mod, cg_debug_type(m, type), name.len, cast(char const *)name.text, 0);
+				}
+			}
+
+			if (pt->result_count) {
+				GB_ASSERT(pt->results);
+				if (is_odin_cc) {
+					// Split Returns
+					for (isize i = 0; i < pt->results->Tuple.variables.count-1; i++) {
+						Entity *e = pt->results->Tuple.variables[i];
+						GB_ASSERT(e->kind == Entity_Variable);
+						Type *type = e->type;
+						if (is_type_proc(e->type)) {
+							type = t_rawptr;
+						}
+						type = alloc_type_pointer(type);
+
+						String name = e->token.string;
+						if (name.len == 0) {
+							name = str_lit("_");
+						}
+						params[param_index++] = tb_debug_create_field(m->mod, cg_debug_type(m, type), name.len, cast(char const *)name.text, 0);
+					}
+
+					Type *last_type = pt->results->Tuple.variables[pt->results->Tuple.variables.count-1]->type;
+					if (is_type_proc(last_type)) {
+						last_type = t_rawptr;
+					}
+					returns[return_index++] = cg_debug_type(m, last_type);
+				} else {
+					returns[return_index++] = cg_debug_type(m, pt->results);
+				}
+			}
+
+			if (pt->calling_convention == ProcCC_Odin) {
+				Type *type = t_context_ptr;
+				String name = str_lit("__.context_ptr");
+				params[param_index++] = tb_debug_create_field(m->mod, cg_debug_type(m, type), name.len, cast(char const *)name.text, 0);
+			}
+
+			GB_ASSERT_MSG(param_index  == param_count,  "%td vs %td for %s", param_index,  param_count, type_to_string(type));
+			GB_ASSERT_MSG(return_index == return_count, "%td vs %td for %s", return_index, return_count, type_to_string(type));
+
+			return func_ptr;
+		}
+		break;
+	case Type_BitSet:
+		return cg_debug_type(m, bit_set_to_int(type));
+	case Type_SimdVector:
+		return tb_debug_create_array(m->mod, cg_debug_type(m, type->SimdVector.elem), type->SimdVector.count);
+	case Type_RelativePointer:
+		return cg_debug_type(m, type->RelativePointer.base_integer);
+	case Type_RelativeMultiPointer:
+		return cg_debug_type(m, type->RelativeMultiPointer.base_integer);
+	case Type_Matrix:
+		{
+			i64 count = matrix_type_total_internal_elems(type);
+			return tb_debug_create_array(m->mod, cg_debug_type(m, type->Matrix.elem), count);
+		}
+	case Type_SoaPointer:
+		{
+			String name = {};
+			TB_DebugType *record = tb_debug_create_struct(m->mod, name.len, cast(char const *)name.text);
+			TB_DebugType **fields = tb_debug_record_begin(record, 2);
+			fields[0] = tb_debug_create_field(m->mod, cg_debug_type(m, alloc_type_pointer(type->SoaPointer.elem)), -1, "ptr", 0*int_size);
+			fields[1] = tb_debug_create_field(m->mod, cg_debug_type(m, t_int), -1, "offset",  1*int_size);
+
+			tb_debug_record_end(record, size, align);
+			return record;
+		}
+	}
+
+	// TODO(bill): cg_debug_type
+	return tb_debug_get_void(m->mod);
+}

+ 3871 - 0
src/tilde_expr.cpp

@@ -0,0 +1,3871 @@
+gb_internal cgValue cg_flatten_value(cgProcedure *p, cgValue value) {
+	GB_ASSERT(value.kind != cgValue_Multi);
+	if (value.kind == cgValue_Symbol) {
+		GB_ASSERT(is_type_internally_pointer_like(value.type));
+		return cg_value(tb_inst_get_symbol_address(p->func, value.symbol), value.type);
+	} else if (value.kind == cgValue_Addr) {
+		// TODO(bill): Is this a good idea?
+		// this converts an lvalue to an rvalue if trivially possible
+		TB_DataType dt = cg_data_type(value.type);
+		if (!TB_IS_VOID_TYPE(dt)) {
+			TB_CharUnits align = cast(TB_CharUnits)type_align_of(value.type);
+			return cg_value(tb_inst_load(p->func, dt, value.node, align, false), value.type);
+		}
+	}
+	return value;
+}
+
+gb_internal cgValue cg_emit_select(cgProcedure *p, cgValue const &cond, cgValue const &x, cgValue const &y) {
+	GB_ASSERT(x.kind == y.kind);
+	GB_ASSERT(cond.kind == cgValue_Value);
+	cgValue res = x;
+	res.node = tb_inst_select(p->func, cond.node, x.node, y.node);
+	return res;
+}
+
+
+gb_internal bool cg_is_expr_untyped_const(Ast *expr) {
+	auto const &tv = type_and_value_of_expr(expr);
+	if (is_type_untyped(tv.type)) {
+		return tv.value.kind != ExactValue_Invalid;
+	}
+	return false;
+}
+gb_internal cgValue cg_expr_untyped_const_to_typed(cgProcedure *p, Ast *expr, Type *t) {
+	GB_ASSERT(is_type_typed(t));
+	auto const &tv = type_and_value_of_expr(expr);
+	return cg_const_value(p, t, tv.value);
+}
+
+gb_internal cgContextData *cg_push_context_onto_stack(cgProcedure *p, cgAddr ctx) {
+	ctx.kind = cgAddr_Context;
+	cgContextData *cd = array_add_and_get(&p->context_stack);
+	cd->ctx = ctx;
+	cd->scope_index = p->scope_index;
+	return cd;
+}
+
+gb_internal cgAddr cg_find_or_generate_context_ptr(cgProcedure *p) {
+	if (p->context_stack.count > 0) {
+		return p->context_stack[p->context_stack.count-1].ctx;
+	}
+
+	Type *pt = base_type(p->type);
+	GB_ASSERT(pt->kind == Type_Proc);
+	GB_ASSERT(pt->Proc.calling_convention != ProcCC_Odin);
+
+	cgAddr c = cg_add_local(p, t_context, nullptr, true);
+	tb_node_append_attrib(c.addr.node, tb_function_attrib_variable(p->func, -1, "context", cg_debug_type(p->module, t_context)));
+	c.kind = cgAddr_Context;
+	// lb_emit_init_context(p, c);
+	cg_push_context_onto_stack(p, c);
+	// lb_add_debug_context_variable(p, c);
+
+	return c;
+}
+
+gb_internal cgValue cg_find_value_from_entity(cgModule *m, Entity *e) {
+	e = strip_entity_wrapping(e);
+	GB_ASSERT(e != nullptr);
+
+	GB_ASSERT(e->token.string != "_");
+
+	if (e->kind == Entity_Procedure) {
+		return cg_find_procedure_value_from_entity(m, e);
+	}
+
+	cgValue *found = nullptr;
+	rw_mutex_shared_lock(&m->values_mutex);
+	found = map_get(&m->values, e);
+	rw_mutex_shared_unlock(&m->values_mutex);
+	if (found) {
+		return *found;
+	}
+
+	GB_PANIC("\n\tError in: %s, missing value '%.*s'\n", token_pos_to_string(e->token.pos), LIT(e->token.string));
+	return {};
+}
+
+gb_internal cgValue cg_get_using_variable(cgProcedure *p, Entity *e) {
+	GB_ASSERT(e->kind == Entity_Variable && e->flags & EntityFlag_Using);
+	String name = e->token.string;
+	Entity *parent = e->using_parent;
+	Selection sel = lookup_field(parent->type, name, false);
+	GB_ASSERT(sel.entity != nullptr);
+	cgValue *pv = map_get(&p->module->values, parent);
+
+	cgValue v = {};
+
+	if (pv == nullptr && parent->flags & EntityFlag_SoaPtrField) {
+		// NOTE(bill): using SOA value (probably from for-in statement)
+		GB_PANIC("TODO(bill): cg_get_soa_variable_addr");
+		// cgAddr parent_addr = cg_get_soa_variable_addr(p, parent);
+		// v = cg_addr_get_ptr(p, parent_addr);
+	} else if (pv != nullptr) {
+		v = *pv;
+	} else {
+		GB_ASSERT_MSG(e->using_expr != nullptr, "%.*s %.*s", LIT(e->token.string), LIT(name));
+		v = cg_build_addr_ptr(p, e->using_expr);
+	}
+	GB_ASSERT(v.node != nullptr);
+	GB_ASSERT_MSG(parent->type == type_deref(v.type), "%s %s", type_to_string(parent->type), type_to_string(v.type));
+	cgValue ptr = cg_emit_deep_field_gep(p, v, sel);
+	// if (parent->scope) {
+	// 	if ((parent->scope->flags & (ScopeFlag_File|ScopeFlag_Pkg)) == 0) {
+	// 		cg_add_debug_local_variable(p, ptr.value, e->type, e->token);
+	// 	}
+	// } else {
+	// 	cg_add_debug_local_variable(p, ptr.value, e->type, e->token);
+	// }
+	return ptr;
+}
+gb_internal cgAddr cg_build_addr_from_entity(cgProcedure *p, Entity *e, Ast *expr) {
+	GB_ASSERT(e != nullptr);
+	if (e->kind == Entity_Constant) {
+		Type *t = default_type(type_of_expr(expr));
+		cgValue v = cg_const_value(p, t, e->Constant.value);
+		GB_PANIC("TODO(bill): cg_add_global_generated");
+		// return cg_add_global_generated(p->module, t, v);
+		return {};
+	}
+
+	cgAddr *local_found = map_get(&p->variable_map, e);
+	if (local_found) {
+		return *local_found;
+	}
+
+	cgValue v = {};
+
+	cgModule *m = p->module;
+
+	rw_mutex_lock(&m->values_mutex);
+	cgValue *found = map_get(&m->values, e);
+	rw_mutex_unlock(&m->values_mutex);
+	if (found) {
+		v = *found;
+	} else if (e->kind == Entity_Variable && e->flags & EntityFlag_Using) {
+		// NOTE(bill): Calculate the using variable every time
+		v = cg_get_using_variable(p, e);
+	} else if (e->flags & EntityFlag_SoaPtrField) {
+		GB_PANIC("TODO(bill): cg_get_soa_variable_addr");
+		// return cg_get_soa_variable_addr(p, e);
+	}
+
+
+	if (v.node == nullptr) {
+		cgValue v = cg_find_value_from_entity(m, e);
+		v = cg_flatten_value(p, v);
+		return cg_addr(v);
+	}
+
+	return cg_addr(v);
+}
+
+gb_internal cgValue cg_emit_union_tag_ptr(cgProcedure *p, cgValue const &parent_ptr) {
+	Type *t = parent_ptr.type;
+	Type *ut = base_type(type_deref(t));
+	GB_ASSERT_MSG(is_type_pointer(t), "%s", type_to_string(t));
+	GB_ASSERT_MSG(ut->kind == Type_Union, "%s", type_to_string(t));
+
+	GB_ASSERT(!is_type_union_maybe_pointer_original_alignment(ut));
+	GB_ASSERT(!is_type_union_maybe_pointer(ut));
+	GB_ASSERT(type_size_of(ut) > 0);
+
+	Type *tag_type = union_tag_type(ut);
+	i64 tag_offset = ut->Union.variant_block_size;
+
+	GB_ASSERT(parent_ptr.kind == cgValue_Value);
+	TB_Node *ptr = parent_ptr.node;
+	TB_Node *tag_ptr = tb_inst_member_access(p->func, ptr, tag_offset);
+	return cg_value(tag_ptr, alloc_type_pointer(tag_type));
+}
+
+
+
+gb_internal cgValue cg_correct_endianness(cgProcedure *p, cgValue value) {
+	Type *src = core_type(value.type);
+	GB_ASSERT(is_type_integer(src) || is_type_float(src));
+	if (is_type_different_to_arch_endianness(src)) {
+		GB_PANIC("TODO(bill): cg_correct_endianness");
+		// Type *platform_src_type = integer_endian_type_to_platform_type(src);
+		// value = cg_emit_byte_swap(p, value, platform_src_type);
+	}
+	return value;
+}
+
+gb_internal cgValue cg_emit_transmute(cgProcedure *p, cgValue value, Type *type) {
+	GB_ASSERT(type_size_of(value.type) == type_size_of(type));
+
+	value = cg_flatten_value(p, value);
+
+	if (are_types_identical(value.type, type)) {
+		return value;
+	}
+	if (are_types_identical(core_type(value.type), core_type(type))) {
+		value.type = type;
+		return value;
+	}
+
+	i64 src_align = type_align_of(value.type);
+	i64 dst_align = type_align_of(type);
+
+	if (dst_align > src_align) {
+		cgAddr local = cg_add_local(p, type, nullptr, false);
+		cgValue dst = local.addr;
+		dst.type = alloc_type_pointer(value.type);
+		cg_emit_store(p, dst, value);
+		return cg_addr_load(p, local);
+	}
+
+	TB_DataType dt = cg_data_type(type);
+	switch (value.kind) {
+	case cgValue_Value:
+		GB_ASSERT_MSG(!TB_IS_VOID_TYPE(dt), "%d %s -> %s", dt.type, type_to_string(value.type), type_to_string(type));
+		value.type = type;
+		if (value.node->dt.raw != dt.raw) {
+			switch (value.node->dt.type) {
+			case TB_INT:
+				switch (value.node->dt.type) {
+				case TB_INT:
+					break;
+				case TB_FLOAT:
+					value.node = tb_inst_bitcast(p->func, value.node, dt);
+					break;
+				case TB_PTR:
+					value.node = tb_inst_int2ptr(p->func, value.node);
+					break;
+				}
+				break;
+			case TB_FLOAT:
+				switch (value.node->dt.type) {
+				case TB_INT:
+					value.node = tb_inst_bitcast(p->func, value.node, dt);
+					break;
+				case TB_FLOAT:
+					break;
+				case TB_PTR:
+					value.node = tb_inst_bitcast(p->func, value.node, TB_TYPE_INTPTR);
+					value.node = tb_inst_int2ptr(p->func, value.node);
+					break;
+				}
+				break;
+			case TB_PTR:
+				switch (value.node->dt.type) {
+				case TB_INT:
+					value.node = tb_inst_ptr2int(p->func, value.node, dt);
+					break;
+				case TB_FLOAT:
+					value.node = tb_inst_ptr2int(p->func, value.node, TB_TYPE_INTPTR);
+					value.node = tb_inst_bitcast(p->func, value.node, dt);
+					break;
+				case TB_PTR:
+					break;
+				}
+				break;
+			}
+		}
+		return value;
+	case cgValue_Addr:
+		value.type = type;
+		return value;
+	case cgValue_Symbol:
+		GB_PANIC("should be handled above");
+		break;
+	case cgValue_Multi:
+		GB_PANIC("cannot transmute multiple values at once");
+		break;
+	}
+	return value;
+
+}
+gb_internal cgValue cg_emit_byte_swap(cgProcedure *p, cgValue value, Type *end_type) {
+	GB_ASSERT(type_size_of(value.type) == type_size_of(end_type));
+
+	if (type_size_of(value.type) < 2) {
+		return value;
+	}
+
+	if (is_type_float(value.type)) {
+		i64 sz = type_size_of(value.type);
+		Type *integer_type = nullptr;
+		switch (sz) {
+		case 2: integer_type = t_u16; break;
+		case 4: integer_type = t_u32; break;
+		case 8: integer_type = t_u64; break;
+		}
+		GB_ASSERT(integer_type != nullptr);
+		value = cg_emit_transmute(p, value, integer_type);
+	}
+
+	GB_ASSERT(value.kind == cgValue_Value);
+
+	// TODO(bill): bswap
+	// value.node = tb_inst_bswap(p->func, value.node);
+	return cg_emit_transmute(p, value, end_type);
+}
+
+gb_internal cgValue cg_emit_comp_records(cgProcedure *p, TokenKind op_kind, cgValue left, cgValue right, Type *type) {
+	GB_ASSERT((is_type_struct(type) || is_type_union(type)) && is_type_comparable(type));
+	cgValue left_ptr  = cg_address_from_load_or_generate_local(p, left);
+	cgValue right_ptr = cg_address_from_load_or_generate_local(p, right);
+	cgValue res = {};
+	if (type_size_of(type) == 0) {
+		switch (op_kind) {
+		case Token_CmpEq:
+			return cg_const_bool(p, t_bool, true);
+		case Token_NotEq:
+			return cg_const_bool(p, t_bool, false);
+		}
+		GB_PANIC("invalid operator");
+	}
+	TEMPORARY_ALLOCATOR_GUARD();
+	if (is_type_simple_compare(type)) {
+		// TODO(bill): Test to see if this is actually faster!!!!
+		auto args = slice_make<cgValue>(temporary_allocator(), 3);
+		args[0] = cg_emit_conv(p, left_ptr, t_rawptr);
+		args[1] = cg_emit_conv(p, right_ptr, t_rawptr);
+		args[2] = cg_const_int(p, t_int, type_size_of(type));
+		res = cg_emit_runtime_call(p, "memory_equal", args);
+	} else {
+		cgProcedure *equal_proc = cg_equal_proc_for_type(p->module, type);
+		cgValue value = cg_value(tb_inst_get_symbol_address(p->func, equal_proc->symbol), equal_proc->type);
+		auto args = slice_make<cgValue>(temporary_allocator(), 2);
+		args[0] = cg_emit_conv(p, left_ptr, t_rawptr);
+		args[1] = cg_emit_conv(p, right_ptr, t_rawptr);
+		res = cg_emit_call(p, value, args);
+	}
+	if (op_kind == Token_NotEq) {
+		res = cg_emit_unary_arith(p, Token_Not, res, res.type);
+	}
+	return res;
+}
+
+gb_internal cgValue cg_emit_comp(cgProcedure *p, TokenKind op_kind, cgValue left, cgValue right) {
+	GB_ASSERT(gb_is_between(op_kind, Token__ComparisonBegin+1, Token__ComparisonEnd-1));
+
+	Type *a = core_type(left.type);
+	Type *b = core_type(right.type);
+
+	cgValue nil_check = {};
+	if (is_type_array_like(left.type) || is_type_array_like(right.type)) {
+		// don't do `nil` check if it is array-like
+	} else if (is_type_untyped_nil(left.type)) {
+		nil_check = cg_emit_comp_against_nil(p, op_kind, right);
+	} else if (is_type_untyped_nil(right.type)) {
+		nil_check = cg_emit_comp_against_nil(p, op_kind, left);
+	}
+	if (nil_check.node != nullptr) {
+		return nil_check;
+	}
+
+	if (are_types_identical(a, b)) {
+		// NOTE(bill): No need for a conversion
+	} /*else if (cg_is_const(left) || cg_is_const_nil(left)) {
+		left = cg_emit_conv(p, left, right.type);
+	} else if (cg_is_const(right) || cg_is_const_nil(right)) {
+		right = cg_emit_conv(p, right, left.type);
+	}*/ else {
+		Type *lt = left.type;
+		Type *rt = right.type;
+
+		lt = left.type;
+		rt = right.type;
+		i64 ls = type_size_of(lt);
+		i64 rs = type_size_of(rt);
+
+		// NOTE(bill): Quick heuristic, larger types are usually the target type
+		if (ls < rs) {
+			left = cg_emit_conv(p, left, rt);
+		} else if (ls > rs) {
+			right = cg_emit_conv(p, right, lt);
+		} else {
+			if (is_type_union(rt)) {
+				left = cg_emit_conv(p, left, rt);
+			} else {
+				right = cg_emit_conv(p, right, lt);
+			}
+		}
+	}
+
+	a = core_type(left.type);
+	b = core_type(right.type);
+	left  = cg_flatten_value(p, left);
+	right = cg_flatten_value(p, right);
+
+
+	if (is_type_matrix(a) && (op_kind == Token_CmpEq || op_kind == Token_NotEq)) {
+		GB_PANIC("TODO(bill): cg_emit_comp matrix");
+		// Type *tl = base_type(a);
+		// lbValue lhs = lb_address_from_load_or_generate_local(p, left);
+		// lbValue rhs = lb_address_from_load_or_generate_local(p, right);
+
+
+		// // TODO(bill): Test to see if this is actually faster!!!!
+		// auto args = array_make<lbValue>(permanent_allocator(), 3);
+		// args[0] = lb_emit_conv(p, lhs, t_rawptr);
+		// args[1] = lb_emit_conv(p, rhs, t_rawptr);
+		// args[2] = lb_const_int(p->module, t_int, type_size_of(tl));
+		// lbValue val = lb_emit_runtime_call(p, "memory_compare", args);
+		// lbValue res = lb_emit_comp(p, op_kind, val, lb_const_nil(p->module, val.type));
+		// return lb_emit_conv(p, res, t_bool);
+	}
+	if (is_type_array_like(a)) {
+		GB_PANIC("TODO(bill): cg_emit_comp is_type_array_like");
+		// Type *tl = base_type(a);
+		// lbValue lhs = lb_address_from_load_or_generate_local(p, left);
+		// lbValue rhs = lb_address_from_load_or_generate_local(p, right);
+
+
+		// TokenKind cmp_op = Token_And;
+		// lbValue res = lb_const_bool(p->module, t_bool, true);
+		// if (op_kind == Token_NotEq) {
+		// 	res = lb_const_bool(p->module, t_bool, false);
+		// 	cmp_op = Token_Or;
+		// } else if (op_kind == Token_CmpEq) {
+		// 	res = lb_const_bool(p->module, t_bool, true);
+		// 	cmp_op = Token_And;
+		// }
+
+		// bool inline_array_arith = lb_can_try_to_inline_array_arith(tl);
+		// i32 count = 0;
+		// switch (tl->kind) {
+		// case Type_Array:           count = cast(i32)tl->Array.count;           break;
+		// case Type_EnumeratedArray: count = cast(i32)tl->EnumeratedArray.count; break;
+		// }
+
+		// if (inline_array_arith) {
+		// 	// inline
+		// 	lbAddr val = lb_add_local_generated(p, t_bool, false);
+		// 	lb_addr_store(p, val, res);
+		// 	for (i32 i = 0; i < count; i++) {
+		// 		lbValue x = lb_emit_load(p, lb_emit_array_epi(p, lhs, i));
+		// 		lbValue y = lb_emit_load(p, lb_emit_array_epi(p, rhs, i));
+		// 		lbValue cmp = lb_emit_comp(p, op_kind, x, y);
+		// 		lbValue new_res = lb_emit_arith(p, cmp_op, lb_addr_load(p, val), cmp, t_bool);
+		// 		lb_addr_store(p, val, lb_emit_conv(p, new_res, t_bool));
+		// 	}
+
+		// 	return lb_addr_load(p, val);
+		// } else {
+		// 	if (is_type_simple_compare(tl) && (op_kind == Token_CmpEq || op_kind == Token_NotEq)) {
+		// 		// TODO(bill): Test to see if this is actually faster!!!!
+		// 		auto args = array_make<lbValue>(permanent_allocator(), 3);
+		// 		args[0] = lb_emit_conv(p, lhs, t_rawptr);
+		// 		args[1] = lb_emit_conv(p, rhs, t_rawptr);
+		// 		args[2] = lb_const_int(p->module, t_int, type_size_of(tl));
+		// 		lbValue val = lb_emit_runtime_call(p, "memory_compare", args);
+		// 		lbValue res = lb_emit_comp(p, op_kind, val, lb_const_nil(p->module, val.type));
+		// 		return lb_emit_conv(p, res, t_bool);
+		// 	} else {
+		// 		lbAddr val = lb_add_local_generated(p, t_bool, false);
+		// 		lb_addr_store(p, val, res);
+		// 		auto loop_data = lb_loop_start(p, count, t_i32);
+		// 		{
+		// 			lbValue i = loop_data.idx;
+		// 			lbValue x = lb_emit_load(p, lb_emit_array_ep(p, lhs, i));
+		// 			lbValue y = lb_emit_load(p, lb_emit_array_ep(p, rhs, i));
+		// 			lbValue cmp = lb_emit_comp(p, op_kind, x, y);
+		// 			lbValue new_res = lb_emit_arith(p, cmp_op, lb_addr_load(p, val), cmp, t_bool);
+		// 			lb_addr_store(p, val, lb_emit_conv(p, new_res, t_bool));
+		// 		}
+		// 		lb_loop_end(p, loop_data);
+
+		// 		return lb_addr_load(p, val);
+		// 	}
+		// }
+	}
+
+	if ((is_type_struct(a) || is_type_union(a)) && is_type_comparable(a)) {
+		return cg_emit_comp_records(p, op_kind, left, right, a);
+	}
+
+	if ((is_type_struct(b) || is_type_union(b)) && is_type_comparable(b)) {
+		return cg_emit_comp_records(p, op_kind, left, right, b);
+	}
+
+	if (is_type_string(a)) {
+		if (is_type_cstring(a)) {
+			left  = cg_emit_conv(p, left, t_string);
+			right = cg_emit_conv(p, right, t_string);
+		}
+
+		char const *runtime_procedure = nullptr;
+		switch (op_kind) {
+		case Token_CmpEq: runtime_procedure = "string_eq"; break;
+		case Token_NotEq: runtime_procedure = "string_ne"; break;
+		case Token_Lt:    runtime_procedure = "string_lt"; break;
+		case Token_Gt:    runtime_procedure = "string_gt"; break;
+		case Token_LtEq:  runtime_procedure = "string_le"; break;
+		case Token_GtEq:  runtime_procedure = "string_gt"; break;
+		}
+		GB_ASSERT(runtime_procedure != nullptr);
+
+		auto args = slice_make<cgValue>(permanent_allocator(), 2);
+		args[0] = left;
+		args[1] = right;
+		return cg_emit_runtime_call(p, runtime_procedure, args);
+	}
+
+	if (is_type_complex(a)) {
+		char const *runtime_procedure = "";
+		i64 sz = 8*type_size_of(a);
+		switch (sz) {
+		case 32:
+			switch (op_kind) {
+			case Token_CmpEq: runtime_procedure = "complex32_eq"; break;
+			case Token_NotEq: runtime_procedure = "complex32_ne"; break;
+			}
+			break;
+		case 64:
+			switch (op_kind) {
+			case Token_CmpEq: runtime_procedure = "complex64_eq"; break;
+			case Token_NotEq: runtime_procedure = "complex64_ne"; break;
+			}
+			break;
+		case 128:
+			switch (op_kind) {
+			case Token_CmpEq: runtime_procedure = "complex128_eq"; break;
+			case Token_NotEq: runtime_procedure = "complex128_ne"; break;
+			}
+			break;
+		}
+		GB_ASSERT(runtime_procedure != nullptr);
+
+		GB_PANIC("TODO(bill): cg_emit_runtime_call");
+		// auto args = array_make<lbValue>(permanent_allocator(), 2);
+		// args[0] = left;
+		// args[1] = right;
+		// return lb_emit_runtime_call(p, runtime_procedure, args);
+	}
+
+	if (is_type_quaternion(a)) {
+		char const *runtime_procedure = "";
+		i64 sz = 8*type_size_of(a);
+		switch (sz) {
+		case 64:
+			switch (op_kind) {
+			case Token_CmpEq: runtime_procedure = "quaternion64_eq"; break;
+			case Token_NotEq: runtime_procedure = "quaternion64_ne"; break;
+			}
+			break;
+		case 128:
+			switch (op_kind) {
+			case Token_CmpEq: runtime_procedure = "quaternion128_eq"; break;
+			case Token_NotEq: runtime_procedure = "quaternion128_ne"; break;
+			}
+			break;
+		case 256:
+			switch (op_kind) {
+			case Token_CmpEq: runtime_procedure = "quaternion256_eq"; break;
+			case Token_NotEq: runtime_procedure = "quaternion256_ne"; break;
+			}
+			break;
+		}
+		GB_ASSERT(runtime_procedure != nullptr);
+
+		GB_PANIC("TODO(bill): cg_emit_runtime_call");
+		// auto args = array_make<lbValue>(permanent_allocator(), 2);
+		// args[0] = left;
+		// args[1] = right;
+		// return lb_emit_runtime_call(p, runtime_procedure, args);
+	}
+
+	if (is_type_bit_set(a)) {
+		switch (op_kind) {
+		case Token_Lt:
+		case Token_LtEq:
+		case Token_Gt:
+		case Token_GtEq:
+			{
+				Type *it = bit_set_to_int(a);
+				cgValue lhs = cg_emit_transmute(p, left, it);
+				cgValue rhs = cg_emit_transmute(p, right, it);
+				cgValue res = cg_emit_arith(p, Token_And, lhs, rhs, it);
+				GB_ASSERT(lhs.kind == cgValue_Value);
+				GB_ASSERT(rhs.kind == cgValue_Value);
+				GB_ASSERT(res.kind == cgValue_Value);
+
+				if (op_kind == Token_Lt || op_kind == Token_LtEq) {
+					// (lhs & rhs) == lhs
+					res = cg_value(tb_inst_cmp_eq(p->func, res.node, lhs.node), t_bool);
+				} else if (op_kind == Token_Gt || op_kind == Token_GtEq) {
+					// (lhs & rhs) == rhs
+					res = cg_value(tb_inst_cmp_eq(p->func, res.node, rhs.node), t_bool);
+				}
+
+				// NOTE(bill): Strict subsets
+				if (op_kind == Token_Lt || op_kind == Token_Gt) {
+					// res &~ (lhs == rhs)
+					cgValue eq = cg_value(tb_inst_cmp_eq(p->func, lhs.node, rhs.node), t_bool);
+					res = cg_emit_arith(p, Token_AndNot, res, eq, t_bool);
+				}
+				return res;
+			}
+
+		case Token_CmpEq:
+			GB_ASSERT(left.kind  == cgValue_Value);
+			GB_ASSERT(right.kind == cgValue_Value);
+			return cg_value(tb_inst_cmp_eq(p->func, left.node, right.node), t_bool);
+		case Token_NotEq:
+			GB_ASSERT(left.kind  == cgValue_Value);
+			GB_ASSERT(right.kind == cgValue_Value);
+			return cg_value(tb_inst_cmp_ne(p->func, left.node, right.node), t_bool);
+		}
+	}
+
+	if (op_kind != Token_CmpEq && op_kind != Token_NotEq) {
+		Type *t = left.type;
+		if (is_type_integer(t) && is_type_different_to_arch_endianness(t)) {
+			Type *platform_type = integer_endian_type_to_platform_type(t);
+			cgValue x = cg_emit_byte_swap(p, left, platform_type);
+			cgValue y = cg_emit_byte_swap(p, right, platform_type);
+			left = x;
+			right = y;
+		} else if (is_type_float(t) && is_type_different_to_arch_endianness(t)) {
+			Type *platform_type = integer_endian_type_to_platform_type(t);
+			cgValue x = cg_emit_conv(p, left, platform_type);
+			cgValue y = cg_emit_conv(p, right, platform_type);
+			left = x;
+			right = y;
+		}
+	}
+
+	a = core_type(left.type);
+	b = core_type(right.type);
+
+
+	if (is_type_integer(a) ||
+	    is_type_boolean(a) ||
+	    is_type_pointer(a) ||
+	    is_type_multi_pointer(a) ||
+	    is_type_proc(a) ||
+	    is_type_enum(a) ||
+	    is_type_typeid(a)) {
+	    	TB_Node *lhs = left.node;
+		TB_Node *rhs = right.node;
+		TB_Node *res = nullptr;
+
+		bool is_signed = is_type_integer(left.type) && !is_type_unsigned(left.type);
+		switch (op_kind) {
+		case Token_CmpEq: res = tb_inst_cmp_eq(p->func, lhs, rhs); break;
+		case Token_NotEq: res = tb_inst_cmp_ne(p->func, lhs, rhs); break;
+		case Token_Gt:    res = tb_inst_cmp_igt(p->func, lhs, rhs, is_signed); break;
+		case Token_GtEq:  res = tb_inst_cmp_ige(p->func, lhs, rhs, is_signed); break;
+		case Token_Lt:    res = tb_inst_cmp_ilt(p->func, lhs, rhs, is_signed); break;
+		case Token_LtEq:  res = tb_inst_cmp_ile(p->func, lhs, rhs, is_signed); break;
+		}
+
+		GB_ASSERT(res != nullptr);
+		return cg_value(res, t_bool);
+	} else if (is_type_float(a)) {
+	    	TB_Node *lhs = left.node;
+		TB_Node *rhs = right.node;
+		TB_Node *res = nullptr;
+		switch (op_kind) {
+		case Token_CmpEq: res = tb_inst_cmp_eq(p->func, lhs, rhs);  break;
+		case Token_NotEq: res = tb_inst_cmp_ne(p->func, lhs, rhs);  break;
+		case Token_Gt:    res = tb_inst_cmp_fgt(p->func, lhs, rhs); break;
+		case Token_GtEq:  res = tb_inst_cmp_fge(p->func, lhs, rhs); break;
+		case Token_Lt:    res = tb_inst_cmp_flt(p->func, lhs, rhs); break;
+		case Token_LtEq:  res = tb_inst_cmp_fle(p->func, lhs, rhs); break;
+		}
+		GB_ASSERT(res != nullptr);
+		return cg_value(res, t_bool);
+	} else if (is_type_simd_vector(a)) {
+		GB_PANIC("TODO(bill): #simd vector");
+		// LLVMValueRef mask = nullptr;
+		// Type *elem = base_array_type(a);
+		// if (is_type_float(elem)) {
+		// 	LLVMRealPredicate pred = {};
+		// 	switch (op_kind) {
+		// 	case Token_CmpEq: pred = LLVMRealOEQ; break;
+		// 	case Token_NotEq: pred = LLVMRealONE; break;
+		// 	}
+		// 	mask = LLVMBuildFCmp(p->builder, pred, left.value, right.value, "");
+		// } else {
+		// 	LLVMIntPredicate pred = {};
+		// 	switch (op_kind) {
+		// 	case Token_CmpEq: pred = LLVMIntEQ; break;
+		// 	case Token_NotEq: pred = LLVMIntNE; break;
+		// 	}
+		// 	mask = LLVMBuildICmp(p->builder, pred, left.value, right.value, "");
+		// }
+		// GB_ASSERT_MSG(mask != nullptr, "Unhandled comparison kind %s (%s) %.*s %s (%s)", type_to_string(left.type), type_to_string(base_type(left.type)), LIT(token_strings[op_kind]), type_to_string(right.type), type_to_string(base_type(right.type)));
+
+		// /* NOTE(bill, 2022-05-28):
+		// 	Thanks to Per Vognsen, sign extending <N x i1> to
+		// 	a vector of the same width as the input vector, bit casting to an integer,
+		// 	and then comparing against zero is the better option
+		// 	See: https://lists.llvm.org/pipermail/llvm-dev/2012-September/053046.html
+
+		// 	// Example assuming 128-bit vector
+
+		// 	%1 = <4 x float> ...
+		// 	%2 = <4 x float> ...
+		// 	%3 = fcmp oeq <4 x float> %1, %2
+		// 	%4 = sext <4 x i1> %3 to <4 x i32>
+		// 	%5 = bitcast <4 x i32> %4 to i128
+		// 	%6 = icmp ne i128 %5, 0
+		// 	br i1 %6, label %true1, label %false2
+
+		// 	This will result in 1 cmpps + 1 ptest + 1 br
+		// 	(even without SSE4.1, contrary to what the mail list states, because of pmovmskb)
+
+		// */
+
+		// unsigned count = cast(unsigned)get_array_type_count(a);
+		// unsigned elem_sz = cast(unsigned)(type_size_of(elem)*8);
+		// LLVMTypeRef mask_type = LLVMVectorType(LLVMIntTypeInContext(p->module->ctx, elem_sz), count);
+		// mask = LLVMBuildSExtOrBitCast(p->builder, mask, mask_type, "");
+
+		// LLVMTypeRef mask_int_type = LLVMIntTypeInContext(p->module->ctx, cast(unsigned)(8*type_size_of(a)));
+		// LLVMValueRef mask_int = LLVMBuildBitCast(p->builder, mask, mask_int_type, "");
+		// res.value = LLVMBuildICmp(p->builder, LLVMIntNE, mask_int, LLVMConstNull(LLVMTypeOf(mask_int)), "");
+		// return res;
+	}
+
+	GB_PANIC("Unhandled comparison kind %s (%s) %.*s %s (%s)", type_to_string(left.type), type_to_string(base_type(left.type)), LIT(token_strings[op_kind]), type_to_string(right.type), type_to_string(base_type(right.type)));
+	return {};
+}
+
+gb_internal cgValue cg_emit_comp_against_nil(cgProcedure *p, TokenKind op_kind, cgValue x) {
+	GB_ASSERT(op_kind == Token_CmpEq || op_kind == Token_NotEq);
+	x = cg_flatten_value(p, x);
+	cgValue res = {};
+	Type *t = x.type;
+
+	TB_DataType dt = cg_data_type(t);
+
+	Type *bt = base_type(t);
+	TypeKind type_kind = bt->kind;
+
+	switch (type_kind) {
+	case Type_Basic:
+		switch (bt->Basic.kind) {
+		case Basic_rawptr:
+		case Basic_cstring:
+			GB_ASSERT(x.kind == cgValue_Value);
+			if (op_kind == Token_CmpEq) {
+				return cg_value(tb_inst_cmp_eq(p->func, x.node, tb_inst_uint(p->func, dt, 0)), t_bool);
+			} else if (op_kind == Token_NotEq) {
+				return cg_value(tb_inst_cmp_ne(p->func, x.node, tb_inst_uint(p->func, dt, 0)), t_bool);
+			}
+			break;
+		case Basic_any:
+			{
+				GB_ASSERT(x.kind == cgValue_Addr);
+				// // TODO(bill): is this correct behaviour for nil comparison for any?
+				cgValue data = cg_emit_struct_ev(p, x, 0);
+				cgValue id   = cg_emit_struct_ev(p, x, 1);
+
+				if (op_kind == Token_CmpEq) {
+					TB_Node *a = tb_inst_cmp_eq(p->func, data.node, tb_inst_uint(p->func, data.node->dt, 0));
+					TB_Node *b = tb_inst_cmp_eq(p->func, id.node,   tb_inst_uint(p->func, id.node->dt,   0));
+					TB_Node *c = tb_inst_or(p->func, a, b);
+					return cg_value(c, t_bool);
+				} else if (op_kind == Token_NotEq) {
+					TB_Node *a = tb_inst_cmp_ne(p->func, data.node, tb_inst_uint(p->func, data.node->dt, 0));
+					TB_Node *b = tb_inst_cmp_ne(p->func, id.node,   tb_inst_uint(p->func, id.node->dt,   0));
+					TB_Node *c = tb_inst_and(p->func, a, b);
+					return cg_value(c, t_bool);
+				}
+			}
+			break;
+		case Basic_typeid:
+			cgValue invalid_typeid = cg_const_value(p, t_typeid, exact_value_i64(0));
+			return cg_emit_comp(p, op_kind, x, invalid_typeid);
+		}
+		break;
+
+	case Type_Enum:
+	case Type_Pointer:
+	case Type_MultiPointer:
+	case Type_Proc:
+	case Type_BitSet:
+		GB_ASSERT(x.kind == cgValue_Value);
+		if (op_kind == Token_CmpEq) {
+			return cg_value(tb_inst_cmp_eq(p->func, x.node, tb_inst_uint(p->func, dt, 0)), t_bool);
+		} else if (op_kind == Token_NotEq) {
+			return cg_value(tb_inst_cmp_ne(p->func, x.node, tb_inst_uint(p->func, dt, 0)), t_bool);
+		}
+		break;
+
+	case Type_Slice:
+	case Type_DynamicArray:
+	case Type_Map:
+		{
+			// NOTE(bill): all of their data "pointer-like" fields are at the 0-index
+			cgValue data = cg_emit_struct_ev(p, x, 0);
+			if (op_kind == Token_CmpEq) {
+				TB_Node *a = tb_inst_cmp_eq(p->func, data.node, tb_inst_uint(p->func, data.node->dt, 0));
+				return cg_value(a, t_bool);
+			} else if (op_kind == Token_NotEq) {
+				TB_Node *a = tb_inst_cmp_ne(p->func, data.node, tb_inst_uint(p->func, data.node->dt, 0));
+				return cg_value(a, t_bool);
+			}
+		}
+		break;
+
+	case Type_Union:
+		{
+			if (type_size_of(t) == 0) {
+				return cg_const_bool(p, t_bool, op_kind == Token_CmpEq);
+			} else if (is_type_union_maybe_pointer(t)) {
+				cgValue tag = cg_emit_transmute(p, x, t_rawptr);
+				return cg_emit_comp_against_nil(p, op_kind, tag);
+			} else {
+				GB_ASSERT("TODO(bill): cg_emit_union_tag_value");
+				// cgValue tag = cg_emit_union_tag_value(p, x);
+				// return cg_emit_comp(p, op_kind, tag, cg_zero(p->module, tag.type));
+			}
+		}
+		break;
+	case Type_Struct:
+		GB_PANIC("TODO(bill): cg_emit_struct_ev");
+		// if (is_type_soa_struct(t)) {
+		// 	Type *bt = base_type(t);
+		// 	if (bt->Struct.soa_kind == StructSoa_Slice) {
+		// 		LLVMValueRef the_value = {};
+		// 		if (bt->Struct.fields.count == 0) {
+		// 			cgValue len = cg_soa_struct_len(p, x);
+		// 			the_value = len.value;
+		// 		} else {
+		// 			cgValue first_field = cg_emit_struct_ev(p, x, 0);
+		// 			the_value = first_field.value;
+		// 		}
+		// 		if (op_kind == Token_CmpEq) {
+		// 			res.value = LLVMBuildIsNull(p->builder, the_value, "");
+		// 			return res;
+		// 		} else if (op_kind == Token_NotEq) {
+		// 			res.value = LLVMBuildIsNotNull(p->builder, the_value, "");
+		// 			return res;
+		// 		}
+		// 	} else if (bt->Struct.soa_kind == StructSoa_Dynamic) {
+		// 		LLVMValueRef the_value = {};
+		// 		if (bt->Struct.fields.count == 0) {
+		// 			cgValue cap = cg_soa_struct_cap(p, x);
+		// 			the_value = cap.value;
+		// 		} else {
+		// 			cgValue first_field = cg_emit_struct_ev(p, x, 0);
+		// 			the_value = first_field.value;
+		// 		}
+		// 		if (op_kind == Token_CmpEq) {
+		// 			res.value = LLVMBuildIsNull(p->builder, the_value, "");
+		// 			return res;
+		// 		} else if (op_kind == Token_NotEq) {
+		// 			res.value = LLVMBuildIsNotNull(p->builder, the_value, "");
+		// 			return res;
+		// 		}
+		// 	}
+		// } else if (is_type_struct(t) && type_has_nil(t)) {
+		// 	auto args = array_make<cgValue>(permanent_allocator(), 2);
+		// 	cgValue lhs = cg_address_from_load_or_generate_local(p, x);
+		// 	args[0] = cg_emit_conv(p, lhs, t_rawptr);
+		// 	args[1] = cg_const_int(p->module, t_int, type_size_of(t));
+		// 	cgValue val = cg_emit_runtime_call(p, "memory_compare_zero", args);
+		// 	cgValue res = cg_emit_comp(p, op_kind, val, cg_const_int(p->module, t_int, 0));
+		// 	return res;
+		// }
+		break;
+	}
+	GB_PANIC("Unknown handled type: %s -> %s", type_to_string(t), type_to_string(bt));
+	return {};
+}
+
+gb_internal cgValue cg_emit_conv(cgProcedure *p, cgValue value, Type *t) {
+	t = reduce_tuple_to_single_type(t);
+
+	value = cg_flatten_value(p, value);
+
+	Type *src_type = value.type;
+	if (are_types_identical(t, src_type)) {
+		return value;
+	}
+
+	if (is_type_untyped_uninit(src_type)) {
+		// return cg_const_undef(m, t);
+		return cg_const_nil(p, t);
+	}
+	if (is_type_untyped_nil(src_type)) {
+		return cg_const_nil(p, t);
+	}
+
+	Type *src = core_type(src_type);
+	Type *dst = core_type(t);
+	GB_ASSERT(src != nullptr);
+	GB_ASSERT(dst != nullptr);
+
+	if (are_types_identical(src, dst)) {
+		return cg_emit_transmute(p, value, t);
+	}
+
+	TB_DataType st = cg_data_type(src);
+	if (value.kind == cgValue_Value && !TB_IS_VOID_TYPE(value.node->dt)) {
+		st = value.node->dt;
+	}
+	TB_DataType dt = cg_data_type(t);
+
+	if (is_type_integer(src) && is_type_integer(dst)) {
+		GB_ASSERT(src->kind == Type_Basic &&
+		          dst->kind == Type_Basic);
+		GB_ASSERT(value.kind == cgValue_Value);
+
+		i64 sz = type_size_of(default_type(src));
+		i64 dz = type_size_of(default_type(dst));
+
+		if (sz == dz) {
+			if (dz > 1 && !types_have_same_internal_endian(src, dst)) {
+				return cg_emit_byte_swap(p, value, t);
+			}
+			value.type = t;
+			return value;
+		}
+
+		if (sz > 1 && is_type_different_to_arch_endianness(src)) {
+			Type *platform_src_type = integer_endian_type_to_platform_type(src);
+			value = cg_emit_byte_swap(p, value, platform_src_type);
+		}
+
+		TB_Node* (*op)(TB_Function* f, TB_Node* src, TB_DataType dt) = tb_inst_trunc;
+
+		if (dz < sz) {
+			op = tb_inst_trunc;
+		} else if (dz == sz) {
+			op = tb_inst_bitcast;
+		} else if (dz > sz) {
+			op = is_type_unsigned(src) ? tb_inst_zxt : tb_inst_sxt; // zero extent
+		}
+
+		if (dz > 1 && is_type_different_to_arch_endianness(dst)) {
+			Type *platform_dst_type = integer_endian_type_to_platform_type(dst);
+
+			cgValue res = cg_value(op(p->func, value.node, cg_data_type(platform_dst_type)), platform_dst_type);
+			return cg_emit_byte_swap(p, res, t);
+		} else {
+			return cg_value(op(p->func, value.node, dt), t);
+		}
+	}
+
+	// boolean -> boolean/integer
+	if (is_type_boolean(src) && (is_type_boolean(dst) || is_type_integer(dst))) {
+		TB_Node *v = tb_inst_cmp_ne(p->func, value.node, tb_inst_uint(p->func, st, 0));
+		return cg_value(tb_inst_zxt(p->func, v, dt), t);
+	}
+
+	// integer -> boolean
+	if (is_type_integer(src) && is_type_boolean(dst)) {
+		TB_Node *v = tb_inst_cmp_ne(p->func, value.node, tb_inst_uint(p->func, st, 0));
+		return cg_value(tb_inst_zxt(p->func, v, dt), t);
+	}
+
+	if (is_type_cstring(src) && is_type_u8_ptr(dst)) {
+		return cg_emit_transmute(p, value, dst);
+	}
+	if (is_type_u8_ptr(src) && is_type_cstring(dst)) {
+		return cg_emit_transmute(p, value, dst);
+	}
+	if (is_type_cstring(src) && is_type_u8_multi_ptr(dst)) {
+		return cg_emit_transmute(p, value, dst);
+	}
+	if (is_type_u8_multi_ptr(src) && is_type_cstring(dst)) {
+		return cg_emit_transmute(p, value, dst);
+	}
+	if (is_type_cstring(src) && is_type_rawptr(dst)) {
+		return cg_emit_transmute(p, value, dst);
+	}
+	if (is_type_rawptr(src) && is_type_cstring(dst)) {
+		return cg_emit_transmute(p, value, dst);
+	}
+
+
+	if (are_types_identical(src, t_cstring) && are_types_identical(dst, t_string)) {
+		TEMPORARY_ALLOCATOR_GUARD();
+		cgValue c = cg_emit_conv(p, value, t_cstring);
+		auto args = slice_make<cgValue>(temporary_allocator(), 1);
+		args[0] = c;
+		cgValue s = cg_emit_runtime_call(p, "cstring_to_string", args);
+		return cg_emit_conv(p, s, dst);
+	}
+
+	// float -> float
+	if (is_type_float(src) && is_type_float(dst)) {
+		i64 sz = type_size_of(src);
+		i64 dz = type_size_of(dst);
+
+		if (sz == 2 || dz == 2) {
+			GB_PANIC("TODO(bill): f16 conversions");
+		}
+
+
+		if (dz == sz) {
+			if (types_have_same_internal_endian(src, dst)) {
+				return cg_value(value.node, t);
+			} else {
+				return cg_emit_byte_swap(p, value, t);
+			}
+		}
+
+		if (is_type_different_to_arch_endianness(src) || is_type_different_to_arch_endianness(dst)) {
+			Type *platform_src_type = integer_endian_type_to_platform_type(src);
+			Type *platform_dst_type = integer_endian_type_to_platform_type(dst);
+			cgValue res = {};
+			res = cg_emit_conv(p, value, platform_src_type);
+			res = cg_emit_conv(p, res, platform_dst_type);
+			if (is_type_different_to_arch_endianness(dst)) {
+				res = cg_emit_byte_swap(p, res, t);
+			}
+			return cg_emit_conv(p, res, t);
+		}
+
+
+		if (dz >= sz) {
+			return cg_value(tb_inst_fpxt(p->func, value.node, dt), t);
+		}
+		return cg_value(tb_inst_trunc(p->func, value.node, dt), t);
+	}
+
+	if (is_type_complex(src) && is_type_complex(dst)) {
+		GB_PANIC("TODO(bill): complex -> complex");
+	}
+
+	if (is_type_quaternion(src) && is_type_quaternion(dst)) {
+		// @QuaternionLayout
+		GB_PANIC("TODO(bill): quaternion -> quaternion");
+	}
+	if (is_type_integer(src) && is_type_complex(dst)) {
+		GB_PANIC("TODO(bill): int -> complex");
+	}
+	if (is_type_float(src) && is_type_complex(dst)) {
+		GB_PANIC("TODO(bill): float -> complex");
+	}
+	if (is_type_integer(src) && is_type_quaternion(dst)) {
+		GB_PANIC("TODO(bill): int -> quaternion");
+	}
+	if (is_type_float(src) && is_type_quaternion(dst)) {
+		GB_PANIC("TODO(bill): float -> quaternion");
+	}
+	if (is_type_complex(src) && is_type_quaternion(dst)) {
+		GB_PANIC("TODO(bill): complex -> quaternion");
+	}
+
+
+	// float <-> integer
+	if (is_type_float(src) && is_type_integer(dst)) {
+		if (is_type_different_to_arch_endianness(src) || is_type_different_to_arch_endianness(dst)) {
+			Type *platform_src_type = integer_endian_type_to_platform_type(src);
+			Type *platform_dst_type = integer_endian_type_to_platform_type(dst);
+			cgValue res = {};
+			res = cg_emit_conv(p, value, platform_src_type);
+			res = cg_emit_conv(p, res, platform_dst_type);
+			return cg_emit_conv(p, res, t);
+		}
+
+		// if (is_type_integer_128bit(dst)) {
+		// 	TEMPORARY_ALLOCATOR_GUARD();
+
+		// 	auto args = array_make<lbValue>(temporary_allocator(), 1);
+		// 	args[0] = value;
+		// 	char const *call = "fixunsdfdi";
+		// 	if (is_type_unsigned(dst)) {
+		// 		call = "fixunsdfti";
+		// 	}
+		// 	lbValue res_i128 = lb_emit_runtime_call(p, call, args);
+		// 	return lb_emit_conv(p, res_i128, t);
+		// }
+
+		bool is_signed = !is_type_unsigned(dst);
+		return cg_value(tb_inst_float2int(p->func, value.node, dt, is_signed), t);
+	}
+	if (is_type_integer(src) && is_type_float(dst)) {
+		if (is_type_different_to_arch_endianness(src) || is_type_different_to_arch_endianness(dst)) {
+			Type *platform_src_type = integer_endian_type_to_platform_type(src);
+			Type *platform_dst_type = integer_endian_type_to_platform_type(dst);
+			cgValue res = {};
+			res = cg_emit_conv(p, value, platform_src_type);
+			res = cg_emit_conv(p, res, platform_dst_type);
+			if (is_type_different_to_arch_endianness(dst)) {
+				res = cg_emit_byte_swap(p, res, t);
+			}
+			return cg_emit_conv(p, res, t);
+		}
+
+		// if (is_type_integer_128bit(src)) {
+		// 	TEMPORARY_ALLOCATOR_GUARD();
+
+		// 	auto args = array_make<lbValue>(temporary_allocator(), 1);
+		// 	args[0] = value;
+		// 	char const *call = "floattidf";
+		// 	if (is_type_unsigned(src)) {
+		// 		call = "floattidf_unsigned";
+		// 	}
+		// 	lbValue res_f64 = lb_emit_runtime_call(p, call, args);
+		// 	return lb_emit_conv(p, res_f64, t);
+		// }
+
+		bool is_signed = !is_type_unsigned(dst);
+		return cg_value(tb_inst_int2float(p->func, value.node, dt, is_signed), t);
+	}
+
+	if (is_type_simd_vector(dst)) {
+		GB_PANIC("TODO(bill): ? -> #simd vector");
+	}
+
+
+	// Pointer <-> uintptr
+	if (is_type_pointer(src) && is_type_uintptr(dst)) {
+		return cg_value(tb_inst_ptr2int(p->func, value.node, dt), t);
+	}
+	if (is_type_uintptr(src) && is_type_pointer(dst)) {
+		return cg_value(tb_inst_int2ptr(p->func, value.node), t);
+	}
+	if (is_type_multi_pointer(src) && is_type_uintptr(dst)) {
+		return cg_value(tb_inst_ptr2int(p->func, value.node, dt), t);
+	}
+	if (is_type_uintptr(src) && is_type_multi_pointer(dst)) {
+		return cg_value(tb_inst_int2ptr(p->func, value.node), t);
+	}
+
+	if (is_type_union(dst)) {
+		GB_PANIC("TODO(bill): ? -> union");
+	}
+
+	// NOTE(bill): This has to be done before 'Pointer <-> Pointer' as it's
+	// subtype polymorphism casting
+	if (check_is_assignable_to_using_subtype(src_type, t)) {
+		GB_PANIC("TODO(bill): ? -> subtyping");
+	}
+
+	// Pointer <-> Pointer
+	if (is_type_pointer(src) && is_type_pointer(dst)) {
+		return cg_value(value.node, t);
+	}
+	if (is_type_multi_pointer(src) && is_type_pointer(dst)) {
+		return cg_value(value.node, t);
+	}
+	if (is_type_pointer(src) && is_type_multi_pointer(dst)) {
+		return cg_value(value.node, t);
+	}
+	if (is_type_multi_pointer(src) && is_type_multi_pointer(dst)) {
+		return cg_value(value.node, t);
+	}
+
+	// proc <-> proc
+	if (is_type_proc(src) && is_type_proc(dst)) {
+		return cg_value(value.node, t);
+	}
+
+	// pointer -> proc
+	if (is_type_pointer(src) && is_type_proc(dst)) {
+		return cg_value(value.node, t);
+	}
+	// proc -> pointer
+	if (is_type_proc(src) && is_type_pointer(dst)) {
+		return cg_value(value.node, t);
+	}
+
+	// []byte/[]u8 <-> string
+	if (is_type_u8_slice(src) && is_type_string(dst)) {
+		return cg_emit_transmute(p, value, t);
+	}
+	if (is_type_string(src) && is_type_u8_slice(dst)) {
+		return cg_emit_transmute(p, value, t);
+	}
+
+	if (is_type_matrix(dst) && !is_type_matrix(src)) {
+		GB_PANIC("TODO(bill): !matrix -> matrix");
+	}
+
+	if (is_type_matrix(dst) && is_type_matrix(src)) {
+		GB_PANIC("TODO(bill): matrix -> matrix");
+	}
+
+	if (is_type_any(dst)) {
+		if (is_type_untyped_nil(src) ||
+		    is_type_untyped_uninit(src)) {
+			return cg_const_nil(p, t);
+		}
+
+		cgAddr result = cg_add_local(p, t, nullptr, false);
+
+		Type *st = default_type(src_type);
+
+		cgValue data = cg_address_from_load_or_generate_local(p, value);
+		GB_ASSERT(is_type_pointer(data.type));
+		GB_ASSERT(is_type_typed(st));
+
+		data = cg_emit_conv(p, data, t_rawptr);
+
+		cgValue id = cg_typeid(p, st);
+		cgValue data_ptr = cg_emit_struct_ep(p, result.addr, 0);
+		cgValue id_ptr   = cg_emit_struct_ep(p, result.addr, 1);
+
+		cg_emit_store(p, data_ptr, data);
+		cg_emit_store(p, id_ptr,   id);
+
+		return cg_addr_load(p, result);
+	}
+
+	i64 src_sz = type_size_of(src);
+	i64 dst_sz = type_size_of(dst);
+
+	if (src_sz == dst_sz) {
+		// bit_set <-> integer
+		if (is_type_integer(src) && is_type_bit_set(dst)) {
+			cgValue v = cg_emit_conv(p, value, bit_set_to_int(dst));
+			return cg_emit_transmute(p, v, t);
+		}
+		if (is_type_bit_set(src) && is_type_integer(dst)) {
+			cgValue bs = cg_emit_transmute(p, value, bit_set_to_int(src));
+			return cg_emit_conv(p, bs, dst);
+		}
+
+		// typeid <-> integer
+		if (is_type_integer(src) && is_type_typeid(dst)) {
+			return cg_emit_transmute(p, value, dst);
+		}
+		if (is_type_typeid(src) && is_type_integer(dst)) {
+			return cg_emit_transmute(p, value, dst);
+		}
+	}
+
+
+	if (is_type_untyped(src)) {
+		if (is_type_string(src) && is_type_string(dst)) {
+			cgAddr result = cg_add_local(p, t, nullptr, false);
+			cg_addr_store(p, result, value);
+			return cg_addr_load(p, result);
+		}
+	}
+
+
+	gb_printf_err("%.*s\n", LIT(p->name));
+	gb_printf_err("cg_emit_conv: src -> dst\n");
+	gb_printf_err("Not Identical %s != %s\n", type_to_string(src_type), type_to_string(t));
+	gb_printf_err("Not Identical %s != %s\n", type_to_string(src), type_to_string(dst));
+	gb_printf_err("Not Identical %p != %p\n", src_type, t);
+	gb_printf_err("Not Identical %p != %p\n", src, dst);
+
+
+	GB_PANIC("Invalid type conversion: '%s' to '%s' for procedure '%.*s'",
+	         type_to_string(src_type), type_to_string(t),
+	         LIT(p->name));
+
+	return {};
+}
+
+gb_internal cgValue cg_emit_arith(cgProcedure *p, TokenKind op, cgValue lhs, cgValue rhs, Type *type) {
+	if (is_type_array_like(lhs.type) || is_type_array_like(rhs.type)) {
+		GB_PANIC("TODO(bill): cg_emit_arith_array");
+	} else if (is_type_matrix(lhs.type) || is_type_matrix(rhs.type)) {
+		GB_PANIC("TODO(bill): cg_emit_arith_matrix");
+	} else if (is_type_complex(type)) {
+		GB_PANIC("TODO(bill): cg_emit_arith complex");
+	} else if (is_type_quaternion(type)) {
+		GB_PANIC("TODO(bill): cg_emit_arith quaternion");
+	}
+
+	lhs = cg_flatten_value(p, cg_emit_conv(p, lhs, type));
+	rhs = cg_flatten_value(p, cg_emit_conv(p, rhs, type));
+	GB_ASSERT(lhs.kind == cgValue_Value);
+	GB_ASSERT(rhs.kind == cgValue_Value);
+
+	if (is_type_integer(type) && is_type_different_to_arch_endianness(type)) {
+		switch (op) {
+		case Token_AndNot:
+		case Token_And:
+		case Token_Or:
+		case Token_Xor:
+			goto handle_op;
+		}
+
+		Type *platform_type = integer_endian_type_to_platform_type(type);
+		cgValue x = cg_emit_byte_swap(p, lhs, integer_endian_type_to_platform_type(lhs.type));
+		cgValue y = cg_emit_byte_swap(p, rhs, integer_endian_type_to_platform_type(rhs.type));
+
+		cgValue res = cg_emit_arith(p, op, x, y, platform_type);
+
+		return cg_emit_byte_swap(p, res, type);
+	}
+
+	if (is_type_float(type) && is_type_different_to_arch_endianness(type)) {
+		Type *platform_type = integer_endian_type_to_platform_type(type);
+		cgValue x = cg_emit_conv(p, lhs, integer_endian_type_to_platform_type(lhs.type));
+		cgValue y = cg_emit_conv(p, rhs, integer_endian_type_to_platform_type(rhs.type));
+
+		cgValue res = cg_emit_arith(p, op, x, y, platform_type);
+
+		return cg_emit_byte_swap(p, res, type);
+	}
+
+handle_op:;
+
+	// NOTE(bill): Bit Set Aliases for + and -
+	if (is_type_bit_set(type)) {
+		switch (op) {
+		case Token_Add: op = Token_Or;     break;
+		case Token_Sub: op = Token_AndNot; break;
+		}
+	}
+
+	TB_ArithmeticBehavior arith_behavior = cast(TB_ArithmeticBehavior)0;
+
+	Type *integral_type = type;
+	if (is_type_simd_vector(integral_type)) {
+		GB_PANIC("TODO(bill): cg_emit_arith #simd vector");
+		// integral_type = core_array_type(integral_type);
+	}
+
+	switch (op) {
+	case Token_Add:
+		if (is_type_float(integral_type)) {
+			return cg_value(tb_inst_fadd(p->func, lhs.node, rhs.node), type);
+		}
+		return cg_value(tb_inst_add(p->func, lhs.node, rhs.node, arith_behavior), type);
+	case Token_Sub:
+		if (is_type_float(integral_type)) {
+			return cg_value(tb_inst_fsub(p->func, lhs.node, rhs.node), type);
+		}
+		return cg_value(tb_inst_sub(p->func, lhs.node, rhs.node, arith_behavior), type);
+	case Token_Mul:
+		if (is_type_float(integral_type)) {
+			return cg_value(tb_inst_fmul(p->func, lhs.node, rhs.node), type);
+		}
+		return cg_value(tb_inst_mul(p->func, lhs.node, rhs.node, arith_behavior), type);
+	case Token_Quo:
+		if (is_type_float(integral_type)) {
+			return cg_value(tb_inst_fdiv(p->func, lhs.node, rhs.node), type);
+		}
+		return cg_value(tb_inst_div(p->func, lhs.node, rhs.node, !is_type_unsigned(integral_type)), type);
+	case Token_Mod:
+		if (is_type_float(integral_type)) {
+			GB_PANIC("TODO(bill): float %% float");
+		}
+		return cg_value(tb_inst_mod(p->func, lhs.node, rhs.node, !is_type_unsigned(integral_type)), type);
+	case Token_ModMod:
+		if (is_type_unsigned(integral_type)) {
+			return cg_value(tb_inst_mod(p->func, lhs.node, rhs.node, false), type);
+		} else {
+			TB_Node *a = tb_inst_mod(p->func, lhs.node, rhs.node, true);
+			TB_Node *b = tb_inst_add(p->func, a, rhs.node, arith_behavior);
+			TB_Node *c = tb_inst_mod(p->func, b, rhs.node, true);
+			return cg_value(c, type);
+		}
+
+	case Token_And:
+		return cg_value(tb_inst_and(p->func, lhs.node, rhs.node), type);
+	case Token_Or:
+		return cg_value(tb_inst_or(p->func, lhs.node, rhs.node), type);
+	case Token_Xor:
+		return cg_value(tb_inst_xor(p->func, lhs.node, rhs.node), type);
+	case Token_Shl:
+		{
+			rhs = cg_emit_conv(p, rhs, lhs.type);
+			TB_DataType dt = cg_data_type(lhs.type);
+			TB_Node *lhsval = lhs.node;
+			TB_Node *bits = rhs.node;
+
+			TB_Node *bit_size = tb_inst_uint(p->func, dt, 8*type_size_of(lhs.type));
+			TB_Node *zero = tb_inst_uint(p->func, dt, 0);
+
+			TB_Node *width_test = tb_inst_cmp_ilt(p->func, bits, bit_size, false);
+
+			TB_Node *res = tb_inst_shl(p->func, lhsval, bits, arith_behavior);
+			res = tb_inst_select(p->func, width_test, res, zero);
+			return cg_value(res, type);
+		}
+	case Token_Shr:
+		{
+			rhs = cg_emit_conv(p, rhs, lhs.type);
+			TB_DataType dt = cg_data_type(lhs.type);
+			TB_Node *lhsval = lhs.node;
+			TB_Node *bits = rhs.node;
+
+			TB_Node *bit_size = tb_inst_uint(p->func, dt, 8*type_size_of(lhs.type));
+			TB_Node *zero = tb_inst_uint(p->func, dt, 0);
+
+			TB_Node *width_test = tb_inst_cmp_ilt(p->func, bits, bit_size, false);
+
+			TB_Node *res = nullptr;
+
+			if (is_type_unsigned(integral_type)) {
+				res = tb_inst_shr(p->func, lhsval, bits);
+			} else {
+				res = tb_inst_sar(p->func, lhsval, bits);
+			}
+
+
+			res = tb_inst_select(p->func, width_test, res, zero);
+			return cg_value(res, type);
+		}
+	case Token_AndNot:
+		return cg_value(tb_inst_and(p->func, lhs.node, tb_inst_not(p->func, rhs.node)), type);
+	}
+
+	GB_PANIC("unhandled operator of cg_emit_arith");
+
+	return {};
+}
+
+
+gb_internal void cg_fill_slice(cgProcedure *p, cgAddr const &slice, cgValue data, cgValue len) {
+	cgValue slice_ptr = cg_addr_get_ptr(p, slice);
+	cgValue data_ptr = cg_emit_struct_ep(p, slice_ptr, 0);
+	cgValue len_ptr  = cg_emit_struct_ep(p, slice_ptr, 1);
+
+	data = cg_emit_conv(p, data, type_deref(data_ptr.type));
+	len = cg_emit_conv(p, len, t_int);
+	cg_emit_store(p, data_ptr, data);
+	cg_emit_store(p, len_ptr,  len);
+}
+
+gb_internal cgAddr cg_build_addr_slice_expr(cgProcedure *p, Ast *expr) {
+	ast_node(se, SliceExpr, expr);
+
+	cgValue low  = cg_const_int(p, t_int, 0);
+	cgValue high = {};
+
+	if (se->low  != nullptr) {
+		low = cg_correct_endianness(p, cg_build_expr(p, se->low));
+	}
+	if (se->high != nullptr) {
+		high = cg_correct_endianness(p, cg_build_expr(p, se->high));
+	}
+
+	bool no_indices = se->low == nullptr && se->high == nullptr;
+	gb_unused(no_indices);
+
+	cgAddr addr = cg_build_addr(p, se->expr);
+	cgValue base = cg_addr_load(p, addr);
+	Type *type = base_type(base.type);
+
+	if (is_type_pointer(type)) {
+		type = base_type(type_deref(type));
+		addr = cg_addr(base);
+		base = cg_addr_load(p, addr);
+	}
+
+	switch (type->kind) {
+	case Type_Basic:
+	case Type_Slice: {
+		if (type->kind == Type_Basic) {
+			GB_ASSERT(type->Basic.kind == Basic_string);
+		}
+
+		Type *slice_type = type;
+		if (high.node == nullptr) {
+			cgValue len = cg_builtin_len(p, base);
+			high = len;
+		}
+
+		if (!no_indices) {
+			// cg_emit_slice_bounds_check(p, se->open, low, high, len, se->low != nullptr);
+		}
+
+		cgValue elem    = cg_emit_ptr_offset(p, cg_builtin_raw_data(p, base), low);
+		cgValue new_len = cg_emit_arith(p, Token_Sub, high, low, t_int);
+
+		cgAddr slice = cg_add_local(p, slice_type, nullptr, true);
+		cg_fill_slice(p, slice, elem, new_len);
+		return slice;
+	}
+
+	case Type_RelativeMultiPointer:
+		GB_PANIC("TODO(bill): Type_RelativeMultiPointer should be handled above already on the cg_addr_load");
+		break;
+
+	case Type_DynamicArray: {
+		// Type *elem_type = type->DynamicArray.elem;
+		// Type *slice_type = alloc_type_slice(elem_type);
+
+		// lbValue len = lb_dynamic_array_len(p, base);
+		// if (high.value == nullptr) high = len;
+
+		// if (!no_indices) {
+		// 	lb_emit_slice_bounds_check(p, se->open, low, high, len, se->low != nullptr);
+		// }
+
+		// lbValue elem    = lb_emit_ptr_offset(p, lb_dynamic_array_elem(p, base), low);
+		// lbValue new_len = lb_emit_arith(p, Token_Sub, high, low, t_int);
+
+		// lbAddr slice = lb_add_local_generated(p, slice_type, false);
+		// lb_fill_slice(p, slice, elem, new_len);
+		// return slice;
+		GB_PANIC("cg_build_addr_slice_expr Type_DynamicArray");
+		break;
+	}
+
+	case Type_MultiPointer: {
+		Type *res_type = type_of_expr(expr);
+		if (se->high == nullptr) {
+			cgAddr res = cg_add_local(p, res_type, nullptr, false);
+			GB_ASSERT(base.kind == cgValue_Value);
+			GB_ASSERT(low.kind == cgValue_Value);
+
+			i64 stride = type_size_of(type->MultiPointer.elem);
+			cgValue offset = cg_value(tb_inst_array_access(p->func, base.node, low.node, stride), base.type);
+			cg_addr_store(p, res, offset);
+			return res;
+		} else {
+			cgAddr res = cg_add_local(p, res_type, nullptr, true);
+			low  = cg_emit_conv(p, low,  t_int);
+			high = cg_emit_conv(p, high, t_int);
+
+			// cg_emit_multi_pointer_slice_bounds_check(p, se->open, low, high);
+
+			i64 stride = type_size_of(type->MultiPointer.elem);
+			TB_Node *offset = tb_inst_array_access(p->func, base.node, low.node, stride);
+			TB_Node *len = tb_inst_sub(p->func, high.node, low.node, cast(TB_ArithmeticBehavior)0);
+
+			TB_Node *data_ptr = tb_inst_member_access(p->func, res.addr.node, type_offset_of(res_type, 0));
+			TB_Node *len_ptr  = tb_inst_member_access(p->func, res.addr.node, type_offset_of(res_type, 1));
+
+			tb_inst_store(p->func, TB_TYPE_PTR, data_ptr, offset, cast(TB_CharUnits)build_context.ptr_size, false);
+			tb_inst_store(p->func, TB_TYPE_INT, len_ptr,  len,    cast(TB_CharUnits)build_context.int_size, false);
+			return res;
+		}
+	}
+
+	case Type_Array: {
+		Type *slice_type = type_of_expr(expr);
+		GB_ASSERT(is_type_slice(slice_type));
+		cgValue len = cg_const_int(p, t_int, type->Array.count);
+		if (high.node == nullptr) high = len;
+
+		// bool low_const  = type_and_value_of_expr(se->low).mode  == Addressing_Constant;
+		// bool high_const = type_and_value_of_expr(se->high).mode == Addressing_Constant;
+		// if (!low_const || !high_const) {
+		// 	if (!no_indices) {
+		// 		lb_emit_slice_bounds_check(p, se->open, low, high, len, se->low != nullptr);
+		// 	}
+		// }
+		cgValue elem    = cg_emit_ptr_offset(p, cg_builtin_raw_data(p, cg_addr_get_ptr(p, addr)), low);
+		cgValue new_len = cg_emit_arith(p, Token_Sub, high, low, t_int);
+
+		cgAddr slice = cg_add_local(p, slice_type, nullptr, true);
+		cg_fill_slice(p, slice, elem, new_len);
+		return slice;
+	}
+
+
+	case Type_Struct:
+		// if (is_type_soa_struct(type)) {
+		// 	lbValue len = lb_soa_struct_len(p, lb_addr_get_ptr(p, addr));
+		// 	if (high.value == nullptr) high = len;
+
+		// 	if (!no_indices) {
+		// 		lb_emit_slice_bounds_check(p, se->open, low, high, len, se->low != nullptr);
+		// 	}
+		// 	#if 1
+
+		// 	lbAddr dst = lb_add_local_generated(p, type_of_expr(expr), true);
+		// 	if (type->Struct.soa_kind == StructSoa_Fixed) {
+		// 		i32 field_count = cast(i32)type->Struct.fields.count;
+		// 		for (i32 i = 0; i < field_count; i++) {
+		// 			lbValue field_dst = lb_emit_struct_ep(p, dst.addr, i);
+		// 			lbValue field_src = lb_emit_struct_ep(p, lb_addr_get_ptr(p, addr), i);
+		// 			field_src = lb_emit_array_ep(p, field_src, low);
+		// 			lb_emit_store(p, field_dst, field_src);
+		// 		}
+
+		// 		lbValue len_dst = lb_emit_struct_ep(p, dst.addr, field_count);
+		// 		lbValue new_len = lb_emit_arith(p, Token_Sub, high, low, t_int);
+		// 		lb_emit_store(p, len_dst, new_len);
+		// 	} else if (type->Struct.soa_kind == StructSoa_Slice) {
+		// 		if (no_indices) {
+		// 			lb_addr_store(p, dst, base);
+		// 		} else {
+		// 			i32 field_count = cast(i32)type->Struct.fields.count - 1;
+		// 			for (i32 i = 0; i < field_count; i++) {
+		// 				lbValue field_dst = lb_emit_struct_ep(p, dst.addr, i);
+		// 				lbValue field_src = lb_emit_struct_ev(p, base, i);
+		// 				field_src = lb_emit_ptr_offset(p, field_src, low);
+		// 				lb_emit_store(p, field_dst, field_src);
+		// 			}
+
+
+		// 			lbValue len_dst = lb_emit_struct_ep(p, dst.addr, field_count);
+		// 			lbValue new_len = lb_emit_arith(p, Token_Sub, high, low, t_int);
+		// 			lb_emit_store(p, len_dst, new_len);
+		// 		}
+		// 	} else if (type->Struct.soa_kind == StructSoa_Dynamic) {
+		// 		i32 field_count = cast(i32)type->Struct.fields.count - 3;
+		// 		for (i32 i = 0; i < field_count; i++) {
+		// 			lbValue field_dst = lb_emit_struct_ep(p, dst.addr, i);
+		// 			lbValue field_src = lb_emit_struct_ev(p, base, i);
+		// 			field_src = lb_emit_ptr_offset(p, field_src, low);
+		// 			lb_emit_store(p, field_dst, field_src);
+		// 		}
+
+
+		// 		lbValue len_dst = lb_emit_struct_ep(p, dst.addr, field_count);
+		// 		lbValue new_len = lb_emit_arith(p, Token_Sub, high, low, t_int);
+		// 		lb_emit_store(p, len_dst, new_len);
+		// 	}
+
+		// 	return dst;
+		// 	#endif
+		// }
+		GB_PANIC("cg_build_addr_slice_expr Type_Struct");
+		break;
+
+	}
+
+	GB_PANIC("Unknown slicable type");
+	return {};
+}
+
+gb_internal cgValue cg_emit_unary_arith(cgProcedure *p, TokenKind op, cgValue x, Type *type) {
+	switch (op) {
+	case Token_Add:
+		return x;
+	case Token_Not: // Boolean not
+	case Token_Xor: // Bitwise not
+	case Token_Sub: // Number negation
+		break;
+	case Token_Pointer:
+		GB_PANIC("This should be handled elsewhere");
+		break;
+	}
+
+	x = cg_flatten_value(p, x);
+
+	if (is_type_array_like(x.type)) {
+		GB_PANIC("TODO(bill): cg_emit_unary_arith is_type_array_like");
+		// // IMPORTANT TODO(bill): This is very wasteful with regards to stack memory
+		// Type *tl = base_type(x.type);
+		// cgValue val = cg_address_from_load_or_generate_local(p, x);
+		// GB_ASSERT(is_type_array_like(type));
+		// Type *elem_type = base_array_type(type);
+
+		// // NOTE(bill): Doesn't need to be zero because it will be initialized in the loops
+		// cgAddr res_addr = cg_add_local(p, type, nullptr, false);
+		// cgValue res = cg_addr_get_ptr(p, res_addr);
+
+		// bool inline_array_arith = cg_can_try_to_inline_array_arith(type);
+
+		// i32 count = cast(i32)get_array_type_count(tl);
+
+		// LLVMTypeRef vector_type = nullptr;
+		// if (op != Token_Not && cg_try_vector_cast(p->module, val, &vector_type)) {
+		// 	LLVMValueRef vp = LLVMBuildPointerCast(p->builder, val.value, LLVMPointerType(vector_type, 0), "");
+		// 	LLVMValueRef v = LLVMBuildLoad2(p->builder, vector_type, vp, "");
+
+		// 	LLVMValueRef opv = nullptr;
+		// 	switch (op) {
+		// 	case Token_Xor:
+		// 		opv = LLVMBuildNot(p->builder, v, "");
+		// 		break;
+		// 	case Token_Sub:
+		// 		if (is_type_float(elem_type)) {
+		// 			opv = LLVMBuildFNeg(p->builder, v, "");
+		// 		} else {
+		// 			opv = LLVMBuildNeg(p->builder, v, "");
+		// 		}
+		// 		break;
+		// 	}
+
+		// 	if (opv != nullptr) {
+		// 		LLVMSetAlignment(res.value, cast(unsigned)cg_alignof(vector_type));
+		// 		LLVMValueRef res_ptr = LLVMBuildPointerCast(p->builder, res.value, LLVMPointerType(vector_type, 0), "");
+		// 		LLVMBuildStore(p->builder, opv, res_ptr);
+		// 		return cg_emit_conv(p, cg_emit_load(p, res), type);
+		// 	}
+		// }
+
+		// if (inline_array_arith) {
+		// 	// inline
+		// 	for (i32 i = 0; i < count; i++) {
+		// 		cgValue e = cg_emit_load(p, cg_emit_array_epi(p, val, i));
+		// 		cgValue z = cg_emit_unary_arith(p, op, e, elem_type);
+		// 		cg_emit_store(p, cg_emit_array_epi(p, res, i), z);
+		// 	}
+		// } else {
+		// 	auto loop_data = cg_loop_start(p, count, t_i32);
+
+		// 	cgValue e = cg_emit_load(p, cg_emit_array_ep(p, val, loop_data.idx));
+		// 	cgValue z = cg_emit_unary_arith(p, op, e, elem_type);
+		// 	cg_emit_store(p, cg_emit_array_ep(p, res, loop_data.idx), z);
+
+		// 	cg_loop_end(p, loop_data);
+		// }
+		// return cg_emit_load(p, res);
+	}
+
+	if (op == Token_Xor) {
+		GB_ASSERT(x.kind == cgValue_Value);
+		cgValue cmp = cg_value(tb_inst_not(p->func, x.node), x.type);
+		return cg_emit_conv(p, cmp, type);
+	}
+
+	if (op == Token_Not) {
+		TB_Node *zero = cg_const_nil(p, x.type).node;
+		cgValue cmp = cg_value(tb_inst_cmp_ne(p->func, x.node, zero), x.type);
+		return cg_emit_conv(p, cmp, type);
+	}
+
+	if (op == Token_Sub && is_type_integer(type) && is_type_different_to_arch_endianness(type)) {
+		Type *platform_type = integer_endian_type_to_platform_type(type);
+		cgValue v = cg_emit_byte_swap(p, x, platform_type);
+
+		cgValue res = cg_value(tb_inst_neg(p->func, v.node), platform_type);
+		return cg_emit_byte_swap(p, res, type);
+	}
+
+	if (op == Token_Sub && is_type_float(type) && is_type_different_to_arch_endianness(type)) {
+		Type *platform_type = integer_endian_type_to_platform_type(type);
+		cgValue v = cg_emit_byte_swap(p, x, platform_type);
+
+		cgValue res = cg_value(tb_inst_neg(p->func, v.node), platform_type);
+		return cg_emit_byte_swap(p, res, type);
+	}
+
+	cgValue res = {};
+
+	if (op == Token_Sub) { // Number negation
+		if (is_type_integer(x.type)) {
+			res = cg_value(tb_inst_neg(p->func, x.node), x.type);
+		} else if (is_type_float(x.type)) {
+			res = cg_value(tb_inst_neg(p->func, x.node), x.type);
+		} else if (is_type_complex(x.type)) {
+			GB_PANIC("TODO(bill): neg complex");
+			// LLVMValueRef v0 = LLVMBuildFNeg(p->builder, LLVMBuildExtractValue(p->builder, x.value, 0, ""), "");
+			// LLVMValueRef v1 = LLVMBuildFNeg(p->builder, LLVMBuildExtractValue(p->builder, x.value, 1, ""), "");
+
+			// cgAddr addr = cg_add_local_generated(p, x.type, false);
+			// LLVMTypeRef type = llvm_addr_type(p->module, addr.addr);
+			// LLVMBuildStore(p->builder, v0, LLVMBuildStructGEP2(p->builder, type, addr.addr.value, 0, ""));
+			// LLVMBuildStore(p->builder, v1, LLVMBuildStructGEP2(p->builder, type, addr.addr.value, 1, ""));
+			// return cg_addr_load(p, addr);
+
+		} else if (is_type_quaternion(x.type)) {
+			GB_PANIC("TODO(bill): neg quaternion");
+			// LLVMValueRef v0 = LLVMBuildFNeg(p->builder, LLVMBuildExtractValue(p->builder, x.value, 0, ""), "");
+			// LLVMValueRef v1 = LLVMBuildFNeg(p->builder, LLVMBuildExtractValue(p->builder, x.value, 1, ""), "");
+			// LLVMValueRef v2 = LLVMBuildFNeg(p->builder, LLVMBuildExtractValue(p->builder, x.value, 2, ""), "");
+			// LLVMValueRef v3 = LLVMBuildFNeg(p->builder, LLVMBuildExtractValue(p->builder, x.value, 3, ""), "");
+
+			// cgAddr addr = cg_add_local_generated(p, x.type, false);
+			// LLVMTypeRef type = llvm_addr_type(p->module, addr.addr);
+			// LLVMBuildStore(p->builder, v0, LLVMBuildStructGEP2(p->builder, type, addr.addr.value, 0, ""));
+			// LLVMBuildStore(p->builder, v1, LLVMBuildStructGEP2(p->builder, type, addr.addr.value, 1, ""));
+			// LLVMBuildStore(p->builder, v2, LLVMBuildStructGEP2(p->builder, type, addr.addr.value, 2, ""));
+			// LLVMBuildStore(p->builder, v3, LLVMBuildStructGEP2(p->builder, type, addr.addr.value, 3, ""));
+			// return cg_addr_load(p, addr);
+		} else if (is_type_simd_vector(x.type)) {
+			GB_PANIC("TODO(bill): neg simd");
+			// Type *elem = base_array_type(x.type);
+			// if (is_type_float(elem)) {
+			// 	res.value = LLVMBuildFNeg(p->builder, x.value, "");
+			// } else {
+			// 	res.value = LLVMBuildNeg(p->builder, x.value, "");
+			// }
+		} else if (is_type_matrix(x.type)) {
+			GB_PANIC("TODO(bill): neg matrix");
+			// cgValue zero = {};
+			// zero.value = LLVMConstNull(cg_type(p->module, type));
+			// zero.type = type;
+			// return cg_emit_arith_matrix(p, Token_Sub, zero, x, type, true);
+		} else {
+			GB_PANIC("Unhandled type %s", type_to_string(x.type));
+		}
+		res.type = x.type;
+		return res;
+	}
+
+	return res;
+}
+
+gb_internal void cg_emit_if(cgProcedure *p, cgValue const &cond, TB_Node *true_region, TB_Node *false_region) {
+	GB_ASSERT(cond.kind == cgValue_Value);
+	tb_inst_if(p->func, cond.node, true_region, false_region);
+}
+
+
+struct cgLoopData {
+	cgAddr  index_addr;
+	cgValue index;
+	TB_Node *body;
+	TB_Node *done;
+	TB_Node *loop;
+};
+
+gb_internal cgLoopData cg_loop_start(cgProcedure *p, isize count, Type *index_type) {
+	cgLoopData data = {};
+
+	cgValue max = cg_const_int(p, index_type, count);
+
+	data.index_addr = cg_add_local(p, index_type, nullptr, true);
+
+	data.body = cg_control_region(p, "loop_body");
+	data.done = cg_control_region(p, "loop_done");
+	data.loop = cg_control_region(p, "loop_loop");
+
+	cg_emit_goto(p, data.loop);
+	tb_inst_set_control(p->func, data.loop);
+
+	data.index = cg_addr_load(p, data.index_addr);
+
+	cgValue cond = cg_emit_comp(p, Token_Lt, data.index, max);
+	cg_emit_if(p, cond, data.body, data.done);
+	tb_inst_set_control(p->func, data.body);
+
+	return data;
+}
+
+gb_internal void cg_loop_end(cgProcedure *p, cgLoopData const &data) {
+	if (data.index_addr.addr.node != nullptr) {
+		cg_emit_increment(p, data.index_addr.addr);
+		cg_emit_goto(p, data.loop);
+		tb_inst_set_control(p->func, data.done);
+	}
+}
+
+
+
+gb_internal void cg_build_try_lhs_rhs(cgProcedure *p, Ast *arg, Type *final_type, cgValue *lhs_, cgValue *rhs_) {
+	cgValue lhs = {};
+	cgValue rhs = {};
+
+	cgValue value = cg_build_expr(p, arg);
+	if (value.kind == cgValue_Multi) {
+		auto const &values = value.multi->values;
+		if (values.count == 2) {
+			lhs = values[0];
+			rhs = values[1];
+		} else {
+			rhs = values[values.count-1];
+			if (values.count > 1) {
+				lhs = cg_value_multi(slice(values, 0, values.count-1), final_type);
+			}
+		}
+	} else {
+		rhs = value;
+	}
+
+	GB_ASSERT(rhs.node != nullptr);
+
+	if (lhs_) *lhs_ = lhs;
+	if (rhs_) *rhs_ = rhs;
+}
+
+gb_internal cgValue cg_emit_try_has_value(cgProcedure *p, cgValue rhs) {
+	cgValue has_value = {};
+	if (is_type_boolean(rhs.type)) {
+		has_value = rhs;
+	} else {
+		GB_ASSERT_MSG(type_has_nil(rhs.type), "%s", type_to_string(rhs.type));
+		has_value = cg_emit_comp_against_nil(p, Token_CmpEq, rhs);
+	}
+	GB_ASSERT(has_value.node != nullptr);
+	return has_value;
+}
+
+gb_internal cgValue cg_build_or_return(cgProcedure *p, Ast *arg, Type *final_type) {
+	cgValue lhs = {};
+	cgValue rhs = {};
+	cg_build_try_lhs_rhs(p, arg, final_type, &lhs, &rhs);
+
+	TB_Node *return_region   = cg_control_region(p, "or_return_return");
+	TB_Node *continue_region = cg_control_region(p, "or_return_continue");
+
+	cgValue cond = cg_emit_try_has_value(p, rhs);
+	cg_emit_if(p, cond, continue_region, return_region);
+	tb_inst_set_control(p->func, return_region);
+	{
+		Type *proc_type = base_type(p->type);
+		Type *results = proc_type->Proc.results;
+		GB_ASSERT(results != nullptr && results->kind == Type_Tuple);
+		TypeTuple *tuple = &results->Tuple;
+
+		GB_ASSERT(tuple->variables.count != 0);
+
+		Entity *end_entity = tuple->variables[tuple->variables.count-1];
+		rhs = cg_emit_conv(p, rhs, end_entity->type);
+		if (p->type->Proc.has_named_results) {
+			GB_ASSERT(end_entity->token.string.len != 0);
+
+			// NOTE(bill): store the named values before returning
+			cgAddr found = map_must_get(&p->variable_map, end_entity);
+			cg_addr_store(p, found, rhs);
+
+			cg_build_return_stmt(p, {});
+		} else {
+			GB_ASSERT(tuple->variables.count == 1);
+			Slice<cgValue> results = {};
+			results.data = &rhs;
+			results.count = 1;;
+			cg_build_return_stmt_internal(p, results);
+		}
+	}
+	tb_inst_set_control(p->func, continue_region);
+	if (final_type != nullptr && !is_type_tuple(final_type)) {
+		return cg_emit_conv(p, lhs, final_type);
+	}
+	return {};
+}
+
+gb_internal cgValue cg_build_or_else(cgProcedure *p, Ast *arg, Ast *else_expr, Type *final_type) {
+	if (arg->state_flags & StateFlag_DirectiveWasFalse) {
+		return cg_build_expr(p, else_expr);
+	}
+
+	cgValue lhs = {};
+	cgValue rhs = {};
+	cg_build_try_lhs_rhs(p, arg, final_type, &lhs, &rhs);
+
+	GB_ASSERT(else_expr != nullptr);
+
+	if (is_diverging_expr(else_expr)) {
+		TB_Node *then  = cg_control_region(p, "or_else_then");
+		TB_Node *else_ = cg_control_region(p, "or_else_else");
+
+		cg_emit_if(p, cg_emit_try_has_value(p, rhs), then, else_);
+		// NOTE(bill): else block needs to be straight afterwards to make sure that the actual value is used
+		// from the then block
+		tb_inst_set_control(p->func, else_);
+
+		cg_build_expr(p, else_expr);
+
+		tb_inst_set_control(p->func, then);
+		return cg_emit_conv(p, lhs, final_type);
+	} else {
+		TB_Node *incoming_values[2] = {};
+		TB_Node *incoming_regions[2] = {};
+
+		TB_Node *then  = cg_control_region(p, "or_else_then");
+		TB_Node *done  = cg_control_region(p, "or_else_done"); // NOTE(bill): Append later
+		TB_Node *else_ = cg_control_region(p, "or_else_else");
+
+		cg_emit_if(p, cg_emit_try_has_value(p, rhs), then, else_);
+		tb_inst_set_control(p->func, then);
+
+		cgValue x = cg_emit_conv(p, lhs, final_type);
+		incoming_values[0] = x.node;
+		incoming_regions[0] = tb_inst_get_control(p->func);
+
+		tb_inst_goto(p->func, done);
+		tb_inst_set_control(p->func, else_);
+
+		cgValue y = cg_emit_conv(p, cg_build_expr(p, else_expr), final_type);
+		incoming_values[1] = y.node;
+		incoming_regions[1] = tb_inst_get_control(p->func);
+
+		tb_inst_goto(p->func, done);
+		tb_inst_set_control(p->func, done);
+
+		GB_ASSERT(x.kind == y.kind);
+		GB_ASSERT(incoming_values[0]->dt.raw == incoming_values[1]->dt.raw);
+		cgValue res = {};
+		res.kind = x.kind;
+		res.type = final_type;
+
+		res.node = tb_inst_incomplete_phi(p->func, incoming_values[0]->dt, done, 2);
+		tb_inst_add_phi_operand(p->func, res.node, incoming_regions[0], incoming_values[0]);
+		tb_inst_add_phi_operand(p->func, res.node, incoming_regions[1], incoming_values[1]);
+		return res;
+	}
+}
+
+
+gb_internal isize cg_control_region_pred_count(TB_Node *region) {
+	GB_ASSERT(region->type == TB_REGION);
+	GB_ASSERT(region->input_count > 0);
+	return region->input_count;
+}
+
+gb_internal cgValue cg_build_logical_binary_expr(cgProcedure *p, TokenKind op, Ast *left, Ast *right, Type *final_type) {
+	TB_Node *rhs  = cg_control_region(p, "logical_cmp_rhs");
+	TB_Node *done = cg_control_region(p, "logical_cmp_done");
+
+	cgValue short_circuit = {};
+	if (op == Token_CmpAnd) {
+		cg_build_cond(p, left, rhs, done);
+		short_circuit = cg_const_bool(p, t_bool, false);
+	} else if (op == Token_CmpOr) {
+		cg_build_cond(p, left, done, rhs);
+		short_circuit = cg_const_bool(p, t_bool, true);
+	}
+
+	if (rhs->input_count == 0) {
+		tb_inst_set_control(p->func, done);
+		return cg_emit_conv(p, short_circuit, final_type);
+	}
+
+	if (done->input_count == 0) {
+		tb_inst_set_control(p->func, rhs);
+		return cg_build_expr(p, right);
+	}
+
+	tb_inst_set_control(p->func, rhs);
+	cgValue edge = cg_build_expr(p, right);
+	TB_Node *edge_region = tb_inst_get_control(p->func);
+
+	tb_inst_goto(p->func, done);
+	tb_inst_set_control(p->func, done);
+
+	TB_DataType dt = edge.node->dt;
+	TB_Node *phi = tb_inst_incomplete_phi(p->func, dt, done, done->input_count);
+	for (size_t i = 0; i < done->input_count; i++) {
+		TB_Node *val = short_circuit.node;
+		TB_Node *region = done->inputs[i];
+		if (region == edge_region) {
+			val = edge.node;
+		}
+		tb_inst_add_phi_operand(p->func, phi, region, val);
+	}
+	return cg_emit_conv(p, cg_value(phi, t_bool), final_type);
+}
+
+
+
+gb_internal cgValue cg_build_binary_expr(cgProcedure *p, Ast *expr) {
+	ast_node(be, BinaryExpr, expr);
+
+	TypeAndValue tv = type_and_value_of_expr(expr);
+
+	if (is_type_matrix(be->left->tav.type) || is_type_matrix(be->right->tav.type)) {
+		cgValue left = cg_build_expr(p, be->left);
+		cgValue right = cg_build_expr(p, be->right);
+		GB_PANIC("TODO(bill): cg_emit_arith_matrix");
+		// return cg_emit_arith_matrix(p, be->op.kind, left, right, default_type(tv.type), false);
+	}
+
+
+	switch (be->op.kind) {
+	case Token_Add:
+	case Token_Sub:
+	case Token_Mul:
+	case Token_Quo:
+	case Token_Mod:
+	case Token_ModMod:
+	case Token_And:
+	case Token_Or:
+	case Token_Xor:
+	case Token_AndNot: {
+		Type *type = default_type(tv.type);
+		cgValue left = cg_build_expr(p, be->left);
+		cgValue right = cg_build_expr(p, be->right);
+		return cg_emit_arith(p, be->op.kind, left, right, type);
+	}
+
+	case Token_Shl:
+	case Token_Shr: {
+		cgValue left, right;
+		Type *type = default_type(tv.type);
+		left = cg_build_expr(p, be->left);
+
+		if (cg_is_expr_untyped_const(be->right)) {
+			// NOTE(bill): RHS shift operands can still be untyped
+			// Just bypass the standard cg_build_expr
+			right = cg_expr_untyped_const_to_typed(p, be->right, type);
+		} else {
+			right = cg_build_expr(p, be->right);
+		}
+		return cg_emit_arith(p, be->op.kind, left, right, type);
+	}
+
+	case Token_CmpEq:
+	case Token_NotEq:
+		if (is_type_untyped_nil(be->right->tav.type)) {
+			// `x == nil` or `x != nil`
+			cgValue left = cg_build_expr(p, be->left);
+			cgValue cmp = cg_emit_comp_against_nil(p, be->op.kind, left);
+			Type *type = default_type(tv.type);
+			return cg_emit_conv(p, cmp, type);
+		} else if (is_type_untyped_nil(be->left->tav.type)) {
+			// `nil == x` or `nil != x`
+			cgValue right = cg_build_expr(p, be->right);
+			cgValue cmp = cg_emit_comp_against_nil(p, be->op.kind, right);
+			Type *type = default_type(tv.type);
+			return cg_emit_conv(p, cmp, type);
+		}/* else if (cg_is_empty_string_constant(be->right)) {
+			// `x == ""` or `x != ""`
+			cgValue s = cg_build_expr(p, be->left);
+			s = cg_emit_conv(p, s, t_string);
+			cgValue len = cg_string_len(p, s);
+			cgValue cmp = cg_emit_comp(p, be->op.kind, len, cg_const_int(p->module, t_int, 0));
+			Type *type = default_type(tv.type);
+			return cg_emit_conv(p, cmp, type);
+		} else if (cg_is_empty_string_constant(be->left)) {
+			// `"" == x` or `"" != x`
+			cgValue s = cg_build_expr(p, be->right);
+			s = cg_emit_conv(p, s, t_string);
+			cgValue len = cg_string_len(p, s);
+			cgValue cmp = cg_emit_comp(p, be->op.kind, len, cg_const_int(p->module, t_int, 0));
+			Type *type = default_type(tv.type);
+			return cg_emit_conv(p, cmp, type);
+		}*/
+		/*fallthrough*/
+	case Token_Lt:
+	case Token_LtEq:
+	case Token_Gt:
+	case Token_GtEq:
+		{
+			cgValue left = {};
+			cgValue right = {};
+
+			if (be->left->tav.mode == Addressing_Type) {
+				left = cg_typeid(p, be->left->tav.type);
+			}
+			if (be->right->tav.mode == Addressing_Type) {
+				right = cg_typeid(p, be->right->tav.type);
+			}
+			if (left.node == nullptr)  left  = cg_build_expr(p, be->left);
+			if (right.node == nullptr) right = cg_build_expr(p, be->right);
+			cgValue cmp = cg_emit_comp(p, be->op.kind, left, right);
+			Type *type = default_type(tv.type);
+			return cg_emit_conv(p, cmp, type);
+		}
+
+	case Token_CmpAnd:
+	case Token_CmpOr:
+		return cg_build_logical_binary_expr(p, be->op.kind, be->left, be->right, tv.type);
+
+	case Token_in:
+	case Token_not_in:
+		{
+			cgValue left = cg_build_expr(p, be->left);
+			cgValue right = cg_build_expr(p, be->right);
+			Type *rt = base_type(right.type);
+			if (is_type_pointer(rt)) {
+				right = cg_emit_load(p, right);
+				rt = base_type(type_deref(rt));
+			}
+
+			switch (rt->kind) {
+			case Type_Map:
+				{
+					GB_PANIC("TODO(bill): in/not_in for maps");
+					// cgValue map_ptr = cg_address_from_load_or_generate_local(p, right);
+					// cgValue key = left;
+					// cgValue ptr = cg_internal_dynamic_map_get_ptr(p, map_ptr, key);
+					// if (be->op.kind == Token_in) {
+					// 	return cg_emit_conv(p, cg_emit_comp_against_nil(p, Token_NotEq, ptr), t_bool);
+					// } else {
+					// 	return cg_emit_conv(p, cg_emit_comp_against_nil(p, Token_CmpEq, ptr), t_bool);
+					// }
+				}
+				break;
+			case Type_BitSet:
+				{
+					Type *key_type = rt->BitSet.elem;
+					GB_ASSERT(are_types_identical(left.type, key_type));
+
+					Type *it = bit_set_to_int(rt);
+					left = cg_emit_conv(p, left, it);
+					if (is_type_different_to_arch_endianness(it)) {
+						left = cg_emit_byte_swap(p, left, integer_endian_type_to_platform_type(it));
+					}
+
+					cgValue lower = cg_const_value(p, left.type, exact_value_i64(rt->BitSet.lower));
+					cgValue key = cg_emit_arith(p, Token_Sub, left, lower, left.type);
+					cgValue bit = cg_emit_arith(p, Token_Shl, cg_const_int(p, left.type, 1), key, left.type);
+					bit = cg_emit_conv(p, bit, it);
+
+					cgValue old_value = cg_emit_transmute(p, right, it);
+					cgValue new_value = cg_emit_arith(p, Token_And, old_value, bit, it);
+
+					GB_PANIC("TODO(bill): cg_emit_comp");
+					// TokenKind op = (be->op.kind == Token_in) ? Token_NotEq : Token_CmpEq;
+					// return cg_emit_conv(p, cg_emit_comp(p, op, new_value, cg_const_int(p, new_value.type, 0)), t_bool);
+				}
+				break;
+			default:
+				GB_PANIC("Invalid 'in' type");
+			}
+			break;
+		}
+		break;
+	default:
+		GB_PANIC("Invalid binary expression");
+		break;
+	}
+	return {};
+}
+
+gb_internal cgValue cg_build_cond(cgProcedure *p, Ast *cond, TB_Node *true_block, TB_Node *false_block) {
+	cond = unparen_expr(cond);
+
+	GB_ASSERT(cond != nullptr);
+	GB_ASSERT(true_block  != nullptr);
+	GB_ASSERT(false_block != nullptr);
+
+	// Use to signal not to do compile time short circuit for consts
+	cgValue no_comptime_short_circuit = {};
+
+	switch (cond->kind) {
+	case_ast_node(ue, UnaryExpr, cond);
+		if (ue->op.kind == Token_Not) {
+			cgValue cond_val = cg_build_cond(p, ue->expr, false_block, true_block);
+			return cond_val;
+			// if (cond_val.value && LLVMIsConstant(cond_val.value)) {
+			// 	return cg_const_bool(p->module, cond_val.type, LLVMConstIntGetZExtValue(cond_val.value) == 0);
+			// }
+			// return no_comptime_short_circuit;
+		}
+	case_end;
+
+	case_ast_node(be, BinaryExpr, cond);
+		if (be->op.kind == Token_CmpAnd) {
+			TB_Node *block = cg_control_region(p, "cmp_and");
+			cg_build_cond(p, be->left, block, false_block);
+			tb_inst_set_control(p->func, block);
+			cg_build_cond(p, be->right, true_block, false_block);
+			return no_comptime_short_circuit;
+		} else if (be->op.kind == Token_CmpOr) {
+			TB_Node *block = cg_control_region(p, "cmp_or");
+			cg_build_cond(p, be->left, true_block, block);
+			tb_inst_set_control(p->func, block);
+			cg_build_cond(p, be->right, true_block, false_block);
+			return no_comptime_short_circuit;
+		}
+	case_end;
+	}
+
+	cgValue v = {};
+	if (cg_is_expr_untyped_const(cond)) {
+		v = cg_expr_untyped_const_to_typed(p, cond, t_bool);
+	} else {
+		v = cg_build_expr(p, cond);
+	}
+	cg_emit_if(p, v, true_block, false_block);
+	return v;
+}
+
+gb_internal cgValue cg_build_expr_internal(cgProcedure *p, Ast *expr);
+gb_internal cgValue cg_build_expr(cgProcedure *p, Ast *expr) {
+	cg_set_debug_pos_from_node(p, expr);
+
+	u16 prev_state_flags = p->state_flags;
+	defer (p->state_flags = prev_state_flags);
+
+	if (expr->state_flags != 0) {
+		u16 in = expr->state_flags;
+		u16 out = p->state_flags;
+
+		if (in & StateFlag_bounds_check) {
+			out |= StateFlag_bounds_check;
+			out &= ~StateFlag_no_bounds_check;
+		} else if (in & StateFlag_no_bounds_check) {
+			out |= StateFlag_no_bounds_check;
+			out &= ~StateFlag_bounds_check;
+		}
+
+		if (in & StateFlag_type_assert) {
+			out |= StateFlag_type_assert;
+			out &= ~StateFlag_no_type_assert;
+		} else if (in & StateFlag_no_type_assert) {
+			out |= StateFlag_no_type_assert;
+			out &= ~StateFlag_type_assert;
+		}
+
+		p->state_flags = out;
+	}
+
+
+	// IMPORTANT NOTE(bill):
+	// Selector Call Expressions (foo->bar(...))
+	// must only evaluate `foo` once as it gets transformed into
+	// `foo.bar(foo, ...)`
+	// And if `foo` is a procedure call or something more complex, storing the value
+	// once is a very good idea
+	// If a stored value is found, it must be removed from the cache
+	if (expr->state_flags & StateFlag_SelectorCallExpr) {
+		// cgValue *pp = map_get(&p->selector_values, expr);
+		// if (pp != nullptr) {
+		// 	cgValue res = *pp;
+		// 	map_remove(&p->selector_values, expr);
+		// 	return res;
+		// }
+		// cgAddr *pa = map_get(&p->selector_addr, expr);
+		// if (pa != nullptr) {
+		// 	cgAddr res = *pa;
+		// 	map_remove(&p->selector_addr, expr);
+		// 	return cg_addr_load(p, res);
+		// }
+	}
+
+	cgValue res = cg_build_expr_internal(p, expr);
+	if (res.kind == cgValue_Symbol) {
+		GB_ASSERT(is_type_internally_pointer_like(res.type));
+		res = cg_value(tb_inst_get_symbol_address(p->func, res.symbol), res.type);
+	}
+
+	if (expr->state_flags & StateFlag_SelectorCallExpr) {
+		// map_set(&p->selector_values, expr, res);
+	}
+	return res;
+}
+
+
+gb_internal cgValue cg_find_ident(cgProcedure *p, Entity *e, Ast *expr) {
+	cgAddr *found_addr = map_get(&p->variable_map, e);
+	if (found_addr) {
+		return cg_addr_load(p, *found_addr);
+	}
+
+	cgValue *found = nullptr;
+	rw_mutex_shared_lock(&p->module->values_mutex);
+	found = map_get(&p->module->values, e);
+	rw_mutex_shared_unlock(&p->module->values_mutex);
+
+	if (found) {
+
+		auto v = *found;
+		// NOTE(bill): This is because pointers are already pointers in LLVM
+		if (is_type_proc(v.type)) {
+			return v;
+		}
+		return cg_emit_load(p, v);
+	} else if (e != nullptr && e->kind == Entity_Variable) {
+		return cg_addr_load(p, cg_build_addr(p, expr));
+	}
+
+	if (e->kind == Entity_Procedure) {
+		return cg_find_procedure_value_from_entity(p->module, e);
+	}
+
+	String pkg = {};
+	if (e->pkg) {
+		pkg = e->pkg->name;
+	}
+	gb_printf_err("Error in: %s\n", token_pos_to_string(ast_token(expr).pos));
+	GB_PANIC("nullptr value for expression from identifier: %.*s.%.*s (%p) : %s @ %p", LIT(pkg), LIT(e->token.string), e, type_to_string(e->type), expr);
+	return {};
+}
+
+cgAddr cg_build_addr_compound_lit(cgProcedure *p, Ast *expr) {
+	struct cgCompoundLitElemTempData {
+		Ast *   expr;
+		cgValue value;
+		i64     elem_index;
+		i64     elem_length;
+		cgValue gep;
+	};
+
+
+	auto const &populate = [](cgProcedure *p, Slice<Ast *> const &elems, Array<cgCompoundLitElemTempData> *temp_data, Type *compound_type) {
+		Type *bt = base_type(compound_type);
+		Type *et = nullptr;
+		switch (bt->kind) {
+		case Type_Array:           et = bt->Array.elem;           break;
+		case Type_EnumeratedArray: et = bt->EnumeratedArray.elem; break;
+		case Type_Slice:           et = bt->Slice.elem;           break;
+		case Type_BitSet:          et = bt->BitSet.elem;          break;
+		case Type_DynamicArray:    et = bt->DynamicArray.elem;    break;
+		case Type_SimdVector:      et = bt->SimdVector.elem;      break;
+		case Type_Matrix:          et = bt->Matrix.elem;          break;
+		}
+		GB_ASSERT(et != nullptr);
+
+
+		// NOTE(bill): Separate value, gep, store into their own chunks
+		for_array(i, elems) {
+			Ast *elem = elems[i];
+			if (elem->kind == Ast_FieldValue) {
+				ast_node(fv, FieldValue, elem);
+				if (is_ast_range(fv->field)) {
+					ast_node(ie, BinaryExpr, fv->field);
+					TypeAndValue lo_tav = ie->left->tav;
+					TypeAndValue hi_tav = ie->right->tav;
+					GB_ASSERT(lo_tav.mode == Addressing_Constant);
+					GB_ASSERT(hi_tav.mode == Addressing_Constant);
+
+					TokenKind op = ie->op.kind;
+					i64 lo = exact_value_to_i64(lo_tav.value);
+					i64 hi = exact_value_to_i64(hi_tav.value);
+					if (op != Token_RangeHalf) {
+						hi += 1;
+					}
+
+					cgValue value = cg_emit_conv(p, cg_build_expr(p, fv->value), et);
+
+					GB_ASSERT((hi-lo) > 0);
+
+					if (bt->kind == Type_Matrix) {
+						GB_PANIC("TODO(bill): Type_Matrix");
+						// for (i64 k = lo; k < hi; k++) {
+						// 	cgCompoundLitElemTempData data = {};
+						// 	data.value = value;
+
+						// 	data.elem_index = matrix_row_major_index_to_offset(bt, k);
+						// 	array_add(temp_data, data);
+						// }
+					} else {
+						enum {MAX_ELEMENT_AMOUNT = 32};
+						if ((hi-lo) <= MAX_ELEMENT_AMOUNT) {
+							for (i64 k = lo; k < hi; k++) {
+								cgCompoundLitElemTempData data = {};
+								data.value = value;
+								data.elem_index = k;
+								array_add(temp_data, data);
+							}
+						} else {
+							cgCompoundLitElemTempData data = {};
+							data.value = value;
+							data.elem_index = lo;
+							data.elem_length = hi-lo;
+							array_add(temp_data, data);
+						}
+					}
+				} else {
+					auto tav = fv->field->tav;
+					GB_ASSERT(tav.mode == Addressing_Constant);
+					i64 index = exact_value_to_i64(tav.value);
+
+					cgValue value = cg_emit_conv(p, cg_build_expr(p, fv->value), et);
+					GB_ASSERT(!is_type_tuple(value.type));
+
+					cgCompoundLitElemTempData data = {};
+					data.value = value;
+					data.expr = fv->value;
+					if (bt->kind == Type_Matrix) {
+						GB_PANIC("TODO(bill): Type_Matrix");
+						// data.elem_index = matrix_row_major_index_to_offset(bt, index);
+					} else {
+						data.elem_index = index;
+					}
+					array_add(temp_data, data);
+				}
+
+			} else {
+				// if (bt->kind != Type_DynamicArray && lb_is_elem_const(elem, et)) {
+					// continue;
+				// }
+
+				cgValue field_expr = cg_build_expr(p, elem);
+				GB_ASSERT(!is_type_tuple(field_expr.type));
+
+				cgValue ev = cg_emit_conv(p, field_expr, et);
+
+				cgCompoundLitElemTempData data = {};
+				data.value = ev;
+				if (bt->kind == Type_Matrix) {
+						GB_PANIC("TODO(bill): Type_Matrix");
+					// data.elem_index = matrix_row_major_index_to_offset(bt, i);
+				} else {
+					data.elem_index = i;
+				}
+				array_add(temp_data, data);
+			}
+		}
+	};
+
+	auto const &assign_array = [](cgProcedure *p, Array<cgCompoundLitElemTempData> const &temp_data) {
+		for (auto const &td : temp_data) if (td.value.node != nullptr) {
+			if (td.elem_length > 0) {
+				GB_PANIC("TODO(bill): range");
+				// auto loop_data = cg_loop_start(p, cast(isize)td.elem_length, t_i32);
+				// {
+				// 	cgValue dst = td.gep;
+				// 	dst = cg_emit_ptr_offset(p, dst, loop_data.idx);
+				// 	cg_emit_store(p, dst, td.value);
+				// }
+				// cg_loop_end(p, loop_data);
+			} else {
+				cg_emit_store(p, td.gep, td.value);
+			}
+		}
+	};
+
+
+
+	ast_node(cl, CompoundLit, expr);
+
+	Type *type = type_of_expr(expr);
+	Type *bt = base_type(type);
+
+	cgAddr v = {};
+	if (p->is_startup) {
+		v = cg_add_global(p, type, nullptr);
+	} else {
+		v = cg_add_local(p, type, nullptr, true);
+	}
+
+	if (cl->elems.count == 0) {
+		// No need to create it
+		return v;
+	}
+
+	TEMPORARY_ALLOCATOR_GUARD();
+
+	Type *et = nullptr;
+	switch (bt->kind) {
+	case Type_Array:           et = bt->Array.elem;           break;
+	case Type_EnumeratedArray: et = bt->EnumeratedArray.elem; break;
+	case Type_Slice:           et = bt->Slice.elem;           break;
+	case Type_BitSet:          et = bt->BitSet.elem;          break;
+	case Type_SimdVector:      et = bt->SimdVector.elem;      break;
+	case Type_Matrix:          et = bt->Matrix.elem;          break;
+	}
+
+	String proc_name = {};
+	if (p->entity) {
+		proc_name = p->entity->token.string;
+	}
+	TokenPos pos = ast_token(expr).pos;
+
+
+	switch (bt->kind) {
+	default: GB_PANIC("Unknown CompoundLit type: %s", type_to_string(type)); break;
+
+	case Type_Struct: {
+		TypeStruct *st = &bt->Struct;
+		cgValue comp_lit_ptr = cg_addr_get_ptr(p, v);
+
+		for_array(field_index, cl->elems) {
+			Ast *elem = cl->elems[field_index];
+
+			cgValue field_expr = {};
+			Entity *field = nullptr;
+			isize index = field_index;
+
+			if (elem->kind == Ast_FieldValue) {
+				ast_node(fv, FieldValue, elem);
+				String name = fv->field->Ident.token.string;
+				Selection sel = lookup_field(bt, name, false);
+				GB_ASSERT(!sel.indirect);
+
+				elem = fv->value;
+				if (sel.index.count > 1) {
+					cgValue dst = cg_emit_deep_field_gep(p, comp_lit_ptr, sel);
+					field_expr = cg_build_expr(p, elem);
+					field_expr = cg_emit_conv(p, field_expr, sel.entity->type);
+					cg_emit_store(p, dst, field_expr);
+					continue;
+				}
+
+				index = sel.index[0];
+			} else {
+				Selection sel = lookup_field_from_index(bt, st->fields[field_index]->Variable.field_index);
+				GB_ASSERT(sel.index.count == 1);
+				GB_ASSERT(!sel.indirect);
+				index = sel.index[0];
+			}
+
+			field = st->fields[index];
+			Type *ft = field->type;
+
+			field_expr = cg_build_expr(p, elem);
+
+			cgValue gep = {};
+			if (st->is_raw_union) {
+				gep = cg_emit_conv(p, comp_lit_ptr, alloc_type_pointer(ft));
+			} else {
+				gep = cg_emit_struct_ep(p, comp_lit_ptr, cast(i32)index);
+			}
+
+			Type *fet = field_expr.type;
+			GB_ASSERT(fet->kind != Type_Tuple);
+
+			// HACK TODO(bill): THIS IS A MASSIVE HACK!!!!
+			if (is_type_union(ft) && !are_types_identical(fet, ft) && !is_type_untyped(fet)) {
+				GB_ASSERT_MSG(union_variant_index(ft, fet) >= 0, "%s", type_to_string(fet));
+
+				GB_PANIC("TODO(bill): cg_emit_store_union_variant");
+				// cg_emit_store_union_variant(p, gep, field_expr, fet);
+			} else {
+				cgValue fv = cg_emit_conv(p, field_expr, ft);
+				cg_emit_store(p, gep, fv);
+			}
+		}
+		return v;
+	}
+
+	case Type_Map: {
+		GB_ASSERT(!build_context.no_dynamic_literals);
+		GB_PANIC("TODO(bill): map literals");
+
+		// cgValue err = cg_dynamic_map_reserve(p, v.addr, 2*cl->elems.count, pos);
+		// gb_unused(err);
+
+		// for (Ast *elem : cl->elems) {
+		// 	ast_node(fv, FieldValue, elem);
+
+		// 	cgValue key   = cg_build_expr(p, fv->field);
+		// 	cgValue value = cg_build_expr(p, fv->value);
+		// 	cg_internal_dynamic_map_set(p, v.addr, type, key, value, elem);
+		// }
+		break;
+	}
+
+	case Type_Array: {
+		auto temp_data = array_make<cgCompoundLitElemTempData>(temporary_allocator(), 0, cl->elems.count);
+
+		populate(p, cl->elems, &temp_data, type);
+
+		cgValue dst_ptr = cg_addr_get_ptr(p, v);
+		for_array(i, temp_data) {
+			i32 index = cast(i32)(temp_data[i].elem_index);
+			temp_data[i].gep = cg_emit_array_epi(p, dst_ptr, index);
+		}
+
+		assign_array(p, temp_data);
+		break;
+	}
+	case Type_EnumeratedArray: {
+		auto temp_data = array_make<cgCompoundLitElemTempData>(temporary_allocator(), 0, cl->elems.count);
+
+		populate(p, cl->elems, &temp_data, type);
+
+		cgValue dst_ptr = cg_addr_get_ptr(p, v);
+		i64 index_offset = exact_value_to_i64(*bt->EnumeratedArray.min_value);
+		for_array(i, temp_data) {
+			i32 index = cast(i32)(temp_data[i].elem_index - index_offset);
+			temp_data[i].gep = cg_emit_array_epi(p, dst_ptr, index);
+		}
+
+		assign_array(p, temp_data);
+		break;
+	}
+	case Type_Slice: {
+		isize count = gb_max(cl->elems.count, cl->max_count);
+
+		TB_CharUnits backing_size = cast(TB_CharUnits)(type_size_of(bt->Slice.elem) * count);
+		TB_CharUnits align = cast(TB_CharUnits)type_align_of(bt->Slice.elem);
+
+		TB_Node *backing = nullptr;
+		if (p->is_startup) {
+			TB_Global *global = tb_global_create(p->module->mod, 0, "", nullptr, TB_LINKAGE_PRIVATE);
+			tb_global_set_storage(p->module->mod, tb_module_get_data(p->module->mod), global, backing_size, align, 0);
+			backing = tb_inst_get_symbol_address(p->func, cast(TB_Symbol *)global);
+		} else {
+			backing = tb_inst_local(p->func, backing_size, align);
+		}
+
+		cgValue data = cg_value(backing, alloc_type_multi_pointer(bt->Slice.elem));
+
+		auto temp_data = array_make<cgCompoundLitElemTempData>(temporary_allocator(), 0, cl->elems.count);
+		populate(p, cl->elems, &temp_data, type);
+
+
+		for_array(i, temp_data) {
+			temp_data[i].gep = cg_emit_ptr_offset(p, data, cg_const_int(p, t_int, temp_data[i].elem_index));
+		}
+
+		assign_array(p, temp_data);
+		cg_fill_slice(p, v, data, cg_const_int(p, t_int, count));
+		return v;
+	}
+
+	case Type_DynamicArray: {
+		GB_ASSERT(!build_context.no_dynamic_literals);
+
+		Type *et = bt->DynamicArray.elem;
+		cgValue size  = cg_const_int(p, t_int, type_size_of(et));
+		cgValue align = cg_const_int(p, t_int, type_align_of(et));
+
+		i64 item_count = gb_max(cl->max_count, cl->elems.count);
+		{
+
+			auto args = slice_make<cgValue>(temporary_allocator(), 5);
+			args[0] = cg_emit_conv(p, cg_addr_get_ptr(p, v), t_rawptr);
+			args[1] = size;
+			args[2] = align;
+			args[3] = cg_const_int(p, t_int, item_count);
+			args[4] = cg_emit_source_code_location_as_global(p, proc_name, pos);
+			cg_emit_runtime_call(p, "__dynamic_array_reserve", args);
+		}
+
+		Type *array_type = alloc_type_array(et, item_count);
+		cgAddr items_addr = cg_add_local(p, array_type, nullptr, true);
+		cgValue items = cg_addr_get_ptr(p, items_addr);
+
+		auto temp_data = array_make<cgCompoundLitElemTempData>(temporary_allocator(), 0, cl->elems.count);
+		populate(p, cl->elems, &temp_data, type);
+
+		for_array(i, temp_data) {
+			temp_data[i].gep = cg_emit_array_epi(p, items, temp_data[i].elem_index);
+		}
+		assign_array(p, temp_data);
+
+		{
+			auto args = slice_make<cgValue>(temporary_allocator(), 6);
+			args[0] = cg_emit_conv(p, v.addr, t_rawptr);
+			args[1] = size;
+			args[2] = align;
+			args[3] = cg_emit_conv(p, items, t_rawptr);
+			args[4] = cg_const_int(p, t_int, item_count);
+			args[5] = cg_emit_source_code_location_as_global(p, proc_name, pos);
+			cg_emit_runtime_call(p, "__dynamic_array_append", args);
+		}
+		break;
+	}
+
+	case Type_Basic: {
+		GB_ASSERT(is_type_any(bt));
+		String field_names[2] = {
+			str_lit("data"),
+			str_lit("id"),
+		};
+		Type *field_types[2] = {
+			t_rawptr,
+			t_typeid,
+		};
+
+		for_array(field_index, cl->elems) {
+			Ast *elem = cl->elems[field_index];
+
+			cgValue field_expr = {};
+			isize index = field_index;
+
+			if (elem->kind == Ast_FieldValue) {
+				ast_node(fv, FieldValue, elem);
+				Selection sel = lookup_field(bt, fv->field->Ident.token.string, false);
+				index = sel.index[0];
+				elem = fv->value;
+			} else {
+				TypeAndValue tav = type_and_value_of_expr(elem);
+				Selection sel = lookup_field(bt, field_names[field_index], false);
+				index = sel.index[0];
+			}
+
+			field_expr = cg_build_expr(p, elem);
+
+			GB_ASSERT(field_expr.type->kind != Type_Tuple);
+
+			Type *ft = field_types[index];
+			cgValue fv = cg_emit_conv(p, field_expr, ft);
+			cgValue gep = cg_emit_struct_ep(p, cg_addr_get_ptr(p, v), index);
+			cg_emit_store(p, gep, fv);
+		}
+		break;
+	}
+
+	case Type_BitSet: {
+		i64 sz = type_size_of(type);
+		if (sz == 0) {
+			return v;
+		}
+		cgValue lower = cg_const_value(p, t_int, exact_value_i64(bt->BitSet.lower));
+		Type *it = bit_set_to_int(bt);
+		cgValue one = cg_const_value(p, it, exact_value_i64(1));
+		for (Ast *elem : cl->elems) {
+			GB_ASSERT(elem->kind != Ast_FieldValue);
+
+			cgValue expr = cg_build_expr(p, elem);
+			GB_ASSERT(expr.type->kind != Type_Tuple);
+
+			cgValue e = cg_emit_conv(p, expr, it);
+			e = cg_emit_arith(p, Token_Sub, e, lower, it);
+			e = cg_emit_arith(p, Token_Shl, one, e, it);
+
+			cgValue old_value = cg_emit_transmute(p, cg_addr_load(p, v), it);
+			cgValue new_value = cg_emit_arith(p, Token_Or, old_value, e, it);
+			new_value = cg_emit_transmute(p, new_value, type);
+			cg_addr_store(p, v, new_value);
+		}
+		return v;
+	}
+
+	case Type_Matrix: {
+		auto temp_data = array_make<cgCompoundLitElemTempData>(temporary_allocator(), 0, cl->elems.count);
+
+		populate(p, cl->elems, &temp_data, type);
+
+		cgValue dst_ptr = cg_addr_get_ptr(p, v);
+		for_array(i, temp_data) {
+			temp_data[i].gep = cg_emit_array_epi(p, dst_ptr, temp_data[i].elem_index);
+		}
+
+		assign_array(p, temp_data);
+		break;
+	}
+
+	case Type_SimdVector: {
+		// auto temp_data = array_make<cgCompoundLitElemTempData>(temporary_allocator(), 0, cl->elems.count);
+
+		// populate(p, cl->elems, &temp_data, type);
+
+		// // TODO(bill): reduce the need for individual `insertelement` if a `shufflevector`
+		// // might be a better option
+		// for (auto const &td : temp_data) if (td.value.node != nullptr) {
+		// 	if (td.elem_length > 0) {
+		// 		for (i64 k = 0; k < td.elem_length; k++) {
+		// 			LLVMValueRef index = cg_const_int(p->module, t_u32, td.elem_index + k).value;
+		// 			vector_value.value = LLVMBuildInsertElement(p->builder, vector_value.value, td.value.value, index, "");
+		// 		}
+		// 	} else {
+		// 		LLVMValueRef index = cg_const_int(p->module, t_u32, td.elem_index).value;
+		// 		vector_value.value = LLVMBuildInsertElement(p->builder, vector_value.value, td.value.value, index, "");
+
+		// 	}
+		// }
+		break;
+	}
+	}
+
+	return v;
+}
+
+gb_internal cgValue cg_build_unary_and(cgProcedure *p, Ast *expr) {
+	ast_node(ue, UnaryExpr, expr);
+	auto tv = type_and_value_of_expr(expr);
+
+
+	Ast *ue_expr = unparen_expr(ue->expr);
+	if (ue_expr->kind == Ast_IndexExpr && tv.mode == Addressing_OptionalOkPtr && is_type_tuple(tv.type)) {
+		GB_PANIC("TODO(bill): &m[k]");
+		// Type *tuple = tv.type;
+
+		// Type *map_type = type_of_expr(ue_expr->IndexExpr.expr);
+		// Type *ot = base_type(map_type);
+		// Type *t = base_type(type_deref(ot));
+		// bool deref = t != ot;
+		// GB_ASSERT(t->kind == Type_Map);
+		// ast_node(ie, IndexExpr, ue_expr);
+
+		// cgValue map_val = cg_build_addr_ptr(p, ie->expr);
+		// if (deref) {
+		// 	map_val = cg_emit_load(p, map_val);
+		// }
+
+		// cgValue key = lb_build_expr(p, ie->index);
+		// key = lb_emit_conv(p, key, t->Map.key);
+
+		// lbAddr addr = lb_addr_map(map_val, key, t, alloc_type_pointer(t->Map.value));
+		// lbValue ptr = lb_addr_get_ptr(p, addr);
+
+		// lbValue ok = lb_emit_comp_against_nil(p, Token_NotEq, ptr);
+		// ok = lb_emit_conv(p, ok, tuple->Tuple.variables[1]->type);
+
+		// lbAddr res = lb_add_local_generated(p, tuple, false);
+		// lbValue gep0 = lb_emit_struct_ep(p, res.addr, 0);
+		// lbValue gep1 = lb_emit_struct_ep(p, res.addr, 1);
+		// lb_emit_store(p, gep0, ptr);
+		// lb_emit_store(p, gep1, ok);
+		// return lb_addr_load(p, res);
+
+	} else if (is_type_soa_pointer(tv.type)) {
+		GB_PANIC("TODO(bill): &soa[i]");
+		// ast_node(ie, IndexExpr, ue_expr);
+		// lbValue addr = lb_build_addr_ptr(p, ie->expr);
+		// lbValue index = lb_build_expr(p, ie->index);
+
+		// if (!build_context.no_bounds_check) {
+		// 	// TODO(bill): soa bounds checking
+		// }
+
+		// return lb_make_soa_pointer(p, tv.type, addr, index);
+	} else if (ue_expr->kind == Ast_CompoundLit) {
+		cgAddr addr = cg_build_addr_compound_lit(p, expr);
+		return addr.addr;
+	} else if (ue_expr->kind == Ast_TypeAssertion) {
+		GB_PANIC("TODO(bill): &v.(T)");
+		// if (is_type_tuple(tv.type)) {
+		// 	Type *tuple = tv.type;
+		// 	Type *ptr_type = tuple->Tuple.variables[0]->type;
+		// 	Type *ok_type = tuple->Tuple.variables[1]->type;
+
+		// 	ast_node(ta, TypeAssertion, ue_expr);
+		// 	TokenPos pos = ast_token(expr).pos;
+		// 	Type *type = type_of_expr(ue_expr);
+		// 	GB_ASSERT(!is_type_tuple(type));
+
+		// 	lbValue e = lb_build_expr(p, ta->expr);
+		// 	Type *t = type_deref(e.type);
+		// 	if (is_type_union(t)) {
+		// 		lbValue v = e;
+		// 		if (!is_type_pointer(v.type)) {
+		// 			v = lb_address_from_load_or_generate_local(p, v);
+		// 		}
+		// 		Type *src_type = type_deref(v.type);
+		// 		Type *dst_type = type;
+
+		// 		lbValue src_tag = {};
+		// 		lbValue dst_tag = {};
+		// 		if (is_type_union_maybe_pointer(src_type)) {
+		// 			src_tag = lb_emit_comp_against_nil(p, Token_NotEq, v);
+		// 			dst_tag = lb_const_bool(p->module, t_bool, true);
+		// 		} else {
+		// 			src_tag = lb_emit_load(p, lb_emit_union_tag_ptr(p, v));
+		// 			dst_tag = lb_const_union_tag(p->module, src_type, dst_type);
+		// 		}
+
+		// 		lbValue ok = lb_emit_comp(p, Token_CmpEq, src_tag, dst_tag);
+
+		// 		lbValue data_ptr = lb_emit_conv(p, v, ptr_type);
+		// 		lbAddr res = lb_add_local_generated(p, tuple, true);
+		// 		lbValue gep0 = lb_emit_struct_ep(p, res.addr, 0);
+		// 		lbValue gep1 = lb_emit_struct_ep(p, res.addr, 1);
+		// 		lb_emit_store(p, gep0, lb_emit_select(p, ok, data_ptr, lb_const_nil(p->module, ptr_type)));
+		// 		lb_emit_store(p, gep1, lb_emit_conv(p, ok, ok_type));
+		// 		return lb_addr_load(p, res);
+		// 	} else if (is_type_any(t)) {
+		// 		lbValue v = e;
+		// 		if (is_type_pointer(v.type)) {
+		// 			v = lb_emit_load(p, v);
+		// 		}
+
+		// 		lbValue data_ptr = lb_emit_conv(p, lb_emit_struct_ev(p, v, 0), ptr_type);
+		// 		lbValue any_id = lb_emit_struct_ev(p, v, 1);
+		// 		lbValue id = lb_typeid(p->module, type);
+
+		// 		lbValue ok = lb_emit_comp(p, Token_CmpEq, any_id, id);
+
+		// 		lbAddr res = lb_add_local_generated(p, tuple, false);
+		// 		lbValue gep0 = lb_emit_struct_ep(p, res.addr, 0);
+		// 		lbValue gep1 = lb_emit_struct_ep(p, res.addr, 1);
+		// 		lb_emit_store(p, gep0, lb_emit_select(p, ok, data_ptr, lb_const_nil(p->module, ptr_type)));
+		// 		lb_emit_store(p, gep1, lb_emit_conv(p, ok, ok_type));
+		// 		return lb_addr_load(p, res);
+		// 	} else {
+		// 		GB_PANIC("TODO(bill): type assertion %s", type_to_string(type));
+		// 	}
+
+		// } else {
+		// 	GB_ASSERT(is_type_pointer(tv.type));
+
+		// 	ast_node(ta, TypeAssertion, ue_expr);
+		// 	TokenPos pos = ast_token(expr).pos;
+		// 	Type *type = type_of_expr(ue_expr);
+		// 	GB_ASSERT(!is_type_tuple(type));
+
+		// 	lbValue e = lb_build_expr(p, ta->expr);
+		// 	Type *t = type_deref(e.type);
+		// 	if (is_type_union(t)) {
+		// 		lbValue v = e;
+		// 		if (!is_type_pointer(v.type)) {
+		// 			v = lb_address_from_load_or_generate_local(p, v);
+		// 		}
+		// 		Type *src_type = type_deref(v.type);
+		// 		Type *dst_type = type;
+
+
+		// 		if ((p->state_flags & StateFlag_no_type_assert) == 0) {
+		// 			lbValue src_tag = {};
+		// 			lbValue dst_tag = {};
+		// 			if (is_type_union_maybe_pointer(src_type)) {
+		// 				src_tag = lb_emit_comp_against_nil(p, Token_NotEq, v);
+		// 				dst_tag = lb_const_bool(p->module, t_bool, true);
+		// 			} else {
+		// 				src_tag = lb_emit_load(p, lb_emit_union_tag_ptr(p, v));
+		// 				dst_tag = lb_const_union_tag(p->module, src_type, dst_type);
+		// 			}
+
+
+		// 			isize arg_count = 6;
+		// 			if (build_context.no_rtti) {
+		// 				arg_count = 4;
+		// 			}
+
+		// 			lbValue ok = lb_emit_comp(p, Token_CmpEq, src_tag, dst_tag);
+		// 			auto args = array_make<lbValue>(permanent_allocator(), arg_count);
+		// 			args[0] = ok;
+
+		// 			args[1] = lb_find_or_add_entity_string(p->module, get_file_path_string(pos.file_id));
+		// 			args[2] = lb_const_int(p->module, t_i32, pos.line);
+		// 			args[3] = lb_const_int(p->module, t_i32, pos.column);
+
+		// 			if (!build_context.no_rtti) {
+		// 				args[4] = lb_typeid(p->module, src_type);
+		// 				args[5] = lb_typeid(p->module, dst_type);
+		// 			}
+		// 			lb_emit_runtime_call(p, "type_assertion_check", args);
+		// 		}
+
+		// 		lbValue data_ptr = v;
+		// 		return lb_emit_conv(p, data_ptr, tv.type);
+		// 	} else if (is_type_any(t)) {
+		// 		lbValue v = e;
+		// 		if (is_type_pointer(v.type)) {
+		// 			v = lb_emit_load(p, v);
+		// 		}
+		// 		lbValue data_ptr = lb_emit_struct_ev(p, v, 0);
+		// 		if ((p->state_flags & StateFlag_no_type_assert) == 0) {
+		// 			GB_ASSERT(!build_context.no_rtti);
+
+		// 			lbValue any_id = lb_emit_struct_ev(p, v, 1);
+
+		// 			lbValue id = lb_typeid(p->module, type);
+		// 			lbValue ok = lb_emit_comp(p, Token_CmpEq, any_id, id);
+		// 			auto args = array_make<lbValue>(permanent_allocator(), 6);
+		// 			args[0] = ok;
+
+		// 			args[1] = lb_find_or_add_entity_string(p->module, get_file_path_string(pos.file_id));
+		// 			args[2] = lb_const_int(p->module, t_i32, pos.line);
+		// 			args[3] = lb_const_int(p->module, t_i32, pos.column);
+
+		// 			args[4] = any_id;
+		// 			args[5] = id;
+		// 			lb_emit_runtime_call(p, "type_assertion_check", args);
+		// 		}
+
+		// 		return lb_emit_conv(p, data_ptr, tv.type);
+		// 	} else {
+		// 		GB_PANIC("TODO(bill): type assertion %s", type_to_string(type));
+		// 	}
+		// }
+	}
+
+	return cg_build_addr_ptr(p, ue->expr);
+}
+
+gb_internal cgValue cg_emit_cast_union(cgProcedure *p, cgValue value, Type *type, TokenPos pos) {
+	Type *src_type = value.type;
+	bool is_ptr = is_type_pointer(src_type);
+
+	bool is_tuple = true;
+	Type *tuple = type;
+	if (type->kind != Type_Tuple) {
+		is_tuple = false;
+		tuple = make_optional_ok_type(type);
+	}
+
+
+	if (is_ptr) {
+		value = cg_emit_load(p, value);
+	}
+	Type *src = base_type(type_deref(src_type));
+	GB_ASSERT_MSG(is_type_union(src), "%s", type_to_string(src_type));
+	Type *dst = tuple->Tuple.variables[0]->type;
+
+	cgValue value_  = cg_address_from_load_or_generate_local(p, value);
+
+	if ((p->state_flags & StateFlag_no_type_assert) != 0 && !is_tuple) {
+		// just do a bit cast of the data at the front
+		cgValue ptr = cg_emit_conv(p, value_, alloc_type_pointer(type));
+		return cg_emit_load(p, ptr);
+	}
+
+
+	cgValue tag = {};
+	cgValue dst_tag = {};
+	cgValue cond = {};
+	cgValue data = {};
+
+	cgValue gep0 = cg_add_local(p, tuple->Tuple.variables[0]->type, nullptr, true).addr;
+	cgValue gep1 = cg_add_local(p, tuple->Tuple.variables[1]->type, nullptr, true).addr;
+
+	if (is_type_union_maybe_pointer(src)) {
+		data = cg_emit_load(p, cg_emit_conv(p, value_, gep0.type));
+	} else {
+		tag     = cg_emit_load(p, cg_emit_union_tag_ptr(p, value_));
+		dst_tag = cg_const_union_tag(p, src, dst);
+	}
+
+	TB_Node *ok_block  = cg_control_region(p, "union_cast_ok");
+	TB_Node *end_block = cg_control_region(p, "union_cast_end");
+
+	if (data.node != nullptr) {
+		GB_ASSERT(is_type_union_maybe_pointer(src));
+		cond = cg_emit_comp_against_nil(p, Token_NotEq, data);
+	} else {
+		cond = cg_emit_comp(p, Token_CmpEq, tag, dst_tag);
+	}
+
+	cg_emit_if(p, cond, ok_block, end_block);
+	tb_inst_set_control(p->func, ok_block);
+
+	if (data.node == nullptr) {
+		data = cg_emit_load(p, cg_emit_conv(p, value_, gep0.type));
+	}
+	cg_emit_store(p, gep0, data);
+	cg_emit_store(p, gep1, cg_const_bool(p, t_bool, true));
+
+	cg_emit_goto(p, end_block);
+	tb_inst_set_control(p->func, end_block);
+
+	if (!is_tuple) {
+		GB_ASSERT((p->state_flags & StateFlag_no_type_assert) == 0);
+		// NOTE(bill): Panic on invalid conversion
+		Type *dst_type = tuple->Tuple.variables[0]->type;
+
+		isize arg_count = 7;
+		if (build_context.no_rtti) {
+			arg_count = 4;
+		}
+
+		cgValue ok = cg_emit_load(p, gep1);
+		auto args = slice_make<cgValue>(permanent_allocator(), arg_count);
+		args[0] = ok;
+
+		args[1] = cg_const_string(p, t_string, get_file_path_string(pos.file_id));
+		args[2] = cg_const_int(p, t_i32, pos.line);
+		args[3] = cg_const_int(p, t_i32, pos.column);
+
+		if (!build_context.no_rtti) {
+			args[4] = cg_typeid(p, src_type);
+			args[5] = cg_typeid(p, dst_type);
+			args[6] = cg_emit_conv(p, value_, t_rawptr);
+		}
+		cg_emit_runtime_call(p, "type_assertion_check2", args);
+
+		return cg_emit_load(p, gep0);
+	}
+
+	return cg_value_multi2(cg_emit_load(p, gep0), cg_emit_load(p, gep1), tuple);
+}
+
+gb_internal cgValue cg_emit_cast_any(cgProcedure *p, cgValue value, Type *type, TokenPos pos) {
+	Type *src_type = value.type;
+
+	if (is_type_pointer(src_type)) {
+		value = cg_emit_load(p, value);
+	}
+
+	bool is_tuple = true;
+	Type *tuple = type;
+	if (type->kind != Type_Tuple) {
+		is_tuple = false;
+		tuple = make_optional_ok_type(type);
+	}
+	Type *dst_type = tuple->Tuple.variables[0]->type;
+
+	if ((p->state_flags & StateFlag_no_type_assert) != 0 && !is_tuple) {
+		// just do a bit cast of the data at the front
+		cgValue ptr = cg_emit_struct_ev(p, value, 0);
+		ptr = cg_emit_conv(p, ptr, alloc_type_pointer(type));
+		return cg_emit_load(p, ptr);
+	}
+
+	cgValue dst_typeid = cg_typeid(p, dst_type);
+	cgValue any_typeid = cg_emit_struct_ev(p, value, 1);
+
+
+	TB_Node *ok_block = cg_control_region(p, "any_cast_ok");
+	TB_Node *end_block = cg_control_region(p, "any_cast_end");
+	cgValue cond = cg_emit_comp(p, Token_CmpEq, any_typeid, dst_typeid);
+	cg_emit_if(p, cond, ok_block, end_block);
+	tb_inst_set_control(p->func, ok_block);
+
+	cgValue gep0 = cg_add_local(p, tuple->Tuple.variables[0]->type, nullptr, true).addr;
+	cgValue gep1 = cg_add_local(p, tuple->Tuple.variables[1]->type, nullptr, true).addr;
+
+	cgValue any_data = cg_emit_struct_ev(p, value, 0);
+	cgValue ptr = cg_emit_conv(p, any_data, alloc_type_pointer(dst_type));
+	cg_emit_store(p, gep0, cg_emit_load(p, ptr));
+	cg_emit_store(p, gep1, cg_const_bool(p, t_bool, true));
+
+	cg_emit_goto(p, end_block);
+	tb_inst_set_control(p->func, end_block);
+
+	if (!is_tuple) {
+		// NOTE(bill): Panic on invalid conversion
+		cgValue ok = cg_emit_load(p, gep1);
+
+		isize arg_count = 7;
+		if (build_context.no_rtti) {
+			arg_count = 4;
+		}
+		auto args = slice_make<cgValue>(permanent_allocator(), arg_count);
+		args[0] = ok;
+
+		args[1] = cg_const_string(p, t_string, get_file_path_string(pos.file_id));
+		args[2] = cg_const_int(p, t_i32, pos.line);
+		args[3] = cg_const_int(p, t_i32, pos.column);
+
+		if (!build_context.no_rtti) {
+			args[4] = any_typeid;
+			args[5] = dst_typeid;
+			args[6] = cg_emit_struct_ev(p, value, 0);
+		}
+		cg_emit_runtime_call(p, "type_assertion_check2", args);
+
+		return cg_emit_load(p, gep0);
+	}
+
+	return cg_value_multi2(cg_emit_load(p, gep0), cg_emit_load(p, gep1), tuple);
+}
+
+
+gb_internal cgValue cg_build_type_assertion(cgProcedure *p, Ast *expr, Type *type) {
+	ast_node(ta, TypeAssertion, expr);
+
+	TokenPos pos = ast_token(expr).pos;
+	cgValue e = cg_build_expr(p, ta->expr);
+	Type *t = type_deref(e.type);
+
+	if (is_type_union(t)) {
+		return cg_emit_cast_union(p, e, type, pos);
+	} else if (is_type_any(t)) {
+		return cg_emit_cast_any(p, e, type, pos);
+	}
+	GB_PANIC("TODO(bill): type assertion %s", type_to_string(e.type));
+	return {};
+}
+
+
+gb_internal cgValue cg_build_expr_internal(cgProcedure *p, Ast *expr) {
+	expr = unparen_expr(expr);
+
+	TokenPos expr_pos = ast_token(expr).pos;
+	TypeAndValue tv = type_and_value_of_expr(expr);
+	Type *type = type_of_expr(expr);
+	GB_ASSERT_MSG(tv.mode != Addressing_Invalid, "invalid expression '%s' (tv.mode = %d, tv.type = %s) @ %s\n Current Proc: %.*s : %s", expr_to_string(expr), tv.mode, type_to_string(tv.type), token_pos_to_string(expr_pos), LIT(p->name), type_to_string(p->type));
+
+	if (tv.value.kind != ExactValue_Invalid &&
+	    expr->kind != Ast_CompoundLit) {
+		// NOTE(bill): The commented out code below is just for debug purposes only
+		// if (is_type_untyped(type)) {
+		// 	gb_printf_err("%s %s : %s @ %p\n", token_pos_to_string(expr_pos), expr_to_string(expr), type_to_string(expr->tav.type), expr);
+		// 	GB_PANIC("%s\n", type_to_string(tv.type));
+		// }
+		// NOTE(bill): Short on constant values
+		return cg_const_value(p, type, tv.value);
+	} else if (tv.mode == Addressing_Type) {
+		// NOTE(bill, 2023-01-16): is this correct? I hope so at least
+		return cg_typeid(p, tv.type);
+	}
+
+	switch (expr->kind) {
+	case_ast_node(bl, BasicLit, expr);
+		TokenPos pos = bl->token.pos;
+		GB_PANIC("Non-constant basic literal %s - %.*s", token_pos_to_string(pos), LIT(token_strings[bl->token.kind]));
+	case_end;
+
+	case_ast_node(bd, BasicDirective, expr);
+		TokenPos pos = bd->token.pos;
+		GB_PANIC("Non-constant basic literal %s - %.*s", token_pos_to_string(pos), LIT(bd->name.string));
+	case_end;
+
+	case_ast_node(i, Ident, expr);
+		Entity *e = entity_from_expr(expr);
+		e = strip_entity_wrapping(e);
+
+		GB_ASSERT_MSG(e != nullptr, "%s in %.*s %p", expr_to_string(expr), LIT(p->name), expr);
+
+		if (e->kind == Entity_Builtin) {
+			Token token = ast_token(expr);
+			GB_PANIC("TODO(bill): lb_build_expr Entity_Builtin '%.*s'\n"
+			         "\t at %s", LIT(builtin_procs[e->Builtin.id].name),
+			         token_pos_to_string(token.pos));
+			return {};
+		} else if (e->kind == Entity_Nil) {
+			// TODO(bill): is this correct?
+			return cg_value(cast(TB_Node *)nullptr, e->type);
+		}
+		GB_ASSERT(e->kind != Entity_ProcGroup);
+
+		cgAddr *addr = map_get(&p->variable_map, e);
+		if (addr) {
+			return cg_addr_load(p, *addr);
+		}
+		return cg_find_ident(p, e, expr);
+	case_end;
+
+	case_ast_node(i, Implicit, expr);
+		return cg_addr_load(p, cg_build_addr(p, expr));
+	case_end;
+
+	case_ast_node(u, Uninit, expr);
+		if (is_type_untyped(type)) {
+			return cg_value(cast(TB_Node *)nullptr, t_untyped_uninit);
+		}
+		return cg_value(tb_inst_poison(p->func), type);
+	case_end;
+
+	case_ast_node(de, DerefExpr, expr);
+		return cg_addr_load(p, cg_build_addr(p, expr));
+	case_end;
+
+
+	case_ast_node(se, SelectorExpr, expr);
+		TypeAndValue tav = type_and_value_of_expr(expr);
+		GB_ASSERT(tav.mode != Addressing_Invalid);
+		return cg_addr_load(p, cg_build_addr(p, expr));
+	case_end;
+
+	case_ast_node(ise, ImplicitSelectorExpr, expr);
+		TypeAndValue tav = type_and_value_of_expr(expr);
+		GB_ASSERT(tav.mode == Addressing_Constant);
+
+		return cg_const_value(p, type, tv.value);
+	case_end;
+
+
+	case_ast_node(se, SelectorCallExpr, expr);
+		GB_ASSERT(se->modified_call);
+		return cg_build_call_expr(p, se->call);
+	case_end;
+
+	case_ast_node(i, CallExpr, expr);
+		return cg_build_call_expr(p, expr);
+	case_end;
+
+	case_ast_node(cl, CompoundLit, expr);
+		cgAddr addr = cg_build_addr_compound_lit(p, expr);
+		return cg_addr_load(p, addr);
+	case_end;
+
+
+	case_ast_node(te, TernaryIfExpr, expr);
+		cgValue incoming_values[2] = {};
+		TB_Node *incoming_regions[2] = {};
+
+		TB_Node *then  = cg_control_region(p, "if_then");
+		TB_Node *done  = cg_control_region(p, "if_done");
+		TB_Node *else_ = cg_control_region(p, "if_else");
+
+		cg_build_cond(p, te->cond, then, else_);
+		tb_inst_set_control(p->func, then);
+
+		Type *type = default_type(type_of_expr(expr));
+
+		incoming_values [0] = cg_emit_conv(p, cg_build_expr(p, te->x), type);
+		incoming_regions[0] = tb_inst_get_control(p->func);
+
+		cg_emit_goto(p, done);
+		tb_inst_set_control(p->func, else_);
+
+		incoming_values [1] = cg_emit_conv(p, cg_build_expr(p, te->y), type);
+		incoming_regions[1] = tb_inst_get_control(p->func);
+
+		cg_emit_goto(p, done);
+		tb_inst_set_control(p->func, done);
+
+		GB_ASSERT(incoming_values[0].kind == cgValue_Value ||
+		          incoming_values[0].kind == cgValue_Addr);
+		GB_ASSERT(incoming_values[0].kind == incoming_values[1].kind);
+
+		cgValue res = {};
+		res.kind = incoming_values[0].kind;
+		res.type = type;
+		TB_DataType dt = cg_data_type(type);
+		if (res.kind == cgValue_Addr) {
+			dt = TB_TYPE_PTR;
+		}
+		res.node = tb_inst_incomplete_phi(p->func, dt, done, 2);
+		tb_inst_add_phi_operand(p->func, res.node, incoming_regions[0], incoming_values[0].node);
+		tb_inst_add_phi_operand(p->func, res.node, incoming_regions[1], incoming_values[1].node);
+		return res;
+	case_end;
+
+	case_ast_node(te, TernaryWhenExpr, expr);
+		TypeAndValue tav = type_and_value_of_expr(te->cond);
+		GB_ASSERT(tav.mode == Addressing_Constant);
+		GB_ASSERT(tav.value.kind == ExactValue_Bool);
+		if (tav.value.value_bool) {
+			return cg_build_expr(p, te->x);
+		} else {
+			return cg_build_expr(p, te->y);
+		}
+	case_end;
+
+	case_ast_node(tc, TypeCast, expr);
+		cgValue e = cg_build_expr(p, tc->expr);
+		switch (tc->token.kind) {
+		case Token_cast:
+			return cg_emit_conv(p, e, type);
+		case Token_transmute:
+			return cg_emit_transmute(p, e, type);
+		}
+		GB_PANIC("Invalid AST TypeCast");
+	case_end;
+
+	case_ast_node(ac, AutoCast, expr);
+		cgValue value = cg_build_expr(p, ac->expr);
+		return cg_emit_conv(p, value, type);
+	case_end;
+
+	case_ast_node(se, SliceExpr, expr);
+		if (is_type_slice(type_of_expr(se->expr))) {
+			// NOTE(bill): Quick optimization
+			if (se->high == nullptr &&
+			    (se->low == nullptr || cg_is_expr_constant_zero(se->low))) {
+				return cg_build_expr(p, se->expr);
+			}
+		}
+		return cg_addr_load(p, cg_build_addr(p, expr));
+	case_end;
+
+	case_ast_node(ie, IndexExpr, expr);
+		return cg_addr_load(p, cg_build_addr(p, expr));
+	case_end;
+
+	case_ast_node(ie, MatrixIndexExpr, expr);
+		return cg_addr_load(p, cg_build_addr(p, expr));
+	case_end;
+
+	case_ast_node(ue, UnaryExpr, expr);
+		if (ue->op.kind == Token_And) {
+			return cg_build_unary_and(p, expr);
+		}
+		cgValue v = cg_build_expr(p, ue->expr);
+		return cg_emit_unary_arith(p, ue->op.kind, v, type);
+	case_end;
+	case_ast_node(be, BinaryExpr, expr);
+		return cg_build_binary_expr(p, expr);
+	case_end;
+
+	case_ast_node(oe, OrReturnExpr, expr);
+		return cg_build_or_return(p, oe->expr, tv.type);
+	case_end;
+
+	case_ast_node(oe, OrElseExpr, expr);
+		return cg_build_or_else(p, oe->x, oe->y, tv.type);
+	case_end;
+
+	case_ast_node(ta, TypeAssertion, expr);
+		return cg_build_type_assertion(p, expr, tv.type);
+	case_end;
+
+	case_ast_node(pl, ProcLit, expr);
+		cgProcedure *anon = cg_procedure_generate_anonymous(p->module, expr, p);
+		GB_ASSERT(anon != nullptr);
+		GB_ASSERT(anon->symbol != nullptr);
+		return cg_value(tb_inst_get_symbol_address(p->func, anon->symbol), type);
+	case_end;
+
+	}
+	TokenPos token_pos = ast_token(expr).pos;
+	GB_PANIC("Unexpected expression\n"
+	         "\tAst: %.*s @ "
+	         "%s\n",
+	         LIT(ast_strings[expr->kind]),
+	         token_pos_to_string(token_pos));
+
+	return {};
+
+}
+
+gb_internal cgValue cg_build_addr_ptr(cgProcedure *p, Ast *expr) {
+	cgAddr addr = cg_build_addr(p, expr);
+	return cg_addr_get_ptr(p, addr);
+}
+
+gb_internal cgAddr cg_build_addr_internal(cgProcedure *p, Ast *expr);
+gb_internal cgAddr cg_build_addr(cgProcedure *p, Ast *expr) {
+	expr = unparen_expr(expr);
+
+	// IMPORTANT NOTE(bill):
+	// Selector Call Expressions (foo->bar(...))
+	// must only evaluate `foo` once as it gets transformed into
+	// `foo.bar(foo, ...)`
+	// And if `foo` is a procedure call or something more complex, storing the value
+	// once is a very good idea
+	// If a stored value is found, it must be removed from the cache
+	if (expr->state_flags & StateFlag_SelectorCallExpr) {
+		// lbAddr *pp = map_get(&p->selector_addr, expr);
+		// if (pp != nullptr) {
+		// 	lbAddr res = *pp;
+		// 	map_remove(&p->selector_addr, expr);
+		// 	return res;
+		// }
+	}
+	cgAddr addr = cg_build_addr_internal(p, expr);
+	if (expr->state_flags & StateFlag_SelectorCallExpr) {
+		// map_set(&p->selector_addr, expr, addr);
+	}
+	return addr;
+}
+
+gb_internal cgAddr cg_build_addr_index_expr(cgProcedure *p, Ast *expr) {
+	ast_node(ie, IndexExpr, expr);
+
+	Type *t = base_type(type_of_expr(ie->expr));
+
+	bool deref = is_type_pointer(t);
+	t = base_type(type_deref(t));
+	if (is_type_soa_struct(t)) {
+		GB_PANIC("TODO(bill): #soa");
+		// // SOA STRUCTURES!!!!
+		// lbValue val = cg_build_addr_ptr(p, ie->expr);
+		// if (deref) {
+		// 	val = cg_emit_load(p, val);
+		// }
+
+		// cgValue index = cg_build_expr(p, ie->index);
+		// return cg_addr_soa_variable(val, index, ie->index);
+	}
+
+	if (ie->expr->tav.mode == Addressing_SoaVariable) {
+		GB_PANIC("TODO(bill): #soa");
+		// // SOA Structures for slices/dynamic arrays
+		// GB_ASSERT(is_type_pointer(type_of_expr(ie->expr)));
+
+		// lbValue field = lb_build_expr(p, ie->expr);
+		// lbValue index = lb_build_expr(p, ie->index);
+
+
+		// if (!build_context.no_bounds_check) {
+		// 	// TODO HACK(bill): Clean up this hack to get the length for bounds checking
+		// 	// GB_ASSERT(LLVMIsALoadInst(field.value));
+
+		// 	// lbValue a = {};
+		// 	// a.value = LLVMGetOperand(field.value, 0);
+		// 	// a.type = alloc_type_pointer(field.type);
+
+		// 	// irInstr *b = &a->Instr;
+		// 	// GB_ASSERT(b->kind == irInstr_StructElementPtr);
+		// 	// lbValue base_struct = b->StructElementPtr.address;
+
+		// 	// GB_ASSERT(is_type_soa_struct(type_deref(ir_type(base_struct))));
+		// 	// lbValue len = ir_soa_struct_len(p, base_struct);
+		// 	// lb_emit_bounds_check(p, ast_token(ie->index), index, len);
+		// }
+		// lbValue val = lb_emit_ptr_offset(p, field, index);
+		// return lb_addr(val);
+	}
+
+	GB_ASSERT_MSG(is_type_indexable(t), "%s %s", type_to_string(t), expr_to_string(expr));
+
+	if (is_type_map(t)) {
+		GB_PANIC("TODO(bill): map indexing");
+		// lbAddr map_addr = lb_build_addr(p, ie->expr);
+		// lbValue key = lb_build_expr(p, ie->index);
+		// key = lb_emit_conv(p, key, t->Map.key);
+
+		// Type *result_type = type_of_expr(expr);
+		// lbValue map_ptr = lb_addr_get_ptr(p, map_addr);
+		// if (is_type_pointer(type_deref(map_ptr.type))) {
+		// 	map_ptr = lb_emit_load(p, map_ptr);
+		// }
+		// return lb_addr_map(map_ptr, key, t, result_type);
+	}
+
+	switch (t->kind) {
+	case Type_Array: {
+		cgValue array = {};
+		array = cg_build_addr_ptr(p, ie->expr);
+		if (deref) {
+			array = cg_emit_load(p, array);
+		}
+		cgValue index = cg_build_expr(p, ie->index);
+		index = cg_emit_conv(p, index, t_int);
+		cgValue elem = cg_emit_array_ep(p, array, index);
+
+		auto index_tv = type_and_value_of_expr(ie->index);
+		if (index_tv.mode != Addressing_Constant) {
+			// cgValue len = cg_const_int(p->module, t_int, t->Array.count);
+			// cg_emit_bounds_check(p, ast_token(ie->index), index, len);
+		}
+		return cg_addr(elem);
+	}
+
+	case Type_EnumeratedArray: {
+		cgValue array = {};
+		array = cg_build_addr_ptr(p, ie->expr);
+		if (deref) {
+			array = cg_emit_load(p, array);
+		}
+
+		Type *index_type = t->EnumeratedArray.index;
+
+		auto index_tv = type_and_value_of_expr(ie->index);
+
+		cgValue index = {};
+		if (compare_exact_values(Token_NotEq, *t->EnumeratedArray.min_value, exact_value_i64(0))) {
+			if (index_tv.mode == Addressing_Constant) {
+				ExactValue idx = exact_value_sub(index_tv.value, *t->EnumeratedArray.min_value);
+				index = cg_const_value(p, index_type, idx);
+			} else {
+				index = cg_emit_arith(p, Token_Sub,
+				                      cg_build_expr(p, ie->index),
+				                      cg_const_value(p, index_type, *t->EnumeratedArray.min_value),
+				                      index_type);
+				index = cg_emit_conv(p, index, t_int);
+			}
+		} else {
+			index = cg_emit_conv(p, cg_build_expr(p, ie->index), t_int);
+		}
+
+		cgValue elem = cg_emit_array_ep(p, array, index);
+
+		if (index_tv.mode != Addressing_Constant) {
+			// cgValue len = cg_const_int(p->module, t_int, t->EnumeratedArray.count);
+			// cg_emit_bounds_check(p, ast_token(ie->index), index, len);
+		}
+		return cg_addr(elem);
+	}
+
+	case Type_Slice: {
+		cgValue slice = {};
+		slice = cg_build_expr(p, ie->expr);
+		if (deref) {
+			slice = cg_emit_load(p, slice);
+		}
+		cgValue elem = cg_builtin_raw_data(p, slice);
+		cgValue index = cg_emit_conv(p, cg_build_expr(p, ie->index), t_int);
+		// cgValue len = cg_builtin_len(p, slice);
+		// cg_emit_bounds_check(p, ast_token(ie->index), index, len);
+		cgValue v = cg_emit_ptr_offset(p, elem, index);
+		v.type = alloc_type_pointer(type_deref(v.type, true));
+		return cg_addr(v);
+	}
+
+	case Type_MultiPointer: {
+		cgValue multi_ptr = {};
+		multi_ptr = cg_build_expr(p, ie->expr);
+		if (deref) {
+			multi_ptr = cg_emit_load(p, multi_ptr);
+		}
+		cgValue index = cg_build_expr(p, ie->index);
+		index = cg_emit_conv(p, index, t_int);
+
+		cgValue v = cg_emit_ptr_offset(p, multi_ptr, index);
+		v.type = alloc_type_pointer(type_deref(v.type, true));
+		return cg_addr(v);
+	}
+
+	case Type_RelativeMultiPointer: {
+		cgValue multi_ptr = {};
+		multi_ptr = cg_build_expr(p, ie->expr);
+		if (deref) {
+			multi_ptr = cg_emit_load(p, multi_ptr);
+		}
+		cgValue index = cg_build_expr(p, ie->index);
+		index = cg_emit_conv(p, index, t_int);
+
+		cgValue v = cg_emit_ptr_offset(p, multi_ptr, index);
+		v.type = alloc_type_pointer(type_deref(v.type, true));
+		return cg_addr(v);
+	}
+
+	case Type_DynamicArray: {
+		cgValue dynamic_array = {};
+		dynamic_array = cg_build_expr(p, ie->expr);
+		if (deref) {
+			dynamic_array = cg_emit_load(p, dynamic_array);
+		}
+		cgValue elem = cg_builtin_raw_data(p, dynamic_array);
+		cgValue index = cg_emit_conv(p, cg_build_expr(p, ie->index), t_int);
+		// cgValue len = cg_dynamic_array_len(p, dynamic_array);
+		// cg_emit_bounds_check(p, ast_token(ie->index), index, len);
+		cgValue v = cg_emit_ptr_offset(p, elem, index);
+		v.type = alloc_type_pointer(type_deref(v.type, true));
+		return cg_addr(v);
+	}
+
+	case Type_Matrix: {
+		GB_PANIC("TODO(bill): matrix");
+		// lbValue matrix = {};
+		// matrix = lb_build_addr_ptr(p, ie->expr);
+		// if (deref) {
+		// 	matrix = lb_emit_load(p, matrix);
+		// }
+		// lbValue index = lb_build_expr(p, ie->index);
+		// index = lb_emit_conv(p, index, t_int);
+		// lbValue elem = lb_emit_matrix_ep(p, matrix, lb_const_int(p->module, t_int, 0), index);
+		// elem = lb_emit_conv(p, elem, alloc_type_pointer(type_of_expr(expr)));
+
+		// auto index_tv = type_and_value_of_expr(ie->index);
+		// if (index_tv.mode != Addressing_Constant) {
+		// 	lbValue len = lb_const_int(p->module, t_int, t->Matrix.column_count);
+		// 	lb_emit_bounds_check(p, ast_token(ie->index), index, len);
+		// }
+		// return lb_addr(elem);
+	}
+
+
+	case Type_Basic: { // Basic_string
+		cgValue str;
+		cgValue elem;
+		cgValue len;
+		cgValue index;
+
+		str = cg_build_expr(p, ie->expr);
+		if (deref) {
+			str = cg_emit_load(p, str);
+		}
+		elem = cg_builtin_raw_data(p, str);
+		len = cg_builtin_len(p, str);
+
+		index = cg_emit_conv(p, cg_build_expr(p, ie->index), t_int);
+		// cg_emit_bounds_check(p, ast_token(ie->index), index, len);
+
+		cgValue v = cg_emit_ptr_offset(p, elem, index);
+		v.type = alloc_type_pointer(type_deref(v.type, true));
+		return cg_addr(v);
+	}
+	}
+	return {};
+}
+
+gb_internal cgAddr cg_build_addr_internal(cgProcedure *p, Ast *expr) {
+	switch (expr->kind) {
+	case_ast_node(i, Implicit, expr);
+		cgAddr v = {};
+		switch (i->kind) {
+		case Token_context:
+			v = cg_find_or_generate_context_ptr(p);
+			break;
+		}
+
+		GB_ASSERT(v.addr.node != nullptr);
+		return v;
+	case_end;
+
+	case_ast_node(i, Ident, expr);
+		if (is_blank_ident(expr)) {
+			cgAddr val = {};
+			return val;
+		}
+		String name = i->token.string;
+		Entity *e = entity_of_node(expr);
+		return cg_build_addr_from_entity(p, e, expr);
+	case_end;
+
+	case_ast_node(de, DerefExpr, expr);
+		Type *t = type_of_expr(de->expr);
+		if (is_type_relative_pointer(t)) {
+			cgAddr addr = cg_build_addr(p, de->expr);
+			addr.relative.deref = true;
+			return addr;
+		} else if (is_type_soa_pointer(t)) {
+			cgValue value = cg_build_expr(p, de->expr);
+			cgValue ptr = cg_emit_struct_ev(p, value, 0);
+			cgValue idx = cg_emit_struct_ev(p, value, 1);
+			GB_PANIC("TODO(bill): cg_addr_soa_variable");
+			// return cg_addr_soa_variable(ptr, idx, nullptr);
+		}
+		cgValue addr = cg_build_expr(p, de->expr);
+		return cg_addr(addr);
+	case_end;
+
+	case_ast_node(ie, IndexExpr, expr);
+		return cg_build_addr_index_expr(p, expr);
+	case_end;
+
+	case_ast_node(se, SliceExpr, expr);
+		return cg_build_addr_slice_expr(p, expr);
+	case_end;
+
+	case_ast_node(se, SelectorExpr, expr);
+		Ast *sel_node = unparen_expr(se->selector);
+		if (sel_node->kind != Ast_Ident) {
+			GB_PANIC("Unsupported selector expression");
+		}
+		String selector = sel_node->Ident.token.string;
+		TypeAndValue tav = type_and_value_of_expr(se->expr);
+
+		if (tav.mode == Addressing_Invalid) {
+			// NOTE(bill): Imports
+			Entity *imp = entity_of_node(se->expr);
+			if (imp != nullptr) {
+				GB_ASSERT(imp->kind == Entity_ImportName);
+			}
+			return cg_build_addr(p, unparen_expr(se->selector));
+		}
+
+
+		Type *type = base_type(tav.type);
+		if (tav.mode == Addressing_Type) { // Addressing_Type
+			Selection sel = lookup_field(tav.type, selector, true);
+			if (sel.pseudo_field) {
+				GB_ASSERT(sel.entity->kind == Entity_Procedure);
+				return cg_addr(cg_find_value_from_entity(p->module, sel.entity));
+			}
+			GB_PANIC("Unreachable %.*s", LIT(selector));
+		}
+
+		if (se->swizzle_count > 0) {
+			Type *array_type = base_type(type_deref(tav.type));
+			GB_ASSERT(array_type->kind == Type_Array);
+			u8 swizzle_count = se->swizzle_count;
+			u8 swizzle_indices_raw = se->swizzle_indices;
+			u8 swizzle_indices[4] = {};
+			for (u8 i = 0; i < swizzle_count; i++) {
+				u8 index = swizzle_indices_raw>>(i*2) & 3;
+				swizzle_indices[i] = index;
+			}
+			cgValue a = {};
+			if (is_type_pointer(tav.type)) {
+				a = cg_build_expr(p, se->expr);
+			} else {
+				cgAddr addr = cg_build_addr(p, se->expr);
+				a = cg_addr_get_ptr(p, addr);
+			}
+
+			GB_ASSERT(is_type_array(expr->tav.type));
+			GB_PANIC("TODO(bill): cg_addr_swizzle");
+			// return cg_addr_swizzle(a, expr->tav.type, swizzle_count, swizzle_indices);
+		}
+
+		Selection sel = lookup_field(type, selector, false);
+		GB_ASSERT(sel.entity != nullptr);
+		if (sel.pseudo_field) {
+			GB_ASSERT(sel.entity->kind == Entity_Procedure);
+			Entity *e = entity_of_node(sel_node);
+			return cg_addr(cg_find_value_from_entity(p->module, e));
+		}
+
+		{
+			cgAddr addr = cg_build_addr(p, se->expr);
+			if (addr.kind == cgAddr_Map) {
+				cgValue v = cg_addr_load(p, addr);
+				cgValue a = cg_address_from_load_or_generate_local(p, v);
+				a = cg_emit_deep_field_gep(p, a, sel);
+				return cg_addr(a);
+			} else if (addr.kind == cgAddr_Context) {
+				GB_ASSERT(sel.index.count > 0);
+				if (addr.ctx.sel.index.count >= 0) {
+					sel = selection_combine(addr.ctx.sel, sel);
+				}
+				addr.ctx.sel = sel;
+				addr.kind = cgAddr_Context;
+				return addr;
+			} else if (addr.kind == cgAddr_SoaVariable) {
+				cgValue index = addr.soa.index;
+				i64 first_index = sel.index[0];
+				Selection sub_sel = sel;
+				sub_sel.index.data += 1;
+				sub_sel.index.count -= 1;
+
+				cgValue arr = cg_emit_struct_ep(p, addr.addr, first_index);
+
+				Type *t = base_type(type_deref(addr.addr.type));
+				GB_ASSERT(is_type_soa_struct(t));
+
+				// TODO(bill): bounds checking for soa variable
+				// if (addr.soa.index_expr != nullptr && (!cg_is_const(addr.soa.index) || t->Struct.soa_kind != StructSoa_Fixed)) {
+				// 	cgValue len = cg_soa_struct_len(p, addr.addr);
+				// 	cg_emit_bounds_check(p, ast_token(addr.soa.index_expr), addr.soa.index, len);
+				// }
+
+				cgValue item = {};
+
+				if (t->Struct.soa_kind == StructSoa_Fixed) {
+					item = cg_emit_array_ep(p, arr, index);
+				} else {
+					item = cg_emit_ptr_offset(p, cg_emit_load(p, arr), index);
+				}
+				if (sub_sel.index.count > 0) {
+					item = cg_emit_deep_field_gep(p, item, sub_sel);
+				}
+				item.type = alloc_type_pointer(type_deref(item.type, true));
+				return cg_addr(item);
+			} else if (addr.kind == cgAddr_Swizzle) {
+				GB_ASSERT(sel.index.count > 0);
+				// NOTE(bill): just patch the index in place
+				sel.index[0] = addr.swizzle.indices[sel.index[0]];
+			} else if (addr.kind == cgAddr_SwizzleLarge) {
+				GB_ASSERT(sel.index.count > 0);
+				// NOTE(bill): just patch the index in place
+				sel.index[0] = addr.swizzle.indices[sel.index[0]];
+			}
+
+			cgValue a = cg_addr_get_ptr(p, addr);
+			a = cg_emit_deep_field_gep(p, a, sel);
+			return cg_addr(a);
+		}
+	case_end;
+
+	case_ast_node(ce, CallExpr, expr);
+		cgValue res = cg_build_expr(p, expr);
+		switch (res.kind) {
+		case cgValue_Value:
+			return cg_addr(cg_address_from_load_or_generate_local(p, res));
+		case cgValue_Addr:
+			return cg_addr(res);
+		case cgValue_Multi:
+			GB_PANIC("cannot address a multi-valued expression");
+			break;
+		}
+	case_end;
+
+	case_ast_node(cl, CompoundLit, expr);
+		return cg_build_addr_compound_lit(p, expr);
+	case_end;
+
+	}
+
+	TokenPos token_pos = ast_token(expr).pos;
+	GB_PANIC("Unexpected address expression\n"
+	         "\tAst: %.*s @ "
+	         "%s\n",
+	         LIT(ast_strings[expr->kind]),
+	         token_pos_to_string(token_pos));
+
+	return {};
+}

+ 1307 - 0
src/tilde_proc.cpp

@@ -0,0 +1,1307 @@
+gb_internal TB_FunctionPrototype *cg_procedure_type_as_prototype(cgModule *m, Type *type) {
+	GB_ASSERT(is_type_proc(type));
+	mutex_lock(&m->proc_proto_mutex);
+	defer (mutex_unlock(&m->proc_proto_mutex));
+
+	if (type->kind == Type_Named) {
+		type = base_type(type);
+	}
+	TB_FunctionPrototype **found = map_get(&m->proc_proto_map, type);
+	if (found) {
+		return *found;
+	}
+
+	TB_DebugType *dbg = cg_debug_type_for_proc(m, type);
+	TB_FunctionPrototype *proto = tb_prototype_from_dbg(m->mod, dbg);
+
+	map_set(&m->proc_proto_map, type, proto);
+	return proto;
+}
+
+gb_internal cgProcedure *cg_procedure_create(cgModule *m, Entity *entity, bool ignore_body) {
+	GB_ASSERT(entity != nullptr);
+	GB_ASSERT(entity->kind == Entity_Procedure);
+	if (!entity->Procedure.is_foreign) {
+		if ((entity->flags & EntityFlag_ProcBodyChecked) == 0) {
+			GB_PANIC("%.*s :: %s (was parapoly: %d %d)", LIT(entity->token.string), type_to_string(entity->type), is_type_polymorphic(entity->type, true), is_type_polymorphic(entity->type, false));
+		}
+	}
+
+	String link_name = cg_get_entity_name(m, entity);
+
+	cgProcedure *p = nullptr;
+	{
+		StringHashKey key = string_hash_string(link_name);
+		cgValue *found = string_map_get(&m->members, key);
+		if (found) {
+			cg_add_entity(m, entity, *found);
+			rw_mutex_lock(&m->values_mutex);
+			p = string_map_must_get(&m->procedures, key);
+			rw_mutex_unlock(&m->values_mutex);
+			if (!ignore_body && p->func != nullptr) {
+				return nullptr;
+			}
+		}
+	}
+
+	if (p == nullptr) {
+		p = gb_alloc_item(permanent_allocator(), cgProcedure);
+	}
+
+	p->module = m;
+	p->entity = entity;
+	p->name = link_name;
+
+	DeclInfo *decl = entity->decl_info;
+
+	ast_node(pl, ProcLit, decl->proc_lit);
+	Type *pt = base_type(entity->type);
+	GB_ASSERT(pt->kind == Type_Proc);
+
+	p->type           = entity->type;
+	p->type_expr      = decl->type_expr;
+	p->body           = pl->body;
+	p->inlining       = pl->inlining;
+	p->is_foreign     = entity->Procedure.is_foreign;
+	p->is_export      = entity->Procedure.is_export;
+	p->is_entry_point = false;
+	p->split_returns_index = -1;
+
+	gbAllocator a = heap_allocator();
+	p->children.allocator      = a;
+
+	p->defer_stack.allocator   = a;
+	p->scope_stack.allocator   = a;
+	p->context_stack.allocator = a;
+
+	p->control_regions.allocator = a;
+	p->branch_regions.allocator = a;
+
+	map_init(&p->variable_map);
+
+	TB_Linkage linkage = TB_LINKAGE_PRIVATE;
+	if (p->is_export) {
+		linkage = TB_LINKAGE_PUBLIC;
+	} else if (p->is_foreign || ignore_body) {
+		if (ignore_body) {
+			linkage = TB_LINKAGE_PUBLIC;
+		}
+		p->symbol = cast(TB_Symbol *)tb_extern_create(m->mod, link_name.len, cast(char const *)link_name.text, TB_EXTERNAL_SO_LOCAL);
+	}
+	if (p->name == "main") {
+		// TODO(bill): figure out when this should be public or not
+		linkage = TB_LINKAGE_PUBLIC;
+	}
+
+	if (p->symbol == nullptr)  {
+		p->func = tb_function_create(m->mod, link_name.len, cast(char const *)link_name.text, linkage, TB_COMDAT_NONE);
+
+		p->debug_type = cg_debug_type_for_proc(m, p->type);
+		p->proto = tb_prototype_from_dbg(m->mod, p->debug_type);
+
+		p->symbol = cast(TB_Symbol *)p->func;
+	}
+
+	p->value = cg_value(p->symbol, p->type);
+
+	cg_add_symbol(m, entity, p->symbol);
+	cg_add_entity(m, entity, p->value);
+	cg_add_member(m, p->name, p->value);
+	cg_add_procedure_value(m, p);
+
+
+	return p;
+}
+
+gb_internal cgProcedure *cg_procedure_create_dummy(cgModule *m, String const &link_name, Type *type) {
+	auto *prev_found = string_map_get(&m->members, link_name);
+	GB_ASSERT_MSG(prev_found == nullptr, "failed to create dummy procedure for: %.*s", LIT(link_name));
+
+	cgProcedure *p = gb_alloc_item(permanent_allocator(), cgProcedure);
+
+	p->module = m;
+	p->name = link_name;
+
+	p->type           = type;
+	p->type_expr      = nullptr;
+	p->body           = nullptr;
+	p->tags           = 0;
+	p->inlining       = ProcInlining_none;
+	p->is_foreign     = false;
+	p->is_export      = false;
+	p->is_entry_point = false;
+	p->split_returns_index = -1;
+
+	gbAllocator a = heap_allocator();
+	p->children.allocator      = a;
+
+	p->defer_stack.allocator   = a;
+	p->scope_stack.allocator   = a;
+	p->context_stack.allocator = a;
+
+	p->control_regions.allocator = a;
+	p->branch_regions.allocator = a;
+
+	map_init(&p->variable_map);
+
+
+	TB_Linkage linkage = TB_LINKAGE_PRIVATE;
+
+	p->func = tb_function_create(m->mod, link_name.len, cast(char const *)link_name.text, linkage, TB_COMDAT_NONE);
+
+	p->debug_type = cg_debug_type_for_proc(m, p->type);
+	p->proto = tb_prototype_from_dbg(m->mod, p->debug_type);
+
+	p->symbol = cast(TB_Symbol *)p->func;
+
+	cgValue proc_value = cg_value(p->symbol, p->type);
+	cg_add_member(m, p->name, proc_value);
+	cg_add_procedure_value(m, p);
+
+	return p;
+}
+
+gb_internal cgProcedure *cg_procedure_generate_anonymous(cgModule *m, Ast *expr, cgProcedure *parent) {
+	expr = unparen_expr(expr);
+	ast_node(pl, ProcLit, expr);
+
+	mutex_lock(&m->anonymous_proc_lits_mutex);
+	defer (mutex_unlock(&m->anonymous_proc_lits_mutex));
+
+	cgProcedure **found = map_get(&m->anonymous_proc_lits_map, expr);
+	if (found) {
+		return *found;
+	}
+
+	TokenPos pos = ast_token(expr).pos;
+
+	// NOTE(bill): Generate a new name
+	// parent$count
+
+	String prefix_name = str_lit("proc_lit");
+	if (parent) {
+		prefix_name = parent->name;
+	}
+
+	isize name_len = prefix_name.len + 6 + 11;
+	char *name_text = gb_alloc_array(permanent_allocator(), char, name_len);
+
+	static std::atomic<i32> name_id;
+	name_len = gb_snprintf(name_text, name_len, "%.*s$anon-%d", LIT(prefix_name), 1+name_id.fetch_add(1));
+	String name = make_string((u8 *)name_text, name_len-1);
+
+	Type *type = type_of_expr(expr);
+
+	GB_ASSERT(pl->decl->entity == nullptr);
+	Token token = {};
+	token.pos = ast_token(expr).pos;
+	token.kind = Token_Ident;
+	token.string = name;
+	Entity *e = alloc_entity_procedure(nullptr, token, type, pl->tags);
+	e->file = expr->file();
+
+	// NOTE(bill): this is to prevent a race condition since these procedure literals can be created anywhere at any time
+	e->decl_info = pl->decl;
+	pl->decl->entity = e;
+	e->flags |= EntityFlag_ProcBodyChecked;
+
+	cgProcedure *p = cg_procedure_create(m, e);
+
+	map_set(&m->anonymous_proc_lits_map, expr, p);
+
+	if (parent != nullptr) {
+		array_add(&parent->children, p);
+	}
+
+	cg_add_procedure_to_queue(p);
+	return p;
+
+}
+
+gb_internal void cg_procedure_begin(cgProcedure *p) {
+	if (p == nullptr || p->func == nullptr) {
+		return;
+	}
+
+	tb_function_set_prototype(p->func, p->proto, cg_arena());
+
+	if (p->body == nullptr) {
+		return;
+	}
+
+
+	DeclInfo *decl = decl_info_of_entity(p->entity);
+	if (decl != nullptr) {
+		for_array(i, decl->labels) {
+			BlockLabel bl = decl->labels[i];
+			cgBranchRegions bb = {bl.label, nullptr, nullptr};
+			array_add(&p->branch_regions, bb);
+		}
+	}
+
+	GB_ASSERT(p->type->kind == Type_Proc);
+	TypeProc *pt = &p->type->Proc;
+	bool is_odin_like_cc = is_calling_convention_odin(pt->calling_convention);
+	int param_index = 0;
+	int param_count = p->proto->param_count;
+
+	if (pt->results) {
+		Type *result_type = nullptr;
+		if (is_odin_like_cc) {
+			result_type = pt->results->Tuple.variables[pt->results->Tuple.variables.count-1]->type;
+		} else {
+			result_type = pt->results;
+		}
+		TB_DebugType *debug_type = cg_debug_type(p->module, result_type);
+		TB_PassingRule rule = tb_get_passing_rule_from_dbg(p->module->mod, debug_type, true);
+		if (rule == TB_PASSING_INDIRECT) {
+			p->return_by_ptr = true;
+			param_index++;
+		}
+	}
+
+	if (pt->params != nullptr) for (Entity *e : pt->params->Tuple.variables) {
+		if (e->kind != Entity_Variable) {
+			continue;
+		}
+
+		GB_ASSERT_MSG(param_index < param_count, "%d < %d %.*s :: %s", param_index, param_count, LIT(p->name), type_to_string(p->type));
+
+		TB_Node *param_ptr = nullptr;
+
+		TB_CharUnits size  = cast(TB_CharUnits)type_size_of(e->type);
+		TB_CharUnits align = cast(TB_CharUnits)type_align_of(e->type);
+		TB_DebugType *debug_type = cg_debug_type(p->module, e->type);
+		TB_PassingRule rule = tb_get_passing_rule_from_dbg(p->module->mod, debug_type, false);
+		switch (rule) {
+		case TB_PASSING_DIRECT: {
+			TB_Node *param = tb_inst_param(p->func, param_index++);
+			param_ptr = tb_inst_local(p->func, size, align);
+			tb_inst_store(p->func, param->dt, param_ptr, param, align, false);
+		} break;
+		case TB_PASSING_INDIRECT:
+			// TODO(bill): does this need a copy? for non-odin calling convention stuff?
+			param_ptr = tb_inst_param(p->func, param_index++);
+			break;
+		case TB_PASSING_IGNORE:
+			continue;
+		}
+
+		GB_ASSERT(param_ptr->dt.type == TB_PTR);
+
+		cgValue local = cg_value(param_ptr, alloc_type_pointer(e->type));
+
+		if (e != nullptr && e->token.string.len > 0 && e->token.string != "_") {
+			// NOTE(bill): for debugging purposes only
+			String name = e->token.string;
+			TB_DebugType *param_debug_type = debug_type;
+			TB_Node *     param_ptr_to_use = param_ptr;
+			if (rule == TB_PASSING_INDIRECT) {
+				// HACK TODO(bill): this is just to get the debug information
+				TB_CharUnits ptr_size = cast(TB_CharUnits)build_context.ptr_size;
+				TB_Node *dummy_param = tb_inst_local(p->func, ptr_size, ptr_size);
+				tb_inst_store(p->func, TB_TYPE_PTR, dummy_param, param_ptr, ptr_size, false);
+				param_ptr_to_use = dummy_param;
+				param_debug_type = tb_debug_create_ptr(p->module->mod, param_debug_type);
+			}
+			tb_node_append_attrib(
+				param_ptr_to_use,
+				tb_function_attrib_variable(
+					p->func,
+					name.len, cast(char const *)name.text,
+					param_debug_type
+				)
+			);
+		}
+		cgAddr addr = cg_addr(local);
+		if (e) {
+			map_set(&p->variable_map, e, addr);
+		}
+	}
+
+	if (is_odin_like_cc) {
+		p->split_returns_index = param_index;
+	}
+
+	if (pt->calling_convention == ProcCC_Odin) {
+		// NOTE(bill): Push context on to stack from implicit parameter
+
+		String name = str_lit("__.context_ptr");
+
+		Entity *e = alloc_entity_param(nullptr, make_token_ident(name), t_context_ptr, false, false);
+		e->flags |= EntityFlag_NoAlias;
+
+		TB_Node *param_ptr = tb_inst_param(p->func, param_count-1);
+		cgValue local = cg_value(param_ptr, t_context_ptr);
+		cgAddr addr = cg_addr(local);
+		map_set(&p->variable_map, e, addr);
+
+
+		cgContextData *cd = array_add_and_get(&p->context_stack);
+		cd->ctx = addr;
+		cd->scope_index = -1;
+		cd->uses = +1; // make sure it has been used already
+	}
+
+	if (pt->has_named_results) {
+		auto const &results = pt->results->Tuple.variables;
+		for_array(i, results) {
+			Entity *e = results[i];
+			GB_ASSERT(e->kind == Entity_Variable);
+
+			if (e->token.string == "") {
+				continue;
+			}
+			GB_ASSERT(!is_blank_ident(e->token));
+
+			cgAddr res = cg_add_local(p, e->type, e, true);
+
+			if (e->Variable.param_value.kind != ParameterValue_Invalid) {
+				cgValue c = cg_handle_param_value(p, e->type, e->Variable.param_value, e->token.pos);
+				cg_addr_store(p, res, c);
+			}
+		}
+	}
+}
+
+
+gb_internal WORKER_TASK_PROC(cg_procedure_compile_worker_proc) {
+	cgProcedure *p = cast(cgProcedure *)data;
+
+	TB_Passes *opt = tb_pass_enter(p->func, cg_arena());
+	defer (tb_pass_exit(opt));
+
+	// optimization passes
+	if (false) {
+		tb_pass_peephole(opt);
+		tb_pass_mem2reg(opt);
+		tb_pass_peephole(opt);
+	}
+
+	bool emit_asm = false;
+	if (
+	    // string_starts_with(p->name, str_lit("runtime@_windows_default_alloc_or_resize")) ||
+	    false
+	) {
+		emit_asm = true;
+	}
+
+	// emit ir
+	if (
+	    // string_starts_with(p->name, str_lit("bug@main")) ||
+	    // p->name == str_lit("runtime@_windows_default_alloc_or_resize") ||
+	    false
+	) { // IR Printing
+		TB_Arena *arena = cg_arena();
+		TB_Passes *passes = tb_pass_enter(p->func, arena);
+		defer (tb_pass_exit(passes));
+
+		tb_pass_print(passes);
+		fprintf(stdout, "\n");
+	}
+	if (false) { // GraphViz printing
+		tb_function_print(p->func, tb_default_print_callback, stdout);
+	}
+
+	// compile
+	TB_FunctionOutput *output = tb_pass_codegen(opt, emit_asm);
+	if (emit_asm) {
+		tb_output_print_asm(output, stdout);
+		fprintf(stdout, "\n");
+	}
+
+	return 0;
+}
+
+gb_internal void cg_procedure_end(cgProcedure *p) {
+	if (p == nullptr || p->func == nullptr) {
+		return;
+	}
+	if (tb_inst_get_control(p->func)) {
+		if (p->type->Proc.result_count == 0) {
+			tb_inst_ret(p->func, 0, nullptr);
+		} else {
+			tb_inst_unreachable(p->func);
+		}
+	}
+
+	if (p->module->do_threading) {
+		thread_pool_add_task(cg_procedure_compile_worker_proc, p);
+	} else {
+		cg_procedure_compile_worker_proc(p);
+	}
+}
+
+gb_internal void cg_procedure_generate(cgProcedure *p) {
+	if (p->body == nullptr) {
+		return;
+	}
+
+	cg_procedure_begin(p);
+	cg_build_stmt(p, p->body);
+	cg_procedure_end(p);
+}
+
+gb_internal void cg_build_nested_proc(cgProcedure *p, AstProcLit *pd, Entity *e) {
+	GB_ASSERT(pd->body != nullptr);
+	cgModule *m = p->module;
+	auto *min_dep_set = &m->info->minimum_dependency_set;
+
+	if (ptr_set_exists(min_dep_set, e) == false) {
+		// NOTE(bill): Nothing depends upon it so doesn't need to be built
+		return;
+	}
+
+	// NOTE(bill): Generate a new name
+	// parent.name-guid
+	String original_name = e->token.string;
+	String pd_name = original_name;
+	if (e->Procedure.link_name.len > 0) {
+		pd_name = e->Procedure.link_name;
+	}
+
+
+	isize name_len = p->name.len + 1 + pd_name.len + 1 + 10 + 1;
+	char *name_text = gb_alloc_array(permanent_allocator(), char, name_len);
+
+	i32 guid = cast(i32)p->children.count;
+	name_len = gb_snprintf(name_text, name_len, "%.*s" ABI_PKG_NAME_SEPARATOR "%.*s-%d", LIT(p->name), LIT(pd_name), guid);
+	String name = make_string(cast(u8 *)name_text, name_len-1);
+
+	e->Procedure.link_name = name;
+
+	cgProcedure *nested_proc = cg_procedure_create(p->module, e);
+	e->cg_procedure = nested_proc;
+
+	cgValue value = nested_proc->value;
+
+	cg_add_entity(m, e, value);
+	array_add(&p->children, nested_proc);
+	cg_add_procedure_to_queue(nested_proc);
+}
+
+
+
+
+
+gb_internal cgValue cg_find_procedure_value_from_entity(cgModule *m, Entity *e) {
+	GB_ASSERT(is_type_proc(e->type));
+	e = strip_entity_wrapping(e);
+	GB_ASSERT(e != nullptr);
+	GB_ASSERT(e->kind == Entity_Procedure);
+
+	cgValue *found = nullptr;
+	rw_mutex_shared_lock(&m->values_mutex);
+	found = map_get(&m->values, e);
+	rw_mutex_shared_unlock(&m->values_mutex);
+	if (found) {
+		GB_ASSERT(found->node != nullptr);
+		return *found;
+	}
+
+	GB_PANIC("Error in: %s, missing procedure %.*s\n", token_pos_to_string(e->token.pos), LIT(e->token.string));
+	return {};
+}
+
+
+
+gb_internal cgValue cg_build_call_expr_internal(cgProcedure *p, Ast *expr);
+gb_internal cgValue cg_build_call_expr(cgProcedure *p, Ast *expr) {
+	expr = unparen_expr(expr);
+	ast_node(ce, CallExpr, expr);
+
+	cgValue res = cg_build_call_expr_internal(p, expr);
+
+	if (ce->optional_ok_one) { // TODO(bill): Minor hack for #optional_ok procedures
+		GB_ASSERT(res.kind == cgValue_Multi);
+		GB_ASSERT(res.multi->values.count == 2);
+		return res.multi->values[0];
+	}
+	return res;
+}
+
+gb_internal cgValue cg_emit_call(cgProcedure * p, cgValue value, Slice<cgValue> const &args) {
+	if (value.kind == cgValue_Symbol) {
+		value = cg_value(tb_inst_get_symbol_address(p->func, value.symbol), value.type);
+	}
+	GB_ASSERT(value.kind == cgValue_Value);
+	TEMPORARY_ALLOCATOR_GUARD();
+
+	TB_Module *m = p->module->mod;
+
+
+	Type *type = base_type(value.type);
+	GB_ASSERT(type->kind == Type_Proc);
+	TypeProc *pt = &type->Proc;
+	gb_unused(pt);
+
+	TB_FunctionPrototype *proto = cg_procedure_type_as_prototype(p->module, type);
+	TB_Node *target = value.node;
+	auto params = slice_make<TB_Node *>(temporary_allocator(), proto->param_count);
+
+
+	GB_ASSERT(build_context.metrics.os == TargetOs_windows);
+	// TODO(bill): Support more than Win64 ABI
+
+	bool is_odin_like_cc = is_calling_convention_odin(pt->calling_convention);
+
+	bool return_is_indirect = false;
+
+	Slice<Entity *> result_entities = {};
+	Slice<Entity *> param_entities  = {};
+	if (pt->results) {
+		result_entities = pt->results->Tuple.variables;
+	}
+	if (pt->params) {
+		param_entities = pt->params->Tuple.variables;
+	}
+
+	isize param_index = 0;
+	if (pt->result_count != 0) {
+		Type *return_type = nullptr;
+		if (is_odin_like_cc) {
+			return_type = result_entities[result_entities.count-1]->type;
+		} else {
+			return_type = pt->results;
+		}
+		TB_DebugType *dbg = cg_debug_type(p->module, return_type);
+		TB_PassingRule rule = tb_get_passing_rule_from_dbg(m, dbg, true);
+		if (rule == TB_PASSING_INDIRECT) {
+			return_is_indirect = true;
+			TB_CharUnits size = cast(TB_CharUnits)type_size_of(return_type);
+			TB_CharUnits align = cast(TB_CharUnits)gb_max(type_align_of(return_type), 16);
+			TB_Node *local = tb_inst_local(p->func, size, align);
+			tb_inst_memzero(p->func, local, tb_inst_uint(p->func, TB_TYPE_INT, size), align, false);
+			params[param_index++] = local;
+		}
+	}
+	isize param_entity_index = 0;
+	for_array(i, args) {
+		Entity *param_entity = nullptr;
+		do {
+			param_entity = param_entities[param_entity_index++];
+		} while (param_entity->kind != Entity_Variable);
+		Type *param_type = param_entity->type;
+		cgValue arg = args[i];
+		arg = cg_emit_conv(p, arg, param_type);
+		arg = cg_flatten_value(p, arg);
+
+		TB_Node *param = nullptr;
+
+		TB_DebugType *dbg = cg_debug_type(p->module, param_type);
+		TB_PassingRule rule = tb_get_passing_rule_from_dbg(m, dbg, false);
+		switch (rule) {
+		case TB_PASSING_DIRECT:
+			GB_ASSERT(arg.kind == cgValue_Value);
+			param = arg.node;
+			break;
+		case TB_PASSING_INDIRECT:
+			{
+				cgValue arg_ptr = {};
+				// indirect
+				if (is_odin_like_cc) {
+					arg_ptr = cg_address_from_load_or_generate_local(p, arg);
+				} else {
+					arg_ptr = cg_copy_value_to_ptr(p, arg, param_type, 16);
+				}
+				GB_ASSERT(arg_ptr.kind == cgValue_Value);
+				param = arg_ptr.node;
+			}
+			break;
+		case TB_PASSING_IGNORE:
+			continue;
+		}
+
+		params[param_index++] = param;
+	}
+
+	// Split returns
+	isize split_offset = -1;
+	if (is_odin_like_cc) {
+		split_offset = param_index;
+		for (isize i = 0; i < pt->result_count-1; i++) {
+			Type *result = result_entities[i]->type;
+			TB_CharUnits size = cast(TB_CharUnits)type_size_of(result);
+			TB_CharUnits align = cast(TB_CharUnits)gb_max(type_align_of(result), 16);
+			TB_Node *local = tb_inst_local(p->func, size, align);
+			// TODO(bill): Should this need to be zeroed any way?
+			tb_inst_memzero(p->func, local, tb_inst_uint(p->func, TB_TYPE_INT, size), align, false);
+			params[param_index++] = local;
+		}
+	}
+
+	if (pt->calling_convention == ProcCC_Odin) {
+		cgValue ctx_ptr = cg_find_or_generate_context_ptr(p).addr;
+		GB_ASSERT(ctx_ptr.kind == cgValue_Value);
+		params[param_index++] = ctx_ptr.node;
+	}
+	GB_ASSERT_MSG(param_index == params.count, "%td vs %td\n %s %u %u",
+	              param_index, params.count,
+	              type_to_string(type),
+	              proto->return_count,
+	              proto->param_count);
+
+	for (TB_Node *param : params) {
+		GB_ASSERT(param != nullptr);
+	}
+
+	GB_ASSERT(target != nullptr);
+	TB_MultiOutput multi_output = tb_inst_call(p->func, proto, target, params.count, params.data);
+	gb_unused(multi_output);
+
+	switch (pt->result_count) {
+	case 0:
+		return {};
+	case 1:
+		if (return_is_indirect) {
+			return cg_lvalue_addr(params[0], pt->results->Tuple.variables[0]->type);
+		} else {
+			GB_ASSERT(multi_output.count == 1);
+			TB_Node *node = multi_output.single;
+			return cg_value(node, pt->results->Tuple.variables[0]->type);
+		}
+	}
+
+	cgValueMulti *multi = gb_alloc_item(permanent_allocator(), cgValueMulti);
+	multi->values = slice_make<cgValue>(permanent_allocator(), pt->result_count);
+
+	if (is_odin_like_cc) {
+		GB_ASSERT(split_offset >= 0);
+		for (isize i = 0; i < pt->result_count-1; i++) {
+			multi->values[i] = cg_lvalue_addr(params[split_offset+i], result_entities[i]->type);
+		}
+
+		Type *end_type = result_entities[pt->result_count-1]->type;
+		if (return_is_indirect) {
+			multi->values[pt->result_count-1] = cg_lvalue_addr(params[0], end_type);
+		} else {
+			GB_ASSERT(multi_output.count == 1);
+			TB_DataType dt = cg_data_type(end_type);
+			TB_Node *res = multi_output.single;
+			if (res->dt.raw != dt.raw) {
+				// struct-like returns passed in registers
+				TB_CharUnits size  = cast(TB_CharUnits)type_size_of(end_type);
+				TB_CharUnits align = cast(TB_CharUnits)type_align_of(end_type);
+				TB_Node *addr = tb_inst_local(p->func, size, align);
+				tb_inst_store(p->func, res->dt, addr, res, align, false);
+				multi->values[pt->result_count-1] = cg_lvalue_addr(addr, end_type);
+			} else {
+				multi->values[pt->result_count-1] = cg_value(res, end_type);
+			}
+		}
+	} else {
+		TB_Node *the_tuple = {};
+		if (return_is_indirect) {
+			the_tuple = params[0];
+		} else {
+			GB_ASSERT(multi_output.count == 1);
+			TB_Node *res = multi_output.single;
+
+			// struct-like returns passed in registers
+			TB_CharUnits size  = cast(TB_CharUnits)type_size_of(pt->results);
+			TB_CharUnits align = cast(TB_CharUnits)type_align_of(pt->results);
+			the_tuple = tb_inst_local(p->func, size, align);
+			tb_inst_store(p->func, res->dt, the_tuple, res, align, false);
+		}
+		for (isize i = 0; i < pt->result_count; i++) {
+			i64 offset = type_offset_of(pt->results, i, nullptr);
+			TB_Node *ptr = tb_inst_member_access(p->func, the_tuple, offset);
+			multi->values[i] = cg_lvalue_addr(ptr, result_entities[i]->type);
+		}
+	}
+
+	return cg_value_multi(multi, pt->results);
+}
+
+gb_internal cgValue cg_emit_runtime_call(cgProcedure *p, char const *name, Slice<cgValue> const &args) {
+	AstPackage *pkg = p->module->info->runtime_package;
+	Entity *e = scope_lookup_current(pkg->scope, make_string_c(name));
+	cgValue value = cg_find_procedure_value_from_entity(p->module, e);
+	return cg_emit_call(p, value, args);
+}
+
+gb_internal cgValue cg_handle_param_value(cgProcedure *p, Type *parameter_type, ParameterValue const &param_value, TokenPos const &pos) {
+	switch (param_value.kind) {
+	case ParameterValue_Constant:
+		if (is_type_constant_type(parameter_type)) {
+			auto res = cg_const_value(p, parameter_type, param_value.value);
+			return res;
+		} else {
+			ExactValue ev = param_value.value;
+			cgValue arg = {};
+			Type *type = type_of_expr(param_value.original_ast_expr);
+			if (type != nullptr) {
+				arg = cg_const_value(p, type, ev);
+			} else {
+				arg = cg_const_value(p, parameter_type, param_value.value);
+			}
+			return cg_emit_conv(p, arg, parameter_type);
+		}
+
+	case ParameterValue_Nil:
+		return cg_const_nil(p, parameter_type);
+	case ParameterValue_Location:
+		{
+			String proc_name = {};
+			if (p->entity != nullptr) {
+				proc_name = p->entity->token.string;
+			}
+			return cg_emit_source_code_location_as_global(p, proc_name, pos);
+		}
+	case ParameterValue_Value:
+		return cg_build_expr(p, param_value.ast_value);
+	}
+	return cg_const_nil(p, parameter_type);
+}
+
+gb_internal cgValue cg_build_call_expr_internal(cgProcedure *p, Ast *expr) {
+	ast_node(ce, CallExpr, expr);
+
+	TypeAndValue tv = type_and_value_of_expr(expr);
+
+	TypeAndValue proc_tv = type_and_value_of_expr(ce->proc);
+	AddressingMode proc_mode = proc_tv.mode;
+	if (proc_mode == Addressing_Type) {
+		GB_ASSERT(ce->args.count == 1);
+		cgValue x = cg_build_expr(p, ce->args[0]);
+		return cg_emit_conv(p, x, tv.type);
+	}
+
+	Ast *proc_expr = unparen_expr(ce->proc);
+	if (proc_mode == Addressing_Builtin) {
+		Entity *e = entity_of_node(proc_expr);
+		BuiltinProcId id = BuiltinProc_Invalid;
+		if (e != nullptr) {
+			id = cast(BuiltinProcId)e->Builtin.id;
+		} else {
+			id = BuiltinProc_DIRECTIVE;
+		}
+		if (id == BuiltinProc___entry_point) {
+			if (p->module->info->entry_point) {
+				cgValue entry_point = cg_find_procedure_value_from_entity(p->module, p->module->info->entry_point);
+				GB_ASSERT(entry_point.node != nullptr);
+				cg_emit_call(p, entry_point, {});
+			}
+			return {};
+		}
+
+		return cg_build_builtin(p, id, expr);
+	}
+
+	// NOTE(bill): Regular call
+	cgValue value = {};
+
+	Entity *proc_entity = entity_of_node(proc_expr);
+	if (proc_entity != nullptr) {
+		if (proc_entity->flags & EntityFlag_Disabled) {
+			GB_ASSERT(tv.type == nullptr);
+			return {};
+		}
+	}
+
+	if (proc_expr->tav.mode == Addressing_Constant) {
+		ExactValue v = proc_expr->tav.value;
+		switch (v.kind) {
+		case ExactValue_Integer:
+			{
+				u64 u = big_int_to_u64(&v.value_integer);
+				cgValue x = cg_value(tb_inst_uint(p->func, TB_TYPE_PTR, u), t_rawptr);
+				value = cg_emit_conv(p, x, proc_expr->tav.type);
+				break;
+			}
+		case ExactValue_Pointer:
+			{
+				u64 u = cast(u64)v.value_pointer;
+				cgValue x = cg_value(tb_inst_uint(p->func, TB_TYPE_PTR, u), t_rawptr);
+				value = cg_emit_conv(p, x, proc_expr->tav.type);
+				break;
+			}
+		}
+	}
+
+	if (value.node == nullptr) {
+		value = cg_build_expr(p, proc_expr);
+	}
+	if (value.kind == cgValue_Addr) {
+		value = cg_emit_load(p, value);
+	}
+	GB_ASSERT(value.kind == cgValue_Value);
+	GB_ASSERT(value.node != nullptr);
+	GB_ASSERT(is_type_proc(value.type));
+
+	TEMPORARY_ALLOCATOR_GUARD();
+
+	Type *proc_type_ = base_type(value.type);
+	GB_ASSERT(proc_type_->kind == Type_Proc);
+	TypeProc *pt = &proc_type_->Proc;
+
+	GB_ASSERT(ce->split_args != nullptr);
+
+	isize internal_param_count = 0;
+	if (pt->params) for (Entity *e : pt->params->Tuple.variables) {
+		if (e->kind == Entity_Variable) {
+			internal_param_count += 1;
+		}
+	}
+	GB_ASSERT(internal_param_count <= pt->param_count);
+
+	auto args = array_make<cgValue>(temporary_allocator(), 0, internal_param_count);
+
+	bool vari_expand = (ce->ellipsis.pos.line != 0);
+	bool is_c_vararg = pt->c_vararg;
+
+	for_array(i, ce->split_args->positional) {
+		Entity *e = pt->params->Tuple.variables[i];
+		if (e->kind == Entity_TypeName) {
+			continue;
+		} else if (e->kind == Entity_Constant) {
+			continue;
+		}
+
+		GB_ASSERT(e->kind == Entity_Variable);
+
+		if (pt->variadic && pt->variadic_index == i) {
+			cgValue variadic_args = cg_const_nil(p, e->type);
+			auto variadic = slice(ce->split_args->positional, pt->variadic_index, ce->split_args->positional.count);
+			if (variadic.count != 0) {
+				// variadic call argument generation
+				Type *slice_type = e->type;
+				GB_ASSERT(slice_type->kind == Type_Slice);
+
+				if (is_c_vararg) {
+					GB_ASSERT(!vari_expand);
+
+					Type *elem_type = slice_type->Slice.elem;
+
+					for (Ast *var_arg : variadic) {
+						cgValue arg = cg_build_expr(p, var_arg);
+						if (is_type_any(elem_type)) {
+							array_add(&args, cg_emit_conv(p, arg, default_type(arg.type)));
+						} else {
+							array_add(&args, cg_emit_conv(p, arg, elem_type));
+						}
+					}
+					break;
+				} else if (vari_expand) {
+					GB_ASSERT(variadic.count == 1);
+					variadic_args = cg_build_expr(p, variadic[0]);
+					variadic_args = cg_emit_conv(p, variadic_args, slice_type);
+				} else {
+					Type *elem_type = slice_type->Slice.elem;
+
+					auto var_args = array_make<cgValue>(temporary_allocator(), 0, variadic.count);
+					for (Ast *var_arg : variadic) {
+						cgValue v = cg_build_expr(p, var_arg);
+						cg_append_tuple_values(p, &var_args, v);
+					}
+					isize slice_len = var_args.count;
+					if (slice_len > 0) {
+						cgAddr slice = cg_add_local(p, slice_type, nullptr, true);
+						cgAddr base_array = cg_add_local(p, alloc_type_array(elem_type, slice_len), nullptr, true);
+
+						for (isize i = 0; i < var_args.count; i++) {
+							cgValue addr = cg_emit_array_epi(p, base_array.addr, cast(i32)i);
+							cgValue var_arg = var_args[i];
+							var_arg = cg_emit_conv(p, var_arg, elem_type);
+							cg_emit_store(p, addr, var_arg);
+						}
+
+						cgValue base_elem = cg_emit_array_epi(p, base_array.addr, 0);
+						cgValue len = cg_const_int(p, t_int, slice_len);
+						cg_fill_slice(p, slice, base_elem, len);
+
+						variadic_args = cg_addr_load(p, slice);
+					}
+				}
+			}
+			array_add(&args, variadic_args);
+
+			break;
+		} else {
+			cgValue value = cg_build_expr(p, ce->split_args->positional[i]);
+			cg_append_tuple_values(p, &args, value);
+		}
+	}
+
+	if (!is_c_vararg) {
+		array_resize(&args, internal_param_count);
+	}
+
+	for (Ast *arg : ce->split_args->named) {
+		ast_node(fv, FieldValue, arg);
+		GB_ASSERT(fv->field->kind == Ast_Ident);
+		String name = fv->field->Ident.token.string;
+		gb_unused(name);
+		isize param_index = lookup_procedure_parameter(pt, name);
+		GB_ASSERT(param_index >= 0);
+
+		cgValue value = cg_build_expr(p, fv->value);
+		GB_ASSERT(!is_type_tuple(value.type));
+		args[param_index] = value;
+	}
+
+	TokenPos pos = ast_token(ce->proc).pos;
+
+
+	if (pt->params != nullptr)  {
+		isize min_count = internal_param_count;
+		if (is_c_vararg) {
+			min_count -= 1;
+		}
+		GB_ASSERT_MSG(args.count >= min_count, "in %.*s", LIT(p->name));
+		isize arg_index = 0;
+		for_array(param_index, pt->params->Tuple.variables) {
+			Entity *e = pt->params->Tuple.variables[param_index];
+			if (e->kind == Entity_TypeName) {
+				continue;
+			} else if (e->kind == Entity_Constant) {
+				continue;
+			}
+			GB_ASSERT(e->kind == Entity_Variable);
+
+			if (pt->variadic && param_index == pt->variadic_index) {
+				if (!is_c_vararg && args[arg_index].node == nullptr) {
+					args[arg_index++] = cg_const_nil(p, e->type);
+				}
+				continue;
+			}
+
+			cgValue arg = args[arg_index];
+			if (arg.node == nullptr) {
+				GB_ASSERT(e->kind == Entity_Variable);
+				args[arg_index++] = cg_handle_param_value(p, e->type, e->Variable.param_value, pos);
+			} else {
+				args[arg_index++] = cg_emit_conv(p, arg, e->type);
+			}
+		}
+	}
+
+	isize final_count = is_c_vararg ? args.count : internal_param_count;
+	auto call_args = slice(args, 0, final_count);
+
+	return cg_emit_call(p, value, call_args);
+}
+
+
+
+gb_internal cgValue cg_hasher_proc_value_for_type(cgProcedure *p, Type *type) {
+	cgProcedure *found = cg_hasher_proc_for_type(p->module, type);
+	return cg_value(tb_inst_get_symbol_address(p->func, found->symbol), found->type);
+}
+
+gb_internal cgValue cg_equal_proc_value_for_type(cgProcedure *p, Type *type) {
+	cgProcedure *found = cg_equal_proc_for_type(p->module, type);
+	return cg_value(tb_inst_get_symbol_address(p->func, found->symbol), found->type);
+}
+
+
+
+gb_internal cgProcedure *cg_equal_proc_for_type(cgModule *m, Type *type) {
+	type = base_type(type);
+	GB_ASSERT(is_type_comparable(type));
+
+	mutex_lock(&m->generated_procs_mutex);
+	defer (mutex_unlock(&m->generated_procs_mutex));
+
+	cgProcedure **found = map_get(&m->equal_procs, type);
+	if (found) {
+		return *found;
+	}
+
+	static std::atomic<u32> proc_index;
+
+	char buf[32] = {};
+	isize n = gb_snprintf(buf, 32, "__$equal%u", 1+proc_index.fetch_add(1));
+	char *str = gb_alloc_str_len(permanent_allocator(), buf, n-1);
+	String proc_name = make_string_c(str);
+
+
+	cgProcedure *p = cg_procedure_create_dummy(m, proc_name, t_equal_proc);
+	map_set(&m->equal_procs, type, p);
+
+	cg_procedure_begin(p);
+
+	TB_Node *x = tb_inst_param(p->func, 0);
+	TB_Node *y = tb_inst_param(p->func, 1);
+	GB_ASSERT(x->dt.type == TB_PTR);
+	GB_ASSERT(y->dt.type == TB_PTR);
+
+	TB_DataType ret_dt = TB_PROTOTYPE_RETURNS(p->proto)->dt;
+
+	TB_Node *node_true  = tb_inst_uint(p->func, ret_dt, true);
+	TB_Node *node_false = tb_inst_uint(p->func, ret_dt, false);
+
+	TB_Node *same_ptr_region = cg_control_region(p, "same_ptr");
+	TB_Node *diff_ptr_region = cg_control_region(p, "diff_ptr");
+
+	TB_Node *is_same_ptr = tb_inst_cmp_eq(p->func, x, y);
+	tb_inst_if(p->func, is_same_ptr, same_ptr_region, diff_ptr_region);
+
+	tb_inst_set_control(p->func, same_ptr_region);
+	tb_inst_ret(p->func, 1, &node_true);
+
+	tb_inst_set_control(p->func, diff_ptr_region);
+
+	Type *pt = alloc_type_pointer(type);
+	cgValue lhs = cg_value(x, pt);
+	cgValue rhs = cg_value(y, pt);
+
+	if (type->kind == Type_Struct) {
+		type_set_offsets(type);
+
+		TB_Node *false_region  = cg_control_region(p, "bfalse");
+		cgValue res = cg_const_bool(p, t_bool, true);
+
+		for_array(i, type->Struct.fields) {
+			TB_Node *next_region = cg_control_region(p, "btrue");
+
+			cgValue plhs  = cg_emit_struct_ep(p, lhs, i);
+			cgValue prhs  = cg_emit_struct_ep(p, rhs, i);
+			cgValue left  = cg_emit_load(p, plhs);
+			cgValue right = cg_emit_load(p, prhs);
+			cgValue ok    = cg_emit_comp(p, Token_CmpEq, left, right);
+
+			cg_emit_if(p, ok, next_region, false_region);
+
+			cg_emit_goto(p, next_region);
+			tb_inst_set_control(p->func, next_region);
+		}
+
+		tb_inst_ret(p->func, 1, &node_true);
+		tb_inst_set_control(p->func, false_region);
+		tb_inst_ret(p->func, 1, &node_false);
+
+	} else if (type->kind == Type_Union) {
+		if (type_size_of(type) == 0) {
+			tb_inst_ret(p->func, 1, &node_true);
+		} else if (is_type_union_maybe_pointer(type)) {
+			Type *v = type->Union.variants[0];
+			Type *pv = alloc_type_pointer(v);
+
+			cgValue left  = cg_emit_load(p, cg_emit_conv(p, lhs, pv));
+			cgValue right = cg_emit_load(p, cg_emit_conv(p, rhs, pv));
+			cgValue ok = cg_emit_comp(p, Token_CmpEq, left, right);
+			cg_build_return_stmt_internal_single(p, ok);
+		} else {
+			TB_Node *false_region  = cg_control_region(p, "bfalse");
+			TB_Node *switch_region = cg_control_region(p, "bswitch");
+
+			cgValue lhs_tag = cg_emit_load(p, cg_emit_union_tag_ptr(p, lhs));
+			cgValue rhs_tag = cg_emit_load(p, cg_emit_union_tag_ptr(p, rhs));
+
+			cgValue tag_eq = cg_emit_comp(p, Token_CmpEq, lhs_tag, rhs_tag);
+			cg_emit_if(p, tag_eq, switch_region, false_region);
+
+			size_t entry_count = type->Union.variants.count;
+			TB_SwitchEntry *keys = gb_alloc_array(temporary_allocator(), TB_SwitchEntry, entry_count);
+			for (size_t i = 0; i < entry_count; i++) {
+				TB_Node *region = cg_control_region(p, "bcase");
+				Type *variant = type->Union.variants[i];
+				keys[i].key = union_variant_index(type, variant);
+				keys[i].value = region;
+
+				tb_inst_set_control(p->func, region);
+				Type *vp = alloc_type_pointer(variant);
+				cgValue left  = cg_emit_load(p, cg_emit_conv(p, lhs, vp));
+				cgValue right = cg_emit_load(p, cg_emit_conv(p, rhs, vp));
+				cgValue ok = cg_emit_comp(p, Token_CmpEq, left, right);
+				cg_build_return_stmt_internal_single(p, ok);
+			}
+
+
+			tb_inst_set_control(p->func, switch_region);
+			TB_DataType tag_dt = cg_data_type(lhs_tag.type);
+			GB_ASSERT(lhs_tag.kind == cgValue_Value);
+			tb_inst_branch(p->func, tag_dt, lhs_tag.node, false_region, entry_count, keys);
+
+			tb_inst_set_control(p->func, false_region);
+			tb_inst_ret(p->func, 1, &node_false);
+		}
+	} else {
+		cgValue left  = cg_lvalue_addr(x, type);
+		cgValue right = cg_lvalue_addr(y, type);
+		cgValue ok = cg_emit_comp(p, Token_CmpEq, left, right);
+		cg_build_return_stmt_internal_single(p, ok);
+	}
+
+	cg_procedure_end(p);
+
+	return p;
+}
+
+
+gb_internal cgValue cg_simple_compare_hash(cgProcedure *p, Type *type, cgValue data, cgValue seed) {
+	TEMPORARY_ALLOCATOR_GUARD();
+
+	GB_ASSERT_MSG(is_type_simple_compare(type), "%s", type_to_string(type));
+
+	auto args = slice_make<cgValue>(temporary_allocator(), 3);
+	args[0] = data;
+	args[1] = seed;
+	args[2] = cg_const_int(p, t_int, type_size_of(type));
+	return cg_emit_runtime_call(p, "default_hasher", args);
+}
+
+
+
+
+
+gb_internal cgProcedure *cg_hasher_proc_for_type(cgModule *m, Type *type) {
+	type = base_type(type);
+	GB_ASSERT(is_type_valid_for_keys(type));
+
+	mutex_lock(&m->generated_procs_mutex);
+	defer (mutex_unlock(&m->generated_procs_mutex));
+
+	cgProcedure **found = map_get(&m->hasher_procs, type);
+	if (found) {
+		return *found;
+	}
+
+	static std::atomic<u32> proc_index;
+
+	char buf[32] = {};
+	isize n = gb_snprintf(buf, 32, "__$hasher%u", 1+proc_index.fetch_add(1));
+	char *str = gb_alloc_str_len(permanent_allocator(), buf, n-1);
+	String proc_name = make_string_c(str);
+
+
+	cgProcedure *p = cg_procedure_create_dummy(m, proc_name, t_hasher_proc);
+	map_set(&m->hasher_procs, type, p);
+
+	cg_procedure_begin(p);
+	defer (cg_procedure_end(p));
+
+	TB_Node *x = tb_inst_param(p->func, 0); // data
+	TB_Node *y = tb_inst_param(p->func, 1); // seed
+
+	cgValue data = cg_value(x, t_rawptr);
+	cgValue seed = cg_value(y, t_uintptr);
+
+	if (is_type_simple_compare(type)) {
+		cgValue res = cg_simple_compare_hash(p, type, data, seed);
+		cg_build_return_stmt_internal_single(p, res);
+		return p;
+	}
+
+	TEMPORARY_ALLOCATOR_GUARD();
+
+	auto args = slice_make<cgValue>(temporary_allocator(), 2);
+
+	if (type->kind == Type_Struct) {
+		type_set_offsets(type);
+		for_array(i, type->Struct.fields) {
+			i64 offset = type->Struct.offsets[i];
+			Entity *field = type->Struct.fields[i];
+			cgValue field_hasher = cg_hasher_proc_value_for_type(p, field->type);
+
+			TB_Node *ptr = tb_inst_member_access(p->func, data.node, offset);
+
+			args[0] = cg_value(ptr, alloc_type_pointer(field->type));
+			args[1] = seed;
+			seed = cg_emit_call(p, field_hasher, args);
+		}
+
+		cg_build_return_stmt_internal_single(p, seed);
+	} else if (type->kind == Type_Union) {
+		if (type_size_of(type) == 0) {
+			cg_build_return_stmt_internal_single(p, seed);
+		} else if (is_type_union_maybe_pointer(type)) {
+			Type *v = type->Union.variants[0];
+			cgValue variant_hasher = cg_hasher_proc_value_for_type(p, v);
+
+			args[0] = data;
+			args[1] = seed;
+			cgValue res = cg_emit_call(p, variant_hasher, args);
+			cg_build_return_stmt_internal_single(p, seed);
+		} else {
+			TB_Node *end_region = cg_control_region(p, "bend");
+			TB_Node *switch_region = cg_control_region(p, "bswitch");
+
+			cg_emit_goto(p, switch_region);
+
+			size_t entry_count = type->Union.variants.count;
+			TB_SwitchEntry *keys = gb_alloc_array(temporary_allocator(), TB_SwitchEntry, entry_count);
+			for (size_t i = 0; i < entry_count; i++) {
+				TB_Node *region = cg_control_region(p, "bcase");
+				Type *variant = type->Union.variants[i];
+				keys[i].key = union_variant_index(type, variant);
+				keys[i].value = region;
+
+				tb_inst_set_control(p->func, region);
+
+				cgValue variant_hasher = cg_hasher_proc_value_for_type(p, variant);
+
+				args[0] = data;
+				args[1] = seed;
+				cgValue res = cg_emit_call(p, variant_hasher, args);
+				cg_build_return_stmt_internal_single(p, res);
+			}
+
+			tb_inst_set_control(p->func, switch_region);
+
+			cgValue tag_ptr = cg_emit_union_tag_ptr(p, data);
+			cgValue tag = cg_emit_load(p, tag_ptr);
+
+			TB_DataType tag_dt = cg_data_type(tag.type);
+			GB_ASSERT(tag.kind == cgValue_Value);
+			tb_inst_branch(p->func, tag_dt, tag.node, end_region, entry_count, keys);
+
+			tb_inst_set_control(p->func, end_region);
+			cg_build_return_stmt_internal_single(p, seed);
+		}
+	} else if (type->kind == Type_Array) {
+		cgAddr pres = cg_add_local(p, t_uintptr, nullptr, false);
+		cg_addr_store(p, pres, seed);
+
+		cgValue elem_hasher = cg_hasher_proc_value_for_type(p, type->Array.elem);
+
+		auto loop_data = cg_loop_start(p, type->Array.count, t_int);
+
+		i64 stride = type_size_of(type->Array.elem);
+		TB_Node *ptr = tb_inst_array_access(p->func, data.node, loop_data.index.node, stride);
+		args[0] = cg_value(ptr, alloc_type_pointer(type->Array.elem));
+		args[1] = cg_addr_load(p, pres);
+
+		cgValue new_seed = cg_emit_call(p, elem_hasher, args);
+		cg_addr_store(p, pres, new_seed);
+
+		cg_loop_end(p, loop_data);
+
+		cgValue res = cg_addr_load(p, pres);
+		cg_build_return_stmt_internal_single(p, res);
+	} else if (type->kind == Type_EnumeratedArray) {
+		cgAddr pres = cg_add_local(p, t_uintptr, nullptr, false);
+		cg_addr_store(p, pres, seed);
+
+		cgValue elem_hasher = cg_hasher_proc_value_for_type(p, type->EnumeratedArray.elem);
+
+		auto loop_data = cg_loop_start(p, type->EnumeratedArray.count, t_int);
+
+		i64 stride = type_size_of(type->EnumeratedArray.elem);
+		TB_Node *ptr = tb_inst_array_access(p->func, data.node, loop_data.index.node, stride);
+		args[0] = cg_value(ptr, alloc_type_pointer(type->EnumeratedArray.elem));
+		args[1] = cg_addr_load(p, pres);
+
+		cgValue new_seed = cg_emit_call(p, elem_hasher, args);
+		cg_addr_store(p, pres, new_seed);
+
+		cg_loop_end(p, loop_data);
+
+		cgValue res = cg_addr_load(p, pres);
+		cg_build_return_stmt_internal_single(p, res);
+	} else if (is_type_cstring(type)) {
+		args[0] = data;
+		args[1] = seed;
+		cgValue res = cg_emit_runtime_call(p, "default_hasher_cstring", args);
+		cg_build_return_stmt_internal_single(p, seed);
+	} else if (is_type_string(type)) {
+		args[0] = data;
+		args[1] = seed;
+		cgValue res = cg_emit_runtime_call(p, "default_hasher_string", args);
+		cg_build_return_stmt_internal_single(p, seed);
+	} else {
+		GB_PANIC("Unhandled type for hasher: %s", type_to_string(type));
+	}
+	return p;
+}

+ 2614 - 0
src/tilde_stmt.cpp

@@ -0,0 +1,2614 @@
+gb_internal bool cg_emit_goto(cgProcedure *p, TB_Node *control_region) {
+	if (tb_inst_get_control(p->func)) {
+		tb_inst_goto(p->func, control_region);
+		return true;
+	}
+	return false;
+}
+
+gb_internal TB_Node *cg_control_region(cgProcedure *p, char const *name) {
+	TEMPORARY_ALLOCATOR_GUARD();
+
+	isize n = gb_strlen(name);
+
+	char *new_name = gb_alloc_array(temporary_allocator(), char, n+12);
+	n = -1 + gb_snprintf(new_name, n+11, "%.*s_%u", cast(int)n, name, p->control_regions.count);
+
+	TB_Node *region = tb_inst_region(p->func);
+	tb_inst_set_region_name(region, n, new_name);
+
+	GB_ASSERT(p->scope_index >= 0);
+	array_add(&p->control_regions, cgControlRegion{region, p->scope_index});
+
+	return region;
+}
+
+gb_internal cgValue cg_emit_load(cgProcedure *p, cgValue const &ptr, bool is_volatile) {
+	GB_ASSERT(is_type_pointer(ptr.type));
+	Type *type = type_deref(ptr.type);
+	TB_DataType dt = cg_data_type(type);
+
+	if (TB_IS_VOID_TYPE(dt)) {
+		switch (ptr.kind) {
+		case cgValue_Value:
+			return cg_lvalue_addr(ptr.node, type);
+		case cgValue_Addr:
+			GB_PANIC("NOT POSSIBLE - Cannot load an lvalue to begin with");
+			break;
+		case cgValue_Multi:
+			GB_PANIC("NOT POSSIBLE - Cannot load multiple values at once");
+			break;
+		case cgValue_Symbol:
+			return cg_lvalue_addr(tb_inst_get_symbol_address(p->func, ptr.symbol), type);
+		}
+	}
+
+	// use the natural alignment
+	// if people need a special alignment, they can use `intrinsics.unaligned_load`
+	TB_CharUnits alignment = cast(TB_CharUnits)type_align_of(type);
+
+	TB_Node *the_ptr = nullptr;
+	switch (ptr.kind) {
+	case cgValue_Value:
+		the_ptr = ptr.node;
+		break;
+	case cgValue_Addr:
+		the_ptr = tb_inst_load(p->func, TB_TYPE_PTR, ptr.node, alignment, is_volatile);
+		break;
+	case cgValue_Multi:
+		GB_PANIC("NOT POSSIBLE - Cannot load multiple values at once");
+		break;
+	case cgValue_Symbol:
+		the_ptr = tb_inst_get_symbol_address(p->func, ptr.symbol);
+		break;
+	}
+	return cg_value(tb_inst_load(p->func, dt, the_ptr, alignment, is_volatile), type);
+}
+
+gb_internal void cg_emit_store(cgProcedure *p, cgValue dst, cgValue src, bool is_volatile) {
+	GB_ASSERT_MSG(dst.kind != cgValue_Multi, "cannot store to multiple values at once");
+
+	if (dst.kind == cgValue_Addr) {
+		dst = cg_emit_load(p, dst, is_volatile);
+	} else if (dst.kind == cgValue_Symbol) {
+		dst = cg_value(tb_inst_get_symbol_address(p->func, dst.symbol), dst.type);
+	}
+
+	GB_ASSERT(is_type_pointer(dst.type));
+	Type *dst_type = type_deref(dst.type);
+
+	GB_ASSERT_MSG(are_types_identical(dst_type, src.type), "%s vs %s", type_to_string(dst_type), type_to_string(src.type));
+
+	TB_DataType dt = cg_data_type(dst_type);
+	TB_DataType st = cg_data_type(src.type);
+	GB_ASSERT(dt.raw == st.raw);
+
+	// use the natural alignment
+	// if people need a special alignment, they can use `intrinsics.unaligned_store`
+	TB_CharUnits alignment = cast(TB_CharUnits)type_align_of(dst_type);
+
+	if (TB_IS_VOID_TYPE(dt)) {
+		TB_Node *dst_ptr = nullptr;
+		TB_Node *src_ptr = nullptr;
+
+		switch (dst.kind) {
+		case cgValue_Value:
+			dst_ptr	= dst.node;
+			break;
+		case cgValue_Addr:
+			GB_PANIC("DST cgValue_Addr should be handled above");
+			break;
+		case cgValue_Symbol:
+			dst_ptr = tb_inst_get_symbol_address(p->func, dst.symbol);
+			break;
+		}
+
+		switch (src.kind) {
+		case cgValue_Value:
+			GB_PANIC("SRC cgValue_Value should be handled above");
+			break;
+		case cgValue_Symbol:
+			GB_PANIC("SRC cgValue_Symbol should be handled above");
+			break;
+		case cgValue_Addr:
+			src_ptr = src.node;
+			break;
+		}
+
+		// IMPORTANT TODO(bill): needs to be memmove
+		i64 sz = type_size_of(dst_type);
+		TB_Node *count = tb_inst_uint(p->func, TB_TYPE_INT, cast(u64)sz);
+		tb_inst_memcpy(p->func, dst_ptr, src_ptr, count, alignment, is_volatile);
+		return;
+	}
+
+
+	switch (dst.kind) {
+	case cgValue_Value:
+		switch (src.kind) {
+		case cgValue_Value:
+			if (src.node->dt.type == TB_INT && src.node->dt.data == 1) {
+				src.node = tb_inst_zxt(p->func, src.node, dt);
+			}
+			tb_inst_store(p->func, dt, dst.node, src.node, alignment, is_volatile);
+			return;
+		case cgValue_Addr:
+			tb_inst_store(p->func, dt, dst.node,
+			              tb_inst_load(p->func, st, src.node, alignment, is_volatile),
+			              alignment, is_volatile);
+			return;
+		case cgValue_Symbol:
+			tb_inst_store(p->func, dt, dst.node,
+			              tb_inst_get_symbol_address(p->func, src.symbol),
+			              alignment, is_volatile);
+			return;
+		}
+	case cgValue_Addr:
+		GB_PANIC("cgValue_Addr should be handled above");
+		break;
+	case cgValue_Symbol:
+		GB_PANIC(" cgValue_Symbol should be handled above");
+		break;
+	}
+}
+
+
+gb_internal cgValue cg_address_from_load(cgProcedure *p, cgValue value) {
+	switch (value.kind) {
+	case cgValue_Value:
+		{
+			TB_Node *load_inst = value.node;
+			GB_ASSERT_MSG(load_inst->type == TB_LOAD, "expected a load instruction");
+			TB_Node *ptr = load_inst->inputs[1];
+			return cg_value(ptr, alloc_type_pointer(value.type));
+		}
+	case cgValue_Addr:
+		return cg_value(value.node, alloc_type_pointer(value.type));
+	case cgValue_Symbol:
+		GB_PANIC("Symbol is an invalid use case for cg_address_from_load");
+		return {};
+	case cgValue_Multi:
+		GB_PANIC("Multi is an invalid use case for cg_address_from_load");
+		break;
+	}
+	GB_PANIC("Invalid cgValue for cg_address_from_load");
+	return {};
+
+}
+
+gb_internal bool cg_addr_is_empty(cgAddr const &addr) {
+	switch (addr.kind) {
+	case cgValue_Value:
+	case cgValue_Addr:
+		return addr.addr.node == nullptr;
+	case cgValue_Symbol:
+		return addr.addr.symbol == nullptr;
+	case cgValue_Multi:
+		return addr.addr.multi == nullptr;
+	}
+	return true;
+}
+
+gb_internal Type *cg_addr_type(cgAddr const &addr) {
+	if (cg_addr_is_empty(addr)) {
+		return nullptr;
+	}
+	switch (addr.kind) {
+	case cgAddr_Map:
+		{
+			Type *t = base_type(addr.map.type);
+			GB_ASSERT(is_type_map(t));
+			return t->Map.value;
+		}
+	case cgAddr_Swizzle:
+		return addr.swizzle.type;
+	case cgAddr_SwizzleLarge:
+		return addr.swizzle_large.type;
+	case cgAddr_Context:
+		if (addr.ctx.sel.index.count > 0) {
+			Type *t = t_context;
+			for_array(i, addr.ctx.sel.index) {
+				GB_ASSERT(is_type_struct(t));
+				t = base_type(t)->Struct.fields[addr.ctx.sel.index[i]]->type;
+			}
+			return t;
+		}
+		break;
+	}
+	return type_deref(addr.addr.type);
+}
+
+gb_internal cgValue cg_addr_load(cgProcedure *p, cgAddr addr) {
+	if (addr.addr.node == nullptr) {
+		return {};
+	}
+	switch (addr.kind) {
+	case cgAddr_Default:
+		return cg_emit_load(p, addr.addr);
+	}
+	GB_PANIC("TODO(bill): cg_addr_load %p", addr.addr.node);
+	return {};
+}
+
+
+gb_internal void cg_addr_store(cgProcedure *p, cgAddr addr, cgValue value) {
+	if (cg_addr_is_empty(addr)) {
+		return;
+	}
+	GB_ASSERT(value.type != nullptr);
+	if (is_type_untyped_uninit(value.type)) {
+		Type *t = cg_addr_type(addr);
+		value = cg_value(tb_inst_poison(p->func), t);
+		// TODO(bill): IS THIS EVEN A GOOD IDEA?
+	} else if (is_type_untyped_nil(value.type)) {
+		Type *t = cg_addr_type(addr);
+		value = cg_const_nil(p, t);
+	}
+
+	if (addr.kind == cgAddr_RelativePointer && addr.relative.deref) {
+		addr = cg_addr(cg_address_from_load(p, cg_addr_load(p, addr)));
+	}
+
+	if (addr.kind == cgAddr_RelativePointer) {
+		GB_PANIC("TODO(bill): cgAddr_RelativePointer");
+	} else if (addr.kind == cgAddr_RelativeSlice) {
+		GB_PANIC("TODO(bill): cgAddr_RelativeSlice");
+	} else if (addr.kind == cgAddr_Map) {
+		GB_PANIC("TODO(bill): cgAddr_Map");
+	} else if (addr.kind == cgAddr_Context) {
+		cgAddr old_addr = cg_find_or_generate_context_ptr(p);
+
+		bool create_new = true;
+		for_array(i, p->context_stack) {
+			cgContextData *ctx_data = &p->context_stack[i];
+			if (ctx_data->ctx.addr.node == old_addr.addr.node) {
+				if (ctx_data->uses > 0) {
+					create_new = true;
+				} else if (p->scope_index > ctx_data->scope_index) {
+					create_new = true;
+				} else {
+					// gb_printf_err("%.*s (curr:%td) (ctx:%td) (uses:%td)\n", LIT(p->name), p->scope_index, ctx_data->scope_index, ctx_data->uses);
+					create_new = false;
+				}
+				break;
+			}
+		}
+
+		cgValue next = {};
+		if (create_new) {
+			cgValue old = cg_addr_load(p, old_addr);
+			cgAddr next_addr = cg_add_local(p, t_context, nullptr, true);
+			cg_addr_store(p, next_addr, old);
+			cg_push_context_onto_stack(p, next_addr);
+			next = next_addr.addr;
+		} else {
+			next = old_addr.addr;
+		}
+
+		if (addr.ctx.sel.index.count > 0) {
+			cgValue lhs = cg_emit_deep_field_gep(p, next, addr.ctx.sel);
+			cgValue rhs = cg_emit_conv(p, value, type_deref(lhs.type));
+			cg_emit_store(p, lhs, rhs);
+		} else {
+			cgValue lhs = next;
+			cgValue rhs = cg_emit_conv(p, value, cg_addr_type(addr));
+			cg_emit_store(p, lhs, rhs);
+		}
+		return;
+	} else if (addr.kind == cgAddr_SoaVariable) {
+		GB_PANIC("TODO(bill): cgAddr_SoaVariable");
+	} else if (addr.kind == cgAddr_Swizzle) {
+		GB_ASSERT(addr.swizzle.count <= 4);
+		GB_PANIC("TODO(bill): cgAddr_Swizzle");
+	} else if (addr.kind == cgAddr_SwizzleLarge) {
+		GB_PANIC("TODO(bill): cgAddr_SwizzleLarge");
+	}
+
+	value = cg_emit_conv(p, value, cg_addr_type(addr));
+	cg_emit_store(p, addr.addr, value);
+}
+
+gb_internal cgValue cg_addr_get_ptr(cgProcedure *p, cgAddr const &addr) {
+	if (cg_addr_is_empty(addr)) {
+		GB_PANIC("Illegal addr -> nullptr");
+		return {};
+	}
+
+	switch (addr.kind) {
+	case cgAddr_Map:
+		GB_PANIC("TODO(bill): cg_addr_get_ptr cgAddr_Map");
+		// return cg_internal_dynamic_map_get_ptr(p, addr.addr, addr.map.key);
+		break;
+
+	case cgAddr_RelativePointer: {
+		Type *rel_ptr = base_type(cg_addr_type(addr));
+		GB_ASSERT(rel_ptr->kind == Type_RelativePointer);
+
+		cgValue ptr = cg_emit_conv(p, addr.addr, t_uintptr);
+		cgValue offset = cg_emit_conv(p, ptr, alloc_type_pointer(rel_ptr->RelativePointer.base_integer));
+		offset = cg_emit_load(p, offset);
+
+		if (!is_type_unsigned(rel_ptr->RelativePointer.base_integer)) {
+			offset = cg_emit_conv(p, offset, t_i64);
+		}
+		offset = cg_emit_conv(p, offset, t_uintptr);
+
+		cgValue absolute_ptr = cg_emit_arith(p, Token_Add, ptr, offset, t_uintptr);
+		absolute_ptr = cg_emit_conv(p, absolute_ptr, rel_ptr->RelativePointer.pointer_type);
+
+		GB_PANIC("TODO(bill): cg_addr_get_ptr cgAddr_RelativePointer");
+		// cgValue cond = cg_emit_comp(p, Token_CmpEq, offset, cg_const_nil(p->module, rel_ptr->RelativePointer.base_integer));
+
+		// NOTE(bill): nil check
+		// cgValue nil_ptr = cg_const_nil(p->module, rel_ptr->RelativePointer.pointer_type);
+		// cgValue final_ptr = cg_emit_select(p, cond, nil_ptr, absolute_ptr);
+		// return final_ptr;
+		break;
+	}
+
+	case cgAddr_SoaVariable:
+		// TODO(bill): FIX THIS HACK
+		return cg_address_from_load(p, cg_addr_load(p, addr));
+
+	case cgAddr_Context:
+		GB_PANIC("cgAddr_Context should be handled elsewhere");
+		break;
+
+	case cgAddr_Swizzle:
+	case cgAddr_SwizzleLarge:
+		// TOOD(bill): is this good enough logic?
+		break;
+	}
+
+	return addr.addr;
+}
+
+gb_internal cgValue cg_emit_ptr_offset(cgProcedure *p, cgValue ptr, cgValue index) {
+	GB_ASSERT(ptr.kind == cgValue_Value);
+	GB_ASSERT(index.kind == cgValue_Value);
+	GB_ASSERT(is_type_pointer(ptr.type) || is_type_multi_pointer(ptr.type));
+	GB_ASSERT(is_type_integer(index.type));
+
+	Type *elem = type_deref(ptr.type, true);
+	i64 stride = type_size_of(elem);
+	return cg_value(tb_inst_array_access(p->func, ptr.node, index.node, stride), alloc_type_pointer(elem));
+}
+gb_internal cgValue cg_emit_array_ep(cgProcedure *p, cgValue s, cgValue index) {
+	GB_ASSERT(s.kind == cgValue_Value);
+	GB_ASSERT(index.kind == cgValue_Value);
+
+	Type *t = s.type;
+	GB_ASSERT_MSG(is_type_pointer(t), "%s", type_to_string(t));
+	Type *st = base_type(type_deref(t));
+	GB_ASSERT_MSG(is_type_array(st) || is_type_enumerated_array(st) || is_type_matrix(st), "%s", type_to_string(st));
+	GB_ASSERT_MSG(is_type_integer(core_type(index.type)), "%s", type_to_string(index.type));
+
+
+	Type *elem = base_array_type(st);
+	i64 stride = type_size_of(elem);
+	return cg_value(tb_inst_array_access(p->func, s.node, index.node, stride), alloc_type_pointer(elem));
+}
+gb_internal cgValue cg_emit_array_epi(cgProcedure *p, cgValue s, i64 index) {
+	return cg_emit_array_ep(p, s, cg_const_int(p, t_int, index));
+}
+
+
+gb_internal cgValue cg_emit_struct_ep(cgProcedure *p, cgValue s, i64 index) {
+	s = cg_flatten_value(p, s);
+
+	GB_ASSERT(is_type_pointer(s.type));
+	Type *t = base_type(type_deref(s.type));
+	Type *result_type = nullptr;
+
+	if (is_type_relative_pointer(t)) {
+		s = cg_addr_get_ptr(p, cg_addr(s));
+	}
+	i64 offset = -1;
+	i64 int_size = build_context.int_size;
+	i64 ptr_size = build_context.ptr_size;
+
+	switch (t->kind) {
+	case Type_Struct:
+		type_set_offsets(t);
+		result_type = t->Struct.fields[index]->type;
+		offset = t->Struct.offsets[index];
+		break;
+	case Type_Union:
+		GB_ASSERT(index == -1);
+		GB_PANIC("TODO(bill): cg_emit_union_tag_ptr");
+		break;
+		// return cg_emit_union_tag_ptr(p, s);
+	case Type_Tuple:
+		type_set_offsets(t);
+		result_type = t->Tuple.variables[index]->type;
+		offset = t->Tuple.offsets[index];
+		GB_PANIC("TODO(bill): cg_emit_tuple_ep %d", s.kind);
+		break;
+		// return cg_emit_tuple_ep(p, s, index);
+	case Type_Slice:
+		switch (index) {
+		case 0:
+			result_type = alloc_type_multi_pointer(t->Slice.elem);
+			offset = 0;
+			break;
+		case 1:
+			result_type = t_int;
+			offset = int_size;
+			break;
+		}
+		break;
+	case Type_Basic:
+		switch (t->Basic.kind) {
+		case Basic_string:
+			switch (index) {
+			case 0:
+				result_type = t_u8_multi_ptr;
+				offset = 0;
+				break;
+			case 1:
+				result_type = t_int;
+				offset = int_size;
+				break;
+			}
+			break;
+		case Basic_any:
+			switch (index) {
+			case 0:
+				result_type = t_rawptr;
+				offset = 0;
+				break;
+			case 1:
+				result_type = t_typeid;
+				offset = ptr_size;
+				break;
+			}
+			break;
+
+		case Basic_complex32:
+		case Basic_complex64:
+		case Basic_complex128:
+			{
+				Type *ft = base_complex_elem_type(t);
+				i64 sz = type_size_of(ft);
+				switch (index) {
+				case 0: case 1:
+					result_type = ft; offset = sz * index; break;
+				default: goto error_case;
+				}
+				break;
+			}
+		case Basic_quaternion64:
+		case Basic_quaternion128:
+		case Basic_quaternion256:
+			{
+				Type *ft = base_complex_elem_type(t);
+				i64 sz = type_size_of(ft);
+				switch (index) {
+				case 0: case 1: case 2: case 3:
+					result_type = ft; offset = sz * index; break;
+				default: goto error_case;
+				}
+			}
+			break;
+		default:
+			goto error_case;
+		}
+		break;
+	case Type_DynamicArray:
+		switch (index) {
+		case 0:
+			result_type = alloc_type_multi_pointer(t->DynamicArray.elem);
+			offset = index*int_size;
+			break;
+		case 1: case 2:
+			result_type = t_int;
+			offset = index*int_size;
+			break;
+		case 3:
+			result_type = t_allocator;
+			offset = index*int_size;
+			break;
+		default: goto error_case;
+		}
+		break;
+	case Type_Map:
+		{
+			init_map_internal_types(t);
+			Type *itp = alloc_type_pointer(t_raw_map);
+			s = cg_emit_transmute(p, s, itp);
+
+			Type *rms = base_type(t_raw_map);
+			GB_ASSERT(rms->kind == Type_Struct);
+
+			if (0 <= index && index < 3) {
+				result_type = rms->Struct.fields[index]->type;
+				offset = rms->Struct.offsets[index];
+			} else {
+				goto error_case;
+			}
+			break;
+		}
+	case Type_Array:
+		return cg_emit_array_epi(p, s, index);
+	case Type_SoaPointer:
+		switch (index) {
+		case 0: result_type = alloc_type_pointer(t->SoaPointer.elem); break;
+		case 1: result_type = t_int; break;
+		}
+		break;
+	default:
+	error_case:;
+		GB_PANIC("TODO(bill): struct_gep type: %s, %d", type_to_string(s.type), index);
+		break;
+	}
+
+	GB_ASSERT_MSG(result_type != nullptr, "%s %d", type_to_string(t), index);
+	GB_ASSERT(offset >= 0);
+
+	GB_ASSERT(s.kind == cgValue_Value);
+	return cg_value(
+		tb_inst_member_access(p->func, s.node, offset),
+		alloc_type_pointer(result_type)
+	);
+}
+
+
+gb_internal cgValue cg_emit_struct_ev(cgProcedure *p, cgValue s, i64 index) {
+	s = cg_address_from_load_or_generate_local(p, s);
+	cgValue ptr = cg_emit_struct_ep(p, s, index);
+	return cg_flatten_value(p, cg_emit_load(p, ptr));
+}
+
+
+gb_internal cgValue cg_emit_deep_field_gep(cgProcedure *p, cgValue e, Selection const &sel) {
+	GB_ASSERT(sel.index.count > 0);
+	Type *type = type_deref(e.type);
+
+	for_array(i, sel.index) {
+		i64 index = sel.index[i];
+		if (is_type_pointer(type)) {
+			type = type_deref(type);
+			e = cg_emit_load(p, e);
+		}
+		type = core_type(type);
+
+		switch (type->kind) {
+		case Type_SoaPointer: {
+			cgValue addr = cg_emit_struct_ep(p, e, 0);
+			cgValue index = cg_emit_struct_ep(p, e, 1);
+			addr = cg_emit_load(p, addr);
+			index = cg_emit_load(p, index);
+
+			i64 first_index = sel.index[0];
+			Selection sub_sel = sel;
+			sub_sel.index.data += 1;
+			sub_sel.index.count -= 1;
+
+			cgValue arr = cg_emit_struct_ep(p, addr, first_index);
+
+			Type *t = base_type(type_deref(addr.type));
+			GB_ASSERT(is_type_soa_struct(t));
+
+			if (t->Struct.soa_kind == StructSoa_Fixed) {
+				e = cg_emit_array_ep(p, arr, index);
+			} else {
+				e = cg_emit_ptr_offset(p, cg_emit_load(p, arr), index);
+			}
+			break;
+		}
+		case Type_Basic:
+			switch (type->Basic.kind) {
+			case Basic_any:
+				if (index == 0) {
+					type = t_rawptr;
+				} else if (index == 1) {
+					type = t_type_info_ptr;
+				}
+				e = cg_emit_struct_ep(p, e, index);
+				break;
+			default:
+				e = cg_emit_struct_ep(p, e, index);
+				break;
+			}
+			break;
+		case Type_Struct:
+			if (type->Struct.is_raw_union) {
+				type = get_struct_field_type(type, index);
+				GB_ASSERT(is_type_pointer(e.type));
+				e = cg_emit_transmute(p, e, alloc_type_pointer(type));
+			} else {
+				type = get_struct_field_type(type, index);
+				e = cg_emit_struct_ep(p, e, index);
+			}
+			break;
+		case Type_Union:
+			GB_ASSERT(index == -1);
+			type = t_type_info_ptr;
+			e = cg_emit_struct_ep(p, e, index);
+			break;
+		case Type_Tuple:
+			type = type->Tuple.variables[index]->type;
+			e = cg_emit_struct_ep(p, e, index);
+			break;
+		case Type_Slice:
+		case Type_DynamicArray:
+		case Type_Map:
+		case Type_RelativePointer:
+			e = cg_emit_struct_ep(p, e, index);
+			break;
+		case Type_Array:
+			e = cg_emit_array_epi(p, e, index);
+			break;
+		default:
+			GB_PANIC("un-gep-able type %s", type_to_string(type));
+			break;
+		}
+	}
+
+	return e;
+}
+
+
+
+
+
+
+
+
+gb_internal cgBranchRegions cg_lookup_branch_regions(cgProcedure *p, Ast *ident) {
+	GB_ASSERT(ident->kind == Ast_Ident);
+	Entity *e = entity_of_node(ident);
+	GB_ASSERT(e->kind == Entity_Label);
+	for (cgBranchRegions const &b : p->branch_regions) {
+		if (b.label == e->Label.node) {
+			return b;
+		}
+	}
+
+	GB_PANIC("Unreachable");
+	cgBranchRegions empty = {};
+	return empty;
+}
+
+gb_internal cgTargetList *cg_push_target_list(cgProcedure *p, Ast *label, TB_Node *break_, TB_Node *continue_, TB_Node *fallthrough_) {
+	cgTargetList *tl = gb_alloc_item(permanent_allocator(), cgTargetList);
+	tl->prev = p->target_list;
+	tl->break_ = break_;
+	tl->continue_ = continue_;
+	tl->fallthrough_ = fallthrough_;
+	p->target_list = tl;
+
+	if (label != nullptr) { // Set label blocks
+		GB_ASSERT(label->kind == Ast_Label);
+
+		for (cgBranchRegions &b : p->branch_regions) {
+			GB_ASSERT(b.label != nullptr && label != nullptr);
+			GB_ASSERT(b.label->kind == Ast_Label);
+			if (b.label == label) {
+				b.break_    = break_;
+				b.continue_ = continue_;
+				return tl;
+			}
+		}
+
+		GB_PANIC("Unreachable");
+	}
+
+	return tl;
+}
+
+gb_internal void cg_pop_target_list(cgProcedure *p) {
+	p->target_list = p->target_list->prev;
+}
+gb_internal cgAddr cg_add_local(cgProcedure *p, Type *type, Entity *e, bool zero_init) {
+	GB_ASSERT(type != nullptr);
+
+	isize size = type_size_of(type);
+	TB_CharUnits alignment = cast(TB_CharUnits)type_align_of(type);
+	if (is_type_matrix(type)) {
+		alignment *= 2; // NOTE(bill): Just in case
+	}
+
+	TB_Node *local = tb_inst_local(p->func, cast(u32)size, alignment);
+
+	if (e != nullptr && e->token.string.len > 0 && e->token.string != "_") {
+		// NOTE(bill): for debugging purposes only
+		String name = e->token.string;
+		TB_DebugType *debug_type = cg_debug_type(p->module, type);
+		tb_node_append_attrib(local, tb_function_attrib_variable(p->func, name.len, cast(char const *)name.text, debug_type));
+	}
+
+	if (zero_init) {
+		bool is_volatile = false;
+		TB_Node *zero = tb_inst_uint(p->func, TB_TYPE_I8, 0);
+		TB_Node *count = tb_inst_uint(p->func, TB_TYPE_I32, cast(u64)size);
+		tb_inst_memset(p->func, local, zero, count, alignment, is_volatile);
+	}
+
+	cgAddr addr = cg_addr(cg_value(local, alloc_type_pointer(type)));
+	if (e) {
+		map_set(&p->variable_map, e, addr);
+	}
+	return addr;
+}
+
+gb_internal cgAddr cg_add_global(cgProcedure *p, Type *type, Entity *e) {
+	GB_ASSERT(type != nullptr);
+
+	isize size = type_size_of(type);
+	TB_CharUnits alignment = cast(TB_CharUnits)type_align_of(type);
+	if (is_type_matrix(type)) {
+		alignment *= 2; // NOTE(bill): Just in case
+	}
+
+	TB_Global *global = tb_global_create(p->module->mod, 0, "", nullptr, TB_LINKAGE_PRIVATE);
+	tb_global_set_storage(p->module->mod, tb_module_get_data(p->module->mod), global, size, alignment, 0);
+	TB_Node *local = tb_inst_get_symbol_address(p->func, cast(TB_Symbol *)global);
+
+	if (e != nullptr && e->token.string.len > 0 && e->token.string != "_") {
+		// NOTE(bill): for debugging purposes only
+		String name = e->token.string;
+		TB_DebugType *debug_type = cg_debug_type(p->module, type);
+		tb_node_append_attrib(local, tb_function_attrib_variable(p->func, name.len, cast(char const *)name.text, debug_type));
+	}
+
+	cgAddr addr = cg_addr(cg_value(local, alloc_type_pointer(type)));
+	if (e) {
+		map_set(&p->variable_map, e, addr);
+	}
+	return addr;
+}
+
+
+gb_internal cgValue cg_copy_value_to_ptr(cgProcedure *p, cgValue value, Type *original_type, isize min_alignment) {
+	TB_CharUnits size  = cast(TB_CharUnits)type_size_of(original_type);
+	TB_CharUnits align = cast(TB_CharUnits)gb_max(type_align_of(original_type), min_alignment);
+	TB_Node *copy = tb_inst_local(p->func, size, align);
+	if (value.kind == cgValue_Value) {
+		tb_inst_store(p->func, cg_data_type(original_type), copy, value.node, align, false);
+	} else {
+		GB_ASSERT(value.kind == cgValue_Addr);
+		tb_inst_memcpy(p->func, copy, value.node, tb_inst_uint(p->func, TB_TYPE_INT, size), align, false);
+	}
+
+	return cg_value(copy, alloc_type_pointer(original_type));
+}
+
+gb_internal cgValue cg_address_from_load_or_generate_local(cgProcedure *p, cgValue value) {
+	switch (value.kind) {
+	case cgValue_Value:
+		if (value.node->type == TB_LOAD) {
+			TB_Node *ptr = value.node->inputs[1];
+			return cg_value(ptr, alloc_type_pointer(value.type));
+		}
+		break;
+	case cgValue_Addr:
+		return cg_value(value.node, alloc_type_pointer(value.type));
+	case cgValue_Multi:
+		GB_PANIC("cgValue_Multi not allowed");
+	}
+
+	cgAddr res = cg_add_local(p, value.type, nullptr, false);
+	cg_addr_store(p, res, value);
+	return res.addr;
+}
+
+
+gb_internal void cg_build_defer_stmt(cgProcedure *p, cgDefer const &d) {
+	TB_Node *curr_region = tb_inst_get_control(p->func);
+	if (curr_region == nullptr) {
+		return;
+	}
+
+	// NOTE(bill): The prev block may defer injection before it's terminator
+	TB_Node *last_inst = nullptr;
+	// if (curr_region->input_count) {
+	// 	last_inst = *(curr_region->inputs + curr_region->input_count);
+	// }
+	// if (last_inst && TB_IS_NODE_TERMINATOR(last_inst->type)) {
+	// 	// NOTE(bill): ReturnStmt defer stuff will be handled previously
+	// 	return;
+	// }
+
+	isize prev_context_stack_count = p->context_stack.count;
+	GB_ASSERT(prev_context_stack_count <= p->context_stack.capacity);
+	defer (p->context_stack.count = prev_context_stack_count);
+	p->context_stack.count = d.context_stack_count;
+
+	TB_Node *b = cg_control_region(p, "defer");
+	if (last_inst == nullptr) {
+		cg_emit_goto(p, b);
+	}
+
+	tb_inst_set_control(p->func, b);
+	if (d.kind == cgDefer_Node) {
+		cg_build_stmt(p, d.stmt);
+	} else if (d.kind == cgDefer_Proc) {
+		cg_emit_call(p, d.proc.deferred, d.proc.result_as_args);
+	}
+}
+
+
+gb_internal void cg_emit_defer_stmts(cgProcedure *p, cgDeferExitKind kind, TB_Node *control_region) {
+	isize count = p->defer_stack.count;
+	isize i = count;
+	while (i --> 0) {
+		cgDefer const &d = p->defer_stack[i];
+
+		if (kind == cgDeferExit_Default) {
+			if (p->scope_index == d.scope_index &&
+			    d.scope_index > 0) {
+				cg_build_defer_stmt(p, d);
+				array_pop(&p->defer_stack);
+				continue;
+			} else {
+				break;
+			}
+		} else if (kind == cgDeferExit_Return) {
+			cg_build_defer_stmt(p, d);
+		} else if (kind == cgDeferExit_Branch) {
+			GB_ASSERT(control_region != nullptr);
+			isize lower_limit = -1;
+			for (auto const &cr : p->control_regions) {
+				if (cr.control_region == control_region) {
+					lower_limit = cr.scope_index;
+					break;
+				}
+			}
+			GB_ASSERT(lower_limit >= 0);
+			if (lower_limit < d.scope_index) {
+				cg_build_defer_stmt(p, d);
+			}
+		}
+	}
+}
+
+gb_internal void cg_scope_open(cgProcedure *p, Scope *scope) {
+	// TODO(bill): debug scope information
+
+	p->scope_index += 1;
+	array_add(&p->scope_stack, scope);
+}
+
+gb_internal void cg_scope_close(cgProcedure *p, cgDeferExitKind kind, TB_Node *control_region) {
+	cg_emit_defer_stmts(p, kind, control_region);
+	GB_ASSERT(p->scope_index > 0);
+
+	while (p->context_stack.count > 0) {
+		auto *ctx = &p->context_stack[p->context_stack.count-1];
+		if (ctx->scope_index < p->scope_index) {
+			break;
+		}
+		array_pop(&p->context_stack);
+	}
+
+	p->scope_index -= 1;
+	array_pop(&p->scope_stack);
+}
+
+
+gb_internal isize cg_append_tuple_values(cgProcedure *p, Array<cgValue> *dst_values, cgValue src_value) {
+	isize init_count = dst_values->count;
+	Type *t = src_value.type;
+	if (t && t->kind == Type_Tuple) {
+		GB_ASSERT(src_value.kind == cgValue_Multi);
+		GB_ASSERT(src_value.multi != nullptr);
+		GB_ASSERT(src_value.multi->values.count == t->Tuple.variables.count);
+		for (cgValue const &value : src_value.multi->values) {
+			array_add(dst_values, value);
+		}
+	} else {
+		array_add(dst_values, src_value);
+	}
+	return dst_values->count - init_count;
+}
+gb_internal void cg_build_assignment(cgProcedure *p, Array<cgAddr> const &lvals, Slice<Ast *> const &values) {
+	if (values.count == 0) {
+		return;
+	}
+
+	auto inits = array_make<cgValue>(permanent_allocator(), 0, lvals.count);
+
+	for (Ast *rhs : values) {
+		cgValue init = cg_build_expr(p, rhs);
+		cg_append_tuple_values(p, &inits, init);
+	}
+
+	bool prev_in_assignment = p->in_multi_assignment;
+
+	isize lval_count = 0;
+	for (cgAddr const &lval : lvals) {
+		if (!cg_addr_is_empty(lval)) {
+			// check if it is not a blank identifier
+			lval_count += 1;
+		}
+	}
+	p->in_multi_assignment = lval_count > 1;
+
+	GB_ASSERT(lvals.count == inits.count);
+
+
+	if (inits.count > 1) for_array(i, inits) {
+		cgAddr lval = lvals[i];
+		cgValue init = cg_flatten_value(p, inits[i]);
+
+		GB_ASSERT(init.kind != cgValue_Multi);
+		if (init.type == nullptr) {
+			continue;
+		}
+
+	    	Type *type = cg_addr_type(lval);
+		if (!cg_addr_is_empty(lval)) {
+			GB_ASSERT_MSG(are_types_identical(init.type, type), "%s = %s", type_to_string(init.type), type_to_string(type));
+		}
+
+		if (init.kind == cgValue_Addr &&
+		    !cg_addr_is_empty(lval)) {
+		    	// NOTE(bill): This is needed for certain constructs such as this:
+		    	// a, b = b, a
+		    	// NOTE(bill): This is a bodge and not necessarily a good way of doing things whatsoever
+		    	TB_CharUnits size  = cast(TB_CharUnits)type_size_of(type);
+		    	TB_CharUnits align = cast(TB_CharUnits)type_align_of(type);
+		    	TB_Node *copy = tb_inst_local(p->func, size, align);
+		    	tb_inst_memcpy(p->func, copy, init.node, tb_inst_uint(p->func, TB_TYPE_INT, size), align, false);
+		    	// use the copy instead
+		    	init.node = copy;
+		}
+		inits[i] = init;
+	}
+
+	for_array(i, inits) {
+		cgAddr  lval = lvals[i];
+		cgValue init = inits[i];
+		GB_ASSERT(init.kind != cgValue_Multi);
+		if (init.type == nullptr) {
+			continue;
+		}
+		cg_addr_store(p, lval, init);
+	}
+
+	p->in_multi_assignment = prev_in_assignment;
+}
+
+gb_internal void cg_build_assign_stmt(cgProcedure *p, AstAssignStmt *as) {
+	if (as->op.kind == Token_Eq) {
+		auto lvals = array_make<cgAddr>(permanent_allocator(), 0, as->lhs.count);
+
+		for (Ast *lhs : as->lhs) {
+			cgAddr lval = {};
+			if (!is_blank_ident(lhs)) {
+				lval = cg_build_addr(p, lhs);
+			}
+			array_add(&lvals, lval);
+		}
+		cg_build_assignment(p, lvals, as->rhs);
+		return;
+	}
+
+	GB_ASSERT(as->lhs.count == 1);
+	GB_ASSERT(as->rhs.count == 1);
+	// NOTE(bill): Only 1 += 1 is allowed, no tuples
+	// +=, -=, etc
+
+	i32 op_ = cast(i32)as->op.kind;
+	op_ += Token_Add - Token_AddEq; // Convert += to +
+	TokenKind op = cast(TokenKind)op_;
+
+	if (op == Token_CmpAnd || op == Token_CmpOr) {
+		GB_PANIC("TODO(bill): cg_emit_logical_binary_expr");
+		// Type *type = as->lhs[0]->tav.type;
+		// cgValue new_value = cg_emit_logical_binary_expr(p, op, as->lhs[0], as->rhs[0], type);
+
+		// cgAddr lhs = cg_build_addr(p, as->lhs[0]);
+		// cg_addr_store(p, lhs, new_value);
+	} else {
+		cgAddr lhs = cg_build_addr(p, as->lhs[0]);
+		cgValue value = cg_build_expr(p, as->rhs[0]);
+		Type *lhs_type = cg_addr_type(lhs);
+
+		// NOTE(bill): Allow for the weird edge case of:
+		// array *= matrix
+		if (op == Token_Mul && is_type_matrix(value.type) && is_type_array(lhs_type)) {
+			GB_PANIC("TODO(bill): array *= matrix");
+			// cgValue old_value = cg_addr_load(p, lhs);
+			// Type *type = old_value.type;
+			// cgValue new_value = cg_emit_vector_mul_matrix(p, old_value, value, type);
+			// cg_addr_store(p, lhs, new_value);
+			// return;
+		}
+
+		if (is_type_array(lhs_type)) {
+			GB_PANIC("TODO(bill): cg_build_assign_stmt_array");
+			// cg_build_assign_stmt_array(p, op, lhs, value);
+			// return;
+		} else {
+			cgValue old_value = cg_addr_load(p, lhs);
+			Type *type = old_value.type;
+
+			cgValue change = cg_emit_conv(p, value, type);
+			cgValue new_value = cg_emit_arith(p, op, old_value, change, type);
+			cg_addr_store(p, lhs, new_value);
+		}
+	}
+}
+
+gb_internal void cg_build_return_stmt_internal_single(cgProcedure *p, cgValue result) {
+	Slice<cgValue> results = {};
+	results.data = &result;
+	results.count = 1;
+	cg_build_return_stmt_internal(p, results);
+}
+
+
+gb_internal void cg_build_return_stmt_internal(cgProcedure *p, Slice<cgValue> const &results) {
+	TypeTuple *tuple  = &p->type->Proc.results->Tuple;
+	isize return_count = p->type->Proc.result_count;
+
+	if (return_count == 0) {
+		tb_inst_ret(p->func, 0, nullptr);
+		return;
+	}
+
+	if (p->split_returns_index >= 0) {
+		GB_ASSERT(is_calling_convention_odin(p->type->Proc.calling_convention));
+
+		for (isize i = 0; i < return_count-1; i++) {
+			Entity *e = tuple->variables[i];
+			TB_Node *ret_ptr = tb_inst_param(p->func, cast(int)(p->split_returns_index+i));
+			cgValue ptr = cg_value(ret_ptr, alloc_type_pointer(e->type));
+			cg_emit_store(p, ptr, results[i]);
+		}
+
+		if (p->return_by_ptr) {
+			Entity *e = tuple->variables[return_count-1];
+			TB_Node *ret_ptr = tb_inst_param(p->func, 0);
+			cgValue ptr = cg_value(ret_ptr, alloc_type_pointer(e->type));
+			cg_emit_store(p, ptr, results[return_count-1]);
+
+			tb_inst_ret(p->func, 0, nullptr);
+			return;
+		} else {
+			GB_ASSERT(p->proto->return_count == 1);
+			TB_DataType dt = TB_PROTOTYPE_RETURNS(p->proto)->dt;
+
+			cgValue result = results[return_count-1];
+			result = cg_flatten_value(p, result);
+			TB_Node *final_res = nullptr;
+			if (result.kind == cgValue_Addr) {
+				TB_CharUnits align = cast(TB_CharUnits)type_align_of(result.type);
+				final_res = tb_inst_load(p->func, dt, result.node, align, false);
+			} else {
+				GB_ASSERT(result.kind == cgValue_Value);
+				TB_DataType st = result.node->dt;
+				GB_ASSERT(st.type == dt.type);
+				if (st.raw == dt.raw) {
+					final_res = result.node;
+				} else if (st.type == TB_INT && st.data == 1) {
+					final_res = tb_inst_zxt(p->func, result.node, dt);
+				} else {
+					final_res = tb_inst_bitcast(p->func, result.node, dt);
+				}
+			}
+			GB_ASSERT(final_res != nullptr);
+
+			tb_inst_ret(p->func, 1, &final_res);
+			return;
+		}
+
+	} else {
+		GB_ASSERT(!is_calling_convention_odin(p->type->Proc.calling_convention));
+
+		if (p->return_by_ptr) {
+			Entity *e = tuple->variables[return_count-1];
+			TB_Node *ret_ptr = tb_inst_param(p->func, 0);
+			cgValue ptr = cg_value(ret_ptr, alloc_type_pointer(e->type));
+			cg_emit_store(p, ptr, results[return_count-1]);
+
+			tb_inst_ret(p->func, 0, nullptr);
+			return;
+		} else {
+			GB_ASSERT(p->proto->return_count == 1);
+			TB_DataType dt = TB_PROTOTYPE_RETURNS(p->proto)->dt;
+			if (results.count == 1) {
+				cgValue result = results[0];
+				result = cg_flatten_value(p, result);
+
+				TB_Node *final_res = nullptr;
+				if (result.kind == cgValue_Addr) {
+					TB_CharUnits align = cast(TB_CharUnits)type_align_of(result.type);
+					final_res = tb_inst_load(p->func, dt, result.node, align, false);
+				} else {
+					GB_ASSERT(result.kind == cgValue_Value);
+					TB_DataType st = result.node->dt;
+					GB_ASSERT(st.type == dt.type);
+					if (st.raw == dt.raw) {
+						final_res = result.node;
+					} else if (st.type == TB_INT && st.data == 1) {
+						final_res = tb_inst_zxt(p->func, result.node, dt);
+					} else {
+						final_res = tb_inst_bitcast(p->func, result.node, dt);
+					}
+				}
+
+				GB_ASSERT(final_res != nullptr);
+
+				tb_inst_ret(p->func, 1, &final_res);
+				return;
+			} else {
+				GB_ASSERT_MSG(results.count == 1, "TODO(bill): multi-return values for the return");
+				return;
+			}
+		}
+
+	}
+}
+
+
+gb_internal void cg_build_return_stmt(cgProcedure *p, Slice<Ast *> const &return_results) {
+	TypeTuple *tuple  = &p->type->Proc.results->Tuple;
+	isize return_count = p->type->Proc.result_count;
+
+	if (return_count == 0) {
+		tb_inst_ret(p->func, 0, nullptr);
+		return;
+	}
+	TEMPORARY_ALLOCATOR_GUARD();
+
+	auto results = array_make<cgValue>(temporary_allocator(), 0, return_count);
+
+	if (return_results.count != 0) {
+		for (isize i = 0; i < return_results.count; i++) {
+			cgValue res = cg_build_expr(p, return_results[i]);
+			cg_append_tuple_values(p, &results, res);
+		}
+	} else {
+		for_array(i, tuple->variables) {
+			Entity *e = tuple->variables[i];
+			cgAddr addr = map_must_get(&p->variable_map, e);
+			cgValue res = cg_addr_load(p, addr);
+			array_add(&results, res);
+		}
+	}
+	GB_ASSERT(results.count == return_count);
+
+	if (return_results.count != 0 && p->type->Proc.has_named_results) {
+		// NOTE(bill): store the named values before returning
+		for_array(i, tuple->variables) {
+			Entity *e = tuple->variables[i];
+			cgAddr addr = map_must_get(&p->variable_map, e);
+			cg_addr_store(p, addr, results[i]);
+		}
+	}
+	for_array(i, tuple->variables) {
+		Entity *e = tuple->variables[i];
+		results[i] = cg_emit_conv(p, results[i], e->type);
+	}
+
+	cg_build_return_stmt_internal(p, slice_from_array(results));
+}
+
+gb_internal void cg_build_if_stmt(cgProcedure *p, Ast *node) {
+	ast_node(is, IfStmt, node);
+	cg_scope_open(p, is->scope); // Scope #1
+	defer (cg_scope_close(p, cgDeferExit_Default, nullptr));
+
+	if (is->init != nullptr) {
+		TB_Node *init = cg_control_region(p, "if_init");
+		cg_emit_goto(p, init);
+		tb_inst_set_control(p->func, init);
+		cg_build_stmt(p, is->init);
+	}
+
+	TB_Node *then  = cg_control_region(p, "if_then");
+	TB_Node *done  = cg_control_region(p, "if_done");
+	TB_Node *else_ = done;
+	if (is->else_stmt != nullptr) {
+		else_ = cg_control_region(p, "if_else");
+	}
+
+	cgValue cond = cg_build_cond(p, is->cond, then, else_);
+	gb_unused(cond);
+
+	if (is->label != nullptr) {
+		cgTargetList *tl = cg_push_target_list(p, is->label, done, nullptr, nullptr);
+		tl->is_block = true;
+	}
+
+	// TODO(bill): should we do a constant check?
+	// Which philosophy are we following?
+	// - IR represents what the code represents (probably this)
+	// - IR represents what the code executes
+
+	tb_inst_set_control(p->func, then);
+
+	cg_build_stmt(p, is->body);
+
+	cg_emit_goto(p, done);
+
+	if (is->else_stmt != nullptr) {
+		tb_inst_set_control(p->func, else_);
+
+		cg_scope_open(p, scope_of_node(is->else_stmt));
+		cg_build_stmt(p, is->else_stmt);
+		cg_scope_close(p, cgDeferExit_Default, nullptr);
+
+		cg_emit_goto(p, done);
+	}
+
+	tb_inst_set_control(p->func, done);
+}
+
+gb_internal void cg_build_for_stmt(cgProcedure *p, Ast *node) {
+	ast_node(fs, ForStmt, node);
+
+	cg_scope_open(p, fs->scope);
+	defer (cg_scope_close(p, cgDeferExit_Default, nullptr));
+
+	if (fs->init != nullptr) {
+		TB_Node *init = cg_control_region(p, "for_init");
+		cg_emit_goto(p, init);
+		tb_inst_set_control(p->func, init);
+		cg_build_stmt(p, fs->init);
+	}
+	TB_Node *body = cg_control_region(p, "for_body");
+	TB_Node *done = cg_control_region(p, "for_done");
+	TB_Node *loop = body;
+	if (fs->cond != nullptr) {
+		loop = cg_control_region(p, "for_loop");
+	}
+	TB_Node *post = loop;
+	if (fs->post != nullptr) {
+		post = cg_control_region(p, "for_post");
+	}
+
+	cg_emit_goto(p, loop);
+	tb_inst_set_control(p->func, loop);
+
+	if (loop != body) {
+		cg_build_cond(p, fs->cond, body, done);
+		tb_inst_set_control(p->func, body);
+	}
+
+	cg_push_target_list(p, fs->label, done, post, nullptr);
+	cg_build_stmt(p, fs->body);
+	cg_pop_target_list(p);
+
+	cg_emit_goto(p, post);
+
+	if (fs->post != nullptr) {
+		tb_inst_set_control(p->func, post);
+		cg_build_stmt(p, fs->post);
+		cg_emit_goto(p, loop);
+	}
+	tb_inst_set_control(p->func, done);
+}
+
+
+gb_internal Ast *cg_strip_and_prefix(Ast *ident) {
+	if (ident != nullptr) {
+		if (ident->kind == Ast_UnaryExpr && ident->UnaryExpr.op.kind == Token_And) {
+			ident = ident->UnaryExpr.expr;
+		}
+		GB_ASSERT(ident->kind == Ast_Ident);
+	}
+	return ident;
+}
+
+gb_internal void cg_emit_increment(cgProcedure *p, cgValue addr) {
+	GB_ASSERT(is_type_pointer(addr.type));
+	Type *type = type_deref(addr.type);
+	cgValue v_one = cg_const_value(p, type, exact_value_i64(1));
+	cg_emit_store(p, addr, cg_emit_arith(p, Token_Add, cg_emit_load(p, addr), v_one, type));
+
+}
+
+gb_internal void cg_range_stmt_store_val(cgProcedure *p, Ast *stmt_val, cgValue const &value) {
+	Entity *e = entity_of_node(stmt_val);
+	if (e == nullptr) {
+		return;
+	}
+
+	if (e->flags & EntityFlag_Value) {
+		if (value.kind == cgValue_Addr) {
+			cgValue ptr = cg_address_from_load_or_generate_local(p, value);
+			cg_add_entity(p->module, e, ptr);
+			return;
+		}
+	}
+
+	cgAddr addr = cg_add_local(p, e->type, e, false);
+	cg_addr_store(p, addr, value);
+	return;
+}
+
+gb_internal void cg_build_range_stmt_interval(cgProcedure *p, AstBinaryExpr *node,
+                                              AstRangeStmt *rs, Scope *scope) {
+	bool ADD_EXTRA_WRAPPING_CHECK = true;
+
+	cg_scope_open(p, scope);
+
+	Ast *val0 = rs->vals.count > 0 ? cg_strip_and_prefix(rs->vals[0]) : nullptr;
+	Ast *val1 = rs->vals.count > 1 ? cg_strip_and_prefix(rs->vals[1]) : nullptr;
+	Type *val0_type = nullptr;
+	Type *val1_type = nullptr;
+	if (val0 != nullptr && !is_blank_ident(val0)) {
+		val0_type = type_of_expr(val0);
+	}
+	if (val1 != nullptr && !is_blank_ident(val1)) {
+		val1_type = type_of_expr(val1);
+	}
+
+	TokenKind op = Token_Lt;
+	switch (node->op.kind) {
+	case Token_Ellipsis:  op = Token_LtEq; break;
+	case Token_RangeFull: op = Token_LtEq; break;
+	case Token_RangeHalf: op = Token_Lt;  break;
+	default: GB_PANIC("Invalid interval operator"); break;
+	}
+
+
+	cgValue lower = cg_build_expr(p, node->left);
+	cgValue upper = {}; // initialized each time in the loop
+
+	cgAddr value;
+	if (val0_type != nullptr) {
+		value = cg_add_local(p, val0_type, entity_of_node(val0), false);
+	} else {
+		value = cg_add_local(p, lower.type, nullptr, false);
+	}
+	cg_addr_store(p, value, lower);
+
+	cgAddr index;
+	if (val1_type != nullptr) {
+		index = cg_add_local(p, val1_type, entity_of_node(val1), false);
+	} else {
+		index = cg_add_local(p, t_int, nullptr, false);
+	}
+	cg_addr_store(p, index, cg_const_int(p, t_int, 0));
+
+	TB_Node *loop = cg_control_region(p, "for_interval_loop");
+	TB_Node *body = cg_control_region(p, "for_interval_body");
+	TB_Node *done = cg_control_region(p, "for_interval_done");
+
+	cg_emit_goto(p, loop);
+	tb_inst_set_control(p->func, loop);
+
+	upper = cg_build_expr(p, node->right);
+	cgValue curr_value = cg_addr_load(p, value);
+	cgValue cond = cg_emit_comp(p, op, curr_value, upper);
+	cg_emit_if(p, cond, body, done);
+	tb_inst_set_control(p->func, body);
+
+	cgValue val = cg_addr_load(p, value);
+	cgValue idx = cg_addr_load(p, index);
+
+	if (val0_type) cg_range_stmt_store_val(p, val0, val);
+	if (val1_type) cg_range_stmt_store_val(p, val1, idx);
+
+
+	{
+		// NOTE: this check block will most likely be optimized out, and is here
+		// to make this code easier to read
+		TB_Node *check = nullptr;
+		TB_Node *post = cg_control_region(p, "for_interval_post");
+
+		TB_Node *continue_block = post;
+
+		if (ADD_EXTRA_WRAPPING_CHECK &&
+		    op == Token_LtEq) {
+			check = cg_control_region(p, "for_interval_check");
+			continue_block = check;
+		}
+
+		cg_push_target_list(p, rs->label, done, continue_block, nullptr);
+
+		cg_build_stmt(p, rs->body);
+
+		cg_scope_close(p, cgDeferExit_Default, nullptr);
+		cg_pop_target_list(p);
+
+		if (check != nullptr) {
+			cg_emit_goto(p, check);
+			tb_inst_set_control(p->func, check);
+
+			cgValue check_cond = cg_emit_comp(p, Token_NotEq, curr_value, upper);
+			cg_emit_if(p, check_cond, post, done);
+		} else {
+			cg_emit_goto(p, post);
+		}
+
+		tb_inst_set_control(p->func, post);
+		cg_emit_increment(p, value.addr);
+		cg_emit_increment(p, index.addr);
+		cg_emit_goto(p, loop);
+	}
+
+	tb_inst_set_control(p->func, done);
+}
+
+gb_internal void cg_build_range_stmt_indexed(cgProcedure *p, cgValue expr, Type *val_type, cgValue count_ptr,
+                                             cgValue *val_, cgValue *idx_, TB_Node **loop_, TB_Node **done_,
+                                             bool is_reverse) {
+	cgValue count = {};
+	Type *expr_type = base_type(type_deref(expr.type));
+	switch (expr_type->kind) {
+	case Type_Array:
+		count = cg_const_int(p, t_int, expr_type->Array.count);
+		break;
+	}
+
+	cgValue val = {};
+	cgValue idx = {};
+	TB_Node *loop = nullptr;
+	TB_Node *done = nullptr;
+	TB_Node *body = nullptr;
+
+	loop = cg_control_region(p, "for_index_loop");
+	body = cg_control_region(p, "for_index_body");
+	done = cg_control_region(p, "for_index_done");
+
+	cgAddr index = cg_add_local(p, t_int, nullptr, false);
+
+	if (!is_reverse) {
+		/*
+			for x, i in array {
+				...
+			}
+
+			i := -1
+			for {
+				i += 1
+				if !(i < len(array)) {
+					break
+				}
+				#no_bounds_check x := array[i]
+				...
+			}
+		*/
+
+		cg_addr_store(p, index, cg_const_int(p, t_int, cast(u64)-1));
+
+		cg_emit_goto(p, loop);
+		tb_inst_set_control(p->func, loop);
+
+		cgValue incr = cg_emit_arith(p, Token_Add, cg_addr_load(p, index), cg_const_int(p, t_int, 1), t_int);
+		cg_addr_store(p, index, incr);
+
+		if (count.node == nullptr) {
+			GB_ASSERT(count_ptr.node != nullptr);
+			count = cg_emit_load(p, count_ptr);
+		}
+		cgValue cond = cg_emit_comp(p, Token_Lt, incr, count);
+		cg_emit_if(p, cond, body, done);
+	} else {
+		// NOTE(bill): REVERSED LOGIC
+		/*
+			#reverse for x, i in array {
+				...
+			}
+
+			i := len(array)
+			for {
+				i -= 1
+				if i < 0 {
+					break
+				}
+				#no_bounds_check x := array[i]
+				...
+			}
+		*/
+
+		if (count.node == nullptr) {
+			GB_ASSERT(count_ptr.node != nullptr);
+			count = cg_emit_load(p, count_ptr);
+		}
+		count = cg_emit_conv(p, count, t_int);
+		cg_addr_store(p, index, count);
+
+		cg_emit_goto(p, loop);
+		tb_inst_set_control(p->func, loop);
+
+		cgValue incr = cg_emit_arith(p, Token_Sub, cg_addr_load(p, index), cg_const_int(p, t_int, 1), t_int);
+		cg_addr_store(p, index, incr);
+
+		cgValue anti_cond = cg_emit_comp(p, Token_Lt, incr, cg_const_int(p, t_int, 0));
+		cg_emit_if(p, anti_cond, done, body);
+	}
+
+	tb_inst_set_control(p->func, body);
+
+	idx = cg_addr_load(p, index);
+	switch (expr_type->kind) {
+	case Type_Array: {
+		if (val_type != nullptr) {
+			val = cg_emit_load(p, cg_emit_array_ep(p, expr, idx));
+		}
+		break;
+	}
+	case Type_EnumeratedArray: {
+		if (val_type != nullptr) {
+			val = cg_emit_load(p, cg_emit_array_ep(p, expr, idx));
+			// NOTE(bill): Override the idx value for the enumeration
+			Type *index_type = expr_type->EnumeratedArray.index;
+			if (compare_exact_values(Token_NotEq, *expr_type->EnumeratedArray.min_value, exact_value_u64(0))) {
+				idx = cg_emit_arith(p, Token_Add, idx, cg_const_value(p, index_type, *expr_type->EnumeratedArray.min_value), index_type);
+			}
+		}
+		break;
+	}
+	case Type_Slice: {
+		if (val_type != nullptr) {
+			cgValue elem = cg_builtin_raw_data(p, expr);
+			val = cg_emit_load(p, cg_emit_ptr_offset(p, elem, idx));
+		}
+		break;
+	}
+	case Type_DynamicArray: {
+		if (val_type != nullptr) {
+			cgValue elem = cg_emit_struct_ep(p, expr, 0);
+			elem = cg_emit_load(p, elem);
+			val = cg_emit_load(p, cg_emit_ptr_offset(p, elem, idx));
+		}
+		break;
+	}
+	case Type_Struct: {
+		GB_ASSERT(is_type_soa_struct(expr_type));
+		break;
+	}
+
+	default:
+		GB_PANIC("Cannot do range_indexed of %s", type_to_string(expr_type));
+		break;
+	}
+
+	if (val_)  *val_  = val;
+	if (idx_)  *idx_  = idx;
+	if (loop_) *loop_ = loop;
+	if (done_) *done_ = done;
+
+}
+
+gb_internal void cg_build_range_stmt(cgProcedure *p, Ast *node) {
+	ast_node(rs, RangeStmt, node);
+
+	Ast *expr = unparen_expr(rs->expr);
+
+	if (is_ast_range(expr)) {
+		cg_build_range_stmt_interval(p, &expr->BinaryExpr, rs, rs->scope);
+		return;
+	}
+
+	Type *expr_type = type_of_expr(expr);
+	if (expr_type != nullptr) {
+		Type *et = base_type(type_deref(expr_type));
+	 	if (is_type_soa_struct(et)) {
+	 		GB_PANIC("TODO(bill): #soa array range statements");
+			// cg_build_range_stmt_struct_soa(p, rs, scope);
+			return;
+		}
+	}
+
+	cg_scope_open(p, rs->scope);
+
+
+	Ast *val0 = rs->vals.count > 0 ? cg_strip_and_prefix(rs->vals[0]) : nullptr;
+	Ast *val1 = rs->vals.count > 1 ? cg_strip_and_prefix(rs->vals[1]) : nullptr;
+	Type *val0_type = nullptr;
+	Type *val1_type = nullptr;
+	if (val0 != nullptr && !is_blank_ident(val0)) {
+		val0_type = type_of_expr(val0);
+	}
+	if (val1 != nullptr && !is_blank_ident(val1)) {
+		val1_type = type_of_expr(val1);
+	}
+
+	cgValue val = {};
+	cgValue key = {};
+	TB_Node *loop = nullptr;
+	TB_Node *done = nullptr;
+	bool is_map = false;
+	TypeAndValue tav = type_and_value_of_expr(expr);
+
+	if (tav.mode == Addressing_Type) {
+		GB_PANIC("TODO(bill): range statement over enum type");
+	} else {
+		Type *expr_type = type_of_expr(expr);
+		Type *et = base_type(type_deref(expr_type));
+		switch (et->kind) {
+		case Type_Map: {
+			is_map = true;
+			cgValue map = cg_build_addr_ptr(p, expr);
+			if (is_type_pointer(type_deref(map.type))) {
+				map = cg_emit_load(p, map);
+			}
+			GB_PANIC("TODO(bill): cg_build_range_map");
+			// cg_build_range_map(p, map, val1_type, &val, &key, &loop, &done);
+			break;
+		}
+		case Type_Array: {
+			cgValue array = cg_build_addr_ptr(p, expr);
+			if (is_type_pointer(type_deref(array.type))) {
+				array = cg_emit_load(p, array);
+			}
+			cgAddr count_ptr = cg_add_local(p, t_int, nullptr, false);
+			cg_addr_store(p, count_ptr, cg_const_int(p, t_int, et->Array.count));
+			cg_build_range_stmt_indexed(p, array, val0_type, count_ptr.addr, &val, &key, &loop, &done, rs->reverse);
+			break;
+		}
+		case Type_EnumeratedArray: {
+			cgValue array = cg_build_addr_ptr(p, expr);
+			if (is_type_pointer(type_deref(array.type))) {
+				array = cg_emit_load(p, array);
+			}
+			cgAddr count_ptr = cg_add_local(p, t_int, nullptr, false);
+			cg_addr_store(p, count_ptr, cg_const_int(p, t_int, et->EnumeratedArray.count));
+			cg_build_range_stmt_indexed(p, array, val0_type, count_ptr.addr, &val, &key, &loop, &done, rs->reverse);
+			break;
+		}
+		case Type_DynamicArray: {
+			cgValue count_ptr = {};
+			cgValue array = cg_build_addr_ptr(p, expr);
+			if (is_type_pointer(type_deref(array.type))) {
+				array = cg_emit_load(p, array);
+			}
+			count_ptr = cg_emit_struct_ep(p, array, 1);
+			cg_build_range_stmt_indexed(p, array, val0_type, count_ptr, &val, &key, &loop, &done, rs->reverse);
+			break;
+		}
+		case Type_Slice: {
+			cgValue count_ptr = {};
+			cgValue slice = cg_build_expr(p, expr);
+			if (is_type_pointer(slice.type)) {
+				count_ptr = cg_emit_struct_ep(p, slice, 1);
+				slice = cg_emit_load(p, slice);
+			} else {
+				count_ptr = cg_add_local(p, t_int, nullptr, false).addr;
+				cg_emit_store(p, count_ptr, cg_builtin_len(p, slice));
+			}
+			cg_build_range_stmt_indexed(p, slice, val0_type, count_ptr, &val, &key, &loop, &done, rs->reverse);
+			break;
+		}
+		case Type_Basic: {
+			cgValue string = cg_build_expr(p, expr);
+			if (is_type_pointer(string.type)) {
+				string = cg_emit_load(p, string);
+			}
+			if (is_type_untyped(expr_type)) {
+				cgAddr s = cg_add_local(p, default_type(string.type), nullptr, false);
+				cg_addr_store(p, s, string);
+				string = cg_addr_load(p, s);
+			}
+			Type *t = base_type(string.type);
+			GB_ASSERT(!is_type_cstring(t));
+			GB_PANIC("TODO(bill): cg_build_range_string");
+			// cg_build_range_string(p, string, val0_type, &val, &key, &loop, &done, rs->reverse);
+			break;
+		}
+		case Type_Tuple:
+			GB_PANIC("TODO(bill): cg_build_range_tuple");
+			// cg_build_range_tuple(p, expr, val0_type, val1_type, &val, &key, &loop, &done);
+			break;
+		default:
+			GB_PANIC("Cannot range over %s", type_to_string(expr_type));
+			break;
+		}
+	}
+
+	if (is_map) {
+		if (val0_type) cg_range_stmt_store_val(p, val0, key);
+		if (val1_type) cg_range_stmt_store_val(p, val1, val);
+	} else {
+		if (val0_type) cg_range_stmt_store_val(p, val0, val);
+		if (val1_type) cg_range_stmt_store_val(p, val1, key);
+	}
+
+	cg_push_target_list(p, rs->label, done, loop, nullptr);
+
+	cg_build_stmt(p, rs->body);
+
+	cg_scope_close(p, cgDeferExit_Default, nullptr);
+	cg_pop_target_list(p);
+	cg_emit_goto(p, loop);
+	tb_inst_set_control(p->func, done);
+}
+
+gb_internal bool cg_switch_stmt_can_be_trivial_jump_table(AstSwitchStmt *ss) {
+	if (ss->tag == nullptr) {
+		return false;
+	}
+	bool is_typeid = false;
+	TypeAndValue tv = type_and_value_of_expr(ss->tag);
+	if (is_type_integer(core_type(tv.type))) {
+		if (type_size_of(tv.type) > 8) {
+			return false;
+		}
+		// okay
+	} else if (is_type_typeid(tv.type)) {
+		// okay
+		is_typeid = true;
+	} else {
+		return false;
+	}
+
+	ast_node(body, BlockStmt, ss->body);
+	for (Ast *clause : body->stmts) {
+		ast_node(cc, CaseClause, clause);
+
+		if (cc->list.count == 0) {
+			continue;
+		}
+
+		for (Ast *expr : cc->list) {
+			expr = unparen_expr(expr);
+			if (is_ast_range(expr)) {
+				return false;
+			}
+			if (expr->tav.mode == Addressing_Type) {
+				GB_ASSERT(is_typeid);
+				continue;
+			}
+			tv = type_and_value_of_expr(expr);
+			if (tv.mode != Addressing_Constant) {
+				return false;
+			}
+			if (!is_type_integer(core_type(tv.type))) {
+				return false;
+			}
+		}
+
+	}
+
+	return true;
+}
+
+
+gb_internal void cg_build_switch_stmt(cgProcedure *p, Ast *node) {
+	ast_node(ss, SwitchStmt, node);
+	cg_scope_open(p, ss->scope);
+
+	if (ss->init != nullptr) {
+		cg_build_stmt(p, ss->init);
+	}
+	cgValue tag = {};
+	if (ss->tag != nullptr) {
+		tag = cg_build_expr(p, ss->tag);
+	} else {
+		tag = cg_const_bool(p, t_bool, true);
+	}
+
+	TB_Node *done = cg_control_region(p, "switch_done");
+
+	ast_node(body, BlockStmt, ss->body);
+
+	isize case_count = body->stmts.count;
+	Slice<Ast *> default_stmts = {};
+	TB_Node *default_fall  = nullptr;
+	TB_Node *default_block = nullptr;
+	Scope *  default_scope = nullptr;
+	TB_Node *fall = nullptr;
+
+
+	auto body_regions = slice_make<TB_Node *>(permanent_allocator(), body->stmts.count);
+	auto body_scopes = slice_make<Scope *>(permanent_allocator(), body->stmts.count);
+	for_array(i, body->stmts) {
+		Ast *clause = body->stmts[i];
+		ast_node(cc, CaseClause, clause);
+
+		body_regions[i] = cg_control_region(p, cc->list.count == 0 ? "switch_default_body" : "switch_case_body");
+		body_scopes[i] = cc->scope;
+		if (cc->list.count == 0) {
+			default_block = body_regions[i];
+			default_scope = cc->scope;
+		}
+	}
+
+	bool is_trivial = cg_switch_stmt_can_be_trivial_jump_table(ss);
+	if (is_trivial) {
+		isize key_count = 0;
+		for (Ast *clause : body->stmts) {
+			ast_node(cc, CaseClause, clause);
+			key_count += cc->list.count;
+		}
+		TB_SwitchEntry *keys = gb_alloc_array(temporary_allocator(), TB_SwitchEntry, key_count);
+		isize key_index = 0;
+		for_array(i, body->stmts) {
+			Ast *clause = body->stmts[i];
+			ast_node(cc, CaseClause, clause);
+
+			TB_Node *region = body_regions[i];
+			for (Ast *expr : cc->list) {
+				i64 key = 0;
+				expr = unparen_expr(expr);
+				GB_ASSERT(!is_ast_range(expr));
+				if (expr->tav.mode == Addressing_Type) {
+					Type *type = expr->tav.value.value_typeid;
+					if (type == nullptr || type == t_invalid) {
+						type = expr->tav.type;
+					}
+					key = cg_typeid_as_u64(p->module, type);
+				} else {
+					auto tv = type_and_value_of_expr(expr);
+					GB_ASSERT(tv.mode == Addressing_Constant);
+					key = exact_value_to_i64(tv.value);
+				}
+				keys[key_index++] = {key, region};
+			}
+		}
+		GB_ASSERT(key_index == key_count);
+
+		TB_Node *end_block = done;
+		if (default_block) {
+			end_block = default_block;
+		}
+
+		TB_DataType dt = cg_data_type(tag.type);
+		GB_ASSERT(tag.kind == cgValue_Value);
+		GB_ASSERT(!TB_IS_VOID_TYPE(dt));
+
+		tb_inst_branch(p->func, dt, tag.node, end_block, key_count, keys);
+	}
+
+	for_array(i, body->stmts) {
+		Ast *clause = body->stmts[i];
+		ast_node(cc, CaseClause, clause);
+
+		TB_Node *body_region = body_regions[i];
+		Scope *body_scope = body_scopes[i];
+		fall = done;
+		if (i+1 < case_count) {
+			fall = body_regions[i+1];
+		}
+
+		if (cc->list.count == 0) {
+			// default case
+			default_stmts = cc->stmts;
+			default_fall  = fall;
+			GB_ASSERT(default_block == body_region);
+			continue;
+		}
+
+		TB_Node *next_cond = nullptr;
+		if (!is_trivial) for (Ast *expr : cc->list) {
+			expr = unparen_expr(expr);
+
+			next_cond = cg_control_region(p, "switch_case_next");
+
+			cgValue cond = {};
+			if (is_ast_range(expr)) {
+				ast_node(ie, BinaryExpr, expr);
+				TokenKind op = Token_Invalid;
+				switch (ie->op.kind) {
+				case Token_Ellipsis:  op = Token_LtEq; break;
+				case Token_RangeFull: op = Token_LtEq; break;
+				case Token_RangeHalf: op = Token_Lt;   break;
+				default: GB_PANIC("Invalid interval operator"); break;
+				}
+				cgValue lhs = cg_build_expr(p, ie->left);
+				cgValue rhs = cg_build_expr(p, ie->right);
+
+				cgValue cond_lhs = cg_emit_comp(p, Token_LtEq, lhs, tag);
+				cgValue cond_rhs = cg_emit_comp(p, op, tag, rhs);
+				cond = cg_emit_arith(p, Token_And, cond_lhs, cond_rhs, t_bool);
+			} else {
+				if (expr->tav.mode == Addressing_Type) {
+					GB_ASSERT(is_type_typeid(tag.type));
+					cgValue e = cg_typeid(p, expr->tav.type);
+					e = cg_emit_conv(p, e, tag.type);
+					cond = cg_emit_comp(p, Token_CmpEq, tag, e);
+				} else {
+					cond = cg_emit_comp(p, Token_CmpEq, tag, cg_build_expr(p, expr));
+				}
+			}
+
+			GB_ASSERT(cond.kind == cgValue_Value);
+			tb_inst_if(p->func, cond.node, body_region, next_cond);
+			tb_inst_set_control(p->func, next_cond);
+		}
+
+		tb_inst_set_control(p->func, body_region);
+
+		cg_push_target_list(p, ss->label, done, nullptr, fall);
+		cg_scope_open(p, body_scope);
+		cg_build_stmt_list(p, cc->stmts);
+		cg_scope_close(p, cgDeferExit_Default, body_region);
+		cg_pop_target_list(p);
+
+		cg_emit_goto(p, done);
+		tb_inst_set_control(p->func, next_cond);
+	}
+
+	if (default_block != nullptr) {
+		if (!is_trivial) {
+			cg_emit_goto(p, default_block);
+		}
+		tb_inst_set_control(p->func, default_block);
+
+		cg_push_target_list(p, ss->label, done, nullptr, default_fall);
+		cg_scope_open(p, default_scope);
+		cg_build_stmt_list(p, default_stmts);
+		cg_scope_close(p, cgDeferExit_Default, default_block);
+		cg_pop_target_list(p);
+	}
+
+
+	cg_emit_goto(p, done);
+	tb_inst_set_control(p->func, done);
+
+	cg_scope_close(p, cgDeferExit_Default, done);
+}
+
+gb_internal void cg_build_type_switch_stmt(cgProcedure *p, Ast *node) {
+	ast_node(ss, TypeSwitchStmt, node);
+
+	TB_Node *done_region = cg_control_region(p, "typeswitch_done");
+	TB_Node *else_region = done_region;
+	TB_Node *default_region = nullptr;
+	isize num_cases = 0;
+
+	cg_scope_open(p, ss->scope);
+	defer (cg_scope_close(p, cgDeferExit_Default, done_region));
+
+	ast_node(as, AssignStmt, ss->tag);
+	GB_ASSERT(as->lhs.count == 1);
+	GB_ASSERT(as->rhs.count == 1);
+
+	cgValue parent = cg_build_expr(p, as->rhs[0]);
+	bool is_parent_ptr = is_type_pointer(parent.type);
+	Type *parent_base_type = type_deref(parent.type);
+	gb_unused(parent_base_type);
+
+	TypeSwitchKind switch_kind = check_valid_type_switch_type(parent.type);
+	GB_ASSERT(switch_kind != TypeSwitch_Invalid);
+
+
+	cgValue parent_value = parent;
+
+	cgValue parent_ptr = parent;
+	if (!is_parent_ptr) {
+		parent_ptr = cg_address_from_load_or_generate_local(p, parent);
+	}
+
+	cgValue tag = {};
+	cgValue union_data = {};
+	if (switch_kind == TypeSwitch_Union) {
+		union_data = cg_emit_conv(p, parent_ptr, t_rawptr);
+		Type *union_type = type_deref(parent_ptr.type);
+		if (is_type_union_maybe_pointer(union_type)) {
+			tag = cg_emit_conv(p, cg_emit_comp_against_nil(p, Token_NotEq, union_data), t_int);
+		} else if (union_tag_size(union_type) == 0) {
+			tag = {}; // there is no tag for a zero sized union
+		} else {
+			cgValue tag_ptr = cg_emit_union_tag_ptr(p, parent_ptr);
+			tag = cg_emit_load(p, tag_ptr);
+		}
+	} else if (switch_kind == TypeSwitch_Any) {
+		tag = cg_emit_load(p, cg_emit_struct_ep(p, parent_ptr, 1));
+	} else {
+		GB_PANIC("Unknown switch kind");
+	}
+
+	ast_node(body, BlockStmt, ss->body);
+
+	for (Ast *clause : body->stmts) {
+		ast_node(cc, CaseClause, clause);
+		num_cases += cc->list.count;
+		if (cc->list.count == 0) {
+			GB_ASSERT(default_region == nullptr);
+			default_region = cg_control_region(p, "typeswitch_default_body");
+			else_region = default_region;
+		}
+	}
+
+	bool all_by_reference = false;
+	for (Ast *clause : body->stmts) {
+		ast_node(cc, CaseClause, clause);
+		if (cc->list.count != 1) {
+			continue;
+		}
+		Entity *case_entity = implicit_entity_of_node(clause);
+		all_by_reference |= (case_entity->flags & EntityFlag_Value) == 0;
+		break;
+	}
+
+	TB_Node *backing_ptr = nullptr;
+	if (!all_by_reference) {
+		bool variants_found = false;
+		i64 max_size = 0;
+		i64 max_align = 1;
+		for (Ast *clause : body->stmts) {
+			ast_node(cc, CaseClause, clause);
+			if (cc->list.count != 1) {
+				continue;
+			}
+			Entity *case_entity = implicit_entity_of_node(clause);
+			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) {
+			backing_ptr = tb_inst_local(p->func, cast(TB_CharUnits)max_size, cast(TB_CharUnits)max_align);
+		}
+	}
+
+	TEMPORARY_ALLOCATOR_GUARD();
+	TB_Node **control_regions = gb_alloc_array(temporary_allocator(), TB_Node *, body->stmts.count);
+	TB_SwitchEntry *switch_entries = gb_alloc_array(temporary_allocator(), TB_SwitchEntry, num_cases);
+
+	isize case_index = 0;
+	for_array(i, body->stmts) {
+		Ast *clause = body->stmts[i];
+		ast_node(cc, CaseClause, clause);
+		if (cc->list.count == 0) {
+			control_regions[i] = default_region;
+			continue;
+		}
+
+		TB_Node *region = cg_control_region(p, "typeswitch_body");
+		control_regions[i] = region;
+
+		for (Ast *type_expr : cc->list) {
+			Type *case_type = type_of_expr(type_expr);
+			i64 key = -1;
+			if (switch_kind == TypeSwitch_Union) {
+				Type *ut = base_type(type_deref(parent.type));
+				if (is_type_untyped_nil(case_type)) {
+					key = 0;
+				} else {
+					key = union_variant_index(ut, case_type);
+				}
+			} else if (switch_kind == TypeSwitch_Any) {
+				if (is_type_untyped_nil(case_type)) {
+					key = 0;
+				} else {
+					key = cast(i64)cg_typeid_as_u64(p->module, case_type);
+				}
+			}
+			GB_ASSERT(key >= 0);
+
+			switch_entries[case_index++] = TB_SwitchEntry{key, region};
+		}
+	}
+
+	GB_ASSERT(case_index == num_cases);
+
+	{
+		TB_DataType dt = {};
+		TB_Node *key = nullptr;
+		if (type_size_of(parent_base_type) == 0) {
+			GB_ASSERT(tag.node == nullptr);
+			key = tb_inst_bool(p->func, false);
+			dt = cg_data_type(t_bool);
+		} else {
+			GB_ASSERT(tag.kind == cgValue_Value && tag.node != nullptr);
+			dt = cg_data_type(tag.type);
+			key = tag.node;
+		}
+
+		GB_ASSERT(!TB_IS_VOID_TYPE(dt));
+		tb_inst_branch(p->func, dt, key, else_region, num_cases, switch_entries);
+	}
+
+
+	for_array(i, body->stmts) {
+		Ast *clause = body->stmts[i];
+		ast_node(cc, CaseClause, clause);
+
+		bool saw_nil = false;
+		for (Ast *type_expr : cc->list) {
+			Type *case_type = type_of_expr(type_expr);
+			if (is_type_untyped_nil(case_type)) {
+				saw_nil = true;
+			}
+		}
+
+		Entity *case_entity = implicit_entity_of_node(clause);
+		bool by_reference = (case_entity->flags & EntityFlag_Value) == 0;
+
+		cg_scope_open(p, cc->scope);
+
+		TB_Node *body_region = control_regions[i];
+		tb_inst_set_control(p->func, body_region);
+
+		if (cc->list.count == 1 && !saw_nil) {
+			cgValue data = {};
+			if (switch_kind == TypeSwitch_Union) {
+				data = union_data;
+			} else if (switch_kind == TypeSwitch_Any) {
+				data = cg_emit_load(p, cg_emit_struct_ep(p, parent_ptr, 0));
+			}
+			GB_ASSERT(data.kind == cgValue_Value);
+
+			Type *ct = case_entity->type;
+			Type *ct_ptr = alloc_type_pointer(ct);
+
+			cgValue ptr = {};
+
+			if (backing_ptr) { // by value
+				GB_ASSERT(!by_reference);
+
+				i64 size = type_size_of(case_entity->type);
+				i64 align = type_align_of(case_entity->type);
+
+				// make a copy of the case value
+				tb_inst_memcpy(p->func,
+				               backing_ptr, // dst
+				               data.node,   // src
+				               tb_inst_uint(p->func, TB_TYPE_INT, size),
+				               cast(TB_CharUnits)align,
+				               false
+				);
+
+				ptr = cg_value(backing_ptr, ct_ptr);
+
+			} else { // by reference
+				GB_ASSERT(by_reference);
+				ptr = cg_emit_conv(p, data, ct_ptr);
+			}
+			GB_ASSERT(are_types_identical(case_entity->type, type_deref(ptr.type)));
+
+			cg_add_entity(p->module, case_entity, ptr);
+			String name = case_entity->token.string;
+			TB_Attrib *dbg = tb_function_attrib_variable(p->func, name.len, cast(char const *)name.text, cg_debug_type(p->module, ct));
+			tb_node_append_attrib(ptr.node, dbg);
+		} else {
+			if (case_entity->flags & EntityFlag_Value) {
+				// by value
+				cgAddr x = cg_add_local(p, case_entity->type, case_entity, false);
+				cg_addr_store(p, x, parent_value);
+			} else {
+				// by reference
+				cg_add_entity(p->module, case_entity, parent_value);
+			}
+		}
+
+		cg_push_target_list(p, ss->label, done_region, nullptr, nullptr);
+		cg_build_stmt_list(p, cc->stmts);
+		cg_scope_close(p, cgDeferExit_Default, body_region);
+		cg_pop_target_list(p);
+
+		cg_emit_goto(p, done_region);
+	}
+
+	cg_emit_goto(p, done_region);
+	tb_inst_set_control(p->func, done_region);
+}
+
+
+gb_internal void cg_build_mutable_value_decl(cgProcedure *p, Ast *node) {
+	ast_node(vd, ValueDecl, node);
+	if (!vd->is_mutable) {
+		return;
+	}
+
+	bool is_static = false;
+	for (Ast *name : vd->names) if (!is_blank_ident(name)) {
+		// NOTE(bill): Sanity check to check for the existence of the variable's Entity
+		GB_ASSERT(name->kind == Ast_Ident);
+		Entity *e = entity_of_node(name);
+		TokenPos pos = ast_token(name).pos;
+		GB_ASSERT_MSG(e != nullptr, "\n%s missing entity for %.*s", token_pos_to_string(pos), LIT(name->Ident.token.string));
+		if (e->flags & EntityFlag_Static) {
+			// NOTE(bill): If one of the entities is static, they all are
+			is_static = true;
+		}
+	}
+
+	if (is_static) {
+		for_array(i, vd->names) {
+			Ast *ident = vd->names[i];
+			GB_ASSERT(!is_blank_ident(ident));
+			Entity *e = entity_of_node(ident);
+			GB_ASSERT(e->flags & EntityFlag_Static);
+			String name = e->token.string;
+
+			String mangled_name = {};
+			{
+				gbString str = gb_string_make_length(permanent_allocator(), p->name.text, p->name.len);
+				str = gb_string_appendc(str, "-");
+				str = gb_string_append_fmt(str, ".%.*s-%llu", LIT(name), cast(long long)e->id);
+				mangled_name.text = cast(u8 *)str;
+				mangled_name.len = gb_string_length(str);
+			}
+
+			cgModule *m = p->module;
+
+			TB_DebugType *debug_type = cg_debug_type(m, e->type);
+			TB_Global *global = tb_global_create(m->mod, mangled_name.len, cast(char const *)mangled_name.text, debug_type, TB_LINKAGE_PRIVATE);
+
+			TB_ModuleSection *section = tb_module_get_data(m->mod);
+			if (e->Variable.thread_local_model != "") {
+				section = tb_module_get_tls(m->mod);
+				String model = e->Variable.thread_local_model;
+				if (model == "default") {
+					// TODO(bill): Thread Local Storage models
+				} else if (model == "localdynamic") {
+					// TODO(bill): Thread Local Storage models
+				} else if (model == "initialexec") {
+					// TODO(bill): Thread Local Storage models
+				} else if (model == "localexec") {
+					// TODO(bill): Thread Local Storage models
+				} else {
+					GB_PANIC("Unhandled thread local mode %.*s", LIT(model));
+				}
+			}
+
+			i64 max_objects = 0;
+			ExactValue value = {};
+
+			if (vd->values.count > 0) {
+				GB_ASSERT(vd->names.count == vd->values.count);
+				Ast *ast_value = vd->values[i];
+				GB_ASSERT(ast_value->tav.mode == Addressing_Constant ||
+				          ast_value->tav.mode == Addressing_Invalid);
+
+				value = ast_value->tav.value;
+				max_objects = cg_global_const_calculate_region_count(value, e->type);
+			}
+			tb_global_set_storage(m->mod, section, global, type_size_of(e->type), type_align_of(e->type), max_objects);
+
+			cg_global_const_add_region(m, value, e->type, global, 0);
+
+			TB_Node *node = tb_inst_get_symbol_address(p->func, cast(TB_Symbol *)global);
+			cgValue global_val = cg_value(node, alloc_type_pointer(e->type));
+			cg_add_entity(p->module, e, global_val);
+			cg_add_member(p->module, mangled_name, global_val);
+		}
+		return;
+	}
+
+	TEMPORARY_ALLOCATOR_GUARD();
+
+	auto inits = array_make<cgValue>(temporary_allocator(), 0, vd->values.count != 0 ? vd->names.count : 0);
+	for (Ast *rhs : vd->values) {
+		cgValue init = cg_build_expr(p, rhs);
+		cg_append_tuple_values(p, &inits, init);
+	}
+
+
+	auto lvals = slice_make<cgAddr>(temporary_allocator(), vd->names.count);
+	for_array(i, vd->names) {
+		Ast *name = vd->names[i];
+		if (!is_blank_ident(name)) {
+			Entity *e = entity_of_node(name);
+			bool zero_init = vd->values.count == 0;
+			if (vd->names.count == vd->values.count) {
+				Ast *expr = unparen_expr(vd->values[i]);
+				if (expr->kind == Ast_CompoundLit &&
+				    inits[i].kind == cgValue_Addr) {
+				    	TB_Node *ptr = inits[i].node;
+
+				    	if (e != nullptr && e->token.string.len > 0 && e->token.string != "_") {
+				    		// NOTE(bill): for debugging purposes only
+				    		String name = e->token.string;
+				    		TB_DebugType *debug_type = cg_debug_type(p->module, e->type);
+				    		tb_node_append_attrib(ptr, tb_function_attrib_variable(p->func, name.len, cast(char const *)name.text, debug_type));
+				    	}
+
+					cgAddr addr = cg_addr(inits[i]);
+					map_set(&p->variable_map, e, addr);
+					continue;
+				}
+			}
+
+			lvals[i] = cg_add_local(p, e->type, e, zero_init);
+		}
+	}
+
+
+	GB_ASSERT(vd->values.count == 0 || lvals.count == inits.count);
+	for_array(i, inits) {
+		cgAddr  lval = lvals[i];
+		cgValue init = inits[i];
+		cg_addr_store(p, lval, init);
+	}
+}
+
+
+gb_internal void cg_build_stmt(cgProcedure *p, Ast *node) {
+	Ast *prev_stmt = p->curr_stmt;
+	defer (p->curr_stmt = prev_stmt);
+	p->curr_stmt = node;
+
+	// TODO(bill): check if last instruction was a terminating one or not
+
+	cg_set_debug_pos_from_node(p, node);
+
+	u16 prev_state_flags = p->state_flags;
+	defer (p->state_flags = prev_state_flags);
+
+	if (node->state_flags != 0) {
+		u16 in = node->state_flags;
+		u16 out = p->state_flags;
+
+		if (in & StateFlag_bounds_check) {
+			out |= StateFlag_bounds_check;
+			out &= ~StateFlag_no_bounds_check;
+		} else if (in & StateFlag_no_bounds_check) {
+			out |= StateFlag_no_bounds_check;
+			out &= ~StateFlag_bounds_check;
+		}
+		if (in & StateFlag_no_type_assert) {
+			out |= StateFlag_no_type_assert;
+			out &= ~StateFlag_type_assert;
+		} else if (in & StateFlag_type_assert) {
+			out |= StateFlag_type_assert;
+			out &= ~StateFlag_no_type_assert;
+		}
+
+		p->state_flags = out;
+	}
+
+	switch (node->kind) {
+	case_ast_node(bs, EmptyStmt, node);
+	case_end;
+
+	case_ast_node(us, UsingStmt, node);
+	case_end;
+
+	case_ast_node(ws, WhenStmt, node);
+		cg_build_when_stmt(p, ws);
+	case_end;
+
+	case_ast_node(bs, BlockStmt, node);
+		TB_Node *done = nullptr;
+		if (bs->label != nullptr) {
+			done = cg_control_region(p, "block_done");
+			cgTargetList *tl = cg_push_target_list(p, bs->label, done, nullptr, nullptr);
+			tl->is_block = true;
+		}
+
+		cg_scope_open(p, bs->scope);
+		cg_build_stmt_list(p, bs->stmts);
+		cg_scope_close(p, cgDeferExit_Default, nullptr);
+
+		if (done != nullptr) {
+			cg_emit_goto(p, done);
+			tb_inst_set_control(p->func, done);
+		}
+
+		if (bs->label != nullptr) {
+			cg_pop_target_list(p);
+		}
+	case_end;
+
+	case_ast_node(vd, ValueDecl, node);
+		cg_build_mutable_value_decl(p, node);
+	case_end;
+
+	case_ast_node(bs, BranchStmt, node);
+ 		TB_Node *block = nullptr;
+
+		if (bs->label != nullptr) {
+			cgBranchRegions bb = cg_lookup_branch_regions(p, bs->label);
+			switch (bs->token.kind) {
+			case Token_break:    block = bb.break_;    break;
+			case Token_continue: block = bb.continue_; break;
+			case Token_fallthrough:
+				GB_PANIC("fallthrough cannot have a label");
+				break;
+			}
+		} else {
+			for (cgTargetList *t = p->target_list; t != nullptr && block == nullptr; t = t->prev) {
+				if (t->is_block) {
+					continue;
+				}
+
+				switch (bs->token.kind) {
+				case Token_break:       block = t->break_;       break;
+				case Token_continue:    block = t->continue_;    break;
+				case Token_fallthrough: block = t->fallthrough_; break;
+				}
+			}
+		}
+		GB_ASSERT(block != nullptr);
+
+		cg_emit_defer_stmts(p, cgDeferExit_Branch, block);
+		cg_emit_goto(p, block);
+	case_end;
+
+	case_ast_node(es, ExprStmt, node);
+		cg_build_expr(p, es->expr);
+	case_end;
+
+	case_ast_node(as, AssignStmt, node);
+		cg_build_assign_stmt(p, as);
+	case_end;
+
+	case_ast_node(rs, ReturnStmt, node);
+		cg_build_return_stmt(p, rs->results);
+	case_end;
+
+	case_ast_node(is, IfStmt, node);
+		cg_build_if_stmt(p, node);
+	case_end;
+
+	case_ast_node(fs, ForStmt, node);
+		cg_build_for_stmt(p, node);
+	case_end;
+
+	case_ast_node(rs, RangeStmt, node);
+		cg_build_range_stmt(p, node);
+	case_end;
+
+	case_ast_node(rs, UnrollRangeStmt, node);
+		GB_PANIC("TODO(bill): lb_build_unroll_range_stmt");
+		// cg_build_range_stmt(p, rs, rs->scope);
+	case_end;
+
+	case_ast_node(fs, SwitchStmt, node);
+		cg_build_switch_stmt(p, node);
+	case_end;
+
+	case_ast_node(ts, TypeSwitchStmt, node);
+		cg_build_type_switch_stmt(p, node);
+	case_end;
+
+	case_ast_node(ds, DeferStmt, node);
+		Type *pt = base_type(p->type);
+		GB_ASSERT(pt->kind == Type_Proc);
+		if (pt->Proc.calling_convention == ProcCC_Odin) {
+			GB_ASSERT(p->context_stack.count != 0);
+		}
+
+		cgDefer *d = array_add_and_get(&p->defer_stack);
+		d->kind = cgDefer_Node;
+		d->scope_index = p->scope_index;
+		d->context_stack_count = p->context_stack.count;
+		d->control_region = tb_inst_get_control(p->func);
+		GB_ASSERT(d->control_region != nullptr);
+		d->stmt = ds->stmt;
+	case_end;
+
+
+
+	default:
+		GB_PANIC("TODO cg_build_stmt %.*s", LIT(ast_strings[node->kind]));
+		break;
+	}
+}
+
+gb_internal void cg_build_constant_value_decl(cgProcedure *p, AstValueDecl *vd) {
+	if (vd == nullptr || vd->is_mutable) {
+		return;
+	}
+
+	auto *min_dep_set = &p->module->info->minimum_dependency_set;
+
+	static i32 global_guid = 0;
+
+	for (Ast *ident : vd->names) {
+		GB_ASSERT(ident->kind == Ast_Ident);
+		Entity *e = entity_of_node(ident);
+		GB_ASSERT(e != nullptr);
+		if (e->kind != Entity_TypeName) {
+			continue;
+		}
+
+		bool polymorphic_struct = false;
+		if (e->type != nullptr && e->kind == Entity_TypeName) {
+			Type *bt = base_type(e->type);
+			if (bt->kind == Type_Struct) {
+				polymorphic_struct = bt->Struct.is_polymorphic;
+			}
+		}
+
+		if (!polymorphic_struct && !ptr_set_exists(min_dep_set, e)) {
+			continue;
+		}
+
+		if (e->TypeName.ir_mangled_name.len != 0) {
+			// NOTE(bill): Already set
+			continue;
+		}
+
+		cg_set_nested_type_name_ir_mangled_name(e, p);
+	}
+
+	for_array(i, vd->names) {
+		Ast *ident = vd->names[i];
+		GB_ASSERT(ident->kind == Ast_Ident);
+		Entity *e = entity_of_node(ident);
+		GB_ASSERT(e != nullptr);
+		if (e->kind != Entity_Procedure) {
+			continue;
+		}
+		GB_ASSERT (vd->values[i] != nullptr);
+
+		Ast *value = unparen_expr(vd->values[i]);
+		if (value->kind != Ast_ProcLit) {
+			continue; // It's an alias
+		}
+
+		DeclInfo *decl = decl_info_of_entity(e);
+		ast_node(pl, ProcLit, decl->proc_lit);
+		if (pl->body != nullptr) {
+			GenProcsData *gpd = e->Procedure.gen_procs;
+			if (gpd) {
+				rw_mutex_shared_lock(&gpd->mutex);
+				for (Entity *e : gpd->procs) {
+					if (!ptr_set_exists(min_dep_set, e)) {
+						continue;
+					}
+					DeclInfo *d = decl_info_of_entity(e);
+					cg_build_nested_proc(p, &d->proc_lit->ProcLit, e);
+				}
+				rw_mutex_shared_unlock(&gpd->mutex);
+			} else {
+				cg_build_nested_proc(p, pl, e);
+			}
+		} else {
+
+			// FFI - Foreign function interace
+			String original_name = e->token.string;
+			String name = original_name;
+
+			if (e->Procedure.is_foreign) {
+				GB_PANIC("cg_add_foreign_library_path");
+				// cg_add_foreign_library_path(p->module, e->Procedure.foreign_library);
+			}
+
+			if (e->Procedure.link_name.len > 0) {
+				name = e->Procedure.link_name;
+			}
+
+			cgValue *prev_value = string_map_get(&p->module->members, name);
+			if (prev_value != nullptr) {
+				// NOTE(bill): Don't do mutliple declarations in the IR
+				return;
+			}
+
+			e->Procedure.link_name = name;
+
+			cgProcedure *nested_proc = cg_procedure_create(p->module, e);
+
+			cgValue value = p->value;
+
+			array_add(&p->children, nested_proc);
+			string_map_set(&p->module->members, name, value);
+			cg_add_procedure_to_queue(nested_proc);
+		}
+	}
+}
+
+
+gb_internal void cg_build_stmt_list(cgProcedure *p, Slice<Ast *> const &stmts) {
+	for (Ast *stmt : stmts) {
+		switch (stmt->kind) {
+		case_ast_node(vd, ValueDecl, stmt);
+			cg_build_constant_value_decl(p, vd);
+		case_end;
+		case_ast_node(fb, ForeignBlockDecl, stmt);
+			ast_node(block, BlockStmt, fb->body);
+			cg_build_stmt_list(p, block->stmts);
+		case_end;
+		}
+	}
+	for (Ast *stmt : stmts) {
+		cg_build_stmt(p, stmt);
+	}
+}
+
+
+gb_internal void cg_build_when_stmt(cgProcedure *p, AstWhenStmt *ws) {
+	TypeAndValue tv = type_and_value_of_expr(ws->cond);
+	GB_ASSERT(is_type_boolean(tv.type));
+	GB_ASSERT(tv.value.kind == ExactValue_Bool);
+	if (tv.value.value_bool) {
+		cg_build_stmt_list(p, ws->body->BlockStmt.stmts);
+	} else if (ws->else_stmt) {
+		switch (ws->else_stmt->kind) {
+		case Ast_BlockStmt:
+			cg_build_stmt_list(p, ws->else_stmt->BlockStmt.stmts);
+			break;
+		case Ast_WhenStmt:
+			cg_build_when_stmt(p, &ws->else_stmt->WhenStmt);
+			break;
+		default:
+			GB_PANIC("Invalid 'else' statement in 'when' statement");
+			break;
+		}
+	}
+}
+

+ 983 - 0
src/tilde_type_info.cpp

@@ -0,0 +1,983 @@
+gb_internal void cg_global_const_type_info_ptr(cgModule *m, Type *type, TB_Global *global, i64 offset) {
+	GB_ASSERT(type != nullptr);
+	TB_Symbol *type_table_array = cg_find_symbol_from_entity(m, cg_global_type_info_data_entity);
+
+
+	i64 index_in_bytes = cast(i64)cg_type_info_index(m->info, type);
+	index_in_bytes *= type_size_of(t_type_info);
+
+	void *ti_ptr_ptr = tb_global_add_region(m->mod, global, offset, build_context.ptr_size);
+	// NOTE(bill): define the byte offset for the pointer
+	cg_write_int_at_ptr(ti_ptr_ptr, index_in_bytes, t_uintptr);
+
+	// NOTE(bill): this will add to the byte offset set previously
+	tb_global_add_symbol_reloc(m->mod, global, offset, type_table_array);
+}
+
+gb_internal cgValue cg_global_type_info_data_ptr(cgProcedure *p) {
+	cgValue v = cg_find_value_from_entity(p->module, cg_global_type_info_data_entity);
+	return cg_flatten_value(p, v);
+}
+
+gb_internal isize cg_type_info_index(CheckerInfo *info, Type *type, bool err_on_not_found) {
+	auto *set = &info->minimum_dependency_type_info_set;
+	isize index = type_info_index(info, type, err_on_not_found);
+	if (index >= 0) {
+		auto *found = map_get(set, index);
+		if (found) {
+			GB_ASSERT(*found >= 0);
+			return *found + 1;
+		}
+	}
+	if (err_on_not_found) {
+		GB_PANIC("NOT FOUND lb_type_info_index '%s' @ index %td", type_to_string(type), index);
+	}
+	return -1;
+}
+
+gb_internal cgValue cg_type_info(cgProcedure *p, Type *type) {
+	GB_ASSERT(!build_context.no_rtti);
+
+	type = default_type(type);
+
+	isize index = cg_type_info_index(p->module->info, type);
+	GB_ASSERT(index >= 0);
+
+	cgValue data = cg_global_type_info_data_ptr(p);
+	return cg_emit_array_epi(p, data, index);
+}
+
+
+gb_internal u64 cg_typeid_as_u64(cgModule *m, Type *type) {
+	GB_ASSERT(!build_context.no_rtti);
+
+	type = default_type(type);
+
+	u64 id = cast(u64)cg_type_info_index(m->info, type);
+	GB_ASSERT(id >= 0);
+
+	u64 kind = Typeid_Invalid;
+	u64 named = is_type_named(type) && type->kind != Type_Basic;
+	u64 special = 0;
+	u64 reserved = 0;
+
+	Type *bt = base_type(type);
+	TypeKind tk = bt->kind;
+	switch (tk) {
+	case Type_Basic: {
+		u32 flags = bt->Basic.flags;
+		if (flags & BasicFlag_Boolean)  kind = Typeid_Boolean;
+		if (flags & BasicFlag_Integer)  kind = Typeid_Integer;
+		if (flags & BasicFlag_Unsigned) kind = Typeid_Integer;
+		if (flags & BasicFlag_Float)    kind = Typeid_Float;
+		if (flags & BasicFlag_Complex)  kind = Typeid_Complex;
+		if (flags & BasicFlag_Pointer)  kind = Typeid_Pointer;
+		if (flags & BasicFlag_String)   kind = Typeid_String;
+		if (flags & BasicFlag_Rune)     kind = Typeid_Rune;
+	} break;
+	case Type_Pointer:              kind = Typeid_Pointer;                break;
+	case Type_MultiPointer:         kind = Typeid_Multi_Pointer;          break;
+	case Type_Array:                kind = Typeid_Array;                  break;
+	case Type_Matrix:               kind = Typeid_Matrix;                 break;
+	case Type_EnumeratedArray:      kind = Typeid_Enumerated_Array;       break;
+	case Type_Slice:                kind = Typeid_Slice;                  break;
+	case Type_DynamicArray:         kind = Typeid_Dynamic_Array;          break;
+	case Type_Map:                  kind = Typeid_Map;                    break;
+	case Type_Struct:               kind = Typeid_Struct;                 break;
+	case Type_Enum:                 kind = Typeid_Enum;                   break;
+	case Type_Union:                kind = Typeid_Union;                  break;
+	case Type_Tuple:                kind = Typeid_Tuple;                  break;
+	case Type_Proc:                 kind = Typeid_Procedure;              break;
+	case Type_BitSet:               kind = Typeid_Bit_Set;                break;
+	case Type_SimdVector:           kind = Typeid_Simd_Vector;            break;
+	case Type_RelativePointer:      kind = Typeid_Relative_Pointer;       break;
+	case Type_RelativeMultiPointer: kind = Typeid_Relative_Multi_Pointer; break;
+	case Type_SoaPointer:           kind = Typeid_SoaPointer;             break;
+	}
+
+	if (is_type_cstring(type)) {
+		special = 1;
+	} else if (is_type_integer(type) && !is_type_unsigned(type)) {
+		special = 1;
+	}
+
+	u64 data = 0;
+	if (build_context.ptr_size == 4) {
+		GB_ASSERT(id <= (1u<<24u));
+		data |= (id       &~ (1u<<24)) << 0u;  // index
+		data |= (kind     &~ (1u<<5))  << 24u; // kind
+		data |= (named    &~ (1u<<1))  << 29u; // named
+		data |= (special  &~ (1u<<1))  << 30u; // special
+		data |= (reserved &~ (1u<<1))  << 31u; // reserved
+	} else {
+		GB_ASSERT(build_context.ptr_size == 8);
+		GB_ASSERT(id <= (1ull<<56u));
+		data |= (id       &~ (1ull<<56)) << 0ul;  // index
+		data |= (kind     &~ (1ull<<5))  << 56ull; // kind
+		data |= (named    &~ (1ull<<1))  << 61ull; // named
+		data |= (special  &~ (1ull<<1))  << 62ull; // special
+		data |= (reserved &~ (1ull<<1))  << 63ull; // reserved
+	}
+	return data;
+}
+
+gb_internal cgValue cg_typeid(cgProcedure *p, Type *t) {
+	u64 x = cg_typeid_as_u64(p->module, t);
+	return cg_value(tb_inst_uint(p->func, cg_data_type(t_typeid), x), t_typeid);
+}
+
+
+
+
+gb_internal void cg_set_type_info_member_types(cgModule *m, TB_Global *global, isize offset, isize count, void *userdata, Type *(*type_proc)(isize index, void *userdata)) {
+	if (count == 0) {
+		return;
+	}
+
+	void *data_ptr = tb_global_add_region(m->mod, global, offset+0, build_context.ptr_size);
+	i64 offset_in_bytes = cg_global_type_info_member_types.index * type_size_of(cg_global_type_info_member_types.elem_type);
+	cg_global_type_info_member_types.index += count;
+
+	cg_write_int_at_ptr(data_ptr, offset_in_bytes, t_uintptr);
+	tb_global_add_symbol_reloc(m->mod, global, offset+0, cast(TB_Symbol *)cg_global_type_info_member_types.global);
+
+	for (isize i = 0; i < count; i++) {
+		i64 elem_size = type_size_of(cg_global_type_info_member_types.elem_type);
+		Type *type = type_proc(i, userdata);
+		i64 offset_for_elem = offset_in_bytes + i*elem_size;
+		cg_global_const_type_info_ptr(m, type, cg_global_type_info_member_types.global, offset_for_elem);
+	}
+
+	void *len_ptr  = tb_global_add_region(m->mod, global, offset+build_context.int_size, build_context.int_size);
+	cg_write_int_at_ptr(len_ptr, count, t_int);
+}
+
+
+gb_internal void cg_set_type_info_member_names(cgModule *m, TB_Global *global, isize offset, isize count, void *userdata, String (*name_proc)(isize index, void *userdata)) {
+	if (count == 0) {
+		return;
+	}
+	void *data_ptr = tb_global_add_region(m->mod, global, offset+0, build_context.ptr_size);
+	i64 offset_in_bytes = cg_global_type_info_member_names.index * type_size_of(cg_global_type_info_member_names.elem_type);
+	cg_global_type_info_member_names.index += count;
+
+	cg_write_int_at_ptr(data_ptr, offset_in_bytes, t_uintptr);
+	tb_global_add_symbol_reloc(m->mod, global, offset+0, cast(TB_Symbol *)cg_global_type_info_member_names.global);
+
+	for (isize i = 0; i < count; i++) {
+		i64 elem_size = type_size_of(cg_global_type_info_member_names.elem_type);
+		String name = name_proc(i, userdata);
+		i64 offset_for_elem = offset_in_bytes + i*elem_size;
+		cg_global_const_string(m, name, cg_global_type_info_member_names.elem_type, cg_global_type_info_member_names.global, offset_for_elem);
+
+	}
+
+	void *len_ptr  = tb_global_add_region(m->mod, global, offset+build_context.int_size, build_context.int_size);
+	cg_write_int_at_ptr(len_ptr, count, t_int);
+}
+
+
+gb_internal void cg_set_type_info_member_offsets(cgModule *m, TB_Global *global, isize offset, isize count, void *userdata, i64 (*offset_proc)(isize index, void *userdata)) {
+	if (count == 0) {
+		return;
+	}
+	void *data_ptr = tb_global_add_region(m->mod, global, offset+0, build_context.ptr_size);
+	i64 offset_in_bytes = cg_global_type_info_member_offsets.index * type_size_of(cg_global_type_info_member_offsets.elem_type);
+	cg_global_type_info_member_offsets.index += count;
+
+	cg_write_int_at_ptr(data_ptr, offset_in_bytes, t_uintptr);
+	tb_global_add_symbol_reloc(m->mod, global, offset+0, cast(TB_Symbol *)cg_global_type_info_member_offsets.global);
+
+	for (isize i = 0; i < count; i++) {
+		i64 elem_size = type_size_of(cg_global_type_info_member_offsets.elem_type);
+		i64 the_offset = offset_proc(i, userdata);
+		i64 offset_for_elem = offset_in_bytes + i*elem_size;
+
+		void *offset_ptr = tb_global_add_region(m->mod, cg_global_type_info_member_offsets.global, offset_for_elem, elem_size);
+		cg_write_uint_at_ptr(offset_ptr, the_offset, t_uintptr);
+	}
+
+	void *len_ptr  = tb_global_add_region(m->mod, global, offset+build_context.int_size, build_context.int_size);
+	cg_write_int_at_ptr(len_ptr, count, t_int);
+}
+
+gb_internal void cg_set_type_info_member_usings(cgModule *m, TB_Global *global, isize offset, isize count, void *userdata, bool (*usings_proc)(isize index, void *userdata)) {
+	if (count == 0) {
+		return;
+	}
+	void *data_ptr = tb_global_add_region(m->mod, global, offset+0, build_context.ptr_size);
+	i64 offset_in_bytes = cg_global_type_info_member_usings.index * type_size_of(cg_global_type_info_member_usings.elem_type);
+	cg_global_type_info_member_usings.index += count;
+
+	cg_write_int_at_ptr(data_ptr, offset_in_bytes, t_uintptr);
+	tb_global_add_symbol_reloc(m->mod, global, offset+0, cast(TB_Symbol *)cg_global_type_info_member_usings.global);
+
+	for (isize i = 0; i < count; i++) {
+		i64 elem_size = type_size_of(cg_global_type_info_member_usings.elem_type);
+		GB_ASSERT(elem_size == 1);
+		bool the_usings = usings_proc(i, userdata);
+		i64 offset_for_elem = offset_in_bytes + i*elem_size;
+
+		bool *usings_ptr = cast(bool *)tb_global_add_region(m->mod, cg_global_type_info_member_usings.global, offset_for_elem, 1);
+		*usings_ptr = the_usings;
+	}
+
+	void *len_ptr  = tb_global_add_region(m->mod, global, offset+build_context.int_size, build_context.int_size);
+	cg_write_int_at_ptr(len_ptr, count, t_int);
+}
+
+
+
+gb_internal void cg_set_type_info_member_tags(cgModule *m, TB_Global *global, isize offset, isize count, void *userdata, String (*tag_proc)(isize index, void *userdata)) {
+	if (count == 0) {
+		return;
+	}
+	void *data_ptr = tb_global_add_region(m->mod, global, offset+0, build_context.ptr_size);
+	i64 offset_in_bytes = cg_global_type_info_member_tags.index * type_size_of(cg_global_type_info_member_tags.elem_type);
+	cg_global_type_info_member_tags.index += count;
+
+	cg_write_int_at_ptr(data_ptr, offset_in_bytes, t_uintptr);
+	tb_global_add_symbol_reloc(m->mod, global, offset+0, cast(TB_Symbol *)cg_global_type_info_member_tags.global);
+
+	for (isize i = 0; i < count; i++) {
+		i64 elem_size = type_size_of(cg_global_type_info_member_tags.elem_type);
+		String tag = tag_proc(i, userdata);
+		i64 offset_for_elem = offset_in_bytes + i*elem_size;
+		cg_global_const_string(m, tag, cg_global_type_info_member_tags.elem_type, cg_global_type_info_member_tags.global, offset_for_elem);
+
+	}
+
+	void *len_ptr  = tb_global_add_region(m->mod, global, offset+build_context.int_size, build_context.int_size);
+	cg_write_int_at_ptr(len_ptr, count, t_int);
+}
+
+gb_internal void cg_set_type_info_member_enum_values(cgModule *m, TB_Global *global, isize offset, isize count, void *userdata, i64 (*value_proc)(isize index, void *userdata)) {
+	if (count == 0) {
+		return;
+	}
+	void *data_ptr = tb_global_add_region(m->mod, global, offset+0, build_context.ptr_size);
+	i64 offset_in_bytes = cg_global_type_info_member_enum_values.index * type_size_of(cg_global_type_info_member_enum_values.elem_type);
+	cg_global_type_info_member_enum_values.index += count;
+
+	cg_write_int_at_ptr(data_ptr, offset_in_bytes, t_uintptr);
+	tb_global_add_symbol_reloc(m->mod, global, offset+0, cast(TB_Symbol *)cg_global_type_info_member_enum_values.global);
+
+	for (isize i = 0; i < count; i++) {
+		i64 elem_size = type_size_of(cg_global_type_info_member_enum_values.elem_type);
+		GB_ASSERT(elem_size == 8);
+		i64 the_value = value_proc(i, userdata);
+		i64 offset_for_elem = offset_in_bytes + i*elem_size;
+
+		void *offset_ptr = tb_global_add_region(m->mod, cg_global_type_info_member_enum_values.global, offset_for_elem, elem_size);
+		cg_write_uint_at_ptr(offset_ptr, the_value, cg_global_type_info_member_enum_values.elem_type);
+	}
+
+	void *len_ptr  = tb_global_add_region(m->mod, global, offset+build_context.int_size, build_context.int_size);
+	cg_write_int_at_ptr(len_ptr, count, t_int);
+}
+
+
+
+gb_internal void cg_setup_type_info_data(cgModule *m) {
+	if (build_context.no_rtti) {
+		return;
+	}
+
+	CheckerInfo *info = m->info;
+	{ // Add type info data
+		isize max_type_info_count = info->minimum_dependency_type_info_set.count+1;
+		// gb_printf_err("max_type_info_count: %td\n", max_type_info_count);
+		Type *t = alloc_type_array(t_type_info, max_type_info_count);
+
+		i64 max_objects = cast(i64)max_type_info_count * cg_global_const_calculate_region_count_from_basic_type(t_type_info);
+
+		TB_Global *g = tb_global_create(m->mod, -1, CG_TYPE_INFO_DATA_NAME, nullptr, TB_LINKAGE_PRIVATE);
+		tb_global_set_storage(m->mod, tb_module_get_rdata(m->mod), g, type_size_of(t), 16, max_objects);
+
+		cgValue value = cg_value(g, alloc_type_pointer(t));
+		cg_global_type_info_data_entity = alloc_entity_variable(nullptr, make_token_ident(CG_TYPE_INFO_DATA_NAME), t, EntityState_Resolved);
+		cg_add_symbol(m, cg_global_type_info_data_entity, cast(TB_Symbol *)g);
+		cg_add_entity(m, cg_global_type_info_data_entity, value);
+	}
+
+	{ // Type info member buffer
+		// NOTE(bill): Removes need for heap allocation by making it global memory
+		isize count = 0;
+		isize enum_count = 0;
+
+		for (Type *t : m->info->type_info_types) {
+			isize index = cg_type_info_index(m->info, t, false);
+			if (index < 0) {
+				continue;
+			}
+
+			switch (t->kind) {
+			case Type_Union:
+				count += t->Union.variants.count;
+				break;
+			case Type_Struct:
+				count += t->Struct.fields.count;
+				break;
+			case Type_Tuple:
+				count += t->Tuple.variables.count;
+				break;
+			case Type_Enum:
+				enum_count += t->Enum.fields.count;
+				break;
+			}
+		}
+
+		if (count > 0) {
+			{
+				char const *name = CG_TYPE_INFO_TYPES_NAME;
+				Type *t = alloc_type_array(t_type_info_ptr, count);
+				TB_Global *g = tb_global_create(m->mod, -1, name, nullptr, TB_LINKAGE_PRIVATE);
+				tb_global_set_storage(m->mod, tb_module_get_rdata(m->mod), g, type_size_of(t), 16, count*2);
+				cg_global_type_info_member_types = GlobalTypeInfoData{g, t, t_type_info_ptr, 0};
+			}
+			{
+				char const *name = CG_TYPE_INFO_NAMES_NAME;
+				Type *t = alloc_type_array(t_string, count);
+				TB_Global *g = tb_global_create(m->mod, -1, name, nullptr, TB_LINKAGE_PRIVATE);
+				tb_global_set_storage(m->mod, tb_module_get_rdata(m->mod), g, type_size_of(t), 16, count*2);
+				cg_global_type_info_member_names = GlobalTypeInfoData{g, t, t_string, 0};
+			}
+			{
+				char const *name = CG_TYPE_INFO_OFFSETS_NAME;
+				Type *t = alloc_type_array(t_uintptr, count);
+				TB_Global *g = tb_global_create(m->mod, -1, name, nullptr, TB_LINKAGE_PRIVATE);
+				tb_global_set_storage(m->mod, tb_module_get_rdata(m->mod), g, type_size_of(t), 16, count);
+				cg_global_type_info_member_offsets = GlobalTypeInfoData{g, t, t_uintptr, 0};
+			}
+
+			{
+				char const *name = CG_TYPE_INFO_USINGS_NAME;
+				Type *t = alloc_type_array(t_bool, count);
+				TB_Global *g = tb_global_create(m->mod, -1, name, nullptr, TB_LINKAGE_PRIVATE);
+				tb_global_set_storage(m->mod, tb_module_get_rdata(m->mod), g, type_size_of(t), 16, count);
+				cg_global_type_info_member_usings = GlobalTypeInfoData{g, t, t_bool, 0};
+			}
+
+			{
+				char const *name = CG_TYPE_INFO_TAGS_NAME;
+				Type *t = alloc_type_array(t_string, count);
+				TB_Global *g = tb_global_create(m->mod, -1, name, nullptr, TB_LINKAGE_PRIVATE);
+				tb_global_set_storage(m->mod, tb_module_get_rdata(m->mod), g, type_size_of(t), 16, count*2);
+				cg_global_type_info_member_tags = GlobalTypeInfoData{g, t, t_string, 0};
+			}
+		}
+
+		if (enum_count > 0) {
+			char const *name = CG_TYPE_INFO_ENUM_VALUES_NAME;
+			Type *t = alloc_type_array(t_i64, enum_count);
+			TB_Global *g = tb_global_create(m->mod, -1, name, nullptr, TB_LINKAGE_PRIVATE);
+			tb_global_set_storage(m->mod, tb_module_get_rdata(m->mod), g, type_size_of(t), 16, enum_count);
+			cg_global_type_info_member_enum_values = GlobalTypeInfoData{g, t, t_i64, 0};
+		}
+	}
+	gb_unused(info);
+
+
+	i64 global_type_info_data_entity_count = 0;
+
+	// NOTE(bill): Set the type_table slice with the global backing array
+	TB_Global *type_table_slice = cast(TB_Global *)cg_find_symbol_from_entity(m, scope_lookup_current(m->info->runtime_package->scope, str_lit("type_table")));
+	GB_ASSERT(type_table_slice != nullptr);
+
+	TB_Global *type_table_array = cast(TB_Global *)cg_find_symbol_from_entity(m, cg_global_type_info_data_entity);
+	GB_ASSERT(type_table_array != nullptr);
+
+	Type *type = base_type(cg_global_type_info_data_entity->type);
+	GB_ASSERT(is_type_array(type));
+	global_type_info_data_entity_count = type->Array.count;
+
+	tb_global_add_symbol_reloc(m->mod, type_table_slice, 0, cast(TB_Symbol *)type_table_array);
+
+	void *len_ptr = tb_global_add_region(m->mod, type_table_slice, build_context.int_size, build_context.int_size);
+	cg_write_int_at_ptr(len_ptr, type->Array.count, t_int);
+
+	// Useful types
+	Entity *type_info_flags_entity = find_core_entity(info->checker, str_lit("Type_Info_Flags"));
+	Type *t_type_info_flags = type_info_flags_entity->type;
+	GB_ASSERT(type_size_of(t_type_info_flags) == 4);
+
+	auto entries_handled = slice_make<bool>(heap_allocator(), cast(isize)global_type_info_data_entity_count);
+	defer (gb_free(heap_allocator(), entries_handled.data));
+	entries_handled[0] = true;
+
+
+	i64 type_info_size = type_size_of(t_type_info);
+	i64 size_offset    = type_offset_of(t_type_info, 0);
+	i64 align_offset   = type_offset_of(t_type_info, 1);
+	i64 flags_offset   = type_offset_of(t_type_info, 2);
+	i64 id_offset      = type_offset_of(t_type_info, 3);
+	i64 variant_offset = type_offset_of(t_type_info, 4);
+
+	Type *type_info_union = base_type(t_type_info)->Struct.fields[4]->type;
+	GB_ASSERT(type_info_union->kind == Type_Union);
+
+	i64 union_tag_offset    = type_info_union->Union.variant_block_size;
+	Type *ti_union_tag_type = union_tag_type(type_info_union);
+	u64 union_tag_type_size = type_size_of(ti_union_tag_type);
+
+	auto const &set_bool = [](cgModule *m, TB_Global *global, i64 offset, bool value) {
+		bool *ptr = cast(bool *)tb_global_add_region(m->mod, global, offset, 1);
+		*ptr = value;
+	};
+
+
+	for_array(type_info_type_index, info->type_info_types) {
+		Type *t = info->type_info_types[type_info_type_index];
+		if (t == nullptr || t == t_invalid) {
+			continue;
+		}
+
+		isize entry_index = cg_type_info_index(info, t, false);
+		if (entry_index <= 0) {
+			continue;
+		}
+
+		if (entries_handled[entry_index]) {
+			continue;
+		}
+		entries_handled[entry_index] = true;
+
+		TB_Global *global = type_table_array;
+
+		i64 offset = entry_index * type_info_size;
+
+		i64 size  = type_size_of(t);
+		i64 align = type_align_of(t);
+		u32 flags = type_info_flags_of_type(t);
+		u64 id    = cg_typeid_as_u64(m, t);
+
+		void *size_ptr  = tb_global_add_region(m->mod,  global, offset+size_offset, build_context.int_size);
+		void *align_ptr = tb_global_add_region(m->mod, global, offset+align_offset, build_context.int_size);
+		void *flags_ptr = tb_global_add_region(m->mod, global, offset+flags_offset, 4);
+		void *id_ptr    = tb_global_add_region(m->mod, global, offset+id_offset,    build_context.ptr_size);
+		cg_write_int_at_ptr (size_ptr,  size,  t_int);
+		cg_write_int_at_ptr (align_ptr, align, t_int);
+		cg_write_int_at_ptr (flags_ptr, flags, t_u32);
+		cg_write_uint_at_ptr(id_ptr,    id,    t_typeid);
+
+
+		// add data to the offset to make it easier to deal with later on
+		offset += variant_offset;
+
+		Type *tag_type = nullptr;
+
+		switch (t->kind) {
+		case Type_Named: {
+			// Type_Info_Named :: struct {
+			// 	name: string,
+			// 	base: ^Type_Info,
+			// 	pkg:  string,
+			// 	loc:  Source_Code_Location,
+			// }
+			tag_type = t_type_info_named;
+
+			if (t->Named.type_name->pkg) {
+				i64 pkg_offset = type_offset_of(tag_type, 2);
+				String pkg_name = t->Named.type_name->pkg->name;
+				cg_global_const_string(m, pkg_name, t_string, global, offset+pkg_offset);
+			}
+
+			String proc_name = {};
+			if (t->Named.type_name->parent_proc_decl) {
+				DeclInfo *decl = t->Named.type_name->parent_proc_decl;
+				if (decl->entity && decl->entity->kind == Entity_Procedure) {
+					i64 name_offset = type_offset_of(tag_type, 0);
+					proc_name = decl->entity->token.string;
+					cg_global_const_string(m, proc_name, t_string, global, offset+name_offset);
+				}
+			}
+
+			i64 loc_offset = type_offset_of(tag_type, 3);
+			TokenPos pos = t->Named.type_name->token.pos;
+			cg_global_source_code_location_const(m, proc_name, pos, global, offset+loc_offset);
+
+			i64 base_offset = type_offset_of(tag_type, 1);
+			cg_global_const_type_info_ptr(m, t->Named.base, global, offset+base_offset);
+			break;
+		}
+
+		case Type_Basic:
+			switch (t->Basic.kind) {
+			case Basic_bool:
+			case Basic_b8:
+			case Basic_b16:
+			case Basic_b32:
+			case Basic_b64:
+				tag_type = t_type_info_boolean;
+				break;
+
+			case Basic_i8:
+			case Basic_u8:
+			case Basic_i16:
+			case Basic_u16:
+			case Basic_i32:
+			case Basic_u32:
+			case Basic_i64:
+			case Basic_u64:
+			case Basic_i128:
+			case Basic_u128:
+
+			case Basic_i16le:
+			case Basic_u16le:
+			case Basic_i32le:
+			case Basic_u32le:
+			case Basic_i64le:
+			case Basic_u64le:
+			case Basic_i128le:
+			case Basic_u128le:
+			case Basic_i16be:
+			case Basic_u16be:
+			case Basic_i32be:
+			case Basic_u32be:
+			case Basic_i64be:
+			case Basic_u64be:
+			case Basic_i128be:
+			case Basic_u128be:
+
+			case Basic_int:
+			case Basic_uint:
+			case Basic_uintptr: {
+				tag_type = t_type_info_integer;
+
+				bool is_signed = (t->Basic.flags & BasicFlag_Unsigned) == 0;
+				// NOTE(bill): This is matches the runtime layout
+				u8 endianness_value = 0;
+				if (t->Basic.flags & BasicFlag_EndianLittle) {
+					endianness_value = 1;
+				} else if (t->Basic.flags & BasicFlag_EndianBig) {
+					endianness_value = 2;
+				}
+				u8 *signed_ptr     = cast(u8 *)tb_global_add_region(m->mod, global, offset+0, 1);
+				u8 *endianness_ptr = cast(u8 *)tb_global_add_region(m->mod, global, offset+1, 1);
+				*signed_ptr     = is_signed;
+				*endianness_ptr = endianness_value;
+				break;
+			}
+
+			case Basic_rune:
+				tag_type = t_type_info_rune;
+				break;
+
+			case Basic_f16:
+			case Basic_f32:
+			case Basic_f64:
+			case Basic_f16le:
+			case Basic_f32le:
+			case Basic_f64le:
+			case Basic_f16be:
+			case Basic_f32be:
+			case Basic_f64be:
+				{
+					tag_type = t_type_info_float;
+
+					// // NOTE(bill): This is matches the runtime layout
+					u8 endianness_value = 0;
+					if (t->Basic.flags & BasicFlag_EndianLittle) {
+						endianness_value = 1;
+					} else if (t->Basic.flags & BasicFlag_EndianBig) {
+						endianness_value = 2;
+					}
+
+					u8 *ptr = cast(u8 *)tb_global_add_region(m->mod, global, offset+0, 1);
+					*ptr = endianness_value;
+				}
+				break;
+
+			case Basic_complex32:
+			case Basic_complex64:
+			case Basic_complex128:
+				tag_type = t_type_info_complex;
+				break;
+
+			case Basic_quaternion64:
+			case Basic_quaternion128:
+			case Basic_quaternion256:
+				tag_type = t_type_info_quaternion;
+				break;
+
+			case Basic_rawptr:
+				tag_type = t_type_info_pointer;
+				break;
+
+			case Basic_string:
+				tag_type = t_type_info_string;
+				break;
+
+			case Basic_cstring:
+				tag_type = t_type_info_string;
+				set_bool(m, global, offset+0, true);
+				break;
+
+			case Basic_any:
+				tag_type = t_type_info_any;
+				break;
+
+			case Basic_typeid:
+				tag_type = t_type_info_typeid;
+				break;
+			}
+			break;
+
+		case Type_Pointer:
+			tag_type = t_type_info_pointer;
+			cg_global_const_type_info_ptr(m, t->Pointer.elem, global, offset+0);
+			break;
+		case Type_MultiPointer:
+			tag_type = t_type_info_multi_pointer;
+			cg_global_const_type_info_ptr(m, t->MultiPointer.elem, global, offset+0);
+			break;
+		case Type_SoaPointer:
+			tag_type = t_type_info_soa_pointer;
+			cg_global_const_type_info_ptr(m, t->SoaPointer.elem, global, offset+0);
+			break;
+
+		case Type_Array:
+			{
+				tag_type = t_type_info_array;
+
+				cg_global_const_type_info_ptr(m, t->Array.elem, global, offset+0);
+				void *elem_size_ptr = tb_global_add_region(m->mod, global, offset+1*build_context.int_size, build_context.int_size);
+				void *count_ptr     = tb_global_add_region(m->mod, global, offset+2*build_context.int_size, build_context.int_size);
+
+				cg_write_int_at_ptr(elem_size_ptr, type_size_of(t->Array.elem), t_int);
+				cg_write_int_at_ptr(count_ptr,     t->Array.count,              t_int);
+			}
+			break;
+
+		case Type_EnumeratedArray:
+			{
+				tag_type = t_type_info_enumerated_array;
+
+				i64 elem_offset      = type_offset_of(tag_type, 0);
+				i64 index_offset     = type_offset_of(tag_type, 1);
+				i64 elem_size_offset = type_offset_of(tag_type, 2);
+				i64 count_offset     = type_offset_of(tag_type, 3);
+				i64 min_value_offset = type_offset_of(tag_type, 4);
+				i64 max_value_offset = type_offset_of(tag_type, 5);
+				i64 is_sparse_offset = type_offset_of(tag_type, 6);
+
+				cg_global_const_type_info_ptr(m, t->EnumeratedArray.elem,  global, offset+elem_offset);
+				cg_global_const_type_info_ptr(m, t->EnumeratedArray.index, global, offset+index_offset);
+
+				void *elem_size_ptr = tb_global_add_region(m->mod, global, offset+elem_size_offset, build_context.int_size);
+				void *count_ptr     = tb_global_add_region(m->mod, global, offset+count_offset,     build_context.int_size);
+
+				void *min_value_ptr = tb_global_add_region(m->mod, global, offset+min_value_offset, type_size_of(t_type_info_enum_value));
+				void *max_value_ptr = tb_global_add_region(m->mod, global, offset+max_value_offset, type_size_of(t_type_info_enum_value));
+
+				cg_write_int_at_ptr(elem_size_ptr, type_size_of(t->EnumeratedArray.elem), t_int);
+				cg_write_int_at_ptr(count_ptr,     t->EnumeratedArray.count,              t_int);
+
+				cg_write_int_at_ptr(min_value_ptr, exact_value_to_i64(*t->EnumeratedArray.min_value), t_type_info_enum_value);
+				cg_write_int_at_ptr(max_value_ptr, exact_value_to_i64(*t->EnumeratedArray.max_value), t_type_info_enum_value);
+				set_bool(m, global, offset+is_sparse_offset, t->EnumeratedArray.is_sparse);
+			}
+			break;
+
+		case Type_DynamicArray:
+			{
+				tag_type = t_type_info_dynamic_array;
+
+				cg_global_const_type_info_ptr(m, t->DynamicArray.elem, global, offset+0);
+				void *elem_size_ptr = tb_global_add_region(m->mod, global, offset+1*build_context.int_size, build_context.int_size);
+				cg_write_int_at_ptr(elem_size_ptr, type_size_of(t->DynamicArray.elem), t_int);
+			}
+			break;
+		case Type_Slice:
+			{
+				tag_type = t_type_info_slice;
+
+				cg_global_const_type_info_ptr(m, t->Slice.elem, global, offset+0);
+				void *elem_size_ptr = tb_global_add_region(m->mod, global, offset+1*build_context.int_size, build_context.int_size);
+				cg_write_int_at_ptr(elem_size_ptr, type_size_of(t->Slice.elem), t_int);
+			}
+			break;
+
+		case Type_Proc:
+			{
+				tag_type = t_type_info_procedure;
+
+				i64 params_offset     = type_offset_of(tag_type, 0);
+				i64 results_offset    = type_offset_of(tag_type, 1);
+				i64 variadic_offset   = type_offset_of(tag_type, 2);
+				i64 convention_offset = type_offset_of(tag_type, 3);
+
+				if (t->Proc.params) {
+					cg_global_const_type_info_ptr(m, t->Proc.params, global, offset+params_offset);
+				}
+				if (t->Proc.results) {
+					cg_global_const_type_info_ptr(m, t->Proc.results, global, offset+results_offset);
+				}
+
+				set_bool(m, global, offset+variadic_offset, t->Proc.variadic);
+
+				u8 *convention_ptr = cast(u8 *)tb_global_add_region(m->mod, global, offset+convention_offset, 1);
+				*convention_ptr = cast(u8)t->Proc.calling_convention;
+			}
+			break;
+
+		case Type_Tuple:
+			{
+				tag_type = t_type_info_parameters;
+
+				i64 types_offset = type_offset_of(tag_type, 0);
+				i64 names_offset = type_offset_of(tag_type, 1);
+
+				i64 count = t->Tuple.variables.count;
+
+				cg_set_type_info_member_types(m, global, offset+types_offset, count, t, [](isize i, void *userdata) -> Type * {
+					Type *t = cast(Type *)userdata;
+					return t->Tuple.variables[i]->type;
+				});
+
+				cg_set_type_info_member_names(m, global, offset+names_offset, count, t, [](isize i, void *userdata) -> String {
+					Type *t = cast(Type *)userdata;
+					return t->Tuple.variables[i]->token.string;
+				});
+			}
+			break;
+
+		case Type_Enum:
+			{
+				tag_type = t_type_info_enum;
+
+				i64 base_offset   = type_offset_of(tag_type, 0);
+				i64 names_offset  = type_offset_of(tag_type, 1);
+				i64 values_offset = type_offset_of(tag_type, 2);
+
+				cg_global_const_type_info_ptr(m, t->Enum.base_type, global, offset+base_offset);
+
+				i64 count = t->Enum.fields.count;
+
+				cg_set_type_info_member_names(m, global, offset+names_offset, count, t, [](isize i, void *userdata) -> String {
+					Type *t = cast(Type *)userdata;
+					return t->Enum.fields[i]->token.string;
+				});
+
+				cg_set_type_info_member_enum_values(m, global, offset+values_offset, count, t, [](isize i, void *userdata) -> i64 {
+					Type *t = cast(Type *)userdata;
+					Entity *e = t->Enum.fields[i];
+					GB_ASSERT(e->kind == Entity_Constant);
+					return exact_value_to_i64(e->Constant.value);
+				});
+			}
+			break;
+		case Type_Struct:
+			{
+				tag_type = t_type_info_struct;
+
+				i64 types_offset         = type_offset_of(tag_type, 0);
+				i64 names_offset         = type_offset_of(tag_type, 1);
+				i64 offsets_offset       = type_offset_of(tag_type, 2);
+				i64 usings_offset        = type_offset_of(tag_type, 3);
+				i64 tags_offset          = type_offset_of(tag_type, 4);
+
+				i64 is_packed_offset     = type_offset_of(tag_type, 5);
+				i64 is_raw_union_offset  = type_offset_of(tag_type, 6);
+				i64 is_no_copy_offset    = type_offset_of(tag_type, 7);
+				i64 custom_align_offset  = type_offset_of(tag_type, 8);
+
+				i64 equal_offset         = type_offset_of(tag_type, 9);
+
+				i64 soa_kind_offset      = type_offset_of(tag_type, 10);
+				i64 soa_base_type_offset = type_offset_of(tag_type, 11);
+				i64 soa_len_offset       = type_offset_of(tag_type, 12);
+
+				// TODO(bill): equal proc stuff
+				gb_unused(equal_offset);
+
+				i64 count = t->Struct.fields.count;
+
+				cg_set_type_info_member_types(m, global, offset+types_offset, count, t, [](isize i, void *userdata) -> Type * {
+					Type *t = cast(Type *)userdata;
+					return t->Struct.fields[i]->type;
+				});
+
+				cg_set_type_info_member_names(m, global, offset+names_offset, count, t, [](isize i, void *userdata) -> String {
+					Type *t = cast(Type *)userdata;
+					return t->Struct.fields[i]->token.string;
+				});
+
+				cg_set_type_info_member_offsets(m, global, offset+offsets_offset, count, t, [](isize i, void *userdata) -> i64 {
+					Type *t = cast(Type *)userdata;
+					return t->Struct.offsets[i];
+				});
+
+				cg_set_type_info_member_usings(m, global, offset+usings_offset, count, t, [](isize i, void *userdata) -> bool {
+					Type *t = cast(Type *)userdata;
+					return (t->Struct.fields[i]->flags & EntityFlag_Using) != 0;
+				});
+
+				cg_set_type_info_member_tags(m, global, offset+tags_offset, count, t, [](isize i, void *userdata) -> String {
+					Type *t = cast(Type *)userdata;
+					return t->Struct.tags[i];
+				});
+
+
+				set_bool(m, global, offset+is_packed_offset,    t->Struct.is_packed);
+				set_bool(m, global, offset+is_raw_union_offset, t->Struct.is_raw_union);
+				set_bool(m, global, offset+is_no_copy_offset,   t->Struct.is_no_copy);
+				set_bool(m, global, offset+custom_align_offset, t->Struct.custom_align != 0);
+
+				if (t->Struct.soa_kind != StructSoa_None) {
+					u8 *kind_ptr = cast(u8 *)tb_global_add_region(m->mod, global, offset+soa_kind_offset, 1);
+					*kind_ptr = cast(u8)t->Struct.soa_kind;
+
+					cg_global_const_type_info_ptr(m, t->Struct.soa_elem, global, offset+soa_base_type_offset);
+
+					void *soa_len_ptr = tb_global_add_region(m->mod, global, offset+soa_len_offset, build_context.int_size);
+					cg_write_int_at_ptr(soa_len_ptr, t->Struct.soa_count, t_int);
+				}
+			}
+			break;
+		case Type_Union:
+			{
+				tag_type = t_type_info_union;
+
+				i64 variants_offset      = type_offset_of(tag_type, 0);
+				i64 tag_offset_offset    = type_offset_of(tag_type, 1);
+				i64 tag_type_offset      = type_offset_of(tag_type, 2);
+
+				i64 equal_offset         = type_offset_of(tag_type, 3);
+
+				i64 custom_align_offset  = type_offset_of(tag_type, 4);
+				i64 no_nil_offset        = type_offset_of(tag_type, 5);
+				i64 shared_nil_offset    = type_offset_of(tag_type, 6);
+
+				// TODO(bill): equal procs
+				gb_unused(equal_offset);
+
+				i64 count = t->Union.variants.count;
+
+				cg_set_type_info_member_types(m, global, offset+variants_offset, count, t, [](isize i, void *userdata) -> Type * {
+					Type *t = cast(Type *)userdata;
+					return t->Union.variants[i];
+				});
+
+				void *tag_offset_ptr = tb_global_add_region(m->mod, global, offset+tag_offset_offset, build_context.ptr_size);
+				cg_write_uint_at_ptr(tag_offset_ptr, t->Union.variant_block_size, t_uintptr);
+
+				cg_global_const_type_info_ptr(m, union_tag_type(t), global, offset+tag_type_offset);
+
+				set_bool(m, global, offset+custom_align_offset, t->Union.custom_align != 0);
+				set_bool(m, global, offset+no_nil_offset, t->Union.kind == UnionType_no_nil);
+				set_bool(m, global, offset+shared_nil_offset, t->Union.kind == UnionType_shared_nil);
+			}
+			break;
+		case Type_Map:
+			{
+				tag_type = t_type_info_map;
+
+				i64 key_offset      = type_offset_of(tag_type, 0);
+				i64 value_offset    = type_offset_of(tag_type, 1);
+				i64 map_info_offset = type_offset_of(tag_type, 2);
+
+				// TODO(bill): map info
+				gb_unused(map_info_offset);
+
+				cg_global_const_type_info_ptr(m, t->Map.key,   global, offset+key_offset);
+				cg_global_const_type_info_ptr(m, t->Map.value, global, offset+value_offset);
+
+			}
+			break;
+		case Type_BitSet:
+			{
+				tag_type = t_type_info_bit_set;
+
+				i64 elem_offset       = type_offset_of(tag_type, 0);
+				i64 underlying_offset = type_offset_of(tag_type, 1);
+				i64 lower_offset      = type_offset_of(tag_type, 2);
+				i64 upper_offset      = type_offset_of(tag_type, 3);
+
+				cg_global_const_type_info_ptr(m, t->BitSet.elem, global, offset+elem_offset);
+				if (t->BitSet.underlying) {
+					cg_global_const_type_info_ptr(m, t->BitSet.underlying, global, offset+underlying_offset);
+				}
+
+				void *lower_ptr = tb_global_add_region(m->mod, global, offset+lower_offset, 8);
+				void *upper_ptr = tb_global_add_region(m->mod, global, offset+upper_offset, 8);
+
+				cg_write_int_at_ptr(lower_ptr, t->BitSet.lower, t_i64);
+				cg_write_int_at_ptr(upper_ptr, t->BitSet.upper, t_i64);
+			}
+			break;
+		case Type_SimdVector:
+			{
+				tag_type = t_type_info_simd_vector;
+
+				i64 elem_offset      = type_offset_of(tag_type, 0);
+				i64 elem_size_offset = type_offset_of(tag_type, 1);
+				i64 count_offset     = type_offset_of(tag_type, 2);
+
+				cg_global_const_type_info_ptr(m, t->SimdVector.elem, global, offset+elem_offset);
+
+				void *elem_size_ptr = tb_global_add_region(m->mod, global, offset+elem_size_offset, build_context.int_size);
+				void *count_ptr     = tb_global_add_region(m->mod, global, offset+count_offset,     build_context.int_size);
+
+				cg_write_int_at_ptr(elem_size_ptr, type_size_of(t->SimdVector.elem), t_int);
+				cg_write_int_at_ptr(count_ptr,     t->SimdVector.count,              t_int);
+			}
+			break;
+
+		case Type_RelativePointer:
+			{
+				tag_type = t_type_info_relative_pointer;
+
+				i64 pointer_offset      = type_offset_of(tag_type, 0);
+				i64 base_integer_offset = type_offset_of(tag_type, 1);
+
+				cg_global_const_type_info_ptr(m, t->RelativePointer.pointer_type, global, offset+pointer_offset);
+				cg_global_const_type_info_ptr(m, t->RelativePointer.base_integer, global, offset+base_integer_offset);
+			}
+			break;
+		case Type_RelativeMultiPointer:
+			{
+				tag_type = t_type_info_relative_multi_pointer;
+
+				i64 pointer_offset      = type_offset_of(tag_type, 0);
+				i64 base_integer_offset = type_offset_of(tag_type, 1);
+
+				cg_global_const_type_info_ptr(m, t->RelativePointer.pointer_type, global, offset+pointer_offset);
+				cg_global_const_type_info_ptr(m, t->RelativePointer.base_integer, global, offset+base_integer_offset);
+			}
+			break;
+		case Type_Matrix:
+			{
+				tag_type = t_type_info_matrix;
+
+				i64 elem_offset         = type_offset_of(tag_type, 0);
+				i64 elem_size_offset    = type_offset_of(tag_type, 1);
+				i64 elem_stride_offset  = type_offset_of(tag_type, 2);
+				i64 row_count_offset    = type_offset_of(tag_type, 3);
+				i64 column_count_offset = type_offset_of(tag_type, 4);
+
+				cg_global_const_type_info_ptr(m, t->Matrix.elem, global, offset+elem_offset);
+
+				void *elem_size_ptr    = tb_global_add_region(m->mod, global, offset+elem_size_offset,    build_context.int_size);
+				void *elem_stride_ptr  = tb_global_add_region(m->mod, global, offset+elem_stride_offset,  build_context.int_size);
+				void *row_count_ptr    = tb_global_add_region(m->mod, global, offset+row_count_offset,    build_context.int_size);
+				void *column_count_ptr = tb_global_add_region(m->mod, global, offset+column_count_offset, build_context.int_size);
+
+				cg_write_int_at_ptr(elem_size_ptr,    type_size_of(t->Matrix.elem),   t_int);
+				cg_write_int_at_ptr(elem_stride_ptr,  matrix_type_stride_in_elems(t), t_int);
+				cg_write_int_at_ptr(row_count_ptr,    t->Matrix.row_count,            t_int);
+				cg_write_int_at_ptr(column_count_ptr, t->Matrix.column_count,         t_int);
+
+			}
+			break;
+		}
+
+		if (tag_type != nullptr) {
+			i64 union_index = union_variant_index(type_info_union, tag_type);
+			GB_ASSERT(union_index != 0);
+			void *tag_ptr = tb_global_add_region(m->mod, global, offset+union_tag_offset, union_tag_type_size);
+			cg_write_int_at_ptr(tag_ptr, union_index, ti_union_tag_type);
+		}
+
+	}
+}

+ 2 - 2
src/tokenizer.cpp

@@ -696,8 +696,8 @@ gb_internal void tokenizer_get_token(Tokenizer *t, Token *token, int repeat=0) {
 			if (entry->kind != Token_Invalid && entry->hash == hash) {
 				if (str_eq(entry->text, token->string)) {
 					token->kind = entry->kind;
-					if (token->kind == Token_not_in && entry->text == "notin") {
-						syntax_warning(*token, "'notin' is deprecated in favour of 'not_in'");
+					if (token->kind == Token_not_in && entry->text.len == 5) {
+						syntax_error(*token, "Did you mean 'not_in'?");
 					}
 				}
 			}

+ 127 - 70
src/types.cpp

@@ -143,6 +143,7 @@ struct TypeStruct {
 	Type *          soa_elem;
 	i32             soa_count;
 	StructSoaKind   soa_kind;
+	BlockingMutex   mutex; // for settings offsets
 
 	bool            is_polymorphic;
 	bool            are_offsets_set             : 1;
@@ -244,6 +245,7 @@ struct TypeProc {
 	TYPE_KIND(Tuple, struct {                                 \
 		Slice<Entity *> variables; /* Entity_Variable */  \
 		i64 *           offsets;                          \
+		BlockingMutex   mutex; /* for settings offsets */ \
 		bool            are_offsets_being_processed;      \
 		bool            are_offsets_set;                  \
 		bool            is_packed;                        \
@@ -265,8 +267,8 @@ struct TypeProc {
 		Type *pointer_type;                               \
 		Type *base_integer;                               \
 	})                                                        \
-	TYPE_KIND(RelativeSlice, struct {                         \
-		Type *slice_type;                                 \
+	TYPE_KIND(RelativeMultiPointer, struct {                  \
+		Type *pointer_type;                               \
 		Type *base_integer;                               \
 	})                                                        \
 	TYPE_KIND(Matrix, struct {                                \
@@ -347,7 +349,7 @@ enum Typeid_Kind : u8 {
 	Typeid_Bit_Set,
 	Typeid_Simd_Vector,
 	Typeid_Relative_Pointer,
-	Typeid_Relative_Slice,
+	Typeid_Relative_Multi_Pointer,
 	Typeid_Matrix,
 	Typeid_SoaPointer,
 };
@@ -594,6 +596,7 @@ gb_global Type *t_untyped_uninit     = &basic_types[Basic_UntypedUninit];
 
 
 gb_global Type *t_u8_ptr       = nullptr;
+gb_global Type *t_u8_multi_ptr = nullptr;
 gb_global Type *t_int_ptr      = nullptr;
 gb_global Type *t_i64_ptr      = nullptr;
 gb_global Type *t_f64_ptr      = nullptr;
@@ -632,7 +635,7 @@ gb_global Type *t_type_info_map                  = nullptr;
 gb_global Type *t_type_info_bit_set              = nullptr;
 gb_global Type *t_type_info_simd_vector          = nullptr;
 gb_global Type *t_type_info_relative_pointer     = nullptr;
-gb_global Type *t_type_info_relative_slice       = nullptr;
+gb_global Type *t_type_info_relative_multi_pointer = nullptr;
 gb_global Type *t_type_info_matrix               = nullptr;
 gb_global Type *t_type_info_soa_pointer          = nullptr;
 
@@ -661,7 +664,7 @@ gb_global Type *t_type_info_map_ptr              = nullptr;
 gb_global Type *t_type_info_bit_set_ptr          = nullptr;
 gb_global Type *t_type_info_simd_vector_ptr      = nullptr;
 gb_global Type *t_type_info_relative_pointer_ptr = nullptr;
-gb_global Type *t_type_info_relative_slice_ptr   = nullptr;
+gb_global Type *t_type_info_relative_multi_pointer_ptr = nullptr;
 gb_global Type *t_type_info_matrix_ptr           = nullptr;
 gb_global Type *t_type_info_soa_pointer_ptr      = nullptr;
 
@@ -725,7 +728,7 @@ struct TypePath;
 
 gb_internal i64      type_size_of   (Type *t);
 gb_internal i64      type_align_of  (Type *t);
-gb_internal i64      type_offset_of (Type *t, i32 index);
+gb_internal i64      type_offset_of (Type *t, i64 index, Type **field_type_=nullptr);
 gb_internal gbString type_to_string (Type *type, bool shorthand=true);
 gb_internal gbString type_to_string (Type *type, gbAllocator allocator, bool shorthand=true);
 gb_internal i64      type_size_of_internal(Type *t, TypePath *path);
@@ -734,6 +737,7 @@ gb_internal Type *   bit_set_to_int(Type *t);
 gb_internal bool     are_types_identical(Type *x, Type *y);
 
 gb_internal bool  is_type_pointer(Type *t);
+gb_internal bool  is_type_multi_pointer(Type *t);
 gb_internal bool  is_type_soa_pointer(Type *t);
 gb_internal bool  is_type_proc(Type *t);
 gb_internal bool  is_type_slice(Type *t);
@@ -821,6 +825,9 @@ gb_internal void type_path_pop(TypePath *tp) {
 #define FAILURE_ALIGNMENT 0
 
 gb_internal bool type_ptr_set_update(PtrSet<Type *> *s, Type *t) {
+	if (t == nullptr) {
+		return true;
+	}
 	if (ptr_set_exists(s, t)) {
 		return true;
 	}
@@ -829,6 +836,10 @@ gb_internal bool type_ptr_set_update(PtrSet<Type *> *s, Type *t) {
 }
 
 gb_internal bool type_ptr_set_exists(PtrSet<Type *> *s, Type *t) {
+	if (t == nullptr) {
+		return true;
+	}
+
 	if (ptr_set_exists(s, t)) {
 		return true;
 	}
@@ -988,7 +999,7 @@ gb_internal Type *alloc_type_enumerated_array(Type *elem, Type *index, ExactValu
 
 gb_internal Type *alloc_type_slice(Type *elem) {
 	Type *t = alloc_type(Type_Slice);
-	t->Array.elem = elem;
+	t->Slice.elem = elem;
 	return t;
 }
 
@@ -1025,12 +1036,12 @@ gb_internal Type *alloc_type_relative_pointer(Type *pointer_type, Type *base_int
 	return t;
 }
 
-gb_internal Type *alloc_type_relative_slice(Type *slice_type, Type *base_integer) {
-	GB_ASSERT(is_type_slice(slice_type));
+gb_internal Type *alloc_type_relative_multi_pointer(Type *pointer_type, Type *base_integer) {
+	GB_ASSERT(is_type_multi_pointer(pointer_type));
 	GB_ASSERT(is_type_integer(base_integer));
-	Type *t = alloc_type(Type_RelativeSlice);
-	t->RelativeSlice.slice_type   = slice_type;
-	t->RelativeSlice.base_integer = base_integer;
+	Type *t = alloc_type(Type_RelativeMultiPointer);
+	t->RelativeMultiPointer.pointer_type = pointer_type;
+	t->RelativeMultiPointer.base_integer = base_integer;
 	return t;
 }
 
@@ -1541,9 +1552,9 @@ gb_internal bool is_type_relative_pointer(Type *t) {
 	t = base_type(t);
 	return t->kind == Type_RelativePointer;
 }
-gb_internal bool is_type_relative_slice(Type *t) {
+gb_internal bool is_type_relative_multi_pointer(Type *t) {
 	t = base_type(t);
-	return t->kind == Type_RelativeSlice;
+	return t->kind == Type_RelativeMultiPointer;
 }
 
 gb_internal bool is_type_u8_slice(Type *t) {
@@ -1960,7 +1971,7 @@ gb_internal bool is_type_indexable(Type *t) {
 		return true;
 	case Type_EnumeratedArray:
 		return true;
-	case Type_RelativeSlice:
+	case Type_RelativeMultiPointer:
 		return true;
 	case Type_Matrix:
 		return true;
@@ -1979,7 +1990,7 @@ gb_internal bool is_type_sliceable(Type *t) {
 		return true;
 	case Type_EnumeratedArray:
 		return false;
-	case Type_RelativeSlice:
+	case Type_RelativeMultiPointer:
 		return true;
 	case Type_Matrix:
 		return false;
@@ -2185,12 +2196,12 @@ gb_internal bool is_type_polymorphic(Type *t, bool or_specialized=false) {
 		}
 		break;
 
-	case Type_RelativeSlice:
-		if (is_type_polymorphic(t->RelativeSlice.slice_type, or_specialized)) {
+	case Type_RelativeMultiPointer:
+		if (is_type_polymorphic(t->RelativeMultiPointer.pointer_type, or_specialized)) {
 			return true;
 		}
-		if (t->RelativeSlice.base_integer != nullptr &&
-		    is_type_polymorphic(t->RelativeSlice.base_integer, or_specialized)) {
+		if (t->RelativeMultiPointer.base_integer != nullptr &&
+		    is_type_polymorphic(t->RelativeMultiPointer.base_integer, or_specialized)) {
 			return true;
 		}
 		break;
@@ -2248,7 +2259,7 @@ gb_internal bool type_has_nil(Type *t) {
 		return false;
 
 	case Type_RelativePointer:
-	case Type_RelativeSlice:
+	case Type_RelativeMultiPointer:
 		return true;
 	}
 	return false;
@@ -2415,7 +2426,7 @@ gb_internal bool is_type_load_safe(Type *type) {
 		return true;
 
 	case Type_RelativePointer:
-	case Type_RelativeSlice:
+	case Type_RelativeMultiPointer:
 		return true;
 
 	case Type_Pointer:
@@ -2666,7 +2677,6 @@ gb_internal bool are_types_identical_internal(Type *x, Type *y, bool check_tuple
 		    x->Struct.soa_kind == y->Struct.soa_kind &&
 		    x->Struct.soa_count == y->Struct.soa_count &&
 		    are_types_identical(x->Struct.soa_elem, y->Struct.soa_elem)) {
-			// TODO(bill); Fix the custom alignment rule
 			for_array(i, x->Struct.fields) {
 				Entity *xf = x->Struct.fields[i];
 				Entity *yf = y->Struct.fields[i];
@@ -2807,7 +2817,6 @@ gb_internal i64 union_tag_size(Type *u) {
 		return 0;
 	}
 
-	// TODO(bill): Is this an okay approach?
 	i64 max_align = 1;
 
 	if (u->Union.variants.count < 1ull<<8) {
@@ -2817,7 +2826,7 @@ gb_internal i64 union_tag_size(Type *u) {
 	} else if (u->Union.variants.count < 1ull<<32) {
 		max_align = 4;
 	} else {
-		GB_PANIC("how many variants do you have?!");
+		compiler_error("how many variants do you have?! %lld", cast(long long)u->Union.variants.count);
 	}
 
 	for_array(i, u->Union.variants) {
@@ -3081,7 +3090,7 @@ gb_internal Selection lookup_field_with_selection(Type *type_, String field_name
 				mutex_lock(md->mutex);
 				defer (mutex_unlock(md->mutex));
 				for (TypeNameObjCMetadataEntry const &entry : md->value_entries) {
-					GB_ASSERT(entry.entity->kind == Entity_Procedure);
+					GB_ASSERT(entry.entity->kind == Entity_Procedure || entry.entity->kind == Entity_ProcGroup);
 					if (entry.name == field_name) {
 						sel.entity = entry.entity;
 						sel.pseudo_field = true;
@@ -3136,8 +3145,6 @@ gb_internal Selection lookup_field_with_selection(Type *type_, String field_name
 		switch (type->Basic.kind) {
 		case Basic_any: {
 		#if 1
-			// IMPORTANT TODO(bill): Should these members be available to should I only allow them with
-			// `Raw_Any` type?
 			String data_str = str_lit("data");
 			String id_str = str_lit("id");
 			gb_local_persist Entity *entity__any_data = alloc_entity_field(nullptr, make_token_ident(data_str), t_rawptr, false, 0);
@@ -3623,8 +3630,8 @@ gb_internal i64 type_align_of_internal(Type *t, TypePath *path) {
 
 	case Type_RelativePointer:
 		return type_align_of_internal(t->RelativePointer.base_integer, path);
-	case Type_RelativeSlice:
-		return type_align_of_internal(t->RelativeSlice.base_integer, path);
+	case Type_RelativeMultiPointer:
+		return type_align_of_internal(t->RelativeMultiPointer.base_integer, path);
 
 	case Type_SoaPointer:
 		return build_context.int_size;
@@ -3645,28 +3652,35 @@ gb_internal i64 *type_set_offsets_of(Slice<Entity *> const &fields, bool is_pack
 		}
 	} else if (is_packed) {
 		for_array(i, fields) {
-			i64 size = type_size_of(fields[i]->type);
-			offsets[i] = curr_offset;
-			curr_offset += size;
+			if (fields[i]->kind != Entity_Variable) {
+				offsets[i] = -1;
+			} else {
+				i64 size = type_size_of(fields[i]->type);
+				offsets[i] = curr_offset;
+				curr_offset += size;
+			}
 		}
 	} else {
 		for_array(i, fields) {
-			Type *t = fields[i]->type;
-			i64 align = gb_max(type_align_of(t), 1);
-			i64 size  = gb_max(type_size_of( t), 0);
-			curr_offset = align_formula(curr_offset, align);
-			offsets[i] = curr_offset;
-			curr_offset += size;
+			if (fields[i]->kind != Entity_Variable) {
+				offsets[i] = -1;
+			} else {
+				Type *t = fields[i]->type;
+				i64 align = gb_max(type_align_of(t), 1);
+				i64 size  = gb_max(type_size_of( t), 0);
+				curr_offset = align_formula(curr_offset, align);
+				offsets[i] = curr_offset;
+				curr_offset += size;
+			}
 		}
 	}
 	return offsets;
 }
 
 gb_internal bool type_set_offsets(Type *t) {
-	MUTEX_GUARD(&g_type_mutex); // TODO(bill): only per struct
-
 	t = base_type(t);
 	if (t->kind == Type_Struct) {
+		MUTEX_GUARD(&t->Struct.mutex);
 		if (!t->Struct.are_offsets_set) {
 			t->Struct.are_offsets_being_processed = true;
 			t->Struct.offsets = type_set_offsets_of(t->Struct.fields, t->Struct.is_packed, t->Struct.is_raw_union);
@@ -3675,6 +3689,7 @@ gb_internal bool type_set_offsets(Type *t) {
 			return true;
 		}
 	} else if (is_type_tuple(t)) {
+		MUTEX_GUARD(&t->Tuple.mutex);
 		if (!t->Tuple.are_offsets_set) {
 			t->Tuple.are_offsets_being_processed = true;
 			t->Tuple.offsets = type_set_offsets_of(t->Tuple.variables, t->Tuple.is_packed, false);
@@ -3849,7 +3864,6 @@ gb_internal i64 type_size_of_internal(Type *t, TypePath *path) {
 					max = size;
 				}
 			}
-			// TODO(bill): Is this how it should work?
 			return align_formula(max, align);
 		} else {
 			i64 count = 0, size = 0, align = 0;
@@ -3899,59 +3913,100 @@ gb_internal i64 type_size_of_internal(Type *t, TypePath *path) {
 
 	case Type_RelativePointer:
 		return type_size_of_internal(t->RelativePointer.base_integer, path);
-	case Type_RelativeSlice:
-		return 2*type_size_of_internal(t->RelativeSlice.base_integer, path);
+	case Type_RelativeMultiPointer:
+		return type_size_of_internal(t->RelativeMultiPointer.base_integer, path);
 	}
 
 	// Catch all
 	return build_context.ptr_size;
 }
 
-gb_internal i64 type_offset_of(Type *t, i32 index) {
+gb_internal i64 type_offset_of(Type *t, i64 index, Type **field_type_) {
 	t = base_type(t);
-	if (t->kind == Type_Struct) {
+	switch (t->kind) {
+	case Type_Struct:
 		type_set_offsets(t);
 		if (gb_is_between(index, 0, t->Struct.fields.count-1)) {
 			GB_ASSERT(t->Struct.offsets != nullptr);
+			if (field_type_) *field_type_ = t->Struct.fields[index]->type;
 			return t->Struct.offsets[index];
 		}
-	} else if (t->kind == Type_Tuple) {
+		break;
+	case Type_Tuple:
 		type_set_offsets(t);
 		if (gb_is_between(index, 0, t->Tuple.variables.count-1)) {
 			GB_ASSERT(t->Tuple.offsets != nullptr);
-			return t->Tuple.offsets[index];
+			if (field_type_) *field_type_ = t->Tuple.variables[index]->type;
+			i64 offset = t->Tuple.offsets[index];
+			GB_ASSERT(offset >= 0);
+			return offset;
 		}
-	}  else if (t->kind == Type_Basic) {
+		break;
+
+	case Type_Array:
+		GB_ASSERT(0 <= index && index < t->Array.count);
+		return index * type_size_of(t->Array.elem);
+
+	case Type_Basic:
 		if (t->Basic.kind == Basic_string) {
 			switch (index) {
-			case 0: return 0;                      // data
-			case 1: return build_context.int_size; // len
+			case 0:
+				if (field_type_) *field_type_ = t_u8_ptr;
+				return 0;                      // data
+			case 1:
+				if (field_type_) *field_type_ = t_int;
+				return build_context.int_size; // len
 			}
 		} else if (t->Basic.kind == Basic_any) {
 			switch (index) {
-			case 0: return 0;                      // type_info
-			case 1: return build_context.ptr_size; // data
+			case 0:
+				if (field_type_) *field_type_ = t_rawptr;
+				return 0;                      // data
+			case 1:
+				if (field_type_) *field_type_ = t_typeid;
+				return build_context.ptr_size; // id
 			}
 		}
-	} else if (t->kind == Type_Slice) {
+		break;
+	case Type_Slice:
 		switch (index) {
-		case 0: return 0;                        // data
-		case 1: return 1*build_context.int_size; // len
-		case 2: return 2*build_context.int_size; // cap
+		case 0:
+			if (field_type_) *field_type_ = alloc_type_multi_pointer(t->Slice.elem);
+			return 0;                        // data
+		case 1:
+			if (field_type_) *field_type_ = t_int;
+			return 1*build_context.int_size; // len
 		}
-	} else if (t->kind == Type_DynamicArray) {
+		break;
+	case Type_DynamicArray:
 		switch (index) {
-		case 0: return 0;                        // data
-		case 1: return 1*build_context.int_size; // len
-		case 2: return 2*build_context.int_size; // cap
-		case 3: return 3*build_context.int_size; // allocator
+		case 0:
+			if (field_type_) *field_type_ = alloc_type_multi_pointer(t->DynamicArray.elem);
+			return 0;                        // data
+		case 1:
+			if (field_type_) *field_type_ = t_int;
+			return 1*build_context.int_size; // len
+		case 2:
+			if (field_type_) *field_type_ = t_int;
+			return 2*build_context.int_size; // cap
+		case 3:
+			if (field_type_) *field_type_ = t_allocator;
+			return 3*build_context.int_size; // allocator
 		}
-	} else if (t->kind == Type_Union) {
-		/* i64 s = */ type_size_of(t);
-		switch (index) {
-		case -1: return align_formula(t->Union.variant_block_size, build_context.ptr_size); // __type_info
+		break;
+	case Type_Union:
+		if (!is_type_union_maybe_pointer(t)) {
+			/* i64 s = */ type_size_of(t);
+			switch (index) {
+			case -1:
+				if (field_type_) *field_type_ = union_tag_type(t);
+				union_tag_size(t);
+				return t->Union.variant_block_size;
+			}
 		}
+		break;
 	}
+	GB_ASSERT(index == 0);
 	return 0;
 }
 
@@ -3965,8 +4020,10 @@ gb_internal i64 type_offset_of_from_selection(Type *type, Selection sel) {
 		i32 index = sel.index[i];
 		t = base_type(t);
 		offset += type_offset_of(t, index);
-		if (t->kind == Type_Struct && !t->Struct.is_raw_union) {
+		if (t->kind == Type_Struct) {
 			t = t->Struct.fields[index]->type;
+		} else if (t->kind == Type_Array) {
+			t = t->Array.elem;
 		} else {
 			// NOTE(bill): No need to worry about custom types, just need the alignment
 			switch (t->kind) {
@@ -4410,11 +4467,11 @@ gb_internal gbString write_type_to_string(gbString str, Type *type, bool shortha
 		str = gb_string_append_fmt(str, ") ");
 		str = write_type_to_string(str, type->RelativePointer.pointer_type);
 		break;
-	case Type_RelativeSlice:
+	case Type_RelativeMultiPointer:
 		str = gb_string_append_fmt(str, "#relative(");
-		str = write_type_to_string(str, type->RelativeSlice.base_integer);
+		str = write_type_to_string(str, type->RelativePointer.base_integer);
 		str = gb_string_append_fmt(str, ") ");
-		str = write_type_to_string(str, type->RelativeSlice.slice_type);
+		str = write_type_to_string(str, type->RelativePointer.pointer_type);
 		break;
 		
 	case Type_Matrix:

+ 14 - 20
tests/core/encoding/hxa/test_core_hxa.odin

@@ -21,16 +21,13 @@ main :: proc() {
 
 @test
 test_read :: proc(t: ^testing.T) {
-
-	using hxa
-
 	filename := tc.get_data_path(t, TEAPOT_PATH)
 	defer delete(filename)
 
-	file, err := read_from_file(filename)
+	file, err := hxa.read_from_file(filename)
 	e :: hxa.Read_Error.None
 	tc.expect(t, err == e, fmt.tprintf("%v: read_from_file(%v) -> %v != %v", #procedure, filename, err, e))
-	defer file_destroy(file)
+	defer hxa.file_destroy(file)
 
 	/* Header */
 	tc.expect(t, file.magic_number == 0x417848, fmt.tprintf("%v: file.magic_number %v != %v",
@@ -134,38 +131,35 @@ test_read :: proc(t: ^testing.T) {
 
 @test
 test_write :: proc(t: ^testing.T) {
-
-	using hxa
-
-	n1 :Node
+	n1: hxa.Node
 
 	n1_m1_value := []f64le{0.4, -1.23, 2341.6, -333.333}
-	n1_m1 := Meta{"m1", n1_m1_value}
+	n1_m1 := hxa.Meta{"m1", n1_m1_value}
 
-	n1.meta_data = []Meta{n1_m1}
+	n1.meta_data = []hxa.Meta{n1_m1}
 
-	n1_l1 := Layer{"l1", 2, []f32le{32.1, -41.3}}
-	n1_l2 := Layer{"l2", 3, []f64le{0.64, 1.64, -2.64}}
+	n1_l1 := hxa.Layer{"l1", 2, []f32le{32.1, -41.3}}
+	n1_l2 := hxa.Layer{"l2", 3, []f64le{0.64, 1.64, -2.64}}
 
-	n1_content := Node_Image{Image_Type.Image_1D, [3]u32le{1, 1, 2}, Layer_Stack{n1_l1, n1_l2}} 
+	n1_content := hxa.Node_Image{.Image_1D, [3]u32le{1, 1, 2}, hxa.Layer_Stack{n1_l1, n1_l2}}
 
 	n1.content = n1_content
 
-	w_file :File
-	w_file.nodes = []Node{n1}
+	w_file: hxa.File
+	w_file.nodes = []hxa.Node{n1}
 
-	required_size := required_write_size(w_file)
+	required_size := hxa.required_write_size(w_file)
 	buf := make([]u8, required_size)
 
-	n, write_err := write(buf, w_file)
+	n, write_err := hxa.write(buf, w_file)
 	write_e :: hxa.Write_Error.None
 	tc.expect(t, write_err == write_e, fmt.tprintf("%v: write_err %v != %v", #procedure, write_err, write_e))
 	tc.expect(t, n == required_size, fmt.tprintf("%v: n %v != %v", #procedure, n, required_size))
 
-	file, read_err := read(buf)
+	file, read_err := hxa.read(buf)
 	read_e :: hxa.Read_Error.None
 	tc.expect(t, read_err == read_e, fmt.tprintf("%v: read_err %v != %v", #procedure, read_err, read_e))
-	defer file_destroy(file)
+	defer hxa.file_destroy(file)
 
 	delete(buf)
 

+ 33 - 36
tests/core/encoding/xml/test_core_xml.odin

@@ -47,7 +47,7 @@ TESTS :: []TEST{
 			},
 			expected_doctype = "恥ずべきフクロウ",
 		},
-		crc32     = 0x30d82264,
+		crc32     = 0xe9b62f03,
 	},
 
 	{
@@ -62,7 +62,7 @@ TESTS :: []TEST{
 			},
 			expected_doctype = "恥ずべきフクロウ",
 		},
-		crc32     = 0xad31d8e8,
+		crc32     = 0x9c2643ed,
 	},
 
 	{
@@ -77,7 +77,7 @@ TESTS :: []TEST{
 			},
 			expected_doctype = "TS",
 		},
-		crc32     = 0x7bce2630,
+		crc32     = 0x859b7443,
 	},
 
 	{
@@ -92,7 +92,7 @@ TESTS :: []TEST{
 			},
 			expected_doctype = "xliff",
 		},
-		crc32     = 0x43f19d61,
+		crc32     = 0x3deaf329,
 	},
 
 	{
@@ -107,7 +107,7 @@ TESTS :: []TEST{
 			},
 			expected_doctype = "xliff",
 		},
-		crc32     = 0x961e7635,
+		crc32     = 0x0c55e287,
 	},
 
 	{
@@ -118,7 +118,7 @@ TESTS :: []TEST{
 			},
 			expected_doctype = "html",
 		},
-		crc32     = 0x573c1033,
+		crc32     = 0x05373317,
 	},
 
 	{
@@ -129,7 +129,7 @@ TESTS :: []TEST{
 			},
 			expected_doctype = "html",
 		},
-		crc32     = 0x82588917,
+		crc32     = 0x3b6d4a90,
 	},
 
 	{
@@ -140,7 +140,7 @@ TESTS :: []TEST{
 			},
 			expected_doctype = "html",
 		},
-		crc32     = 0x5e74d8a6,
+		crc32     = 0x5be2ffdc,
 	},
 
 	/*
@@ -170,7 +170,7 @@ TESTS :: []TEST{
 			expected_doctype = "",
 		},
 		err       = .None,
-		crc32     = 0xcaa042b9,
+		crc32     = 0x420dbac5,
 	},
 }
 
@@ -214,43 +214,40 @@ doc_to_string :: proc(doc: ^xml.Document) -> (result: string) {
 	*/
 	print :: proc(writer: io.Writer, doc: ^xml.Document) -> (written: int, err: io.Error) {
 		if doc == nil { return }
-		using fmt
 
-		written += wprintf(writer, "[XML Prolog]\n")
+		written += fmt.wprintf(writer, "[XML Prolog]\n")
 
 		for attr in doc.prologue {
-			written += wprintf(writer, "\t%v: %v\n", attr.key, attr.val)
+			written += fmt.wprintf(writer, "\t%v: %v\n", attr.key, attr.val)
 		}
 
-		written += wprintf(writer, "[Encoding] %v\n", doc.encoding)
+		written += fmt.wprintf(writer, "[Encoding] %v\n", doc.encoding)
 
 		if len(doc.doctype.ident) > 0 {
-			written += wprintf(writer, "[DOCTYPE]  %v\n", doc.doctype.ident)
+			written += fmt.wprintf(writer, "[DOCTYPE]  %v\n", doc.doctype.ident)
 
 			if len(doc.doctype.rest) > 0 {
-			 	wprintf(writer, "\t%v\n", doc.doctype.rest)
+			 	fmt.wprintf(writer, "\t%v\n", doc.doctype.rest)
 			}
 		}
 
 		for comment in doc.comments {
-			written += wprintf(writer, "[Pre-root comment]  %v\n", comment)
+			written += fmt.wprintf(writer, "[Pre-root comment]  %v\n", comment)
 		}
 
 		if doc.element_count > 0 {
-		 	wprintln(writer, " --- ")
+		 	fmt.wprintln(writer, " --- ")
 		 	print_element(writer, doc, 0)
-		 	wprintln(writer, " --- ")
+		 	fmt.wprintln(writer, " --- ")
 		 }
 
 		return written, .None
 	}
 
 	print_element :: proc(writer: io.Writer, doc: ^xml.Document, element_id: xml.Element_ID, indent := 0) -> (written: int, err: io.Error) {
-		using fmt
-
 		tab :: proc(writer: io.Writer, indent: int) {
 			for _ in 0..=indent {
-				wprintf(writer, "\t")
+				fmt.wprintf(writer, "\t")
 			}
 		}
 
@@ -259,22 +256,24 @@ doc_to_string :: proc(doc: ^xml.Document) -> (result: string) {
 		element := doc.elements[element_id]
 
 		if element.kind == .Element {
-			wprintf(writer, "<%v>\n", element.ident)
-			if len(element.value) > 0 {
-				tab(writer, indent + 1)
-				wprintf(writer, "[Value] %v\n", element.value)
+			fmt.wprintf(writer, "<%v>\n", element.ident)
+
+			for value in element.value {
+				switch v in value {
+				case string:
+					tab(writer, indent + 1)
+					fmt.wprintf(writer, "[Value] %v\n", v)
+				case xml.Element_ID:
+					print_element(writer, doc, v, indent + 1)
+				}
 			}
 
 			for attr in element.attribs {
 				tab(writer, indent + 1)
-				wprintf(writer, "[Attr] %v: %v\n", attr.key, attr.val)
-			}
-
-			for child in element.children {
-				print_element(writer, doc, child, indent + 1)
+				fmt.wprintf(writer, "[Attr] %v: %v\n", attr.key, attr.val)
 			}
 		} else if element.kind == .Comment {
-			wprintf(writer, "[COMMENT] %v\n", element.value)
+			fmt.wprintf(writer, "[COMMENT] %v\n", element.value)
 		}
 
 		return written, .None
@@ -289,8 +288,6 @@ doc_to_string :: proc(doc: ^xml.Document) -> (result: string) {
 
 @test
 run_tests :: proc(t: ^testing.T) {
-	using fmt
-
 	for test in TESTS {
 		path := test_file_path(test.filename)
 		log(t, fmt.tprintf("Trying to parse %v", path))
@@ -305,11 +302,11 @@ run_tests :: proc(t: ^testing.T) {
 		crc32 := hash.crc32(tree_bytes)
 
 		failed := err != test.err
-		err_msg := tprintf("Expected return value %v, got %v", test.err, err)
+		err_msg := fmt.tprintf("Expected return value %v, got %v", test.err, err)
 		expect(t, err == test.err, err_msg)
 
 		failed |= crc32 != test.crc32
-		err_msg  = tprintf("Expected CRC 0x%08x, got 0x%08x, with options %v", test.crc32, crc32, test.options)
+		err_msg  = fmt.tprintf("Expected CRC 0x%08x, got 0x%08x, with options %v", test.crc32, crc32, test.options)
 		expect(t, crc32 == test.crc32, err_msg)
 
 		if failed {
@@ -317,7 +314,7 @@ run_tests :: proc(t: ^testing.T) {
 				Don't fully print big trees.
 			*/
 			tree_string = tree_string[:min(2_048, len(tree_string))]
-			println(tree_string)
+			fmt.println(tree_string)
 		}
 	}
 }

+ 4 - 10
tests/core/math/linalg/glsl/test_linalg_glsl_math.odin

@@ -22,9 +22,6 @@ main :: proc() {
 
 @test
 test_fract_f32 :: proc(t: ^testing.T) {
-
-	using math
-
 	r: f32
 
 	Datum :: struct {
@@ -35,8 +32,8 @@ test_fract_f32 :: proc(t: ^testing.T) {
 	@static data := []Datum{
 		{ 0, 10.5, 0.5 }, // Issue #1574 fract in linalg/glm is broken
 		{ 1, -10.5, -0.5 },
-		{ 2, F32_MIN, F32_MIN }, // 0x1p-126
-		{ 3, -F32_MIN, -F32_MIN },
+		{ 2, math.F32_MIN, math.F32_MIN }, // 0x1p-126
+		{ 3, -math.F32_MIN, -math.F32_MIN },
 		{ 4, 0.0, 0.0 },
 		{ 5, -0.0, -0.0 },
 		{ 6, 1, 0.0 },
@@ -54,9 +51,6 @@ test_fract_f32 :: proc(t: ^testing.T) {
 
 @test
 test_fract_f64 :: proc(t: ^testing.T) {
-
-	using math
-
 	r: f64
 
 	Datum :: struct {
@@ -67,8 +61,8 @@ test_fract_f64 :: proc(t: ^testing.T) {
 	@static data := []Datum{
 		{ 0, 10.5, 0.5 }, // Issue #1574 fract in linalg/glm is broken
 		{ 1, -10.5, -0.5 },
-		{ 2, F64_MIN, F64_MIN }, // 0x1p-1022
-		{ 3, -F64_MIN, -F64_MIN },
+		{ 2, math.F64_MIN, math.F64_MIN }, // 0x1p-1022
+		{ 3, -math.F64_MIN, -math.F64_MIN },
 		{ 4, 0.0, 0.0 },
 		{ 5, -0.0, -0.0 },
 		{ 6, 1, 0.0 },

+ 73 - 94
tests/core/math/test_core_math.odin

@@ -43,11 +43,7 @@ main :: proc() {
 
 @test
 test_classify_f16 :: proc(t: ^testing.T) {
-
-	using math
-	using Float_Class
-
-	r: Float_Class
+	r: math.Float_Class
 
 	Datum :: struct {
 		i: int,
@@ -55,38 +51,34 @@ test_classify_f16 :: proc(t: ^testing.T) {
 		e: math.Float_Class,
 	}
 	@static data := []Datum{
-		{ 0, 1.2, Normal },
-		{ 1, 0h0001, Subnormal },
-		{ 2, 0.0, Zero },
-		{ 3, -0.0, Neg_Zero },
-		{ 4, SNAN_F16, NaN },
-		{ 5, QNAN_F16, NaN },
-		{ 6, INF_F16, Inf },
-		{ 7, NEG_INF_F16, Neg_Inf },
+		{ 0, 1.2, .Normal },
+		{ 1, 0h0001, .Subnormal },
+		{ 2, 0.0, .Zero },
+		{ 3, -0.0, .Neg_Zero },
+		{ 4, math.SNAN_F16, .NaN },
+		{ 5, math.QNAN_F16, .NaN },
+		{ 6, math.INF_F16, .Inf },
+		{ 7, math.NEG_INF_F16, .Neg_Inf },
 	}
 
 	for d, i in data {
 		assert(i == d.i)
-		r = classify_f16(d.v)
+		r = math.classify_f16(d.v)
 		tc.expect(t, r == d.e, fmt.tprintf("i:%d %s(%h) -> %v != %v", i, #procedure, d.v, r, d.e))
 	}
 
 	/* Check all subnormals (exponent 0, 10-bit significand non-zero) */
-	for i :u16 = 1; i < 0x400; i += 1 {
-		v :f16 = transmute(f16)i
-		r = classify_f16(v)
-		e :Float_Class: Subnormal
+	for i in u16(1)..<0x400 {
+		v := transmute(f16)i
+		r = math.classify_f16(v)
+		e :: math.Float_Class.Subnormal
 		tc.expect(t, r == e, fmt.tprintf("i:%d %s(%h) -> %v != %v", i, #procedure, v, r, e))
 	}
 }
 
 @test
 test_classify_f32 :: proc(t: ^testing.T) {
-
-	using math
-	using Float_Class
-
-	r: Float_Class
+	r: math.Float_Class
 
 	Datum :: struct {
 		i: int,
@@ -94,30 +86,26 @@ test_classify_f32 :: proc(t: ^testing.T) {
 		e: math.Float_Class,
 	}
 	@static data := []Datum{
-		{ 0, 1.2, Normal },
-		{ 1, 0h0000_0001, Subnormal },
-		{ 2, 0.0, Zero },
-		{ 3, -0.0, Neg_Zero },
-		{ 4, SNAN_F32, NaN },
-		{ 5, QNAN_F32, NaN },
-		{ 6, INF_F32, Inf },
-		{ 7, NEG_INF_F32, Neg_Inf },
+		{ 0, 1.2, .Normal },
+		{ 1, 0h0000_0001, .Subnormal },
+		{ 2, 0.0, .Zero },
+		{ 3, -0.0, .Neg_Zero },
+		{ 4, math.SNAN_F32, .NaN },
+		{ 5, math.QNAN_F32, .NaN },
+		{ 6, math.INF_F32, .Inf },
+		{ 7, math.NEG_INF_F32, .Neg_Inf },
 	}
 
 	for d, i in data {
 		assert(i == d.i)
-		r = classify_f32(d.v)
+		r = math.classify_f32(d.v)
 		tc.expect(t, r == d.e, fmt.tprintf("i:%d %s(%h) -> %v != %v", i, #procedure, d.v, r, d.e))
 	}
 }
 
 @test
 test_classify_f64 :: proc(t: ^testing.T) {
-
-	using math
-	using Float_Class
-
-	r: Float_Class
+	r: math.Float_Class
 
 	Datum :: struct {
 		i: int,
@@ -125,28 +113,25 @@ test_classify_f64 :: proc(t: ^testing.T) {
 		e: math.Float_Class,
 	}
 	@static data := []Datum{
-		{ 0, 1.2, Normal },
-		{ 1, 0h0000_0000_0000_0001, Subnormal },
-		{ 2, 0.0, Zero },
-		{ 3, -0.0, Neg_Zero },
-		{ 4, SNAN_F64, NaN },
-		{ 5, QNAN_F64, NaN },
-		{ 6, INF_F64, Inf },
-		{ 7, NEG_INF_F64, Neg_Inf },
+		{ 0, 1.2, .Normal },
+		{ 1, 0h0000_0000_0000_0001, .Subnormal },
+		{ 2, 0.0, .Zero },
+		{ 3, -0.0, .Neg_Zero },
+		{ 4, math.SNAN_F64, .NaN },
+		{ 5, math.QNAN_F64, .NaN },
+		{ 6, math.INF_F64, .Inf },
+		{ 7, math.NEG_INF_F64, .Neg_Inf },
 	}
 
 	for d, i in data {
 		assert(i == d.i)
-		r = classify_f64(d.v)
+		r = math.classify_f64(d.v)
 		tc.expect(t, r == d.e, fmt.tprintf("i:%d %s(%h) -> %v != %v", i, #procedure, d.v, r, d.e))
 	}
 }
 
 @test
 test_trunc_f16 :: proc(t: ^testing.T) {
-
-	using math
-
 	r, v: f16
 
 	Datum :: struct {
@@ -158,16 +143,16 @@ test_trunc_f16 :: proc(t: ^testing.T) {
 		{ 0, 10.5, 10 }, // Issue #1574 fract in linalg/glm is broken
 		{ 1, -10.5, -10 },
 
-		{ 2, F16_MAX, F16_MAX },
-		{ 3, -F16_MAX, -F16_MAX },
-		{ 4, F16_MIN, 0.0 },
-		{ 5, -F16_MIN, -0.0 },
+		{ 2, math.F16_MAX, math.F16_MAX },
+		{ 3, -math.F16_MAX, -math.F16_MAX },
+		{ 4, math.F16_MIN, 0.0 },
+		{ 5, -math.F16_MIN, -0.0 },
 		{ 6, 0.0, 0.0 },
 		{ 7, -0.0, -0.0 },
 		{ 8, 1, 1 },
 		{ 9, -1, -1 },
-		{ 10, INF_F16, INF_F16 },
-		{ 11, NEG_INF_F16, NEG_INF_F16 },
+		{ 10, math.INF_F16, math.INF_F16 },
+		{ 11, math.NEG_INF_F16, math.NEG_INF_F16 },
 
 		/* From https://en.wikipedia.org/wiki/Half-precision_floating-point_format */
 		{ 12, 0h3C01, 1 }, // 0x1.004p+0 (smallest > 1)
@@ -185,24 +170,21 @@ test_trunc_f16 :: proc(t: ^testing.T) {
 
 	for d, i in data {
 		assert(i == d.i)
-		r = trunc_f16(d.v)
+		r = math.trunc_f16(d.v)
 		tc.expect(t, r == d.e, fmt.tprintf("i:%d %s(%h) -> %h != %h", i, #procedure, d.v, r, d.e))
 	}
 
-	v = SNAN_F16
-	r = trunc_f16(v)
-	tc.expect(t, is_nan_f16(r), fmt.tprintf("%s(%f) -> %f != NaN", #procedure, v, r))
+	v = math.SNAN_F16
+	r = math.trunc_f16(v)
+	tc.expect(t, math.is_nan_f16(r), fmt.tprintf("%s(%f) -> %f != NaN", #procedure, v, r))
 
-	v = QNAN_F16
-	r = trunc_f16(v)
-	tc.expect(t, is_nan_f16(r), fmt.tprintf("%s(%f) -> %f != NaN", #procedure, v, r))
+	v = math.QNAN_F16
+	r = math.trunc_f16(v)
+	tc.expect(t, math.is_nan_f16(r), fmt.tprintf("%s(%f) -> %f != NaN", #procedure, v, r))
 }
 
 @test
 test_trunc_f32 :: proc(t: ^testing.T) {
-
-	using math
-
 	r, v: f32
 
 	Datum :: struct {
@@ -214,16 +196,16 @@ test_trunc_f32 :: proc(t: ^testing.T) {
 		{ 0, 10.5, 10 }, // Issue #1574 fract in linalg/glm is broken
 		{ 1, -10.5, -10 },
 
-		{ 2, F32_MAX, F32_MAX },
-		{ 3, -F32_MAX, -F32_MAX },
-		{ 4, F32_MIN, 0.0 },
-		{ 5, -F32_MIN, -0.0 },
+		{ 2, math.F32_MAX, math.F32_MAX },
+		{ 3, -math.F32_MAX, -math.F32_MAX },
+		{ 4, math.F32_MIN, 0.0 },
+		{ 5, -math.F32_MIN, -0.0 },
 		{ 6, 0.0, 0.0 },
 		{ 7, -0.0, -0.0 },
 		{ 8, 1, 1 },
 		{ 9, -1, -1 },
-		{ 10, INF_F32, INF_F32 },
-		{ 11, NEG_INF_F32, NEG_INF_F32 },
+		{ 10, math.INF_F32, math.INF_F32 },
+		{ 11, math.NEG_INF_F32, math.NEG_INF_F32 },
 
 		/* From https://en.wikipedia.org/wiki/Single-precision_floating-point_format */
 		{ 12, 0h3F80_0001, 1 }, // 0x1.000002p+0 (smallest > 1)
@@ -250,24 +232,21 @@ test_trunc_f32 :: proc(t: ^testing.T) {
 
 	for d, i in data {
 		assert(i == d.i)
-		r = trunc_f32(d.v)
+		r = math.trunc_f32(d.v)
 		tc.expect(t, r == d.e, fmt.tprintf("i:%d %s(%h) -> %h != %h", i, #procedure, d.v, r, d.e))
 	}
 
-	v = SNAN_F32
-	r = trunc_f32(v)
-	tc.expect(t, is_nan_f32(r), fmt.tprintf("%s(%f) -> %f != NaN", #procedure, v, r))
+	v = math.SNAN_F32
+	r = math.trunc_f32(v)
+	tc.expect(t, math.is_nan_f32(r), fmt.tprintf("%s(%f) -> %f != NaN", #procedure, v, r))
 
-	v = QNAN_F32
-	r = trunc_f32(v)
-	tc.expect(t, is_nan_f32(r), fmt.tprintf("%s(%f) -> %f != NaN", #procedure, v, r))
+	v = math.QNAN_F32
+	r = math.trunc_f32(v)
+	tc.expect(t, math.is_nan_f32(r), fmt.tprintf("%s(%f) -> %f != NaN", #procedure, v, r))
 }
 
 @test
 test_trunc_f64 :: proc(t: ^testing.T) {
-
-	using math
-
 	r, v: f64
 
 	Datum :: struct {
@@ -279,16 +258,16 @@ test_trunc_f64 :: proc(t: ^testing.T) {
 		{ 0, 10.5, 10 }, // Issue #1574 fract in linalg/glm is broken
 		{ 1, -10.5, -10 },
 
-		{ 2, F64_MAX, F64_MAX },
-		{ 3, -F64_MAX, -F64_MAX },
-		{ 4, F64_MIN, 0.0 },
-		{ 5, -F64_MIN, -0.0 },
+		{ 2, math.F64_MAX, math.F64_MAX },
+		{ 3, -math.F64_MAX, -math.F64_MAX },
+		{ 4, math.F64_MIN, 0.0 },
+		{ 5, -math.F64_MIN, -0.0 },
 		{ 6, 0.0, 0.0 },
 		{ 7, -0.0, -0.0 },
 		{ 8, 1, 1 },
 		{ 9, -1, -1 },
-		{ 10, INF_F64, INF_F64 },
-		{ 11, NEG_INF_F64, NEG_INF_F64 },
+		{ 10, math.INF_F64, math.INF_F64 },
+		{ 11, math.NEG_INF_F64, math.NEG_INF_F64 },
 
 		/* From https://en.wikipedia.org/wiki/Double-precision_floating-point_format */
 		{ 12, 0h3FF0_0000_0000_0001, 1 }, // 0x1.0000000000001p+0 (smallest > 1)
@@ -315,17 +294,17 @@ test_trunc_f64 :: proc(t: ^testing.T) {
 
 	for d, i in data {
 		assert(i == d.i)
-		r = trunc_f64(d.v)
+		r = math.trunc_f64(d.v)
 		tc.expect(t, r == d.e, fmt.tprintf("i:%d %s(%h) -> %h != %h", i, #procedure, d.v, r, d.e))
 	}
 
-	v = SNAN_F64
-	r = trunc_f64(v)
-	tc.expect(t, is_nan_f64(r), fmt.tprintf("%s(%f) -> %f != NaN", #procedure, v, r))
+	v = math.SNAN_F64
+	r = math.trunc_f64(v)
+	tc.expect(t, math.is_nan_f64(r), fmt.tprintf("%s(%f) -> %f != NaN", #procedure, v, r))
 
-	v = QNAN_F64
-	r = trunc_f64(v)
-	tc.expect(t, is_nan_f64(r), fmt.tprintf("%s(%f) -> %f != NaN", #procedure, v, r))
+	v = math.QNAN_F64
+	r = math.trunc_f64(v)
+	tc.expect(t, math.is_nan_f64(r), fmt.tprintf("%s(%f) -> %f != NaN", #procedure, v, r))
 }
 
 

+ 6 - 12
tests/core/path/filepath/test_core_filepath.odin

@@ -22,9 +22,6 @@ main :: proc() {
 
 @test
 test_split_list_windows :: proc(t: ^testing.T) {
-
-	using filepath
-
 	Datum :: struct {
 		i: int,
 		v: string,
@@ -43,7 +40,7 @@ test_split_list_windows :: proc(t: ^testing.T) {
 
 	for d, i in data {
 		assert(i == d.i, fmt.tprintf("wrong data index: i %d != d.i %d\n", i, d.i))
-		r := split_list(d.v)
+		r := filepath.split_list(d.v)
 		defer delete(r)
 		tc.expect(t, len(r) == len(d.e), fmt.tprintf("i:%d %s(%s) len(r) %d != len(d.e) %d",
 													 i, #procedure, d.v, len(r), len(d.e)))
@@ -57,12 +54,12 @@ test_split_list_windows :: proc(t: ^testing.T) {
 
 	{
 		v := ""
-		r := split_list(v)
+		r := filepath.split_list(v)
 		tc.expect(t, r == nil, fmt.tprintf("%s(%s) -> %v != nil", #procedure, v, r))
 	}
 	{
 		v := "a"
-		r := split_list(v)
+		r := filepath.split_list(v)
 		defer delete(r)
 		tc.expect(t, len(r) == 1, fmt.tprintf("%s(%s) len(r) %d != 1", #procedure, v, len(r)))
 		if len(r) == 1 {
@@ -73,9 +70,6 @@ test_split_list_windows :: proc(t: ^testing.T) {
 
 @test
 test_split_list_unix :: proc(t: ^testing.T) {
-
-	using filepath
-
 	Datum :: struct {
 		i: int,
 		v: string,
@@ -94,7 +88,7 @@ test_split_list_unix :: proc(t: ^testing.T) {
 
 	for d, i in data {
 		assert(i == d.i, fmt.tprintf("wrong data index: i %d != d.i %d\n", i, d.i))
-		r := split_list(d.v)
+		r := filepath.split_list(d.v)
 		defer delete(r)
 		tc.expect(t, len(r) == len(d.e), fmt.tprintf("i:%d %s(%s) len(r) %d != len(d.e) %d",
 													 i, #procedure, d.v, len(r), len(d.e)))
@@ -108,12 +102,12 @@ test_split_list_unix :: proc(t: ^testing.T) {
 
 	{
 		v := ""
-		r := split_list(v)
+		r := filepath.split_list(v)
 		tc.expect(t, r == nil, fmt.tprintf("%s(%s) -> %v != nil", #procedure, v, r))
 	}
 	{
 		v := "a"
-		r := split_list(v)
+		r := filepath.split_list(v)
 		defer delete(r)
 		tc.expect(t, len(r) == 1, fmt.tprintf("%s(%s) len(r) %d != 1", #procedure, v, len(r)))
 		if len(r) == 1 {

+ 16 - 20
tests/core/reflect/test_core_reflect.odin

@@ -19,8 +19,6 @@ main :: proc() {
 
 @test
 test_as_u64 :: proc(t: ^testing.T) {
-	using reflect
-
 	{
 		/* i8 */
 		Datum :: struct { i: int, v: i8, e: u64 }
@@ -32,7 +30,7 @@ test_as_u64 :: proc(t: ^testing.T) {
 
 		for d, i in data {
 			assert(i == d.i)
-			r, valid := as_u64(d.v)
+			r, valid := reflect.as_u64(d.v)
 			tc.expect(t, valid, fmt.tprintf("i:%d %s(i8 %v) !valid\n", i, #procedure, d.v))
 			tc.expect(t, r == d.e, fmt.tprintf("i:%d %s(i8 %v) -> %v (0x%X) != %v (0x%X)\n",
 												i, #procedure, d.v, r, r, d.e, d.e))
@@ -49,7 +47,7 @@ test_as_u64 :: proc(t: ^testing.T) {
 
 		for d, i in data {
 			assert(i == d.i)
-			r, valid := as_u64(d.v)
+			r, valid := reflect.as_u64(d.v)
 			tc.expect(t, valid, fmt.tprintf("i:%d %s(i16 %v) !valid\n", i, #procedure, d.v))
 			tc.expect(t, r == d.e, fmt.tprintf("i:%d %s(i16 %v) -> %v (0x%X) != %v (0x%X)\n",
 												i, #procedure, d.v, r, r, d.e, d.e))
@@ -66,7 +64,7 @@ test_as_u64 :: proc(t: ^testing.T) {
 
 		for d, i in data {
 			assert(i == d.i)
-			r, valid := as_u64(d.v)
+			r, valid := reflect.as_u64(d.v)
 			tc.expect(t, valid, fmt.tprintf("i:%d %s(i32 %v) !valid\n", i, #procedure, d.v))
 			tc.expect(t, r == d.e, fmt.tprintf("i:%d %s(i32 %v) -> %v (0x%X) != %v (0x%X)\n",
 												i, #procedure, d.v, r, r, d.e, d.e))
@@ -83,7 +81,7 @@ test_as_u64 :: proc(t: ^testing.T) {
 
 		for d, i in data {
 			assert(i == d.i)
-			r, valid := as_u64(d.v)
+			r, valid := reflect.as_u64(d.v)
 			tc.expect(t, valid, fmt.tprintf("i:%d %s(i64 %v) !valid\n", i, #procedure, d.v))
 			tc.expect(t, r == d.e, fmt.tprintf("i:%d %s(i64 %v) -> %v (0x%X) != %v (0x%X)\n",
 												i, #procedure, d.v, r, r, d.e, d.e))
@@ -103,7 +101,7 @@ test_as_u64 :: proc(t: ^testing.T) {
 
 		for d, i in data {
 			assert(i == d.i)
-			r, valid := as_u64(d.v)
+			r, valid := reflect.as_u64(d.v)
 			tc.expect(t, valid, fmt.tprintf("i:%d %s(i128 %v) !valid\n", i, #procedure, d.v))
 			tc.expect(t, r == d.e, fmt.tprintf("i:%d %s(i128 %v) -> %v (0x%X) != %v (0x%X)\n",
 												i, #procedure, d.v, r, r, d.e, d.e))
@@ -119,7 +117,7 @@ test_as_u64 :: proc(t: ^testing.T) {
 
 		for d, i in data {
 			assert(i == d.i)
-			r, valid := as_u64(d.v)
+			r, valid := reflect.as_u64(d.v)
 			tc.expect(t, valid, fmt.tprintf("i:%d %s(f16 %v) !valid\n", i, #procedure, d.v))
 			tc.expect(t, r == d.e, fmt.tprintf("i:%d %s(f16 %v) -> %v != %v\n", i, #procedure, d.v, r, d.e))
 		}
@@ -133,7 +131,7 @@ test_as_u64 :: proc(t: ^testing.T) {
 
 		for d, i in data {
 			assert(i == d.i)
-			r, valid := as_u64(d.v)
+			r, valid := reflect.as_u64(d.v)
 			tc.expect(t, valid, fmt.tprintf("i:%d %s(f32 %v) !valid\n", i, #procedure, d.v))
 			tc.expect(t, r == d.e, fmt.tprintf("i:%d %s(f32 %v) -> %v != %v\n", i, #procedure, d.v, r, d.e))
 		}
@@ -147,7 +145,7 @@ test_as_u64 :: proc(t: ^testing.T) {
 
 		for d, i in data {
 			assert(i == d.i)
-			r, valid := as_u64(d.v)
+			r, valid := reflect.as_u64(d.v)
 			tc.expect(t, valid, fmt.tprintf("i:%d %s(f64 %v) !valid\n", i, #procedure, d.v))
 			tc.expect(t, r == d.e, fmt.tprintf("i:%d %s(f64 %v) -> %v != %v\n", i, #procedure, d.v, r, d.e))
 		}
@@ -156,8 +154,6 @@ test_as_u64 :: proc(t: ^testing.T) {
 
 @test
 test_as_f64 :: proc(t: ^testing.T) {
-	using reflect
-
 	{
 		/* i8 */
 		Datum :: struct { i: int, v: i8, e: f64 }
@@ -169,7 +165,7 @@ test_as_f64 :: proc(t: ^testing.T) {
 
 		for d, i in data {
 			assert(i == d.i)
-			r, valid := as_f64(d.v)
+			r, valid := reflect.as_f64(d.v)
 			tc.expect(t, valid, fmt.tprintf("i:%d %s(i8 %v) !valid\n", i, #procedure, d.v))
 			tc.expect(t, r == d.e, fmt.tprintf("i:%d %s(i8 %v) -> %v != %v\n", i, #procedure, d.v, r, d.e))
 		}
@@ -185,7 +181,7 @@ test_as_f64 :: proc(t: ^testing.T) {
 
 		for d, i in data {
 			assert(i == d.i)
-			r, valid := as_f64(d.v)
+			r, valid := reflect.as_f64(d.v)
 			tc.expect(t, valid, fmt.tprintf("i:%d %s(i16 %v) !valid\n", i, #procedure, d.v))
 			tc.expect(t, r == d.e, fmt.tprintf("i:%d %s(i16 %v) -> %v != %v\n", i, #procedure, d.v, r, d.e))
 		}
@@ -201,7 +197,7 @@ test_as_f64 :: proc(t: ^testing.T) {
 
 		for d, i in data {
 			assert(i == d.i)
-			r, valid := as_f64(d.v)
+			r, valid := reflect.as_f64(d.v)
 			tc.expect(t, valid, fmt.tprintf("i:%d %s(i32 %v) !valid\n", i, #procedure, d.v))
 			tc.expect(t, r == d.e, fmt.tprintf("i:%d %s(i32 %v) -> %v != %v\n", i, #procedure, d.v, r, d.e))
 		}
@@ -217,7 +213,7 @@ test_as_f64 :: proc(t: ^testing.T) {
 
 		for d, i in data {
 			assert(i == d.i)
-			r, valid := as_f64(d.v)
+			r, valid := reflect.as_f64(d.v)
 			tc.expect(t, valid, fmt.tprintf("i:%d %s(i64 %v) !valid\n", i, #procedure, d.v))
 			tc.expect(t, r == d.e, fmt.tprintf("i:%d %s(i64 %v) -> %v != %v\n", i, #procedure, d.v, r, d.e))
 		}
@@ -234,7 +230,7 @@ test_as_f64 :: proc(t: ^testing.T) {
 
 		for d, i in data {
 			assert(i == d.i)
-			r, valid := as_f64(d.v)
+			r, valid := reflect.as_f64(d.v)
 			tc.expect(t, valid, fmt.tprintf("i:%d %s(i128 %v) !valid\n", i, #procedure, d.v))
 			tc.expect(t, r == d.e, fmt.tprintf("i:%d %s(i128 %v) -> %v (%H) != %v (%H)\n",
 												i, #procedure, d.v, r, r, d.e, d.e))
@@ -250,7 +246,7 @@ test_as_f64 :: proc(t: ^testing.T) {
 
 		for d, i in data {
 			assert(i == d.i)
-			r, valid := as_f64(d.v)
+			r, valid := reflect.as_f64(d.v)
 			tc.expect(t, valid, fmt.tprintf("i:%d %s(f16 %v) !valid\n", i, #procedure, d.v))
 			tc.expect(t, r == d.e, fmt.tprintf("i:%d %s(f16 %v (%H)) -> %v (%H) != %v (%H)\n",
 												i, #procedure, d.v, d.v, r, r, d.e, d.e))
@@ -265,7 +261,7 @@ test_as_f64 :: proc(t: ^testing.T) {
 
 		for d, i in data {
 			assert(i == d.i)
-			r, valid := as_f64(d.v)
+			r, valid := reflect.as_f64(d.v)
 			tc.expect(t, valid, fmt.tprintf("i:%d %s(f32 %v) !valid\n", i, #procedure, d.v))
 			tc.expect(t, r == d.e, fmt.tprintf("i:%d %s(f32 %v (%H)) -> %v (%H) != %v (%H)\n",
 												i, #procedure, d.v, d.v, r, r, d.e, d.e))
@@ -280,7 +276,7 @@ test_as_f64 :: proc(t: ^testing.T) {
 
 		for d, i in data {
 			assert(i == d.i)
-			r, valid := as_f64(d.v)
+			r, valid := reflect.as_f64(d.v)
 			tc.expect(t, valid, fmt.tprintf("i:%d %s(f64 %v) !valid\n", i, #procedure, d.v))
 			tc.expect(t, r == d.e, fmt.tprintf("i:%d %s(f64 %v) -> %v != %v\n", i, #procedure, d.v, r, d.e))
 		}

+ 2 - 6
tests/core/text/i18n/test_core_text_i18n.odin

@@ -118,8 +118,6 @@ TESTS := []Test_Suite{
 
 @test
 tests :: proc(t: ^testing.T) {
-	using fmt
-
 	cat: ^i18n.Translation
 	err: i18n.Error
 
@@ -142,8 +140,6 @@ tests :: proc(t: ^testing.T) {
 }
 
 main :: proc() {
-	using fmt
-
 	track: mem.Tracking_Allocator
 	mem.tracking_allocator_init(&track, context.allocator)
 	context.allocator = mem.tracking_allocator(&track)
@@ -157,9 +153,9 @@ main :: proc() {
 	}
 
 	if len(track.allocation_map) > 0 {
-		println()
+		fmt.println()
 		for _, v in track.allocation_map {
-			printf("%v Leaked %v bytes.\n", v.location, v.size)
+			fmt.printf("%v Leaked %v bytes.\n", v.location, v.size)
 		}
 	}
 }

+ 1 - 0
tests/issues/run.bat

@@ -15,6 +15,7 @@ set COMMON=-collection:tests=..\..
 ..\..\..\odin test ..\test_issue_2466.odin %COMMON% -file || exit /b
 ..\..\..\odin test ..\test_issue_2615.odin %COMMON% -file || exit /b
 ..\..\..\odin test ..\test_issue_2637.odin %COMMON% -file || exit /b
+..\..\..\odin test ..\test_issue_2666.odin %COMMON% -file || exit /b
 
 @echo off
 

+ 1 - 0
tests/issues/run.sh

@@ -18,6 +18,7 @@ $ODIN build ../test_issue_2113.odin $COMMON -file -debug
 $ODIN test ../test_issue_2466.odin $COMMON -file
 $ODIN test ../test_issue_2615.odin $COMMON -file
 $ODIN test ../test_issue_2637.odin $COMMON -file
+$ODIN test ../test_issue_2666.odin $COMMON -file
 if [[ $($ODIN build ../test_issue_2395.odin $COMMON -file 2>&1 >/dev/null | grep -c "$NO_NIL_ERR") -eq 2 ]] ; then
 	echo "SUCCESSFUL 1/1"
 else

+ 26 - 0
tests/issues/test_issue_2666.odin

@@ -0,0 +1,26 @@
+// Tests issue https://github.com/odin-lang/Odin/issues/2666
+// @(disabled=<boolean>) does not work with polymorphic procs
+package test_issues
+
+import "core:testing"
+
+@(test)
+test_disabled_parapoly :: proc(t: ^testing.T) {
+	disabled_parapoly(t, 1)
+	disabled_parapoly_constant(t, 1)
+}
+
+@(private="file")
+@(disabled = true)
+disabled_parapoly :: proc(t: ^testing.T, num: $T) {
+	testing.error(t, "disabled_parapoly should be disabled")
+}
+
+@(private="file")
+DISABLE :: true
+
+@(disabled = DISABLE)
+@(private = "file")
+disabled_parapoly_constant :: proc(t: ^testing.T, num: $T) {
+	testing.error(t, "disabled_parapoly_constant should be disabled")
+}

+ 2 - 2
vendor/OpenGL/helpers.odin

@@ -5,7 +5,9 @@ package vendor_gl
 import "core:os"
 import "core:fmt"
 import "core:strings"
+import "core:runtime"
 _ :: fmt
+_ :: runtime
 
 Shader_Type :: enum i32 {
 	NONE = 0x0000,
@@ -47,8 +49,6 @@ get_last_error_message :: proc() -> (compile_message: string, compile_type: Shad
 // except for calling differently named GL functions
 // it's a bit ugly looking, but meh
 when GL_DEBUG {
-	import "core:runtime"
-	
 	@private
 	check_error :: proc(
 		id: u32, type: Shader_Type, status: u32,

+ 5 - 3
vendor/OpenGL/wrappers.odin

@@ -2,6 +2,11 @@ package vendor_gl
 
 #assert(size_of(bool) == size_of(u8))
 
+import "core:runtime"
+import "core:fmt"
+_ :: runtime
+_ :: fmt
+
 when !GL_DEBUG {
 	// VERSION_1_0
 	CullFace               :: proc "c" (mode: u32)                                                                                         {        impl_CullFace(mode)                                                                         }
@@ -756,9 +761,6 @@ when !GL_DEBUG {
 	MultiDrawElementsIndirectCount :: proc "c" (mode: i32, type: i32, indirect: [^]DrawElementsIndirectCommand, drawcount: i32, maxdrawcount, stride: i32)      { impl_MultiDrawElementsIndirectCount(mode, type, indirect, drawcount, maxdrawcount, stride)             }
 	PolygonOffsetClamp             :: proc "c" (factor, units, clamp: f32)                                                                                      { impl_PolygonOffsetClamp(factor, units, clamp)                                                          }
 } else {
-	import "core:runtime"
-	import "core:fmt"
-
 	debug_helper :: proc"c"(from_loc: runtime.Source_Code_Location, num_ret: int, args: ..any, loc := #caller_location) {
 		context = runtime.default_context()
 

+ 2 - 3
vendor/darwin/Foundation/NSNumber.odin

@@ -1,9 +1,8 @@
 package objc_Foundation
 
+import "core:c"
+_ :: c
 when ODIN_OS == .Darwin {
-	import "core:c"
-	_ :: c
-
 	#assert(size_of(c.long)  == size_of(int))
 	#assert(size_of(c.ulong) == size_of(uint))
 }

+ 14 - 0
vendor/directx/d3d11/d3d11.odin

@@ -3551,6 +3551,8 @@ PARAMETER_DESC :: struct {
 	FirstOutComponent: u32,
 }
 
+ID3D11ShaderReflectionType_UUID_STRING :: "6E6FFA6A-9BAE-4613-A51E-91652D508C21"
+ID3D11ShaderReflectionType_UUID := &IID{0x6E6FFA6A, 0x9BAE, 0x4613, {0xA5, 0x1E, 0x91, 0x65, 0x2D, 0x50, 0x8C, 0x21}}
 IShaderReflectionType :: struct {
 	using vtable: ^IShaderReflectionType_VTable,
 }
@@ -3568,6 +3570,8 @@ IShaderReflectionType_VTable :: struct {
 	ImplementsInterface:  proc "stdcall" (this: ^IShaderReflectionType, pBase: ^IShaderReflectionType) -> HRESULT,
 }
 
+ID3D11ShaderReflectionVariable_UUID_STRING :: "51F23923-F3E5-4BD1-91CB-606177D8DB4C"
+ID3D11ShaderReflectionVariable_UUID := &IID{0x51F23923, 0xF3E5, 0x4BD1, {0x91, 0xCB, 0x60, 0x61, 0x77, 0xD8, 0xDB, 0x4C}}
 IShaderReflectionVariable :: struct {
 	using vtable: ^IShaderReflectionVariable_VTable,
 }
@@ -3578,6 +3582,8 @@ IShaderReflectionVariable_VTable :: struct {
 	GetInterfaceSlot: proc "stdcall" (this: ^IShaderReflectionVariable, uArrayIndex: u32) -> u32,
 }
 
+ID3D11ShaderReflectionConstantBuffer_UUID_STRING :: "EB62D63D-93DD-4318-8AE8-C6F83AD371B8"
+ID3D11ShaderReflectionConstantBuffer_UUID := &IID{0xEB62D63D, 0x93DD, 0x4318, {0x8A, 0xE8, 0xC6, 0xF8, 0x3A, 0xD3, 0x71, 0xB8}}
 IShaderReflectionConstantBuffer :: struct {
 	using vtable: ^IShaderReflectionConstantBuffer_VTable,
 }
@@ -3588,6 +3594,8 @@ IShaderReflectionConstantBuffer_VTable :: struct {
 }
 
 
+ID3D11ShaderReflection_UUID_STRING :: "8D536CA1-0CCA-4956-A837-786963755584"
+ID3D11ShaderReflection_UUID := &IID{0x8D536CA1, 0x0CCA, 0x4956, {0xA8, 0x37, 0x78, 0x69, 0x63, 0x75, 0x55, 0x84}}
 IShaderReflection :: struct #raw_union {
 	#subtype iunknown: IUnknown,
 	using id3d11shaderreflection_vtable: ^IShaderReflection_VTable,
@@ -3616,6 +3624,8 @@ IShaderReflection_VTable :: struct {
 }
 
 
+ID3D11LibraryReflection_UUID_STRING :: "54384F1B-5B3E-4BB7-AE01-60BA3097CBB6"
+ID3D11LibraryReflection_UUID := &IID{0x54384F1B, 0x5B3E, 0x4BB7, {0xAE, 0x1, 0x60, 0xBA, 0x30, 0x97, 0xCD, 0xB6}}
 ILibraryReflection :: struct #raw_union {
 	#subtype iunknown: IUnknown,
 	using id3d11libraryreflection_vtable: ^ILibraryReflection_VTable,
@@ -3626,6 +3636,8 @@ ILibraryReflection_VTable :: struct {
 	GetFunctionByIndex: proc "stdcall" (this: ^ILibraryReflection, FunctionIndex: i32) -> ^IFunctionReflection,
 }
 
+ID3D11FunctionReflection_UUID_STRING :: "207BCECB-D683-4A06-A8A3-9B149B9F73A4"
+ID3D11FunctionReflection_UUID := &IID{0x207BCECB, 0xD683, 0x4A06, {0xA8, 0xA3, 0x9B, 0x14, 0x9B, 0x9F, 0x73, 0xA4}}
 IFunctionReflection :: struct {
 	using vtable: ^IFunctionReflection_VTable,
 }
@@ -3639,6 +3651,8 @@ IFunctionReflection_VTable :: struct {
 	GetFunctionParameter:         proc "stdcall" (this: ^IFunctionReflection, ParameterIndex: i32) -> ^IFunctionParameterReflection,
 }
 
+ID3D11FunctionParameterReflection_UUID_STRING :: "42757488-334F-47FE-982E-1A65D08CC462"
+ID3D11FunctionParameterReflection_UUID := &IID{0x42757488, 0x334f, 0x47FE, {0x98, 0x2E, 0x1A, 0x65, 0xD0, 0x8C, 0xC4, 0x62}}
 IFunctionParameterReflection :: struct {
 	using vtable: ^IFunctionParameterReflection_VTable,
 }

+ 3 - 0
vendor/directx/d3d_compiler/d3d_compiler.odin

@@ -149,6 +149,9 @@ ID3DInclude_VTable :: struct {
 	Close: proc "stdcall" (this: ^ID3DInclude, pData: rawptr) -> HRESULT,
 }
 
+// Default file includer
+D3DCOMPILE_STANDARD_FILE_INCLUDE :: (^ID3DInclude)(uintptr(1))
+
 
 ID3D11Module :: struct #raw_union {
 	#subtype iunknown: IUnknown,

Einige Dateien werden nicht angezeigt, da zu viele Dateien in diesem Diff geändert wurden.