fix: preserve Cargo context in parallel Rust resolution

Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
This commit is contained in:
Martin Vogel
2026-08-27 21:14:22 +02:00
parent 909051ccee
commit 0f1e65d625
5 changed files with 255 additions and 107 deletions
+40 -4
View File
@@ -693,7 +693,10 @@ bool cbm_pxc_has_cross_lsp(CBMLanguage lang) {
/* Append cross-file results from `src_out` (allocated in a scratch arena
* about to be destroyed) into `dst_calls` (lives in cache_entry->arena),
* copying every string field into dst_arena. Skips entries whose
* copying every string field into dst_arena. A manifest-qualified Rust
* cross-crate result supersedes any earlier result for the exact same source
* occurrence: the Cargo member path is stronger evidence than a same-named
* local fallback. Skips entries whose
* (kind, caller_qn, callee_qn, source span) is already present — avoids
* inflating the array with cross-file duplicates without conflating distinct
* same-named occurrences. For an exact duplicate, retain the higher-confidence
@@ -715,6 +718,39 @@ static void pxc_append_results(CBMArena *dst_arena, CBMResolvedCallArray *dst_ca
cbm_arena_init(&keys);
CBMHashTable *seen = cbm_ht_create((uint32_t)(dst_calls->count + src_out->count + 1));
/* Per-file Rust resolution may already have confidently matched the tail
* of `member::call()` to a same-named local function. Once the cross pass
* proves that `member` is a declared Cargo workspace member, replace only
* records for that exact parser occurrence before constructing the dedup
* table. This preserves distinct same-named calls at other spans while
* preventing the stale local result from out-ranking manifest evidence. */
for (int j = 0; j < src_out->count; j++) {
const CBMResolvedCall *src = &src_out->items[j];
if (!src->strategy || strcmp(src->strategy, "lsp_cross_crate") != 0 || !src->caller_qn ||
!src->callee_qn || src->site_end_byte <= src->site_start_byte) {
continue;
}
for (int i = 0; i < dst_calls->count; i++) {
CBMResolvedCall *dst = &dst_calls->items[i];
if (!dst->caller_qn || dst->kind != src->kind ||
dst->site_start_byte != src->site_start_byte ||
dst->site_end_byte != src->site_end_byte ||
dst->source_origin != src->source_origin ||
strcmp(dst->caller_qn, src->caller_qn) != 0) {
continue;
}
dst->caller_qn = cbm_arena_strdup(dst_arena, src->caller_qn);
dst->callee_qn = cbm_arena_strdup(dst_arena, src->callee_qn);
dst->strategy = cbm_arena_strdup(dst_arena, src->strategy);
dst->confidence = src->confidence;
dst->reason = src->reason ? cbm_arena_strdup(dst_arena, src->reason) : NULL;
dst->kind = src->kind;
dst->site_start_byte = src->site_start_byte;
dst->site_end_byte = src->site_end_byte;
dst->source_origin = src->source_origin;
}
}
for (int i = 0; i < dst_calls->count; i++) {
const CBMResolvedCall *rc = &dst_calls->items[i];
if (rc->caller_qn && rc->callee_qn) {
@@ -1191,8 +1227,8 @@ void cbm_pxc_dispatch_file(CBMLanguage lang, CBMFileResult *result, const char *
free(filtered);
}
static bool pxc_build_rust_manifest(const cbm_pipeline_ctx_t *ctx, CBMArena *marena,
CBMCargoManifest *out_m) {
bool cbm_pxc_build_rust_manifest(const cbm_pipeline_ctx_t *ctx, CBMArena *marena,
CBMCargoManifest *out_m) {
if (!ctx || !ctx->repo_path || !marena || !out_m)
return false;
char path[1024];
@@ -1234,7 +1270,7 @@ int cbm_pipeline_pass_lsp_cross(cbm_pipeline_ctx_t *ctx, const cbm_file_info_t *
bool have_manifest = false;
if (have_rust) {
cbm_arena_init(&cargo_arena);
have_manifest = pxc_build_rust_manifest(ctx, &cargo_arena, &cargo_manifest);
have_manifest = cbm_pxc_build_rust_manifest(ctx, &cargo_arena, &cargo_manifest);
cbm_pxc_set_rust_manifest(have_manifest ? &cargo_manifest : NULL);
}
+7 -3
View File
@@ -169,10 +169,14 @@ static inline CBMTypeRegistry *cbm_pxc_registry_for_lang(const CBMCrossLspRegist
}
}
/* Borrow the (thread-local) Rust Cargo manifest the cross-file LSP pass set for
* cross-crate (#56) routing. The Tier-2 prebuilt Rust resolve reads it so it sees
* exactly what the per-file fallback (cbm_pxc_run_one) would on the same thread. */
/* Build and borrow the Rust Cargo manifest used for cross-crate (#56) routing.
* The manifest owns strings in manifest_arena; callers keep that arena alive
* until every resolver worker has joined. Each worker must install the shared
* immutable pointer in its own TLS slot before dispatch and clear it afterward. */
struct CBMCargoManifest;
bool cbm_pxc_build_rust_manifest(const cbm_pipeline_ctx_t *ctx, CBMArena *manifest_arena,
struct CBMCargoManifest *out_manifest);
void cbm_pxc_set_rust_manifest(const struct CBMCargoManifest *manifest);
const struct CBMCargoManifest *cbm_pxc_get_rust_manifest(void);
/* Run the cross-file LSP resolver for non-TS languages. Appends
+36 -1
View File
@@ -70,6 +70,7 @@ enum { PP_CSHARP_M_PREFIX_LEN = 2 };
#include "pipeline/pipeline_internal.h"
#include "pipeline/pass_lsp_cross.h" /* cbm_pxc_* helpers for fused cross-file LSP */
#include "pipeline/lsp_resolve.h"
#include "lsp/rust_cargo.h"
#include "helpers.h" /* cbm_kind_in_set_free_cache — per-worker-thread cache teardown */
#include "pipeline/worker_pool.h"
#include "foundation/compat.h"
@@ -1398,6 +1399,11 @@ typedef struct {
* Stored as CBMCrossLspRegistries* (typedef from pass_lsp_cross.h). */
CBMCrossLspRegistries *cross_registries;
/* Parsed once on the coordinator thread and borrowed read-only by resolve
* workers. The pointer is installed into each worker's TLS slot so Rust's
* manifest-aware cross-file resolver sees the same Cargo context. */
const CBMCargoManifest *rust_manifest;
/* F4: LAZILY-built shared Rust registry (built ONCE, on the first NULL-filter
* rust file — the ~all_defs amplifier files). Not eager: repos whose rust files
* all filter to subsets never pay the O(all_defs) build + multi-GB RSS. Built
@@ -2944,6 +2950,8 @@ static void resolve_worker(int worker_id, void *ctx_ptr) {
resolve_ctx_t *rc = ctx_ptr;
resolve_worker_state_t *ws = &rc->workers[worker_id];
cbm_pxc_set_rust_manifest(rc->rust_manifest);
if (!ws->local_edge_buf) {
ws->local_edge_buf =
cbm_gbuf_new_shared_ids(rc->project_name, rc->repo_path, rc->shared_ids);
@@ -3009,6 +3017,8 @@ static void resolve_worker(int worker_id, void *ctx_ptr) {
* a mixed-source-root Java↔Kotlin site remains unresolved, so JVM
* callers run whenever either kind of site exists. */
bool jvm_cross_lsp = (lang == CBM_LANG_JAVA || lang == CBM_LANG_KOTLIN);
bool rust_workspace_cross_lsp =
(lang == CBM_LANG_RUST && rc->rust_manifest && rc->rust_manifest->member_count > 0);
int call_reference_sites = pp_call_reference_site_count(result, lang);
int semantic_sites = result->calls.count + call_reference_sites;
int qualified_lsp_sites = pp_qualified_lsp_site_count(result);
@@ -3016,7 +3026,8 @@ static void resolve_worker(int worker_id, void *ctx_ptr) {
bool cross_lsp_eligible =
(rc->all_defs && rc->def_count > 0 && cbm_pxc_has_cross_lsp(lang) &&
(semantic_sites > 0 || pending_lsp_site) &&
(jvm_cross_lsp || pending_lsp_site || qualified_lsp_sites < semantic_sites) &&
(jvm_cross_lsp || rust_workspace_cross_lsp || pending_lsp_site ||
qualified_lsp_sites < semantic_sites) &&
!is_generated);
/* Skip files with nothing else to resolve and no cross-LSP work. */
@@ -3187,6 +3198,7 @@ static void resolve_worker(int worker_id, void *ctx_ptr) {
* into dead TLS and are never retired, so a later cross-thread free can
* never bring their refcount to zero (leak). Retiring them here releases
* each page as its final chunk returns. */
cbm_pxc_set_rust_manifest(NULL);
cbm_destroy_thread_parser();
cbm_slab_destroy_thread();
cbm_service_pattern_cache_end();
@@ -3213,6 +3225,25 @@ int cbm_parallel_resolve(cbm_pipeline_ctx_t *ctx, const cbm_file_info_t *files,
}
memset(workers, 0, (size_t)worker_count * sizeof(resolve_worker_state_t));
bool have_rust = false;
for (int i = 0; i < file_count; i++) {
if (result_cache[i] && files[i].language == CBM_LANG_RUST) {
have_rust = true;
break;
}
}
CBMArena rust_manifest_arena;
CBMCargoManifest rust_manifest;
bool rust_manifest_arena_live = false;
const CBMCargoManifest *rust_manifest_ptr = NULL;
if (have_rust) {
cbm_arena_init(&rust_manifest_arena);
rust_manifest_arena_live = true;
if (cbm_pxc_build_rust_manifest(ctx, &rust_manifest_arena, &rust_manifest)) {
rust_manifest_ptr = &rust_manifest;
}
}
resolve_ctx_t rc = {
.files = files,
.file_count = file_count,
@@ -3230,6 +3261,7 @@ int cbm_parallel_resolve(cbm_pipeline_ctx_t *ctx, const cbm_file_info_t *files,
.def_modules = def_modules,
.module_def_index = module_def_index,
.cross_registries = cross_registries,
.rust_manifest = rust_manifest_ptr,
};
atomic_init(&rc.next_file_idx, 0);
atomic_init(&rc.lsp_cross_processed, 0);
@@ -3255,6 +3287,9 @@ int cbm_parallel_resolve(cbm_pipeline_ctx_t *ctx, const cbm_file_info_t *files,
rc.rust_shared_arena_live = false;
}
cbm_mutex_destroy(&rc.rust_shared_mu);
if (rust_manifest_arena_live) {
cbm_arena_destroy(&rust_manifest_arena);
}
/* Sub-phase: Merge all local edge bufs into main gbuf (SEQUENTIAL) */
CBM_PROF_START(t_resolve_merge);