Make global checking state thread-safe: read-only global scopes, per-group untyped and #soa, deferred link-name checks, fresh contexts for on-demand globals

This commit is contained in:
gingerBill committed 2026-10-01 21:07:41 +01:00
1 parent 94bb955871
commit 73c3a96de1
10 files changed
+396 -201

No files matched your search

+127 -77
View File
@@ -170,7 +170,7 @@ gb_internal void override_entity_in_scope(Entity *original_entity, Entity *new_e
// has been "evaluated" and the variant data can be copied across
rw_mutex_lock(&found_scope->mutex);
scope_map_insert(&found_scope->elements, original_intern, hash, new_entity);
scope_map_replace(&found_scope->elements, original_intern, hash, new_entity);
rw_mutex_unlock(&found_scope->mutex);
original_entity->flags |= EntityFlag_Overridden;
@@ -1257,41 +1257,95 @@ gb_internal void check_target_feature_attributes(AttributeContext &ac, Entity *e
}
}
gb_internal void check_foreign_procedure(CheckerContext *ctx, Entity *e, DeclInfo *d) {
GB_ASSERT(e != nullptr);
GB_ASSERT(e->kind == Entity_Procedure);
String name = e->Procedure.link_name;
gb_internal void add_link_name_use(CheckerInfo *info, String name, Entity *e, DeclInfo *d, LinkNameUseKind kind) {
mutex_lock(&info->foreign_mutex);
array_add(&info->link_names, LinkNameUse{name, e, d, kind});
mutex_unlock(&info->foreign_mutex);
}
mutex_lock(&ctx->info->foreign_mutex);
gb_internal GB_COMPARE_PROC(link_name_use_cmp) {
LinkNameUse const *x = cast(LinkNameUse const *)a;
LinkNameUse const *y = cast(LinkNameUse const *)b;
i32 cmp = string_compare(x->name, y->name);
if (cmp != 0) {
return cmp;
}
if (x->entity != y->entity) {
return entity_source_order_cmp(x->entity, y->entity);
}
return i32_cmp(x->kind, y->kind);
}
gb_internal void check_link_name_uses(Checker *c) {
auto &uses = c->info.link_names;
array_sort(uses, link_name_use_cmp);
LinkNameUse *first = nullptr;
for (isize i = 0; i < uses.count; i++) {
LinkNameUse *u = &uses[i];
if (i > 0 && uses[i-1].name != u->name) {
first = nullptr;
} else if (i > 0 && uses[i-1].entity == u->entity) {
continue;
}
String name = u->name;
if (name == "main" && u->kind != LinkNameUse_Variable) {
if (u->kind == LinkNameUse_ForeignProcedure || u->entity->pkg->kind != Package_Runtime) {
error(u->decl->proc_lit, "The link name 'main' is reserved for internal use");
}
continue;
}
if (first == nullptr) {
first = u;
continue;
}
Entity *e = u->entity;
Entity *f = first->entity;
auto *fp = &ctx->info->foreigns;
StringHashKey key = string_hash_string(name);
Entity **found = string_map_get(fp, key);
if (found && e != *found) {
Entity *f = *found;
TokenPos pos = f->token.pos;
Type *this_type = base_type(e->type);
Type *other_type = base_type(f->type);
if (is_type_proc(this_type) && is_type_proc(other_type)) {
if (!are_signatures_similar_enough(this_type, other_type)) {
error(d->proc_lit,
"Redeclaration of foreign procedure '%.*s' with different type signatures\n"
switch (u->kind) {
case LinkNameUse_ForeignProcedure:
if (is_type_proc(this_type) && is_type_proc(other_type)) {
if (!are_signatures_similar_enough(this_type, other_type)) {
error(u->decl->proc_lit,
"Redeclaration of foreign procedure '%.*s' with different type signatures\n"
"\tat %s",
LIT(name), token_pos_to_string(pos));
}
} else if (!signature_parameter_similar_enough(this_type, other_type)) {
error(u->decl->proc_lit,
"Foreign entity '%.*s' previously declared elsewhere with a different type\n"
"\tat %s",
LIT(name), token_pos_to_string(pos));
}
} else if (!signature_parameter_similar_enough(this_type, other_type)) {
error(d->proc_lit,
"Foreign entity '%.*s' previously declared elsewhere with a different type\n"
"\tat %s",
break;
case LinkNameUse_Procedure:
// TODO(bill): Better error message?
error(u->decl->proc_lit,
"Non-unique linking name for procedure '%.*s'\n"
"\tother at %s",
LIT(name), token_pos_to_string(pos));
break;
case LinkNameUse_Variable:
if (e->type == nullptr || f->type == nullptr || !signature_parameter_similar_enough(this_type, other_type)) {
error(e->token,
"Foreign entity '%.*s' previously declared elsewhere with a different type\n"
"\tat %s",
LIT(name), token_pos_to_string(pos));
}
break;
}
} else if (name == "main") {
error(d->proc_lit, "The link name 'main' is reserved for internal use");
} else {
string_map_set(fp, key, e);
}
}
mutex_unlock(&ctx->info->foreign_mutex);
gb_internal void check_foreign_procedure(CheckerContext *ctx, Entity *e, DeclInfo *d) {
GB_ASSERT(e != nullptr);
GB_ASSERT(e->kind == Entity_Procedure);
add_link_name_use(ctx->info, e->Procedure.link_name, e, d, LinkNameUse_ForeignProcedure);
}
gb_internal void check_proc_decl(CheckerContext *ctx, Entity *e, DeclInfo *d) {
@@ -1562,14 +1616,17 @@ gb_internal void check_proc_decl(CheckerContext *ctx, Entity *e, DeclInfo *d) {
}
if (e->pkg->kind == Package_Init) {
if (ctx->info->entry_point != nullptr) {
mutex_lock(&ctx->info->entry_point_mutex);
Entity *prev_entry_point = ctx->info->entry_point;
if (prev_entry_point == nullptr) {
ctx->info->entry_point = e;
}
mutex_unlock(&ctx->info->entry_point_mutex);
if (prev_entry_point != nullptr) {
begin_error_block();
error(e->token, "Redeclaration of the entry point procedure 'main'");
error_line("\tSuggestion: Is this a single-file package? If so, try compiling using the `-file` flag.\n");
end_error_block();
} else {
ctx->info->entry_point = e;
}
}
}
@@ -1660,28 +1717,7 @@ gb_internal void check_proc_decl(CheckerContext *ctx, Entity *e, DeclInfo *d) {
name = e->Procedure.link_name;
}
if (e->Procedure.link_name.len > 0 || is_export) {
mutex_lock(&ctx->info->foreign_mutex);
auto *fp = &ctx->info->foreigns;
StringHashKey key = string_hash_string(name);
Entity **found = string_map_get(fp, key);
if (found) {
Entity *f = *found;
TokenPos pos = f->token.pos;
// TODO(bill): Better error message?
error(d->proc_lit,
"Non unique linking name for procedure '%.*s'\n"
"\tother at %s",
LIT(name), token_pos_to_string(pos));
} else if (name == "main") {
if (d->entity.load()->pkg->kind != Package_Runtime) {
error(d->proc_lit, "The link name 'main' is reserved for internal use");
}
} else {
string_map_set(fp, key, e);
}
mutex_unlock(&ctx->info->foreign_mutex);
add_link_name_use(ctx->info, name, e, d, LinkNameUse_Procedure);
}
}
@@ -1777,24 +1813,7 @@ gb_internal void check_global_variable_decl(CheckerContext *ctx, Entity *e, Ast
name = e->Variable.link_name;
}
auto *fp = &ctx->info->foreigns;
StringHashKey key = string_hash_string(name);
Entity **found = string_map_get(fp, key);
if (found) {
Entity *f = *found;
TokenPos pos = f->token.pos;
Type *this_type = base_type(e->type);
Type *other_type = base_type(f->type);
bool type_is_null = (e->type == nullptr || f->type == nullptr);
if (type_is_null || !signature_parameter_similar_enough(this_type, other_type)) {
error(e->token,
"Foreign entity '%.*s' previously declared elsewhere with a different type\n"
"\tat %s",
LIT(name), token_pos_to_string(pos));
}
} else {
string_map_set(fp, key, e);
}
add_link_name_use(ctx->info, name, e, decl, LinkNameUse_Variable);
}
if (e->Variable.link_name.len > 0) {
@@ -1910,9 +1929,15 @@ gb_internal void check_proc_group_decl(CheckerContext *ctx, Entity *pg_entity, D
ptr_set_destroy(&entity_set);
// NOTE(bill, 2026-10-01): an invalid overload is dropped from this group only, as others may be using that procedure
auto invalid = array_make<bool>(temporary_allocator(), pge->entities.count);
for (isize j = 0; j < pge->entities.count; j++) {
invalid[j] = false;
}
for (isize j = 0; j < pge->entities.count; j++) {
Entity *p = pge->entities[j];
if (p->type == t_invalid) {
if (p->type == t_invalid || invalid[j]) {
// NOTE(bill): This invalid overload has already been handled
continue;
}
@@ -1931,7 +1956,7 @@ gb_internal void check_proc_group_decl(CheckerContext *ctx, Entity *pg_entity, D
TokenPos pos = q->token.pos;
if (q->type == nullptr || q->type == t_invalid) {
if (q->type == nullptr || q->type == t_invalid || invalid[k]) {
continue;
}
@@ -1987,10 +2012,17 @@ gb_internal void check_proc_group_decl(CheckerContext *ctx, Entity *pg_entity, D
if (is_invalid) {
error_line("\tprevious procedure at %s\n", token_pos_to_string(pos));
q->type = t_invalid;
invalid[k] = true;
}
}
}
isize valid_count = 0;
for (isize j = 0; j < pge->entities.count; j++) {
if (!invalid[j]) {
pge->entities[valid_count++] = pge->entities[j];
}
}
pge->entities.count = valid_count;
AttributeContext ac = {};
check_decl_attributes(ctx, d->attributes, proc_group_attribute, &ac);
@@ -2153,6 +2185,7 @@ gb_internal void check_entity_decl(CheckerContext *ctx, Entity *e, DeclInfo *d,
mutex_unlock(&ctx->info->lazy_mutex);
return;
}
lazy_mutex_depth += 1;
}
GlobalEntityTimingFrame timing_frame = global_entity_timing_begin(e);
@@ -2174,18 +2207,34 @@ gb_internal void check_entity_decl(CheckerContext *ctx, Entity *e, DeclInfo *d,
}
CheckerContext c = *ctx;
if (d->scope->flags & ScopeFlag_File) {
// NOTE(bill): a global is checked in a context of its own file, never in that of whatever needed it first,
// which may be in another file or package, or a procedure body.
// Only the cycle detection carries over.
CheckerTypePath *type_path = c.type_path;
UntypedExprInfoMap *untyped = c.untyped;
gb_zero_size(&c.pkg, gb_size_of(CheckerContext) - gb_offset_of(CheckerContext, pkg));
add_curr_ast_file(&c, d->scope->file);
c.type_path = type_path;
c.untyped = untyped;
}
c.scope = d->scope;
c.decl = d;
c.type_level = 0;
c.curr_proc_calling_convention = ProcCC_Contextless;
// NOTE: a file scope's is set from its own file and shared by every thread, see `create_scope_from_file`
bool set_context = (c.scope->flags & ScopeFlag_File) == 0;
auto prev_flags = c.scope->flags;
defer (c.scope->flags = prev_flags);
if (check_feature_flags(ctx, d->decl_node) & OptInFeatureFlag_GlobalContext) {
c.scope->flags |= ScopeFlag_ContextDefined;
} else {
c.scope->flags &= ~ScopeFlag_ContextDefined;
defer (if (set_context) {
c.scope->flags = prev_flags;
});
if (set_context) {
if (check_feature_flags(ctx, d->decl_node) & OptInFeatureFlag_GlobalContext) {
c.scope->flags |= ScopeFlag_ContextDefined;
} else {
c.scope->flags &= ~ScopeFlag_ContextDefined;
}
}
global_group_check_edge(ctx, e);
@@ -2242,6 +2291,7 @@ end:;
// NOTE(bill): Add it to the list of checked entities
if (is_lazy) {
array_add(&ctx->info->entities, e);
lazy_mutex_depth -= 1;
mutex_unlock(&ctx->info->lazy_mutex);
}
}