⚡ Z-Engine 8.5.0 — "PHP 8.5, byte for byte"
73 commits. 60 files. +27,400 lines — almost all of them generated engine definitions.
Everything in 8.4.2, now byte-exact for PHP 8.5 on seven platform targets. 🐘
composer require lisachenko/z-engine:^8.5🔢 Version = your PHP minor.
8.5.xtargets PHP 8.5 and nothing else ("php": "~8.5.0"). On PHP 8.4, stay on8.4.x— it remains fully supported, and every fix still lands there first and cascades upward. This release is 8.4.2 plus the 8.5 port.
🐘 The support matrix, filled in
master went from "🚧 in progress, linux-x64-nts" to a complete matrix:
| 8.5 target | Status |
|---|---|
linux-x64-nts / linux-x64-zts
| ✅ |
darwin-arm64-nts / darwin-arm64-zts
| ✅ |
darwin-x64-nts
| ✅ |
windows-x64-nts / windows-x64-zts
| ✅ |
¹
darwin-x64-ztsarrives the moment a ZTS PHP 8.5 build exists for Intel macOS runners — the generation workflow picks it up automatically, no code change needed. CI now skips a generation leg whose thread-safety build is genuinely unavailable upstream, instead of failing the run.
The generator learned the PHP 8.5 source layout and now targets only 8.5 on master. Two platform-specific generator fixes were needed along the way: the intsafe.h signed-overflow helpers had to be declared for clang on Windows and enabled through the build define, and truncated declaration slices are now extended to the semicolon so macOS headers parse.
🪟 One Windows nuance is documented rather than discovered the hard way: opcache.preload does not exist on Windows, so definitions can't be published once at server start — every process binds them for itself. Same autoload bootstrap, different branch; nothing changes on your side.
🔬 What actually changed inside PHP 8.5
Engine layouts shift every minor, and this is the part that would have silently corrupted memory if it hadn't been ported by hand. The hand-written constants — the ones with no engine export to generate from — were re-verified against Zend/zend_compile.h:
⚠️ ZEND_ACC_USE_GUARDS moved
0x800 → 1 << 30 (0x40000000). In 8.5, 0x800 now means only ZEND_ACC_DEPRECATED. The stale value would have silently mis-read every class with magic property accessors — no crash, just wrong answers. This one alone justifies the version pinning Z-Engine enforces at boot. 🚨
🌳 The AST enum was reshuffled
Regenerated from the 8.5 _zend_ast_kind enum:
- ➕ New:
AST_OP_ARRAY,AST_CAST_VOID,AST_PIPE— the last being PHP 8.5's pipe operator - ➖ Gone:
AST_CLONEandAST_EXIT— both constructs now compile to aZEND_AST_CALLon the construct name - 🔀 32 kinds shift as a result. Neither removed kind was referenced outside the table.
⚙️ A new opcode
DECLARE_ATTRIBUTED_CONST (210), which 8.5 emits instead of DECLARE_CONST when a global const carries attributes. No opcode was renumbered.
💾 The opcache relocator, ported
Three file-cache serializer changes in 8.5 needed a matching port in PayloadRelocator:
- Attributes carry the new
validation_errorstring - A constant-expression AST may now hold a
ZEND_AST_OP_ARRAYnode — a compiled static closure — whoseop_arraypointer must be relocated and walked; or aZEND_AST_CALLABLE_CONVERTnode that only touches execution-onlyZEND_MAP_PTRstate - An attributed global constant hangs its attribute table off a
ZEND_OP_DATAoperand as anIS_PTRliteral, which the ordinary literal walk skips entirely
✅ The binary format itself is unchanged in 8.5 — same metainfo, same adler32 checksum, same system_id stamp, same .bin path layout — so CacheMetaInfo, BinaryCacheFile and SystemId needed no change at all. The opcache fixture now compiles both new node shapes into the cached binary, so the relocator walks them on every round trip.
🩹 8.5-specific fixes
- 🧊
redefine()stopped duplicating static-variable defaults - 🧯 Swapped
$thisis restored so the live frame stays heap-safe on PHP 8.5 - 🔧 The nullable
getThis()call repaired in the live-frame restore path - 🎛️ Opline operands read off the
znode_opunion instead of cast through it — the old approach worked by luck of layout, not by contract - 🧭 Cross-branch drift settled where the cascade exposed it
🏗️ On the tooling side: PHPUnit 13 is now accepted, the performance workflow follows the branch's minor (8.5 on master), and Docker image layers are cached and keyed on the base image so they can actually be refreshed.
📦 Everything from 8.4.2, included
This release carries the whole 8.4.2 payload — the PHP 8.4 modernization sweep, the pluggable allocator seam, the consumer-safe compiler surface, and the quality/CI hardening. See the 8.4.2 release notes for the detail; nothing there is 8.4-only.
⚠️ Unchanged: still experimental
Z-Engine operates on raw engine memory. Segfaults are a feature of the territory, not a bug in your code. Match the branch to your PHP minor, keep the JIT off for the debugger primitives, and develop against a debug build.
Version matching is not optional.
Core::init()enforces the match and aborts with a clear message rather than letting you corrupt memory — as theZEND_ACC_USE_GUARDSmove above shows, the failure mode otherwise is silent and wrong.
💙 Thank you
Two PHP minors, seven platform targets, and an engine port where a single stale constant reads wrong instead of crashing. Thanks to Claude for making this one possible. 🙏
Now go break something interesting — on 8.5. 🧨