php.net |  support |  documentation |  report a bug |  advanced search |  search howto |  statistics |  random bug |  login
Bug #81225 Wrong result with pow operator with JIT enabled
Submitted: 2021-07-05 23:31 UTC Modified: 2021-07-06 12:48 UTC
From: smokey101stair at gmail dot com Assigned: dmitry (profile)
Status: Closed Package: JIT
PHP Version: 8.0.8 OS: Ubuntu 20.04
Private report: No CVE-ID: None
 [2021-07-05 23:31 UTC] smokey101stair at gmail dot com
Description:
------------
When the JIT is enabled, the test script sometimes returns -4294967294 instead of 2. This seems to be related to the ** operator as it appears to work fine if replaced with the pow() function.

I get the following with opcache.jit_debug=1

TRACE-1$/home/trevor/jit_test.php$16: ; (unknown)
        mov $EG(jit_trace_num), %rax
        mov $0x1, (%rax)
        mov $EG(vm_stack_end), %rax
        mov (%rax), %rcx
        mov $EG(vm_stack_top), %rax
        sub (%rax), %rcx
        cmp $0xf0, %rcx
        jb jit$$trace_exit_0
.L1:
        cmp $0x4, 0x68(%r14)
        jnz jit$$trace_exit_1
        cmp $0xc8, 0x60(%r14)
        jge jit$$trace_exit_2
        add $0xfffffffffffffec0, %r15
        mov $0x5633ad4d2080, %rax
        call *%rax
        mov $EG(exception), %rcx
        cmp $0x0, (%rcx)
        jnz JIT$$exception_handler
        cmp $0x41675ab8, %r15d
        jnz jit$$trace_exit_3
        mov $EG(vm_stack_top), %r15
        mov (%r15), %r15
        mov $EG(vm_stack_top), %rdx
        add $0x70, (%rdx)
        mov $0x0, 0x28(%r15)
        mov $0x5633af37bbf0, %rcx
        mov %rcx, 0x18(%r15)
        mov $0x0, 0x20(%r15)
        mov $0x2, 0x2c(%r15)
        mov $0x0, 0x30(%r15)
        mov %r15, 0x8(%r14)
        mov 0x40(%r14), %rax
        mov 0x10(%rax), %rax
        test %rax, %rax
        jz .L7
.L2:
        mov $EG(vm_stack_top), %r15
        mov (%r15), %r15
        mov $EG(vm_stack_top), %rdx
        add $0x80, (%rdx)
        mov $0x0, 0x28(%r15)
        mov %rax, 0x18(%r15)
        mov $0x0, 0x20(%r15)
        mov $0x1, 0x2c(%r15)
        cmp $0x4, 0x58(%r14)
        jnz jit$$trace_exit_4
        mov 0x50(%r14), %rdx
        mov %rdx, 0x50(%r15)
        mov $0x4, 0x58(%r15)
        mov $0x41675b18, (%r14)
        mov %r14, 0x30(%r15)
        mov $0x0, 0x8(%r15)
        lea 0x80(%r14), %rdx
        mov %rdx, 0x10(%r15)
        mov 0x18(%r15), %rax
        mov 0x50(%rax), %rdx
        mov $0x5633ae520f78, %rcx
        add (%rcx), %rdx
        mov (%rdx), %rdx
        mov %rdx, 0x40(%r15)
        mov $EG(current_execute_data), %rcx
        mov %r15, (%rcx)
        mov %r15, %r14
        mov $0x0, 0x68(%r15)
        cmp $0x1, 0x2c(%r14)
        jb jit$$trace_exit_5
        cmp $0x4, 0x58(%r14)
        jnz .L8
.L3:
        mov 0x50(%r14), %rax
        mov $0x100000000, %rcx
        cqo 
        idiv %rcx
        test %rdx, %rdx
        jge jit$$trace_exit_6
        lea (%rdx), %rdx
        mov 0x10(%r14), %rcx
        mov %rdx, (%rcx)
        mov $0x4, 0x8(%rcx)
        mov $EG(vm_stack_top), %rax
        mov %r14, (%rax)
        mov 0x30(%r14), %r14
        mov $EG(current_execute_data), %rax
        mov %r14, (%rax)
        cmp $0x2, 0x80(%r14)
        setz %al
        movzx %al, %eax
        add $0x2, %eax
        mov %eax, 0x78(%r14)
        cmp $0x3, 0x78(%r14)
        jnz jit$$trace_exit_7
        mov 0x8(%r14), %r15
        mov $0x3, 0x58(%r15)
        mov $0x40f454d0, 0x60(%r15)
        mov $0x6, 0x68(%r15)
        mov $0x41675b98, (%r14)
        mov $0x0, 0x8(%r14)
        mov %r14, 0x30(%r15)
        mov %rsp, %rsi
        mov $0x1, 0x8(%rsi)
        mov $EG(current_execute_data), %rcx
        mov %r15, (%rcx)
        mov %r15, %rdi
        mov $0x5633ad3dbfb0, %rax
        call *%rax
        mov $EG(current_execute_data), %rax
        mov %r14, (%rax)
        test $0x1, 0x59(%r15)
        jnz .L9
.L4:
        test $0x1, 0x69(%r15)
        jnz .L10
.L5:
        mov $EG(vm_stack_top), %rax
        mov %r15, (%rax)
        mov $EG(exception), %rax
        cmp $0x0, (%rax)
        jnz JIT$$icall_throw
        mov $EG(vm_interrupt), %rax
        cmp $0x0, (%rax)
        jnz jit$$trace_exit_8
        cmp $0x4, 0x68(%r14)
        jnz jit$$trace_exit_9
        add $0x1, 0x60(%r14)
        jo .L11
.L6:
        mov $0x41675bd8, %r15
        mov $EG(vm_interrupt), %rax
        cmp $0x0, (%rax)
        jz .L1
        jmp JIT$$interrupt_handler
.L7:
        mov $0x416757a0, %rdi
        mov $zend_jit_init_func_run_time_cache_helper, %rax
        call *%rax
        mov 0x40(%r14), %rcx
        mov %rax, 0x10(%rcx)
        jmp .L2
.L8:
        lea 0x50(%r14), %rdi
        mov $0x416758a8, (%r14)
        mov $0x41675988, %rsi
        mov $zend_jit_verify_arg_slow, %rax
        call *%rax
        test %al, %al
        jnz .L3
        jmp JIT$$exception_handler
.L9:
        mov 0x50(%r15), %rdi
        sub $0x1, (%rdi)
        jnz .L4
        mov $0x41675b98, (%r14)
        mov $rc_dtor_func, %rax
        call *%rax
        jmp .L4
.L10:
        mov 0x60(%r15), %rdi
        sub $0x1, (%rdi)
        jnz .L5
        mov $0x41675b98, (%r14)
        mov $rc_dtor_func, %rax
        call *%rax
        jmp .L5
.L11:
        mov $0x43e0000000000000, %rax
        mov %rax, 0x60(%r14)
        mov $0x5, 0x68(%r14)
        jmp .L6

Test script:
---------------
<?php

function unsignedLong(int $offset): int
{
    $normalizedOffset = $offset % (2 ** 32);

    if ($normalizedOffset < 0) {
        $normalizedOffset += 2 ** 32;
    }

    return $normalizedOffset;
}

$offset = -0x100000000 + 2;

for ($i = 0; $i < 200; ++$i) {
    assert(unsignedLong($offset) === 2);
}



Patches

Pull Requests

History

AllCommentsChangesGit/SVN commitsRelated reports
 [2021-07-06 12:48 UTC] nikic@php.net
-Status: Open +Status: Verified -Assigned To: +Assigned To: dmitry
 [2021-07-06 12:48 UTC] nikic@php.net
Also reproduces with function JIT. I see these lines:

    492d4da3:	mov $0x100000000, %rcx
    492d4dad:	cqo 
    492d4daf:	idiv %rcx
    492d4db2:	test %rdx, %rdx
	jge .L2
    492d4db7:	lea (%rdx), %rdx
.L2:

The lea looks suspicious to me, maybe there is an issue with encoding of immediates > 32-bit?
 [2021-07-12 05:48 UTC] hao dot sun at arm dot com
Based on my analysis, the root cause is "lea (%rdx), %rdx".

"lea" instruction would be used to optimize "add" or "sub" with constants in JIT/x86. See https://github.com/php/php-src/blob/master/ext/opcache/jit/zend_jit_x86.dasc#L4296-L4310

In this test case, the "lea" is generated at line 4300.

As @nikic suspected, it's an issue of encoding of immediate > 32bits.
AFAIK, for "lea reg, [reg + imm]" in x86_64, the imm field should not be > 32bits, otherwise, truncation would be done.

One possible solution may be adding one check, whether "Z_LVAL_P(Z_ZV(op2_addr))" can be represented by 32-bits, just before L4300.


Note that this bug doesn't affect JIT/arm64 in master branch, since "lea" optimization is not conducted in arm64.
 [2021-07-19 07:43 UTC] git@php.net
Automatic comment on behalf of dstogov
Revision: https://github.com/php/php-src/commit/9cd437138e2a7c172427876c8754b445fa1e1ab1
Log: Fixed bug #81225 (Wrong result with pow operator with JIT enabled)
 [2021-07-19 07:43 UTC] git@php.net
-Status: Verified +Status: Closed
 
PHP Copyright © 2001-2024 The PHP Group
All rights reserved.
Last updated: Mon Dec 30 14:01:28 2024 UTC