c: precise GC tracing for NaN-boxed Values
Boehm's conservative pointer scan cannot recognize lumbda's Value layout — heap pointers live in the low 48 bits with QNAN + tag bits in the upper mantissa, so a raw word never looks like a heap address. Until now main.c neutralized this with GC_disable(): every allocation leaked, OOMing any long-running workload. Add precise tracing via a custom Boehm kind: - New c/gc.c: mark proc walks 8-byte words in mixed mode — when the QNAN bits are set with a pointer-bearing tag (0/2/4/5/6) extract the low-48 pointer; otherwise fall through to raw-pointer validation. GC_set_push_other_roots callback decodes NaN-boxed Values on the C stack via setjmp anchor + scan up to the stack base captured at process start. - Allocations holding Values (Pair, Env bindings, ValueStack data, ULVector data, HTEntry, Proc params + body, FullCont stack, CodeObj instrs, SymbolEntry) route through lumbda_value_malloc. Pure-byte sites (bignum limbs, char buffers, source files) stay on regular GC_MALLOC. - main.c / test.c / bench.c capture stack-base then drop GC_disable. types.c also zeros popped slots on the value stack so stale pointers do not survive a vs_pop and pin freed objects — independent correctness fix that pays off once GC actually runs. Build: USE_GC=1 (default when /usr/include/gc.h exists). Tests with GC enabled: - 88/88 c-test - 4/4 regression-named-let-leak (test that motivated GC_disable) - 205/205 functional (Python + C) - zoe-favorites all tiers (Python + C + asm + asm-full) alloc-test 1M cons drop-loop: - Before: 0.60s wall, 156 MB RSS, leaks every cell - After: 0.37s wall, 4 MB RSS, ~1500 GC cycles each freeing ~370 KB
This commit is contained in:
parent
f398902cc4
commit
b841b30bc4
12 changed files with 300 additions and 74 deletions
45
c/types.c
45
c/types.c
|
|
@ -47,23 +47,33 @@ void lisp_error_with_obj(Value obj, const char *fmt, ...) {
|
|||
* Value stack
|
||||
* ═══════════════════════════════════════════════════════════════════════════ */
|
||||
|
||||
/* Unused slots in s->data are zeroed so Boehm's conservative scan of
|
||||
* this GC_MALLOC region does not treat stale popped pointers as live
|
||||
* roots. Without this, popped Scheme values stay pinned for the
|
||||
* lifetime of the stack — every closure ever pushed leaks. */
|
||||
|
||||
void vs_init(ValueStack *s, int cap) {
|
||||
s->data = (Value *)ul_malloc(sizeof(Value) * cap);
|
||||
s->data = (Value *)ul_malloc_values(sizeof(Value) * cap);
|
||||
memset(s->data, 0, sizeof(Value) * cap);
|
||||
s->len = 0;
|
||||
s->cap = cap;
|
||||
}
|
||||
|
||||
void vs_push(ValueStack *s, Value v) {
|
||||
if (s->len >= s->cap) {
|
||||
int old_cap = s->cap;
|
||||
s->cap = s->cap * 2;
|
||||
s->data = (Value *)ul_realloc(s->data, sizeof(Value) * s->cap);
|
||||
s->data = (Value *)ul_realloc_values(s->data, sizeof(Value) * s->cap);
|
||||
memset(s->data + old_cap, 0, sizeof(Value) * (s->cap - old_cap));
|
||||
}
|
||||
s->data[s->len++] = v;
|
||||
}
|
||||
|
||||
Value vs_pop(ValueStack *s) {
|
||||
if (s->len <= 0) lisp_error("stack underflow");
|
||||
return s->data[--s->len];
|
||||
Value v = s->data[--s->len];
|
||||
s->data[s->len] = 0;
|
||||
return v;
|
||||
}
|
||||
|
||||
Value vs_peek(ValueStack *s) {
|
||||
|
|
@ -72,6 +82,7 @@ Value vs_peek(ValueStack *s) {
|
|||
}
|
||||
|
||||
void vs_clear(ValueStack *s) {
|
||||
if (s->len > 0) memset(s->data, 0, sizeof(Value) * s->len);
|
||||
s->len = 0;
|
||||
}
|
||||
|
||||
|
|
@ -96,7 +107,7 @@ Value intern(const char *name) {
|
|||
}
|
||||
/* New symbol */
|
||||
char *copy = ul_strdup(name);
|
||||
SymbolEntry *ne = (SymbolEntry *)ul_malloc(sizeof(SymbolEntry));
|
||||
SymbolEntry *ne = (SymbolEntry *)ul_malloc_values(sizeof(SymbolEntry));
|
||||
ne->name = copy;
|
||||
ne->value = VAL_SYM_RAW(copy);
|
||||
ne->next = g_symbols.buckets[h];
|
||||
|
|
@ -113,7 +124,7 @@ const char *sym_name(Value sym) {
|
|||
* ═══════════════════════════════════════════════════════════════════════════ */
|
||||
|
||||
Pair *make_pair(Value car, Value cdr) {
|
||||
Pair *p = (Pair *)ul_malloc(sizeof(Pair));
|
||||
Pair *p = (Pair *)ul_malloc_values(sizeof(Pair));
|
||||
p->hdr.type = OBJ_PAIR;
|
||||
p->car = car;
|
||||
p->cdr = cdr;
|
||||
|
|
@ -153,7 +164,7 @@ Value make_vector(size_t len, Value fill) {
|
|||
v->hdr.type = OBJ_VECTOR;
|
||||
v->len = len;
|
||||
v->cap = len > 0 ? len : 4;
|
||||
v->data = (Value *)ul_malloc(sizeof(Value) * v->cap);
|
||||
v->data = (Value *)ul_malloc_values(sizeof(Value) * v->cap);
|
||||
for (size_t i = 0; i < len; i++) v->data[i] = fill;
|
||||
return VAL_PTR(v);
|
||||
}
|
||||
|
|
@ -163,7 +174,7 @@ Value make_vector_from(Value *items, size_t len) {
|
|||
v->hdr.type = OBJ_VECTOR;
|
||||
v->len = len;
|
||||
v->cap = len > 0 ? len : 4;
|
||||
v->data = (Value *)ul_malloc(sizeof(Value) * v->cap);
|
||||
v->data = (Value *)ul_malloc_values(sizeof(Value) * v->cap);
|
||||
memcpy(v->data, items, sizeof(Value) * len);
|
||||
return VAL_PTR(v);
|
||||
}
|
||||
|
|
@ -206,7 +217,7 @@ void ht_set(ULHashTable *ht, Value key, Value val) {
|
|||
if (values_equal(e->key, key)) { e->value = val; return; }
|
||||
e = e->next;
|
||||
}
|
||||
HTEntry *ne = (HTEntry *)ul_malloc(sizeof(HTEntry));
|
||||
HTEntry *ne = (HTEntry *)ul_malloc_values(sizeof(HTEntry));
|
||||
ne->key = key;
|
||||
ne->value = val;
|
||||
ne->next = ht->buckets[h];
|
||||
|
|
@ -502,7 +513,7 @@ void env_define(Env *e, Value sym, Value val) {
|
|||
b = b->next;
|
||||
}
|
||||
/* New binding */
|
||||
EnvBinding *nb = (EnvBinding *)ul_malloc(sizeof(EnvBinding));
|
||||
EnvBinding *nb = (EnvBinding *)ul_malloc_values(sizeof(EnvBinding));
|
||||
nb->sym = sym;
|
||||
nb->val = val;
|
||||
nb->next = e->buckets[h];
|
||||
|
|
@ -609,7 +620,7 @@ VMFrame *deep_copy_frames(VMFrame *frames, int nframes) {
|
|||
copy[i].env = deep_copy_env(frames[i].env);
|
||||
copy[i].stack_len = frames[i].stack_len;
|
||||
copy[i].stack_cap = frames[i].stack_cap;
|
||||
copy[i].stack = (Value *)ul_malloc(sizeof(Value) * copy[i].stack_cap);
|
||||
copy[i].stack = (Value *)ul_malloc_values(sizeof(Value) * copy[i].stack_cap);
|
||||
memcpy(copy[i].stack, frames[i].stack, sizeof(Value) * frames[i].stack_len);
|
||||
}
|
||||
return copy;
|
||||
|
|
@ -621,7 +632,7 @@ FullCont *make_full_cont(VMFrame *frames, int nframes, Value *stack, int stack_l
|
|||
c->hdr.type = OBJ_CONTINUATION;
|
||||
c->frames = deep_copy_frames(frames, nframes);
|
||||
c->nframes = nframes;
|
||||
c->stack = (Value *)ul_malloc(sizeof(Value) * (stack_len > 0 ? stack_len : 4));
|
||||
c->stack = (Value *)ul_malloc_values(sizeof(Value) * (stack_len > 0 ? stack_len : 4));
|
||||
memcpy(c->stack, stack, sizeof(Value) * stack_len);
|
||||
c->stack_len = stack_len;
|
||||
c->ip = ip;
|
||||
|
|
@ -646,7 +657,7 @@ Env *deep_copy_env(Env *env) {
|
|||
EnvBinding *src = env->buckets[i];
|
||||
EnvBinding **dst = &ne->buckets[i];
|
||||
while (src) {
|
||||
EnvBinding *nb = (EnvBinding *)ul_malloc(sizeof(EnvBinding));
|
||||
EnvBinding *nb = (EnvBinding *)ul_malloc_values(sizeof(EnvBinding));
|
||||
nb->sym = src->sym;
|
||||
nb->val = src->val;
|
||||
nb->next = NULL;
|
||||
|
|
@ -666,13 +677,13 @@ Env *deep_copy_env(Env *env) {
|
|||
|
||||
Proc *make_proc(Value *params, int nparams, Value rest,
|
||||
ExprList body, Env *env, const char *name) {
|
||||
Proc *p = (Proc *)ul_malloc(sizeof(Proc));
|
||||
Proc *p = (Proc *)ul_malloc_values(sizeof(Proc));
|
||||
p->hdr.type = OBJ_PROC;
|
||||
p->params = (Value *)ul_malloc(sizeof(Value) * nparams);
|
||||
p->params = (Value *)ul_malloc_values(sizeof(Value) * nparams);
|
||||
memcpy(p->params, params, sizeof(Value) * nparams);
|
||||
p->nparams = nparams;
|
||||
p->rest = rest;
|
||||
p->body.exprs = (Value *)ul_malloc(sizeof(Value) * body.count);
|
||||
p->body.exprs = (Value *)ul_malloc_values(sizeof(Value) * body.count);
|
||||
memcpy(p->body.exprs, body.exprs, sizeof(Value) * body.count);
|
||||
p->body.count = body.count;
|
||||
p->env = env;
|
||||
|
|
@ -687,7 +698,7 @@ Proc *make_proc(Value *params, int nparams, Value rest,
|
|||
* ═══════════════════════════════════════════════════════════════════════════ */
|
||||
|
||||
Value make_macro(Value transformer) {
|
||||
ULMacro *m = (ULMacro *)ul_malloc(sizeof(ULMacro));
|
||||
ULMacro *m = (ULMacro *)ul_malloc_values(sizeof(ULMacro));
|
||||
m->hdr.type = OBJ_MACRO;
|
||||
m->transformer = transformer;
|
||||
return VAL_PTR(m);
|
||||
|
|
@ -703,7 +714,7 @@ Value make_error_object(const char *msg, Value *irritants, int nirr) {
|
|||
e->message = ul_strdup(msg);
|
||||
e->nirritants = nirr;
|
||||
if (nirr > 0) {
|
||||
e->irritants = (Value *)ul_malloc(sizeof(Value) * nirr);
|
||||
e->irritants = (Value *)ul_malloc_values(sizeof(Value) * nirr);
|
||||
memcpy(e->irritants, irritants, sizeof(Value) * nirr);
|
||||
} else {
|
||||
e->irritants = NULL;
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue