From 38fa57e9072b91a94adff585cf304675ab33f1e9 Mon Sep 17 00:00:00 2001 From: gingerBill Date: Thu, 1 Oct 2026 15:01:14 +0100 Subject: [PATCH] Make global checking and codegen order-independent --- src/build_settings.cpp | 1 + src/check_decl.cpp | 3 + src/check_expr.cpp | 76 +++++++++- src/checker.cpp | 263 +++++++++++++++-------------------- src/checker.hpp | 2 + src/llvm_backend.cpp | 7 +- src/llvm_backend_const.cpp | 28 ++-- src/llvm_backend_general.cpp | 22 ++- src/llvm_backend_proc.cpp | 13 +- src/llvm_backend_stmt.cpp | 30 +++- src/main.cpp | 8 +- 11 files changed, 275 insertions(+), 178 deletions(-) diff --git a/src/build_settings.cpp b/src/build_settings.cpp index f6ebd5755..041622a30 100644 --- a/src/build_settings.cpp +++ b/src/build_settings.cpp @@ -599,6 +599,7 @@ struct BuildContext { bool internal_ignore_llvm_verification; bool internal_llvm_no_sroa; bool internal_global_entity_graph; + u64 internal_shuffle_global_entities; // seed, 0 = no shuffle bool enable_rvo; diff --git a/src/check_decl.cpp b/src/check_decl.cpp index 48865d890..472daaf6f 100644 --- a/src/check_decl.cpp +++ b/src/check_decl.cpp @@ -996,6 +996,9 @@ gb_internal Entity *init_entity_foreign_library(CheckerContext *ctx, Entity *e) } else { String name = ident->Ident.token.string; Entity *found = scope_lookup(ctx->scope, ident->Ident.interned, ident->Ident.hash); + if (found != nullptr) { + found = resolve_alias_entity(ctx, found, nullptr); + } if (found == nullptr) { if (is_blank_ident(name)) { diff --git a/src/check_expr.cpp b/src/check_expr.cpp index 98c3e7051..2985c8c67 100644 --- a/src/check_expr.cpp +++ b/src/check_expr.cpp @@ -1592,6 +1592,45 @@ gb_internal CIdentSuggestion const c_ident_suggestions[] = { {str_lit("float64_t"), str_lit("'f64'?")}, }; +gb_internal Entity *resolve_alias_entity(CheckerContext *c, Entity *e, Type *named_type) { + if (e->kind == Entity_Constant && e->state == EntityState_Unresolved) { + check_entity_decl(c, e, nullptr, named_type); + } + while ((e->flags & EntityFlag_Overridden) && e->aliased_of != nullptr) { + e = e->aliased_of; + } + return e; +} + +gb_thread_local Array in_progress_alias_expansions; + +gb_internal Type *check_in_progress_type_alias(CheckerContext *c, Entity *e) { + for (Entity *other : in_progress_alias_expansions) { + if (other == e) { + // NOTE(bill): an alias-only cycle, which it is to be left to the usual error + return nullptr; + } + } + DeclInfo *d = decl_info_of_entity(e); + if (d == nullptr || d->init_expr == nullptr) { + return nullptr; + } + + CheckerContext nc = *c; + nc.scope = d->scope; + nc.decl = d; + nc.type_level = 0; + nc.curr_proc_calling_convention = ProcCC_Contextless; + + if (in_progress_alias_expansions.allocator.proc == nullptr) { + in_progress_alias_expansions = array_make(heap_allocator()); + } + array_add(&in_progress_alias_expansions, e); + Type *t = check_type(&nc, remove_type_alias_clutter(d->init_expr)); + array_pop(&in_progress_alias_expansions); + return t; +} + gb_internal Entity *check_ident(CheckerContext *c, Operand *o, Ast *n, Type *named_type, Type *type_hint, bool allow_import_name) { GB_ASSERT(n->kind == Ast_Ident); o->mode = Addressing_Invalid; @@ -1637,6 +1676,8 @@ gb_internal Entity *check_ident(CheckerContext *c, Operand *o, Ast *n, Type *nam } } + e = resolve_alias_entity(c, e, named_type); + if (e->kind == Entity_ProcGroup) { auto *pge = &e->ProcGroup; @@ -1760,6 +1801,9 @@ gb_internal Entity *check_ident(CheckerContext *c, Operand *o, Ast *n, Type *nam } if (o->type != nullptr && o->type->kind == Type_Named && o->type->Named.type_name->TypeName.is_type_alias) { Type *bt = base_type(o->type); + if (bt == nullptr && e->state == EntityState_InProgress) { + bt = check_in_progress_type_alias(c, e); + } // Keep struct aliases named so recursive fields retain their alias edge. if (bt != nullptr && bt->kind != Type_Struct) { o->type = bt; @@ -5754,6 +5798,7 @@ gb_internal Entity *check_entity_from_ident_or_selector(CheckerContext *c, Ast * return nullptr; } + entity = resolve_alias_entity(c, entity, nullptr); check_entity_decl(c, entity, nullptr, nullptr); if (entity->kind == Entity_ProcGroup) { return entity; @@ -5884,6 +5929,7 @@ gb_internal Entity *check_selector(CheckerContext *c, Operand *operand, Ast *nod // return nullptr; } + entity = resolve_alias_entity(c, entity, nullptr); check_entity_decl(c, entity, nullptr, nullptr); if (entity->kind == Entity_ProcGroup) { operand->mode = Addressing_ProcGroup; @@ -8486,11 +8532,14 @@ gb_internal CallArgumentError check_polymorphic_record_type(CheckerContext *c, O // NOTE(bill, 2019-10-26): Allow a cycle in the parameters but not in the fields themselves auto prev_type_path = c->type_path; + bool prev_allow_in_progress = c->allow_in_progress_type_operand; c->type_path = new_checker_type_path(); + c->allow_in_progress_type_operand = true; defer ({ destroy_checker_type_path(c->type_path); c->type_path = prev_type_path; + c->allow_in_progress_type_operand = prev_allow_in_progress; }); TEMPORARY_ALLOCATOR_GUARD(); @@ -9014,6 +9063,11 @@ gb_internal void check_objc_call_expr(CheckerContext *c, Operand *operand, Ast * } gb_internal ExprKind check_call_expr(CheckerContext *c, Operand *operand, Ast *call, Ast *proc, Slice const &args, ProcInlining inlining, ProcTailing tailing, Type *type_hint) { + // NOTE(bill): only the direct arguments of a polymorphic record call may name a type still being checked + bool prev_allow_in_progress = c->allow_in_progress_type_operand; + c->allow_in_progress_type_operand = false; + defer (c->allow_in_progress_type_operand = prev_allow_in_progress); + if (proc != nullptr && proc->kind == Ast_BasicDirective) { ast_node(bd, BasicDirective, proc); @@ -13100,9 +13154,21 @@ gb_internal ExprKind check_expr_base_internal(CheckerContext *c, Operand *o, Ast +gb_internal bool is_in_progress_type_operand(CheckerContext *c, Operand *o, Ast *node) { + if (!c->allow_in_progress_type_operand || o->mode != Addressing_Type || o->type->kind != Type_Named) { + return false; + } + node = unparen_expr(node); + if (node == nullptr || (node->kind != Ast_Ident && node->kind != Ast_SelectorExpr)) { + return false; + } + Entity *e = o->type->Named.type_name; + return e != nullptr && e->state == EntityState_InProgress; +} + gb_internal ExprKind check_expr_base(CheckerContext *c, Operand *o, Ast *node, Type *type_hint) { ExprKind kind = check_expr_base_internal(c, o, node, type_hint); - if (o->type != nullptr && core_type(o->type) == nullptr) { + if (o->type != nullptr && core_type(o->type) == nullptr && !is_in_progress_type_operand(c, o, node)) { o->type = t_invalid; gbString xs = expr_to_string(o->expr); if (o->mode == Addressing_Type) { @@ -13490,9 +13556,13 @@ gb_internal gbString write_expr_to_string(gbString str, Ast *node, bool shorthan // NOTE(tf2spi): // Two proc literals with the same signature output the same expr above // which poses challenges for name canonicalization. Include the below - // discriminator with the file ID and offset to help with this. + // discriminator with the file and offset to help with this. + // NOTE: the file by package and name, not its ID, as IDs follow the order files were parsed in TokenPos pos = ast_token(node).pos; - str = gb_string_append_fmt(str, " /* %d!%d */", pos.file_id, pos.offset); + AstFile *pl_file = node->file(); + String pl_pkg = (pl_file && pl_file->pkg) ? pl_file->pkg->name : String{}; + String pl_name = pl_file ? filename_without_directory(pl_file->fullpath) : String{}; + str = gb_string_append_fmt(str, " /* %.*s:%.*s!%d */", LIT(pl_pkg), LIT(pl_name), pos.offset); case_end; case_ast_node(cl, CompoundLit, node); diff --git a/src/checker.cpp b/src/checker.cpp index 3cb10f95b..36d294352 100644 --- a/src/checker.cpp +++ b/src/checker.cpp @@ -82,18 +82,45 @@ gb_internal void entity_graph_node_destroy(EntityGraphNode *n, gbAllocator a) { } +gb_internal int entity_source_order_cmp(Entity *x, Entity *y) { + if (x == y) { + return 0; + } + int cmp = 0; + if (x->pkg != y->pkg) { + isize order_x = x->pkg ? x->pkg->order : 0; + isize order_y = y->pkg ? y->pkg->order : 0; + cmp = isize_cmp(order_x, order_y); + if (cmp) { + return cmp; + } + } + if (x->file != y->file) { + String fullpath_x = x->file ? x->file->fullpath : (String{}); + String fullpath_y = y->file ? y->file->fullpath : (String{}); + String file_x = filename_from_path(fullpath_x); + String file_y = filename_from_path(fullpath_y); + + cmp = string_compare(file_x, file_y); + if (cmp) { + return cmp; + } + } + + cmp = u64_cmp(x->order_in_src, y->order_in_src); + if (cmp) { + return cmp; + } + return i32_cmp(x->token.pos.offset, y->token.pos.offset); +} + gb_internal int entity_graph_node_cmp(EntityGraphNode **data, isize i, isize j) { EntityGraphNode *x = data[i]; EntityGraphNode *y = data[j]; - u64 a = x->entity->order_in_src; - u64 b = y->entity->order_in_src; - if (x->dep_count < y->dep_count) { - return -1; + if (x->dep_count != y->dep_count) { + return x->dep_count < y->dep_count ? -1 : +1; } - if (x->dep_count == y->dep_count) { - return a < b ? -1 : b > a; - } - return +1; + return entity_source_order_cmp(x->entity, y->entity); } gb_internal void entity_graph_node_swap(EntityGraphNode **data, isize i, isize j) { @@ -147,10 +174,10 @@ gb_internal int import_graph_node_cmp(ImportGraphNode **data, isize i, isize j) bool xg = (x->scope->flags&ScopeFlag_Global) != 0; bool yg = (y->scope->flags&ScopeFlag_Global) != 0; if (xg != yg) return xg ? -1 : +1; - if (xg && yg) return x->pkg->id < y->pkg->id ? +1 : -1; if (x->dep_count < y->dep_count) return -1; if (x->dep_count > y->dep_count) return +1; - return 0; + // NOTE: a total order, so the package order does not depend on parsing or hashing order + return string_compare(x->pkg->fullpath, y->pkg->fullpath); } gb_internal void import_graph_node_swap(ImportGraphNode **data, isize i, isize j) { @@ -360,8 +387,9 @@ gb_internal void check_open_scope(CheckerContext *c, Ast *node) { break; } if (c->decl && c->decl->proc_lit) { - // Number the scopes within a procedure body depth-first - scope->index = c->decl->scope_index++; + // NOTE: numbered by position rather than in checking order, as that order varies (e.g. with + // which caller instantiates a record first); 0 is the procedure's own scope + scope->index = node->kind == Ast_ProcType ? 0 : 1 + ast_token(node).pos.offset; } c->scope = scope; c->state_flags |= StateFlag_bounds_check; @@ -3246,43 +3274,44 @@ gb_internal gb_inline bool is_entity_a_dependency(Entity *e) { } gb_internal Array generate_entity_dependency_graph(CheckerInfo *info, Arena *arena) { - PtrMap M_procs = {}; - PtrMap M_vars = {}; - PtrMap M_other = {}; - - map_init(&M_procs, info->entities.count); - defer (map_destroy(&M_procs)); - + PtrMap M_vars = {}; map_init(&M_vars, info->entities.count); defer (map_destroy(&M_vars)); - map_init(&M_other, info->entities.count); - defer (map_destroy(&M_other)); - - for_array(i, info->entities) { - Entity *e = info->entities[i]; - if (e == nullptr || !is_entity_a_dependency(e)) { + auto G = array_make(arena_allocator(arena), 0, info->entities.count); + for (Entity *e : info->entities) { + if (e == nullptr || e->kind != Entity_Variable || !is_entity_a_dependency(e)) { continue; } EntityGraphNode *n = arena_alloc_item(arena); n->entity = e; - switch (e->kind) { - case Entity_Procedure: map_set(&M_procs, e, n); break; - case Entity_Variable: map_set(&M_vars, e, n); break; - default: map_set(&M_other, e, n); break; - } + map_set(&M_vars, e, n); + array_add(&G, n); } - TIME_SECTION("generate_entity_dependency_graph: Calculate edges for graph M - Part 1"); - // Calculate edges for graph M - for (auto const &entry : M_procs) { - EntityGraphNode *n = entry.value; - Entity *e = n->entity; + TIME_SECTION("generate_entity_dependency_graph: Calculate edges"); - DeclInfo *decl = decl_info_of_entity(e); - GB_ASSERT(decl != nullptr); + // NOTE(bill): A variable depends on every variable reachable from its declaration through procedures and constants, + // as its initializer may read any of them. Variables are not walked through, as they are ordered by their own edges. + PtrSet visited = {}; + defer (ptr_set_destroy(&visited)); + auto stack = array_make(heap_allocator(), 0, 64); + defer (array_free(&stack)); + for (EntityGraphNode *n : G) { + ptr_set_clear(&visited); + array_clear(&stack); + + DeclInfo *decl = decl_info_of_entity(n->entity); + if (decl == nullptr) { + continue; + } FOR_PTR_SET(dep, decl->deps) { + array_add(&stack, dep); + } + + while (stack.count > 0) { + Entity *dep = array_pop(&stack); GB_ASSERT(dep != nullptr); if (dep->flags & EntityFlag_Field) { continue; @@ -3290,67 +3319,26 @@ gb_internal Array generate_entity_dependency_graph(CheckerInf if (!is_entity_a_dependency(dep)) { continue; } - EntityGraphNode *m = nullptr; - - switch (dep->kind) { - case Entity_Procedure: m = map_must_get(&M_procs, dep); break; - case Entity_Variable: m = map_must_get(&M_vars, dep); break; - default: m = map_must_get(&M_other, dep); break; - } - entity_graph_node_set_add(&n->succ, m); - entity_graph_node_set_add(&m->pred, n); - } - } - - TIME_SECTION("generate_entity_dependency_graph: Calculate edges for graph M - Part 2a (init)"); - - auto G = array_make(arena_allocator(arena), 0, M_procs.count + M_vars.count + M_other.count); - - TIME_SECTION("generate_entity_dependency_graph: Calculate edges for graph M - Part 2b (procs)"); - - for (auto const &m_entry : M_procs) { - EntityGraphNode *n = m_entry.value; - - // Connect each pred 'p' of 'n' with each succ 's' and from - // the procedure node - FOR_PTR_SET(p, n->pred) { - // Ignore self-cycles - if (p == n) { + if (dep->kind == Entity_Variable) { + EntityGraphNode **m = map_get(&M_vars, dep); + if (m != nullptr) { + entity_graph_node_set_add(&n->succ, *m); + entity_graph_node_set_add(&(*m)->pred, n); + } continue; } - // Each succ 's' of 'n' becomes a succ of 'p', and - // each pred 'p' of 'n' becomes a pred of 's' - FOR_PTR_SET(s, n->succ) { - // Ignore self-cycles - if (s == n) { - continue; - } - if (p->entity->kind == Entity_Procedure && - s->entity->kind == Entity_Procedure) { - // NOTE(bill, 2020-11-15): Only care about variable initialization ordering - // TODO(bill): This is probably wrong!!!! - continue; - } - // IMPORTANT NOTE/TODO(bill, 2020-11-15): These three calls take the majority of the - // the time to process - entity_graph_node_set_add(&p->succ, s); - entity_graph_node_set_add(&s->pred, p); - // Remove edge to 'n' - entity_graph_node_set_remove(&s->pred, n); + if (ptr_set_update(&visited, dep)) { + continue; + } + DeclInfo *dep_decl = decl_info_of_entity(dep); + if (dep_decl != nullptr) { + FOR_PTR_SET(next, dep_decl->deps) { + array_add(&stack, next); + } } - - // Remove edge to 'n' - entity_graph_node_set_remove(&p->succ, n); } } - TIME_SECTION("generate_entity_dependency_graph: Calculate edges for graph M - Part 2c (vars)"); - - for (auto const &m_entry : M_vars) { - EntityGraphNode *n = m_entry.value; - array_add(&G, n); - } - TIME_SECTION("generate_entity_dependency_graph: Dependency Count Checker"); for_array(i, G) { EntityGraphNode *n = G[i]; @@ -3359,28 +3347,6 @@ gb_internal Array generate_entity_dependency_graph(CheckerInf GB_ASSERT(n->dep_count >= 0); } - // f64 succ_count = 0.0; - // f64 pred_count = 0.0; - // f64 succ_capacity = 0.0; - // f64 pred_capacity = 0.0; - // f64 succ_max = 0.0; - // f64 pred_max = 0.0; - // for_array(i, G) { - // EntityGraphNode *n = G[i]; - // succ_count += n->succ.entries.count; - // pred_count += n->pred.entries.count; - // succ_capacity += n->succ.entries.capacity; - // pred_capacity += n->pred.entries.capacity; - - // succ_max = gb_max(succ_max, n->succ.entries.capacity); - // pred_max = gb_max(pred_max, n->pred.entries.capacity); - - // } - // f64 count = cast(f64)G.count; - // gb_printf_err(">>>count pred: %f succ: %f\n", pred_count/count, succ_count/count); - // gb_printf_err(">>>capacity pred: %f succ: %f\n", pred_capacity/count, succ_capacity/count); - // gb_printf_err(">>>max pred: %f succ: %f\n", pred_max, succ_max); - return G; } @@ -5342,14 +5308,15 @@ gb_internal CheckerContext *create_checker_context(Checker *c) { gb_internal void check_single_global_entity(Checker *c, Entity *e, DeclInfo *d) { GB_ASSERT(e != nullptr); + if (e->state == EntityState_Resolved) { + // NOTE: also an alias already overridden by what it aliases, which may have no `DeclInfo` + return; + } GB_ASSERT(d != nullptr); if (d->scope != e->scope) { return; } - if (e->state == EntityState_Resolved) { - return; - } CheckerContext *ctx = create_checker_context(c); @@ -5373,13 +5340,32 @@ gb_internal void check_single_global_entity(Checker *c, Entity *e, DeclInfo *d) check_entity_decl(ctx, e, d, nullptr); } +// for `-internal-shuffle-global-entities` +// visit globals in a seeded random order, so that any result depending on the visiting order shows up without needing threads +gb_internal void shuffle_global_entities(Array *entities, u64 seed) { + if (seed == 0) { + return; + } + u64 state = seed; + for (isize i = entities->count-1; i > 0; i--) { + state = state*6364136223846793005ull + 1442695040888963407ull; + isize j = cast(isize)((state >> 33) % cast(u64)(i+1)); + Entity *tmp = (*entities)[i]; + (*entities)[i] = (*entities)[j]; + (*entities)[j] = tmp; + } +} + gb_internal void check_all_global_entities(Checker *c) { in_single_threaded_checker_stage.store(true, std::memory_order_relaxed); + auto order = array_clone(heap_allocator(), c->info.entities); + defer (array_free(&order)); + shuffle_global_entities(&order, build_context.internal_shuffle_global_entities); + // NOTE(bill): This must be single threaded // Don't bother trying - for_array(i, c->info.entities) { - Entity *e = c->info.entities[i]; + for (Entity *e : order) { GB_ASSERT(e != nullptr); if (e->flags & EntityFlag_Lazy) { continue; @@ -7537,40 +7523,7 @@ gb_internal void check_merge_queues_into_arrays(Checker *c) { } gb_internal GB_COMPARE_PROC(init_procedures_cmp) { - int cmp = 0; - Entity *x = *(Entity **)a; - Entity *y = *(Entity **)b; - if (x == y) { - cmp = 0; - return cmp; - } - - if (x->pkg != y->pkg) { - isize order_x = x->pkg ? x->pkg->order : 0; - isize order_y = y->pkg ? y->pkg->order : 0; - cmp = isize_cmp(order_x, order_y); - if (cmp) { - return cmp; - } - } - if (x->file != y->file) { - String fullpath_x = x->file ? x->file->fullpath : (String{}); - String fullpath_y = y->file ? y->file->fullpath : (String{}); - String file_x = filename_from_path(fullpath_x); - String file_y = filename_from_path(fullpath_y); - - cmp = string_compare(file_x, file_y); - if (cmp) { - return cmp; - } - } - - - cmp = u64_cmp(x->order_in_src, y->order_in_src); - if (cmp) { - return cmp; - } - return i32_cmp(x->token.pos.offset, y->token.pos.offset); + return entity_source_order_cmp(*(Entity **)a, *(Entity **)b); } gb_internal GB_COMPARE_PROC(fini_procedures_cmp) { @@ -7777,6 +7730,10 @@ gb_internal void check_parsed_files(Checker *c) { TIME_SECTION("add entities from packages"); check_merge_queues_into_arrays(c); + TIME_SECTION("sort global entities"); + // NOTE: the queues are filled by parallel workers, so their order differs between runs + array_sort(c->info.entities, init_procedures_cmp); + TIME_SECTION("check all global entities"); check_all_global_entities(c); diff --git a/src/checker.hpp b/src/checker.hpp index c382d28fe..e51b48da3 100644 --- a/src/checker.hpp +++ b/src/checker.hpp @@ -846,6 +846,7 @@ struct CheckerContext { bool in_polymorphic_specialization; bool allow_arrow_right_selector_expr; bool allow_c_vararg_param; + bool allow_in_progress_type_operand; // a bare type name may still be being checked (polymorphic record arguments) u8 bit_field_bit_size; Scope * polymorphic_scope; @@ -917,6 +918,7 @@ 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 wait_for_lazy_entity(CheckerContext *c, Entity *e); +gb_internal Ast *remove_type_alias_clutter(Ast *node); gb_internal void check_const_decl(CheckerContext *c, Entity *e, Ast *type_expr, Ast *init_expr, Type *named_type); gb_internal void check_type_decl(CheckerContext *c, Entity *e, Ast *type_expr, Type *def); diff --git a/src/llvm_backend.cpp b/src/llvm_backend.cpp index 6ff3d182a..a0d87630c 100644 --- a/src/llvm_backend.cpp +++ b/src/llvm_backend.cpp @@ -2254,10 +2254,13 @@ gb_internal GB_COMPARE_PROC(llvm_global_entity_cmp) { i32 cmp = 0; cmp = token_pos_cmp(x->token.pos, y->token.pos); - if (!cmp) { + if (cmp) { return cmp; } - return cmp; + // NOTE(bill): polymorphic instances share their declaration's token, + // so order them by their type, otherwise their order (and every name numbered after them) + // follows the checking order + return lb_entity_type_cmp(x, y); } gb_internal void lb_create_global_procedures_and_types(lbGenerator *gen, CheckerInfo *info, bool do_threading) { diff --git a/src/llvm_backend_const.cpp b/src/llvm_backend_const.cpp index 49d59d62f..7a53e3fb1 100644 --- a/src/llvm_backend_const.cpp +++ b/src/llvm_backend_const.cpp @@ -427,25 +427,35 @@ gb_internal String lb_source_code_location_gen_name(lbProcedure *p, Ast *node) { -gb_internal lbValue lb_emit_source_code_location_as_global_ptr(lbProcedure *p, String const &procedure, TokenPos const &pos) { - lbValue loc = lb_emit_source_code_location_const(p, procedure, pos); - lbAddr addr = lb_add_global_generated_with_name(p->module, loc.type, loc, lb_source_code_location_gen_name(procedure, pos)); +gb_internal lbValue lb_source_code_location_global_ptr(lbModule *m, lbValue loc, String const &name) { + // NOTE(bill): every polymorphic instance of a procedure produces the same location under the same name. + // Reuse it rather than letting LLVM rename the duplicate, as which instance got the new name depended + // on the order the instances were generated in. + LLVMValueRef found = LLVMGetNamedGlobal(m->mod, alloc_cstring(temporary_allocator(), name)); + if (found != nullptr && LLVMGetInitializer(found) == loc.value) { + lbValue g = {}; + g.type = alloc_type_pointer(default_type(loc.type)); + g.value = LLVMConstPointerCast(found, lb_type(m, g.type)); + return g; + } + lbAddr addr = lb_add_global_generated_with_name(m, loc.type, loc, name); lb_make_global_private_const(addr); return addr.addr; } +gb_internal lbValue lb_emit_source_code_location_as_global_ptr(lbProcedure *p, String const &procedure, TokenPos const &pos) { + lbValue loc = lb_emit_source_code_location_const(p, procedure, pos); + return lb_source_code_location_global_ptr(p->module, loc, lb_source_code_location_gen_name(procedure, pos)); +} + gb_internal lbValue lb_const_source_code_location_as_global_ptr(lbModule *m, String const &procedure, TokenPos const &pos) { lbValue loc = lb_const_source_code_location_const(m, procedure, pos); - lbAddr addr = lb_add_global_generated_with_name(m, loc.type, loc, lb_source_code_location_gen_name(procedure, pos)); - lb_make_global_private_const(addr); - return addr.addr; + return lb_source_code_location_global_ptr(m, loc, lb_source_code_location_gen_name(procedure, pos)); } gb_internal lbValue lb_emit_source_code_location_as_global_ptr(lbProcedure *p, Ast *node) { lbValue loc = lb_emit_source_code_location_const(p, node); - lbAddr addr = lb_add_global_generated_with_name(p->module, loc.type, loc, lb_source_code_location_gen_name(p, node)); - lb_make_global_private_const(addr); - return addr.addr; + return lb_source_code_location_global_ptr(p->module, loc, lb_source_code_location_gen_name(p, node)); } diff --git a/src/llvm_backend_general.cpp b/src/llvm_backend_general.cpp index 9a45899d4..84e1c015d 100644 --- a/src/llvm_backend_general.cpp +++ b/src/llvm_backend_general.cpp @@ -3647,11 +3647,25 @@ gb_internal lbValue lb_generate_anonymous_proc_lit(lbModule *m, String const &pr TokenPos pos = ast_token(expr).pos; // NOTE(bill): Generate a new name - // parent$count - isize name_len = prefix_name.len + 6 + 11; + // parent$anon-pkg:file:offset + // NOTE(bill): named by position rather than a counter, as the order these are generated in varies + String prefix = prefix_name; + if (parent == nullptr) { + // NOTE(bill): a literal inside a polymorphic procedure exists once per instance at the same position, so name it after the enclosing procedure + for (DeclInfo *d = pl->decl->parent; d != nullptr; d = d->parent) { + Entity *pe = d->entity.load(); + if (pe != nullptr && pe->kind == Entity_Procedure) { + prefix = lb_get_entity_name(m, pe); + break; + } + } + } + AstFile *lit_file = expr->file(); + String lit_pkg = (lit_file && lit_file->pkg) ? lit_file->pkg->name : str_lit(""); + String lit_name = lit_file ? filename_without_directory(lit_file->fullpath) : str_lit(""); + isize name_len = prefix.len + lit_pkg.len + lit_name.len + 6 + 2 + 11 + 1; char *name_text = gb_alloc_array(permanent_allocator(), char, name_len); - static std::atomic name_id; - name_len = gb_snprintf(name_text, name_len, "%.*s$anon-%d", LIT(prefix_name), 1+name_id.fetch_add(1)); + name_len = gb_snprintf(name_text, name_len, "%.*s$anon-%.*s:%.*s:%d", LIT(prefix), LIT(lit_pkg), LIT(lit_name), pos.offset); String name = make_string((u8 *)name_text, name_len-1); Type *type = type_of_expr(expr); diff --git a/src/llvm_backend_proc.cpp b/src/llvm_backend_proc.cpp index 1d2cf6c7e..902a43e36 100644 --- a/src/llvm_backend_proc.cpp +++ b/src/llvm_backend_proc.cpp @@ -886,11 +886,18 @@ gb_internal void lb_build_nested_proc(lbProcedure *p, AstProcLit *pd, Entity *e) } - isize name_len = p->name.len + 1 + pd_name.len + 1 + 10 + 1; + isize name_len = p->name.len + 1 + pd_name.len + 1 + 10 + 1 + 16 + 1; char *name_text = gb_alloc_array(permanent_allocator(), char, name_len); - i32 guid = cast(i32)p->children.count; - name_len = gb_snprintf(name_text, name_len, "%.*s" ABI_PKG_NAME_SEPARATOR "%.*s-%d", LIT(p->name), LIT(pd_name), guid); + // NOTE(bill): named by declaration position (and type, for polymorphic instances, which share it) + // rather than by how many children were built before it, as that order varies + i32 guid = e->token.pos.offset; + if (e->decl_info != nullptr && e->decl_info->para_poly_original != nullptr) { + name_len = gb_snprintf(name_text, name_len, "%.*s" ABI_PKG_NAME_SEPARATOR "%.*s-%d-%llx", LIT(p->name), LIT(pd_name), guid, + cast(unsigned long long)type_hash_canonical_type(e->type)); + } else { + name_len = gb_snprintf(name_text, name_len, "%.*s" ABI_PKG_NAME_SEPARATOR "%.*s-%d", LIT(p->name), LIT(pd_name), guid); + } String name = make_string(cast(u8 *)name_text, name_len-1); e->Procedure.link_name = name; diff --git a/src/llvm_backend_stmt.cpp b/src/llvm_backend_stmt.cpp index cab4615af..e8a86ce8b 100644 --- a/src/llvm_backend_stmt.cpp +++ b/src/llvm_backend_stmt.cpp @@ -1,6 +1,26 @@ #define LB_ENABLE_BASIC_RVO true #define LB_ENABLE_ADVANCED_RVO build_context.enable_rvo +// NOTE(bill): Orders entities by their canonical type +gb_internal i32 lb_entity_type_cmp(Entity *x, Entity *y) { + if (x->type == y->type || x->type == nullptr || y->type == nullptr) { + return 0; + } + u64 hx = type_hash_canonical_type(x->type); + u64 hy = type_hash_canonical_type(y->type); + if (hx != hy) { + return hx < hy ? -1 : +1; + } + // NOTE(bill): Polymorphic instances share their declaration's token, so this is what tells them apart deterministically + TEMPORARY_ALLOCATOR_GUARD(); + return string_compare(type_to_canonical_string(temporary_allocator(), x->type), + type_to_canonical_string(temporary_allocator(), y->type)); +} + +gb_internal GB_COMPARE_PROC(lb_polymorphic_instance_cmp) { + return lb_entity_type_cmp(*cast(Entity **)a, *cast(Entity **)b); +} + gb_internal LLVMValueRef lb_coerce_fields_load(lbProcedure *p, lbValue x, lbArgType const *arg); // NOTE(bill): @RVO Check if a call expression returns by sret with a return type matching dst_type. @@ -180,14 +200,18 @@ gb_internal void lb_build_constant_value_decl(lbProcedure *p, AstValueDecl *vd) GenProcsData *gpd = e->Procedure.gen_procs; if (gpd) { rw_mutex_shared_lock(&gpd->mutex); - for (Entity *e : gpd->procs) { + // NOTE)bill):: build the instances by type, not in the order they were instantiated, which varies + TEMPORARY_ALLOCATOR_GUARD(); + auto procs = array_clone(temporary_allocator(), gpd->procs); + rw_mutex_shared_unlock(&gpd->mutex); + array_sort(procs, lb_polymorphic_instance_cmp); + for (Entity *e : procs) { if (e->min_dep_count.load(std::memory_order_relaxed) == 0) { continue; } DeclInfo *d = decl_info_of_entity(e); lb_build_nested_proc(p, &d->proc_lit->ProcLit, e); } - rw_mutex_shared_unlock(&gpd->mutex); } else { lb_build_nested_proc(p, pl, e); } @@ -2447,7 +2471,7 @@ gb_internal void lb_build_static_variables(lbProcedure *p, AstValueDecl *vd) { { gbString str = gb_string_make_length(permanent_allocator(), p->name.text, p->name.len); str = gb_string_appendc(str, "-"); - str = gb_string_append_fmt(str, ".%.*s-%llu", LIT(name), cast(long long)e->id); + str = gb_string_append_fmt(str, ".%.*s-%d", LIT(name), e->token.pos.offset); mangled_name.text = cast(u8 *)str; mangled_name.len = gb_string_length(str); } diff --git a/src/main.cpp b/src/main.cpp index ca56c40f8..6f0308cfb 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -533,6 +533,7 @@ enum BuildFlagKind { BuildFlag_InternalLLVMNoSROA, BuildFlag_InternalEnableRVO, BuildFlag_InternalGlobalEntityGraph, + BuildFlag_InternalShuffleGlobalEntities, BuildFlag_Sanitize, BuildFlag_LTO, @@ -795,6 +796,7 @@ gb_internal bool parse_build_flags(Array args) { add_flag(&build_flags, BuildFlag_InternalLLVMNoSROA, str_lit("internal-llvm-no-sroa"), BuildFlagParam_None, Command_all); add_flag(&build_flags, BuildFlag_InternalEnableRVO, str_lit("internal-enable-rvo"), BuildFlagParam_None, Command_all); add_flag(&build_flags, BuildFlag_InternalGlobalEntityGraph, str_lit("internal-global-entity-graph"), BuildFlagParam_None, Command__does_check); + add_flag(&build_flags, BuildFlag_InternalShuffleGlobalEntities, str_lit("internal-shuffle-global-entities"), BuildFlagParam_Integer, Command__does_check); add_flag(&build_flags, BuildFlag_Sanitize, str_lit("sanitize"), BuildFlagParam_String, Command__does_build, true); @@ -1846,6 +1848,10 @@ gb_internal bool parse_build_flags(Array args) { case BuildFlag_InternalGlobalEntityGraph: build_context.internal_global_entity_graph = true; break; + case BuildFlag_InternalShuffleGlobalEntities: + GB_ASSERT(value.kind == ExactValue_Integer); + build_context.internal_shuffle_global_entities = cast(u64)big_int_to_i64(&value.value_integer); + break; case BuildFlag_Sanitize: @@ -2542,7 +2548,7 @@ gb_internal void export_dependencies(Checker *c) { } array_add(&load_files, cache); } - array_sort(files, file_cache_sort_cmp); + array_sort(load_files, file_cache_sort_cmp); if (build_context.export_dependencies_format == DependenciesExportMake) { String exe_name = path_to_string(heap_allocator(), build_context.build_paths[BuildPath_Output]);