fix(lsp): unify the two type short-name indexes that broke the main build

main does not compile. internal/cbm/lsp/type_registry.h declares
type_short_buckets, type_short_entries and type_short_bucket_count twice, and
cbm_registry_types_by_short_name twice with different parameter types.

Two changes implemented the same feature independently:

  a4c0ffbc  perf(cs): eliminate the C# corpus-proportional scans  (2026-08-16, ours)
            CBMTypeShortIter, cbm_type_short_iter_next
  793716dc  perf(lsp): index C++ short-name type lookups     (PR #1679, astandrik)
            CBMTypeNameIter,  cbm_type_name_iter_next

They landed in different hunks of the same struct and the same header section,
so git merged them with no conflict and nothing failed until the merge commit
was built. This is the failure mode where a clean merge is not evidence that
two changes compose.

The two iterators are structurally identical -- same five fields, byte-identical
_next bodies reading the same reg->type_short_entries chain. They differ in one
place, and it matters:

  #1679          on aux-index allocation failure, sets tail_i = 0, so the
                 iterator degrades to a full types[] scan and the candidate
                 set is unchanged.
  a4c0ffbc       sets tail_i = reg->type_qn_entry_count unconditionally, so on
                 that same failure it SKIPS the first type_qn_entry_count types
                 and silently yields an incomplete candidate set.

So this keeps a4c0ffbc's names -- CBMTypeShortIter reads correctly beside its
type_short_* members and beside CBMTypeEmbedIter and CBMFreeFuncIter -- and
#1679's body, which is the correct one. The surviving declaration carries both
rationales: the C++ short-name lookup and cs_resolve_type_name's step-9
fallback are now stated as two consumers of one index, which is what they are.

The merge also broke two comment/member pairings, repaired here: the duplicated
members had displaced the free-function index members from under their own
comment, and the CBMTypeShortIter block had been interleaved between
CBMFreeFuncIter's typedef and its declarations.

No behaviour change for either consumer beyond the allocation-failure path,
which strictly gains coverage for C#. c_lsp.c and cs_lsp.c now call one
function. #1679's tests, including the fault-injection case that NULLs the
buckets and asserts the full-scan fallback, carry over unchanged apart from the
type name.

Local: c_lsp, cs_lsp and registry -- 881 passed, 0 failed.

Co-Authored-By: astandrik <astandrik@yandex-team.ru>
Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
This commit is contained in:
Martin Vogelandastandrik committed 2026-09-01 18:43:37 +02:00
1 parent 7808686cee
commit da61c81af2
4 files changed
+22 -73

No files matched your search

+2 -2
View File
@@ -2818,10 +2818,10 @@ static const CBMRegisteredFunc *c_lookup_member_depth(CLSPContext *ctx, const ch
const char *shortn = dot ? dot + 1 : type_qn;
size_t slen = strlen(shortn);
const char *best_qn = NULL;
CBMTypeNameIter it;
CBMTypeShortIter it;
cbm_registry_types_by_short_name(ctx->registry, shortn, &it);
int i;
while ((i = cbm_type_name_iter_next(&it)) >= 0) {
while ((i = cbm_type_short_iter_next(&it)) >= 0) {
const char *q = ctx->registry->types[i].qualified_name;
if (!q) {
continue;
+2 -38
View File
@@ -299,7 +299,7 @@ static void build_ffunc_short_index(CBMTypeRegistry *reg, CBMArena *idx_arena) {
}
void cbm_registry_types_by_short_name(const CBMTypeRegistry *reg, const char *short_name,
CBMTypeNameIter *out) {
CBMTypeShortIter *out) {
out->reg = reg;
out->hash = fnv1a(short_name);
if (reg->type_qn_buckets && reg->type_qn_bucket_count > 0) {
@@ -319,7 +319,7 @@ void cbm_registry_types_by_short_name(const CBMTypeRegistry *reg, const char *sh
}
}
int cbm_type_name_iter_next(CBMTypeNameIter *it) {
int cbm_type_short_iter_next(CBMTypeShortIter *it) {
const CBMTypeRegistry *reg = it->reg;
while (it->chain_idx >= 0) {
const CBMRegistryHashEntry *e = &reg->type_short_entries[it->chain_idx];
@@ -413,42 +413,6 @@ int cbm_free_func_iter_next(CBMFreeFuncIter *it) {
return -1;
}
void cbm_registry_types_by_short_name(const CBMTypeRegistry *reg, const char *short_name,
CBMTypeShortIter *out) {
out->reg = reg;
out->hash = fnv1a(short_name);
if (reg->type_qn_buckets && reg->type_qn_bucket_count > 0) {
if (reg->type_short_buckets && reg->type_short_bucket_count > 0) {
int slot = (int)(out->hash & (uint64_t)(reg->type_short_bucket_count - 1));
out->chain_idx = reg->type_short_buckets[slot];
} else {
out->chain_idx = -1;
}
out->tail_i = reg->type_qn_entry_count;
out->tail_end = reg->type_count;
} else {
out->chain_idx = -1;
out->tail_i = 0;
out->tail_end = reg->type_count;
}
}
int cbm_type_short_iter_next(CBMTypeShortIter *it) {
const CBMTypeRegistry *reg = it->reg;
while (it->chain_idx >= 0) {
const CBMRegistryHashEntry *e = &reg->type_short_entries[it->chain_idx];
int p = e->payload_index;
uint64_t h = e->hash;
it->chain_idx = e->next_index;
if (h != it->hash)
continue;
return p;
}
if (it->tail_i < it->tail_end)
return it->tail_i++;
return -1;
}
void cbm_registry_methods(const CBMTypeRegistry *reg, const char *receiver_qn,
const char *method_name, CBMMethodIter *out) {
memset(out, 0, sizeof(*out));
+7 -22
View File
@@ -119,6 +119,7 @@ typedef struct CBMTypeRegistry {
int *type_short_buckets;
CBMRegistryHashEntry *type_short_entries;
int type_short_bucket_count;
int type_short_entry_count;
// Embedded-type index: fnv1a(bare last-'.'-segment of each embedded_type) -> chain
// of TYPE indices declaring it. payload_index = type index (a type may appear once
// per embedded entry; consumers dedup adjacent same-type via the iterator).
@@ -128,10 +129,6 @@ typedef struct CBMTypeRegistry {
int type_embed_entry_count;
// Free-function short-name index: fnv1a(short_name) -> chain of FREE-function
// (receiver_type==NULL) indices. payload_index = func index.
int *type_short_buckets;
CBMRegistryHashEntry *type_short_entries;
int type_short_bucket_count;
int type_short_entry_count;
int *ffunc_short_buckets;
CBMRegistryHashEntry *ffunc_short_entries;
int ffunc_short_bucket_count;
@@ -236,16 +233,19 @@ const CBMRegisteredFunc *cbm_registry_lookup_symbol_by_types(const CBMTypeRegist
// any post-finalize tail; otherwise it degrades to the original full scan. Results
// preserve ascending registry order. The index is a hash prefilter; callers must
// re-check their exact predicate, including tail entries.
// Built by finalize into type_short_buckets. Shared by the C++ short-name lookup
// and by cs_resolve_type_name's step-9 fallback, which scanned type_count per
// unresolved name — quadratic against the shared Tier-2 registry.
typedef struct {
const CBMTypeRegistry *reg;
uint64_t hash;
int chain_idx;
int tail_i;
int tail_end;
} CBMTypeNameIter;
} CBMTypeShortIter;
void cbm_registry_types_by_short_name(const CBMTypeRegistry *reg, const char *short_name,
CBMTypeNameIter *out);
int cbm_type_name_iter_next(CBMTypeNameIter *it);
CBMTypeShortIter *out);
int cbm_type_short_iter_next(CBMTypeShortIter *it);
// Iterate registry TYPE indices whose embedded_types contain an entry whose BARE
// name (last '.'-segment) equals `bare`. On a finalized registry this walks the
@@ -278,21 +278,6 @@ typedef struct {
int tail_i;
int tail_end;
} CBMFreeFuncIter;
/* Iterate TYPE indices sharing a short name — the type-side twin of the free-
* function iterator. Built by finalize into type_short_buckets; degrades to a
* full types[] scan on an unfinalized registry (same correctness, old cost).
* Added for cs_resolve_type_name's step-9 fallback, which scanned type_count
* per unresolved name — quadratic against the shared Tier-2 registry. */
typedef struct {
const CBMTypeRegistry *reg;
uint64_t hash;
int chain_idx;
int tail_i;
int tail_end;
} CBMTypeShortIter;
void cbm_registry_types_by_short_name(const CBMTypeRegistry *reg, const char *short_name,
CBMTypeShortIter *out);
int cbm_type_short_iter_next(CBMTypeShortIter *it);
void cbm_registry_free_funcs_by_short_name(const CBMTypeRegistry *reg, const char *short_name,
CBMFreeFuncIter *out);
+11 -11
View File
@@ -15331,13 +15331,13 @@ TEST(registry_short_name_indexes) {
/* Qualified-name bare segment "Trait": types 0, 4, then the bare-QN type
* 5. The iterator is a hash prefilter, so apply the exact predicate before
* asserting its order just like production consumers do. */
CBMTypeNameIter nit;
CBMTypeShortIter nit;
cbm_registry_types_by_short_name(&reg, "Trait", &nit);
{
int expected[] = {0, 4, 5};
int exact_count = 0;
int candidate;
while ((candidate = cbm_type_name_iter_next(&nit)) >= 0) {
while ((candidate = cbm_type_short_iter_next(&nit)) >= 0) {
const char *qn = reg.types[candidate].qualified_name;
const char *last_dot = qn ? strrchr(qn, '.') : NULL;
const char *candidate_short = last_dot ? last_dot + 1 : qn;
@@ -15352,7 +15352,7 @@ TEST(registry_short_name_indexes) {
{
int exact_count = 0;
int candidate;
while ((candidate = cbm_type_name_iter_next(&nit)) >= 0) {
while ((candidate = cbm_type_short_iter_next(&nit)) >= 0) {
const char *qn = reg.types[candidate].qualified_name;
const char *last_dot = qn ? strrchr(qn, '.') : NULL;
const char *candidate_short = last_dot ? last_dot + 1 : qn;
@@ -15382,7 +15382,7 @@ TEST(registry_short_name_indexes) {
int actual;
for (;;) {
actual = cbm_type_name_iter_next(&nit);
actual = cbm_type_short_iter_next(&nit);
if (actual < 0)
break;
const char *qn = reg.types[actual].qualified_name;
@@ -15437,11 +15437,11 @@ TEST(registry_short_name_indexes) {
t.short_name = "Trait";
cbm_registry_add_type(&reg, t);
cbm_registry_types_by_short_name(&reg, "Trait", &nit);
ASSERT_EQ(cbm_type_name_iter_next(&nit), 0);
ASSERT_EQ(cbm_type_name_iter_next(&nit), 4);
ASSERT_EQ(cbm_type_name_iter_next(&nit), 5);
ASSERT_EQ(cbm_type_name_iter_next(&nit), tail_type_i);
ASSERT_EQ(cbm_type_name_iter_next(&nit), -1);
ASSERT_EQ(cbm_type_short_iter_next(&nit), 0);
ASSERT_EQ(cbm_type_short_iter_next(&nit), 4);
ASSERT_EQ(cbm_type_short_iter_next(&nit), 5);
ASSERT_EQ(cbm_type_short_iter_next(&nit), tail_type_i);
ASSERT_EQ(cbm_type_short_iter_next(&nit), -1);
int *saved_type_short_buckets = reg.type_short_buckets;
reg.type_short_buckets = NULL;
@@ -15449,7 +15449,7 @@ TEST(registry_short_name_indexes) {
int fallback_expected[] = {0, 4, 5, tail_type_i};
int fallback_count = 0;
int candidate;
while ((candidate = cbm_type_name_iter_next(&nit)) >= 0) {
while ((candidate = cbm_type_short_iter_next(&nit)) >= 0) {
const char *qn = reg.types[candidate].qualified_name;
const char *last_dot = qn ? strrchr(qn, '.') : NULL;
const char *candidate_short = last_dot ? last_dot + 1 : qn;
@@ -15479,7 +15479,7 @@ TEST(registry_short_name_indexes) {
cbm_registry_types_by_short_name(&reg, "Trait", &nit);
fallback_count = 0;
while ((candidate = cbm_type_name_iter_next(&nit)) >= 0) {
while ((candidate = cbm_type_short_iter_next(&nit)) >= 0) {
const char *qn = reg.types[candidate].qualified_name;
const char *last_dot = qn ? strrchr(qn, '.') : NULL;
const char *candidate_short = last_dot ? last_dot + 1 : qn;