prelinked/helper-runtime/x86_64-linux/manifest.json is keyed on a single core_fingerprint covering the whole lowering machinery — lib/JIT.php, lib/Runtime.php, lib/JIT/Context.php, lib/JIT/JitVmHelperLink.php, composer.lock, plus the patches digest. Any change to any of those invalidates every committed unit at once, and a cold phpc build re-emits the entire ~257-unit corpus live.
Why this matters more than it looks
master touches at least one of those files most days. Measured on this repo, the committed fingerprint had not moved since 2026-07-06 while master's computed value had moved repeatedly — 20 days during which every cold build paid full corpus emission. Compiling this:
<?php echo "hello ", 1 + 2, "\n";
pinned 13 cores emitting helper units. After #23457 refreshed the cache, the same build reports helper-runtime cache hit and takes 5.1 s.
So the cache works — it just spends most of its life switched off. Refreshing it (#23457) buys days, not weeks.
The structural shape
This is the same failure mode as the gen-0 stamp (#22642): a committed artifact keyed on a fingerprint that changes several times a day, so it is stale far more often than fresh, and "refresh it" is a treadmill rather than a fix.
Suggested direction
Key each unit on what that unit actually depends on, rather than on a global fingerprint:
- per-unit source sha (the helper file itself) + the transitive set of lowerings it actually reached during emission — the emitter already knows this closure, since
markEmitting()/JitVmHelperLink walk it;
- keep a coarse fingerprint only for genuinely global inputs (patches digest,
composer.lock, LLVM version);
- then an edit to one lowering invalidates the units that reached it, not the corpus.
The per-unit manifests already exist (units/*/manifest.json), so the storage shape is mostly there — what is missing is recording the dependency closure at emit time and checking it per unit instead of checking one global value.
Done when: editing a single helper or a single lowering invalidates a bounded subset of units, demonstrated by a cold build after such an edit re-emitting that subset rather than ~257 units.
prelinked/helper-runtime/x86_64-linux/manifest.jsonis keyed on a singlecore_fingerprintcovering the whole lowering machinery —lib/JIT.php,lib/Runtime.php,lib/JIT/Context.php,lib/JIT/JitVmHelperLink.php,composer.lock, plus the patches digest. Any change to any of those invalidates every committed unit at once, and a coldphpc buildre-emits the entire ~257-unit corpus live.Why this matters more than it looks
master touches at least one of those files most days. Measured on this repo, the committed fingerprint had not moved since 2026-07-06 while master's computed value had moved repeatedly — 20 days during which every cold build paid full corpus emission. Compiling this:
pinned 13 cores emitting helper units. After #23457 refreshed the cache, the same build reports
helper-runtime cache hitand takes 5.1 s.So the cache works — it just spends most of its life switched off. Refreshing it (#23457) buys days, not weeks.
The structural shape
This is the same failure mode as the gen-0 stamp (#22642): a committed artifact keyed on a fingerprint that changes several times a day, so it is stale far more often than fresh, and "refresh it" is a treadmill rather than a fix.
Suggested direction
Key each unit on what that unit actually depends on, rather than on a global fingerprint:
markEmitting()/JitVmHelperLinkwalk it;composer.lock, LLVM version);The per-unit manifests already exist (
units/*/manifest.json), so the storage shape is mostly there — what is missing is recording the dependency closure at emit time and checking it per unit instead of checking one global value.Done when: editing a single helper or a single lowering invalidates a bounded subset of units, demonstrated by a cold build after such an edit re-emitting that subset rather than ~257 units.