[PHP-CVS] [php-src] PHP-8.4: JIT: Preserve parent regs in zend_jit_deoptimizer_start() (#22916)

[email protected] (Arnaud Le Blanc via GitHub)
Newsgroups php.cvs
Message-ID <[email protected]>
Author: Arnaud Le Blanc (arnaud-lb)
Committer: GitHub (web-flow)
Pusher: arnaud-lb
Date: 2026-08-07T16:15:21+02:00

Commit: https://github.com/php/php-src/commit/905afb536542079157a50048a85871890f746f5d
Raw diff: https://github.com/php/php-src/commit/905afb536542079157a50048a85871890f746f5d.diff

JIT: Preserve parent regs in zend_jit_deoptimizer_start() (#22916)

Fixes GH-22915

Changed paths:
  A  ext/opcache/tests/jit/gh22915.phpt
  M  NEWS
  M  ext/opcache/jit/zend_jit_ir.c
  M  ext/opcache/jit/zend_jit_trace.c


Diff:

diff --git a/NEWS b/NEWS
index 474db936ef15..ba832ef05888 100644
--- a/NEWS
+++ b/NEWS
@@ -25,6 +25,10 @@ PHP                                                                        NEWS
 - OpenSSL:
   . Fix missing error check on invalid alpn protocols. (ndossche)
 
+- Opcache:
+  . Fixed bug GH-22916 (Preserve parent regs in zend_jit_deoptimizer_start()).
+    (Arnaud)
+
 - PCRE:
   . Fixed bug GH-21134 (Crash with \C + UTF-8). Using \C in UTF-8 patterns is
     now forbidden. (Arnaud)
diff --git a/ext/opcache/jit/zend_jit_ir.c b/ext/opcache/jit/zend_jit_ir.c
index b48058196c95..4c20c115b848 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);
@@ -17043,6 +17048,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);
@@ -17055,6 +17061,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;
 }
 
@@ -17087,6 +17095,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;
@@ -17094,7 +17117,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) {
@@ -17138,10 +17160,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..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"
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.