ソースを参照

Merge pull request #4855 from odin-lang/bill/canonical-type-hashing

Deterministic Canonical Naming for Link Names and Types
gingerBill 6 ヶ月 前
コミット
82ddf358d0

+ 8 - 2
src/check_decl.cpp

@@ -1742,8 +1742,8 @@ gb_internal void add_deps_from_child_to_parent(DeclInfo *decl) {
 			rw_mutex_shared_lock(&decl->type_info_deps_mutex);
 			rw_mutex_lock(&decl->parent->type_info_deps_mutex);
 
-			for (Type *t : decl->type_info_deps) {
-				ptr_set_add(&decl->parent->type_info_deps, t);
+			for (auto const &tt : decl->type_info_deps) {
+				type_set_add(&decl->parent->type_info_deps, tt);
 			}
 
 			rw_mutex_unlock(&decl->parent->type_info_deps_mutex);
@@ -1784,6 +1784,10 @@ gb_internal bool check_proc_body(CheckerContext *ctx_, Token token, DeclInfo *de
 	ctx->curr_proc_sig  = type;
 	ctx->curr_proc_calling_convention = type->Proc.calling_convention;
 
+	if (decl->parent && decl->entity && decl->parent->entity) {
+		decl->entity->parent_proc_decl = decl->parent;
+	}
+
 	if (ctx->pkg->name != "runtime") {
 		switch (type->Proc.calling_convention) {
 		case ProcCC_None:
@@ -1873,6 +1877,8 @@ gb_internal bool check_proc_body(CheckerContext *ctx_, Token token, DeclInfo *de
 
 	check_open_scope(ctx, body);
 	{
+		ctx->scope->decl_info = decl;
+
 		for (auto const &entry : using_entities) {
 			Entity *uvar = entry.uvar;
 			Entity *prev = scope_insert(ctx->scope, uvar);

+ 1 - 1
src/check_expr.cpp

@@ -345,7 +345,7 @@ gb_internal void check_scope_decls(CheckerContext *c, Slice<Ast *> const &nodes,
 	check_collect_entities(c, nodes);
 
 	for (auto const &entry : s->elements) {
-		Entity *e = entry.value;
+		Entity *e = entry.value;\
 		switch (e->kind) {
 		case Entity_Constant:
 		case Entity_TypeName:

+ 91 - 47
src/checker.cpp

@@ -3,7 +3,10 @@
 #include "entity.cpp"
 #include "types.cpp"
 
-String get_final_microarchitecture();
+
+gb_internal u64 type_hash_canonical_type(Type *type);
+
+gb_internal String get_final_microarchitecture();
 
 gb_internal void check_expr(CheckerContext *c, Operand *operand, Ast *expression);
 gb_internal void check_expr_or_type(CheckerContext *c, Operand *operand, Ast *expression, Type *type_hint=nullptr);
@@ -170,7 +173,7 @@ gb_internal void init_decl_info(DeclInfo *d, Scope *scope, DeclInfo *parent) {
 	d->parent = parent;
 	d->scope  = scope;
 	ptr_set_init(&d->deps, 0);
-	ptr_set_init(&d->type_info_deps, 0);
+	type_set_init(&d->type_info_deps, 0);
 	d->labels.allocator = heap_allocator();
 	d->variadic_reuses.allocator = heap_allocator();
 	d->variadic_reuse_max_bytes = 0;
@@ -355,6 +358,10 @@ gb_internal void check_open_scope(CheckerContext *c, Ast *node) {
 		scope->flags |= ScopeFlag_Type;
 		break;
 	}
+	if (c->decl && c->decl->proc_lit) {
+		// Number the scopes within a procedure body depth-first
+		scope->index = c->decl->scope_index++;
+	}
 	c->scope = scope;
 	c->state_flags |= StateFlag_bounds_check;
 }
@@ -825,11 +832,17 @@ gb_internal void add_dependency(CheckerInfo *info, DeclInfo *d, Entity *e) {
 	rw_mutex_unlock(&d->deps_mutex);
 }
 gb_internal void add_type_info_dependency(CheckerInfo *info, DeclInfo *d, Type *type) {
-	if (d == nullptr) {
+	if (d == nullptr || type == nullptr) {
 		return;
 	}
+	if (type->kind == Type_Named) {
+		Entity *e = type->Named.type_name;
+		if (e->TypeName.is_type_alias) {
+			type = type->Named.base;
+		}
+	}
 	rw_mutex_lock(&d->type_info_deps_mutex);
-	ptr_set_add(&d->type_info_deps, type);
+	type_set_add(&d->type_info_deps, type);
 	rw_mutex_unlock(&d->type_info_deps_mutex);
 }
 
@@ -1358,8 +1371,12 @@ gb_internal void init_checker_info(CheckerInfo *i) {
 	string_map_init(&i->foreigns);
 	// map_init(&i->gen_procs);
 	map_init(&i->gen_types);
+
 	array_init(&i->type_info_types, a);
-	map_init(&i->type_info_map);
+	type_set_init(&i->min_dep_type_info_set);
+	map_init(&i->minimum_dependency_type_info_index_map);
+
+	// map_init(&i->type_info_map);
 	string_map_init(&i->files);
 	string_map_init(&i->packages);
 	array_init(&i->variable_init_order, a);
@@ -1392,8 +1409,11 @@ gb_internal void destroy_checker_info(CheckerInfo *i) {
 	string_map_destroy(&i->foreigns);
 	// map_destroy(&i->gen_procs);
 	map_destroy(&i->gen_types);
+
 	array_free(&i->type_info_types);
-	map_destroy(&i->type_info_map);
+	type_set_destroy(&i->min_dep_type_info_set);
+	map_destroy(&i->minimum_dependency_type_info_index_map);
+
 	string_map_destroy(&i->files);
 	string_map_destroy(&i->packages);
 	array_free(&i->variable_init_order);
@@ -1627,41 +1647,36 @@ gb_internal void check_remove_expr_info(CheckerContext *c, Ast *e) {
 	}
 }
 
-
-gb_internal isize type_info_index(CheckerInfo *info, Type *type, bool error_on_failure) {
-	type = default_type(type);
-	if (type == t_llvm_bool) {
-		type = t_bool;
-	}
-
-	mutex_lock(&info->type_info_mutex);
+gb_internal isize type_info_index(CheckerInfo *info, TypeInfoPair pair, bool error_on_failure) {
+	mutex_lock(&info->minimum_dependency_type_info_mutex);
 
 	isize entry_index = -1;
-	isize *found_entry_index = map_get(&info->type_info_map, type);
+	uintptr hash = cast(uintptr)pair.hash;
+	isize *found_entry_index = map_get(&info->minimum_dependency_type_info_index_map, hash);
 	if (found_entry_index) {
 		entry_index = *found_entry_index;
 	}
-	if (entry_index < 0) {
-		// NOTE(bill): Do manual linear search
-		for (auto const &e : info->type_info_map) {
-			if (are_types_identical_unique_tuples(e.key, type)) {
-				entry_index = e.value;
-				// NOTE(bill): Add it to the search map
-				map_set(&info->type_info_map, type, entry_index);
-				break;
-			}
-		}
-	}
-
-	mutex_unlock(&info->type_info_mutex);
+	mutex_unlock(&info->minimum_dependency_type_info_mutex);
 
 	if (error_on_failure && entry_index < 0) {
-		compiler_error("Type_Info for '%s' could not be found", type_to_string(type));
+		compiler_error("Type_Info for '%s' could not be found", type_to_string(pair.type));
 	}
 	return entry_index;
 }
 
 
+gb_internal isize type_info_index(CheckerInfo *info, Type *type, bool error_on_failure) {
+	type = default_type(type);
+	if (type == t_llvm_bool) {
+		type = t_bool;
+	}
+
+	u64 hash = type_hash_canonical_type(type);
+	return type_info_index(info, {type, hash}, error_on_failure);
+}
+
+
+
 gb_internal void add_untyped(CheckerContext *c, Ast *expr, AddressingMode mode, Type *type, ExactValue const &value) {
 	if (expr == nullptr) {
 		return;
@@ -2013,8 +2028,12 @@ gb_internal void add_type_info_type_internal(CheckerContext *c, Type *t) {
 	}
 
 	add_type_info_dependency(c->info, c->decl, t);
-
+#if 0
 	MUTEX_GUARD_BLOCK(&c->info->type_info_mutex) {
+		if (type_set_update(&c->info->type_info_set, t)) {
+			// return;
+		}
+
 		auto found = map_get(&c->info->type_info_map, t);
 		if (found != nullptr) {
 			// Types have already been added
@@ -2037,7 +2056,8 @@ gb_internal void add_type_info_type_internal(CheckerContext *c, Type *t) {
 			// Unique entry
 			// NOTE(bill): map entries grow linearly and in order
 			ti_index = c->info->type_info_types.count;
-			array_add(&c->info->type_info_types, t);
+			TypeInfoPair tt = {t, type_hash_canonical_type(t)};
+			array_add(&c->info->type_info_types, tt);
 		}
 		map_set(&c->checker->info.type_info_map, t, ti_index);
 
@@ -2232,6 +2252,7 @@ gb_internal void add_type_info_type_internal(CheckerContext *c, Type *t) {
 		GB_PANIC("Unhandled type: %*.s %d", LIT(type_strings[bt->kind]), bt->kind);
 		break;
 	}
+#endif
 }
 
 
@@ -2289,19 +2310,7 @@ gb_internal void add_min_dep_type_info(Checker *c, Type *t) {
 		return;
 	}
 
-	auto *set = &c->info.minimum_dependency_type_info_set;
-
-	isize ti_index = type_info_index(&c->info, t, false);
-	if (ti_index < 0) {
-		add_type_info_type(&c->builtin_ctx, t); // Missing the type information
-		ti_index = type_info_index(&c->info, t, false);
-	}
-	GB_ASSERT(ti_index >= 0);
-	// IMPORTANT NOTE(bill): this must be copied as `map_set` takes a const ref
-	// and effectively assigns the `+1` of the value
-	isize const count = set->count;
-	if (map_set_if_not_previously_exists(set, ti_index+1, count)) {
-		// Type already exists;
+	if (type_set_update(&c->info.min_dep_type_info_set, t)) {
 		return;
 	}
 
@@ -2501,8 +2510,8 @@ gb_internal void add_dependency_to_set(Checker *c, Entity *entity) {
 	if (decl == nullptr) {
 		return;
 	}
-	for (Type *t : decl->type_info_deps) {
-		add_min_dep_type_info(c, t);
+	for (TypeInfoPair const tt : decl->type_info_deps) {
+		add_min_dep_type_info(c, tt.type);
 	}
 
 	for (Entity *e : decl->deps) {
@@ -2702,7 +2711,6 @@ gb_internal void generate_minimum_dependency_set(Checker *c, Entity *start) {
 	isize min_dep_set_cap = next_pow2_isize(entity_count*4); // empirically determined factor
 
 	ptr_set_init(&c->info.minimum_dependency_set, min_dep_set_cap);
-	map_init(&c->info.minimum_dependency_type_info_set);
 
 #define FORCE_ADD_RUNTIME_ENTITIES(condition, ...) do {                                              \
 	if (condition) {                                                                             \
@@ -3894,6 +3902,7 @@ gb_internal DECL_ATTRIBUTE_PROC(type_decl_attribute) {
 #include "check_expr.cpp"
 #include "check_builtin.cpp"
 #include "check_type.cpp"
+#include "name_canonicalization.cpp"
 #include "check_decl.cpp"
 #include "check_stmt.cpp"
 
@@ -6724,6 +6733,41 @@ gb_internal void check_parsed_files(Checker *c) {
 		add_type_and_value(&c->builtin_ctx, u.expr, u.info->mode, u.info->type, u.info->value);
 	}
 
+	TIME_SECTION("initialize and check for collisions in type info array");
+	{
+		for (auto const &tt : c->info.min_dep_type_info_set) {
+			array_add(&c->info.type_info_types, tt);
+		}
+		array_sort(c->info.type_info_types, type_info_pair_cmp);
+
+		map_reserve(&c->info.minimum_dependency_type_info_index_map, c->info.type_info_types.count);
+
+		for_array(i, c->info.type_info_types) {
+			auto const &tt = c->info.type_info_types[i];
+			bool exists = map_set_if_not_previously_exists(&c->info.minimum_dependency_type_info_index_map, cast(uintptr)tt.hash, i);
+			if (!exists) {
+				continue;
+			}
+			for (auto const &entry : c->info.minimum_dependency_type_info_index_map) {
+				if (entry.key != cast(uintptr)tt.hash) {
+					continue;
+				}
+				auto const &other = c->info.type_info_types[entry.value];
+				if (are_types_identical_unique_tuples(tt.type, other.type)) {
+					continue;
+				}
+				gbString t = temp_canonical_string(tt.type);
+				gbString o = temp_canonical_string(other.type);
+				GB_PANIC("%s (%s) %llu vs %s (%s) %llu",
+				         type_to_string(tt.type, false),    t, cast(unsigned long long)tt.hash,
+				         type_to_string(other.type, false), o, cast(unsigned long long)other.hash);
+			}
+		}
+
+		GB_ASSERT(c->info.minimum_dependency_type_info_index_map.count <= c->info.type_info_types.count);
+	}
+
+
 	TIME_SECTION("sort init and fini procedures");
 	check_sort_init_and_fini_procedures(c);
 

+ 18 - 7
src/checker.hpp

@@ -167,6 +167,7 @@ typedef DECL_ATTRIBUTE_PROC(DeclAttributeProc);
 
 gb_internal void check_decl_attributes(CheckerContext *c, Array<Ast *> const &attributes, DeclAttributeProc *proc, AttributeContext *ac);
 
+#include "name_canonicalization.hpp"
 
 enum ProcCheckedState : u8 {
 	ProcCheckedState_Unchecked,
@@ -221,13 +222,15 @@ struct DeclInfo {
 	RwMutex          deps_mutex;
 	PtrSet<Entity *> deps;
 
-	RwMutex     type_info_deps_mutex;
-	PtrSet<Type *>    type_info_deps;
+	RwMutex type_info_deps_mutex;
+	TypeSet type_info_deps;
 
 	BlockingMutex type_and_value_mutex;
 
 	Array<BlockLabel> labels;
 
+	i32 scope_index;
+
 	Array<VariadicReuseData> variadic_reuses;
 	i64 variadic_reuse_max_bytes;
 	i64 variadic_reuse_max_align;
@@ -272,10 +275,14 @@ struct Scope {
 	std::atomic<Scope *> next;
 	std::atomic<Scope *> head_child;
 
+	i32 index; // within a procedure
+
 	RwMutex mutex;
 	StringMap<Entity *> elements;
 	PtrSet<Scope *> imported;
 
+	DeclInfo *decl_info;
+
 	i32             flags; // ScopeFlag
 	union {
 		AstPackage *pkg;
@@ -421,8 +428,10 @@ struct CheckerInfo {
 	Scope *               init_scope;
 	Entity *              entry_point;
 	PtrSet<Entity *>      minimum_dependency_set;
-	PtrMap</*type info index*/isize, /*min dep index*/isize>  minimum_dependency_type_info_set;
-
+	BlockingMutex minimum_dependency_type_info_mutex;
+	PtrMap</*type info hash*/uintptr, /*min dep index*/isize> minimum_dependency_type_info_index_map;
+	TypeSet min_dep_type_info_set;
+	Array<TypeInfoPair> type_info_types; // sorted after filled
 
 
 	Array<Entity *> testing_procedures;
@@ -450,9 +459,10 @@ struct CheckerInfo {
 	BlockingMutex                  gen_types_mutex;
 	PtrMap<Type *, GenTypesData *> gen_types;
 
-	BlockingMutex type_info_mutex; // NOT recursive
-	Array<Type *> type_info_types;
-	PtrMap<Type *, isize> type_info_map;
+	// BlockingMutex type_info_mutex; // NOT recursive
+	// Array<TypeInfoPair> type_info_types;
+	// PtrMap<Type *, isize> type_info_map;
+	// TypeSet type_info_set;
 
 	BlockingMutex foreign_mutex; // NOT recursive
 	StringMap<Entity *> foreigns;
@@ -571,6 +581,7 @@ gb_internal DeclInfo *   decl_info_of_entity    (Entity * e);
 gb_internal AstFile *    ast_file_of_filename   (CheckerInfo *i, String   filename);
 // IMPORTANT: Only to use once checking is done
 gb_internal isize        type_info_index        (CheckerInfo *i, Type *type, bool error_on_failure);
+gb_internal isize        type_info_index        (CheckerInfo *info, TypeInfoPair pair, bool error_on_failure);
 
 // Will return nullptr if not found
 gb_internal Entity *entity_of_node(Ast *expr);

+ 2 - 2
src/common.cpp

@@ -134,9 +134,9 @@ gb_internal u32 fnv32a(void const *data, isize len) {
 	return h;
 }
 
-gb_internal u64 fnv64a(void const *data, isize len) {
+gb_internal u64 fnv64a(void const *data, isize len, u64 seed=0xcbf29ce484222325ull) {
 	u8 const *bytes = cast(u8 const *)data;
-	u64 h = 0xcbf29ce484222325ull;
+	u64 h = seed;
 	
 	for (; len >= 8; len -= 8, bytes += 8) {
 		h = (h ^ bytes[0]) * 0x100000001b3ull;

+ 1 - 0
src/entity.cpp

@@ -257,6 +257,7 @@ struct Entity {
 			bool    has_instrumentation        : 1;
 			bool    is_memcpy_like             : 1;
 			bool    uses_branch_location       : 1;
+			bool    is_anonymous               : 1;
 		} Procedure;
 		struct {
 			Array<Entity *> entities;

+ 1 - 1
src/gb/gb.h

@@ -5856,7 +5856,7 @@ gb_inline isize gb_fprintf_va(struct gbFile *f, char const *fmt, va_list va) {
 
 
 gb_inline char *gb_bprintf_va(char const *fmt, va_list va) {
-	gb_local_persist char buffer[4096];
+	gb_thread_local gb_local_persist char buffer[4096];
 	gb_snprintf_va(buffer, gb_size_of(buffer), fmt, va);
 	return buffer;
 }

+ 7 - 4
src/llvm_backend.cpp

@@ -24,7 +24,7 @@
 #include "llvm_backend_stmt.cpp"
 #include "llvm_backend_proc.cpp"
 
-String get_default_microarchitecture() {
+gb_internal String get_default_microarchitecture() {
 	String default_march = str_lit("generic");
 	if (build_context.metrics.arch == TargetArch_amd64) {
 		// NOTE(bill): x86-64-v2 is more than enough for everyone
@@ -47,7 +47,7 @@ String get_default_microarchitecture() {
 	return default_march;
 }
 
-String get_final_microarchitecture() {
+gb_internal String get_final_microarchitecture() {
 	BuildContext *bc = &build_context;
 
 	String microarch = bc->microarch;
@@ -3154,7 +3154,9 @@ gb_internal bool lb_generate_code(lbGenerator *gen) {
 		lbModule *m = default_module;
 
 		{ // Add type info data
-			isize max_type_info_count = info->minimum_dependency_type_info_set.count+1;
+			GB_ASSERT_MSG(info->minimum_dependency_type_info_index_map.count == info->type_info_types.count, "%tu vs %tu", info->minimum_dependency_type_info_index_map.count, info->type_info_types.count);
+
+			isize max_type_info_count = info->minimum_dependency_type_info_index_map.count+1;
 			Type *t = alloc_type_array(t_type_info_ptr, max_type_info_count);
 
 			// IMPORTANT NOTE(bill): As LLVM does not have a union type, an array of unions cannot be initialized
@@ -3182,7 +3184,8 @@ gb_internal bool lb_generate_code(lbGenerator *gen) {
 			isize count = 0;
 			isize offsets_extra = 0;
 
-			for (Type *t : m->info->type_info_types) {
+			for (auto const &tt : m->info->type_info_types) {
+				Type *t = tt.type;
 				isize index = lb_type_info_index(m->info, t, false);
 				if (index < 0) {
 					continue;

+ 1 - 1
src/llvm_backend.hpp

@@ -399,7 +399,7 @@ struct lbProcedure {
 gb_internal bool lb_init_generator(lbGenerator *gen, Checker *c);
 
 gb_internal String lb_mangle_name(Entity *e);
-gb_internal String lb_get_entity_name(lbModule *m, Entity *e, String name = {});
+gb_internal String lb_get_entity_name(lbModule *m, Entity *e);
 
 gb_internal LLVMAttributeRef lb_create_enum_attribute(LLVMContextRef ctx, char const *name, u64 value=0);
 gb_internal LLVMAttributeRef lb_create_enum_attribute_with_type(LLVMContextRef ctx, char const *name, LLVMTypeRef type);

+ 12 - 128
src/llvm_backend_general.cpp

@@ -1443,148 +1443,30 @@ gb_internal void lb_clone_struct_type(LLVMTypeRef dst, LLVMTypeRef src) {
 	LLVMStructSetBody(dst, fields, field_count, LLVMIsPackedStruct(src));
 }
 
-gb_internal String lb_mangle_name(Entity *e) {
-	String name = e->token.string;
-
-	AstPackage *pkg = e->pkg;
-	GB_ASSERT_MSG(pkg != nullptr, "Missing package for '%.*s'", LIT(name));
-	String pkgn = pkg->name;
-	GB_ASSERT(!rune_is_digit(pkgn[0]));
-	if (pkgn == "llvm") {
-		pkgn = str_lit("llvm$");
-	}
-
-	isize max_len = pkgn.len + 1 + name.len + 1;
-	bool require_suffix_id = is_type_polymorphic(e->type, true);
-
-	if ((e->scope->flags & (ScopeFlag_File | ScopeFlag_Pkg)) == 0) {
-		require_suffix_id = true;
-	} else if (is_blank_ident(e->token)) {
-		require_suffix_id = true;
-	}if (e->flags & EntityFlag_NotExported) {
-		require_suffix_id = true;
-	}
-
-	if (require_suffix_id) {
-		max_len += 21;
-	}
-
-	char *new_name = gb_alloc_array(permanent_allocator(), char, max_len);
-	isize new_name_len = gb_snprintf(
-		new_name, max_len,
-		"%.*s" ABI_PKG_NAME_SEPARATOR "%.*s", LIT(pkgn), LIT(name)
-	);
-	if (require_suffix_id) {
-		char *str = new_name + new_name_len-1;
-		isize len = max_len-new_name_len;
-		isize extra = gb_snprintf(str, len, "-%llu", cast(unsigned long long)e->id);
-		new_name_len += extra-1;
-	}
-
-	String mangled_name = make_string((u8 const *)new_name, new_name_len-1);
-	return mangled_name;
-}
-
-gb_internal String lb_set_nested_type_name_ir_mangled_name(Entity *e, lbProcedure *p, lbModule *module) {
-	// NOTE(bill, 2020-03-08): A polymorphic procedure may take a nested type declaration
-	// and as a result, the declaration does not have time to determine what it should be
-
-	GB_ASSERT(e != nullptr && e->kind == Entity_TypeName);
-	if (e->TypeName.ir_mangled_name.len != 0)  {
-		return e->TypeName.ir_mangled_name;
-	}
-	GB_ASSERT((e->scope->flags & ScopeFlag_File) == 0);
-
-	if (p == nullptr) {
-		Entity *proc = nullptr;
-		if (e->parent_proc_decl != nullptr) {
-			proc = e->parent_proc_decl->entity;
-		} else {
-			Scope *scope = e->scope;
-			while (scope != nullptr && (scope->flags & ScopeFlag_Proc) == 0) {
-				scope = scope->parent;
-			}
-			GB_ASSERT(scope != nullptr);
-			GB_ASSERT(scope->flags & ScopeFlag_Proc);
-			proc = scope->procedure_entity;
-		}
-		if (proc != nullptr) {
-			GB_ASSERT(proc->kind == Entity_Procedure);
-			if (proc->code_gen_procedure != nullptr) {
-				p = proc->code_gen_procedure;
-			}
-		}
-	}
-
-	// NOTE(bill): Generate a new name
-	// parent_proc.name-guid
-	String ts_name = e->token.string;
-
-	if (p != nullptr) {
-		isize name_len = p->name.len + 1 + ts_name.len + 1 + 10 + 1;
-		char *name_text = gb_alloc_array(permanent_allocator(), char, name_len);
-		u32 guid = 1+p->module->nested_type_name_guid.fetch_add(1);
-		name_len = gb_snprintf(name_text, name_len, "%.*s" ABI_PKG_NAME_SEPARATOR "%.*s-%u", LIT(p->name), LIT(ts_name), guid);
-
-		String name = make_string(cast(u8 *)name_text, name_len-1);
-		e->TypeName.ir_mangled_name = name;
-		return name;
-	} else {
-		// NOTE(bill): a nested type be required before its parameter procedure exists. Just give it a temp name for now
-		isize name_len = 9 + 1 + ts_name.len + 1 + 10 + 1;
-		char *name_text = gb_alloc_array(permanent_allocator(), char, name_len);
-		static std::atomic<u32> guid;
-		name_len = gb_snprintf(name_text, name_len, "_internal" ABI_PKG_NAME_SEPARATOR "%.*s-%u", LIT(ts_name), 1+guid.fetch_add(1));
-
-		String name = make_string(cast(u8 *)name_text, name_len-1);
-		e->TypeName.ir_mangled_name = name;
-		return name;
-	}
-}
-
-gb_internal String lb_get_entity_name(lbModule *m, Entity *e, String default_name) {
+gb_internal String lb_get_entity_name(lbModule *m, Entity *e) {
 	GB_ASSERT(m != nullptr);
-	if (e != nullptr && e->kind == Entity_TypeName && e->TypeName.ir_mangled_name.len != 0) {
+	GB_ASSERT(e != nullptr);
+	if (e->kind == Entity_TypeName && e->TypeName.ir_mangled_name.len != 0) {
 		return e->TypeName.ir_mangled_name;
+	} else if (e->kind == Entity_Procedure && e->Procedure.link_name.len != 0) {
+		return e->Procedure.link_name;
 	}
-	GB_ASSERT(e != nullptr);
 
 	if (e->pkg == nullptr) {
 		return e->token.string;
 	}
 
-	if (e->kind == Entity_TypeName && (e->scope->flags & ScopeFlag_File) == 0) {
-		return lb_set_nested_type_name_ir_mangled_name(e, nullptr, m);
-	}
-
-	String name = {};
-
-	bool no_name_mangle = false;
+	gbString w = string_canonical_entity_name(heap_allocator(), e);
+	defer (gb_string_free(w));
 
-	if (e->kind == Entity_Variable) {
-		bool is_foreign = e->Variable.is_foreign;
-		bool is_export  = e->Variable.is_export;
-		no_name_mangle = e->Variable.link_name.len > 0 || is_foreign || is_export;
-		if (e->Variable.link_name.len > 0) {
-			return e->Variable.link_name;
-		}
-	} else if (e->kind == Entity_Procedure && e->Procedure.link_name.len > 0) {
-		return e->Procedure.link_name;
-	} else if (e->kind == Entity_Procedure && e->Procedure.is_export) {
-		no_name_mangle = true;
-	}
-
-	if (!no_name_mangle) {
-		name = lb_mangle_name(e);
-	}
-	if (name.len == 0) {
-		name = e->token.string;
-	}
+	String name = copy_string(permanent_allocator(), make_string(cast(u8 const *)w, gb_string_length(w)));
 
 	if (e->kind == Entity_TypeName) {
 		e->TypeName.ir_mangled_name = name;
 	} else if (e->kind == Entity_Procedure) {
 		e->Procedure.link_name = name;
+	} else if (e->kind == Entity_Variable) {
+		e->Variable.link_name = name;
 	}
 
 	return name;
@@ -2869,6 +2751,8 @@ gb_internal lbValue lb_generate_anonymous_proc_lit(lbModule *m, String const &pr
 	pl->decl->code_gen_module = m;
 	e->decl_info = pl->decl;
 	pl->decl->entity = e;
+	e->parent_proc_decl = pl->decl->parent;
+	e->Procedure.is_anonymous = true;
 	e->flags |= EntityFlag_ProcBodyChecked;
 
 	lbProcedure *p = lb_create_procedure(m, e);

+ 2 - 1
src/llvm_backend_stmt.cpp

@@ -32,7 +32,8 @@ gb_internal void lb_build_constant_value_decl(lbProcedure *p, AstValueDecl *vd)
 			continue;
 		}
 
-		lb_set_nested_type_name_ir_mangled_name(e, p, p->module);
+		String name = lb_get_entity_name(p->module, e);
+		gb_unused(name);
 	}
 
 	for_array(i, vd->names) {

+ 15 - 14
src/llvm_backend_type.cpp

@@ -1,24 +1,24 @@
-gb_internal isize lb_type_info_index(CheckerInfo *info, Type *type, bool err_on_not_found=true) {
-	auto *set = &info->minimum_dependency_type_info_set;
-	isize index = type_info_index(info, type, err_on_not_found);
+
+gb_internal isize lb_type_info_index(CheckerInfo *info, TypeInfoPair pair, bool err_on_not_found=true) {
+	isize index = type_info_index(info, pair, err_on_not_found);
 	if (index >= 0) {
-		auto *found = map_get(set, index+1);
-		if (found) {
-			GB_ASSERT(*found >= 0);
-			return *found + 1;
-		}
+		return index+1;
 	}
 	if (err_on_not_found) {
-		gb_printf_err("NOT FOUND lb_type_info_index:\n\t%s\n\t@ index %td\n\tmax count: %u\nFound:\n", type_to_string(type), index, set->count);
-		for (auto const &entry : *set) {
+		gb_printf_err("NOT FOUND lb_type_info_index:\n\t%s\n\t@ index %td\n\tmax count: %u\nFound:\n", type_to_string(pair.type), index, info->minimum_dependency_type_info_index_map.count);
+		for (auto const &entry : info->minimum_dependency_type_info_index_map) {
 			isize type_info_index = entry.key;
-			gb_printf_err("\t%s\n", type_to_string(info->type_info_types[type_info_index]));
+			gb_printf_err("\t%s\n", type_to_string(info->type_info_types[type_info_index].type));
 		}
 		GB_PANIC("NOT FOUND");
 	}
 	return -1;
 }
 
+gb_internal isize lb_type_info_index(CheckerInfo *info, Type *type, bool err_on_not_found=true) {
+	return lb_type_info_index(info, {type, type_hash_canonical_type(type)}, err_on_not_found);
+}
+
 gb_internal u64 lb_typeid_kind(lbModule *m, Type *type, u64 id=0) {
 	GB_ASSERT(!build_context.no_rtti);
 
@@ -280,12 +280,13 @@ gb_internal void lb_setup_type_info_data_giant_array(lbModule *m, i64 global_typ
 	LLVMTypeRef *modified_types = lb_setup_modified_types_for_type_info(m, global_type_info_data_entity_count);
 	defer (gb_free(heap_allocator(), modified_types));
 	for_array(type_info_type_index, info->type_info_types) {
-		Type *t = info->type_info_types[type_info_type_index];
+		auto const &tt = info->type_info_types[type_info_type_index];
+		Type *t = tt.type;
 		if (t == nullptr || t == t_invalid) {
 			continue;
 		}
 
-		isize entry_index = lb_type_info_index(info, t, false);
+		isize entry_index = lb_type_info_index(info, tt, false);
 		if (entry_index <= 0) {
 			continue;
 		}
@@ -343,7 +344,7 @@ gb_internal void lb_setup_type_info_data_giant_array(lbModule *m, i64 global_typ
 	};
 
 	for_array(type_info_type_index, info->type_info_types) {
-		Type *t = info->type_info_types[type_info_type_index];
+		Type *t = info->type_info_types[type_info_type_index].type;
 		if (t == nullptr || t == t_invalid) {
 			continue;
 		}

+ 730 - 0
src/name_canonicalization.cpp

@@ -0,0 +1,730 @@
+gb_internal GB_COMPARE_PROC(type_info_pair_cmp) {
+	TypeInfoPair *x = cast(TypeInfoPair *)a;
+	TypeInfoPair *y = cast(TypeInfoPair *)b;
+	if (x->hash == y->hash) {
+		return 0;
+	}
+	return x->hash < y->hash ? -1 : +1;
+}
+
+
+gb_internal gbAllocator type_set_allocator(void) {
+	return heap_allocator();
+}
+
+gb_internal TypeSetIterator begin(TypeSet &set) noexcept {
+	usize index = 0;
+	while (index < set.capacity) {
+		TypeInfoPair key = set.keys[index];
+		if (key.hash != 0 && key.hash != TYPE_SET_TOMBSTONE) {
+			break;
+		}
+		index++;
+	}
+	return TypeSetIterator{&set, index};
+}
+gb_internal TypeSetIterator end(TypeSet &set) noexcept {
+	return TypeSetIterator{&set, set.capacity};
+}
+
+
+gb_internal void type_set_init(TypeSet *s, isize capacity) {
+	GB_ASSERT(s->keys == nullptr);
+	if (capacity != 0) {
+		capacity = next_pow2_isize(gb_max(16, capacity));
+		s->keys = gb_alloc_array(type_set_allocator(), TypeInfoPair, capacity);
+		// This memory will be zeroed, no need to explicitly zero it
+	}
+	s->count = 0;
+	s->capacity = capacity;
+}
+
+gb_internal void type_set_destroy(TypeSet *s) {
+	gb_free(type_set_allocator(), s->keys);
+	s->keys = nullptr;
+	s->count = 0;
+	s->capacity = 0;
+}
+
+
+gb_internal isize type_set__find(TypeSet *s, TypeInfoPair pair) {
+	GB_ASSERT(pair.type != nullptr);
+	GB_ASSERT(pair.hash != 0);
+	if (s->count != 0) {
+		usize hash = pair.hash;
+		usize mask = s->capacity-1;
+		usize hash_index = cast(usize)hash & mask;
+		for (usize i = 0; i < s->capacity; i++) {
+			Type *key = s->keys[hash_index].type;
+			if (are_types_identical_unique_tuples(key, pair.type)) {
+				return hash_index;
+			} else if (key == 0) {
+				return -1;
+			}
+			hash_index = (hash_index+1)&mask;
+		}
+	}
+	return -1;
+}
+gb_internal isize type_set__find(TypeSet *s, Type *ptr) {
+	GB_ASSERT(ptr != 0);
+	if (s->count != 0) {
+		usize hash = cast(usize)type_hash_canonical_type(ptr);
+		usize mask = s->capacity-1;
+		usize hash_index = cast(usize)hash & mask;
+		for (usize i = 0; i < s->capacity; i++) {
+			Type *key = s->keys[hash_index].type;
+			if (are_types_identical_unique_tuples(key, ptr)) {
+				return hash_index;
+			} else if (key == 0) {
+				return -1;
+			}
+			hash_index = (hash_index+1)&mask;
+		}
+	}
+	return -1;
+}
+
+gb_internal bool type_set__full(TypeSet *s) {
+	return 0.75f * s->capacity <= s->count;
+}
+
+gb_internal gb_inline void type_set_grow(TypeSet *old_set) {
+	if (old_set->capacity == 0) {
+		type_set_init(old_set);
+		return;
+	}
+
+	TypeSet new_set = {};
+	type_set_init(&new_set, gb_max(old_set->capacity<<1, 16));
+
+	for (TypeInfoPair const &set : *old_set) {
+		bool was_new = type_set_update(&new_set, set);
+		GB_ASSERT(!was_new);
+	}
+	GB_ASSERT(old_set->count == new_set.count);
+
+	type_set_destroy(old_set);
+
+	*old_set = new_set;
+}
+
+
+gb_internal gb_inline bool type_set_exists(TypeSet *s, Type *ptr) {
+	return type_set__find(s, ptr) >= 0;
+}
+gb_internal gb_inline bool type_set_exists(TypeSet *s, TypeInfoPair pair) {
+	return type_set__find(s, pair) >= 0;
+}
+gb_internal gb_inline TypeInfoPair *type_set_retrieve(TypeSet *s, Type *type) {
+	isize index = type_set__find(s, type);
+	if (index >= 0) {
+		return &s->keys[index];
+	}
+	return nullptr;
+}
+
+
+gb_internal bool type_set_update(TypeSet *s, TypeInfoPair pair) { // returns true if it previously existsed
+	if (type_set_exists(s, pair)) {
+		return true;
+	}
+
+	if (s->keys == nullptr) {
+		type_set_init(s);
+	} else if (type_set__full(s)) {
+		type_set_grow(s);
+	}
+	GB_ASSERT(s->count < s->capacity);
+	GB_ASSERT(s->capacity >= 0);
+
+	usize mask = s->capacity-1;
+	usize hash = cast(usize)pair.hash;
+	usize hash_index = (cast(usize)hash) & mask;
+	GB_ASSERT(hash_index < s->capacity);
+	for (usize i = 0; i < s->capacity; i++) {
+		TypeInfoPair *key = &s->keys[hash_index];
+		GB_ASSERT(!are_types_identical_unique_tuples(key->type, pair.type));
+		if (key->hash == TYPE_SET_TOMBSTONE || key->hash == 0) {
+			*key = pair;
+			s->count++;
+			return false;
+		}
+		hash_index = (hash_index+1)&mask;
+	}
+
+	GB_PANIC("ptr set out of memory");
+	return false;
+}
+
+gb_internal bool type_set_update(TypeSet *s, Type *ptr) { // returns true if it previously existsed
+	TypeInfoPair pair = {ptr, type_hash_canonical_type(ptr)};
+	return type_set_update(s, pair);
+}
+
+
+gb_internal Type *type_set_add(TypeSet *s, Type *ptr) {
+	type_set_update(s, ptr);
+	return ptr;
+}
+
+gb_internal Type *type_set_add(TypeSet *s, TypeInfoPair pair) {
+	type_set_update(s, pair);
+	return pair.type;
+}
+
+
+
+gb_internal void type_set_remove(TypeSet *s, Type *ptr) {
+	isize index = type_set__find(s, ptr);
+	if (index >= 0) {
+		GB_ASSERT(s->count > 0);
+		s->keys[index].type = nullptr;
+		s->keys[index].hash = TYPE_SET_TOMBSTONE;
+		s->count--;
+	}
+}
+
+gb_internal gb_inline void type_set_clear(TypeSet *s) {
+	s->count = 0;
+	gb_zero_size(s->keys, s->capacity*gb_size_of(*s->keys));
+}
+
+
+#define TYPE_WRITER_PROC(name) bool name(TypeWriter *w, void const *ptr, isize len)
+typedef TYPE_WRITER_PROC(TypeWriterProc);
+
+
+struct TypeWriter {
+	TypeWriterProc *proc;
+	void *user_data;
+};
+
+bool type_writer_append(TypeWriter *w, void const *ptr, isize len) {
+	return w->proc(w, ptr, len);
+}
+
+bool type_writer_appendb(TypeWriter *w, char b) {
+	return w->proc(w, &b, 1);
+}
+
+bool type_writer_appendc(TypeWriter *w, char const *str) {
+	isize len = gb_strlen(str);
+	return w->proc(w, str, len);
+}
+
+bool type_writer_append_fmt(TypeWriter *w, char const *fmt, ...) {
+	va_list va;
+	char *str;
+	va_start(va, fmt);
+	str = gb_bprintf_va(fmt, va);
+	va_end(va);
+
+	return type_writer_appendc(w, str);
+}
+
+
+
+TYPE_WRITER_PROC(type_writer_string_writer_proc) {
+	gbString *s = cast(gbString *)&w->user_data;
+	*s = gb_string_append_length(*s, ptr, len);
+	return true;
+}
+
+void type_writer_make_string(TypeWriter *w, gbAllocator allocator) {
+	w->user_data = gb_string_make(allocator, "");
+	w->proc = type_writer_string_writer_proc;
+}
+
+void type_writer_destroy_string(TypeWriter *w) {
+	gb_string_free(cast(gbString)w->user_data);
+}
+
+
+TYPE_WRITER_PROC(type_writer_hasher_writer_proc) {
+	u64 *seed = cast(u64 *)w->user_data;
+	*seed = fnv64a(ptr, len, *seed);
+	return true;
+}
+
+void type_writer_make_hasher(TypeWriter *w, u64 *hash) {
+	w->user_data = hash;
+	w->proc = type_writer_hasher_writer_proc;
+}
+
+
+
+
+gb_internal void write_canonical_params(TypeWriter *w, Type *params) {
+	type_writer_appendc(w, "(");
+	defer (type_writer_appendc(w, ")"));
+
+	if (params == nullptr) {
+		return;
+	}
+	GB_ASSERT(params->kind == Type_Tuple);
+	for_array(i, params->Tuple.variables) {
+		Entity *v = params->Tuple.variables[i];
+		if (i > 0) {
+			type_writer_appendc(w, CANONICAL_PARAM_SEPARATOR);
+		}
+		type_writer_append(w, v->token.string.text, v->token.string.len);
+		type_writer_appendc(w, CANONICAL_TYPE_SEPARATOR);
+
+		switch (v->kind) {
+		case Entity_Variable:
+			if (v->flags&EntityFlag_CVarArg) {
+				type_writer_appendc(w, CANONICAL_PARAM_C_VARARG);
+			}
+			if (v->flags&EntityFlag_Ellipsis) {
+				Type *slice = base_type(v->type);
+				type_writer_appendc(w, CANONICAL_PARAM_VARARG);
+				GB_ASSERT(v->type->kind == Type_Slice);
+				write_type_to_canonical_string(w, slice->Slice.elem);
+			} else {
+				write_type_to_canonical_string(w, v->type);
+			}
+			break;
+		case Entity_TypeName:
+			type_writer_appendc(w, CANONICAL_PARAM_TYPEID);
+			write_type_to_canonical_string(w, v->type);
+			break;
+		case Entity_Constant:
+			{
+				type_writer_appendc(w, CANONICAL_PARAM_CONST);
+				gbString s = exact_value_to_string(v->Constant.value, 1<<16);
+				type_writer_append(w, s, gb_string_length(s));
+				gb_string_free(s);
+			}
+			break;
+		default:
+			GB_PANIC("TODO(bill): handle non type/const parapoly parameter values");
+			break;
+		}
+	}
+	return;
+}
+
+gb_internal u64 type_hash_canonical_type(Type *type) {
+	if (type == nullptr) {
+		return 0;
+	}
+	u64 hash = fnv64a(nullptr, 0);
+	TypeWriter w = {};
+	type_writer_make_hasher(&w, &hash);
+	write_type_to_canonical_string(&w, type);
+
+	return hash ? hash : 1;
+}
+
+gb_internal String type_to_canonical_string(gbAllocator allocator, Type *type) {
+	TypeWriter w = {};
+	type_writer_make_string(&w, allocator);
+	write_type_to_canonical_string(&w, type);
+
+	gbString s = cast(gbString)w.user_data;
+	return make_string(cast(u8 const *)s, gb_string_length(s));
+}
+
+gb_internal gbString temp_canonical_string(Type *type) {
+	TypeWriter w = {};
+	type_writer_make_string(&w, temporary_allocator());
+	write_type_to_canonical_string(&w, type);
+
+	return cast(gbString)w.user_data;
+}
+
+gb_internal gbString string_canonical_entity_name(gbAllocator allocator, Entity *e) {
+	TypeWriter w = {};
+	type_writer_make_string(&w, allocator);
+	write_canonical_entity_name(&w, e);
+	return cast(gbString)w.user_data;
+}
+
+
+
+gb_internal void write_canonical_parent_prefix(TypeWriter *w, Entity *e) {
+	GB_ASSERT(e != nullptr);
+	if (e->kind == Entity_Procedure || e->kind == Entity_TypeName) {
+		if (e->kind == Entity_Procedure && (e->Procedure.is_export || e->Procedure.is_foreign)) {
+			// no prefix
+			return;
+		}
+		if (e->parent_proc_decl) {
+			Entity *p = e->parent_proc_decl->entity;
+			write_canonical_parent_prefix(w, p);
+			type_writer_append(w, p->token.string.text, p->token.string.len);
+			if (is_type_polymorphic(p->type)) {
+				type_writer_appendc(w, CANONICAL_TYPE_SEPARATOR);
+				write_type_to_canonical_string(w, p->type);
+			}
+			type_writer_appendc(w, CANONICAL_NAME_SEPARATOR);
+
+		} else if (e->pkg && (scope_lookup_current(e->pkg->scope, e->token.string) == e)) {
+			type_writer_append(w, e->pkg->name.text, e->pkg->name.len);
+			if (e->pkg->name == "llvm") {
+				type_writer_appendc(w, "$");
+			}
+			type_writer_appendc(w, CANONICAL_NAME_SEPARATOR);
+		} else {
+			String file_name = filename_without_directory(e->file->fullpath);
+			type_writer_append(w, e->pkg->name.text, e->pkg->name.len);
+			if (e->pkg->name == "llvm") {
+				type_writer_appendc(w, "$");
+			}
+			type_writer_append_fmt(w, CANONICAL_NAME_SEPARATOR "[%.*s]" CANONICAL_NAME_SEPARATOR, LIT(file_name));
+		}
+	} else {
+		GB_PANIC("TODO(bill): handle entity kind: %d", e->kind);
+	}
+	if (e->kind == Entity_Procedure && e->Procedure.is_anonymous) {
+		String file_name = filename_without_directory(e->file->fullpath);
+		type_writer_append_fmt(w, CANONICAL_ANON_PREFIX "[%.*s:%d]", LIT(file_name), e->token.pos.offset);
+	} else {
+		type_writer_append(w, e->token.string.text, e->token.string.len);
+	}
+
+	if (is_type_polymorphic(e->type)) {
+		type_writer_appendc(w, CANONICAL_TYPE_SEPARATOR);
+		write_type_to_canonical_string(w, e->type);
+	}
+	type_writer_appendc(w, CANONICAL_NAME_SEPARATOR);
+
+	return;
+}
+
+gb_internal void write_canonical_entity_name(TypeWriter *w, Entity *e) {
+	GB_ASSERT(e != nullptr);
+
+	if (e->token.string == "_") {
+		GB_PANIC("_ string");
+	}
+	if (e->token.string.len == 0) {
+		GB_PANIC("empty string");
+	}
+
+	if (e->kind == Entity_Variable) {
+		bool is_foreign = e->Variable.is_foreign;
+		bool is_export  = e->Variable.is_export;
+		if (e->Variable.link_name.len > 0) {
+			type_writer_append(w, e->Variable.link_name.text, e->Variable.link_name.len);
+			return;
+		} else if (is_foreign || is_export) {
+			type_writer_append(w, e->token.string.text, e->token.string.len);
+			return;
+		}
+	} else if (e->kind == Entity_Procedure && e->Procedure.link_name.len > 0) {
+		type_writer_append(w, e->Procedure.link_name.text, e->Procedure.link_name.len);
+		return;
+	} else if (e->kind == Entity_Procedure && e->Procedure.is_export) {
+		type_writer_append(w, e->token.string.text, e->token.string.len);
+		return;
+	}
+
+	if (e->scope->flags & (ScopeFlag_Builtin)) {
+		goto write_base_name;
+	} else if ((e->scope->flags & (ScopeFlag_File | ScopeFlag_Pkg)) == 0 ||
+	           e->flags & EntityFlag_NotExported) {
+		Scope *s = e->scope;
+
+		while ((s->flags & (ScopeFlag_Proc|ScopeFlag_File)) == 0 && s->decl_info == nullptr) {
+			if (s->parent == nullptr) {
+				break;
+			}
+			s = s->parent;
+		}
+
+		if (s->decl_info != nullptr && s->decl_info->entity)  {
+			Entity *parent = s->decl_info->entity;
+			write_canonical_parent_prefix(w, parent);
+			if (e->scope->index > 0) {
+				type_writer_append_fmt(w, CANONICAL_TYPE_SEPARATOR "[%d]", e->scope->index);
+			}
+
+			goto write_base_name;
+		} else if ((s->flags & ScopeFlag_File) && s->file != nullptr) {
+			String file_name = filename_without_directory(s->file->fullpath);
+			type_writer_append(w, e->pkg->name.text, e->pkg->name.len);
+			if (e->pkg->name == "llvm") {
+				type_writer_appendc(w, "$");
+			}
+			type_writer_appendc(w, gb_bprintf(CANONICAL_NAME_SEPARATOR "[%.*s]" CANONICAL_NAME_SEPARATOR, LIT(file_name)));
+			goto write_base_name;
+		} else if (s->flags & (ScopeFlag_Builtin)) {
+			goto write_base_name;
+		}
+		gb_printf_err("%s WEIRD ENTITY TYPE %s %u %p\n", token_pos_to_string(e->token.pos), type_to_string(e->type), s->flags, s->decl_info);
+
+		auto const print_scope_flags = [](Scope *s) {
+			if (s->flags & ScopeFlag_Pkg)             gb_printf_err("Pkg ");
+			if (s->flags & ScopeFlag_Builtin)         gb_printf_err("Builtin ");
+			if (s->flags & ScopeFlag_Global)          gb_printf_err("Global ");
+			if (s->flags & ScopeFlag_File)            gb_printf_err("File ");
+			if (s->flags & ScopeFlag_Init)            gb_printf_err("Init ");
+			if (s->flags & ScopeFlag_Proc)            gb_printf_err("Proc ");
+			if (s->flags & ScopeFlag_Type)            gb_printf_err("Type ");
+			if (s->flags & ScopeFlag_HasBeenImported) gb_printf_err("HasBeenImported ");
+			if (s->flags & ScopeFlag_ContextDefined)  gb_printf_err("ContextDefined ");
+			gb_printf_err("\n");
+		};
+
+		print_scope_flags(s);
+		GB_PANIC("weird entity %.*s", LIT(e->token.string));
+	}
+	if (e->pkg != nullptr) {
+		type_writer_append(w, e->pkg->name.text, e->pkg->name.len);
+		type_writer_appendc(w, CANONICAL_NAME_SEPARATOR);
+	}
+
+write_base_name:
+
+	switch (e->kind) {
+	case Entity_TypeName:
+		{
+
+			Type *params = nullptr;
+			Entity *parent = type_get_polymorphic_parent(e->type, &params);
+			if (parent && (parent->token.string == e->token.string)) {
+				type_writer_append(w, parent->token.string.text, parent->token.string.len);
+				write_canonical_params(w, params);
+			} else {
+				type_writer_append(w, e->token.string.text, e->token.string.len);
+			}
+		}
+		return;
+
+	case Entity_Procedure:
+	case Entity_Variable:
+		type_writer_append(w, e->token.string.text, e->token.string.len);
+		if (is_type_polymorphic(e->type)) {
+			type_writer_appendc(w, CANONICAL_TYPE_SEPARATOR);
+			write_type_to_canonical_string(w, e->type);
+		}
+		return;
+
+	default:
+		GB_PANIC("TODO(bill): entity kind %d", e->kind);
+		break;
+	}
+	return;
+}
+
+// NOTE(bill): This exists so that we deterministically hash a type by serializing it to a canonical string
+gb_internal void write_type_to_canonical_string(TypeWriter *w, Type *type) {
+	if (type == nullptr) {
+		type_writer_appendc(w, CANONICAL_NONE_TYPE); // none/void type
+		return;
+	}
+
+	type = default_type(type);
+	GB_ASSERT(!is_type_untyped(type));
+
+	switch (type->kind) {
+	case Type_Basic:
+		type_writer_append(w, type->Basic.name.text, type->Basic.name.len);
+		return;
+	case Type_Pointer:
+		type_writer_appendb(w, '^');
+		write_type_to_canonical_string(w, type->Pointer.elem);
+		return;
+	case Type_MultiPointer:
+		type_writer_appendc(w, "[^]");
+		write_type_to_canonical_string(w, type->Pointer.elem);
+		return;
+	case Type_SoaPointer:
+		type_writer_appendc(w, "#soa^");
+		write_type_to_canonical_string(w, type->Pointer.elem);
+		return;
+	case Type_EnumeratedArray:
+		if (type->EnumeratedArray.is_sparse) {
+			type_writer_appendc(w, "#sparse");
+		}
+		type_writer_appendb(w, '[');
+		write_type_to_canonical_string(w, type->EnumeratedArray.index);
+		type_writer_appendb(w, ']');
+		write_type_to_canonical_string(w, type->EnumeratedArray.elem);
+		return;
+	case Type_Array:
+		type_writer_append_fmt(w, "[%lld]", cast(long long)type->Array.count);
+		write_type_to_canonical_string(w, type->Array.elem);
+		return;
+	case Type_Slice:
+		type_writer_appendc(w, "[]");
+		write_type_to_canonical_string(w, type->Array.elem);
+		return;
+	case Type_DynamicArray:
+		type_writer_appendc(w, "[dynamic]");
+		write_type_to_canonical_string(w, type->DynamicArray.elem);
+		return;
+	case Type_SimdVector:
+		type_writer_append_fmt(w, "#simd[%lld]", cast(long long)type->SimdVector.count);
+		write_type_to_canonical_string(w, type->SimdVector.elem);
+		return;
+	case Type_Matrix:
+		if (type->Matrix.is_row_major) {
+			type_writer_appendc(w, "#row_major ");
+		}
+		type_writer_append_fmt(w, "matrix[%lld, %lld]", cast(long long)type->Matrix.row_count, cast(long long)type->Matrix.column_count);
+		write_type_to_canonical_string(w, type->Matrix.elem);
+		return;
+	case Type_Map:
+		type_writer_appendc(w, "map[");
+		write_type_to_canonical_string(w, type->Map.key);
+		type_writer_appendc(w, "]");
+		write_type_to_canonical_string(w, type->Map.value);
+		return;
+
+	case Type_Enum:
+		type_writer_appendc(w, "enum");
+		if (type->Enum.base_type != nullptr) {
+			type_writer_appendb(w, ' ');
+			write_type_to_canonical_string(w, type->Enum.base_type);
+			type_writer_appendb(w, ' ');
+		}
+		type_writer_appendb(w, '{');
+		for_array(i, type->Enum.fields) {
+			Entity *f = type->Enum.fields[i];
+			GB_ASSERT(f->kind == Entity_Constant);
+			if (i > 0) {
+				type_writer_appendc(w, CANONICAL_FIELD_SEPARATOR);
+			}
+			type_writer_append(w, f->token.string.text, f->token.string.len);
+			type_writer_appendc(w, "=");
+
+			gbString s = exact_value_to_string(f->Constant.value, 1<<16);
+			type_writer_append(w, s, gb_string_length(s));
+			gb_string_free(s);
+		}
+		type_writer_appendb(w, '}');
+		return;
+	case Type_BitSet:
+		type_writer_appendc(w, "bit_set[");
+		if (type->BitSet.elem == nullptr) {
+			type_writer_appendc(w, CANONICAL_NONE_TYPE);
+		} else if (is_type_enum(type->BitSet.elem)) {
+			write_type_to_canonical_string(w, type->BitSet.elem);
+		} else {
+			type_writer_append_fmt(w, "%lld", type->BitSet.lower);
+			type_writer_append_fmt(w, CANONICAL_RANGE_OPERATOR);
+			type_writer_append_fmt(w, "%lld", type->BitSet.upper);
+		}
+		if (type->BitSet.underlying != nullptr) {
+			type_writer_appendc(w, ";");
+			write_type_to_canonical_string(w, type->BitSet.underlying);
+		}
+		type_writer_appendc(w, "]");
+		return;
+
+	case Type_Union:
+		type_writer_appendc(w, "union");
+
+		switch (type->Union.kind) {
+		case UnionType_no_nil:     type_writer_appendc(w, "#no_nil");     break;
+		case UnionType_shared_nil: type_writer_appendc(w, "#shared_nil"); break;
+		}
+		if (type->Union.custom_align != 0) {
+			type_writer_append_fmt(w, "#align(%lld)", cast(long long)type->Union.custom_align);
+		}
+		type_writer_appendc(w, "{");
+		for_array(i, type->Union.variants) {
+			Type *t = type->Union.variants[i];
+			if (i > 0) type_writer_appendc(w, CANONICAL_FIELD_SEPARATOR);
+			write_type_to_canonical_string(w, t);
+		}
+		type_writer_appendc(w, "}");
+		return;
+	case Type_Struct:
+		if (type->Struct.soa_kind != StructSoa_None) {
+			switch (type->Struct.soa_kind) {
+			case StructSoa_Fixed:   type_writer_append_fmt(w, "#soa[%lld]", cast(long long)type->Struct.soa_count); break;
+			case StructSoa_Slice:   type_writer_appendc(w,    "#soa[]");                                    break;
+			case StructSoa_Dynamic: type_writer_appendc(w,    "#soa[dynamic]");                             break;
+			default: GB_PANIC("Unknown StructSoaKind"); break;
+			}
+			return write_type_to_canonical_string(w, type->Struct.soa_elem);
+		}
+
+		type_writer_appendc(w, "struct");
+		if (type->Struct.is_packed)    type_writer_appendc(w, "#packed");
+		if (type->Struct.is_raw_union) type_writer_appendc(w, "#raw_union");
+		if (type->Struct.is_no_copy)   type_writer_appendc(w, "#no_copy");
+		if (type->Struct.custom_min_field_align != 0) type_writer_append_fmt(w, "#min_field_align(%lld)", cast(long long)type->Struct.custom_min_field_align);
+		if (type->Struct.custom_max_field_align != 0) type_writer_append_fmt(w, "#max_field_align(%lld)", cast(long long)type->Struct.custom_max_field_align);
+		if (type->Struct.custom_align != 0)           type_writer_append_fmt(w, "#align(%lld)",           cast(long long)type->Struct.custom_align);
+		type_writer_appendb(w, '{');
+		for_array(i, type->Struct.fields) {
+			Entity *f = type->Struct.fields[i];
+			GB_ASSERT(f->kind == Entity_Variable);
+			if (i > 0) {
+				type_writer_appendc(w, CANONICAL_FIELD_SEPARATOR);
+			}
+			type_writer_append(w, f->token.string.text, f->token.string.len);
+			type_writer_appendc(w, CANONICAL_TYPE_SEPARATOR);
+			write_type_to_canonical_string(w, f->type);
+			String tag = type->Struct.tags[i];
+			if (tag.len != 0) {
+				String s = quote_to_ascii(heap_allocator(), tag);
+				type_writer_append(w, s.text, s.len);
+				gb_free(heap_allocator(), s.text);
+			}
+		}
+		type_writer_appendb(w, '}');
+		return;
+
+	case Type_BitField:
+		type_writer_appendc(w, "bit_field");
+		write_type_to_canonical_string(w, type->BitField.backing_type);
+		type_writer_appendc(w, " {");
+		for (isize i = 0; i < type->BitField.fields.count; i++) {
+			Entity *f = type->BitField.fields[i];
+			if (i > 0) {
+				type_writer_appendc(w, CANONICAL_FIELD_SEPARATOR);
+			}
+			type_writer_append(w, f->token.string.text, f->token.string.len);
+			type_writer_appendc(w, CANONICAL_TYPE_SEPARATOR);
+			write_type_to_canonical_string(w, f->type);
+			type_writer_appendc(w, CANONICAL_BIT_FIELD_SEPARATOR);
+			type_writer_append_fmt(w, "%u", type->BitField.bit_sizes[i]);
+		}
+		type_writer_appendc(w, " }");
+		return;
+
+	case Type_Proc:
+		type_writer_appendc(w, "proc");
+		if (default_calling_convention() != type->Proc.calling_convention) {
+			type_writer_appendc(w, "\"");
+			type_writer_appendc(w, proc_calling_convention_strings[type->Proc.calling_convention]);
+			type_writer_appendc(w, "\"");
+		}
+
+		write_canonical_params(w, type->Proc.params);
+		if (type->Proc.result_count > 0) {
+			type_writer_appendc(w, "->");
+			write_canonical_params(w, type->Proc.results);
+		}
+		return;
+
+	case Type_Generic:
+		GB_PANIC("Type_Generic should never be hit");
+		return;
+
+	case Type_Named:
+		if (type->Named.type_name != nullptr) {
+			write_canonical_entity_name(w, type->Named.type_name);
+			return;
+		} else {
+			type_writer_append(w, type->Named.name.text, type->Named.name.len);
+		}
+		return;
+
+	case Type_Tuple:
+		type_writer_appendc(w, "params");
+		write_canonical_params(w, type);
+		return;
+	default:
+		GB_PANIC("unknown type kind %d %.*s", type->kind, LIT(type_strings[type->kind]));
+		break;
+	}
+
+	return;
+}

+ 111 - 0
src/name_canonicalization.hpp

@@ -0,0 +1,111 @@
+/*
+	General Rules for canonical name mangling
+
+	* No spaces between any values
+
+	* normal declarations - pkg::name
+	* builtin names - just their normal name e.g. `i32` or `string`
+	* nested (zero level) - pkg::parent1::parent2::name
+	* nested (more scopes) - pkg::parent1::parent2::name[4]
+		* [4] indicates the 4th scope within a procedure numbered in depth-first order
+	* file private - pkg::[file_name]::name
+		* Example: `pkg::[file.odin]::Type`
+	* polymorphic procedure/type - pkg::foo:TYPE
+		* naming convention for parameters
+			* type
+			* $typeid_based_name
+			* $$constant_parameter
+		* Example: `foo::to_thing:proc(u64)->([]u8)`
+	* nested decl in polymorphic procedure - pkg::foo:TYPE::name
+	* anonymous procedures - pkg::foo::$anon[file.odin:123]
+		* 123 is the file offset in bytes
+*/
+
+#define CANONICAL_TYPE_SEPARATOR  ":"
+#define CANONICAL_NAME_SEPARATOR  "::"
+// #define CANONICAL_NAME_SEPARATOR  "·"
+
+#define CANONICAL_BIT_FIELD_SEPARATOR "|"
+
+#define CANONICAL_PARAM_SEPARATOR ","
+
+#define CANONICAL_PARAM_TYPEID    "$"
+#define CANONICAL_PARAM_CONST     "$$"
+
+#define CANONICAL_PARAM_C_VARARG  "#c_vararg"
+#define CANONICAL_PARAM_VARARG    ".."
+
+#define CANONICAL_FIELD_SEPARATOR ","
+
+#define CANONICAL_ANON_PREFIX     "$anon"
+
+#define CANONICAL_NONE_TYPE       "<>"
+
+#define CANONICAL_RANGE_OPERATOR  "..="
+
+struct TypeWriter;
+
+gb_internal void     write_type_to_canonical_string(TypeWriter *w, Type *type);
+gb_internal void     write_canonical_entity_name(TypeWriter *w, Entity *e);
+gb_internal u64      type_hash_canonical_type(Type *type);
+gb_internal String   type_to_canonical_string(gbAllocator allocator, Type *type);
+gb_internal gbString temp_canonical_string(Type *type);
+
+
+gb_internal GB_COMPARE_PROC(type_info_pair_cmp);
+
+
+struct TypeInfoPair {
+	Type *type;
+	u64   hash; // see: type_hash_canonical_type
+};
+
+struct TypeSet {
+	TypeInfoPair *keys;
+	usize count;
+	usize capacity;
+};
+
+static constexpr u64 TYPE_SET_TOMBSTONE = ~(u64)(0ull);
+
+struct TypeSetIterator {
+	TypeSet *set;
+	usize index;
+
+	TypeSetIterator &operator++() noexcept {
+		for (;;) {
+			++index;
+			if (set->capacity == index) {
+				return *this;
+			}
+			TypeInfoPair key = set->keys[index];
+			if (key.hash != 0 && key.hash != TYPE_SET_TOMBSTONE) {
+				return *this;
+			}
+		}
+	}
+
+	bool operator==(TypeSetIterator const &other) const noexcept {
+		return this->set == other.set && this->index == other.index;
+	}
+
+
+	operator TypeInfoPair *() const {
+		return &set->keys[index];
+	}
+};
+
+
+gb_internal void  type_set_init   (TypeSet *s, isize capacity = 16);
+gb_internal void  type_set_destroy(TypeSet *s);
+gb_internal Type *type_set_add    (TypeSet *s, Type *ptr);
+gb_internal Type *type_set_add    (TypeSet *s, TypeInfoPair pair);
+gb_internal bool  type_set_update (TypeSet *s, Type *ptr); // returns true if it previously existed
+gb_internal bool  type_set_update (TypeSet *s, TypeInfoPair pair); // returns true if it previously existed
+gb_internal bool  type_set_exists (TypeSet *s, Type *ptr);
+gb_internal void  type_set_remove (TypeSet *s, Type *ptr);
+gb_internal void  type_set_clear  (TypeSet *s);
+gb_internal TypeInfoPair *type_set_retrieve(TypeSet *s, Type *ptr);
+
+gb_internal TypeSetIterator begin(TypeSet &set) noexcept;
+gb_internal TypeSetIterator end(TypeSet &set) noexcept;

+ 5 - 5
src/ptr_set.cpp

@@ -42,7 +42,7 @@ gb_internal void ptr_set_destroy(PtrSet<T> *s) {
 
 template <typename T>
 gb_internal isize ptr_set__find(PtrSet<T> *s, T ptr) {
-	GB_ASSERT(ptr != nullptr);
+	GB_ASSERT(ptr != 0);
 	if (s->count != 0) {
 	#if 0
 		for (usize i = 0; i < s->capacity; i++) {
@@ -58,7 +58,7 @@ gb_internal isize ptr_set__find(PtrSet<T> *s, T ptr) {
 			T key = s->keys[hash_index];
 			if (key == ptr) {
 				return hash_index;
-			} else if (key == nullptr) {
+			} else if (key == 0) {
 				return -1;
 			}
 			hash_index = (hash_index+1)&mask;
@@ -122,7 +122,7 @@ gb_internal bool ptr_set_update(PtrSet<T> *s, T ptr) { // returns true if it pre
 	for (usize i = 0; i < s->capacity; i++) {
 		T *key = &s->keys[hash_index];
 		GB_ASSERT(*key != ptr);
-		if (*key == (T)PtrSet<T>::TOMBSTONE || *key == nullptr) {
+		if (*key == (T)PtrSet<T>::TOMBSTONE || *key == 0) {
 			*key = ptr;
 			s->count++;
 			return false;
@@ -169,7 +169,7 @@ struct PtrSetIterator {
 				return *this;
 			}
 			T key = set->keys[index];
-			if (key != nullptr && key != (T)PtrSet<T>::TOMBSTONE) {
+			if (key != 0 && key != (T)PtrSet<T>::TOMBSTONE) {
 				return *this;
 			}
 		}
@@ -191,7 +191,7 @@ gb_internal PtrSetIterator<T> begin(PtrSet<T> &set) noexcept {
 	usize index = 0;
 	while (index < set.capacity) {
 		T key = set.keys[index];
-		if (key != nullptr && key != (T)PtrSet<T>::TOMBSTONE) {
+		if (key != 0 && key != (T)PtrSet<T>::TOMBSTONE) {
 			break;
 		}
 		index++;

+ 34 - 256
src/types.cpp

@@ -2774,7 +2774,37 @@ gb_internal bool are_types_identical_internal(Type *x, Type *y, bool check_tuple
 
 
 	case Type_Enum:
-		return x == y; // NOTE(bill): All enums are unique
+		if (x == y) {
+			return true;
+		}
+		if (x->Enum.fields.count != y->Enum.fields.count) {
+			return false;
+		}
+		if (!are_types_identical(x->Enum.base_type, y->Enum.base_type)) {
+			return false;
+		}
+		if (x->Enum.min_value_index != y->Enum.min_value_index) {
+			return false;
+		}
+		if (x->Enum.max_value_index != y->Enum.max_value_index) {
+			return false;
+		}
+
+		for (isize i = 0; i < x->Enum.fields.count; i++) {
+			Entity *a = x->Enum.fields[i];
+			Entity *b = y->Enum.fields[i];
+			if (a->token.string != b->token.string) {
+				return false;
+			}
+			GB_ASSERT(a->kind == b->kind);
+			GB_ASSERT(a->kind == Entity_Constant);
+			bool same = compare_exact_values(Token_CmpEq, a->Constant.value, b->Constant.value);
+			if (!same) {
+				return false;
+			}
+		}
+
+		return true;
 
 	case Type_Union:
 		if (x->Union.variants.count == y->Union.variants.count &&
@@ -2832,7 +2862,9 @@ gb_internal bool are_types_identical_internal(Type *x, Type *y, bool check_tuple
 					return false;
 				}
 			}
-			return are_types_identical(x->Struct.polymorphic_params, y->Struct.polymorphic_params);
+			// TODO(bill): Which is the correct logic here?
+			// return are_types_identical(x->Struct.polymorphic_params, y->Struct.polymorphic_params);
+			return true;
 		}
 		break;
 
@@ -4872,257 +4904,3 @@ gb_internal gbString type_to_string(Type *type, bool shorthand) {
 gb_internal gbString type_to_string_shorthand(Type *type) {
 	return type_to_string(type, true);
 }
-
-gb_internal gbString write_type_to_canonical_string(gbString w, Type *type);
-gb_internal gbString write_canonical_params(gbString w, Type *params) {
-	w = gb_string_appendc(w, "(");
-	if (params) {
-		GB_ASSERT(params->kind == Type_Tuple);
-		for_array(i, params->Tuple.variables) {
-			Entity *v = params->Tuple.variables[i];
-			if (i > 0) {
-				w = gb_string_appendc(w, ",");
-			}
-			if (v->kind == Entity_Variable) {
-				if (v->flags&EntityFlag_CVarArg) {
-					w = gb_string_appendc(w, "#c_vararg");
-				}
-				if (v->flags&EntityFlag_Ellipsis) {
-					Type *slice = base_type(v->type);
-					w = gb_string_appendc(w, "..");
-					GB_ASSERT(v->type->kind == Type_Slice);
-					w = write_type_to_canonical_string(w, slice->Slice.elem);
-				} else {
-					w = write_type_to_canonical_string(w, v->type);
-				}
-			} else if (v->kind == Entity_TypeName) {
-				w = gb_string_appendc(w, "$");
-				w = write_type_to_canonical_string(w, v->type);
-			} else if (v->kind == Entity_Constant) {
-				w = gb_string_appendc(w, "$$");
-				w = write_exact_value_to_string(w, v->Constant.value);
-			} else {
-				GB_PANIC("TODO(bill): handle non type/const parapoly parameter values");
-			}
-		}
-	}
-	return gb_string_appendc(w, ")");
-}
-
-gb_internal u64 type_hash_canonical_type(Type *type) {
-	if (type == nullptr) {
-		return 0;
-	}
-	TEMPORARY_ALLOCATOR_GUARD();
-	gbString w = write_type_to_canonical_string(gb_string_make(temporary_allocator(), ""), type);
-	u64 hash = fnv64a(w, gb_string_length(w));
-	return hash;
-}
-
-// NOTE(bill): This exists so that we deterministically hash a type by serializing it to a canonical string
-gb_internal gbString write_type_to_canonical_string(gbString w, Type *type) {
-	if (type == nullptr) {
-		return gb_string_appendc(w, "<>"); // none/void type
-	}
-
-	type = default_type(type);
-	GB_ASSERT(!is_type_untyped(type));
-
-	switch (type->kind) {
-	case Type_Basic:
-		return gb_string_append_length(w, type->Basic.name.text, type->Basic.name.len);
-	case Type_Pointer:
-		w = gb_string_append_rune(w, '^');
-		return write_type_to_canonical_string(w, type->Pointer.elem);
-	case Type_MultiPointer:
-		w = gb_string_appendc(w, "[^]");
-		return write_type_to_canonical_string(w, type->Pointer.elem);
-	case Type_SoaPointer:
-		w = gb_string_appendc(w, "#soa^");
-		return write_type_to_canonical_string(w, type->Pointer.elem);
-	case Type_EnumeratedArray:
-		if (type->EnumeratedArray.is_sparse) {
-			w = gb_string_appendc(w, "#sparse");
-		}
-		w = gb_string_append_rune(w, '[');
-		w = write_type_to_canonical_string(w, type->EnumeratedArray.index);
-		w = gb_string_append_rune(w, ']');
-		return write_type_to_canonical_string(w, type->EnumeratedArray.elem);
-	case Type_Array:
-		w = gb_string_appendc(w, gb_bprintf("[%lld]", cast(long long)type->Array.count));
-		return write_type_to_canonical_string(w, type->Array.elem);
-	case Type_Slice:
-		w = gb_string_appendc(w, "[]");
-		return write_type_to_canonical_string(w, type->Array.elem);
-	case Type_DynamicArray:
-		w = gb_string_appendc(w, "[dynamic]");
-		return write_type_to_canonical_string(w, type->DynamicArray.elem);
-	case Type_SimdVector:
-		w = gb_string_appendc(w, gb_bprintf("#simd[%lld]", cast(long long)type->SimdVector.count));
-		return write_type_to_canonical_string(w, type->SimdVector.elem);
-	case Type_Matrix:
-		if (type->Matrix.is_row_major) {
-			w = gb_string_appendc(w, "#row_major ");
-		}
-		w = gb_string_appendc(w, gb_bprintf("matrix[%lld, %lld]", cast(long long)type->Matrix.row_count, cast(long long)type->Matrix.column_count));
-		return write_type_to_canonical_string(w, type->Matrix.elem);
-	case Type_Map:
-		w = gb_string_appendc(w, "map[");
-		w = write_type_to_canonical_string(w, type->Map.key);
-		w = gb_string_appendc(w, "]");
-		return write_type_to_canonical_string(w, type->Map.value);
-
-	case Type_Enum:
-		w = gb_string_appendc(w, "enum");
-		if (type->Enum.base_type != nullptr) {
-			w = gb_string_append_rune(w, ' ');
-			w = write_type_to_canonical_string(w, type->Enum.base_type);
-			w = gb_string_append_rune(w, ' ');
-		}
-		w = gb_string_append_rune(w, '{');
-		for_array(i, type->Enum.fields) {
-			Entity *f = type->Enum.fields[i];
-			GB_ASSERT(f->kind == Entity_Constant);
-			if (i > 0) {
-				w = gb_string_appendc(w, ",");
-			}
-			w = gb_string_append_length(w, f->token.string.text, f->token.string.len);
-			w = gb_string_appendc(w, "=");
-			w = write_exact_value_to_string(w, f->Constant.value);
-		}
-		return gb_string_append_rune(w, '}');
-	case Type_BitSet:
-		w = gb_string_appendc(w, "bit_set[");
-		if (type->BitSet.elem == nullptr) {
-			w = write_type_to_canonical_string(w, type->BitSet.elem);
-		} else if (is_type_enum(type->BitSet.elem)) {
-			w = write_type_to_canonical_string(w, type->BitSet.elem);
-		} else {
-			w = gb_string_append_fmt(w, "%lld", type->BitSet.lower);
-			w = gb_string_append_fmt(w, "..=");
-			w = gb_string_append_fmt(w, "%lld", type->BitSet.upper);
-		}
-		if (type->BitSet.underlying != nullptr) {
-			w = gb_string_appendc(w, ";");
-			w = write_type_to_canonical_string(w, type->BitSet.underlying);
-		}
-		return gb_string_appendc(w, "]");
-
-	case Type_Union:
-		w = gb_string_appendc(w, "union");
-
-		switch (type->Union.kind) {
-		case UnionType_no_nil:     w = gb_string_appendc(w, "#no_nil");     break;
-		case UnionType_shared_nil: w = gb_string_appendc(w, "#shared_nil"); break;
-		}
-		if (type->Union.custom_align != 0) {
-			w = gb_string_append_fmt(w, "#align(%lld)", cast(long long)type->Union.custom_align);
-		}
-		w = gb_string_appendc(w, "{");
-		for_array(i, type->Union.variants) {
-			Type *t = type->Union.variants[i];
-			if (i > 0) w = gb_string_appendc(w, ", ");
-			w = write_type_to_canonical_string(w, t);
-		}
-		return gb_string_appendc(w, "}");
-	case Type_Struct:
-		if (type->Struct.soa_kind != StructSoa_None) {
-			switch (type->Struct.soa_kind) {
-			case StructSoa_Fixed:   w = gb_string_append_fmt(w, "#soa[%lld]", cast(long long)type->Struct.soa_count); break;
-			case StructSoa_Slice:   w = gb_string_appendc(w,    "#soa[]");                                    break;
-			case StructSoa_Dynamic: w = gb_string_appendc(w,    "#soa[dynamic]");                             break;
-			default: GB_PANIC("Unknown StructSoaKind"); break;
-			}
-			return write_type_to_canonical_string(w, type->Struct.soa_elem);
-		}
-
-		w = gb_string_appendc(w, "struct");
-		if (type->Struct.is_packed)    w = gb_string_appendc(w, "#packed");
-		if (type->Struct.is_raw_union) w = gb_string_appendc(w, "#raw_union");
-		if (type->Struct.is_no_copy)   w = gb_string_appendc(w, "#no_copy");
-		if (type->Struct.custom_min_field_align != 0) w = gb_string_append_fmt(w, "#min_field_align(%lld)", cast(long long)type->Struct.custom_min_field_align);
-		if (type->Struct.custom_max_field_align != 0) w = gb_string_append_fmt(w, "#max_field_align(%lld)", cast(long long)type->Struct.custom_max_field_align);
-		if (type->Struct.custom_align != 0)           w = gb_string_append_fmt(w, "#align(%lld)",           cast(long long)type->Struct.custom_align);
-		w = gb_string_appendc(w, "{");
-		for_array(i, type->Struct.fields) {
-			Entity *f = type->Struct.fields[i];
-			GB_ASSERT(f->kind == Entity_Variable);
-			if (i > 0) {
-				w = gb_string_appendc(w, ",");
-			}
-			w = gb_string_append_length       (w, f->token.string.text, f->token.string.len);
-			w = gb_string_appendc             (w, ":");
-			w = write_type_to_canonical_string(w, f->type);
-			String tag = type->Struct.tags[i];
-			if (tag.len != 0) {
-				String s = quote_to_ascii(heap_allocator(), tag);
-				w = gb_string_append_length(w, s.text, s.len);
-				gb_free(heap_allocator(), s.text);
-			}
-		}
-		return gb_string_appendc(w, "}");
-
-	case Type_BitField:
-		w = gb_string_appendc(w, "bit_field");
-		w = write_type_to_canonical_string(w, type->BitField.backing_type);
-		w = gb_string_appendc(w, " {");
-		for (isize i = 0; i < type->BitField.fields.count; i++) {
-			Entity *f = type->BitField.fields[i];
-			if (i > 0) {
-				w = gb_string_appendc(w, ",");
-			}
-			w = gb_string_append_length(w, f->token.string.text, f->token.string.len);
-			w = gb_string_appendc(w, ":");
-			w = write_type_to_canonical_string(w, f->type);
-			w = gb_string_appendc(w, "|");
-			w = gb_string_appendc(w, gb_bprintf("%u", type->BitField.bit_sizes[i]));
-		}
-		return gb_string_appendc(w, " }");
-
-	case Type_Proc:
-		w = gb_string_appendc(w, "proc");
-		if (default_calling_convention() != type->Proc.calling_convention) {
-			w = gb_string_appendc(w, "\"");
-			w = gb_string_appendc(w, proc_calling_convention_strings[type->Proc.calling_convention]);
-			w = gb_string_appendc(w, "\"");
-		}
-
-		w = write_canonical_params(w, type->Proc.params);
-		if (type->Proc.result_count > 0) {
-			w = gb_string_appendc(w, "->");
-			w = write_canonical_params(w, type->Proc.results);
-		}
-		return w;
-
-	case Type_Generic:
-		GB_PANIC("Type_Generic should never be hit");
-		return w;
-
-	case Type_Named:
-		if (type->Named.type_name != nullptr) {
-			Entity *e = type->Named.type_name;
-			if (e->pkg != nullptr) {
-				w = gb_string_append_length(w, e->pkg->name.text, e->pkg->name.len);
-				w = gb_string_appendc(w, ".");
-			}
-			Type *params = nullptr;
-			Entity *parent = type_get_polymorphic_parent(type, &params);
-			if (parent) {
-				w = gb_string_append_length(w, parent->token.string.text, parent->token.string.len);
-				w = write_canonical_params(w, params);
-			} else {
-				w = gb_string_append_length(w, e->token.string.text, e->token.string.len);
-			}
-		} else {
-			w = gb_string_append_length(w, type->Named.name.text, type->Named.name.len);
-		}
-		// Handle parapoly stuff here?
-		return w;
-
-	default:
-		GB_PANIC("unknown type kind %d", type->kind);
-		break;
-	}
-
-	return w;
-}