Merge checker_global_*.cpp into checker_global.cpp; base -internal-global-entity-graph on the scheduled groups

This commit is contained in:
gingerBill committed 2026-10-01 17:43:03 +01:00
1 parent 63bd49c88b
commit f7ee910f93
6 files changed
+1580 -1786

No files matched your search

+1 -4
View File
@@ -4574,7 +4574,6 @@ gb_internal DECL_ATTRIBUTE_PROC(asm_decl_attribute) {
return false;
}
#include "checker_global_graph.cpp"
#include "check_expr.cpp"
#include "check_builtin.cpp"
#include "check_type.cpp"
@@ -5339,7 +5338,6 @@ gb_internal void check_single_global_entity(Checker *c, Entity *e, DeclInfo *d)
check_entity_decl(ctx, e, d, nullptr);
}
#include "checker_global_groups.cpp"
gb_internal bool is_string_an_identifier(String s) {
isize offset = 0;
@@ -5996,7 +5994,7 @@ gb_internal void check_export_entities(Checker *c) {
thread_pool_wait();
}
#include "checker_global_when.cpp"
#include "checker_global.cpp"
gb_internal void check_import_entities(Checker *c) {
TEMPORARY_ALLOCATOR_GUARD();
@@ -7477,7 +7475,6 @@ gb_internal void check_parsed_files(Checker *c) {
if (build_context.internal_global_entity_graph) {
TIME_SECTION("print global entity graph");
print_global_entity_graph(c);
print_global_groups(&global_groups);
}
destroy_global_groups(&global_groups);
+9
View File
@@ -918,6 +918,15 @@ 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);
// -internal-global-entity-graph
struct GlobalEntityTimingFrame {
u64 start;
u64 saved_child_ticks;
bool active;
};
gb_internal GlobalEntityTimingFrame global_entity_timing_begin(Entity *e);
gb_internal void global_entity_timing_end(GlobalEntityTimingFrame const &f, Entity *e);
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);
File diff suppressed because it is too large. Load diff
-539
View File
@@ -1,539 +0,0 @@
// -internal-global-entity-graph: per-entity self time of global declaration checking,
// plus statistics of the dependency graph between global entities (DeclInfo::deps)
struct GlobalEntityTime {
u64 ticks;
bool in_global_loop;
};
struct GlobalEntityTimingFrame {
u64 start;
u64 saved_child_ticks;
bool active;
};
enum GlobalImportStagePart {
GlobalImportStage_Imports,
GlobalImportStage_Placeholders,
GlobalImportStage_DeclSources,
GlobalImportStage_TypeAliases,
GlobalImportStage_DelayedExprs,
GlobalImportStage_COUNT,
};
gb_global char const *global_import_stage_names[GlobalImportStage_COUNT] = {
"imports",
"'when' and 'foreign' placeholders",
"resolve 'when' and 'foreign' blocks",
"type alias correction",
"delayed expressions (#assert etc.)",
};
gb_global u64 global_import_stage_ticks[GlobalImportStage_COUNT];
gb_internal u64 global_import_stage_begin(void) {
return build_context.internal_global_entity_graph ? time_stamp_time_now() : 0;
}
gb_internal void global_import_stage_end(GlobalImportStagePart part, u64 start) {
if (build_context.internal_global_entity_graph) {
global_import_stage_ticks[part] += time_stamp_time_now() - start;
}
}
gb_global BlockingMutex global_entity_time_mutex;
gb_global PtrMap<Entity *, GlobalEntityTime> global_entity_times;
gb_thread_local u64 global_entity_child_ticks;
gb_internal GlobalEntityTimingFrame global_entity_timing_begin(Entity *e) {
GlobalEntityTimingFrame f = {};
if (!build_context.internal_global_entity_graph) {
return f;
}
if (e->scope == nullptr || (e->scope->flags & ScopeFlag_File) == 0) {
return f;
}
f.active = true;
f.saved_child_ticks = global_entity_child_ticks;
global_entity_child_ticks = 0;
f.start = time_stamp_time_now();
return f;
}
gb_internal void global_entity_timing_end(GlobalEntityTimingFrame const &f, Entity *e) {
if (!f.active) {
return;
}
u64 total = time_stamp_time_now() - f.start;
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);
MUTEX_GUARD(&global_entity_time_mutex);
GlobalEntityTime *found = map_get(&global_entity_times, e);
if (found) {
found->ticks += self;
} else {
map_set(&global_entity_times, e, GlobalEntityTime{self, in_global_loop});
}
}
// Iterative Tarjan; components are numbered so that every edge v->w has comp(w) <= comp(v)
gb_internal i32 global_graph_scc(i32 node_count, Array<i32> const &offsets, Array<i32> const &targets, Array<i32> *comp_of_) {
struct Frame {
i32 v;
i32 pos;
};
auto index = array_make<i32>(heap_allocator(), node_count);
auto low = array_make<i32>(heap_allocator(), node_count);
auto on_stack = array_make<bool>(heap_allocator(), node_count);
auto stack = array_make<i32>(heap_allocator(), 0, node_count);
auto frames = array_make<Frame>(heap_allocator(), 0, 64);
defer (array_free(&index));
defer (array_free(&low));
defer (array_free(&on_stack));
defer (array_free(&stack));
defer (array_free(&frames));
Array<i32> &comp_of = *comp_of_;
for (i32 v = 0; v < node_count; v++) {
index[v] = -1;
low[v] = 0;
on_stack[v] = false;
comp_of[v] = -1;
}
i32 counter = 0;
i32 comp_count = 0;
for (i32 root = 0; root < node_count; root++) {
if (index[root] >= 0) {
continue;
}
index[root] = low[root] = counter++;
array_add(&stack, root);
on_stack[root] = true;
array_add(&frames, Frame{root, 0});
while (frames.count > 0) {
Frame *top = &frames[frames.count-1];
i32 v = top->v;
if (offsets[v] + top->pos < offsets[v+1]) {
i32 w = targets[offsets[v] + top->pos];
top->pos += 1;
if (index[w] < 0) {
index[w] = low[w] = counter++;
array_add(&stack, w);
on_stack[w] = true;
array_add(&frames, Frame{w, 0});
} else if (on_stack[w]) {
low[v] = gb_min(low[v], index[w]);
}
continue;
}
if (low[v] == index[v]) {
for (;;) {
i32 w = array_pop(&stack);
on_stack[w] = false;
comp_of[w] = comp_count;
if (w == v) {
break;
}
}
comp_count += 1;
}
array_pop(&frames);
if (frames.count > 0) {
i32 u = frames[frames.count-1].v;
low[u] = gb_min(low[u], low[v]);
}
}
}
return comp_count;
}
struct GlobalGraphComp {
i32 start; // into the member order array
i32 count;
u64 ticks;
u64 path_ticks; // heaviest dependency chain ending at this component, inclusive
i32 path_len;
i32 path_prev;
bool has_self_edge;
};
struct GlobalGraphSortItem {
u64 key;
i32 id;
};
gb_internal GB_COMPARE_PROC(global_graph_sort_item_desc) {
GlobalGraphSortItem const *x = cast(GlobalGraphSortItem const *)a;
GlobalGraphSortItem const *y = cast(GlobalGraphSortItem const *)b;
if (x->key != y->key) {
return x->key > y->key ? -1 : +1;
}
return i32_cmp(x->id, y->id);
}
// Fills `comps` (indexed by component) and `order` (node indices grouped by component)
gb_internal void global_graph_condense(i32 node_count, Array<i32> const &offsets, Array<i32> const &targets, Array<u64> const &node_ticks,
Array<i32> const &comp_of, i32 comp_count, Array<GlobalGraphComp> *comps_, Array<i32> *order_) {
Array<GlobalGraphComp> &comps = *comps_;
Array<i32> &order = *order_;
for (i32 i = 0; i < comp_count; i++) {
comps[i] = {};
comps[i].path_prev = -1;
}
for (i32 v = 0; v < node_count; v++) {
GlobalGraphComp *g = &comps[comp_of[v]];
g->count += 1;
g->ticks += node_ticks[v];
}
i32 start = 0;
for (i32 i = 0; i < comp_count; i++) {
comps[i].start = start;
start += comps[i].count;
comps[i].count = 0;
}
for (i32 v = 0; v < node_count; v++) {
GlobalGraphComp *g = &comps[comp_of[v]];
order[g->start + g->count] = v;
g->count += 1;
}
for (i32 ci = 0; ci < comp_count; ci++) {
GlobalGraphComp *g = &comps[ci];
u64 best_ticks = 0;
i32 best_len = 0;
i32 best_prev = -1;
for (i32 k = 0; k < g->count; k++) {
i32 v = order[g->start + k];
for (i32 e = offsets[v]; e < offsets[v+1]; e++) {
i32 w = targets[e];
if (w == v) {
g->has_self_edge = true;
}
i32 cw = comp_of[w];
if (cw == ci) {
continue;
}
GB_ASSERT(cw < ci);
GlobalGraphComp *d = &comps[cw];
if (best_prev < 0 || d->path_ticks > best_ticks) {
best_ticks = d->path_ticks;
best_prev = cw;
}
best_len = gb_max(best_len, d->path_len);
}
}
g->path_ticks = g->ticks + best_ticks;
g->path_len = 1 + best_len;
g->path_prev = best_prev;
}
}
gb_internal f64 global_graph_ms(u64 ticks, u64 freq) {
return 1000.0 * cast(f64)ticks / cast(f64)freq;
}
gb_internal void global_graph_print_entity(Entity *e) {
String pkg = e->pkg ? e->pkg->name : str_lit("?");
String file = e->file ? filename_without_directory(e->file->fullpath) : str_lit("?");
gb_printf_err("%.*s.%.*s (%.*s:%d)", LIT(pkg), LIT(e->token.string), LIT(file), e->token.pos.line);
}
gb_internal void global_graph_print_comp(Array<Entity *> const &nodes, Array<i32> const &order, GlobalGraphComp const *g, u64 ticks, u64 freq, isize max_names) {
gb_printf_err(" %10.3f ms %7d entities ", global_graph_ms(ticks, freq), g->count);
for (i32 k = 0; k < g->count && k < max_names; k++) {
if (k > 0) {
gb_printf_err(", ");
}
global_graph_print_entity(nodes[order[g->start + k]]);
}
if (g->count > max_names) {
gb_printf_err(", ...");
}
gb_printf_err("\n");
}
gb_internal void print_global_entity_graph(Checker *c) {
u64 const freq = time_stamp__freq();
// Nodes
auto nodes = array_make<Entity *>(heap_allocator(), 0, c->info.entities.count);
defer (array_free(&nodes));
PtrMap<Entity *, i32> node_index = {};
map_init(&node_index, c->info.entities.count);
defer (map_destroy(&node_index));
for (Entity *e : c->info.entities) {
if (e->decl_info == nullptr || e->scope == nullptr || (e->scope->flags & ScopeFlag_File) == 0) {
continue;
}
if (map_get(&node_index, e) != nullptr) {
continue;
}
map_set(&node_index, e, cast(i32)nodes.count);
array_add(&nodes, e);
}
i32 node_count = cast(i32)nodes.count;
// Edges, from what each declaration actually used while being checked
auto offsets = array_make<i32>(heap_allocator(), node_count+1);
auto targets = array_make<i32>(heap_allocator(), 0, node_count*4);
defer (array_free(&offsets));
defer (array_free(&targets));
isize self_edges = 0;
for (i32 v = 0; v < node_count; v++) {
offsets[v] = cast(i32)targets.count;
DeclInfo *d = nodes[v]->decl_info;
FOR_PTR_SET(dep, d->deps) {
i32 *w = map_get(&node_index, dep);
if (w != nullptr) {
array_add(&targets, *w);
self_edges += *w == v;
}
}
}
offsets[node_count] = cast(i32)targets.count;
// Weights
auto node_ticks = array_make<u64>(heap_allocator(), node_count);
defer (array_free(&node_ticks));
u64 total_ticks = 0;
u64 when_ticks = 0;
isize untimed = 0;
mutex_lock(&global_entity_time_mutex);
for (i32 v = 0; v < node_count; v++) {
GlobalEntityTime *t = map_get(&global_entity_times, nodes[v]);
node_ticks[v] = t ? t->ticks : 0;
total_ticks += node_ticks[v];
if (t == nullptr) {
untimed += 1;
} else if (!t->in_global_loop) {
when_ticks += t->ticks;
}
}
mutex_unlock(&global_entity_time_mutex);
// Entity groups
auto comp_of = array_make<i32>(heap_allocator(), node_count);
defer (array_free(&comp_of));
i32 comp_count = global_graph_scc(node_count, offsets, targets, &comp_of);
auto comps = array_make<GlobalGraphComp>(heap_allocator(), comp_count);
auto order = array_make<i32>(heap_allocator(), node_count);
defer (array_free(&comps));
defer (array_free(&order));
global_graph_condense(node_count, offsets, targets, node_ticks, comp_of, comp_count, &comps, &order);
i32 critical = -1;
i32 largest = -1;
isize cyclic_comps = 0;
for (i32 ci = 0; ci < comp_count; ci++) {
GlobalGraphComp *g = &comps[ci];
if (critical < 0 || g->path_ticks > comps[critical].path_ticks) {
critical = ci;
}
if (largest < 0 || g->count > comps[largest].count) {
largest = ci;
}
cyclic_comps += g->count > 1 || g->has_self_edge;
}
// Packages, weighted by the entities they own
PtrMap<AstPackage *, i32> pkg_index = {};
map_init(&pkg_index, c->info.packages.count);
defer (map_destroy(&pkg_index));
auto pkgs = array_make<AstPackage *>(heap_allocator(), 0, c->info.packages.count);
defer (array_free(&pkgs));
auto node_pkg = array_make<i32>(heap_allocator(), node_count);
defer (array_free(&node_pkg));
for (i32 v = 0; v < node_count; v++) {
AstPackage *p = nodes[v]->pkg;
i32 *found = map_get(&pkg_index, p);
if (found == nullptr) {
map_set(&pkg_index, p, cast(i32)pkgs.count);
node_pkg[v] = cast(i32)pkgs.count;
array_add(&pkgs, p);
} else {
node_pkg[v] = *found;
}
}
i32 pkg_count = cast(i32)pkgs.count;
auto pkg_ticks = array_make<u64>(heap_allocator(), pkg_count);
auto pkg_entities = array_make<i32>(heap_allocator(), pkg_count);
defer (array_free(&pkg_ticks));
defer (array_free(&pkg_entities));
for (i32 p = 0; p < pkg_count; p++) {
pkg_ticks[p] = 0;
pkg_entities[p] = 0;
}
for (i32 v = 0; v < node_count; v++) {
pkg_ticks[node_pkg[v]] += node_ticks[v];
pkg_entities[node_pkg[v]] += 1;
}
auto pkg_offsets = array_make<i32>(heap_allocator(), pkg_count+1);
auto pkg_targets = array_make<i32>(heap_allocator(), 0, pkg_count*4);
defer (array_free(&pkg_offsets));
defer (array_free(&pkg_targets));
{
auto pkg_edges = array_make<Array<i32>>(heap_allocator(), pkg_count);
PtrMap<u64, bool> seen = {};
map_init(&seen);
for (i32 p = 0; p < pkg_count; p++) {
pkg_edges[p] = array_make<i32>(heap_allocator());
}
for (i32 v = 0; v < node_count; v++) {
for (i32 e = offsets[v]; e < offsets[v+1]; e++) {
i32 pv = node_pkg[v];
i32 pw = node_pkg[targets[e]];
if (pv == pw) {
continue;
}
u64 key = (cast(u64)(pv+1) << 32) | cast(u64)(pw+1);
if (map_get(&seen, key) == nullptr) {
map_set(&seen, key, true);
array_add(&pkg_edges[pv], pw);
}
}
}
for (i32 p = 0; p < pkg_count; p++) {
pkg_offsets[p] = cast(i32)pkg_targets.count;
for (i32 w : pkg_edges[p]) {
array_add(&pkg_targets, w);
}
array_free(&pkg_edges[p]);
}
pkg_offsets[pkg_count] = cast(i32)pkg_targets.count;
array_free(&pkg_edges);
map_destroy(&seen);
}
auto pkg_comp_of = array_make<i32>(heap_allocator(), pkg_count);
defer (array_free(&pkg_comp_of));
i32 pkg_comp_count = global_graph_scc(pkg_count, pkg_offsets, pkg_targets, &pkg_comp_of);
auto pkg_comps = array_make<GlobalGraphComp>(heap_allocator(), pkg_comp_count);
auto pkg_order = array_make<i32>(heap_allocator(), pkg_count);
defer (array_free(&pkg_comps));
defer (array_free(&pkg_order));
global_graph_condense(pkg_count, pkg_offsets, pkg_targets, pkg_ticks, pkg_comp_of, pkg_comp_count, &pkg_comps, &pkg_order);
u64 pkg_critical_ticks = 0;
isize pkg_cyclic = 0;
for (i32 ci = 0; ci < pkg_comp_count; ci++) {
pkg_critical_ticks = gb_max(pkg_critical_ticks, pkg_comps[ci].path_ticks);
pkg_cyclic += pkg_comps[ci].count > 1;
}
// Report
f64 total_ms = global_graph_ms(total_ticks, freq);
f64 critical_ms = critical >= 0 ? global_graph_ms(comps[critical].path_ticks, freq) : 0;
f64 pkg_critical_ms = global_graph_ms(pkg_critical_ticks, freq);
gb_printf_err("Global entity graph\n");
gb_printf_err(" entities: %td (%td not timed), dependency edges: %td (%td self edges)\n", nodes.count, untimed, targets.count, self_edges);
gb_printf_err(" self time: %.3f ms total; %.3f ms during 'when' resolution, %.3f ms in the global loop\n",
total_ms, global_graph_ms(when_ticks, freq), global_graph_ms(total_ticks - when_ticks, freq));
gb_printf_err(" groups: %d (%td with a cycle), largest has %d entities\n",
comp_count, cyclic_comps, largest >= 0 ? comps[largest].count : 0);
gb_printf_err(" critical path (entity groups): %.3f ms over %d groups -> at most %.2fx speedup\n",
critical_ms, critical >= 0 ? comps[critical].path_len : 0, critical_ms > 0 ? total_ms/critical_ms : 0.0);
gb_printf_err(" critical path (packages): %.3f ms -> at most %.2fx speedup (%d packages, %td package cycles)\n",
pkg_critical_ms, pkg_critical_ms > 0 ? total_ms/pkg_critical_ms : 0.0, pkg_count, pkg_cyclic);
gb_printf_err(" check_import_entities (sequential, includes entity checks it triggers):\n");
for (isize i = 0; i < GlobalImportStage_COUNT; i++) {
gb_printf_err(" %10.3f ms %s\n", global_graph_ms(global_import_stage_ticks[i], freq), global_import_stage_names[i]);
}
{
i32 const BUCKET_COUNT = 10;
i32 const bucket_max[BUCKET_COUNT] = {1, 2, 4, 8, 16, 64, 256, 1024, 4096, 0x7fffffff};
char const *bucket_name[BUCKET_COUNT] = {"1", "2", "3-4", "5-8", "9-16", "17-64", "65-256", "257-1024", "1025-4096", ">4096"};
isize bucket_groups[BUCKET_COUNT] = {};
isize bucket_entities[BUCKET_COUNT] = {};
u64 bucket_ticks[BUCKET_COUNT] = {};
for (i32 ci = 0; ci < comp_count; ci++) {
GlobalGraphComp *g = &comps[ci];
for (i32 b = 0; b < BUCKET_COUNT; b++) {
if (g->count <= bucket_max[b]) {
bucket_groups[b] += 1;
bucket_entities[b] += g->count;
bucket_ticks[b] += g->ticks;
break;
}
}
}
gb_printf_err(" group sizes:\n");
gb_printf_err(" %10s %9s %10s %12s\n", "size", "groups", "entities", "self time");
for (i32 b = 0; b < BUCKET_COUNT; b++) {
if (bucket_groups[b] == 0) {
continue;
}
gb_printf_err(" %10s %9td %10td %9.3f ms\n", bucket_name[b], bucket_groups[b], bucket_entities[b], global_graph_ms(bucket_ticks[b], freq));
}
}
auto items = array_make<GlobalGraphSortItem>(heap_allocator(), 0, gb_max(comp_count, pkg_count));
defer (array_free(&items));
isize const TOP = 10;
isize const MAX_NAMES = 4;
array_clear(&items);
for (i32 ci = 0; ci < comp_count; ci++) {
array_add(&items, GlobalGraphSortItem{comps[ci].ticks, ci});
}
array_sort(items, global_graph_sort_item_desc);
gb_printf_err(" slowest groups:\n");
for (isize i = 0; i < items.count && i < TOP; i++) {
GlobalGraphComp *g = &comps[items[i].id];
global_graph_print_comp(nodes, order, g, g->ticks, freq, MAX_NAMES);
}
array_clear(&items);
for (i32 ci = 0; ci < comp_count; ci++) {
if (comps[ci].count > 1) {
array_add(&items, GlobalGraphSortItem{cast(u64)comps[ci].count, ci});
}
}
array_sort(items, global_graph_sort_item_desc);
gb_printf_err(" largest groups:\n");
for (isize i = 0; i < items.count && i < TOP; i++) {
GlobalGraphComp *g = &comps[items[i].id];
global_graph_print_comp(nodes, order, g, g->ticks, freq, MAX_NAMES);
}
gb_printf_err(" critical path, last group first:\n");
{
isize printed = 0;
for (i32 ci = critical; ci >= 0; ci = comps[ci].path_prev) {
if (printed == 30) {
gb_printf_err(" ... %d more groups\n", comps[ci].path_len);
break;
}
global_graph_print_comp(nodes, order, &comps[ci], comps[ci].ticks, freq, 2);
printed += 1;
}
}
array_clear(&items);
for (i32 p = 0; p < pkg_count; p++) {
array_add(&items, GlobalGraphSortItem{pkg_ticks[p], p});
}
array_sort(items, global_graph_sort_item_desc);
gb_printf_err(" slowest packages:\n");
for (isize i = 0; i < items.count && i < TOP; i++) {
i32 p = items[i].id;
AstPackage *pkg = pkgs[p];
String name = pkg ? pkg->name : str_lit("?");
gb_printf_err(" %10.3f ms %7d entities %.*s\n", global_graph_ms(pkg_ticks[p], freq), pkg_entities[p], LIT(name));
}
}
-769
View File
@@ -1,769 +0,0 @@
// Global entities are checked in groups: the strongly connected components of a dependency graph built
// from syntax, in dependency order. A group only names entities of its own or of finished groups, which
// `-internal-check-global-edges` verifies
struct GlobalGroup {
i32 start; // into `GlobalGroupGraph::members`
i32 count;
bool done;
};
struct GlobalGroupGraph {
Array<Entity *> nodes;
PtrMap<Entity *, i32> node_of;
Array<i32> offsets; // node -> the nodes it names, as `targets[offsets[v]..offsets[v+1]]`
Array<i32> targets;
Array<i32> group_of;
Array<GlobalGroup> groups; // every dependency of a group has a lower index
Array<i32> members; // nodes, by group, in source order
bool active;
i32 current_group;
Entity *current_entity;
isize missing_edges;
};
gb_global GlobalGroupGraph global_groups;
struct GlobalGraphWalk {
Scope *scope;
Array<Entity *> *refs;
};
gb_internal void global_graph_walk(GlobalGraphWalk *w, Ast *node);
gb_internal void global_graph_walk_slice(GlobalGraphWalk *w, Slice<Ast *> const &nodes) {
for (Ast *node : nodes) {
global_graph_walk(w, node);
}
}
gb_internal void global_graph_add_ref(GlobalGraphWalk *w, Entity *e) {
if (e != nullptr) {
array_add(w->refs, e);
}
}
// NOTE: names bound within the expression (parameters, fields, '$T') are also looked up globally, which at
// worst adds an edge; only procedure bodies are skipped, as they are checked after this stage
gb_internal void global_graph_walk(GlobalGraphWalk *w, Ast *node) {
if (node == nullptr) {
return;
}
switch (node->kind) {
case Ast_Ident:
global_graph_add_ref(w, scope_lookup(w->scope, node->Ident.interned, node->Ident.hash));
break;
case Ast_SelectorExpr: {
Ast *expr = node->SelectorExpr.expr;
Ast *selector = node->SelectorExpr.selector;
if (expr != nullptr && expr->kind == Ast_Ident) {
Entity *e = scope_lookup(w->scope, expr->Ident.interned, expr->Ident.hash);
if (e != nullptr && e->kind == Entity_ImportName && selector != nullptr && selector->kind == Ast_Ident) {
global_graph_add_ref(w, scope_lookup_current(e->ImportName.scope, selector->Ident.interned, selector->Ident.hash));
} else {
global_graph_add_ref(w, e);
}
} else {
global_graph_walk(w, expr);
}
} break;
case Ast_PolyType:
global_graph_walk(w, node->PolyType.specialization);
break;
case Ast_Ellipsis:
global_graph_walk(w, node->Ellipsis.expr);
break;
case Ast_ProcGroup:
global_graph_walk_slice(w, node->ProcGroup.args);
break;
case Ast_AsmGroup:
global_graph_walk_slice(w, node->AsmGroup.args);
break;
case Ast_ProcLit:
global_graph_walk(w, node->ProcLit.type);
global_graph_walk_slice(w, node->ProcLit.where_clauses);
break;
case Ast_CompoundLit:
global_graph_walk(w, node->CompoundLit.type);
global_graph_walk_slice(w, node->CompoundLit.elems);
global_graph_walk(w, node->CompoundLit.tag);
break;
case Ast_TagExpr:
global_graph_walk(w, node->TagExpr.expr);
break;
case Ast_UnaryExpr:
global_graph_walk(w, node->UnaryExpr.expr);
break;
case Ast_BinaryExpr:
global_graph_walk(w, node->BinaryExpr.left);
global_graph_walk(w, node->BinaryExpr.right);
break;
case Ast_ParenExpr:
global_graph_walk(w, node->ParenExpr.expr);
break;
case Ast_SelectorCallExpr:
global_graph_walk(w, node->SelectorCallExpr.expr);
global_graph_walk(w, node->SelectorCallExpr.call);
break;
case Ast_IndexExpr:
global_graph_walk(w, node->IndexExpr.expr);
global_graph_walk(w, node->IndexExpr.index);
break;
case Ast_MatrixIndexExpr:
global_graph_walk(w, node->MatrixIndexExpr.expr);
global_graph_walk(w, node->MatrixIndexExpr.row_index);
global_graph_walk(w, node->MatrixIndexExpr.column_index);
break;
case Ast_DerefExpr:
global_graph_walk(w, node->DerefExpr.expr);
break;
case Ast_SliceExpr:
global_graph_walk(w, node->SliceExpr.expr);
global_graph_walk(w, node->SliceExpr.low);
global_graph_walk(w, node->SliceExpr.high);
break;
case Ast_CallExpr:
global_graph_walk(w, node->CallExpr.proc);
global_graph_walk_slice(w, node->CallExpr.args);
break;
case Ast_FieldValue:
global_graph_walk(w, node->FieldValue.field);
global_graph_walk(w, node->FieldValue.value);
break;
case Ast_EnumFieldValue:
global_graph_walk(w, node->EnumFieldValue.value);
break;
case Ast_TernaryIfExpr:
global_graph_walk(w, node->TernaryIfExpr.x);
global_graph_walk(w, node->TernaryIfExpr.cond);
global_graph_walk(w, node->TernaryIfExpr.y);
break;
case Ast_TernaryWhenExpr:
global_graph_walk(w, node->TernaryWhenExpr.x);
global_graph_walk(w, node->TernaryWhenExpr.cond);
global_graph_walk(w, node->TernaryWhenExpr.y);
break;
case Ast_OrElseExpr:
global_graph_walk(w, node->OrElseExpr.x);
global_graph_walk(w, node->OrElseExpr.y);
break;
case Ast_OrReturnExpr:
global_graph_walk(w, node->OrReturnExpr.expr);
break;
case Ast_OrBranchExpr:
global_graph_walk(w, node->OrBranchExpr.expr);
break;
case Ast_TypeAssertion:
global_graph_walk(w, node->TypeAssertion.expr);
global_graph_walk(w, node->TypeAssertion.type);
break;
case Ast_TypeCast:
global_graph_walk(w, node->TypeCast.type);
global_graph_walk(w, node->TypeCast.expr);
break;
case Ast_AutoCast:
global_graph_walk(w, node->AutoCast.expr);
break;
case Ast_Field:
global_graph_walk(w, node->Field.type);
global_graph_walk(w, node->Field.default_value);
break;
case Ast_BitFieldField:
global_graph_walk(w, node->BitFieldField.type);
global_graph_walk(w, node->BitFieldField.bit_size);
break;
case Ast_FieldList:
global_graph_walk_slice(w, node->FieldList.list);
break;
case Ast_TypeidType:
global_graph_walk(w, node->TypeidType.specialization);
break;
case Ast_HelperType:
global_graph_walk(w, node->HelperType.type);
break;
case Ast_DistinctType:
global_graph_walk(w, node->DistinctType.type);
break;
case Ast_ProcType:
global_graph_walk(w, node->ProcType.params);
global_graph_walk(w, node->ProcType.results);
break;
case Ast_RelativeType:
global_graph_walk(w, node->RelativeType.tag);
global_graph_walk(w, node->RelativeType.type);
break;
case Ast_PointerType:
global_graph_walk(w, node->PointerType.type);
global_graph_walk(w, node->PointerType.tag);
break;
case Ast_MultiPointerType:
global_graph_walk(w, node->MultiPointerType.type);
break;
case Ast_ArrayType:
global_graph_walk(w, node->ArrayType.count);
global_graph_walk(w, node->ArrayType.elem);
global_graph_walk(w, node->ArrayType.tag);
break;
case Ast_DynamicArrayType:
global_graph_walk(w, node->DynamicArrayType.elem);
global_graph_walk(w, node->DynamicArrayType.tag);
break;
case Ast_FixedCapacityDynamicArrayType:
global_graph_walk(w, node->FixedCapacityDynamicArrayType.elem);
global_graph_walk(w, node->FixedCapacityDynamicArrayType.capacity);
global_graph_walk(w, node->FixedCapacityDynamicArrayType.tag);
break;
case Ast_StructType:
global_graph_walk_slice(w, node->StructType.fields);
global_graph_walk(w, node->StructType.polymorphic_params);
global_graph_walk(w, node->StructType.align);
global_graph_walk(w, node->StructType.min_field_align);
global_graph_walk(w, node->StructType.max_field_align);
global_graph_walk_slice(w, node->StructType.where_clauses);
break;
case Ast_UnionType:
global_graph_walk_slice(w, node->UnionType.variants);
global_graph_walk(w, node->UnionType.polymorphic_params);
global_graph_walk_slice(w, node->UnionType.where_clauses);
break;
case Ast_EnumType:
global_graph_walk(w, node->EnumType.base_type);
for (Ast *field : node->EnumType.fields) {
if (field->kind == Ast_EnumFieldValue) {
global_graph_walk(w, field->EnumFieldValue.value);
}
}
break;
case Ast_BitSetType:
global_graph_walk(w, node->BitSetType.elem);
global_graph_walk(w, node->BitSetType.underlying);
break;
case Ast_BitFieldType:
global_graph_walk(w, node->BitFieldType.backing_type);
global_graph_walk_slice(w, node->BitFieldType.fields);
break;
case Ast_MapType:
global_graph_walk(w, node->MapType.count);
global_graph_walk(w, node->MapType.key);
global_graph_walk(w, node->MapType.value);
break;
case Ast_MatrixType:
global_graph_walk(w, node->MatrixType.row_count);
global_graph_walk(w, node->MatrixType.column_count);
global_graph_walk(w, node->MatrixType.elem);
break;
case Ast_AsmTemplate:
global_graph_walk(w, node->AsmTemplate.signature);
global_graph_walk_slice(w, node->AsmTemplate.specs);
global_graph_walk_slice(w, node->AsmTemplate.clobbers);
global_graph_walk_slice(w, node->AsmTemplate.instructions);
break;
case Ast_AsmSpec:
global_graph_walk(w, node->AsmSpec.type);
global_graph_walk(w, node->AsmSpec.value);
for (Ast *d : node->AsmSpec.directives) {
global_graph_walk(w, d);
}
break;
case Ast_AsmClobber:
global_graph_walk(w, node->AsmClobber.value);
break;
case Ast_AsmInstruction:
global_graph_walk_slice(w, node->AsmInstruction.operands);
break;
case Ast_AsmMemoryTerm:
global_graph_walk(w, node->AsmMemoryTerm.operand);
global_graph_walk(w, node->AsmMemoryTerm.scale);
break;
case Ast_AsmMemoryOperand:
global_graph_walk(w, node->AsmMemoryOperand.segment_override);
global_graph_walk_slice(w, node->AsmMemoryOperand.terms);
global_graph_walk(w, node->AsmMemoryOperand.type);
break;
case Ast_AsmRegisterGroup:
for (Ast *r : node->AsmRegisterGroup.registers) {
global_graph_walk(w, r);
}
global_graph_walk(w, node->AsmRegisterGroup.type);
break;
case Ast_AsmDirective:
global_graph_walk_slice(w, node->AsmDirective.operands);
break;
}
}
gb_internal void global_graph_walk_entity(GlobalGraphWalk *w, Entity *e, DeclInfo *d) {
w->scope = d->scope;
global_graph_walk(w, d->type_expr);
global_graph_walk(w, d->init_expr);
for (Ast *attr : d->attributes) {
if (attr->kind != Ast_Attribute) {
continue;
}
for (Ast *elem : attr->Attribute.elems) {
if (elem->kind == Ast_FieldValue) {
global_graph_walk(w, elem->FieldValue.value);
}
}
}
if (e->kind == Entity_Procedure) {
global_graph_walk(w, e->Procedure.foreign_library_ident);
} else if (e->kind == Entity_Variable) {
global_graph_walk(w, e->Variable.foreign_library_ident);
}
}
gb_internal bool is_global_graph_node(Entity *e) {
if (e->state == EntityState_Resolved) {
return false;
}
DeclInfo *d = e->decl_info;
if (d == nullptr || e->scope == nullptr || d->scope != e->scope || (e->scope->flags & ScopeFlag_File) == 0) {
return false;
}
switch (e->kind) {
case Entity_Constant:
case Entity_TypeName:
case Entity_Variable:
case Entity_Procedure:
case Entity_ProcGroup:
case Entity_AsmTemplate:
return true;
}
return false;
}
gb_internal i32 global_graph_add_node(GlobalGroupGraph *g, Entity *e) {
i32 *found = map_get(&g->node_of, e);
if (found != nullptr) {
return *found;
}
i32 v = cast(i32)g->nodes.count;
map_set(&g->node_of, e, v);
array_add(&g->nodes, e);
return v;
}
gb_internal u64 global_group_random(u64 *state) {
*state = *state*6364136223846793005ull + 1442695040888963407ull;
return *state >> 33;
}
// The nodes `[lo, hi)` walked on one thread; a name that is not a node yet is kept as an entity in `refs`
struct GlobalGraphWalkChunk {
GlobalGroupGraph *g;
i32 lo, hi;
Array<i32> targets;
Array<i32> target_ends; // per node
Array<Entity *> refs;
Array<i32> ref_ends; // per node
};
gb_internal WORKER_TASK_PROC(global_graph_walk_worker) {
GlobalGraphWalkChunk *chunk = cast(GlobalGraphWalkChunk *)data;
GlobalGroupGraph *g = chunk->g;
auto refs = array_make<Entity *>(heap_allocator(), 0, 64);
defer (array_free(&refs));
GlobalGraphWalk w = {};
w.refs = &refs;
for (i32 v = chunk->lo; v < chunk->hi; v++) {
Entity *e = g->nodes[v];
array_clear(&refs);
global_graph_walk_entity(&w, e, e->decl_info);
for (Entity *r : refs) {
i32 *found = map_get(&g->node_of, r);
if (found != nullptr) {
array_add(&chunk->targets, *found);
} else if (r->flags & EntityFlag_Lazy) {
array_add(&chunk->refs, r);
}
}
array_add(&chunk->target_ends, cast(i32)chunk->targets.count);
array_add(&chunk->ref_ends, cast(i32)chunk->refs.count);
}
return 0;
}
gb_internal void build_global_groups(Checker *c, GlobalGroupGraph *g) {
array_init(&g->nodes, heap_allocator(), 0, c->info.entities.count);
map_init(&g->node_of, c->info.entities.count);
for (Entity *e : c->info.entities) {
if ((e->flags & EntityFlag_Lazy) == 0 && is_global_graph_node(e)) {
global_graph_add_node(g, e);
}
}
// NOTE: walked in parallel, as nothing writes to the scopes now
i32 const CHUNK_SIZE = 64;
i32 initial_count = cast(i32)g->nodes.count;
auto chunks = array_make<GlobalGraphWalkChunk>(heap_allocator(), (initial_count + CHUNK_SIZE-1)/CHUNK_SIZE);
defer (array_free(&chunks));
for (isize i = 0; i < chunks.count; i++) {
GlobalGraphWalkChunk *chunk = &chunks[i];
*chunk = {};
chunk->g = g;
chunk->lo = cast(i32)(i*CHUNK_SIZE);
chunk->hi = gb_min(chunk->lo + CHUNK_SIZE, initial_count);
array_init(&chunk->targets, heap_allocator(), 0, 4*CHUNK_SIZE);
array_init(&chunk->target_ends, heap_allocator(), 0, CHUNK_SIZE);
array_init(&chunk->refs, heap_allocator(), 0);
array_init(&chunk->ref_ends, heap_allocator(), 0, CHUNK_SIZE);
thread_pool_add_task(global_graph_walk_worker, chunk);
}
thread_pool_wait();
auto edge_from = array_make<i32>(heap_allocator(), 0, 4*g->nodes.count);
auto edge_to = array_make<i32>(heap_allocator(), 0, 4*g->nodes.count);
auto refs = array_make<Entity *>(heap_allocator(), 0, 64);
defer (array_free(&edge_from));
defer (array_free(&edge_to));
defer (array_free(&refs));
auto add_ref = [&](i32 v, Entity *r) {
i32 *found = map_get(&g->node_of, r);
if (found != nullptr) {
array_add(&edge_from, v);
array_add(&edge_to, *found);
} else if ((r->flags & EntityFlag_Lazy) && is_global_graph_node(r)) {
// NOTE: a lazy entity becomes a node once a node names it
array_add(&edge_from, v);
array_add(&edge_to, global_graph_add_node(g, r));
}
};
GlobalGraphWalk w = {};
w.refs = &refs;
i32 first_of_decl = -1;
for (i32 v = 0; v < g->nodes.count; v++) {
Entity *e = g->nodes[v];
DeclInfo *d = e->decl_info;
// NOTE: entities sharing one declaration share its AST, e.g. `a, b: struct{x: int}`, so they share a group;
// in source order they are adjacent, and lazy ones are only checked under `lazy_mutex`
if (first_of_decl >= 0 && d->decl_node != nullptr && g->nodes[first_of_decl]->decl_info->decl_node == d->decl_node) {
array_add(&edge_from, v);
array_add(&edge_to, first_of_decl);
array_add(&edge_from, first_of_decl);
array_add(&edge_to, v);
} else {
first_of_decl = v;
}
if (v < initial_count) {
GlobalGraphWalkChunk *chunk = &chunks[v / CHUNK_SIZE];
i32 k = v - chunk->lo;
for (i32 i = k > 0 ? chunk->target_ends[k-1] : 0; i < chunk->target_ends[k]; i++) {
array_add(&edge_from, v);
array_add(&edge_to, chunk->targets[i]);
}
for (i32 i = k > 0 ? chunk->ref_ends[k-1] : 0; i < chunk->ref_ends[k]; i++) {
add_ref(v, chunk->refs[i]);
}
} else {
array_clear(&refs);
global_graph_walk_entity(&w, e, d);
for (Entity *r : refs) {
add_ref(v, r);
}
}
}
for (GlobalGraphWalkChunk &chunk : chunks) {
array_free(&chunk.targets);
array_free(&chunk.target_ends);
array_free(&chunk.refs);
array_free(&chunk.ref_ends);
}
i32 node_count = cast(i32)g->nodes.count;
array_init(&g->offsets, heap_allocator(), node_count+1);
array_init(&g->targets, heap_allocator(), edge_to.count);
for (i32 v = 0; v <= node_count; v++) {
g->offsets[v] = 0;
}
for (i32 from : edge_from) {
g->offsets[from+1] += 1;
}
for (i32 v = 0; v < node_count; v++) {
g->offsets[v+1] += g->offsets[v];
}
{
auto fill = array_clone(heap_allocator(), g->offsets);
defer (array_free(&fill));
for (isize i = 0; i < edge_from.count; i++) {
g->targets[fill[edge_from[i]]++] = edge_to[i];
}
}
array_init(&g->group_of, heap_allocator(), node_count);
i32 group_count = global_graph_scc(node_count, g->offsets, g->targets, &g->group_of);
array_init(&g->groups, heap_allocator(), group_count);
array_init(&g->members, heap_allocator(), node_count);
for (i32 gi = 0; gi < group_count; gi++) {
g->groups[gi] = {};
}
for (i32 v = 0; v < node_count; v++) {
g->groups[g->group_of[v]].count += 1;
}
i32 start = 0;
for (i32 gi = 0; gi < group_count; gi++) {
g->groups[gi].start = start;
start += g->groups[gi].count;
g->groups[gi].count = 0;
}
// NOTE: in source order, as `c->info.entities` is sorted; lazy nodes come last, but are not checked here
for (i32 v = 0; v < node_count; v++) {
GlobalGroup *group = &g->groups[g->group_of[v]];
g->members[group->start + group->count++] = v;
}
}
gb_internal void global_group_print_entity(Entity *e) {
if (e == nullptr) {
gb_printf_err("?");
return;
}
global_graph_print_entity(e);
}
// Called when `e` starts being checked: it must be in the current group or a finished one
gb_internal void global_group_check_edge(CheckerContext *ctx, Entity *e) {
GlobalGroupGraph *g = &global_groups;
if (!g->active) {
return;
}
i32 *v = map_get(&g->node_of, e);
if (v == nullptr) {
if (!is_global_graph_node(e)) {
return;
}
} else {
i32 gi = g->group_of[*v];
if (gi == g->current_group || g->groups[gi].done) {
return;
}
}
g->missing_edges += 1;
if (build_context.internal_check_global_edges) {
Entity *by = ctx->decl ? ctx->decl->entity.load() : nullptr;
gb_printf_err("Missing global dependency: ");
global_group_print_entity(by);
gb_printf_err(" needs ");
global_group_print_entity(e);
gb_printf_err(v == nullptr ? ", which is not in the graph" : "");
if (g->current_entity != by) {
gb_printf_err(", while checking ");
global_group_print_entity(g->current_entity);
}
gb_printf_err("\n");
}
}
// 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
gb_internal void check_global_group(Checker *c, GlobalGroupGraph *g, i32 gi) {
GlobalGroup *group = &g->groups[gi];
i32 *members = g->members.data + group->start;
g->current_group = gi;
for (i32 k = 0; k < group->count; k++) {
Entity *e = g->nodes[members[k]];
if (e->flags & EntityFlag_Lazy) {
// NOTE: only checked when something uses it; the group orders it after what it names
continue;
}
g->current_entity = e;
GlobalEntityTimingFrame timing_frame = global_entity_timing_begin(e);
check_single_global_entity(c, e, e->decl_info);
if (e->type != nullptr && is_type_typed(e->type)) {
for (Type *t = nullptr; mpsc_dequeue(&c->soa_types_to_complete, &t); /**/) {
complete_soa_type(c, t, false);
}
(void)type_size_of(e->type);
(void)type_align_of(e->type);
}
global_entity_timing_end(timing_frame, e);
}
group->done = true;
g->current_group = -1;
g->current_entity = nullptr;
}
// Groups in dependency order; with `-internal-shuffle-global-entities`, a random one of the groups whose
// dependencies are done, as a parallel checker might
gb_internal void check_global_groups(Checker *c, GlobalGroupGraph *g) {
i32 group_count = cast(i32)g->groups.count;
u64 seed = build_context.internal_shuffle_global_entities;
if (seed == 0) {
for (i32 gi = 0; gi < group_count; gi++) {
check_global_group(c, g, gi);
}
return;
}
auto dependents = array_make<Array<i32> >(heap_allocator(), group_count);
auto dep_count = array_make<i32> (heap_allocator(), group_count);
auto seen = array_make<i32> (heap_allocator(), group_count);
auto ready = array_make<i32> (heap_allocator(), 0, group_count);
defer ({
for (auto &d : dependents) {
array_free(&d);
}
array_free(&dependents);
});
defer (array_free(&dep_count));
defer (array_free(&seen));
defer (array_free(&ready));
for (i32 gi = 0; gi < group_count; gi++) {
dep_count[gi] = 0;
dependents[gi] = {};
seen[gi] = -1;
}
for (i32 gi = 0; gi < group_count; gi++) {
GlobalGroup const &group = g->groups[gi];
for (i32 k = 0; k < group.count; k++) {
i32 v = g->members[group.start + k];
for (i32 i = g->offsets[v]; i < g->offsets[v+1]; i++) {
i32 dep = g->group_of[g->targets[i]];
if (dep != gi && seen[dep] != gi) {
seen[dep] = gi;
dep_count[gi] += 1;
if (dependents[dep].allocator.proc == nullptr) {
array_init(&dependents[dep], heap_allocator());
}
array_add(&dependents[dep], gi);
}
}
}
if (dep_count[gi] == 0) {
array_add(&ready, gi);
}
}
u64 state = seed;
isize checked = 0;
while (ready.count > 0) {
isize i = cast(isize)(global_group_random(&state) % cast(u64)ready.count);
i32 gi = ready[i];
ready[i] = ready[ready.count-1];
array_pop(&ready);
check_global_group(c, g, gi);
checked += 1;
for (i32 next : dependents[gi]) {
if (--dep_count[next] == 0) {
array_add(&ready, next);
}
}
}
GB_ASSERT(checked == group_count);
}
gb_internal void destroy_global_groups(GlobalGroupGraph *g) {
array_free(&g->nodes);
map_destroy(&g->node_of);
array_free(&g->offsets);
array_free(&g->targets);
array_free(&g->group_of);
array_free(&g->groups);
array_free(&g->members);
}
// -internal-global-entity-graph: the graph the groups come from, weighted by the measured self times
gb_internal void print_global_groups(GlobalGroupGraph *g) {
u64 const freq = time_stamp__freq();
i32 group_count = cast(i32)g->groups.count;
auto ticks = array_make<u64>(heap_allocator(), group_count);
auto path = array_make<u64>(heap_allocator(), group_count);
auto seen = array_make<i32>(heap_allocator(), group_count);
defer (array_free(&ticks));
defer (array_free(&path));
defer (array_free(&seen));
u64 total = 0;
i32 largest = 0;
isize cyclic = 0;
mutex_lock(&global_entity_time_mutex);
for (i32 gi = 0; gi < group_count; gi++) {
GlobalGroup const &group = g->groups[gi];
ticks[gi] = 0;
seen[gi] = -1;
for (i32 k = 0; k < group.count; k++) {
GlobalEntityTime *t = map_get(&global_entity_times, g->nodes[g->members[group.start + k]]);
ticks[gi] += t ? t->ticks : 0;
}
total += ticks[gi];
largest = gb_max(largest, group.count);
cyclic += group.count > 1;
}
mutex_unlock(&global_entity_time_mutex);
u64 critical = 0;
for (i32 gi = 0; gi < group_count; gi++) {
GlobalGroup const &group = g->groups[gi];
u64 longest = 0;
for (i32 k = 0; k < group.count; k++) {
i32 v = g->members[group.start + k];
for (i32 i = g->offsets[v]; i < g->offsets[v+1]; i++) {
i32 dep = g->group_of[g->targets[i]];
if (dep != gi && seen[dep] != gi) {
seen[dep] = gi;
longest = gb_max(longest, path[dep]);
}
}
}
path[gi] = ticks[gi] + longest;
critical = gb_max(critical, path[gi]);
}
f64 total_ms = global_graph_ms(total, freq);
f64 critical_ms = global_graph_ms(critical, freq);
gb_printf_err("Global groups (syntactic graph, as scheduled)\n");
gb_printf_err(" nodes: %td, edges: %td, groups: %d (%td with a cycle), largest has %d entities\n",
g->nodes.count, g->targets.count, group_count, cyclic, largest);
gb_printf_err(" critical path: %.3f ms of %.3f ms -> at most %.2fx speedup\n",
critical_ms, total_ms, critical_ms > 0 ? total_ms/critical_ms : 0.0);
gb_printf_err(" missing edges: %td\n", g->missing_edges);
}
gb_internal void check_all_global_entities(Checker *c) {
in_single_threaded_checker_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);
{
u32 hash = 0;
InternedString name = string_interner_insert(str_lit("Load_Directory_File"), 0, &hash);
if (scope_lookup_current(c->info.runtime_package->scope, name, hash) != nullptr) {
init_core_load_directory_file(c);
}
}
TIME_SECTION("check all global entities - build groups");
GlobalGroupGraph *g = &global_groups;
build_global_groups(c, g);
TIME_SECTION("check all global entities - check groups");
g->active = true;
g->current_group = -1;
check_global_groups(c, g);
g->active = false;
if (build_context.internal_check_global_edges && g->missing_edges > 0) {
gb_printf_err("%td missing global dependencies\n", g->missing_edges);
gb_exit(1);
}
in_single_threaded_checker_stage.store(false, std::memory_order_relaxed);
}
-474
View File
@@ -1,474 +0,0 @@
// Global 'when's and 'foreign' blocks: every name one may declare is a placeholder in its scope, and the
// first lookup of a placeholder resolves them, so the order of files and declarations does not matter
struct GlobalDeclSource {
Ast * node; // WhenStmt or ForeignBlockDecl
AstFile * file;
GlobalDeclSource *parent;
bool in_else; // within the else branch of `parent`
bool reachable;
bool reported_cycle;
EntityState state;
ForeignContext foreign_context; // of a resolved 'foreign' block
};
struct GlobalDeclSourceFrame {
GlobalDeclSource *source;
InternedString needs; // the placeholder being resolved for it
};
gb_global Array<GlobalDeclSource *> global_decl_sources;
gb_global Array<GlobalDeclSourceFrame> global_decl_source_stack;
gb_global Array<Scope *> global_placeholder_scopes;
gb_global CheckerContext global_decl_source_export_ctx;
gb_global UntypedExprInfoMap global_decl_source_export_untyped;
enum : u8 {
PlaceholderScope_File = 1<<0,
PlaceholderScope_Pkg = 1<<1,
};
// -1 when 'private' has a value that is not a string literal
gb_internal i32 syntactic_visibility(Array<Ast *> const &attributes) {
for (Ast *attr : attributes) {
if (attr->kind != Ast_Attribute) {
continue;
}
for (Ast *elem : attr->Attribute.elems) {
if (elem->kind == Ast_Ident && elem->Ident.token.string == "private") {
return EntityVisiblity_PrivateToPackage;
}
if (elem->kind == Ast_FieldValue &&
elem->FieldValue.field->kind == Ast_Ident &&
elem->FieldValue.field->Ident.token.string == "private") {
Ast *value = elem->FieldValue.value;
if (value != nullptr && value->tav.value.kind == ExactValue_String) {
return value->tav.value.value_string == "file" ? EntityVisiblity_PrivateToFile : EntityVisiblity_PrivateToPackage;
}
return -1;
}
}
}
return EntityVisiblity_Public;
}
gb_internal bool has_syntactic_attribute(Array<Ast *> const &attributes, String const &name) {
for (Ast *attr : attributes) {
if (attr->kind != Ast_Attribute) {
continue;
}
for (Ast *elem : attr->Attribute.elems) {
Ast *field = elem->kind == Ast_FieldValue ? elem->FieldValue.field : elem;
if (field->kind == Ast_Ident && field->Ident.token.string == name) {
return true;
}
}
}
return false;
}
gb_internal void add_placeholder(Scope *s, InternedString name, GlobalDeclSource *src) {
if (name.value == 0 || name.is_blank()) {
return;
}
if (s->placeholders == nullptr) {
s->placeholders = permanent_alloc_item<PtrMap<u64, GlobalDeclSource *>>();
map_init(s->placeholders);
array_add(&global_placeholder_scopes, s);
}
u64 key = name.value;
for (auto *e = multi_map_find_first(s->placeholders, key); e != nullptr; e = multi_map_find_next(s->placeholders, e)) {
if (e->value == src) {
return;
}
}
multi_map_insert(s->placeholders, key, src);
}
gb_internal void add_placeholders(AstFile *f, u8 scopes, InternedString name, GlobalDeclSource *src) {
if (scopes & PlaceholderScope_File) {
add_placeholder(f->scope, name, src);
}
if (scopes & PlaceholderScope_Pkg) {
add_placeholder(f->pkg->scope, name, src);
}
}
gb_internal GlobalDeclSource *add_global_decl_source(Ast *node, AstFile *f, GlobalDeclSource *parent, bool in_else) {
GlobalDeclSource *src = permanent_alloc_item<GlobalDeclSource>();
src->node = node;
src->file = f;
src->parent = parent;
src->in_else = in_else;
src->reachable = true;
src->state = EntityState_Unresolved;
array_add(&global_decl_sources, src);
return src;
}
gb_internal void scan_global_decl_sources(AstFile *f, Slice<Ast *> const &stmts, GlobalDeclSource *owner, bool in_else, i32 foreign_visibility);
gb_internal void scan_global_when_stmt(AstFile *f, Ast *node, GlobalDeclSource *parent, bool in_else, i32 foreign_visibility) {
ast_node(ws, WhenStmt, node);
GlobalDeclSource *src = add_global_decl_source(node, f, parent, in_else);
if (ws->body != nullptr && ws->body->kind == Ast_BlockStmt) {
scan_global_decl_sources(f, ws->body->BlockStmt.stmts, src, false, foreign_visibility);
}
if (ws->else_stmt != nullptr) {
switch (ws->else_stmt->kind) {
case Ast_BlockStmt:
scan_global_decl_sources(f, ws->else_stmt->BlockStmt.stmts, src, true, foreign_visibility);
break;
case Ast_WhenStmt:
scan_global_when_stmt(f, ws->else_stmt, src, true, foreign_visibility);
break;
}
}
}
gb_internal void scan_global_decl_sources(AstFile *f, Slice<Ast *> const &stmts, GlobalDeclSource *owner, bool in_else, i32 foreign_visibility) {
// NOTE(bill): `owner == nullptr` is the file scope itself, whose other declarations are already collected
for (Ast *decl : stmts) {
switch (decl->kind) {
case_ast_node(vd, ValueDecl, decl);
if (owner == nullptr) {
break;
}
i32 visibility = syntactic_visibility(vd->attributes);
if (visibility == EntityVisiblity_Public) {
visibility = foreign_visibility;
}
if (visibility == EntityVisiblity_Public && (f->flags & AstFile_IsPrivateFile)) {
visibility = EntityVisiblity_PrivateToFile;
}
u8 scopes = PlaceholderScope_Pkg;
if (visibility == EntityVisiblity_PrivateToFile) {
scopes = PlaceholderScope_File;
} else if (visibility < 0) {
scopes = PlaceholderScope_File|PlaceholderScope_Pkg;
}
for (Ast *name : vd->names) {
if (name->kind == Ast_Ident) {
add_placeholders(f, scopes, name->Ident.interned, owner);
}
}
case_end;
case_ast_node(fl, ForeignImportDecl, decl);
if (owner == nullptr) {
break;
}
String library_name = fl->library_name.string;
if (library_name.len == 0 && fl->fullpaths.count != 0) {
library_name = path_to_entity_name(fl->library_name.string, fl->fullpaths[0]);
}
if (library_name.len != 0) {
u8 scopes = has_syntactic_attribute(fl->attributes, str_lit("export")) ? PlaceholderScope_Pkg : PlaceholderScope_File;
add_placeholders(f, scopes, string_interner_insert(library_name), owner);
}
case_end;
case_ast_node(fb, ForeignBlockDecl, decl);
GlobalDeclSource *src = add_global_decl_source(decl, f, owner, in_else);
if (fb->body != nullptr && fb->body->kind == Ast_BlockStmt) {
scan_global_decl_sources(f, fb->body->BlockStmt.stmts, src, false, syntactic_visibility(fb->attributes));
}
case_end;
case_ast_node(ws, WhenStmt, decl);
scan_global_when_stmt(f, decl, owner, in_else, foreign_visibility);
case_end;
case_ast_node(es, ExprStmt, decl);
if (owner == nullptr && es->expr->kind == Ast_CallExpr &&
es->expr->CallExpr.proc->kind == Ast_BasicDirective &&
(decl->state_flags & StateFlag_BeenHandled) == 0) {
decl->state_flags |= StateFlag_BeenHandled;
array_add(&f->delayed_decls_queues[AstDelayQueue_Expr], es->expr);
}
case_end;
}
}
}
gb_internal bool is_global_decl_source_in_when(GlobalDeclSource *src) {
for (; src != nullptr; src = src->parent) {
if (src->node->kind == Ast_WhenStmt) {
return true;
}
}
return false;
}
gb_internal Slice<Ast *> global_decl_source_taken_stmts(GlobalDeclSource *src) {
if (src->node->kind == Ast_ForeignBlockDecl) {
Ast *body = src->node->ForeignBlockDecl.body;
if (body != nullptr && body->kind == Ast_BlockStmt) {
return body->BlockStmt.stmts;
}
return {};
}
ast_node(ws, WhenStmt, src->node);
if (ws->determined_cond) {
if (ws->body != nullptr && ws->body->kind == Ast_BlockStmt) {
return ws->body->BlockStmt.stmts;
}
} else if (ws->else_stmt != nullptr && ws->else_stmt->kind == Ast_BlockStmt) {
return ws->else_stmt->BlockStmt.stmts;
}
return {};
}
gb_internal void collect_global_decl_source_stmts(CheckerContext *ctx, Slice<Ast *> const &stmts) {
AstFile *f = ctx->file;
for (Ast *decl : stmts) {
if (decl->kind == Ast_ValueDecl) {
check_collect_value_decl(ctx, decl);
}
}
check_export_entities_in_pkg(&global_decl_source_export_ctx, f->pkg, &global_decl_source_export_untyped);
// NOTE(bill): after the value declarations, as their attributes are evaluated
for (Ast *decl : stmts) {
switch (decl->kind) {
case_ast_node(fl, ForeignImportDecl, decl);
check_add_foreign_import_decl(ctx, decl);
case_end;
case_ast_node(es, ExprStmt, decl);
if (es->expr->kind == Ast_CallExpr && es->expr->CallExpr.proc->kind == Ast_BasicDirective &&
(decl->state_flags & StateFlag_BeenHandled) == 0) {
decl->state_flags |= StateFlag_BeenHandled;
array_add(&f->delayed_decls_queues[AstDelayQueue_Expr], es->expr);
}
case_end;
}
}
}
gb_internal Token global_decl_source_token(GlobalDeclSource *src) {
if (src->node->kind == Ast_WhenStmt) {
return src->node->WhenStmt.token;
}
return src->node->ForeignBlockDecl.token;
}
gb_internal void report_global_decl_source_cycle(GlobalDeclSource *src, InternedString needed) {
if (src->reported_cycle) {
return;
}
src->reported_cycle = true;
isize start = 0;
for (isize i = global_decl_source_stack.count-1; i >= 0; i--) {
if (global_decl_source_stack[i].source == src) {
start = i;
break;
}
}
ERROR_BLOCK();
Token token = global_decl_source_token(src);
error(token, "Cyclic dependency between global '%.*s' declarations", LIT(token.string));
for (isize i = start; i < global_decl_source_stack.count; i++) {
Token t = global_decl_source_token(global_decl_source_stack[i].source);
InternedString name = i+1 < global_decl_source_stack.count ? global_decl_source_stack[i].needs : needed;
error_line("\t'%.*s' at %s needs '%s', which may be declared by\n", LIT(t.string), token_pos_to_string(t.pos), name.cstring());
}
error_line("\t'%.*s' at %s\n", LIT(token.string), token_pos_to_string(token.pos));
}
gb_internal void resolve_global_decl_source(GlobalDeclSource *src, InternedString needed) {
if (src->state == EntityState_Resolved) {
return;
}
if (src->state == EntityState_InProgress) {
report_global_decl_source_cycle(src, needed);
return;
}
GlobalDeclSource *foreign_block = nullptr;
if (src->parent != nullptr) {
GlobalDeclSource *parent = src->parent;
resolve_global_decl_source(parent, needed);
if (parent->state != EntityState_Resolved) {
return;
}
bool reachable = parent->reachable;
if (parent->node->kind == Ast_WhenStmt) {
reachable = reachable && parent->node->WhenStmt.determined_cond != src->in_else;
}
if (!reachable) {
src->reachable = false;
src->state = EntityState_Resolved;
return;
}
for (GlobalDeclSource *p = parent; p != nullptr; p = p->parent) {
if (p->node->kind == Ast_ForeignBlockDecl) {
foreign_block = p;
break;
}
}
}
src->state = EntityState_InProgress;
array_add(&global_decl_source_stack, GlobalDeclSourceFrame{src, {}});
CheckerContext ctx = {};
init_checker_context(&ctx, global_checker_ptr.load(std::memory_order_relaxed));
UntypedExprInfoMap untyped = {};
reset_checker_context(&ctx, src->file, &untyped);
if (foreign_block != nullptr) {
ctx.foreign_context = foreign_block->foreign_context;
}
if (src->node->kind == Ast_WhenStmt) {
ast_node(ws, WhenStmt, src->node);
Operand operand = {Addressing_Invalid};
check_expr(&ctx, &operand, ws->cond);
if (operand.mode != Addressing_Invalid && !is_type_boolean(operand.type)) {
error(ws->cond, "Non-boolean condition in 'when' statement");
}
if (operand.mode != Addressing_Constant) {
error(ws->cond, "Non-constant condition in 'when' statement");
}
ws->is_cond_determined = true;
ws->determined_cond = operand.value.kind == ExactValue_Bool && operand.value.value_bool;
if (ws->body == nullptr || ws->body->kind != Ast_BlockStmt) {
error(ws->cond, "Invalid body for 'when' statement");
} else if (ws->else_stmt != nullptr && ws->else_stmt->kind != Ast_BlockStmt && ws->else_stmt->kind != Ast_WhenStmt) {
error(ws->else_stmt, "Invalid 'else' statement in 'when' statement");
}
} else {
ast_node(fb, ForeignBlockDecl, src->node);
if (fb->foreign_library->kind == Ast_Ident) {
ctx.foreign_context.curr_library = fb->foreign_library;
} else {
error(fb->foreign_library, "Foreign block name must be an identifier or 'export'");
ctx.foreign_context.curr_library = nullptr;
}
check_decl_attributes(&ctx, fb->attributes, foreign_block_decl_attribute, nullptr);
src->foreign_context = ctx.foreign_context;
}
// NOTE: resolved before its declarations are collected, which evaluates the attributes of 'foreign import's
src->state = EntityState_Resolved;
array_pop(&global_decl_source_stack);
collect_global_decl_source_stmts(&ctx, global_decl_source_taken_stmts(src));
add_untyped_expressions(ctx.info, &untyped);
map_destroy(&untyped);
destroy_checker_context(&ctx);
}
gb_internal Entity *force_scope_placeholders(Scope *s, InternedString name, u32 hash) {
PtrMap<u64, GlobalDeclSource *> *m = s->placeholders;
bool forced = false;
for (auto *e = multi_map_find_first(m, cast(u64)name.value); e != nullptr; e = multi_map_find_next(m, e)) {
GlobalDeclSource *src = e->value;
if (src->state != EntityState_Resolved) {
if (global_decl_source_stack.count > 0) {
global_decl_source_stack[global_decl_source_stack.count-1].needs = name;
}
resolve_global_decl_source(src, name);
forced = true;
}
}
if (!forced) {
return nullptr;
}
rw_mutex_shared_lock(&s->mutex);
Entity *found = scope_map_get(&s->elements, name, hash);
rw_mutex_shared_unlock(&s->mutex);
return found;
}
gb_internal void check_vet_when_shadowing_entity(Entity *e) {
if (e == nullptr || e->scope == nullptr || (e->scope->flags & ScopeFlag_File) == 0) {
return;
}
InternedString name = entity_interned_name(e);
u32 hash = e->interned_name_hash.load(std::memory_order_relaxed);
Scope *outer = e->scope->parent;
if (scope_map_get(&e->scope->elements, name, hash) != e) {
outer = outer->parent; // in the package scope
}
if (outer == nullptr) {
return;
}
Entity *shadowed = scope_lookup(outer, name, hash);
if (shadowed == nullptr || shadowed == e) {
return;
}
if (shadowed->scope == builtin_pkg->scope) {
error(e->token, "Declaration of '%.*s' within a global 'when' shadows the builtin '%.*s'", LIT(e->token.string), LIT(e->token.string));
} else {
error(e->token, "Declaration of '%.*s' within a global 'when' shadows the declaration at %s", LIT(e->token.string), token_pos_to_string(shadowed->token.pos));
}
}
gb_internal void check_vet_when_shadowing(void) {
for (GlobalDeclSource *src : global_decl_sources) {
if (!src->reachable || src->state != EntityState_Resolved) {
continue;
}
if ((ast_file_vet_flags(src->file) & VetFlag_WhenShadowing) == 0 || !is_global_decl_source_in_when(src)) {
continue;
}
for (Ast *decl : global_decl_source_taken_stmts(src)) {
if (decl->kind == Ast_ValueDecl) {
for (Ast *name : decl->ValueDecl.names) {
if (name->kind == Ast_Ident) {
check_vet_when_shadowing_entity(name->Ident.entity.load());
}
}
} else if (decl->kind == Ast_ForeignImportDecl) {
Token token = decl->ForeignImportDecl.library_name;
InternedString name = string_interner_insert(token.string);
for (Scope *s = src->file->scope; s != nullptr && s != builtin_pkg->scope; s = s->parent) {
Entity *e = scope_map_get(&s->elements, name, name.hash());
if (e != nullptr && e->kind == Entity_LibraryName && e->LibraryName.decl == decl) {
check_vet_when_shadowing_entity(e);
break;
}
}
}
}
}
}
// Placeholders for every file, then every source resolved in package, file and source order, which
// only matters for which errors are reported
gb_internal void resolve_global_decl_sources(Checker *c, Array<ImportGraphNode *> const &package_order) {
array_init(&global_decl_sources, heap_allocator());
array_init(&global_decl_source_stack, heap_allocator());
array_init(&global_placeholder_scopes, heap_allocator());
init_checker_context(&global_decl_source_export_ctx, c);
defer (destroy_checker_context(&global_decl_source_export_ctx));
u64 stage_start = global_import_stage_begin();
for (ImportGraphNode *node : package_order) {
for (AstFile *f : node->pkg->files) {
scan_global_decl_sources(f, f->decls, nullptr, false, EntityVisiblity_Public);
}
}
global_import_stage_end(GlobalImportStage_Placeholders, stage_start);
stage_start = global_import_stage_begin();
for (GlobalDeclSource *src : global_decl_sources) {
resolve_global_decl_source(src, {});
}
GB_ASSERT(global_decl_source_stack.count == 0);
for (Scope *s : global_placeholder_scopes) {
map_destroy(s->placeholders);
s->placeholders = nullptr;
}
array_clear(&global_placeholder_scopes);
check_vet_when_shadowing();
global_import_stage_end(GlobalImportStage_DeclSources, stage_start);
map_destroy(&global_decl_source_export_untyped);
}