Browse Source

Add experimental support for a threaded semantic checker to `-threaded-checker`

gingerBill 4 years ago
parent
commit
d9e6ade030
12 changed files with 253 additions and 102 deletions
  1. 3 1
      src/big_int.cpp
  2. 1 0
      src/build_settings.cpp
  3. 3 3
      src/check_builtin.cpp
  4. 2 1
      src/check_decl.cpp
  5. 64 30
      src/check_expr.cpp
  6. 3 3
      src/check_stmt.cpp
  7. 9 6
      src/check_type.cpp
  8. 127 44
      src/checker.cpp
  9. 2 1
      src/checker.hpp
  10. 12 12
      src/exact_value.cpp
  11. 12 0
      src/main.cpp
  12. 15 1
      src/types.cpp

+ 3 - 1
src/big_int.cpp

@@ -587,7 +587,9 @@ void big_int_add(BigInt *dst, BigInt const *x, BigInt const *y) {
 			}
 		}
 
-		GB_ASSERT(overflow == 0);
+		if (overflow != 0) {
+			GB_ASSERT_MSG(overflow == 0, "%p %p %p", dst, x, y);
+		}
 		dst->len = i;
 		big_int_normalize(dst);
 		return;

+ 1 - 0
src/build_settings.cpp

@@ -208,6 +208,7 @@ struct BuildContext {
 	bool   linker_map_file;
 
 	bool use_separate_modules;
+	bool threaded_checker;
 
 	u32 cmd_doc_flags;
 	Array<String> extra_packages;

+ 3 - 3
src/check_builtin.cpp

@@ -1686,7 +1686,7 @@ bool check_builtin_procedure(CheckerContext *c, Operand *operand, Ast *call, i32
 
 		Ast *dummy_node_struct = alloc_ast_node(nullptr, Ast_Invalid);
 		Ast *dummy_node_soa = alloc_ast_node(nullptr, Ast_Invalid);
-		Scope *s = create_scope(builtin_pkg->scope);
+		Scope *s = create_scope(c->info, builtin_pkg->scope);
 
 		auto fields = array_make<Entity *>(permanent_allocator(), 0, types.count);
 		for_array(i, types) {
@@ -1917,7 +1917,7 @@ bool check_builtin_procedure(CheckerContext *c, Operand *operand, Ast *call, i32
 			soa_struct->Struct.soa_elem = elem;
 			soa_struct->Struct.soa_count = count;
 
-			scope = create_scope(c->scope);
+			scope = create_scope(c->info, c->scope);
 			soa_struct->Struct.scope = scope;
 
 			String params_xyzw[4] = {
@@ -1950,7 +1950,7 @@ bool check_builtin_procedure(CheckerContext *c, Operand *operand, Ast *call, i32
 			soa_struct->Struct.soa_elem = elem;
 			soa_struct->Struct.soa_count = count;
 
-			scope = create_scope(old_struct->Struct.scope->parent);
+			scope = create_scope(c->info, old_struct->Struct.scope->parent);
 			soa_struct->Struct.scope = scope;
 
 			for_array(i, old_struct->Struct.fields) {

+ 2 - 1
src/check_decl.cpp

@@ -1276,7 +1276,8 @@ void check_proc_body(CheckerContext *ctx_, Token token, DeclInfo *decl, Type *ty
 		Entity *uvar = using_entities[i].uvar;
 		Entity *prev = scope_insert(ctx->scope, uvar);
 		if (prev != nullptr) {
-			error(e->token, "Namespace collision while 'using' '%.*s' of: %.*s", LIT(e->token.string), LIT(prev->token.string));
+			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));
 			break;
 		}
 	}

+ 64 - 30
src/check_expr.cpp

@@ -283,7 +283,7 @@ bool find_or_generate_polymorphic_procedure(CheckerContext *c, Entity *base_enti
 	CheckerInfo *info = c->info;
 	CheckerContext nctx = *c;
 
-	Scope *scope = create_scope(base_entity->scope);
+	Scope *scope = create_scope(c->info, base_entity->scope);
 	scope->flags |= ScopeFlag_Proc;
 	nctx.scope = scope;
 	nctx.allow_polymorphic_types = true;
@@ -307,11 +307,11 @@ bool find_or_generate_polymorphic_procedure(CheckerContext *c, Entity *base_enti
 	}
 
 	gb_mutex_lock(&info->gen_procs_mutex);
+	defer (gb_mutex_unlock(&info->gen_procs_mutex));
 	auto *found_gen_procs = map_get(&info->gen_procs, hash_pointer(base_entity->identifier));
-	gb_mutex_unlock(&info->gen_procs_mutex);
 	if (found_gen_procs) {
-		gb_mutex_lock(&info->gen_procs_mutex);
-		defer (gb_mutex_unlock(&info->gen_procs_mutex));
+		// gb_mutex_lock(&info->gen_procs_mutex);
+		// defer (gb_mutex_unlock(&info->gen_procs_mutex));
 
 		auto procs = *found_gen_procs;
 		for_array(i, procs) {
@@ -349,8 +349,8 @@ bool find_or_generate_polymorphic_procedure(CheckerContext *c, Entity *base_enti
 		}
 
 		if (found_gen_procs) {
-			gb_mutex_lock(&info->gen_procs_mutex);
-			defer (gb_mutex_unlock(&info->gen_procs_mutex));
+			// gb_mutex_lock(&info->gen_procs_mutex);
+			// defer (gb_mutex_unlock(&info->gen_procs_mutex));
 
 			auto procs = *found_gen_procs;
 			for_array(i, procs) {
@@ -421,7 +421,7 @@ bool find_or_generate_polymorphic_procedure(CheckerContext *c, Entity *base_enti
 	proc_info->generated_from_polymorphic = true;
 	proc_info->poly_def_node = poly_def_node;
 
-	gb_mutex_lock(&info->gen_procs_mutex);
+	// gb_mutex_lock(&info->gen_procs_mutex);
 	if (found_gen_procs) {
 		array_add(found_gen_procs, entity);
 	} else {
@@ -429,7 +429,7 @@ bool find_or_generate_polymorphic_procedure(CheckerContext *c, Entity *base_enti
 		array_add(&array, entity);
 		map_set(&info->gen_procs, hash_pointer(base_entity->identifier), array);
 	}
-	gb_mutex_unlock(&info->gen_procs_mutex);
+	// gb_mutex_unlock(&info->gen_procs_mutex);
 
 	GB_ASSERT(entity != nullptr);
 
@@ -1738,7 +1738,12 @@ void check_cast_error_suggestion(CheckerContext *c, Operand *o, Type *type) {
 
 void check_is_expressible(CheckerContext *ctx, Operand *o, Type *type) {
 	GB_ASSERT(o->mode == Addressing_Constant);
-	if (!is_type_constant_type(type) || !check_representable_as_constant(ctx, o->value, type, &o->value)) {
+	ExactValue out_value = o->value;
+	if (is_type_constant_type(type) && check_representable_as_constant(ctx, o->value, type, &out_value)) {
+		o->value = out_value;
+	} else {
+		o->value = out_value;
+
 		gbString a = expr_to_string(o->expr);
 		gbString b = type_to_string(type);
 		gbString c = type_to_string(o->type);
@@ -1753,7 +1758,13 @@ void check_is_expressible(CheckerContext *ctx, Operand *o, Type *type) {
 			if (!is_type_integer(o->type) && is_type_integer(type)) {
 				error(o->expr, "'%s' truncated to '%s'", a, b);
 			} else {
-				error(o->expr, "Cannot convert '%s' to '%s' from '%s", a, b, c);
+				#if 0
+				gb_printf_err("AddressingMode, %d\n", o->mode);
+				gb_printf_err("ExactValueKind, %d\n", o->value.kind);
+				bool ok = check_representable_as_constant(ctx, o->value, type, &out_value);
+				gb_printf_err("ok, %d\n", ok);
+				#endif
+				error(o->expr, "Cannot convert numeric value '%s' to '%s' from '%s", a, b, c);
 				check_assignment_error_suggestion(ctx, o, type);
 			}
 		} else {
@@ -1797,10 +1808,6 @@ bool check_is_not_addressable(CheckerContext *c, Operand *o) {
 void check_unary_expr(CheckerContext *c, Operand *o, Token op, Ast *node) {
 	switch (op.kind) {
 	case Token_And: { // Pointer address
-		if (node->kind == Ast_TypeAssertion) {
-			gb_printf_err("%s\n", expr_to_string(node));
-		}
-
 		if (check_is_not_addressable(c, o)) {
 			if (ast_node_expect(node, Ast_UnaryExpr)) {
 				ast_node(ue, UnaryExpr, node);
@@ -2225,7 +2232,8 @@ void check_shift(CheckerContext *c, Operand *x, Operand *y, Ast *node, Type *typ
 
 		TokenPos pos = ast_token(x->expr).pos;
 		if (x_is_untyped) {
-			ExprInfo *info = check_get_expr_info(&c->checker->info, x->expr);
+			gb_mutex_lock(&c->info->untyped_mutex);
+			ExprInfo *info = check_get_expr_info(c->info, x->expr);
 			if (info != nullptr) {
 				info->is_lhs = true;
 			}
@@ -2234,6 +2242,8 @@ void check_shift(CheckerContext *c, Operand *x, Operand *y, Ast *node, Type *typ
 				x->type = type_hint;
 			}
 			// x->value = x_val;
+
+			gb_mutex_unlock(&c->info->untyped_mutex);
 			return;
 		}
 	}
@@ -2519,6 +2529,25 @@ bool check_transmute(CheckerContext *c, Ast *node, Operand *o, Type *t) {
 		return false;
 	}
 
+	Type *dst_bt = base_type(t);
+	if (dst_bt == nullptr || dst_bt == t_invalid) {
+		GB_ASSERT(global_error_collector.count != 0);
+
+		o->mode = Addressing_Invalid;
+		o->expr = node;
+		return false;
+	}
+
+	Type *src_bt = base_type(o->type);
+	if (src_bt == nullptr || src_bt == t_invalid) {
+		// NOTE(bill): this should be an error
+		GB_ASSERT(global_error_collector.count != 0);
+		o->mode = Addressing_Value;
+		o->expr = node;
+		o->type = t;
+		return true;
+	}
+
 	i64 srcz = type_size_of(o->type);
 	i64 dstz = type_size_of(t);
 	if (srcz != dstz) {
@@ -2899,11 +2928,13 @@ void check_binary_expr(CheckerContext *c, Operand *x, Ast *node, Type *type_hint
 
 void update_expr_type(CheckerContext *c, Ast *e, Type *type, bool final) {
 	GB_ASSERT(e != nullptr);
-	ExprInfo *old = check_get_expr_info(&c->checker->info, e);
+	gb_mutex_lock(&c->info->untyped_mutex);
+	defer (gb_mutex_unlock(&c->info->untyped_mutex));
+	ExprInfo *old = check_get_expr_info(c->info, e);
 	if (old == nullptr) {
 		if (type != nullptr && type != t_invalid) {
 			if (e->tav.type == nullptr || e->tav.type == t_invalid) {
-				add_type_and_value(&c->checker->info, e, e->tav.mode, type ? type : e->tav.type, e->tav.value);
+				add_type_and_value(c->info, e, e->tav.mode, type ? type : e->tav.type, e->tav.value);
 			}
 		}
 		return;
@@ -2966,7 +2997,7 @@ void update_expr_type(CheckerContext *c, Ast *e, Type *type, bool final) {
 	}
 
 	// We need to remove it and then give it a new one
-	check_remove_expr_info(&c->checker->info, e);
+	map_remove(&c->info->untyped, hash_node(e));
 
 	if (old->is_lhs && !is_type_integer(type)) {
 		gbString expr_str = expr_to_string(e);
@@ -2977,11 +3008,14 @@ void update_expr_type(CheckerContext *c, Ast *e, Type *type, bool final) {
 		return;
 	}
 
-	add_type_and_value(&c->checker->info, e, old->mode, type, old->value);
+	add_type_and_value(c->info, e, old->mode, type, old->value);
 }
 
 void update_expr_value(CheckerContext *c, Ast *e, ExactValue value) {
-	ExprInfo *found = check_get_expr_info(&c->checker->info, e);
+	ExprInfo *found = nullptr;
+	gb_mutex_lock(&c->info->untyped_mutex);
+	found = check_get_expr_info(c->info, e);
+	gb_mutex_unlock(&c->info->untyped_mutex);
 	if (found) {
 		found->value = value;
 	}
@@ -3001,7 +3035,7 @@ void convert_untyped_error(CheckerContext *c, Operand *operand, Type *target_typ
 			}
 		}
 	}
-	error(operand->expr, "Cannot convert '%s' to '%s' from '%s'%s", expr_str, type_str, from_type_str, extra_text);
+	error(operand->expr, "Cannot convert untyped value '%s' to '%s' from '%s'%s", expr_str, type_str, from_type_str, extra_text);
 
 	gb_string_free(from_type_str);
 	gb_string_free(type_str);
@@ -5682,7 +5716,7 @@ ExprKind check_call_expr(CheckerContext *c, Operand *operand, Ast *call, Ast *pr
 			operand->builtin_id = BuiltinProc_DIRECTIVE;
 			operand->expr = proc;
 			operand->type = t_invalid;
-			add_type_and_value(&c->checker->info, proc, operand->mode, operand->type, operand->value);
+			add_type_and_value(c->info, proc, operand->mode, operand->type, operand->value);
 		} else {
 			GB_PANIC("Unhandled #%.*s", LIT(name));
 		}
@@ -5757,7 +5791,7 @@ ExprKind check_call_expr(CheckerContext *c, Operand *operand, Ast *call, Ast *pr
 				GB_ASSERT(ot->kind == Type_Named);
 				Entity *e = ot->Named.type_name;
 				add_entity_use(c, ident, e);
-				add_type_and_value(&c->checker->info, call, Addressing_Type, ot, empty_exact_value);
+				add_type_and_value(c->info, call, Addressing_Type, ot, empty_exact_value);
 			} else {
 				operand->mode = Addressing_Invalid;
 				operand->type = t_invalid;
@@ -6153,8 +6187,8 @@ bool check_range(CheckerContext *c, Ast *node, Operand *x, Operand *y, ExactValu
 		return false;
 	}
 
-	add_type_and_value(&c->checker->info, ie->left,  x->mode, x->type, x->value);
-	add_type_and_value(&c->checker->info, ie->right, y->mode, y->type, y->value);
+	add_type_and_value(c->info, ie->left,  x->mode, x->type, x->value);
+	add_type_and_value(c->info, ie->right, y->mode, y->type, y->value);
 
 	return true;
 }
@@ -6284,7 +6318,7 @@ void check_promote_optional_ok(CheckerContext *c, Operand *x, Type **val_type_,
 		Type *pt = base_type(type_of_expr(expr->CallExpr.proc));
 		if (is_type_proc(pt)) {
 			Type *tuple = pt->Proc.results;
-			add_type_and_value(&c->checker->info, x->expr, x->mode, tuple, x->value);
+			add_type_and_value(c->info, x->expr, x->mode, tuple, x->value);
 
 			if (pt->Proc.result_count >= 2) {
 				if (ok_type_) *ok_type_ = tuple->Tuple.variables[1]->type;
@@ -6297,7 +6331,7 @@ void check_promote_optional_ok(CheckerContext *c, Operand *x, Type **val_type_,
 
 	Type *tuple = make_optional_ok_type(x->type);
 	if (ok_type_) *ok_type_ = tuple->Tuple.variables[1]->type;
-	add_type_and_value(&c->checker->info, x->expr, x->mode, tuple, x->value);
+	add_type_and_value(c->info, x->expr, x->mode, tuple, x->value);
 	x->type = tuple;
 	GB_ASSERT(is_type_tuple(type_of_expr(x->expr)));
 }
@@ -8163,7 +8197,7 @@ ExprKind check_expr_base_internal(CheckerContext *c, Operand *o, Ast *node, Type
 			error(x.expr, "Expected a constant string for the inline asm constraints parameter");
 		}
 
-		Scope *scope = create_scope(c->scope);
+		Scope *scope = create_scope(c->info, c->scope);
 		scope->flags |= ScopeFlag_Proc;
 
 		Type *params = alloc_type_tuple();
@@ -8221,9 +8255,9 @@ ExprKind check_expr_base(CheckerContext *c, Operand *o, Ast *node, Type *type_hi
 		gb_string_free(xs);
 	}
 	if (o->type != nullptr && is_type_untyped(o->type)) {
-		add_untyped(&c->checker->info, node, false, o->mode, o->type, o->value);
+		add_untyped(c->info, node, false, o->mode, o->type, o->value);
 	}
-	add_type_and_value(&c->checker->info, node, o->mode, o->type, o->value);
+	add_type_and_value(c->info, node, o->mode, o->type, o->value);
 	return kind;
 }
 

+ 3 - 3
src/check_stmt.cpp

@@ -587,7 +587,7 @@ bool check_using_stmt_entity(CheckerContext *ctx, AstUsingStmt *us, Ast *expr, b
 				Entity *found = scope_insert(ctx->scope, f);
 				if (found != nullptr) {
 					gbString expr_str = expr_to_string(expr);
-					error(us->token, "Namespace collision while 'using' '%s' of: %.*s", expr_str, LIT(found->token.string));
+					error(us->token, "Namespace collision while 'using' enum '%s' of: %.*s", expr_str, LIT(found->token.string));
 					gb_string_free(expr_str);
 					return false;
 				}
@@ -611,7 +611,7 @@ bool check_using_stmt_entity(CheckerContext *ctx, AstUsingStmt *us, Ast *expr, b
 			if (found != nullptr) {
 				gbString expr_str = expr_to_string(expr);
 				error(us->token,
-				      "Namespace collision while 'using' '%s' of: %.*s\n"
+				      "Namespace collision while 'using' import name '%s' of: %.*s\n"
 				      "\tat %s\n"
 				      "\tat %s",
 				      expr_str, LIT(found->token.string),
@@ -1103,7 +1103,7 @@ void check_switch_stmt(CheckerContext *ctx, Ast *node, u32 mod_flags) {
 					if (y.mode != Addressing_Constant) {
 						continue;
 					}
-
+					update_expr_type(ctx, z.expr, x.type, !is_type_untyped(x.type));
 					add_constant_switch_case(ctx, &seen, y);
 				}
 			}

+ 9 - 6
src/check_type.cpp

@@ -1260,7 +1260,10 @@ ParameterValue handle_parameter_value(CheckerContext *ctx, Type *in_type, Type *
 				param_value.kind = ParameterValue_Constant;
 				param_value.value = o.value;
 			} else {
-				error(o.expr, "Invalid constant parameter");
+				gbString s = expr_to_string(o.expr);
+				error(o.expr, "Invalid constant parameter, got '%s'", s);
+				// error(o.expr, "Invalid constant parameter, got '%s' %d %d", s, o.mode, o.value.kind);
+				gb_string_free(s);
 			}
 		}
 	}
@@ -2044,7 +2047,7 @@ void init_map_entry_type(Type *type) {
 	}
 	*/
 	Ast *dummy_node = alloc_ast_node(nullptr, Ast_Invalid);
-	Scope *s = create_scope(builtin_pkg->scope);
+	Scope *s = create_scope(nullptr, builtin_pkg->scope);
 
 	auto fields = array_make<Entity *>(permanent_allocator(), 0, 4);
 	array_add(&fields, alloc_entity_field(s, make_token_ident(str_lit("hash")),  t_uintptr,       false, cast(i32)fields.count, EntityState_Resolved));
@@ -2078,7 +2081,7 @@ void init_map_internal_types(Type *type) {
 	}
 	*/
 	Ast *dummy_node = alloc_ast_node(nullptr, Ast_Invalid);
-	Scope *s = create_scope(builtin_pkg->scope);
+	Scope *s = create_scope(nullptr, builtin_pkg->scope);
 
 	Type *hashes_type  = alloc_type_slice(t_int);
 	Type *entries_type = alloc_type_dynamic_array(type->Map.entry_type);
@@ -2211,7 +2214,7 @@ Type *make_soa_struct_internal(CheckerContext *ctx, Ast *array_typ_expr, Ast *el
 		soa_struct->Struct.soa_count = 0;
 		soa_struct->Struct.is_polymorphic = true;
 
-		scope = create_scope(ctx->scope);
+		scope = create_scope(ctx->info, ctx->scope);
 		soa_struct->Struct.scope = scope;
 	} else if (is_type_array(elem)) {
 		Type *old_array = base_type(elem);
@@ -2225,7 +2228,7 @@ Type *make_soa_struct_internal(CheckerContext *ctx, Ast *array_typ_expr, Ast *el
 		soa_struct->Struct.soa_elem = elem;
 		soa_struct->Struct.soa_count = count;
 
-		scope = create_scope(ctx->scope);
+		scope = create_scope(ctx->info, ctx->scope);
 		soa_struct->Struct.scope = scope;
 
 		String params_xyzw[4] = {
@@ -2267,7 +2270,7 @@ Type *make_soa_struct_internal(CheckerContext *ctx, Ast *array_typ_expr, Ast *el
 		soa_struct->Struct.soa_elem = elem;
 		soa_struct->Struct.soa_count = count;
 
-		scope = create_scope(old_struct->Struct.scope->parent);
+		scope = create_scope(ctx->info, old_struct->Struct.scope->parent);
 		soa_struct->Struct.scope = scope;
 
 		for_array(i, old_struct->Struct.fields) {

+ 127 - 44
src/checker.cpp

@@ -224,7 +224,7 @@ bool decl_info_has_init(DeclInfo *d) {
 
 
 
-Scope *create_scope(Scope *parent, isize init_elements_capacity=DEFAULT_SCOPE_CAPACITY) {
+Scope *create_scope(CheckerInfo *info, Scope *parent, isize init_elements_capacity=DEFAULT_SCOPE_CAPACITY) {
 	Scope *s = gb_alloc_item(permanent_allocator(), Scope);
 	s->parent = parent;
 	string_map_init(&s->elements, heap_allocator(), init_elements_capacity);
@@ -234,7 +234,9 @@ Scope *create_scope(Scope *parent, isize init_elements_capacity=DEFAULT_SCOPE_CA
 	s->delayed_directives.allocator = heap_allocator();
 
 	if (parent != nullptr && parent != builtin_pkg->scope) {
+		if (info) gb_mutex_lock(&info->scope_mutex);
 		DLIST_APPEND(parent->first_child, parent->last_child, s);
+		if (info) gb_mutex_unlock(&info->scope_mutex);
 	}
 
 	if (parent != nullptr && parent->flags & ScopeFlag_ContextDefined) {
@@ -244,12 +246,12 @@ Scope *create_scope(Scope *parent, isize init_elements_capacity=DEFAULT_SCOPE_CA
 	return s;
 }
 
-Scope *create_scope_from_file(AstFile *f) {
+Scope *create_scope_from_file(CheckerInfo *info, AstFile *f) {
 	GB_ASSERT(f != nullptr);
 	GB_ASSERT(f->pkg != nullptr);
 	GB_ASSERT(f->pkg->scope != nullptr);
 
-	Scope *s = create_scope(f->pkg->scope);
+	Scope *s = create_scope(info, f->pkg->scope);
 
 	array_reserve(&s->delayed_imports, f->imports.count);
 	array_reserve(&s->delayed_directives, f->directive_count);
@@ -269,7 +271,7 @@ Scope *create_scope_from_package(CheckerContext *c, AstPackage *pkg) {
 		decl_count += pkg->files[i]->decls.count;
 	}
 	isize init_elements_capacity = 2*decl_count;
-	Scope *s = create_scope(builtin_pkg->scope, init_elements_capacity);
+	Scope *s = create_scope(c->info, builtin_pkg->scope, init_elements_capacity);
 
 	s->flags |= ScopeFlag_Pkg;
 	s->pkg = pkg;
@@ -329,7 +331,7 @@ void check_open_scope(CheckerContext *c, Ast *node) {
 	GB_ASSERT(node->kind == Ast_Invalid ||
 	          is_ast_stmt(node) ||
 	          is_ast_type(node));
-	Scope *scope = create_scope(c->scope);
+	Scope *scope = create_scope(c->info, c->scope);
 	add_scope(c, node, scope);
 	switch (node->kind) {
 	case Ast_ProcType:
@@ -715,7 +717,7 @@ AstPackage *create_builtin_package(char const *name) {
 	pkg->name = make_string_c(name);
 	pkg->kind = Package_Normal;
 
-	pkg->scope = create_scope(nullptr);
+	pkg->scope = create_scope(nullptr, nullptr);
 	pkg->scope->flags |= ScopeFlag_Pkg | ScopeFlag_Global | ScopeFlag_Builtin;
 	pkg->scope->pkg = pkg;
 	return pkg;
@@ -860,6 +862,7 @@ void init_checker_info(CheckerInfo *i) {
 	gb_mutex_init(&i->identifier_uses_mutex);
 	gb_mutex_init(&i->entity_mutex);
 	gb_mutex_init(&i->foreign_mutex);
+	gb_mutex_init(&i->scope_mutex);
 
 }
 
@@ -887,6 +890,7 @@ void destroy_checker_info(CheckerInfo *i) {
 	gb_mutex_destroy(&i->identifier_uses_mutex);
 	gb_mutex_destroy(&i->entity_mutex);
 	gb_mutex_destroy(&i->foreign_mutex);
+	gb_mutex_destroy(&i->scope_mutex);
 }
 
 CheckerContext make_checker_context(Checker *c) {
@@ -902,6 +906,10 @@ CheckerContext make_checker_context(Checker *c) {
 	ctx.poly_level = 0;
 	return ctx;
 }
+void destroy_checker_context(CheckerContext *ctx) {
+	destroy_checker_type_path(ctx->type_path);
+	destroy_checker_poly_path(ctx->poly_path);
+}
 
 void add_curr_ast_file(CheckerContext *ctx, AstFile *file) {
 	if (file != nullptr) {
@@ -917,30 +925,13 @@ void reset_checker_context(CheckerContext *ctx, AstFile *file) {
 	if (ctx == nullptr) {
 		return;
 	}
-	auto checker = ctx->checker;
-	auto info = ctx->info;
-	auto type_path = ctx->type_path;
-	auto poly_path = ctx->poly_path;
-	array_clear(type_path);
-	array_clear(poly_path);
-
-	gb_zero_item(ctx);
-	ctx->checker = checker;
-	ctx->info = info;
-	ctx->type_path = type_path;
-	ctx->poly_path = poly_path;
-	ctx->scope     = builtin_pkg->scope;
-	ctx->pkg       = builtin_pkg;
-
+	destroy_checker_context(ctx);
+	*ctx = make_checker_context(ctx->checker);
 	add_curr_ast_file(ctx, file);
 }
 
 
 
-void destroy_checker_context(CheckerContext *ctx) {
-	destroy_checker_type_path(ctx->type_path);
-	destroy_checker_poly_path(ctx->poly_path);
-}
 
 bool init_checker(Checker *c, Parser *parser) {
 	c->parser = parser;
@@ -1051,21 +1042,13 @@ Scope *scope_of_node(Ast *node) {
 	return node->scope;
 }
 ExprInfo *check_get_expr_info(CheckerInfo *i, Ast *expr) {
-	gb_mutex_lock(&i->untyped_mutex);
 	ExprInfo *res = nullptr;
 	ExprInfo **found = map_get(&i->untyped, hash_node(expr));
 	if (found) {
 		res = *found;
 	}
-	gb_mutex_unlock(&i->untyped_mutex);
 	return res;
 }
-void check_remove_expr_info(CheckerInfo *i, Ast *expr) {
-	gb_mutex_lock(&i->untyped_mutex);
-	map_remove(&i->untyped, hash_node(expr));
-	gb_mutex_unlock(&i->untyped_mutex);
-}
-
 
 
 isize type_info_index(CheckerInfo *info, Type *type, bool error_on_failure) {
@@ -1527,6 +1510,7 @@ void check_procedure_later(Checker *c, ProcInfo *info) {
 	GB_ASSERT(info->decl != nullptr);
 
 	mpmc_enqueue(&c->procs_to_check_queue, info);
+	gb_semaphore_post(&c->procs_to_check_semaphore, 1);
 }
 
 void check_procedure_later(Checker *c, AstFile *file, Token token, DeclInfo *decl, Type *type, Ast *body, u64 tags) {
@@ -4281,6 +4265,9 @@ void check_proc_info(Checker *c, ProcInfo *pi) {
 	if (pi->type == nullptr) {
 		return;
 	}
+	if (pi->decl->proc_checked) {
+		return;
+	}
 
 	CheckerContext ctx = make_checker_context(c);
 	defer (destroy_checker_context(&ctx));
@@ -4316,11 +4303,16 @@ void check_proc_info(Checker *c, ProcInfo *pi) {
 		ctx.state_flags |= StateFlag_no_bounds_check;
 		ctx.state_flags &= ~StateFlag_bounds_check;
 	}
+	if (pi->body != nullptr && pi->decl->entity != nullptr) {
+		GB_ASSERT((pi->decl->entity->flags & EntityFlag_ProcBodyChecked) == 0);
+	}
 
 	check_proc_body(&ctx, pi->token, pi->decl, pi->type, pi->body);
 	if (pi->body != nullptr && pi->decl->entity != nullptr) {
 		pi->decl->entity->flags |= EntityFlag_ProcBodyChecked;
 	}
+	pi->decl->proc_checked = true;
+
 }
 
 GB_STATIC_ASSERT(sizeof(isize) == sizeof(void *));
@@ -4400,23 +4392,114 @@ void check_test_names(Checker *c) {
 
 }
 
+static bool proc_bodies_is_running;
+
+GB_THREAD_PROC(thread_proc_body) {
+	Checker *c = cast(Checker *)thread->user_data;
+	auto *q = &c->procs_to_check_queue;
+	ProcInfo *pi = nullptr;
+
+	while (proc_bodies_is_running) {
+		gb_semaphore_wait(&c->procs_to_check_semaphore);
+
+		if (mpmc_dequeue(q, &pi)) {
+			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
+				// This is prevent any possible race conditions in evaluation when multithreaded
+				// NOTE(bill): In single threaded mode, this should never happen
+				if (parent->kind == Entity_Procedure && (parent->flags & EntityFlag_ProcBodyChecked) == 0) {
+					mpmc_enqueue(q, pi);
+					continue;
+				}
+			}
+			check_proc_info(c, pi);
+		}
+	}
+
+	gb_semaphore_release(&c->procs_to_check_semaphore);
+
+	return 0;
+}
 
 void check_procedure_bodies(Checker *c) {
 	auto *q = &c->procs_to_check_queue;
 	ProcInfo *pi = nullptr;
 
-	while (mpmc_dequeue(q, &pi)) {
-		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
-			// This is prevent any possible race conditions in evaluation when multithreaded
-			// NOTE(bill): In single threaded mode, this should never happen
-			if (parent->kind == Entity_Procedure && (parent->flags & EntityFlag_ProcBodyChecked) == 0) {
-				mpmc_enqueue(q, pi);
-				continue;
+	isize thread_count = gb_max(build_context.thread_count, 1);
+	isize worker_count = thread_count-1; // NOTE(bill): The main thread will also be used for work
+	if (!build_context.threaded_checker) {
+		worker_count = 0;
+	}
+
+	if (worker_count == 0) {
+		while (mpmc_dequeue(q, &pi)) {
+			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
+				// This is prevent any possible race conditions in evaluation when multithreaded
+				// NOTE(bill): In single threaded mode, this should never happen
+				if (parent->kind == Entity_Procedure && (parent->flags & EntityFlag_ProcBodyChecked) == 0) {
+					mpmc_enqueue(q, pi);
+					continue;
+				}
+			}
+			check_proc_info(c, pi);
+		}
+	} else {
+		proc_bodies_is_running = true;
+
+		gbThread threads[64] = {};
+		for (isize i = 0; i < worker_count; i++) {
+			gb_thread_init(threads+i);
+		}
+
+		for (isize i = 0; i < worker_count; i++) {
+			gb_thread_start(threads+i, thread_proc_body, c);
+		}
+
+		while (q->count.load(std::memory_order_relaxed) > 0) {
+			if (mpmc_dequeue(q, &pi)) {
+				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
+					// This is prevent any possible race conditions in evaluation when multithreaded
+					// NOTE(bill): In single threaded mode, this should never happen
+					if (parent->kind == Entity_Procedure && (parent->flags & EntityFlag_ProcBodyChecked) == 0) {
+						mpmc_enqueue(q, pi);
+
+						gb_yield();
+						continue;
+					}
+				}
+				check_proc_info(c, pi);
+			}
+
+			gb_yield();
+		}
+
+		proc_bodies_is_running = false;
+		gb_semaphore_post(&c->procs_to_check_semaphore, cast(i32)worker_count);
+
+		gb_yield();
+
+		for (isize i = 0; i < worker_count; i++) {
+			gb_thread_destroy(threads+i);
+		}
+
+		while (mpmc_dequeue(q, &pi)) {
+			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
+				// This is prevent any possible race conditions in evaluation when multithreaded
+				// NOTE(bill): In single threaded mode, this should never happen
+				if (parent->kind == Entity_Procedure && (parent->flags & EntityFlag_ProcBodyChecked) == 0) {
+					mpmc_enqueue(q, pi);
+					continue;
+				}
 			}
+			check_proc_info(c, pi);
 		}
-		check_proc_info(c, pi);
 	}
 }
 
@@ -4457,7 +4540,7 @@ void check_parsed_files(Checker *c) {
 			AstFile *f = pkg->files[j];
 			string_map_set(&c->info.files, f->fullpath, f);
 
-			create_scope_from_file(f);
+			create_scope_from_file(nullptr, f);
 			reset_checker_context(ctx, f);
 			check_collect_entities(ctx, f->decls);
 		}

+ 2 - 1
src/checker.hpp

@@ -144,6 +144,7 @@ struct DeclInfo {
 	Type *        gen_proc_type; // Precalculated
 	bool          is_using;
 	bool          where_clauses_evaluated;
+	bool          proc_checked;
 
 	CommentGroup *comment;
 	CommentGroup *docs;
@@ -289,6 +290,7 @@ struct CheckerInfo {
 	gbMutex identifier_uses_mutex;
 	gbMutex entity_mutex;
 	gbMutex foreign_mutex;
+	gbMutex scope_mutex;
 
 	Map<ExprInfo *>       untyped; // Key: Ast * | Expression -> ExprInfo *
 	                               // NOTE(bill): This needs to be a map and not on the Ast
@@ -398,7 +400,6 @@ Entity *scope_insert (Scope *s, Entity *entity);
 
 
 ExprInfo *check_get_expr_info     (CheckerInfo *i, Ast *expr);
-void      check_remove_expr_info  (CheckerInfo *i, Ast *expr);
 void      add_untyped             (CheckerInfo *i, Ast *expression, bool lhs, AddressingMode mode, Type *basic_type, ExactValue value);
 void      add_type_and_value      (CheckerInfo *i, Ast *expression, AddressingMode mode, Type *type, ExactValue value);
 void      add_entity_use          (CheckerContext *c, Ast *identifier, Entity *entity);

+ 12 - 12
src/exact_value.cpp

@@ -27,18 +27,18 @@ Quaternion256 quaternion256_inverse(Quaternion256 x) {
 
 
 enum ExactValueKind {
-	ExactValue_Invalid,
-
-	ExactValue_Bool,
-	ExactValue_String,
-	ExactValue_Integer,
-	ExactValue_Float,
-	ExactValue_Complex,
-	ExactValue_Quaternion,
-	ExactValue_Pointer,
-	ExactValue_Compound,  // TODO(bill): Is this good enough?
-	ExactValue_Procedure, // TODO(bill): Is this good enough?
-	ExactValue_Typeid,
+	ExactValue_Invalid    = 0,
+
+	ExactValue_Bool       = 1,
+	ExactValue_String     = 2,
+	ExactValue_Integer    = 3,
+	ExactValue_Float      = 4,
+	ExactValue_Complex    = 5,
+	ExactValue_Quaternion = 6,
+	ExactValue_Pointer    = 7,
+	ExactValue_Compound   = 8,  // TODO(bill): Is this good enough?
+	ExactValue_Procedure  = 9, // TODO(bill): Is this good enough?
+	ExactValue_Typeid     = 10,
 
 	ExactValue_Count,
 };

+ 12 - 0
src/main.cpp

@@ -600,6 +600,7 @@ enum BuildFlagKind {
 	BuildFlag_NoEntryPoint,
 	BuildFlag_UseLLD,
 	BuildFlag_UseSeparateModules,
+	BuildFlag_ThreadedChecker,
 	BuildFlag_Vet,
 	BuildFlag_VetExtra,
 	BuildFlag_UseLLVMApi,
@@ -722,6 +723,7 @@ bool parse_build_flags(Array<String> args) {
 	add_flag(&build_flags, BuildFlag_NoEntryPoint,      str_lit("no-entry-point"),      BuildFlagParam_None, Command__does_check &~ Command_test);
 	add_flag(&build_flags, BuildFlag_UseLLD,            str_lit("lld"),                 BuildFlagParam_None, Command__does_build);
 	add_flag(&build_flags, BuildFlag_UseSeparateModules,str_lit("use-separate-modules"),BuildFlagParam_None, Command__does_build);
+	add_flag(&build_flags, BuildFlag_ThreadedChecker,   str_lit("threaded-checker"),    BuildFlagParam_None, Command__does_check);
 	add_flag(&build_flags, BuildFlag_Vet,               str_lit("vet"),                 BuildFlagParam_None, Command__does_check);
 	add_flag(&build_flags, BuildFlag_VetExtra,          str_lit("vet-extra"),           BuildFlagParam_None, Command__does_check);
 	add_flag(&build_flags, BuildFlag_UseLLVMApi,        str_lit("llvm-api"),            BuildFlagParam_None, Command__does_build);
@@ -1206,6 +1208,10 @@ bool parse_build_flags(Array<String> args) {
 							build_context.use_separate_modules = true;
 							break;
 
+						case BuildFlag_ThreadedChecker:
+							build_context.threaded_checker = true;
+							break;
+
 						case BuildFlag_Vet:
 							build_context.vet = true;
 							break;
@@ -1746,6 +1752,11 @@ void print_show_help(String const arg0, String const &command) {
 	}
 
 	if (check) {
+		print_usage_line(1, "-threaded-checker");
+		print_usage_line(1, "[EXPERIMENTAL]");
+		print_usage_line(2, "Multithread the semantic checker stage");
+		print_usage_line(0, "");
+
 		print_usage_line(1, "-vet");
 		print_usage_line(2, "Do extra checks on the code");
 		print_usage_line(2, "Extra checks include:");
@@ -1960,6 +1971,7 @@ int main(int arg_count, char const **arg_ptr) {
 	init_global_error_collector();
 	init_keyword_hash_table();
 	global_big_int_init();
+	init_type_mutex();
 
 	if (!check_env()) {
 		return 1;

+ 15 - 1
src/types.cpp

@@ -661,6 +661,8 @@ gb_global Type *t_map_header                     = nullptr;
 gb_global Type *t_equal_proc  = nullptr;
 gb_global Type *t_hasher_proc = nullptr;
 
+gb_global gbMutex g_type_mutex;
+
 
 i64      type_size_of               (Type *t);
 i64      type_align_of              (Type *t);
@@ -674,6 +676,10 @@ bool is_type_pointer(Type *t);
 bool is_type_slice(Type *t);
 bool is_type_integer(Type *t);
 
+void init_type_mutex(void) {
+	gb_mutex_init(&g_type_mutex);
+}
+
 bool type_ptr_set_exists(PtrSet<Type *> *s, Type *t) {
 	if (ptr_set_exists(s, t)) {
 		return true;
@@ -2727,7 +2733,7 @@ void type_path_print_illegal_cycle(TypePath *tp, isize start_index) {
 	GB_ASSERT(start_index < tp->path.count);
 	Entity *e = tp->path[start_index];
 	GB_ASSERT(e != nullptr);
-	error(e->token, "Illegal declaration cycle of `%.*s`", LIT(e->token.string));
+	error(e->token, "Illegal type declaration cycle of `%.*s`", LIT(e->token.string));
 	// NOTE(bill): Print cycle, if it's deep enough
 	for (isize j = start_index; j < tp->path.count; j++) {
 		Entity *e = tp->path[j];
@@ -2844,6 +2850,8 @@ i64 type_align_of_internal(Type *t, TypePath *path) {
 	if (t->failure) {
 		return FAILURE_ALIGNMENT;
 	}
+	gb_mutex_lock(&g_type_mutex);
+	defer (gb_mutex_unlock(&g_type_mutex));
 
 	t = base_type(t);
 
@@ -3038,6 +3046,9 @@ Array<i64> type_set_offsets_of(Array<Entity *> const &fields, bool is_packed, bo
 }
 
 bool type_set_offsets(Type *t) {
+	gb_mutex_lock(&g_type_mutex);
+	defer (gb_mutex_unlock(&g_type_mutex));
+
 	t = base_type(t);
 	if (t->kind == Type_Struct) {
 		if (!t->Struct.are_offsets_set) {
@@ -3066,6 +3077,9 @@ i64 type_size_of_internal(Type *t, TypePath *path) {
 	if (t->failure) {
 		return FAILURE_SIZE;
 	}
+	gb_mutex_lock(&g_type_mutex);
+	defer (gb_mutex_unlock(&g_type_mutex));
+
 
 	switch (t->kind) {
 	case Type_Named: {