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
49 lines
1.3 KiB
Makefile
49 lines
1.3 KiB
Makefile
# Makefile for lumbda C interpreter
|
|
# Targets: all, test, bench, clean
|
|
|
|
CC = gcc
|
|
CFLAGS = -O2 -Wall -Wextra -Wno-unused-parameter -std=c11 -D_POSIX_C_SOURCE=200809L -D_GNU_SOURCE
|
|
LDFLAGS = -lm
|
|
|
|
# Boehm GC autodetect — enable when /usr/include/gc.h is present.
|
|
# Without GC, every allocation leaks (ul_free is a no-op in our header);
|
|
# small REPL snippets work but anything iterating past a few thousand
|
|
# allocations OOMs the process. The named-let + per-iteration user-fn
|
|
# call pattern in tests/regression/named-let-gc.lsp pins this down.
|
|
# Override with USE_GC=0 to force the malloc-only path for diagnostics.
|
|
USE_GC ?= $(shell test -f /usr/include/gc.h && echo 1 || echo 0)
|
|
ifeq ($(USE_GC),1)
|
|
CFLAGS += -DUSE_BOEHM_GC
|
|
LDFLAGS += -lgc
|
|
GC_SRCS = gc.c
|
|
else
|
|
GC_SRCS =
|
|
endif
|
|
|
|
SRCS = types.c bignum.c reader.c printer.c eval.c builtins.c vm.c jit.c portal.c $(GC_SRCS)
|
|
OBJS = $(SRCS:.c=.o)
|
|
|
|
.PHONY: all clean test bench
|
|
|
|
all: lumbda
|
|
|
|
lumbda: main.o $(OBJS)
|
|
$(CC) $(CFLAGS) -o $@ $^ $(LDFLAGS)
|
|
|
|
test: test_runner
|
|
./test_runner
|
|
|
|
test_runner: test.c $(OBJS)
|
|
$(CC) $(CFLAGS) -o $@ $^ $(LDFLAGS)
|
|
|
|
bench: bench_runner lumbda
|
|
ulimit -s 65536 && ./bench_runner
|
|
|
|
bench_runner: bench.c $(OBJS)
|
|
$(CC) $(CFLAGS) -o $@ $^ $(LDFLAGS)
|
|
|
|
%.o: %.c lumbda.h
|
|
$(CC) $(CFLAGS) -c -o $@ $<
|
|
|
|
clean:
|
|
rm -f *.o lumbda test_runner bench_runner
|