#209 Image-restored mini-clos registries: make-instance initarg matching broken on build/funcl (component-name => :NAME)

open
Opened by atgreen

Found landing asdf:load-system (cave#205). On build/funcl (IMAGE-BAKED mini-clos), (component-name (make-instance 'system :name :demo :version "9")) returns :NAME — the initarg KEY, not the value — and a direct (mc-slot-value-for-init 'system 'name (list :name :demo)) call cored the session. The SAME probes on build/funcl-stage2 with runtime-loaded src/mini-clos.lisp return :DEMO correctly. So the mini-clos registries (mc-initargs/mc-initforms/mc-ctors pair-keyed alists) restored from the FUNCLIMG image misbehave — the cave#132/#135 restored-structure class, possibly pair-key cons identity or a half-canonicalized keyword in the restored alist keys post-#155. Workaround in use: probe asdf-lite on stage2 + fresh mini-clos. Diagnose: on build/funcl, print (mc-assoc-pair (cons 'system 'name) mc-initargs) and %fn-ptr-as-fixnum the key halves vs fresh symbols; compare against the same on stage2.

Comments (3)

atgreen3992785829

ASSESSMENT 2026-07-11: see #227's comment — image-fidelity family, retest after #288 (save-image segfault at HEAD) is fixed. One data point from today: make-instance 'asdf:system works on the RUNTIME-LOADED path (fresh (load "asdf.lisp")), so the initarg-matching breakage is specific to image-RESTORED registries, consistent with the split/dangle class the tag-7 + canonicalizer work addresses.

atgreen3992843336

ROOT CAUSE FOUND 2026-07-12 (gdb on build/funcl-asdf).

The initarg-key symptom (component-name => :NAME) is GONE on current HEAD — (asdf:component-name (make-instance 'asdf:system :name :demo)) returns :DEMO. The real remaining blocker is a SEGFAULT in (asdf:load-system "alexandria"), and it is NOT a mini-clos registry bug at all — it's an image save/restore gap for cave#266 dynamic symbols.

Chain:

  • Crash: rip = 0xfffffffffffffffe. Disasm of the faulting site (asdf code at 0x40a2743b) is the cave#266 EARLY-BOUND call (compiler.lisp:2506-2517): movabs $0x4251c228,%rcx; mov 0x20(%rcx),%rcx; cmp $0x17(unbound); je fb; cmp $0x7(nil); je fb; sub $2; call *rcx. The symbol's fn-cell held raw 0 (not unbound 0x17, not nil 0x7), so it slipped both guards → sub 2 → call -2 → SIGSEGV.
  • The WHOLE 40-byte symbol record at 0x4251c228 is zero on restore.
  • 0x4251c228 is in the top 1 MiB of the 32 MiB RWX arena (0x42500000..0x42600000) — the ag-dynamic-symbol-record-allocate slice (ADR-0026 Phase C / cave#266). Runtime canonical (pkg,name) symbols are minted there, growing UP from top-1MiB, DISJOINT from the compiler's code cursor (which grows up from the arena base).
  • save-image copies the CODE-MMAP section for (byte-vector-length *ag-image-current-code-mmap*) = the code cursor high-water (~5-15 MiB from the base). The dynamic-symbol slice near the TOP (offset ~31 MiB) is OUTSIDE that length → NOT saved → zero on restore.
  • Also, cave#135 slot-relocation (ag-image-code-mmap-slot-offset ... code-len) returns -1 for any slot beyond code-len, so even the dynamic records' heap-pointer slots (name/pkg/value/function) are skipped.

Independent of (gc): a no-(gc) bake segfaults identically — the dynamic symbols are GC-rooted (gc-register-root-range), so GC keeps them; the gap is purely in save-image's copy extent, not collection.

Why fresh-load works but baked fails: fresh (load "asdf.lisp") uses the live arena in-process (dynamic records present at their real addresses), so early-bound calls resolve. Only the save/restore round-trip drops the top slice.

FIX DIRECTION: save-image must also persist the used dynamic-symbol slice [top-1MiB, ag-dyn-sym-next) and restore it verbatim to the same fixed vaddrs, AND extend the cave#135 relocation bound to cover those records (their name/pkg/value/function slots point into the re-laid-out collectable heap). Cold/build path is unaffected (ag-dyn-sym-next=0, unification off) so the self-host fixed point holds. This is an image-fidelity fix, not a mini-clos fix — retitling accordingly.

atgreen3992862082

FIXED (crash) 9ae6a0a 2026-07-12.

The segfault root cause was NOT mini-clos registries — it was the runtime canonical-symbol arena not surviving save/restore. cave#266 early-bound calls bake ABSOLUTE addresses of (pkg,name) canonical symbol records (ag-dynamic-symbol-record-allocate's slice at the TOP 1 MiB of the 32 MiB code arena) into emitted asdf code. save-image only persisted the code region [base, code-hi); the top slice restored zero, so the baked call jumped through a null fn-cell -> SIGSEGV.

Fix: widen the saved CODE-MMAP section's file-sz to cover the dynamic slice (mem-sz stays the live code cursor); restore copies the full extent to the fixed vaddr, relocates the records' heap-pointer slots via the cave#135 index encoding, and repoints ag-dyn-sym-next. Gated on a dynamic symbol existing, so the default mini-clos image is byte-identical (self-host holds).

Two traps diagnosed with codex:

  1. Do NOT mutate the LIVE code bv length to reach the slice — that leaks a 31 MiB object into the image heap-walk + O(n^2) canonical sort and hangs the save at asdf scale. Thread an explicit length; copy the slice from absolute mmap memory into the save-LOCAL copy.
  2. ag-image-canonical-sort was a recursive O(n^2) insertion sort ("n at most thousands" — false at asdf scale). Replaced with a stable O(n log n) merge sort (same unique ordering -> byte-identical -> self-host holds). This alone was the visible "hang."

Result on build/funcl-asdf: NO more segfault. (make-instance 'asdf:system :name :demo) -> :DEMO; (asdf:load-system "alexandria") now runs deep into asdf's operate path and returns cleanly instead of coring.

REMAINING (separate issue, NOT cave#209): baked load-system hits INVALID-SOURCE-REGISTRY, which escapes even handler-case (error ...) — likely asdf source-registry config state not reconstituted on the baked image, possibly compounded by condition-class hierarchy (cave#204) in the baked image so the handler doesn't match. Filing as a follow-up; the crash class this issue tracked is resolved.