aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGinger Bill <bill@gingerbill.org>2016-11-23 14:41:20 +0000
committerGinger Bill <bill@gingerbill.org>2016-11-23 14:41:20 +0000
commit7792f009b8d8fbc4949b8110ebbcffc943878cae (patch)
tree63379f0cb69726648000898996256937c17bedc4
parent4110324588bc68b6f10435c462b041cf9c34336d (diff)
Numpty forgot to add .c files
-rw-r--r--README.md2
-rw-r--r--logo-slim.pngbin0 -> 251710 bytes
-rw-r--r--src/array.c235
-rw-r--r--src/checker/checker.c1353
-rw-r--r--src/checker/decl.c545
-rw-r--r--src/checker/entity.c179
-rw-r--r--src/checker/expr.c4465
-rw-r--r--src/checker/stmt.c1130
-rw-r--r--src/checker/types.c1487
-rw-r--r--src/common.c195
-rw-r--r--src/exact_value.c400
-rw-r--r--src/main.c272
-rw-r--r--src/old_vm.c1305
-rw-r--r--src/parser.c3250
-rw-r--r--src/printer.c221
-rw-r--r--src/ssa.c5419
-rw-r--r--src/ssa_opt.c493
-rw-r--r--src/ssa_print.c1439
-rw-r--r--src/string.c422
-rw-r--r--src/timings.c105
-rw-r--r--src/tokenizer.c816
-rw-r--r--src/unicode.c66
22 files changed, 23799 insertions, 0 deletions
diff --git a/README.md b/README.md
index 0a5496ba6..5b962f919 100644
--- a/README.md
+++ b/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:
diff --git a/logo-slim.png b/logo-slim.png
new file mode 100644
index 000000000..2b70e6a0c
--- /dev/null
+++ b/logo-slim.png
Binary files differ
diff --git a/src/array.c b/src/array.c
new file mode 100644
index 000000000..7e185e872
--- /dev/null
+++ b/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
diff --git a/src/checker/checker.c b/src/checker/checker.c
new file mode 100644
index 000000000..889efa1d3
--- /dev/null
+++ b/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);
+}
+
+
+
diff --git a/src/checker/decl.c b/src/checker/decl.c
new file mode 100644
index 000000000..f5a5daad6
--- /dev/null
+++ b/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;
+}
+
+
+
diff --git a/src/checker/entity.c b/src/checker/entity.c
new file mode 100644
index 000000000..df1ecf28d
--- /dev/null
+++ b/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);
+}
+
diff --git a/src/checker/expr.c b/src/checker/expr.c
new file mode 100644
index 000000000..6f16da451
--- /dev/null
+++ b/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);
+}
diff --git a/src/checker/stmt.c b/src/checker/stmt.c
new file mode 100644
index 000000000..ee56c3cd1
--- /dev/null
+++ b/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;
+ }
+}
diff --git a/src/checker/types.c b/src/checker/types.c
new file mode 100644
index 000000000..f51cbb660
--- /dev/null
+++ b/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);
+}
+
+
diff --git a/src/common.c b/src/common.c
new file mode 100644
index 000000000..9b70722d1
--- /dev/null
+++ b/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"
diff --git a/src/exact_value.c b/src/exact_value.c
new file mode 100644
index 000000000..313cda694
--- /dev/null
+++ b/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;
+}
diff --git a/src/main.c b/src/main.c
new file mode 100644
index 000000000..cf5d2379b
--- /dev/null
+++ b/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
diff --git a/src/old_vm.c b/src/old_vm.c
new file mode 100644
index 000000000..071af7ae3
--- /dev/null
+++ b/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(")");
+ }
+ }
+}
diff --git a/src/parser.c b/src/parser.c
new file mode 100644
index 000000000..1ea8ba430
--- /dev/null
+++ b/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;
+}
+
+
diff --git a/src/printer.c b/src/printer.c
new file mode 100644
index 000000000..4d7184631
--- /dev/null
+++ b/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);
+}
diff --git a/src/ssa.c b/src/ssa.c
new file mode 100644
index 000000000..b34bc7c51
--- /dev/null
+++ b/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");
+}
+
diff --git a/src/ssa_opt.c b/src/ssa_opt.c
new file mode 100644
index 000000000..5fccbfcb6
--- /dev/null
+++ b/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);
+ }
+}
diff --git a/src/ssa_print.c b/src/ssa_print.c
new file mode 100644
index 000000000..e6e6532d5
--- /dev/null
+++ b/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);
+}
diff --git a/src/string.c b/src/string.c
new file mode 100644
index 000000000..b52b8886e
--- /dev/null
+++ b/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;
+}
diff --git a/src/timings.c b/src/timings.c
new file mode 100644
index 000000000..a1eecc01a
--- /dev/null
+++ b/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));
+ }
+}
diff --git a/src/tokenizer.c b/src/tokenizer.c
new file mode 100644
index 000000000..edf6e9721
--- /dev/null
+++ b/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;
+}
diff --git a/src/unicode.c b/src/unicode.c
new file mode 100644
index 000000000..5c9f91f46
--- /dev/null
+++ b/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;
+}