diff --git a/UPGRADING.INTERNALS b/UPGRADING.INTERNALS index 448f220ff659a..388ad898fd01b 100644 --- a/UPGRADING.INTERNALS +++ b/UPGRADING.INTERNALS @@ -356,6 +356,11 @@ PHP 8.4 INTERNALS UPGRADE NOTES 4. OpCode changes ======================== +* DO_ICALL, DO_FCALL, and DO_FCALL_BY_NAME now call zend_interrupt_function + while the internal frame is still on the stack. This means interrupt handlers + will now see the internal call. If your interrupt handler does something like + switching EG(current_execute_data), it should not do so if an internal func + is on top. * New FRAMELESS_ICALL_[0,3] opcodes for faster internal function calls have been added. These opcodes don't create a stack frame, but pass arguments via opcode operands. They only work for functions that are known at compile-time, and diff --git a/Zend/tests/fibers/signal-async.phpt b/Zend/tests/fibers/signal-async.phpt index 2487f874d30dc..9543e8e079d1f 100644 --- a/Zend/tests/fibers/signal-async.phpt +++ b/Zend/tests/fibers/signal-async.phpt @@ -17,7 +17,7 @@ pcntl_signal(SIGUSR1, function (): void { $fiber = new Fiber(function (): void { echo "Fiber start\n"; posix_kill(posix_getpid(), SIGUSR1); - time_nanosleep(1); + time_nanosleep(1, 0); echo "Fiber end\n"; }); @@ -30,8 +30,9 @@ Fiber start Fatal error: Uncaught FiberError: Cannot switch fibers in current execution context in %ssignal-async.php:%d Stack trace: #0 %ssignal-async.php(%d): Fiber::suspend() -#1 %ssignal-async.php(%d): {closure:%s:%d}(%d, Array) -#2 [internal function]: {closure:%s:%d}() -#3 %ssignal-async.php(%d): Fiber->start() -#4 {main} +#1 [internal function]: {closure:%s:%d}(%d, Array) +#2 %ssignal-async.php(%d): posix_kill(%d, %d) +#3 [internal function]: {closure:%s:%d}() +#4 %ssignal-async.php(%d): Fiber->start() +#5 {main} thrown in %ssignal-async.php on line %d diff --git a/Zend/zend.h b/Zend/zend.h index 4fe0703d42f69..1506b868e1690 100644 --- a/Zend/zend.h +++ b/Zend/zend.h @@ -341,7 +341,22 @@ extern ZEND_API size_t (*zend_printf)(const char *format, ...) ZEND_ATTRIBUTE_PT extern ZEND_API zend_write_func_t zend_write; extern ZEND_API FILE *(*zend_fopen)(zend_string *filename, zend_string **opened_path); extern ZEND_API void (*zend_ticks_function)(int ticks); + +/* Called by the VM in certain places like at the loop header, user function + * entry, and after internal function calls, if EG(vm_interrupt) has been set. + * + * If this is used to switch the EG(current_execute_data), such as implementing + * a coroutine scheduler, then it needs to check the top frame to see if it's + * an internal function. If an internal function is on top, then the frame + * shouldn't be switched away. + * + * Prior to PHP 8.0, this check was not necessary. In PHP 8.0, + * zend_call_function started calling zend_interrupt_function, and in 8.4 the + * DO_*CALL* opcodes started calling the zend_interrupt_function while the + * internal frame is still on top. + */ extern ZEND_API void (*zend_interrupt_function)(zend_execute_data *execute_data); + extern ZEND_API void (*zend_error_cb)(int type, zend_string *error_filename, const uint32_t error_lineno, zend_string *message); extern ZEND_API void (*zend_on_timeout)(int seconds); extern ZEND_API zend_result (*zend_stream_open_function)(zend_file_handle *handle); diff --git a/Zend/zend_execute.c b/Zend/zend_execute.c index 602bb3b0e79f5..785816c2d30de 100644 --- a/Zend/zend_execute.c +++ b/Zend/zend_execute.c @@ -4054,6 +4054,16 @@ ZEND_API void ZEND_FASTCALL zend_free_compiled_variables(zend_execute_data *exec } /* }}} */ +ZEND_API ZEND_COLD void ZEND_FASTCALL zend_fcall_interrupt(zend_execute_data *call) +{ + zend_atomic_bool_store_ex(&EG(vm_interrupt), false); + if (zend_atomic_bool_load_ex(&EG(timed_out))) { + zend_timeout(); + } else if (zend_interrupt_function) { + zend_interrupt_function(call); + } +} + #define ZEND_VM_INTERRUPT_CHECK() do { \ if (UNEXPECTED(zend_atomic_bool_load_ex(&EG(vm_interrupt)))) { \ ZEND_VM_INTERRUPT(); \ @@ -4066,6 +4076,12 @@ ZEND_API void ZEND_FASTCALL zend_free_compiled_variables(zend_execute_data *exec } \ } while (0) +#define ZEND_VM_FCALL_INTERRUPT_CHECK(call) do { \ + if (UNEXPECTED(zend_atomic_bool_load_ex(&EG(vm_interrupt)))) { \ + zend_fcall_interrupt(call); \ + } \ + } while (0) + /* * Stack Frame Layout (the whole stack frame is allocated at once) * ================== @@ -5493,9 +5509,12 @@ static zend_always_inline zend_execute_data *_zend_vm_stack_push_call_frame(uint CHECK_SYMBOL_TABLES() \ OPLINE = new_op -#define ZEND_VM_SET_OPCODE(new_op) \ +#define ZEND_VM_SET_OPCODE_NO_INTERRUPT(new_op) \ CHECK_SYMBOL_TABLES() \ - OPLINE = new_op; \ + OPLINE = new_op + +#define ZEND_VM_SET_OPCODE(new_op) \ + ZEND_VM_SET_OPCODE_NO_INTERRUPT(new_op); \ ZEND_VM_INTERRUPT_CHECK() #define ZEND_VM_SET_RELATIVE_OPCODE(opline, offset) \ diff --git a/Zend/zend_execute.h b/Zend/zend_execute.h index fe854f305150c..c6b52b59e0a6c 100644 --- a/Zend/zend_execute.h +++ b/Zend/zend_execute.h @@ -539,6 +539,11 @@ ZEND_COLD void zend_magic_get_property_type_inconsistency_error(const zend_prope ZEND_COLD void zend_match_unhandled_error(const zval *value); +/* Call this to handle the timeout or the interrupt function. It will set + * EG(vm_interrupt) to false. + */ +ZEND_API ZEND_COLD void ZEND_FASTCALL zend_fcall_interrupt(zend_execute_data *call); + static zend_always_inline void *zend_get_bad_ptr(void) { ZEND_UNREACHABLE(); diff --git a/Zend/zend_system_id.c b/Zend/zend_system_id.c index 37bed895a7b96..2c3ebab0f4807 100644 --- a/Zend/zend_system_id.c +++ b/Zend/zend_system_id.c @@ -55,10 +55,11 @@ void zend_startup_system_id(void) zend_system_id[0] = '\0'; } -#define ZEND_HOOK_AST_PROCESS (1 << 0) -#define ZEND_HOOK_COMPILE_FILE (1 << 1) -#define ZEND_HOOK_EXECUTE_EX (1 << 2) -#define ZEND_HOOK_EXECUTE_INTERNAL (1 << 3) +#define ZEND_HOOK_AST_PROCESS (1 << 0) +#define ZEND_HOOK_COMPILE_FILE (1 << 1) +#define ZEND_HOOK_EXECUTE_EX (1 << 2) +#define ZEND_HOOK_EXECUTE_INTERNAL (1 << 3) +#define ZEND_HOOK_INTERRUPT_FUNCTION (1 << 4) void zend_finalize_system_id(void) { @@ -77,6 +78,9 @@ void zend_finalize_system_id(void) if (zend_execute_internal) { hooks |= ZEND_HOOK_EXECUTE_INTERNAL; } + if (zend_interrupt_function) { + hooks |= ZEND_HOOK_INTERRUPT_FUNCTION; + } PHP_MD5Update(&context, &hooks, sizeof hooks); for (int16_t i = 0; i < 256; i++) { diff --git a/Zend/zend_vm_def.h b/Zend/zend_vm_def.h index 55fdb7d46582b..92cec7c059c7f 100644 --- a/Zend/zend_vm_def.h +++ b/Zend/zend_vm_def.h @@ -4074,6 +4074,7 @@ ZEND_VM_HOT_HANDLER(129, ZEND_DO_ICALL, ANY, ANY, SPEC(RETVAL,OBSERVER)) } #endif ZEND_OBSERVER_FCALL_END(call, EG(exception) ? NULL : ret); + ZEND_VM_FCALL_INTERRUPT_CHECK(call); EG(current_execute_data) = execute_data; zend_vm_stack_free_args(call); @@ -4097,7 +4098,7 @@ ZEND_VM_HOT_HANDLER(129, ZEND_DO_ICALL, ANY, ANY, SPEC(RETVAL,OBSERVER)) HANDLE_EXCEPTION(); } - ZEND_VM_SET_OPCODE(opline + 1); + ZEND_VM_SET_OPCODE_NO_INTERRUPT(opline + 1); ZEND_VM_CONTINUE(); } @@ -4195,6 +4196,7 @@ ZEND_VM_HOT_HANDLER(131, ZEND_DO_FCALL_BY_NAME, ANY, ANY, SPEC(RETVAL,OBSERVER)) } #endif ZEND_OBSERVER_FCALL_END(call, EG(exception) ? NULL : ret); + ZEND_VM_FCALL_INTERRUPT_CHECK(call); EG(current_execute_data) = execute_data; @@ -4225,7 +4227,7 @@ ZEND_VM_C_LABEL(fcall_by_name_end): zend_rethrow_exception(execute_data); HANDLE_EXCEPTION(); } - ZEND_VM_SET_OPCODE(opline + 1); + ZEND_VM_SET_OPCODE_NO_INTERRUPT(opline + 1); ZEND_VM_CONTINUE(); } @@ -4315,6 +4317,7 @@ ZEND_VM_HOT_HANDLER(60, ZEND_DO_FCALL, ANY, ANY, SPEC(RETVAL,OBSERVER)) } #endif ZEND_OBSERVER_FCALL_END(call, EG(exception) ? NULL : ret); + ZEND_VM_FCALL_INTERRUPT_CHECK(call); EG(current_execute_data) = execute_data; @@ -4343,8 +4346,7 @@ ZEND_VM_C_LABEL(fcall_end): zend_rethrow_exception(execute_data); HANDLE_EXCEPTION(); } - - ZEND_VM_SET_OPCODE(opline + 1); + ZEND_VM_SET_OPCODE_NO_INTERRUPT(opline + 1); ZEND_VM_CONTINUE(); } diff --git a/Zend/zend_vm_execute.h b/Zend/zend_vm_execute.h index 16455b6e0cd58..a7cd17da96b85 100644 --- a/Zend/zend_vm_execute.h +++ b/Zend/zend_vm_execute.h @@ -1299,6 +1299,8 @@ static ZEND_VM_HOT ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_DO_ICALL_SPEC_RETV } #endif + ZEND_VM_FCALL_INTERRUPT_CHECK(call); + EG(current_execute_data) = execute_data; zend_vm_stack_free_args(call); @@ -1321,7 +1323,7 @@ static ZEND_VM_HOT ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_DO_ICALL_SPEC_RETV HANDLE_EXCEPTION(); } - ZEND_VM_SET_OPCODE(opline + 1); + ZEND_VM_SET_OPCODE_NO_INTERRUPT(opline + 1); ZEND_VM_CONTINUE(); } @@ -1361,6 +1363,8 @@ static ZEND_VM_HOT ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_DO_ICALL_SPEC_RETV } #endif + ZEND_VM_FCALL_INTERRUPT_CHECK(call); + EG(current_execute_data) = execute_data; zend_vm_stack_free_args(call); @@ -1383,7 +1387,7 @@ static ZEND_VM_HOT ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_DO_ICALL_SPEC_RETV HANDLE_EXCEPTION(); } - ZEND_VM_SET_OPCODE(opline + 1); + ZEND_VM_SET_OPCODE_NO_INTERRUPT(opline + 1); ZEND_VM_CONTINUE(); } @@ -1424,6 +1428,7 @@ static ZEND_VM_COLD ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_DO_ICALL_SPEC_OBS } #endif zend_observer_fcall_end(call, EG(exception) ? NULL : ret); + ZEND_VM_FCALL_INTERRUPT_CHECK(call); EG(current_execute_data) = execute_data; zend_vm_stack_free_args(call); @@ -1447,7 +1452,7 @@ static ZEND_VM_COLD ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_DO_ICALL_SPEC_OBS HANDLE_EXCEPTION(); } - ZEND_VM_SET_OPCODE(opline + 1); + ZEND_VM_SET_OPCODE_NO_INTERRUPT(opline + 1); ZEND_VM_CONTINUE(); } @@ -1591,6 +1596,8 @@ static ZEND_VM_HOT ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_DO_FCALL_BY_NAME_S } #endif + ZEND_VM_FCALL_INTERRUPT_CHECK(call); + EG(current_execute_data) = execute_data; goto fcall_by_name_end; @@ -1620,7 +1627,7 @@ static ZEND_VM_HOT ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_DO_FCALL_BY_NAME_S zend_rethrow_exception(execute_data); HANDLE_EXCEPTION(); } - ZEND_VM_SET_OPCODE(opline + 1); + ZEND_VM_SET_OPCODE_NO_INTERRUPT(opline + 1); ZEND_VM_CONTINUE(); } @@ -1691,6 +1698,8 @@ static ZEND_VM_HOT ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_DO_FCALL_BY_NAME_S } #endif + ZEND_VM_FCALL_INTERRUPT_CHECK(call); + EG(current_execute_data) = execute_data; goto fcall_by_name_end; @@ -1720,7 +1729,7 @@ static ZEND_VM_HOT ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_DO_FCALL_BY_NAME_S zend_rethrow_exception(execute_data); HANDLE_EXCEPTION(); } - ZEND_VM_SET_OPCODE(opline + 1); + ZEND_VM_SET_OPCODE_NO_INTERRUPT(opline + 1); ZEND_VM_CONTINUE(); } @@ -1793,6 +1802,7 @@ static ZEND_VM_COLD ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_DO_FCALL_BY_NAME_ } #endif zend_observer_fcall_end(call, EG(exception) ? NULL : ret); + ZEND_VM_FCALL_INTERRUPT_CHECK(call); EG(current_execute_data) = execute_data; @@ -1823,7 +1833,7 @@ static ZEND_VM_COLD ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_DO_FCALL_BY_NAME_ zend_rethrow_exception(execute_data); HANDLE_EXCEPTION(); } - ZEND_VM_SET_OPCODE(opline + 1); + ZEND_VM_SET_OPCODE_NO_INTERRUPT(opline + 1); ZEND_VM_CONTINUE(); } @@ -1912,6 +1922,8 @@ static ZEND_VM_HOT ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_DO_FCALL_SPEC_RETV } #endif + ZEND_VM_FCALL_INTERRUPT_CHECK(call); + EG(current_execute_data) = execute_data; goto fcall_end; @@ -1939,8 +1951,7 @@ static ZEND_VM_HOT ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_DO_FCALL_SPEC_RETV zend_rethrow_exception(execute_data); HANDLE_EXCEPTION(); } - - ZEND_VM_SET_OPCODE(opline + 1); + ZEND_VM_SET_OPCODE_NO_INTERRUPT(opline + 1); ZEND_VM_CONTINUE(); } @@ -2029,6 +2040,8 @@ static ZEND_VM_HOT ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_DO_FCALL_SPEC_RETV } #endif + ZEND_VM_FCALL_INTERRUPT_CHECK(call); + EG(current_execute_data) = execute_data; goto fcall_end; @@ -2056,8 +2069,7 @@ static ZEND_VM_HOT ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_DO_FCALL_SPEC_RETV zend_rethrow_exception(execute_data); HANDLE_EXCEPTION(); } - - ZEND_VM_SET_OPCODE(opline + 1); + ZEND_VM_SET_OPCODE_NO_INTERRUPT(opline + 1); ZEND_VM_CONTINUE(); } @@ -2147,6 +2159,7 @@ static ZEND_VM_COLD ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_DO_FCALL_SPEC_OBS } #endif zend_observer_fcall_end(call, EG(exception) ? NULL : ret); + ZEND_VM_FCALL_INTERRUPT_CHECK(call); EG(current_execute_data) = execute_data; @@ -2175,8 +2188,7 @@ static ZEND_VM_COLD ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_DO_FCALL_SPEC_OBS zend_rethrow_exception(execute_data); HANDLE_EXCEPTION(); } - - ZEND_VM_SET_OPCODE(opline + 1); + ZEND_VM_SET_OPCODE_NO_INTERRUPT(opline + 1); ZEND_VM_CONTINUE(); } diff --git a/ext/opcache/jit/zend_jit.c b/ext/opcache/jit/zend_jit.c index 379a5122217d1..a9a020154dc9a 100644 --- a/ext/opcache/jit/zend_jit.c +++ b/ext/opcache/jit/zend_jit.c @@ -1425,9 +1425,7 @@ static int zend_jit(const zend_op_array *op_array, zend_ssa *ssa, const zend_op zend_jit_set_last_valid_opline(&ctx, op_array->opcodes + ssa->cfg.blocks[b].start); } if (ssa->cfg.blocks[b].flags & ZEND_BB_LOOP_HEADER) { - if (!zend_jit_check_timeout(&ctx, op_array->opcodes + ssa->cfg.blocks[b].start, NULL)) { - goto jit_failure; - } + zend_jit_check_timeout(&ctx, op_array->opcodes + ssa->cfg.blocks[b].start, NULL); } if (!ssa->cfg.blocks[b].len) { zend_jit_bb_end(&ctx, b); diff --git a/ext/opcache/jit/zend_jit_ir.c b/ext/opcache/jit/zend_jit_ir.c index bb0caf477e511..37c512ff198fa 100644 --- a/ext/opcache/jit/zend_jit_ir.c +++ b/ext/opcache/jit/zend_jit_ir.c @@ -1857,7 +1857,7 @@ static void jit_OBJ_RELEASE(zend_jit_ctx *jit, ir_ref ref) ir_MERGE_list(end_inputs); } -static int zend_jit_check_timeout(zend_jit_ctx *jit, const zend_op *opline, const void *exit_addr) +static void zend_jit_check_timeout(zend_jit_ctx *jit, const zend_op *opline, const void *exit_addr) { ir_ref ref = ir_LOAD_U8(jit_EG(vm_interrupt)); @@ -1873,7 +1873,6 @@ static int zend_jit_check_timeout(zend_jit_ctx *jit, const zend_op *opline, cons ir_IJMP(jit_STUB_ADDR(jit, jit_stub_interrupt_handler)); ir_IF_FALSE(if_timeout); } - return 1; } /* stubs */ @@ -2451,9 +2450,7 @@ static int zend_jit_trace_exit_stub(zend_jit_ctx *jit) } // check for interrupt (try to avoid this ???) - if (!zend_jit_check_timeout(jit, NULL, NULL)) { - return 0; - } + zend_jit_check_timeout(jit, NULL, NULL); addr = zend_jit_orig_opline_handler(jit); if (GCC_GLOBAL_REGS) { @@ -3078,6 +3075,7 @@ static void zend_jit_setup_disasm(void) REGISTER_HELPER(zend_jit_pre_dec_obj_helper); REGISTER_HELPER(zend_jit_post_dec_obj_helper); REGISTER_HELPER(zend_jit_rope_end); + REGISTER_HELPER(zend_fcall_interrupt); #ifndef ZTS REGISTER_DATA(EG(current_execute_data)); @@ -10199,6 +10197,19 @@ static int zend_jit_do_fcall(zend_jit_ctx *jit, const zend_op *opline, const zen jit_observer_fcall_end(jit, rx, res_ref); } + /* When zend_interrupt_function is set, it gets called while + * the frame is still on top. This is less efficient than + * doing it later once it's popped off. There is code further + * down that handles when there isn't an interrupt function. + */ + if (zend_interrupt_function) { + // JIT: if (EG(vm_interrupt)) zend_fcall_interrupt(execute_data); + ir_ref if_interrupt = ir_IF(ir_LOAD_U8(jit_EG(vm_interrupt))); + ir_IF_TRUE_cold(if_interrupt); + ir_CALL_1(IR_VOID, ir_CONST_FC_FUNC(zend_fcall_interrupt), rx); + ir_MERGE_WITH_EMPTY_FALSE(if_interrupt); + } + // JIT: EG(current_execute_data) = execute_data; ir_STORE(jit_EG(current_execute_data), jit_FP(jit)); @@ -10299,20 +10310,23 @@ static int zend_jit_do_fcall(zend_jit_ctx *jit, const zend_op *opline, const zen ir_GUARD_NOT(ir_LOAD_A(jit_EG_exception(jit)), jit_STUB_ADDR(jit, jit_stub_icall_throw)); - // TODO: Can we avoid checking for interrupts after each call ??? - if (trace && jit->last_valid_opline != opline) { - int32_t exit_point = zend_jit_trace_get_exit_point(opline + 1, ZEND_JIT_EXIT_TO_VM); + /* If there isn't a zend_interrupt_function, the timeout is + * handled here because it's more efficient. + */ + if (!zend_interrupt_function) { + // TODO: Can we avoid checking for interrupts after each call ??? + if (trace && jit->last_valid_opline != opline) { + int32_t exit_point = zend_jit_trace_get_exit_point(opline + 1, ZEND_JIT_EXIT_TO_VM); - exit_addr = zend_jit_trace_get_exit_addr(exit_point); - if (!exit_addr) { - return 0; + exit_addr = zend_jit_trace_get_exit_addr(exit_point); + if (!exit_addr) { + return 0; + } + } else { + exit_addr = NULL; } - } else { - exit_addr = NULL; - } - if (!zend_jit_check_timeout(jit, opline + 1, exit_addr)) { - return 0; + zend_jit_check_timeout(jit, opline + 1, exit_addr); } if ((!trace || !func) && opline->opcode != ZEND_DO_ICALL) { @@ -10347,7 +10361,7 @@ static int zend_jit_constructor(zend_jit_ctx *jit, const zend_op *opline, const } } - /* override predecessors of the next block */ + /* override predecessors of the next block */ ZEND_ASSERT(jit->ssa->cfg.blocks[next_block].predecessors_count == 1); if (!jit->ctx.control) { ZEND_ASSERT(jit->bb_edges[jit->bb_predecessors[next_block]]);