koga: fix two bugs that let the generated lisp-info go stale or corrupt - #1826
koga: fix two bugs that let the generated lisp-info go stale or corrupt#1826dg1sbg wants to merge 2 commits into
Conversation
generate-lisp-info opens its eight outputs with :if-exists :overwrite and never closes them, so a regeneration producing shorter content than the file already on disk leaves the tail of the previous version past the new end of file. That happens routinely when switching branches in a reused build tree. The failure mode depends on whether the leftover bytes happen to parse. When they do not, the build dies in compile-bytecode-image.lisp with an Eclector reader error whose reported position is a few bytes short of the file size, preceded by a volume of "Unknown variable ...: treating as special" warnings that are the reader recovering past the corruption and that point nowhere near the cause. When they do parse, the build succeeds and the image is quietly built from generated definitions that no longer match the source tree. Measured: after building one branch and switching to a shorter one without clearing, runtime-packages.lisp kept 178 bytes and runtime-functions.lisp 214 bytes of the previous branch, ninja returned 0, and the suite ran to completion reporting success. :supersede alone is not sufficient here. It is not required to replace the old file until the stream is closed, and this script closes none of them -- verified directly: writing 5 bytes over a 1000-byte file leaves 1000 bytes with :supersede and no close, and 5 bytes with :supersede and a close. So bind each stream, and close it once written. Verified on x86-64 Linux: padding runtime-packages.lisp with 500 junk bytes and regenerating now yields exactly the clean 88383 bytes, where before the fix all 500 survived. Fixes clasp-developers#1823.
|
Cross-referencing #1828 / #1829, since these two interact and the merge order matters. #1829 fixes a separate bug: the generated lisp-info depends on That has a direct bearing on this PR:
So this PR should land before or together with #1829, otherwise fixing the dependency edge will make the truncation bug substantially more frequent rather than less. Worth noting the two also explain each other's evidence: I originally hit the corruption fixed here while switching branches in a reused tree, and could not reproduce it reliably afterwards — because whether the generator runs at all is itself unreliable, for the reason #1829 fixes. |
|
Note on the CI reds here — every one has been a known pre-existing flake, in three separate tests across two runs, and none is attributable to this change. Run 1 — Run 2 (after a retrigger) — two reds:
Meanwhile Run 2 is the more useful of the two for reviewing this PR, because For completeness about my own testing: I verified the truncation semantics directly — padding Not retriggering further: two runs have produced three flake failures across two separate known issues, so another attempt is a coin flip rather than information. |
generate-lisp-info runs iclasp, which loads libclasp at runtime via rpath and dumps the packages, functions, variables, classes and type map it finds there. Its output therefore depends on libclasp's contents. The edge declared only iclasp as an implicit input, and iclasp depends on libclasp order-only -- in ninja that means "build it first if missing, but changes to it do not make me dirty". Since iclasp is just main.o plus a link, it rarely changes on its own, so editing any Lisp or C++ source never refreshed the generated files. Before this change, touching libclasp.so and rebuilding left the generated files untouched and ninja reported "no work to do". Walking one build tree through eight branches in sequence left them at their original timestamp and byte sizes throughout, including across a branch that adds C++ sources and therefore new scraped symbols -- eight images built from another branch's lisp-info, with nothing to indicate it. Naming libclasp as an implicit input restores the dependency. lib-filename already returns the static archive under --static-linking, so one call covers both linkage modes. Verified on x86-64 Linux: the edge now lists libclasp.so, touching it marks the generator dirty, the files regenerate, and the regression suite is unchanged at 1963.
Two related bugs in how the generated lisp-info files are produced. Both let a build proceed with generated data that does not match the source tree, and they interact, so they are fixed together here. Fixes #1823 and #1828.
Originally submitted as two PRs (#1826 and #1829); combined at review request.
1. The generator almost never runs (#1828)
generate-lisp-inforunsiclasp, which loadslibclaspat runtime via rpath and dumps the packages, functions, variables, classes and type map it finds there — so its output depends onlibclasp's contents. But the only path to that library is throughiclasp, which depends on it order-only: in ninja that means "build it first if missing, but changes to it do not make me dirty".iclaspis justmain.oplus a link, so it rarely changes on its own.Declaring
iclaspas an implicit input shows the dependency was intended; it simply names the wrong artifact.Worse in practice: walking a single tree through eight branches, rebuilding at each, left the generated files at their original timestamp and byte sizes throughout — including across a branch adding C++ sources and therefore new scraped symbols. Eight images, each built from another branch's lisp-info.
2. When it does run, it can leave a stale tail (#1823)
The generator opens its eight outputs with
:if-exists :overwriteand never closes them.:overwritewrites over an existing file without truncating, so shorter new content leaves the previous version's tail past the new EOF.Whether that breaks loudly or silently depends on whether the leftover bytes happen to parse. Loudly: the build dies in
compile-bytecode-image.lispwith an Eclector reader error whose position is a few bytes short of the file size, behind a wall ofWARNING: Unknown variable ...: treating as specialthat is only the reader recovering and points nowhere near the cause. Silently: the build succeeds on mismatched definitions with no diagnostic at all. Measured — after switching to a shorter branch without clearing,runtime-packages.lispkept 178 B andruntime-functions.lisp214 B of the previous branch,ninjareturned 0, and the suite ran to completion reporting success.:supersedealone does not fix this. It is not required to replace the old file until the stream is closed, and this script closes none of its eight. Verified in clasp, writing 5 bytes over a 1000-byte file::supersede, no close:supersede+close:overwrite+closeSo each stream is bound and closed as well as switched to
:supersede. Onlyletandcloseare used, since this script runs in primitive Clasp before any image loads.Why together
They are opposite halves of the same failure. Today the truncation bug is rare precisely because the generator seldom runs; fixing the dependency alone would make it fire constantly. Fixing the truncation alone leaves the files stale. Ordered here so the truncation fix precedes the dependency fix within the branch.
The truncation risk applies to a reused build tree — local development, CI caches,
git bisect— where a longer file is already on disk. Fresh CI runners build from scratch and have nothing to leave a tail, which matches what CI showed for the dependency fix in isolation: clean builds, no corruption.Verification, x86-64 Linux / LLVM 18
Truncation: padding
runtime-packages.lispwith 500 junk bytes and regenerating yields exactly the clean 88383 bytes, where before all 500 survived. Generated script confirmed to carry 8:supersedeand 8close.Dependency: the edge now reads
generate-lisp-info | boehmprecise/iclasp boehmprecise/lib/libclasp.so; touching the library marks the generator dirty and the files regenerate.Both: full build clean, regression suite 1963 /
TEST_EXIT=0, identical to baseline. CI additionally exercised the truncation change through a completeclasp/ubuntu-latest/bytecodebuild and baseline regression run.lib-filenamealready returns.aunder--static-linking, so the single call covers both linkage modes.