From d87bacbd81ca3e037412dc516106cb16bc3a9be7 Mon Sep 17 00:00:00 2001 From: Ilija Tovilo Date: Wed, 1 Apr 2026 23:40:25 +0200 Subject: [PATCH] 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 --- ext/opcache/jit/zend_jit.c | 2 +- ext/opcache/jit/zend_jit_ir.c | 5 +-- ext/opcache/tests/jit/gh21593.phpt | 49 ++++++++++++++++++++++++++++++ 3 files changed, 53 insertions(+), 3 deletions(-) create mode 100644 ext/opcache/tests/jit/gh21593.phpt diff --git a/ext/opcache/jit/zend_jit.c b/ext/opcache/jit/zend_jit.c index 19e5520b1569e..da73c98c43552 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 2bad605c537d0..4251d6b891c94 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 0000000000000..d37500195737d --- /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-- +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