Adds a second asm build (asm/uncommonlisp-gc) behind the GC_NAIVE
assembler flag, providing the benchmark baseline we previously had
no data for. Same binary, same surface, different allocator:
- 8-byte header per heap block (size << 1 | mark), placed at -8
from the tagged pointer so existing untag + offset accesses
stay unchanged.
- Chunk list tracked in a side array, letting sweep walk every
mmap'd region by header-chained blocks instead of guessing.
- Free list rebuilt each sweep, first-fit alloc with split on
large-leftover (>= 24 bytes).
- Mark phase enumerates five root classes: %r14 (global env,
untagged chain), sym_else_val, sym_table entries, every
sym_hash_bucket chain, and a conservative scan from current
%rsp to the initial stack_top captured at _start. The stack
scan runs twice per word — once as a tagged value, once as a
potential untagged env-node pointer (size-guarded to 24 bytes
so it can't walk off a wrong-size block).
- Transitive marking via an explicit 16K-entry mark stack;
gc_mark_env walks untagged env chains from %r14 and from every
closure's env field.
- heap_alloc preserves the non-GC ABI (only %rax clobbered) so
existing callers like bi_append, which holds state in %rcx
across make_pair, keep working.
- Overflow path uses check-then-write bumps and pads the old
chunk's tail with a single dead block before growing, so sweep
never walks into uninitialized mmap'd memory.
- HEAP_SIZE shrinks to 1 MB under GC_NAIVE so the collector
actually runs on ordinary workloads.
- Two diagnostic builtins in the GC build: (gc-collect) to force
a collection, (gc-stats) -> (collections . live-bytes).
Control-group bench (examples/bench-gc-memory.lsp, 2000 iterations
of build-sum-discard over 200-element lists, i5-8350U):
tier time_ms peak_rss final_rss
asm no-GC 1097 133.9 MB 133.9 MB (grows, never shrinks)
asm naive GC 1431 1.1 MB 1.1 MB (steady state)
124x less memory at a ~30% throughput cost. That is the number we
were guessing at before. Reproduce: make bench-gc.
Tests: 137 asm (no-GC) + 137 asm (GC) + 189 shared functional pass.
The two asm builds are tested independently via UNCOMMONLISP_BIN in
asm/test.sh; asm/Makefile now builds both and exposes a test-gc
target.
20 lines
500 B
Makefile
20 lines
500 B
Makefile
all: uncommonlisp uncommonlisp-gc
|
|
|
|
uncommonlisp: uncommonlisp.s
|
|
as --64 -o uncommonlisp.o uncommonlisp.s
|
|
ld -o uncommonlisp uncommonlisp.o
|
|
|
|
uncommonlisp-gc: uncommonlisp.s
|
|
as --64 --defsym GC_NAIVE=1 -o uncommonlisp-gc.o uncommonlisp.s
|
|
ld -o uncommonlisp-gc uncommonlisp-gc.o
|
|
|
|
test: uncommonlisp
|
|
@bash test.sh
|
|
|
|
test-gc: uncommonlisp-gc
|
|
@UNCOMMONLISP_BIN=./uncommonlisp-gc bash test.sh
|
|
|
|
clean:
|
|
rm -f uncommonlisp.o uncommonlisp uncommonlisp-gc.o uncommonlisp-gc
|
|
|
|
.PHONY: all test test-gc clean
|