Commit 118f06a7b3a for php

commit 118f06a7b3ad8b1a2c2bea962654f921b762495b
Author: Nora Dossche <7771979+ndossche@users.noreply.github.com>
Date:   Thu Oct 29 19:05:26 2026 +0100

    Avoid generating exception check in tracing JIT for uncoerced scalar typed property writes (#23925)

    * Avoid generating exception check in tracing JIT for uncoerced scalar typed property writes

    Consider this PHP code, which we execute using tracing JIT:

    ```php
    class Foo {
        public int $test = 1;
    }

    function main() {
        $foo = new Foo;
        for ($i = 0; $i < 1000000; $i++) {
            $foo->test = $i;
        }
        return $foo;
    }

    main();
    ```

    The loop previously generated this code on x86-64:

    ```asm
    .L1:
            cmpq $0xf4240, %rbx
            jge jit$$trace_exit_0
            movq 0x50(%r14), %rax
            leaq 0x28(%rax), %rdi
            cmpl $0, 0x30(%rax)
            je jit$$trace_exit_1
            movq $0x41cd8cc0, (%r14)
            leaq 0x60(%r14), %rdx
            movq %rbx, (%rdx)
            movl $0x41cd89e0, %esi
            xorq %rcx, %rcx
            movabsq $zend_jit_assign_to_typed_prop, %rax
            callq *%rax
            movabsq $EG(exception), %rax
            movq (%rax), %rax
            testq %rax, %rax
            jne JIT$$exception_handler
            leaq 1(%rbx), %rbx
            movabsq $EG(vm_interrupt), %rax
            movb (%rax), %al
            testb %al, %al
            je .L1
            jmp jit$$trace_exit_2
    ```

    Note the exception check after the `zend_jit_assign_to_typed_prop` call.
    This is not necessary because the property is known to hold a scalar.
    Extending `zend_may_throw_ex` allows eliminating that check:

    ```asm
    .L1:
            cmpq $0xf4240, %rbx
            jge jit$$trace_exit_0
            movq 0x50(%r14), %rax
            leaq 0x28(%rax), %rdi
            cmpl $0, 0x30(%rax)
            je jit$$trace_exit_1
            movq $0x424d8cc0, (%r14)
            leaq 0x60(%r14), %rdx
            movq %rbx, (%rdx)
            movl $0x424d89e0, %esi
            xorq %rcx, %rcx
            movabsq $zend_jit_assign_to_typed_prop, %rax
            callq *%rax
            leaq 1(%rbx), %rbx
            movabsq $EG(vm_interrupt), %rax
            movb (%rax), %al
            testb %al, %al
            je .L1
            jmp jit$$trace_exit_2
    ```

    This is a minor first step in optimizing the JIT frontend for property assignments.
    This mainly reduces code bloat.

    * fix comment

diff --git a/Zend/Optimizer/zend_inference.c b/Zend/Optimizer/zend_inference.c
index 3359a4f214c..04742707238 100644
--- a/Zend/Optimizer/zend_inference.c
+++ b/Zend/Optimizer/zend_inference.c
@@ -5258,7 +5258,19 @@ ZEND_API bool zend_may_throw_ex(const zend_op *opline, const zend_ssa_op *ssa_op
 					zend_hash_find_ptr(&ce->properties_info, prop_name);
 				if (prop_info) {
 					if (ZEND_TYPE_IS_SET(prop_info->type)) {
-						return 1;
+						uint32_t type_mask = ZEND_TYPE_PURE_MASK(prop_info->type);
+
+						/* The assignment can't fail if the property only accepts a single scalar type
+						 * and the value already has this type: the old value has no destructor.
+						 * If the property holds a reference, all its type sources accept this type because they accept the current value. */
+						if ((prop_info->flags & (ZEND_ACC_READONLY|ZEND_ACC_PPP_SET_MASK))
+						 || ZEND_TYPE_IS_COMPLEX(prop_info->type)
+						 || (type_mask & ~(MAY_BE_NULL|MAY_BE_BOOL|MAY_BE_LONG|MAY_BE_DOUBLE|MAY_BE_STRING))
+						 /* single type, this excludes bool too as a reference can exist to true or false standalone types. */
+						 || (type_mask & (type_mask - 1))
+						 || (OP1_DATA_INFO() & (MAY_BE_ANY|MAY_BE_UNDEF|MAY_BE_REF)) != type_mask) {
+							return 1;
+						}
 					}
 					return !(prop_info->flags & ZEND_ACC_PUBLIC)
 						&& prop_info->ce != op_array->scope;