Commit 57a90a3c030 for php
commit 57a90a3c030def70cafd214fee4e396f7b1adf1a
Author: Nora Dossche <7771979+ndossche@users.noreply.github.com>
Date: Thu Oct 15 19:12:49 2026 +0200
JIT: implement empty($cv) fast path in function & tracing JIT (#24239)
We can simply defer to zend_jit_bool_jmpznz() which already generates
optimized paths for many types.
Example PHP:
```php
function f(int $i) {
if (empty($i)) return 1;
return 2;
}
```
Before (function JIT):
```asm
.L1:
movl $0x414d88a0, %r15d ; opline = ISSET_ISEMPTY_CV
movabsq $0x55a80140fe00, %rax
callq *%rax ; Call VM handler
cmpl $0x414d88e0, %r15d ; check if the handler branched
jne .L2
movq 0x10(%r14), %rax ; return 1
...
.L2: ; return 2
...
```
After (function JIT):
```asm
.L1:
cmpq $0, 0x50(%r14) ; empty($i) check
je .L3
movq 0x10(%r14), %rax ; return 2
...
.L3: ; return 1
...
```
diff --git a/ext/opcache/jit/zend_jit.c b/ext/opcache/jit/zend_jit.c
index d6ce931fb97..2b71ce7a1fb 100644
--- a/ext/opcache/jit/zend_jit.c
+++ b/ext/opcache/jit/zend_jit.c
@@ -2305,7 +2305,7 @@ static int zend_jit(const zend_op_array *op_array, zend_ssa *ssa, const zend_op
OP1_INFO(), OP1_REG_ADDR(), RES_REG_ADDR(),
-1, -1,
zend_may_throw(opline, ssa_op, op_array, ssa),
- opline->opcode, NULL)) {
+ opline->opcode, NULL, true)) {
goto jit_failure;
}
goto done;
@@ -2331,15 +2331,11 @@ static int zend_jit(const zend_op_array *op_array, zend_ssa *ssa, const zend_op
OP1_INFO(), OP1_REG_ADDR(), res_addr,
ssa->cfg.blocks[b].successors[0], ssa->cfg.blocks[b].successors[1],
zend_may_throw(opline, ssa_op, op_array, ssa),
- opline->opcode, NULL)) {
+ opline->opcode, NULL, true)) {
goto jit_failure;
}
goto done;
case ZEND_ISSET_ISEMPTY_CV:
- if ((opline->extended_value & ZEND_ISEMPTY)) {
- // TODO: support for empty() ???
- break;
- }
if ((opline->result_type & IS_TMP_VAR)
&& (i + 1) <= end
&& ((opline+1)->opcode == ZEND_JMPZ
diff --git a/ext/opcache/jit/zend_jit_ir.c b/ext/opcache/jit/zend_jit_ir.c
index 188144de495..9d85bdc9765 100644
--- a/ext/opcache/jit/zend_jit_ir.c
+++ b/ext/opcache/jit/zend_jit_ir.c
@@ -7519,7 +7519,7 @@ static int zend_jit_identical(zend_jit_ctx *jit,
return 1;
}
-static int zend_jit_bool_jmpznz(zend_jit_ctx *jit, const zend_op *opline, uint32_t op1_info, zend_jit_addr op1_addr, zend_jit_addr res_addr, uint32_t target_label, uint32_t target_label2, int may_throw, uint8_t branch_opcode, const void *exit_addr)
+static int zend_jit_bool_jmpznz(zend_jit_ctx *jit, const zend_op *opline, uint32_t op1_info, zend_jit_addr op1_addr, zend_jit_addr res_addr, uint32_t target_label, uint32_t target_label2, int may_throw, uint8_t branch_opcode, const void *exit_addr, bool warn_undef)
{
uint32_t true_label = -1;
uint32_t false_label = -1;
@@ -7570,7 +7570,7 @@ static int zend_jit_bool_jmpznz(zend_jit_ctx *jit, const zend_op *opline, uint32
if (!(op1_info & ((MAY_BE_UNDEF|MAY_BE_ANY)-MAY_BE_TRUE))) {
always_true = true;
} else if (!(op1_info & (MAY_BE_ANY-(MAY_BE_NULL|MAY_BE_FALSE)))) {
- if (opline->op1_type == IS_CV && (op1_info & MAY_BE_UNDEF)) {
+ if (opline->op1_type == IS_CV && (op1_info & MAY_BE_UNDEF) && warn_undef) {
ref = jit_ZVAL_ADDR(jit, op1_addr);
zend_jit_zval_check_undef(jit, ref, opline->op1.var, opline, false);
}
@@ -7615,7 +7615,7 @@ static int zend_jit_bool_jmpznz(zend_jit_ctx *jit, const zend_op *opline, uint32
ir_IF_TRUE_cold(if_type);
- if (op1_info & MAY_BE_UNDEF) {
+ if ((op1_info & MAY_BE_UNDEF) && warn_undef) {
zend_jit_type_check_undef(jit,
type,
opline->op1.var,
@@ -8351,8 +8351,23 @@ static int zend_jit_isset_isempty_cv(zend_jit_ctx *jit, const zend_op *opline, u
uint32_t true_label = -1, false_label = -1;
ir_ref end_inputs = IR_UNUSED, true_inputs = IR_UNUSED, false_inputs = IR_UNUSED;
- // TODO: support for empty() ???
- ZEND_ASSERT(opline->extended_value != MAY_BE_RESOURCE);
+ if (opline->extended_value & ZEND_ISEMPTY) {
+ /* empty($cv) is !$cv, except that an undefined CV doesn't emit a warning. */
+ uint8_t branch_opcode;
+
+ if (!smart_branch_opcode) {
+ branch_opcode = ZEND_BOOL_NOT;
+ } else if (smart_branch_opcode == ZEND_JMPZ) {
+ branch_opcode = ZEND_JMPNZ;
+ } else if (smart_branch_opcode == ZEND_JMPNZ) {
+ branch_opcode = ZEND_JMPZ;
+ } else {
+ ZEND_UNREACHABLE();
+ }
+ /* zend_is_true() may call an object's cast handler. */
+ return zend_jit_bool_jmpznz(jit, opline, op1_info, op1_addr, res_addr,
+ target_label, target_label2, (op1_info & MAY_BE_OBJECT) != 0, branch_opcode, exit_addr, false);
+ }
if (smart_branch_opcode && !exit_addr) {
if (smart_branch_opcode == ZEND_JMPZ) {
diff --git a/ext/opcache/jit/zend_jit_trace.c b/ext/opcache/jit/zend_jit_trace.c
index 755d8adbeb2..e1836b82610 100644
--- a/ext/opcache/jit/zend_jit_trace.c
+++ b/ext/opcache/jit/zend_jit_trace.c
@@ -2074,10 +2074,6 @@ static zend_ssa *zend_jit_trace_build_tssa(zend_jit_trace_rec *trace_buffer, uin
ADD_OP1_TRACE_GUARD();
break;
case ZEND_ISSET_ISEMPTY_CV:
- if ((opline->extended_value & ZEND_ISEMPTY)) {
- // TODO: support for empty() ???
- break;
- }
ADD_OP1_TRACE_GUARD();
break;
case ZEND_IN_ARRAY:
@@ -5682,7 +5678,7 @@ static zend_vm_opcode_handler_t zend_jit_trace(zend_jit_trace_rec *trace_buffer,
op1_info, OP1_REG_ADDR(), RES_REG_ADDR(),
-1, -1,
zend_may_throw(opline, ssa_op, op_array, ssa),
- opline->opcode, NULL)) {
+ opline->opcode, NULL, true)) {
goto jit_failure;
}
goto done;
@@ -5752,7 +5748,7 @@ static zend_vm_opcode_handler_t zend_jit_trace(zend_jit_trace_rec *trace_buffer,
op1_info, OP1_REG_ADDR(), res_addr,
-1, -1,
zend_may_throw(opline, ssa_op, op_array, ssa),
- smart_branch_opcode, exit_addr)) {
+ smart_branch_opcode, exit_addr, true)) {
goto jit_failure;
}
goto done;
@@ -5784,10 +5780,6 @@ static zend_vm_opcode_handler_t zend_jit_trace(zend_jit_trace_rec *trace_buffer,
}
goto done;
case ZEND_ISSET_ISEMPTY_CV:
- if ((opline->extended_value & ZEND_ISEMPTY)) {
- // TODO: support for empty() ???
- break;
- }
op1_info = OP1_INFO();
op1_addr = OP1_REG_ADDR();
if (orig_op1_type != IS_UNKNOWN