Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
91 changes: 91 additions & 0 deletions .github/workflows/diagnose-280.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
# Diagnostics for issue #280 (php-src: user opcode handlers mis-resume under the
# PHP 8.6 tail-call VM). Manually dispatched: runs the tools/diagnostics/issue-280
# probe ladder and the pure-FFI php-src repro on macos-latest (arm64, the affected
# build) with macos-15-intel as the in-run control. Re-run it against a new PHP
# build to check whether the upstream bug is fixed; drop it (and the probes) once
# php-src resolves the issue and the OpCodeHook guard is retired.
name: Diagnose issue 280

on:
workflow_dispatch:

permissions:
contents: read

concurrency:
group: diagnose-280-${{ github.ref }}
cancel-in-progress: true

env:
PHP_MINOR: '8.6'

jobs:
probes:
name: Probes (${{ matrix.runner-arch.arch }})
runs-on: ${{ matrix.runner-arch.runner }}
# The x64 leg is only the hybrid-VM control; setup-php currently has no
# PHP 8.6 build for macos-15-intel (it gives up after ~25 minutes), and a
# control leg that cannot get PHP must not fail the diagnostic run - the
# arm64 leg alone answers "is the php-src bug fixed yet".
continue-on-error: ${{ matrix.runner-arch.arch == 'x64' }}
env:
HOMEBREW_NO_AUTO_UPDATE: '1'
HOMEBREW_NO_INSTALL_CLEANUP: '1'
ZENGINE_STRICT_LAYOUT_CHECK: '1'
PHP_FLAGS: -d ffi.enable=1 -d zend.assertions=1 -d opcache.enable_cli=0 -d opcache.jit=off
strategy:
fail-fast: false
matrix:
runner-arch:
- { runner: macos-latest, arch: arm64 }
- { runner: macos-15-intel, arch: x64 }
steps:
- uses: actions/checkout@v7

- name: Set up PHP
uses: shivammathur/setup-php@v2
with:
php-version: ${{ env.PHP_MINOR }}
extensions: ffi, opcache
ini-values: ffi.enable=1, zend.assertions=1, opcache.enable=1, opcache.enable_cli=0, opcache.jit=off, opcache.jit_buffer_size=0
coverage: none

- name: Environment report
run: |
uname -m
php -v
php -r 'echo "ZEND_THREAD_SAFE=", var_export(ZEND_THREAD_SAFE, true), " PHP_DEBUG=", PHP_DEBUG, PHP_EOL;'

- name: Install dependencies
uses: ramsey/composer-install@v4

- name: Run every probe mode
run: |
for mode in install-only noop log-const globals use-ref diag add-baseline; do
echo "=== MODE ${mode} ==="
php $PHP_FLAGS tools/diagnostics/issue-280/probe.php "$mode" || echo "exit=$?"
done

- name: Pure-FFI repro (no z-engine, for the php-src report)
if: always()
run: php $PHP_FLAGS tools/diagnostics/issue-280/pure-ffi-repro.php || echo "exit=$?"

- name: noop under lldb (backtrace on crash)
if: always()
run: |
lldb --batch \
-o 'run' \
-o 'bt' \
-o 'register read' \
-o 'disassemble --pc --count 12' \
-o 'quit' \
-- "$(which php)" $PHP_FLAGS tools/diagnostics/issue-280/probe.php noop || true

- name: Latest macOS crash report
if: always()
run: |
latest=$(ls -t ~/Library/Logs/DiagnosticReports/php* 2>/dev/null | head -1 || true)
if [ -n "$latest" ]; then
echo "===== $latest ====="
head -c 24000 "$latest"
fi
6 changes: 6 additions & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -191,6 +191,12 @@ composer test:internal # destructive/segfault-prone group, process-isolated
build** (`tools/docker/php-debug.Dockerfile`, which CI builds inline and runs
the group in). Process isolation keeps one crash from taking down the whole
run.
- On PHP 8.6 builds using the tail-call VM (`Core::vmKind()` =
`VM_KIND_TAILCALL`; clang without global-register support, notably Apple
Silicon), user opcode handlers are refused by `OpCode::setHandler()` — the
engine mis-resumes execution after a user handler there and corrupts the
process (issue #280, a php-src bug). The guard test
(`OpCodeHookVmKindGuardTest`) covers both branches in every CI leg.
- FFI must be enabled (`ffi.enable=1`) and the JIT disabled (`opcache.jit=off`)
— the JIT rewrites the executor internals z-engine hooks into. The PHPUnit
config sets what it can; `ffi.enable` and `zend.assertions` must come from
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ Engine memory layouts change between every PHP minor version, so each PHP minor
| 8.4 | linux-x64 (nts, zts), darwin-x64 (nts, zts), darwin-arm64 (nts, zts), windows-x64 (nts, zts) | `8.4` | ✅ supported |
| 8.0 | linux-x64-nts | `8.0` | 🧊 frozen (legacy) |

¹ PHP 8.6 is pre-release; definitions track the latest beta. darwin-* and windows-* artifacts land through the generation workflows as 8.6 builds become available on those runners.
¹ PHP 8.6 is pre-release; definitions track the latest beta. darwin-* and windows-* artifacts land through the generation workflows as 8.6 builds become available on those runners. One 8.6 caveat: on builds using the new **tail-call VM** (`ZEND_VM_KIND_TAILCALL` — clang without global-register support, notably Apple Silicon), user opcode handlers mis-resume execution inside the engine and corrupt the process ([#280](https://github.com/lisachenko/z-engine/issues/280)); `OpCode::setHandler()` refuses with a clear error there until php-src resolves it. Everything not built on user opcode hooks is unaffected.

² `darwin-x64-zts` on 8.5 lands as soon as a ZTS PHP 8.5 build exists for Intel macOS runners — the generation workflow picks it up automatically.

Expand Down
2 changes: 1 addition & 1 deletion include/8.6/darwin-arm64-nts/constants.php
Original file line number Diff line number Diff line change
Expand Up @@ -138,7 +138,7 @@
'HASH_ADD_NEXT' => 16,
'HT_MIN_MASK' => 4294967294,
'HT_MIN_SIZE' => 8,
'ZEND_MODULE_API_NO' => 20250926,
'ZEND_MODULE_API_NO' => 20260924,
'MODULE_PERSISTENT' => 1,
'MODULE_TEMPORARY' => 2,
'CONST_CS' => 0,
Expand Down
4 changes: 4 additions & 0 deletions include/8.6/darwin-arm64-nts/engine.h
Original file line number Diff line number Diff line change
Expand Up @@ -864,6 +864,10 @@ struct _zend_executor_globals {
HashTable callable_convert_cache;
HashTable partial_function_application_cache;
zend_stack lambda_cache;
zend_vm_stack vm_stack_page_cache;
uint32_t vm_stack_page_cache_count;
zend_vm_stack fiber_vm_stack_page_cache;
uint32_t fiber_vm_stack_page_cache_count;
void *reserved[6];
};
typedef enum {
Expand Down
10 changes: 7 additions & 3 deletions include/8.6/darwin-arm64-nts/layouts.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"meta": {
"php": "8.6",
"api": 20250926,
"api": 20260924,
"zts": 0,
"debug": 0
},
Expand Down Expand Up @@ -312,7 +312,7 @@
}
},
"zend_executor_globals": {
"size": 2168,
"size": 2200,
"fields": {
"uninitialized_zval": 0,
"error_zval": 16,
Expand Down Expand Up @@ -400,7 +400,11 @@
"callable_convert_cache": 1984,
"partial_function_application_cache": 2040,
"lambda_cache": 2096,
"reserved": 2120
"vm_stack_page_cache": 2120,
"vm_stack_page_cache_count": 2128,
"fiber_vm_stack_page_cache": 2136,
"fiber_vm_stack_page_cache_count": 2144,
"reserved": 2152
}
},
"zend_compiler_globals": {
Expand Down
4 changes: 4 additions & 0 deletions include/8.6/darwin-arm64-nts/probe.c
Original file line number Diff line number Diff line change
Expand Up @@ -1277,6 +1277,10 @@ int main(void) {
fprintf(layouts, ", \"callable_convert_cache\": %zu", offsetof(zend_executor_globals, callable_convert_cache));
fprintf(layouts, ", \"partial_function_application_cache\": %zu", offsetof(zend_executor_globals, partial_function_application_cache));
fprintf(layouts, ", \"lambda_cache\": %zu", offsetof(zend_executor_globals, lambda_cache));
fprintf(layouts, ", \"vm_stack_page_cache\": %zu", offsetof(zend_executor_globals, vm_stack_page_cache));
fprintf(layouts, ", \"vm_stack_page_cache_count\": %zu", offsetof(zend_executor_globals, vm_stack_page_cache_count));
fprintf(layouts, ", \"fiber_vm_stack_page_cache\": %zu", offsetof(zend_executor_globals, fiber_vm_stack_page_cache));
fprintf(layouts, ", \"fiber_vm_stack_page_cache_count\": %zu", offsetof(zend_executor_globals, fiber_vm_stack_page_cache_count));
fprintf(layouts, ", \"reserved\": %zu", offsetof(zend_executor_globals, reserved));
fputs("}},\n", layouts);
fputs(" \"zend_compiler_globals\": {", layouts);
Expand Down
2 changes: 1 addition & 1 deletion include/8.6/darwin-arm64-zts/constants.php
Original file line number Diff line number Diff line change
Expand Up @@ -138,7 +138,7 @@
'HASH_ADD_NEXT' => 16,
'HT_MIN_MASK' => 4294967294,
'HT_MIN_SIZE' => 8,
'ZEND_MODULE_API_NO' => 20250926,
'ZEND_MODULE_API_NO' => 20260924,
'MODULE_PERSISTENT' => 1,
'MODULE_TEMPORARY' => 2,
'CONST_CS' => 0,
Expand Down
4 changes: 4 additions & 0 deletions include/8.6/darwin-arm64-zts/engine.h
Original file line number Diff line number Diff line change
Expand Up @@ -866,6 +866,10 @@ struct _zend_executor_globals {
HashTable callable_convert_cache;
HashTable partial_function_application_cache;
zend_stack lambda_cache;
zend_vm_stack vm_stack_page_cache;
uint32_t vm_stack_page_cache_count;
zend_vm_stack fiber_vm_stack_page_cache;
uint32_t fiber_vm_stack_page_cache_count;
void *reserved[6];
};
typedef enum {
Expand Down
10 changes: 7 additions & 3 deletions include/8.6/darwin-arm64-zts/layouts.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"meta": {
"php": "8.6",
"api": 20250926,
"api": 20260924,
"zts": 1,
"debug": 0
},
Expand Down Expand Up @@ -312,7 +312,7 @@
}
},
"zend_executor_globals": {
"size": 2168,
"size": 2200,
"fields": {
"uninitialized_zval": 0,
"error_zval": 16,
Expand Down Expand Up @@ -400,7 +400,11 @@
"callable_convert_cache": 1984,
"partial_function_application_cache": 2040,
"lambda_cache": 2096,
"reserved": 2120
"vm_stack_page_cache": 2120,
"vm_stack_page_cache_count": 2128,
"fiber_vm_stack_page_cache": 2136,
"fiber_vm_stack_page_cache_count": 2144,
"reserved": 2152
}
},
"zend_compiler_globals": {
Expand Down
4 changes: 4 additions & 0 deletions include/8.6/darwin-arm64-zts/probe.c
Original file line number Diff line number Diff line change
Expand Up @@ -1277,6 +1277,10 @@ int main(void) {
fprintf(layouts, ", \"callable_convert_cache\": %zu", offsetof(zend_executor_globals, callable_convert_cache));
fprintf(layouts, ", \"partial_function_application_cache\": %zu", offsetof(zend_executor_globals, partial_function_application_cache));
fprintf(layouts, ", \"lambda_cache\": %zu", offsetof(zend_executor_globals, lambda_cache));
fprintf(layouts, ", \"vm_stack_page_cache\": %zu", offsetof(zend_executor_globals, vm_stack_page_cache));
fprintf(layouts, ", \"vm_stack_page_cache_count\": %zu", offsetof(zend_executor_globals, vm_stack_page_cache_count));
fprintf(layouts, ", \"fiber_vm_stack_page_cache\": %zu", offsetof(zend_executor_globals, fiber_vm_stack_page_cache));
fprintf(layouts, ", \"fiber_vm_stack_page_cache_count\": %zu", offsetof(zend_executor_globals, fiber_vm_stack_page_cache_count));
fprintf(layouts, ", \"reserved\": %zu", offsetof(zend_executor_globals, reserved));
fputs("}},\n", layouts);
fputs(" \"zend_compiler_globals\": {", layouts);
Expand Down
2 changes: 1 addition & 1 deletion include/8.6/linux-x64-nts/constants.php
Original file line number Diff line number Diff line change
Expand Up @@ -138,7 +138,7 @@
'HASH_ADD_NEXT' => 16,
'HT_MIN_MASK' => 4294967294,
'HT_MIN_SIZE' => 8,
'ZEND_MODULE_API_NO' => 20250926,
'ZEND_MODULE_API_NO' => 20260924,
'MODULE_PERSISTENT' => 1,
'MODULE_TEMPORARY' => 2,
'CONST_CS' => 0,
Expand Down
4 changes: 4 additions & 0 deletions include/8.6/linux-x64-nts/engine.h
Original file line number Diff line number Diff line change
Expand Up @@ -868,6 +868,10 @@ struct _zend_executor_globals {
HashTable callable_convert_cache;
HashTable partial_function_application_cache;
zend_stack lambda_cache;
zend_vm_stack vm_stack_page_cache;
uint32_t vm_stack_page_cache_count;
zend_vm_stack fiber_vm_stack_page_cache;
uint32_t fiber_vm_stack_page_cache_count;
void *reserved[6];
};
typedef enum {
Expand Down
10 changes: 7 additions & 3 deletions include/8.6/linux-x64-nts/layouts.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"meta": {
"php": "8.6",
"api": 20250926,
"api": 20260924,
"zts": 0,
"debug": 0
},
Expand Down Expand Up @@ -312,7 +312,7 @@
}
},
"zend_executor_globals": {
"size": 2168,
"size": 2200,
"fields": {
"uninitialized_zval": 0,
"error_zval": 16,
Expand Down Expand Up @@ -400,7 +400,11 @@
"callable_convert_cache": 1984,
"partial_function_application_cache": 2040,
"lambda_cache": 2096,
"reserved": 2120
"vm_stack_page_cache": 2120,
"vm_stack_page_cache_count": 2128,
"fiber_vm_stack_page_cache": 2136,
"fiber_vm_stack_page_cache_count": 2144,
"reserved": 2152
}
},
"zend_compiler_globals": {
Expand Down
4 changes: 4 additions & 0 deletions include/8.6/linux-x64-nts/probe.c
Original file line number Diff line number Diff line change
Expand Up @@ -1277,6 +1277,10 @@ int main(void) {
fprintf(layouts, ", \"callable_convert_cache\": %zu", offsetof(zend_executor_globals, callable_convert_cache));
fprintf(layouts, ", \"partial_function_application_cache\": %zu", offsetof(zend_executor_globals, partial_function_application_cache));
fprintf(layouts, ", \"lambda_cache\": %zu", offsetof(zend_executor_globals, lambda_cache));
fprintf(layouts, ", \"vm_stack_page_cache\": %zu", offsetof(zend_executor_globals, vm_stack_page_cache));
fprintf(layouts, ", \"vm_stack_page_cache_count\": %zu", offsetof(zend_executor_globals, vm_stack_page_cache_count));
fprintf(layouts, ", \"fiber_vm_stack_page_cache\": %zu", offsetof(zend_executor_globals, fiber_vm_stack_page_cache));
fprintf(layouts, ", \"fiber_vm_stack_page_cache_count\": %zu", offsetof(zend_executor_globals, fiber_vm_stack_page_cache_count));
fprintf(layouts, ", \"reserved\": %zu", offsetof(zend_executor_globals, reserved));
fputs("}},\n", layouts);
fputs(" \"zend_compiler_globals\": {", layouts);
Expand Down
2 changes: 1 addition & 1 deletion include/8.6/linux-x64-zts/constants.php
Original file line number Diff line number Diff line change
Expand Up @@ -138,7 +138,7 @@
'HASH_ADD_NEXT' => 16,
'HT_MIN_MASK' => 4294967294,
'HT_MIN_SIZE' => 8,
'ZEND_MODULE_API_NO' => 20250926,
'ZEND_MODULE_API_NO' => 20260924,
'MODULE_PERSISTENT' => 1,
'MODULE_TEMPORARY' => 2,
'CONST_CS' => 0,
Expand Down
4 changes: 4 additions & 0 deletions include/8.6/linux-x64-zts/engine.h
Original file line number Diff line number Diff line change
Expand Up @@ -960,6 +960,10 @@ struct _zend_executor_globals {
HashTable callable_convert_cache;
HashTable partial_function_application_cache;
zend_stack lambda_cache;
zend_vm_stack vm_stack_page_cache;
uint32_t vm_stack_page_cache_count;
zend_vm_stack fiber_vm_stack_page_cache;
uint32_t fiber_vm_stack_page_cache_count;
void *reserved[6];
};
typedef enum {
Expand Down
10 changes: 7 additions & 3 deletions include/8.6/linux-x64-zts/layouts.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"meta": {
"php": "8.6",
"api": 20250926,
"api": 20260924,
"zts": 1,
"debug": 0
},
Expand Down Expand Up @@ -312,7 +312,7 @@
}
},
"zend_executor_globals": {
"size": 2336,
"size": 2368,
"fields": {
"uninitialized_zval": 0,
"error_zval": 16,
Expand Down Expand Up @@ -403,7 +403,11 @@
"callable_convert_cache": 2152,
"partial_function_application_cache": 2208,
"lambda_cache": 2264,
"reserved": 2288
"vm_stack_page_cache": 2288,
"vm_stack_page_cache_count": 2296,
"fiber_vm_stack_page_cache": 2304,
"fiber_vm_stack_page_cache_count": 2312,
"reserved": 2320
}
},
"zend_compiler_globals": {
Expand Down
4 changes: 4 additions & 0 deletions include/8.6/linux-x64-zts/probe.c
Original file line number Diff line number Diff line change
Expand Up @@ -1280,6 +1280,10 @@ int main(void) {
fprintf(layouts, ", \"callable_convert_cache\": %zu", offsetof(zend_executor_globals, callable_convert_cache));
fprintf(layouts, ", \"partial_function_application_cache\": %zu", offsetof(zend_executor_globals, partial_function_application_cache));
fprintf(layouts, ", \"lambda_cache\": %zu", offsetof(zend_executor_globals, lambda_cache));
fprintf(layouts, ", \"vm_stack_page_cache\": %zu", offsetof(zend_executor_globals, vm_stack_page_cache));
fprintf(layouts, ", \"vm_stack_page_cache_count\": %zu", offsetof(zend_executor_globals, vm_stack_page_cache_count));
fprintf(layouts, ", \"fiber_vm_stack_page_cache\": %zu", offsetof(zend_executor_globals, fiber_vm_stack_page_cache));
fprintf(layouts, ", \"fiber_vm_stack_page_cache_count\": %zu", offsetof(zend_executor_globals, fiber_vm_stack_page_cache_count));
fprintf(layouts, ", \"reserved\": %zu", offsetof(zend_executor_globals, reserved));
fputs("}},\n", layouts);
fputs(" \"zend_compiler_globals\": {", layouts);
Expand Down
12 changes: 9 additions & 3 deletions phpstan.dist.neon
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,8 @@ includes:

parameters:
level: max
# TODO: bump to 80600 once PHPStan accepts it (2.2.9 still caps phpVersion
# at 80599 while PHP 8.6 is in beta); the runtime target of this branch is 8.6
phpVersion: 80500
# The runtime target of this branch (PHPStan >= 2.2.16 accepts 80600)
phpVersion: 80600
# Deterministic result-cache location (gitignored, /var/) so CI can cache it
tmpDir: var/phpstan
paths:
Expand Down Expand Up @@ -70,6 +69,13 @@ parameters:
-
identifier: offsetAccess.nonOffsetAccessible
path: src/Type/StructArray.php
# Core::vmKind() binds a dedicated one-symbol cdef (`int zend_vm_kind(void)`):
# methods declared by cdef source are not statically resolvable, exactly like the
# engine binding behind call() - scoped to this one symbol in this one file.
-
identifier: method.notFound
message: '#Call to an undefined method FFI::zend_vm_kind\(\)#'
path: src/Core.php
# The dimension tests exist to prove that a plain `count($object)` reaches the engine's
# count_elements handler on a class that never declared the count itself. Rewriting them
# as assertCount() would measure PHPUnit's Count constraint instead of the language
Expand Down
Loading
Loading