Skip to content

Commit 6435bb5

Browse files
morrisonlevibwoebi
andauthored
Interrupt while internal frame is on the stack (#14627)
* Check VM interrupt while internal frame is on top * Use tab instead of spaces * fix frame used in interrupt and refactor * remove unused failures for zend_jit_check_timeout * Fix JIT support Co-authored-by: Bob Weinand <[email protected]> * Fix the missing store to vm_interrupt * Rename new functions * Special case zend_interrupt_function in JIT code * refactor to use ZEND_VM_SET_OPCODE_NO_INTERRUPT * Split atomic exchange into load + store It is difficult to determine performance of atomics sometimes. In this case, the separate load+store is still correct, and a load does not cause a modification, and might be faster for some platforms than an exchange. A load+store is slower than an exchange, but we're fine trading the penalty to the slow path and keeping the happy path faster. --------- Co-authored-by: Bob Weinand <[email protected]>
1 parent 816aea7 commit 6435bb5

10 files changed

+122
-47
lines changed

UPGRADING.INTERNALS

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -377,6 +377,11 @@ PHP 8.4 INTERNALS UPGRADE NOTES
377377
4. OpCode changes
378378
========================
379379

380+
* DO_ICALL, DO_FCALL, and DO_FCALL_BY_NAME now call zend_interrupt_function
381+
while the internal frame is still on the stack. This means interrupt handlers
382+
will now see the internal call. If your interrupt handler does something like
383+
switching EG(current_execute_data), it should not do so if an internal func
384+
is on top.
380385
* New FRAMELESS_ICALL_[0,3] opcodes for faster internal function calls have been
381386
added. These opcodes don't create a stack frame, but pass arguments via opcode
382387
operands. They only work for functions that are known at compile-time, and

Zend/tests/fibers/signal-async.phpt

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ pcntl_signal(SIGUSR1, function (): void {
1717
$fiber = new Fiber(function (): void {
1818
echo "Fiber start\n";
1919
posix_kill(posix_getpid(), SIGUSR1);
20-
time_nanosleep(1);
20+
time_nanosleep(1, 0);
2121
echo "Fiber end\n";
2222
});
2323

@@ -30,8 +30,9 @@ Fiber start
3030
Fatal error: Uncaught FiberError: Cannot switch fibers in current execution context in %ssignal-async.php:%d
3131
Stack trace:
3232
#0 %ssignal-async.php(%d): Fiber::suspend()
33-
#1 %ssignal-async.php(%d): {closure:%s:%d}(%d, Array)
34-
#2 [internal function]: {closure:%s:%d}()
35-
#3 %ssignal-async.php(%d): Fiber->start()
36-
#4 {main}
33+
#1 [internal function]: {closure:%s:%d}(%d, Array)
34+
#2 %ssignal-async.php(%d): posix_kill(%d, %d)
35+
#3 [internal function]: {closure:%s:%d}()
36+
#4 %ssignal-async.php(%d): Fiber->start()
37+
#5 {main}
3738
thrown in %ssignal-async.php on line %d

Zend/zend.h

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -341,7 +341,22 @@ extern ZEND_API size_t (*zend_printf)(const char *format, ...) ZEND_ATTRIBUTE_PT
341341
extern ZEND_API zend_write_func_t zend_write;
342342
extern ZEND_API FILE *(*zend_fopen)(zend_string *filename, zend_string **opened_path);
343343
extern ZEND_API void (*zend_ticks_function)(int ticks);
344+
345+
/* Called by the VM in certain places like at the loop header, user function
346+
* entry, and after internal function calls, if EG(vm_interrupt) has been set.
347+
*
348+
* If this is used to switch the EG(current_execute_data), such as implementing
349+
* a coroutine scheduler, then it needs to check the top frame to see if it's
350+
* an internal function. If an internal function is on top, then the frame
351+
* shouldn't be switched away.
352+
*
353+
* Prior to PHP 8.0, this check was not necessary. In PHP 8.0,
354+
* zend_call_function started calling zend_interrupt_function, and in 8.4 the
355+
* DO_*CALL* opcodes started calling the zend_interrupt_function while the
356+
* internal frame is still on top.
357+
*/
344358
extern ZEND_API void (*zend_interrupt_function)(zend_execute_data *execute_data);
359+
345360
extern ZEND_API void (*zend_error_cb)(int type, zend_string *error_filename, const uint32_t error_lineno, zend_string *message);
346361
extern ZEND_API void (*zend_on_timeout)(int seconds);
347362
extern ZEND_API zend_result (*zend_stream_open_function)(zend_file_handle *handle);

Zend/zend_execute.c

Lines changed: 21 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4095,6 +4095,16 @@ ZEND_API void ZEND_FASTCALL zend_free_compiled_variables(zend_execute_data *exec
40954095
}
40964096
/* }}} */
40974097

4098+
ZEND_API ZEND_COLD void ZEND_FASTCALL zend_fcall_interrupt(zend_execute_data *call)
4099+
{
4100+
zend_atomic_bool_store_ex(&EG(vm_interrupt), false);
4101+
if (zend_atomic_bool_load_ex(&EG(timed_out))) {
4102+
zend_timeout();
4103+
} else if (zend_interrupt_function) {
4104+
zend_interrupt_function(call);
4105+
}
4106+
}
4107+
40984108
#define ZEND_VM_INTERRUPT_CHECK() do { \
40994109
if (UNEXPECTED(zend_atomic_bool_load_ex(&EG(vm_interrupt)))) { \
41004110
ZEND_VM_INTERRUPT(); \
@@ -4107,6 +4117,12 @@ ZEND_API void ZEND_FASTCALL zend_free_compiled_variables(zend_execute_data *exec
41074117
} \
41084118
} while (0)
41094119

4120+
#define ZEND_VM_FCALL_INTERRUPT_CHECK(call) do { \
4121+
if (UNEXPECTED(zend_atomic_bool_load_ex(&EG(vm_interrupt)))) { \
4122+
zend_fcall_interrupt(call); \
4123+
} \
4124+
} while (0)
4125+
41104126
/*
41114127
* Stack Frame Layout (the whole stack frame is allocated at once)
41124128
* ==================
@@ -5541,9 +5557,12 @@ static zend_always_inline zend_execute_data *_zend_vm_stack_push_call_frame(uint
55415557
CHECK_SYMBOL_TABLES() \
55425558
OPLINE = new_op
55435559

5544-
#define ZEND_VM_SET_OPCODE(new_op) \
5560+
#define ZEND_VM_SET_OPCODE_NO_INTERRUPT(new_op) \
55455561
CHECK_SYMBOL_TABLES() \
5546-
OPLINE = new_op; \
5562+
OPLINE = new_op
5563+
5564+
#define ZEND_VM_SET_OPCODE(new_op) \
5565+
ZEND_VM_SET_OPCODE_NO_INTERRUPT(new_op); \
55475566
ZEND_VM_INTERRUPT_CHECK()
55485567

55495568
#define ZEND_VM_SET_RELATIVE_OPCODE(opline, offset) \

Zend/zend_execute.h

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -542,6 +542,11 @@ ZEND_COLD void zend_magic_get_property_type_inconsistency_error(const zend_prope
542542

543543
ZEND_COLD void zend_match_unhandled_error(const zval *value);
544544

545+
/* Call this to handle the timeout or the interrupt function. It will set
546+
* EG(vm_interrupt) to false.
547+
*/
548+
ZEND_API ZEND_COLD void ZEND_FASTCALL zend_fcall_interrupt(zend_execute_data *call);
549+
545550
static zend_always_inline void *zend_get_bad_ptr(void)
546551
{
547552
ZEND_UNREACHABLE();

Zend/zend_system_id.c

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -55,10 +55,11 @@ void zend_startup_system_id(void)
5555
zend_system_id[0] = '\0';
5656
}
5757

58-
#define ZEND_HOOK_AST_PROCESS (1 << 0)
59-
#define ZEND_HOOK_COMPILE_FILE (1 << 1)
60-
#define ZEND_HOOK_EXECUTE_EX (1 << 2)
61-
#define ZEND_HOOK_EXECUTE_INTERNAL (1 << 3)
58+
#define ZEND_HOOK_AST_PROCESS (1 << 0)
59+
#define ZEND_HOOK_COMPILE_FILE (1 << 1)
60+
#define ZEND_HOOK_EXECUTE_EX (1 << 2)
61+
#define ZEND_HOOK_EXECUTE_INTERNAL (1 << 3)
62+
#define ZEND_HOOK_INTERRUPT_FUNCTION (1 << 4)
6263

6364
void zend_finalize_system_id(void)
6465
{
@@ -77,6 +78,9 @@ void zend_finalize_system_id(void)
7778
if (zend_execute_internal) {
7879
hooks |= ZEND_HOOK_EXECUTE_INTERNAL;
7980
}
81+
if (zend_interrupt_function) {
82+
hooks |= ZEND_HOOK_INTERRUPT_FUNCTION;
83+
}
8084
PHP_MD5Update(&context, &hooks, sizeof hooks);
8185

8286
for (int16_t i = 0; i < 256; i++) {

Zend/zend_vm_def.h

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4084,6 +4084,7 @@ ZEND_VM_HOT_HANDLER(129, ZEND_DO_ICALL, ANY, ANY, SPEC(RETVAL,OBSERVER))
40844084
}
40854085
#endif
40864086
ZEND_OBSERVER_FCALL_END(call, EG(exception) ? NULL : ret);
4087+
ZEND_VM_FCALL_INTERRUPT_CHECK(call);
40874088

40884089
EG(current_execute_data) = execute_data;
40894090
zend_vm_stack_free_args(call);
@@ -4107,7 +4108,7 @@ ZEND_VM_HOT_HANDLER(129, ZEND_DO_ICALL, ANY, ANY, SPEC(RETVAL,OBSERVER))
41074108
HANDLE_EXCEPTION();
41084109
}
41094110

4110-
ZEND_VM_SET_OPCODE(opline + 1);
4111+
ZEND_VM_SET_OPCODE_NO_INTERRUPT(opline + 1);
41114112
ZEND_VM_CONTINUE();
41124113
}
41134114

@@ -4205,6 +4206,7 @@ ZEND_VM_HOT_HANDLER(131, ZEND_DO_FCALL_BY_NAME, ANY, ANY, SPEC(RETVAL,OBSERVER))
42054206
}
42064207
#endif
42074208
ZEND_OBSERVER_FCALL_END(call, EG(exception) ? NULL : ret);
4209+
ZEND_VM_FCALL_INTERRUPT_CHECK(call);
42084210

42094211
EG(current_execute_data) = execute_data;
42104212

@@ -4235,7 +4237,7 @@ ZEND_VM_C_LABEL(fcall_by_name_end):
42354237
zend_rethrow_exception(execute_data);
42364238
HANDLE_EXCEPTION();
42374239
}
4238-
ZEND_VM_SET_OPCODE(opline + 1);
4240+
ZEND_VM_SET_OPCODE_NO_INTERRUPT(opline + 1);
42394241
ZEND_VM_CONTINUE();
42404242
}
42414243

@@ -4325,6 +4327,7 @@ ZEND_VM_HOT_HANDLER(60, ZEND_DO_FCALL, ANY, ANY, SPEC(RETVAL,OBSERVER))
43254327
}
43264328
#endif
43274329
ZEND_OBSERVER_FCALL_END(call, EG(exception) ? NULL : ret);
4330+
ZEND_VM_FCALL_INTERRUPT_CHECK(call);
43284331

43294332
EG(current_execute_data) = execute_data;
43304333

@@ -4353,8 +4356,7 @@ ZEND_VM_C_LABEL(fcall_end):
43534356
zend_rethrow_exception(execute_data);
43544357
HANDLE_EXCEPTION();
43554358
}
4356-
4357-
ZEND_VM_SET_OPCODE(opline + 1);
4359+
ZEND_VM_SET_OPCODE_NO_INTERRUPT(opline + 1);
43584360
ZEND_VM_CONTINUE();
43594361
}
43604362

Zend/zend_vm_execute.h

Lines changed: 24 additions & 12 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

ext/opcache/jit/zend_jit.c

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1425,9 +1425,7 @@ static int zend_jit(const zend_op_array *op_array, zend_ssa *ssa, const zend_op
14251425
zend_jit_set_last_valid_opline(&ctx, op_array->opcodes + ssa->cfg.blocks[b].start);
14261426
}
14271427
if (ssa->cfg.blocks[b].flags & ZEND_BB_LOOP_HEADER) {
1428-
if (!zend_jit_check_timeout(&ctx, op_array->opcodes + ssa->cfg.blocks[b].start, NULL)) {
1429-
goto jit_failure;
1430-
}
1428+
zend_jit_check_timeout(&ctx, op_array->opcodes + ssa->cfg.blocks[b].start, NULL);
14311429
}
14321430
if (!ssa->cfg.blocks[b].len) {
14331431
zend_jit_bb_end(&ctx, b);

0 commit comments

Comments
 (0)