frankenphp: put the tests back in the suite, guarded by an ISA probe #32

Merged
geusebio merged 3 commits from fix/frankenphp-isa-gate into main 2026-08-04 13:06:45 +00:00
Owner

Closes #19. Closes #10.

What the evidence actually says

#19 is built on readelf -n /usr/bin/php reporting x86 ISA used: …, x86-64-v3, x86-64-v4, and infers from it that the amd64 images we publish will SIGILL on CPUs without AVX-512. Digging into the CI history, that inference does not hold up:

  • The runner is an x86-64-v2 part. docker buildx ls in every run reports Platforms: linux/amd64, linux/amd64/v2, … and nothing above — no v3, no v4. So the runner is already the "older CPU" #19 is worried about.
  • Same host, same reported microarchitecture, opposite outcomes. Run 192 (2026-07-07, the one #10 was filed from) died with Illegal instruction (core dumped) from php --version on host red-leader. Run 253 (2026-08-04) ran RUN /usr/local/bin/validate for [frankenphp-85-base linux/amd64 …] natively on the same host and printed Validation passed. Nothing about the CPU changed in between.
  • What changed is the packages. The php-zts binary in the current images is dated Jul 23 2026 — an upstream rebuild made after #10 was filed.
  • The note is not a fault predictor. The same binary that carries the v3/v4 property also runs under qemu-x86_64 -cpu qemu64, i.e. plain baseline x86-64 with SSE2 and nothing else. The GNU property note records what the compiler emitted somewhere in the link, not what executes unconditionally: the AVX paths sit behind CPUID dispatch, as they are meant to.

So the build-time assertion #19 proposes — parse readelf -n, fail on v3/v4 — would fail today, on binaries that demonstrably work everywhere we can run them. Of the four options in the issue, none is needed: 1 is already done upstream, and 2–4 were all priced against a defect that no longer exists.

What this changes

The frankenphp services go back into the test service and the separate test-frankenphp service is gone. This is the answer to the real complaint in #19"nothing in the pipeline ever executes the amd64 artefact on a real amd64 CPU". Now something does, on every push: test gates build, the runner is native amd64, and it is a conservative v2 part rather than something modern. That includes the assertions, all four healthcheck variants, and frankenphp-85-stack-healthchecks from #30.

A behavioural guard replaces the ISA-note assertion. /etc/validate.d/isa.sh runs isa-probe.php, which pushes real data through the routines the vector code actually lives in — zlib-ng, OpenSSL (AES-GCM), libsodium, PCRE2's JIT, ICU, libxml2, libjpeg-turbo — rather than assuming php --version touched them. It runs in every build, on whatever CPU is doing the building, and costs about 0.4s. A rebuild that gets the dispatch wrong again fails there instead of on whoever pulls the tag.

php-zts-igbinary / php-zts-redis stay omitted. Re-checked on 2026-08-04 against native aarch64: installing php-zts-igbinary is still enough to make plain php --version core-dump. #19 guessed this was the same class of problem, and it is — which is the case for keeping a guard around even though the amd64 instance of it is fixed.

Residual risk, stated plainly

We validate on x86-64-v2, not on true baseline. If a future rebuild starts requiring SSE3/SSSE3/SSE4.2/POPCNT unconditionally, CI will not catch it. That gap is documented in the README rather than papered over; closing it properly needs an emulated-CPU run, and qemu-x86_64 is not up to it — it takes its own internal SIGSEGV loading this extension set, so it would be a flaky gate rather than a real one.

Testing

Full frankenphp group run locally on native aarch64: all four assertion services pass with the new probe, and all eight healthcheck services (including the new stack one) report healthy. amd64 verified by running the published matthewbaggett/frankenphp:8.5 image and the extracted binaries.

🤖 Generated with Claude Code

Closes #19. Closes #10. ## What the evidence actually says #19 is built on `readelf -n /usr/bin/php` reporting `x86 ISA used: …, x86-64-v3, x86-64-v4`, and infers from it that the amd64 images we publish will `SIGILL` on CPUs without AVX-512. Digging into the CI history, that inference does not hold up: - **The runner is an x86-64-v2 part.** `docker buildx ls` in every run reports `Platforms: linux/amd64, linux/amd64/v2, …` and nothing above — no `v3`, no `v4`. So the runner is already the "older CPU" #19 is worried about. - **Same host, same reported microarchitecture, opposite outcomes.** Run 192 (2026-07-07, the one #10 was filed from) died with `Illegal instruction (core dumped)` from `php --version` on host `red-leader`. Run 253 (2026-08-04) ran `RUN /usr/local/bin/validate` for `[frankenphp-85-base linux/amd64 …]` **natively on the same host** and printed `Validation passed`. Nothing about the CPU changed in between. - **What changed is the packages.** The `php-zts` binary in the current images is dated `Jul 23 2026` — an upstream rebuild made after #10 was filed. - **The note is not a fault predictor.** The same binary that carries the `v3/v4` property also runs under `qemu-x86_64 -cpu qemu64`, i.e. plain baseline x86-64 with SSE2 and nothing else. The GNU property note records what the compiler emitted somewhere in the link, not what executes unconditionally: the AVX paths sit behind CPUID dispatch, as they are meant to. So the build-time assertion #19 proposes — parse `readelf -n`, fail on `v3`/`v4` — would fail today, on binaries that demonstrably work everywhere we can run them. Of the four options in the issue, none is needed: 1 is already done upstream, and 2–4 were all priced against a defect that no longer exists. ## What this changes **The frankenphp services go back into the `test` service** and the separate `test-frankenphp` service is gone. This is the answer to the real complaint in #19 — *"nothing in the pipeline ever executes the amd64 artefact on a real amd64 CPU"*. Now something does, on every push: `test` gates `build`, the runner is native amd64, and it is a conservative v2 part rather than something modern. That includes the assertions, all four healthcheck variants, and `frankenphp-85-stack-healthchecks` from #30. **A behavioural guard replaces the ISA-note assertion.** `/etc/validate.d/isa.sh` runs `isa-probe.php`, which pushes real data through the routines the vector code actually lives in — zlib-ng, OpenSSL (AES-GCM), libsodium, PCRE2's JIT, ICU, libxml2, libjpeg-turbo — rather than assuming `php --version` touched them. It runs in every build, on whatever CPU is doing the building, and costs about 0.4s. A rebuild that gets the dispatch wrong again fails there instead of on whoever pulls the tag. **`php-zts-igbinary` / `php-zts-redis` stay omitted.** Re-checked on 2026-08-04 against native aarch64: installing `php-zts-igbinary` is still enough to make plain `php --version` core-dump. #19 guessed this was the same class of problem, and it is — which is the case for keeping a guard around even though the amd64 instance of it is fixed. ## Residual risk, stated plainly We validate on x86-64-v2, not on true baseline. If a future rebuild starts requiring SSE3/SSSE3/SSE4.2/POPCNT unconditionally, CI will not catch it. That gap is documented in the README rather than papered over; closing it properly needs an emulated-CPU run, and `qemu-x86_64` is not up to it — it takes its own internal SIGSEGV loading this extension set, so it would be a flaky gate rather than a real one. ## Testing Full frankenphp group run locally on native aarch64: all four assertion services pass with the new probe, and all eight healthcheck services (including the new stack one) report healthy. amd64 verified by running the published `matthewbaggett/frankenphp:8.5` image and the extracted binaries. 🤖 Generated with [Claude Code](https://claude.com/claude-code)
frankenphp: put the tests back in the suite, guarded by an ISA probe
Some checks failed
Build / Run container tests (push) Has been cancelled
Build / Build (push) Has been cancelled
5b8debb547
The FrankenPHP services were pulled out of the `test` service in #10 because
every `php` invocation died with "Illegal instruction" on the CI runner. That
was real, but it was an upstream build regression, not a property of the
packages: the runner (red-leader) is an x86-64-v2 part -- buildx reports
`linux/amd64, linux/amd64/v2` and nothing above -- and it was the same host,
with the same reported microarchitecture, in run 192 (2026-07-07, SIGILL) as
in run 253 (2026-08-04, clean). What changed in between is the packages; the
php-zts binary now in the images is dated 2026-07-23, and run 253's build job
ran `RUN /usr/local/bin/validate` for `frankenphp-*-base linux/amd64`
natively on that runner and passed.

So the `readelf -n` note #19 is built around -- "x86 ISA used: ...,
x86-64-v3, x86-64-v4" -- is not the smoking gun it looks like. It records
what the compiler emitted somewhere in the link, not what executes
unconditionally: the same binary that carries it runs on a v2 CPU, and runs
under `qemu-x86_64 -cpu qemu64` (plain baseline x86-64, SSE2 only). An
assertion that failed the build on seeing v3/v4 in that note would fail on
binaries that demonstrably work.

Re-enable the frankenphp assertions and healthchecks in the `test` service
and drop the separate `test-frankenphp` service. That is the thing #19 asked
for and could not have: `test` gates `build`, and it runs the amd64 artefact
on a real, fairly conservative amd64 CPU before anything is published.

Add /etc/validate.d/isa.sh, which runs isa-probe.php through the routines the
vectorised code actually lives in -- zlib-ng, OpenSSL, libsodium, PCRE2's
JIT, ICU, libxml2, libjpeg-turbo -- rather than trusting `php --version` to
have touched them. ~0.4s per build. A rebuild that gets the CPUID dispatch
wrong now fails on the machine that built the image instead of on whoever
pulls it.

php-zts-igbinary and php-zts-redis stay omitted: re-checked 2026-08-04 and
installing igbinary is still enough to make plain `php --version` core-dump
on native aarch64.

Closes #10
Closes #19

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Merge remote-tracking branch 'origin/main' into fix/frankenphp-isa-gate
Some checks failed
Build / Build (push) Has been cancelled
Build / Run container tests (push) Has been cancelled
71842ca2ca
# Conflicts:
#	test.yml
Merge remote-tracking branch 'origin/main' into fix/frankenphp-isa-gate
Some checks failed
Build / Build (push) Has been cancelled
Build / Run container tests (push) Has been cancelled
ecfcd6066a
Sign in to join this conversation.
No reviewers
No labels
No milestone
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
Grey.ooo/docker!32
No description provided.