From cffb4e374a465ce92ab3eb909d3b13bf049984e8 Mon Sep 17 00:00:00 2001 From: Martin Vogel Date: Sun, 16 Aug 2026 14:52:22 +0200 Subject: [PATCH] feat(diagnostics): report per-file cross-LSP registry cost MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The per-file cross-LSP path rebuilds a type registry for every file. Whether that is cheap or quadratic depends entirely on how many defs the module filter leaves, and nothing reported it — so a filter that reduces by a constant FACTOR rather than to a constant SIZE looked identical to one that worked. parallel.resolve.perfile_registry reports defs_per_file next to defs_total, plus how often the filter failed. Measured on one Java tree: 1/4 corpus: defs_total=179033 defs_per_file=1292 full corpus: defs_total=689216 defs_per_file=5031 defs_per_file grew 3.89x while the corpus grew 3.85x — lockstep, so per-file work is proportional to the whole corpus and cross-file LSP is O(files x corpus_defs). filter_failed=0 throughout, which is why this was invisible: the filter always 'succeeds', it just does not bound the set. Diagnostics only; no behaviour change. Refs #1669. Signed-off-by: Martin Vogel --- src/pipeline/pass_lsp_cross.c | 30 ++++++++++++++++++++++++++++++ src/pipeline/pass_lsp_cross.h | 5 +++++ src/pipeline/pass_parallel.c | 20 ++++++++++++++++++++ 3 files changed, 55 insertions(+) diff --git a/src/pipeline/pass_lsp_cross.c b/src/pipeline/pass_lsp_cross.c index e6b4eea77..ac1ddf4ef 100644 --- a/src/pipeline/pass_lsp_cross.c +++ b/src/pipeline/pass_lsp_cross.c @@ -36,6 +36,7 @@ #include #include #include +#include #include /* ── Constants ─────────────────────────────────────────────────── */ @@ -1001,6 +1002,25 @@ void cbm_pxc_run_one_ts(CBMFileResult *r, const char *source, int source_len, co * `rust_shared_get` supplies the lazily-built shared Rust all-defs registry * (the parallel resolver owns its once-guard); NULL means "no shared rust * registry available" and rust NULL-filter files take the per-file build. */ +/* Per-file registry-build cost counters (#1669). Surfaced by pass_parallel at + * end of resolve. */ +_Atomic uint64_t g_pxc_defs_registered = 0; +_Atomic uint64_t g_pxc_build_files = 0; +_Atomic uint64_t g_pxc_filter_files = 0; +_Atomic uint64_t g_pxc_filter_failed = 0; + +void cbm_pxc_filter_stats(uint64_t *defs_registered, uint64_t *build_files, uint64_t *filter_files, + uint64_t *filter_failed) { + if (defs_registered) + *defs_registered = atomic_load_explicit(&g_pxc_defs_registered, memory_order_relaxed); + if (build_files) + *build_files = atomic_load_explicit(&g_pxc_build_files, memory_order_relaxed); + if (filter_files) + *filter_files = atomic_load_explicit(&g_pxc_filter_files, memory_order_relaxed); + if (filter_failed) + *filter_failed = atomic_load_explicit(&g_pxc_filter_failed, memory_order_relaxed); +} + void cbm_pxc_dispatch_file(CBMLanguage lang, CBMFileResult *result, const char *source, int source_len, const char *rel, const char *def_module, const CBMCrossLspRegistries *cross_registries, @@ -1112,7 +1132,17 @@ void cbm_pxc_dispatch_file(CBMLanguage lang, CBMFileResult *result, const char * file_defs = filtered; file_def_count = filtered_count; } + atomic_fetch_add_explicit(&g_pxc_filter_files, 1, memory_order_relaxed); + if (!filter_succeeded) { + atomic_fetch_add_explicit(&g_pxc_filter_failed, 1, memory_order_relaxed); + } } + /* Per-file registry build cost is driven by THIS number. If it tracks the + * corpus instead of the file's own module + imports, cross-file LSP is + * O(files x corpus_defs) — see #1669. */ + atomic_fetch_add_explicit(&g_pxc_defs_registered, (uint64_t)file_def_count, + memory_order_relaxed); + atomic_fetch_add_explicit(&g_pxc_build_files, 1, memory_order_relaxed); if (lang == CBM_LANG_RUST) { CBMTypeRegistry *shared = rust_shared_get ? rust_shared_get(rust_shared_ctx) : NULL; if (shared) { diff --git a/src/pipeline/pass_lsp_cross.h b/src/pipeline/pass_lsp_cross.h index 11269ce62..d60d8fa16 100644 --- a/src/pipeline/pass_lsp_cross.h +++ b/src/pipeline/pass_lsp_cross.h @@ -131,6 +131,11 @@ typedef struct { /* Return the appropriate pre-built registry for a language, or NULL * if none was built (or language has no cross-LSP entrypoint). */ +/* Per-file registry-build cost (#1669): how many defs the per-file cross-LSP + * path actually registered, and how often the module filter failed. */ +void cbm_pxc_filter_stats(uint64_t *defs_registered, uint64_t *build_files, uint64_t *filter_files, + uint64_t *filter_failed); + static inline CBMTypeRegistry *cbm_pxc_registry_for_lang(const CBMCrossLspRegistries *r, CBMLanguage lang) { if (!r) diff --git a/src/pipeline/pass_parallel.c b/src/pipeline/pass_parallel.c index ab12f383d..c3eb2aeec 100644 --- a/src/pipeline/pass_parallel.c +++ b/src/pipeline/pass_parallel.c @@ -3315,6 +3315,26 @@ int cbm_parallel_resolve(cbm_pipeline_ctx_t *ctx, const cbm_file_info_t *files, cbm_log_info("parallel.resolve.cross_lsp_cost", "cross_lsp_ms", cu_buf, "files", nf_buf, "us_per_file", cf_buf, "defs_total", itoa_log(def_count), "us_per_file_per_kdef", pk_buf); + + /* What the per-file registry build actually cost. defs_per_file is the + * lever: if it tracks defs_total rather than the file's own module plus + * imports, the module filter is not containing the work and cross-file + * LSP is O(files x corpus_defs). */ + uint64_t reg_defs = 0; + uint64_t reg_files = 0; + uint64_t flt_files = 0; + uint64_t flt_failed = 0; + cbm_pxc_filter_stats(®_defs, ®_files, &flt_files, &flt_failed); + if (reg_files > 0) { + char rd_buf[CBM_SZ_32]; + char ff_buf[CBM_SZ_32]; + char fp_buf[CBM_SZ_32]; + snprintf(rd_buf, sizeof(rd_buf), "%llu", (unsigned long long)(reg_defs / reg_files)); + snprintf(ff_buf, sizeof(ff_buf), "%llu", (unsigned long long)flt_failed); + snprintf(fp_buf, sizeof(fp_buf), "%llu", (unsigned long long)flt_files); + cbm_log_info("parallel.resolve.perfile_registry", "defs_per_file", rd_buf, "defs_total", + itoa_log(def_count), "filtered_files", fp_buf, "filter_failed", ff_buf); + } } cbm_log_info("parallel.resolve.done", "calls", itoa_log(total_calls), "usages",