Commit 78d56e0e86e for php

commit 78d56e0e86e3f83258e5be9922bed6ff6fe97d62
Author: Nora Dossche <7771979+ndossche@users.noreply.github.com>
Date:   Thu Oct 8 18:42:44 2026 +0200

    JIT: Optimize array checks in comparisons (#24084)

    There are two optimizations:
    1. This patch implements the JIT counterpart of the VM change 2d6b8694 to
       optimize comparisons against empty arrays.
    2. This patch ensures that if a type can be array, the element count
       check happens inline rather than going to a slow path.

    PHP demo code for #1:
    ```php
    function f(array $a) {
            if ($a) return 0;
            return 1;
    }
    ```

    Before #1 (for func JIT in this case):
    ```asm
    .L1:
            movq $0x418d88c0, (%r14)        ; EX(opline) = opline
            leaq 0x50(%r14), %rdi           ; arg = &CV0($a)
            movabsq $zend_is_true, %rax
            callq *%rax
            testb %al, %al
            je .L2
    ```

    After #1:
    ```asm
    .L1:
            movq 0x50(%r14), %rax           ; Z_ARRVAL($a)
            cmpl $0, 0x1c(%rax)             ; compare nNumOfElements
            je .L2
    ```

    PHP demo code for #2:
    ```php
    function is_empty(array $a) {
            if ($a === []) return 1;
            return 2;
    }
    ```

    Before #2:
    ```asm
    .L1:
            leaq 0x50(%r14), %rdi           ; arg1 = &CV0($a)
            movl $0x418d8ad8, %esi          ; arg2 = &literal []
            movabsq $zend_is_identical, %rax
            callq *%rax
            testb %al, %al
            je .L2
    ```

    After #2:
    ```asm
    .L1:
            movq 0x50(%r14), %rax           ; Z_ARRVAL($a)
            cmpl $0, 0x1c(%rax)             ; compare nNumOfElements
            jne .L2
    ```

    It ain't much, but it should help.
    I think many small frontend improvements can make a nice change in
    total together.

diff --git a/ext/opcache/jit/zend_jit_ir.c b/ext/opcache/jit/zend_jit_ir.c
index 85f614e459b..76fa8a0947f 100644
--- a/ext/opcache/jit/zend_jit_ir.c
+++ b/ext/opcache/jit/zend_jit_ir.c
@@ -7263,6 +7263,13 @@ static int zend_jit_cmp(zend_jit_ctx   *jit,
 	return 1;
 }

+static bool zend_jit_is_const_empty_array(zend_jit_addr addr)
+{
+	return Z_MODE(addr) == IS_CONST_ZVAL
+		&& Z_TYPE_P(Z_ZV(addr)) == IS_ARRAY
+		&& zend_hash_num_elements(Z_ARRVAL_P(Z_ZV(addr))) == 0;
+}
+
 static int zend_jit_identical(zend_jit_ctx   *jit,
                               const zend_op  *opline,
                               uint32_t        op1_info,
@@ -7424,6 +7431,14 @@ static int zend_jit_identical(zend_jit_ctx   *jit,
 			zval *val = Z_ZV(op2_addr);

 			ref = ir_EQ(jit_Z_TYPE(jit, op1_addr), ir_CONST_U8(Z_TYPE_P(val)));
+		} else if (zend_jit_is_const_empty_array(op1_addr) && (op2_info & (MAY_BE_ANY|MAY_BE_UNDEF)) == MAY_BE_ARRAY) {
+			// JIT: zend_hash_num_elements(Z_ARRVAL_P(op2)) == 0
+			ref = ir_EQ(ir_LOAD_U32(ir_ADD_OFFSET(jit_Z_PTR(jit, op2_addr), offsetof(HashTable, nNumOfElements))),
+				ir_CONST_U32(0));
+		} else if (zend_jit_is_const_empty_array(op2_addr) && (op1_info & (MAY_BE_ANY|MAY_BE_UNDEF)) == MAY_BE_ARRAY) {
+			// JIT: zend_hash_num_elements(Z_ARRVAL_P(op1)) == 0
+			ref = ir_EQ(ir_LOAD_U32(ir_ADD_OFFSET(jit_Z_PTR(jit, op1_addr), offsetof(HashTable, nNumOfElements))),
+				ir_CONST_U32(0));
 		} else {
 			if (Z_MODE(op1_addr) == IS_REG) {
 				zend_jit_addr real_addr = ZEND_ADDR_MEM_ZVAL(ZREG_FP, opline->op1.var);
@@ -7762,8 +7777,20 @@ static int zend_jit_bool_jmpznz(zend_jit_ctx *jit, const zend_op *opline, uint32
 	}

 	if (op1_info & (MAY_BE_ANY - (MAY_BE_NULL|MAY_BE_FALSE|MAY_BE_TRUE|MAY_BE_LONG|MAY_BE_DOUBLE))) {
-		jit_SET_EX_OPLINE(jit, opline);
-		ref = ir_CALL_1(IR_BOOL, ir_CONST_FC_FUNC(zend_is_true), jit_ZVAL_ADDR(jit, op1_addr));
+		if ((op1_info & (MAY_BE_ANY - (MAY_BE_NULL|MAY_BE_FALSE|MAY_BE_TRUE|MAY_BE_LONG|MAY_BE_DOUBLE))) == MAY_BE_ARRAY) {
+			/* CV deref handled above. These opcodes only take CONST|TMP|CV operands, and TMPs never hold references. */
+			ZEND_ASSERT(opline->op1_type == IS_CV || !(op1_info & MAY_BE_REF));
+			// JIT: zend_hash_num_elements(Z_ARRVAL_P(op1)) != 0
+			ref = ir_NE(ir_LOAD_U32(ir_ADD_OFFSET(jit_Z_PTR(jit, op1_addr), offsetof(HashTable, nNumOfElements))),
+				ir_CONST_U32(0));
+			if (opline->op1_type == IS_CV) {
+				/* The check above can't throw, and a CV isn't freed. */
+				may_throw = 0;
+			}
+		} else {
+			jit_SET_EX_OPLINE(jit, opline);
+			ref = ir_CALL_1(IR_BOOL, ir_CONST_FC_FUNC(zend_is_true), jit_ZVAL_ADDR(jit, op1_addr));
+		}
 		jit_FREE_OP(jit, opline->op1_type, opline->op1, op1_info, NULL);
 		if (may_throw) {
 			zend_jit_check_exception_undef_result(jit, opline);