Commit 455ae2880e4 for php.net

commit 455ae2880e44bb8372afcdd9cb3b52f1042fa59e
Author: Ilija Tovilo <ilija.tovilo@me.com>
Date:   Wed Apr 1 23:40:25 2026 +0200

    Fix function JIT JMPNZ smart branch

    When a smart branch and jump live in separate basic blocks, the JIT can't skip
    the jitting of the jump, as it may be reachable through another predecessor.
    When the smart branch is executed using zend_jit_handler(), we're manually
    writing the result of the branch to the given temporary so that the jump will
    work as expected. That happens in zend_jit_set_cond().

    However, this was only correctly handled for JMPZ branches. The current opline
    was compared to opline following the jump, which would set the var to 1 if
    equal, i.e. the branch was not taken, meaning var was not zero. For JMPNZ we
    need to do the opposite.

    Fixes GH-21593

diff --git a/NEWS b/NEWS
index b9dffade35d..2472825c0da 100644
--- a/NEWS
+++ b/NEWS
@@ -16,6 +16,7 @@ PHP                                                                        NEWS
 - Opcache:
   . Fixed bug GH-21158 (JIT: Assertion jit->ra[var].flags & (1<<0) failed in
     zend_jit_use_reg). (Arnaud)
+  . Fixed bug GH-21593 (Borked function JIT JMPNZ smart branch). (ilutov)

 - SPL:
   . Fixed bug GH-21499 (RecursiveArrayIterator getChildren UAF after parent
diff --git a/ext/opcache/jit/zend_jit.c b/ext/opcache/jit/zend_jit.c
index 19e5520b156..da73c98c435 100644
--- a/ext/opcache/jit/zend_jit.c
+++ b/ext/opcache/jit/zend_jit.c
@@ -2748,7 +2748,7 @@ static int zend_jit(const zend_op_array *op_array, zend_ssa *ssa, const zend_op
 					if (i == end
 					 && (opline->result_type & (IS_SMART_BRANCH_JMPZ|IS_SMART_BRANCH_JMPNZ)) != 0) {
 						/* smart branch split across basic blocks */
-						if (!zend_jit_set_cond(&ctx, opline + 2, opline->result.var)) {
+						if (!zend_jit_set_cond(&ctx, opline, opline + 2, opline->result.var)) {
 							goto jit_failure;
 						}
 					}
diff --git a/ext/opcache/jit/zend_jit_ir.c b/ext/opcache/jit/zend_jit_ir.c
index 2bad605c537..4251d6b891c 100644
--- a/ext/opcache/jit/zend_jit_ir.c
+++ b/ext/opcache/jit/zend_jit_ir.c
@@ -4105,11 +4105,12 @@ static int zend_jit_cond_jmp(zend_jit_ctx *jit, const zend_op *next_opline, int
 	return 1;
 }

-static int zend_jit_set_cond(zend_jit_ctx *jit, const zend_op *next_opline, uint32_t var)
+static int zend_jit_set_cond(zend_jit_ctx *jit, const zend_op *opline, const zend_op *next_opline, uint32_t var)
 {
 	ir_ref ref;

-	ref = ir_ADD_U32(ir_ZEXT_U32(jit_CMP_IP(jit, IR_EQ, next_opline)), ir_CONST_U32(IS_FALSE));
+	ir_op op = (opline->result_type & IS_SMART_BRANCH_JMPZ) ? IR_EQ : IR_NE;
+	ref = ir_ADD_U32(ir_ZEXT_U32(jit_CMP_IP(jit, op, next_opline)), ir_CONST_U32(IS_FALSE));

 	// EX_VAR(var) = ...
 	ir_STORE(ir_ADD_OFFSET(jit_FP(jit), var + offsetof(zval, u1.type_info)), ref);
diff --git a/ext/opcache/tests/jit/gh21593.phpt b/ext/opcache/tests/jit/gh21593.phpt
new file mode 100644
index 00000000000..d3750019573
--- /dev/null
+++ b/ext/opcache/tests/jit/gh21593.phpt
@@ -0,0 +1,49 @@
+--TEST--
+GH-21593: Function JIT JMPNZ smart branch
+--CREDITS--
+paulmhh
+--EXTENSIONS--
+opcache
+--INI--
+opcache.enable=1
+opcache.enable_cli=1
+opcache.jit=function
+--FILE--
+<?php
+
+function test1($a) {
+    if (isset($a?->a)) {
+        echo "1\n";
+    }
+}
+
+function test2($a) {
+    if (!isset($a?->a)) {
+        echo "2\n";
+    }
+}
+
+function test3($a) {
+    if (empty($a?->a)) {
+        echo "3\n";
+    }
+}
+
+function test4($a) {
+    if (!empty($a?->a)) {
+        echo "4\n";
+    }
+}
+
+$a = new stdClass;
+$a->a = 'a';
+
+test1($a);
+test2($a);
+test3($a);
+test4($a);
+
+?>
+--EXPECT--
+1
+4