Commit 6abb600217a for php

commit 6abb600217a20d1aa313000b08eed3d6eb9d8675
Author: ndossche <7771979+ndossche@users.noreply.github.com>
Date:   Tue Sep 29 19:58:06 2026 +0200

    Fix GH-23991: JIT generates invalid IR for multiple recursive calls

    A direct recursive call that jumps to a RECV entry is converted into a
    loop back edge. The continuation ENTRY of such a call uses the call's
    LOOP_END as its fake control edge. Once the entry already was a
    LOOP_BEGIN with 3 inputs, a further recursive call demoted the previous
    LOOP_END to an END and put it into a new MERGE. That END then had both a
    real successor and the fake ENTRY edge, which violates IR assumptions.
    That causes assert failures in basic block handling code (merge &
    fix_bb_order).
    This happened in WordPress's rest_sanitize_value_from_schema().
    I don't believe this optimization was ever fully correct, doing this
    optimization requires more complex handling and is certainly not worth
    the risk on stable branches.

    The hangs that OP reports are likely because the worker crashes while
    holding the shm lock, or something alike.

    Closes GH-24001.

diff --git a/NEWS b/NEWS
index 0052e326ce8..38a3f909e85 100644
--- a/NEWS
+++ b/NEWS
@@ -114,6 +114,8 @@ PHP                                                                        NEWS
     single `-0.0` constant). (lazerg)
   . Fixed crash when a file cache is reused with a different set of
     extensions declaring frameless functions. (Ilia Alshanetsky)
+  . Fixed bug GH-23991 (JIT generates invalid IR for multiple recursive calls).
+    (ndossche)

 - OpenSSL:
   . Fixed stream_socket_enable_crypto() leaving the socket non-blocking
diff --git a/ext/opcache/jit/zend_jit_ir.c b/ext/opcache/jit/zend_jit_ir.c
index 4c20c115b84..50851d75abb 100644
--- a/ext/opcache/jit/zend_jit_ir.c
+++ b/ext/opcache/jit/zend_jit_ir.c
@@ -10388,14 +10388,6 @@ static int zend_jit_do_fcall(zend_jit_ctx *jit, const zend_op *opline, const zen
 						insn->inputs_count = 3;
 						insn->op3 = end;
 						break;
-					} else if (insn->op == IR_LOOP_BEGIN && insn->inputs_count == 3) {
-						ZEND_ASSERT(jit->ctx.ir_base[insn->op3].op == IR_LOOP_END);
-						jit->ctx.ir_base[insn->op3].op = IR_END;
-						ir_MERGE_2(insn->op3, ir_END());
-						end = ir_LOOP_END();
-						insn = &jit->ctx.ir_base[begin];
-						insn->op3 = end;
-						break;
 					}
 				}
 				/* fallback to indirect JMP or RETURN */
diff --git a/ext/opcache/tests/jit/gh23991.phpt b/ext/opcache/tests/jit/gh23991.phpt
new file mode 100644
index 00000000000..21c07279362
--- /dev/null
+++ b/ext/opcache/tests/jit/gh23991.phpt
@@ -0,0 +1,36 @@
+--TEST--
+GH-23991 (JIT generates invalid IR for multiple recursive calls converted into loop)
+--INI--
+opcache.enable=1
+opcache.enable_cli=1
+opcache.jit_buffer_size=64M
+opcache.jit=1235
+opcache.jit_hot_func=1
+--EXTENSIONS--
+opcache
+--FILE--
+<?php
+function f($value, $recurse, $param = '') {
+    if (is_array($value)) {
+        if ($recurse) {
+            foreach ($value as $k => $v) {
+                $value[$k] = f($v, $recurse, $param);
+            }
+        }
+        foreach ($value as $k => $v) {
+            if ($undef) {
+                $value[$k] = f($v, $recurse, $param);
+            }
+        }
+    }
+    return $value;
+}
+var_dump(f([[]], true));
+?>
+--EXPECTF--
+Warning: Undefined variable $undef in %s on line %d
+array(1) {
+  [0]=>
+  array(0) {
+  }
+}