From 7c26c21da904eb490ac10a7b4e42bcea1fbaa47a Mon Sep 17 00:00:00 2001 From: Levi Morrison Date: Thu, 30 Apr 2026 10:07:12 -0600 Subject: [PATCH 1/7] Add VM interrupt tailcall helper reproducer --- ext/zend_test/object_handlers.c | 43 +++++++++++++++++++ ext/zend_test/object_handlers.stub.php | 5 +++ ext/zend_test/object_handlers_arginfo.h | 26 ++++++++++- ...observer_vm_interrupt_tailcall_helper.phpt | 26 +++++++++++ 4 files changed, 99 insertions(+), 1 deletion(-) create mode 100644 ext/zend_test/tests/observer_vm_interrupt_tailcall_helper.phpt diff --git a/ext/zend_test/object_handlers.c b/ext/zend_test/object_handlers.c index 15e362605f8e..9163c2f416a1 100644 --- a/ext/zend_test/object_handlers.c +++ b/ext/zend_test/object_handlers.c @@ -232,6 +232,44 @@ ZEND_METHOD(NumericCastableNoOperations, __construct) ZVAL_COPY(OBJ_PROP_NUM(Z_OBJ_P(ZEND_THIS), 0), n); } +static zend_class_entry *vm_interrupt_comparable_ce; +static zend_object_handlers vm_interrupt_comparable_object_handlers; + +static zend_object* vm_interrupt_comparable_object_create_ex(zend_class_entry* ce, zend_long l) { + zend_object *obj = zend_objects_new(ce); + object_properties_init(obj, ce); + obj->handlers = &vm_interrupt_comparable_object_handlers; + ZVAL_LONG(OBJ_PROP_NUM(obj, 0), l); + return obj; +} + +static zend_object *vm_interrupt_comparable_object_create(zend_class_entry *ce) +{ + return vm_interrupt_comparable_object_create_ex(ce, 0); +} + +static int vm_interrupt_comparable_compare(zval *op1, zval *op2) +{ + ZEND_COMPARE_OBJECTS_FALLBACK(op1, op2); + + zend_atomic_bool_store_ex(&EG(vm_interrupt), true); + + return ZEND_THREEWAY_COMPARE( + Z_LVAL_P(OBJ_PROP_NUM(Z_OBJ_P(op1), 0)), + Z_LVAL_P(OBJ_PROP_NUM(Z_OBJ_P(op2), 0))); +} + +ZEND_METHOD(VmInterruptComparable, __construct) +{ + zend_long l; + + ZEND_PARSE_PARAMETERS_START(1, 1) + Z_PARAM_LONG(l) + ZEND_PARSE_PARAMETERS_END(); + + ZVAL_LONG(OBJ_PROP_NUM(Z_OBJ_P(ZEND_THIS), 0), l); +} + static zend_class_entry *dimension_handlers_no_ArrayAccess_ce; static zend_object_handlers dimension_handlers_no_ArrayAccess_object_handlers; @@ -302,6 +340,11 @@ void zend_test_object_handlers_init(void) memcpy(&numeric_castable_no_operation_object_handlers, &std_object_handlers, sizeof(zend_object_handlers)); numeric_castable_no_operation_object_handlers.cast_object = numeric_castable_no_operation_cast_object; + vm_interrupt_comparable_ce = register_class_VmInterruptComparable(); + vm_interrupt_comparable_ce->create_object = vm_interrupt_comparable_object_create; + memcpy(&vm_interrupt_comparable_object_handlers, &std_object_handlers, sizeof(zend_object_handlers)); + vm_interrupt_comparable_object_handlers.compare = vm_interrupt_comparable_compare; + dimension_handlers_no_ArrayAccess_ce = register_class_DimensionHandlersNoArrayAccess(); dimension_handlers_no_ArrayAccess_ce->create_object = dimension_handlers_no_ArrayAccess_object_create; memcpy(&dimension_handlers_no_ArrayAccess_object_handlers, &std_object_handlers, sizeof(zend_object_handlers)); diff --git a/ext/zend_test/object_handlers.stub.php b/ext/zend_test/object_handlers.stub.php index a474908b1095..8c8e7f9bfe9b 100644 --- a/ext/zend_test/object_handlers.stub.php +++ b/ext/zend_test/object_handlers.stub.php @@ -23,6 +23,11 @@ final class NumericCastableNoOperations { public function __construct(int|float $val) {} } +final class VmInterruptComparable { + private int $val; + public function __construct(int $val) {} +} + class DimensionHandlersNoArrayAccess { public bool $read = false; public bool $write = false; diff --git a/ext/zend_test/object_handlers_arginfo.h b/ext/zend_test/object_handlers_arginfo.h index 370ad13894aa..b46028a6e011 100644 --- a/ext/zend_test/object_handlers_arginfo.h +++ b/ext/zend_test/object_handlers_arginfo.h @@ -1,5 +1,5 @@ /* This is a generated file, edit the .stub.php file instead. - * Stub hash: 81be60f2c465ffe5c036739d072ab80d9c388907 */ + * Stub hash: 1a70ed60c5af38539b1222a979f97fddf7d1826e */ ZEND_BEGIN_ARG_INFO_EX(arginfo_class_DoOperationNoCast___construct, 0, 0, 1) ZEND_ARG_TYPE_INFO(0, val, IS_LONG, 0) @@ -15,10 +15,13 @@ ZEND_BEGIN_ARG_INFO_EX(arginfo_class_NumericCastableNoOperations___construct, 0, ZEND_ARG_TYPE_MASK(0, val, MAY_BE_LONG|MAY_BE_DOUBLE, NULL) ZEND_END_ARG_INFO() +#define arginfo_class_VmInterruptComparable___construct arginfo_class_DoOperationNoCast___construct + static ZEND_METHOD(DoOperationNoCast, __construct); static ZEND_METHOD(LongCastableNoOperations, __construct); static ZEND_METHOD(FloatCastableNoOperations, __construct); static ZEND_METHOD(NumericCastableNoOperations, __construct); +static ZEND_METHOD(VmInterruptComparable, __construct); static const zend_function_entry class_DoOperationNoCast_methods[] = { ZEND_ME(DoOperationNoCast, __construct, arginfo_class_DoOperationNoCast___construct, ZEND_ACC_PUBLIC) @@ -40,6 +43,11 @@ static const zend_function_entry class_NumericCastableNoOperations_methods[] = { ZEND_FE_END }; +static const zend_function_entry class_VmInterruptComparable_methods[] = { + ZEND_ME(VmInterruptComparable, __construct, arginfo_class_VmInterruptComparable___construct, ZEND_ACC_PUBLIC) + ZEND_FE_END +}; + static zend_class_entry *register_class_DoOperationNoCast(void) { zend_class_entry ce, *class_entry; @@ -104,6 +112,22 @@ static zend_class_entry *register_class_NumericCastableNoOperations(void) return class_entry; } +static zend_class_entry *register_class_VmInterruptComparable(void) +{ + zend_class_entry ce, *class_entry; + + INIT_CLASS_ENTRY(ce, "VmInterruptComparable", class_VmInterruptComparable_methods); + class_entry = zend_register_internal_class_with_flags(&ce, NULL, ZEND_ACC_FINAL); + + zval property_val_default_value; + ZVAL_UNDEF(&property_val_default_value); + zend_string *property_val_name = zend_string_init("val", sizeof("val") - 1, 1); + zend_declare_typed_property(class_entry, property_val_name, &property_val_default_value, ZEND_ACC_PRIVATE, NULL, (zend_type) ZEND_TYPE_INIT_MASK(MAY_BE_LONG)); + zend_string_release(property_val_name); + + return class_entry; +} + static zend_class_entry *register_class_DimensionHandlersNoArrayAccess(void) { zend_class_entry ce, *class_entry; diff --git a/ext/zend_test/tests/observer_vm_interrupt_tailcall_helper.phpt b/ext/zend_test/tests/observer_vm_interrupt_tailcall_helper.phpt new file mode 100644 index 000000000000..d0178bdbf614 --- /dev/null +++ b/ext/zend_test/tests/observer_vm_interrupt_tailcall_helper.phpt @@ -0,0 +1,26 @@ +--TEST-- +Observer: VM interrupt during tailcall helper dispatch +--DESCRIPTION-- +This exercises a VM interrupt raised while an opcode handler dispatches to an +extra-argument helper. On the tailcall VM, the helper may return an opline +tagged with ZEND_VM_ENTER_BIT; treating that tagged value as a zend_op * before +tailcalling the next handler can crash. +--EXTENSIONS-- +zend_test +--INI-- +opcache.jit=0 +zend_test.observer.set_vm_interrupt_on_begin=1 +--FILE-- + +--EXPECT-- +stdClass From 914efebf89b4ada533e9a528943eee96db7733fb Mon Sep 17 00:00:00 2001 From: Levi Morrison Date: Thu, 30 Apr 2026 10:12:40 -0600 Subject: [PATCH 2/7] Fix tailcall helper dispatch after VM interrupt --- Zend/zend_vm_execute.h | 3 +++ Zend/zend_vm_gen.php | 3 +++ 2 files changed, 6 insertions(+) diff --git a/Zend/zend_vm_execute.h b/Zend/zend_vm_execute.h index 0d11a17ce781..47021b8a04e1 100644 --- a/Zend/zend_vm_execute.h +++ b/Zend/zend_vm_execute.h @@ -56523,6 +56523,9 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_NULL_HANDLER( # define ZEND_VM_DISPATCH_TO_HELPER(call) \ do { \ opline = call; \ + if (UNEXPECTED(((uintptr_t)opline & ZEND_VM_ENTER_BIT))) { \ + return opline; \ + } \ ZEND_VM_TAIL_CALL(opline->handler(ZEND_OPCODE_HANDLER_ARGS_PASSTHRU)); \ } while (0) # define ZEND_VM_DISPATCH_TO_LEAVE_HELPER(helper) opline = &call_leave_op; SAVE_OPLINE(); ZEND_VM_CONTINUE() diff --git a/Zend/zend_vm_gen.php b/Zend/zend_vm_gen.php index 38c20b24da2e..09c940882ec4 100755 --- a/Zend/zend_vm_gen.php +++ b/Zend/zend_vm_gen.php @@ -2136,6 +2136,9 @@ function gen_executor($f, $skl, $spec, $kind, $executor_name, $initializer_name) out($f,"# define ZEND_VM_DISPATCH_TO_HELPER(call) \\\n"); out($f," do { \\\n"); out($f," opline = call; \\\n"); + out($f," if (UNEXPECTED(((uintptr_t)opline & ZEND_VM_ENTER_BIT))) { \\\n"); + out($f," return opline; \\\n"); + out($f," } \\\n"); out($f," ZEND_VM_TAIL_CALL(opline->handler(ZEND_OPCODE_HANDLER_ARGS_PASSTHRU)); \\\n"); out($f," } while (0)\n"); out($f,"# define ZEND_VM_DISPATCH_TO_LEAVE_HELPER(helper) opline = &call_leave_op; SAVE_OPLINE(); ZEND_VM_CONTINUE()\n"); From 57d406a655e680a5c1ae55fd0b942a22ef385bb2 Mon Sep 17 00:00:00 2001 From: Arnaud Le Blanc Date: Fri, 1 May 2026 14:17:34 +0200 Subject: [PATCH 3/7] call_interrupt_op --- Zend/zend_vm_def.h | 5 +++++ Zend/zend_vm_execute.h | 35 ++++++++++++++++++++++++++++++----- Zend/zend_vm_gen.php | 30 +++++++++++++++++++++++++----- 3 files changed, 60 insertions(+), 10 deletions(-) diff --git a/Zend/zend_vm_def.h b/Zend/zend_vm_def.h index 6712625e83b1..fdfe48fc8421 100644 --- a/Zend/zend_vm_def.h +++ b/Zend/zend_vm_def.h @@ -10523,7 +10523,12 @@ ZEND_VM_DEFINE_OP(137, ZEND_OP_DATA); ZEND_VM_HELPER(zend_interrupt_helper, ANY, ANY) { zend_atomic_bool_store_ex(&EG(vm_interrupt), false); +#if ZEND_VM_KIND == ZEND_VM_KIND_TAILCALL + /* opline is &call_interrupt_op. Load orig opline. */ + LOAD_OPLINE(); +#else SAVE_OPLINE(); +#endif if (zend_atomic_bool_load_ex(&EG(timed_out))) { zend_timeout(); } else if (zend_interrupt_function) { diff --git a/Zend/zend_vm_execute.h b/Zend/zend_vm_execute.h index 47021b8a04e1..e85025978e25 100644 --- a/Zend/zend_vm_execute.h +++ b/Zend/zend_vm_execute.h @@ -331,6 +331,11 @@ static zend_op hybrid_halt_op; static zend_vm_opcode_handler_func_t const * zend_opcode_handler_funcs; #endif +#if ZEND_VM_KIND == ZEND_VM_KIND_TAILCALL + +static const zend_op call_interrupt_op; +#endif + #if (ZEND_VM_KIND != ZEND_VM_KIND_HYBRID && ZEND_VM_KIND != ZEND_VM_KIND_TAILCALL) || !ZEND_VM_SPEC static zend_vm_opcode_handler_t zend_vm_get_opcode_handler(uint8_t opcode, const zend_op* op); #endif @@ -449,6 +454,7 @@ static zend_vm_opcode_handler_func_t zend_vm_get_opcode_handler_func(uint8_t opc #define ZEND_VM_DISPATCH(opcode, opline) return zend_vm_get_opcode_handler_func(opcode, opline)(ZEND_OPCODE_HANDLER_ARGS_PASSTHRU); static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV zend_interrupt_helper_SPEC(ZEND_OPCODE_HANDLER_ARGS); +static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV zend_interrupt(ZEND_OPCODE_HANDLER_ARGS); static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_NULL_HANDLER(ZEND_OPCODE_HANDLER_ARGS); static zend_never_inline ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV_EX zend_add_helper_SPEC(ZEND_OPCODE_HANDLER_ARGS_EX zval *op_1, zval *op_2) @@ -4011,7 +4017,12 @@ static ZEND_VM_HOT ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_J static zend_never_inline ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV zend_interrupt_helper_SPEC(ZEND_OPCODE_HANDLER_ARGS) { zend_atomic_bool_store_ex(&EG(vm_interrupt), false); +#if ZEND_VM_KIND == ZEND_VM_KIND_TAILCALL + /* opline is &call_interrupt_op. Load orig opline. */ + LOAD_OPLINE(); +#else SAVE_OPLINE(); +#endif if (zend_atomic_bool_load_ex(&EG(timed_out))) { zend_timeout(); } else if (zend_interrupt_function) { @@ -56498,6 +56509,10 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_NULL_HANDLER( ZEND_VM_NEXT_OPCODE(); /* Never reached */ } +static ZEND_COLD zend_never_inline ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV zend_interrupt(ZEND_OPCODE_HANDLER_ARGS) { + SAVE_OPLINE(); + return &call_interrupt_op; +} #if (ZEND_VM_KIND == ZEND_VM_KIND_HYBRID) # undef ZEND_VM_TAIL_CALL @@ -56523,15 +56538,13 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_NULL_HANDLER( # define ZEND_VM_DISPATCH_TO_HELPER(call) \ do { \ opline = call; \ - if (UNEXPECTED(((uintptr_t)opline & ZEND_VM_ENTER_BIT))) { \ - return opline; \ - } \ ZEND_VM_TAIL_CALL(opline->handler(ZEND_OPCODE_HANDLER_ARGS_PASSTHRU)); \ } while (0) # define ZEND_VM_DISPATCH_TO_LEAVE_HELPER(helper) opline = &call_leave_op; SAVE_OPLINE(); ZEND_VM_CONTINUE() -# define ZEND_VM_INTERRUPT() ZEND_VM_TAIL_CALL(zend_interrupt_helper_SPEC_TAILCALL(ZEND_OPCODE_HANDLER_ARGS_PASSTHRU)) +# define ZEND_VM_INTERRUPT() ZEND_VM_TAIL_CALL(zend_interrupt_TAILCALL(ZEND_OPCODE_HANDLER_ARGS_PASSTHRU)) static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV zend_interrupt_helper_SPEC_TAILCALL(ZEND_OPCODE_HANDLER_ARGS); +static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV zend_interrupt_TAILCALL(ZEND_OPCODE_HANDLER_ARGS); static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_NULL_TAILCALL_HANDLER(ZEND_OPCODE_HANDLER_ARGS); static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_HALT_TAILCALL_HANDLER(ZEND_OPCODE_HANDLER_ARGS); static zend_never_inline const zend_op *ZEND_OPCODE_HANDLER_CCONV zend_leave_helper_SPEC_TAILCALL(zend_execute_data *ex, const zend_op *opline); @@ -56542,6 +56555,9 @@ static const zend_op call_halt_op = { static const zend_op call_leave_op = { .handler = zend_leave_helper_SPEC_TAILCALL, }; +static const zend_op call_interrupt_op = { + .handler = zend_interrupt_helper_SPEC_TAILCALL, +}; static zend_never_inline ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV_EX zend_add_helper_SPEC_TAILCALL(ZEND_OPCODE_HANDLER_ARGS_EX zval *op_1, zval *op_2); static zend_never_inline ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV_EX zend_sub_helper_SPEC_TAILCALL(ZEND_OPCODE_HANDLER_ARGS_EX zval *op_1, zval *op_2); @@ -59677,7 +59693,12 @@ static ZEND_VM_HOT ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_JMP_FO static zend_never_inline ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV zend_interrupt_helper_SPEC_TAILCALL(ZEND_OPCODE_HANDLER_ARGS) { zend_atomic_bool_store_ex(&EG(vm_interrupt), false); +#if ZEND_VM_KIND == ZEND_VM_KIND_TAILCALL + /* opline is &call_interrupt_op. Load orig opline. */ + LOAD_OPLINE(); +#else SAVE_OPLINE(); +#endif if (zend_atomic_bool_load_ex(&EG(timed_out))) { zend_timeout(); } else if (zend_interrupt_function) { @@ -111865,13 +111886,17 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_HALT_TAILCALL_HAND return (zend_op*) ZEND_VM_ENTER_BIT; } +static ZEND_COLD zend_never_inline ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV zend_interrupt_TAILCALL(ZEND_OPCODE_HANDLER_ARGS) { + SAVE_OPLINE(); + ZEND_VM_TAIL_CALL(zend_interrupt_helper_SPEC_TAILCALL(ZEND_OPCODE_HANDLER_ARGS_PASSTHRU)); +} /* The following helpers can not tailcall due to signature mismatch. Redefine some macros so they do not enforce tailcall. */ #pragma push_macro("ZEND_VM_CONTINUE") #undef ZEND_VM_CONTINUE #pragma push_macro("ZEND_VM_INTERRUPT") #undef ZEND_VM_INTERRUPT #define ZEND_VM_CONTINUE(handler) return opline -#define ZEND_VM_INTERRUPT() return zend_interrupt_helper_SPEC(ZEND_OPCODE_HANDLER_ARGS_PASSTHRU) +#define ZEND_VM_INTERRUPT() return zend_interrupt(ZEND_OPCODE_HANDLER_ARGS_PASSTHRU); static zend_never_inline ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV_EX zend_add_helper_SPEC_TAILCALL(ZEND_OPCODE_HANDLER_ARGS_EX zval *op_1, zval *op_2) { USE_OPLINE diff --git a/Zend/zend_vm_gen.php b/Zend/zend_vm_gen.php index 09c940882ec4..b03790a7013d 100755 --- a/Zend/zend_vm_gen.php +++ b/Zend/zend_vm_gen.php @@ -1594,6 +1594,19 @@ function gen_halt_handler($f, $kind) { out($f,"}\n\n"); } +function gen_interrupt_func($f, $kind, $spec) { + $cconv = $kind === ZEND_VM_KIND_TAILCALL ? 'ZEND_OPCODE_HANDLER_CCONV' : 'ZEND_OPCODE_HANDLER_FUNC_CCONV'; + $variant = $kind === ZEND_VM_KIND_TAILCALL ? '_TAILCALL' : ''; + out($f, "static ZEND_COLD zend_never_inline ZEND_OPCODE_HANDLER_RET {$cconv} zend_interrupt{$variant}(ZEND_OPCODE_HANDLER_ARGS) {\n"); + out($f,"\tSAVE_OPLINE();\n"); + if ($kind === ZEND_VM_KIND_TAILCALL) { + out($f,"\tZEND_VM_TAIL_CALL(zend_interrupt_helper".($spec?"_SPEC":"")."_TAILCALL(ZEND_OPCODE_HANDLER_ARGS_PASSTHRU));\n"); + } else { + out($f, "\treturn &call_interrupt_op;\n"); + } + out($f, "}\n"); +} + function extra_spec_name($extra_spec) { global $prefix; @@ -1804,10 +1817,12 @@ function gen_executor_code($f, $spec, $kind, $prolog, &$switch_labels = array()) switch ($kind) { case ZEND_VM_KIND_CALL: gen_null_handler($f, $kind); + gen_interrupt_func($f, $kind, $spec); break; case ZEND_VM_KIND_TAILCALL: gen_null_handler($f, $kind); gen_halt_handler($f, $kind); + gen_interrupt_func($f, $kind, $spec); break; case ZEND_VM_KIND_SWITCH: out($f,"default: ZEND_NULL_LABEL:\n"); @@ -1845,7 +1860,7 @@ function gen_executor_code($f, $spec, $kind, $prolog, &$switch_labels = array()) out($f, "#pragma push_macro(\"ZEND_VM_INTERRUPT\")\n"); out($f, "#undef ZEND_VM_INTERRUPT\n"); out($f, "#define ZEND_VM_CONTINUE(handler) return opline\n"); - out($f, "#define ZEND_VM_INTERRUPT() return zend_interrupt_helper".($spec?"_SPEC":"")."(ZEND_OPCODE_HANDLER_ARGS_PASSTHRU)\n"); + out($f, "#define ZEND_VM_INTERRUPT() return zend_interrupt(ZEND_OPCODE_HANDLER_ARGS_PASSTHRU);\n"); out($f, $delayed_helpers); out($f, "#pragma pop_macro(\"ZEND_VM_INTERRUPT\")\n"); out($f, "#pragma pop_macro(\"ZEND_VM_CONTINUE\")\n"); @@ -1901,6 +1916,9 @@ function gen_executor($f, $skl, $spec, $kind, $executor_name, $initializer_name) out($f,"#if ZEND_VM_KIND == ZEND_VM_KIND_HYBRID || ZEND_VM_KIND == ZEND_VM_KIND_TAILCALL\n\n"); out($f,"static zend_vm_opcode_handler_func_t const * zend_opcode_handler_funcs;\n"); out($f,"#endif\n"); + out($f,"#if ZEND_VM_KIND == ZEND_VM_KIND_TAILCALL\n\n"); + out($f,"static const zend_op call_interrupt_op;\n"); + out($f,"#endif\n\n"); } out($f,"#if (ZEND_VM_KIND != ZEND_VM_KIND_HYBRID && ZEND_VM_KIND != ZEND_VM_KIND_TAILCALL) || !ZEND_VM_SPEC\n"); out($f,"static zend_vm_opcode_handler_t zend_vm_get_opcode_handler(uint8_t opcode, const zend_op* op);\n"); @@ -2038,6 +2056,7 @@ function gen_executor($f, $skl, $spec, $kind, $executor_name, $initializer_name) } out($f,"\n"); out($f,"static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV zend_interrupt_helper".($spec?"_SPEC":"")."(ZEND_OPCODE_HANDLER_ARGS);\n"); + out($f,"static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV zend_interrupt(ZEND_OPCODE_HANDLER_ARGS);\n"); out($f,"static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_NULL_HANDLER(ZEND_OPCODE_HANDLER_ARGS);\n"); out($f,"\n"); break; @@ -2136,15 +2155,13 @@ function gen_executor($f, $skl, $spec, $kind, $executor_name, $initializer_name) out($f,"# define ZEND_VM_DISPATCH_TO_HELPER(call) \\\n"); out($f," do { \\\n"); out($f," opline = call; \\\n"); - out($f," if (UNEXPECTED(((uintptr_t)opline & ZEND_VM_ENTER_BIT))) { \\\n"); - out($f," return opline; \\\n"); - out($f," } \\\n"); out($f," ZEND_VM_TAIL_CALL(opline->handler(ZEND_OPCODE_HANDLER_ARGS_PASSTHRU)); \\\n"); out($f," } while (0)\n"); out($f,"# define ZEND_VM_DISPATCH_TO_LEAVE_HELPER(helper) opline = &call_leave_op; SAVE_OPLINE(); ZEND_VM_CONTINUE()\n"); - out($f,"# define ZEND_VM_INTERRUPT() ZEND_VM_TAIL_CALL(zend_interrupt_helper".($spec?"_SPEC":"")."_TAILCALL(ZEND_OPCODE_HANDLER_ARGS_PASSTHRU))\n"); + out($f,"# define ZEND_VM_INTERRUPT() ZEND_VM_TAIL_CALL(zend_interrupt_TAILCALL(ZEND_OPCODE_HANDLER_ARGS_PASSTHRU))\n"); out($f,"\n"); out($f,"static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV zend_interrupt_helper".($spec?"_SPEC":"")."_TAILCALL(ZEND_OPCODE_HANDLER_ARGS);\n"); + out($f,"static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV zend_interrupt_TAILCALL(ZEND_OPCODE_HANDLER_ARGS);\n"); out($f,"static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_NULL_TAILCALL_HANDLER(ZEND_OPCODE_HANDLER_ARGS);\n"); out($f,"static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_HALT_TAILCALL_HANDLER(ZEND_OPCODE_HANDLER_ARGS);\n"); out($f,"static zend_never_inline const zend_op *ZEND_OPCODE_HANDLER_CCONV zend_leave_helper_SPEC_TAILCALL(zend_execute_data *ex, const zend_op *opline);\n"); @@ -2155,6 +2172,9 @@ function gen_executor($f, $skl, $spec, $kind, $executor_name, $initializer_name) out($f,"static const zend_op call_leave_op = {\n"); out($f," .handler = zend_leave_helper_SPEC_TAILCALL,\n"); out($f,"};\n"); + out($f,"static const zend_op call_interrupt_op = {\n"); + out($f," .handler = zend_interrupt_helper_SPEC_TAILCALL,\n"); + out($f,"};\n"); out($f,"\n"); gen_executor_code($f, $spec, ZEND_VM_KIND_TAILCALL, $m[1]); From 0c31be160811024e7dc770b5f84a8d60008647e3 Mon Sep 17 00:00:00 2001 From: Levi Morrison Date: Mon, 11 May 2026 14:26:11 -0600 Subject: [PATCH 4/7] Fix zend_interrupt compilation for non-tailcall VM builds The call_interrupt_op sentinel approach only applies to TAILCALL VM, where musttail constraints prevent checking ZEND_VM_ENTER_BIT in ZEND_VM_DISPATCH_TO_HELPER. For CALL VM (non-global-reg), zend_interrupt now forwards directly to zend_interrupt_helper_SPEC instead of returning &call_interrupt_op. Guard zend_interrupt (function and forward declaration) with: #if !(defined(ZEND_VM_FP_GLOBAL_REG) && defined(ZEND_VM_IP_GLOBAL_REG)) && ZEND_VM_KIND != ZEND_VM_KIND_TAILCALL This fixes three failure modes: - Global-reg builds (HYBRID/CALL with FP+IP regs): ZEND_OPCODE_HANDLER_RET is void, making return &call_interrupt_op a constraint violation - CALL non-global-reg builds (x32, Windows): call_interrupt_op was undeclared for non-TAILCALL VM kinds - TAILCALL builds: zend_interrupt (CALL variant) compiled but unused, triggering -Werror=unused-function Co-Authored-By: Claude Sonnet 4.6 --- Zend/zend_vm_execute.h | 12 ++++++++++-- Zend/zend_vm_gen.php | 17 +++++++++++++++-- 2 files changed, 25 insertions(+), 4 deletions(-) diff --git a/Zend/zend_vm_execute.h b/Zend/zend_vm_execute.h index e85025978e25..3eafecbf0da5 100644 --- a/Zend/zend_vm_execute.h +++ b/Zend/zend_vm_execute.h @@ -454,7 +454,9 @@ static zend_vm_opcode_handler_func_t zend_vm_get_opcode_handler_func(uint8_t opc #define ZEND_VM_DISPATCH(opcode, opline) return zend_vm_get_opcode_handler_func(opcode, opline)(ZEND_OPCODE_HANDLER_ARGS_PASSTHRU); static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV zend_interrupt_helper_SPEC(ZEND_OPCODE_HANDLER_ARGS); +#if !(defined(ZEND_VM_FP_GLOBAL_REG) && defined(ZEND_VM_IP_GLOBAL_REG)) && ZEND_VM_KIND != ZEND_VM_KIND_TAILCALL static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV zend_interrupt(ZEND_OPCODE_HANDLER_ARGS); +#endif static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_NULL_HANDLER(ZEND_OPCODE_HANDLER_ARGS); static zend_never_inline ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV_EX zend_add_helper_SPEC(ZEND_OPCODE_HANDLER_ARGS_EX zval *op_1, zval *op_2) @@ -56509,10 +56511,12 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_NULL_HANDLER( ZEND_VM_NEXT_OPCODE(); /* Never reached */ } +#if !(defined(ZEND_VM_FP_GLOBAL_REG) && defined(ZEND_VM_IP_GLOBAL_REG)) && ZEND_VM_KIND != ZEND_VM_KIND_TAILCALL static ZEND_COLD zend_never_inline ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV zend_interrupt(ZEND_OPCODE_HANDLER_ARGS) { SAVE_OPLINE(); - return &call_interrupt_op; + return zend_interrupt_helper_SPEC(ZEND_OPCODE_HANDLER_ARGS_PASSTHRU); } +#endif #if (ZEND_VM_KIND == ZEND_VM_KIND_HYBRID) # undef ZEND_VM_TAIL_CALL @@ -111896,7 +111900,11 @@ static ZEND_COLD zend_never_inline ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_C #pragma push_macro("ZEND_VM_INTERRUPT") #undef ZEND_VM_INTERRUPT #define ZEND_VM_CONTINUE(handler) return opline -#define ZEND_VM_INTERRUPT() return zend_interrupt(ZEND_OPCODE_HANDLER_ARGS_PASSTHRU); +#if !(defined(ZEND_VM_FP_GLOBAL_REG) && defined(ZEND_VM_IP_GLOBAL_REG)) && ZEND_VM_KIND != ZEND_VM_KIND_TAILCALL +# define ZEND_VM_INTERRUPT() return zend_interrupt(ZEND_OPCODE_HANDLER_ARGS_PASSTHRU); +#else +# define ZEND_VM_INTERRUPT() return zend_interrupt_helper_SPEC(ZEND_OPCODE_HANDLER_ARGS_PASSTHRU); +#endif static zend_never_inline ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV_EX zend_add_helper_SPEC_TAILCALL(ZEND_OPCODE_HANDLER_ARGS_EX zval *op_1, zval *op_2) { USE_OPLINE diff --git a/Zend/zend_vm_gen.php b/Zend/zend_vm_gen.php index b03790a7013d..754c2aa1bfd2 100755 --- a/Zend/zend_vm_gen.php +++ b/Zend/zend_vm_gen.php @@ -1597,14 +1597,21 @@ function gen_halt_handler($f, $kind) { function gen_interrupt_func($f, $kind, $spec) { $cconv = $kind === ZEND_VM_KIND_TAILCALL ? 'ZEND_OPCODE_HANDLER_CCONV' : 'ZEND_OPCODE_HANDLER_FUNC_CCONV'; $variant = $kind === ZEND_VM_KIND_TAILCALL ? '_TAILCALL' : ''; + if ($kind !== ZEND_VM_KIND_TAILCALL) { + /* Guard: exclude global-reg builds (void return) and TAILCALL builds (have own variant) */ + out($f, "#if !(defined(ZEND_VM_FP_GLOBAL_REG) && defined(ZEND_VM_IP_GLOBAL_REG)) && ZEND_VM_KIND != ZEND_VM_KIND_TAILCALL\n"); + } out($f, "static ZEND_COLD zend_never_inline ZEND_OPCODE_HANDLER_RET {$cconv} zend_interrupt{$variant}(ZEND_OPCODE_HANDLER_ARGS) {\n"); out($f,"\tSAVE_OPLINE();\n"); if ($kind === ZEND_VM_KIND_TAILCALL) { out($f,"\tZEND_VM_TAIL_CALL(zend_interrupt_helper".($spec?"_SPEC":"")."_TAILCALL(ZEND_OPCODE_HANDLER_ARGS_PASSTHRU));\n"); } else { - out($f, "\treturn &call_interrupt_op;\n"); + out($f, "\treturn zend_interrupt_helper".($spec?"_SPEC":"")."(ZEND_OPCODE_HANDLER_ARGS_PASSTHRU);\n"); } out($f, "}\n"); + if ($kind !== ZEND_VM_KIND_TAILCALL) { + out($f, "#endif\n"); + } } function extra_spec_name($extra_spec) { @@ -1860,7 +1867,11 @@ function gen_executor_code($f, $spec, $kind, $prolog, &$switch_labels = array()) out($f, "#pragma push_macro(\"ZEND_VM_INTERRUPT\")\n"); out($f, "#undef ZEND_VM_INTERRUPT\n"); out($f, "#define ZEND_VM_CONTINUE(handler) return opline\n"); - out($f, "#define ZEND_VM_INTERRUPT() return zend_interrupt(ZEND_OPCODE_HANDLER_ARGS_PASSTHRU);\n"); + out($f, "#if !(defined(ZEND_VM_FP_GLOBAL_REG) && defined(ZEND_VM_IP_GLOBAL_REG)) && ZEND_VM_KIND != ZEND_VM_KIND_TAILCALL\n"); + out($f, "# define ZEND_VM_INTERRUPT() return zend_interrupt(ZEND_OPCODE_HANDLER_ARGS_PASSTHRU);\n"); + out($f, "#else\n"); + out($f, "# define ZEND_VM_INTERRUPT() return zend_interrupt_helper".($spec?"_SPEC":"")."(ZEND_OPCODE_HANDLER_ARGS_PASSTHRU);\n"); + out($f, "#endif\n"); out($f, $delayed_helpers); out($f, "#pragma pop_macro(\"ZEND_VM_INTERRUPT\")\n"); out($f, "#pragma pop_macro(\"ZEND_VM_CONTINUE\")\n"); @@ -2056,7 +2067,9 @@ function gen_executor($f, $skl, $spec, $kind, $executor_name, $initializer_name) } out($f,"\n"); out($f,"static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV zend_interrupt_helper".($spec?"_SPEC":"")."(ZEND_OPCODE_HANDLER_ARGS);\n"); + out($f,"#if !(defined(ZEND_VM_FP_GLOBAL_REG) && defined(ZEND_VM_IP_GLOBAL_REG)) && ZEND_VM_KIND != ZEND_VM_KIND_TAILCALL\n"); out($f,"static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV zend_interrupt(ZEND_OPCODE_HANDLER_ARGS);\n"); + out($f,"#endif\n"); out($f,"static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_NULL_HANDLER(ZEND_OPCODE_HANDLER_ARGS);\n"); out($f,"\n"); break; From f9fd505bc88b0dc591b0972191dccbffc084c0aa Mon Sep 17 00:00:00 2001 From: Levi Morrison Date: Mon, 11 May 2026 18:44:04 -0600 Subject: [PATCH 5/7] Fix TAILCALL delayed helper interrupt via call_interrupt_op sentinel In TAILCALL delayed helpers (EX-signature helpers that can't musttail), ZEND_VM_INTERRUPT() was routing through zend_interrupt_helper_SPEC, which can return a tagged pointer (ZEND_VM_ENTER_BIT set) via ZEND_VM_ENTER(). That tagged pointer propagated back to ZEND_VM_DISPATCH_TO_HELPER, which blindly dispatched to it, causing a misaligned access crash. The fix: TAILCALL delayed helpers use SAVE_OPLINE() + return &call_interrupt_op, the sentinel pattern already used by TAILCALL VM. ZEND_VM_DISPATCH_TO_HELPER then dispatches to call_interrupt_op->handler (zend_interrupt_helper_SPEC_TAILCALL), which loads the real opline from EX(opline) and handles the interrupt cleanly. Co-Authored-By: Claude Sonnet 4.6 --- Zend/zend_vm_execute.h | 8 +++++--- Zend/zend_vm_gen.php | 8 +++++--- 2 files changed, 10 insertions(+), 6 deletions(-) diff --git a/Zend/zend_vm_execute.h b/Zend/zend_vm_execute.h index 3eafecbf0da5..3eade76277bf 100644 --- a/Zend/zend_vm_execute.h +++ b/Zend/zend_vm_execute.h @@ -111900,10 +111900,12 @@ static ZEND_COLD zend_never_inline ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_C #pragma push_macro("ZEND_VM_INTERRUPT") #undef ZEND_VM_INTERRUPT #define ZEND_VM_CONTINUE(handler) return opline -#if !(defined(ZEND_VM_FP_GLOBAL_REG) && defined(ZEND_VM_IP_GLOBAL_REG)) && ZEND_VM_KIND != ZEND_VM_KIND_TAILCALL -# define ZEND_VM_INTERRUPT() return zend_interrupt(ZEND_OPCODE_HANDLER_ARGS_PASSTHRU); -#else +#if ZEND_VM_KIND == ZEND_VM_KIND_TAILCALL +# define ZEND_VM_INTERRUPT() SAVE_OPLINE(); return &call_interrupt_op; +#elif defined(ZEND_VM_FP_GLOBAL_REG) && defined(ZEND_VM_IP_GLOBAL_REG) # define ZEND_VM_INTERRUPT() return zend_interrupt_helper_SPEC(ZEND_OPCODE_HANDLER_ARGS_PASSTHRU); +#else +# define ZEND_VM_INTERRUPT() return zend_interrupt(ZEND_OPCODE_HANDLER_ARGS_PASSTHRU); #endif static zend_never_inline ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV_EX zend_add_helper_SPEC_TAILCALL(ZEND_OPCODE_HANDLER_ARGS_EX zval *op_1, zval *op_2) { diff --git a/Zend/zend_vm_gen.php b/Zend/zend_vm_gen.php index 754c2aa1bfd2..d9f609f614fb 100755 --- a/Zend/zend_vm_gen.php +++ b/Zend/zend_vm_gen.php @@ -1867,10 +1867,12 @@ function gen_executor_code($f, $spec, $kind, $prolog, &$switch_labels = array()) out($f, "#pragma push_macro(\"ZEND_VM_INTERRUPT\")\n"); out($f, "#undef ZEND_VM_INTERRUPT\n"); out($f, "#define ZEND_VM_CONTINUE(handler) return opline\n"); - out($f, "#if !(defined(ZEND_VM_FP_GLOBAL_REG) && defined(ZEND_VM_IP_GLOBAL_REG)) && ZEND_VM_KIND != ZEND_VM_KIND_TAILCALL\n"); - out($f, "# define ZEND_VM_INTERRUPT() return zend_interrupt(ZEND_OPCODE_HANDLER_ARGS_PASSTHRU);\n"); - out($f, "#else\n"); + out($f, "#if ZEND_VM_KIND == ZEND_VM_KIND_TAILCALL\n"); + out($f, "# define ZEND_VM_INTERRUPT() SAVE_OPLINE(); return &call_interrupt_op;\n"); + out($f, "#elif defined(ZEND_VM_FP_GLOBAL_REG) && defined(ZEND_VM_IP_GLOBAL_REG)\n"); out($f, "# define ZEND_VM_INTERRUPT() return zend_interrupt_helper".($spec?"_SPEC":"")."(ZEND_OPCODE_HANDLER_ARGS_PASSTHRU);\n"); + out($f, "#else\n"); + out($f, "# define ZEND_VM_INTERRUPT() return zend_interrupt(ZEND_OPCODE_HANDLER_ARGS_PASSTHRU);\n"); out($f, "#endif\n"); out($f, $delayed_helpers); out($f, "#pragma pop_macro(\"ZEND_VM_INTERRUPT\")\n"); From 6af994d3e340e7bbc444ead47d831baf0bb0e126 Mon Sep 17 00:00:00 2001 From: Levi Morrison Date: Tue, 12 May 2026 13:39:11 -0600 Subject: [PATCH 6/7] Remove unused zend_interrupt CALL variant; simplify delayed helpers macro MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The CALL variant of zend_interrupt was never called: delayed helpers only exist in TAILCALL builds ($delayed_helpers is only populated when $kind === ZEND_VM_KIND_TAILCALL), so the #elif/#else branches in the ZEND_VM_INTERRUPT macro were dead code, and zend_interrupt itself was compiled but unreferenced — triggering an unused-function warning on macOS ARM64 DEBUG NTS. Remove the CALL case from gen_interrupt_func, drop its forward declaration from the CALL section preamble, and replace the three-way C-level #if in the delayed helpers ZEND_VM_INTERRUPT macro with a single unconditional line (the TAILCALL sentinel path), since that block is only ever emitted for TAILCALL builds. Co-Authored-By: Claude Sonnet 4.6 --- Zend/zend_vm_execute.h | 15 --------------- Zend/zend_vm_gen.php | 27 ++------------------------- 2 files changed, 2 insertions(+), 40 deletions(-) diff --git a/Zend/zend_vm_execute.h b/Zend/zend_vm_execute.h index 3eade76277bf..1649b4ce3970 100644 --- a/Zend/zend_vm_execute.h +++ b/Zend/zend_vm_execute.h @@ -454,9 +454,6 @@ static zend_vm_opcode_handler_func_t zend_vm_get_opcode_handler_func(uint8_t opc #define ZEND_VM_DISPATCH(opcode, opline) return zend_vm_get_opcode_handler_func(opcode, opline)(ZEND_OPCODE_HANDLER_ARGS_PASSTHRU); static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV zend_interrupt_helper_SPEC(ZEND_OPCODE_HANDLER_ARGS); -#if !(defined(ZEND_VM_FP_GLOBAL_REG) && defined(ZEND_VM_IP_GLOBAL_REG)) && ZEND_VM_KIND != ZEND_VM_KIND_TAILCALL -static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV zend_interrupt(ZEND_OPCODE_HANDLER_ARGS); -#endif static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_NULL_HANDLER(ZEND_OPCODE_HANDLER_ARGS); static zend_never_inline ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV_EX zend_add_helper_SPEC(ZEND_OPCODE_HANDLER_ARGS_EX zval *op_1, zval *op_2) @@ -56511,12 +56508,6 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_NULL_HANDLER( ZEND_VM_NEXT_OPCODE(); /* Never reached */ } -#if !(defined(ZEND_VM_FP_GLOBAL_REG) && defined(ZEND_VM_IP_GLOBAL_REG)) && ZEND_VM_KIND != ZEND_VM_KIND_TAILCALL -static ZEND_COLD zend_never_inline ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV zend_interrupt(ZEND_OPCODE_HANDLER_ARGS) { - SAVE_OPLINE(); - return zend_interrupt_helper_SPEC(ZEND_OPCODE_HANDLER_ARGS_PASSTHRU); -} -#endif #if (ZEND_VM_KIND == ZEND_VM_KIND_HYBRID) # undef ZEND_VM_TAIL_CALL @@ -111900,13 +111891,7 @@ static ZEND_COLD zend_never_inline ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_C #pragma push_macro("ZEND_VM_INTERRUPT") #undef ZEND_VM_INTERRUPT #define ZEND_VM_CONTINUE(handler) return opline -#if ZEND_VM_KIND == ZEND_VM_KIND_TAILCALL # define ZEND_VM_INTERRUPT() SAVE_OPLINE(); return &call_interrupt_op; -#elif defined(ZEND_VM_FP_GLOBAL_REG) && defined(ZEND_VM_IP_GLOBAL_REG) -# define ZEND_VM_INTERRUPT() return zend_interrupt_helper_SPEC(ZEND_OPCODE_HANDLER_ARGS_PASSTHRU); -#else -# define ZEND_VM_INTERRUPT() return zend_interrupt(ZEND_OPCODE_HANDLER_ARGS_PASSTHRU); -#endif static zend_never_inline ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV_EX zend_add_helper_SPEC_TAILCALL(ZEND_OPCODE_HANDLER_ARGS_EX zval *op_1, zval *op_2) { USE_OPLINE diff --git a/Zend/zend_vm_gen.php b/Zend/zend_vm_gen.php index d9f609f614fb..25d32ac81a94 100755 --- a/Zend/zend_vm_gen.php +++ b/Zend/zend_vm_gen.php @@ -1595,23 +1595,10 @@ function gen_halt_handler($f, $kind) { } function gen_interrupt_func($f, $kind, $spec) { - $cconv = $kind === ZEND_VM_KIND_TAILCALL ? 'ZEND_OPCODE_HANDLER_CCONV' : 'ZEND_OPCODE_HANDLER_FUNC_CCONV'; - $variant = $kind === ZEND_VM_KIND_TAILCALL ? '_TAILCALL' : ''; - if ($kind !== ZEND_VM_KIND_TAILCALL) { - /* Guard: exclude global-reg builds (void return) and TAILCALL builds (have own variant) */ - out($f, "#if !(defined(ZEND_VM_FP_GLOBAL_REG) && defined(ZEND_VM_IP_GLOBAL_REG)) && ZEND_VM_KIND != ZEND_VM_KIND_TAILCALL\n"); - } - out($f, "static ZEND_COLD zend_never_inline ZEND_OPCODE_HANDLER_RET {$cconv} zend_interrupt{$variant}(ZEND_OPCODE_HANDLER_ARGS) {\n"); + out($f, "static ZEND_COLD zend_never_inline ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV zend_interrupt_TAILCALL(ZEND_OPCODE_HANDLER_ARGS) {\n"); out($f,"\tSAVE_OPLINE();\n"); - if ($kind === ZEND_VM_KIND_TAILCALL) { - out($f,"\tZEND_VM_TAIL_CALL(zend_interrupt_helper".($spec?"_SPEC":"")."_TAILCALL(ZEND_OPCODE_HANDLER_ARGS_PASSTHRU));\n"); - } else { - out($f, "\treturn zend_interrupt_helper".($spec?"_SPEC":"")."(ZEND_OPCODE_HANDLER_ARGS_PASSTHRU);\n"); - } + out($f,"\tZEND_VM_TAIL_CALL(zend_interrupt_helper".($spec?"_SPEC":"")."_TAILCALL(ZEND_OPCODE_HANDLER_ARGS_PASSTHRU));\n"); out($f, "}\n"); - if ($kind !== ZEND_VM_KIND_TAILCALL) { - out($f, "#endif\n"); - } } function extra_spec_name($extra_spec) { @@ -1824,7 +1811,6 @@ function gen_executor_code($f, $spec, $kind, $prolog, &$switch_labels = array()) switch ($kind) { case ZEND_VM_KIND_CALL: gen_null_handler($f, $kind); - gen_interrupt_func($f, $kind, $spec); break; case ZEND_VM_KIND_TAILCALL: gen_null_handler($f, $kind); @@ -1867,13 +1853,7 @@ function gen_executor_code($f, $spec, $kind, $prolog, &$switch_labels = array()) out($f, "#pragma push_macro(\"ZEND_VM_INTERRUPT\")\n"); out($f, "#undef ZEND_VM_INTERRUPT\n"); out($f, "#define ZEND_VM_CONTINUE(handler) return opline\n"); - out($f, "#if ZEND_VM_KIND == ZEND_VM_KIND_TAILCALL\n"); out($f, "# define ZEND_VM_INTERRUPT() SAVE_OPLINE(); return &call_interrupt_op;\n"); - out($f, "#elif defined(ZEND_VM_FP_GLOBAL_REG) && defined(ZEND_VM_IP_GLOBAL_REG)\n"); - out($f, "# define ZEND_VM_INTERRUPT() return zend_interrupt_helper".($spec?"_SPEC":"")."(ZEND_OPCODE_HANDLER_ARGS_PASSTHRU);\n"); - out($f, "#else\n"); - out($f, "# define ZEND_VM_INTERRUPT() return zend_interrupt(ZEND_OPCODE_HANDLER_ARGS_PASSTHRU);\n"); - out($f, "#endif\n"); out($f, $delayed_helpers); out($f, "#pragma pop_macro(\"ZEND_VM_INTERRUPT\")\n"); out($f, "#pragma pop_macro(\"ZEND_VM_CONTINUE\")\n"); @@ -2069,9 +2049,6 @@ function gen_executor($f, $skl, $spec, $kind, $executor_name, $initializer_name) } out($f,"\n"); out($f,"static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV zend_interrupt_helper".($spec?"_SPEC":"")."(ZEND_OPCODE_HANDLER_ARGS);\n"); - out($f,"#if !(defined(ZEND_VM_FP_GLOBAL_REG) && defined(ZEND_VM_IP_GLOBAL_REG)) && ZEND_VM_KIND != ZEND_VM_KIND_TAILCALL\n"); - out($f,"static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV zend_interrupt(ZEND_OPCODE_HANDLER_ARGS);\n"); - out($f,"#endif\n"); out($f,"static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_NULL_HANDLER(ZEND_OPCODE_HANDLER_ARGS);\n"); out($f,"\n"); break; From 36a1c61e133cf1c33f046d4e19d8aed741548a04 Mon Sep 17 00:00:00 2001 From: Levi Morrison Date: Thu, 14 May 2026 08:19:16 -0600 Subject: [PATCH 7/7] Move blank line to after #endif per style nitpick Co-Authored-By: Claude Sonnet 4.6 --- Zend/zend_vm_execute.h | 2 +- Zend/zend_vm_gen.php | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Zend/zend_vm_execute.h b/Zend/zend_vm_execute.h index 1649b4ce3970..de92c7c33d5a 100644 --- a/Zend/zend_vm_execute.h +++ b/Zend/zend_vm_execute.h @@ -331,8 +331,8 @@ static zend_op hybrid_halt_op; static zend_vm_opcode_handler_func_t const * zend_opcode_handler_funcs; #endif -#if ZEND_VM_KIND == ZEND_VM_KIND_TAILCALL +#if ZEND_VM_KIND == ZEND_VM_KIND_TAILCALL static const zend_op call_interrupt_op; #endif diff --git a/Zend/zend_vm_gen.php b/Zend/zend_vm_gen.php index 25d32ac81a94..6331ffd3fc56 100755 --- a/Zend/zend_vm_gen.php +++ b/Zend/zend_vm_gen.php @@ -1908,8 +1908,8 @@ function gen_executor($f, $skl, $spec, $kind, $executor_name, $initializer_name) if ($kind == ZEND_VM_KIND_HYBRID || $kind == ZEND_VM_KIND_CALL) { out($f,"#if ZEND_VM_KIND == ZEND_VM_KIND_HYBRID || ZEND_VM_KIND == ZEND_VM_KIND_TAILCALL\n\n"); out($f,"static zend_vm_opcode_handler_func_t const * zend_opcode_handler_funcs;\n"); - out($f,"#endif\n"); - out($f,"#if ZEND_VM_KIND == ZEND_VM_KIND_TAILCALL\n\n"); + out($f,"#endif\n\n"); + out($f,"#if ZEND_VM_KIND == ZEND_VM_KIND_TAILCALL\n"); out($f,"static const zend_op call_interrupt_op;\n"); out($f,"#endif\n\n"); }