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",