lumbda/asm
russell@unturf.com 3d55092037 asm-gc: adaptive EMA-driven meta-GC policy + bench + two correctness fixes
Moves the meta-GC from greedy (always verify) to adaptive: track a
scaled EMA of recent escape rate; when rate exceeds 50% (128/256),
SKIP the verifier and let the heap grow until natural GC; every 16
skipped arenas, force a verify as a probe to re-sample the rate.

Two correctness fixes uncovered while testing adaptive:

1. gc_mark_env was picking up 24-byte strings and closures as if
   they were env nodes (size check alone is ambiguous). Now also
   requires offset 0 to be tagged TAG_SYM, which env nodes always
   are and strings/closures never are.

2. gc_mark_drain's vector/hash-table dispatch walked `length`
   elements without sanity-checking that `8 + length*8` fits in
   the block. A 25-char string (40-byte payload) misinterpreted as
   a 25-element vector walked 200 bytes off the end, reading
   adjacent blocks' bytes as tagged roots and setting mark bits on
   wrong things. Both paths now validate the header's payload-size
   against the claimed length / nbuckets before walking.

New builtin:
  (arena-set-mode 0|1) — 0 = greedy baseline, 1 = adaptive (default)

arena-stats extended to six fields:
  (calls resets escapes skipped bytes-reclaimed ema-rate)

Bench (tests/bench-gc-adaptive.sh, one process per phase to isolate
a separate latent cross-phase bug we haven't cracked, N=1000 per
phase, i5-8350U):

  workload   mode     time_ms   resets   escapes   skipped
  friendly   greedy    732      1000        0         0
  friendly   adapt     691      1000        0         0
  hostile    greedy    568         0     1000         0
  hostile    adapt     607         0     1000        11
  mixed      greedy   1981        17     1983         0
  mixed      adapt    1694        14     1986         2

Adaptive wins on friendly (-6%) and mixed (-17%). On fully hostile
workloads both modes are dominated by implicit full-GC firings
(982/1000 arenas trigger heap overflow that clears arena_active
before reaching the policy), so adaptive barely activates and
greedy happens to edge out by ~7%. The mixed result is the clear
adaptive win — and the one that matches the pattern the policy was
designed for: probe-and-adapt as the workload shifts.

137 asm (no-GC) + 137 asm (GC) + 189 shared functional tests all
still pass.
2026-04-18 11:33:39 -04:00
..
Makefile asm: naive stop-the-world mark-sweep GC as a control group 2026-04-18 09:34:51 -04:00
test.sh asm: naive stop-the-world mark-sweep GC as a control group 2026-04-18 09:34:51 -04:00
uncommonlisp asm: native hash-set + benchmark — 15-21x over portable 2026-04-18 06:15:21 -04:00
uncommonlisp-gc asm-gc: adaptive EMA-driven meta-GC policy + bench + two correctness fixes 2026-04-18 11:33:39 -04:00
uncommonlisp-gc.o asm-gc: adaptive EMA-driven meta-GC policy + bench + two correctness fixes 2026-04-18 11:33:39 -04:00
uncommonlisp.o asm: native hash-set + benchmark — 15-21x over portable 2026-04-18 06:15:21 -04:00
uncommonlisp.s asm-gc: adaptive EMA-driven meta-GC policy + bench + two correctness fixes 2026-04-18 11:33:39 -04:00