From 7abd7044917771546576e4644f79f20fe2a92218 Mon Sep 17 00:00:00 2001 From: ndossche <7771979+ndossche@users.noreply.github.com> Date: Sat, 26 Sep 2026 14:45:16 +0200 Subject: [PATCH] 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. --- Zend/Optimizer/zend_inference.c | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/Zend/Optimizer/zend_inference.c b/Zend/Optimizer/zend_inference.c index c2f6098a8122..ac3f858de9bf 100644 --- a/Zend/Optimizer/zend_inference.c +++ b/Zend/Optimizer/zend_inference.c @@ -5234,7 +5234,20 @@ 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) + /* can't include bool due to references to false or true types. */ + || (type_mask & ~(MAY_BE_NULL|MAY_BE_BOOL|MAY_BE_LONG|MAY_BE_DOUBLE|MAY_BE_STRING)) + /* single type */ + || (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;