Skip to content

Commit 617e9c5

Browse files
authored
Merge pull request #921 from DeusData/perf/futile-nap-fast-exit
perf: kernel-scale indexing campaign (-61% wall, deterministic output)
2 parents f092d92 + 3aae43f commit 617e9c5

38 files changed

Lines changed: 2619 additions & 223 deletions

Makefile.cbm

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -220,7 +220,7 @@ PIPELINE_SRCS = \
220220
SIMHASH_SRCS = src/simhash/minhash.c
221221

222222
# Semantic embedding module
223-
SEMANTIC_SRCS = src/semantic/semantic.c src/semantic/ast_profile.c
223+
SEMANTIC_SRCS = src/semantic/semantic.c src/semantic/ast_profile.c src/semantic/rotsq.c
224224

225225
# nomic-embed-code pretrained vectors (assembler blob)
226226
UNIXCODER_BLOB_SRC = vendored/nomic/code_vectors_blob.S
@@ -412,6 +412,7 @@ TEST_STACK_OVERFLOW_SRCS = tests/test_stack_overflow.c
412412
# Kept out of the gating `make test` so `ci-ok` stays green; run via `make test-repro`.
413413
TEST_REPRO_SRCS = \
414414
tests/repro/repro_main.c \
415+
tests/repro/repro_parallel_determinism.c \
415416
tests/repro/repro_extraction.c \
416417
tests/repro/repro_issue495.c \
417418
tests/repro/repro_issue521.c \

internal/cbm/lsp/c_lsp.c

Lines changed: 146 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,7 @@ void c_lsp_init(CLSPContext *ctx, CBMArena *arena, const char *source, int sourc
6464
ctx->source_len = source_len;
6565
ctx->registry = registry;
6666
ctx->module_qn = module_qn;
67+
ctx->module_qn_len = module_qn ? strlen(module_qn) : 0;
6768
ctx->cpp_mode = cpp_mode;
6869
ctx->resolved_calls = out;
6970
ctx->current_scope = cbm_scope_push(arena, NULL);
@@ -2506,28 +2507,148 @@ static const CBMType *c_eval_expr_type_inner(CLSPContext *ctx, TSNode node) {
25062507
// c_lookup_member: method/field lookup with base class traversal
25072508
// ============================================================================
25082509

2510+
// Build "<module_qn>.<type_qn>" into a caller-provided stack buffer, replacing the
2511+
// vsnprintf("%s.%s") path (whose %s does a strlen of both args) that otherwise
2512+
// dominates the kernel cross-file resolve miss cascade. Uses the cached module_qn_len.
2513+
// Returns buf on success, or NULL if the result would not fit (caller falls back to
2514+
// cbm_arena_sprintf). The buffer is only read by the immediate registry lookup — a
2515+
// stack lifetime is sufficient (the QN is never retained).
2516+
static const char *c_build_module_prefixed(const CLSPContext *ctx, const char *type_qn, char *buf,
2517+
size_t bufsz) {
2518+
size_t mlen = ctx->module_qn_len;
2519+
size_t tlen = strlen(type_qn);
2520+
if (mlen + 1 + tlen + 1 > bufsz)
2521+
return NULL; // would truncate → fall back to arena sprintf
2522+
memcpy(buf, ctx->module_qn, mlen);
2523+
buf[mlen] = '.';
2524+
memcpy(buf + mlen + 1, type_qn, tlen);
2525+
buf[mlen + 1 + tlen] = '\0';
2526+
return buf;
2527+
}
2528+
2529+
// FNV-1a over (type_qn, 0xff separator, member_name) for the negative-lookup memo.
2530+
// 0 is remapped to 1 so it can serve as the empty-slot sentinel.
2531+
static uint64_t c_neg_memo_hash(const char *type_qn, const char *member_name) {
2532+
uint64_t h = 1469598103934665603ULL; // FNV-1a offset basis
2533+
for (const unsigned char *p = (const unsigned char *)type_qn; *p; p++) {
2534+
h ^= (uint64_t)*p;
2535+
h *= 1099511628211ULL;
2536+
}
2537+
h ^= (uint64_t)0xffu;
2538+
h *= 1099511628211ULL;
2539+
for (const unsigned char *p = (const unsigned char *)member_name; *p; p++) {
2540+
h ^= (uint64_t)*p;
2541+
h *= 1099511628211ULL;
2542+
}
2543+
return h ? h : 1ULL;
2544+
}
2545+
2546+
static bool c_neg_memo_contains(const CLSPContext *ctx, uint64_t h) {
2547+
if (!ctx->neg_memo || ctx->neg_memo_cap == 0)
2548+
return false;
2549+
uint64_t mask = (uint64_t)ctx->neg_memo_cap - 1;
2550+
for (uint64_t i = h & mask;; i = (i + 1) & mask) {
2551+
uint64_t slot = ctx->neg_memo[i];
2552+
if (slot == 0)
2553+
return false; // empty slot → not present
2554+
if (slot == h)
2555+
return true;
2556+
}
2557+
}
2558+
2559+
static void c_neg_memo_insert(CLSPContext *ctx, uint64_t h) {
2560+
// Lazy alloc / grow-by-rehash at 70% load. On OOM, silently disable the memo
2561+
// (correctness is unaffected — the full cascade still runs).
2562+
if (ctx->neg_memo == NULL) {
2563+
uint64_t *nm = (uint64_t *)calloc(1024, sizeof(uint64_t));
2564+
if (!nm)
2565+
return;
2566+
ctx->neg_memo = nm;
2567+
ctx->neg_memo_cap = 1024;
2568+
ctx->neg_memo_count = 0;
2569+
} else if ((ctx->neg_memo_count + 1) * 10 >= ctx->neg_memo_cap * 7) {
2570+
int new_cap = ctx->neg_memo_cap * 2;
2571+
uint64_t *nm = (uint64_t *)calloc((size_t)new_cap, sizeof(uint64_t));
2572+
if (nm) {
2573+
uint64_t nmask = (uint64_t)new_cap - 1;
2574+
for (int j = 0; j < ctx->neg_memo_cap; j++) {
2575+
uint64_t v = ctx->neg_memo[j];
2576+
if (v == 0)
2577+
continue;
2578+
uint64_t k = v & nmask;
2579+
while (nm[k] != 0)
2580+
k = (k + 1) & nmask;
2581+
nm[k] = v;
2582+
}
2583+
free(ctx->neg_memo);
2584+
ctx->neg_memo = nm;
2585+
ctx->neg_memo_cap = new_cap;
2586+
}
2587+
// if calloc failed: keep the existing table (load may exceed 70%, still correct)
2588+
}
2589+
uint64_t mask = (uint64_t)ctx->neg_memo_cap - 1;
2590+
for (uint64_t i = h & mask;; i = (i + 1) & mask) {
2591+
if (ctx->neg_memo[i] == 0) {
2592+
ctx->neg_memo[i] = h;
2593+
ctx->neg_memo_count++;
2594+
return;
2595+
}
2596+
if (ctx->neg_memo[i] == h)
2597+
return; // already recorded
2598+
}
2599+
}
2600+
2601+
static void c_neg_memo_free(CLSPContext *ctx) {
2602+
if (ctx->neg_memo)
2603+
free(ctx->neg_memo);
2604+
ctx->neg_memo = NULL;
2605+
ctx->neg_memo_cap = 0;
2606+
ctx->neg_memo_count = 0;
2607+
}
2608+
25092609
static const CBMRegisteredFunc *c_lookup_member_depth(CLSPContext *ctx, const char *type_qn,
25102610
const char *member_name, int depth) {
25112611
if (!type_qn || !member_name)
25122612
return NULL;
25132613
if (depth > CBM_LSP_MAX_LOOKUP_DEPTH)
25142614
return NULL;
25152615

2516-
// Direct method lookup
2616+
// Direct method lookup. Runs FIRST, before consulting the memo, so a real
2617+
// direct-resolvable member (incl. a hash-collision victim, or one registered
2618+
// mid-file) can never be skipped: it is returned here regardless of the memo.
25172619
const CBMRegisteredFunc *f = cbm_registry_lookup_method(ctx->registry, type_qn, member_name);
25182620
if (f)
25192621
return f;
25202622

2521-
// Try module-prefixed QN (e.g., "Container" -> "test.main.Container")
2522-
if (ctx->module_qn) {
2523-
const char *prefixed = cbm_arena_sprintf(ctx->arena, "%s.%s", ctx->module_qn, type_qn);
2623+
// Negative-lookup memo (depth==0, shared read-only registry only). A recorded
2624+
// hit means this exact (type_qn, member) already failed the whole cascade. Under
2625+
// the sealed Tier-2 registry the module-prefix (below), base-class and short-name
2626+
// cascades are pure, immutable functions of (type_qn, registry), so they are
2627+
// provably still NULL and can be skipped. Only the SCOPE-ALIAS path is
2628+
// context-dependent, so it is still evaluated below before we trust the memo.
2629+
// The direct lookup above already served as the collision/staleness verification.
2630+
bool neg_memo_hit = false;
2631+
uint64_t neg_h = 0;
2632+
if (depth == 0 && ctx->registry_shared) {
2633+
neg_h = c_neg_memo_hash(type_qn, member_name);
2634+
neg_memo_hit = c_neg_memo_contains(ctx, neg_h);
2635+
}
2636+
2637+
// Try module-prefixed QN (e.g., "Container" -> "test.main.Container").
2638+
// Skipped on a memo hit: provably NULL under the read-only shared registry.
2639+
if (!neg_memo_hit && ctx->module_qn) {
2640+
char sbuf[1024];
2641+
const char *prefixed = c_build_module_prefixed(ctx, type_qn, sbuf, sizeof(sbuf));
2642+
if (!prefixed)
2643+
prefixed = cbm_arena_sprintf(ctx->arena, "%s.%s", ctx->module_qn, type_qn);
25242644
f = cbm_registry_lookup_method(ctx->registry, prefixed, member_name);
25252645
if (f)
25262646
return f;
25272647
}
25282648

25292649
// Scope-based alias: using Vec = std::vector<T>;
2530-
// Vec is in scope as ALIAS → follow to underlying type's QN
2650+
// Vec is in scope as ALIAS → follow to underlying type's QN.
2651+
// ALWAYS evaluated (scope is context-dependent — not covered by the memo).
25312652
{
25322653
const CBMType *scoped = cbm_scope_lookup(ctx->current_scope, type_qn);
25332654
if (scoped && scoped->kind == CBM_TYPE_ALIAS) {
@@ -2543,11 +2664,21 @@ static const CBMRegisteredFunc *c_lookup_member_depth(CLSPContext *ctx, const ch
25432664
}
25442665
}
25452666

2667+
// Memo hit and the scope-alias path also missed: the remaining base-class and
2668+
// short-name cascades are registry-only and provably NULL → skip them (this is
2669+
// where the O(type_count) short-name scan is avoided). Idempotent re-insert is
2670+
// unnecessary since neg_h is already present.
2671+
if (neg_memo_hit)
2672+
return NULL;
2673+
25462674
// Check registered type for alias and base classes
25472675
const CBMRegisteredType *rt = cbm_registry_lookup_type(ctx->registry, type_qn);
25482676
if (!rt && ctx->module_qn) {
2549-
rt = cbm_registry_lookup_type(
2550-
ctx->registry, cbm_arena_sprintf(ctx->arena, "%s.%s", ctx->module_qn, type_qn));
2677+
char sbuf[1024];
2678+
const char *prefixed = c_build_module_prefixed(ctx, type_qn, sbuf, sizeof(sbuf));
2679+
if (!prefixed)
2680+
prefixed = cbm_arena_sprintf(ctx->arena, "%s.%s", ctx->module_qn, type_qn);
2681+
rt = cbm_registry_lookup_type(ctx->registry, prefixed);
25512682
}
25522683
if (rt) {
25532684
// Alias chain
@@ -2606,6 +2737,10 @@ static const CBMRegisteredFunc *c_lookup_member_depth(CLSPContext *ctx, const ch
26062737
}
26072738
}
26082739

2740+
// Whole cascade missed: record the negative result so a repeat of this exact
2741+
// (type_qn, member) can skip the module-prefix/base-class/short-name work.
2742+
if (depth == 0 && ctx->registry_shared)
2743+
c_neg_memo_insert(ctx, neg_h);
26092744
return NULL;
26102745
}
26112746

@@ -4845,6 +4980,10 @@ __attribute__((no_sanitize("address"))) void c_lsp_process_file(CLSPContext *ctx
48454980
child = kids[i];
48464981
c_process_body_child(ctx, child);
48474982
}
4983+
4984+
// Release the per-file negative-lookup memo (malloc-owned; only allocated in
4985+
// shared-registry mode). No-op when it was never populated.
4986+
c_neg_memo_free(ctx);
48484987
}
48494988

48504989
// ============================================================================

internal/cbm/lsp/c_lsp.h

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,19 @@ typedef struct {
4444
const char *enclosing_func_qn;
4545
const char *enclosing_class_qn; // for implicit `this` resolution
4646
const char *module_qn;
47+
size_t module_qn_len; // cached strlen(module_qn); for stack-buffer QN building
48+
49+
// Negative-lookup memo for c_lookup_member_depth (depth==0 misses only).
50+
// Open-addressing uint64 hash SET; 0 is the empty-slot sentinel. Populated
51+
// ONLY when the Tier-2 registry is shared+read-only (registry_shared), where
52+
// the module-prefix/base-class/short-name cascades are pure, stable functions
53+
// of (type_qn, registry) — so a recorded miss can never turn into a hit. Lets
54+
// the hot resolve path skip the sprintf("%s.%s") strlen storm + the O(type_count)
55+
// short-name scan on repeated misses of the same (type_qn, member). malloc-owned;
56+
// freed at end of c_lsp_process_file.
57+
uint64_t *neg_memo;
58+
int neg_memo_cap; // power-of-two; 0 until first insert
59+
int neg_memo_count; // live entries (grow by rehash at 70% load)
4760

4861
// Output
4962
CBMResolvedCallArray *resolved_calls;

0 commit comments

Comments
 (0)