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;