perf(resolver): read a Dart barrel's exports as a walk reaches them

Every file that imports a barrel read the barrel's whole export list again, though one name's walk stops at the chain's bound of 128 libraries. With the barrel listing the project, that is files x exports: 16,000 libraries took 39.7 s in resolve.

Exports are now read when a walk first reaches them, and a walk stops once the bound is passed, since no further library can then be visited or change its answer. So `dart_export_graph_not_walked` is still what a name past the bound gets.

Left as it was: an export that a `show` or `hide` rules out costs a filter check per name and does not count toward the bound.

complexity_dart_barrel_exports_are_near_linear: 3.71 on the old read, 1.94 now.

Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
This commit is contained in:
Martin Vogel committed 2026-11-03 01:27:36 +01:00
1 parent 681208abcc
commit eccd026782
2 files changed
+106 -25

No files matched your search

+66 -25
View File
@@ -32,6 +32,7 @@
#include "source_adapter_internal.h"
#include <stdio.h>
#include <stdlib.h>
#include "stats.h"
#include "syntax_index.h"
#define DART_LIBRARY_RECORD "&library"
@@ -52,9 +53,13 @@ typedef struct {
bool conditional; /* its library depends on the build's configuration */
} dart_directive_t;
/* A barrel's exports can list the whole project, and every file that imports it would read them
* all again while a name's walk stops at the chain's bound. So they are read as a walk reaches
* them: export_next is the first declaration not read yet. */
typedef struct {
dart_directive_t *imports, *exports;
size_t import_count, export_count;
size_t import_count, export_count, export_capacity;
const cbm_source_decl_t *export_next;
} dart_library_t;
typedef struct dart_local {
@@ -636,6 +641,30 @@ static bool dart_filter_passes(const char *show, const char *hide, const char *n
return !dart_list_has(hide, name);
}
/* False for a directive this table holds in another form, which says nothing. */
static bool dart_read_directive(dart_ctx_t *ctx, const cbm_source_decl_t *d,
dart_directive_t *out) {
cbm_resolve_stats_add(CBM_RESOLVE_STAT_LOOKUP_PROBES, 1);
char *copy = d->type ? cbm_arena_strdup(ctx->walk->arena, d->type) : NULL;
char *parts[4] = {0};
int used = 0;
for (char *p = copy; p && used < 4;) {
parts[used++] = p;
char *mark = strchr(p, DART_DIRECTIVE_SEPARATOR);
if (mark)
*mark = '\0';
p = mark ? mark + 1 : NULL;
}
if (used != 4)
return false;
const char *path = dart_uri_library(ctx->walk, d->file, parts[0]);
if (path && !cbm_source_lookup(ctx->walk, path, DART_LIBRARY_RECORD))
path = NULL;
*out =
(dart_directive_t){path, parts[1][0] ? parts[1] : NULL, parts[2], parts[3], !parts[0][0]};
return true;
}
static void dart_read_directives(dart_ctx_t *ctx, const char *library, const char *key,
dart_directive_t **out, size_t *out_count) {
size_t count = 0;
@@ -647,28 +676,36 @@ static void dart_read_directives(dart_ctx_t *ctx, const char *library, const cha
*out_count = 0;
if (!items)
return;
for (const cbm_source_decl_t *d = cbm_source_lookup(ctx->walk, library, key); d; d = d->next) {
char *copy = d->type ? cbm_arena_strdup(ctx->walk->arena, d->type) : NULL;
char *parts[4] = {0};
int used = 0;
for (char *p = copy; p && used < 4;) {
parts[used++] = p;
char *mark = strchr(p, DART_DIRECTIVE_SEPARATOR);
if (mark)
*mark = '\0';
p = mark ? mark + 1 : NULL;
}
if (used != 4)
continue;
const char *path = dart_uri_library(ctx->walk, d->file, parts[0]);
if (path && !cbm_source_lookup(ctx->walk, path, DART_LIBRARY_RECORD))
path = NULL;
items[(*out_count)++] = (dart_directive_t){path, parts[1][0] ? parts[1] : NULL, parts[2],
parts[3], !parts[0][0]};
}
for (const cbm_source_decl_t *d = cbm_source_lookup(ctx->walk, library, key); d; d = d->next)
if (dart_read_directive(ctx, d, &items[*out_count]))
(*out_count)++;
}
static const dart_library_t *dart_library(dart_ctx_t *ctx, const char *path) {
/* The export at this position, read on the way if it has not been; NULL past the last. */
static const dart_directive_t *dart_export_at(dart_ctx_t *ctx, dart_library_t *library,
size_t position) {
while (position >= library->export_count && library->export_next) {
if (library->export_count == library->export_capacity) {
size_t capacity = library->export_capacity ? library->export_capacity * 2 : 16;
dart_directive_t *items = cbm_arena_alloc(ctx->walk->arena, capacity * sizeof(*items));
if (!items) {
cbm_source_fail(ctx->walk, "dart_library_allocation");
return NULL;
}
if (library->export_count)
memcpy(items, library->exports, library->export_count * sizeof(*items));
library->exports = items;
library->export_capacity = capacity;
}
const cbm_source_decl_t *d = library->export_next;
library->export_next = d->next;
if (dart_read_directive(ctx, d, &library->exports[library->export_count]))
library->export_count++;
}
return position < library->export_count ? &library->exports[position] : NULL;
}
static dart_library_t *dart_library(dart_ctx_t *ctx, const char *path) {
dart_library_t *library = cbm_ht_get(ctx->libraries, path);
if (library)
return library;
@@ -679,7 +716,7 @@ static const dart_library_t *dart_library(dart_ctx_t *ctx, const char *path) {
return NULL;
}
dart_read_directives(ctx, path, DART_IMPORTS, &library->imports, &library->import_count);
dart_read_directives(ctx, path, DART_EXPORTS, &library->exports, &library->export_count);
library->export_next = cbm_source_lookup(ctx->walk, path, DART_EXPORTS);
cbm_ht_set(ctx->libraries, key, library);
return library;
}
@@ -693,12 +730,16 @@ static const cbm_source_decl_t *dart_library_public(dart_ctx_t *ctx, const char
const cbm_source_decl_t *own = cbm_source_lookup(ctx->walk, path, key);
if (own)
return own;
const dart_library_t *library = dart_library(ctx, path);
dart_library_t *library = dart_library(ctx, path);
if (!library)
return NULL;
const cbm_source_decl_t *found = NULL;
for (size_t i = 0; i < library->export_count; i++) {
const dart_directive_t *directive = &library->exports[i];
/* Past the chain's bound no further library can be visited, so none can change this. */
for (size_t i = 0; !chain->overflow; i++) {
const dart_directive_t *directive = dart_export_at(ctx, library, i);
if (!directive)
break;
cbm_resolve_stats_add(CBM_RESOLVE_STAT_LOOKUP_PROBES, 1);
if (!dart_filter_passes(directive->show, directive->hide, name))
continue;
const cbm_source_decl_t *exported =
+40
View File
@@ -3263,6 +3263,45 @@ TEST(complexity_source_same_named_definitions_are_near_linear) {
PASS();
}
/* A barrel library re-exports the project, and every library that imports it read its whole
* export list again. */
static uint64_t cx_dart_barrel_work(int libraries) {
CxSourceFile *files = calloc((size_t)libraries + 1, sizeof(*files));
if (!files)
return 0;
size_t room = (size_t)libraries * 32 + 1;
char *barrel = malloc(room);
size_t used = 0;
bool ok = barrel != NULL;
for (int i = 0; ok && i < libraries; i++) {
used += (size_t)snprintf(barrel + used, room - used, "export 'a%d.dart';\n", i);
snprintf(files[i].path, sizeof(files[i].path), "lib/a%d.dart", i);
files[i].text = malloc(96);
ok = files[i].text != NULL;
if (ok)
snprintf(files[i].text, 96, "import 'all.dart';\nint f%d() => f%d();\n", i,
(i + 1) % libraries);
}
snprintf(files[libraries].path, sizeof(files[libraries].path), "lib/all.dart");
files[libraries].text = barrel;
uint64_t work = ok ? cx_source_work(CBM_LANG_DART, files, libraries + 1) : 0;
for (int i = 0; i <= libraries; i++)
free(files[i].text);
free(files);
return work;
}
TEST(complexity_dart_barrel_exports_are_near_linear) {
enum { BARREL_LIBRARIES = 500 };
uint64_t first = cx_dart_barrel_work(BARREL_LIBRARIES);
uint64_t second = cx_dart_barrel_work(BARREL_LIBRARIES * 2);
double ratio = cx_ratio((double)second, (double)first);
printf(" barrel source probes %llu -> %llu (ratio %.2f)\n", (unsigned long long)first,
(unsigned long long)second, ratio);
ASSERT_GTE(first, (uint64_t)BARREL_LIBRARIES);
ASSERT_TRUE(ratio >= CX_RATIO_LO && ratio <= CX_RATIO_HI);
PASS();
}
/* A function of route registrations asks, for each, what its receiver was bound to. */
CX_LINE(cx_line_go_route, " r.GET(\"/p%d\", h)\n")
CX_LINE3(cx_line_go_group, " g%d := r.Group(\"/g%d\")\n g%d.GET(\"/x\", h)\n")
@@ -3418,6 +3457,7 @@ SUITE(complexity) {
RUN_TEST(complexity_syntax_index_prev_siblings_answer_as_tree_sitter_does);
RUN_TEST(complexity_prev_sibling_walk_is_linear);
RUN_TEST(complexity_source_same_named_definitions_are_near_linear);
RUN_TEST(complexity_dart_barrel_exports_are_near_linear);
RUN_TEST(complexity_go_route_bindings_are_near_linear);
RUN_TEST(complexity_flat_preceding_label_is_linear);
RUN_TEST(complexity_import_map_is_linear_in_a_files_imports);