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
48
c/eval.c
48
c/eval.c
|
|
@ -41,7 +41,7 @@ int value_to_list(Value v, Value **out) {
|
|||
while (IS_PAIR(cur)) { n++; cur = CDR(cur); }
|
||||
if (!IS_NIL(cur)) lisp_error("not a list");
|
||||
|
||||
*out = (Value *)ul_malloc(sizeof(Value) * n);
|
||||
*out = (Value *)ul_malloc_values(sizeof(Value) * n);
|
||||
cur = v;
|
||||
for (int i = 0; i < n; i++) {
|
||||
(*out)[i] = CAR(cur);
|
||||
|
|
@ -80,7 +80,7 @@ Formals parse_formals(Value f) {
|
|||
bool has_rest = !IS_NIL(cur);
|
||||
|
||||
result.nparams = n;
|
||||
result.params = (Value *)ul_malloc(sizeof(Value) * n);
|
||||
result.params = (Value *)ul_malloc_values(sizeof(Value) * n);
|
||||
cur = f;
|
||||
for (int i = 0; i < n; i++) {
|
||||
if (!IS_SYM(CAR(cur)))
|
||||
|
|
@ -113,7 +113,7 @@ ExprList body_with_env(Value *forms, int count, Env *env) {
|
|||
|
||||
/* We may need to splice begin forms */
|
||||
int cap = count + 64;
|
||||
Value *expanded = (Value *)ul_malloc(sizeof(Value) * cap);
|
||||
Value *expanded = (Value *)ul_malloc_values(sizeof(Value) * cap);
|
||||
memcpy(expanded, forms, sizeof(Value) * count);
|
||||
int n = count;
|
||||
|
||||
|
|
@ -133,7 +133,7 @@ ExprList body_with_env(Value *forms, int count, Env *env) {
|
|||
Value *spliced; int ns = value_to_list(CDR(f), &spliced);
|
||||
if (n + ns - 1 >= cap) {
|
||||
cap = (n + ns) * 2;
|
||||
expanded = (Value *)ul_realloc(expanded, sizeof(Value) * cap);
|
||||
expanded = (Value *)ul_realloc_values(expanded, sizeof(Value) * cap);
|
||||
}
|
||||
memmove(expanded + i + ns, expanded + i + 1, sizeof(Value) * (n - i - 1));
|
||||
memcpy(expanded + i, spliced, sizeof(Value) * ns);
|
||||
|
|
@ -211,7 +211,7 @@ Value qq_expand(Value tmpl, Env *env, int depth) {
|
|||
|
||||
/* Collect parts */
|
||||
int cap = 64;
|
||||
Value *parts = (Value *)ul_malloc(sizeof(Value) * cap);
|
||||
Value *parts = (Value *)ul_malloc_values(sizeof(Value) * cap);
|
||||
int nparts = 0;
|
||||
Value n = tmpl;
|
||||
|
||||
|
|
@ -222,17 +222,17 @@ Value qq_expand(Value tmpl, Env *env, int depth) {
|
|||
Value spliced = leval(CADR(item), env);
|
||||
Value *items; int ni = value_to_list(spliced, &items);
|
||||
for (int i = 0; i < ni; i++) {
|
||||
if (nparts >= cap) { cap *= 2; parts = (Value *)ul_realloc(parts, sizeof(Value) * cap); }
|
||||
if (nparts >= cap) { cap *= 2; parts = (Value *)ul_realloc_values(parts, sizeof(Value) * cap); }
|
||||
parts[nparts++] = items[i];
|
||||
}
|
||||
ul_free(items);
|
||||
} else {
|
||||
if (nparts >= cap) { cap *= 2; parts = (Value *)ul_realloc(parts, sizeof(Value) * cap); }
|
||||
if (nparts >= cap) { cap *= 2; parts = (Value *)ul_realloc_values(parts, sizeof(Value) * cap); }
|
||||
parts[nparts++] = cons(SYM_UNQUOTE_SPLICING,
|
||||
cons(qq_expand(CADR(item), env, depth - 1), VAL_NIL));
|
||||
}
|
||||
} else {
|
||||
if (nparts >= cap) { cap *= 2; parts = (Value *)ul_realloc(parts, sizeof(Value) * cap); }
|
||||
if (nparts >= cap) { cap *= 2; parts = (Value *)ul_realloc_values(parts, sizeof(Value) * cap); }
|
||||
parts[nparts++] = qq_expand(item, env, depth);
|
||||
}
|
||||
n = CDR(n);
|
||||
|
|
@ -339,7 +339,7 @@ static Value define_record_type(Value *a, int na, Env *env) {
|
|||
/* Capture field count and name symbol */
|
||||
const char *type_tag = sym_name(name);
|
||||
int nf = nfields;
|
||||
Value *field_syms = (Value *)ul_malloc(sizeof(Value) * nf);
|
||||
Value *field_syms = (Value *)ul_malloc_values(sizeof(Value) * nf);
|
||||
for (int i = 0; i < nf; i++) field_syms[i] = ctor_spec[i + 1];
|
||||
|
||||
/* Create a Proc that builds (list 'type-name f1 f2 ...) */
|
||||
|
|
@ -348,10 +348,10 @@ static Value define_record_type(Value *a, int na, Env *env) {
|
|||
/* Actually, let's just define a Proc that builds the list */
|
||||
ExprList body;
|
||||
body.count = 1;
|
||||
body.exprs = (Value *)ul_malloc(sizeof(Value));
|
||||
body.exprs = (Value *)ul_malloc_values(sizeof(Value));
|
||||
|
||||
/* Build: (list (quote name) f1 f2 ...) */
|
||||
Value *listargs = (Value *)ul_malloc(sizeof(Value) * (nf + 2));
|
||||
Value *listargs = (Value *)ul_malloc_values(sizeof(Value) * (nf + 2));
|
||||
listargs[0] = intern("list");
|
||||
listargs[1] = cons(SYM_QUOTE, cons(name, VAL_NIL));
|
||||
for (int i = 0; i < nf; i++) listargs[2 + i] = field_syms[i];
|
||||
|
|
@ -382,7 +382,7 @@ static Value define_record_type(Value *a, int na, Env *env) {
|
|||
Value arg_sym = intern("x");
|
||||
ExprList body;
|
||||
body.count = 1;
|
||||
body.exprs = (Value *)ul_malloc(sizeof(Value));
|
||||
body.exprs = (Value *)ul_malloc_values(sizeof(Value));
|
||||
/* Build: (and (pair? x) (symbol? (car x)) (eq? (car x) 'name)) */
|
||||
/* Simpler: use a special check that understands subtypes */
|
||||
/* We'll need a native predicate. Let's put the type name in an env binding. */
|
||||
|
|
@ -541,7 +541,7 @@ static bool sr_match(SyntaxTransformer *st, Value pat, Value form, Env *bindings
|
|||
ULVector *vec = AS_VECTOR(existing->val);
|
||||
if (vec->len >= vec->cap) {
|
||||
vec->cap *= 2;
|
||||
vec->data = (Value *)ul_realloc(vec->data, sizeof(Value) * vec->cap);
|
||||
vec->data = (Value *)ul_realloc_values(vec->data, sizeof(Value) * vec->cap);
|
||||
}
|
||||
vec->data[vec->len++] = bind->val;
|
||||
} else {
|
||||
|
|
@ -549,7 +549,7 @@ static bool sr_match(SyntaxTransformer *st, Value pat, Value form, Env *bindings
|
|||
Value vec = make_vector(0, VAL_NIL);
|
||||
ULVector *v = AS_VECTOR(vec);
|
||||
v->cap = n_ell > 0 ? (size_t)n_ell : 4;
|
||||
v->data = (Value *)ul_realloc(v->data, sizeof(Value) * v->cap);
|
||||
v->data = (Value *)ul_realloc_values(v->data, sizeof(Value) * v->cap);
|
||||
v->data[0] = bind->val;
|
||||
v->len = 1;
|
||||
env_define(bindings, bind->sym, vec);
|
||||
|
|
@ -615,7 +615,7 @@ static Value sr_expand(SyntaxTransformer *st, Value tmpl, Env *bindings) {
|
|||
if (n < 0) n = 0;
|
||||
|
||||
/* Expand each iteration */
|
||||
Value *expanded = (Value *)ul_malloc(sizeof(Value) * n);
|
||||
Value *expanded = (Value *)ul_malloc_values(sizeof(Value) * n);
|
||||
for (int i = 0; i < n; i++) {
|
||||
/* Create a sub-binding env where vector vars are replaced by their i-th element */
|
||||
Env *sb = make_env(NULL);
|
||||
|
|
@ -921,8 +921,8 @@ Value leval(Value expr, Env *env) {
|
|||
int nbody = na - 2;
|
||||
Value *body_arr = a + 2;
|
||||
|
||||
Value *bps = (Value *)ul_malloc(sizeof(Value) * nb);
|
||||
Value *bvs = (Value *)ul_malloc(sizeof(Value) * nb);
|
||||
Value *bps = (Value *)ul_malloc_values(sizeof(Value) * nb);
|
||||
Value *bvs = (Value *)ul_malloc_values(sizeof(Value) * nb);
|
||||
for (int i = 0; i < nb; i++) {
|
||||
Value *bp; int nbp = value_to_list(binds[i], &bp);
|
||||
bps[i] = bp[0];
|
||||
|
|
@ -1034,7 +1034,7 @@ Value leval(Value expr, Env *env) {
|
|||
Env *c = make_env(env);
|
||||
/* Parse variable specs: (var init step) */
|
||||
typedef struct { Value var; Value step; } DoSpec;
|
||||
DoSpec *specs = (DoSpec *)ul_malloc(sizeof(DoSpec) * nvc);
|
||||
DoSpec *specs = (DoSpec *)ul_malloc_values(sizeof(DoSpec) * nvc);
|
||||
for (int i = 0; i < nvc; i++) {
|
||||
Value *sp; int nsp = value_to_list(vcs[i], &sp);
|
||||
specs[i].var = sp[0];
|
||||
|
|
@ -1055,7 +1055,7 @@ Value leval(Value expr, Env *env) {
|
|||
break;
|
||||
}
|
||||
for (int i = 0; i < nbody; i++) leval(body_arr[i], c);
|
||||
Value *nvs = (Value *)ul_malloc(sizeof(Value) * nvc);
|
||||
Value *nvs = (Value *)ul_malloc_values(sizeof(Value) * nvc);
|
||||
for (int i = 0; i < nvc; i++) nvs[i] = leval(specs[i].step, c);
|
||||
for (int i = 0; i < nvc; i++) env_set(c, specs[i].var, nvs[i]);
|
||||
ul_free(nvs);
|
||||
|
|
@ -1110,7 +1110,7 @@ Value leval(Value expr, Env *env) {
|
|||
st->literals = (char **)ul_malloc(sizeof(char *) * nlits);
|
||||
for (int i = 0; i < nlits; i++) st->literals[i] = ul_strdup(sym_name(lits[i]));
|
||||
st->nrules = na - 1;
|
||||
st->rules = (SyntaxRule *)ul_malloc(sizeof(SyntaxRule) * st->nrules);
|
||||
st->rules = (SyntaxRule *)ul_malloc_values(sizeof(SyntaxRule) * st->nrules);
|
||||
for (int i = 0; i < st->nrules; i++) {
|
||||
Value *rl; int nrl = value_to_list(a[i + 1], &rl);
|
||||
st->rules[i].pattern = rl[0];
|
||||
|
|
@ -1210,13 +1210,13 @@ Value leval(Value expr, Env *env) {
|
|||
int npre = na - 2;
|
||||
Value *pre = NULL;
|
||||
if (npre > 0) {
|
||||
pre = (Value *)ul_malloc(sizeof(Value) * npre);
|
||||
pre = (Value *)ul_malloc_values(sizeof(Value) * npre);
|
||||
for (int i = 0; i < npre; i++) pre[i] = leval(a[i + 1], env);
|
||||
}
|
||||
Value last = leval(a[na - 1], env);
|
||||
Value *lst; int nlst = value_to_list(last, &lst);
|
||||
|
||||
Value *all_args = (Value *)ul_malloc(sizeof(Value) * (npre + nlst));
|
||||
Value *all_args = (Value *)ul_malloc_values(sizeof(Value) * (npre + nlst));
|
||||
if (pre) memcpy(all_args, pre, sizeof(Value) * npre);
|
||||
memcpy(all_args + npre, lst, sizeof(Value) * nlst);
|
||||
int total = npre + nlst;
|
||||
|
|
@ -1260,7 +1260,7 @@ Value leval(Value expr, Env *env) {
|
|||
Value *irr = NULL;
|
||||
int nirr = na - 1;
|
||||
if (nirr > 0) {
|
||||
irr = (Value *)ul_malloc(sizeof(Value) * nirr);
|
||||
irr = (Value *)ul_malloc_values(sizeof(Value) * nirr);
|
||||
for (int i = 0; i < nirr; i++) irr[i] = leval(a[i + 1], env);
|
||||
}
|
||||
Value obj = make_error_object(msg, irr, nirr);
|
||||
|
|
@ -1409,7 +1409,7 @@ Value leval(Value expr, Env *env) {
|
|||
|
||||
/* Save old values and set new */
|
||||
typedef struct { Value param; Value old_val; } PBind;
|
||||
PBind *pb = (PBind *)ul_malloc(sizeof(PBind) * nb);
|
||||
PBind *pb = (PBind *)ul_malloc_values(sizeof(PBind) * nb);
|
||||
for (int i = 0; i < nb; i++) {
|
||||
Value *bp; int nbp = value_to_list(binds[i], &bp);
|
||||
pb[i].param = leval(bp[0], env);
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue