Skip to content

feat: move master to PHP 8.5 - #134

Merged
lisachenko merged 9 commits into
masterfrom
claude/z-engine-php-8.5-hhjbss
Aug 6, 2026
Merged

feat: move master to PHP 8.5#134
lisachenko merged 9 commits into
masterfrom
claude/z-engine-php-8.5-hhjbss

Conversation

@lisachenko

Copy link
Copy Markdown
Owner

Closes #61. Closes #120.

Moves master to PHP 8.5 now that the 8.4 maintenance branch is forked (at 87b7b9b, tagged 8.4.0) and the 8.4 → master merge-up cascade is active.

What's in here

  • Generated engine definitions for 8.5: include/8.5/linux-x64-nts/{engine.h,probe.c,constants.php,layouts.json}, produced by tools/generator/emit.php against php-src php-8.5.9. FFI validated 40/40 struct layouts. include/8.4/ stays on master and generate.php now targets both 8.4 and 8.5, so header-drift guards both and cascade merges from 8.4 never hit modify/delete conflicts on include/.
  • Generator: one additive change — zend_ast_op_array added to types/layout_structs in tools/generator/symbols.php. All sliceStructs() regexes from chore(opcache): generate & verify opcache structs for PHP 8.5 / master #120 still match 8.5 sources unchanged.
  • Constants audit (8.4 → 8.5: 5 added, 2 removed, 38 changed):
    • New opcode ZEND_DECLARE_ATTRIBUTED_CONST (210); no renumbering of existing opcodes.
    • New AST kinds ZEND_AST_OP_ARRAY, ZEND_AST_CAST_VOID, ZEND_AST_PIPE; ZEND_AST_CLONE/ZEND_AST_EXIT removed (both compile to ZEND_AST_CALL now); 34 AST kinds renumbered — NodeKind regenerated (123 kinds).
    • ZEND_ACC_USE_GUARDS moved 0x800 → 0x40000000; 0x800 is now ZEND_ACC_DEPRECATED. The stale value would silently mis-read every magic-accessor class.
    • LiveRange/ArgumentEntry hand-verified against 8.5 zend_compile.h — unchanged.
  • Layouts: four structs grew (zend_attribute +validation_error, zend_object_handlers +clone_obj_with, zend_constant +filename/attributes, zend_executor_globals fatal-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.
  • OpCache (chore(opcache): generate & verify opcache structs for PHP 8.5 / master #120): binary file-cache format is unchanged (metainfo, adler32, system_id, bin path). PayloadRelocator ports three 8.5 serializer changes: attribute validation_error, ZEND_AST_OP_ARRAY/ZEND_AST_CALLABLE_CONVERT AST branches, and the ZEND_DECLARE_ATTRIBUTED_CONST + ZEND_OP_DATA pair whose IS_PTR literal 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 extended tests/OpCache/fixtures/answer.php now guards both new shapes.
  • Version bumps: Core::SUPPORTED_PHP_VERSION_ID[80500, 80600), composer ~8.5.0 + branch-alias dev-master: 8.5.x-dev, CI PHP_MINOR: 8.5, Dockerfile defaults, phpstan phpVersion: 80500, README matrix (8.5 ✅ on master), issue-template placeholders. Deprecated report_memleaks ini_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 failures
  • worker-loop soak: OK (+32 bytes over 10 000 iterations)
  • PHPStan: no errors · php-cs-fixer: clean

Caveat

Artifacts were generated from a sury 8.5.9 build (no docker daemon in the session environment); CI's header-drift job regenerates via php:8.5-cli and 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

claude added 6 commits August 6, 2026 14:11
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
Comment thread tools/generator/generate.php Outdated
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
Comment thread tools/generator/symbols.php Outdated
claude added 2 commits August 6, 2026 14:38
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
@lisachenko
lisachenko marked this pull request as ready for review August 6, 2026 15:02
@lisachenko
lisachenko merged commit e588c70 into master Aug 6, 2026
5 checks passed
@lisachenko
lisachenko deleted the claude/z-engine-php-8.5-hhjbss branch August 6, 2026 15:02
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

chore(opcache): generate &amp; verify opcache structs for PHP 8.5 / master Track: fork branch 8.4 and move master to PHP 8.5

2 participants