Skip to content

Commit dd3f4e8

Browse files
feat: add bounded two-pass large-repo indexing
Signed-off-by: Zhiyu <zhiyuzhang001@gmail.com>
1 parent f740623 commit dd3f4e8

13 files changed

Lines changed: 823 additions & 152 deletions

File tree

internal/cbm/arena.c

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ void cbm_arena_init(CBMArena *a) {
99
a->block_size = CBM_ARENA_DEFAULT_BLOCK_SIZE;
1010
a->blocks[0] = (char *)malloc(a->block_size);
1111
if (a->blocks[0]) {
12+
a->block_sizes[0] = a->block_size;
1213
a->nblocks = SKIP_ONE;
1314
}
1415
}
@@ -26,6 +27,7 @@ static int arena_grow(CBMArena *a, size_t min_size) {
2627
return 0;
2728
}
2829
a->blocks[a->nblocks] = block;
30+
a->block_sizes[a->nblocks] = new_size;
2931
a->nblocks++;
3032
a->block_size = new_size;
3133
a->used = 0;
@@ -39,6 +41,16 @@ void *cbm_arena_alloc(CBMArena *a, size_t n) {
3941
// 8-byte alignment
4042
n = (n + 7) & ~(size_t)7;
4143

44+
if (n <= 64) {
45+
a->alloc_le_64 += n;
46+
} else if (n <= 256) {
47+
a->alloc_le_256 += n;
48+
} else if (n <= 4096) {
49+
a->alloc_le_4096 += n;
50+
} else {
51+
a->alloc_gt_4096 += n;
52+
}
53+
4254
if (a->nblocks == 0) {
4355
return NULL;
4456
}
@@ -51,6 +63,7 @@ void *cbm_arena_alloc(CBMArena *a, size_t n) {
5163

5264
char *ptr = a->blocks[a->nblocks - SKIP_ONE] + a->used;
5365
a->used += n;
66+
a->total_alloc += n;
5467
return ptr;
5568
}
5669

@@ -60,6 +73,7 @@ char *cbm_arena_strdup(CBMArena *a, const char *s) {
6073
size_t len = strlen(s);
6174
char *dst = (char *)cbm_arena_alloc(a, len + SKIP_ONE);
6275
if (dst) {
76+
a->strdup_alloc += len + SKIP_ONE;
6377
memcpy(dst, s, len + SKIP_ONE);
6478
}
6579
return dst;
@@ -70,6 +84,7 @@ char *cbm_arena_strndup(CBMArena *a, const char *s, size_t len) {
7084
return NULL;
7185
char *dst = (char *)cbm_arena_alloc(a, len + SKIP_ONE);
7286
if (dst) {
87+
a->strdup_alloc += len + SKIP_ONE;
7388
memcpy(dst, s, len);
7489
dst[len] = '\0';
7590
}
@@ -91,6 +106,7 @@ char *cbm_arena_sprintf(CBMArena *a, const char *fmt, ...) {
91106
if (!dst) {
92107
return NULL;
93108
}
109+
a->sprintf_alloc += (size_t)needed + SKIP_ONE;
94110

95111
va_start(args, fmt);
96112
vsnprintf(dst, (size_t)needed + SKIP_ONE, fmt, args);

internal/cbm/arena.h

Lines changed: 13 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -1,41 +1,13 @@
1-
#ifndef CBM_ARENA_H
2-
#define CBM_ARENA_H
3-
4-
#include <stddef.h>
5-
6-
// CBMArena is a simple bump allocator that allocates from fixed-size blocks.
7-
// All memory is freed at once via cbm_arena_destroy(). Individual frees are not
8-
// supported — this is by design for per-file extraction where all data has the
9-
// same lifetime.
10-
#define CBM_ARENA_MAX_BLOCKS 256
11-
#define CBM_ARENA_DEFAULT_BLOCK_SIZE (64 * 1024) // 64KB initial
12-
13-
typedef struct {
14-
char *blocks[CBM_ARENA_MAX_BLOCKS];
15-
size_t block_sizes[CBM_ARENA_MAX_BLOCKS]; // per-block sizes (for stats)
16-
int nblocks;
17-
size_t block_size;
18-
size_t used; // bytes used in current block
19-
size_t total_alloc; // cumulative bytes allocated (for stats)
20-
} CBMArena;
21-
22-
// Initialize an arena with the default block size.
23-
void cbm_arena_init(CBMArena *a);
24-
25-
// Allocate n bytes from the arena. Returns NULL on OOM or block exhaustion.
26-
// All returned pointers are 8-byte aligned.
27-
void *cbm_arena_alloc(CBMArena *a, size_t n);
28-
29-
// Duplicate a string into arena memory. Returns arena-owned copy.
30-
char *cbm_arena_strdup(CBMArena *a, const char *s);
31-
32-
// Duplicate a string of known length into arena memory. NUL-terminates.
33-
char *cbm_arena_strndup(CBMArena *a, const char *s, size_t len);
34-
35-
// sprintf into arena memory. Returns arena-owned string.
36-
char *cbm_arena_sprintf(CBMArena *a, const char *fmt, ...) __attribute__((format(printf, 2, 3)));
37-
38-
// Free all blocks. Arena is invalid after this call.
39-
void cbm_arena_destroy(CBMArena *a);
40-
41-
#endif // CBM_ARENA_H
1+
/*
2+
* Extraction compatibility include.
3+
*
4+
* CBMArena used to be duplicated here and in src/foundation/arena.h. The
5+
* production binary links the foundation implementation, so any field drift
6+
* between those definitions is an ABI violation. Keep one canonical layout.
7+
*/
8+
#ifndef CBM_EXTRACTION_ARENA_COMPAT_H
9+
#define CBM_EXTRACTION_ARENA_COMPAT_H
10+
11+
#include "../../src/foundation/arena.h"
12+
13+
#endif /* CBM_EXTRACTION_ARENA_COMPAT_H */

internal/cbm/cbm.c

Lines changed: 24 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -105,14 +105,12 @@ void cbm_reset_profile(void) {
105105

106106
#define GROW_ARRAY(arr, arena) \
107107
do { \
108+
(void)(arena); \
108109
if ((arr)->count >= (arr)->cap) { \
109110
int new_cap = (arr)->cap == 0 ? CBM_SZ_32 : (arr)->cap * PAIR_LEN; \
110-
void *new_items = cbm_arena_alloc((arena), (size_t)new_cap * sizeof(*(arr)->items)); \
111+
void *new_items = realloc((arr)->items, (size_t)new_cap * sizeof(*(arr)->items)); \
111112
if (!new_items) \
112113
return; \
113-
if ((arr)->items && (arr)->count > 0) { \
114-
memcpy(new_items, (arr)->items, (size_t)(arr)->count * sizeof(*(arr)->items)); \
115-
} \
116114
(arr)->items = new_items; \
117115
(arr)->cap = new_cap; \
118116
} \
@@ -1679,6 +1677,28 @@ void cbm_free_result(CBMFileResult *result) {
16791677
free(result->owned_results);
16801678
result->owned_results = NULL;
16811679
result->owned_result_count = 0;
1680+
#define FREE_RESULT_ARRAY(field) \
1681+
do { \
1682+
free(result->field.items); \
1683+
result->field.items = NULL; \
1684+
result->field.count = 0; \
1685+
result->field.cap = 0; \
1686+
} while (0)
1687+
FREE_RESULT_ARRAY(defs);
1688+
FREE_RESULT_ARRAY(calls);
1689+
FREE_RESULT_ARRAY(imports);
1690+
FREE_RESULT_ARRAY(usages);
1691+
FREE_RESULT_ARRAY(throws);
1692+
FREE_RESULT_ARRAY(rw);
1693+
FREE_RESULT_ARRAY(type_refs);
1694+
FREE_RESULT_ARRAY(env_accesses);
1695+
FREE_RESULT_ARRAY(type_assigns);
1696+
FREE_RESULT_ARRAY(impl_traits);
1697+
FREE_RESULT_ARRAY(resolved_calls);
1698+
FREE_RESULT_ARRAY(string_refs);
1699+
FREE_RESULT_ARRAY(infra_bindings);
1700+
FREE_RESULT_ARRAY(channels);
1701+
#undef FREE_RESULT_ARRAY
16821702
cbm_arena_destroy(&result->arena);
16831703
free(result);
16841704
}

internal/cbm/extract_node_stack.h

Lines changed: 15 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,12 @@
44
* Replaces fixed-size TSNode stack[] arrays that silently drop AST subtrees
55
* when the stack overflows (GitHub issue #199).
66
*
7-
* Uses the arena allocator for zero-fragmentation growth: old blocks are
8-
* abandoned (freed when the arena is destroyed at end of file extraction).
9-
* Initial capacity matches the previous fixed caps so small files allocate
10-
* no extra memory.
7+
* The common capacity lives inline in the traversal's own stack frame. Only an
8+
* unusually broad AST spills into the result arena. Call-site capacities
9+
* historically mirrored fixed stack limits (usually 512, sometimes 4096), so
10+
* allocating every traversal there made temporary work survive with durable
11+
* extraction results across the whole repository. Inline storage preserves the
12+
* same traversal order and geometric growth without that lifetime inversion.
1113
*/
1214
#ifndef CBM_EXTRACT_NODE_STACK_H
1315
#define CBM_EXTRACT_NODE_STACK_H
@@ -20,19 +22,24 @@ typedef struct {
2022
TSNode *items;
2123
int count;
2224
int cap;
25+
TSNode inline_items[128];
2326
} TSNodeStack;
2427

25-
/* Initialize a stack with the given initial capacity, arena-allocated. */
28+
enum { TS_NSTACK_INLINE_CAP = 128 };
29+
30+
/* Initialize with inline storage; arena is used only if the stack spills. */
2631
static inline void ts_nstack_init(TSNodeStack *s, CBMArena *arena, int initial_cap) {
27-
s->items = (TSNode *)cbm_arena_alloc(arena, (size_t)initial_cap * sizeof(TSNode));
32+
(void)arena;
33+
(void)initial_cap;
34+
s->items = s->inline_items;
2835
s->count = 0;
29-
s->cap = s->items ? initial_cap : 0;
36+
s->cap = TS_NSTACK_INLINE_CAP;
3037
}
3138

3239
/* Push a node onto the stack, growing 2x if needed. */
3340
static inline void ts_nstack_push(TSNodeStack *s, CBMArena *arena, TSNode node) {
3441
if (s->count >= s->cap) {
35-
int new_cap = s->cap ? s->cap * 2 : 512;
42+
int new_cap = s->cap ? s->cap * 2 : TS_NSTACK_INLINE_CAP;
3643
TSNode *new_items = (TSNode *)cbm_arena_alloc(arena, (size_t)new_cap * sizeof(TSNode));
3744
if (!new_items)
3845
return; /* OOM: best-effort, stop growing */

internal/cbm/lsp/kotlin_lsp.c

Lines changed: 7 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -412,28 +412,13 @@ static void kt_emit_resolved_kind(KotlinLSPContext *ctx, const char *callee_qn,
412412
return;
413413
}
414414

415-
CBMResolvedCallArray *arr = ctx->resolved_calls;
416-
if (arr->count >= arr->cap) {
417-
int new_cap = arr->cap == 0 ? 16 : arr->cap * 2;
418-
CBMResolvedCall *new_items = (CBMResolvedCall *)cbm_arena_alloc(
419-
ctx->arena, (size_t)new_cap * sizeof(CBMResolvedCall));
420-
if (!new_items) {
421-
return;
422-
}
423-
if (arr->items && arr->count > 0) {
424-
memcpy(new_items, arr->items, (size_t)arr->count * sizeof(CBMResolvedCall));
425-
}
426-
arr->items = new_items;
427-
arr->cap = new_cap;
428-
}
429-
CBMResolvedCall *rc = &arr->items[arr->count];
430-
memset(rc, 0, sizeof(CBMResolvedCall));
431-
rc->caller_qn = ctx->enclosing_func_qn;
432-
rc->callee_qn = cbm_arena_strdup(ctx->arena, callee_qn);
433-
rc->strategy = strategy;
434-
rc->confidence = confidence;
435-
rc->kind = kind;
436-
arr->count++;
415+
CBMResolvedCall rc = {0};
416+
rc.caller_qn = ctx->enclosing_func_qn;
417+
rc.callee_qn = cbm_arena_strdup(ctx->arena, callee_qn);
418+
rc.strategy = strategy;
419+
rc.confidence = confidence;
420+
rc.kind = kind;
421+
cbm_resolvedcall_push(ctx->resolved_calls, ctx->arena, rc);
437422
}
438423

439424
static void kt_stamp_resolved_site(KotlinLSPContext *ctx, int first, TSNode site) {

src/foundation/arena.c

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,15 @@ void *cbm_arena_alloc(CBMArena *a, size_t n) {
6161
}
6262
/* 8-byte alignment */
6363
n = (n + ARENA_ALIGN) & ~(size_t)ARENA_ALIGN;
64+
if (n <= 64) {
65+
a->alloc_le_64 += n;
66+
} else if (n <= 256) {
67+
a->alloc_le_256 += n;
68+
} else if (n <= 4096) {
69+
a->alloc_le_4096 += n;
70+
} else {
71+
a->alloc_gt_4096 += n;
72+
}
6473
if (a->nblocks == 0) {
6574
return NULL;
6675
}
@@ -90,6 +99,7 @@ char *cbm_arena_strdup(CBMArena *a, const char *s) {
9099
size_t len = strlen(s);
91100
char *dst = (char *)cbm_arena_alloc(a, len + SKIP_ONE);
92101
if (dst) {
102+
a->strdup_alloc += len + SKIP_ONE;
93103
memcpy(dst, s, len + SKIP_ONE);
94104
}
95105
return dst;
@@ -101,6 +111,7 @@ char *cbm_arena_strndup(CBMArena *a, const char *s, size_t len) {
101111
}
102112
char *dst = (char *)cbm_arena_alloc(a, len + SKIP_ONE);
103113
if (dst) {
114+
a->strdup_alloc += len + SKIP_ONE;
104115
memcpy(dst, s, len);
105116
dst[len] = '\0';
106117
}
@@ -120,6 +131,7 @@ char *cbm_arena_sprintf(CBMArena *a, const char *fmt, ...) {
120131
if (!dst) {
121132
return NULL;
122133
}
134+
a->sprintf_alloc += (size_t)needed + SKIP_ONE;
123135

124136
va_start(args, fmt);
125137
vsnprintf(dst, (size_t)needed + SKIP_ONE, fmt, args);

src/foundation/arena.h

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,12 @@ typedef struct {
2424
size_t block_size; /* current block capacity */
2525
size_t used; /* bytes used in current block */
2626
size_t total_alloc; /* cumulative bytes allocated (for stats) */
27+
size_t strdup_alloc; /* requested bytes returned by strdup/strndup helpers */
28+
size_t sprintf_alloc; /* requested bytes returned by sprintf helper */
29+
size_t alloc_le_64;
30+
size_t alloc_le_256;
31+
size_t alloc_le_4096;
32+
size_t alloc_gt_4096;
2733
} CBMArena;
2834

2935
/* Initialize arena with default block size. */

0 commit comments

Comments
 (0)