feat: move master to PHP 8.5 - #134
Merged
Merged
Conversation
PHP 8.5 constant expressions may embed a compiled static closure, which the compiler represents as a zend_ast_op_array node carrying a zend_op_array pointer instead of the child pointers the generic AST shape implies. The OpCache payload relocator has to dereference that node, so the type joins both the emitted-types list and the probed layout ground truth. The rest of the generator needed no change for 8.5: every sliceStructs() regex still matches (zend_closure, accel_time_t, zend_early_binding, zend_persistent_script, zend_file_cache_metainfo), the clang include list is unchanged, and FFI validates all 40 layouts against the C compiler. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01HRDZ2XsoVuB5uG4qXKL3ny
Generated by tools/generator against PHP 8.5.9 NTS x64 (release) from the matching php-src tag, ZEND_MODULE_API_NO 20250925. Layout changes carried by these artifacts: zend_attribute gains validation_error (delayed attribute target validation), zend_object_handlers gains clone_obj_with (the `clone with` rework), zend_constant gains filename and attributes, and zend_executor_globals gains the fatal-error backtrace fields. The opcache structs (zend_persistent_script, zend_early_binding, zend_file_cache_metainfo) and zend_closure are byte-identical to 8.4. include/8.4 stays in the tree untouched: the 8.4 maintenance branch merges up into master, and removing it here would turn every merge-up into a modify/delete conflict. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01HRDZ2XsoVuB5uG4qXKL3ny
ZEND_ACC_USE_GUARDS moved from 0x800 to 1 << 30 (0x40000000): 0x800 now means only ZEND_ACC_DEPRECATED, so the stale value would have silently mis-read every class with magic property accessors. OpCode gains DECLARE_ATTRIBUTED_CONST (210), which PHP 8.5 emits instead of DECLARE_CONST when a global const carries attributes. No opcode was renumbered. NodeKind is regenerated from the 8.5 enum: AST_OP_ARRAY, AST_CAST_VOID and AST_PIPE are new, AST_CLONE and AST_EXIT are gone (both constructs now compile to a ZEND_AST_CALL on the construct name), and 32 kinds shift as a result. Neither removed kind was referenced outside the table. LiveRange and ArgumentEntry were verified against Zend/zend_compile.h by hand - no engine export exists for those macros - and are unchanged in 8.5. OpCache: three file-cache serializer changes needed a matching port. Attributes carry the new validation_error string; constant-expression ASTs may 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; and 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. Note that the operand is resolved as a literal index rather than through the engine's RT_CONSTANT() byte-offset math: opcache stores IS_CONST operands as indexes in the file cache and only converts them on load, and this port keeps every opline in its serialized form. 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. The opcache fixture now compiles both new node shapes into the cached binary, so the relocator walks them on every round trip. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01HRDZ2XsoVuB5uG4qXKL3ny
master now builds against PHP 8.5; the 8.4 line lives on its own maintenance branch. Core's boot guard, the composer platform requirement, the CI matrix, both Dockerfiles and the PHPStan php version move together, since running z-engine against a PHP minor it was not generated for is memory corruption rather than a degraded mode. The generator keeps 8.4 in its default target list even though master targets 8.5: the 8.4 branch merges up into master, and dropping include/8.4 from the regenerated set would turn every merge-up into a modify/delete conflict. PHPStan: the single baseline entry that spelled out ZEND_ACC_USE_GUARDS' old numeric value is updated in place. A full baseline regeneration was rejected - it rewrites ~540 lines on the untouched 8.4 master too, so the churn is pre-existing drift and unrelated to this change. Verified that regenerating on 8.4 master and on this branch differ in exactly that one line, i.e. these edits introduce no new static-analysis errors. PHP 8.5 deprecates the report_memleaks directive; the test-suite calls that use it to suppress leak reports for immortal-by-design allocations are silenced, as it remains the only switch for that behaviour. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01HRDZ2XsoVuB5uG4qXKL3ny
master is now the PHP 8.5 line; 8.4 keeps its own maintenance branch and stays supported. The bug-report template asks for an 8.5 `php -v` line and spells out which branch each version lives on, so reports land against the right one. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01HRDZ2XsoVuB5uG4qXKL3ny
… 8.5 The dual-target gen-headers run broke on the 8.4 image: zend_ast_op_array only exists since PHP 8.5, so gate the manifest entries on the running PHP version (symbols.php executes inside the target container). The debug image build required an opcache.so, but since PHP 8.5 opcache is linked statically - emit the zend_extension line only when the shared extension exists and rely on the existing extension_loaded sanity check. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01HRDZ2XsoVuB5uG4qXKL3ny
lisachenko
commented
Aug 6, 2026
Review feedback on #134: include/8.4 artifacts are maintained on the 8.4 branch and flow into master via the cascade merge-up, so master's gen-headers run (and the header-drift job) only needs the 8.5 target. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01HRDZ2XsoVuB5uG4qXKL3ny
lisachenko
commented
Aug 6, 2026
Review feedback on #134: master's generator only ever runs against PHP 8.5 now, so the manifest can list the type unconditionally. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01HRDZ2XsoVuB5uG4qXKL3ny
The debug-build leak gate caught redefine-churn losing three blocks per cycle on PHP 8.5 (a zend_array, its arData and one key string - a whole static-variables table), leak-free on 8.4. PHP 8.5 changed who owns a closure's static variables. zend_create_closure_ex() used to duplicate the prototype defaults into the closure's OWN op_array.static_variables field; it now duplicates them into the ZEND_MAP_PTR slot only and leaves the struct field aliasing the prototype's table, whose single destroy is tied to the body refcount. The two releases that used to compensate went away with it: zend_closure_free_storage() no longer nulls static_variables, and destroy_op_array() no longer unconditionally frees the static_variables of a dying op_array's closure prototypes. The swap assumed the old model and minted the entry an independent duplicate, overwriting op_array.static_variables. On 8.5 that field is the only handle through which the entry's body reference could still reach the prototype's table, so once it was replaced nothing freed the original: the eval'd op_array had already returned early on the shared refcount, and the entry's own destroy_op_array went on to free the duplicate instead. No donor kind owns that table on 8.5, so the duplication is not merely unnecessary but actively wrong - the body refcount already guards it and the last holder frees it exactly once. Dropping it takes the whole minted-defaults bookkeeping with it (the registry, the rollback restore records and the duplicateStatics parameter, whose only `true` caller was redefine()). The live per-entry table is still unshared by dropping the ZEND_MAP_PTR slot, so statics keep materializing lazily from the defaults on first ZEND_BIND_STATIC. Verified against a locally built PHP 8.5.9 --enable-debug: all 25 leak scenarios report no leaks (redefine-churn was 300), and the internal group runs 138 tests with only 2 skips instead of 27, since the leak gates actually execute on a debug build. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01HRDZ2XsoVuB5uG4qXKL3ny
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes #61. Closes #120.
Moves
masterto PHP 8.5 now that the8.4maintenance branch is forked (at87b7b9b, tagged8.4.0) and the8.4 → mastermerge-up cascade is active.What's in here
include/8.5/linux-x64-nts/{engine.h,probe.c,constants.php,layouts.json}, produced bytools/generator/emit.phpagainst php-srcphp-8.5.9. FFI validated 40/40 struct layouts.include/8.4/stays on master andgenerate.phpnow targets both8.4and8.5, soheader-driftguards both and cascade merges from8.4never hit modify/delete conflicts oninclude/.zend_ast_op_arrayadded totypes/layout_structsintools/generator/symbols.php. AllsliceStructs()regexes from chore(opcache): generate & verify opcache structs for PHP 8.5 / master #120 still match 8.5 sources unchanged.ZEND_DECLARE_ATTRIBUTED_CONST(210); no renumbering of existing opcodes.ZEND_AST_OP_ARRAY,ZEND_AST_CAST_VOID,ZEND_AST_PIPE;ZEND_AST_CLONE/ZEND_AST_EXITremoved (both compile toZEND_AST_CALLnow); 34 AST kinds renumbered —NodeKindregenerated (123 kinds).ZEND_ACC_USE_GUARDSmoved 0x800 → 0x40000000; 0x800 is nowZEND_ACC_DEPRECATED. The stale value would silently mis-read every magic-accessor class.LiveRange/ArgumentEntryhand-verified against 8.5zend_compile.h— unchanged.zend_attribute+validation_error,zend_object_handlers+clone_obj_with,zend_constant+filename/attributes,zend_executor_globalsfatal-error backtrace fields); all accessed by field name, offsets absorb automatically. The opcache structs (zend_persistent_script,zend_early_binding,zend_file_cache_metainfo,zend_closure) are byte-identical to 8.4.PayloadRelocatorports three 8.5 serializer changes: attributevalidation_error,ZEND_AST_OP_ARRAY/ZEND_AST_CALLABLE_CONVERTAST branches, and theZEND_DECLARE_ATTRIBUTED_CONST+ZEND_OP_DATApair whoseIS_PTRliteral carries the attribute table — note the payload stores a literal index there while the live engine uses a byte offset (RT_CONSTANT); using the engine's math segfaults, and the extendedtests/OpCache/fixtures/answer.phpnow guards both new shapes.Core::SUPPORTED_PHP_VERSION_ID→[80500, 80600), composer~8.5.0+branch-alias dev-master: 8.5.x-dev, CIPHP_MINOR: 8.5, Dockerfile defaults,phpstan phpVersion: 80500, README matrix (8.5 ✅ on master), issue-template placeholders. Deprecatedreport_memleaksini_set calls silenced in the internal test group.Verification (local, PHP 8.5.9 NTS x64)
phpunit: 395 tests / 3827 assertions, 0 failures--group opcache --fail-on-skipped: 26/26--group internal --process-isolation: 138 tests, 0 failuresCaveat
Artifacts were generated from a sury 8.5.9 build (no docker daemon in the session environment); CI's
header-driftjob regenerates viaphp:8.5-cliand may flag configure-option differences. If it does, the diff from that job is canonical and will be reconciled on this branch.🤖 Generated with Claude Code
https://claude.ai/code/session_01HRDZ2XsoVuB5uG4qXKL3ny
Generated by Claude Code