php.net |  support |  documentation |  report a bug |  advanced search |  search howto |  statistics |  random bug |  login
Bug #81249 Intermittent property assignment failure with JIT enabled
Submitted: 2021-07-12 03:46 UTC Modified: 2021-07-12 08:13 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
View Add Comment Developer Edit
Welcome! If you don't have a Git account, you can't do anything here.
You can add a comment by following this link or if you reported this bug, you can edit this bug over here.
(description)
Block user comment
Status: Assign to:
Package:
Bug Type:
Summary:
From: smokey101stair at gmail dot com
New email:
PHP Version: OS:

 

 [2021-07-12 03:46 UTC] smokey101stair at gmail dot com
Description:
------------
PHP 8.0.8 (cli) (built: Jul  4 2021 21:22:34) ( NTS )
Copyright (c) The PHP Group
Zend Engine v4.0.8, Copyright (c) Zend Technologies
    with Zend OPcache v8.0.8, Copyright (c), by Zend Technologies

PHP was compiled using PHPBrew, if that makes a difference.

opcache.jit=1255
opcache.memory_consumption=256
opcache.max_accelerated_files=20000
opcache.jit_buffer_size=256M
zend.assertions=1

The test script decodes EUC-JP text to Unicode Code Points. I tried to reduce the script as much as possible, but it was very temperamental when removing if statements.

When processing the first byte, 8F, the assignment at https://gist.github.com/TRowbotham/4dc7c8dc1714edab69019e0d71c17c02#file-jit1-php-L90 fails intermittently. This leaves $this->lead set to its initial value of 0, which eventually leads to the script returning an error instead of a code point.

The script is expected to transform the EUC-JP encoded bytes to the Unicode U+2D8 code point.

Test script:
---------------
https://gist.github.com/TRowbotham/4dc7c8dc1714edab69019e0d71c17c02


Patches

Add a Patch

Pull Requests

Add a Pull Request

History

AllCommentsChangesGit/SVN commitsRelated reports
 [2021-07-12 08:13 UTC] nikic@php.net
-Status: Open +Status: Verified -Assigned To: +Assigned To: dmitry
 [2021-07-19 00:30 UTC] hao dot sun at arm dot com
FYI.

1. This bug occurred for master branch as well, for both JIT/x86 and JIT/arm64.
2. In my test, the test case can be further downsized, that is, Lines 85 to 87 can be removed as well, i.e. https://gist.github.com/TRowbotham/4dc7c8dc1714edab69019e0d71c17c02#file-jit1-php-L85-L87
3. I suspect this bug is due to "hot side exit".
1) -d opcache.jit_hot_side_exit=0, this bug is gone.
2) -d opcache.jit_hot_side_exit=N, the smaller the number we assigned, the earlier this bug showed up.
 [2021-07-19 06:27 UTC] hao dot sun at arm dot com
Based on my local debugging, I suspected function zend_jit_may_skip_comparison().

Code is not generated as expected for the hot side trace for statement: "if ($byte === 0x8E || $byte === 0x8F || ($byte >= 0xA1 && $byte <= 0xFE)) {"
Here is trace #5: (Note several "echo" statements are inserted in my local debugging). For "$byte === 0x8E || $byte === 0x8F", there are two IS_IDENTICAL opcodes.

     TRACE 3 exit 1 EucJpDecoder::handle() /home/haosun01/php/7.php:91
---- TRACE 5 start (side trace 3/1) EucJpDecoder::handle() /home/haosun01/php/7.php:91
========3333333 143
===== 44444444 set lead here: 143 : 143
0086 T7 = ROPE_INIT 3 string("========3333333 ")
0087 T7 = ROPE_ADD 1 T7 CV1($byte) ; op2(int)
0088 T6 = ROPE_END 2 T7 string("
")
0089 ECHO T6 ; op1(string)
0090 T6 = IS_IDENTICAL CV1($byte) int(142) ; op1(int)
0091 ;JMPNZ T6 0098
0092 T6 = IS_IDENTICAL CV1($byte) int(143) ; op1(int)
0093 ;JMPNZ T6 0098
0098 ASSIGN_OBJ THIS string("lead") ; op3(int)
0099 ;OP_DATA CV1($byte)
0100 T7 = ROPE_INIT 5 string("===== 44444444 set lead here: ")
0101 T7 = ROPE_ADD 1 T7 CV1($byte) ; op2(int)
0102 T7 = ROPE_ADD 2 T7 string(" : ")
0103 T6 = FETCH_OBJ_R THIS string("lead")
0104 T7 = ROPE_ADD 3 T7 T6 ; op2(int)
0105 T6 = ROPE_END 4 T7 string("
")
0106 ECHO T6 ; op1(string)
0107 RETURN int(-1)
---- TRACE 5 stop (return)


However, in the compiled code, I found that only the code for "$byte === 0x8E" is generated. Here is part of the code. See "cmp x15, #0x8e".

.L1:
    ffffbb9c8250:       movz x15, #0x51c0
    ffffbb9c8254:       movk x15, #0xabb6, lsl #16
    ffffbb9c8258:       movk x15, #0xaaaa, lsl #32
    ffffbb9c825c:       ldr x8, [x15]
    ffffbb9c8260:       cbnz x8, #JIT$$exception_handler
    ffffbb9c8264:       ldr x15, [x27, #0x60]
    ffffbb9c8268:       cmp x15, #0x8e
    ffffbb9c826c:       b.eq #jit$$trace_exit_0
    ffffbb9c8270:       b.ne #jit$$trace_exit_1
    ffffbb9c8274:       ldr x0, [x27, #0x20]
    ffffbb9c8278:       ldrb w16, [x0, #0x30]
    ffffbb9c827c:       cbz w16, #jit$$trace_exit_2
    ffffbb9c8280:       add x0, x0, #0x28
    ffffbb9c8284:       add x1, x27, #0x60
    ffffbb9c8288:       adrp x8, #0xffffb4298000
    ffffbb9c828c:       add x8, x8, #0xa70
    ffffbb9c8290:       str x8, [x27]
    ffffbb9c8294:       bl #JIT$$assign_tmp
    ffffbb9c8298:       movz x15, #0x51c0
    ffffbb9c829c:       movk x15, #0xabb6, lsl #16
    ffffbb9c82a0:       movk x15, #0xaaaa, lsl #32
    ffffbb9c82a4:       ldr x8, [x15]
    ffffbb9c82a8:       cbnz x8, #JIT$$exception_handler
    ffffbb9c82ac:       movz x15, #0x160
    ffffbb9c82b0:       add x28, x28, x15


That is because "skip_comparison" is set as true for the second ZEND_IS_IDENTICAL, i.e. JIT is bypassed.
In details, in file zend_jit_trace.c, these code (https://github.com/php/php-src/blob/master/ext/opcache/jit/zend_jit_trace.c#L4762-L4812) is used to generate the JIT code for ZEND_IS_IDENTICAL. However, I found that variable "skip_comparison" is set as false for "$byte === 0x8E", but is set as true for "$byte === 0x8F".
I further looked at function zend_jit_may_skip_comparison(). I noticed that for $byte === 0x8F, this function returns "1" at https://github.com/php/php-src/blob/master/ext/opcache/jit/zend_jit_trace.c#L3568



In one word, I guess the root cause of this bug lies in how to determine "skip_comparison" for the case of two consecutive "ZEND_IS_IDENTICAL".
As I tried, if we always set variable "skip_comparison" as false, this bug can be gone.
 [2021-07-19 09:15 UTC] git@php.net
Automatic comment on behalf of dstogov
Revision: https://github.com/php/php-src/commit/c0e49328168bee315e72f21543b2ef2c01ad9169
Log: Fixed bug #81249 (Intermittent property assignment failure with JIT enabled)
 [2021-07-19 09:15 UTC] git@php.net
-Status: Verified +Status: Closed
 
PHP Copyright © 2001-2024 The PHP Group
All rights reserved.
Last updated: Fri Mar 29 06:01:29 2024 UTC