Explorar o código

Numpty forgot to add .c files

Ginger Bill %!s(int64=8) %!d(string=hai) anos
pai
achega
7792f009b8
Modificáronse 22 ficheiros con 23799 adicións e 0 borrados
  1. 2 0
      README.md
  2. BIN=BIN
      logo-slim.png
  3. 235 0
      src/array.c
  4. 1353 0
      src/checker/checker.c
  5. 545 0
      src/checker/decl.c
  6. 179 0
      src/checker/entity.c
  7. 4465 0
      src/checker/expr.c
  8. 1130 0
      src/checker/stmt.c
  9. 1487 0
      src/checker/types.c
  10. 195 0
      src/common.c
  11. 400 0
      src/exact_value.c
  12. 272 0
      src/main.c
  13. 1305 0
      src/old_vm.c
  14. 3250 0
      src/parser.c
  15. 221 0
      src/printer.c
  16. 5419 0
      src/ssa.c
  17. 493 0
      src/ssa_opt.c
  18. 1439 0
      src/ssa_print.c
  19. 422 0
      src/string.c
  20. 105 0
      src/timings.c
  21. 816 0
      src/tokenizer.c
  22. 66 0
      src/unicode.c

+ 2 - 0
README.md

@@ -1,3 +1,5 @@
+<img src="logo.png" alt="Odin logo" height="74">
+
 # The Odin Programming Language
 
 Odin is fast, concise, readable, pragmatic and open sourced. It is designed with the intent of replacing C with the following goals:

BIN=BIN
logo-slim.png


+ 235 - 0
src/array.c

@@ -0,0 +1,235 @@
+#define ARRAY_GROW_FORMULA(x) (2*(x) + 8)
+GB_STATIC_ASSERT(ARRAY_GROW_FORMULA(0) > 0);
+
+#define Array(Type_) struct { \
+	gbAllocator allocator; \
+	Type_ *     e; \
+	isize       count; \
+	isize       capacity; \
+}
+
+typedef Array(void) ArrayVoid;
+
+#define array_init_reserve(x_, allocator_, init_capacity_) do { \
+	GB_ASSERT((x_) != NULL); \
+	void **e = cast(void **)&((x_)->e); \
+	(x_)->allocator = (allocator_); \
+	(x_)->count = 0; \
+	(x_)->capacity = (init_capacity_); \
+	*e = gb_alloc((allocator_), gb_size_of(*(x_)->e)*(init_capacity_)); \
+} while (0)
+
+#define array_init_count(x_, allocator_, init_count_) do { \
+	GB_ASSERT((x_) != NULL); \
+	void **e = cast(void **)&((x_)->e); \
+	(x_)->allocator = (allocator_); \
+	(x_)->count = (init_count_); \
+	(x_)->capacity = (init_count_); \
+	*e = gb_alloc((allocator_), gb_size_of(*(x_)->e)*(init_count_)); \
+} while (0)
+
+#define array_init(x_, allocator_)        do { array_init_reserve(x_, allocator_, ARRAY_GROW_FORMULA(0)); } while (0)
+#define array_free(x_)                    do { gb_free((x_)->allocator, (x_)->e); } while (0)
+#define array_set_capacity(x_, capacity_) do { array__set_capacity((x_), (capacity_), gb_size_of(*(x_)->e)); } while (0)
+
+#define array_grow(x_, min_capacity_) do { \
+	isize new_capacity = ARRAY_GROW_FORMULA((x_)->capacity); \
+	if (new_capacity < (min_capacity_)) { \
+		new_capacity = (min_capacity_); \
+	} \
+	array_set_capacity(x_, new_capacity); \
+} while (0)
+
+#define array_add(x_, item_) do { \
+	if ((x_)->capacity < (x_)->count+1) { \
+		array_grow(x_, 0); \
+	} \
+	(x_)->e[(x_)->count++] = item_; \
+} while (0)
+
+#define array_pop(x_)   do { GB_ASSERT((x_)->count > 0); (x_)->count--; } while (0)
+#define array_clear(x_) do { (x_)->count = 0; } while (0)
+
+#define array_resize(x_, new_count_) do { \
+	if ((x_)->capacity < (new_count_)) { \
+		array_grow((x_), (new_count_)); \
+	} \
+	(x_)->count = (new_count_); \
+} while (0)
+
+#define array_reserve(x_, new_capacity_) do { \
+	if ((x_)->capacity < (new_capacity_)) { \
+		array_set_capacity((x_), (new_capacity_)); \
+	} \
+} while (0)
+
+
+
+
+void array__set_capacity(void *ptr, isize capacity, isize element_size) {
+	GB_ASSERT(ptr != NULL);
+	ArrayVoid *x = cast(ArrayVoid *)ptr;
+
+	GB_ASSERT(element_size > 0);
+
+	if (capacity == x->capacity) {
+		return;
+	}
+
+	if (capacity < x->count) {
+		if (x->capacity < capacity) {
+			isize new_capacity = ARRAY_GROW_FORMULA(x->capacity);
+			if (new_capacity < capacity) {
+				new_capacity = capacity;
+			}
+			array__set_capacity(ptr, new_capacity, element_size);
+		}
+		x->count = capacity;
+	}
+
+	{
+		// TODO(bill): Resize rather than copy and delete
+		void *new_data = gb_alloc(x->allocator, element_size*capacity);
+		gb_memmove(new_data, x->e, element_size*x->count);
+		gb_free(x->allocator, x->e);
+		x->capacity = capacity;
+		x->e = new_data;
+	}
+}
+
+
+#if 0
+template <typename T>
+struct Array {
+	gbAllocator allocator;
+	T *         data;
+	isize       count;
+	isize       capacity;
+
+	T &operator[](isize index) {
+		GB_ASSERT_MSG(0 <= index && index < count, "Index out of bounds");
+		return data[index];
+	}
+
+	T const &operator[](isize index) const {
+		GB_ASSERT_MSG(0 <= index && index < count, "Index out of bounds");
+		return data[index];
+	}
+};
+
+template <typename T> void     array_init        (Array<T> *array, gbAllocator a, isize init_capacity = ARRAY_GROW_FORMULA(0));
+template <typename T> void     array_init_count  (Array<T> *array, gbAllocator a, isize count);
+template <typename T> Array<T> array_make        (T *data, isize count, isize capacity);
+template <typename T> void     array_free        (Array<T> *array);
+template <typename T> void     array_add         (Array<T> *array, T const &t);
+template <typename T> T        array_pop         (Array<T> *array);
+template <typename T> void     array_clear       (Array<T> *array);
+template <typename T> void     array_reserve     (Array<T> *array, isize capacity);
+template <typename T> void     array_resize      (Array<T> *array, isize count);
+template <typename T> void     array_set_capacity(Array<T> *array, isize capacity);
+
+
+template <typename T>
+void array_init(Array<T> *array, gbAllocator a, isize init_capacity) {
+	array->allocator = a;
+	array->data = gb_alloc_array(a, T, init_capacity);
+	array->count = 0;
+	array->capacity = init_capacity;
+}
+
+template <typename T>
+void array_init_count(Array<T> *array, gbAllocator a, isize count) {
+	array->allocator = a;
+	array->data = gb_alloc_array(a, T, count);
+	array->count = count;
+	array->capacity = count;
+}
+
+
+template <typename T>
+Array<T> array_make(T *data, isize count, isize capacity) {
+	Array<T> a = {0};
+	a.data = data;
+	a.count = count;
+	a.capacity = capacity;
+	return a;
+}
+
+
+template <typename T>
+void array_free(Array<T> *array) {
+	if (array->allocator.proc != NULL) {
+		gb_free(array->allocator, array->data);
+	}
+	array->count = 0;
+	array->capacity = 0;
+}
+
+template <typename T>
+void array__grow(Array<T> *array, isize min_capacity) {
+	isize new_capacity = ARRAY_GROW_FORMULA(array->capacity);
+	if (new_capacity < min_capacity) {
+		new_capacity = min_capacity;
+	}
+	array_set_capacity(array, new_capacity);
+}
+
+template <typename T>
+void array_add(Array<T> *array, T const &t) {
+	if (array->capacity < array->count+1) {
+		array__grow(array, 0);
+	}
+	array->data[array->count] = t;
+	array->count++;
+}
+
+template <typename T>
+T array_pop(Array<T> *array) {
+	GB_ASSERT(array->count > 0);
+	array->count--;
+	return array->data[array->count];
+}
+
+template <typename T>
+void array_clear(Array<T> *array) {
+	array->count = 0;
+}
+
+template <typename T>
+void array_reserve(Array<T> *array, isize capacity) {
+	if (array->capacity < capacity) {
+		array_set_capacity(array, capacity);
+	}
+}
+
+template <typename T>
+void array_resize(Array<T> *array, isize count) {
+	if (array->capacity < count) {
+		array__grow(array, count);
+	}
+	array->count = count;
+}
+
+template <typename T>
+void array_set_capacity(Array<T> *array, isize capacity) {
+	if (capacity == array->capacity) {
+		return;
+	}
+
+	if (capacity < array->count) {
+		array_resize(array, capacity);
+	}
+
+	T *new_data = NULL;
+	if (capacity > 0) {
+		new_data = gb_alloc_array(array->allocator, T, capacity);
+		gb_memmove(new_data, array->data, gb_size_of(T) * array->capacity);
+	}
+	gb_free(array->allocator, array->data);
+	array->data = new_data;
+	array->capacity = capacity;
+}
+
+
+
+#endif

+ 1353 - 0
src/checker/checker.c

@@ -0,0 +1,1353 @@
+#include "../exact_value.c"
+#include "entity.c"
+#include "types.c"
+
+#define MAP_TYPE Entity *
+#define MAP_PROC map_entity_
+#define MAP_NAME MapEntity
+#include "../map.c"
+
+typedef enum AddressingMode {
+	Addressing_Invalid,
+	Addressing_NoValue,
+	Addressing_Value,
+	Addressing_Variable,
+	Addressing_Constant,
+	Addressing_Type,
+	Addressing_Builtin,
+	Addressing_Count,
+} AddressingMode;
+
+typedef struct Operand {
+	AddressingMode mode;
+	Type *         type;
+	ExactValue     value;
+	AstNode *      expr;
+	BuiltinProcId  builtin_id;
+} Operand;
+
+typedef struct TypeAndValue {
+	AddressingMode mode;
+	Type *         type;
+	ExactValue     value;
+} TypeAndValue;
+
+
+
+typedef struct DeclInfo {
+	Scope *scope;
+
+	Entity **entities;
+	isize    entity_count;
+
+	AstNode *type_expr;
+	AstNode *init_expr;
+	AstNode *proc_decl; // AstNode_ProcDecl
+	u32      var_decl_tags;
+
+	MapBool deps; // Key: Entity *
+} DeclInfo;
+
+typedef struct ExprInfo {
+	bool           is_lhs; // Debug info
+	AddressingMode mode;
+	Type *         type; // Type_Basic
+	ExactValue     value;
+} ExprInfo;
+
+ExprInfo make_expr_info(bool is_lhs, AddressingMode mode, Type *type, ExactValue value) {
+	ExprInfo ei = {is_lhs, mode, type, value};
+	return ei;
+}
+
+typedef struct ProcedureInfo {
+	AstFile * file;
+	Token     token;
+	DeclInfo *decl;
+	Type *    type; // Type_Procedure
+	AstNode * body; // AstNode_BlockStatement
+	u32       tags;
+} ProcedureInfo;
+
+typedef struct Scope {
+	Scope *        parent;
+	Scope *        prev, *next;
+	Scope *        first_child;
+	Scope *        last_child;
+	MapEntity      elements; // Key: String
+	MapEntity      implicit; // Key: String
+
+	Array(Scope *) shared;
+	Array(Scope *) imported;
+	bool           is_proc;
+	bool           is_global;
+	bool           is_file;
+	bool           is_init;
+	AstFile *      file;
+} Scope;
+gb_global Scope *universal_scope = NULL;
+
+typedef enum ExprKind {
+	Expr_Expr,
+	Expr_Stmt,
+} ExprKind;
+
+typedef enum BuiltinProcId {
+	BuiltinProc_Invalid,
+
+	BuiltinProc_new,
+	BuiltinProc_new_slice,
+
+	BuiltinProc_size_of,
+	BuiltinProc_size_of_val,
+	BuiltinProc_align_of,
+	BuiltinProc_align_of_val,
+	BuiltinProc_offset_of,
+	BuiltinProc_offset_of_val,
+	BuiltinProc_type_of_val,
+
+	BuiltinProc_type_info,
+	BuiltinProc_type_info_of_val,
+
+	BuiltinProc_compile_assert,
+	BuiltinProc_assert,
+	BuiltinProc_panic,
+
+	BuiltinProc_copy,
+	BuiltinProc_append,
+
+	BuiltinProc_swizzle,
+
+	// BuiltinProc_ptr_offset,
+	// BuiltinProc_ptr_sub,
+	BuiltinProc_slice_ptr,
+
+	BuiltinProc_min,
+	BuiltinProc_max,
+	BuiltinProc_abs,
+
+	BuiltinProc_enum_to_string,
+
+	BuiltinProc_Count,
+} BuiltinProcId;
+typedef struct BuiltinProc {
+	String   name;
+	isize    arg_count;
+	bool      variadic;
+	ExprKind kind;
+} BuiltinProc;
+gb_global BuiltinProc builtin_procs[BuiltinProc_Count] = {
+	{STR_LIT(""),                 0, false, Expr_Stmt},
+
+	{STR_LIT("new"),              1, false, Expr_Expr},
+	{STR_LIT("new_slice"),        2, true,  Expr_Expr},
+
+	{STR_LIT("size_of"),          1, false, Expr_Expr},
+	{STR_LIT("size_of_val"),      1, false, Expr_Expr},
+	{STR_LIT("align_of"),         1, false, Expr_Expr},
+	{STR_LIT("align_of_val"),     1, false, Expr_Expr},
+	{STR_LIT("offset_of"),        2, false, Expr_Expr},
+	{STR_LIT("offset_of_val"),    1, false, Expr_Expr},
+	{STR_LIT("type_of_val"),      1, false, Expr_Expr},
+
+	{STR_LIT("type_info"),        1, false, Expr_Expr},
+	{STR_LIT("type_info_of_val"), 1, false, Expr_Expr},
+
+	{STR_LIT("compile_assert"),   1, false, Expr_Stmt},
+	{STR_LIT("assert"),           1, false, Expr_Stmt},
+	{STR_LIT("panic"),            1, false, Expr_Stmt},
+
+	{STR_LIT("copy"),             2, false, Expr_Expr},
+	{STR_LIT("append"),           2, false, Expr_Expr},
+
+	{STR_LIT("swizzle"),          1, true,  Expr_Expr},
+
+	// {STR_LIT("ptr_offset"),       2, false, Expr_Expr},
+	// {STR_LIT("ptr_sub"),          2, false, Expr_Expr},
+	{STR_LIT("slice_ptr"),        2, true,  Expr_Expr},
+
+	{STR_LIT("min"),              2, false, Expr_Expr},
+	{STR_LIT("max"),              2, false, Expr_Expr},
+	{STR_LIT("abs"),              1, false, Expr_Expr},
+
+	{STR_LIT("enum_to_string"),   1, false, Expr_Expr},
+};
+
+typedef enum ImplicitValueId {
+	ImplicitValue_Invalid,
+
+	ImplicitValue_context,
+
+	ImplicitValue_Count,
+} ImplicitValueId;
+typedef struct ImplicitValueInfo {
+	String  name;
+	String  backing_name;
+	Type *  type;
+} ImplicitValueInfo;
+// NOTE(bill): This is initialized later
+gb_global ImplicitValueInfo implicit_value_infos[ImplicitValue_Count] = {0};
+
+
+
+typedef struct CheckerContext {
+	Scope *   scope;
+	DeclInfo *decl;
+	u32       stmt_state_flags;
+} CheckerContext;
+
+#define MAP_TYPE TypeAndValue
+#define MAP_PROC map_tav_
+#define MAP_NAME MapTypeAndValue
+#include "../map.c"
+
+#define MAP_TYPE Scope *
+#define MAP_PROC map_scope_
+#define MAP_NAME MapScope
+#include "../map.c"
+
+#define MAP_TYPE DeclInfo *
+#define MAP_PROC map_decl_info_
+#define MAP_NAME MapDeclInfo
+#include "../map.c"
+
+#define MAP_TYPE AstFile *
+#define MAP_PROC map_ast_file_
+#define MAP_NAME MapAstFile
+#include "../map.c"
+
+#define MAP_TYPE ExprInfo
+#define MAP_PROC map_expr_info_
+#define MAP_NAME MapExprInfo
+#include "../map.c"
+
+
+// NOTE(bill): Symbol tables
+typedef struct CheckerInfo {
+	MapTypeAndValue     types;           // Key: AstNode * | Expression -> Type (and value)
+	MapEntity           definitions;     // Key: AstNode * | Identifier -> Entity
+	MapEntity           uses;            // Key: AstNode * | Identifier -> Entity
+	MapScope            scopes;          // Key: AstNode * | Node       -> Scope
+	MapExprInfo         untyped;         // Key: AstNode * | Expression -> ExprInfo
+	MapDeclInfo         entities;        // Key: Entity *
+	MapEntity           foreign_procs;   // Key: String
+	MapAstFile          files;           // Key: String (full path)
+	MapIsize            type_info_map;   // Key: Type *
+	isize               type_info_count;
+	Entity *            implicit_values[ImplicitValue_Count];
+} CheckerInfo;
+
+typedef struct Checker {
+	Parser *    parser;
+	CheckerInfo info;
+
+	AstFile *              curr_ast_file;
+	BaseTypeSizes          sizes;
+	Scope *                global_scope;
+	Array(ProcedureInfo)   procs; // NOTE(bill): Procedures to check
+
+	gbArena                arena;
+	gbArena                tmp_arena;
+	gbAllocator            allocator;
+	gbAllocator            tmp_allocator;
+
+	CheckerContext         context;
+
+	Array(Type *)          proc_stack;
+	bool                    in_defer; // TODO(bill): Actually handle correctly
+} Checker;
+
+typedef struct CycleChecker {
+	Array(Entity *) path; // Entity_TypeName
+} CycleChecker;
+
+
+
+
+CycleChecker *cycle_checker_add(CycleChecker *cc, Entity *e) {
+	if (cc == NULL) {
+		return NULL;
+	}
+	if (cc->path.e == NULL) {
+		array_init(&cc->path, heap_allocator());
+	}
+	GB_ASSERT(e != NULL && e->kind == Entity_TypeName);
+	array_add(&cc->path, e);
+	return cc;
+}
+
+void cycle_checker_destroy(CycleChecker *cc) {
+	if (cc != NULL && cc->path.e != NULL)  {
+		array_free(&cc->path);
+	}
+}
+
+
+void init_declaration_info(DeclInfo *d, Scope *scope) {
+	d->scope = scope;
+	map_bool_init(&d->deps, heap_allocator());
+}
+
+DeclInfo *make_declaration_info(gbAllocator a, Scope *scope) {
+	DeclInfo *d = gb_alloc_item(a, DeclInfo);
+	init_declaration_info(d, scope);
+	return d;
+}
+
+void destroy_declaration_info(DeclInfo *d) {
+	map_bool_destroy(&d->deps);
+}
+
+bool decl_info_has_init(DeclInfo *d) {
+	if (d->init_expr != NULL) {
+		return true;
+	}
+	if (d->proc_decl != NULL) {
+		ast_node(pd, ProcDecl, d->proc_decl);
+		if (pd->body != NULL) {
+			return true;
+		}
+	}
+
+	return false;
+}
+
+
+
+
+
+Scope *make_scope(Scope *parent, gbAllocator allocator) {
+	Scope *s = gb_alloc_item(allocator, Scope);
+	s->parent = parent;
+	map_entity_init(&s->elements,   heap_allocator());
+	map_entity_init(&s->implicit,   heap_allocator());
+	array_init(&s->shared,   heap_allocator());
+	array_init(&s->imported, heap_allocator());
+
+	if (parent != NULL && parent != universal_scope) {
+		DLIST_APPEND(parent->first_child, parent->last_child, s);
+	}
+	return s;
+}
+
+void destroy_scope(Scope *scope) {
+	for_array(i, scope->elements.entries) {
+		Entity *e =scope->elements.entries.e[i].value;
+		if (e->kind == Entity_Variable) {
+			if (!(e->flags & EntityFlag_Used)) {
+#if 0
+				warning(e->token, "Unused variable `%.*s`", LIT(e->token.string));
+#endif
+			}
+		}
+	}
+
+	for (Scope *child = scope->first_child; child != NULL; child = child->next) {
+		destroy_scope(child);
+	}
+
+	map_entity_destroy(&scope->elements);
+	map_entity_destroy(&scope->implicit);
+	array_free(&scope->shared);
+	array_free(&scope->imported);
+
+	// NOTE(bill): No need to free scope as it "should" be allocated in an arena (except for the global scope)
+}
+
+void add_scope(Checker *c, AstNode *node, Scope *scope) {
+	GB_ASSERT(node != NULL);
+	GB_ASSERT(scope != NULL);
+	map_scope_set(&c->info.scopes, hash_pointer(node), scope);
+}
+
+
+void check_open_scope(Checker *c, AstNode *node) {
+	GB_ASSERT(node != NULL);
+	GB_ASSERT(node->kind == AstNode_Invalid ||
+	          is_ast_node_stmt(node) ||
+	          is_ast_node_type(node));
+	Scope *scope = make_scope(c->context.scope, c->allocator);
+	add_scope(c, node, scope);
+	if (node->kind == AstNode_ProcType) {
+		scope->is_proc = true;
+	}
+	c->context.scope = scope;
+	c->context.stmt_state_flags |= StmtStateFlag_bounds_check;
+}
+
+void check_close_scope(Checker *c) {
+	c->context.scope = c->context.scope->parent;
+}
+
+void scope_lookup_parent_entity(Scope *scope, String name, Scope **scope_, Entity **entity_) {
+	bool gone_thru_proc = false;
+	HashKey key = hash_string(name);
+	for (Scope *s = scope; s != NULL; s = s->parent) {
+		Entity **found = map_entity_get(&s->elements, key);
+		if (found) {
+			Entity *e = *found;
+			if (gone_thru_proc) {
+				if (e->kind == Entity_Variable &&
+				    !e->scope->is_file &&
+				    !e->scope->is_global) {
+					continue;
+				}
+			}
+
+			if (entity_) *entity_ = e;
+			if (scope_) *scope_ = s;
+			return;
+		}
+
+		if (s->is_proc) {
+			gone_thru_proc = true;
+		} else {
+			// Check shared scopes - i.e. other files @ global scope
+			for_array(i, s->shared) {
+				Scope *shared = s->shared.e[i];
+				Entity **found = map_entity_get(&shared->elements, key);
+				if (found) {
+					Entity *e = *found;
+					if (e->kind == Entity_Variable &&
+					    !e->scope->is_file &&
+					    !e->scope->is_global) {
+						continue;
+					}
+
+					if (e->scope != shared) {
+						// Do not return imported entities even #load ones
+						continue;
+					}
+
+					if (entity_) *entity_ = e;
+					if (scope_) *scope_ = shared;
+					return;
+				}
+			}
+		}
+	}
+
+
+	if (entity_) *entity_ = NULL;
+	if (scope_) *scope_ = NULL;
+}
+
+Entity *scope_lookup_entity(Scope *s, String name) {
+	Entity *entity = NULL;
+	scope_lookup_parent_entity(s, name, NULL, &entity);
+	return entity;
+}
+
+Entity *current_scope_lookup_entity(Scope *s, String name) {
+	HashKey key = hash_string(name);
+	Entity **found = map_entity_get(&s->elements, key);
+	if (found) {
+		return *found;
+	}
+	for_array(i, s->shared) {
+		Entity **found = map_entity_get(&s->shared.e[i]->elements, key);
+		if (found) {
+			return *found;
+		}
+	}
+	return NULL;
+}
+
+
+
+Entity *scope_insert_entity(Scope *s, Entity *entity) {
+	String name = entity->token.string;
+	HashKey key = hash_string(name);
+	Entity **found = map_entity_get(&s->elements, key);
+	if (found) {
+		return *found;
+	}
+	map_entity_set(&s->elements, key, entity);
+	if (entity->scope == NULL) {
+		entity->scope = s;
+	}
+	return NULL;
+}
+
+void check_scope_usage(Checker *c, Scope *scope) {
+	// TODO(bill): Use this?
+}
+
+
+void add_dependency(DeclInfo *d, Entity *e) {
+	map_bool_set(&d->deps, hash_pointer(e), cast(bool)true);
+}
+
+void add_declaration_dependency(Checker *c, Entity *e) {
+	if (e == NULL) {
+		return;
+	}
+	if (c->context.decl != NULL) {
+		DeclInfo **found = map_decl_info_get(&c->info.entities, hash_pointer(e));
+		if (found) {
+			add_dependency(c->context.decl, e);
+		}
+	}
+}
+
+
+void add_global_entity(Entity *entity) {
+	String name = entity->token.string;
+	if (gb_memchr(name.text, ' ', name.len)) {
+		return; // NOTE(bill): `untyped thing`
+	}
+	if (scope_insert_entity(universal_scope, entity)) {
+		compiler_error("double declaration");
+	}
+}
+
+void add_global_constant(gbAllocator a, String name, Type *type, ExactValue value) {
+	Entity *entity = alloc_entity(a, Entity_Constant, NULL, make_token_ident(name), type);
+	entity->Constant.value = value;
+	add_global_entity(entity);
+}
+
+
+
+void init_universal_scope(void) {
+	// NOTE(bill): No need to free these
+	gbAllocator a = heap_allocator();
+	universal_scope = make_scope(NULL, a);
+
+// Types
+	for (isize i = 0; i < gb_count_of(basic_types); i++) {
+		add_global_entity(make_entity_type_name(a, NULL, make_token_ident(basic_types[i].Basic.name), &basic_types[i]));
+	}
+	for (isize i = 0; i < gb_count_of(basic_type_aliases); i++) {
+		add_global_entity(make_entity_type_name(a, NULL, make_token_ident(basic_type_aliases[i].Basic.name), &basic_type_aliases[i]));
+	}
+
+// Constants
+	add_global_constant(a, str_lit("true"),  t_untyped_bool, make_exact_value_bool(true));
+	add_global_constant(a, str_lit("false"), t_untyped_bool, make_exact_value_bool(false));
+
+	add_global_entity(make_entity_nil(a, str_lit("nil"), t_untyped_nil));
+
+// Builtin Procedures
+	for (isize i = 0; i < gb_count_of(builtin_procs); i++) {
+		BuiltinProcId id = cast(BuiltinProcId)i;
+		Entity *entity = alloc_entity(a, Entity_Builtin, NULL, make_token_ident(builtin_procs[i].name), t_invalid);
+		entity->Builtin.id = id;
+		add_global_entity(entity);
+	}
+
+	t_u8_ptr = make_type_pointer(a, t_u8);
+	t_int_ptr = make_type_pointer(a, t_int);
+}
+
+
+
+
+void init_checker_info(CheckerInfo *i) {
+	gbAllocator a = heap_allocator();
+	map_tav_init(&i->types,            a);
+	map_entity_init(&i->definitions,   a);
+	map_entity_init(&i->uses,          a);
+	map_scope_init(&i->scopes,         a);
+	map_decl_info_init(&i->entities,   a);
+	map_expr_info_init(&i->untyped,    a);
+	map_entity_init(&i->foreign_procs, a);
+	map_isize_init(&i->type_info_map,  a);
+	map_ast_file_init(&i->files,       a);
+	i->type_info_count = 0;
+
+}
+
+void destroy_checker_info(CheckerInfo *i) {
+	map_tav_destroy(&i->types);
+	map_entity_destroy(&i->definitions);
+	map_entity_destroy(&i->uses);
+	map_scope_destroy(&i->scopes);
+	map_decl_info_destroy(&i->entities);
+	map_expr_info_destroy(&i->untyped);
+	map_entity_destroy(&i->foreign_procs);
+	map_isize_destroy(&i->type_info_map);
+	map_ast_file_destroy(&i->files);
+}
+
+
+void init_checker(Checker *c, Parser *parser, BaseTypeSizes sizes) {
+	gbAllocator a = heap_allocator();
+
+	c->parser = parser;
+	init_checker_info(&c->info);
+	c->sizes = sizes;
+
+	array_init(&c->proc_stack, a);
+	array_init(&c->procs, a);
+
+	// NOTE(bill): Is this big enough or too small?
+	isize item_size = gb_max3(gb_size_of(Entity), gb_size_of(Type), gb_size_of(Scope));
+	isize total_token_count = 0;
+	for_array(i, c->parser->files) {
+		AstFile *f = &c->parser->files.e[i];
+		total_token_count += f->tokens.count;
+	}
+	isize arena_size = 2 * item_size * total_token_count;
+	gb_arena_init_from_allocator(&c->arena, a, arena_size);
+	gb_arena_init_from_allocator(&c->tmp_arena, a, arena_size);
+
+
+	c->allocator     = gb_arena_allocator(&c->arena);
+	c->tmp_allocator = gb_arena_allocator(&c->tmp_arena);
+
+	c->global_scope = make_scope(universal_scope, c->allocator);
+	c->context.scope = c->global_scope;
+}
+
+void destroy_checker(Checker *c) {
+	destroy_checker_info(&c->info);
+	destroy_scope(c->global_scope);
+	array_free(&c->proc_stack);
+	array_free(&c->procs);
+
+	gb_arena_free(&c->arena);
+}
+
+
+TypeAndValue *type_and_value_of_expression(CheckerInfo *i, AstNode *expression) {
+	TypeAndValue *found = map_tav_get(&i->types, hash_pointer(expression));
+	return found;
+}
+
+
+Entity *entity_of_ident(CheckerInfo *i, AstNode *identifier) {
+	if (identifier->kind == AstNode_Ident) {
+		Entity **found = map_entity_get(&i->definitions, hash_pointer(identifier));
+		if (found) {
+			return *found;
+		}
+		found = map_entity_get(&i->uses, hash_pointer(identifier));
+		if (found) {
+			return *found;
+		}
+	}
+	return NULL;
+}
+
+Type *type_of_expr(CheckerInfo *i, AstNode *expression) {
+	TypeAndValue *found = type_and_value_of_expression(i, expression);
+	if (found) {
+		return found->type;
+	}
+	if (expression->kind == AstNode_Ident) {
+		Entity *entity = entity_of_ident(i, expression);
+		if (entity) {
+			return entity->type;
+		}
+	}
+
+	return NULL;
+}
+
+
+void add_untyped(CheckerInfo *i, AstNode *expression, bool lhs, AddressingMode mode, Type *basic_type, ExactValue value) {
+	map_expr_info_set(&i->untyped, hash_pointer(expression), make_expr_info(lhs, mode, basic_type, value));
+}
+
+void add_type_and_value(CheckerInfo *i, AstNode *expression, AddressingMode mode, Type *type, ExactValue value) {
+	GB_ASSERT(expression != NULL);
+	if (mode == Addressing_Invalid) {
+		return;
+	}
+
+	if (mode == Addressing_Constant) {
+		if (is_type_constant_type(type)) {
+			GB_ASSERT(value.kind != ExactValue_Invalid);
+			if (!(type != t_invalid || is_type_constant_type(type))) {
+				compiler_error("add_type_and_value - invalid type: %s", type_to_string(type));
+			}
+		}
+	}
+
+	TypeAndValue tv = {0};
+	tv.type  = type;
+	tv.value = value;
+	tv.mode  = mode;
+	map_tav_set(&i->types, hash_pointer(expression), tv);
+}
+
+void add_entity_definition(CheckerInfo *i, AstNode *identifier, Entity *entity) {
+	GB_ASSERT(identifier != NULL);
+	if (identifier->kind == AstNode_Ident) {
+		GB_ASSERT(identifier->kind == AstNode_Ident);
+		HashKey key = hash_pointer(identifier);
+		map_entity_set(&i->definitions, key, entity);
+	} else {
+		// NOTE(bill): Error should handled elsewhere
+	}
+}
+
+bool add_entity(Checker *c, Scope *scope, AstNode *identifier, Entity *entity) {
+	if (str_ne(entity->token.string, str_lit("_"))) {
+		Entity *insert_entity = scope_insert_entity(scope, entity);
+		if (insert_entity) {
+			Entity *up = insert_entity->using_parent;
+			if (up != NULL) {
+				error(entity->token,
+				      "Redeclararation of `%.*s` in this scope through `using`\n"
+				      "\tat %.*s(%td:%td)",
+				      LIT(entity->token.string),
+				      LIT(up->token.pos.file), up->token.pos.line, up->token.pos.column);
+				return false;
+			} else {
+				TokenPos pos = insert_entity->token.pos;
+				if (token_pos_are_equal(pos, entity->token.pos)) {
+					// NOTE(bill): Error should have been handled already
+					return false;
+				}
+				error(entity->token,
+				      "Redeclararation of `%.*s` in this scope\n"
+				      "\tat %.*s(%td:%td)",
+				      LIT(entity->token.string),
+				      LIT(pos.file), pos.line, pos.column);
+				return false;
+			}
+		}
+	}
+	if (identifier != NULL) {
+		add_entity_definition(&c->info, identifier, entity);
+	}
+	return true;
+}
+
+void add_entity_use(Checker *c, AstNode *identifier, Entity *entity) {
+	GB_ASSERT(identifier != NULL);
+	if (identifier->kind != AstNode_Ident) {
+		return;
+	}
+	map_entity_set(&c->info.uses, hash_pointer(identifier), entity);
+	add_declaration_dependency(c, entity); // TODO(bill): Should this be here?
+}
+
+
+void add_entity_and_decl_info(Checker *c, AstNode *identifier, Entity *e, DeclInfo *d) {
+	GB_ASSERT(str_eq(identifier->Ident.string, e->token.string));
+	add_entity(c, e->scope, identifier, e);
+	map_decl_info_set(&c->info.entities, hash_pointer(e), d);
+}
+
+void add_type_info_type(Checker *c, Type *t) {
+	if (t == NULL) {
+		return;
+	}
+	t = default_type(t);
+	if (is_type_untyped(t)) {
+		return; // Could be nil
+	}
+
+	if (map_isize_get(&c->info.type_info_map, hash_pointer(t)) != NULL) {
+		// Types have already been added
+		return;
+	}
+
+	isize ti_index = -1;
+	for_array(i, c->info.type_info_map.entries) {
+		MapIsizeEntry *e = &c->info.type_info_map.entries.e[i];
+		Type *prev_type = cast(Type *)e->key.ptr;
+		if (are_types_identical(t, prev_type)) {
+			// Duplicate entry
+			ti_index = e->value;
+			break;
+		}
+	}
+	if (ti_index < 0) {
+		// Unique entry
+		// NOTE(bill): map entries grow linearly and in order
+		ti_index = c->info.type_info_count;
+		c->info.type_info_count++;
+	}
+	map_isize_set(&c->info.type_info_map, hash_pointer(t), ti_index);
+
+
+
+
+	// Add nested types
+
+	if (t->kind == Type_Named) {
+		// NOTE(bill): Just in case
+		add_type_info_type(c, t->Named.base);
+		return;
+	}
+
+	Type *bt = base_type(t);
+	add_type_info_type(c, bt);
+
+	switch (bt->kind) {
+	case Type_Basic: {
+		switch (bt->Basic.kind) {
+		case Basic_string:
+			add_type_info_type(c, t_u8_ptr);
+			add_type_info_type(c, t_int);
+			break;
+		case Basic_any:
+			add_type_info_type(c, t_type_info_ptr);
+			add_type_info_type(c, t_rawptr);
+			break;
+		}
+	} break;
+
+	case Type_Maybe:
+		add_type_info_type(c, bt->Maybe.elem);
+		add_type_info_type(c, t_bool);
+		break;
+
+	case Type_Pointer:
+		add_type_info_type(c, bt->Pointer.elem);
+		break;
+
+	case Type_Array:
+		add_type_info_type(c, bt->Array.elem);
+		add_type_info_type(c, make_type_pointer(c->allocator, bt->Array.elem));
+		add_type_info_type(c, t_int);
+		break;
+	case Type_Slice:
+		add_type_info_type(c, bt->Slice.elem);
+		add_type_info_type(c, make_type_pointer(c->allocator, bt->Slice.elem));
+		add_type_info_type(c, t_int);
+		break;
+	case Type_Vector:
+		add_type_info_type(c, bt->Vector.elem);
+		add_type_info_type(c, t_int);
+		break;
+
+	case Type_Record: {
+		switch (bt->Record.kind) {
+		case TypeRecord_Enum:
+			add_type_info_type(c, bt->Record.enum_base);
+			break;
+
+		case TypeRecord_Union:
+			add_type_info_type(c, t_int);
+			/* fallthrough */
+		default:
+			for (isize i = 0; i < bt->Record.field_count; i++) {
+				Entity *f = bt->Record.fields[i];
+				add_type_info_type(c, f->type);
+			}
+			break;
+		}
+	} break;
+
+	case Type_Tuple:
+		for (isize i = 0; i < bt->Tuple.variable_count; i++) {
+			Entity *var = bt->Tuple.variables[i];
+			add_type_info_type(c, var->type);
+		}
+		break;
+
+	case Type_Proc:
+		add_type_info_type(c, bt->Proc.params);
+		add_type_info_type(c, bt->Proc.results);
+		break;
+	}
+}
+
+
+void check_procedure_later(Checker *c, AstFile *file, Token token, DeclInfo *decl, Type *type, AstNode *body, u32 tags) {
+	ProcedureInfo info = {0};
+	info.file = file;
+	info.token = token;
+	info.decl  = decl;
+	info.type  = type;
+	info.body  = body;
+	info.tags  = tags;
+	array_add(&c->procs, info);
+}
+
+void push_procedure(Checker *c, Type *type) {
+	array_add(&c->proc_stack, type);
+}
+
+void pop_procedure(Checker *c) {
+	array_pop(&c->proc_stack);
+}
+
+Type *const curr_procedure(Checker *c) {
+	isize count = c->proc_stack.count;
+	if (count > 0) {
+		return c->proc_stack.e[count-1];
+	}
+	return NULL;
+}
+
+void add_curr_ast_file(Checker *c, AstFile *file) {
+	TokenPos zero_pos = {0};
+	global_error_collector.prev = zero_pos;
+	c->curr_ast_file = file;
+	c->context.decl = file->decl_info;
+}
+
+
+
+
+void add_dependency_to_map(MapEntity *map, CheckerInfo *info, Entity *node) {
+	if (node == NULL) {
+		return;
+	}
+	if (map_entity_get(map, hash_pointer(node)) != NULL) {
+		return;
+	}
+	map_entity_set(map, hash_pointer(node), node);
+
+
+	DeclInfo **found = map_decl_info_get(&info->entities, hash_pointer(node));
+	if (found == NULL) {
+		return;
+	}
+
+	DeclInfo *decl = *found;
+	for_array(i, decl->deps.entries) {
+		Entity *e = cast(Entity *)decl->deps.entries.e[i].key.ptr;
+		add_dependency_to_map(map, info, e);
+	}
+}
+
+MapEntity generate_minimum_dependency_map(CheckerInfo *info, Entity *start) {
+	MapEntity map = {0}; // Key: Entity *
+	map_entity_init(&map, heap_allocator());
+
+	for_array(i, info->entities.entries) {
+		MapDeclInfoEntry *entry = &info->entities.entries.e[i];
+		Entity *e = cast(Entity *)cast(uintptr)entry->key.key;
+		if (e->scope->is_global) {
+			// NOTE(bill): Require runtime stuff
+			add_dependency_to_map(&map, info, e);
+		}
+	}
+
+	add_dependency_to_map(&map, info, start);
+
+	return map;
+}
+
+
+
+
+#include "expr.c"
+#include "decl.c"
+#include "stmt.c"
+
+void init_preload_types(Checker *c) {
+	if (t_type_info == NULL) {
+		Entity *e = current_scope_lookup_entity(c->global_scope, str_lit("Type_Info"));
+		if (e == NULL) {
+			compiler_error("Could not find type declaration for `Type_Info`\n"
+			               "Is `runtime.odin` missing from the `core` directory relative to odin.exe?");
+		}
+		t_type_info = e->type;
+		t_type_info_ptr = make_type_pointer(c->allocator, t_type_info);
+		GB_ASSERT(is_type_union(e->type));
+		TypeRecord *record = &base_type(e->type)->Record;
+
+		t_type_info_member = record->other_fields[0]->type;
+		t_type_info_member_ptr = make_type_pointer(c->allocator, t_type_info_member);
+
+		if (record->field_count != 18) {
+			compiler_error("Invalid `Type_Info` layout");
+		}
+		t_type_info_named     = record->fields[ 1]->type;
+		t_type_info_integer   = record->fields[ 2]->type;
+		t_type_info_float     = record->fields[ 3]->type;
+		t_type_info_any       = record->fields[ 4]->type;
+		t_type_info_string    = record->fields[ 5]->type;
+		t_type_info_boolean   = record->fields[ 6]->type;
+		t_type_info_pointer   = record->fields[ 7]->type;
+		t_type_info_maybe     = record->fields[ 8]->type;
+		t_type_info_procedure = record->fields[ 9]->type;
+		t_type_info_array     = record->fields[10]->type;
+		t_type_info_slice     = record->fields[11]->type;
+		t_type_info_vector    = record->fields[12]->type;
+		t_type_info_tuple     = record->fields[13]->type;
+		t_type_info_struct    = record->fields[14]->type;
+		t_type_info_union     = record->fields[15]->type;
+		t_type_info_raw_union = record->fields[16]->type;
+		t_type_info_enum      = record->fields[17]->type;
+	}
+
+	if (t_allocator == NULL) {
+		Entity *e = current_scope_lookup_entity(c->global_scope, str_lit("Allocator"));
+		if (e == NULL) {
+			compiler_error("Could not find type declaration for `Allocator`\n"
+			               "Is `runtime.odin` missing from the `core` directory relative to odin.exe?");
+		}
+		t_allocator = e->type;
+		t_allocator_ptr = make_type_pointer(c->allocator, t_allocator);
+	}
+
+	if (t_context == NULL) {
+		Entity *e = current_scope_lookup_entity(c->global_scope, str_lit("Context"));
+		if (e == NULL) {
+			compiler_error("Could not find type declaration for `Context`\n"
+			               "Is `runtime.odin` missing from the `core` directory relative to odin.exe?");
+		}
+		t_context = e->type;
+		t_context_ptr = make_type_pointer(c->allocator, t_context);
+
+	}
+
+}
+
+void add_implicit_value(Checker *c, ImplicitValueId id, String name, String backing_name, Type *type) {
+	ImplicitValueInfo info = {name, backing_name, type};
+	Entity *value = make_entity_implicit_value(c->allocator, info.name, info.type, id);
+	Entity *prev = scope_insert_entity(c->global_scope, value);
+	GB_ASSERT(prev == NULL);
+	implicit_value_infos[id] = info;
+	c->info.implicit_values[id] = value;
+}
+
+
+void check_global_entity(Checker *c, EntityKind kind) {
+	for_array(i, c->info.entities.entries) {
+		MapDeclInfoEntry *entry = &c->info.entities.entries.e[i];
+		Entity *e = cast(Entity *)cast(uintptr)entry->key.key;
+		if (e->kind == kind) {
+			DeclInfo *d = entry->value;
+
+			add_curr_ast_file(c, d->scope->file);
+
+			if (d->scope == e->scope) {
+				if (kind != Entity_Procedure && str_eq(e->token.string, str_lit("main"))) {
+					if (e->scope->is_init) {
+						error(e->token, "`main` is reserved as the entry point procedure in the initial scope");
+						continue;
+					}
+				} else if (e->scope->is_global && str_eq(e->token.string, str_lit("main"))) {
+					error(e->token, "`main` is reserved as the entry point procedure in the initial scope");
+					continue;
+				}
+
+				Scope *prev_scope = c->context.scope;
+				c->context.scope = d->scope;
+				check_entity_decl(c, e, d, NULL, NULL);
+			}
+		}
+	}
+}
+
+void check_parsed_files(Checker *c) {
+	AstNodeArray import_decls;
+	array_init(&import_decls, heap_allocator());
+
+	MapScope file_scopes; // Key: String (fullpath)
+	map_scope_init(&file_scopes, heap_allocator());
+
+	// Map full filepaths to Scopes
+	for_array(i, c->parser->files) {
+		AstFile *f = &c->parser->files.e[i];
+		Scope *scope = NULL;
+		scope = make_scope(c->global_scope, c->allocator);
+		scope->is_global = f->is_global_scope;
+		scope->is_file   = true;
+		scope->file      = f;
+		if (i == 0) {
+			// NOTE(bill): First file is always the initial file
+			// thus it must contain main
+			scope->is_init = true;
+		}
+
+		if (scope->is_global) {
+			array_add(&c->global_scope->shared, scope);
+		}
+
+		f->scope = scope;
+		f->decl_info = make_declaration_info(c->allocator, f->scope);
+		HashKey key = hash_string(f->tokenizer.fullpath);
+		map_scope_set(&file_scopes, key, scope);
+		map_ast_file_set(&c->info.files, key, f);
+	}
+
+	// Collect Entities
+	for_array(i, c->parser->files) {
+		AstFile *f = &c->parser->files.e[i];
+		add_curr_ast_file(c, f);
+
+		Scope *file_scope = f->scope;
+
+		for_array(decl_index, f->decls) {
+			AstNode *decl = f->decls.e[decl_index];
+			if (!is_ast_node_decl(decl)) {
+				continue;
+			}
+
+			switch (decl->kind) {
+			case_ast_node(bd, BadDecl, decl);
+			case_end;
+			case_ast_node(id, ImportDecl, decl);
+				// NOTE(bill): Handle later
+			case_end;
+			case_ast_node(fsl, ForeignLibrary, decl);
+				// NOTE(bill): ignore
+			case_end;
+
+			case_ast_node(cd, ConstDecl, decl);
+				for_array(i, cd->values) {
+					AstNode *name = cd->names.e[i];
+					AstNode *value = cd->values.e[i];
+					ExactValue v = {ExactValue_Invalid};
+					Entity *e = make_entity_constant(c->allocator, file_scope, name->Ident, NULL, v);
+					e->identifier = name;
+					DeclInfo *di = make_declaration_info(c->allocator, file_scope);
+					di->type_expr = cd->type;
+					di->init_expr = value;
+					add_entity_and_decl_info(c, name, e, di);
+				}
+
+				isize lhs_count = cd->names.count;
+				isize rhs_count = cd->values.count;
+
+				if (rhs_count == 0 && cd->type == NULL) {
+					error(ast_node_token(decl), "Missing type or initial expression");
+				} else if (lhs_count < rhs_count) {
+					error(ast_node_token(decl), "Extra initial expression");
+				}
+			case_end;
+
+			case_ast_node(vd, VarDecl, decl);
+				isize entity_count = vd->names.count;
+				isize entity_index = 0;
+				Entity **entities = gb_alloc_array(c->allocator, Entity *, entity_count);
+				DeclInfo *di = NULL;
+				if (vd->values.count > 0) {
+					di = make_declaration_info(heap_allocator(), file_scope);
+					di->entities = entities;
+					di->entity_count = entity_count;
+					di->type_expr = vd->type;
+					di->init_expr = vd->values.e[0];
+				}
+
+				for_array(i, vd->names) {
+					AstNode *name = vd->names.e[i];
+					AstNode *value = NULL;
+					if (i < vd->values.count) {
+						value = vd->values.e[i];
+					}
+					Entity *e = make_entity_variable(c->allocator, file_scope, name->Ident, NULL);
+					e->identifier = name;
+					entities[entity_index++] = e;
+
+					DeclInfo *d = di;
+					if (d == NULL) {
+						AstNode *init_expr = value;
+						d = make_declaration_info(heap_allocator(), file_scope);
+						d->type_expr = vd->type;
+						d->init_expr = init_expr;
+						d->var_decl_tags = vd->tags;
+					}
+
+					add_entity_and_decl_info(c, name, e, d);
+				}
+			case_end;
+
+			case_ast_node(td, TypeDecl, decl);
+				ast_node(n, Ident, td->name);
+				Entity *e = make_entity_type_name(c->allocator, file_scope, *n, NULL);
+				e->identifier = td->name;
+				DeclInfo *d = make_declaration_info(c->allocator, e->scope);
+				d->type_expr = td->type;
+				add_entity_and_decl_info(c, td->name, e, d);
+			case_end;
+
+			case_ast_node(pd, ProcDecl, decl);
+				ast_node(n, Ident, pd->name);
+				Token token = *n;
+				Entity *e = make_entity_procedure(c->allocator, file_scope, token, NULL);
+				e->identifier = pd->name;
+				DeclInfo *d = make_declaration_info(c->allocator, e->scope);
+				d->proc_decl = decl;
+				add_entity_and_decl_info(c, pd->name, e, d);
+			case_end;
+
+			default:
+				error(ast_node_token(decl), "Only declarations are allowed at file scope");
+				break;
+			}
+		}
+	}
+
+	for_array(i, c->parser->files) {
+		AstFile *f = &c->parser->files.e[i];
+		add_curr_ast_file(c, f);
+
+		Scope *file_scope = f->scope;
+
+		for_array(decl_index, f->decls) {
+			AstNode *decl = f->decls.e[decl_index];
+			if (decl->kind != AstNode_ImportDecl) {
+				continue;
+			}
+			ast_node(id, ImportDecl, decl);
+
+			HashKey key = hash_string(id->fullpath);
+			Scope **found = map_scope_get(&file_scopes, key);
+			GB_ASSERT_MSG(found != NULL, "Unable to find scope for file: %.*s", LIT(id->fullpath));
+			Scope *scope = *found;
+
+			if (scope->is_global) {
+				error(id->token, "Importing a #shared_global_scope is disallowed and unnecessary");
+				continue;
+			}
+
+			bool previously_added = false;
+			for_array(import_index, file_scope->imported) {
+				Scope *prev = file_scope->imported.e[import_index];
+				if (prev == scope) {
+					previously_added = true;
+					break;
+				}
+			}
+
+			if (!previously_added) {
+				array_add(&file_scope->imported, scope);
+			} else {
+				warning(id->token, "Multiple #import of the same file within this scope");
+			}
+
+			if (str_eq(id->import_name.string, str_lit("."))) {
+				// NOTE(bill): Add imported entities to this file's scope
+				for_array(elem_index, scope->elements.entries) {
+					Entity *e = scope->elements.entries.e[elem_index].value;
+					if (e->scope == file_scope) {
+						continue;
+					}
+
+					// NOTE(bill): Do not add other imported entities
+					add_entity(c, file_scope, NULL, e);
+					if (!id->is_load) { // `#import`ed entities don't get exported
+						HashKey key = hash_string(e->token.string);
+						map_entity_set(&file_scope->implicit, key, e);
+					}
+				}
+			} else {
+				String import_name = id->import_name.string;
+				if (import_name.len == 0) {
+					// NOTE(bill): use file name (without extension) as the identifier
+					// If it is a valid identifier
+					String filename = id->fullpath;
+					isize slash = 0;
+					isize dot = 0;
+					for (isize i = filename.len-1; i >= 0; i--) {
+						u8 c = filename.text[i];
+						if (c == '/' || c == '\\') {
+							break;
+						}
+						slash = i;
+					}
+
+					filename.text += slash;
+					filename.len -= slash;
+
+					dot = filename.len;
+					while (dot --> 0) {
+						u8 c = filename.text[dot];
+						if (c == '.') {
+							break;
+						}
+					}
+
+					filename.len = dot;
+
+					if (is_string_an_identifier(filename)) {
+						import_name = filename;
+					} else {
+						error(ast_node_token(decl),
+						      "File name, %.*s, cannot be as an import name as it is not a valid identifier",
+						      LIT(filename));
+					}
+				}
+
+				if (import_name.len > 0) {
+					id->import_name.string = import_name;
+					Entity *e = make_entity_import_name(c->allocator, file_scope, id->import_name, t_invalid,
+					                                    id->fullpath, id->import_name.string,
+					                                    scope);
+					add_entity(c, file_scope, NULL, e);
+				}
+			}
+		}
+	}
+
+	check_global_entity(c, Entity_TypeName);
+
+	init_preload_types(c);
+	add_implicit_value(c, ImplicitValue_context, str_lit("context"), str_lit("__context"), t_context);
+
+	check_global_entity(c, Entity_Constant);
+	check_global_entity(c, Entity_Procedure);
+	check_global_entity(c, Entity_Variable);
+
+	for (isize i = 1; i < ImplicitValue_Count; i++) {
+		// NOTE(bill): First is invalid
+		Entity *e = c->info.implicit_values[i];
+		GB_ASSERT(e->kind == Entity_ImplicitValue);
+
+		ImplicitValueInfo *ivi = &implicit_value_infos[i];
+		Entity *backing = scope_lookup_entity(e->scope, ivi->backing_name);
+		GB_ASSERT(backing != NULL);
+		e->ImplicitValue.backing = backing;
+	}
+
+
+	// Check procedure bodies
+	for_array(i, c->procs) {
+		ProcedureInfo *pi = &c->procs.e[i];
+		add_curr_ast_file(c, pi->file);
+
+		bool bounds_check    = (pi->tags & ProcTag_bounds_check)    != 0;
+		bool no_bounds_check = (pi->tags & ProcTag_no_bounds_check) != 0;
+
+		CheckerContext prev_context = c->context;
+
+		if (bounds_check) {
+			c->context.stmt_state_flags |= StmtStateFlag_bounds_check;
+			c->context.stmt_state_flags &= ~StmtStateFlag_no_bounds_check;
+		} else if (no_bounds_check) {
+			c->context.stmt_state_flags |= StmtStateFlag_no_bounds_check;
+			c->context.stmt_state_flags &= ~StmtStateFlag_bounds_check;
+		}
+
+		check_proc_body(c, pi->token, pi->decl, pi->type, pi->body);
+
+		c->context = prev_context;
+	}
+
+	// Add untyped expression values
+	for_array(i, c->info.untyped.entries) {
+		MapExprInfoEntry *entry = &c->info.untyped.entries.e[i];
+		HashKey key = entry->key;
+		AstNode *expr = cast(AstNode *)cast(uintptr)key.key;
+		ExprInfo *info = &entry->value;
+		if (info != NULL && expr != NULL) {
+			if (is_type_typed(info->type)) {
+				compiler_error("%s (type %s) is typed!", expr_to_string(expr), type_to_string(info->type));
+			}
+			add_type_and_value(&c->info, expr, info->mode, info->type, info->value);
+		}
+	}
+
+	for (isize i = 0; i < gb_count_of(basic_types)-1; i++) {
+		Type *t = &basic_types[i];
+		if (t->Basic.size > 0) {
+			add_type_info_type(c, t);
+		}
+	}
+
+	for (isize i = 0; i < gb_count_of(basic_type_aliases)-1; i++) {
+		Type *t = &basic_type_aliases[i];
+		if (t->Basic.size > 0) {
+			add_type_info_type(c, t);
+		}
+	}
+
+	map_scope_destroy(&file_scopes);
+	array_free(&import_decls);
+}
+
+
+

+ 545 - 0
src/checker/decl.c

@@ -0,0 +1,545 @@
+bool check_is_terminating(AstNode *node);
+void check_stmt         (Checker *c, AstNode *node, u32 flags);
+void check_stmt_list    (Checker *c, AstNodeArray stmts, u32 flags);
+void check_type_decl    (Checker *c, Entity *e, AstNode *type_expr, Type *def, CycleChecker *cycle_checker);
+void check_const_decl   (Checker *c, Entity *e, AstNode *type_expr, AstNode *init_expr);
+void check_proc_decl    (Checker *c, Entity *e, DeclInfo *d);
+void check_var_decl     (Checker *c, Entity *e, Entity **entities, isize entity_count, AstNode *type_expr, AstNode *init_expr);
+
+// NOTE(bill): `content_name` is for debugging and error messages
+Type *check_init_variable(Checker *c, Entity *e, Operand *operand, String context_name) {
+	if (operand->mode == Addressing_Invalid ||
+	    operand->type == t_invalid ||
+	    e->type == t_invalid) {
+
+		if (operand->mode == Addressing_Builtin) {
+			gbString expr_str = expr_to_string(operand->expr);
+
+			// TODO(bill): is this a good enough error message?
+			error(ast_node_token(operand->expr),
+			      "Cannot assign builtin procedure `%s` in %.*s",
+			      expr_str,
+			      LIT(context_name));
+
+			operand->mode = Addressing_Invalid;
+
+			gb_string_free(expr_str);
+		}
+
+
+		if (e->type == NULL) {
+			e->type = t_invalid;
+		}
+		return NULL;
+	}
+
+	if (e->type == NULL) {
+		// NOTE(bill): Use the type of the operand
+		Type *t = operand->type;
+		if (is_type_untyped(t)) {
+			if (t == t_invalid || is_type_untyped_nil(t)) {
+				error(e->token, "Use of untyped nil in %.*s", LIT(context_name));
+				e->type = t_invalid;
+				return NULL;
+			}
+			t = default_type(t);
+		}
+		e->type = t;
+	}
+
+	check_assignment(c, operand, e->type, context_name);
+	if (operand->mode == Addressing_Invalid) {
+		return NULL;
+	}
+
+	return e->type;
+}
+
+void check_init_variables(Checker *c, Entity **lhs, isize lhs_count, AstNodeArray inits, String context_name) {
+	if ((lhs == NULL || lhs_count == 0) && inits.count == 0) {
+		return;
+	}
+
+	gbTempArenaMemory tmp = gb_temp_arena_memory_begin(&c->tmp_arena);
+
+	// NOTE(bill): If there is a bad syntax error, rhs > lhs which would mean there would need to be
+	// an extra allocation
+	Array(Operand) operands;
+	array_init_reserve(&operands, c->tmp_allocator, 2*lhs_count);
+
+	for_array(i, inits) {
+		AstNode *rhs = inits.e[i];
+		Operand o = {0};
+		check_multi_expr(c, &o, rhs);
+		if (o.type->kind != Type_Tuple) {
+			array_add(&operands, o);
+		} else {
+			TypeTuple *tuple = &o.type->Tuple;
+			for (isize j = 0; j < tuple->variable_count; j++) {
+				o.type = tuple->variables[j]->type;
+				array_add(&operands, o);
+			}
+		}
+	}
+
+	isize rhs_count = operands.count;
+	for_array(i, operands) {
+		if (operands.e[i].mode == Addressing_Invalid) {
+			rhs_count--;
+		}
+	}
+
+
+	isize max = gb_min(lhs_count, rhs_count);
+	for (isize i = 0; i < max; i++) {
+		check_init_variable(c, lhs[i], &operands.e[i], context_name);
+	}
+
+	if (rhs_count > 0 && lhs_count != rhs_count) {
+		error(lhs[0]->token, "Assignment count mismatch `%td` := `%td`", lhs_count, rhs_count);
+	}
+
+	gb_temp_arena_memory_end(tmp);
+}
+
+
+
+void check_entity_decl(Checker *c, Entity *e, DeclInfo *d, Type *named_type, CycleChecker *cycle_checker) {
+	if (e->type != NULL) {
+		return;
+	}
+
+	if (d == NULL) {
+		DeclInfo **found = map_decl_info_get(&c->info.entities, hash_pointer(e));
+		if (found) {
+			d = *found;
+		} else {
+			e->type = t_invalid;
+			set_base_type(named_type, t_invalid);
+			return;
+			// GB_PANIC("`%.*s` should been declared!", LIT(e->token.string));
+		}
+	}
+
+	if (e->kind == Entity_Procedure) {
+		check_proc_decl(c, e, d);
+		return;
+	}
+	CheckerContext prev = c->context;
+	c->context.scope = d->scope;
+	c->context.decl  = d;
+
+	switch (e->kind) {
+	case Entity_Constant:
+		check_const_decl(c, e, d->type_expr, d->init_expr);
+		break;
+	case Entity_Variable:
+		check_var_decl(c, e, d->entities, d->entity_count, d->type_expr, d->init_expr);
+		break;
+	case Entity_TypeName:
+		check_type_decl(c, e, d->type_expr, named_type, cycle_checker);
+		break;
+	}
+
+	c->context = prev;
+}
+
+
+
+void check_var_decl_node(Checker *c, AstNode *node) {
+	ast_node(vd, VarDecl, node);
+	isize entity_count = vd->names.count;
+	isize entity_index = 0;
+	Entity **entities = gb_alloc_array(c->allocator, Entity *, entity_count);
+
+	for_array(i, vd->names) {
+		AstNode *name = vd->names.e[i];
+		Entity *entity = NULL;
+		if (name->kind == AstNode_Ident) {
+			Token token = name->Ident;
+			String str = token.string;
+			Entity *found = NULL;
+			// NOTE(bill): Ignore assignments to `_`
+			if (str_ne(str, str_lit("_"))) {
+				found = current_scope_lookup_entity(c->context.scope, str);
+			}
+			if (found == NULL) {
+				entity = make_entity_variable(c->allocator, c->context.scope, token, NULL);
+				add_entity_definition(&c->info, name, entity);
+			} else {
+				TokenPos pos = found->token.pos;
+				error(token,
+				      "Redeclaration of `%.*s` in this scope\n"
+				      "\tat %.*s(%td:%td)",
+				      LIT(str), LIT(pos.file), pos.line, pos.column);
+				entity = found;
+			}
+		} else {
+			error(ast_node_token(name), "A variable declaration must be an identifier");
+		}
+		if (entity == NULL) {
+			entity = make_entity_dummy_variable(c->allocator, c->global_scope, ast_node_token(name));
+		}
+		entities[entity_index++] = entity;
+	}
+
+	Type *init_type = NULL;
+	if (vd->type) {
+		init_type = check_type_extra(c, vd->type, NULL, NULL);
+		if (init_type == NULL)
+			init_type = t_invalid;
+	}
+
+	for (isize i = 0; i < entity_count; i++) {
+		Entity *e = entities[i];
+		GB_ASSERT(e != NULL);
+		if (e->flags & EntityFlag_Visited) {
+			e->type = t_invalid;
+			continue;
+		}
+		e->flags |= EntityFlag_Visited;
+
+		if (e->type == NULL)
+			e->type = init_type;
+	}
+
+	check_init_variables(c, entities, entity_count, vd->values, str_lit("variable declaration"));
+
+	for_array(i, vd->names) {
+		if (entities[i] != NULL) {
+			add_entity(c, c->context.scope, vd->names.e[i], entities[i]);
+		}
+	}
+
+}
+
+
+
+void check_init_constant(Checker *c, Entity *e, Operand *operand) {
+	if (operand->mode == Addressing_Invalid ||
+	    operand->type == t_invalid ||
+	    e->type == t_invalid) {
+		if (e->type == NULL) {
+			e->type = t_invalid;
+		}
+		return;
+	}
+
+	if (operand->mode != Addressing_Constant) {
+		// TODO(bill): better error
+		error(ast_node_token(operand->expr),
+		      "`%.*s` is not a constant", LIT(ast_node_token(operand->expr).string));
+		if (e->type == NULL) {
+			e->type = t_invalid;
+		}
+		return;
+	}
+	// if (!is_type_constant_type(operand->type)) {
+	// 	gbString type_str = type_to_string(operand->type);
+	// 	defer (gb_string_free(type_str));
+	// 	error(ast_node_token(operand->expr),
+	// 	      "Invalid constant type: `%s`", type_str);
+	// 	if (e->type == NULL) {
+	// 		e->type = t_invalid;
+	// 	}
+	// 	return;
+	// }
+
+	if (e->type == NULL) { // NOTE(bill): type inference
+		e->type = operand->type;
+	}
+
+	check_assignment(c, operand, e->type, str_lit("constant declaration"));
+	if (operand->mode == Addressing_Invalid) {
+		return;
+	}
+
+	e->Constant.value = operand->value;
+}
+
+
+void check_const_decl(Checker *c, Entity *e, AstNode *type_expr, AstNode *init_expr) {
+	GB_ASSERT(e->type == NULL);
+
+	if (e->flags & EntityFlag_Visited) {
+		e->type = t_invalid;
+		return;
+	}
+	e->flags |= EntityFlag_Visited;
+
+	if (type_expr) {
+		Type *t = check_type(c, type_expr);
+		// if (!is_type_constant_type(t)) {
+		// 	gbString str = type_to_string(t);
+		// 	defer (gb_string_free(str));
+		// 	error(ast_node_token(type_expr),
+		// 	      "Invalid constant type `%s`", str);
+		// 	e->type = t_invalid;
+		// 	return;
+		// }
+		e->type = t;
+	}
+
+	Operand operand = {0};
+	if (init_expr) {
+		check_expr(c, &operand, init_expr);
+	}
+	check_init_constant(c, e, &operand);
+}
+
+void check_type_decl(Checker *c, Entity *e, AstNode *type_expr, Type *def, CycleChecker *cycle_checker) {
+	GB_ASSERT(e->type == NULL);
+	Type *named = make_type_named(c->allocator, e->token.string, NULL, e);
+	named->Named.type_name = e;
+	if (def != NULL && def->kind == Type_Named) {
+		def->Named.base = named;
+	}
+	e->type = named;
+
+	CycleChecker local_cycle_checker = {0};
+	if (cycle_checker == NULL) {
+		cycle_checker = &local_cycle_checker;
+	}
+
+	Type *bt = check_type_extra(c, type_expr, named, cycle_checker_add(cycle_checker, e));
+	named->Named.base = bt;
+	named->Named.base = base_type(named->Named.base);
+	if (named->Named.base == t_invalid) {
+		gb_printf("check_type_decl: %s\n", type_to_string(named));
+	}
+
+	cycle_checker_destroy(&local_cycle_checker);
+}
+
+
+bool are_signatures_similar_enough(Type *a_, Type *b_) {
+	GB_ASSERT(a_->kind == Type_Proc);
+	GB_ASSERT(b_->kind == Type_Proc);
+	TypeProc *a = &a_->Proc;
+	TypeProc *b = &b_->Proc;
+
+	if (a->param_count != b->param_count) {
+		return false;
+	}
+	if (a->result_count != b->result_count) {
+		return false;
+	}
+	for (isize i = 0; i < a->param_count; i++) {
+		Type *x = base_type(a->params->Tuple.variables[i]->type);
+		Type *y = base_type(b->params->Tuple.variables[i]->type);
+		if (is_type_pointer(x) && is_type_pointer(y)) {
+			continue;
+		}
+
+		if (!are_types_identical(x, y)) {
+			return false;
+		}
+	}
+	for (isize i = 0; i < a->result_count; i++) {
+		Type *x = base_type(a->results->Tuple.variables[i]->type);
+		Type *y = base_type(b->results->Tuple.variables[i]->type);
+		if (is_type_pointer(x) && is_type_pointer(y)) {
+			continue;
+		}
+
+		if (!are_types_identical(x, y)) {
+			return false;
+		}
+	}
+
+	return true;
+}
+
+void check_proc_decl(Checker *c, Entity *e, DeclInfo *d) {
+	GB_ASSERT(e->type == NULL);
+
+	Type *proc_type = make_type_proc(c->allocator, e->scope, NULL, 0, NULL, 0, false);
+	e->type = proc_type;
+	ast_node(pd, ProcDecl, d->proc_decl);
+
+	check_open_scope(c, pd->type);
+	check_procedure_type(c, proc_type, pd->type);
+
+	bool is_foreign      = (pd->tags & ProcTag_foreign)   != 0;
+	bool is_link_name    = (pd->tags & ProcTag_link_name) != 0;
+	bool is_inline       = (pd->tags & ProcTag_inline)    != 0;
+	bool is_no_inline    = (pd->tags & ProcTag_no_inline) != 0;
+
+	if ((d->scope->is_file || d->scope->is_global) &&
+	    str_eq(e->token.string, str_lit("main"))) {
+		if (proc_type != NULL) {
+			TypeProc *pt = &proc_type->Proc;
+			if (pt->param_count != 0 ||
+			    pt->result_count) {
+				gbString str = type_to_string(proc_type);
+				error(e->token,
+				      "Procedure type of `main` was expected to be `proc()`, got %s", str);
+				gb_string_free(str);
+			}
+		}
+	}
+
+	if (is_inline && is_no_inline) {
+		error(ast_node_token(pd->type),
+		      "You cannot apply both `inline` and `no_inline` to a procedure");
+	}
+
+	if (is_foreign && is_link_name) {
+		error(ast_node_token(pd->type),
+		      "You cannot apply both `foreign` and `link_name` to a procedure");
+	}
+
+	if (pd->body != NULL) {
+		if (is_foreign) {
+			error(ast_node_token(pd->body),
+			      "A procedure tagged as `#foreign` cannot have a body");
+		}
+
+		d->scope = c->context.scope;
+
+		GB_ASSERT(pd->body->kind == AstNode_BlockStmt);
+		check_procedure_later(c, c->curr_ast_file, e->token, d, proc_type, pd->body, pd->tags);
+	}
+
+	if (is_foreign) {
+		MapEntity *fp = &c->info.foreign_procs;
+		AstNodeProcDecl *proc_decl = &d->proc_decl->ProcDecl;
+		String name = proc_decl->name->Ident.string;
+		if (proc_decl->foreign_name.len > 0) {
+			name = proc_decl->foreign_name;
+		}
+		HashKey key = hash_string(name);
+		Entity **found = map_entity_get(fp, key);
+		if (found) {
+			Entity *f = *found;
+			TokenPos pos = f->token.pos;
+			Type *this_type = base_type(e->type);
+			Type *other_type = base_type(f->type);
+			if (!are_signatures_similar_enough(this_type, other_type)) {
+				error(ast_node_token(d->proc_decl),
+				      "Redeclaration of #foreign procedure `%.*s` with different type signatures\n"
+				      "\tat %.*s(%td:%td)",
+				      LIT(name), LIT(pos.file), pos.line, pos.column);
+			}
+		} else {
+			map_entity_set(fp, key, e);
+		}
+	} else if (is_link_name) {
+		MapEntity *fp = &c->info.foreign_procs;
+		AstNodeProcDecl *proc_decl = &d->proc_decl->ProcDecl;
+		String name = proc_decl->link_name;
+
+		HashKey key = hash_string(name);
+		Entity **found = map_entity_get(fp, key);
+		if (found) {
+			Entity *f = *found;
+			TokenPos pos = f->token.pos;
+			error(ast_node_token(d->proc_decl),
+			      "Non unique #link_name for procedure `%.*s`\n"
+			      "\tother at %.*s(%td:%td)",
+			      LIT(name), LIT(pos.file), pos.line, pos.column);
+		} else {
+			map_entity_set(fp, key, e);
+		}
+	}
+
+	check_close_scope(c);
+}
+
+void check_var_decl(Checker *c, Entity *e, Entity **entities, isize entity_count, AstNode *type_expr, AstNode *init_expr) {
+	GB_ASSERT(e->type == NULL);
+	GB_ASSERT(e->kind == Entity_Variable);
+
+	if (e->flags & EntityFlag_Visited) {
+		e->type = t_invalid;
+		return;
+	}
+	e->flags |= EntityFlag_Visited;
+
+	if (type_expr != NULL)
+		e->type = check_type_extra(c, type_expr, NULL, NULL);
+
+	if (init_expr == NULL) {
+		if (type_expr == NULL)
+			e->type = t_invalid;
+		return;
+	}
+
+	if (entities == NULL || entity_count == 1) {
+		GB_ASSERT(entities == NULL || entities[0] == e);
+		Operand operand = {0};
+		check_expr(c, &operand, init_expr);
+		check_init_variable(c, e, &operand, str_lit("variable declaration"));
+	}
+
+	if (type_expr != NULL) {
+		for (isize i = 0; i < entity_count; i++)
+			entities[i]->type = e->type;
+	}
+
+	AstNodeArray inits;
+	array_init_reserve(&inits, c->allocator, 1);
+	array_add(&inits, init_expr);
+	check_init_variables(c, entities, entity_count, inits, str_lit("variable declaration"));
+}
+
+void check_proc_body(Checker *c, Token token, DeclInfo *decl, Type *type, AstNode *body) {
+	GB_ASSERT(body->kind == AstNode_BlockStmt);
+
+	CheckerContext old_context = c->context;
+	c->context.scope = decl->scope;
+	c->context.decl = decl;
+
+	GB_ASSERT(type->kind == Type_Proc);
+	if (type->Proc.param_count > 0) {
+		TypeTuple *params = &type->Proc.params->Tuple;
+		for (isize i = 0; i < params->variable_count; i++) {
+			Entity *e = params->variables[i];
+			GB_ASSERT(e->kind == Entity_Variable);
+			if (!(e->flags & EntityFlag_Anonymous)) {
+				continue;
+			}
+			String name = e->token.string;
+			Type *t = base_type(type_deref(e->type));
+			if (is_type_struct(t) || is_type_raw_union(t)) {
+				Scope **found = map_scope_get(&c->info.scopes, hash_pointer(t->Record.node));
+				GB_ASSERT(found != NULL);
+				for_array(i, (*found)->elements.entries) {
+					Entity *f = (*found)->elements.entries.e[i].value;
+					if (f->kind == Entity_Variable) {
+						Entity *uvar = make_entity_using_variable(c->allocator, e, f->token, f->type);
+						Entity *prev = scope_insert_entity(c->context.scope, uvar);
+						if (prev != NULL) {
+							error(e->token, "Namespace collision while `using` `%.*s` of: %.*s", LIT(name), LIT(prev->token.string));
+							break;
+						}
+					}
+				}
+			} else {
+				error(e->token, "`using` can only be applied to variables of type struct or raw_union");
+				break;
+			}
+		}
+	}
+
+	push_procedure(c, type);
+	{
+		ast_node(bs, BlockStmt, body);
+		// TODO(bill): Check declarations first (except mutable variable declarations)
+		check_stmt_list(c, bs->stmts, 0);
+		if (type->Proc.result_count > 0) {
+			if (!check_is_terminating(body)) {
+				error(bs->close, "Missing return statement at the end of the procedure");
+			}
+		}
+	}
+	pop_procedure(c);
+
+
+	check_scope_usage(c, c->context.scope);
+
+	c->context = old_context;
+}
+
+
+

+ 179 - 0
src/checker/entity.c

@@ -0,0 +1,179 @@
+typedef struct Scope Scope;
+typedef struct Checker Checker;
+typedef struct Type Type;
+typedef enum BuiltinProcId BuiltinProcId;
+typedef enum ImplicitValueId ImplicitValueId;
+
+#define ENTITY_KINDS \
+	ENTITY_KIND(Invalid) \
+	ENTITY_KIND(Constant) \
+	ENTITY_KIND(Variable) \
+	ENTITY_KIND(TypeName) \
+	ENTITY_KIND(Procedure) \
+	ENTITY_KIND(Builtin) \
+	ENTITY_KIND(ImportName) \
+	ENTITY_KIND(Nil) \
+	ENTITY_KIND(ImplicitValue) \
+	ENTITY_KIND(Count)
+
+typedef enum EntityKind {
+#define ENTITY_KIND(k) GB_JOIN2(Entity_, k),
+	ENTITY_KINDS
+#undef ENTITY_KIND
+} EntityKind;
+
+String const entity_strings[] = {
+#define ENTITY_KIND(k) {cast(u8 *)#k, gb_size_of(#k)-1},
+	ENTITY_KINDS
+#undef ENTITY_KIND
+};
+
+typedef enum EntityFlag {
+	EntityFlag_Visited    = 1<<0,
+	EntityFlag_Used       = 1<<1,
+	EntityFlag_Anonymous  = 1<<2,
+	EntityFlag_Field      = 1<<3,
+	EntityFlag_Param      = 1<<4,
+	EntityFlag_VectorElem = 1<<5,
+} EntityFlag;
+
+typedef struct Entity Entity;
+struct Entity {
+	EntityKind kind;
+	u32        flags;
+	Token      token;
+	Scope *    scope;
+	Type *     type;
+	AstNode *  identifier; // Can be NULL
+
+	// TODO(bill): Cleanup how `using` works for entities
+	Entity *   using_parent;
+	AstNode *  using_expr;
+
+	union {
+		struct {
+			ExactValue value;
+		} Constant;
+		struct {
+			i32 field_index;
+			i32 field_src_index;
+		} Variable;
+		i32 TypeName;
+		i32 Procedure;
+		struct {
+			BuiltinProcId id;
+		} Builtin;
+		struct {
+			String path;
+			String name;
+			Scope *scope;
+			bool    used;
+		} ImportName;
+		i32 Nil;
+		struct {
+			// TODO(bill): Should this be a user-level construct rather than compiler-level?
+			ImplicitValueId id;
+			Entity *        backing;
+		} ImplicitValue;
+	};
+};
+
+Entity *alloc_entity(gbAllocator a, EntityKind kind, Scope *scope, Token token, Type *type) {
+	Entity *entity = gb_alloc_item(a, Entity);
+	entity->kind   = kind;
+	entity->scope  = scope;
+	entity->token  = token;
+	entity->type   = type;
+	return entity;
+}
+
+Entity *make_entity_variable(gbAllocator a, Scope *scope, Token token, Type *type) {
+	Entity *entity = alloc_entity(a, Entity_Variable, scope, token, type);
+	return entity;
+}
+
+Entity *make_entity_using_variable(gbAllocator a, Entity *parent, Token token, Type *type) {
+	GB_ASSERT(parent != NULL);
+	Entity *entity = alloc_entity(a, Entity_Variable, parent->scope, token, type);
+	entity->using_parent = parent;
+	entity->flags |= EntityFlag_Anonymous;
+	return entity;
+}
+
+
+Entity *make_entity_constant(gbAllocator a, Scope *scope, Token token, Type *type, ExactValue value) {
+	Entity *entity = alloc_entity(a, Entity_Constant, scope, token, type);
+	entity->Constant.value = value;
+	return entity;
+}
+
+Entity *make_entity_type_name(gbAllocator a, Scope *scope, Token token, Type *type) {
+	Entity *entity = alloc_entity(a, Entity_TypeName, scope, token, type);
+	return entity;
+}
+
+Entity *make_entity_param(gbAllocator a, Scope *scope, Token token, Type *type, bool anonymous) {
+	Entity *entity = make_entity_variable(a, scope, token, type);
+	entity->flags |= EntityFlag_Used;
+	entity->flags |= EntityFlag_Anonymous*(anonymous != 0);
+	entity->flags |= EntityFlag_Param;
+	return entity;
+}
+
+Entity *make_entity_field(gbAllocator a, Scope *scope, Token token, Type *type, bool anonymous, i32 field_src_index) {
+	Entity *entity = make_entity_variable(a, scope, token, type);
+	entity->Variable.field_src_index = field_src_index;
+	entity->Variable.field_index = field_src_index;
+	entity->flags |= EntityFlag_Field;
+	entity->flags |= EntityFlag_Anonymous*(anonymous != 0);
+	return entity;
+}
+
+Entity *make_entity_vector_elem(gbAllocator a, Scope *scope, Token token, Type *type, i32 field_src_index) {
+	Entity *entity = make_entity_variable(a, scope, token, type);
+	entity->Variable.field_src_index = field_src_index;
+	entity->Variable.field_index = field_src_index;
+	entity->flags |= EntityFlag_Field;
+	entity->flags |= EntityFlag_VectorElem;
+	return entity;
+}
+
+Entity *make_entity_procedure(gbAllocator a, Scope *scope, Token token, Type *signature_type) {
+	Entity *entity = alloc_entity(a, Entity_Procedure, scope, token, signature_type);
+	return entity;
+}
+
+Entity *make_entity_builtin(gbAllocator a, Scope *scope, Token token, Type *type, BuiltinProcId id) {
+	Entity *entity = alloc_entity(a, Entity_Builtin, scope, token, type);
+	entity->Builtin.id = id;
+	return entity;
+}
+
+Entity *make_entity_import_name(gbAllocator a, Scope *scope, Token token, Type *type,
+                                String path, String name, Scope *import_scope) {
+	Entity *entity = alloc_entity(a, Entity_ImportName, scope, token, type);
+	entity->ImportName.path = path;
+	entity->ImportName.name = name;
+	entity->ImportName.scope = import_scope;
+	return entity;
+}
+
+Entity *make_entity_nil(gbAllocator a, String name, Type *type) {
+	Token token = make_token_ident(name);
+	Entity *entity = alloc_entity(a, Entity_Nil, NULL, token, type);
+	return entity;
+}
+
+Entity *make_entity_implicit_value(gbAllocator a, String name, Type *type, ImplicitValueId id) {
+	Token token = make_token_ident(name);
+	Entity *entity = alloc_entity(a, Entity_ImplicitValue, NULL, token, type);
+	entity->ImplicitValue.id = id;
+	return entity;
+}
+
+
+Entity *make_entity_dummy_variable(gbAllocator a, Scope *file_scope, Token token) {
+	token.string = str_lit("_");
+	return make_entity_variable(a, file_scope, token, NULL);
+}
+

+ 4465 - 0
src/checker/expr.c

@@ -0,0 +1,4465 @@
+void     check_expr                (Checker *c, Operand *operand, AstNode *expression);
+void     check_multi_expr          (Checker *c, Operand *operand, AstNode *expression);
+void     check_expr_or_type        (Checker *c, Operand *operand, AstNode *expression);
+ExprKind check_expr_base           (Checker *c, Operand *operand, AstNode *expression, Type *type_hint);
+Type *   check_type_extra          (Checker *c, AstNode *expression, Type *named_type, CycleChecker *cycle_checker);
+Type *   check_type                (Checker *c, AstNode *expression);
+void     check_type_decl           (Checker *c, Entity *e, AstNode *type_expr, Type *def, CycleChecker *cycle_checker);
+Entity * check_selector            (Checker *c, Operand *operand, AstNode *node);
+void     check_not_tuple           (Checker *c, Operand *operand);
+bool     check_value_is_expressible(Checker *c, ExactValue in_value, Type *type, ExactValue *out_value);
+void     convert_to_typed          (Checker *c, Operand *operand, Type *target_type, i32 level);
+gbString expr_to_string            (AstNode *expression);
+void     check_entity_decl         (Checker *c, Entity *e, DeclInfo *decl, Type *named_type, CycleChecker *cycle_checker);
+void     check_proc_body           (Checker *c, Token token, DeclInfo *decl, Type *type, AstNode *body);
+void     update_expr_type          (Checker *c, AstNode *e, Type *type, bool final);
+
+gb_inline Type *check_type(Checker *c, AstNode *expression) {
+	return check_type_extra(c, expression, NULL, NULL);
+}
+
+
+
+bool check_is_assignable_to_using_subtype(Type *dst, Type *src) {
+	Type *prev_src = src;
+	// Type *prev_dst = dst;
+	src = base_type(type_deref(src));
+	// dst = base_type(type_deref(dst));
+	bool src_is_ptr = src != prev_src;
+	// bool dst_is_ptr = dst != prev_dst;
+
+	if (is_type_struct(src)) {
+		for (isize i = 0; i < src->Record.field_count; i++) {
+			Entity *f = src->Record.fields[i];
+			if (f->kind == Entity_Variable && (f->flags & EntityFlag_Anonymous)) {
+				if (are_types_identical(dst, f->type)) {
+					return true;
+				}
+				if (src_is_ptr && is_type_pointer(dst)) {
+					if (are_types_identical(type_deref(dst), f->type)) {
+						return true;
+					}
+				}
+				bool ok = check_is_assignable_to_using_subtype(dst, f->type);
+				if (ok) {
+					return true;
+				}
+			}
+		}
+	}
+	return false;
+}
+
+
+bool check_is_assignable_to(Checker *c, Operand *operand, Type *type) {
+	if (operand->mode == Addressing_Invalid ||
+	    type == t_invalid) {
+		return true;
+	}
+
+	if (operand->mode == Addressing_Builtin) {
+		return false;
+	}
+
+	Type *s = operand->type;
+
+	if (are_types_identical(s, type)) {
+		return true;
+	}
+
+	Type *src = base_type(s);
+	Type *dst = base_type(type);
+
+	if (is_type_untyped(src)) {
+		switch (dst->kind) {
+		case Type_Basic:
+			if (operand->mode == Addressing_Constant) {
+				return check_value_is_expressible(c, operand->value, dst, NULL);
+			}
+			if (src->kind == Type_Basic && src->Basic.kind == Basic_UntypedBool) {
+				return is_type_boolean(dst);
+			}
+			break;
+		}
+		if (type_has_nil(dst)) {
+			return operand->mode == Addressing_Value && operand->type == t_untyped_nil;
+		}
+	}
+
+	if (are_types_identical(dst, src) && (!is_type_named(dst) || !is_type_named(src))) {
+		if (is_type_enum(dst) && is_type_enum(src))  {
+			return are_types_identical(s, type);
+		}
+		return true;
+	}
+
+	if (is_type_maybe(dst)) {
+		Type *elem = base_type(dst)->Maybe.elem;
+		return are_types_identical(elem, s);
+	}
+
+	if (is_type_untyped_nil(src)) {
+		return type_has_nil(dst);
+	}
+
+	// ^T <- rawptr
+	// TODO(bill): Should C-style (not C++) pointer cast be allowed?
+	// if (is_type_pointer(dst) && is_type_rawptr(src)) {
+	    // return true;
+	// }
+
+	// rawptr <- ^T
+	if (is_type_rawptr(dst) && is_type_pointer(src)) {
+	    return true;
+	}
+
+
+
+	if (dst->kind == Type_Array && src->kind == Type_Array) {
+		if (are_types_identical(dst->Array.elem, src->Array.elem)) {
+			return dst->Array.count == src->Array.count;
+		}
+	}
+
+	if (dst->kind == Type_Slice && src->kind == Type_Slice) {
+		if (are_types_identical(dst->Slice.elem, src->Slice.elem)) {
+			return true;
+		}
+	}
+
+	if (is_type_union(dst)) {
+		for (isize i = 0; i < dst->Record.field_count; i++) {
+			Entity *f = dst->Record.fields[i];
+			if (are_types_identical(f->type, s)) {
+				return true;
+			}
+		}
+	}
+
+
+	if (dst == t_any) {
+		// NOTE(bill): Anything can cast to `Any`
+		add_type_info_type(c, s);
+		return true;
+	}
+
+	return false;
+}
+
+
+// NOTE(bill): `content_name` is for debugging and error messages
+void check_assignment(Checker *c, Operand *operand, Type *type, String context_name) {
+	check_not_tuple(c, operand);
+	if (operand->mode == Addressing_Invalid) {
+		return;
+	}
+
+	if (is_type_untyped(operand->type)) {
+		Type *target_type = type;
+
+		if (type == NULL || is_type_any(type) || is_type_untyped_nil(type)) {
+			if (type == NULL && base_type(operand->type) == t_untyped_nil) {
+				error(ast_node_token(operand->expr), "Use of untyped nil in %.*s", LIT(context_name));
+				operand->mode = Addressing_Invalid;
+				return;
+			}
+
+			add_type_info_type(c, type);
+			target_type = default_type(operand->type);
+		}
+		convert_to_typed(c, operand, target_type, 0);
+		if (operand->mode == Addressing_Invalid) {
+			return;
+		}
+	}
+
+	if (type != NULL) {
+		if (!check_is_assignable_to(c, operand, type)) {
+			gbString type_str    = type_to_string(type);
+			gbString op_type_str = type_to_string(operand->type);
+			gbString expr_str    = expr_to_string(operand->expr);
+
+			if (operand->mode == Addressing_Builtin) {
+				// TODO(bill): is this a good enough error message?
+				error(ast_node_token(operand->expr),
+				      "Cannot assign builtin procedure `%s` in %.*s",
+				      expr_str,
+				      LIT(context_name));
+			} else {
+				// TODO(bill): is this a good enough error message?
+				error(ast_node_token(operand->expr),
+				      "Cannot assign value `%s` of type `%s` to `%s` in %.*s",
+				      expr_str,
+				      op_type_str,
+				      type_str,
+				      LIT(context_name));
+			}
+			operand->mode = Addressing_Invalid;
+
+			gb_string_free(expr_str);
+			gb_string_free(op_type_str);
+			gb_string_free(type_str);
+			return;
+		}
+	}
+}
+
+
+void populate_using_entity_map(Checker *c, AstNode *node, Type *t, MapEntity *entity_map) {
+	t = base_type(type_deref(t));
+	gbString str = expr_to_string(node);
+
+	if (t->kind == Type_Record) {
+		for (isize i = 0; i < t->Record.field_count; i++) {
+			Entity *f = t->Record.fields[i];
+			GB_ASSERT(f->kind == Entity_Variable);
+			String name = f->token.string;
+			HashKey key = hash_string(name);
+			Entity **found = map_entity_get(entity_map, key);
+			if (found != NULL) {
+				Entity *e = *found;
+				// TODO(bill): Better type error
+				error(e->token, "`%.*s` is already declared in `%s`", LIT(name), str);
+			} else {
+				map_entity_set(entity_map, key, f);
+				add_entity(c, c->context.scope, NULL, f);
+				if (f->flags & EntityFlag_Anonymous) {
+					populate_using_entity_map(c, node, f->type, entity_map);
+				}
+			}
+		}
+	}
+
+	gb_string_free(str);
+}
+
+void check_const_decl(Checker *c, Entity *e, AstNode *type_expr, AstNode *init_expr);
+
+void check_fields(Checker *c, AstNode *node, AstNodeArray decls,
+                  Entity **fields, isize field_count,
+                  Entity **other_fields, isize other_field_count,
+                  CycleChecker *cycle_checker, String context) {
+	gbTempArenaMemory tmp = gb_temp_arena_memory_begin(&c->tmp_arena);
+
+	MapEntity entity_map = {0};
+	map_entity_init_with_reserve(&entity_map, c->tmp_allocator, 2*(field_count+other_field_count));
+
+	isize other_field_index = 0;
+	Entity *using_index_expr = NULL;
+
+
+	typedef struct {
+		Entity *e;
+		AstNode *t;
+	} Delay;
+	Array(Delay) delayed_const; array_init_reserve(&delayed_const, c->tmp_allocator, other_field_count);
+	Array(Delay) delayed_type;  array_init_reserve(&delayed_type,  c->tmp_allocator, other_field_count);
+
+	for_array(decl_index, decls) {
+		AstNode *decl = decls.e[decl_index];
+		if (decl->kind == AstNode_ConstDecl) {
+			ast_node(cd, ConstDecl, decl);
+
+			isize entity_count = cd->names.count;
+			isize entity_index = 0;
+			Entity **entities = gb_alloc_array(c->allocator, Entity *, entity_count);
+
+			for_array(i, cd->values) {
+				AstNode *name = cd->names.e[i];
+				AstNode *value = cd->values.e[i];
+
+				GB_ASSERT(name->kind == AstNode_Ident);
+				ExactValue v = {ExactValue_Invalid};
+				Token name_token = name->Ident;
+				Entity *e = make_entity_constant(c->allocator, c->context.scope, name_token, NULL, v);
+				entities[entity_index++] = e;
+
+				Delay delay = {e, cd->type};
+				array_add(&delayed_const, delay);
+			}
+
+			isize lhs_count = cd->names.count;
+			isize rhs_count = cd->values.count;
+
+			// TODO(bill): Better error messages or is this good enough?
+			if (rhs_count == 0 && cd->type == NULL) {
+				error(ast_node_token(node), "Missing type or initial expression");
+			} else if (lhs_count < rhs_count) {
+				error(ast_node_token(node), "Extra initial expression");
+			}
+
+			for_array(i, cd->names) {
+				AstNode *name = cd->names.e[i];
+				Entity *e = entities[i];
+				Token name_token = name->Ident;
+				if (str_eq(name_token.string, str_lit("_"))) {
+					other_fields[other_field_index++] = e;
+				} else {
+					HashKey key = hash_string(name_token.string);
+					if (map_entity_get(&entity_map, key) != NULL) {
+						// TODO(bill): Scope checking already checks the declaration
+						error(name_token, "`%.*s` is already declared in this structure", LIT(name_token.string));
+					} else {
+						map_entity_set(&entity_map, key, e);
+						other_fields[other_field_index++] = e;
+					}
+					add_entity(c, c->context.scope, name, e);
+				}
+			}
+		} else if (decl->kind == AstNode_TypeDecl) {
+			ast_node(td, TypeDecl, decl);
+			Token name_token = td->name->Ident;
+
+			Entity *e = make_entity_type_name(c->allocator, c->context.scope, name_token, NULL);
+			Delay delay = {e, td->type};
+			array_add(&delayed_type, delay);
+
+			if (str_eq(name_token.string, str_lit("_"))) {
+				other_fields[other_field_index++] = e;
+			} else {
+				HashKey key = hash_string(name_token.string);
+				if (map_entity_get(&entity_map, key) != NULL) {
+					// TODO(bill): Scope checking already checks the declaration
+					error(name_token, "`%.*s` is already declared in this structure", LIT(name_token.string));
+				} else {
+					map_entity_set(&entity_map, key, e);
+					other_fields[other_field_index++] = e;
+				}
+				add_entity(c, c->context.scope, td->name, e);
+				add_entity_use(c, td->name, e);
+			}
+		}
+	}
+
+	for_array(i, delayed_type) {
+		check_const_decl(c, delayed_type.e[i].e, delayed_type.e[i].t, NULL);
+	}
+	for_array(i, delayed_const) {
+		check_type_decl(c, delayed_const.e[i].e, delayed_const.e[i].t, NULL, NULL);
+	}
+
+	if (node->kind == AstNode_UnionType) {
+		isize field_index = 0;
+		fields[field_index++] = make_entity_type_name(c->allocator, c->context.scope, empty_token, NULL);
+		for_array(decl_index, decls) {
+			AstNode *decl = decls.e[decl_index];
+			if (decl->kind != AstNode_VarDecl) {
+				continue;
+			}
+
+			ast_node(vd, VarDecl, decl);
+			Type *base_type = check_type_extra(c, vd->type, NULL, cycle_checker);
+
+			for_array(name_index, vd->names) {
+				AstNode *name = vd->names.e[name_index];
+				Token name_token = name->Ident;
+
+				Type *type = make_type_named(c->allocator, name_token.string, base_type, NULL);
+				Entity *e = make_entity_type_name(c->allocator, c->context.scope, name_token, type);
+				type->Named.type_name = e;
+				add_entity(c, c->context.scope, name, e);
+
+				if (str_eq(name_token.string, str_lit("_"))) {
+					error(name_token, "`_` cannot be used a union subtype");
+					continue;
+				}
+
+				HashKey key = hash_string(name_token.string);
+				if (map_entity_get(&entity_map, key) != NULL) {
+					// TODO(bill): Scope checking already checks the declaration
+					error(name_token, "`%.*s` is already declared in this union", LIT(name_token.string));
+				} else {
+					map_entity_set(&entity_map, key, e);
+					fields[field_index++] = e;
+				}
+				add_entity_use(c, name, e);
+			}
+		}
+	} else {
+		isize field_index = 0;
+		for_array(decl_index, decls) {
+			AstNode *decl = decls.e[decl_index];
+			if (decl->kind != AstNode_VarDecl) {
+				continue;
+			}
+			ast_node(vd, VarDecl, decl);
+
+			Type *type = check_type_extra(c, vd->type, NULL, cycle_checker);
+
+			if (vd->is_using) {
+				if (vd->names.count > 1) {
+					error(ast_node_token(vd->names.e[0]),
+					      "Cannot apply `using` to more than one of the same type");
+				}
+			}
+
+			for_array(name_index, vd->names) {
+				AstNode *name = vd->names.e[name_index];
+				Token name_token = name->Ident;
+
+				Entity *e = make_entity_field(c->allocator, c->context.scope, name_token, type, vd->is_using, cast(i32)field_index);
+				e->identifier = name;
+				if (str_eq(name_token.string, str_lit("_"))) {
+					fields[field_index++] = e;
+				} else {
+					HashKey key = hash_string(name_token.string);
+					if (map_entity_get(&entity_map, key) != NULL) {
+						// TODO(bill): Scope checking already checks the declaration
+						error(name_token, "`%.*s` is already declared in this type", LIT(name_token.string));
+					} else {
+						map_entity_set(&entity_map, key, e);
+						fields[field_index++] = e;
+						add_entity(c, c->context.scope, name, e);
+					}
+					add_entity_use(c, name, e);
+				}
+			}
+
+
+			if (vd->is_using) {
+				Type *t = base_type(type_deref(type));
+				if (!is_type_struct(t) && !is_type_raw_union(t)) {
+					Token name_token = vd->names.e[0]->Ident;
+					if (is_type_indexable(t)) {
+						bool ok = true;
+						for_array(emi, entity_map.entries) {
+							Entity *e = entity_map.entries.e[emi].value;
+							if (e->kind == Entity_Variable && e->flags & EntityFlag_Anonymous) {
+								if (is_type_indexable(e->type)) {
+									if (e->identifier != vd->names.e[0]) {
+										ok = false;
+										using_index_expr = e;
+										break;
+									}
+								}
+							}
+						}
+						if (ok) {
+							using_index_expr = fields[field_index-1];
+						} else {
+							fields[field_index-1]->flags &= ~EntityFlag_Anonymous;
+							error(name_token, "Previous `using` for an index expression `%.*s`", LIT(name_token.string));
+						}
+					} else {
+						error(name_token, "`using` on a field `%.*s` must be a `struct` or `raw_union`", LIT(name_token.string));
+						continue;
+					}
+				}
+
+				populate_using_entity_map(c, node, type, &entity_map);
+			}
+		}
+	}
+
+	gb_temp_arena_memory_end(tmp);
+}
+
+
+// TODO(bill): Cleanup struct field reordering
+// TODO(bill): Inline sorting procedure?
+gb_global BaseTypeSizes __checker_sizes = {0};
+gb_global gbAllocator   __checker_allocator = {0};
+
+GB_COMPARE_PROC(cmp_struct_entity_size) {
+	// Rule:
+	// Biggest to smallest alignment
+	// if same alignment: biggest to smallest size
+	// if same size: order by source order
+	Entity *x = *(Entity **)a;
+	Entity *y = *(Entity **)b;
+	GB_ASSERT(x != NULL);
+	GB_ASSERT(y != NULL);
+	GB_ASSERT(x->kind == Entity_Variable);
+	GB_ASSERT(y->kind == Entity_Variable);
+	i64 xa = type_align_of(__checker_sizes, __checker_allocator, x->type);
+	i64 ya = type_align_of(__checker_sizes, __checker_allocator, y->type);
+	i64 xs = type_size_of(__checker_sizes, __checker_allocator, x->type);
+	i64 ys = type_size_of(__checker_sizes, __checker_allocator, y->type);
+
+	if (xa == ya) {
+		if (xs == ys) {
+			i32 diff = x->Variable.field_index - y->Variable.field_index;
+			return diff < 0 ? -1 : diff > 0;
+		}
+		return xs > ys ? -1 : xs < ys;
+	}
+	return xa > ya ? -1 : xa < ya;
+}
+
+void check_struct_type(Checker *c, Type *struct_type, AstNode *node, CycleChecker *cycle_checker) {
+	GB_ASSERT(is_type_struct(struct_type));
+	ast_node(st, StructType, node);
+
+	isize field_count = 0;
+	isize other_field_count = 0;
+	for_array(decl_index, st->decls) {
+		AstNode *decl = st->decls.e[decl_index];
+		switch (decl->kind) {
+		case_ast_node(vd, VarDecl, decl);
+			field_count += vd->names.count;
+		case_end;
+
+		case_ast_node(cd, ConstDecl, decl);
+			other_field_count += cd->names.count;
+		case_end;
+
+		case_ast_node(td, TypeDecl, decl);
+			other_field_count += 1;
+		case_end;
+		}
+	}
+
+	Entity **fields = gb_alloc_array(c->allocator, Entity *, field_count);
+	Entity **other_fields = gb_alloc_array(c->allocator, Entity *, other_field_count);
+
+	check_fields(c, node, st->decls, fields, field_count, other_fields, other_field_count, cycle_checker, str_lit("struct"));
+
+
+	struct_type->Record.struct_is_packed    = st->is_packed;
+	struct_type->Record.struct_is_ordered   = st->is_ordered;
+	struct_type->Record.fields              = fields;
+	struct_type->Record.fields_in_src_order = fields;
+	struct_type->Record.field_count         = field_count;
+	struct_type->Record.other_fields        = other_fields;
+	struct_type->Record.other_field_count   = other_field_count;
+
+
+
+	if (!st->is_packed && !st->is_ordered) {
+		// NOTE(bill): Reorder fields for reduced size/performance
+
+		Entity **reordered_fields = gb_alloc_array(c->allocator, Entity *, field_count);
+		for (isize i = 0; i < field_count; i++) {
+			reordered_fields[i] = struct_type->Record.fields_in_src_order[i];
+		}
+
+		// NOTE(bill): Hacky thing
+		// TODO(bill): Probably make an inline sorting procedure rather than use global variables
+		__checker_sizes = c->sizes;
+		__checker_allocator = c->allocator;
+		// NOTE(bill): compound literal order must match source not layout
+		gb_sort_array(reordered_fields, field_count, cmp_struct_entity_size);
+
+		for (isize i = 0; i < field_count; i++) {
+			reordered_fields[i]->Variable.field_index = i;
+		}
+
+		struct_type->Record.fields = reordered_fields;
+	}
+
+	type_set_offsets(c->sizes, c->allocator, struct_type);
+}
+
+void check_union_type(Checker *c, Type *union_type, AstNode *node, CycleChecker *cycle_checker) {
+	GB_ASSERT(is_type_union(union_type));
+	ast_node(ut, UnionType, node);
+
+	isize field_count = 1;
+	isize other_field_count = 0;
+	for_array(decl_index, ut->decls) {
+		AstNode *decl = ut->decls.e[decl_index];
+		switch (decl->kind) {
+		case_ast_node(vd, VarDecl, decl);
+			field_count += vd->names.count;
+		case_end;
+
+		case_ast_node(cd, ConstDecl, decl);
+			other_field_count += cd->names.count;
+		case_end;
+
+		case_ast_node(td, TypeDecl, decl);
+			other_field_count += 1;
+		case_end;
+		}
+	}
+
+	Entity **fields = gb_alloc_array(c->allocator, Entity *, field_count);
+	Entity **other_fields = gb_alloc_array(c->allocator, Entity *, other_field_count);
+
+	check_fields(c, node, ut->decls, fields, field_count, other_fields, other_field_count, cycle_checker, str_lit("union"));
+
+	union_type->Record.fields            = fields;
+	union_type->Record.field_count       = field_count;
+	union_type->Record.other_fields      = other_fields;
+	union_type->Record.other_field_count = other_field_count;
+}
+
+void check_raw_union_type(Checker *c, Type *union_type, AstNode *node, CycleChecker *cycle_checker) {
+	GB_ASSERT(node->kind == AstNode_RawUnionType);
+	GB_ASSERT(is_type_raw_union(union_type));
+	ast_node(ut, RawUnionType, node);
+
+	isize field_count = 0;
+	isize other_field_count = 0;
+	for_array(decl_index, ut->decls) {
+		AstNode *decl = ut->decls.e[decl_index];
+		switch (decl->kind) {
+		case_ast_node(vd, VarDecl, decl);
+			field_count += vd->names.count;
+		case_end;
+
+		case_ast_node(cd, ConstDecl, decl);
+			other_field_count += cd->names.count;
+		case_end;
+
+		case_ast_node(td, TypeDecl, decl);
+			other_field_count += 1;
+		case_end;
+		}
+	}
+
+	Entity **fields = gb_alloc_array(c->allocator, Entity *, field_count);
+	Entity **other_fields = gb_alloc_array(c->allocator, Entity *, other_field_count);
+
+	check_fields(c, node, ut->decls, fields, field_count, other_fields, other_field_count, cycle_checker, str_lit("raw union"));
+
+	union_type->Record.fields = fields;
+	union_type->Record.field_count = field_count;
+	union_type->Record.other_fields = other_fields;
+	union_type->Record.other_field_count = other_field_count;
+}
+
+GB_COMPARE_PROC(cmp_enum_order) {
+	// Rule:
+	// Biggest to smallest alignment
+	// if same alignment: biggest to smallest size
+	// if same size: order by source order
+	Entity *x = *(Entity **)a;
+	Entity *y = *(Entity **)b;
+	GB_ASSERT(x != NULL);
+	GB_ASSERT(y != NULL);
+	GB_ASSERT(x->kind == Entity_Constant);
+	GB_ASSERT(y->kind == Entity_Constant);
+	GB_ASSERT(x->Constant.value.kind == ExactValue_Integer);
+	GB_ASSERT(y->Constant.value.kind == ExactValue_Integer);
+	i64 i = x->Constant.value.value_integer;
+	i64 j = y->Constant.value.value_integer;
+
+	return i < j ? -1 : i > j;
+}
+
+
+
+void check_enum_type(Checker *c, Type *enum_type, Type *named_type, AstNode *node) {
+	GB_ASSERT(node->kind == AstNode_EnumType);
+	GB_ASSERT(is_type_enum(enum_type));
+	ast_node(et, EnumType, node);
+
+
+
+	Type *base_type = t_int;
+	if (et->base_type != NULL) {
+		base_type = check_type(c, et->base_type);
+	}
+
+	if (base_type == NULL || !is_type_integer(base_type)) {
+		error(et->token, "Base type for enumeration must be an integer");
+		return;
+	} else
+	if (base_type == NULL) {
+		base_type = t_int;
+	}
+	enum_type->Record.enum_base = base_type;
+
+	Entity **fields = gb_alloc_array(c->allocator, Entity *, et->fields.count);
+	isize field_index = 0;
+	ExactValue iota = make_exact_value_integer(-1);
+	i64 min_value = 0;
+	i64 max_value = 0;
+
+	Type *constant_type = enum_type;
+	if (named_type != NULL) {
+		constant_type = named_type;
+	}
+
+
+	gbTempArenaMemory tmp = gb_temp_arena_memory_begin(&c->tmp_arena);
+
+	MapEntity entity_map = {0};
+	map_entity_init_with_reserve(&entity_map, c->tmp_allocator, 2*(et->fields.count));
+
+	Entity *blank_entity = make_entity_constant(c->allocator, c->context.scope, blank_token, constant_type, make_exact_value_integer(0));;
+
+	for_array(i, et->fields) {
+		AstNode *field = et->fields.e[i];
+
+		ast_node(f, FieldValue, field);
+		Token name_token = f->field->Ident;
+
+		if (str_eq(name_token.string, str_lit("count"))) {
+			error(name_token, "`count` is a reserved identifier for enumerations");
+			fields[field_index++] = blank_entity;
+			continue;
+		} else if (str_eq(name_token.string, str_lit("min_value"))) {
+			error(name_token, "`min_value` is a reserved identifier for enumerations");
+			fields[field_index++] = blank_entity;
+			continue;
+		} else if (str_eq(name_token.string, str_lit("max_value"))) {
+			error(name_token, "`max_value` is a reserved identifier for enumerations");
+			fields[field_index++] = blank_entity;
+			continue;
+		}
+
+		Operand o = {0};
+		if (f->value != NULL) {
+			check_expr(c, &o, f->value);
+			if (o.mode != Addressing_Constant) {
+				error(ast_node_token(f->value), "Enumeration value must be a constant integer");
+				o.mode = Addressing_Invalid;
+			}
+			if (o.mode != Addressing_Invalid) {
+				check_assignment(c, &o, constant_type, str_lit("enumeration"));
+			}
+			if (o.mode != Addressing_Invalid) {
+				iota = o.value;
+			} else {
+				Token add_token = {Token_Add};
+				iota = exact_binary_operator_value(add_token, iota, make_exact_value_integer(1));
+			}
+		} else {
+			Token add_token = {Token_Add};
+			iota = exact_binary_operator_value(add_token, iota, make_exact_value_integer(1));
+		}
+
+
+		Entity *e = make_entity_constant(c->allocator, c->context.scope, name_token, constant_type, iota);
+		if (min_value > iota.value_integer) {
+			min_value = iota.value_integer;
+		}
+		if (max_value < iota.value_integer) {
+			max_value = iota.value_integer;
+		}
+
+		HashKey key = hash_string(name_token.string);
+		if (map_entity_get(&entity_map, key)) {
+			// TODO(bill): Scope checking already checks the declaration
+			error(name_token, "`%.*s` is already declared in this enumeration", LIT(name_token.string));
+		} else {
+			map_entity_set(&entity_map, key, e);
+			add_entity(c, c->context.scope, NULL, e);
+			fields[field_index++] = e;
+		}
+		add_entity_use(c, f->field, e);
+	}
+
+	GB_ASSERT(field_index <= et->fields.count);
+
+	gb_sort_array(fields, field_index, cmp_enum_order);
+
+	enum_type->Record.other_fields = fields;
+	enum_type->Record.other_field_count = field_index;
+
+	enum_type->Record.enum_count = make_entity_constant(c->allocator, NULL,
+		make_token_ident(str_lit("count")), t_int, make_exact_value_integer(enum_type->Record.other_field_count));
+	enum_type->Record.min_value  = make_entity_constant(c->allocator, NULL,
+		make_token_ident(str_lit("min_value")), constant_type, make_exact_value_integer(min_value));
+	enum_type->Record.max_value  = make_entity_constant(c->allocator, NULL,
+		make_token_ident(str_lit("max_value")), constant_type, make_exact_value_integer(max_value));
+
+	gb_temp_arena_memory_end(tmp);
+}
+
+Type *check_get_params(Checker *c, Scope *scope, AstNodeArray params, bool *is_variadic_) {
+	if (params.count == 0) {
+		return NULL;
+	}
+
+	bool is_variadic = false;
+
+	Type *tuple = make_type_tuple(c->allocator);
+
+	isize variable_count = 0;
+	for_array(i, params) {
+		AstNode *field = params.e[i];
+		ast_node(p, Parameter, field);
+		variable_count += p->names.count;
+	}
+
+	Entity **variables = gb_alloc_array(c->allocator, Entity *, variable_count);
+	isize variable_index = 0;
+	for_array(i, params) {
+		ast_node(p, Parameter, params.e[i]);
+		AstNode *type_expr = p->type;
+		if (type_expr) {
+			if (type_expr->kind == AstNode_Ellipsis) {
+				type_expr = type_expr->Ellipsis.expr;
+				if (i+1 == params.count) {
+					is_variadic = true;
+				} else {
+					error(ast_node_token(params.e[i]), "Invalid AST: Invalid variadic parameter");
+				}
+			}
+
+			Type *type = check_type(c, type_expr);
+			for_array(j, p->names) {
+				AstNode *name = p->names.e[j];
+				if (name->kind == AstNode_Ident) {
+					Entity *param = make_entity_param(c->allocator, scope, name->Ident, type, p->is_using);
+					add_entity(c, scope, name, param);
+					variables[variable_index++] = param;
+				} else {
+					error(ast_node_token(name), "Invalid AST: Invalid parameter");
+				}
+			}
+		}
+	}
+
+	variable_count = variable_index;
+
+	if (is_variadic) {
+		GB_ASSERT(params.count > 0);
+		// NOTE(bill): Change last variadic parameter to be a slice
+		// Custom Calling convention for variadic parameters
+		Entity *end = variables[variable_count-1];
+		end->type = make_type_slice(c->allocator, end->type);
+	}
+
+	tuple->Tuple.variables = variables;
+	tuple->Tuple.variable_count = variable_count;
+
+	if (is_variadic_) *is_variadic_ = is_variadic;
+
+	return tuple;
+}
+
+Type *check_get_results(Checker *c, Scope *scope, AstNodeArray results) {
+	if (results.count == 0) {
+		return NULL;
+	}
+	Type *tuple = make_type_tuple(c->allocator);
+
+	Entity **variables = gb_alloc_array(c->allocator, Entity *, results.count);
+	isize variable_index = 0;
+	for_array(i, results) {
+		AstNode *item = results.e[i];
+		Type *type = check_type(c, item);
+		Token token = ast_node_token(item);
+		token.string = str_lit(""); // NOTE(bill): results are not named
+		// TODO(bill): Should I have named results?
+		Entity *param = make_entity_param(c->allocator, scope, token, type, false);
+		// NOTE(bill): No need to record
+		variables[variable_index++] = param;
+	}
+	tuple->Tuple.variables = variables;
+	tuple->Tuple.variable_count = results.count;
+
+	return tuple;
+}
+
+
+void check_procedure_type(Checker *c, Type *type, AstNode *proc_type_node) {
+	ast_node(pt, ProcType, proc_type_node);
+
+	bool variadic = false;
+	Type *params  = check_get_params(c, c->context.scope, pt->params, &variadic);
+	Type *results = check_get_results(c, c->context.scope, pt->results);
+
+	isize param_count = 0;
+	isize result_count = 0;
+	if (params)  param_count  = params ->Tuple.variable_count;
+	if (results) result_count = results->Tuple.variable_count;
+
+
+	type->Proc.scope            = c->context.scope;
+	type->Proc.params           = params;
+	type->Proc.param_count      = param_count;
+	type->Proc.results          = results;
+	type->Proc.result_count     = result_count;
+	type->Proc.variadic         = variadic;
+	// type->Proc.implicit_context = implicit_context;
+}
+
+
+void check_identifier(Checker *c, Operand *o, AstNode *n, Type *named_type, CycleChecker *cycle_checker) {
+	GB_ASSERT(n->kind == AstNode_Ident);
+	o->mode = Addressing_Invalid;
+	o->expr = n;
+	Entity *e = scope_lookup_entity(c->context.scope, n->Ident.string);
+	if (e == NULL) {
+		if (str_eq(n->Ident.string, str_lit("_"))) {
+			error(n->Ident, "`_` cannot be used as a value type");
+		} else {
+			error(n->Ident, "Undeclared name: %.*s", LIT(n->Ident.string));
+		}
+		o->type = t_invalid;
+		o->mode = Addressing_Invalid;
+		if (named_type != NULL) {
+			set_base_type(named_type, t_invalid);
+		}
+		return;
+	}
+	add_entity_use(c, n, e);
+
+	// CycleChecker local_cycle_checker = {0};
+	// if (cycle_checker == NULL) {
+	// 	cycle_checker = &local_cycle_checker;
+	// }
+	// defer (cycle_checker_destroy(&local_cycle_checker));
+
+	check_entity_decl(c, e, NULL, named_type, cycle_checker);
+
+	if (e->type == NULL) {
+		compiler_error("Compiler error: How did this happen? type: %s; identifier: %.*s\n", type_to_string(e->type), LIT(n->Ident.string));
+		return;
+	}
+
+	Type *type = e->type;
+
+	switch (e->kind) {
+	case Entity_Constant:
+		if (type == t_invalid) {
+			o->type = t_invalid;
+			return;
+		}
+		o->value = e->Constant.value;
+		GB_ASSERT(o->value.kind != ExactValue_Invalid);
+		o->mode = Addressing_Constant;
+		break;
+
+	case Entity_Variable:
+		e->flags |= EntityFlag_Used;
+		if (type == t_invalid) {
+			o->type = t_invalid;
+			return;
+		}
+	#if 0
+		if (e->Variable.param) {
+			o->mode = Addressing_Value;
+		} else {
+			o->mode = Addressing_Variable;
+		}
+	#else
+		o->mode = Addressing_Variable;
+	#endif
+		break;
+
+	case Entity_TypeName: {
+		o->mode = Addressing_Type;
+#if 0
+	// TODO(bill): Fix cyclical dependancy checker
+		if (cycle_checker != NULL) {
+			for_array(i, cycle_checker->path) {
+				Entity *prev = cycle_checker->path[i];
+				if (prev == e) {
+					error(e->token, "Illegal declaration cycle for %.*s", LIT(e->token.string));
+					for (isize j = i; j < gb_array_count(cycle_checker->path); j++) {
+						Entity *ref = cycle_checker->path[j];
+						error(ref->token, "\t%.*s refers to", LIT(ref->token.string));
+					}
+					error(e->token, "\t%.*s", LIT(e->token.string));
+					type = t_invalid;
+					break;
+				}
+			}
+		}
+#endif
+	} break;
+
+	case Entity_Procedure:
+		o->mode = Addressing_Value;
+		break;
+
+	case Entity_Builtin:
+		o->builtin_id = e->Builtin.id;
+		o->mode = Addressing_Builtin;
+		break;
+
+	case Entity_ImportName:
+		error(ast_node_token(n), "Use of import `%.*s` not in selector", LIT(e->ImportName.name));
+		return;
+
+	case Entity_Nil:
+		o->mode = Addressing_Value;
+		break;
+
+	case Entity_ImplicitValue:
+		o->mode = Addressing_Value;
+		break;
+
+	default:
+		compiler_error("Compiler error: Unknown EntityKind");
+		break;
+	}
+
+	o->type = type;
+}
+
+i64 check_array_count(Checker *c, AstNode *e) {
+	if (e == NULL) {
+		return 0;
+	}
+	Operand o = {0};
+	check_expr(c, &o, e);
+	if (o.mode != Addressing_Constant) {
+		if (o.mode != Addressing_Invalid) {
+			error(ast_node_token(e), "Array count must be a constant");
+		}
+		return 0;
+	}
+	if (is_type_untyped(o.type) || is_type_integer(o.type)) {
+		if (o.value.kind == ExactValue_Integer) {
+			i64 count = o.value.value_integer;
+			if (count >= 0) {
+				return count;
+			}
+			error(ast_node_token(e), "Invalid array count");
+			return 0;
+		}
+	}
+
+	error(ast_node_token(e), "Array count must be an integer");
+	return 0;
+}
+
+Type *check_type_extra(Checker *c, AstNode *e, Type *named_type, CycleChecker *cycle_checker) {
+	ExactValue null_value = {ExactValue_Invalid};
+	Type *type = NULL;
+	gbString err_str = NULL;
+
+	switch (e->kind) {
+	case_ast_node(i, Ident, e);
+		Operand o = {0};
+		check_identifier(c, &o, e, named_type, cycle_checker);
+
+		switch (o.mode) {
+		case Addressing_Invalid:
+			break;
+		case Addressing_Type: {
+			type = o.type;
+			goto end;
+		} break;
+		case Addressing_NoValue:
+			err_str = expr_to_string(e);
+			error(ast_node_token(e), "`%s` used as a type", err_str);
+			break;
+		default:
+			err_str = expr_to_string(e);
+			error(ast_node_token(e), "`%s` used as a type when not a type", err_str);
+			break;
+		}
+	case_end;
+
+	case_ast_node(se, SelectorExpr, e);
+		Operand o = {0};
+		check_selector(c, &o, e);
+
+		switch (o.mode) {
+		case Addressing_Invalid:
+			break;
+		case Addressing_Type:
+			GB_ASSERT(o.type != NULL);
+			type = o.type;
+			goto end;
+		case Addressing_NoValue:
+			err_str = expr_to_string(e);
+			error(ast_node_token(e), "`%s` used as a type", err_str);
+			break;
+		default:
+			err_str = expr_to_string(e);
+			error(ast_node_token(e), "`%s` is not a type", err_str);
+			break;
+		}
+	case_end;
+
+	case_ast_node(pe, ParenExpr, e);
+		type = check_type_extra(c, pe->expr, named_type, cycle_checker);
+		goto end;
+	case_end;
+
+	case_ast_node(ue, UnaryExpr, e);
+		if (ue->op.kind == Token_Pointer) {
+			type = make_type_pointer(c->allocator, check_type(c, ue->expr));
+			goto end;
+		} else if (ue->op.kind == Token_Maybe) {
+			type = make_type_maybe(c->allocator, check_type(c, ue->expr));
+			goto end;
+		}
+	case_end;
+
+	case_ast_node(pt, PointerType, e);
+		Type *elem = check_type(c, pt->type);
+		type = make_type_pointer(c->allocator, elem);
+		goto end;
+	case_end;
+
+	case_ast_node(mt, MaybeType, e);
+		Type *elem = check_type(c, mt->type);
+		type = make_type_maybe(c->allocator, elem);
+		goto end;
+	case_end;
+
+	case_ast_node(at, ArrayType, e);
+		if (at->count != NULL) {
+			Type *elem = check_type_extra(c, at->elem, NULL, cycle_checker);
+			type = make_type_array(c->allocator, elem, check_array_count(c, at->count));
+		} else {
+			Type *elem = check_type(c, at->elem);
+			type = make_type_slice(c->allocator, elem);
+		}
+		goto end;
+	case_end;
+
+
+	case_ast_node(vt, VectorType, e);
+		Type *elem = check_type(c, vt->elem);
+		Type *be = base_type(elem);
+		i64 count = check_array_count(c, vt->count);
+		if (!is_type_boolean(be) && !is_type_numeric(be)) {
+			err_str = type_to_string(elem);
+			error(ast_node_token(vt->elem), "Vector element type must be numerical or a boolean. Got `%s`", err_str);
+		}
+		type = make_type_vector(c->allocator, elem, count);
+		goto end;
+	case_end;
+
+	case_ast_node(st, StructType, e);
+		type = make_type_struct(c->allocator);
+		set_base_type(named_type, type);
+		check_open_scope(c, e);
+		check_struct_type(c, type, e, cycle_checker);
+		check_close_scope(c);
+		type->Record.node = e;
+		goto end;
+	case_end;
+
+	case_ast_node(ut, UnionType, e);
+		type = make_type_union(c->allocator);
+		set_base_type(named_type, type);
+		check_open_scope(c, e);
+		check_union_type(c, type, e, cycle_checker);
+		check_close_scope(c);
+		type->Record.node = e;
+		goto end;
+	case_end;
+
+	case_ast_node(rut, RawUnionType, e);
+		type = make_type_raw_union(c->allocator);
+		set_base_type(named_type, type);
+		check_open_scope(c, e);
+		check_raw_union_type(c, type, e, cycle_checker);
+		check_close_scope(c);
+		type->Record.node = e;
+		goto end;
+	case_end;
+
+	case_ast_node(et, EnumType, e);
+		type = make_type_enum(c->allocator);
+		set_base_type(named_type, type);
+		check_open_scope(c, e);
+		check_enum_type(c, type, named_type, e);
+		check_close_scope(c);
+		type->Record.node = e;
+		goto end;
+	case_end;
+
+	case_ast_node(pt, ProcType, e);
+		type = alloc_type(c->allocator, Type_Proc);
+		set_base_type(named_type, type);
+		check_open_scope(c, e);
+		check_procedure_type(c, type, e);
+		check_close_scope(c);
+		goto end;
+	case_end;
+
+	case_ast_node(ce, CallExpr, e);
+		Operand o = {0};
+		check_expr_or_type(c, &o, e);
+		if (o.mode == Addressing_Type) {
+			type = o.type;
+			goto end;
+		}
+	case_end;
+	}
+	err_str = expr_to_string(e);
+	error(ast_node_token(e), "`%s` is not a type", err_str);
+
+	type = t_invalid;
+end:
+	gb_string_free(err_str);
+
+	if (type == NULL) {
+		type = t_invalid;
+	}
+
+	set_base_type(named_type, type);
+	GB_ASSERT(is_type_typed(type));
+
+	add_type_and_value(&c->info, e, Addressing_Type, type, null_value);
+
+
+	return type;
+}
+
+
+bool check_unary_op(Checker *c, Operand *o, Token op) {
+	// TODO(bill): Handle errors correctly
+	Type *type = base_type(base_vector_type(o->type));
+	gbString str = NULL;
+	switch (op.kind) {
+	case Token_Add:
+	case Token_Sub:
+		if (!is_type_numeric(type)) {
+			str = expr_to_string(o->expr);
+			error(op, "Operator `%.*s` is not allowed with `%s`", LIT(op.string), str);
+			gb_string_free(str);
+		}
+		break;
+
+	case Token_Xor:
+		if (!is_type_integer(type)) {
+			error(op, "Operator `%.*s` is only allowed with integers", LIT(op.string));
+		}
+		break;
+
+	case Token_Not:
+		if (!is_type_boolean(type)) {
+			str = expr_to_string(o->expr);
+			error(op, "Operator `%.*s` is only allowed on boolean expression", LIT(op.string));
+			gb_string_free(str);
+		}
+		break;
+
+	default:
+		error(op, "Unknown operator `%.*s`", LIT(op.string));
+		return false;
+	}
+
+	return true;
+}
+
+bool check_binary_op(Checker *c, Operand *o, Token op) {
+	// TODO(bill): Handle errors correctly
+	Type *type = base_type(base_vector_type(o->type));
+	switch (op.kind) {
+	case Token_Sub:
+	case Token_SubEq:
+		if (!is_type_numeric(type) && !is_type_pointer(type)) {
+			error(op, "Operator `%.*s` is only allowed with numeric or pointer expressions", LIT(op.string));
+			return false;
+		}
+		if (is_type_pointer(type)) {
+			o->type = t_int;
+		}
+		if (base_type(type) == t_rawptr) {
+			gbString str = type_to_string(type);
+			error(ast_node_token(o->expr), "Invalid pointer type for pointer arithmetic: `%s`", str);
+			gb_string_free(str);
+			return false;
+		}
+		break;
+
+	case Token_Add:
+	case Token_Mul:
+	case Token_Quo:
+	case Token_AddEq:
+	case Token_MulEq:
+	case Token_QuoEq:
+		if (!is_type_numeric(type)) {
+			error(op, "Operator `%.*s` is only allowed with numeric expressions", LIT(op.string));
+			return false;
+		}
+		break;
+
+	case Token_And:
+	case Token_Or:
+	case Token_AndEq:
+	case Token_OrEq:
+		if (!is_type_integer(type) && !is_type_boolean(type)) {
+			error(op, "Operator `%.*s` is only allowed with integers or booleans", LIT(op.string));
+			return false;
+		}
+		break;
+
+	case Token_Mod:
+	case Token_Xor:
+	case Token_AndNot:
+	case Token_ModEq:
+	case Token_XorEq:
+	case Token_AndNotEq:
+		if (!is_type_integer(type)) {
+			error(op, "Operator `%.*s` is only allowed with integers", LIT(op.string));
+			return false;
+		}
+		break;
+
+	case Token_CmpAnd:
+	case Token_CmpOr:
+
+	case Token_CmpAndEq:
+	case Token_CmpOrEq:
+		if (!is_type_boolean(type)) {
+			error(op, "Operator `%.*s` is only allowed with boolean expressions", LIT(op.string));
+			return false;
+		}
+		break;
+
+	default:
+		error(op, "Unknown operator `%.*s`", LIT(op.string));
+		return false;
+	}
+
+	return true;
+
+}
+bool check_value_is_expressible(Checker *c, ExactValue in_value, Type *type, ExactValue *out_value) {
+	if (in_value.kind == ExactValue_Invalid) {
+		// NOTE(bill): There's already been an error
+		return true;
+	}
+
+	if (is_type_boolean(type)) {
+		return in_value.kind == ExactValue_Bool;
+	} else if (is_type_string(type)) {
+		return in_value.kind == ExactValue_String;
+	} else if (is_type_integer(type)) {
+		ExactValue v = exact_value_to_integer(in_value);
+		if (v.kind != ExactValue_Integer) {
+			return false;
+		}
+		if (out_value) *out_value = v;
+		i64 i = v.value_integer;
+		u64 u = *cast(u64 *)&i;
+		i64 s = 8*type_size_of(c->sizes, c->allocator, type);
+		u64 umax = ~0ull;
+		if (s < 64) {
+			umax = (1ull << s) - 1ull;
+		} else {
+			// TODO(bill): I NEED A PROPER BIG NUMBER LIBRARY THAT CAN SUPPORT 128 bit integers and floats
+			s = 64;
+		}
+		i64 imax = (1ll << (s-1ll));
+
+
+		switch (type->Basic.kind) {
+		case Basic_i8:
+		case Basic_i16:
+		case Basic_i32:
+		case Basic_i64:
+		case Basic_i128:
+		case Basic_int:
+			return gb_is_between(i, -imax, imax-1);
+
+		case Basic_u8:
+		case Basic_u16:
+		case Basic_u32:
+		case Basic_u64:
+		case Basic_u128:
+		case Basic_uint:
+			return !(u < 0 || u > umax);
+
+		case Basic_UntypedInteger:
+			return true;
+
+		default: GB_PANIC("Compiler error: Unknown integer type!"); break;
+		}
+	} else if (is_type_float(type)) {
+		ExactValue v = exact_value_to_float(in_value);
+		if (v.kind != ExactValue_Float) {
+			return false;
+		}
+
+		switch (type->Basic.kind) {
+		// case Basic_f16:
+		case Basic_f32:
+		case Basic_f64:
+		// case Basic_f128:
+			if (out_value) *out_value = v;
+			return true;
+
+		case Basic_UntypedFloat:
+			return true;
+		}
+	} else if (is_type_pointer(type)) {
+		if (in_value.kind == ExactValue_Pointer) {
+			return true;
+		}
+		if (in_value.kind == ExactValue_Integer) {
+			return true;
+		}
+		if (out_value) *out_value = in_value;
+	}
+
+
+	return false;
+}
+
+void check_is_expressible(Checker *c, Operand *o, Type *type) {
+	GB_ASSERT(type->kind == Type_Basic);
+	GB_ASSERT(o->mode == Addressing_Constant);
+	if (!check_value_is_expressible(c, o->value, type, &o->value)) {
+		gbString a = expr_to_string(o->expr);
+		gbString b = type_to_string(type);
+		if (is_type_numeric(o->type) && is_type_numeric(type)) {
+			if (!is_type_integer(o->type) && is_type_integer(type)) {
+				error(ast_node_token(o->expr), "`%s` truncated to `%s`", a, b);
+			} else {
+				error(ast_node_token(o->expr), "`%s = %lld` overflows `%s`", a, o->value.value_integer, b);
+			}
+		} else {
+			error(ast_node_token(o->expr), "Cannot convert `%s`  to `%s`", a, b);
+		}
+
+		gb_string_free(b);
+		gb_string_free(a);
+		o->mode = Addressing_Invalid;
+	}
+}
+
+bool check_is_expr_vector_index(Checker *c, AstNode *expr) {
+	// HACK(bill): Handle this correctly. Maybe with a custom AddressingMode
+	expr = unparen_expr(expr);
+	if (expr->kind == AstNode_IndexExpr) {
+		ast_node(ie, IndexExpr, expr);
+		Type *t = type_deref(type_of_expr(&c->info, ie->expr));
+		if (t != NULL) {
+			return is_type_vector(t);
+		}
+	}
+	return false;
+}
+
+bool check_is_vector_elem(Checker *c, AstNode *expr) {
+	// HACK(bill): Handle this correctly. Maybe with a custom AddressingMode
+	expr = unparen_expr(expr);
+	if (expr->kind == AstNode_SelectorExpr) {
+		ast_node(se, SelectorExpr, expr);
+		Type *t = type_deref(type_of_expr(&c->info, se->expr));
+		if (t != NULL && is_type_vector(t)) {
+			return true;
+		}
+	}
+	return false;
+}
+
+void check_unary_expr(Checker *c, Operand *o, Token op, AstNode *node) {
+	switch (op.kind) {
+	case Token_Pointer: { // Pointer address
+		if (o->mode != Addressing_Variable ||
+		    check_is_expr_vector_index(c, o->expr) ||
+		    check_is_vector_elem(c, o->expr)) {
+			ast_node(ue, UnaryExpr, node);
+			gbString str = expr_to_string(ue->expr);
+			error(op, "Cannot take the pointer address of `%s`", str);
+			gb_string_free(str);
+			o->mode = Addressing_Invalid;
+			return;
+		}
+		o->mode = Addressing_Value;
+		o->type = make_type_pointer(c->allocator, o->type);
+		return;
+	}
+
+	case Token_Maybe: { // Make maybe
+		Type *t = default_type(o->type);
+		bool is_value =
+			o->mode == Addressing_Variable ||
+			o->mode == Addressing_Value ||
+			o->mode == Addressing_Constant;
+
+		if (!is_value || is_type_untyped(t)) {
+			ast_node(ue, UnaryExpr, node);
+			gbString str = expr_to_string(ue->expr);
+			error(op, "Cannot convert `%s` to a maybe", str);
+			gb_string_free(str);
+			o->mode = Addressing_Invalid;
+			return;
+		}
+		o->mode = Addressing_Value;
+		o->type = make_type_maybe(c->allocator, t);
+		return;
+	}
+	}
+
+	if (!check_unary_op(c, o, op)) {
+		o->mode = Addressing_Invalid;
+		return;
+	}
+
+	if (o->mode == Addressing_Constant) {
+		Type *type = base_type(o->type);
+		if (type->kind != Type_Basic) {
+			gbString xt = type_to_string(o->type);
+			gbString err_str = expr_to_string(node);
+			error(op, "Invalid type, `%s`, for constant unary expression `%s`", xt, err_str);
+			gb_string_free(err_str);
+			gb_string_free(xt);
+			o->mode = Addressing_Invalid;
+			return;
+		}
+
+
+		i32 precision = 0;
+		if (is_type_unsigned(type)) {
+			precision = cast(i32)(8 * type_size_of(c->sizes, c->allocator, type));
+		}
+		o->value = exact_unary_operator_value(op, o->value, precision);
+
+		if (is_type_typed(type)) {
+			if (node != NULL) {
+				o->expr = node;
+			}
+			check_is_expressible(c, o, type);
+		}
+		return;
+	}
+
+	o->mode = Addressing_Value;
+}
+
+void check_comparison(Checker *c, Operand *x, Operand *y, Token op) {
+	gbTempArenaMemory tmp = gb_temp_arena_memory_begin(&c->tmp_arena);
+
+	gbString err_str = NULL;
+
+	if (check_is_assignable_to(c, x, y->type) ||
+	    check_is_assignable_to(c, y, x->type)) {
+		Type *err_type = x->type;
+		bool defined = false;
+		switch (op.kind) {
+		case Token_CmpEq:
+		case Token_NotEq:
+			defined = is_type_comparable(x->type);
+			break;
+		case Token_Lt:
+		case Token_Gt:
+		case Token_LtEq:
+		case Token_GtEq: {
+			defined = is_type_ordered(x->type);
+		} break;
+		}
+
+		// CLEANUP(bill) NOTE(bill): there is an auto assignment to `any` which needs to be checked
+		if (is_type_any(x->type) && !is_type_any(y->type)) {
+			err_type = x->type;
+			defined = false;
+		} else if (is_type_any(y->type) && !is_type_any(x->type)) {
+			err_type = y->type;
+			defined = false;
+		}
+
+		if (!defined) {
+			gbString type_string = type_to_string(err_type);
+			err_str = gb_string_make(c->tmp_allocator,
+			                         gb_bprintf("operator `%.*s` not defined for type `%s`", LIT(op.string), type_string));
+			gb_string_free(type_string);
+		}
+	} else {
+		gbString xt = type_to_string(x->type);
+		gbString yt = type_to_string(y->type);
+		err_str = gb_string_make(c->tmp_allocator,
+		                         gb_bprintf("mismatched types `%s` and `%s`", xt, yt));
+		gb_string_free(yt);
+		gb_string_free(xt);
+	}
+
+	if (err_str != NULL) {
+		error(ast_node_token(x->expr), "Cannot compare expression, %s", err_str);
+		x->type = t_untyped_bool;
+	} else {
+		if (x->mode == Addressing_Constant &&
+		    y->mode == Addressing_Constant) {
+			x->value = make_exact_value_bool(compare_exact_values(op, x->value, y->value));
+		} else {
+			x->mode = Addressing_Value;
+
+			update_expr_type(c, x->expr, default_type(x->type), true);
+			update_expr_type(c, y->expr, default_type(y->type), true);
+		}
+
+		if (is_type_vector(base_type(y->type))) {
+			x->type = make_type_vector(c->allocator, t_bool, base_type(y->type)->Vector.count);
+		} else {
+			x->type = t_untyped_bool;
+		}
+	}
+
+	if (err_str != NULL) {
+		gb_string_free(err_str);
+	};
+
+	gb_temp_arena_memory_end(tmp);
+}
+
+void check_shift(Checker *c, Operand *x, Operand *y, AstNode *node) {
+	GB_ASSERT(node->kind == AstNode_BinaryExpr);
+	ast_node(be, BinaryExpr, node);
+
+	ExactValue x_val = {0};
+	if (x->mode == Addressing_Constant) {
+		x_val = exact_value_to_integer(x->value);
+	}
+
+	bool x_is_untyped = is_type_untyped(x->type);
+	if (!(is_type_integer(x->type) || (x_is_untyped && x_val.kind == ExactValue_Integer))) {
+		gbString err_str = expr_to_string(x->expr);
+		error(ast_node_token(node),
+		      "Shifted operand `%s` must be an integer", err_str);
+		gb_string_free(err_str);
+		x->mode = Addressing_Invalid;
+		return;
+	}
+
+	if (is_type_unsigned(y->type)) {
+
+	} else if (is_type_untyped(y->type)) {
+		convert_to_typed(c, y, t_untyped_integer, 0);
+		if (y->mode == Addressing_Invalid) {
+			x->mode = Addressing_Invalid;
+			return;
+		}
+	} else {
+		gbString err_str = expr_to_string(y->expr);
+		error(ast_node_token(node),
+		      "Shift amount `%s` must be an unsigned integer", err_str);
+		gb_string_free(err_str);
+		x->mode = Addressing_Invalid;
+		return;
+	}
+
+
+	if (x->mode == Addressing_Constant) {
+		if (y->mode == Addressing_Constant) {
+			ExactValue y_val = exact_value_to_integer(y->value);
+			if (y_val.kind != ExactValue_Integer) {
+				gbString err_str = expr_to_string(y->expr);
+				error(ast_node_token(node),
+				      "Shift amount `%s` must be an unsigned integer", err_str);
+				gb_string_free(err_str);
+				x->mode = Addressing_Invalid;
+				return;
+			}
+
+			u64 amount = cast(u64)y_val.value_integer;
+			if (amount > 1074) {
+				gbString err_str = expr_to_string(y->expr);
+				error(ast_node_token(node),
+				      "Shift amount too large: `%s`", err_str);
+				gb_string_free(err_str);
+				x->mode = Addressing_Invalid;
+				return;
+			}
+
+			if (!is_type_integer(x->type)) {
+				// NOTE(bill): It could be an untyped float but still representable
+				// as an integer
+				x->type = t_untyped_integer;
+			}
+
+			x->value = exact_value_shift(be->op, x_val, make_exact_value_integer(amount));
+
+			if (is_type_typed(x->type)) {
+				check_is_expressible(c, x, base_type(x->type));
+			}
+			return;
+		}
+
+		if (x_is_untyped) {
+			ExprInfo *info = map_expr_info_get(&c->info.untyped, hash_pointer(x->expr));
+			if (info != NULL) {
+				info->is_lhs = true;
+			}
+			x->mode = Addressing_Value;
+			return;
+		}
+	}
+
+	if (y->mode == Addressing_Constant && y->value.value_integer < 0) {
+		gbString err_str = expr_to_string(y->expr);
+		error(ast_node_token(node),
+		      "Shift amount cannot be negative: `%s`", err_str);
+		gb_string_free(err_str);
+	}
+
+	x->mode = Addressing_Value;
+}
+
+bool check_is_castable_to(Checker *c, Operand *operand, Type *y) {
+	if (check_is_assignable_to(c, operand, y)) {
+		return true;
+	}
+
+	Type *x = operand->type;
+	Type *xb = base_type(x);
+	Type *yb = base_type(y);
+	if (are_types_identical(xb, yb)) {
+		return true;
+	}
+	xb = get_enum_base_type(x);
+	yb = get_enum_base_type(y);
+
+
+	// Cast between booleans and integers
+	if (is_type_boolean(xb) || is_type_integer(xb)) {
+		if (is_type_boolean(yb) || is_type_integer(yb)) {
+			return true;
+		}
+	}
+
+	// Cast between numbers
+	if (is_type_integer(xb) || is_type_float(xb)) {
+		if (is_type_integer(yb) || is_type_float(yb)) {
+			return true;
+		}
+	}
+
+	// Cast between pointers
+	if (is_type_pointer(xb) && is_type_pointer(yb)) {
+		return true;
+	}
+
+	// (u)int <-> pointer
+	if (is_type_int_or_uint(xb) && is_type_rawptr(yb)) {
+		return true;
+	}
+	if (is_type_rawptr(xb) && is_type_int_or_uint(yb)) {
+		return true;
+	}
+
+	// []byte/[]u8 <-> string
+	if (is_type_u8_slice(xb) && is_type_string(yb)) {
+		return true;
+	}
+	if (is_type_string(xb) && is_type_u8_slice(yb)) {
+		if (is_type_typed(xb)) {
+			return true;
+		}
+	}
+
+	// proc <-> proc
+	if (is_type_proc(xb) && is_type_proc(yb)) {
+		return true;
+	}
+
+	// proc -> rawptr
+	if (is_type_proc(xb) && is_type_rawptr(yb)) {
+		return true;
+	}
+
+	return false;
+}
+
+String check_down_cast_name(Type *dst_, Type *src_) {
+	String result = {0};
+	Type *dst = type_deref(dst_);
+	Type *src = type_deref(src_);
+	Type *dst_s = base_type(dst);
+	GB_ASSERT(is_type_struct(dst_s) || is_type_raw_union(dst_s));
+	for (isize i = 0; i < dst_s->Record.field_count; i++) {
+		Entity *f = dst_s->Record.fields[i];
+		GB_ASSERT(f->kind == Entity_Variable && f->flags & EntityFlag_Field);
+		if (f->flags & EntityFlag_Anonymous) {
+			if (are_types_identical(f->type, src_)) {
+				return f->token.string;
+			}
+			if (are_types_identical(type_deref(f->type), src_)) {
+				return f->token.string;
+			}
+
+			if (!is_type_pointer(f->type)) {
+				result = check_down_cast_name(f->type, src_);
+				if (result.len > 0) {
+					return result;
+				}
+			}
+		}
+	}
+
+	return result;
+}
+
+Operand check_ptr_addition(Checker *c, TokenKind op, Operand *ptr, Operand *offset, AstNode *node) {
+	GB_ASSERT(node->kind == AstNode_BinaryExpr);
+	ast_node(be, BinaryExpr, node);
+	GB_ASSERT(is_type_pointer(ptr->type));
+	GB_ASSERT(is_type_integer(offset->type));
+	GB_ASSERT(op == Token_Add || op == Token_Sub);
+
+	Operand operand = {0};
+	operand.mode = Addressing_Value;
+	operand.type = ptr->type;
+	operand.expr = node;
+
+	if (base_type(ptr->type) == t_rawptr) {
+		gbString str = type_to_string(ptr->type);
+		error(ast_node_token(node), "Invalid pointer type for pointer arithmetic: `%s`", str);
+		gb_string_free(str);
+		operand.mode = Addressing_Invalid;
+		return operand;
+	}
+
+
+	if (ptr->mode == Addressing_Constant && offset->mode == Addressing_Constant) {
+		i64 elem_size = type_size_of(c->sizes, c->allocator, ptr->type);
+		i64 ptr_val = ptr->value.value_pointer;
+		i64 offset_val = exact_value_to_integer(offset->value).value_integer;
+		i64 new_ptr_val = ptr_val;
+		if (op == Token_Add) {
+			new_ptr_val += elem_size*offset_val;
+		} else {
+			new_ptr_val -= elem_size*offset_val;
+		}
+		operand.mode = Addressing_Constant;
+		operand.value = make_exact_value_pointer(new_ptr_val);
+	}
+
+	return operand;
+}
+
+void check_binary_expr(Checker *c, Operand *x, AstNode *node) {
+	GB_ASSERT(node->kind == AstNode_BinaryExpr);
+	Operand y_ = {0}, *y = &y_;
+
+	ast_node(be, BinaryExpr, node);
+
+	if (be->op.kind == Token_as) {
+		check_expr(c, x, be->left);
+		Type *type = check_type(c, be->right);
+		if (x->mode == Addressing_Invalid) {
+			return;
+		}
+
+		bool is_const_expr = x->mode == Addressing_Constant;
+		bool can_convert = false;
+
+		Type *bt = base_type(type);
+		if (is_const_expr && is_type_constant_type(bt)) {
+			if (bt->kind == Type_Basic) {
+				if (check_value_is_expressible(c, x->value, bt, &x->value)) {
+					can_convert = true;
+				}
+			}
+		} else if (check_is_castable_to(c, x, type)) {
+			if (x->mode != Addressing_Constant) {
+				x->mode = Addressing_Value;
+			}
+			can_convert = true;
+		}
+
+		if (!can_convert) {
+			gbString expr_str = expr_to_string(x->expr);
+			gbString to_type  = type_to_string(type);
+			gbString from_type = type_to_string(x->type);
+			error(ast_node_token(x->expr), "Cannot cast `%s` as `%s` from `%s`", expr_str, to_type, from_type);
+			gb_string_free(from_type);
+			gb_string_free(to_type);
+			gb_string_free(expr_str);
+
+			x->mode = Addressing_Invalid;
+			return;
+		}
+
+		if (is_type_untyped(x->type)) {
+			Type *final_type = type;
+			if (is_const_expr && !is_type_constant_type(type)) {
+				final_type = default_type(x->type);
+			}
+			update_expr_type(c, x->expr, final_type, true);
+		}
+
+		x->type = type;
+		return;
+	} else if (be->op.kind == Token_transmute) {
+		check_expr(c, x, be->left);
+		Type *type = check_type(c, be->right);
+		if (x->mode == Addressing_Invalid) {
+			return;
+		}
+
+		if (x->mode == Addressing_Constant) {
+			gbString expr_str = expr_to_string(x->expr);
+			error(ast_node_token(x->expr), "Cannot transmute constant expression: `%s`", expr_str);
+			gb_string_free(expr_str);
+			x->mode = Addressing_Invalid;
+			return;
+		}
+
+		if (is_type_untyped(x->type)) {
+			gbString expr_str = expr_to_string(x->expr);
+			error(ast_node_token(x->expr), "Cannot transmute untyped expression: `%s`", expr_str);
+			gb_string_free(expr_str);
+			x->mode = Addressing_Invalid;
+			return;
+		}
+
+		i64 srcz = type_size_of(c->sizes, c->allocator, x->type);
+		i64 dstz = type_size_of(c->sizes, c->allocator, type);
+		if (srcz != dstz) {
+			gbString expr_str = expr_to_string(x->expr);
+			gbString type_str = type_to_string(type);
+			error(ast_node_token(x->expr), "Cannot transmute `%s` to `%s`, %lld vs %lld bytes", expr_str, type_str, srcz, dstz);
+			gb_string_free(type_str);
+			gb_string_free(expr_str);
+			x->mode = Addressing_Invalid;
+			return;
+		}
+
+		x->type = type;
+
+		return;
+	} else if (be->op.kind == Token_down_cast) {
+		check_expr(c, x, be->left);
+		Type *type = check_type(c, be->right);
+		if (x->mode == Addressing_Invalid) {
+			return;
+		}
+
+		if (x->mode == Addressing_Constant) {
+			gbString expr_str = expr_to_string(node);
+			error(ast_node_token(node), "Cannot `down_cast` a constant expression: `%s`", expr_str);
+			gb_string_free(expr_str);
+			x->mode = Addressing_Invalid;
+			return;
+		}
+
+		if (is_type_untyped(x->type)) {
+			gbString expr_str = expr_to_string(node);
+			error(ast_node_token(node), "Cannot `down_cast` an untyped expression: `%s`", expr_str);
+			gb_string_free(expr_str);
+			x->mode = Addressing_Invalid;
+			return;
+		}
+
+		if (!(is_type_pointer(x->type) && is_type_pointer(type))) {
+			gbString expr_str = expr_to_string(node);
+			error(ast_node_token(node), "Can only `down_cast` pointers: `%s`", expr_str);
+			gb_string_free(expr_str);
+			x->mode = Addressing_Invalid;
+			return;
+		}
+
+		Type *src = type_deref(x->type);
+		Type *dst = type_deref(type);
+		Type *bsrc = base_type(src);
+		Type *bdst = base_type(dst);
+
+		if (!(is_type_struct(bsrc) || is_type_raw_union(bsrc))) {
+			gbString expr_str = expr_to_string(node);
+			error(ast_node_token(node), "Can only `down_cast` pointer from structs or unions: `%s`", expr_str);
+			gb_string_free(expr_str);
+			x->mode = Addressing_Invalid;
+			return;
+		}
+
+		if (!(is_type_struct(bdst) || is_type_raw_union(bdst))) {
+			gbString expr_str = expr_to_string(node);
+			error(ast_node_token(node), "Can only `down_cast` pointer to structs or unions: `%s`", expr_str);
+			gb_string_free(expr_str);
+			x->mode = Addressing_Invalid;
+			return;
+		}
+
+		String param_name = check_down_cast_name(dst, src);
+		if (param_name.len == 0) {
+			gbString expr_str = expr_to_string(node);
+			error(ast_node_token(node), "Illegal `down_cast`: `%s`", expr_str);
+			gb_string_free(expr_str);
+			x->mode = Addressing_Invalid;
+			return;
+		}
+
+		x->mode = Addressing_Value;
+		x->type = type;
+		return;
+	} else if (be->op.kind == Token_union_cast) {
+		check_expr(c, x, be->left);
+		Type *type = check_type(c, be->right);
+		if (x->mode == Addressing_Invalid) {
+			return;
+		}
+
+		if (x->mode == Addressing_Constant) {
+			gbString expr_str = expr_to_string(node);
+			error(ast_node_token(node), "Cannot `union_cast` a constant expression: `%s`", expr_str);
+			gb_string_free(expr_str);
+			x->mode = Addressing_Invalid;
+			return;
+		}
+
+		if (is_type_untyped(x->type)) {
+			gbString expr_str = expr_to_string(node);
+			error(ast_node_token(node), "Cannot `union_cast` an untyped expression: `%s`", expr_str);
+			gb_string_free(expr_str);
+			x->mode = Addressing_Invalid;
+			return;
+		}
+
+		bool src_is_ptr = is_type_pointer(x->type);
+		bool dst_is_ptr = is_type_pointer(type);
+		Type *src = type_deref(x->type);
+		Type *dst = type_deref(type);
+		Type *bsrc = base_type(src);
+		Type *bdst = base_type(dst);
+
+		if (src_is_ptr != dst_is_ptr) {
+			gbString src_type_str = type_to_string(x->type);
+			gbString dst_type_str = type_to_string(type);
+			error(ast_node_token(node), "Invalid `union_cast` types: `%s` and `%s`", src_type_str, dst_type_str);
+			gb_string_free(dst_type_str);
+			gb_string_free(src_type_str);
+			x->mode = Addressing_Invalid;
+			return;
+		}
+
+		if (!is_type_union(src)) {
+			error(ast_node_token(node), "`union_cast` can only operate on unions");
+			x->mode = Addressing_Invalid;
+			return;
+		}
+
+		bool ok = false;
+		for (isize i = 1; i < bsrc->Record.field_count; i++) {
+			Entity *f = bsrc->Record.fields[i];
+			if (are_types_identical(f->type, dst)) {
+				ok = true;
+				break;
+			}
+		}
+
+		if (!ok) {
+			gbString expr_str = expr_to_string(node);
+			gbString dst_type_str = type_to_string(type);
+			error(ast_node_token(node), "Cannot `union_cast` `%s` to `%s`", expr_str, dst_type_str);
+			gb_string_free(dst_type_str);
+			gb_string_free(expr_str);
+			x->mode = Addressing_Invalid;
+			return;
+		}
+
+		Entity **variables = gb_alloc_array(c->allocator, Entity *, 2);
+		Token tok = make_token_ident(str_lit(""));
+		variables[0] = make_entity_param(c->allocator, NULL, tok, type, false);
+		variables[1] = make_entity_param(c->allocator, NULL, tok, t_bool, false);
+
+		Type *tuple = make_type_tuple(c->allocator);
+		tuple->Tuple.variables = variables;
+		tuple->Tuple.variable_count = 2;
+
+		x->type = tuple;
+		x->mode = Addressing_Value;
+		return;
+	}
+
+	check_expr(c, x, be->left);
+	check_expr(c, y, be->right);
+	if (x->mode == Addressing_Invalid) {
+		return;
+	}
+	if (y->mode == Addressing_Invalid) {
+		x->mode = Addressing_Invalid;
+		x->expr = y->expr;
+		return;
+	}
+
+	Token op = be->op;
+
+	if (token_is_shift(op)) {
+		check_shift(c, x, y, node);
+		return;
+	}
+
+	if (op.kind == Token_Add || op.kind == Token_Sub) {
+		if (is_type_pointer(x->type) && is_type_integer(y->type)) {
+			*x = check_ptr_addition(c, op.kind, x, y, node);
+			return;
+		} else if (is_type_integer(x->type) && is_type_pointer(y->type)) {
+			if (op.kind == Token_Sub) {
+				gbString lhs = expr_to_string(x->expr);
+				gbString rhs = expr_to_string(y->expr);
+				error(ast_node_token(node), "Invalid pointer arithmetic, did you mean `%s %.*s %s`?", rhs, LIT(op.string), lhs);
+				gb_string_free(rhs);
+				gb_string_free(lhs);
+				x->mode = Addressing_Invalid;
+				return;
+			}
+			*x = check_ptr_addition(c, op.kind, y, x, node);
+			return;
+		}
+	}
+
+
+	convert_to_typed(c, x, y->type, 0);
+	if (x->mode == Addressing_Invalid) {
+		return;
+	}
+	convert_to_typed(c, y, x->type, 0);
+	if (y->mode == Addressing_Invalid) {
+		x->mode = Addressing_Invalid;
+		return;
+	}
+
+	if (token_is_comparison(op)) {
+		check_comparison(c, x, y, op);
+		return;
+	}
+
+	if (!are_types_identical(x->type, y->type)) {
+		if (x->type != t_invalid &&
+		    y->type != t_invalid) {
+			gbString xt = type_to_string(x->type);
+			gbString yt = type_to_string(y->type);
+			gbString expr_str = expr_to_string(x->expr);
+			error(op, "Mismatched types in binary expression `%s` : `%s` vs `%s`", expr_str, xt, yt);
+			gb_string_free(expr_str);
+			gb_string_free(yt);
+			gb_string_free(xt);
+		}
+		x->mode = Addressing_Invalid;
+		return;
+	}
+
+	if (!check_binary_op(c, x, op)) {
+		x->mode = Addressing_Invalid;
+		return;
+	}
+
+	switch (op.kind) {
+	case Token_Quo:
+	case Token_Mod:
+	case Token_QuoEq:
+	case Token_ModEq:
+		if ((x->mode == Addressing_Constant || is_type_integer(x->type)) &&
+		    y->mode == Addressing_Constant) {
+			bool fail = false;
+			switch (y->value.kind) {
+			case ExactValue_Integer:
+				if (y->value.value_integer == 0) {
+					fail = true;
+				}
+				break;
+			case ExactValue_Float:
+				if (y->value.value_float == 0.0) {
+					fail = true;
+				}
+				break;
+			}
+
+			if (fail) {
+				error(ast_node_token(y->expr), "Division by zero not allowed");
+				x->mode = Addressing_Invalid;
+				return;
+			}
+		}
+	}
+
+	if (x->mode == Addressing_Constant &&
+	    y->mode == Addressing_Constant) {
+		ExactValue a = x->value;
+		ExactValue b = y->value;
+
+		Type *type = base_type(x->type);
+		if (is_type_pointer(type)) {
+			GB_ASSERT(op.kind == Token_Sub);
+			i64 bytes = a.value_pointer - b.value_pointer;
+			i64 diff = bytes/type_size_of(c->sizes, c->allocator, type);
+			x->value = make_exact_value_pointer(diff);
+			return;
+		}
+
+		if (type->kind != Type_Basic) {
+			gbString xt = type_to_string(x->type);
+			gbString err_str = expr_to_string(node);
+			error(op, "Invalid type, `%s`, for constant binary expression `%s`", xt, err_str);
+			gb_string_free(err_str);
+			gb_string_free(xt);
+			x->mode = Addressing_Invalid;
+			return;
+		}
+
+		if (op.kind == Token_Quo && is_type_integer(type)) {
+			op.kind = Token_QuoEq; // NOTE(bill): Hack to get division of integers
+		}
+		x->value = exact_binary_operator_value(op, a, b);
+		if (is_type_typed(type)) {
+			if (node != NULL) {
+				x->expr = node;
+			}
+			check_is_expressible(c, x, type);
+		}
+		return;
+	}
+
+	x->mode = Addressing_Value;
+}
+
+
+void update_expr_type(Checker *c, AstNode *e, Type *type, bool final) {
+	HashKey key = hash_pointer(e);
+	ExprInfo *found = map_expr_info_get(&c->info.untyped, key);
+	if (found == NULL) {
+		return;
+	}
+
+	switch (e->kind) {
+	case_ast_node(ue, UnaryExpr, e);
+		if (found->value.kind != ExactValue_Invalid) {
+			break;
+		}
+		update_expr_type(c, ue->expr, type, final);
+	case_end;
+
+	case_ast_node(be, BinaryExpr, e);
+		if (found->value.kind != ExactValue_Invalid) {
+			break;
+		}
+		if (!token_is_comparison(be->op)) {
+			if (token_is_shift(be->op)) {
+				update_expr_type(c, be->left,  type, final);
+			} else {
+				update_expr_type(c, be->left,  type, final);
+				update_expr_type(c, be->right, type, final);
+			}
+		}
+	case_end;
+	}
+
+	if (!final && is_type_untyped(type)) {
+		found->type = base_type(type);
+		map_expr_info_set(&c->info.untyped, key, *found);
+	} else {
+		ExprInfo old = *found;
+		map_expr_info_remove(&c->info.untyped, key);
+
+		if (old.is_lhs && !is_type_integer(type)) {
+			gbString expr_str = expr_to_string(e);
+			gbString type_str = type_to_string(type);
+			error(ast_node_token(e), "Shifted operand %s must be an integer, got %s", expr_str, type_str);
+			gb_string_free(type_str);
+			gb_string_free(expr_str);
+			return;
+		}
+
+		add_type_and_value(&c->info, e, found->mode, type, found->value);
+	}
+}
+
+void update_expr_value(Checker *c, AstNode *e, ExactValue value) {
+	ExprInfo *found = map_expr_info_get(&c->info.untyped, hash_pointer(e));
+	if (found) {
+		found->value = value;
+	}
+}
+
+void convert_untyped_error(Checker *c, Operand *operand, Type *target_type) {
+	gbString expr_str = expr_to_string(operand->expr);
+	gbString type_str = type_to_string(target_type);
+	char *extra_text = "";
+
+	if (operand->mode == Addressing_Constant) {
+		if (operand->value.value_integer == 0) {
+			if (str_ne(make_string_c(expr_str), str_lit("nil"))) { // HACK NOTE(bill): Just in case
+				// NOTE(bill): Doesn't matter what the type is as it's still zero in the union
+				extra_text = " - Did you want `nil`?";
+			}
+		}
+	}
+	error(ast_node_token(operand->expr), "Cannot convert `%s` to `%s`%s", expr_str, type_str, extra_text);
+
+	gb_string_free(type_str);
+	gb_string_free(expr_str);
+	operand->mode = Addressing_Invalid;
+}
+
+// NOTE(bill): Set initial level to 0
+void convert_to_typed(Checker *c, Operand *operand, Type *target_type, i32 level) {
+	GB_ASSERT_NOT_NULL(target_type);
+	if (operand->mode == Addressing_Invalid ||
+	    is_type_typed(operand->type) ||
+	    target_type == t_invalid) {
+		return;
+	}
+
+	if (is_type_untyped(target_type)) {
+		Type *x = operand->type;
+		Type *y = target_type;
+		if (is_type_numeric(x) && is_type_numeric(y)) {
+			if (x < y) {
+				operand->type = target_type;
+				update_expr_type(c, operand->expr, target_type, false);
+			}
+		} else if (x != y) {
+			convert_untyped_error(c, operand, target_type);
+		}
+		return;
+	}
+
+	Type *t = get_enum_base_type(base_type(target_type));
+	switch (t->kind) {
+	case Type_Basic:
+		if (operand->mode == Addressing_Constant) {
+			check_is_expressible(c, operand, t);
+			if (operand->mode == Addressing_Invalid) {
+				return;
+			}
+			update_expr_value(c, operand->expr, operand->value);
+		} else {
+			switch (operand->type->Basic.kind) {
+			case Basic_UntypedBool:
+				if (!is_type_boolean(target_type)) {
+					convert_untyped_error(c, operand, target_type);
+					return;
+				}
+				break;
+			case Basic_UntypedInteger:
+			case Basic_UntypedFloat:
+			case Basic_UntypedRune:
+				if (!is_type_numeric(target_type)) {
+					convert_untyped_error(c, operand, target_type);
+					return;
+				}
+				break;
+
+			case Basic_UntypedNil:
+				if (!type_has_nil(target_type)) {
+					convert_untyped_error(c, operand, target_type);
+					return;
+				}
+				break;
+			}
+		}
+		break;
+
+	case Type_Maybe:
+		if (is_type_untyped_nil(operand->type)) {
+			// Okay
+		} else if (level == 0) {
+			convert_to_typed(c, operand, t->Maybe.elem, level+1);
+			return;
+		}
+
+	default:
+		if (!is_type_untyped_nil(operand->type) || !type_has_nil(target_type)) {
+			convert_untyped_error(c, operand, target_type);
+			return;
+		}
+		break;
+	}
+
+
+
+	operand->type = target_type;
+}
+
+bool check_index_value(Checker *c, AstNode *index_value, i64 max_count, i64 *value) {
+	Operand operand = {Addressing_Invalid};
+	check_expr(c, &operand, index_value);
+	if (operand.mode == Addressing_Invalid) {
+		if (value) *value = 0;
+		return false;
+	}
+
+	convert_to_typed(c, &operand, t_int, 0);
+	if (operand.mode == Addressing_Invalid) {
+		if (value) *value = 0;
+		return false;
+	}
+
+	if (!is_type_integer(get_enum_base_type(operand.type))) {
+		gbString expr_str = expr_to_string(operand.expr);
+		error(ast_node_token(operand.expr),
+		            "Index `%s` must be an integer", expr_str);
+		gb_string_free(expr_str);
+		if (value) *value = 0;
+		return false;
+	}
+
+	if (operand.mode == Addressing_Constant &&
+	    (c->context.stmt_state_flags & StmtStateFlag_bounds_check) != 0) {
+		i64 i = exact_value_to_integer(operand.value).value_integer;
+		if (i < 0) {
+			gbString expr_str = expr_to_string(operand.expr);
+			error(ast_node_token(operand.expr),
+			            "Index `%s` cannot be a negative value", expr_str);
+			gb_string_free(expr_str);
+			if (value) *value = 0;
+			return false;
+		}
+
+		if (max_count >= 0) { // NOTE(bill): Do array bound checking
+			if (value) *value = i;
+			if (i >= max_count) {
+				gbString expr_str = expr_to_string(operand.expr);
+				error(ast_node_token(operand.expr),
+				            "Index `%s` is out of bounds range 0..<%lld", expr_str, max_count);
+				gb_string_free(expr_str);
+				return false;
+			}
+
+			return true;
+		}
+	}
+
+	// NOTE(bill): It's alright :D
+	if (value) *value = -1;
+	return true;
+}
+
+Entity *check_selector(Checker *c, Operand *operand, AstNode *node) {
+	ast_node(se, SelectorExpr, node);
+
+	bool check_op_expr = true;
+	Entity *expr_entity = NULL;
+	Entity *entity = NULL;
+	Selection sel = {0}; // NOTE(bill): Not used if it's an import name
+
+	AstNode *op_expr  = se->expr;
+	AstNode *selector = unparen_expr(se->selector);
+	if (selector == NULL) {
+		goto error;
+	}
+
+	GB_ASSERT(selector->kind == AstNode_Ident);
+
+
+	if (op_expr->kind == AstNode_Ident) {
+		String name = op_expr->Ident.string;
+		Entity *e = scope_lookup_entity(c->context.scope, name);
+		add_entity_use(c, op_expr, e);
+		expr_entity = e;
+		if (e != NULL && e->kind == Entity_ImportName) {
+			String sel_name = selector->Ident.string;
+			check_op_expr = false;
+			entity = scope_lookup_entity(e->ImportName.scope, sel_name);
+			if (entity == NULL) {
+				error(ast_node_token(op_expr), "`%.*s` is not declared by `%.*s`", LIT(sel_name), LIT(name));
+				goto error;
+			}
+			if (entity->type == NULL) { // Not setup yet
+				check_entity_decl(c, entity, NULL, NULL, NULL);
+			}
+			GB_ASSERT(entity->type != NULL);
+			// bool is_not_exported = !is_entity_exported(entity);
+
+			b32 is_not_exported = true;
+
+			Entity **found = map_entity_get(&e->ImportName.scope->implicit, hash_string(sel_name));
+			if (!found) {
+				is_not_exported = false;
+			} else {
+				Entity *f = *found;
+				if (f->kind == Entity_ImportName) {
+					is_not_exported = true;
+				}
+			}
+
+			// // TODO(bill): Fix this for `#import "file.odin" as .`
+			// if (true || is_not_exported) {
+			// 	Entity **found =
+			// 	if (!found && e->ImportName.scope != entity->scope) {
+			// 		is_not_exported = false;
+			// 	}
+			// 	gb_printf("%.*s\n", LIT(entity->token.string));
+			// }
+
+			if (is_not_exported) {
+				gbString sel_str = expr_to_string(selector);
+				error(ast_node_token(op_expr), "`%s` is not exported by `%.*s`", sel_str, LIT(name));
+				gb_string_free(sel_str);
+				// NOTE(bill): Not really an error so don't goto error
+			}
+
+			add_entity_use(c, selector, entity);
+		}
+	}
+	if (check_op_expr) {
+		check_expr_base(c, operand, op_expr, NULL);
+		if (operand->mode == Addressing_Invalid) {
+			goto error;
+		}
+	}
+
+
+	if (entity == NULL) {
+		sel = lookup_field(c->allocator, operand->type, selector->Ident.string, operand->mode == Addressing_Type);
+		entity = sel.entity;
+	}
+	if (entity == NULL) {
+		gbString op_str   = expr_to_string(op_expr);
+		gbString type_str = type_to_string(operand->type);
+		gbString sel_str  = expr_to_string(selector);
+		error(ast_node_token(op_expr), "`%s` (`%s`) has no field `%s`", op_str, type_str, sel_str);
+		gb_string_free(sel_str);
+		gb_string_free(type_str);
+		gb_string_free(op_str);
+		goto error;
+	}
+
+	if (expr_entity != NULL && expr_entity->kind == Entity_Constant && entity->kind != Entity_Constant) {
+		gbString op_str   = expr_to_string(op_expr);
+		gbString type_str = type_to_string(operand->type);
+		gbString sel_str  = expr_to_string(selector);
+		error(ast_node_token(op_expr), "Cannot access non-constant field `%s` from `%s`", sel_str, op_str);
+		gb_string_free(sel_str);
+		gb_string_free(type_str);
+		gb_string_free(op_str);
+		goto error;
+	}
+
+
+	add_entity_use(c, selector, entity);
+
+	switch (entity->kind) {
+	case Entity_Constant:
+		operand->mode = Addressing_Constant;
+		operand->value = entity->Constant.value;
+		break;
+	case Entity_Variable:
+		// TODO(bill): This is the rule I need?
+		if (sel.indirect || operand->mode != Addressing_Value) {
+			operand->mode = Addressing_Variable;
+		}
+		break;
+	case Entity_TypeName:
+		operand->mode = Addressing_Type;
+		break;
+	case Entity_Procedure:
+		operand->mode = Addressing_Value;
+		break;
+	case Entity_Builtin:
+		operand->mode = Addressing_Builtin;
+		operand->builtin_id = entity->Builtin.id;
+		break;
+
+	// NOTE(bill): These cases should never be hit but are here for sanity reasons
+	case Entity_Nil:
+		operand->mode = Addressing_Value;
+		break;
+	case Entity_ImplicitValue:
+		operand->mode = Addressing_Value;
+		break;
+	}
+
+	operand->type = entity->type;
+	operand->expr = node;
+
+	return entity;
+
+error:
+	operand->mode = Addressing_Invalid;
+	operand->expr = node;
+	return NULL;
+}
+
+bool check_builtin_procedure(Checker *c, Operand *operand, AstNode *call, i32 id) {
+	GB_ASSERT(call->kind == AstNode_CallExpr);
+	ast_node(ce, CallExpr, call);
+	BuiltinProc *bp = &builtin_procs[id];
+	{
+		char *err = NULL;
+		if (ce->args.count < bp->arg_count) {
+			err = "Too few";
+		} else if (ce->args.count > bp->arg_count && !bp->variadic) {
+			err = "Too many";
+		}
+
+		if (err) {
+			ast_node(proc, Ident, ce->proc);
+			error(ce->close, "`%s` arguments for `%.*s`, expected %td, got %td",
+			      err, LIT(proc->string),
+			      bp->arg_count, ce->args.count);
+			return false;
+		}
+	}
+
+	switch (id) {
+	case BuiltinProc_new:
+	case BuiltinProc_new_slice:
+	case BuiltinProc_size_of:
+	case BuiltinProc_align_of:
+	case BuiltinProc_offset_of:
+	case BuiltinProc_type_info:
+		// NOTE(bill): The first arg may be a Type, this will be checked case by case
+		break;
+	default:
+		check_multi_expr(c, operand, ce->args.e[0]);
+	}
+
+	switch (id) {
+	case BuiltinProc_new: {
+		// new :: proc(Type) -> ^Type
+		Operand op = {0};
+		check_expr_or_type(c, &op, ce->args.e[0]);
+		Type *type = op.type;
+		if ((op.mode != Addressing_Type && type == NULL) || type == t_invalid) {
+			error(ast_node_token(ce->args.e[0]), "Expected a type for `new`");
+			return false;
+		}
+		operand->mode = Addressing_Value;
+		operand->type = make_type_pointer(c->allocator, type);
+	} break;
+	case BuiltinProc_new_slice: {
+		// new_slice :: proc(Type, len: int[, cap: int]) -> []Type
+		Operand op = {0};
+		check_expr_or_type(c, &op, ce->args.e[0]);
+		Type *type = op.type;
+		if ((op.mode != Addressing_Type && type == NULL) || type == t_invalid) {
+			error(ast_node_token(ce->args.e[0]), "Expected a type for `new_slice`");
+			return false;
+		}
+
+		AstNode *len = ce->args.e[1];
+		AstNode *cap = NULL;
+		if (ce->args.count > 2) {
+			cap = ce->args.e[2];
+		}
+
+		check_expr(c, &op, len);
+		if (op.mode == Addressing_Invalid) {
+			return false;
+		}
+		if (!is_type_integer(op.type)) {
+			gbString type_str = type_to_string(operand->type);
+			error(ast_node_token(call),
+			      "Length for `new_slice` must be an integer, got `%s`",
+			      type_str);
+			gb_string_free(type_str);
+			return false;
+		}
+
+		if (cap != NULL) {
+			check_expr(c, &op, cap);
+			if (op.mode == Addressing_Invalid) {
+				return false;
+			}
+			if (!is_type_integer(op.type)) {
+				gbString type_str = type_to_string(operand->type);
+				error(ast_node_token(call),
+				      "Capacity for `new_slice` must be an integer, got `%s`",
+				      type_str);
+				gb_string_free(type_str);
+				return false;
+			}
+			if (ce->args.count > 3) {
+				error(ast_node_token(call),
+				      "Too many arguments to `new_slice`, expected either 2 or 3");
+				return false;
+			}
+		}
+
+		operand->mode = Addressing_Value;
+		operand->type = make_type_slice(c->allocator, type);
+	} break;
+
+	case BuiltinProc_size_of: {
+		// size_of :: proc(Type) -> untyped int
+		Type *type = check_type(c, ce->args.e[0]);
+		if (type == NULL || type == t_invalid) {
+			error(ast_node_token(ce->args.e[0]), "Expected a type for `size_of`");
+			return false;
+		}
+
+		operand->mode = Addressing_Constant;
+		operand->value = make_exact_value_integer(type_size_of(c->sizes, c->allocator, type));
+		operand->type = t_untyped_integer;
+
+	} break;
+
+	case BuiltinProc_size_of_val:
+		// size_of_val :: proc(val: Type) -> untyped int
+		check_assignment(c, operand, NULL, str_lit("argument of `size_of_val`"));
+		if (operand->mode == Addressing_Invalid) {
+			return false;
+		}
+
+		operand->mode = Addressing_Constant;
+		operand->value = make_exact_value_integer(type_size_of(c->sizes, c->allocator, operand->type));
+		operand->type = t_untyped_integer;
+		break;
+
+	case BuiltinProc_align_of: {
+		// align_of :: proc(Type) -> untyped int
+		Type *type = check_type(c, ce->args.e[0]);
+		if (type == NULL || type == t_invalid) {
+			error(ast_node_token(ce->args.e[0]), "Expected a type for `align_of`");
+			return false;
+		}
+		operand->mode = Addressing_Constant;
+		operand->value = make_exact_value_integer(type_align_of(c->sizes, c->allocator, type));
+		operand->type = t_untyped_integer;
+	} break;
+
+	case BuiltinProc_align_of_val:
+		// align_of_val :: proc(val: Type) -> untyped int
+		check_assignment(c, operand, NULL, str_lit("argument of `align_of_val`"));
+		if (operand->mode == Addressing_Invalid) {
+			return false;
+		}
+
+		operand->mode = Addressing_Constant;
+		operand->value = make_exact_value_integer(type_align_of(c->sizes, c->allocator, operand->type));
+		operand->type = t_untyped_integer;
+		break;
+
+	case BuiltinProc_offset_of: {
+		// offset_of :: proc(Type, field) -> untyped int
+		Operand op = {0};
+		Type *bt = check_type(c, ce->args.e[0]);
+		Type *type = base_type(bt);
+		if (type == NULL || type == t_invalid) {
+			error(ast_node_token(ce->args.e[0]), "Expected a type for `offset_of`");
+			return false;
+		}
+
+		AstNode *field_arg = unparen_expr(ce->args.e[1]);
+		if (field_arg == NULL ||
+		    field_arg->kind != AstNode_Ident) {
+			error(ast_node_token(field_arg), "Expected an identifier for field argument");
+			return false;
+		}
+		if (is_type_array(type) || is_type_vector(type)) {
+			error(ast_node_token(field_arg), "Invalid type for `offset_of`");
+			return false;
+		}
+
+
+		ast_node(arg, Ident, field_arg);
+		Selection sel = lookup_field(c->allocator, type, arg->string, operand->mode == Addressing_Type);
+		if (sel.entity == NULL) {
+			gbString type_str = type_to_string(bt);
+			error(ast_node_token(ce->args.e[0]),
+			      "`%s` has no field named `%.*s`", type_str, LIT(arg->string));
+			gb_string_free(type_str);
+			return false;
+		}
+		if (sel.indirect) {
+			gbString type_str = type_to_string(bt);
+			error(ast_node_token(ce->args.e[0]),
+			      "Field `%.*s` is embedded via a pointer in `%s`", LIT(arg->string), type_str);
+			gb_string_free(type_str);
+			return false;
+		}
+
+		operand->mode = Addressing_Constant;
+		operand->value = make_exact_value_integer(type_offset_of_from_selection(c->sizes, c->allocator, type, sel));
+		operand->type  = t_untyped_integer;
+	} break;
+
+	case BuiltinProc_offset_of_val: {
+		// offset_of_val :: proc(val: expression) -> untyped int
+		AstNode *arg = unparen_expr(ce->args.e[0]);
+		if (arg->kind != AstNode_SelectorExpr) {
+			gbString str = expr_to_string(arg);
+			error(ast_node_token(arg), "`%s` is not a selector expression", str);
+			return false;
+		}
+		ast_node(s, SelectorExpr, arg);
+
+		check_expr(c, operand, s->expr);
+		if (operand->mode == Addressing_Invalid) {
+			return false;
+		}
+
+		Type *type = operand->type;
+		if (base_type(type)->kind == Type_Pointer) {
+			Type *p = base_type(type);
+			if (is_type_struct(p)) {
+				type = p->Pointer.elem;
+			}
+		}
+		if (is_type_array(type) || is_type_vector(type)) {
+			error(ast_node_token(arg), "Invalid type for `offset_of_val`");
+			return false;
+		}
+
+		ast_node(i, Ident, s->selector);
+		Selection sel = lookup_field(c->allocator, type, i->string, operand->mode == Addressing_Type);
+		if (sel.entity == NULL) {
+			gbString type_str = type_to_string(type);
+			error(ast_node_token(arg),
+			      "`%s` has no field named `%.*s`", type_str, LIT(i->string));
+			return false;
+		}
+		if (sel.indirect) {
+			gbString type_str = type_to_string(type);
+			error(ast_node_token(ce->args.e[0]),
+			      "Field `%.*s` is embedded via a pointer in `%s`", LIT(i->string), type_str);
+			gb_string_free(type_str);
+			return false;
+		}
+
+
+		operand->mode = Addressing_Constant;
+		// IMPORTANT TODO(bill): Fix for anonymous fields
+		operand->value = make_exact_value_integer(type_offset_of_from_selection(c->sizes, c->allocator, type, sel));
+		operand->type  = t_untyped_integer;
+	} break;
+
+	case BuiltinProc_type_of_val:
+		// type_of_val :: proc(val: Type) -> type(Type)
+		check_assignment(c, operand, NULL, str_lit("argument of `type_of_val`"));
+		if (operand->mode == Addressing_Invalid || operand->mode == Addressing_Builtin) {
+			return false;
+		}
+		operand->mode = Addressing_Type;
+		break;
+
+
+	case BuiltinProc_type_info: {
+		// type_info :: proc(Type) -> ^Type_Info
+		AstNode *expr = ce->args.e[0];
+		Type *type = check_type(c, expr);
+		if (type == NULL || type == t_invalid) {
+			error(ast_node_token(expr), "Invalid argument to `type_info`");
+			return false;
+		}
+
+		add_type_info_type(c, type);
+
+		operand->mode = Addressing_Value;
+		operand->type = t_type_info_ptr;
+	} break;
+
+	case BuiltinProc_type_info_of_val: {
+		// type_info_of_val :: proc(val: Type) -> ^Type_Info
+		AstNode *expr = ce->args.e[0];
+
+		check_assignment(c, operand, NULL, str_lit("argument of `type_info_of_val`"));
+		if (operand->mode == Addressing_Invalid || operand->mode == Addressing_Builtin)
+			return false;
+		add_type_info_type(c, operand->type);
+
+		operand->mode = Addressing_Value;
+		operand->type = t_type_info_ptr;
+	} break;
+
+
+
+	case BuiltinProc_compile_assert:
+		// compile_assert :: proc(cond: bool)
+
+		if (!is_type_boolean(operand->type) && operand->mode != Addressing_Constant) {
+			gbString str = expr_to_string(ce->args.e[0]);
+			error(ast_node_token(call), "`%s` is not a constant boolean", str);
+			gb_string_free(str);
+			return false;
+		}
+		if (!operand->value.value_bool) {
+			gbString str = expr_to_string(ce->args.e[0]);
+			error(ast_node_token(call), "Compile time assertion: `%s`", str);
+			gb_string_free(str);
+		}
+		break;
+
+	case BuiltinProc_assert:
+		// assert :: proc(cond: bool)
+
+		if (!is_type_boolean(operand->type)) {
+			gbString str = expr_to_string(ce->args.e[0]);
+			error(ast_node_token(call), "`%s` is not a boolean", str);
+			gb_string_free(str);
+			return false;
+		}
+
+		operand->mode = Addressing_NoValue;
+		break;
+
+	case BuiltinProc_panic:
+		// panic :: proc(msg: string)
+
+		if (!is_type_string(operand->type)) {
+			gbString str = expr_to_string(ce->args.e[0]);
+			error(ast_node_token(call), "`%s` is not a string", str);
+			gb_string_free(str);
+			return false;
+		}
+
+		operand->mode = Addressing_NoValue;
+		break;
+
+	case BuiltinProc_copy: {
+		// copy :: proc(x, y: []Type) -> int
+		Type *dest_type = NULL, *src_type = NULL;
+
+		Type *d = base_type(operand->type);
+		if (d->kind == Type_Slice) {
+			dest_type = d->Slice.elem;
+		}
+		Operand op = {0};
+		check_expr(c, &op, ce->args.e[1]);
+		if (op.mode == Addressing_Invalid) {
+			return false;
+		}
+		Type *s = base_type(op.type);
+		if (s->kind == Type_Slice) {
+			src_type = s->Slice.elem;
+		}
+
+		if (dest_type == NULL || src_type == NULL) {
+			error(ast_node_token(call), "`copy` only expects slices as arguments");
+			return false;
+		}
+
+		if (!are_types_identical(dest_type, src_type)) {
+			gbString d_arg = expr_to_string(ce->args.e[0]);
+			gbString s_arg = expr_to_string(ce->args.e[1]);
+			gbString d_str = type_to_string(dest_type);
+			gbString s_str = type_to_string(src_type);
+			error(ast_node_token(call),
+			      "Arguments to `copy`, %s, %s, have different elem types: %s vs %s",
+			      d_arg, s_arg, d_str, s_str);
+			gb_string_free(s_str);
+			gb_string_free(d_str);
+			gb_string_free(s_arg);
+			gb_string_free(d_arg);
+			return false;
+		}
+
+		operand->type = t_int; // Returns number of elems copied
+		operand->mode = Addressing_Value;
+	} break;
+
+	case BuiltinProc_append: {
+		// append :: proc(x : ^[]Type, y : Type) -> bool
+		Type *x_type = NULL, *y_type = NULL;
+		x_type = base_type(operand->type);
+
+		Operand op = {0};
+		check_expr(c, &op, ce->args.e[1]);
+		if (op.mode == Addressing_Invalid) {
+			return false;
+		}
+		y_type = base_type(op.type);
+
+		if (!(is_type_pointer(x_type) && is_type_slice(x_type->Pointer.elem))) {
+			error(ast_node_token(call), "First argument to `append` must be a pointer to a slice");
+			return false;
+		}
+
+		Type *elem_type = x_type->Pointer.elem->Slice.elem;
+		if (!check_is_assignable_to(c, &op, elem_type)) {
+			gbString d_arg = expr_to_string(ce->args.e[0]);
+			gbString s_arg = expr_to_string(ce->args.e[1]);
+			gbString d_str = type_to_string(elem_type);
+			gbString s_str = type_to_string(y_type);
+			error(ast_node_token(call),
+			      "Arguments to `append`, %s, %s, have different element types: %s vs %s",
+			      d_arg, s_arg, d_str, s_str);
+			gb_string_free(s_str);
+			gb_string_free(d_str);
+			gb_string_free(s_arg);
+			gb_string_free(d_arg);
+			return false;
+		}
+
+		operand->type = t_bool; // Returns if it was successful
+		operand->mode = Addressing_Value;
+	} break;
+
+	case BuiltinProc_swizzle: {
+		// swizzle :: proc(v: {N}T, T...) -> {M}T
+		Type *vector_type = base_type(operand->type);
+		if (!is_type_vector(vector_type)) {
+			gbString type_str = type_to_string(operand->type);
+			error(ast_node_token(call),
+			      "You can only `swizzle` a vector, got `%s`",
+			      type_str);
+			gb_string_free(type_str);
+			return false;
+		}
+
+		isize max_count = vector_type->Vector.count;
+		isize arg_count = 0;
+		for_array(i, ce->args) {
+			if (i == 0) {
+				continue;
+			}
+			AstNode *arg = ce->args.e[i];
+			Operand op = {0};
+			check_expr(c, &op, arg);
+			if (op.mode == Addressing_Invalid) {
+				return false;
+			}
+			Type *arg_type = base_type(op.type);
+			if (!is_type_integer(arg_type) || op.mode != Addressing_Constant) {
+				error(ast_node_token(op.expr), "Indices to `swizzle` must be constant integers");
+				return false;
+			}
+
+			if (op.value.value_integer < 0) {
+				error(ast_node_token(op.expr), "Negative `swizzle` index");
+				return false;
+			}
+
+			if (max_count <= op.value.value_integer) {
+				error(ast_node_token(op.expr), "`swizzle` index exceeds vector length");
+				return false;
+			}
+
+			arg_count++;
+		}
+
+		if (arg_count > max_count) {
+			error(ast_node_token(call), "Too many `swizzle` indices, %td > %td", arg_count, max_count);
+			return false;
+		}
+
+		Type *elem_type = vector_type->Vector.elem;
+		operand->type = make_type_vector(c->allocator, elem_type, arg_count);
+		operand->mode = Addressing_Value;
+	} break;
+
+#if 0
+	case BuiltinProc_ptr_offset: {
+		// ptr_offset :: proc(ptr: ^T, offset: int) -> ^T
+		// ^T cannot be rawptr
+		Type *ptr_type = base_type(operand->type);
+		if (!is_type_pointer(ptr_type)) {
+			gbString type_str = type_to_string(operand->type);
+			defer (gb_string_free(type_str));
+			error(ast_node_token(call),
+			      "Expected a pointer to `ptr_offset`, got `%s`",
+			      type_str);
+			return false;
+		}
+
+		if (ptr_type == t_rawptr) {
+			error(ast_node_token(call),
+			      "`rawptr` cannot have pointer arithmetic");
+			return false;
+		}
+
+		AstNode *offset = ce->args.e[1];
+		Operand op = {0};
+		check_expr(c, &op, offset);
+		if (op.mode == Addressing_Invalid)
+			return false;
+		Type *offset_type = base_type(op.type);
+		if (!is_type_integer(offset_type)) {
+			error(ast_node_token(op.expr), "Pointer offsets for `ptr_offset` must be an integer");
+			return false;
+		}
+
+		if (operand->mode == Addressing_Constant &&
+		    op.mode == Addressing_Constant) {
+			i64 ptr = operand->value.value_pointer;
+			i64 elem_size = type_size_of(c->sizes, c->allocator, ptr_type->Pointer.elem);
+			ptr += elem_size * op.value.value_integer;
+			operand->value.value_pointer = ptr;
+		} else {
+			operand->mode = Addressing_Value;
+		}
+
+	} break;
+
+	case BuiltinProc_ptr_sub: {
+		// ptr_sub :: proc(a, b: ^T) -> int
+		// ^T cannot be rawptr
+		Type *ptr_type = base_type(operand->type);
+		if (!is_type_pointer(ptr_type)) {
+			gbString type_str = type_to_string(operand->type);
+			defer (gb_string_free(type_str));
+			error(ast_node_token(call),
+			      "Expected a pointer to `ptr_add`, got `%s`",
+			      type_str);
+			return false;
+		}
+
+		if (ptr_type == t_rawptr) {
+			error(ast_node_token(call),
+			      "`rawptr` cannot have pointer arithmetic");
+			return false;
+		}
+		AstNode *offset = ce->args[1];
+		Operand op = {0};
+		check_expr(c, &op, offset);
+		if (op.mode == Addressing_Invalid)
+			return false;
+		if (!is_type_pointer(op.type)) {
+			gbString type_str = type_to_string(operand->type);
+			defer (gb_string_free(type_str));
+			error(ast_node_token(call),
+			      "Expected a pointer to `ptr_add`, got `%s`",
+			      type_str);
+			return false;
+		}
+
+		if (base_type(op.type) == t_rawptr) {
+			error(ast_node_token(call),
+			      "`rawptr` cannot have pointer arithmetic");
+			return false;
+		}
+
+		if (!are_types_identical(operand->type, op.type)) {
+			gbString a = type_to_string(operand->type);
+			gbString b = type_to_string(op.type);
+			defer (gb_string_free(a));
+			defer (gb_string_free(b));
+			error(ast_node_token(op.expr),
+			      "`ptr_sub` requires to pointer of the same type. Got `%s` and `%s`.", a, b);
+			return false;
+		}
+
+		operand->type = t_int;
+
+		if (operand->mode == Addressing_Constant &&
+		    op.mode == Addressing_Constant) {
+			u8 *ptr_a = cast(u8 *)operand->value.value_pointer;
+			u8 *ptr_b = cast(u8 *)op.value.value_pointer;
+			isize elem_size = type_size_of(c->sizes, c->allocator, ptr_type->Pointer.elem);
+			operand->value = make_exact_value_integer((ptr_a - ptr_b) / elem_size);
+		} else {
+			operand->mode = Addressing_Value;
+		}
+	} break;
+#endif
+
+	case BuiltinProc_slice_ptr: {
+		// slice_ptr :: proc(a: ^T, len: int[, cap: int]) -> []T
+		// ^T cannot be rawptr
+		Type *ptr_type = base_type(operand->type);
+		if (!is_type_pointer(ptr_type)) {
+			gbString type_str = type_to_string(operand->type);
+			error(ast_node_token(call),
+			      "Expected a pointer to `slice_ptr`, got `%s`",
+			      type_str);
+			gb_string_free(type_str);
+			return false;
+		}
+
+		if (ptr_type == t_rawptr) {
+			error(ast_node_token(call),
+			      "`rawptr` cannot have pointer arithmetic");
+			return false;
+		}
+
+		AstNode *len = ce->args.e[1];
+		AstNode *cap = NULL;
+		if (ce->args.count > 2) {
+			cap = ce->args.e[2];
+		}
+
+		Operand op = {0};
+		check_expr(c, &op, len);
+		if (op.mode == Addressing_Invalid)
+			return false;
+		if (!is_type_integer(op.type)) {
+			gbString type_str = type_to_string(operand->type);
+			error(ast_node_token(call),
+			      "Length for `slice_ptr` must be an integer, got `%s`",
+			      type_str);
+			gb_string_free(type_str);
+			return false;
+		}
+
+		if (cap != NULL) {
+			check_expr(c, &op, cap);
+			if (op.mode == Addressing_Invalid)
+				return false;
+			if (!is_type_integer(op.type)) {
+				gbString type_str = type_to_string(operand->type);
+				error(ast_node_token(call),
+				      "Capacity for `slice_ptr` must be an integer, got `%s`",
+				      type_str);
+				gb_string_free(type_str);
+				return false;
+			}
+			if (ce->args.count > 3) {
+				error(ast_node_token(call),
+				      "Too many arguments to `slice_ptr`, expected either 2 or 3");
+				return false;
+			}
+		}
+
+		operand->type = make_type_slice(c->allocator, ptr_type->Pointer.elem);
+		operand->mode = Addressing_Value;
+	} break;
+
+	case BuiltinProc_min: {
+		// min :: proc(a, b: comparable) -> comparable
+		Type *type = base_type(operand->type);
+		if (!is_type_comparable(type) || !is_type_numeric(type)) {
+			gbString type_str = type_to_string(operand->type);
+			error(ast_node_token(call),
+			      "Expected a comparable numeric type to `min`, got `%s`",
+			      type_str);
+			gb_string_free(type_str);
+			return false;
+		}
+
+		AstNode *other_arg = ce->args.e[1];
+		Operand a = *operand;
+		Operand b = {0};
+		check_expr(c, &b, other_arg);
+		if (b.mode == Addressing_Invalid) {
+			return false;
+		}
+		if (!is_type_comparable(b.type) || !is_type_numeric(type)) {
+			gbString type_str = type_to_string(b.type);
+			error(ast_node_token(call),
+			      "Expected a comparable numeric type to `min`, got `%s`",
+			      type_str);
+			gb_string_free(type_str);
+			return false;
+		}
+
+		if (a.mode == Addressing_Constant &&
+		    b.mode == Addressing_Constant) {
+			ExactValue x = a.value;
+			ExactValue y = b.value;
+			Token lt = {Token_Lt};
+
+			operand->mode = Addressing_Constant;
+			if (compare_exact_values(lt, x, y)) {
+				operand->value = x;
+				operand->type = a.type;
+			} else {
+				operand->value = y;
+				operand->type = b.type;
+			}
+		} else {
+			operand->mode = Addressing_Value;
+			operand->type = type;
+
+			convert_to_typed(c, &a, b.type, 0);
+			if (a.mode == Addressing_Invalid) {
+				return false;
+			}
+			convert_to_typed(c, &b, a.type, 0);
+			if (b.mode == Addressing_Invalid) {
+				return false;
+			}
+
+			if (!are_types_identical(operand->type, b.type)) {
+				gbString type_a = type_to_string(a.type);
+				gbString type_b = type_to_string(b.type);
+				error(ast_node_token(call),
+				      "Mismatched types to `min`, `%s` vs `%s`",
+				      type_a, type_b);
+				gb_string_free(type_b);
+				gb_string_free(type_a);
+				return false;
+			}
+		}
+
+	} break;
+
+	case BuiltinProc_max: {
+		// min :: proc(a, b: comparable) -> comparable
+		Type *type = base_type(operand->type);
+		if (!is_type_comparable(type) || !is_type_numeric(type)) {
+			gbString type_str = type_to_string(operand->type);
+			error(ast_node_token(call),
+			      "Expected a comparable numeric type to `max`, got `%s`",
+			      type_str);
+			gb_string_free(type_str);
+			return false;
+		}
+
+		AstNode *other_arg = ce->args.e[1];
+		Operand a = *operand;
+		Operand b = {0};
+		check_expr(c, &b, other_arg);
+		if (b.mode == Addressing_Invalid) {
+			return false;
+		}
+		if (!is_type_comparable(b.type) || !is_type_numeric(type)) {
+			gbString type_str = type_to_string(b.type);
+			error(ast_node_token(call),
+			      "Expected a comparable numeric type to `max`, got `%s`",
+			      type_str);
+			gb_string_free(type_str);
+			return false;
+		}
+
+		if (a.mode == Addressing_Constant &&
+		    b.mode == Addressing_Constant) {
+			ExactValue x = a.value;
+			ExactValue y = b.value;
+			Token gt = {Token_Gt};
+
+			operand->mode = Addressing_Constant;
+			if (compare_exact_values(gt, x, y)) {
+				operand->value = x;
+				operand->type = a.type;
+			} else {
+				operand->value = y;
+				operand->type = b.type;
+			}
+		} else {
+			operand->mode = Addressing_Value;
+			operand->type = type;
+
+			convert_to_typed(c, &a, b.type, 0);
+			if (a.mode == Addressing_Invalid) {
+				return false;
+			}
+			convert_to_typed(c, &b, a.type, 0);
+			if (b.mode == Addressing_Invalid) {
+				return false;
+			}
+
+			if (!are_types_identical(operand->type, b.type)) {
+				gbString type_a = type_to_string(a.type);
+				gbString type_b = type_to_string(b.type);
+				error(ast_node_token(call),
+				      "Mismatched types to `max`, `%s` vs `%s`",
+				      type_a, type_b);
+				gb_string_free(type_b);
+				gb_string_free(type_a);
+				return false;
+			}
+		}
+
+	} break;
+
+	case BuiltinProc_abs: {
+		// abs :: proc(n: numeric) -> numeric
+		Type *type = base_type(operand->type);
+		if (!is_type_numeric(type)) {
+			gbString type_str = type_to_string(operand->type);
+			error(ast_node_token(call),
+			      "Expected a numeric type to `abs`, got `%s`",
+			      type_str);
+			gb_string_free(type_str);
+			return false;
+		}
+
+		if (operand->mode == Addressing_Constant) {
+			switch (operand->value.kind) {
+			case ExactValue_Integer:
+				operand->value.value_integer = gb_abs(operand->value.value_integer);
+				break;
+			case ExactValue_Float:
+				operand->value.value_float = gb_abs(operand->value.value_float);
+				break;
+			default:
+				GB_PANIC("Invalid numeric constant");
+				break;
+			}
+		} else {
+			operand->mode = Addressing_Value;
+		}
+
+		operand->type = type;
+	} break;
+
+	case BuiltinProc_enum_to_string: {
+		Type *type = base_type(operand->type);
+		if (!is_type_enum(type)) {
+			gbString type_str = type_to_string(operand->type);
+			gb_string_free(type_str);
+			error(ast_node_token(call),
+			      "Expected an enum to `enum_to_string`, got `%s`",
+			      type_str);
+			return false;
+		}
+
+		if (operand->mode == Addressing_Constant) {
+			ExactValue value = make_exact_value_string(str_lit(""));
+			if (operand->value.kind == ExactValue_Integer) {
+				i64 index = operand->value.value_integer;
+				for (isize i = 0; i < type->Record.other_field_count; i++) {
+					Entity *f = type->Record.other_fields[i];
+					if (f->kind == Entity_Constant && f->Constant.value.kind == ExactValue_Integer) {
+						i64 fv = f->Constant.value.value_integer;
+						if (index == fv) {
+							value = make_exact_value_string(f->token.string);
+							break;
+						}
+					}
+				}
+			}
+
+			operand->value = value;
+			operand->type = t_string;
+			return true;
+		}
+
+		add_type_info_type(c, operand->type);
+
+		operand->mode = Addressing_Value;
+		operand->type = t_string;
+	} break;
+	}
+
+	return true;
+}
+
+
+void check_call_arguments(Checker *c, Operand *operand, Type *proc_type, AstNode *call) {
+	GB_ASSERT(call->kind == AstNode_CallExpr);
+	GB_ASSERT(proc_type->kind == Type_Proc);
+	ast_node(ce, CallExpr, call);
+
+	isize param_count = 0;
+	bool variadic = proc_type->Proc.variadic;
+	bool vari_expand = (ce->ellipsis.pos.line != 0);
+
+	if (proc_type->Proc.params != NULL) {
+		param_count = proc_type->Proc.params->Tuple.variable_count;
+		if (variadic) {
+			param_count--;
+		}
+	}
+
+	if (vari_expand && !variadic) {
+		error(ce->ellipsis,
+		      "Cannot use `..` in call to a non-variadic procedure: `%.*s`",
+		      LIT(ce->proc->Ident.string));
+		return;
+	}
+
+	if (ce->args.count == 0 && param_count == 0) {
+		return;
+	}
+
+	gbTempArenaMemory tmp = gb_temp_arena_memory_begin(&c->tmp_arena);
+
+	Array(Operand) operands;
+	array_init_reserve(&operands, c->tmp_allocator, 2*param_count);
+
+	for_array(i, ce->args) {
+		Operand o = {0};
+		check_multi_expr(c, &o, ce->args.e[i]);
+		if (o.type->kind != Type_Tuple) {
+			array_add(&operands, o);
+		} else {
+			TypeTuple *tuple = &o.type->Tuple;
+			if (variadic && i >= param_count) {
+				error(ast_node_token(ce->args.e[i]),
+				      "`..` in a variadic procedure cannot be applied to a %td-valued expression", tuple->variable_count);
+				operand->mode = Addressing_Invalid;
+				goto end;
+			}
+			for (isize j = 0; j < tuple->variable_count; j++) {
+				o.type = tuple->variables[j]->type;
+				array_add(&operands, o);
+			}
+		}
+	}
+
+	i32 error_code = 0;
+	if (operands.count < param_count) {
+		error_code = -1;
+	} else if (!variadic && operands.count > param_count) {
+		error_code = +1;
+	}
+	if (error_code != 0) {
+		char *err_fmt = "Too many arguments for `%s`, expected %td arguments";
+		if (error_code < 0) {
+			err_fmt = "Too few arguments for `%s`, expected %td arguments";
+		}
+
+		gbString proc_str = expr_to_string(ce->proc);
+		error(ast_node_token(call), err_fmt, proc_str, param_count);
+		gb_string_free(proc_str);
+		operand->mode = Addressing_Invalid;
+		goto end;
+	}
+
+	GB_ASSERT(proc_type->Proc.params != NULL);
+	Entity **sig_params = proc_type->Proc.params->Tuple.variables;
+	isize operand_index = 0;
+	for (; operand_index < param_count; operand_index++) {
+		Type *arg_type = sig_params[operand_index]->type;
+		Operand o = operands.e[operand_index];
+		if (variadic) {
+			o = operands.e[operand_index];
+		}
+		check_assignment(c, &o, arg_type, str_lit("argument"));
+	}
+
+	if (variadic) {
+		bool variadic_expand = false;
+		Type *slice = sig_params[param_count]->type;
+		GB_ASSERT(is_type_slice(slice));
+		Type *elem = base_type(slice)->Slice.elem;
+		Type *t = elem;
+		for (; operand_index < operands.count; operand_index++) {
+			Operand o = operands.e[operand_index];
+			if (vari_expand) {
+				variadic_expand = true;
+				t = slice;
+				if (operand_index != param_count) {
+					error(ast_node_token(o.expr),
+					      "`..` in a variadic procedure can only have one variadic argument at the end");
+					break;
+				}
+			}
+			check_assignment(c, &o, t, str_lit("argument"));
+		}
+	}
+end:
+	gb_temp_arena_memory_end(tmp);
+}
+
+
+Entity *find_using_index_expr(Type *t) {
+	t = base_type(t);
+	if (t->kind != Type_Record) {
+		return NULL;
+	}
+
+	for (isize i = 0; i < t->Record.field_count; i++) {
+		Entity *f = t->Record.fields[i];
+		if (f->kind == Entity_Variable &&
+		    f->flags & (EntityFlag_Anonymous|EntityFlag_Field)) {
+			if (is_type_indexable(f->type)) {
+				return f;
+			}
+			Entity *res = find_using_index_expr(f->type);
+			if (res != NULL) {
+				return res;
+			}
+		}
+	}
+	return NULL;
+}
+
+ExprKind check_call_expr(Checker *c, Operand *operand, AstNode *call) {
+	GB_ASSERT(call->kind == AstNode_CallExpr);
+	ast_node(ce, CallExpr, call);
+	check_expr_or_type(c, operand, ce->proc);
+
+	if (operand->mode == Addressing_Invalid) {
+		for_array(i, ce->args) {
+			check_expr_base(c, operand, ce->args.e[i], NULL);
+		}
+		operand->mode = Addressing_Invalid;
+		operand->expr = call;
+		return Expr_Stmt;
+	}
+
+
+	if (operand->mode == Addressing_Builtin) {
+		i32 id = operand->builtin_id;
+		if (!check_builtin_procedure(c, operand, call, id)) {
+			operand->mode = Addressing_Invalid;
+		}
+		operand->expr = call;
+		return builtin_procs[id].kind;
+	}
+
+	Type *proc_type = base_type(operand->type);
+	if (proc_type == NULL || proc_type->kind != Type_Proc) {
+		AstNode *e = operand->expr;
+		gbString str = expr_to_string(e);
+		error(ast_node_token(e), "Cannot call a non-procedure: `%s`", str);
+		gb_string_free(str);
+
+		operand->mode = Addressing_Invalid;
+		operand->expr = call;
+
+		return Expr_Stmt;
+	}
+
+	check_call_arguments(c, operand, proc_type, call);
+
+	switch (proc_type->Proc.result_count) {
+	case 0:
+		operand->mode = Addressing_NoValue;
+		break;
+	case 1:
+		operand->mode = Addressing_Value;
+		operand->type = proc_type->Proc.results->Tuple.variables[0]->type;
+		break;
+	default:
+		operand->mode = Addressing_Value;
+		operand->type = proc_type->Proc.results;
+		break;
+	}
+
+	operand->expr = call;
+	return Expr_Stmt;
+}
+
+void check_expr_with_type_hint(Checker *c, Operand *o, AstNode *e, Type *t) {
+	check_expr_base(c, o, e, t);
+	check_not_tuple(c, o);
+	char *err_str = NULL;
+	switch (o->mode) {
+	case Addressing_NoValue:
+		err_str = "used as a value";
+		break;
+	case Addressing_Type:
+		err_str = "is not an expression";
+		break;
+	case Addressing_Builtin:
+		err_str = "must be called";
+		break;
+	}
+	if (err_str != NULL) {
+		gbString str = expr_to_string(e);
+		error(ast_node_token(e), "`%s` %s", str, err_str);
+		gb_string_free(str);
+		o->mode = Addressing_Invalid;
+	}
+}
+
+bool check_set_index_data(Operand *o, Type *t, i64 *max_count) {
+	t = base_type(type_deref(t));
+
+	switch (t->kind) {
+	case Type_Basic:
+		if (is_type_string(t)) {
+			if (o->mode == Addressing_Constant) {
+				*max_count = o->value.value_string.len;
+			}
+			if (o->mode != Addressing_Variable) {
+				o->mode = Addressing_Value;
+			}
+			o->type = t_u8;
+			return true;
+		}
+		break;
+
+	case Type_Array:
+		*max_count = t->Array.count;
+		if (o->mode != Addressing_Variable) {
+			o->mode = Addressing_Value;
+		}
+		o->type = t->Array.elem;
+		return true;
+
+	case Type_Vector:
+		*max_count = t->Vector.count;
+		if (o->mode != Addressing_Variable) {
+			o->mode = Addressing_Value;
+		}
+		o->type = t->Vector.elem;
+		return true;
+
+
+	case Type_Slice:
+		o->type = t->Slice.elem;
+		o->mode = Addressing_Variable;
+		return true;
+	}
+
+	return false;
+}
+
+ExprKind check__expr_base(Checker *c, Operand *o, AstNode *node, Type *type_hint) {
+	ExprKind kind = Expr_Stmt;
+
+	o->mode = Addressing_Invalid;
+	o->type = t_invalid;
+
+	switch (node->kind) {
+	default:
+		goto error;
+		break;
+
+	case_ast_node(be, BadExpr, node)
+		goto error;
+	case_end;
+
+	case_ast_node(i, Ident, node);
+		check_identifier(c, o, node, type_hint, NULL);
+	case_end;
+
+	case_ast_node(bl, BasicLit, node);
+		Type *t = t_invalid;
+		switch (bl->kind) {
+		case Token_Integer: t = t_untyped_integer; break;
+		case Token_Float:   t = t_untyped_float;   break;
+		case Token_String:  t = t_untyped_string;  break;
+		case Token_Rune:    t = t_untyped_rune;    break;
+		default:            GB_PANIC("Unknown literal"); break;
+		}
+		o->mode  = Addressing_Constant;
+		o->type  = t;
+		o->value = make_exact_value_from_basic_literal(*bl);
+	case_end;
+
+	case_ast_node(pl, ProcLit, node);
+		check_open_scope(c, pl->type);
+		c->context.decl = make_declaration_info(c->allocator, c->context.scope);
+		Type *proc_type = check_type(c, pl->type);
+		if (proc_type != NULL) {
+			check_proc_body(c, empty_token, c->context.decl, proc_type, pl->body);
+			o->mode = Addressing_Value;
+			o->type = proc_type;
+			check_close_scope(c);
+		} else {
+			gbString str = expr_to_string(node);
+			error(ast_node_token(node), "Invalid procedure literal `%s`", str);
+			gb_string_free(str);
+			check_close_scope(c);
+			goto error;
+		}
+	case_end;
+
+	case_ast_node(cl, CompoundLit, node);
+		Type *type = type_hint;
+		bool ellipsis_array = false;
+		bool is_constant = true;
+		if (cl->type != NULL) {
+			type = NULL;
+
+			// [..]Type
+			if (cl->type->kind == AstNode_ArrayType && cl->type->ArrayType.count != NULL) {
+				if (cl->type->ArrayType.count->kind == AstNode_Ellipsis) {
+					type = make_type_array(c->allocator, check_type(c, cl->type->ArrayType.elem), -1);
+					ellipsis_array = true;
+				}
+			}
+
+			if (type == NULL) {
+				type = check_type(c, cl->type);
+			}
+		}
+
+		if (type == NULL) {
+			error(ast_node_token(node), "Missing type in compound literal");
+			goto error;
+		}
+
+		Type *t = base_type(type);
+		switch (t->kind) {
+		case Type_Record: {
+			if (!is_type_struct(t)) {
+				if (cl->elems.count != 0) {
+					error(ast_node_token(node), "Illegal compound literal");
+				}
+				break;
+			}
+			if (cl->elems.count == 0) {
+				break; // NOTE(bill): No need to init
+			}
+			{ // Checker values
+				isize field_count = t->Record.field_count;
+				if (cl->elems.e[0]->kind == AstNode_FieldValue) {
+					bool *fields_visited = gb_alloc_array(c->allocator, bool, field_count);
+
+					for_array(i, cl->elems) {
+						AstNode *elem = cl->elems.e[i];
+						if (elem->kind != AstNode_FieldValue) {
+							error(ast_node_token(elem),
+							      "Mixture of `field = value` and value elements in a structure literal is not allowed");
+							continue;
+						}
+						ast_node(fv, FieldValue, elem);
+						if (fv->field->kind != AstNode_Ident) {
+							gbString expr_str = expr_to_string(fv->field);
+							error(ast_node_token(elem),
+							      "Invalid field name `%s` in structure literal", expr_str);
+							gb_string_free(expr_str);
+							continue;
+						}
+						String name = fv->field->Ident.string;
+
+						Selection sel = lookup_field(c->allocator, type, name, o->mode == Addressing_Type);
+						if (sel.entity == NULL) {
+							error(ast_node_token(elem),
+							      "Unknown field `%.*s` in structure literal", LIT(name));
+							continue;
+						}
+
+						if (sel.index.count > 1) {
+							error(ast_node_token(elem),
+							      "Cannot assign to an anonymous field `%.*s` in a structure literal (at the moment)", LIT(name));
+							continue;
+						}
+
+						Entity *field = t->Record.fields[sel.index.e[0]];
+						add_entity_use(c, fv->field, field);
+
+						if (fields_visited[sel.index.e[0]]) {
+							error(ast_node_token(elem),
+							      "Duplicate field `%.*s` in structure literal", LIT(name));
+							continue;
+						}
+
+						fields_visited[sel.index.e[0]] = true;
+						check_expr(c, o, fv->value);
+
+						if (base_type(field->type) == t_any) {
+							is_constant = false;
+						}
+						if (is_constant) {
+							is_constant = o->mode == Addressing_Constant;
+						}
+
+
+						check_assignment(c, o, field->type, str_lit("structure literal"));
+					}
+				} else {
+					for_array(index, cl->elems) {
+						AstNode *elem = cl->elems.e[index];
+						if (elem->kind == AstNode_FieldValue) {
+							error(ast_node_token(elem),
+							      "Mixture of `field = value` and value elements in a structure literal is not allowed");
+							continue;
+						}
+						Entity *field = t->Record.fields_in_src_order[index];
+
+						check_expr(c, o, elem);
+						if (index >= field_count) {
+							error(ast_node_token(o->expr), "Too many values in structure literal, expected %td", field_count);
+							break;
+						}
+
+						if (base_type(field->type) == t_any) {
+							is_constant = false;
+						}
+						if (is_constant) {
+							is_constant = o->mode == Addressing_Constant;
+						}
+
+						check_assignment(c, o, field->type, str_lit("structure literal"));
+					}
+					if (cl->elems.count < field_count) {
+						error(cl->close, "Too few values in structure literal, expected %td, got %td", field_count, cl->elems.count);
+					}
+				}
+			}
+
+		} break;
+
+		case Type_Slice:
+		case Type_Array:
+		case Type_Vector:
+		{
+			Type *elem_type = NULL;
+			String context_name = {0};
+			if (t->kind == Type_Slice) {
+				elem_type = t->Slice.elem;
+				context_name = str_lit("slice literal");
+			} else if (t->kind == Type_Vector) {
+				elem_type = t->Vector.elem;
+				context_name = str_lit("vector literal");
+			} else {
+				elem_type = t->Array.elem;
+				context_name = str_lit("array literal");
+			}
+
+
+			i64 max = 0;
+			isize index = 0;
+			isize elem_count = cl->elems.count;
+
+			if (base_type(elem_type) == t_any) {
+				is_constant = false;
+			}
+
+			for (; index < elem_count; index++) {
+				AstNode *e = cl->elems.e[index];
+				if (e->kind == AstNode_FieldValue) {
+					error(ast_node_token(e),
+					      "`field = value` is only allowed in struct literals");
+					continue;
+				}
+
+				if (t->kind == Type_Array &&
+				    t->Array.count >= 0 &&
+				    index >= t->Array.count) {
+					error(ast_node_token(e), "Index %lld is out of bounds (>= %lld) for array literal", index, t->Array.count);
+				}
+				if (t->kind == Type_Vector &&
+				    t->Vector.count >= 0 &&
+				    index >= t->Vector.count) {
+					error(ast_node_token(e), "Index %lld is out of bounds (>= %lld) for vector literal", index, t->Vector.count);
+				}
+
+				Operand operand = {0};
+				check_expr_with_type_hint(c, &operand, e, elem_type);
+				check_assignment(c, &operand, elem_type, context_name);
+
+				if (is_constant) {
+					is_constant = operand.mode == Addressing_Constant;
+				}
+			}
+			if (max < index) {
+				max = index;
+			}
+
+			if (t->kind == Type_Vector) {
+				if (t->Vector.count > 1 && gb_is_between(index, 2, t->Vector.count-1)) {
+					error(ast_node_token(cl->elems.e[0]),
+					      "Expected either 1 (broadcast) or %td elements in vector literal, got %td", t->Vector.count, index);
+				}
+			}
+
+			if (t->kind == Type_Array && ellipsis_array) {
+				t->Array.count = max;
+			}
+		} break;
+
+		default: {
+			gbString str = type_to_string(type);
+			error(ast_node_token(node), "Invalid compound literal type `%s`", str);
+			gb_string_free(str);
+			goto error;
+		} break;
+		}
+
+		if (is_constant) {
+			o->mode = Addressing_Constant;
+			o->value = make_exact_value_compound(node);
+		} else {
+			o->mode = Addressing_Value;
+		}
+		o->type = type;
+	case_end;
+
+	case_ast_node(pe, ParenExpr, node);
+		kind = check_expr_base(c, o, pe->expr, type_hint);
+		o->expr = node;
+	case_end;
+
+
+	case_ast_node(te, TagExpr, node);
+		// TODO(bill): Tag expressions
+		error(ast_node_token(node), "Tag expressions are not supported yet");
+		kind = check_expr_base(c, o, te->expr, type_hint);
+		o->expr = node;
+	case_end;
+
+	case_ast_node(re, RunExpr, node);
+		// TODO(bill): Tag expressions
+		kind = check_expr_base(c, o, re->expr, type_hint);
+		o->expr = node;
+	case_end;
+
+
+	case_ast_node(ue, UnaryExpr, node);
+		check_expr(c, o, ue->expr);
+		if (o->mode == Addressing_Invalid) {
+			goto error;
+		}
+		check_unary_expr(c, o, ue->op, node);
+		if (o->mode == Addressing_Invalid) {
+			goto error;
+		}
+	case_end;
+
+
+	case_ast_node(be, BinaryExpr, node);
+		check_binary_expr(c, o, node);
+		if (o->mode == Addressing_Invalid) {
+			goto error;
+		}
+	case_end;
+
+
+
+	case_ast_node(se, SelectorExpr, node);
+		check_selector(c, o, node);
+	case_end;
+
+
+	case_ast_node(ie, IndexExpr, node);
+		check_expr(c, o, ie->expr);
+		if (o->mode == Addressing_Invalid) {
+			goto error;
+		}
+
+		Type *t = base_type(type_deref(o->type));
+		bool is_const = o->mode == Addressing_Constant;
+
+		i64 max_count = -1;
+		bool valid = check_set_index_data(o, t, &max_count);
+
+		if (is_const) {
+			valid = false;
+		}
+
+		if (!valid && (is_type_struct(t) || is_type_raw_union(t))) {
+			Entity *found = find_using_index_expr(t);
+			if (found != NULL) {
+				valid = check_set_index_data(o, found->type, &max_count);
+			}
+		}
+
+		if (!valid) {
+			gbString str = expr_to_string(o->expr);
+			if (is_const) {
+				error(ast_node_token(o->expr), "Cannot index a constant `%s`", str);
+			} else {
+				error(ast_node_token(o->expr), "Cannot index `%s`", str);
+			}
+			gb_string_free(str);
+			goto error;
+		}
+
+		if (ie->index == NULL) {
+			gbString str = expr_to_string(o->expr);
+			error(ast_node_token(o->expr), "Missing index for `%s`", str);
+			gb_string_free(str);
+			goto error;
+		}
+
+		i64 index = 0;
+		bool ok = check_index_value(c, ie->index, max_count, &index);
+
+	case_end;
+
+
+
+	case_ast_node(se, SliceExpr, node);
+		check_expr(c, o, se->expr);
+		if (o->mode == Addressing_Invalid) {
+			goto error;
+		}
+
+		bool valid = false;
+		i64 max_count = -1;
+		Type *t = base_type(type_deref(o->type));
+		switch (t->kind) {
+		case Type_Basic:
+			if (is_type_string(t)) {
+				valid = true;
+				if (o->mode == Addressing_Constant) {
+					max_count = o->value.value_string.len;
+				}
+				if (se->max != NULL) {
+					error(ast_node_token(se->max), "Max (3rd) index not needed in substring expression");
+				}
+				o->type = t_string;
+			}
+			break;
+
+		case Type_Array:
+			valid = true;
+			max_count = t->Array.count;
+			if (o->mode != Addressing_Variable) {
+				gbString str = expr_to_string(node);
+				error(ast_node_token(node), "Cannot slice array `%s`, value is not addressable", str);
+				gb_string_free(str);
+				goto error;
+			}
+			o->type = make_type_slice(c->allocator, t->Array.elem);
+			break;
+
+		case Type_Slice:
+			valid = true;
+			break;
+		}
+
+		if (!valid) {
+			gbString str = expr_to_string(o->expr);
+			error(ast_node_token(o->expr), "Cannot slice `%s`", str);
+			gb_string_free(str);
+			goto error;
+		}
+
+		o->mode = Addressing_Value;
+
+		i64 indices[3] = {0};
+		AstNode *nodes[3] = {se->low, se->high, se->max};
+		for (isize i = 0; i < gb_count_of(nodes); i++) {
+			i64 index = max_count;
+			if (nodes[i] != NULL) {
+				i64 capacity = -1;
+				if (max_count >= 0)
+					capacity = max_count;
+				i64 j = 0;
+				if (check_index_value(c, nodes[i], capacity, &j)) {
+					index = j;
+				}
+			} else if (i == 0) {
+				index = 0;
+			}
+			indices[i] = index;
+		}
+
+		for (isize i = 0; i < gb_count_of(indices); i++) {
+			i64 a = indices[i];
+			for (isize j = i+1; j < gb_count_of(indices); j++) {
+				i64 b = indices[j];
+				if (a > b && b >= 0) {
+					error(se->close, "Invalid slice indices: [%td > %td]", a, b);
+				}
+			}
+		}
+
+	case_end;
+
+
+	case_ast_node(ce, CallExpr, node);
+		return check_call_expr(c, o, node);
+	case_end;
+
+	case_ast_node(de, DerefExpr, node);
+		check_expr_or_type(c, o, de->expr);
+		if (o->mode == Addressing_Invalid) {
+			goto error;
+		} else {
+			Type *t = base_type(o->type);
+			if (t->kind == Type_Pointer) {
+				o->mode = Addressing_Variable;
+				o->type = t->Pointer.elem;
+ 			} else {
+ 				gbString str = expr_to_string(o->expr);
+ 				error(ast_node_token(o->expr), "Cannot dereference `%s`", str);
+ 				gb_string_free(str);
+ 				goto error;
+ 			}
+		}
+	case_end;
+
+	case_ast_node(de, DemaybeExpr, node);
+		check_expr_or_type(c, o, de->expr);
+		if (o->mode == Addressing_Invalid) {
+			goto error;
+		} else {
+			Type *t = base_type(o->type);
+			if (t->kind == Type_Maybe) {
+				Entity **variables = gb_alloc_array(c->allocator, Entity *, 2);
+				Type *elem = t->Maybe.elem;
+				Token tok = make_token_ident(str_lit(""));
+				variables[0] = make_entity_param(c->allocator, NULL, tok, elem, false);
+				variables[1] = make_entity_param(c->allocator, NULL, tok, t_bool, false);
+
+				Type *tuple = make_type_tuple(c->allocator);
+				tuple->Tuple.variables = variables;
+				tuple->Tuple.variable_count = 2;
+
+				o->type = tuple;
+				o->mode = Addressing_Variable;
+ 			} else {
+ 				gbString str = expr_to_string(o->expr);
+ 				error(ast_node_token(o->expr), "Cannot demaybe `%s`", str);
+ 				gb_string_free(str);
+ 				goto error;
+ 			}
+		}
+	case_end;
+
+	case AstNode_ProcType:
+	case AstNode_PointerType:
+	case AstNode_MaybeType:
+	case AstNode_ArrayType:
+	case AstNode_VectorType:
+	case AstNode_StructType:
+	case AstNode_RawUnionType:
+		o->mode = Addressing_Type;
+		o->type = check_type(c, node);
+		break;
+	}
+
+	kind = Expr_Expr;
+	o->expr = node;
+	return kind;
+
+error:
+	o->mode = Addressing_Invalid;
+	o->expr = node;
+	return kind;
+}
+
+ExprKind check_expr_base(Checker *c, Operand *o, AstNode *node, Type *type_hint) {
+	ExprKind kind = check__expr_base(c, o, node, type_hint);
+	Type *type = NULL;
+	ExactValue value = {ExactValue_Invalid};
+	switch (o->mode) {
+	case Addressing_Invalid:
+		type = t_invalid;
+		break;
+	case Addressing_NoValue:
+		type = NULL;
+		break;
+	case Addressing_Constant:
+		type = o->type;
+		value = o->value;
+		break;
+	default:
+		type = o->type;
+		break;
+	}
+
+	if (type != NULL && is_type_untyped(type)) {
+		add_untyped(&c->info, node, false, o->mode, type, value);
+	} else {
+		add_type_and_value(&c->info, node, o->mode, type, value);
+	}
+	return kind;
+}
+
+
+void check_multi_expr(Checker *c, Operand *o, AstNode *e) {
+	gbString err_str = NULL;
+	check_expr_base(c, o, e, NULL);
+	switch (o->mode) {
+	default:
+		return; // NOTE(bill): Valid
+
+	case Addressing_NoValue:
+		err_str = expr_to_string(e);
+		error(ast_node_token(e), "`%s` used as value", err_str);
+		break;
+	case Addressing_Type:
+		err_str = expr_to_string(e);
+		error(ast_node_token(e), "`%s` is not an expression", err_str);
+		break;
+	}
+	gb_string_free(err_str);
+	o->mode = Addressing_Invalid;
+}
+
+void check_not_tuple(Checker *c, Operand *o) {
+	if (o->mode == Addressing_Value) {
+		// NOTE(bill): Tuples are not first class thus never named
+		if (o->type->kind == Type_Tuple) {
+			isize count = o->type->Tuple.variable_count;
+			GB_ASSERT(count != 1);
+			error(ast_node_token(o->expr),
+			      "%td-valued tuple found where single value expected", count);
+			o->mode = Addressing_Invalid;
+		}
+	}
+}
+
+void check_expr(Checker *c, Operand *o, AstNode *e) {
+	check_multi_expr(c, o, e);
+	check_not_tuple(c, o);
+}
+
+
+void check_expr_or_type(Checker *c, Operand *o, AstNode *e) {
+	check_expr_base(c, o, e, NULL);
+	check_not_tuple(c, o);
+	if (o->mode == Addressing_NoValue) {
+		gbString str = expr_to_string(o->expr);
+		error(ast_node_token(o->expr),
+		      "`%s` used as value or type", str);
+		o->mode = Addressing_Invalid;
+		gb_string_free(str);
+	}
+}
+
+
+gbString write_expr_to_string(gbString str, AstNode *node);
+
+gbString write_params_to_string(gbString str, AstNodeArray params, char *sep) {
+	for_array(i, params) {
+		ast_node(p, Parameter, params.e[i]);
+		if (i > 0) {
+			str = gb_string_appendc(str, sep);
+		}
+
+		str = write_expr_to_string(str, params.e[i]);
+	}
+	return str;
+}
+
+gbString string_append_token(gbString str, Token token) {
+	if (token.string.len > 0) {
+		return gb_string_append_length(str, token.string.text, token.string.len);
+	}
+	return str;
+}
+
+
+gbString write_expr_to_string(gbString str, AstNode *node) {
+	if (node == NULL)
+		return str;
+
+	if (is_ast_node_stmt(node)) {
+		GB_ASSERT("stmt passed to write_expr_to_string");
+	}
+
+	switch (node->kind) {
+	default:
+		str = gb_string_appendc(str, "(BadExpr)");
+		break;
+
+	case_ast_node(i, Ident, node);
+		str = string_append_token(str, *i);
+	case_end;
+
+	case_ast_node(bl, BasicLit, node);
+		str = string_append_token(str, *bl);
+	case_end;
+
+	case_ast_node(pl, ProcLit, node);
+		str = write_expr_to_string(str, pl->type);
+	case_end;
+
+	case_ast_node(cl, CompoundLit, node);
+		str = write_expr_to_string(str, cl->type);
+		str = gb_string_appendc(str, "{");
+		for_array(i, cl->elems) {
+			if (i > 0) {
+				str = gb_string_appendc(str, ", ");
+			}
+			str = write_expr_to_string(str, cl->elems.e[i]);
+		}
+		str = gb_string_appendc(str, "}");
+	case_end;
+
+	case_ast_node(te, TagExpr, node);
+		str = gb_string_appendc(str, "#");
+		str = string_append_token(str, te->name);
+		str = write_expr_to_string(str, te->expr);
+	case_end;
+
+	case_ast_node(ue, UnaryExpr, node);
+		str = string_append_token(str, ue->op);
+		str = write_expr_to_string(str, ue->expr);
+	case_end;
+
+	case_ast_node(de, DerefExpr, node);
+		str = write_expr_to_string(str, de->expr);
+		str = gb_string_appendc(str, "^");
+	case_end;
+
+	case_ast_node(de, DemaybeExpr, node);
+		str = write_expr_to_string(str, de->expr);
+		str = gb_string_appendc(str, "?");
+	case_end;
+
+	case_ast_node(be, BinaryExpr, node);
+		str = write_expr_to_string(str, be->left);
+		str = gb_string_appendc(str, " ");
+		str = string_append_token(str, be->op);
+		str = gb_string_appendc(str, " ");
+		str = write_expr_to_string(str, be->right);
+	case_end;
+
+	case_ast_node(pe, ParenExpr, node);
+		str = gb_string_appendc(str, "(");
+		str = write_expr_to_string(str, pe->expr);
+		str = gb_string_appendc(str, ")");
+	case_end;
+
+	case_ast_node(se, SelectorExpr, node);
+		str = write_expr_to_string(str, se->expr);
+		str = gb_string_appendc(str, ".");
+		str = write_expr_to_string(str, se->selector);
+	case_end;
+
+	case_ast_node(ie, IndexExpr, node);
+		str = write_expr_to_string(str, ie->expr);
+		str = gb_string_appendc(str, "[");
+		str = write_expr_to_string(str, ie->index);
+		str = gb_string_appendc(str, "]");
+	case_end;
+
+	case_ast_node(se, SliceExpr, node);
+		str = write_expr_to_string(str, se->expr);
+		str = gb_string_appendc(str, "[");
+		str = write_expr_to_string(str, se->low);
+		str = gb_string_appendc(str, ":");
+		str = write_expr_to_string(str, se->high);
+		if (se->triple_indexed) {
+			str = gb_string_appendc(str, ":");
+			str = write_expr_to_string(str, se->max);
+		}
+		str = gb_string_appendc(str, "]");
+	case_end;
+
+	case_ast_node(e, Ellipsis, node);
+		str = gb_string_appendc(str, "..");
+	case_end;
+
+	case_ast_node(fv, FieldValue, node);
+		str = write_expr_to_string(str, fv->field);
+		str = gb_string_appendc(str, " = ");
+		str = write_expr_to_string(str, fv->value);
+	case_end;
+
+	case_ast_node(pt, PointerType, node);
+		str = gb_string_appendc(str, "^");
+		str = write_expr_to_string(str, pt->type);
+	case_end;
+
+	case_ast_node(mt, MaybeType, node);
+		str = gb_string_appendc(str, "?");
+		str = write_expr_to_string(str, mt->type);
+	case_end;
+
+	case_ast_node(at, ArrayType, node);
+		str = gb_string_appendc(str, "[");
+		str = write_expr_to_string(str, at->count);
+		str = gb_string_appendc(str, "]");
+		str = write_expr_to_string(str, at->elem);
+	case_end;
+
+	case_ast_node(vt, VectorType, node);
+		str = gb_string_appendc(str, "{");
+		str = write_expr_to_string(str, vt->count);
+		str = gb_string_appendc(str, "}");
+		str = write_expr_to_string(str, vt->elem);
+	case_end;
+
+	case_ast_node(p, Parameter, node);
+		if (p->is_using) {
+			str = gb_string_appendc(str, "using ");
+		}
+		for_array(i, p->names) {
+			AstNode *name = p->names.e[i];
+			if (i > 0)
+				str = gb_string_appendc(str, ", ");
+			str = write_expr_to_string(str, name);
+		}
+
+		str = gb_string_appendc(str, ": ");
+		str = write_expr_to_string(str, p->type);
+	case_end;
+
+	case_ast_node(ce, CallExpr, node);
+		str = write_expr_to_string(str, ce->proc);
+		str = gb_string_appendc(str, "(");
+
+		for_array(i, ce->args) {
+			AstNode *arg = ce->args.e[i];
+			if (i > 0) {
+				str = gb_string_appendc(str, ", ");
+			}
+			str = write_expr_to_string(str, arg);
+		}
+		str = gb_string_appendc(str, ")");
+	case_end;
+
+	case_ast_node(pt, ProcType, node);
+		str = gb_string_appendc(str, "proc(");
+		str = write_params_to_string(str, pt->params, ", ");
+		str = gb_string_appendc(str, ")");
+	case_end;
+
+	case_ast_node(st, StructType, node);
+		str = gb_string_appendc(str, "struct ");
+		if (st->is_packed)  str = gb_string_appendc(str, "#packed ");
+		if (st->is_ordered) str = gb_string_appendc(str, "#ordered ");
+		for_array(i, st->decls) {
+			if (i > 0) {
+				str = gb_string_appendc(str, "; ");
+			}
+			str = write_expr_to_string(str, st->decls.e[i]);
+		}
+		// str = write_params_to_string(str, st->decl_list, ", ");
+		str = gb_string_appendc(str, "}");
+	case_end;
+
+	case_ast_node(st, RawUnionType, node);
+		str = gb_string_appendc(str, "raw_union {");
+		for_array(i, st->decls) {
+			if (i > 0) {
+				str = gb_string_appendc(str, "; ");
+			}
+			str = write_expr_to_string(str, st->decls.e[i]);
+		}
+		// str = write_params_to_string(str, st->decl_list, ", ");
+		str = gb_string_appendc(str, "}");
+	case_end;
+
+	case_ast_node(st, UnionType, node);
+		str = gb_string_appendc(str, "union {");
+		for_array(i, st->decls) {
+			if (i > 0) {
+				str = gb_string_appendc(str, "; ");
+			}
+			str = write_expr_to_string(str, st->decls.e[i]);
+		}
+		// str = write_params_to_string(str, st->decl_list, ", ");
+		str = gb_string_appendc(str, "}");
+	case_end;
+
+	case_ast_node(et, EnumType, node);
+		str = gb_string_appendc(str, "enum ");
+		if (et->base_type != NULL) {
+			str = write_expr_to_string(str, et->base_type);
+			str = gb_string_appendc(str, " ");
+		}
+		str = gb_string_appendc(str, "{");
+		str = gb_string_appendc(str, "}");
+	case_end;
+	}
+
+	return str;
+}
+
+gbString expr_to_string(AstNode *expression) {
+	return write_expr_to_string(gb_string_make(heap_allocator(), ""), expression);
+}

+ 1130 - 0
src/checker/stmt.c

@@ -0,0 +1,1130 @@
+bool check_is_terminating(AstNode *node);
+bool check_has_break     (AstNode *stmt, bool implicit);
+void check_stmt          (Checker *c, AstNode *node, u32 flags);
+
+
+// Statements and Declarations
+typedef enum StmtFlag {
+	Stmt_BreakAllowed       = GB_BIT(0),
+	Stmt_ContinueAllowed    = GB_BIT(1),
+	Stmt_FallthroughAllowed = GB_BIT(2), // TODO(bill): fallthrough
+} StmtFlag;
+
+
+
+void check_stmt_list(Checker *c, AstNodeArray stmts, u32 flags) {
+	if (stmts.count == 0) {
+		return;
+	}
+
+	gbTempArenaMemory tmp = gb_temp_arena_memory_begin(&c->tmp_arena);
+
+	typedef struct {
+		Entity *e;
+		DeclInfo *d;
+	} Delay;
+	Array(Delay) delayed_const; array_init_reserve(&delayed_const, c->tmp_allocator, stmts.count);
+	Array(Delay) delayed_type;  array_init_reserve(&delayed_type,  c->tmp_allocator, stmts.count);
+
+	for_array(i, stmts) {
+		AstNode *node = stmts.e[i];
+		switch (node->kind) {
+		case_ast_node(cd, ConstDecl, node);
+			for_array(i, cd->values) {
+				AstNode *name = cd->names.e[i];
+				AstNode *value = cd->values.e[i];
+				ExactValue v = {ExactValue_Invalid};
+
+				Entity *e = make_entity_constant(c->allocator, c->context.scope, name->Ident, NULL, v);
+				e->identifier = name;
+
+				DeclInfo *d = make_declaration_info(c->allocator, e->scope);
+				d->type_expr = cd->type;
+				d->init_expr = value;
+
+				add_entity_and_decl_info(c, name, e, d);
+
+				Delay delay = {e, d};
+				array_add(&delayed_const, delay);
+			}
+
+			isize lhs_count = cd->names.count;
+			isize rhs_count = cd->values.count;
+
+			if (rhs_count == 0 && cd->type == NULL) {
+				error(ast_node_token(node), "Missing type or initial expression");
+			} else if (lhs_count < rhs_count) {
+				error(ast_node_token(node), "Extra initial expression");
+			}
+		case_end;
+
+		case_ast_node(td, TypeDecl, node);
+			Entity *e = make_entity_type_name(c->allocator, c->context.scope, td->name->Ident, NULL);
+			e->identifier = td->name;
+
+			DeclInfo *d = make_declaration_info(c->allocator, e->scope);
+			d->type_expr = td->type;
+
+			add_entity_and_decl_info(c, td->name, e, d);
+
+			Delay delay = {e, d};
+			array_add(&delayed_type, delay);
+		case_end;
+		}
+	}
+
+	for_array(i, delayed_type) {
+		check_entity_decl(c, delayed_type.e[i].e, delayed_type.e[i].d, NULL, NULL);
+	}
+	for_array(i, delayed_const) {
+		check_entity_decl(c, delayed_const.e[i].e, delayed_const.e[i].d, NULL, NULL);
+	}
+
+	bool ft_ok = (flags & Stmt_FallthroughAllowed) != 0;
+	u32 f = flags & (~Stmt_FallthroughAllowed);
+
+	for_array(i, stmts) {
+		AstNode *n = stmts.e[i];
+		if (n->kind == AstNode_EmptyStmt) {
+			continue;
+		}
+		u32 new_flags = f;
+		if (ft_ok && i+1 == stmts.count) {
+			new_flags |= Stmt_FallthroughAllowed;
+		}
+		check_stmt(c, n, new_flags);
+	}
+
+	gb_temp_arena_memory_end(tmp);
+}
+
+bool check_is_terminating_list(AstNodeArray stmts) {
+
+	// Iterate backwards
+	for (isize n = stmts.count-1; n >= 0; n--) {
+		AstNode *stmt = stmts.e[n];
+		if (stmt->kind != AstNode_EmptyStmt) {
+			return check_is_terminating(stmt);
+		}
+	}
+
+	return false;
+}
+
+bool check_has_break_list(AstNodeArray stmts, bool implicit) {
+	for_array(i, stmts) {
+		AstNode *stmt = stmts.e[i];
+		if (check_has_break(stmt, implicit)) {
+			return true;
+		}
+	}
+	return false;
+}
+
+
+bool check_has_break(AstNode *stmt, bool implicit) {
+	switch (stmt->kind) {
+	case AstNode_BranchStmt:
+		if (stmt->BranchStmt.token.kind == Token_break) {
+			return implicit;
+		}
+		break;
+	case AstNode_BlockStmt:
+		return check_has_break_list(stmt->BlockStmt.stmts, implicit);
+
+	case AstNode_IfStmt:
+		if (check_has_break(stmt->IfStmt.body, implicit) ||
+		    (stmt->IfStmt.else_stmt != NULL && check_has_break(stmt->IfStmt.else_stmt, implicit))) {
+			return true;
+		}
+		break;
+
+	case AstNode_CaseClause:
+		return check_has_break_list(stmt->CaseClause.stmts, implicit);
+	}
+
+	return false;
+}
+
+
+
+// NOTE(bill): The last expression has to be a `return` statement
+// TODO(bill): This is a mild hack and should be probably handled properly
+// TODO(bill): Warn/err against code after `return` that it won't be executed
+bool check_is_terminating(AstNode *node) {
+	switch (node->kind) {
+	case_ast_node(rs, ReturnStmt, node);
+		return true;
+	case_end;
+
+	case_ast_node(bs, BlockStmt, node);
+		return check_is_terminating_list(bs->stmts);
+	case_end;
+
+	case_ast_node(es, ExprStmt, node);
+		return check_is_terminating(es->expr);
+	case_end;
+
+	case_ast_node(is, IfStmt, node);
+		if (is->else_stmt != NULL) {
+			if (check_is_terminating(is->body) &&
+			    check_is_terminating(is->else_stmt)) {
+			    return true;
+		    }
+		}
+	case_end;
+
+	case_ast_node(fs, ForStmt, node);
+		if (fs->cond == NULL && !check_has_break(fs->body, true)) {
+			return true;
+		}
+	case_end;
+
+	case_ast_node(ms, MatchStmt, node);
+		bool has_default = false;
+		for_array(i, ms->body->BlockStmt.stmts) {
+			AstNode *clause = ms->body->BlockStmt.stmts.e[i];
+			ast_node(cc, CaseClause, clause);
+			if (cc->list.count == 0) {
+				has_default = true;
+			}
+			if (!check_is_terminating_list(cc->stmts) ||
+			    check_has_break_list(cc->stmts, true)) {
+				return false;
+			}
+		}
+		return has_default;
+	case_end;
+
+	case_ast_node(ms, TypeMatchStmt, node);
+		bool has_default = false;
+		for_array(i, ms->body->BlockStmt.stmts) {
+			AstNode *clause = ms->body->BlockStmt.stmts.e[i];
+			ast_node(cc, CaseClause, clause);
+			if (cc->list.count == 0) {
+				has_default = true;
+			}
+			if (!check_is_terminating_list(cc->stmts) ||
+			    check_has_break_list(cc->stmts, true)) {
+				return false;
+			}
+		}
+		return has_default;
+	case_end;
+
+	case_ast_node(pa, PushAllocator, node);
+		return check_is_terminating(pa->body);
+	case_end;
+	case_ast_node(pc, PushContext, node);
+		return check_is_terminating(pc->body);
+	case_end;
+	}
+
+	return false;
+}
+
+Type *check_assignment_variable(Checker *c, Operand *op_a, AstNode *lhs) {
+	if (op_a->mode == Addressing_Invalid ||
+	    op_a->type == t_invalid) {
+		return NULL;
+	}
+
+	AstNode *node = unparen_expr(lhs);
+
+	// NOTE(bill): Ignore assignments to `_`
+	if (node->kind == AstNode_Ident &&
+	    str_eq(node->Ident.string, str_lit("_"))) {
+		add_entity_definition(&c->info, node, NULL);
+		check_assignment(c, op_a, NULL, str_lit("assignment to `_` identifier"));
+		if (op_a->mode == Addressing_Invalid)
+			return NULL;
+		return op_a->type;
+	}
+
+	Entity *e = NULL;
+	bool used = false;
+	if (node->kind == AstNode_Ident) {
+		ast_node(i, Ident, node);
+		e = scope_lookup_entity(c->context.scope, i->string);
+		if (e != NULL && e->kind == Entity_Variable) {
+			used = (e->flags & EntityFlag_Used) != 0; // TODO(bill): Make backup just in case
+		}
+	}
+
+
+	Operand op_b = {Addressing_Invalid};
+	check_expr(c, &op_b, lhs);
+	if (e) {
+		e->flags |= EntityFlag_Used*used;
+	}
+
+	if (op_b.mode == Addressing_Invalid ||
+	    op_b.type == t_invalid) {
+		return NULL;
+	}
+
+	switch (op_b.mode) {
+	case Addressing_Invalid:
+		return NULL;
+	case Addressing_Variable:
+		break;
+	default: {
+		if (op_b.expr->kind == AstNode_SelectorExpr) {
+			// NOTE(bill): Extra error checks
+			Operand op_c = {Addressing_Invalid};
+			ast_node(se, SelectorExpr, op_b.expr);
+			check_expr(c, &op_c, se->expr);
+		}
+
+		gbString str = expr_to_string(op_b.expr);
+		switch (op_b.mode) {
+		case Addressing_Value:
+			error(ast_node_token(op_b.expr), "Cannot assign to `%s`", str);
+			break;
+		default:
+			error(ast_node_token(op_b.expr), "Cannot assign to `%s`", str);
+			break;
+		}
+		gb_string_free(str);
+	} break;
+	}
+
+	check_assignment(c, op_a, op_b.type, str_lit("assignment"));
+	if (op_a->mode == Addressing_Invalid) {
+		return NULL;
+	}
+
+	return op_a->type;
+}
+
+bool check_valid_type_match_type(Type *type, bool *is_union_ptr, bool *is_any) {
+	if (is_type_pointer(type)) {
+		*is_union_ptr = is_type_union(type_deref(type));
+		return *is_union_ptr;
+	}
+	if (is_type_any(type)) {
+		*is_any = true;
+		return *is_any;
+	}
+	return false;
+}
+
+void check_stmt_internal(Checker *c, AstNode *node, u32 flags);
+void check_stmt(Checker *c, AstNode *node, u32 flags) {
+	u32 prev_stmt_state_flags = c->context.stmt_state_flags;
+
+	if (node->stmt_state_flags != 0) {
+		u32 in = node->stmt_state_flags;
+		u32 out = c->context.stmt_state_flags;
+
+		if (in & StmtStateFlag_bounds_check) {
+			out |= StmtStateFlag_bounds_check;
+			out &= ~StmtStateFlag_no_bounds_check;
+		} else if (in & StmtStateFlag_no_bounds_check) {
+			out |= StmtStateFlag_no_bounds_check;
+			out &= ~StmtStateFlag_bounds_check;
+		}
+
+		c->context.stmt_state_flags = out;
+	}
+
+	check_stmt_internal(c, node, flags);
+
+	c->context.stmt_state_flags = prev_stmt_state_flags;
+}
+
+typedef struct TypeAndToken {
+	Type *type;
+	Token token;
+} TypeAndToken;
+
+#define MAP_TYPE TypeAndToken
+#define MAP_PROC map_type_and_token_
+#define MAP_NAME MapTypeAndToken
+#include "../map.c"
+
+void check_stmt_internal(Checker *c, AstNode *node, u32 flags) {
+	u32 mod_flags = flags & (~Stmt_FallthroughAllowed);
+	switch (node->kind) {
+	case_ast_node(_, EmptyStmt, node); case_end;
+	case_ast_node(_, BadStmt,   node); case_end;
+	case_ast_node(_, BadDecl,   node); case_end;
+
+	case_ast_node(es, ExprStmt, node)
+		Operand operand = {Addressing_Invalid};
+		ExprKind kind = check_expr_base(c, &operand, es->expr, NULL);
+		switch (operand.mode) {
+		case Addressing_Type:
+			error(ast_node_token(node), "Is not an expression");
+			break;
+		case Addressing_NoValue:
+			return;
+		default: {
+			if (kind == Expr_Stmt) {
+				return;
+			}
+			if (operand.expr->kind == AstNode_CallExpr) {
+				return;
+			}
+			gbString expr_str = expr_to_string(operand.expr);
+			error(ast_node_token(node), "Expression is not used: `%s`", expr_str);
+			gb_string_free(expr_str);
+		} break;
+		}
+	case_end;
+
+	case_ast_node(ts, TagStmt, node);
+		// TODO(bill): Tag Statements
+		error(ast_node_token(node), "Tag statements are not supported yet");
+		check_stmt(c, ts->stmt, flags);
+	case_end;
+
+	case_ast_node(ids, IncDecStmt, node);
+		Token op = ids->op;
+		switch (ids->op.kind) {
+		case Token_Increment:
+			op.kind = Token_Add;
+			op.string.len = 1;
+			break;
+		case Token_Decrement:
+			op.kind = Token_Sub;
+			op.string.len = 1;
+			break;
+		default:
+			error(ids->op, "Unknown inc/dec operation %.*s", LIT(ids->op.string));
+			return;
+		}
+
+		Operand operand = {Addressing_Invalid};
+		check_expr(c, &operand, ids->expr);
+		if (operand.mode == Addressing_Invalid)
+			return;
+		if (!is_type_numeric(operand.type)) {
+			error(ids->op, "Non numeric type");
+			return;
+		}
+
+		AstNode basic_lit = {AstNode_BasicLit};
+		ast_node(bl, BasicLit, &basic_lit);
+		*bl = ids->op;
+		bl->kind = Token_Integer;
+		bl->string = str_lit("1");
+
+		AstNode binary_expr = {AstNode_BinaryExpr};
+		ast_node(be, BinaryExpr, &binary_expr);
+		be->op = op;
+		be->left = ids->expr;
+		be->right = &basic_lit;
+		check_binary_expr(c, &operand, &binary_expr);
+	case_end;
+
+	case_ast_node(as, AssignStmt, node);
+		switch (as->op.kind) {
+		case Token_Eq: {
+			// a, b, c = 1, 2, 3;  // Multisided
+			if (as->lhs.count == 0) {
+				error(as->op, "Missing lhs in assignment statement");
+				return;
+			}
+
+			gbTempArenaMemory tmp = gb_temp_arena_memory_begin(&c->tmp_arena);
+
+			// NOTE(bill): If there is a bad syntax error, rhs > lhs which would mean there would need to be
+			// an extra allocation
+			Array(Operand) operands;
+			array_init_reserve(&operands, c->tmp_allocator, 2 * as->lhs.count);
+
+			for_array(i, as->rhs) {
+				AstNode *rhs = as->rhs.e[i];
+				Operand o = {0};
+				check_multi_expr(c, &o, rhs);
+				if (o.type->kind != Type_Tuple) {
+					array_add(&operands, o);
+				} else {
+					TypeTuple *tuple = &o.type->Tuple;
+					for (isize j = 0; j < tuple->variable_count; j++) {
+						o.type = tuple->variables[j]->type;
+						array_add(&operands, o);
+					}
+				}
+			}
+
+			isize lhs_count = as->lhs.count;
+			isize rhs_count = operands.count;
+
+			isize operand_count = gb_min(as->lhs.count, operands.count);
+			for (isize i = 0; i < operand_count; i++) {
+				AstNode *lhs = as->lhs.e[i];
+				check_assignment_variable(c, &operands.e[i], lhs);
+			}
+			if (lhs_count != rhs_count) {
+				error(ast_node_token(as->lhs.e[0]), "Assignment count mismatch `%td` = `%td`", lhs_count, rhs_count);
+			}
+
+			gb_temp_arena_memory_end(tmp);
+		} break;
+
+		default: {
+			// a += 1; // Single-sided
+			Token op = as->op;
+			if (as->lhs.count != 1 || as->rhs.count != 1) {
+				error(op, "Assignment operation `%.*s` requires single-valued expressions", LIT(op.string));
+				return;
+			}
+			if (!gb_is_between(op.kind, Token__AssignOpBegin+1, Token__AssignOpEnd-1)) {
+				error(op, "Unknown Assignment operation `%.*s`", LIT(op.string));
+				return;
+			}
+			// TODO(bill): Check if valid assignment operator
+			Operand operand = {Addressing_Invalid};
+			AstNode binary_expr = {AstNode_BinaryExpr};
+			ast_node(be, BinaryExpr, &binary_expr);
+			be->op = op;
+			be->op.kind = cast(TokenKind)(cast(i32)be->op.kind - (Token_AddEq - Token_Add));
+			 // NOTE(bill): Only use the first one will be used
+			be->left  = as->lhs.e[0];
+			be->right = as->rhs.e[0];
+
+			check_binary_expr(c, &operand, &binary_expr);
+			if (operand.mode == Addressing_Invalid) {
+				return;
+			}
+			// NOTE(bill): Only use the first one will be used
+			check_assignment_variable(c, &operand, as->lhs.e[0]);
+		} break;
+		}
+	case_end;
+
+	case_ast_node(bs, BlockStmt, node);
+		check_open_scope(c, node);
+		check_stmt_list(c, bs->stmts, mod_flags);
+		check_close_scope(c);
+	case_end;
+
+	case_ast_node(is, IfStmt, node);
+		check_open_scope(c, node);
+
+		if (is->init != NULL) {
+			check_stmt(c, is->init, 0);
+		}
+
+		Operand operand = {Addressing_Invalid};
+		check_expr(c, &operand, is->cond);
+		if (operand.mode != Addressing_Invalid &&
+		    !is_type_boolean(operand.type)) {
+			error(ast_node_token(is->cond),
+			            "Non-boolean condition in `if` statement");
+		}
+
+		check_stmt(c, is->body, mod_flags);
+
+		if (is->else_stmt) {
+			switch (is->else_stmt->kind) {
+			case AstNode_IfStmt:
+			case AstNode_BlockStmt:
+				check_stmt(c, is->else_stmt, mod_flags);
+				break;
+			default:
+				error(ast_node_token(is->else_stmt),
+				            "Invalid `else` statement in `if` statement");
+				break;
+			}
+		}
+
+		check_close_scope(c);
+	case_end;
+
+	case_ast_node(rs, ReturnStmt, node);
+		GB_ASSERT(c->proc_stack.count > 0);
+
+		if (c->in_defer) {
+			error(rs->token, "You cannot `return` within a defer statement");
+			// TODO(bill): Should I break here?
+			break;
+		}
+
+
+		Type *proc_type = c->proc_stack.e[c->proc_stack.count-1];
+		isize result_count = 0;
+		if (proc_type->Proc.results) {
+			result_count = proc_type->Proc.results->Tuple.variable_count;
+		}
+
+		if (result_count > 0) {
+			Entity **variables = NULL;
+			if (proc_type->Proc.results != NULL) {
+				TypeTuple *tuple = &proc_type->Proc.results->Tuple;
+				variables = tuple->variables;
+			}
+			if (rs->results.count == 0) {
+				error(ast_node_token(node), "Expected %td return values, got 0", result_count);
+			} else {
+				check_init_variables(c, variables, result_count,
+				                     rs->results, str_lit("return statement"));
+			}
+		} else if (rs->results.count > 0) {
+			error(ast_node_token(rs->results.e[0]), "No return values expected");
+		}
+	case_end;
+
+	case_ast_node(fs, ForStmt, node);
+		u32 new_flags = mod_flags | Stmt_BreakAllowed | Stmt_ContinueAllowed;
+		check_open_scope(c, node);
+
+		if (fs->init != NULL) {
+			check_stmt(c, fs->init, 0);
+		}
+		if (fs->cond) {
+			Operand operand = {Addressing_Invalid};
+			check_expr(c, &operand, fs->cond);
+			if (operand.mode != Addressing_Invalid &&
+			    !is_type_boolean(operand.type)) {
+				error(ast_node_token(fs->cond),
+				      "Non-boolean condition in `for` statement");
+			}
+		}
+		if (fs->post != NULL) {
+			check_stmt(c, fs->post, 0);
+		}
+		check_stmt(c, fs->body, new_flags);
+
+		check_close_scope(c);
+	case_end;
+
+	case_ast_node(ms, MatchStmt, node);
+		Operand x = {0};
+
+		mod_flags |= Stmt_BreakAllowed;
+		check_open_scope(c, node);
+
+		if (ms->init != NULL) {
+			check_stmt(c, ms->init, 0);
+		}
+		if (ms->tag != NULL) {
+			check_expr(c, &x, ms->tag);
+			check_assignment(c, &x, NULL, str_lit("match expression"));
+		} else {
+			x.mode  = Addressing_Constant;
+			x.type  = t_bool;
+			x.value = make_exact_value_bool(true);
+
+			Token token = {0};
+			token.pos = ast_node_token(ms->body).pos;
+			token.string = str_lit("true");
+			x.expr  = make_ident(c->curr_ast_file, token);
+		}
+
+		// NOTE(bill): Check for multiple defaults
+		AstNode *first_default = NULL;
+		ast_node(bs, BlockStmt, ms->body);
+		for_array(i, bs->stmts) {
+			AstNode *stmt = bs->stmts.e[i];
+			AstNode *default_stmt = NULL;
+			if (stmt->kind == AstNode_CaseClause) {
+				ast_node(cc, CaseClause, stmt);
+				if (cc->list.count == 0) {
+					default_stmt = stmt;
+				}
+			} else {
+				error(ast_node_token(stmt), "Invalid AST - expected case clause");
+			}
+
+			if (default_stmt != NULL) {
+				if (first_default != NULL) {
+					TokenPos pos = ast_node_token(first_default).pos;
+					error(ast_node_token(stmt),
+					      "multiple `default` clauses\n"
+					      "\tfirst at %.*s(%td:%td)", LIT(pos.file), pos.line, pos.column);
+				} else {
+					first_default = default_stmt;
+				}
+			}
+		}
+;
+
+		MapTypeAndToken seen = {0}; // NOTE(bill): Multimap
+		map_type_and_token_init(&seen, heap_allocator());
+
+		for_array(i, bs->stmts) {
+			AstNode *stmt = bs->stmts.e[i];
+			if (stmt->kind != AstNode_CaseClause) {
+				// NOTE(bill): error handled by above multiple default checker
+				continue;
+			}
+			ast_node(cc, CaseClause, stmt);
+
+
+			for_array(j, cc->list) {
+				AstNode *expr = cc->list.e[j];
+				Operand y = {0};
+				Operand z = {0};
+				Token eq = {Token_CmpEq};
+
+				check_expr(c, &y, expr);
+				if (x.mode == Addressing_Invalid ||
+				    y.mode == Addressing_Invalid) {
+					continue;
+				}
+				convert_to_typed(c, &y, x.type, 0);
+				if (y.mode == Addressing_Invalid) {
+					continue;
+				}
+
+				z = y;
+				check_comparison(c, &z, &x, eq);
+				if (z.mode == Addressing_Invalid) {
+					continue;
+				}
+				if (y.mode != Addressing_Constant) {
+					continue;
+				}
+
+				if (y.value.kind != ExactValue_Invalid) {
+					HashKey key = hash_exact_value(y.value);
+					TypeAndToken *found = map_type_and_token_get(&seen, key);
+					if (found != NULL) {
+						gbTempArenaMemory tmp = gb_temp_arena_memory_begin(&c->tmp_arena);
+						isize count = map_type_and_token_multi_count(&seen, key);
+						TypeAndToken *taps = gb_alloc_array(c->tmp_allocator, TypeAndToken, count);
+
+						map_type_and_token_multi_get_all(&seen, key, taps);
+						bool continue_outer = false;
+
+						for (isize i = 0; i < count; i++) {
+							TypeAndToken tap = taps[i];
+							if (are_types_identical(y.type, tap.type)) {
+								TokenPos pos = tap.token.pos;
+								gbString expr_str = expr_to_string(y.expr);
+								error(ast_node_token(y.expr),
+								      "Duplicate case `%s`\n"
+								      "\tprevious case at %.*s(%td:%td)",
+								      expr_str,
+								      LIT(pos.file), pos.line, pos.column);
+								gb_string_free(expr_str);
+								continue_outer = true;
+								break;
+							}
+						}
+
+						gb_temp_arena_memory_end(tmp);
+
+						if (continue_outer) {
+							continue;
+						}
+					}
+					TypeAndToken tap = {y.type, ast_node_token(y.expr)};
+					map_type_and_token_multi_insert(&seen, key, tap);
+				}
+			}
+
+			check_open_scope(c, stmt);
+			u32 ft_flags = mod_flags;
+			if (i+1 < bs->stmts.count) {
+				ft_flags |= Stmt_FallthroughAllowed;
+			}
+			check_stmt_list(c, cc->stmts, ft_flags);
+			check_close_scope(c);
+		}
+
+		map_type_and_token_destroy(&seen);
+
+		check_close_scope(c);
+	case_end;
+
+	case_ast_node(ms, TypeMatchStmt, node);
+		Operand x = {0};
+
+		mod_flags |= Stmt_BreakAllowed;
+		check_open_scope(c, node);
+
+		bool is_union_ptr = false;
+		bool is_any = false;
+
+		check_expr(c, &x, ms->tag);
+		check_assignment(c, &x, NULL, str_lit("type match expression"));
+		if (!check_valid_type_match_type(x.type, &is_union_ptr, &is_any)) {
+			gbString str = type_to_string(x.type);
+			error(ast_node_token(x.expr),
+			      "Invalid type for this type match expression, got `%s`", str);
+			gb_string_free(str);
+			break;
+		}
+
+
+		// NOTE(bill): Check for multiple defaults
+		AstNode *first_default = NULL;
+		ast_node(bs, BlockStmt, ms->body);
+		for_array(i, bs->stmts) {
+			AstNode *stmt = bs->stmts.e[i];
+			AstNode *default_stmt = NULL;
+			if (stmt->kind == AstNode_CaseClause) {
+				ast_node(cc, CaseClause, stmt);
+				if (cc->list.count == 0) {
+					default_stmt = stmt;
+				}
+			} else {
+				error(ast_node_token(stmt), "Invalid AST - expected case clause");
+			}
+
+			if (default_stmt != NULL) {
+				if (first_default != NULL) {
+					TokenPos pos = ast_node_token(first_default).pos;
+					error(ast_node_token(stmt),
+					      "multiple `default` clauses\n"
+					      "\tfirst at %.*s(%td:%td)", LIT(pos.file), pos.line, pos.column);
+				} else {
+					first_default = default_stmt;
+				}
+			}
+		}
+
+		if (ms->var->kind != AstNode_Ident) {
+			break;
+		}
+
+
+		MapBool seen = {0};
+		map_bool_init(&seen, heap_allocator());
+
+		for_array(i, bs->stmts) {
+			AstNode *stmt = bs->stmts.e[i];
+			if (stmt->kind != AstNode_CaseClause) {
+				// NOTE(bill): error handled by above multiple default checker
+				continue;
+			}
+			ast_node(cc, CaseClause, stmt);
+
+			// TODO(bill): Make robust
+			Type *bt = base_type(type_deref(x.type));
+
+
+			AstNode *type_expr = cc->list.count > 0 ? cc->list.e[0] : NULL;
+			Type *case_type = NULL;
+			if (type_expr != NULL) { // Otherwise it's a default expression
+				Operand y = {0};
+				check_expr_or_type(c, &y, type_expr);
+
+				if (is_union_ptr) {
+					GB_ASSERT(is_type_union(bt));
+					bool tag_type_found = false;
+					for (isize i = 0; i < bt->Record.field_count; i++) {
+						Entity *f = bt->Record.fields[i];
+						if (are_types_identical(f->type, y.type)) {
+							tag_type_found = true;
+							break;
+						}
+					}
+					if (!tag_type_found) {
+						gbString type_str = type_to_string(y.type);
+						error(ast_node_token(y.expr),
+						      "Unknown tag type, got `%s`", type_str);
+						gb_string_free(type_str);
+						continue;
+					}
+					case_type = y.type;
+				} else if (is_any) {
+					case_type = y.type;
+				} else {
+					GB_PANIC("Unknown type to type match statement");
+				}
+
+				HashKey key = hash_pointer(y.type);
+				bool *found = map_bool_get(&seen, key);
+				if (found) {
+					TokenPos pos = cc->token.pos;
+					gbString expr_str = expr_to_string(y.expr);
+					error(ast_node_token(y.expr),
+					      "Duplicate type case `%s`\n"
+					      "\tprevious type case at %.*s(%td:%td)",
+					      expr_str,
+					      LIT(pos.file), pos.line, pos.column);
+					gb_string_free(expr_str);
+					break;
+				}
+				map_bool_set(&seen, key, cast(bool)true);
+			}
+
+			check_open_scope(c, stmt);
+			if (case_type != NULL) {
+				add_type_info_type(c, case_type);
+
+				// NOTE(bill): Dummy type
+				Type *tt = case_type;
+				if (is_union_ptr) {
+					tt = make_type_pointer(c->allocator, case_type);
+					add_type_info_type(c, tt);
+				}
+				Entity *tag_var = make_entity_variable(c->allocator, c->context.scope, ms->var->Ident, tt);
+				tag_var->flags |= EntityFlag_Used;
+				add_entity(c, c->context.scope, ms->var, tag_var);
+				add_entity_use(c, ms->var, tag_var);
+			}
+			check_stmt_list(c, cc->stmts, mod_flags);
+			check_close_scope(c);
+		}
+		map_bool_destroy(&seen);
+
+		check_close_scope(c);
+	case_end;
+
+
+	case_ast_node(ds, DeferStmt, node);
+		if (is_ast_node_decl(ds->stmt)) {
+			error(ds->token, "You cannot defer a declaration");
+		} else {
+			bool out_in_defer = c->in_defer;
+			c->in_defer = true;
+			check_stmt(c, ds->stmt, 0);
+			c->in_defer = out_in_defer;
+		}
+	case_end;
+
+	case_ast_node(bs, BranchStmt, node);
+		Token token = bs->token;
+		switch (token.kind) {
+		case Token_break:
+			if ((flags & Stmt_BreakAllowed) == 0) {
+				error(token, "`break` only allowed in `for` or `match` statements");
+			}
+			break;
+		case Token_continue:
+			if ((flags & Stmt_ContinueAllowed) == 0) {
+				error(token, "`continue` only allowed in `for` statements");
+			}
+			break;
+		case Token_fallthrough:
+			if ((flags & Stmt_FallthroughAllowed) == 0) {
+				error(token, "`fallthrough` statement in illegal position");
+			}
+			break;
+		default:
+			error(token, "Invalid AST: Branch Statement `%.*s`", LIT(token.string));
+			break;
+		}
+	case_end;
+
+	case_ast_node(us, UsingStmt, node);
+		switch (us->node->kind) {
+		case_ast_node(es, ExprStmt, us->node);
+			// TODO(bill): Allow for just a LHS expression list rather than this silly code
+			Entity *e = NULL;
+
+			bool is_selector = false;
+			AstNode *expr = unparen_expr(es->expr);
+			if (expr->kind == AstNode_Ident) {
+				String name = expr->Ident.string;
+				e = scope_lookup_entity(c->context.scope, name);
+			} else if (expr->kind == AstNode_SelectorExpr) {
+				Operand o = {0};
+				e = check_selector(c, &o, expr);
+				is_selector = true;
+			}
+
+			if (e == NULL) {
+				error(us->token, "`using` applied to an unknown entity");
+				return;
+			}
+
+			switch (e->kind) {
+			case Entity_TypeName: {
+				Type *t = base_type(e->type);
+				if (is_type_struct(t) || is_type_enum(t)) {
+					for (isize i = 0; i < t->Record.other_field_count; i++) {
+						Entity *f = t->Record.other_fields[i];
+						Entity *found = scope_insert_entity(c->context.scope, f);
+						if (found != NULL) {
+							gbString expr_str = expr_to_string(expr);
+							error(us->token, "Namespace collision while `using` `%s` of: %.*s", expr_str, LIT(found->token.string));
+							gb_string_free(expr_str);
+							return;
+						}
+						f->using_parent = e;
+					}
+				} else if (is_type_union(t)) {
+					for (isize i = 0; i < t->Record.field_count; i++) {
+						Entity *f = t->Record.fields[i];
+						Entity *found = scope_insert_entity(c->context.scope, f);
+						if (found != NULL) {
+							gbString expr_str = expr_to_string(expr);
+							error(us->token, "Namespace collision while `using` `%s` of: %.*s", expr_str, LIT(found->token.string));
+							gb_string_free(expr_str);
+							return;
+						}
+						f->using_parent = e;
+					}
+					for (isize i = 0; i < t->Record.other_field_count; i++) {
+						Entity *f = t->Record.other_fields[i];
+						Entity *found = scope_insert_entity(c->context.scope, f);
+						if (found != NULL) {
+							gbString expr_str = expr_to_string(expr);
+							error(us->token, "Namespace collision while `using` `%s` of: %.*s", expr_str, LIT(found->token.string));
+							gb_string_free(expr_str);
+							return;
+						}
+						f->using_parent = e;
+					}
+				}
+			} break;
+
+			case Entity_ImportName: {
+				Scope *scope = e->ImportName.scope;
+				for_array(i, scope->elements.entries) {
+					Entity *decl = scope->elements.entries.e[i].value;
+					Entity *found = scope_insert_entity(c->context.scope, decl);
+					if (found != NULL) {
+						gbString expr_str = expr_to_string(expr);
+						error(us->token,
+						      "Namespace collision while `using` `%s` of: %.*s\n"
+						      "\tat %.*s(%td:%td)\n"
+						      "\tat %.*s(%td:%td)",
+						      expr_str, LIT(found->token.string),
+						      LIT(found->token.pos.file), found->token.pos.line, found->token.pos.column,
+						      LIT(decl->token.pos.file), decl->token.pos.line, decl->token.pos.column
+						      );
+						gb_string_free(expr_str);
+						return;
+					}
+				}
+			} break;
+
+			case Entity_Variable: {
+				Type *t = base_type(type_deref(e->type));
+				if (is_type_struct(t) || is_type_raw_union(t)) {
+					Scope **found = map_scope_get(&c->info.scopes, hash_pointer(t->Record.node));
+					GB_ASSERT(found != NULL);
+					for_array(i, (*found)->elements.entries) {
+						Entity *f = (*found)->elements.entries.e[i].value;
+						if (f->kind == Entity_Variable) {
+							Entity *uvar = make_entity_using_variable(c->allocator, e, f->token, f->type);
+							if (is_selector) {
+								uvar->using_expr = expr;
+							}
+							Entity *prev = scope_insert_entity(c->context.scope, uvar);
+							if (prev != NULL) {
+								gbString expr_str = expr_to_string(expr);
+								error(us->token, "Namespace collision while `using` `%s` of: %.*s", expr_str, LIT(prev->token.string));
+								gb_string_free(expr_str);
+								return;
+							}
+						}
+					}
+				} else {
+					error(us->token, "`using` can only be applied to variables of type struct or raw_union");
+					return;
+				}
+			} break;
+
+			case Entity_Constant:
+				error(us->token, "`using` cannot be applied to a constant");
+				break;
+
+			case Entity_Procedure:
+			case Entity_Builtin:
+				error(us->token, "`using` cannot be applied to a procedure");
+				break;
+
+			case Entity_ImplicitValue:
+				error(us->token, "`using` cannot be applied to an implicit value");
+				break;
+
+			case Entity_Nil:
+				error(us->token, "`using` cannot be applied to `nil`");
+				break;
+
+			case Entity_Invalid:
+				error(us->token, "`using` cannot be applied to an invalid entity");
+				break;
+
+			default:
+				GB_PANIC("TODO(bill): `using` other expressions?");
+			}
+		case_end;
+
+		case_ast_node(vd, VarDecl, us->node);
+			if (vd->names.count > 1 && vd->type != NULL) {
+				error(us->token, "`using` can only be applied to one variable of the same type");
+			}
+			check_var_decl_node(c, us->node);
+
+			for_array(name_index, vd->names) {
+				AstNode *item = vd->names.e[name_index];
+				ast_node(i, Ident, item);
+				String name = i->string;
+				Entity *e = scope_lookup_entity(c->context.scope, name);
+				Type *t = base_type(type_deref(e->type));
+				if (is_type_struct(t) || is_type_raw_union(t)) {
+					Scope **found = map_scope_get(&c->info.scopes, hash_pointer(t->Record.node));
+					GB_ASSERT(found != NULL);
+					for_array(i, (*found)->elements.entries) {
+						Entity *f = (*found)->elements.entries.e[i].value;
+						if (f->kind == Entity_Variable) {
+							Entity *uvar = make_entity_using_variable(c->allocator, e, f->token, f->type);
+							Entity *prev = scope_insert_entity(c->context.scope, uvar);
+							if (prev != NULL) {
+								error(us->token, "Namespace collision while `using` `%.*s` of: %.*s", LIT(name), LIT(prev->token.string));
+								return;
+							}
+						}
+					}
+				} else {
+					error(us->token, "`using` can only be applied to variables of type struct or raw_union");
+					return;
+				}
+			}
+		case_end;
+
+
+		default:
+			error(us->token, "Invalid AST: Using Statement");
+			break;
+		}
+	case_end;
+
+
+
+	case_ast_node(pa, PushAllocator, node);
+		Operand op = {0};
+		check_expr(c, &op, pa->expr);
+		check_assignment(c, &op, t_allocator, str_lit("argument to push_allocator"));
+		check_stmt(c, pa->body, mod_flags);
+	case_end;
+
+
+	case_ast_node(pa, PushContext, node);
+		Operand op = {0};
+		check_expr(c, &op, pa->expr);
+		check_assignment(c, &op, t_context, str_lit("argument to push_context"));
+		check_stmt(c, pa->body, mod_flags);
+	case_end;
+
+
+
+
+
+
+	case_ast_node(vd, VarDecl, node);
+		check_var_decl_node(c, node);
+	case_end;
+
+	case_ast_node(cd, ConstDecl, node);
+		// NOTE(bill): Handled elsewhere
+	case_end;
+
+	case_ast_node(td, TypeDecl, node);
+		// NOTE(bill): Handled elsewhere
+	case_end;
+
+	case_ast_node(pd, ProcDecl, node);
+		// NOTE(bill): This must be handled here so it has access to the parent scope stuff
+		// e.g. using
+		Entity *e = make_entity_procedure(c->allocator, c->context.scope, pd->name->Ident, NULL);
+		e->identifier = pd->name;
+
+		DeclInfo *d = make_declaration_info(c->allocator, e->scope);
+		d->proc_decl = node;
+
+		add_entity_and_decl_info(c, pd->name, e, d);
+		check_entity_decl(c, e, d, NULL, NULL);
+	case_end;
+	}
+}

+ 1487 - 0
src/checker/types.c

@@ -0,0 +1,1487 @@
+typedef struct Scope Scope;
+
+typedef enum BasicKind {
+	Basic_Invalid,
+	Basic_bool,
+	Basic_i8,
+	Basic_u8,
+	Basic_i16,
+	Basic_u16,
+	Basic_i32,
+	Basic_u32,
+	Basic_i64,
+	Basic_u64,
+	Basic_i128,
+	Basic_u128,
+	// Basic_f16,
+	Basic_f32,
+	Basic_f64,
+	// Basic_f128,
+	Basic_int,
+	Basic_uint,
+	Basic_rawptr,
+	Basic_string, // ^u8 + int
+	Basic_any,    // ^Type_Info + rawptr
+
+	Basic_UntypedBool,
+	Basic_UntypedInteger,
+	Basic_UntypedFloat,
+	Basic_UntypedString,
+	Basic_UntypedRune,
+	Basic_UntypedNil,
+
+	Basic_Count,
+
+	Basic_byte = Basic_u8,
+	Basic_rune = Basic_i32,
+} BasicKind;
+
+typedef enum BasicFlag {
+	BasicFlag_Boolean  = GB_BIT(0),
+	BasicFlag_Integer  = GB_BIT(1),
+	BasicFlag_Unsigned = GB_BIT(2),
+	BasicFlag_Float    = GB_BIT(3),
+	BasicFlag_Pointer  = GB_BIT(4),
+	BasicFlag_String   = GB_BIT(5),
+	BasicFlag_Rune     = GB_BIT(6),
+	BasicFlag_Untyped  = GB_BIT(7),
+
+	BasicFlag_Numeric      = BasicFlag_Integer | BasicFlag_Float,
+	BasicFlag_Ordered      = BasicFlag_Numeric | BasicFlag_String  | BasicFlag_Pointer,
+	BasicFlag_ConstantType = BasicFlag_Boolean | BasicFlag_Numeric | BasicFlag_Pointer | BasicFlag_String | BasicFlag_Rune,
+} BasicFlag;
+
+typedef struct BasicType {
+	BasicKind kind;
+	u32       flags;
+	i64       size; // -1 if arch. dep.
+	String    name;
+} BasicType;
+
+typedef enum TypeRecordKind {
+	TypeRecord_Invalid,
+
+	TypeRecord_Struct,
+	TypeRecord_Enum,
+	TypeRecord_RawUnion,
+	TypeRecord_Union, // Tagged
+
+	TypeRecord_Count,
+} TypeRecordKind;
+
+typedef struct TypeRecord {
+	TypeRecordKind kind;
+
+	// All record types
+	// Theses are arrays
+	Entity **fields;      // Entity_Variable (otherwise Entity_TypeName if union)
+	i32      field_count; // == offset_count is struct
+	AstNode *node;
+
+	union { // NOTE(bill): Reduce size_of Type
+		struct { // enum only
+			Type *   enum_base; // Default is `int`
+			Entity * enum_count;
+			Entity * min_value;
+			Entity * max_value;
+		};
+		struct { // struct only
+			i64 *    struct_offsets;
+			bool      struct_are_offsets_set;
+			bool      struct_is_packed;
+			bool      struct_is_ordered;
+			Entity **fields_in_src_order; // Entity_Variable
+		};
+	};
+
+	// Entity_Constant or Entity_TypeName
+	Entity **other_fields;
+	i32      other_field_count;
+} TypeRecord;
+
+#define TYPE_KINDS \
+	TYPE_KIND(Basic,   BasicType) \
+	TYPE_KIND(Pointer, struct { Type *elem; }) \
+	TYPE_KIND(Array,   struct { Type *elem; i64 count; }) \
+	TYPE_KIND(Vector,  struct { Type *elem; i64 count; }) \
+	TYPE_KIND(Slice,   struct { Type *elem; }) \
+	TYPE_KIND(Maybe,   struct { Type *elem; }) \
+	TYPE_KIND(Record,  TypeRecord) \
+	TYPE_KIND(Named, struct { \
+		String  name; \
+		Type *  base; \
+		Entity *type_name; /* Entity_TypeName */ \
+	}) \
+	TYPE_KIND(Tuple, struct { \
+		Entity **variables; /* Entity_Variable */ \
+		i32      variable_count; \
+		bool     are_offsets_set; \
+		i64 *    offsets; \
+	}) \
+	TYPE_KIND(Proc, struct { \
+		Scope *scope; \
+		Type * params;  /* Type_Tuple */ \
+		Type * results; /* Type_Tuple */ \
+		i32    param_count; \
+		i32    result_count; \
+		bool   variadic; \
+	})
+
+typedef enum TypeKind {
+	Type_Invalid,
+#define TYPE_KIND(k, ...) GB_JOIN2(Type_, k),
+	TYPE_KINDS
+#undef TYPE_KIND
+	Type_Count,
+} TypeKind;
+
+String const type_strings[] = {
+	{cast(u8 *)"Invalid", gb_size_of("Invalid")},
+#define TYPE_KIND(k, ...) {cast(u8 *)#k, gb_size_of(#k)-1},
+	TYPE_KINDS
+#undef TYPE_KIND
+};
+
+#define TYPE_KIND(k, ...) typedef __VA_ARGS__ GB_JOIN2(Type, k);
+	TYPE_KINDS
+#undef TYPE_KIND
+
+typedef struct Type {
+	TypeKind kind;
+	union {
+#define TYPE_KIND(k, ...) GB_JOIN2(Type, k) k;
+	TYPE_KINDS
+#undef TYPE_KIND
+	};
+} Type;
+
+// NOTE(bill): Internal sizes of certain types
+// string: 2*word_size  (ptr+len)
+// slice:  3*word_size  (ptr+len+cap)
+// array:  count*size_of(elem) aligned
+
+// NOTE(bill): Alignment of structures and other types are to be compatible with C
+
+typedef struct BaseTypeSizes {
+	i64 word_size;
+	i64 max_align;
+} BaseTypeSizes;
+
+typedef Array(isize) Array_isize;
+
+typedef struct Selection {
+	Entity *    entity;
+	Array_isize index;
+	bool        indirect; // Set if there was a pointer deref anywhere down the line
+} Selection;
+Selection empty_selection = {0};
+
+Selection make_selection(Entity *entity, Array_isize index, bool indirect) {
+	Selection s = {entity, index, indirect};
+	return s;
+}
+
+void selection_add_index(Selection *s, isize index) {
+	// IMPORTANT NOTE(bill): this requires a stretchy buffer/dynamic array so it requires some form
+	// of heap allocation
+	if (s->index.e == NULL) {
+		array_init(&s->index, heap_allocator());
+	}
+	array_add(&s->index, index);
+}
+
+
+
+#define STR_LIT(x) {cast(u8 *)(x), gb_size_of(x)-1}
+gb_global Type basic_types[] = {
+	{Type_Basic, {Basic_Invalid,        0,                                       0, STR_LIT("invalid type")}},
+	{Type_Basic, {Basic_bool,           BasicFlag_Boolean,                       1, STR_LIT("bool")}},
+	{Type_Basic, {Basic_i8,             BasicFlag_Integer,                       1, STR_LIT("i8")}},
+	{Type_Basic, {Basic_u8,             BasicFlag_Integer | BasicFlag_Unsigned,  1, STR_LIT("u8")}},
+	{Type_Basic, {Basic_i16,            BasicFlag_Integer,                       2, STR_LIT("i16")}},
+	{Type_Basic, {Basic_u16,            BasicFlag_Integer | BasicFlag_Unsigned,  2, STR_LIT("u16")}},
+	{Type_Basic, {Basic_i32,            BasicFlag_Integer,                       4, STR_LIT("i32")}},
+	{Type_Basic, {Basic_u32,            BasicFlag_Integer | BasicFlag_Unsigned,  4, STR_LIT("u32")}},
+	{Type_Basic, {Basic_i64,            BasicFlag_Integer,                       8, STR_LIT("i64")}},
+	{Type_Basic, {Basic_u64,            BasicFlag_Integer | BasicFlag_Unsigned,  8, STR_LIT("u64")}},
+	{Type_Basic, {Basic_i128,           BasicFlag_Integer,                      16, STR_LIT("i128")}},
+	{Type_Basic, {Basic_u128,           BasicFlag_Integer | BasicFlag_Unsigned, 16, STR_LIT("u128")}},
+	// {Type_Basic, {Basic_f16,            BasicFlag_Float,                         2, STR_LIT("f16")}},
+	{Type_Basic, {Basic_f32,            BasicFlag_Float,                         4, STR_LIT("f32")}},
+	{Type_Basic, {Basic_f64,            BasicFlag_Float,                         8, STR_LIT("f64")}},
+	// {Type_Basic, {Basic_f128,           BasicFlag_Float,                        16, STR_LIT("f128")}},
+	{Type_Basic, {Basic_int,            BasicFlag_Integer,                      -1, STR_LIT("int")}},
+	{Type_Basic, {Basic_uint,           BasicFlag_Integer | BasicFlag_Unsigned, -1, STR_LIT("uint")}},
+	{Type_Basic, {Basic_rawptr,         BasicFlag_Pointer,                      -1, STR_LIT("rawptr")}},
+	{Type_Basic, {Basic_string,         BasicFlag_String,                       -1, STR_LIT("string")}},
+	{Type_Basic, {Basic_any,            0,                                      -1, STR_LIT("any")}},
+	{Type_Basic, {Basic_UntypedBool,    BasicFlag_Boolean | BasicFlag_Untyped,   0, STR_LIT("untyped bool")}},
+	{Type_Basic, {Basic_UntypedInteger, BasicFlag_Integer | BasicFlag_Untyped,   0, STR_LIT("untyped integer")}},
+	{Type_Basic, {Basic_UntypedFloat,   BasicFlag_Float   | BasicFlag_Untyped,   0, STR_LIT("untyped float")}},
+	{Type_Basic, {Basic_UntypedString,  BasicFlag_String  | BasicFlag_Untyped,   0, STR_LIT("untyped string")}},
+	{Type_Basic, {Basic_UntypedRune,    BasicFlag_Integer | BasicFlag_Untyped,   0, STR_LIT("untyped rune")}},
+	{Type_Basic, {Basic_UntypedNil,     BasicFlag_Untyped,                       0, STR_LIT("untyped nil")}},
+};
+
+gb_global Type basic_type_aliases[] = {
+	{Type_Basic, {Basic_byte, BasicFlag_Integer | BasicFlag_Unsigned, 1, STR_LIT("byte")}},
+	{Type_Basic, {Basic_rune, BasicFlag_Integer,                      4, STR_LIT("rune")}},
+};
+
+gb_global Type *t_invalid         = &basic_types[Basic_Invalid];
+gb_global Type *t_bool            = &basic_types[Basic_bool];
+gb_global Type *t_i8              = &basic_types[Basic_i8];
+gb_global Type *t_u8              = &basic_types[Basic_u8];
+gb_global Type *t_i16             = &basic_types[Basic_i16];
+gb_global Type *t_u16             = &basic_types[Basic_u16];
+gb_global Type *t_i32             = &basic_types[Basic_i32];
+gb_global Type *t_u32             = &basic_types[Basic_u32];
+gb_global Type *t_i64             = &basic_types[Basic_i64];
+gb_global Type *t_u64             = &basic_types[Basic_u64];
+gb_global Type *t_i128            = &basic_types[Basic_i128];
+gb_global Type *t_u128            = &basic_types[Basic_u128];
+// gb_global Type *t_f16             = &basic_types[Basic_f16];
+gb_global Type *t_f32             = &basic_types[Basic_f32];
+gb_global Type *t_f64             = &basic_types[Basic_f64];
+// gb_global Type *t_f128            = &basic_types[Basic_f128];
+gb_global Type *t_int             = &basic_types[Basic_int];
+gb_global Type *t_uint            = &basic_types[Basic_uint];
+gb_global Type *t_rawptr          = &basic_types[Basic_rawptr];
+gb_global Type *t_string          = &basic_types[Basic_string];
+gb_global Type *t_any             = &basic_types[Basic_any];
+gb_global Type *t_untyped_bool    = &basic_types[Basic_UntypedBool];
+gb_global Type *t_untyped_integer = &basic_types[Basic_UntypedInteger];
+gb_global Type *t_untyped_float   = &basic_types[Basic_UntypedFloat];
+gb_global Type *t_untyped_string  = &basic_types[Basic_UntypedString];
+gb_global Type *t_untyped_rune    = &basic_types[Basic_UntypedRune];
+gb_global Type *t_untyped_nil     = &basic_types[Basic_UntypedNil];
+gb_global Type *t_byte            = &basic_type_aliases[0];
+gb_global Type *t_rune            = &basic_type_aliases[1];
+
+
+gb_global Type *t_u8_ptr  = NULL;
+gb_global Type *t_int_ptr = NULL;
+
+gb_global Type *t_type_info            = NULL;
+gb_global Type *t_type_info_ptr        = NULL;
+gb_global Type *t_type_info_member     = NULL;
+gb_global Type *t_type_info_member_ptr = NULL;
+
+gb_global Type *t_type_info_named      = NULL;
+gb_global Type *t_type_info_integer    = NULL;
+gb_global Type *t_type_info_float      = NULL;
+gb_global Type *t_type_info_any        = NULL;
+gb_global Type *t_type_info_string     = NULL;
+gb_global Type *t_type_info_boolean    = NULL;
+gb_global Type *t_type_info_pointer    = NULL;
+gb_global Type *t_type_info_maybe      = NULL;
+gb_global Type *t_type_info_procedure  = NULL;
+gb_global Type *t_type_info_array      = NULL;
+gb_global Type *t_type_info_slice      = NULL;
+gb_global Type *t_type_info_vector     = NULL;
+gb_global Type *t_type_info_tuple      = NULL;
+gb_global Type *t_type_info_struct     = NULL;
+gb_global Type *t_type_info_union      = NULL;
+gb_global Type *t_type_info_raw_union  = NULL;
+gb_global Type *t_type_info_enum       = NULL;
+
+gb_global Type *t_allocator            = NULL;
+gb_global Type *t_allocator_ptr        = NULL;
+gb_global Type *t_context              = NULL;
+gb_global Type *t_context_ptr          = NULL;
+
+
+
+
+
+
+gbString type_to_string(Type *type);
+
+Type *base_type(Type *t) {
+	for (;;) {
+		if (t == NULL || t->kind != Type_Named) {
+			break;
+		}
+		t = t->Named.base;
+	}
+	return t;
+}
+
+void set_base_type(Type *t, Type *base) {
+	if (t && t->kind == Type_Named) {
+		t->Named.base = base;
+	}
+}
+
+
+Type *alloc_type(gbAllocator a, TypeKind kind) {
+	Type *t = gb_alloc_item(a, Type);
+	t->kind = kind;
+	return t;
+}
+
+
+Type *make_type_basic(gbAllocator a, BasicType basic) {
+	Type *t = alloc_type(a, Type_Basic);
+	t->Basic = basic;
+	return t;
+}
+
+Type *make_type_pointer(gbAllocator a, Type *elem) {
+	Type *t = alloc_type(a, Type_Pointer);
+	t->Pointer.elem = elem;
+	return t;
+}
+
+Type *make_type_maybe(gbAllocator a, Type *elem) {
+	Type *t = alloc_type(a, Type_Maybe);
+	t->Maybe.elem = elem;
+	return t;
+}
+
+Type *make_type_array(gbAllocator a, Type *elem, i64 count) {
+	Type *t = alloc_type(a, Type_Array);
+	t->Array.elem = elem;
+	t->Array.count = count;
+	return t;
+}
+
+Type *make_type_vector(gbAllocator a, Type *elem, i64 count) {
+	Type *t = alloc_type(a, Type_Vector);
+	t->Vector.elem = elem;
+	t->Vector.count = count;
+	return t;
+}
+
+Type *make_type_slice(gbAllocator a, Type *elem) {
+	Type *t = alloc_type(a, Type_Slice);
+	t->Array.elem = elem;
+	return t;
+}
+
+
+Type *make_type_struct(gbAllocator a) {
+	Type *t = alloc_type(a, Type_Record);
+	t->Record.kind = TypeRecord_Struct;
+	return t;
+}
+
+Type *make_type_union(gbAllocator a) {
+	Type *t = alloc_type(a, Type_Record);
+	t->Record.kind = TypeRecord_Union;
+	return t;
+}
+
+Type *make_type_raw_union(gbAllocator a) {
+	Type *t = alloc_type(a, Type_Record);
+	t->Record.kind = TypeRecord_RawUnion;
+	return t;
+}
+
+Type *make_type_enum(gbAllocator a) {
+	Type *t = alloc_type(a, Type_Record);
+	t->Record.kind = TypeRecord_Enum;
+	return t;
+}
+
+
+
+Type *make_type_named(gbAllocator a, String name, Type *base, Entity *type_name) {
+	Type *t = alloc_type(a, Type_Named);
+	t->Named.name = name;
+	t->Named.base = base;
+	t->Named.type_name = type_name;
+	return t;
+}
+
+Type *make_type_tuple(gbAllocator a) {
+	Type *t = alloc_type(a, Type_Tuple);
+	return t;
+}
+
+Type *make_type_proc(gbAllocator a, Scope *scope, Type *params, isize param_count, Type *results, isize result_count, bool variadic) {
+	Type *t = alloc_type(a, Type_Proc);
+
+	if (variadic) {
+		if (param_count == 0) {
+			GB_PANIC("variadic procedure must have at least one parameter");
+		}
+		GB_ASSERT(params != NULL && params->kind == Type_Tuple);
+		Entity *e = params->Tuple.variables[param_count-1];
+		if (base_type(e->type)->kind != Type_Slice) {
+			// NOTE(bill): For custom calling convention
+			GB_PANIC("variadic parameter must be of type slice");
+		}
+	}
+
+	t->Proc.scope        = scope;
+	t->Proc.params       = params;
+	t->Proc.param_count  = param_count;
+	t->Proc.results      = results;
+	t->Proc.result_count = result_count;
+	t->Proc.variadic     = variadic;
+	return t;
+}
+
+
+Type *type_deref(Type *t) {
+	if (t != NULL) {
+		Type *bt = base_type(t);
+		if (bt == NULL)
+			return NULL;
+		if (bt != NULL && bt->kind == Type_Pointer)
+			return bt->Pointer.elem;
+	}
+	return t;
+}
+
+Type *get_enum_base_type(Type *t) {
+	Type *bt = base_type(t);
+	if (bt->kind == Type_Record && bt->Record.kind == TypeRecord_Enum) {
+		GB_ASSERT(bt->Record.enum_base != NULL);
+		return bt->Record.enum_base;
+	}
+	return t;
+}
+
+bool is_type_named(Type *t) {
+	if (t->kind == Type_Basic) {
+		return true;
+	}
+	return t->kind == Type_Named;
+}
+bool is_type_boolean(Type *t) {
+	t = base_type(t);
+	if (t->kind == Type_Basic) {
+		return (t->Basic.flags & BasicFlag_Boolean) != 0;
+	}
+	return false;
+}
+bool is_type_integer(Type *t) {
+	t = base_type(t);
+	if (t->kind == Type_Basic) {
+		return (t->Basic.flags & BasicFlag_Integer) != 0;
+	}
+	return false;
+}
+bool is_type_unsigned(Type *t) {
+	t = base_type(t);
+	if (t->kind == Type_Basic) {
+		return (t->Basic.flags & BasicFlag_Unsigned) != 0;
+	}
+	return false;
+}
+bool is_type_numeric(Type *t) {
+	t = base_type(t);
+	if (t->kind == Type_Basic) {
+		return (t->Basic.flags & BasicFlag_Numeric) != 0;
+	}
+	if (t->kind == Type_Vector) {
+		return is_type_numeric(t->Vector.elem);
+	}
+	return false;
+}
+bool is_type_string(Type *t) {
+	t = base_type(t);
+	if (t->kind == Type_Basic) {
+		return (t->Basic.flags & BasicFlag_String) != 0;
+	}
+	return false;
+}
+bool is_type_typed(Type *t) {
+	t = base_type(t);
+	if (t->kind == Type_Basic) {
+		return (t->Basic.flags & BasicFlag_Untyped) == 0;
+	}
+	return true;
+}
+bool is_type_untyped(Type *t) {
+	t = base_type(t);
+	if (t->kind == Type_Basic) {
+		return (t->Basic.flags & BasicFlag_Untyped) != 0;
+	}
+	return false;
+}
+bool is_type_ordered(Type *t) {
+	t = base_type(get_enum_base_type(t));
+	if (t->kind == Type_Basic) {
+		return (t->Basic.flags & BasicFlag_Ordered) != 0;
+	}
+	if (t->kind == Type_Pointer) {
+		return true;
+	}
+	return false;
+}
+bool is_type_constant_type(Type *t) {
+	t = base_type(t);
+	if (t->kind == Type_Basic) {
+		return (t->Basic.flags & BasicFlag_ConstantType) != 0;
+	}
+	if (t->kind == Type_Record) {
+		return t->Record.kind == TypeRecord_Enum;
+	}
+	return false;
+}
+bool is_type_float(Type *t) {
+	t = base_type(t);
+	if (t->kind == Type_Basic) {
+		return (t->Basic.flags & BasicFlag_Float) != 0;
+	}
+	return false;
+}
+bool is_type_f32(Type *t) {
+	t = base_type(t);
+	if (t->kind == Type_Basic) {
+		return t->Basic.kind == Basic_f32;
+	}
+	return false;
+}
+bool is_type_f64(Type *t) {
+	t = base_type(t);
+	if (t->kind == Type_Basic) {
+		return t->Basic.kind == Basic_f64;
+	}
+	return false;
+}
+bool is_type_pointer(Type *t) {
+	t = base_type(t);
+	if (t->kind == Type_Basic) {
+		return (t->Basic.flags & BasicFlag_Pointer) != 0;
+	}
+	return t->kind == Type_Pointer;
+}
+bool is_type_maybe(Type *t) {
+	t = base_type(t);
+	return t->kind == Type_Maybe;
+}
+bool is_type_tuple(Type *t) {
+	t = base_type(t);
+	return t->kind == Type_Tuple;
+}
+
+
+bool is_type_int_or_uint(Type *t) {
+	if (t->kind == Type_Basic) {
+		return (t->Basic.kind == Basic_int) || (t->Basic.kind == Basic_uint);
+	}
+	return false;
+}
+bool is_type_rawptr(Type *t) {
+	if (t->kind == Type_Basic) {
+		return t->Basic.kind == Basic_rawptr;
+	}
+	return false;
+}
+bool is_type_u8(Type *t) {
+	if (t->kind == Type_Basic) {
+		return t->Basic.kind == Basic_u8;
+	}
+	return false;
+}
+bool is_type_array(Type *t) {
+	t = base_type(t);
+	return t->kind == Type_Array;
+}
+bool is_type_slice(Type *t) {
+	t = base_type(t);
+	return t->kind == Type_Slice;
+}
+bool is_type_u8_slice(Type *t) {
+	t = base_type(t);
+	if (t->kind == Type_Slice) {
+		return is_type_u8(t->Slice.elem);
+	}
+	return false;
+}
+bool is_type_vector(Type *t) {
+	t = base_type(t);
+	return t->kind == Type_Vector;
+}
+bool is_type_proc(Type *t) {
+	t = base_type(t);
+	return t->kind == Type_Proc;
+}
+Type *base_vector_type(Type *t) {
+	if (is_type_vector(t)) {
+		t = base_type(t);
+		return t->Vector.elem;
+	}
+	return t;
+}
+
+
+bool is_type_enum(Type *t) {
+	t = base_type(t);
+	return (t->kind == Type_Record && t->Record.kind == TypeRecord_Enum);
+}
+bool is_type_struct(Type *t) {
+	t = base_type(t);
+	return (t->kind == Type_Record && t->Record.kind == TypeRecord_Struct);
+}
+bool is_type_union(Type *t) {
+	t = base_type(t);
+	return (t->kind == Type_Record && t->Record.kind == TypeRecord_Union);
+}
+bool is_type_raw_union(Type *t) {
+	t = base_type(t);
+	return (t->kind == Type_Record && t->Record.kind == TypeRecord_RawUnion);
+}
+
+bool is_type_any(Type *t) {
+	t = base_type(t);
+	return (t->kind == Type_Basic && t->Basic.kind == Basic_any);
+}
+bool is_type_untyped_nil(Type *t) {
+	t = base_type(t);
+	return (t->kind == Type_Basic && t->Basic.kind == Basic_UntypedNil);
+}
+
+
+
+bool is_type_indexable(Type *t) {
+	return is_type_array(t) || is_type_slice(t) || is_type_vector(t) || is_type_string(t);
+}
+
+
+bool type_has_nil(Type *t) {
+	t = base_type(t);
+	switch (t->kind) {
+	case Type_Basic:
+		return is_type_rawptr(t);
+
+	case Type_Tuple:
+		return false;
+
+	case Type_Record:
+		switch (t->Record.kind) {
+		case TypeRecord_Enum:
+			return false;
+		}
+		break;
+	}
+	return true;
+}
+
+
+bool is_type_comparable(Type *t) {
+	t = base_type(get_enum_base_type(t));
+	switch (t->kind) {
+	case Type_Basic:
+		return t->kind != Basic_UntypedNil;
+	case Type_Pointer:
+		return true;
+	case Type_Record: {
+		if (false && is_type_struct(t)) {
+			// TODO(bill): Should I even allow this?
+			for (isize i = 0; i < t->Record.field_count; i++) {
+				if (!is_type_comparable(t->Record.fields[i]->type))
+					return false;
+			}
+		} else if (is_type_enum(t)) {
+			return is_type_comparable(t->Record.enum_base);
+		}
+		return false;
+	} break;
+	case Type_Array:
+		return is_type_comparable(t->Array.elem);
+	case Type_Vector:
+		return is_type_comparable(t->Vector.elem);
+	case Type_Proc:
+		return true;
+	}
+	return false;
+}
+
+bool are_types_identical(Type *x, Type *y) {
+	if (x == y)
+		return true;
+
+	if ((x == NULL && y != NULL) ||
+	    (x != NULL && y == NULL)) {
+		return false;
+	}
+
+	switch (x->kind) {
+	case Type_Basic:
+		if (y->kind == Type_Basic) {
+			return x->Basic.kind == y->Basic.kind;
+		}
+		break;
+
+	case Type_Array:
+		if (y->kind == Type_Array) {
+			return (x->Array.count == y->Array.count) && are_types_identical(x->Array.elem, y->Array.elem);
+		}
+		break;
+
+	case Type_Vector:
+		if (y->kind == Type_Vector) {
+			return (x->Vector.count == y->Vector.count) && are_types_identical(x->Vector.elem, y->Vector.elem);
+		}
+		break;
+
+	case Type_Slice:
+		if (y->kind == Type_Slice) {
+			return are_types_identical(x->Slice.elem, y->Slice.elem);
+		}
+		break;
+
+	case Type_Record:
+		if (y->kind == Type_Record) {
+			if (x->Record.kind == y->Record.kind) {
+				switch (x->Record.kind) {
+				case TypeRecord_Struct:
+				case TypeRecord_RawUnion:
+				case TypeRecord_Union:
+					if (x->Record.field_count == y->Record.field_count &&
+					    x->Record.struct_is_packed == y->Record.struct_is_packed &&
+					    x->Record.struct_is_ordered == y->Record.struct_is_ordered) {
+						for (isize i = 0; i < x->Record.field_count; i++) {
+							if (!are_types_identical(x->Record.fields[i]->type, y->Record.fields[i]->type)) {
+								return false;
+							}
+							if (str_ne(x->Record.fields[i]->token.string, y->Record.fields[i]->token.string)) {
+								return false;
+							}
+						}
+						return true;
+					}
+					break;
+
+				case TypeRecord_Enum:
+					// NOTE(bill): Each enum is unique
+					return x == y;
+				}
+			}
+		}
+		break;
+
+	case Type_Pointer:
+		if (y->kind == Type_Pointer) {
+			return are_types_identical(x->Pointer.elem, y->Pointer.elem);
+		}
+		break;
+
+	case Type_Maybe:
+		if (y->kind == Type_Maybe) {
+			return are_types_identical(x->Maybe.elem, y->Maybe.elem);
+		}
+		break;
+
+	case Type_Named:
+		if (y->kind == Type_Named) {
+			return x->Named.base == y->Named.base;
+		}
+		break;
+
+	case Type_Tuple:
+		if (y->kind == Type_Tuple) {
+			if (x->Tuple.variable_count == y->Tuple.variable_count) {
+				for (isize i = 0; i < x->Tuple.variable_count; i++) {
+					if (!are_types_identical(x->Tuple.variables[i]->type, y->Tuple.variables[i]->type)) {
+						return false;
+					}
+				}
+				return true;
+			}
+		}
+		break;
+
+	case Type_Proc:
+		if (y->kind == Type_Proc) {
+			return are_types_identical(x->Proc.params, y->Proc.params) &&
+			       are_types_identical(x->Proc.results, y->Proc.results);
+		}
+		break;
+	}
+
+
+	return false;
+}
+
+
+Type *default_type(Type *type) {
+	if (type->kind == Type_Basic) {
+		switch (type->Basic.kind) {
+		case Basic_UntypedBool:    return t_bool;
+		case Basic_UntypedInteger: return t_int;
+		case Basic_UntypedFloat:   return t_f64;
+		case Basic_UntypedString:  return t_string;
+		case Basic_UntypedRune:    return t_rune;
+		}
+	}
+	return type;
+}
+
+
+
+
+gb_global Entity *entity__any_type_info  = NULL;
+gb_global Entity *entity__any_data       = NULL;
+gb_global Entity *entity__string_data    = NULL;
+gb_global Entity *entity__string_count   = NULL;
+gb_global Entity *entity__slice_count    = NULL;
+gb_global Entity *entity__slice_capacity = NULL;
+
+Selection lookup_field_with_selection(gbAllocator a, Type *type_, String field_name, bool is_type, Selection sel);
+
+Selection lookup_field(gbAllocator a, Type *type_, String field_name, bool is_type) {
+	return lookup_field_with_selection(a, type_, field_name, is_type, empty_selection);
+}
+
+Selection lookup_field_with_selection(gbAllocator a, Type *type_, String field_name, bool is_type, Selection sel) {
+	GB_ASSERT(type_ != NULL);
+
+	if (str_eq(field_name, str_lit("_"))) {
+		return empty_selection;
+	}
+
+	Type *type = type_deref(type_);
+	bool is_ptr = type != type_;
+	sel.indirect = sel.indirect || is_ptr;
+
+	type = base_type(type);
+
+	if (type->kind == Type_Basic) {
+		switch (type->Basic.kind) {
+		case Basic_any: {
+			String type_info_str = str_lit("type_info");
+			String data_str = str_lit("data");
+			if (entity__any_type_info == NULL) {
+				entity__any_type_info = make_entity_field(a, NULL, make_token_ident(type_info_str), t_type_info_ptr, false, 0);
+			}
+			if (entity__any_data == NULL) {
+				entity__any_data = make_entity_field(a, NULL, make_token_ident(data_str), t_rawptr, false, 1);
+			}
+
+			if (str_eq(field_name, type_info_str)) {
+				selection_add_index(&sel, 0);
+				sel.entity = entity__any_type_info;
+				return sel;
+			} else if (str_eq(field_name, data_str)) {
+				selection_add_index(&sel, 1);
+				sel.entity = entity__any_data;
+				return sel;
+			}
+		} break;
+		case Basic_string: {
+			String data_str = str_lit("data");
+			String count_str = str_lit("count");
+			if (entity__string_data == NULL) {
+				entity__string_data = make_entity_field(a, NULL, make_token_ident(data_str), make_type_pointer(a, t_u8), false, 0);
+			}
+
+			if (entity__string_count == NULL) {
+				entity__string_count = make_entity_field(a, NULL, make_token_ident(count_str), t_int, false, 1);
+			}
+
+			if (str_eq(field_name, data_str)) {
+				selection_add_index(&sel, 0);
+				sel.entity = entity__string_data;
+				return sel;
+			} else if (str_eq(field_name, count_str)) {
+				selection_add_index(&sel, 1);
+				sel.entity = entity__string_count;
+				return sel;
+			}
+		} break;
+		}
+
+		return sel;
+	} else if (type->kind == Type_Array) {
+		String count_str = str_lit("count");
+		// NOTE(bill): Underlying memory address cannot be changed
+		if (str_eq(field_name, count_str)) {
+			// HACK(bill): Memory leak
+			sel.entity = make_entity_constant(a, NULL, make_token_ident(count_str), t_int, make_exact_value_integer(type->Array.count));
+			return sel;
+		}
+	} else if (type->kind == Type_Vector) {
+		String count_str = str_lit("count");
+		// NOTE(bill): Vectors are not addressable
+		if (str_eq(field_name, count_str)) {
+			// HACK(bill): Memory leak
+			sel.entity = make_entity_constant(a, NULL, make_token_ident(count_str), t_int, make_exact_value_integer(type->Vector.count));
+			return sel;
+		}
+
+		if (type->Vector.count <= 4 && !is_type_boolean(type->Vector.elem)) {
+			// HACK(bill): Memory leak
+			switch (type->Vector.count) {
+			#define _VECTOR_FIELD_CASE(_length, _name) \
+			case (_length): \
+				if (str_eq(field_name, str_lit(_name))) { \
+					selection_add_index(&sel, (_length)-1); \
+					sel.entity = make_entity_vector_elem(a, NULL, make_token_ident(str_lit(_name)), type->Vector.elem, (_length)-1); \
+					return sel; \
+				} \
+				/*fallthrough*/
+
+			_VECTOR_FIELD_CASE(4, "w");
+			_VECTOR_FIELD_CASE(3, "z");
+			_VECTOR_FIELD_CASE(2, "y");
+			_VECTOR_FIELD_CASE(1, "x");
+			default: break;
+
+			#undef _VECTOR_FIELD_CASE
+			}
+		}
+
+	} else if (type->kind == Type_Slice) {
+		String data_str     = str_lit("data");
+		String count_str    = str_lit("count");
+		String capacity_str = str_lit("capacity");
+
+		if (str_eq(field_name, data_str)) {
+			selection_add_index(&sel, 0);
+			// HACK(bill): Memory leak
+			sel.entity = make_entity_field(a, NULL, make_token_ident(data_str), make_type_pointer(a, type->Slice.elem), false, 0);
+			return sel;
+		} else if (str_eq(field_name, count_str)) {
+			selection_add_index(&sel, 1);
+			if (entity__slice_count == NULL) {
+				entity__slice_count = make_entity_field(a, NULL, make_token_ident(count_str), t_int, false, 1);
+			}
+
+			sel.entity = entity__slice_count;
+			return sel;
+		} else if (str_eq(field_name, capacity_str)) {
+			selection_add_index(&sel, 2);
+			if (entity__slice_capacity == NULL) {
+				entity__slice_capacity = make_entity_field(a, NULL, make_token_ident(capacity_str), t_int, false, 2);
+			}
+
+			sel.entity = entity__slice_capacity;
+			return sel;
+		}
+	}
+
+	if (type->kind != Type_Record) {
+		return sel;
+	}
+	if (is_type) {
+		if (is_type_union(type)) {
+			// NOTE(bill): The subtype for a union are stored in the fields
+			// as they are "kind of" like variables but not
+			for (isize i = 0; i < type->Record.field_count; i++) {
+				Entity *f = type->Record.fields[i];
+				GB_ASSERT(f->kind == Entity_TypeName);
+				String str = f->token.string;
+
+				if (str_eq(field_name, str)) {
+					sel.entity = f;
+					selection_add_index(&sel, i);
+					return sel;
+				}
+			}
+		}
+
+		for (isize i = 0; i < type->Record.other_field_count; i++) {
+			Entity *f = type->Record.other_fields[i];
+			GB_ASSERT(f->kind != Entity_Variable);
+			String str = f->token.string;
+
+			if (str_eq(field_name, str)) {
+				sel.entity = f;
+				selection_add_index(&sel, i);
+				return sel;
+			}
+		}
+
+		if (is_type_enum(type)) {
+			if (str_eq(field_name, str_lit("count"))) {
+				sel.entity = type->Record.enum_count;
+				return sel;
+			} else if (str_eq(field_name, str_lit("min_value"))) {
+				sel.entity = type->Record.min_value;
+				return sel;
+			} else if (str_eq(field_name, str_lit("max_value"))) {
+				sel.entity = type->Record.max_value;
+				return sel;
+			}
+		}
+
+	} else if (!is_type_enum(type) && !is_type_union(type)) {
+		for (isize i = 0; i < type->Record.field_count; i++) {
+			Entity *f = type->Record.fields[i];
+			GB_ASSERT(f->kind == Entity_Variable && f->flags & EntityFlag_Field);
+			String str = f->token.string;
+			if (str_eq(field_name, str)) {
+				selection_add_index(&sel, i);  // HACK(bill): Leaky memory
+				sel.entity = f;
+				return sel;
+			}
+
+			if (f->flags & EntityFlag_Anonymous) {
+				isize prev_count = sel.index.count;
+				selection_add_index(&sel, i); // HACK(bill): Leaky memory
+
+				sel = lookup_field_with_selection(a, f->type, field_name, is_type, sel);
+
+				if (sel.entity != NULL) {
+					if (is_type_pointer(f->type)) {
+						sel.indirect = true;
+					}
+					return sel;
+				}
+				sel.index.count = prev_count;
+			}
+		}
+	}
+
+	return sel;
+}
+
+
+
+i64 type_size_of(BaseTypeSizes s, gbAllocator allocator, Type *t);
+i64 type_align_of(BaseTypeSizes s, gbAllocator allocator, Type *t);
+i64 type_offset_of(BaseTypeSizes s, gbAllocator allocator, Type *t, i64 index);
+
+i64 align_formula(i64 size, i64 align) {
+	if (align > 0) {
+		i64 result = size + align-1;
+		return result - result%align;
+	}
+	return size;
+}
+
+i64 type_align_of(BaseTypeSizes s, gbAllocator allocator, Type *t) {
+	t = base_type(t);
+
+	switch (t->kind) {
+	case Type_Array:
+		return type_align_of(s, allocator, t->Array.elem);
+	case Type_Vector: {
+		i64 size = type_size_of(s, allocator, t->Vector.elem);
+		i64 count = gb_max(prev_pow2(t->Vector.count), 1);
+		i64 total = size * count;
+		return gb_clamp(total, 1, s.max_align);
+	} break;
+
+	case Type_Tuple: {
+		i64 max = 1;
+		for (isize i = 0; i < t->Tuple.variable_count; i++) {
+			i64 align = type_align_of(s, allocator, t->Tuple.variables[i]->type);
+			if (max < align) {
+				max = align;
+			}
+		}
+		return max;
+	} break;
+
+	case Type_Maybe:
+		return gb_max(type_align_of(s, allocator, t->Maybe.elem), type_align_of(s, allocator, t_bool));
+
+	case Type_Record: {
+		switch (t->Record.kind) {
+		case TypeRecord_Struct:
+			if (t->Record.field_count > 0) {
+				// TODO(bill): What is this supposed to be?
+				if (t->Record.struct_is_packed) {
+					i64 max = s.word_size;
+					for (isize i = 1; i < t->Record.field_count; i++) {
+						// NOTE(bill): field zero is null
+						i64 align = type_align_of(s, allocator, t->Record.fields[i]->type);
+						if (max < align) {
+							max = align;
+						}
+					}
+					return max;
+				}
+				return type_align_of(s, allocator, t->Record.fields[0]->type);
+			}
+			break;
+		case TypeRecord_Union: {
+			i64 max = 1;
+			for (isize i = 1; i < t->Record.field_count; i++) {
+				// NOTE(bill): field zero is null
+				i64 align = type_align_of(s, allocator, t->Record.fields[i]->type);
+				if (max < align) {
+					max = align;
+				}
+			}
+			return max;
+		} break;
+		case TypeRecord_RawUnion: {
+			i64 max = 1;
+			for (isize i = 0; i < t->Record.field_count; i++) {
+				i64 align = type_align_of(s, allocator, t->Record.fields[i]->type);
+				if (max < align) {
+					max = align;
+				}
+			}
+			return max;
+		} break;
+		case TypeRecord_Enum:
+			return type_align_of(s, allocator, t->Record.enum_base);
+		}
+	} break;
+	}
+
+	// return gb_clamp(next_pow2(type_size_of(s, allocator, t)), 1, s.max_align);
+	// NOTE(bill): Things that are bigger than s.word_size, are actually comprised of smaller types
+	// TODO(bill): Is this correct for 128-bit types (integers)?
+	return gb_clamp(next_pow2(type_size_of(s, allocator, t)), 1, s.word_size);
+}
+
+i64 *type_set_offsets_of(BaseTypeSizes s, gbAllocator allocator, Entity **fields, isize field_count, bool is_packed) {
+	i64 *offsets = gb_alloc_array(allocator, i64, field_count);
+	i64 curr_offset = 0;
+	if (is_packed) {
+		for (isize i = 0; i < field_count; i++) {
+			offsets[i] = curr_offset;
+			curr_offset += type_size_of(s, allocator, fields[i]->type);
+		}
+
+	} else {
+		for (isize i = 0; i < field_count; i++) {
+			i64 align = type_align_of(s, allocator, fields[i]->type);
+			curr_offset = align_formula(curr_offset, align);
+			offsets[i] = curr_offset;
+			curr_offset += type_size_of(s, allocator, fields[i]->type);
+		}
+	}
+	return offsets;
+}
+
+bool type_set_offsets(BaseTypeSizes s, gbAllocator allocator, Type *t) {
+	t = base_type(t);
+	if (is_type_struct(t)) {
+		if (!t->Record.struct_are_offsets_set) {
+			t->Record.struct_offsets = type_set_offsets_of(s, allocator, t->Record.fields, t->Record.field_count, t->Record.struct_is_packed);
+			t->Record.struct_are_offsets_set = true;
+			return true;
+		}
+	} else if (is_type_tuple(t)) {
+		if (!t->Tuple.are_offsets_set) {
+			t->Tuple.offsets = type_set_offsets_of(s, allocator, t->Tuple.variables, t->Tuple.variable_count, false);
+			t->Tuple.are_offsets_set = true;
+			return true;
+		}
+	} else {
+		GB_PANIC("Invalid type for setting offsets");
+	}
+	return false;
+}
+
+i64 type_size_of(BaseTypeSizes s, gbAllocator allocator, Type *t) {
+	t = base_type(t);
+	switch (t->kind) {
+	case Type_Basic: {
+		GB_ASSERT(is_type_typed(t));
+		BasicKind kind = t->Basic.kind;
+		i64 size = t->Basic.size;
+		if (size > 0) {
+			return size;
+		}
+		switch (kind) {
+		case Basic_string: return 2*s.word_size;
+		case Basic_any:    return 2*s.word_size;
+
+		case Basic_int: case Basic_uint: case Basic_rawptr:
+			return s.word_size;
+		}
+	} break;
+
+	case Type_Array: {
+		i64 count = t->Array.count;
+		if (count == 0) {
+			return 0;
+		}
+		i64 align = type_align_of(s, allocator, t->Array.elem);
+		i64 size  = type_size_of(s,  allocator, t->Array.elem);
+		i64 alignment = align_formula(size, align);
+		return alignment*(count-1) + size;
+	} break;
+
+	case Type_Vector: {
+		i64 count = t->Vector.count;
+		if (count == 0) {
+			return 0;
+		}
+		// i64 align = type_align_of(s, allocator, t->Vector.elem);
+		i64 bit_size = 8*type_size_of(s,  allocator, t->Vector.elem);
+		if (is_type_boolean(t->Vector.elem)) {
+			bit_size = 1; // NOTE(bill): LLVM can store booleans as 1 bit because a boolean _is_ an `i1`
+			              // Silly LLVM spec
+		}
+		i64 total_size_in_bits = bit_size * count;
+		i64 total_size = (total_size_in_bits+7)/8;
+		return total_size;
+	} break;
+
+
+	case Type_Slice: // ptr + len + cap
+		return 3 * s.word_size;
+
+	case Type_Maybe: { // value + bool
+		Type *elem = t->Maybe.elem;
+		i64 align = type_align_of(s, allocator, elem);
+		i64 size = align_formula(type_size_of(s, allocator, elem), align);
+		size += type_size_of(s, allocator, t_bool);
+		return align_formula(size, align);
+	}
+
+	case Type_Tuple: {
+		i64 count = t->Tuple.variable_count;
+		if (count == 0) {
+			return 0;
+		}
+		type_set_offsets(s, allocator, t);
+		i64 size = t->Tuple.offsets[count-1] + type_size_of(s, allocator, t->Tuple.variables[count-1]->type);
+		i64 align = type_align_of(s, allocator, t);
+		return align_formula(size, align);
+	} break;
+
+	case Type_Record: {
+		switch (t->Record.kind) {
+		case TypeRecord_Struct: {
+			i64 count = t->Record.field_count;
+			if (count == 0) {
+				return 0;
+			}
+			type_set_offsets(s, allocator, t);
+			i64 size = t->Record.struct_offsets[count-1] + type_size_of(s, allocator, t->Record.fields[count-1]->type);
+			i64 align = type_align_of(s, allocator, t);
+			return align_formula(size, align);
+		} break;
+
+		case TypeRecord_Union: {
+			i64 count = t->Record.field_count;
+			i64 max = 0;
+			// NOTE(bill): Zeroth field is invalid
+			for (isize i = 1; i < count; i++) {
+				i64 size = type_size_of(s, allocator, t->Record.fields[i]->type);
+				if (max < size) {
+					max = size;
+				}
+			}
+			// NOTE(bill): Align to int
+			i64 align = type_align_of(s, allocator, t);
+			isize size =  align_formula(max, s.word_size);
+			size += type_size_of(s, allocator, t_int);
+			return align_formula(size, align);
+		} break;
+
+		case TypeRecord_RawUnion: {
+			i64 count = t->Record.field_count;
+			i64 max = 0;
+			for (isize i = 0; i < count; i++) {
+				i64 size = type_size_of(s, allocator, t->Record.fields[i]->type);
+				if (max < size) {
+					max = size;
+				}
+			}
+			// TODO(bill): Is this how it should work?
+			i64 align = type_align_of(s, allocator, t);
+			return align_formula(max, align);
+		} break;
+
+		case TypeRecord_Enum: {
+			return type_size_of(s, allocator, t->Record.enum_base);
+		} break;
+		}
+	} break;
+	}
+
+	// Catch all
+	return s.word_size;
+}
+
+i64 type_offset_of(BaseTypeSizes s, gbAllocator allocator, Type *t, isize index) {
+	t = base_type(t);
+	if (t->kind == Type_Record && t->Record.kind == TypeRecord_Struct) {
+		type_set_offsets(s, allocator, t);
+		if (gb_is_between(index, 0, t->Record.field_count-1)) {
+			return t->Record.struct_offsets[index];
+		}
+	} else if (t->kind == Type_Tuple) {
+		type_set_offsets(s, allocator, t);
+		if (gb_is_between(index, 0, t->Tuple.variable_count-1)) {
+			return t->Tuple.offsets[index];
+		}
+	}  else if (t->kind == Type_Basic) {
+		if (t->Basic.kind == Basic_string) {
+			switch (index) {
+			case 0: return 0;
+			case 1: return s.word_size;
+			}
+		} else if (t->Basic.kind == Basic_any) {
+			switch (index) {
+			case 0: return 0;
+			case 1: return s.word_size;
+			}
+		}
+	} else if (t->kind == Type_Slice) {
+		switch (index) {
+		case 0: return 0;
+		case 1: return 1*s.word_size;
+		case 2: return 2*s.word_size;
+		}
+	}
+	return 0;
+}
+
+
+i64 type_offset_of_from_selection(BaseTypeSizes s, gbAllocator allocator, Type *type, Selection sel) {
+	GB_ASSERT(sel.indirect == false);
+
+	Type *t = type;
+	i64 offset = 0;
+	for_array(i, sel.index) {
+		isize index = sel.index.e[i];
+		t = base_type(t);
+		offset += type_offset_of(s, allocator, t, index);
+		if (t->kind == Type_Record && t->Record.kind == TypeRecord_Struct) {
+			t = t->Record.fields[index]->type;
+		} else {
+			// NOTE(bill): string/any/slices don't have record fields so this case doesn't need to be handled
+		}
+	}
+	return offset;
+}
+
+
+
+gbString write_type_to_string(gbString str, Type *type) {
+	if (type == NULL) {
+		return gb_string_appendc(str, "<no type>");
+	}
+
+	switch (type->kind) {
+	case Type_Basic:
+		str = gb_string_append_length(str, type->Basic.name.text, type->Basic.name.len);
+		break;
+
+	case Type_Pointer:
+		str = gb_string_appendc(str, "^");
+		str = write_type_to_string(str, type->Pointer.elem);
+		break;
+
+	case Type_Maybe:
+		str = gb_string_appendc(str, "?");
+		str = write_type_to_string(str, type->Maybe.elem);
+		break;
+
+	case Type_Array:
+		str = gb_string_appendc(str, gb_bprintf("[%td]", type->Array.count));
+		str = write_type_to_string(str, type->Array.elem);
+		break;
+
+	case Type_Vector:
+		str = gb_string_appendc(str, gb_bprintf("{%td}", type->Vector.count));
+		str = write_type_to_string(str, type->Vector.elem);
+		break;
+
+	case Type_Slice:
+		str = gb_string_appendc(str, "[]");
+		str = write_type_to_string(str, type->Array.elem);
+		break;
+
+	case Type_Record: {
+		switch (type->Record.kind) {
+		case TypeRecord_Struct:
+			str = gb_string_appendc(str, "struct");
+			if (type->Record.struct_is_packed) {
+				str = gb_string_appendc(str, " #packed");
+			}
+			if (type->Record.struct_is_ordered) {
+				str = gb_string_appendc(str, " #ordered");
+			}
+			str = gb_string_appendc(str, " {");
+			for (isize i = 0; i < type->Record.field_count; i++) {
+				Entity *f = type->Record.fields[i];
+				GB_ASSERT(f->kind == Entity_Variable);
+				if (i > 0)
+					str = gb_string_appendc(str, "; ");
+				str = gb_string_append_length(str, f->token.string.text, f->token.string.len);
+				str = gb_string_appendc(str, ": ");
+				str = write_type_to_string(str, f->type);
+			}
+			str = gb_string_appendc(str, "}");
+			break;
+
+		case TypeRecord_Union:
+			str = gb_string_appendc(str, "union{");
+			for (isize i = 1; i < type->Record.field_count; i++) {
+				Entity *f = type->Record.fields[i];
+				GB_ASSERT(f->kind == Entity_TypeName);
+				if (i > 1) {
+					str = gb_string_appendc(str, "; ");
+				}
+				str = gb_string_append_length(str, f->token.string.text, f->token.string.len);
+				str = gb_string_appendc(str, ": ");
+				str = write_type_to_string(str, base_type(f->type));
+			}
+			str = gb_string_appendc(str, "}");
+			break;
+
+		case TypeRecord_RawUnion:
+			str = gb_string_appendc(str, "raw_union{");
+			for (isize i = 0; i < type->Record.field_count; i++) {
+				Entity *f = type->Record.fields[i];
+				GB_ASSERT(f->kind == Entity_Variable);
+				if (i > 0) {
+					str = gb_string_appendc(str, ", ");
+				}
+				str = gb_string_append_length(str, f->token.string.text, f->token.string.len);
+				str = gb_string_appendc(str, ": ");
+				str = write_type_to_string(str, f->type);
+			}
+			str = gb_string_appendc(str, "}");
+			break;
+
+		case TypeRecord_Enum:
+			str = gb_string_appendc(str, "enum ");
+			str = write_type_to_string(str, type->Record.enum_base);
+			break;
+		}
+	} break;
+
+
+	case Type_Named:
+		if (type->Named.type_name != NULL) {
+			str = gb_string_append_length(str, type->Named.name.text, type->Named.name.len);
+		} else {
+			// NOTE(bill): Just in case
+			str = gb_string_appendc(str, "<named type>");
+		}
+		break;
+
+	case Type_Tuple:
+		if (type->Tuple.variable_count > 0) {
+			for (isize i = 0; i < type->Tuple.variable_count; i++) {
+				Entity *var = type->Tuple.variables[i];
+				if (var != NULL) {
+					GB_ASSERT(var->kind == Entity_Variable);
+					if (i > 0)
+						str = gb_string_appendc(str, ", ");
+					str = write_type_to_string(str, var->type);
+				}
+			}
+		}
+		break;
+
+	case Type_Proc:
+		str = gb_string_appendc(str, "proc(");
+		if (type->Proc.params)
+			str = write_type_to_string(str, type->Proc.params);
+		str = gb_string_appendc(str, ")");
+		if (type->Proc.results) {
+			str = gb_string_appendc(str, " -> ");
+			str = write_type_to_string(str, type->Proc.results);
+		}
+		break;
+	}
+
+	return str;
+}
+
+
+gbString type_to_string(Type *type) {
+	gbString str = gb_string_make(gb_heap_allocator(), "");
+	return write_type_to_string(str, type);
+}
+
+

+ 195 - 0
src/common.c

@@ -0,0 +1,195 @@
+#define GB_NO_DEFER
+#define GB_IMPLEMENTATION
+#include "gb/gb.h"
+
+gbAllocator heap_allocator(void) {
+	return gb_heap_allocator();
+}
+
+#include "string.c"
+#include "array.c"
+
+gb_global String global_module_path = {0};
+gb_global bool global_module_path_set = false;
+
+
+String get_module_dir() {
+	if (global_module_path_set) {
+		return global_module_path;
+	}
+
+	Array(wchar_t) path_buf;
+	array_init_count(&path_buf, heap_allocator(), 300);
+
+	isize len = 0;
+	for (;;) {
+		len = GetModuleFileNameW(NULL, &path_buf.e[0], path_buf.count);
+		if (len == 0) {
+			return make_string(NULL, 0);
+		}
+		if (len < path_buf.count) {
+			break;
+		}
+		array_resize(&path_buf, 2*path_buf.count + 300);
+	}
+
+	gbTempArenaMemory tmp = gb_temp_arena_memory_begin(&string_buffer_arena);
+
+	wchar_t *text = gb_alloc_array(string_buffer_allocator, wchar_t, len+1);
+
+	GetModuleFileNameW(NULL, text, len);
+	String path = string16_to_string(heap_allocator(), make_string16(text, len));
+	for (isize i = path.len-1; i >= 0; i--) {
+		u8 c = path.text[i];
+		if (c == '/' || c == '\\') {
+			break;
+		}
+		path.len--;
+	}
+
+	global_module_path = path;
+	global_module_path_set = true;
+
+	gb_temp_arena_memory_end(tmp);
+
+	array_free(&path_buf);
+
+	return path;
+}
+
+String path_to_fullpath(gbAllocator a, String s) {
+	gbTempArenaMemory tmp = gb_temp_arena_memory_begin(&string_buffer_arena);
+	String16 string16 = string_to_string16(string_buffer_allocator, s);
+	String result = {0};
+
+	DWORD len = GetFullPathNameW(string16.text, 0, NULL, NULL);
+	if (len != 0) {
+		wchar_t *text = gb_alloc_array(string_buffer_allocator, wchar_t, len+1);
+		GetFullPathNameW(string16.text, len, text, NULL);
+		text[len] = 0;
+		result = string16_to_string(a, make_string16(text, len));
+	}
+	gb_temp_arena_memory_end(tmp);
+	return result;
+}
+
+i64 next_pow2(i64 n) {
+	if (n <= 0) {
+		return 0;
+	}
+	n--;
+	n |= n >> 1;
+	n |= n >> 2;
+	n |= n >> 4;
+	n |= n >> 8;
+	n |= n >> 16;
+	n |= n >> 32;
+	n++;
+	return n;
+}
+
+i64 prev_pow2(i64 n) {
+	if (n <= 0) {
+		return 0;
+	}
+	n |= n >> 1;
+	n |= n >> 2;
+	n |= n >> 4;
+	n |= n >> 8;
+	n |= n >> 16;
+	n |= n >> 32;
+	return n - (n >> 1);
+}
+
+i16 f32_to_f16(f32 value) {
+	union { u32 i; f32 f; } v;
+	i32 i, s, e, m;
+
+	v.f = value;
+	i = (i32)v.i;
+
+	s =  (i >> 16) & 0x00008000;
+	e = ((i >> 23) & 0x000000ff) - (127 - 15);
+	m =   i        & 0x007fffff;
+
+
+	if (e <= 0) {
+		if (e < -10) return cast(i16)s;
+		m = (m | 0x00800000) >> (1 - e);
+
+		if (m & 0x00001000)
+			m += 0x00002000;
+
+		return cast(i16)(s | (m >> 13));
+	} else if (e == 0xff - (127 - 15)) {
+		if (m == 0) {
+			return cast(i16)(s | 0x7c00); /* NOTE(bill): infinity */
+		} else {
+			/* NOTE(bill): NAN */
+			m >>= 13;
+			return cast(i16)(s | 0x7c00 | m | (m == 0));
+		}
+	} else {
+		if (m & 0x00001000) {
+			m += 0x00002000;
+			if (m & 0x00800000) {
+				m = 0;
+				e += 1;
+			}
+		}
+
+		if (e > 30) {
+			float volatile f = 1e12f;
+			int j;
+			for (j = 0; j < 10; j++)
+				f *= f; /* NOTE(bill): Cause overflow */
+
+			return cast(i16)(s | 0x7c00);
+		}
+
+		return cast(i16)(s | (e << 10) | (m >> 13));
+	}
+}
+
+
+
+#define for_array(index_, array_) for (isize index_ = 0; index_ < (array_).count; index_++)
+
+
+// Doubly Linked Lists
+
+#define DLIST_SET(curr_element, next_element)  do { \
+	(curr_element)->next = (next_element);             \
+	(curr_element)->next->prev = (curr_element);       \
+	(curr_element) = (curr_element)->next;             \
+} while (0)
+
+#define DLIST_APPEND(root_element, curr_element, next_element) do { \
+	if ((root_element) == NULL) { \
+		(root_element) = (curr_element) = (next_element); \
+	} else { \
+		DLIST_SET(curr_element, next_element); \
+	} \
+} while (0)
+
+////////////////////////////////////////////////////////////////
+//
+// Generic Data Structures
+//
+////////////////////////////////////////////////////////////////
+
+
+#define MAP_TYPE String
+#define MAP_PROC map_string_
+#define MAP_NAME MapString
+#include "map.c"
+
+#define MAP_TYPE bool
+#define MAP_PROC map_bool_
+#define MAP_NAME MapBool
+#include "map.c"
+
+#define MAP_TYPE isize
+#define MAP_PROC map_isize_
+#define MAP_NAME MapIsize
+#include "map.c"

+ 400 - 0
src/exact_value.c

@@ -0,0 +1,400 @@
+#include <math.h>
+
+// TODO(bill): Big numbers
+// IMPORTANT TODO(bill): This needs to be completely fixed!!!!!!!!
+
+typedef struct AstNode AstNode;
+
+typedef enum ExactValueKind {
+	ExactValue_Invalid,
+
+	ExactValue_Bool,
+	ExactValue_String,
+	ExactValue_Integer,
+	ExactValue_Float,
+	ExactValue_Pointer,
+	ExactValue_Compound, // TODO(bill): Is this good enough?
+
+	ExactValue_Count,
+} ExactValueKind;
+
+typedef struct ExactValue {
+	ExactValueKind kind;
+	union {
+		bool      value_bool;
+		String   value_string;
+		i64      value_integer; // NOTE(bill): This must be an integer and not a pointer
+		f64      value_float;
+		i64      value_pointer;
+		AstNode *value_compound;
+	};
+} ExactValue;
+
+HashKey hash_exact_value(ExactValue v) {
+	return hashing_proc(&v, gb_size_of(ExactValue));
+}
+
+
+ExactValue make_exact_value_compound(AstNode *node) {
+	ExactValue result = {ExactValue_Compound};
+	result.value_compound = node;
+	return result;
+}
+
+ExactValue make_exact_value_bool(bool b) {
+	ExactValue result = {ExactValue_Bool};
+	result.value_bool = (b != 0);
+	return result;
+}
+
+ExactValue make_exact_value_string(String string) {
+	// TODO(bill): Allow for numbers with underscores in them
+	ExactValue result = {ExactValue_String};
+	result.value_string = string;
+	return result;
+}
+
+ExactValue make_exact_value_integer_from_string(String string) {
+	// TODO(bill): Allow for numbers with underscores in them
+	ExactValue result = {ExactValue_Integer};
+	i32 base = 10;
+	if (string.text[0] == '0') {
+		switch (string.text[1]) {
+		case 'b': base = 2;  break;
+		case 'o': base = 8;  break;
+		case 'd': base = 10; break;
+		case 'x': base = 16; break;
+		}
+	}
+
+	result.value_integer = gb_str_to_i64(cast(char *)string.text, NULL, base);
+
+	return result;
+}
+
+ExactValue make_exact_value_integer(i64 i) {
+	ExactValue result = {ExactValue_Integer};
+	result.value_integer = i;
+	return result;
+}
+
+ExactValue make_exact_value_float_from_string(String string) {
+	// TODO(bill): Allow for numbers with underscores in them
+	ExactValue result = {ExactValue_Float};
+	result.value_float = gb_str_to_f64(cast(char *)string.text, NULL);
+	return result;
+}
+
+ExactValue make_exact_value_float(f64 f) {
+	ExactValue result = {ExactValue_Float};
+	result.value_float = f;
+	return result;
+}
+
+ExactValue make_exact_value_pointer(i64 ptr) {
+	ExactValue result = {ExactValue_Pointer};
+	result.value_pointer = ptr;
+	return result;
+}
+
+ExactValue make_exact_value_from_basic_literal(Token token) {
+	switch (token.kind) {
+	case Token_String:  return make_exact_value_string(token.string);
+	case Token_Integer: return make_exact_value_integer_from_string(token.string);
+	case Token_Float:   return make_exact_value_float_from_string(token.string);
+	case Token_Rune: {
+		Rune r = GB_RUNE_INVALID;
+		gb_utf8_decode(token.string.text, token.string.len, &r);
+		// gb_printf("%.*s rune: %d\n", LIT(token.string), r);
+		return make_exact_value_integer(r);
+	}
+	default:
+		GB_PANIC("Invalid token for basic literal");
+		break;
+	}
+
+	ExactValue result = {ExactValue_Invalid};
+	return result;
+}
+
+ExactValue exact_value_to_integer(ExactValue v) {
+	switch (v.kind) {
+	case ExactValue_Integer:
+		return v;
+	case ExactValue_Float: {
+		i64 i = cast(i64)v.value_float;
+		f64 f = cast(f64)i;
+		if (f == v.value_float) {
+			return make_exact_value_integer(i);
+		}
+	} break;
+
+	case ExactValue_Pointer:
+		return make_exact_value_integer(cast(i64)cast(intptr)v.value_pointer);
+	}
+	ExactValue r = {ExactValue_Invalid};
+	return r;
+}
+
+ExactValue exact_value_to_float(ExactValue v) {
+	switch (v.kind) {
+	case ExactValue_Integer:
+		return make_exact_value_float(cast(i64)v.value_integer);
+	case ExactValue_Float:
+		return v;
+	}
+	ExactValue r = {ExactValue_Invalid};
+	return r;
+}
+
+
+ExactValue exact_unary_operator_value(Token op, ExactValue v, i32 precision) {
+	switch (op.kind) {
+	case Token_Add:	{
+		switch (v.kind) {
+		case ExactValue_Invalid:
+		case ExactValue_Integer:
+		case ExactValue_Float:
+			return v;
+		}
+	} break;
+
+	case Token_Sub:	{
+		switch (v.kind) {
+		case ExactValue_Invalid:
+			return v;
+		case ExactValue_Integer: {
+			ExactValue i = v;
+			i.value_integer = -i.value_integer;
+			return i;
+		}
+		case ExactValue_Float: {
+			ExactValue i = v;
+			i.value_float = -i.value_float;
+			return i;
+		}
+		}
+	} break;
+
+	case Token_Xor: {
+		i64 i = 0;
+		switch (v.kind) {
+		case ExactValue_Invalid:
+			return v;
+		case ExactValue_Integer:
+			i = v.value_integer;
+			i = ~i;
+			break;
+		default:
+			goto failure;
+		}
+
+		// NOTE(bill): unsigned integers will be negative and will need to be
+		// limited to the types precision
+		if (precision > 0)
+			i &= ~((~0ll)<<precision);
+
+		return make_exact_value_integer(i);
+	} break;
+
+	case Token_Not: {
+		switch (v.kind) {
+		case ExactValue_Invalid: return v;
+		case ExactValue_Bool:
+			return make_exact_value_bool(!v.value_bool);
+		}
+	} break;
+	}
+
+failure:
+	GB_PANIC("Invalid unary operation, %.*s", LIT(token_strings[op.kind]));
+
+	ExactValue error_value = {0};
+	return error_value;
+}
+
+// NOTE(bill): Make sure things are evaluated in correct order
+i32 exact_value_order(ExactValue v) {
+	switch (v.kind) {
+	case ExactValue_Invalid:
+		return 0;
+	case ExactValue_Bool:
+	case ExactValue_String:
+		return 1;
+	case ExactValue_Integer:
+		return 2;
+	case ExactValue_Float:
+		return 3;
+	case ExactValue_Pointer:
+		return 4;
+
+	default:
+		GB_PANIC("How'd you get here? Invalid Value.kind");
+		return -1;
+	}
+}
+
+void match_exact_values(ExactValue *x, ExactValue *y) {
+	if (exact_value_order(*y) < exact_value_order(*x)) {
+		match_exact_values(y, x);
+		return;
+	}
+
+	switch (x->kind) {
+	case ExactValue_Invalid:
+		*y = *x;
+		return;
+
+	case ExactValue_Bool:
+	case ExactValue_String:
+		return;
+
+	case ExactValue_Integer:
+		switch (y->kind) {
+		case ExactValue_Integer:
+			return;
+		case ExactValue_Float:
+			// TODO(bill): Is this good enough?
+			*x = make_exact_value_float(cast(f64)x->value_integer);
+			return;
+		}
+		break;
+
+	case ExactValue_Float:
+		if (y->kind == ExactValue_Float)
+			return;
+		break;
+	}
+
+	compiler_error("How'd you get here? Invalid ExactValueKind");
+}
+
+// TODO(bill): Allow for pointer arithmetic? Or are pointer slices good enough?
+ExactValue exact_binary_operator_value(Token op, ExactValue x, ExactValue y) {
+	match_exact_values(&x, &y);
+
+	switch (x.kind) {
+	case ExactValue_Invalid:
+		return x;
+
+	case ExactValue_Bool:
+		switch (op.kind) {
+		case Token_CmpAnd: return make_exact_value_bool(x.value_bool && y.value_bool);
+		case Token_CmpOr:  return make_exact_value_bool(x.value_bool || y.value_bool);
+		case Token_And:    return make_exact_value_bool(x.value_bool & y.value_bool);
+		case Token_Or:     return make_exact_value_bool(x.value_bool | y.value_bool);
+		default: goto error;
+		}
+		break;
+
+	case ExactValue_Integer: {
+		i64 a = x.value_integer;
+		i64 b = y.value_integer;
+		i64 c = 0;
+		switch (op.kind) {
+		case Token_Add:    c = a + b;  break;
+		case Token_Sub:    c = a - b;  break;
+		case Token_Mul:    c = a * b;  break;
+		case Token_Quo:    return make_exact_value_float(fmod(cast(f64)a, cast(f64)b));
+		case Token_QuoEq:  c = a / b;  break; // NOTE(bill): Integer division
+		case Token_Mod:    c = a % b;  break;
+		case Token_And:    c = a & b;  break;
+		case Token_Or:     c = a | b;  break;
+		case Token_Xor:    c = a ^ b;  break;
+		case Token_AndNot: c = a&(~b); break;
+		case Token_Shl:    c = a << b; break;
+		case Token_Shr:    c = a >> b; break;
+		default: goto error;
+		}
+		return make_exact_value_integer(c);
+	} break;
+
+	case ExactValue_Float: {
+		f64 a = x.value_float;
+		f64 b = y.value_float;
+		switch (op.kind) {
+		case Token_Add: return make_exact_value_float(a + b);
+		case Token_Sub: return make_exact_value_float(a - b);
+		case Token_Mul: return make_exact_value_float(a * b);
+		case Token_Quo: return make_exact_value_float(a / b);
+		default: goto error;
+		}
+	} break;
+	}
+
+error:
+	ExactValue error_value = {0};
+	// gb_printf_err("Invalid binary operation: %s\n", token_kind_to_string(op.kind));
+	return error_value;
+}
+
+gb_inline ExactValue exact_value_add(ExactValue x, ExactValue y) { Token op = {Token_Add};        return exact_binary_operator_value(op, x, y); }
+gb_inline ExactValue exact_value_sub(ExactValue x, ExactValue y) { Token op = {Token_Sub};        return exact_binary_operator_value(op, x, y); }
+gb_inline ExactValue exact_value_mul(ExactValue x, ExactValue y) { Token op = {Token_Mul};        return exact_binary_operator_value(op, x, y); }
+gb_inline ExactValue exact_value_quo(ExactValue x, ExactValue y) { Token op = {Token_Quo};        return exact_binary_operator_value(op, x, y); }
+gb_inline ExactValue exact_value_shift(Token op, ExactValue x, ExactValue y) { return exact_binary_operator_value(op, x, y); }
+
+
+i32 cmp_f64(f64 a, f64 b) {
+	return (a > b) - (a < b);
+}
+
+bool compare_exact_values(Token op, ExactValue x, ExactValue y) {
+	match_exact_values(&x, &y);
+
+	switch (x.kind) {
+	case ExactValue_Invalid:
+		return false;
+
+	case ExactValue_Bool:
+		switch (op.kind) {
+		case Token_CmpEq: return x.value_bool == y.value_bool;
+		case Token_NotEq: return x.value_bool != y.value_bool;
+		}
+		break;
+
+	case ExactValue_Integer: {
+		i64 a = x.value_integer;
+		i64 b = y.value_integer;
+		switch (op.kind) {
+		case Token_CmpEq: return a == b;
+		case Token_NotEq: return a != b;
+		case Token_Lt:    return a <  b;
+		case Token_LtEq:  return a <= b;
+		case Token_Gt:    return a >  b;
+		case Token_GtEq:  return a >= b;
+		}
+	} break;
+
+	case ExactValue_Float: {
+		f64 a = x.value_float;
+		f64 b = y.value_float;
+		switch (op.kind) {
+		case Token_CmpEq: return cmp_f64(a, b) == 0;
+		case Token_NotEq: return cmp_f64(a, b) != 0;
+		case Token_Lt:    return cmp_f64(a, b) <  0;
+		case Token_LtEq:  return cmp_f64(a, b) <= 0;
+		case Token_Gt:    return cmp_f64(a, b) >  0;
+		case Token_GtEq:  return cmp_f64(a, b) >= 0;
+		}
+	} break;
+
+	case ExactValue_String: {
+		String a = x.value_string;
+		String b = y.value_string;
+		isize len = gb_min(a.len, b.len);
+		// TODO(bill): gb_memcompare is used because the strings are UTF-8
+		switch (op.kind) {
+		case Token_CmpEq: return gb_memcompare(a.text, b.text, len) == 0;
+		case Token_NotEq: return gb_memcompare(a.text, b.text, len) != 0;
+		case Token_Lt:    return gb_memcompare(a.text, b.text, len) <  0;
+		case Token_LtEq:  return gb_memcompare(a.text, b.text, len) <= 0;
+		case Token_Gt:    return gb_memcompare(a.text, b.text, len) >  0;
+		case Token_GtEq:  return gb_memcompare(a.text, b.text, len) >= 0;
+		}
+	} break;
+	}
+
+	GB_PANIC("Invalid comparison");
+	return false;
+}

+ 272 - 0
src/main.c

@@ -0,0 +1,272 @@
+#if defined(__cplusplus)
+extern "C" {
+#endif
+#define VERSION_STRING "v0.0.3c"
+
+#include "common.c"
+#include "timings.c"
+#include "unicode.c"
+#include "tokenizer.c"
+#include "parser.c"
+// #include "printer.c"
+#include "checker/checker.c"
+#include "ssa.c"
+#include "ssa_opt.c"
+#include "ssa_print.c"
+// #include "vm.c"
+
+// NOTE(bill): `name` is used in debugging and profiling modes
+i32 win32_exec_command_line_app(char *name, char *fmt, ...) {
+	STARTUPINFOW start_info = {gb_size_of(STARTUPINFOW)};
+	PROCESS_INFORMATION pi = {0};
+	char cmd_line[4096] = {0};
+	isize cmd_len;
+	va_list va;
+	gbTempArenaMemory tmp;
+	String16 cmd;
+	i32 exit_code = 0;
+
+	start_info.dwFlags = STARTF_USESTDHANDLES | STARTF_USESHOWWINDOW;
+	start_info.wShowWindow = SW_SHOW;
+	start_info.hStdInput   = GetStdHandle(STD_INPUT_HANDLE);
+	start_info.hStdOutput  = GetStdHandle(STD_OUTPUT_HANDLE);
+	start_info.hStdError   = GetStdHandle(STD_ERROR_HANDLE);
+
+	va_start(va, fmt);
+	cmd_len = gb_snprintf_va(cmd_line, gb_size_of(cmd_line), fmt, va);
+	va_end(va);
+	// gb_printf("%.*s\n", cast(int)cmd_len, cmd_line);
+
+	tmp = gb_temp_arena_memory_begin(&string_buffer_arena);
+
+	cmd = string_to_string16(string_buffer_allocator, make_string(cast(u8 *)cmd_line, cmd_len-1));
+
+	if (CreateProcessW(NULL, cmd.text,
+	                   NULL, NULL, true, 0, NULL, NULL,
+	                   &start_info, &pi)) {
+		WaitForSingleObject(pi.hProcess, INFINITE);
+		GetExitCodeProcess(pi.hProcess, cast(DWORD *)&exit_code);
+
+		CloseHandle(pi.hProcess);
+		CloseHandle(pi.hThread);
+	} else {
+		// NOTE(bill): failed to create process
+		gb_printf_err("Failed to execute command:\n\t%s\n", cmd_line);
+		exit_code = -1;
+	}
+
+	gb_temp_arena_memory_end(tmp);
+	return exit_code;
+}
+
+typedef enum ArchKind {
+	ArchKind_x64,
+	ArchKind_x86,
+} ArchKind;
+
+typedef struct ArchData {
+	BaseTypeSizes sizes;
+	String llc_flags;
+	String link_flags;
+} ArchData;
+
+ArchData make_arch_data(ArchKind kind) {
+	ArchData data = {0};
+
+	switch (kind) {
+	case ArchKind_x64:
+	default:
+		data.sizes.word_size = 8;
+		data.sizes.max_align = 16;
+		data.llc_flags = str_lit("-march=x86-64 ");
+		data.link_flags = str_lit("/machine:x64 ");
+		break;
+
+	case ArchKind_x86:
+		data.sizes.word_size = 4;
+		data.sizes.max_align = 8;
+		data.llc_flags = str_lit("-march=x86 ");
+		data.link_flags = str_lit("/machine:x86 ");
+		break;
+	}
+
+	return data;
+}
+
+void usage(char *argv0) {
+	gb_printf_err("%s is a tool for managing Odin source code\n", argv0);
+	gb_printf_err("Usage:");
+	gb_printf_err("\n\t%s command [arguments]\n", argv0);
+	gb_printf_err("Commands:");
+	gb_printf_err("\n\tbuild   compile .odin file");
+	gb_printf_err("\n\trun     compile and run .odin file");
+	gb_printf_err("\n\tversion print Odin version");
+	gb_printf_err("\n\n");
+}
+
+int main(int argc, char **argv) {
+	if (argc < 2) {
+		usage(argv[0]);
+		return 1;
+	}
+
+	Timings timings = {0};
+	timings_init(&timings, str_lit("Total Time"), 128);
+	// defer (timings_destroy(&timings));
+
+#if 1
+	init_string_buffer_memory();
+	init_global_error_collector();
+
+	String module_dir = get_module_dir();
+
+	init_universal_scope();
+
+	char *init_filename = NULL;
+	bool run_output = false;
+	String arg1 = make_string_c(argv[1]);
+	if (str_eq(arg1, str_lit("run"))) {
+		run_output = true;
+		init_filename = argv[2];
+	} else if (str_eq(arg1, str_lit("build"))) {
+		init_filename = argv[2];
+	} else if (str_eq(arg1, str_lit("version"))) {
+		gb_printf("%s version %s", argv[0], VERSION_STRING);
+		return 0;
+	} else {
+		usage(argv[0]);
+		return 1;
+	}
+
+	// TODO(bill): prevent compiling without a linker
+
+	timings_start_section(&timings, str_lit("parse files"));
+
+	Parser parser = {0};
+	if (!init_parser(&parser)) {
+		return 1;
+	}
+	// defer (destroy_parser(&parser));
+
+	if (parse_files(&parser, init_filename) != ParseFile_None) {
+		return 1;
+	}
+
+
+#if 1
+	timings_start_section(&timings, str_lit("type check"));
+
+	Checker checker = {0};
+	ArchData arch_data = make_arch_data(ArchKind_x64);
+
+	init_checker(&checker, &parser, arch_data.sizes);
+	// defer (destroy_checker(&checker));
+
+	check_parsed_files(&checker);
+
+
+#endif
+#if 1
+
+	ssaGen ssa = {0};
+	if (!ssa_gen_init(&ssa, &checker)) {
+		return 1;
+	}
+	// defer (ssa_gen_destroy(&ssa));
+
+	timings_start_section(&timings, str_lit("ssa gen"));
+	ssa_gen_tree(&ssa);
+
+	timings_start_section(&timings, str_lit("ssa opt"));
+	ssa_opt_tree(&ssa);
+
+	timings_start_section(&timings, str_lit("ssa print"));
+	ssa_print_llvm_ir(&ssa);
+
+	// prof_print_all();
+
+#if 1
+	timings_start_section(&timings, str_lit("llvm-opt"));
+
+	char const *output_name = ssa.output_file.filename;
+	isize base_name_len = gb_path_extension(output_name)-1 - output_name;
+	String output = make_string(cast(u8 *)output_name, base_name_len);
+
+	i32 optimization_level = 0;
+	optimization_level = gb_clamp(optimization_level, 0, 3);
+
+	i32 exit_code = 0;
+	// For more passes arguments: http://llvm.org/docs/Passes.html
+	exit_code = win32_exec_command_line_app("llvm-opt",
+		"%.*sbin/opt %s -o %.*s.bc "
+		"-mem2reg "
+		"-memcpyopt "
+		"-die "
+		// "-dse "
+		// "-dce "
+		// "-S "
+		"",
+		LIT(module_dir),
+		output_name, LIT(output));
+	if (exit_code != 0) {
+		return exit_code;
+	}
+
+	#if 1
+	timings_start_section(&timings, str_lit("llvm-llc"));
+	// For more arguments: http://llvm.org/docs/CommandGuide/llc.html
+	exit_code = win32_exec_command_line_app("llvm-llc",
+		"%.*sbin/llc %.*s.bc -filetype=obj -O%d "
+		"%.*s "
+		// "-debug-pass=Arguments "
+		"",
+		LIT(module_dir),
+		LIT(output),
+		optimization_level,
+		LIT(arch_data.llc_flags));
+	if (exit_code != 0) {
+		return exit_code;
+	}
+
+	timings_start_section(&timings, str_lit("msvc-link"));
+
+	gbString lib_str = gb_string_make(heap_allocator(), "Kernel32.lib");
+	// defer (gb_string_free(lib_str));
+	char lib_str_buf[1024] = {0};
+	for_array(i, parser.foreign_libraries) {
+		String lib = parser.foreign_libraries.e[i];
+		isize len = gb_snprintf(lib_str_buf, gb_size_of(lib_str_buf),
+		                        " %.*s.lib", LIT(lib));
+		lib_str = gb_string_appendc(lib_str, lib_str_buf);
+	}
+
+	exit_code = win32_exec_command_line_app("msvc-link",
+		"link %.*s.obj -OUT:%.*s.exe %s "
+		"/defaultlib:libcmt "
+		"/nologo /incremental:no /opt:ref /subsystem:console "
+		" %.*s "
+		"",
+		LIT(output), LIT(output),
+		lib_str, LIT(arch_data.link_flags));
+	if (exit_code != 0) {
+		return exit_code;
+	}
+
+	// timings_print_all(&timings);
+
+	if (run_output) {
+		win32_exec_command_line_app("odin run",
+			"%.*s.exe", cast(int)base_name_len, output_name);
+	}
+	#endif
+#endif
+#endif
+#endif
+
+
+	return 0;
+}
+
+#if defined(__cplusplus)
+}
+#endif

+ 1305 - 0
src/old_vm.c

@@ -0,0 +1,1305 @@
+// TODO(bill): COMPLETELY REWORK THIS ENTIRE INTERPRETER
+#include "dyncall/include/dyncall.h"
+
+struct VirtualMachine;
+
+struct vmValueProc {
+	ssaProcedure *proc; // If `NULL`, use `ptr` instead and call external procedure
+	void *        ptr;
+};
+
+
+struct vmValue {
+	// NOTE(bill): Shouldn't need to store type here as the type checking
+	// has already been handled in the SSA
+	union {
+		f32            val_f32;
+		f64            val_f64;
+		void *         val_ptr;
+		i64            val_int;
+		vmValueProc    val_proc;
+	};
+	Array<vmValue> val_comp; // NOTE(bill): Will be freed through "stack"
+	Type *type;
+};
+
+vmValue vm_make_value_ptr(Type *type, void *ptr) {
+	GB_ASSERT(is_type_pointer(type));
+	vmValue v = {0};
+	v.type = default_type(type);
+	v.val_ptr = ptr;
+	return v;
+}
+vmValue vm_make_value_int(Type *type, i64 i) {
+	GB_ASSERT(is_type_integer(type) ||
+	          is_type_boolean(type) ||
+	          is_type_enum(type));
+	vmValue v = {0};
+	v.type = default_type(type);
+	v.val_int = i;
+	return v;
+}
+vmValue vm_make_value_f32(Type *type, f32 f) {
+	GB_ASSERT(is_type_f32(type));
+	vmValue v = {0};
+	v.type = default_type(type);
+	v.val_f32 = f;
+	return v;
+}
+vmValue vm_make_value_f64(Type *type, f64 f) {
+	GB_ASSERT(is_type_f64(type));
+	vmValue v = {0};
+	v.type = default_type(type);
+	v.val_f64 = f;
+	return v;
+}
+vmValue vm_make_value_comp(Type *type, gbAllocator allocator, isize count) {
+	GB_ASSERT(is_type_string(type)    ||
+	          is_type_any   (type)    ||
+	          is_type_array (type)    ||
+	          is_type_vector(type)    ||
+	          is_type_slice (type)    ||
+	          is_type_maybe (type)    ||
+	          is_type_struct(type)    ||
+	          is_type_union(type)     ||
+	          is_type_raw_union(type) ||
+	          is_type_tuple (type)    ||
+	          is_type_proc  (type));
+	vmValue v = {0};
+	v.type = default_type(type);
+	array_init_count(&v.val_comp, allocator, count);
+	return v;
+}
+
+
+
+
+
+
+struct vmFrame {
+	VirtualMachine *  vm;
+	vmFrame *         caller;
+	ssaProcedure *    curr_proc;
+	ssaBlock *        prev_block;
+	ssaBlock *        curr_block;
+	i32               instr_index; // For the current block
+
+	Map<vmValue>      values; // Key: ssaValue *
+	gbTempArenaMemory temp_arena_memory;
+	gbAllocator       stack_allocator;
+	Array<void *>     locals; // Memory to locals
+	vmValue           result;
+};
+
+struct VirtualMachine {
+	ssaModule *         module;
+	gbArena             stack_arena;
+	gbAllocator         stack_allocator;
+	gbAllocator         heap_allocator;
+	Array<vmFrame>      frame_stack;
+	Map<vmValue>        globals;             // Key: ssaValue *
+	Map<vmValue>        const_compound_lits; // Key: ssaValue *
+	vmValue             exit_value;
+};
+
+void    vm_exec_instr   (VirtualMachine *vm, ssaValue *value);
+vmValue vm_operand_value(VirtualMachine *vm, ssaValue *value);
+void    vm_store        (VirtualMachine *vm, void *dst, vmValue val, Type *type);
+vmValue vm_load         (VirtualMachine *vm, void *ptr, Type *type);
+void    vm_print_value  (vmValue value, Type *type);
+
+void vm_jump_block(vmFrame *f, ssaBlock *target) {
+	f->prev_block = f->curr_block;
+	f->curr_block = target;
+	f->instr_index = 0;
+}
+
+
+vmFrame *vm_back_frame(VirtualMachine *vm) {
+	if (vm->frame_stack.count > 0) {
+		return &vm->frame_stack[vm->frame_stack.count-1];
+	}
+	return NULL;
+}
+
+i64 vm_type_size_of(VirtualMachine *vm, Type *type) {
+	return type_size_of(vm->module->sizes, vm->heap_allocator, type);
+}
+i64 vm_type_align_of(VirtualMachine *vm, Type *type) {
+	return type_align_of(vm->module->sizes, vm->heap_allocator, type);
+}
+i64 vm_type_offset_of(VirtualMachine *vm, Type *type, i64 index) {
+	return type_offset_of(vm->module->sizes, vm->heap_allocator, type, index);
+}
+
+
+void vm_init(VirtualMachine *vm, ssaModule *module) {
+	gb_arena_init_from_allocator(&vm->stack_arena, heap_allocator(), gb_megabytes(64));
+
+	vm->module = module;
+	vm->stack_allocator = gb_arena_allocator(&vm->stack_arena);
+	vm->heap_allocator = heap_allocator();
+	array_init(&vm->frame_stack, vm->heap_allocator);
+	map_init(&vm->globals, vm->heap_allocator);
+	map_init(&vm->const_compound_lits, vm->heap_allocator);
+
+	for_array(i, vm->module->values.entries) {
+		ssaValue *v = vm->module->values.entries[i].value;
+		switch (v->kind) {
+		case ssaValue_Global: {
+			Type *t = ssa_type(v);
+			GB_ASSERT(is_type_pointer(t));
+			i64 size  = vm_type_size_of(vm, t);
+			i64 align = vm_type_align_of(vm, t);
+			void *mem = gb_alloc_align(vm->heap_allocator, size, align);
+			if (v->Global.value != NULL && v->Global.value->kind == ssaValue_Constant) {
+				vm_store(vm, mem, vm_operand_value(vm, v->Global.value), type_deref(t));
+			}
+			map_set(&vm->globals, hash_pointer(v), vm_make_value_ptr(t, mem));
+		} break;
+		}
+	}
+
+}
+void vm_destroy(VirtualMachine *vm) {
+	array_free(&vm->frame_stack);
+	map_destroy(&vm->globals);
+	map_destroy(&vm->const_compound_lits);
+	gb_arena_free(&vm->stack_arena);
+}
+
+
+
+
+
+
+void vm_set_value(vmFrame *f, ssaValue *v, vmValue val) {
+	if (v != NULL) {
+		GB_ASSERT(ssa_type(v) != NULL);
+		map_set(&f->values, hash_pointer(v), val);
+	}
+}
+
+
+
+vmFrame *vm_push_frame(VirtualMachine *vm, ssaProcedure *proc) {
+	vmFrame frame = {0};
+
+	frame.vm          = vm;
+	frame.curr_proc   = proc;
+	frame.prev_block  = proc->blocks[0];
+	frame.curr_block  = proc->blocks[0];
+	frame.instr_index = 0;
+	frame.caller      = vm_back_frame(vm);
+	frame.stack_allocator   = vm->stack_allocator;
+	frame.temp_arena_memory = gb_temp_arena_memory_begin(&vm->stack_arena);
+
+	map_init(&frame.values, vm->heap_allocator);
+	array_init(&frame.locals, vm->heap_allocator, proc->local_count);
+	array_add(&vm->frame_stack, frame);
+	return vm_back_frame(vm);
+}
+
+void vm_pop_frame(VirtualMachine *vm) {
+	vmFrame *f = vm_back_frame(vm);
+
+	gb_temp_arena_memory_end(f->temp_arena_memory);
+	array_free(&f->locals);
+	map_destroy(&f->values);
+
+	array_pop(&vm->frame_stack);
+}
+
+
+vmValue vm_call_proc(VirtualMachine *vm, ssaProcedure *proc, Array<vmValue> values) {
+	Type *type = base_type(proc->type);
+	GB_ASSERT_MSG(type->Proc.param_count == values.count,
+	              "Incorrect number of arguments passed into procedure call!\n"
+	              "%.*s -> %td vs %td",
+	              LIT(proc->name),
+	              type->Proc.param_count, values.count);
+	Type *result_type = type->Proc.results;
+	if (result_type != NULL &&
+	    result_type->Tuple.variable_count == 1) {
+		result_type = result_type->Tuple.variables[0]->type;
+	}
+
+	if (proc->body == NULL) {
+		// GB_PANIC("TODO(bill): external procedure");
+		gb_printf_err("TODO(bill): external procedure: %.*s\n", LIT(proc->name));
+		vmValue result = {0};
+		result.type = result_type;
+		return result;
+	}
+
+	void *result_mem = NULL;
+	if (result_type != NULL) {
+		result_mem = gb_alloc_align(vm->stack_allocator,
+		                            vm_type_size_of(vm, result_type),
+		                            vm_type_align_of(vm, result_type));
+	}
+
+	gb_printf("call: %.*s\n", LIT(proc->name));
+
+	vmFrame *f = vm_push_frame(vm, proc);
+	for_array(i, proc->params) {
+		vm_set_value(f, proc->params[i], values[i]);
+	}
+
+	while (f->curr_block != NULL) {
+		ssaValue *curr_instr = f->curr_block->instrs[f->instr_index++];
+		vm_exec_instr(vm, curr_instr);
+	}
+
+
+
+
+	if (type->Proc.result_count > 0) {
+		vmValue r = f->result;
+
+		gb_printf("%.*s -> ", LIT(proc->name));
+		vm_print_value(r, result_type);
+		gb_printf("\n");
+
+		vm_store(vm, result_mem, r, result_type);
+	}
+
+	vm_pop_frame(vm);
+	if (result_mem != NULL) {
+		return vm_load(vm, result_mem, result_type);
+	}
+
+	vmValue void_result = {0};
+	return void_result;
+}
+
+
+ssaProcedure *vm_lookup_procedure(VirtualMachine *vm, String name) {
+	ssaValue *v = ssa_lookup_member(vm->module, name);
+	GB_ASSERT(v->kind == ssaValue_Proc);
+	return &v->Proc;
+}
+
+vmValue vm_call_proc_by_name(VirtualMachine *vm, String name, Array<vmValue> args) {
+	return vm_call_proc(vm, vm_lookup_procedure(vm, name), args);
+}
+
+vmValue vm_exact_value(VirtualMachine *vm, ssaValue *ptr, ExactValue value, Type *t) {
+	Type *original_type = t;
+	t = base_type(get_enum_base_type(t));
+	// i64 size = vm_type_size_of(vm, t);
+	if (is_type_boolean(t)) {
+		return vm_make_value_int(original_type, value.value_bool);
+	} else if (is_type_integer(t)) {
+		return vm_make_value_int(original_type, value.value_integer);
+	} else if (is_type_float(t)) {
+		if (t->Basic.kind == Basic_f32) {
+			return vm_make_value_f32(original_type, cast(f32)value.value_float);
+		} else if (t->Basic.kind == Basic_f64) {
+			return vm_make_value_f64(original_type, cast(f64)value.value_float);
+		}
+	} else if (is_type_pointer(t)) {
+		return vm_make_value_ptr(original_type, cast(void *)cast(intptr)value.value_pointer);
+	} else if (is_type_string(t)) {
+		vmValue result = vm_make_value_comp(original_type, vm->stack_allocator, 2);
+
+		String str = value.value_string;
+		i64 len = str.len;
+		u8 *text = gb_alloc_array(vm->heap_allocator, u8, len);
+		gb_memcopy(text, str.text, len);
+
+		result.val_comp[0] = vm_make_value_ptr(t_u8_ptr, text);
+		result.val_comp[1] = vm_make_value_int(t_int, len);
+
+		return result;
+	} else if (value.kind == ExactValue_Compound) {
+		if (ptr != NULL) {
+			vmValue *found = map_get(&vm->const_compound_lits, hash_pointer(ptr));
+			if (found != NULL)  {
+				return *found;
+			}
+		}
+
+		ast_node(cl, CompoundLit, value.value_compound);
+
+		if (is_type_array(t)) {
+			vmValue result = {0};
+
+			isize elem_count = cl->elems.count;
+			if (elem_count == 0) {
+				if (ptr != NULL) {
+					map_set(&vm->const_compound_lits, hash_pointer(ptr), result);
+				}
+				return result;
+			}
+
+			Type *type = base_type(t);
+			result = vm_make_value_comp(t, vm->heap_allocator, type->Array.count);
+			for (isize i = 0; i < elem_count; i++) {
+				TypeAndValue *tav = type_and_value_of_expression(vm->module->info, cl->elems[i]);
+				vmValue elem = vm_exact_value(vm, NULL, tav->value, tav->type);
+				result.val_comp[i] = elem;
+			}
+
+			if (ptr != NULL) {
+				map_set(&vm->const_compound_lits, hash_pointer(ptr), result);
+			}
+
+			return result;
+		} else if (is_type_vector(t)) {
+			vmValue result = {0};
+
+			isize elem_count = cl->elems.count;
+			if (elem_count == 0) {
+				if (ptr != NULL) {
+					map_set(&vm->const_compound_lits, hash_pointer(ptr), result);
+				}
+				return result;
+			}
+
+			Type *type = base_type(t);
+			result = vm_make_value_comp(t, vm->heap_allocator, type->Array.count);
+			for (isize i = 0; i < elem_count; i++) {
+				TypeAndValue *tav = type_and_value_of_expression(vm->module->info, cl->elems[i]);
+				vmValue elem = vm_exact_value(vm, NULL, tav->value, tav->type);
+				result.val_comp[i] = elem;
+			}
+
+			if (ptr != NULL) {
+				map_set(&vm->const_compound_lits, hash_pointer(ptr), result);
+			}
+
+			return result;
+		} else if (is_type_struct(t)) {
+			ast_node(cl, CompoundLit, value.value_compound);
+
+			isize value_count = t->Record.field_count;
+			vmValue result = vm_make_value_comp(t, vm->heap_allocator, value_count);
+
+			if (cl->elems.count == 0) {
+				return result;
+			}
+
+			if (cl->elems[0]->kind == AstNode_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.string;
+
+					TypeAndValue *tav = type_and_value_of_expression(vm->module->info, fv->value);
+					GB_ASSERT(tav != NULL);
+
+					Selection sel = lookup_field(vm->heap_allocator, t, name, false);
+					Entity *f = t->Record.fields[sel.index[0]];
+
+					result.val_comp[f->Variable.field_index] = vm_exact_value(vm, NULL, tav->value, f->type);
+				}
+			} else {
+				for (isize i = 0; i < value_count; i++) {
+					TypeAndValue *tav = type_and_value_of_expression(vm->module->info, cl->elems[i]);
+					GB_ASSERT(tav != NULL);
+					Entity *f = t->Record.fields_in_src_order[i];
+					result.val_comp[f->Variable.field_index] = vm_exact_value(vm, NULL, tav->value, f->type);
+				}
+			}
+
+			return result;
+		} else {
+			GB_PANIC("TODO(bill): Other compound types\n");
+		}
+
+	} else if (value.kind == ExactValue_Invalid) {
+		vmValue zero_result = {0};
+		zero_result.type = t;
+		return zero_result;
+	} else {
+		gb_printf_err("TODO(bill): Other constant types: %s\n", type_to_string(original_type));
+	}
+
+	GB_ASSERT_MSG(t == NULL, "%s - %d", type_to_string(t), value.kind);
+	vmValue void_result = {0};
+	return void_result;
+}
+
+
+vmValue vm_operand_value(VirtualMachine *vm, ssaValue *value) {
+	vmFrame *f = vm_back_frame(vm);
+	vmValue v = {0};
+	switch (value->kind) {
+	case ssaValue_Constant: {
+		v = vm_exact_value(vm, value, value->Constant.value, value->Constant.type);
+	} break;
+	case ssaValue_ConstantSlice: {
+		ssaValueConstant *cs = &value->ConstantSlice;
+		v = vm_make_value_comp(ssa_type(value), vm->stack_allocator, 3);
+		v.val_comp[0] = vm_operand_value(vm, cs->backing_array);
+		v.val_comp[1] = vm_make_value_int(t_int, cs->count);
+		v.val_comp[2] = vm_make_value_int(t_int, cs->count);
+	} break;
+	case ssaValue_Nil:
+		GB_PANIC("TODO(bill): ssaValue_Nil");
+		break;
+	case ssaValue_TypeName:
+		GB_PANIC("ssaValue_TypeName has no operand value");
+		break;
+	case ssaValue_Global:
+		v = *map_get(&vm->globals, hash_pointer(value));
+		break;
+	case ssaValue_Param:
+		v = *map_get(&f->values, hash_pointer(value));
+		break;
+	case ssaValue_Proc: {
+		v.type = ssa_type(value);
+		v.val_proc.proc = &value->Proc;
+		// GB_PANIC("TODO(bill): ssaValue_Proc");
+	} break;
+	case ssaValue_Block:
+		GB_PANIC("ssaValue_Block has no operand value");
+		break;
+	case ssaValue_Instr: {
+		vmValue *found = map_get(&f->values, hash_pointer(value));
+		if (found) {
+			v = *found;
+		} else {
+			GB_PANIC("Invalid instruction");
+		}
+	} break;
+	}
+
+	return v;
+}
+
+void vm_store_integer(VirtualMachine *vm, void *dst, vmValue val) {
+	// TODO(bill): I assume little endian here
+	GB_ASSERT(dst != NULL);
+	Type *type = val.type;
+	GB_ASSERT_MSG(is_type_integer(type) || is_type_boolean(type),
+	              "\nExpected integer/boolean, got %s (%s)",
+	              type_to_string(type),
+	              type_to_string(base_type(type)));
+	i64 size = vm_type_size_of(vm, type);
+	gb_memcopy(dst, &val.val_int, size);
+}
+
+void vm_store_pointer(VirtualMachine *vm, void *dst, vmValue val) {
+	// TODO(bill): I assume little endian here
+	GB_ASSERT(dst != NULL);
+	GB_ASSERT(is_type_pointer(val.type));
+	gb_memcopy(dst, &val.val_ptr, vm_type_size_of(vm, t_rawptr));
+}
+
+
+void vm_store(VirtualMachine *vm, void *dst, vmValue val, Type *type) {
+	i64 size = vm_type_size_of(vm, type);
+	Type *original_type = type;
+	// NOTE(bill): enums are pretty much integers
+	type = base_type(get_enum_base_type(type));
+
+	switch (type->kind) {
+	case Type_Basic:
+		switch (type->Basic.kind) {
+		case Basic_bool:
+		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_int:
+		case Basic_uint:
+			vm_store_integer(vm, dst, val);
+			break;
+		case Basic_f32:
+			*cast(f32 *)dst = val.val_f32;
+			break;
+		case Basic_f64:
+			*cast(f64 *)dst = val.val_f64;
+			break;
+		case Basic_rawptr:
+			vm_store_pointer(vm, dst, val); // NOTE(bill): A pointer can be treated as an integer
+			break;
+		case Basic_string: {
+			i64 word_size = vm_type_size_of(vm, t_int);
+
+			u8 *mem = cast(u8 *)dst;
+			vm_store_pointer(vm, mem+0*word_size, val.val_comp[0]);
+			vm_store_integer(vm, mem+1*word_size, val.val_comp[1]);
+		} break;
+		case Basic_any: {
+			i64 word_size = vm_type_size_of(vm, t_int);
+
+			u8 *mem = cast(u8 *)dst;
+			vm_store_pointer(vm, mem+0*word_size, val.val_comp[0]);
+			vm_store_pointer(vm, mem+1*word_size, val.val_comp[1]);
+		} break;
+		default:
+			gb_printf_err("TODO(bill): other basic types for `vm_store` %s\n", type_to_string(type));
+			break;
+		}
+		break;
+
+	case Type_Pointer:
+		vm_store_pointer(vm, dst, val);
+		break;
+
+	case Type_Record: {
+		u8 *mem = cast(u8 *)dst;
+		gb_zero_size(mem, size);
+
+		if (is_type_struct(type)) {
+			GB_ASSERT_MSG(type->Record.field_count >= val.val_comp.count,
+			              "%td vs %td",
+			              type->Record.field_count, val.val_comp.count);
+
+			isize field_count = gb_min(val.val_comp.count, type->Record.field_count);
+
+			for (isize i = 0; i < field_count; i++) {
+				Entity *f = type->Record.fields[i];
+				i64 offset = vm_type_offset_of(vm, type, i);
+				vm_store(vm, mem+offset, val.val_comp[i], f->type);
+			}
+		} else if (is_type_union(type)) {
+			GB_ASSERT(val.val_comp.count == 2);
+			i64 word_size = vm_type_size_of(vm, t_int);
+			i64 size_of_union = vm_type_size_of(vm, type) - word_size;
+			for (isize i = 0; i < size_of_union; i++) {
+				mem[i] = cast(u8)val.val_comp[0].val_comp[i].val_int;
+			}
+			vm_store_integer(vm, mem + size_of_union, val.val_comp[1]);
+
+		} else if (is_type_raw_union(type)) {
+			GB_ASSERT(val.val_comp.count == 1);
+			i64 word_size = vm_type_size_of(vm, t_int);
+			i64 size_of_union = vm_type_size_of(vm, type) - word_size;
+			for (isize i = 0; i < size_of_union; i++) {
+				mem[i] = cast(u8)val.val_comp[0].val_comp[i].val_int;
+			}
+		} else {
+			GB_PANIC("Unknown record type: %s", type_to_string(type));
+		}
+	} break;
+
+	case Type_Tuple: {
+		u8 *mem = cast(u8 *)dst;
+
+		GB_ASSERT_MSG(type->Tuple.variable_count >= val.val_comp.count,
+		              "%td vs %td",
+		              type->Tuple.variable_count, val.val_comp.count);
+
+		isize variable_count = gb_min(val.val_comp.count, type->Tuple.variable_count);
+
+		for (isize i = 0; i < variable_count; i++) {
+			Entity *f = type->Tuple.variables[i];
+			void *ptr = mem + vm_type_offset_of(vm, type, i);
+			vm_store(vm, ptr, val.val_comp[i], f->type);
+		}
+	} break;
+
+	case Type_Array: {
+		Type *elem_type = type->Array.elem;
+		u8 *mem = cast(u8 *)dst;
+		i64 elem_size = vm_type_size_of(vm, elem_type);
+		i64 elem_count = gb_min(val.val_comp.count, type->Array.count);
+
+		for (i64 i = 0; i < elem_count; i++) {
+			vm_store(vm, mem + i*elem_size, val.val_comp[i], elem_type);
+		}
+	} break;
+
+	case Type_Vector: {
+		Type *elem_type = type->Array.elem;
+		GB_ASSERT_MSG(!is_type_boolean(elem_type), "TODO(bill): Booleans of vectors");
+		u8 *mem = cast(u8 *)dst;
+		i64 elem_size = vm_type_size_of(vm, elem_type);
+		i64 elem_count = gb_min(val.val_comp.count, type->Array.count);
+
+		for (i64 i = 0; i < elem_count; i++) {
+			vm_store(vm, mem + i*elem_size, val.val_comp[i], elem_type);
+		}
+	} break;
+
+	case Type_Slice: {
+		i64 word_size = vm_type_size_of(vm, t_int);
+
+		u8 *mem = cast(u8 *)dst;
+		vm_store_pointer(vm, mem+0*word_size, val.val_comp[0]);
+		vm_store_integer(vm, mem+1*word_size, val.val_comp[1]);
+		vm_store_integer(vm, mem+2*word_size, val.val_comp[2]);
+	} break;
+
+	default:
+		gb_printf_err("TODO(bill): other types for `vm_store` %s\n", type_to_string(type));
+		break;
+	}
+}
+
+vmValue vm_load_integer(VirtualMachine *vm, void *ptr, Type *type) {
+	// TODO(bill): I assume little endian here
+	vmValue v = {0};
+	v.type = type;
+	GB_ASSERT(is_type_integer(type) || is_type_boolean(type));
+	// NOTE(bill): Only load the needed amount
+	gb_memcopy(&v.val_int, ptr, vm_type_size_of(vm, type));
+	return v;
+}
+
+vmValue vm_load_pointer(VirtualMachine *vm, void *ptr, Type *type) {
+	// TODO(bill): I assume little endian here
+	vmValue v = {0};
+	v.type = type;
+	GB_ASSERT(is_type_pointer(type));
+	// NOTE(bill): Only load the needed amount
+	gb_memcopy(&v.val_int, ptr, vm_type_size_of(vm, type));
+	return v;
+}
+
+
+vmValue vm_load(VirtualMachine *vm, void *ptr, Type *type) {
+	i64 size = vm_type_size_of(vm, type);
+	Type *original_type = type;
+	type = base_type(get_enum_base_type(type));
+
+	switch (type->kind) {
+	case Type_Basic:
+		switch (type->Basic.kind) {
+		case Basic_bool:
+		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_int:
+		case Basic_uint:
+			return vm_load_integer(vm, ptr, original_type);
+		case Basic_f32:
+			return vm_make_value_f32(original_type, *cast(f32 *)ptr);
+		case Basic_f64:
+			return vm_make_value_f64(original_type, *cast(f64 *)ptr);
+		case Basic_rawptr:
+			return vm_load_pointer(vm, ptr, original_type);
+
+
+		case Basic_string: {
+			u8 *mem = cast(u8 *)ptr;
+			i64 word_size = vm_type_size_of(vm, t_int);
+			vmValue result = vm_make_value_comp(type, vm->stack_allocator, 2);
+			result.val_comp[0] = vm_load_pointer(vm, mem+0*word_size, t_u8_ptr);
+			result.val_comp[1] = vm_load_integer(vm, mem+1*word_size, t_int);
+			return result;
+		} break;
+
+		default:
+			GB_PANIC("TODO(bill): other basic types for `vm_load` %s", type_to_string(type));
+			break;
+		}
+		break;
+
+	case Type_Pointer:
+		return vm_load_pointer(vm, ptr, original_type);
+
+	case Type_Array: {
+		i64 count = type->Array.count;
+		Type *elem_type = type->Array.elem;
+		i64 elem_size = vm_type_size_of(vm, elem_type);
+
+		vmValue result = vm_make_value_comp(type, vm->stack_allocator, count);
+
+		u8 *mem = cast(u8 *)ptr;
+		for (isize i = 0; i < count; i++) {
+			i64 offset = elem_size*i;
+			vmValue val = vm_load(vm, mem+offset, elem_type);
+			result.val_comp[i] = val;
+		}
+
+		return result;
+	} break;
+
+	case Type_Slice: {
+		Type *elem_type = type->Slice.elem;
+		i64 elem_size = vm_type_size_of(vm, elem_type);
+		i64 word_size = vm_type_size_of(vm, t_int);
+
+		vmValue result = vm_make_value_comp(type, vm->stack_allocator, 3);
+
+		u8 *mem = cast(u8 *)ptr;
+		result.val_comp[0] = vm_load(vm, mem+0*word_size, t_rawptr); // data
+		result.val_comp[1] = vm_load(vm, mem+1*word_size, t_int);    // count
+		result.val_comp[2] = vm_load(vm, mem+2*word_size, t_int);    // capacity
+		return result;
+	} break;
+
+	case Type_Record: {
+		if (is_type_struct(type)) {
+			isize field_count = type->Record.field_count;
+
+			vmValue result = vm_make_value_comp(type, vm->stack_allocator, field_count);
+
+			u8 *mem = cast(u8 *)ptr;
+			for (isize i = 0; i < field_count; i++) {
+				Entity *f = type->Record.fields[i];
+				i64 offset = vm_type_offset_of(vm, type, i);
+				result.val_comp[i] = vm_load(vm, mem+offset, f->type);
+			}
+
+			return result;
+		} else if (is_type_union(type)) {
+			i64 word_size = vm_type_size_of(vm, t_int);
+			i64 size_of_union = vm_type_size_of(vm, type) - word_size;
+			u8 *mem = cast(u8 *)ptr;
+
+			vmValue result = vm_make_value_comp(type, vm->stack_allocator, 2);
+			result.val_comp[0] = vm_load(vm, mem, make_type_array(vm->stack_allocator, t_u8, size_of_union));
+			result.val_comp[1] = vm_load(vm, mem+size_of_union, t_int);
+			return result;
+		} else if (is_type_raw_union(type)) {
+			gb_printf_err("TODO(bill): load raw_union\n");
+		} else {
+			gb_printf_err("TODO(bill): load other records\n");
+		}
+	} break;
+
+	case Type_Tuple: {
+		isize count = type->Tuple.variable_count;
+
+		vmValue result = vm_make_value_comp(type, vm->stack_allocator, count);
+
+		u8 *mem = cast(u8 *)ptr;
+		for (isize i = 0; i < count; i++) {
+			Entity *f = type->Tuple.variables[i];
+			i64 offset = vm_type_offset_of(vm, type, i);
+			result.val_comp[i] = vm_load(vm, mem+offset, f->type);
+		}
+		return result;
+	} break;
+
+	default:
+		GB_PANIC("TODO(bill): other types for `vm_load` %s", type_to_string(type));
+		break;
+	}
+
+	GB_ASSERT(type == NULL);
+	vmValue void_result = {0};
+	return void_result;
+}
+
+vmValue vm_exec_binary_op(VirtualMachine *vm, Type *type, vmValue lhs, vmValue rhs, TokenKind op) {
+	vmValue result = {0};
+
+	type = base_type(type);
+	if (is_type_vector(type)) {
+		Type *elem = type->Vector.elem;
+		i64 count = type->Vector.count;
+
+		result = vm_make_value_comp(type, vm->stack_allocator, count);
+
+		for (i64 i = 0; i < count; i++) {
+			result.val_comp[i] = vm_exec_binary_op(vm, elem, lhs.val_comp[i], rhs.val_comp[i], op);
+		}
+
+		return result;
+	}
+
+	if (gb_is_between(op, Token__ComparisonBegin+1, Token__ComparisonEnd-1)) {
+		if (is_type_integer(type) || is_type_boolean(type)) {
+			// TODO(bill): Do I need to take into account the size of the integer?
+			switch (op) {
+			case Token_CmpEq: result.val_int = lhs.val_int == rhs.val_int; break;
+			case Token_NotEq: result.val_int = lhs.val_int != rhs.val_int; break;
+			case Token_Lt:    result.val_int = lhs.val_int <  rhs.val_int; break;
+			case Token_Gt:    result.val_int = lhs.val_int >  rhs.val_int; break;
+			case Token_LtEq:  result.val_int = lhs.val_int <= rhs.val_int; break;
+			case Token_GtEq:  result.val_int = lhs.val_int >= rhs.val_int; break;
+			}
+		} else if (type == t_f32) {
+			switch (op) {
+			case Token_CmpEq: result.val_f32 = lhs.val_f32 == rhs.val_f32; break;
+			case Token_NotEq: result.val_f32 = lhs.val_f32 != rhs.val_f32; break;
+			case Token_Lt:    result.val_f32 = lhs.val_f32 <  rhs.val_f32; break;
+			case Token_Gt:    result.val_f32 = lhs.val_f32 >  rhs.val_f32; break;
+			case Token_LtEq:  result.val_f32 = lhs.val_f32 <= rhs.val_f32; break;
+			case Token_GtEq:  result.val_f32 = lhs.val_f32 >= rhs.val_f32; break;
+			}
+		} else if (type == t_f64) {
+			switch (op) {
+			case Token_CmpEq: result.val_f64 = lhs.val_f64 == rhs.val_f64; break;
+			case Token_NotEq: result.val_f64 = lhs.val_f64 != rhs.val_f64; break;
+			case Token_Lt:    result.val_f64 = lhs.val_f64 <  rhs.val_f64; break;
+			case Token_Gt:    result.val_f64 = lhs.val_f64 >  rhs.val_f64; break;
+			case Token_LtEq:  result.val_f64 = lhs.val_f64 <= rhs.val_f64; break;
+			case Token_GtEq:  result.val_f64 = lhs.val_f64 >= rhs.val_f64; break;
+			}
+		} else if (is_type_string(type)) {
+			Array<vmValue> args = {0};
+			array_init_count(&args, vm->stack_allocator, 2);
+			args[0] = lhs;
+			args[1] = rhs;
+			switch (op) {
+			case Token_CmpEq: result = vm_call_proc_by_name(vm, make_string("__string_eq"), args); break;
+			case Token_NotEq: result = vm_call_proc_by_name(vm, make_string("__string_ne"), args); break;
+			case Token_Lt:    result = vm_call_proc_by_name(vm, make_string("__string_lt"), args); break;
+			case Token_Gt:    result = vm_call_proc_by_name(vm, make_string("__string_gt"), args); break;
+			case Token_LtEq:  result = vm_call_proc_by_name(vm, make_string("__string_le"), args); break;
+			case Token_GtEq:  result = vm_call_proc_by_name(vm, make_string("__string_ge"), args); break;
+			}
+		} else {
+			GB_PANIC("TODO(bill): Vector BinaryOp");
+		}
+	} else {
+		if (is_type_integer(type) || is_type_boolean(type)) {
+			switch (op) {
+			case Token_Add: result.val_int = lhs.val_int + rhs.val_int;  break;
+			case Token_Sub: result.val_int = lhs.val_int - rhs.val_int;  break;
+			case Token_And: result.val_int = lhs.val_int & rhs.val_int;  break;
+			case Token_Or:  result.val_int = lhs.val_int | rhs.val_int;  break;
+			case Token_Xor: result.val_int = lhs.val_int ^ rhs.val_int;  break;
+			case Token_Shl: result.val_int = lhs.val_int << rhs.val_int; break;
+			case Token_Shr: result.val_int = lhs.val_int >> rhs.val_int; break;
+			case Token_Mul: result.val_int = lhs.val_int * rhs.val_int;  break;
+			case Token_Not: result.val_int = lhs.val_int ^ rhs.val_int;  break;
+
+			case Token_AndNot: result.val_int = lhs.val_int & (~rhs.val_int); break;
+
+			// TODO(bill): Take into account size of integer and signedness
+			case Token_Quo: GB_PANIC("TODO(bill): BinaryOp Integer Token_Quo"); break;
+			case Token_Mod: GB_PANIC("TODO(bill): BinaryOp Integer Token_Mod"); break;
+
+			}
+		} else if (is_type_float(type)) {
+			if (type == t_f32) {
+				switch (op) {
+				case Token_Add: result.val_f32 = lhs.val_f32 + rhs.val_f32;  break;
+				case Token_Sub: result.val_f32 = lhs.val_f32 - rhs.val_f32;  break;
+				case Token_Mul: result.val_f32 = lhs.val_f32 * rhs.val_f32;  break;
+				case Token_Quo: result.val_f32 = lhs.val_f32 / rhs.val_f32;  break;
+
+				case Token_Mod: GB_PANIC("TODO(bill): BinaryOp f32 Token_Mod"); break;
+				}
+			} else if (type == t_f64) {
+				switch (op) {
+				case Token_Add: result.val_f64 = lhs.val_f64 + rhs.val_f64;  break;
+				case Token_Sub: result.val_f64 = lhs.val_f64 - rhs.val_f64;  break;
+				case Token_Mul: result.val_f64 = lhs.val_f64 * rhs.val_f64;  break;
+				case Token_Quo: result.val_f64 = lhs.val_f64 / rhs.val_f64;  break;
+
+				case Token_Mod: GB_PANIC("TODO(bill): BinaryOp f64 Token_Mod"); break;
+				}
+			}
+		} else {
+			GB_PANIC("Invalid binary op type");
+		}
+	}
+
+	return result;
+}
+
+void vm_exec_instr(VirtualMachine *vm, ssaValue *value) {
+	GB_ASSERT(value != NULL);
+	GB_ASSERT(value->kind == ssaValue_Instr);
+	ssaInstr *instr = &value->Instr;
+	vmFrame *f = vm_back_frame(vm);
+
+#if 0
+	if (instr->kind != ssaInstr_Comment) {
+		gb_printf("exec_instr: %.*s\n", LIT(ssa_instr_strings[instr->kind]));
+	}
+#endif
+
+	switch (instr->kind) {
+	case ssaInstr_StartupRuntime: {
+#if 1
+		Array<vmValue> args = {0}; // Empty
+		vm_call_proc_by_name(vm, make_string(SSA_STARTUP_RUNTIME_PROC_NAME), args); // NOTE(bill): No return value
+#endif
+	} break;
+
+	case ssaInstr_Comment:
+		break;
+
+	case ssaInstr_Local: {
+		Type *type = ssa_type(value);
+		GB_ASSERT(is_type_pointer(type));
+		isize size  = gb_max(1, vm_type_size_of(vm, type));
+		isize align = gb_max(1, vm_type_align_of(vm, type));
+		void *memory = gb_alloc_align(vm->stack_allocator, size, align);
+		GB_ASSERT(memory != NULL);
+		vm_set_value(f, value, vm_make_value_ptr(type, memory));
+		array_add(&f->locals, memory);
+	} break;
+
+	case ssaInstr_ZeroInit: {
+		Type *t = type_deref(ssa_type(instr->ZeroInit.address));
+		vmValue addr = vm_operand_value(vm, instr->ZeroInit.address);
+		void *data = addr.val_ptr;
+		i64 size = vm_type_size_of(vm, t);
+		gb_zero_size(data, size);
+	} break;
+
+	case ssaInstr_Store: {
+		vmValue addr = vm_operand_value(vm, instr->Store.address);
+		vmValue val = vm_operand_value(vm, instr->Store.value);
+		GB_ASSERT(val.type != NULL);
+		Type *t = type_deref(ssa_type(instr->Store.address));
+		vm_store(vm, addr.val_ptr, val, t);
+	} break;
+
+	case ssaInstr_Load: {
+		vmValue addr = vm_operand_value(vm, instr->Load.address);
+		Type *t = ssa_type(value);
+		vmValue v = vm_load(vm, addr.val_ptr, t);
+		vm_set_value(f, value, v);
+	} break;
+
+	case ssaInstr_ArrayElementPtr: {
+		vmValue address    = vm_operand_value(vm, instr->ArrayElementPtr.address);
+		vmValue elem_index = vm_operand_value(vm, instr->ArrayElementPtr.elem_index);
+
+		Type *t = ssa_type(instr->ArrayElementPtr.address);
+		GB_ASSERT(is_type_pointer(t));
+		i64 elem_size = vm_type_size_of(vm, type_deref(t));
+		void *ptr = cast(u8 *)address.val_ptr + elem_index.val_int*elem_size;
+		vm_set_value(f, value, vm_make_value_ptr(t, ptr));
+	} break;
+
+	case ssaInstr_StructElementPtr: {
+		vmValue address = vm_operand_value(vm, instr->StructElementPtr.address);
+		i32 elem_index  = instr->StructElementPtr.elem_index;
+
+		Type *t = ssa_type(instr->StructElementPtr.address);
+		GB_ASSERT(is_type_pointer(t));
+		i64 offset = vm_type_offset_of(vm, type_deref(t), elem_index);
+		void *ptr = cast(u8 *)address.val_ptr + offset;
+		vm_set_value(f, value, vm_make_value_ptr(t, ptr));
+	} break;
+
+	case ssaInstr_PtrOffset: {
+		Type *t = ssa_type(instr->PtrOffset.address);
+		GB_ASSERT(is_type_pointer(t));
+		i64 elem_size = vm_type_size_of(vm, type_deref(t));
+		vmValue address = vm_operand_value(vm, instr->PtrOffset.address);
+		vmValue offset  = vm_operand_value(vm, instr->PtrOffset.offset);
+
+		void *ptr = cast(u8 *)address.val_ptr + offset.val_int*elem_size;
+		vm_set_value(f, value, vm_make_value_ptr(t, ptr));
+	} break;
+
+	case ssaInstr_Phi: {
+		for_array(i, f->curr_block->preds) {
+			ssaBlock *pred = f->curr_block->preds[i];
+			if (f->prev_block == pred) {
+				vmValue edge = vm_operand_value(vm, instr->Phi.edges[i]);
+				vm_set_value(f, value, edge);
+				break;
+			}
+		}
+	} break;
+
+	case ssaInstr_ArrayExtractValue: {
+		vmValue s = vm_operand_value(vm, instr->ArrayExtractValue.address);
+		vmValue v = s.val_comp[instr->ArrayExtractValue.index];
+		vm_set_value(f, value, v);
+	} break;
+
+	case ssaInstr_StructExtractValue: {
+		vmValue s = vm_operand_value(vm, instr->StructExtractValue.address);
+		vmValue v = s.val_comp[instr->StructExtractValue.index];
+		vm_set_value(f, value, v);
+	} break;
+
+	case ssaInstr_Jump: {
+		vm_jump_block(f, instr->Jump.block);
+	} break;
+
+	case ssaInstr_If: {
+		vmValue cond = vm_operand_value(vm, instr->If.cond);
+		if (cond.val_int != 0) {
+			vm_jump_block(f, instr->If.true_block);
+		} else {
+			vm_jump_block(f, instr->If.false_block);
+		}
+	} break;
+
+	case ssaInstr_Return: {
+		Type *return_type = NULL;
+		vmValue result = {0};
+
+		if (instr->Return.value != NULL) {
+			return_type = ssa_type(instr->Return.value);
+			result = vm_operand_value(vm, instr->Return.value);
+		}
+
+		f->result = result;
+		f->curr_block = NULL;
+		f->instr_index = 0;
+		return;
+	} break;
+
+	case ssaInstr_Conv: {
+		// TODO(bill): Assuming little endian
+		vmValue dst = {0};
+		vmValue src = vm_operand_value(vm, instr->Conv.value);
+		i64 from_size = vm_type_size_of(vm, instr->Conv.from);
+		i64 to_size   = vm_type_size_of(vm, instr->Conv.to);
+		switch (instr->Conv.kind) {
+		case ssaConv_trunc:
+			gb_memcopy(&dst, &src, to_size);
+			break;
+		case ssaConv_zext:
+			gb_memcopy(&dst, &src, from_size);
+			break;
+		case ssaConv_fptrunc: {
+			GB_ASSERT(from_size > to_size);
+			GB_ASSERT(base_type(instr->Conv.from) == t_f64);
+			GB_ASSERT(base_type(instr->Conv.to) == t_f32);
+			dst.val_f32 = cast(f32)src.val_f64;
+		} break;
+		case ssaConv_fpext: {
+			GB_ASSERT(from_size < to_size);
+			GB_ASSERT(base_type(instr->Conv.from) == t_f32);
+			GB_ASSERT(base_type(instr->Conv.to) == t_f64);
+			dst.val_f64 = cast(f64)src.val_f32;
+		} break;
+		case ssaConv_fptoui: {
+			Type *from = base_type(instr->Conv.from);
+			if (from == t_f64) {
+				u64 u = cast(u64)src.val_f64;
+				vm_store_integer(vm, &dst, vm_make_value_int(instr->Conv.to, u));
+			} else {
+				u64 u = cast(u64)src.val_f32;
+				vm_store_integer(vm, &dst, vm_make_value_int(instr->Conv.to, u));
+			}
+		} break;
+		case ssaConv_fptosi: {
+			Type *from = base_type(instr->Conv.from);
+			if (from == t_f64) {
+				i64 i = cast(i64)src.val_f64;
+				vm_store_integer(vm, &dst, vm_make_value_int(instr->Conv.to, i));
+			} else {
+				i64 i = cast(i64)src.val_f32;
+				vm_store_integer(vm, &dst, vm_make_value_int(instr->Conv.to, i));
+			}
+		} break;
+		case ssaConv_uitofp: {
+			Type *to = base_type(instr->Conv.to);
+			if (to == t_f64) {
+				dst = vm_make_value_f64(instr->Conv.to, cast(f64)cast(u64)src.val_int);
+			} else {
+				dst = vm_make_value_f32(instr->Conv.to, cast(f32)cast(u64)src.val_int);
+			}
+		} break;
+		case ssaConv_sitofp: {
+			Type *to = base_type(instr->Conv.to);
+			if (to == t_f64) {
+				dst = vm_make_value_f64(instr->Conv.to, cast(f64)cast(i64)src.val_int);
+			} else {
+				dst = vm_make_value_f32(instr->Conv.to, cast(f32)cast(i64)src.val_int);
+			}
+		} break;
+
+		case ssaConv_ptrtoint:
+			dst = vm_make_value_int(instr->Conv.to, cast(i64)src.val_ptr);
+			break;
+		case ssaConv_inttoptr:
+			dst = vm_make_value_ptr(instr->Conv.to, cast(void *)src.val_int);
+			break;
+		case ssaConv_bitcast:
+			dst = src;
+			dst.type = instr->Conv.to;
+			break;
+		}
+
+		vm_set_value(f, value, dst);
+	} break;
+
+	case ssaInstr_Unreachable: {
+		GB_PANIC("Unreachable");
+	} break;
+
+	case ssaInstr_BinaryOp: {
+		ssaInstrBinaryOp *bo = &instr->BinaryOp;
+		Type *type = ssa_type(bo->left);
+		vmValue lhs = vm_operand_value(vm, bo->left);
+		vmValue rhs = vm_operand_value(vm, bo->right);
+		vmValue v = vm_exec_binary_op(vm, type, lhs, rhs, bo->op);
+		vm_set_value(f, value, v);
+	} break;
+
+	case ssaInstr_Call: {
+		Array<vmValue> args = {0};
+		array_init(&args, f->stack_allocator, instr->Call.arg_count);
+		for (isize i = 0; i < instr->Call.arg_count; i++) {
+			array_add(&args, vm_operand_value(vm, instr->Call.args[i]));
+		}
+		vmValue proc = vm_operand_value(vm, instr->Call.value);
+		if (proc.val_proc.proc != NULL) {
+			vmValue result = vm_call_proc(vm, proc.val_proc.proc, args);
+			vm_set_value(f, value, result);
+		} else {
+			GB_PANIC("TODO(bill): external procedure calls");
+		}
+
+	} break;
+
+	case ssaInstr_Select: {
+		vmValue v = {0};
+		vmValue cond = vm_operand_value(vm, instr->Select.cond);
+		if (cond.val_int != 0) {
+			v = vm_operand_value(vm, instr->Select.true_value);
+		} else {
+			v = vm_operand_value(vm, instr->Select.false_value);
+		}
+
+		vm_set_value(f, value, v);
+	} break;
+
+	case ssaInstr_VectorExtractElement: {
+		vmValue vector = vm_operand_value(vm, instr->VectorExtractElement.vector);
+		vmValue index  = vm_operand_value(vm, instr->VectorExtractElement.index);
+		vmValue v = vector.val_comp[index.val_int];
+		vm_set_value(f, value, v);
+	} break;
+
+	case ssaInstr_VectorInsertElement: {
+		vmValue vector = vm_operand_value(vm, instr->VectorInsertElement.vector);
+		vmValue elem   = vm_operand_value(vm, instr->VectorInsertElement.elem);
+		vmValue index  = vm_operand_value(vm, instr->VectorInsertElement.index);
+		vector.val_comp[index.val_int] = elem;
+	} break;
+
+	case ssaInstr_VectorShuffle: {
+		ssaValueVectorShuffle *vs = &instr->VectorShuffle;
+		vmValue old_vector = vm_operand_value(vm, instr->VectorShuffle.vector);
+		vmValue new_vector = vm_make_value_comp(ssa_type(value), vm->stack_allocator, vs->index_count);
+
+		for (i32 i = 0; i < vs->index_count; i++) {
+			new_vector.val_comp[i] = old_vector.val_comp[vs->indices[i]];
+		}
+
+		vm_set_value(f, value, new_vector);
+	} break;
+
+	case ssaInstr_BoundsCheck: {
+		ssaInstrBoundsCheck *bc = &instr->BoundsCheck;
+		Array<vmValue> args = {0};
+		array_init(&args, vm->stack_allocator, 5);
+		array_add(&args, vm_exact_value(vm, NULL, make_exact_value_string(bc->pos.file), t_string));
+		array_add(&args, vm_exact_value(vm, NULL, make_exact_value_integer(bc->pos.line), t_int));
+		array_add(&args, vm_exact_value(vm, NULL, make_exact_value_integer(bc->pos.column), t_int));
+		array_add(&args, vm_operand_value(vm, bc->index));
+		array_add(&args, vm_operand_value(vm, bc->len));
+
+		vm_call_proc_by_name(vm, make_string("__bounds_check_error"), args);
+	} break;
+
+	case ssaInstr_SliceBoundsCheck: {
+		ssaInstrSliceBoundsCheck *bc = &instr->SliceBoundsCheck;
+		Array<vmValue> args = {0};
+
+		array_init(&args, vm->stack_allocator, 7);
+		array_add(&args, vm_exact_value(vm, NULL, make_exact_value_string(bc->pos.file), t_string));
+		array_add(&args, vm_exact_value(vm, NULL, make_exact_value_integer(bc->pos.line), t_int));
+		array_add(&args, vm_exact_value(vm, NULL, make_exact_value_integer(bc->pos.column), t_int));
+		array_add(&args, vm_operand_value(vm, bc->low));
+		array_add(&args, vm_operand_value(vm, bc->high));
+		if (!bc->is_substring) {
+			array_add(&args, vm_operand_value(vm, bc->max));
+			vm_call_proc_by_name(vm, make_string("__slice_expr_error"), args);
+		} else {
+			vm_call_proc_by_name(vm, make_string("__substring_expr_error"), args);
+		}
+	} break;
+
+	default: {
+		GB_PANIC("<unknown instr> %d\n", instr->kind);
+	} break;
+	}
+}
+
+
+
+void vm_print_value(vmValue value, Type *type) {
+	type = base_type(type);
+	if (is_type_string(type)) {
+		vmValue data  = value.val_comp[0];
+		vmValue count = value.val_comp[1];
+		gb_printf("`%.*s`", cast(int)count.val_int, cast(u8 *)data.val_ptr);
+	} else if (is_type_boolean(type)) {
+		if (value.val_int != 0) {
+			gb_printf("true");
+		} else {
+			gb_printf("false");
+		}
+	} else if (is_type_integer(type)) {
+		gb_printf("%lld", cast(i64)value.val_int);
+	} else if (type == t_f32) {
+		gb_printf("%f", value.val_f32);
+	} else if (type == t_f64) {
+		gb_printf("%f", value.val_f64);
+	} else if (is_type_pointer(type)) {
+		gb_printf("0x%08x", value.val_ptr);
+	} else if (is_type_array(type)) {
+		gb_printf("[");
+		for_array(i, value.val_comp) {
+			if (i > 0) {
+				gb_printf(", ");
+			}
+			vm_print_value(value.val_comp[i], type->Array.elem);
+		}
+		gb_printf("]");
+	} else if (is_type_vector(type)) {
+		gb_printf("<");
+		for_array(i, value.val_comp) {
+			if (i > 0) {
+				gb_printf(", ");
+			}
+			vm_print_value(value.val_comp[i], type->Vector.elem);
+		}
+		gb_printf(">");
+	} else if (is_type_slice(type)) {
+		gb_printf("[");
+		for_array(i, value.val_comp) {
+			if (i > 0) {
+				gb_printf(", ");
+			}
+			vm_print_value(value.val_comp[i], type->Slice.elem);
+		}
+		gb_printf("]");
+	} else if (is_type_maybe(type)) {
+		if (value.val_comp[1].val_int != 0) {
+			gb_printf("?");
+			vm_print_value(value.val_comp[0], type->Maybe.elem);
+		} else {
+			gb_printf("nil");
+		}
+	} else if (is_type_struct(type)) {
+		if (value.val_comp.count == 0) {
+			gb_printf("nil");
+		} else {
+			gb_printf("{");
+			for_array(i, value.val_comp) {
+				if (i > 0) {
+					gb_printf(", ");
+				}
+				vm_print_value(value.val_comp[i], type->Record.fields[i]->type);
+			}
+			gb_printf("}");
+		}
+	} else if (is_type_tuple(type)) {
+		if (value.val_comp.count != 1) {
+			gb_printf("(");
+		}
+		for_array(i, value.val_comp) {
+			if (i > 0) {
+				gb_printf(", ");
+			}
+			vm_print_value(value.val_comp[i], type->Tuple.variables[i]->type);
+		}
+		if (value.val_comp.count != 1) {
+			gb_printf(")");
+		}
+	}
+}

+ 3250 - 0
src/parser.c

@@ -0,0 +1,3250 @@
+typedef struct AstNode AstNode;
+typedef struct Scope Scope;
+typedef struct DeclInfo DeclInfo;
+
+typedef enum ParseFileError {
+	ParseFile_None,
+
+	ParseFile_WrongExtension,
+	ParseFile_InvalidFile,
+	ParseFile_EmptyFile,
+	ParseFile_Permission,
+	ParseFile_NotFound,
+	ParseFile_InvalidToken,
+
+	ParseFile_Count,
+} ParseFileError;
+
+typedef Array(AstNode *) AstNodeArray;
+
+typedef struct AstFile {
+	i32            id;
+	gbArena        arena;
+	Tokenizer      tokenizer;
+	Array(Token)   tokens;
+	isize          curr_token_index;
+	Token          curr_token;
+	Token          prev_token; // previous non-comment
+
+	// >= 0: In Expression
+	// <  0: In Control Clause
+	// NOTE(bill): Used to prevent type literals in control clauses
+	isize          expr_level;
+
+	AstNodeArray   decls;
+	bool            is_global_scope;
+
+	AstNode *      curr_proc;
+	isize          scope_level;
+	Scope *        scope;       // NOTE(bill): Created in checker
+	DeclInfo *     decl_info;   // NOTE(bill): Created in checker
+
+	// TODO(bill): Error recovery
+#define PARSER_MAX_FIX_COUNT 6
+	isize    fix_count;
+	TokenPos fix_prev_pos;
+} AstFile;
+
+typedef struct ImportedFile {
+	String   path;
+	String   rel_path;
+	TokenPos pos; // #import
+} ImportedFile;
+
+typedef struct Parser {
+	String              init_fullpath;
+	Array(AstFile)      files;
+	Array(ImportedFile) imports;
+	gbAtomic32          import_index;
+	Array(String)       foreign_libraries;
+	isize               total_token_count;
+	gbMutex             mutex;
+} Parser;
+
+typedef enum ProcTag {
+	ProcTag_bounds_check    = GB_BIT(0),
+	ProcTag_no_bounds_check = GB_BIT(1),
+
+	ProcTag_foreign         = GB_BIT(10),
+	ProcTag_link_name       = GB_BIT(11),
+	ProcTag_inline          = GB_BIT(12),
+	ProcTag_no_inline       = GB_BIT(13),
+	ProcTag_dll_import      = GB_BIT(14),
+	ProcTag_dll_export      = GB_BIT(15),
+
+	ProcTag_stdcall         = GB_BIT(16),
+	ProcTag_fastcall        = GB_BIT(17),
+	// ProcTag_cdecl           = GB_BIT(18),
+} ProcTag;
+
+typedef enum VarDeclTag {
+	VarDeclTag_thread_local = GB_BIT(0),
+} VarDeclTag;
+
+typedef enum StmtStateFlag {
+	StmtStateFlag_bounds_check    = GB_BIT(0),
+	StmtStateFlag_no_bounds_check = GB_BIT(1),
+} StmtStateFlag;
+
+
+typedef enum CallExprKind {
+	CallExpr_Prefix,  // call(...)
+	CallExpr_Postfix, // a'call
+	CallExpr_Infix,   // a ''call b
+} CallExprKind;
+
+AstNodeArray make_ast_node_array(AstFile *f) {
+	AstNodeArray a;
+	array_init(&a, gb_arena_allocator(&f->arena));
+	return a;
+}
+
+
+#define AST_NODE_KINDS \
+	AST_NODE_KIND(BasicLit, "basic literal", Token) \
+	AST_NODE_KIND(Ident,    "identifier",    Token) \
+	AST_NODE_KIND(Ellipsis, "ellipsis", struct { \
+		Token token; \
+		AstNode *expr; \
+	}) \
+	AST_NODE_KIND(ProcLit, "procedure literal", struct { \
+		AstNode *type; \
+		AstNode *body; \
+		u64 tags;      \
+	}) \
+	AST_NODE_KIND(CompoundLit, "compound literal", struct { \
+		AstNode *type; \
+		AstNodeArray elems; \
+		Token open, close; \
+	}) \
+AST_NODE_KIND(_ExprBegin,  "",  i32) \
+	AST_NODE_KIND(BadExpr,      "bad expression",         struct { Token begin, end; }) \
+	AST_NODE_KIND(TagExpr,      "tag expression",         struct { Token token, name; AstNode *expr; }) \
+	AST_NODE_KIND(RunExpr,      "run expression",         struct { Token token, name; AstNode *expr; }) \
+	AST_NODE_KIND(UnaryExpr,    "unary expression",       struct { Token op; AstNode *expr; }) \
+	AST_NODE_KIND(BinaryExpr,   "binary expression",      struct { Token op; AstNode *left, *right; } ) \
+	AST_NODE_KIND(ParenExpr,    "parentheses expression", struct { AstNode *expr; Token open, close; }) \
+	AST_NODE_KIND(SelectorExpr, "selector expression",    struct { Token token; AstNode *expr, *selector; }) \
+	AST_NODE_KIND(IndexExpr,    "index expression",       struct { AstNode *expr, *index; Token open, close; }) \
+	AST_NODE_KIND(DerefExpr,    "dereference expression", struct { Token op; AstNode *expr; }) \
+	AST_NODE_KIND(DemaybeExpr,  "demaybe expression",     struct { Token op; AstNode *expr; }) \
+	AST_NODE_KIND(CallExpr,     "call expression", struct { \
+		AstNode *proc; \
+		AstNodeArray args; \
+		Token open, close; \
+		Token ellipsis; \
+		CallExprKind kind; \
+	}) \
+	AST_NODE_KIND(SliceExpr, "slice expression", struct { \
+		AstNode *expr; \
+		Token open, close; \
+		AstNode *low, *high, *max; \
+		bool triple_indexed; \
+	}) \
+	AST_NODE_KIND(FieldValue, "field value", struct { Token eq; AstNode *field, *value; }) \
+AST_NODE_KIND(_ExprEnd,       "", i32) \
+AST_NODE_KIND(_StmtBegin,     "", i32) \
+	AST_NODE_KIND(BadStmt,    "bad statement",                 struct { Token begin, end; }) \
+	AST_NODE_KIND(EmptyStmt,  "empty statement",               struct { Token token; }) \
+	AST_NODE_KIND(ExprStmt,   "expression statement",          struct { AstNode *expr; } ) \
+	AST_NODE_KIND(IncDecStmt, "increment/decrement statement", struct { Token op; AstNode *expr; }) \
+	AST_NODE_KIND(TagStmt,    "tag statement", struct { \
+		Token token; \
+		Token name; \
+		AstNode *stmt; \
+	}) \
+	AST_NODE_KIND(AssignStmt, "assign statement", struct { \
+		Token op; \
+		AstNodeArray lhs, rhs; \
+	}) \
+AST_NODE_KIND(_ComplexStmtBegin, "", i32) \
+	AST_NODE_KIND(BlockStmt, "block statement", struct { \
+		AstNodeArray stmts; \
+		Token open, close; \
+	}) \
+	AST_NODE_KIND(IfStmt, "if statement", struct { \
+		Token token; \
+		AstNode *init; \
+		AstNode *cond; \
+		AstNode *body; \
+		AstNode *else_stmt; \
+	}) \
+	AST_NODE_KIND(ReturnStmt, "return statement", struct { \
+		Token token; \
+		AstNodeArray results; \
+	}) \
+	AST_NODE_KIND(ForStmt, "for statement", struct { \
+		Token token; \
+		AstNode *init, *cond, *post; \
+		AstNode *body; \
+	}) \
+	AST_NODE_KIND(CaseClause, "case clause", struct { \
+		Token token; \
+		AstNodeArray list, stmts; \
+	}) \
+	AST_NODE_KIND(MatchStmt, "match statement", struct { \
+		Token token; \
+		AstNode *init, *tag; \
+		AstNode *body; \
+	}) \
+	AST_NODE_KIND(TypeMatchStmt, "type match statement", struct { \
+		Token token; \
+		AstNode *tag, *var; \
+		AstNode *body; \
+	}) \
+	AST_NODE_KIND(DeferStmt,  "defer statement",  struct { Token token; AstNode *stmt; }) \
+	AST_NODE_KIND(BranchStmt, "branch statement", struct { Token token; }) \
+	AST_NODE_KIND(UsingStmt,  "using statement",  struct { Token token; AstNode *node; }) \
+	AST_NODE_KIND(AsmOperand, "assembly operand", struct { \
+		Token string; \
+		AstNode *operand; \
+	}) \
+	AST_NODE_KIND(AsmStmt,    "assembly statement", struct { \
+		Token token; \
+		bool is_volatile; \
+		Token open, close; \
+		Token code_string; \
+		AstNode *output_list; \
+		AstNode *input_list; \
+		AstNode *clobber_list; \
+		isize output_count, input_count, clobber_count; \
+	}) \
+	AST_NODE_KIND(PushAllocator, "push_allocator statement", struct { \
+		Token token; \
+		AstNode *expr; \
+		AstNode *body; \
+	}) \
+	AST_NODE_KIND(PushContext, "push_context statement", struct { \
+		Token token; \
+		AstNode *expr; \
+		AstNode *body; \
+	}) \
+\
+AST_NODE_KIND(_ComplexStmtEnd, "", i32) \
+AST_NODE_KIND(_StmtEnd,        "", i32) \
+AST_NODE_KIND(_DeclBegin,      "", i32) \
+	AST_NODE_KIND(BadDecl,  "bad declaration", struct { Token begin, end; }) \
+	AST_NODE_KIND(VarDecl,  "variable declaration", struct { \
+			u64          tags; \
+			bool          is_using; \
+			AstNodeArray names; \
+			AstNode *    type; \
+			AstNodeArray values; \
+			AstNode *    note; \
+	}) \
+	AST_NODE_KIND(ConstDecl,  "constant declaration", struct { \
+			u64          tags; \
+			AstNodeArray names; \
+			AstNode *    type; \
+			AstNodeArray values; \
+			AstNode *    note; \
+	}) \
+	AST_NODE_KIND(ProcDecl, "procedure declaration", struct { \
+			AstNode *name;         \
+			AstNode *type;         \
+			AstNode *body;         \
+			u64      tags;         \
+			String   foreign_name; \
+			String   link_name;    \
+			AstNode *note;          \
+	}) \
+	AST_NODE_KIND(TypeDecl,   "type declaration",   struct { \
+		Token token; \
+		AstNode *name, *type; \
+		AstNode *note; \
+	}) \
+	AST_NODE_KIND(ImportDecl, "import declaration", struct { \
+		Token token, relpath; \
+		String fullpath;      \
+		Token import_name;    \
+		bool is_load;          \
+		AstNode *note;        \
+	}) \
+	AST_NODE_KIND(ForeignLibrary, "foreign library", struct { \
+		Token token, filepath; \
+		bool is_system; \
+	}) \
+AST_NODE_KIND(_DeclEnd,   "", i32) \
+AST_NODE_KIND(_TypeBegin, "", i32) \
+	AST_NODE_KIND(Parameter, "parameter", struct { \
+		AstNodeArray names; \
+		AstNode *type; \
+		bool is_using; \
+	}) \
+	AST_NODE_KIND(ProcType, "procedure type", struct { \
+		Token token;          \
+		AstNodeArray params;  \
+		AstNodeArray results; \
+	}) \
+	AST_NODE_KIND(PointerType, "pointer type", struct { \
+		Token token; \
+		AstNode *type; \
+	}) \
+	AST_NODE_KIND(MaybeType, "maybe type", struct { \
+		Token token; \
+		AstNode *type; \
+	}) \
+	AST_NODE_KIND(ArrayType, "array type", struct { \
+		Token token; \
+		AstNode *count; \
+		AstNode *elem; \
+	}) \
+	AST_NODE_KIND(VectorType, "vector type", struct { \
+		Token token; \
+		AstNode *count; \
+		AstNode *elem; \
+	}) \
+	AST_NODE_KIND(StructType, "struct type", struct { \
+		Token token; \
+		AstNodeArray decls; \
+		isize decl_count; \
+		bool is_packed; \
+		bool is_ordered; \
+	}) \
+	AST_NODE_KIND(UnionType, "union type", struct { \
+		Token token; \
+		AstNodeArray decls; \
+		isize decl_count; \
+	}) \
+	AST_NODE_KIND(RawUnionType, "raw union type", struct { \
+		Token token; \
+		AstNodeArray decls; \
+		isize decl_count; \
+	}) \
+	AST_NODE_KIND(EnumType, "enum type", struct { \
+		Token token; \
+		AstNode *base_type; \
+		AstNodeArray fields; \
+	}) \
+AST_NODE_KIND(_TypeEnd,  "", i32)
+
+typedef enum AstNodeKind {
+	AstNode_Invalid,
+#define AST_NODE_KIND(_kind_name_, ...) GB_JOIN2(AstNode_, _kind_name_),
+	AST_NODE_KINDS
+#undef AST_NODE_KIND
+	AstNode_Count,
+} AstNodeKind;
+
+String const ast_node_strings[] = {
+	{cast(u8 *)"invalid node", gb_size_of("invalid node")},
+#define AST_NODE_KIND(_kind_name_, name, ...) {cast(u8 *)name, gb_size_of(name)-1},
+	AST_NODE_KINDS
+#undef AST_NODE_KIND
+};
+
+#define AST_NODE_KIND(_kind_name_, name, ...) typedef __VA_ARGS__ GB_JOIN2(AstNode, _kind_name_);
+	AST_NODE_KINDS
+#undef AST_NODE_KIND
+
+typedef struct AstNode {
+	AstNodeKind kind;
+	// AstNode *prev, *next; // NOTE(bill): allow for Linked list
+	u32 stmt_state_flags;
+	union {
+#define AST_NODE_KIND(_kind_name_, name, ...) GB_JOIN2(AstNode, _kind_name_) _kind_name_;
+	AST_NODE_KINDS
+#undef AST_NODE_KIND
+	};
+} AstNode;
+
+
+#define ast_node(n_, Kind_, node_) GB_JOIN2(AstNode, Kind_) *n_ = &(node_)->Kind_; GB_ASSERT((node_)->kind == GB_JOIN2(AstNode_, Kind_))
+#define case_ast_node(n_, Kind_, node_) case GB_JOIN2(AstNode_, Kind_): { ast_node(n_, Kind_, node_);
+#define case_end } break;
+
+
+
+
+gb_inline bool is_ast_node_expr(AstNode *node) {
+	return gb_is_between(node->kind, AstNode__ExprBegin+1, AstNode__ExprEnd-1);
+}
+gb_inline bool is_ast_node_stmt(AstNode *node) {
+	return gb_is_between(node->kind, AstNode__StmtBegin+1, AstNode__StmtEnd-1);
+}
+gb_inline bool is_ast_node_complex_stmt(AstNode *node) {
+	return gb_is_between(node->kind, AstNode__ComplexStmtBegin+1, AstNode__ComplexStmtEnd-1);
+}
+gb_inline bool is_ast_node_decl(AstNode *node) {
+	return gb_is_between(node->kind, AstNode__DeclBegin+1, AstNode__DeclEnd-1);
+}
+gb_inline bool is_ast_node_type(AstNode *node) {
+	return gb_is_between(node->kind, AstNode__TypeBegin+1, AstNode__TypeEnd-1);
+}
+
+
+Token ast_node_token(AstNode *node) {
+	switch (node->kind) {
+	case AstNode_BasicLit:
+		return node->BasicLit;
+	case AstNode_Ident:
+		return node->Ident;
+	case AstNode_ProcLit:
+		return ast_node_token(node->ProcLit.type);
+	case AstNode_CompoundLit:
+		if (node->CompoundLit.type != NULL) {
+			return ast_node_token(node->CompoundLit.type);
+		}
+		return node->CompoundLit.open;
+	case AstNode_TagExpr:
+		return node->TagExpr.token;
+	case AstNode_RunExpr:
+		return node->RunExpr.token;
+	case AstNode_BadExpr:
+		return node->BadExpr.begin;
+	case AstNode_UnaryExpr:
+		return node->UnaryExpr.op;
+	case AstNode_BinaryExpr:
+		return ast_node_token(node->BinaryExpr.left);
+	case AstNode_ParenExpr:
+		return node->ParenExpr.open;
+	case AstNode_CallExpr:
+		return ast_node_token(node->CallExpr.proc);
+	case AstNode_SelectorExpr:
+		return ast_node_token(node->SelectorExpr.selector);
+	case AstNode_IndexExpr:
+		return node->IndexExpr.open;
+	case AstNode_SliceExpr:
+		return node->SliceExpr.open;
+	case AstNode_Ellipsis:
+		return node->Ellipsis.token;
+	case AstNode_FieldValue:
+		return node->FieldValue.eq;
+	case AstNode_DerefExpr:
+		return node->DerefExpr.op;
+	case AstNode_DemaybeExpr:
+		return node->DemaybeExpr.op;
+	case AstNode_BadStmt:
+		return node->BadStmt.begin;
+	case AstNode_EmptyStmt:
+		return node->EmptyStmt.token;
+	case AstNode_ExprStmt:
+		return ast_node_token(node->ExprStmt.expr);
+	case AstNode_TagStmt:
+		return node->TagStmt.token;
+	case AstNode_IncDecStmt:
+		return node->IncDecStmt.op;
+	case AstNode_AssignStmt:
+		return node->AssignStmt.op;
+	case AstNode_BlockStmt:
+		return node->BlockStmt.open;
+	case AstNode_IfStmt:
+		return node->IfStmt.token;
+	case AstNode_ReturnStmt:
+		return node->ReturnStmt.token;
+	case AstNode_ForStmt:
+		return node->ForStmt.token;
+	case AstNode_MatchStmt:
+		return node->MatchStmt.token;
+	case AstNode_CaseClause:
+		return node->CaseClause.token;
+	case AstNode_DeferStmt:
+		return node->DeferStmt.token;
+	case AstNode_BranchStmt:
+		return node->BranchStmt.token;
+	case AstNode_UsingStmt:
+		return node->UsingStmt.token;
+	case AstNode_AsmStmt:
+		return node->AsmStmt.token;
+	case AstNode_PushAllocator:
+		return node->PushAllocator.token;
+	case AstNode_PushContext:
+		return node->PushContext.token;
+	case AstNode_BadDecl:
+		return node->BadDecl.begin;
+	case AstNode_VarDecl:
+		return ast_node_token(node->VarDecl.names.e[0]);
+	case AstNode_ConstDecl:
+		return ast_node_token(node->ConstDecl.names.e[0]);
+	case AstNode_ProcDecl:
+		return node->ProcDecl.name->Ident;
+	case AstNode_TypeDecl:
+		return node->TypeDecl.token;
+	case AstNode_ImportDecl:
+		return node->ImportDecl.token;
+	case AstNode_ForeignLibrary:
+		return node->ForeignLibrary.token;
+	case AstNode_Parameter: {
+		if (node->Parameter.names.count > 0) {
+			return ast_node_token(node->Parameter.names.e[0]);
+		} else {
+			return ast_node_token(node->Parameter.type);
+		}
+	}
+	case AstNode_ProcType:
+		return node->ProcType.token;
+	case AstNode_PointerType:
+		return node->PointerType.token;
+	case AstNode_MaybeType:
+		return node->MaybeType.token;
+	case AstNode_ArrayType:
+		return node->ArrayType.token;
+	case AstNode_VectorType:
+		return node->VectorType.token;
+	case AstNode_StructType:
+		return node->StructType.token;
+	case AstNode_UnionType:
+		return node->UnionType.token;
+	case AstNode_RawUnionType:
+		return node->RawUnionType.token;
+	case AstNode_EnumType:
+		return node->EnumType.token;
+	}
+
+	return empty_token;
+}
+
+// NOTE(bill): And this below is why is I/we need a new language! Discriminated unions are a pain in C/C++
+AstNode *make_node(AstFile *f, AstNodeKind kind) {
+	gbArena *arena = &f->arena;
+	if (gb_arena_size_remaining(arena, GB_DEFAULT_MEMORY_ALIGNMENT) <= gb_size_of(AstNode)) {
+		// NOTE(bill): If a syntax error is so bad, just quit!
+		gb_exit(1);
+	}
+	AstNode *node = gb_alloc_item(gb_arena_allocator(arena), AstNode);
+	node->kind = kind;
+	return node;
+}
+
+AstNode *make_bad_expr(AstFile *f, Token begin, Token end) {
+	AstNode *result = make_node(f, AstNode_BadExpr);
+	result->BadExpr.begin = begin;
+	result->BadExpr.end   = end;
+	return result;
+}
+
+AstNode *make_tag_expr(AstFile *f, Token token, Token name, AstNode *expr) {
+	AstNode *result = make_node(f, AstNode_TagExpr);
+	result->TagExpr.token = token;
+	result->TagExpr.name = name;
+	result->TagExpr.expr = expr;
+	return result;
+}
+
+AstNode *make_run_expr(AstFile *f, Token token, Token name, AstNode *expr) {
+	AstNode *result = make_node(f, AstNode_RunExpr);
+	result->RunExpr.token = token;
+	result->RunExpr.name = name;
+	result->RunExpr.expr = expr;
+	return result;
+}
+
+
+AstNode *make_tag_stmt(AstFile *f, Token token, Token name, AstNode *stmt) {
+	AstNode *result = make_node(f, AstNode_TagStmt);
+	result->TagStmt.token = token;
+	result->TagStmt.name = name;
+	result->TagStmt.stmt = stmt;
+	return result;
+}
+
+AstNode *make_unary_expr(AstFile *f, Token op, AstNode *expr) {
+	AstNode *result = make_node(f, AstNode_UnaryExpr);
+	result->UnaryExpr.op = op;
+	result->UnaryExpr.expr = expr;
+	return result;
+}
+
+AstNode *make_binary_expr(AstFile *f, Token op, AstNode *left, AstNode *right) {
+	AstNode *result = make_node(f, AstNode_BinaryExpr);
+
+	if (left == NULL) {
+		syntax_error(op, "No lhs expression for binary expression `%.*s`", LIT(op.string));
+		left = make_bad_expr(f, op, op);
+	}
+	if (right == NULL) {
+		syntax_error(op, "No rhs expression for binary expression `%.*s`", LIT(op.string));
+		right = make_bad_expr(f, op, op);
+	}
+
+	result->BinaryExpr.op = op;
+	result->BinaryExpr.left = left;
+	result->BinaryExpr.right = right;
+
+	return result;
+}
+
+AstNode *make_paren_expr(AstFile *f, AstNode *expr, Token open, Token close) {
+	AstNode *result = make_node(f, AstNode_ParenExpr);
+	result->ParenExpr.expr = expr;
+	result->ParenExpr.open = open;
+	result->ParenExpr.close = close;
+	return result;
+}
+
+AstNode *make_call_expr(AstFile *f, AstNode *proc, AstNodeArray args, Token open, Token close, Token ellipsis) {
+	AstNode *result = make_node(f, AstNode_CallExpr);
+	result->CallExpr.proc = proc;
+	result->CallExpr.args = args;
+	result->CallExpr.open     = open;
+	result->CallExpr.close    = close;
+	result->CallExpr.ellipsis = ellipsis;
+	return result;
+}
+
+AstNode *make_selector_expr(AstFile *f, Token token, AstNode *expr, AstNode *selector) {
+	AstNode *result = make_node(f, AstNode_SelectorExpr);
+	result->SelectorExpr.expr = expr;
+	result->SelectorExpr.selector = selector;
+	return result;
+}
+
+AstNode *make_index_expr(AstFile *f, AstNode *expr, AstNode *index, Token open, Token close) {
+	AstNode *result = make_node(f, AstNode_IndexExpr);
+	result->IndexExpr.expr = expr;
+	result->IndexExpr.index = index;
+	result->IndexExpr.open = open;
+	result->IndexExpr.close = close;
+	return result;
+}
+
+
+AstNode *make_slice_expr(AstFile *f, AstNode *expr, Token open, Token close, AstNode *low, AstNode *high, AstNode *max, bool triple_indexed) {
+	AstNode *result = make_node(f, AstNode_SliceExpr);
+	result->SliceExpr.expr = expr;
+	result->SliceExpr.open = open;
+	result->SliceExpr.close = close;
+	result->SliceExpr.low = low;
+	result->SliceExpr.high = high;
+	result->SliceExpr.max = max;
+	result->SliceExpr.triple_indexed = triple_indexed;
+	return result;
+}
+
+AstNode *make_deref_expr(AstFile *f, AstNode *expr, Token op) {
+	AstNode *result = make_node(f, AstNode_DerefExpr);
+	result->DerefExpr.expr = expr;
+	result->DerefExpr.op = op;
+	return result;
+}
+
+AstNode *make_demaybe_expr(AstFile *f, AstNode *expr, Token op) {
+	AstNode *result = make_node(f, AstNode_DemaybeExpr);
+	result->DemaybeExpr.expr = expr;
+	result->DemaybeExpr.op = op;
+	return result;
+}
+
+
+AstNode *make_basic_lit(AstFile *f, Token basic_lit) {
+	AstNode *result = make_node(f, AstNode_BasicLit);
+	result->BasicLit = basic_lit;
+	return result;
+}
+
+AstNode *make_ident(AstFile *f, Token token) {
+	AstNode *result = make_node(f, AstNode_Ident);
+	result->Ident = token;
+	return result;
+}
+
+AstNode *make_ellipsis(AstFile *f, Token token, AstNode *expr) {
+	AstNode *result = make_node(f, AstNode_Ellipsis);
+	result->Ellipsis.token = token;
+	result->Ellipsis.expr = expr;
+	return result;
+}
+
+
+AstNode *make_proc_lit(AstFile *f, AstNode *type, AstNode *body, u64 tags) {
+	AstNode *result = make_node(f, AstNode_ProcLit);
+	result->ProcLit.type = type;
+	result->ProcLit.body = body;
+	result->ProcLit.tags = tags;
+	return result;
+}
+
+AstNode *make_field_value(AstFile *f, AstNode *field, AstNode *value, Token eq) {
+	AstNode *result = make_node(f, AstNode_FieldValue);
+	result->FieldValue.field = field;
+	result->FieldValue.value = value;
+	result->FieldValue.eq = eq;
+	return result;
+}
+
+AstNode *make_compound_lit(AstFile *f, AstNode *type, AstNodeArray elems, Token open, Token close) {
+	AstNode *result = make_node(f, AstNode_CompoundLit);
+	result->CompoundLit.type = type;
+	result->CompoundLit.elems = elems;
+	result->CompoundLit.open = open;
+	result->CompoundLit.close = close;
+	return result;
+}
+
+AstNode *make_bad_stmt(AstFile *f, Token begin, Token end) {
+	AstNode *result = make_node(f, AstNode_BadStmt);
+	result->BadStmt.begin = begin;
+	result->BadStmt.end   = end;
+	return result;
+}
+
+AstNode *make_empty_stmt(AstFile *f, Token token) {
+	AstNode *result = make_node(f, AstNode_EmptyStmt);
+	result->EmptyStmt.token = token;
+	return result;
+}
+
+AstNode *make_expr_stmt(AstFile *f, AstNode *expr) {
+	AstNode *result = make_node(f, AstNode_ExprStmt);
+	result->ExprStmt.expr = expr;
+	return result;
+}
+
+AstNode *make_inc_dec_stmt(AstFile *f, Token op, AstNode *expr) {
+	AstNode *result = make_node(f, AstNode_IncDecStmt);
+	result->IncDecStmt.op = op;
+	result->IncDecStmt.expr = expr;
+	return result;
+}
+
+AstNode *make_assign_stmt(AstFile *f, Token op, AstNodeArray lhs, AstNodeArray rhs) {
+	AstNode *result = make_node(f, AstNode_AssignStmt);
+	result->AssignStmt.op = op;
+	result->AssignStmt.lhs = lhs;
+	result->AssignStmt.rhs = rhs;
+	return result;
+}
+
+AstNode *make_block_stmt(AstFile *f, AstNodeArray stmts, Token open, Token close) {
+	AstNode *result = make_node(f, AstNode_BlockStmt);
+	result->BlockStmt.stmts = stmts;
+	result->BlockStmt.open = open;
+	result->BlockStmt.close = close;
+	return result;
+}
+
+AstNode *make_if_stmt(AstFile *f, Token token, AstNode *init, AstNode *cond, AstNode *body, AstNode *else_stmt) {
+	AstNode *result = make_node(f, AstNode_IfStmt);
+	result->IfStmt.token = token;
+	result->IfStmt.init = init;
+	result->IfStmt.cond = cond;
+	result->IfStmt.body = body;
+	result->IfStmt.else_stmt = else_stmt;
+	return result;
+}
+
+AstNode *make_return_stmt(AstFile *f, Token token, AstNodeArray results) {
+	AstNode *result = make_node(f, AstNode_ReturnStmt);
+	result->ReturnStmt.token = token;
+	result->ReturnStmt.results = results;
+	return result;
+}
+
+AstNode *make_for_stmt(AstFile *f, Token token, AstNode *init, AstNode *cond, AstNode *post, AstNode *body) {
+	AstNode *result = make_node(f, AstNode_ForStmt);
+	result->ForStmt.token = token;
+	result->ForStmt.init  = init;
+	result->ForStmt.cond  = cond;
+	result->ForStmt.post  = post;
+	result->ForStmt.body  = body;
+	return result;
+}
+
+
+AstNode *make_match_stmt(AstFile *f, Token token, AstNode *init, AstNode *tag, AstNode *body) {
+	AstNode *result = make_node(f, AstNode_MatchStmt);
+	result->MatchStmt.token = token;
+	result->MatchStmt.init  = init;
+	result->MatchStmt.tag   = tag;
+	result->MatchStmt.body  = body;
+	return result;
+}
+
+
+AstNode *make_type_match_stmt(AstFile *f, Token token, AstNode *tag, AstNode *var, AstNode *body) {
+	AstNode *result = make_node(f, AstNode_TypeMatchStmt);
+	result->TypeMatchStmt.token = token;
+	result->TypeMatchStmt.tag   = tag;
+	result->TypeMatchStmt.var   = var;
+	result->TypeMatchStmt.body  = body;
+	return result;
+}
+
+AstNode *make_case_clause(AstFile *f, Token token, AstNodeArray list, AstNodeArray stmts) {
+	AstNode *result = make_node(f, AstNode_CaseClause);
+	result->CaseClause.token = token;
+	result->CaseClause.list  = list;
+	result->CaseClause.stmts = stmts;
+	return result;
+}
+
+
+AstNode *make_defer_stmt(AstFile *f, Token token, AstNode *stmt) {
+	AstNode *result = make_node(f, AstNode_DeferStmt);
+	result->DeferStmt.token = token;
+	result->DeferStmt.stmt = stmt;
+	return result;
+}
+
+AstNode *make_branch_stmt(AstFile *f, Token token) {
+	AstNode *result = make_node(f, AstNode_BranchStmt);
+	result->BranchStmt.token = token;
+	return result;
+}
+
+AstNode *make_using_stmt(AstFile *f, Token token, AstNode *node) {
+	AstNode *result = make_node(f, AstNode_UsingStmt);
+	result->UsingStmt.token = token;
+	result->UsingStmt.node  = node;
+	return result;
+}
+
+AstNode *make_asm_operand(AstFile *f, Token string, AstNode *operand) {
+	AstNode *result = make_node(f, AstNode_AsmOperand);
+	result->AsmOperand.string  = string;
+	result->AsmOperand.operand = operand;
+	return result;
+
+}
+
+AstNode *make_asm_stmt(AstFile *f, Token token, bool is_volatile, Token open, Token close, Token code_string,
+                                 AstNode *output_list, AstNode *input_list, AstNode *clobber_list,
+                                 isize output_count, isize input_count, isize clobber_count) {
+	AstNode *result = make_node(f, AstNode_AsmStmt);
+	result->AsmStmt.token = token;
+	result->AsmStmt.is_volatile = is_volatile;
+	result->AsmStmt.open  = open;
+	result->AsmStmt.close = close;
+	result->AsmStmt.code_string = code_string;
+	result->AsmStmt.output_list = output_list;
+	result->AsmStmt.input_list = input_list;
+	result->AsmStmt.clobber_list = clobber_list;
+	result->AsmStmt.output_count = output_count;
+	result->AsmStmt.input_count = input_count;
+	result->AsmStmt.clobber_count = clobber_count;
+	return result;
+}
+
+AstNode *make_push_allocator(AstFile *f, Token token, AstNode *expr, AstNode *body) {
+	AstNode *result = make_node(f, AstNode_PushAllocator);
+	result->PushAllocator.token = token;
+	result->PushAllocator.expr = expr;
+	result->PushAllocator.body = body;
+	return result;
+}
+
+AstNode *make_push_context(AstFile *f, Token token, AstNode *expr, AstNode *body) {
+	AstNode *result = make_node(f, AstNode_PushContext);
+	result->PushContext.token = token;
+	result->PushContext.expr = expr;
+	result->PushContext.body = body;
+	return result;
+}
+
+
+
+
+AstNode *make_bad_decl(AstFile *f, Token begin, Token end) {
+	AstNode *result = make_node(f, AstNode_BadDecl);
+	result->BadDecl.begin = begin;
+	result->BadDecl.end = end;
+	return result;
+}
+
+AstNode *make_var_decl(AstFile *f, AstNodeArray names, AstNode *type, AstNodeArray values) {
+	AstNode *result = make_node(f, AstNode_VarDecl);
+	result->VarDecl.names = names;
+	result->VarDecl.type = type;
+	result->VarDecl.values = values;
+	return result;
+}
+
+AstNode *make_const_decl(AstFile *f, AstNodeArray names, AstNode *type, AstNodeArray values) {
+	AstNode *result = make_node(f, AstNode_ConstDecl);
+	result->ConstDecl.names = names;
+	result->ConstDecl.type = type;
+	result->ConstDecl.values = values;
+	return result;
+}
+
+AstNode *make_parameter(AstFile *f, AstNodeArray names, AstNode *type, bool is_using) {
+	AstNode *result = make_node(f, AstNode_Parameter);
+	result->Parameter.names = names;
+	result->Parameter.type = type;
+	result->Parameter.is_using = is_using;
+	return result;
+}
+
+AstNode *make_proc_type(AstFile *f, Token token, AstNodeArray params, AstNodeArray results) {
+	AstNode *result = make_node(f, AstNode_ProcType);
+	result->ProcType.token = token;
+	result->ProcType.params = params;
+	result->ProcType.results = results;
+	return result;
+}
+
+AstNode *make_proc_decl(AstFile *f, AstNode *name, AstNode *proc_type, AstNode *body, u64 tags, String foreign_name, String link_name) {
+	AstNode *result = make_node(f, AstNode_ProcDecl);
+	result->ProcDecl.name = name;
+	result->ProcDecl.type = proc_type;
+	result->ProcDecl.body = body;
+	result->ProcDecl.tags = tags;
+	result->ProcDecl.foreign_name = foreign_name;
+	result->ProcDecl.link_name = link_name;
+	return result;
+}
+
+AstNode *make_pointer_type(AstFile *f, Token token, AstNode *type) {
+	AstNode *result = make_node(f, AstNode_PointerType);
+	result->PointerType.token = token;
+	result->PointerType.type = type;
+	return result;
+}
+
+AstNode *make_maybe_type(AstFile *f, Token token, AstNode *type) {
+	AstNode *result = make_node(f, AstNode_MaybeType);
+	result->MaybeType.token = token;
+	result->MaybeType.type = type;
+	return result;
+}
+
+AstNode *make_array_type(AstFile *f, Token token, AstNode *count, AstNode *elem) {
+	AstNode *result = make_node(f, AstNode_ArrayType);
+	result->ArrayType.token = token;
+	result->ArrayType.count = count;
+	result->ArrayType.elem = elem;
+	return result;
+}
+
+AstNode *make_vector_type(AstFile *f, Token token, AstNode *count, AstNode *elem) {
+	AstNode *result = make_node(f, AstNode_VectorType);
+	result->VectorType.token = token;
+	result->VectorType.count = count;
+	result->VectorType.elem  = elem;
+	return result;
+}
+
+AstNode *make_struct_type(AstFile *f, Token token, AstNodeArray decls, isize decl_count, bool is_packed, bool is_ordered) {
+	AstNode *result = make_node(f, AstNode_StructType);
+	result->StructType.token = token;
+	result->StructType.decls = decls;
+	result->StructType.decl_count = decl_count;
+	result->StructType.is_packed = is_packed;
+	result->StructType.is_ordered = is_ordered;
+	return result;
+}
+
+
+AstNode *make_union_type(AstFile *f, Token token, AstNodeArray decls, isize decl_count) {
+	AstNode *result = make_node(f, AstNode_UnionType);
+	result->UnionType.token = token;
+	result->UnionType.decls = decls;
+	result->UnionType.decl_count = decl_count;
+	return result;
+}
+
+AstNode *make_raw_union_type(AstFile *f, Token token, AstNodeArray decls, isize decl_count) {
+	AstNode *result = make_node(f, AstNode_RawUnionType);
+	result->RawUnionType.token = token;
+	result->RawUnionType.decls = decls;
+	result->RawUnionType.decl_count = decl_count;
+	return result;
+}
+
+
+AstNode *make_enum_type(AstFile *f, Token token, AstNode *base_type, AstNodeArray fields) {
+	AstNode *result = make_node(f, AstNode_EnumType);
+	result->EnumType.token = token;
+	result->EnumType.base_type = base_type;
+	result->EnumType.fields = fields;
+	return result;
+}
+
+AstNode *make_type_decl(AstFile *f, Token token, AstNode *name, AstNode *type) {
+	AstNode *result = make_node(f, AstNode_TypeDecl);
+	result->TypeDecl.token = token;
+	result->TypeDecl.name = name;
+	result->TypeDecl.type = type;
+	return result;
+}
+
+AstNode *make_import_decl(AstFile *f, Token token, Token relpath, Token import_name, bool is_load) {
+	AstNode *result = make_node(f, AstNode_ImportDecl);
+	result->ImportDecl.token = token;
+	result->ImportDecl.relpath = relpath;
+	result->ImportDecl.import_name = import_name;
+	result->ImportDecl.is_load = is_load;
+	return result;
+}
+
+AstNode *make_foreign_library(AstFile *f, Token token, Token filepath, bool is_system) {
+	AstNode *result = make_node(f, AstNode_ForeignLibrary);
+	result->ForeignLibrary.token = token;
+	result->ForeignLibrary.filepath = filepath;
+	result->ForeignLibrary.is_system = is_system;
+	return result;
+}
+
+bool next_token(AstFile *f) {
+	if (f->curr_token_index+1 < f->tokens.count) {
+		if (f->curr_token.kind != Token_Comment) {
+			f->prev_token = f->curr_token;
+		}
+
+		f->curr_token_index++;
+		f->curr_token = f->tokens.e[f->curr_token_index];
+		if (f->curr_token.kind == Token_Comment) {
+			return next_token(f);
+		}
+		return true;
+	}
+	syntax_error(f->curr_token, "Token is EOF");
+	return false;
+}
+
+Token expect_token(AstFile *f, TokenKind kind) {
+	Token prev = f->curr_token;
+	if (prev.kind != kind) {
+		syntax_error(f->curr_token, "Expected `%.*s`, got `%.*s`",
+		             LIT(token_strings[kind]),
+		             LIT(token_strings[prev.kind]));
+	}
+	next_token(f);
+	return prev;
+}
+
+Token expect_token_after(AstFile *f, TokenKind kind, char *msg) {
+	Token prev = f->curr_token;
+	if (prev.kind != kind) {
+		syntax_error(f->curr_token, "Expected `%.*s` after %s, got `%.*s`",
+		             LIT(token_strings[kind]),
+		             msg,
+		             LIT(token_strings[prev.kind]));
+	}
+	next_token(f);
+	return prev;
+}
+
+
+Token expect_operator(AstFile *f) {
+	Token prev = f->curr_token;
+	if (!gb_is_between(prev.kind, Token__OperatorBegin+1, Token__OperatorEnd-1)) {
+		syntax_error(f->curr_token, "Expected an operator, got `%.*s`",
+		             LIT(token_strings[prev.kind]));
+	}
+	next_token(f);
+	return prev;
+}
+
+Token expect_keyword(AstFile *f) {
+	Token prev = f->curr_token;
+	if (!gb_is_between(prev.kind, Token__KeywordBegin+1, Token__KeywordEnd-1)) {
+		syntax_error(f->curr_token, "Expected a keyword, got `%.*s`",
+		             LIT(token_strings[prev.kind]));
+	}
+	next_token(f);
+	return prev;
+}
+
+bool allow_token(AstFile *f, TokenKind kind) {
+	Token prev = f->curr_token;
+	if (prev.kind == kind) {
+		next_token(f);
+		return true;
+	}
+	return false;
+}
+
+
+bool is_blank_ident(String str) {
+	if (str.len == 1) {
+		return str.text[0] == '_';
+	}
+	return false;
+}
+
+
+// NOTE(bill): Go to next statement to prevent numerous error messages popping up
+void fix_advance_to_next_stmt(AstFile *f) {
+	// TODO(bill): fix_advance_to_next_stmt
+#if 1
+	for (;;) {
+		Token t = f->curr_token;
+		switch (t.kind) {
+		case Token_EOF:
+		case Token_Semicolon:
+			return;
+
+		case Token_if:
+		case Token_return:
+		case Token_for:
+		case Token_match:
+		case Token_defer:
+		case Token_asm:
+		case Token_using:
+
+		case Token_break:
+		case Token_continue:
+		case Token_fallthrough:
+
+		case Token_push_allocator:
+		case Token_push_context:
+
+		case Token_Hash:
+		{
+			if (token_pos_are_equal(t.pos, f->fix_prev_pos) &&
+			    f->fix_count < PARSER_MAX_FIX_COUNT) {
+				f->fix_count++;
+				return;
+			}
+			if (token_pos_cmp(f->fix_prev_pos, t.pos) < 0) {
+				f->fix_prev_pos = t.pos;
+				f->fix_count = 0; // NOTE(bill): Reset
+				return;
+			}
+			// NOTE(bill): Reaching here means there is a parsing bug
+		} break;
+		}
+		next_token(f);
+	}
+#endif
+}
+
+bool expect_semicolon_after_stmt(AstFile *f, AstNode *s) {
+	if (allow_token(f, Token_Semicolon)) {
+		return true;
+	}
+
+	if (f->curr_token.pos.line != f->prev_token.pos.line) {
+		return true;
+	}
+
+	switch (f->curr_token.kind) {
+	case Token_EOF:
+	case Token_CloseBrace:
+		return true;
+	}
+
+	syntax_error(f->curr_token,
+	             "Expected `;` after %.*s, got `%.*s`",
+	             LIT(ast_node_strings[s->kind]), LIT(token_strings[f->curr_token.kind]));
+	fix_advance_to_next_stmt(f);
+	return false;
+}
+
+
+AstNode *    parse_expr(AstFile *f, bool lhs);
+AstNode *    parse_proc_type(AstFile *f);
+AstNodeArray parse_stmt_list(AstFile *f);
+AstNode *    parse_stmt(AstFile *f);
+AstNode *    parse_body(AstFile *f);
+
+AstNode *parse_identifier(AstFile *f) {
+	Token token = f->curr_token;
+	if (token.kind == Token_Identifier) {
+		next_token(f);
+	} else {
+		token.string = str_lit("_");
+		expect_token(f, Token_Identifier);
+	}
+	return make_ident(f, token);
+}
+
+AstNode *parse_tag_expr(AstFile *f, AstNode *expression) {
+	Token token = expect_token(f, Token_Hash);
+	Token name  = expect_token(f, Token_Identifier);
+	return make_tag_expr(f, token, name, expression);
+}
+
+AstNode *parse_tag_stmt(AstFile *f, AstNode *statement) {
+	Token token = expect_token(f, Token_Hash);
+	Token name  = expect_token(f, Token_Identifier);
+	return make_tag_stmt(f, token, name, statement);
+}
+
+AstNode *unparen_expr(AstNode *node) {
+	for (;;) {
+		if (node->kind != AstNode_ParenExpr)
+			return node;
+		node = node->ParenExpr.expr;
+	}
+}
+
+AstNode *parse_value(AstFile *f);
+
+AstNodeArray parse_element_list(AstFile *f) {
+	AstNodeArray elems = make_ast_node_array(f);
+
+	while (f->curr_token.kind != Token_CloseBrace &&
+	       f->curr_token.kind != Token_EOF) {
+		AstNode *elem = parse_value(f);
+		if (f->curr_token.kind == Token_Eq) {
+			Token eq = expect_token(f, Token_Eq);
+			AstNode *value = parse_value(f);
+			elem = make_field_value(f, elem, value, eq);
+		}
+
+		array_add(&elems, elem);
+
+		if (f->curr_token.kind != Token_Comma) {
+			break;
+		}
+		next_token(f);
+	}
+
+	return elems;
+}
+
+AstNode *parse_literal_value(AstFile *f, AstNode *type) {
+	AstNodeArray elems = {0};
+	Token open = expect_token(f, Token_OpenBrace);
+	f->expr_level++;
+	if (f->curr_token.kind != Token_CloseBrace) {
+		elems = parse_element_list(f);
+	}
+	f->expr_level--;
+	Token close = expect_token(f, Token_CloseBrace);
+
+	return make_compound_lit(f, type, elems, open, close);
+}
+
+AstNode *parse_value(AstFile *f) {
+	if (f->curr_token.kind == Token_OpenBrace)
+		return parse_literal_value(f, NULL);
+
+	AstNode *value = parse_expr(f, false);
+	return value;
+}
+
+AstNode *parse_identifier_or_type(AstFile *f, u32 flags);
+
+
+void check_proc_add_tag(AstFile *f, AstNode *tag_expr, u64 *tags, ProcTag tag, String tag_name) {
+	if (*tags & tag) {
+		syntax_error(ast_node_token(tag_expr), "Procedure tag already used: %.*s", LIT(tag_name));
+	}
+	*tags |= tag;
+}
+
+bool is_foreign_name_valid(String name) {
+	// TODO(bill): is_foreign_name_valid
+	if (name.len == 0)
+		return false;
+	isize offset = 0;
+	while (offset < name.len) {
+		Rune rune;
+		isize remaining = name.len - offset;
+		isize width = gb_utf8_decode(name.text+offset, remaining, &rune);
+		if (rune == GB_RUNE_INVALID && width == 1) {
+			return false;
+		} else if (rune == GB_RUNE_BOM && remaining > 0) {
+			return false;
+		}
+
+		if (offset == 0) {
+			switch (rune) {
+			case '-':
+			case '$':
+			case '.':
+			case '_':
+				break;
+			default:
+				if (!gb_char_is_alpha(cast(char)rune))
+					return false;
+				break;
+			}
+		} else {
+			switch (rune) {
+			case '-':
+			case '$':
+			case '.':
+			case '_':
+				break;
+			default:
+				if (!gb_char_is_alphanumeric(cast(char)rune)) {
+					return false;
+				}
+				break;
+			}
+		}
+
+		offset += width;
+	}
+
+	return true;
+}
+
+void parse_proc_tags(AstFile *f, u64 *tags, String *foreign_name, String *link_name) {
+	// TODO(bill): Add this to procedure literals too
+	GB_ASSERT(foreign_name != NULL);
+	GB_ASSERT(link_name    != NULL);
+
+	while (f->curr_token.kind == Token_Hash) {
+		AstNode *tag_expr = parse_tag_expr(f, NULL);
+		ast_node(te, TagExpr, tag_expr);
+		String tag_name = te->name.string;
+
+		#define ELSE_IF_ADD_TAG(name) \
+		else if (str_eq(tag_name, str_lit(#name))) { \
+			check_proc_add_tag(f, tag_expr, tags, ProcTag_##name, tag_name); \
+		}
+
+		if (str_eq(tag_name, str_lit("foreign"))) {
+			check_proc_add_tag(f, tag_expr, tags, ProcTag_foreign, tag_name);
+			if (f->curr_token.kind == Token_String) {
+				*foreign_name = f->curr_token.string;
+				// TODO(bill): Check if valid string
+				if (!is_foreign_name_valid(*foreign_name)) {
+					syntax_error(ast_node_token(tag_expr), "Invalid alternative foreign procedure name: `%.*s`", LIT(*foreign_name));
+				}
+
+				next_token(f);
+			}
+		} else if (str_eq(tag_name, str_lit("link_name"))) {
+			check_proc_add_tag(f, tag_expr, tags, ProcTag_link_name, tag_name);
+			if (f->curr_token.kind == Token_String) {
+				*link_name = f->curr_token.string;
+				// TODO(bill): Check if valid string
+				if (!is_foreign_name_valid(*link_name)) {
+					syntax_error(ast_node_token(tag_expr), "Invalid alternative link procedure name `%.*s`", LIT(*link_name));
+				}
+
+				next_token(f);
+			} else {
+				expect_token(f, Token_String);
+			}
+		}
+		ELSE_IF_ADD_TAG(bounds_check)
+		ELSE_IF_ADD_TAG(no_bounds_check)
+		ELSE_IF_ADD_TAG(inline)
+		ELSE_IF_ADD_TAG(no_inline)
+		ELSE_IF_ADD_TAG(dll_import)
+		ELSE_IF_ADD_TAG(dll_export)
+		ELSE_IF_ADD_TAG(stdcall)
+		ELSE_IF_ADD_TAG(fastcall)
+		// ELSE_IF_ADD_TAG(cdecl)
+		else {
+			syntax_error(ast_node_token(tag_expr), "Unknown procedure tag");
+		}
+
+		#undef ELSE_IF_ADD_TAG
+	}
+
+	if ((*tags & ProcTag_foreign) && (*tags & ProcTag_link_name)) {
+		syntax_error(f->curr_token, "You cannot apply both #foreign and #link_name to a procedure");
+	}
+
+	if ((*tags & ProcTag_inline) && (*tags & ProcTag_no_inline)) {
+		syntax_error(f->curr_token, "You cannot apply both #inline and #no_inline to a procedure");
+	}
+
+	if ((*tags & ProcTag_bounds_check) && (*tags & ProcTag_no_bounds_check)) {
+		syntax_error(f->curr_token, "You cannot apply both #bounds_check and #no_bounds_check to a procedure");
+	}
+
+	if (((*tags & ProcTag_bounds_check) || (*tags & ProcTag_no_bounds_check)) && (*tags & ProcTag_foreign)) {
+		syntax_error(f->curr_token, "You cannot apply both #bounds_check or #no_bounds_check to a procedure without a body");
+	}
+
+	if ((*tags & ProcTag_stdcall) && (*tags & ProcTag_fastcall)) {
+		syntax_error(f->curr_token, "You cannot apply one calling convention to a procedure");
+	}
+}
+
+AstNode *parse_operand(AstFile *f, bool lhs) {
+	AstNode *operand = NULL; // Operand
+	switch (f->curr_token.kind) {
+	case Token_Identifier:
+		operand = parse_identifier(f);
+		if (!lhs) {
+			// TODO(bill): Handle?
+		}
+		return operand;
+
+	case Token_Integer:
+	case Token_Float:
+	case Token_String:
+	case Token_Rune:
+		operand = make_basic_lit(f, f->curr_token);
+		next_token(f);
+		return operand;
+
+	case Token_OpenParen: {
+		Token open, close;
+		// NOTE(bill): Skip the Paren Expression
+		open = expect_token(f, Token_OpenParen);
+		f->expr_level++;
+		operand = parse_expr(f, false);
+		f->expr_level--;
+		close = expect_token(f, Token_CloseParen);
+		return make_paren_expr(f, operand, open, close);
+	}
+
+	case Token_Hash: {
+		Token token = expect_token(f, Token_Hash);
+		Token name  = expect_token(f, Token_Identifier);
+		if (str_eq(name.string, str_lit("rune"))) {
+			if (f->curr_token.kind == Token_String) {
+				Token *s = &f->curr_token;
+
+				if (gb_utf8_strnlen(s->string.text, s->string.len) != 1) {
+					syntax_error(*s, "Invalid rune literal %.*s", LIT(s->string));
+				}
+				s->kind = Token_Rune; // NOTE(bill): Change it
+			} else {
+				expect_token(f, Token_String);
+			}
+			operand = parse_operand(f, lhs);
+		} else if (str_eq(name.string, str_lit("file"))) {
+			Token token = name;
+			token.kind = Token_String;
+			token.string = token.pos.file;
+			return make_basic_lit(f, token);
+		} else if (str_eq(name.string, str_lit("line"))) {
+			Token token = name;
+			token.kind = Token_Integer;
+			char *str = gb_alloc_array(gb_arena_allocator(&f->arena), char, 20);
+			gb_i64_to_str(token.pos.line, str, 10);
+			token.string = make_string_c(str);
+			return make_basic_lit(f, token);
+		} else if (str_eq(name.string, str_lit("run"))) {
+			AstNode *expr = parse_expr(f, false);
+			operand = make_run_expr(f, token, name, expr);
+			if (unparen_expr(expr)->kind != AstNode_CallExpr) {
+				error(ast_node_token(expr), "#run can only be applied to procedure calls");
+				operand = make_bad_expr(f, token, f->curr_token);
+			}
+			warning(token, "#run is not yet implemented");
+		} else {
+			operand = make_tag_expr(f, token, name, parse_expr(f, false));
+		}
+		return operand;
+	}
+
+	// Parse Procedure Type or Literal
+	case Token_proc: {
+		AstNode *curr_proc = f->curr_proc;
+		AstNode *type = parse_proc_type(f);
+		f->curr_proc = type;
+
+		u64 tags = 0;
+		String foreign_name = {0};
+		String link_name = {0};
+		parse_proc_tags(f, &tags, &foreign_name, &link_name);
+		if (tags & ProcTag_foreign) {
+			syntax_error(f->curr_token, "#foreign cannot be applied to procedure literals");
+		}
+		if (tags & ProcTag_link_name) {
+			syntax_error(f->curr_token, "#link_name cannot be applied to procedure literals");
+		}
+
+		if (f->curr_token.kind == Token_OpenBrace) {
+			AstNode *body;
+
+			f->expr_level++;
+			body = parse_body(f);
+			f->expr_level--;
+
+			type = make_proc_lit(f, type, body, tags);
+		}
+		f->curr_proc = curr_proc;
+		return type;
+	}
+
+	default: {
+		AstNode *type = parse_identifier_or_type(f, 0);
+		if (type != NULL) {
+			// NOTE(bill): Sanity check as identifiers should be handled already
+			GB_ASSERT_MSG(type->kind != AstNode_Ident, "Type Cannot be identifier");
+			return type;
+		}
+	}
+	}
+
+	Token begin = f->curr_token;
+	syntax_error(begin, "Expected an operand");
+	fix_advance_to_next_stmt(f);
+	return make_bad_expr(f, begin, f->curr_token);
+}
+
+bool is_literal_type(AstNode *node) {
+	switch (node->kind) {
+	case AstNode_BadExpr:
+	case AstNode_Ident:
+	case AstNode_SelectorExpr:
+	case AstNode_ArrayType:
+	case AstNode_VectorType:
+	case AstNode_StructType:
+		return true;
+	}
+	return false;
+}
+
+AstNode *parse_call_expr(AstFile *f, AstNode *operand) {
+	AstNodeArray args = make_ast_node_array(f);
+	Token open_paren, close_paren;
+	Token ellipsis = {0};
+
+	f->expr_level++;
+	open_paren = expect_token(f, Token_OpenParen);
+
+	while (f->curr_token.kind != Token_CloseParen &&
+	       f->curr_token.kind != Token_EOF &&
+	       ellipsis.pos.line == 0) {
+		if (f->curr_token.kind == Token_Comma)
+			syntax_error(f->curr_token, "Expected an expression not a ,");
+
+		if (f->curr_token.kind == Token_Ellipsis) {
+			ellipsis = f->curr_token;
+			next_token(f);
+		}
+
+		AstNode *arg = parse_expr(f, false);
+		array_add(&args, arg);
+
+		if (f->curr_token.kind != Token_Comma) {
+			if (f->curr_token.kind == Token_CloseParen)
+				break;
+		}
+
+		next_token(f);
+	}
+
+	f->expr_level--;
+	close_paren = expect_token(f, Token_CloseParen);
+
+	return make_call_expr(f, operand, args, open_paren, close_paren, ellipsis);
+}
+
+AstNode *parse_atom_expr(AstFile *f, bool lhs) {
+	AstNode *operand = parse_operand(f, lhs);
+
+	bool loop = true;
+	while (loop) {
+		switch (f->curr_token.kind) {
+
+		case Token_Prime: {
+			Token op = expect_token(f, Token_Prime);
+			if (lhs) {
+				// TODO(bill): Handle this
+			}
+			AstNode *proc = parse_identifier(f);
+			AstNodeArray args;
+			array_init_reserve(&args, gb_arena_allocator(&f->arena), 1);
+			array_add(&args, operand);
+			operand = make_call_expr(f, proc, args, ast_node_token(operand), op, empty_token);
+		} break;
+
+		case Token_OpenParen: {
+			if (lhs) {
+				// TODO(bill): Handle this shit! Is this even allowed in this language?!
+			}
+			operand = parse_call_expr(f, operand);
+		} break;
+
+		case Token_Period: {
+			Token token = f->curr_token;
+			next_token(f);
+			if (lhs) {
+				// TODO(bill): handle this
+			}
+			switch (f->curr_token.kind) {
+			case Token_Identifier:
+				operand = make_selector_expr(f, token, operand, parse_identifier(f));
+				break;
+			default: {
+				syntax_error(f->curr_token, "Expected a selector");
+				next_token(f);
+				operand = make_selector_expr(f, f->curr_token, operand, NULL);
+			} break;
+			}
+		} break;
+
+		case Token_OpenBracket: {
+			if (lhs) {
+				// TODO(bill): Handle this
+			}
+			Token open, close;
+			AstNode *indices[3] = {0};
+
+			f->expr_level++;
+			open = expect_token(f, Token_OpenBracket);
+
+			if (f->curr_token.kind != Token_Colon)
+				indices[0] = parse_expr(f, false);
+			isize colon_count = 0;
+			Token colons[2] = {0};
+
+			while (f->curr_token.kind == Token_Colon && colon_count < 2) {
+				colons[colon_count++] = f->curr_token;
+				next_token(f);
+				if (f->curr_token.kind != Token_Colon &&
+				    f->curr_token.kind != Token_CloseBracket &&
+				    f->curr_token.kind != Token_EOF) {
+					indices[colon_count] = parse_expr(f, false);
+				}
+			}
+
+			f->expr_level--;
+			close = expect_token(f, Token_CloseBracket);
+
+			if (colon_count == 0) {
+				operand = make_index_expr(f, operand, indices[0], open, close);
+			} else {
+				bool triple_indexed = false;
+				if (colon_count == 2) {
+					triple_indexed = true;
+					if (indices[1] == NULL) {
+						syntax_error(colons[0], "Second index is required in a triple indexed slice");
+						indices[1] = make_bad_expr(f, colons[0], colons[1]);
+					}
+					if (indices[2] == NULL) {
+						syntax_error(colons[1], "Third index is required in a triple indexed slice");
+						indices[2] = make_bad_expr(f, colons[1], close);
+					}
+				}
+				operand = make_slice_expr(f, operand, open, close, indices[0], indices[1], indices[2], triple_indexed);
+			}
+		} break;
+
+		case Token_Pointer: // Deference
+			operand = make_deref_expr(f, operand, expect_token(f, Token_Pointer));
+			break;
+
+		case Token_Maybe: // Demaybe
+			operand = make_demaybe_expr(f, operand, expect_token(f, Token_Maybe));
+			break;
+
+		case Token_OpenBrace: {
+			if (!lhs && is_literal_type(operand) && f->expr_level >= 0) {
+				if (f->curr_token.pos.line == f->prev_token.pos.line) {
+					// TODO(bill): This is a hack due to optional semicolons
+					// TODO(bill): It's probably much better to solve this by changing
+					// the syntax for struct literals and array literals
+					operand = parse_literal_value(f, operand);
+				} else {
+					loop = false;
+				}
+			} else {
+				loop = false;
+			}
+		} break;
+
+		default:
+			loop = false;
+			break;
+		}
+
+		lhs = false; // NOTE(bill): 'tis not lhs anymore
+	}
+
+	return operand;
+}
+
+AstNode *parse_type(AstFile *f);
+
+AstNode *parse_unary_expr(AstFile *f, bool lhs) {
+	switch (f->curr_token.kind) {
+	case Token_Pointer:
+	case Token_Maybe:
+	case Token_Add:
+	case Token_Sub:
+	case Token_Not:
+	case Token_Xor: {
+		AstNode *operand;
+		Token op = f->curr_token;
+		next_token(f);
+		operand = parse_unary_expr(f, lhs);
+		return make_unary_expr(f, op, operand);
+	} break;
+	}
+
+	return parse_atom_expr(f, lhs);
+}
+
+// NOTE(bill): result == priority
+i32 token_precedence(Token t) {
+	switch (t.kind) {
+	case Token_CmpOr:
+		return 1;
+	case Token_CmpAnd:
+		return 2;
+	case Token_CmpEq:
+	case Token_NotEq:
+	case Token_Lt:
+	case Token_Gt:
+	case Token_LtEq:
+	case Token_GtEq:
+		return 3;
+	case Token_Add:
+	case Token_Sub:
+	case Token_Or:
+	case Token_Xor:
+		return 4;
+	case Token_Mul:
+	case Token_Quo:
+	case Token_Mod:
+	case Token_And:
+	case Token_AndNot:
+	case Token_Shl:
+	case Token_Shr:
+		return 5;
+	case Token_DoublePrime:
+		return 6;
+	case Token_as:
+	case Token_transmute:
+	case Token_down_cast:
+	case Token_union_cast:
+		return 7;
+	}
+
+	return 0;
+}
+
+AstNode *parse_binary_expr(AstFile *f, bool lhs, i32 prec_in) {
+	AstNode *expression = parse_unary_expr(f, lhs);
+	for (i32 prec = token_precedence(f->curr_token); prec >= prec_in; prec--) {
+		for (;;) {
+			AstNode *right;
+			Token op = f->curr_token;
+			i32 op_prec = token_precedence(op);
+			if (op_prec != prec)
+				break;
+			expect_operator(f); // NOTE(bill): error checks too
+			if (lhs) {
+				// TODO(bill): error checking
+				lhs = false;
+			}
+
+			switch (op.kind) {
+			case Token_DoublePrime: {
+				// TODO(bill): Properly define semantic for in-fix and post-fix calls
+				AstNode *proc = parse_identifier(f);
+				/* if (f->curr_token.kind == Token_OpenParen) {
+					AstNode *call = parse_call_expr(f, proc);
+					array_add(&call->CallExpr.args, expression);
+					for (isize i = gb_array_count(call->CallExpr.args)-1; i > 0; i--) {
+						gb_swap(AstNode *, call->CallExpr.args[i], call->CallExpr.args[i-1]);
+					}
+
+					expression = call;
+				} else  */{
+					right = parse_binary_expr(f, false, prec+1);
+					AstNodeArray args = {0};
+					array_init_reserve(&args, gb_arena_allocator(&f->arena), 2);
+					array_add(&args, expression);
+					array_add(&args, right);
+					expression = make_call_expr(f, proc, args, op, ast_node_token(right), empty_token);
+				}
+				continue;
+			} break;
+
+			case Token_as:
+			case Token_transmute:
+			case Token_down_cast:
+			case Token_union_cast:
+				right = parse_type(f);
+				break;
+
+			default:
+				right = parse_binary_expr(f, false, prec+1);
+				if (!right) {
+					syntax_error(op, "Expected expression on the right hand side of the binary operator");
+				}
+				break;
+			}
+			expression = make_binary_expr(f, op, expression, right);
+		}
+	}
+	return expression;
+}
+
+AstNode *parse_expr(AstFile *f, bool lhs) {
+	return parse_binary_expr(f, lhs, 0+1);
+}
+
+
+AstNodeArray parse_expr_list(AstFile *f, bool lhs) {
+	AstNodeArray list = make_ast_node_array(f);
+	do {
+		AstNode *e = parse_expr(f, lhs);
+		array_add(&list, e);
+		if (f->curr_token.kind != Token_Comma ||
+		    f->curr_token.kind == Token_EOF) {
+		    break;
+		}
+		next_token(f);
+	} while (true);
+
+	return list;
+}
+
+AstNodeArray parse_lhs_expr_list(AstFile *f) {
+	return parse_expr_list(f, true);
+}
+
+AstNodeArray parse_rhs_expr_list(AstFile *f) {
+	return parse_expr_list(f, false);
+}
+
+AstNode *parse_decl(AstFile *f, AstNodeArray names);
+
+AstNode *parse_simple_stmt(AstFile *f) {
+	isize lhs_count = 0, rhs_count = 0;
+	AstNodeArray lhs = parse_lhs_expr_list(f);
+
+
+	AstNode *statement = NULL;
+	Token token = f->curr_token;
+	switch (token.kind) {
+	case Token_Eq:
+	case Token_AddEq:
+	case Token_SubEq:
+	case Token_MulEq:
+	case Token_QuoEq:
+	case Token_ModEq:
+	case Token_AndEq:
+	case Token_OrEq:
+	case Token_XorEq:
+	case Token_ShlEq:
+	case Token_ShrEq:
+	case Token_AndNotEq:
+	case Token_CmpAndEq:
+	case Token_CmpOrEq:
+	{
+		if (f->curr_proc == NULL) {
+			syntax_error(f->curr_token, "You cannot use a simple statement in the file scope");
+			return make_bad_stmt(f, f->curr_token, f->curr_token);
+		}
+		next_token(f);
+		AstNodeArray rhs = parse_rhs_expr_list(f);
+		if (rhs.count == 0) {
+			syntax_error(token, "No right-hand side in assignment statement.");
+			return make_bad_stmt(f, token, f->curr_token);
+		}
+		return make_assign_stmt(f, token, lhs, rhs);
+	} break;
+
+	case Token_Colon: // Declare
+		return parse_decl(f, lhs);
+	}
+
+	if (lhs_count > 1) {
+		syntax_error(token, "Expected 1 expression");
+		return make_bad_stmt(f, token, f->curr_token);
+	}
+
+	token = f->curr_token;
+	switch (token.kind) {
+	case Token_Increment:
+	case Token_Decrement:
+		if (f->curr_proc == NULL) {
+			syntax_error(f->curr_token, "You cannot use a simple statement in the file scope");
+			return make_bad_stmt(f, f->curr_token, f->curr_token);
+		}
+		statement = make_inc_dec_stmt(f, token, lhs.e[0]);
+		next_token(f);
+		return statement;
+	}
+
+	return make_expr_stmt(f, lhs.e[0]);
+}
+
+
+
+AstNode *parse_block_stmt(AstFile *f) {
+	if (f->curr_proc == NULL) {
+		syntax_error(f->curr_token, "You cannot use a block statement in the file scope");
+		return make_bad_stmt(f, f->curr_token, f->curr_token);
+	}
+	AstNode *block_stmt = parse_body(f);
+	return block_stmt;
+}
+
+AstNode *convert_stmt_to_expr(AstFile *f, AstNode *statement, String kind) {
+	if (statement == NULL)
+		return NULL;
+
+	if (statement->kind == AstNode_ExprStmt)
+		return statement->ExprStmt.expr;
+
+	syntax_error(f->curr_token, "Expected `%.*s`, found a simple statement.", LIT(kind));
+	return make_bad_expr(f, f->curr_token, f->tokens.e[f->curr_token_index+1]);
+}
+
+AstNodeArray parse_identfier_list(AstFile *f) {
+	AstNodeArray list = make_ast_node_array(f);
+
+	do {
+		array_add(&list, parse_identifier(f));
+		if (f->curr_token.kind != Token_Comma ||
+		    f->curr_token.kind == Token_EOF) {
+		    break;
+		}
+		next_token(f);
+	} while (true);
+
+	return list;
+}
+
+
+
+AstNode *parse_type_attempt(AstFile *f) {
+	AstNode *type = parse_identifier_or_type(f, 0);
+	if (type != NULL) {
+		// TODO(bill): Handle?
+	}
+	return type;
+}
+
+AstNode *parse_type(AstFile *f) {
+	AstNode *type = parse_type_attempt(f);
+	if (type == NULL) {
+		Token token = f->curr_token;
+		syntax_error(token, "Expected a type");
+		next_token(f);
+		return make_bad_expr(f, token, f->curr_token);
+	}
+	return type;
+}
+
+
+Token parse_procedure_signature(AstFile *f,
+                                AstNodeArray *params, AstNodeArray *results);
+
+AstNode *parse_proc_type(AstFile *f) {
+	AstNodeArray params = {0};
+	AstNodeArray results = {0};
+
+	Token proc_token = parse_procedure_signature(f, &params, &results);
+
+	return make_proc_type(f, proc_token, params, results);
+}
+
+
+AstNodeArray parse_parameter_list(AstFile *f) {
+	AstNodeArray params = make_ast_node_array(f);
+
+	while (f->curr_token.kind == Token_Identifier ||
+	       f->curr_token.kind == Token_using) {
+		bool is_using = false;
+		if (allow_token(f, Token_using)) {
+			is_using = true;
+		}
+
+		AstNodeArray names = parse_lhs_expr_list(f);
+		if (names.count == 0) {
+			syntax_error(f->curr_token, "Empty parameter declaration");
+		}
+
+		if (names.count > 1 && is_using) {
+			syntax_error(f->curr_token, "Cannot apply `using` to more than one of the same type");
+			is_using = false;
+		}
+
+		expect_token_after(f, Token_Colon, "parameter list");
+
+		AstNode *type = NULL;
+		if (f->curr_token.kind == Token_Ellipsis) {
+			Token ellipsis = f->curr_token;
+			next_token(f);
+			type = parse_type_attempt(f);
+			if (type == NULL) {
+				syntax_error(f->curr_token, "variadic parameter is missing a type after `..`");
+				type = make_bad_expr(f, ellipsis, f->curr_token);
+			} else {
+				if (names.count > 1) {
+					syntax_error(f->curr_token, "mutliple variadic parameters, only  `..`");
+				} else {
+					type = make_ellipsis(f, ellipsis, type);
+				}
+			}
+		} else {
+			type = parse_type_attempt(f);
+		}
+
+
+		if (type == NULL) {
+			syntax_error(f->curr_token, "Expected a type for this parameter declaration");
+		}
+
+		array_add(&params, make_parameter(f, names, type, is_using));
+		if (f->curr_token.kind != Token_Comma) {
+			break;
+		}
+		next_token(f);
+	}
+
+	return params;
+}
+
+
+AstNodeArray parse_struct_params(AstFile *f, isize *decl_count_, bool using_allowed) {
+	AstNodeArray decls = make_ast_node_array(f);
+	isize decl_count = 0;
+
+	while (f->curr_token.kind == Token_Identifier ||
+	       f->curr_token.kind == Token_using) {
+		bool is_using = false;
+		if (allow_token(f, Token_using)) {
+			is_using = true;
+		}
+		AstNodeArray names = parse_lhs_expr_list(f);
+		if (names.count == 0) {
+			syntax_error(f->curr_token, "Empty field declaration");
+		}
+
+		if (!using_allowed && is_using) {
+			syntax_error(f->curr_token, "Cannot apply `using` to members of a union");
+			is_using = false;
+		}
+		if (names.count > 1 && is_using) {
+			syntax_error(f->curr_token, "Cannot apply `using` to more than one of the same type");
+		}
+
+		AstNode *decl = NULL;
+
+		if (f->curr_token.kind == Token_Colon) {
+			decl = parse_decl(f, names);
+
+			if (decl->kind == AstNode_ProcDecl) {
+				syntax_error(f->curr_token, "Procedure declarations are not allowed within a structure");
+				decl = make_bad_decl(f, ast_node_token(names.e[0]), f->curr_token);
+			}
+		} else {
+			syntax_error(f->curr_token, "Illegal structure field");
+			decl = make_bad_decl(f, ast_node_token(names.e[0]), f->curr_token);
+		}
+
+		expect_semicolon_after_stmt(f, decl);
+
+		if (is_ast_node_decl(decl)) {
+			array_add(&decls, decl);
+			if (decl->kind == AstNode_VarDecl) {
+				decl->VarDecl.is_using = is_using && using_allowed;
+				if (decl->VarDecl.values.count > 0) {
+					syntax_error(f->curr_token, "Default variable assignments within a structure will be ignored (at the moment)");
+				}
+			} else {
+				decl_count += 1;
+			}
+		}
+	}
+
+	if (decl_count_) *decl_count_ = decl_count;
+
+	return decls;
+}
+
+AstNode *parse_identifier_or_type(AstFile *f, u32 flags) {
+	switch (f->curr_token.kind) {
+	case Token_Identifier: {
+		AstNode *e = parse_identifier(f);
+		while (f->curr_token.kind == Token_Period) {
+			Token token = f->curr_token;
+			next_token(f);
+			AstNode *sel = parse_identifier(f);
+			e = make_selector_expr(f, token, e, sel);
+		}
+		if (f->curr_token.kind == Token_OpenParen) {
+			// HACK NOTE(bill): For type_of_val(expr)
+			e = parse_call_expr(f, e);
+		}
+		return e;
+	}
+
+	case Token_Pointer: {
+		Token token = expect_token(f, Token_Pointer);
+		AstNode *elem = parse_type(f);
+		return make_pointer_type(f, token, elem);
+	}
+
+	case Token_Maybe: {
+		Token token = expect_token(f, Token_Maybe);
+		AstNode *elem = parse_type(f);
+		return make_maybe_type(f, token, elem);
+	}
+
+	case Token_OpenBracket: {
+		f->expr_level++;
+		Token token = expect_token(f, Token_OpenBracket);
+		AstNode *count_expr = NULL;
+
+		if (f->curr_token.kind == Token_Ellipsis) {
+			count_expr = make_ellipsis(f, f->curr_token, NULL);
+			next_token(f);
+		} else if (f->curr_token.kind != Token_CloseBracket) {
+			count_expr = parse_expr(f, false);
+		}
+		expect_token(f, Token_CloseBracket);
+		f->expr_level--;
+		AstNode *e = make_array_type(f, token, count_expr, parse_type(f));
+		return e;
+	}
+
+	case Token_OpenBrace: {
+		f->expr_level++;
+		Token token = expect_token(f, Token_OpenBrace);
+		AstNode *count_expr = parse_expr(f, false);
+		expect_token(f, Token_CloseBrace);
+		f->expr_level--;
+		return make_vector_type(f, token, count_expr, parse_type(f));
+	}
+
+	case Token_struct: {
+		Token token = expect_token(f, Token_struct);
+		bool is_packed = false;
+		bool is_ordered = false;
+		while (allow_token(f, Token_Hash)) {
+			Token tag = expect_token_after(f, Token_Identifier, "`#`");
+			if (str_eq(tag.string, str_lit("packed"))) {
+				if (is_packed) {
+					syntax_error(tag, "Duplicate struct tag `#%.*s`", LIT(tag.string));
+				}
+				is_packed = true;
+			} else if (str_eq(tag.string, str_lit("ordered"))) {
+				if (is_ordered) {
+					syntax_error(tag, "Duplicate struct tag `#%.*s`", LIT(tag.string));
+				}
+				is_ordered = true;
+			} else {
+				syntax_error(tag, "Invalid struct tag `#%.*s`", LIT(tag.string));
+			}
+		}
+
+		if (is_packed && is_ordered) {
+			syntax_error(token, "`#ordered` is not needed with `#packed` which implies ordering");
+		}
+
+		Token open = expect_token_after(f, Token_OpenBrace, "`struct`");
+		isize decl_count = 0;
+		AstNodeArray decls = parse_struct_params(f, &decl_count, true);
+		Token close = expect_token(f, Token_CloseBrace);
+
+		return make_struct_type(f, token, decls, decl_count, is_packed, is_ordered);
+	} break;
+
+	case Token_union: {
+		Token token = expect_token(f, Token_union);
+		Token open = expect_token_after(f, Token_OpenBrace, "`union`");
+		isize decl_count = 0;
+		AstNodeArray decls = parse_struct_params(f, &decl_count, false);
+		Token close = expect_token(f, Token_CloseBrace);
+
+		return make_union_type(f, token, decls, decl_count);
+	}
+
+	case Token_raw_union: {
+		Token token = expect_token(f, Token_raw_union);
+		Token open = expect_token_after(f, Token_OpenBrace, "`raw_union`");
+		isize decl_count = 0;
+		AstNodeArray decls = parse_struct_params(f, &decl_count, true);
+		Token close = expect_token(f, Token_CloseBrace);
+
+		return make_raw_union_type(f, token, decls, decl_count);
+	}
+
+	case Token_enum: {
+		Token token = expect_token(f, Token_enum);
+		AstNode *base_type = NULL;
+		Token open, close;
+
+		if (f->curr_token.kind != Token_OpenBrace) {
+			base_type = parse_type(f);
+		}
+
+		AstNodeArray fields = make_ast_node_array(f);
+
+		open = expect_token_after(f, Token_OpenBrace, "`enum`");
+
+		while (f->curr_token.kind != Token_CloseBrace &&
+		       f->curr_token.kind != Token_EOF) {
+			AstNode *name = parse_identifier(f);
+			AstNode *value = NULL;
+			Token eq = empty_token;
+			if (f->curr_token.kind == Token_Eq) {
+				eq = expect_token(f, Token_Eq);
+				value = parse_value(f);
+			}
+			AstNode *field = make_field_value(f, name, value, eq);
+			array_add(&fields, field);
+			if (f->curr_token.kind != Token_Comma) {
+				break;
+			}
+			next_token(f);
+		}
+
+		close = expect_token(f, Token_CloseBrace);
+
+		return make_enum_type(f, token, base_type, fields);
+	}
+
+	case Token_proc:
+		return parse_proc_type(f);
+
+	case Token_OpenParen: {
+		// NOTE(bill): Skip the paren expression
+		AstNode *type;
+		Token open, close;
+		open = expect_token(f, Token_OpenParen);
+		type = parse_type(f);
+		close = expect_token(f, Token_CloseParen);
+		return type;
+		// return make_paren_expr(f, type, open, close);
+	}
+
+	// TODO(bill): Why is this even allowed? Is this a parsing error?
+	case Token_Colon:
+		break;
+
+	case Token_Eq:
+		if (f->prev_token.kind == Token_Colon)
+			break;
+		// fallthrough
+	default:
+		syntax_error(f->curr_token,
+		             "Expected a type or identifier after `%.*s`, got `%.*s`", LIT(f->prev_token.string), LIT(f->curr_token.string));
+		break;
+	}
+
+	return NULL;
+}
+
+
+AstNodeArray parse_results(AstFile *f) {
+	AstNodeArray results = make_ast_node_array(f);
+	if (allow_token(f, Token_ArrowRight)) {
+		if (f->curr_token.kind == Token_OpenParen) {
+			expect_token(f, Token_OpenParen);
+			while (f->curr_token.kind != Token_CloseParen &&
+			       f->curr_token.kind != Token_EOF) {
+				array_add(&results, parse_type(f));
+				if (f->curr_token.kind != Token_Comma) {
+					break;
+				}
+				next_token(f);
+			}
+			expect_token(f, Token_CloseParen);
+
+			return results;
+		}
+
+		array_add(&results, parse_type(f));
+		return results;
+	}
+	return results;
+}
+
+Token parse_procedure_signature(AstFile *f,
+                               AstNodeArray *params,
+                               AstNodeArray *results) {
+	Token proc_token = expect_token(f, Token_proc);
+	expect_token(f, Token_OpenParen);
+	*params = parse_parameter_list(f);
+	expect_token_after(f, Token_CloseParen, "parameter list");
+	*results = parse_results(f);
+	return proc_token;
+}
+
+AstNode *parse_body(AstFile *f) {
+	AstNodeArray stmts = {0};
+	Token open, close;
+	open = expect_token(f, Token_OpenBrace);
+	stmts = parse_stmt_list(f);
+	close = expect_token(f, Token_CloseBrace);
+
+	return make_block_stmt(f, stmts, open, close);
+}
+
+
+
+AstNode *parse_proc_decl(AstFile *f, Token proc_token, AstNode *name) {
+	AstNodeArray params = {0};
+	AstNodeArray results = {0};
+
+	parse_procedure_signature(f, &params, &results);
+	AstNode *proc_type = make_proc_type(f, proc_token, params, results);
+
+	AstNode *body = NULL;
+	u64 tags = 0;
+	String foreign_name = {0};
+	String link_name = {0};
+
+	parse_proc_tags(f, &tags, &foreign_name, &link_name);
+
+	AstNode *curr_proc = f->curr_proc;
+	f->curr_proc = proc_type;
+
+	if (f->curr_token.kind == Token_OpenBrace) {
+		if ((tags & ProcTag_foreign) != 0) {
+			syntax_error(f->curr_token, "A procedure tagged as `#foreign` cannot have a body");
+		}
+		body = parse_body(f);
+	}
+
+	f->curr_proc = curr_proc;
+	return make_proc_decl(f, name, proc_type, body, tags, foreign_name, link_name);
+}
+
+AstNode *parse_decl(AstFile *f, AstNodeArray names) {
+	AstNodeArray values = {0};
+	AstNode *type = NULL;
+
+	for_array(i, names) {
+		AstNode *name = names.e[i];
+		if (name->kind == AstNode_Ident) {
+			String n = name->Ident.string;
+			// NOTE(bill): Check for reserved identifiers
+			if (str_eq(n, str_lit("context"))) {
+				syntax_error(ast_node_token(name), "`context` is a reserved identifier");
+				break;
+			}
+		}
+	}
+
+	if (allow_token(f, Token_Colon)) {
+		if (!allow_token(f, Token_type)) {
+			type = parse_identifier_or_type(f, 0);
+		}
+	} else if (f->curr_token.kind != Token_Eq && f->curr_token.kind != Token_Semicolon) {
+		syntax_error(f->curr_token, "Expected type separator `:` or `=`");
+	}
+
+	bool is_mutable = true;
+
+	if (f->curr_token.kind == Token_Eq ||
+	    f->curr_token.kind == Token_Colon) {
+		if (f->curr_token.kind == Token_Colon) {
+			is_mutable = false;
+		}
+		next_token(f);
+
+		if (f->curr_token.kind == Token_type ||
+		    f->curr_token.kind == Token_struct ||
+		    f->curr_token.kind == Token_enum ||
+		    f->curr_token.kind == Token_union ||
+		    f->curr_token.kind == Token_raw_union) {
+			Token token = f->curr_token;
+			if (token.kind == Token_type) {
+				next_token(f);
+			}
+			if (names.count != 1) {
+				syntax_error(ast_node_token(names.e[0]), "You can only declare one type at a time");
+				return make_bad_decl(f, names.e[0]->Ident, token);
+			}
+
+			if (type != NULL) {
+				syntax_error(f->prev_token, "Expected either `type` or nothing between : and :");
+				// NOTE(bill): Do not fail though
+			}
+
+			return make_type_decl(f, token, names.e[0], parse_type(f));
+		} else if (f->curr_token.kind == Token_proc &&
+		    is_mutable == false) {
+		    // NOTE(bill): Procedure declarations
+			Token proc_token = f->curr_token;
+			AstNode *name = names.e[0];
+			if (names.count != 1) {
+				syntax_error(proc_token, "You can only declare one procedure at a time");
+				return make_bad_decl(f, name->Ident, proc_token);
+			}
+
+			return parse_proc_decl(f, proc_token, name);
+
+		} else {
+			values = parse_rhs_expr_list(f);
+			if (values.count > names.count) {
+				syntax_error(f->curr_token, "Too many values on the right hand side of the declaration");
+			} else if (values.count < names.count && !is_mutable) {
+				syntax_error(f->curr_token, "All constant declarations must be defined");
+			} else if (values.count == 0) {
+				syntax_error(f->curr_token, "Expected an expression for this declaration");
+			}
+		}
+	}
+
+	if (is_mutable) {
+		if (type == NULL && values.count == 0) {
+			syntax_error(f->curr_token, "Missing variable type or initialization");
+			return make_bad_decl(f, f->curr_token, f->curr_token);
+		}
+	} else {
+		if (type == NULL && values.count == 0 && names.count > 0) {
+			syntax_error(f->curr_token, "Missing constant value");
+			return make_bad_decl(f, f->curr_token, f->curr_token);
+		}
+	}
+
+	if (values.e == NULL) {
+		values = make_ast_node_array(f);
+	}
+
+	if (is_mutable) {
+		return make_var_decl(f, names, type, values);
+	}
+	return make_const_decl(f, names, type, values);
+}
+
+
+AstNode *parse_if_stmt(AstFile *f) {
+	if (f->curr_proc == NULL) {
+		syntax_error(f->curr_token, "You cannot use an if statement in the file scope");
+		return make_bad_stmt(f, f->curr_token, f->curr_token);
+	}
+
+	Token token = expect_token(f, Token_if);
+	AstNode *init = NULL;
+	AstNode *cond = NULL;
+	AstNode *body = NULL;
+	AstNode *else_stmt = NULL;
+
+	isize prev_level = f->expr_level;
+	f->expr_level = -1;
+
+
+	if (allow_token(f, Token_Semicolon)) {
+		cond = parse_expr(f, false);
+	} else {
+		init = parse_simple_stmt(f);
+		if (allow_token(f, Token_Semicolon)) {
+			cond = parse_expr(f, false);
+		} else {
+			cond = convert_stmt_to_expr(f, init, str_lit("boolean expression"));
+			init = NULL;
+		}
+	}
+
+	f->expr_level = prev_level;
+
+	if (cond == NULL) {
+		syntax_error(f->curr_token, "Expected condition for if statement");
+	}
+
+	body = parse_block_stmt(f);
+
+	if (allow_token(f, Token_else)) {
+		switch (f->curr_token.kind) {
+		case Token_if:
+			else_stmt = parse_if_stmt(f);
+			break;
+		case Token_OpenBrace:
+			else_stmt = parse_block_stmt(f);
+			break;
+		default:
+			syntax_error(f->curr_token, "Expected if statement block statement");
+			else_stmt = make_bad_stmt(f, f->curr_token, f->tokens.e[f->curr_token_index+1]);
+			break;
+		}
+	}
+
+	return make_if_stmt(f, token, init, cond, body, else_stmt);
+}
+
+AstNode *parse_return_stmt(AstFile *f) {
+	if (f->curr_proc == NULL) {
+		syntax_error(f->curr_token, "You cannot use a return statement in the file scope");
+		return make_bad_stmt(f, f->curr_token, f->curr_token);
+	}
+
+	Token token = expect_token(f, Token_return);
+	AstNodeArray results = make_ast_node_array(f);
+
+	if (f->curr_token.kind != Token_Semicolon && f->curr_token.kind != Token_CloseBrace &&
+	    f->curr_token.pos.line == token.pos.line) {
+		results = parse_rhs_expr_list(f);
+	}
+	if (f->curr_token.kind != Token_CloseBrace) {
+		expect_semicolon_after_stmt(f, results.e[0]);
+	}
+
+	return make_return_stmt(f, token, results);
+}
+
+AstNode *parse_for_stmt(AstFile *f) {
+	if (f->curr_proc == NULL) {
+		syntax_error(f->curr_token, "You cannot use a for statement in the file scope");
+		return make_bad_stmt(f, f->curr_token, f->curr_token);
+	}
+
+	Token token = expect_token(f, Token_for);
+
+	AstNode *init = NULL;
+	AstNode *cond = NULL;
+	AstNode *end  = NULL;
+	AstNode *body = NULL;
+
+	if (f->curr_token.kind != Token_OpenBrace) {
+		isize prev_level = f->expr_level;
+		f->expr_level = -1;
+		if (f->curr_token.kind != Token_Semicolon) {
+			cond = parse_simple_stmt(f);
+			if (is_ast_node_complex_stmt(cond)) {
+				syntax_error(f->curr_token,
+				             "You are not allowed that type of statement in a for statement, it is too complex!");
+			}
+		}
+
+		if (allow_token(f, Token_Semicolon)) {
+			init = cond;
+			cond = NULL;
+			if (f->curr_token.kind != Token_Semicolon) {
+				cond = parse_simple_stmt(f);
+			}
+			expect_token(f, Token_Semicolon);
+			if (f->curr_token.kind != Token_OpenBrace) {
+				end = parse_simple_stmt(f);
+			}
+		}
+		f->expr_level = prev_level;
+	}
+	body = parse_block_stmt(f);
+
+	cond = convert_stmt_to_expr(f, cond, str_lit("boolean expression"));
+
+	return make_for_stmt(f, token, init, cond, end, body);
+}
+
+AstNode *parse_case_clause(AstFile *f) {
+	Token token = f->curr_token;
+	AstNodeArray list = make_ast_node_array(f);
+	if (allow_token(f, Token_case)) {
+		list = parse_rhs_expr_list(f);
+	} else {
+		expect_token(f, Token_default);
+	}
+	expect_token(f, Token_Colon); // TODO(bill): Is this the best syntax?
+	// expect_token(f, Token_ArrowRight); // TODO(bill): Is this the best syntax?
+	AstNodeArray stmts = parse_stmt_list(f);
+
+	return make_case_clause(f, token, list, stmts);
+}
+
+
+AstNode *parse_type_case_clause(AstFile *f) {
+	Token token = f->curr_token;
+	AstNodeArray clause = make_ast_node_array(f);
+	if (allow_token(f, Token_case)) {
+		array_add(&clause, parse_type(f));
+	} else {
+		expect_token(f, Token_default);
+	}
+	expect_token(f, Token_Colon); // TODO(bill): Is this the best syntax?
+	// expect_token(f, Token_ArrowRight); // TODO(bill): Is this the best syntax?
+	AstNodeArray stmts = parse_stmt_list(f);
+
+	return make_case_clause(f, token, clause, stmts);
+}
+
+
+AstNode *parse_match_stmt(AstFile *f) {
+	if (f->curr_proc == NULL) {
+		syntax_error(f->curr_token, "You cannot use a match statement in the file scope");
+		return make_bad_stmt(f, f->curr_token, f->curr_token);
+	}
+
+	Token token = expect_token(f, Token_match);
+	AstNode *init = NULL;
+	AstNode *tag  = NULL;
+	AstNode *body = NULL;
+	Token open, close;
+
+	if (allow_token(f, Token_type)) {
+		isize prev_level = f->expr_level;
+		f->expr_level = -1;
+
+		AstNode *var = parse_identifier(f);
+		expect_token(f, Token_Colon);
+		tag = parse_simple_stmt(f);
+
+		f->expr_level = prev_level;
+
+		open = expect_token(f, Token_OpenBrace);
+		AstNodeArray list = make_ast_node_array(f);
+
+		while (f->curr_token.kind == Token_case ||
+		       f->curr_token.kind == Token_default) {
+			array_add(&list, parse_type_case_clause(f));
+		}
+
+		close = expect_token(f, Token_CloseBrace);
+		body = make_block_stmt(f, list, open, close);
+
+		tag = convert_stmt_to_expr(f, tag, str_lit("type match expression"));
+		return make_type_match_stmt(f, token, tag, var, body);
+	} else {
+		if (f->curr_token.kind != Token_OpenBrace) {
+			isize prev_level = f->expr_level;
+			f->expr_level = -1;
+			if (f->curr_token.kind != Token_Semicolon) {
+				tag = parse_simple_stmt(f);
+			}
+			if (allow_token(f, Token_Semicolon)) {
+				init = tag;
+				tag = NULL;
+				if (f->curr_token.kind != Token_OpenBrace) {
+					tag = parse_simple_stmt(f);
+				}
+			}
+
+			f->expr_level = prev_level;
+		}
+
+		open = expect_token(f, Token_OpenBrace);
+		AstNodeArray list = make_ast_node_array(f);
+
+		while (f->curr_token.kind == Token_case ||
+		       f->curr_token.kind == Token_default) {
+			array_add(&list, parse_case_clause(f));
+		}
+
+		close = expect_token(f, Token_CloseBrace);
+
+		body = make_block_stmt(f, list, open, close);
+
+		tag = convert_stmt_to_expr(f, tag, str_lit("match expression"));
+		return make_match_stmt(f, token, init, tag, body);
+	}
+}
+
+
+AstNode *parse_defer_stmt(AstFile *f) {
+	if (f->curr_proc == NULL) {
+		syntax_error(f->curr_token, "You cannot use a defer statement in the file scope");
+		return make_bad_stmt(f, f->curr_token, f->curr_token);
+	}
+
+	Token token = expect_token(f, Token_defer);
+	AstNode *statement = parse_stmt(f);
+	switch (statement->kind) {
+	case AstNode_EmptyStmt:
+		syntax_error(token, "Empty statement after defer (e.g. `;`)");
+		break;
+	case AstNode_DeferStmt:
+		syntax_error(token, "You cannot defer a defer statement");
+		break;
+	case AstNode_ReturnStmt:
+		syntax_error(token, "You cannot a return statement");
+		break;
+	}
+
+	return make_defer_stmt(f, token, statement);
+}
+
+AstNode *parse_asm_stmt(AstFile *f) {
+	Token token = expect_token(f, Token_asm);
+	bool is_volatile = false;
+	if (allow_token(f, Token_volatile)) {
+		is_volatile = true;
+	}
+	Token open, close, code_string;
+	open = expect_token(f, Token_OpenBrace);
+	code_string = expect_token(f, Token_String);
+	AstNode *output_list = NULL;
+	AstNode *input_list = NULL;
+	AstNode *clobber_list = NULL;
+	isize output_count = 0;
+	isize input_count = 0;
+	isize clobber_count = 0;
+
+	// TODO(bill): Finish asm statement and determine syntax
+
+	// if (f->curr_token.kind != Token_CloseBrace) {
+		// expect_token(f, Token_Colon);
+	// }
+
+	close = expect_token(f, Token_CloseBrace);
+
+	return make_asm_stmt(f, token, is_volatile, open, close, code_string,
+	                     output_list, input_list, clobber_list,
+	                     output_count, input_count, clobber_count);
+
+}
+
+
+
+AstNode *parse_stmt(AstFile *f) {
+	AstNode *s = NULL;
+	Token token = f->curr_token;
+	switch (token.kind) {
+	case Token_Comment:
+		next_token(f);
+		return parse_stmt(f);
+
+	// Operands
+	case Token_Identifier:
+	case Token_Integer:
+	case Token_Float:
+	case Token_Rune:
+	case Token_String:
+	case Token_OpenParen:
+	case Token_proc:
+	// Unary Operators
+	case Token_Add:
+	case Token_Sub:
+	case Token_Xor:
+	case Token_Not:
+		s = parse_simple_stmt(f);
+		expect_semicolon_after_stmt(f, s);
+		return s;
+
+	// TODO(bill): other keywords
+	case Token_if:     return parse_if_stmt(f);
+	case Token_return: return parse_return_stmt(f);
+	case Token_for:    return parse_for_stmt(f);
+	case Token_match:  return parse_match_stmt(f);
+	case Token_defer:  return parse_defer_stmt(f);
+	case Token_asm:    return parse_asm_stmt(f);
+
+	case Token_break:
+	case Token_continue:
+	case Token_fallthrough:
+		next_token(f);
+		s = make_branch_stmt(f, token);
+		expect_semicolon_after_stmt(f, s);
+		return s;
+
+
+	case Token_using: {
+		AstNode *node = NULL;
+
+		next_token(f);
+		node = parse_stmt(f);
+
+		bool valid = false;
+
+		switch (node->kind) {
+		case AstNode_ExprStmt: {
+			AstNode *e = unparen_expr(node->ExprStmt.expr);
+			while (e->kind == AstNode_SelectorExpr) {
+				e = unparen_expr(e->SelectorExpr.selector);
+			}
+			if (e->kind == AstNode_Ident) {
+				valid = true;
+			}
+		} break;
+		case AstNode_VarDecl:
+			valid = true;
+			break;
+		}
+
+		if (!valid) {
+			syntax_error(token, "Illegal use of `using` statement.");
+			return make_bad_stmt(f, token, f->curr_token);
+		}
+
+
+		return make_using_stmt(f, token, node);
+	} break;
+
+	case Token_push_allocator: {
+		next_token(f);
+		isize prev_level = f->expr_level;
+		f->expr_level = -1;
+		AstNode *expr = parse_expr(f, false);
+		f->expr_level = prev_level;
+
+		AstNode *body = parse_block_stmt(f);
+		return make_push_allocator(f, token, expr, body);
+	} break;
+
+	case Token_push_context: {
+		next_token(f);
+		isize prev_level = f->expr_level;
+		f->expr_level = -1;
+		AstNode *expr = parse_expr(f, false);
+		f->expr_level = prev_level;
+
+		AstNode *body = parse_block_stmt(f);
+		return make_push_context(f, token, expr, body);
+	} break;
+
+	case Token_Hash: {
+		s = parse_tag_stmt(f, NULL);
+		String tag = s->TagStmt.name.string;
+		if (str_eq(tag, str_lit("shared_global_scope"))) {
+			if (f->curr_proc == NULL) {
+				f->is_global_scope = true;
+				return make_empty_stmt(f, f->curr_token);
+			}
+			syntax_error(token, "You cannot use #shared_global_scope within a procedure. This must be done at the file scope");
+			return make_bad_decl(f, token, f->curr_token);
+		} else if (str_eq(tag, str_lit("import"))) {
+			// TODO(bill): better error messages
+			Token import_name = {0};
+			Token file_path = expect_token_after(f, Token_String, "#import");
+			if (allow_token(f, Token_as)) {
+				// NOTE(bill): Custom import name
+				if (f->curr_token.kind == Token_Period) {
+					import_name = f->curr_token;
+					import_name.kind = Token_Identifier;
+					next_token(f);
+				} else {
+					import_name = expect_token_after(f, Token_Identifier, "`as` for import declaration");
+				}
+
+				if (str_eq(import_name.string, str_lit("_"))) {
+					syntax_error(token, "Illegal import name: `_`");
+					return make_bad_decl(f, token, f->curr_token);
+				}
+			}
+
+			if (f->curr_proc == NULL) {
+				return make_import_decl(f, s->TagStmt.token, file_path, import_name, false);
+			}
+			syntax_error(token, "You cannot use #import within a procedure. This must be done at the file scope");
+			return make_bad_decl(f, token, file_path);
+		} else if (str_eq(tag, str_lit("load"))) {
+			// TODO(bill): better error messages
+			Token file_path = expect_token(f, Token_String);
+			Token import_name = file_path;
+			import_name.string = str_lit(".");
+
+			if (f->curr_proc == NULL) {
+				return make_import_decl(f, s->TagStmt.token, file_path, import_name, true);
+			}
+			syntax_error(token, "You cannot use #load within a procedure. This must be done at the file scope");
+			return make_bad_decl(f, token, file_path);
+		} else if (str_eq(tag, str_lit("foreign_system_library"))) {
+			Token file_path = expect_token(f, Token_String);
+			if (f->curr_proc == NULL) {
+				return make_foreign_library(f, s->TagStmt.token, file_path, true);
+			}
+			syntax_error(token, "You cannot use #foreign_system_library within a procedure. This must be done at the file scope");
+			return make_bad_decl(f, token, file_path);
+		} else if (str_eq(tag, str_lit("foreign_library"))) {
+			Token file_path = expect_token(f, Token_String);
+			if (f->curr_proc == NULL) {
+				return make_foreign_library(f, s->TagStmt.token, file_path, false);
+			}
+			syntax_error(token, "You cannot use #foreign_library within a procedure. This must be done at the file scope");
+			return make_bad_decl(f, token, file_path);
+		} else if (str_eq(tag, str_lit("thread_local"))) {
+			AstNode *var_decl = parse_simple_stmt(f);
+			if (var_decl->kind != AstNode_VarDecl) {
+				syntax_error(token, "#thread_local may only be applied to variable declarations");
+				return make_bad_decl(f, token, ast_node_token(var_decl));
+			}
+			if (f->curr_proc != NULL) {
+				syntax_error(token, "#thread_local is only allowed at the file scope");
+				return make_bad_decl(f, token, ast_node_token(var_decl));
+			}
+			var_decl->VarDecl.tags |= VarDeclTag_thread_local;
+			return var_decl;
+		} else if (str_eq(tag, str_lit("bounds_check"))) {
+			s = parse_stmt(f);
+			s->stmt_state_flags |= StmtStateFlag_bounds_check;
+			if ((s->stmt_state_flags & StmtStateFlag_no_bounds_check) != 0) {
+				syntax_error(token, "#bounds_check and #no_bounds_check cannot be applied together");
+			}
+			return s;
+		} else if (str_eq(tag, str_lit("no_bounds_check"))) {
+			s = parse_stmt(f);
+			s->stmt_state_flags |= StmtStateFlag_no_bounds_check;
+			if ((s->stmt_state_flags & StmtStateFlag_bounds_check) != 0) {
+				syntax_error(token, "#bounds_check and #no_bounds_check cannot be applied together");
+			}
+			return s;
+		}
+
+		s->TagStmt.stmt = parse_stmt(f); // TODO(bill): Find out why this doesn't work as an argument
+		return s;
+	} break;
+
+	case Token_OpenBrace:
+		return parse_block_stmt(f);
+
+	case Token_Semicolon:
+		s = make_empty_stmt(f, token);
+		next_token(f);
+		return s;
+	}
+
+	syntax_error(token,
+	             "Expected a statement, got `%.*s`",
+	             LIT(token_strings[token.kind]));
+	fix_advance_to_next_stmt(f);
+	return make_bad_stmt(f, token, f->curr_token);
+}
+
+AstNodeArray parse_stmt_list(AstFile *f) {
+	AstNodeArray list = make_ast_node_array(f);
+
+	while (f->curr_token.kind != Token_case &&
+	       f->curr_token.kind != Token_default &&
+	       f->curr_token.kind != Token_CloseBrace &&
+	       f->curr_token.kind != Token_EOF) {
+		AstNode *stmt = parse_stmt(f);
+		if (stmt && stmt->kind != AstNode_EmptyStmt) {
+			array_add(&list, stmt);
+		}
+	}
+
+	return list;
+}
+
+
+ParseFileError init_ast_file(AstFile *f, String fullpath) {
+	if (!string_has_extension(fullpath, str_lit("odin"))) {
+		return ParseFile_WrongExtension;
+	}
+	TokenizerInitError err = init_tokenizer(&f->tokenizer, fullpath);
+	if (err == TokenizerInit_None) {
+		array_init(&f->tokens, heap_allocator());
+		{
+			for (;;) {
+				Token token = tokenizer_get_token(&f->tokenizer);
+				if (token.kind == Token_Invalid) {
+					return ParseFile_InvalidToken;
+				}
+				if (token.kind == Token_Comment) {
+					continue;
+				}
+				array_add(&f->tokens, token);
+
+				if (token.kind == Token_EOF) {
+					break;
+				}
+			}
+		}
+
+		f->curr_token_index = 0;
+		f->prev_token = f->tokens.e[f->curr_token_index];
+		f->curr_token = f->tokens.e[f->curr_token_index];
+
+		// NOTE(bill): Is this big enough or too small?
+		isize arena_size = gb_size_of(AstNode);
+		arena_size *= 2*f->tokens.count;
+		gb_arena_init_from_allocator(&f->arena, heap_allocator(), arena_size);
+
+		f->curr_proc = NULL;
+
+		return ParseFile_None;
+	}
+
+	switch (err) {
+	case TokenizerInit_NotExists:
+		return ParseFile_NotFound;
+	case TokenizerInit_Permission:
+		return ParseFile_Permission;
+	case TokenizerInit_Empty:
+		return ParseFile_EmptyFile;
+	}
+
+	return ParseFile_InvalidFile;
+}
+
+void destroy_ast_file(AstFile *f) {
+	gb_arena_free(&f->arena);
+	array_free(&f->tokens);
+	gb_free(heap_allocator(), f->tokenizer.fullpath.text);
+	destroy_tokenizer(&f->tokenizer);
+}
+
+bool init_parser(Parser *p) {
+	array_init(&p->files, heap_allocator());
+	array_init(&p->imports, heap_allocator());
+	array_init(&p->foreign_libraries, heap_allocator());
+	gb_mutex_init(&p->mutex);
+	return true;
+}
+
+void destroy_parser(Parser *p) {
+	// TODO(bill): Fix memory leak
+	for_array(i, p->files) {
+		destroy_ast_file(&p->files.e[i]);
+	}
+#if 1
+	for_array(i, p->imports) {
+		// gb_free(heap_allocator(), p->imports[i].text);
+	}
+#endif
+	array_free(&p->files);
+	array_free(&p->imports);
+	array_free(&p->foreign_libraries);
+	gb_mutex_destroy(&p->mutex);
+}
+
+// NOTE(bill): Returns true if it's added
+bool try_add_import_path(Parser *p, String path, String rel_path, TokenPos pos) {
+	gb_mutex_lock(&p->mutex);
+
+	for_array(i, p->imports) {
+		String import = p->imports.e[i].path;
+		if (str_eq(import, path)) {
+			return false;
+		}
+	}
+
+	ImportedFile item;
+	item.path = path;
+	item.rel_path = rel_path;
+	item.pos = pos;
+	array_add(&p->imports, item);
+
+	gb_mutex_unlock(&p->mutex);
+
+	return true;
+}
+
+String get_fullpath_relative(gbAllocator a, String base_dir, String path) {
+	String res = {0};
+	isize str_len = base_dir.len+path.len;
+
+	u8 *str = gb_alloc_array(heap_allocator(), u8, str_len+1);
+
+	isize i = 0;
+	gb_memmove(str+i, base_dir.text, base_dir.len); i += base_dir.len;
+	gb_memmove(str+i, path.text, path.len);
+	str[str_len] = '\0';
+	res = path_to_fullpath(a, make_string(str, str_len));
+	gb_free(heap_allocator(), str);
+	return res;
+}
+
+String get_fullpath_core(gbAllocator a, String path) {
+	String module_dir = get_module_dir();
+	String res = {0};
+
+	char core[] = "core/";
+	isize core_len = gb_size_of(core)-1;
+
+	isize str_len = module_dir.len + core_len + path.len;
+	u8 *str = gb_alloc_array(heap_allocator(), u8, str_len+1);
+
+	gb_memmove(str, module_dir.text, module_dir.len);
+	gb_memmove(str+module_dir.len, core, core_len);
+	gb_memmove(str+module_dir.len+core_len, path.text, path.len);
+	str[str_len] = '\0';
+
+	res = path_to_fullpath(a, make_string(str, str_len));
+	gb_free(heap_allocator(), str);
+	return res;
+}
+
+// NOTE(bill): Returns true if it's added
+bool try_add_foreign_library_path(Parser *p, String import_file) {
+	gb_mutex_lock(&p->mutex);
+
+	for_array(i, p->foreign_libraries) {
+		String import = p->foreign_libraries.e[i];
+		if (str_eq(import, import_file)) {
+			return false;
+		}
+	}
+	array_add(&p->foreign_libraries, import_file);
+	gb_mutex_unlock(&p->mutex);
+	return true;
+}
+
+gb_global Rune illegal_import_runes[] = {
+	'"', '\'', '`', ' ', '\t', '\r', '\n', '\v', '\f',
+	'\\', // NOTE(bill): Disallow windows style filepaths
+	'!', '$', '%', '^', '&', '*', '(', ')', '=', '+',
+	'[', ']', '{', '}',
+	';', ':', '#',
+	'|', ',',  '<', '>', '?',
+};
+
+bool is_import_path_valid(String path) {
+	if (path.len > 0) {
+		u8 *start = path.text;
+		u8 *end = path.text + path.len;
+		u8 *curr = start;
+		Rune r = -1;
+		while (curr < end) {
+			isize width = 1;
+			r = curr[0];
+			if (r >= 0x80) {
+				width = gb_utf8_decode(curr, end-curr, &r);
+				if (r == GB_RUNE_INVALID && width == 1)
+					return false;
+				else if (r == GB_RUNE_BOM && curr-start > 0)
+					return false;
+			}
+
+			for (isize i = 0; i < gb_count_of(illegal_import_runes); i++) {
+				if (r == illegal_import_runes[i])
+					return false;
+			}
+
+			curr += width;
+		}
+
+		return true;
+	}
+	return false;
+}
+
+String get_filepath_extension(String path) {
+	isize dot = 0;
+	bool seen_slash = false;
+	for (isize i = path.len-1; i >= 0; i--) {
+		u8 c = path.text[i];
+		if (c == '/' || c == '\\') {
+			seen_slash = true;
+		}
+
+		if (c == '.') {
+			if (seen_slash) {
+				return str_lit("");
+			}
+
+			dot = i;
+			break;
+		}
+	}
+	return make_string(path.text, dot);
+}
+
+void parse_file(Parser *p, AstFile *f) {
+	String filepath = f->tokenizer.fullpath;
+	String base_dir = filepath;
+	for (isize i = filepath.len-1; i >= 0; i--) {
+		if (base_dir.text[i] == '\\' ||
+		    base_dir.text[i] == '/') {
+			break;
+		}
+		base_dir.len--;
+	}
+
+
+	f->decls = parse_stmt_list(f);
+
+	for_array(i, f->decls) {
+		AstNode *node = f->decls.e[i];
+		if (!is_ast_node_decl(node) &&
+		    node->kind != AstNode_BadStmt &&
+		    node->kind != AstNode_EmptyStmt) {
+			// NOTE(bill): Sanity check
+			syntax_error(ast_node_token(node), "Only declarations are allowed at file scope");
+		} else {
+			if (node->kind == AstNode_ImportDecl) {
+				AstNodeImportDecl *id = &node->ImportDecl;
+				String file_str = id->relpath.string;
+
+				if (!is_import_path_valid(file_str)) {
+					if (id->is_load) {
+						syntax_error(ast_node_token(node), "Invalid #load path: `%.*s`", LIT(file_str));
+					} else {
+						syntax_error(ast_node_token(node), "Invalid #import path: `%.*s`", LIT(file_str));
+					}
+					// NOTE(bill): It's a naughty name
+					f->decls.e[i] = make_bad_decl(f, id->token, id->token);
+					continue;
+				}
+
+				gbAllocator allocator = heap_allocator(); // TODO(bill): Change this allocator
+
+				String rel_path = get_fullpath_relative(allocator, base_dir, file_str);
+				String import_file = rel_path;
+				if (!gb_file_exists(cast(char *)rel_path.text)) { // NOTE(bill): This should be null terminated
+					String abs_path = get_fullpath_core(allocator, file_str);
+					if (gb_file_exists(cast(char *)abs_path.text)) {
+						import_file = abs_path;
+					}
+				}
+
+				id->fullpath = import_file;
+				try_add_import_path(p, import_file, file_str, ast_node_token(node).pos);
+
+			} else if (node->kind == AstNode_ForeignLibrary) {
+				AstNodeForeignLibrary *id = &node->ForeignLibrary;
+				String file_str = id->filepath.string;
+
+				if (!is_import_path_valid(file_str)) {
+					if (id->is_system) {
+						syntax_error(ast_node_token(node), "Invalid `foreign_system_library` path");
+					} else {
+						syntax_error(ast_node_token(node), "Invalid `foreign_library` path");
+					}
+					// NOTE(bill): It's a naughty name
+					f->decls.e[i] = make_bad_decl(f, id->token, id->token);
+					continue;
+				}
+
+				if (!id->is_system) {
+					gbAllocator allocator = heap_allocator(); // TODO(bill): Change this allocator
+
+					String rel_path = get_fullpath_relative(allocator, base_dir, file_str);
+					String import_file = rel_path;
+					if (!gb_file_exists(cast(char *)rel_path.text)) { // NOTE(bill): This should be null terminated
+						String abs_path = get_fullpath_core(allocator, file_str);
+						if (gb_file_exists(cast(char *)abs_path.text)) {
+							import_file = abs_path;
+						}
+					}
+					file_str = import_file;
+				}
+
+				try_add_foreign_library_path(p, file_str);
+			}
+		}
+	}
+}
+
+
+
+ParseFileError parse_files(Parser *p, char *init_filename) {
+	char *fullpath_str = gb_path_get_full_name(heap_allocator(), init_filename);
+	String init_fullpath = make_string_c(fullpath_str);
+	TokenPos init_pos = {0};
+	ImportedFile init_imported_file = {init_fullpath, init_fullpath, init_pos};
+	array_add(&p->imports, init_imported_file);
+	p->init_fullpath = init_fullpath;
+
+	{
+		String s = get_fullpath_core(heap_allocator(), str_lit("_preload.odin"));
+		ImportedFile runtime_file = {s, s, init_pos};
+		array_add(&p->imports, runtime_file);
+	}
+	{
+		String s = get_fullpath_core(heap_allocator(), str_lit("_soft_numbers.odin"));
+		ImportedFile runtime_file = {s, s, init_pos};
+		array_add(&p->imports, runtime_file);
+	}
+
+	for_array(i, p->imports) {
+		ImportedFile imported_file = p->imports.e[i];
+		String import_path = imported_file.path;
+		String import_rel_path = imported_file.rel_path;
+		TokenPos pos = imported_file.pos;
+		AstFile file = {0};
+		ParseFileError err = init_ast_file(&file, import_path);
+
+		if (err != ParseFile_None) {
+			if (pos.line != 0) {
+				gb_printf_err("%.*s(%td:%td) ", LIT(pos.file), pos.line, pos.column);
+			}
+			gb_printf_err("Failed to parse file: %.*s\n\t", LIT(import_rel_path));
+			switch (err) {
+			case ParseFile_WrongExtension:
+				gb_printf_err("Invalid file extension: File must have the extension `.odin`");
+				break;
+			case ParseFile_InvalidFile:
+				gb_printf_err("Invalid file");
+				break;
+			case ParseFile_EmptyFile:
+				gb_printf_err("File is empty");
+				break;
+			case ParseFile_Permission:
+				gb_printf_err("File permissions problem");
+				break;
+			case ParseFile_NotFound:
+				gb_printf_err("File cannot be found");
+				break;
+			case ParseFile_InvalidToken:
+				gb_printf_err("Invalid token found in file");
+				break;
+			}
+			gb_printf_err("\n");
+			return err;
+		}
+		parse_file(p, &file);
+
+		{
+			gb_mutex_lock(&p->mutex);
+			file.id = p->files.count;
+			array_add(&p->files, file);
+			gb_mutex_unlock(&p->mutex);
+		}
+	}
+
+	for_array(i, p->files) {
+		p->total_token_count += p->files.e[i].tokens.count;
+	}
+
+
+	return ParseFile_None;
+}
+
+

+ 221 - 0
src/printer.c

@@ -0,0 +1,221 @@
+
+
+gb_inline void print_indent(isize indent) {
+	while (indent --> 0)
+		gb_printf("  ");
+}
+
+void print_ast(AstNode *node, isize indent) {
+	if (node == NULL)
+		return;
+
+	switch (node->kind) {
+	case AstNode_BasicLit:
+		print_indent(indent);
+		print_token(node->BasicLit);
+		break;
+	case AstNode_Ident:
+		print_indent(indent);
+		print_token(node->Ident);
+		break;
+	case AstNode_ProcLit:
+		print_indent(indent);
+		gb_printf("(proc lit)\n");
+		print_ast(node->ProcLit.type, indent+1);
+		print_ast(node->ProcLit.body, indent+1);
+		break;
+
+	case AstNode_CompoundLit:
+		print_indent(indent);
+		gb_printf("(compound lit)\n");
+		print_ast(node->CompoundLit.type, indent+1);
+		for_array(i, node->CompoundLit.elems) {
+			print_ast(node->CompoundLit.elems[i], indent+1);
+		}
+		break;
+
+
+	case AstNode_TagExpr:
+		print_indent(indent);
+		gb_printf("(tag)\n");
+		print_indent(indent+1);
+		print_token(node->TagExpr.name);
+		print_ast(node->TagExpr.expr, indent+1);
+		break;
+
+	case AstNode_UnaryExpr:
+		print_indent(indent);
+		print_token(node->UnaryExpr.op);
+		print_ast(node->UnaryExpr.expr, indent+1);
+		break;
+	case AstNode_BinaryExpr:
+		print_indent(indent);
+		print_token(node->BinaryExpr.op);
+		print_ast(node->BinaryExpr.left, indent+1);
+		print_ast(node->BinaryExpr.right, indent+1);
+		break;
+	case AstNode_CallExpr:
+		print_indent(indent);
+		gb_printf("(call)\n");
+		print_ast(node->CallExpr.proc, indent+1);
+		for_array(i, node->CallExpr.args) {
+			print_ast(node->CallExpr.args[i], indent+1);
+		}
+		break;
+	case AstNode_SelectorExpr:
+		print_indent(indent);
+		gb_printf(".\n");
+		print_ast(node->SelectorExpr.expr,  indent+1);
+		print_ast(node->SelectorExpr.selector, indent+1);
+		break;
+	case AstNode_IndexExpr:
+		print_indent(indent);
+		gb_printf("([])\n");
+		print_ast(node->IndexExpr.expr, indent+1);
+		print_ast(node->IndexExpr.index, indent+1);
+		break;
+	case AstNode_DerefExpr:
+		print_indent(indent);
+		gb_printf("(deref)\n");
+		print_ast(node->DerefExpr.expr, indent+1);
+		break;
+
+
+	case AstNode_ExprStmt:
+		print_ast(node->ExprStmt.expr, indent);
+		break;
+	case AstNode_IncDecStmt:
+		print_indent(indent);
+		print_token(node->IncDecStmt.op);
+		print_ast(node->IncDecStmt.expr, indent+1);
+		break;
+	case AstNode_AssignStmt:
+		print_indent(indent);
+		print_token(node->AssignStmt.op);
+		for_array(i, node->AssignStmt.lhs) {
+			print_ast(node->AssignStmt.lhs[i], indent+1);
+		}
+		for_array(i, node->AssignStmt.rhs) {
+			print_ast(node->AssignStmt.rhs[i], indent+1);
+		}
+		break;
+	case AstNode_BlockStmt:
+		print_indent(indent);
+		gb_printf("(block)\n");
+		for_array(i, node->BlockStmt.stmts) {
+			print_ast(node->BlockStmt.stmts[i], indent+1);
+		}
+		break;
+
+	case AstNode_IfStmt:
+		print_indent(indent);
+		gb_printf("(if)\n");
+		print_ast(node->IfStmt.cond, indent+1);
+		print_ast(node->IfStmt.body, indent+1);
+		if (node->IfStmt.else_stmt) {
+			print_indent(indent);
+			gb_printf("(else)\n");
+			print_ast(node->IfStmt.else_stmt, indent+1);
+		}
+		break;
+	case AstNode_ReturnStmt:
+		print_indent(indent);
+		gb_printf("(return)\n");
+		for_array(i, node->ReturnStmt.results) {
+			print_ast(node->ReturnStmt.results[i], indent+1);
+		}
+		break;
+	case AstNode_ForStmt:
+		print_indent(indent);
+		gb_printf("(for)\n");
+		print_ast(node->ForStmt.init, indent+1);
+		print_ast(node->ForStmt.cond, indent+1);
+		print_ast(node->ForStmt.post, indent+1);
+		print_ast(node->ForStmt.body, indent+1);
+		break;
+	case AstNode_DeferStmt:
+		print_indent(indent);
+		gb_printf("(defer)\n");
+		print_ast(node->DeferStmt.stmt, indent+1);
+		break;
+
+
+	case AstNode_VarDecl:
+		print_indent(indent);
+		gb_printf("(decl:var)\n");
+		for_array(i, node->VarDecl.names) {
+			print_ast(node->VarDecl.names[i], indent+1);
+		}
+		print_ast(node->VarDecl.type, indent+1);
+		for_array(i, node->VarDecl.values) {
+			print_ast(node->VarDecl.values[i], indent+1);
+		}
+		break;
+	case AstNode_ConstDecl:
+		print_indent(indent);
+		gb_printf("(decl:const)\n");
+		for_array(i, node->VarDecl.names) {
+			print_ast(node->VarDecl.names[i], indent+1);
+		}
+		print_ast(node->VarDecl.type, indent+1);
+		for_array(i, node->VarDecl.values) {
+			print_ast(node->VarDecl.values[i], indent+1);
+		}
+		break;
+	case AstNode_ProcDecl:
+		print_indent(indent);
+		gb_printf("(decl:proc)\n");
+		print_ast(node->ProcDecl.type, indent+1);
+		print_ast(node->ProcDecl.body, indent+1);
+		break;
+
+	case AstNode_TypeDecl:
+		print_indent(indent);
+		gb_printf("(type)\n");
+		print_ast(node->TypeDecl.name, indent+1);
+		print_ast(node->TypeDecl.type, indent+1);
+		break;
+
+	case AstNode_ProcType:
+		print_indent(indent);
+		gb_printf("(type:proc)(%td -> %td)\n", node->ProcType.params.count, node->ProcType.results.count);
+		for_array(i, node->ProcType.params) {
+			print_ast(node->ProcType.params[i], indent+1);
+		}
+		if (node->ProcType.results.count > 0) {
+			print_indent(indent+1);
+			gb_printf("->\n");
+			for_array(i, node->ProcType.results) {
+				print_ast(node->ProcType.results[i], indent+1);
+			}
+		}
+		break;
+	case AstNode_Parameter:
+		for_array(i, node->Parameter.names) {
+			print_ast(node->Parameter.names[i], indent+1);
+		}
+		print_ast(node->Parameter.type, indent);
+		break;
+	case AstNode_PointerType:
+		print_indent(indent);
+		print_token(node->PointerType.token);
+		print_ast(node->PointerType.type, indent+1);
+		break;
+	case AstNode_ArrayType:
+		print_indent(indent);
+		gb_printf("[]\n");
+		print_ast(node->ArrayType.count, indent+1);
+		print_ast(node->ArrayType.elem, indent+1);
+		break;
+	case AstNode_StructType:
+		print_indent(indent);
+		gb_printf("(struct)\n");
+		for_array(i, node->StructType.decls) {
+			print_ast(node->StructType.decls[i], indent+1);
+		}
+		break;
+	}
+
+	// if (node->next)
+		// print_ast(node->next, indent);
+}

+ 5419 - 0
src/ssa.c

@@ -0,0 +1,5419 @@
+typedef struct ssaProcedure ssaProcedure;
+typedef struct ssaBlock ssaBlock;
+typedef struct ssaValue ssaValue;
+typedef struct ssaDebugInfo ssaDebugInfo;
+
+typedef Array(ssaValue *) ssaValueArray;
+
+#define MAP_TYPE ssaValue *
+#define MAP_PROC map_ssa_value_
+#define MAP_NAME MapSsaValue
+#include "map.c"
+
+#define MAP_TYPE ssaDebugInfo *
+#define MAP_PROC map_ssa_debug_info_
+#define MAP_NAME MapSsaDebugInfo
+#include "map.c"
+
+typedef struct ssaModule {
+	CheckerInfo * info;
+	BaseTypeSizes sizes;
+	gbArena       arena;
+	gbArena       tmp_arena;
+	gbAllocator   allocator;
+	gbAllocator   tmp_allocator;
+	bool generate_debug_info;
+
+	u32 stmt_state_flags;
+
+	// String source_filename;
+	String layout;
+	// String triple;
+
+
+	MapEntity       min_dep_map; // Key: Entity *
+	MapSsaValue     values;      // Key: Entity *
+	MapSsaValue     members;     // Key: String
+	MapString       type_names;  // Key: Type *
+	MapSsaDebugInfo debug_info;  // Key: Unique pointer
+	i32             global_string_index;
+	i32             global_array_index; // For ConstantSlice
+
+	Array(ssaProcedure *) procs;             // NOTE(bill): All procedures with bodies
+	ssaValueArray         procs_to_generate; // NOTE(bill): Procedures to generate
+} ssaModule;
+
+// NOTE(bill): For more info, see https://en.wikipedia.org/wiki/Dominator_(graph_theory)
+typedef struct ssaDomNode {
+	ssaBlock *        idom; // Parent (Immediate Dominator)
+	Array(ssaBlock *) children;
+	i32               pre, post; // Ordering in tree
+} ssaDomNode;
+
+
+typedef struct ssaBlock {
+	i32           index;
+	String        label;
+	ssaProcedure *parent;
+	AstNode *     node; // Can be NULL
+	Scope *       scope;
+	isize         scope_index;
+	ssaDomNode    dom;
+	i32           gaps;
+
+	ssaValueArray instrs;
+	ssaValueArray locals;
+
+	Array(ssaBlock *) preds;
+	Array(ssaBlock *) succs;
+} ssaBlock;
+
+typedef struct ssaTargetList ssaTargetList;
+struct ssaTargetList {
+	ssaTargetList *prev;
+	ssaBlock *     break_;
+	ssaBlock *     continue_;
+	ssaBlock *     fallthrough_;
+};
+
+typedef enum ssaDeferExitKind {
+	ssaDeferExit_Default,
+	ssaDeferExit_Return,
+	ssaDeferExit_Branch,
+} ssaDeferExitKind;
+typedef enum ssaDeferKind {
+	ssaDefer_Node,
+	ssaDefer_Instr,
+} ssaDeferKind;
+
+typedef struct ssaDefer {
+	ssaDeferKind kind;
+	isize        scope_index;
+	ssaBlock *   block;
+	union {
+		AstNode *stmt;
+		// NOTE(bill): `instr` will be copied every time to create a new one
+		ssaValue *instr;
+	};
+} ssaDefer;
+
+typedef struct ssaProcedure ssaProcedure;
+struct ssaProcedure {
+	ssaProcedure *        parent;
+	Array(ssaProcedure *) children;
+
+	Entity *              entity;
+	ssaModule *           module;
+	String                name;
+	Type *                type;
+	AstNode *             type_expr;
+	AstNode *             body;
+	u64                   tags;
+
+	ssaValueArray     params;
+	Array(ssaDefer)       defer_stmts;
+	Array(ssaBlock *)     blocks;
+	i32                   scope_index;
+	ssaBlock *            decl_block;
+	ssaBlock *            entry_block;
+	ssaBlock *            curr_block;
+	ssaTargetList *       target_list;
+	ssaValueArray     referrers;
+
+	i32                   local_count;
+	i32                   instr_count;
+	i32                   block_count;
+};
+
+#define SSA_STARTUP_RUNTIME_PROC_NAME  "__$startup_runtime"
+#define SSA_TYPE_INFO_DATA_NAME        "__$type_info_data"
+#define SSA_TYPE_INFO_DATA_MEMBER_NAME "__$type_info_data_member"
+
+
+#define SSA_INSTR_KINDS \
+	SSA_INSTR_KIND(Comment, struct { String text; }) \
+	SSA_INSTR_KIND(Local,   struct { \
+		Entity *      entity; \
+		Type *        type; \
+		bool          zero_initialized; \
+		ssaValueArray referrers; \
+	}) \
+	SSA_INSTR_KIND(ZeroInit, struct { ssaValue *address; }) \
+	SSA_INSTR_KIND(Store,    struct { ssaValue *address, *value; }) \
+	SSA_INSTR_KIND(Load,     struct { Type *type; ssaValue *address; }) \
+	SSA_INSTR_KIND(PtrOffset, struct { \
+		ssaValue *address; \
+		ssaValue *offset; \
+	}) \
+	SSA_INSTR_KIND(ArrayElementPtr, struct { \
+		ssaValue *address; \
+		Type *    result_type; \
+		ssaValue *elem_index; \
+	}) \
+	SSA_INSTR_KIND(StructElementPtr, struct {  \
+		ssaValue *address; \
+		Type *    result_type; \
+		i32       elem_index; \
+	}) \
+	SSA_INSTR_KIND(ArrayExtractValue, struct { \
+		ssaValue *address; \
+		Type *    result_type; \
+		i32       index; \
+	}) \
+	SSA_INSTR_KIND(StructExtractValue, struct { \
+		ssaValue *address; \
+		Type *    result_type; \
+		i32       index; \
+	}) \
+	SSA_INSTR_KIND(UnionTagPtr, struct { \
+		ssaValue *address; \
+		Type     *type; /* ^int */  \
+	}) \
+	SSA_INSTR_KIND(UnionTagValue, struct { \
+		ssaValue *address; \
+		Type     *type; /* int */ \
+	}) \
+	SSA_INSTR_KIND(Conv, struct { \
+		ssaConvKind kind; \
+		ssaValue *value; \
+		Type *from, *to; \
+	}) \
+	SSA_INSTR_KIND(Jump, struct { ssaBlock *block; }) \
+	SSA_INSTR_KIND(If, struct { \
+		ssaValue *cond; \
+		ssaBlock *true_block; \
+		ssaBlock *false_block; \
+	}) \
+	SSA_INSTR_KIND(Return, struct { ssaValue *value; }) \
+	SSA_INSTR_KIND(Select, struct { \
+		ssaValue *cond; \
+		ssaValue *true_value; \
+		ssaValue *false_value; \
+	}) \
+	SSA_INSTR_KIND(Phi, struct { ssaValueArray edges; Type *type; }) \
+	SSA_INSTR_KIND(Unreachable, i32) \
+	SSA_INSTR_KIND(BinaryOp, struct { \
+		Type *    type; \
+		TokenKind op; \
+		ssaValue *left, *right; \
+	}) \
+	SSA_INSTR_KIND(Call, struct { \
+		Type *    type; /* return type */  \
+		ssaValue *value; \
+		ssaValue **args; \
+		isize      arg_count; \
+	}) \
+	SSA_INSTR_KIND(VectorExtractElement, struct { \
+		ssaValue *vector; \
+		ssaValue *index; \
+	}) \
+	SSA_INSTR_KIND(VectorInsertElement, struct { \
+		ssaValue *vector; \
+		ssaValue *elem; \
+		ssaValue *index; \
+	}) \
+	SSA_INSTR_KIND(VectorShuffle, struct { \
+		ssaValue *vector; \
+		i32 *     indices; \
+		i32       index_count; \
+		Type *    type; \
+	}) \
+	SSA_INSTR_KIND(StartupRuntime, i32) \
+	SSA_INSTR_KIND(BoundsCheck, struct { \
+		TokenPos  pos; \
+		ssaValue *index; \
+		ssaValue *len; \
+	}) \
+	SSA_INSTR_KIND(SliceBoundsCheck, struct { \
+		TokenPos  pos; \
+		ssaValue *low; \
+		ssaValue *high; \
+		ssaValue *max; \
+		bool      is_substring; \
+	})
+
+#define SSA_CONV_KINDS \
+	SSA_CONV_KIND(trunc) \
+	SSA_CONV_KIND(zext) \
+	SSA_CONV_KIND(fptrunc) \
+	SSA_CONV_KIND(fpext) \
+	SSA_CONV_KIND(fptoui) \
+	SSA_CONV_KIND(fptosi) \
+	SSA_CONV_KIND(uitofp) \
+	SSA_CONV_KIND(sitofp) \
+	SSA_CONV_KIND(ptrtoint) \
+	SSA_CONV_KIND(inttoptr) \
+	SSA_CONV_KIND(bitcast)
+
+typedef enum ssaInstrKind {
+	ssaInstr_Invalid,
+#define SSA_INSTR_KIND(x, ...) GB_JOIN2(ssaInstr_, x),
+	SSA_INSTR_KINDS
+#undef SSA_INSTR_KIND
+} ssaInstrKind;
+
+String const ssa_instr_strings[] = {
+	{cast(u8 *)"Invalid", gb_size_of("Invalid")-1},
+#define SSA_INSTR_KIND(x, ...) {cast(u8 *)#x, gb_size_of(#x)-1},
+	SSA_INSTR_KINDS
+#undef SSA_INSTR_KIND
+};
+
+typedef enum ssaConvKind {
+	ssaConv_Invalid,
+#define SSA_CONV_KIND(x) GB_JOIN2(ssaConv_, x),
+	SSA_CONV_KINDS
+#undef SSA_CONV_KIND
+} ssaConvKind;
+
+String const ssa_conv_strings[] = {
+	{cast(u8 *)"Invalid", gb_size_of("Invalid")-1},
+#define SSA_CONV_KIND(x) {cast(u8 *)#x, gb_size_of(#x)-1},
+	SSA_CONV_KINDS
+#undef SSA_CONV_KIND
+};
+
+#define SSA_INSTR_KIND(k, ...) typedef __VA_ARGS__ GB_JOIN2(ssaInstr, k);
+	SSA_INSTR_KINDS
+#undef SSA_INSTR_KIND
+
+typedef struct ssaInstr ssaInstr;
+struct ssaInstr {
+	ssaInstrKind kind;
+
+	ssaBlock *parent;
+	Type *type;
+
+	union {
+#define SSA_INSTR_KIND(k, ...) GB_JOIN2(ssaInstr, k) k;
+	SSA_INSTR_KINDS
+#undef SSA_INSTR_KIND
+	};
+};
+
+
+typedef enum ssaValueKind {
+	ssaValue_Invalid,
+
+	ssaValue_Constant,
+	ssaValue_ConstantSlice,
+	ssaValue_Nil,
+	ssaValue_TypeName,
+	ssaValue_Global,
+	ssaValue_Param,
+
+	ssaValue_Proc,
+	ssaValue_Block,
+	ssaValue_Instr,
+
+	ssaValue_Count,
+} ssaValueKind;
+
+typedef struct ssaValueConstant {
+	Type *     type;
+	ExactValue value;
+} ssaValueConstant;
+
+typedef struct ssaValueConstantSlice {
+	Type *    type;
+	ssaValue *backing_array;
+	i64       count;
+} ssaValueConstantSlice;
+
+typedef struct ssaValueNil {
+	Type *type;
+} ssaValueNil;
+
+typedef struct ssaValueTypeName {
+	Type * type;
+	String name;
+} ssaValueTypeName;
+
+typedef struct ssaValueGlobal {
+	Entity *      entity;
+	Type *        type;
+	ssaValue *    value;
+	ssaValueArray referrers;
+	bool          is_constant;
+	bool          is_private;
+	bool          is_thread_local;
+	bool          is_unnamed_addr;
+} ssaValueGlobal;
+
+typedef struct ssaValueParam {
+	ssaProcedure *parent;
+	Entity *      entity;
+	Type *        type;
+	ssaValueArray referrers;
+} ssaValueParam;
+
+typedef struct ssaValue {
+	ssaValueKind kind;
+	i32 index;
+	union {
+		ssaValueConstant      Constant;
+		ssaValueConstantSlice ConstantSlice;
+		ssaValueNil           Nil;
+		ssaValueTypeName      TypeName;
+		ssaValueGlobal        Global;
+		ssaValueParam         Param;
+		ssaProcedure          Proc;
+		ssaBlock              Block;
+		ssaInstr              Instr;
+	};
+} ssaValue;
+
+gb_global ssaValue *v_zero    = NULL;
+gb_global ssaValue *v_one     = NULL;
+gb_global ssaValue *v_zero32  = NULL;
+gb_global ssaValue *v_one32   = NULL;
+gb_global ssaValue *v_two32   = NULL;
+gb_global ssaValue *v_false   = NULL;
+gb_global ssaValue *v_true    = NULL;
+
+typedef enum ssaAddrKind {
+	ssaAddr_Default,
+	ssaAddr_Vector,
+} ssaAddrKind;
+
+typedef struct ssaAddr {
+	ssaValue *  addr;
+	AstNode *   expr; // NOTE(bill): Just for testing - probably remove later
+	ssaAddrKind kind;
+	union {
+		struct { ssaValue *index; } Vector;
+	};
+} ssaAddr;
+
+ssaAddr ssa_make_addr(ssaValue *addr, AstNode *expr) {
+	ssaAddr v = {addr, expr};
+	return v;
+}
+ssaAddr ssa_make_addr_vector(ssaValue *addr, ssaValue *index, AstNode *expr) {
+	ssaAddr v = ssa_make_addr(addr, expr);
+	v.kind = ssaAddr_Vector;
+	v.Vector.index = index;
+	return v;
+}
+
+
+
+typedef enum ssaDebugEncoding {
+	ssaDebugBasicEncoding_Invalid       = 0,
+
+	ssaDebugBasicEncoding_address       = 1,
+	ssaDebugBasicEncoding_boolean       = 2,
+	ssaDebugBasicEncoding_float         = 3,
+	ssaDebugBasicEncoding_signed        = 4,
+	ssaDebugBasicEncoding_signed_char   = 5,
+	ssaDebugBasicEncoding_unsigned      = 6,
+	ssaDebugBasicEncoding_unsigned_char = 7,
+
+	ssaDebugBasicEncoding_member       = 13,
+	ssaDebugBasicEncoding_pointer_type = 15,
+	ssaDebugBasicEncoding_typedef      = 22,
+
+	ssaDebugBasicEncoding_array_type       = 1,
+	ssaDebugBasicEncoding_enumeration_type = 4,
+	ssaDebugBasicEncoding_structure_type   = 19,
+	ssaDebugBasicEncoding_union_type       = 23,
+
+} ssaDebugEncoding;
+
+typedef enum ssaDebugInfoKind {
+	ssaDebugInfo_Invalid,
+
+	ssaDebugInfo_CompileUnit,
+	ssaDebugInfo_File,
+	ssaDebugInfo_Scope,
+	ssaDebugInfo_Proc,
+	ssaDebugInfo_AllProcs,
+
+	ssaDebugInfo_BasicType,      // basic types
+	ssaDebugInfo_ProcType,
+	ssaDebugInfo_DerivedType,    // pointer, typedef
+	ssaDebugInfo_CompositeType,  // array, struct, enum, (raw_)union
+	ssaDebugInfo_Enumerator,     // For ssaDebugInfo_CompositeType if enum
+	ssaDebugInfo_GlobalVariable,
+	ssaDebugInfo_LocalVariable,
+
+
+	ssaDebugInfo_Count,
+} ssaDebugInfoKind;
+
+typedef struct ssaDebugInfo ssaDebugInfo;
+struct ssaDebugInfo {
+	ssaDebugInfoKind kind;
+	i32 id;
+
+	union {
+		struct {
+			AstFile *     file;
+			String        producer;
+			ssaDebugInfo *all_procs;
+		} CompileUnit;
+		struct {
+			AstFile *file;
+			String   filename;
+			String   directory;
+		} File;
+		struct {
+			ssaDebugInfo *parent;
+			ssaDebugInfo *file;
+			TokenPos      pos;
+			Scope *       scope; // Actual scope
+		} Scope;
+		struct {
+			Entity *      entity;
+			String        name;
+			ssaDebugInfo *file;
+			TokenPos      pos;
+		} Proc;
+		struct {
+			Array(ssaDebugInfo *) procs;
+		} AllProcs;
+
+
+		struct {
+			String           name;
+			i32              size;
+			i32              align;
+			ssaDebugEncoding encoding;
+		} BasicType;
+		struct {
+			ssaDebugInfo *        return_type;
+			Array(ssaDebugInfo *) param_types;
+		} ProcType;
+		struct {
+			ssaDebugInfo *   base_type;
+			ssaDebugEncoding encoding;
+		} DerivedType;
+		struct {
+			ssaDebugEncoding      encoding;
+			String                name;
+			String                identifier;
+			ssaDebugInfo *        file;
+			TokenPos              pos;
+			i32                   size;
+			i32                   align;
+			Array(ssaDebugInfo *) elements;
+		} CompositeType;
+		struct {
+			String name;
+			i64    value;
+		} Enumerator;
+		struct {
+			String        name;
+			String        linkage_name;
+			ssaDebugInfo *scope;
+			ssaDebugInfo *file;
+			TokenPos      pos;
+			ssaValue     *variable;
+			ssaDebugInfo *declaration;
+		} GlobalVariable;
+		struct {
+			String        name;
+			ssaDebugInfo *scope;
+			ssaDebugInfo *file;
+			TokenPos      pos;
+			i32           arg; // Non-zero if proc parameter
+			ssaDebugInfo *type;
+		} LocalVariable;
+	};
+};
+
+typedef struct ssaGen {
+	ssaModule module;
+	gbFile    output_file;
+	bool       opt_called;
+} ssaGen;
+
+ssaValue *ssa_lookup_member(ssaModule *m, String name) {
+	ssaValue **v = map_ssa_value_get(&m->members, hash_string(name));
+	if (v != NULL) {
+		return *v;
+	}
+	return NULL;
+}
+
+
+Type *ssa_type(ssaValue *value);
+Type *ssa_instr_type(ssaInstr *instr) {
+	switch (instr->kind) {
+	case ssaInstr_Local:
+		return instr->Local.type;
+	case ssaInstr_Load:
+		return instr->Load.type;
+	case ssaInstr_StructElementPtr:
+		return instr->StructElementPtr.result_type;
+	case ssaInstr_ArrayElementPtr:
+		return instr->ArrayElementPtr.result_type;
+	case ssaInstr_PtrOffset:
+		return ssa_type(instr->PtrOffset.address);
+	case ssaInstr_Phi:
+		return instr->Phi.type;
+	case ssaInstr_ArrayExtractValue:
+		return instr->ArrayExtractValue.result_type;
+	case ssaInstr_StructExtractValue:
+		return instr->StructExtractValue.result_type;
+	case ssaInstr_UnionTagPtr:
+		return instr->UnionTagPtr.type;
+	case ssaInstr_UnionTagValue:
+		return instr->UnionTagValue.type;
+	case ssaInstr_BinaryOp:
+		return instr->BinaryOp.type;
+	case ssaInstr_Conv:
+		return instr->Conv.to;
+	case ssaInstr_Select:
+		return ssa_type(instr->Select.true_value);
+	case ssaInstr_Call: {
+		Type *pt = base_type(instr->Call.type);
+		if (pt != NULL) {
+			if (pt->kind == Type_Tuple && pt->Tuple.variable_count == 1) {
+				return pt->Tuple.variables[0]->type;
+			}
+			return pt;
+		}
+		return NULL;
+	} break;
+	case ssaInstr_VectorExtractElement: {
+		Type *vt = ssa_type(instr->VectorExtractElement.vector);
+		Type *bt = base_vector_type(vt);
+		GB_ASSERT(!is_type_vector(bt));
+		return bt;
+	} break;
+	case ssaInstr_VectorInsertElement:
+		return ssa_type(instr->VectorInsertElement.vector);
+	case ssaInstr_VectorShuffle:
+		return instr->VectorShuffle.type;
+	}
+	return NULL;
+}
+
+Type *ssa_type(ssaValue *value) {
+	switch (value->kind) {
+	case ssaValue_Constant:
+		return value->Constant.type;
+	case ssaValue_ConstantSlice:
+		return value->ConstantSlice.type;
+	case ssaValue_Nil:
+		return value->Nil.type;
+	case ssaValue_TypeName:
+		return value->TypeName.type;
+	case ssaValue_Global:
+		return value->Global.type;
+	case ssaValue_Param:
+		return value->Param.type;
+	case ssaValue_Proc:
+		return value->Proc.type;
+	case ssaValue_Instr:
+		return ssa_instr_type(&value->Instr);
+	}
+	return NULL;
+}
+
+Type *ssa_addr_type(ssaAddr lval) {
+	if (lval.addr != NULL) {
+		Type *t = ssa_type(lval.addr);
+		GB_ASSERT(is_type_pointer(t));
+		return type_deref(t);
+	}
+	return NULL;
+}
+
+
+
+bool ssa_is_blank_ident(AstNode *node) {
+	if (node->kind == AstNode_Ident) {
+		ast_node(i, Ident, node);
+		return is_blank_ident(i->string);
+	}
+	return false;
+}
+
+
+ssaInstr *ssa_get_last_instr(ssaBlock *block) {
+	if (block != NULL) {
+		isize len = block->instrs.count;
+		if (len > 0) {
+			ssaValue *v = block->instrs.e[len-1];
+			GB_ASSERT(v->kind == ssaValue_Instr);
+			return &v->Instr;
+		}
+	}
+	return NULL;
+
+}
+
+bool ssa_is_instr_terminating(ssaInstr *i) {
+	if (i != NULL) {
+		switch (i->kind) {
+		case ssaInstr_Return:
+		case ssaInstr_Unreachable:
+			return true;
+		}
+	}
+
+	return false;
+}
+
+
+void ssa_add_edge(ssaBlock *from, ssaBlock *to) {
+	array_add(&from->succs, to);
+	array_add(&to->preds, from);
+}
+
+void ssa_set_instr_parent(ssaValue *instr, ssaBlock *parent) {
+	if (instr->kind == ssaValue_Instr) {
+		instr->Instr.parent = parent;
+	}
+}
+
+ssaValueArray *ssa_value_referrers(ssaValue *v) {
+	switch (v->kind) {
+	case ssaValue_Global:
+		return &v->Global.referrers;
+	case ssaValue_Param:
+		return &v->Param.referrers;
+	case ssaValue_Proc: {
+		if (v->Proc.parent != NULL) {
+			return &v->Proc.referrers;
+		}
+		return NULL;
+	}
+	case ssaValue_Instr: {
+		ssaInstr *i = &v->Instr;
+		switch (i->kind) {
+		case ssaInstr_Local:
+			return &i->Local.referrers;
+		}
+	} break;
+	}
+
+	return NULL;
+}
+
+
+
+////////////////////////////////////////////////////////////////
+//
+// @Make
+//
+////////////////////////////////////////////////////////////////
+
+void      ssa_module_add_value    (ssaModule *m, Entity *e, ssaValue *v);
+ssaValue *ssa_emit_zero_init      (ssaProcedure *p, ssaValue *address);
+ssaValue *ssa_emit_comment        (ssaProcedure *p, String text);
+ssaValue *ssa_emit_store          (ssaProcedure *p, ssaValue *address, ssaValue *value);
+ssaValue *ssa_emit_load           (ssaProcedure *p, ssaValue *address);
+void      ssa_emit_jump           (ssaProcedure *proc, ssaBlock *block);
+ssaValue *ssa_emit_conv           (ssaProcedure *proc, ssaValue *value, Type *t);
+ssaValue *ssa_type_info           (ssaProcedure *proc, Type *type);
+ssaValue *ssa_build_expr          (ssaProcedure *proc, AstNode *expr);
+void      ssa_build_stmt          (ssaProcedure *proc, AstNode *node);
+void      ssa_build_cond          (ssaProcedure *proc, AstNode *cond, ssaBlock *true_block, ssaBlock *false_block);
+void      ssa_build_defer_stmt    (ssaProcedure *proc, ssaDefer d);
+ssaAddr   ssa_build_addr          (ssaProcedure *proc, AstNode *expr);
+void      ssa_build_proc          (ssaValue *value, ssaProcedure *parent);
+void      ssa_gen_global_type_name(ssaModule *m, Entity *e, String name);
+
+
+
+
+ssaValue *ssa_alloc_value(gbAllocator a, ssaValueKind kind) {
+	ssaValue *v = gb_alloc_item(a, ssaValue);
+	v->kind = kind;
+	return v;
+}
+ssaValue *ssa_alloc_instr(ssaProcedure *proc, ssaInstrKind kind) {
+	ssaValue *v = ssa_alloc_value(proc->module->allocator, ssaValue_Instr);
+	v->Instr.kind = kind;
+	proc->instr_count++;
+	return v;
+}
+ssaDebugInfo *ssa_alloc_debug_info(gbAllocator a, ssaDebugInfoKind kind) {
+	ssaDebugInfo *di = gb_alloc_item(a, ssaDebugInfo);
+	di->kind = kind;
+	return di;
+}
+
+
+
+
+ssaValue *ssa_make_value_type_name(gbAllocator a, String name, Type *type) {
+	ssaValue *v = ssa_alloc_value(a, ssaValue_TypeName);
+	v->TypeName.name = name;
+	v->TypeName.type = type;
+	return v;
+}
+
+ssaValue *ssa_make_value_global(gbAllocator a, Entity *e, ssaValue *value) {
+	ssaValue *v = ssa_alloc_value(a, ssaValue_Global);
+	v->Global.entity = e;
+	v->Global.type = make_type_pointer(a, e->type);
+	v->Global.value = value;
+	array_init(&v->Global.referrers, heap_allocator()); // TODO(bill): Replace heap allocator here
+	return v;
+}
+ssaValue *ssa_make_value_param(gbAllocator a, ssaProcedure *parent, Entity *e) {
+	ssaValue *v = ssa_alloc_value(a, ssaValue_Param);
+	v->Param.parent = parent;
+	v->Param.entity = e;
+	v->Param.type   = e->type;
+	array_init(&v->Param.referrers, heap_allocator()); // TODO(bill): Replace heap allocator here
+	return v;
+}
+ssaValue *ssa_make_value_nil(gbAllocator a, Type *type) {
+	ssaValue *v = ssa_alloc_value(a, ssaValue_Nil);
+	v->Nil.type = type;
+	return v;
+}
+
+
+
+ssaValue *ssa_make_instr_local(ssaProcedure *p, Entity *e, bool zero_initialized) {
+	ssaValue *v = ssa_alloc_instr(p, ssaInstr_Local);
+	ssaInstr *i = &v->Instr;
+	i->Local.entity = e;
+	i->Local.type = make_type_pointer(p->module->allocator, e->type);
+	i->Local.zero_initialized = zero_initialized;
+	array_init(&i->Local.referrers, heap_allocator()); // TODO(bill): Replace heap allocator here
+	ssa_module_add_value(p->module, e, v);
+	return v;
+}
+
+
+ssaValue *ssa_make_instr_store(ssaProcedure *p, ssaValue *address, ssaValue *value) {
+	ssaValue *v = ssa_alloc_instr(p, ssaInstr_Store);
+	ssaInstr *i = &v->Instr;
+	i->Store.address = address;
+	i->Store.value = value;
+	return v;
+}
+
+ssaValue *ssa_make_instr_zero_init(ssaProcedure *p, ssaValue *address) {
+	ssaValue *v = ssa_alloc_instr(p, ssaInstr_ZeroInit);
+	ssaInstr *i = &v->Instr;
+	i->ZeroInit.address = address;
+	return v;
+}
+
+ssaValue *ssa_make_instr_load(ssaProcedure *p, ssaValue *address) {
+	ssaValue *v = ssa_alloc_instr(p, ssaInstr_Load);
+	ssaInstr *i = &v->Instr;
+	i->Load.address = address;
+	i->Load.type = type_deref(ssa_type(address));
+	return v;
+}
+
+ssaValue *ssa_make_instr_array_element_ptr(ssaProcedure *p, ssaValue *address, ssaValue *elem_index) {
+	ssaValue *v = ssa_alloc_instr(p, ssaInstr_ArrayElementPtr);
+	ssaInstr *i = &v->Instr;
+	Type *t = ssa_type(address);
+	GB_ASSERT(is_type_pointer(t));
+	t = base_type(type_deref(t));
+	GB_ASSERT(is_type_array(t) || is_type_vector(t));
+
+	Type *result_type = make_type_pointer(p->module->allocator, t->Array.elem);
+
+	i->ArrayElementPtr.address = address;
+	i->ArrayElementPtr.elem_index = elem_index;
+	i->ArrayElementPtr.result_type = result_type;
+
+	GB_ASSERT_MSG(is_type_pointer(ssa_type(address)),
+	              "%s", type_to_string(ssa_type(address)));
+	return v;
+}
+ssaValue *ssa_make_instr_struct_element_ptr(ssaProcedure *p, ssaValue *address, i32 elem_index, Type *result_type) {
+	ssaValue *v = ssa_alloc_instr(p, ssaInstr_StructElementPtr);
+	ssaInstr *i = &v->Instr;
+	i->StructElementPtr.address     = address;
+	i->StructElementPtr.elem_index  = elem_index;
+	i->StructElementPtr.result_type = result_type;
+
+	GB_ASSERT_MSG(is_type_pointer(ssa_type(address)),
+	              "%s", type_to_string(ssa_type(address)));
+	return v;
+}
+ssaValue *ssa_make_instr_ptr_offset(ssaProcedure *p, ssaValue *address, ssaValue *offset) {
+	ssaValue *v = ssa_alloc_instr(p, ssaInstr_PtrOffset);
+	ssaInstr *i = &v->Instr;
+	i->PtrOffset.address = address;
+	i->PtrOffset.offset  = offset;
+
+	GB_ASSERT_MSG(is_type_pointer(ssa_type(address)),
+	              "%s", type_to_string(ssa_type(address)));
+	GB_ASSERT_MSG(is_type_integer(ssa_type(offset)),
+	              "%s", type_to_string(ssa_type(address)));
+
+	return v;
+}
+
+
+
+ssaValue *ssa_make_instr_array_extract_value(ssaProcedure *p, ssaValue *address, i32 index) {
+	ssaValue *v = ssa_alloc_instr(p, ssaInstr_ArrayExtractValue);
+	ssaInstr *i = &v->Instr;
+	i->ArrayExtractValue.address = address;
+	i->ArrayExtractValue.index = index;
+	Type *t = base_type(ssa_type(address));
+	GB_ASSERT(is_type_array(t));
+	i->ArrayExtractValue.result_type = t->Array.elem;
+	return v;
+}
+
+ssaValue *ssa_make_instr_struct_extract_value(ssaProcedure *p, ssaValue *address, i32 index, Type *result_type) {
+	ssaValue *v = ssa_alloc_instr(p, ssaInstr_StructExtractValue);
+	ssaInstr *i = &v->Instr;
+	i->StructExtractValue.address = address;
+	i->StructExtractValue.index = index;
+	i->StructExtractValue.result_type = result_type;
+	return v;
+}
+
+ssaValue *ssa_make_instr_union_tag_ptr(ssaProcedure *p, ssaValue *address) {
+	ssaValue *v = ssa_alloc_instr(p, ssaInstr_UnionTagPtr);
+	ssaInstr *i = &v->Instr;
+	i->UnionTagPtr.address = address;
+	i->UnionTagPtr.type = t_int_ptr;
+	return v;
+}
+
+ssaValue *ssa_make_instr_union_tag_value(ssaProcedure *p, ssaValue *address) {
+	ssaValue *v = ssa_alloc_instr(p, ssaInstr_UnionTagValue);
+	ssaInstr *i = &v->Instr;
+	i->UnionTagValue.address = address;
+	i->UnionTagValue.type = t_int_ptr;
+	return v;
+}
+
+
+ssaValue *ssa_make_instr_binary_op(ssaProcedure *p, TokenKind op, ssaValue *left, ssaValue *right, Type *type) {
+	ssaValue *v = ssa_alloc_instr(p, ssaInstr_BinaryOp);
+	ssaInstr *i = &v->Instr;
+	i->BinaryOp.op = op;
+	i->BinaryOp.left = left;
+	i->BinaryOp.right = right;
+	i->BinaryOp.type = type;
+	return v;
+}
+
+ssaValue *ssa_make_instr_jump(ssaProcedure *p, ssaBlock *block) {
+	ssaValue *v = ssa_alloc_instr(p, ssaInstr_Jump);
+	ssaInstr *i = &v->Instr;
+	i->Jump.block = block;
+	return v;
+}
+ssaValue *ssa_make_instr_if(ssaProcedure *p, ssaValue *cond, ssaBlock *true_block, ssaBlock *false_block) {
+	ssaValue *v = ssa_alloc_instr(p, ssaInstr_If);
+	ssaInstr *i = &v->Instr;
+	i->If.cond = cond;
+	i->If.true_block = true_block;
+	i->If.false_block = false_block;
+	return v;
+}
+
+
+ssaValue *ssa_make_instr_phi(ssaProcedure *p, ssaValueArray edges, Type *type) {
+	ssaValue *v = ssa_alloc_instr(p, ssaInstr_Phi);
+	ssaInstr *i = &v->Instr;
+	i->Phi.edges = edges;
+	i->Phi.type = type;
+	return v;
+}
+
+ssaValue *ssa_make_instr_unreachable(ssaProcedure *p) {
+	ssaValue *v = ssa_alloc_instr(p, ssaInstr_Unreachable);
+	return v;
+}
+
+ssaValue *ssa_make_instr_return(ssaProcedure *p, ssaValue *value) {
+	ssaValue *v = ssa_alloc_instr(p, ssaInstr_Return);
+	v->Instr.Return.value = value;
+	return v;
+}
+
+ssaValue *ssa_make_instr_select(ssaProcedure *p, ssaValue *cond, ssaValue *t, ssaValue *f) {
+	ssaValue *v = ssa_alloc_instr(p, ssaInstr_Select);
+	v->Instr.Select.cond = cond;
+	v->Instr.Select.true_value = t;
+	v->Instr.Select.false_value = f;
+	return v;
+}
+
+ssaValue *ssa_make_instr_call(ssaProcedure *p, ssaValue *value, ssaValue **args, isize arg_count, Type *result_type) {
+	ssaValue *v = ssa_alloc_instr(p, ssaInstr_Call);
+	v->Instr.Call.value = value;
+	v->Instr.Call.args = args;
+	v->Instr.Call.arg_count = arg_count;
+	v->Instr.Call.type = result_type;
+	return v;
+}
+
+ssaValue *ssa_make_instr_conv(ssaProcedure *p, ssaConvKind kind, ssaValue *value, Type *from, Type *to) {
+	ssaValue *v = ssa_alloc_instr(p, ssaInstr_Conv);
+	v->Instr.Conv.kind = kind;
+	v->Instr.Conv.value = value;
+	v->Instr.Conv.from = from;
+	v->Instr.Conv.to = to;
+	return v;
+}
+
+ssaValue *ssa_make_instr_extract_element(ssaProcedure *p, ssaValue *vector, ssaValue *index) {
+	ssaValue *v = ssa_alloc_instr(p, ssaInstr_VectorExtractElement);
+	v->Instr.VectorExtractElement.vector = vector;
+	v->Instr.VectorExtractElement.index = index;
+	return v;
+}
+
+ssaValue *ssa_make_instr_insert_element(ssaProcedure *p, ssaValue *vector, ssaValue *elem, ssaValue *index) {
+	ssaValue *v = ssa_alloc_instr(p, ssaInstr_VectorInsertElement);
+	v->Instr.VectorInsertElement.vector = vector;
+	v->Instr.VectorInsertElement.elem   = elem;
+	v->Instr.VectorInsertElement.index  = index;
+	return v;
+}
+
+ssaValue *ssa_make_instr_vector_shuffle(ssaProcedure *p, ssaValue *vector, i32 *indices, isize index_count) {
+	ssaValue *v = ssa_alloc_instr(p, ssaInstr_VectorShuffle);
+	v->Instr.VectorShuffle.vector      = vector;
+	v->Instr.VectorShuffle.indices     = indices;
+	v->Instr.VectorShuffle.index_count = index_count;
+
+	Type *vt = base_type(ssa_type(vector));
+	v->Instr.VectorShuffle.type = make_type_vector(p->module->allocator, vt->Vector.elem, index_count);
+
+	return v;
+}
+
+ssaValue *ssa_make_instr_comment(ssaProcedure *p, String text) {
+	ssaValue *v = ssa_alloc_instr(p, ssaInstr_Comment);
+	v->Instr.Comment.text = text;
+	return v;
+}
+
+ssaValue *ssa_make_instr_bounds_check(ssaProcedure *p, TokenPos pos, ssaValue *index, ssaValue *len) {
+	ssaValue *v = ssa_alloc_instr(p, ssaInstr_BoundsCheck);
+	v->Instr.BoundsCheck.pos   = pos;
+	v->Instr.BoundsCheck.index = index;
+	v->Instr.BoundsCheck.len   = len;
+	return v;
+}
+ssaValue *ssa_make_instr_slice_bounds_check(ssaProcedure *p, TokenPos pos, ssaValue *low, ssaValue *high, ssaValue *max, bool is_substring) {
+	ssaValue *v = ssa_alloc_instr(p, ssaInstr_SliceBoundsCheck);
+	v->Instr.SliceBoundsCheck.pos  = pos;
+	v->Instr.SliceBoundsCheck.low  = low;
+	v->Instr.SliceBoundsCheck.high = high;
+	v->Instr.SliceBoundsCheck.max  = max;
+	v->Instr.SliceBoundsCheck.is_substring = is_substring;
+	return v;
+}
+
+
+
+ssaValue *ssa_make_value_constant(gbAllocator a, Type *type, ExactValue value) {
+	ssaValue *v = ssa_alloc_value(a, ssaValue_Constant);
+	v->Constant.type  = type;
+	v->Constant.value = value;
+	return v;
+}
+
+
+ssaValue *ssa_make_value_constant_slice(gbAllocator a, Type *type, ssaValue *backing_array, i64 count) {
+	ssaValue *v = ssa_alloc_value(a, ssaValue_ConstantSlice);
+	v->ConstantSlice.type = type;
+	v->ConstantSlice.backing_array = backing_array;
+	v->ConstantSlice.count = count;
+	return v;
+}
+
+ssaValue *ssa_make_const_int(gbAllocator a, i64 i) {
+	return ssa_make_value_constant(a, t_int, make_exact_value_integer(i));
+}
+ssaValue *ssa_make_const_i32(gbAllocator a, i64 i) {
+	return ssa_make_value_constant(a, t_i32, make_exact_value_integer(i));
+}
+ssaValue *ssa_make_const_i64(gbAllocator a, i64 i) {
+	return ssa_make_value_constant(a, t_i64, make_exact_value_integer(i));
+}
+ssaValue *ssa_make_const_bool(gbAllocator a, bool b) {
+	return ssa_make_value_constant(a, t_bool, make_exact_value_bool(b != 0));
+}
+ssaValue *ssa_make_const_string(gbAllocator a, String s) {
+	return ssa_make_value_constant(a, t_string, make_exact_value_string(s));
+}
+
+ssaValue *ssa_make_value_procedure(gbAllocator a, ssaModule *m, Entity *entity, Type *type, AstNode *type_expr, AstNode *body, String name) {
+	ssaValue *v = ssa_alloc_value(a, ssaValue_Proc);
+	v->Proc.module = m;
+	v->Proc.entity = entity;
+	v->Proc.type   = type;
+	v->Proc.type_expr = type_expr;
+	v->Proc.body   = body;
+	v->Proc.name   = name;
+	array_init(&v->Proc.referrers, heap_allocator()); // TODO(bill): replace heap allocator
+
+	Type *t = base_type(type);
+	GB_ASSERT(is_type_proc(t));
+	array_init_reserve(&v->Proc.params, heap_allocator(), t->Proc.param_count);
+
+	return v;
+}
+
+ssaBlock *ssa_add_block(ssaProcedure *proc, AstNode *node, char *label) {
+	Scope *scope = NULL;
+	if (node != NULL) {
+		Scope **found = map_scope_get(&proc->module->info->scopes, hash_pointer(node));
+		if (found) {
+			scope = *found;
+		} else {
+			GB_PANIC("Block scope not found for %.*s", LIT(ast_node_strings[node->kind]));
+		}
+	}
+
+	ssaValue *v = ssa_alloc_value(proc->module->allocator, ssaValue_Block);
+	v->Block.label  = make_string_c(label);
+	v->Block.node   = node;
+	v->Block.scope  = scope;
+	v->Block.parent = proc;
+
+	array_init(&v->Block.instrs, heap_allocator());
+	array_init(&v->Block.locals, heap_allocator());
+
+	array_init(&v->Block.preds,  heap_allocator());
+	array_init(&v->Block.succs,  heap_allocator());
+
+	ssaBlock *block = &v->Block;
+
+	array_add(&proc->blocks, block);
+	proc->block_count++;
+
+	return block;
+}
+
+
+
+
+
+ssaDefer ssa_add_defer_node(ssaProcedure *proc, isize scope_index, AstNode *stmt) {
+	ssaDefer d = {ssaDefer_Node};
+	d.scope_index = scope_index;
+	d.block = proc->curr_block;
+	d.stmt = stmt;
+	array_add(&proc->defer_stmts, d);
+	return d;
+}
+
+
+ssaDefer ssa_add_defer_instr(ssaProcedure *proc, isize scope_index, ssaValue *instr) {
+	ssaDefer d = {ssaDefer_Instr};
+	d.scope_index = proc->scope_index;
+	d.block = proc->curr_block;
+	d.instr = instr; // NOTE(bill): It will make a copy everytime it is called
+	array_add(&proc->defer_stmts, d);
+	return d;
+}
+
+
+
+ssaValue *ssa_add_module_constant(ssaModule *m, Type *type, ExactValue value) {
+	gbAllocator a = m->allocator;
+	// gbAllocator a = gb_heap_allocator();
+
+	if (is_type_slice(type)) {
+		ast_node(cl, CompoundLit, value.value_compound);
+
+		isize count = cl->elems.count;
+		if (count == 0) {
+			return ssa_make_value_nil(a, type);
+		}
+		Type *elem = base_type(type)->Slice.elem;
+		Type *t = make_type_array(a, elem, count);
+		ssaValue *backing_array = ssa_add_module_constant(m, t, value);
+
+
+		isize max_len = 7+8+1;
+		u8 *str = cast(u8 *)gb_alloc_array(a, u8, max_len);
+		isize len = gb_snprintf(cast(char *)str, max_len, "__csba$%x", m->global_array_index);
+		m->global_array_index++;
+
+		String name = make_string(str, len-1);
+
+		Entity *e = make_entity_constant(a, NULL, make_token_ident(name), t, value);
+		ssaValue *g = ssa_make_value_global(a, e, backing_array);
+		ssa_module_add_value(m, e, g);
+		map_ssa_value_set(&m->members, hash_string(name), g);
+
+		return ssa_make_value_constant_slice(a, type, g, count);
+	}
+
+	return ssa_make_value_constant(a, type, value);
+}
+
+ssaValue *ssa_add_global_string_array(ssaModule *m, String string) {
+	// TODO(bill): Should this use the arena allocator or the heap allocator?
+	// Strings could be huge!
+	gbAllocator a = m->allocator;
+	// gbAllocator a = gb_heap_allocator();
+
+	isize max_len = 6+8+1;
+	u8 *str = cast(u8 *)gb_alloc_array(a, u8, max_len);
+	isize len = gb_snprintf(cast(char *)str, max_len, "__str$%x", m->global_string_index);
+	m->global_string_index++;
+
+	String name = make_string(str, len-1);
+	Token token = {Token_String};
+	token.string = name;
+	Type *type = make_type_array(a, t_u8, string.len);
+	ExactValue ev = make_exact_value_string(string);
+	Entity *entity = make_entity_constant(a, NULL, token, type, ev);
+	ssaValue *g = ssa_make_value_global(a, entity, ssa_add_module_constant(m, type, ev));
+	g->Global.is_private      = true;
+	// g->Global.is_unnamed_addr = true;
+	// g->Global.is_constant = true;
+
+	ssa_module_add_value(m, entity, g);
+	map_ssa_value_set(&m->members, hash_string(name), g);
+
+	return g;
+}
+
+
+
+
+ssaValue *ssa_add_local(ssaProcedure *proc, Entity *e) {
+	ssaBlock *b = proc->decl_block; // all variables must be in the first block
+	ssaValue *instr = ssa_make_instr_local(proc, e, true);
+	instr->Instr.parent = b;
+	array_add(&b->instrs, instr);
+	array_add(&b->locals, instr);
+	proc->local_count++;
+
+	// if (zero_initialized) {
+		ssa_emit_zero_init(proc, instr);
+	// }
+
+	return instr;
+}
+
+ssaValue *ssa_add_local_for_identifier(ssaProcedure *proc, AstNode *name, bool zero_initialized) {
+	Entity **found = map_entity_get(&proc->module->info->definitions, hash_pointer(name));
+	if (found) {
+		Entity *e = *found;
+		ssa_emit_comment(proc, e->token.string);
+		return ssa_add_local(proc, e);
+	}
+	return NULL;
+}
+
+ssaValue *ssa_add_local_generated(ssaProcedure *proc, Type *type) {
+	GB_ASSERT(type != NULL);
+
+	Scope *scope = NULL;
+	if (proc->curr_block) {
+		scope = proc->curr_block->scope;
+	}
+	Entity *e = make_entity_variable(proc->module->allocator,
+	                                 scope,
+	                                 empty_token,
+	                                 type);
+	return ssa_add_local(proc, e);
+}
+
+ssaValue *ssa_add_param(ssaProcedure *proc, Entity *e) {
+	ssaValue *v = ssa_make_value_param(proc->module->allocator, proc, e);
+#if 1
+	ssaValue *l = ssa_add_local(proc, e);
+	ssa_emit_store(proc, l, v);
+#else
+	ssa_module_add_value(proc->module, e, v);
+#endif
+	return v;
+}
+
+
+
+////////////////////////////////////////////////////////////////
+//
+// @Debug
+//
+////////////////////////////////////////////////////////////////
+
+ssaDebugInfo *ssa_add_debug_info_file(ssaProcedure *proc, AstFile *file) {
+	if (!proc->module->generate_debug_info) {
+		return NULL;
+	}
+
+	GB_ASSERT(file != NULL);
+	ssaDebugInfo *di = ssa_alloc_debug_info(proc->module->allocator, ssaDebugInfo_File);
+	di->File.file = file;
+
+	String filename = file->tokenizer.fullpath;
+	String directory = filename;
+	isize slash_index = 0;
+	for (isize i = filename.len-1; i >= 0; i--) {
+		if (filename.text[i] == '\\' ||
+		    filename.text[i] == '/') {
+			break;
+		}
+		slash_index = i;
+	}
+	directory.len = slash_index-1;
+	filename.text = filename.text + slash_index;
+	filename.len -= slash_index;
+
+
+	di->File.filename = filename;
+	di->File.directory = directory;
+
+	map_ssa_debug_info_set(&proc->module->debug_info, hash_pointer(file), di);
+	return di;
+}
+
+
+ssaDebugInfo *ssa_add_debug_info_proc(ssaProcedure *proc, Entity *entity, String name, ssaDebugInfo *file) {
+	if (!proc->module->generate_debug_info) {
+		return NULL;
+	}
+
+	GB_ASSERT(entity != NULL);
+	ssaDebugInfo *di = ssa_alloc_debug_info(proc->module->allocator, ssaDebugInfo_Proc);
+	di->Proc.entity = entity;
+	di->Proc.name = name;
+	di->Proc.file = file;
+	di->Proc.pos = entity->token.pos;
+
+	map_ssa_debug_info_set(&proc->module->debug_info, hash_pointer(entity), di);
+	return di;
+}
+
+////////////////////////////////////////////////////////////////
+//
+// @Emit
+//
+////////////////////////////////////////////////////////////////
+
+
+ssaValue *ssa_emit(ssaProcedure *proc, ssaValue *instr) {
+	GB_ASSERT(instr->kind == ssaValue_Instr);
+	ssaBlock *b = proc->curr_block;
+	instr->Instr.parent = b;
+	if (b != NULL) {
+		ssaInstr *i = ssa_get_last_instr(b);
+		if (!ssa_is_instr_terminating(i)) {
+			array_add(&b->instrs, instr);
+		}
+	}
+	return instr;
+}
+ssaValue *ssa_emit_store(ssaProcedure *p, ssaValue *address, ssaValue *value) {
+	return ssa_emit(p, ssa_make_instr_store(p, address, value));
+}
+ssaValue *ssa_emit_load(ssaProcedure *p, ssaValue *address) {
+	return ssa_emit(p, ssa_make_instr_load(p, address));
+}
+ssaValue *ssa_emit_select(ssaProcedure *p, ssaValue *cond, ssaValue *t, ssaValue *f) {
+	return ssa_emit(p, ssa_make_instr_select(p, cond, t, f));
+}
+
+ssaValue *ssa_emit_zero_init(ssaProcedure *p, ssaValue *address)  {
+	return ssa_emit(p, ssa_make_instr_zero_init(p, address));
+}
+
+ssaValue *ssa_emit_comment(ssaProcedure *p, String text) {
+	return ssa_emit(p, ssa_make_instr_comment(p, text));
+}
+
+
+ssaValue *ssa_emit_call(ssaProcedure *p, ssaValue *value, ssaValue **args, isize arg_count) {
+	Type *pt = base_type(ssa_type(value));
+	GB_ASSERT(pt->kind == Type_Proc);
+	Type *results = pt->Proc.results;
+	return ssa_emit(p, ssa_make_instr_call(p, value, args, arg_count, results));
+}
+
+ssaValue *ssa_emit_global_call(ssaProcedure *proc, char *name_, ssaValue **args, isize arg_count) {
+	String name = make_string_c(name_);
+	ssaValue **found = map_ssa_value_get(&proc->module->members, hash_string(name));
+	GB_ASSERT_MSG(found != NULL, "%.*s", LIT(name));
+	ssaValue *gp = *found;
+	return ssa_emit_call(proc, gp, args, arg_count);
+}
+
+
+
+void ssa_emit_defer_stmts(ssaProcedure *proc, ssaDeferExitKind kind, ssaBlock *block) {
+	isize count = proc->defer_stmts.count;
+	isize i = count;
+	while (i --> 0) {
+		ssaDefer d = proc->defer_stmts.e[i];
+		if (kind == ssaDeferExit_Default) {
+			if (proc->scope_index == d.scope_index &&
+			    d.scope_index > 1) {
+				ssa_build_defer_stmt(proc, d);
+				array_pop(&proc->defer_stmts);
+				continue;
+			} else {
+				break;
+			}
+		} else if (kind == ssaDeferExit_Return) {
+			ssa_build_defer_stmt(proc, d);
+		} else if (kind == ssaDeferExit_Branch) {
+			GB_ASSERT(block != NULL);
+			isize lower_limit = block->scope_index+1;
+			if (lower_limit < d.scope_index) {
+				ssa_build_defer_stmt(proc, d);
+			}
+		}
+	}
+}
+
+
+void ssa_open_scope(ssaProcedure *proc) {
+	proc->scope_index++;
+}
+
+void ssa_close_scope(ssaProcedure *proc, ssaDeferExitKind kind, ssaBlock *block) {
+	ssa_emit_defer_stmts(proc, kind, block);
+	GB_ASSERT(proc->scope_index > 0);
+	proc->scope_index--;
+}
+
+
+
+void ssa_emit_unreachable(ssaProcedure *proc) {
+	ssa_emit(proc, ssa_make_instr_unreachable(proc));
+}
+
+void ssa_emit_return(ssaProcedure *proc, ssaValue *v) {
+	ssa_emit_defer_stmts(proc, ssaDeferExit_Return, NULL);
+	ssa_emit(proc, ssa_make_instr_return(proc, v));
+}
+
+void ssa_emit_jump(ssaProcedure *proc, ssaBlock *target_block) {
+	ssaBlock *b = proc->curr_block;
+	if (b == NULL) {
+		return;
+	}
+	ssa_emit(proc, ssa_make_instr_jump(proc, target_block));
+	ssa_add_edge(b, target_block);
+	proc->curr_block = NULL;
+}
+
+void ssa_emit_if(ssaProcedure *proc, ssaValue *cond, ssaBlock *true_block, ssaBlock *false_block) {
+	ssaBlock *b = proc->curr_block;
+	if (b == NULL) {
+		return;
+	}
+	ssa_emit(proc, ssa_make_instr_if(proc, cond, true_block, false_block));
+	ssa_add_edge(b, true_block);
+	ssa_add_edge(b, false_block);
+	proc->curr_block = NULL;
+}
+
+void ssa_emit_startup_runtime(ssaProcedure *proc) {
+	GB_ASSERT(proc->parent == NULL && str_eq(proc->name, str_lit("main")));
+	ssa_emit(proc, ssa_alloc_instr(proc, ssaInstr_StartupRuntime));
+}
+
+
+
+
+ssaValue *ssa_addr_store(ssaProcedure *proc, ssaAddr addr, ssaValue *value) {
+	if (addr.addr == NULL) {
+		return NULL;
+	}
+
+	if (addr.kind == ssaAddr_Vector) {
+		ssaValue *v = ssa_emit_load(proc, addr.addr);
+		Type *elem_type = base_type(ssa_type(v))->Vector.elem;
+		ssaValue *elem = ssa_emit_conv(proc, value, elem_type);
+		ssaValue *out = ssa_emit(proc, ssa_make_instr_insert_element(proc, v, elem, addr.Vector.index));
+		return ssa_emit_store(proc, addr.addr, out);
+	} else {
+		ssaValue *v = ssa_emit_conv(proc, value, ssa_addr_type(addr));
+		return ssa_emit_store(proc, addr.addr, v);
+	}
+}
+ssaValue *ssa_addr_load(ssaProcedure *proc, ssaAddr addr) {
+	if (addr.addr == NULL) {
+		GB_PANIC("Illegal addr load");
+		return NULL;
+	}
+
+	if (addr.kind == ssaAddr_Vector) {
+		ssaValue *v = ssa_emit_load(proc, addr.addr);
+		return ssa_emit(proc, ssa_make_instr_extract_element(proc, v, addr.Vector.index));
+	}
+	Type *t = base_type(ssa_type(addr.addr));
+	if (t->kind == Type_Proc) {
+		// NOTE(bill): Imported procedures don't require a load as they are pointers
+		return addr.addr;
+	}
+	return ssa_emit_load(proc, addr.addr);
+}
+
+
+
+
+ssaValue *ssa_emit_ptr_offset(ssaProcedure *proc, ssaValue *ptr, ssaValue *offset) {
+	offset = ssa_emit_conv(proc, offset, t_int);
+	return ssa_emit(proc, ssa_make_instr_ptr_offset(proc, ptr, offset));
+}
+
+ssaValue *ssa_emit_arith(ssaProcedure *proc, TokenKind op, ssaValue *left, ssaValue *right, Type *type) {
+	Type *t_left = ssa_type(left);
+	Type *t_right = ssa_type(right);
+
+	if (op == Token_Add) {
+		if (is_type_pointer(t_left)) {
+			ssaValue *ptr = ssa_emit_conv(proc, left, type);
+			ssaValue *offset = right;
+			return ssa_emit_ptr_offset(proc, ptr, offset);
+		} else if (is_type_pointer(ssa_type(right))) {
+			ssaValue *ptr = ssa_emit_conv(proc, right, type);
+			ssaValue *offset = left;
+			return ssa_emit_ptr_offset(proc, ptr, offset);
+		}
+	} else if (op == Token_Sub) {
+		if (is_type_pointer(t_left) && is_type_integer(t_right)) {
+			// ptr - int
+			ssaValue *ptr = ssa_emit_conv(proc, left, type);
+			ssaValue *offset = right;
+			return ssa_emit_ptr_offset(proc, ptr, offset);
+		} else if (is_type_pointer(t_left) && is_type_pointer(t_right)) {
+			GB_ASSERT(is_type_integer(type));
+			Type *ptr_type = t_left;
+			ssaModule *m = proc->module;
+			ssaValue *x = ssa_emit_conv(proc, left, type);
+			ssaValue *y = ssa_emit_conv(proc, right, type);
+			ssaValue *diff = ssa_emit_arith(proc, op, x, y, type);
+			ssaValue *elem_size = ssa_make_const_int(m->allocator, type_size_of(m->sizes, m->allocator, ptr_type));
+			return ssa_emit_arith(proc, Token_Quo, diff, elem_size, type);
+		}
+	}
+
+
+	switch (op) {
+	case Token_AndNot: {
+		// NOTE(bill): x &~ y == x & (~y) == x & (y ~ -1)
+		// NOTE(bill): "not" `x` == `x` "xor" `-1`
+		ssaValue *neg = ssa_add_module_constant(proc->module, type, make_exact_value_integer(-1));
+		op = Token_Xor;
+		right = ssa_emit_arith(proc, op, right, neg, type);
+		GB_ASSERT(right->Instr.kind == ssaInstr_BinaryOp);
+		right->Instr.BinaryOp.type = type;
+		op = Token_And;
+	} /* fallthrough */
+	case Token_Add:
+	case Token_Sub:
+	case Token_Mul:
+	case Token_Quo:
+	case Token_Mod:
+	case Token_And:
+	case Token_Or:
+	case Token_Xor:
+	case Token_Shl:
+	case Token_Shr:
+		left  = ssa_emit_conv(proc, left, type);
+		right = ssa_emit_conv(proc, right, type);
+		break;
+	}
+
+	return ssa_emit(proc, ssa_make_instr_binary_op(proc, op, left, right, type));
+}
+
+ssaValue *ssa_emit_comp(ssaProcedure *proc, TokenKind op_kind, ssaValue *left, ssaValue *right) {
+	Type *a = base_type(ssa_type(left));
+	Type *b = base_type(ssa_type(right));
+
+	if (are_types_identical(a, b)) {
+		// NOTE(bill): No need for a conversion
+	} else if (left->kind == ssaValue_Constant || left->kind == ssaValue_Nil) {
+		left = ssa_emit_conv(proc, left, ssa_type(right));
+	} else if (right->kind == ssaValue_Constant || right->kind == ssaValue_Nil) {
+		right = ssa_emit_conv(proc, right, ssa_type(left));
+	}
+
+	Type *result = t_bool;
+	if (is_type_vector(a)) {
+		result = make_type_vector(proc->module->allocator, t_bool, a->Vector.count);
+	}
+	return ssa_emit(proc, ssa_make_instr_binary_op(proc, op_kind, left, right, result));
+}
+
+ssaValue *ssa_emit_array_ep(ssaProcedure *proc, ssaValue *s, ssaValue *index) {
+	GB_ASSERT(index != NULL);
+	Type *st = base_type(type_deref(ssa_type(s)));
+	GB_ASSERT(is_type_array(st) || is_type_vector(st));
+
+	// NOTE(bill): For some weird legacy reason in LLVM, structure elements must be accessed as an i32
+	index = ssa_emit_conv(proc, index, t_i32);
+	return ssa_emit(proc, ssa_make_instr_array_element_ptr(proc, s, index));
+}
+
+ssaValue *ssa_emit_array_epi(ssaProcedure *proc, ssaValue *s, i32 index) {
+	return ssa_emit_array_ep(proc, s, ssa_make_const_i32(proc->module->allocator, index));
+}
+
+ssaValue *ssa_emit_union_tag_ptr(ssaProcedure *proc, ssaValue *u) {
+	Type *t = ssa_type(u);
+	GB_ASSERT(is_type_pointer(t) &&
+	          is_type_union(type_deref(t)));
+	return ssa_emit(proc, ssa_make_instr_union_tag_ptr(proc, u));
+}
+
+ssaValue *ssa_emit_union_tag_value(ssaProcedure *proc, ssaValue *u) {
+	Type *t = ssa_type(u);
+	GB_ASSERT(is_type_union(t));
+	return ssa_emit(proc, ssa_make_instr_union_tag_value(proc, u));
+}
+
+
+
+ssaValue *ssa_emit_struct_ep(ssaProcedure *proc, ssaValue *s, i32 index) {
+	gbAllocator a = proc->module->allocator;
+	Type *t = base_type(type_deref(ssa_type(s)));
+	Type *result_type = NULL;
+	ssaValue *gep = NULL;
+
+	if (is_type_struct(t)) {
+		GB_ASSERT(t->Record.field_count > 0);
+		GB_ASSERT(gb_is_between(index, 0, t->Record.field_count-1));
+		result_type = make_type_pointer(a, t->Record.fields[index]->type);
+	} else if (is_type_tuple(t)) {
+		GB_ASSERT(t->Tuple.variable_count > 0);
+		GB_ASSERT(gb_is_between(index, 0, t->Tuple.variable_count-1));
+		result_type = make_type_pointer(a, t->Tuple.variables[index]->type);
+	} else if (is_type_slice(t)) {
+		switch (index) {
+		case 0: result_type = make_type_pointer(a, make_type_pointer(a, t->Slice.elem)); break;
+		case 1: result_type = make_type_pointer(a, t_int); break;
+		case 2: result_type = make_type_pointer(a, t_int); break;
+		}
+	} else if (is_type_string(t)) {
+		switch (index) {
+		case 0: result_type = make_type_pointer(a, t_u8_ptr); break;
+		case 1: result_type = make_type_pointer(a, t_int);    break;
+		}
+	} else if (is_type_any(t)) {
+		switch (index) {
+		case 0: result_type = make_type_pointer(a, t_type_info_ptr); break;
+		case 1: result_type = make_type_pointer(a, t_rawptr);        break;
+		}
+	} else if (is_type_maybe(t)) {
+		switch (index) {
+		case 0: result_type = make_type_pointer(a, t->Maybe.elem); break;
+		case 1: result_type = make_type_pointer(a, t_bool);        break;
+		}
+	} else {
+		GB_PANIC("TODO(bill): struct_gep type: %s, %d", type_to_string(ssa_type(s)), index);
+	}
+
+	GB_ASSERT(result_type != NULL);
+
+	gep = ssa_make_instr_struct_element_ptr(proc, s, index, result_type);
+	return ssa_emit(proc, gep);
+}
+
+
+
+ssaValue *ssa_emit_array_ev(ssaProcedure *proc, ssaValue *s, i32 index) {
+	Type *st = base_type(ssa_type(s));
+	GB_ASSERT(is_type_array(st));
+	return ssa_emit(proc, ssa_make_instr_array_extract_value(proc, s, index));
+}
+
+ssaValue *ssa_emit_struct_ev(ssaProcedure *proc, ssaValue *s, i32 index) {
+	// NOTE(bill): For some weird legacy reason in LLVM, structure elements must be accessed as an i32
+
+	gbAllocator a = proc->module->allocator;
+	Type *t = base_type(ssa_type(s));
+	Type *result_type = NULL;
+
+	if (is_type_struct(t)) {
+		GB_ASSERT(t->Record.field_count > 0);
+		GB_ASSERT(gb_is_between(index, 0, t->Record.field_count-1));
+		result_type = t->Record.fields[index]->type;
+	} else if (is_type_tuple(t)) {
+		GB_ASSERT(t->Tuple.variable_count > 0);
+		GB_ASSERT(gb_is_between(index, 0, t->Tuple.variable_count-1));
+		result_type = t->Tuple.variables[index]->type;
+	} else if (is_type_slice(t)) {
+		switch (index) {
+		case 0: result_type = make_type_pointer(a, t->Slice.elem); break;
+		case 1: result_type = t_int; break;
+		case 2: result_type = t_int; break;
+		}
+	} else if (is_type_string(t)) {
+		switch (index) {
+		case 0: result_type = t_u8_ptr; break;
+		case 1: result_type = t_int;    break;
+		}
+	} else if (is_type_any(t)) {
+		switch (index) {
+		case 0: result_type = t_type_info_ptr; break;
+		case 1: result_type = t_rawptr;        break;
+		}
+	} else if (is_type_maybe(t)) {
+		switch (index) {
+		case 0: result_type = t->Maybe.elem; break;
+		case 1: result_type = t_bool;        break;
+		}
+	} else {
+		GB_PANIC("TODO(bill): struct_ev type: %s, %d", type_to_string(ssa_type(s)), index);
+	}
+
+	GB_ASSERT(result_type != NULL);
+
+	return ssa_emit(proc, ssa_make_instr_struct_extract_value(proc, s, index, result_type));
+}
+
+
+ssaValue *ssa_emit_deep_field_gep(ssaProcedure *proc, Type *type, ssaValue *e, Selection sel) {
+	GB_ASSERT(sel.index.count > 0);
+
+	for_array(i, sel.index) {
+		i32 index = cast(i32)sel.index.e[i];
+		if (is_type_pointer(type)) {
+			type = type_deref(type);
+			e = ssa_emit_load(proc, e);
+			e = ssa_emit_ptr_offset(proc, e, v_zero); // TODO(bill): Do I need these copies?
+		}
+		type = base_type(type);
+
+
+		if (is_type_raw_union(type)) {
+			type = type->Record.fields[index]->type;
+			e = ssa_emit_conv(proc, e, make_type_pointer(proc->module->allocator, type));
+		} else if (type->kind == Type_Record) {
+			type = type->Record.fields[index]->type;
+			e = ssa_emit_struct_ep(proc, e, index);
+		} else if (type->kind == Type_Basic) {
+			switch (type->Basic.kind) {
+			case Basic_any: {
+				if (index == 0) {
+					type = t_type_info_ptr;
+				} else if (index == 1) {
+					type = t_rawptr;
+				}
+				e = ssa_emit_struct_ep(proc, e, index);
+			} break;
+
+			case Basic_string:
+				e = ssa_emit_struct_ep(proc, e, index);
+				break;
+
+			default:
+				GB_PANIC("un-gep-able type");
+				break;
+			}
+		} else if (type->kind == Type_Slice) {
+			e = ssa_emit_struct_ep(proc, e, index);
+		} else if (type->kind == Type_Vector) {
+			e = ssa_emit_array_epi(proc, e, index);
+		} else if (type->kind == Type_Array) {
+			e = ssa_emit_array_epi(proc, e, index);
+		} else {
+			GB_PANIC("un-gep-able type");
+		}
+	}
+
+	return e;
+}
+
+
+ssaValue *ssa_emit_deep_field_ev(ssaProcedure *proc, Type *type, ssaValue *e, Selection sel) {
+	GB_ASSERT(sel.index.count > 0);
+
+	for_array(i, sel.index) {
+		i32 index = cast(i32)sel.index.e[i];
+		if (is_type_pointer(type)) {
+			type = type_deref(type);
+			e = ssa_emit_load(proc, e);
+			e = ssa_emit_ptr_offset(proc, e, v_zero); // TODO(bill): Do I need these copies?
+		}
+		type = base_type(type);
+
+
+		if (is_type_raw_union(type)) {
+			GB_PANIC("TODO(bill): IS THIS EVEN CORRECT?");
+			type = type->Record.fields[index]->type;
+			e = ssa_emit_conv(proc, e, type);
+		} else {
+			e = ssa_emit_struct_ev(proc, e, index);
+		}
+	}
+
+	return e;
+}
+
+
+
+
+ssaValue *ssa_array_elem(ssaProcedure *proc, ssaValue *array) {
+	return ssa_emit_array_ep(proc, array, v_zero32);
+}
+ssaValue *ssa_array_len(ssaProcedure *proc, ssaValue *array) {
+	Type *t = ssa_type(array);
+	GB_ASSERT(t->kind == Type_Array);
+	return ssa_make_const_int(proc->module->allocator, t->Array.count);
+}
+ssaValue *ssa_array_cap(ssaProcedure *proc, ssaValue *array) {
+	return ssa_array_len(proc, array);
+}
+
+ssaValue *ssa_slice_elem(ssaProcedure *proc, ssaValue *slice) {
+	Type *t = ssa_type(slice);
+	GB_ASSERT(t->kind == Type_Slice);
+	return ssa_emit_struct_ev(proc, slice, 0);
+}
+ssaValue *ssa_slice_len(ssaProcedure *proc, ssaValue *slice) {
+	Type *t = ssa_type(slice);
+	GB_ASSERT(t->kind == Type_Slice);
+	return ssa_emit_struct_ev(proc, slice, 1);
+}
+ssaValue *ssa_slice_cap(ssaProcedure *proc, ssaValue *slice) {
+	Type *t = ssa_type(slice);
+	GB_ASSERT(t->kind == Type_Slice);
+	return ssa_emit_struct_ev(proc, slice, 2);
+}
+
+ssaValue *ssa_string_elem(ssaProcedure *proc, ssaValue *string) {
+	Type *t = ssa_type(string);
+	GB_ASSERT(t->kind == Type_Basic && t->Basic.kind == Basic_string);
+	return ssa_emit_struct_ev(proc, string, 0);
+}
+ssaValue *ssa_string_len(ssaProcedure *proc, ssaValue *string) {
+	Type *t = ssa_type(string);
+	GB_ASSERT(t->kind == Type_Basic && t->Basic.kind == Basic_string);
+	return ssa_emit_struct_ev(proc, string, 1);
+}
+
+
+
+ssaValue *ssa_add_local_slice(ssaProcedure *proc, Type *slice_type, ssaValue *base, ssaValue *low, ssaValue *high, ssaValue *max) {
+	// TODO(bill): array bounds checking for slice creation
+	// TODO(bill): check that low < high <= max
+	gbAllocator a = proc->module->allocator;
+	Type *bt = base_type(ssa_type(base));
+
+	if (low == NULL) {
+		low = v_zero;
+	}
+	if (high == NULL) {
+		switch (bt->kind) {
+		case Type_Array:   high = ssa_array_len(proc, base); break;
+		case Type_Slice:   high = ssa_slice_len(proc, base); break;
+		case Type_Pointer: high = v_one;                     break;
+		}
+	}
+	if (max == NULL) {
+		switch (bt->kind) {
+		case Type_Array:   max = ssa_array_cap(proc, base); break;
+		case Type_Slice:   max = ssa_slice_cap(proc, base); break;
+		case Type_Pointer: max = high;                      break;
+		}
+	}
+	GB_ASSERT(max != NULL);
+
+	ssaValue *len = ssa_emit_arith(proc, Token_Sub, high, low, t_int);
+	ssaValue *cap = ssa_emit_arith(proc, Token_Sub, max,  low, t_int);
+
+	ssaValue *elem = NULL;
+	switch (bt->kind) {
+	case Type_Array:   elem = ssa_array_elem(proc, base); break;
+	case Type_Slice:   elem = ssa_slice_elem(proc, base); break;
+	case Type_Pointer: elem = ssa_emit_load(proc, base);  break;
+	}
+
+	elem = ssa_emit_ptr_offset(proc, elem, low);
+
+	ssaValue *slice = ssa_add_local_generated(proc, slice_type);
+
+	ssaValue *gep = NULL;
+	gep = ssa_emit_struct_ep(proc, slice, 0);
+	ssa_emit_store(proc, gep, elem);
+
+	gep = ssa_emit_struct_ep(proc, slice, 1);
+	ssa_emit_store(proc, gep, len);
+
+	gep = ssa_emit_struct_ep(proc, slice, 2);
+	ssa_emit_store(proc, gep, cap);
+
+	return slice;
+}
+
+ssaValue *ssa_emit_string(ssaProcedure *proc, ssaValue *elem, ssaValue *len) {
+	ssaValue *str = ssa_add_local_generated(proc, t_string);
+	ssaValue *str_elem = ssa_emit_struct_ep(proc, str, 0);
+	ssaValue *str_len = ssa_emit_struct_ep(proc, str, 1);
+	ssa_emit_store(proc, str_elem, elem);
+	ssa_emit_store(proc, str_len, len);
+	return ssa_emit_load(proc, str);
+}
+
+
+
+
+String lookup_polymorphic_field(CheckerInfo *info, Type *dst, Type *src) {
+	Type *prev_src = src;
+	// Type *prev_dst = dst;
+	src = base_type(type_deref(src));
+	// dst = base_type(type_deref(dst));
+	bool src_is_ptr = src != prev_src;
+	// bool dst_is_ptr = dst != prev_dst;
+
+	GB_ASSERT(is_type_struct(src));
+	for (isize i = 0; i < src->Record.field_count; i++) {
+		Entity *f = src->Record.fields[i];
+		if (f->kind == Entity_Variable && f->flags & EntityFlag_Anonymous) {
+			if (are_types_identical(dst, f->type)) {
+				return f->token.string;
+			}
+			if (src_is_ptr && is_type_pointer(dst)) {
+				if (are_types_identical(type_deref(dst), f->type)) {
+					return f->token.string;
+				}
+			}
+			if (is_type_struct(f->type)) {
+				String name = lookup_polymorphic_field(info, dst, f->type);
+				if (name.len > 0) {
+					return name;
+				}
+			}
+		}
+	}
+	return str_lit("");
+}
+
+ssaValue *ssa_emit_bitcast(ssaProcedure *proc, ssaValue *data, Type *type) {
+	return ssa_emit(proc, ssa_make_instr_conv(proc, ssaConv_bitcast, data, ssa_type(data), type));
+}
+
+
+ssaValue *ssa_emit_conv(ssaProcedure *proc, ssaValue *value, Type *t) {
+	Type *src_type = ssa_type(value);
+	if (are_types_identical(t, src_type)) {
+		return value;
+	}
+
+	Type *src = base_type(get_enum_base_type(src_type));
+	Type *dst = base_type(get_enum_base_type(t));
+
+	if (value->kind == ssaValue_Constant) {
+		if (is_type_any(dst)) {
+			ssaValue *default_value = ssa_add_local_generated(proc, default_type(src_type));
+			ssa_emit_store(proc, default_value, value);
+			return ssa_emit_conv(proc, ssa_emit_load(proc, default_value), t_any);
+		} else if (dst->kind == Type_Basic) {
+			ExactValue ev = value->Constant.value;
+			if (is_type_float(dst)) {
+				ev = exact_value_to_float(ev);
+			} else if (is_type_string(dst)) {
+				// Handled elsewhere
+				GB_ASSERT(ev.kind == ExactValue_String);
+			} else if (is_type_integer(dst)) {
+				ev = exact_value_to_integer(ev);
+			} else if (is_type_pointer(dst)) {
+				// IMPORTANT NOTE(bill): LLVM doesn't support pointer constants expect `null`
+				ssaValue *i = ssa_add_module_constant(proc->module, t_uint, ev);
+				return ssa_emit(proc, ssa_make_instr_conv(proc, ssaConv_inttoptr, i, t_uint, dst));
+			}
+			return ssa_add_module_constant(proc->module, t, ev);
+		}
+	}
+
+	if (are_types_identical(src, dst)) {
+		return value;
+	}
+
+	if (is_type_maybe(dst)) {
+		ssaValue *maybe = ssa_add_local_generated(proc, dst);
+		ssaValue *val = ssa_emit_struct_ep(proc, maybe, 0);
+		ssaValue *set = ssa_emit_struct_ep(proc, maybe, 1);
+		ssa_emit_store(proc, val, value);
+		ssa_emit_store(proc, set, v_true);
+		return ssa_emit_load(proc, maybe);
+	}
+
+	// integer -> integer
+	if (is_type_integer(src) && is_type_integer(dst)) {
+		GB_ASSERT(src->kind == Type_Basic &&
+		          dst->kind == Type_Basic);
+		i64 sz = type_size_of(proc->module->sizes, proc->module->allocator, src);
+		i64 dz = type_size_of(proc->module->sizes, proc->module->allocator, dst);
+		if (sz == dz) {
+			// NOTE(bill): In LLVM, all integers are signed and rely upon 2's compliment
+			return value;
+		}
+
+		ssaConvKind kind = ssaConv_trunc;
+		if (dz >= sz) {
+			kind = ssaConv_zext;
+		}
+		return ssa_emit(proc, ssa_make_instr_conv(proc, kind, value, src, dst));
+	}
+
+	// boolean -> integer
+	if (is_type_boolean(src) && is_type_integer(dst)) {
+		return ssa_emit(proc, ssa_make_instr_conv(proc, ssaConv_zext, value, src, dst));
+	}
+
+	// integer -> boolean
+	if (is_type_integer(src) && is_type_boolean(dst)) {
+		return ssa_emit_comp(proc, Token_NotEq, value, v_zero);
+	}
+
+
+	// float -> float
+	if (is_type_float(src) && is_type_float(dst)) {
+		i64 sz = type_size_of(proc->module->sizes, proc->module->allocator, src);
+		i64 dz = type_size_of(proc->module->sizes, proc->module->allocator, dst);
+		ssaConvKind kind = ssaConv_fptrunc;
+		if (dz >= sz) {
+			kind = ssaConv_fpext;
+		}
+		return ssa_emit(proc, ssa_make_instr_conv(proc, kind, value, src, dst));
+	}
+
+	// float <-> integer
+	if (is_type_float(src) && is_type_integer(dst)) {
+		ssaConvKind kind = ssaConv_fptosi;
+		if (is_type_unsigned(dst)) {
+			kind = ssaConv_fptoui;
+		}
+		return ssa_emit(proc, ssa_make_instr_conv(proc, kind, value, src, dst));
+	}
+	if (is_type_integer(src) && is_type_float(dst)) {
+		ssaConvKind kind = ssaConv_sitofp;
+		if (is_type_unsigned(src)) {
+			kind = ssaConv_uitofp;
+		}
+		return ssa_emit(proc, ssa_make_instr_conv(proc, kind, value, src, dst));
+	}
+
+	// Pointer <-> int
+	if (is_type_pointer(src) && is_type_int_or_uint(dst)) {
+		return ssa_emit(proc, ssa_make_instr_conv(proc, ssaConv_ptrtoint, value, src, dst));
+	}
+	if (is_type_int_or_uint(src) && is_type_pointer(dst)) {
+		return ssa_emit(proc, ssa_make_instr_conv(proc, ssaConv_inttoptr, value, src, dst));
+	}
+
+	if (is_type_union(dst)) {
+		for (isize i = 0; i < dst->Record.field_count; i++) {
+			Entity *f = dst->Record.fields[i];
+			if (are_types_identical(f->type, src_type)) {
+				ssa_emit_comment(proc, str_lit("union - child to parent"));
+				gbAllocator allocator = proc->module->allocator;
+				ssaValue *parent = ssa_add_local_generated(proc, t);
+				ssaValue *tag = ssa_make_const_int(allocator, i);
+				ssa_emit_store(proc, ssa_emit_union_tag_ptr(proc, parent), tag);
+
+				ssaValue *data = ssa_emit_conv(proc, parent, t_rawptr);
+
+				Type *tag_type = src_type;
+				Type *tag_type_ptr = make_type_pointer(allocator, tag_type);
+				ssaValue *underlying = ssa_emit_bitcast(proc, data, tag_type_ptr);
+				ssa_emit_store(proc, underlying, value);
+
+				return ssa_emit_load(proc, parent);
+			}
+		}
+	}
+
+	// NOTE(bill): This has to be done beofre `Pointer <-> Pointer` as it's
+	// subtype polymorphism casting
+	{
+		Type *sb = base_type(type_deref(src));
+		bool src_is_ptr = src != sb;
+		if (is_type_struct(sb)) {
+			String field_name = lookup_polymorphic_field(proc->module->info, t, src);
+			// gb_printf("field_name: %.*s\n", LIT(field_name));
+			if (field_name.len > 0) {
+				// NOTE(bill): It can be casted
+				Selection sel = lookup_field(proc->module->allocator, sb, field_name, false);
+				if (sel.entity != NULL) {
+					ssa_emit_comment(proc, str_lit("cast - polymorphism"));
+					if (src_is_ptr) {
+						value = ssa_emit_load(proc, value);
+					}
+					return ssa_emit_deep_field_ev(proc, sb, value, sel);
+				}
+			}
+		}
+	}
+
+
+
+	// Pointer <-> Pointer
+	if (is_type_pointer(src) && is_type_pointer(dst)) {
+		return ssa_emit_bitcast(proc, value, dst);
+	}
+
+
+
+	// proc <-> proc
+	if (is_type_proc(src) && is_type_proc(dst)) {
+		return ssa_emit_bitcast(proc, value, dst);
+	}
+
+	// pointer -> proc
+	if (is_type_pointer(src) && is_type_proc(dst)) {
+		return ssa_emit_bitcast(proc, value, dst);
+	}
+	// proc -> pointer
+	if (is_type_proc(src) && is_type_pointer(dst)) {
+		return ssa_emit_bitcast(proc, value, dst);
+	}
+
+
+
+	// []byte/[]u8 <-> string
+	if (is_type_u8_slice(src) && is_type_string(dst)) {
+		ssaValue *elem = ssa_slice_elem(proc, value);
+		ssaValue *len  = ssa_slice_len(proc, value);
+		return ssa_emit_string(proc, elem, len);
+	}
+	if (is_type_string(src) && is_type_u8_slice(dst)) {
+		ssaValue *elem = ssa_string_elem(proc, value);
+		ssaValue *elem_ptr = ssa_add_local_generated(proc, ssa_type(elem));
+		ssa_emit_store(proc, elem_ptr, elem);
+
+		ssaValue *len  = ssa_string_len(proc, value);
+		ssaValue *slice = ssa_add_local_slice(proc, dst, elem_ptr, v_zero, len, len);
+		return ssa_emit_load(proc, slice);
+	}
+
+	if (is_type_vector(dst)) {
+		Type *dst_elem = dst->Vector.elem;
+		value = ssa_emit_conv(proc, value, dst_elem);
+		ssaValue *v = ssa_add_local_generated(proc, t);
+		v = ssa_emit_load(proc, v);
+		v = ssa_emit(proc, ssa_make_instr_insert_element(proc, v, value, v_zero32));
+		// NOTE(bill): Broadcast lowest value to all values
+		isize index_count = dst->Vector.count;
+		i32 *indices = gb_alloc_array(proc->module->allocator, i32, index_count);
+		for (isize i = 0; i < index_count; i++) {
+			indices[i] = 0;
+		}
+
+		v = ssa_emit(proc, ssa_make_instr_vector_shuffle(proc, v, indices, index_count));
+		return v;
+	}
+
+	if (is_type_any(dst)) {
+		ssaValue *result = ssa_add_local_generated(proc, t_any);
+
+		if (is_type_untyped_nil(src)) {
+			return ssa_emit_load(proc, result);
+		}
+
+		ssaValue *data = NULL;
+		if (value->kind == ssaValue_Instr &&
+		    value->Instr.kind == ssaInstr_Load) {
+			// NOTE(bill): Addressable value
+			data = value->Instr.Load.address;
+		} else {
+			// NOTE(bill): Non-addressable value
+			data = ssa_add_local_generated(proc, src_type);
+			ssa_emit_store(proc, data, value);
+		}
+		GB_ASSERT(is_type_pointer(ssa_type(data)));
+		GB_ASSERT(is_type_typed(src_type));
+		data = ssa_emit_conv(proc, data, t_rawptr);
+
+
+		ssaValue *ti = ssa_type_info(proc, src_type);
+
+		ssaValue *gep0 = ssa_emit_struct_ep(proc, result, 0);
+		ssaValue *gep1 = ssa_emit_struct_ep(proc, result, 1);
+		ssa_emit_store(proc, gep0, ti);
+		ssa_emit_store(proc, gep1, data);
+
+		return ssa_emit_load(proc, result);
+	}
+
+	if (is_type_untyped_nil(src) && type_has_nil(dst)) {
+		return ssa_make_value_nil(proc->module->allocator, t);
+	}
+
+
+	gb_printf_err("ssa_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_PANIC("Invalid type conversion: `%s` to `%s`", type_to_string(src_type), type_to_string(t));
+
+	return NULL;
+}
+
+bool ssa_is_type_aggregate(Type *t) {
+	t = base_type(get_enum_base_type(t));
+	switch (t->kind) {
+	case Type_Basic:
+		switch (t->Basic.kind) {
+		case Basic_string:
+		case Basic_any:
+			return true;
+		}
+		break;
+
+	case Type_Pointer:
+	case Type_Vector:
+		return false;
+
+	case Type_Array:
+	case Type_Slice:
+	case Type_Maybe:
+	case Type_Record:
+	case Type_Tuple:
+		return true;
+
+	case Type_Named:
+		return ssa_is_type_aggregate(t->Named.base);
+	}
+
+	return false;
+}
+
+ssaValue *ssa_emit_transmute(ssaProcedure *proc, ssaValue *value, Type *t) {
+	Type *src_type = ssa_type(value);
+	if (are_types_identical(t, src_type)) {
+		return value;
+	}
+
+	Type *src = base_type(src_type);
+	Type *dst = base_type(t);
+	if (are_types_identical(t, src_type)) {
+		return value;
+	}
+
+	ssaModule *m = proc->module;
+
+	i64 sz = type_size_of(m->sizes, m->allocator, src);
+	i64 dz = type_size_of(m->sizes, m->allocator, dst);
+
+	GB_ASSERT_MSG(sz == dz, "Invalid transmute conversion: `%s` to `%s`", type_to_string(src_type), type_to_string(t));
+
+	if (ssa_is_type_aggregate(src) || ssa_is_type_aggregate(dst)) {
+		ssaValue *s = ssa_add_local_generated(proc, src);
+		ssa_emit_store(proc, s, value);
+
+		ssaValue *d = ssa_emit_bitcast(proc, s, make_type_pointer(m->allocator, dst));
+		return ssa_emit_load(proc, d);
+	}
+
+	// TODO(bill): Actually figure out what the conversion needs to be correctly 'cause LLVM
+
+	return ssa_emit_bitcast(proc, value, dst);
+}
+
+ssaValue *ssa_emit_down_cast(ssaProcedure *proc, ssaValue *value, Type *t) {
+	GB_ASSERT(is_type_pointer(ssa_type(value)));
+	gbAllocator allocator = proc->module->allocator;
+
+	String field_name = check_down_cast_name(t, type_deref(ssa_type(value)));
+	GB_ASSERT(field_name.len > 0);
+	Selection sel = lookup_field(proc->module->allocator, t, field_name, false);
+	ssaValue *bytes = ssa_emit_conv(proc, value, t_u8_ptr);
+
+	i64 offset_ = type_offset_of_from_selection(proc->module->sizes, allocator, type_deref(t), sel);
+	ssaValue *offset = ssa_make_const_int(allocator, -offset_);
+	ssaValue *head = ssa_emit_ptr_offset(proc, bytes, offset);
+	return ssa_emit_conv(proc, head, t);
+}
+
+ssaValue *ssa_emit_union_cast(ssaProcedure *proc, ssaValue *value, Type *tuple) {
+	GB_ASSERT(tuple->kind == Type_Tuple);
+	gbAllocator a = proc->module->allocator;
+
+	Type *src_type = ssa_type(value);
+	bool is_ptr = is_type_pointer(src_type);
+
+	ssaValue *v = ssa_add_local_generated(proc, tuple);
+
+	if (is_ptr) {
+		Type *src = base_type(type_deref(src_type));
+		Type *src_ptr = src_type;
+		GB_ASSERT(is_type_union(src));
+		Type *dst_ptr = tuple->Tuple.variables[0]->type;
+		Type *dst = type_deref(dst_ptr);
+
+		ssaValue *tag = ssa_emit_load(proc, ssa_emit_union_tag_ptr(proc, value));
+		ssaValue *dst_tag = NULL;
+		for (isize i = 1; i < src->Record.field_count; i++) {
+			Entity *f = src->Record.fields[i];
+			if (are_types_identical(f->type, dst)) {
+				dst_tag = ssa_make_const_int(a, i);
+				break;
+			}
+		}
+		GB_ASSERT(dst_tag != NULL);
+
+		ssaBlock *ok_block = ssa_add_block(proc, NULL, "union_cast.ok");
+		ssaBlock *end_block = ssa_add_block(proc, NULL, "union_cast.end");
+		ssaValue *cond = ssa_emit_comp(proc, Token_CmpEq, tag, dst_tag);
+		ssa_emit_if(proc, cond, ok_block, end_block);
+		proc->curr_block = ok_block;
+
+		ssaValue *gep0 = ssa_emit_struct_ep(proc, v, 0);
+		ssaValue *gep1 = ssa_emit_struct_ep(proc, v, 1);
+
+		ssaValue *data = ssa_emit_conv(proc, value, dst_ptr);
+		ssa_emit_store(proc, gep0, data);
+		ssa_emit_store(proc, gep1, v_true);
+
+		ssa_emit_jump(proc, end_block);
+		proc->curr_block = end_block;
+
+	} else {
+		Type *src = base_type(src_type);
+		GB_ASSERT(is_type_union(src));
+		Type *dst = tuple->Tuple.variables[0]->type;
+		Type *dst_ptr = make_type_pointer(a, dst);
+
+		ssaValue *tag = ssa_emit_union_tag_value(proc, value);
+		ssaValue *dst_tag = NULL;
+		for (isize i = 1; i < src->Record.field_count; i++) {
+			Entity *f = src->Record.fields[i];
+			if (are_types_identical(f->type, dst)) {
+				dst_tag = ssa_make_const_int(a, i);
+				break;
+			}
+		}
+		GB_ASSERT(dst_tag != NULL);
+
+		// HACK(bill): This is probably not very efficient
+		ssaValue *union_copy = ssa_add_local_generated(proc, src_type);
+		ssa_emit_store(proc, union_copy, value);
+
+		ssaBlock *ok_block = ssa_add_block(proc, NULL, "union_cast.ok");
+		ssaBlock *end_block = ssa_add_block(proc, NULL, "union_cast.end");
+		ssaValue *cond = ssa_emit_comp(proc, Token_CmpEq, tag, dst_tag);
+		ssa_emit_if(proc, cond, ok_block, end_block);
+		proc->curr_block = ok_block;
+
+		ssaValue *gep0 = ssa_emit_struct_ep(proc, v, 0);
+		ssaValue *gep1 = ssa_emit_struct_ep(proc, v, 1);
+
+		ssaValue *data = ssa_emit_load(proc, ssa_emit_conv(proc, union_copy, dst_ptr));
+		ssa_emit_store(proc, gep0, data);
+		ssa_emit_store(proc, gep1, v_true);
+
+		ssa_emit_jump(proc, end_block);
+		proc->curr_block = end_block;
+
+	}
+	return ssa_emit_load(proc, v);
+}
+
+
+isize ssa_type_info_index(CheckerInfo *info, Type *type) {
+	type = default_type(type);
+
+	isize entry_index = -1;
+	HashKey key = hash_pointer(type);
+	isize *found_entry_index = map_isize_get(&info->type_info_map, key);
+	if (found_entry_index) {
+		entry_index = *found_entry_index;
+	}
+	if (entry_index < 0) {
+		// NOTE(bill): Do manual search
+		// TODO(bill): This is O(n) and can be very slow
+		for_array(i, info->type_info_map.entries){
+			MapIsizeEntry *e = &info->type_info_map.entries.e[i];
+			Type *prev_type = cast(Type *)e->key.ptr;
+			if (are_types_identical(prev_type, type)) {
+				entry_index = e->value;
+				// NOTE(bill): Add it to the search map
+				map_isize_set(&info->type_info_map, key, entry_index);
+				break;
+			}
+		}
+	}
+
+	if (entry_index < 0) {
+		compiler_error("Type_Info for `%s` could not be found", type_to_string(type));
+	}
+	return entry_index;
+}
+
+ssaValue *ssa_type_info(ssaProcedure *proc, Type *type) {
+	ssaValue **found = map_ssa_value_get(&proc->module->members, hash_string(str_lit(SSA_TYPE_INFO_DATA_NAME)));
+	GB_ASSERT(found != NULL);
+	ssaValue *type_info_data = *found;
+	CheckerInfo *info = proc->module->info;
+
+	type = default_type(type);
+
+	i32 entry_index = ssa_type_info_index(info, type);
+
+	// gb_printf_err("%d %s\n", entry_index, type_to_string(type));
+
+	return ssa_emit_array_ep(proc, type_info_data, ssa_make_const_i32(proc->module->allocator, entry_index));
+}
+
+
+
+ssaValue *ssa_emit_logical_binary_expr(ssaProcedure *proc, AstNode *expr) {
+	ast_node(be, BinaryExpr, expr);
+#if 0
+	ssaBlock *true_   = ssa_add_block(proc, NULL, "logical.cmp.true");
+	ssaBlock *false_  = ssa_add_block(proc, NULL, "logical.cmp.false");
+	ssaBlock *done  = ssa_add_block(proc, NULL, "logical.cmp.done");
+
+	ssaValue *result = ssa_add_local_generated(proc, t_bool);
+	ssa_build_cond(proc, expr, true_, false_);
+
+	proc->curr_block = true_;
+	ssa_emit_store(proc, result, v_true);
+	ssa_emit_jump(proc, done);
+
+	proc->curr_block = false_;
+	ssa_emit_store(proc, result, v_false);
+	ssa_emit_jump(proc, done);
+
+	proc->curr_block = done;
+
+	return ssa_emit_load(proc, result);
+#else
+	ssaBlock *rhs = ssa_add_block(proc, NULL, "logical.cmp.rhs");
+	ssaBlock *done = ssa_add_block(proc, NULL, "logical.cmp.done");
+
+	Type *type = type_of_expr(proc->module->info, expr);
+	type = default_type(type);
+
+	ssaValue *short_circuit = NULL;
+	if (be->op.kind == Token_CmpAnd) {
+		ssa_build_cond(proc, be->left, rhs, done);
+		short_circuit = v_false;
+	} else if (be->op.kind == Token_CmpOr) {
+		ssa_build_cond(proc, be->left, done, rhs);
+		short_circuit = v_true;
+	}
+
+	if (rhs->preds.count == 0) {
+		proc->curr_block = done;
+		return short_circuit;
+	}
+
+	if (done->preds.count == 0) {
+		proc->curr_block = rhs;
+		return ssa_build_expr(proc, be->right);
+	}
+
+	ssaValueArray edges = {0};
+	array_init_reserve(&edges, proc->module->allocator, done->preds.count+1);
+	for_array(i, done->preds) {
+		array_add(&edges, short_circuit);
+	}
+
+	proc->curr_block = rhs;
+	array_add(&edges, ssa_build_expr(proc, be->right));
+	ssa_emit_jump(proc, done);
+	proc->curr_block = done;
+
+	return ssa_emit(proc, ssa_make_instr_phi(proc, edges, type));
+#endif
+}
+
+
+void ssa_emit_bounds_check(ssaProcedure *proc, Token token, ssaValue *index, ssaValue *len) {
+	if ((proc->module->stmt_state_flags & StmtStateFlag_no_bounds_check) != 0) {
+		return;
+	}
+
+	index = ssa_emit_conv(proc, index, t_int);
+	len = ssa_emit_conv(proc, len, t_int);
+
+	ssa_emit(proc, ssa_make_instr_bounds_check(proc, token.pos, index, len));
+
+	// gbAllocator a = proc->module->allocator;
+	// ssaValue **args = gb_alloc_array(a, ssaValue *, 5);
+	// args[0] = ssa_emit_global_string(proc, token.pos.file);
+	// args[1] = ssa_make_const_int(a, token.pos.line);
+	// args[2] = ssa_make_const_int(a, token.pos.column);
+	// args[3] = ssa_emit_conv(proc, index, t_int);
+	// args[4] = ssa_emit_conv(proc, len, t_int);
+
+	// ssa_emit_global_call(proc, "__bounds_check_error", args, 5);
+}
+
+void ssa_emit_slice_bounds_check(ssaProcedure *proc, Token token, ssaValue *low, ssaValue *high, ssaValue *max, bool is_substring) {
+	if ((proc->module->stmt_state_flags & StmtStateFlag_no_bounds_check) != 0) {
+		return;
+	}
+
+
+	low  = ssa_emit_conv(proc, low,  t_int);
+	high = ssa_emit_conv(proc, high, t_int);
+	max  = ssa_emit_conv(proc, max,  t_int);
+
+	ssa_emit(proc, ssa_make_instr_slice_bounds_check(proc, token.pos, low, high, max, is_substring));
+
+	// gbAllocator a = proc->module->allocator;
+	// ssaValue **args = gb_alloc_array(a, ssaValue *, 6);
+	// args[0] = ssa_emit_global_string(proc, token.pos.file);
+	// args[1] = ssa_make_const_int(a, token.pos.line);
+	// args[2] = ssa_make_const_int(a, token.pos.column);
+	// args[3] = ssa_emit_conv(proc, low, t_int);
+	// args[4] = ssa_emit_conv(proc, high, t_int);
+	// args[5] = ssa_emit_conv(proc, max, t_int);
+
+	// if (!is_substring) {
+	// 	ssa_emit_global_call(proc, "__slice_expr_error", args, 6);
+	// } else {
+	// 	ssa_emit_global_call(proc, "__substring_expr_error", args, 5);
+	// }
+}
+
+
+////////////////////////////////////////////////////////////////
+//
+// @Build
+//
+////////////////////////////////////////////////////////////////
+
+
+void ssa_push_target_list(ssaProcedure *proc, ssaBlock *break_, ssaBlock *continue_, ssaBlock *fallthrough_) {
+	ssaTargetList *tl = gb_alloc_item(proc->module->allocator, ssaTargetList);
+	tl->prev          = proc->target_list;
+	tl->break_        = break_;
+	tl->continue_     = continue_;
+	tl->fallthrough_  = fallthrough_;
+	proc->target_list = tl;
+}
+
+void ssa_pop_target_list(ssaProcedure *proc) {
+	proc->target_list = proc->target_list->prev;
+}
+
+
+void ssa_mangle_sub_type_name(ssaModule *m, Entity *field, String parent) {
+	if (field->kind != Entity_TypeName) {
+		return;
+	}
+	String cn = field->token.string;
+
+	isize len = parent.len + 1 + cn.len;
+	String child = {NULL, len};
+	child.text = gb_alloc_array(m->allocator, u8, len);
+
+	isize i = 0;
+	gb_memmove(child.text+i, parent.text, parent.len);
+	i += parent.len;
+	child.text[i++] = '.';
+	gb_memmove(child.text+i, cn.text, cn.len);
+
+	map_string_set(&m->type_names, hash_pointer(field->type), child);
+	ssa_gen_global_type_name(m, field, child);
+}
+
+void ssa_gen_global_type_name(ssaModule *m, Entity *e, String name) {
+	ssaValue *t = ssa_make_value_type_name(m->allocator, name, e->type);
+	ssa_module_add_value(m, e, t);
+	map_ssa_value_set(&m->members, hash_string(name), t);
+
+	Type *bt = base_type(e->type);
+	if (bt->kind == Type_Record) {
+		TypeRecord *s = &bt->Record;
+		for (isize j = 0; j < s->other_field_count; j++) {
+			ssa_mangle_sub_type_name(m, s->other_fields[j], name);
+		}
+	}
+
+	if (is_type_union(bt)) {
+		TypeRecord *s = &bt->Record;
+		// NOTE(bill): Zeroth entry is null (for `match type` stmts)
+		for (isize j = 1; j < s->field_count; j++) {
+			ssa_mangle_sub_type_name(m, s->fields[j], name);
+		}
+	}
+}
+
+
+
+
+void ssa_build_defer_stmt(ssaProcedure *proc, ssaDefer d) {
+	ssaBlock *b = ssa_add_block(proc, NULL, "defer");
+	// NOTE(bill): The prev block may defer injection before it's terminator
+	ssaInstr *last_instr = ssa_get_last_instr(proc->curr_block);
+	if (last_instr == NULL || !ssa_is_instr_terminating(last_instr)) {
+		ssa_emit_jump(proc, b);
+	}
+	proc->curr_block = b;
+	ssa_emit_comment(proc, str_lit("defer"));
+	if (d.kind == ssaDefer_Node) {
+		ssa_build_stmt(proc, d.stmt);
+	} else if (d.kind == ssaDefer_Instr) {
+		// NOTE(bill): Need to make a new copy
+		ssaValue *instr = cast(ssaValue *)gb_alloc_copy(proc->module->allocator, d.instr, gb_size_of(ssaValue));
+		ssa_emit(proc, instr);
+	}
+}
+
+
+
+ssaValue *ssa_find_global_variable(ssaProcedure *proc, String name) {
+	ssaValue **value = map_ssa_value_get(&proc->module->members, hash_string(name));
+	GB_ASSERT_MSG(value != NULL, "Unable to find global variable `%.*s`", LIT(name));
+	return *value;
+}
+
+ssaValue *ssa_find_implicit_value_backing(ssaProcedure *proc, ImplicitValueId id) {
+	Entity *e = proc->module->info->implicit_values[id];
+	GB_ASSERT(e->kind == Entity_ImplicitValue);
+	Entity *backing = e->ImplicitValue.backing;
+	ssaValue **value = map_ssa_value_get(&proc->module->values, hash_pointer(backing));
+	GB_ASSERT_MSG(value != NULL, "Unable to find implicit value backing `%.*s`", LIT(backing->token.string));
+	return *value;
+}
+
+
+
+ssaValue *ssa_build_single_expr(ssaProcedure *proc, AstNode *expr, TypeAndValue *tv) {
+	expr = unparen_expr(expr);
+	switch (expr->kind) {
+	case_ast_node(bl, BasicLit, expr);
+		GB_PANIC("Non-constant basic literal");
+	case_end;
+
+	case_ast_node(i, Ident, expr);
+		Entity *e = *map_entity_get(&proc->module->info->uses, hash_pointer(expr));
+		if (e->kind == Entity_Builtin) {
+			Token token = ast_node_token(expr);
+			GB_PANIC("TODO(bill): ssa_build_single_expr Entity_Builtin `%.*s`\n"
+			         "\t at %.*s(%td:%td)", LIT(builtin_procs[e->Builtin.id].name),
+			         LIT(token.pos.file), token.pos.line, token.pos.column);
+			return NULL;
+		} else if (e->kind == Entity_Nil) {
+			return ssa_make_value_nil(proc->module->allocator, tv->type);
+		} else if (e->kind == Entity_ImplicitValue) {
+			return ssa_emit_load(proc, ssa_find_implicit_value_backing(proc, e->ImplicitValue.id));
+		}
+
+		ssaValue **found = map_ssa_value_get(&proc->module->values, hash_pointer(e));
+		if (found) {
+			ssaValue *v = *found;
+			if (v->kind == ssaValue_Proc) {
+				return v;
+			}
+			// if (e->kind == Entity_Variable && e->Variable.param) {
+				// return v;
+			// }
+			return ssa_emit_load(proc, v);
+		}
+		return NULL;
+	case_end;
+
+	case_ast_node(re, RunExpr, expr);
+		// TODO(bill): Run Expression
+		return ssa_build_single_expr(proc, re->expr, tv);
+	case_end;
+
+	case_ast_node(de, DerefExpr, expr);
+		return ssa_addr_load(proc, ssa_build_addr(proc, expr));
+	case_end;
+
+	case_ast_node(se, SelectorExpr, expr);
+		TypeAndValue *tav = map_tav_get(&proc->module->info->types, hash_pointer(expr));
+		GB_ASSERT(tav != NULL);
+		return ssa_addr_load(proc, ssa_build_addr(proc, expr));
+	case_end;
+
+	case_ast_node(ue, UnaryExpr, expr);
+		switch (ue->op.kind) {
+		case Token_Pointer:
+			return ssa_emit_ptr_offset(proc, ssa_build_addr(proc, ue->expr).addr, v_zero); // Make a copy of the pointer
+
+		case Token_Maybe:
+			return ssa_emit_conv(proc, ssa_build_expr(proc, ue->expr), type_of_expr(proc->module->info, expr));
+
+		case Token_Add:
+			return ssa_build_expr(proc, ue->expr);
+
+		case Token_Sub: // NOTE(bill): -`x` == 0 - `x`
+			return ssa_emit_arith(proc, ue->op.kind, v_zero, ssa_build_expr(proc, ue->expr), tv->type);
+
+		case Token_Not:   // Boolean not
+		case Token_Xor: { // Bitwise not
+			// NOTE(bill): "not" `x` == `x` "xor" `-1`
+			ssaValue *left = ssa_build_expr(proc, ue->expr);
+			ssaValue *right = ssa_add_module_constant(proc->module, tv->type, make_exact_value_integer(-1));
+			return ssa_emit_arith(proc, ue->op.kind,
+			                      left, right,
+			                      tv->type);
+		} break;
+		}
+	case_end;
+
+	case_ast_node(be, BinaryExpr, expr);
+		ssaValue *left = ssa_build_expr(proc, be->left);
+		Type *type = default_type(tv->type);
+
+		switch (be->op.kind) {
+		case Token_Add:
+		case Token_Sub:
+		case Token_Mul:
+		case Token_Quo:
+		case Token_Mod:
+		case Token_And:
+		case Token_Or:
+		case Token_Xor:
+		case Token_AndNot:
+		case Token_Shl:
+		case Token_Shr: {
+			ssaValue *right = ssa_build_expr(proc, be->right);
+			return ssa_emit_arith(proc, be->op.kind, left, right, type);
+		}
+
+
+		case Token_CmpEq:
+		case Token_NotEq:
+		case Token_Lt:
+		case Token_LtEq:
+		case Token_Gt:
+		case Token_GtEq: {
+			ssaValue *right = ssa_build_expr(proc, be->right);
+			ssaValue *cmp = ssa_emit_comp(proc, be->op.kind, left, right);
+			return ssa_emit_conv(proc, cmp, type);
+		} break;
+
+		case Token_CmpAnd:
+		case Token_CmpOr:
+			return ssa_emit_logical_binary_expr(proc, expr);
+
+		case Token_as:
+			ssa_emit_comment(proc, str_lit("cast - as"));
+			return ssa_emit_conv(proc, left, type);
+
+		case Token_transmute:
+			ssa_emit_comment(proc, str_lit("cast - transmute"));
+			return ssa_emit_transmute(proc, left, type);
+
+		case Token_down_cast:
+			ssa_emit_comment(proc, str_lit("cast - down_cast"));
+			return ssa_emit_down_cast(proc, left, type);
+
+		case Token_union_cast:
+			ssa_emit_comment(proc, str_lit("cast - union_cast"));
+			return ssa_emit_union_cast(proc, left, type);
+
+		default:
+			GB_PANIC("Invalid binary expression");
+			break;
+		}
+	case_end;
+
+	case_ast_node(pl, ProcLit, expr);
+		// NOTE(bill): Generate a new name
+		// parent$count
+		isize name_len = proc->name.len + 1 + 8 + 1;
+		u8 *name_text = gb_alloc_array(proc->module->allocator, u8, name_len);
+		name_len = gb_snprintf(cast(char *)name_text, name_len, "%.*s$%d", LIT(proc->name), cast(i32)proc->children.count);
+		String name = make_string(name_text, name_len-1);
+
+		Type *type = type_of_expr(proc->module->info, expr);
+		ssaValue *value = ssa_make_value_procedure(proc->module->allocator,
+		                                           proc->module, NULL, type, pl->type, pl->body, name);
+
+		value->Proc.tags = pl->tags;
+
+		array_add(&proc->children, &value->Proc);
+		ssa_build_proc(value, proc);
+
+		return value;
+	case_end;
+
+
+	case_ast_node(cl, CompoundLit, expr);
+		return ssa_emit_load(proc, ssa_build_addr(proc, expr).addr);
+	case_end;
+
+
+	case_ast_node(ce, CallExpr, expr);
+		AstNode *p = unparen_expr(ce->proc);
+		if (p->kind == AstNode_Ident) {
+			Entity **found = map_entity_get(&proc->module->info->uses, hash_pointer(p));
+			if (found && (*found)->kind == Entity_Builtin) {
+				Entity *e = *found;
+				switch (e->Builtin.id) {
+				case BuiltinProc_type_info: {
+					Type *t = default_type(type_of_expr(proc->module->info, ce->args.e[0]));
+					return ssa_type_info(proc, t);
+				} break;
+				case BuiltinProc_type_info_of_val: {
+					Type *t = default_type(type_of_expr(proc->module->info, ce->args.e[0]));
+					return ssa_type_info(proc, t);
+				} break;
+
+				case BuiltinProc_new: {
+					ssa_emit_comment(proc, str_lit("new"));
+					// new :: proc(Type) -> ^Type
+					gbAllocator allocator = proc->module->allocator;
+
+					Type *type = type_of_expr(proc->module->info, ce->args.e[0]);
+					Type *ptr_type = make_type_pointer(allocator, type);
+
+					i64 s = type_size_of(proc->module->sizes, allocator, type);
+					i64 a = type_align_of(proc->module->sizes, allocator, type);
+
+					ssaValue **args = gb_alloc_array(allocator, ssaValue *, 2);
+					args[0] = ssa_make_const_int(allocator, s);
+					args[1] = ssa_make_const_int(allocator, a);
+					ssaValue *call = ssa_emit_global_call(proc, "alloc_align", args, 2);
+					ssaValue *v = ssa_emit_conv(proc, call, ptr_type);
+					return v;
+				} break;
+
+				case BuiltinProc_new_slice: {
+					ssa_emit_comment(proc, str_lit("new_slice"));
+					// new_slice :: proc(Type, len: int[, cap: int]) -> ^Type
+					gbAllocator allocator = proc->module->allocator;
+
+					Type *type = type_of_expr(proc->module->info, ce->args.e[0]);
+					Type *ptr_type = make_type_pointer(allocator, type);
+					Type *slice_type = make_type_slice(allocator, type);
+
+					i64 s = type_size_of(proc->module->sizes, allocator, type);
+					i64 a = type_align_of(proc->module->sizes, allocator, type);
+
+					ssaValue *elem_size  = ssa_make_const_int(allocator, s);
+					ssaValue *elem_align = ssa_make_const_int(allocator, a);
+
+					ssaValue *len = ssa_emit_conv(proc, ssa_build_expr(proc, ce->args.e[1]), t_int);
+					ssaValue *cap = len;
+					if (ce->args.count == 3) {
+						cap = ssa_emit_conv(proc, ssa_build_expr(proc, ce->args.e[2]), t_int);
+					}
+
+					ssa_emit_slice_bounds_check(proc, ast_node_token(ce->args.e[1]), v_zero, len, cap, false);
+
+					ssaValue *slice_size = ssa_emit_arith(proc, Token_Mul, elem_size, cap, t_int);
+
+					ssaValue **args = gb_alloc_array(allocator, ssaValue *, 2);
+					args[0] = slice_size;
+					args[1] = elem_align;
+					ssaValue *call = ssa_emit_global_call(proc, "alloc_align", args, 2);
+
+					ssaValue *ptr = ssa_emit_conv(proc, call, ptr_type);
+					ssaValue *slice = ssa_add_local_generated(proc, slice_type);
+
+					ssaValue *gep0 = ssa_emit_struct_ep(proc, slice, 0);
+					ssaValue *gep1 = ssa_emit_struct_ep(proc, slice, 1);
+					ssaValue *gep2 = ssa_emit_struct_ep(proc, slice, 2);
+					ssa_emit_store(proc, gep0, ptr);
+					ssa_emit_store(proc, gep1, len);
+					ssa_emit_store(proc, gep2, cap);
+					return ssa_emit_load(proc, slice);
+				} break;
+
+				case BuiltinProc_assert: {
+					ssa_emit_comment(proc, str_lit("assert"));
+					ssaValue *cond = ssa_build_expr(proc, ce->args.e[0]);
+					GB_ASSERT(is_type_boolean(ssa_type(cond)));
+
+					cond = ssa_emit_comp(proc, Token_CmpEq, cond, v_false);
+					ssaBlock *err  = ssa_add_block(proc, NULL, "builtin.assert.err");
+					ssaBlock *done = ssa_add_block(proc, NULL, "builtin.assert.done");
+
+					ssa_emit_if(proc, cond, err, done);
+					proc->curr_block = err;
+
+					// TODO(bill): Cleanup allocations here
+					Token token = ast_node_token(ce->args.e[0]);
+					TokenPos pos = token.pos;
+					gbString expr = expr_to_string(ce->args.e[0]);
+					isize expr_len = gb_string_length(expr);
+					String expr_str = {0};
+					expr_str.text = cast(u8 *)gb_alloc_copy_align(proc->module->allocator, expr, expr_len, 1);
+					expr_str.len = expr_len;
+					gb_string_free(expr);
+
+
+					ssaValue **args = gb_alloc_array(proc->module->allocator, ssaValue *, 4);
+					args[0] = ssa_make_const_string(proc->module->allocator, pos.file);
+					args[1] = ssa_make_const_int(proc->module->allocator, pos.line);
+					args[2] = ssa_make_const_int(proc->module->allocator, pos.column);
+					args[3] = ssa_make_const_string(proc->module->allocator, expr_str);
+					ssa_emit_global_call(proc, "__assert", args, 4);
+
+					ssa_emit_jump(proc, done);
+					proc->curr_block = done;
+
+					return NULL;
+				} break;
+
+				case BuiltinProc_panic: {
+					ssa_emit_comment(proc, str_lit("panic"));
+					ssaValue *msg = ssa_build_expr(proc, ce->args.e[0]);
+					GB_ASSERT(is_type_string(ssa_type(msg)));
+
+					Token token = ast_node_token(ce->args.e[0]);
+					TokenPos pos = token.pos;
+
+					ssaValue **args = gb_alloc_array(proc->module->allocator, ssaValue *, 4);
+					args[0] = ssa_make_const_string(proc->module->allocator, pos.file);
+					args[1] = ssa_make_const_int(proc->module->allocator, pos.line);
+					args[2] = ssa_make_const_int(proc->module->allocator, pos.column);
+					args[3] = msg;
+					ssa_emit_global_call(proc, "__assert", args, 4);
+
+					return NULL;
+				} break;
+
+
+				case BuiltinProc_copy: {
+					ssa_emit_comment(proc, str_lit("copy"));
+					// copy :: proc(dst, src: []Type) -> int
+					AstNode *dst_node = ce->args.e[0];
+					AstNode *src_node = ce->args.e[1];
+					ssaValue *dst_slice = ssa_build_expr(proc, dst_node);
+					ssaValue *src_slice = ssa_build_expr(proc, src_node);
+					Type *slice_type = base_type(ssa_type(dst_slice));
+					GB_ASSERT(slice_type->kind == Type_Slice);
+					Type *elem_type = slice_type->Slice.elem;
+					i64 size_of_elem = type_size_of(proc->module->sizes, proc->module->allocator, elem_type);
+
+
+					ssaValue *dst = ssa_emit_conv(proc, ssa_slice_elem(proc, dst_slice), t_rawptr);
+					ssaValue *src = ssa_emit_conv(proc, ssa_slice_elem(proc, src_slice), t_rawptr);
+
+					ssaValue *len_dst = ssa_slice_len(proc, dst_slice);
+					ssaValue *len_src = ssa_slice_len(proc, src_slice);
+
+					ssaValue *cond = ssa_emit_comp(proc, Token_Lt, len_dst, len_src);
+					ssaValue *len = ssa_emit_select(proc, cond, len_dst, len_src);
+
+					ssaValue *elem_size = ssa_make_const_int(proc->module->allocator, size_of_elem);
+					ssaValue *byte_count = ssa_emit_arith(proc, Token_Mul, len, elem_size, t_int);
+
+					ssaValue **args = gb_alloc_array(proc->module->allocator, ssaValue *, 3);
+					args[0] = dst;
+					args[1] = src;
+					args[2] = byte_count;
+
+					ssa_emit_global_call(proc, "__mem_copy", args, 3);
+
+					return len;
+				} break;
+				case BuiltinProc_append: {
+					ssa_emit_comment(proc, str_lit("append"));
+					// append :: proc(s: ^[]Type, item: Type) -> bool
+					AstNode *sptr_node = ce->args.e[0];
+					AstNode *item_node = ce->args.e[1];
+					ssaValue *slice_ptr = ssa_build_expr(proc, sptr_node);
+					ssaValue *slice = ssa_emit_load(proc, slice_ptr);
+
+					ssaValue *elem = ssa_slice_elem(proc, slice);
+					ssaValue *len  = ssa_slice_len(proc,  slice);
+					ssaValue *cap  = ssa_slice_cap(proc,  slice);
+
+					Type *elem_type = type_deref(ssa_type(elem));
+
+					ssaValue *item_value = ssa_build_expr(proc, item_node);
+					item_value = ssa_emit_conv(proc, item_value, elem_type);
+
+					ssaValue *item = ssa_add_local_generated(proc, elem_type);
+					ssa_emit_store(proc, item, item_value);
+
+
+					// NOTE(bill): Check if can append is possible
+					ssaValue *cond = ssa_emit_comp(proc, Token_Lt, len, cap);
+					ssaBlock *able = ssa_add_block(proc, NULL, "builtin.append.able");
+					ssaBlock *done = ssa_add_block(proc, NULL, "builtin.append.done");
+
+					ssa_emit_if(proc, cond, able, done);
+					proc->curr_block = able;
+
+					// Add new slice item
+					i64 item_size = type_size_of(proc->module->sizes, proc->module->allocator, elem_type);
+					ssaValue *byte_count = ssa_make_const_int(proc->module->allocator, item_size);
+
+					ssaValue *offset = ssa_emit_ptr_offset(proc, elem, len);
+					offset = ssa_emit_conv(proc, offset, t_rawptr);
+
+					item = ssa_emit_ptr_offset(proc, item, v_zero);
+					item = ssa_emit_conv(proc, item, t_rawptr);
+
+					ssaValue **args = gb_alloc_array(proc->module->allocator, ssaValue *, 3);
+					args[0] = offset;
+					args[1] = item;
+					args[2] = byte_count;
+
+					ssa_emit_global_call(proc, "__mem_copy", args, 3);
+
+					// Increment slice length
+					ssaValue *new_len = ssa_emit_arith(proc, Token_Add, len, v_one, t_int);
+					ssaValue *gep = ssa_emit_struct_ep(proc, slice_ptr, 1);
+					ssa_emit_store(proc, gep, new_len);
+
+					ssa_emit_jump(proc, done);
+					proc->curr_block = done;
+
+					return ssa_emit_conv(proc, cond, t_bool);
+				} break;
+
+				case BuiltinProc_swizzle: {
+					ssa_emit_comment(proc, str_lit("swizzle"));
+					ssaValue *vector = ssa_build_expr(proc, ce->args.e[0]);
+					isize index_count = ce->args.count-1;
+					if (index_count == 0) {
+						return vector;
+					}
+
+					i32 *indices = gb_alloc_array(proc->module->allocator, i32, index_count);
+					isize index = 0;
+					for_array(i, ce->args) {
+						if (i == 0) continue;
+						TypeAndValue *tv = type_and_value_of_expression(proc->module->info, ce->args.e[i]);
+						GB_ASSERT(is_type_integer(tv->type));
+						GB_ASSERT(tv->value.kind == ExactValue_Integer);
+						indices[index++] = cast(i32)tv->value.value_integer;
+					}
+
+					return ssa_emit(proc, ssa_make_instr_vector_shuffle(proc, vector, indices, index_count));
+
+				} break;
+
+#if 0
+				case BuiltinProc_ptr_offset: {
+					ssa_emit_comment(proc, str_lit("ptr_offset"));
+					ssaValue *ptr = ssa_build_expr(proc, ce->args.e[0]);
+					ssaValue *offset = ssa_build_expr(proc, ce->args.e[1]);
+					return ssa_emit_ptr_offset(proc, ptr, offset);
+				} break;
+
+				case BuiltinProc_ptr_sub: {
+					ssa_emit_comment(proc, str_lit("ptr_sub"));
+					ssaValue *ptr_a = ssa_build_expr(proc, ce->args.e[0]);
+					ssaValue *ptr_b = ssa_build_expr(proc, ce->args.e[1]);
+					Type *ptr_type = base_type(ssa_type(ptr_a));
+					GB_ASSERT(ptr_type->kind == Type_Pointer);
+					isize elem_size = type_size_of(proc->module->sizes, proc->module->allocator, ptr_type->Pointer.elem);
+
+					ssaValue *v = ssa_emit_arith(proc, Token_Sub, ptr_a, ptr_b, t_int);
+					if (elem_size > 1) {
+						ssaValue *ez = ssa_make_const_int(proc->module->allocator, elem_size);
+						v = ssa_emit_arith(proc, Token_Quo, v, ez, t_int);
+					}
+
+					return v;
+				} break;
+#endif
+
+				case BuiltinProc_slice_ptr: {
+					ssa_emit_comment(proc, str_lit("slice_ptr"));
+					ssaValue *ptr = ssa_build_expr(proc, ce->args.e[0]);
+					ssaValue *len = ssa_build_expr(proc, ce->args.e[1]);
+					ssaValue *cap = len;
+
+					len = ssa_emit_conv(proc, len, t_int);
+
+					if (ce->args.count == 3) {
+						cap = ssa_build_expr(proc, ce->args.e[2]);
+						cap = ssa_emit_conv(proc, cap, t_int);
+					}
+
+
+					Type *slice_type = make_type_slice(proc->module->allocator, type_deref(ssa_type(ptr)));
+					ssaValue *slice = ssa_add_local_generated(proc, slice_type);
+					ssa_emit_store(proc, ssa_emit_struct_ep(proc, slice, 0), ptr);
+					ssa_emit_store(proc, ssa_emit_struct_ep(proc, slice, 1), len);
+					ssa_emit_store(proc, ssa_emit_struct_ep(proc, slice, 2), cap);
+					return ssa_emit_load(proc, slice);
+				} break;
+
+				case BuiltinProc_min: {
+					ssa_emit_comment(proc, str_lit("min"));
+					ssaValue *x = ssa_build_expr(proc, ce->args.e[0]);
+					ssaValue *y = ssa_build_expr(proc, ce->args.e[1]);
+					Type *t = base_type(ssa_type(x));
+					ssaValue *cond = ssa_emit_comp(proc, Token_Lt, x, y);
+					return ssa_emit_select(proc, cond, x, y);
+				} break;
+
+				case BuiltinProc_max: {
+					ssa_emit_comment(proc, str_lit("max"));
+					ssaValue *x = ssa_build_expr(proc, ce->args.e[0]);
+					ssaValue *y = ssa_build_expr(proc, ce->args.e[1]);
+					Type *t = base_type(ssa_type(x));
+					ssaValue *cond = ssa_emit_comp(proc, Token_Gt, x, y);
+					return ssa_emit_select(proc, cond, x, y);
+				} break;
+
+				case BuiltinProc_abs: {
+					ssa_emit_comment(proc, str_lit("abs"));
+					gbAllocator a = proc->module->allocator;
+
+					ssaValue *x = ssa_build_expr(proc, ce->args.e[0]);
+					Type *original_type = ssa_type(x);
+					Type *t = original_type;
+					i64 sz = type_size_of(proc->module->sizes, a, t);
+					GB_ASSERT(is_type_integer(t) || is_type_float(t));
+					if (is_type_float(t)) {
+						if (sz == 4) {
+							t = t_i32;
+						} else if (sz == 8) {
+							t = t_i64;
+						} else {
+							GB_PANIC("unknown float type for `abs`");
+						}
+
+						x = ssa_emit_bitcast(proc, x, t);
+					}
+
+					/*
+						NOTE(bill): See Hacker's Delight, section 2-4.
+						m := x >> (int_size-1)
+						b := x ^ m
+						return b - m
+					*/
+
+					ssaValue *m = ssa_emit_arith(proc, Token_Shr,
+					                             x,
+					                             ssa_make_value_constant(a, t, make_exact_value_integer(sz-1)),
+					                             t);
+					ssaValue *b = ssa_emit_arith(proc, Token_Xor, x, m, t);
+					ssaValue *v = ssa_emit_arith(proc, Token_Sub, b, m, t);
+
+					if (is_type_float(t)) {
+						v = ssa_emit_bitcast(proc, v, original_type);
+					}
+					return v;
+				} break;
+
+				case BuiltinProc_enum_to_string: {
+					ssa_emit_comment(proc, str_lit("enum_to_string"));
+					ssaValue *x = ssa_build_expr(proc, ce->args.e[0]);
+					Type *t = ssa_type(x);
+					ssaValue *ti = ssa_type_info(proc, t);
+
+
+					ssaValue **args = gb_alloc_array(proc->module->allocator, ssaValue *, 2);
+					args[0] = ti;
+					args[1] = ssa_emit_conv(proc, x, t_i64);
+					return ssa_emit_global_call(proc, "__enum_to_string", args, 2);
+				} break;
+				}
+			}
+		}
+
+
+		// NOTE(bill): Regular call
+		ssaValue *value = ssa_build_expr(proc, ce->proc);
+		Type *proc_type_ = base_type(ssa_type(value));
+		GB_ASSERT(proc_type_->kind == Type_Proc);
+		TypeProc *type = &proc_type_->Proc;
+
+		isize arg_index = 0;
+
+		isize arg_count = 0;
+		for_array(i, ce->args) {
+			AstNode *a = ce->args.e[i];
+			Type *at = base_type(type_of_expr(proc->module->info, a));
+			if (at->kind == Type_Tuple) {
+				arg_count += at->Tuple.variable_count;
+			} else {
+				arg_count++;
+			}
+		}
+		ssaValue **args = gb_alloc_array(proc->module->allocator, ssaValue *, arg_count);
+		bool variadic = proc_type_->Proc.variadic;
+		bool vari_expand = ce->ellipsis.pos.line != 0;
+
+		for_array(i, ce->args) {
+			ssaValue *a = ssa_build_expr(proc, ce->args.e[i]);
+			Type *at = ssa_type(a);
+			if (at->kind == Type_Tuple) {
+				for (isize i = 0; i < at->Tuple.variable_count; i++) {
+					Entity *e = at->Tuple.variables[i];
+					ssaValue *v = ssa_emit_struct_ev(proc, a, i);
+					args[arg_index++] = v;
+				}
+			} else {
+				args[arg_index++] = a;
+			}
+		}
+
+		TypeTuple *pt = &type->params->Tuple;
+
+		if (variadic) {
+			isize i = 0;
+			for (; i < type->param_count-1; i++) {
+				args[i] = ssa_emit_conv(proc, args[i], pt->variables[i]->type);
+			}
+			if (!vari_expand) {
+				Type *variadic_type = pt->variables[i]->type;
+				GB_ASSERT(is_type_slice(variadic_type));
+				variadic_type = base_type(variadic_type)->Slice.elem;
+				for (; i < arg_count; i++) {
+					args[i] = ssa_emit_conv(proc, args[i], variadic_type);
+				}
+			}
+		} else {
+			for (isize i = 0; i < arg_count; i++) {
+				args[i] = ssa_emit_conv(proc, args[i], pt->variables[i]->type);
+			}
+		}
+
+		if (variadic && !vari_expand) {
+			ssa_emit_comment(proc, str_lit("variadic call argument generation"));
+			gbAllocator allocator = proc->module->allocator;
+			Type *slice_type = pt->variables[type->param_count-1]->type;
+			Type *elem_type  = base_type(slice_type)->Slice.elem;
+			ssaValue *slice = ssa_add_local_generated(proc, slice_type);
+			isize slice_len = arg_count+1 - type->param_count;
+
+			if (slice_len > 0) {
+				ssaValue *base_array = ssa_add_local_generated(proc, make_type_array(allocator, elem_type, slice_len));
+
+				for (isize i = type->param_count-1, j = 0; i < arg_count; i++, j++) {
+					ssaValue *addr = ssa_emit_array_epi(proc, base_array, j);
+					ssa_emit_store(proc, addr, args[i]);
+				}
+
+				ssaValue *base_elem  = ssa_emit_array_epi(proc, base_array, 0);
+				ssaValue *slice_elem = ssa_emit_struct_ep(proc, slice,      0);
+				ssa_emit_store(proc, slice_elem, base_elem);
+				ssaValue *len = ssa_make_const_int(allocator, slice_len);
+				ssa_emit_store(proc, ssa_emit_struct_ep(proc, slice, 1), len);
+				ssa_emit_store(proc, ssa_emit_struct_ep(proc, slice, 2), len);
+			}
+
+			if (args[0]->kind == ssaValue_Constant) {
+				ssaValueConstant *c = &args[0]->Constant;
+				gb_printf_err("%s %d\n", type_to_string(c->type), c->value.kind);
+			}
+
+			arg_count = type->param_count;
+			args[arg_count-1] = ssa_emit_load(proc, slice);
+		}
+
+		return ssa_emit_call(proc, value, args, arg_count);
+	case_end;
+
+	case_ast_node(de, DemaybeExpr, expr);
+		return ssa_emit_load(proc, ssa_build_addr(proc, expr).addr);
+	case_end;
+
+	case_ast_node(se, SliceExpr, expr);
+		return ssa_emit_load(proc, ssa_build_addr(proc, expr).addr);
+	case_end;
+
+	case_ast_node(ie, IndexExpr, expr);
+		return ssa_emit_load(proc, ssa_build_addr(proc, expr).addr);
+	case_end;
+	}
+
+	GB_PANIC("Unexpected expression: %.*s", LIT(ast_node_strings[expr->kind]));
+	return NULL;
+}
+
+
+ssaValue *ssa_build_expr(ssaProcedure *proc, AstNode *expr) {
+	expr = unparen_expr(expr);
+
+	TypeAndValue *tv = map_tav_get(&proc->module->info->types, hash_pointer(expr));
+	GB_ASSERT_NOT_NULL(tv);
+
+	if (tv->value.kind != ExactValue_Invalid) {
+		return ssa_add_module_constant(proc->module, tv->type, tv->value);
+	}
+
+	ssaValue *value = NULL;
+	if (tv->mode == Addressing_Variable) {
+		value = ssa_addr_load(proc, ssa_build_addr(proc, expr));
+	} else {
+		value = ssa_build_single_expr(proc, expr, tv);
+	}
+
+	return value;
+}
+
+ssaValue *ssa_add_using_variable(ssaProcedure *proc, Entity *e) {
+	GB_ASSERT(e->kind == Entity_Variable && e->flags & EntityFlag_Anonymous);
+	String name = e->token.string;
+	Entity *parent = e->using_parent;
+	Selection sel = lookup_field(proc->module->allocator, parent->type, name, false);
+	GB_ASSERT(sel.entity != NULL);
+	ssaValue **pv = map_ssa_value_get(&proc->module->values, hash_pointer(parent));
+	ssaValue *v = NULL;
+	if (pv != NULL) {
+		v = *pv;
+	} else {
+		v = ssa_build_addr(proc, e->using_expr).addr;
+	}
+	GB_ASSERT(v != NULL);
+	ssaValue *var = ssa_emit_deep_field_gep(proc, parent->type, v, sel);
+	map_ssa_value_set(&proc->module->values, hash_pointer(e), var);
+	return var;
+}
+
+bool ssa_is_elem_const(ssaModule *m, AstNode *elem, Type *elem_type) {
+	if (base_type(elem_type) == t_any) {
+		return false;
+	}
+	if (elem->kind == AstNode_FieldValue) {
+		elem = elem->FieldValue.value;
+	}
+	TypeAndValue *tav = type_and_value_of_expression(m->info, elem);
+	GB_ASSERT(tav != NULL);
+	return tav->value.kind != ExactValue_Invalid;
+}
+
+ssaAddr ssa_build_addr(ssaProcedure *proc, AstNode *expr) {
+	switch (expr->kind) {
+	case_ast_node(i, Ident, expr);
+		if (ssa_is_blank_ident(expr)) {
+			ssaAddr val = {0};
+			return val;
+		}
+
+		Entity *e = entity_of_ident(proc->module->info, expr);
+		TypeAndValue *tv = map_tav_get(&proc->module->info->types, hash_pointer(expr));
+
+		GB_ASSERT(e->kind != Entity_Constant);
+
+		ssaValue *v = NULL;
+		ssaValue **found = map_ssa_value_get(&proc->module->values, hash_pointer(e));
+		if (found) {
+			v = *found;
+		} else if (e->kind == Entity_Variable && e->flags & EntityFlag_Anonymous) {
+			v = ssa_add_using_variable(proc, e);
+		} else if (e->kind == Entity_ImplicitValue) {
+			// TODO(bill): Should a copy be made?
+			v = ssa_find_implicit_value_backing(proc, e->ImplicitValue.id);
+		}
+
+		if (v == NULL) {
+			GB_PANIC("Unknown value: %s, entity: %p %.*s\n", expr_to_string(expr), e, LIT(entity_strings[e->kind]));
+		}
+
+		return ssa_make_addr(v, expr);
+	case_end;
+
+	case_ast_node(pe, ParenExpr, expr);
+		return ssa_build_addr(proc, unparen_expr(expr));
+	case_end;
+
+	case_ast_node(se, SelectorExpr, expr);
+		ssa_emit_comment(proc, str_lit("SelectorExpr"));
+		String selector = unparen_expr(se->selector)->Ident.string;
+		Type *type = base_type(type_of_expr(proc->module->info, se->expr));
+
+		if (type == t_invalid) {
+			// NOTE(bill): Imports
+			Entity *imp = entity_of_ident(proc->module->info, se->expr);
+			if (imp != NULL) {
+				GB_ASSERT(imp->kind == Entity_ImportName);
+			}
+			return ssa_build_addr(proc, unparen_expr(se->selector));
+		} else {
+			Selection sel = lookup_field(proc->module->allocator, type, selector, false);
+			GB_ASSERT(sel.entity != NULL);
+
+			ssaValue *a = ssa_build_addr(proc, se->expr).addr;
+			a = ssa_emit_deep_field_gep(proc, type, a, sel);
+			return ssa_make_addr(a, expr);
+		}
+	case_end;
+
+	case_ast_node(ue, UnaryExpr, expr);
+		switch (ue->op.kind) {
+		case Token_Pointer: {
+			return ssa_build_addr(proc, ue->expr);
+		}
+		default:
+			GB_PANIC("Invalid unary expression for ssa_build_addr");
+		}
+	case_end;
+
+	case_ast_node(be, BinaryExpr, expr);
+		switch (be->op.kind) {
+		case Token_as: {
+			ssa_emit_comment(proc, str_lit("Cast - as"));
+			// NOTE(bill): Needed for dereference of pointer conversion
+			Type *type = type_of_expr(proc->module->info, expr);
+			ssaValue *v = ssa_add_local_generated(proc, type);
+			ssa_emit_store(proc, v, ssa_emit_conv(proc, ssa_build_expr(proc, be->left), type));
+			return ssa_make_addr(v, expr);
+		}
+		case Token_transmute: {
+			ssa_emit_comment(proc, str_lit("Cast - transmute"));
+			// NOTE(bill): Needed for dereference of pointer conversion
+			Type *type = type_of_expr(proc->module->info, expr);
+			ssaValue *v = ssa_add_local_generated(proc, type);
+			ssa_emit_store(proc, v, ssa_emit_transmute(proc, ssa_build_expr(proc, be->left), type));
+			return ssa_make_addr(v, expr);
+		}
+		default:
+			GB_PANIC("Invalid binary expression for ssa_build_addr: %.*s\n", LIT(be->op.string));
+			break;
+		}
+	case_end;
+
+	case_ast_node(ie, IndexExpr, expr);
+		ssa_emit_comment(proc, str_lit("IndexExpr"));
+		Type *t = base_type(type_of_expr(proc->module->info, ie->expr));
+		gbAllocator a = proc->module->allocator;
+
+
+		bool deref = is_type_pointer(t);
+		t = type_deref(t);
+
+		ssaValue *using_addr = NULL;
+		if (!is_type_indexable(t)) {
+			// Using index expression
+			Entity *using_field = find_using_index_expr(t);
+			if (using_field != NULL) {
+				Selection sel = lookup_field(a, t, using_field->token.string, false);
+				ssaValue *e = ssa_build_addr(proc, ie->expr).addr;
+				using_addr = ssa_emit_deep_field_gep(proc, t, e, sel);
+
+				t = using_field->type;
+			}
+		}
+
+
+		switch (t->kind) {
+		case Type_Vector: {
+			ssaValue *vector = NULL;
+			if (using_addr != NULL) {
+				vector = using_addr;
+			} else {
+				vector = ssa_build_addr(proc, ie->expr).addr;
+				if (deref) {
+					vector = ssa_emit_load(proc, vector);
+				}
+			}
+			ssaValue *index = ssa_emit_conv(proc, ssa_build_expr(proc, ie->index), t_int);
+			ssaValue *len = ssa_make_const_int(a, t->Vector.count);
+			ssa_emit_bounds_check(proc, ast_node_token(ie->index), index, len);
+			return ssa_make_addr_vector(vector, index, expr);
+		} break;
+
+		case Type_Array: {
+			ssaValue *array = NULL;
+			if (using_addr != NULL) {
+				array = using_addr;
+			} else {
+				array = ssa_build_addr(proc, ie->expr).addr;
+				if (deref) {
+					array = ssa_emit_load(proc, array);
+				}
+			}
+			ssaValue *index = ssa_emit_conv(proc, ssa_build_expr(proc, ie->index), t_int);
+			ssaValue *elem = ssa_emit_array_ep(proc, array, index);
+			ssaValue *len = ssa_make_const_int(a, t->Vector.count);
+			ssa_emit_bounds_check(proc, ast_node_token(ie->index), index, len);
+			return ssa_make_addr(elem, expr);
+		} break;
+
+		case Type_Slice: {
+			ssaValue *slice = NULL;
+			if (using_addr != NULL) {
+				slice = ssa_emit_load(proc, using_addr);
+			} else {
+				slice = ssa_build_expr(proc, ie->expr);
+				if (deref) {
+					slice = ssa_emit_load(proc, slice);
+				}
+			}
+			ssaValue *elem = ssa_slice_elem(proc, slice);
+			ssaValue *len = ssa_slice_len(proc, slice);
+			ssaValue *index = ssa_emit_conv(proc, ssa_build_expr(proc, ie->index), t_int);
+			ssa_emit_bounds_check(proc, ast_node_token(ie->index), index, len);
+			ssaValue *v = ssa_emit_ptr_offset(proc, elem, index);
+			return ssa_make_addr(v, expr);
+
+		} break;
+
+		case Type_Basic: { // Basic_string
+			TypeAndValue *tv = map_tav_get(&proc->module->info->types, hash_pointer(ie->expr));
+			ssaValue *str;
+			ssaValue *elem;
+			ssaValue *len;
+			ssaValue *index;
+
+			if (using_addr != NULL) {
+				str = ssa_emit_load(proc, using_addr);
+			} else {
+				str = ssa_build_expr(proc, ie->expr);
+				if (deref) {
+					str = ssa_emit_load(proc, str);
+				}
+			}
+			elem = ssa_string_elem(proc, str);
+			len = ssa_string_len(proc, str);
+
+			index = ssa_emit_conv(proc, ssa_build_expr(proc, ie->index), t_int);
+			ssa_emit_bounds_check(proc, ast_node_token(ie->index), index, len);
+
+			return ssa_make_addr(ssa_emit_ptr_offset(proc, elem, index), expr);
+		} break;
+		}
+	case_end;
+
+	case_ast_node(se, SliceExpr, expr);
+		ssa_emit_comment(proc, str_lit("SliceExpr"));
+		gbAllocator a = proc->module->allocator;
+		ssaValue *low  = v_zero;
+		ssaValue *high = NULL;
+		ssaValue *max  = NULL;
+
+		if (se->low  != NULL)    low  = ssa_build_expr(proc, se->low);
+		if (se->high != NULL)    high = ssa_build_expr(proc, se->high);
+		if (se->triple_indexed)  max  = ssa_build_expr(proc, se->max);
+		ssaValue *addr = ssa_build_addr(proc, se->expr).addr;
+		ssaValue *base = ssa_emit_load(proc, addr);
+		Type *type = base_type(ssa_type(base));
+
+		if (is_type_pointer(type)) {
+			type = type_deref(type);
+			addr = base;
+			base = ssa_emit_load(proc, base);
+		}
+
+		// TODO(bill): Cleanup like mad!
+
+		switch (type->kind) {
+		case Type_Slice: {
+			Type *slice_type = type;
+
+			if (high == NULL) high = ssa_slice_len(proc, base);
+			if (max == NULL)  max  = ssa_slice_cap(proc, base);
+			GB_ASSERT(max != NULL);
+
+			ssa_emit_slice_bounds_check(proc, se->open, low, high, max, false);
+
+			ssaValue *elem = ssa_slice_elem(proc, base);
+			ssaValue *len  = ssa_emit_arith(proc, Token_Sub, high, low, t_int);
+			ssaValue *cap  = ssa_emit_arith(proc, Token_Sub, max,  low, t_int);
+			ssaValue *slice = ssa_add_local_generated(proc, slice_type);
+
+			ssaValue *gep0 = ssa_emit_struct_ep(proc, slice, 0);
+			ssaValue *gep1 = ssa_emit_struct_ep(proc, slice, 1);
+			ssaValue *gep2 = ssa_emit_struct_ep(proc, slice, 2);
+			ssa_emit_store(proc, gep0, elem);
+			ssa_emit_store(proc, gep1, len);
+			ssa_emit_store(proc, gep2, cap);
+
+			return ssa_make_addr(slice, expr);
+		}
+
+		case Type_Array: {
+			Type *slice_type = make_type_slice(a, type->Array.elem);
+
+			if (high == NULL) high = ssa_array_len(proc, base);
+			if (max == NULL)  max  = ssa_array_cap(proc, base);
+			GB_ASSERT(max != NULL);
+
+			ssa_emit_slice_bounds_check(proc, se->open, low, high, max, false);
+
+			ssaValue *elem = ssa_array_elem(proc, addr);
+			ssaValue *len  = ssa_emit_arith(proc, Token_Sub, high, low, t_int);
+			ssaValue *cap  = ssa_emit_arith(proc, Token_Sub, max,  low, t_int);
+			ssaValue *slice = ssa_add_local_generated(proc, slice_type);
+
+			ssaValue *gep0 = ssa_emit_struct_ep(proc, slice, 0);
+			ssaValue *gep1 = ssa_emit_struct_ep(proc, slice, 1);
+			ssaValue *gep2 = ssa_emit_struct_ep(proc, slice, 2);
+			ssa_emit_store(proc, gep0, elem);
+			ssa_emit_store(proc, gep1, len);
+			ssa_emit_store(proc, gep2, cap);
+
+			return ssa_make_addr(slice, expr);
+		}
+
+		case Type_Basic: {
+			GB_ASSERT(type == t_string);
+			if (high == NULL) {
+				high = ssa_string_len(proc, base);
+			}
+
+			ssa_emit_slice_bounds_check(proc, se->open, low, high, high, true);
+
+			ssaValue *elem, *len;
+			len = ssa_emit_arith(proc, Token_Sub, high, low, t_int);
+
+			elem = ssa_string_elem(proc, base);
+			elem = ssa_emit_ptr_offset(proc, elem, low);
+
+			ssaValue *str = ssa_add_local_generated(proc, t_string);
+			ssaValue *gep0 = ssa_emit_struct_ep(proc, str, 0);
+			ssaValue *gep1 = ssa_emit_struct_ep(proc, str, 1);
+			ssa_emit_store(proc, gep0, elem);
+			ssa_emit_store(proc, gep1, len);
+
+			return ssa_make_addr(str, expr);
+		} break;
+		}
+
+		GB_PANIC("Unknown slicable type");
+	case_end;
+
+	case_ast_node(de, DerefExpr, expr);
+		// TODO(bill): Is a ptr copy needed?
+		ssaValue *addr = ssa_build_expr(proc, de->expr);
+		addr = ssa_emit_ptr_offset(proc, addr, v_zero);
+		return ssa_make_addr(addr, expr);
+	case_end;
+
+	case_ast_node(de, DemaybeExpr, expr);
+		ssa_emit_comment(proc, str_lit("DemaybeExpr"));
+		ssaValue *maybe = ssa_build_expr(proc, de->expr);
+		Type *t = default_type(type_of_expr(proc->module->info, expr));
+		GB_ASSERT(is_type_tuple(t));
+
+		ssaValue *result = ssa_add_local_generated(proc, t);
+		ssa_emit_store(proc, result, maybe);
+
+		return ssa_make_addr(result, expr);
+	case_end;
+
+	case_ast_node(ce, CallExpr, expr);
+		ssaValue *e = ssa_build_expr(proc, expr);
+		ssaValue *v = ssa_add_local_generated(proc, ssa_type(e));
+		ssa_emit_store(proc, v, e);
+		return ssa_make_addr(v, expr);
+	case_end;
+
+
+	case_ast_node(cl, CompoundLit, expr);
+		ssa_emit_comment(proc, str_lit("CompoundLit"));
+		Type *type = type_of_expr(proc->module->info, expr);
+		Type *bt = base_type(type);
+		ssaValue *v = ssa_add_local_generated(proc, type);
+
+		Type *et = NULL;
+		switch (bt->kind) {
+		case Type_Vector: et = bt->Vector.elem; break;
+		case Type_Array:  et = bt->Array.elem;  break;
+		case Type_Slice:  et = bt->Slice.elem;  break;
+		}
+
+		switch (bt->kind) {
+		default: GB_PANIC("Unknown CompoundLit type: %s", type_to_string(type)); break;
+
+		case Type_Vector: {
+			ssaValue *result = ssa_add_module_constant(proc->module, type, make_exact_value_compound(expr));
+			for_array(index, cl->elems) {
+				AstNode *elem = cl->elems.e[index];
+				if (ssa_is_elem_const(proc->module, elem, et)) {
+					continue;
+				}
+				ssaValue *field_elem = ssa_build_expr(proc, elem);
+				Type *t = ssa_type(field_elem);
+				GB_ASSERT(t->kind != Type_Tuple);
+				ssaValue *ev = ssa_emit_conv(proc, field_elem, et);
+				ssaValue *i = ssa_make_const_int(proc->module->allocator, index);
+				result = ssa_emit(proc, ssa_make_instr_insert_element(proc, result, ev, i));
+			}
+
+			if (cl->elems.count == 1 && bt->Vector.count > 1) {
+				isize index_count = bt->Vector.count;
+				i32 *indices = gb_alloc_array(proc->module->allocator, i32, index_count);
+				for (isize i = 0; i < index_count; i++) {
+					indices[i] = 0;
+				}
+				ssaValue *sv = ssa_emit(proc, ssa_make_instr_vector_shuffle(proc, result, indices, index_count));
+				ssa_emit_store(proc, v, sv);
+				return ssa_make_addr(v, expr);
+			}
+			ssa_emit_store(proc, v, result);
+		} break;
+
+		case Type_Record: {
+			GB_ASSERT(is_type_struct(bt));
+			TypeRecord *st = &bt->Record;
+			if (cl->elems.count > 0) {
+				ssa_emit_store(proc, v, ssa_add_module_constant(proc->module, type, make_exact_value_compound(expr)));
+				for_array(field_index, cl->elems) {
+					AstNode *elem = cl->elems.e[field_index];
+
+					ssaValue *field_expr = NULL;
+					Entity *field = NULL;
+					isize index = field_index;
+
+					if (elem->kind == AstNode_FieldValue) {
+						ast_node(fv, FieldValue, elem);
+						Selection sel = lookup_field(proc->module->allocator, bt, fv->field->Ident.string, false);
+						index = sel.index.e[0];
+						elem = fv->value;
+					} else {
+						TypeAndValue *tav = type_and_value_of_expression(proc->module->info, elem);
+						Selection sel = lookup_field(proc->module->allocator, bt, st->fields_in_src_order[field_index]->token.string, false);
+						index = sel.index.e[0];
+					}
+
+					field = st->fields[index];
+					if (ssa_is_elem_const(proc->module, elem, field->type)) {
+						continue;
+					}
+
+					field_expr = ssa_build_expr(proc, elem);
+
+					GB_ASSERT(ssa_type(field_expr)->kind != Type_Tuple);
+
+					Type *ft = field->type;
+					ssaValue *fv = ssa_emit_conv(proc, field_expr, ft);
+					ssaValue *gep = ssa_emit_struct_ep(proc, v, index);
+					ssa_emit_store(proc, gep, fv);
+				}
+			}
+		} break;
+		case Type_Array: {
+			if (cl->elems.count > 0) {
+				ssa_emit_store(proc, v, ssa_add_module_constant(proc->module, type, make_exact_value_compound(expr)));
+				for_array(i, cl->elems) {
+					AstNode *elem = cl->elems.e[i];
+					if (ssa_is_elem_const(proc->module, elem, et)) {
+						continue;
+					}
+					ssaValue *field_expr = ssa_build_expr(proc, elem);
+					Type *t = ssa_type(field_expr);
+					GB_ASSERT(t->kind != Type_Tuple);
+					ssaValue *ev = ssa_emit_conv(proc, field_expr, et);
+					ssaValue *gep = ssa_emit_array_epi(proc, v, i);
+					ssa_emit_store(proc, gep, ev);
+				}
+			}
+		} break;
+		case Type_Slice: {
+			if (cl->elems.count > 0) {
+				Type *elem_type = bt->Slice.elem;
+				Type *elem_ptr_type = make_type_pointer(proc->module->allocator, elem_type);
+				Type *elem_ptr_ptr_type = make_type_pointer(proc->module->allocator, elem_ptr_type);
+				ssaValue *slice = ssa_add_module_constant(proc->module, type, make_exact_value_compound(expr));
+				GB_ASSERT(slice->kind == ssaValue_ConstantSlice);
+
+				ssaValue *data = ssa_emit_array_ep(proc, slice->ConstantSlice.backing_array, v_zero32);
+
+				for_array(i, cl->elems) {
+					AstNode *elem = cl->elems.e[i];
+					if (ssa_is_elem_const(proc->module, elem, et)) {
+						continue;
+					}
+
+					ssaValue *field_expr = ssa_build_expr(proc, elem);
+					Type *t = ssa_type(field_expr);
+					GB_ASSERT(t->kind != Type_Tuple);
+					ssaValue *ev = ssa_emit_conv(proc, field_expr, elem_type);
+					ssaValue *offset = ssa_emit_ptr_offset(proc, data, ssa_make_const_int(proc->module->allocator, i));
+					ssa_emit_store(proc, offset, ev);
+				}
+
+				ssaValue *gep0 = ssa_emit_struct_ep(proc, v, 0);
+				ssaValue *gep1 = ssa_emit_struct_ep(proc, v, 1);
+				ssaValue *gep2 = ssa_emit_struct_ep(proc, v, 1);
+
+				ssa_emit_store(proc, gep0, data);
+				ssa_emit_store(proc, gep1, ssa_make_const_int(proc->module->allocator, slice->ConstantSlice.count));
+				ssa_emit_store(proc, gep2, ssa_make_const_int(proc->module->allocator, slice->ConstantSlice.count));
+			}
+		} break;
+		}
+
+		return ssa_make_addr(v, expr);
+	case_end;
+
+
+	}
+
+	TokenPos token_pos = ast_node_token(expr).pos;
+	GB_PANIC("Unexpected address expression\n"
+	         "\tAstNode: %.*s @ "
+	         "%.*s(%td:%td)\n",
+	         LIT(ast_node_strings[expr->kind]),
+	         LIT(token_pos.file), token_pos.line, token_pos.column);
+
+
+	return ssa_make_addr(NULL, NULL);
+}
+
+void ssa_build_assign_op(ssaProcedure *proc, ssaAddr lhs, ssaValue *value, TokenKind op) {
+	ssaValue *old_value = ssa_addr_load(proc, lhs);
+	Type *type = ssa_type(old_value);
+
+	ssaValue *change = value;
+	if (is_type_pointer(type) && is_type_integer(ssa_type(value))) {
+		change = ssa_emit_conv(proc, value, default_type(ssa_type(value)));
+	} else {
+		change = ssa_emit_conv(proc, value, type);
+	}
+	ssaValue *new_value = ssa_emit_arith(proc, op, old_value, change, type);
+	ssa_addr_store(proc, lhs, new_value);
+}
+
+void ssa_build_cond(ssaProcedure *proc, AstNode *cond, ssaBlock *true_block, ssaBlock *false_block) {
+	switch (cond->kind) {
+	case_ast_node(pe, ParenExpr, cond);
+		ssa_build_cond(proc, pe->expr, true_block, false_block);
+		return;
+	case_end;
+
+	case_ast_node(ue, UnaryExpr, cond);
+		if (ue->op.kind == Token_Not) {
+			ssa_build_cond(proc, ue->expr, false_block, true_block);
+			return;
+		}
+	case_end;
+
+	case_ast_node(be, BinaryExpr, cond);
+		if (be->op.kind == Token_CmpAnd) {
+			ssaBlock *block = ssa_add_block(proc, NULL, "cmp.and");
+			ssa_build_cond(proc, be->left, block, false_block);
+			proc->curr_block = block;
+			ssa_build_cond(proc, be->right, true_block, false_block);
+			return;
+		} else if (be->op.kind == Token_CmpOr) {
+			ssaBlock *block = ssa_add_block(proc, NULL, "cmp.or");
+			ssa_build_cond(proc, be->left, true_block, block);
+			proc->curr_block = block;
+			ssa_build_cond(proc, be->right, true_block, false_block);
+			return;
+		}
+	case_end;
+	}
+
+	ssaValue *expr = ssa_build_expr(proc, cond);
+	expr = ssa_emit_conv(proc, expr, t_bool);
+	ssa_emit_if(proc, expr, true_block, false_block);
+}
+
+
+
+
+void ssa_build_stmt_list(ssaProcedure *proc, AstNodeArray stmts) {
+	for_array(i, stmts) {
+		ssa_build_stmt(proc, stmts.e[i]);
+	}
+}
+
+void ssa_build_stmt_internal(ssaProcedure *proc, AstNode *node);
+void ssa_build_stmt(ssaProcedure *proc, AstNode *node) {
+	u32 prev_stmt_state_flags = proc->module->stmt_state_flags;
+
+	if (node->stmt_state_flags != 0) {
+		u32 in = node->stmt_state_flags;
+		u32 out = proc->module->stmt_state_flags;
+
+		if (in & StmtStateFlag_bounds_check) {
+			out |= StmtStateFlag_bounds_check;
+			out &= ~StmtStateFlag_no_bounds_check;
+		} else if (in & StmtStateFlag_no_bounds_check) {
+			out |= StmtStateFlag_no_bounds_check;
+			out &= ~StmtStateFlag_bounds_check;
+		}
+
+		proc->module->stmt_state_flags = out;
+	}
+
+	ssa_build_stmt_internal(proc, node);
+
+	proc->module->stmt_state_flags = prev_stmt_state_flags;
+}
+
+void ssa_build_stmt_internal(ssaProcedure *proc, AstNode *node) {
+	switch (node->kind) {
+	case_ast_node(bs, EmptyStmt, node);
+	case_end;
+
+	case_ast_node(us, UsingStmt, node);
+		AstNode *decl = unparen_expr(us->node);
+		if (decl->kind == AstNode_VarDecl) {
+			ssa_build_stmt(proc, decl);
+		}
+	case_end;
+
+	case_ast_node(vd, VarDecl, node);
+		ssaModule *m = proc->module;
+		gbTempArenaMemory tmp = gb_temp_arena_memory_begin(&m->tmp_arena);
+
+		if (vd->values.count == 0) { // declared and zero-initialized
+			for_array(i, vd->names) {
+				AstNode *name = vd->names.e[i];
+				if (!ssa_is_blank_ident(name)) {
+					ssa_add_local_for_identifier(proc, name, true);
+				}
+			}
+		} else { // Tuple(s)
+			Array(ssaAddr) lvals;
+			ssaValueArray  inits;
+			array_init_reserve(&lvals, m->tmp_allocator, vd->names.count);
+			array_init_reserve(&inits, m->tmp_allocator, vd->names.count);
+
+			for_array(i, vd->names) {
+				AstNode *name = vd->names.e[i];
+				ssaAddr lval = ssa_make_addr(NULL, NULL);
+				if (!ssa_is_blank_ident(name)) {
+					ssa_add_local_for_identifier(proc, name, false);
+					lval = ssa_build_addr(proc, name);
+				}
+
+				array_add(&lvals, lval);
+			}
+
+			for_array(i, vd->values) {
+				ssaValue *init = ssa_build_expr(proc, vd->values.e[i]);
+				Type *t = ssa_type(init);
+				if (t->kind == Type_Tuple) {
+					for (isize i = 0; i < t->Tuple.variable_count; i++) {
+						Entity *e = t->Tuple.variables[i];
+						ssaValue *v = ssa_emit_struct_ev(proc, init, i);
+						array_add(&inits, v);
+					}
+				} else {
+					array_add(&inits, init);
+				}
+			}
+
+
+			for_array(i, inits) {
+				if (lvals.e[i].addr == NULL) {
+					continue;
+				}
+				ssaValue *v = ssa_emit_conv(proc, inits.e[i], ssa_addr_type(lvals.e[i]));
+				ssa_addr_store(proc, lvals.e[i], v);
+			}
+		}
+
+		gb_temp_arena_memory_end(tmp);
+	case_end;
+
+	case_ast_node(pd, ProcDecl, node);
+		if (pd->body != NULL) {
+			CheckerInfo *info = proc->module->info;
+
+			Entity **found = map_entity_get(&info->definitions, hash_pointer(pd->name));
+			GB_ASSERT_MSG(found != NULL, "Unable to find: %.*s", LIT(pd->name->Ident.string));
+			Entity *e = *found;
+
+
+			if (map_entity_get(&proc->module->min_dep_map, hash_pointer(e)) == NULL) {
+				// NOTE(bill): Nothing depends upon it so doesn't need to be built
+				break;
+			}
+
+			// NOTE(bill): Generate a new name
+			// parent.name-guid
+			String original_name = pd->name->Ident.string;
+			String pd_name = original_name;
+			if (pd->link_name.len > 0) {
+				pd_name = pd->link_name;
+			}
+
+			isize name_len = proc->name.len + 1 + pd_name.len + 1 + 10 + 1;
+			u8 *name_text = gb_alloc_array(proc->module->allocator, u8, name_len);
+			i32 guid = cast(i32)proc->children.count;
+			name_len = gb_snprintf(cast(char *)name_text, name_len, "%.*s.%.*s-%d", LIT(proc->name), LIT(pd_name), guid);
+			String name = make_string(name_text, name_len-1);
+
+
+			ssaValue *value = ssa_make_value_procedure(proc->module->allocator,
+			                                           proc->module, e, e->type, pd->type, pd->body, name);
+
+			value->Proc.tags = pd->tags;
+			value->Proc.parent = proc;
+
+			ssa_module_add_value(proc->module, e, value);
+			array_add(&proc->children, &value->Proc);
+			array_add(&proc->module->procs_to_generate, value);
+		} else {
+			CheckerInfo *info = proc->module->info;
+
+			Entity **found = map_entity_get(&info->definitions, hash_pointer(pd->name));
+			GB_ASSERT_MSG(found != NULL, "Unable to find: %.*s", LIT(pd->name->Ident.string));
+			Entity *e = *found;
+
+			// FFI - Foreign function interace
+			String original_name = pd->name->Ident.string;
+			String name = original_name;
+			if (pd->foreign_name.len > 0) {
+				name = pd->foreign_name;
+			}
+
+			ssaValue *value = ssa_make_value_procedure(proc->module->allocator,
+			                                           proc->module, e, e->type, pd->type, pd->body, name);
+
+			value->Proc.tags = pd->tags;
+
+			ssa_module_add_value(proc->module, e, value);
+			ssa_build_proc(value, proc);
+
+			if (value->Proc.tags & ProcTag_foreign) {
+				HashKey key = hash_string(name);
+				ssaValue **prev_value = map_ssa_value_get(&proc->module->members, key);
+				if (prev_value == NULL) {
+					// NOTE(bill): Don't do mutliple declarations in the IR
+					map_ssa_value_set(&proc->module->members, key, value);
+				}
+			} else {
+				array_add(&proc->children, &value->Proc);
+			}
+		}
+	case_end;
+
+	case_ast_node(td, TypeDecl, node);
+
+		// NOTE(bill): Generate a new name
+		// parent_proc.name-guid
+		String td_name = td->name->Ident.string;
+		isize name_len = proc->name.len + 1 + td_name.len + 1 + 10 + 1;
+		u8 *name_text = gb_alloc_array(proc->module->allocator, u8, name_len);
+		i32 guid = cast(i32)proc->module->members.entries.count;
+		name_len = gb_snprintf(cast(char *)name_text, name_len, "%.*s.%.*s-%d", LIT(proc->name), LIT(td_name), guid);
+		String name = make_string(name_text, name_len-1);
+
+		Entity **found = map_entity_get(&proc->module->info->definitions, hash_pointer(td->name));
+		GB_ASSERT(found != NULL);
+		Entity *e = *found;
+		ssaValue *value = ssa_make_value_type_name(proc->module->allocator,
+		                                           name, e->type);
+		map_string_set(&proc->module->type_names, hash_pointer(e->type), name);
+		ssa_gen_global_type_name(proc->module, e, name);
+	case_end;
+
+	case_ast_node(ids, IncDecStmt, node);
+		ssa_emit_comment(proc, str_lit("IncDecStmt"));
+		TokenKind op = ids->op.kind;
+		if (op == Token_Increment) {
+			op = Token_Add;
+		} else if (op == Token_Decrement) {
+			op = Token_Sub;
+		}
+		ssaAddr lval = ssa_build_addr(proc, ids->expr);
+		ssaValue *one = ssa_emit_conv(proc, v_one, ssa_addr_type(lval));
+		ssa_build_assign_op(proc, lval, one, op);
+
+	case_end;
+
+	case_ast_node(as, AssignStmt, node);
+		ssa_emit_comment(proc, str_lit("AssignStmt"));
+
+		ssaModule *m = proc->module;
+		gbTempArenaMemory tmp = gb_temp_arena_memory_begin(&m->tmp_arena);
+
+		switch (as->op.kind) {
+		case Token_Eq: {
+			Array(ssaAddr) lvals;
+			array_init(&lvals, m->tmp_allocator);
+
+			for_array(i, as->lhs) {
+				AstNode *lhs = as->lhs.e[i];
+				ssaAddr lval = {0};
+				if (!ssa_is_blank_ident(lhs)) {
+					lval = ssa_build_addr(proc, lhs);
+				}
+				array_add(&lvals, lval);
+			}
+
+			if (as->lhs.count == as->rhs.count) {
+				if (as->lhs.count == 1) {
+					AstNode *rhs = as->rhs.e[0];
+					ssaValue *init = ssa_build_expr(proc, rhs);
+					ssa_addr_store(proc, lvals.e[0], init);
+				} else {
+					ssaValueArray inits;
+					array_init_reserve(&inits, m->tmp_allocator, lvals.count);
+
+					for_array(i, as->rhs) {
+						ssaValue *init = ssa_build_expr(proc, as->rhs.e[i]);
+						array_add(&inits, init);
+					}
+
+					for_array(i, inits) {
+						ssa_addr_store(proc, lvals.e[i], inits.e[i]);
+					}
+				}
+			} else {
+				ssaValueArray inits;
+				array_init_reserve(&inits, m->tmp_allocator, lvals.count);
+
+				for_array(i, as->rhs) {
+					ssaValue *init = ssa_build_expr(proc, as->rhs.e[i]);
+					Type *t = ssa_type(init);
+					// TODO(bill): refactor for code reuse as this is repeated a bit
+					if (t->kind == Type_Tuple) {
+						for (isize i = 0; i < t->Tuple.variable_count; i++) {
+							Entity *e = t->Tuple.variables[i];
+							ssaValue *v = ssa_emit_struct_ev(proc, init, i);
+							array_add(&inits, v);
+						}
+					} else {
+						array_add(&inits, init);
+					}
+				}
+
+				for_array(i, inits) {
+					ssa_addr_store(proc, lvals.e[i], inits.e[i]);
+				}
+			}
+
+		} break;
+
+		default: {
+			// NOTE(bill): Only 1 += 1 is allowed, no tuples
+			// +=, -=, etc
+			i32 op = cast(i32)as->op.kind;
+			op += Token_Add - Token_AddEq; // Convert += to +
+			ssaAddr lhs = ssa_build_addr(proc, as->lhs.e[0]);
+			ssaValue *value = ssa_build_expr(proc, as->rhs.e[0]);
+			ssa_build_assign_op(proc, lhs, value, cast(TokenKind)op);
+		} break;
+		}
+
+		gb_temp_arena_memory_end(tmp);
+	case_end;
+
+	case_ast_node(es, ExprStmt, node);
+		// NOTE(bill): No need to use return value
+		ssa_build_expr(proc, es->expr);
+	case_end;
+
+	case_ast_node(bs, BlockStmt, node);
+		ssa_open_scope(proc);
+		ssa_build_stmt_list(proc, bs->stmts);
+		ssa_close_scope(proc, ssaDeferExit_Default, NULL);
+	case_end;
+
+	case_ast_node(ds, DeferStmt, node);
+		ssa_emit_comment(proc, str_lit("DeferStmt"));
+		isize scope_index = proc->scope_index;
+		if (ds->stmt->kind == AstNode_BlockStmt) {
+			scope_index--;
+		}
+		ssa_add_defer_node(proc, scope_index, ds->stmt);
+	case_end;
+
+	case_ast_node(rs, ReturnStmt, node);
+		ssa_emit_comment(proc, str_lit("ReturnStmt"));
+		ssaValue *v = NULL;
+		TypeTuple *return_type_tuple  = &proc->type->Proc.results->Tuple;
+		isize return_count = proc->type->Proc.result_count;
+		if (return_count == 0) {
+			// No return values
+		} else if (return_count == 1) {
+			Entity *e = return_type_tuple->variables[0];
+			v = ssa_emit_conv(proc, ssa_build_expr(proc, rs->results.e[0]), e->type);
+		} else {
+			gbTempArenaMemory tmp = gb_temp_arena_memory_begin(&proc->module->tmp_arena);
+
+			ssaValueArray results;
+			array_init_reserve(&results, proc->module->tmp_allocator, return_count);
+
+			for_array(res_index, rs->results) {
+				ssaValue *res = ssa_build_expr(proc, rs->results.e[res_index]);
+				Type *t = ssa_type(res);
+				if (t->kind == Type_Tuple) {
+					for (isize i = 0; i < t->Tuple.variable_count; i++) {
+						Entity *e = t->Tuple.variables[i];
+						ssaValue *v = ssa_emit_struct_ev(proc, res, i);
+						array_add(&results, v);
+					}
+				} else {
+					array_add(&results, res);
+				}
+			}
+
+			Type *ret_type = proc->type->Proc.results;
+			v = ssa_add_local_generated(proc, ret_type);
+			for_array(i, results) {
+				Entity *e = return_type_tuple->variables[i];
+				ssaValue *res = ssa_emit_conv(proc, results.e[i], e->type);
+				ssaValue *field = ssa_emit_struct_ep(proc, v, i);
+				ssa_emit_store(proc, field, res);
+			}
+
+			v = ssa_emit_load(proc, v);
+
+			gb_temp_arena_memory_end(tmp);
+		}
+		ssa_emit_return(proc, v);
+
+	case_end;
+
+	case_ast_node(is, IfStmt, node);
+		ssa_emit_comment(proc, str_lit("IfStmt"));
+		if (is->init != NULL) {
+			ssaBlock *init = ssa_add_block(proc, node, "if.init");
+			ssa_emit_jump(proc, init);
+			proc->curr_block = init;
+			ssa_build_stmt(proc, is->init);
+		}
+		ssaBlock *then = ssa_add_block(proc, node, "if.then");
+		ssaBlock *done = ssa_add_block(proc, node, "if.done"); // NOTE(bill): Append later
+		ssaBlock *else_ = done;
+		if (is->else_stmt != NULL) {
+			else_ = ssa_add_block(proc, is->else_stmt, "if.else");
+		}
+
+		ssa_build_cond(proc, is->cond, then, else_);
+		proc->curr_block = then;
+
+		ssa_open_scope(proc);
+		ssa_build_stmt(proc, is->body);
+		ssa_close_scope(proc, ssaDeferExit_Default, NULL);
+
+		ssa_emit_jump(proc, done);
+
+		if (is->else_stmt != NULL) {
+			proc->curr_block = else_;
+
+			ssa_open_scope(proc);
+			ssa_build_stmt(proc, is->else_stmt);
+			ssa_close_scope(proc, ssaDeferExit_Default, NULL);
+
+			ssa_emit_jump(proc, done);
+		}
+		proc->curr_block = done;
+	case_end;
+
+	case_ast_node(fs, ForStmt, node);
+		ssa_emit_comment(proc, str_lit("ForStmt"));
+		if (fs->init != NULL) {
+			ssaBlock *init = ssa_add_block(proc, node, "for.init");
+			ssa_emit_jump(proc, init);
+			proc->curr_block = init;
+			ssa_build_stmt(proc, fs->init);
+		}
+		ssaBlock *body = ssa_add_block(proc, node, "for.body");
+		ssaBlock *done = ssa_add_block(proc, node, "for.done"); // NOTE(bill): Append later
+
+		ssaBlock *loop = body;
+
+		if (fs->cond != NULL) {
+			loop = ssa_add_block(proc, node, "for.loop");
+		}
+		ssaBlock *cont = loop;
+		if (fs->post != NULL) {
+			cont = ssa_add_block(proc, node, "for.post");
+
+		}
+		ssa_emit_jump(proc, loop);
+		proc->curr_block = loop;
+		if (loop != body) {
+			ssa_build_cond(proc, fs->cond, body, done);
+			proc->curr_block = body;
+		}
+
+		ssa_push_target_list(proc, done, cont, NULL);
+
+		ssa_open_scope(proc);
+		ssa_build_stmt(proc, fs->body);
+		ssa_close_scope(proc, ssaDeferExit_Default, NULL);
+
+		ssa_pop_target_list(proc);
+		ssa_emit_jump(proc, cont);
+
+		if (fs->post != NULL) {
+			proc->curr_block = cont;
+			ssa_build_stmt(proc, fs->post);
+			ssa_emit_jump(proc, loop);
+		}
+
+
+		proc->curr_block = done;
+
+	case_end;
+
+	case_ast_node(ms, MatchStmt, node);
+		ssa_emit_comment(proc, str_lit("MatchStmt"));
+		if (ms->init != NULL) {
+			ssa_build_stmt(proc, ms->init);
+		}
+		ssaValue *tag = v_true;
+		if (ms->tag != NULL) {
+			tag = ssa_build_expr(proc, ms->tag);
+		}
+		ssaBlock *done = ssa_add_block(proc, node, "match.done"); // NOTE(bill): Append later
+
+		ast_node(body, BlockStmt, ms->body);
+
+		AstNodeArray default_stmts = {0};
+		ssaBlock *default_fall = NULL;
+		ssaBlock *default_block = NULL;
+
+		ssaBlock *fall = NULL;
+		bool append_fall = false;
+
+		isize case_count = body->stmts.count;
+		for_array(i, body->stmts) {
+			AstNode *clause = body->stmts.e[i];
+			ssaBlock *body = fall;
+
+			ast_node(cc, CaseClause, clause);
+
+			if (body == NULL) {
+				if (cc->list.count == 0) {
+					body = ssa_add_block(proc, clause, "match.dflt.body");
+				} else {
+					body = ssa_add_block(proc, clause, "match.case.body");
+				}
+			}
+			if (append_fall && body == fall) {
+				append_fall = false;
+			}
+
+			fall = done;
+			if (i+1 < case_count) {
+				append_fall = true;
+				fall = ssa_add_block(proc, clause, "match.fall.body");
+			}
+
+			if (cc->list.count == 0) {
+				// default case
+				default_stmts = cc->stmts;
+				default_fall  = fall;
+				default_block = body;
+				continue;
+			}
+
+			ssaBlock *next_cond = NULL;
+			for_array(j, cc->list) {
+				AstNode *expr = cc->list.e[j];
+				next_cond = ssa_add_block(proc, clause, "match.case.next");
+
+				ssaValue *cond = ssa_emit_comp(proc, Token_CmpEq, tag, ssa_build_expr(proc, expr));
+				ssa_emit_if(proc, cond, body, next_cond);
+				proc->curr_block = next_cond;
+			}
+			proc->curr_block = body;
+
+			ssa_push_target_list(proc, done, NULL, fall);
+			ssa_open_scope(proc);
+			ssa_build_stmt_list(proc, cc->stmts);
+			ssa_close_scope(proc, ssaDeferExit_Default, body);
+			ssa_pop_target_list(proc);
+
+			ssa_emit_jump(proc, done);
+			proc->curr_block = next_cond;
+		}
+
+		if (default_block != NULL) {
+			ssa_emit_jump(proc, default_block);
+			proc->curr_block = default_block;
+
+			ssa_push_target_list(proc, done, NULL, default_fall);
+			ssa_open_scope(proc);
+			ssa_build_stmt_list(proc, default_stmts);
+			ssa_close_scope(proc, ssaDeferExit_Default, default_block);
+			ssa_pop_target_list(proc);
+		}
+
+		ssa_emit_jump(proc, done);
+		proc->curr_block = done;
+	case_end;
+
+
+	case_ast_node(ms, TypeMatchStmt, node);
+		ssa_emit_comment(proc, str_lit("TypeMatchStmt"));
+		gbAllocator allocator = proc->module->allocator;
+
+		ssaValue *parent = ssa_build_expr(proc, ms->tag);
+		bool is_union_ptr = false;
+		bool is_any = false;
+		GB_ASSERT(check_valid_type_match_type(ssa_type(parent), &is_union_ptr, &is_any));
+
+		ssaValue *tag_index = NULL;
+		ssaValue *union_data = NULL;
+		if (is_union_ptr) {
+			ssa_emit_comment(proc, str_lit("get union's tag"));
+			tag_index = ssa_emit_load(proc, ssa_emit_union_tag_ptr(proc, parent));
+			union_data = ssa_emit_conv(proc, parent, t_rawptr);
+		}
+
+		ssaBlock *start_block = ssa_add_block(proc, node, "type-match.case.first");
+		ssa_emit_jump(proc, start_block);
+		proc->curr_block = start_block;
+
+		ssaBlock *done = ssa_add_block(proc, node, "type-match.done"); // NOTE(bill): Append later
+
+		ast_node(body, BlockStmt, ms->body);
+
+		String tag_var_name = ms->var->Ident.string;
+
+		AstNodeArray default_stmts = {0};
+		ssaBlock *default_block = NULL;
+
+
+		isize case_count = body->stmts.count;
+		for_array(i, body->stmts) {
+			AstNode *clause = body->stmts.e[i];
+			ast_node(cc, CaseClause, clause);
+
+			if (cc->list.count == 0) {
+				// default case
+				default_stmts = cc->stmts;
+				default_block = ssa_add_block(proc, clause, "type-match.dflt.body");
+				continue;
+			}
+
+
+			ssaBlock *body = ssa_add_block(proc, clause, "type-match.case.body");
+
+			Scope *scope = *map_scope_get(&proc->module->info->scopes, hash_pointer(clause));
+			Entity *tag_var_entity = current_scope_lookup_entity(scope, tag_var_name);
+			GB_ASSERT_MSG(tag_var_entity != NULL, "%.*s", LIT(tag_var_name));
+
+			ssaBlock *next_cond = NULL;
+			ssaValue *cond = NULL;
+
+			if (is_union_ptr) {
+				Type *bt = type_deref(tag_var_entity->type);
+				ssaValue *index = NULL;
+				Type *ut = base_type(type_deref(ssa_type(parent)));
+				GB_ASSERT(ut->Record.kind == TypeRecord_Union);
+				for (isize field_index = 1; field_index < ut->Record.field_count; field_index++) {
+					Entity *f = ut->Record.fields[field_index];
+					if (are_types_identical(f->type, bt)) {
+						index = ssa_make_const_int(allocator, field_index);
+						break;
+					}
+				}
+				GB_ASSERT(index != NULL);
+
+				ssaValue *tag_var = ssa_add_local(proc, tag_var_entity);
+				ssaValue *data_ptr = ssa_emit_conv(proc, union_data, tag_var_entity->type);
+				ssa_emit_store(proc, tag_var, data_ptr);
+
+				cond = ssa_emit_comp(proc, Token_CmpEq, tag_index, index);
+			} else if (is_any) {
+				Type *type = tag_var_entity->type;
+				ssaValue *any_data = ssa_emit_struct_ev(proc, parent, 1);
+				ssaValue *data = ssa_emit_conv(proc, any_data, make_type_pointer(proc->module->allocator, type));
+				ssa_module_add_value(proc->module, tag_var_entity, data);
+
+				ssaValue *any_ti  = ssa_emit_struct_ev(proc, parent, 0);
+				ssaValue *case_ti = ssa_type_info(proc, type);
+				cond = ssa_emit_comp(proc, Token_CmpEq, any_ti, case_ti);
+			} else {
+				GB_PANIC("Invalid type for type match statement");
+			}
+
+			next_cond = ssa_add_block(proc, clause, "type-match.case.next");
+			ssa_emit_if(proc, cond, body, next_cond);
+			proc->curr_block = next_cond;
+
+			proc->curr_block = body;
+
+			ssa_push_target_list(proc, done, NULL, NULL);
+			ssa_open_scope(proc);
+			ssa_build_stmt_list(proc, cc->stmts);
+			ssa_close_scope(proc, ssaDeferExit_Default, body);
+			ssa_pop_target_list(proc);
+
+			ssa_emit_jump(proc, done);
+			proc->curr_block = next_cond;
+		}
+
+		if (default_block != NULL) {
+			ssa_emit_jump(proc, default_block);
+			proc->curr_block = default_block;
+
+			ssa_push_target_list(proc, done, NULL, NULL);
+			ssa_open_scope(proc);
+			ssa_build_stmt_list(proc, default_stmts);
+			ssa_close_scope(proc, ssaDeferExit_Default, default_block);
+			ssa_pop_target_list(proc);
+		}
+
+		ssa_emit_jump(proc, done);
+		proc->curr_block = done;
+	case_end;
+
+	case_ast_node(bs, BranchStmt, node);
+		ssaBlock *block = NULL;
+		switch (bs->token.kind) {
+		case Token_break:
+			for (ssaTargetList *t = proc->target_list; t != NULL && block == NULL; t = t->prev) {
+				block = t->break_;
+			}
+			break;
+		case Token_continue:
+			for (ssaTargetList *t = proc->target_list; t != NULL && block == NULL; t = t->prev) {
+				block = t->continue_;
+			}
+			break;
+		case Token_fallthrough:
+			for (ssaTargetList *t = proc->target_list; t != NULL && block == NULL; t = t->prev) {
+				block = t->fallthrough_;
+			}
+			break;
+		}
+		if (block != NULL) {
+			ssa_emit_defer_stmts(proc, ssaDeferExit_Branch, block);
+		}
+		switch (bs->token.kind) {
+		case Token_break:       ssa_emit_comment(proc, str_lit("break"));       break;
+		case Token_continue:    ssa_emit_comment(proc, str_lit("continue"));    break;
+		case Token_fallthrough: ssa_emit_comment(proc, str_lit("fallthrough")); break;
+		}
+		ssa_emit_jump(proc, block);
+		ssa_emit_unreachable(proc);
+	case_end;
+
+
+
+	case_ast_node(pa, PushAllocator, node);
+		ssa_emit_comment(proc, str_lit("PushAllocator"));
+		ssa_open_scope(proc);
+
+		ssaValue *context_ptr = ssa_find_implicit_value_backing(proc, ImplicitValue_context);
+		ssaValue *prev_context = ssa_add_local_generated(proc, t_context);
+		ssa_emit_store(proc, prev_context, ssa_emit_load(proc, context_ptr));
+
+		ssa_add_defer_instr(proc, proc->scope_index, ssa_make_instr_store(proc, context_ptr, ssa_emit_load(proc, prev_context)));
+
+		ssaValue *gep = ssa_emit_struct_ep(proc, context_ptr, 1);
+		ssa_emit_store(proc, gep, ssa_build_expr(proc, pa->expr));
+
+		ssa_build_stmt(proc, pa->body);
+
+		ssa_close_scope(proc, ssaDeferExit_Default, NULL);
+	case_end;
+
+
+	case_ast_node(pa, PushContext, node);
+		ssa_emit_comment(proc, str_lit("PushContext"));
+		ssa_open_scope(proc);
+
+		ssaValue *context_ptr = ssa_find_implicit_value_backing(proc, ImplicitValue_context);
+		ssaValue *prev_context = ssa_add_local_generated(proc, t_context);
+		ssa_emit_store(proc, prev_context, ssa_emit_load(proc, context_ptr));
+
+		ssa_add_defer_instr(proc, proc->scope_index, ssa_make_instr_store(proc, context_ptr, ssa_emit_load(proc, prev_context)));
+
+		ssa_emit_store(proc, context_ptr, ssa_build_expr(proc, pa->expr));
+
+		ssa_build_stmt(proc, pa->body);
+
+		ssa_close_scope(proc, ssaDeferExit_Default, NULL);
+	case_end;
+
+
+	}
+}
+
+
+
+
+
+
+
+////////////////////////////////////////////////////////////////
+//
+// @Procedure
+//
+////////////////////////////////////////////////////////////////
+
+void ssa_number_proc_registers(ssaProcedure *proc) {
+	i32 reg_index = 0;
+	for_array(i, proc->blocks) {
+		ssaBlock *b = proc->blocks.e[i];
+		b->index = i;
+		for_array(j, b->instrs) {
+			ssaValue *value = b->instrs.e[j];
+			GB_ASSERT(value->kind == ssaValue_Instr);
+			ssaInstr *instr = &value->Instr;
+			if (ssa_instr_type(instr) == NULL) { // NOTE(bill): Ignore non-returning instructions
+				continue;
+			}
+			value->index = reg_index;
+			reg_index++;
+		}
+	}
+}
+
+void ssa_begin_procedure_body(ssaProcedure *proc) {
+	array_add(&proc->module->procs, proc);
+
+	array_init(&proc->blocks,      heap_allocator());
+	array_init(&proc->defer_stmts, heap_allocator());
+	array_init(&proc->children,    heap_allocator());
+
+	proc->decl_block  = ssa_add_block(proc, proc->type_expr, "decls");
+	proc->entry_block = ssa_add_block(proc, proc->type_expr, "entry");
+	proc->curr_block  = proc->entry_block;
+
+	if (proc->type->Proc.params != NULL) {
+		TypeTuple *params = &proc->type->Proc.params->Tuple;
+		for (isize i = 0; i < params->variable_count; i++) {
+			Entity *e = params->variables[i];
+			ssaValue *param = ssa_add_param(proc, e);
+			array_add(&proc->params, param);
+		}
+	}
+}
+
+
+void ssa_end_procedure_body(ssaProcedure *proc) {
+	if (proc->type->Proc.result_count == 0) {
+		ssa_emit_return(proc, NULL);
+	}
+
+	if (proc->curr_block->instrs.count == 0) {
+		ssa_emit_unreachable(proc);
+	}
+
+	proc->curr_block = proc->decl_block;
+	ssa_emit_jump(proc, proc->entry_block);
+
+	ssa_number_proc_registers(proc);
+}
+
+
+void ssa_insert_code_before_proc(ssaProcedure* proc, ssaProcedure *parent) {
+	if (parent == NULL) {
+		if (str_eq(proc->name, str_lit("main"))) {
+			ssa_emit_startup_runtime(proc);
+		}
+	}
+}
+
+void ssa_build_proc(ssaValue *value, ssaProcedure *parent) {
+	ssaProcedure *proc = &value->Proc;
+
+	proc->parent = parent;
+
+	if (proc->entity != NULL) {
+		ssaModule *m = proc->module;
+		CheckerInfo *info = m->info;
+		Entity *e = proc->entity;
+		String filename = e->token.pos.file;
+		AstFile **found = map_ast_file_get(&info->files, hash_string(filename));
+		GB_ASSERT(found != NULL);
+		AstFile *f = *found;
+		ssaDebugInfo *di_file = NULL;
+
+		ssaDebugInfo **di_file_found = map_ssa_debug_info_get(&m->debug_info, hash_pointer(f));
+		if (di_file_found) {
+			di_file = *di_file_found;
+			GB_ASSERT(di_file->kind == ssaDebugInfo_File);
+		} else {
+			di_file = ssa_add_debug_info_file(proc, f);
+		}
+
+		ssa_add_debug_info_proc(proc, e, proc->name, di_file);
+	}
+
+	if (proc->body != NULL) {
+		u32 prev_stmt_state_flags = proc->module->stmt_state_flags;
+
+		if (proc->tags != 0) {
+			u32 in = proc->tags;
+			u32 out = proc->module->stmt_state_flags;
+			if (in & ProcTag_bounds_check) {
+				out |= StmtStateFlag_bounds_check;
+				out &= ~StmtStateFlag_no_bounds_check;
+			} else if (in & ProcTag_no_bounds_check) {
+				out |= StmtStateFlag_no_bounds_check;
+				out &= ~StmtStateFlag_bounds_check;
+			}
+			proc->module->stmt_state_flags = out;
+		}
+
+
+		ssa_begin_procedure_body(proc);
+		ssa_insert_code_before_proc(proc, parent);
+		ssa_build_stmt(proc, proc->body);
+		ssa_end_procedure_body(proc);
+
+		proc->module->stmt_state_flags = prev_stmt_state_flags;
+	}
+}
+
+
+
+
+
+
+
+////////////////////////////////////////////////////////////////
+//
+// @Module
+//
+////////////////////////////////////////////////////////////////
+
+
+
+void ssa_module_add_value(ssaModule *m, Entity *e, ssaValue *v) {
+	map_ssa_value_set(&m->values, hash_pointer(e), v);
+}
+
+void ssa_init_module(ssaModule *m, Checker *c) {
+	// TODO(bill): Determine a decent size for the arena
+	isize token_count = c->parser->total_token_count;
+	isize arena_size = 4 * token_count * gb_size_of(ssaValue);
+	gb_arena_init_from_allocator(&m->arena, heap_allocator(), arena_size);
+	gb_arena_init_from_allocator(&m->tmp_arena, heap_allocator(), arena_size);
+	m->allocator     = gb_arena_allocator(&m->arena);
+	m->tmp_allocator = gb_arena_allocator(&m->tmp_arena);
+	m->info = &c->info;
+	m->sizes = c->sizes;
+
+	map_ssa_value_init(&m->values,  heap_allocator());
+	map_ssa_value_init(&m->members, heap_allocator());
+	map_ssa_debug_info_init(&m->debug_info, heap_allocator());
+	map_string_init(&m->type_names, heap_allocator());
+	array_init(&m->procs,    heap_allocator());
+	array_init(&m->procs_to_generate, heap_allocator());
+
+	// Default states
+	m->stmt_state_flags = 0;
+	m->stmt_state_flags |= StmtStateFlag_bounds_check;
+
+	{
+		// Add type info data
+		{
+			String name = str_lit(SSA_TYPE_INFO_DATA_NAME);
+			isize count = c->info.type_info_map.entries.count;
+			Entity *e = make_entity_variable(m->allocator, NULL, make_token_ident(name), make_type_array(m->allocator, t_type_info, count));
+			ssaValue *g = ssa_make_value_global(m->allocator, e, NULL);
+			g->Global.is_private  = true;
+			ssa_module_add_value(m, e, g);
+			map_ssa_value_set(&m->members, hash_string(name), g);
+		}
+
+		// Type info member buffer
+		{
+			// NOTE(bill): Removes need for heap allocation by making it global memory
+			isize count = 0;
+
+			for_array(entry_index, m->info->type_info_map.entries) {
+				MapIsizeEntry *entry = &m->info->type_info_map.entries.e[entry_index];
+				Type *t = cast(Type *)cast(uintptr)entry->key.key;
+
+				switch (t->kind) {
+				case Type_Record:
+					switch (t->Record.kind) {
+					case TypeRecord_Struct:
+					case TypeRecord_RawUnion:
+						count += t->Record.field_count;
+					}
+					break;
+				case Type_Tuple:
+					count += t->Tuple.variable_count;
+					break;
+				}
+			}
+
+			String name = str_lit(SSA_TYPE_INFO_DATA_MEMBER_NAME);
+			Entity *e = make_entity_variable(m->allocator, NULL, make_token_ident(name),
+			                                 make_type_array(m->allocator, t_type_info_member, count));
+			ssaValue *g = ssa_make_value_global(m->allocator, e, NULL);
+			ssa_module_add_value(m, e, g);
+			map_ssa_value_set(&m->members, hash_string(name), g);
+		}
+	}
+
+	{
+		ssaDebugInfo *di = ssa_alloc_debug_info(m->allocator, ssaDebugInfo_CompileUnit);
+		di->CompileUnit.file = m->info->files.entries.e[0].value; // Zeroth is the init file
+		di->CompileUnit.producer = str_lit("odin");
+
+		map_ssa_debug_info_set(&m->debug_info, hash_pointer(m), di);
+	}
+}
+
+void ssa_destroy_module(ssaModule *m) {
+	map_ssa_value_destroy(&m->values);
+	map_ssa_value_destroy(&m->members);
+	map_string_destroy(&m->type_names);
+	map_ssa_debug_info_destroy(&m->debug_info);
+	array_free(&m->procs_to_generate);
+	gb_arena_free(&m->arena);
+}
+
+
+
+////////////////////////////////////////////////////////////////
+//
+// @Code Generation
+//
+////////////////////////////////////////////////////////////////
+
+
+bool ssa_gen_init(ssaGen *s, Checker *c) {
+	if (global_error_collector.count != 0) {
+		return false;
+	}
+
+	isize tc = c->parser->total_token_count;
+	if (tc < 2) {
+		return false;
+	}
+
+	ssa_init_module(&s->module, c);
+	s->module.generate_debug_info = false;
+
+	// TODO(bill): generate appropriate output name
+	int pos = cast(int)string_extension_position(c->parser->init_fullpath);
+	gbFileError err = gb_file_create(&s->output_file, gb_bprintf("%.*s.ll", pos, c->parser->init_fullpath.text));
+	if (err != gbFileError_None) {
+		return false;
+	}
+
+	return true;
+}
+
+void ssa_gen_destroy(ssaGen *s) {
+	ssa_destroy_module(&s->module);
+	gb_file_close(&s->output_file);
+}
+
+String ssa_mangle_name(ssaGen *s, String path, String name) {
+	// NOTE(bill): prefix names not in the init scope
+	// TODO(bill): make robust and not just rely on the file's name
+
+	ssaModule *m = &s->module;
+	CheckerInfo *info = m->info;
+	gbAllocator a = m->allocator;
+	AstFile *file = *map_ast_file_get(&info->files, hash_string(path));
+
+	char *str = gb_alloc_array(a, char, path.len+1);
+	gb_memmove(str, path.text, path.len);
+	str[path.len] = 0;
+	for (isize i = 0; i < path.len; i++) {
+		if (str[i] == '\\') {
+			str[i] = '/';
+		}
+	}
+
+	char const *base = gb_path_base_name(str);
+	char const *ext = gb_path_extension(base);
+	isize base_len = ext-1-base;
+
+	isize max_len = base_len + 1 + 10 + 1 + name.len;
+	u8 *new_name = gb_alloc_array(a, u8, max_len);
+	isize new_name_len = gb_snprintf(
+		cast(char *)new_name, max_len,
+		"%.*s-%u.%.*s",
+		cast(int)base_len, base,
+		file->id,
+		LIT(name));
+
+	return make_string(new_name, new_name_len-1);
+}
+
+ssaValue *ssa_get_type_info_ptr(ssaProcedure *proc, ssaValue *type_info_data, Type *type) {
+	i32 index = cast(i32)ssa_type_info_index(proc->module->info, type);
+	// gb_printf_err("%d %s\n", index, type_to_string(type));
+	return ssa_emit_array_epi(proc, type_info_data, index);
+}
+
+ssaValue *ssa_type_info_member_offset(ssaProcedure *proc, ssaValue *data, isize count, i32 *index) {
+	ssaValue *offset = ssa_emit_array_epi(proc, data, *index);
+	*index += count;
+	return offset;
+}
+
+void ssa_gen_tree(ssaGen *s) {
+	ssaModule *m = &s->module;
+	CheckerInfo *info = m->info;
+	gbAllocator a = m->allocator;
+
+	if (v_zero == NULL) {
+		v_zero   = ssa_make_const_int (m->allocator, 0);
+		v_one    = ssa_make_const_int (m->allocator, 1);
+		v_zero32 = ssa_make_const_i32 (m->allocator, 0);
+		v_one32  = ssa_make_const_i32 (m->allocator, 1);
+		v_two32  = ssa_make_const_i32 (m->allocator, 2);
+		v_false  = ssa_make_const_bool(m->allocator, false);
+		v_true   = ssa_make_const_bool(m->allocator, true);
+	}
+
+	isize global_variable_max_count = 0;
+	Entity *entry_point = NULL;
+
+	for_array(i, info->entities.entries) {
+		MapDeclInfoEntry *entry = &info->entities.entries.e[i];
+		Entity *e = cast(Entity *)cast(uintptr)entry->key.key;
+		String name = e->token.string;
+		if (e->kind == Entity_Variable) {
+			global_variable_max_count++;
+		} else if (e->kind == Entity_Procedure) {
+			if (e->scope->is_init && str_eq(name, str_lit("main"))) {
+				entry_point = e;
+			}
+		}
+	}
+
+	typedef struct ssaGlobalVariable {
+		ssaValue *var, *init;
+		DeclInfo *decl;
+	} ssaGlobalVariable;
+	Array(ssaGlobalVariable) global_variables;
+	array_init_reserve(&global_variables, m->tmp_allocator, global_variable_max_count);
+
+	m->min_dep_map = generate_minimum_dependency_map(info, entry_point);
+
+	for_array(i, info->entities.entries) {
+		MapDeclInfoEntry *entry = &info->entities.entries.e[i];
+		Entity *e = cast(Entity *)entry->key.ptr;
+		String name = e->token.string;
+		DeclInfo *decl = entry->value;
+		Scope *scope = e->scope;
+
+		if (!scope->is_file) {
+			continue;
+		}
+
+		if (map_entity_get(&m->min_dep_map, hash_pointer(e)) == NULL) {
+			// NOTE(bill): Nothing depends upon it so doesn't need to be built
+			continue;
+		}
+
+		if (!scope->is_global && !scope->is_init) {
+			name = ssa_mangle_name(s, e->token.pos.file, name);
+		}
+
+
+		switch (e->kind) {
+		case Entity_TypeName:
+			GB_ASSERT(e->type->kind == Type_Named);
+			map_string_set(&m->type_names, hash_pointer(e->type), name);
+			ssa_gen_global_type_name(m, e, name);
+			break;
+
+		case Entity_Variable: {
+			ssaValue *g = ssa_make_value_global(a, e, NULL);
+			if (decl->var_decl_tags & VarDeclTag_thread_local) {
+				g->Global.is_thread_local = true;
+			}
+			ssaGlobalVariable var = {0};
+			var.var = g;
+			var.decl = decl;
+
+			if (decl->init_expr != NULL) {
+				TypeAndValue *tav = map_tav_get(&info->types, hash_pointer(decl->init_expr));
+				if (tav != NULL) {
+					if (tav->value.kind != ExactValue_Invalid) {
+						ExactValue v = tav->value;
+						// if (v.kind != ExactValue_String) {
+							g->Global.value = ssa_add_module_constant(m, tav->type, v);
+						// }
+					}
+				}
+			}
+
+			if (g->Global.value == NULL) {
+				array_add(&global_variables, var);
+			}
+
+			map_ssa_value_set(&m->values,  hash_pointer(e), g);
+			map_ssa_value_set(&m->members, hash_string(name), g);
+		} break;
+
+		case Entity_Procedure: {
+			AstNodeProcDecl *pd = &decl->proc_decl->ProcDecl;
+			String original_name = name;
+			AstNode *body = pd->body;
+			if (pd->tags & ProcTag_foreign) {
+				name = pd->name->Ident.string;
+			}
+			if (pd->foreign_name.len > 0) {
+				name = pd->foreign_name;
+			} else if (pd->link_name.len > 0) {
+				name = pd->link_name;
+			}
+
+			ssaValue *p = ssa_make_value_procedure(a, m, e, e->type, decl->type_expr, body, name);
+			p->Proc.tags = pd->tags;
+
+			map_ssa_value_set(&m->values, hash_pointer(e), p);
+			HashKey hash_name = hash_string(name);
+			if (map_ssa_value_get(&m->members, hash_name) == NULL) {
+				map_ssa_value_set(&m->members, hash_name, p);
+			}
+		} break;
+		}
+	}
+
+	for_array(i, m->members.entries) {
+		MapSsaValueEntry *entry = &m->members.entries.e[i];
+		ssaValue *v = entry->value;
+		if (v->kind == ssaValue_Proc)
+			ssa_build_proc(v, NULL);
+	}
+
+	ssaDebugInfo *compile_unit = m->debug_info.entries.e[0].value;
+	GB_ASSERT(compile_unit->kind == ssaDebugInfo_CompileUnit);
+	ssaDebugInfo *all_procs = ssa_alloc_debug_info(m->allocator, ssaDebugInfo_AllProcs);
+
+	isize all_proc_max_count = 0;
+	for_array(i, m->debug_info.entries) {
+		MapSsaDebugInfoEntry *entry = &m->debug_info.entries.e[i];
+		ssaDebugInfo *di = entry->value;
+		di->id = i;
+		if (di->kind == ssaDebugInfo_Proc) {
+			all_proc_max_count++;
+		}
+	}
+
+	array_init_reserve(&all_procs->AllProcs.procs, m->allocator, all_proc_max_count);
+	map_ssa_debug_info_set(&m->debug_info, hash_pointer(all_procs), all_procs); // NOTE(bill): This doesn't need to be mapped
+	compile_unit->CompileUnit.all_procs = all_procs;
+
+
+	for_array(i, m->debug_info.entries) {
+		MapSsaDebugInfoEntry *entry = &m->debug_info.entries.e[i];
+		ssaDebugInfo *di = entry->value;
+		di->id = i;
+		if (di->kind == ssaDebugInfo_Proc) {
+			array_add(&all_procs->AllProcs.procs, di);
+		}
+	}
+
+
+	{ // Startup Runtime
+		// Cleanup(bill): probably better way of doing code insertion
+		String name = str_lit(SSA_STARTUP_RUNTIME_PROC_NAME);
+		Type *proc_type = make_type_proc(a, gb_alloc_item(a, Scope),
+		                                 NULL, 0,
+		                                 NULL, 0, false);
+		AstNode *body = gb_alloc_item(a, AstNode);
+		ssaValue *p = ssa_make_value_procedure(a, m, NULL, proc_type, NULL, body, name);
+		Token token = {0};
+		token.string = name;
+		Entity *e = make_entity_procedure(a, NULL, token, proc_type);
+
+		map_ssa_value_set(&m->values, hash_pointer(e), p);
+		map_ssa_value_set(&m->members, hash_string(name), p);
+
+		ssaProcedure *proc = &p->Proc;
+		proc->tags = ProcTag_no_inline; // TODO(bill): is no_inline a good idea?
+
+		ssa_begin_procedure_body(proc);
+
+		// TODO(bill): Should do a dependency graph do check which order to initialize them in?
+		for_array(i, global_variables) {
+			ssaGlobalVariable *var = &global_variables.e[i];
+			if (var->decl->init_expr != NULL) {
+				var->init = ssa_build_expr(proc, var->decl->init_expr);
+			}
+		}
+
+		// NOTE(bill): Initialize constants first
+		for_array(i, global_variables) {
+			ssaGlobalVariable *var = &global_variables.e[i];
+			if (var->init != NULL) {
+				if (var->init->kind == ssaValue_Constant) {
+					ssa_emit_store(proc, var->var, var->init);
+				}
+			}
+		}
+
+		for_array(i, global_variables) {
+			ssaGlobalVariable *var = &global_variables.e[i];
+			if (var->init != NULL) {
+				if (var->init->kind != ssaValue_Constant) {
+					ssa_emit_store(proc, var->var, var->init);
+				}
+			}
+		}
+
+		{ // NOTE(bill): Setup type_info data
+			// TODO(bill): Try and make a lot of this constant aggregate literals in LLVM IR
+			ssaValue *type_info_data = NULL;
+			ssaValue *type_info_member_data = NULL;
+
+			ssaValue **found = NULL;
+			found = map_ssa_value_get(&proc->module->members, hash_string(str_lit(SSA_TYPE_INFO_DATA_NAME)));
+			GB_ASSERT(found != NULL);
+			type_info_data = *found;
+
+			found = map_ssa_value_get(&proc->module->members, hash_string(str_lit(SSA_TYPE_INFO_DATA_MEMBER_NAME)));
+			GB_ASSERT(found != NULL);
+			type_info_member_data = *found;
+
+			CheckerInfo *info = proc->module->info;
+
+			// Useful types
+			Type *t_i64_slice_ptr    = make_type_pointer(a, make_type_slice(a, t_i64));
+			Type *t_string_slice_ptr = make_type_pointer(a, make_type_slice(a, t_string));
+
+			i32 type_info_member_index = 0;
+
+			for_array(type_info_map_index, info->type_info_map.entries) {
+				MapIsizeEntry *entry = &info->type_info_map.entries.e[type_info_map_index];
+				Type *t = cast(Type *)cast(uintptr)entry->key.key;
+				t = default_type(t);
+				isize entry_index = entry->value;
+
+				ssaValue *tag = NULL;
+
+				switch (t->kind) {
+				case Type_Named: {
+					tag = ssa_add_local_generated(proc, t_type_info_named);
+
+					// TODO(bill): Which is better? The mangled name or actual name?
+					ssaValue *name = ssa_make_const_string(a, t->Named.type_name->token.string);
+					ssaValue *gtip = ssa_get_type_info_ptr(proc, type_info_data, t->Named.base);
+
+					ssa_emit_store(proc, ssa_emit_struct_ep(proc, tag, 0), name);
+					ssa_emit_store(proc, ssa_emit_struct_ep(proc, tag, 1), gtip);
+				} break;
+
+				case Type_Basic:
+					switch (t->Basic.kind) {
+					case Basic_bool:
+						tag = ssa_add_local_generated(proc, 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_int:
+					case Basic_uint: {
+						tag = ssa_add_local_generated(proc, t_type_info_integer);
+						bool is_unsigned = (t->Basic.flags & BasicFlag_Unsigned) != 0;
+						ssaValue *bits = ssa_make_const_int(a, type_size_of(m->sizes, a, t));
+						ssaValue *is_signed = ssa_make_const_bool(a, !is_unsigned);
+						ssa_emit_store(proc, ssa_emit_struct_ep(proc, tag, 0), bits);
+						ssa_emit_store(proc, ssa_emit_struct_ep(proc, tag, 1), is_signed);
+					} break;
+
+					// case Basic_f16:
+					case Basic_f32:
+					case Basic_f64:
+					// case Basic_f128:
+					{
+						tag = ssa_add_local_generated(proc, t_type_info_float);
+						ssaValue *bits = ssa_make_const_int(a, type_size_of(m->sizes, a, t));
+						ssa_emit_store(proc, ssa_emit_struct_ep(proc, tag, 0), bits);
+					} break;
+
+					case Basic_rawptr:
+						tag = ssa_add_local_generated(proc, t_type_info_pointer);
+						break;
+
+					case Basic_string:
+						tag = ssa_add_local_generated(proc, t_type_info_string);
+						break;
+
+					case Basic_any:
+						tag = ssa_add_local_generated(proc, t_type_info_any);
+						break;
+					}
+					break;
+
+				case Type_Pointer: {
+					tag = ssa_add_local_generated(proc, t_type_info_pointer);
+					ssaValue *gep = ssa_get_type_info_ptr(proc, type_info_data, t->Pointer.elem);
+					ssa_emit_store(proc, ssa_emit_struct_ep(proc, tag, 0), gep);
+				} break;
+				case Type_Maybe: {
+					tag = ssa_add_local_generated(proc, t_type_info_maybe);
+					ssaValue *gep = ssa_get_type_info_ptr(proc, type_info_data, t->Maybe.elem);
+					ssa_emit_store(proc, ssa_emit_struct_ep(proc, tag, 0), gep);
+				} break;
+				case Type_Array: {
+					tag = ssa_add_local_generated(proc, t_type_info_array);
+					ssaValue *gep = ssa_get_type_info_ptr(proc, type_info_data, t->Array.elem);
+					ssa_emit_store(proc, ssa_emit_struct_ep(proc, tag, 0), gep);
+
+					isize ez = type_size_of(m->sizes, a, t->Array.elem);
+					ssaValue *elem_size = ssa_emit_struct_ep(proc, tag, 1);
+					ssa_emit_store(proc, elem_size, ssa_make_const_int(a, ez));
+
+					ssaValue *count = ssa_emit_struct_ep(proc, tag, 2);
+					ssa_emit_store(proc, count, ssa_make_const_int(a, t->Array.count));
+
+				} break;
+				case Type_Slice: {
+					tag = ssa_add_local_generated(proc, t_type_info_slice);
+					ssaValue *gep = ssa_get_type_info_ptr(proc, type_info_data, t->Slice.elem);
+					ssa_emit_store(proc, ssa_emit_struct_ep(proc, tag, 0), gep);
+
+					isize ez = type_size_of(m->sizes, a, t->Slice.elem);
+					ssaValue *elem_size = ssa_emit_struct_ep(proc, tag, 1);
+					ssa_emit_store(proc, elem_size, ssa_make_const_int(a, ez));
+
+				} break;
+				case Type_Vector: {
+					tag = ssa_add_local_generated(proc, t_type_info_vector);
+					ssaValue *gep = ssa_get_type_info_ptr(proc, type_info_data, t->Vector.elem);
+					ssa_emit_store(proc, ssa_emit_struct_ep(proc, tag, 0), gep);
+
+					isize ez = type_size_of(m->sizes, a, t->Vector.elem);
+					ssaValue *elem_size = ssa_emit_struct_ep(proc, tag, 1);
+					ssa_emit_store(proc, elem_size, ssa_make_const_int(a, ez));
+
+					ssaValue *count = ssa_emit_struct_ep(proc, tag, 2);
+					ssa_emit_store(proc, count, ssa_make_const_int(a, t->Vector.count));
+
+					ssaValue *align = ssa_emit_struct_ep(proc, tag, 3);
+					ssa_emit_store(proc, count, ssa_make_const_int(a, type_align_of(m->sizes, a, t)));
+
+				} break;
+				case Type_Record: {
+					switch (t->Record.kind) {
+					case TypeRecord_Struct: {
+						tag = ssa_add_local_generated(proc, t_type_info_struct);
+
+						{
+							ssaValue *packed  = ssa_make_const_bool(a, t->Record.struct_is_packed);
+							ssaValue *ordered = ssa_make_const_bool(a, t->Record.struct_is_ordered);
+							ssaValue *size    = ssa_make_const_int(a, type_size_of(m->sizes, a, t));
+							ssaValue *align   = ssa_make_const_int(a, type_align_of(m->sizes, a, t));
+							ssa_emit_store(proc, ssa_emit_struct_ep(proc, tag, 1), size);
+							ssa_emit_store(proc, ssa_emit_struct_ep(proc, tag, 2), align);
+							ssa_emit_store(proc, ssa_emit_struct_ep(proc, tag, 3), packed);
+							ssa_emit_store(proc, ssa_emit_struct_ep(proc, tag, 4), ordered);
+						}
+
+						ssaValue *memory = ssa_type_info_member_offset(proc, type_info_member_data, t->Record.field_count, &type_info_member_index);
+
+						type_set_offsets(m->sizes, a, t); // NOTE(bill): Just incase the offsets have not been set yet
+						for (isize source_index = 0; source_index < t->Record.field_count; source_index++) {
+							// TODO(bill): Order fields in source order not layout order
+							Entity *f = t->Record.fields_in_src_order[source_index];
+							ssaValue *tip = ssa_get_type_info_ptr(proc, type_info_data, f->type);
+							i64 foffset = t->Record.struct_offsets[f->Variable.field_index];
+							GB_ASSERT(f->kind == Entity_Variable && f->flags & EntityFlag_Field);
+
+							ssaValue *field     = ssa_emit_ptr_offset(proc, memory, ssa_make_const_int(a, source_index));
+							ssaValue *name      = ssa_emit_struct_ep(proc, field, 0);
+							ssaValue *type_info = ssa_emit_struct_ep(proc, field, 1);
+							ssaValue *offset    = ssa_emit_struct_ep(proc, field, 2);
+
+							if (f->token.string.len > 0) {
+								ssa_emit_store(proc, name, ssa_make_const_string(a, f->token.string));
+							}
+							ssa_emit_store(proc, type_info, tip);
+							ssa_emit_store(proc, offset, ssa_make_const_int(a, foffset));
+						}
+
+						Type *slice_type = make_type_slice(a, t_type_info_member);
+						Type *slice_type_ptr = make_type_pointer(a, slice_type);
+						ssaValue *slice = ssa_emit_struct_ep(proc, tag, 0);
+						ssaValue *field_count = ssa_make_const_int(a, t->Record.field_count);
+
+						ssaValue *elem = ssa_emit_struct_ep(proc, slice, 0);
+						ssaValue *len  = ssa_emit_struct_ep(proc, slice, 1);
+						ssaValue *cap  = ssa_emit_struct_ep(proc, slice, 2);
+
+						ssa_emit_store(proc, elem, memory);
+						ssa_emit_store(proc, len, field_count);
+						ssa_emit_store(proc, cap, field_count);
+					} break;
+					case TypeRecord_Union:
+						tag = ssa_add_local_generated(proc, t_type_info_union);
+						{
+							ssaValue *size    = ssa_make_const_int(a, type_size_of(m->sizes, a, t));
+							ssaValue *align   = ssa_make_const_int(a, type_align_of(m->sizes, a, t));
+							ssa_emit_store(proc, ssa_emit_struct_ep(proc, tag, 1),  size);
+							ssa_emit_store(proc, ssa_emit_struct_ep(proc, tag, 2),  align);
+						}
+						break;
+					case TypeRecord_RawUnion: {
+						tag = ssa_add_local_generated(proc, t_type_info_raw_union);
+						{
+							ssaValue *size    = ssa_make_const_int(a, type_size_of(m->sizes, a, t));
+							ssaValue *align   = ssa_make_const_int(a, type_align_of(m->sizes, a, t));
+							ssa_emit_store(proc, ssa_emit_struct_ep(proc, tag, 1),  size);
+							ssa_emit_store(proc, ssa_emit_struct_ep(proc, tag, 2),  align);
+						}
+
+						ssaValue *memory = ssa_type_info_member_offset(proc, type_info_member_data, t->Record.field_count, &type_info_member_index);
+
+						for (isize i = 0; i < t->Record.field_count; i++) {
+							ssaValue *field     = ssa_emit_ptr_offset(proc, memory, ssa_make_const_int(a, i));
+							ssaValue *name      = ssa_emit_struct_ep(proc, field, 0);
+							ssaValue *type_info = ssa_emit_struct_ep(proc, field, 1);
+							ssaValue *offset    = ssa_emit_struct_ep(proc, field, 2);
+
+							Entity *f = t->Record.fields[i];
+							ssaValue *tip = ssa_get_type_info_ptr(proc, type_info_data, f->type);
+
+							if (f->token.string.len > 0) {
+								ssa_emit_store(proc, name, ssa_make_const_string(a, f->token.string));
+							}
+							ssa_emit_store(proc, type_info, tip);
+							ssa_emit_store(proc, offset, ssa_make_const_int(a, 0));
+						}
+
+						Type *slice_type = make_type_slice(a, t_type_info_member);
+						Type *slice_type_ptr = make_type_pointer(a, slice_type);
+						ssaValue *slice = ssa_emit_struct_ep(proc, tag, 0);
+						ssaValue *field_count = ssa_make_const_int(a, t->Record.field_count);
+
+						ssaValue *elem = ssa_emit_struct_ep(proc, slice, 0);
+						ssaValue *len  = ssa_emit_struct_ep(proc, slice, 1);
+						ssaValue *cap  = ssa_emit_struct_ep(proc, slice, 2);
+
+						ssa_emit_store(proc, elem, memory);
+						ssa_emit_store(proc, len, field_count);
+						ssa_emit_store(proc, cap, field_count);
+					} break;
+					case TypeRecord_Enum: {
+						tag = ssa_add_local_generated(proc, t_type_info_enum);
+						Type *enum_base = t->Record.enum_base;
+						if (enum_base == NULL) {
+							enum_base = t_int;
+						}
+						ssaValue *base = ssa_emit_struct_ep(proc, tag, 0);
+						ssa_emit_store(proc, base, ssa_get_type_info_ptr(proc, type_info_data, enum_base));
+
+						if (t->Record.other_field_count > 0) {
+							Entity **fields = t->Record.other_fields;
+							isize count = t->Record.other_field_count;
+							ssaValue *value_array = NULL;
+							ssaValue *name_array = NULL;
+
+
+							{
+								Token token = {Token_Identifier};
+								i32 id = cast(i32)entry_index;
+								char name_base[] = "__$enum_values";
+								isize name_len = gb_size_of(name_base) + 10;
+								token.string.text = gb_alloc_array(a, u8, name_len);
+								token.string.len = gb_snprintf(cast(char *)token.string.text, name_len,
+								                               "%s-%d", name_base, id)-1;
+								Entity *e = make_entity_variable(a, NULL, token, make_type_array(a, t_i64, count));
+								value_array = ssa_make_value_global(a, e, NULL);
+								value_array->Global.is_private = true;
+								ssa_module_add_value(m, e, value_array);
+								map_ssa_value_set(&m->members, hash_string(token.string), value_array);
+							}
+							{
+								Token token = {Token_Identifier};
+								i32 id = cast(i32)entry_index;
+								char name_base[] = "__$enum_names";
+								isize name_len = gb_size_of(name_base) + 10;
+								token.string.text = gb_alloc_array(a, u8, name_len);
+								token.string.len = gb_snprintf(cast(char *)token.string.text, name_len,
+								                               "%s-%d", name_base, id)-1;
+								Entity *e = make_entity_variable(a, NULL, token, make_type_array(a, t_string, count));
+								name_array = ssa_make_value_global(a, e, NULL);
+								name_array->Global.is_private = true;
+								ssa_module_add_value(m, e, name_array);
+								map_ssa_value_set(&m->members, hash_string(token.string), name_array);
+							}
+
+							for (isize i = 0; i < count; i++) {
+								ssaValue *value_gep = ssa_emit_array_epi(proc, value_array, i);
+								ssaValue *name_gep  = ssa_emit_array_epi(proc, name_array, i);
+
+								ssa_emit_store(proc, value_gep, ssa_make_const_i64(a, fields[i]->Constant.value.value_integer));
+								ssa_emit_store(proc, name_gep,  ssa_make_const_string(a, fields[i]->token.string));
+							}
+
+							ssaValue *v_count = ssa_make_const_int(a, count);
+
+
+							ssaValue *values = ssa_emit_struct_ep(proc, tag, 1);
+							ssaValue *names  = ssa_emit_struct_ep(proc, tag, 2);
+							ssaValue *value_slice = ssa_add_local_generated(proc, type_deref(t_i64_slice_ptr));
+							ssaValue *name_slice  = ssa_add_local_generated(proc, type_deref(t_string_slice_ptr));
+
+							ssa_emit_store(proc, ssa_emit_struct_ep(proc, value_slice, 0), ssa_array_elem(proc, value_array));
+							ssa_emit_store(proc, ssa_emit_struct_ep(proc, value_slice, 1), v_count);
+							ssa_emit_store(proc, ssa_emit_struct_ep(proc, value_slice, 2), v_count);
+
+							ssa_emit_store(proc, ssa_emit_struct_ep(proc, name_slice, 0), ssa_array_elem(proc, name_array));
+							ssa_emit_store(proc, ssa_emit_struct_ep(proc, name_slice, 1), v_count);
+							ssa_emit_store(proc, ssa_emit_struct_ep(proc, name_slice, 2), v_count);
+
+							ssa_emit_store(proc, values, ssa_emit_load(proc, value_slice));
+							ssa_emit_store(proc, names,  ssa_emit_load(proc, name_slice));
+						}
+					} break;
+					}
+				} break;
+
+				case Type_Tuple: {
+					tag = ssa_add_local_generated(proc, t_type_info_tuple);
+
+					{
+						ssaValue *align = ssa_make_const_int(a, type_align_of(m->sizes, a, t));
+						ssa_emit_store(proc, ssa_emit_struct_ep(proc, tag, 2), align);
+					}
+
+					ssaValue *memory = ssa_type_info_member_offset(proc, type_info_member_data, t->Tuple.variable_count, &type_info_member_index);
+
+					for (isize i = 0; i < t->Tuple.variable_count; i++) {
+						ssaValue *field     = ssa_emit_ptr_offset(proc, memory, ssa_make_const_int(a, i));
+						ssaValue *name      = ssa_emit_struct_ep(proc, field, 0);
+						ssaValue *type_info = ssa_emit_struct_ep(proc, field, 1);
+						// NOTE(bill): offset is not used for tuples
+
+						Entity *f = t->Tuple.variables[i];
+						ssaValue *tip = ssa_get_type_info_ptr(proc, type_info_data, f->type);
+
+						if (f->token.string.len > 0) {
+							ssa_emit_store(proc, name, ssa_make_const_string(a, f->token.string));
+						}
+						ssa_emit_store(proc, type_info, tip);
+					}
+
+					Type *slice_type = make_type_slice(a, t_type_info_member);
+					Type *slice_type_ptr = make_type_pointer(a, slice_type);
+					ssaValue *slice = ssa_emit_struct_ep(proc, tag, 0);
+					ssaValue *variable_count = ssa_make_const_int(a, t->Tuple.variable_count);
+
+					ssaValue *elem = ssa_emit_struct_ep(proc, slice, 0);
+					ssaValue *len  = ssa_emit_struct_ep(proc, slice, 1);
+					ssaValue *cap  = ssa_emit_struct_ep(proc, slice, 2);
+
+					ssa_emit_store(proc, elem, memory);
+					ssa_emit_store(proc, len, variable_count);
+					ssa_emit_store(proc, cap, variable_count);
+				} break;
+
+				case Type_Proc: {
+					tag = ssa_add_local_generated(proc, t_type_info_procedure);
+
+					ssaValue *params   = ssa_emit_struct_ep(proc, tag, 0);
+					ssaValue *results  = ssa_emit_struct_ep(proc, tag, 1);
+					ssaValue *variadic = ssa_emit_struct_ep(proc, tag, 2);
+
+					if (t->Proc.params) {
+						ssa_emit_store(proc, params, ssa_get_type_info_ptr(proc, type_info_data, t->Proc.params));
+					}
+					if (t->Proc.results) {
+						ssa_emit_store(proc, results, ssa_get_type_info_ptr(proc, type_info_data, t->Proc.results));
+					}
+					ssa_emit_store(proc, variadic, ssa_make_const_bool(a, t->Proc.variadic));
+
+					// TODO(bill): Type_Info for procedures
+				} break;
+				}
+
+				if (tag != NULL) {
+					ssaValue *gep = ssa_emit_array_epi(proc, type_info_data, entry_index);
+					ssaValue *val = ssa_emit_conv(proc, ssa_emit_load(proc, tag), t_type_info);
+					ssa_emit_store(proc, gep, val);
+				}
+			}
+		}
+
+		ssa_end_procedure_body(proc);
+	}
+
+	for_array(i, m->procs_to_generate) {
+		ssa_build_proc(m->procs_to_generate.e[i], m->procs_to_generate.e[i]->Proc.parent);
+	}
+
+	// {
+	// 	DWORD old_protect = 0;
+	// 	DWORD new_protect = PAGE_READONLY;
+	// 	BOOL ok = VirtualProtect(m->arena.physical_start, m->arena.total_size, new_protect, &old_protect);
+	// }
+
+
+
+	// m->layout = str_lit("e-p:64:64:64-i1:8:8-i8:8:8-i16:16:16-i32:32:32-i64:64:64-f32:32:32-f64:64:64-v64:64:64-v128:128:128-a0:0:64-s0:64:64-f80:128:128-n8:16:32:64");
+}
+

+ 493 - 0
src/ssa_opt.c

@@ -0,0 +1,493 @@
+// Optimizations for the SSA code
+
+void ssa_opt_add_operands(ssaValueArray *ops, ssaInstr *i) {
+	switch (i->kind) {
+	case ssaInstr_Comment:
+		break;
+	case ssaInstr_Local:
+		break;
+	case ssaInstr_ZeroInit:
+		array_add(ops, i->ZeroInit.address);
+		break;
+	case ssaInstr_Store:
+		array_add(ops, i->Store.address);
+		array_add(ops, i->Store.value);
+		break;
+	case ssaInstr_Load:
+		array_add(ops, i->Load.address);
+		break;
+	case ssaInstr_ArrayElementPtr:
+		array_add(ops, i->ArrayElementPtr.address);
+		array_add(ops, i->ArrayElementPtr.elem_index);
+		break;
+	case ssaInstr_StructElementPtr:
+		array_add(ops, i->StructElementPtr.address);
+		break;
+	case ssaInstr_PtrOffset:
+		array_add(ops, i->PtrOffset.address);
+		array_add(ops, i->PtrOffset.offset);
+		break;
+	case ssaInstr_ArrayExtractValue:
+		array_add(ops, i->ArrayExtractValue.address);
+		break;
+	case ssaInstr_StructExtractValue:
+		array_add(ops, i->StructExtractValue.address);
+		break;
+	case ssaInstr_Conv:
+		array_add(ops, i->Conv.value);
+		break;
+	case ssaInstr_Jump:
+		break;
+	case ssaInstr_If:
+		array_add(ops, i->If.cond);
+		break;
+	case ssaInstr_Return:
+		if (i->Return.value != NULL) {
+			array_add(ops, i->Return.value);
+		}
+		break;
+	case ssaInstr_Select:
+		array_add(ops, i->Select.cond);
+		break;
+	case ssaInstr_Phi:
+		for_array(j, i->Phi.edges) {
+			array_add(ops, i->Phi.edges.e[j]);
+		}
+		break;
+	case ssaInstr_Unreachable: break;
+	case ssaInstr_BinaryOp:
+		array_add(ops, i->BinaryOp.left);
+		array_add(ops, i->BinaryOp.right);
+		break;
+	case ssaInstr_Call:
+		array_add(ops, i->Call.value);
+		for (isize j = 0; j < i->Call.arg_count; j++) {
+			array_add(ops, i->Call.args[j]);
+		}
+		break;
+	case ssaInstr_VectorExtractElement:
+		array_add(ops, i->VectorExtractElement.vector);
+		array_add(ops, i->VectorExtractElement.index);
+		break;
+	case ssaInstr_VectorInsertElement:
+		array_add(ops, i->VectorInsertElement.vector);
+		array_add(ops, i->VectorInsertElement.elem);
+		array_add(ops, i->VectorInsertElement.index);
+		break;
+	case ssaInstr_VectorShuffle:
+		array_add(ops, i->VectorShuffle.vector);
+		break;
+	case ssaInstr_StartupRuntime:
+		break;
+	case ssaInstr_BoundsCheck:
+		array_add(ops, i->BoundsCheck.index);
+		array_add(ops, i->BoundsCheck.len);
+		break;
+	case ssaInstr_SliceBoundsCheck:
+		array_add(ops, i->SliceBoundsCheck.low);
+		array_add(ops, i->SliceBoundsCheck.high);
+		array_add(ops, i->SliceBoundsCheck.max);
+		break;
+
+
+	}
+}
+
+
+
+
+
+void ssa_opt_block_replace_pred(ssaBlock *b, ssaBlock *from, ssaBlock *to) {
+	for_array(i, b->preds) {
+		ssaBlock *pred = b->preds.e[i];
+		if (pred == from) {
+			b->preds.e[i] = to;
+		}
+	}
+}
+
+void ssa_opt_block_replace_succ(ssaBlock *b, ssaBlock *from, ssaBlock *to) {
+	for_array(i, b->succs) {
+		ssaBlock *succ = b->succs.e[i];
+		if (succ == from) {
+			b->succs.e[i] = to;
+		}
+	}
+}
+
+bool ssa_opt_block_has_phi(ssaBlock *b) {
+	return b->instrs.e[0]->Instr.kind == ssaInstr_Phi;
+}
+
+
+
+
+
+
+
+
+
+
+ssaValueArray ssa_get_block_phi_nodes(ssaBlock *b) {
+	ssaValueArray phis = {0};
+	for_array(i, b->instrs) {
+		ssaInstr *instr = &b->instrs.e[i]->Instr;
+		if (instr->kind != ssaInstr_Phi) {
+			phis = b->instrs;
+			phis.count = i;
+			return phis;
+		}
+	}
+	return phis;
+}
+
+void ssa_remove_pred(ssaBlock *b, ssaBlock *p) {
+	ssaValueArray phis = ssa_get_block_phi_nodes(b);
+	isize i = 0;
+	for_array(j, b->preds) {
+		ssaBlock *pred = b->preds.e[j];
+		if (pred != p) {
+			b->preds.e[i] = b->preds.e[j];
+			for_array(k, phis) {
+				ssaInstrPhi *phi = &phis.e[k]->Instr.Phi;
+				phi->edges.e[i] = phi->edges.e[j];
+			}
+			i++;
+		}
+	}
+	b->preds.count = i;
+	for_array(k, phis) {
+		ssaInstrPhi *phi = &phis.e[k]->Instr.Phi;
+		phi->edges.count = i;
+	}
+
+}
+
+void ssa_remove_dead_blocks(ssaProcedure *proc) {
+	isize j = 0;
+	for_array(i, proc->blocks) {
+		ssaBlock *b = proc->blocks.e[i];
+		if (b == NULL) {
+			continue;
+		}
+		// NOTE(bill): Swap order
+		b->index = j;
+		proc->blocks.e[j++] = b;
+	}
+	proc->blocks.count = j;
+}
+
+void ssa_mark_reachable(ssaBlock *b) {
+	isize const WHITE =  0;
+	isize const BLACK = -1;
+	b->index = BLACK;
+	for_array(i, b->succs) {
+		ssaBlock *succ = b->succs.e[i];
+		if (succ->index == WHITE) {
+			ssa_mark_reachable(succ);
+		}
+	}
+}
+
+void ssa_remove_unreachable_blocks(ssaProcedure *proc) {
+	isize const WHITE =  0;
+	isize const BLACK = -1;
+	for_array(i, proc->blocks) {
+		proc->blocks.e[i]->index = WHITE;
+	}
+
+	ssa_mark_reachable(proc->blocks.e[0]);
+
+	for_array(i, proc->blocks) {
+		ssaBlock *b = proc->blocks.e[i];
+		if (b->index == WHITE) {
+			for_array(j, b->succs) {
+				ssaBlock *c = b->succs.e[j];
+				if (c->index == BLACK) {
+					ssa_remove_pred(c, b);
+				}
+			}
+			// NOTE(bill): Mark as empty but don't actually free it
+			// As it's been allocated with an arena
+			proc->blocks.e[i] = NULL;
+		}
+	}
+	ssa_remove_dead_blocks(proc);
+}
+
+bool ssa_opt_block_fusion(ssaProcedure *proc, ssaBlock *a) {
+	if (a->succs.count != 1) {
+		return false;
+	}
+	ssaBlock *b = a->succs.e[0];
+	if (b->preds.count != 1) {
+		return false;
+	}
+
+	if (ssa_opt_block_has_phi(b)) {
+		return false;
+	}
+
+	array_pop(&a->instrs); // Remove branch at end
+	for_array(i, b->instrs) {
+		array_add(&a->instrs, b->instrs.e[i]);
+		ssa_set_instr_parent(b->instrs.e[i], a);
+	}
+
+	array_clear(&a->succs);
+	for_array(i, b->succs) {
+		array_add(&a->succs, b->succs.e[i]);
+	}
+
+	// Fix preds links
+	for_array(i, b->succs) {
+		ssa_opt_block_replace_pred(b->succs.e[i], b, a);
+	}
+
+	proc->blocks.e[b->index] = NULL;
+	return true;
+}
+
+void ssa_opt_blocks(ssaProcedure *proc) {
+	ssa_remove_unreachable_blocks(proc);
+
+#if 1
+	bool changed = true;
+	while (changed) {
+		changed = false;
+		for_array(i, proc->blocks) {
+			ssaBlock *b = proc->blocks.e[i];
+			if (b == NULL) {
+				continue;
+			}
+			GB_ASSERT(b->index == i);
+
+			if (ssa_opt_block_fusion(proc, b)) {
+				changed = true;
+			}
+			// TODO(bill): other simple block optimizations
+		}
+	}
+#endif
+
+	ssa_remove_dead_blocks(proc);
+}
+void ssa_opt_build_referrers(ssaProcedure *proc) {
+	gbTempArenaMemory tmp = gb_temp_arena_memory_begin(&proc->module->tmp_arena);
+
+	ssaValueArray ops = {0}; // NOTE(bill): Act as a buffer
+	array_init_reserve(&ops, proc->module->tmp_allocator, 64); // HACK(bill): This _could_ overflow the temp arena
+	for_array(i, proc->blocks) {
+		ssaBlock *b = proc->blocks.e[i];
+		for_array(j, b->instrs) {
+			ssaValue *instr = b->instrs.e[j];
+			array_clear(&ops);
+			ssa_opt_add_operands(&ops, &instr->Instr);
+			for_array(k, ops) {
+				ssaValue *op = ops.e[k];
+				if (op == NULL) {
+					continue;
+				}
+				ssaValueArray *refs = ssa_value_referrers(op);
+				if (refs != NULL) {
+					array_add(refs, instr);
+				}
+			}
+		}
+	}
+
+	gb_temp_arena_memory_end(tmp);
+}
+
+
+
+
+
+
+
+// State of Lengauer-Tarjan algorithm
+// Based on this paper: http://jgaa.info/accepted/2006/GeorgiadisTarjanWerneck2006.10.1.pdf
+typedef struct ssaLTState {
+	isize count;
+	// NOTE(bill): These are arrays
+	ssaBlock **sdom;     // Semidominator
+	ssaBlock **parent;   // Parent in DFS traversal of CFG
+	ssaBlock **ancestor;
+} ssaLTState;
+
+// §2.2 - bottom of page
+void ssa_lt_link(ssaLTState *lt, ssaBlock *p, ssaBlock *q) {
+	lt->ancestor[q->index] = p;
+}
+
+i32 ssa_lt_depth_first_search(ssaLTState *lt, ssaBlock *p, i32 i, ssaBlock **preorder) {
+	preorder[i] = p;
+	p->dom.pre = i++;
+	lt->sdom[p->index] = p;
+	ssa_lt_link(lt, NULL, p);
+	for_array(index, p->succs) {
+		ssaBlock *q = p->succs.e[index];
+		if (lt->sdom[q->index] == NULL) {
+			lt->parent[q->index] = p;
+			i = ssa_lt_depth_first_search(lt, q, i, preorder);
+		}
+	}
+	return i;
+}
+
+ssaBlock *ssa_lt_eval(ssaLTState *lt, ssaBlock *v) {
+	ssaBlock *u = v;
+	for (;
+	     lt->ancestor[v->index] != NULL;
+	     v = lt->ancestor[v->index]) {
+		if (lt->sdom[v->index]->dom.pre < lt->sdom[u->index]->dom.pre) {
+			u = v;
+		}
+	}
+	return u;
+}
+
+typedef struct ssaDomPrePost {
+	i32 pre, post;
+} ssaDomPrePost;
+
+ssaDomPrePost ssa_opt_number_dom_tree(ssaBlock *v, i32 pre, i32 post) {
+	ssaDomPrePost result = {pre, post};
+
+	v->dom.pre = pre++;
+	for_array(i, v->dom.children) {
+		result = ssa_opt_number_dom_tree(v->dom.children.e[i], result.pre, result.post);
+	}
+	v->dom.post = post++;
+
+	result.pre  = pre;
+	result.post = post;
+	return result;
+}
+
+
+// NOTE(bill): Requires `ssa_opt_blocks` to be called before this
+void ssa_opt_build_dom_tree(ssaProcedure *proc) {
+	// Based on this paper: http://jgaa.info/accepted/2006/GeorgiadisTarjanWerneck2006.10.1.pdf
+
+	gbTempArenaMemory tmp = gb_temp_arena_memory_begin(&proc->module->tmp_arena);
+
+	isize n = proc->blocks.count;
+	ssaBlock **buf = gb_alloc_array(proc->module->tmp_allocator, ssaBlock *, 5*n);
+
+	ssaLTState lt = {0};
+	lt.count    = n;
+	lt.sdom     = &buf[0*n];
+	lt.parent   = &buf[1*n];
+	lt.ancestor = &buf[2*n];
+
+	ssaBlock **preorder = &buf[3*n];
+	ssaBlock **buckets  = &buf[4*n];
+	ssaBlock *root = proc->blocks.e[0];
+
+	// Step 1 - number vertices
+	i32 pre_num = ssa_lt_depth_first_search(&lt, root, 0, preorder);
+	gb_memmove(buckets, preorder, n*gb_size_of(preorder[0]));
+
+	for (i32 i = n-1; i > 0; i--) {
+		ssaBlock *w = preorder[i];
+
+		// Step 3 - Implicitly define idom for nodes
+		for (ssaBlock *v = buckets[i]; v != w; v = buckets[v->dom.pre]) {
+			ssaBlock *u = ssa_lt_eval(&lt, v);
+			if (lt.sdom[u->index]->dom.pre < i) {
+				v->dom.idom = u;
+			} else {
+				v->dom.idom = w;
+			}
+		}
+
+		// Step 2 - Compute all sdoms
+		lt.sdom[w->index] = lt.parent[w->index];
+		for_array(pred_index, w->preds) {
+			ssaBlock *v = w->preds.e[pred_index];
+			ssaBlock *u = ssa_lt_eval(&lt, v);
+			if (lt.sdom[u->index]->dom.pre < lt.sdom[w->index]->dom.pre) {
+				lt.sdom[w->index] = lt.sdom[u->index];
+			}
+		}
+
+		ssa_lt_link(&lt, lt.parent[w->index], w);
+
+		if (lt.parent[w->index] == lt.sdom[w->index]) {
+			w->dom.idom = lt.parent[w->index];
+		} else {
+			buckets[i] = buckets[lt.sdom[w->index]->dom.pre];
+			buckets[lt.sdom[w->index]->dom.pre] = w;
+		}
+	}
+
+	// The rest of Step 3
+	for (ssaBlock *v = buckets[0]; v != root; v = buckets[v->dom.pre]) {
+		v->dom.idom = root;
+	}
+
+	// Step 4 - Explicitly define idom for nodes (in preorder)
+	for (isize i = 1; i < n; i++) {
+		ssaBlock *w = preorder[i];
+		if (w == root) {
+			w->dom.idom = NULL;
+		} else {
+			// Weird tree relationships here!
+
+			if (w->dom.idom != lt.sdom[w->index]) {
+				w->dom.idom = w->dom.idom->dom.idom;
+			}
+
+			// Calculate children relation as inverse of idom
+			if (w->dom.idom->dom.children.e == NULL) {
+				// TODO(bill): Is this good enough for memory allocations?
+				array_init(&w->dom.idom->dom.children, heap_allocator());
+			}
+			array_add(&w->dom.idom->dom.children, w);
+		}
+	}
+
+	ssa_opt_number_dom_tree(root, 0, 0);
+
+	gb_temp_arena_memory_end(tmp);
+}
+
+void ssa_opt_mem2reg(ssaProcedure *proc) {
+	// TODO(bill): ssa_opt_mem2reg
+}
+
+
+
+void ssa_opt_tree(ssaGen *s) {
+	s->opt_called = true;
+
+	for_array(member_index, s->module.procs) {
+		ssaProcedure *proc = s->module.procs.e[member_index];
+		if (proc->blocks.count == 0) { // Prototype/external procedure
+			continue;
+		}
+
+		ssa_opt_blocks(proc);
+	#if 1
+		ssa_opt_build_referrers(proc);
+		ssa_opt_build_dom_tree(proc);
+
+		// TODO(bill): ssa optimization
+		// [ ] cse (common-subexpression) elim
+		// [ ] copy elim
+		// [ ] dead code elim
+		// [ ] dead store/load elim
+		// [ ] phi elim
+		// [ ] short circuit elim
+		// [ ] bounds check elim
+		// [ ] lift/mem2reg
+		// [ ] lift/mem2reg
+
+		ssa_opt_mem2reg(proc);
+	#endif
+
+		GB_ASSERT(proc->blocks.count > 0);
+		ssa_number_proc_registers(proc);
+	}
+}

+ 1439 - 0
src/ssa_print.c

@@ -0,0 +1,1439 @@
+typedef struct ssaFileBuffer {
+	gbVirtualMemory vm;
+	isize           offset;
+	gbFile *        output;
+} ssaFileBuffer;
+
+void ssa_file_buffer_init(ssaFileBuffer *f, gbFile *output) {
+	isize size = 8*gb_virtual_memory_page_size(NULL);
+	f->vm = gb_vm_alloc(NULL, size);
+	f->offset = 0;
+	f->output = output;
+}
+
+void ssa_file_buffer_destroy(ssaFileBuffer *f) {
+	if (f->offset > 0) {
+		// NOTE(bill): finish writing buffered data
+		gb_file_write(f->output, f->vm.data, f->offset);
+	}
+
+	gb_vm_free(f->vm);
+}
+
+void ssa_file_buffer_write(ssaFileBuffer *f, void *data, isize len) {
+	if (len > f->vm.size) {
+		gb_file_write(f->output, data, len);
+		return;
+	}
+
+	if ((f->vm.size - f->offset) < len) {
+		gb_file_write(f->output, f->vm.data, f->offset);
+		f->offset = 0;
+	}
+	u8 *cursor = cast(u8 *)f->vm.data + f->offset;
+	gb_memmove(cursor, data, len);
+	f->offset += len;
+}
+
+
+void ssa_fprintf(ssaFileBuffer *f, char *fmt, ...) {
+	va_list va;
+	va_start(va, fmt);
+	char buf[4096] = {0};
+	isize len = gb_snprintf_va(buf, gb_size_of(buf), fmt, va);
+	ssa_file_buffer_write(f, buf, len-1);
+	va_end(va);
+}
+
+
+void ssa_file_write(ssaFileBuffer *f, void *data, isize len) {
+	ssa_file_buffer_write(f, data, len);
+}
+
+
+bool ssa_valid_char(u8 c) {
+	if (c >= 0x80) {
+		return false;
+	}
+
+	if (gb_char_is_alphanumeric(c)) {
+		return true;
+	}
+
+	switch (c) {
+	case '$':
+	case '-':
+	case '.':
+	case '_':
+		return true;
+	}
+
+	return false;
+}
+
+void ssa_print_escape_string(ssaFileBuffer *f, String name, bool print_quotes) {
+	isize extra = 0;
+	for (isize i = 0; i < name.len; i++) {
+		u8 c = name.text[i];
+		if (!ssa_valid_char(c)) {
+			extra += 2;
+		}
+	}
+
+	if (extra == 0) {
+		ssa_fprintf(f, "%.*s", LIT(name));
+		return;
+	}
+
+
+	char hex_table[] = "0123456789ABCDEF";
+	isize buf_len = name.len + extra + 2;
+
+	gbTempArenaMemory tmp = gb_temp_arena_memory_begin(&string_buffer_arena);
+
+	u8 *buf = gb_alloc_array(string_buffer_allocator, u8, buf_len);
+
+	isize j = 0;
+
+	if (print_quotes) {
+		buf[j++] = '"';
+	}
+
+	for (isize i = 0; i < name.len; i++) {
+		u8 c = name.text[i];
+		if (ssa_valid_char(c)) {
+			buf[j++] = c;
+		} else {
+			buf[j] = '\\';
+			buf[j+1] = hex_table[c >> 4];
+			buf[j+2] = hex_table[c & 0x0f];
+			j += 3;
+		}
+	}
+
+	if (print_quotes) {
+		buf[j++] = '"';
+	}
+
+	ssa_file_write(f, buf, j);
+
+	gb_temp_arena_memory_end(tmp);
+}
+
+
+
+void ssa_print_encoded_local(ssaFileBuffer *f, String name) {
+	ssa_fprintf(f, "%%");
+	ssa_print_escape_string(f, name, true);
+}
+
+void ssa_print_encoded_global(ssaFileBuffer *f, String name, bool global_scope) {
+	ssa_fprintf(f, "@");
+	if (!global_scope && str_ne(name, str_lit("main"))) {
+		ssa_fprintf(f, ".");
+	}
+	ssa_print_escape_string(f, name, true);
+}
+
+
+void ssa_print_type(ssaFileBuffer *f, ssaModule *m, Type *t) {
+	BaseTypeSizes s = m->sizes;
+	i64 word_bits = 8*s.word_size;
+	GB_ASSERT_NOT_NULL(t);
+	t = default_type(t);
+
+	switch (t->kind) {
+	case Type_Basic:
+		switch (t->Basic.kind) {
+		case Basic_bool:   ssa_fprintf(f, "i1");                      break;
+		case Basic_i8:     ssa_fprintf(f, "i8");                      break;
+		case Basic_u8:     ssa_fprintf(f, "i8");                      break;
+		case Basic_i16:    ssa_fprintf(f, "i16");                     break;
+		case Basic_u16:    ssa_fprintf(f, "i16");                     break;
+		case Basic_i32:    ssa_fprintf(f, "i32");                     break;
+		case Basic_u32:    ssa_fprintf(f, "i32");                     break;
+		case Basic_i64:    ssa_fprintf(f, "i64");                     break;
+		case Basic_u64:    ssa_fprintf(f, "i64");                     break;
+		case Basic_i128:   ssa_fprintf(f, "i128");                    break;
+		case Basic_u128:   ssa_fprintf(f, "i128");                    break;
+		// case Basic_f16:    ssa_fprintf(f, "half");                    break;
+		case Basic_f32:    ssa_fprintf(f, "float");                   break;
+		case Basic_f64:    ssa_fprintf(f, "double");                  break;
+		// case Basic_f128:   ssa_fprintf(f, "fp128");                   break;
+		case Basic_rawptr: ssa_fprintf(f, "%%..rawptr");              break;
+		case Basic_string: ssa_fprintf(f, "%%..string");              break;
+		case Basic_uint:   ssa_fprintf(f, "i%lld", word_bits);        break;
+		case Basic_int:    ssa_fprintf(f, "i%lld", word_bits);        break;
+		case Basic_any:    ssa_fprintf(f, "%%..any");                 break;
+		}
+		break;
+	case Type_Pointer:
+		ssa_print_type(f, m, t->Pointer.elem);
+		ssa_fprintf(f, "*");
+		break;
+	case Type_Maybe:
+		ssa_fprintf(f, "{");
+		ssa_print_type(f, m, t->Maybe.elem);
+		ssa_fprintf(f, ", ");
+		ssa_print_type(f, m, t_bool);
+		ssa_fprintf(f, "}");
+		break;
+	case Type_Array:
+		ssa_fprintf(f, "[%lld x ", t->Array.count);
+		ssa_print_type(f, m, t->Array.elem);
+		ssa_fprintf(f, "]");
+		break;
+	case Type_Vector:
+		ssa_fprintf(f, "<%lld x ", t->Vector.count);
+		ssa_print_type(f, m, t->Vector.elem);
+		ssa_fprintf(f, ">");
+		break;
+	case Type_Slice:
+		ssa_fprintf(f, "{");
+		ssa_print_type(f, m, t->Slice.elem);
+		ssa_fprintf(f, "*, i%lld, i%lld}", word_bits, word_bits);
+		break;
+	case Type_Record: {
+		switch (t->Record.kind) {
+		case TypeRecord_Struct:
+			if (t->Record.struct_is_packed) {
+				ssa_fprintf(f, "<");
+			}
+			ssa_fprintf(f, "{");
+			for (isize i = 0; i < t->Record.field_count; i++) {
+				if (i > 0) {
+					ssa_fprintf(f, ", ");
+				}
+				Type *ft = t->Record.fields[i]->type;
+				Type *bft = base_type(ft);
+				if (!is_type_struct(bft)) {
+					ft = bft;
+				}
+				ssa_print_type(f, m, ft);
+			}
+			ssa_fprintf(f, "}");
+			if (t->Record.struct_is_packed) {
+				ssa_fprintf(f, ">");
+			}
+			break;
+		case TypeRecord_Union: {
+			// NOTE(bill): The zero size array is used to fix the alignment used in a structure as
+			// LLVM takes the first element's alignment as the entire alignment (like C)
+			i64 size_of_union  = type_size_of(s, heap_allocator(), t) - s.word_size;
+			i64 align_of_union = type_align_of(s, heap_allocator(), t);
+			ssa_fprintf(f, "{[0 x <%lld x i8>], [%lld x i8], i%lld}", align_of_union, size_of_union, word_bits);
+		} break;
+		case TypeRecord_RawUnion: {
+			// NOTE(bill): The zero size array is used to fix the alignment used in a structure as
+			// LLVM takes the first element's alignment as the entire alignment (like C)
+			i64 size_of_union  = type_size_of(s, heap_allocator(), t);
+			i64 align_of_union = type_align_of(s, heap_allocator(), t);
+			ssa_fprintf(f, "{[0 x <%lld x i8>], [%lld x i8]}", align_of_union, size_of_union);
+		} break;
+		case TypeRecord_Enum:
+			ssa_print_type(f, m, t->Record.enum_base);
+			break;
+		}
+	} break;
+
+
+	case Type_Named:
+		if (is_type_struct(t) || is_type_union(t)) {
+			String *name = map_string_get(&m->type_names, hash_pointer(t));
+			GB_ASSERT_MSG(name != NULL, "%.*s", LIT(t->Named.name));
+			ssa_print_encoded_local(f, *name);
+			// ssa_print_encoded_local(f, t->Named.name);
+		} else {
+			ssa_print_type(f, m, base_type(t));
+		}
+		break;
+	case Type_Tuple:
+		if (t->Tuple.variable_count == 1) {
+			ssa_print_type(f, m, t->Tuple.variables[0]->type);
+		} else {
+			ssa_fprintf(f, "{");
+			for (isize i = 0; i < t->Tuple.variable_count; i++) {
+				if (i > 0) {
+					ssa_fprintf(f, ", ");
+				}
+				ssa_print_type(f, m, t->Tuple.variables[i]->type);
+			}
+			ssa_fprintf(f, "}");
+		}
+		break;
+	case Type_Proc: {
+		if (t->Proc.result_count == 0) {
+			ssa_fprintf(f, "void");
+		} else {
+			ssa_print_type(f, m, t->Proc.results);
+		}
+		ssa_fprintf(f, " (");
+		TypeTuple *params = &t->Proc.params->Tuple;
+		for (isize i = 0; i < t->Proc.param_count; i++) {
+			if (i > 0) {
+				ssa_fprintf(f, ", ");
+			}
+			ssa_print_type(f, m, params->variables[i]->type);
+		}
+		ssa_fprintf(f, ")*");
+	} break;
+	}
+}
+
+void ssa_print_exact_value(ssaFileBuffer *f, ssaModule *m, ExactValue value, Type *type);
+
+void ssa_print_compound_element(ssaFileBuffer *f, ssaModule *m, ExactValue v, Type *elem_type) {
+	ssa_print_type(f, m, elem_type);
+	ssa_fprintf(f, " ");
+
+	if (v.kind != ExactValue_Invalid && is_type_maybe(elem_type)) {
+		Type *t = base_type(elem_type)->Maybe.elem;
+		ssa_fprintf(f, "{");
+		ssa_print_type(f, m, t);
+		ssa_fprintf(f, " ");
+	}
+
+	if (v.kind == ExactValue_Invalid || base_type(elem_type) == t_any) {
+		ssa_fprintf(f, "zeroinitializer");
+	} else {
+		ssa_print_exact_value(f, m, v, elem_type);
+	}
+
+	if (v.kind != ExactValue_Invalid && is_type_maybe(elem_type)) {
+		ssa_fprintf(f, ", ");
+		ssa_print_type(f, m, t_bool);
+		ssa_fprintf(f, " ");
+		ssa_fprintf(f, "true}");
+	}
+}
+
+void ssa_print_exact_value(ssaFileBuffer *f, ssaModule *m, ExactValue value, Type *type) {
+	type = base_type(type);
+	if (is_type_float(type)) {
+		value = exact_value_to_float(value);
+	} else if (is_type_integer(type)) {
+		value = exact_value_to_integer(value);
+	} else if (is_type_pointer(type)) {
+		value = exact_value_to_integer(value);
+	}
+
+	switch (value.kind) {
+	case ExactValue_Bool:
+		ssa_fprintf(f, "%s", (value.value_bool ? "true" : "false"));
+		break;
+	case ExactValue_String: {
+		String str = value.value_string;
+		if (str.len == 0) {
+			ssa_fprintf(f, "zeroinitializer");
+			break;
+		}
+		if (!is_type_string(type)) {
+			GB_ASSERT(is_type_array(type));
+			ssa_fprintf(f, "c\"");
+			ssa_print_escape_string(f, str, false);
+			ssa_fprintf(f, "\"");
+		} else {
+			// HACK NOTE(bill): This is a hack but it works because strings are created at the very end
+			// of the .ll file
+			ssaValue *str_array = ssa_add_global_string_array(m, str);
+
+			ssa_fprintf(f, "{i8* getelementptr inbounds (");
+			ssa_print_type(f, m, str_array->Global.entity->type);
+			ssa_fprintf(f, ", ");
+			ssa_print_type(f, m, str_array->Global.entity->type);
+			ssa_fprintf(f, "* ");
+			ssa_print_encoded_global(f, str_array->Global.entity->token.string, false);
+			ssa_fprintf(f, ", ");
+			ssa_print_type(f, m, t_int);
+			ssa_fprintf(f, " 0, i32 0), ");
+			ssa_print_type(f, m, t_int);
+			ssa_fprintf(f, " %lld}", cast(i64)str.len);
+		}
+	} break;
+	case ExactValue_Integer: {
+		if (is_type_pointer(type)) {
+			if (value.value_integer == 0) {
+				ssa_fprintf(f, "null");
+			} else {
+				ssa_fprintf(f, "inttoptr (");
+				ssa_print_type(f, m, t_int);
+				ssa_fprintf(f, " %llu to ", value.value_integer);
+				ssa_print_type(f, m, t_rawptr);
+				ssa_fprintf(f, ")");
+			}
+		} else {
+			ssa_fprintf(f, "%lld", value.value_integer);
+		}
+	} break;
+	case ExactValue_Float: {
+		GB_ASSERT(is_type_float(type));
+		type = base_type(type);
+		u64 u = *cast(u64*)&value.value_float;
+		switch (type->Basic.kind) {
+		case Basic_f32:
+			// IMPORTANT NOTE(bill): LLVM requires all floating point constants to be
+			// a 64 bit number if bits_of(float type) <= 64.
+			// https://groups.google.com/forum/#!topic/llvm-dev/IlqV3TbSk6M
+			// 64 bit mantissa: 52 bits
+			// 32 bit mantissa: 23 bits
+			// 29 == 52-23
+			u >>= 29;
+			u <<= 29;
+			break;
+		}
+
+		switch (type->Basic.kind) {
+		case 0: break;
+#if 0
+		case Basic_f16:
+			ssa_fprintf(f, "bitcast (");
+			ssa_print_type(f, m, t_u16);
+			ssa_fprintf(f, " %u to ", cast(u16)f32_to_f16(cast(f32)value.value_float));
+			ssa_print_type(f, m, t_f16);
+			ssa_fprintf(f, ")");
+			break;
+		case Basic_f128:
+			ssa_fprintf(f, "bitcast (");
+			ssa_fprintf(f, "i128");
+			// TODO(bill): Actually support f128
+			ssa_fprintf(f, " %llu to ", u);
+			ssa_print_type(f, m, t_f128);
+			ssa_fprintf(f, ")");
+			break;
+#endif
+		default:
+			ssa_fprintf(f, "0x%016llx", u);
+			break;
+		}
+	} break;
+	case ExactValue_Pointer:
+		if (value.value_pointer == 0) {
+			ssa_fprintf(f, "null");
+		} else {
+			ssa_fprintf(f, "inttoptr (");
+			ssa_print_type(f, m, t_int);
+			ssa_fprintf(f, " %llu to ", cast(u64)cast(uintptr)value.value_pointer);
+			ssa_print_type(f, m, t_rawptr);
+			ssa_fprintf(f, ")");
+		}
+		break;
+
+	case ExactValue_Compound: {
+		type = base_type(type);
+		if (is_type_array(type)) {
+			ast_node(cl, CompoundLit, value.value_compound);
+			isize elem_count = cl->elems.count;
+			if (elem_count == 0) {
+				ssa_fprintf(f, "zeroinitializer");
+				break;
+			}
+
+			ssa_fprintf(f, "[");
+			Type *elem_type = type->Array.elem;
+
+			for (isize i = 0; i < elem_count; i++) {
+				if (i > 0) {
+					ssa_fprintf(f, ", ");
+				}
+				TypeAndValue *tav = type_and_value_of_expression(m->info, cl->elems.e[i]);
+				GB_ASSERT(tav != NULL);
+				ssa_print_compound_element(f, m, tav->value, elem_type);
+			}
+			for (isize i = elem_count; i < type->Array.count; i++) {
+				if (i >= elem_count) {
+					ssa_fprintf(f, ", ");
+				}
+				ssa_print_type(f, m, elem_type);
+				ssa_fprintf(f, " zeroinitializer");
+			}
+
+			ssa_fprintf(f, "]");
+		} else if (is_type_vector(type)) {
+			ast_node(cl, CompoundLit, value.value_compound);
+			isize elem_count = cl->elems.count;
+			if (elem_count == 0) {
+				ssa_fprintf(f, "zeroinitializer");
+				break;
+			}
+
+			ssa_fprintf(f, "<");
+			Type *elem_type = type->Vector.elem;
+
+			if (elem_count == 1 && type->Vector.count > 1) {
+				TypeAndValue *tav = type_and_value_of_expression(m->info, cl->elems.e[0]);
+				GB_ASSERT(tav != NULL);
+
+				for (isize i = 0; i < type->Vector.count; i++) {
+					if (i > 0) {
+						ssa_fprintf(f, ", ");
+					}
+					ssa_print_compound_element(f, m, tav->value, elem_type);
+				}
+			} else {
+				for (isize i = 0; i < elem_count; i++) {
+					if (i > 0) {
+						ssa_fprintf(f, ", ");
+					}
+					TypeAndValue *tav = type_and_value_of_expression(m->info, cl->elems.e[i]);
+					GB_ASSERT(tav != NULL);
+					ssa_print_compound_element(f, m, tav->value, elem_type);
+				}
+			}
+
+			ssa_fprintf(f, ">");
+		} else if (is_type_struct(type)) {
+			gbTempArenaMemory tmp = gb_temp_arena_memory_begin(&m->tmp_arena);
+
+			ast_node(cl, CompoundLit, value.value_compound);
+
+			if (cl->elems.count == 0) {
+				ssa_fprintf(f, "zeroinitializer");
+				break;
+			}
+
+
+			isize value_count = type->Record.field_count;
+			ExactValue *values = gb_alloc_array(m->tmp_allocator, ExactValue, value_count);
+
+
+			if (cl->elems.e[0]->kind == AstNode_FieldValue) {
+				isize elem_count = cl->elems.count;
+				for (isize i = 0; i < elem_count; i++) {
+					ast_node(fv, FieldValue, cl->elems.e[i]);
+					String name = fv->field->Ident.string;
+
+					TypeAndValue *tav = type_and_value_of_expression(m->info, fv->value);
+					GB_ASSERT(tav != NULL);
+
+					Selection sel = lookup_field(m->allocator, type, name, false);
+					Entity *f = type->Record.fields[sel.index.e[0]];
+
+					values[f->Variable.field_index] = tav->value;
+				}
+			} else {
+				for (isize i = 0; i < value_count; i++) {
+					TypeAndValue *tav = type_and_value_of_expression(m->info, cl->elems.e[i]);
+					GB_ASSERT(tav != NULL);
+
+					Entity *f = type->Record.fields_in_src_order[i];
+
+					values[f->Variable.field_index] = tav->value;
+				}
+			}
+
+
+
+			if (type->Record.struct_is_packed) {
+				ssa_fprintf(f, "<");
+			}
+			ssa_fprintf(f, "{");
+
+
+			for (isize i = 0; i < value_count; i++) {
+				if (i > 0) {
+					ssa_fprintf(f, ", ");
+				}
+				Type *elem_type = type->Record.fields[i]->type;
+
+				ssa_print_compound_element(f, m, values[i], elem_type);
+			}
+
+
+			ssa_fprintf(f, "}");
+			if (type->Record.struct_is_packed) {
+				ssa_fprintf(f, ">");
+			}
+
+			gb_temp_arena_memory_end(tmp);
+		} else {
+			ssa_fprintf(f, "zeroinitializer");
+		}
+
+	} break;
+
+	default:
+		ssa_fprintf(f, "zeroinitializer");
+		// GB_PANIC("Invalid ExactValue: %d", value.kind);
+		break;
+	}
+}
+
+void ssa_print_block_name(ssaFileBuffer *f, ssaBlock *b) {
+	if (b != NULL) {
+		ssa_print_escape_string(f, b->label, false);
+		ssa_fprintf(f, "-%td", b->index);
+	} else {
+		ssa_fprintf(f, "<INVALID-BLOCK>");
+	}
+}
+
+void ssa_print_value(ssaFileBuffer *f, ssaModule *m, ssaValue *value, Type *type_hint) {
+	if (value == NULL) {
+		ssa_fprintf(f, "!!!NULL_VALUE");
+		return;
+	}
+	switch (value->kind) {
+	default: GB_PANIC("Unknown ssaValue kind"); break;
+
+	case ssaValue_Constant:
+		ssa_print_exact_value(f, m, value->Constant.value, type_hint);
+		break;
+
+	case ssaValue_ConstantSlice: {
+		ssaValueConstantSlice *cs = &value->ConstantSlice;
+		if (cs->backing_array == NULL || cs->count == 0) {
+			ssa_fprintf(f, "zeroinitializer");
+		} else {
+			Type *at = base_type(type_deref(ssa_type(cs->backing_array)));
+			Type *et = at->Array.elem;
+			ssa_fprintf(f, "{");
+			ssa_print_type(f, m, et);
+			ssa_fprintf(f, "* getelementptr inbounds (");
+			ssa_print_type(f, m, at);
+			ssa_fprintf(f, ", ");
+			ssa_print_type(f, m, at);
+			ssa_fprintf(f, "* ");
+			ssa_print_value(f, m, cs->backing_array, at);
+			ssa_fprintf(f, ", ");
+			ssa_print_type(f, m, t_int);
+			ssa_fprintf(f, " 0, i32 0), ");
+			ssa_print_type(f, m, t_int);
+			ssa_fprintf(f, " %lld, ", cs->count);
+			ssa_print_type(f, m, t_int);
+			ssa_fprintf(f, " %lld}", cs->count);
+		}
+	} break;
+
+	case ssaValue_Nil:
+		ssa_fprintf(f, "zeroinitializer");
+		break;
+
+	case ssaValue_TypeName:
+		ssa_print_encoded_local(f, value->TypeName.name);
+		break;
+	case ssaValue_Global: {
+		Scope *scope = value->Global.entity->scope;
+		bool in_global_scope = false;
+		if (scope != NULL) {
+			in_global_scope = scope->is_global || scope->is_init;
+		}
+		ssa_print_encoded_global(f, value->Global.entity->token.string, in_global_scope);
+	} break;
+	case ssaValue_Param:
+		ssa_print_encoded_local(f, value->Param.entity->token.string);
+		break;
+	case ssaValue_Proc:
+		ssa_print_encoded_global(f, value->Proc.name, (value->Proc.tags & (ProcTag_foreign|ProcTag_link_name)) != 0);
+		break;
+	case ssaValue_Instr:
+		ssa_fprintf(f, "%%%d", value->index);
+		break;
+	}
+}
+
+void ssa_print_instr(ssaFileBuffer *f, ssaModule *m, ssaValue *value) {
+	GB_ASSERT(value->kind == ssaValue_Instr);
+	ssaInstr *instr = &value->Instr;
+
+	ssa_fprintf(f, "\t");
+
+	switch (instr->kind) {
+	case ssaInstr_StartupRuntime: {
+		ssa_fprintf(f, "call void ");
+		ssa_print_encoded_global(f, str_lit(SSA_STARTUP_RUNTIME_PROC_NAME), false);
+		ssa_fprintf(f, "()\n");
+	} break;
+
+	case ssaInstr_Comment:
+		ssa_fprintf(f, "; %.*s\n", LIT(instr->Comment.text));
+		break;
+
+	case ssaInstr_Local: {
+		Type *type = instr->Local.entity->type;
+		ssa_fprintf(f, "%%%d = alloca ", value->index);
+		ssa_print_type(f, m, type);
+		ssa_fprintf(f, ", align %lld\n", type_align_of(m->sizes, m->allocator, type));
+	} break;
+
+	case ssaInstr_ZeroInit: {
+		Type *type = type_deref(ssa_type(instr->ZeroInit.address));
+		ssa_fprintf(f, "store ");
+		ssa_print_type(f, m, type);
+		ssa_fprintf(f, " zeroinitializer, ");
+		ssa_print_type(f, m, type);
+		ssa_fprintf(f, "* %%%d\n", instr->ZeroInit.address->index);
+	} break;
+
+	case ssaInstr_Store: {
+		Type *type = ssa_type(instr->Store.value);
+		ssa_fprintf(f, "store ");
+		ssa_print_type(f, m, type);
+		ssa_fprintf(f, " ");
+		ssa_print_value(f, m, instr->Store.value, type);
+		ssa_fprintf(f, ", ");
+		ssa_print_type(f, m, type);
+		ssa_fprintf(f, "* ");
+		ssa_print_value(f, m, instr->Store.address, type);
+		ssa_fprintf(f, "\n");
+	} break;
+
+	case ssaInstr_Load: {
+		Type *type = instr->Load.type;
+		ssa_fprintf(f, "%%%d = load ", value->index);
+		ssa_print_type(f, m, type);
+		ssa_fprintf(f, ", ");
+		ssa_print_type(f, m, type);
+		ssa_fprintf(f, "* ");
+		ssa_print_value(f, m, instr->Load.address, type);
+		ssa_fprintf(f, ", align %lld\n", type_align_of(m->sizes, m->allocator, type));
+	} break;
+
+	case ssaInstr_ArrayElementPtr: {
+		Type *et = ssa_type(instr->ArrayElementPtr.address);
+		ssa_fprintf(f, "%%%d = getelementptr inbounds ", value->index);
+
+		ssa_print_type(f, m, type_deref(et));
+		ssa_fprintf(f, ", ");
+		ssa_print_type(f, m, et);
+		ssa_fprintf(f, " ");
+		ssa_print_value(f, m, instr->ArrayElementPtr.address, et);
+		ssa_fprintf(f, ", ");
+		ssa_print_type(f, m, t_int);
+		ssa_fprintf(f, " 0, ");
+
+		ssaValue *index =instr->ArrayElementPtr.elem_index;
+		Type *t = ssa_type(index);
+		ssa_print_type(f, m, t);
+		ssa_fprintf(f, " ");
+		ssa_print_value(f, m, index, t);
+		ssa_fprintf(f, "\n");
+	} break;
+
+	case ssaInstr_StructElementPtr: {
+		Type *et = ssa_type(instr->StructElementPtr.address);
+		ssa_fprintf(f, "%%%d = getelementptr inbounds ", value->index);
+
+		ssa_print_type(f, m, type_deref(et));
+		ssa_fprintf(f, ", ");
+		ssa_print_type(f, m, et);
+		ssa_fprintf(f, " ");
+		ssa_print_value(f, m, instr->StructElementPtr.address, et);
+		ssa_fprintf(f, ", ");
+		ssa_print_type(f, m, t_int);
+		ssa_fprintf(f, " 0, ");
+		ssa_print_type(f, m, t_i32);
+		ssa_fprintf(f, " %d", instr->StructElementPtr.elem_index);
+		ssa_fprintf(f, "\n");
+	} break;
+
+	case ssaInstr_PtrOffset: {
+		Type *pt = ssa_type(instr->PtrOffset.address);
+		ssa_fprintf(f, "%%%d = getelementptr inbounds ", value->index);
+		ssa_print_type(f, m, type_deref(pt));
+		ssa_fprintf(f, ", ");
+		ssa_print_type(f, m, pt);
+		ssa_fprintf(f, " ");
+		ssa_print_value(f, m, instr->PtrOffset.address, pt);
+
+		ssaValue *offset = instr->PtrOffset.offset;
+		Type *t = ssa_type(offset);
+		ssa_fprintf(f, ", ");
+		ssa_print_type(f, m, t);
+		ssa_fprintf(f, " ");
+		ssa_print_value(f, m, offset, t);
+		ssa_fprintf(f, "\n");
+	} break;
+
+	case ssaInstr_Phi: {
+		ssa_fprintf(f, "%%%d = phi ", value->index);
+		ssa_print_type(f, m, instr->Phi.type);
+		ssa_fprintf(f, " ", value->index);
+
+		for (isize i = 0; i < instr->Phi.edges.count; i++) {
+			if (i > 0) {
+				ssa_fprintf(f, ", ");
+			}
+
+			ssaValue *edge = instr->Phi.edges.e[i];
+			ssaBlock *block = NULL;
+			if (instr->parent != NULL &&
+			    i < instr->parent->preds.count) {
+				block = instr->parent->preds.e[i];
+			}
+
+			ssa_fprintf(f, "[ ");
+			ssa_print_value(f, m, edge, instr->Phi.type);
+			ssa_fprintf(f, ", %%");
+			ssa_print_block_name(f, block);
+			ssa_fprintf(f, " ]");
+		}
+		ssa_fprintf(f, "\n");
+	} break;
+
+	case ssaInstr_ArrayExtractValue: {
+		Type *et = ssa_type(instr->ArrayExtractValue.address);
+		ssa_fprintf(f, "%%%d = extractvalue ", value->index);
+
+		ssa_print_type(f, m, et);
+		ssa_fprintf(f, " ");
+		ssa_print_value(f, m, instr->ArrayExtractValue.address, et);
+		ssa_fprintf(f, ", %d\n", instr->ArrayExtractValue.index);
+	} break;
+
+	case ssaInstr_StructExtractValue: {
+		Type *et = ssa_type(instr->StructExtractValue.address);
+		ssa_fprintf(f, "%%%d = extractvalue ", value->index);
+
+		ssa_print_type(f, m, et);
+		ssa_fprintf(f, " ");
+		ssa_print_value(f, m, instr->StructExtractValue.address, et);
+		ssa_fprintf(f, ", %d\n", instr->StructExtractValue.index);
+	} break;
+
+	case ssaInstr_UnionTagPtr: {
+		Type *et = ssa_type(instr->UnionTagPtr.address);
+		ssa_fprintf(f, "%%%d = getelementptr inbounds ", value->index);
+
+		ssa_print_type(f, m, type_deref(et));
+		ssa_fprintf(f, ", ");
+		ssa_print_type(f, m, et);
+		ssa_fprintf(f, " ");
+		ssa_print_value(f, m, instr->UnionTagPtr.address, et);
+		ssa_fprintf(f, ", ");
+		ssa_print_type(f, m, t_int);
+		ssa_fprintf(f, " 0, ");
+		ssa_print_type(f, m, t_i32);
+		ssa_fprintf(f, " %d", 2);
+		ssa_fprintf(f, "\n");
+	} break;
+
+	case ssaInstr_UnionTagValue: {
+		Type *et = ssa_type(instr->UnionTagValue.address);
+		ssa_fprintf(f, "%%%d = extractvalue ", value->index);
+
+		ssa_print_type(f, m, et);
+		ssa_fprintf(f, " ");
+		ssa_print_value(f, m, instr->UnionTagValue.address, et);
+		ssa_fprintf(f, ", %d\n", 2);
+	} break;
+
+	case ssaInstr_Jump: {;
+		ssa_fprintf(f, "br label %%");
+		ssa_print_block_name(f, instr->Jump.block);
+		ssa_fprintf(f, "\n");
+	} break;
+
+	case ssaInstr_If: {;
+		ssa_fprintf(f, "br ");
+		ssa_print_type(f, m, t_bool);
+		ssa_fprintf(f, " ");
+		ssa_print_value(f, m, instr->If.cond, t_bool);
+		ssa_fprintf(f, ", ", instr->If.cond->index);
+		ssa_fprintf(f, "label %%");   ssa_print_block_name(f, instr->If.true_block);
+		ssa_fprintf(f, ", label %%"); ssa_print_block_name(f, instr->If.false_block);
+		ssa_fprintf(f, "\n");
+	} break;
+
+	case ssaInstr_Return: {
+		ssaInstrReturn *ret = &instr->Return;
+		ssa_fprintf(f, "ret ");
+		if (ret->value == NULL) {
+			ssa_fprintf(f, "void");
+		} else {
+			Type *t = ssa_type(ret->value);
+			ssa_print_type(f, m, t);
+			ssa_fprintf(f, " ");
+			ssa_print_value(f, m, ret->value, t);
+		}
+
+		ssa_fprintf(f, "\n");
+
+	} break;
+
+	case ssaInstr_Conv: {
+		ssaInstrConv *c = &instr->Conv;
+		ssa_fprintf(f, "%%%d = %.*s ", value->index, LIT(ssa_conv_strings[c->kind]));
+		ssa_print_type(f, m, c->from);
+		ssa_fprintf(f, " ");
+		ssa_print_value(f, m, c->value, c->from);
+		ssa_fprintf(f, " to ");
+		ssa_print_type(f, m, c->to);
+		ssa_fprintf(f, "\n");
+
+	} break;
+
+	case ssaInstr_Unreachable: {
+		ssa_fprintf(f, "unreachable\n");
+	} break;
+
+	case ssaInstr_BinaryOp: {
+		ssaInstrBinaryOp *bo = &value->Instr.BinaryOp;
+		Type *type = base_type(ssa_type(bo->left));
+		Type *elem_type = type;
+		while (elem_type->kind == Type_Vector) {
+			elem_type = base_type(elem_type->Vector.elem);
+		}
+
+		ssa_fprintf(f, "%%%d = ", value->index);
+
+		if (gb_is_between(bo->op, Token__ComparisonBegin+1, Token__ComparisonEnd-1)) {
+			if (is_type_string(elem_type)) {
+				ssa_fprintf(f, "call ");
+				ssa_print_type(f, m, t_bool);
+				char *runtime_proc = "";
+				switch (bo->op) {
+				case Token_CmpEq: runtime_proc = "__string_eq"; break;
+				case Token_NotEq: runtime_proc = "__string_ne"; break;
+				case Token_Lt:    runtime_proc = "__string_lt"; break;
+				case Token_Gt:    runtime_proc = "__string_gt"; break;
+				case Token_LtEq:  runtime_proc = "__string_le"; break;
+				case Token_GtEq:  runtime_proc = "__string_gt"; break;
+				}
+
+				ssa_fprintf(f, " ");
+				ssa_print_encoded_global(f, make_string_c(runtime_proc), false);
+				ssa_fprintf(f, "(");
+				ssa_print_type(f, m, type);
+				ssa_fprintf(f, " ");
+				ssa_print_value(f, m, bo->left, type);
+				ssa_fprintf(f, ", ");
+				ssa_print_type(f, m, type);
+				ssa_fprintf(f, " ");
+				ssa_print_value(f, m, bo->right, type);
+				ssa_fprintf(f, ")\n");
+				return;
+
+			} else if (is_type_float(elem_type)) {
+				ssa_fprintf(f, "fcmp ");
+				switch (bo->op) {
+				case Token_CmpEq: ssa_fprintf(f, "oeq"); break;
+				case Token_NotEq: ssa_fprintf(f, "one"); break;
+				case Token_Lt:    ssa_fprintf(f, "olt"); break;
+				case Token_Gt:    ssa_fprintf(f, "ogt"); break;
+				case Token_LtEq:  ssa_fprintf(f, "ole"); break;
+				case Token_GtEq:  ssa_fprintf(f, "oge"); break;
+				}
+			} else {
+				ssa_fprintf(f, "icmp ");
+				if (bo->op != Token_CmpEq &&
+				    bo->op != Token_NotEq) {
+					if (is_type_unsigned(elem_type)) {
+						ssa_fprintf(f, "u");
+					} else {
+						ssa_fprintf(f, "s");
+					}
+				}
+				switch (bo->op) {
+				case Token_CmpEq: ssa_fprintf(f, "eq"); break;
+				case Token_NotEq: ssa_fprintf(f, "ne"); break;
+				case Token_Lt:    ssa_fprintf(f, "lt"); break;
+				case Token_Gt:    ssa_fprintf(f, "gt"); break;
+				case Token_LtEq:  ssa_fprintf(f, "le"); break;
+				case Token_GtEq:  ssa_fprintf(f, "ge"); break;
+				}
+			}
+		} else {
+			if (is_type_float(elem_type)) {
+				ssa_fprintf(f, "f");
+			}
+
+			switch (bo->op) {
+			case Token_Add:    ssa_fprintf(f, "add");  break;
+			case Token_Sub:    ssa_fprintf(f, "sub");  break;
+			case Token_And:    ssa_fprintf(f, "and");  break;
+			case Token_Or:     ssa_fprintf(f, "or");   break;
+			case Token_Xor:    ssa_fprintf(f, "xor");  break;
+			case Token_Shl:    ssa_fprintf(f, "shl");  break;
+			case Token_Shr:    ssa_fprintf(f, "lshr"); break;
+			case Token_Mul:    ssa_fprintf(f, "mul");  break;
+			case Token_Not:    ssa_fprintf(f, "xor");  break;
+
+			case Token_AndNot: GB_PANIC("Token_AndNot Should never be called");
+
+			default: {
+				if (!is_type_float(elem_type)) {
+					if (is_type_unsigned(elem_type)) ssa_fprintf(f, "u");
+					else                             ssa_fprintf(f, "s");
+				}
+
+				switch (bo->op) {
+				case Token_Quo: ssa_fprintf(f, "div"); break;
+				case Token_Mod: ssa_fprintf(f, "rem"); break;
+				}
+			} break;
+			}
+		}
+
+		ssa_fprintf(f, " ");
+		ssa_print_type(f, m, type);
+		ssa_fprintf(f, " ");
+		ssa_print_value(f, m, bo->left, type);
+		ssa_fprintf(f, ", ");
+		ssa_print_value(f, m, bo->right, type);
+		ssa_fprintf(f, "\n");
+
+	} break;
+
+	case ssaInstr_Call: {
+		ssaInstrCall *call = &instr->Call;
+		Type *result_type = call->type;
+		if (result_type) {
+			ssa_fprintf(f, "%%%d = ", value->index);
+		}
+		ssa_fprintf(f, "call ");
+		if (result_type) {
+			ssa_print_type(f, m, result_type);
+		} else {
+			ssa_fprintf(f, "void");
+		}
+		ssa_fprintf(f, " ");
+		ssa_print_value(f, m, call->value, call->type);
+
+
+		ssa_fprintf(f, "(");
+		if (call->arg_count > 0) {
+			Type *proc_type = base_type(ssa_type(call->value));
+			GB_ASSERT(proc_type->kind == Type_Proc);
+			TypeTuple *params = &proc_type->Proc.params->Tuple;
+			for (isize i = 0; i < call->arg_count; i++) {
+				Entity *e = params->variables[i];
+				GB_ASSERT(e != NULL);
+				Type *t = e->type;
+				if (i > 0) {
+					ssa_fprintf(f, ", ");
+				}
+				ssa_print_type(f, m, t);
+				ssa_fprintf(f, " ");
+				ssaValue *arg = call->args[i];
+				ssa_print_value(f, m, arg, t);
+			}
+		}
+		ssa_fprintf(f, ")\n");
+
+	} break;
+
+	case ssaInstr_Select: {
+		ssa_fprintf(f, "%%%d = select i1 ", value->index);
+		ssa_print_value(f, m, instr->Select.cond, t_bool);
+		ssa_fprintf(f, ", ");
+		ssa_print_type(f, m, ssa_type(instr->Select.true_value));
+		ssa_fprintf(f, " ");
+		ssa_print_value(f, m, instr->Select.true_value, ssa_type(instr->Select.true_value));
+		ssa_fprintf(f, ", ");
+		ssa_print_type(f, m, ssa_type(instr->Select.false_value));
+		ssa_fprintf(f, " ");
+		ssa_print_value(f, m, instr->Select.false_value, ssa_type(instr->Select.false_value));
+		ssa_fprintf(f, "\n");
+	} break;
+
+	case ssaInstr_VectorExtractElement: {
+		Type *vt = ssa_type(instr->VectorExtractElement.vector);
+		Type *it = ssa_type(instr->VectorExtractElement.index);
+		ssa_fprintf(f, "%%%d = extractelement ", value->index);
+
+		ssa_print_type(f, m, vt);
+		ssa_fprintf(f, " ");
+		ssa_print_value(f, m, instr->VectorExtractElement.vector, vt);
+		ssa_fprintf(f, ", ");
+		ssa_print_type(f, m, it);
+		ssa_fprintf(f, " ");
+		ssa_print_value(f, m, instr->VectorExtractElement.index, it);
+		ssa_fprintf(f, "\n");
+	} break;
+
+	case ssaInstr_VectorInsertElement: {
+		ssaInstrVectorInsertElement *ie = &instr->VectorInsertElement;
+		Type *vt = ssa_type(ie->vector);
+		ssa_fprintf(f, "%%%d = insertelement ", value->index);
+
+		ssa_print_type(f, m, vt);
+		ssa_fprintf(f, " ");
+		ssa_print_value(f, m, ie->vector, vt);
+		ssa_fprintf(f, ", ");
+
+		ssa_print_type(f, m, ssa_type(ie->elem));
+		ssa_fprintf(f, " ");
+		ssa_print_value(f, m, ie->elem, ssa_type(ie->elem));
+		ssa_fprintf(f, ", ");
+
+		ssa_print_type(f, m, ssa_type(ie->index));
+		ssa_fprintf(f, " ");
+		ssa_print_value(f, m, ie->index, ssa_type(ie->index));
+
+		ssa_fprintf(f, "\n");
+	} break;
+
+	case ssaInstr_VectorShuffle: {
+		ssaInstrVectorShuffle *sv = &instr->VectorShuffle;
+		Type *vt = ssa_type(sv->vector);
+		ssa_fprintf(f, "%%%d = shufflevector ", value->index);
+
+		ssa_print_type(f, m, vt);
+		ssa_fprintf(f, " ");
+		ssa_print_value(f, m, sv->vector, vt);
+		ssa_fprintf(f, ", ");
+
+		ssa_print_type(f, m, vt);
+		ssa_fprintf(f, " ");
+		ssa_print_value(f, m, sv->vector, vt);
+		ssa_fprintf(f, ", ");
+
+		ssa_fprintf(f, "<%td x i32> <", sv->index_count);
+		for (isize i = 0; i < sv->index_count; i++) {
+			if (i > 0) {
+				ssa_fprintf(f, ", ");
+			}
+			ssa_fprintf(f, "i32 %d", sv->indices[i]);
+		}
+		ssa_fprintf(f, ">");
+		ssa_fprintf(f, "\n");
+	} break;
+
+	case ssaInstr_BoundsCheck: {
+		ssaInstrBoundsCheck *bc = &instr->BoundsCheck;
+		ssa_fprintf(f, "call void ");
+		ssa_print_encoded_global(f, str_lit("__bounds_check_error"), false);
+		ssa_fprintf(f, "(");
+		ssa_print_compound_element(f, m, make_exact_value_string(bc->pos.file), t_string);
+		ssa_fprintf(f, ", ");
+
+		ssa_print_type(f, m, t_int);
+		ssa_fprintf(f, " ");
+		ssa_print_exact_value(f, m, make_exact_value_integer(bc->pos.line), t_int);
+		ssa_fprintf(f, ", ");
+
+		ssa_print_type(f, m, t_int);
+		ssa_fprintf(f, " ");
+		ssa_print_exact_value(f, m, make_exact_value_integer(bc->pos.column), t_int);
+		ssa_fprintf(f, ", ");
+
+		ssa_print_type(f, m, t_int);
+		ssa_fprintf(f, " ");
+		ssa_print_value(f, m, bc->index, t_int);
+		ssa_fprintf(f, ", ");
+
+		ssa_print_type(f, m, t_int);
+		ssa_fprintf(f, " ");
+		ssa_print_value(f, m, bc->len, t_int);
+
+		ssa_fprintf(f, ")\n");
+	} break;
+
+	case ssaInstr_SliceBoundsCheck: {
+		ssaInstrSliceBoundsCheck *bc = &instr->SliceBoundsCheck;
+		ssa_fprintf(f, "call void ");
+		if (bc->is_substring) {
+			ssa_print_encoded_global(f, str_lit("__substring_expr_error"), false);
+		} else {
+			ssa_print_encoded_global(f, str_lit("__slice_expr_error"), false);
+		}
+
+		ssa_fprintf(f, "(");
+		ssa_print_compound_element(f, m, make_exact_value_string(bc->pos.file), t_string);
+		ssa_fprintf(f, ", ");
+
+		ssa_print_type(f, m, t_int);
+		ssa_fprintf(f, " ");
+		ssa_print_exact_value(f, m, make_exact_value_integer(bc->pos.line), t_int);
+		ssa_fprintf(f, ", ");
+
+		ssa_print_type(f, m, t_int);
+		ssa_fprintf(f, " ");
+		ssa_print_exact_value(f, m, make_exact_value_integer(bc->pos.column), t_int);
+		ssa_fprintf(f, ", ");
+
+		ssa_print_type(f, m, t_int);
+		ssa_fprintf(f, " ");
+		ssa_print_value(f, m, bc->low, t_int);
+		ssa_fprintf(f, ", ");
+
+		ssa_print_type(f, m, t_int);
+		ssa_fprintf(f, " ");
+		ssa_print_value(f, m, bc->high, t_int);
+
+		if (!bc->is_substring) {
+			ssa_fprintf(f, ", ");
+			ssa_print_type(f, m, t_int);
+			ssa_fprintf(f, " ");
+			ssa_print_value(f, m, bc->max, t_int);
+		}
+
+		ssa_fprintf(f, ")\n");
+	} break;
+
+
+	default: {
+		GB_PANIC("<unknown instr> %d\n", instr->kind);
+		ssa_fprintf(f, "; <unknown instr> %d\n", instr->kind);
+	} break;
+	}
+}
+
+void ssa_print_proc(ssaFileBuffer *f, ssaModule *m, ssaProcedure *proc) {
+	if (proc->body == NULL) {
+		ssa_fprintf(f, "declare ");
+		if (proc->tags & ProcTag_dll_import) {
+			ssa_fprintf(f, "dllimport ");
+		}
+		if (proc->tags & ProcTag_dll_export) {
+			ssa_fprintf(f, "dllexport ");
+		}
+	} else {
+		ssa_fprintf(f, "\ndefine ");
+	}
+
+	if (proc->tags & ProcTag_stdcall) {
+		ssa_fprintf(f, "cc 64 ");
+	} else if (proc->tags & ProcTag_fastcall) {
+		ssa_fprintf(f, "cc 65 ");
+	}
+
+	TypeProc *proc_type = &proc->type->Proc;
+
+	if (proc_type->result_count == 0) {
+		ssa_fprintf(f, "void");
+	} else {
+		ssa_print_type(f, m, proc_type->results);
+	}
+
+	ssa_fprintf(f, " ");
+	ssa_print_encoded_global(f, proc->name, (proc->tags & (ProcTag_foreign|ProcTag_link_name)) != 0);
+	ssa_fprintf(f, "(");
+
+	if (proc_type->param_count > 0) {
+		TypeTuple *params = &proc_type->params->Tuple;
+		for (isize i = 0; i < params->variable_count; i++) {
+			Entity *e = params->variables[i];
+			if (i > 0) {
+				ssa_fprintf(f, ", ");
+			}
+			ssa_print_type(f, m, e->type);
+			if (proc->body != NULL) {
+				ssa_fprintf(f, " %%%.*s", LIT(e->token.string));
+			}
+		}
+	}
+
+	ssa_fprintf(f, ") ");
+
+	if (proc->tags & ProcTag_inline) {
+		ssa_fprintf(f, "alwaysinline ");
+	}
+	if (proc->tags & ProcTag_no_inline) {
+		ssa_fprintf(f, "noinline ");
+	}
+
+
+	if (proc->module->generate_debug_info && proc->entity != NULL) {
+		if (proc->body != NULL) {
+			ssaDebugInfo *di = *map_ssa_debug_info_get(&proc->module->debug_info, hash_pointer(proc->entity));
+			GB_ASSERT(di->kind == ssaDebugInfo_Proc);
+			ssa_fprintf(f, "!dbg !%d ", di->id);
+		}
+	}
+
+
+	if (proc->body != NULL) {
+		// ssa_fprintf(f, "nounwind uwtable {\n");
+
+		ssa_fprintf(f, "{\n");
+		for_array(i, proc->blocks) {
+			ssaBlock *block = proc->blocks.e[i];
+
+			if (i > 0) ssa_fprintf(f, "\n");
+			ssa_print_block_name(f, block);
+			ssa_fprintf(f, ":\n");
+
+			for_array(j, block->instrs) {
+				ssaValue *value = block->instrs.e[j];
+				ssa_print_instr(f, m, value);
+			}
+		}
+		ssa_fprintf(f, "}\n");
+	} else {
+		ssa_fprintf(f, "\n");
+	}
+
+	for_array(i, proc->children) {
+		ssa_print_proc(f, m, proc->children.e[i]);
+	}
+}
+
+void ssa_print_type_name(ssaFileBuffer *f, ssaModule *m, ssaValue *v) {
+	GB_ASSERT(v->kind == ssaValue_TypeName);
+	Type *bt = base_type(ssa_type(v));
+	if (!is_type_struct(bt) && !is_type_union(bt)) {
+		return;
+	}
+	ssa_print_encoded_local(f, v->TypeName.name);
+	ssa_fprintf(f, " = type ");
+	ssa_print_type(f, m, base_type(v->TypeName.type));
+	ssa_fprintf(f, "\n");
+}
+
+void ssa_print_llvm_ir(ssaGen *ssa) {
+	ssaModule *m = &ssa->module;
+	ssaFileBuffer buf = {0}, *f = &buf;
+	ssa_file_buffer_init(f, &ssa->output_file);
+
+	if (m->layout.len > 0) {
+		ssa_fprintf(f, "target datalayout = \"%.*s\"\n", LIT(m->layout));
+	}
+
+	ssa_print_encoded_local(f, str_lit("..string"));
+	ssa_fprintf(f, " = type {i8*, ");
+	ssa_print_type(f, m, t_int);
+	ssa_fprintf(f, "} ; Basic_string\n");
+	ssa_print_encoded_local(f, str_lit("..rawptr"));
+	ssa_fprintf(f, " = type i8* ; Basic_rawptr\n");
+
+	ssa_print_encoded_local(f, str_lit("..any"));
+	ssa_fprintf(f, " = type {");
+	ssa_print_type(f, m, t_type_info_ptr);
+	ssa_fprintf(f, ", ");
+	ssa_print_type(f, m, t_rawptr);
+	ssa_fprintf(f, "} ; Basic_any\n");
+
+
+	for_array(member_index, m->members.entries) {
+		MapSsaValueEntry *entry = &m->members.entries.e[member_index];
+		ssaValue *v = entry->value;
+		if (v->kind != ssaValue_TypeName) {
+			continue;
+		}
+		ssa_print_type_name(f, m, v);
+	}
+
+	ssa_fprintf(f, "\n");
+
+	for_array(member_index, m->members.entries) {
+		MapSsaValueEntry *entry = &m->members.entries.e[member_index];
+		ssaValue *v = entry->value;
+		if (v->kind != ssaValue_Proc) {
+			continue;
+		}
+		if (v->Proc.body == NULL) {
+			ssa_print_proc(f, m, &v->Proc);
+		}
+	}
+
+	for_array(member_index, m->members.entries) {
+		MapSsaValueEntry *entry = &m->members.entries.e[member_index];
+		ssaValue *v = entry->value;
+		if (v->kind != ssaValue_Proc) {
+			continue;
+		}
+		if (v->Proc.body != NULL) {
+			ssa_print_proc(f, m, &v->Proc);
+		}
+	}
+
+
+	for_array(member_index, m->members.entries) {
+		MapSsaValueEntry *entry = &m->members.entries.e[member_index];
+		ssaValue *v = entry->value;
+		if (v->kind != ssaValue_Global) {
+			continue;
+		}
+		ssaValueGlobal *g = &v->Global;
+		Scope *scope = g->entity->scope;
+		bool in_global_scope = false;
+		if (scope != NULL) {
+			in_global_scope = scope->is_global || scope->is_init;
+		}
+		ssa_print_encoded_global(f, g->entity->token.string, in_global_scope);
+		ssa_fprintf(f, " = ");
+		if (g->is_thread_local) {
+			ssa_fprintf(f, "thread_local ");
+		}
+
+		if (g->is_private) {
+			ssa_fprintf(f, "private ");
+		}
+		if (g->is_constant) {
+			if (g->is_unnamed_addr) {
+				ssa_fprintf(f, "unnamed_addr ");
+			}
+			ssa_fprintf(f, "constant ");
+		} else {
+			ssa_fprintf(f, "global ");
+		}
+
+
+		ssa_print_type(f, m, g->entity->type);
+		ssa_fprintf(f, " ");
+		if (g->value != NULL) {
+			ssa_print_value(f, m, g->value, g->entity->type);
+		} else {
+			ssa_fprintf(f, "zeroinitializer");
+		}
+		ssa_fprintf(f, "\n");
+	}
+
+
+#if 0
+	if (m->generate_debug_info) {
+		ssa_fprintf(f, "\n");
+		ssa_fprintf(f, "!llvm.dbg.cu = !{!0}\n");
+
+		for_array(di_index, m->debug_info.entries) {
+			MapSsaDebugInfoEntry *entry = &m->debug_info.entries.e[di_index];
+			ssaDebugInfo *di = entry->value;
+			ssa_fprintf(f, "!%d = ", di->id);
+
+			switch (di->kind) {
+			case ssaDebugInfo_CompileUnit: {
+				auto *cu = &di->CompileUnit;
+				ssaDebugInfo *file = *map_ssa_debug_info_get(&m->debug_info, hash_pointer(cu->file));
+				ssa_fprintf(f,
+				            "distinct !DICompileUnit("
+				            "language: DW_LANG_Go, " // Is this good enough?
+				            "file: !%d, "
+				            "producer: \"%.*s\", "
+				            "flags: \"\", "
+				            "runtimeVersion: 0, "
+				            "isOptimized: false, "
+				            "emissionKind: FullDebug"
+				            ")",
+				            file->id, LIT(cu->producer));
+
+			} break;
+			case ssaDebugInfo_File:
+				ssa_fprintf(f, "!DIFile(filename: \"");
+				ssa_print_escape_string(f, di->File.filename, false);
+				ssa_fprintf(f, "\", directory: \"");
+				ssa_print_escape_string(f, di->File.directory, false);
+				ssa_fprintf(f, "\")");
+				break;
+			case ssaDebugInfo_Proc:
+				ssa_fprintf(f, "distinct !DISubprogram("
+				            "name: \"%.*s\", "
+				            // "linkageName: \"\", "
+				            "file: !%d, "
+				            "line: %td, "
+				            "isDefinition: true, "
+				            "isLocal: false, "
+				            "unit: !0"
+				            ")",
+				            LIT(di->Proc.name),
+				            di->Proc.file->id,
+				            di->Proc.pos.line);
+				break;
+
+			case ssaDebugInfo_AllProcs:
+				ssa_fprintf(f, "!{");
+				for_array(proc_index, di->AllProcs.procs) {
+					ssaDebugInfo *p = di->AllProcs.procs.e[proc_index];
+					if (proc_index > 0) {ssa_fprintf(f, ",");}
+					ssa_fprintf(f, "!%d", p->id);
+				}
+				ssa_fprintf(f, "}");
+				break;
+			}
+
+			ssa_fprintf(f, "\n");
+		}
+	}
+#endif
+	ssa_file_buffer_destroy(f);
+}

+ 422 - 0
src/string.c

@@ -0,0 +1,422 @@
+gb_global gbArena string_buffer_arena = {0};
+gb_global gbAllocator string_buffer_allocator = {0};
+
+void init_string_buffer_memory(void) {
+	// NOTE(bill): This should be enough memory for file systems
+	gb_arena_init_from_allocator(&string_buffer_arena, heap_allocator(), gb_megabytes(1));
+	string_buffer_allocator = gb_arena_allocator(&string_buffer_arena);
+}
+
+
+// NOTE(bill): Used for UTF-8 strings
+typedef struct String {
+	u8 *  text;
+	isize len;
+} String;
+// NOTE(bill): used for printf style arguments
+#define LIT(x) ((int)(x).len), (x).text
+
+
+typedef struct String16 {
+	wchar_t *text;
+	isize    len;
+} String16;
+
+
+gb_inline String make_string(u8 *text, isize len) {
+	String s;
+	s.text = text;
+	if (len < 0) {
+		len = gb_strlen(cast(char *)text);
+	}
+	s.len = len;
+	return s;
+}
+
+
+gb_inline String16 make_string16(wchar_t *text, isize len) {
+	String16 s;
+	s.text = text;
+	s.len = len;
+	return s;
+}
+
+
+gb_inline String make_string_c(char *text) {
+	return make_string(cast(u8 *)cast(void *)text, gb_strlen(text));
+}
+
+#define str_lit(c_str) make_string(cast(u8 *)c_str, gb_size_of(c_str)-1)
+
+
+gb_inline bool are_strings_equal(String a, String b) {
+	if (a.len == b.len) {
+		return gb_memcompare(a.text, b.text, a.len) == 0;
+	}
+	return false;
+}
+
+gb_inline bool str_eq_ignore_case(String a, String b) {
+	if (a.len == b.len) {
+		for (isize i = 0; i < a.len; i++) {
+			char x = cast(char)a.text[i];
+			char y = cast(char)b.text[i];
+			if (gb_char_to_lower(x) != gb_char_to_lower(y))
+				return false;
+		}
+		return true;
+	}
+	return false;
+}
+
+int string_compare(String x, String y) {
+	if (x.len == y.len &&
+	    x.text == y.text) {
+		return 0;
+	}
+
+	isize n = gb_min(x.len, y.len);
+
+	isize fast = n/gb_size_of(isize) + 1;
+	isize offset = (fast-1)*gb_size_of(isize);
+	isize curr_block = 0;
+	if (n <= gb_size_of(isize)) {
+		fast = 0;
+	}
+
+	isize *la = cast(isize *)x.text;
+	isize *lb = cast(isize *)y.text;
+
+	for (; curr_block < fast; curr_block++) {
+		if (la[curr_block] ^ lb[curr_block]) {
+			for (isize pos = curr_block*gb_size_of(isize); pos < n; pos++) {
+				if (x.text[pos] ^ y.text[pos]) {
+					return cast(int)x.text[pos] - cast(int)y.text[pos];
+				}
+			}
+		}
+	}
+
+	for (; offset < n; offset++) {
+		if (x.text[offset] ^ y.text[offset]) {
+			return cast(int)x.text[offset] - cast(int)y.text[offset];
+		}
+	}
+
+	return 0;
+}
+
+GB_COMPARE_PROC(string_cmp_proc) {
+	String x = *(String *)a;
+	String y = *(String *)b;
+	return string_compare(x, y);
+}
+
+
+// gb_inline bool operator ==(String a, String b) { return are_strings_equal(a, b) != 0; }
+// gb_inline bool operator !=(String a, String b) { return !operator==(a, b); }
+// gb_inline bool operator < (String a, String b) { return string_compare(a, b) < 0; }
+// gb_inline bool operator > (String a, String b) { return string_compare(a, b) > 0; }
+// gb_inline bool operator <=(String a, String b) { return string_compare(a, b) <= 0; }
+// gb_inline bool operator >=(String a, String b) { return string_compare(a, b) >= 0; }
+
+// template <size_t N> gb_inline bool operator ==(String a, char const (&b)[N]) { return a == make_string(cast(u8 *)b, N-1); }
+// template <size_t N> gb_inline bool operator !=(String a, char const (&b)[N]) { return a != make_string(cast(u8 *)b, N-1); }
+// template <size_t N> gb_inline bool operator ==(char const (&a)[N], String b) { return make_string(cast(u8 *)a, N-1) == b; }
+// template <size_t N> gb_inline bool operator !=(char const (&a)[N], String b) { return make_string(cast(u8 *)a, N-1) != b; }
+
+gb_inline bool str_eq(String a, String b) { return are_strings_equal(a, b) != 0; }
+gb_inline bool str_ne(String a, String b) { return !str_eq(a, b);                }
+gb_inline bool str_lt(String a, String b) { return string_compare(a, b) < 0;     }
+gb_inline bool str_gt(String a, String b) { return string_compare(a, b) > 0;     }
+gb_inline bool str_le(String a, String b) { return string_compare(a, b) <= 0;    }
+gb_inline bool str_ge(String a, String b) { return string_compare(a, b) >= 0;    }
+
+
+
+gb_inline isize string_extension_position(String str) {
+	isize dot_pos = -1;
+	isize i = str.len;
+	bool seen_dot = false;
+	while (i --> 0) {
+		if (str.text[i] == GB_PATH_SEPARATOR)
+			break;
+		if (str.text[i] == '.') {
+			dot_pos = i;
+			break;
+		}
+	}
+
+	return dot_pos;
+}
+
+gb_inline bool string_has_extension(String str, String ext) {
+	if (str.len > ext.len+1) {
+		u8 *s = str.text+str.len - ext.len-1;
+		if (s[0] == '.') {
+			s++;
+			return gb_memcompare(s, ext.text, ext.len) == 0;
+		}
+		return false;
+	}
+	return false;
+}
+
+bool string_contains_char(String s, u8 c) {
+	for (isize i = 0; i < s.len; i++) {
+		if (s.text[i] == c)
+			return true;
+	}
+	return false;
+}
+
+// TODO(bill): Make this non-windows specific
+String16 string_to_string16(gbAllocator a, String s) {
+	if (s.len < 1) {
+		return make_string16(NULL, 0);
+	}
+
+	int len = MultiByteToWideChar(CP_UTF8, MB_ERR_INVALID_CHARS,
+	                              cast(char *)s.text, s.len, NULL, 0);
+	if (len == 0) {
+		return make_string16(NULL, 0);
+	}
+
+	wchar_t *text = gb_alloc_array(a, wchar_t, len+1);
+
+	int len1 = MultiByteToWideChar(CP_UTF8, MB_ERR_INVALID_CHARS,
+	                               cast(char *)s.text, s.len, text, len);
+	if (len1 == 0) {
+		gb_free(a, text);
+		return make_string16(NULL, 0);
+	}
+	text[len] = 0;
+
+	return make_string16(text, len-1);
+}
+
+String string16_to_string(gbAllocator a, String16 s) {
+	if (s.len < 1) {
+		return make_string(NULL, 0);
+	}
+
+	int len = WideCharToMultiByte(CP_UTF8, WC_ERR_INVALID_CHARS,
+	                              s.text, s.len, NULL, 0,
+	                              NULL, NULL);
+	if (len == 0) {
+		return make_string(NULL, 0);
+	}
+
+	u8 *text = gb_alloc_array(a, u8, len+1);
+
+	int len1 = WideCharToMultiByte(CP_UTF8, WC_ERR_INVALID_CHARS,
+	                               s.text, s.len, cast(char *)text, len,
+	                               NULL, NULL);
+	if (len1 == 0) {
+		gb_free(a, text);
+		return make_string(NULL, 0);
+	}
+	text[len] = 0;
+
+	return make_string(text, len-1);
+}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+bool unquote_char(String s, u8 quote, Rune *rune, bool *multiple_bytes, String *tail_string) {
+	if (s.text[0] == quote &&
+	    (quote == '$' || quote == '"')) {
+		return false;
+	} else if (s.text[0] >= 0x80) {
+		Rune r = -1;
+		isize size = gb_utf8_decode(s.text, s.len, &r);
+		*rune = r;
+		*multiple_bytes = true;
+		*tail_string = make_string(s.text+size, s.len-size);
+		return true;
+	} else if (s.text[0] != '\\') {
+		*rune = s.text[0];
+		*tail_string = make_string(s.text+1, s.len-1);
+		return true;
+	}
+
+	if (s.len <= 1) {
+		return false;
+	}
+	u8 c = s.text[1];
+	s = make_string(s.text+2, s.len-2);
+
+	switch (c) {
+	default: return false;
+
+	case 'a':  *rune = '\a'; break;
+	case 'b':  *rune = '\b'; break;
+	case 'f':  *rune = '\f'; break;
+	case 'n':  *rune = '\n'; break;
+	case 'r':  *rune = '\r'; break;
+	case 't':  *rune = '\t'; break;
+	case 'v':  *rune = '\v'; break;
+	case '\\': *rune = '\\'; break;
+
+
+	case '$':
+	case '"':
+		if (c != quote) {
+			return false;
+		}
+		*rune = c;
+		break;
+
+	case '0':
+	case '1':
+	case '2':
+	case '3':
+	case '4':
+	case '5':
+	case '6':
+	case '7': {
+		i32 r = gb_digit_to_int(c);
+		if (s.len < 2) {
+			return false;
+		}
+		for (isize i = 0; i < 2; i++) {
+			i32 d = gb_digit_to_int(s.text[i]);
+			if (d < 0 || d > 7) {
+				return false;
+			}
+			r = (r<<3) | d;
+		}
+		s = make_string(s.text+2, s.len-2);
+		if (r > 0xff) {
+			return false;
+		}
+		*rune = r;
+	} break;
+
+	case 'x':
+	case 'u':
+	case 'U': {
+		isize count = 0;
+		switch (c) {
+		case 'x': count = 2; break;
+		case 'u': count = 4; break;
+		case 'U': count = 8; break;
+		}
+
+		Rune r = 0;
+		if (s.len < count) {
+			return false;
+		}
+		for (isize i = 0; i < count; i++) {
+			i32 d = gb_hex_digit_to_int(s.text[i]);
+			if (d < 0) {
+				return false;
+			}
+			r = (r<<4) | d;
+		}
+		s = make_string(s.text+count, s.len-count);
+		if (c == 'x') {
+			*rune = r;
+			break;
+		}
+		if (r > GB_RUNE_MAX) {
+			return false;
+		}
+		*rune = r;
+		*multiple_bytes = true;
+	} break;
+	}
+	*tail_string = s;
+	return true;
+}
+
+
+// 0 == failure
+// 1 == original memory
+// 2 == new allocation
+i32 unquote_string(gbAllocator a, String *s_) {
+	GB_ASSERT(s_ != NULL);
+	String s = *s_;
+	isize n = s.len;
+	if (n < 2)
+		return 0;
+	u8 quote = s.text[0];
+	if (quote != s.text[n-1])
+		return 0;
+	s.text += 1;
+	s.len -= 2;
+
+	if (quote == '`') {
+		if (string_contains_char(s, '`')) {
+			return 0;
+		}
+		*s_ = s;
+		return 1;
+	}
+	if (quote != '"' && quote != '$')
+		return 0;
+
+	if (string_contains_char(s, '\n'))
+		return 0;
+
+	if (!string_contains_char(s, '\\') && !string_contains_char(s, quote)) {
+		if (quote == '"') {
+			*s_ = s;
+			return 1;
+		} else if (quote == '$') {
+			Rune r = GB_RUNE_INVALID;
+			isize size = gb_utf8_decode(s.text, s.len, &r);
+			if ((size == s.len) && (r != -1 || size != 1)) {
+				*s_ = s;
+				return 1;
+			}
+		}
+	}
+
+
+	u8 rune_temp[4] = {0};
+	isize buf_len = 3*s.len / 2;
+	u8 *buf = gb_alloc_array(a, u8, buf_len);
+	isize offset = 0;
+	while (s.len > 0) {
+		String tail_string = {0};
+		Rune r = 0;
+		bool multiple_bytes = false;
+		bool success = unquote_char(s, quote, &r, &multiple_bytes, &tail_string);
+		if (!success) {
+			gb_free(a, buf);
+			return 0;
+		}
+		s = tail_string;
+
+		if (r < 0x80 || !multiple_bytes) {
+			buf[offset++] = cast(u8)r;
+		} else {
+			isize size = gb_utf8_encode_rune(rune_temp, r);
+			gb_memmove(buf+offset, rune_temp, size);
+			offset += size;
+		}
+
+		if (quote == '$' && s.len != 0) {
+			gb_free(a, buf);
+			return 0;
+		}
+	}
+	*s_ = make_string(buf, offset);
+	return 2;
+}

+ 105 - 0
src/timings.c

@@ -0,0 +1,105 @@
+typedef struct TimeStamp {
+	u64    start;
+	u64    finish;
+	String label;
+} TimeStamp;
+
+typedef struct Timings {
+	TimeStamp        total;
+	Array(TimeStamp) sections;
+	u64              freq;
+} Timings;
+
+
+u64 win32_time_stamp_time_now(void) {
+	LARGE_INTEGER counter;
+	QueryPerformanceCounter(&counter);
+	return counter.QuadPart;
+}
+
+u64 win32_time_stamp__freq(void) {
+	gb_local_persist LARGE_INTEGER win32_perf_count_freq = {0};
+	if (!win32_perf_count_freq.QuadPart) {
+		QueryPerformanceFrequency(&win32_perf_count_freq);
+		GB_ASSERT(win32_perf_count_freq.QuadPart != 0);
+	}
+
+	return win32_perf_count_freq.QuadPart;
+}
+
+u64 time_stamp_time_now(void) {
+#if defined(GB_SYSTEM_WINDOWS)
+	return win32_time_stamp_time_now();
+#else
+#error time_stamp_time_now
+#endif
+}
+
+u64 time_stamp__freq(void) {
+#if defined(GB_SYSTEM_WINDOWS)
+	return win32_time_stamp__freq();
+#else
+#error time_stamp__freq
+#endif
+}
+
+TimeStamp make_time_stamp(String label) {
+	TimeStamp ts = {0};
+	ts.start = time_stamp_time_now();
+	ts.label = label;
+	return ts;
+}
+
+void timings_init(Timings *t, String label, isize buffer_size) {
+	array_init_reserve(&t->sections, heap_allocator(), buffer_size);
+	t->total = make_time_stamp(label);
+	t->freq  = time_stamp__freq();
+}
+
+void timings_destroy(Timings *t) {
+	array_free(&t->sections);
+}
+
+void timings__stop_current_section(Timings *t) {
+	if (t->sections.count > 0) {
+		t->sections.e[t->sections.count-1].finish = time_stamp_time_now();
+	}
+}
+
+void timings_start_section(Timings *t, String label) {
+	timings__stop_current_section(t);
+	array_add(&t->sections, make_time_stamp(label));
+}
+
+f64 time_stamp_as_ms(TimeStamp ts, u64 freq) {
+	GB_ASSERT_MSG(ts.finish >= ts.start, "time_stamp_as_ms - %.*s", LIT(ts.label));
+	return 1000.0 * cast(f64)(ts.finish - ts.start) / cast(f64)freq;
+}
+
+void timings_print_all(Timings *t) {
+	timings__stop_current_section(t);
+	t->total.finish = time_stamp_time_now();
+
+	char const SPACES[] = "                                                                ";
+
+	isize max_len = t->total.label.len;
+	for_array(i, t->sections) {
+		TimeStamp ts = t->sections.e[i];
+		max_len = gb_max(max_len, ts.label.len);
+	}
+
+	GB_ASSERT(max_len <= gb_size_of(SPACES)-1);
+
+	gb_printf("%.*s%.*s - %.3f ms\n",
+	          LIT(t->total.label),
+	          cast(int)(max_len-t->total.label.len), SPACES,
+	          time_stamp_as_ms(t->total, t->freq));
+
+	for_array(i, t->sections) {
+		TimeStamp ts = t->sections.e[i];
+		gb_printf("%.*s%.*s - %.3f ms\n",
+		          LIT(ts.label),
+	              cast(int)(max_len-ts.label.len), SPACES,
+		          time_stamp_as_ms(ts, t->freq));
+	}
+}

+ 816 - 0
src/tokenizer.c

@@ -0,0 +1,816 @@
+#define TOKEN_KINDS \
+	TOKEN_KIND(Token_Invalid, "Invalid"), \
+	TOKEN_KIND(Token_EOF, "EOF"), \
+	TOKEN_KIND(Token_Comment, "Comment"), \
+\
+TOKEN_KIND(Token__LiteralBegin, "_LiteralBegin"), \
+	TOKEN_KIND(Token_Identifier, "Identifier"), \
+	TOKEN_KIND(Token_Integer, "Integer"), \
+	TOKEN_KIND(Token_Float, "Float"), \
+	TOKEN_KIND(Token_Rune, "Rune"), \
+	TOKEN_KIND(Token_String, "String"), \
+TOKEN_KIND(Token__LiteralEnd, "_LiteralEnd"), \
+\
+TOKEN_KIND(Token__OperatorBegin, "_OperatorBegin"), \
+	TOKEN_KIND(Token_Eq, "="), \
+	TOKEN_KIND(Token_Not, "!"), \
+	TOKEN_KIND(Token_Hash, "#"), \
+	TOKEN_KIND(Token_At, "@"), \
+	TOKEN_KIND(Token_Pointer, "^"), \
+	TOKEN_KIND(Token_Maybe, "?"), \
+	TOKEN_KIND(Token_Add, "+"), \
+	TOKEN_KIND(Token_Sub, "-"), \
+	TOKEN_KIND(Token_Mul, "*"), \
+	TOKEN_KIND(Token_Quo, "/"), \
+	TOKEN_KIND(Token_Mod, "%"), \
+	TOKEN_KIND(Token_And, "&"), \
+	TOKEN_KIND(Token_Or, "|"), \
+	TOKEN_KIND(Token_Xor, "~"), \
+	TOKEN_KIND(Token_AndNot, "&~"), \
+	TOKEN_KIND(Token_Shl, "<<"), \
+	TOKEN_KIND(Token_Shr, ">>"), \
+\
+	TOKEN_KIND(Token_as,         "as"), \
+	TOKEN_KIND(Token_transmute,  "transmute"), \
+	TOKEN_KIND(Token_down_cast,  "down_cast"), \
+	TOKEN_KIND(Token_union_cast, "union_cast"), \
+\
+	TOKEN_KIND(Token_Prime, "'"), \
+	TOKEN_KIND(Token_DoublePrime, "''"), \
+\
+	TOKEN_KIND(Token_CmpAnd, "&&"), \
+	TOKEN_KIND(Token_CmpOr, "||"), \
+\
+TOKEN_KIND(Token__AssignOpBegin, "_AssignOpBegin"), \
+	TOKEN_KIND(Token_AddEq, "+="), \
+	TOKEN_KIND(Token_SubEq, "-="), \
+	TOKEN_KIND(Token_MulEq, "*="), \
+	TOKEN_KIND(Token_QuoEq, "/="), \
+	TOKEN_KIND(Token_ModEq, "%="), \
+	TOKEN_KIND(Token_AndEq, "&="), \
+	TOKEN_KIND(Token_OrEq, "|="), \
+	TOKEN_KIND(Token_XorEq, "~="), \
+	TOKEN_KIND(Token_AndNotEq, "&~="), \
+	TOKEN_KIND(Token_ShlEq, "<<="), \
+	TOKEN_KIND(Token_ShrEq, ">>="), \
+	TOKEN_KIND(Token_CmpAndEq, "&&="), \
+	TOKEN_KIND(Token_CmpOrEq, "||="), \
+TOKEN_KIND(Token__AssignOpEnd, "_AssignOpEnd"), \
+	TOKEN_KIND(Token_Increment, "++"), \
+	TOKEN_KIND(Token_Decrement, "--"), \
+	TOKEN_KIND(Token_ArrowRight, "->"), \
+	TOKEN_KIND(Token_ArrowLeft, "<-"), \
+\
+TOKEN_KIND(Token__ComparisonBegin, "_ComparisonBegin"), \
+	TOKEN_KIND(Token_CmpEq, "=="), \
+	TOKEN_KIND(Token_NotEq, "!="), \
+	TOKEN_KIND(Token_Lt, "<"), \
+	TOKEN_KIND(Token_Gt, ">"), \
+	TOKEN_KIND(Token_LtEq, "<="), \
+	TOKEN_KIND(Token_GtEq, ">="), \
+TOKEN_KIND(Token__ComparisonEnd, "_ComparisonEnd"), \
+\
+	TOKEN_KIND(Token_OpenParen, "("), \
+	TOKEN_KIND(Token_CloseParen, ")"), \
+	TOKEN_KIND(Token_OpenBracket, "["), \
+	TOKEN_KIND(Token_CloseBracket, "]"), \
+	TOKEN_KIND(Token_OpenBrace, "{"), \
+	TOKEN_KIND(Token_CloseBrace, "}"), \
+	TOKEN_KIND(Token_Colon, ":"), \
+	TOKEN_KIND(Token_Semicolon, ";"), \
+	TOKEN_KIND(Token_Period, "."), \
+	TOKEN_KIND(Token_Comma, ","), \
+	TOKEN_KIND(Token_Ellipsis, ".."), \
+	TOKEN_KIND(Token_RangeExclusive, "..<"), \
+TOKEN_KIND(Token__OperatorEnd, "_OperatorEnd"), \
+\
+TOKEN_KIND(Token__KeywordBegin, "_KeywordBegin"), \
+	TOKEN_KIND(Token_type,           "type"), \
+	TOKEN_KIND(Token_proc,           "proc"), \
+	TOKEN_KIND(Token_match,          "match"), \
+	TOKEN_KIND(Token_break,          "break"), \
+	TOKEN_KIND(Token_continue,       "continue"), \
+	TOKEN_KIND(Token_fallthrough,    "fallthrough"), \
+	TOKEN_KIND(Token_case,           "case"), \
+	TOKEN_KIND(Token_default,        "default"), \
+	TOKEN_KIND(Token_then,           "then"), \
+	TOKEN_KIND(Token_if,             "if"), \
+	TOKEN_KIND(Token_else,           "else"), \
+	TOKEN_KIND(Token_for,            "for"), \
+	TOKEN_KIND(Token_range,          "range"), \
+	TOKEN_KIND(Token_defer,          "defer"), \
+	TOKEN_KIND(Token_return,         "return"), \
+	TOKEN_KIND(Token_struct,         "struct"), \
+	TOKEN_KIND(Token_union,          "union"), \
+	TOKEN_KIND(Token_raw_union,      "raw_union"), \
+	TOKEN_KIND(Token_enum,           "enum"), \
+	TOKEN_KIND(Token_using,          "using"), \
+	TOKEN_KIND(Token_asm,            "asm"), \
+	TOKEN_KIND(Token_volatile,       "volatile"), \
+	TOKEN_KIND(Token_atomic,         "atomic"), \
+	TOKEN_KIND(Token_push_allocator, "push_allocator"), \
+	TOKEN_KIND(Token_push_context,   "push_context"), \
+TOKEN_KIND(Token__KeywordEnd, "_KeywordEnd"), \
+	TOKEN_KIND(Token_Count, "")
+
+typedef enum TokenKind {
+#define TOKEN_KIND(e, s) e
+	TOKEN_KINDS
+#undef TOKEN_KIND
+} TokenKind;
+
+String const token_strings[] = {
+#define TOKEN_KIND(e, s) {cast(u8 *)s, gb_size_of(s)-1}
+	TOKEN_KINDS
+#undef TOKEN_KIND
+};
+
+
+typedef struct TokenPos {
+	String file;
+	isize  line;
+	isize  column;
+} TokenPos;
+
+i32 token_pos_cmp(TokenPos a, TokenPos b) {
+	if (a.line == b.line) {
+		if (a.column == b.column) {
+			isize min_len = gb_min(a.file.len, b.file.len);
+			return gb_memcompare(a.file.text, b.file.text, min_len);
+		}
+		return (a.column < b.column) ? -1 : +1;
+	}
+
+	return (a.line < b.line) ? -1 : +1;
+}
+
+bool token_pos_are_equal(TokenPos a, TokenPos b) {
+	return token_pos_cmp(a, b) == 0;
+}
+
+// NOTE(bill): Text is UTF-8, thus why u8 and not char
+typedef struct Token {
+	TokenKind kind;
+	String string;
+	TokenPos pos;
+} Token;
+
+Token empty_token = {Token_Invalid};
+Token blank_token = {Token_Identifier, {cast(u8 *)"_", 1}};
+
+Token make_token_ident(String s) {
+	Token t = {Token_Identifier, s};
+	return t;
+}
+
+
+typedef struct ErrorCollector {
+	TokenPos prev;
+	i64 count;
+	i64 warning_count;
+	gbMutex mutex;
+} ErrorCollector;
+
+gb_global ErrorCollector global_error_collector;
+
+void init_global_error_collector(void) {
+	gb_mutex_init(&global_error_collector.mutex);
+}
+
+
+void warning(Token token, char *fmt, ...) {
+	gb_mutex_lock(&global_error_collector.mutex);
+
+	global_error_collector.warning_count++;
+	// NOTE(bill): Duplicate error, skip it
+	if (!token_pos_are_equal(global_error_collector.prev, token.pos)) {
+		va_list va;
+
+		global_error_collector.prev = token.pos;
+
+		va_start(va, fmt);
+		gb_printf_err("%.*s(%td:%td) Warning: %s\n",
+		              LIT(token.pos.file), token.pos.line, token.pos.column,
+		              gb_bprintf_va(fmt, va));
+		va_end(va);
+	}
+
+	gb_mutex_unlock(&global_error_collector.mutex);
+}
+
+void error(Token token, char *fmt, ...) {
+	gb_mutex_lock(&global_error_collector.mutex);
+
+	global_error_collector.count++;
+	// NOTE(bill): Duplicate error, skip it
+	if (!token_pos_are_equal(global_error_collector.prev, token.pos)) {
+		va_list va;
+
+		global_error_collector.prev = token.pos;
+
+		va_start(va, fmt);
+		gb_printf_err("%.*s(%td:%td) %s\n",
+		              LIT(token.pos.file), token.pos.line, token.pos.column,
+		              gb_bprintf_va(fmt, va));
+		va_end(va);
+	}
+
+	gb_mutex_unlock(&global_error_collector.mutex);
+}
+
+void syntax_error(Token token, char *fmt, ...) {
+	gb_mutex_lock(&global_error_collector.mutex);
+
+	global_error_collector.count++;
+	// NOTE(bill): Duplicate error, skip it
+	if (!token_pos_are_equal(global_error_collector.prev, token.pos)) {
+		va_list va;
+
+		global_error_collector.prev = token.pos;
+
+		va_start(va, fmt);
+		gb_printf_err("%.*s(%td:%td) Syntax Error: %s\n",
+		              LIT(token.pos.file), token.pos.line, token.pos.column,
+		              gb_bprintf_va(fmt, va));
+		va_end(va);
+	}
+
+	gb_mutex_unlock(&global_error_collector.mutex);
+}
+
+
+void compiler_error(char *fmt, ...) {
+	va_list va;
+
+	va_start(va, fmt);
+	gb_printf_err("Internal Compiler Error: %s\n",
+	              gb_bprintf_va(fmt, va));
+	va_end(va);
+	gb_exit(1);
+}
+
+
+
+
+
+gb_inline bool token_is_literal(Token t) {
+	return gb_is_between(t.kind, Token__LiteralBegin+1, Token__LiteralEnd-1);
+}
+gb_inline bool token_is_operator(Token t) {
+	return gb_is_between(t.kind, Token__OperatorBegin+1, Token__OperatorEnd-1);
+}
+gb_inline bool token_is_keyword(Token t) {
+	return gb_is_between(t.kind, Token__KeywordBegin+1, Token__KeywordEnd-1);
+}
+gb_inline bool token_is_comparison(Token t) {
+	return gb_is_between(t.kind, Token__ComparisonBegin+1, Token__ComparisonEnd-1);
+}
+gb_inline bool token_is_shift(Token t) {
+	return t.kind == Token_Shl || t.kind == Token_Shr;
+}
+
+gb_inline void print_token(Token t) { gb_printf("%.*s\n", LIT(t.string)); }
+
+
+typedef enum TokenizerInitError {
+	TokenizerInit_None,
+
+	TokenizerInit_Invalid,
+	TokenizerInit_NotExists,
+	TokenizerInit_Permission,
+	TokenizerInit_Empty,
+
+	TokenizerInit_Count,
+} TokenizerInitError;
+
+
+typedef struct Tokenizer {
+	String fullpath;
+	u8 *start;
+	u8 *end;
+
+	Rune  curr_rune;   // current character
+	u8 *  curr;        // character pos
+	u8 *  read_curr;   // pos from start
+	u8 *  line;        // current line pos
+	isize line_count;
+
+	isize error_count;
+	Array(String) allocated_strings;
+} Tokenizer;
+
+
+void tokenizer_err(Tokenizer *t, char *msg, ...) {
+	va_list va;
+	isize column = t->read_curr - t->line+1;
+	if (column < 1)
+		column = 1;
+
+	gb_printf_err("%.*s(%td:%td) Syntax error: ", LIT(t->fullpath), t->line_count, column);
+
+	va_start(va, msg);
+	gb_printf_err_va(msg, va);
+	va_end(va);
+
+	gb_printf_err("\n");
+
+	t->error_count++;
+}
+
+void advance_to_next_rune(Tokenizer *t) {
+	if (t->read_curr < t->end) {
+		Rune rune;
+		isize width = 1;
+
+		t->curr = t->read_curr;
+		if (t->curr_rune == '\n') {
+			t->line = t->curr;
+			t->line_count++;
+		}
+		rune = *t->read_curr;
+		if (rune == 0) {
+			tokenizer_err(t, "Illegal character NUL");
+		} else if (rune >= 0x80) { // not ASCII
+			width = gb_utf8_decode(t->read_curr, t->end-t->read_curr, &rune);
+			if (rune == GB_RUNE_INVALID && width == 1)
+				tokenizer_err(t, "Illegal UTF-8 encoding");
+			else if (rune == GB_RUNE_BOM && t->curr-t->start > 0)
+				tokenizer_err(t, "Illegal byte order mark");
+		}
+		t->read_curr += width;
+		t->curr_rune = rune;
+	} else {
+		t->curr = t->end;
+		if (t->curr_rune == '\n') {
+			t->line = t->curr;
+			t->line_count++;
+		}
+		t->curr_rune = GB_RUNE_EOF;
+	}
+}
+
+TokenizerInitError init_tokenizer(Tokenizer *t, String fullpath) {
+	TokenizerInitError err = TokenizerInit_None;
+
+	char *c_str = gb_alloc_array(heap_allocator(), char, fullpath.len+1);
+	memcpy(c_str, fullpath.text, fullpath.len);
+	c_str[fullpath.len] = '\0';
+
+	// TODO(bill): Memory map rather than copy contents
+	gbFileContents fc = gb_file_read_contents(heap_allocator(), true, c_str);
+	gb_zero_item(t);
+	if (fc.data != NULL) {
+		t->start = cast(u8 *)fc.data;
+		t->line = t->read_curr = t->curr = t->start;
+		t->end = t->start + fc.size;
+		t->fullpath = fullpath;
+		t->line_count = 1;
+
+		advance_to_next_rune(t);
+		if (t->curr_rune == GB_RUNE_BOM) {
+			advance_to_next_rune(t); // Ignore BOM at file beginning
+		}
+
+		array_init(&t->allocated_strings, heap_allocator());
+	} else {
+		gbFile f = {0};
+		gbFileError file_err = gb_file_open(&f, c_str);
+
+		switch (file_err) {
+		case gbFileError_Invalid:    err = TokenizerInit_Invalid;    break;
+		case gbFileError_NotExists:  err = TokenizerInit_NotExists;  break;
+		case gbFileError_Permission: err = TokenizerInit_Permission; break;
+		}
+
+		if (err == TokenizerInit_None && gb_file_size(&f) == 0) {
+			err = TokenizerInit_Empty;
+		}
+
+		gb_file_close(&f);
+	}
+
+	gb_free(heap_allocator(), c_str);
+	return err;
+}
+
+gb_inline void destroy_tokenizer(Tokenizer *t) {
+	if (t->start != NULL) {
+		gb_free(heap_allocator(), t->start);
+	}
+	for_array(i, t->allocated_strings) {
+		gb_free(heap_allocator(), t->allocated_strings.e[i].text);
+	}
+	array_free(&t->allocated_strings);
+}
+
+void tokenizer_skip_whitespace(Tokenizer *t) {
+	while (rune_is_whitespace(t->curr_rune)) {
+		advance_to_next_rune(t);
+	}
+}
+
+gb_inline i32 digit_value(Rune r) {
+	if (gb_char_is_digit(cast(char)r)) {
+		return r - '0';
+	} else if (gb_is_between(cast(char)r, 'a', 'f')) {
+		return r - 'a' + 10;
+	} else if (gb_is_between(cast(char)r, 'A', 'F')) {
+		return r - 'A' + 10;
+	}
+	return 16; // NOTE(bill): Larger than highest possible
+}
+
+gb_inline void scan_mantissa(Tokenizer *t, i32 base) {
+	// TODO(bill): Allow for underscores in numbers as a number separator
+	// TODO(bill): Is this a good idea?
+	// while (digit_value(t->curr_rune) < base || t->curr_rune == '_')
+	while (digit_value(t->curr_rune) < base) {
+		advance_to_next_rune(t);
+	}
+}
+
+
+Token scan_number_to_token(Tokenizer *t, bool seen_decimal_point) {
+	Token token = {0};
+	token.kind = Token_Integer;
+	token.string = make_string(t->curr, 1);
+	token.pos.file = t->fullpath;
+	token.pos.line = t->line_count;
+	token.pos.column = t->curr-t->line+1;
+
+	if (seen_decimal_point) {
+		token.kind = Token_Float;
+		scan_mantissa(t, 10);
+		goto exponent;
+	}
+
+	if (t->curr_rune == '0') {
+		u8 *prev = t->curr;
+		advance_to_next_rune(t);
+		if (t->curr_rune == 'b') { // Binary
+			advance_to_next_rune(t);
+			scan_mantissa(t, 2);
+			if (t->curr - prev <= 2)
+				token.kind = Token_Invalid;
+		} else if (t->curr_rune == 'o') { // Octal
+			advance_to_next_rune(t);
+			scan_mantissa(t, 8);
+			if (t->curr - prev <= 2)
+				token.kind = Token_Invalid;
+		} else if (t->curr_rune == 'd') { // Decimal
+			advance_to_next_rune(t);
+			scan_mantissa(t, 10);
+			if (t->curr - prev <= 2)
+				token.kind = Token_Invalid;
+		} else if (t->curr_rune == 'x') { // Hexadecimal
+			advance_to_next_rune(t);
+			scan_mantissa(t, 16);
+			if (t->curr - prev <= 2)
+				token.kind = Token_Invalid;
+		} else {
+			seen_decimal_point = false;
+			scan_mantissa(t, 10);
+
+			if (t->curr_rune == '.' || t->curr_rune == 'e' || t->curr_rune == 'E') {
+				seen_decimal_point = true;
+				goto fraction;
+			}
+		}
+
+		token.string.len = t->curr - token.string.text;
+		return token;
+	}
+
+	scan_mantissa(t, 10);
+
+fraction:
+	if (t->curr_rune == '.') {
+		token.kind = Token_Float;
+		advance_to_next_rune(t);
+		scan_mantissa(t, 10);
+	}
+
+exponent:
+	if (t->curr_rune == 'e' || t->curr_rune == 'E') {
+		token.kind = Token_Float;
+		advance_to_next_rune(t);
+		if (t->curr_rune == '-' || t->curr_rune == '+') {
+			advance_to_next_rune(t);
+		}
+		scan_mantissa(t, 10);
+	}
+
+	token.string.len = t->curr - token.string.text;
+	return token;
+}
+
+// Quote == " for string
+bool scan_escape(Tokenizer *t, Rune quote) {
+	isize len = 0;
+	u32 base = 0, max = 0, x = 0;
+
+	Rune r = t->curr_rune;
+	if (r == 'a'  ||
+	    r == 'b'  ||
+	    r == 'f'  ||
+	    r == 'n'  ||
+	    r == 'r'  ||
+	    r == 't'  ||
+	    r == 'v'  ||
+	    r == '\\' ||
+	    r == quote) {
+		advance_to_next_rune(t);
+		return true;
+	} else if (gb_is_between(r, '0', '7')) {
+		len = 3; base = 8; max = 255;
+	} else if (r == 'x') {
+		advance_to_next_rune(t);
+		len = 2; base = 16; max = 255;
+	} else if (r == 'u') {
+		advance_to_next_rune(t);
+		len = 4; base = 16; max = GB_RUNE_MAX;
+	} else if (r == 'U') {
+		advance_to_next_rune(t);
+		len = 8; base = 16; max = GB_RUNE_MAX;
+	} else {
+		if (t->curr_rune < 0)
+			tokenizer_err(t, "Escape sequence was not terminated");
+		else
+			tokenizer_err(t, "Unknown escape sequence");
+		return false;
+	}
+
+	while (len --> 0) {
+		u32 d = cast(u32)digit_value(t->curr_rune);
+		if (d >= base) {
+			if (t->curr_rune < 0)
+				tokenizer_err(t, "Escape sequence was not terminated");
+			else
+				tokenizer_err(t, "Illegal character %d in escape sequence", t->curr_rune);
+			return false;
+		}
+
+		x = x*base + d;
+		advance_to_next_rune(t);
+	}
+
+	return true;
+}
+
+gb_inline TokenKind token_kind_variant2(Tokenizer *t, TokenKind a, TokenKind b) {
+	if (t->curr_rune == '=') {
+		advance_to_next_rune(t);
+		return b;
+	}
+	return a;
+}
+
+
+gb_inline TokenKind token_kind_variant3(Tokenizer *t, TokenKind a, TokenKind b, Rune ch_c, TokenKind c) {
+	if (t->curr_rune == '=') {
+		advance_to_next_rune(t);
+		return b;
+	}
+	if (t->curr_rune == ch_c) {
+		advance_to_next_rune(t);
+		return c;
+	}
+	return a;
+}
+
+gb_inline TokenKind token_kind_variant4(Tokenizer *t, TokenKind a, TokenKind b, Rune ch_c, TokenKind c, Rune ch_d, TokenKind d) {
+	if (t->curr_rune == '=') {
+		advance_to_next_rune(t);
+		return b;
+	} else if (t->curr_rune == ch_c) {
+		advance_to_next_rune(t);
+		return c;
+	} else if (t->curr_rune == ch_d) {
+		advance_to_next_rune(t);
+		return d;
+	}
+	return a;
+}
+
+
+gb_inline TokenKind token_kind_dub_eq(Tokenizer *t, Rune sing_rune, TokenKind sing, TokenKind sing_eq, TokenKind dub, TokenKind dub_eq) {
+	if (t->curr_rune == '=') {
+		advance_to_next_rune(t);
+		return sing_eq;
+	} else if (t->curr_rune == sing_rune) {
+		advance_to_next_rune(t);
+		if (t->curr_rune == '=') {
+			advance_to_next_rune(t);
+			return dub_eq;
+		}
+		return dub;
+	}
+	return sing;
+}
+
+Token tokenizer_get_token(Tokenizer *t) {
+	Token token = {0};
+	Rune curr_rune;
+
+	tokenizer_skip_whitespace(t);
+	token.string = make_string(t->curr, 1);
+	token.pos.file = t->fullpath;
+	token.pos.line = t->line_count;
+	token.pos.column = t->curr - t->line + 1;
+
+	curr_rune = t->curr_rune;
+	if (rune_is_letter(curr_rune)) {
+		token.kind = Token_Identifier;
+		while (rune_is_letter(t->curr_rune) || rune_is_digit(t->curr_rune)) {
+			advance_to_next_rune(t);
+		}
+
+		token.string.len = t->curr - token.string.text;
+
+		// NOTE(bill): All keywords are > 1
+		if (token.string.len > 1) {
+			if (str_eq(token.string, token_strings[Token_as])) {
+				token.kind = Token_as;
+			} else if (str_eq(token.string, token_strings[Token_transmute])) {
+				token.kind = Token_transmute;
+			} else if (str_eq(token.string, token_strings[Token_down_cast])) {
+				token.kind = Token_down_cast;
+			} else if (str_eq(token.string, token_strings[Token_union_cast])) {
+				token.kind = Token_union_cast;
+			} else {
+				for (i32 k = Token__KeywordBegin+1; k < Token__KeywordEnd; k++) {
+					if (str_eq(token.string, token_strings[k])) {
+						token.kind = cast(TokenKind)k;
+						break;
+					}
+				}
+			}
+		}
+
+	} else if (gb_is_between(curr_rune, '0', '9')) {
+		token = scan_number_to_token(t, false);
+	} else {
+		advance_to_next_rune(t);
+		switch (curr_rune) {
+		case GB_RUNE_EOF:
+			token.kind = Token_EOF;
+			break;
+
+		case '\'':
+			token.kind = Token_Prime;
+			if (t->curr_rune == '\'') {
+				advance_to_next_rune(t);
+				token.kind = Token_DoublePrime;
+			}
+			break;
+
+		case '`': // Raw String Literal
+		case '"': // String Literal
+		{
+			Rune quote = curr_rune;
+			token.kind = Token_String;
+			if (curr_rune == '"') {
+				for (;;) {
+					Rune r = t->curr_rune;
+					if (r == '\n' || r < 0) {
+						tokenizer_err(t, "String literal not terminated");
+						break;
+					}
+					advance_to_next_rune(t);
+					if (r == quote)
+						break;
+					if (r == '\\')
+						scan_escape(t, '"');
+				}
+			} else {
+				for (;;) {
+					Rune r = t->curr_rune;
+					if (r < 0) {
+						tokenizer_err(t, "String literal not terminated");
+						break;
+					}
+					advance_to_next_rune(t);
+					if (r == quote)
+						break;
+				}
+			}
+			token.string.len = t->curr - token.string.text;
+			i32 success = unquote_string(heap_allocator(), &token.string);
+			if (success > 0) {
+				if (success == 2) {
+					array_add(&t->allocated_strings, token.string);
+				}
+				return token;
+			} else {
+				tokenizer_err(t, "Invalid string literal");
+			}
+		} break;
+
+		case '.':
+			token.kind = Token_Period; // Default
+			if (gb_is_between(t->curr_rune, '0', '9')) { // Might be a number
+				token = scan_number_to_token(t, true);
+			} else if (t->curr_rune == '.') { // Could be an ellipsis
+				advance_to_next_rune(t);
+				token.kind = Token_Ellipsis;
+				if (t->curr_rune == '<') {
+					advance_to_next_rune(t);
+					token.kind = Token_RangeExclusive;
+				}
+			}
+			break;
+
+		case '#': token.kind = Token_Hash;         break;
+		case '@': token.kind = Token_At;           break;
+		case '^': token.kind = Token_Pointer;      break;
+		case '?': token.kind = Token_Maybe;        break;
+		case ';': token.kind = Token_Semicolon;    break;
+		case ',': token.kind = Token_Comma;        break;
+		case '(': token.kind = Token_OpenParen;    break;
+		case ')': token.kind = Token_CloseParen;   break;
+		case '[': token.kind = Token_OpenBracket;  break;
+		case ']': token.kind = Token_CloseBracket; break;
+		case '{': token.kind = Token_OpenBrace;    break;
+		case '}': token.kind = Token_CloseBrace;   break;
+		case ':': token.kind = Token_Colon;        break;
+
+		case '*': token.kind = token_kind_variant2(t, Token_Mul,   Token_MulEq);     break;
+		case '%': token.kind = token_kind_variant2(t, Token_Mod,   Token_ModEq);     break;
+		case '=': token.kind = token_kind_variant2(t, Token_Eq,    Token_CmpEq);     break;
+		case '~': token.kind = token_kind_variant2(t, Token_Xor,   Token_XorEq);     break;
+		case '!': token.kind = token_kind_variant2(t, Token_Not,   Token_NotEq);     break;
+		case '+': token.kind = token_kind_variant3(t, Token_Add,   Token_AddEq, '+', Token_Increment); break;
+		case '-': token.kind = token_kind_variant4(t, Token_Sub,   Token_SubEq, '-', Token_Decrement, '>', Token_ArrowRight); break;
+		case '/': {
+			if (t->curr_rune == '/') {
+				while (t->curr_rune != '\n') {
+					advance_to_next_rune(t);
+				}
+				token.kind = Token_Comment;
+			} else if (t->curr_rune == '*') {
+				isize comment_scope = 1;
+				advance_to_next_rune(t);
+				while (comment_scope > 0) {
+					if (t->curr_rune == '/') {
+						advance_to_next_rune(t);
+						if (t->curr_rune == '*') {
+							advance_to_next_rune(t);
+							comment_scope++;
+						}
+					} else if (t->curr_rune == '*') {
+						advance_to_next_rune(t);
+						if (t->curr_rune == '/') {
+							advance_to_next_rune(t);
+							comment_scope--;
+						}
+					} else {
+						advance_to_next_rune(t);
+					}
+				}
+				token.kind = Token_Comment;
+			} else {
+				token.kind = token_kind_variant2(t, Token_Quo, Token_QuoEq);
+			}
+		} break;
+
+		case '<':
+			if (t->curr_rune == '-') {
+				token.kind = Token_ArrowLeft;
+			} else {
+				token.kind = token_kind_dub_eq(t, '<', Token_Lt, Token_LtEq, Token_Shl, Token_ShlEq);
+			}
+			break;
+		case '>':
+			token.kind = token_kind_dub_eq(t, '>', Token_Gt, Token_GtEq, Token_Shr, Token_ShrEq);
+			break;
+
+		case '&':
+			token.kind = Token_And;
+			if (t->curr_rune == '~') {
+				token.kind = Token_AndNot;
+				advance_to_next_rune(t);
+				if (t->curr_rune == '=') {
+					token.kind = Token_AndNotEq;
+					advance_to_next_rune(t);
+				}
+			} else {
+				token.kind = token_kind_dub_eq(t, '&', Token_And, Token_AndEq, Token_CmpAnd, Token_CmpAndEq);
+			}
+			break;
+
+		case '|': token.kind = token_kind_dub_eq(t, '|', Token_Or, Token_OrEq, Token_CmpOr, Token_CmpOrEq); break;
+
+		default:
+			if (curr_rune != GB_RUNE_BOM) {
+				u8 str[4] = {0};
+				int len = cast(int)gb_utf8_encode_rune(str, curr_rune);
+				tokenizer_err(t, "Illegal character: %.*s (%d) ", len, str, curr_rune);
+			}
+			token.kind = Token_Invalid;
+			break;
+		}
+	}
+
+	token.string.len = t->curr - token.string.text;
+	return token;
+}

+ 66 - 0
src/unicode.c

@@ -0,0 +1,66 @@
+#pragma warning(push)
+#pragma warning(disable: 4245)
+
+// #include "utf8proc/utf8proc.h"
+#include "utf8proc/utf8proc.c"
+
+#pragma warning(pop)
+
+bool rune_is_letter(Rune r) {
+	if ((r < 0x80 && gb_char_is_alpha(cast(char)r)) ||
+	    r == '_') {
+		return true;
+	}
+	switch (utf8proc_category(r)) {
+	case UTF8PROC_CATEGORY_LU:
+	case UTF8PROC_CATEGORY_LL:
+	case UTF8PROC_CATEGORY_LT:
+	case UTF8PROC_CATEGORY_LM:
+	case UTF8PROC_CATEGORY_LO:
+		return true;
+	}
+	return false;
+}
+
+bool rune_is_digit(Rune r) {
+	if (r < 0x80 && gb_is_between(r, '0', '9')) {
+		return true;
+	}
+	return utf8proc_category(r) == UTF8PROC_CATEGORY_ND;
+}
+
+bool rune_is_whitespace(Rune r) {
+	switch (r) {
+	case ' ':
+	case '\t':
+	case '\n':
+	case '\r':
+		return true;
+	}
+	return false;
+}
+
+
+bool is_string_an_identifier(String s) {
+	if (s.len < 1) {
+		return false;
+	}
+	isize offset = 0;
+	while (offset < s.len) {
+		bool ok = false;
+		Rune r = -1;
+		isize size = gb_utf8_decode(s.text+offset, s.len-offset, &r);
+		if (offset == 0) {
+			ok = rune_is_letter(r);
+		} else {
+			ok = rune_is_letter(r) || rune_is_digit(r);
+		}
+
+		if (!ok) {
+			return false;
+		}
+		offset += size;
+	}
+
+	return offset == s.len;
+}