From bbc2b17222a411531cf62a9ab6fdc838768515cc Mon Sep 17 00:00:00 2001 From: ndossche <7771979+ndossche@users.noreply.github.com> Date: Sat, 10 Oct 2026 14:38:40 +0200 Subject: [PATCH 1/2] JIT: implement empty($cv) fast path in function & tracing JIT 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 ... ``` --- ext/opcache/jit/zend_jit.c | 4 ---- ext/opcache/jit/zend_jit_ir.c | 23 +++++++++++++++++++++-- ext/opcache/jit/zend_jit_trace.c | 8 -------- 3 files changed, 21 insertions(+), 14 deletions(-) diff --git a/ext/opcache/jit/zend_jit.c b/ext/opcache/jit/zend_jit.c index e5d26096984b..fcb1d02e6069 100644 --- a/ext/opcache/jit/zend_jit.c +++ b/ext/opcache/jit/zend_jit.c @@ -2336,10 +2336,6 @@ static int zend_jit(const zend_op_array *op_array, zend_ssa *ssa, const zend_op } 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 eec5434cde74..2f722adef368 100644 --- a/ext/opcache/jit/zend_jit_ir.c +++ b/ext/opcache/jit/zend_jit_ir.c @@ -8349,8 +8349,27 @@ 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. + * Treat UNDEF as NULL: both are falsy and have a type below IS_TRUE. */ + uint8_t branch_opcode; + + if (op1_info & MAY_BE_UNDEF) { + op1_info = (op1_info & ~MAY_BE_UNDEF) | MAY_BE_NULL; + } + 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); + } 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 9afe1244a49d..a5b46eb1b25d 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: @@ -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 From 158e9096d4d637dbd3c50da6736417688dbe471c Mon Sep 17 00:00:00 2001 From: ndossche <7771979+ndossche@users.noreply.github.com> Date: Thu, 15 Oct 2026 19:03:04 +0200 Subject: [PATCH 2/2] Use argument instead of playing with op1 type --- ext/opcache/jit/zend_jit.c | 4 ++-- ext/opcache/jit/zend_jit_ir.c | 14 +++++--------- ext/opcache/jit/zend_jit_trace.c | 4 ++-- 3 files changed, 9 insertions(+), 13 deletions(-) diff --git a/ext/opcache/jit/zend_jit.c b/ext/opcache/jit/zend_jit.c index fcb1d02e6069..04e6831f5b51 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,7 +2331,7 @@ 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; diff --git a/ext/opcache/jit/zend_jit_ir.c b/ext/opcache/jit/zend_jit_ir.c index 2f722adef368..91f4fe7c943d 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, @@ -8350,13 +8350,9 @@ static int zend_jit_isset_isempty_cv(zend_jit_ctx *jit, const zend_op *opline, u ir_ref end_inputs = IR_UNUSED, true_inputs = IR_UNUSED, false_inputs = IR_UNUSED; if (opline->extended_value & ZEND_ISEMPTY) { - /* empty($cv) is !$cv, except that an undefined CV doesn't emit a warning. - * Treat UNDEF as NULL: both are falsy and have a type below IS_TRUE. */ + /* empty($cv) is !$cv, except that an undefined CV doesn't emit a warning. */ uint8_t branch_opcode; - if (op1_info & MAY_BE_UNDEF) { - op1_info = (op1_info & ~MAY_BE_UNDEF) | MAY_BE_NULL; - } if (!smart_branch_opcode) { branch_opcode = ZEND_BOOL_NOT; } else if (smart_branch_opcode == ZEND_JMPZ) { @@ -8368,7 +8364,7 @@ static int zend_jit_isset_isempty_cv(zend_jit_ctx *jit, const zend_op *opline, u } /* 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); + target_label, target_label2, (op1_info & MAY_BE_OBJECT) != 0, branch_opcode, exit_addr, false); } if (smart_branch_opcode && !exit_addr) { diff --git a/ext/opcache/jit/zend_jit_trace.c b/ext/opcache/jit/zend_jit_trace.c index a5b46eb1b25d..e8cbe249fae5 100644 --- a/ext/opcache/jit/zend_jit_trace.c +++ b/ext/opcache/jit/zend_jit_trace.c @@ -5678,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; @@ -5748,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;