#323 B3: emit per-pointer-word relocation bitmap from GC slot descriptors, delta-0 (Axis B / ADR 0025 D2)

open
Opened by atgreen

Part 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

Code anchors

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)

atgreen3993112314

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:122 gc-object-size-from-header (widetag header-word) — total bytes per object.
  • src/cold/gc.lisp:170 gc-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.
  • The heap walk already exists on the image side (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-count to set exactly the pointer-word bits (cons = both words; records = the header-derived slot count). No new pointer analysis — reuse gc.lisp. Emit as a bm section 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.

atgreen3993113001

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).

atgreen3993113540

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.