Skip to content

Release 8.5.0

Latest

Choose a tag to compare

@lisachenko lisachenko released this 18 Aug 12:08
· 0 commits to 8.4 since this release
29a82a9

⚡ 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.x targets PHP 8.5 and nothing else ("php": "~8.5.0"). On PHP 8.4, stay on 8.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-zts arrives 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

0x8001 << 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_CLONE and AST_EXIT — both constructs now compile to a ZEND_AST_CALL on 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_error string
  • A constant-expression AST may now hold a ZEND_AST_OP_ARRAY node — a compiled static closure — whose op_array pointer must be relocated and walked; or a ZEND_AST_CALLABLE_CONVERT node that only touches execution-only ZEND_MAP_PTR state
  • An attributed global constant hangs its attribute table off a ZEND_OP_DATA operand as an IS_PTR literal, 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 $this is 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_op union 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 the ZEND_ACC_USE_GUARDS move 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. 🙏


🔍 Full diff 8.4.2...8.5.0

Now go break something interesting — on 8.5. 🧨