File: //usr/src/debug/lsphp80-8.0.30-2.el9.x86_64/ext/opcache/jit/zend_jit_x86.dasc
/*
* +----------------------------------------------------------------------+
* | Zend JIT |
* +----------------------------------------------------------------------+
* | Copyright (c) The PHP Group |
* +----------------------------------------------------------------------+
* | This source file is subject to version 3.01 of the PHP license, |
* | that is bundled with this package in the file LICENSE, and is |
* | available through the world-wide-web at the following url: |
* | http://www.php.net/license/3_01.txt |
* | If you did not receive a copy of the PHP license and are unable to |
* | obtain it through the world-wide-web, please send a note to |
* | [email protected] so we can mail you a copy immediately. |
* +----------------------------------------------------------------------+
* | Authors: Dmitry Stogov <[email protected]> |
* | Xinchen Hui <[email protected]> |
* +----------------------------------------------------------------------+
*/
|.if X64
|.arch x64
|.else
|.arch x86
|.endif
|.if X64WIN
|.define FP, r14
|.define IP, r15
|.define IPl, r15d
|.define RX, r15 // the same as VM IP reused as a general purpose reg
|.define CARG1, rcx // x64/POSIX C call arguments.
|.define CARG2, rdx
|.define CARG3, r8
|.define CARG4, r9
|.define CARG1d, ecx
|.define CARG2d, edx
|.define CARG3d, r8d
|.define CARG4d, r9d
|.define FCARG1a, CARG1 // Simulate x86 fastcall.
|.define FCARG2a, CARG2
|.define FCARG1d, CARG1d
|.define FCARG2d, CARG2d
|.define SPAD, 0x58 // padding for CPU stack alignment
|.define NR_SPAD, 0x58 // padding for CPU stack alignment
|.define T3, [r4+0x50] // Used to store old value of IP
|.define T2, [r4+0x48] // Used to store old value of FP
|.define T1, [r4+0x40]
|.define A6, [r4+0x28] // preallocated slot for 6-th argument
|.define A5, [r4+0x20] // preallocated slot for 5-th argument
|.elif X64
|.define FP, r14
|.define IP, r15
|.define IPl, r15d
|.define RX, r15 // the same as VM IP reused as a general purpose reg
|.define CARG1, rdi // x64/POSIX C call arguments.
|.define CARG2, rsi
|.define CARG3, rdx
|.define CARG4, rcx
|.define CARG5, r8
|.define CARG6, r9
|.define CARG1d, edi
|.define CARG2d, esi
|.define CARG3d, edx
|.define CARG4d, ecx
|.define CARG5d, r8d
|.define CARG6d, r9d
|.define FCARG1a, CARG1 // Simulate x86 fastcall.
|.define FCARG2a, CARG2
|.define FCARG1d, CARG1d
|.define FCARG2d, CARG2d
|.define SPAD, 0x18 // padding for CPU stack alignment
|.define NR_SPAD, 0x28 // padding for CPU stack alignment
|.define T3, [r4+0x20] // Used to store old value of IP (CALL VM only)
|.define T2, [r4+0x18] // Used to store old value of FP (CALL VM only)
|.define T1, [r4]
|.else
|.define FP, esi
|.define IP, edi
|.define IPl, edi
|.define RX, edi // the same as VM IP reused as a general purpose reg
|.define FCARG1a, ecx // x86 fastcall arguments.
|.define FCARG2a, edx
|.define FCARG1d, ecx
|.define FCARG2d, edx
|.define SPAD, 0x1c // padding for CPU stack alignment
|.define NR_SPAD, 0x1c // padding for CPU stack alignment
|.define T3, [r4+0x18] // Used to store old value of IP (CALL VM only)
|.define T2, [r4+0x14] // Used to store old value of FP (CALL VM only)
|.define T1, [r4]
|.define A4, [r4+0xC] // preallocated slots for arguments of "cdecl" functions (intersect with T1)
|.define A3, [r4+0x8]
|.define A2, [r4+0x4]
|.define A1, [r4]
|.endif
|.define HYBRID_SPAD, 16 // padding for stack alignment
#ifdef _WIN64
# define TMP_ZVAL_OFFSET 0x20
#else
# define TMP_ZVAL_OFFSET 0
#endif
#define DASM_ALIGNMENT 16
/* According to x86 and x86_64 ABI, CPU stack has to be 16 byte aligned to
* guarantee proper alignment of 128-bit SSE data allocated on stack.
* With broken alignment any execution of SSE code, including calls to
* memcpy() and others, may lead to crash.
*/
#include "Zend/zend_cpuinfo.h"
#include "jit/zend_jit_x86.h"
#ifdef HAVE_VALGRIND
# include <valgrind/valgrind.h>
#endif
/* The generated code may contain tautological comparisons, ignore them. */
#if defined(__clang__)
# pragma clang diagnostic push
# pragma clang diagnostic ignored "-Wtautological-compare"
# pragma clang diagnostic ignored "-Wstring-compare"
#endif
const char* zend_reg_name[] = {
#if defined(__x86_64__) || defined(_M_X64)
"rax", "rcx", "rdx", "rbx", "rsp", "rbp", "rsi", "rdi",
"r8", "r9", "r10", "r11", "r12", "r13", "r14", "r15",
"xmm0", "xmm1", "xmm2", "xmm3", "xmm4", "xmm5", "xmm6", "xmm7",
"xmm8", "xmm9", "xmm10", "xmm11", "xmm12", "xmm13", "xmm14", "xmm15"
#else
"rax", "rcx", "rdx", "rbx", "rsp", "rbp", "rsi", "rdi",
"xmm0", "xmm1", "xmm2", "xmm3", "xmm4", "xmm5", "xmm6", "xmm7"
#endif
};
#ifdef HAVE_GCC_GLOBAL_REGS
# define GCC_GLOBAL_REGS 1
#else
# define GCC_GLOBAL_REGS 0
#endif
#if ZTS
static size_t tsrm_ls_cache_tcb_offset = 0;
static size_t tsrm_tls_index;
static size_t tsrm_tls_offset;
#endif
/* By default avoid JITing inline handlers if it does not seem profitable due to lack of
* type information. Disabling this option allows testing some JIT handlers in the
* presence of try/catch blocks, which prevent SSA construction. */
#ifndef PROFITABILITY_CHECKS
# define PROFITABILITY_CHECKS 1
#endif
|.type EX, zend_execute_data, FP
|.type OP, zend_op
|.type ZVAL, zval
|.actionlist dasm_actions
|.globals zend_lb
static void* dasm_labels[zend_lb_MAX];
|.section code, cold_code, jmp_table
#define IS_32BIT(addr) (((uintptr_t)(addr)) <= 0x7fffffff)
#define IS_SIGNED_32BIT(val) ((((intptr_t)(val)) <= 0x7fffffff) && (((intptr_t)(val)) >= (-2147483647 - 1)))
#define BP_JIT_IS 6
#define CAN_USE_AVX() (JIT_G(opt_flags) & allowed_opt_flags & ZEND_JIT_CPU_AVX)
|.macro ADD_HYBRID_SPAD
||#ifndef ZEND_VM_HYBRID_JIT_RED_ZONE_SIZE
| add r4, HYBRID_SPAD
||#endif
|.endmacro
|.macro SUB_HYBRID_SPAD
||#ifndef ZEND_VM_HYBRID_JIT_RED_ZONE_SIZE
| sub r4, HYBRID_SPAD
||#endif
|.endmacro
|.macro LOAD_ADDR, reg, addr
| .if X64
|| if (IS_SIGNED_32BIT(addr)) {
| mov reg, ((ptrdiff_t)addr) // 0x48 0xc7 0xc0 <imm-32-bit>
|| } else {
| mov64 reg, ((ptrdiff_t)addr) // 0x48 0xb8 <imm-64-bit>
|| }
| .else
| mov reg, ((ptrdiff_t)addr)
| .endif
|.endmacro
|.macro LOAD_TSRM_CACHE, reg
| .if X64WIN
| gs
| mov reg, aword [0x58]
| mov reg, aword [reg+tsrm_tls_index]
| mov reg, aword [reg+tsrm_tls_offset]
| .elif WIN
| fs
| mov reg, aword [0x2c]
| mov reg, aword [reg+tsrm_tls_index]
| mov reg, aword [reg+tsrm_tls_offset]
| .elif X64APPLE
| gs
|| if (tsrm_ls_cache_tcb_offset) {
| mov reg, aword [tsrm_ls_cache_tcb_offset]
|| } else {
| mov reg, aword [tsrm_tls_index]
| mov reg, aword [reg+tsrm_tls_offset]
|| }
| .elif X64
| fs
|| if (tsrm_ls_cache_tcb_offset) {
| mov reg, aword [tsrm_ls_cache_tcb_offset]
|| } else {
| mov reg, [0x8]
| mov reg, aword [reg+tsrm_tls_index]
| mov reg, aword [reg+tsrm_tls_offset]
|| }
| .else
| gs
|| if (tsrm_ls_cache_tcb_offset) {
| mov reg, aword [tsrm_ls_cache_tcb_offset]
|| } else {
| mov reg, [0x4]
| mov reg, aword [reg+tsrm_tls_index]
| mov reg, aword [reg+tsrm_tls_offset]
|| }
| .endif
|.endmacro
|.macro LOAD_ADDR_ZTS, reg, struct, field
| .if ZTS
| LOAD_TSRM_CACHE reg
| lea reg, aword [reg + (struct.._offset + offsetof(zend_..struct, field))]
| .else
| LOAD_ADDR reg, &struct.field
| .endif
|.endmacro
|.macro ADDR_OP1, addr_ins, addr, tmp_reg
| .if X64
|| if (IS_SIGNED_32BIT(addr)) {
| addr_ins ((ptrdiff_t)addr)
|| } else {
| mov64 tmp_reg, ((ptrdiff_t)addr)
| addr_ins tmp_reg
|| }
| .else
| addr_ins ((ptrdiff_t)addr)
| .endif
|.endmacro
|.macro ADDR_OP2_2, addr_ins, op1, addr, tmp_reg
| .if X64
|| if (IS_SIGNED_32BIT(addr)) {
| addr_ins op1, ((ptrdiff_t)addr)
|| } else {
| mov64 tmp_reg, ((ptrdiff_t)addr)
| addr_ins op1, tmp_reg
|| }
| .else
| addr_ins op1, ((ptrdiff_t)addr)
| .endif
|.endmacro
|.macro PUSH_ADDR, addr, tmp_reg
| ADDR_OP1 push, addr, tmp_reg
|.endmacro
|.macro PUSH_ADDR_ZTS, struct, field, tmp_reg
| .if ZTS
| LOAD_TSRM_CACHE tmp_reg
| lea tmp_reg, aword [tmp_reg + (struct.._offset + offsetof(zend_..struct, field))]
| push tmp_reg
| .else
| ADDR_OP1 push, &struct.field, tmp_reg
| .endif
|.endmacro
|.macro MEM_OP1, mem_ins, prefix, addr, tmp_reg
| .if X64
|| if (IS_SIGNED_32BIT(addr)) {
| mem_ins prefix [addr]
|| } else {
| mov64 tmp_reg, ((ptrdiff_t)addr)
| mem_ins prefix [tmp_reg]
|| }
| .else
| mem_ins prefix [addr]
| .endif
|.endmacro
|.macro MEM_OP2_1, mem_ins, prefix, addr, op2, tmp_reg
| .if X64
|| if (IS_SIGNED_32BIT(addr)) {
| mem_ins prefix [addr], op2
|| } else {
| mov64 tmp_reg, ((ptrdiff_t)addr)
| mem_ins prefix [tmp_reg], op2
|| }
| .else
| mem_ins prefix [addr], op2
| .endif
|.endmacro
|.macro MEM_OP2_2, mem_ins, op1, prefix, addr, tmp_reg
| .if X64
|| if (IS_SIGNED_32BIT(addr)) {
| mem_ins op1, prefix [addr]
|| } else {
| mov64 tmp_reg, ((ptrdiff_t)addr)
| mem_ins op1, prefix [tmp_reg]
|| }
| .else
| mem_ins op1, prefix [addr]
| .endif
|.endmacro
|.macro MEM_OP2_1_ZTS, mem_ins, prefix, struct, field, op2, tmp_reg
| .if ZTS
| LOAD_TSRM_CACHE tmp_reg
| mem_ins prefix [tmp_reg+(struct.._offset+offsetof(zend_..struct, field))], op2
| .else
| MEM_OP2_1 mem_ins, prefix, &struct.field, op2, tmp_reg
| .endif
|.endmacro
|.macro MEM_OP2_2_ZTS, mem_ins, op1, prefix, struct, field, tmp_reg
| .if ZTS
| LOAD_TSRM_CACHE tmp_reg
| mem_ins op1, prefix [tmp_reg+(struct.._offset+offsetof(zend_..struct, field))]
| .else
| MEM_OP2_2 mem_ins, op1, prefix, &struct.field, tmp_reg
| .endif
|.endmacro
|.macro MEM_OP3_3, mem_ins, op1, op2, prefix, addr, tmp_reg
| .if X64
|| if (IS_SIGNED_32BIT(addr)) {
| mem_ins op1, op2, prefix [addr]
|| } else {
| mov64 tmp_reg, ((ptrdiff_t)addr)
| mem_ins op1, op2, prefix [tmp_reg]
|| }
| .else
| mem_ins op1, op2, prefix [addr]
| .endif
|.endmacro
|.macro LOAD_BASE_ADDR, reg, base, offset
|| if (offset) {
| lea reg, qword [Ra(base)+offset]
|| } else {
| mov reg, Ra(base)
|| }
|.endmacro
|.macro PUSH_BASE_ADDR, base, offset, tmp_reg
|| if (offset) {
| lea tmp_reg, qword [Ra(base)+offset]
| push tmp_reg
|| } else {
| push Ra(base)
|| }
|.endmacro
|.macro EXT_CALL, func, tmp_reg
| .if X64
|| if (IS_32BIT(dasm_end) && IS_32BIT(func)) {
| call qword &func
|| } else {
| LOAD_ADDR tmp_reg, func
| call tmp_reg
|| }
| .else
| call dword &func
| .endif
|.endmacro
|.macro EXT_JMP, func, tmp_reg
| .if X64
|| if (IS_32BIT(dasm_end) && IS_32BIT(func)) {
| jmp qword &func
|| } else {
| LOAD_ADDR tmp_reg, func
| jmp tmp_reg
|| }
| .else
| jmp dword &func
| .endif
|.endmacro
|.macro SAVE_IP
|| if (GCC_GLOBAL_REGS) {
| mov aword EX->opline, IP
|| }
|.endmacro
|.macro LOAD_IP
|| if (GCC_GLOBAL_REGS) {
| mov IP, aword EX->opline
|| }
|.endmacro
|.macro LOAD_IP_ADDR, addr
|| if (GCC_GLOBAL_REGS) {
| LOAD_ADDR IP, addr
|| } else {
| ADDR_OP2_2 mov, aword EX->opline, addr, RX
|| }
|.endmacro
|.macro LOAD_IP_ADDR_ZTS, struct, field
| .if ZTS
|| if (GCC_GLOBAL_REGS) {
| LOAD_TSRM_CACHE IP
| mov IP, aword [IP + (struct.._offset + offsetof(zend_..struct, field))]
|| } else {
| LOAD_TSRM_CACHE RX
| lea RX, aword [RX + (struct.._offset + offsetof(zend_..struct, field))]
| mov aword EX->opline, RX
|| }
| .else
| LOAD_IP_ADDR &struct.field
| .endif
|.endmacro
|.macro GET_IP, reg
|| if (GCC_GLOBAL_REGS) {
| mov reg, IP
|| } else {
| mov reg, aword EX->opline
|| }
|.endmacro
|.macro ADD_IP, val
|| if (GCC_GLOBAL_REGS) {
| add IP, val
|| } else {
| add aword EX->opline, val
|| }
|.endmacro
|.macro JMP_IP
|| if (GCC_GLOBAL_REGS) {
| jmp aword [IP]
|| } else {
| mov r0, aword EX:FCARG1a->opline
| jmp aword [r0]
|| }
|.endmacro
/* In 64-bit build we compare only low 32-bits.
* x86_64 cmp instruction doesn't support immediate 64-bit operand, and full
* comparison would require an additional load of 64-bit address into register.
* This is not a problem at all, while JIT buffer size is less than 4GB.
*/
|.macro CMP_IP, addr
|| if (GCC_GLOBAL_REGS) {
| cmp IPl, addr
|| } else {
| cmp dword EX->opline, addr
|| }
|.endmacro
|.macro LOAD_ZVAL_ADDR, reg, addr
|| if (Z_MODE(addr) == IS_CONST_ZVAL) {
| LOAD_ADDR reg, Z_ZV(addr)
|| } else if (Z_MODE(addr) == IS_MEM_ZVAL) {
| LOAD_BASE_ADDR reg, Z_REG(addr), Z_OFFSET(addr)
|| } else {
|| ZEND_UNREACHABLE();
|| }
|.endmacro
|.macro PUSH_ZVAL_ADDR, addr, tmp_reg
|| if (Z_MODE(addr) == IS_CONST_ZVAL) {
| PUSH_ADDR Z_ZV(addr), tmp_reg
|| } else if (Z_MODE(addr) == IS_MEM_ZVAL) {
| PUSH_BASE_ADDR Z_REG(addr), Z_OFFSET(addr), tmp_reg
|| } else {
|| ZEND_UNREACHABLE();
|| }
|.endmacro
|.macro GET_Z_TYPE_INFO, reg, zv
| mov reg, dword [zv+offsetof(zval,u1.type_info)]
|.endmacro
|.macro SET_Z_TYPE_INFO, zv, type
| mov dword [zv+offsetof(zval,u1.type_info)], type
|.endmacro
|.macro GET_ZVAL_TYPE, reg, addr
|| ZEND_ASSERT(Z_MODE(addr) == IS_MEM_ZVAL);
| mov reg, byte [Ra(Z_REG(addr))+Z_OFFSET(addr)+offsetof(zval,u1.v.type)]
|.endmacro
|.macro GET_ZVAL_TYPE_INFO, reg, addr
|| ZEND_ASSERT(Z_MODE(addr) == IS_MEM_ZVAL);
| mov reg, dword [Ra(Z_REG(addr))+Z_OFFSET(addr)+offsetof(zval,u1.type_info)]
|.endmacro
|.macro SET_ZVAL_TYPE_INFO, addr, type
|| ZEND_ASSERT(Z_MODE(addr) == IS_MEM_ZVAL);
| mov dword [Ra(Z_REG(addr))+Z_OFFSET(addr)+offsetof(zval,u1.type_info)], type
|.endmacro
|.macro GET_Z_PTR, reg, zv
| mov reg, aword [zv]
|.endmacro
|.macro SET_Z_PTR, zv, val
| mov aword [zv], val
|.endmacro
|.macro GET_Z_W2, reg, zv
| mov reg, dword [zv+4]
|.endmacro
|.macro SET_Z_W2, zv, reg
| mov dword [zv+4], reg
|.endmacro
|.macro GET_ZVAL_PTR, reg, addr
|| ZEND_ASSERT(Z_MODE(addr) == IS_MEM_ZVAL);
| mov reg, aword [Ra(Z_REG(addr))+Z_OFFSET(addr)]
|.endmacro
|.macro SET_ZVAL_PTR, addr, val
|| ZEND_ASSERT(Z_MODE(addr) == IS_MEM_ZVAL);
| mov aword [Ra(Z_REG(addr))+Z_OFFSET(addr)], val
|.endmacro
|.macro GET_ZVAL_W2, reg, addr
|| ZEND_ASSERT(Z_MODE(addr) == IS_MEM_ZVAL);
| mov reg, dword [Ra(Z_REG(addr))+Z_OFFSET(addr)+4]
|.endmacro
|.macro SET_ZVAL_W2, addr, val
|| ZEND_ASSERT(Z_MODE(addr) == IS_MEM_ZVAL);
| mov dword [Ra(Z_REG(addr))+Z_OFFSET(addr)+4], val
|.endmacro
|.macro UNDEF_OPLINE_RESULT
| mov r0, EX->opline
| mov eax, dword OP:r0->result.var
| SET_Z_TYPE_INFO FP + r0, IS_UNDEF
|.endmacro
|.macro UNDEF_OPLINE_RESULT_IF_USED
| test byte OP:RX->result_type, (IS_TMP_VAR|IS_VAR)
| jz >1
| mov eax, dword OP:RX->result.var
| SET_Z_TYPE_INFO FP + r0, IS_UNDEF
|1:
|.endmacro
|.macro SSE_AVX_INS, sse_ins, avx_ins, op1, op2
|| if (CAN_USE_AVX()) {
| avx_ins op1, op2
|| } else {
| sse_ins op1, op2
|| }
|.endmacro
|.macro SSE_OP, sse_ins, reg, addr, tmp_reg
|| if (Z_MODE(addr) == IS_CONST_ZVAL) {
| MEM_OP2_2 sse_ins, xmm(reg-ZREG_XMM0), qword, Z_ZV(addr), tmp_reg
|| } else if (Z_MODE(addr) == IS_MEM_ZVAL) {
| sse_ins xmm(reg-ZREG_XMM0), qword [Ra(Z_REG(addr))+Z_OFFSET(addr)]
|| } else if (Z_MODE(addr) == IS_REG) {
| sse_ins xmm(reg-ZREG_XMM0), xmm(Z_REG(addr)-ZREG_XMM0)
|| } else {
|| ZEND_UNREACHABLE();
|| }
|.endmacro
|.macro SSE_AVX_OP, sse_ins, avx_ins, reg, addr
|| if (Z_MODE(addr) == IS_CONST_ZVAL) {
| .if X64
|| if (IS_SIGNED_32BIT(Z_ZV(addr))) {
| SSE_AVX_INS sse_ins, avx_ins, xmm(reg-ZREG_XMM0), qword [Z_ZV(addr)]
|| } else {
| LOAD_ADDR r0, Z_ZV(addr)
| SSE_AVX_INS sse_ins, avx_ins, xmm(reg-ZREG_XMM0), qword [r0]
|| }
| .else
| SSE_AVX_INS sse_ins, avx_ins, xmm(reg-ZREG_XMM0), qword [Z_ZV(addr)]
| .endif
|| } else if (Z_MODE(addr) == IS_MEM_ZVAL) {
| SSE_AVX_INS sse_ins, avx_ins, xmm(reg-ZREG_XMM0), qword [Ra(Z_REG(addr))+Z_OFFSET(addr)]
|| } else if (Z_MODE(addr) == IS_REG) {
| SSE_AVX_INS sse_ins, avx_ins, xmm(reg-ZREG_XMM0), xmm(Z_REG(addr)-ZREG_XMM0)
|| } else {
|| ZEND_UNREACHABLE();
|| }
|.endmacro
|.macro SSE_GET_LONG, reg, lval, tmp_reg
|| if (lval == 0) {
|| if (CAN_USE_AVX()) {
| vxorps xmm(reg-ZREG_XMM0), xmm(reg-ZREG_XMM0), xmm(reg-ZREG_XMM0)
|| } else {
| xorps xmm(reg-ZREG_XMM0), xmm(reg-ZREG_XMM0)
|| }
|| } else {
|.if X64
|| if (!IS_SIGNED_32BIT(lval)) {
| mov64 Ra(tmp_reg), lval
|| } else {
| mov Ra(tmp_reg), lval
|| }
|.else
| mov Ra(tmp_reg), lval
|.endif
|| if (CAN_USE_AVX()) {
| vxorps xmm(reg-ZREG_XMM0), xmm(reg-ZREG_XMM0), xmm(reg-ZREG_XMM0)
| vcvtsi2sd, xmm(reg-ZREG_XMM0), xmm(reg-ZREG_XMM0), Ra(tmp_reg)
|| } else {
| xorps xmm(reg-ZREG_XMM0), xmm(reg-ZREG_XMM0)
| cvtsi2sd, xmm(reg-ZREG_XMM0), Ra(tmp_reg)
|| }
|| }
|.endmacro
|.macro SSE_GET_ZVAL_LVAL, reg, addr, tmp_reg
|| if (Z_MODE(addr) == IS_CONST_ZVAL) {
| SSE_GET_LONG reg, Z_LVAL_P(Z_ZV(addr)), tmp_reg
|| } else if (Z_MODE(addr) == IS_MEM_ZVAL) {
|| if (CAN_USE_AVX()) {
| vxorps xmm(reg-ZREG_XMM0), xmm(reg-ZREG_XMM0), xmm(reg-ZREG_XMM0)
| vcvtsi2sd xmm(reg-ZREG_XMM0), xmm(reg-ZREG_XMM0), aword [Ra(Z_REG(addr))+Z_OFFSET(addr)]
|| } else {
| xorps xmm(reg-ZREG_XMM0), xmm(reg-ZREG_XMM0)
| cvtsi2sd xmm(reg-ZREG_XMM0), aword [Ra(Z_REG(addr))+Z_OFFSET(addr)]
|| }
|| } else if (Z_MODE(addr) == IS_REG) {
|| if (CAN_USE_AVX()) {
| vxorps xmm(reg-ZREG_XMM0), xmm(reg-ZREG_XMM0), xmm(reg-ZREG_XMM0)
| vcvtsi2sd xmm(reg-ZREG_XMM0), xmm(reg-ZREG_XMM0), Ra(Z_REG(addr))
|| } else {
| xorps xmm(reg-ZREG_XMM0), xmm(reg-ZREG_XMM0)
| cvtsi2sd xmm(reg-ZREG_XMM0), Ra(Z_REG(addr))
|| }
|| } else {
|| ZEND_UNREACHABLE();
|| }
|.endmacro
|.macro SSE_GET_ZVAL_DVAL, reg, addr
|| if (Z_MODE(addr) != IS_REG || reg != Z_REG(addr)) {
|| if (Z_MODE(addr) == IS_CONST_ZVAL) {
| .if X64
|| if (IS_SIGNED_32BIT(Z_ZV(addr))) {
| SSE_AVX_INS movsd, vmovsd, xmm(reg-ZREG_XMM0), qword [Z_ZV(addr)]
|| } else {
| LOAD_ADDR r0, Z_ZV(addr)
| SSE_AVX_INS movsd, vmovsd, xmm(reg-ZREG_XMM0), qword [r0]
|| }
| .else
| SSE_AVX_INS movsd, vmovsd, xmm(reg-ZREG_XMM0), qword [Z_ZV(addr)]
| .endif
|| } else if (Z_MODE(addr) == IS_MEM_ZVAL) {
| SSE_AVX_INS movsd, vmovsd, xmm(reg-ZREG_XMM0), qword [Ra(Z_REG(addr))+Z_OFFSET(addr)]
|| } else if (Z_MODE(addr) == IS_REG) {
| SSE_AVX_INS movaps, vmovaps, xmm(reg-ZREG_XMM0), xmm(Z_REG(addr)-ZREG_XMM0)
|| } else {
|| ZEND_UNREACHABLE();
|| }
|| }
|.endmacro
|.macro SSE_MATH, opcode, reg, addr, tmp_reg
|| switch (opcode) {
|| case ZEND_ADD:
| SSE_OP addsd, reg, addr, tmp_reg
|| break;
|| case ZEND_SUB:
| SSE_OP subsd, reg, addr, tmp_reg
|| break;
|| case ZEND_MUL:
| SSE_OP mulsd, reg, addr, tmp_reg
|| break;
|| case ZEND_DIV:
| SSE_OP divsd, reg, addr, tmp_reg
|| break;
|| }
|.endmacro
|.macro SSE_MATH_REG, opcode, dst_reg, src_reg
|| switch (opcode) {
|| case ZEND_ADD:
| addsd xmm(dst_reg-ZREG_XMM0), xmm(src_reg-ZREG_XMM0)
|| break;
|| case ZEND_SUB:
| subsd xmm(dst_reg-ZREG_XMM0), xmm(src_reg-ZREG_XMM0)
|| break;
|| case ZEND_MUL:
| mulsd xmm(dst_reg-ZREG_XMM0), xmm(src_reg-ZREG_XMM0)
|| break;
|| case ZEND_DIV:
| divsd xmm(dst_reg-ZREG_XMM0), xmm(src_reg-ZREG_XMM0)
|| break;
|| }
|.endmacro
|.macro SSE_SET_ZVAL_DVAL, addr, reg
|| if (Z_MODE(addr) == IS_REG) {
|| if (reg != Z_REG(addr)) {
| SSE_AVX_INS movaps, vmovaps, xmm(Z_REG(addr)-ZREG_XMM0), xmm(reg-ZREG_XMM0)
|| }
|| } else {
|| ZEND_ASSERT(Z_MODE(addr) == IS_MEM_ZVAL);
| SSE_AVX_INS movsd, vmovsd, qword [Ra(Z_REG(addr))+Z_OFFSET(addr)], xmm(reg-ZREG_XMM0)
|| }
|.endmacro
|.macro AVX_OP, avx_ins, reg, op1_reg, addr, tmp_reg
|| if (Z_MODE(addr) == IS_CONST_ZVAL) {
| MEM_OP3_3 avx_ins, xmm(reg-ZREG_XMM0), xmm(op1_reg-ZREG_XMM0), qword, Z_ZV(addr), tmp_reg
|| } else if (Z_MODE(addr) == IS_MEM_ZVAL) {
| avx_ins xmm(reg-ZREG_XMM0), xmm(op1_reg-ZREG_XMM0), qword [Ra(Z_REG(addr))+Z_OFFSET(addr)]
|| } else if (Z_MODE(addr) == IS_REG) {
| avx_ins xmm(reg-ZREG_XMM0), xmm(op1_reg-ZREG_XMM0), xmm(Z_REG(addr)-ZREG_XMM0)
|| } else {
|| ZEND_UNREACHABLE();
|| }
|.endmacro
|.macro AVX_MATH, opcode, reg, op1_reg, addr, tmp_reg
|| switch (opcode) {
|| case ZEND_ADD:
| AVX_OP vaddsd, reg, op1_reg, addr, tmp_reg
|| break;
|| case ZEND_SUB:
| AVX_OP vsubsd, reg, op1_reg, addr, tmp_reg
|| break;
|| case ZEND_MUL:
| AVX_OP vmulsd, reg, op1_reg, addr, tmp_reg
|| break;
|| case ZEND_DIV:
| AVX_OP vdivsd, reg, op1_reg, addr, tmp_reg
|| break;
|| }
|.endmacro
|.macro AVX_MATH_REG, opcode, dst_reg, op1_reg, src_reg
|| switch (opcode) {
|| case ZEND_ADD:
| vaddsd xmm(dst_reg-ZREG_XMM0), xmm(op1_reg-ZREG_XMM0), xmm(src_reg-ZREG_XMM0)
|| break;
|| case ZEND_SUB:
| vsubsd xmm(dst_reg-ZREG_XMM0), xmm(op1_reg-ZREG_XMM0), xmm(src_reg-ZREG_XMM0)
|| break;
|| case ZEND_MUL:
| vmulsd xmm(dst_reg-ZREG_XMM0), xmm(op1_reg-ZREG_XMM0), xmm(src_reg-ZREG_XMM0)
|| break;
|| case ZEND_DIV:
| vdivsd xmm(dst_reg-ZREG_XMM0), xmm(op1_reg-ZREG_XMM0), xmm(src_reg-ZREG_XMM0)
|| break;
|| }
|.endmacro
|.macro LONG_OP, long_ins, reg, addr, tmp_reg
|| if (Z_MODE(addr) == IS_CONST_ZVAL) {
| .if X64
|| if (!IS_SIGNED_32BIT(Z_LVAL_P(Z_ZV(addr)))) {
| mov64 tmp_reg, Z_LVAL_P(Z_ZV(addr))
| long_ins Ra(reg), tmp_reg
|| } else {
| long_ins Ra(reg), Z_LVAL_P(Z_ZV(addr))
|| }
| .else
| long_ins Ra(reg), Z_LVAL_P(Z_ZV(addr))
| .endif
|| } else if (Z_MODE(addr) == IS_MEM_ZVAL) {
| long_ins Ra(reg), aword [Ra(Z_REG(addr))+Z_OFFSET(addr)]
|| } else if (Z_MODE(addr) == IS_REG) {
| long_ins Ra(reg), Ra(Z_REG(addr))
|| } else {
|| ZEND_UNREACHABLE();
|| }
|.endmacro
|.macro LONG_OP_WITH_32BIT_CONST, long_ins, op1_addr, lval
|| if (Z_MODE(op1_addr) == IS_MEM_ZVAL) {
| long_ins aword [Ra(Z_REG(op1_addr))+Z_OFFSET(op1_addr)], lval
|| } else if (Z_MODE(op1_addr) == IS_REG) {
| long_ins Ra(Z_REG(op1_addr)), lval
|| } else {
|| ZEND_UNREACHABLE();
|| }
|.endmacro
|.macro LONG_OP_WITH_CONST, long_ins, op1_addr, lval
|| if (Z_MODE(op1_addr) == IS_MEM_ZVAL) {
| .if X64
|| if (!IS_SIGNED_32BIT(lval)) {
| mov64 r0, lval
| long_ins aword [Ra(Z_REG(op1_addr))+Z_OFFSET(op1_addr)], r0
|| } else {
| long_ins aword [Ra(Z_REG(op1_addr))+Z_OFFSET(op1_addr)], lval
|| }
| .else
| long_ins aword [Ra(Z_REG(op1_addr))+Z_OFFSET(op1_addr)], lval
| .endif
|| } else if (Z_MODE(op1_addr) == IS_REG) {
| .if X64
|| if (!IS_SIGNED_32BIT(lval)) {
| mov64 r0, lval
| long_ins Ra(Z_REG(op1_addr)), r0
|| } else {
| long_ins Ra(Z_REG(op1_addr)), lval
|| }
| .else
| long_ins Ra(Z_REG(op1_addr)), lval
| .endif
|| } else {
|| ZEND_UNREACHABLE();
|| }
|.endmacro
|.macro GET_ZVAL_LVAL, reg, addr
|| if (Z_MODE(addr) == IS_CONST_ZVAL) {
|| if (Z_LVAL_P(Z_ZV(addr)) == 0) {
| xor Ra(reg), Ra(reg)
|| } else {
| .if X64
|| if (!IS_SIGNED_32BIT(Z_LVAL_P(Z_ZV(addr)))) {
| mov64 Ra(reg), Z_LVAL_P(Z_ZV(addr))
|| } else {
| mov Ra(reg), Z_LVAL_P(Z_ZV(addr))
|| }
| .else
| mov Ra(reg), Z_LVAL_P(Z_ZV(addr))
| .endif
|| }
|| } else if (Z_MODE(addr) == IS_MEM_ZVAL) {
| mov Ra(reg), aword [Ra(Z_REG(addr))+Z_OFFSET(addr)]
|| } else if (Z_MODE(addr) == IS_REG) {
|| if (reg != Z_REG(addr)) {
| mov Ra(reg), Ra(Z_REG(addr))
|| }
|| } else {
|| ZEND_UNREACHABLE();
|| }
|.endmacro
|.macro LONG_MATH, opcode, reg, addr, tmp_reg
|| switch (opcode) {
|| case ZEND_ADD:
| LONG_OP add, reg, addr, Ra(tmp_reg)
|| break;
|| case ZEND_SUB:
| LONG_OP sub, reg, addr, Ra(tmp_reg)
|| break;
|| case ZEND_MUL:
| LONG_OP imul, reg, addr, Ra(tmp_reg)
|| break;
|| case ZEND_BW_OR:
| LONG_OP or, reg, addr, Ra(tmp_reg)
|| break;
|| case ZEND_BW_AND:
| LONG_OP and, reg, addr, Ra(tmp_reg)
|| break;
|| case ZEND_BW_XOR:
| LONG_OP xor, reg, addr, Ra(tmp_reg)
|| break;
|| default:
|| ZEND_UNREACHABLE();
|| }
|.endmacro
|.macro LONG_MATH_REG, opcode, dst_reg, src_reg
|| switch (opcode) {
|| case ZEND_ADD:
| add dst_reg, src_reg
|| break;
|| case ZEND_SUB:
| sub dst_reg, src_reg
|| break;
|| case ZEND_MUL:
| imul dst_reg, src_reg
|| break;
|| case ZEND_BW_OR:
| or dst_reg, src_reg
|| break;
|| case ZEND_BW_AND:
| and dst_reg, src_reg
|| break;
|| case ZEND_BW_XOR:
| xor dst_reg, src_reg
|| break;
|| default:
|| ZEND_UNREACHABLE();
|| }
|.endmacro
|.macro SET_ZVAL_LVAL, addr, lval
|| if (Z_MODE(addr) == IS_REG) {
| mov Ra(Z_REG(addr)), lval
|| } else {
|| ZEND_ASSERT(Z_MODE(addr) == IS_MEM_ZVAL);
| mov aword [Ra(Z_REG(addr))+Z_OFFSET(addr)], lval
|| }
|.endmacro
|.macro ZVAL_COPY_CONST, dst_addr, dst_info, dst_def_info, zv, tmp_reg
|| if (Z_TYPE_P(zv) > IS_TRUE) {
|| if (Z_TYPE_P(zv) == IS_DOUBLE) {
|| zend_reg dst_reg = (Z_MODE(dst_addr) == IS_REG) ? Z_REG(dst_addr) : ZREG_XMM0;
|| if (Z_DVAL_P(zv) == 0.0 && !is_signed(Z_DVAL_P(zv))) {
|| if (CAN_USE_AVX()) {
| vxorps xmm(dst_reg-ZREG_XMM0), xmm(dst_reg-ZREG_XMM0), xmm(dst_reg-ZREG_XMM0)
|| } else {
| xorps xmm(dst_reg-ZREG_XMM0), xmm(dst_reg-ZREG_XMM0)
|| }
| .if X64
|| } else if (!IS_SIGNED_32BIT(zv)) {
| mov64 Ra(tmp_reg), ((uintptr_t)zv)
| SSE_AVX_INS movsd, vmovsd, xmm(dst_reg-ZREG_XMM0), qword [Ra(tmp_reg)]
| .endif
|| } else {
| SSE_AVX_INS movsd, vmovsd, xmm(dst_reg-ZREG_XMM0), qword [((uint32_t)(uintptr_t)zv)]
|| }
| SSE_SET_ZVAL_DVAL dst_addr, dst_reg
|| } else if (Z_TYPE_P(zv) == IS_LONG && dst_def_info == MAY_BE_DOUBLE) {
|| zend_reg dst_reg = (Z_MODE(dst_addr) == IS_REG) ? Z_REG(dst_addr) : ZREG_XMM0;
| SSE_GET_LONG dst_reg, Z_LVAL_P(zv), ZREG_R0
| SSE_SET_ZVAL_DVAL dst_addr, dst_reg
|| } else if (Z_LVAL_P(zv) == 0 && Z_MODE(dst_addr) == IS_REG) {
| xor Ra(Z_REG(dst_addr)), Ra(Z_REG(dst_addr))
|| } else {
| .if X64
|| if (!IS_SIGNED_32BIT(Z_LVAL_P(zv))) {
|| if (Z_MODE(dst_addr) == IS_REG) {
| mov64 Ra(Z_REG(dst_addr)), ((uintptr_t)Z_LVAL_P(zv))
|| } else {
| mov64 Ra(tmp_reg), ((uintptr_t)Z_LVAL_P(zv))
| SET_ZVAL_LVAL dst_addr, Ra(tmp_reg)
|| }
|| } else {
| SET_ZVAL_LVAL dst_addr, Z_LVAL_P(zv)
|| }
| .else
| SET_ZVAL_LVAL dst_addr, Z_LVAL_P(zv)
| .endif
|| }
|| }
|| if (Z_MODE(dst_addr) == IS_MEM_ZVAL) {
|| if (dst_def_info == MAY_BE_DOUBLE) {
|| if ((dst_info & (MAY_BE_ANY|MAY_BE_UNDEF|MAY_BE_GUARD)) != MAY_BE_DOUBLE) {
| SET_ZVAL_TYPE_INFO dst_addr, IS_DOUBLE
|| }
|| } else if (((dst_info & (MAY_BE_ANY|MAY_BE_UNDEF|MAY_BE_GUARD)) != (1<<Z_TYPE_P(zv))) || (dst_info & (MAY_BE_STRING|MAY_BE_ARRAY)) != 0) {
| SET_ZVAL_TYPE_INFO dst_addr, Z_TYPE_INFO_P(zv)
|| }
|| }
|.endmacro
|.macro ZVAL_COPY_CONST_2, dst_addr, res_addr, dst_info, dst_def_info, zv, tmp_reg
|| if (Z_TYPE_P(zv) > IS_TRUE) {
|| if (Z_TYPE_P(zv) == IS_DOUBLE) {
|| zend_reg dst_reg = (Z_MODE(dst_addr) == IS_REG) ?
|| Z_REG(dst_addr) : ((Z_MODE(res_addr) == IS_REG) ? Z_REG(res_addr) : ZREG_XMM0);
|| if (Z_DVAL_P(zv) == 0.0 && !is_signed(Z_DVAL_P(zv))) {
|| if (CAN_USE_AVX()) {
| vxorps xmm(dst_reg-ZREG_XMM0), xmm(dst_reg-ZREG_XMM0), xmm(dst_reg-ZREG_XMM0)
|| } else {
| xorps xmm(dst_reg-ZREG_XMM0), xmm(dst_reg-ZREG_XMM0)
|| }
| .if X64
|| } else if (!IS_SIGNED_32BIT(zv)) {
| mov64 Ra(tmp_reg), ((uintptr_t)zv)
| SSE_AVX_INS movsd, vmovsd, xmm(dst_reg-ZREG_XMM0), qword [Ra(tmp_reg)]
| .endif
|| } else {
| SSE_AVX_INS movsd, vmovsd, xmm(dst_reg-ZREG_XMM0), qword [((uint32_t)(uintptr_t)zv)]
|| }
| SSE_SET_ZVAL_DVAL dst_addr, ZREG_XMM0
| SSE_SET_ZVAL_DVAL res_addr, ZREG_XMM0
|| } else if (Z_TYPE_P(zv) == IS_LONG && dst_def_info == MAY_BE_DOUBLE) {
|| if (Z_MODE(dst_addr) == IS_REG) {
| SSE_GET_LONG Z_REG(dst_addr), Z_LVAL_P(zv), ZREG_R0
| SSE_SET_ZVAL_DVAL res_addr, Z_REG(dst_addr)
|| } else if (Z_MODE(res_addr) == IS_REG) {
| SSE_GET_LONG Z_REG(res_addr), Z_LVAL_P(zv), ZREG_R0
| SSE_SET_ZVAL_DVAL dst_addr, Z_REG(res_addr)
|| } else {
| SSE_GET_LONG ZREG_XMM0, Z_LVAL_P(zv), ZREG_R0
| SSE_SET_ZVAL_DVAL dst_addr, ZREG_XMM0
| SSE_SET_ZVAL_DVAL res_addr, ZREG_XMM0
|| }
|| } else if (Z_LVAL_P(zv) == 0 && (Z_MODE(dst_addr) == IS_REG || Z_MODE(res_addr) == IS_REG)) {
|| if (Z_MODE(dst_addr) == IS_REG) {
| xor Ra(Z_REG(dst_addr)), Ra(Z_REG(dst_addr))
| SET_ZVAL_LVAL res_addr, Ra(Z_REG(dst_addr))
|| } else {
| xor Ra(Z_REG(res_addr)), Ra(Z_REG(res_addr))
| SET_ZVAL_LVAL dst_addr, Ra(Z_REG(res_addr))
|| }
|| } else {
| .if X64
|| if (!IS_SIGNED_32BIT(Z_LVAL_P(zv))) {
|| if (Z_MODE(dst_addr) == IS_REG) {
| mov64 Ra(Z_REG(dst_addr)), ((uintptr_t)Z_LVAL_P(zv))
| SET_ZVAL_LVAL res_addr, Ra(Z_REG(dst_addr))
|| } else if (Z_MODE(res_addr) == IS_REG) {
| mov64 Ra(Z_REG(res_addr)), ((uintptr_t)Z_LVAL_P(zv))
| SET_ZVAL_LVAL dst_addr, Ra(Z_REG(res_addr))
|| } else {
| mov64 Ra(tmp_reg), ((uintptr_t)Z_LVAL_P(zv))
| SET_ZVAL_LVAL dst_addr, Ra(tmp_reg)
| SET_ZVAL_LVAL res_addr, Ra(tmp_reg)
|| }
|| } else if (Z_MODE(dst_addr) == IS_REG) {
| SET_ZVAL_LVAL dst_addr, Z_LVAL_P(zv)
| SET_ZVAL_LVAL res_addr, Ra(Z_REG(dst_addr))
|| } else if (Z_MODE(res_addr) == IS_REG) {
| SET_ZVAL_LVAL res_addr, Z_LVAL_P(zv)
| SET_ZVAL_LVAL dst_addr, Ra(Z_REG(res_addr))
|| } else {
| SET_ZVAL_LVAL dst_addr, Z_LVAL_P(zv)
| SET_ZVAL_LVAL res_addr, Z_LVAL_P(zv)
|| }
| .else
|| if (Z_MODE(dst_addr) == IS_REG) {
| SET_ZVAL_LVAL dst_addr, Z_LVAL_P(zv)
| SET_ZVAL_LVAL res_addr, Ra(Z_REG(dst_addr))
|| } else if (Z_MODE(res_addr) == IS_REG) {
| SET_ZVAL_LVAL res_addr, Z_LVAL_P(zv)
| SET_ZVAL_LVAL dst_addr, Ra(Z_REG(res_addr))
|| } else {
| SET_ZVAL_LVAL dst_addr, Z_LVAL_P(zv)
| SET_ZVAL_LVAL res_addr, Z_LVAL_P(zv)
|| }
| .endif
|| }
|| }
|| if (Z_MODE(dst_addr) == IS_MEM_ZVAL) {
|| if (dst_def_info == MAY_BE_DOUBLE) {
|| if ((dst_info & (MAY_BE_ANY|MAY_BE_UNDEF|MAY_BE_GUARD)) != MAY_BE_DOUBLE) {
| SET_ZVAL_TYPE_INFO dst_addr, IS_DOUBLE
|| }
|| } else if (((dst_info & (MAY_BE_ANY|MAY_BE_UNDEF|MAY_BE_GUARD)) != (1<<Z_TYPE_P(zv))) || (dst_info & (MAY_BE_STRING|MAY_BE_ARRAY)) != 0) {
| SET_ZVAL_TYPE_INFO dst_addr, Z_TYPE_INFO_P(zv)
|| }
|| }
|| if (Z_MODE(res_addr) == IS_MEM_ZVAL) {
|| if (dst_def_info == MAY_BE_DOUBLE) {
| SET_ZVAL_TYPE_INFO res_addr, IS_DOUBLE
|| } else {
| SET_ZVAL_TYPE_INFO res_addr, Z_TYPE_INFO_P(zv)
|| }
|| }
|.endmacro
/* the same as above, but "src" may overlap with "tmp_reg1" */
|.macro ZVAL_COPY_VALUE, dst_addr, dst_info, src_addr, src_info, tmp_reg1, tmp_reg2
| ZVAL_COPY_VALUE_V dst_addr, dst_info, src_addr, src_info, tmp_reg1, tmp_reg2
|| if ((src_info & (MAY_BE_NULL|MAY_BE_FALSE|MAY_BE_TRUE|MAY_BE_LONG|MAY_BE_DOUBLE)) &&
|| !(src_info & MAY_BE_GUARD) &&
|| has_concrete_type(src_info & MAY_BE_ANY)) {
|| if (Z_MODE(dst_addr) == IS_MEM_ZVAL) {
|| if ((dst_info & (MAY_BE_ANY|MAY_BE_UNDEF|MAY_BE_GUARD)) != (src_info & (MAY_BE_ANY|MAY_BE_UNDEF|MAY_BE_GUARD))) {
|| zend_uchar type = concrete_type(src_info);
| SET_ZVAL_TYPE_INFO dst_addr, type
|| }
|| }
|| } else {
| GET_ZVAL_TYPE_INFO Rd(tmp_reg1), src_addr
| SET_ZVAL_TYPE_INFO dst_addr, Rd(tmp_reg1)
|| }
|.endmacro
|.macro ZVAL_COPY_VALUE_V, dst_addr, dst_info, src_addr, src_info, tmp_reg1, tmp_reg2
|| if (src_info & (MAY_BE_ANY-(MAY_BE_NULL|MAY_BE_FALSE|MAY_BE_TRUE))) {
|| if ((src_info & (MAY_BE_ANY|MAY_BE_GUARD)) == MAY_BE_LONG) {
|| if (Z_MODE(src_addr) == IS_REG) {
|| if (Z_MODE(dst_addr) != IS_REG || Z_REG(dst_addr) != Z_REG(src_addr)) {
| SET_ZVAL_LVAL dst_addr, Ra(Z_REG(src_addr))
|| }
|| } else if (Z_MODE(dst_addr) == IS_REG) {
| GET_ZVAL_LVAL Z_REG(dst_addr), src_addr
|| } else {
| GET_ZVAL_LVAL tmp_reg2, src_addr
| SET_ZVAL_LVAL dst_addr, Ra(tmp_reg2)
|| }
|| } else if ((src_info & (MAY_BE_ANY|MAY_BE_GUARD)) == MAY_BE_DOUBLE) {
|| if (Z_MODE(src_addr) == IS_REG) {
| SSE_SET_ZVAL_DVAL dst_addr, Z_REG(src_addr)
|| } else if (Z_MODE(dst_addr) == IS_REG) {
| SSE_GET_ZVAL_DVAL Z_REG(dst_addr), src_addr
|| } else {
| SSE_GET_ZVAL_DVAL ZREG_XMM0, src_addr
| SSE_SET_ZVAL_DVAL dst_addr, ZREG_XMM0
|| }
|| } else if (!(src_info & (MAY_BE_DOUBLE|MAY_BE_GUARD))) {
| GET_ZVAL_PTR Ra(tmp_reg2), src_addr
| SET_ZVAL_PTR dst_addr, Ra(tmp_reg2)
|| } else {
| .if X64
| GET_ZVAL_PTR Ra(tmp_reg2), src_addr
| SET_ZVAL_PTR dst_addr, Ra(tmp_reg2)
| .else
|| if ((tmp_reg1 == tmp_reg2 || tmp_reg1 == Z_REG(src_addr))) {
| GET_ZVAL_W2 Ra(tmp_reg2), src_addr
| SET_ZVAL_W2 dst_addr, Ra(tmp_reg2)
| GET_ZVAL_PTR Ra(tmp_reg2), src_addr
| SET_ZVAL_PTR dst_addr, Ra(tmp_reg2)
|| } else {
| GET_ZVAL_PTR Ra(tmp_reg2), src_addr
| GET_ZVAL_W2 Ra(tmp_reg1), src_addr
| SET_ZVAL_PTR dst_addr, Ra(tmp_reg2)
| SET_ZVAL_W2 dst_addr, Ra(tmp_reg1)
|| }
| .endif
|| }
|| }
|.endmacro
|.macro ZVAL_COPY_VALUE_2, dst_addr, dst_info, res_addr, src_addr, src_info, tmp_reg1, tmp_reg2
|| if (src_info & (MAY_BE_ANY-(MAY_BE_NULL|MAY_BE_FALSE|MAY_BE_TRUE))) {
|| if ((src_info & MAY_BE_ANY) == MAY_BE_LONG) {
|| if (Z_MODE(src_addr) == IS_REG) {
|| if (Z_MODE(dst_addr) != IS_REG || Z_REG(dst_addr) != Z_REG(src_addr)) {
| SET_ZVAL_LVAL dst_addr, Ra(Z_REG(src_addr))
|| }
|| if (Z_MODE(res_addr) != IS_REG || Z_REG(res_addr) != Z_REG(src_addr)) {
| SET_ZVAL_LVAL res_addr, Ra(Z_REG(src_addr))
|| }
|| } else if (Z_MODE(dst_addr) == IS_REG) {
| GET_ZVAL_LVAL Z_REG(dst_addr), src_addr
|| if (Z_MODE(res_addr) != IS_REG || Z_REG(res_addr) != Z_REG(dst_addr)) {
| SET_ZVAL_LVAL res_addr, Ra(Z_REG(dst_addr))
|| }
|| } else if (Z_MODE(res_addr) == IS_REG) {
| GET_ZVAL_LVAL Z_REG(res_addr), src_addr
| SET_ZVAL_LVAL dst_addr, Ra(Z_REG(res_addr))
|| } else {
| GET_ZVAL_LVAL tmp_reg2, src_addr
| SET_ZVAL_LVAL dst_addr, Ra(tmp_reg2)
| SET_ZVAL_LVAL res_addr, Ra(tmp_reg2)
|| }
|| } else if ((src_info & MAY_BE_ANY) == MAY_BE_DOUBLE) {
|| if (Z_MODE(src_addr) == IS_REG) {
| SSE_SET_ZVAL_DVAL dst_addr, Z_REG(src_addr)
| SSE_SET_ZVAL_DVAL res_addr, Z_REG(src_addr)
|| } else if (Z_MODE(dst_addr) == IS_REG) {
| SSE_GET_ZVAL_DVAL Z_REG(dst_addr), src_addr
| SSE_SET_ZVAL_DVAL res_addr, Z_REG(dst_addr)
|| } else if (Z_MODE(res_addr) == IS_REG) {
| SSE_GET_ZVAL_DVAL Z_REG(res_addr), src_addr
| SSE_SET_ZVAL_DVAL dst_addr, Z_REG(res_addr)
|| } else {
| SSE_GET_ZVAL_DVAL ZREG_XMM0, src_addr
| SSE_SET_ZVAL_DVAL dst_addr, ZREG_XMM0
| SSE_SET_ZVAL_DVAL res_addr, ZREG_XMM0
|| }
|| } else if (!(src_info & MAY_BE_DOUBLE)) {
| GET_ZVAL_PTR Ra(tmp_reg2), src_addr
| SET_ZVAL_PTR dst_addr, Ra(tmp_reg2)
| SET_ZVAL_PTR res_addr, Ra(tmp_reg2)
|| } else {
| .if X64
| GET_ZVAL_PTR Ra(tmp_reg2), src_addr
| SET_ZVAL_PTR dst_addr, Ra(tmp_reg2)
| SET_ZVAL_PTR res_addr, Ra(tmp_reg2)
| .else
|| if (tmp_reg1 == tmp_reg2 || tmp_reg1 == Z_REG(src_addr)) {
| GET_ZVAL_W2 Ra(tmp_reg2), src_addr
| SET_ZVAL_W2 dst_addr, Ra(tmp_reg2)
| SET_ZVAL_W2 res_addr, Ra(tmp_reg2)
| GET_ZVAL_PTR Ra(tmp_reg2), src_addr
| SET_ZVAL_PTR dst_addr, Ra(tmp_reg2)
| SET_ZVAL_PTR res_addr, Ra(tmp_reg2)
|| } else {
| GET_ZVAL_PTR Ra(tmp_reg2), src_addr
| GET_ZVAL_W2 Ra(tmp_reg1), src_addr
| SET_ZVAL_PTR dst_addr, Ra(tmp_reg2)
| SET_ZVAL_PTR res_addr, Ra(tmp_reg2)
| SET_ZVAL_W2 dst_addr, Ra(tmp_reg1)
| SET_ZVAL_W2 res_addr, Ra(tmp_reg1)
|| }
| .endif
|| }
|| }
|| if ((src_info & (MAY_BE_NULL|MAY_BE_FALSE|MAY_BE_TRUE|MAY_BE_LONG|MAY_BE_DOUBLE)) &&
|| has_concrete_type(src_info & MAY_BE_ANY)) {
|| zend_uchar type = concrete_type(src_info);
|| if (Z_MODE(dst_addr) == IS_MEM_ZVAL) {
|| if ((dst_info & (MAY_BE_ANY|MAY_BE_UNDEF|MAY_BE_GUARD)) != (src_info & (MAY_BE_ANY|MAY_BE_UNDEF))) {
| SET_ZVAL_TYPE_INFO dst_addr, type
|| }
|| }
|| if (Z_MODE(res_addr) == IS_MEM_ZVAL) {
| SET_ZVAL_TYPE_INFO res_addr, type
|| }
|| } else {
| GET_ZVAL_TYPE_INFO Rd(tmp_reg1), src_addr
| SET_ZVAL_TYPE_INFO dst_addr, Rd(tmp_reg1)
| SET_ZVAL_TYPE_INFO res_addr, Rd(tmp_reg1)
|| }
|.endmacro
|.macro IF_UNDEF, type_reg, label
| test type_reg, type_reg
| je label
|.endmacro
|.macro IF_TYPE, type, val, label
| cmp type, val
| je label
|.endmacro
|.macro IF_NOT_TYPE, type, val, label
| cmp type, val
| jne label
|.endmacro
|.macro IF_Z_TYPE, zv, val, label
| IF_TYPE byte [zv+offsetof(zval, u1.v.type)], val, label
|.endmacro
|.macro IF_NOT_Z_TYPE, zv, val, label
| IF_NOT_TYPE byte [zv+offsetof(zval, u1.v.type)], val, label
|.endmacro
|.macro CMP_ZVAL_TYPE, addr, val
|| ZEND_ASSERT(Z_MODE(addr) == IS_MEM_ZVAL);
| cmp byte [Ra(Z_REG(addr))+Z_OFFSET(addr)+offsetof(zval, u1.v.type)], val
|.endmacro
|.macro IF_ZVAL_TYPE, addr, val, label
|| ZEND_ASSERT(Z_MODE(addr) == IS_MEM_ZVAL);
| IF_TYPE byte [Ra(Z_REG(addr))+Z_OFFSET(addr)+offsetof(zval, u1.v.type)], val, label
|.endmacro
|.macro IF_NOT_ZVAL_TYPE, addr, val, label
|| ZEND_ASSERT(Z_MODE(addr) == IS_MEM_ZVAL);
| IF_NOT_TYPE byte [Ra(Z_REG(addr))+Z_OFFSET(addr)+offsetof(zval, u1.v.type)], val, label
|.endmacro
|.macro IF_FLAGS, type_flags, mask, label
| test type_flags, mask
| jnz label
|.endmacro
|.macro IF_NOT_FLAGS, type_flags, mask, label
| test type_flags, mask
| jz label
|.endmacro
|.macro IF_REFCOUNTED, type_flags, label
| IF_FLAGS type_flags, IS_TYPE_REFCOUNTED, label
|.endmacro
|.macro IF_NOT_REFCOUNTED, type_flags, label
| //IF_NOT_FLAGS type_flags, IS_TYPE_REFCOUNTED, label
| test type_flags, type_flags
| jz label
|.endmacro
|.macro IF_ZVAL_FLAGS, addr, mask, label
|| ZEND_ASSERT(Z_MODE(addr) == IS_MEM_ZVAL);
| IF_FLAGS byte [Ra(Z_REG(addr))+Z_OFFSET(addr)+offsetof(zval, u1.v.type_flags)], mask, label
|.endmacro
|.macro IF_NOT_ZVAL_FLAGS, addr, mask, label
|| ZEND_ASSERT(Z_MODE(addr) == IS_MEM_ZVAL);
| IF_NOT_FLAGS byte [Ra(Z_REG(addr))+Z_OFFSET(addr)+offsetof(zval, u1.v.type_flags)], mask, label
|.endmacro
|.macro IF_ZVAL_REFCOUNTED, addr, label
| IF_ZVAL_FLAGS addr, IS_TYPE_REFCOUNTED, label
|.endmacro
|.macro IF_NOT_ZVAL_REFCOUNTED, addr, label
| IF_NOT_ZVAL_FLAGS addr, IS_TYPE_REFCOUNTED, label
|.endmacro
|.macro IF_NOT_ZVAL_COLLECTABLE, addr, label
| IF_NOT_ZVAL_FLAGS addr, IS_TYPE_COLLECTABLE, label
|.endmacro
|.macro GC_ADDREF, zv
| add dword [zv], 1
|.endmacro
|.macro GC_DELREF, zv
| sub dword [zv], 1
|.endmacro
|.macro IF_GC_MAY_NOT_LEAK, ptr, label
| test dword [ptr+4],(GC_INFO_MASK | (GC_NOT_COLLECTABLE << GC_FLAGS_SHIFT))
| jne label
|.endmacro
|.macro ADDREF_CONST, zv, tmp_reg
| .if X64
|| if (!IS_SIGNED_32BIT(Z_LVAL_P(zv))) {
| mov64 tmp_reg, ((uintptr_t)Z_LVAL_P(zv))
| add dword [tmp_reg], 1
|| } else {
| add dword [Z_LVAL_P(zv)], 1
|| }
| .else
| add dword [Z_LVAL_P(zv)], 1
| .endif
|.endmacro
|.macro ADDREF_CONST_2, zv, tmp_reg
| .if X64
|| if (!IS_SIGNED_32BIT(Z_LVAL_P(zv))) {
| mov64 tmp_reg, ((uintptr_t)Z_LVAL_P(zv))
| add dword [tmp_reg], 2
|| } else {
| add dword [Z_LVAL_P(zv)], 2
|| }
| .else
| add dword [Z_LVAL_P(zv)], 2
| .endif
|.endmacro
|.macro TRY_ADDREF, val_info, type_flags_reg, value_ptr_reg
|| if (val_info & (MAY_BE_STRING|MAY_BE_ARRAY|MAY_BE_OBJECT|MAY_BE_RESOURCE)) {
|| if (val_info & (MAY_BE_ANY-(MAY_BE_OBJECT|MAY_BE_RESOURCE))) {
| IF_NOT_REFCOUNTED type_flags_reg, >1
|| }
| GC_ADDREF value_ptr_reg
|1:
|| }
|.endmacro
|.macro TRY_ADDREF_2, val_info, type_flags_reg, value_ptr_reg
|| if (val_info & (MAY_BE_STRING|MAY_BE_ARRAY|MAY_BE_OBJECT|MAY_BE_RESOURCE)) {
|| if (val_info & (MAY_BE_ANY-(MAY_BE_OBJECT|MAY_BE_RESOURCE))) {
| IF_NOT_REFCOUNTED type_flags_reg, >1
|| }
| add dword [value_ptr_reg], 2
|1:
|| }
|.endmacro
|.macro ZVAL_DEREF, reg, info
|| if (info & MAY_BE_REF) {
| IF_NOT_Z_TYPE, reg, IS_REFERENCE, >1
| GET_Z_PTR reg, reg
| add reg, offsetof(zend_reference, val)
|1:
|| }
|.endmacro
|.macro SET_EX_OPLINE, op, tmp_reg
|| if (op == last_valid_opline) {
|| zend_jit_use_last_valid_opline();
| SAVE_IP
|| } else {
| ADDR_OP2_2 mov, aword EX->opline, op, tmp_reg
|| if (!GCC_GLOBAL_REGS) {
|| zend_jit_reset_last_valid_opline();
|| }
|| }
|.endmacro
// zval should be in FCARG1a
|.macro ZVAL_DTOR_FUNC, var_info, opline // arg1 must be in FCARG1a
|| do {
|| if (!((var_info) & MAY_BE_GUARD)
|| && has_concrete_type((var_info) & (MAY_BE_STRING|MAY_BE_ARRAY|MAY_BE_OBJECT|MAY_BE_RESOURCE))) {
|| zend_uchar type = concrete_type((var_info) & (MAY_BE_STRING|MAY_BE_ARRAY|MAY_BE_OBJECT|MAY_BE_RESOURCE));
|| if (type == IS_STRING && !ZEND_DEBUG) {
| EXT_CALL _efree, r0
|| break;
|| } else if (type == IS_ARRAY) {
|| if ((var_info) & (MAY_BE_ARRAY_KEY_STRING|MAY_BE_ARRAY_OF_STRING|MAY_BE_ARRAY_OF_ARRAY|MAY_BE_ARRAY_OF_OBJECT|MAY_BE_ARRAY_OF_RESOURCE|MAY_BE_ARRAY_OF_REF)) {
|| if (opline && ((var_info) & (MAY_BE_ARRAY_OF_ARRAY|MAY_BE_ARRAY_OF_OBJECT|MAY_BE_ARRAY_OF_RESOURCE|MAY_BE_ARRAY_OF_REF))) {
| SET_EX_OPLINE opline, r0
|| }
| EXT_CALL zend_array_destroy, r0
|| } else {
| EXT_CALL zend_jit_array_free, r0
|| }
|| break;
|| } else if (type == IS_OBJECT) {
|| if (opline) {
| SET_EX_OPLINE opline, r0
|| }
| EXT_CALL zend_objects_store_del, r0
|| break;
|| }
|| }
|| if (opline) {
| SET_EX_OPLINE opline, r0
|| }
| EXT_CALL rc_dtor_func, r0
|| } while(0);
|.endmacro
|.macro ZVAL_PTR_DTOR, addr, op_info, gc, cold, opline
|| if ((op_info) & (MAY_BE_STRING|MAY_BE_ARRAY|MAY_BE_OBJECT|MAY_BE_RESOURCE|MAY_BE_REF|MAY_BE_GUARD)) {
|| if ((op_info) & ((MAY_BE_ANY|MAY_BE_UNDEF|MAY_BE_INDIRECT|MAY_BE_GUARD)-(MAY_BE_OBJECT|MAY_BE_RESOURCE))) {
| // if (Z_REFCOUNTED_P(cv)) {
|| if (cold) {
| IF_ZVAL_REFCOUNTED addr, >1
|.cold_code
|1:
|| } else {
| IF_NOT_ZVAL_REFCOUNTED addr, >4
|| }
|| }
| // if (!Z_DELREF_P(cv)) {
| GET_ZVAL_PTR FCARG1a, addr
| GC_DELREF FCARG1a
|| if (((op_info) & MAY_BE_GUARD) || RC_MAY_BE_1(op_info)) {
|| if (((op_info) & MAY_BE_GUARD) || RC_MAY_BE_N(op_info)) {
|| if (gc && (((op_info) & MAY_BE_GUARD) || (RC_MAY_BE_N(op_info) && ((op_info) & (MAY_BE_REF|MAY_BE_ARRAY|MAY_BE_OBJECT)) != 0))) {
| jnz >3
|| } else {
| jnz >4
|| }
|| }
| // zval_dtor_func(r);
| ZVAL_DTOR_FUNC op_info, opline
|| if (gc && (((op_info) & MAY_BE_GUARD) || (RC_MAY_BE_N(op_info) && ((op_info) & (MAY_BE_REF|MAY_BE_ARRAY|MAY_BE_OBJECT)) != 0))) {
| jmp >4
|| }
|3:
|| }
|| if (gc && (((op_info) & MAY_BE_GUARD) || (RC_MAY_BE_N(op_info) && ((op_info) & (MAY_BE_REF|MAY_BE_ARRAY|MAY_BE_OBJECT)) != 0))) {
|| if ((op_info) & (MAY_BE_REF|MAY_BE_GUARD)) {
|| zend_jit_addr ref_addr = ZEND_ADDR_MEM_ZVAL(ZREG_FCARG1a, offsetof(zend_reference, val));
| IF_NOT_ZVAL_TYPE addr, IS_REFERENCE, >1
| IF_NOT_ZVAL_COLLECTABLE ref_addr, >4
| GET_ZVAL_PTR FCARG1a, ref_addr
|1:
|| }
| IF_GC_MAY_NOT_LEAK FCARG1a, >4
| // gc_possible_root(Z_COUNTED_P(z))
| EXT_CALL gc_possible_root, r0
|| }
|| if (cold && ((op_info) & ((MAY_BE_ANY|MAY_BE_UNDEF|MAY_BE_INDIRECT|MAY_BE_GUARD)-(MAY_BE_OBJECT|MAY_BE_RESOURCE))) != 0) {
| jmp >4
|.code
|| }
|4:
|| }
|.endmacro
|.macro FREE_OP, op_type, op, op_info, cold, opline
|| if (op_type & (IS_VAR|IS_TMP_VAR)) {
| ZVAL_PTR_DTOR ZEND_ADDR_MEM_ZVAL(ZREG_FP, op.var), op_info, 0, cold, opline
|| }
|.endmacro
|.macro SEPARATE_ARRAY, addr, op_info, cold
|| if (RC_MAY_BE_N(op_info)) {
|| if (Z_REG(addr) != ZREG_FP) {
| GET_ZVAL_LVAL ZREG_R0, addr
|| if (RC_MAY_BE_1(op_info)) {
| cmp dword [r0], 1 // if (GC_REFCOUNT() > 1)
| jbe >2
|| }
|| if (Z_REG(addr) != ZREG_FCARG1a || Z_OFFSET(addr) != 0) {
| LOAD_ZVAL_ADDR FCARG1a, addr
|| }
| EXT_CALL zend_jit_zval_array_dup, r0
|2:
| mov FCARG1a, r0
|| } else {
| GET_ZVAL_LVAL ZREG_FCARG1a, addr
|| if (RC_MAY_BE_1(op_info)) {
| cmp dword [FCARG1a], 1 // if (GC_REFCOUNT() > 1)
|| if (cold) {
| ja >1
|.cold_code
|1:
|| } else {
| jbe >2
|| }
|| }
| IF_NOT_ZVAL_REFCOUNTED addr, >1
| GC_DELREF FCARG1a
|1:
| EXT_CALL zend_array_dup, r0
| SET_ZVAL_PTR addr, r0
| SET_ZVAL_TYPE_INFO addr, IS_ARRAY_EX
| mov FCARG1a, r0
|| if (RC_MAY_BE_1(op_info)) {
|| if (cold) {
| jmp >2
|.code
|| }
|| }
|2:
|| }
|| } else {
| GET_ZVAL_LVAL ZREG_FCARG1a, addr
|| }
|.endmacro
|.macro EFREE_REG_REFERENCE
||#if ZEND_DEBUG
| xor FCARG2a, FCARG2a // filename
| .if X64WIN
| xor CARG3d, CARG3d // lineno
| xor CARG4, CARG4
| mov aword A5, 0
| EXT_CALL _efree, r0
| .elif X64
| xor CARG3d, CARG3d // lineno
| xor CARG4, CARG4
| xor CARG5, CARG5
| EXT_CALL _efree, r0
| .else
| sub r4, 4
| push 0
| push 0
| push 0 // lineno
| EXT_CALL _efree, r0
| add r4, 4
| .endif
||#else
||#ifdef HAVE_BUILTIN_CONSTANT_P
| EXT_CALL _efree_32, r0
||#else
| EXT_CALL _efree, r0
||#endif
||#endif
|.endmacro
|.macro EFREE_REFERENCE, ptr
| mov FCARG1a, ptr
| EFREE_REG_REFERENCE
|.endmacro
|.macro EMALLOC, size, op_array, opline
||#if ZEND_DEBUG
|| const char *filename = op_array->filename ? op_array->filename->val : NULL;
| mov FCARG1a, size
| LOAD_ADDR FCARG2a, filename
| .if X64WIN
| mov CARG3d, opline->lineno
| xor CARG4, CARG4
| mov aword A5, 0
| EXT_CALL _emalloc, r0
| .elif X64
| mov CARG3d, opline->lineno
| xor CARG4, CARG4
| xor CARG5, CARG5
| EXT_CALL _emalloc, r0
| .else
| sub r4, 4
| push 0
| push 0
| push opline->lineno
| EXT_CALL _emalloc, r0
| add r4, 4
| .endif
||#else
||#ifdef HAVE_BUILTIN_CONSTANT_P
|| if (size > 24 && size <= 32) {
| EXT_CALL _emalloc_32, r0
|| } else {
| mov FCARG1a, size
| EXT_CALL _emalloc, r0
|| }
||#else
| mov FCARG1a, size
| EXT_CALL _emalloc, r0
||#endif
||#endif
|.endmacro
|.macro OBJ_RELEASE, reg, exit_label
| GC_DELREF Ra(reg)
| jne >1
| // zend_objects_store_del(obj);
|| if (reg != ZREG_FCARG1a) {
| mov FCARG1a, Ra(reg)
|| }
| EXT_CALL zend_objects_store_del, r0
| jmp exit_label
|1:
| IF_GC_MAY_NOT_LEAK Ra(reg), >1
| // gc_possible_root(obj)
|| if (reg != ZREG_FCARG1a) {
| mov FCARG1a, Ra(reg)
|| }
| EXT_CALL gc_possible_root, r0
|1:
|.endmacro
|.macro UNDEFINED_OFFSET, opline
|| if (opline == last_valid_opline) {
|| zend_jit_use_last_valid_opline();
| call ->undefined_offset_ex
|| } else {
| SET_EX_OPLINE opline, r0
| call ->undefined_offset
|| }
|.endmacro
|.macro UNDEFINED_INDEX, opline
|| if (opline == last_valid_opline) {
|| zend_jit_use_last_valid_opline();
| call ->undefined_index_ex
|| } else {
| SET_EX_OPLINE opline, r0
| call ->undefined_index
|| }
|.endmacro
|.macro CANNOT_ADD_ELEMENT, opline
|| if (opline == last_valid_opline) {
|| zend_jit_use_last_valid_opline();
| call ->cannot_add_element_ex
|| } else {
| SET_EX_OPLINE opline, r0
| call ->cannot_add_element
|| }
|.endmacro
static zend_bool reuse_ip = 0;
static zend_bool delayed_call_chain = 0;
static uint32_t delayed_call_level = 0;
static const zend_op *last_valid_opline = NULL;
static zend_bool use_last_vald_opline = 0;
static zend_bool track_last_valid_opline = 0;
static int jit_return_label = -1;
static uint32_t current_trace_num = 0;
static uint32_t allowed_opt_flags = 0;
static void zend_jit_track_last_valid_opline(void)
{
use_last_vald_opline = 0;
track_last_valid_opline = 1;
}
static void zend_jit_use_last_valid_opline(void)
{
if (track_last_valid_opline) {
use_last_vald_opline = 1;
track_last_valid_opline = 0;
}
}
static zend_bool zend_jit_trace_uses_initial_ip(void)
{
return use_last_vald_opline;
}
static void zend_jit_set_last_valid_opline(const zend_op *target_opline)
{
if (!reuse_ip) {
track_last_valid_opline = 0;
last_valid_opline = target_opline;
}
}
static void zend_jit_reset_last_valid_opline(void)
{
track_last_valid_opline = 0;
last_valid_opline = NULL;
}
static void zend_jit_start_reuse_ip(void)
{
zend_jit_reset_last_valid_opline();
reuse_ip = 1;
}
static int zend_jit_reuse_ip(dasm_State **Dst)
{
if (!reuse_ip) {
zend_jit_start_reuse_ip();
| // call = EX(call);
| mov RX, EX->call
}
return 1;
}
static void zend_jit_stop_reuse_ip(void)
{
reuse_ip = 0;
}
/* bit helpers */
/* from http://aggregate.org/MAGIC/ */
static uint32_t ones32(uint32_t x)
{
x -= ((x >> 1) & 0x55555555);
x = (((x >> 2) & 0x33333333) + (x & 0x33333333));
x = (((x >> 4) + x) & 0x0f0f0f0f);
x += (x >> 8);
x += (x >> 16);
return x & 0x0000003f;
}
static uint32_t floor_log2(uint32_t x)
{
ZEND_ASSERT(x != 0);
x |= (x >> 1);
x |= (x >> 2);
x |= (x >> 4);
x |= (x >> 8);
x |= (x >> 16);
return ones32(x) - 1;
}
static zend_bool is_power_of_two(uint32_t x)
{
return !(x & (x - 1)) && x != 0;
}
static zend_bool has_concrete_type(uint32_t value_type)
{
return is_power_of_two (value_type & (MAY_BE_ANY|MAY_BE_UNDEF));
}
static uint32_t concrete_type(uint32_t value_type)
{
return floor_log2(value_type & (MAY_BE_ANY|MAY_BE_UNDEF));
}
static inline zend_bool is_signed(double d)
{
return (((unsigned char*)&d)[sizeof(double)-1] & 0x80) != 0;
}
static int zend_jit_interrupt_handler_stub(dasm_State **Dst)
{
|->interrupt_handler:
| SAVE_IP
| //EG(vm_interrupt) = 0;
| MEM_OP2_1_ZTS mov, byte, executor_globals, vm_interrupt, 0, r0
| //if (EG(timed_out)) {
| MEM_OP2_1_ZTS cmp, byte, executor_globals, timed_out, 0, r0
| je >1
| //zend_timeout();
| EXT_CALL zend_timeout, r0
|1:
| //} else if (zend_interrupt_function) {
if (zend_interrupt_function) {
| //zend_interrupt_function(execute_data);
|.if X64
| mov CARG1, FP
| EXT_CALL zend_interrupt_function, r0
|.else
| mov aword A1, FP
| EXT_CALL zend_interrupt_function, r0
|.endif
| MEM_OP2_1_ZTS cmp, aword, executor_globals, exception, 0, r0
| je >1
| EXT_CALL zend_jit_exception_in_interrupt_handler_helper, r0
|1:
| //ZEND_VM_ENTER();
| //execute_data = EG(current_execute_data);
| MEM_OP2_2_ZTS mov, FP, aword, executor_globals, current_execute_data, r0
| LOAD_IP
}
| //ZEND_VM_CONTINUE()
if (zend_jit_vm_kind == ZEND_VM_KIND_HYBRID) {
| ADD_HYBRID_SPAD
| JMP_IP
} else if (GCC_GLOBAL_REGS) {
| add r4, SPAD // stack alignment
| JMP_IP
} else {
| mov FP, aword T2 // restore FP
| mov RX, aword T3 // restore IP
| add r4, NR_SPAD // stack alignment
| mov r0, 1 // ZEND_VM_ENTER
| ret
}
return 1;
}
static int zend_jit_exception_handler_stub(dasm_State **Dst)
{
|->exception_handler:
if (zend_jit_vm_kind == ZEND_VM_KIND_HYBRID) {
const void *handler = zend_get_opcode_handler_func(EG(exception_op));
| ADD_HYBRID_SPAD
| EXT_CALL handler, r0
| JMP_IP
} else {
const void *handler = EG(exception_op)->handler;
if (GCC_GLOBAL_REGS) {
| add r4, SPAD // stack alignment
| EXT_JMP handler, r0
} else {
| mov FCARG1a, FP
| EXT_CALL handler, r0
| mov FP, aword T2 // restore FP
| mov RX, aword T3 // restore IP
| add r4, NR_SPAD // stack alignment
| test eax, eax
| jl >1
| mov r0, 1 // ZEND_VM_ENTER
|1:
| ret
}
}
return 1;
}
static int zend_jit_exception_handler_undef_stub(dasm_State **Dst)
{
|->exception_handler_undef:
| MEM_OP2_2_ZTS mov, r0, aword, executor_globals, opline_before_exception, r0
| test byte OP:r0->result_type, (IS_TMP_VAR|IS_VAR)
| jz >1
| mov eax, dword OP:r0->result.var
| SET_Z_TYPE_INFO FP + r0, IS_UNDEF
|1:
| jmp ->exception_handler
return 1;
}
static int zend_jit_exception_handler_free_op1_op2_stub(dasm_State **Dst)
{
|->exception_handler_free_op1_op2:
| UNDEF_OPLINE_RESULT_IF_USED
| test byte OP:RX->op1_type, (IS_TMP_VAR|IS_VAR)
| je >9
| mov eax, dword OP:RX->op1.var
| add r0, FP
| ZVAL_PTR_DTOR ZEND_ADDR_MEM_ZVAL(ZREG_R0, 0), MAY_BE_ANY|MAY_BE_RC1|MAY_BE_RCN|MAY_BE_REF, 0, 0, NULL
|9:
| test byte OP:RX->op2_type, (IS_TMP_VAR|IS_VAR)
| je >9
| mov eax, dword OP:RX->op2.var
| add r0, FP
| ZVAL_PTR_DTOR ZEND_ADDR_MEM_ZVAL(ZREG_R0, 0), MAY_BE_ANY|MAY_BE_RC1|MAY_BE_RCN|MAY_BE_REF, 0, 0, NULL
|9:
| jmp ->exception_handler
return 1;
}
static int zend_jit_exception_handler_free_op2_stub(dasm_State **Dst)
{
|->exception_handler_free_op2:
| MEM_OP2_2_ZTS mov, RX, aword, executor_globals, opline_before_exception, r0
| UNDEF_OPLINE_RESULT_IF_USED
| test byte OP:RX->op2_type, (IS_TMP_VAR|IS_VAR)
| je >9
| mov eax, dword OP:RX->op2.var
| add r0, FP
| ZVAL_PTR_DTOR ZEND_ADDR_MEM_ZVAL(ZREG_R0, 0), MAY_BE_ANY|MAY_BE_RC1|MAY_BE_RCN|MAY_BE_REF, 0, 0, NULL
|9:
| jmp ->exception_handler
return 1;
}
static int zend_jit_leave_function_stub(dasm_State **Dst)
{
|->leave_function_handler:
if (zend_jit_vm_kind == ZEND_VM_KIND_HYBRID) {
| test FCARG1d, ZEND_CALL_TOP
| jnz >1
| EXT_CALL zend_jit_leave_nested_func_helper, r0
| ADD_HYBRID_SPAD
| JMP_IP
|1:
| EXT_CALL zend_jit_leave_top_func_helper, r0
| ADD_HYBRID_SPAD
| JMP_IP
} else {
if (GCC_GLOBAL_REGS) {
| add r4, SPAD
} else {
| mov FCARG2a, FP
| mov FP, aword T2 // restore FP
| mov RX, aword T3 // restore IP
| add r4, NR_SPAD
}
| test FCARG1d, ZEND_CALL_TOP
| jnz >1
| EXT_JMP zend_jit_leave_nested_func_helper, r0
|1:
| EXT_JMP zend_jit_leave_top_func_helper, r0
}
return 1;
}
static int zend_jit_leave_throw_stub(dasm_State **Dst)
{
|->leave_throw_handler:
| // if (opline->opcode != ZEND_HANDLE_EXCEPTION) {
if (GCC_GLOBAL_REGS) {
| cmp byte OP:IP->opcode, ZEND_HANDLE_EXCEPTION
| je >5
| // EG(opline_before_exception) = opline;
| MEM_OP2_1_ZTS mov, aword, executor_globals, opline_before_exception, IP, r0
|5:
| // opline = EG(exception_op);
| LOAD_IP_ADDR_ZTS executor_globals, exception_op
| // HANDLE_EXCEPTION()
| jmp ->exception_handler
} else {
| GET_IP FCARG1a
| cmp byte OP:FCARG1a->opcode, ZEND_HANDLE_EXCEPTION
| je >5
| // EG(opline_before_exception) = opline;
| MEM_OP2_1_ZTS mov, aword, executor_globals, opline_before_exception, FCARG1a, r0
|5:
| // opline = EG(exception_op);
| LOAD_IP_ADDR_ZTS executor_globals, exception_op
| mov FP, aword T2 // restore FP
| mov RX, aword T3 // restore IP
| add r4, NR_SPAD // stack alignment
| mov r0, 2 // ZEND_VM_LEAVE
| ret
}
return 1;
}
static int zend_jit_icall_throw_stub(dasm_State **Dst)
{
|->icall_throw_handler:
| // zend_rethrow_exception(zend_execute_data *execute_data)
| mov IP, aword EX->opline
| // if (EX(opline)->opcode != ZEND_HANDLE_EXCEPTION) {
| cmp byte OP:IP->opcode, ZEND_HANDLE_EXCEPTION
| je >1
| // EG(opline_before_exception) = opline;
| MEM_OP2_1_ZTS mov, aword, executor_globals, opline_before_exception, IP, r0
|1:
| // opline = EG(exception_op);
| LOAD_IP_ADDR_ZTS executor_globals, exception_op
|| if (GCC_GLOBAL_REGS) {
| mov aword EX->opline, IP
|| }
| // HANDLE_EXCEPTION()
| jmp ->exception_handler
return 1;
}
static int zend_jit_throw_cannot_pass_by_ref_stub(dasm_State **Dst)
{
|->throw_cannot_pass_by_ref:
| mov r0, EX->opline
| mov ecx, dword OP:r0->result.var
| SET_Z_TYPE_INFO RX+r1, IS_UNDEF
| // last EX(call) frame may be delayed
| cmp RX, EX->call
| je >1
| mov r1, EX->call
| mov EX:RX->prev_execute_data, r1
| mov EX->call, RX
|1:
| mov RX, r0
| mov FCARG1d, dword OP:r0->op2.num
| EXT_CALL zend_cannot_pass_by_reference, r0
| cmp byte OP:RX->op1_type, IS_TMP_VAR
| jne >9
| mov eax, dword OP:RX->op1.var
| add r0, FP
| ZVAL_PTR_DTOR ZEND_ADDR_MEM_ZVAL(ZREG_R0, 0), MAY_BE_ANY|MAY_BE_RC1|MAY_BE_RCN|MAY_BE_REF, 0, 0, NULL
|9:
| jmp ->exception_handler
return 1;
}
static int zend_jit_undefined_offset_ex_stub(dasm_State **Dst)
{
|->undefined_offset_ex:
| SAVE_IP
| jmp ->undefined_offset
return 1;
}
static int zend_jit_undefined_offset_stub(dasm_State **Dst)
{
|->undefined_offset:
|.if X64WIN
| sub r4, 0x28
|.elif X64
| sub r4, 8
|.else
| sub r4, 12
|.endif
| mov r0, EX->opline
| mov ecx, dword OP:r0->result.var
| cmp byte OP:r0->op2_type, IS_CONST
| SET_Z_TYPE_INFO FP + r1, IS_NULL
| jne >2
|.if X64
| movsxd r1, dword OP:r0->op2.constant
| add r0, r1
|.else
| mov r0, aword OP:r0->op2.zv
|.endif
| jmp >3
|2:
| mov eax, dword OP:r0->op2.var
| add r0, FP
|3:
|.if X64WIN
| mov CARG1, E_WARNING
| LOAD_ADDR CARG2, "Undefined array key " ZEND_LONG_FMT
| mov CARG3, aword [r0]
| EXT_CALL zend_error, r0
| add r4, 0x28 // stack alignment
|.elif X64
| mov CARG1, E_WARNING
| LOAD_ADDR CARG2, "Undefined array key " ZEND_LONG_FMT
| mov CARG3, aword [r0]
| EXT_CALL zend_error, r0
| add r4, 8 // stack alignment
|.else
| sub r4, 4
| push aword [r0]
| push "Undefined array key " ZEND_LONG_FMT
| push E_WARNING
| EXT_CALL zend_error, r0
| add r4, 28
|.endif
| ret
return 1;
}
static int zend_jit_undefined_index_ex_stub(dasm_State **Dst)
{
|->undefined_index_ex:
| SAVE_IP
| jmp ->undefined_index
return 1;
}
static int zend_jit_undefined_index_stub(dasm_State **Dst)
{
|->undefined_index:
|.if X64WIN
| sub r4, 0x28
|.elif X64
| sub r4, 8
|.else
| sub r4, 12
|.endif
| mov r0, EX->opline
| mov ecx, dword OP:r0->result.var
| cmp byte OP:r0->op2_type, IS_CONST
| SET_Z_TYPE_INFO FP + r1, IS_NULL
| jne >2
|.if X64
| movsxd r1, dword OP:r0->op2.constant
| add r0, r1
|.else
| mov r0, aword OP:r0->op2.zv
|.endif
| jmp >3
|2:
| mov eax, dword OP:r0->op2.var
| add r0, FP
|3:
|.if X64WIN
| mov CARG1, E_WARNING
| LOAD_ADDR CARG2, "Undefined array key \"%s\""
| mov CARG3, aword [r0]
| add CARG3, offsetof(zend_string, val)
| EXT_CALL zend_error, r0
| add r4, 0x28
|.elif X64
| mov CARG1, E_WARNING
| LOAD_ADDR CARG2, "Undefined array key \"%s\""
| mov CARG3, aword [r0]
| add CARG3, offsetof(zend_string, val)
| EXT_CALL zend_error, r0
| add r4, 8
|.else
| sub r4, 4
| mov r0, aword [r0]
| add r0, offsetof(zend_string, val)
| push r0
| push "Undefined array key \"%s\""
| push E_WARNING
| EXT_CALL zend_error, r0
| add r4, 28
|.endif
| ret
return 1;
}
static int zend_jit_cannot_add_element_ex_stub(dasm_State **Dst)
{
|->cannot_add_element_ex:
| SAVE_IP
| jmp ->cannot_add_element
return 1;
}
static int zend_jit_cannot_add_element_stub(dasm_State **Dst)
{
|->cannot_add_element:
|.if X64WIN
| sub r4, 0x28
|.elif X64
| sub r4, 8
|.else
| sub r4, 12
|.endif
| mov r0, EX->opline
| cmp byte OP:r0->result_type, IS_UNUSED
| jz >1
| mov eax, dword OP:r0->result.var
| SET_Z_TYPE_INFO FP + r0, IS_NULL
|1:
|.if X64WIN
| xor CARG1, CARG1
| LOAD_ADDR CARG2, "Cannot add element to the array as the next element is already occupied"
| EXT_CALL zend_throw_error, r0
| add r4, 0x28
|.elif X64
| xor CARG1, CARG1
| LOAD_ADDR CARG2, "Cannot add element to the array as the next element is already occupied"
| EXT_CALL zend_throw_error, r0
| add r4, 8
|.else
| sub r4, 8
| push "Cannot add element to the array as the next element is already occupied"
| push 0
| EXT_CALL zend_throw_error, r0
| add r4, 28
|.endif
| ret
return 1;
}
static int zend_jit_undefined_function_stub(dasm_State **Dst)
{
|->undefined_function:
| mov r0, aword EX->opline
|.if X64
| xor CARG1, CARG1
| LOAD_ADDR CARG2, "Call to undefined function %s()"
| movsxd CARG3, dword [r0 + offsetof(zend_op, op2.constant)]
| mov CARG3, aword [r0 + CARG3]
| add CARG3, offsetof(zend_string, val)
| EXT_CALL zend_throw_error, r0
|.else
| mov r0, aword [r0 + offsetof(zend_op, op2.zv)]
| mov r0, aword [r0]
| add r0, offsetof(zend_string, val)
| mov aword A3, r0
| mov aword A2, "Call to undefined function %s()"
| mov aword A1, 0
| EXT_CALL zend_throw_error, r0
|.endif
| jmp ->exception_handler
return 1;
}
static int zend_jit_negative_shift_stub(dasm_State **Dst)
{
|->negative_shift:
| mov RX, EX->opline
|.if X64
|.if WIN
| LOAD_ADDR CARG1, &zend_ce_arithmetic_error
| mov CARG1, aword [CARG1]
|.else
| LOAD_ADDR CARG1, zend_ce_arithmetic_error
|.endif
| LOAD_ADDR CARG2, "Bit shift by negative number"
| EXT_CALL zend_throw_error, r0
|.else
| sub r4, 8
| push "Bit shift by negative number"
|.if WIN
| LOAD_ADDR r0, &zend_ce_arithmetic_error
| push aword [r0]
|.else
| PUSH_ADDR zend_ce_arithmetic_error, r0
|.endif
| EXT_CALL zend_throw_error, r0
| add r4, 16
|.endif
| jmp ->exception_handler_free_op1_op2
return 1;
}
static int zend_jit_mod_by_zero_stub(dasm_State **Dst)
{
|->mod_by_zero:
| mov RX, EX->opline
|.if X64
|.if WIN
| LOAD_ADDR CARG1, &zend_ce_division_by_zero_error
| mov CARG1, aword [CARG1]
|.else
| LOAD_ADDR CARG1, zend_ce_division_by_zero_error
|.endif
| LOAD_ADDR CARG2, "Modulo by zero"
| EXT_CALL zend_throw_error, r0
|.else
| sub r4, 8
| push "Modulo by zero"
|.if WIN
| LOAD_ADDR r0, &zend_ce_division_by_zero_error
| push aword [r0]
|.else
| PUSH_ADDR zend_ce_division_by_zero_error, r0
|.endif
| EXT_CALL zend_throw_error, r0
| add r4, 16
|.endif
| jmp ->exception_handler_free_op1_op2
return 1;
}
static int zend_jit_invalid_this_stub(dasm_State **Dst)
{
|->invalid_this:
| UNDEF_OPLINE_RESULT
|.if X64
| xor CARG1, CARG1
| LOAD_ADDR CARG2, "Using $this when not in object context"
| EXT_CALL zend_throw_error, r0
|.else
| sub r4, 8
| push "Using $this when not in object context"
| push 0
| EXT_CALL zend_throw_error, r0
| add r4, 16
|.endif
| jmp ->exception_handler
return 1;
}
static int zend_jit_double_one_stub(dasm_State **Dst)
{
|->one:
|.dword 0, 0x3ff00000
return 1;
}
static int zend_jit_hybrid_runtime_jit_stub(dasm_State **Dst)
{
if (zend_jit_vm_kind != ZEND_VM_KIND_HYBRID) {
return 1;
}
|->hybrid_runtime_jit:
| EXT_CALL zend_runtime_jit, r0
| JMP_IP
return 1;
}
static int zend_jit_hybrid_profile_jit_stub(dasm_State **Dst)
{
if (zend_jit_vm_kind != ZEND_VM_KIND_HYBRID) {
return 1;
}
|->hybrid_profile_jit:
| // ++zend_jit_profile_counter;
| .if X64
| LOAD_ADDR r0, &zend_jit_profile_counter
| inc aword [r0]
| .else
| inc aword [&zend_jit_profile_counter]
| .endif
| // op_array = (zend_op_array*)EX(func);
| mov r0, EX->func
| // run_time_cache = EX(run_time_cache);
| mov r2, EX->run_time_cache
| // jit_extension = (const void*)ZEND_FUNC_INFO(op_array);
| mov r0, aword [r0 + offsetof(zend_op_array, reserved[zend_func_info_rid])]
| // ++ZEND_COUNTER_INFO(op_array)
| inc aword [r2 + zend_jit_profile_counter_rid * sizeof(void*)]
| // return ((zend_vm_opcode_handler_t)jit_extension->orig_handler)()
| jmp aword [r0 + offsetof(zend_jit_op_array_extension, orig_handler)]
return 1;
}
static int zend_jit_hybrid_hot_code_stub(dasm_State **Dst)
{
if (zend_jit_vm_kind != ZEND_VM_KIND_HYBRID) {
return 1;
}
|->hybrid_hot_code:
| mov word [r2], ZEND_JIT_COUNTER_INIT
| mov FCARG1a, FP
| GET_IP FCARG2a
| EXT_CALL zend_jit_hot_func, r0
| JMP_IP
return 1;
}
/*
* This code is based Mike Pall's "Hashed profile counters" idea, implemented
* in LuaJIT. The full description may be found in "LuaJIT 2.0 intellectual
* property disclosure and research opportunities" email
* at http://lua-users.org/lists/lua-l/2009-11/msg00089.html
*
* In addition we use a variation of Knuth's multiplicative hash function
* described at https://code.i-harness.com/en/q/a21ce
*
* uint64_t hash(uint64_t x) {
* x = (x ^ (x >> 30)) * 0xbf58476d1ce4e5b9;
* x = (x ^ (x >> 27)) * 0x94d049bb133111eb;
* x = x ^ (x >> 31);
* return x;
* }
*
* uint_32_t hash(uint32_t x) {
* x = ((x >> 16) ^ x) * 0x45d9f3b;
* x = ((x >> 16) ^ x) * 0x45d9f3b;
* x = (x >> 16) ^ x;
* return x;
* }
*
*/
static int zend_jit_hybrid_hot_counter_stub(dasm_State **Dst, uint32_t cost)
{
| mov r0, EX->func
| mov r1, aword [r0 + offsetof(zend_op_array, reserved[zend_func_info_rid])]
| mov r2, aword [r1 + offsetof(zend_jit_op_array_hot_extension, counter)]
| sub word [r2], cost
| jle ->hybrid_hot_code
| GET_IP r2
| sub r2, aword [r0 + offsetof(zend_op_array, opcodes)]
| // divide by sizeof(zend_op)
| .if X64
|| ZEND_ASSERT(sizeof(zend_op) == 32);
| sar r2, 2
| .else
|| ZEND_ASSERT(sizeof(zend_op) == 28);
| imul r2, 0xb6db6db7
| .endif
| .if X64
| jmp aword [r1+r2+offsetof(zend_jit_op_array_hot_extension, orig_handlers)]
| .else
| jmp aword [r1+r2+offsetof(zend_jit_op_array_hot_extension, orig_handlers)]
| .endif
return 1;
}
static int zend_jit_hybrid_func_hot_counter_stub(dasm_State **Dst)
{
if (zend_jit_vm_kind != ZEND_VM_KIND_HYBRID || !JIT_G(hot_func)) {
return 1;
}
|->hybrid_func_hot_counter:
return zend_jit_hybrid_hot_counter_stub(Dst,
((ZEND_JIT_COUNTER_INIT + JIT_G(hot_func) - 1) / JIT_G(hot_func)));
}
static int zend_jit_hybrid_loop_hot_counter_stub(dasm_State **Dst)
{
if (zend_jit_vm_kind != ZEND_VM_KIND_HYBRID || !JIT_G(hot_loop)) {
return 1;
}
|->hybrid_loop_hot_counter:
return zend_jit_hybrid_hot_counter_stub(Dst,
((ZEND_JIT_COUNTER_INIT + JIT_G(hot_loop) - 1) / JIT_G(hot_loop)));
}
static int zend_jit_hybrid_hot_trace_stub(dasm_State **Dst)
{
if (zend_jit_vm_kind != ZEND_VM_KIND_HYBRID) {
return 1;
}
|->hybrid_hot_trace:
| mov word [r2], ZEND_JIT_COUNTER_INIT
| mov FCARG1a, FP
| GET_IP FCARG2a
| EXT_CALL zend_jit_trace_hot_root, r0
| test eax, eax // TODO : remove this check at least for HYBRID VM ???
| jl >1
| MEM_OP2_2_ZTS mov, FP, aword, executor_globals, current_execute_data, r0
| LOAD_IP
| JMP_IP
|1:
| EXT_JMP zend_jit_halt_op->handler, r0
return 1;
}
static int zend_jit_hybrid_trace_counter_stub(dasm_State **Dst, uint32_t cost)
{
| mov r0, EX->func
| mov r1, aword [r0 + offsetof(zend_op_array, reserved[zend_func_info_rid])]
| mov r1, aword [r1 + offsetof(zend_jit_op_array_trace_extension, offset)]
| mov r2, aword [IP + r1 + offsetof(zend_op_trace_info, counter)]
| sub word [r2], cost
| jle ->hybrid_hot_trace
| jmp aword [IP + r1]
return 1;
}
static int zend_jit_hybrid_func_trace_counter_stub(dasm_State **Dst)
{
if (zend_jit_vm_kind != ZEND_VM_KIND_HYBRID || !JIT_G(hot_func)) {
return 1;
}
|->hybrid_func_trace_counter:
return zend_jit_hybrid_trace_counter_stub(Dst,
((ZEND_JIT_COUNTER_INIT + JIT_G(hot_func) - 1) / JIT_G(hot_func)));
}
static int zend_jit_hybrid_ret_trace_counter_stub(dasm_State **Dst)
{
if (zend_jit_vm_kind != ZEND_VM_KIND_HYBRID || !JIT_G(hot_return)) {
return 1;
}
|->hybrid_ret_trace_counter:
return zend_jit_hybrid_trace_counter_stub(Dst,
((ZEND_JIT_COUNTER_INIT + JIT_G(hot_return) - 1) / JIT_G(hot_return)));
}
static int zend_jit_hybrid_loop_trace_counter_stub(dasm_State **Dst)
{
if (zend_jit_vm_kind != ZEND_VM_KIND_HYBRID || !JIT_G(hot_loop)) {
return 1;
}
|->hybrid_loop_trace_counter:
return zend_jit_hybrid_trace_counter_stub(Dst,
((ZEND_JIT_COUNTER_INIT + JIT_G(hot_loop) - 1) / JIT_G(hot_loop)));
}
static int zend_jit_trace_halt_stub(dasm_State **Dst)
{
|->trace_halt:
if (zend_jit_vm_kind == ZEND_VM_KIND_HYBRID) {
| ADD_HYBRID_SPAD
| EXT_JMP zend_jit_halt_op->handler, r0
} else if (GCC_GLOBAL_REGS) {
| add r4, SPAD // stack alignment
| xor IP, IP // PC must be zero
| ret
} else {
| mov FP, aword T2 // restore FP
| mov RX, aword T3 // restore IP
| add r4, NR_SPAD // stack alignment
| mov r0, -1 // ZEND_VM_RETURN
| ret
}
return 1;
}
static int zend_jit_trace_exit_stub(dasm_State **Dst)
{
|->trace_exit:
|
| // Save CPU registers
|.if X64
| sub r4, 16*8+16*8-8 /* CPU regs + SSE regs */
| mov aword [r4+15*8], r15
| mov aword [r4+11*8], r11
| mov aword [r4+10*8], r10
| mov aword [r4+9*8], r9
| mov aword [r4+8*8], r8
| mov aword [r4+7*8], rdi
| mov aword [r4+6*8], rsi
| mov aword [r4+2*8], rdx
| mov aword [r4+1*8], rcx
| mov aword [r4+0*8], rax
| mov FCARG1a, aword [r4+16*8+16*8-8] // exit_num = POP
| mov FCARG2a, r4
| movsd qword [r4+16*8+15*8], xmm15
| movsd qword [r4+16*8+14*8], xmm14
| movsd qword [r4+16*8+13*8], xmm13
| movsd qword [r4+16*8+12*8], xmm12
| movsd qword [r4+16*8+11*8], xmm11
| movsd qword [r4+16*8+10*8], xmm10
| movsd qword [r4+16*8+9*8], xmm9
| movsd qword [r4+16*8+8*8], xmm8
| movsd qword [r4+16*8+7*8], xmm7
| movsd qword [r4+16*8+6*8], xmm6
| movsd qword [r4+16*8+5*8], xmm5
| movsd qword [r4+16*8+4*8], xmm4
| movsd qword [r4+16*8+3*8], xmm3
| movsd qword [r4+16*8+2*8], xmm2
| movsd qword [r4+16*8+1*8], xmm1
| movsd qword [r4+16*8+0*8], xmm0
|.if X64WIN
| sub r4, 32 /* shadow space */
|.endif
|.else
| sub r4, 8*4+8*8-4 /* CPU regs + SSE regs */
| mov aword [r4+7*4], edi
| mov aword [r4+2*4], edx
| mov aword [r4+1*4], ecx
| mov aword [r4+0*4], eax
| mov FCARG1a, aword [r4+8*4+8*8-4] // exit_num = POP
| mov FCARG2a, r4
| movsd qword [r4+8*4+7*8], xmm7
| movsd qword [r4+8*4+6*8], xmm6
| movsd qword [r4+8*4+5*8], xmm5
| movsd qword [r4+8*4+4*8], xmm4
| movsd qword [r4+8*4+3*8], xmm3
| movsd qword [r4+8*4+2*8], xmm2
| movsd qword [r4+8*4+1*8], xmm1
| movsd qword [r4+8*4+0*8], xmm0
|.endif
|
| // EX(opline) = opline
| SAVE_IP
| // zend_jit_trace_exit(trace_num, exit_num)
| EXT_CALL zend_jit_trace_exit, r0
|.if X64WIN
| add r4, 16*8+16*8+32 /* CPU regs + SSE regs + shadow space */
|.elif X64
| add r4, 16*8+16*8 /* CPU regs + SSE regs */
|.else
| add r4, 8*4+8*8 /* CPU regs + SSE regs */
|.endif
| test eax, eax
| jne >1
| // execute_data = EG(current_execute_data)
| MEM_OP2_2_ZTS mov, FP, aword, executor_globals, current_execute_data, r0
| // opline = EX(opline)
| LOAD_IP
if (zend_jit_vm_kind == ZEND_VM_KIND_HYBRID) {
| ADD_HYBRID_SPAD
| JMP_IP
} else if (GCC_GLOBAL_REGS) {
| add r4, SPAD // stack alignment
| JMP_IP
} else {
| mov FP, aword T2 // restore FP
| mov RX, aword T3 // restore IP
| add r4, NR_SPAD // stack alignment
| mov r0, 1 // ZEND_VM_ENTER
| ret
}
|1:
| jl ->trace_halt
| // execute_data = EG(current_execute_data)
| MEM_OP2_2_ZTS mov, FP, aword, executor_globals, current_execute_data, r0
| // opline = EX(opline)
| LOAD_IP
| // check for interrupt (try to avoid this ???)
| MEM_OP2_1_ZTS cmp, byte, executor_globals, vm_interrupt, 0, r0
| jne ->interrupt_handler
if (zend_jit_vm_kind == ZEND_VM_KIND_HYBRID) {
| ADD_HYBRID_SPAD
| mov r0, EX->func
| mov r0, aword [r0 + offsetof(zend_op_array, reserved[zend_func_info_rid])]
| mov r0, aword [r0 + offsetof(zend_jit_op_array_trace_extension, offset)]
| jmp aword [IP + r0]
} else if (GCC_GLOBAL_REGS) {
| add r4, SPAD // stack alignment
| mov r0, EX->func
| mov r0, aword [r0 + offsetof(zend_op_array, reserved[zend_func_info_rid])]
| mov r0, aword [r0 + offsetof(zend_jit_op_array_trace_extension, offset)]
| jmp aword [IP + r0]
} else {
| mov IP, aword EX->opline
| mov FCARG1a, FP
| mov r0, EX->func
| mov r0, aword [r0 + offsetof(zend_op_array, reserved[zend_func_info_rid])]
| mov r0, aword [r0 + offsetof(zend_jit_op_array_trace_extension, offset)]
| call aword [IP + r0]
| test eax, eax
| jl ->trace_halt
| mov FP, aword T2 // restore FP
| mov RX, aword T3 // restore IP
| add r4, NR_SPAD // stack alignment
| mov r0, 1 // ZEND_VM_ENTER
| ret
}
return 1;
}
static int zend_jit_trace_escape_stub(dasm_State **Dst)
{
|->trace_escape:
|
if (zend_jit_vm_kind == ZEND_VM_KIND_HYBRID) {
| ADD_HYBRID_SPAD
| JMP_IP
} else if (GCC_GLOBAL_REGS) {
| add r4, SPAD // stack alignment
| JMP_IP
} else {
| mov FP, aword T2 // restore FP
| mov RX, aword T3 // restore IP
| add r4, NR_SPAD // stack alignment
| mov r0, 1 // ZEND_VM_ENTER
| ret
}
return 1;
}
/* Keep 32 exit points in a single code block */
#define ZEND_JIT_EXIT_POINTS_SPACING 4 // push byte + short jmp = bytes
#define ZEND_JIT_EXIT_POINTS_PER_GROUP 32 // number of continuous exit points
static int zend_jit_trace_exit_group_stub(dasm_State **Dst, uint32_t n)
{
uint32_t i;
for (i = 0; i < ZEND_JIT_EXIT_POINTS_PER_GROUP - 1; i++) {
| push byte i
| .byte 0xeb, (4*(ZEND_JIT_EXIT_POINTS_PER_GROUP-i)-6) // jmp >1
}
| push byte i
|// 1:
| add aword [r4], n
| jmp ->trace_exit
return 1;
}
#ifdef CONTEXT_THREADED_JIT
static int zend_jit_context_threaded_call_stub(dasm_State **Dst)
{
|->context_threaded_call:
| pop r0
if (zend_jit_vm_kind == ZEND_VM_KIND_HYBRID) {
| ADD_HYBRID_SPAD
| jmp aword [IP]
} else if (GCC_GLOBAL_REGS) {
| add r4, SPAD // stack alignment
| jmp aword [IP]
} else {
ZEND_UNREACHABLE();
// TODO: context threading can't work without GLOBAL REGS because we have to change
// the value of execute_data in execute_ex()
| mov FCARG1a, FP
| mov r0, aword [FP]
| mov FP, aword T2 // restore FP
| mov RX, aword T3 // restore IP
| add r4, NR_SPAD // stack alignment
| jmp aword [r0]
}
return 1;
}
#endif
static int zend_jit_assign_to_variable(dasm_State **Dst,
const zend_op *opline,
zend_jit_addr var_use_addr,
zend_jit_addr var_addr,
uint32_t var_info,
uint32_t var_def_info,
zend_uchar val_type,
zend_jit_addr val_addr,
uint32_t val_info,
zend_jit_addr res_addr,
zend_bool check_exception);
static int zend_jit_assign_const_stub(dasm_State **Dst)
{
zend_jit_addr var_addr = ZEND_ADDR_MEM_ZVAL(ZREG_FCARG1a, 0);
zend_jit_addr val_addr = ZEND_ADDR_MEM_ZVAL(ZREG_FCARG2a, 0);
uint32_t val_info = MAY_BE_ANY|MAY_BE_RC1|MAY_BE_RCN;
|->assign_const:
|.if X64WIN
| sub r4, 0x28
|.elif X64
| sub r4, 8
|.else
| sub r4, 12
|.endif
if (!zend_jit_assign_to_variable(
Dst, NULL,
var_addr, var_addr, -1, -1,
IS_CONST, val_addr, val_info,
0, 0)) {
return 0;
}
|.if X64WIN
| add r4, 0x28
|.elif X64
| add r4, 8
|.else
| add r4, 12
|.endif
| ret
return 1;
}
static int zend_jit_assign_tmp_stub(dasm_State **Dst)
{
zend_jit_addr var_addr = ZEND_ADDR_MEM_ZVAL(ZREG_FCARG1a, 0);
zend_jit_addr val_addr = ZEND_ADDR_MEM_ZVAL(ZREG_FCARG2a, 0);
uint32_t val_info = MAY_BE_ANY|MAY_BE_RC1|MAY_BE_RCN;
|->assign_tmp:
|.if X64WIN
| sub r4, 0x28
|.elif X64
| sub r4, 8
|.else
| sub r4, 12
|.endif
if (!zend_jit_assign_to_variable(
Dst, NULL,
var_addr, var_addr, -1, -1,
IS_TMP_VAR, val_addr, val_info,
0, 0)) {
return 0;
}
|.if X64WIN
| add r4, 0x28
|.elif X64
| add r4, 8
|.else
| add r4, 12
|.endif
| ret
return 1;
}
static int zend_jit_assign_var_stub(dasm_State **Dst)
{
zend_jit_addr var_addr = ZEND_ADDR_MEM_ZVAL(ZREG_FCARG1a, 0);
zend_jit_addr val_addr = ZEND_ADDR_MEM_ZVAL(ZREG_FCARG2a, 0);
uint32_t val_info = MAY_BE_ANY|MAY_BE_RC1|MAY_BE_RCN|MAY_BE_REF;
|->assign_var:
|.if X64WIN
| sub r4, 0x28
|.elif X64
| sub r4, 8
|.else
| sub r4, 12
|.endif
if (!zend_jit_assign_to_variable(
Dst, NULL,
var_addr, var_addr, -1, -1,
IS_VAR, val_addr, val_info,
0, 0)) {
return 0;
}
|.if X64WIN
| add r4, 0x28
|.elif X64
| add r4, 8
|.else
| add r4, 12
|.endif
| ret
return 1;
}
static int zend_jit_assign_cv_noref_stub(dasm_State **Dst)
{
zend_jit_addr var_addr = ZEND_ADDR_MEM_ZVAL(ZREG_FCARG1a, 0);
zend_jit_addr val_addr = ZEND_ADDR_MEM_ZVAL(ZREG_FCARG2a, 0);
uint32_t val_info = MAY_BE_ANY|MAY_BE_RC1|MAY_BE_RCN/*|MAY_BE_UNDEF*/;
|->assign_cv_noref:
|.if X64WIN
| sub r4, 0x28
|.elif X64
| sub r4, 8
|.else
| sub r4, 12
|.endif
if (!zend_jit_assign_to_variable(
Dst, NULL,
var_addr, var_addr, -1, -1,
IS_CV, val_addr, val_info,
0, 0)) {
return 0;
}
|.if X64WIN
| add r4, 0x28
|.elif X64
| add r4, 8
|.else
| add r4, 12
|.endif
| ret
return 1;
}
static int zend_jit_assign_cv_stub(dasm_State **Dst)
{
zend_jit_addr var_addr = ZEND_ADDR_MEM_ZVAL(ZREG_FCARG1a, 0);
zend_jit_addr val_addr = ZEND_ADDR_MEM_ZVAL(ZREG_FCARG2a, 0);
uint32_t val_info = MAY_BE_ANY|MAY_BE_RC1|MAY_BE_RCN|MAY_BE_REF/*|MAY_BE_UNDEF*/;
|->assign_cv:
|.if X64WIN
| sub r4, 0x28
|.elif X64
| sub r4, 8
|.else
| sub r4, 12
|.endif
if (!zend_jit_assign_to_variable(
Dst, NULL,
var_addr, var_addr, -1, -1,
IS_CV, val_addr, val_info,
0, 0)) {
return 0;
}
|.if X64WIN
| add r4, 0x28
|.elif X64
| add r4, 8
|.else
| add r4, 12
|.endif
| ret
return 1;
}
static const zend_jit_stub zend_jit_stubs[] = {
JIT_STUB(interrupt_handler),
JIT_STUB(exception_handler),
JIT_STUB(exception_handler_undef),
JIT_STUB(exception_handler_free_op1_op2),
JIT_STUB(exception_handler_free_op2),
JIT_STUB(leave_function),
JIT_STUB(leave_throw),
JIT_STUB(icall_throw),
JIT_STUB(throw_cannot_pass_by_ref),
JIT_STUB(undefined_offset),
JIT_STUB(undefined_index),
JIT_STUB(cannot_add_element),
JIT_STUB(undefined_offset_ex),
JIT_STUB(undefined_index_ex),
JIT_STUB(cannot_add_element_ex),
JIT_STUB(undefined_function),
JIT_STUB(negative_shift),
JIT_STUB(mod_by_zero),
JIT_STUB(invalid_this),
JIT_STUB(trace_halt),
JIT_STUB(trace_exit),
JIT_STUB(trace_escape),
JIT_STUB(hybrid_runtime_jit),
JIT_STUB(hybrid_profile_jit),
JIT_STUB(hybrid_hot_code),
JIT_STUB(hybrid_func_hot_counter),
JIT_STUB(hybrid_loop_hot_counter),
JIT_STUB(hybrid_hot_trace),
JIT_STUB(hybrid_func_trace_counter),
JIT_STUB(hybrid_ret_trace_counter),
JIT_STUB(hybrid_loop_trace_counter),
JIT_STUB(assign_const),
JIT_STUB(assign_tmp),
JIT_STUB(assign_var),
JIT_STUB(assign_cv_noref),
JIT_STUB(assign_cv),
JIT_STUB(double_one),
#ifdef CONTEXT_THREADED_JIT
JIT_STUB(context_threaded_call),
#endif
};
#if ZTS && defined(ZEND_WIN32)
extern uint32_t _tls_index;
extern char *_tls_start;
extern char *_tls_end;
#endif
static int zend_jit_setup(void)
{
if (!zend_cpu_supports_sse2()) {
zend_error(E_CORE_ERROR, "CPU doesn't support SSE2");
return FAILURE;
}
allowed_opt_flags = 0;
if (zend_cpu_supports_avx()) {
allowed_opt_flags |= ZEND_JIT_CPU_AVX;
}
#if ZTS
# ifdef _WIN64
tsrm_tls_index = _tls_index * sizeof(void*);
/* To find offset of "_tsrm_ls_cache" in TLS segment we perform a linear scan of local TLS memory */
/* Probably, it might be better solution */
do {
void ***tls_mem = ((void**)__readgsqword(0x58))[_tls_index];
void *val = _tsrm_ls_cache;
size_t offset = 0;
size_t size = (char*)&_tls_end - (char*)&_tls_start;
while (offset < size) {
if (*tls_mem == val) {
tsrm_tls_offset = offset;
break;
}
tls_mem++;
offset += sizeof(void*);
}
if (offset >= size) {
// TODO: error message ???
return FAILURE;
}
} while(0);
# elif ZEND_WIN32
tsrm_tls_index = _tls_index * sizeof(void*);
/* To find offset of "_tsrm_ls_cache" in TLS segment we perform a linear scan of local TLS memory */
/* Probably, it might be better solution */
do {
void ***tls_mem = ((void***)__readfsdword(0x2c))[_tls_index];
void *val = _tsrm_ls_cache;
size_t offset = 0;
size_t size = (char*)&_tls_end - (char*)&_tls_start;
while (offset < size) {
if (*tls_mem == val) {
tsrm_tls_offset = offset;
break;
}
tls_mem++;
offset += sizeof(void*);
}
if (offset >= size) {
// TODO: error message ???
return FAILURE;
}
} while(0);
# elif defined(__APPLE__) && defined(__x86_64__)
tsrm_ls_cache_tcb_offset = tsrm_get_ls_cache_tcb_offset();
if (tsrm_ls_cache_tcb_offset == 0) {
size_t *ti;
__asm__(
"leaq __tsrm_ls_cache(%%rip),%0"
: "=r" (ti));
tsrm_tls_offset = ti[2];
tsrm_tls_index = ti[1] * 8;
}
# elif defined(__GNUC__) && defined(__x86_64__)
tsrm_ls_cache_tcb_offset = tsrm_get_ls_cache_tcb_offset();
if (tsrm_ls_cache_tcb_offset == 0) {
#if defined(__has_attribute) && __has_attribute(tls_model) && !defined(__FreeBSD__) && !defined(__OpenBSD__) && !defined(__MUSL__)
size_t ret;
asm ("movq _tsrm_ls_cache@gottpoff(%%rip),%0"
: "=r" (ret));
tsrm_ls_cache_tcb_offset = ret;
#else
size_t *ti;
__asm__(
"leaq _tsrm_ls_cache@tlsgd(%%rip), %0\n"
: "=a" (ti));
tsrm_tls_offset = ti[1];
tsrm_tls_index = ti[0] * 16;
#endif
}
# elif defined(__GNUC__) && defined(__i386__)
tsrm_ls_cache_tcb_offset = tsrm_get_ls_cache_tcb_offset();
if (tsrm_ls_cache_tcb_offset == 0) {
#if !defined(__FreeBSD__) && !defined(__OpenBSD__) && !defined(__MUSL__)
size_t ret;
asm ("leal _tsrm_ls_cache@ntpoff,%0\n"
: "=a" (ret));
tsrm_ls_cache_tcb_offset = ret;
#else
size_t *ti, _ebx, _ecx, _edx;
__asm__(
"call 1f\n"
".subsection 1\n"
"1:\tmovl (%%esp), %%ebx\n\t"
"ret\n"
".previous\n\t"
"addl $_GLOBAL_OFFSET_TABLE_, %%ebx\n\t"
"leal _tsrm_ls_cache@tlsldm(%%ebx), %0\n\t"
"call ___tls_get_addr@plt\n\t"
"leal _tsrm_ls_cache@tlsldm(%%ebx), %0\n"
: "=a" (ti), "=&b" (_ebx), "=&c" (_ecx), "=&d" (_edx));
tsrm_tls_offset = ti[1];
tsrm_tls_index = ti[0] * 8;
#endif
}
# endif
#endif
return SUCCESS;
}
static ZEND_ATTRIBUTE_UNUSED int zend_jit_trap(dasm_State **Dst)
{
| int3
return 1;
}
static int zend_jit_align_func(dasm_State **Dst)
{
reuse_ip = 0;
delayed_call_chain = 0;
last_valid_opline = NULL;
use_last_vald_opline = 0;
track_last_valid_opline = 0;
jit_return_label = -1;
|.align 16
return 1;
}
static int zend_jit_prologue(dasm_State **Dst)
{
if (zend_jit_vm_kind == ZEND_VM_KIND_HYBRID) {
| SUB_HYBRID_SPAD
} else if (GCC_GLOBAL_REGS) {
| sub r4, SPAD // stack alignment
} else {
| sub r4, NR_SPAD // stack alignment
| mov aword T2, FP // save FP
| mov aword T3, RX // save IP
| mov FP, FCARG1a
}
return 1;
}
static int zend_jit_label(dasm_State **Dst, unsigned int label)
{
|=>label:
return 1;
}
static int zend_jit_save_call_chain(dasm_State **Dst, uint32_t call_level)
{
| // call->prev_execute_data = EX(call);
if (call_level == 1) {
| mov aword EX:RX->prev_execute_data, 0
} else {
| mov r0, EX->call
| mov EX:RX->prev_execute_data, r0
}
| // EX(call) = call;
| mov EX->call, RX
delayed_call_chain = 0;
return 1;
}
static int zend_jit_set_ip(dasm_State **Dst, const zend_op *opline)
{
if (last_valid_opline == opline) {
zend_jit_use_last_valid_opline();
} else if (GCC_GLOBAL_REGS && last_valid_opline) {
zend_jit_use_last_valid_opline();
| ADD_IP (opline - last_valid_opline) * sizeof(zend_op);
} else {
| LOAD_IP_ADDR opline
}
zend_jit_set_last_valid_opline(opline);
return 1;
}
static int zend_jit_set_ip_ex(dasm_State **Dst, const zend_op *opline, bool set_ip_reg)
{
if (last_valid_opline == opline) {
zend_jit_use_last_valid_opline();
} else if (GCC_GLOBAL_REGS && last_valid_opline) {
zend_jit_use_last_valid_opline();
| ADD_IP (opline - last_valid_opline) * sizeof(zend_op);
} else if (!GCC_GLOBAL_REGS && set_ip_reg) {
| LOAD_ADDR RX, opline
| mov aword EX->opline, RX
} else {
| LOAD_IP_ADDR opline
}
zend_jit_set_last_valid_opline(opline);
return 1;
}
static int zend_jit_set_valid_ip(dasm_State **Dst, const zend_op *opline)
{
if (delayed_call_chain) {
if (!zend_jit_save_call_chain(Dst, delayed_call_level)) {
return 0;
}
}
if (!zend_jit_set_ip(Dst, opline)) {
return 0;
}
reuse_ip = 0;
return 1;
}
static int zend_jit_check_timeout(dasm_State **Dst, const zend_op *opline, const void *exit_addr)
{
#if 0
if (!zend_jit_set_valid_ip(Dst, opline)) {
return 0;
}
| MEM_OP2_1_ZTS cmp, byte, executor_globals, vm_interrupt, 0, r0
| jne ->interrupt_handler
#else
| MEM_OP2_1_ZTS cmp, byte, executor_globals, vm_interrupt, 0, r0
if (exit_addr) {
| jne &exit_addr
} else if (last_valid_opline == opline) {
|| zend_jit_use_last_valid_opline();
| jne ->interrupt_handler
} else {
| jne >1
|.cold_code
|1:
| LOAD_IP_ADDR opline
| jmp ->interrupt_handler
|.code
}
#endif
return 1;
}
static int zend_jit_trace_end_loop(dasm_State **Dst, int loop_label, const void *timeout_exit_addr)
{
if (timeout_exit_addr) {
| MEM_OP2_1_ZTS cmp, byte, executor_globals, vm_interrupt, 0, r0
| je =>loop_label
| jmp &timeout_exit_addr
} else {
| jmp =>loop_label
}
return 1;
}
static int zend_jit_check_exception(dasm_State **Dst)
{
| MEM_OP2_1_ZTS cmp, aword, executor_globals, exception, 0, r0
| jne ->exception_handler
return 1;
}
static int zend_jit_check_exception_undef_result(dasm_State **Dst, const zend_op *opline)
{
if (opline->result_type & (IS_TMP_VAR|IS_VAR)) {
| MEM_OP2_1_ZTS cmp, aword, executor_globals, exception, 0, r0
| jne ->exception_handler_undef
return 1;
}
return zend_jit_check_exception(Dst);
}
static int zend_jit_trace_begin(dasm_State **Dst, uint32_t trace_num, zend_jit_trace_info *parent, uint32_t exit_num)
{
zend_regset regset = ZEND_REGSET_SCRATCH;
#if ZTS
if (1) {
#else
if ((sizeof(void*) == 8 && !IS_SIGNED_32BIT(&EG(jit_trace_num)))) {
#endif
/* assignment to EG(jit_trace_num) shouldn't clober CPU register used by deoptimizer */
if (parent) {
int i;
int parent_vars_count = parent->exit_info[exit_num].stack_size;
zend_jit_trace_stack *parent_stack =
parent->stack_map +
parent->exit_info[exit_num].stack_offset;
for (i = 0; i < parent_vars_count; i++) {
if (STACK_REG(parent_stack, i) != ZREG_NONE) {
if (STACK_REG(parent_stack, i) < ZREG_NUM) {
ZEND_REGSET_EXCL(regset, STACK_REG(parent_stack, i));
} else if (STACK_REG(parent_stack, i) == ZREG_ZVAL_COPY_R0) {
ZEND_REGSET_EXCL(regset, ZREG_R0);
}
}
}
}
}
if (parent && parent->exit_info[exit_num].flags & ZEND_JIT_EXIT_METHOD_CALL) {
ZEND_REGSET_EXCL(regset, ZREG_R0);
}
current_trace_num = trace_num;
| // EG(jit_trace_num) = trace_num;
if (regset == ZEND_REGSET_EMPTY) {
| push r0
| MEM_OP2_1_ZTS mov, dword, executor_globals, jit_trace_num, trace_num, r0
| pop r0
} else {
zend_reg tmp = ZEND_REGSET_FIRST(regset);
| MEM_OP2_1_ZTS mov, dword, executor_globals, jit_trace_num, trace_num, Ra(tmp)
(void)tmp;
}
return 1;
}
/* This taken from LuaJIT. Thanks to Mike Pall. */
static uint32_t _asm_x86_inslen(const uint8_t* p)
{
static const uint8_t map_op1[256] = {
0x92,0x92,0x92,0x92,0x52,0x45,0x51,0x51,0x92,0x92,0x92,0x92,0x52,0x45,0x51,0x20,
0x92,0x92,0x92,0x92,0x52,0x45,0x51,0x51,0x92,0x92,0x92,0x92,0x52,0x45,0x51,0x51,
0x92,0x92,0x92,0x92,0x52,0x45,0x10,0x51,0x92,0x92,0x92,0x92,0x52,0x45,0x10,0x51,
0x92,0x92,0x92,0x92,0x52,0x45,0x10,0x51,0x92,0x92,0x92,0x92,0x52,0x45,0x10,0x51,
#if defined(__x86_64__) || defined(_M_X64)
0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x14,0x14,0x14,0x14,0x14,0x14,0x14,0x14,
#else
0x51,0x51,0x51,0x51,0x51,0x51,0x51,0x51,0x51,0x51,0x51,0x51,0x51,0x51,0x51,0x51,
#endif
0x51,0x51,0x51,0x51,0x51,0x51,0x51,0x51,0x51,0x51,0x51,0x51,0x51,0x51,0x51,0x51,
0x51,0x51,0x92,0x92,0x10,0x10,0x12,0x11,0x45,0x86,0x52,0x93,0x51,0x51,0x51,0x51,
0x52,0x52,0x52,0x52,0x52,0x52,0x52,0x52,0x52,0x52,0x52,0x52,0x52,0x52,0x52,0x52,
0x93,0x86,0x93,0x93,0x92,0x92,0x92,0x92,0x92,0x92,0x92,0x92,0x92,0x92,0x92,0x92,
0x51,0x51,0x51,0x51,0x51,0x51,0x51,0x51,0x51,0x51,0x47,0x51,0x51,0x51,0x51,0x51,
#if defined(__x86_64__) || defined(_M_X64)
0x59,0x59,0x59,0x59,0x51,0x51,0x51,0x51,0x52,0x45,0x51,0x51,0x51,0x51,0x51,0x51,
#else
0x55,0x55,0x55,0x55,0x51,0x51,0x51,0x51,0x52,0x45,0x51,0x51,0x51,0x51,0x51,0x51,
#endif
0x52,0x52,0x52,0x52,0x52,0x52,0x52,0x52,0x05,0x05,0x05,0x05,0x05,0x05,0x05,0x05,
0x93,0x93,0x53,0x51,0x70,0x71,0x93,0x86,0x54,0x51,0x53,0x51,0x51,0x52,0x51,0x51,
0x92,0x92,0x92,0x92,0x52,0x52,0x51,0x51,0x92,0x92,0x92,0x92,0x92,0x92,0x92,0x92,
0x52,0x52,0x52,0x52,0x52,0x52,0x52,0x52,0x45,0x45,0x47,0x52,0x51,0x51,0x51,0x51,
0x10,0x51,0x10,0x10,0x51,0x51,0x63,0x66,0x51,0x51,0x51,0x51,0x51,0x51,0x92,0x92
};
static const uint8_t map_op2[256] = {
0x93,0x93,0x93,0x93,0x52,0x52,0x52,0x52,0x52,0x52,0x51,0x52,0x51,0x93,0x52,0x94,
0x93,0x93,0x93,0x93,0x93,0x93,0x93,0x93,0x93,0x93,0x93,0x93,0x93,0x93,0x93,0x93,
0x53,0x53,0x53,0x53,0x53,0x53,0x53,0x53,0x93,0x93,0x93,0x93,0x93,0x93,0x93,0x93,
0x52,0x52,0x52,0x52,0x52,0x52,0x52,0x52,0x34,0x51,0x35,0x51,0x51,0x51,0x51,0x51,
0x93,0x93,0x93,0x93,0x93,0x93,0x93,0x93,0x93,0x93,0x93,0x93,0x93,0x93,0x93,0x93,
0x53,0x93,0x93,0x93,0x93,0x93,0x93,0x93,0x93,0x93,0x93,0x93,0x93,0x93,0x93,0x93,
0x93,0x93,0x93,0x93,0x93,0x93,0x93,0x93,0x93,0x93,0x93,0x93,0x93,0x93,0x93,0x93,
0x94,0x54,0x54,0x54,0x93,0x93,0x93,0x52,0x93,0x93,0x93,0x93,0x93,0x93,0x93,0x93,
0x46,0x46,0x46,0x46,0x46,0x46,0x46,0x46,0x46,0x46,0x46,0x46,0x46,0x46,0x46,0x46,
0x93,0x93,0x93,0x93,0x93,0x93,0x93,0x93,0x93,0x93,0x93,0x93,0x93,0x93,0x93,0x93,
0x52,0x52,0x52,0x93,0x94,0x93,0x51,0x51,0x52,0x52,0x52,0x93,0x94,0x93,0x93,0x93,
0x93,0x93,0x93,0x93,0x93,0x93,0x93,0x93,0x93,0x93,0x94,0x93,0x93,0x93,0x93,0x93,
0x93,0x93,0x94,0x93,0x94,0x94,0x94,0x93,0x52,0x52,0x52,0x52,0x52,0x52,0x52,0x52,
0x93,0x93,0x93,0x93,0x93,0x93,0x93,0x93,0x93,0x93,0x93,0x93,0x93,0x93,0x93,0x93,
0x93,0x93,0x93,0x93,0x93,0x93,0x93,0x93,0x93,0x93,0x93,0x93,0x93,0x93,0x93,0x93,
0x93,0x93,0x93,0x93,0x93,0x93,0x93,0x93,0x93,0x93,0x93,0x93,0x93,0x93,0x93,0x52
};
uint32_t result = 0;
uint32_t prefixes = 0;
uint32_t x = map_op1[*p];
for (;;) {
switch (x >> 4) {
case 0:
return result + x + (prefixes & 4);
case 1:
prefixes |= x;
x = map_op1[*++p];
result++;
break;
case 2:
x = map_op2[*++p];
break;
case 3:
p++;
goto mrm;
case 4:
result -= (prefixes & 2);
/* fallthrough */
case 5:
return result + (x & 15);
case 6: /* Group 3. */
if (p[1] & 0x38) {
x = 2;
} else if ((prefixes & 2) && (x == 0x66)) {
x = 4;
}
goto mrm;
case 7: /* VEX c4/c5. */
#if !defined(__x86_64__) && !defined(_M_X64)
if (p[1] < 0xc0) {
x = 2;
goto mrm;
}
#endif
if (x == 0x70) {
x = *++p & 0x1f;
result++;
if (x >= 2) {
p += 2;
result += 2;
goto mrm;
}
}
p++;
result++;
x = map_op2[*++p];
break;
case 8:
result -= (prefixes & 2);
/* fallthrough */
case 9:
mrm:
/* ModR/M and possibly SIB. */
result += (x & 15);
x = *++p;
switch (x >> 6) {
case 0:
if ((x & 7) == 5) {
return result + 4;
}
break;
case 1:
result++;
break;
case 2:
result += 4;
break;
case 3:
return result;
}
if ((x & 7) == 4) {
result++;
if (x < 0x40 && (p[1] & 7) == 5) {
result += 4;
}
}
return result;
}
}
}
typedef ZEND_SET_ALIGNED(1, uint16_t unaligned_uint16_t);
typedef ZEND_SET_ALIGNED(1, int32_t unaligned_int32_t);
static int zend_jit_patch(const void *code, size_t size, uint32_t jmp_table_size, const void *from_addr, const void *to_addr)
{
int ret = 0;
uint8_t *p, *end;
if (jmp_table_size) {
const void **jmp_slot = (const void **)((char*)code + size);
size -= jmp_table_size * sizeof(void*);
do {
jmp_slot--;
if (*jmp_slot == from_addr) {
*jmp_slot = to_addr;
ret++;
}
} while (--jmp_table_size);
}
p = (uint8_t*)code;
end = p + size - 5;
while (p < end) {
if ((*(unaligned_uint16_t*)p & 0xf0ff) == 0x800f && p + *(unaligned_int32_t*)(p+2) == (uint8_t*)from_addr - 6) {
*(unaligned_int32_t*)(p+2) = ((uint8_t*)to_addr - (p + 6));
ret++;
} else if (*p == 0xe9 && p + *(unaligned_int32_t*)(p+1) == (uint8_t*)from_addr - 5) {
*(unaligned_int32_t*)(p+1) = ((uint8_t*)to_addr - (p + 5));
ret++;
}
p += _asm_x86_inslen(p);
}
#ifdef HAVE_VALGRIND
VALGRIND_DISCARD_TRANSLATIONS(code, size);
#endif
return ret;
}
static int zend_jit_link_side_trace(const void *code, size_t size, uint32_t jmp_table_size, uint32_t exit_num, const void *addr)
{
return zend_jit_patch(code, size, jmp_table_size, zend_jit_trace_get_exit_addr(exit_num), addr);
}
static int zend_jit_trace_link_to_root(dasm_State **Dst, zend_jit_trace_info *t, const void *timeout_exit_addr)
{
const void *link_addr;
size_t prologue_size;
/* Skip prologue. */
// TODO: don't hardcode this ???
if (zend_jit_vm_kind == ZEND_VM_KIND_HYBRID) {
#ifdef ZEND_VM_HYBRID_JIT_RED_ZONE_SIZE
prologue_size = 0;
#elif defined(__x86_64__) || defined(_M_X64)
// sub r4, HYBRID_SPAD
prologue_size = 4;
#else
// sub r4, HYBRID_SPAD
prologue_size = 3;
#endif
} else if (GCC_GLOBAL_REGS) {
// sub r4, SPAD // stack alignment
#if defined(__x86_64__) || defined(_M_X64)
prologue_size = 4;
#else
prologue_size = 3;
#endif
} else {
// sub r4, NR_SPAD // stack alignment
// mov aword T2, FP // save FP
// mov aword T3, RX // save IP
// mov FP, FCARG1a
#if defined(__x86_64__) || defined(_M_X64)
prologue_size = 17;
#else
prologue_size = 13;
#endif
}
link_addr = (const void*)((const char*)t->code_start + prologue_size);
if (timeout_exit_addr) {
/* Check timeout for links to LOOP */
| MEM_OP2_1_ZTS cmp, byte, executor_globals, vm_interrupt, 0, r0
| je &link_addr
| jmp &timeout_exit_addr
} else {
| jmp &link_addr
}
return 1;
}
static int zend_jit_trace_return(dasm_State **Dst, zend_bool original_handler, const zend_op *opline)
{
#if 0
| jmp ->trace_escape
#else
if (zend_jit_vm_kind == ZEND_VM_KIND_HYBRID) {
| ADD_HYBRID_SPAD
if (!original_handler) {
| JMP_IP
} else {
| mov r0, EX->func
| mov r0, aword [r0 + offsetof(zend_op_array, reserved[zend_func_info_rid])]
| mov r0, aword [r0 + offsetof(zend_jit_op_array_trace_extension, offset)]
| jmp aword [IP + r0]
}
} else if (GCC_GLOBAL_REGS) {
| add r4, SPAD // stack alignment
if (!original_handler) {
| JMP_IP
} else {
| mov r0, EX->func
| mov r0, aword [r0 + offsetof(zend_op_array, reserved[zend_func_info_rid])]
| mov r0, aword [r0 + offsetof(zend_jit_op_array_trace_extension, offset)]
| jmp aword [IP + r0]
}
} else {
if (original_handler) {
| mov FCARG1a, FP
| mov r0, EX->func
| mov r0, aword [r0 + offsetof(zend_op_array, reserved[zend_func_info_rid])]
| mov r0, aword [r0 + offsetof(zend_jit_op_array_trace_extension, offset)]
| call aword [IP + r0]
}
| mov FP, aword T2 // restore FP
| mov RX, aword T3 // restore IP
| add r4, NR_SPAD // stack alignment
if (!original_handler || !opline ||
(opline->opcode != ZEND_RETURN
&& opline->opcode != ZEND_RETURN_BY_REF
&& opline->opcode != ZEND_GENERATOR_RETURN
&& opline->opcode != ZEND_GENERATOR_CREATE
&& opline->opcode != ZEND_YIELD
&& opline->opcode != ZEND_YIELD_FROM)) {
| mov r0, 2 // ZEND_VM_LEAVE
}
| ret
}
#endif
return 1;
}
static int zend_jit_type_guard(dasm_State **Dst, const zend_op *opline, uint32_t var, uint8_t type)
{
int32_t exit_point = zend_jit_trace_get_exit_point(opline, 0);
const void *exit_addr = zend_jit_trace_get_exit_addr(exit_point);
if (!exit_addr) {
return 0;
}
| IF_NOT_Z_TYPE FP + var, type, &exit_addr
return 1;
}
static int zend_jit_packed_guard(dasm_State **Dst, const zend_op *opline, uint32_t var, uint32_t op_info)
{
int32_t exit_point = zend_jit_trace_get_exit_point(opline, ZEND_JIT_EXIT_PACKED_GUARD);
const void *exit_addr = zend_jit_trace_get_exit_addr(exit_point);
if (!exit_addr) {
return 0;
}
| GET_ZVAL_LVAL ZREG_FCARG1a, ZEND_ADDR_MEM_ZVAL(ZREG_FP, var)
if (op_info & MAY_BE_ARRAY_PACKED) {
| test dword [FCARG1a + offsetof(zend_array, u.flags)], HASH_FLAG_PACKED
| jz &exit_addr
} else {
| test dword [FCARG1a + offsetof(zend_array, u.flags)], HASH_FLAG_PACKED
| jnz &exit_addr
}
return 1;
}
static int zend_jit_trace_handler(dasm_State **Dst, const zend_op_array *op_array, const zend_op *opline, int may_throw, zend_jit_trace_rec *trace)
{
zend_jit_op_array_trace_extension *jit_extension =
(zend_jit_op_array_trace_extension*)ZEND_FUNC_INFO(op_array);
size_t offset = jit_extension->offset;
const void *handler =
(zend_vm_opcode_handler_t)ZEND_OP_TRACE_INFO(opline, offset)->call_handler;
if (!zend_jit_set_valid_ip(Dst, opline)) {
return 0;
}
if (!GCC_GLOBAL_REGS) {
| mov FCARG1a, FP
}
| EXT_CALL handler, r0
if (may_throw
&& opline->opcode != ZEND_RETURN
&& opline->opcode != ZEND_RETURN_BY_REF) {
| MEM_OP2_1_ZTS cmp, aword, executor_globals, exception, 0, r1
| jne ->exception_handler
}
while (trace->op != ZEND_JIT_TRACE_VM && trace->op != ZEND_JIT_TRACE_END) {
trace++;
}
if (!GCC_GLOBAL_REGS
&& (trace->op != ZEND_JIT_TRACE_END || trace->stop != ZEND_JIT_TRACE_STOP_RETURN)) {
if (opline->opcode == ZEND_RETURN ||
opline->opcode == ZEND_RETURN_BY_REF ||
opline->opcode == ZEND_DO_UCALL ||
opline->opcode == ZEND_DO_FCALL_BY_NAME ||
opline->opcode == ZEND_DO_FCALL ||
opline->opcode == ZEND_GENERATOR_CREATE) {
| MEM_OP2_2_ZTS mov, FP, aword, executor_globals, current_execute_data, r1
}
}
if (zend_jit_trace_may_exit(op_array, opline)) {
if (opline->opcode == ZEND_RETURN ||
opline->opcode == ZEND_RETURN_BY_REF ||
opline->opcode == ZEND_GENERATOR_CREATE) {
if (zend_jit_vm_kind == ZEND_VM_KIND_HYBRID) {
#if 0
/* this check should be handled by the following OPLINE guard or jmp [IP] */
| cmp IP, zend_jit_halt_op
| je ->trace_halt
#endif
} else if (GCC_GLOBAL_REGS) {
| test IP, IP
| je ->trace_halt
} else {
| test eax, eax
| jl ->trace_halt
}
} else if (opline->opcode == ZEND_EXIT ||
opline->opcode == ZEND_GENERATOR_RETURN ||
opline->opcode == ZEND_YIELD ||
opline->opcode == ZEND_YIELD_FROM) {
| jmp ->trace_halt
}
if (trace->op != ZEND_JIT_TRACE_END ||
(trace->stop != ZEND_JIT_TRACE_STOP_RETURN &&
trace->stop != ZEND_JIT_TRACE_STOP_INTERPRETER)) {
const zend_op *next_opline = trace->opline;
const zend_op *exit_opline = NULL;
uint32_t exit_point;
const void *exit_addr;
uint32_t old_info = 0;
uint32_t old_res_info = 0;
zend_jit_trace_stack *stack = JIT_G(current_frame)->stack;
if (zend_is_smart_branch(opline)) {
zend_bool exit_if_true = 0;
exit_opline = zend_jit_trace_get_exit_opline(trace, opline + 1, &exit_if_true);
} else {
switch (opline->opcode) {
case ZEND_JMPZ:
case ZEND_JMPNZ:
case ZEND_JMPZ_EX:
case ZEND_JMPNZ_EX:
case ZEND_JMP_SET:
case ZEND_COALESCE:
case ZEND_JMP_NULL:
case ZEND_FE_RESET_R:
case ZEND_FE_RESET_RW:
exit_opline = (trace->opline == opline + 1) ?
OP_JMP_ADDR(opline, opline->op2) :
opline + 1;
break;
case ZEND_JMPZNZ:
exit_opline = (trace->opline == OP_JMP_ADDR(opline, opline->op2)) ?
ZEND_OFFSET_TO_OPLINE(opline, opline->extended_value) :
OP_JMP_ADDR(opline, opline->op2);
break;
case ZEND_FE_FETCH_R:
case ZEND_FE_FETCH_RW:
exit_opline = (trace->opline == opline + 1) ?
ZEND_OFFSET_TO_OPLINE(opline, opline->extended_value) :
opline + 1;
break;
}
}
switch (opline->opcode) {
case ZEND_FE_FETCH_R:
case ZEND_FE_FETCH_RW:
if (opline->op2_type != IS_UNUSED) {
old_info = STACK_INFO(stack, EX_VAR_TO_NUM(opline->op2.var));
SET_STACK_TYPE(stack, EX_VAR_TO_NUM(opline->op2.var), IS_UNKNOWN, 1);
}
break;
}
if (opline->result_type == IS_VAR || opline->result_type == IS_TMP_VAR) {
old_res_info = STACK_INFO(stack, EX_VAR_TO_NUM(opline->result.var));
SET_STACK_TYPE(stack, EX_VAR_TO_NUM(opline->result.var), IS_UNKNOWN, 1);
}
exit_point = zend_jit_trace_get_exit_point(exit_opline, 0);
exit_addr = zend_jit_trace_get_exit_addr(exit_point);
if (opline->result_type == IS_VAR || opline->result_type == IS_TMP_VAR) {
SET_STACK_INFO(stack, EX_VAR_TO_NUM(opline->result.var), old_res_info);
}
switch (opline->opcode) {
case ZEND_FE_FETCH_R:
case ZEND_FE_FETCH_RW:
if (opline->op2_type != IS_UNUSED) {
SET_STACK_INFO(stack, EX_VAR_TO_NUM(opline->op2.var), old_info);
}
break;
}
if (!exit_addr) {
return 0;
}
| CMP_IP next_opline
| jne &exit_addr
}
}
zend_jit_set_last_valid_opline(trace->opline);
return 1;
}
static int zend_jit_handler(dasm_State **Dst, const zend_op *opline, int may_throw)
{
const void *handler;
if (zend_jit_vm_kind == ZEND_VM_KIND_HYBRID) {
handler = zend_get_opcode_handler_func(opline);
} else {
handler = opline->handler;
}
if (!zend_jit_set_valid_ip(Dst, opline)) {
return 0;
}
if (!GCC_GLOBAL_REGS) {
| mov FCARG1a, FP
}
| EXT_CALL handler, r0
if (may_throw) {
zend_jit_check_exception(Dst);
}
/* Skip the following OP_DATA */
switch (opline->opcode) {
case ZEND_ASSIGN_DIM:
case ZEND_ASSIGN_OBJ:
case ZEND_ASSIGN_STATIC_PROP:
case ZEND_ASSIGN_DIM_OP:
case ZEND_ASSIGN_OBJ_OP:
case ZEND_ASSIGN_STATIC_PROP_OP:
case ZEND_ASSIGN_STATIC_PROP_REF:
case ZEND_ASSIGN_OBJ_REF:
zend_jit_set_last_valid_opline(opline + 2);
break;
default:
zend_jit_set_last_valid_opline(opline + 1);
break;
}
return 1;
}
static int zend_jit_tail_handler(dasm_State **Dst, const zend_op *opline)
{
if (!zend_jit_set_valid_ip(Dst, opline)) {
return 0;
}
if (zend_jit_vm_kind == ZEND_VM_KIND_HYBRID) {
if (opline->opcode == ZEND_DO_UCALL ||
opline->opcode == ZEND_DO_FCALL_BY_NAME ||
opline->opcode == ZEND_DO_FCALL ||
opline->opcode == ZEND_RETURN) {
/* Use inlined HYBRID VM handler */
const void *handler = opline->handler;
| ADD_HYBRID_SPAD
| EXT_JMP handler, r0
} else {
const void *handler = zend_get_opcode_handler_func(opline);
| EXT_CALL handler, r0
| ADD_HYBRID_SPAD
| JMP_IP
}
} else {
const void *handler = opline->handler;
if (GCC_GLOBAL_REGS) {
| add r4, SPAD // stack alignment
} else {
| mov FCARG1a, FP
| mov FP, aword T2 // restore FP
| mov RX, aword T3 // restore IP
| add r4, NR_SPAD // stack alignment
}
| EXT_JMP handler, r0
}
zend_jit_reset_last_valid_opline();
return 1;
}
static int zend_jit_trace_opline_guard(dasm_State **Dst, const zend_op *opline)
{
uint32_t exit_point = zend_jit_trace_get_exit_point(NULL, 0);
const void *exit_addr = zend_jit_trace_get_exit_addr(exit_point);
if (!exit_addr) {
return 0;
}
| CMP_IP opline
| jne &exit_addr
zend_jit_set_last_valid_opline(opline);
return 1;
}
static int zend_jit_jmp(dasm_State **Dst, unsigned int target_label)
{
| jmp =>target_label
return 1;
}
static int zend_jit_cond_jmp(dasm_State **Dst, const zend_op *next_opline, unsigned int target_label)
{
| CMP_IP next_opline
| jne =>target_label
zend_jit_set_last_valid_opline(next_opline);
return 1;
}
#ifdef CONTEXT_THREADED_JIT
static int zend_jit_context_threaded_call(dasm_State **Dst, const zend_op *opline, unsigned int next_block)
{
if (!zend_jit_handler(Dst, opline, 1)) return 0;
if (opline->opcode == ZEND_DO_UCALL) {
| call ->context_threaded_call
} else {
const zend_op *next_opline = opline + 1;
| CMP_IP next_opline
| je =>next_block
| call ->context_threaded_call
}
return 1;
}
#endif
static int zend_jit_call(dasm_State **Dst, const zend_op *opline, unsigned int next_block)
{
#ifdef CONTEXT_THREADED_JIT
return zend_jit_context_threaded_call(Dst, opline, next_block);
#else
return zend_jit_tail_handler(Dst, opline);
#endif
}
static int zend_jit_spill_store(dasm_State **Dst, zend_jit_addr src, zend_jit_addr dst, uint32_t info, zend_bool set_type)
{
ZEND_ASSERT(Z_MODE(src) == IS_REG);
ZEND_ASSERT(Z_MODE(dst) == IS_MEM_ZVAL);
if ((info & MAY_BE_ANY) == MAY_BE_LONG) {
| SET_ZVAL_LVAL dst, Ra(Z_REG(src))
if (set_type) {
| SET_ZVAL_TYPE_INFO dst, IS_LONG
}
} else if ((info & MAY_BE_ANY) == MAY_BE_DOUBLE) {
| SSE_SET_ZVAL_DVAL dst, Z_REG(src)
if (set_type) {
| SET_ZVAL_TYPE_INFO dst, IS_DOUBLE
}
} else {
ZEND_UNREACHABLE();
}
return 1;
}
static int zend_jit_load_reg(dasm_State **Dst, zend_jit_addr src, zend_jit_addr dst, uint32_t info)
{
ZEND_ASSERT(Z_MODE(src) == IS_MEM_ZVAL);
ZEND_ASSERT(Z_MODE(dst) == IS_REG);
if ((info & MAY_BE_ANY) == MAY_BE_LONG) {
| GET_ZVAL_LVAL Z_REG(dst), src
} else if ((info & MAY_BE_ANY) == MAY_BE_DOUBLE) {
| SSE_GET_ZVAL_DVAL Z_REG(dst), src
} else {
ZEND_UNREACHABLE();
}
return 1;
}
static int zend_jit_store_var(dasm_State **Dst, uint32_t info, int var, zend_reg reg, zend_bool set_type)
{
zend_jit_addr src = ZEND_ADDR_REG(reg);
zend_jit_addr dst = ZEND_ADDR_MEM_ZVAL(ZREG_FP, EX_NUM_TO_VAR(var));
return zend_jit_spill_store(Dst, src, dst, info, set_type);
}
static int zend_jit_store_var_type(dasm_State **Dst, int var, uint8_t type)
{
zend_jit_addr dst = ZEND_ADDR_MEM_ZVAL(ZREG_FP, EX_NUM_TO_VAR(var));
| SET_ZVAL_TYPE_INFO dst, type
return 1;
}
static int zend_jit_store_var_if_necessary(dasm_State **Dst, int var, zend_jit_addr src, uint32_t info)
{
if (Z_MODE(src) == IS_REG && Z_STORE(src)) {
zend_jit_addr dst = ZEND_ADDR_MEM_ZVAL(ZREG_FP, var);
return zend_jit_spill_store(Dst, src, dst, info, 1);
}
return 1;
}
static int zend_jit_store_var_if_necessary_ex(dasm_State **Dst, int var, zend_jit_addr src, uint32_t info, zend_jit_addr old, uint32_t old_info)
{
if (Z_MODE(src) == IS_REG && Z_STORE(src)) {
zend_jit_addr dst = ZEND_ADDR_MEM_ZVAL(ZREG_FP, var);
zend_bool set_type = 1;
if ((info & (MAY_BE_ANY|MAY_BE_REF|MAY_BE_UNDEF)) ==
(old_info & (MAY_BE_ANY|MAY_BE_REF|MAY_BE_UNDEF))) {
if (Z_MODE(old) != IS_REG || Z_LOAD(old) || Z_STORE(old)) {
set_type = 0;
}
}
return zend_jit_spill_store(Dst, src, dst, info, set_type);
}
return 1;
}
static int zend_jit_load_var(dasm_State **Dst, uint32_t info, int var, zend_reg reg)
{
zend_jit_addr src = ZEND_ADDR_MEM_ZVAL(ZREG_FP, EX_NUM_TO_VAR(var));
zend_jit_addr dst = ZEND_ADDR_REG(reg);
return zend_jit_load_reg(Dst, src, dst, info);
}
static int zend_jit_invalidate_var_if_necessary(dasm_State **Dst, zend_uchar op_type, zend_jit_addr addr, znode_op op)
{
if ((op_type & (IS_TMP_VAR|IS_VAR)) && Z_MODE(addr) == IS_REG && !Z_LOAD(addr) && !Z_STORE(addr)) {
zend_jit_addr dst = ZEND_ADDR_MEM_ZVAL(ZREG_FP, op.var);
| SET_ZVAL_TYPE_INFO dst, IS_UNDEF
}
return 1;
}
static int zend_jit_update_regs(dasm_State **Dst, uint32_t var, zend_jit_addr src, zend_jit_addr dst, uint32_t info)
{
if (!zend_jit_same_addr(src, dst)) {
if (Z_MODE(src) == IS_REG) {
if (Z_MODE(dst) == IS_REG) {
if ((info & MAY_BE_ANY) == MAY_BE_LONG) {
| mov Ra(Z_REG(dst)), Ra(Z_REG(src))
} else if ((info & MAY_BE_ANY) == MAY_BE_DOUBLE) {
| SSE_AVX_INS movaps, vmovaps, xmm(Z_REG(dst)-ZREG_XMM0), xmm(Z_REG(src)-ZREG_XMM0)
} else {
ZEND_UNREACHABLE();
}
if (!Z_LOAD(src) && !Z_STORE(src) && Z_STORE(dst)) {
zend_jit_addr var_addr = ZEND_ADDR_MEM_ZVAL(ZREG_FP, var);
if (!zend_jit_spill_store(Dst, dst, var_addr, info,
JIT_G(trigger) != ZEND_JIT_ON_HOT_TRACE ||
JIT_G(current_frame) == NULL ||
STACK_MEM_TYPE(JIT_G(current_frame)->stack, EX_VAR_TO_NUM(var)) == IS_UNKNOWN ||
(1 << STACK_MEM_TYPE(JIT_G(current_frame)->stack, EX_VAR_TO_NUM(var))) != (info & MAY_BE_ANY)
)) {
return 0;
}
}
} else if (Z_MODE(dst) == IS_MEM_ZVAL) {
if (!Z_LOAD(src) && !Z_STORE(src)) {
if (!zend_jit_spill_store(Dst, src, dst, info,
JIT_G(trigger) != ZEND_JIT_ON_HOT_TRACE ||
JIT_G(current_frame) == NULL ||
STACK_MEM_TYPE(JIT_G(current_frame)->stack, EX_VAR_TO_NUM(var)) == IS_UNKNOWN ||
(1 << STACK_MEM_TYPE(JIT_G(current_frame)->stack, EX_VAR_TO_NUM(var))) != (info & MAY_BE_ANY)
)) {
return 0;
}
}
} else {
ZEND_UNREACHABLE();
}
} else if (Z_MODE(src) == IS_MEM_ZVAL) {
if (Z_MODE(dst) == IS_REG) {
if (!zend_jit_load_reg(Dst, src, dst, info)) {
return 0;
}
} else {
ZEND_UNREACHABLE();
}
} else {
ZEND_UNREACHABLE();
}
} else if (Z_MODE(dst) == IS_REG && Z_STORE(dst)) {
dst = ZEND_ADDR_MEM_ZVAL(ZREG_FP, var);
if (!zend_jit_spill_store(Dst, src, dst, info,
JIT_G(trigger) != ZEND_JIT_ON_HOT_TRACE ||
JIT_G(current_frame) == NULL ||
STACK_MEM_TYPE(JIT_G(current_frame)->stack, EX_VAR_TO_NUM(var)) == IS_UNKNOWN ||
(1 << STACK_MEM_TYPE(JIT_G(current_frame)->stack, EX_VAR_TO_NUM(var))) != (info & MAY_BE_ANY)
)) {
return 0;
}
}
return 1;
}
static int zend_jit_escape_if_undef_r0(dasm_State **Dst, int var, uint32_t flags, const zend_op *opline)
{
zend_jit_addr val_addr = ZEND_ADDR_MEM_ZVAL(ZREG_R0, 0);
| IF_NOT_ZVAL_TYPE val_addr, IS_UNDEF, >1
if (flags & ZEND_JIT_EXIT_RESTORE_CALL) {
if (!zend_jit_save_call_chain(Dst, -1)) {
return 0;
}
}
ZEND_ASSERT(opline);
if ((opline-1)->opcode != ZEND_FETCH_CONSTANT
&& (opline-1)->opcode != ZEND_FETCH_LIST_R
&& ((opline-1)->op1_type & (IS_VAR|IS_TMP_VAR))
&& !(flags & ZEND_JIT_EXIT_FREE_OP1)) {
val_addr = ZEND_ADDR_MEM_ZVAL(ZREG_FP, (opline-1)->op1.var);
| IF_NOT_ZVAL_REFCOUNTED val_addr, >2
| GET_ZVAL_PTR r0, val_addr
| GC_ADDREF r0
|2:
}
| LOAD_IP_ADDR (opline - 1)
| jmp ->trace_escape
|1:
return 1;
}
static int zend_jit_store_const(dasm_State **Dst, int var, zend_reg reg)
{
zend_jit_addr dst = ZEND_ADDR_MEM_ZVAL(ZREG_FP, EX_NUM_TO_VAR(var));
if (reg == ZREG_LONG_MIN_MINUS_1) {
|.if X64
| SET_ZVAL_LVAL dst, 0x00000000
| SET_ZVAL_W2 dst, 0xc3e00000
|.else
| SET_ZVAL_LVAL dst, 0x00200000
| SET_ZVAL_W2 dst, 0xc1e00000
|.endif
| SET_ZVAL_TYPE_INFO dst, IS_DOUBLE
} else if (reg == ZREG_LONG_MIN) {
|.if X64
| SET_ZVAL_LVAL dst, 0x00000000
| SET_ZVAL_W2 dst, 0x80000000
|.else
| SET_ZVAL_LVAL dst, ZEND_LONG_MIN
|.endif
| SET_ZVAL_TYPE_INFO dst, IS_LONG
} else if (reg == ZREG_LONG_MAX) {
|.if X64
| SET_ZVAL_LVAL dst, 0xffffffff
| SET_ZVAL_W2 dst, 0x7fffffff
|.else
| SET_ZVAL_LVAL dst, ZEND_LONG_MAX
|.endif
| SET_ZVAL_TYPE_INFO dst, IS_LONG
} else if (reg == ZREG_LONG_MAX_PLUS_1) {
|.if X64
| SET_ZVAL_LVAL dst, 0
| SET_ZVAL_W2 dst, 0x43e00000
|.else
| SET_ZVAL_LVAL dst, 0
| SET_ZVAL_W2 dst, 0x41e00000
|.endif
| SET_ZVAL_TYPE_INFO dst, IS_DOUBLE
} else if (reg == ZREG_NULL) {
| SET_ZVAL_TYPE_INFO dst, IS_NULL
} else if (reg == ZREG_ZVAL_TRY_ADDREF) {
| IF_NOT_ZVAL_REFCOUNTED dst, >1
| GET_ZVAL_PTR r1, dst
| GC_ADDREF r1
|1:
} else if (reg == ZREG_ZVAL_COPY_R0) {
zend_jit_addr val_addr = ZEND_ADDR_MEM_ZVAL(ZREG_R0, 0);
| ZVAL_COPY_VALUE dst, -1, val_addr, -1, ZREG_R1, ZREG_R2
| TRY_ADDREF -1, ch, r2
} else {
ZEND_UNREACHABLE();
}
return 1;
}
static int zend_jit_free_trampoline(dasm_State **Dst)
{
| /// if (UNEXPECTED(func->common.fn_flags & ZEND_ACC_CALL_VIA_TRAMPOLINE))
| test dword [r0 + offsetof(zend_function, common.fn_flags)], ZEND_ACC_CALL_VIA_TRAMPOLINE
| jz >1
| mov FCARG1a, r0
| EXT_CALL zend_jit_free_trampoline_helper, r0
|1:
return 1;
}
static int zend_jit_inc_dec(dasm_State **Dst, const zend_op *opline, uint32_t op1_info, zend_jit_addr op1_addr, uint32_t op1_def_info, zend_jit_addr op1_def_addr, uint32_t res_use_info, uint32_t res_info, zend_jit_addr res_addr, int may_overflow, int may_throw)
{
if (op1_info & ((MAY_BE_UNDEF|MAY_BE_ANY)-MAY_BE_LONG)) {
| IF_NOT_ZVAL_TYPE op1_addr, IS_LONG, >2
}
if (opline->opcode == ZEND_POST_INC || opline->opcode == ZEND_POST_DEC) {
| ZVAL_COPY_VALUE res_addr, res_use_info, op1_addr, MAY_BE_LONG, ZREG_R0, ZREG_R1
}
if (!zend_jit_update_regs(Dst, opline->op1.var, op1_addr, op1_def_addr, MAY_BE_LONG)) {
return 0;
}
if (opline->opcode == ZEND_PRE_INC || opline->opcode == ZEND_POST_INC) {
| LONG_OP_WITH_32BIT_CONST add, op1_def_addr, Z_L(1)
} else {
| LONG_OP_WITH_32BIT_CONST sub, op1_def_addr, Z_L(1)
}
if (may_overflow &&
(((op1_def_info & MAY_BE_GUARD) && (op1_def_info & MAY_BE_LONG)) ||
((opline->result_type != IS_UNUSED && (res_info & MAY_BE_GUARD) && (res_info & MAY_BE_LONG))))) {
int32_t exit_point;
const void *exit_addr;
zend_jit_trace_stack *stack;
uint32_t old_op1_info, old_res_info = 0;
stack = JIT_G(current_frame)->stack;
old_op1_info = STACK_INFO(stack, EX_VAR_TO_NUM(opline->op1.var));
SET_STACK_TYPE(stack, EX_VAR_TO_NUM(opline->op1.var), IS_DOUBLE, 0);
if (opline->opcode == ZEND_PRE_INC || opline->opcode == ZEND_POST_INC) {
SET_STACK_REG(stack, EX_VAR_TO_NUM(opline->op1.var), ZREG_LONG_MAX_PLUS_1);
} else {
SET_STACK_REG(stack, EX_VAR_TO_NUM(opline->op1.var), ZREG_LONG_MIN_MINUS_1);
}
if (opline->result_type != IS_UNUSED) {
old_res_info = STACK_INFO(stack, EX_VAR_TO_NUM(opline->result.var));
if (opline->opcode == ZEND_PRE_INC) {
SET_STACK_TYPE(stack, EX_VAR_TO_NUM(opline->result.var), IS_DOUBLE, 0);
SET_STACK_REG(stack, EX_VAR_TO_NUM(opline->result.var), ZREG_LONG_MAX_PLUS_1);
} else if (opline->opcode == ZEND_PRE_DEC) {
SET_STACK_TYPE(stack, EX_VAR_TO_NUM(opline->result.var), IS_DOUBLE, 0);
SET_STACK_REG(stack, EX_VAR_TO_NUM(opline->result.var), ZREG_LONG_MIN_MINUS_1);
} else if (opline->opcode == ZEND_POST_INC) {
SET_STACK_TYPE(stack, EX_VAR_TO_NUM(opline->result.var), IS_LONG, 0);
SET_STACK_REG(stack, EX_VAR_TO_NUM(opline->result.var), ZREG_LONG_MAX);
} else if (opline->opcode == ZEND_POST_DEC) {
SET_STACK_TYPE(stack, EX_VAR_TO_NUM(opline->result.var), IS_LONG, 0);
SET_STACK_REG(stack, EX_VAR_TO_NUM(opline->result.var), ZREG_LONG_MIN);
}
}
exit_point = zend_jit_trace_get_exit_point(opline + 1, 0);
exit_addr = zend_jit_trace_get_exit_addr(exit_point);
| jo &exit_addr
if ((opline->opcode == ZEND_PRE_INC || opline->opcode == ZEND_PRE_DEC) &&
opline->result_type != IS_UNUSED) {
| ZVAL_COPY_VALUE res_addr, res_use_info, op1_def_addr, MAY_BE_LONG, ZREG_R0, ZREG_R1
}
SET_STACK_INFO(stack, EX_VAR_TO_NUM(opline->op1.var), old_op1_info);
if (opline->result_type != IS_UNUSED) {
SET_STACK_INFO(stack, EX_VAR_TO_NUM(opline->result.var), old_res_info);
}
} else if (may_overflow) {
| jo >1
if ((opline->opcode == ZEND_PRE_INC || opline->opcode == ZEND_PRE_DEC) &&
opline->result_type != IS_UNUSED) {
| ZVAL_COPY_VALUE res_addr, res_use_info, op1_def_addr, MAY_BE_LONG, ZREG_R0, ZREG_R1
}
|.cold_code
|1:
if (opline->opcode == ZEND_PRE_INC || opline->opcode == ZEND_POST_INC) {
|.if X64
| mov64 rax, 0x43e0000000000000
| SET_ZVAL_LVAL op1_def_addr, rax
|.else
| SET_ZVAL_LVAL op1_def_addr, 0
| SET_ZVAL_W2 op1_def_addr, 0x41e00000
|.endif
} else {
|.if X64
| mov64 rax, 0xc3e0000000000000
| SET_ZVAL_LVAL op1_def_addr, rax
|.else
| SET_ZVAL_LVAL op1_def_addr, 0x00200000
| SET_ZVAL_W2 op1_def_addr, 0xc1e00000
|.endif
}
if (Z_MODE(op1_def_addr) == IS_MEM_ZVAL) {
| SET_ZVAL_TYPE_INFO op1_def_addr, IS_DOUBLE
}
if ((opline->opcode == ZEND_PRE_INC || opline->opcode == ZEND_PRE_DEC) &&
opline->result_type != IS_UNUSED) {
| ZVAL_COPY_VALUE res_addr, res_use_info, op1_def_addr, MAY_BE_DOUBLE, ZREG_R0, ZREG_R1
}
| jmp >3
|.code
} else {
if ((opline->opcode == ZEND_PRE_INC || opline->opcode == ZEND_PRE_DEC) &&
opline->result_type != IS_UNUSED) {
| ZVAL_COPY_VALUE res_addr, res_use_info, op1_def_addr, MAY_BE_LONG, ZREG_R0, ZREG_R1
}
}
if (op1_info & ((MAY_BE_ANY|MAY_BE_UNDEF)-MAY_BE_LONG)) {
|.cold_code
|2:
if (op1_info & ((MAY_BE_ANY|MAY_BE_UNDEF)-(MAY_BE_LONG|MAY_BE_DOUBLE))) {
| SET_EX_OPLINE opline, r0
if (op1_info & MAY_BE_UNDEF) {
| IF_NOT_ZVAL_TYPE op1_addr, IS_UNDEF, >2
| // zend_error(E_WARNING, "Undefined variable $%s", ZSTR_VAL(CV_DEF_OF(EX_VAR_TO_NUM(opline->op1.var))));
| mov FCARG1d, opline->op1.var
| SET_ZVAL_TYPE_INFO op1_addr, IS_NULL
| EXT_CALL zend_jit_undefined_op_helper, r0
op1_info |= MAY_BE_NULL;
}
|2:
| LOAD_ZVAL_ADDR FCARG1a, op1_addr
| // ZVAL_DEREF(var_ptr);
if (op1_info & MAY_BE_REF) {
| IF_NOT_Z_TYPE, FCARG1a, IS_REFERENCE, >2
| GET_Z_PTR FCARG1a, FCARG1a
| cmp aword [FCARG1a + offsetof(zend_reference, sources.ptr)], 0
| jz >1
if (RETURN_VALUE_USED(opline)) {
| LOAD_ZVAL_ADDR FCARG2a, res_addr
} else {
| xor FCARG2a, FCARG2a
}
if (opline->opcode == ZEND_PRE_INC) {
| EXT_CALL zend_jit_pre_inc_typed_ref, r0
} else if (opline->opcode == ZEND_PRE_DEC) {
| EXT_CALL zend_jit_pre_dec_typed_ref, r0
} else if (opline->opcode == ZEND_POST_INC) {
| EXT_CALL zend_jit_post_inc_typed_ref, r0
} else if (opline->opcode == ZEND_POST_DEC) {
| EXT_CALL zend_jit_post_dec_typed_ref, r0
} else {
ZEND_UNREACHABLE();
}
zend_jit_check_exception(Dst);
| jmp >3
|1:
| lea FCARG1a, [FCARG1a + offsetof(zend_reference, val)]
|2:
}
if (opline->opcode == ZEND_POST_INC || opline->opcode == ZEND_POST_DEC) {
zend_jit_addr val_addr = ZEND_ADDR_MEM_ZVAL(ZREG_FCARG1a, 0);
| ZVAL_COPY_VALUE res_addr, res_use_info, val_addr, op1_info, ZREG_R0, ZREG_R2
| TRY_ADDREF op1_info, ah, r2
}
if (opline->opcode == ZEND_PRE_INC || opline->opcode == ZEND_POST_INC) {
if (opline->opcode == ZEND_PRE_INC && opline->result_type != IS_UNUSED) {
| LOAD_ZVAL_ADDR FCARG2a, res_addr
| EXT_CALL zend_jit_pre_inc, r0
} else {
| EXT_CALL increment_function, r0
}
} else {
if (opline->opcode == ZEND_PRE_DEC && opline->result_type != IS_UNUSED) {
| LOAD_ZVAL_ADDR FCARG2a, res_addr
| EXT_CALL zend_jit_pre_dec, r0
} else {
| EXT_CALL decrement_function, r0
}
}
if (may_throw) {
zend_jit_check_exception(Dst);
}
} else {
zend_reg tmp_reg;
if (opline->opcode == ZEND_POST_INC || opline->opcode == ZEND_POST_DEC) {
| ZVAL_COPY_VALUE res_addr, res_use_info, op1_addr, MAY_BE_DOUBLE, ZREG_R0, ZREG_R2
}
if (Z_MODE(op1_def_addr) == IS_REG) {
tmp_reg = Z_REG(op1_def_addr);
} else if (Z_MODE(op1_addr) == IS_REG && Z_LAST_USE(op1_addr)) {
tmp_reg = Z_REG(op1_addr);
} else {
tmp_reg = ZREG_XMM0;
}
| SSE_GET_ZVAL_DVAL tmp_reg, op1_addr
if (opline->opcode == ZEND_PRE_INC || opline->opcode == ZEND_POST_INC) {
if (CAN_USE_AVX()) {
| vaddsd xmm(tmp_reg-ZREG_XMM0), xmm(tmp_reg-ZREG_XMM0), qword [->one]
} else {
| addsd xmm(tmp_reg-ZREG_XMM0), qword [->one]
}
} else {
if (CAN_USE_AVX()) {
| vsubsd xmm(tmp_reg-ZREG_XMM0), xmm(tmp_reg-ZREG_XMM0), qword [->one]
} else {
| subsd xmm(tmp_reg-ZREG_XMM0), qword [->one]
}
}
| SSE_SET_ZVAL_DVAL op1_def_addr, tmp_reg
if ((opline->opcode == ZEND_PRE_INC || opline->opcode == ZEND_PRE_DEC) &&
opline->result_type != IS_UNUSED) {
| ZVAL_COPY_VALUE res_addr, res_use_info, op1_addr, op1_def_info, ZREG_R0, ZREG_R1
| TRY_ADDREF op1_def_info, ah, r1
}
}
| jmp >3
|.code
}
|3:
if (!zend_jit_store_var_if_necessary_ex(Dst, opline->op1.var, op1_def_addr, op1_def_info, op1_addr, op1_info)) {
return 0;
}
if (opline->result_type != IS_UNUSED) {
if (!zend_jit_store_var_if_necessary(Dst, opline->result.var, res_addr, res_info)) {
return 0;
}
}
return 1;
}
static int zend_jit_opline_uses_reg(const zend_op *opline, int8_t reg)
{
if ((opline+1)->opcode == ZEND_OP_DATA
&& ((opline+1)->op1_type & (IS_VAR|IS_TMP_VAR|IS_CV))
&& JIT_G(current_frame)->stack[EX_VAR_TO_NUM((opline+1)->op1.var)].reg == reg) {
return 1;
}
return
((opline->result_type & (IS_VAR|IS_TMP_VAR|IS_CV)) &&
JIT_G(current_frame)->stack[EX_VAR_TO_NUM(opline->result.var)].reg == reg) ||
((opline->op1_type & (IS_VAR|IS_TMP_VAR|IS_CV)) &&
JIT_G(current_frame)->stack[EX_VAR_TO_NUM(opline->op1.var)].reg == reg) ||
((opline->op2_type & (IS_VAR|IS_TMP_VAR|IS_CV)) &&
JIT_G(current_frame)->stack[EX_VAR_TO_NUM(opline->op2.var)].reg == reg);
}
static int zend_jit_math_long_long(dasm_State **Dst,
const zend_op *opline,
zend_uchar opcode,
zend_jit_addr op1_addr,
zend_jit_addr op2_addr,
zend_jit_addr res_addr,
uint32_t res_info,
uint32_t res_use_info,
int may_overflow)
{
zend_bool same_ops = zend_jit_same_addr(op1_addr, op2_addr);
zend_reg result_reg;
zend_reg tmp_reg = ZREG_R0;
if (Z_MODE(res_addr) == IS_REG && (res_info & MAY_BE_LONG)) {
if (may_overflow && (res_info & MAY_BE_GUARD)
&& JIT_G(current_frame)
&& zend_jit_opline_uses_reg(opline, Z_REG(res_addr))) {
result_reg = ZREG_R0;
} else {
result_reg = Z_REG(res_addr);
}
} else if (Z_MODE(op1_addr) == IS_REG && Z_LAST_USE(op1_addr) && !may_overflow) {
result_reg = Z_REG(op1_addr);
} else if (Z_REG(res_addr) != ZREG_R0) {
result_reg = ZREG_R0;
} else {
/* ASSIGN_DIM_OP */
result_reg = ZREG_FCARG1a;
tmp_reg = ZREG_FCARG1a;
}
if (opcode == ZEND_MUL &&
Z_MODE(op2_addr) == IS_CONST_ZVAL &&
Z_LVAL_P(Z_ZV(op2_addr)) == 2) {
if (Z_MODE(op1_addr) == IS_REG && !may_overflow) {
| lea Ra(result_reg), [Ra(Z_REG(op1_addr))+Ra(Z_REG(op1_addr))]
} else {
| GET_ZVAL_LVAL result_reg, op1_addr
| add Ra(result_reg), Ra(result_reg)
}
} else if (opcode == ZEND_MUL &&
Z_MODE(op2_addr) == IS_CONST_ZVAL &&
!may_overflow &&
Z_LVAL_P(Z_ZV(op2_addr)) > 0 &&
zend_long_is_power_of_two(Z_LVAL_P(Z_ZV(op2_addr)))) {
| GET_ZVAL_LVAL result_reg, op1_addr
| shl Ra(result_reg), zend_long_floor_log2(Z_LVAL_P(Z_ZV(op2_addr)))
} else if (opcode == ZEND_MUL &&
Z_MODE(op1_addr) == IS_CONST_ZVAL &&
Z_LVAL_P(Z_ZV(op1_addr)) == 2) {
if (Z_MODE(op2_addr) == IS_REG && !may_overflow) {
| lea Ra(result_reg), [Ra(Z_REG(op2_addr))+Ra(Z_REG(op2_addr))]
} else {
| GET_ZVAL_LVAL result_reg, op2_addr
| add Ra(result_reg), Ra(result_reg)
}
} else if (opcode == ZEND_MUL &&
Z_MODE(op1_addr) == IS_CONST_ZVAL &&
!may_overflow &&
Z_LVAL_P(Z_ZV(op1_addr)) > 0 &&
zend_long_is_power_of_two(Z_LVAL_P(Z_ZV(op1_addr)))) {
| GET_ZVAL_LVAL result_reg, op2_addr
| shl Ra(result_reg), zend_long_floor_log2(Z_LVAL_P(Z_ZV(op1_addr)))
} else if (opcode == ZEND_DIV &&
(Z_MODE(op2_addr) == IS_CONST_ZVAL &&
zend_long_is_power_of_two(Z_LVAL_P(Z_ZV(op2_addr))))) {
| GET_ZVAL_LVAL result_reg, op1_addr
| shr Ra(result_reg), zend_long_floor_log2(Z_LVAL_P(Z_ZV(op2_addr)))
} else if (opcode == ZEND_ADD &&
!may_overflow &&
Z_MODE(op1_addr) == IS_REG &&
Z_MODE(op2_addr) == IS_CONST_ZVAL &&
IS_SIGNED_32BIT(Z_LVAL_P(Z_ZV(op2_addr)))) {
| lea Ra(result_reg), [Ra(Z_REG(op1_addr))+Z_LVAL_P(Z_ZV(op2_addr))]
} else if (opcode == ZEND_ADD &&
!may_overflow &&
Z_MODE(op2_addr) == IS_REG &&
Z_MODE(op1_addr) == IS_CONST_ZVAL &&
IS_SIGNED_32BIT(Z_LVAL_P(Z_ZV(op1_addr)))) {
| lea Ra(result_reg), [Ra(Z_REG(op2_addr))+Z_LVAL_P(Z_ZV(op1_addr))]
} else if (opcode == ZEND_SUB &&
!may_overflow &&
Z_MODE(op1_addr) == IS_REG &&
Z_MODE(op2_addr) == IS_CONST_ZVAL &&
IS_SIGNED_32BIT(-Z_LVAL_P(Z_ZV(op2_addr)))) {
| lea Ra(result_reg), [Ra(Z_REG(op1_addr))-Z_LVAL_P(Z_ZV(op2_addr))]
} else {
| GET_ZVAL_LVAL result_reg, op1_addr
if ((opcode == ZEND_ADD || opcode == ZEND_SUB)
&& Z_MODE(op2_addr) == IS_CONST_ZVAL
&& Z_LVAL_P(Z_ZV(op2_addr)) == 0) {
/* +/- 0 */
may_overflow = 0;
} else if (same_ops && opcode != ZEND_DIV) {
| LONG_MATH_REG opcode, Ra(result_reg), Ra(result_reg)
} else {
zend_reg tmp_reg;
if (Z_MODE(res_addr) == IS_MEM_ZVAL && Z_REG(res_addr) == ZREG_R0) {
tmp_reg = ZREG_R1;
} else if (result_reg != ZREG_R0) {
tmp_reg = ZREG_R0;
} else {
tmp_reg = ZREG_R1;
}
| LONG_MATH opcode, result_reg, op2_addr, tmp_reg
(void)tmp_reg;
}
}
if (may_overflow) {
if (res_info & MAY_BE_GUARD) {
int32_t exit_point = zend_jit_trace_get_exit_point(opline, 0);
const void *exit_addr = zend_jit_trace_get_exit_addr(exit_point);
if ((res_info & MAY_BE_ANY) == MAY_BE_LONG) {
| jo &exit_addr
if (Z_MODE(res_addr) == IS_REG && result_reg != Z_REG(res_addr)) {
| mov Ra(Z_REG(res_addr)), Ra(result_reg)
}
} else if ((res_info & MAY_BE_ANY) == MAY_BE_DOUBLE) {
| jno &exit_addr
} else {
ZEND_UNREACHABLE();
}
} else {
if (res_info & MAY_BE_LONG) {
| jo >1
} else {
| jno >1
}
}
}
if (Z_MODE(res_addr) == IS_MEM_ZVAL && (res_info & MAY_BE_LONG)) {
| SET_ZVAL_LVAL res_addr, Ra(result_reg)
if (Z_MODE(op1_addr) != IS_MEM_ZVAL || Z_REG(op1_addr) != Z_REG(res_addr) || Z_OFFSET(op1_addr) != Z_OFFSET(res_addr)) {
if ((res_use_info & (MAY_BE_ANY|MAY_BE_UNDEF|MAY_BE_REF|MAY_BE_GUARD)) != MAY_BE_LONG) {
| SET_ZVAL_TYPE_INFO res_addr, IS_LONG
}
}
}
if (may_overflow && (!(res_info & MAY_BE_GUARD) || (res_info & MAY_BE_ANY) == MAY_BE_DOUBLE)) {
zend_reg tmp_reg1 = ZREG_XMM0;
zend_reg tmp_reg2 = ZREG_XMM1;
if (res_info & MAY_BE_LONG) {
|.cold_code
|1:
}
do {
if ((sizeof(void*) == 8 || Z_MODE(res_addr) != IS_REG) &&
((Z_MODE(op1_addr) == IS_CONST_ZVAL && Z_LVAL_P(Z_ZV(op1_addr)) == 1) ||
(Z_MODE(op2_addr) == IS_CONST_ZVAL && Z_LVAL_P(Z_ZV(op2_addr)) == 1))) {
if (opcode == ZEND_ADD) {
|.if X64
| mov64 Ra(tmp_reg), 0x43e0000000000000
if (Z_MODE(res_addr) == IS_REG) {
| movd xmm(Z_REG(res_addr)-ZREG_XMM0), Ra(tmp_reg)
} else {
| SET_ZVAL_LVAL res_addr, Ra(tmp_reg)
}
|.else
| SET_ZVAL_LVAL res_addr, 0
| SET_ZVAL_W2 res_addr, 0x41e00000
|.endif
break;
} else if (opcode == ZEND_SUB) {
|.if X64
| mov64 Ra(tmp_reg), 0xc3e0000000000000
if (Z_MODE(res_addr) == IS_REG) {
| movd xmm(Z_REG(res_addr)-ZREG_XMM0), Ra(tmp_reg)
} else {
| SET_ZVAL_LVAL res_addr, Ra(tmp_reg)
}
|.else
| SET_ZVAL_LVAL res_addr, 0x00200000
| SET_ZVAL_W2 res_addr, 0xc1e00000
|.endif
break;
}
}
| SSE_GET_ZVAL_LVAL tmp_reg1, op1_addr, tmp_reg
| SSE_GET_ZVAL_LVAL tmp_reg2, op2_addr, tmp_reg
if (CAN_USE_AVX()) {
| AVX_MATH_REG opcode, tmp_reg1, tmp_reg1, tmp_reg2
} else {
| SSE_MATH_REG opcode, tmp_reg1, tmp_reg2
}
| SSE_SET_ZVAL_DVAL res_addr, tmp_reg1
} while (0);
if (Z_MODE(res_addr) == IS_MEM_ZVAL
&& (res_use_info & (MAY_BE_ANY|MAY_BE_UNDEF|MAY_BE_REF|MAY_BE_GUARD)) != MAY_BE_DOUBLE) {
| SET_ZVAL_TYPE_INFO res_addr, IS_DOUBLE
}
if (res_info & MAY_BE_LONG) {
| jmp >2
|.code
}
|2:
}
return 1;
}
static int zend_jit_math_long_double(dasm_State **Dst,
zend_uchar opcode,
zend_jit_addr op1_addr,
zend_jit_addr op2_addr,
zend_jit_addr res_addr,
uint32_t res_use_info)
{
zend_reg result_reg =
(Z_MODE(res_addr) == IS_REG) ? Z_REG(res_addr) : ZREG_XMM0;
zend_reg tmp_reg;
if (Z_MODE(res_addr) == IS_MEM_ZVAL && Z_REG(res_addr) == ZREG_R0) {
/* ASSIGN_DIM_OP */
tmp_reg = ZREG_R1;
} else {
tmp_reg = ZREG_R0;
}
| SSE_GET_ZVAL_LVAL result_reg, op1_addr, tmp_reg
if (Z_MODE(res_addr) == IS_MEM_ZVAL && Z_REG(res_addr) == ZREG_R0) {
/* ASSIGN_DIM_OP */
if (CAN_USE_AVX()) {
| AVX_MATH opcode, result_reg, result_reg, op2_addr, r1
} else {
| SSE_MATH opcode, result_reg, op2_addr, r1
}
} else {
if (CAN_USE_AVX()) {
| AVX_MATH opcode, result_reg, result_reg, op2_addr, r0
} else {
| SSE_MATH opcode, result_reg, op2_addr, r0
}
}
| SSE_SET_ZVAL_DVAL res_addr, result_reg
if (Z_MODE(res_addr) == IS_MEM_ZVAL) {
if ((res_use_info & (MAY_BE_ANY|MAY_BE_UNDEF|MAY_BE_REF|MAY_BE_GUARD)) != MAY_BE_DOUBLE) {
| SET_ZVAL_TYPE_INFO res_addr, IS_DOUBLE
}
}
return 1;
}
static int zend_jit_math_double_long(dasm_State **Dst,
zend_uchar opcode,
zend_jit_addr op1_addr,
zend_jit_addr op2_addr,
zend_jit_addr res_addr,
uint32_t res_use_info)
{
zend_reg result_reg, tmp_reg_gp;
if (Z_MODE(res_addr) == IS_MEM_ZVAL && Z_REG(res_addr) == ZREG_R0) {
/* ASSIGN_DIM_OP */
tmp_reg_gp = ZREG_R1;
} else {
tmp_reg_gp = ZREG_R0;
}
if (zend_is_commutative(opcode)
&& (Z_MODE(res_addr) != IS_REG || Z_MODE(op1_addr) != IS_REG || Z_REG(res_addr) != Z_REG(op1_addr))) {
if (Z_MODE(res_addr) == IS_REG) {
result_reg = Z_REG(res_addr);
} else {
result_reg = ZREG_XMM0;
}
| SSE_GET_ZVAL_LVAL result_reg, op2_addr, tmp_reg_gp
if (Z_MODE(res_addr) == IS_MEM_ZVAL && Z_REG(res_addr) == ZREG_R0) {
/* ASSIGN_DIM_OP */
if (CAN_USE_AVX()) {
| AVX_MATH opcode, result_reg, result_reg, op1_addr, r1
} else {
| SSE_MATH opcode, result_reg, op1_addr, r1
}
} else {
if (CAN_USE_AVX()) {
| AVX_MATH opcode, result_reg, result_reg, op1_addr, r0
} else {
| SSE_MATH opcode, result_reg, op1_addr, r0
}
}
} else {
zend_reg tmp_reg;
if (Z_MODE(res_addr) == IS_REG) {
result_reg = Z_REG(res_addr);
tmp_reg = (result_reg == ZREG_XMM0) ? ZREG_XMM1 : ZREG_XMM0;
} else if (Z_MODE(op1_addr) == IS_REG && Z_LAST_USE(op1_addr)) {
result_reg = Z_REG(op1_addr);
tmp_reg = ZREG_XMM0;
} else {
result_reg = ZREG_XMM0;
tmp_reg = ZREG_XMM1;
}
if (CAN_USE_AVX()) {
zend_reg op1_reg;
if (Z_MODE(op1_addr) == IS_REG) {
op1_reg = Z_REG(op1_addr);
} else {
| SSE_GET_ZVAL_DVAL result_reg, op1_addr
op1_reg = result_reg;
}
if ((opcode == ZEND_ADD || opcode == ZEND_SUB)
&& Z_MODE(op2_addr) == IS_CONST_ZVAL
&& Z_LVAL_P(Z_ZV(op2_addr)) == 0) {
/* +/- 0 */
} else {
| SSE_GET_ZVAL_LVAL tmp_reg, op2_addr, tmp_reg_gp
| AVX_MATH_REG opcode, result_reg, op1_reg, tmp_reg
}
} else {
| SSE_GET_ZVAL_DVAL result_reg, op1_addr
if ((opcode == ZEND_ADD || opcode == ZEND_SUB)
&& Z_MODE(op2_addr) == IS_CONST_ZVAL
&& Z_LVAL_P(Z_ZV(op2_addr)) == 0) {
/* +/- 0 */
} else {
| SSE_GET_ZVAL_LVAL tmp_reg, op2_addr, tmp_reg_gp
| SSE_MATH_REG opcode, result_reg, tmp_reg
}
}
}
| SSE_SET_ZVAL_DVAL res_addr, result_reg
if (Z_MODE(res_addr) == IS_MEM_ZVAL) {
if (Z_MODE(op1_addr) != IS_MEM_ZVAL || Z_REG(op1_addr) != Z_REG(res_addr) || Z_OFFSET(op1_addr) != Z_OFFSET(res_addr)) {
if ((res_use_info & (MAY_BE_ANY|MAY_BE_UNDEF|MAY_BE_REF|MAY_BE_GUARD)) != MAY_BE_DOUBLE) {
| SET_ZVAL_TYPE_INFO res_addr, IS_DOUBLE
}
}
}
return 1;
}
static int zend_jit_math_double_double(dasm_State **Dst,
zend_uchar opcode,
zend_jit_addr op1_addr,
zend_jit_addr op2_addr,
zend_jit_addr res_addr,
uint32_t res_use_info)
{
zend_bool same_ops = zend_jit_same_addr(op1_addr, op2_addr);
zend_reg result_reg;
if (Z_MODE(res_addr) == IS_REG) {
result_reg = Z_REG(res_addr);
} else if (Z_MODE(op1_addr) == IS_REG && Z_LAST_USE(op1_addr)) {
result_reg = Z_REG(op1_addr);
} else if (zend_is_commutative(opcode) && Z_MODE(op2_addr) == IS_REG && Z_LAST_USE(op2_addr)) {
result_reg = Z_REG(op2_addr);
} else {
result_reg = ZREG_XMM0;
}
if (CAN_USE_AVX()) {
zend_reg op1_reg;
zend_jit_addr val_addr;
if (Z_MODE(op1_addr) == IS_REG) {
op1_reg = Z_REG(op1_addr);
val_addr = op2_addr;
} else if (Z_MODE(op2_addr) == IS_REG && zend_is_commutative(opcode)) {
op1_reg = Z_REG(op2_addr);
val_addr = op1_addr;
} else {
| SSE_GET_ZVAL_DVAL result_reg, op1_addr
op1_reg = result_reg;
val_addr = op2_addr;
}
if ((opcode == ZEND_MUL) &&
Z_MODE(val_addr) == IS_CONST_ZVAL && Z_DVAL_P(Z_ZV(val_addr)) == 2.0) {
| AVX_MATH_REG ZEND_ADD, result_reg, op1_reg, op1_reg
} else if (Z_MODE(res_addr) == IS_MEM_ZVAL && Z_REG(res_addr) == ZREG_R0) {
/* ASSIGN_DIM_OP */
| AVX_MATH opcode, result_reg, op1_reg, val_addr, r1
} else {
| AVX_MATH opcode, result_reg, op1_reg, val_addr, r0
}
} else {
zend_jit_addr val_addr;
if (Z_MODE(op1_addr) != IS_REG && Z_MODE(op2_addr) == IS_REG && zend_is_commutative(opcode)) {
| SSE_GET_ZVAL_DVAL result_reg, op2_addr
val_addr = op1_addr;
} else {
| SSE_GET_ZVAL_DVAL result_reg, op1_addr
val_addr = op2_addr;
}
if (same_ops) {
| SSE_MATH_REG opcode, result_reg, result_reg
} else if ((opcode == ZEND_MUL) &&
Z_MODE(val_addr) == IS_CONST_ZVAL && Z_DVAL_P(Z_ZV(val_addr)) == 2.0) {
| SSE_MATH_REG ZEND_ADD, result_reg, result_reg
} else if (Z_MODE(res_addr) == IS_MEM_ZVAL && Z_REG(res_addr) == ZREG_R0) {
/* ASSIGN_DIM_OP */
| SSE_MATH opcode, result_reg, val_addr, r1
} else {
| SSE_MATH opcode, result_reg, val_addr, r0
}
}
| SSE_SET_ZVAL_DVAL res_addr, result_reg
if (Z_MODE(res_addr) == IS_MEM_ZVAL) {
if (Z_MODE(op1_addr) != IS_MEM_ZVAL || Z_REG(op1_addr) != Z_REG(res_addr) || Z_OFFSET(op1_addr) != Z_OFFSET(res_addr)) {
if ((res_use_info & (MAY_BE_ANY|MAY_BE_UNDEF|MAY_BE_REF|MAY_BE_GUARD)) != MAY_BE_DOUBLE) {
| SET_ZVAL_TYPE_INFO res_addr, IS_DOUBLE
}
}
}
return 1;
}
static int zend_jit_math_helper(dasm_State **Dst,
const zend_op *opline,
zend_uchar opcode,
zend_uchar op1_type,
znode_op op1,
zend_jit_addr op1_addr,
uint32_t op1_info,
zend_uchar op2_type,
znode_op op2,
zend_jit_addr op2_addr,
uint32_t op2_info,
uint32_t res_var,
zend_jit_addr res_addr,
uint32_t res_info,
uint32_t res_use_info,
int may_overflow,
int may_throw)
/* Labels: 1,2,3,4,5,6 */
{
zend_bool same_ops = zend_jit_same_addr(op1_addr, op2_addr);
if ((op1_info & MAY_BE_LONG) && (op2_info & MAY_BE_LONG) && (res_info & (MAY_BE_LONG|MAY_BE_DOUBLE))) {
if (op1_info & (MAY_BE_ANY-MAY_BE_LONG)) {
if (op1_info & MAY_BE_DOUBLE) {
| IF_NOT_ZVAL_TYPE op1_addr, IS_LONG, >3
} else {
| IF_NOT_ZVAL_TYPE op1_addr, IS_LONG, >6
}
}
if (!same_ops && (op2_info & (MAY_BE_ANY-MAY_BE_LONG))) {
if (op2_info & MAY_BE_DOUBLE) {
| IF_NOT_ZVAL_TYPE op2_addr, IS_LONG, >1
|.cold_code
|1:
if (op2_info & (MAY_BE_ANY-(MAY_BE_LONG|MAY_BE_DOUBLE))) {
| IF_NOT_ZVAL_TYPE op2_addr, IS_DOUBLE, >6
}
if (!zend_jit_math_long_double(Dst, opcode, op1_addr, op2_addr, res_addr, res_use_info)) {
return 0;
}
| jmp >5
|.code
} else {
| IF_NOT_ZVAL_TYPE op2_addr, IS_LONG, >6
}
}
if (!zend_jit_math_long_long(Dst, opline, opcode, op1_addr, op2_addr, res_addr, res_info, res_use_info, may_overflow)) {
return 0;
}
if (op1_info & MAY_BE_DOUBLE) {
|.cold_code
|3:
if (op1_info & (MAY_BE_ANY-(MAY_BE_LONG|MAY_BE_DOUBLE))) {
| IF_NOT_ZVAL_TYPE op1_addr, IS_DOUBLE, >6
}
if (op2_info & MAY_BE_DOUBLE) {
if (!same_ops && (op2_info & (MAY_BE_ANY-MAY_BE_DOUBLE))) {
if (!same_ops) {
| IF_NOT_ZVAL_TYPE, op2_addr, IS_DOUBLE, >1
} else {
| IF_NOT_ZVAL_TYPE, op2_addr, IS_DOUBLE, >6
}
}
if (!zend_jit_math_double_double(Dst, opcode, op1_addr, op2_addr, res_addr, res_use_info)) {
return 0;
}
| jmp >5
}
if (!same_ops) {
|1:
if (op2_info & (MAY_BE_ANY-(MAY_BE_LONG|MAY_BE_DOUBLE))) {
| IF_NOT_ZVAL_TYPE op2_addr, IS_LONG, >6
}
if (!zend_jit_math_double_long(Dst, opcode, op1_addr, op2_addr, res_addr, res_use_info)) {
return 0;
}
| jmp >5
}
|.code
}
} else if ((op1_info & MAY_BE_DOUBLE) &&
!(op1_info & MAY_BE_LONG) &&
(op2_info & (MAY_BE_LONG|MAY_BE_DOUBLE)) &&
(res_info & MAY_BE_DOUBLE)) {
if (op1_info & (MAY_BE_ANY-MAY_BE_DOUBLE)) {
| IF_NOT_ZVAL_TYPE op1_addr, IS_DOUBLE, >6
}
if (op2_info & MAY_BE_DOUBLE) {
if (!same_ops && (op2_info & (MAY_BE_ANY-MAY_BE_DOUBLE))) {
if (!same_ops && (op2_info & MAY_BE_LONG)) {
| IF_NOT_ZVAL_TYPE op2_addr, IS_DOUBLE, >1
} else {
| IF_NOT_ZVAL_TYPE op2_addr, IS_DOUBLE, >6
}
}
if (!zend_jit_math_double_double(Dst, opcode, op1_addr, op2_addr, res_addr, res_use_info)) {
return 0;
}
}
if (!same_ops && (op2_info & MAY_BE_LONG)) {
if (op2_info & MAY_BE_DOUBLE) {
|.cold_code
}
|1:
if (op2_info & (MAY_BE_ANY-(MAY_BE_DOUBLE|MAY_BE_LONG))) {
| IF_NOT_ZVAL_TYPE op2_addr, IS_LONG, >6
}
if (!zend_jit_math_double_long(Dst, opcode, op1_addr, op2_addr, res_addr, res_use_info)) {
return 0;
}
if (op2_info & MAY_BE_DOUBLE) {
| jmp >5
|.code
}
}
} else if ((op2_info & MAY_BE_DOUBLE) &&
!(op2_info & MAY_BE_LONG) &&
(op1_info & (MAY_BE_LONG|MAY_BE_DOUBLE)) &&
(res_info & MAY_BE_DOUBLE)) {
if (op2_info & (MAY_BE_ANY-MAY_BE_DOUBLE)) {
| IF_NOT_ZVAL_TYPE op2_addr, IS_DOUBLE, >6
}
if (op1_info & MAY_BE_DOUBLE) {
if (!same_ops && (op1_info & (MAY_BE_ANY-MAY_BE_DOUBLE))) {
if (!same_ops && (op1_info & MAY_BE_LONG)) {
| IF_NOT_ZVAL_TYPE op1_addr, IS_DOUBLE, >1
} else {
| IF_NOT_ZVAL_TYPE op1_addr, IS_DOUBLE, >6
}
}
if (!zend_jit_math_double_double(Dst, opcode, op1_addr, op2_addr, res_addr, res_use_info)) {
return 0;
}
}
if (!same_ops && (op1_info & MAY_BE_LONG)) {
if (op1_info & MAY_BE_DOUBLE) {
|.cold_code
}
|1:
if (op1_info & (MAY_BE_ANY-(MAY_BE_DOUBLE|MAY_BE_LONG))) {
| IF_NOT_ZVAL_TYPE op1_addr, IS_LONG, >6
}
if (!zend_jit_math_long_double(Dst, opcode, op1_addr, op2_addr, res_addr, res_use_info)) {
return 0;
}
if (op1_info & MAY_BE_DOUBLE) {
| jmp >5
|.code
}
}
}
|5:
if ((op1_info & ((MAY_BE_ANY|MAY_BE_UNDEF)-(MAY_BE_LONG|MAY_BE_DOUBLE))) ||
(op2_info & ((MAY_BE_ANY|MAY_BE_UNDEF)-(MAY_BE_LONG|MAY_BE_DOUBLE)))) {
if ((op1_info & (MAY_BE_LONG|MAY_BE_DOUBLE)) &&
(op2_info & (MAY_BE_LONG|MAY_BE_DOUBLE)) &&
(res_info & (MAY_BE_LONG|MAY_BE_DOUBLE))) {
|.cold_code
}
|6:
if (Z_MODE(res_addr) == IS_REG) {
zend_jit_addr real_addr = ZEND_ADDR_MEM_ZVAL(ZREG_FP, res_var);
| LOAD_ZVAL_ADDR FCARG1a, real_addr
} else if (Z_REG(res_addr) != ZREG_FCARG1a || Z_OFFSET(res_addr) != 0) {
| LOAD_ZVAL_ADDR FCARG1a, res_addr
}
if (Z_MODE(op1_addr) == IS_REG) {
zend_jit_addr real_addr = ZEND_ADDR_MEM_ZVAL(ZREG_FP, op1.var);
if (!zend_jit_spill_store(Dst, op1_addr, real_addr, op1_info, 1)) {
return 0;
}
op1_addr = real_addr;
}
| LOAD_ZVAL_ADDR FCARG2a, op1_addr
if (Z_MODE(op2_addr) == IS_REG) {
zend_jit_addr real_addr = ZEND_ADDR_MEM_ZVAL(ZREG_FP, op2.var);
if (!zend_jit_spill_store(Dst, op2_addr, real_addr, op2_info, 1)) {
return 0;
}
op2_addr = real_addr;
}
|.if X64
| LOAD_ZVAL_ADDR CARG3, op2_addr
|.else
| sub r4, 12
| PUSH_ZVAL_ADDR op2_addr, r0
|.endif
| SET_EX_OPLINE opline, r0
if (opcode == ZEND_ADD) {
| EXT_CALL add_function, r0
} else if (opcode == ZEND_SUB) {
| EXT_CALL sub_function, r0
} else if (opcode == ZEND_MUL) {
| EXT_CALL mul_function, r0
} else if (opcode == ZEND_DIV) {
| EXT_CALL div_function, r0
} else {
ZEND_UNREACHABLE();
}
|.if not(X64)
| add r4, 12
|.endif
| FREE_OP op1_type, op1, op1_info, 0, opline
| FREE_OP op2_type, op2, op2_info, 0, opline
if (may_throw) {
if (opline->opcode == ZEND_ASSIGN_DIM_OP && (opline->op2_type & (IS_VAR|IS_TMP_VAR))) {
| MEM_OP2_1_ZTS cmp, aword, executor_globals, exception, 0, r0
| jne ->exception_handler_free_op2
} else if (Z_MODE(res_addr) == IS_MEM_ZVAL && Z_REG(res_addr) == ZREG_RX) {
zend_jit_check_exception_undef_result(Dst, opline);
} else {
zend_jit_check_exception(Dst);
}
}
if (Z_MODE(res_addr) == IS_REG) {
zend_jit_addr real_addr = ZEND_ADDR_MEM_ZVAL(ZREG_FP, res_var);
if (!zend_jit_load_reg(Dst, real_addr, res_addr, res_info)) {
return 0;
}
}
if ((op1_info & (MAY_BE_LONG|MAY_BE_DOUBLE)) &&
(op2_info & (MAY_BE_LONG|MAY_BE_DOUBLE)) &&
(res_info & (MAY_BE_LONG|MAY_BE_DOUBLE))) {
| jmp <5
|.code
}
}
return 1;
}
static int zend_jit_math(dasm_State **Dst, const zend_op *opline, uint32_t op1_info, zend_jit_addr op1_addr, uint32_t op2_info, zend_jit_addr op2_addr, uint32_t res_use_info, uint32_t res_info, zend_jit_addr res_addr, int may_overflow, int may_throw)
{
ZEND_ASSERT(!(op1_info & MAY_BE_UNDEF) && !(op2_info & MAY_BE_UNDEF));
ZEND_ASSERT((op1_info & (MAY_BE_LONG|MAY_BE_DOUBLE)) &&
(op2_info & (MAY_BE_LONG|MAY_BE_DOUBLE)));
if (!zend_jit_math_helper(Dst, opline, opline->opcode, opline->op1_type, opline->op1, op1_addr, op1_info, opline->op2_type, opline->op2, op2_addr, op2_info, opline->result.var, res_addr, res_info, res_use_info, may_overflow, may_throw)) {
return 0;
}
if (!zend_jit_store_var_if_necessary(Dst, opline->result.var, res_addr, res_info)) {
return 0;
}
return 1;
}
static int zend_jit_add_arrays(dasm_State **Dst, const zend_op *opline, uint32_t op1_info, uint32_t op2_info, zend_jit_addr res_addr)
{
zend_jit_addr op1_addr = OP1_ADDR();
zend_jit_addr op2_addr = OP2_ADDR();
| GET_ZVAL_LVAL ZREG_FCARG1a, op1_addr
| GET_ZVAL_LVAL ZREG_FCARG2a, op2_addr
| EXT_CALL zend_jit_add_arrays_helper, r0
| SET_ZVAL_PTR res_addr, r0
| SET_ZVAL_TYPE_INFO res_addr, IS_ARRAY_EX
| FREE_OP opline->op1_type, opline->op1, op1_info, 0, opline
| FREE_OP opline->op2_type, opline->op2, op2_info, 0, opline
return 1;
}
static int zend_jit_long_math_helper(dasm_State **Dst,
const zend_op *opline,
zend_uchar opcode,
zend_uchar op1_type,
znode_op op1,
zend_jit_addr op1_addr,
uint32_t op1_info,
zend_ssa_range *op1_range,
zend_uchar op2_type,
znode_op op2,
zend_jit_addr op2_addr,
uint32_t op2_info,
zend_ssa_range *op2_range,
uint32_t res_var,
zend_jit_addr res_addr,
uint32_t res_info,
uint32_t res_use_info,
int may_throw)
/* Labels: 6 */
{
zend_bool same_ops = zend_jit_same_addr(op1_addr, op2_addr);
zend_reg result_reg;
if (op1_info & ((MAY_BE_ANY|MAY_BE_UNDEF)-MAY_BE_LONG)) {
| IF_NOT_ZVAL_TYPE op1_addr, IS_LONG, >6
}
if (!same_ops && (op2_info & ((MAY_BE_ANY|MAY_BE_UNDEF)-MAY_BE_LONG))) {
| IF_NOT_ZVAL_TYPE op2_addr, IS_LONG, >6
}
if (opcode == ZEND_MOD) {
result_reg = ZREG_RAX;
} else if (Z_MODE(res_addr) == IS_REG) {
if ((opline->opcode == ZEND_SL || opline->opcode == ZEND_SR)
&& opline->op2_type != IS_CONST) {
result_reg = ZREG_R0;
} else {
result_reg = Z_REG(res_addr);
}
} else if (Z_MODE(op1_addr) == IS_REG && Z_LAST_USE(op1_addr)) {
result_reg = Z_REG(op1_addr);
} else if (Z_REG(res_addr) != ZREG_R0) {
result_reg = ZREG_R0;
} else {
/* ASSIGN_DIM_OP */
if (sizeof(void*) == 4
&& (opcode == ZEND_SL || opcode == ZEND_SR)
&& Z_MODE(op2_addr) != IS_CONST_ZVAL) {
result_reg = ZREG_R2;
} else {
result_reg = ZREG_FCARG1a;
}
}
if (opcode == ZEND_SL) {
if (Z_MODE(op2_addr) == IS_CONST_ZVAL) {
zend_long op2_lval = Z_LVAL_P(Z_ZV(op2_addr));
if (UNEXPECTED((zend_ulong)op2_lval >= SIZEOF_ZEND_LONG * 8)) {
if (EXPECTED(op2_lval > 0)) {
| xor Ra(result_reg), Ra(result_reg)
} else {
zend_jit_invalidate_var_if_necessary(Dst, op1_type, op1_addr, op1);
zend_jit_invalidate_var_if_necessary(Dst, op2_type, op2_addr, op2);
| SET_EX_OPLINE opline, r0
| jmp ->negative_shift
}
} else if (Z_MODE(op1_addr) == IS_REG && op2_lval == 1) {
| lea Ra(result_reg), [Ra(Z_REG(op1_addr))+Ra(Z_REG(op1_addr))]
} else {
| GET_ZVAL_LVAL result_reg, op1_addr
| shl Ra(result_reg), op2_lval
}
} else {
if (Z_MODE(op2_addr) != IS_REG || Z_REG(op2_addr) != ZREG_RCX) {
| GET_ZVAL_LVAL ZREG_RCX, op2_addr
}
if (!op2_range ||
op2_range->min < 0 ||
op2_range->max >= SIZEOF_ZEND_LONG * 8) {
| cmp r1, (SIZEOF_ZEND_LONG*8)
| jae >1
|.cold_code
|1:
| cmp r1, 0
| mov Ra(result_reg), 0
| jg >1
zend_jit_invalidate_var_if_necessary(Dst, op1_type, op1_addr, op1);
zend_jit_invalidate_var_if_necessary(Dst, op2_type, op2_addr, op2);
| SET_EX_OPLINE opline, r0
| jmp ->negative_shift
|.code
}
| GET_ZVAL_LVAL result_reg, op1_addr
| shl Ra(result_reg), cl
|1:
}
} else if (opcode == ZEND_SR) {
| GET_ZVAL_LVAL result_reg, op1_addr
if (Z_MODE(op2_addr) == IS_CONST_ZVAL) {
zend_long op2_lval = Z_LVAL_P(Z_ZV(op2_addr));
if (UNEXPECTED((zend_ulong)op2_lval >= SIZEOF_ZEND_LONG * 8)) {
if (EXPECTED(op2_lval > 0)) {
| sar Ra(result_reg), (SIZEOF_ZEND_LONG * 8) - 1
} else {
zend_jit_invalidate_var_if_necessary(Dst, op1_type, op1_addr, op1);
zend_jit_invalidate_var_if_necessary(Dst, op2_type, op2_addr, op2);
| SET_EX_OPLINE opline, r0
| jmp ->negative_shift
}
} else {
| sar Ra(result_reg), op2_lval
}
} else {
if (Z_MODE(op2_addr) != IS_REG || Z_REG(op2_addr) != ZREG_RCX) {
| GET_ZVAL_LVAL ZREG_RCX, op2_addr
}
if (!op2_range ||
op2_range->min < 0 ||
op2_range->max >= SIZEOF_ZEND_LONG * 8) {
| cmp r1, (SIZEOF_ZEND_LONG*8)
| jae >1
|.cold_code
|1:
| cmp r1, 0
| mov r1, (SIZEOF_ZEND_LONG * 8) - 1
| jg >1
zend_jit_invalidate_var_if_necessary(Dst, op1_type, op1_addr, op1);
zend_jit_invalidate_var_if_necessary(Dst, op2_type, op2_addr, op2);
| SET_EX_OPLINE opline, r0
| jmp ->negative_shift
|.code
}
|1:
| sar Ra(result_reg), cl
}
} else if (opcode == ZEND_MOD) {
if (Z_MODE(op2_addr) == IS_CONST_ZVAL) {
zend_long op2_lval = Z_LVAL_P(Z_ZV(op2_addr));
if (op2_lval == 0) {
zend_jit_invalidate_var_if_necessary(Dst, op1_type, op1_addr, op1);
zend_jit_invalidate_var_if_necessary(Dst, op2_type, op2_addr, op2);
| SET_EX_OPLINE opline, r0
| jmp ->mod_by_zero
} else if (zend_long_is_power_of_two(op2_lval) && op1_range && op1_range->min >= 0) {
zval tmp;
zend_jit_addr tmp_addr;
zend_reg tmp_reg;
/* Optimisation for mod of power of 2 */
ZVAL_LONG(&tmp, op2_lval - 1);
tmp_addr = ZEND_ADDR_CONST_ZVAL(&tmp);
if (Z_MODE(res_addr) == IS_MEM_ZVAL && Z_REG(res_addr) == ZREG_R0) {
tmp_reg = ZREG_R1;
} else if (result_reg != ZREG_R0) {
tmp_reg = ZREG_R0;
} else {
tmp_reg = ZREG_R1;
}
| GET_ZVAL_LVAL result_reg, op1_addr
| LONG_MATH ZEND_BW_AND, result_reg, tmp_addr, tmp_reg
(void)tmp_reg;
} else {
if (Z_MODE(res_addr) == IS_MEM_ZVAL && Z_REG(res_addr) == ZREG_RAX) {
| mov aword T1, r0 // save
} else if (Z_MODE(res_addr) == IS_MEM_ZVAL && Z_REG(res_addr) == ZREG_RCX) {
| mov aword T1, Ra(ZREG_RCX) // save
}
result_reg = ZREG_RDX;
if (op2_lval == -1) {
| xor Ra(result_reg), Ra(result_reg)
} else {
| GET_ZVAL_LVAL ZREG_RAX, op1_addr
| GET_ZVAL_LVAL ZREG_RCX, op2_addr
|.if X64
| cqo
|.else
| cdq
|.endif
| idiv Ra(ZREG_RCX)
}
if (Z_MODE(res_addr) == IS_MEM_ZVAL && Z_REG(res_addr) == ZREG_RAX) {
| mov r0, aword T1 // restore
} else if (Z_MODE(res_addr) == IS_MEM_ZVAL && Z_REG(res_addr) == ZREG_RCX) {
| mov Ra(ZREG_RCX), aword T1 // restore
}
}
} else {
if (!op2_range || (op2_range->min <= 0 && op2_range->max >= 0)) {
if (Z_MODE(op2_addr) == IS_MEM_ZVAL) {
| cmp aword [Ra(Z_REG(op2_addr))+Z_OFFSET(op2_addr)], 0
} else if (Z_MODE(op2_addr) == IS_REG) {
| test Ra(Z_REG(op2_addr)), Ra(Z_REG(op2_addr))
}
| jz >1
|.cold_code
|1:
zend_jit_invalidate_var_if_necessary(Dst, op1_type, op1_addr, op1);
zend_jit_invalidate_var_if_necessary(Dst, op2_type, op2_addr, op2);
| SET_EX_OPLINE opline, r0
| jmp ->mod_by_zero
|.code
}
/* Prevent overflow error/crash if op1 == LONG_MIN and op2 == -1 */
if (!op2_range || (op2_range->min <= -1 && op2_range->max >= -1)) {
if (Z_MODE(op2_addr) == IS_MEM_ZVAL) {
| cmp aword [Ra(Z_REG(op2_addr))+Z_OFFSET(op2_addr)], -1
} else if (Z_MODE(op2_addr) == IS_REG) {
| cmp Ra(Z_REG(op2_addr)), -1
}
| jz >1
|.cold_code
|1:
| SET_ZVAL_LVAL res_addr, 0
if (Z_MODE(res_addr) == IS_MEM_ZVAL) {
if (Z_MODE(op1_addr) != IS_MEM_ZVAL || Z_REG(op1_addr) != Z_REG(res_addr) || Z_OFFSET(op1_addr) != Z_OFFSET(res_addr)) {
if ((res_use_info & (MAY_BE_ANY|MAY_BE_UNDEF|MAY_BE_REF|MAY_BE_GUARD)) != MAY_BE_LONG) {
| SET_ZVAL_TYPE_INFO res_addr, IS_LONG
}
}
}
| jmp >5
|.code
}
if (Z_MODE(res_addr) == IS_MEM_ZVAL && Z_REG(res_addr) == ZREG_RAX) {
| mov aword T1, r0 // save
}
result_reg = ZREG_RDX;
| GET_ZVAL_LVAL ZREG_RAX, op1_addr
|.if X64
| cqo
|.else
| cdq
|.endif
if (Z_MODE(op2_addr) == IS_MEM_ZVAL) {
| idiv aword [Ra(Z_REG(op2_addr))+Z_OFFSET(op2_addr)]
} else if (Z_MODE(op2_addr) == IS_REG) {
| idiv Ra(Z_REG(op2_addr))
}
if (Z_MODE(res_addr) == IS_MEM_ZVAL && Z_REG(res_addr) == ZREG_RAX) {
| mov r0, aword T1 // restore
}
}
} else if (same_ops) {
| GET_ZVAL_LVAL result_reg, op1_addr
| LONG_MATH_REG opcode, Ra(result_reg), Ra(result_reg)
} else {
zend_reg tmp_reg;
if (Z_MODE(res_addr) == IS_MEM_ZVAL && Z_REG(res_addr) == ZREG_R0) {
tmp_reg = ZREG_R1;
} else if (result_reg != ZREG_R0) {
tmp_reg = ZREG_R0;
} else {
tmp_reg = ZREG_R1;
}
| GET_ZVAL_LVAL result_reg, op1_addr
| LONG_MATH opcode, result_reg, op2_addr, tmp_reg
(void)tmp_reg;
}
if (Z_MODE(res_addr) != IS_REG || Z_REG(res_addr) != result_reg) {
| SET_ZVAL_LVAL res_addr, Ra(result_reg)
}
if (Z_MODE(res_addr) == IS_MEM_ZVAL) {
if (Z_MODE(op1_addr) != IS_MEM_ZVAL || Z_REG(op1_addr) != Z_REG(res_addr) || Z_OFFSET(op1_addr) != Z_OFFSET(res_addr)) {
if ((res_use_info & (MAY_BE_ANY|MAY_BE_UNDEF|MAY_BE_REF|MAY_BE_GUARD)) != MAY_BE_LONG) {
| SET_ZVAL_TYPE_INFO res_addr, IS_LONG
}
}
}
if ((op1_info & ((MAY_BE_ANY|MAY_BE_UNDEF)-MAY_BE_LONG)) ||
(op2_info & ((MAY_BE_ANY|MAY_BE_UNDEF)-MAY_BE_LONG))) {
if ((op1_info & MAY_BE_LONG) &&
(op2_info & MAY_BE_LONG)) {
|.cold_code
}
|6:
if (Z_MODE(res_addr) == IS_REG) {
zend_jit_addr real_addr = ZEND_ADDR_MEM_ZVAL(ZREG_FP, res_var);
| LOAD_ZVAL_ADDR FCARG1a, real_addr
} else if (Z_REG(res_addr) != ZREG_FCARG1a || Z_OFFSET(res_addr) != 0) {
| LOAD_ZVAL_ADDR FCARG1a, res_addr
}
if (Z_MODE(op1_addr) == IS_REG) {
zend_jit_addr real_addr = ZEND_ADDR_MEM_ZVAL(ZREG_FP, op1.var);
if (!zend_jit_spill_store(Dst, op1_addr, real_addr, op1_info, 1)) {
return 0;
}
op1_addr = real_addr;
}
| LOAD_ZVAL_ADDR FCARG2a, op1_addr
if (Z_MODE(op2_addr) == IS_REG) {
zend_jit_addr real_addr = ZEND_ADDR_MEM_ZVAL(ZREG_FP, op2.var);
if (!zend_jit_spill_store(Dst, op2_addr, real_addr, op2_info, 1)) {
return 0;
}
op2_addr = real_addr;
}
|.if X64
| LOAD_ZVAL_ADDR CARG3, op2_addr
|.else
| sub r4, 12
| PUSH_ZVAL_ADDR op2_addr, r0
|.endif
| SET_EX_OPLINE opline, r0
if (opcode == ZEND_BW_OR) {
| EXT_CALL bitwise_or_function, r0
} else if (opcode == ZEND_BW_AND) {
| EXT_CALL bitwise_and_function, r0
} else if (opcode == ZEND_BW_XOR) {
| EXT_CALL bitwise_xor_function, r0
} else if (opcode == ZEND_SL) {
| EXT_CALL shift_left_function, r0
} else if (opcode == ZEND_SR) {
| EXT_CALL shift_right_function, r0
} else if (opcode == ZEND_MOD) {
| EXT_CALL mod_function, r0
} else {
ZEND_UNREACHABLE();
}
|.if not(X64)
| add r4, 12
|.endif
if (op1_addr == res_addr && (op2_info & MAY_BE_RCN)) {
/* compound assignment may decrement "op2" refcount */
op2_info |= MAY_BE_RC1;
}
| FREE_OP op1_type, op1, op1_info, 0, opline
| FREE_OP op2_type, op2, op2_info, 0, opline
if (may_throw) {
if (opline->opcode == ZEND_ASSIGN_DIM_OP && (opline->op2_type & (IS_VAR|IS_TMP_VAR))) {
| MEM_OP2_1_ZTS cmp, aword, executor_globals, exception, 0, r0
| jne ->exception_handler_free_op2
} else if (Z_MODE(res_addr) == IS_MEM_ZVAL && Z_REG(res_addr) == ZREG_RX) {
zend_jit_check_exception_undef_result(Dst, opline);
} else {
zend_jit_check_exception(Dst);
}
}
if (Z_MODE(res_addr) == IS_REG) {
zend_jit_addr real_addr = ZEND_ADDR_MEM_ZVAL(ZREG_FP, res_var);
if (!zend_jit_load_reg(Dst, real_addr, res_addr, res_info)) {
return 0;
}
}
if ((op1_info & MAY_BE_LONG) &&
(op2_info & MAY_BE_LONG)) {
| jmp >5
|.code
}
}
|5:
return 1;
}
static int zend_jit_long_math(dasm_State **Dst, const zend_op *opline, uint32_t op1_info, zend_ssa_range *op1_range, zend_jit_addr op1_addr, uint32_t op2_info, zend_ssa_range *op2_range, zend_jit_addr op2_addr, uint32_t res_use_info, uint32_t res_info, zend_jit_addr res_addr, int may_throw)
{
ZEND_ASSERT(!(op1_info & MAY_BE_UNDEF) && !(op2_info & MAY_BE_UNDEF));
ZEND_ASSERT((op1_info & MAY_BE_LONG) && (op2_info & MAY_BE_LONG));
if (!zend_jit_long_math_helper(Dst, opline, opline->opcode,
opline->op1_type, opline->op1, op1_addr, op1_info, op1_range,
opline->op2_type, opline->op2, op2_addr, op2_info, op2_range,
opline->result.var, res_addr, res_info, res_use_info, may_throw)) {
return 0;
}
if (!zend_jit_store_var_if_necessary(Dst, opline->result.var, res_addr, res_info)) {
return 0;
}
return 1;
}
static int zend_jit_concat_helper(dasm_State **Dst,
const zend_op *opline,
zend_uchar op1_type,
znode_op op1,
zend_jit_addr op1_addr,
uint32_t op1_info,
zend_uchar op2_type,
znode_op op2,
zend_jit_addr op2_addr,
uint32_t op2_info,
zend_jit_addr res_addr,
int may_throw)
{
#if 1
if ((op1_info & MAY_BE_STRING) && (op2_info & MAY_BE_STRING)) {
if (op1_info & ((MAY_BE_UNDEF|MAY_BE_ANY|MAY_BE_REF) - MAY_BE_STRING)) {
| IF_NOT_ZVAL_TYPE op1_addr, IS_STRING, >6
}
if (op2_info & ((MAY_BE_UNDEF|MAY_BE_ANY|MAY_BE_REF) - MAY_BE_STRING)) {
| IF_NOT_ZVAL_TYPE op2_addr, IS_STRING, >6
}
if (Z_MODE(op1_addr) == IS_MEM_ZVAL && Z_REG(op1_addr) == Z_REG(res_addr) && Z_OFFSET(op1_addr) == Z_OFFSET(res_addr)) {
if (Z_REG(res_addr) != ZREG_FCARG1a || Z_OFFSET(res_addr) != 0) {
| LOAD_ZVAL_ADDR FCARG1a, res_addr
}
| LOAD_ZVAL_ADDR FCARG2a, op2_addr
| EXT_CALL zend_jit_fast_assign_concat_helper, r0
/* concatination with itself may reduce refcount */
op2_info |= MAY_BE_RC1;
} else {
if (Z_REG(res_addr) != ZREG_FCARG1a || Z_OFFSET(res_addr) != 0) {
| LOAD_ZVAL_ADDR FCARG1a, res_addr
}
| LOAD_ZVAL_ADDR FCARG2a, op1_addr
|.if X64
| LOAD_ZVAL_ADDR CARG3, op2_addr
|.else
| sub r4, 12
| PUSH_ZVAL_ADDR op2_addr, r0
|.endif
| EXT_CALL zend_jit_fast_concat_helper, r0
|.if not(X64)
| add r4, 12
|.endif
}
/* concatination with empty string may increase refcount */
op1_info |= MAY_BE_RCN;
op2_info |= MAY_BE_RCN;
| FREE_OP op1_type, op1, op1_info, 0, opline
| FREE_OP op2_type, op2, op2_info, 0, opline
|5:
}
if ((op1_info & ((MAY_BE_UNDEF|MAY_BE_ANY|MAY_BE_REF) - MAY_BE_STRING)) ||
(op2_info & ((MAY_BE_UNDEF|MAY_BE_ANY|MAY_BE_REF) - MAY_BE_STRING))) {
if ((op1_info & MAY_BE_STRING) && (op2_info & MAY_BE_STRING)) {
|.cold_code
|6:
}
#endif
if (Z_REG(res_addr) != ZREG_FCARG1a || Z_OFFSET(res_addr) != 0) {
| LOAD_ZVAL_ADDR FCARG1a, res_addr
}
| LOAD_ZVAL_ADDR FCARG2a, op1_addr
|.if X64
| LOAD_ZVAL_ADDR CARG3, op2_addr
|.else
| sub r4, 12
| PUSH_ZVAL_ADDR op2_addr, r0
|.endif
| SET_EX_OPLINE opline, r0
| EXT_CALL concat_function, r0
|.if not(X64)
| add r4, 12
|.endif
/* concatination with empty string may increase refcount */
op1_info |= MAY_BE_RCN;
op2_info |= MAY_BE_RCN;
| FREE_OP op1_type, op1, op1_info, 0, opline
| FREE_OP op2_type, op2, op2_info, 0, opline
if (may_throw) {
if (Z_MODE(res_addr) == IS_MEM_ZVAL && Z_REG(res_addr) == ZREG_RX) {
zend_jit_check_exception_undef_result(Dst, opline);
} else {
zend_jit_check_exception(Dst);
}
}
#if 1
if ((op1_info & MAY_BE_STRING) && (op2_info & MAY_BE_STRING)) {
| jmp <5
|.code
}
}
#endif
return 1;
}
static int zend_jit_concat(dasm_State **Dst, const zend_op *opline, uint32_t op1_info, uint32_t op2_info, zend_jit_addr res_addr, int may_throw)
{
zend_jit_addr op1_addr, op2_addr;
ZEND_ASSERT(!(op1_info & MAY_BE_UNDEF) && !(op2_info & MAY_BE_UNDEF));
ZEND_ASSERT((op1_info & MAY_BE_STRING) && (op2_info & MAY_BE_STRING));
op1_addr = OP1_ADDR();
op2_addr = OP2_ADDR();
return zend_jit_concat_helper(Dst, opline, opline->op1_type, opline->op1, op1_addr, op1_info, opline->op2_type, opline->op2, op2_addr, op2_info, res_addr, may_throw);
}
static int zend_jit_fetch_dimension_address_inner(dasm_State **Dst, const zend_op *opline, uint32_t type, uint32_t op1_info, uint32_t op2_info, const void *found_exit_addr, const void *not_found_exit_addr, const void *exit_addr)
/* Labels: 1,2,3,4,5 */
{
zend_jit_addr op2_addr = OP2_ADDR();
zend_jit_addr res_addr = ZEND_ADDR_MEM_ZVAL(ZREG_FP, opline->result.var);
if (JIT_G(trigger) == ZEND_JIT_ON_HOT_TRACE
&& type == BP_VAR_R
&& !exit_addr) {
int32_t exit_point = zend_jit_trace_get_exit_point(opline, ZEND_JIT_EXIT_TO_VM);
exit_addr = zend_jit_trace_get_exit_addr(exit_point);
if (!exit_addr) {
return 0;
}
}
if (op2_info & MAY_BE_LONG) {
zend_bool op2_loaded = 0;
zend_bool packed_loaded = 0;
zend_bool bad_packed_key = 0;
if (op2_info & ((MAY_BE_ANY|MAY_BE_UNDEF) - MAY_BE_LONG)) {
| // if (EXPECTED(Z_TYPE_P(dim) == IS_LONG))
| IF_NOT_ZVAL_TYPE op2_addr, IS_LONG, >3
}
if (op1_info & MAY_BE_PACKED_GUARD) {
int32_t exit_point = zend_jit_trace_get_exit_point(opline, ZEND_JIT_EXIT_PACKED_GUARD);
const void *exit_addr = zend_jit_trace_get_exit_addr(exit_point);
if (!exit_addr) {
return 0;
}
if (op1_info & MAY_BE_ARRAY_PACKED) {
| test dword [FCARG1a + offsetof(zend_array, u.flags)], HASH_FLAG_PACKED
| jz &exit_addr
} else {
| test dword [FCARG1a + offsetof(zend_array, u.flags)], HASH_FLAG_PACKED
| jnz &exit_addr
}
}
if (type == BP_VAR_W) {
| // hval = Z_LVAL_P(dim);
| GET_ZVAL_LVAL ZREG_FCARG2a, op2_addr
op2_loaded = 1;
}
if (op1_info & MAY_BE_ARRAY_PACKED) {
zend_long val = -1;
if (Z_MODE(op2_addr) == IS_CONST_ZVAL) {
val = Z_LVAL_P(Z_ZV(op2_addr));
if (val >= 0 && val < HT_MAX_SIZE) {
packed_loaded = 1;
} else {
bad_packed_key = 1;
}
} else {
if (!op2_loaded) {
| // hval = Z_LVAL_P(dim);
| GET_ZVAL_LVAL ZREG_FCARG2a, op2_addr
op2_loaded = 1;
}
packed_loaded = 1;
}
if (packed_loaded) {
| // ZEND_HASH_INDEX_FIND(ht, hval, retval, num_undef);
if (op1_info & MAY_BE_ARRAY_HASH) {
| test dword [FCARG1a + offsetof(zend_array, u.flags)], HASH_FLAG_PACKED
| jz >4 // HASH_FIND
}
| // if (EXPECTED((zend_ulong)(_h) < (zend_ulong)(_ht)->nNumUsed))
|.if X64
| mov eax, dword [FCARG1a + offsetof(zend_array, nNumUsed)]
if (val == 0) {
| test r0, r0
} else if (val > 0 && !op2_loaded) {
| cmp r0, val
} else {
| cmp r0, FCARG2a
}
|.else
if (val >= 0 && !op2_loaded) {
| cmp dword [FCARG1a + offsetof(zend_array, nNumUsed)], val
} else {
| cmp dword [FCARG1a + offsetof(zend_array, nNumUsed)], FCARG2a
}
|.endif
if (type == BP_JIT_IS) {
if (not_found_exit_addr) {
| jbe ¬_found_exit_addr
} else {
| jbe >9 // NOT_FOUND
}
} else if (JIT_G(trigger) == ZEND_JIT_ON_HOT_TRACE && type == BP_VAR_R) {
| jbe &exit_addr
} else if (type == BP_VAR_IS && not_found_exit_addr) {
| jbe ¬_found_exit_addr
} else if (type == BP_VAR_IS && found_exit_addr) {
| jbe >7 // NOT_FOUND
} else {
| jbe >2 // NOT_FOUND
}
| // _ret = &_ht->arData[_h].val;
if (val >= 0) {
| mov r0, aword [FCARG1a + offsetof(zend_array, arData)]
if (val != 0) {
| add r0, val * sizeof(Bucket)
}
} else {
|.if X64
| mov r0, FCARG2a
| shl r0, 5
|.else
| imul r0, FCARG2a, sizeof(Bucket)
|.endif
| add r0, aword [FCARG1a + offsetof(zend_array, arData)]
}
}
}
switch (type) {
case BP_JIT_IS:
if (op1_info & MAY_BE_ARRAY_HASH) {
if (packed_loaded) {
| jmp >5
}
|4:
if (!op2_loaded) {
| // hval = Z_LVAL_P(dim);
| GET_ZVAL_LVAL ZREG_FCARG2a, op2_addr
}
if (packed_loaded) {
| EXT_CALL _zend_hash_index_find, r0
} else {
| EXT_CALL zend_hash_index_find, r0
}
| test r0, r0
if (not_found_exit_addr) {
| jz ¬_found_exit_addr
} else {
| jz >9 // NOT_FOUND
}
if (op2_info & MAY_BE_STRING) {
| jmp >5
}
} else if (packed_loaded) {
if (op2_info & MAY_BE_STRING) {
| jmp >5
}
} else if (not_found_exit_addr) {
| jmp ¬_found_exit_addr
} else {
| jmp >9 // NOT_FOUND
}
break;
case BP_VAR_R:
case BP_VAR_IS:
case BP_VAR_UNSET:
if (packed_loaded) {
if (op1_info & MAY_BE_ARRAY_HASH) {
| IF_NOT_Z_TYPE r0, IS_UNDEF, >8
} else if (JIT_G(trigger) == ZEND_JIT_ON_HOT_TRACE && type == BP_VAR_R) {
/* perform IS_UNDEF check only after result type guard (during deoptimization) */
if (!found_exit_addr || (op1_info & MAY_BE_ARRAY_HASH)) {
| IF_Z_TYPE r0, IS_UNDEF, &exit_addr
}
} else if (type == BP_VAR_IS && not_found_exit_addr) {
| IF_Z_TYPE r0, IS_UNDEF, ¬_found_exit_addr
} else if (type == BP_VAR_IS && found_exit_addr) {
| IF_Z_TYPE r0, IS_UNDEF, >7 // NOT_FOUND
} else {
| IF_Z_TYPE r0, IS_UNDEF, >2 // NOT_FOUND
}
}
if (!(op1_info & MAY_BE_ARRAY_KEY_LONG) || (packed_loaded && (op1_info & MAY_BE_ARRAY_HASH))) {
if (JIT_G(trigger) == ZEND_JIT_ON_HOT_TRACE && type == BP_VAR_R) {
| jmp &exit_addr
} else if (type == BP_VAR_IS && not_found_exit_addr) {
| jmp ¬_found_exit_addr
} else if (type == BP_VAR_IS && found_exit_addr) {
| jmp >7 // NOT_FOUND
} else {
| jmp >2 // NOT_FOUND
}
}
if (op1_info & MAY_BE_ARRAY_HASH) {
|4:
if (!op2_loaded) {
| // hval = Z_LVAL_P(dim);
| GET_ZVAL_LVAL ZREG_FCARG2a, op2_addr
}
if (packed_loaded) {
| EXT_CALL _zend_hash_index_find, r0
} else {
| EXT_CALL zend_hash_index_find, r0
}
| test r0, r0
if (JIT_G(trigger) == ZEND_JIT_ON_HOT_TRACE && type == BP_VAR_R) {
| jz &exit_addr
} else if (type == BP_VAR_IS && not_found_exit_addr) {
| jz ¬_found_exit_addr
} else if (type == BP_VAR_IS && found_exit_addr) {
| jz >7 // NOT_FOUND
} else {
| jz >2 // NOT_FOUND
}
}
|.cold_code
|2:
switch (type) {
case BP_VAR_R:
if (JIT_G(trigger) != ZEND_JIT_ON_HOT_TRACE) {
| // zend_error(E_WARNING,"Undefined array key " ZEND_LONG_FMT, hval);
| // retval = &EG(uninitialized_zval);
| UNDEFINED_OFFSET opline
| jmp >9
}
break;
case BP_VAR_IS:
case BP_VAR_UNSET:
if (!not_found_exit_addr && !found_exit_addr) {
| // retval = &EG(uninitialized_zval);
| SET_ZVAL_TYPE_INFO res_addr, IS_NULL
| jmp >9
}
break;
default:
ZEND_UNREACHABLE();
}
|.code
break;
case BP_VAR_RW:
if (packed_loaded) {
| IF_NOT_Z_TYPE r0, IS_UNDEF, >8
}
|2:
|4:
if (!op2_loaded) {
| // hval = Z_LVAL_P(dim);
| GET_ZVAL_LVAL ZREG_FCARG2a, op2_addr
}
| SET_EX_OPLINE opline, r0
if (packed_loaded) {
| EXT_CALL zend_jit_hash_index_lookup_rw_no_packed, r0
} else {
| EXT_CALL zend_jit_hash_index_lookup_rw, r0
}
| test r0, r0
| jz >9
break;
case BP_VAR_W:
if (packed_loaded) {
| IF_NOT_Z_TYPE r0, IS_UNDEF, >8
}
if (!(op1_info & MAY_BE_ARRAY_KEY_LONG) || packed_loaded || bad_packed_key) {
|2:
| //retval = zend_hash_index_add_new(ht, hval, &EG(uninitialized_zval));
if (!op2_loaded) {
| // hval = Z_LVAL_P(dim);
| GET_ZVAL_LVAL ZREG_FCARG2a, op2_addr
}
|.if X64
| LOAD_ADDR_ZTS CARG3, executor_globals, uninitialized_zval
|.else
| sub r4, 12
| PUSH_ADDR_ZTS executor_globals, uninitialized_zval, r0
|.endif
| EXT_CALL zend_hash_index_add_new, r0
|.if not(X64)
| add r4, 12
|.endif
if (op1_info & MAY_BE_ARRAY_HASH) {
| jmp >8
}
}
if (op1_info & MAY_BE_ARRAY_HASH) {
|4:
if (!op2_loaded) {
| // hval = Z_LVAL_P(dim);
| GET_ZVAL_LVAL ZREG_FCARG2a, op2_addr
}
| EXT_CALL zend_jit_hash_index_lookup_w, r0
}
break;
default:
ZEND_UNREACHABLE();
}
if (type != BP_JIT_IS && (op2_info & MAY_BE_STRING)) {
| jmp >8
}
}
if (op2_info & MAY_BE_STRING) {
|3:
if (op2_info & ((MAY_BE_ANY|MAY_BE_UNDEF) - (MAY_BE_LONG|MAY_BE_STRING))) {
| // if (EXPECTED(Z_TYPE_P(dim) == IS_STRING))
| IF_NOT_ZVAL_TYPE op2_addr, IS_STRING, >3
}
| // offset_key = Z_STR_P(dim);
| GET_ZVAL_LVAL ZREG_FCARG2a, op2_addr
| // retval = zend_hash_find(ht, offset_key);
switch (type) {
case BP_JIT_IS:
if (opline->op2_type != IS_CONST) {
| cmp byte [FCARG2a + offsetof(zend_string, val)], '9'
| jle >1
|.cold_code
|1:
| EXT_CALL zend_jit_symtable_find, r0
| jmp >1
|.code
| EXT_CALL zend_hash_find, r0
|1:
} else {
| EXT_CALL _zend_hash_find_known_hash, r0
}
| test r0, r0
if (not_found_exit_addr) {
| jz ¬_found_exit_addr
} else {
| jz >9 // NOT_FOUND
}
| // if (UNEXPECTED(Z_TYPE_P(retval) == IS_INDIRECT))
| IF_NOT_Z_TYPE r0, IS_INDIRECT, >1
| GET_Z_PTR r0, r0
|1:
break;
case BP_VAR_R:
case BP_VAR_IS:
case BP_VAR_UNSET:
if (opline->op2_type != IS_CONST) {
| cmp byte [FCARG2a + offsetof(zend_string, val)], '9'
| jle >1
|.cold_code
|1:
| EXT_CALL zend_jit_symtable_find, r0
| jmp >1
|.code
| EXT_CALL zend_hash_find, r0
|1:
} else {
| EXT_CALL _zend_hash_find_known_hash, r0
}
| test r0, r0
if (JIT_G(trigger) == ZEND_JIT_ON_HOT_TRACE && type == BP_VAR_R) {
| jz &exit_addr
} else if (type == BP_VAR_IS && not_found_exit_addr) {
| jz ¬_found_exit_addr
} else if (type == BP_VAR_IS && found_exit_addr) {
| jz >7 // NOT_FOUND
} else {
| jz >2 // NOT_FOUND
}
| // if (UNEXPECTED(Z_TYPE_P(retval) == IS_INDIRECT))
| IF_Z_TYPE r0, IS_INDIRECT, >1 // SLOW
|.cold_code
|1:
| // retval = Z_INDIRECT_P(retval);
| GET_Z_PTR r0, r0
| IF_NOT_Z_TYPE r0, IS_UNDEF, >8
if (JIT_G(trigger) == ZEND_JIT_ON_HOT_TRACE && type == BP_VAR_R) {
| jmp &exit_addr
} else if (type == BP_VAR_IS && not_found_exit_addr) {
| jmp ¬_found_exit_addr
}
|2:
switch (type) {
case BP_VAR_R:
if (JIT_G(trigger) != ZEND_JIT_ON_HOT_TRACE) {
// zend_error(E_WARNING, "Undefined array key \"%s\"", ZSTR_VAL(offset_key));
| UNDEFINED_INDEX opline
| jmp >9
}
break;
case BP_VAR_IS:
case BP_VAR_UNSET:
if (!not_found_exit_addr && !found_exit_addr) {
| // retval = &EG(uninitialized_zval);
| SET_ZVAL_TYPE_INFO res_addr, IS_NULL
| jmp >9
}
break;
default:
ZEND_UNREACHABLE();
}
|.code
break;
case BP_VAR_RW:
| SET_EX_OPLINE opline, r0
if (opline->op2_type != IS_CONST) {
| EXT_CALL zend_jit_symtable_lookup_rw, r0
} else {
| EXT_CALL zend_jit_hash_lookup_rw, r0
}
| test r0, r0
| jz >9
break;
case BP_VAR_W:
if (opline->op2_type != IS_CONST) {
| EXT_CALL zend_jit_symtable_lookup_w, r0
} else {
| EXT_CALL zend_jit_hash_lookup_w, r0
}
break;
default:
ZEND_UNREACHABLE();
}
}
if (type == BP_JIT_IS && (op2_info & (MAY_BE_LONG|MAY_BE_STRING))) {
|5:
if (op1_info & MAY_BE_ARRAY_OF_REF) {
| ZVAL_DEREF r0, MAY_BE_REF
}
| cmp byte [r0 + 8], IS_NULL
if (not_found_exit_addr) {
| jle ¬_found_exit_addr
} else if (found_exit_addr) {
| jg &found_exit_addr
} else {
| jle >9 // NOT FOUND
}
}
if (op2_info & ((MAY_BE_ANY|MAY_BE_UNDEF) - (MAY_BE_LONG|MAY_BE_STRING))) {
if (op2_info & (MAY_BE_LONG|MAY_BE_STRING)) {
|.cold_code
|3:
}
| SET_EX_OPLINE opline, r0
| LOAD_ZVAL_ADDR FCARG2a, op2_addr
switch (type) {
case BP_VAR_R:
|.if X64
| LOAD_ZVAL_ADDR CARG3, res_addr
|.else
| sub r4, 12
| PUSH_ZVAL_ADDR res_addr, r0
|.endif
| EXT_CALL zend_jit_fetch_dim_r_helper, r0
|.if not(X64)
| add r4, 12
|.endif
| jmp >9
break;
case BP_JIT_IS:
| EXT_CALL zend_jit_fetch_dim_isset_helper, r0
| test r0, r0
if (not_found_exit_addr) {
| je ¬_found_exit_addr
if (op2_info & (MAY_BE_LONG|MAY_BE_STRING)) {
| jmp >8
}
} else if (found_exit_addr) {
| jne &found_exit_addr
if (op2_info & (MAY_BE_LONG|MAY_BE_STRING)) {
| jmp >9
}
} else {
| jne >8
| jmp >9
}
break;
case BP_VAR_IS:
case BP_VAR_UNSET:
|.if X64
| LOAD_ZVAL_ADDR CARG3, res_addr
|.else
| sub r4, 12
| PUSH_ZVAL_ADDR res_addr, r0
|.endif
| EXT_CALL zend_jit_fetch_dim_is_helper, r0
|.if not(X64)
| add r4, 12
|.endif
| jmp >9
break;
case BP_VAR_RW:
| EXT_CALL zend_jit_fetch_dim_rw_helper, r0
| test r0, r0
| jne >8
| jmp >9
break;
case BP_VAR_W:
| EXT_CALL zend_jit_fetch_dim_w_helper, r0
| test r0, r0
| jne >8
| jmp >9
break;
default:
ZEND_UNREACHABLE();
}
if (op2_info & (MAY_BE_LONG|MAY_BE_STRING)) {
|.code
}
}
return 1;
}
static int zend_jit_simple_assign(dasm_State **Dst,
const zend_op *opline,
zend_jit_addr var_addr,
uint32_t var_info,
uint32_t var_def_info,
zend_uchar val_type,
zend_jit_addr val_addr,
uint32_t val_info,
zend_jit_addr res_addr,
int in_cold,
int save_r1)
/* Labels: 1,2,3 */
{
zend_reg tmp_reg;
if (Z_MODE(var_addr) == IS_REG || Z_REG(var_addr) != ZREG_R0) {
tmp_reg = ZREG_R0;
} else {
/* ASSIGN_DIM */
tmp_reg = ZREG_FCARG1a;
}
if (Z_MODE(val_addr) == IS_CONST_ZVAL) {
zval *zv = Z_ZV(val_addr);
if (!res_addr) {
| ZVAL_COPY_CONST var_addr, var_info, var_def_info, zv, tmp_reg
} else {
| ZVAL_COPY_CONST_2 var_addr, res_addr, var_info, var_def_info, zv, tmp_reg
}
if (Z_REFCOUNTED_P(zv)) {
if (!res_addr) {
| ADDREF_CONST zv, Ra(tmp_reg)
} else {
| ADDREF_CONST_2 zv, Ra(tmp_reg)
}
}
} else {
if (val_info & MAY_BE_UNDEF) {
if (in_cold) {
| IF_NOT_ZVAL_TYPE val_addr, IS_UNDEF, >2
} else {
| IF_ZVAL_TYPE val_addr, IS_UNDEF, >1
|.cold_code
|1:
}
| // zend_error(E_WARNING, "Undefined variable $%s", ZSTR_VAL(CV_DEF_OF(EX_VAR_TO_NUM(opline->op1.var))));
if (save_r1) {
| mov aword T1, FCARG1a // save
}
| SET_ZVAL_TYPE_INFO var_addr, IS_NULL
if (res_addr) {
| SET_ZVAL_TYPE_INFO res_addr, IS_NULL
}
if (opline) {
| SET_EX_OPLINE opline, Ra(tmp_reg)
}
ZEND_ASSERT(Z_MODE(val_addr) == IS_MEM_ZVAL && Z_REG(val_addr) == ZREG_FP);
| mov FCARG1d, Z_OFFSET(val_addr)
| EXT_CALL zend_jit_undefined_op_helper, r0
| test r0, r0
| jz ->exception_handler_undef
if (save_r1) {
| mov FCARG1a, aword T1 // restore
}
| jmp >3
if (in_cold) {
|2:
} else {
|.code
}
}
if (val_info & MAY_BE_REF) {
if (val_type == IS_CV) {
ZEND_ASSERT(Z_REG(var_addr) != ZREG_R2);
if (Z_MODE(val_addr) != IS_MEM_ZVAL || Z_REG(val_addr) != ZREG_R2 || Z_OFFSET(val_addr) != 0) {
| LOAD_ZVAL_ADDR r2, val_addr
}
| ZVAL_DEREF r2, val_info
val_addr = ZEND_ADDR_MEM_ZVAL(ZREG_R2, 0);
} else {
zend_jit_addr ref_addr;
zend_reg type_reg = tmp_reg;
if (in_cold) {
| IF_NOT_ZVAL_TYPE val_addr, IS_REFERENCE, >1
} else {
| IF_ZVAL_TYPE val_addr, IS_REFERENCE, >1
|.cold_code
|1:
}
| // zend_refcounted *ref = Z_COUNTED_P(retval_ptr);
| GET_ZVAL_PTR r2, val_addr
| GC_DELREF r2
| // ZVAL_COPY_VALUE(return_value, &ref->value);
ref_addr = ZEND_ADDR_MEM_ZVAL(ZREG_R2, 8);
if (!res_addr) {
| ZVAL_COPY_VALUE var_addr, var_info, ref_addr, val_info, type_reg, tmp_reg
} else {
| ZVAL_COPY_VALUE_2 var_addr, var_info, res_addr, ref_addr, val_info, type_reg, tmp_reg
}
| je >2
if (tmp_reg == ZREG_R0) {
| IF_NOT_REFCOUNTED ah, >3
} else {
| IF_NOT_FLAGS Rd(tmp_reg), (IS_TYPE_REFCOUNTED << Z_TYPE_FLAGS_SHIFT), >3
}
| GET_ZVAL_PTR Ra(tmp_reg), var_addr
if (!res_addr) {
| GC_ADDREF Ra(tmp_reg)
} else {
| add dword [Ra(tmp_reg)], 2
}
| jmp >3
|2:
if (res_addr) {
if (tmp_reg == ZREG_R0) {
| IF_NOT_REFCOUNTED ah, >2
} else {
| IF_NOT_FLAGS Rd(tmp_reg), (IS_TYPE_REFCOUNTED << Z_TYPE_FLAGS_SHIFT), >2
}
| GET_ZVAL_PTR Ra(tmp_reg), var_addr
| GC_ADDREF Ra(tmp_reg)
|2:
}
if (save_r1) {
| mov aword T1, FCARG1a // save
}
| EFREE_REFERENCE r2
if (save_r1) {
| mov FCARG1a, aword T1 // restore
}
| jmp >3
if (in_cold) {
|1:
} else {
|.code
}
}
}
if (!res_addr) {
| ZVAL_COPY_VALUE var_addr, var_info, val_addr, val_info, ZREG_R2, tmp_reg
} else {
| ZVAL_COPY_VALUE_2 var_addr, var_info, res_addr, val_addr, val_info, ZREG_R2, tmp_reg
}
if (val_type == IS_CV) {
if (!res_addr) {
| TRY_ADDREF val_info, dh, Ra(tmp_reg)
} else {
| TRY_ADDREF_2 val_info, dh, Ra(tmp_reg)
}
} else {
if (res_addr) {
| TRY_ADDREF val_info, dh, Ra(tmp_reg)
}
}
|3:
}
return 1;
}
static int zend_jit_assign_to_typed_ref(dasm_State **Dst,
const zend_op *opline,
zend_uchar val_type,
zend_jit_addr val_addr,
zend_jit_addr res_addr,
zend_bool check_exception)
{
| // if (UNEXPECTED(ZEND_REF_HAS_TYPE_SOURCES(Z_REF_P(variable_ptr)))) {
| cmp aword [FCARG1a + offsetof(zend_reference, sources.ptr)], 0
| jnz >2
|.cold_code
|2:
if (Z_MODE(val_addr) != IS_MEM_ZVAL || Z_REG(val_addr) != ZREG_FCARG2a || Z_OFFSET(val_addr) != 0) {
| LOAD_ZVAL_ADDR FCARG2a, val_addr
}
if (opline) {
| SET_EX_OPLINE opline, r0
}
if (val_type == IS_CONST) {
| EXT_CALL zend_jit_assign_const_to_typed_ref, r0
} else if (val_type == IS_TMP_VAR) {
| EXT_CALL zend_jit_assign_tmp_to_typed_ref, r0
} else if (val_type == IS_VAR) {
| EXT_CALL zend_jit_assign_var_to_typed_ref, r0
} else if (val_type == IS_CV) {
| EXT_CALL zend_jit_assign_cv_to_typed_ref, r0
} else {
ZEND_UNREACHABLE();
}
if (res_addr) {
zend_jit_addr ret_addr = ZEND_ADDR_MEM_ZVAL(ZREG_R0, 0);
| ZVAL_COPY_VALUE res_addr, -1, ret_addr, -1, ZREG_R1, ZREG_R2
| TRY_ADDREF -1, ch, r2
}
if (check_exception) {
| // if (UNEXPECTED(EG(exception) != NULL)) {
| MEM_OP2_1_ZTS cmp, aword, executor_globals, exception, 0, r0
| je >8 // END OF zend_jit_assign_to_variable()
| jmp ->exception_handler
} else {
| jmp >8
}
|.code
return 1;
}
static int zend_jit_assign_to_variable_call(dasm_State **Dst,
const zend_op *opline,
zend_jit_addr __var_use_addr,
zend_jit_addr var_addr,
uint32_t __var_info,
uint32_t __var_def_info,
zend_uchar val_type,
zend_jit_addr val_addr,
uint32_t val_info,
zend_jit_addr __res_addr,
zend_bool __check_exception)
{
if (val_info & MAY_BE_UNDEF) {
if (JIT_G(trigger) == ZEND_JIT_ON_HOT_TRACE) {
int32_t exit_point = zend_jit_trace_get_exit_point(opline, ZEND_JIT_EXIT_TO_VM);
const void *exit_addr = zend_jit_trace_get_exit_addr(exit_point);
if (!exit_addr) {
return 0;
}
| IF_ZVAL_TYPE val_addr, IS_UNDEF, &exit_addr
} else {
| IF_ZVAL_TYPE val_addr, IS_UNDEF, >1
|.cold_code
|1:
ZEND_ASSERT(Z_REG(val_addr) == ZREG_FP);
if (Z_REG(var_addr) != ZREG_FP) {
| mov aword T1, Ra(Z_REG(var_addr)) // save
}
| SET_EX_OPLINE opline, r0
| mov FCARG1d, Z_OFFSET(val_addr)
| EXT_CALL zend_jit_undefined_op_helper, r0
if (Z_REG(var_addr) != ZREG_FP) {
| mov Ra(Z_REG(var_addr)), aword T1 // restore
}
if (Z_MODE(var_addr) != IS_MEM_ZVAL || Z_REG(var_addr) != ZREG_FCARG1a || Z_OFFSET(var_addr) != 0) {
| LOAD_ZVAL_ADDR FCARG1a, var_addr
}
| LOAD_ADDR_ZTS FCARG2a, executor_globals, uninitialized_zval
| call ->assign_const
| jmp >9
|.code
}
}
if (Z_MODE(var_addr) != IS_MEM_ZVAL || Z_REG(var_addr) != ZREG_FCARG1a || Z_OFFSET(var_addr) != 0) {
| LOAD_ZVAL_ADDR FCARG1a, var_addr
}
if (Z_MODE(val_addr) != IS_MEM_ZVAL || Z_REG(val_addr) != ZREG_FCARG2a || Z_OFFSET(val_addr) != 0) {
| LOAD_ZVAL_ADDR FCARG2a, val_addr
}
if (opline) {
| SET_EX_OPLINE opline, r0
}
if (!(val_info & (MAY_BE_STRING|MAY_BE_ARRAY|MAY_BE_OBJECT|MAY_BE_RESOURCE|MAY_BE_REF))) {
| call ->assign_tmp
} else if (val_type == IS_CONST) {
| call ->assign_const
} else if (val_type == IS_TMP_VAR) {
| call ->assign_tmp
} else if (val_type == IS_VAR) {
if (!(val_info & MAY_BE_REF)) {
| call ->assign_tmp
} else {
| call ->assign_var
}
} else if (val_type == IS_CV) {
if (!(val_info & MAY_BE_REF)) {
| call ->assign_cv_noref
} else {
| call ->assign_cv
}
if ((val_info & MAY_BE_UNDEF) && JIT_G(trigger) != ZEND_JIT_ON_HOT_TRACE) {
|9:
}
} else {
ZEND_UNREACHABLE();
}
return 1;
}
static int zend_jit_assign_to_variable(dasm_State **Dst,
const zend_op *opline,
zend_jit_addr var_use_addr,
zend_jit_addr var_addr,
uint32_t var_info,
uint32_t var_def_info,
zend_uchar val_type,
zend_jit_addr val_addr,
uint32_t val_info,
zend_jit_addr res_addr,
zend_bool check_exception)
/* Labels: 1,2,3,4,5,8 */
{
int done = 0;
zend_reg ref_reg, tmp_reg;
if (Z_MODE(var_addr) == IS_REG || Z_REG(var_use_addr) != ZREG_R0) {
ref_reg = ZREG_FCARG1a;
tmp_reg = ZREG_R0;
} else {
/* ASSIGN_DIM */
ref_reg = ZREG_R0;
tmp_reg = ZREG_FCARG1a;
}
if (var_info & MAY_BE_REF) {
if (Z_MODE(var_use_addr) != IS_MEM_ZVAL || Z_REG(var_use_addr) != ref_reg || Z_OFFSET(var_use_addr) != 0) {
| LOAD_ZVAL_ADDR Ra(ref_reg), var_use_addr
var_addr = var_use_addr = ZEND_ADDR_MEM_ZVAL(ref_reg, 0);
}
| // if (Z_ISREF_P(variable_ptr)) {
| IF_NOT_Z_TYPE, Ra(ref_reg), IS_REFERENCE, >3
| // if (UNEXPECTED(ZEND_REF_HAS_TYPE_SOURCES(Z_REF_P(variable_ptr)))) {
| GET_Z_PTR FCARG1a, Ra(ref_reg)
if (!zend_jit_assign_to_typed_ref(Dst, opline, val_type, val_addr, res_addr, check_exception)) {
return 0;
}
| lea Ra(ref_reg), [FCARG1a + offsetof(zend_reference, val)]
|3:
}
if (var_info & (MAY_BE_STRING|MAY_BE_ARRAY|MAY_BE_OBJECT|MAY_BE_RESOURCE)) {
if (RC_MAY_BE_1(var_info)) {
int in_cold = 0;
if (var_info & ((MAY_BE_ANY|MAY_BE_UNDEF)-(MAY_BE_OBJECT|MAY_BE_RESOURCE))) {
| IF_ZVAL_REFCOUNTED var_use_addr, >1
|.cold_code
|1:
in_cold = 1;
}
if (Z_REG(var_use_addr) == ZREG_FCARG1a || Z_REG(var_use_addr) == ZREG_R0) {
zend_bool keep_gc = 0;
| GET_ZVAL_PTR Ra(tmp_reg), var_use_addr
if (tmp_reg == ZREG_FCARG1a) {
if (Z_MODE(val_addr) == IS_REG) {
keep_gc = 1;
} else if ((val_info & ((MAY_BE_UNDEF|MAY_BE_ANY|MAY_BE_GUARD)-(MAY_BE_NULL|MAY_BE_FALSE|MAY_BE_TRUE))) == 0) {
keep_gc = 1;
} else if (Z_MODE(val_addr) == IS_CONST_ZVAL) {
if (sizeof(void*) == 4) {
keep_gc = 1;
} else {
zval *zv = Z_ZV(val_addr);
if (Z_TYPE_P(zv) == IS_DOUBLE) {
if (Z_DVAL_P(zv) == 0 || IS_SIGNED_32BIT(zv)) {
keep_gc = 1;
}
} else if (IS_SIGNED_32BIT(Z_LVAL_P(zv))) {
keep_gc = 1;
}
}
} else if (Z_MODE(val_addr) == IS_MEM_ZVAL) {
if ((val_info & (MAY_BE_UNDEF|MAY_BE_ANY|MAY_BE_GUARD)) == MAY_BE_DOUBLE) {
keep_gc = 1;
}
}
}
if (!keep_gc) {
| mov aword T1, Ra(tmp_reg) // save
}
if (!zend_jit_simple_assign(Dst, opline, var_addr, var_info, var_def_info, val_type, val_addr, val_info, res_addr, in_cold, 0)) {
return 0;
}
if (!keep_gc) {
| mov FCARG1a, aword T1 // restore
}
} else {
| GET_ZVAL_PTR FCARG1a, var_use_addr
if (!zend_jit_simple_assign(Dst, opline, var_addr, var_info, var_def_info, val_type, val_addr, val_info, res_addr, in_cold, 1)) {
return 0;
}
}
| GC_DELREF FCARG1a
if (RC_MAY_BE_N(var_info) && (var_info & (MAY_BE_ARRAY|MAY_BE_OBJECT)) != 0) {
| jnz >4
} else {
| jnz >8
}
| ZVAL_DTOR_FUNC var_info, opline
if (in_cold || (RC_MAY_BE_N(var_info) && (var_info & (MAY_BE_ARRAY|MAY_BE_OBJECT)) != 0)) {
if (check_exception) {
| MEM_OP2_1_ZTS cmp, aword, executor_globals, exception, 0, r0
| je >8
| jmp ->exception_handler
} else {
| jmp >8
}
}
if (RC_MAY_BE_N(var_info) && (var_info & (MAY_BE_ARRAY|MAY_BE_OBJECT)) != 0) {
|4:
| IF_GC_MAY_NOT_LEAK FCARG1a, >8
| EXT_CALL gc_possible_root, r0
if (in_cold) {
| jmp >8
}
}
if (in_cold) {
|.code
} else {
done = 1;
}
} else /* if (RC_MAY_BE_N(var_info)) */ {
if (var_info & ((MAY_BE_ANY|MAY_BE_UNDEF)-(MAY_BE_OBJECT|MAY_BE_RESOURCE))) {
| IF_NOT_ZVAL_REFCOUNTED var_use_addr, >5
}
if (var_info & (MAY_BE_ARRAY|MAY_BE_OBJECT)) {
if (Z_REG(var_use_addr) == ZREG_FP) {
| mov T1, Ra(Z_REG(var_use_addr)) // save
}
| GET_ZVAL_PTR FCARG1a, var_use_addr
| GC_DELREF FCARG1a
| IF_GC_MAY_NOT_LEAK FCARG1a, >5
| EXT_CALL gc_possible_root, r0
if (Z_REG(var_use_addr) != ZREG_FP) {
| mov Ra(Z_REG(var_use_addr)), T1 // restore
}
} else {
| GET_ZVAL_PTR Ra(tmp_reg), var_use_addr
| GC_DELREF Ra(tmp_reg)
}
|5:
}
}
if (!done && !zend_jit_simple_assign(Dst, opline, var_addr, var_info, var_def_info, val_type, val_addr, val_info, res_addr, 0, 0)) {
return 0;
}
|8:
return 1;
}
static int zend_jit_assign_dim(dasm_State **Dst, const zend_op *opline, uint32_t op1_info, zend_jit_addr op1_addr, uint32_t op2_info, uint32_t val_info, int may_throw)
{
zend_jit_addr op2_addr, op3_addr, res_addr;
op2_addr = (opline->op2_type != IS_UNUSED) ? OP2_ADDR() : 0;
op3_addr = OP1_DATA_ADDR();
if (opline->result_type == IS_UNUSED) {
res_addr = 0;
} else {
res_addr = ZEND_ADDR_MEM_ZVAL(ZREG_FP, opline->result.var);
}
if (JIT_G(trigger) == ZEND_JIT_ON_HOT_TRACE && (val_info & MAY_BE_UNDEF)) {
int32_t exit_point = zend_jit_trace_get_exit_point(opline, ZEND_JIT_EXIT_TO_VM);
const void *exit_addr = zend_jit_trace_get_exit_addr(exit_point);
if (!exit_addr) {
return 0;
}
| IF_ZVAL_TYPE op3_addr, IS_UNDEF, &exit_addr
val_info &= ~MAY_BE_UNDEF;
}
if (op1_info & MAY_BE_REF) {
| LOAD_ZVAL_ADDR FCARG1a, op1_addr
| IF_NOT_Z_TYPE FCARG1a, IS_REFERENCE, >1
| GET_Z_PTR FCARG2a, FCARG1a
| IF_NOT_TYPE byte [FCARG2a + offsetof(zend_reference, val) + offsetof(zval, u1.v.type)], IS_ARRAY, >2
| lea FCARG1a, [FCARG2a + offsetof(zend_reference, val)]
| jmp >3
|.cold_code
|2:
| SET_EX_OPLINE opline, r0
| EXT_CALL zend_jit_prepare_assign_dim_ref, r0
| test r0, r0
| mov FCARG1a, r0
| jne >1
| jmp ->exception_handler_undef
|.code
|1:
op1_addr = ZEND_ADDR_MEM_ZVAL(ZREG_FCARG1a, 0);
}
if (op1_info & MAY_BE_ARRAY) {
if (op1_info & ((MAY_BE_ANY|MAY_BE_UNDEF) - MAY_BE_ARRAY)) {
| IF_NOT_ZVAL_TYPE op1_addr, IS_ARRAY, >7
}
|3:
| SEPARATE_ARRAY op1_addr, op1_info, 1
} else if (op1_info & (MAY_BE_UNDEF|MAY_BE_NULL|MAY_BE_FALSE)) {
if (op1_info & (MAY_BE_ANY-(MAY_BE_NULL|MAY_BE_FALSE|MAY_BE_ARRAY))) {
| CMP_ZVAL_TYPE op1_addr, IS_FALSE
| jg >7
}
| // ZVAL_ARR(container, zend_new_array(8));
if (Z_REG(op1_addr) != ZREG_FP) {
| mov T1, Ra(Z_REG(op1_addr)) // save
}
| EXT_CALL _zend_new_array_0, r0
if (Z_REG(op1_addr) != ZREG_FP) {
| mov Ra(Z_REG(op1_addr)), T1 // restore
}
| SET_ZVAL_LVAL op1_addr, r0
| SET_ZVAL_TYPE_INFO op1_addr, IS_ARRAY_EX
| mov FCARG1a, r0
}
if (op1_info & (MAY_BE_UNDEF|MAY_BE_NULL|MAY_BE_FALSE|MAY_BE_ARRAY)) {
|6:
if (opline->op2_type == IS_UNUSED) {
uint32_t var_info = MAY_BE_NULL;
zend_jit_addr var_addr = ZEND_ADDR_MEM_ZVAL(ZREG_R0, 0);
| // var_ptr = zend_hash_next_index_insert(Z_ARRVAL_P(container), &EG(uninitialized_zval));
| LOAD_ADDR_ZTS FCARG2a, executor_globals, uninitialized_zval
| EXT_CALL zend_hash_next_index_insert, r0
| // if (UNEXPECTED(!var_ptr)) {
| test r0, r0
| jz >1
|.cold_code
|1:
| // zend_throw_error(NULL, "Cannot add element to the array as the next element is already occupied");
| CANNOT_ADD_ELEMENT opline
| //ZEND_VM_C_GOTO(assign_dim_op_ret_null);
| jmp >9
|.code
if (!zend_jit_simple_assign(Dst, opline, var_addr, var_info, -1, (opline+1)->op1_type, op3_addr, val_info, res_addr, 0, 0)) {
return 0;
}
} else {
uint32_t var_info = zend_array_element_type(op1_info, opline->op1_type, 0, 0);
zend_jit_addr var_addr = ZEND_ADDR_MEM_ZVAL(ZREG_R0, 0);
if (!zend_jit_fetch_dimension_address_inner(Dst, opline, BP_VAR_W, op1_info, op2_info, NULL, NULL, NULL)) {
return 0;
}
if (op1_info & (MAY_BE_ARRAY_OF_REF|MAY_BE_OBJECT)) {
var_info |= MAY_BE_REF;
}
if (var_info & (MAY_BE_STRING|MAY_BE_ARRAY|MAY_BE_OBJECT|MAY_BE_RESOURCE)) {
var_info |= MAY_BE_RC1;
}
|8:
| // value = zend_assign_to_variable(variable_ptr, value, OP_DATA_TYPE);
if (opline->op1_type == IS_VAR) {
ZEND_ASSERT(opline->result_type == IS_UNUSED);
if (!zend_jit_assign_to_variable_call(Dst, opline, var_addr, var_addr, var_info, -1, (opline+1)->op1_type, op3_addr, val_info, res_addr, 0)) {
return 0;
}
} else {
if (!zend_jit_assign_to_variable(Dst, opline, var_addr, var_addr, var_info, -1, (opline+1)->op1_type, op3_addr, val_info, res_addr, 0)) {
return 0;
}
}
}
}
if (((op1_info & MAY_BE_ARRAY) &&
(op1_info & (MAY_BE_UNDEF|MAY_BE_NULL|MAY_BE_FALSE))) ||
(op1_info & (MAY_BE_ANY-(MAY_BE_NULL|MAY_BE_FALSE|MAY_BE_ARRAY)))) {
if (op1_info & (MAY_BE_UNDEF|MAY_BE_NULL|MAY_BE_FALSE|MAY_BE_ARRAY)) {
|.cold_code
|7:
}
if ((op1_info & (MAY_BE_UNDEF|MAY_BE_NULL|MAY_BE_FALSE)) &&
(op1_info & MAY_BE_ARRAY)) {
if (op1_info & (MAY_BE_ANY-(MAY_BE_NULL|MAY_BE_FALSE|MAY_BE_ARRAY))) {
| CMP_ZVAL_TYPE op1_addr, IS_FALSE
| jg >2
}
| // ZVAL_ARR(container, zend_new_array(8));
if (Z_REG(op1_addr) != ZREG_FP) {
| mov T1, Ra(Z_REG(op1_addr)) // save
}
| EXT_CALL _zend_new_array_0, r0
if (Z_REG(op1_addr) != ZREG_FP) {
| mov Ra(Z_REG(op1_addr)), T1 // restore
}
| SET_ZVAL_LVAL op1_addr, r0
| SET_ZVAL_TYPE_INFO op1_addr, IS_ARRAY_EX
| mov FCARG1a, r0
| // ZEND_VM_C_GOTO(assign_dim_op_new_array);
| jmp <6
|2:
}
if (op1_info & (MAY_BE_ANY-(MAY_BE_NULL|MAY_BE_FALSE|MAY_BE_ARRAY))) {
| SET_EX_OPLINE opline, r0
if (Z_REG(op1_addr) != ZREG_FCARG1a || Z_OFFSET(op1_addr) != 0) {
| LOAD_ZVAL_ADDR FCARG1a, op1_addr
}
if (opline->op2_type == IS_UNUSED) {
| xor FCARG2a, FCARG2a
} else if (opline->op2_type == IS_CONST && Z_EXTRA_P(RT_CONSTANT(opline, opline->op2)) == ZEND_EXTRA_VALUE) {
ZEND_ASSERT(Z_MODE(op2_addr) == IS_CONST_ZVAL);
| LOAD_ADDR FCARG2a, (Z_ZV(op2_addr) + 1)
} else {
| LOAD_ZVAL_ADDR FCARG2a, op2_addr
}
|.if not(X64)
| sub r4, 8
|.endif
if (opline->result_type == IS_UNUSED) {
|.if X64
| xor CARG4, CARG4
|.else
| push 0
|.endif
} else {
|.if X64
| LOAD_ZVAL_ADDR CARG4, res_addr
|.else
| PUSH_ZVAL_ADDR res_addr, r0
|.endif
}
|.if X64
| LOAD_ZVAL_ADDR CARG3, op3_addr
|.else
| PUSH_ZVAL_ADDR op3_addr, r0
|.endif
| EXT_CALL zend_jit_assign_dim_helper, r0
|.if not(X64)
| add r4, 8
|.endif
#ifdef ZEND_JIT_USE_RC_INFERENCE
if (((opline+1)->op1_type & (IS_TMP_VAR|IS_VAR)) && (val_info & MAY_BE_RC1)) {
/* ASSIGN_DIM may increase refcount of the value */
val_info |= MAY_BE_RCN;
}
#endif
| FREE_OP (opline+1)->op1_type, (opline+1)->op1, val_info, 0, opline
}
if (op1_info & (MAY_BE_UNDEF|MAY_BE_NULL|MAY_BE_FALSE|MAY_BE_ARRAY)) {
if (op1_info & (MAY_BE_ANY-(MAY_BE_NULL|MAY_BE_FALSE|MAY_BE_ARRAY))) {
| jmp >9 // END
}
|.code
}
}
#ifdef ZEND_JIT_USE_RC_INFERENCE
if ((opline->op2_type & (IS_TMP_VAR|IS_VAR)) && (op1_info & (MAY_BE_UNDEF|MAY_BE_NULL|MAY_BE_FALSE|MAY_BE_ARRAY|MAY_BE_OBJECT))) {
/* ASSIGN_DIM may increase refcount of the key */
op2_info |= MAY_BE_RCN;
}
#endif
|9:
| FREE_OP opline->op2_type, opline->op2, op2_info, 0, opline
if (may_throw) {
zend_jit_check_exception(Dst);
}
return 1;
}
static int zend_jit_assign_dim_op(dasm_State **Dst, const zend_op *opline, uint32_t op1_info, uint32_t op1_def_info, zend_jit_addr op1_addr, uint32_t op2_info, uint32_t op1_data_info, zend_ssa_range *op1_data_range, int may_throw)
{
zend_jit_addr op2_addr, op3_addr, var_addr;
ZEND_ASSERT(opline->result_type == IS_UNUSED);
op2_addr = (opline->op2_type != IS_UNUSED) ? OP2_ADDR() : 0;
op3_addr = OP1_DATA_ADDR();
if (op1_info & MAY_BE_REF) {
| LOAD_ZVAL_ADDR FCARG1a, op1_addr
| IF_NOT_Z_TYPE FCARG1a, IS_REFERENCE, >1
| GET_Z_PTR FCARG2a, FCARG1a
| IF_NOT_TYPE byte [FCARG2a + offsetof(zend_reference, val) + offsetof(zval, u1.v.type)], IS_ARRAY, >2
| lea FCARG1a, [FCARG2a + offsetof(zend_reference, val)]
| jmp >3
|.cold_code
|2:
| SET_EX_OPLINE opline, r0
| EXT_CALL zend_jit_prepare_assign_dim_ref, r0
| test r0, r0
| mov FCARG1a, r0
| jne >1
| jmp ->exception_handler_undef
|.code
|1:
op1_addr = ZEND_ADDR_MEM_ZVAL(ZREG_FCARG1a, 0);
}
if (op1_info & MAY_BE_ARRAY) {
if (op1_info & ((MAY_BE_ANY|MAY_BE_UNDEF) - MAY_BE_ARRAY)) {
| IF_NOT_ZVAL_TYPE op1_addr, IS_ARRAY, >7
}
|3:
| SEPARATE_ARRAY op1_addr, op1_info, 1
}
if (op1_info & (MAY_BE_UNDEF|MAY_BE_NULL|MAY_BE_FALSE)) {
if (op1_info & MAY_BE_ARRAY) {
|.cold_code
|7:
}
if (op1_info & (MAY_BE_ANY-(MAY_BE_NULL|MAY_BE_FALSE|MAY_BE_ARRAY))) {
| CMP_ZVAL_TYPE op1_addr, IS_FALSE
| jg >7
}
if (Z_REG(op1_addr) != ZREG_FP) {
| mov T1, Ra(Z_REG(op1_addr)) // save
}
if (op1_info & MAY_BE_UNDEF) {
if (op1_info & (MAY_BE_NULL|MAY_BE_FALSE)) {
| IF_NOT_ZVAL_TYPE op1_addr, IS_UNDEF, >1
}
| SET_EX_OPLINE opline, r0
| mov FCARG1a, opline->op1.var
| EXT_CALL zend_jit_undefined_op_helper, r0
|1:
}
| // ZVAL_ARR(container, zend_new_array(8));
| EXT_CALL _zend_new_array_0, r0
if (Z_REG(op1_addr) != ZREG_FP) {
| mov Ra(Z_REG(op1_addr)), T1 // restore
}
| SET_ZVAL_LVAL op1_addr, r0
| SET_ZVAL_TYPE_INFO op1_addr, IS_ARRAY_EX
| mov FCARG1a, r0
if (op1_info & MAY_BE_ARRAY) {
| jmp >1
|.code
|1:
}
}
if (op1_info & (MAY_BE_UNDEF|MAY_BE_NULL|MAY_BE_FALSE|MAY_BE_ARRAY)) {
uint32_t var_info;
uint32_t var_def_info = zend_array_element_type(op1_def_info, opline->op1_type, 1, 0);
|6:
if (opline->op2_type == IS_UNUSED) {
var_info = MAY_BE_NULL;
| // var_ptr = zend_hash_next_index_insert(Z_ARRVAL_P(container), &EG(uninitialized_zval));
| LOAD_ADDR_ZTS FCARG2a, executor_globals, uninitialized_zval
| EXT_CALL zend_hash_next_index_insert, r0
| // if (UNEXPECTED(!var_ptr)) {
| test r0, r0
| jz >1
|.cold_code
|1:
| // zend_throw_error(NULL, "Cannot add element to the array as the next element is already occupied");
| CANNOT_ADD_ELEMENT opline
| //ZEND_VM_C_GOTO(assign_dim_op_ret_null);
| jmp >9
|.code
} else {
var_info = zend_array_element_type(op1_info, opline->op1_type, 0, 0);
if (op1_info & (MAY_BE_ARRAY_OF_REF|MAY_BE_OBJECT)) {
var_info |= MAY_BE_REF;
}
if (var_info & (MAY_BE_STRING|MAY_BE_ARRAY|MAY_BE_OBJECT|MAY_BE_RESOURCE)) {
var_info |= MAY_BE_RC1;
}
if (!zend_jit_fetch_dimension_address_inner(Dst, opline, BP_VAR_RW, op1_info, op2_info, NULL, NULL, NULL)) {
return 0;
}
|8:
if (op1_info & (MAY_BE_ARRAY_OF_REF)) {
binary_op_type binary_op = get_binary_op(opline->extended_value);
| IF_NOT_Z_TYPE, r0, IS_REFERENCE, >1
| GET_Z_PTR FCARG1a, r0
| cmp aword [FCARG1a + offsetof(zend_reference, sources.ptr)], 0
| jnz >2
| lea r0, aword [FCARG1a + offsetof(zend_reference, val)]
|.cold_code
|2:
| LOAD_ZVAL_ADDR FCARG2a, op3_addr
|.if X64
| LOAD_ADDR CARG3, binary_op
|.else
| sub r4, 12
| PUSH_ADDR binary_op, r0
|.endif
| SET_EX_OPLINE opline, r0
if (((opline+1)->op1_type & (IS_TMP_VAR|IS_VAR))
&& (op1_data_info & (MAY_BE_STRING|MAY_BE_ARRAY|MAY_BE_OBJECT|MAY_BE_RESOURCE))) {
| EXT_CALL zend_jit_assign_op_to_typed_ref_tmp, r0
} else {
| EXT_CALL zend_jit_assign_op_to_typed_ref, r0
}
|.if not(X64)
| add r4, 12
|.endif
zend_jit_check_exception(Dst);
| jmp >9
|.code
|1:
}
}
var_addr = ZEND_ADDR_MEM_ZVAL(ZREG_R0, 0);
switch (opline->extended_value) {
case ZEND_ADD:
case ZEND_SUB:
case ZEND_MUL:
case ZEND_DIV:
if (!zend_jit_math_helper(Dst, opline, opline->extended_value, IS_CV, opline->op1, var_addr, var_info, (opline+1)->op1_type, (opline+1)->op1, op3_addr, op1_data_info, 0, var_addr, var_def_info, var_info,
1 /* may overflow */, may_throw)) {
return 0;
}
break;
case ZEND_BW_OR:
case ZEND_BW_AND:
case ZEND_BW_XOR:
case ZEND_SL:
case ZEND_SR:
case ZEND_MOD:
if (!zend_jit_long_math_helper(Dst, opline, opline->extended_value,
IS_CV, opline->op1, var_addr, var_info, NULL,
(opline+1)->op1_type, (opline+1)->op1, op3_addr, op1_data_info,
op1_data_range,
0, var_addr, var_def_info, var_info, may_throw)) {
return 0;
}
break;
case ZEND_CONCAT:
if (!zend_jit_concat_helper(Dst, opline, IS_CV, opline->op1, var_addr, var_info, (opline+1)->op1_type, (opline+1)->op1, op3_addr, op1_data_info, var_addr,
may_throw)) {
return 0;
}
break;
default:
ZEND_UNREACHABLE();
}
}
if (op1_info & (MAY_BE_ANY-(MAY_BE_NULL|MAY_BE_FALSE|MAY_BE_ARRAY))) {
binary_op_type binary_op;
if (op1_info & (MAY_BE_UNDEF|MAY_BE_NULL|MAY_BE_FALSE|MAY_BE_ARRAY)) {
|.cold_code
|7:
}
| SET_EX_OPLINE opline, r0
if (Z_REG(op1_addr) != ZREG_FCARG1a || Z_OFFSET(op1_addr) != 0) {
| LOAD_ZVAL_ADDR FCARG1a, op1_addr
}
if (opline->op2_type == IS_UNUSED) {
| xor FCARG2a, FCARG2a
} else if (opline->op2_type == IS_CONST && Z_EXTRA_P(RT_CONSTANT(opline, opline->op2)) == ZEND_EXTRA_VALUE) {
ZEND_ASSERT(Z_MODE(op2_addr) == IS_CONST_ZVAL);
| LOAD_ADDR FCARG2a, (Z_ZV(op2_addr) + 1)
} else {
| LOAD_ZVAL_ADDR FCARG2a, op2_addr
}
binary_op = get_binary_op(opline->extended_value);
|.if X64
| LOAD_ZVAL_ADDR CARG3, op3_addr
| LOAD_ADDR CARG4, binary_op
|.else
| sub r4, 8
| PUSH_ADDR binary_op, r0
| PUSH_ZVAL_ADDR op3_addr, r0
|.endif
| EXT_CALL zend_jit_assign_dim_op_helper, r0
|.if not(X64)
| add r4, 8
|.endif
if (!zend_jit_check_exception(Dst)) {
return 0;
}
if (op1_info & (MAY_BE_UNDEF|MAY_BE_NULL|MAY_BE_FALSE|MAY_BE_ARRAY)) {
| jmp >9 // END
|.code
}
}
|9:
| FREE_OP opline->op2_type, opline->op2, op2_info, 0, opline
return 1;
}
static int zend_jit_assign_op(dasm_State **Dst, const zend_op *opline, uint32_t op1_info, uint32_t op1_def_info, zend_ssa_range *op1_range, uint32_t op2_info, zend_ssa_range *op2_range, int may_overflow, int may_throw)
{
zend_jit_addr op1_addr, op2_addr;
ZEND_ASSERT(opline->op1_type == IS_CV && opline->result_type == IS_UNUSED);
ZEND_ASSERT(!(op1_info & MAY_BE_UNDEF) && !(op2_info & MAY_BE_UNDEF));
op1_addr = OP1_ADDR();
op2_addr = OP2_ADDR();
if (op1_info & MAY_BE_REF) {
binary_op_type binary_op = get_binary_op(opline->extended_value);
| LOAD_ZVAL_ADDR FCARG1a, op1_addr
| IF_NOT_Z_TYPE, FCARG1a, IS_REFERENCE, >1
| GET_Z_PTR FCARG1a, FCARG1a
| cmp aword [FCARG1a + offsetof(zend_reference, sources.ptr)], 0
| jnz >2
| add FCARG1a, offsetof(zend_reference, val)
|.cold_code
|2:
| LOAD_ZVAL_ADDR FCARG2a, op2_addr
|.if X64
| LOAD_ADDR CARG3, binary_op
|.else
| sub r4, 12
| PUSH_ADDR binary_op, r0
|.endif
| SET_EX_OPLINE opline, r0
if ((opline->op2_type & (IS_TMP_VAR|IS_VAR))
&& (op2_info & (MAY_BE_STRING|MAY_BE_ARRAY|MAY_BE_OBJECT|MAY_BE_RESOURCE))) {
| EXT_CALL zend_jit_assign_op_to_typed_ref_tmp, r0
} else {
| EXT_CALL zend_jit_assign_op_to_typed_ref, r0
}
|.if not(X64)
| add r4, 12
|.endif
zend_jit_check_exception(Dst);
| jmp >9
|.code
|1:
op1_addr = ZEND_ADDR_MEM_ZVAL(ZREG_FCARG1a, 0);
}
int result;
switch (opline->extended_value) {
case ZEND_ADD:
case ZEND_SUB:
case ZEND_MUL:
case ZEND_DIV:
result = zend_jit_math_helper(Dst, opline, opline->extended_value, opline->op1_type, opline->op1, op1_addr, op1_info, opline->op2_type, opline->op2, op2_addr, op2_info, opline->op1.var, op1_addr, op1_def_info, op1_info, may_overflow, may_throw);
break;
case ZEND_BW_OR:
case ZEND_BW_AND:
case ZEND_BW_XOR:
case ZEND_SL:
case ZEND_SR:
case ZEND_MOD:
result = zend_jit_long_math_helper(Dst, opline, opline->extended_value,
opline->op1_type, opline->op1, op1_addr, op1_info, op1_range,
opline->op2_type, opline->op2, op2_addr, op2_info, op2_range,
opline->op1.var, op1_addr, op1_def_info, op1_info, may_throw);
break;
case ZEND_CONCAT:
result = zend_jit_concat_helper(Dst, opline, opline->op1_type, opline->op1, op1_addr, op1_info, opline->op2_type, opline->op2, op2_addr, op2_info, op1_addr, may_throw);
break;
default:
ZEND_UNREACHABLE();
}
|9:
return result;
}
static int zend_jit_is_constant_cmp_long_long(const zend_op *opline,
zend_ssa_range *op1_range,
zend_jit_addr op1_addr,
zend_ssa_range *op2_range,
zend_jit_addr op2_addr,
zend_bool *result)
{
zend_long op1_min;
zend_long op1_max;
zend_long op2_min;
zend_long op2_max;
if (op1_range) {
op1_min = op1_range->min;
op1_max = op1_range->max;
} else if (Z_MODE(op1_addr) == IS_CONST_ZVAL) {
ZEND_ASSERT(Z_TYPE_P(Z_ZV(op1_addr)) == IS_LONG);
op1_min = op1_max = Z_LVAL_P(Z_ZV(op1_addr));
} else {
return 0;
}
if (op2_range) {
op2_min = op2_range->min;
op2_max = op2_range->max;
} else if (Z_MODE(op2_addr) == IS_CONST_ZVAL) {
ZEND_ASSERT(Z_TYPE_P(Z_ZV(op2_addr)) == IS_LONG);
op2_min = op2_max = Z_LVAL_P(Z_ZV(op2_addr));
} else {
return 0;
}
switch (opline->opcode) {
case ZEND_IS_EQUAL:
case ZEND_IS_IDENTICAL:
case ZEND_CASE:
case ZEND_CASE_STRICT:
if (op1_min == op1_max && op2_min == op2_max && op1_min == op2_min) {
*result = 1;
return 1;
} else if (op1_max < op2_min || op1_min > op2_max) {
*result = 0;
return 1;
}
return 0;
case ZEND_IS_NOT_EQUAL:
case ZEND_IS_NOT_IDENTICAL:
if (op1_min == op1_max && op2_min == op2_max && op1_min == op2_min) {
*result = 0;
return 1;
} else if (op1_max < op2_min || op1_min > op2_max) {
*result = 1;
return 1;
}
return 0;
case ZEND_IS_SMALLER:
if (op1_max < op2_min) {
*result = 1;
return 1;
} else if (op1_min >= op2_max) {
*result = 0;
return 1;
}
return 0;
case ZEND_IS_SMALLER_OR_EQUAL:
if (op1_max <= op2_min) {
*result = 1;
return 1;
} else if (op1_min > op2_max) {
*result = 0;
return 1;
}
return 0;
default:
ZEND_UNREACHABLE();
}
return 0;
}
static int zend_jit_cmp_long_long(dasm_State **Dst,
const zend_op *opline,
zend_ssa_range *op1_range,
zend_jit_addr op1_addr,
zend_ssa_range *op2_range,
zend_jit_addr op2_addr,
zend_jit_addr res_addr,
zend_uchar smart_branch_opcode,
uint32_t target_label,
uint32_t target_label2,
const void *exit_addr,
zend_bool skip_comparison)
{
zend_bool swap = 0;
zend_bool result;
if (zend_jit_is_constant_cmp_long_long(opline, op1_range, op1_addr, op2_range, op2_addr, &result)) {
if (!smart_branch_opcode ||
smart_branch_opcode == ZEND_JMPZ_EX ||
smart_branch_opcode == ZEND_JMPNZ_EX) {
| SET_ZVAL_TYPE_INFO res_addr, (result ? IS_TRUE : IS_FALSE)
}
if (smart_branch_opcode && !exit_addr) {
if (smart_branch_opcode == ZEND_JMPZ ||
smart_branch_opcode == ZEND_JMPZ_EX) {
if (!result) {
| jmp => target_label
}
} else if (smart_branch_opcode == ZEND_JMPNZ ||
smart_branch_opcode == ZEND_JMPNZ_EX) {
if (result) {
| jmp => target_label
}
} else if (smart_branch_opcode == ZEND_JMPZNZ) {
if (!result) {
| jmp => target_label
} else {
| jmp => target_label2
}
} else {
ZEND_UNREACHABLE();
}
}
return 1;
}
if (skip_comparison) {
if (Z_MODE(op1_addr) != IS_REG &&
(Z_MODE(op2_addr) == IS_REG ||
(Z_MODE(op1_addr) == IS_CONST_ZVAL && Z_MODE(op2_addr) != IS_CONST_ZVAL))) {
swap = 1;
}
} else if (Z_MODE(op1_addr) == IS_REG) {
if (Z_MODE(op2_addr) == IS_CONST_ZVAL && Z_LVAL_P(Z_ZV(op2_addr)) == 0) {
| test Ra(Z_REG(op1_addr)), Ra(Z_REG(op1_addr))
} else {
| LONG_OP cmp, Z_REG(op1_addr), op2_addr, r0
}
} else if (Z_MODE(op2_addr) == IS_REG) {
if (Z_MODE(op1_addr) == IS_CONST_ZVAL && Z_LVAL_P(Z_ZV(op1_addr)) == 0) {
| test Ra(Z_REG(op2_addr)), Ra(Z_REG(op2_addr))
} else {
| LONG_OP cmp, Z_REG(op2_addr), op1_addr, r0
}
swap = 1;
} else if (Z_MODE(op1_addr) == IS_CONST_ZVAL && Z_MODE(op2_addr) != IS_CONST_ZVAL) {
| LONG_OP_WITH_CONST cmp, op2_addr, Z_LVAL_P(Z_ZV(op1_addr))
swap = 1;
} else if (Z_MODE(op2_addr) == IS_CONST_ZVAL && Z_MODE(op1_addr) != IS_CONST_ZVAL) {
| LONG_OP_WITH_CONST cmp, op1_addr, Z_LVAL_P(Z_ZV(op2_addr))
} else {
| GET_ZVAL_LVAL ZREG_R0, op1_addr
if (Z_MODE(op2_addr) == IS_CONST_ZVAL && Z_LVAL_P(Z_ZV(op2_addr)) == 0) {
| test r0, r0
} else {
| LONG_OP cmp, ZREG_R0, op2_addr, r0
}
}
if (smart_branch_opcode) {
if (smart_branch_opcode == ZEND_JMPZ_EX ||
smart_branch_opcode == ZEND_JMPNZ_EX) {
switch (opline->opcode) {
case ZEND_IS_EQUAL:
case ZEND_IS_IDENTICAL:
case ZEND_CASE:
case ZEND_CASE_STRICT:
| sete al
break;
case ZEND_IS_NOT_EQUAL:
case ZEND_IS_NOT_IDENTICAL:
| setne al
break;
case ZEND_IS_SMALLER:
if (swap) {
| setg al
} else {
| setl al
}
break;
case ZEND_IS_SMALLER_OR_EQUAL:
if (swap) {
| setge al
} else {
| setle al
}
break;
default:
ZEND_UNREACHABLE();
}
| movzx eax, al
| lea eax, [eax + 2]
| SET_ZVAL_TYPE_INFO res_addr, eax
}
if (smart_branch_opcode == ZEND_JMPZ ||
smart_branch_opcode == ZEND_JMPZ_EX) {
switch (opline->opcode) {
case ZEND_IS_EQUAL:
case ZEND_IS_IDENTICAL:
case ZEND_CASE:
case ZEND_CASE_STRICT:
if (exit_addr) {
| jne &exit_addr
} else {
| jne => target_label
}
break;
case ZEND_IS_NOT_EQUAL:
if (exit_addr) {
| je &exit_addr
} else {
| je => target_label
}
break;
case ZEND_IS_NOT_IDENTICAL:
if (exit_addr) {
| jne &exit_addr
} else {
| je => target_label
}
break;
case ZEND_IS_SMALLER:
if (swap) {
if (exit_addr) {
| jle &exit_addr
} else {
| jle => target_label
}
} else {
if (exit_addr) {
| jge &exit_addr
} else {
| jge => target_label
}
}
break;
case ZEND_IS_SMALLER_OR_EQUAL:
if (swap) {
if (exit_addr) {
| jl &exit_addr
} else {
| jl => target_label
}
} else {
if (exit_addr) {
| jg &exit_addr
} else {
| jg => target_label
}
}
break;
default:
ZEND_UNREACHABLE();
}
} else if (smart_branch_opcode == ZEND_JMPNZ ||
smart_branch_opcode == ZEND_JMPNZ_EX) {
switch (opline->opcode) {
case ZEND_IS_EQUAL:
case ZEND_IS_IDENTICAL:
case ZEND_CASE:
case ZEND_CASE_STRICT:
if (exit_addr) {
| je &exit_addr
} else {
| je => target_label
}
break;
case ZEND_IS_NOT_EQUAL:
if (exit_addr) {
| jne &exit_addr
} else {
| jne => target_label
}
break;
case ZEND_IS_NOT_IDENTICAL:
if (exit_addr) {
| je &exit_addr
} else {
| jne => target_label
}
break;
case ZEND_IS_SMALLER:
if (swap) {
if (exit_addr) {
| jg &exit_addr
} else {
| jg => target_label
}
} else {
if (exit_addr) {
| jl &exit_addr
} else {
| jl => target_label
}
}
break;
case ZEND_IS_SMALLER_OR_EQUAL:
if (swap) {
if (exit_addr) {
| jge &exit_addr
} else {
| jge => target_label
}
} else {
if (exit_addr) {
| jle &exit_addr
} else {
| jle => target_label
}
}
break;
default:
ZEND_UNREACHABLE();
}
} else if (smart_branch_opcode == ZEND_JMPZNZ) {
switch (opline->opcode) {
case ZEND_IS_EQUAL:
case ZEND_IS_IDENTICAL:
case ZEND_CASE:
case ZEND_CASE_STRICT:
| jne => target_label
break;
case ZEND_IS_NOT_EQUAL:
case ZEND_IS_NOT_IDENTICAL:
| je => target_label
break;
case ZEND_IS_SMALLER:
if (swap) {
| jle => target_label
} else {
| jge => target_label
}
break;
case ZEND_IS_SMALLER_OR_EQUAL:
if (swap) {
| jl => target_label
} else {
| jg => target_label
}
break;
default:
ZEND_UNREACHABLE();
}
| jmp => target_label2
} else {
ZEND_UNREACHABLE();
}
} else {
switch (opline->opcode) {
case ZEND_IS_EQUAL:
case ZEND_IS_IDENTICAL:
case ZEND_CASE:
case ZEND_CASE_STRICT:
| sete al
break;
case ZEND_IS_NOT_EQUAL:
case ZEND_IS_NOT_IDENTICAL:
| setne al
break;
case ZEND_IS_SMALLER:
if (swap) {
| setg al
} else {
| setl al
}
break;
case ZEND_IS_SMALLER_OR_EQUAL:
if (swap) {
| setge al
} else {
| setle al
}
break;
default:
ZEND_UNREACHABLE();
}
| movzx eax, al
| add eax, 2
| SET_ZVAL_TYPE_INFO res_addr, eax
}
return 1;
}
static int zend_jit_cmp_double_common(dasm_State **Dst, const zend_op *opline, zend_jit_addr res_addr, zend_bool swap, zend_uchar smart_branch_opcode, uint32_t target_label, uint32_t target_label2, const void *exit_addr)
{
if (smart_branch_opcode) {
if (smart_branch_opcode == ZEND_JMPZ) {
switch (opline->opcode) {
case ZEND_IS_EQUAL:
case ZEND_IS_IDENTICAL:
case ZEND_CASE:
case ZEND_CASE_STRICT:
if (exit_addr) {
| jne &exit_addr
| jp &exit_addr
} else {
| jne => target_label
| jp => target_label
}
break;
case ZEND_IS_NOT_EQUAL:
| jp >1
if (exit_addr) {
| je &exit_addr
} else {
| je => target_label
}
|1:
break;
case ZEND_IS_NOT_IDENTICAL:
if (exit_addr) {
| jne &exit_addr
| jp &exit_addr
} else {
| jp >1
| je => target_label
|1:
}
break;
case ZEND_IS_SMALLER:
if (swap) {
if (exit_addr) {
| jbe &exit_addr
} else {
| jbe => target_label
}
} else {
if (exit_addr) {
| jae &exit_addr
| jp &exit_addr
} else {
| jae => target_label
| jp => target_label
}
}
break;
case ZEND_IS_SMALLER_OR_EQUAL:
if (swap) {
if (exit_addr) {
| jb &exit_addr
} else {
| jb => target_label
}
} else {
if (exit_addr) {
| ja &exit_addr
| jp &exit_addr
} else {
| ja => target_label
| jp => target_label
}
}
break;
default:
ZEND_UNREACHABLE();
}
} else if (smart_branch_opcode == ZEND_JMPNZ) {
switch (opline->opcode) {
case ZEND_IS_EQUAL:
case ZEND_IS_IDENTICAL:
case ZEND_CASE:
case ZEND_CASE_STRICT:
| jp >1
if (exit_addr) {
| je &exit_addr
} else {
| je => target_label
}
|1:
break;
case ZEND_IS_NOT_EQUAL:
if (exit_addr) {
| jne &exit_addr
| jp &exit_addr
} else {
| jne => target_label
| jp => target_label
}
break;
case ZEND_IS_NOT_IDENTICAL:
if (exit_addr) {
| jp >1
| je &exit_addr
|1:
} else {
| jne => target_label
| jp => target_label
}
break;
case ZEND_IS_SMALLER:
if (swap) {
if (exit_addr) {
| ja &exit_addr
} else {
| ja => target_label
}
} else {
| jp >1
if (exit_addr) {
| jb &exit_addr
} else {
| jb => target_label
}
|1:
}
break;
case ZEND_IS_SMALLER_OR_EQUAL:
if (swap) {
if (exit_addr) {
| jae &exit_addr
} else {
| jae => target_label
}
} else {
| jp >1
if (exit_addr) {
| jbe &exit_addr
} else {
| jbe => target_label
}
|1:
}
break;
default:
ZEND_UNREACHABLE();
}
} else if (smart_branch_opcode == ZEND_JMPZNZ) {
switch (opline->opcode) {
case ZEND_IS_EQUAL:
case ZEND_IS_IDENTICAL:
case ZEND_CASE:
case ZEND_CASE_STRICT:
| jne => target_label
| jp => target_label
break;
case ZEND_IS_NOT_EQUAL:
case ZEND_IS_NOT_IDENTICAL:
| jp => target_label2
| je => target_label
break;
case ZEND_IS_SMALLER:
if (swap) {
| jbe => target_label
} else {
| jae => target_label
| jp => target_label
}
break;
case ZEND_IS_SMALLER_OR_EQUAL:
if (swap) {
| jb => target_label
} else {
| ja => target_label
| jp => target_label
}
break;
default:
ZEND_UNREACHABLE();
}
| jmp => target_label2
} else if (smart_branch_opcode == ZEND_JMPZ_EX) {
switch (opline->opcode) {
case ZEND_IS_EQUAL:
case ZEND_IS_IDENTICAL:
case ZEND_CASE:
case ZEND_CASE_STRICT:
| SET_ZVAL_TYPE_INFO res_addr, IS_FALSE
| jne => target_label
| jp => target_label
| SET_ZVAL_TYPE_INFO res_addr, IS_TRUE
break;
case ZEND_IS_NOT_EQUAL:
case ZEND_IS_NOT_IDENTICAL:
| jp >1
| SET_ZVAL_TYPE_INFO res_addr, IS_FALSE
| je => target_label
|1:
| SET_ZVAL_TYPE_INFO res_addr, IS_TRUE
break;
case ZEND_IS_SMALLER:
if (swap) {
| SET_ZVAL_TYPE_INFO res_addr, IS_FALSE
| jbe => target_label
| SET_ZVAL_TYPE_INFO res_addr, IS_TRUE
} else {
| SET_ZVAL_TYPE_INFO res_addr, IS_FALSE
| jae => target_label
| jp => target_label
| SET_ZVAL_TYPE_INFO res_addr, IS_TRUE
}
break;
case ZEND_IS_SMALLER_OR_EQUAL:
if (swap) {
| SET_ZVAL_TYPE_INFO res_addr, IS_FALSE
| jb => target_label
| SET_ZVAL_TYPE_INFO res_addr, IS_TRUE
} else {
| SET_ZVAL_TYPE_INFO res_addr, IS_FALSE
| ja => target_label
| jp => target_label
| SET_ZVAL_TYPE_INFO res_addr, IS_TRUE
}
break;
default:
ZEND_UNREACHABLE();
}
} else if (smart_branch_opcode == ZEND_JMPNZ_EX) {
switch (opline->opcode) {
case ZEND_IS_EQUAL:
case ZEND_IS_IDENTICAL:
case ZEND_CASE:
case ZEND_CASE_STRICT:
| jp >1
| SET_ZVAL_TYPE_INFO res_addr, IS_TRUE
| je => target_label
|1:
| SET_ZVAL_TYPE_INFO res_addr, IS_FALSE
break;
case ZEND_IS_NOT_EQUAL:
case ZEND_IS_NOT_IDENTICAL:
| SET_ZVAL_TYPE_INFO res_addr, IS_TRUE
| jne => target_label
| jp => target_label
| SET_ZVAL_TYPE_INFO res_addr, IS_FALSE
break;
case ZEND_IS_SMALLER:
if (swap) {
| seta al
| movzx eax, al
| lea eax, [eax + 2]
| SET_ZVAL_TYPE_INFO res_addr, eax
| ja => target_label
} else {
| jp >1
| SET_ZVAL_TYPE_INFO res_addr, IS_TRUE
| jb => target_label
|1:
| SET_ZVAL_TYPE_INFO res_addr, IS_FALSE
}
break;
case ZEND_IS_SMALLER_OR_EQUAL:
if (swap) {
| setae al
| movzx eax, al
| lea eax, [eax + 2]
| SET_ZVAL_TYPE_INFO res_addr, eax
| jae => target_label
} else {
| jp >1
| SET_ZVAL_TYPE_INFO res_addr, IS_TRUE
| jbe => target_label
|1:
| SET_ZVAL_TYPE_INFO res_addr, IS_FALSE
}
break;
default:
ZEND_UNREACHABLE();
}
} else {
ZEND_UNREACHABLE();
}
} else {
switch (opline->opcode) {
case ZEND_IS_EQUAL:
case ZEND_IS_IDENTICAL:
case ZEND_CASE:
case ZEND_CASE_STRICT:
| jp >1
| mov eax, IS_TRUE
| je >2
|1:
| mov eax, IS_FALSE
|2:
break;
case ZEND_IS_NOT_EQUAL:
case ZEND_IS_NOT_IDENTICAL:
| jp >1
| mov eax, IS_FALSE
| je >2
|1:
| mov eax, IS_TRUE
|2:
break;
case ZEND_IS_SMALLER:
if (swap) {
| seta al
| movzx eax, al
| add eax, 2
} else {
| jp >1
| mov eax, IS_TRUE
| jb >2
|1:
| mov eax, IS_FALSE
|2:
}
break;
case ZEND_IS_SMALLER_OR_EQUAL:
if (swap) {
| setae al
| movzx eax, al
| add eax, 2
} else {
| jp >1
| mov eax, IS_TRUE
| jbe >2
|1:
| mov eax, IS_FALSE
|2:
}
break;
default:
ZEND_UNREACHABLE();
}
| SET_ZVAL_TYPE_INFO res_addr, eax
}
return 1;
}
static int zend_jit_cmp_long_double(dasm_State **Dst, const zend_op *opline, zend_jit_addr op1_addr, zend_jit_addr op2_addr, zend_jit_addr res_addr, zend_uchar smart_branch_opcode, uint32_t target_label, uint32_t target_label2, const void *exit_addr)
{
zend_reg tmp_reg = ZREG_XMM0;
| SSE_GET_ZVAL_LVAL tmp_reg, op1_addr, ZREG_R0
| SSE_AVX_OP ucomisd, vucomisd, tmp_reg, op2_addr
return zend_jit_cmp_double_common(Dst, opline, res_addr, 0, smart_branch_opcode, target_label, target_label2, exit_addr);
}
static int zend_jit_cmp_double_long(dasm_State **Dst, const zend_op *opline, zend_jit_addr op1_addr, zend_jit_addr op2_addr, zend_jit_addr res_addr, zend_uchar smart_branch_opcode, uint32_t target_label, uint32_t target_label2, const void *exit_addr)
{
zend_reg tmp_reg = ZREG_XMM0;
| SSE_GET_ZVAL_LVAL tmp_reg, op2_addr, ZREG_R0
| SSE_AVX_OP ucomisd, vucomisd, tmp_reg, op1_addr
return zend_jit_cmp_double_common(Dst, opline, res_addr, /* swap */ 1, smart_branch_opcode, target_label, target_label2, exit_addr);
}
static int zend_jit_cmp_double_double(dasm_State **Dst, const zend_op *opline, zend_jit_addr op1_addr, zend_jit_addr op2_addr, zend_jit_addr res_addr, zend_uchar smart_branch_opcode, uint32_t target_label, uint32_t target_label2, const void *exit_addr)
{
zend_bool swap = 0;
if (Z_MODE(op1_addr) == IS_REG) {
| SSE_AVX_OP ucomisd, vucomisd, Z_REG(op1_addr), op2_addr
} else if (Z_MODE(op2_addr) == IS_REG) {
| SSE_AVX_OP ucomisd, vucomisd, Z_REG(op2_addr), op1_addr
swap = 1;
} else {
zend_reg tmp_reg = ZREG_XMM0;
| SSE_GET_ZVAL_DVAL tmp_reg, op1_addr
| SSE_AVX_OP ucomisd, vucomisd, tmp_reg, op2_addr
}
return zend_jit_cmp_double_common(Dst, opline, res_addr, swap, smart_branch_opcode, target_label, target_label2, exit_addr);
}
static int zend_jit_cmp_slow(dasm_State **Dst, const zend_op *opline, zend_jit_addr res_addr, zend_uchar smart_branch_opcode, uint32_t target_label, uint32_t target_label2, const void *exit_addr)
{
| LONG_OP_WITH_CONST cmp, res_addr, Z_L(0)
if (smart_branch_opcode) {
if (smart_branch_opcode == ZEND_JMPZ_EX ||
smart_branch_opcode == ZEND_JMPNZ_EX) {
switch (opline->opcode) {
case ZEND_IS_EQUAL:
case ZEND_CASE:
| sete al
break;
case ZEND_IS_NOT_EQUAL:
| setne al
break;
case ZEND_IS_SMALLER:
| setl al
break;
case ZEND_IS_SMALLER_OR_EQUAL:
| setle al
break;
default:
ZEND_UNREACHABLE();
}
| movzx eax, al
| lea eax, [eax + 2]
| SET_ZVAL_TYPE_INFO res_addr, eax
}
if (smart_branch_opcode == ZEND_JMPZ ||
smart_branch_opcode == ZEND_JMPZ_EX) {
switch (opline->opcode) {
case ZEND_IS_EQUAL:
case ZEND_CASE:
if (exit_addr) {
| jne &exit_addr
} else {
| jne => target_label
}
break;
case ZEND_IS_NOT_EQUAL:
if (exit_addr) {
| je &exit_addr
} else {
| je => target_label
}
break;
case ZEND_IS_SMALLER:
if (exit_addr) {
| jge &exit_addr
} else {
| jge => target_label
}
break;
case ZEND_IS_SMALLER_OR_EQUAL:
if (exit_addr) {
| jg &exit_addr
} else {
| jg => target_label
}
break;
default:
ZEND_UNREACHABLE();
}
} else if (smart_branch_opcode == ZEND_JMPNZ ||
smart_branch_opcode == ZEND_JMPNZ_EX) {
switch (opline->opcode) {
case ZEND_IS_EQUAL:
case ZEND_CASE:
if (exit_addr) {
| je &exit_addr
} else {
| je => target_label
}
break;
case ZEND_IS_NOT_EQUAL:
if (exit_addr) {
| jne &exit_addr
} else {
| jne => target_label
}
break;
case ZEND_IS_SMALLER:
if (exit_addr) {
| jl &exit_addr
} else {
| jl => target_label
}
break;
case ZEND_IS_SMALLER_OR_EQUAL:
if (exit_addr) {
| jle &exit_addr
} else {
| jle => target_label
}
break;
default:
ZEND_UNREACHABLE();
}
} else if (smart_branch_opcode == ZEND_JMPZNZ) {
switch (opline->opcode) {
case ZEND_IS_EQUAL:
case ZEND_CASE:
| jne => target_label
break;
case ZEND_IS_NOT_EQUAL:
| je => target_label
break;
case ZEND_IS_SMALLER:
| jge => target_label
break;
case ZEND_IS_SMALLER_OR_EQUAL:
| jg => target_label
break;
default:
ZEND_UNREACHABLE();
}
| jmp => target_label2
} else {
ZEND_UNREACHABLE();
}
} else {
switch (opline->opcode) {
case ZEND_IS_EQUAL:
case ZEND_CASE:
| sete al
break;
case ZEND_IS_NOT_EQUAL:
| setne al
break;
case ZEND_IS_SMALLER:
| setl al
break;
case ZEND_IS_SMALLER_OR_EQUAL:
| setle al
break;
default:
ZEND_UNREACHABLE();
}
| movzx eax, al
| add eax, 2
| SET_ZVAL_TYPE_INFO res_addr, eax
}
return 1;
}
static int zend_jit_cmp(dasm_State **Dst,
const zend_op *opline,
uint32_t op1_info,
zend_ssa_range *op1_range,
zend_jit_addr op1_addr,
uint32_t op2_info,
zend_ssa_range *op2_range,
zend_jit_addr op2_addr,
zend_jit_addr res_addr,
int may_throw,
zend_uchar smart_branch_opcode,
uint32_t target_label,
uint32_t target_label2,
const void *exit_addr,
zend_bool skip_comparison)
{
zend_bool same_ops = (opline->op1_type == opline->op2_type) && (opline->op1.var == opline->op2.var);
zend_bool has_slow;
has_slow =
(op1_info & (MAY_BE_LONG|MAY_BE_DOUBLE)) &&
(op2_info & (MAY_BE_LONG|MAY_BE_DOUBLE)) &&
((op1_info & ((MAY_BE_ANY|MAY_BE_UNDEF)-(MAY_BE_LONG|MAY_BE_DOUBLE))) ||
(op2_info & ((MAY_BE_ANY|MAY_BE_UNDEF)-(MAY_BE_LONG|MAY_BE_DOUBLE))));
if ((op1_info & MAY_BE_LONG) && (op2_info & MAY_BE_LONG)) {
if (op1_info & ((MAY_BE_ANY|MAY_BE_UNDEF)-MAY_BE_LONG)) {
if (op1_info & MAY_BE_DOUBLE) {
| IF_NOT_ZVAL_TYPE op1_addr, IS_LONG, >4
} else {
| IF_NOT_ZVAL_TYPE op1_addr, IS_LONG, >9
}
}
if (!same_ops && (op2_info & ((MAY_BE_ANY|MAY_BE_UNDEF)-MAY_BE_LONG))) {
if (op2_info & MAY_BE_DOUBLE) {
| IF_NOT_ZVAL_TYPE op2_addr, IS_LONG, >3
|.cold_code
|3:
if (op2_info & ((MAY_BE_ANY|MAY_BE_UNDEF)-(MAY_BE_LONG|MAY_BE_DOUBLE))) {
| IF_NOT_ZVAL_TYPE op2_addr, IS_DOUBLE, >9
}
if (!zend_jit_cmp_long_double(Dst, opline, op1_addr, op2_addr, res_addr, smart_branch_opcode, target_label, target_label2, exit_addr)) {
return 0;
}
| jmp >6
|.code
} else {
| IF_NOT_ZVAL_TYPE op2_addr, IS_LONG, >9
}
}
if (!zend_jit_cmp_long_long(Dst, opline, op1_range, op1_addr, op2_range, op2_addr, res_addr, smart_branch_opcode, target_label, target_label2, exit_addr, skip_comparison)) {
return 0;
}
if (op1_info & MAY_BE_DOUBLE) {
|.cold_code
|4:
if (op1_info & ((MAY_BE_ANY|MAY_BE_UNDEF)-(MAY_BE_LONG|MAY_BE_DOUBLE))) {
| IF_NOT_ZVAL_TYPE op1_addr, IS_DOUBLE, >9
}
if (op2_info & MAY_BE_DOUBLE) {
if (!same_ops && (op2_info & ((MAY_BE_ANY|MAY_BE_UNDEF)-MAY_BE_DOUBLE))) {
if (!same_ops) {
| IF_NOT_ZVAL_TYPE op2_addr, IS_DOUBLE, >5
} else {
| IF_NOT_ZVAL_TYPE op2_addr, IS_DOUBLE, >9
}
}
if (!zend_jit_cmp_double_double(Dst, opline, op1_addr, op2_addr, res_addr, smart_branch_opcode, target_label, target_label2, exit_addr)) {
return 0;
}
| jmp >6
}
if (!same_ops) {
|5:
if (op2_info & ((MAY_BE_ANY|MAY_BE_UNDEF)-(MAY_BE_LONG|MAY_BE_DOUBLE))) {
| IF_NOT_ZVAL_TYPE op2_addr, IS_LONG, >9
}
if (!zend_jit_cmp_double_long(Dst, opline, op1_addr, op2_addr, res_addr, smart_branch_opcode, target_label, target_label2, exit_addr)) {
return 0;
}
| jmp >6
}
|.code
}
} else if ((op1_info & MAY_BE_DOUBLE) &&
!(op1_info & MAY_BE_LONG) &&
(op2_info & (MAY_BE_LONG|MAY_BE_DOUBLE))) {
if (op1_info & ((MAY_BE_ANY|MAY_BE_UNDEF)-MAY_BE_DOUBLE)) {
| IF_NOT_ZVAL_TYPE op1_addr, IS_DOUBLE, >9
}
if (op2_info & MAY_BE_DOUBLE) {
if (!same_ops && (op2_info & ((MAY_BE_ANY|MAY_BE_UNDEF)-MAY_BE_DOUBLE))) {
if (!same_ops && (op2_info & MAY_BE_LONG)) {
| IF_NOT_ZVAL_TYPE op2_addr, IS_DOUBLE, >3
} else {
| IF_NOT_ZVAL_TYPE op2_addr, IS_DOUBLE, >9
}
}
if (!zend_jit_cmp_double_double(Dst, opline, op1_addr, op2_addr, res_addr, smart_branch_opcode, target_label, target_label2, exit_addr)) {
return 0;
}
}
if (!same_ops && (op2_info & MAY_BE_LONG)) {
if (op2_info & MAY_BE_DOUBLE) {
|.cold_code
}
|3:
if (op2_info & ((MAY_BE_ANY|MAY_BE_UNDEF)-(MAY_BE_DOUBLE|MAY_BE_LONG))) {
| IF_NOT_ZVAL_TYPE op2_addr, IS_LONG, >9
}
if (!zend_jit_cmp_double_long(Dst, opline, op1_addr, op2_addr, res_addr, smart_branch_opcode, target_label, target_label2, exit_addr)) {
return 0;
}
if (op2_info & MAY_BE_DOUBLE) {
| jmp >6
|.code
}
}
} else if ((op2_info & MAY_BE_DOUBLE) &&
!(op2_info & MAY_BE_LONG) &&
(op1_info & (MAY_BE_LONG|MAY_BE_DOUBLE))) {
if (op2_info & ((MAY_BE_ANY|MAY_BE_UNDEF)-MAY_BE_DOUBLE)) {
| IF_NOT_ZVAL_TYPE op2_addr, IS_DOUBLE, >9
}
if (op1_info & MAY_BE_DOUBLE) {
if (!same_ops && (op1_info & ((MAY_BE_ANY|MAY_BE_UNDEF)-MAY_BE_DOUBLE))) {
if (!same_ops && (op1_info & MAY_BE_LONG)) {
| IF_NOT_ZVAL_TYPE op1_addr, IS_DOUBLE, >3
} else {
| IF_NOT_ZVAL_TYPE op1_addr, IS_DOUBLE, >9
}
}
if (!zend_jit_cmp_double_double(Dst, opline, op1_addr, op2_addr, res_addr, smart_branch_opcode, target_label, target_label2, exit_addr)) {
return 0;
}
}
if (!same_ops && (op1_info & MAY_BE_LONG)) {
if (op1_info & MAY_BE_DOUBLE) {
|.cold_code
}
|3:
if (op1_info & ((MAY_BE_ANY|MAY_BE_UNDEF)-(MAY_BE_DOUBLE|MAY_BE_LONG))) {
| IF_NOT_ZVAL_TYPE op1_addr, IS_LONG, >9
}
if (!zend_jit_cmp_long_double(Dst, opline, op1_addr, op2_addr, res_addr, smart_branch_opcode, target_label, target_label2, exit_addr)) {
return 0;
}
if (op1_info & MAY_BE_DOUBLE) {
| jmp >6
|.code
}
}
}
if (has_slow ||
(op1_info & ((MAY_BE_ANY|MAY_BE_UNDEF)-(MAY_BE_LONG|MAY_BE_DOUBLE))) ||
(op2_info & ((MAY_BE_ANY|MAY_BE_UNDEF)-(MAY_BE_LONG|MAY_BE_DOUBLE)))) {
if (has_slow) {
|.cold_code
|9:
}
| SET_EX_OPLINE opline, r0
if (Z_MODE(op1_addr) == IS_REG) {
zend_jit_addr real_addr = ZEND_ADDR_MEM_ZVAL(ZREG_FP, opline->op1.var);
if (!zend_jit_spill_store(Dst, op1_addr, real_addr, op1_info, 1)) {
return 0;
}
op1_addr = real_addr;
}
if (Z_MODE(op2_addr) == IS_REG) {
zend_jit_addr real_addr = ZEND_ADDR_MEM_ZVAL(ZREG_FP, opline->op2.var);
if (!zend_jit_spill_store(Dst, op2_addr, real_addr, op2_info, 1)) {
return 0;
}
op2_addr = real_addr;
}
| LOAD_ZVAL_ADDR FCARG2a, op1_addr
if (opline->op1_type == IS_CV && (op1_info & MAY_BE_UNDEF)) {
| IF_NOT_Z_TYPE FCARG2a, IS_UNDEF, >1
| mov FCARG1a, opline->op1.var
| EXT_CALL zend_jit_undefined_op_helper, r0
| LOAD_ADDR_ZTS FCARG2a, executor_globals, uninitialized_zval
|1:
}
if (opline->op2_type == IS_CV && (op2_info & MAY_BE_UNDEF)) {
| IF_NOT_ZVAL_TYPE op2_addr, IS_UNDEF, >1
| mov T1, FCARG2a // save
| mov FCARG1a, opline->op2.var
| EXT_CALL zend_jit_undefined_op_helper, r0
| mov FCARG2a, T1 // restore
|.if X64
| LOAD_ADDR_ZTS CARG3, executor_globals, uninitialized_zval
|.else
| sub r4, 12
| PUSH_ADDR_ZTS executor_globals, uninitialized_zval, r0
|.endif
| jmp >2
|1:
|.if X64
| LOAD_ZVAL_ADDR CARG3, op2_addr
|.else
| sub r4, 12
| PUSH_ZVAL_ADDR op2_addr, r0
|.endif
|2:
} else {
|.if X64
| LOAD_ZVAL_ADDR CARG3, op2_addr
|.else
| sub r4, 12
| PUSH_ZVAL_ADDR op2_addr, r0
|.endif
}
| LOAD_ZVAL_ADDR FCARG1a, res_addr
| EXT_CALL compare_function, r0
|.if not(X64)
| add r4, 12
|.endif
if (opline->opcode != ZEND_CASE) {
| FREE_OP opline->op1_type, opline->op1, op1_info, 0, opline
}
| FREE_OP opline->op2_type, opline->op2, op2_info, 0, opline
if (may_throw) {
zend_jit_check_exception_undef_result(Dst, opline);
}
if (!zend_jit_cmp_slow(Dst, opline, res_addr, smart_branch_opcode, target_label, target_label2, exit_addr)) {
return 0;
}
if (has_slow) {
| jmp >6
|.code
}
}
|6:
return 1;
}
static int zend_jit_identical(dasm_State **Dst,
const zend_op *opline,
uint32_t op1_info,
zend_ssa_range *op1_range,
zend_jit_addr op1_addr,
uint32_t op2_info,
zend_ssa_range *op2_range,
zend_jit_addr op2_addr,
zend_jit_addr res_addr,
int may_throw,
zend_uchar smart_branch_opcode,
uint32_t target_label,
uint32_t target_label2,
const void *exit_addr,
zend_bool skip_comparison)
{
uint32_t identical_label = (uint32_t)-1;
uint32_t not_identical_label = (uint32_t)-1;
if (smart_branch_opcode && !exit_addr) {
if (opline->opcode != ZEND_IS_NOT_IDENTICAL) {
if (smart_branch_opcode == ZEND_JMPZ) {
not_identical_label = target_label;
} else if (smart_branch_opcode == ZEND_JMPNZ) {
identical_label = target_label;
} else if (smart_branch_opcode == ZEND_JMPZNZ) {
not_identical_label = target_label;
identical_label = target_label2;
} else {
ZEND_UNREACHABLE();
}
} else {
if (smart_branch_opcode == ZEND_JMPZ) {
identical_label = target_label;
} else if (smart_branch_opcode == ZEND_JMPNZ) {
not_identical_label = target_label;
} else if (smart_branch_opcode == ZEND_JMPZNZ) {
identical_label = target_label;
not_identical_label = target_label2;
} else {
ZEND_UNREACHABLE();
}
}
}
if ((op1_info & (MAY_BE_REF|MAY_BE_ANY|MAY_BE_UNDEF)) == MAY_BE_LONG &&
(op2_info & (MAY_BE_REF|MAY_BE_ANY|MAY_BE_UNDEF)) == MAY_BE_LONG) {
if (!zend_jit_cmp_long_long(Dst, opline, op1_range, op1_addr, op2_range, op2_addr, res_addr, smart_branch_opcode, target_label, target_label2, exit_addr, skip_comparison)) {
return 0;
}
return 1;
} else if ((op1_info & (MAY_BE_REF|MAY_BE_ANY|MAY_BE_UNDEF)) == MAY_BE_DOUBLE &&
(op2_info & (MAY_BE_REF|MAY_BE_ANY|MAY_BE_UNDEF)) == MAY_BE_DOUBLE) {
if (!zend_jit_cmp_double_double(Dst, opline, op1_addr, op2_addr, res_addr, smart_branch_opcode, target_label, target_label2, exit_addr)) {
return 0;
}
return 1;
}
if ((op1_info & MAY_BE_UNDEF) && (op2_info & MAY_BE_UNDEF)) {
op1_info |= MAY_BE_NULL;
op2_info |= MAY_BE_NULL;
| LOAD_ZVAL_ADDR FCARG1a, op1_addr
| IF_Z_TYPE FCARG1a, IS_UNDEF, >1
|.cold_code
|1:
| // zend_error(E_WARNING, "Undefined variable $%s", ZSTR_VAL(CV_DEF_OF(EX_VAR_TO_NUM(opline->op1.var))));
| SET_EX_OPLINE opline, r0
| mov FCARG1d, opline->op1.var
| EXT_CALL zend_jit_undefined_op_helper, r0
if (may_throw) {
zend_jit_check_exception_undef_result(Dst, opline);
}
| LOAD_ADDR_ZTS FCARG1a, executor_globals, uninitialized_zval
| jmp >1
|.code
|1:
| LOAD_ZVAL_ADDR FCARG2a, op2_addr
| IF_Z_TYPE FCARG2a, IS_UNDEF, >1
|.cold_code
|1:
| // zend_error(E_WARNING, "Undefined variable $%s", ZSTR_VAL(CV_DEF_OF(EX_VAR_TO_NUM(opline->op1.var))));
| SET_EX_OPLINE opline, r0
| mov aword T1, FCARG1a // save
| mov FCARG1d, opline->op2.var
| EXT_CALL zend_jit_undefined_op_helper, r0
if (may_throw) {
zend_jit_check_exception_undef_result(Dst, opline);
}
| mov FCARG1a, aword T1 // restore
| LOAD_ADDR_ZTS FCARG2a, executor_globals, uninitialized_zval
| jmp >1
|.code
|1:
} else if (op1_info & MAY_BE_UNDEF) {
op1_info |= MAY_BE_NULL;
| LOAD_ZVAL_ADDR FCARG1a, op1_addr
| IF_Z_TYPE FCARG1a, IS_UNDEF, >1
|.cold_code
|1:
| // zend_error(E_WARNING, "Undefined variable $%s", ZSTR_VAL(CV_DEF_OF(EX_VAR_TO_NUM(opline->op1.var))));
| SET_EX_OPLINE opline, r0
| mov FCARG1d, opline->op1.var
| EXT_CALL zend_jit_undefined_op_helper, r0
if (may_throw) {
zend_jit_check_exception_undef_result(Dst, opline);
}
| LOAD_ADDR_ZTS FCARG1a, executor_globals, uninitialized_zval
| jmp >1
|.code
|1:
if (opline->op2_type != IS_CONST) {
| LOAD_ZVAL_ADDR FCARG2a, op2_addr
}
} else if (op2_info & MAY_BE_UNDEF) {
op2_info |= MAY_BE_NULL;
| LOAD_ZVAL_ADDR FCARG2a, op2_addr
| IF_Z_TYPE FCARG2a, IS_UNDEF, >1
|.cold_code
|1:
| // zend_error(E_WARNING, "Undefined variable $%s", ZSTR_VAL(CV_DEF_OF(EX_VAR_TO_NUM(opline->op1.var))));
| SET_EX_OPLINE opline, r0
| mov FCARG1d, opline->op2.var
| EXT_CALL zend_jit_undefined_op_helper, r0
if (may_throw) {
zend_jit_check_exception_undef_result(Dst, opline);
}
| LOAD_ADDR_ZTS FCARG2a, executor_globals, uninitialized_zval
| jmp >1
|.code
|1:
if (opline->op1_type != IS_CONST) {
| LOAD_ZVAL_ADDR FCARG1a, op1_addr
}
} else {
if (opline->op1_type != IS_CONST) {
if (Z_MODE(op1_addr) == IS_REG) {
zend_jit_addr real_addr = ZEND_ADDR_MEM_ZVAL(ZREG_FP, opline->op1.var);
if (!zend_jit_spill_store(Dst, op1_addr, real_addr, op1_info, 1)) {
return 0;
}
op1_addr = real_addr;
}
| LOAD_ZVAL_ADDR FCARG1a, op1_addr
}
if (opline->op2_type != IS_CONST) {
if (Z_MODE(op2_addr) == IS_REG) {
zend_jit_addr real_addr = ZEND_ADDR_MEM_ZVAL(ZREG_FP, opline->op2.var);
if (!zend_jit_spill_store(Dst, op2_addr, real_addr, op2_info, 1)) {
return 0;
}
op2_addr = real_addr;
}
| LOAD_ZVAL_ADDR FCARG2a, op2_addr
}
}
if (opline->op1_type & (IS_CV|IS_VAR)) {
| ZVAL_DEREF FCARG1a, op1_info
}
if (opline->op2_type & (IS_CV|IS_VAR)) {
| ZVAL_DEREF FCARG2a, op2_info
}
if ((op1_info & op2_info & MAY_BE_ANY) == 0) {
if ((opline->opcode != ZEND_CASE_STRICT &&
(opline->op1_type & (IS_VAR|IS_TMP_VAR)) &&
(op1_info & (MAY_BE_STRING|MAY_BE_ARRAY|MAY_BE_OBJECT|MAY_BE_RESOURCE|MAY_BE_REF))) ||
((opline->op2_type & (IS_VAR|IS_TMP_VAR)) &&
(op2_info & (MAY_BE_STRING|MAY_BE_ARRAY|MAY_BE_OBJECT|MAY_BE_RESOURCE|MAY_BE_REF)))) {
| SET_EX_OPLINE opline, r0
if (opline->opcode != ZEND_CASE_STRICT) {
| FREE_OP opline->op1_type, opline->op1, op1_info, 1, opline
}
| FREE_OP opline->op2_type, opline->op2, op2_info, 1, opline
}
if (smart_branch_opcode) {
zend_jit_check_exception_undef_result(Dst, opline);
if (exit_addr) {
if (smart_branch_opcode == ZEND_JMPZ) {
| jmp &exit_addr
}
} else if (not_identical_label != (uint32_t)-1) {
| jmp =>not_identical_label
}
} else {
| SET_ZVAL_TYPE_INFO res_addr, (opline->opcode != ZEND_IS_NOT_IDENTICAL ? IS_FALSE : IS_TRUE)
zend_jit_check_exception(Dst);
}
} else if (has_concrete_type(op1_info) &&
has_concrete_type(op2_info) &&
concrete_type(op1_info) == concrete_type(op2_info) &&
concrete_type(op1_info) <= IS_TRUE) {
if (smart_branch_opcode) {
if (exit_addr) {
if (smart_branch_opcode == ZEND_JMPNZ) {
| jmp &exit_addr
}
} else if (identical_label != (uint32_t)-1) {
| jmp =>identical_label
}
} else {
| SET_ZVAL_TYPE_INFO res_addr, (opline->opcode != ZEND_IS_NOT_IDENTICAL ? IS_TRUE : IS_FALSE)
}
} else if (Z_MODE(op1_addr) == IS_CONST_ZVAL && Z_MODE(op2_addr) == IS_CONST_ZVAL) {
if (zend_is_identical(Z_ZV(op1_addr), Z_ZV(op2_addr))) {
if (smart_branch_opcode) {
if (exit_addr) {
if (smart_branch_opcode == ZEND_JMPNZ) {
| jmp &exit_addr
}
} else if (identical_label != (uint32_t)-1) {
| jmp =>identical_label
}
} else {
| SET_ZVAL_TYPE_INFO res_addr, (opline->opcode != ZEND_IS_NOT_IDENTICAL ? IS_TRUE : IS_FALSE)
}
} else {
if (smart_branch_opcode) {
if (exit_addr) {
if (smart_branch_opcode == ZEND_JMPZ) {
| jmp &exit_addr
}
} else if (not_identical_label != (uint32_t)-1) {
| jmp =>not_identical_label
}
} else {
| SET_ZVAL_TYPE_INFO res_addr, (opline->opcode != ZEND_IS_NOT_IDENTICAL ? IS_FALSE : IS_TRUE)
}
}
} else if (Z_MODE(op1_addr) == IS_CONST_ZVAL && Z_TYPE_P(Z_ZV(op1_addr)) <= IS_TRUE) {
zval *val = Z_ZV(op1_addr);
| cmp byte [FCARG2a + offsetof(zval, u1.v.type)], Z_TYPE_P(val)
if (smart_branch_opcode) {
if (opline->op2_type == IS_VAR && (op2_info & MAY_BE_REF)) {
| jne >8
| SET_EX_OPLINE opline, r0
| FREE_OP opline->op2_type, opline->op2, op2_info, 1, opline
zend_jit_check_exception_undef_result(Dst, opline);
if (exit_addr && smart_branch_opcode == ZEND_JMPNZ) {
| jmp &exit_addr
} else if (identical_label != (uint32_t)-1) {
| jmp =>identical_label
} else {
| jmp >9
}
|8:
} else if (exit_addr && smart_branch_opcode == ZEND_JMPNZ) {
| je &exit_addr
} else if (identical_label != (uint32_t)-1) {
| je =>identical_label
} else {
| je >9
}
} else {
if (opline->opcode != ZEND_IS_NOT_IDENTICAL) {
| sete al
} else {
| setne al
}
| movzx eax, al
| lea eax, [eax + 2]
| SET_ZVAL_TYPE_INFO res_addr, eax
}
if ((opline->op2_type & (IS_VAR|IS_TMP_VAR)) &&
(op2_info & (MAY_BE_STRING|MAY_BE_ARRAY|MAY_BE_OBJECT|MAY_BE_RESOURCE|MAY_BE_REF))) {
| SET_EX_OPLINE opline, r0
| FREE_OP opline->op2_type, opline->op2, op2_info, 1, opline
zend_jit_check_exception_undef_result(Dst, opline);
}
if (exit_addr) {
if (smart_branch_opcode == ZEND_JMPZ) {
| jmp &exit_addr
}
} else if (smart_branch_opcode && not_identical_label != (uint32_t)-1) {
| jmp =>not_identical_label
}
} else if (Z_MODE(op2_addr) == IS_CONST_ZVAL && Z_TYPE_P(Z_ZV(op2_addr)) <= IS_TRUE) {
zval *val = Z_ZV(op2_addr);
| cmp byte [FCARG1a + offsetof(zval, u1.v.type)], Z_TYPE_P(val)
if (smart_branch_opcode) {
if (opline->opcode != ZEND_CASE_STRICT
&& opline->op1_type == IS_VAR && (op1_info & MAY_BE_REF)) {
| jne >8
| SET_EX_OPLINE opline, r0
| FREE_OP opline->op1_type, opline->op1, op1_info, 1, opline
zend_jit_check_exception_undef_result(Dst, opline);
if (exit_addr && smart_branch_opcode == ZEND_JMPNZ) {
| jmp &exit_addr
} else if (identical_label != (uint32_t)-1) {
| jmp =>identical_label
} else {
| jmp >9
}
|8:
} else if (exit_addr && smart_branch_opcode == ZEND_JMPNZ) {
| je &exit_addr
} else if (identical_label != (uint32_t)-1) {
| je =>identical_label
} else {
| je >9
}
} else {
if (opline->opcode != ZEND_IS_NOT_IDENTICAL) {
| sete al
} else {
| setne al
}
| movzx eax, al
| lea eax, [eax + 2]
| SET_ZVAL_TYPE_INFO res_addr, eax
}
if (opline->opcode != ZEND_CASE_STRICT
&& (opline->op1_type & (IS_VAR|IS_TMP_VAR)) &&
(op1_info & (MAY_BE_STRING|MAY_BE_ARRAY|MAY_BE_OBJECT|MAY_BE_RESOURCE|MAY_BE_REF))) {
| SET_EX_OPLINE opline, r0
| FREE_OP opline->op1_type, opline->op1, op1_info, 1, opline
zend_jit_check_exception_undef_result(Dst, opline);
}
if (smart_branch_opcode) {
if (exit_addr) {
if (smart_branch_opcode == ZEND_JMPZ) {
| jmp &exit_addr
}
} else if (not_identical_label != (uint32_t)-1) {
| jmp =>not_identical_label
}
}
} else {
if (opline->op1_type == IS_CONST) {
| LOAD_ZVAL_ADDR FCARG1a, op1_addr
}
if (opline->op2_type == IS_CONST) {
| LOAD_ZVAL_ADDR FCARG2a, op2_addr
}
| EXT_CALL zend_is_identical, r0
if ((opline->opcode != ZEND_CASE_STRICT &&
(opline->op1_type & (IS_VAR|IS_TMP_VAR)) &&
(op1_info & (MAY_BE_STRING|MAY_BE_ARRAY|MAY_BE_OBJECT|MAY_BE_RESOURCE|MAY_BE_REF))) ||
((opline->op2_type & (IS_VAR|IS_TMP_VAR)) &&
(op2_info & (MAY_BE_STRING|MAY_BE_ARRAY|MAY_BE_OBJECT|MAY_BE_RESOURCE|MAY_BE_REF)))) {
| mov aword T1, r0 // save
| SET_EX_OPLINE opline, r0
if (opline->opcode != ZEND_CASE_STRICT) {
| FREE_OP opline->op1_type, opline->op1, op1_info, 1, opline
}
| FREE_OP opline->op2_type, opline->op2, op2_info, 1, opline
zend_jit_check_exception_undef_result(Dst, opline);
| mov r0, aword T1 // restore
}
if (smart_branch_opcode) {
| test al, al
if (exit_addr) {
if (smart_branch_opcode == ZEND_JMPNZ) {
| jnz &exit_addr
} else {
| jz &exit_addr
}
} else if (not_identical_label != (uint32_t)-1) {
| jz =>not_identical_label
if (identical_label != (uint32_t)-1) {
| jmp =>identical_label
}
} else if (identical_label != (uint32_t)-1) {
| jnz =>identical_label
}
} else {
| movzx eax, al
if (opline->opcode != ZEND_IS_NOT_IDENTICAL) {
| lea eax, [eax + 2]
} else {
| neg eax
| lea eax, [eax + 3]
}
| SET_ZVAL_TYPE_INFO res_addr, eax
}
}
|9:
if (may_throw) {
zend_jit_check_exception(Dst);
}
return 1;
}
static int zend_jit_bool_jmpznz(dasm_State **Dst, const zend_op *opline, uint32_t op1_info, zend_jit_addr op1_addr, zend_jit_addr res_addr, uint32_t target_label, uint32_t target_label2, int may_throw, zend_uchar branch_opcode, const void *exit_addr)
{
uint32_t true_label = -1;
uint32_t false_label = -1;
zend_bool set_bool = 0;
zend_bool set_bool_not = 0;
zend_bool set_delayed = 0;
zend_bool jmp_done = 0;
if (branch_opcode == ZEND_BOOL) {
set_bool = 1;
} else if (branch_opcode == ZEND_BOOL_NOT) {
set_bool = 1;
set_bool_not = 1;
} else if (branch_opcode == ZEND_JMPZ) {
false_label = target_label;
} else if (branch_opcode == ZEND_JMPNZ) {
true_label = target_label;
} else if (branch_opcode == ZEND_JMPZNZ) {
true_label = target_label2;
false_label = target_label;
} else if (branch_opcode == ZEND_JMPZ_EX) {
set_bool = 1;
false_label = target_label;
} else if (branch_opcode == ZEND_JMPNZ_EX) {
set_bool = 1;
true_label = target_label;
} else {
ZEND_UNREACHABLE();
}
if (Z_MODE(op1_addr) == IS_CONST_ZVAL) {
if (zend_is_true(Z_ZV(op1_addr))) {
/* Always TRUE */
if (set_bool) {
if (set_bool_not) {
| SET_ZVAL_TYPE_INFO res_addr, IS_FALSE
} else {
| SET_ZVAL_TYPE_INFO res_addr, IS_TRUE
}
}
if (true_label != (uint32_t)-1) {
| jmp =>true_label;
}
} else {
/* Always FALSE */
if (set_bool) {
if (set_bool_not) {
| SET_ZVAL_TYPE_INFO res_addr, IS_TRUE
} else {
| SET_ZVAL_TYPE_INFO res_addr, IS_FALSE
}
}
if (false_label != (uint32_t)-1) {
| jmp =>false_label;
}
}
return 1;
}
if (opline->op1_type == IS_CV && (op1_info & MAY_BE_REF)) {
| LOAD_ZVAL_ADDR FCARG1a, op1_addr
| ZVAL_DEREF FCARG1a, op1_info
op1_addr = ZEND_ADDR_MEM_ZVAL(ZREG_FCARG1a, 0);
}
if (op1_info & (MAY_BE_UNDEF|MAY_BE_NULL|MAY_BE_FALSE|MAY_BE_TRUE)) {
if (!(op1_info & ((MAY_BE_UNDEF|MAY_BE_ANY)-MAY_BE_TRUE))) {
/* Always TRUE */
if (set_bool) {
if (set_bool_not) {
| SET_ZVAL_TYPE_INFO res_addr, IS_FALSE
} else {
| SET_ZVAL_TYPE_INFO res_addr, IS_TRUE
}
}
if (true_label != (uint32_t)-1) {
| jmp =>true_label;
}
} else {
if (!(op1_info & (MAY_BE_ANY-(MAY_BE_NULL|MAY_BE_FALSE)))) {
/* Always FALSE */
if (set_bool) {
if (set_bool_not) {
| SET_ZVAL_TYPE_INFO res_addr, IS_TRUE
} else {
| SET_ZVAL_TYPE_INFO res_addr, IS_FALSE
}
}
} else {
| CMP_ZVAL_TYPE op1_addr, IS_TRUE
if (op1_info & (MAY_BE_ANY-(MAY_BE_NULL|MAY_BE_FALSE|MAY_BE_TRUE))) {
if ((op1_info & MAY_BE_LONG) &&
!(op1_info & MAY_BE_UNDEF) &&
!set_bool) {
if (exit_addr) {
if (branch_opcode == ZEND_JMPNZ) {
| jl >9
} else {
| jl &exit_addr
}
} else if (false_label != (uint32_t)-1) {
| jl =>false_label
} else {
| jl >9
}
jmp_done = 1;
} else {
| jg >2
}
}
if (!(op1_info & MAY_BE_TRUE)) {
/* It's FALSE */
if (set_bool) {
if (set_bool_not) {
| SET_ZVAL_TYPE_INFO res_addr, IS_TRUE
} else {
| SET_ZVAL_TYPE_INFO res_addr, IS_FALSE
}
}
} else {
if (exit_addr) {
if (set_bool) {
| jne >1
| SET_ZVAL_TYPE_INFO res_addr, IS_TRUE
if (branch_opcode == ZEND_JMPNZ || branch_opcode == ZEND_JMPNZ_EX) {
| jmp &exit_addr
} else {
| jmp >9
}
|1:
| SET_ZVAL_TYPE_INFO res_addr, IS_FALSE
if (branch_opcode == ZEND_JMPZ || branch_opcode == ZEND_JMPZ_EX) {
if (!(op1_info & (MAY_BE_UNDEF|MAY_BE_LONG))) {
| jne &exit_addr
}
}
} else {
if (branch_opcode == ZEND_JMPNZ || branch_opcode == ZEND_JMPNZ_EX) {
| je &exit_addr
} else if (!(op1_info & (MAY_BE_UNDEF|MAY_BE_LONG))) {
| jne &exit_addr
} else {
| je >9
}
}
} else if (true_label != (uint32_t)-1 || false_label != (uint32_t)-1) {
if (set_bool) {
| jne >1
| SET_ZVAL_TYPE_INFO res_addr, IS_TRUE
if (true_label != (uint32_t)-1) {
| jmp =>true_label
} else {
| jmp >9
}
|1:
| SET_ZVAL_TYPE_INFO res_addr, IS_FALSE
} else {
if (true_label != (uint32_t)-1) {
| je =>true_label
} else if (!(op1_info & (MAY_BE_UNDEF|MAY_BE_LONG))) {
| jne =>false_label
jmp_done = 1;
} else {
| je >9
}
}
} else if (set_bool) {
| sete al
| movzx eax, al
if (set_bool_not) {
| neg eax
| add eax, 3
} else {
| add eax, 2
}
if ((op1_info & MAY_BE_UNDEF) && (op1_info & MAY_BE_ANY)) {
set_delayed = 1;
} else {
| SET_ZVAL_TYPE_INFO res_addr, eax
}
}
}
}
/* It's FALSE, but may be UNDEF */
if (op1_info & MAY_BE_UNDEF) {
if (op1_info & MAY_BE_ANY) {
if (set_delayed) {
| CMP_ZVAL_TYPE op1_addr, IS_UNDEF
| SET_ZVAL_TYPE_INFO res_addr, eax
| jz >1
} else {
| IF_ZVAL_TYPE op1_addr, IS_UNDEF, >1
}
|.cold_code
|1:
}
| mov FCARG1d, opline->op1.var
| SET_EX_OPLINE opline, r0
| EXT_CALL zend_jit_undefined_op_helper, r0
if (may_throw) {
if (!zend_jit_check_exception_undef_result(Dst, opline)) {
return 0;
}
}
if (exit_addr) {
if (branch_opcode == ZEND_JMPZ || branch_opcode == ZEND_JMPZ_EX) {
| jmp &exit_addr
}
} else if (false_label != (uint32_t)-1) {
| jmp =>false_label
}
if (op1_info & MAY_BE_ANY) {
if (exit_addr) {
if (branch_opcode == ZEND_JMPNZ || branch_opcode == ZEND_JMPNZ_EX) {
| jmp >9
}
} else if (false_label == (uint32_t)-1) {
| jmp >9
}
|.code
}
}
if (!jmp_done) {
if (exit_addr) {
if (branch_opcode == ZEND_JMPNZ || branch_opcode == ZEND_JMPNZ_EX) {
if (op1_info & MAY_BE_LONG) {
| jmp >9
}
} else if (op1_info & MAY_BE_LONG) {
| jmp &exit_addr
}
} else if (false_label != (uint32_t)-1) {
| jmp =>false_label
} else if ((op1_info & MAY_BE_LONG) || (op1_info & MAY_BE_ANY) == MAY_BE_DOUBLE) {
| jmp >9
}
}
}
}
if (op1_info & MAY_BE_LONG) {
|2:
if (op1_info & (MAY_BE_ANY-(MAY_BE_NULL|MAY_BE_FALSE|MAY_BE_TRUE|MAY_BE_LONG))) {
| IF_NOT_ZVAL_TYPE op1_addr, IS_LONG, >2
}
if (Z_MODE(op1_addr) == IS_REG) {
| test Ra(Z_REG(op1_addr)), Ra(Z_REG(op1_addr))
} else {
| LONG_OP_WITH_CONST, cmp, op1_addr, Z_L(0)
}
if (set_bool) {
| setne al
| movzx eax, al
if (set_bool_not) {
| neg eax
| add eax, 3
} else {
| lea eax, [eax + 2]
}
| SET_ZVAL_TYPE_INFO res_addr, eax
}
if (exit_addr) {
if (branch_opcode == ZEND_JMPNZ || branch_opcode == ZEND_JMPNZ_EX) {
| jne &exit_addr
} else {
| je &exit_addr
}
} else if (true_label != (uint32_t)-1 || false_label != (uint32_t)-1) {
if (true_label != (uint32_t)-1) {
| jne =>true_label
if (false_label != (uint32_t)-1) {
| jmp =>false_label
}
} else {
| je =>false_label
}
}
}
if ((op1_info & (MAY_BE_ANY-(MAY_BE_NULL|MAY_BE_FALSE|MAY_BE_TRUE|MAY_BE_LONG))) == MAY_BE_DOUBLE) {
if (op1_info & (MAY_BE_UNDEF|MAY_BE_NULL|MAY_BE_FALSE|MAY_BE_TRUE|MAY_BE_LONG)) {
|.cold_code
}
|2:
if (CAN_USE_AVX()) {
| vxorps xmm0, xmm0, xmm0
} else {
| xorps xmm0, xmm0
}
| SSE_AVX_OP ucomisd, vucomisd, ZREG_XMM0, op1_addr
if (set_bool) {
if (exit_addr) {
if (branch_opcode == ZEND_JMPNZ || branch_opcode == ZEND_JMPNZ_EX) {
| SET_ZVAL_TYPE_INFO res_addr, IS_TRUE
| jp &exit_addr
| jne &exit_addr
| SET_ZVAL_TYPE_INFO res_addr, IS_FALSE
} else {
| jp >1
| SET_ZVAL_TYPE_INFO res_addr, IS_FALSE
| je &exit_addr
|1:
| SET_ZVAL_TYPE_INFO res_addr, IS_TRUE
}
} else if (false_label != (uint32_t)-1) { // JMPZ_EX (p=>true, z=>false, false=>jmp)
| jp >1
| SET_ZVAL_TYPE_INFO res_addr, IS_FALSE
| je => false_label
|1:
| SET_ZVAL_TYPE_INFO res_addr, IS_TRUE
} else if (true_label != (uint32_t)-1) { // JMPNZ_EX (p=>true, z=>false, true=>jmp)
| SET_ZVAL_TYPE_INFO res_addr, IS_TRUE
| jp => true_label
| jne => true_label
| SET_ZVAL_TYPE_INFO res_addr, IS_FALSE
} else if (set_bool_not) { // BOOL_NOT (p=>false, z=>true)
| mov eax, IS_FALSE
| jp >1
| jne >1
| mov eax, IS_TRUE
|1:
| SET_ZVAL_TYPE_INFO res_addr, eax
} else { // BOOL (p=>true, z=>false)
| mov eax, IS_TRUE
| jp >1
| jne >1
| mov eax, IS_FALSE
|1:
| SET_ZVAL_TYPE_INFO res_addr, eax
}
if (op1_info & (MAY_BE_UNDEF|MAY_BE_NULL|MAY_BE_FALSE|MAY_BE_TRUE|MAY_BE_LONG)) {
| jmp >9
|.code
}
} else {
if (exit_addr) {
if (branch_opcode == ZEND_JMPNZ || branch_opcode == ZEND_JMPNZ_EX) {
| jp &exit_addr
| jne &exit_addr
|1:
} else {
| jp >1
| je &exit_addr
|1:
}
if (op1_info & (MAY_BE_UNDEF|MAY_BE_NULL|MAY_BE_FALSE|MAY_BE_TRUE|MAY_BE_LONG)) {
| jmp >9
}
} else {
ZEND_ASSERT(true_label != (uint32_t)-1 || false_label != (uint32_t)-1);
if (false_label != (uint32_t)-1 ) {
| jp >1
| je => false_label
|1:
if (true_label != (uint32_t)-1) {
| jmp =>true_label
} else if (op1_info & (MAY_BE_UNDEF|MAY_BE_NULL|MAY_BE_FALSE|MAY_BE_TRUE|MAY_BE_LONG)) {
| jmp >9
}
} else {
| jp => true_label
| jne => true_label
if (op1_info & (MAY_BE_UNDEF|MAY_BE_NULL|MAY_BE_FALSE|MAY_BE_TRUE|MAY_BE_LONG)) {
| jmp >9
}
}
}
if (op1_info & (MAY_BE_UNDEF|MAY_BE_NULL|MAY_BE_FALSE|MAY_BE_TRUE|MAY_BE_LONG)) {
|.code
}
}
} else if (op1_info & (MAY_BE_ANY - (MAY_BE_NULL|MAY_BE_FALSE|MAY_BE_TRUE|MAY_BE_LONG))) {
if (op1_info & (MAY_BE_UNDEF|MAY_BE_NULL|MAY_BE_FALSE|MAY_BE_TRUE|MAY_BE_LONG)) {
|.cold_code
|2:
}
if (Z_REG(op1_addr) != ZREG_FCARG1a || Z_OFFSET(op1_addr) != 0) {
| LOAD_ZVAL_ADDR FCARG1a, op1_addr
}
| SET_EX_OPLINE opline, r0
| EXT_CALL zend_is_true, r0
if ((opline->op1_type & (IS_VAR|IS_TMP_VAR)) &&
(op1_info & (MAY_BE_STRING|MAY_BE_ARRAY|MAY_BE_OBJECT|MAY_BE_RESOURCE))) {
op1_addr = ZEND_ADDR_MEM_ZVAL(ZREG_FP, opline->op1.var);
if (op1_info & ((MAY_BE_ANY|MAY_BE_UNDEF)-(MAY_BE_OBJECT|MAY_BE_RESOURCE))) {
| IF_NOT_ZVAL_REFCOUNTED op1_addr, >3
}
| GET_ZVAL_PTR FCARG1a, op1_addr
| GC_DELREF FCARG1a
| jnz >3
| mov aword T1, r0 // save
| ZVAL_DTOR_FUNC op1_info, opline
| mov r0, aword T1 // restore
|3:
}
if (may_throw) {
| MEM_OP2_1_ZTS cmp, aword, executor_globals, exception, 0, r1
| jne ->exception_handler_undef
}
if (set_bool) {
if (set_bool_not) {
| neg eax
| add eax, 3
} else {
| add eax, 2
}
| SET_ZVAL_TYPE_INFO res_addr, eax
if (exit_addr) {
| CMP_ZVAL_TYPE res_addr, IS_FALSE
if (branch_opcode == ZEND_JMPNZ || branch_opcode == ZEND_JMPNZ_EX) {
| jne &exit_addr
} else {
| je &exit_addr
}
} else if (true_label != (uint32_t)-1 || false_label != (uint32_t)-1) {
| CMP_ZVAL_TYPE res_addr, IS_FALSE
if (true_label != (uint32_t)-1) {
| jne =>true_label
if (false_label != (uint32_t)-1) {
| jmp =>false_label
} else if (op1_info & (MAY_BE_UNDEF|MAY_BE_NULL|MAY_BE_FALSE|MAY_BE_TRUE|MAY_BE_LONG)) {
| jmp >9
}
} else {
| je =>false_label
}
}
if (op1_info & (MAY_BE_UNDEF|MAY_BE_NULL|MAY_BE_FALSE|MAY_BE_TRUE|MAY_BE_LONG)) {
| jmp >9
|.code
}
} else {
| test r0, r0
if (exit_addr) {
if (branch_opcode == ZEND_JMPNZ || branch_opcode == ZEND_JMPNZ_EX) {
| jne &exit_addr
if (op1_info & (MAY_BE_UNDEF|MAY_BE_NULL|MAY_BE_FALSE|MAY_BE_TRUE|MAY_BE_LONG)) {
| jmp >9
}
} else {
| je &exit_addr
if (op1_info & (MAY_BE_UNDEF|MAY_BE_NULL|MAY_BE_FALSE|MAY_BE_TRUE|MAY_BE_LONG)) {
| jmp >9
}
}
} else if (true_label != (uint32_t)-1) {
| jne =>true_label
if (false_label != (uint32_t)-1) {
| jmp =>false_label
} else if (op1_info & (MAY_BE_UNDEF|MAY_BE_NULL|MAY_BE_FALSE|MAY_BE_TRUE|MAY_BE_LONG)) {
| jmp >9
}
} else {
| je =>false_label
if (op1_info & (MAY_BE_UNDEF|MAY_BE_NULL|MAY_BE_FALSE|MAY_BE_TRUE|MAY_BE_LONG)) {
| jmp >9
}
}
if (op1_info & (MAY_BE_UNDEF|MAY_BE_NULL|MAY_BE_FALSE|MAY_BE_TRUE|MAY_BE_LONG)) {
|.code
}
}
}
|9:
return 1;
}
static int zend_jit_qm_assign(dasm_State **Dst, const zend_op *opline, uint32_t op1_info, zend_jit_addr op1_addr, zend_jit_addr op1_def_addr, uint32_t res_use_info, uint32_t res_info, zend_jit_addr res_addr)
{
if (op1_addr != op1_def_addr) {
if (!zend_jit_update_regs(Dst, opline->op1.var, op1_addr, op1_def_addr, op1_info)) {
return 0;
}
if (Z_MODE(op1_def_addr) == IS_REG && Z_MODE(op1_addr) != IS_REG) {
op1_addr = op1_def_addr;
}
}
if (!zend_jit_simple_assign(Dst, opline, res_addr, res_use_info, res_info, opline->op1_type, op1_addr, op1_info, 0, 0, 0)) {
return 0;
}
if (!zend_jit_store_var_if_necessary(Dst, opline->result.var, res_addr, res_info)) {
return 0;
}
if (op1_info & MAY_BE_UNDEF) {
zend_jit_check_exception(Dst);
}
return 1;
}
static int zend_jit_assign(dasm_State **Dst, const zend_op *opline, uint32_t op1_info, zend_jit_addr op1_use_addr, uint32_t op1_def_info, zend_jit_addr op1_addr, uint32_t op2_info, zend_jit_addr op2_addr, zend_jit_addr op2_def_addr, uint32_t res_info, zend_jit_addr res_addr, int may_throw)
{
ZEND_ASSERT(opline->op1_type == IS_CV);
if (op2_addr != op2_def_addr) {
if (!zend_jit_update_regs(Dst, opline->op2.var, op2_addr, op2_def_addr, op2_info)) {
return 0;
}
if (Z_MODE(op2_def_addr) == IS_REG && Z_MODE(op2_addr) != IS_REG) {
op2_addr = op2_def_addr;
}
}
if (Z_MODE(op1_addr) != IS_REG
&& Z_MODE(op1_use_addr) == IS_REG
&& !Z_LOAD(op1_use_addr)
&& !Z_STORE(op1_use_addr)) {
/* Force type update */
op1_info |= MAY_BE_UNDEF;
}
if (!zend_jit_assign_to_variable(Dst, opline, op1_use_addr, op1_addr, op1_info, op1_def_info, opline->op2_type, op2_addr, op2_info, res_addr,
may_throw)) {
return 0;
}
if (!zend_jit_store_var_if_necessary_ex(Dst, opline->op1.var, op1_addr, op1_def_info, op1_use_addr, op1_info)) {
return 0;
}
if (opline->result_type != IS_UNUSED) {
if (!zend_jit_store_var_if_necessary(Dst, opline->result.var, res_addr, res_info)) {
return 0;
}
}
return 1;
}
/* copy of hidden zend_closure */
typedef struct _zend_closure {
zend_object std;
zend_function func;
zval this_ptr;
zend_class_entry *called_scope;
zif_handler orig_internal_handler;
} zend_closure;
static int zend_jit_stack_check(dasm_State **Dst, const zend_op *opline, uint32_t used_stack)
{
int32_t exit_point = zend_jit_trace_get_exit_point(opline, ZEND_JIT_EXIT_TO_VM);
const void *exit_addr = zend_jit_trace_get_exit_addr(exit_point);
if (!exit_addr) {
return 0;
}
| // Check Stack Overflow
| MEM_OP2_2_ZTS mov, r1, aword, executor_globals, vm_stack_end, r0
| MEM_OP2_2_ZTS sub, r1, aword, executor_globals, vm_stack_top, r0
| cmp r1, used_stack
| jb &exit_addr
return 1;
}
static int zend_jit_push_call_frame(dasm_State **Dst, const zend_op *opline, const zend_op_array *op_array, zend_function *func, zend_bool is_closure, zend_bool use_this, zend_bool stack_check)
{
uint32_t used_stack;
if (func) {
used_stack = zend_vm_calc_used_stack(opline->extended_value, func);
} else {
used_stack = (ZEND_CALL_FRAME_SLOT + opline->extended_value) * sizeof(zval);
| // if (EXPECTED(ZEND_USER_CODE(func->type))) {
if (!is_closure) {
| test byte [r0 + offsetof(zend_function, type)], 1
| mov FCARG1a, used_stack
| jnz >1
} else {
| mov FCARG1a, used_stack
}
| // used_stack += (func->op_array.last_var + func->op_array.T - MIN(func->op_array.num_args, num_args)) * sizeof(zval);
| mov edx, opline->extended_value
if (!is_closure) {
| cmp edx, dword [r0 + offsetof(zend_function, op_array.num_args)]
| cmova edx, dword [r0 + offsetof(zend_function, op_array.num_args)]
| sub edx, dword [r0 + offsetof(zend_function, op_array.last_var)]
| sub edx, dword [r0 + offsetof(zend_function, op_array.T)]
} else {
| cmp edx, dword [r0 + offsetof(zend_closure, func.op_array.num_args)]
| cmova edx, dword [r0 + offsetof(zend_closure, func.op_array.num_args)]
| sub edx, dword [r0 + offsetof(zend_closure, func.op_array.last_var)]
| sub edx, dword [r0 + offsetof(zend_closure, func.op_array.T)]
}
| shl edx, 4
|.if X64
| movsxd r2, edx
|.endif
| sub FCARG1a, r2
|1:
}
zend_jit_start_reuse_ip();
| // if (UNEXPECTED(used_stack > (size_t)(((char*)EG(vm_stack_end)) - (char*)call))) {
| MEM_OP2_2_ZTS mov, RX, aword, executor_globals, vm_stack_top, RX
if (stack_check) {
| // Check Stack Overflow
| MEM_OP2_2_ZTS mov, r2, aword, executor_globals, vm_stack_end, r2
| sub r2, RX
if (func) {
| cmp r2, used_stack
} else {
| cmp r2, FCARG1a
}
if (JIT_G(trigger) == ZEND_JIT_ON_HOT_TRACE) {
int32_t exit_point = zend_jit_trace_get_exit_point(opline, ZEND_JIT_EXIT_TO_VM);
const void *exit_addr = zend_jit_trace_get_exit_addr(exit_point);
if (!exit_addr) {
return 0;
}
| jb &exit_addr
} else {
| jb >1
| // EG(vm_stack_top) = (zval*)((char*)call + used_stack);
|.cold_code
|1:
if (func) {
| mov FCARG1d, used_stack
}
#ifdef _WIN32
if (0) {
#else
if (opline->opcode == ZEND_INIT_FCALL && func && func->type == ZEND_INTERNAL_FUNCTION) {
#endif
| SET_EX_OPLINE opline, r0
| EXT_CALL zend_jit_int_extend_stack_helper, r0
} else {
if (!is_closure) {
if (func
&& op_array == &func->op_array
&& (func->op_array.fn_flags & ZEND_ACC_IMMUTABLE)
&& (sizeof(void*) != 8 || IS_SIGNED_32BIT(func))) {
| LOAD_ADDR FCARG2a, func
} else {
| mov FCARG2a, r0
}
} else {
| lea FCARG2a, aword [r0 + offsetof(zend_closure, func)]
}
| SET_EX_OPLINE opline, r0
| EXT_CALL zend_jit_extend_stack_helper, r0
}
| mov RX, r0
| jmp >1
|.code
}
}
if (func) {
| MEM_OP2_1_ZTS add, aword, executor_globals, vm_stack_top, used_stack, r2
} else {
| MEM_OP2_1_ZTS add, aword, executor_globals, vm_stack_top, FCARG1a, r2
}
| // zend_vm_init_call_frame(call, call_info, func, num_args, called_scope, object);
if (JIT_G(trigger) != ZEND_JIT_ON_HOT_TRACE || opline->opcode != ZEND_INIT_METHOD_CALL) {
| // ZEND_SET_CALL_INFO(call, 0, call_info);
| mov dword EX:RX->This.u1.type_info, (IS_UNDEF | ZEND_CALL_NESTED_FUNCTION)
}
#ifdef _WIN32
if (0) {
#else
if (opline->opcode == ZEND_INIT_FCALL && func && func->type == ZEND_INTERNAL_FUNCTION) {
#endif
| // call->func = func;
|1:
| ADDR_OP2_2 mov, aword EX:RX->func, func, r1
} else {
if (!is_closure) {
| // call->func = func;
if (func
&& op_array == &func->op_array
&& (func->op_array.fn_flags & ZEND_ACC_IMMUTABLE)
&& (sizeof(void*) != 8 || IS_SIGNED_32BIT(func))) {
| ADDR_OP2_2 mov, aword EX:RX->func, func, r1
} else {
| mov aword EX:RX->func, r0
}
} else {
| // call->func = &closure->func;
| lea r1, aword [r0 + offsetof(zend_closure, func)]
| mov aword EX:RX->func, r1
}
|1:
}
if (opline->opcode == ZEND_INIT_METHOD_CALL) {
| // Z_PTR(call->This) = obj;
| mov r1, aword T1
| mov aword EX:RX->This.value.ptr, r1
if (opline->op1_type == IS_UNUSED || use_this) {
| // call->call_info |= ZEND_CALL_HAS_THIS;
if (JIT_G(trigger) == ZEND_JIT_ON_HOT_TRACE) {
| mov dword EX:RX->This.u1.type_info, ZEND_CALL_HAS_THIS
} else {
| or dword EX:RX->This.u1.type_info, ZEND_CALL_HAS_THIS
}
} else {
if (opline->op1_type == IS_CV) {
| // GC_ADDREF(obj);
| add dword [r1], 1
}
| // call->call_info |= ZEND_CALL_HAS_THIS | ZEND_CALL_RELEASE_THIS;
if (JIT_G(trigger) == ZEND_JIT_ON_HOT_TRACE) {
| mov dword EX:RX->This.u1.type_info, (ZEND_CALL_HAS_THIS | ZEND_CALL_RELEASE_THIS)
} else {
| or dword EX:RX->This.u1.type_info, (ZEND_CALL_HAS_THIS | ZEND_CALL_RELEASE_THIS)
}
}
} else if (!is_closure) {
| // Z_CE(call->This) = called_scope;
| mov aword EX:RX->This.value.ptr, 0
} else {
if (opline->op2_type == IS_CV) {
| // GC_ADDREF(closure);
| add dword [r0], 1
}
| // object_or_called_scope = closure->called_scope;
| mov r1, aword [r0 + offsetof(zend_closure, called_scope)]
| mov aword EX:RX->This.value.ptr, r1
| // call_info = ZEND_CALL_NESTED_FUNCTION | ZEND_CALL_DYNAMIC | ZEND_CALL_CLOSURE |
| // (closure->func->common.fn_flags & ZEND_ACC_FAKE_CLOSURE);
| mov edx, dword [r0 + offsetof(zend_closure, func.common.fn_flags)]
| and edx, ZEND_ACC_FAKE_CLOSURE
| or edx, (ZEND_CALL_NESTED_FUNCTION | ZEND_CALL_DYNAMIC | ZEND_CALL_CLOSURE)
| // if (Z_TYPE(closure->this_ptr) != IS_UNDEF) {
| cmp byte [r0 + offsetof(zend_closure, this_ptr.u1.v.type)], IS_UNDEF
| jz >1
| // call_info |= ZEND_CALL_HAS_THIS;
| or edx, ZEND_CALL_HAS_THIS
| // object_or_called_scope = Z_OBJ(closure->this_ptr);
| mov r1, aword [r0 + offsetof(zend_closure, this_ptr.value.ptr)]
|1:
| // ZEND_SET_CALL_INFO(call, 0, call_info);
| or dword EX:RX->This.u1.type_info, edx
| // Z_PTR(call->This) = object_or_called_scope;
| mov aword EX:RX->This.value.ptr, r1
| cmp aword [r0 + offsetof(zend_closure, func.op_array.run_time_cache__ptr)], 0
| jnz >1
| lea FCARG1a, aword [r0 + offsetof(zend_closure, func)]
| EXT_CALL zend_jit_init_func_run_time_cache_helper, r0
|1:
}
| // ZEND_CALL_NUM_ARGS(call) = num_args;
| mov dword EX:RX->This.u2.num_args, opline->extended_value
return 1;
}
static int zend_jit_needs_call_chain(zend_call_info *call_info, uint32_t b, const zend_op_array *op_array, zend_ssa *ssa, const zend_ssa_op *ssa_op, const zend_op *opline, int call_level, zend_jit_trace_rec *trace)
{
int skip;
if (trace) {
zend_jit_trace_rec *p = trace;
ssa_op++;
while (1) {
if (p->op == ZEND_JIT_TRACE_VM) {
switch (p->opline->opcode) {
case ZEND_SEND_ARRAY:
case ZEND_SEND_USER:
case ZEND_SEND_UNPACK:
case ZEND_INIT_FCALL:
case ZEND_INIT_METHOD_CALL:
case ZEND_INIT_STATIC_METHOD_CALL:
case ZEND_INIT_FCALL_BY_NAME:
case ZEND_INIT_NS_FCALL_BY_NAME:
case ZEND_INIT_DYNAMIC_CALL:
case ZEND_NEW:
case ZEND_INIT_USER_CALL:
case ZEND_FAST_CALL:
case ZEND_JMP:
case ZEND_JMPZNZ:
case ZEND_JMPZ:
case ZEND_JMPNZ:
case ZEND_JMPZ_EX:
case ZEND_JMPNZ_EX:
case ZEND_FE_RESET_R:
case ZEND_FE_RESET_RW:
case ZEND_JMP_SET:
case ZEND_COALESCE:
case ZEND_JMP_NULL:
case ZEND_ASSERT_CHECK:
case ZEND_CATCH:
case ZEND_DECLARE_ANON_CLASS:
case ZEND_FE_FETCH_R:
case ZEND_FE_FETCH_RW:
return 1;
case ZEND_DO_ICALL:
case ZEND_DO_UCALL:
case ZEND_DO_FCALL_BY_NAME:
case ZEND_DO_FCALL:
return 0;
case ZEND_SEND_VAL:
case ZEND_SEND_VAR:
case ZEND_SEND_VAL_EX:
case ZEND_SEND_VAR_EX:
case ZEND_SEND_FUNC_ARG:
case ZEND_SEND_REF:
case ZEND_SEND_VAR_NO_REF:
case ZEND_SEND_VAR_NO_REF_EX:
/* skip */
break;
default:
if (zend_may_throw(opline, ssa_op, op_array, ssa)) {
return 1;
}
}
ssa_op += zend_jit_trace_op_len(opline);
} else if (p->op == ZEND_JIT_TRACE_ENTER ||
p->op == ZEND_JIT_TRACE_BACK ||
p->op == ZEND_JIT_TRACE_END) {
return 1;
}
p++;
}
}
if (!call_info) {
const zend_op *end = op_array->opcodes + op_array->last;
opline++;
ssa_op++;
skip = (call_level == 1);
while (opline != end) {
if (!skip) {
if (zend_may_throw(opline, ssa_op, op_array, ssa)) {
return 1;
}
}
switch (opline->opcode) {
case ZEND_SEND_VAL:
case ZEND_SEND_VAR:
case ZEND_SEND_VAL_EX:
case ZEND_SEND_VAR_EX:
case ZEND_SEND_FUNC_ARG:
case ZEND_SEND_REF:
case ZEND_SEND_VAR_NO_REF:
case ZEND_SEND_VAR_NO_REF_EX:
skip = 0;
break;
case ZEND_SEND_ARRAY:
case ZEND_SEND_USER:
case ZEND_SEND_UNPACK:
case ZEND_INIT_FCALL:
case ZEND_INIT_METHOD_CALL:
case ZEND_INIT_STATIC_METHOD_CALL:
case ZEND_INIT_FCALL_BY_NAME:
case ZEND_INIT_NS_FCALL_BY_NAME:
case ZEND_INIT_DYNAMIC_CALL:
case ZEND_NEW:
case ZEND_INIT_USER_CALL:
case ZEND_FAST_CALL:
case ZEND_JMP:
case ZEND_JMPZNZ:
case ZEND_JMPZ:
case ZEND_JMPNZ:
case ZEND_JMPZ_EX:
case ZEND_JMPNZ_EX:
case ZEND_FE_RESET_R:
case ZEND_FE_RESET_RW:
case ZEND_JMP_SET:
case ZEND_COALESCE:
case ZEND_JMP_NULL:
case ZEND_ASSERT_CHECK:
case ZEND_CATCH:
case ZEND_DECLARE_ANON_CLASS:
case ZEND_FE_FETCH_R:
case ZEND_FE_FETCH_RW:
return 1;
case ZEND_DO_ICALL:
case ZEND_DO_UCALL:
case ZEND_DO_FCALL_BY_NAME:
case ZEND_DO_FCALL:
end = opline;
if (end - op_array->opcodes >= ssa->cfg.blocks[b].start + ssa->cfg.blocks[b].len) {
/* INIT_FCALL and DO_FCALL in different BasicBlocks */
return 1;
}
return 0;
}
opline++;
ssa_op++;
}
return 1;
} else {
const zend_op *end = call_info->caller_call_opline;
if (end - op_array->opcodes >= ssa->cfg.blocks[b].start + ssa->cfg.blocks[b].len) {
/* INIT_FCALL and DO_FCALL in different BasicBlocks */
return 1;
}
opline++;
ssa_op++;
skip = (call_level == 1);
while (opline != end) {
if (skip) {
switch (opline->opcode) {
case ZEND_SEND_VAL:
case ZEND_SEND_VAR:
case ZEND_SEND_VAL_EX:
case ZEND_SEND_VAR_EX:
case ZEND_SEND_FUNC_ARG:
case ZEND_SEND_REF:
case ZEND_SEND_VAR_NO_REF:
case ZEND_SEND_VAR_NO_REF_EX:
skip = 0;
break;
case ZEND_SEND_ARRAY:
case ZEND_SEND_USER:
case ZEND_SEND_UNPACK:
return 1;
}
} else {
if (zend_may_throw(opline, ssa_op, op_array, ssa)) {
return 1;
}
}
opline++;
ssa_op++;
}
return 0;
}
}
static int zend_jit_init_fcall_guard(dasm_State **Dst, uint32_t level, const zend_function *func, const zend_op *to_opline)
{
int32_t exit_point;
const void *exit_addr;
if (func->type == ZEND_INTERNAL_FUNCTION) {
#ifdef ZEND_WIN32
// TODO: ASLR may cause different addresses in different workers ???
return 0;
#endif
} else if (func->type == ZEND_USER_FUNCTION) {
if (!zend_accel_in_shm(func->op_array.opcodes)) {
/* op_array and op_array->opcodes are not persistent. We can't link. */
return 0;
}
} else {
ZEND_UNREACHABLE();
return 0;
}
exit_point = zend_jit_trace_get_exit_point(to_opline, ZEND_JIT_EXIT_POLYMORPHISM);
exit_addr = zend_jit_trace_get_exit_addr(exit_point);
if (!exit_addr) {
return 0;
}
| // call = EX(call);
| mov r1, EX->call
while (level > 0) {
| mov r1, EX:r1->prev_execute_data
level--;
}
if (func->type == ZEND_USER_FUNCTION &&
(!(func->common.fn_flags & ZEND_ACC_IMMUTABLE) ||
(func->common.fn_flags & ZEND_ACC_CLOSURE) ||
!func->common.function_name)) {
const zend_op *opcodes = func->op_array.opcodes;
| mov r1, aword EX:r1->func
| .if X64
|| if (!IS_SIGNED_32BIT(opcodes)) {
| mov64 r2, ((ptrdiff_t)opcodes)
| cmp aword [r1 + offsetof(zend_op_array, opcodes)], r2
|| } else {
| cmp aword [r1 + offsetof(zend_op_array, opcodes)], opcodes
|| }
| .else
| cmp aword [r1 + offsetof(zend_op_array, opcodes)], opcodes
| .endif
| jne &exit_addr
} else {
| .if X64
|| if (!IS_SIGNED_32BIT(func)) {
| mov64 r2, ((ptrdiff_t)func)
| cmp aword EX:r1->func, r2
|| } else {
| cmp aword EX:r1->func, func
|| }
| .else
| cmp aword EX:r1->func, func
| .endif
| jne &exit_addr
}
return 1;
}
static int zend_jit_init_fcall(dasm_State **Dst, const zend_op *opline, uint32_t b, const zend_op_array *op_array, zend_ssa *ssa, const zend_ssa_op *ssa_op, int call_level, zend_jit_trace_rec *trace, zend_bool stack_check)
{
zend_func_info *info = ZEND_FUNC_INFO(op_array);
zend_call_info *call_info = NULL;
zend_function *func = NULL;
if (delayed_call_chain) {
if (!zend_jit_save_call_chain(Dst, delayed_call_level)) {
return 0;
}
}
if (info) {
call_info = info->callee_info;
while (call_info && call_info->caller_init_opline != opline) {
call_info = call_info->next_callee;
}
if (call_info && call_info->callee_func) {
func = call_info->callee_func;
}
}
if (!func
&& trace
&& trace->op == ZEND_JIT_TRACE_INIT_CALL) {
#ifdef _WIN32
/* ASLR */
if (trace->func->type != ZEND_INTERNAL_FUNCTION) {
func = (zend_function*)trace->func;
}
#else
func = (zend_function*)trace->func;
#endif
}
#ifdef _WIN32
if (0) {
#else
if (opline->opcode == ZEND_INIT_FCALL
&& func
&& func->type == ZEND_INTERNAL_FUNCTION) {
#endif
/* load constant address later */
} else if (func && op_array == &func->op_array) {
/* recursive call */
if (!(func->op_array.fn_flags & ZEND_ACC_IMMUTABLE) ||
(sizeof(void*) == 8 && !IS_SIGNED_32BIT(func))) {
| mov r0, EX->func
}
} else {
| // if (CACHED_PTR(opline->result.num))
| mov r0, EX->run_time_cache
| mov r0, aword [r0 + opline->result.num]
| test r0, r0
| jz >1
|.cold_code
|1:
if (opline->opcode == ZEND_INIT_FCALL
&& func
&& func->type == ZEND_USER_FUNCTION
&& (func->op_array.fn_flags & ZEND_ACC_IMMUTABLE)) {
| LOAD_ADDR FCARG1a, func
| EXT_CALL zend_jit_init_func_run_time_cache_helper, r0
| mov r1, EX->run_time_cache
| mov aword [r1 + opline->result.num], r0
| jmp >3
} else {
zval *zv = RT_CONSTANT(opline, opline->op2);
if (opline->opcode == ZEND_INIT_FCALL) {
| LOAD_ADDR FCARG1a, Z_STR_P(zv);
| EXT_CALL zend_jit_find_func_helper, r0
} else if (opline->opcode == ZEND_INIT_FCALL_BY_NAME) {
| LOAD_ADDR FCARG1a, Z_STR_P(zv + 1);
| EXT_CALL zend_jit_find_func_helper, r0
} else if (opline->opcode == ZEND_INIT_NS_FCALL_BY_NAME) {
| LOAD_ADDR FCARG1a, zv;
| EXT_CALL zend_jit_find_ns_func_helper, r0
} else {
ZEND_UNREACHABLE();
}
| // CACHE_PTR(opline->result.num, fbc);
| mov r1, EX->run_time_cache
| mov aword [r1 + opline->result.num], r0
if (JIT_G(trigger) == ZEND_JIT_ON_HOT_TRACE) {
int32_t exit_point = zend_jit_trace_get_exit_point(opline, 0);
const void *exit_addr = zend_jit_trace_get_exit_addr(exit_point);
if (!exit_addr) {
return 0;
}
if (!func || opline->opcode == ZEND_INIT_FCALL) {
| test r0, r0
| jnz >3
} else if (func->type == ZEND_USER_FUNCTION
&& !(func->common.fn_flags & ZEND_ACC_IMMUTABLE)) {
const zend_op *opcodes = func->op_array.opcodes;
| .if X64
|| if (!IS_SIGNED_32BIT(opcodes)) {
| mov64 r1, ((ptrdiff_t)opcodes)
| cmp aword [r0 + offsetof(zend_op_array, opcodes)], r1
|| } else {
| cmp aword [r0 + offsetof(zend_op_array, opcodes)], opcodes
|| }
| .else
| cmp aword [r0 + offsetof(zend_op_array, opcodes)], opcodes
| .endif
| jz >3
} else {
| .if X64
|| if (!IS_SIGNED_32BIT(func)) {
| mov64 r1, ((ptrdiff_t)func)
| cmp r0, r1
|| } else {
| cmp r0, func
|| }
| .else
| cmp r0, func
| .endif
| jz >3
}
| jmp &exit_addr
} else {
| test r0, r0
| jnz >3
| // SAVE_OPLINE();
| SET_EX_OPLINE opline, r0
| jmp ->undefined_function
}
}
|.code
|3:
}
if (!zend_jit_push_call_frame(Dst, opline, op_array, func, 0, 0, stack_check)) {
return 0;
}
if (zend_jit_needs_call_chain(call_info, b, op_array, ssa, ssa_op, opline, call_level, trace)) {
if (!zend_jit_save_call_chain(Dst, call_level)) {
return 0;
}
} else {
delayed_call_chain = 1;
delayed_call_level = call_level;
}
return 1;
}
static int zend_jit_init_method_call(dasm_State **Dst,
const zend_op *opline,
uint32_t b,
const zend_op_array *op_array,
zend_ssa *ssa,
const zend_ssa_op *ssa_op,
int call_level,
uint32_t op1_info,
zend_jit_addr op1_addr,
zend_class_entry *ce,
zend_bool ce_is_instanceof,
zend_bool use_this,
zend_class_entry *trace_ce,
zend_jit_trace_rec *trace,
zend_bool stack_check,
zend_bool polymorphic_side_trace)
{
zend_func_info *info = ZEND_FUNC_INFO(op_array);
zend_call_info *call_info = NULL;
zend_function *func = NULL;
zval *function_name;
ZEND_ASSERT(opline->op2_type == IS_CONST);
ZEND_ASSERT(op1_info & MAY_BE_OBJECT);
function_name = RT_CONSTANT(opline, opline->op2);
if (info) {
call_info = info->callee_info;
while (call_info && call_info->caller_init_opline != opline) {
call_info = call_info->next_callee;
}
if (call_info && call_info->callee_func) {
func = call_info->callee_func;
}
}
if (polymorphic_side_trace) {
/* function is passed in r0 from parent_trace */
} else {
if (opline->op1_type == IS_UNUSED || use_this) {
zend_jit_addr this_addr = ZEND_ADDR_MEM_ZVAL(ZREG_FP, offsetof(zend_execute_data, This));
| GET_ZVAL_PTR FCARG1a, this_addr
} else {
if (op1_info & MAY_BE_REF) {
if (opline->op1_type == IS_CV) {
if (Z_REG(op1_addr) != ZREG_FCARG1a || Z_OFFSET(op1_addr) != 0) {
| LOAD_ZVAL_ADDR FCARG1a, op1_addr
}
| ZVAL_DEREF FCARG1a, op1_info
op1_addr = ZEND_ADDR_MEM_ZVAL(ZREG_FCARG1a, 0);
} else {
/* Hack: Convert reference to regular value to simplify JIT code */
ZEND_ASSERT(Z_REG(op1_addr) == ZREG_FP);
| IF_NOT_ZVAL_TYPE op1_addr, IS_REFERENCE, >1
| LOAD_ZVAL_ADDR FCARG1a, op1_addr
| EXT_CALL zend_jit_unref_helper, r0
|1:
}
}
if (op1_info & ((MAY_BE_UNDEF|MAY_BE_ANY)- MAY_BE_OBJECT)) {
if (JIT_G(trigger) == ZEND_JIT_ON_HOT_TRACE) {
int32_t exit_point = zend_jit_trace_get_exit_point(opline, ZEND_JIT_EXIT_TO_VM);
const void *exit_addr = zend_jit_trace_get_exit_addr(exit_point);
if (!exit_addr) {
return 0;
}
| IF_NOT_ZVAL_TYPE op1_addr, IS_OBJECT, &exit_addr
} else {
| IF_NOT_ZVAL_TYPE op1_addr, IS_OBJECT, >1
|.cold_code
|1:
if (Z_REG(op1_addr) != ZREG_FCARG1a || Z_OFFSET(op1_addr) != 0) {
| LOAD_ZVAL_ADDR FCARG1a, op1_addr
}
| SET_EX_OPLINE opline, r0
if ((opline->op1_type & (IS_VAR|IS_TMP_VAR)) && !use_this) {
| EXT_CALL zend_jit_invalid_method_call_tmp, r0
} else {
| EXT_CALL zend_jit_invalid_method_call, r0
}
| jmp ->exception_handler
|.code
}
}
| GET_ZVAL_PTR FCARG1a, op1_addr
}
if (delayed_call_chain) {
if (!zend_jit_save_call_chain(Dst, delayed_call_level)) {
return 0;
}
}
| mov aword T1, FCARG1a // save
if (func) {
| // fbc = CACHED_PTR(opline->result.num + sizeof(void*));
| mov r0, EX->run_time_cache
| mov r0, aword [r0 + opline->result.num + sizeof(void*)]
| test r0, r0
| jz >1
} else {
| // if (CACHED_PTR(opline->result.num) == obj->ce)) {
| mov r0, EX->run_time_cache
| mov r2, aword [r0 + opline->result.num]
| cmp r2, [FCARG1a + offsetof(zend_object, ce)]
| jnz >1
| // fbc = CACHED_PTR(opline->result.num + sizeof(void*));
| mov r0, aword [r0 + opline->result.num + sizeof(void*)]
}
|.cold_code
|1:
| LOAD_ADDR FCARG2a, function_name
|.if X64
| lea CARG3, aword T1
|.else
| lea r0, aword T1
| sub r4, 12
| push r0
|.endif
| SET_EX_OPLINE opline, r0
if ((opline->op1_type & (IS_VAR|IS_TMP_VAR)) && !use_this) {
| EXT_CALL zend_jit_find_method_tmp_helper, r0
} else {
| EXT_CALL zend_jit_find_method_helper, r0
}
|.if not(X64)
| add r4, 12
|.endif
| test r0, r0
| jnz >2
| jmp ->exception_handler
|.code
|2:
}
if (!func
&& trace
&& trace->op == ZEND_JIT_TRACE_INIT_CALL
&& trace->func
#ifdef _WIN32
&& trace->func->type != ZEND_INTERNAL_FUNCTION
#endif
) {
int32_t exit_point;
const void *exit_addr;
exit_point = zend_jit_trace_get_exit_point(opline, ZEND_JIT_EXIT_METHOD_CALL);
exit_addr = zend_jit_trace_get_exit_addr(exit_point);
if (!exit_addr) {
return 0;
}
func = (zend_function*)trace->func;
if (func->type == ZEND_USER_FUNCTION &&
(!(func->common.fn_flags & ZEND_ACC_IMMUTABLE) ||
(func->common.fn_flags & ZEND_ACC_CLOSURE) ||
!func->common.function_name)) {
const zend_op *opcodes = func->op_array.opcodes;
| .if X64
|| if (!IS_SIGNED_32BIT(opcodes)) {
| mov64 r1, ((ptrdiff_t)opcodes)
| cmp aword [r0 + offsetof(zend_op_array, opcodes)], r1
|| } else {
| cmp aword [r0 + offsetof(zend_op_array, opcodes)], opcodes
|| }
| .else
| cmp aword [r0 + offsetof(zend_op_array, opcodes)], opcodes
| .endif
| jne &exit_addr
} else {
| .if X64
|| if (!IS_SIGNED_32BIT(func)) {
| mov64 r1, ((ptrdiff_t)func)
| cmp r0, r1
|| } else {
| cmp r0, func
|| }
| .else
| cmp r0, func
| .endif
| jne &exit_addr
}
}
if (!func) {
| // if (fbc->common.fn_flags & ZEND_ACC_STATIC) {
| test dword [r0 + offsetof(zend_function, common.fn_flags)], ZEND_ACC_STATIC
| jnz >1
|.cold_code
|1:
}
if (!func || (func->common.fn_flags & ZEND_ACC_STATIC) != 0) {
| mov FCARG1a, aword T1 // restore
| mov FCARG2a, r0
|.if X64
| mov CARG3d, opline->extended_value
|.else
| sub r4, 12
| push opline->extended_value
|.endif
if ((opline->op1_type & (IS_VAR|IS_TMP_VAR)) && !use_this) {
| EXT_CALL zend_jit_push_static_metod_call_frame_tmp, r0
} else {
| EXT_CALL zend_jit_push_static_metod_call_frame, r0
}
|.if not(X64)
| add r4, 12
|.endif
if ((opline->op1_type & (IS_VAR|IS_TMP_VAR) && !use_this)) {
| test r0, r0
| jz ->exception_handler
}
| mov RX, r0
}
if (!func) {
| jmp >9
|.code
}
if (!func || (func->common.fn_flags & ZEND_ACC_STATIC) == 0) {
if (!zend_jit_push_call_frame(Dst, opline, NULL, func, 0, use_this, stack_check)) {
return 0;
}
}
if (!func) {
|9:
}
zend_jit_start_reuse_ip();
if (zend_jit_needs_call_chain(call_info, b, op_array, ssa, ssa_op, opline, call_level, trace)) {
if (!zend_jit_save_call_chain(Dst, call_level)) {
return 0;
}
} else {
delayed_call_chain = 1;
delayed_call_level = call_level;
}
return 1;
}
static int zend_jit_init_closure_call(dasm_State **Dst,
const zend_op *opline,
uint32_t b,
const zend_op_array *op_array,
zend_ssa *ssa,
const zend_ssa_op *ssa_op,
int call_level,
zend_jit_trace_rec *trace,
zend_bool stack_check)
{
zend_function *func = NULL;
zend_jit_addr op2_addr = ZEND_ADDR_MEM_ZVAL(ZREG_FP, opline->op2.var);
| GET_ZVAL_PTR r0, op2_addr
if (ssa->var_info[ssa_op->op2_use].ce != zend_ce_closure
&& !(ssa->var_info[ssa_op->op2_use].type & MAY_BE_CLASS_GUARD)) {
int32_t exit_point = zend_jit_trace_get_exit_point(opline, ZEND_JIT_EXIT_TO_VM);
const void *exit_addr = zend_jit_trace_get_exit_addr(exit_point);
if (!exit_addr) {
return 0;
}
|.if X64
|| if (!IS_SIGNED_32BIT(zend_ce_closure)) {
| mov64 FCARG1a, ((ptrdiff_t)zend_ce_closure)
| cmp aword [r0 + offsetof(zend_object, ce)], FCARG1a
|| } else {
| cmp aword [r0 + offsetof(zend_object, ce)], zend_ce_closure
|| }
|.else
| cmp aword [r0 + offsetof(zend_object, ce)], zend_ce_closure
|.endif
| jne &exit_addr
if (ssa->var_info && ssa_op->op2_use >= 0) {
ssa->var_info[ssa_op->op2_use].type |= MAY_BE_CLASS_GUARD;
ssa->var_info[ssa_op->op2_use].ce = zend_ce_closure;
ssa->var_info[ssa_op->op2_use].is_instanceof = 0;
}
}
if (trace
&& trace->op == ZEND_JIT_TRACE_INIT_CALL
&& trace->func
&& trace->func->type == ZEND_USER_FUNCTION) {
const zend_op *opcodes;
int32_t exit_point;
const void *exit_addr;
func = (zend_function*)trace->func;
opcodes = func->op_array.opcodes;
exit_point = zend_jit_trace_get_exit_point(opline, ZEND_JIT_EXIT_CLOSURE_CALL);
exit_addr = zend_jit_trace_get_exit_addr(exit_point);
if (!exit_addr) {
return 0;
}
| .if X64
|| if (!IS_SIGNED_32BIT(opcodes)) {
| mov64 FCARG1a, ((ptrdiff_t)opcodes)
| cmp aword [r0 + offsetof(zend_closure, func.op_array.opcodes)], FCARG1a
|| } else {
| cmp aword [r0 + offsetof(zend_closure, func.op_array.opcodes)], opcodes
|| }
| .else
| cmp aword [r0 + offsetof(zend_closure, func.op_array.opcodes)], opcodes
| .endif
| jne &exit_addr
}
if (delayed_call_chain) {
if (!zend_jit_save_call_chain(Dst, delayed_call_level)) {
return 0;
}
}
if (!zend_jit_push_call_frame(Dst, opline, NULL, func, 1, 0, stack_check)) {
return 0;
}
if (zend_jit_needs_call_chain(NULL, b, op_array, ssa, ssa_op, opline, call_level, trace)) {
if (!zend_jit_save_call_chain(Dst, call_level)) {
return 0;
}
} else {
delayed_call_chain = 1;
delayed_call_level = call_level;
}
if (trace
&& trace->op == ZEND_JIT_TRACE_END
&& trace->stop == ZEND_JIT_TRACE_STOP_INTERPRETER) {
if (!zend_jit_set_valid_ip(Dst, opline + 1)) {
return 0;
}
}
return 1;
}
static uint32_t skip_valid_arguments(const zend_op_array *op_array, zend_ssa *ssa, const zend_call_info *call_info)
{
uint32_t num_args = 0;
zend_function *func = call_info->callee_func;
while (num_args < call_info->num_args) {
zend_arg_info *arg_info = func->op_array.arg_info + num_args;
if (ZEND_TYPE_IS_SET(arg_info->type)) {
if (ZEND_TYPE_IS_ONLY_MASK(arg_info->type)) {
zend_op *opline = call_info->arg_info[num_args].opline;
zend_ssa_op *ssa_op = &ssa->ops[opline - op_array->opcodes];
uint32_t type_mask = ZEND_TYPE_PURE_MASK(arg_info->type);
if ((OP1_INFO() & (MAY_BE_ANY|MAY_BE_UNDEF)) & ~type_mask) {
break;
}
} else {
break;
}
}
num_args++;
}
return num_args;
}
static int zend_jit_do_fcall(dasm_State **Dst, const zend_op *opline, const zend_op_array *op_array, zend_ssa *ssa, int call_level, unsigned int next_block, zend_jit_trace_rec *trace)
{
zend_func_info *info = ZEND_FUNC_INFO(op_array);
zend_call_info *call_info = NULL;
const zend_function *func = NULL;
uint32_t i;
zend_jit_addr res_addr;
uint32_t call_num_args = 0;
zend_bool unknown_num_args = 0;
const void *exit_addr = NULL;
const zend_op *prev_opline;
if (RETURN_VALUE_USED(opline)) {
res_addr = ZEND_ADDR_MEM_ZVAL(ZREG_FP, opline->result.var);
} else {
/* CPU stack allocated temporary zval */
res_addr = ZEND_ADDR_MEM_ZVAL(ZREG_R4, TMP_ZVAL_OFFSET);
}
prev_opline = opline - 1;
while (prev_opline->opcode == ZEND_EXT_FCALL_BEGIN || prev_opline->opcode == ZEND_TICKS) {
prev_opline--;
}
if (prev_opline->opcode == ZEND_SEND_UNPACK || prev_opline->opcode == ZEND_SEND_ARRAY ||
prev_opline->opcode == ZEND_CHECK_UNDEF_ARGS) {
unknown_num_args = 1;
}
if (info) {
call_info = info->callee_info;
while (call_info && call_info->caller_call_opline != opline) {
call_info = call_info->next_callee;
}
if (call_info && call_info->callee_func) {
func = call_info->callee_func;
}
if ((op_array->fn_flags & ZEND_ACC_TRAIT_CLONE)
&& JIT_G(current_frame)
&& JIT_G(current_frame)->call
&& !JIT_G(current_frame)->call->func) {
call_info = NULL; func = NULL; /* megamorphic call from trait */
}
}
if (!func) {
/* resolve function at run time */
} else if (func->type == ZEND_USER_FUNCTION) {
ZEND_ASSERT(opline->opcode != ZEND_DO_ICALL);
call_num_args = call_info->num_args;
} else if (func->type == ZEND_INTERNAL_FUNCTION) {
ZEND_ASSERT(opline->opcode != ZEND_DO_UCALL);
call_num_args = call_info->num_args;
} else {
ZEND_UNREACHABLE();
}
if (trace && !func) {
if (trace->op == ZEND_JIT_TRACE_DO_ICALL) {
ZEND_ASSERT(trace->func->type == ZEND_INTERNAL_FUNCTION);
#ifndef ZEND_WIN32
// TODO: ASLR may cause different addresses in different workers ???
func = trace->func;
if (JIT_G(current_frame) &&
JIT_G(current_frame)->call &&
TRACE_FRAME_NUM_ARGS(JIT_G(current_frame)->call) >= 0) {
call_num_args = TRACE_FRAME_NUM_ARGS(JIT_G(current_frame)->call);
} else {
unknown_num_args = 1;
}
#endif
} else if (trace->op == ZEND_JIT_TRACE_ENTER) {
ZEND_ASSERT(trace->func->type == ZEND_USER_FUNCTION);
if (zend_accel_in_shm(trace->func->op_array.opcodes)) {
func = trace->func;
if (JIT_G(current_frame) &&
JIT_G(current_frame)->call &&
TRACE_FRAME_NUM_ARGS(JIT_G(current_frame)->call) >= 0) {
call_num_args = TRACE_FRAME_NUM_ARGS(JIT_G(current_frame)->call);
} else {
unknown_num_args = 1;
}
}
}
}
bool may_have_extra_named_params =
opline->extended_value == ZEND_FCALL_MAY_HAVE_EXTRA_NAMED_PARAMS &&
(!func || func->common.fn_flags & ZEND_ACC_VARIADIC);
if (!reuse_ip) {
zend_jit_start_reuse_ip();
| // call = EX(call);
| mov RX, EX->call
}
zend_jit_stop_reuse_ip();
| // fbc = call->func;
| // mov r2, EX:RX->func ???
| // SAVE_OPLINE();
| SET_EX_OPLINE opline, r0
if (opline->opcode == ZEND_DO_FCALL) {
if (!func) {
if (trace) {
uint32_t exit_point = zend_jit_trace_get_exit_point(opline, ZEND_JIT_EXIT_TO_VM);
exit_addr = zend_jit_trace_get_exit_addr(exit_point);
if (!exit_addr) {
return 0;
}
| mov r0, EX:RX->func
| test dword [r0 + offsetof(zend_op_array, fn_flags)], ZEND_ACC_DEPRECATED
| jnz &exit_addr
}
}
}
if (!delayed_call_chain) {
if (call_level == 1) {
| mov aword EX->call, 0
} else {
| //EX(call) = call->prev_execute_data;
| mov r0, EX:RX->prev_execute_data
| mov EX->call, r0
}
}
delayed_call_chain = 0;
| //call->prev_execute_data = execute_data;
| mov EX:RX->prev_execute_data, EX
if (!func) {
| mov r0, EX:RX->func
}
if (opline->opcode == ZEND_DO_FCALL) {
if (!func) {
if (!trace) {
| test dword [r0 + offsetof(zend_op_array, fn_flags)], ZEND_ACC_DEPRECATED
| jnz >1
|.cold_code
|1:
if (!GCC_GLOBAL_REGS) {
| mov FCARG1a, RX
}
| EXT_CALL zend_jit_deprecated_helper, r0
| test al, al
| mov r0, EX:RX->func // reload
| jne >1
| jmp ->exception_handler
|.code
|1:
}
} else if (func->common.fn_flags & ZEND_ACC_DEPRECATED) {
if (!GCC_GLOBAL_REGS) {
| mov FCARG1a, RX
}
| EXT_CALL zend_jit_deprecated_helper, r0
| test al, al
| je ->exception_handler
}
}
if (!func
&& opline->opcode != ZEND_DO_UCALL
&& opline->opcode != ZEND_DO_ICALL) {
| cmp byte [r0 + offsetof(zend_function, type)], ZEND_USER_FUNCTION
| jne >8
}
if ((!func || func->type == ZEND_USER_FUNCTION)
&& opline->opcode != ZEND_DO_ICALL) {
| // EX(call) = NULL;
| mov aword EX:RX->call, 0
if (RETURN_VALUE_USED(opline)) {
| // EX(return_value) = EX_VAR(opline->result.var);
| LOAD_ZVAL_ADDR r2, res_addr
| mov aword EX:RX->return_value, r2
} else {
| // EX(return_value) = 0;
| mov aword EX:RX->return_value, 0
}
//EX_LOAD_RUN_TIME_CACHE(op_array);
if (!func || func->op_array.cache_size) {
if (func && op_array == &func->op_array) {
/* recursive call */
if (trace || func->op_array.cache_size > sizeof(void*)) {
| mov r2, EX->run_time_cache
| mov EX:RX->run_time_cache, r2
}
} else {
if (func) {
| mov r0, EX:RX->func
}
| mov r2, aword [r0 + offsetof(zend_op_array, run_time_cache__ptr)]
#if ZEND_MAP_PTR_KIND == ZEND_MAP_PTR_KIND_PTR
| mov r2, aword [r2]
#elif ZEND_MAP_PTR_KIND == ZEND_MAP_PTR_KIND_PTR_OR_OFFSET
if (func && !(func->op_array.fn_flags & ZEND_ACC_CLOSURE)) {
if (ZEND_MAP_PTR_IS_OFFSET(func->op_array.run_time_cache)) {
| MEM_OP2_2_ZTS add, r2, aword, compiler_globals, map_ptr_base, r1
} else if (!zend_accel_in_shm(func->op_array.opcodes)) {
/* the called op_array may be not persisted yet */
| test r2, 1
| jz >1
| MEM_OP2_2_ZTS add, r2, aword, compiler_globals, map_ptr_base, r1
|1:
}
| mov r2, aword [r2]
} else {
| test r2, 1
| jz >1
| MEM_OP2_2_ZTS add, r2, aword, compiler_globals, map_ptr_base, r1
|1:
| mov r2, aword [r2]
}
#else
# error "Unknown ZEND_MAP_PTR_KIND"
#endif
| mov EX:RX->run_time_cache, r2
}
}
| // EG(current_execute_data) = execute_data;
| MEM_OP2_1_ZTS mov, aword, executor_globals, current_execute_data, RX, r1
| mov FP, RX
| // opline = op_array->opcodes;
if (func && !unknown_num_args) {
for (i = call_num_args; i < func->op_array.last_var; i++) {
uint32_t n = EX_NUM_TO_VAR(i);
| SET_Z_TYPE_INFO RX + n, IS_UNDEF
}
if (call_num_args <= func->op_array.num_args) {
if (!trace || (trace->op == ZEND_JIT_TRACE_END
&& trace->stop == ZEND_JIT_TRACE_STOP_INTERPRETER)) {
uint32_t num_args;
if ((func->op_array.fn_flags & ZEND_ACC_HAS_TYPE_HINTS) != 0) {
if (trace) {
num_args = 0;
} else if (call_info) {
num_args = skip_valid_arguments(op_array, ssa, call_info);
} else {
num_args = call_num_args;
}
} else {
num_args = call_num_args;
}
if (zend_accel_in_shm(func->op_array.opcodes)) {
| LOAD_IP_ADDR (func->op_array.opcodes + num_args)
} else {
| mov r0, EX->func
if (GCC_GLOBAL_REGS) {
| mov IP, aword [r0 + offsetof(zend_op_array, opcodes)]
if (num_args) {
| add IP, (num_args * sizeof(zend_op))
}
} else {
| mov FCARG1a, aword [r0 + offsetof(zend_op_array, opcodes)]
if (num_args) {
| add FCARG1a, (num_args * sizeof(zend_op))
}
| mov aword EX->opline, FCARG1a
}
}
if (GCC_GLOBAL_REGS && !trace && op_array == &func->op_array
&& num_args >= op_array->required_num_args) {
/* recursive call */
if (ZEND_OBSERVER_ENABLED) {
| SAVE_IP
| mov FCARG1a, FP
| EXT_CALL zend_observer_fcall_begin, r0
}
#ifdef CONTEXT_THREADED_JIT
| call >1
|.cold_code
|1:
| pop r0
| jmp =>num_args
|.code
#else
| jmp =>num_args
#endif
return 1;
}
}
} else {
if (!trace || (trace->op == ZEND_JIT_TRACE_END
&& trace->stop == ZEND_JIT_TRACE_STOP_INTERPRETER)) {
if (func && zend_accel_in_shm(func->op_array.opcodes)) {
| LOAD_IP_ADDR (func->op_array.opcodes)
} else if (GCC_GLOBAL_REGS) {
| mov IP, aword [r0 + offsetof(zend_op_array, opcodes)]
} else {
| mov FCARG1a, aword [r0 + offsetof(zend_op_array, opcodes)]
| mov aword EX->opline, FCARG1a
}
}
if (!GCC_GLOBAL_REGS) {
| mov FCARG1a, FP
}
| EXT_CALL zend_jit_copy_extra_args_helper, r0
}
} else {
| // opline = op_array->opcodes
if (func && zend_accel_in_shm(func->op_array.opcodes)) {
| LOAD_IP_ADDR (func->op_array.opcodes)
} else if (GCC_GLOBAL_REGS) {
| mov IP, aword [r0 + offsetof(zend_op_array, opcodes)]
} else {
| mov FCARG1a, aword [r0 + offsetof(zend_op_array, opcodes)]
| mov aword EX->opline, FCARG1a
}
if (func) {
| // num_args = EX_NUM_ARGS();
| mov ecx, dword [FP + offsetof(zend_execute_data, This.u2.num_args)]
| // if (UNEXPECTED(num_args > first_extra_arg))
| cmp ecx, (func->op_array.num_args)
} else {
| // first_extra_arg = op_array->num_args;
| mov edx, dword [r0 + offsetof(zend_op_array, num_args)]
| // num_args = EX_NUM_ARGS();
| mov ecx, dword [FP + offsetof(zend_execute_data, This.u2.num_args)]
| // if (UNEXPECTED(num_args > first_extra_arg))
| cmp ecx, edx
}
| jg >1
|.cold_code
|1:
if (!GCC_GLOBAL_REGS) {
| mov FCARG1a, FP
}
| EXT_CALL zend_jit_copy_extra_args_helper, r0
if (!func) {
| mov r0, EX->func // reload
}
| mov ecx, dword [FP + offsetof(zend_execute_data, This.u2.num_args)] // reload
| jmp >1
|.code
if (!func || (func->op_array.fn_flags & ZEND_ACC_HAS_TYPE_HINTS) == 0) {
if (!func) {
| // if (EXPECTED((op_array->fn_flags & ZEND_ACC_HAS_TYPE_HINTS) == 0))
| test dword [r0 + offsetof(zend_op_array, fn_flags)], ZEND_ACC_HAS_TYPE_HINTS
| jnz >1
}
| // opline += num_args;
|.if X64
|| ZEND_ASSERT(sizeof(zend_op) == 32);
| mov edx, ecx
| shl r2, 5
|.else
| imul r2, ecx, sizeof(zend_op)
|.endif
| ADD_IP r2
}
|1:
| // if (EXPECTED((int)num_args < op_array->last_var)) {
if (func) {
| mov edx, (func->op_array.last_var)
} else {
| mov edx, dword [r0 + offsetof(zend_op_array, last_var)]
}
| sub edx, ecx
| jle >3 //???
| // zval *var = EX_VAR_NUM(num_args);
// |.if X64
// | movsxd r1, ecx
// |.endif
| shl r1, 4
| lea r1, [FP + r1 + (ZEND_CALL_FRAME_SLOT * sizeof(zval))]
|2:
| SET_Z_TYPE_INFO r1, IS_UNDEF
| sub edx, 1
| lea r1, [r1 + 16]
| jne <2
|3:
}
if (ZEND_OBSERVER_ENABLED) {
| SAVE_IP
| mov FCARG1a, FP
| EXT_CALL zend_observer_fcall_begin, r0
}
if (trace) {
if (!func && (opline->opcode != ZEND_DO_UCALL)) {
| jmp >9
}
} else {
#ifdef CONTEXT_THREADED_JIT
| call ->context_threaded_call
if (!func && (opline->opcode != ZEND_DO_UCALL)) {
| jmp >9
}
| call ->context_threaded_call
if (!func) {
| jmp >9
}
#else
if (zend_jit_vm_kind == ZEND_VM_KIND_HYBRID) {
| ADD_HYBRID_SPAD
| JMP_IP
} else if (GCC_GLOBAL_REGS) {
| add r4, SPAD // stack alignment
| JMP_IP
} else {
| mov FP, aword T2 // restore FP
| mov RX, aword T3 // restore IP
| add r4, NR_SPAD // stack alignment
| mov r0, 1 // ZEND_VM_ENTER
| ret
}
}
#endif
}
if ((!func || func->type == ZEND_INTERNAL_FUNCTION)
&& (opline->opcode != ZEND_DO_UCALL)) {
if (!func && (opline->opcode != ZEND_DO_ICALL)) {
|8:
}
if (opline->opcode == ZEND_DO_FCALL_BY_NAME) {
if (!func) {
if (trace) {
uint32_t exit_point = zend_jit_trace_get_exit_point(opline, ZEND_JIT_EXIT_TO_VM);
exit_addr = zend_jit_trace_get_exit_addr(exit_point);
if (!exit_addr) {
return 0;
}
| test dword [r0 + offsetof(zend_op_array, fn_flags)], ZEND_ACC_DEPRECATED
| jnz &exit_addr
} else {
| test dword [r0 + offsetof(zend_op_array, fn_flags)], ZEND_ACC_DEPRECATED
| jnz >1
|.cold_code
|1:
if (!GCC_GLOBAL_REGS) {
| mov FCARG1a, RX
}
| EXT_CALL zend_jit_deprecated_helper, r0
| test al, al
| mov r0, EX:RX->func // reload
| jne >1
| jmp ->exception_handler
|.code
|1:
}
} else if (func->common.fn_flags & ZEND_ACC_DEPRECATED) {
if (!GCC_GLOBAL_REGS) {
| mov FCARG1a, RX
}
| EXT_CALL zend_jit_deprecated_helper, r0
| test al, al
| je ->exception_handler
| mov r0, EX:RX->func // reload
}
}
| // ZVAL_NULL(EX_VAR(opline->result.var));
| LOAD_ZVAL_ADDR FCARG2a, res_addr
| SET_Z_TYPE_INFO FCARG2a, IS_NULL
| // EG(current_execute_data) = execute_data;
| MEM_OP2_1_ZTS mov, aword, executor_globals, current_execute_data, RX, r1
zend_jit_reset_last_valid_opline();
| // fbc->internal_function.handler(call, ret);
| mov FCARG1a, RX
if (func) {
| EXT_CALL func->internal_function.handler, r0
} else {
| call aword [r0 + offsetof(zend_internal_function, handler)]
}
| // EG(current_execute_data) = execute_data;
| MEM_OP2_1_ZTS mov, aword, executor_globals, current_execute_data, FP, r0
| // zend_vm_stack_free_args(call);
if (func && !unknown_num_args) {
for (i = 0; i < call_num_args; i++ ) {
uint32_t offset = EX_NUM_TO_VAR(i);
| ZVAL_PTR_DTOR ZEND_ADDR_MEM_ZVAL(ZREG_RX, offset), MAY_BE_ANY|MAY_BE_RC1|MAY_BE_RCN, 0, 1, opline
}
} else {
| mov FCARG1a, RX
| EXT_CALL zend_jit_vm_stack_free_args_helper, r0
}
if (may_have_extra_named_params) {
| test byte [RX + offsetof(zend_execute_data, This.u1.type_info) + 3], (ZEND_CALL_HAS_EXTRA_NAMED_PARAMS >> 24)
| jnz >1
|.cold_code
|1:
| mov FCARG1a, aword [RX + offsetof(zend_execute_data, extra_named_params)]
| EXT_CALL zend_free_extra_named_params, r0
| jmp >2
|.code
|2:
}
|8:
if (opline->opcode == ZEND_DO_FCALL) {
// TODO: optimize ???
| // if (UNEXPECTED(ZEND_CALL_INFO(call) & ZEND_CALL_RELEASE_THIS))
| test byte [RX + offsetof(zend_execute_data, This.u1.type_info) + 2], (ZEND_CALL_RELEASE_THIS >> 16)
| jnz >1
|.cold_code
|1:
| GET_Z_PTR FCARG1a, RX + offsetof(zend_execute_data, This)
| // OBJ_RELEASE(object);
| OBJ_RELEASE ZREG_FCARG1a, >2
| jmp >2
|.code
|2:
}
if (JIT_G(trigger) != ZEND_JIT_ON_HOT_TRACE ||
!JIT_G(current_frame) ||
!JIT_G(current_frame)->call ||
!TRACE_FRAME_IS_NESTED(JIT_G(current_frame)->call) ||
prev_opline->opcode == ZEND_SEND_UNPACK ||
prev_opline->opcode == ZEND_SEND_ARRAY ||
prev_opline->opcode == ZEND_CHECK_UNDEF_ARGS) {
| // zend_vm_stack_free_call_frame(call);
| test byte [RX + offsetof(zend_execute_data, This.u1.type_info) + 2], (ZEND_CALL_ALLOCATED >> 16)
| jnz >1
|.cold_code
|1:
| mov FCARG1a, RX
| EXT_CALL zend_jit_free_call_frame, r0
| jmp >1
|.code
}
| MEM_OP2_1_ZTS mov, aword, executor_globals, vm_stack_top, RX, r0
|1:
if (!RETURN_VALUE_USED(opline)) {
zend_class_entry *ce;
zend_bool ce_is_instanceof;
uint32_t func_info = call_info ?
zend_get_func_info(call_info, ssa, &ce, &ce_is_instanceof) :
(MAY_BE_ANY|MAY_BE_REF|MAY_BE_RC1|MAY_BE_RCN);
/* If an exception is thrown, the return_value may stay at the
* original value of null. */
func_info |= MAY_BE_NULL;
if (func_info & (MAY_BE_STRING|MAY_BE_ARRAY|MAY_BE_OBJECT|MAY_BE_RESOURCE|MAY_BE_REF)) {
| ZVAL_PTR_DTOR res_addr, func_info, 1, 1, opline
}
}
| // if (UNEXPECTED(EG(exception) != NULL)) {
| MEM_OP2_1_ZTS cmp, aword, executor_globals, exception, 0, r0
| jne ->icall_throw_handler
// TODO: Can we avoid checking for interrupts after each call ???
if (trace && last_valid_opline != opline) {
int32_t exit_point = zend_jit_trace_get_exit_point(opline + 1, ZEND_JIT_EXIT_TO_VM);
exit_addr = zend_jit_trace_get_exit_addr(exit_point);
if (!exit_addr) {
return 0;
}
} else {
exit_addr = NULL;
}
if (!zend_jit_check_timeout(Dst, opline + 1, exit_addr)) {
return 0;
}
if ((!trace || !func) && opline->opcode != ZEND_DO_ICALL) {
| LOAD_IP_ADDR (opline + 1)
} else if (trace
&& trace->op == ZEND_JIT_TRACE_END
&& trace->stop == ZEND_JIT_TRACE_STOP_INTERPRETER) {
| LOAD_IP_ADDR (opline + 1)
}
}
if (!func) {
|9:
}
return 1;
}
static int zend_jit_send_val(dasm_State **Dst, const zend_op *opline, uint32_t op1_info, zend_jit_addr op1_addr)
{
uint32_t arg_num = opline->op2.num;
zend_jit_addr arg_addr;
ZEND_ASSERT(opline->opcode == ZEND_SEND_VAL || arg_num <= MAX_ARG_FLAG_NUM);
if (!zend_jit_reuse_ip(Dst)) {
return 0;
}
if (opline->opcode == ZEND_SEND_VAL_EX) {
uint32_t mask = ZEND_SEND_BY_REF << ((arg_num + 3) * 2);
ZEND_ASSERT(arg_num <= MAX_ARG_FLAG_NUM);
if (JIT_G(trigger) == ZEND_JIT_ON_HOT_TRACE
&& JIT_G(current_frame)
&& JIT_G(current_frame)->call
&& JIT_G(current_frame)->call->func) {
if (ARG_MUST_BE_SENT_BY_REF(JIT_G(current_frame)->call->func, arg_num)) {
/* Don't generate code that always throws exception */
return 0;
}
} else if (JIT_G(trigger) == ZEND_JIT_ON_HOT_TRACE) {
int32_t exit_point = zend_jit_trace_get_exit_point(opline, ZEND_JIT_EXIT_TO_VM);
const void *exit_addr = zend_jit_trace_get_exit_addr(exit_point);
if (!exit_addr) {
return 0;
}
| mov r0, EX:RX->func
| test dword [r0 + offsetof(zend_function, quick_arg_flags)], mask
| jnz &exit_addr
} else {
| mov r0, EX:RX->func
| test dword [r0 + offsetof(zend_function, quick_arg_flags)], mask
| jnz >1
|.cold_code
|1:
| SET_EX_OPLINE opline, r0
| jmp ->throw_cannot_pass_by_ref
|.code
}
}
arg_addr = ZEND_ADDR_MEM_ZVAL(ZREG_RX, opline->result.var);
if (opline->op1_type == IS_CONST) {
zval *zv = RT_CONSTANT(opline, opline->op1);
| ZVAL_COPY_CONST arg_addr, MAY_BE_ANY, MAY_BE_ANY, zv, ZREG_R0
if (Z_REFCOUNTED_P(zv)) {
| ADDREF_CONST zv, r0
}
} else {
| ZVAL_COPY_VALUE arg_addr, MAY_BE_ANY, op1_addr, op1_info, ZREG_R0, ZREG_R2
}
return 1;
}
static int zend_jit_check_undef_args(dasm_State **Dst, const zend_op *opline)
{
| mov FCARG1a, EX->call
| test byte [FCARG1a + offsetof(zend_execute_data, This.u1.type_info) + 3], (ZEND_CALL_MAY_HAVE_UNDEF >> 24)
| jnz >1
|.cold_code
|1:
| SET_EX_OPLINE opline, r0
| EXT_CALL zend_handle_undef_args, r0
| test r0, r0
| jnz ->exception_handler
| jmp >2
|.code
|2:
return 1;
}
static int zend_jit_send_ref(dasm_State **Dst, const zend_op *opline, const zend_op_array *op_array, uint32_t op1_info, int cold)
{
zend_jit_addr op1_addr, arg_addr, ref_addr;
op1_addr = OP1_ADDR();
arg_addr = ZEND_ADDR_MEM_ZVAL(ZREG_RX, opline->result.var);
if (!zend_jit_reuse_ip(Dst)) {
return 0;
}
if (opline->op1_type == IS_VAR) {
if (op1_info & MAY_BE_INDIRECT) {
| LOAD_ZVAL_ADDR r0, op1_addr
| // if (EXPECTED(Z_TYPE_P(ret) == IS_INDIRECT)) {
| IF_NOT_Z_TYPE r0, IS_INDIRECT, >1
| // ret = Z_INDIRECT_P(ret);
| GET_Z_PTR r0, r0
|1:
op1_addr = ZEND_ADDR_MEM_ZVAL(ZREG_R0, 0);
}
} else if (opline->op1_type == IS_CV) {
if (op1_info & MAY_BE_UNDEF) {
if (op1_info & (MAY_BE_ANY|MAY_BE_REF)) {
| IF_NOT_ZVAL_TYPE op1_addr, IS_UNDEF, >1
| SET_ZVAL_TYPE_INFO op1_addr, IS_NULL
| jmp >2
|1:
}
op1_info &= ~MAY_BE_UNDEF;
op1_info |= MAY_BE_NULL;
}
} else {
ZEND_UNREACHABLE();
}
if (op1_info & (MAY_BE_UNDEF|MAY_BE_ANY|MAY_BE_REF)) {
if (op1_info & MAY_BE_REF) {
| IF_NOT_ZVAL_TYPE op1_addr, IS_REFERENCE, >2
| GET_ZVAL_PTR r1, op1_addr
| GC_ADDREF r1
| SET_ZVAL_PTR arg_addr, r1
| SET_ZVAL_TYPE_INFO arg_addr, IS_REFERENCE_EX
| jmp >6
}
|2:
| // ZVAL_NEW_REF(arg, varptr);
if (opline->op1_type == IS_VAR) {
if (Z_REG(op1_addr) != ZREG_R0 || Z_OFFSET(op1_addr) != 0) {
| LOAD_ZVAL_ADDR r0, op1_addr
}
| mov aword T1, r0 // save
}
| EMALLOC sizeof(zend_reference), op_array, opline
| mov dword [r0], 2
| mov dword [r0 + offsetof(zend_reference, gc.u.type_info)], GC_REFERENCE
| mov aword [r0 + offsetof(zend_reference, sources.ptr)], 0
ref_addr = ZEND_ADDR_MEM_ZVAL(ZREG_R0, offsetof(zend_reference, val));
if (opline->op1_type == IS_VAR) {
zend_jit_addr val_addr = ZEND_ADDR_MEM_ZVAL(ZREG_R1, 0);
| mov r1, aword T1 // restore
| ZVAL_COPY_VALUE ref_addr, MAY_BE_ANY, val_addr, op1_info, ZREG_R2, ZREG_R2
| SET_ZVAL_PTR val_addr, r0
| SET_ZVAL_TYPE_INFO val_addr, IS_REFERENCE_EX
} else {
| ZVAL_COPY_VALUE ref_addr, MAY_BE_ANY, op1_addr, op1_info, ZREG_R1, ZREG_R2
| SET_ZVAL_PTR op1_addr, r0
| SET_ZVAL_TYPE_INFO op1_addr, IS_REFERENCE_EX
}
| SET_ZVAL_PTR arg_addr, r0
| SET_ZVAL_TYPE_INFO arg_addr, IS_REFERENCE_EX
}
|6:
| FREE_OP opline->op1_type, opline->op1, op1_info, !cold, opline
|7:
return 1;
}
static int zend_jit_send_var(dasm_State **Dst, const zend_op *opline, const zend_op_array *op_array, uint32_t op1_info, zend_jit_addr op1_addr, zend_jit_addr op1_def_addr)
{
uint32_t arg_num = opline->op2.num;
zend_jit_addr arg_addr;
ZEND_ASSERT((opline->opcode != ZEND_SEND_VAR_EX &&
opline->opcode != ZEND_SEND_VAR_NO_REF_EX) ||
arg_num <= MAX_ARG_FLAG_NUM);
arg_addr = ZEND_ADDR_MEM_ZVAL(ZREG_RX, opline->result.var);
if (!zend_jit_reuse_ip(Dst)) {
return 0;
}
if (opline->opcode == ZEND_SEND_VAR_EX) {
if (JIT_G(trigger) == ZEND_JIT_ON_HOT_TRACE
&& JIT_G(current_frame)
&& JIT_G(current_frame)->call
&& JIT_G(current_frame)->call->func) {
if (ARG_SHOULD_BE_SENT_BY_REF(JIT_G(current_frame)->call->func, arg_num)) {
if (!zend_jit_send_ref(Dst, opline, op_array, op1_info, 0)) {
return 0;
}
return 1;
}
} else {
uint32_t mask = (ZEND_SEND_BY_REF|ZEND_SEND_PREFER_REF) << ((arg_num + 3) * 2);
| mov r0, EX:RX->func
| test dword [r0 + offsetof(zend_function, quick_arg_flags)], mask
| jnz >1
|.cold_code
|1:
if (!zend_jit_send_ref(Dst, opline, op_array, op1_info, 1)) {
return 0;
}
| jmp >7
|.code
}
} else if (opline->opcode == ZEND_SEND_VAR_NO_REF_EX) {
if (JIT_G(trigger) == ZEND_JIT_ON_HOT_TRACE
&& JIT_G(current_frame)
&& JIT_G(current_frame)->call
&& JIT_G(current_frame)->call->func) {
if (ARG_SHOULD_BE_SENT_BY_REF(JIT_G(current_frame)->call->func, arg_num)) {
| ZVAL_COPY_VALUE arg_addr, MAY_BE_ANY, op1_addr, op1_info, ZREG_R1, ZREG_R2
if (!ARG_MAY_BE_SENT_BY_REF(JIT_G(current_frame)->call->func, arg_num)) {
if (!(op1_info & MAY_BE_REF)) {
/* Don't generate code that always throws exception */
return 0;
} else {
int32_t exit_point = zend_jit_trace_get_exit_point(opline, ZEND_JIT_EXIT_TO_VM);
const void *exit_addr = zend_jit_trace_get_exit_addr(exit_point);
if (!exit_addr) {
return 0;
}
| cmp cl, IS_REFERENCE
| jne &exit_addr
}
}
return 1;
}
} else {
uint32_t mask = (ZEND_SEND_BY_REF|ZEND_SEND_PREFER_REF) << ((arg_num + 3) * 2);
| mov r0, EX:RX->func
| test dword [r0 + offsetof(zend_function, quick_arg_flags)], mask
| jnz >1
|.cold_code
|1:
mask = ZEND_SEND_PREFER_REF << ((arg_num + 3) * 2);
| ZVAL_COPY_VALUE arg_addr, MAY_BE_ANY, op1_addr, op1_info, ZREG_R1, ZREG_R2
if (op1_info & MAY_BE_REF) {
| cmp cl, IS_REFERENCE
| je >7
}
| test dword [r0 + offsetof(zend_function, quick_arg_flags)], mask
| jnz >7
if (JIT_G(trigger) == ZEND_JIT_ON_HOT_TRACE) {
int32_t exit_point = zend_jit_trace_get_exit_point(opline, ZEND_JIT_EXIT_TO_VM);
const void *exit_addr = zend_jit_trace_get_exit_addr(exit_point);
if (!exit_addr) {
return 0;
}
| jmp &exit_addr
} else {
| SET_EX_OPLINE opline, r0
| LOAD_ZVAL_ADDR FCARG1a, arg_addr
| EXT_CALL zend_jit_only_vars_by_reference, r0
if (!zend_jit_check_exception(Dst)) {
return 0;
}
| jmp >7
}
|.code
}
} else if (opline->opcode == ZEND_SEND_FUNC_ARG) {
if (JIT_G(trigger) == ZEND_JIT_ON_HOT_TRACE
&& JIT_G(current_frame)
&& JIT_G(current_frame)->call
&& JIT_G(current_frame)->call->func) {
if (ARG_SHOULD_BE_SENT_BY_REF(JIT_G(current_frame)->call->func, arg_num)) {
if (!zend_jit_send_ref(Dst, opline, op_array, op1_info, 0)) {
return 0;
}
return 1;
}
} else {
| test dword [RX + offsetof(zend_execute_data, This.u1.type_info)], ZEND_CALL_SEND_ARG_BY_REF
| jnz >1
|.cold_code
|1:
if (!zend_jit_send_ref(Dst, opline, op_array, op1_info, 1)) {
return 0;
}
| jmp >7
|.code
}
}
if (op1_info & MAY_BE_UNDEF) {
if (op1_info & (MAY_BE_ANY|MAY_BE_REF)) {
| IF_ZVAL_TYPE op1_addr, IS_UNDEF, >1
|.cold_code
|1:
}
| SET_EX_OPLINE opline, r0
| mov FCARG1d, opline->op1.var
| EXT_CALL zend_jit_undefined_op_helper, r0
| SET_ZVAL_TYPE_INFO arg_addr, IS_NULL
| test r0, r0
| jz ->exception_handler
if (op1_info & (MAY_BE_ANY|MAY_BE_REF)) {
| jmp >7
|.code
} else {
|7:
return 1;
}
}
if (opline->opcode == ZEND_SEND_VAR_NO_REF) {
| ZVAL_COPY_VALUE arg_addr, MAY_BE_ANY, op1_addr, op1_info, ZREG_R1, ZREG_R2
if (op1_info & MAY_BE_REF) {
| cmp cl, IS_REFERENCE
| je >7
}
if (JIT_G(trigger) == ZEND_JIT_ON_HOT_TRACE) {
int32_t exit_point = zend_jit_trace_get_exit_point(opline, ZEND_JIT_EXIT_TO_VM);
const void *exit_addr = zend_jit_trace_get_exit_addr(exit_point);
if (!exit_addr) {
return 0;
}
| jmp &exit_addr
} else {
| SET_EX_OPLINE opline, r0
| LOAD_ZVAL_ADDR FCARG1a, arg_addr
| EXT_CALL zend_jit_only_vars_by_reference, r0
if (!zend_jit_check_exception(Dst)) {
return 0;
}
}
} else {
if (op1_info & MAY_BE_REF) {
if (opline->op1_type == IS_CV) {
zend_jit_addr val_addr = ZEND_ADDR_MEM_ZVAL(ZREG_FCARG1a, 0);
| LOAD_ZVAL_ADDR FCARG1a, op1_addr
| ZVAL_DEREF FCARG1a, op1_info
| ZVAL_COPY_VALUE arg_addr, MAY_BE_ANY, val_addr, op1_info, ZREG_R0, ZREG_R2
| TRY_ADDREF op1_info, ah, r2
} else {
zend_jit_addr ref_addr = ZEND_ADDR_MEM_ZVAL(ZREG_FCARG1a, 8);
| IF_ZVAL_TYPE op1_addr, IS_REFERENCE, >1
|.cold_code
|1:
| // zend_refcounted *ref = Z_COUNTED_P(retval_ptr);
| GET_ZVAL_PTR FCARG1a, op1_addr
| // ZVAL_COPY_VALUE(return_value, &ref->value);
| ZVAL_COPY_VALUE arg_addr, MAY_BE_ANY, ref_addr, op1_info, ZREG_R0, ZREG_R2
| GC_DELREF FCARG1a
| je >1
| IF_NOT_REFCOUNTED ah, >2
| GC_ADDREF r2
| jmp >2
|1:
| EFREE_REG_REFERENCE
| jmp >2
|.code
| ZVAL_COPY_VALUE arg_addr, MAY_BE_ANY, op1_addr, op1_info, ZREG_R0, ZREG_R2
|2:
}
} else {
if (op1_addr != op1_def_addr) {
if (!zend_jit_update_regs(Dst, opline->op1.var, op1_addr, op1_def_addr, op1_info)) {
return 0;
}
if (Z_MODE(op1_def_addr) == IS_REG && Z_MODE(op1_addr) != IS_REG) {
op1_addr= op1_def_addr;
}
}
| ZVAL_COPY_VALUE arg_addr, MAY_BE_ANY, op1_addr, op1_info, ZREG_R0, ZREG_R2
if (opline->op1_type == IS_CV) {
| TRY_ADDREF op1_info, ah, r2
}
}
}
|7:
return 1;
}
static int zend_jit_check_func_arg(dasm_State **Dst, const zend_op *opline)
{
uint32_t arg_num = opline->op2.num;
if (JIT_G(trigger) == ZEND_JIT_ON_HOT_TRACE
&& JIT_G(current_frame)
&& JIT_G(current_frame)->call
&& JIT_G(current_frame)->call->func) {
if (ARG_SHOULD_BE_SENT_BY_REF(JIT_G(current_frame)->call->func, arg_num)) {
if (!TRACE_FRAME_IS_LAST_SEND_BY_REF(JIT_G(current_frame)->call)) {
TRACE_FRAME_SET_LAST_SEND_BY_REF(JIT_G(current_frame)->call);
| // ZEND_ADD_CALL_FLAG(EX(call), ZEND_CALL_SEND_ARG_BY_REF);
|| if (reuse_ip) {
| or dword [RX + offsetof(zend_execute_data, This.u1.type_info)], ZEND_CALL_SEND_ARG_BY_REF
|| } else {
| mov r0, EX->call
| or dword [r0 + offsetof(zend_execute_data, This.u1.type_info)], ZEND_CALL_SEND_ARG_BY_REF
|| }
}
} else {
if (!TRACE_FRAME_IS_LAST_SEND_BY_VAL(JIT_G(current_frame)->call)) {
TRACE_FRAME_SET_LAST_SEND_BY_VAL(JIT_G(current_frame)->call);
| // ZEND_DEL_CALL_FLAG(EX(call), ZEND_CALL_SEND_ARG_BY_REF);
|| if (reuse_ip) {
| and dword [RX + offsetof(zend_execute_data, This.u1.type_info)], ~ZEND_CALL_SEND_ARG_BY_REF
|| } else {
| mov r0, EX->call
| and dword [r0 + offsetof(zend_execute_data, This.u1.type_info)], ~ZEND_CALL_SEND_ARG_BY_REF
|| }
}
}
} else {
// if (QUICK_ARG_SHOULD_BE_SENT_BY_REF(EX(call)->func, arg_num)) {
uint32_t mask = (ZEND_SEND_BY_REF|ZEND_SEND_PREFER_REF) << ((arg_num + 3) * 2);
if (!zend_jit_reuse_ip(Dst)) {
return 0;
}
| mov r0, EX:RX->func
| test dword [r0 + offsetof(zend_function, quick_arg_flags)], mask
| jnz >1
|.cold_code
|1:
| // ZEND_ADD_CALL_FLAG(EX(call), ZEND_CALL_SEND_ARG_BY_REF);
| or dword [RX + offsetof(zend_execute_data, This.u1.type_info)], ZEND_CALL_SEND_ARG_BY_REF
| jmp >1
|.code
| // ZEND_DEL_CALL_FLAG(EX(call), ZEND_CALL_SEND_ARG_BY_REF);
| and dword [RX + offsetof(zend_execute_data, This.u1.type_info)], ~ZEND_CALL_SEND_ARG_BY_REF
|1:
}
return 1;
}
static int zend_jit_smart_true(dasm_State **Dst, const zend_op *opline, int jmp, zend_uchar smart_branch_opcode, uint32_t target_label, uint32_t target_label2)
{
if (smart_branch_opcode) {
if (smart_branch_opcode == ZEND_JMPZ) {
if (jmp) {
| jmp >7
}
} else if (smart_branch_opcode == ZEND_JMPNZ) {
| jmp =>target_label
} else if (smart_branch_opcode == ZEND_JMPZNZ) {
| jmp =>target_label2
} else {
ZEND_UNREACHABLE();
}
} else {
zend_jit_addr res_addr = ZEND_ADDR_MEM_ZVAL(ZREG_FP, opline->result.var);
| SET_ZVAL_TYPE_INFO res_addr, IS_TRUE
if (jmp) {
| jmp >7
}
}
return 1;
}
static int zend_jit_smart_false(dasm_State **Dst, const zend_op *opline, int jmp, zend_uchar smart_branch_opcode, uint32_t target_label)
{
if (smart_branch_opcode) {
if (smart_branch_opcode == ZEND_JMPZ) {
| jmp =>target_label
} else if (smart_branch_opcode == ZEND_JMPNZ) {
if (jmp) {
| jmp >7
}
} else if (smart_branch_opcode == ZEND_JMPZNZ) {
| jmp =>target_label
} else {
ZEND_UNREACHABLE();
}
} else {
zend_jit_addr res_addr = ZEND_ADDR_MEM_ZVAL(ZREG_FP, opline->result.var);
| SET_ZVAL_TYPE_INFO res_addr, IS_FALSE
if (jmp) {
| jmp >7
}
}
return 1;
}
static int zend_jit_defined(dasm_State **Dst, const zend_op *opline, zend_uchar smart_branch_opcode, uint32_t target_label, uint32_t target_label2, const void *exit_addr)
{
uint32_t defined_label = (uint32_t)-1;
uint32_t undefined_label = (uint32_t)-1;
zval *zv = RT_CONSTANT(opline, opline->op1);
zend_jit_addr res_addr = 0;
if (smart_branch_opcode && !exit_addr) {
if (smart_branch_opcode == ZEND_JMPZ) {
undefined_label = target_label;
} else if (smart_branch_opcode == ZEND_JMPNZ) {
defined_label = target_label;
} else if (smart_branch_opcode == ZEND_JMPZNZ) {
undefined_label = target_label;
defined_label = target_label2;
} else {
ZEND_UNREACHABLE();
}
}
| // if (CACHED_PTR(opline->extended_value)) {
| mov r0, EX->run_time_cache
| mov r0, aword [r0 + opline->extended_value]
| test r0, r0
| jz >1
| test r0, 0x1
| jnz >4
|.cold_code
|4:
| MEM_OP2_2_ZTS mov, FCARG1a, aword, executor_globals, zend_constants, FCARG1a
| shr r0, 1
| cmp dword [FCARG1a + offsetof(HashTable, nNumOfElements)], eax
if (smart_branch_opcode) {
if (exit_addr) {
if (smart_branch_opcode == ZEND_JMPZ) {
| jz &exit_addr
} else {
| jz >3
}
} else if (undefined_label != (uint32_t)-1) {
| jz =>undefined_label
} else {
| jz >3
}
} else {
| jz >2
}
|1:
| SET_EX_OPLINE opline, r0
| LOAD_ADDR FCARG1a, zv
| EXT_CALL zend_jit_check_constant, r0
| test r0, r0
if (exit_addr) {
if (smart_branch_opcode == ZEND_JMPNZ) {
| jz >3
} else {
| jnz >3
}
| jmp &exit_addr
} else if (smart_branch_opcode) {
if (undefined_label != (uint32_t)-1) {
| jz =>undefined_label
} else {
| jz >3
}
if (defined_label != (uint32_t)-1) {
| jmp =>defined_label
} else {
| jmp >3
}
} else {
res_addr = ZEND_ADDR_MEM_ZVAL(ZREG_FP, opline->result.var);
| jnz >1
|2:
| SET_ZVAL_TYPE_INFO res_addr, IS_FALSE
| jmp >3
}
|.code
if (smart_branch_opcode) {
if (exit_addr) {
if (smart_branch_opcode == ZEND_JMPNZ) {
| jmp &exit_addr
}
} else if (defined_label != (uint32_t)-1) {
| jmp =>defined_label
}
} else {
|1:
| SET_ZVAL_TYPE_INFO res_addr, IS_TRUE
}
|3:
return 1;
}
static int zend_jit_type_check(dasm_State **Dst, const zend_op *opline, uint32_t op1_info, zend_uchar smart_branch_opcode, uint32_t target_label, uint32_t target_label2, const void *exit_addr)
{
uint32_t mask;
zend_uchar type;
zend_jit_addr op1_addr = OP1_ADDR();
// TODO: support for is_resource() ???
ZEND_ASSERT(opline->extended_value != MAY_BE_RESOURCE);
if (op1_info & MAY_BE_UNDEF) {
if (op1_info & (MAY_BE_ANY|MAY_BE_REF)) {
| IF_ZVAL_TYPE op1_addr, IS_UNDEF, >1
|.cold_code
|1:
}
| SET_EX_OPLINE opline, r0
| mov FCARG1d, opline->op1.var
| EXT_CALL zend_jit_undefined_op_helper, r0
zend_jit_check_exception_undef_result(Dst, opline);
if (opline->extended_value & MAY_BE_NULL) {
if (exit_addr) {
if (smart_branch_opcode == ZEND_JMPNZ) {
| jmp &exit_addr
} else if ((op1_info & (MAY_BE_ANY|MAY_BE_REF)) != 0) {
| jmp >7
}
} else if (!zend_jit_smart_true(Dst, opline, (op1_info & (MAY_BE_ANY|MAY_BE_REF)) != 0, smart_branch_opcode, target_label, target_label2)) {
return 0;
}
} else {
if (exit_addr) {
if (smart_branch_opcode == ZEND_JMPZ) {
| jmp &exit_addr
} else if ((op1_info & (MAY_BE_ANY|MAY_BE_REF)) != 0) {
| jmp >7
}
} else if (!zend_jit_smart_false(Dst, opline, (op1_info & (MAY_BE_ANY|MAY_BE_REF)) != 0, smart_branch_opcode, target_label)) {
return 0;
}
}
if (op1_info & (MAY_BE_ANY|MAY_BE_REF)) {
|.code
}
}
if (op1_info & (MAY_BE_ANY|MAY_BE_REF)) {
mask = opline->extended_value;
switch (mask) {
case MAY_BE_NULL: type = IS_NULL; break;
case MAY_BE_FALSE: type = IS_FALSE; break;
case MAY_BE_TRUE: type = IS_TRUE; break;
case MAY_BE_LONG: type = IS_LONG; break;
case MAY_BE_DOUBLE: type = IS_DOUBLE; break;
case MAY_BE_STRING: type = IS_STRING; break;
case MAY_BE_ARRAY: type = IS_ARRAY; break;
case MAY_BE_OBJECT: type = IS_OBJECT; break;
default:
type = 0;
}
if (!(op1_info & MAY_BE_GUARD) && !(op1_info & (MAY_BE_ANY - mask))) {
| FREE_OP opline->op1_type, opline->op1, op1_info, 1, opline
if (exit_addr) {
if (smart_branch_opcode == ZEND_JMPNZ) {
| jmp &exit_addr
}
} else if (!zend_jit_smart_true(Dst, opline, 0, smart_branch_opcode, target_label, target_label2)) {
return 0;
}
} else if (!(op1_info & MAY_BE_GUARD) && !(op1_info & mask)) {
| FREE_OP opline->op1_type, opline->op1, op1_info, 1, opline
if (exit_addr) {
if (smart_branch_opcode == ZEND_JMPZ) {
| jmp &exit_addr
}
} else if (!zend_jit_smart_false(Dst, opline, 0, smart_branch_opcode, target_label)) {
return 0;
}
} else {
if (op1_info & MAY_BE_REF) {
| LOAD_ZVAL_ADDR r0, op1_addr
| ZVAL_DEREF r0, op1_info
}
if (type == 0) {
if (smart_branch_opcode &&
(opline->op1_type & (IS_VAR|IS_TMP_VAR)) &&
(op1_info & (MAY_BE_STRING|MAY_BE_ARRAY|MAY_BE_OBJECT|MAY_BE_RESOURCE))) {
if ((op1_info) & (MAY_BE_ANY-(MAY_BE_OBJECT|MAY_BE_RESOURCE))) {
| // if (Z_REFCOUNTED_P(cv)) {
| IF_ZVAL_REFCOUNTED op1_addr, >1
|.cold_code
|1:
}
| // if (!Z_DELREF_P(cv)) {
| GET_ZVAL_PTR FCARG1a, op1_addr
| GC_DELREF FCARG1a
if (RC_MAY_BE_1(op1_info)) {
if (RC_MAY_BE_N(op1_info)) {
| jnz >3
}
if (op1_info & MAY_BE_REF) {
| mov al, byte [r0 + 8]
} else {
| mov al, byte [FP + opline->op1.var + 8]
}
| mov byte T1, al // save
| // zval_dtor_func(r);
| ZVAL_DTOR_FUNC op1_info, opline
| mov cl, byte T1 // restore
|jmp >2
}
if ((op1_info) & (MAY_BE_ANY-(MAY_BE_OBJECT|MAY_BE_RESOURCE))) {
if (!RC_MAY_BE_1(op1_info)) {
| jmp >3
}
|.code
}
|3:
if (op1_info & MAY_BE_REF) {
| mov cl, byte [r0 + 8]
} else {
| mov cl, byte [FP + opline->op1.var + 8]
}
|2:
} else {
if (op1_info & MAY_BE_REF) {
| mov cl, byte [r0 + 8]
} else {
| mov cl, byte [FP + opline->op1.var + 8]
}
}
| mov eax, 1
| shl eax, cl
| test eax, mask
if (exit_addr) {
if (smart_branch_opcode == ZEND_JMPNZ) {
| jne &exit_addr
} else {
| je &exit_addr
}
} else if (smart_branch_opcode) {
if (smart_branch_opcode == ZEND_JMPZ) {
| je =>target_label
} else if (smart_branch_opcode == ZEND_JMPNZ) {
| jne =>target_label
} else if (smart_branch_opcode == ZEND_JMPZNZ) {
| je =>target_label
| jmp =>target_label2
} else {
ZEND_UNREACHABLE();
}
} else {
zend_jit_addr res_addr = ZEND_ADDR_MEM_ZVAL(ZREG_FP, opline->result.var);
| setne al
| movzx eax, al
| add eax, 2
| SET_ZVAL_TYPE_INFO res_addr, eax
| FREE_OP opline->op1_type, opline->op1, op1_info, 1, opline
}
} else {
if (smart_branch_opcode &&
(opline->op1_type & (IS_VAR|IS_TMP_VAR)) &&
(op1_info & (MAY_BE_STRING|MAY_BE_ARRAY|MAY_BE_OBJECT|MAY_BE_RESOURCE))) {
if ((op1_info) & (MAY_BE_ANY-(MAY_BE_OBJECT|MAY_BE_RESOURCE))) {
| // if (Z_REFCOUNTED_P(cv)) {
| IF_ZVAL_REFCOUNTED op1_addr, >1
|.cold_code
|1:
}
| // if (!Z_DELREF_P(cv)) {
| GET_ZVAL_PTR FCARG1a, op1_addr
| GC_DELREF FCARG1a
if (RC_MAY_BE_1(op1_info)) {
if (RC_MAY_BE_N(op1_info)) {
| jnz >3
}
if (op1_info & MAY_BE_REF) {
| mov al, byte [r0 + 8]
} else {
| mov al, byte [FP + opline->op1.var + 8]
}
| mov byte T1, al // save
| // zval_dtor_func(r);
| ZVAL_DTOR_FUNC op1_info, opline
| mov cl, byte T1 // restore
|jmp >2
}
if ((op1_info) & (MAY_BE_ANY-(MAY_BE_OBJECT|MAY_BE_RESOURCE))) {
if (!RC_MAY_BE_1(op1_info)) {
| jmp >3
}
|.code
}
|3:
if (op1_info & MAY_BE_REF) {
| mov cl, byte [r0 + 8]
} else {
| mov cl, byte [FP + opline->op1.var + 8]
}
|2:
| cmp cl, type
} else {
if (op1_info & MAY_BE_REF) {
| cmp byte [r0 + 8], type
} else {
| cmp byte [FP + opline->op1.var + 8], type
}
}
if (exit_addr) {
if (smart_branch_opcode == ZEND_JMPNZ) {
| je &exit_addr
} else {
| jne &exit_addr
}
} else if (smart_branch_opcode) {
if (smart_branch_opcode == ZEND_JMPZ) {
| jne =>target_label
} else if (smart_branch_opcode == ZEND_JMPNZ) {
| je =>target_label
} else if (smart_branch_opcode == ZEND_JMPZNZ) {
| jne =>target_label
| jmp =>target_label2
} else {
ZEND_UNREACHABLE();
}
} else {
zend_jit_addr res_addr = ZEND_ADDR_MEM_ZVAL(ZREG_FP, opline->result.var);
| sete al
| movzx eax, al
| add eax, 2
| SET_ZVAL_TYPE_INFO res_addr, eax
| FREE_OP opline->op1_type, opline->op1, op1_info, 1, opline
}
}
}
}
|7:
return 1;
}
static uint32_t zend_ssa_cv_info(const zend_op_array *op_array, zend_ssa *ssa, uint32_t var)
{
uint32_t j, info;
if (ssa->vars && ssa->var_info) {
info = ssa->var_info[var].type;
for (j = op_array->last_var; j < ssa->vars_count; j++) {
if (ssa->vars[j].var == var) {
info |= ssa->var_info[j].type;
}
}
} else {
info = MAY_BE_RC1 | MAY_BE_RCN | MAY_BE_REF | MAY_BE_ANY | MAY_BE_UNDEF |
MAY_BE_ARRAY_KEY_ANY | MAY_BE_ARRAY_OF_ANY | MAY_BE_ARRAY_OF_REF;
}
#ifdef ZEND_JIT_USE_RC_INFERENCE
/* Refcount may be increased by RETURN opcode */
if ((info & MAY_BE_RC1) && !(info & MAY_BE_RCN)) {
for (j = 0; j < ssa->cfg.blocks_count; j++) {
if ((ssa->cfg.blocks[j].flags & ZEND_BB_REACHABLE) &&
ssa->cfg.blocks[j].len > 0) {
const zend_op *opline = op_array->opcodes + ssa->cfg.blocks[j].start + ssa->cfg.blocks[j].len - 1;
if (opline->opcode == ZEND_RETURN) {
if (opline->op1_type == IS_CV && opline->op1.var == EX_NUM_TO_VAR(var)) {
info |= MAY_BE_RCN;
break;
}
}
}
}
}
#endif
return info;
}
static int zend_jit_leave_frame(dasm_State **Dst)
{
| // EG(current_execute_data) = EX(prev_execute_data);
| mov r0, EX->prev_execute_data
| MEM_OP2_1_ZTS mov, aword, executor_globals, current_execute_data, r0, r2
return 1;
}
static int zend_jit_free_cv(dasm_State **Dst, uint32_t info, uint32_t var)
{
if (info & (MAY_BE_STRING|MAY_BE_ARRAY|MAY_BE_OBJECT|MAY_BE_RESOURCE|MAY_BE_REF)) {
uint32_t offset = EX_NUM_TO_VAR(var);
| ZVAL_PTR_DTOR ZEND_ADDR_MEM_ZVAL(ZREG_FP, offset), info, 1, 1, NULL
}
return 1;
}
static int zend_jit_free_op(dasm_State **Dst, const zend_op *opline, uint32_t info, uint32_t var_offset)
{
if (info & (MAY_BE_STRING|MAY_BE_ARRAY|MAY_BE_OBJECT|MAY_BE_RESOURCE|MAY_BE_REF)) {
| ZVAL_PTR_DTOR ZEND_ADDR_MEM_ZVAL(ZREG_FP, var_offset), info, 0, 1, opline
}
return 1;
}
static int zend_jit_leave_func(dasm_State **Dst,
const zend_op_array *op_array,
const zend_op *opline,
uint32_t op1_info,
zend_bool left_frame,
zend_jit_trace_rec *trace,
zend_jit_trace_info *trace_info,
int indirect_var_access,
int may_throw)
{
zend_bool may_be_top_frame =
JIT_G(trigger) != ZEND_JIT_ON_HOT_TRACE ||
!JIT_G(current_frame) ||
!TRACE_FRAME_IS_NESTED(JIT_G(current_frame));
zend_bool may_need_call_helper =
indirect_var_access || /* may have symbol table */
!op_array->function_name || /* may have symbol table */
may_be_top_frame ||
(op_array->fn_flags & ZEND_ACC_VARIADIC) || /* may have extra named args */
JIT_G(trigger) != ZEND_JIT_ON_HOT_TRACE ||
!JIT_G(current_frame) ||
TRACE_FRAME_NUM_ARGS(JIT_G(current_frame)) == -1 || /* unknown number of args */
(uint32_t)TRACE_FRAME_NUM_ARGS(JIT_G(current_frame)) > op_array->num_args; /* extra args */
zend_bool may_need_release_this =
!(op_array->fn_flags & ZEND_ACC_CLOSURE) &&
op_array->scope &&
!(op_array->fn_flags & ZEND_ACC_STATIC) &&
(JIT_G(trigger) != ZEND_JIT_ON_HOT_TRACE ||
!JIT_G(current_frame) ||
!TRACE_FRAME_NO_NEED_RELEASE_THIS(JIT_G(current_frame)));
if (may_need_call_helper || may_need_release_this) {
| mov FCARG1d, dword [FP + offsetof(zend_execute_data, This.u1.type_info)]
}
if (may_need_call_helper) {
if (!left_frame) {
left_frame = 1;
if (!zend_jit_leave_frame(Dst)) {
return 0;
}
}
/* ZEND_CALL_FAKE_CLOSURE handled on slow path to eliminate check for ZEND_CALL_CLOSURE on fast path */
| test FCARG1d, (ZEND_CALL_TOP|ZEND_CALL_HAS_SYMBOL_TABLE|ZEND_CALL_FREE_EXTRA_ARGS|ZEND_CALL_ALLOCATED|ZEND_CALL_HAS_EXTRA_NAMED_PARAMS|ZEND_CALL_FAKE_CLOSURE)
if (trace && trace->op != ZEND_JIT_TRACE_END) {
| jnz >1
|.cold_code
|1:
if (!GCC_GLOBAL_REGS) {
| mov FCARG2a, FP
}
| EXT_CALL zend_jit_leave_func_helper, r0
if (may_be_top_frame) {
// TODO: try to avoid this check ???
if (zend_jit_vm_kind == ZEND_VM_KIND_HYBRID) {
#if 0
/* this check should be handled by the following OPLINE guard */
| cmp IP, zend_jit_halt_op
| je ->trace_halt
#endif
} else if (GCC_GLOBAL_REGS) {
| test IP, IP
| je ->trace_halt
} else {
| test eax, eax
| jl ->trace_halt
}
}
if (!GCC_GLOBAL_REGS) {
| // execute_data = EG(current_execute_data)
| MEM_OP2_2_ZTS mov, FP, aword, executor_globals, current_execute_data, r0
}
| jmp >8
|.code
} else {
| jnz ->leave_function_handler
}
}
if (op_array->fn_flags & ZEND_ACC_CLOSURE) {
if (!left_frame) {
left_frame = 1;
if (!zend_jit_leave_frame(Dst)) {
return 0;
}
}
| // OBJ_RELEASE(ZEND_CLOSURE_OBJECT(EX(func)));
| mov FCARG1a, EX->func
| sub FCARG1a, sizeof(zend_object)
| OBJ_RELEASE ZREG_FCARG1a, >4
|4:
} else if (may_need_release_this) {
if (!left_frame) {
left_frame = 1;
if (!zend_jit_leave_frame(Dst)) {
return 0;
}
}
| // if (call_info & ZEND_CALL_RELEASE_THIS)
| test FCARG1d, ZEND_CALL_RELEASE_THIS
| je >4
| // zend_object *object = Z_OBJ(execute_data->This);
| mov FCARG1a, EX->This.value.obj
| // OBJ_RELEASE(object);
| OBJ_RELEASE ZREG_FCARG1a, >4
|4:
// TODO: avoid EG(excption) check for $this->foo() calls
may_throw = 1;
}
| // EG(vm_stack_top) = (zval*)execute_data;
| MEM_OP2_1_ZTS mov, aword, executor_globals, vm_stack_top, FP, r0
| // execute_data = EX(prev_execute_data);
| mov FP, EX->prev_execute_data
if (!left_frame) {
| // EG(current_execute_data) = execute_data;
| MEM_OP2_1_ZTS mov, aword, executor_globals, current_execute_data, FP, r0
}
|9:
if (trace) {
if (trace->op != ZEND_JIT_TRACE_END
&& (JIT_G(current_frame) && !TRACE_FRAME_IS_UNKNOWN_RETURN(JIT_G(current_frame)))) {
zend_jit_reset_last_valid_opline();
} else {
| LOAD_IP
| ADD_IP sizeof(zend_op)
}
|8:
if (trace->op == ZEND_JIT_TRACE_BACK
&& (!JIT_G(current_frame) || TRACE_FRAME_IS_UNKNOWN_RETURN(JIT_G(current_frame)))) {
const zend_op *next_opline = trace->opline;
if ((opline->op1_type & (IS_VAR|IS_TMP_VAR))
&& (op1_info & MAY_BE_RC1)
&& (op1_info & (MAY_BE_OBJECT|MAY_BE_RESOURCE|MAY_BE_ARRAY_OF_OBJECT|MAY_BE_ARRAY_OF_RESOURCE|MAY_BE_ARRAY_OF_ARRAY))) {
/* exception might be thrown during destruction of unused return value */
| // if (EG(exception))
| MEM_OP2_1_ZTS cmp, aword, executor_globals, exception, 0, r0
| jne ->leave_throw_handler
}
do {
trace++;
} while (trace->op == ZEND_JIT_TRACE_INIT_CALL);
ZEND_ASSERT(trace->op == ZEND_JIT_TRACE_VM || trace->op == ZEND_JIT_TRACE_END);
next_opline = trace->opline;
ZEND_ASSERT(next_opline != NULL);
if (trace->op == ZEND_JIT_TRACE_END
&& trace->stop == ZEND_JIT_TRACE_STOP_RECURSIVE_RET) {
trace_info->flags |= ZEND_JIT_TRACE_LOOP;
| CMP_IP next_opline
| je =>0 // LOOP
#ifdef ZEND_VM_HYBRID_JIT_RED_ZONE_SIZE
| JMP_IP
#else
| jmp ->trace_escape
#endif
} else {
| CMP_IP next_opline
| jne ->trace_escape
}
zend_jit_set_last_valid_opline(trace->opline);
return 1;
} else if (may_throw ||
(((opline->op1_type & (IS_VAR|IS_TMP_VAR))
&& (op1_info & MAY_BE_RC1)
&& (op1_info & (MAY_BE_OBJECT|MAY_BE_RESOURCE|MAY_BE_ARRAY_OF_OBJECT|MAY_BE_ARRAY_OF_RESOURCE|MAY_BE_ARRAY_OF_ARRAY)))
&& (!JIT_G(current_frame) || TRACE_FRAME_IS_RETURN_VALUE_UNUSED(JIT_G(current_frame))))) {
| // if (EG(exception))
| MEM_OP2_1_ZTS cmp, aword, executor_globals, exception, 0, r0
| jne ->leave_throw_handler
}
return 1;
} else {
| // if (EG(exception))
| MEM_OP2_1_ZTS cmp, aword, executor_globals, exception, 0, r0
| LOAD_IP
| jne ->leave_throw_handler
| // opline = EX(opline) + 1
| ADD_IP sizeof(zend_op)
}
if (zend_jit_vm_kind == ZEND_VM_KIND_HYBRID) {
| ADD_HYBRID_SPAD
#ifdef CONTEXT_THREADED_JIT
| push aword [IP]
| ret
#else
| JMP_IP
#endif
} else if (GCC_GLOBAL_REGS) {
| add r4, SPAD // stack alignment
#ifdef CONTEXT_THREADED_JIT
| push aword [IP]
| ret
#else
| JMP_IP
#endif
} else {
#ifdef CONTEXT_THREADED_JIT
ZEND_UNREACHABLE();
// TODO: context threading can't work without GLOBAL REGS because we have to change
// the value of execute_data in execute_ex()
| mov FCARG1a, FP
| mov r0, aword [FP]
| mov FP, aword T2 // restore FP
| mov RX, aword T3 // restore IP
| add r4, NR_SPAD // stack alignment
| push aword [r0]
| ret
#else
| mov FP, aword T2 // restore FP
| mov RX, aword T3 // restore IP
| add r4, NR_SPAD // stack alignment
| mov r0, 2 // ZEND_VM_LEAVE
| ret
#endif
}
return 1;
}
static int zend_jit_return(dasm_State **Dst, const zend_op *opline, const zend_op_array *op_array, uint32_t op1_info, zend_jit_addr op1_addr)
{
zend_jit_addr ret_addr;
int8_t return_value_used;
ZEND_ASSERT(op_array->type != ZEND_EVAL_CODE && op_array->function_name);
ZEND_ASSERT(!(op1_info & MAY_BE_UNDEF));
if (JIT_G(trigger) == ZEND_JIT_ON_HOT_TRACE && JIT_G(current_frame)) {
if (TRACE_FRAME_IS_RETURN_VALUE_USED(JIT_G(current_frame))) {
return_value_used = 1;
} else if (TRACE_FRAME_IS_RETURN_VALUE_UNUSED(JIT_G(current_frame))) {
return_value_used = 0;
} else {
return_value_used = -1;
}
} else {
return_value_used = -1;
}
if (ZEND_OBSERVER_ENABLED) {
if (Z_MODE(op1_addr) == IS_REG) {
zend_jit_addr dst = ZEND_ADDR_MEM_ZVAL(ZREG_FP, opline->op1.var);
if (!zend_jit_spill_store(Dst, op1_addr, dst, op1_info, 1)) {
return 0;
}
op1_addr = dst;
}
| LOAD_ZVAL_ADDR FCARG2a, op1_addr
| mov FCARG1a, FP
| SET_EX_OPLINE opline, r0
| EXT_CALL zend_observer_fcall_end, r0
}
// if (!EX(return_value))
if (Z_MODE(op1_addr) == IS_REG && Z_REG(op1_addr) == ZREG_R1) {
if (return_value_used != 0) {
| mov r2, EX->return_value
}
if (return_value_used == -1) {
| test r2, r2
}
ret_addr = ZEND_ADDR_MEM_ZVAL(ZREG_R2, 0);
} else {
if (return_value_used != 0) {
| mov r1, EX->return_value
}
if (return_value_used == -1) {
| test r1, r1
}
ret_addr = ZEND_ADDR_MEM_ZVAL(ZREG_R1, 0);
}
if ((opline->op1_type & (IS_VAR|IS_TMP_VAR)) &&
(op1_info & (MAY_BE_STRING|MAY_BE_ARRAY|MAY_BE_OBJECT|MAY_BE_RESOURCE))) {
if (return_value_used == -1) {
| jz >1
|.cold_code
|1:
}
if (return_value_used != 1) {
if (op1_info & ((MAY_BE_UNDEF|MAY_BE_ANY|MAY_BE_REF)-(MAY_BE_OBJECT|MAY_BE_RESOURCE))) {
if (jit_return_label >= 0) {
| IF_NOT_ZVAL_REFCOUNTED op1_addr, =>jit_return_label
} else {
| IF_NOT_ZVAL_REFCOUNTED op1_addr, >9
}
}
| GET_ZVAL_PTR FCARG1a, op1_addr
| GC_DELREF FCARG1a
if (RC_MAY_BE_1(op1_info)) {
if (RC_MAY_BE_N(op1_info)) {
if (jit_return_label >= 0) {
| jnz =>jit_return_label
} else {
| jnz >9
}
}
| //SAVE_OPLINE()
| ZVAL_DTOR_FUNC op1_info, opline
| //????mov r1, EX->return_value // reload ???
}
if (return_value_used == -1) {
if (jit_return_label >= 0) {
| jmp =>jit_return_label
} else {
| jmp >9
}
|.code
}
}
} else if (return_value_used == -1) {
if (jit_return_label >= 0) {
| jz =>jit_return_label
} else {
| jz >9
}
}
if (return_value_used == 0) {
|9:
return 1;
}
if (opline->op1_type == IS_CONST) {
zval *zv = RT_CONSTANT(opline, opline->op1);
| ZVAL_COPY_CONST ret_addr, MAY_BE_ANY, MAY_BE_ANY, zv, ZREG_R0
if (Z_REFCOUNTED_P(zv)) {
| ADDREF_CONST zv, r0
}
} else if (opline->op1_type == IS_TMP_VAR) {
| ZVAL_COPY_VALUE ret_addr, MAY_BE_ANY, op1_addr, op1_info, ZREG_R0, ZREG_R2
} else if (opline->op1_type == IS_CV) {
if (op1_info & MAY_BE_REF) {
| LOAD_ZVAL_ADDR r0, op1_addr
| ZVAL_DEREF r0, op1_info
op1_addr = ZEND_ADDR_MEM_ZVAL(ZREG_R0, 0);
}
| ZVAL_COPY_VALUE ret_addr, MAY_BE_ANY, op1_addr, op1_info, ZREG_R0, ZREG_R2
if (op1_info & (MAY_BE_STRING|MAY_BE_ARRAY|MAY_BE_OBJECT|MAY_BE_RESOURCE)) {
if (JIT_G(trigger) != ZEND_JIT_ON_HOT_TRACE ||
(op1_info & (MAY_BE_REF|MAY_BE_OBJECT)) ||
!op_array->function_name) {
| TRY_ADDREF op1_info, ah, r2
} else if (return_value_used != 1) {
| // if (EXPECTED(!(EX_CALL_INFO() & ZEND_CALL_CODE))) ZVAL_NULL(retval_ptr);
| SET_ZVAL_TYPE_INFO op1_addr, IS_NULL
}
}
} else {
if (op1_info & MAY_BE_REF) {
zend_jit_addr ref_addr = ZEND_ADDR_MEM_ZVAL(ZREG_R0, offsetof(zend_reference, val));
| IF_ZVAL_TYPE op1_addr, IS_REFERENCE, >1
|.cold_code
|1:
| // zend_refcounted *ref = Z_COUNTED_P(retval_ptr);
| GET_ZVAL_PTR r0, op1_addr
| // ZVAL_COPY_VALUE(return_value, &ref->value);
| ZVAL_COPY_VALUE ret_addr, MAY_BE_ANY, ref_addr, op1_info, ZREG_R2, ZREG_R2
| GC_DELREF r0
| je >2
| // if (IS_REFCOUNTED())
if (jit_return_label >= 0) {
| IF_NOT_REFCOUNTED dh, =>jit_return_label
} else {
| IF_NOT_REFCOUNTED dh, >9
}
| // ADDREF
| GET_ZVAL_PTR r2, ret_addr // reload
| GC_ADDREF r2
if (jit_return_label >= 0) {
| jmp =>jit_return_label
} else {
| jmp >9
}
|2:
| EFREE_REFERENCE r0
if (jit_return_label >= 0) {
| jmp =>jit_return_label
} else {
| jmp >9
}
|.code
}
| ZVAL_COPY_VALUE ret_addr, MAY_BE_ANY, op1_addr, op1_info, ZREG_R0, ZREG_R2
}
|9:
return 1;
}
static int zend_jit_zval_copy_deref(dasm_State **Dst, zend_jit_addr res_addr, zend_jit_addr val_addr, zend_reg type_reg)
{
ZEND_ASSERT(type_reg == ZREG_R2);
|.if not(X64)
|| if (Z_REG(val_addr) == ZREG_R1) {
| GET_ZVAL_W2 r0, val_addr
|| }
|.endif
| GET_ZVAL_PTR r1, val_addr
|.if not(X64)
|| if (Z_REG(val_addr) != ZREG_R1) {
| GET_ZVAL_W2 r0, val_addr
|| }
|.endif
| IF_NOT_REFCOUNTED dh, >2
| IF_NOT_TYPE dl, IS_REFERENCE, >1
| GET_Z_TYPE_INFO edx, r1+offsetof(zend_reference, val)
|.if not(X64)
| GET_Z_W2 r0, r1+offsetof(zend_reference, val)
|.endif
| GET_Z_PTR r1, r1+offsetof(zend_reference, val)
| IF_NOT_REFCOUNTED dh, >2
|1:
| GC_ADDREF r1
|2:
| SET_ZVAL_PTR res_addr, r1
|.if not(X64)
| SET_ZVAL_W2 res_addr, r0
|.endif
| SET_ZVAL_TYPE_INFO res_addr, edx
return 1;
}
static zend_bool zend_jit_may_avoid_refcounting(const zend_op *opline, uint32_t op1_info)
{
switch (opline->opcode) {
case ZEND_FETCH_OBJ_FUNC_ARG:
if (!JIT_G(current_frame) ||
!JIT_G(current_frame)->call->func ||
!TRACE_FRAME_IS_LAST_SEND_BY_VAL(JIT_G(current_frame)->call)) {
return 0;
}
/* break missing intentionally */
case ZEND_FETCH_OBJ_R:
case ZEND_FETCH_OBJ_IS:
if ((op1_info & MAY_BE_OBJECT)
&& opline->op2_type == IS_CONST
&& Z_TYPE_P(RT_CONSTANT(opline, opline->op2)) == IS_STRING
&& Z_STRVAL_P(RT_CONSTANT(opline, opline->op2))[0] != '\0') {
return 1;
}
break;
case ZEND_FETCH_DIM_FUNC_ARG:
if (!JIT_G(current_frame) ||
!JIT_G(current_frame)->call->func ||
!TRACE_FRAME_IS_LAST_SEND_BY_VAL(JIT_G(current_frame)->call)) {
return 0;
}
/* break missing intentionally */
case ZEND_FETCH_DIM_R:
case ZEND_FETCH_DIM_IS:
return 1;
case ZEND_ISSET_ISEMPTY_DIM_OBJ:
if (!(opline->extended_value & ZEND_ISEMPTY)) {
return 1;
}
break;
}
return 0;
}
static int zend_jit_fetch_dim_read(dasm_State **Dst,
const zend_op *opline,
zend_ssa *ssa,
const zend_ssa_op *ssa_op,
uint32_t op1_info,
zend_jit_addr op1_addr,
zend_bool op1_avoid_refcounting,
uint32_t op2_info,
uint32_t res_info,
zend_jit_addr res_addr,
int may_throw)
{
zend_jit_addr orig_op1_addr, op2_addr;
const void *exit_addr = NULL;
const void *not_found_exit_addr = NULL;
const void *res_exit_addr = NULL;
zend_bool result_avoid_refcounting = 0;
uint32_t may_be_string = (opline->opcode != ZEND_FETCH_LIST_R) ? MAY_BE_STRING : 0;
orig_op1_addr = OP1_ADDR();
op2_addr = OP2_ADDR();
if (opline->opcode != ZEND_FETCH_DIM_IS
&& JIT_G(trigger) == ZEND_JIT_ON_HOT_TRACE) {
int32_t exit_point = zend_jit_trace_get_exit_point(opline, ZEND_JIT_EXIT_TO_VM);
exit_addr = zend_jit_trace_get_exit_addr(exit_point);
if (!exit_addr) {
return 0;
}
}
if ((res_info & MAY_BE_GUARD)
&& JIT_G(current_frame)
&& (op1_info & (MAY_BE_ANY|MAY_BE_UNDEF)) == MAY_BE_ARRAY) {
uint32_t flags = 0;
uint32_t old_op1_info = 0;
uint32_t old_info;
zend_jit_trace_stack *stack = JIT_G(current_frame)->stack;
int32_t exit_point;
if (opline->opcode != ZEND_FETCH_LIST_R
&& (opline->op1_type & (IS_VAR|IS_TMP_VAR))
&& !op1_avoid_refcounting) {
flags |= ZEND_JIT_EXIT_FREE_OP1;
}
if ((opline->op2_type & (IS_VAR|IS_TMP_VAR))
&& (op2_info & (MAY_BE_STRING|MAY_BE_ARRAY|MAY_BE_OBJECT|MAY_BE_RESOURCE))) {
flags |= ZEND_JIT_EXIT_FREE_OP2;
}
if ((opline->result_type & (IS_VAR|IS_TMP_VAR))
&& !(flags & ZEND_JIT_EXIT_FREE_OP1)
&& (res_info & (MAY_BE_STRING|MAY_BE_ARRAY|MAY_BE_OBJECT|MAY_BE_RESOURCE))
&& (ssa_op+1)->op1_use == ssa_op->result_def
&& !(op2_info & ((MAY_BE_ANY|MAY_BE_UNDEF|MAY_BE_REF) - (MAY_BE_STRING|MAY_BE_LONG)))
&& zend_jit_may_avoid_refcounting(opline+1, res_info)) {
result_avoid_refcounting = 1;
ssa->var_info[ssa_op->result_def].avoid_refcounting = 1;
}
if (op1_avoid_refcounting) {
old_op1_info = STACK_INFO(stack, EX_VAR_TO_NUM(opline->op1.var));
SET_STACK_REG(stack, EX_VAR_TO_NUM(opline->op1.var), ZREG_NONE);
}
if (!(op2_info & ((MAY_BE_ANY|MAY_BE_UNDEF|MAY_BE_REF) - (MAY_BE_STRING|MAY_BE_LONG)))) {
old_info = STACK_INFO(stack, EX_VAR_TO_NUM(opline->result.var));
SET_STACK_TYPE(stack, EX_VAR_TO_NUM(opline->result.var), IS_UNKNOWN, 1);
SET_STACK_REG(stack, EX_VAR_TO_NUM(opline->result.var), ZREG_ZVAL_COPY_R0);
exit_point = zend_jit_trace_get_exit_point(opline+1, flags);
SET_STACK_INFO(stack, EX_VAR_TO_NUM(opline->result.var), old_info);
res_exit_addr = zend_jit_trace_get_exit_addr(exit_point);
if (!res_exit_addr) {
return 0;
}
res_info &= ~MAY_BE_GUARD;
ssa->var_info[ssa_op->result_def].type &= ~MAY_BE_GUARD;
}
if (opline->opcode == ZEND_FETCH_DIM_IS
&& !(res_info & MAY_BE_NULL)) {
old_info = STACK_INFO(stack, EX_VAR_TO_NUM(opline->result.var));
SET_STACK_TYPE(stack, EX_VAR_TO_NUM(opline->result.var), IS_NULL, 0);
SET_STACK_REG(stack, EX_VAR_TO_NUM(opline->result.var), ZREG_NULL);
exit_point = zend_jit_trace_get_exit_point(opline+1, flags);
SET_STACK_INFO(stack, EX_VAR_TO_NUM(opline->result.var), old_info);
not_found_exit_addr = zend_jit_trace_get_exit_addr(exit_point);
if (!not_found_exit_addr) {
return 0;
}
}
if (op1_avoid_refcounting) {
SET_STACK_INFO(stack, EX_VAR_TO_NUM(opline->op1.var), old_op1_info);
}
}
if (op1_info & MAY_BE_REF) {
| LOAD_ZVAL_ADDR FCARG1a, op1_addr
| ZVAL_DEREF FCARG1a, op1_info
op1_addr = ZEND_ADDR_MEM_ZVAL(ZREG_FCARG1a, 0);
}
if (op1_info & MAY_BE_ARRAY) {
if (op1_info & ((MAY_BE_ANY|MAY_BE_UNDEF) - MAY_BE_ARRAY)) {
if (exit_addr && !(op1_info & (MAY_BE_OBJECT|may_be_string))) {
| IF_NOT_ZVAL_TYPE op1_addr, IS_ARRAY, &exit_addr
} else {
| IF_NOT_ZVAL_TYPE op1_addr, IS_ARRAY, >7
}
}
| GET_ZVAL_LVAL ZREG_FCARG1a, op1_addr
if (!zend_jit_fetch_dimension_address_inner(Dst, opline, (opline->opcode != ZEND_FETCH_DIM_IS) ? BP_VAR_R : BP_VAR_IS, op1_info, op2_info, res_exit_addr, not_found_exit_addr, exit_addr)) {
return 0;
}
}
if (op1_info & ((MAY_BE_ANY|MAY_BE_UNDEF)-MAY_BE_ARRAY)) {
if (op1_info & MAY_BE_ARRAY) {
|.cold_code
|7:
}
if (opline->opcode != ZEND_FETCH_LIST_R && (op1_info & MAY_BE_STRING)) {
if (op1_info & ((MAY_BE_ANY|MAY_BE_UNDEF)-(MAY_BE_ARRAY|MAY_BE_STRING))) {
if (exit_addr && !(op1_info & MAY_BE_OBJECT)) {
| IF_NOT_ZVAL_TYPE op1_addr, IS_STRING, &exit_addr
} else {
| IF_NOT_ZVAL_TYPE op1_addr, IS_STRING, >6
}
}
| SET_EX_OPLINE opline, r0
| GET_ZVAL_LVAL ZREG_FCARG1a, op1_addr
if (opline->opcode != ZEND_FETCH_DIM_IS) {
if ((op2_info & (MAY_BE_ANY|MAY_BE_UNDEF|MAY_BE_GUARD)) == MAY_BE_LONG) {
| GET_ZVAL_LVAL ZREG_FCARG2a, op2_addr
| EXT_CALL zend_jit_fetch_dim_str_offset_r_helper, r0
} else {
| LOAD_ZVAL_ADDR FCARG2a, op2_addr
| EXT_CALL zend_jit_fetch_dim_str_r_helper, r0
}
| SET_ZVAL_PTR res_addr, r0
| SET_ZVAL_TYPE_INFO res_addr, IS_STRING
} else {
| LOAD_ZVAL_ADDR FCARG2a, op2_addr
|.if X64
| LOAD_ZVAL_ADDR CARG3, res_addr
|.else
| sub r4, 12
| PUSH_ZVAL_ADDR res_addr, r0
|.endif
| EXT_CALL zend_jit_fetch_dim_str_is_helper, r0
|.if not(X64)
| add r4, 12
|.endif
}
if ((op1_info & MAY_BE_ARRAY) ||
(op1_info & ((MAY_BE_ANY|MAY_BE_UNDEF)-(MAY_BE_ARRAY|MAY_BE_STRING)))) {
| jmp >9 // END
}
|6:
}
if (op1_info & MAY_BE_OBJECT) {
if (op1_info & ((MAY_BE_ANY|MAY_BE_UNDEF)-(MAY_BE_ARRAY|MAY_BE_OBJECT|may_be_string))) {
if (exit_addr) {
| IF_NOT_ZVAL_TYPE op1_addr, IS_OBJECT, &exit_addr
} else {
| IF_NOT_ZVAL_TYPE op1_addr, IS_OBJECT, >6
}
}
| SET_EX_OPLINE opline, r0
if (Z_REG(op1_addr) != ZREG_FCARG1a || Z_OFFSET(op1_addr) != 0) {
| LOAD_ZVAL_ADDR FCARG1a, op1_addr
}
if (opline->op2_type == IS_CONST && Z_EXTRA_P(RT_CONSTANT(opline, opline->op2)) == ZEND_EXTRA_VALUE) {
ZEND_ASSERT(Z_MODE(op2_addr) == IS_CONST_ZVAL);
| LOAD_ADDR FCARG2a, (Z_ZV(op2_addr) + 1)
} else {
| LOAD_ZVAL_ADDR FCARG2a, op2_addr
}
|.if X64
| LOAD_ZVAL_ADDR CARG3, res_addr
|.else
| sub r4, 12
| PUSH_ZVAL_ADDR res_addr, r0
|.endif
if (opline->opcode != ZEND_FETCH_DIM_IS) {
| EXT_CALL zend_jit_fetch_dim_obj_r_helper, r0
} else {
| EXT_CALL zend_jit_fetch_dim_obj_is_helper, r0
}
|.if not(X64)
| add r4, 12
|.endif
if ((op1_info & MAY_BE_ARRAY) ||
(op1_info & ((MAY_BE_ANY|MAY_BE_UNDEF)-(MAY_BE_ARRAY|MAY_BE_OBJECT|may_be_string)))) {
| jmp >9 // END
}
|6:
}
if ((op1_info & ((MAY_BE_ANY|MAY_BE_UNDEF)-(MAY_BE_ARRAY|MAY_BE_OBJECT|may_be_string)))
&& (!exit_addr || !(op1_info & (MAY_BE_ARRAY|MAY_BE_OBJECT|may_be_string)))) {
if ((opline->opcode != ZEND_FETCH_DIM_IS && (op1_info & MAY_BE_UNDEF)) || (op2_info & MAY_BE_UNDEF)) {
| SET_EX_OPLINE opline, r0
if (opline->opcode != ZEND_FETCH_DIM_IS && (op1_info & MAY_BE_UNDEF)) {
| IF_NOT_ZVAL_TYPE op1_addr, IS_UNDEF, >1
| // zend_error(E_WARNING, "Undefined variable $%s", ZSTR_VAL(CV_DEF_OF(EX_VAR_TO_NUM(opline->op1.var))));
| mov FCARG1d, opline->op1.var
| EXT_CALL zend_jit_undefined_op_helper, r0
|1:
}
if (op2_info & MAY_BE_UNDEF) {
| IF_NOT_ZVAL_TYPE op2_addr, IS_UNDEF, >1
| mov FCARG1d, opline->op2.var
| EXT_CALL zend_jit_undefined_op_helper, r0
|1:
}
}
if (opline->opcode != ZEND_FETCH_DIM_IS && opline->opcode != ZEND_FETCH_LIST_R) {
if ((op1_info & MAY_BE_UNDEF) || (op2_info & MAY_BE_UNDEF)) {
| LOAD_ZVAL_ADDR FCARG1a, orig_op1_addr
} else {
| SET_EX_OPLINE opline, r0
if (Z_MODE(op1_addr) != IS_MEM_ZVAL ||
Z_REG(op1_addr) != ZREG_FCARG1a ||
Z_OFFSET(op1_addr) != 0) {
| LOAD_ZVAL_ADDR FCARG1a, op1_addr
}
}
| EXT_CALL zend_jit_invalid_array_access, r0
}
| SET_ZVAL_TYPE_INFO res_addr, IS_NULL
if (op1_info & MAY_BE_ARRAY) {
| jmp >9 // END
}
}
if (op1_info & MAY_BE_ARRAY) {
|.code
}
}
if (op1_info & MAY_BE_ARRAY) {
zend_jit_addr val_addr = ZEND_ADDR_MEM_ZVAL(ZREG_R0, 0);
|8:
if (res_exit_addr) {
zend_uchar type = concrete_type(res_info);
if (op1_info & MAY_BE_ARRAY_OF_REF) {
| ZVAL_DEREF r0, MAY_BE_REF
}
if (type < IS_STRING) {
| IF_NOT_ZVAL_TYPE val_addr, type, &res_exit_addr
} else {
| GET_ZVAL_TYPE_INFO edx, val_addr
| IF_NOT_TYPE dl, type, &res_exit_addr
}
| // ZVAL_COPY
|7:
| ZVAL_COPY_VALUE_V res_addr, -1, val_addr, res_info, ZREG_R0, ZREG_R1
if (Z_MODE(res_addr) == IS_MEM_ZVAL) {
if (type < IS_STRING) {
if (Z_REG(res_addr) != ZREG_FP ||
JIT_G(current_frame) == NULL ||
STACK_MEM_TYPE(JIT_G(current_frame)->stack, EX_VAR_TO_NUM(Z_OFFSET(res_addr))) != type) {
| SET_ZVAL_TYPE_INFO res_addr, type
}
} else {
| SET_ZVAL_TYPE_INFO res_addr, edx
if (!result_avoid_refcounting) {
| TRY_ADDREF res_info, dh, r1
}
}
} else if (!zend_jit_store_var_if_necessary(Dst, opline->result.var, res_addr, res_info)) {
return 0;
}
} else if (op1_info & MAY_BE_ARRAY_OF_REF) {
| // ZVAL_COPY_DEREF
| GET_ZVAL_TYPE_INFO Rd(ZREG_R2), val_addr
if (!zend_jit_zval_copy_deref(Dst, res_addr, val_addr, ZREG_R2)) {
return 0;
}
} else {
| // ZVAL_COPY
| ZVAL_COPY_VALUE res_addr, -1, val_addr, res_info, ZREG_R1, ZREG_R2
| TRY_ADDREF res_info, ch, r2
}
}
|9: // END
#ifdef ZEND_JIT_USE_RC_INFERENCE
if ((opline->op2_type & (IS_TMP_VAR|IS_VAR)) && (op1_info & MAY_BE_OBJECT)) {
/* Magic offsetGet() may increase refcount of the key */
op2_info |= MAY_BE_RCN;
}
#endif
| FREE_OP opline->op2_type, opline->op2, op2_info, 0, opline
if (opline->opcode != ZEND_FETCH_LIST_R && !op1_avoid_refcounting) {
| FREE_OP opline->op1_type, opline->op1, op1_info, 0, opline
}
if (may_throw) {
if (!zend_jit_check_exception(Dst)) {
return 0;
}
}
return 1;
}
static int zend_jit_fetch_dim(dasm_State **Dst,
const zend_op *opline,
uint32_t op1_info,
zend_jit_addr op1_addr,
uint32_t op2_info,
zend_jit_addr res_addr,
int may_throw)
{
zend_jit_addr op2_addr;
op2_addr = (opline->op2_type != IS_UNUSED) ? OP2_ADDR() : 0;
if (op1_info & MAY_BE_REF) {
| LOAD_ZVAL_ADDR FCARG1a, op1_addr
| IF_NOT_Z_TYPE FCARG1a, IS_REFERENCE, >1
| GET_Z_PTR FCARG2a, FCARG1a
| IF_NOT_TYPE byte [FCARG2a + offsetof(zend_reference, val) + offsetof(zval, u1.v.type)], IS_ARRAY, >2
| lea FCARG1a, [FCARG2a + offsetof(zend_reference, val)]
| jmp >3
|.cold_code
|2:
| SET_EX_OPLINE opline, r0
| EXT_CALL zend_jit_prepare_assign_dim_ref, r0
| test r0, r0
| mov FCARG1a, r0
| jne >1
| jmp ->exception_handler_undef
|.code
|1:
op1_addr = ZEND_ADDR_MEM_ZVAL(ZREG_FCARG1a, 0);
}
if (op1_info & MAY_BE_ARRAY) {
if (op1_info & ((MAY_BE_ANY|MAY_BE_UNDEF) - MAY_BE_ARRAY)) {
| IF_NOT_ZVAL_TYPE op1_addr, IS_ARRAY, >7
}
|3:
| SEPARATE_ARRAY op1_addr, op1_info, 1
}
if (op1_info & (MAY_BE_UNDEF|MAY_BE_NULL|MAY_BE_FALSE)) {
if (op1_info & MAY_BE_ARRAY) {
|.cold_code
|7:
}
if (op1_info & (MAY_BE_ANY-(MAY_BE_NULL|MAY_BE_FALSE|MAY_BE_ARRAY))) {
| CMP_ZVAL_TYPE op1_addr, IS_FALSE
| jg >7
}
if (Z_REG(op1_addr) != ZREG_FP) {
| mov T1, Ra(Z_REG(op1_addr)) // save
}
if ((op1_info & MAY_BE_UNDEF)
&& opline->opcode == ZEND_FETCH_DIM_RW) {
if (op1_info & (MAY_BE_NULL|MAY_BE_FALSE)) {
| IF_NOT_ZVAL_TYPE op1_addr, IS_UNDEF, >1
}
| SET_EX_OPLINE opline, r0
| mov FCARG1a, opline->op1.var
| EXT_CALL zend_jit_undefined_op_helper, r0
|1:
}
| // ZVAL_ARR(container, zend_new_array(8));
| EXT_CALL _zend_new_array_0, r0
if (Z_REG(op1_addr) != ZREG_FP) {
| mov Ra(Z_REG(op1_addr)), T1 // restore
}
| SET_ZVAL_LVAL op1_addr, r0
| SET_ZVAL_TYPE_INFO op1_addr, IS_ARRAY_EX
| mov FCARG1a, r0
if (op1_info & MAY_BE_ARRAY) {
| jmp >1
|.code
|1:
}
}
if (op1_info & (MAY_BE_UNDEF|MAY_BE_NULL|MAY_BE_FALSE|MAY_BE_ARRAY)) {
|6:
if (opline->op2_type == IS_UNUSED) {
| // var_ptr = zend_hash_next_index_insert(Z_ARRVAL_P(container), &EG(uninitialized_zval));
| LOAD_ADDR_ZTS FCARG2a, executor_globals, uninitialized_zval
| EXT_CALL zend_hash_next_index_insert, r0
| // if (UNEXPECTED(!var_ptr)) {
| test r0, r0
| jz >1
|.cold_code
|1:
| // zend_throw_error(NULL, "Cannot add element to the array as the next element is already occupied");
| CANNOT_ADD_ELEMENT opline
| SET_ZVAL_TYPE_INFO res_addr, IS_UNDEF
| //ZEND_VM_C_GOTO(assign_dim_op_ret_null);
| jmp >8
|.code
| SET_ZVAL_PTR res_addr, r0
| SET_ZVAL_TYPE_INFO res_addr, IS_INDIRECT
} else {
uint32_t type;
switch (opline->opcode) {
case ZEND_FETCH_DIM_W:
case ZEND_FETCH_LIST_W:
type = BP_VAR_W;
break;
case ZEND_FETCH_DIM_RW:
type = BP_VAR_RW;
break;
case ZEND_FETCH_DIM_UNSET:
type = BP_VAR_UNSET;
break;
default:
ZEND_UNREACHABLE();
}
if (!zend_jit_fetch_dimension_address_inner(Dst, opline, type, op1_info, op2_info, NULL, NULL, NULL)) {
return 0;
}
|8:
| SET_ZVAL_PTR res_addr, r0
| SET_ZVAL_TYPE_INFO res_addr, IS_INDIRECT
if (type == BP_VAR_RW || (op2_info & ((MAY_BE_ANY|MAY_BE_UNDEF) - (MAY_BE_LONG|MAY_BE_STRING)))) {
|.cold_code
|9:
| SET_ZVAL_TYPE_INFO res_addr, IS_NULL
| jmp >8
|.code
}
}
}
if (op1_info & (MAY_BE_ANY-(MAY_BE_NULL|MAY_BE_FALSE|MAY_BE_ARRAY))) {
if (op1_info & (MAY_BE_UNDEF|MAY_BE_NULL|MAY_BE_FALSE|MAY_BE_ARRAY)) {
|.cold_code
|7:
}
| SET_EX_OPLINE opline, r0
if (Z_REG(op1_addr) != ZREG_FCARG1a || Z_OFFSET(op1_addr) != 0) {
| LOAD_ZVAL_ADDR FCARG1a, op1_addr
}
if (opline->op2_type == IS_UNUSED) {
| xor FCARG2a, FCARG2a
} else if (opline->op2_type == IS_CONST && Z_EXTRA_P(RT_CONSTANT(opline, opline->op2)) == ZEND_EXTRA_VALUE) {
ZEND_ASSERT(Z_MODE(op2_addr) == IS_CONST_ZVAL);
| LOAD_ADDR FCARG2a, (Z_ZV(op2_addr) + 1)
} else {
| LOAD_ZVAL_ADDR FCARG2a, op2_addr
}
|.if X64
| LOAD_ZVAL_ADDR CARG3, res_addr
|.else
| sub r4, 12
| PUSH_ZVAL_ADDR res_addr, r0
|.endif
switch (opline->opcode) {
case ZEND_FETCH_DIM_W:
case ZEND_FETCH_LIST_W:
| EXT_CALL zend_jit_fetch_dim_obj_w_helper, r0
break;
case ZEND_FETCH_DIM_RW:
| EXT_CALL zend_jit_fetch_dim_obj_rw_helper, r0
break;
// case ZEND_FETCH_DIM_UNSET:
// | EXT_CALL zend_jit_fetch_dim_obj_unset_helper, r0
// break;
default:
ZEND_UNREACHABLE();
}
|.if not(X64)
| add r4, 12
|.endif
if (op1_info & (MAY_BE_UNDEF|MAY_BE_NULL|MAY_BE_FALSE|MAY_BE_ARRAY)) {
| jmp >8 // END
|.code
}
}
#ifdef ZEND_JIT_USE_RC_INFERENCE
if ((opline->op2_type & (IS_TMP_VAR|IS_VAR)) && (op1_info & (MAY_BE_UNDEF|MAY_BE_NULL|MAY_BE_FALSE|MAY_BE_ARRAY|MAY_BE_OBJECT))) {
/* ASSIGN_DIM may increase refcount of the key */
op2_info |= MAY_BE_RCN;
}
#endif
|8:
| FREE_OP opline->op2_type, opline->op2, op2_info, 0, opline
if (may_throw) {
if (!zend_jit_check_exception(Dst)) {
return 0;
}
}
return 1;
}
static int zend_jit_isset_isempty_dim(dasm_State **Dst,
const zend_op *opline,
uint32_t op1_info,
zend_jit_addr op1_addr,
zend_bool op1_avoid_refcounting,
uint32_t op2_info,
int may_throw,
zend_uchar smart_branch_opcode,
uint32_t target_label,
uint32_t target_label2,
const void *exit_addr)
{
zend_jit_addr op2_addr, res_addr;
// TODO: support for empty() ???
ZEND_ASSERT(!(opline->extended_value & ZEND_ISEMPTY));
op2_addr = OP2_ADDR();
res_addr = ZEND_ADDR_MEM_ZVAL(ZREG_FP, opline->result.var);
if (op1_info & MAY_BE_REF) {
| LOAD_ZVAL_ADDR FCARG1a, op1_addr
| ZVAL_DEREF FCARG1a, op1_info
op1_addr = ZEND_ADDR_MEM_ZVAL(ZREG_FCARG1a, 0);
}
if (op1_info & MAY_BE_ARRAY) {
const void *found_exit_addr = NULL;
const void *not_found_exit_addr = NULL;
if (op1_info & ((MAY_BE_ANY|MAY_BE_UNDEF) - MAY_BE_ARRAY)) {
| IF_NOT_ZVAL_TYPE op1_addr, IS_ARRAY, >7
}
| GET_ZVAL_LVAL ZREG_FCARG1a, op1_addr
if (exit_addr
&& !(op1_info & ((MAY_BE_ANY|MAY_BE_UNDEF)-MAY_BE_ARRAY))
&& !may_throw
&& (!(opline->op1_type & (IS_TMP_VAR|IS_VAR)) || op1_avoid_refcounting)
&& (!(opline->op2_type & (IS_TMP_VAR|IS_VAR)) || !(op2_info & ((MAY_BE_ANY|MAY_BE_UNDEF)-MAY_BE_LONG)))) {
if (smart_branch_opcode == ZEND_JMPNZ) {
found_exit_addr = exit_addr;
} else {
not_found_exit_addr = exit_addr;
}
}
if (!zend_jit_fetch_dimension_address_inner(Dst, opline, BP_JIT_IS, op1_info, op2_info, found_exit_addr, not_found_exit_addr, NULL)) {
return 0;
}
if (found_exit_addr) {
|9:
return 1;
} else if (not_found_exit_addr) {
|8:
return 1;
}
}
if (op1_info & ((MAY_BE_ANY|MAY_BE_UNDEF)-MAY_BE_ARRAY)) {
if (op1_info & MAY_BE_ARRAY) {
|.cold_code
|7:
}
if (op1_info & (MAY_BE_STRING|MAY_BE_OBJECT)) {
| SET_EX_OPLINE opline, r0
if (Z_REG(op1_addr) != ZREG_FCARG1a || Z_OFFSET(op1_addr) != 0) {
| LOAD_ZVAL_ADDR FCARG1a, op1_addr
}
if (opline->op2_type == IS_CONST && Z_EXTRA_P(RT_CONSTANT(opline, opline->op2)) == ZEND_EXTRA_VALUE) {
ZEND_ASSERT(Z_MODE(op2_addr) == IS_CONST_ZVAL);
| LOAD_ADDR FCARG2a, (Z_ZV(op2_addr) + 1)
} else {
| LOAD_ZVAL_ADDR FCARG2a, op2_addr
}
| EXT_CALL zend_jit_isset_dim_helper, r0
| test r0, r0
| jz >9
if (op1_info & MAY_BE_ARRAY) {
| jmp >8
|.code
}
} else {
if (op2_info & MAY_BE_UNDEF) {
if (op2_info & MAY_BE_ANY) {
| IF_NOT_ZVAL_TYPE op2_addr, IS_UNDEF, >1
}
| SET_EX_OPLINE opline, r0
| mov FCARG1d, opline->op2.var
| EXT_CALL zend_jit_undefined_op_helper, r0
|1:
}
if (op1_info & MAY_BE_ARRAY) {
| jmp >9
|.code
}
}
}
#ifdef ZEND_JIT_USE_RC_INFERENCE
if ((opline->op2_type & (IS_TMP_VAR|IS_VAR)) && (op1_info & MAY_BE_OBJECT)) {
/* Magic offsetExists() may increase refcount of the key */
op2_info |= MAY_BE_RCN;
}
#endif
if (op1_info & (MAY_BE_ARRAY|MAY_BE_STRING|MAY_BE_OBJECT)) {
|8:
| FREE_OP opline->op2_type, opline->op2, op2_info, 0, opline
if (!op1_avoid_refcounting) {
| FREE_OP opline->op1_type, opline->op1, op1_info, 0, opline
}
if (may_throw) {
if (!zend_jit_check_exception_undef_result(Dst, opline)) {
return 0;
}
}
if (!(opline->extended_value & ZEND_ISEMPTY)) {
if (exit_addr) {
if (smart_branch_opcode == ZEND_JMPNZ) {
| jmp &exit_addr
} else {
| jmp >8
}
} else if (smart_branch_opcode) {
if (smart_branch_opcode == ZEND_JMPZ) {
| jmp =>target_label2
} else if (smart_branch_opcode == ZEND_JMPNZ) {
| jmp =>target_label
} else if (smart_branch_opcode == ZEND_JMPZNZ) {
| jmp =>target_label2
} else {
ZEND_UNREACHABLE();
}
} else {
| SET_ZVAL_TYPE_INFO res_addr, IS_TRUE
| jmp >8
}
} else {
| //????
| int3
}
}
|9: // not found
| FREE_OP opline->op2_type, opline->op2, op2_info, 0, opline
if (!op1_avoid_refcounting) {
| FREE_OP opline->op1_type, opline->op1, op1_info, 0, opline
}
if (may_throw) {
if (!zend_jit_check_exception_undef_result(Dst, opline)) {
return 0;
}
}
if (!(opline->extended_value & ZEND_ISEMPTY)) {
if (exit_addr) {
if (smart_branch_opcode == ZEND_JMPZ) {
| jmp &exit_addr
}
} else if (smart_branch_opcode) {
if (smart_branch_opcode == ZEND_JMPZ) {
| jmp =>target_label
} else if (smart_branch_opcode == ZEND_JMPNZ) {
} else if (smart_branch_opcode == ZEND_JMPZNZ) {
| jmp =>target_label
} else {
ZEND_UNREACHABLE();
}
} else {
| SET_ZVAL_TYPE_INFO res_addr, IS_FALSE
}
} else {
| //????
| int3
}
|8:
return 1;
}
static int zend_jit_bind_global(dasm_State **Dst, const zend_op *opline, uint32_t op1_info)
{
zend_jit_addr op1_addr = OP1_ADDR();
zend_string *varname = Z_STR_P(RT_CONSTANT(opline, opline->op2));
| // idx = (uint32_t)(uintptr_t)CACHED_PTR(opline->extended_value) - 1;
| mov r0, EX->run_time_cache
| mov r0, aword [r0 + opline->extended_value]
| sub r0, 1
| // if (EXPECTED(idx < EG(symbol_table).nNumUsed * sizeof(Bucket)))
| MEM_OP2_2_ZTS mov, ecx, dword, executor_globals, symbol_table.nNumUsed, r1
|.if X64
| shl r1, 5
|.else
| imul r1, sizeof(Bucket)
|.endif
| cmp r0, r1
| jae >9
| // Bucket *p = (Bucket*)((char*)EG(symbol_table).arData + idx);
| MEM_OP2_2_ZTS add, r0, aword, executor_globals, symbol_table.arData, r1
| IF_NOT_Z_TYPE r0, IS_REFERENCE, >9
| // (EXPECTED(p->key == varname))
| ADDR_OP2_2 cmp, aword [r0 + offsetof(Bucket, key)], varname, r1
| jne >9
| GET_Z_PTR r0, r0
| GC_ADDREF r0
|1:
if (op1_info & (MAY_BE_STRING|MAY_BE_ARRAY|MAY_BE_OBJECT|MAY_BE_RESOURCE|MAY_BE_REF)) {
if (op1_info & ((MAY_BE_ANY|MAY_BE_UNDEF) - (MAY_BE_STRING|MAY_BE_ARRAY|MAY_BE_OBJECT|MAY_BE_RESOURCE))) {
| // if (UNEXPECTED(Z_REFCOUNTED_P(variable_ptr)))
| IF_ZVAL_REFCOUNTED op1_addr, >2
|.cold_code
|2:
}
| // zend_refcounted *garbage = Z_COUNTED_P(variable_ptr);
| GET_ZVAL_PTR FCARG1a, op1_addr
| // ZVAL_REF(variable_ptr, ref)
| SET_ZVAL_PTR op1_addr, r0
| SET_ZVAL_TYPE_INFO op1_addr, IS_REFERENCE_EX
| // if (GC_DELREF(garbage) == 0)
| GC_DELREF FCARG1a
if (op1_info & (MAY_BE_REF|MAY_BE_ARRAY|MAY_BE_OBJECT)) {
| jnz >3
} else {
| jnz >5
}
| ZVAL_DTOR_FUNC op1_info, opline
| jmp >5
if (op1_info & (MAY_BE_REF|MAY_BE_ARRAY|MAY_BE_OBJECT)) {
|3:
| // GC_ZVAL_CHECK_POSSIBLE_ROOT(variable_ptr)
| IF_GC_MAY_NOT_LEAK FCARG1a, >5
| EXT_CALL gc_possible_root, r1
| jmp >5
}
if (op1_info & ((MAY_BE_ANY|MAY_BE_UNDEF) - (MAY_BE_STRING|MAY_BE_ARRAY|MAY_BE_OBJECT|MAY_BE_RESOURCE))) {
|.code
}
}
if (op1_info & ((MAY_BE_ANY|MAY_BE_UNDEF) - (MAY_BE_STRING|MAY_BE_ARRAY|MAY_BE_OBJECT|MAY_BE_RESOURCE))) {
| // ZVAL_REF(variable_ptr, ref)
| SET_ZVAL_PTR op1_addr, r0
| SET_ZVAL_TYPE_INFO op1_addr, IS_REFERENCE_EX
}
|5:
//END of handler
|.cold_code
|9:
| LOAD_ADDR FCARG1a, (ptrdiff_t)varname
| mov FCARG2a, EX->run_time_cache
if (opline->extended_value) {
| add FCARG2a, opline->extended_value
}
| EXT_CALL zend_jit_fetch_global_helper, r0
| jmp <1
|.code
return 1;
}
static int zend_jit_verify_arg_type(dasm_State **Dst, const zend_op *opline, zend_arg_info *arg_info, zend_bool check_exception)
{
zend_jit_addr res_addr = ZEND_ADDR_MEM_ZVAL(ZREG_FP, opline->result.var);
zend_bool in_cold = 0;
uint32_t type_mask = ZEND_TYPE_PURE_MASK(arg_info->type) & MAY_BE_ANY;
zend_reg tmp_reg = (type_mask == 0 || is_power_of_two(type_mask)) ? ZREG_FCARG1a : ZREG_R0;
if (ZEND_ARG_SEND_MODE(arg_info)) {
if (opline->opcode == ZEND_RECV_INIT) {
| LOAD_ZVAL_ADDR Ra(tmp_reg), res_addr
| ZVAL_DEREF Ra(tmp_reg), MAY_BE_REF
res_addr = ZEND_ADDR_MEM_ZVAL(tmp_reg, 0);
} else {
| GET_ZVAL_PTR Ra(tmp_reg), res_addr
res_addr = ZEND_ADDR_MEM_ZVAL(tmp_reg, offsetof(zend_reference, val));
}
}
if (type_mask != 0) {
if (is_power_of_two(type_mask)) {
uint32_t type_code = concrete_type(type_mask);
| IF_NOT_ZVAL_TYPE res_addr, type_code, >1
} else {
| mov edx, 1
| mov cl, byte [Ra(Z_REG(res_addr))+Z_OFFSET(res_addr)+offsetof(zval, u1.v.type)]
| shl edx, cl
| test edx, type_mask
| je >1
}
|.cold_code
|1:
in_cold = 1;
}
if (Z_REG(res_addr) != ZREG_FCARG1a || Z_OFFSET(res_addr) != 0) {
| LOAD_ZVAL_ADDR FCARG1a, res_addr
}
if (JIT_G(trigger) == ZEND_JIT_ON_HOT_TRACE) {
| SET_EX_OPLINE opline, r0
} else {
| ADDR_OP2_2 mov, aword EX->opline, opline, r0
}
| LOAD_ADDR FCARG2a, (ptrdiff_t)arg_info
| EXT_CALL zend_jit_verify_arg_slow, r0
if (check_exception) {
| test al, al
if (in_cold) {
| jnz >1
| jmp ->exception_handler
|.code
|1:
} else {
| jz ->exception_handler
}
} else if (in_cold) {
| jmp >1
|.code
|1:
}
return 1;
}
static int zend_jit_recv(dasm_State **Dst, const zend_op *opline, const zend_op_array *op_array)
{
uint32_t arg_num = opline->op1.num;
zend_arg_info *arg_info = NULL;
if (op_array->fn_flags & ZEND_ACC_HAS_TYPE_HINTS) {
if (EXPECTED(arg_num <= op_array->num_args)) {
arg_info = &op_array->arg_info[arg_num-1];
} else if (UNEXPECTED(op_array->fn_flags & ZEND_ACC_VARIADIC)) {
arg_info = &op_array->arg_info[op_array->num_args];
}
if (arg_info && !ZEND_TYPE_IS_SET(arg_info->type)) {
arg_info = NULL;
}
}
if (arg_info || (opline+1)->opcode != ZEND_RECV) {
| cmp dword EX->This.u2.num_args, arg_num
if (JIT_G(trigger) == ZEND_JIT_ON_HOT_TRACE) {
int32_t exit_point = zend_jit_trace_get_exit_point(opline, ZEND_JIT_EXIT_TO_VM);
const void *exit_addr = zend_jit_trace_get_exit_addr(exit_point);
if (!exit_addr) {
return 0;
}
| jb &exit_addr
} else {
| jb >1
|.cold_code
|1:
if (JIT_G(trigger) == ZEND_JIT_ON_HOT_TRACE) {
| SET_EX_OPLINE opline, r0
} else {
| ADDR_OP2_2 mov, aword EX->opline, opline, r0
}
| mov FCARG1a, FP
| EXT_CALL zend_missing_arg_error, r0
| jmp ->exception_handler
|.code
}
}
if (arg_info) {
if (!zend_jit_verify_arg_type(Dst, opline, arg_info, 1)) {
return 0;
}
}
if (JIT_G(trigger) != ZEND_JIT_ON_HOT_TRACE) {
if ((opline+1)->opcode != ZEND_RECV && (opline+1)->opcode != ZEND_RECV_INIT) {
| LOAD_IP_ADDR (opline + 1)
zend_jit_set_last_valid_opline(opline + 1);
}
}
return 1;
}
static int zend_jit_recv_init(dasm_State **Dst, const zend_op *opline, const zend_op_array *op_array, zend_bool is_last, int may_throw)
{
uint32_t arg_num = opline->op1.num;
zval *zv = RT_CONSTANT(opline, opline->op2);
zend_jit_addr res_addr = ZEND_ADDR_MEM_ZVAL(ZREG_FP, opline->result.var);
if (JIT_G(trigger) != ZEND_JIT_ON_HOT_TRACE ||
(op_array->fn_flags & ZEND_ACC_HAS_TYPE_HINTS)) {
| cmp dword EX->This.u2.num_args, arg_num
| jae >5
}
| ZVAL_COPY_CONST res_addr, -1, -1, zv, ZREG_R0
if (Z_REFCOUNTED_P(zv)) {
| ADDREF_CONST zv, r0
}
if (Z_CONSTANT_P(zv)) {
if (JIT_G(trigger) == ZEND_JIT_ON_HOT_TRACE) {
| SET_EX_OPLINE opline, r0
} else {
| ADDR_OP2_2 mov, aword EX->opline, opline, r0
}
| LOAD_ZVAL_ADDR FCARG1a, res_addr
| mov r0, EX->func
| mov FCARG2a, [r0 + offsetof(zend_op_array, scope)]
| .if X64
| EXT_CALL zval_update_constant_ex, r0
| .else
||#if (PHP_VERSION_ID < 80100) && (SIZEOF_SIZE_T == 4)
| EXT_CALL zval_jit_update_constant_ex, r0
||#else
| EXT_CALL zval_update_constant_ex, r0
||#endif
| .endif
| test al, al
| jnz >1
|.cold_code
|1:
| ZVAL_PTR_DTOR res_addr, MAY_BE_ANY|MAY_BE_RC1|MAY_BE_RCN, 1, 0, opline
| SET_ZVAL_TYPE_INFO res_addr, IS_UNDEF
| jmp ->exception_handler
|.code
}
|5:
if (op_array->fn_flags & ZEND_ACC_HAS_TYPE_HINTS) {
do {
zend_arg_info *arg_info;
if (arg_num <= op_array->num_args) {
arg_info = &op_array->arg_info[arg_num-1];
} else if (op_array->fn_flags & ZEND_ACC_VARIADIC) {
arg_info = &op_array->arg_info[op_array->num_args];
} else {
break;
}
if (!ZEND_TYPE_IS_SET(arg_info->type)) {
break;
}
if (!zend_jit_verify_arg_type(Dst, opline, arg_info, may_throw)) {
return 0;
}
} while (0);
}
if (JIT_G(trigger) != ZEND_JIT_ON_HOT_TRACE) {
if (is_last) {
| LOAD_IP_ADDR (opline + 1)
zend_jit_set_last_valid_opline(opline + 1);
}
}
return 1;
}
static zend_property_info* zend_get_known_property_info(zend_class_entry *ce, zend_string *member, zend_bool on_this, zend_string *filename)
{
zend_property_info *info = NULL;
if (!ce ||
!(ce->ce_flags & ZEND_ACC_LINKED) ||
(ce->ce_flags & ZEND_ACC_TRAIT) ||
ce->create_object) {
return NULL;
}
if (!(ce->ce_flags & ZEND_ACC_IMMUTABLE)) {
if (ce->info.user.filename != filename) {
/* class declaration might be changed independently */
return NULL;
}
if (ce->parent) {
zend_class_entry *parent = ce->parent;
do {
if (parent->type == ZEND_INTERNAL_CLASS) {
break;
} else if (parent->info.user.filename != filename) {
/* some of parents class declarations might be changed independently */
/* TODO: this check may be not enough, because even
* in the same it's possible to conditionally define
* few classes with the same name, and "parent" may
* change from request to request.
*/
return NULL;
}
parent = parent->parent;
} while (parent);
}
}
info = (zend_property_info*)zend_hash_find_ptr(&ce->properties_info, member);
if (info == NULL ||
!IS_VALID_PROPERTY_OFFSET(info->offset) ||
(info->flags & ZEND_ACC_STATIC)) {
return NULL;
}
if (!(info->flags & ZEND_ACC_PUBLIC) &&
(!on_this || info->ce != ce)) {
return NULL;
}
return info;
}
static zend_bool zend_may_be_dynamic_property(zend_class_entry *ce, zend_string *member, zend_bool on_this, zend_string *filename)
{
zend_property_info *info;
if (!ce || (ce->ce_flags & ZEND_ACC_TRAIT)) {
return 1;
}
if (!(ce->ce_flags & ZEND_ACC_IMMUTABLE)) {
if (ce->info.user.filename != filename) {
/* class declaration might be changed independently */
return 1;
}
}
info = (zend_property_info*)zend_hash_find_ptr(&ce->properties_info, member);
if (info == NULL ||
!IS_VALID_PROPERTY_OFFSET(info->offset) ||
(info->flags & ZEND_ACC_STATIC)) {
return 1;
}
if (!(info->flags & ZEND_ACC_PUBLIC) &&
(!on_this || info->ce != ce)) {
return 1;
}
return 0;
}
static int zend_jit_class_guard(dasm_State **Dst, const zend_op *opline, zend_class_entry *ce)
{
int32_t exit_point = zend_jit_trace_get_exit_point(opline, 0);
const void *exit_addr = zend_jit_trace_get_exit_addr(exit_point);
if (!exit_addr) {
return 0;
}
|.if X64
|| if (!IS_SIGNED_32BIT(ce)) {
| mov64 r0, ((ptrdiff_t)ce)
| cmp aword [FCARG1a + offsetof(zend_object, ce)], r0
|| } else {
| cmp aword [FCARG1a + offsetof(zend_object, ce)], ce
|| }
|.else
| cmp aword [FCARG1a + offsetof(zend_object, ce)], ce
|.endif
| jne &exit_addr
return 1;
}
static int zend_jit_fetch_obj(dasm_State **Dst,
const zend_op *opline,
const zend_op_array *op_array,
zend_ssa *ssa,
const zend_ssa_op *ssa_op,
uint32_t op1_info,
zend_jit_addr op1_addr,
zend_bool op1_indirect,
zend_class_entry *ce,
zend_bool ce_is_instanceof,
zend_bool use_this,
zend_bool op1_avoid_refcounting,
zend_class_entry *trace_ce,
int may_throw)
{
zval *member;
zend_property_info *prop_info;
zend_bool may_be_dynamic = 1;
zend_jit_addr res_addr = ZEND_ADDR_MEM_ZVAL(ZREG_FP, opline->result.var);
zend_jit_addr this_addr = ZEND_ADDR_MEM_ZVAL(ZREG_FP, offsetof(zend_execute_data, This));
zend_jit_addr prop_addr;
uint32_t res_info = RES_INFO();
ZEND_ASSERT(opline->op2_type == IS_CONST);
ZEND_ASSERT(op1_info & MAY_BE_OBJECT);
member = RT_CONSTANT(opline, opline->op2);
ZEND_ASSERT(Z_TYPE_P(member) == IS_STRING && Z_STRVAL_P(member)[0] != '\0');
prop_info = zend_get_known_property_info(ce, Z_STR_P(member), opline->op1_type == IS_UNUSED, op_array->filename);
if (opline->op1_type == IS_UNUSED || use_this) {
| GET_ZVAL_PTR FCARG1a, this_addr
} else {
if (opline->op1_type == IS_VAR
&& opline->opcode == ZEND_FETCH_OBJ_W
&& (op1_info & MAY_BE_INDIRECT)
&& Z_REG(op1_addr) == ZREG_FP) {
| LOAD_ZVAL_ADDR FCARG1a, op1_addr
| IF_NOT_Z_TYPE FCARG1a, IS_INDIRECT, >1
| GET_Z_PTR FCARG1a, FCARG1a
|1:
op1_addr = ZEND_ADDR_MEM_ZVAL(ZREG_FCARG1a, 0);
}
if (op1_info & MAY_BE_REF) {
if (Z_REG(op1_addr) != ZREG_FCARG1a || Z_OFFSET(op1_addr) != 0) {
| LOAD_ZVAL_ADDR FCARG1a, op1_addr
}
| ZVAL_DEREF FCARG1a, op1_info
op1_addr = ZEND_ADDR_MEM_ZVAL(ZREG_FCARG1a, 0);
}
if (op1_info & ((MAY_BE_UNDEF|MAY_BE_ANY)- MAY_BE_OBJECT)) {
if (JIT_G(trigger) == ZEND_JIT_ON_HOT_TRACE) {
int32_t exit_point = zend_jit_trace_get_exit_point(opline, ZEND_JIT_EXIT_TO_VM);
const void *exit_addr = zend_jit_trace_get_exit_addr(exit_point);
if (!exit_addr) {
return 0;
}
| IF_NOT_ZVAL_TYPE op1_addr, IS_OBJECT, &exit_addr
} else {
| IF_NOT_ZVAL_TYPE op1_addr, IS_OBJECT, >7
}
}
| GET_ZVAL_PTR FCARG1a, op1_addr
}
if (!prop_info && trace_ce && (trace_ce->ce_flags & ZEND_ACC_IMMUTABLE)) {
prop_info = zend_get_known_property_info(trace_ce, Z_STR_P(member), opline->op1_type == IS_UNUSED, op_array->filename);
if (prop_info) {
ce = trace_ce;
ce_is_instanceof = 0;
if (!(op1_info & MAY_BE_CLASS_GUARD)) {
if (!zend_jit_class_guard(Dst, opline, trace_ce)) {
return 0;
}
if (ssa->var_info && ssa_op->op1_use >= 0) {
ssa->var_info[ssa_op->op1_use].type |= MAY_BE_CLASS_GUARD;
ssa->var_info[ssa_op->op1_use].ce = ce;
ssa->var_info[ssa_op->op1_use].is_instanceof = ce_is_instanceof;
}
}
}
}
if (!prop_info) {
| mov r0, EX->run_time_cache
| mov r2, aword [r0 + (opline->extended_value & ~ZEND_FETCH_OBJ_FLAGS)]
| cmp r2, aword [FCARG1a + offsetof(zend_object, ce)]
| jne >5
| mov r0, aword [r0 + (opline->extended_value & ~ZEND_FETCH_OBJ_FLAGS) + sizeof(void*)]
may_be_dynamic = zend_may_be_dynamic_property(ce, Z_STR_P(member), opline->op1_type == IS_UNUSED, op_array->filename);
if (may_be_dynamic) {
| test r0, r0
if (opline->opcode == ZEND_FETCH_OBJ_W) {
| jl >5
} else {
| jl >8 // dynamic property
}
}
| mov edx, dword [FCARG1a + r0 + 8]
| IF_UNDEF dl, >5
| add FCARG1a, r0
prop_addr = ZEND_ADDR_MEM_ZVAL(ZREG_FCARG1a, 0);
if (opline->opcode == ZEND_FETCH_OBJ_W
&& (opline->extended_value & ZEND_FETCH_OBJ_FLAGS)
&& (!ce || ce_is_instanceof || (ce->ce_flags & ZEND_ACC_HAS_TYPE_HINTS))) {
uint32_t flags = opline->extended_value & ZEND_FETCH_OBJ_FLAGS;
| mov r0, EX->run_time_cache
| mov FCARG2a, aword [r0 + (opline->extended_value & ~ZEND_FETCH_OBJ_FLAGS) + sizeof(void*) * 2]
| test FCARG2a, FCARG2a
| jnz >1
|.cold_code
|1:
if (flags == ZEND_FETCH_DIM_WRITE) {
| SET_EX_OPLINE opline, r0
| EXT_CALL zend_jit_check_array_promotion, r0
| jmp >9
} else if (flags == ZEND_FETCH_REF) {
|.if X64
| LOAD_ZVAL_ADDR CARG3, res_addr
|.else
| sub r4, 12
| PUSH_ZVAL_ADDR res_addr, r0
|.endif
| EXT_CALL zend_jit_create_typed_ref, r0
|.if not(X64)
| add r4, 12
|.endif
| jmp >9
} else {
ZEND_UNREACHABLE();
}
|.code
}
} else {
prop_addr = ZEND_ADDR_MEM_ZVAL(ZREG_FCARG1a, prop_info->offset);
| mov edx, dword [FCARG1a + prop_info->offset + 8]
if (JIT_G(trigger) == ZEND_JIT_ON_HOT_TRACE) {
if (opline->opcode == ZEND_FETCH_OBJ_W || !(res_info & MAY_BE_GUARD) || !JIT_G(current_frame)) {
/* perform IS_UNDEF check only after result type guard (during deoptimization) */
int32_t exit_point = zend_jit_trace_get_exit_point(opline, ZEND_JIT_EXIT_TO_VM);
const void *exit_addr = zend_jit_trace_get_exit_addr(exit_point);
if (!exit_addr) {
return 0;
}
| IF_UNDEF dl, &exit_addr
}
} else {
| IF_UNDEF dl, >5
}
if (opline->opcode == ZEND_FETCH_OBJ_W
&& (opline->extended_value & ZEND_FETCH_OBJ_FLAGS)
&& ZEND_TYPE_IS_SET(prop_info->type)) {
uint32_t flags = opline->extended_value & ZEND_FETCH_OBJ_FLAGS;
if (flags == ZEND_FETCH_DIM_WRITE) {
if ((ZEND_TYPE_FULL_MASK(prop_info->type) & (MAY_BE_ITERABLE|MAY_BE_ARRAY)) == 0) {
| cmp dl, IS_FALSE
| jle >1
|.cold_code
|1:
if (Z_REG(prop_addr) != ZREG_FCARG1a || Z_OFFSET(prop_addr) != 0) {
| LOAD_ZVAL_ADDR FCARG1a, prop_addr
}
| LOAD_ADDR FCARG2a, prop_info
| SET_EX_OPLINE opline, r0
| EXT_CALL zend_jit_check_array_promotion, r0
| jmp >9
|.code
}
} else if (flags == ZEND_FETCH_REF) {
| IF_TYPE dl, IS_REFERENCE, >1
if (ce && ce->ce_flags & ZEND_ACC_IMMUTABLE) {
| LOAD_ADDR FCARG2a, prop_info
} else {
int prop_info_offset =
(((prop_info->offset - (sizeof(zend_object) - sizeof(zval))) / sizeof(zval)) * sizeof(void*));
| mov r0, aword [FCARG1a + offsetof(zend_object, ce)]
| mov r0, aword [r0 + offsetof(zend_class_entry, properties_info_table)]
| mov FCARG2a, aword[r0 + prop_info_offset]
}
if (Z_REG(prop_addr) != ZREG_FCARG1a || Z_OFFSET(prop_addr) != 0) {
| LOAD_ZVAL_ADDR FCARG1a, prop_addr
}
|.if X64
| LOAD_ZVAL_ADDR CARG3, res_addr
|.else
| sub r4, 12
| PUSH_ZVAL_ADDR res_addr, r0
|.endif
| EXT_CALL zend_jit_create_typed_ref, r0
|.if not(X64)
| add r4, 12
|.endif
| jmp >9
|1:
} else {
ZEND_UNREACHABLE();
}
}
}
if (op1_avoid_refcounting) {
SET_STACK_REG(JIT_G(current_frame)->stack,
EX_VAR_TO_NUM(opline->op1.var), ZREG_NONE);
}
if (opline->opcode == ZEND_FETCH_OBJ_W) {
if (Z_REG(prop_addr) != ZREG_FCARG1a || Z_OFFSET(prop_addr) != 0) {
| LOAD_ZVAL_ADDR FCARG1a, prop_addr
}
| SET_ZVAL_PTR res_addr, FCARG1a
| SET_ZVAL_TYPE_INFO res_addr, IS_INDIRECT
if (JIT_G(trigger) == ZEND_JIT_ON_HOT_TRACE && prop_info) {
ssa->var_info[ssa_op->result_def].indirect_reference = 1;
}
} else {
zend_bool result_avoid_refcounting = 0;
if ((res_info & MAY_BE_GUARD) && JIT_G(current_frame) && prop_info) {
uint32_t flags = 0;
uint32_t old_info;
zend_jit_trace_stack *stack = JIT_G(current_frame)->stack;
int32_t exit_point;
const void *exit_addr;
zend_uchar type;
zend_jit_addr val_addr = ZEND_ADDR_MEM_ZVAL(ZREG_R0, 0);
if ((opline->op1_type & (IS_VAR|IS_TMP_VAR))
&& !use_this
&& !op1_avoid_refcounting) {
flags = ZEND_JIT_EXIT_FREE_OP1;
}
| LOAD_ZVAL_ADDR r0, prop_addr
if ((opline->result_type & (IS_VAR|IS_TMP_VAR))
&& !(flags & ZEND_JIT_EXIT_FREE_OP1)
&& (res_info & (MAY_BE_STRING|MAY_BE_ARRAY|MAY_BE_OBJECT|MAY_BE_RESOURCE))
&& (ssa_op+1)->op1_use == ssa_op->result_def
&& zend_jit_may_avoid_refcounting(opline+1, res_info)) {
result_avoid_refcounting = 1;
ssa->var_info[ssa_op->result_def].avoid_refcounting = 1;
}
old_info = STACK_INFO(stack, EX_VAR_TO_NUM(opline->result.var));
SET_STACK_TYPE(stack, EX_VAR_TO_NUM(opline->result.var), IS_UNKNOWN, 1);
SET_STACK_REG(stack, EX_VAR_TO_NUM(opline->result.var), ZREG_ZVAL_COPY_R0);
exit_point = zend_jit_trace_get_exit_point(opline+1, flags);
SET_STACK_INFO(stack, EX_VAR_TO_NUM(opline->result.var), old_info);
exit_addr = zend_jit_trace_get_exit_addr(exit_point);
if (!exit_addr) {
return 0;
}
res_info &= ~MAY_BE_GUARD;
ssa->var_info[ssa_op->result_def].type &= ~MAY_BE_GUARD;
type = concrete_type(res_info);
| // ZVAL_DEREF()
| IF_NOT_TYPE dl, IS_REFERENCE, >1
| GET_Z_PTR r0, r0
| add r0, offsetof(zend_reference, val)
if (type < IS_STRING) {
|1:
| IF_NOT_ZVAL_TYPE val_addr, type, &exit_addr
} else {
| GET_ZVAL_TYPE_INFO edx, val_addr
|1:
| IF_NOT_TYPE dl, type, &exit_addr
}
| // ZVAL_COPY
| ZVAL_COPY_VALUE_V res_addr, -1, val_addr, res_info, ZREG_R0, ZREG_R1
if (type < IS_STRING) {
if (Z_REG(res_addr) != ZREG_FP ||
JIT_G(current_frame) == NULL ||
STACK_MEM_TYPE(JIT_G(current_frame)->stack, EX_VAR_TO_NUM(Z_OFFSET(res_addr))) != type) {
| SET_ZVAL_TYPE_INFO res_addr, type
}
} else {
| SET_ZVAL_TYPE_INFO res_addr, edx
if (!result_avoid_refcounting) {
| TRY_ADDREF res_info, dh, r1
}
}
} else {
if (!zend_jit_zval_copy_deref(Dst, res_addr, prop_addr, ZREG_R2)) {
return 0;
}
}
}
|.cold_code
if (JIT_G(trigger) != ZEND_JIT_ON_HOT_TRACE || !prop_info) {
|5:
| SET_EX_OPLINE opline, r0
if (opline->opcode == ZEND_FETCH_OBJ_W) {
| EXT_CALL zend_jit_fetch_obj_w_slow, r0
} else if (opline->opcode != ZEND_FETCH_OBJ_IS) {
| EXT_CALL zend_jit_fetch_obj_r_slow, r0
} else {
| EXT_CALL zend_jit_fetch_obj_is_slow, r0
}
| jmp >9
}
if ((op1_info & ((MAY_BE_UNDEF|MAY_BE_ANY|MAY_BE_REF)- MAY_BE_OBJECT)) && JIT_G(trigger) != ZEND_JIT_ON_HOT_TRACE) {
|7:
if (opline->opcode != ZEND_FETCH_OBJ_IS) {
| SET_EX_OPLINE opline, r0
if (opline->opcode != ZEND_FETCH_OBJ_W
&& (op1_info & MAY_BE_UNDEF)) {
zend_jit_addr orig_op1_addr = OP1_ADDR();
if (op1_info & MAY_BE_ANY) {
| IF_NOT_ZVAL_TYPE op1_addr, IS_UNDEF, >1
}
| mov FCARG1d, opline->op1.var
| EXT_CALL zend_jit_undefined_op_helper, r0
|1:
| LOAD_ZVAL_ADDR FCARG1a, orig_op1_addr
} else if (Z_REG(op1_addr) != ZREG_FCARG1a || Z_OFFSET(op1_addr) != 0) {
| LOAD_ZVAL_ADDR FCARG1a, op1_addr
}
| LOAD_ADDR FCARG2a, Z_STRVAL_P(member)
if (opline->opcode == ZEND_FETCH_OBJ_W) {
| EXT_CALL zend_jit_invalid_property_write, r0
| SET_ZVAL_TYPE_INFO res_addr, _IS_ERROR
} else {
| EXT_CALL zend_jit_invalid_property_read, r0
| SET_ZVAL_TYPE_INFO res_addr, IS_NULL
}
| jmp >9
} else {
| SET_ZVAL_TYPE_INFO res_addr, IS_NULL
| jmp >9
}
}
if (!prop_info
&& may_be_dynamic
&& opline->opcode != ZEND_FETCH_OBJ_W) {
|8:
| mov FCARG2a, r0
| SET_EX_OPLINE opline, r0
if (opline->opcode != ZEND_FETCH_OBJ_IS) {
| EXT_CALL zend_jit_fetch_obj_r_dynamic, r0
} else {
| EXT_CALL zend_jit_fetch_obj_is_dynamic, r0
}
| jmp >9
}
|.code;
|9: // END
if (opline->op1_type != IS_UNUSED && !use_this && !op1_indirect) {
if (opline->op1_type == IS_VAR
&& opline->opcode == ZEND_FETCH_OBJ_W
&& (op1_info & MAY_BE_RC1)) {
zend_jit_addr orig_op1_addr = OP1_ADDR();
| IF_NOT_ZVAL_REFCOUNTED orig_op1_addr, >1
| GET_ZVAL_PTR FCARG1a, orig_op1_addr
| GC_DELREF FCARG1a
| jnz >1
| SET_EX_OPLINE opline, r0
| EXT_CALL zend_jit_extract_helper, r0
|1:
} else if (!op1_avoid_refcounting) {
| FREE_OP opline->op1_type, opline->op1, op1_info, 1, opline
}
}
if (JIT_G(trigger) == ZEND_JIT_ON_HOT_TRACE
&& prop_info
&& (opline->opcode != ZEND_FETCH_OBJ_W ||
!(opline->extended_value & ZEND_FETCH_OBJ_FLAGS) ||
!ZEND_TYPE_IS_SET(prop_info->type))
&& opline->op1_type != IS_VAR
&& opline->op1_type != IS_TMP_VAR) {
may_throw = 0;
}
if (may_throw) {
if (!zend_jit_check_exception(Dst)) {
return 0;
}
}
return 1;
}
static int zend_jit_incdec_obj(dasm_State **Dst,
const zend_op *opline,
const zend_op_array *op_array,
zend_ssa *ssa,
const zend_ssa_op *ssa_op,
uint32_t op1_info,
zend_jit_addr op1_addr,
zend_bool op1_indirect,
zend_class_entry *ce,
zend_bool ce_is_instanceof,
zend_bool use_this,
zend_class_entry *trace_ce,
int may_throw)
{
zval *member;
zend_string *name;
zend_property_info *prop_info;
zend_jit_addr this_addr = ZEND_ADDR_MEM_ZVAL(ZREG_FP, offsetof(zend_execute_data, This));
zend_jit_addr res_addr = 0;
zend_jit_addr prop_addr;
zend_bool needs_slow_path = 0;
ZEND_ASSERT(opline->op2_type == IS_CONST);
ZEND_ASSERT(op1_info & MAY_BE_OBJECT);
if (opline->result_type != IS_UNUSED) {
res_addr = ZEND_ADDR_MEM_ZVAL(ZREG_FP, opline->result.var);
}
member = RT_CONSTANT(opline, opline->op2);
ZEND_ASSERT(Z_TYPE_P(member) == IS_STRING && Z_STRVAL_P(member)[0] != '\0');
name = Z_STR_P(member);
prop_info = zend_get_known_property_info(ce, name, opline->op1_type == IS_UNUSED, op_array->filename);
if (opline->op1_type == IS_UNUSED || use_this) {
| GET_ZVAL_PTR FCARG1a, this_addr
} else {
if (opline->op1_type == IS_VAR
&& (op1_info & MAY_BE_INDIRECT)
&& Z_REG(op1_addr) == ZREG_FP) {
| LOAD_ZVAL_ADDR FCARG1a, op1_addr
| IF_NOT_Z_TYPE FCARG1a, IS_INDIRECT, >1
| GET_Z_PTR FCARG1a, FCARG1a
|1:
op1_addr = ZEND_ADDR_MEM_ZVAL(ZREG_FCARG1a, 0);
}
if (op1_info & MAY_BE_REF) {
if (Z_REG(op1_addr) != ZREG_FCARG1a || Z_OFFSET(op1_addr) != 0) {
| LOAD_ZVAL_ADDR FCARG1a, op1_addr
}
| ZVAL_DEREF FCARG1a, op1_info
op1_addr = ZEND_ADDR_MEM_ZVAL(ZREG_FCARG1a, 0);
}
if (op1_info & ((MAY_BE_UNDEF|MAY_BE_ANY)- MAY_BE_OBJECT)) {
if (JIT_G(trigger) == ZEND_JIT_ON_HOT_TRACE) {
int32_t exit_point = zend_jit_trace_get_exit_point(opline, ZEND_JIT_EXIT_TO_VM);
const void *exit_addr = zend_jit_trace_get_exit_addr(exit_point);
if (!exit_addr) {
return 0;
}
| IF_NOT_ZVAL_TYPE op1_addr, IS_OBJECT, &exit_addr
} else {
| IF_NOT_ZVAL_TYPE op1_addr, IS_OBJECT, >1
|.cold_code
|1:
| SET_EX_OPLINE opline, r0
if (Z_REG(op1_addr) != ZREG_FCARG1a || Z_OFFSET(op1_addr) != 0) {
| LOAD_ZVAL_ADDR FCARG1a, op1_addr
}
| LOAD_ADDR FCARG2a, ZSTR_VAL(name)
| EXT_CALL zend_jit_invalid_property_incdec, r0
| jmp ->exception_handler
|.code
}
}
| GET_ZVAL_PTR FCARG1a, op1_addr
}
if (!prop_info && trace_ce && (trace_ce->ce_flags & ZEND_ACC_IMMUTABLE)) {
prop_info = zend_get_known_property_info(trace_ce, name, opline->op1_type == IS_UNUSED, op_array->filename);
if (prop_info) {
ce = trace_ce;
ce_is_instanceof = 0;
if (!(op1_info & MAY_BE_CLASS_GUARD)) {
if (!zend_jit_class_guard(Dst, opline, trace_ce)) {
return 0;
}
if (ssa->var_info && ssa_op->op1_use >= 0) {
ssa->var_info[ssa_op->op1_use].type |= MAY_BE_CLASS_GUARD;
ssa->var_info[ssa_op->op1_use].ce = ce;
ssa->var_info[ssa_op->op1_use].is_instanceof = ce_is_instanceof;
}
if (ssa->var_info && ssa_op->op1_def >= 0) {
ssa->var_info[ssa_op->op1_def].type |= MAY_BE_CLASS_GUARD;
ssa->var_info[ssa_op->op1_def].ce = ce;
ssa->var_info[ssa_op->op1_def].is_instanceof = ce_is_instanceof;
}
}
}
}
if (!prop_info) {
needs_slow_path = 1;
| mov r0, EX->run_time_cache
| mov r2, aword [r0 + opline->extended_value]
| cmp r2, aword [FCARG1a + offsetof(zend_object, ce)]
| jne >7
if (!ce || ce_is_instanceof || (ce->ce_flags & ZEND_ACC_HAS_TYPE_HINTS)) {
| cmp aword [r0 + opline->extended_value + sizeof(void*) * 2], 0
| jnz >7
}
| mov r0, aword [r0 + opline->extended_value + sizeof(void*)]
| test r0, r0
| jl >7
| IF_TYPE byte [FCARG1a + r0 + 8], IS_UNDEF, >7
| add FCARG1a, r0
prop_addr = ZEND_ADDR_MEM_ZVAL(ZREG_FCARG1a, 0);
} else {
prop_addr = ZEND_ADDR_MEM_ZVAL(ZREG_FCARG1a, prop_info->offset);
if (JIT_G(trigger) == ZEND_JIT_ON_HOT_TRACE) {
int32_t exit_point = zend_jit_trace_get_exit_point(opline, ZEND_JIT_EXIT_TO_VM);
const void *exit_addr = zend_jit_trace_get_exit_addr(exit_point);
if (!exit_addr) {
return 0;
}
| IF_TYPE byte [FCARG1a + prop_info->offset + 8], IS_UNDEF, &exit_addr
} else {
| IF_TYPE byte [FCARG1a + prop_info->offset + 8], IS_UNDEF, >7
needs_slow_path = 1;
}
if (ZEND_TYPE_IS_SET(prop_info->type)) {
| SET_EX_OPLINE opline, r0
if (ce && ce->ce_flags & ZEND_ACC_IMMUTABLE) {
| LOAD_ADDR FCARG2a, prop_info
} else {
int prop_info_offset =
(((prop_info->offset - (sizeof(zend_object) - sizeof(zval))) / sizeof(zval)) * sizeof(void*));
| mov r0, aword [FCARG1a + offsetof(zend_object, ce)]
| mov r0, aword [r0 + offsetof(zend_class_entry, properties_info_table)]
| mov FCARG2a, aword[r0 + prop_info_offset]
}
| LOAD_ZVAL_ADDR FCARG1a, prop_addr
if (opline->result_type == IS_UNUSED) {
switch (opline->opcode) {
case ZEND_PRE_INC_OBJ:
case ZEND_POST_INC_OBJ:
| EXT_CALL zend_jit_inc_typed_prop, r0
break;
case ZEND_PRE_DEC_OBJ:
case ZEND_POST_DEC_OBJ:
| EXT_CALL zend_jit_dec_typed_prop, r0
break;
default:
ZEND_UNREACHABLE();
}
} else {
|.if X64
| LOAD_ZVAL_ADDR CARG3, res_addr
|.else
| sub r4, 12
| PUSH_ZVAL_ADDR res_addr, r0
|.endif
switch (opline->opcode) {
case ZEND_PRE_INC_OBJ:
| EXT_CALL zend_jit_pre_inc_typed_prop, r0
break;
case ZEND_PRE_DEC_OBJ:
| EXT_CALL zend_jit_pre_dec_typed_prop, r0
break;
case ZEND_POST_INC_OBJ:
| EXT_CALL zend_jit_post_inc_typed_prop, r0
break;
case ZEND_POST_DEC_OBJ:
| EXT_CALL zend_jit_post_dec_typed_prop, r0
break;
default:
ZEND_UNREACHABLE();
}
|.if not(X64)
| add r4, 12
|.endif
}
}
}
if (!prop_info || !ZEND_TYPE_IS_SET(prop_info->type)) {
zend_jit_addr var_addr = prop_addr;
var_addr = ZEND_ADDR_MEM_ZVAL(ZREG_FCARG1a, 0);
if (Z_REG(prop_addr) != ZREG_FCARG1a || Z_OFFSET(prop_addr) != 0) {
| LOAD_ZVAL_ADDR FCARG1a, prop_addr
}
| IF_NOT_ZVAL_TYPE var_addr, IS_REFERENCE, >2
| GET_ZVAL_PTR FCARG1a, var_addr
| cmp aword [FCARG1a + offsetof(zend_reference, sources.ptr)], 0
| jnz >1
| lea FCARG1a, aword [FCARG1a + offsetof(zend_reference, val)]
|.cold_code
|1:
if (opline) {
| SET_EX_OPLINE opline, r0
}
if (opline->result_type == IS_UNUSED) {
| xor FCARG2a, FCARG2a
} else {
| LOAD_ZVAL_ADDR FCARG2a, res_addr
}
switch (opline->opcode) {
case ZEND_PRE_INC_OBJ:
| EXT_CALL zend_jit_pre_inc_typed_ref, r0
break;
case ZEND_PRE_DEC_OBJ:
| EXT_CALL zend_jit_pre_dec_typed_ref, r0
break;
case ZEND_POST_INC_OBJ:
| EXT_CALL zend_jit_post_inc_typed_ref, r0
break;
case ZEND_POST_DEC_OBJ:
| EXT_CALL zend_jit_post_dec_typed_ref, r0
break;
default:
ZEND_UNREACHABLE();
}
| jmp >9
|.code
|2:
| IF_NOT_ZVAL_TYPE var_addr, IS_LONG, >2
if (opline->opcode == ZEND_POST_INC_OBJ || opline->opcode == ZEND_POST_DEC_OBJ) {
if (opline->result_type != IS_UNUSED) {
| ZVAL_COPY_VALUE res_addr, -1, var_addr, MAY_BE_LONG, ZREG_R1, ZREG_R2
}
}
if (opline->opcode == ZEND_PRE_INC_OBJ || opline->opcode == ZEND_POST_INC_OBJ) {
| LONG_OP_WITH_32BIT_CONST add, var_addr, Z_L(1)
} else {
| LONG_OP_WITH_32BIT_CONST sub, var_addr, Z_L(1)
}
| jo >3
if (opline->opcode == ZEND_PRE_INC_OBJ || opline->opcode == ZEND_PRE_DEC_OBJ) {
if (opline->result_type != IS_UNUSED) {
| ZVAL_COPY_VALUE res_addr, -1, var_addr, MAY_BE_LONG, ZREG_R0, ZREG_R2
}
}
|.cold_code
|2:
if (opline->opcode == ZEND_POST_INC_OBJ || opline->opcode == ZEND_POST_DEC_OBJ) {
| ZVAL_COPY_VALUE res_addr, -1, var_addr, MAY_BE_ANY, ZREG_R0, ZREG_R2
| TRY_ADDREF MAY_BE_ANY, ah, r2
}
if (opline->opcode == ZEND_PRE_INC_OBJ || opline->opcode == ZEND_POST_INC_OBJ) {
if (opline->opcode == ZEND_PRE_INC_OBJ && opline->result_type != IS_UNUSED) {
| LOAD_ZVAL_ADDR FCARG2a, res_addr
| EXT_CALL zend_jit_pre_inc, r0
} else {
| EXT_CALL increment_function, r0
}
} else {
if (opline->opcode == ZEND_PRE_DEC_OBJ && opline->result_type != IS_UNUSED) {
| LOAD_ZVAL_ADDR FCARG2a, res_addr
| EXT_CALL zend_jit_pre_dec, r0
} else {
| EXT_CALL decrement_function, r0
}
}
| jmp >4
|3:
if (opline->opcode == ZEND_PRE_INC_OBJ || opline->opcode == ZEND_POST_INC_OBJ) {
|.if X64
| mov64 rax, 0x43e0000000000000
| SET_ZVAL_LVAL var_addr, rax
| SET_ZVAL_TYPE_INFO var_addr, IS_DOUBLE
if (opline->opcode == ZEND_PRE_INC_OBJ && opline->result_type != IS_UNUSED) {
| SET_ZVAL_LVAL res_addr, rax
| SET_ZVAL_TYPE_INFO res_addr, IS_DOUBLE
}
|.else
| SET_ZVAL_LVAL var_addr, 0
| SET_ZVAL_W2 var_addr, 0x41e00000
| SET_ZVAL_TYPE_INFO var_addr, IS_DOUBLE
if (opline->opcode == ZEND_PRE_INC_OBJ && opline->result_type != IS_UNUSED) {
| SET_ZVAL_LVAL res_addr, 0
| SET_ZVAL_W2 res_addr, 0x41e00000
| SET_ZVAL_TYPE_INFO res_addr, IS_DOUBLE
}
|.endif
} else {
|.if X64
| mov64 rax, 0xc3e0000000000000
| SET_ZVAL_LVAL var_addr, rax
| SET_ZVAL_TYPE_INFO var_addr, IS_DOUBLE
if (opline->opcode == ZEND_PRE_DEC_OBJ && opline->result_type != IS_UNUSED) {
| SET_ZVAL_LVAL res_addr, rax
| SET_ZVAL_TYPE_INFO res_addr, IS_DOUBLE
}
|.else
| SET_ZVAL_LVAL var_addr, 0x00200000
| SET_ZVAL_W2 var_addr, 0xc1e00000
| SET_ZVAL_TYPE_INFO var_addr, IS_DOUBLE
if (opline->opcode == ZEND_PRE_DEC_OBJ && opline->result_type != IS_UNUSED) {
| SET_ZVAL_LVAL res_addr, 0x00200000
| SET_ZVAL_W2 res_addr, 0xc1e00000
| SET_ZVAL_TYPE_INFO res_addr, IS_DOUBLE
}
|.endif
}
| jmp >4
|.code
|4:
}
if (needs_slow_path) {
|.cold_code
|7:
| SET_EX_OPLINE opline, r0
| // value = zobj->handlers->write_property(zobj, name, value, CACHE_ADDR(opline->extended_value));
| LOAD_ADDR FCARG2a, name
|.if X64
| mov CARG3, EX->run_time_cache
| add CARG3, opline->extended_value
if (opline->result_type == IS_UNUSED) {
| xor CARG4, CARG4
} else {
| LOAD_ZVAL_ADDR CARG4, res_addr
}
|.else
| sub r4, 8
if (opline->result_type == IS_UNUSED) {
| push 0
} else {
| PUSH_ZVAL_ADDR res_addr, r0
}
| mov r0, EX->run_time_cache
| add r0, opline->extended_value
| push r0
|.endif
switch (opline->opcode) {
case ZEND_PRE_INC_OBJ:
| EXT_CALL zend_jit_pre_inc_obj_helper, r0
break;
case ZEND_PRE_DEC_OBJ:
| EXT_CALL zend_jit_pre_dec_obj_helper, r0
break;
case ZEND_POST_INC_OBJ:
| EXT_CALL zend_jit_post_inc_obj_helper, r0
break;
case ZEND_POST_DEC_OBJ:
| EXT_CALL zend_jit_post_dec_obj_helper, r0
break;
default:
ZEND_UNREACHABLE();
}
|.if not(X64)
| add r4, 8
|.endif
| jmp >9
|.code
}
|9:
if (opline->op1_type != IS_UNUSED && !use_this && !op1_indirect) {
| FREE_OP opline->op1_type, opline->op1, op1_info, 1, opline
}
if (may_throw) {
if (!zend_jit_check_exception(Dst)) {
return 0;
}
}
return 1;
}
static int zend_jit_assign_obj_op(dasm_State **Dst,
const zend_op *opline,
const zend_op_array *op_array,
zend_ssa *ssa,
const zend_ssa_op *ssa_op,
uint32_t op1_info,
zend_jit_addr op1_addr,
uint32_t val_info,
zend_ssa_range *val_range,
zend_bool op1_indirect,
zend_class_entry *ce,
zend_bool ce_is_instanceof,
zend_bool use_this,
zend_class_entry *trace_ce,
int may_throw)
{
zval *member;
zend_string *name;
zend_property_info *prop_info;
zend_jit_addr val_addr = OP1_DATA_ADDR();
zend_jit_addr this_addr = ZEND_ADDR_MEM_ZVAL(ZREG_FP, offsetof(zend_execute_data, This));
zend_jit_addr prop_addr;
zend_bool needs_slow_path = 0;
binary_op_type binary_op = get_binary_op(opline->extended_value);
ZEND_ASSERT(opline->op2_type == IS_CONST);
ZEND_ASSERT(op1_info & MAY_BE_OBJECT);
ZEND_ASSERT(opline->result_type == IS_UNUSED);
member = RT_CONSTANT(opline, opline->op2);
ZEND_ASSERT(Z_TYPE_P(member) == IS_STRING && Z_STRVAL_P(member)[0] != '\0');
name = Z_STR_P(member);
prop_info = zend_get_known_property_info(ce, name, opline->op1_type == IS_UNUSED, op_array->filename);
if (opline->op1_type == IS_UNUSED || use_this) {
| GET_ZVAL_PTR FCARG1a, this_addr
} else {
if (opline->op1_type == IS_VAR
&& (op1_info & MAY_BE_INDIRECT)
&& Z_REG(op1_addr) == ZREG_FP) {
| LOAD_ZVAL_ADDR FCARG1a, op1_addr
| IF_NOT_Z_TYPE FCARG1a, IS_INDIRECT, >1
| GET_Z_PTR FCARG1a, FCARG1a
|1:
op1_addr = ZEND_ADDR_MEM_ZVAL(ZREG_FCARG1a, 0);
}
if (op1_info & MAY_BE_REF) {
if (Z_REG(op1_addr) != ZREG_FCARG1a || Z_OFFSET(op1_addr) != 0) {
| LOAD_ZVAL_ADDR FCARG1a, op1_addr
}
| ZVAL_DEREF FCARG1a, op1_info
op1_addr = ZEND_ADDR_MEM_ZVAL(ZREG_FCARG1a, 0);
}
if (op1_info & ((MAY_BE_UNDEF|MAY_BE_ANY)- MAY_BE_OBJECT)) {
if (JIT_G(trigger) == ZEND_JIT_ON_HOT_TRACE) {
int32_t exit_point = zend_jit_trace_get_exit_point(opline, ZEND_JIT_EXIT_TO_VM);
const void *exit_addr = zend_jit_trace_get_exit_addr(exit_point);
if (!exit_addr) {
return 0;
}
| IF_NOT_ZVAL_TYPE op1_addr, IS_OBJECT, &exit_addr
} else {
| IF_NOT_ZVAL_TYPE op1_addr, IS_OBJECT, >1
|.cold_code
|1:
| SET_EX_OPLINE opline, r0
if (Z_REG(op1_addr) != ZREG_FCARG1a || Z_OFFSET(op1_addr) != 0) {
| LOAD_ZVAL_ADDR FCARG1a, op1_addr
}
| LOAD_ADDR FCARG2a, ZSTR_VAL(name)
if (op1_info & MAY_BE_UNDEF) {
| EXT_CALL zend_jit_invalid_property_assign_op, r0
} else {
| EXT_CALL zend_jit_invalid_property_assign, r0
}
may_throw = 1;
if (((opline+1)->op1_type & (IS_VAR|IS_TMP_VAR))
&& (val_info & (MAY_BE_REF|MAY_BE_STRING|MAY_BE_ARRAY|MAY_BE_OBJECT|MAY_BE_RESOURCE))) {
| jmp >8
} else {
| jmp >9
}
|.code
}
}
| GET_ZVAL_PTR FCARG1a, op1_addr
}
if (!prop_info && trace_ce && (trace_ce->ce_flags & ZEND_ACC_IMMUTABLE)) {
prop_info = zend_get_known_property_info(trace_ce, name, opline->op1_type == IS_UNUSED, op_array->filename);
if (prop_info) {
ce = trace_ce;
ce_is_instanceof = 0;
if (!(op1_info & MAY_BE_CLASS_GUARD)) {
if (!zend_jit_class_guard(Dst, opline, trace_ce)) {
return 0;
}
if (ssa->var_info && ssa_op->op1_use >= 0) {
ssa->var_info[ssa_op->op1_use].type |= MAY_BE_CLASS_GUARD;
ssa->var_info[ssa_op->op1_use].ce = ce;
ssa->var_info[ssa_op->op1_use].is_instanceof = ce_is_instanceof;
}
if (ssa->var_info && ssa_op->op1_def >= 0) {
ssa->var_info[ssa_op->op1_def].type |= MAY_BE_CLASS_GUARD;
ssa->var_info[ssa_op->op1_def].ce = ce;
ssa->var_info[ssa_op->op1_def].is_instanceof = ce_is_instanceof;
}
}
}
}
if (!prop_info) {
needs_slow_path = 1;
| mov r0, EX->run_time_cache
| mov r2, aword [r0 + (opline+1)->extended_value]
| cmp r2, aword [FCARG1a + offsetof(zend_object, ce)]
| jne >7
if (!ce || ce_is_instanceof || (ce->ce_flags & ZEND_ACC_HAS_TYPE_HINTS)) {
| cmp aword [r0 + ((opline+1)->extended_value & ~ZEND_FETCH_OBJ_FLAGS) + sizeof(void*) * 2], 0
| jnz >7
}
| mov r0, aword [r0 + (opline+1)->extended_value + sizeof(void*)]
| test r0, r0
| jl >7
| IF_TYPE byte [FCARG1a + r0 + 8], IS_UNDEF, >7
| add FCARG1a, r0
prop_addr = ZEND_ADDR_MEM_ZVAL(ZREG_FCARG1a, 0);
} else {
prop_addr = ZEND_ADDR_MEM_ZVAL(ZREG_FCARG1a, prop_info->offset);
if (JIT_G(trigger) == ZEND_JIT_ON_HOT_TRACE) {
int32_t exit_point = zend_jit_trace_get_exit_point(opline, ZEND_JIT_EXIT_TO_VM);
const void *exit_addr = zend_jit_trace_get_exit_addr(exit_point);
if (!exit_addr) {
return 0;
}
| IF_TYPE byte [FCARG1a + prop_info->offset + 8], IS_UNDEF, &exit_addr
} else {
| IF_TYPE byte [FCARG1a + prop_info->offset + 8], IS_UNDEF, >7
needs_slow_path = 1;
}
if (ZEND_TYPE_IS_SET(prop_info->type)) {
uint32_t info = val_info;
if (opline) {
| SET_EX_OPLINE opline, r0
}
| IF_ZVAL_TYPE prop_addr, IS_REFERENCE, >1
|.cold_code
|1:
| GET_ZVAL_PTR FCARG1a, prop_addr
if (Z_MODE(val_addr) != IS_MEM_ZVAL || Z_REG(val_addr) != ZREG_FCARG2a || Z_OFFSET(val_addr) != 0) {
| LOAD_ZVAL_ADDR FCARG2a, val_addr
}
|.if X64
| LOAD_ADDR CARG3, binary_op
|.else
| sub r4, 12
| PUSH_ADDR binary_op, r0
|.endif
if (((opline+1)->op1_type & (IS_TMP_VAR|IS_VAR))
&& (val_info & (MAY_BE_STRING|MAY_BE_ARRAY|MAY_BE_OBJECT|MAY_BE_RESOURCE))) {
| EXT_CALL zend_jit_assign_op_to_typed_ref_tmp, r0
} else {
| EXT_CALL zend_jit_assign_op_to_typed_ref, r0
}
|.if not(X64)
| add r4, 12
|.endif
| jmp >9
|.code
| // value = zend_assign_to_typed_prop(prop_info, property_val, value EXECUTE_DATA_CC);
if (ce && ce->ce_flags & ZEND_ACC_IMMUTABLE) {
| LOAD_ADDR FCARG2a, prop_info
} else {
int prop_info_offset =
(((prop_info->offset - (sizeof(zend_object) - sizeof(zval))) / sizeof(zval)) * sizeof(void*));
| mov r0, aword [FCARG1a + offsetof(zend_object, ce)]
| mov r0, aword [r0 + offsetof(zend_class_entry, properties_info_table)]
| mov FCARG2a, aword[r0 + prop_info_offset]
}
| LOAD_ZVAL_ADDR FCARG1a, prop_addr
|.if X64
| LOAD_ZVAL_ADDR CARG3, val_addr
| LOAD_ADDR CARG4, binary_op
|.else
| sub r4, 8
| PUSH_ADDR binary_op, r0
| PUSH_ZVAL_ADDR val_addr, r0
|.endif
| EXT_CALL zend_jit_assign_op_to_typed_prop, r0
|.if not(X64)
| add r4, 8
|.endif
if (info & (MAY_BE_REF|MAY_BE_STRING|MAY_BE_ARRAY|MAY_BE_OBJECT|MAY_BE_RESOURCE)) {
info |= MAY_BE_RC1|MAY_BE_RCN;
}
| FREE_OP (opline+1)->op1_type, (opline+1)->op1, info, 0, opline
}
}
if (!prop_info || !ZEND_TYPE_IS_SET(prop_info->type)) {
zend_jit_addr var_addr = prop_addr;
uint32_t var_info = MAY_BE_ANY|MAY_BE_REF|MAY_BE_RC1|MAY_BE_RCN;
uint32_t var_def_info = MAY_BE_ANY|MAY_BE_REF|MAY_BE_RC1|MAY_BE_RCN;
var_addr = ZEND_ADDR_MEM_ZVAL(ZREG_R0, 0);
| LOAD_ZVAL_ADDR r0, prop_addr
| IF_NOT_ZVAL_TYPE var_addr, IS_REFERENCE, >2
| GET_ZVAL_PTR FCARG1a, var_addr
| cmp aword [FCARG1a + offsetof(zend_reference, sources.ptr)], 0
| jnz >1
| lea r0, aword [FCARG1a + offsetof(zend_reference, val)]
|.cold_code
|1:
if (Z_MODE(val_addr) != IS_MEM_ZVAL || Z_REG(val_addr) != ZREG_FCARG2a || Z_OFFSET(val_addr) != 0) {
| LOAD_ZVAL_ADDR FCARG2a, val_addr
}
if (opline) {
| SET_EX_OPLINE opline, r0
}
|.if X64
| LOAD_ADDR CARG3, binary_op
|.else
| sub r4, 12
| PUSH_ADDR binary_op, r0
|.endif
if (((opline+1)->op1_type & (IS_TMP_VAR|IS_VAR))
&& (val_info & (MAY_BE_STRING|MAY_BE_ARRAY|MAY_BE_OBJECT|MAY_BE_RESOURCE))) {
| EXT_CALL zend_jit_assign_op_to_typed_ref_tmp, r0
} else {
| EXT_CALL zend_jit_assign_op_to_typed_ref, r0
}
|.if not(X64)
| add r4, 12
|.endif
| jmp >9
|.code
|2:
switch (opline->extended_value) {
case ZEND_ADD:
case ZEND_SUB:
case ZEND_MUL:
case ZEND_DIV:
if (!zend_jit_math_helper(Dst, opline, opline->extended_value, IS_CV, opline->op1, var_addr, var_info, (opline+1)->op1_type, (opline+1)->op1, val_addr, val_info, 0, var_addr, var_def_info, var_info,
1 /* may overflow */, 0)) {
return 0;
}
break;
case ZEND_BW_OR:
case ZEND_BW_AND:
case ZEND_BW_XOR:
case ZEND_SL:
case ZEND_SR:
case ZEND_MOD:
if (!zend_jit_long_math_helper(Dst, opline, opline->extended_value,
IS_CV, opline->op1, var_addr, var_info, NULL,
(opline+1)->op1_type, (opline+1)->op1, val_addr, val_info,
val_range,
0, var_addr, var_def_info, var_info, 0)) {
return 0;
}
break;
case ZEND_CONCAT:
if (!zend_jit_concat_helper(Dst, opline, IS_CV, opline->op1, var_addr, var_info, (opline+1)->op1_type, (opline+1)->op1, val_addr, val_info, var_addr,
0)) {
return 0;
}
break;
default:
ZEND_UNREACHABLE();
}
}
if (needs_slow_path) {
|.cold_code
|7:
| SET_EX_OPLINE opline, r0
| // value = zobj->handlers->write_property(zobj, name, value, CACHE_ADDR(opline->extended_value));
| LOAD_ADDR FCARG2a, name
|.if X64
| LOAD_ZVAL_ADDR CARG3, val_addr
| mov CARG4, EX->run_time_cache
| add CARG4, (opline+1)->extended_value
|.if X64WIN
| LOAD_ADDR r0, binary_op
| mov aword A5, r0
|.else
| LOAD_ADDR CARG5, binary_op
|.endif
|.else
| sub r4, 4
| PUSH_ADDR binary_op, r0
| mov r0, EX->run_time_cache
| add r0, (opline+1)->extended_value
| push r0
| PUSH_ZVAL_ADDR val_addr, r0
|.endif
| EXT_CALL zend_jit_assign_obj_op_helper, r0
|.if not(X64)
| add r4, 4
|.endif
if (val_info & (MAY_BE_REF|MAY_BE_STRING|MAY_BE_ARRAY|MAY_BE_OBJECT|MAY_BE_RESOURCE)) {
val_info |= MAY_BE_RC1|MAY_BE_RCN;
}
|8:
| // FREE_OP_DATA();
| FREE_OP (opline+1)->op1_type, (opline+1)->op1, val_info, 0, opline
| jmp >9
|.code
}
|9:
if (opline->op1_type != IS_UNUSED && !use_this && !op1_indirect) {
| FREE_OP opline->op1_type, opline->op1, op1_info, 1, opline
}
if (may_throw) {
if (!zend_jit_check_exception(Dst)) {
return 0;
}
}
return 1;
}
static int zend_jit_assign_obj(dasm_State **Dst,
const zend_op *opline,
const zend_op_array *op_array,
zend_ssa *ssa,
const zend_ssa_op *ssa_op,
uint32_t op1_info,
zend_jit_addr op1_addr,
uint32_t val_info,
zend_bool op1_indirect,
zend_class_entry *ce,
zend_bool ce_is_instanceof,
zend_bool use_this,
zend_class_entry *trace_ce,
int may_throw)
{
zval *member;
zend_string *name;
zend_property_info *prop_info;
zend_jit_addr val_addr = OP1_DATA_ADDR();
zend_jit_addr res_addr = 0;
zend_jit_addr this_addr = ZEND_ADDR_MEM_ZVAL(ZREG_FP, offsetof(zend_execute_data, This));
zend_jit_addr prop_addr;
zend_bool needs_slow_path = 0;
zend_bool needs_val_dtor = 0;
if (RETURN_VALUE_USED(opline)) {
res_addr = ZEND_ADDR_MEM_ZVAL(ZREG_FP, opline->result.var);
}
ZEND_ASSERT(opline->op2_type == IS_CONST);
ZEND_ASSERT(op1_info & MAY_BE_OBJECT);
member = RT_CONSTANT(opline, opline->op2);
ZEND_ASSERT(Z_TYPE_P(member) == IS_STRING && Z_STRVAL_P(member)[0] != '\0');
name = Z_STR_P(member);
prop_info = zend_get_known_property_info(ce, name, opline->op1_type == IS_UNUSED, op_array->filename);
if (opline->op1_type == IS_UNUSED || use_this) {
| GET_ZVAL_PTR FCARG1a, this_addr
} else {
if (opline->op1_type == IS_VAR
&& (op1_info & MAY_BE_INDIRECT)
&& Z_REG(op1_addr) == ZREG_FP) {
| LOAD_ZVAL_ADDR FCARG1a, op1_addr
| IF_NOT_Z_TYPE FCARG1a, IS_INDIRECT, >1
| GET_Z_PTR FCARG1a, FCARG1a
|1:
op1_addr = ZEND_ADDR_MEM_ZVAL(ZREG_FCARG1a, 0);
}
if (op1_info & MAY_BE_REF) {
if (Z_REG(op1_addr) != ZREG_FCARG1a || Z_OFFSET(op1_addr) != 0) {
| LOAD_ZVAL_ADDR FCARG1a, op1_addr
}
| ZVAL_DEREF FCARG1a, op1_info
op1_addr = ZEND_ADDR_MEM_ZVAL(ZREG_FCARG1a, 0);
}
if (op1_info & ((MAY_BE_UNDEF|MAY_BE_ANY)- MAY_BE_OBJECT)) {
if (JIT_G(trigger) == ZEND_JIT_ON_HOT_TRACE) {
int32_t exit_point = zend_jit_trace_get_exit_point(opline, ZEND_JIT_EXIT_TO_VM);
const void *exit_addr = zend_jit_trace_get_exit_addr(exit_point);
if (!exit_addr) {
return 0;
}
| IF_NOT_ZVAL_TYPE op1_addr, IS_OBJECT, &exit_addr
} else {
| IF_NOT_ZVAL_TYPE op1_addr, IS_OBJECT, >1
|.cold_code
|1:
| SET_EX_OPLINE opline, r0
if (Z_REG(op1_addr) != ZREG_FCARG1a || Z_OFFSET(op1_addr) != 0) {
| LOAD_ZVAL_ADDR FCARG1a, op1_addr
}
| LOAD_ADDR FCARG2a, ZSTR_VAL(name)
| EXT_CALL zend_jit_invalid_property_assign, r0
if (RETURN_VALUE_USED(opline)) {
| SET_ZVAL_TYPE_INFO res_addr, IS_NULL
}
if (((opline+1)->op1_type & (IS_VAR|IS_TMP_VAR))
&& (val_info & (MAY_BE_REF|MAY_BE_STRING|MAY_BE_ARRAY|MAY_BE_OBJECT|MAY_BE_RESOURCE))) {
needs_val_dtor = 1;
| jmp >7
} else {
| jmp >9
}
|.code
}
}
| GET_ZVAL_PTR FCARG1a, op1_addr
}
if (!prop_info && trace_ce && (trace_ce->ce_flags & ZEND_ACC_IMMUTABLE)) {
prop_info = zend_get_known_property_info(trace_ce, name, opline->op1_type == IS_UNUSED, op_array->filename);
if (prop_info) {
ce = trace_ce;
ce_is_instanceof = 0;
if (!(op1_info & MAY_BE_CLASS_GUARD)) {
if (!zend_jit_class_guard(Dst, opline, trace_ce)) {
return 0;
}
if (ssa->var_info && ssa_op->op1_use >= 0) {
ssa->var_info[ssa_op->op1_use].type |= MAY_BE_CLASS_GUARD;
ssa->var_info[ssa_op->op1_use].ce = ce;
ssa->var_info[ssa_op->op1_use].is_instanceof = ce_is_instanceof;
}
if (ssa->var_info && ssa_op->op1_def >= 0) {
ssa->var_info[ssa_op->op1_def].type |= MAY_BE_CLASS_GUARD;
ssa->var_info[ssa_op->op1_def].ce = ce;
ssa->var_info[ssa_op->op1_def].is_instanceof = ce_is_instanceof;
}
}
}
}
if (!prop_info) {
needs_slow_path = 1;
| mov r0, EX->run_time_cache
| mov r2, aword [r0 + opline->extended_value]
| cmp r2, aword [FCARG1a + offsetof(zend_object, ce)]
| jne >5
if (!ce || ce_is_instanceof || (ce->ce_flags & ZEND_ACC_HAS_TYPE_HINTS)) {
| mov FCARG2a, aword [r0 + (opline->extended_value & ~ZEND_FETCH_OBJ_FLAGS) + sizeof(void*) * 2]
}
| mov r0, aword [r0 + opline->extended_value + sizeof(void*)]
| test r0, r0
| jl >5
| IF_TYPE byte [FCARG1a + r0 + 8], IS_UNDEF, >5
| add FCARG1a, r0
prop_addr = ZEND_ADDR_MEM_ZVAL(ZREG_FCARG1a, 0);
if (!ce || ce_is_instanceof || (ce->ce_flags & ZEND_ACC_HAS_TYPE_HINTS)) {
| test FCARG2a, FCARG2a
| jnz >1
|.cold_code
|1:
| // value = zend_assign_to_typed_prop(prop_info, property_val, value EXECUTE_DATA_CC);
| SET_EX_OPLINE opline, r0
|.if X64
| LOAD_ZVAL_ADDR CARG3, val_addr
if (RETURN_VALUE_USED(opline)) {
| LOAD_ZVAL_ADDR CARG4, res_addr
} else {
| xor CARG4, CARG4
}
|.else
| sub r4, 8
if (RETURN_VALUE_USED(opline)) {
| PUSH_ZVAL_ADDR res_addr, r0
} else {
| push 0
}
| PUSH_ZVAL_ADDR val_addr, r0
|.endif
| EXT_CALL zend_jit_assign_to_typed_prop, r0
|.if not(X64)
| add r4, 8
|.endif
if ((opline+1)->op1_type == IS_CONST) {
| // TODO: ???
| // if (Z_TYPE_P(value) == orig_type) {
| // CACHE_PTR_EX(cache_slot + 2, NULL);
}
if (((opline+1)->op1_type & (IS_VAR|IS_TMP_VAR))
&& (val_info & (MAY_BE_REF|MAY_BE_STRING|MAY_BE_ARRAY|MAY_BE_OBJECT|MAY_BE_RESOURCE))) {
| jmp >7
} else {
| jmp >9
}
|.code
}
} else {
prop_addr = ZEND_ADDR_MEM_ZVAL(ZREG_FCARG1a, prop_info->offset);
if (!ce || ce_is_instanceof || !(ce->ce_flags & ZEND_ACC_IMMUTABLE) || ce->__get || ce->__set) {
// Undefined property with magic __get()/__set()
if (JIT_G(trigger) == ZEND_JIT_ON_HOT_TRACE) {
int32_t exit_point = zend_jit_trace_get_exit_point(opline, ZEND_JIT_EXIT_TO_VM);
const void *exit_addr = zend_jit_trace_get_exit_addr(exit_point);
if (!exit_addr) {
return 0;
}
| IF_TYPE byte [FCARG1a + prop_info->offset + 8], IS_UNDEF, &exit_addr
} else {
| IF_TYPE byte [FCARG1a + prop_info->offset + 8], IS_UNDEF, >5
needs_slow_path = 1;
}
}
if (ZEND_TYPE_IS_SET(prop_info->type)) {
uint32_t info = val_info;
| // value = zend_assign_to_typed_prop(prop_info, property_val, value EXECUTE_DATA_CC);
| SET_EX_OPLINE opline, r0
if (ce && ce->ce_flags & ZEND_ACC_IMMUTABLE) {
| LOAD_ADDR FCARG2a, prop_info
} else {
int prop_info_offset =
(((prop_info->offset - (sizeof(zend_object) - sizeof(zval))) / sizeof(zval)) * sizeof(void*));
| mov r0, aword [FCARG1a + offsetof(zend_object, ce)]
| mov r0, aword [r0 + offsetof(zend_class_entry, properties_info_table)]
| mov FCARG2a, aword[r0 + prop_info_offset]
}
| LOAD_ZVAL_ADDR FCARG1a, prop_addr
|.if X64
| LOAD_ZVAL_ADDR CARG3, val_addr
if (RETURN_VALUE_USED(opline)) {
| LOAD_ZVAL_ADDR CARG4, res_addr
} else {
| xor CARG4, CARG4
}
|.else
| sub r4, 8
if (RETURN_VALUE_USED(opline)) {
| PUSH_ZVAL_ADDR res_addr, r0
} else {
| push 0
}
| PUSH_ZVAL_ADDR val_addr, r0
|.endif
| EXT_CALL zend_jit_assign_to_typed_prop, r0
|.if not(X64)
| add r4, 8
|.endif
if (info & (MAY_BE_REF|MAY_BE_STRING|MAY_BE_ARRAY|MAY_BE_OBJECT|MAY_BE_RESOURCE)) {
info |= MAY_BE_RC1|MAY_BE_RCN;
}
| FREE_OP (opline+1)->op1_type, (opline+1)->op1, info, 0, opline
}
}
if (!prop_info || !ZEND_TYPE_IS_SET(prop_info->type)) {
// value = zend_assign_to_variable(property_val, value, OP_DATA_TYPE, EX_USES_STRICT_TYPES());
if (opline->result_type == IS_UNUSED) {
if (!zend_jit_assign_to_variable_call(Dst, opline, prop_addr, prop_addr, -1, -1, (opline+1)->op1_type, val_addr, val_info, res_addr, 0)) {
return 0;
}
} else {
if (!zend_jit_assign_to_variable(Dst, opline, prop_addr, prop_addr, -1, -1, (opline+1)->op1_type, val_addr, val_info, res_addr, 0)) {
return 0;
}
}
}
if (needs_slow_path) {
|.cold_code
|5:
| SET_EX_OPLINE opline, r0
| // value = zobj->handlers->write_property(zobj, name, value, CACHE_ADDR(opline->extended_value));
| LOAD_ADDR FCARG2a, name
|.if X64
| LOAD_ZVAL_ADDR CARG3, val_addr
| mov CARG4, EX->run_time_cache
| add CARG4, opline->extended_value
if (RETURN_VALUE_USED(opline)) {
|.if X64WIN
| LOAD_ZVAL_ADDR r0, res_addr
| mov aword A5, r0
|.else
| LOAD_ZVAL_ADDR CARG5, res_addr
|.endif
} else {
|.if X64WIN
| mov aword A5, 0
|.else
| xor CARG5, CARG5
|.endif
}
|.else
| sub r4, 4
if (RETURN_VALUE_USED(opline)) {
| PUSH_ZVAL_ADDR res_addr, r0
} else {
| push 0
}
| mov r0, EX->run_time_cache
| add r0, opline->extended_value
| push r0
| PUSH_ZVAL_ADDR val_addr, r0
|.endif
| EXT_CALL zend_jit_assign_obj_helper, r0
|.if not(X64)
| add r4, 4
|.endif
if (val_info & (MAY_BE_REF|MAY_BE_STRING|MAY_BE_ARRAY|MAY_BE_OBJECT|MAY_BE_RESOURCE)) {
val_info |= MAY_BE_RC1|MAY_BE_RCN;
}
|7:
| // FREE_OP_DATA();
| FREE_OP (opline+1)->op1_type, (opline+1)->op1, val_info, 0, opline
| jmp >9
|.code
} else if (needs_val_dtor) {
|.cold_code
|7:
| // FREE_OP_DATA();
| FREE_OP (opline+1)->op1_type, (opline+1)->op1, val_info, 0, opline
| jmp >9
|.code
}
|9:
if (opline->op1_type != IS_UNUSED && !use_this && !op1_indirect) {
| FREE_OP opline->op1_type, opline->op1, op1_info, 1, opline
}
if (may_throw) {
if (!zend_jit_check_exception(Dst)) {
return 0;
}
}
return 1;
}
static int zend_jit_free(dasm_State **Dst, const zend_op *opline, uint32_t op1_info, int may_throw)
{
zend_jit_addr op1_addr = OP1_ADDR();
if (op1_info & (MAY_BE_STRING|MAY_BE_ARRAY|MAY_BE_OBJECT|MAY_BE_RESOURCE|MAY_BE_REF)) {
if (may_throw) {
| SET_EX_OPLINE opline, r0
}
if (opline->opcode == ZEND_FE_FREE && (op1_info & (MAY_BE_OBJECT|MAY_BE_REF))) {
if (op1_info & MAY_BE_ARRAY) {
| IF_ZVAL_TYPE op1_addr, IS_ARRAY, >7
}
| mov FCARG1d, dword [FP + opline->op1.var + offsetof(zval, u2.fe_iter_idx)]
| cmp FCARG1d, -1
| je >7
| EXT_CALL zend_hash_iterator_del, r0
|7:
}
| ZVAL_PTR_DTOR op1_addr, op1_info, 0, 0, opline
if (may_throw) {
if (!zend_jit_check_exception(Dst)) {
return 0;
}
}
}
return 1;
}
static int zend_jit_echo(dasm_State **Dst, const zend_op *opline, uint32_t op1_info)
{
if (opline->op1_type == IS_CONST) {
zval *zv;
size_t len;
zv = RT_CONSTANT(opline, opline->op1);
ZEND_ASSERT(Z_TYPE_P(zv) == IS_STRING);
len = Z_STRLEN_P(zv);
if (len > 0) {
const char *str = Z_STRVAL_P(zv);
| SET_EX_OPLINE opline, r0
|.if X64
| LOAD_ADDR CARG1, str
| LOAD_ADDR CARG2, len
| EXT_CALL zend_write, r0
|.else
| mov aword A2, len
| mov aword A1, str
| EXT_CALL zend_write, r0
|.endif
if (!zend_jit_check_exception(Dst)) {
return 0;
}
}
} else {
zend_jit_addr op1_addr = OP1_ADDR();
ZEND_ASSERT((op1_info & (MAY_BE_UNDEF|MAY_BE_ANY|MAY_BE_REF)) == MAY_BE_STRING);
| SET_EX_OPLINE opline, r0
| GET_ZVAL_PTR r0, op1_addr
|.if X64
| lea CARG1, aword [r0 + offsetof(zend_string, val)]
| mov CARG2, aword [r0 + offsetof(zend_string, len)]
| EXT_CALL zend_write, r0
|.else
| add r0, offsetof(zend_string, val)
| mov aword A1, r0
| mov r0, aword [r0 + (offsetof(zend_string, len)-offsetof(zend_string, val))]
| mov aword A2, r0
| EXT_CALL zend_write, r0
|.endif
if (opline->op1_type & (IS_VAR|IS_TMP_VAR)) {
| ZVAL_PTR_DTOR op1_addr, op1_info, 0, 0, opline
}
if (!zend_jit_check_exception(Dst)) {
return 0;
}
}
return 1;
}
static int zend_jit_strlen(dasm_State **Dst, const zend_op *opline, uint32_t op1_info, zend_jit_addr op1_addr)
{
zend_jit_addr res_addr = RES_ADDR();
if (opline->op1_type == IS_CONST) {
zval *zv;
size_t len;
zv = RT_CONSTANT(opline, opline->op1);
ZEND_ASSERT(Z_TYPE_P(zv) == IS_STRING);
len = Z_STRLEN_P(zv);
| SET_ZVAL_LVAL res_addr, len
| SET_ZVAL_TYPE_INFO res_addr, IS_LONG
} else {
ZEND_ASSERT((op1_info & (MAY_BE_UNDEF|MAY_BE_ANY|MAY_BE_REF)) == MAY_BE_STRING);
| GET_ZVAL_PTR r0, op1_addr
| mov r0, aword [r0 + offsetof(zend_string, len)]
| SET_ZVAL_LVAL res_addr, r0
| SET_ZVAL_TYPE_INFO res_addr, IS_LONG
| FREE_OP opline->op1_type, opline->op1, op1_info, 0, opline
}
return 1;
}
static int zend_jit_load_this(dasm_State **Dst, uint32_t var)
{
zend_jit_addr var_addr = ZEND_ADDR_MEM_ZVAL(ZREG_FP, var);
| mov FCARG1a, aword EX->This.value.ptr
| SET_ZVAL_PTR var_addr, FCARG1a
| SET_ZVAL_TYPE_INFO var_addr, IS_OBJECT_EX
| GC_ADDREF FCARG1a
return 1;
}
static int zend_jit_fetch_this(dasm_State **Dst, const zend_op *opline, const zend_op_array *op_array, zend_bool check_only)
{
if (!op_array->scope || (op_array->fn_flags & ZEND_ACC_STATIC)) {
if (JIT_G(trigger) == ZEND_JIT_ON_HOT_TRACE) {
if (!JIT_G(current_frame) ||
!TRACE_FRAME_IS_THIS_CHECKED(JIT_G(current_frame))) {
int32_t exit_point = zend_jit_trace_get_exit_point(opline, ZEND_JIT_EXIT_TO_VM);
const void *exit_addr = zend_jit_trace_get_exit_addr(exit_point);
| cmp byte EX->This.u1.v.type, IS_OBJECT
| jne &exit_addr
if (JIT_G(current_frame)) {
TRACE_FRAME_SET_THIS_CHECKED(JIT_G(current_frame));
}
}
} else {
| cmp byte EX->This.u1.v.type, IS_OBJECT
| jne >1
|.cold_code
|1:
| SET_EX_OPLINE opline, r0
| jmp ->invalid_this
|.code
}
}
if (!check_only) {
if (!zend_jit_load_this(Dst, opline->result.var)) {
return 0;
}
}
return 1;
}
static int zend_jit_hash_jmp(dasm_State **Dst, const zend_op *opline, const zend_op_array *op_array, zend_ssa *ssa, HashTable *jumptable, int default_b, const void *default_label, const zend_op *next_opline, zend_jit_trace_info *trace_info)
{
uint32_t count;
Bucket *p;
const zend_op *target;
int b;
int32_t exit_point;
const void *exit_addr;
| test r0, r0
if (default_label) {
| jz &default_label
} else if (next_opline) {
| jz >3
} else {
| jz =>default_b
}
| LOAD_ADDR FCARG1a, jumptable
| sub r0, aword [FCARG1a + offsetof(HashTable, arData)]
| mov FCARG1a, (sizeof(Bucket) / sizeof(void*))
|.if X64
| cqo
|.else
| cdq
|.endif
| idiv FCARG1a
|.if X64
if (!IS_32BIT(dasm_end)) {
| lea FCARG1a, aword [>4]
| jmp aword [FCARG1a + r0]
} else {
| jmp aword [r0 + >4]
}
|.else
| jmp aword [r0 + >4]
|.endif
|.jmp_table
|.align aword
|4:
if (trace_info) {
trace_info->jmp_table_size += zend_hash_num_elements(jumptable);
}
count = jumptable->nNumUsed;
p = jumptable->arData;
do {
if (Z_TYPE(p->val) == IS_UNDEF) {
if (default_label) {
| .aword &default_label
} else if (next_opline) {
| .aword >3
} else {
| .aword =>default_b
}
} else {
target = ZEND_OFFSET_TO_OPLINE(opline, Z_LVAL(p->val));
if (!next_opline) {
b = ssa->cfg.map[target - op_array->opcodes];
| .aword =>b
} else if (next_opline == target) {
| .aword >3
} else {
exit_point = zend_jit_trace_get_exit_point(target, 0);
exit_addr = zend_jit_trace_get_exit_addr(exit_point);
| .aword &exit_addr
}
}
p++;
count--;
} while (count);
|.code
return 1;
}
static int zend_jit_switch(dasm_State **Dst, const zend_op *opline, const zend_op_array *op_array, zend_ssa *ssa, zend_jit_trace_rec *trace, zend_jit_trace_info *trace_info)
{
HashTable *jumptable = Z_ARRVAL_P(RT_CONSTANT(opline, opline->op2));
const zend_op *next_opline = NULL;
if (trace) {
ZEND_ASSERT(trace->op == ZEND_JIT_TRACE_VM || trace->op == ZEND_JIT_TRACE_END);
ZEND_ASSERT(trace->opline != NULL);
next_opline = trace->opline;
}
if (opline->op1_type == IS_CONST) {
zval *zv = RT_CONSTANT(opline, opline->op1);
zval *jump_zv = NULL;
int b;
if (opline->opcode == ZEND_SWITCH_LONG) {
if (Z_TYPE_P(zv) == IS_LONG) {
jump_zv = zend_hash_index_find(jumptable, Z_LVAL_P(zv));
}
} else if (opline->opcode == ZEND_SWITCH_STRING) {
if (Z_TYPE_P(zv) == IS_STRING) {
jump_zv = zend_hash_find_ex(jumptable, Z_STR_P(zv), 1);
}
} else if (opline->opcode == ZEND_MATCH) {
if (Z_TYPE_P(zv) == IS_LONG) {
jump_zv = zend_hash_index_find(jumptable, Z_LVAL_P(zv));
} else if (Z_TYPE_P(zv) == IS_STRING) {
jump_zv = zend_hash_find_ex(jumptable, Z_STR_P(zv), 1);
}
} else {
ZEND_UNREACHABLE();
}
if (next_opline) {
const zend_op *target;
if (jump_zv != NULL) {
target = ZEND_OFFSET_TO_OPLINE(opline, Z_LVAL_P(jump_zv));
} else {
target = ZEND_OFFSET_TO_OPLINE(opline, opline->extended_value);
}
ZEND_ASSERT(target == next_opline);
} else {
if (jump_zv != NULL) {
b = ssa->cfg.map[ZEND_OFFSET_TO_OPLINE(opline, Z_LVAL_P(jump_zv)) - op_array->opcodes];
} else {
b = ssa->cfg.map[ZEND_OFFSET_TO_OPLINE(opline, opline->extended_value) - op_array->opcodes];
}
| jmp =>b
}
} else {
zend_ssa_op *ssa_op = &ssa->ops[opline - op_array->opcodes];
uint32_t op1_info = OP1_INFO();
zend_jit_addr op1_addr = OP1_ADDR();
const zend_op *default_opline = ZEND_OFFSET_TO_OPLINE(opline, opline->extended_value);
const zend_op *target;
int default_b = next_opline ? -1 : ssa->cfg.map[default_opline - op_array->opcodes];
int b;
int32_t exit_point;
const void *fallback_label = NULL;
const void *default_label = NULL;
const void *exit_addr;
if (next_opline) {
if (next_opline != opline + 1) {
exit_point = zend_jit_trace_get_exit_point(opline + 1, 0);
fallback_label = zend_jit_trace_get_exit_addr(exit_point);
}
if (next_opline != default_opline) {
exit_point = zend_jit_trace_get_exit_point(default_opline, 0);
default_label = zend_jit_trace_get_exit_addr(exit_point);
}
}
if (opline->opcode == ZEND_SWITCH_LONG) {
if (op1_info & MAY_BE_LONG) {
if (op1_info & MAY_BE_REF) {
| IF_NOT_ZVAL_TYPE op1_addr, IS_LONG, >1
| GET_ZVAL_LVAL ZREG_FCARG2a, op1_addr
|.cold_code
|1:
| // ZVAL_DEREF(op)
if (fallback_label) {
| IF_NOT_ZVAL_TYPE op1_addr, IS_REFERENCE, &fallback_label
} else {
| IF_NOT_ZVAL_TYPE op1_addr, IS_REFERENCE, >3
}
| GET_ZVAL_PTR FCARG2a, op1_addr
if (fallback_label) {
| IF_NOT_Z_TYPE FCARG2a + offsetof(zend_reference, val), IS_LONG, &fallback_label
} else {
| IF_NOT_Z_TYPE FCARG2a + offsetof(zend_reference, val), IS_LONG, >3
}
| mov FCARG2a, aword [FCARG2a + offsetof(zend_reference, val.value.lval)]
| jmp >2
|.code
|2:
} else {
if (op1_info & ((MAY_BE_ANY|MAY_BE_UNDEF)-MAY_BE_LONG)) {
if (fallback_label) {
| IF_NOT_ZVAL_TYPE op1_addr, IS_LONG, &fallback_label
} else {
| IF_NOT_ZVAL_TYPE op1_addr, IS_LONG, >3
}
}
| GET_ZVAL_LVAL ZREG_FCARG2a, op1_addr
}
if (HT_IS_PACKED(jumptable)) {
uint32_t count = jumptable->nNumUsed;
Bucket *p = jumptable->arData;
| cmp FCARG2a, jumptable->nNumUsed
if (default_label) {
| jae &default_label
} else if (next_opline) {
| jae >3
} else {
| jae =>default_b
}
|.if X64
if (!IS_32BIT(dasm_end)) {
| lea r0, aword [>4]
| jmp aword [r0 + FCARG2a * 8]
} else {
| jmp aword [FCARG2a * 8 + >4]
}
|.else
| jmp aword [FCARG2a * 4 + >4]
|.endif
|.jmp_table
|.align aword
|4:
if (trace_info) {
trace_info->jmp_table_size += count;
}
p = jumptable->arData;
do {
if (Z_TYPE(p->val) == IS_UNDEF) {
if (default_label) {
| .aword &default_label
} else if (next_opline) {
| .aword >3
} else {
| .aword =>default_b
}
} else {
target = ZEND_OFFSET_TO_OPLINE(opline, Z_LVAL(p->val));
if (!next_opline) {
b = ssa->cfg.map[target - op_array->opcodes];
| .aword =>b
} else if (next_opline == target) {
| .aword >3
} else {
exit_point = zend_jit_trace_get_exit_point(target, 0);
exit_addr = zend_jit_trace_get_exit_addr(exit_point);
| .aword &exit_addr
}
}
p++;
count--;
} while (count);
|.code
|3:
} else {
| LOAD_ADDR FCARG1a, jumptable
| EXT_CALL zend_hash_index_find, r0
if (!zend_jit_hash_jmp(Dst, opline, op_array, ssa, jumptable, default_b, default_label, next_opline, trace_info)) {
return 0;
}
|3:
}
}
} else if (opline->opcode == ZEND_SWITCH_STRING) {
if (op1_info & MAY_BE_STRING) {
if (op1_info & MAY_BE_REF) {
| IF_NOT_ZVAL_TYPE op1_addr, IS_STRING, >1
| GET_ZVAL_PTR FCARG2a, op1_addr
|.cold_code
|1:
| // ZVAL_DEREF(op)
if (fallback_label) {
| IF_NOT_ZVAL_TYPE op1_addr, IS_REFERENCE, &fallback_label
} else {
| IF_NOT_ZVAL_TYPE op1_addr, IS_REFERENCE, >3
}
| GET_ZVAL_PTR FCARG2a, op1_addr
if (fallback_label) {
| IF_NOT_Z_TYPE FCARG2a + offsetof(zend_reference, val), IS_STRING, &fallback_label
} else {
| IF_NOT_Z_TYPE FCARG2a + offsetof(zend_reference, val), IS_STRING, >3
}
| mov FCARG2a, aword [FCARG2a + offsetof(zend_reference, val.value.ptr)]
| jmp >2
|.code
|2:
} else {
if (op1_info & ((MAY_BE_ANY|MAY_BE_UNDEF)-MAY_BE_STRING)) {
if (fallback_label) {
| IF_NOT_ZVAL_TYPE op1_addr, IS_STRING, &fallback_label
} else {
| IF_NOT_ZVAL_TYPE op1_addr, IS_STRING, >3
}
}
| GET_ZVAL_PTR FCARG2a, op1_addr
}
| LOAD_ADDR FCARG1a, jumptable
| EXT_CALL zend_hash_find, r0
if (!zend_jit_hash_jmp(Dst, opline, op_array, ssa, jumptable, default_b, default_label, next_opline, trace_info)) {
return 0;
}
|3:
}
} else if (opline->opcode == ZEND_MATCH) {
if (op1_info & (MAY_BE_LONG|MAY_BE_STRING)) {
if (op1_info & MAY_BE_REF) {
| LOAD_ZVAL_ADDR FCARG2a, op1_addr
| ZVAL_DEREF FCARG2a, op1_info
op1_addr = ZEND_ADDR_MEM_ZVAL(ZREG_FCARG2a, 0);
}
| LOAD_ADDR FCARG1a, jumptable
if (op1_info & MAY_BE_LONG) {
if (op1_info & ((MAY_BE_ANY|MAY_BE_UNDEF)-MAY_BE_LONG)) {
if (op1_info & MAY_BE_STRING) {
| IF_NOT_ZVAL_TYPE op1_addr, IS_LONG, >5
} else if (op1_info & MAY_BE_UNDEF) {
| IF_NOT_ZVAL_TYPE op1_addr, IS_LONG, >6
} else if (default_label) {
| IF_NOT_ZVAL_TYPE op1_addr, IS_LONG, &default_label
} else if (next_opline) {
| IF_NOT_ZVAL_TYPE op1_addr, IS_LONG, >3
} else {
| IF_NOT_ZVAL_TYPE op1_addr, IS_LONG, =>default_b
}
}
| GET_ZVAL_LVAL ZREG_FCARG2a, op1_addr
| EXT_CALL zend_hash_index_find, r0
if (op1_info & MAY_BE_STRING) {
| jmp >2
}
}
if (op1_info & MAY_BE_STRING) {
|5:
if (op1_info & ((MAY_BE_ANY|MAY_BE_UNDEF)-(MAY_BE_LONG|MAY_BE_STRING))) {
if (op1_info & MAY_BE_UNDEF) {
| IF_NOT_ZVAL_TYPE op1_addr, IS_STRING, >6
} else if (default_label) {
| IF_NOT_ZVAL_TYPE op1_addr, IS_STRING, &default_label
} else if (next_opline) {
| IF_NOT_ZVAL_TYPE op1_addr, IS_STRING, >3
} else {
| IF_NOT_ZVAL_TYPE op1_addr, IS_STRING, =>default_b
}
}
| GET_ZVAL_PTR FCARG2a, op1_addr
| EXT_CALL zend_hash_find, r0
}
|2:
if (!zend_jit_hash_jmp(Dst, opline, op_array, ssa, jumptable, default_b, default_label, next_opline, trace_info)) {
return 0;
}
}
if (op1_info & MAY_BE_UNDEF) {
|6:
if (op1_info & (MAY_BE_ANY-(MAY_BE_LONG|MAY_BE_STRING))) {
if (default_label) {
| IF_NOT_ZVAL_TYPE op1_addr, IS_UNDEF, &default_label
} else if (next_opline) {
| IF_NOT_ZVAL_TYPE op1_addr, IS_UNDEF, >3
} else {
| IF_NOT_ZVAL_TYPE op1_addr, IS_UNDEF, =>default_b
}
}
| // zend_error(E_WARNING, "Undefined variable $%s", ZSTR_VAL(CV_DEF_OF(EX_VAR_TO_NUM(opline->op1.var))));
| SET_EX_OPLINE opline, r0
| mov FCARG1d, opline->op1.var
| EXT_CALL zend_jit_undefined_op_helper, r0
if (!zend_jit_check_exception_undef_result(Dst, opline)) {
return 0;
}
}
if (default_label) {
| jmp &default_label
} else if (next_opline) {
| jmp >3
} else {
| jmp =>default_b
}
|3:
} else {
ZEND_UNREACHABLE();
}
}
return 1;
}
static zend_bool zend_jit_verify_return_type(dasm_State **Dst, const zend_op *opline, const zend_op_array *op_array, uint32_t op1_info)
{
zend_arg_info *arg_info = &op_array->arg_info[-1];
ZEND_ASSERT(ZEND_TYPE_IS_SET(arg_info->type));
zend_jit_addr op1_addr = OP1_ADDR();
zend_bool needs_slow_check = 1;
zend_bool slow_check_in_cold = 1;
uint32_t type_mask = ZEND_TYPE_PURE_MASK(arg_info->type) & MAY_BE_ANY;
if (type_mask == 0) {
slow_check_in_cold = 0;
} else {
if (((op1_info & MAY_BE_ANY) & type_mask) == 0) {
slow_check_in_cold = 0;
} else if (((op1_info & MAY_BE_ANY) | type_mask) == type_mask) {
needs_slow_check = 0;
} else if (is_power_of_two(type_mask)) {
uint32_t type_code = concrete_type(type_mask);
| IF_NOT_ZVAL_TYPE op1_addr, type_code, >6
} else {
| mov edx, 1
| GET_ZVAL_TYPE cl, op1_addr
| shl edx, cl
| test edx, type_mask
| je >6
}
}
if (needs_slow_check) {
if (slow_check_in_cold) {
|.cold_code
|6:
}
| SET_EX_OPLINE opline, r1
if (op1_info & MAY_BE_UNDEF) {
| IF_NOT_ZVAL_TYPE op1_addr, IS_UNDEF, >7
| mov FCARG1a, opline->op1.var
| EXT_CALL zend_jit_undefined_op_helper, FCARG2a
| test r0, r0
| jz ->exception_handler
| LOAD_ADDR_ZTS FCARG1a, executor_globals, uninitialized_zval
| jmp >8
}
|7:
| LOAD_ZVAL_ADDR FCARG1a, op1_addr
|8:
| mov FCARG2a, EX->func
|.if X64
| LOAD_ADDR CARG3, (ptrdiff_t)arg_info
| mov r0, EX->run_time_cache
| lea CARG4, aword [r0+opline->op2.num]
| EXT_CALL zend_jit_verify_return_slow, r0
|.else
| sub r4, 8
| mov r0, EX->run_time_cache
| add r0, opline->op2.num
| push r0
| push (ptrdiff_t)arg_info
| EXT_CALL zend_jit_verify_return_slow, r0
| add r4, 8
|.endif
if (!zend_jit_check_exception(Dst)) {
return 0;
}
if (slow_check_in_cold) {
| jmp >9
|.code
}
}
|9:
return 1;
}
static int zend_jit_isset_isempty_cv(dasm_State **Dst, const zend_op *opline, uint32_t op1_info, zend_jit_addr op1_addr, zend_uchar smart_branch_opcode, uint32_t target_label, uint32_t target_label2, const void *exit_addr)
{
zend_jit_addr res_addr = ZEND_ADDR_MEM_ZVAL(ZREG_FP, opline->result.var);
// TODO: support for empty() ???
ZEND_ASSERT(!(opline->extended_value & ZEND_ISEMPTY));
if (op1_info & MAY_BE_REF) {
if (Z_MODE(op1_addr) != IS_MEM_ZVAL || Z_REG(op1_addr) != ZREG_FCARG1a || Z_OFFSET(op1_addr) != 0) {
| LOAD_ZVAL_ADDR FCARG1a, op1_addr
op1_addr = ZEND_ADDR_MEM_ZVAL(ZREG_FCARG1a, 0);
}
| ZVAL_DEREF FCARG1a, op1_info
|1:
}
if (!(op1_info & (MAY_BE_UNDEF|MAY_BE_NULL))) {
if (exit_addr) {
ZEND_ASSERT(smart_branch_opcode == ZEND_JMPZ);
} else if (smart_branch_opcode) {
if (smart_branch_opcode == ZEND_JMPNZ) {
| jmp =>target_label
} else if (smart_branch_opcode == ZEND_JMPZNZ) {
| jmp =>target_label2
}
} else {
| SET_ZVAL_TYPE_INFO res_addr, IS_TRUE
}
} else if (!(op1_info & (MAY_BE_ANY - MAY_BE_NULL))) {
if (exit_addr) {
ZEND_ASSERT(smart_branch_opcode == ZEND_JMPNZ);
} else if (smart_branch_opcode) {
if (smart_branch_opcode != ZEND_JMPNZ) {
| jmp =>target_label
}
} else {
| SET_ZVAL_TYPE_INFO res_addr, IS_FALSE
}
} else {
ZEND_ASSERT(Z_MODE(op1_addr) == IS_MEM_ZVAL);
| cmp byte [Ra(Z_REG(op1_addr))+Z_OFFSET(op1_addr)+offsetof(zval, u1.v.type)], IS_NULL
if (exit_addr) {
if (smart_branch_opcode == ZEND_JMPNZ) {
| jg &exit_addr
} else {
| jle &exit_addr
}
} else if (smart_branch_opcode) {
if (smart_branch_opcode == ZEND_JMPZ) {
| jle =>target_label
} else if (smart_branch_opcode == ZEND_JMPNZ) {
| jg =>target_label
} else if (smart_branch_opcode == ZEND_JMPZNZ) {
| jle =>target_label
| jmp =>target_label2
} else {
ZEND_UNREACHABLE();
}
} else {
| setg al
| movzx eax, al
| lea eax, [eax + IS_FALSE]
| SET_ZVAL_TYPE_INFO res_addr, eax
}
}
return 1;
}
static int zend_jit_fe_reset(dasm_State **Dst, const zend_op *opline, uint32_t op1_info)
{
zend_jit_addr res_addr = ZEND_ADDR_MEM_ZVAL(ZREG_FP, opline->result.var);
if (opline->op1_type == IS_CONST) {
zval *zv = RT_CONSTANT(opline, opline->op1);
| ZVAL_COPY_CONST res_addr, MAY_BE_ANY, MAY_BE_ANY, zv, ZREG_R0
if (Z_REFCOUNTED_P(zv)) {
| ADDREF_CONST zv, r0
}
} else {
zend_jit_addr op1_addr = ZEND_ADDR_MEM_ZVAL(ZREG_FP, opline->op1.var);
| // ZVAL_COPY(res, value);
| ZVAL_COPY_VALUE res_addr, -1, op1_addr, op1_info, ZREG_R0, ZREG_FCARG1a
if (opline->op1_type == IS_CV) {
| TRY_ADDREF op1_info, ah, FCARG1a
}
}
| // Z_FE_POS_P(res) = 0;
| mov dword [FP + opline->result.var + offsetof(zval, u2.fe_pos)], 0
return 1;
}
static int zend_jit_fe_fetch(dasm_State **Dst, const zend_op *opline, uint32_t op1_info, uint32_t op2_info, unsigned int target_label, zend_uchar exit_opcode, const void *exit_addr)
{
zend_jit_addr op1_addr = ZEND_ADDR_MEM_ZVAL(ZREG_FP, opline->op1.var);
| // array = EX_VAR(opline->op1.var);
| // fe_ht = Z_ARRVAL_P(array);
| GET_ZVAL_PTR FCARG2a, op1_addr
| // pos = Z_FE_POS_P(array);
| mov FCARG1d, dword [FP + opline->op1.var + offsetof(zval, u2.fe_pos)]
| // p = fe_ht->arData + pos;
|.if X64
|| ZEND_ASSERT(sizeof(Bucket) == 32);
| mov eax, FCARG1d
| shl r0, 5
|.else
| imul r0, FCARG1a, sizeof(Bucket)
|.endif
| add r0, aword [FCARG2a + offsetof(zend_array, arData)]
|1:
| // if (UNEXPECTED(pos >= fe_ht->nNumUsed)) {
| cmp dword [FCARG2a + offsetof(zend_array, nNumUsed)], FCARG1d
| // ZEND_VM_SET_RELATIVE_OPCODE(opline, opline->extended_value);
| // ZEND_VM_CONTINUE();
if (exit_addr) {
if (exit_opcode == ZEND_JMP) {
| jbe &exit_addr
} else {
| jbe >3
}
} else {
| jbe =>target_label
}
| // pos++;
| add FCARG1d, 1
| // value_type = Z_TYPE_INFO_P(value);
| // if (EXPECTED(value_type != IS_UNDEF)) {
| IF_Z_TYPE r0, IS_UNDEF, >2
if (!exit_addr || exit_opcode == ZEND_JMP) {
| IF_NOT_Z_TYPE r0, IS_INDIRECT, >3
} else {
| IF_NOT_Z_TYPE r0, IS_INDIRECT, &exit_addr
}
| // value = Z_INDIRECT_P(value);
| GET_Z_PTR FCARG2a, r0
| // value_type = Z_TYPE_INFO_P(value);
| // if (EXPECTED(value_type != IS_UNDEF)) {
if (!exit_addr || exit_opcode == ZEND_JMP) {
| IF_NOT_Z_TYPE FCARG2a, IS_UNDEF, >4
} else {
| IF_NOT_Z_TYPE r0, IS_UNDEF, &exit_addr
}
| GET_ZVAL_PTR FCARG2a, op1_addr // reload
|2:
| // p++;
| add r0, sizeof(Bucket)
| jmp <1
|3:
if (!exit_addr || exit_opcode == ZEND_JMP) {
zend_jit_addr val_addr = ZEND_ADDR_MEM_ZVAL(ZREG_FCARG2a, 0);
zend_jit_addr var_addr = ZEND_ADDR_MEM_ZVAL(ZREG_FP, opline->op2.var);
uint32_t val_info;
| mov FCARG2a, r0
|4:
| // Z_FE_POS_P(array) = pos + 1;
| mov dword [FP + opline->op1.var + offsetof(zval, u2.fe_pos)], FCARG1d
if (RETURN_VALUE_USED(opline)) {
zend_jit_addr res_addr = RES_ADDR();
if ((op1_info & MAY_BE_ARRAY_KEY_LONG)
&& (op1_info & MAY_BE_ARRAY_KEY_STRING)) {
| // if (!p->key) {
| cmp aword [r0 + offsetof(Bucket, key)], 0
| jz >2
}
if (op1_info & MAY_BE_ARRAY_KEY_STRING) {
| // ZVAL_STR_COPY(EX_VAR(opline->result.var), p->key);
| mov FCARG1a, aword [r0 + offsetof(Bucket, key)]
| SET_ZVAL_PTR res_addr, FCARG1a
| test dword [FCARG1a + offsetof(zend_refcounted, gc.u.type_info)], IS_STR_INTERNED
| jz >1
| SET_ZVAL_TYPE_INFO res_addr, IS_STRING
| jmp >3
|1:
| GC_ADDREF FCARG1a
| SET_ZVAL_TYPE_INFO res_addr, IS_STRING_EX
if (op1_info & MAY_BE_ARRAY_KEY_LONG) {
| jmp >3
|2:
}
}
if (op1_info & MAY_BE_ARRAY_KEY_LONG) {
| // ZVAL_LONG(EX_VAR(opline->result.var), p->h);
| mov FCARG1a, aword [r0 + offsetof(Bucket, h)]
| SET_ZVAL_LVAL res_addr, FCARG1a
| SET_ZVAL_TYPE_INFO res_addr, IS_LONG
}
|3:
}
val_info = ((op1_info & MAY_BE_ARRAY_OF_ANY) >> MAY_BE_ARRAY_SHIFT);
if (val_info & MAY_BE_ARRAY) {
val_info |= MAY_BE_ARRAY_KEY_ANY | MAY_BE_ARRAY_OF_ANY | MAY_BE_ARRAY_OF_REF;
}
if (op1_info & MAY_BE_ARRAY_OF_REF) {
val_info |= MAY_BE_REF | MAY_BE_RC1 | MAY_BE_RCN | MAY_BE_ANY |
MAY_BE_ARRAY_KEY_ANY | MAY_BE_ARRAY_OF_ANY | MAY_BE_ARRAY_OF_REF;
} else if (val_info & (MAY_BE_STRING|MAY_BE_ARRAY|MAY_BE_OBJECT|MAY_BE_RESOURCE)) {
val_info |= MAY_BE_RC1 | MAY_BE_RCN;
}
if (opline->op2_type == IS_CV) {
| // zend_assign_to_variable(variable_ptr, value, IS_CV, EX_USES_STRICT_TYPES());
if (!zend_jit_assign_to_variable(Dst, opline, var_addr, var_addr, op2_info, -1, IS_CV, val_addr, val_info, 0, 1)) {
return 0;
}
} else {
| // ZVAL_COPY(res, value);
| ZVAL_COPY_VALUE var_addr, -1, val_addr, val_info, ZREG_R0, ZREG_FCARG1a
| TRY_ADDREF val_info, ah, FCARG1a
}
}
return 1;
}
static int zend_jit_fetch_constant(dasm_State **Dst,
const zend_op *opline,
const zend_op_array *op_array,
zend_ssa *ssa,
const zend_ssa_op *ssa_op)
{
zval *zv = RT_CONSTANT(opline, opline->op2) + 1;
zend_jit_addr res_addr = ZEND_ADDR_MEM_ZVAL(ZREG_FP, opline->result.var);
zend_jit_addr const_addr = ZEND_ADDR_MEM_ZVAL(ZREG_R0, 0);
uint32_t res_info = RES_INFO();
| // c = CACHED_PTR(opline->extended_value);
| mov FCARG1a, EX->run_time_cache
| mov r0, aword [FCARG1a + opline->extended_value]
| // if (c != NULL)
| test r0, r0
| jz >9
| // if (!IS_SPECIAL_CACHE_VAL(c))
| test r0, CACHE_SPECIAL
| jnz >9
|8:
if ((res_info & MAY_BE_GUARD) && JIT_G(current_frame)) {
zend_jit_trace_stack *stack = JIT_G(current_frame)->stack;
uint32_t old_info = STACK_INFO(stack, EX_VAR_TO_NUM(opline->result.var));
int32_t exit_point;
const void *exit_addr = NULL;
SET_STACK_TYPE(stack, EX_VAR_TO_NUM(opline->result.var), IS_UNKNOWN, 1);
SET_STACK_REG(stack, EX_VAR_TO_NUM(opline->result.var), ZREG_ZVAL_COPY_R0);
exit_point = zend_jit_trace_get_exit_point(opline+1, 0);
SET_STACK_INFO(stack, EX_VAR_TO_NUM(opline->result.var), old_info);
exit_addr = zend_jit_trace_get_exit_addr(exit_point);
if (!exit_addr) {
return 0;
}
res_info &= ~MAY_BE_GUARD;
ssa->var_info[ssa_op->result_def].type &= ~MAY_BE_GUARD;
zend_uchar type = concrete_type(res_info);
if (type < IS_STRING) {
| IF_NOT_ZVAL_TYPE const_addr, type, &exit_addr
} else {
| GET_ZVAL_TYPE_INFO edx, const_addr
| IF_NOT_TYPE dl, type, &exit_addr
}
| ZVAL_COPY_VALUE_V res_addr, -1, const_addr, res_info, ZREG_R0, ZREG_R1
if (type < IS_STRING) {
| SET_ZVAL_TYPE_INFO res_addr, type
} else {
| SET_ZVAL_TYPE_INFO res_addr, edx
| TRY_ADDREF res_info, dh, r1
}
} else {
| // ZVAL_COPY_OR_DUP(EX_VAR(opline->result.var), &c->value); (no dup)
| ZVAL_COPY_VALUE res_addr, MAY_BE_ANY, const_addr, MAY_BE_ANY, ZREG_R0, ZREG_R1
| TRY_ADDREF MAY_BE_ANY, ah, r1
}
|.cold_code
|9:
| // SAVE_OPLINE();
| SET_EX_OPLINE opline, r0
| // zend_quick_get_constant(RT_CONSTANT(opline, opline->op2) + 1, opline->op1.num OPLINE_CC EXECUTE_DATA_CC);
| LOAD_ADDR FCARG1a, zv
| mov FCARG2a, opline->op1.num
| EXT_CALL zend_jit_get_constant, r0
| // ZEND_VM_NEXT_OPCODE_CHECK_EXCEPTION();
| test r0, r0
| jnz <8
| jmp ->exception_handler
|.code
return 1;
}
static int zend_jit_in_array(dasm_State **Dst, const zend_op *opline, uint32_t op1_info, zend_jit_addr op1_addr, zend_uchar smart_branch_opcode, uint32_t target_label, uint32_t target_label2, const void *exit_addr)
{
HashTable *ht = Z_ARRVAL_P(RT_CONSTANT(opline, opline->op2));
zend_jit_addr res_addr = ZEND_ADDR_MEM_ZVAL(ZREG_FP, opline->result.var);
ZEND_ASSERT(opline->op1_type != IS_VAR && opline->op1_type != IS_TMP_VAR);
ZEND_ASSERT((op1_info & (MAY_BE_ANY|MAY_BE_UNDEF|MAY_BE_REF)) == MAY_BE_STRING);
| // result = zend_hash_find_ex(ht, Z_STR_P(op1), OP1_TYPE == IS_CONST);
| LOAD_ADDR FCARG1a, ht
if (opline->op1_type != IS_CONST) {
| GET_ZVAL_PTR FCARG2a, op1_addr
| EXT_CALL zend_hash_find, r0
} else {
zend_string *str = Z_STR_P(RT_CONSTANT(opline, opline->op1));
| LOAD_ADDR FCARG2a, str
| EXT_CALL _zend_hash_find_known_hash, r0
}
| test r0, r0
if (exit_addr) {
if (smart_branch_opcode == ZEND_JMPZ) {
| jz &exit_addr
} else {
| jnz &exit_addr
}
} else if (smart_branch_opcode) {
if (smart_branch_opcode == ZEND_JMPZ) {
| jz =>target_label
} else if (smart_branch_opcode == ZEND_JMPNZ) {
| jnz =>target_label
} else if (smart_branch_opcode == ZEND_JMPZNZ) {
| jz =>target_label
| jmp =>target_label2
} else {
ZEND_UNREACHABLE();
}
} else {
| setnz al
| movzx eax, al
| lea eax, [eax + IS_FALSE]
| SET_ZVAL_TYPE_INFO res_addr, eax
}
return 1;
}
static zend_bool zend_jit_noref_guard(dasm_State **Dst, const zend_op *opline, zend_jit_addr var_addr)
{
int32_t exit_point = zend_jit_trace_get_exit_point(opline, 0);
const void *exit_addr = zend_jit_trace_get_exit_addr(exit_point);
if (!exit_addr) {
return 0;
}
| IF_ZVAL_TYPE var_addr, IS_REFERENCE, &exit_addr
return 1;
}
static zend_bool zend_jit_fetch_reference(dasm_State **Dst, const zend_op *opline, uint8_t var_type, uint32_t *var_info_ptr, zend_jit_addr *var_addr_ptr, zend_bool add_ref_guard, zend_bool add_type_guard)
{
zend_jit_addr var_addr = *var_addr_ptr;
uint32_t var_info = *var_info_ptr;
const void *exit_addr = NULL;
if (add_ref_guard || add_type_guard) {
int32_t exit_point = zend_jit_trace_get_exit_point(opline, 0);
exit_addr = zend_jit_trace_get_exit_addr(exit_point);
if (!exit_addr) {
return 0;
}
}
if (add_ref_guard) {
| IF_NOT_ZVAL_TYPE var_addr, IS_REFERENCE, &exit_addr
}
if (opline->opcode == ZEND_INIT_METHOD_CALL && opline->op1_type == IS_VAR) {
/* Hack: Convert reference to regular value to simplify JIT code for INIT_METHOD_CALL */
if (Z_REG(var_addr) != ZREG_FCARG1a || Z_OFFSET(var_addr) != 0) {
| LOAD_ZVAL_ADDR FCARG1a, var_addr
}
| EXT_CALL zend_jit_unref_helper, r0
} else {
| GET_ZVAL_PTR FCARG1a, var_addr
var_addr = ZEND_ADDR_MEM_ZVAL(ZREG_FCARG1a, offsetof(zend_reference, val));
*var_addr_ptr = var_addr;
}
if (var_type != IS_UNKNOWN) {
var_type &= ~(IS_TRACE_REFERENCE|IS_TRACE_INDIRECT|IS_TRACE_PACKED);
}
if (add_type_guard
&& var_type != IS_UNKNOWN
&& (var_info & (MAY_BE_ANY|MAY_BE_UNDEF)) != (1 << var_type)) {
| IF_NOT_ZVAL_TYPE var_addr, var_type, &exit_addr
ZEND_ASSERT(var_info & (1 << var_type));
if (var_type < IS_STRING) {
var_info = (1 << var_type);
} else if (var_type != IS_ARRAY) {
var_info = (1 << var_type) | (var_info & (MAY_BE_RC1|MAY_BE_RCN));
} else {
var_info = MAY_BE_ARRAY | (var_info & (MAY_BE_ARRAY_OF_ANY|MAY_BE_ARRAY_OF_REF|MAY_BE_ARRAY_KEY_ANY|MAY_BE_RC1|MAY_BE_RCN));
}
*var_info_ptr = var_info;
} else {
var_info &= ~MAY_BE_REF;
*var_info_ptr = var_info;
}
*var_info_ptr |= MAY_BE_GUARD; /* prevent generation of specialized zval dtor */
return 1;
}
static zend_bool zend_jit_fetch_indirect_var(dasm_State **Dst, const zend_op *opline, uint8_t var_type, uint32_t *var_info_ptr, zend_jit_addr *var_addr_ptr, zend_bool add_indirect_guard)
{
zend_jit_addr var_addr = *var_addr_ptr;
uint32_t var_info = *var_info_ptr;
int32_t exit_point;
const void *exit_addr;
if (add_indirect_guard) {
int32_t exit_point = zend_jit_trace_get_exit_point(opline, 0);
const void *exit_addr = zend_jit_trace_get_exit_addr(exit_point);
if (!exit_addr) {
return 0;
}
| IF_NOT_ZVAL_TYPE var_addr, IS_INDIRECT, &exit_addr
| GET_ZVAL_PTR FCARG1a, var_addr
} else {
/* May be already loaded into FCARG1a or RAX by previus FETCH_OBJ_W/DIM_W */
if (opline->op1_type != IS_VAR ||
(opline-1)->result_type != IS_VAR ||
(opline-1)->result.var != opline->op1.var ||
(opline-1)->op2_type == IS_VAR ||
(opline-1)->op2_type == IS_TMP_VAR) {
| GET_ZVAL_PTR FCARG1a, var_addr
} else if ((opline-1)->opcode == ZEND_FETCH_DIM_W || (opline-1)->opcode == ZEND_FETCH_DIM_RW) {
| mov FCARG1a, r0
}
}
*var_info_ptr &= ~MAY_BE_INDIRECT;
var_addr = ZEND_ADDR_MEM_ZVAL(ZREG_FCARG1a, 0);
*var_addr_ptr = var_addr;
if (var_type != IS_UNKNOWN) {
var_type &= ~(IS_TRACE_INDIRECT|IS_TRACE_PACKED);
}
if (!(var_type & IS_TRACE_REFERENCE)
&& var_type != IS_UNKNOWN
&& (var_info & (MAY_BE_ANY|MAY_BE_UNDEF)) != (1 << var_type)) {
exit_point = zend_jit_trace_get_exit_point(opline, 0);
exit_addr = zend_jit_trace_get_exit_addr(exit_point);
if (!exit_addr) {
return 0;
}
| IF_NOT_Z_TYPE FCARG1a, var_type, &exit_addr
//var_info = zend_jit_trace_type_to_info_ex(var_type, var_info);
ZEND_ASSERT(var_info & (1 << var_type));
if (var_type < IS_STRING) {
var_info = (1 << var_type);
} else if (var_type != IS_ARRAY) {
var_info = (1 << var_type) | (var_info & (MAY_BE_RC1|MAY_BE_RCN));
} else {
var_info = MAY_BE_ARRAY | (var_info & (MAY_BE_ARRAY_OF_ANY|MAY_BE_ARRAY_OF_REF|MAY_BE_ARRAY_KEY_ANY|MAY_BE_RC1|MAY_BE_RCN));
}
*var_info_ptr = var_info;
}
return 1;
}
static zend_bool zend_jit_may_reuse_reg(const zend_op *opline, const zend_ssa_op *ssa_op, zend_ssa *ssa, int def_var, int use_var)
{
if ((ssa->var_info[def_var].type & ~MAY_BE_GUARD) != (ssa->var_info[use_var].type & ~MAY_BE_GUARD)) {
return 0;
}
switch (opline->opcode) {
case ZEND_QM_ASSIGN:
case ZEND_SEND_VAR:
case ZEND_ASSIGN:
case ZEND_PRE_INC:
case ZEND_PRE_DEC:
case ZEND_POST_INC:
case ZEND_POST_DEC:
return 1;
case ZEND_ADD:
case ZEND_SUB:
case ZEND_MUL:
case ZEND_BW_OR:
case ZEND_BW_AND:
case ZEND_BW_XOR:
if (def_var == ssa_op->result_def &&
use_var == ssa_op->op1_use) {
return 1;
}
break;
default:
break;
}
return 0;
}
static zend_bool zend_jit_opline_supports_reg(const zend_op_array *op_array, zend_ssa *ssa, const zend_op *opline, const zend_ssa_op *ssa_op, zend_jit_trace_rec *trace)
{
uint32_t op1_info, op2_info;
switch (opline->opcode) {
case ZEND_SEND_VAR:
case ZEND_SEND_VAL:
case ZEND_SEND_VAL_EX:
return (opline->op2_type != IS_CONST);
case ZEND_QM_ASSIGN:
case ZEND_IS_SMALLER:
case ZEND_IS_SMALLER_OR_EQUAL:
case ZEND_IS_EQUAL:
case ZEND_IS_NOT_EQUAL:
case ZEND_IS_IDENTICAL:
case ZEND_IS_NOT_IDENTICAL:
case ZEND_CASE:
return 1;
case ZEND_RETURN:
return (op_array->type != ZEND_EVAL_CODE && op_array->function_name);
case ZEND_ASSIGN:
op1_info = OP1_INFO();
op2_info = OP2_INFO();
return
opline->op1_type == IS_CV &&
!(op1_info & (MAY_BE_STRING|MAY_BE_ARRAY|MAY_BE_RESOURCE|MAY_BE_OBJECT|MAY_BE_REF)) &&
!(op2_info & ((MAY_BE_ANY|MAY_BE_REF|MAY_BE_UNDEF)-(MAY_BE_LONG|MAY_BE_DOUBLE)));
case ZEND_ADD:
case ZEND_SUB:
case ZEND_MUL:
op1_info = OP1_INFO();
op2_info = OP2_INFO();
return !((op1_info | op2_info) & ((MAY_BE_ANY|MAY_BE_REF|MAY_BE_UNDEF) - (MAY_BE_LONG|MAY_BE_DOUBLE)));
case ZEND_BW_OR:
case ZEND_BW_AND:
case ZEND_BW_XOR:
case ZEND_SL:
case ZEND_SR:
case ZEND_MOD:
op1_info = OP1_INFO();
op2_info = OP2_INFO();
return !((op1_info | op2_info) & ((MAY_BE_ANY|MAY_BE_REF|MAY_BE_UNDEF) - MAY_BE_LONG));
case ZEND_PRE_INC:
case ZEND_PRE_DEC:
case ZEND_POST_INC:
case ZEND_POST_DEC:
op1_info = OP1_INFO();
return opline->op1_type == IS_CV && !(op1_info & ((MAY_BE_ANY|MAY_BE_REF|MAY_BE_UNDEF) - MAY_BE_LONG));
case ZEND_JMPZ:
case ZEND_JMPNZ:
if (JIT_G(trigger) != ZEND_JIT_ON_HOT_TRACE) {
if (!ssa->cfg.map) {
return 0;
}
if (opline > op_array->opcodes + ssa->cfg.blocks[ssa->cfg.map[opline-op_array->opcodes]].start &&
((opline-1)->result_type & (IS_SMART_BRANCH_JMPZ|IS_SMART_BRANCH_JMPNZ)) != 0) {
return 0;
}
}
/* break missing intentionally */
case ZEND_BOOL:
case ZEND_BOOL_NOT:
case ZEND_JMPZNZ:
case ZEND_JMPZ_EX:
case ZEND_JMPNZ_EX:
return 1;
case ZEND_FETCH_DIM_R:
op1_info = OP1_INFO();
op2_info = OP2_INFO();
if (trace
&& trace->op1_type != IS_UNKNOWN
&& (trace->op1_type & ~(IS_TRACE_REFERENCE|IS_TRACE_INDIRECT|IS_TRACE_PACKED)) == IS_ARRAY) {
op1_info &= ~((MAY_BE_ANY|MAY_BE_UNDEF) - MAY_BE_ARRAY);
}
return ((op1_info & (MAY_BE_ANY|MAY_BE_UNDEF)) == MAY_BE_ARRAY) &&
(!(opline->op1_type & (IS_TMP_VAR|IS_VAR)) || !(op1_info & MAY_BE_RC1)) &&
(((op2_info & (MAY_BE_ANY|MAY_BE_UNDEF)) == MAY_BE_LONG) ||
(((op2_info & (MAY_BE_ANY|MAY_BE_UNDEF)) == MAY_BE_STRING) &&
(!(opline->op2_type & (IS_TMP_VAR|IS_VAR)) || !(op2_info & MAY_BE_RC1))));
}
return 0;
}
static zend_bool zend_jit_var_supports_reg(zend_ssa *ssa, int var)
{
if (ssa->vars[var].no_val) {
/* we don't need the value */
return 0;
}
if (!(JIT_G(opt_flags) & ZEND_JIT_REG_ALLOC_GLOBAL)) {
/* Disable global register allocation,
* register allocation for SSA variables connected through Phi functions
*/
if (ssa->vars[var].definition_phi) {
return 0;
}
if (ssa->vars[var].phi_use_chain) {
zend_ssa_phi *phi = ssa->vars[var].phi_use_chain;
do {
if (!ssa->vars[phi->ssa_var].no_val) {
return 0;
}
phi = zend_ssa_next_use_phi(ssa, var, phi);
} while (phi);
}
}
if (((ssa->var_info[var].type & (MAY_BE_ANY|MAY_BE_UNDEF|MAY_BE_REF)) != MAY_BE_DOUBLE) &&
((ssa->var_info[var].type & (MAY_BE_ANY|MAY_BE_UNDEF|MAY_BE_REF)) != MAY_BE_LONG)) {
/* bad type */
return 0;
}
return 1;
}
static zend_bool zend_jit_may_be_in_reg(const zend_op_array *op_array, zend_ssa *ssa, int var)
{
if (!zend_jit_var_supports_reg(ssa, var)) {
return 0;
}
if (ssa->vars[var].definition >= 0) {
uint32_t def = ssa->vars[var].definition;
if (!zend_jit_opline_supports_reg(op_array, ssa, op_array->opcodes + def, ssa->ops + def, NULL)) {
return 0;
}
}
if (ssa->vars[var].use_chain >= 0) {
int use = ssa->vars[var].use_chain;
do {
if (!zend_ssa_is_no_val_use(op_array->opcodes + use, ssa->ops + use, var) &&
!zend_jit_opline_supports_reg(op_array, ssa, op_array->opcodes + use, ssa->ops + use, NULL)) {
return 0;
}
use = zend_ssa_next_use(ssa->ops, var, use);
} while (use >= 0);
}
return 1;
}
static zend_bool zend_needs_extra_reg_for_const(const zend_op *opline, zend_uchar op_type, znode_op op)
{
|.if X64
|| if (op_type == IS_CONST) {
|| zval *zv = RT_CONSTANT(opline, op);
|| if (Z_TYPE_P(zv) == IS_DOUBLE && Z_DVAL_P(zv) != 0 && !IS_SIGNED_32BIT(zv)) {
|| return 1;
|| } else if (Z_TYPE_P(zv) == IS_LONG && !IS_SIGNED_32BIT(Z_LVAL_P(zv))) {
|| return 1;
|| }
|| }
|.endif
return 0;
}
static zend_regset zend_jit_get_def_scratch_regset(const zend_op *opline, const zend_ssa_op *ssa_op, const zend_op_array *op_array, zend_ssa *ssa, int current_var, zend_bool last_use)
{
uint32_t op1_info, op2_info;
switch (opline->opcode) {
case ZEND_FETCH_DIM_R:
op1_info = OP1_INFO();
op2_info = OP2_INFO();
if (((opline->op1_type & (IS_TMP_VAR|IS_VAR)) &&
(op1_info & (MAY_BE_STRING|MAY_BE_ARRAY|MAY_BE_OBJECT|MAY_BE_RESOURCE))) ||
((opline->op2_type & (IS_TMP_VAR|IS_VAR)) &&
(op2_info & (MAY_BE_STRING|MAY_BE_ARRAY|MAY_BE_OBJECT|MAY_BE_RESOURCE)))) {
return ZEND_REGSET(ZREG_FCARG1a);
}
break;
default:
break;
}
return ZEND_REGSET_EMPTY;
}
static zend_regset zend_jit_get_scratch_regset(const zend_op *opline, const zend_ssa_op *ssa_op, const zend_op_array *op_array, zend_ssa *ssa, int current_var, zend_bool last_use)
{
uint32_t op1_info, op2_info, res_info;
zend_regset regset = ZEND_REGSET_SCRATCH;
switch (opline->opcode) {
case ZEND_NOP:
case ZEND_OP_DATA:
case ZEND_JMP:
case ZEND_RETURN:
regset = ZEND_REGSET_EMPTY;
break;
case ZEND_QM_ASSIGN:
if (ssa_op->op1_def == current_var ||
ssa_op->result_def == current_var) {
regset = ZEND_REGSET_EMPTY;
break;
}
/* break missing intentionally */
case ZEND_SEND_VAL:
case ZEND_SEND_VAL_EX:
if (opline->op2_type == IS_CONST) {
break;
}
if (ssa_op->op1_use == current_var) {
regset = ZEND_REGSET(ZREG_R0);
break;
}
op1_info = OP1_INFO();
if (!(op1_info & MAY_BE_UNDEF)) {
if ((op1_info & (MAY_BE_ANY|MAY_BE_REF)) == MAY_BE_DOUBLE) {
regset = ZEND_REGSET(ZREG_XMM0);
} else if ((op1_info & (MAY_BE_ANY|MAY_BE_REF)) == MAY_BE_LONG) {
regset = ZEND_REGSET(ZREG_R0);
} else {
regset = ZEND_REGSET_UNION(ZEND_REGSET(ZREG_R0), ZEND_REGSET(ZREG_R2));
}
}
break;
case ZEND_SEND_VAR:
if (opline->op2_type == IS_CONST) {
break;
}
if (ssa_op->op1_use == current_var ||
ssa_op->op1_def == current_var) {
regset = ZEND_REGSET_EMPTY;
break;
}
op1_info = OP1_INFO();
if (!(op1_info & MAY_BE_UNDEF)) {
if ((op1_info & (MAY_BE_ANY|MAY_BE_REF)) == MAY_BE_DOUBLE) {
regset = ZEND_REGSET(ZREG_XMM0);
} else if ((op1_info & (MAY_BE_ANY|MAY_BE_REF)) == MAY_BE_LONG) {
} else {
regset = ZEND_REGSET_UNION(ZEND_REGSET(ZREG_R0), ZEND_REGSET(ZREG_R2));
if (op1_info & MAY_BE_REF) {
ZEND_REGSET_INCL(regset, ZREG_R1);
}
}
}
break;
case ZEND_ASSIGN:
if (ssa_op->op2_use == current_var ||
ssa_op->op2_def == current_var ||
ssa_op->op1_def == current_var ||
ssa_op->result_def == current_var) {
regset = ZEND_REGSET_EMPTY;
break;
}
op1_info = OP1_INFO();
op2_info = OP2_INFO();
if (opline->op1_type == IS_CV
&& !(op2_info & MAY_BE_UNDEF)
&& !(op1_info & (MAY_BE_STRING|MAY_BE_ARRAY|MAY_BE_OBJECT|MAY_BE_RESOURCE|MAY_BE_REF))) {
if ((op2_info & (MAY_BE_ANY|MAY_BE_REF)) == MAY_BE_DOUBLE) {
regset = ZEND_REGSET(ZREG_XMM0);
} else if ((op2_info & (MAY_BE_ANY|MAY_BE_REF)) == MAY_BE_LONG) {
regset = ZEND_REGSET(ZREG_R0);
} else {
regset = ZEND_REGSET_UNION(ZEND_REGSET(ZREG_R0), ZEND_REGSET(ZREG_R2));
}
}
break;
case ZEND_PRE_INC:
case ZEND_PRE_DEC:
case ZEND_POST_INC:
case ZEND_POST_DEC:
if (ssa_op->op1_use == current_var ||
ssa_op->op1_def == current_var ||
ssa_op->result_def == current_var) {
regset = ZEND_REGSET_EMPTY;
break;
}
op1_info = OP1_INFO();
if (opline->op1_type == IS_CV
&& (op1_info & MAY_BE_LONG)
&& !(op1_info & ((MAY_BE_ANY|MAY_BE_REF|MAY_BE_UNDEF)-(MAY_BE_LONG|MAY_BE_DOUBLE)))) {
regset = ZEND_REGSET_EMPTY;
if (op1_info & MAY_BE_DOUBLE) {
regset = ZEND_REGSET(ZREG_XMM0);
}
if (opline->result_type != IS_UNUSED && (op1_info & MAY_BE_LONG)) {
ZEND_REGSET_INCL(regset, ZREG_R1);
}
}
break;
case ZEND_ADD:
case ZEND_SUB:
case ZEND_MUL:
op1_info = OP1_INFO();
op2_info = OP2_INFO();
if (!(op1_info & ((MAY_BE_ANY|MAY_BE_REF|MAY_BE_UNDEF)-(MAY_BE_LONG|MAY_BE_DOUBLE))) &&
!(op2_info & ((MAY_BE_ANY|MAY_BE_REF|MAY_BE_UNDEF)-(MAY_BE_LONG|MAY_BE_DOUBLE)))) {
regset = ZEND_REGSET_EMPTY;
if ((op1_info & MAY_BE_LONG) && (op2_info & MAY_BE_LONG)) {
if (ssa_op->result_def != current_var &&
(ssa_op->op1_use != current_var || !last_use)) {
ZEND_REGSET_INCL(regset, ZREG_R0);
}
res_info = RES_INFO();
if (res_info & MAY_BE_DOUBLE) {
ZEND_REGSET_INCL(regset, ZREG_R0);
ZEND_REGSET_INCL(regset, ZREG_XMM0);
ZEND_REGSET_INCL(regset, ZREG_XMM1);
} else if (res_info & MAY_BE_GUARD) {
ZEND_REGSET_INCL(regset, ZREG_R0);
}
}
if ((op1_info & MAY_BE_LONG) && (op2_info & MAY_BE_DOUBLE)) {
if (ssa_op->result_def != current_var) {
ZEND_REGSET_INCL(regset, ZREG_XMM0);
}
}
if ((op1_info & MAY_BE_DOUBLE) && (op2_info & MAY_BE_LONG)) {
if (zend_is_commutative(opline->opcode)) {
if (ssa_op->result_def != current_var) {
ZEND_REGSET_INCL(regset, ZREG_XMM0);
}
} else {
ZEND_REGSET_INCL(regset, ZREG_XMM0);
if (ssa_op->result_def != current_var &&
(ssa_op->op1_use != current_var || !last_use)) {
ZEND_REGSET_INCL(regset, ZREG_XMM1);
}
}
}
if ((op1_info & MAY_BE_DOUBLE) && (op2_info & MAY_BE_DOUBLE)) {
if (ssa_op->result_def != current_var &&
(ssa_op->op1_use != current_var || !last_use) &&
(!zend_is_commutative(opline->opcode) || ssa_op->op2_use != current_var || !last_use)) {
ZEND_REGSET_INCL(regset, ZREG_XMM0);
}
}
if (zend_needs_extra_reg_for_const(opline, opline->op1_type, opline->op1) ||
zend_needs_extra_reg_for_const(opline, opline->op2_type, opline->op2)) {
if (!ZEND_REGSET_IN(regset, ZREG_R0)) {
ZEND_REGSET_INCL(regset, ZREG_R0);
} else {
ZEND_REGSET_INCL(regset, ZREG_R1);
}
}
}
break;
case ZEND_BW_OR:
case ZEND_BW_AND:
case ZEND_BW_XOR:
op1_info = OP1_INFO();
op2_info = OP2_INFO();
if (!(op1_info & ((MAY_BE_ANY|MAY_BE_REF|MAY_BE_UNDEF)-MAY_BE_LONG)) &&
!(op2_info & ((MAY_BE_ANY|MAY_BE_REF|MAY_BE_UNDEF)-MAY_BE_LONG))) {
regset = ZEND_REGSET_EMPTY;
if (ssa_op->result_def != current_var &&
(ssa_op->op1_use != current_var || !last_use)) {
ZEND_REGSET_INCL(regset, ZREG_R0);
}
if (zend_needs_extra_reg_for_const(opline, opline->op1_type, opline->op1) ||
zend_needs_extra_reg_for_const(opline, opline->op2_type, opline->op2)) {
if (!ZEND_REGSET_IN(regset, ZREG_R0)) {
ZEND_REGSET_INCL(regset, ZREG_R0);
} else {
ZEND_REGSET_INCL(regset, ZREG_R1);
}
}
}
break;
case ZEND_SL:
case ZEND_SR:
op1_info = OP1_INFO();
op2_info = OP2_INFO();
if (!(op1_info & ((MAY_BE_ANY|MAY_BE_REF|MAY_BE_UNDEF)-MAY_BE_LONG)) &&
!(op2_info & ((MAY_BE_ANY|MAY_BE_REF|MAY_BE_UNDEF)-MAY_BE_LONG))) {
regset = ZEND_REGSET_EMPTY;
if (ssa_op->result_def != current_var &&
(ssa_op->op1_use != current_var || !last_use)) {
ZEND_REGSET_INCL(regset, ZREG_R0);
}
if (opline->op2_type != IS_CONST && ssa_op->op2_use != current_var) {
ZEND_REGSET_INCL(regset, ZREG_R1);
}
}
break;
case ZEND_MOD:
op1_info = OP1_INFO();
op2_info = OP2_INFO();
if (!(op1_info & ((MAY_BE_ANY|MAY_BE_REF|MAY_BE_UNDEF)-MAY_BE_LONG)) &&
!(op2_info & ((MAY_BE_ANY|MAY_BE_REF|MAY_BE_UNDEF)-MAY_BE_LONG))) {
regset = ZEND_REGSET_EMPTY;
if (opline->op2_type == IS_CONST &&
opline->op1_type != IS_CONST &&
Z_TYPE_P(RT_CONSTANT(opline, opline->op2)) == IS_LONG &&
zend_long_is_power_of_two(Z_LVAL_P(RT_CONSTANT(opline, opline->op2))) &&
OP1_HAS_RANGE() &&
OP1_MIN_RANGE() >= 0) {
if (ssa_op->result_def != current_var &&
(ssa_op->op1_use != current_var || !last_use)) {
ZEND_REGSET_INCL(regset, ZREG_R0);
}
if (sizeof(void*) == 8
&& !IS_SIGNED_32BIT(Z_LVAL_P(RT_CONSTANT(opline, opline->op2)) - 1)) {
if (!ZEND_REGSET_IN(regset, ZREG_R0)) {
ZEND_REGSET_INCL(regset, ZREG_R0);
} else {
ZEND_REGSET_INCL(regset, ZREG_R1);
}
}
} else {
ZEND_REGSET_INCL(regset, ZREG_R0);
ZEND_REGSET_INCL(regset, ZREG_R2);
if (opline->op2_type == IS_CONST) {
ZEND_REGSET_INCL(regset, ZREG_R1);
}
}
}
break;
case ZEND_IS_SMALLER:
case ZEND_IS_SMALLER_OR_EQUAL:
case ZEND_IS_EQUAL:
case ZEND_IS_NOT_EQUAL:
case ZEND_IS_IDENTICAL:
case ZEND_IS_NOT_IDENTICAL:
case ZEND_CASE:
op1_info = OP1_INFO();
op2_info = OP2_INFO();
if (!(op1_info & ((MAY_BE_ANY|MAY_BE_REF|MAY_BE_UNDEF)-(MAY_BE_LONG|MAY_BE_DOUBLE))) &&
!(op2_info & ((MAY_BE_ANY|MAY_BE_REF|MAY_BE_UNDEF)-(MAY_BE_LONG|MAY_BE_DOUBLE)))) {
regset = ZEND_REGSET_EMPTY;
if (!(opline->result_type & (IS_SMART_BRANCH_JMPZ|IS_SMART_BRANCH_JMPNZ))) {
ZEND_REGSET_INCL(regset, ZREG_R0);
}
if ((op1_info & MAY_BE_LONG) && (op2_info & MAY_BE_LONG) &&
opline->op1_type != IS_CONST && opline->op2_type != IS_CONST) {
if (ssa_op->op1_use != current_var &&
ssa_op->op2_use != current_var) {
ZEND_REGSET_INCL(regset, ZREG_R0);
}
}
if ((op1_info & MAY_BE_LONG) && (op2_info & MAY_BE_DOUBLE)) {
ZEND_REGSET_INCL(regset, ZREG_XMM0);
}
if ((op1_info & MAY_BE_DOUBLE) && (op2_info & MAY_BE_LONG)) {
ZEND_REGSET_INCL(regset, ZREG_XMM0);
}
if ((op1_info & MAY_BE_DOUBLE) && (op2_info & MAY_BE_DOUBLE)) {
if (ssa_op->op1_use != current_var &&
ssa_op->op2_use != current_var) {
ZEND_REGSET_INCL(regset, ZREG_XMM0);
}
}
if (zend_needs_extra_reg_for_const(opline, opline->op1_type, opline->op1) ||
zend_needs_extra_reg_for_const(opline, opline->op2_type, opline->op2)) {
ZEND_REGSET_INCL(regset, ZREG_R0);
}
}
break;
case ZEND_BOOL:
case ZEND_BOOL_NOT:
case ZEND_JMPZ:
case ZEND_JMPNZ:
case ZEND_JMPZNZ:
case ZEND_JMPZ_EX:
case ZEND_JMPNZ_EX:
op1_info = OP1_INFO();
if (!(op1_info & ((MAY_BE_ANY|MAY_BE_REF|MAY_BE_UNDEF)-(MAY_BE_NULL|MAY_BE_FALSE|MAY_BE_TRUE|MAY_BE_LONG|MAY_BE_DOUBLE)))) {
regset = ZEND_REGSET_EMPTY;
if (op1_info & MAY_BE_DOUBLE) {
ZEND_REGSET_INCL(regset, ZREG_XMM0);
}
if (opline->opcode == ZEND_BOOL ||
opline->opcode == ZEND_BOOL_NOT ||
opline->opcode == ZEND_JMPZ_EX ||
opline->opcode == ZEND_JMPNZ_EX) {
ZEND_REGSET_INCL(regset, ZREG_R0);
}
}
break;
case ZEND_DO_UCALL:
case ZEND_DO_FCALL:
case ZEND_DO_FCALL_BY_NAME:
case ZEND_INCLUDE_OR_EVAL:
case ZEND_GENERATOR_CREATE:
case ZEND_YIELD:
case ZEND_YIELD_FROM:
regset = ZEND_REGSET_UNION(ZEND_REGSET_GP, ZEND_REGSET_FP);
break;
default:
break;
}
if (JIT_G(trigger) == ZEND_JIT_ON_HOT_TRACE) {
if (ssa_op == ssa->ops
&& JIT_G(current_trace)[ZEND_JIT_TRACE_START_REC_SIZE].op == ZEND_JIT_TRACE_INIT_CALL
&& (JIT_G(current_trace)[ZEND_JIT_TRACE_START_REC_SIZE].info & ZEND_JIT_TRACE_FAKE_INIT_CALL)) {
ZEND_REGSET_INCL(regset, ZREG_R0);
ZEND_REGSET_INCL(regset, ZREG_R1);
}
}
/* %r0 is used to check EG(vm_interrupt) */
if (JIT_G(trigger) == ZEND_JIT_ON_HOT_TRACE) {
if (ssa_op == ssa->ops
&& (JIT_G(current_trace)->stop == ZEND_JIT_TRACE_STOP_LOOP ||
JIT_G(current_trace)->stop == ZEND_JIT_TRACE_STOP_RECURSIVE_CALL)) {
#if ZTS
ZEND_REGSET_INCL(regset, ZREG_R0);
#else
if ((sizeof(void*) == 8 && !IS_SIGNED_32BIT(&EG(vm_interrupt)))) {
ZEND_REGSET_INCL(regset, ZREG_R0);
}
#endif
}
} else {
uint32_t b = ssa->cfg.map[ssa_op - ssa->ops];
if ((ssa->cfg.blocks[b].flags & ZEND_BB_LOOP_HEADER) != 0
&& ssa->cfg.blocks[b].start == ssa_op - ssa->ops) {
#if ZTS
ZEND_REGSET_INCL(regset, ZREG_R0);
#else
if ((sizeof(void*) == 8 && !IS_SIGNED_32BIT(&EG(vm_interrupt)))) {
ZEND_REGSET_INCL(regset, ZREG_R0);
}
#endif
}
}
return regset;
}
#if defined(__clang__)
# pragma clang diagnostic pop
#endif
/*
* Local variables:
* tab-width: 4
* c-basic-offset: 4
* indent-tabs-mode: t
* End:
*/