From 05d8e537b51fd03263a14c7e65c4ca39982b761e Mon Sep 17 00:00:00 2001 From: Arnaud Le Blanc Date: Wed, 29 Jul 2026 11:36:20 +0200 Subject: [PATCH 1/3] Make opcache.jit_max_side_traces PHP_INI_ALL for reliable testing --- ext/opcache/zend_accelerator_module.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ext/opcache/zend_accelerator_module.c b/ext/opcache/zend_accelerator_module.c index ffa09aaf9e67..64369db1dbc1 100644 --- a/ext/opcache/zend_accelerator_module.c +++ b/ext/opcache/zend_accelerator_module.c @@ -334,7 +334,7 @@ ZEND_INI_BEGIN() STD_PHP_INI_ENTRY("opcache.jit_bisect_limit" , "0", PHP_INI_ALL, OnUpdateLong, bisect_limit, zend_jit_globals, jit_globals) STD_PHP_INI_ENTRY("opcache.jit_prof_threshold" , "0.005", PHP_INI_ALL, OnUpdateReal, prof_threshold, zend_jit_globals, jit_globals) STD_PHP_INI_ENTRY("opcache.jit_max_root_traces" , "1024", PHP_INI_SYSTEM, OnUpdateLong, max_root_traces, zend_jit_globals, jit_globals) - STD_PHP_INI_ENTRY("opcache.jit_max_side_traces" , "128", PHP_INI_SYSTEM, OnUpdateLong, max_side_traces, zend_jit_globals, jit_globals) + STD_PHP_INI_ENTRY("opcache.jit_max_side_traces" , "128", PHP_INI_ALL, OnUpdateLong, max_side_traces, zend_jit_globals, jit_globals) STD_PHP_INI_ENTRY("opcache.jit_max_exit_counters" , "8192", PHP_INI_SYSTEM, OnUpdateLong, max_exit_counters, zend_jit_globals, jit_globals) STD_PHP_INI_ENTRY("opcache.jit_hot_loop" , "64", PHP_INI_SYSTEM, OnUpdateCounter, hot_loop, zend_jit_globals, jit_globals) STD_PHP_INI_ENTRY("opcache.jit_hot_func" , "127", PHP_INI_SYSTEM, OnUpdateCounter, hot_func, zend_jit_globals, jit_globals) From 59964869a60b73a51ee0febded393e0901bd356f Mon Sep 17 00:00:00 2001 From: Arnaud Le Blanc Date: Wed, 29 Jul 2026 11:41:54 +0200 Subject: [PATCH 2/3] Preserve parent regs in zend_jit_deoptimizer_start() --- ext/opcache/jit/zend_jit_ir.c | 28 +++++++++-- ext/opcache/jit/zend_jit_trace.c | 2 +- ext/opcache/tests/jit/gh22915.phpt | 78 ++++++++++++++++++++++++++++++ 3 files changed, 102 insertions(+), 6 deletions(-) create mode 100644 ext/opcache/tests/jit/gh22915.phpt diff --git a/ext/opcache/jit/zend_jit_ir.c b/ext/opcache/jit/zend_jit_ir.c index 2cc6a01c4107..94792215aead 100644 --- a/ext/opcache/jit/zend_jit_ir.c +++ b/ext/opcache/jit/zend_jit_ir.c @@ -338,6 +338,11 @@ static int zend_jit_assign_to_variable(zend_jit_ctx *jit, zend_jit_addr ref_addr, bool check_exception); +static void zend_jit_preserve_parent_regs(zend_jit_ctx *jit, + zend_ssa *ssa, + zend_jit_trace_info *parent, + uint32_t exit_num); + typedef struct _zend_jit_stub { const char *name; int (*stub)(zend_jit_ctx *jit); @@ -16990,6 +16995,7 @@ static int zend_jit_trace_handler(zend_jit_ctx *jit, const zend_op_array *op_arr static int zend_jit_deoptimizer_start(zend_jit_ctx *jit, zend_string *name, uint32_t trace_num, + zend_jit_trace_info *parent, uint32_t exit_num) { zend_jit_init_ctx(jit, (zend_jit_vm_kind == ZEND_VM_KIND_CALL) ? 0 : IR_START_BR_TARGET); @@ -17002,6 +17008,8 @@ static int zend_jit_deoptimizer_start(zend_jit_ctx *jit, jit->ctx.flags |= IR_SKIP_PROLOGUE; + zend_jit_preserve_parent_regs(jit, NULL, parent, exit_num); + return 1; } @@ -17034,6 +17042,21 @@ static int zend_jit_trace_start(zend_jit_ctx *jit, jit->ctx.flags |= IR_SKIP_PROLOGUE; } + zend_jit_preserve_parent_regs(jit, ssa, parent, exit_num); + + ir_STORE(jit_EG(jit_trace_num), ir_CONST_U32(trace_num)); + + return 1; +} + +static void zend_jit_preserve_parent_regs(zend_jit_ctx *jit, + zend_ssa *ssa, + zend_jit_trace_info *parent, + uint32_t exit_num) +{ + /* Emit early RLOADs of registers used for deoptimization to prevent + * clobbering. zend_jit_deopt_rload() will reference these. */ + if (parent) { int i; int parent_vars_count = parent->exit_info[exit_num].stack_size; @@ -17041,7 +17064,6 @@ static int zend_jit_trace_start(zend_jit_ctx *jit, parent->stack_map + parent->exit_info[exit_num].stack_offset; - /* prevent clobbering of registers used for deoptimization */ for (i = 0; i < parent_vars_count; i++) { if (STACK_FLAGS(parent_stack, i) != ZREG_CONST && STACK_REG(parent_stack, i) != ZREG_NONE) { @@ -17085,10 +17107,6 @@ static int zend_jit_trace_start(zend_jit_ctx *jit, ir_RLOAD_A(parent->exit_info[exit_num].poly_this.reg); } } - - ir_STORE(jit_EG(jit_trace_num), ir_CONST_U32(trace_num)); - - return 1; } static int zend_jit_trace_begin_loop(zend_jit_ctx *jit) diff --git a/ext/opcache/jit/zend_jit_trace.c b/ext/opcache/jit/zend_jit_trace.c index da97d102f202..225257ecd6a4 100644 --- a/ext/opcache/jit/zend_jit_trace.c +++ b/ext/opcache/jit/zend_jit_trace.c @@ -7364,7 +7364,7 @@ static const void *zend_jit_trace_exit_to_vm(uint32_t trace_num, uint32_t exit_n name = zend_jit_trace_escape_name(trace_num, exit_num); - if (!zend_jit_deoptimizer_start(&ctx, name, trace_num, exit_num)) { + if (!zend_jit_deoptimizer_start(&ctx, name, trace_num, &zend_jit_traces[trace_num], exit_num)) { zend_string_release(name); return NULL; } diff --git a/ext/opcache/tests/jit/gh22915.phpt b/ext/opcache/tests/jit/gh22915.phpt new file mode 100644 index 000000000000..5ad859773f08 --- /dev/null +++ b/ext/opcache/tests/jit/gh22915.phpt @@ -0,0 +1,78 @@ +--TEST-- +GH-22915: compiled exit clobbers registers before saving +--ENV-- +F=iter +--FILE-- +values = $values; + } + + public function rewind(): void {} + + public function valid(): bool { + return $this->position === 0; + } + + public function current(): mixed { + if (!isset($this->values[$this->position])) { + throw new Exception(); + } + + return $this->values[$this->position]; + } + + public function key(): mixed { + return $this->position; + } + + public function next(): void { + $this->position++; + } +} + +function iter(It $it) { + foreach ($it as $value) { + var_dump($value); + if (!$value instanceof stdClass) { + continue; + } + } +} + +echo "# First run\n"; +for ($i = 0; $i < 5; $i++) { + getenv('F')(new It([getenv('F')])); // non-immutable, packed array +} + +// Next side-exit should deoptimize +if (ini_set('opcache.jit_max_side_traces', '0') === false) { + die("Failed setting opcache.jit_max_side_traces"); +} +if (ini_set('opcache.jit_blacklist_side_trace', '0') === false) { + die("Failed setting opcache.jit_blacklist_side_trace"); +} + +echo "# Second run\n"; +for ($i = 0; $i < 5; $i++) { + getenv('F')(new It([getenv('F'), 'map' => true])); // non-immutable, map, triggers exit +} + +?> +--EXPECT-- +# First run +string(4) "iter" +string(4) "iter" +string(4) "iter" +string(4) "iter" +string(4) "iter" +# Second run +string(4) "iter" +string(4) "iter" +string(4) "iter" +string(4) "iter" +string(4) "iter" From 5e9816ea561cdfc69f09eacc645c2df67401527b Mon Sep 17 00:00:00 2001 From: Arnaud Le Blanc Date: Wed, 29 Jul 2026 11:53:03 +0200 Subject: [PATCH 3/3] ci