mirror of
https://github.com/DeusData/codebase-memory-mcp.git
synced 2026-11-04 10:48:20 +00:00
perf(registry,rust-lsp): embedded-type + free-func short-name indexes
The Rust cross-file resolver's trait-method, sole-trait-impl, and free-func fallback loops scanned the ENTIRE type/func registry per call site on a miss. On kernel-scale registries (~10^6 entries, macro-inflated) four tiny trait-heavy rust files burned ~96 s EACH (~200k eval-capped call sites x full-registry strcmp scans). Fix: two hash indexes built at registry finalize, mirroring the existing bucket+entries idiom -- fnv1a(bare(embedded_type)) -> chain of declaring type indices (trait -> implementers), and fnv1a(short_name) -> chain of free-func indices. Chains preserve ascending registry order (first-match tie-break unchanged); allocation-free iterators fall back to the post-finalize tail and to a full scan on unfinalized registries, so the candidate set is identical in every mode. The four rust loops now walk the iterator and re-apply their exact original predicates -- byte-identical results, loop bodies untouched. Verified: graph equivalence on the kernel rust/ subtree (nodes 9654, edges 44798 identical with and without the change); registry_short_name_indexes unit test pins iterator contents/order/dedup; suites c_lsp 750, rust_lsp 505, registry 54, pipeline 216 -- zero failures. Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
This commit is contained in:
1 parent
bd005b4b09
commit
2abcebc821
4 files changed
+345
-5
No files matched your search
@@ -2334,10 +2334,16 @@ static const CBMRegisteredFunc *rust_resolve_trait_method(RustLSPContext *ctx,
|
||||
}
|
||||
|
||||
/* Look at every type whose embedded_types include the receiver_type_qn
|
||||
* (treated as a trait): pick the single-impl case. */
|
||||
* (treated as a trait): pick the single-impl case. Prefilter to the types whose
|
||||
* embedded_types carry a matching BARE name via the registry index; the exact
|
||||
* full-QN check below is unchanged, so the result set is identical. */
|
||||
const CBMRegisteredFunc *unique = NULL;
|
||||
int impls = 0;
|
||||
for (int ti = 0; ti < reg->type_count && impls < 3; ti++) {
|
||||
const char *rdot = strrchr(receiver_type_qn, '.');
|
||||
const char *rbare = rdot ? rdot + 1 : receiver_type_qn;
|
||||
CBMTypeEmbedIter eit;
|
||||
cbm_registry_types_by_embedded_bare(reg, rbare, &eit);
|
||||
for (int ti; impls < 3 && (ti = cbm_type_embed_iter_next(&eit)) >= 0;) {
|
||||
const CBMRegisteredType *t = ®->types[ti];
|
||||
if (!t->embedded_types)
|
||||
continue;
|
||||
@@ -2400,7 +2406,12 @@ static const CBMRegisteredFunc *rust_find_sole_trait_impl(RustLSPContext *ctx, c
|
||||
const CBMRegisteredFunc *first = NULL;
|
||||
const char *first_qn = NULL;
|
||||
int n = 0;
|
||||
for (int ti = 0; ti < reg->type_count && n < 2; ti++) {
|
||||
/* Prefilter to types whose embedded_types carry the trait's BARE name; the
|
||||
* exact (full-QN OR bare) check below is unchanged. tbare-keyed index captures
|
||||
* every original match (a full-QN match implies a bare-name match). */
|
||||
CBMTypeEmbedIter eit;
|
||||
cbm_registry_types_by_embedded_bare(reg, tbare, &eit);
|
||||
for (int ti; n < 2 && (ti = cbm_type_embed_iter_next(&eit)) >= 0;) {
|
||||
const CBMRegisteredType *t = ®->types[ti];
|
||||
if (!t->embedded_types || !t->qualified_name)
|
||||
continue;
|
||||
@@ -3762,7 +3773,11 @@ static void rust_resolve_call_expression(RustLSPContext *ctx, TSNode node) {
|
||||
char *needle = cbm_arena_sprintf(ctx->arena, ".%s.", head);
|
||||
const CBMRegisteredFunc *mem_unique = NULL;
|
||||
int mem_matches = 0;
|
||||
for (int i = 0; i < ctx->registry->func_count && mem_matches < 2; i++) {
|
||||
/* Iterate only free funcs whose short_name == tail via the index;
|
||||
* the receiver/short_name/needle re-checks below are unchanged. */
|
||||
CBMFreeFuncIter ffit;
|
||||
cbm_registry_free_funcs_by_short_name(ctx->registry, tail, &ffit);
|
||||
for (int i; mem_matches < 2 && (i = cbm_free_func_iter_next(&ffit)) >= 0;) {
|
||||
const CBMRegisteredFunc *f = &ctx->registry->funcs[i];
|
||||
if (!f->short_name || !f->qualified_name)
|
||||
continue;
|
||||
@@ -3800,7 +3815,11 @@ static void rust_resolve_call_expression(RustLSPContext *ctx, TSNode node) {
|
||||
first_dot ? (size_t)(first_dot - ctx->module_qn) : strlen(ctx->module_qn);
|
||||
const CBMRegisteredFunc *unique = NULL;
|
||||
int matches = 0;
|
||||
for (int i = 0; i < ctx->registry->func_count && matches < 2; i++) {
|
||||
/* Iterate only free funcs whose short_name == tail via the index; the
|
||||
* receiver/short_name/crate-prefix re-checks below are unchanged. */
|
||||
CBMFreeFuncIter ffit;
|
||||
cbm_registry_free_funcs_by_short_name(ctx->registry, tail, &ffit);
|
||||
for (int i; matches < 2 && (i = cbm_free_func_iter_next(&ffit)) >= 0;) {
|
||||
const CBMRegisteredFunc *f = &ctx->registry->funcs[i];
|
||||
if (!f->short_name || !f->qualified_name)
|
||||
continue;
|
||||
|
||||
@@ -115,12 +115,185 @@ static void build_method_index(CBMTypeRegistry *reg, CBMArena *idx_arena) {
|
||||
reg->method_entry_count = idx;
|
||||
}
|
||||
|
||||
/* Index: bare(embedded_type) -> chain of TYPE indices declaring it. One entry per
|
||||
* (type, embedded_type). Types are iterated DESCENDING and prepended so each bucket
|
||||
* chain ends up in ASCENDING type-index order (preserves the Rust loops' first-match
|
||||
* tie-break); a type's multiple entries land adjacent so the iterator can dedup them. */
|
||||
static void build_type_embed_index(CBMTypeRegistry *reg, CBMArena *idx_arena) {
|
||||
int ecount = 0;
|
||||
for (int i = 0; i < reg->type_count; i++) {
|
||||
const char **e = reg->types[i].embedded_types;
|
||||
if (!e)
|
||||
continue;
|
||||
for (int j = 0; e[j]; j++)
|
||||
ecount++;
|
||||
}
|
||||
if (ecount == 0)
|
||||
return;
|
||||
int bucket_count = next_pow2(ecount * 2);
|
||||
if (bucket_count < 16)
|
||||
bucket_count = 16;
|
||||
int *buckets = (int *)cbm_arena_alloc(idx_arena, (size_t)bucket_count * sizeof(int));
|
||||
CBMRegistryHashEntry *entries = (CBMRegistryHashEntry *)cbm_arena_alloc(
|
||||
idx_arena, (size_t)ecount * sizeof(CBMRegistryHashEntry));
|
||||
if (!buckets || !entries)
|
||||
return;
|
||||
for (int i = 0; i < bucket_count; i++)
|
||||
buckets[i] = -1;
|
||||
int idx = 0;
|
||||
for (int i = reg->type_count - 1; i >= 0; i--) {
|
||||
const char **e = reg->types[i].embedded_types;
|
||||
if (!e)
|
||||
continue;
|
||||
for (int j = 0; e[j]; j++) {
|
||||
const char *s = e[j];
|
||||
if (!s)
|
||||
continue;
|
||||
const char *dot = strrchr(s, '.');
|
||||
const char *bare = dot ? dot + 1 : s;
|
||||
uint64_t h = fnv1a(bare);
|
||||
int slot = (int)(h & (uint64_t)(bucket_count - 1));
|
||||
entries[idx].hash = h;
|
||||
entries[idx].payload_index = i;
|
||||
entries[idx].next_index = buckets[slot];
|
||||
entries[idx].slot = slot;
|
||||
buckets[slot] = idx;
|
||||
idx++;
|
||||
}
|
||||
}
|
||||
reg->type_embed_buckets = buckets;
|
||||
reg->type_embed_entries = entries;
|
||||
reg->type_embed_bucket_count = bucket_count;
|
||||
reg->type_embed_entry_count = idx;
|
||||
}
|
||||
|
||||
/* Index: short_name -> chain of FREE-function (receiver_type==NULL) indices.
|
||||
* Descending-iterate + prepend for ascending chain order (as above). */
|
||||
static void build_ffunc_short_index(CBMTypeRegistry *reg, CBMArena *idx_arena) {
|
||||
int fcount = 0;
|
||||
for (int i = 0; i < reg->func_count; i++) {
|
||||
const CBMRegisteredFunc *f = ®->funcs[i];
|
||||
if (!f->receiver_type && f->short_name)
|
||||
fcount++;
|
||||
}
|
||||
if (fcount == 0)
|
||||
return;
|
||||
int bucket_count = next_pow2(fcount * 2);
|
||||
if (bucket_count < 16)
|
||||
bucket_count = 16;
|
||||
int *buckets = (int *)cbm_arena_alloc(idx_arena, (size_t)bucket_count * sizeof(int));
|
||||
CBMRegistryHashEntry *entries = (CBMRegistryHashEntry *)cbm_arena_alloc(
|
||||
idx_arena, (size_t)fcount * sizeof(CBMRegistryHashEntry));
|
||||
if (!buckets || !entries)
|
||||
return;
|
||||
for (int i = 0; i < bucket_count; i++)
|
||||
buckets[i] = -1;
|
||||
int idx = 0;
|
||||
for (int i = reg->func_count - 1; i >= 0; i--) {
|
||||
const CBMRegisteredFunc *f = ®->funcs[i];
|
||||
if (f->receiver_type || !f->short_name)
|
||||
continue;
|
||||
uint64_t h = fnv1a(f->short_name);
|
||||
int slot = (int)(h & (uint64_t)(bucket_count - 1));
|
||||
entries[idx].hash = h;
|
||||
entries[idx].payload_index = i;
|
||||
entries[idx].next_index = buckets[slot];
|
||||
entries[idx].slot = slot;
|
||||
buckets[slot] = idx;
|
||||
idx++;
|
||||
}
|
||||
reg->ffunc_short_buckets = buckets;
|
||||
reg->ffunc_short_entries = entries;
|
||||
reg->ffunc_short_bucket_count = bucket_count;
|
||||
reg->ffunc_short_entry_count = idx;
|
||||
}
|
||||
|
||||
void cbm_registry_types_by_embedded_bare(const CBMTypeRegistry *reg, const char *bare,
|
||||
CBMTypeEmbedIter *out) {
|
||||
out->reg = reg;
|
||||
out->hash = fnv1a(bare);
|
||||
out->prev_type = -1;
|
||||
if (reg->type_qn_buckets && reg->type_qn_bucket_count > 0) {
|
||||
/* finalized: walk the embed chain (if built) then the post-finalize tail */
|
||||
if (reg->type_embed_buckets && reg->type_embed_bucket_count > 0) {
|
||||
int slot = (int)(out->hash & (uint64_t)(reg->type_embed_bucket_count - 1));
|
||||
out->chain_idx = reg->type_embed_buckets[slot];
|
||||
} else {
|
||||
out->chain_idx = -1;
|
||||
}
|
||||
out->tail_i = reg->type_qn_entry_count;
|
||||
out->tail_end = reg->type_count;
|
||||
} else {
|
||||
/* unfinalized: full linear scan over all types (identical candidate set) */
|
||||
out->chain_idx = -1;
|
||||
out->tail_i = 0;
|
||||
out->tail_end = reg->type_count;
|
||||
}
|
||||
}
|
||||
|
||||
int cbm_type_embed_iter_next(CBMTypeEmbedIter *it) {
|
||||
const CBMTypeRegistry *reg = it->reg;
|
||||
while (it->chain_idx >= 0) {
|
||||
const CBMRegistryHashEntry *e = ®->type_embed_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; /* hash collision */
|
||||
if (p == it->prev_type)
|
||||
continue; /* same type, multiple matching embeds — yield once */
|
||||
it->prev_type = p;
|
||||
return p;
|
||||
}
|
||||
if (it->tail_i < it->tail_end)
|
||||
return it->tail_i++;
|
||||
return -1;
|
||||
}
|
||||
|
||||
void cbm_registry_free_funcs_by_short_name(const CBMTypeRegistry *reg, const char *short_name,
|
||||
CBMFreeFuncIter *out) {
|
||||
out->reg = reg;
|
||||
out->hash = fnv1a(short_name);
|
||||
if (reg->func_qn_buckets && reg->func_qn_bucket_count > 0) {
|
||||
if (reg->ffunc_short_buckets && reg->ffunc_short_bucket_count > 0) {
|
||||
int slot = (int)(out->hash & (uint64_t)(reg->ffunc_short_bucket_count - 1));
|
||||
out->chain_idx = reg->ffunc_short_buckets[slot];
|
||||
} else {
|
||||
out->chain_idx = -1;
|
||||
}
|
||||
out->tail_i = reg->func_qn_entry_count;
|
||||
out->tail_end = reg->func_count;
|
||||
} else {
|
||||
out->chain_idx = -1;
|
||||
out->tail_i = 0;
|
||||
out->tail_end = reg->func_count;
|
||||
}
|
||||
}
|
||||
|
||||
int cbm_free_func_iter_next(CBMFreeFuncIter *it) {
|
||||
const CBMTypeRegistry *reg = it->reg;
|
||||
while (it->chain_idx >= 0) {
|
||||
const CBMRegistryHashEntry *e = ®->ffunc_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; /* each free func has one short_name → no dedup needed */
|
||||
}
|
||||
if (it->tail_i < it->tail_end)
|
||||
return it->tail_i++;
|
||||
return -1;
|
||||
}
|
||||
|
||||
void cbm_registry_finalize_into(CBMTypeRegistry *reg, CBMArena *idx_arena) {
|
||||
if (!reg || !idx_arena)
|
||||
return;
|
||||
build_qn_index(reg, idx_arena, /*for_funcs=*/true);
|
||||
build_qn_index(reg, idx_arena, /*for_funcs=*/false);
|
||||
build_method_index(reg, idx_arena);
|
||||
build_type_embed_index(reg, idx_arena);
|
||||
build_ffunc_short_index(reg, idx_arena);
|
||||
}
|
||||
|
||||
void cbm_registry_finalize(CBMTypeRegistry *reg) {
|
||||
|
||||
@@ -101,6 +101,23 @@ typedef struct CBMTypeRegistry {
|
||||
int method_bucket_count;
|
||||
int method_entry_count;
|
||||
|
||||
// Auxiliary short-name / embedded-type indexes (built by finalize alongside the
|
||||
// QN buckets). Turn the Rust trait- and free-function fallback scans from
|
||||
// O(type_count)/O(func_count) into O(chain). Read-only after finalize.
|
||||
// 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).
|
||||
int *type_embed_buckets;
|
||||
CBMRegistryHashEntry *type_embed_entries;
|
||||
int type_embed_bucket_count;
|
||||
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 *ffunc_short_buckets;
|
||||
CBMRegistryHashEntry *ffunc_short_entries;
|
||||
int ffunc_short_bucket_count;
|
||||
int ffunc_short_entry_count;
|
||||
|
||||
/* Sealed / read-only. Set true by the cbm_X_build_cross_registry builders
|
||||
* (c/cpp, python, c#, ts, go) right after finalize: a Tier-2 cross-registry
|
||||
* is built ONCE and shared READ-ONLY across the parallel resolve workers.
|
||||
@@ -188,6 +205,43 @@ const CBMRegisteredFunc *cbm_registry_lookup_symbol_by_types(const CBMTypeRegist
|
||||
const CBMType **arg_types,
|
||||
int arg_count);
|
||||
|
||||
// --- Auxiliary index iterators (Rust trait / free-function fallback fast paths) ---
|
||||
//
|
||||
// Iterate registry TYPE indices whose embedded_types contain an entry whose BARE
|
||||
// name (last '.'-segment) equals `bare`. On a finalized registry this walks the
|
||||
// embedded-type index plus any post-finalize tail; on an unfinalized registry it
|
||||
// degrades to a full linear scan over all types (identical candidate set). Each
|
||||
// matching type index is yielded at most once, in ascending registry order. The
|
||||
// index is a bare-name PREFILTER — the caller MUST still apply its own exact
|
||||
// predicate on each yielded type. Read-only, allocation-free. Usage:
|
||||
// CBMTypeEmbedIter it; cbm_registry_types_by_embedded_bare(reg, bare, &it);
|
||||
// int ti; while ((ti = cbm_type_embed_iter_next(&it)) >= 0) { ... reg->types[ti] ... }
|
||||
typedef struct {
|
||||
const CBMTypeRegistry *reg;
|
||||
uint64_t hash;
|
||||
int chain_idx; // next entry in the embed chain, or -1
|
||||
int tail_i; // next tail/linear type index
|
||||
int tail_end; // reg->type_count snapshot
|
||||
int prev_type; // last yielded type index (adjacent-dedup); -1 = none
|
||||
} CBMTypeEmbedIter;
|
||||
void cbm_registry_types_by_embedded_bare(const CBMTypeRegistry *reg, const char *bare,
|
||||
CBMTypeEmbedIter *out);
|
||||
int cbm_type_embed_iter_next(CBMTypeEmbedIter *it);
|
||||
|
||||
// Iterate FREE-function (receiver_type==NULL) indices whose short_name equals
|
||||
// `short_name`. Same finalized/unfinalized behavior as above; caller re-checks its
|
||||
// own predicate. Read-only, allocation-free.
|
||||
typedef struct {
|
||||
const CBMTypeRegistry *reg;
|
||||
uint64_t hash;
|
||||
int chain_idx;
|
||||
int tail_i;
|
||||
int tail_end;
|
||||
} CBMFreeFuncIter;
|
||||
void cbm_registry_free_funcs_by_short_name(const CBMTypeRegistry *reg, const char *short_name,
|
||||
CBMFreeFuncIter *out);
|
||||
int cbm_free_func_iter_next(CBMFreeFuncIter *it);
|
||||
|
||||
// --- TS-specific helpers (return NULL for types without these signatures) ---
|
||||
|
||||
// If the type has a call signature (e.g., `interface F { (x:number): string }`), return
|
||||
|
||||
@@ -15231,6 +15231,99 @@ TEST(clsp_tier2_shared_registry_readonly_c) {
|
||||
PASS();
|
||||
}
|
||||
|
||||
/* Direct guard for the finalize-time short-name / embedded-type indexes and their
|
||||
* iterators (type_registry.c), which the Rust trait/free-func fast paths rely on.
|
||||
* Verifies: embed index yields exactly the types whose embedded_types carry a
|
||||
* matching BARE name, in ascending registry order, deduped when a type lists the
|
||||
* same bare twice; free-func index yields only free funcs (receiver_type==NULL) with
|
||||
* the given short_name, not methods. */
|
||||
TEST(registry_short_name_indexes) {
|
||||
CBMArena arena;
|
||||
cbm_arena_init(&arena);
|
||||
CBMTypeRegistry reg;
|
||||
cbm_registry_init(®, &arena);
|
||||
|
||||
/* type 0: Trait (no embeds). type 1: A impl "pkg.Trait". type 2: B impl bare
|
||||
* "Trait" AND a second embed also bare "Trait" (dedup case). type 3: C impl
|
||||
* "pkg.Other". */
|
||||
const char *a_emb[] = {"pkg.Trait", NULL};
|
||||
const char *b_emb[] = {"other.Trait", "misc.Trait", NULL}; /* two entries, bare "Trait" */
|
||||
const char *c_emb[] = {"pkg.Other", NULL};
|
||||
CBMRegisteredType t;
|
||||
memset(&t, 0, sizeof(t));
|
||||
t.qualified_name = "pkg.Trait";
|
||||
t.short_name = "Trait";
|
||||
cbm_registry_add_type(®, t);
|
||||
memset(&t, 0, sizeof(t));
|
||||
t.qualified_name = "pkg.A";
|
||||
t.short_name = "A";
|
||||
t.embedded_types = a_emb;
|
||||
cbm_registry_add_type(®, t);
|
||||
memset(&t, 0, sizeof(t));
|
||||
t.qualified_name = "pkg.B";
|
||||
t.short_name = "B";
|
||||
t.embedded_types = b_emb;
|
||||
cbm_registry_add_type(®, t);
|
||||
memset(&t, 0, sizeof(t));
|
||||
t.qualified_name = "pkg.C";
|
||||
t.short_name = "C";
|
||||
t.embedded_types = c_emb;
|
||||
cbm_registry_add_type(®, t);
|
||||
|
||||
/* free func "helper" (x2 — different QNs), method "M.helper", free func "other". */
|
||||
CBMRegisteredFunc f;
|
||||
memset(&f, 0, sizeof(f));
|
||||
f.qualified_name = "pkg.helper";
|
||||
f.short_name = "helper";
|
||||
cbm_registry_add_func(®, f);
|
||||
memset(&f, 0, sizeof(f));
|
||||
f.qualified_name = "pkg.sub.helper";
|
||||
f.short_name = "helper";
|
||||
cbm_registry_add_func(®, f);
|
||||
memset(&f, 0, sizeof(f));
|
||||
f.qualified_name = "pkg.M.helper";
|
||||
f.short_name = "helper";
|
||||
f.receiver_type = "pkg.M"; /* method — must NOT appear in the free-func index */
|
||||
cbm_registry_add_func(®, f);
|
||||
memset(&f, 0, sizeof(f));
|
||||
f.qualified_name = "pkg.other";
|
||||
f.short_name = "other";
|
||||
cbm_registry_add_func(®, f);
|
||||
|
||||
cbm_registry_finalize(®);
|
||||
|
||||
/* Embed index for bare "Trait": types 1 (A) then 2 (B), ascending, B once. */
|
||||
CBMTypeEmbedIter it;
|
||||
cbm_registry_types_by_embedded_bare(®, "Trait", &it);
|
||||
ASSERT_EQ(cbm_type_embed_iter_next(&it), 1);
|
||||
ASSERT_EQ(cbm_type_embed_iter_next(&it), 2);
|
||||
ASSERT_EQ(cbm_type_embed_iter_next(&it), -1);
|
||||
|
||||
/* Bare "Other": only type 3 (C). */
|
||||
cbm_registry_types_by_embedded_bare(®, "Other", &it);
|
||||
ASSERT_EQ(cbm_type_embed_iter_next(&it), 3);
|
||||
ASSERT_EQ(cbm_type_embed_iter_next(&it), -1);
|
||||
|
||||
/* Bare with no implementers: empty. */
|
||||
cbm_registry_types_by_embedded_bare(®, "Nope", &it);
|
||||
ASSERT_EQ(cbm_type_embed_iter_next(&it), -1);
|
||||
|
||||
/* Free-func index for "helper": func 0 then 1 (ascending), NOT the method (2). */
|
||||
CBMFreeFuncIter fit;
|
||||
cbm_registry_free_funcs_by_short_name(®, "helper", &fit);
|
||||
ASSERT_EQ(cbm_free_func_iter_next(&fit), 0);
|
||||
ASSERT_EQ(cbm_free_func_iter_next(&fit), 1);
|
||||
ASSERT_EQ(cbm_free_func_iter_next(&fit), -1);
|
||||
|
||||
/* Free-func "other": only func 3. */
|
||||
cbm_registry_free_funcs_by_short_name(®, "other", &fit);
|
||||
ASSERT_EQ(cbm_free_func_iter_next(&fit), 3);
|
||||
ASSERT_EQ(cbm_free_func_iter_next(&fit), -1);
|
||||
|
||||
cbm_arena_destroy(&arena);
|
||||
PASS();
|
||||
}
|
||||
|
||||
/* ── Suite ─────────────────────────────────────────────────────── */
|
||||
|
||||
/* C++ sibling guard: the same shared-registry read-only invariant for the
|
||||
@@ -15417,6 +15510,7 @@ SUITE(c_lsp) {
|
||||
RUN_TEST(seal_cs_shared_registry_readonly);
|
||||
RUN_TEST(seal_ts_shared_registry_readonly);
|
||||
RUN_TEST(seal_go_shared_registry_readonly);
|
||||
RUN_TEST(registry_short_name_indexes);
|
||||
RUN_TEST(clsp_simple_var_decl);
|
||||
RUN_TEST(clsp_pointer_arrow);
|
||||
RUN_TEST(clsp_dot_access);
|
||||
|
||||
Reference in new issue
Block a user