#305 stage1: (save-image PATH) SIGSEGVs in AG-SAVE-IMAGE-ACTION — funcl-stage1-save-image-restart-evaluates fails

closed
Opened by atgreen

PRE-EXISTING on unmodified main at 7ab1a4b (verified: stashed the cave#303 fix, rebuilt build/funcl-stage1 from clean tree, identical crash), discovered by the cave#303 pre-push gate. Suite impact: test-fast-sharded shard 7 fails funcl-stage1-save-image-restart-evaluates (tests/bootstrap.el:3262) — the PRODUCER subprocess dies.

Repro (one run): printf '(save-image "/tmp/x.img")\n' | build/funcl-stage1 => SIGSEGV

gdb bt (baseline binary): #0 AG-SAVE-IMAGE-ACTION () <- +60ish bytes in, early #2 AG-REPL-EVAL-FORM-DISPATCH () #5 AG-REPL-EVAL-FORM () #10 AG-LOAD-EVAL-ONE ()

build/funcl (image-baked) may mask whatever stage1 trips on (the classic warm-layer masking). Suspect window: one of today's commits (ccc3cd8 ADR-0031 stage 1 intern door, or 7ab1a4b KIND-4 home relocs) touching state ag-save-image-action serializes, or an older break unmasked — the suite was last known fully green before this session. Next: gdb disas around the faulting address in AG-SAVE-IMAGE-ACTION to see which slot read faults, then diff the function's inputs across ccc3cd8^.

Comments (1)

atgreen3992942681

FIXED in 2beac09 — four stacked defects, each unmasking the next (all latent until 5cc5f39's geometry pushed the first one past the stack top):

  1. emit-ir-setq's free-special arm gated on (symbolp name) — the elisp host bv-izes EVERY name (host-symbolize-to-bv), so in stage1 every free-special setq compiled to the documented bogus rsp+8*(d+1000) lexical store (silent caller-frame corruption; SIGSEGV at the shallow REPL-top chain: (setq canonical-string-index nil), first form of ag-save-image-action, wrote 0x40 past the stack top — the gdb'd crash in this issue). Non-symbolp names now DROP the store; funcl-native compiles (real reader records) keep the value-cell store.
  2. ag-image-mc/cond-present-p used prelude fboundp + quoted-designator funcall — both broken in host-built stage1 (a warm quote is a pool bv). Once (1) was fixed, the guard misfired and funcalled into pool data. Replaced by ag-image-call-bridge: ag-fn-lookup probe + an emitted THUNK call (bare call-bv skips the arity register a defun's entry check reads).
  3. Bridge thunks emit into the live mmap → the root set is now evaluated BEFORE the save captures code extents, so the saved image stays self-consistent.
  4. The CONSUMER restore crashed separately (jump to rip=2): ag-image-restore-code-mmap-copy called %BV-DATA-ADDR — a PRELUDE defun (from 64dc50b) — from baked warm code, an unpatched call+0 in stage1. Inlined as %fn-ptr-as-fixnum arithmetic.

funcl-stage1-save-image-restart-evaluates PASSES (4.2s, was 62s-then-crash); test-fast-sharded ALL 8 SHARDS PASSED; spike oracle green; --hosted 42; fixed point held.

Two latent stage1 landmines observed but NOT fixed here (filing separately if they bite): (a) every host-emitted arity-error/undefined-call raise path is an unpatched call+0 that falls through into the function body — arity errors in stage1 detonate instead of reporting; (b) the stage1 REPL cannot call several cold/warm functions by name with string args (crashes in the reporter — condition-report resolution).