#323 B3: emit per-pointer-word relocation bitmap from GC slot descriptors, delta-0 (Axis B / ADR 0025 D2)
openPart of #318 (epic). Axis B, ADR 0025 D2. Recommended first step (with B1).
Goal
Emit the per-pointer-word relocation bitmap (ADR 0025's bm region: one bit
per 8-byte word the emitter recorded as a pointer, ~1.5% of heap) — built
immediately, even while delta is 0, so the ledger exists before it is
load-bearing.
Critical constraint
Build the bitmap from the GC slot descriptors / widetag layout — the SAME metadata the future moving GC (ADR 0001, GC.md) uses. Do NOT invent a private pointer map. funcl-is-the-linker already knows every baked address; this is bookkeeping over existing info, not new analysis.
Approach
- Walk the heap slot maps + the recorded code operands, set one bit per pointer word.
- Emit the bitmap as a section alongside the B2 raw dump.
- At delta 0 the bitmap is produced but not applied (verified in B4).
Code anchors
- GC slot/layout metadata (see
GC.md, ADR 0001) src/warm/image.lispheap walk (ag-image-heap-needs-collection-p:841and the pointer-slot walkers) — reuse the slot knowledge, don't duplicate
Acceptance gate
Bitmap emitted and sized ~N/64 bytes for an N-byte heap; a delta-0 load that applies the bitmap (all deltas 0) is a no-op and stays suite-green. Completeness is proven in B4, not here.
Comments (3)
Complete B3 implementation spec (exact pointer-word layout confirmed)
Iterate objects and set relocation bits using the SAME descriptors the marker uses — no private map:
Object iteration: the object-start side map at +gc-start-map-vaddr+, one
byte per 8-byte granule: bits 1..0 = kind (0 free/continuation, 1 cons, 2 other),
bit 7 = mark. Walk gc-next-start base bound from +gc-collectable-heap-start-vaddr+
to the high-water (pattern in gc-verify-heap, gc.lisp:510). Object extents come
from the side map, NOT payload (growable bvs store length != capacity).
Per-object candidate pointer words:
- Cons (kind 1): 16 bytes, 2 candidate slots at offsets 0 and 8 (no header).
- Other (kind 2): word 0 is the widetag HEADER (never a pointer; skip). Then
gc-object-pointer-slot-count(widetag, header)(gc.lisp:170) slots at 8,16,...: bv/string/bignum=0, symbol=4, vector/ratio=gc-header-length, closure=gc-header-length.
Per candidate slot — set the bit ONLY for a real heap pointer:
- collectable-heap pointer (lowtag 011 into [base,high-water)) => set HEAP bit.
- closure
entry(first closure slot) is a lowtag-010 CODE pointer — marker treats it as a non-heap word. For relocation it is CROSS-REGION: it needs the CODE delta, not the heap delta. Track it in a separate code-reloc bit / plane (ADR 0025 "cross-region pointers add the target region delta" — 2-pass or per-region bitmap). - immediate (fixnum/nil/t/char/unbound) => no bit.
Deferred correctly: at delta 0 (B2 preferred base) the bitmap is produced but
not applied; B4 (#324) restores at a forced non-zero delta and applies it — that
is what proves these bits complete. Emit as a +image-section-reloc+ (id 8, exists)
section alongside the B2 raw dump.
This is fully specified against real primitives now; the only remaining work is writing it carefully (the per-slot lowtag/cross-region logic is where a guess would silently corrupt, so it gets its own careful commit + B4 verification).
B3 part 1 landed — commit 8bd5c24
Bitmap builder from GC descriptors (gc-start-kind-at,
gc-object-pointer-slot-count). The lowtag check (mark iff 001 cons / 011
record in-span) makes the closure-entry / function-cell code pointers
(lowtag 010) fall out automatically — no special-casing needed.
Functionally verified at the REPL (not just compiled inert): bitmap correctly sized (~92KB for the 5.9MB collectable heap) and bits ARE set at pointer words (first non-zero byte @2048 = 0x10).
Bug found by that functional test: ag-image-collectable-heap-hi read only
the GC high-water SLOT, which lags the allocation cursor between collections, so
a never-collected image read span 0 and the bitmap (and the B2 dump) came out
EMPTY. Fixed to max(8*%heap-top, high-water) per gc-verify-heap. Still UNWIRED;
B2 round-trip emits it, B4 (#324) verifies under forced non-zero delta.
Recon: the GC slot metadata B3 builds on already exists
The critical constraint (build the bitmap from the SAME slot descriptors the GC uses, not a private map) is satisfiable directly:
src/cold/gc.lisp:122gc-object-size-from-header (widetag header-word)— total bytes per object.src/cold/gc.lisp:170gc-object-pointer-slot-count (widetag header-word)— count of tagged-pointer slots per object.src/cold/gc.lisp:220+gc-cons-pointer-slot-count+ = 2.ag-image-*slot walkers,image.lisp:368+) and mirrors the GC widetag dispatch.B3 plan is therefore: walk the heap high-water span, and per object use
gc-object-size-from-header+gc-object-pointer-slot-countto set exactly the pointer-word bits (cons = both words; records = the header-derived slot count). No new pointer analysis — reuse gc.lisp. Emit as abmsection alongside the B2 raw dump; at delta 0 produce-but-do-not-apply (B4 exercises non-zero delta).Depends on B2 (#322) for the raw-dump section to attach the bitmap to.