diff --git a/src/check_decl.cpp b/src/check_decl.cpp index 3343465ae..4a40ba7a4 100644 --- a/src/check_decl.cpp +++ b/src/check_decl.cpp @@ -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(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); } } diff --git a/src/check_expr.cpp b/src/check_expr.cpp index 2985c8c67..4f0feab8b 100644 --- a/src/check_expr.cpp +++ b/src/check_expr.cpp @@ -178,10 +178,10 @@ gb_internal void populate_check_did_you_mean_objc_entity(StringSet *set, Entity if (e->kind != Entity_TypeName) { return; } - if (e->TypeName.objc_metadata == nullptr) { + TypeNameObjCMetadata *objc_metadata = entity_objc_metadata(e); + if (objc_metadata == nullptr) { return; } - TypeNameObjCMetadata *objc_metadata = e->TypeName.objc_metadata; Type *t = base_type(e->type); GB_ASSERT(t->kind == Type_Struct); @@ -210,8 +210,8 @@ gb_internal void check_did_you_mean_objc_entity(String const &name, Entity *e, b ERROR_BLOCK(); GB_ASSERT(e->kind == Entity_TypeName); - GB_ASSERT(e->TypeName.objc_metadata != nullptr); - auto *objc_metadata = e->TypeName.objc_metadata; + auto *objc_metadata = entity_objc_metadata(e); + GB_ASSERT(objc_metadata != nullptr); MUTEX_GUARD(objc_metadata->mutex); StringSet set = {}; @@ -515,7 +515,8 @@ gb_internal bool find_or_generate_polymorphic_procedure(CheckerContext *old_c, E // NOTE(bill): This is slightly memory leaking if the type already exists // Maybe it's better to check with the previous types first? Type *final_proc_type = alloc_type_proc(scope, nullptr, 0, nullptr, 0, false, pt->calling_convention); - bool success = check_procedure_type(&nctx, final_proc_type, pt->node, &operands); + // NOTE: a clone, as other threads may be instantiating the same procedure, from the same AST + bool success = check_procedure_type(&nctx, final_proc_type, clone_ast(pt->node), &operands); if (!success) { return false; @@ -655,14 +656,7 @@ gb_internal bool find_or_generate_polymorphic_procedure(CheckerContext *old_c, E d->entity.store(entity); - AstFile *file = nullptr; - { - Scope *s = entity->scope; - while (s != nullptr && s->file == nullptr) { - file = s->file; - s = s->parent; - } - } + AstFile *file = base_entity->file; array_add(&gen_procs->procs, entity); rw_mutex_unlock(&gen_procs->mutex); // @local-mutex @@ -6116,7 +6110,7 @@ gb_internal Entity *check_selector(CheckerContext *c, Operand *operand, Ast *nod if (operand->type->kind == Type_Named && operand->type->Named.type_name && operand->type->Named.type_name->kind == Entity_TypeName && - operand->type->Named.type_name->TypeName.objc_metadata) { + entity_objc_metadata(operand->type->Named.type_name)) { check_did_you_mean_objc_entity(name, operand->type->Named.type_name, operand->mode == Addressing_Type); } else if (bt->kind == Type_Struct) { check_did_you_mean_type(name, bt->Struct.fields); @@ -8816,6 +8810,11 @@ gb_internal CallArgumentError check_polymorphic_record_type(CheckerContext *c, O CheckerContext ctx = *c; // NOTE(bill): We need to make sure the lookup scope for the record is the same as where it was created ctx.scope = polymorphic_record_parent_scope(original_type); + + if (original_type->Named.type_name && original_type->Named.type_name->file) { + ctx.file = original_type->Named.type_name->file; + ctx.pkg = ctx.file->pkg; + } GB_ASSERT(ctx.scope != nullptr); Type *bt = base_type(original_type); @@ -8830,7 +8829,9 @@ gb_internal CallArgumentError check_polymorphic_record_type(CheckerContext *c, O set_base_type(named_type, struct_type); check_open_scope(&ctx, node); + begin_filling_record(struct_type); check_struct_type(&ctx, struct_type, node, &ordered_operands, named_type, original_type, found_gen_types); + end_filling_record(struct_type); // check_struct_type released found_gen_types->mutex after publishing the instantiation. gen_types_locked = false; check_close_scope(&ctx); diff --git a/src/check_stmt.cpp b/src/check_stmt.cpp index ffc6f4cc0..dfb046da3 100644 --- a/src/check_stmt.cpp +++ b/src/check_stmt.cpp @@ -2355,23 +2355,7 @@ gb_internal void check_value_decl_stmt(CheckerContext *ctx, Ast *node, u32 mod_f } init_entity_foreign_library(ctx, e); - auto *fp = &ctx->checker->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); - if (!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, ctx->decl, LinkNameUse_Variable); } else if (e->flags & EntityFlag_Static) { if (vd->values.count > 0) { if (entity_count != vd->values.count) { diff --git a/src/check_type.cpp b/src/check_type.cpp index 61f5a7b3a..6c39472db 100644 --- a/src/check_type.cpp +++ b/src/check_type.cpp @@ -349,7 +349,9 @@ gb_internal void add_polymorphic_record_entity(CheckerContext *ctx, Ast *node, T e = alloc_entity_type_name(s, token, named_type); e->state = EntityState_Resolved; - e->file = ctx->file; + + // NOTE(bille): the generic's file as its token is not that of whichever instantiated it first + e->file = original_type->Named.type_name && original_type->Named.type_name->file ? original_type->Named.type_name->file : ctx->file; e->pkg = pkg; e->TypeName.original_type_for_parapoly = original_type; add_entity_use(ctx, node, e); @@ -770,40 +772,15 @@ gb_internal void check_struct_type(CheckerContext *ctx, Type *struct_type, Ast * } } - if (!struct_type->Struct.is_polymorphic) { - if (st->where_clauses.count > 0 && st->polymorphic_params == nullptr) { - error(st->where_clauses[0], "'where' clauses can only be used on structures with polymorphic parameters"); - } else { - bool where_clause_ok = evaluate_where_clauses(ctx, node, ctx->scope, &st->where_clauses, true); - gb_unused(where_clause_ok); - } - check_struct_fields(ctx, node, &struct_type->Struct.fields, &struct_type->Struct.tags, st->fields, min_field_count, struct_type, context); - - if (st->is_simple) { - bool success = true; - for (Entity *f : struct_type->Struct.fields) { - if (!is_type_nearly_simple_compare(f->type)) { - gbString s = type_to_string(f->type); - error(f->token, "'struct #simple' requires all fields to be at least 'nearly simple compare', got %s", s); - gb_string_free(s); - } - } - if (success) { - struct_type->Struct.is_simple = true; - } - } - - wait_signal_set(&struct_type->Struct.fields_wait_signal); - } #define ST_ALIGN(_name) if (st->_name != nullptr) { \ if (st->is_packed) { \ error(st->_name, "'#%s' cannot be applied with '#packed'", #_name); \ - return; \ - } \ - i64 align = 1; \ - if (check_custom_align(ctx, st->_name, &align, #_name)) { \ - struct_type->Struct.custom_##_name = align; \ + } else { \ + i64 align = 1; \ + if (check_custom_align(ctx, st->_name, &align, #_name)) { \ + struct_type->Struct.custom_##_name = align; \ + } \ } \ } @@ -836,6 +813,33 @@ gb_internal void check_struct_type(CheckerContext *ctx, Type *struct_type, Ast * } #undef ST_ALIGN + + if (!struct_type->Struct.is_polymorphic) { + if (st->where_clauses.count > 0 && st->polymorphic_params == nullptr) { + error(st->where_clauses[0], "'where' clauses can only be used on structures with polymorphic parameters"); + } else { + bool where_clause_ok = evaluate_where_clauses(ctx, node, ctx->scope, &st->where_clauses, true); + gb_unused(where_clause_ok); + } + check_struct_fields(ctx, node, &struct_type->Struct.fields, &struct_type->Struct.tags, st->fields, min_field_count, struct_type, context); + + if (st->is_simple) { + bool success = true; + for (Entity *f : struct_type->Struct.fields) { + if (!is_type_nearly_simple_compare(f->type)) { + gbString s = type_to_string(f->type); + error(f->token, "'struct #simple' requires all fields to be at least 'nearly simple compare', got %s", s); + gb_string_free(s); + } + } + if (success) { + struct_type->Struct.is_simple = true; + } + } + + wait_signal_set(&struct_type->Struct.fields_wait_signal); + } + } gb_internal void check_union_type(CheckerContext *ctx, Type *union_type, Ast *node, Array *poly_operands, Type *named_type, Type *original_type_for_poly, GenTypesData *poly_gen_types_to_unlock) { GB_ASSERT(is_type_union(union_type)); @@ -4622,6 +4626,9 @@ gb_internal Type *make_soa_struct_internal(CheckerContext *ctx, Ast *array_typ_e if (is_complete) { add_type_info_type(ctx, soa_struct); wait_signal_set(&soa_struct->Struct.fields_wait_signal); + } else if (global_group_soa_types != nullptr) { + // NOTE: no task waits on the element type, which could hold every thread of the pool + array_add(global_group_soa_types, soa_struct); } else { SoaTypeWorkerData *wd = permanent_alloc_item(); wd->ctx = *ctx; diff --git a/src/checker.cpp b/src/checker.cpp index 6bc02b9f6..6b7f10159 100644 --- a/src/checker.cpp +++ b/src/checker.cpp @@ -246,10 +246,10 @@ gb_internal Scope *create_scope(CheckerInfo *info, Scope *parent) { s->parent = parent; if (parent != nullptr && parent != builtin_pkg->scope) { - Scope *prev_head_child = parent->head_child.exchange(s, std::memory_order_acq_rel); - if (prev_head_child) { - s->next.store(prev_head_child, std::memory_order_release); - } + Scope *prev_head_child = parent->head_child.load(std::memory_order_acquire); + do { + s->next.store(prev_head_child, std::memory_order_relaxed); + } while (!parent->head_child.compare_exchange_weak(prev_head_child, s, std::memory_order_acq_rel, std::memory_order_acquire)); } if (parent != nullptr && parent->flags & ScopeFlag_ContextDefined) { @@ -272,6 +272,13 @@ gb_internal Scope *create_scope_from_file(CheckerInfo *info, AstFile *f) { s->file = f; f->scope = s; + bool global_context = f->feature_flags_set && (f->feature_flags & OptInFeatureFlag_GlobalContext) != 0; + if (global_context) { + s->flags |= ScopeFlag_ContextDefined; + } else { + s->flags &= ~ScopeFlag_ContextDefined; + } + return s; } @@ -425,10 +432,7 @@ gb_internal Entity *scope_lookup_current(Scope *s, InternedString name, u32 hash } -gb_global std::atomic in_single_threaded_checker_stage; - gb_internal void scope_lookup_parent(Scope *scope, InternedString name, Scope **scope_, Entity **entity_, u32 hash) { - bool is_single_threaded = in_single_threaded_checker_stage.load(std::memory_order_relaxed); if (scope != nullptr) { bool gone_thru_proc = false; bool gone_thru_package = false; @@ -437,9 +441,13 @@ gb_internal void scope_lookup_parent(Scope *scope, InternedString name, Scope ** } for (Scope *s = scope; s != nullptr; s = s->parent) { Entity *found = nullptr; - if (!is_single_threaded) rw_mutex_shared_lock(&s->mutex); - found = scope_map_get(&s->elements, name, hash); - if (!is_single_threaded) rw_mutex_shared_unlock(&s->mutex); + if (s->flags & ScopeFlag_ReadOnly) { + found = scope_map_get(&s->elements, name, hash); + } else { + rw_mutex_shared_lock(&s->mutex); + found = scope_map_get(&s->elements, name, hash); + rw_mutex_shared_unlock(&s->mutex); + } if (global_when_trial != nullptr) { found = global_when_trial_lookup(s, name, hash, found); } @@ -490,6 +498,7 @@ gb_internal Entity *scope_insert_with_name_no_mutex(Scope *s, InternedString nam if (name.value == 0) { return nullptr; } + GB_ASSERT_MSG((s->flags & ScopeFlag_ReadOnly) == 0, "%.*s", LIT(entity->token.string)); Entity *found = nullptr; Entity *result = nullptr; @@ -526,6 +535,7 @@ gb_internal Entity *scope_insert_with_name(Scope *s, InternedString name, u32 ha if (name.value == 0) { return nullptr; } + GB_ASSERT_MSG((s->flags & ScopeFlag_ReadOnly) == 0, "%.*s", LIT(entity->token.string)); Entity *found = nullptr; Entity *result = nullptr; @@ -568,11 +578,7 @@ gb_internal Entity *scope_insert(Scope *s, Entity *entity) { auto name = entity_interned_name(entity); u32 hash = entity->interned_name_hash.load(std::memory_order_relaxed); GB_ASSERT(hash != 0); - if (in_single_threaded_checker_stage.load(std::memory_order_relaxed)) { - return scope_insert_with_name_no_mutex(s, name, hash, entity); - } else { - return scope_insert_with_name(s, name, hash, entity); - } + return scope_insert_with_name(s, name, hash, entity); } gb_internal Entity *scope_insert_no_mutex(Scope *s, Entity *entity) { @@ -918,13 +924,9 @@ gb_internal void check_scope_usage(Checker *c, Scope *scope, u64 vet_flags) { gb_internal void add_dependency(CheckerInfo *info, DeclInfo *d, Entity *e) { - if (in_single_threaded_checker_stage.load(std::memory_order_relaxed)) { - ptr_set_add(&d->deps, e); - } else { - rw_mutex_lock(&d->deps_mutex); - ptr_set_add(&d->deps, e); - rw_mutex_unlock(&d->deps_mutex); - } + rw_mutex_lock(&d->deps_mutex); + ptr_set_add(&d->deps, e); + rw_mutex_unlock(&d->deps_mutex); } gb_internal void add_type_info_dependency(CheckerInfo *info, DeclInfo *d, Type *type) { if (d == nullptr || type == nullptr) { @@ -1649,7 +1651,7 @@ gb_internal void init_checker_info(CheckerInfo *i) { array_init(&i->definitions, a); array_init(&i->entities, a); map_init(&i->global_untyped); - string_map_init(&i->foreigns); + array_init(&i->link_names, heap_allocator()); type_set_init(&i->min_dep_type_info_set); map_init(&i->min_dep_type_info_index_map); @@ -1691,7 +1693,7 @@ gb_internal void destroy_checker_info(CheckerInfo *i) { array_free(&i->definitions); array_free(&i->entities); map_destroy(&i->global_untyped); - string_map_destroy(&i->foreigns); + array_free(&i->link_names); type_set_destroy(&i->min_dep_type_info_set); map_destroy(&i->min_dep_type_info_index_map); @@ -1917,13 +1919,12 @@ gb_internal ExprInfo *check_get_expr_info(CheckerContext *c, Ast *expr) { } return nullptr; } else { + // NOTE: read under the lock, as another thread's insert may move the entries rw_mutex_shared_lock(&c->info->global_untyped_mutex); ExprInfo **found = map_get(&c->info->global_untyped, expr); + ExprInfo *info = found ? *found : nullptr; rw_mutex_shared_unlock(&c->info->global_untyped_mutex); - if (found) { - return *found; - } - return nullptr; + return info; } } @@ -2023,8 +2024,6 @@ gb_internal void add_type_and_value(CheckerContext *ctx, Ast *expr, AddressingMo return; } - BlockingMutex *mutex = tav_mutex_for_node(expr); - /* Previous logic: BlockingMutex *mutex = &ctx->info->type_and_value_mutex; if (ctx->decl) { @@ -2034,10 +2033,11 @@ gb_internal void add_type_and_value(CheckerContext *ctx, Ast *expr, AddressingMo } */ - mutex_lock(mutex); Ast *prev_expr = nullptr; while (prev_expr != expr) { prev_expr = expr; + BlockingMutex *mutex = tav_mutex_for_node(expr); + mutex_lock(mutex); expr->tav.mode = mode; if (type != nullptr && expr->tav.type != nullptr && is_type_any(type) && is_type_untyped(expr->tav.type)) { @@ -2053,13 +2053,13 @@ gb_internal void add_type_and_value(CheckerContext *ctx, Ast *expr, AddressingMo } else if (mode == Addressing_Value && type != nullptr && is_type_proc(type)) { expr->tav.value = value; } + mutex_unlock(mutex); expr = unparen_expr(expr); if (expr == nullptr) { break; }; } - mutex_unlock(mutex); } gb_internal void add_entity_definition(CheckerInfo *i, Ast *identifier, Entity *entity) { @@ -2629,7 +2629,9 @@ gb_internal void check_procedure_later(Checker *c, ProcInfo *info) { if (global_procedure_body_in_worker_queue.load()) { thread_pool_add_task(check_proc_info_worker_proc, info); } else { + mutex_lock(&c->procs_to_check_mutex); array_add(&c->procs_to_check, info); + mutex_unlock(&c->procs_to_check_mutex); } if (DEBUG_CHECK_ALL_PROCEDURES) { @@ -3383,7 +3385,7 @@ gb_internal Array generate_entity_dependency_graph(CheckerInf } -gb_internal void check_single_global_entity(Checker *c, Entity *e, DeclInfo *d); +gb_internal void check_single_global_entity(Checker *c, Entity *e, DeclInfo *d, UntypedExprInfoMap *untyped=nullptr); gb_internal Entity *find_core_entity(Checker *c, String name) { @@ -5329,7 +5331,7 @@ gb_internal CheckerContext *create_checker_context(Checker *c) { return ctx; } -gb_internal void check_single_global_entity(Checker *c, Entity *e, DeclInfo *d) { +gb_internal void check_single_global_entity(Checker *c, Entity *e, DeclInfo *d, UntypedExprInfoMap *untyped) { GB_ASSERT(e != nullptr); if (e->state == EntityState_Resolved) { // NOTE: also an alias already overridden by what it aliases, which may have no `DeclInfo` @@ -5352,6 +5354,7 @@ gb_internal void check_single_global_entity(Checker *c, Entity *e, DeclInfo *d) GB_ASSERT(e->pkg != nullptr); ctx->decl = d; ctx->scope = d->scope; + ctx->untyped = untyped; if (pkg->kind == Package_Init) { if (e->kind != Entity_Procedure && e->token.string == "main") { @@ -7164,6 +7167,22 @@ gb_internal void handle_raddbg_type_view(Checker *c, RaddbgTypeView const &type_ array_add(&c->info.raddbg_type_views, RaddbgTypeView{type, view}); } +gb_internal GB_COMPARE_PROC(raddbg_type_view_cmp) { + RaddbgTypeView const *x = cast(RaddbgTypeView const *)a; + RaddbgTypeView const *y = cast(RaddbgTypeView const *)b; + Entity *xe = (x->type && x->type->kind == Type_Named) ? x->type->Named.type_name : nullptr; + Entity *ye = (y->type && y->type->kind == Type_Named) ? y->type->Named.type_name : nullptr; + if (xe != nullptr && ye != nullptr && xe != ye) { + return entity_source_order_cmp(xe, ye); + } + u64 xh = type_hash_canonical_type(x->type); + u64 yh = type_hash_canonical_type(y->type); + if (xh != yh) { + return xh < yh ? -1 : +1; + } + return string_compare(x->view, y->view); +} + gb_internal void check_objc_context_provider_procedures(Checker *c) { for (Entity *e = nullptr; mpsc_dequeue(&c->procs_with_objc_context_provider_to_check, &e); /**/) { GB_ASSERT(e->kind == Entity_TypeName); @@ -7343,18 +7362,18 @@ gb_internal void check_update_dependency_tree_for_procedures(Checker *c) { #else gb_internal void check_walk_all_dependencies(DeclInfo *decl); -gb_internal WORKER_TASK_PROC(check_walk_all_dependencies_worker_proc) { - if (data == nullptr) { - return 0; - } - DeclInfo *decl = cast(DeclInfo *)data; - +// NOTE: post-order, so a declaration has the dependencies of all those nested in it before they are added to its parent +gb_internal void check_walk_all_dependencies_post_order(DeclInfo *decl) { for (DeclInfo *child = decl->next_child; child != nullptr; child = child->next_sibling) { - thread_pool_add_task(check_walk_all_dependencies_worker_proc, child); - check_walk_all_dependencies(child); + check_walk_all_dependencies_post_order(child); } - add_deps_from_child_to_parent(decl); +} + +gb_internal WORKER_TASK_PROC(check_walk_all_dependencies_worker_proc) { + if (data != nullptr) { + check_walk_all_dependencies_post_order(cast(DeclInfo *)data); + } return 0; } @@ -7488,6 +7507,17 @@ gb_internal void check_parsed_files(Checker *c) { TIME_SECTION("export entities - post"); check_export_entities(c); + // NOTE: no global name is declared from here on, so their scopes are read without locking + for (AstPackage *pkg : c->parser->packages) { + pkg->scope->flags |= ScopeFlag_ReadOnly; + for (AstFile *f : pkg->files) { + f->scope->flags |= ScopeFlag_ReadOnly; + } + } + builtin_pkg->scope->flags |= ScopeFlag_ReadOnly; + intrinsics_pkg->scope->flags |= ScopeFlag_ReadOnly; + config_pkg->scope->flags |= ScopeFlag_ReadOnly; + TIME_SECTION("add entities from packages"); check_merge_queues_into_arrays(c); @@ -7622,6 +7652,9 @@ gb_internal void check_parsed_files(Checker *c) { debugf("Total Procedure Bodies Checked: %td\n", total_bodies_checked.load(std::memory_order_relaxed)); + TIME_SECTION("check unique link names"); + check_link_name_uses(c); + TIME_SECTION("check unique package names"); bool package_names_are_unique = check_unique_package_names(c); @@ -7720,8 +7753,16 @@ gb_internal void check_parsed_files(Checker *c) { } TIME_SECTION("collate type info stuff"); - for (RaddbgTypeView type_view; mpsc_dequeue(&c->info.raddbg_type_views_queue, &type_view); /**/) { - handle_raddbg_type_view(c, type_view); + { + auto views = array_make(heap_allocator()); + defer (array_free(&views)); + for (RaddbgTypeView type_view; mpsc_dequeue(&c->info.raddbg_type_views_queue, &type_view); /**/) { + array_add(&views, type_view); + } + array_sort(views, raddbg_type_view_cmp); + for (RaddbgTypeView const &type_view : views) { + handle_raddbg_type_view(c, type_view); + } } diff --git a/src/checker.hpp b/src/checker.hpp index 633790405..78f579c2d 100644 --- a/src/checker.hpp +++ b/src/checker.hpp @@ -269,6 +269,20 @@ struct ProcInfo { }; +enum LinkNameUseKind : u8 { + LinkNameUse_ForeignProcedure, + LinkNameUse_Procedure, // exported or with a link name + LinkNameUse_Variable, // foreign or exported +}; + +struct LinkNameUse { + String name; + Entity * entity; + DeclInfo * decl; + LinkNameUseKind kind; +}; + + enum { DEFAULT_SCOPE_CAPACITY = 32 }; @@ -461,6 +475,19 @@ gb_internal Entity *scope_map_get(ScopeMap *m, InternedString key, u32 hash) { } } +// NOTE: the key must be present; never grows, so lookups that do not lock see either value +gb_internal void scope_map_replace(ScopeMap *m, InternedString key, u32 hash, Entity *value) { + u32 mask = m->cap-1; + for (u32 pos = hash & mask;; pos = (pos + 1) & mask) { + ScopeMapSlot *s = &m->slots[pos]; + GB_ASSERT(s->hash != 0); + if (s->hash == hash && m->keys[pos] == key) { + s->value = value; + return; + } + } +} + gb_internal void scope_map_clear(ScopeMap *m) { gb_memset(m->slots, 0, gb_size_of(*m->slots) * m->cap); m->count = 0; @@ -548,6 +575,7 @@ enum ScopeFlag : i32 { ScopeFlag_Type = 1<<7, ScopeFlag_HasBeenImported = 1<<10, // This is only applicable to file scopes + ScopeFlag_ReadOnly = 1<<11, // file, package and universe scopes once every global is declared: read without locking ScopeFlag_ContextDefined = 1<<16, }; @@ -761,7 +789,9 @@ struct CheckerInfo { // TypeSet type_info_set; BlockingMutex foreign_mutex; // NOT recursive - StringMap foreigns; + Array link_names; // checked for clashes once everything is checked, see `check_link_name_uses` + + BlockingMutex entry_point_mutex; MPSCQueue definition_queue; MPSCQueue entity_queue; @@ -868,6 +898,7 @@ struct Checker { MPSCQueue procs_with_deferred_to_check; MPSCQueue procs_with_objc_context_provider_to_check; + BlockingMutex procs_to_check_mutex; Array procs_to_check; BlockingMutex nested_proc_lits_mutex; @@ -921,6 +952,9 @@ gb_internal void check_add_foreign_import_decl(CheckerContext *c, Ast *decl); gb_internal void check_entity_decl(CheckerContext *c, Entity *e, DeclInfo *d, Type *named_type); gb_internal void global_group_check_edge(CheckerContext *ctx, Entity *e); +// While a group of global entities is checked: its incomplete '#soa' types, completed by the same thread +gb_thread_local Array *global_group_soa_types; + struct GlobalWhenTrialEntityScope { struct GlobalWhenTrial *trial; i32 mute_depth; diff --git a/src/checker_global.cpp b/src/checker_global.cpp index ccc212505..fd8b51130 100644 --- a/src/checker_global.cpp +++ b/src/checker_global.cpp @@ -39,6 +39,7 @@ gb_internal void global_import_stage_end(GlobalImportStagePart part, u64 start) } } +gb_global std::atomic in_global_entity_stage; // to tell the checks of the global stage from those during 'when' resolution gb_global BlockingMutex global_entity_time_mutex; gb_global PtrMap global_entity_times; gb_thread_local u64 global_entity_child_ticks; @@ -68,7 +69,7 @@ gb_internal void global_entity_timing_end(GlobalEntityTimingFrame const &f, Enti u64 self = total - gb_min(total, global_entity_child_ticks); global_entity_child_ticks = f.saved_child_ticks + total; - bool in_global_loop = in_single_threaded_checker_stage.load(std::memory_order_relaxed); + bool in_global_loop = in_global_entity_stage.load(std::memory_order_relaxed); MUTEX_GUARD(&global_entity_time_mutex); GlobalEntityTime *found = map_get(&global_entity_times, e); @@ -1321,10 +1322,15 @@ gb_internal void global_group_check_edge(CheckerContext *ctx, Entity *e) { // NOTE: members in a fixed order, as which member of a cycle is entered first can decide whether it checks, // e.g. an enum whose values are `union_variant_index`es of a union with pointers back to it +// NOTE: a group's untyped expressions and '#soa' types are its own, so it touches no shared queue meanwhile gb_internal void check_global_group(Checker *c, GlobalGroupGraph *g, i32 gi) { GlobalGroup *group = &g->groups[gi]; i32 *members = g->members.data + group->start; + UntypedExprInfoMap untyped = {}; + auto soa_types = array_make(heap_allocator()); + global_group_soa_types = &soa_types; + g->current_group = gi; for (i32 k = 0; k < group->count; k++) { Entity *e = g->nodes[members[k]]; @@ -1334,17 +1340,27 @@ gb_internal void check_global_group(Checker *c, GlobalGroupGraph *g, i32 gi) { } g->current_entity = e; GlobalEntityTimingFrame timing_frame = global_entity_timing_begin(e); - check_single_global_entity(c, e, e->decl_info); + check_single_global_entity(c, e, e->decl_info, &untyped); if (e->type != nullptr && is_type_typed(e->type)) { - for (Type *t = nullptr; mpsc_dequeue(&c->soa_types_to_complete, &t); /**/) { + for (Type *t : soa_types) { complete_soa_type(c, t, false); } + array_clear(&soa_types); (void)type_size_of(e->type); (void)type_align_of(e->type); } global_entity_timing_end(timing_frame, e); } + for (Type *t : soa_types) { + complete_soa_type(c, t, false); + } + global_group_soa_types = nullptr; + array_free(&soa_types); + + add_untyped_expressions(&c->info, &untyped); + map_destroy(&untyped); + group->done = true; g->current_group = -1; g->current_entity = nullptr; @@ -1432,7 +1448,7 @@ gb_internal void destroy_global_groups(GlobalGroupGraph *g) { } gb_internal void check_all_global_entities(Checker *c) { - in_single_threaded_checker_stage.store(true, std::memory_order_relaxed); + in_global_entity_stage.store(true, std::memory_order_relaxed); // NOTE(bill): the runtime types the checker looks up by name rather than through a declaration init_preload(c); @@ -1444,6 +1460,10 @@ gb_internal void check_all_global_entities(Checker *c) { } } + for (Type *t = nullptr; mpsc_dequeue(&c->soa_types_to_complete, &t); /**/) { + complete_soa_type(c, t, false); + } + TIME_SECTION("check all global entities - build groups"); GlobalGroupGraph *g = &global_groups; build_global_groups(c, g); @@ -1459,7 +1479,7 @@ gb_internal void check_all_global_entities(Checker *c) { gb_exit(1); } - in_single_threaded_checker_stage.store(false, std::memory_order_relaxed); + in_global_entity_stage.store(false, std::memory_order_relaxed); } diff --git a/src/entity.cpp b/src/entity.cpp index b5c1c336b..845e2b910 100644 --- a/src/entity.cpp +++ b/src/entity.cpp @@ -142,6 +142,10 @@ enum ProcedureOptimizationMode : u8 { BlockingMutex global_type_name_objc_metadata_mutex; +struct TypeNameObjCMetadata; + +gb_internal TypeNameObjCMetadata *entity_objc_metadata(struct Entity *e); + struct TypeNameObjCMetadataEntry { InternedString interned; Entity *entity; diff --git a/src/llvm_backend.cpp b/src/llvm_backend.cpp index a0d87630c..ce6a8820a 100644 --- a/src/llvm_backend.cpp +++ b/src/llvm_backend.cpp @@ -1576,7 +1576,13 @@ gb_internal void lb_finalize_objc_names(lbGenerator *gen, lbProcedure *p) { auto class_impls = array_make(temporary_allocator(), 0, 16); // Register all class implementations unconditionally, even if not statically referenced + auto implementations = array_make(temporary_allocator(), 0, 16); for (Entity *e = {}; mpsc_dequeue(&gen->info->objc_class_implementations, &e); /**/) { + array_add(&implementations, e); + } + array_sort(implementations, init_procedures_cmp); + + for (Entity *e : implementations) { GB_ASSERT(e->kind == Entity_TypeName && e->TypeName.objc_is_implementation); lb_handle_objc_find_or_register_class(p, e->TypeName.objc_class_name, e->type); diff --git a/src/types.cpp b/src/types.cpp index 6408669b0..c65a7c430 100644 --- a/src/types.cpp +++ b/src/types.cpp @@ -338,7 +338,6 @@ gb_global String const type_strings[] = { enum TypeFlag : u32 { TypeFlag_Polymorphic = 1<<1, TypeFlag_PolySpecialized = 1<<2, - TypeFlag_InProcessOfCheckingPolymorphic = 1<<3, }; struct Type { @@ -2494,13 +2493,24 @@ gb_internal TypeTuple *get_record_polymorphic_params(Type *t) { } +gb_internal TypeNameObjCMetadata *entity_objc_metadata(Entity *e) { + GB_ASSERT(e->kind == Entity_TypeName); + mutex_lock(&global_type_name_objc_metadata_mutex); + TypeNameObjCMetadata *md = e->TypeName.objc_metadata; + Type *original = e->TypeName.original_type_for_parapoly; + if (md == nullptr && original != nullptr && original->kind == Type_Named && original->Named.type_name != nullptr) { + md = original->Named.type_name->TypeName.objc_metadata; + } + mutex_unlock(&global_type_name_objc_metadata_mutex); + return md; +} + +gb_internal gb_thread_local Array is_type_polymorphic_named_stack; + gb_internal bool is_type_polymorphic(Type *t, bool or_specialized=false) { if (t == nullptr) { return false; } - if (t->flags & TypeFlag_InProcessOfCheckingPolymorphic) { - return false; - } switch (t->kind) { case Type_Generic: @@ -2508,10 +2518,18 @@ gb_internal bool is_type_polymorphic(Type *t, bool or_specialized=false) { case Type_Named: { - u32 flags = t->flags; - t->flags |= TypeFlag_InProcessOfCheckingPolymorphic; + Array &stack = is_type_polymorphic_named_stack; + for (Type *named : stack) { + if (named == t) { + return false; + } + } + if (stack.allocator.proc == nullptr) { + array_init(&stack, heap_allocator()); + } + array_add(&stack, t); bool ok = is_type_polymorphic(t->Named.base, or_specialized); - t->flags = flags; + array_pop(&stack); return ok; } @@ -3815,8 +3833,7 @@ gb_internal Selection lookup_field_with_selection(Type *type_, InternedString fi if (has_type_got_objc_class_attribute(original_type) && original_type->kind == Type_Named) { Entity *e = original_type->Named.type_name; GB_ASSERT(e->kind == Entity_TypeName); - if (e->TypeName.objc_metadata) { - auto *md = e->TypeName.objc_metadata; + if (auto *md = entity_objc_metadata(e)) { mutex_lock(md->mutex); defer (mutex_unlock(md->mutex)); for (TypeNameObjCMetadataEntry const &entry : md->type_entries) { @@ -3907,8 +3924,7 @@ gb_internal Selection lookup_field_with_selection(Type *type_, InternedString fi if (has_type_got_objc_class_attribute(original_type) && original_type->kind == Type_Named) { Entity *e = original_type->Named.type_name; GB_ASSERT(e->kind == Entity_TypeName); - if (e->TypeName.objc_metadata) { - auto *md = e->TypeName.objc_metadata; + if (auto *md = entity_objc_metadata(e)) { mutex_lock(md->mutex); defer (mutex_unlock(md->mutex)); for (TypeNameObjCMetadataEntry const &entry : md->value_entries) { @@ -4405,6 +4421,35 @@ gb_internal i64 type_target_max_align(void) { return max_align; } +// Polymorphic record instances being filled by this thread, which another may already have found in the cache +gb_internal gb_thread_local Array records_being_filled; + +gb_internal void begin_filling_record(Type *t) { + if (records_being_filled.allocator.proc == nullptr) { + array_init(&records_being_filled, heap_allocator()); + } + array_add(&records_being_filled, t); +} + +gb_internal void end_filling_record(Type *t) { + GB_ASSERT(records_being_filled.count > 0 && records_being_filled[records_being_filled.count-1] == t); + array_pop(&records_being_filled); +} + +gb_internal gb_thread_local i32 lazy_mutex_depth; + +gb_internal void wait_for_struct_fields(Type *t) { + if (t->Struct.polymorphic_parent == nullptr || t->Struct.fields_wait_signal.futex.load() != 0 || lazy_mutex_depth > 0) { + return; + } + for (Type *r : records_being_filled) { + if (r == t) { + return; + } + } + wait_signal_until_available(&t->Struct.fields_wait_signal); +} + gb_internal i64 type_align_of_internal(Type *t, TypePath *path) { GB_ASSERT(path != nullptr); if (t->failure) { @@ -4529,6 +4574,7 @@ gb_internal i64 type_align_of_internal(Type *t, TypePath *path) { } break; case Type_Struct: { + wait_for_struct_fields(t); if (t->Struct.custom_align > 0) { return gb_max(t->Struct.custom_align, 1); } @@ -4643,6 +4689,7 @@ gb_internal i64 *type_set_offsets_of(Slice const &fields, bool is_pack gb_internal bool type_set_offsets(Type *t) { t = base_type(t); if (t->kind == Type_Struct) { + wait_for_struct_fields(t); // if (t->Struct.are_offsets_being_processed.load()) { // return true; // } @@ -4847,6 +4894,7 @@ gb_internal i64 type_size_of_internal(Type *t, TypePath *path) { case Type_Struct: { + wait_for_struct_fields(t); if (t->Struct.is_raw_union) { i64 count = t->Struct.fields.count; i64 align = type_align_of_internal(t, path);