Files
wehub-resource-sync 41cb1c0170
OpenSSF Scorecard / scorecard (push) Failing after 0s
DCO / dco (push) Failing after 0s
CodeQL SAST / analyze (push) Failing after 1s
Deploy Pages / deploy (push) Failing after 1s
chore: import upstream snapshot with attribution
2026-07-13 12:28:05 +08:00

231 lines
8.1 KiB
C

/*
* lsp_neg_memo.h — generic negative-lookup memo for LSP resolve cascades.
*
* Problem (all hybrid-LSP languages): a resolve cascade probes a ladder of
* hypotheses (direct hit, module-prefixed retry, alias walk, trait/base
* dispatch, short-name fallback) and most probes MISS — macro-expanded or
* generated code asks the SAME failing question thousands of times per file,
* re-paying the whole ladder each time (linux kernel: 4 trait-heavy rust
* files at ~63 s each; the C resolver had the same disease before its memo).
*
* A miss is a pure fact of (registry, query) — but ONLY while the registry
* cannot change. Callers must therefore gate the memo on a SEALED registry
* (reg->read_only, set at finalize; cbm_registry_add_* hard-return then) and
* must key only queries whose cascade reads nothing but the registry and the
* query strings (no per-function scope state in the memoized rungs).
*
* Shape: open-addressing set of 64-bit keys, arena-backed (dies with the
* per-file arena — no explicit free, mirroring the py field overlay). A memo
* HIT means "this exact query already ran the full cascade and returned
* nothing" → the caller returns its miss result immediately. To make a hash
* collision harmless, callers keep their cheap DIRECT lookup before the memo
* check (the C-memo pattern): a colliding real hit is still found; only the
* expensive registry-pure miss ladder is skipped.
*
* Per-language wiring is a few lines: add a CBMNegMemo to the language ctx,
* key each cascade entry with cbm_negmemo_key (site tag + query strings),
* check at entry, insert on the miss return. Wired: rust. Candidates per the
* 2026-07 resolve audit: php, c# (extension methods), java, kotlin; the C
* resolver's bespoke memo in c_lsp.c predates this header and can migrate.
*/
#ifndef CBM_LSP_NEG_MEMO_H
#define CBM_LSP_NEG_MEMO_H
#include <stdbool.h>
#include <stdint.h>
#include <string.h>
#include "arena.h"
typedef struct {
uint64_t *slots; /* arena-owned; 0 = empty (keys are never 0) */
int cap; /* power of two */
int count;
} CBMNegMemo;
enum {
CBM_NEGMEMO_INIT_CAP = 1024,
CBM_NEGMEMO_GROW = 2,
CBM_NEGMEMO_LOAD_NUM = 7, /* grow at 70% load */
CBM_NEGMEMO_LOAD_DEN = 10,
};
/* FNV-1a over (site tag, a, 0xff, b). The site tag keeps two cascades with
* the same argument strings from sharing keys. Never returns 0. */
static inline uint64_t cbm_negmemo_key(uint8_t site, const char *a, const char *b) {
uint64_t h = 0xcbf29ce484222325ULL;
h ^= site;
h *= 0x100000001b3ULL;
if (a) {
while (*a) {
h ^= (unsigned char)*a++;
h *= 0x100000001b3ULL;
}
}
h ^= 0xff;
h *= 0x100000001b3ULL;
if (b) {
while (*b) {
h ^= (unsigned char)*b++;
h *= 0x100000001b3ULL;
}
}
return h ? h : 1;
}
static inline bool cbm_negmemo_contains(const CBMNegMemo *m, uint64_t key) {
if (!m->slots || m->count == 0) {
return false;
}
uint64_t mask = (uint64_t)(m->cap - 1);
for (uint64_t i = key & mask;; i = (i + 1) & mask) {
uint64_t s = m->slots[i];
if (s == 0) {
return false;
}
if (s == key) {
return true;
}
}
}
static inline void cbm_negmemo_insert_raw(uint64_t *slots, int cap, uint64_t key) {
uint64_t mask = (uint64_t)(cap - 1);
for (uint64_t i = key & mask;; i = (i + 1) & mask) {
if (slots[i] == key) {
return;
}
if (slots[i] == 0) {
slots[i] = key;
return;
}
}
}
/* Arena-backed insert: lazy first allocation, grow-by-rehash at 70% load.
* The abandoned table stays in the arena (bounded, freed with the file). */
static inline void cbm_negmemo_insert(CBMNegMemo *m, CBMArena *arena, uint64_t key) {
if (!arena) {
return; /* no arena — memo silently disabled */
}
if (!m->slots) {
m->slots = cbm_arena_alloc(arena, sizeof(uint64_t) * CBM_NEGMEMO_INIT_CAP);
if (!m->slots) {
return;
}
memset(m->slots, 0, sizeof(uint64_t) * CBM_NEGMEMO_INIT_CAP);
m->cap = CBM_NEGMEMO_INIT_CAP;
m->count = 0;
}
if ((int64_t)(m->count + 1) * CBM_NEGMEMO_LOAD_DEN >=
(int64_t)m->cap * CBM_NEGMEMO_LOAD_NUM) {
int new_cap = m->cap * CBM_NEGMEMO_GROW;
uint64_t *ns = cbm_arena_alloc(arena, sizeof(uint64_t) * (size_t)new_cap);
if (!ns) {
return; /* keep the old (full-ish) table; inserts degrade, reads stay correct */
}
memset(ns, 0, sizeof(uint64_t) * (size_t)new_cap);
for (int i = 0; i < m->cap; i++) {
if (m->slots[i]) {
cbm_negmemo_insert_raw(ns, new_cap, m->slots[i]);
}
}
m->slots = ns;
m->cap = new_cap;
}
if (!cbm_negmemo_contains(m, key)) {
cbm_negmemo_insert_raw(m->slots, m->cap, key);
m->count++;
}
}
/* ── CBMIdxMemo: exact-match string-key → int index map ─────────────────────
* Companion for build-time registration loops that need "have I registered
* this QN, and at which index?" in O(1) — the registry's own buckets don't
* exist before finalize, and probing it linearly from inside the registration
* loop is the classic quadratic (kernel shared rust registry: ~63 s).
* Exact match: each slot stores the borrowed key pointer and verifies with
* strcmp, so hash collisions can't map to a wrong index. First-put wins. */
typedef struct {
struct cbm_idxmemo_slot {
uint64_t h; /* 0 = empty */
const char *key;
int32_t val;
} *slots;
int cap; /* power of two */
int count;
} CBMIdxMemo;
static inline int32_t cbm_idxmemo_get(const CBMIdxMemo *m, const char *key) {
if (!m->slots || !key || m->count == 0) {
return -1;
}
uint64_t h = cbm_negmemo_key(0, key, NULL);
uint64_t mask = (uint64_t)(m->cap - 1);
for (uint64_t i = h & mask;; i = (i + 1) & mask) {
if (m->slots[i].h == 0) {
return -1;
}
if (m->slots[i].h == h && m->slots[i].key && strcmp(m->slots[i].key, key) == 0) {
return m->slots[i].val;
}
}
}
static inline void cbm_idxmemo_put_raw(struct cbm_idxmemo_slot *slots, int cap, uint64_t h,
const char *key, int32_t val) {
uint64_t mask = (uint64_t)(cap - 1);
for (uint64_t i = h & mask;; i = (i + 1) & mask) {
if (slots[i].h == 0) {
slots[i].h = h;
slots[i].key = key;
slots[i].val = val;
return;
}
if (slots[i].h == h && slots[i].key && strcmp(slots[i].key, key) == 0) {
return; /* first-put wins */
}
}
}
static inline void cbm_idxmemo_put_if_absent(CBMIdxMemo *m, CBMArena *arena, const char *key,
int32_t val) {
if (!arena || !key) {
return;
}
if (!m->slots) {
m->slots = cbm_arena_alloc(arena,
sizeof(struct cbm_idxmemo_slot) * CBM_NEGMEMO_INIT_CAP);
if (!m->slots) {
return;
}
memset(m->slots, 0, sizeof(struct cbm_idxmemo_slot) * CBM_NEGMEMO_INIT_CAP);
m->cap = CBM_NEGMEMO_INIT_CAP;
m->count = 0;
}
if ((int64_t)(m->count + 1) * CBM_NEGMEMO_LOAD_DEN >=
(int64_t)m->cap * CBM_NEGMEMO_LOAD_NUM) {
int new_cap = m->cap * CBM_NEGMEMO_GROW;
struct cbm_idxmemo_slot *ns =
cbm_arena_alloc(arena, sizeof(struct cbm_idxmemo_slot) * (size_t)new_cap);
if (!ns) {
return;
}
memset(ns, 0, sizeof(struct cbm_idxmemo_slot) * (size_t)new_cap);
for (int i = 0; i < m->cap; i++) {
if (m->slots[i].h) {
cbm_idxmemo_put_raw(ns, new_cap, m->slots[i].h, m->slots[i].key, m->slots[i].val);
}
}
m->slots = ns;
m->cap = new_cap;
}
uint64_t h = cbm_negmemo_key(0, key, NULL);
if (cbm_idxmemo_get(m, key) < 0) {
cbm_idxmemo_put_raw(m->slots, m->cap, h, key, val);
m->count++;
}
}
#endif /* CBM_LSP_NEG_MEMO_H */