Ver código fonte

Correct a race condition when checking the procedure body

gingerBill 2 anos atrás
pai
commit
529383f5b1

+ 1 - 0
src/build_settings.cpp

@@ -291,6 +291,7 @@ struct BuildContext {
 	bool   show_error_line;
 
 	bool   ignore_lazy;
+	bool   ignore_llvm_build;
 
 	bool   use_subsystem_windows;
 	bool   ignore_microsoft_magic;

+ 14 - 11
src/check_decl.cpp

@@ -1419,9 +1419,9 @@ struct ProcUsingVar {
 };
 
 
-gb_internal void check_proc_body(CheckerContext *ctx_, Token token, DeclInfo *decl, Type *type, Ast *body) {
+gb_internal bool check_proc_body(CheckerContext *ctx_, Token token, DeclInfo *decl, Type *type, Ast *body) {
 	if (body == nullptr) {
-		return;
+		return false;
 	}
 	GB_ASSERT(body->kind == Ast_BlockStmt);
 
@@ -1502,7 +1502,7 @@ gb_internal void check_proc_body(CheckerContext *ctx_, Token token, DeclInfo *de
 	MUTEX_GUARD_BLOCK(ctx->scope->mutex) for_array(i, using_entities) {
 		Entity *e = using_entities[i].e;
 		Entity *uvar = using_entities[i].uvar;
-		Entity *prev = scope_insert(ctx->scope, uvar, false);
+		Entity *prev = scope_insert_no_mutex(ctx->scope, uvar);
 		if (prev != nullptr) {
 			error(e->token, "Namespace collision while 'using' procedure argument '%.*s' of: %.*s", LIT(e->token.string), LIT(prev->token.string));
 			error_line("%.*s != %.*s\n", LIT(uvar->token.string), LIT(prev->token.string));
@@ -1514,7 +1514,7 @@ gb_internal void check_proc_body(CheckerContext *ctx_, Token token, DeclInfo *de
 	bool where_clause_ok = evaluate_where_clauses(ctx, nullptr, decl->scope, &decl->proc_lit->ProcLit.where_clauses, !decl->where_clauses_evaluated);
 	if (!where_clause_ok) {
 		// NOTE(bill, 2019-08-31): Don't check the body as the where clauses failed
-		return;
+		return false;
 	}
 
 	check_open_scope(ctx, body);
@@ -1526,7 +1526,12 @@ gb_internal void check_proc_body(CheckerContext *ctx_, Token token, DeclInfo *de
 			// NOTE(bill): Don't err here
 		}
 
-		GB_ASSERT(decl->defer_use_checked == false);
+		GB_ASSERT(decl->proc_checked_state != ProcCheckedState_Checked);
+		if (decl->defer_use_checked) {
+			GB_ASSERT(is_type_polymorphic(type, true));
+			error(token, "Defer Use Checked: %.*s", LIT(decl->entity->token.string));
+			GB_ASSERT(decl->defer_use_checked == false);
+		}
 
 		check_stmt_list(ctx, bs->stmts, Stmt_CheckScopeDecls);
 
@@ -1575,10 +1580,8 @@ gb_internal void check_proc_body(CheckerContext *ctx_, Token token, DeclInfo *de
 	if (decl->parent != nullptr) {
 		Scope *ps = decl->parent->scope;
 		if (ps->flags & (ScopeFlag_File & ScopeFlag_Pkg & ScopeFlag_Global)) {
-			return;
-		} else {
-			mutex_lock(&ctx->info->deps_mutex);
-
+			return true;
+		} else MUTEX_GUARD_BLOCK(&ctx->info->deps_mutex) {
 			// NOTE(bill): Add the dependencies from the procedure literal (lambda)
 			// But only at the procedure level
 			for (auto const &entry : decl->deps) {
@@ -1589,8 +1592,8 @@ gb_internal void check_proc_body(CheckerContext *ctx_, Token token, DeclInfo *de
 				Type *t = entry.ptr;
 				ptr_set_add(&decl->parent->type_info_deps, t);
 			}
-
-			mutex_unlock(&ctx->info->deps_mutex);
 		}
 	}
+
+	return true;
 }

+ 20 - 3
src/check_expr.cpp

@@ -86,7 +86,6 @@ gb_internal Entity * find_polymorphic_record_entity (CheckerContext *c, Type *or
 gb_internal void     check_not_tuple                (CheckerContext *c, Operand *operand);
 gb_internal void     convert_to_typed               (CheckerContext *c, Operand *operand, Type *target_type);
 gb_internal gbString expr_to_string                 (Ast *expression);
-gb_internal void     check_proc_body                (CheckerContext *c, Token token, DeclInfo *decl, Type *type, Ast *body);
 gb_internal void     update_untyped_expr_type       (CheckerContext *c, Ast *e, Type *type, bool final);
 gb_internal bool     check_is_terminating           (Ast *node, String const &label);
 gb_internal bool     check_has_break                (Ast *stmt, String const &label, bool implicit);
@@ -478,6 +477,22 @@ gb_internal bool find_or_generate_polymorphic_procedure(CheckerContext *old_c, E
 					if (poly_proc_data) {
 						poly_proc_data->gen_entity = other;
 					}
+
+					DeclInfo *decl = other->decl_info;
+					if (decl->proc_checked_state != ProcCheckedState_Checked) {
+						ProcInfo *proc_info = gb_alloc_item(permanent_allocator(), ProcInfo);
+						proc_info->file  = other->file;
+						proc_info->token = other->token;
+						proc_info->decl  = decl;
+						proc_info->type  = other->type;
+						proc_info->body  = decl->proc_lit->ProcLit.body;
+						proc_info->tags  = other->Procedure.tags;;
+						proc_info->generated_from_polymorphic = true;
+						proc_info->poly_def_node = poly_def_node;
+
+						check_procedure_later(nctx.checker, proc_info);
+					}
+
 					return true;
 				}
 			}
@@ -518,7 +533,8 @@ gb_internal bool find_or_generate_polymorphic_procedure(CheckerContext *old_c, E
 	d->gen_proc_type = final_proc_type;
 	d->type_expr = pl->type;
 	d->proc_lit = proc_lit;
-	d->proc_checked = false;
+	d->proc_checked_state = ProcCheckedState_Unchecked;
+	d->defer_use_checked = false;
 
 	Entity *entity = alloc_entity_procedure(nullptr, token, final_proc_type, tags);
 	entity->identifier = ident;
@@ -528,7 +544,8 @@ gb_internal bool find_or_generate_polymorphic_procedure(CheckerContext *old_c, E
 	entity->scope = scope->parent;
 	entity->file = base_entity->file;
 	entity->pkg = base_entity->pkg;
-	entity->flags &= ~EntityFlag_ProcBodyChecked;
+	entity->flags = 0;
+	d->entity = entity;
 
 	AstFile *file = nullptr;
 	{

+ 178 - 46
src/checker.cpp

@@ -1,3 +1,5 @@
+#define DEBUG_CHECK_ALL_PROCEDURES 1
+
 #include "entity.cpp"
 #include "types.cpp"
 
@@ -179,6 +181,7 @@ gb_internal void import_graph_node_swap(ImportGraphNode **data, isize i, isize j
 
 
 gb_internal void init_decl_info(DeclInfo *d, Scope *scope, DeclInfo *parent) {
+	gb_zero_item(d);
 	d->parent = parent;
 	d->scope  = scope;
 	ptr_set_init(&d->deps,           heap_allocator());
@@ -438,9 +441,44 @@ gb_internal Entity *scope_lookup(Scope *s, String const &name) {
 	return entity;
 }
 
+gb_internal Entity *scope_insert_with_name_no_mutex(Scope *s, String const &name, Entity *entity) {
+	if (name == "") {
+		return nullptr;
+	}
+	StringHashKey key = string_hash_string(name);
+	Entity **found = nullptr;
+	Entity *result = nullptr;
+
+	found = string_map_get(&s->elements, key);
+
+	if (found) {
+		if (entity != *found) {
+			result = *found;
+		}
+		goto end;
+	}
+	if (s->parent != nullptr && (s->parent->flags & ScopeFlag_Proc) != 0) {
+		found = string_map_get(&s->parent->elements, key);
+		if (found) {
+			if ((*found)->flags & EntityFlag_Result) {
+				if (entity != *found) {
+					result = *found;
+				}
+				goto end;
+			}
+		}
+	}
+
+	string_map_set(&s->elements, key, entity);
+	if (entity->scope == nullptr) {
+		entity->scope = s;
+	}
+end:;
+	return result;
+}
 
 
-gb_internal Entity *scope_insert_with_name(Scope *s, String const &name, Entity *entity, bool use_mutex=true) {
+gb_internal Entity *scope_insert_with_name(Scope *s, String const &name, Entity *entity) {
 	if (name == "") {
 		return nullptr;
 	}
@@ -448,9 +486,8 @@ gb_internal Entity *scope_insert_with_name(Scope *s, String const &name, Entity
 	Entity **found = nullptr;
 	Entity *result = nullptr;
 
-	if (use_mutex) mutex_lock(&s->mutex);
-	defer (if (use_mutex) mutex_unlock(&s->mutex));
-	
+	MUTEX_GUARD(&s->mutex);
+
 	found = string_map_get(&s->elements, key);
 
 	if (found) {
@@ -479,9 +516,14 @@ end:;
 	return result;
 }
 
-gb_internal Entity *scope_insert(Scope *s, Entity *entity, bool use_mutex) {
+gb_internal Entity *scope_insert(Scope *s, Entity *entity) {
 	String name = entity->token.string;
-	return scope_insert_with_name(s, name, entity, use_mutex);
+	return scope_insert_with_name(s, name, entity);
+}
+
+gb_internal Entity *scope_insert_no_mutex(Scope *s, Entity *entity) {
+	String name = entity->token.string;
+	return scope_insert_with_name_no_mutex(s, name, entity);
 }
 
 
@@ -1135,6 +1177,9 @@ gb_internal void init_checker_info(CheckerInfo *i) {
 
 	map_init(&i->objc_msgSend_types, a);
 	string_map_init(&i->load_file_cache, a);
+
+	array_init(&i->all_procedures, heap_allocator());
+
 }
 
 gb_internal void destroy_checker_info(CheckerInfo *i) {
@@ -1934,6 +1979,7 @@ gb_internal void add_type_info_type_internal(CheckerContext *c, Type *t) {
 
 
 gb_global std::atomic<bool> global_procedure_body_in_worker_queue = false;
+gb_global std::atomic<bool> global_after_checking_procedure_bodies = false;
 
 gb_internal WORKER_TASK_PROC(check_proc_info_worker_proc);
 
@@ -1941,12 +1987,24 @@ gb_internal void check_procedure_later(Checker *c, ProcInfo *info) {
 	GB_ASSERT(info != nullptr);
 	GB_ASSERT(info->decl != nullptr);
 
-	if (global_procedure_body_in_worker_queue) {
+	if (global_after_checking_procedure_bodies) {
+		Entity *e = info->decl->entity;
+		debugf("CHECK PROCEDURE LATER! %.*s :: %s {...}\n", LIT(e->token.string), type_to_string(e->type));
+	}
+
+	if (global_procedure_body_in_worker_queue.load()) {
 		thread_pool_add_task(check_proc_info_worker_proc, info);
 	} else {
-		GB_ASSERT(global_procedure_body_in_worker_queue == false);
 		array_add(&c->procs_to_check, info);
 	}
+
+	if (DEBUG_CHECK_ALL_PROCEDURES) {
+		MUTEX_GUARD_BLOCK(&c->info.all_procedures_mutex) {
+			GB_ASSERT(info != nullptr);
+			GB_ASSERT(info->decl != nullptr);
+			array_add(&c->info.all_procedures, info);
+		}
+	}
 }
 
 gb_internal void check_procedure_later(Checker *c, AstFile *file, Token token, DeclInfo *decl, Type *type, Ast *body, u64 tags) {
@@ -5010,24 +5068,26 @@ gb_internal bool check_proc_info(Checker *c, ProcInfo *pi, UntypedExprInfoMap *u
 	if (pi->type == nullptr) {
 		return false;
 	}
-	Entity *e = pi->decl->entity;
 
-	MUTEX_GUARD_BLOCK(&pi->decl->proc_checked_mutex) {
-		if (pi->decl->proc_checked) {
-			if (e != nullptr) {
-				GB_ASSERT(e->flags & EntityFlag_ProcBodyChecked);
-			}
-			return true;
-		}
-		if (e != nullptr && (e->flags & EntityFlag_ProcBodyChecked) != 0) {
-			GB_ASSERT(pi->decl->proc_checked);
-			return true;
+	MUTEX_GUARD(&pi->decl->proc_checked_mutex);
+
+	Entity *e = pi->decl->entity;
+	switch (pi->decl->proc_checked_state.load()) {
+	case ProcCheckedState_InProgress:
+		if (e) {
+			GB_ASSERT(global_procedure_body_in_worker_queue.load());
 		}
-		pi->decl->proc_checked = true;
+		return false;
+	case ProcCheckedState_Checked:
 		if (e != nullptr) {
-			e->flags |= EntityFlag_ProcBodyChecked;
+			GB_ASSERT(e->flags & EntityFlag_ProcBodyChecked);
 		}
+		return true;
+	case ProcCheckedState_Unchecked:
+		// okay
+		break;
 	}
+	pi->decl->proc_checked_state.store(ProcCheckedState_InProgress);
 
 	GB_ASSERT(pi->type->kind == Type_Proc);
 	TypeProc *pt = &pi->type->Proc;
@@ -5039,17 +5099,21 @@ gb_internal bool check_proc_info(Checker *c, ProcInfo *pi, UntypedExprInfoMap *u
 			token = ast_token(pi->poly_def_node);
 		}
 		error(token, "Unspecialized polymorphic procedure '%.*s'", LIT(name));
+		pi->decl->proc_checked_state.store(ProcCheckedState_Unchecked);
 		return false;
 	}
 
 	if (pt->is_polymorphic && pt->is_poly_specialized) {
+		Entity *e = pi->decl->entity;
+		GB_ASSERT(e != nullptr);
 		if ((e->flags & EntityFlag_Used) == 0) {
 			// NOTE(bill, 2019-08-31): It was never used, don't check
+			// NOTE(bill, 2023-01-02): This may need to be checked again if it is used elsewhere?
+		pi->decl->proc_checked_state.store(ProcCheckedState_Unchecked);
 			return false;
 		}
 	}
 
-
 	CheckerContext ctx = make_checker_context(c);
 	defer (destroy_checker_context(&ctx));
 	reset_checker_context(&ctx, pi->file, untyped);
@@ -5077,14 +5141,34 @@ gb_internal bool check_proc_info(Checker *c, ProcInfo *pi, UntypedExprInfoMap *u
 		ctx.state_flags &= ~StateFlag_type_assert;
 	}
 
-	check_proc_body(&ctx, pi->token, pi->decl, pi->type, pi->body);
+	bool body_was_checked = check_proc_body(&ctx, pi->token, pi->decl, pi->type, pi->body);
+
+	if (body_was_checked) {
+		pi->decl->proc_checked_state.store(ProcCheckedState_Checked);
+		if (pi->body) {
+			Entity *e = pi->decl->entity;
+			if (e != nullptr) {
+				e->flags |= EntityFlag_ProcBodyChecked;
+			}
+		}
+	} else {
+		pi->decl->proc_checked_state.store(ProcCheckedState_Unchecked);
+		if (pi->body) {
+			Entity *e = pi->decl->entity;
+			if (e != nullptr) {
+				e->flags &= ~EntityFlag_ProcBodyChecked;
+			}
+		}
+	}
+
 	add_untyped_expressions(&c->info, ctx.untyped);
+
 	return true;
 }
 
 GB_STATIC_ASSERT(sizeof(isize) == sizeof(void *));
 
-gb_internal bool consume_proc_info_queue(Checker *c, ProcInfo *pi, UntypedExprInfoMap *untyped);
+gb_internal bool consume_proc_info(Checker *c, ProcInfo *pi, UntypedExprInfoMap *untyped);
 
 gb_internal void check_unchecked_bodies(Checker *c) {
 	// NOTE(2021-02-26, bill): Sanity checker
@@ -5092,10 +5176,15 @@ gb_internal void check_unchecked_bodies(Checker *c) {
 	// even ones which should not exist, due to the multithreaded nature of the parser
 	// HACK TODO(2021-02-26, bill): Actually fix this race condition
 
+	GB_ASSERT(c->procs_to_check.count == 0);
+
 	UntypedExprInfoMap untyped = {};
 	map_init(&untyped, heap_allocator());
 	defer (map_destroy(&untyped));
 
+	// use the `procs_to_check` array
+	global_procedure_body_in_worker_queue = false;
+
 	for (auto const &entry : c->info.minimum_dependency_set) {
 		Entity *e = entry.ptr;
 		if (e == nullptr || e->kind != Entity_Procedure) {
@@ -5122,19 +5211,22 @@ gb_internal void check_unchecked_bodies(Checker *c) {
 			}
 
 			debugf("unchecked: %.*s\n", LIT(e->token.string));
-			array_add(&c->procs_to_check, pi);
+			check_procedure_later(c, pi);
 		}
 	}
 
-	for (ProcInfo *pi : c->procs_to_check) {
-		Entity *e = pi->decl->entity;
-		if (consume_proc_info_queue(c, pi, &untyped)) {
-			add_dependency_to_set(c, e);
-			GB_ASSERT(e->flags & EntityFlag_ProcBodyChecked);
+	if (!global_procedure_body_in_worker_queue) {
+		for_array(i, c->procs_to_check) {
+			ProcInfo *pi = c->procs_to_check[i];
+			consume_proc_info(c, pi, &untyped);
 		}
+		array_clear(&c->procs_to_check);
+	} else {
+		thread_pool_wait();
 	}
 
-	thread_pool_wait();
+	global_procedure_body_in_worker_queue = false;
+	global_after_checking_procedure_bodies = true;
 }
 
 gb_internal void check_test_procedures(Checker *c) {
@@ -5172,8 +5264,15 @@ gb_internal void check_test_procedures(Checker *c) {
 
 gb_global std::atomic<isize> total_bodies_checked;
 
-gb_internal bool consume_proc_info_queue(Checker *c, ProcInfo *pi, UntypedExprInfoMap *untyped) {
+gb_internal bool consume_proc_info(Checker *c, ProcInfo *pi, UntypedExprInfoMap *untyped) {
 	GB_ASSERT(pi->decl != nullptr);
+	switch (pi->decl->proc_checked_state.load()) {
+	case ProcCheckedState_InProgress:
+		return false;
+	case ProcCheckedState_Checked:
+		return true;
+	}
+
 	if (pi->decl->parent && pi->decl->parent->entity) {
 		Entity *parent = pi->decl->parent->entity;
 		// NOTE(bill): Only check a nested procedure if its parent's body has been checked first
@@ -5187,9 +5286,11 @@ gb_internal bool consume_proc_info_queue(Checker *c, ProcInfo *pi, UntypedExprIn
 	if (untyped) {
 		map_clear(untyped);
 	}
-	bool ok = check_proc_info(c, pi, untyped);
-	total_bodies_checked.fetch_add(1, std::memory_order_relaxed);
-	return ok;
+	if (check_proc_info(c, pi, untyped)) {
+		total_bodies_checked.fetch_add(1, std::memory_order_relaxed);
+		return true;
+	}
+	return false;
 }
 
 struct CheckProcedureBodyWorkerData {
@@ -5218,9 +5319,11 @@ gb_internal WORKER_TASK_PROC(check_proc_info_worker_proc) {
 		}
 	}
 	map_clear(untyped);
-	bool ok = check_proc_info(c, pi, untyped);
-	total_bodies_checked.fetch_add(1, std::memory_order_relaxed);
-	return !ok;
+	if (check_proc_info(c, pi, untyped)) {
+		total_bodies_checked.fetch_add(1, std::memory_order_relaxed);
+		return 0;
+	}
+	return 1;
 }
 
 
@@ -5247,7 +5350,7 @@ gb_internal void check_procedure_bodies(Checker *c) {
 	if (thread_count == 1) {
 		UntypedExprInfoMap *untyped = &check_procedure_bodies_worker_data[0].untyped;
 		for_array(i, c->procs_to_check) {
-			consume_proc_info_queue(c, c->procs_to_check[i], untyped);
+			consume_proc_info(c, c->procs_to_check[i], untyped);
 		}
 		array_clear(&c->procs_to_check);
 
@@ -5266,9 +5369,6 @@ gb_internal void check_procedure_bodies(Checker *c) {
 
 	thread_pool_wait();
 
-	debugf("Total Procedure Bodies Checked: %td\n", total_bodies_checked.load(std::memory_order_relaxed));
-
-
 	global_procedure_body_in_worker_queue = false;
 }
 gb_internal void add_untyped_expressions(CheckerInfo *cinfo, UntypedExprInfoMap *untyped) {
@@ -5662,9 +5762,6 @@ gb_internal void check_parsed_files(Checker *c) {
 	TIME_SECTION("check test procedures");
 	check_test_procedures(c);
 
-	TIME_SECTION("check bodies have all been checked");
-	check_unchecked_bodies(c);
-
 	TIME_SECTION("add type info for type definitions");
 	add_type_info_for_type_definitions(c);
 	check_merge_queues_into_arrays(c);
@@ -5672,6 +5769,12 @@ gb_internal void check_parsed_files(Checker *c) {
 	TIME_SECTION("generate minimum dependency set");
 	generate_minimum_dependency_set(c, c->info.entry_point);
 
+	TIME_SECTION("check bodies have all been checked");
+	check_unchecked_bodies(c);
+
+	check_merge_queues_into_arrays(c);
+
+
 	TIME_SECTION("check entry point");
 	if (build_context.build_mode == BuildMode_Executable && !build_context.no_entry_point && build_context.command_kind != Command_test) {
 		Scope *s = c->info.init_scope;
@@ -5694,11 +5797,39 @@ gb_internal void check_parsed_files(Checker *c) {
 		}
 	}
 
+	thread_pool_wait();
+	GB_ASSERT(c->procs_to_check.count == 0);
+
+	if (DEBUG_CHECK_ALL_PROCEDURES) {
+		UntypedExprInfoMap untyped = {};
+		map_init(&untyped, heap_allocator());
+		defer (map_destroy(&untyped));
+
+		for_array(i, c->info.all_procedures) {
+			ProcInfo *pi = c->info.all_procedures[i];
+			GB_ASSERT(pi != nullptr);
+			GB_ASSERT(pi->decl != nullptr);
+			Entity *e = pi->decl->entity;
+			auto proc_checked_state = pi->decl->proc_checked_state.load();
+			if (e && ((e->flags & EntityFlag_ProcBodyChecked) == 0)) {
+				if ((e->flags & EntityFlag_Used) != 0) {
+					debugf("%.*s :: %s\n", LIT(e->token.string), type_to_string(e->type));
+					debugf("proc body unchecked\n");
+					debugf("Checked State: %s\n\n", ProcCheckedState_strings[proc_checked_state]);
+
+					consume_proc_info(c, pi, &untyped);
+				}
+			}
+		}
+	}
+
+	debugf("Total Procedure Bodies Checked: %td\n", total_bodies_checked.load(std::memory_order_relaxed));
+
 	TIME_SECTION("check unique package names");
 	check_unique_package_names(c);
 
-
 	TIME_SECTION("sanity checks");
+	check_merge_queues_into_arrays(c);
 	GB_ASSERT(c->info.entity_queue.count.load(std::memory_order_relaxed) == 0);
 	GB_ASSERT(c->info.definition_queue.count.load(std::memory_order_relaxed) == 0);
 
@@ -5717,5 +5848,6 @@ gb_internal void check_parsed_files(Checker *c) {
 		}
 	}
 
+
 	TIME_SECTION("type check finish");
 }

+ 19 - 2
src/checker.hpp

@@ -142,6 +142,20 @@ typedef DECL_ATTRIBUTE_PROC(DeclAttributeProc);
 gb_internal void check_decl_attributes(CheckerContext *c, Array<Ast *> const &attributes, DeclAttributeProc *proc, AttributeContext *ac);
 
 
+enum ProcCheckedState : u8 {
+	ProcCheckedState_Unchecked,
+	ProcCheckedState_InProgress,
+	ProcCheckedState_Checked,
+
+	ProcCheckedState_COUNT
+};
+
+char const *ProcCheckedState_strings[ProcCheckedState_COUNT] {
+	"Unchecked",
+	"In Progress",
+	"Checked",
+};
+
 // DeclInfo is used to store information of certain declarations to allow for "any order" usage
 struct DeclInfo {
 	DeclInfo *    parent; // NOTE(bill): only used for procedure literals at the moment
@@ -157,7 +171,7 @@ struct DeclInfo {
 	Type *        gen_proc_type; // Precalculated
 	bool          is_using;
 	bool          where_clauses_evaluated;
-	bool          proc_checked;
+	std::atomic<ProcCheckedState> proc_checked_state;
 	BlockingMutex proc_checked_mutex;
 	isize         defer_used;
 	bool          defer_use_checked;
@@ -375,6 +389,9 @@ struct CheckerInfo {
 
 	BlockingMutex load_file_mutex;
 	StringMap<LoadFileCache *> load_file_cache;
+
+	BlockingMutex all_procedures_mutex;;
+	Array<ProcInfo *> all_procedures;
 };
 
 struct CheckerContext {
@@ -458,7 +475,7 @@ gb_internal Entity *entity_of_node(Ast *expr);
 gb_internal Entity *scope_lookup_current(Scope *s, String const &name);
 gb_internal Entity *scope_lookup (Scope *s, String const &name);
 gb_internal void    scope_lookup_parent (Scope *s, String const &name, Scope **scope_, Entity **entity_);
-gb_internal Entity *scope_insert (Scope *s, Entity *entity, bool use_mutex=true);
+gb_internal Entity *scope_insert (Scope *s, Entity *entity);
 
 
 gb_internal void      add_type_and_value      (CheckerInfo *i, Ast *expression, AddressingMode mode, Type *type, ExactValue value);

+ 6 - 0
src/llvm_backend.cpp

@@ -2255,6 +2255,12 @@ gb_internal void lb_generate_code(lbGenerator *gen) {
 		}
 	}
 
+	if (build_context.ignore_llvm_build) {
+		gb_printf_err("LLVM SUCCESS!\n");
+		gb_exit(1);
+		return;
+	}
+
 	if (do_threading && non_empty_module_count > 1) {
 		for (auto const &entry : gen->modules) {
 			lbModule *m = entry.value;

+ 1 - 0
src/llvm_backend.hpp

@@ -301,6 +301,7 @@ struct lbProcedure {
 	lbBlock *        curr_block;
 	lbTargetList *   target_list;
 	PtrMap<Entity *, lbValue> direct_parameters;
+	PtrMap<Entity *, lbValue> local_entity_map;
 
 	Ast *curr_stmt;
 

+ 5 - 1
src/llvm_backend_expr.cpp

@@ -3404,7 +3404,11 @@ gb_internal lbAddr lb_build_addr_from_entity(lbProcedure *p, Entity *e, Ast *exp
 
 
 	lbValue v = {};
-	lbValue *found = map_get(&p->module->values, e);
+	lbValue *found = nullptr;
+	found = map_get(&p->local_entity_map, e);
+	if (found == nullptr) {
+		found = map_get(&p->module->values, e);
+	}
 	if (found) {
 		v = *found;
 	} else if (e->kind == Entity_Variable && e->flags & EntityFlag_Using) {

+ 4 - 1
src/llvm_backend_general.cpp

@@ -2714,7 +2714,6 @@ gb_internal lbValue lb_find_value_from_entity(lbModule *m, Entity *e) {
 			return g;
 		}
 	}
-
 	GB_PANIC("\n\tError in: %s, missing value '%.*s'\n", token_pos_to_string(e->token.pos), LIT(e->token.string));
 	return {};
 }
@@ -2845,6 +2844,10 @@ gb_internal lbAddr lb_add_local(lbProcedure *p, Type *type, Entity *e, bool zero
 		lb_mem_zero_ptr(p, ptr, type, alignment);
 	}
 
+	if (e != nullptr) {
+		map_set(&p->local_entity_map, e, val);
+	}
+
 	return lb_addr(val);
 }
 

+ 2 - 1
src/llvm_backend_proc.cpp

@@ -68,7 +68,7 @@ gb_internal lbProcedure *lb_create_procedure(lbModule *m, Entity *entity, bool i
 	GB_ASSERT(entity != nullptr);
 	GB_ASSERT(entity->kind == Entity_Procedure);
 	if (!entity->Procedure.is_foreign) {
-		GB_ASSERT_MSG(entity->flags & EntityFlag_ProcBodyChecked, "%.*s :: %s", LIT(entity->token.string), type_to_string(entity->type));
+		GB_ASSERT_MSG(entity->flags & EntityFlag_ProcBodyChecked, "%.*s :: %s (was parapoly: %d)", LIT(entity->token.string), type_to_string(entity->type), is_type_polymorphic(entity->type, true));
 	}
 
 	String link_name = {};
@@ -487,6 +487,7 @@ gb_internal void lb_begin_procedure_body(lbProcedure *p) {
 	lb_start_block(p, p->entry_block);
 
 	map_init(&p->direct_parameters, heap_allocator());
+	map_init(&p->local_entity_map, heap_allocator());
 
 	GB_ASSERT(p->type != nullptr);
 

+ 5 - 1
src/llvm_backend_stmt.cpp

@@ -1531,6 +1531,9 @@ gb_internal void lb_build_static_variables(lbProcedure *p, AstValueDecl *vd) {
 		lbValue global_val = {global, alloc_type_pointer(e->type)};
 		lb_add_entity(p->module, e, global_val);
 		lb_add_member(p->module, mangled_name, global_val);
+		if (e) {
+			map_set(&p->local_entity_map, e, global_val);
+		}
 	}
 }
 gb_internal void lb_append_tuple_values(lbProcedure *p, Array<lbValue> *dst_values, lbValue src_value) {
@@ -2188,9 +2191,10 @@ gb_internal void lb_build_stmt(lbProcedure *p, Ast *node) {
 			for_array(i, vd->names) {
 				Ast *name = vd->names[i];
 				if (!is_blank_ident(name)) {
+					GB_ASSERT(name->kind == Ast_Ident);
 					Entity *e = entity_of_node(name);
 					TokenPos pos = ast_token(name).pos;
-					GB_ASSERT_MSG(e != nullptr, "%s", token_pos_to_string(pos));
+					GB_ASSERT_MSG(e != nullptr, "\n%s missing entity for %.*s", token_pos_to_string(pos), LIT(name->Ident.token.string));
 					if (e->flags & EntityFlag_Static) {
 						// NOTE(bill): If one of the entities is static, they all are
 						is_static = true;

+ 5 - 0
src/main.cpp

@@ -660,6 +660,7 @@ enum BuildFlagKind {
 
 	// internal use only
 	BuildFlag_InternalIgnoreLazy,
+	BuildFlag_InternalIgnoreLLVMBuild,
 
 #if defined(GB_SYSTEM_WINDOWS)
 	BuildFlag_IgnoreVsSearch,
@@ -832,6 +833,7 @@ gb_internal bool parse_build_flags(Array<String> args) {
 	add_flag(&build_flags, BuildFlag_ErrorPosStyle,           str_lit("error-pos-style"),           BuildFlagParam_String,  Command_all);
 
 	add_flag(&build_flags, BuildFlag_InternalIgnoreLazy,      str_lit("internal-ignore-lazy"),      BuildFlagParam_None,    Command_all);
+	add_flag(&build_flags, BuildFlag_InternalIgnoreLLVMBuild, str_lit("internal-ignore-llvm-build"),BuildFlagParam_None,    Command_all);
 
 #if defined(GB_SYSTEM_WINDOWS)
 	add_flag(&build_flags, BuildFlag_IgnoreVsSearch,          str_lit("ignore-vs-search"),          BuildFlagParam_None,    Command__does_build);
@@ -1491,6 +1493,9 @@ gb_internal bool parse_build_flags(Array<String> args) {
 						case BuildFlag_InternalIgnoreLazy:
 							build_context.ignore_lazy = true;
 							break;
+						case BuildFlag_InternalIgnoreLLVMBuild:
+							build_context.ignore_llvm_build = true;
+							break;
 					#if defined(GB_SYSTEM_WINDOWS)
 						case BuildFlag_IgnoreVsSearch: {
 							GB_ASSERT(value.kind == ExactValue_Invalid);