[php-src] master: Merge branch 'PHP-8.5'
Arnaud Le Blanc <[email protected]>
| Newsgroups | gmane.comp.php.cvs.general |
|---|---|
| Message-ID | <[email protected]> |
Author: Arnaud Le Blanc (arnaud-lb)
Date: 2026-08-07T16:17:02+02:00
Commit: https://github.com/php/php-src/commit/4b4fb492471943cdd851a66aadd68593baf3e4d7
Raw diff: https://github.com/php/php-src/commit/4b4fb492471943cdd851a66aadd68593baf3e4d7.diff
Merge branch 'PHP-8.5'
* PHP-8.5:
JIT: Preserve parent regs in zend_jit_deoptimizer_start() (#22916)
Changed paths:
A ext/opcache/tests/jit/gh22915.phpt
M ext/opcache/jit/zend_jit_ir.c
M ext/opcache/jit/zend_jit_trace.c
Diff:
diff --git a/ext/opcache/jit/zend_jit_ir.c b/ext/opcache/jit/zend_jit_ir.c
index 2c904edd4cfc..2bbd7b0e3f4d 100644
--- a/ext/opcache/jit/zend_jit_ir.c
+++ b/ext/opcache/jit/zend_jit_ir.c
@@ -360,6 +360,11 @@ static int zend_jit_assign_to_variable(zend_jit_ctx *jit,
static ir_ref jit_CONST_FUNC(zend_jit_ctx *jit, uintptr_t addr, uint16_t flags);
+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);
@@ -17351,6 +17356,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_VM_KIND == ZEND_VM_KIND_CALL || ZEND_VM_KIND == ZEND_VM_KIND_TAILCALL) ? 0 : IR_START_BR_TARGET);
@@ -17363,6 +17369,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;
}
@@ -17399,6 +17407,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;
@@ -17406,7 +17429,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) {
@@ -17450,10 +17472,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 505427890129..a47ef18db337 100644
--- a/ext/opcache/jit/zend_jit_trace.c
+++ b/ext/opcache/jit/zend_jit_trace.c
@@ -7455,7 +7455,7 @@ static zend_vm_opcode_handler_t zend_jit_trace_exit_to_vm(uint32_t trace_num, ui
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..cea291d311b1
--- /dev/null
+++ b/ext/opcache/tests/jit/gh22915.phpt
@@ -0,0 +1,75 @@
+--TEST--
+GH-22915: compiled exit clobbers registers before saving
+--EXTENSIONS--
+opcache
+--INI--
+opcache.jit_max_side_traces=0
+opcache.jit_blacklist_side_trace=0
+--ENV--
+F=iter
+--FILE--
+<?php
+
+final class It implements Iterator {
+ public readonly array $values;
+ public int $position = 0;
+ public function __construct(array $values) {
+ $this->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
+}
+
+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"