diff --git a/ext/opcache/jit/zend_jit_vm_helpers.c b/ext/opcache/jit/zend_jit_vm_helpers.c index 8b85324ef897..bb3f396acff1 100644 --- a/ext/opcache/jit/zend_jit_vm_helpers.c +++ b/ext/opcache/jit/zend_jit_vm_helpers.c @@ -25,6 +25,7 @@ #include "Zend/zend_API.h" #include +#include "zend_shared_alloc.h" #include "Optimizer/zend_func_info.h" #include "Optimizer/zend_call_graph.h" #include "zend_jit.h" @@ -543,10 +544,13 @@ static int zend_jit_trace_record_fake_init_call_ex(zend_execute_data *call, zend func = (zend_function*)jit_extension->op_array; } } - if (is_megamorphic == ZEND_JIT_EXIT_POLYMORPHISM - /* TODO: use more accurate check ??? */ - && ((ZEND_CALL_INFO(call) & ZEND_CALL_DYNAMIC) - || func->common.scope)) { + if ((is_megamorphic == ZEND_JIT_EXIT_POLYMORPHISM + /* TODO: use more accurate check ??? */ + && ((ZEND_CALL_INFO(call) & ZEND_CALL_DYNAMIC) + || func->common.scope)) + || (func->type == ZEND_USER_FUNCTION + && !zend_accel_in_shm(func->op_array.opcodes))) { + /* See zend_jit_trace_execute() about not persistent functions */ func = NULL; ZEND_ADD_CALL_FLAG(call, ZEND_CALL_MEGAMORPHIC); } @@ -1175,6 +1179,14 @@ zend_jit_trace_stop ZEND_FASTCALL zend_jit_trace_execute(zend_execute_data *ex, || is_megamorphic == ZEND_JIT_EXIT_CLOSURE_CALL) && trace_buffer[1].opline == opline - 1) { func = NULL; + } else if (func->type == ZEND_USER_FUNCTION + && !zend_accel_in_shm(func->op_array.opcodes)) { + /* The function is not persistent (e.g. its script is not cached because SHM + * is full or the file is blacklisted). In a later request another function may + * get the same op_array.opcodes address, so the guard that compares it + * can't tell the functions apart, and the trace would use the frame size + * and the arguments info of a wrong function. Don't specialize the call. */ + func = NULL; } if (!func) { ZEND_ADD_CALL_FLAG(EX(call), ZEND_CALL_MEGAMORPHIC); diff --git a/ext/opcache/tests/jit/trace_not_persistent_callee.phpt b/ext/opcache/tests/jit/trace_not_persistent_callee.phpt new file mode 100644 index 000000000000..321327f4f5ae --- /dev/null +++ b/ext/opcache/tests/jit/trace_not_persistent_callee.phpt @@ -0,0 +1,93 @@ +--TEST-- +Tracing JIT must not specialize calls to functions that are not persistent +--DESCRIPTION-- +X::run() comes from a blacklisted (never cached) file, so its op_array and opcodes are allocated +per request. Both versions of the file have the same allocation pattern, so after the first +request run() gets the same opcodes address in every request. A trace of work() recorded with +version A must not be used for version B (more CVs, bigger call frame). +--EXTENSIONS-- +opcache +--SKIPIF-- + +--INI-- +opcache.enable=1 +opcache.enable_cli=1 +opcache.jit=disable +opcache.jit_buffer_size=16M +--CONFLICTS-- +server +--FILE-- +run($i); + } + return $s; +} +function names(): array { + return compact(' . implode(', ', $names) . '); +} +'); + +/* Same number of opcodes; version A uses 17 CVs, version B 26 CVs */ +function x_version(int $cvs): string { + $code = " +--CLEAN-- + +--EXPECT-- +a 478 +a 13631167 +b 13478167 +a 13631167 +b 13478167