mirror of
https://github.com/php/php-src.git
synced 2026-03-24 00:02:20 +01:00
* Better trace coverage (JIT trampoline calls) * clenup trampoline by zend_jit_free_trampoline() * Fix ZEND_JIT_TRACE_INIT_CALL/ZEND_JIT_TRACE_DO_ICALL num_args mismatch It may be caused by SEND_UNPACK/SEND_ARRAY * cleanup * cleanup * Don't record function that may be temporary * cleanup * Prevent invalid run_time_cache allocation for "bad" internal functions * Update zend_jit_trace_record_fake_init_call_ex() accordingly * Better handling of "bad" functions and fake closures
17758 lines
559 KiB
C
17758 lines
559 KiB
C
/*
|
|
* +----------------------------------------------------------------------+
|
|
* | 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: |
|
|
* | https://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 |
|
|
* | license@php.net so we can mail you a copy immediately. |
|
|
* +----------------------------------------------------------------------+
|
|
* | Authors: Dmitry Stogov <dmitry@php.net> |
|
|
* +----------------------------------------------------------------------+
|
|
*/
|
|
|
|
#include "jit/ir/ir.h"
|
|
#include "jit/ir/ir_builder.h"
|
|
|
|
#if defined(IR_TARGET_X86)
|
|
# define IR_REG_SP 4 /* IR_REG_RSP */
|
|
# define IR_REG_FP 5 /* IR_REG_RBP */
|
|
# define ZREG_FP 6 /* IR_REG_RSI */
|
|
# define ZREG_IP 7 /* IR_REG_RDI */
|
|
# define ZREG_FIRST_FPR 8
|
|
# define IR_REGSET_PRESERVED ((1<<3) | (1<<5) | (1<<6) | (1<<7)) /* all preserved registers */
|
|
#elif defined(IR_TARGET_X64)
|
|
# define IR_REG_SP 4 /* IR_REG_RSP */
|
|
# define IR_REG_FP 5 /* IR_REG_RBP */
|
|
# define ZREG_FP 14 /* IR_REG_R14 */
|
|
# define ZREG_IP 15 /* IR_REG_R15 */
|
|
# define ZREG_FIRST_FPR 16
|
|
# if defined(_WIN64)
|
|
# define IR_REGSET_PRESERVED ((1<<3) | (1<<5) | (1<<6) | (1<<7) | (1<<12) | (1<<13) | (1<<14) | (1<<15))
|
|
/*
|
|
# define IR_REGSET_PRESERVED ((1<<3) | (1<<5) | (1<<6) | (1<<7) | (1<<12) | (1<<13) | (1<<14) | (1<<15) | \
|
|
(1<<(16+6)) | (1<<(16+7)) | (1<<(16+8)) | (1<<(16+9)) | (1<<(16+10)) | \
|
|
(1<<(16+11)) | (1<<(16+12)) | (1<<(16+13)) | (1<<(16+14)) | (1<<(16+15)))
|
|
*/
|
|
# else
|
|
# define IR_REGSET_PRESERVED ((1<<3) | (1<<5) | (1<<12) | (1<<13) | (1<<14) | (1<<15)) /* all preserved registers */
|
|
# endif
|
|
#elif defined(IR_TARGET_AARCH64)
|
|
# define IR_REG_SP 31 /* IR_REG_RSP */
|
|
# define IR_REG_FP 29 /* IR_REG_X29 */
|
|
# define ZREG_FP 27 /* IR_REG_X27 */
|
|
# define ZREG_IP 28 /* IR_REG_X28 */
|
|
# define ZREG_FIRST_FPR 32
|
|
# define IR_REGSET_PRESERVED ((1<<19) | (1<<20) | (1<<21) | (1<<22) | (1<<23) | \
|
|
(1<<24) | (1<<25) | (1<<26) | (1<<27) | (1<<28)) /* all preserved registers */
|
|
#else
|
|
# error "Unknown IR target"
|
|
#endif
|
|
|
|
#define ZREG_RX ZREG_IP
|
|
|
|
#define OPTIMIZE_FOR_SIZE 0
|
|
|
|
/* IR builder defines */
|
|
#undef _ir_CTX
|
|
#define _ir_CTX (&jit->ctx)
|
|
|
|
#undef ir_CONST_ADDR
|
|
#define ir_CONST_ADDR(_addr) jit_CONST_ADDR(jit, (uintptr_t)(_addr))
|
|
#define ir_CONST_FUNC(_addr) jit_CONST_FUNC(jit, (uintptr_t)(_addr), 0)
|
|
#define ir_CONST_FC_FUNC(_addr) jit_CONST_FUNC(jit, (uintptr_t)(_addr), IR_FASTCALL_FUNC)
|
|
#define ir_CAST_FC_FUNC(_addr) ir_fold2(_ir_CTX, IR_OPT(IR_PROTO, IR_ADDR), (_addr), \
|
|
ir_proto_0(_ir_CTX, IR_FASTCALL_FUNC, IR_I32))
|
|
|
|
#define ir_CONST_FUNC_PROTO(_addr, _proto) \
|
|
jit_CONST_FUNC_PROTO(jit, (uintptr_t)(_addr), (_proto))
|
|
|
|
#undef ir_ADD_OFFSET
|
|
#define ir_ADD_OFFSET(_addr, _offset) \
|
|
jit_ADD_OFFSET(jit, _addr, _offset)
|
|
|
|
#ifdef ZEND_ENABLE_ZVAL_LONG64
|
|
# define IR_LONG IR_I64
|
|
# define ir_CONST_LONG ir_CONST_I64
|
|
# define ir_UNARY_OP_L ir_UNARY_OP_I64
|
|
# define ir_BINARY_OP_L ir_BINARY_OP_I64
|
|
# define ir_ADD_L ir_ADD_I64
|
|
# define ir_SUB_L ir_SUB_I64
|
|
# define ir_MUL_L ir_MUL_I64
|
|
# define ir_DIV_L ir_DIV_I64
|
|
# define ir_MOD_L ir_MOD_I64
|
|
# define ir_NEG_L ir_NEG_I64
|
|
# define ir_ABS_L ir_ABS_I64
|
|
# define ir_SEXT_L ir_SEXT_I64
|
|
# define ir_ZEXT_L ir_ZEXT_I64
|
|
# define ir_TRUNC_L ir_TRUNC_I64
|
|
# define ir_BITCAST_L ir_BITCAST_I64
|
|
# define ir_FP2L ir_FP2I64
|
|
# define ir_ADD_OV_L ir_ADD_OV_I64
|
|
# define ir_SUB_OV_L ir_SUB_OV_I64
|
|
# define ir_MUL_OV_L ir_MUL_OV_I64
|
|
# define ir_NOT_L ir_NOT_I64
|
|
# define ir_OR_L ir_OR_I64
|
|
# define ir_AND_L ir_AND_I64
|
|
# define ir_XOR_L ir_XOR_I64
|
|
# define ir_SHL_L ir_SHL_I64
|
|
# define ir_SHR_L ir_SHR_I64
|
|
# define ir_SAR_L ir_SAR_I64
|
|
# define ir_ROL_L ir_ROL_I64
|
|
# define ir_ROR_L ir_ROR_I64
|
|
# define ir_MIN_L ir_MIN_I64
|
|
# define ir_MAX_L ir_MAX_I64
|
|
# define ir_LOAD_L ir_LOAD_I64
|
|
#else
|
|
# define IR_LONG IR_I32
|
|
# define ir_CONST_LONG ir_CONST_I32
|
|
# define ir_UNARY_OP_L ir_UNARY_OP_I32
|
|
# define ir_BINARY_OP_L ir_BINARY_OP_I32
|
|
# define ir_ADD_L ir_ADD_I32
|
|
# define ir_SUB_L ir_SUB_I32
|
|
# define ir_MUL_L ir_MUL_I32
|
|
# define ir_DIV_L ir_DIV_I32
|
|
# define ir_MOD_L ir_MOD_I32
|
|
# define ir_NEG_L ir_NEG_I32
|
|
# define ir_ABS_L ir_ABS_I32
|
|
# define ir_SEXT_L ir_SEXT_I32
|
|
# define ir_ZEXT_L ir_ZEXT_I32
|
|
# define ir_TRUNC_L ir_TRUNC_I32
|
|
# define ir_BITCAST_L ir_BITCAST_I32
|
|
# define ir_FP2L ir_FP2I32
|
|
# define ir_ADD_OV_L ir_ADD_OV_I32
|
|
# define ir_SUB_OV_L ir_SUB_OV_I32
|
|
# define ir_MUL_OV_L ir_MUL_OV_I32
|
|
# define ir_NOT_L ir_NOT_I32
|
|
# define ir_OR_L ir_OR_I32
|
|
# define ir_AND_L ir_AND_I32
|
|
# define ir_XOR_L ir_XOR_I32
|
|
# define ir_SHL_L ir_SHL_I32
|
|
# define ir_SHR_L ir_SHR_I32
|
|
# define ir_SAR_L ir_SAR_I32
|
|
# define ir_ROL_L ir_ROL_I32
|
|
# define ir_ROR_L ir_ROR_I32
|
|
# define ir_MIN_L ir_MIN_I32
|
|
# define ir_MAX_L ir_MAX_I32
|
|
# define ir_LOAD_L ir_LOAD_I32
|
|
#endif
|
|
|
|
/* A helper structure to collect IT rers for the following use in (MERGE/PHI)_N */
|
|
typedef struct _ir_refs {
|
|
uint32_t count;
|
|
uint32_t limit;
|
|
ir_ref refs[] ZEND_ELEMENT_COUNT(count);
|
|
} ir_refs;
|
|
|
|
#define ir_refs_size(_n) (offsetof(ir_refs, refs) + sizeof(ir_ref) * (_n))
|
|
#define ir_refs_init(_name, _n) _name = alloca(ir_refs_size(_n)); \
|
|
do {_name->count = 0; _name->limit = (_n);} while (0)
|
|
|
|
static void ir_refs_add(ir_refs *refs, ir_ref ref)
|
|
{
|
|
ir_ref *ptr;
|
|
|
|
ZEND_ASSERT(refs->count < refs->limit);
|
|
ptr = refs->refs;
|
|
ptr[refs->count++] = ref;
|
|
}
|
|
|
|
static size_t zend_jit_trace_prologue_size = (size_t)-1;
|
|
#if defined(IR_TARGET_X86) || defined(IR_TARGET_X64)
|
|
static uint32_t allowed_opt_flags = 0;
|
|
static uint32_t default_mflags = 0;
|
|
#endif
|
|
static bool delayed_call_chain = 0; // TODO: remove this var (use jit->delayed_call_level) ???
|
|
|
|
#ifdef ZTS
|
|
# ifdef _WIN32
|
|
extern uint32_t _tls_index;
|
|
extern char *_tls_start;
|
|
extern char *_tls_end;
|
|
# endif
|
|
|
|
static size_t tsrm_ls_cache_tcb_offset = 0;
|
|
static size_t tsrm_tls_index = 0;
|
|
static size_t tsrm_tls_offset = 0;
|
|
|
|
# define EG_TLS_OFFSET(field) \
|
|
(executor_globals_offset + offsetof(zend_executor_globals, field))
|
|
|
|
# define CG_TLS_OFFSET(field) \
|
|
(compiler_globals_offset + offsetof(zend_compiler_globals, field))
|
|
|
|
# define jit_EG(_field) \
|
|
ir_ADD_OFFSET(jit_TLS(jit), EG_TLS_OFFSET(_field))
|
|
|
|
# define jit_CG(_field) \
|
|
ir_ADD_OFFSET(jit_TLS(jit), CG_TLS_OFFSET(_field))
|
|
|
|
#else
|
|
|
|
# define jit_EG(_field) \
|
|
ir_CONST_ADDR(&EG(_field))
|
|
|
|
# define jit_CG(_field) \
|
|
ir_CONST_ADDR(&CG(_field))
|
|
|
|
#endif
|
|
|
|
#define jit_CALL(_call, _field) \
|
|
ir_ADD_OFFSET(_call, offsetof(zend_execute_data, _field))
|
|
|
|
#define jit_EX(_field) \
|
|
jit_CALL(jit_FP(jit), _field)
|
|
|
|
#define jit_RX(_field) \
|
|
jit_CALL(jit_IP(jit), _field)
|
|
|
|
#define JIT_STUBS(_) \
|
|
_(exception_handler, IR_SKIP_PROLOGUE) \
|
|
_(exception_handler_undef, IR_SKIP_PROLOGUE) \
|
|
_(exception_handler_free_op2, IR_SKIP_PROLOGUE) \
|
|
_(exception_handler_free_op1_op2, IR_SKIP_PROLOGUE) \
|
|
_(interrupt_handler, IR_SKIP_PROLOGUE) \
|
|
_(leave_function_handler, IR_SKIP_PROLOGUE) \
|
|
_(negative_shift, IR_SKIP_PROLOGUE) \
|
|
_(mod_by_zero, IR_SKIP_PROLOGUE) \
|
|
_(invalid_this, IR_SKIP_PROLOGUE) \
|
|
_(undefined_function, IR_SKIP_PROLOGUE) \
|
|
_(throw_cannot_pass_by_ref, IR_SKIP_PROLOGUE) \
|
|
_(icall_throw, IR_SKIP_PROLOGUE) \
|
|
_(leave_throw, IR_SKIP_PROLOGUE) \
|
|
_(hybrid_runtime_jit, IR_SKIP_PROLOGUE | IR_START_BR_TARGET) \
|
|
_(hybrid_profile_jit, IR_SKIP_PROLOGUE | IR_START_BR_TARGET) \
|
|
_(hybrid_func_hot_counter, IR_SKIP_PROLOGUE | IR_START_BR_TARGET) \
|
|
_(hybrid_loop_hot_counter, IR_SKIP_PROLOGUE | IR_START_BR_TARGET) \
|
|
_(hybrid_func_trace_counter, IR_SKIP_PROLOGUE | IR_START_BR_TARGET) \
|
|
_(hybrid_ret_trace_counter, IR_SKIP_PROLOGUE | IR_START_BR_TARGET) \
|
|
_(hybrid_loop_trace_counter, IR_SKIP_PROLOGUE | IR_START_BR_TARGET) \
|
|
_(trace_halt, IR_SKIP_PROLOGUE) \
|
|
_(trace_escape, IR_SKIP_PROLOGUE) \
|
|
_(trace_exit, IR_SKIP_PROLOGUE) \
|
|
_(undefined_offset, IR_FUNCTION | IR_FASTCALL_FUNC) \
|
|
_(undefined_key, IR_FUNCTION | IR_FASTCALL_FUNC) \
|
|
_(cannot_add_element, IR_FUNCTION | IR_FASTCALL_FUNC) \
|
|
_(assign_const, IR_FUNCTION | IR_FASTCALL_FUNC) \
|
|
_(assign_tmp, IR_FUNCTION | IR_FASTCALL_FUNC) \
|
|
_(assign_var, IR_FUNCTION | IR_FASTCALL_FUNC) \
|
|
_(assign_cv_noref, IR_FUNCTION | IR_FASTCALL_FUNC) \
|
|
_(assign_cv, IR_FUNCTION | IR_FASTCALL_FUNC) \
|
|
_(new_array, IR_FUNCTION | IR_FASTCALL_FUNC) \
|
|
|
|
#define JIT_STUB_ID(name, flags) \
|
|
jit_stub_ ## name,
|
|
|
|
#define JIT_STUB_FORWARD(name, flags) \
|
|
static int zend_jit_ ## name ## _stub(zend_jit_ctx *jit);
|
|
|
|
#define JIT_STUB(name, flags) \
|
|
{JIT_STUB_PREFIX #name, zend_jit_ ## name ## _stub, flags},
|
|
|
|
typedef enum _jit_stub_id {
|
|
JIT_STUBS(JIT_STUB_ID)
|
|
jit_last_stub
|
|
} jit_stub_id;
|
|
|
|
typedef struct _zend_jit_reg_var {
|
|
ir_ref ref;
|
|
uint32_t flags;
|
|
} zend_jit_reg_var;
|
|
|
|
typedef struct _zend_jit_ctx {
|
|
ir_ctx ctx;
|
|
const zend_op *last_valid_opline;
|
|
bool use_last_valid_opline;
|
|
bool track_last_valid_opline;
|
|
bool reuse_ip;
|
|
uint32_t delayed_call_level;
|
|
int b; /* current basic block number or -1 */
|
|
#ifdef ZTS
|
|
ir_ref tls;
|
|
#endif
|
|
ir_ref fp;
|
|
ir_ref trace_loop_ref;
|
|
ir_ref return_inputs;
|
|
const zend_op_array *op_array;
|
|
const zend_op_array *current_op_array;
|
|
zend_ssa *ssa;
|
|
zend_string *name;
|
|
ir_ref *bb_start_ref; /* PHP BB -> IR ref mapping */
|
|
ir_ref *bb_predecessors; /* PHP BB -> index in bb_edges -> IR refs of predessors */
|
|
ir_ref *bb_edges;
|
|
zend_jit_trace_info *trace;
|
|
zend_jit_reg_var *ra;
|
|
int delay_var;
|
|
ir_refs *delay_refs;
|
|
ir_ref eg_exception_addr;
|
|
HashTable addr_hash;
|
|
ir_ref stub_addr[jit_last_stub];
|
|
} zend_jit_ctx;
|
|
|
|
typedef int8_t zend_reg;
|
|
|
|
typedef struct _zend_jit_registers_buf {
|
|
#if defined(IR_TARGET_X64)
|
|
uint64_t gpr[16]; /* general purpose integer register */
|
|
double fpr[16]; /* floating point registers */
|
|
#elif defined(IR_TARGET_X86)
|
|
uint32_t gpr[8]; /* general purpose integer register */
|
|
double fpr[8]; /* floating point registers */
|
|
#elif defined (IR_TARGET_AARCH64)
|
|
uint64_t gpr[32]; /* general purpose integer register */
|
|
double fpr[32]; /* floating point registers */
|
|
#else
|
|
# error "Unknown IR target"
|
|
#endif
|
|
} zend_jit_registers_buf;
|
|
|
|
/* 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 uint32_t zend_jit_exit_point_by_addr(const void *addr);
|
|
int ZEND_FASTCALL zend_jit_trace_exit(uint32_t exit_num, zend_jit_registers_buf *regs);
|
|
|
|
static int zend_jit_assign_to_variable(zend_jit_ctx *jit,
|
|
const zend_op *opline,
|
|
zend_jit_addr var_use_addr,
|
|
zend_jit_addr var_addr,
|
|
uint32_t var_info,
|
|
uint32_t var_def_info,
|
|
uint8_t val_type,
|
|
zend_jit_addr val_addr,
|
|
uint32_t val_info,
|
|
zend_jit_addr res_addr,
|
|
zend_jit_addr ref_addr,
|
|
bool check_exception);
|
|
|
|
typedef struct _zend_jit_stub {
|
|
const char *name;
|
|
int (*stub)(zend_jit_ctx *jit);
|
|
uint32_t flags;
|
|
} zend_jit_stub;
|
|
|
|
JIT_STUBS(JIT_STUB_FORWARD)
|
|
|
|
static const zend_jit_stub zend_jit_stubs[] = {
|
|
JIT_STUBS(JIT_STUB)
|
|
};
|
|
|
|
#if defined(_WIN32) || defined(IR_TARGET_AARCH64)
|
|
/* We keep addresses in SHM to share them between sepaeate processes (on Windows) or to support veneers (on AArch64) */
|
|
static void** zend_jit_stub_handlers = NULL;
|
|
#else
|
|
static void* zend_jit_stub_handlers[sizeof(zend_jit_stubs) / sizeof(zend_jit_stubs[0])];
|
|
#endif
|
|
|
|
#if defined(IR_TARGET_AARCH64)
|
|
|
|
# ifdef __FreeBSD__
|
|
/* https://github.com/freebsd/freebsd-src/blob/c52ca7dd09066648b1cc40f758289404d68ab886/libexec/rtld-elf/aarch64/reloc.c#L180-L184 */
|
|
typedef struct TLSDescriptor {
|
|
void* thunk;
|
|
int index;
|
|
size_t offset;
|
|
} TLSDescriptor;
|
|
# endif
|
|
|
|
#define IR_HAS_VENEERS (1U<<31) /* IR_RESERVED_FLAG_1 */
|
|
|
|
static const void *zend_jit_get_veneer(ir_ctx *ctx, const void *addr)
|
|
{
|
|
int i, count = sizeof(zend_jit_stubs) / sizeof(zend_jit_stubs[0]);
|
|
|
|
for (i = 0; i < count; i++) {
|
|
if (zend_jit_stub_handlers[i] == addr) {
|
|
return zend_jit_stub_handlers[count + i];
|
|
}
|
|
}
|
|
|
|
if (((zend_jit_ctx*)ctx)->trace
|
|
&& (void*)addr >= dasm_buf && (void*)addr < dasm_end) {
|
|
uint32_t exit_point = zend_jit_exit_point_by_addr(addr);
|
|
|
|
if (exit_point != (uint32_t)-1) {
|
|
zend_jit_trace_info *t = ((zend_jit_ctx*)ctx)->trace;
|
|
|
|
ZEND_ASSERT(exit_point < t->exit_count);
|
|
return (const void*)((char*)ctx->deoptimization_exits_base + (exit_point * 4));
|
|
}
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
static bool zend_jit_set_veneer(ir_ctx *ctx, const void *addr, const void *veneer)
|
|
{
|
|
int i, count = sizeof(zend_jit_stubs) / sizeof(zend_jit_stubs[0]);
|
|
uint32_t exit_point = zend_jit_exit_point_by_addr(addr);
|
|
|
|
if (exit_point != (uint32_t)-1) {
|
|
return 1;
|
|
}
|
|
for (i = 0; i < count; i++) {
|
|
if (zend_jit_stub_handlers[i] == addr) {
|
|
const void **ptr = (const void**)&zend_jit_stub_handlers[count + i];
|
|
*ptr = veneer;
|
|
ctx->flags2 |= IR_HAS_VENEERS;
|
|
#ifdef HAVE_CAPSTONE
|
|
int64_t offset;
|
|
if (JIT_G(debug) & ZEND_JIT_DEBUG_ASM) {
|
|
const char *name = ir_disasm_find_symbol((uint64_t)(uintptr_t)addr, &offset);
|
|
|
|
if (name && !offset) {
|
|
if (strstr(name, "@veneer") == NULL) {
|
|
char *new_name;
|
|
|
|
zend_spprintf(&new_name, 0, "%s@veneer", name);
|
|
ir_disasm_add_symbol(new_name, (uint64_t)(uintptr_t)veneer, 4);
|
|
efree(new_name);
|
|
} else {
|
|
ir_disasm_add_symbol(name, (uint64_t)(uintptr_t)veneer, 4);
|
|
}
|
|
}
|
|
}
|
|
#endif
|
|
return 1;
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void zend_jit_commit_veneers(void)
|
|
{
|
|
int i, count = sizeof(zend_jit_stubs) / sizeof(zend_jit_stubs[0]);
|
|
|
|
for (i = 0; i < count; i++) {
|
|
if (zend_jit_stub_handlers[count + i]) {
|
|
zend_jit_stub_handlers[i] = zend_jit_stub_handlers[count + i];
|
|
zend_jit_stub_handlers[count + i] = NULL;
|
|
}
|
|
}
|
|
}
|
|
#endif
|
|
|
|
static bool zend_jit_prefer_const_addr_load(zend_jit_ctx *jit, uintptr_t addr)
|
|
{
|
|
#if defined(IR_TARGET_X86)
|
|
return 0; /* always use immediate value */
|
|
#elif defined(IR_TARGET_X64)
|
|
return addr > 0xffffffff; /* prefer loading long constant from memery */
|
|
#elif defined(IR_TARGET_AARCH64)
|
|
return addr > 0xffff;
|
|
#else
|
|
# error "Unknown IR target"
|
|
#endif
|
|
}
|
|
|
|
static const char* zend_reg_name(int8_t reg)
|
|
{
|
|
return ir_reg_name(reg, ir_reg_is_int(reg) ? IR_LONG : IR_DOUBLE);
|
|
}
|
|
|
|
/* IR helpers */
|
|
|
|
#ifdef ZTS
|
|
static ir_ref jit_TLS(zend_jit_ctx *jit)
|
|
{
|
|
ZEND_ASSERT(jit->ctx.control);
|
|
if (jit->tls) {
|
|
/* Emit "TLS" once for basic block */
|
|
ir_insn *insn;
|
|
ir_ref ref = jit->ctx.control;
|
|
|
|
while (1) {
|
|
if (ref == jit->tls) {
|
|
return jit->tls;
|
|
}
|
|
insn = &jit->ctx.ir_base[ref];
|
|
if (insn->op >= IR_START || insn->op == IR_CALL) {
|
|
break;
|
|
}
|
|
ref = insn->op1;
|
|
}
|
|
}
|
|
jit->tls = ir_TLS(
|
|
tsrm_ls_cache_tcb_offset ? tsrm_ls_cache_tcb_offset : tsrm_tls_index,
|
|
tsrm_ls_cache_tcb_offset ? IR_NULL : tsrm_tls_offset);
|
|
return jit->tls;
|
|
}
|
|
#endif
|
|
|
|
static ir_ref jit_CONST_ADDR(zend_jit_ctx *jit, uintptr_t addr)
|
|
{
|
|
ir_ref ref;
|
|
zval *zv;
|
|
|
|
if (addr == 0) {
|
|
return IR_NULL;
|
|
}
|
|
zv = zend_hash_index_lookup(&jit->addr_hash, addr);
|
|
if (Z_TYPE_P(zv) == IS_LONG) {
|
|
ref = Z_LVAL_P(zv);
|
|
ZEND_ASSERT(jit->ctx.ir_base[ref].opt == IR_OPT(IR_ADDR, IR_ADDR));
|
|
} else {
|
|
ref = ir_unique_const_addr(&jit->ctx, addr);
|
|
ZVAL_LONG(zv, ref);
|
|
}
|
|
return ref;
|
|
}
|
|
|
|
static ir_ref jit_CONST_FUNC_PROTO(zend_jit_ctx *jit, uintptr_t addr, ir_ref proto)
|
|
{
|
|
ir_ref ref;
|
|
ir_insn *insn;
|
|
zval *zv;
|
|
|
|
ZEND_ASSERT(addr != 0);
|
|
zv = zend_hash_index_lookup(&jit->addr_hash, addr);
|
|
if (Z_TYPE_P(zv) == IS_LONG) {
|
|
ref = Z_LVAL_P(zv);
|
|
ZEND_ASSERT(jit->ctx.ir_base[ref].opt == IR_OPT(IR_FUNC_ADDR, IR_ADDR) && jit->ctx.ir_base[ref].proto == proto);
|
|
} else {
|
|
ref = ir_unique_const_addr(&jit->ctx, addr);
|
|
insn = &jit->ctx.ir_base[ref];
|
|
insn->optx = IR_OPT(IR_FUNC_ADDR, IR_ADDR);
|
|
insn->proto = proto;
|
|
ZVAL_LONG(zv, ref);
|
|
}
|
|
return ref;
|
|
}
|
|
|
|
static ir_ref jit_CONST_FUNC(zend_jit_ctx *jit, uintptr_t addr, uint16_t flags)
|
|
{
|
|
#if defined(IR_TARGET_X86)
|
|
/* TODO: dummy prototype (only flags matter) ??? */
|
|
ir_ref proto = flags ? ir_proto_0(&jit->ctx, flags, IR_I32) : 0;
|
|
#else
|
|
ir_ref proto = 0;
|
|
#endif
|
|
|
|
return jit_CONST_FUNC_PROTO(jit, addr, proto);
|
|
}
|
|
|
|
static ir_ref jit_ADD_OFFSET(zend_jit_ctx *jit, ir_ref addr, uintptr_t offset)
|
|
{
|
|
if (offset) {
|
|
addr = ir_ADD_A(addr, ir_CONST_ADDR(offset));
|
|
}
|
|
return addr;
|
|
}
|
|
|
|
static ir_ref jit_EG_exception(zend_jit_ctx *jit)
|
|
{
|
|
#ifdef ZTS
|
|
return jit_EG(exception);
|
|
#else
|
|
ir_ref ref = jit->eg_exception_addr;
|
|
|
|
if (UNEXPECTED(!ref)) {
|
|
ref = ir_unique_const_addr(&jit->ctx, (uintptr_t)&EG(exception));
|
|
jit->eg_exception_addr = ref;
|
|
}
|
|
return ref;
|
|
#endif
|
|
}
|
|
|
|
static ir_ref jit_STUB_ADDR(zend_jit_ctx *jit, jit_stub_id id)
|
|
{
|
|
ir_ref ref = jit->stub_addr[id];
|
|
|
|
if (UNEXPECTED(!ref)) {
|
|
ref = ir_unique_const_addr(&jit->ctx, (uintptr_t)zend_jit_stub_handlers[id]);
|
|
jit->stub_addr[id] = ref;
|
|
}
|
|
return ref;
|
|
}
|
|
|
|
static ir_ref jit_STUB_FUNC_ADDR(zend_jit_ctx *jit, jit_stub_id id, uint16_t flags)
|
|
{
|
|
ir_ref ref = jit->stub_addr[id];
|
|
ir_insn *insn;
|
|
|
|
if (UNEXPECTED(!ref)) {
|
|
ref = ir_unique_const_addr(&jit->ctx, (uintptr_t)zend_jit_stub_handlers[id]);
|
|
insn = &jit->ctx.ir_base[ref];
|
|
insn->optx = IR_OPT(IR_FUNC_ADDR, IR_ADDR);
|
|
#if defined(IR_TARGET_X86)
|
|
/* TODO: dummy prototype (only flags matter) ??? */
|
|
insn->proto = flags ? ir_proto_0(&jit->ctx, flags, IR_I32) : 0;
|
|
#else
|
|
insn->proto = 0;
|
|
#endif
|
|
jit->stub_addr[id] = ref;
|
|
}
|
|
return ref;
|
|
}
|
|
|
|
static void jit_SNAPSHOT(zend_jit_ctx *jit, ir_ref addr)
|
|
{
|
|
if (JIT_G(trigger) == ZEND_JIT_ON_HOT_TRACE && JIT_G(current_frame)) {
|
|
const void *ptr = (const void*)jit->ctx.ir_base[addr].val.addr;
|
|
const zend_op_array *op_array = &JIT_G(current_frame)->func->op_array;
|
|
uint32_t stack_size = op_array->last_var + op_array->T;
|
|
|
|
if (ptr == zend_jit_stub_handlers[jit_stub_exception_handler]
|
|
|| ptr == zend_jit_stub_handlers[jit_stub_exception_handler_undef]
|
|
|| ptr == zend_jit_stub_handlers[jit_stub_exception_handler_free_op1_op2]
|
|
|| ptr == zend_jit_stub_handlers[jit_stub_exception_handler_free_op2]
|
|
|| ptr == zend_jit_stub_handlers[jit_stub_interrupt_handler]
|
|
|| ptr == zend_jit_stub_handlers[jit_stub_leave_function_handler]
|
|
|| ptr == zend_jit_stub_handlers[jit_stub_negative_shift]
|
|
|| ptr == zend_jit_stub_handlers[jit_stub_mod_by_zero]
|
|
|| ptr == zend_jit_stub_handlers[jit_stub_invalid_this]
|
|
|| ptr == zend_jit_stub_handlers[jit_stub_undefined_function]
|
|
|| ptr == zend_jit_stub_handlers[jit_stub_throw_cannot_pass_by_ref]
|
|
|| ptr == zend_jit_stub_handlers[jit_stub_icall_throw]
|
|
|| ptr == zend_jit_stub_handlers[jit_stub_leave_throw]
|
|
|| ptr == zend_jit_stub_handlers[jit_stub_trace_halt]
|
|
|| ptr == zend_jit_stub_handlers[jit_stub_trace_escape]) {
|
|
/* This is a GUARD that trigger exit through a stub code (without deoptimization) */
|
|
return;
|
|
}
|
|
|
|
/* Check if we need snapshot entries for polymorphic method call */
|
|
zend_jit_trace_info *t = jit->trace;
|
|
uint32_t exit_point = 0, n = 0;
|
|
|
|
if (addr < 0) {
|
|
if (t->exit_count > 0
|
|
&& jit->ctx.ir_base[addr].val.u64 == (uintptr_t)zend_jit_trace_get_exit_addr(t->exit_count - 1)) {
|
|
exit_point = t->exit_count - 1;
|
|
if (t->exit_info[exit_point].flags & ZEND_JIT_EXIT_METHOD_CALL) {
|
|
n = 2;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (stack_size || n) {
|
|
zend_jit_trace_stack *stack = JIT_G(current_frame)->stack;
|
|
uint32_t snapshot_size, i;
|
|
|
|
snapshot_size = stack_size;
|
|
while (snapshot_size > 0) {
|
|
ir_ref ref = STACK_REF(stack, snapshot_size - 1);
|
|
|
|
if (!ref || ref == IR_NULL || (STACK_FLAGS(stack, snapshot_size - 1) & (/*ZREG_LOAD|*/ZREG_STORE))) {
|
|
snapshot_size--;
|
|
} else {
|
|
break;
|
|
}
|
|
}
|
|
if (snapshot_size || n) {
|
|
ir_ref snapshot;
|
|
|
|
snapshot = ir_SNAPSHOT(snapshot_size + n);
|
|
for (i = 0; i < snapshot_size; i++) {
|
|
ir_ref ref = STACK_REF(stack, i);
|
|
|
|
if (!ref || ref == IR_NULL || (STACK_FLAGS(stack, i) & (/*ZREG_LOAD|*/ZREG_STORE))) {
|
|
ref = IR_UNUSED;
|
|
}
|
|
ir_SNAPSHOT_SET_OP(snapshot, i + 1, ref);
|
|
}
|
|
if (n) {
|
|
ir_SNAPSHOT_SET_OP(snapshot, snapshot_size + 1, t->exit_info[exit_point].poly_func_ref);
|
|
ir_SNAPSHOT_SET_OP(snapshot, snapshot_size + 2, t->exit_info[exit_point].poly_this_ref);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
static int32_t _add_trace_const(zend_jit_trace_info *t, int64_t val)
|
|
{
|
|
int32_t i;
|
|
|
|
for (i = 0; i < t->consts_count; i++) {
|
|
if (t->constants[i].i == val) {
|
|
return i;
|
|
}
|
|
}
|
|
ZEND_ASSERT(i < 0x7fffffff);
|
|
t->consts_count = i + 1;
|
|
t->constants = erealloc(t->constants, (i + 1) * sizeof(zend_jit_exit_const));
|
|
t->constants[i].i = val;
|
|
return i;
|
|
}
|
|
|
|
uint32_t zend_jit_duplicate_exit_point(ir_ctx *ctx, zend_jit_trace_info *t, uint32_t exit_point, ir_ref snapshot_ref)
|
|
{
|
|
uint32_t stack_size, stack_offset;
|
|
uint32_t new_exit_point = t->exit_count;
|
|
|
|
if (new_exit_point >= ZEND_JIT_TRACE_MAX_EXITS) {
|
|
ctx->status = -ZEND_JIT_TRACE_STOP_TOO_MANY_EXITS;
|
|
return exit_point;
|
|
}
|
|
|
|
t->exit_count++;
|
|
memcpy(&t->exit_info[new_exit_point], &t->exit_info[exit_point], sizeof(zend_jit_trace_exit_info));
|
|
stack_size = t->exit_info[new_exit_point].stack_size;
|
|
if (stack_size != 0) {
|
|
stack_offset = t->stack_map_size;
|
|
t->stack_map_size += stack_size;
|
|
// TODO: reduce number of reallocations ???
|
|
t->stack_map = erealloc(t->stack_map, t->stack_map_size * sizeof(zend_jit_trace_stack));
|
|
memcpy(t->stack_map + stack_offset, t->stack_map + t->exit_info[new_exit_point].stack_offset, stack_size * sizeof(zend_jit_trace_stack));
|
|
t->exit_info[new_exit_point].stack_offset = stack_offset;
|
|
}
|
|
t->exit_info[new_exit_point].flags &= ~ZEND_JIT_EXIT_FIXED;
|
|
|
|
return new_exit_point;
|
|
}
|
|
|
|
void *zend_jit_snapshot_handler(ir_ctx *ctx, ir_ref snapshot_ref, ir_insn *snapshot, void *addr)
|
|
{
|
|
zend_jit_trace_info *t = ((zend_jit_ctx*)ctx)->trace;
|
|
uint32_t exit_point, exit_flags;
|
|
ir_ref n = snapshot->inputs_count;
|
|
ir_ref i;
|
|
|
|
exit_point = zend_jit_exit_point_by_addr(addr);
|
|
ZEND_ASSERT(exit_point < t->exit_count);
|
|
exit_flags = t->exit_info[exit_point].flags;
|
|
|
|
if (exit_flags & ZEND_JIT_EXIT_METHOD_CALL) {
|
|
int8_t *reg_ops = ctx->regs[snapshot_ref];
|
|
|
|
ZEND_ASSERT(reg_ops[n - 1] != -1 && reg_ops[n] != -1);
|
|
if ((exit_flags & ZEND_JIT_EXIT_FIXED)
|
|
&& (t->exit_info[exit_point].poly_func_reg != reg_ops[n - 1]
|
|
|| t->exit_info[exit_point].poly_this_reg != reg_ops[n])) {
|
|
exit_point = zend_jit_duplicate_exit_point(ctx, t, exit_point, snapshot_ref);
|
|
addr = (void*)zend_jit_trace_get_exit_addr(exit_point);
|
|
exit_flags &= ~ZEND_JIT_EXIT_FIXED;
|
|
}
|
|
t->exit_info[exit_point].poly_func_reg = reg_ops[n - 1];
|
|
t->exit_info[exit_point].poly_this_reg = reg_ops[n];
|
|
n -= 2;
|
|
}
|
|
|
|
for (i = 2; i <= n; i++) {
|
|
ir_ref ref = ir_insn_op(snapshot, i);
|
|
|
|
if (ref) {
|
|
int8_t *reg_ops = ctx->regs[snapshot_ref];
|
|
int8_t reg = reg_ops[i];
|
|
ir_ref var = i - 2;
|
|
|
|
ZEND_ASSERT(var < t->exit_info[exit_point].stack_size);
|
|
if (t->stack_map[t->exit_info[exit_point].stack_offset + var].flags == ZREG_ZVAL_COPY) {
|
|
ZEND_ASSERT(reg != ZREG_NONE);
|
|
if ((exit_flags & ZEND_JIT_EXIT_FIXED)
|
|
&& t->stack_map[t->exit_info[exit_point].stack_offset + var].reg != IR_REG_NUM(reg)) {
|
|
exit_point = zend_jit_duplicate_exit_point(ctx, t, exit_point, snapshot_ref);
|
|
addr = (void*)zend_jit_trace_get_exit_addr(exit_point);
|
|
exit_flags &= ~ZEND_JIT_EXIT_FIXED;
|
|
}
|
|
t->stack_map[t->exit_info[exit_point].stack_offset + var].reg = IR_REG_NUM(reg);
|
|
} else if (t->stack_map[t->exit_info[exit_point].stack_offset + var].flags != ZREG_CONST) {
|
|
ZEND_ASSERT(t->stack_map[t->exit_info[exit_point].stack_offset + var].type == IS_LONG ||
|
|
t->stack_map[t->exit_info[exit_point].stack_offset + var].type == IS_DOUBLE);
|
|
|
|
if (ref > 0) {
|
|
if (reg != ZREG_NONE) {
|
|
if (reg & IR_REG_SPILL_LOAD) {
|
|
ZEND_ASSERT(!(reg & IR_REG_SPILL_SPECIAL));
|
|
/* spill slot on a CPU stack */
|
|
if ((exit_flags & ZEND_JIT_EXIT_FIXED)
|
|
&& (t->stack_map[t->exit_info[exit_point].stack_offset + var].ref != ref
|
|
|| t->stack_map[t->exit_info[exit_point].stack_offset + var].reg != ZREG_NONE
|
|
|| !(t->stack_map[t->exit_info[exit_point].stack_offset + var].flags & ZREG_SPILL_SLOT))) {
|
|
exit_point = zend_jit_duplicate_exit_point(ctx, t, exit_point, snapshot_ref);
|
|
addr = (void*)zend_jit_trace_get_exit_addr(exit_point);
|
|
exit_flags &= ~ZEND_JIT_EXIT_FIXED;
|
|
}
|
|
t->stack_map[t->exit_info[exit_point].stack_offset + var].ref = ref;
|
|
t->stack_map[t->exit_info[exit_point].stack_offset + var].reg = ZREG_NONE;
|
|
t->stack_map[t->exit_info[exit_point].stack_offset + var].flags |= ZREG_SPILL_SLOT;
|
|
} else if (reg & IR_REG_SPILL_SPECIAL) {
|
|
/* spill slot on a VM stack */
|
|
if ((exit_flags & ZEND_JIT_EXIT_FIXED)
|
|
&& (t->stack_map[t->exit_info[exit_point].stack_offset + var].reg != ZREG_NONE
|
|
|| t->stack_map[t->exit_info[exit_point].stack_offset + var].flags != ZREG_TYPE_ONLY)) {
|
|
exit_point = zend_jit_duplicate_exit_point(ctx, t, exit_point, snapshot_ref);
|
|
addr = (void*)zend_jit_trace_get_exit_addr(exit_point);
|
|
exit_flags &= ~ZEND_JIT_EXIT_FIXED;
|
|
}
|
|
t->stack_map[t->exit_info[exit_point].stack_offset + var].reg = ZREG_NONE;
|
|
t->stack_map[t->exit_info[exit_point].stack_offset + var].flags = ZREG_TYPE_ONLY;
|
|
} else {
|
|
if ((exit_flags & ZEND_JIT_EXIT_FIXED)
|
|
&& t->stack_map[t->exit_info[exit_point].stack_offset + var].reg != IR_REG_NUM(reg)) {
|
|
exit_point = zend_jit_duplicate_exit_point(ctx, t, exit_point, snapshot_ref);
|
|
addr = (void*)zend_jit_trace_get_exit_addr(exit_point);
|
|
exit_flags &= ~ZEND_JIT_EXIT_FIXED;
|
|
}
|
|
t->stack_map[t->exit_info[exit_point].stack_offset + var].reg = IR_REG_NUM(reg);
|
|
}
|
|
} else {
|
|
if ((exit_flags & ZEND_JIT_EXIT_FIXED)
|
|
&& (t->stack_map[t->exit_info[exit_point].stack_offset + var].reg != ZREG_NONE
|
|
|| t->stack_map[t->exit_info[exit_point].stack_offset + var].flags != ZREG_TYPE_ONLY)) {
|
|
exit_point = zend_jit_duplicate_exit_point(ctx, t, exit_point, snapshot_ref);
|
|
addr = (void*)zend_jit_trace_get_exit_addr(exit_point);
|
|
exit_flags &= ~ZEND_JIT_EXIT_FIXED;
|
|
}
|
|
t->stack_map[t->exit_info[exit_point].stack_offset + var].flags = ZREG_TYPE_ONLY;
|
|
}
|
|
} else if (!(exit_flags & ZEND_JIT_EXIT_FIXED)) {
|
|
int32_t idx = _add_trace_const(t, ctx->ir_base[ref].val.i64);
|
|
t->stack_map[t->exit_info[exit_point].stack_offset + var].flags = ZREG_CONST;
|
|
t->stack_map[t->exit_info[exit_point].stack_offset + var].ref = idx;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
t->exit_info[exit_point].flags |= ZEND_JIT_EXIT_FIXED;
|
|
return addr;
|
|
}
|
|
|
|
static void jit_SIDE_EXIT(zend_jit_ctx *jit, ir_ref addr)
|
|
{
|
|
jit_SNAPSHOT(jit, addr);
|
|
ir_IJMP(addr);
|
|
}
|
|
|
|
/* PHP JIT helpers */
|
|
|
|
static ir_ref jit_EMALLOC(zend_jit_ctx *jit, size_t size, const zend_op_array *op_array, const zend_op *opline)
|
|
{
|
|
#if ZEND_DEBUG
|
|
return ir_CALL_5(IR_ADDR, ir_CONST_FC_FUNC(_emalloc),
|
|
ir_CONST_ADDR(size),
|
|
op_array->filename ? ir_CONST_ADDR(op_array->filename->val) : IR_NULL,
|
|
ir_CONST_U32(opline ? opline->lineno : 0),
|
|
IR_NULL,
|
|
ir_CONST_U32(0));
|
|
#elif defined(HAVE_BUILTIN_CONSTANT_P)
|
|
if (size > 24 && size <= 32) {
|
|
return ir_CALL(IR_ADDR, ir_CONST_FC_FUNC(_emalloc_32));
|
|
} else {
|
|
return ir_CALL_1(IR_ADDR, ir_CONST_FC_FUNC(_emalloc), ir_CONST_ADDR(size));
|
|
}
|
|
#else
|
|
return ir_CALL_1(IR_ADDR, ir_CONST_FC_FUNC(_emalloc), ir_CONST_ADDR(size));
|
|
#endif
|
|
}
|
|
|
|
static ir_ref jit_EFREE(zend_jit_ctx *jit, ir_ref ptr, size_t size, const zend_op_array *op_array, const zend_op *opline)
|
|
{
|
|
#if ZEND_DEBUG
|
|
return ir_CALL_5(IR_ADDR, ir_CONST_FC_FUNC(_efree),
|
|
ptr,
|
|
op_array && op_array->filename ? ir_CONST_ADDR(op_array->filename->val) : IR_NULL,
|
|
ir_CONST_U32(opline ? opline->lineno : 0),
|
|
IR_NULL,
|
|
ir_CONST_U32(0));
|
|
#elif defined(HAVE_BUILTIN_CONSTANT_P)
|
|
if (size > 24 && size <= 32) {
|
|
return ir_CALL_1(IR_ADDR, ir_CONST_FC_FUNC(_efree_32), ptr);
|
|
} else {
|
|
return ir_CALL_1(IR_ADDR, ir_CONST_FC_FUNC(_efree), ptr);
|
|
}
|
|
#else
|
|
return ir_CALL_1(IR_ADDR, ir_CONST_FC_FUNC(_efree), ptr);
|
|
#endif
|
|
}
|
|
|
|
static ir_ref jit_FP(zend_jit_ctx *jit)
|
|
{
|
|
ZEND_ASSERT(jit->ctx.control);
|
|
if (jit->fp == IR_UNUSED) {
|
|
/* Emit "RLOAD FP" once for basic block */
|
|
jit->fp = ir_RLOAD_A(ZREG_FP);
|
|
} else {
|
|
ir_insn *insn;
|
|
ir_ref ref = jit->ctx.control;
|
|
|
|
while (1) {
|
|
if (ref == jit->fp) {
|
|
break;
|
|
}
|
|
insn = &jit->ctx.ir_base[ref];
|
|
if (insn->op >= IR_START || insn->op == IR_CALL) {
|
|
jit->fp = ir_RLOAD_A(ZREG_FP);
|
|
break;
|
|
}
|
|
ref = insn->op1;
|
|
}
|
|
}
|
|
return jit->fp;
|
|
}
|
|
|
|
static void jit_STORE_FP(zend_jit_ctx *jit, ir_ref ref)
|
|
{
|
|
ir_RSTORE(ZREG_FP, ref);
|
|
jit->fp = IR_UNUSED;
|
|
}
|
|
|
|
static ir_ref jit_IP(zend_jit_ctx *jit)
|
|
{
|
|
return ir_RLOAD_A(ZREG_IP);
|
|
}
|
|
|
|
static void jit_STORE_IP(zend_jit_ctx *jit, ir_ref ref)
|
|
{
|
|
ir_RSTORE(ZREG_IP, ref);
|
|
}
|
|
|
|
static ir_ref jit_IP32(zend_jit_ctx *jit)
|
|
{
|
|
return ir_RLOAD_U32(ZREG_IP);
|
|
}
|
|
|
|
static void jit_LOAD_IP(zend_jit_ctx *jit, ir_ref ref)
|
|
{
|
|
if (GCC_GLOBAL_REGS) {
|
|
jit_STORE_IP(jit, ref);
|
|
} else {
|
|
ir_STORE(jit_EX(opline), ref);
|
|
}
|
|
}
|
|
|
|
static void jit_LOAD_IP_ADDR(zend_jit_ctx *jit, const zend_op *target)
|
|
{
|
|
jit_LOAD_IP(jit, ir_CONST_ADDR(target));
|
|
}
|
|
|
|
static void zend_jit_track_last_valid_opline(zend_jit_ctx *jit)
|
|
{
|
|
jit->use_last_valid_opline = 0;
|
|
jit->track_last_valid_opline = 1;
|
|
}
|
|
|
|
static void zend_jit_use_last_valid_opline(zend_jit_ctx *jit)
|
|
{
|
|
if (jit->track_last_valid_opline) {
|
|
jit->use_last_valid_opline = 1;
|
|
jit->track_last_valid_opline = 0;
|
|
}
|
|
}
|
|
|
|
static bool zend_jit_trace_uses_initial_ip(zend_jit_ctx *jit)
|
|
{
|
|
return jit->use_last_valid_opline;
|
|
}
|
|
|
|
static void zend_jit_set_last_valid_opline(zend_jit_ctx *jit, const zend_op *opline)
|
|
{
|
|
if (!jit->reuse_ip) {
|
|
jit->track_last_valid_opline = 1;
|
|
jit->last_valid_opline = opline;
|
|
}
|
|
}
|
|
|
|
static void zend_jit_reset_last_valid_opline(zend_jit_ctx *jit)
|
|
{
|
|
jit->track_last_valid_opline = 0;
|
|
jit->last_valid_opline = NULL;
|
|
}
|
|
|
|
static void zend_jit_start_reuse_ip(zend_jit_ctx *jit)
|
|
{
|
|
zend_jit_reset_last_valid_opline(jit);
|
|
jit->reuse_ip = 1;
|
|
}
|
|
|
|
static int zend_jit_reuse_ip(zend_jit_ctx *jit)
|
|
{
|
|
if (!jit->reuse_ip) {
|
|
zend_jit_start_reuse_ip(jit);
|
|
// RX = EX(call);
|
|
jit_STORE_IP(jit, ir_LOAD_A(jit_EX(call)));
|
|
}
|
|
return 1;
|
|
}
|
|
|
|
static void zend_jit_stop_reuse_ip(zend_jit_ctx *jit)
|
|
{
|
|
jit->reuse_ip = 0;
|
|
}
|
|
|
|
static int zend_jit_save_call_chain(zend_jit_ctx *jit, uint32_t call_level)
|
|
{
|
|
ir_ref rx, call;
|
|
|
|
if (call_level == 1) {
|
|
// JIT: call = NULL;
|
|
call = IR_NULL;
|
|
} else {
|
|
// JIT: call = EX(call);
|
|
call = ir_LOAD_A(jit_EX(call));
|
|
}
|
|
|
|
rx = jit_IP(jit);
|
|
|
|
// JIT: call->prev_execute_data = call;
|
|
ir_STORE(jit_CALL(rx, prev_execute_data), call);
|
|
|
|
// JIT: EX(call) = call;
|
|
ir_STORE(jit_EX(call), rx);
|
|
|
|
jit->delayed_call_level = 0;
|
|
delayed_call_chain = 0;
|
|
|
|
return 1;
|
|
}
|
|
|
|
static int zend_jit_set_ip(zend_jit_ctx *jit, const zend_op *target)
|
|
{
|
|
ir_ref ref;
|
|
ir_ref addr = IR_UNUSED;
|
|
|
|
if (jit->delayed_call_level) {
|
|
if (!zend_jit_save_call_chain(jit, jit->delayed_call_level)) {
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
if (jit->last_valid_opline) {
|
|
zend_jit_use_last_valid_opline(jit);
|
|
if (jit->last_valid_opline != target) {
|
|
if (GCC_GLOBAL_REGS) {
|
|
ref = jit_IP(jit);
|
|
} else {
|
|
addr = jit_EX(opline);
|
|
ref = ir_LOAD_A(addr);
|
|
}
|
|
if (target > jit->last_valid_opline) {
|
|
ref = ir_ADD_OFFSET(ref, (uintptr_t)target - (uintptr_t)jit->last_valid_opline);
|
|
} else {
|
|
ref = ir_SUB_A(ref, ir_CONST_ADDR((uintptr_t)jit->last_valid_opline - (uintptr_t)target));
|
|
}
|
|
if (GCC_GLOBAL_REGS) {
|
|
jit_STORE_IP(jit, ref);
|
|
} else {
|
|
ir_STORE(addr, ref);
|
|
}
|
|
}
|
|
} else {
|
|
if (GCC_GLOBAL_REGS) {
|
|
jit_STORE_IP(jit, ir_CONST_ADDR(target));
|
|
} else {
|
|
ir_STORE(jit_EX(opline), ir_CONST_ADDR(target));
|
|
}
|
|
}
|
|
jit->reuse_ip = 0;
|
|
zend_jit_set_last_valid_opline(jit, target);
|
|
return 1;
|
|
}
|
|
|
|
static int zend_jit_set_ip_ex(zend_jit_ctx *jit, const zend_op *target, bool set_ip_reg)
|
|
{
|
|
if (!GCC_GLOBAL_REGS && set_ip_reg && !jit->last_valid_opline) {
|
|
/* Optimization to avoid duplicate constant load */
|
|
ir_STORE(jit_EX(opline), ir_HARD_COPY_A(ir_CONST_ADDR(target)));
|
|
return 1;
|
|
}
|
|
return zend_jit_set_ip(jit, target);
|
|
}
|
|
|
|
static void jit_SET_EX_OPLINE(zend_jit_ctx *jit, const zend_op *target)
|
|
{
|
|
if (jit->last_valid_opline == target) {
|
|
zend_jit_use_last_valid_opline(jit);
|
|
if (GCC_GLOBAL_REGS) {
|
|
// EX(opline) = opline
|
|
ir_STORE(jit_EX(opline), jit_IP(jit));
|
|
}
|
|
} else {
|
|
ir_STORE(jit_EX(opline), ir_CONST_ADDR(target));
|
|
if (!GCC_GLOBAL_REGS) {
|
|
zend_jit_reset_last_valid_opline(jit);
|
|
}
|
|
}
|
|
}
|
|
|
|
static ir_ref jit_ZVAL_ADDR(zend_jit_ctx *jit, zend_jit_addr addr)
|
|
{
|
|
if (Z_MODE(addr) == IS_MEM_ZVAL) {
|
|
ir_ref reg;
|
|
|
|
if (Z_REG(addr) == ZREG_FP) {
|
|
reg = jit_FP(jit);
|
|
} else if (Z_REG(addr) == ZREG_RX) {
|
|
reg = jit_IP(jit);
|
|
} else {
|
|
ZEND_UNREACHABLE();
|
|
}
|
|
return ir_ADD_OFFSET(reg, Z_OFFSET(addr));
|
|
} else if (Z_MODE(addr) == IS_REF_ZVAL) {
|
|
return Z_IR_REF(addr);
|
|
} else {
|
|
ZEND_ASSERT(Z_MODE(addr) == IS_CONST_ZVAL);
|
|
return ir_CONST_ADDR(Z_ZV(addr));
|
|
}
|
|
}
|
|
|
|
static ir_ref jit_Z_TYPE_ref(zend_jit_ctx *jit, ir_ref ref)
|
|
{
|
|
return ir_LOAD_U8(ir_ADD_OFFSET(ref, offsetof(zval, u1.v.type)));
|
|
}
|
|
|
|
static ir_ref jit_Z_TYPE(zend_jit_ctx *jit, zend_jit_addr addr)
|
|
{
|
|
if (Z_MODE(addr) == IS_CONST_ZVAL) {
|
|
return ir_CONST_U8(Z_TYPE_P(Z_ZV(addr)));
|
|
} else if (Z_MODE(addr) == IS_MEM_ZVAL) {
|
|
ir_ref reg;
|
|
|
|
ZEND_ASSERT(Z_MODE(addr) == IS_MEM_ZVAL);
|
|
if (Z_REG(addr) == ZREG_FP) {
|
|
reg = jit_FP(jit);
|
|
} else if (Z_REG(addr) == ZREG_RX) {
|
|
reg = jit_IP(jit);
|
|
} else {
|
|
ZEND_UNREACHABLE();
|
|
}
|
|
return ir_LOAD_U8(ir_ADD_OFFSET(reg, Z_OFFSET(addr) + offsetof(zval, u1.v.type)));
|
|
} else {
|
|
return jit_Z_TYPE_ref(jit, jit_ZVAL_ADDR(jit, addr));
|
|
}
|
|
}
|
|
|
|
static ir_ref jit_Z_TYPE_FLAGS_ref(zend_jit_ctx *jit, ir_ref ref)
|
|
{
|
|
return ir_LOAD_U8(ir_ADD_OFFSET(ref, offsetof(zval, u1.v.type_flags)));
|
|
}
|
|
|
|
static ir_ref jit_Z_TYPE_FLAGS(zend_jit_ctx *jit, zend_jit_addr addr)
|
|
{
|
|
if (Z_MODE(addr) == IS_CONST_ZVAL) {
|
|
return ir_CONST_U8(Z_TYPE_FLAGS_P(Z_ZV(addr)));
|
|
} else if (Z_MODE(addr) == IS_MEM_ZVAL) {
|
|
ir_ref reg;
|
|
|
|
ZEND_ASSERT(Z_MODE(addr) == IS_MEM_ZVAL);
|
|
if (Z_REG(addr) == ZREG_FP) {
|
|
reg = jit_FP(jit);
|
|
} else if (Z_REG(addr) == ZREG_RX) {
|
|
reg = jit_IP(jit);
|
|
} else {
|
|
ZEND_UNREACHABLE();
|
|
}
|
|
return ir_LOAD_U8(ir_ADD_OFFSET(reg, Z_OFFSET(addr) + offsetof(zval, u1.v.type_flags)));
|
|
} else {
|
|
return jit_Z_TYPE_FLAGS_ref(jit, jit_ZVAL_ADDR(jit, addr));
|
|
}
|
|
}
|
|
|
|
static ir_ref jit_Z_TYPE_INFO_ref(zend_jit_ctx *jit, ir_ref ref)
|
|
{
|
|
return ir_LOAD_U32(ir_ADD_OFFSET(ref, offsetof(zval, u1.type_info)));
|
|
}
|
|
|
|
static ir_ref jit_Z_TYPE_INFO(zend_jit_ctx *jit, zend_jit_addr addr)
|
|
{
|
|
if (Z_MODE(addr) == IS_CONST_ZVAL) {
|
|
return ir_CONST_U32(Z_TYPE_INFO_P(Z_ZV(addr)));
|
|
} else if (Z_MODE(addr) == IS_MEM_ZVAL) {
|
|
ir_ref reg;
|
|
|
|
ZEND_ASSERT(Z_MODE(addr) == IS_MEM_ZVAL);
|
|
if (Z_REG(addr) == ZREG_FP) {
|
|
reg = jit_FP(jit);
|
|
} else if (Z_REG(addr) == ZREG_RX) {
|
|
reg = jit_IP(jit);
|
|
} else {
|
|
ZEND_UNREACHABLE();
|
|
}
|
|
return ir_LOAD_U32(ir_ADD_OFFSET(reg, Z_OFFSET(addr) + offsetof(zval, u1.type_info)));
|
|
} else {
|
|
return jit_Z_TYPE_INFO_ref(jit, jit_ZVAL_ADDR(jit, addr));
|
|
}
|
|
}
|
|
|
|
static void jit_set_Z_TYPE_INFO_ref(zend_jit_ctx *jit, ir_ref ref, ir_ref type_info)
|
|
{
|
|
ir_STORE(ir_ADD_OFFSET(ref, offsetof(zval, u1.type_info)), type_info);
|
|
}
|
|
|
|
static void jit_set_Z_TYPE_INFO_ex(zend_jit_ctx *jit, zend_jit_addr addr, ir_ref type_info)
|
|
{
|
|
if (Z_MODE(addr) == IS_MEM_ZVAL) {
|
|
ir_ref reg;
|
|
|
|
ZEND_ASSERT(Z_MODE(addr) == IS_MEM_ZVAL);
|
|
if (Z_REG(addr) == ZREG_FP) {
|
|
reg = jit_FP(jit);
|
|
} else if (Z_REG(addr) == ZREG_RX) {
|
|
reg = jit_IP(jit);
|
|
} else {
|
|
ZEND_UNREACHABLE();
|
|
}
|
|
ir_STORE(ir_ADD_OFFSET(reg, Z_OFFSET(addr) + offsetof(zval, u1.type_info)), type_info);
|
|
} else {
|
|
jit_set_Z_TYPE_INFO_ref(jit, jit_ZVAL_ADDR(jit, addr), type_info);
|
|
}
|
|
}
|
|
|
|
static void jit_set_Z_TYPE_INFO(zend_jit_ctx *jit, zend_jit_addr addr, uint32_t type_info)
|
|
{
|
|
if (type_info < IS_STRING
|
|
&& Z_MODE(addr) == IS_MEM_ZVAL
|
|
&& Z_REG(addr) == ZREG_FP
|
|
&& JIT_G(current_frame)
|
|
&& STACK_MEM_TYPE(JIT_G(current_frame)->stack, EX_VAR_TO_NUM(Z_OFFSET(addr))) == type_info) {
|
|
/* type is already set */
|
|
return;
|
|
}
|
|
jit_set_Z_TYPE_INFO_ex(jit, addr, ir_CONST_U32(type_info));
|
|
}
|
|
|
|
static ir_ref jit_if_Z_TYPE_ref(zend_jit_ctx *jit, ir_ref ref, ir_ref type)
|
|
{
|
|
return ir_IF(ir_EQ(jit_Z_TYPE_ref(jit, ref), type));
|
|
}
|
|
|
|
static ir_ref jit_if_Z_TYPE(zend_jit_ctx *jit, zend_jit_addr addr, uint8_t type)
|
|
{
|
|
ZEND_ASSERT(type != IS_UNDEF);
|
|
return ir_IF(ir_EQ(jit_Z_TYPE(jit, addr), ir_CONST_U8(type)));
|
|
}
|
|
|
|
static ir_ref jit_if_not_Z_TYPE(zend_jit_ctx *jit, zend_jit_addr addr, uint8_t type)
|
|
{
|
|
ir_ref ref = jit_Z_TYPE(jit, addr);
|
|
|
|
if (type != IS_UNDEF) {
|
|
ref = ir_NE(ref, ir_CONST_U8(type));
|
|
}
|
|
return ir_IF(ref);
|
|
}
|
|
|
|
static void jit_guard_Z_TYPE(zend_jit_ctx *jit, zend_jit_addr addr, uint8_t type, const void *exit_addr)
|
|
{
|
|
ir_ref ref = jit_Z_TYPE(jit, addr);
|
|
|
|
if (type != IS_UNDEF) {
|
|
ir_GUARD(ir_EQ(ref, ir_CONST_U8(type)), ir_CONST_ADDR(exit_addr));
|
|
} else {
|
|
ir_GUARD_NOT(ref, ir_CONST_ADDR(exit_addr));
|
|
}
|
|
}
|
|
|
|
static void jit_guard_not_Z_TYPE(zend_jit_ctx *jit, zend_jit_addr addr, uint8_t type, const void *exit_addr)
|
|
{
|
|
ir_ref ref = jit_Z_TYPE(jit, addr);
|
|
|
|
if (type != IS_UNDEF) {
|
|
ref = ir_NE(ref, ir_CONST_U8(type));
|
|
}
|
|
ir_GUARD(ref, ir_CONST_ADDR(exit_addr));
|
|
}
|
|
|
|
static ir_ref jit_if_REFCOUNTED(zend_jit_ctx *jit, zend_jit_addr addr)
|
|
{
|
|
return ir_IF(jit_Z_TYPE_FLAGS(jit, addr));
|
|
}
|
|
|
|
static ir_ref jit_if_COLLECTABLE_ref(zend_jit_ctx *jit, ir_ref addr_ref)
|
|
{
|
|
return ir_IF(ir_AND_U8(jit_Z_TYPE_FLAGS_ref(jit, addr_ref), ir_CONST_U8(IS_TYPE_COLLECTABLE)));
|
|
}
|
|
|
|
static ir_ref jit_Z_LVAL_ref(zend_jit_ctx *jit, ir_ref ref)
|
|
{
|
|
return ir_LOAD_L(ref);
|
|
}
|
|
|
|
static ir_ref jit_Z_DVAL_ref(zend_jit_ctx *jit, ir_ref ref)
|
|
{
|
|
return ir_LOAD_D(ref);
|
|
}
|
|
|
|
static bool zend_jit_spilling_may_cause_conflict(zend_jit_ctx *jit, int var, ir_ref val)
|
|
{
|
|
if (jit->ctx.ir_base[val].op == IR_RLOAD) {
|
|
/* Deoptimization */
|
|
return 0;
|
|
}
|
|
// if (jit->ctx.ir_base[val].op == IR_LOAD
|
|
// && jit->ctx.ir_base[jit->ctx.ir_base[val].op2].op == IR_ADD
|
|
// && jit->ctx.ir_base[jit->ctx.ir_base[jit->ctx.ir_base[val].op2].op1].op == IR_RLOAD
|
|
// && jit->ctx.ir_base[jit->ctx.ir_base[jit->ctx.ir_base[val].op2].op1].op2 == ZREG_FP
|
|
// && IR_IS_CONST_REF(jit->ctx.ir_base[jit->ctx.ir_base[val].op2].op2)
|
|
// && jit->ctx.ir_base[jit->ctx.ir_base[jit->ctx.ir_base[val].op2].op2].val.addr == (uintptr_t)EX_NUM_TO_VAR(jit->ssa->vars[var].var)) {
|
|
// /* LOAD from the same location (the LOAD is pinned) */
|
|
// // TODO: should be anti-dependent with the following stores ???
|
|
// return 0;
|
|
// }
|
|
if (jit->ssa->vars[var].var < jit->current_op_array->last_var) {
|
|
/* IS_CV */
|
|
if (jit->ctx.ir_base[val].op == IR_LOAD
|
|
&& jit->ctx.ir_base[jit->ctx.ir_base[val].op2].op == IR_ADD
|
|
&& jit->ctx.ir_base[jit->ctx.ir_base[jit->ctx.ir_base[val].op2].op1].op == IR_RLOAD
|
|
&& jit->ctx.ir_base[jit->ctx.ir_base[jit->ctx.ir_base[val].op2].op1].op2 == ZREG_FP
|
|
&& IR_IS_CONST_REF(jit->ctx.ir_base[jit->ctx.ir_base[val].op2].op2)
|
|
&& jit->ctx.ir_base[jit->ctx.ir_base[jit->ctx.ir_base[val].op2].op2].val.addr != (uintptr_t)EX_NUM_TO_VAR(jit->ssa->vars[var].var)
|
|
&& EX_VAR_TO_NUM(jit->ctx.ir_base[jit->ctx.ir_base[jit->ctx.ir_base[val].op2].op2].val.addr) < jit->current_op_array->last_var) {
|
|
/* binding between different CVs may cause spill conflict */
|
|
return 1;
|
|
} else if (jit->ssa->vars[var].definition >= 0
|
|
&& jit->ssa->ops[jit->ssa->vars[var].definition].op1_def == var
|
|
&& jit->ssa->ops[jit->ssa->vars[var].definition].op1_use >= 0
|
|
&& jit->ssa->vars[jit->ssa->ops[jit->ssa->vars[var].definition].op1_use].no_val
|
|
&& jit->ssa->vars[jit->ssa->ops[jit->ssa->vars[var].definition].op1_use].definition_phi
|
|
&& (jit->ssa->cfg.blocks[jit->ssa->vars[jit->ssa->ops[jit->ssa->vars[var].definition].op1_use].definition_phi->block].flags & ZEND_BB_LOOP_HEADER)) {
|
|
/* Avoid moving spill store out of loop */
|
|
return 1;
|
|
}
|
|
return 0;
|
|
}
|
|
return 1;
|
|
}
|
|
|
|
static void zend_jit_def_reg(zend_jit_ctx *jit, zend_jit_addr addr, ir_ref val)
|
|
{
|
|
int var;
|
|
|
|
ZEND_ASSERT(Z_MODE(addr) == IS_REG);
|
|
var = Z_SSA_VAR(addr);
|
|
if (var == jit->delay_var) {
|
|
ir_refs_add(jit->delay_refs, val);
|
|
return;
|
|
}
|
|
ZEND_ASSERT(jit->ra && jit->ra[var].ref == IR_NULL);
|
|
|
|
/* Negative "var" has special meaning for IR */
|
|
if (val > 0 && !zend_jit_spilling_may_cause_conflict(jit, var, val)) {
|
|
val = ir_bind(&jit->ctx, -EX_NUM_TO_VAR(jit->ssa->vars[var].var), val);
|
|
}
|
|
jit->ra[var].ref = val;
|
|
|
|
if (jit->ra[var].flags & ZREG_FORWARD) {
|
|
zend_ssa_phi *phi = jit->ssa->vars[var].phi_use_chain;
|
|
zend_basic_block *bb;
|
|
int n, j, *p;
|
|
ir_ref *q;
|
|
|
|
jit->ra[var].flags &= ~ZREG_FORWARD;
|
|
while (phi != NULL) {
|
|
zend_ssa_phi *dst_phi = phi;
|
|
int src_var = var;
|
|
|
|
if (dst_phi->pi >= 0) {
|
|
jit->ra[src_var].ref = val;
|
|
src_var = dst_phi->ssa_var;
|
|
if (!(jit->ra[src_var].flags & ZREG_FORWARD)) {
|
|
phi = zend_ssa_next_use_phi(jit->ssa, var, phi);
|
|
continue;
|
|
}
|
|
dst_phi = jit->ssa->vars[src_var].phi_use_chain;
|
|
ZEND_ASSERT(dst_phi != NULL && "reg forwarding");
|
|
ZEND_ASSERT(!zend_ssa_next_use_phi(jit->ssa, src_var, dst_phi) && "reg forwarding");
|
|
jit->ra[src_var].flags &= ~ZREG_FORWARD;
|
|
}
|
|
|
|
if (jit->ra[dst_phi->ssa_var].ref > 0) {
|
|
ir_insn *phi_insn = &jit->ctx.ir_base[jit->ra[dst_phi->ssa_var].ref];
|
|
if (phi_insn->op == IR_PHI) {
|
|
// ZEND_ASSERT(ir_operands_count(ctx, phi_insn) == n + 1);
|
|
bb = &jit->ssa->cfg.blocks[dst_phi->block];
|
|
n = bb->predecessors_count;
|
|
for (j = 0, p = &dst_phi->sources[0], q = phi_insn->ops + 2; j < n; j++, p++, q++) {
|
|
if (*p == src_var) {
|
|
*q = val;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
phi = zend_ssa_next_use_phi(jit->ssa, var, phi);
|
|
}
|
|
}
|
|
}
|
|
|
|
static ir_ref zend_jit_use_reg(zend_jit_ctx *jit, zend_jit_addr addr)
|
|
{
|
|
int var = Z_SSA_VAR(addr);
|
|
|
|
ZEND_ASSERT(Z_MODE(addr) == IS_REG);
|
|
ZEND_ASSERT(jit->ra && jit->ra[var].ref);
|
|
if (jit->ra[var].ref == IR_NULL) {
|
|
zend_jit_addr mem_addr;
|
|
ir_ref ref;
|
|
|
|
ZEND_ASSERT(jit->ra[var].flags & ZREG_LOAD);
|
|
mem_addr = ZEND_ADDR_MEM_ZVAL(ZREG_FP, EX_NUM_TO_VAR(jit->ssa->vars[var].var));
|
|
if ((jit->ssa->var_info[var].type & MAY_BE_ANY) == MAY_BE_LONG) {
|
|
ref = jit_Z_LVAL_ref(jit, jit_ZVAL_ADDR(jit, mem_addr));
|
|
} else if ((jit->ssa->var_info[var].type & MAY_BE_ANY) == MAY_BE_DOUBLE) {
|
|
ref = jit_Z_DVAL_ref(jit, jit_ZVAL_ADDR(jit, mem_addr));
|
|
} else {
|
|
ZEND_UNREACHABLE();
|
|
}
|
|
zend_jit_def_reg(jit, addr, ref);
|
|
return ref;
|
|
}
|
|
return jit->ra[Z_SSA_VAR(addr)].ref;
|
|
}
|
|
|
|
static void zend_jit_gen_pi(zend_jit_ctx *jit, zend_ssa_phi *phi)
|
|
{
|
|
int src_var = phi->sources[0];
|
|
int dst_var = phi->ssa_var;
|
|
|
|
ZEND_ASSERT(phi->pi >= 0);
|
|
ZEND_ASSERT(!(jit->ra[dst_var].flags & ZREG_LOAD));
|
|
ZEND_ASSERT(jit->ra[src_var].ref);
|
|
|
|
if (jit->ra[src_var].ref == IR_NULL) {
|
|
/* Not defined yet */
|
|
if (jit->ssa->vars[dst_var].use_chain < 0
|
|
&& jit->ssa->vars[dst_var].phi_use_chain) {
|
|
zend_ssa_phi *phi = jit->ssa->vars[dst_var].phi_use_chain;
|
|
if (!zend_ssa_next_use_phi(jit->ssa, dst_var, phi)) {
|
|
/* This is a Pi forwarded to Phi */
|
|
jit->ra[src_var].flags |= ZREG_FORWARD;
|
|
return;
|
|
}
|
|
}
|
|
ZEND_ASSERT(0 && "Not defined Pi source");
|
|
}
|
|
/* Reuse register */
|
|
zend_jit_def_reg(jit, ZEND_ADDR_REG(dst_var),
|
|
zend_jit_use_reg(jit, ZEND_ADDR_REG(src_var)));
|
|
}
|
|
|
|
static void zend_jit_gen_phi(zend_jit_ctx *jit, zend_ssa_phi *phi)
|
|
{
|
|
int dst_var = phi->ssa_var;
|
|
zend_basic_block *bb = &jit->ssa->cfg.blocks[phi->block];
|
|
int n = bb->predecessors_count;
|
|
int i;
|
|
ir_type type = (jit->ssa->var_info[phi->ssa_var].type & MAY_BE_LONG) ? IR_LONG : IR_DOUBLE;
|
|
ir_ref merge = jit->bb_start_ref[phi->block];
|
|
ir_ref ref;
|
|
ir_ref old_insns_count = jit->ctx.insns_count;
|
|
ir_ref same_src_ref = IR_UNUSED;
|
|
bool phi_inputs_are_the_same = 1;
|
|
|
|
ZEND_ASSERT(phi->pi < 0);
|
|
ZEND_ASSERT(!(jit->ra[dst_var].flags & ZREG_LOAD));
|
|
ZEND_ASSERT(merge);
|
|
ZEND_ASSERT(jit->ctx.ir_base[merge].op == IR_MERGE || jit->ctx.ir_base[merge].op == IR_LOOP_BEGIN);
|
|
ZEND_ASSERT(n == jit->ctx.ir_base[merge].inputs_count);
|
|
|
|
ref = ir_emit_N(&jit->ctx, IR_OPT(IR_PHI, type), n + 1);
|
|
ir_set_op(&jit->ctx, ref, 1, merge);
|
|
|
|
for (i = 0; i < n; i++) {
|
|
int src_var = phi->sources[i];
|
|
|
|
ZEND_ASSERT(jit->ra[src_var].ref);
|
|
if (jit->ra[src_var].ref == IR_NULL) {
|
|
jit->ra[src_var].flags |= ZREG_FORWARD;
|
|
phi_inputs_are_the_same = 0;
|
|
} else {
|
|
ir_ref src_ref = zend_jit_use_reg(jit, ZEND_ADDR_REG(src_var));
|
|
if (i == 0) {
|
|
same_src_ref = src_ref;
|
|
} else if (same_src_ref != src_ref) {
|
|
phi_inputs_are_the_same = 0;
|
|
}
|
|
ir_set_op(&jit->ctx, ref, i + 2, src_ref);
|
|
}
|
|
}
|
|
if (phi_inputs_are_the_same) {
|
|
ref = same_src_ref;
|
|
jit->ctx.insns_count = old_insns_count;
|
|
}
|
|
|
|
zend_jit_def_reg(jit, ZEND_ADDR_REG(dst_var), ref);
|
|
}
|
|
|
|
static ir_ref jit_Z_LVAL(zend_jit_ctx *jit, zend_jit_addr addr)
|
|
{
|
|
if (Z_MODE(addr) == IS_CONST_ZVAL) {
|
|
return ir_CONST_LONG(Z_LVAL_P(Z_ZV(addr)));
|
|
} else if (Z_MODE(addr) == IS_REG) {
|
|
return zend_jit_use_reg(jit, addr);
|
|
} else {
|
|
return jit_Z_LVAL_ref(jit, jit_ZVAL_ADDR(jit, addr));
|
|
}
|
|
}
|
|
|
|
static void jit_set_Z_LVAL(zend_jit_ctx *jit, zend_jit_addr addr, ir_ref lval)
|
|
{
|
|
if (Z_MODE(addr) == IS_REG) {
|
|
zend_jit_def_reg(jit, addr, lval);
|
|
} else {
|
|
ir_STORE(jit_ZVAL_ADDR(jit, addr), lval);
|
|
}
|
|
}
|
|
|
|
#if SIZEOF_ZEND_LONG == 4
|
|
static ir_ref jit_Z_W2(zend_jit_ctx *jit, zend_jit_addr addr)
|
|
{
|
|
if (Z_MODE(addr) == IS_CONST_ZVAL) {
|
|
return ir_CONST_U32((Z_ZV(addr))->value.ww.w2);
|
|
} else {
|
|
return ir_LOAD_L(ir_ADD_OFFSET(jit_ZVAL_ADDR(jit, addr), offsetof(zval, value.ww.w2)));
|
|
}
|
|
}
|
|
|
|
static void jit_set_Z_W2(zend_jit_ctx *jit, zend_jit_addr addr, ir_ref lval)
|
|
{
|
|
ir_STORE(ir_ADD_OFFSET(jit_ZVAL_ADDR(jit, addr), offsetof(zval, value.ww.w2)), lval);
|
|
}
|
|
#endif
|
|
|
|
static ir_ref jit_Z_DVAL(zend_jit_ctx *jit, zend_jit_addr addr)
|
|
{
|
|
if (Z_MODE(addr) == IS_CONST_ZVAL) {
|
|
return ir_CONST_DOUBLE(Z_DVAL_P(Z_ZV(addr)));
|
|
} else if (Z_MODE(addr) == IS_REG) {
|
|
return zend_jit_use_reg(jit, addr);
|
|
} else {
|
|
return jit_Z_DVAL_ref(jit, jit_ZVAL_ADDR(jit, addr));
|
|
}
|
|
}
|
|
|
|
static void jit_set_Z_DVAL(zend_jit_ctx *jit, zend_jit_addr addr, ir_ref dval)
|
|
{
|
|
if (Z_MODE(addr) == IS_REG) {
|
|
zend_jit_def_reg(jit, addr, dval);
|
|
} else {
|
|
ir_STORE(jit_ZVAL_ADDR(jit, addr), dval);
|
|
}
|
|
}
|
|
|
|
static ir_ref jit_Z_PTR_ref(zend_jit_ctx *jit, ir_ref ref)
|
|
{
|
|
return ir_LOAD_A(ref);
|
|
}
|
|
|
|
static ir_ref jit_Z_PTR(zend_jit_ctx *jit, zend_jit_addr addr)
|
|
{
|
|
if (Z_MODE(addr) == IS_CONST_ZVAL) {
|
|
return ir_CONST_ADDR(Z_PTR_P(Z_ZV(addr)));
|
|
} else {
|
|
return jit_Z_PTR_ref(jit, jit_ZVAL_ADDR(jit, addr));
|
|
}
|
|
}
|
|
|
|
static void jit_set_Z_PTR(zend_jit_ctx *jit, zend_jit_addr addr, ir_ref ptr)
|
|
{
|
|
ir_STORE(jit_ZVAL_ADDR(jit, addr), ptr);
|
|
}
|
|
|
|
static ir_ref jit_GC_REFCOUNT(zend_jit_ctx *jit, ir_ref ref)
|
|
{
|
|
return ir_LOAD_U32(ref);
|
|
}
|
|
|
|
static void jit_set_GC_REFCOUNT(zend_jit_ctx *jit, ir_ref ref, uint32_t refcount)
|
|
{
|
|
ir_STORE(ref, ir_CONST_U32(refcount));
|
|
}
|
|
|
|
static void jit_GC_ADDREF(zend_jit_ctx *jit, ir_ref ref)
|
|
{
|
|
ir_STORE(ref, ir_ADD_U32(ir_LOAD_U32(ref), ir_CONST_U32(1)));
|
|
}
|
|
|
|
static void jit_GC_ADDREF2(zend_jit_ctx *jit, ir_ref ref)
|
|
{
|
|
ir_ref counter = ir_LOAD_U32(ref);
|
|
ir_STORE(ref, ir_ADD_U32(counter, ir_CONST_U32(2)));
|
|
}
|
|
|
|
static ir_ref jit_GC_DELREF(zend_jit_ctx *jit, ir_ref ref)
|
|
{
|
|
ir_ref counter = ir_LOAD_U32(ref);
|
|
counter = ir_SUB_U32(counter, ir_CONST_U32(1));
|
|
ir_STORE(ref, counter);
|
|
return counter;
|
|
}
|
|
|
|
static ir_ref jit_if_GC_MAY_NOT_LEAK(zend_jit_ctx *jit, ir_ref ref)
|
|
{
|
|
return ir_IF(
|
|
ir_AND_U32(
|
|
ir_LOAD_U32(ir_ADD_OFFSET(ref, offsetof(zend_refcounted, gc.u.type_info))),
|
|
ir_CONST_U32(GC_INFO_MASK | (GC_NOT_COLLECTABLE << GC_FLAGS_SHIFT))));
|
|
}
|
|
|
|
static void jit_ZVAL_COPY_CONST(zend_jit_ctx *jit, zend_jit_addr dst, uint32_t dst_info, uint32_t dst_def_info, zval *zv, bool addref)
|
|
{
|
|
ir_ref ref = IR_UNUSED;
|
|
|
|
if (Z_TYPE_P(zv) > IS_TRUE) {
|
|
if (Z_TYPE_P(zv) == IS_DOUBLE) {
|
|
jit_set_Z_DVAL(jit, dst, ir_CONST_DOUBLE(Z_DVAL_P(zv)));
|
|
} else if (Z_TYPE_P(zv) == IS_LONG && dst_def_info == MAY_BE_DOUBLE) {
|
|
jit_set_Z_DVAL(jit, dst, ir_CONST_DOUBLE((double)Z_LVAL_P(zv)));
|
|
} else if (Z_TYPE_P(zv) == IS_LONG) {
|
|
jit_set_Z_LVAL(jit, dst, ir_CONST_LONG(Z_LVAL_P(zv)));
|
|
} else {
|
|
ref = ir_CONST_ADDR(Z_PTR_P(zv));
|
|
jit_set_Z_PTR(jit, dst, ref);
|
|
if (addref && Z_REFCOUNTED_P(zv)) {
|
|
jit_GC_ADDREF(jit, ref);
|
|
}
|
|
}
|
|
}
|
|
if (Z_MODE(dst) != IS_REG) {
|
|
if (dst_def_info == MAY_BE_DOUBLE) {
|
|
if ((dst_info & (MAY_BE_ANY|MAY_BE_UNDEF|MAY_BE_GUARD)) != MAY_BE_DOUBLE) {
|
|
jit_set_Z_TYPE_INFO(jit, dst, 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) {
|
|
jit_set_Z_TYPE_INFO(jit, dst, Z_TYPE_INFO_P(zv));
|
|
}
|
|
}
|
|
}
|
|
|
|
static ir_ref jit_if_TYPED_REF(zend_jit_ctx *jit, ir_ref ref)
|
|
{
|
|
return ir_IF(ir_LOAD_A(ir_ADD_OFFSET(ref, offsetof(zend_reference, sources.ptr))));
|
|
}
|
|
|
|
static void jit_ZVAL_COPY(zend_jit_ctx *jit, zend_jit_addr dst, uint32_t dst_info, zend_jit_addr src, uint32_t src_info, bool addref)
|
|
{
|
|
ir_ref ref = IR_UNUSED;
|
|
|
|
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) {
|
|
jit_set_Z_LVAL(jit, dst, jit_Z_LVAL(jit, src));
|
|
} else if ((src_info & (MAY_BE_ANY|MAY_BE_GUARD)) == MAY_BE_DOUBLE) {
|
|
jit_set_Z_DVAL(jit, dst, jit_Z_DVAL(jit, src));
|
|
} else {
|
|
#if SIZEOF_ZEND_LONG == 4
|
|
if (src_info & (MAY_BE_DOUBLE|MAY_BE_GUARD)) {
|
|
jit_set_Z_W2(jit, dst, jit_Z_W2(jit, src));
|
|
}
|
|
#endif
|
|
ref = jit_Z_PTR(jit, src);
|
|
jit_set_Z_PTR(jit, dst, ref);
|
|
}
|
|
}
|
|
if (has_concrete_type(src_info & MAY_BE_ANY)
|
|
&& (src_info & (MAY_BE_NULL|MAY_BE_FALSE|MAY_BE_TRUE|MAY_BE_LONG|MAY_BE_DOUBLE))
|
|
&& !(src_info & MAY_BE_GUARD)) {
|
|
if (Z_MODE(dst) != IS_REG
|
|
&& (dst_info & (MAY_BE_ANY|MAY_BE_UNDEF|MAY_BE_GUARD)) != (src_info & (MAY_BE_ANY|MAY_BE_UNDEF|MAY_BE_GUARD))) {
|
|
uint8_t type = concrete_type(src_info);
|
|
jit_set_Z_TYPE_INFO(jit, dst, type);
|
|
}
|
|
} else {
|
|
ir_ref type = jit_Z_TYPE_INFO(jit, src);
|
|
jit_set_Z_TYPE_INFO_ex(jit, dst, type);
|
|
if (addref) {
|
|
if (src_info & (MAY_BE_STRING|MAY_BE_ARRAY|MAY_BE_OBJECT|MAY_BE_RESOURCE)) {
|
|
ir_ref if_refcounted = IR_UNUSED;
|
|
|
|
if (src_info & (MAY_BE_ANY-(MAY_BE_OBJECT|MAY_BE_RESOURCE))) {
|
|
if_refcounted = ir_IF(ir_AND_U32(type, ir_CONST_U32(0xff00)));
|
|
ir_IF_TRUE(if_refcounted);
|
|
}
|
|
|
|
jit_GC_ADDREF(jit, ref);
|
|
|
|
if (src_info & (MAY_BE_ANY-(MAY_BE_OBJECT|MAY_BE_RESOURCE))) {
|
|
ir_MERGE_WITH_EMPTY_FALSE(if_refcounted);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
static void jit_ZVAL_COPY_2(zend_jit_ctx *jit, zend_jit_addr dst2, zend_jit_addr dst, uint32_t dst_info, zend_jit_addr src, uint32_t src_info, int addref)
|
|
{
|
|
ir_ref ref = IR_UNUSED;
|
|
|
|
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) {
|
|
ref = jit_Z_LVAL(jit, src);
|
|
jit_set_Z_LVAL(jit, dst, ref);
|
|
jit_set_Z_LVAL(jit, dst2, ref);
|
|
} else if ((src_info & (MAY_BE_ANY|MAY_BE_GUARD)) == MAY_BE_DOUBLE) {
|
|
ref = jit_Z_DVAL(jit, src);
|
|
jit_set_Z_DVAL(jit, dst, ref);
|
|
jit_set_Z_DVAL(jit, dst2, ref);
|
|
} else {
|
|
#if SIZEOF_ZEND_LONG == 4
|
|
if (src_info & (MAY_BE_DOUBLE|MAY_BE_GUARD)) {
|
|
ref = jit_Z_W2(jit, src);
|
|
jit_set_Z_W2(jit, dst, ref);
|
|
jit_set_Z_W2(jit, dst2, ref);
|
|
}
|
|
#endif
|
|
ref = jit_Z_PTR(jit, src);
|
|
jit_set_Z_PTR(jit, dst, ref);
|
|
jit_set_Z_PTR(jit, dst2, ref);
|
|
}
|
|
}
|
|
if (has_concrete_type(src_info & MAY_BE_ANY)
|
|
&& (src_info & (MAY_BE_NULL|MAY_BE_FALSE|MAY_BE_TRUE|MAY_BE_LONG|MAY_BE_DOUBLE))
|
|
&& !(src_info & MAY_BE_GUARD)) {
|
|
uint8_t type = concrete_type(src_info);
|
|
ir_ref type_ref = ir_CONST_U32(type);
|
|
|
|
if (Z_MODE(dst) != IS_REG
|
|
&& (dst_info & (MAY_BE_ANY|MAY_BE_UNDEF|MAY_BE_GUARD)) != (src_info & (MAY_BE_ANY|MAY_BE_UNDEF|MAY_BE_GUARD))) {
|
|
jit_set_Z_TYPE_INFO_ex(jit, dst, type_ref);
|
|
}
|
|
if (Z_MODE(dst2) != IS_REG) {
|
|
jit_set_Z_TYPE_INFO_ex(jit, dst2, type_ref);
|
|
}
|
|
} else {
|
|
ir_ref type = jit_Z_TYPE_INFO(jit, src);
|
|
jit_set_Z_TYPE_INFO_ex(jit, dst, type);
|
|
jit_set_Z_TYPE_INFO_ex(jit, dst2, type);
|
|
if (addref) {
|
|
if (src_info & (MAY_BE_STRING|MAY_BE_ARRAY|MAY_BE_OBJECT|MAY_BE_RESOURCE)) {
|
|
ir_ref if_refcounted = IR_UNUSED;
|
|
|
|
if (src_info & (MAY_BE_ANY-(MAY_BE_OBJECT|MAY_BE_RESOURCE))) {
|
|
if_refcounted = ir_IF(ir_AND_U32(type, ir_CONST_U32(0xff00)));
|
|
ir_IF_TRUE(if_refcounted);
|
|
}
|
|
|
|
if (addref == 2) {
|
|
jit_GC_ADDREF2(jit, ref);
|
|
} else {
|
|
jit_GC_ADDREF(jit, ref);
|
|
}
|
|
|
|
if (src_info & (MAY_BE_ANY-(MAY_BE_OBJECT|MAY_BE_RESOURCE))) {
|
|
ir_MERGE_WITH_EMPTY_FALSE(if_refcounted);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
static void jit_ZVAL_DTOR(zend_jit_ctx *jit, ir_ref ref, uint32_t op_info, const zend_op *opline)
|
|
{
|
|
if (!((op_info) & MAY_BE_GUARD)
|
|
&& has_concrete_type((op_info) & (MAY_BE_STRING|MAY_BE_ARRAY|MAY_BE_OBJECT|MAY_BE_RESOURCE))) {
|
|
uint8_t type = concrete_type((op_info) & (MAY_BE_STRING|MAY_BE_ARRAY|MAY_BE_OBJECT|MAY_BE_RESOURCE));
|
|
if (type == IS_STRING && !ZEND_DEBUG) {
|
|
ir_CALL_1(IR_VOID, ir_CONST_FC_FUNC(_efree), ref);
|
|
return;
|
|
} else if (type == IS_ARRAY) {
|
|
if ((op_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 && ((op_info) & (MAY_BE_ARRAY_OF_ARRAY|MAY_BE_ARRAY_OF_OBJECT|MAY_BE_ARRAY_OF_RESOURCE|MAY_BE_ARRAY_OF_REF))) {
|
|
jit_SET_EX_OPLINE(jit, opline);
|
|
}
|
|
ir_CALL_1(IR_VOID, ir_CONST_FC_FUNC(zend_array_destroy), ref);
|
|
} else {
|
|
ir_CALL_1(IR_VOID, ir_CONST_FC_FUNC(zend_jit_array_free), ref);
|
|
}
|
|
return;
|
|
} else if (type == IS_OBJECT) {
|
|
if (opline) {
|
|
jit_SET_EX_OPLINE(jit, opline);
|
|
}
|
|
ir_CALL_1(IR_VOID, ir_CONST_FC_FUNC(zend_objects_store_del), ref);
|
|
return;
|
|
}
|
|
}
|
|
if (opline) {
|
|
jit_SET_EX_OPLINE(jit, opline);
|
|
}
|
|
ir_CALL_1(IR_VOID, ir_CONST_FC_FUNC(rc_dtor_func), ref);
|
|
}
|
|
|
|
static void jit_ZVAL_PTR_DTOR(zend_jit_ctx *jit,
|
|
zend_jit_addr addr,
|
|
uint32_t op_info,
|
|
bool gc,
|
|
const zend_op *opline)
|
|
{
|
|
ir_ref ref, ref2;
|
|
ir_ref if_refcounted = IR_UNUSED;
|
|
ir_ref if_not_zero = IR_UNUSED;
|
|
ir_ref end_inputs = IR_UNUSED;
|
|
|
|
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_refcounted = jit_if_REFCOUNTED(jit, addr);
|
|
ir_IF_FALSE(if_refcounted);
|
|
ir_END_list(end_inputs);
|
|
ir_IF_TRUE(if_refcounted);
|
|
}
|
|
ref = jit_Z_PTR(jit, addr);
|
|
ref2 = jit_GC_DELREF(jit, ref);
|
|
|
|
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_not_zero = ir_IF(ref2);
|
|
ir_IF_FALSE(if_not_zero);
|
|
}
|
|
// zval_dtor_func(r);
|
|
jit_ZVAL_DTOR(jit, ref, op_info, opline);
|
|
if (if_not_zero) {
|
|
ir_END_list(end_inputs);
|
|
ir_IF_TRUE(if_not_zero);
|
|
}
|
|
}
|
|
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))) {
|
|
ir_ref if_may_not_leak;
|
|
|
|
if ((op_info) & (MAY_BE_REF|MAY_BE_GUARD)) {
|
|
ir_ref if_ref, if_collectable;
|
|
|
|
if_ref = jit_if_Z_TYPE(jit, addr, IS_REFERENCE);
|
|
ir_IF_TRUE(if_ref);
|
|
|
|
ref2 = ir_ADD_OFFSET(ref, offsetof(zend_reference, val));
|
|
|
|
if_collectable = jit_if_COLLECTABLE_ref(jit, ref2);
|
|
ir_IF_FALSE(if_collectable);
|
|
ir_END_list(end_inputs);
|
|
ir_IF_TRUE(if_collectable);
|
|
|
|
ref2 = jit_Z_PTR_ref(jit, ref2);
|
|
|
|
ir_MERGE_WITH_EMPTY_FALSE(if_ref);
|
|
ref = ir_PHI_2(IR_ADDR, ref2, ref);
|
|
}
|
|
|
|
if_may_not_leak = jit_if_GC_MAY_NOT_LEAK(jit, ref);
|
|
ir_IF_TRUE(if_may_not_leak);
|
|
ir_END_list(end_inputs);
|
|
ir_IF_FALSE(if_may_not_leak);
|
|
|
|
ir_CALL_1(IR_VOID, ir_CONST_FC_FUNC(gc_possible_root), ref);
|
|
}
|
|
|
|
if (end_inputs) {
|
|
ir_END_list(end_inputs);
|
|
ir_MERGE_list(end_inputs);
|
|
}
|
|
}
|
|
}
|
|
|
|
static void jit_FREE_OP(zend_jit_ctx *jit,
|
|
uint8_t op_type,
|
|
znode_op op,
|
|
uint32_t op_info,
|
|
const zend_op *opline)
|
|
{
|
|
if (op_type & (IS_VAR|IS_TMP_VAR)) {
|
|
jit_ZVAL_PTR_DTOR(jit,
|
|
ZEND_ADDR_MEM_ZVAL(ZREG_FP, op.var),
|
|
op_info, 0, opline);
|
|
}
|
|
}
|
|
|
|
static void jit_OBJ_RELEASE(zend_jit_ctx *jit, ir_ref ref)
|
|
{
|
|
ir_ref end_inputs = IR_UNUSED;
|
|
ir_ref if_not_zero, if_may_not_leak;
|
|
|
|
// JIT: if (GC_DELREF(obj) == 0) {
|
|
if_not_zero = ir_IF(jit_GC_DELREF(jit, ref));
|
|
ir_IF_FALSE(if_not_zero);
|
|
|
|
// JIT: zend_objects_store_del(obj)
|
|
ir_CALL_1(IR_VOID, ir_CONST_FC_FUNC(zend_objects_store_del), ref);
|
|
ir_END_list(end_inputs);
|
|
|
|
ir_IF_TRUE(if_not_zero);
|
|
if_may_not_leak = jit_if_GC_MAY_NOT_LEAK(jit, ref);
|
|
|
|
ir_IF_TRUE(if_may_not_leak);
|
|
ir_END_list(end_inputs);
|
|
|
|
ir_IF_FALSE(if_may_not_leak);
|
|
ir_CALL_1(IR_VOID, ir_CONST_FC_FUNC(gc_possible_root), ref);
|
|
ir_END_list(end_inputs);
|
|
|
|
ir_MERGE_list(end_inputs);
|
|
}
|
|
|
|
static void zend_jit_check_timeout(zend_jit_ctx *jit, const zend_op *opline, const void *exit_addr)
|
|
{
|
|
ir_ref ref = ir_LOAD_U8(jit_EG(vm_interrupt));
|
|
|
|
if (exit_addr) {
|
|
ir_GUARD_NOT(ref, ir_CONST_ADDR(exit_addr));
|
|
} else if (!opline || jit->last_valid_opline == opline) {
|
|
ir_GUARD_NOT(ref, jit_STUB_ADDR(jit, jit_stub_interrupt_handler));
|
|
} else {
|
|
ir_ref if_timeout = ir_IF(ref);
|
|
|
|
ir_IF_TRUE_cold(if_timeout);
|
|
jit_LOAD_IP_ADDR(jit, opline);
|
|
ir_IJMP(jit_STUB_ADDR(jit, jit_stub_interrupt_handler));
|
|
ir_IF_FALSE(if_timeout);
|
|
}
|
|
}
|
|
|
|
/* stubs */
|
|
|
|
static int zend_jit_exception_handler_stub(zend_jit_ctx *jit)
|
|
{
|
|
const void *handler;
|
|
|
|
if (zend_jit_vm_kind == ZEND_VM_KIND_HYBRID) {
|
|
handler = zend_get_opcode_handler_func(EG(exception_op));
|
|
|
|
ir_CALL(IR_VOID, ir_CONST_FUNC(handler));
|
|
ir_TAILCALL(IR_VOID, ir_LOAD_A(jit_IP(jit)));
|
|
} else {
|
|
handler = EG(exception_op)->handler;
|
|
|
|
if (GCC_GLOBAL_REGS) {
|
|
ir_TAILCALL(IR_VOID, ir_CONST_FUNC(handler));
|
|
} else {
|
|
ir_ref ref, if_negative;
|
|
|
|
ref = ir_CALL_1(IR_I32, ir_CONST_FC_FUNC(handler), jit_FP(jit));
|
|
if_negative = ir_IF(ir_LT(ref, ir_CONST_U32(0)));
|
|
ir_IF_TRUE(if_negative);
|
|
ir_MERGE_WITH_EMPTY_FALSE(if_negative);
|
|
ref = ir_PHI_2(IR_I32, ref, ir_CONST_I32(1));
|
|
ir_RETURN(ref);
|
|
}
|
|
}
|
|
return 1;
|
|
}
|
|
|
|
static int zend_jit_exception_handler_undef_stub(zend_jit_ctx *jit)
|
|
{
|
|
ir_ref ref, result_type, if_result_used;
|
|
|
|
ref = jit_EG(opline_before_exception);
|
|
result_type = ir_LOAD_U8(ir_ADD_OFFSET(ir_LOAD_A(ref), offsetof(zend_op, result_type)));
|
|
|
|
if_result_used = ir_IF(ir_AND_U8(result_type, ir_CONST_U8(IS_TMP_VAR|IS_VAR)));
|
|
ir_IF_TRUE(if_result_used);
|
|
|
|
ref = ir_LOAD_U32(ir_ADD_OFFSET(ir_LOAD_A(ref), offsetof(zend_op, result.var)));
|
|
if (sizeof(void*) == 8) {
|
|
ref = ir_ZEXT_A(ref);
|
|
}
|
|
ir_STORE(ir_ADD_OFFSET(ir_ADD_A(jit_FP(jit), ref), offsetof(zval, u1.type_info)), ir_CONST_U32(IS_UNDEF));
|
|
ir_MERGE_WITH_EMPTY_FALSE(if_result_used);
|
|
|
|
ir_IJMP(jit_STUB_ADDR(jit, jit_stub_exception_handler));
|
|
|
|
return 1;
|
|
}
|
|
|
|
static int zend_jit_exception_handler_free_op1_op2_stub(zend_jit_ctx *jit)
|
|
{
|
|
ir_ref ref, if_dtor;
|
|
zend_jit_addr var_addr;
|
|
|
|
ref = ir_LOAD_A(jit_EG(opline_before_exception));
|
|
if_dtor = ir_IF(ir_AND_U8(ir_LOAD_U8(ir_ADD_OFFSET(ref, offsetof(zend_op, op1_type))),
|
|
ir_CONST_U8(IS_TMP_VAR|IS_VAR)));
|
|
ir_IF_TRUE(if_dtor);
|
|
ref = ir_LOAD_U32(ir_ADD_OFFSET(ref, offsetof(zend_op, op1.var)));
|
|
if (sizeof(void*) == 8) {
|
|
ref = ir_ZEXT_A(ref);
|
|
}
|
|
ref = ir_ADD_A(jit_FP(jit), ref);
|
|
var_addr = ZEND_ADDR_REF_ZVAL(ref);
|
|
jit_ZVAL_PTR_DTOR(jit, var_addr, MAY_BE_ANY|MAY_BE_RC1|MAY_BE_RCN|MAY_BE_REF, 0, NULL);
|
|
ir_MERGE_WITH_EMPTY_FALSE(if_dtor);
|
|
|
|
ir_IJMP(jit_STUB_ADDR(jit, jit_stub_exception_handler_free_op2));
|
|
|
|
return 1;
|
|
}
|
|
|
|
static int zend_jit_exception_handler_free_op2_stub(zend_jit_ctx *jit)
|
|
{
|
|
ir_ref ref, if_dtor;
|
|
zend_jit_addr var_addr;
|
|
|
|
ref = ir_LOAD_A(jit_EG(opline_before_exception));
|
|
if_dtor = ir_IF(ir_AND_U8(ir_LOAD_U8(ir_ADD_OFFSET(ref, offsetof(zend_op, op2_type))),
|
|
ir_CONST_U8(IS_TMP_VAR|IS_VAR)));
|
|
ir_IF_TRUE(if_dtor);
|
|
ref = ir_LOAD_U32(ir_ADD_OFFSET(ref, offsetof(zend_op, op2.var)));
|
|
if (sizeof(void*) == 8) {
|
|
ref = ir_ZEXT_A(ref);
|
|
}
|
|
ref = ir_ADD_A(jit_FP(jit), ref);
|
|
var_addr = ZEND_ADDR_REF_ZVAL(ref);
|
|
jit_ZVAL_PTR_DTOR(jit, var_addr, MAY_BE_ANY|MAY_BE_RC1|MAY_BE_RCN|MAY_BE_REF, 0, NULL);
|
|
ir_MERGE_WITH_EMPTY_FALSE(if_dtor);
|
|
|
|
ir_IJMP(jit_STUB_ADDR(jit, jit_stub_exception_handler_undef));
|
|
|
|
return 1;
|
|
}
|
|
|
|
static int zend_jit_interrupt_handler_stub(zend_jit_ctx *jit)
|
|
{
|
|
ir_ref if_timeout, if_exception;
|
|
|
|
if (GCC_GLOBAL_REGS) {
|
|
// EX(opline) = opline
|
|
ir_STORE(jit_EX(opline), jit_IP(jit));
|
|
}
|
|
|
|
ir_STORE(jit_EG(vm_interrupt), ir_CONST_U8(0));
|
|
if_timeout = ir_IF(ir_EQ(ir_LOAD_U8(jit_EG(timed_out)), ir_CONST_U8(0)));
|
|
ir_IF_FALSE(if_timeout);
|
|
ir_CALL(IR_VOID, ir_CONST_FUNC(zend_timeout));
|
|
ir_MERGE_WITH_EMPTY_TRUE(if_timeout);
|
|
|
|
if (zend_interrupt_function) {
|
|
ir_CALL_1(IR_VOID, ir_CONST_FUNC(zend_interrupt_function), jit_FP(jit));
|
|
if_exception = ir_IF(ir_LOAD_A(jit_EG(exception)));
|
|
ir_IF_TRUE(if_exception);
|
|
ir_CALL(IR_VOID, ir_CONST_FUNC(zend_jit_exception_in_interrupt_handler_helper));
|
|
ir_MERGE_WITH_EMPTY_FALSE(if_exception);
|
|
|
|
jit_STORE_FP(jit, ir_LOAD_A(jit_EG(current_execute_data)));
|
|
jit_STORE_IP(jit, ir_LOAD_A(jit_EX(opline)));
|
|
}
|
|
|
|
if (GCC_GLOBAL_REGS) {
|
|
ir_TAILCALL(IR_VOID, ir_LOAD_A(jit_IP(jit)));
|
|
} else {
|
|
ir_RETURN(ir_CONST_I32(1));
|
|
}
|
|
return 1;
|
|
}
|
|
|
|
static int zend_jit_leave_function_handler_stub(zend_jit_ctx *jit)
|
|
{
|
|
ir_ref call_info = ir_LOAD_U32(jit_EX(This.u1.type_info));
|
|
ir_ref if_top = ir_IF(ir_AND_U32(call_info, ir_CONST_U32(ZEND_CALL_TOP)));
|
|
|
|
ir_IF_FALSE(if_top);
|
|
|
|
if (zend_jit_vm_kind == ZEND_VM_KIND_HYBRID) {
|
|
ir_CALL_1(IR_VOID, ir_CONST_FC_FUNC(zend_jit_leave_nested_func_helper), call_info);
|
|
jit_STORE_IP(jit,
|
|
ir_LOAD_A(jit_EX(opline)));
|
|
ir_TAILCALL(IR_VOID, ir_LOAD_A(jit_IP(jit)));
|
|
} else if (GCC_GLOBAL_REGS) {
|
|
ir_TAILCALL_1(IR_VOID, ir_CONST_FC_FUNC(zend_jit_leave_nested_func_helper), call_info);
|
|
} else {
|
|
ir_TAILCALL_2(IR_I32, ir_CONST_FC_FUNC(zend_jit_leave_nested_func_helper), call_info, jit_FP(jit));
|
|
}
|
|
|
|
ir_IF_TRUE(if_top);
|
|
|
|
if (zend_jit_vm_kind == ZEND_VM_KIND_HYBRID) {
|
|
ir_CALL_1(IR_VOID, ir_CONST_FC_FUNC(zend_jit_leave_top_func_helper), call_info);
|
|
ir_TAILCALL(IR_VOID, ir_LOAD_A(jit_IP(jit)));
|
|
} else if (GCC_GLOBAL_REGS) {
|
|
ir_TAILCALL_1(IR_VOID, ir_CONST_FC_FUNC(zend_jit_leave_top_func_helper), call_info);
|
|
} else {
|
|
ir_TAILCALL_2(IR_I32, ir_CONST_FC_FUNC(zend_jit_leave_top_func_helper), call_info, jit_FP(jit));
|
|
}
|
|
|
|
return 1;
|
|
}
|
|
|
|
static int zend_jit_negative_shift_stub(zend_jit_ctx *jit)
|
|
{
|
|
ir_CALL_2(IR_VOID,
|
|
ir_CONST_FUNC_PROTO(zend_throw_error,
|
|
ir_proto_2(&jit->ctx, IR_VARARG_FUNC, IR_VOID, IR_ADDR, IR_ADDR)),
|
|
ir_CONST_ADDR(zend_ce_arithmetic_error),
|
|
ir_CONST_ADDR("Bit shift by negative number"));
|
|
ir_IJMP(jit_STUB_ADDR(jit, jit_stub_exception_handler_free_op1_op2));
|
|
return 1;
|
|
}
|
|
|
|
static int zend_jit_mod_by_zero_stub(zend_jit_ctx *jit)
|
|
{
|
|
ir_CALL_2(IR_VOID,
|
|
ir_CONST_FUNC_PROTO(zend_throw_error,
|
|
ir_proto_2(&jit->ctx, IR_VARARG_FUNC, IR_VOID, IR_ADDR, IR_ADDR)),
|
|
ir_CONST_ADDR(zend_ce_division_by_zero_error),
|
|
ir_CONST_ADDR("Modulo by zero"));
|
|
ir_IJMP(jit_STUB_ADDR(jit, jit_stub_exception_handler_free_op1_op2));
|
|
return 1;
|
|
}
|
|
|
|
static int zend_jit_invalid_this_stub(zend_jit_ctx *jit)
|
|
{
|
|
ir_CALL_2(IR_VOID,
|
|
ir_CONST_FUNC_PROTO(zend_throw_error,
|
|
ir_proto_2(&jit->ctx, IR_VARARG_FUNC, IR_VOID, IR_ADDR, IR_ADDR)),
|
|
IR_NULL,
|
|
ir_CONST_ADDR("Using $this when not in object context"));
|
|
ir_IJMP(jit_STUB_ADDR(jit, jit_stub_exception_handler_undef));
|
|
return 1;
|
|
}
|
|
|
|
static int zend_jit_undefined_function_stub(zend_jit_ctx *jit)
|
|
{
|
|
// JIT: load EX(opline)
|
|
ir_ref ref = ir_LOAD_A(jit_FP(jit));
|
|
ir_ref arg3 = ir_LOAD_U32(ir_ADD_OFFSET(ref, offsetof(zend_op, op2.constant)));
|
|
|
|
if (sizeof(void*) == 8) {
|
|
arg3 = ir_LOAD_A(ir_ADD_A(ref, ir_SEXT_A(arg3)));
|
|
} else {
|
|
arg3 = ir_LOAD_A(arg3);
|
|
}
|
|
arg3 = ir_ADD_OFFSET(arg3, offsetof(zend_string, val));
|
|
|
|
ir_CALL_3(IR_VOID,
|
|
ir_CONST_FUNC_PROTO(zend_throw_error,
|
|
ir_proto_2(&jit->ctx, IR_VARARG_FUNC, IR_VOID, IR_ADDR, IR_ADDR)),
|
|
IR_NULL,
|
|
ir_CONST_ADDR("Call to undefined function %s()"),
|
|
arg3);
|
|
|
|
ir_IJMP(jit_STUB_ADDR(jit, jit_stub_exception_handler));
|
|
|
|
return 1;
|
|
}
|
|
|
|
static int zend_jit_throw_cannot_pass_by_ref_stub(zend_jit_ctx *jit)
|
|
{
|
|
ir_ref opline, ref, rx, if_eq, if_tmp;
|
|
|
|
// JIT: opline = EX(opline)
|
|
opline = ir_LOAD_A(jit_FP(jit));
|
|
|
|
// JIT: ZVAL_UNDEF(ZEND_CALL_VAR(RX, opline->result.var))
|
|
ref = ir_LOAD_U32(ir_ADD_OFFSET(opline, offsetof(zend_op, result.var)));
|
|
if (sizeof(void*) == 8) {
|
|
ref = ir_ZEXT_A(ref);
|
|
}
|
|
rx = jit_IP(jit);
|
|
jit_set_Z_TYPE_INFO_ref(jit, ir_ADD_A(rx, ref), ir_CONST_U32(IS_UNDEF));
|
|
|
|
// last EX(call) frame may be delayed
|
|
// JIT: if (EX(call) == RX)
|
|
ref = ir_LOAD_A(jit_EX(call));
|
|
if_eq = ir_IF(ir_EQ(rx, ref));
|
|
ir_IF_FALSE(if_eq);
|
|
|
|
// JIT: RX->prev_execute_data == EX(call)
|
|
ir_STORE(jit_CALL(rx, prev_execute_data), ref);
|
|
|
|
// JIT: EX(call) = RX
|
|
ir_STORE(jit_EX(call), rx);
|
|
ir_MERGE_WITH_EMPTY_TRUE(if_eq);
|
|
|
|
// JIT: IP = opline
|
|
jit_STORE_IP(jit, opline);
|
|
|
|
// JIT: zend_cannot_pass_by_reference(opline->op2.num)
|
|
ir_CALL_1(IR_VOID, ir_CONST_FC_FUNC(zend_cannot_pass_by_reference),
|
|
ir_LOAD_U32(ir_ADD_OFFSET(opline, offsetof(zend_op, op2.num))));
|
|
|
|
// JIT: if (IP->op1_type == IS_TMP_VAR)
|
|
ref = ir_LOAD_U8(ir_ADD_OFFSET(jit_IP(jit), offsetof(zend_op, op1_type)));
|
|
if_tmp = ir_IF(ir_EQ(ref, ir_CONST_U8(IS_TMP_VAR)));
|
|
ir_IF_TRUE(if_tmp);
|
|
|
|
// JIT: zval_ptr_dtor(EX_VAR(IP->op1.var))
|
|
ref = ir_LOAD_U32(ir_ADD_OFFSET(jit_IP(jit), offsetof(zend_op, op1.var)));
|
|
if (sizeof(void*) == 8) {
|
|
ref = ir_ZEXT_A(ref);
|
|
}
|
|
ref = ir_ADD_A(jit_FP(jit), ref);
|
|
jit_ZVAL_PTR_DTOR(jit,
|
|
ZEND_ADDR_REF_ZVAL(ref),
|
|
MAY_BE_ANY|MAY_BE_RC1|MAY_BE_RCN|MAY_BE_REF, 0, NULL);
|
|
ir_MERGE_WITH_EMPTY_FALSE(if_tmp);
|
|
|
|
ir_IJMP(jit_STUB_ADDR(jit, jit_stub_exception_handler));
|
|
|
|
return 1;
|
|
}
|
|
|
|
static int zend_jit_icall_throw_stub(zend_jit_ctx *jit)
|
|
{
|
|
ir_ref ip, if_set;
|
|
|
|
// JIT: zend_rethrow_exception(zend_execute_data *execute_data)
|
|
// JIT: if (EX(opline)->opcode != ZEND_HANDLE_EXCEPTION) {
|
|
jit_STORE_IP(jit, ir_LOAD_A(jit_EX(opline)));
|
|
ip = jit_IP(jit);
|
|
if_set = ir_IF(ir_EQ(ir_LOAD_U8(ir_ADD_OFFSET(ip, offsetof(zend_op, opcode))),
|
|
ir_CONST_U8(ZEND_HANDLE_EXCEPTION)));
|
|
ir_IF_FALSE(if_set);
|
|
|
|
// JIT: EG(opline_before_exception) = opline;
|
|
ir_STORE(jit_EG(opline_before_exception), ip);
|
|
ir_MERGE_WITH_EMPTY_TRUE(if_set);
|
|
|
|
// JIT: opline = EG(exception_op);
|
|
jit_STORE_IP(jit, jit_EG(exception_op));
|
|
|
|
if (GCC_GLOBAL_REGS) {
|
|
ir_STORE(jit_EX(opline), jit_IP(jit));
|
|
}
|
|
|
|
ir_IJMP(jit_STUB_ADDR(jit, jit_stub_exception_handler));
|
|
|
|
return 1;
|
|
}
|
|
|
|
static int zend_jit_leave_throw_stub(zend_jit_ctx *jit)
|
|
{
|
|
ir_ref ip, if_set;
|
|
|
|
// JIT: if (opline->opcode != ZEND_HANDLE_EXCEPTION) {
|
|
jit_STORE_IP(jit, ir_LOAD_A(jit_EX(opline)));
|
|
ip = jit_IP(jit);
|
|
if_set = ir_IF(ir_EQ(ir_LOAD_U8(ir_ADD_OFFSET(ip, offsetof(zend_op, opcode))),
|
|
ir_CONST_U8(ZEND_HANDLE_EXCEPTION)));
|
|
ir_IF_FALSE(if_set);
|
|
|
|
// JIT: EG(opline_before_exception) = opline;
|
|
ir_STORE(jit_EG(opline_before_exception), ip);
|
|
ir_MERGE_WITH_EMPTY_TRUE(if_set);
|
|
|
|
// JIT: opline = EG(exception_op);
|
|
jit_LOAD_IP(jit, jit_EG(exception_op));
|
|
|
|
if (GCC_GLOBAL_REGS) {
|
|
ir_STORE(jit_EX(opline), jit_IP(jit));
|
|
|
|
// JIT: HANDLE_EXCEPTION()
|
|
ir_IJMP(jit_STUB_ADDR(jit, jit_stub_exception_handler));
|
|
} else {
|
|
ir_RETURN(ir_CONST_I32(2)); // ZEND_VM_LEAVE
|
|
}
|
|
|
|
return 1;
|
|
}
|
|
|
|
static int zend_jit_hybrid_runtime_jit_stub(zend_jit_ctx *jit)
|
|
{
|
|
if (zend_jit_vm_kind != ZEND_VM_KIND_HYBRID) {
|
|
return 0;
|
|
}
|
|
|
|
ir_CALL(IR_VOID, ir_CONST_FC_FUNC(zend_runtime_jit));
|
|
ir_IJMP(ir_LOAD_A(jit_IP(jit)));
|
|
return 1;
|
|
}
|
|
|
|
static int zend_jit_hybrid_profile_jit_stub(zend_jit_ctx *jit)
|
|
{
|
|
ir_ref addr, func, run_time_cache, jit_extension;
|
|
|
|
if (zend_jit_vm_kind != ZEND_VM_KIND_HYBRID) {
|
|
return 0;
|
|
}
|
|
|
|
addr = ir_CONST_ADDR(&zend_jit_profile_counter),
|
|
ir_STORE(addr, ir_ADD_L(ir_LOAD_L(addr), ir_CONST_LONG(1)));
|
|
|
|
func = ir_LOAD_A(jit_EX(func));
|
|
run_time_cache = ir_LOAD_A(jit_EX(run_time_cache));
|
|
jit_extension = ir_LOAD_A(ir_ADD_OFFSET(func, offsetof(zend_op_array, reserved[zend_func_info_rid])));
|
|
|
|
if (zend_jit_profile_counter_rid) {
|
|
addr = ir_ADD_OFFSET(run_time_cache, zend_jit_profile_counter_rid * sizeof(void*));
|
|
} else {
|
|
addr = run_time_cache;
|
|
}
|
|
ir_STORE(addr, ir_ADD_L(ir_LOAD_L(addr), ir_CONST_LONG(1)));
|
|
|
|
addr = ir_ADD_OFFSET(jit_extension, offsetof(zend_jit_op_array_extension, orig_handler));
|
|
ir_IJMP(ir_LOAD_A(addr));
|
|
|
|
return 1;
|
|
}
|
|
|
|
static int _zend_jit_hybrid_hot_counter_stub(zend_jit_ctx *jit, uint32_t cost)
|
|
{
|
|
ir_ref func, jit_extension, addr, ref, if_overflow;
|
|
|
|
func = ir_LOAD_A(jit_EX(func));
|
|
jit_extension = ir_LOAD_A(ir_ADD_OFFSET(func, offsetof(zend_op_array, reserved[zend_func_info_rid])));
|
|
addr = ir_LOAD_A(ir_ADD_OFFSET(jit_extension, offsetof(zend_jit_op_array_hot_extension, counter)));
|
|
ref = ir_SUB_I16(ir_LOAD_I16(addr), ir_CONST_I16(cost));
|
|
ir_STORE(addr, ref);
|
|
if_overflow = ir_IF(ir_LE(ref, ir_CONST_I16(0)));
|
|
|
|
ir_IF_TRUE_cold(if_overflow);
|
|
ir_STORE(addr, ir_CONST_I16(ZEND_JIT_COUNTER_INIT));
|
|
ir_CALL_2(IR_VOID, ir_CONST_FC_FUNC(zend_jit_hot_func),
|
|
jit_FP(jit),
|
|
jit_IP(jit));
|
|
ir_IJMP(ir_LOAD_A(jit_IP(jit)));
|
|
|
|
ir_IF_FALSE(if_overflow);
|
|
ref = ir_SUB_A(jit_IP(jit),
|
|
ir_LOAD_A(ir_ADD_OFFSET(func, offsetof(zend_op_array, opcodes))));
|
|
ref = ir_DIV_A(ref, ir_CONST_ADDR(sizeof(zend_op) / sizeof(void*)));
|
|
|
|
addr = ir_ADD_A(ir_ADD_OFFSET(jit_extension, offsetof(zend_jit_op_array_hot_extension, orig_handlers)),
|
|
ref);
|
|
ir_IJMP(ir_LOAD_A(addr));
|
|
|
|
return 1;
|
|
}
|
|
|
|
static int zend_jit_hybrid_func_hot_counter_stub(zend_jit_ctx *jit)
|
|
{
|
|
if (zend_jit_vm_kind != ZEND_VM_KIND_HYBRID || !JIT_G(hot_func)) {
|
|
return 0;
|
|
}
|
|
|
|
return _zend_jit_hybrid_hot_counter_stub(jit,
|
|
((ZEND_JIT_COUNTER_INIT + JIT_G(hot_func) - 1) / JIT_G(hot_func)));
|
|
}
|
|
|
|
static int zend_jit_hybrid_loop_hot_counter_stub(zend_jit_ctx *jit)
|
|
{
|
|
if (zend_jit_vm_kind != ZEND_VM_KIND_HYBRID || !JIT_G(hot_loop)) {
|
|
return 0;
|
|
}
|
|
|
|
return _zend_jit_hybrid_hot_counter_stub(jit,
|
|
((ZEND_JIT_COUNTER_INIT + JIT_G(hot_loop) - 1) / JIT_G(hot_loop)));
|
|
}
|
|
|
|
static ir_ref _zend_jit_orig_opline_handler(zend_jit_ctx *jit, ir_ref offset)
|
|
{
|
|
ir_ref addr;
|
|
|
|
if (GCC_GLOBAL_REGS) {
|
|
addr = ir_ADD_A(offset, jit_IP(jit));
|
|
} else {
|
|
addr = ir_ADD_A(offset, ir_LOAD_A(jit_EX(opline)));
|
|
}
|
|
|
|
return ir_LOAD_A(addr);
|
|
}
|
|
|
|
static ir_ref zend_jit_orig_opline_handler(zend_jit_ctx *jit)
|
|
{
|
|
ir_ref func, jit_extension, offset;
|
|
|
|
func = ir_LOAD_A(jit_EX(func));
|
|
jit_extension = ir_LOAD_A(ir_ADD_OFFSET(func, offsetof(zend_op_array, reserved[zend_func_info_rid])));
|
|
offset = ir_LOAD_A(ir_ADD_OFFSET(jit_extension, offsetof(zend_jit_op_array_trace_extension, offset)));
|
|
return _zend_jit_orig_opline_handler(jit, offset);
|
|
}
|
|
|
|
static int _zend_jit_hybrid_trace_counter_stub(zend_jit_ctx *jit, uint32_t cost)
|
|
{
|
|
ir_ref func, jit_extension, offset, addr, ref, if_overflow, ret, if_halt;
|
|
|
|
func = ir_LOAD_A(jit_EX(func));
|
|
jit_extension = ir_LOAD_A(ir_ADD_OFFSET(func, offsetof(zend_op_array, reserved[zend_func_info_rid])));
|
|
offset = ir_LOAD_A(ir_ADD_OFFSET(jit_extension, offsetof(zend_jit_op_array_trace_extension, offset)));
|
|
addr = ir_LOAD_A(ir_ADD_OFFSET(ir_ADD_A(offset, jit_IP(jit)), offsetof(zend_op_trace_info, counter)));
|
|
ref = ir_SUB_I16(ir_LOAD_I16(addr), ir_CONST_I16(cost));
|
|
ir_STORE(addr, ref);
|
|
if_overflow = ir_IF(ir_LE(ref, ir_CONST_I16(0)));
|
|
|
|
ir_IF_TRUE_cold(if_overflow);
|
|
ir_STORE(addr, ir_CONST_I16(ZEND_JIT_COUNTER_INIT));
|
|
ret = ir_CALL_2(IR_I32, ir_CONST_FC_FUNC(zend_jit_trace_hot_root),
|
|
jit_FP(jit),
|
|
jit_IP(jit));
|
|
if_halt = ir_IF(ir_LT(ret, ir_CONST_I32(0)));
|
|
ir_IF_FALSE(if_halt);
|
|
|
|
ref = jit_EG(current_execute_data);
|
|
jit_STORE_FP(jit, ir_LOAD_A(ref));
|
|
ref = ir_LOAD_A(jit_EX(opline));
|
|
jit_STORE_IP(jit, ref);
|
|
ir_IJMP(ir_LOAD_A(jit_IP(jit)));
|
|
|
|
ir_IF_FALSE(if_overflow);
|
|
ir_IJMP(_zend_jit_orig_opline_handler(jit, offset));
|
|
|
|
ir_IF_TRUE(if_halt);
|
|
ir_IJMP(ir_CONST_FC_FUNC(zend_jit_halt_op->handler));
|
|
|
|
return 1;
|
|
}
|
|
|
|
static int zend_jit_hybrid_func_trace_counter_stub(zend_jit_ctx *jit)
|
|
{
|
|
if (zend_jit_vm_kind != ZEND_VM_KIND_HYBRID || !JIT_G(hot_func)) {
|
|
return 0;
|
|
}
|
|
|
|
return _zend_jit_hybrid_trace_counter_stub(jit,
|
|
((ZEND_JIT_COUNTER_INIT + JIT_G(hot_func) - 1) / JIT_G(hot_func)));
|
|
}
|
|
|
|
static int zend_jit_hybrid_ret_trace_counter_stub(zend_jit_ctx *jit)
|
|
{
|
|
if (zend_jit_vm_kind != ZEND_VM_KIND_HYBRID || !JIT_G(hot_return)) {
|
|
return 0;
|
|
}
|
|
|
|
return _zend_jit_hybrid_trace_counter_stub(jit,
|
|
((ZEND_JIT_COUNTER_INIT + JIT_G(hot_return) - 1) / JIT_G(hot_return)));
|
|
}
|
|
|
|
static int zend_jit_hybrid_loop_trace_counter_stub(zend_jit_ctx *jit)
|
|
{
|
|
if (zend_jit_vm_kind != ZEND_VM_KIND_HYBRID || !JIT_G(hot_loop)) {
|
|
return 0;
|
|
}
|
|
|
|
return _zend_jit_hybrid_trace_counter_stub(jit,
|
|
((ZEND_JIT_COUNTER_INIT + JIT_G(hot_loop) - 1) / JIT_G(hot_loop)));
|
|
}
|
|
|
|
static int zend_jit_trace_halt_stub(zend_jit_ctx *jit)
|
|
{
|
|
if (zend_jit_vm_kind == ZEND_VM_KIND_HYBRID) {
|
|
ir_TAILCALL(IR_VOID, ir_CONST_FC_FUNC(zend_jit_halt_op->handler));
|
|
} else if (GCC_GLOBAL_REGS) {
|
|
jit_STORE_IP(jit, IR_NULL);
|
|
ir_RETURN(IR_VOID);
|
|
} else {
|
|
ir_RETURN(ir_CONST_I32(-1)); // ZEND_VM_RETURN
|
|
}
|
|
return 1;
|
|
}
|
|
|
|
static int zend_jit_trace_escape_stub(zend_jit_ctx *jit)
|
|
{
|
|
if (GCC_GLOBAL_REGS) {
|
|
ir_TAILCALL(IR_VOID, ir_LOAD_A(jit_IP(jit)));
|
|
} else {
|
|
ir_RETURN(ir_CONST_I32(1)); // ZEND_VM_ENTER
|
|
}
|
|
|
|
return 1;
|
|
}
|
|
|
|
static int zend_jit_trace_exit_stub(zend_jit_ctx *jit)
|
|
{
|
|
ir_ref ref, ret, if_zero, addr;
|
|
|
|
if (GCC_GLOBAL_REGS) {
|
|
// EX(opline) = opline
|
|
ir_STORE(jit_EX(opline), jit_IP(jit));
|
|
}
|
|
|
|
ret = ir_EXITCALL(ir_CONST_FC_FUNC(zend_jit_trace_exit));
|
|
|
|
if_zero = ir_IF(ir_EQ(ret, ir_CONST_I32(0)));
|
|
|
|
ir_IF_TRUE(if_zero);
|
|
|
|
if (GCC_GLOBAL_REGS) {
|
|
ref = jit_EG(current_execute_data);
|
|
jit_STORE_FP(jit, ir_LOAD_A(ref));
|
|
ref = ir_LOAD_A(jit_EX(opline));
|
|
jit_STORE_IP(jit, ref);
|
|
ir_TAILCALL(IR_VOID, ir_LOAD_A(jit_IP(jit)));
|
|
} else {
|
|
ir_RETURN(ir_CONST_I32(1)); // ZEND_VM_ENTER
|
|
}
|
|
|
|
ir_IF_FALSE(if_zero);
|
|
|
|
ir_GUARD(ir_GE(ret, ir_CONST_I32(0)), jit_STUB_ADDR(jit, jit_stub_trace_halt));
|
|
|
|
ref = jit_EG(current_execute_data);
|
|
jit_STORE_FP(jit, ir_LOAD_A(ref));
|
|
|
|
if (GCC_GLOBAL_REGS) {
|
|
ref = ir_LOAD_A(jit_EX(opline));
|
|
jit_STORE_IP(jit, ref);
|
|
}
|
|
|
|
// check for interrupt (try to avoid this ???)
|
|
zend_jit_check_timeout(jit, NULL, NULL);
|
|
|
|
addr = zend_jit_orig_opline_handler(jit);
|
|
if (GCC_GLOBAL_REGS) {
|
|
ir_TAILCALL(IR_VOID, addr);
|
|
} else {
|
|
#if defined(IR_TARGET_X86)
|
|
addr = ir_CAST_FC_FUNC(addr);
|
|
#endif
|
|
ref = ir_CALL_1(IR_I32, addr, jit_FP(jit));
|
|
ir_GUARD(ir_GE(ref, ir_CONST_I32(0)), jit_STUB_ADDR(jit, jit_stub_trace_halt));
|
|
ir_RETURN(ir_CONST_I32(1)); // ZEND_VM_ENTER
|
|
}
|
|
|
|
return 1;
|
|
}
|
|
|
|
static int zend_jit_undefined_offset_stub(zend_jit_ctx *jit)
|
|
{
|
|
if (GCC_GLOBAL_REGS) {
|
|
ir_TAILCALL(IR_VOID, ir_CONST_FC_FUNC(zend_jit_undefined_long_key));
|
|
} else {
|
|
ir_TAILCALL_1(IR_VOID, ir_CONST_FC_FUNC(zend_jit_undefined_long_key), jit_FP(jit));
|
|
}
|
|
|
|
return 1;
|
|
}
|
|
|
|
static int zend_jit_undefined_key_stub(zend_jit_ctx *jit)
|
|
{
|
|
if (GCC_GLOBAL_REGS) {
|
|
ir_TAILCALL(IR_VOID, ir_CONST_FC_FUNC(zend_jit_undefined_string_key));
|
|
} else {
|
|
ir_TAILCALL_1(IR_VOID, ir_CONST_FC_FUNC(zend_jit_undefined_string_key), jit_FP(jit));
|
|
}
|
|
|
|
return 1;
|
|
}
|
|
|
|
static int zend_jit_cannot_add_element_stub(zend_jit_ctx *jit)
|
|
{
|
|
ir_ref opline = ir_LOAD_A(jit_EX(opline));
|
|
ir_ref ref, if_result_used;
|
|
|
|
if_result_used = ir_IF(ir_AND_U8(
|
|
ir_LOAD_U8(ir_ADD_OFFSET(opline, offsetof(zend_op, result_type))),
|
|
ir_CONST_U8(IS_TMP_VAR|IS_VAR)));
|
|
ir_IF_TRUE(if_result_used);
|
|
|
|
ref = ir_LOAD_U32(ir_ADD_OFFSET(opline, offsetof(zend_op, result.var)));
|
|
if (sizeof(void*) == 8) {
|
|
ref = ir_ZEXT_A(ref);
|
|
}
|
|
jit_set_Z_TYPE_INFO_ref(jit, ir_ADD_A(jit_FP(jit), ref), ir_CONST_U32(IS_UNDEF));
|
|
ir_MERGE_WITH_EMPTY_FALSE(if_result_used);
|
|
|
|
ir_CALL_2(IR_VOID,
|
|
ir_CONST_FUNC_PROTO(zend_throw_error,
|
|
ir_proto_2(&jit->ctx, IR_VARARG_FUNC, IR_VOID, IR_ADDR, IR_ADDR)),
|
|
IR_NULL,
|
|
ir_CONST_ADDR("Cannot add element to the array as the next element is already occupied"));
|
|
ir_RETURN(IR_VOID);
|
|
|
|
return 1;
|
|
}
|
|
|
|
static int zend_jit_assign_const_stub(zend_jit_ctx *jit)
|
|
{
|
|
ir_ref var = ir_PARAM(IR_ADDR, "var", 1);
|
|
ir_ref val = ir_PARAM(IR_ADDR, "val", 2);
|
|
|
|
zend_jit_addr var_addr = ZEND_ADDR_REF_ZVAL(var);
|
|
zend_jit_addr val_addr = ZEND_ADDR_REF_ZVAL(val);
|
|
uint32_t val_info = MAY_BE_ANY|MAY_BE_RC1|MAY_BE_RCN;
|
|
|
|
if (!zend_jit_assign_to_variable(
|
|
jit, NULL,
|
|
var_addr, var_addr, -1, -1,
|
|
IS_CONST, val_addr, val_info,
|
|
0, 0, 0)) {
|
|
return 0;
|
|
}
|
|
ir_RETURN(IR_VOID);
|
|
return 1;
|
|
}
|
|
|
|
static int zend_jit_assign_tmp_stub(zend_jit_ctx *jit)
|
|
{
|
|
ir_ref var = ir_PARAM(IR_ADDR, "var", 1);
|
|
ir_ref val = ir_PARAM(IR_ADDR, "val", 2);
|
|
|
|
zend_jit_addr var_addr = ZEND_ADDR_REF_ZVAL(var);
|
|
zend_jit_addr val_addr = ZEND_ADDR_REF_ZVAL(val);
|
|
uint32_t val_info = MAY_BE_ANY|MAY_BE_RC1|MAY_BE_RCN;
|
|
|
|
if (!zend_jit_assign_to_variable(
|
|
jit, NULL,
|
|
var_addr, var_addr, -1, -1,
|
|
IS_TMP_VAR, val_addr, val_info,
|
|
0, 0, 0)) {
|
|
return 0;
|
|
}
|
|
ir_RETURN(IR_VOID);
|
|
return 1;
|
|
}
|
|
|
|
static int zend_jit_assign_var_stub(zend_jit_ctx *jit)
|
|
{
|
|
ir_ref var = ir_PARAM(IR_ADDR, "var", 1);
|
|
ir_ref val = ir_PARAM(IR_ADDR, "val", 2);
|
|
|
|
zend_jit_addr var_addr = ZEND_ADDR_REF_ZVAL(var);
|
|
zend_jit_addr val_addr = ZEND_ADDR_REF_ZVAL(val);
|
|
uint32_t val_info = MAY_BE_ANY|MAY_BE_RC1|MAY_BE_RCN|MAY_BE_REF;
|
|
|
|
if (!zend_jit_assign_to_variable(
|
|
jit, NULL,
|
|
var_addr, var_addr, -1, -1,
|
|
IS_VAR, val_addr, val_info,
|
|
0, 0, 0)) {
|
|
return 0;
|
|
}
|
|
ir_RETURN(IR_VOID);
|
|
return 1;
|
|
}
|
|
|
|
static int zend_jit_assign_cv_noref_stub(zend_jit_ctx *jit)
|
|
{
|
|
ir_ref var = ir_PARAM(IR_ADDR, "var", 1);
|
|
ir_ref val = ir_PARAM(IR_ADDR, "val", 2);
|
|
|
|
zend_jit_addr var_addr = ZEND_ADDR_REF_ZVAL(var);
|
|
zend_jit_addr val_addr = ZEND_ADDR_REF_ZVAL(val);
|
|
uint32_t val_info = MAY_BE_ANY|MAY_BE_RC1|MAY_BE_RCN/*|MAY_BE_UNDEF*/;
|
|
|
|
if (!zend_jit_assign_to_variable(
|
|
jit, NULL,
|
|
var_addr, var_addr, -1, -1,
|
|
IS_CV, val_addr, val_info,
|
|
0, 0, 0)) {
|
|
return 0;
|
|
}
|
|
ir_RETURN(IR_VOID);
|
|
return 1;
|
|
}
|
|
|
|
static int zend_jit_new_array_stub(zend_jit_ctx *jit)
|
|
{
|
|
ir_ref var = ir_PARAM(IR_ADDR, "var", 1);
|
|
zend_jit_addr var_addr = ZEND_ADDR_REF_ZVAL(var);
|
|
ir_ref ref = ir_CALL(IR_ADDR, ir_CONST_FC_FUNC(_zend_new_array_0));
|
|
|
|
jit_set_Z_PTR(jit, var_addr, ref);
|
|
jit_set_Z_TYPE_INFO(jit, var_addr, IS_ARRAY_EX);
|
|
ir_RETURN(ref);
|
|
return 1;
|
|
}
|
|
|
|
static int zend_jit_assign_cv_stub(zend_jit_ctx *jit)
|
|
{
|
|
ir_ref var = ir_PARAM(IR_ADDR, "var", 1);
|
|
ir_ref val = ir_PARAM(IR_ADDR, "val", 2);
|
|
|
|
zend_jit_addr var_addr = ZEND_ADDR_REF_ZVAL(var);
|
|
zend_jit_addr val_addr = ZEND_ADDR_REF_ZVAL(val);
|
|
uint32_t val_info = MAY_BE_ANY|MAY_BE_RC1|MAY_BE_RCN|MAY_BE_REF/*|MAY_BE_UNDEF*/;
|
|
|
|
if (!zend_jit_assign_to_variable(
|
|
jit, NULL,
|
|
var_addr, var_addr, -1, -1,
|
|
IS_CV, val_addr, val_info,
|
|
0, 0, 0)) {
|
|
return 0;
|
|
}
|
|
ir_RETURN(IR_VOID);
|
|
return 1;
|
|
}
|
|
|
|
static void zend_jit_init_ctx(zend_jit_ctx *jit, uint32_t flags)
|
|
{
|
|
#if defined (__CET__) && (__CET__ & 1) != 0
|
|
flags |= IR_GEN_ENDBR;
|
|
#endif
|
|
flags |= IR_OPT_FOLDING | IR_OPT_CFG | IR_OPT_CODEGEN;
|
|
|
|
ir_init(&jit->ctx, flags, 256, 1024);
|
|
jit->ctx.ret_type = -1;
|
|
|
|
#if defined(IR_TARGET_X86) || defined(IR_TARGET_X64)
|
|
jit->ctx.mflags |= default_mflags;
|
|
if (JIT_G(opt_flags) & allowed_opt_flags & ZEND_JIT_CPU_AVX) {
|
|
jit->ctx.mflags |= IR_X86_AVX;
|
|
}
|
|
#elif defined(IR_TARGET_AARCH64)
|
|
jit->ctx.get_veneer = zend_jit_get_veneer;
|
|
jit->ctx.set_veneer = zend_jit_set_veneer;
|
|
#endif
|
|
|
|
jit->ctx.fixed_regset = (1<<ZREG_FP) | (1<<ZREG_IP);
|
|
if (!(flags & IR_FUNCTION)) {
|
|
jit->ctx.flags |= IR_NO_STACK_COMBINE;
|
|
if (zend_jit_vm_kind == ZEND_VM_KIND_CALL) {
|
|
jit->ctx.flags |= IR_FUNCTION;
|
|
/* Stack must be 16 byte aligned */
|
|
/* TODO: select stack size ??? */
|
|
#if defined(IR_TARGET_AARCH64)
|
|
jit->ctx.flags |= IR_USE_FRAME_POINTER;
|
|
jit->ctx.fixed_stack_frame_size = sizeof(void*) * 16; /* 10 saved registers and 6 spill slots (8 bytes) */
|
|
#elif defined(_WIN64)
|
|
jit->ctx.fixed_stack_frame_size = sizeof(void*) * 11; /* 8 saved registers and 3 spill slots (8 bytes) */
|
|
#elif defined(IR_TARGET_X86_64)
|
|
jit->ctx.fixed_stack_frame_size = sizeof(void*) * 9; /* 6 saved registers and 3 spill slots (8 bytes) */
|
|
#else /* IR_TARGET_x86 */
|
|
jit->ctx.fixed_stack_frame_size = sizeof(void*) * 11; /* 4 saved registers and 7 spill slots (4 bytes) */
|
|
#endif
|
|
if (GCC_GLOBAL_REGS) {
|
|
jit->ctx.fixed_save_regset = IR_REGSET_PRESERVED & ~((1<<ZREG_FP) | (1<<ZREG_IP));
|
|
} else {
|
|
jit->ctx.fixed_save_regset = IR_REGSET_PRESERVED;
|
|
//#ifdef _WIN64
|
|
// jit->ctx.fixed_save_regset &= 0xffff; // TODO: don't save FP registers ???
|
|
//#endif
|
|
}
|
|
jit->ctx.fixed_call_stack_size = 16;
|
|
} else {
|
|
#ifdef ZEND_VM_HYBRID_JIT_RED_ZONE_SIZE
|
|
jit->ctx.fixed_stack_red_zone = ZEND_VM_HYBRID_JIT_RED_ZONE_SIZE;
|
|
if (jit->ctx.fixed_stack_red_zone > 16) {
|
|
jit->ctx.fixed_stack_frame_size = jit->ctx.fixed_stack_red_zone - 16;
|
|
jit->ctx.fixed_call_stack_size = 16;
|
|
}
|
|
jit->ctx.flags |= IR_MERGE_EMPTY_ENTRIES;
|
|
#else
|
|
jit->ctx.fixed_stack_red_zone = 0;
|
|
jit->ctx.fixed_stack_frame_size = 32; /* 4 spill slots (8 bytes) or 8 spill slots (4 bytes) */
|
|
jit->ctx.fixed_call_stack_size = 16;
|
|
#endif
|
|
#if defined(IR_TARGET_X86) || defined(IR_TARGET_X64)
|
|
jit->ctx.fixed_regset |= (1<<IR_REG_FP); /* prevent %rbp (%r5) usage */
|
|
#endif
|
|
}
|
|
}
|
|
|
|
jit->ctx.snapshot_create = (ir_snapshot_create_t)jit_SNAPSHOT;
|
|
|
|
jit->op_array = NULL;
|
|
jit->current_op_array = NULL;
|
|
jit->ssa = NULL;
|
|
jit->name = NULL;
|
|
jit->last_valid_opline = NULL;
|
|
jit->use_last_valid_opline = 0;
|
|
jit->track_last_valid_opline = 0;
|
|
jit->reuse_ip = 0;
|
|
jit->delayed_call_level = 0;
|
|
delayed_call_chain = 0;
|
|
jit->b = -1;
|
|
#ifdef ZTS
|
|
jit->tls = IR_UNUSED;
|
|
#endif
|
|
jit->fp = IR_UNUSED;
|
|
jit->trace_loop_ref = IR_UNUSED;
|
|
jit->return_inputs = IR_UNUSED;
|
|
jit->bb_start_ref = NULL;
|
|
jit->bb_predecessors = NULL;
|
|
jit->bb_edges = NULL;
|
|
jit->trace = NULL;
|
|
jit->ra = NULL;
|
|
jit->delay_var = -1;
|
|
jit->delay_refs = NULL;
|
|
jit->eg_exception_addr = 0;
|
|
zend_hash_init(&jit->addr_hash, 64, NULL, NULL, 0);
|
|
memset(jit->stub_addr, 0, sizeof(jit->stub_addr));
|
|
|
|
ir_START();
|
|
}
|
|
|
|
static int zend_jit_free_ctx(zend_jit_ctx *jit)
|
|
{
|
|
if (jit->name) {
|
|
zend_string_release(jit->name);
|
|
}
|
|
zend_hash_destroy(&jit->addr_hash);
|
|
ir_free(&jit->ctx);
|
|
return 1;
|
|
}
|
|
|
|
static void *zend_jit_ir_compile(ir_ctx *ctx, size_t *size, const char *name)
|
|
{
|
|
void *entry;
|
|
ir_code_buffer code_buffer;
|
|
|
|
if (JIT_G(debug) & ZEND_JIT_DEBUG_IR_SRC) {
|
|
if (name) fprintf(stderr, "%s: ; after folding\n", name);
|
|
ir_save(ctx, 0, stderr);
|
|
}
|
|
|
|
#if ZEND_DEBUG
|
|
ir_check(ctx);
|
|
#endif
|
|
|
|
ir_build_def_use_lists(ctx);
|
|
|
|
#if ZEND_DEBUG
|
|
ir_check(ctx);
|
|
#endif
|
|
|
|
#if 1
|
|
ir_sccp(ctx);
|
|
#endif
|
|
|
|
if (JIT_G(debug) & ZEND_JIT_DEBUG_IR_AFTER_SCCP) {
|
|
if (name) fprintf(stderr, "%s: ; after SCCP\n", name);
|
|
ir_save(ctx, 0, stderr);
|
|
}
|
|
|
|
ir_build_cfg(ctx);
|
|
ir_build_dominators_tree(ctx);
|
|
ir_find_loops(ctx);
|
|
|
|
if (JIT_G(debug) & ZEND_JIT_DEBUG_IR_AFTER_CFG) {
|
|
if (name) fprintf(stderr, "%s: ; after CFG\n", name);
|
|
ir_save(ctx, IR_SAVE_CFG, stderr);
|
|
}
|
|
|
|
ir_gcm(ctx);
|
|
|
|
if (JIT_G(debug) & ZEND_JIT_DEBUG_IR_AFTER_GCM) {
|
|
if (name) fprintf(stderr, "%s: ; after GCM\n", name);
|
|
ir_save(ctx, IR_SAVE_CFG|IR_SAVE_CFG_MAP, stderr);
|
|
}
|
|
|
|
ir_schedule(ctx);
|
|
|
|
if (JIT_G(debug) & ZEND_JIT_DEBUG_IR_AFTER_SCHEDULE) {
|
|
if (name) fprintf(stderr, "%s: ; after schedule\n", name);
|
|
ir_save(ctx, IR_SAVE_CFG, stderr);
|
|
}
|
|
|
|
ir_match(ctx);
|
|
#if !defined(IR_TARGET_AARCH64)
|
|
ctx->flags &= ~IR_USE_FRAME_POINTER; /* don't use FRAME_POINTER even with ALLOCA, TODO: cleanup this ??? */
|
|
#endif
|
|
ir_assign_virtual_registers(ctx);
|
|
ir_compute_live_ranges(ctx);
|
|
ir_coalesce(ctx);
|
|
ir_reg_alloc(ctx);
|
|
|
|
if (JIT_G(debug) & ZEND_JIT_DEBUG_IR_AFTER_REGS) {
|
|
if (name) fprintf(stderr, "%s: ; after register allocation\n", name);
|
|
ir_save(ctx, IR_SAVE_CFG|IR_SAVE_RULES|IR_SAVE_REGS, stderr);
|
|
ir_dump_live_ranges(ctx, stderr);
|
|
}
|
|
|
|
ir_schedule_blocks(ctx);
|
|
|
|
if (JIT_G(debug) & (ZEND_JIT_DEBUG_IR_FINAL|ZEND_JIT_DEBUG_IR_CODEGEN)) {
|
|
if (JIT_G(debug) & ZEND_JIT_DEBUG_IR_CODEGEN) {
|
|
if (name) fprintf(stderr, "%s: ; codegen\n", name);
|
|
ir_dump_codegen(ctx, stderr);
|
|
} else {
|
|
if (name) fprintf(stderr, "%s: ; final\n", name);
|
|
ir_save(ctx, IR_SAVE_CFG|IR_SAVE_RULES|IR_SAVE_REGS, stderr);
|
|
}
|
|
}
|
|
|
|
#if ZEND_DEBUG
|
|
ir_check(ctx);
|
|
#endif
|
|
|
|
code_buffer.start = dasm_buf;
|
|
code_buffer.end = dasm_end;
|
|
code_buffer.pos = *dasm_ptr;
|
|
ctx->code_buffer = &code_buffer;
|
|
|
|
entry = ir_emit_code(ctx, size);
|
|
|
|
*dasm_ptr = code_buffer.pos;
|
|
|
|
#if defined(IR_TARGET_AARCH64)
|
|
if (ctx->flags2 & IR_HAS_VENEERS) {
|
|
zend_jit_commit_veneers();
|
|
}
|
|
#endif
|
|
|
|
return entry;
|
|
}
|
|
|
|
static void zend_jit_setup_stubs(void)
|
|
{
|
|
zend_jit_ctx jit;
|
|
void *entry;
|
|
size_t size;
|
|
uint32_t i;
|
|
|
|
for (i = 0; i < sizeof(zend_jit_stubs)/sizeof(zend_jit_stubs[0]); i++) {
|
|
zend_jit_init_ctx(&jit, zend_jit_stubs[i].flags);
|
|
|
|
if (!zend_jit_stubs[i].stub(&jit)) {
|
|
zend_jit_free_ctx(&jit);
|
|
zend_jit_stub_handlers[i] = NULL;
|
|
continue;
|
|
}
|
|
|
|
entry = zend_jit_ir_compile(&jit.ctx, &size, zend_jit_stubs[i].name);
|
|
if (!entry) {
|
|
zend_jit_free_ctx(&jit);
|
|
zend_accel_error_noreturn(ACCEL_LOG_FATAL, "Could not enable JIT: could not compile stub");
|
|
}
|
|
|
|
zend_jit_stub_handlers[i] = entry;
|
|
|
|
if (JIT_G(debug) & (ZEND_JIT_DEBUG_ASM|ZEND_JIT_DEBUG_ASM_STUBS|ZEND_JIT_DEBUG_GDB|ZEND_JIT_DEBUG_PERF|ZEND_JIT_DEBUG_PERF_DUMP)) {
|
|
#ifdef HAVE_CAPSTONE
|
|
if (JIT_G(debug) & (ZEND_JIT_DEBUG_ASM|ZEND_JIT_DEBUG_ASM_STUBS)) {
|
|
ir_disasm_add_symbol(zend_jit_stubs[i].name, (uintptr_t)entry, size);
|
|
}
|
|
if (JIT_G(debug) & ZEND_JIT_DEBUG_ASM_STUBS) {
|
|
ir_disasm(zend_jit_stubs[i].name,
|
|
entry, size, (JIT_G(debug) & ZEND_JIT_DEBUG_ASM_ADDR) != 0, &jit.ctx, stderr);
|
|
}
|
|
#endif
|
|
#ifndef _WIN32
|
|
if (JIT_G(debug) & ZEND_JIT_DEBUG_GDB) {
|
|
// ir_mem_unprotect(entry, size);
|
|
ir_gdb_register(zend_jit_stubs[i].name, entry, size, 0, 0);
|
|
// ir_mem_protect(entry, size);
|
|
}
|
|
|
|
if (JIT_G(debug) & (ZEND_JIT_DEBUG_PERF|ZEND_JIT_DEBUG_PERF_DUMP)) {
|
|
ir_perf_map_register(zend_jit_stubs[i].name, entry, size);
|
|
if (JIT_G(debug) & ZEND_JIT_DEBUG_PERF_DUMP) {
|
|
ir_perf_jitdump_register(zend_jit_stubs[i].name, entry, size);
|
|
}
|
|
}
|
|
#endif
|
|
}
|
|
zend_jit_free_ctx(&jit);
|
|
}
|
|
}
|
|
|
|
#define REGISTER_HELPER(n) \
|
|
ir_disasm_add_symbol(#n, (uint64_t)(uintptr_t)n, sizeof(void*));
|
|
#define REGISTER_DATA(n) \
|
|
ir_disasm_add_symbol(#n, (uint64_t)(uintptr_t)&n, sizeof(n));
|
|
|
|
static void zend_jit_setup_disasm(void)
|
|
{
|
|
#ifdef HAVE_CAPSTONE
|
|
ir_disasm_init();
|
|
|
|
if (zend_vm_kind() == ZEND_VM_KIND_HYBRID) {
|
|
zend_op opline;
|
|
|
|
memset(&opline, 0, sizeof(opline));
|
|
|
|
opline.opcode = ZEND_DO_UCALL;
|
|
opline.result_type = IS_UNUSED;
|
|
zend_vm_set_opcode_handler(&opline);
|
|
ir_disasm_add_symbol("ZEND_DO_UCALL_SPEC_RETVAL_UNUSED_LABEL", (uint64_t)(uintptr_t)opline.handler, sizeof(void*));
|
|
|
|
opline.opcode = ZEND_DO_UCALL;
|
|
opline.result_type = IS_VAR;
|
|
zend_vm_set_opcode_handler(&opline);
|
|
ir_disasm_add_symbol("ZEND_DO_UCALL_SPEC_RETVAL_USED_LABEL", (uint64_t)(uintptr_t)opline.handler, sizeof(void*));
|
|
|
|
opline.opcode = ZEND_DO_FCALL_BY_NAME;
|
|
opline.result_type = IS_UNUSED;
|
|
zend_vm_set_opcode_handler(&opline);
|
|
ir_disasm_add_symbol("ZEND_DO_FCALL_BY_NAME_SPEC_RETVAL_UNUSED_LABEL", (uint64_t)(uintptr_t)opline.handler, sizeof(void*));
|
|
|
|
opline.opcode = ZEND_DO_FCALL_BY_NAME;
|
|
opline.result_type = IS_VAR;
|
|
zend_vm_set_opcode_handler(&opline);
|
|
ir_disasm_add_symbol("ZEND_DO_FCALL_BY_NAME_SPEC_RETVAL_USED_LABEL", (uint64_t)(uintptr_t)opline.handler, sizeof(void*));
|
|
|
|
opline.opcode = ZEND_DO_FCALL;
|
|
opline.result_type = IS_UNUSED;
|
|
zend_vm_set_opcode_handler(&opline);
|
|
ir_disasm_add_symbol("ZEND_DO_FCALL_SPEC_RETVAL_UNUSED_LABEL", (uint64_t)(uintptr_t)opline.handler, sizeof(void*));
|
|
|
|
opline.opcode = ZEND_DO_FCALL;
|
|
opline.result_type = IS_VAR;
|
|
zend_vm_set_opcode_handler(&opline);
|
|
ir_disasm_add_symbol("ZEND_DO_FCALL_SPEC_RETVAL_USED_LABEL", (uint64_t)(uintptr_t)opline.handler, sizeof(void*));
|
|
|
|
opline.opcode = ZEND_RETURN;
|
|
opline.op1_type = IS_CONST;
|
|
zend_vm_set_opcode_handler(&opline);
|
|
ir_disasm_add_symbol("ZEND_RETURN_SPEC_CONST_LABEL", (uint64_t)(uintptr_t)opline.handler, sizeof(void*));
|
|
|
|
opline.opcode = ZEND_RETURN;
|
|
opline.op1_type = IS_TMP_VAR;
|
|
zend_vm_set_opcode_handler(&opline);
|
|
ir_disasm_add_symbol("ZEND_RETURN_SPEC_TMP_LABEL", (uint64_t)(uintptr_t)opline.handler, sizeof(void*));
|
|
|
|
opline.opcode = ZEND_RETURN;
|
|
opline.op1_type = IS_VAR;
|
|
zend_vm_set_opcode_handler(&opline);
|
|
ir_disasm_add_symbol("ZEND_RETURN_SPEC_VAR_LABEL", (uint64_t)(uintptr_t)opline.handler, sizeof(void*));
|
|
|
|
opline.opcode = ZEND_RETURN;
|
|
opline.op1_type = IS_CV;
|
|
zend_vm_set_opcode_handler(&opline);
|
|
ir_disasm_add_symbol("ZEND_RETURN_SPEC_CV_LABEL", (uint64_t)(uintptr_t)opline.handler, sizeof(void*));
|
|
|
|
ir_disasm_add_symbol("ZEND_HYBRID_HALT_LABEL", (uint64_t)(uintptr_t)zend_jit_halt_op->handler, sizeof(void*));
|
|
}
|
|
|
|
REGISTER_DATA(zend_jit_profile_counter);
|
|
|
|
REGISTER_HELPER(zend_runtime_jit);
|
|
REGISTER_HELPER(zend_jit_hot_func);
|
|
REGISTER_HELPER(zend_jit_trace_hot_root);
|
|
REGISTER_HELPER(zend_jit_trace_exit);
|
|
|
|
REGISTER_HELPER(zend_jit_array_free);
|
|
REGISTER_HELPER(zend_jit_undefined_op_helper);
|
|
REGISTER_HELPER(zend_jit_pre_inc_typed_ref);
|
|
REGISTER_HELPER(zend_jit_pre_dec_typed_ref);
|
|
REGISTER_HELPER(zend_jit_post_inc_typed_ref);
|
|
REGISTER_HELPER(zend_jit_post_dec_typed_ref);
|
|
REGISTER_HELPER(zend_jit_pre_inc);
|
|
REGISTER_HELPER(zend_jit_pre_dec);
|
|
REGISTER_HELPER(zend_jit_add_arrays_helper);
|
|
REGISTER_HELPER(zend_jit_fast_assign_concat_helper);
|
|
REGISTER_HELPER(zend_jit_fast_concat_helper);
|
|
REGISTER_HELPER(zend_jit_fast_concat_tmp_helper);
|
|
REGISTER_HELPER(zend_jit_assign_op_to_typed_ref_tmp);
|
|
REGISTER_HELPER(zend_jit_assign_op_to_typed_ref);
|
|
REGISTER_HELPER(zend_jit_assign_const_to_typed_ref);
|
|
REGISTER_HELPER(zend_jit_assign_tmp_to_typed_ref);
|
|
REGISTER_HELPER(zend_jit_assign_var_to_typed_ref);
|
|
REGISTER_HELPER(zend_jit_assign_cv_to_typed_ref);
|
|
REGISTER_HELPER(zend_jit_assign_const_to_typed_ref2);
|
|
REGISTER_HELPER(zend_jit_assign_tmp_to_typed_ref2);
|
|
REGISTER_HELPER(zend_jit_assign_var_to_typed_ref2);
|
|
REGISTER_HELPER(zend_jit_assign_cv_to_typed_ref2);
|
|
REGISTER_HELPER(zend_jit_check_constant);
|
|
REGISTER_HELPER(zend_jit_get_constant);
|
|
REGISTER_HELPER(zend_jit_int_extend_stack_helper);
|
|
REGISTER_HELPER(zend_jit_extend_stack_helper);
|
|
REGISTER_HELPER(zend_jit_init_func_run_time_cache_helper);
|
|
REGISTER_HELPER(zend_jit_find_func_helper);
|
|
REGISTER_HELPER(zend_jit_find_ns_func_helper);
|
|
REGISTER_HELPER(zend_jit_jmp_frameless_helper);
|
|
REGISTER_HELPER(zend_jit_unref_helper);
|
|
REGISTER_HELPER(zend_jit_invalid_method_call);
|
|
REGISTER_HELPER(zend_jit_invalid_method_call_tmp);
|
|
REGISTER_HELPER(zend_jit_find_method_helper);
|
|
REGISTER_HELPER(zend_jit_find_method_tmp_helper);
|
|
REGISTER_HELPER(zend_jit_push_static_method_call_frame);
|
|
REGISTER_HELPER(zend_jit_push_static_method_call_frame_tmp);
|
|
REGISTER_HELPER(zend_jit_find_class_helper);
|
|
REGISTER_HELPER(zend_jit_find_static_method_helper);
|
|
REGISTER_HELPER(zend_jit_push_this_method_call_frame);
|
|
REGISTER_HELPER(zend_jit_free_trampoline_helper);
|
|
REGISTER_HELPER(zend_jit_verify_return_slow);
|
|
REGISTER_HELPER(zend_jit_deprecated_helper);
|
|
REGISTER_HELPER(zend_jit_undefined_long_key);
|
|
REGISTER_HELPER(zend_jit_undefined_long_key_ex);
|
|
REGISTER_HELPER(zend_jit_undefined_string_key);
|
|
REGISTER_HELPER(zend_jit_copy_extra_args_helper);
|
|
REGISTER_HELPER(zend_jit_vm_stack_free_args_helper);
|
|
REGISTER_HELPER(zend_free_extra_named_params);
|
|
REGISTER_HELPER(zend_jit_free_call_frame);
|
|
REGISTER_HELPER(zend_jit_exception_in_interrupt_handler_helper);
|
|
REGISTER_HELPER(zend_jit_verify_arg_slow);
|
|
REGISTER_HELPER(zend_missing_arg_error);
|
|
REGISTER_HELPER(zend_jit_only_vars_by_reference);
|
|
REGISTER_HELPER(zend_jit_leave_func_helper);
|
|
REGISTER_HELPER(zend_jit_leave_nested_func_helper);
|
|
REGISTER_HELPER(zend_jit_leave_top_func_helper);
|
|
REGISTER_HELPER(zend_jit_fetch_global_helper);
|
|
REGISTER_HELPER(zend_jit_hash_index_lookup_rw_no_packed);
|
|
REGISTER_HELPER(zend_jit_hash_index_lookup_rw);
|
|
REGISTER_HELPER(zend_jit_hash_lookup_rw);
|
|
REGISTER_HELPER(zend_jit_symtable_find);
|
|
REGISTER_HELPER(zend_jit_symtable_lookup_w);
|
|
REGISTER_HELPER(zend_jit_symtable_lookup_rw);
|
|
REGISTER_HELPER(zend_jit_fetch_dim_r_helper);
|
|
REGISTER_HELPER(zend_jit_fetch_dim_is_helper);
|
|
REGISTER_HELPER(zend_jit_fetch_dim_isset_helper);
|
|
REGISTER_HELPER(zend_jit_fetch_dim_rw_helper);
|
|
REGISTER_HELPER(zend_jit_fetch_dim_w_helper);
|
|
REGISTER_HELPER(zend_jit_fetch_dim_str_offset_r_helper);
|
|
REGISTER_HELPER(zend_jit_fetch_dim_str_r_helper);
|
|
REGISTER_HELPER(zend_jit_fetch_dim_str_is_helper);
|
|
REGISTER_HELPER(zend_jit_fetch_dim_obj_r_helper);
|
|
REGISTER_HELPER(zend_jit_fetch_dim_obj_is_helper);
|
|
REGISTER_HELPER(zend_jit_invalid_array_access);
|
|
REGISTER_HELPER(zend_jit_zval_array_dup);
|
|
REGISTER_HELPER(zend_jit_prepare_assign_dim_ref);
|
|
REGISTER_HELPER(zend_jit_fetch_dim_obj_w_helper);
|
|
REGISTER_HELPER(zend_jit_fetch_dim_obj_rw_helper);
|
|
REGISTER_HELPER(zend_jit_isset_dim_helper);
|
|
REGISTER_HELPER(zend_jit_assign_dim_helper);
|
|
REGISTER_HELPER(zend_jit_assign_dim_op_helper);
|
|
REGISTER_HELPER(zend_jit_fetch_obj_w_slow);
|
|
REGISTER_HELPER(zend_jit_fetch_obj_r_slow);
|
|
REGISTER_HELPER(zend_jit_fetch_obj_r_slow_ex);
|
|
REGISTER_HELPER(zend_jit_fetch_obj_is_slow);
|
|
REGISTER_HELPER(zend_jit_fetch_obj_is_slow_ex);
|
|
REGISTER_HELPER(zend_jit_fetch_obj_r_dynamic);
|
|
REGISTER_HELPER(zend_jit_fetch_obj_r_dynamic_ex);
|
|
REGISTER_HELPER(zend_jit_fetch_obj_is_dynamic);
|
|
REGISTER_HELPER(zend_jit_fetch_obj_is_dynamic_ex);
|
|
REGISTER_HELPER(zend_jit_check_array_promotion);
|
|
REGISTER_HELPER(zend_jit_create_typed_ref);
|
|
REGISTER_HELPER(zend_jit_invalid_property_write);
|
|
REGISTER_HELPER(zend_jit_invalid_property_read);
|
|
REGISTER_HELPER(zend_jit_extract_helper);
|
|
REGISTER_HELPER(zend_jit_invalid_property_assign);
|
|
REGISTER_HELPER(zend_jit_assign_to_typed_prop);
|
|
REGISTER_HELPER(zend_jit_assign_obj_helper);
|
|
REGISTER_HELPER(zend_jit_invalid_property_assign_op);
|
|
REGISTER_HELPER(zend_jit_assign_op_to_typed_prop);
|
|
REGISTER_HELPER(zend_jit_assign_obj_op_helper);
|
|
REGISTER_HELPER(zend_jit_invalid_property_incdec);
|
|
REGISTER_HELPER(zend_jit_inc_typed_prop);
|
|
REGISTER_HELPER(zend_jit_dec_typed_prop);
|
|
REGISTER_HELPER(zend_jit_pre_inc_typed_prop);
|
|
REGISTER_HELPER(zend_jit_post_inc_typed_prop);
|
|
REGISTER_HELPER(zend_jit_pre_dec_typed_prop);
|
|
REGISTER_HELPER(zend_jit_post_dec_typed_prop);
|
|
REGISTER_HELPER(zend_jit_pre_inc_obj_helper);
|
|
REGISTER_HELPER(zend_jit_post_inc_obj_helper);
|
|
REGISTER_HELPER(zend_jit_pre_dec_obj_helper);
|
|
REGISTER_HELPER(zend_jit_post_dec_obj_helper);
|
|
REGISTER_HELPER(zend_jit_uninit_static_prop);
|
|
REGISTER_HELPER(zend_jit_rope_end);
|
|
REGISTER_HELPER(zend_fcall_interrupt);
|
|
|
|
#ifndef ZTS
|
|
REGISTER_DATA(EG(current_execute_data));
|
|
REGISTER_DATA(EG(exception));
|
|
REGISTER_DATA(EG(opline_before_exception));
|
|
REGISTER_DATA(EG(vm_interrupt));
|
|
REGISTER_DATA(EG(timed_out));
|
|
REGISTER_DATA(EG(uninitialized_zval));
|
|
REGISTER_DATA(EG(zend_constants));
|
|
REGISTER_DATA(EG(jit_trace_num));
|
|
REGISTER_DATA(EG(vm_stack_top));
|
|
REGISTER_DATA(EG(vm_stack_end));
|
|
REGISTER_DATA(EG(exception_op));
|
|
REGISTER_DATA(EG(symbol_table));
|
|
|
|
REGISTER_DATA(CG(map_ptr_base));
|
|
#endif
|
|
#endif
|
|
}
|
|
|
|
static void zend_jit_calc_trace_prologue_size(void)
|
|
{
|
|
zend_jit_ctx jit_ctx;
|
|
zend_jit_ctx *jit = &jit_ctx;
|
|
void *entry;
|
|
size_t size;
|
|
|
|
zend_jit_init_ctx(jit, (zend_jit_vm_kind == ZEND_VM_KIND_CALL) ? 0 : IR_START_BR_TARGET);
|
|
|
|
if (!GCC_GLOBAL_REGS) {
|
|
ir_ref ref = ir_PARAM(IR_ADDR, "execute_data", 1);
|
|
jit_STORE_FP(jit, ref);
|
|
jit->ctx.flags |= IR_FASTCALL_FUNC;
|
|
}
|
|
|
|
ir_UNREACHABLE();
|
|
|
|
entry = zend_jit_ir_compile(&jit->ctx, &size, "JIT$trace_prologue");
|
|
zend_jit_free_ctx(jit);
|
|
|
|
if (!entry) {
|
|
zend_accel_error_noreturn(ACCEL_LOG_FATAL, "Could not enable JIT: could not compile prologue");
|
|
}
|
|
|
|
zend_jit_trace_prologue_size = size;
|
|
}
|
|
|
|
#if !defined(ZEND_WIN32) && !defined(IR_TARGET_AARCH64)
|
|
static uintptr_t zend_jit_hybrid_vm_sp_adj = 0;
|
|
|
|
typedef struct _Unwind_Context _Unwind_Context;
|
|
typedef int (*_Unwind_Trace_Fn)(_Unwind_Context *, void *);
|
|
extern int _Unwind_Backtrace(_Unwind_Trace_Fn, void *);
|
|
extern uintptr_t _Unwind_GetCFA(_Unwind_Context *);
|
|
|
|
typedef struct _zend_jit_unwind_arg {
|
|
int cnt;
|
|
uintptr_t cfa[3];
|
|
} zend_jit_unwind_arg;
|
|
|
|
static int zend_jit_unwind_cb(_Unwind_Context *ctx, void *a)
|
|
{
|
|
zend_jit_unwind_arg *arg = (zend_jit_unwind_arg*)a;
|
|
arg->cfa[arg->cnt] = _Unwind_GetCFA(ctx);
|
|
arg->cnt++;
|
|
if (arg->cnt == 3) {
|
|
return 5; // _URC_END_OF_STACK
|
|
}
|
|
return 0; // _URC_NO_REASON;
|
|
}
|
|
|
|
static void ZEND_FASTCALL zend_jit_touch_vm_stack_data(void *vm_stack_data)
|
|
{
|
|
zend_jit_unwind_arg arg;
|
|
|
|
memset(&arg, 0, sizeof(arg));
|
|
_Unwind_Backtrace(zend_jit_unwind_cb, &arg);
|
|
if (arg.cnt == 3) {
|
|
zend_jit_hybrid_vm_sp_adj = arg.cfa[2] - arg.cfa[1];
|
|
}
|
|
}
|
|
|
|
extern void (ZEND_FASTCALL *zend_touch_vm_stack_data)(void *vm_stack_data);
|
|
|
|
static zend_never_inline void zend_jit_set_sp_adj_vm(void)
|
|
{
|
|
void (ZEND_FASTCALL *orig_zend_touch_vm_stack_data)(void *);
|
|
|
|
orig_zend_touch_vm_stack_data = zend_touch_vm_stack_data;
|
|
zend_touch_vm_stack_data = zend_jit_touch_vm_stack_data;
|
|
execute_ex(NULL); // set sp_adj[SP_ADJ_VM]
|
|
zend_touch_vm_stack_data = orig_zend_touch_vm_stack_data;
|
|
}
|
|
#endif
|
|
|
|
static void zend_jit_setup(void)
|
|
{
|
|
#if defined(IR_TARGET_X86)
|
|
if (!zend_cpu_supports_sse2()) {
|
|
zend_accel_error_noreturn(ACCEL_LOG_FATAL, "Could not enable JIT: CPU doesn't support SSE2");
|
|
}
|
|
#endif
|
|
#if defined(IR_TARGET_X86) || defined(IR_TARGET_X64)
|
|
allowed_opt_flags = 0;
|
|
if (zend_cpu_supports_avx()) {
|
|
allowed_opt_flags |= ZEND_JIT_CPU_AVX;
|
|
}
|
|
# if defined(PHP_HAVE_BUILTIN_CPU_SUPPORTS) && defined(__GNUC__) && (ZEND_GCC_VERSION >= 11000)
|
|
if (zend_cpu_supports_cldemote()) {
|
|
default_mflags |= IR_X86_CLDEMOTE;
|
|
}
|
|
# endif
|
|
#endif
|
|
#ifdef ZTS
|
|
#if defined(IR_TARGET_AARCH64)
|
|
tsrm_ls_cache_tcb_offset = tsrm_get_ls_cache_tcb_offset();
|
|
|
|
# ifdef __FreeBSD__
|
|
if (tsrm_ls_cache_tcb_offset == 0) {
|
|
TLSDescriptor **where;
|
|
|
|
__asm__(
|
|
"adrp %0, :tlsdesc:_tsrm_ls_cache\n"
|
|
"add %0, %0, :tlsdesc_lo12:_tsrm_ls_cache\n"
|
|
: "=r" (where));
|
|
/* See https://github.com/ARM-software/abi-aa/blob/2a70c42d62e9c3eb5887fa50b71257f20daca6f9/aaelf64/aaelf64.rst
|
|
* section "Relocations for thread-local storage".
|
|
* The first entry holds a pointer to the variable's TLS descriptor resolver function and the second entry holds
|
|
* a platform-specific offset or pointer. */
|
|
TLSDescriptor *tlsdesc = where[1];
|
|
|
|
tsrm_tls_offset = tlsdesc->offset;
|
|
/* Index is offset by 1 on FreeBSD (https://github.com/freebsd/freebsd-src/blob/22ca6db50f4e6bd75a141f57cf953d8de6531a06/lib/libc/gen/tls.c#L88) */
|
|
tsrm_tls_index = (tlsdesc->index + 1) * 8;
|
|
}
|
|
# else
|
|
ZEND_ASSERT(tsrm_ls_cache_tcb_offset != 0);
|
|
# endif
|
|
# elif defined(_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) {
|
|
zend_accel_error_noreturn(ACCEL_LOG_FATAL, "Could not enable JIT: offset >= size");
|
|
}
|
|
} while(0);
|
|
# elif defined(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) {
|
|
zend_accel_error_noreturn(ACCEL_LOG_FATAL, "Could not enable JIT: offset >= size");
|
|
}
|
|
} 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(__NetBSD__) && !defined(__OpenBSD__) && !defined(__MUSL__)
|
|
size_t ret;
|
|
|
|
asm ("movq _tsrm_ls_cache@gottpoff(%%rip),%0"
|
|
: "=r" (ret));
|
|
tsrm_ls_cache_tcb_offset = ret;
|
|
#elif defined(__MUSL__)
|
|
size_t *ti;
|
|
|
|
__asm__(
|
|
"leaq _tsrm_ls_cache@tlsgd(%%rip), %0\n"
|
|
: "=a" (ti));
|
|
tsrm_tls_offset = ti[1];
|
|
tsrm_tls_index = ti[0] * 8;
|
|
#elif defined(__FreeBSD__)
|
|
size_t *ti;
|
|
|
|
__asm__(
|
|
"leaq _tsrm_ls_cache@tlsgd(%%rip), %0\n"
|
|
: "=a" (ti));
|
|
tsrm_tls_offset = ti[1];
|
|
/* Index is offset by 1 on FreeBSD (https://github.com/freebsd/freebsd-src/blob/bf56e8b9c8639ac4447d223b83cdc128107cc3cd/libexec/rtld-elf/rtld.c#L5260) */
|
|
tsrm_tls_index = (ti[0] + 1) * 8;
|
|
#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(__NetBSD__) && !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
|
|
|
|
#if !defined(ZEND_WIN32) && !defined(IR_TARGET_AARCH64)
|
|
if (zend_jit_vm_kind == ZEND_VM_KIND_HYBRID) {
|
|
zend_jit_set_sp_adj_vm(); // set zend_jit_hybrid_vm_sp_adj
|
|
}
|
|
#endif
|
|
|
|
if (JIT_G(debug) & (ZEND_JIT_DEBUG_ASM|ZEND_JIT_DEBUG_ASM_STUBS)) {
|
|
zend_jit_setup_disasm();
|
|
}
|
|
|
|
#ifndef _WIN32
|
|
if (JIT_G(debug) & ZEND_JIT_DEBUG_PERF_DUMP) {
|
|
ir_perf_jitdump_open();
|
|
}
|
|
|
|
#endif
|
|
zend_long debug = JIT_G(debug);
|
|
if (!(debug & ZEND_JIT_DEBUG_ASM_STUBS)) {
|
|
JIT_G(debug) &= ~(ZEND_JIT_DEBUG_IR_SRC|ZEND_JIT_DEBUG_IR_FINAL|
|
|
ZEND_JIT_DEBUG_IR_CODEGEN|
|
|
ZEND_JIT_DEBUG_IR_AFTER_SCCP|ZEND_JIT_DEBUG_IR_AFTER_CFG|ZEND_JIT_DEBUG_IR_AFTER_GCM|
|
|
ZEND_JIT_DEBUG_IR_AFTER_SCHEDULE|ZEND_JIT_DEBUG_IR_AFTER_REGS);
|
|
}
|
|
|
|
zend_jit_calc_trace_prologue_size();
|
|
zend_jit_setup_stubs();
|
|
JIT_G(debug) = debug;
|
|
}
|
|
|
|
static void zend_jit_shutdown_ir(void)
|
|
{
|
|
#ifndef _WIN32
|
|
if (JIT_G(debug) & ZEND_JIT_DEBUG_PERF_DUMP) {
|
|
ir_perf_jitdump_close();
|
|
}
|
|
if (JIT_G(debug) & ZEND_JIT_DEBUG_GDB) {
|
|
ir_gdb_unregister_all();
|
|
}
|
|
#endif
|
|
#ifdef HAVE_CAPSTONE
|
|
if (JIT_G(debug) & (ZEND_JIT_DEBUG_ASM|ZEND_JIT_DEBUG_ASM_STUBS)) {
|
|
ir_disasm_free();
|
|
}
|
|
#endif
|
|
}
|
|
|
|
/* PHP control flow reconstruction helpers */
|
|
static ir_ref jit_IF_ex(zend_jit_ctx *jit, ir_ref condition, ir_ref true_block)
|
|
{
|
|
ir_ref ref = ir_IF(condition);
|
|
/* op3 is used as a temporary storage for PHP BB number to reconstruct PHP control flow.
|
|
*
|
|
* It's used in jit_IF_TRUE_FALSE_ex() to select IF_TRUE or IF_FALSE instructions
|
|
* to start target block
|
|
*/
|
|
ir_set_op(&jit->ctx, ref, 3, true_block);
|
|
return ref;
|
|
}
|
|
|
|
static void jit_IF_TRUE_FALSE_ex(zend_jit_ctx *jit, ir_ref if_ref, ir_ref true_block)
|
|
{
|
|
ZEND_ASSERT(JIT_G(trigger) != ZEND_JIT_ON_HOT_TRACE);
|
|
ZEND_ASSERT(if_ref);
|
|
ZEND_ASSERT(jit->ctx.ir_base[if_ref].op == IR_IF);
|
|
ZEND_ASSERT(jit->ctx.ir_base[if_ref].op3);
|
|
if (jit->ctx.ir_base[if_ref].op3 == true_block) {
|
|
ir_IF_TRUE(if_ref);
|
|
} else {
|
|
ir_IF_FALSE(if_ref);
|
|
}
|
|
}
|
|
|
|
static void _zend_jit_add_predecessor_ref(zend_jit_ctx *jit, int b, int pred, ir_ref ref)
|
|
{
|
|
int i, *p;
|
|
zend_basic_block *bb;
|
|
ir_ref *r, header;
|
|
|
|
ZEND_ASSERT(JIT_G(trigger) != ZEND_JIT_ON_HOT_TRACE);
|
|
bb = &jit->ssa->cfg.blocks[b];
|
|
p = &jit->ssa->cfg.predecessors[bb->predecessor_offset];
|
|
r = &jit->bb_edges[jit->bb_predecessors[b]];
|
|
for (i = 0; i < bb->predecessors_count; i++, p++, r++) {
|
|
if (*p == pred) {
|
|
ZEND_ASSERT(*r == IR_UNUSED || *r == ref);
|
|
header = jit->bb_start_ref[b];
|
|
if (header) {
|
|
/* this is back edge */
|
|
ZEND_ASSERT(jit->ctx.ir_base[header].op == IR_LOOP_BEGIN);
|
|
if (jit->ctx.ir_base[ref].op == IR_END) {
|
|
jit->ctx.ir_base[ref].op = IR_LOOP_END;
|
|
} else if (jit->ctx.ir_base[ref].op == IR_IF) {
|
|
jit_IF_TRUE_FALSE_ex(jit, ref, b);
|
|
ref = ir_LOOP_END();
|
|
} else if (jit->ctx.ir_base[ref].op == IR_UNREACHABLE) {
|
|
ir_BEGIN(ref);
|
|
ref = ir_LOOP_END();
|
|
} else {
|
|
ZEND_UNREACHABLE();
|
|
}
|
|
ir_MERGE_SET_OP(header, i + 1, ref);
|
|
}
|
|
*r = ref;
|
|
return;
|
|
}
|
|
}
|
|
ZEND_UNREACHABLE();
|
|
}
|
|
|
|
static void _zend_jit_merge_smart_branch_inputs(zend_jit_ctx *jit,
|
|
uint32_t true_label,
|
|
uint32_t false_label,
|
|
ir_ref true_inputs,
|
|
ir_ref false_inputs)
|
|
{
|
|
ir_ref true_path = IR_UNUSED, false_path = IR_UNUSED;
|
|
|
|
ZEND_ASSERT(JIT_G(trigger) != ZEND_JIT_ON_HOT_TRACE);
|
|
if (true_inputs) {
|
|
ZEND_ASSERT(jit->ctx.ir_base[true_inputs].op == IR_END);
|
|
if (!jit->ctx.ir_base[true_inputs].op2) {
|
|
true_path = true_inputs;
|
|
} else {
|
|
ir_MERGE_list(true_inputs);
|
|
true_path = ir_END();
|
|
}
|
|
}
|
|
if (false_inputs) {
|
|
ZEND_ASSERT(jit->ctx.ir_base[false_inputs].op == IR_END);
|
|
if (!jit->ctx.ir_base[false_inputs].op2) {
|
|
false_path = false_inputs;
|
|
} else {
|
|
ir_MERGE_list(false_inputs);
|
|
false_path = ir_END();
|
|
}
|
|
}
|
|
|
|
if (true_label == false_label && true_path && false_path) {
|
|
ir_MERGE_2(true_path, false_path);
|
|
_zend_jit_add_predecessor_ref(jit, true_label, jit->b, ir_END());
|
|
} else if (!true_path && !false_path) {
|
|
/* dead code */
|
|
true_path = ir_END();
|
|
_zend_jit_add_predecessor_ref(jit, true_label, jit->b, true_path);
|
|
} else {
|
|
if (true_path) {
|
|
_zend_jit_add_predecessor_ref(jit, true_label, jit->b, true_path);
|
|
}
|
|
if (false_path) {
|
|
_zend_jit_add_predecessor_ref(jit, false_label, jit->b, false_path);
|
|
}
|
|
}
|
|
|
|
jit->b = -1;
|
|
}
|
|
|
|
static void _zend_jit_fix_merges(zend_jit_ctx *jit)
|
|
{
|
|
int i, count;
|
|
ir_ref j, k, n, *p, *q, *r;
|
|
ir_ref ref;
|
|
ir_insn *insn, *phi;
|
|
|
|
ZEND_ASSERT(JIT_G(trigger) != ZEND_JIT_ON_HOT_TRACE);
|
|
count = jit->ssa->cfg.blocks_count;
|
|
for (i = 0, p = jit->bb_start_ref; i < count; i++, p++) {
|
|
ref = *p;
|
|
if (ref) {
|
|
insn = &jit->ctx.ir_base[ref];
|
|
if (insn->op == IR_MERGE || insn->op == IR_LOOP_BEGIN) {
|
|
n = insn->inputs_count;
|
|
/* Remove IS_UNUSED inputs */
|
|
for (j = k = 0, q = r = insn->ops + 1; j < n; j++, q++) {
|
|
if (*q) {
|
|
if (q != r) {
|
|
*r = *q;
|
|
phi = insn + 1 + (n >> 2);
|
|
while (phi->op == IR_PI) {
|
|
phi++;
|
|
}
|
|
while (phi->op == IR_PHI) {
|
|
ir_insn_set_op(phi, k + 2, ir_insn_op(phi, j + 2));
|
|
phi += 1 + ((n + 1) >> 2);
|
|
}
|
|
}
|
|
k++;
|
|
r++;
|
|
}
|
|
}
|
|
if (k != n) {
|
|
ir_ref n2, k2;
|
|
|
|
if (k <= 1) {
|
|
insn->op = IR_BEGIN;
|
|
insn->inputs_count = 0;
|
|
} else {
|
|
insn->inputs_count = k;
|
|
}
|
|
n2 = 1 + (n >> 2);
|
|
k2 = 1 + (k >> 2);
|
|
while (k2 != n2) {
|
|
(insn+k2)->optx = IR_NOP;
|
|
k2++;
|
|
}
|
|
phi = insn + 1 + (n >> 2);
|
|
while (phi->op == IR_PI) {
|
|
phi++;
|
|
}
|
|
while (phi->op == IR_PHI) {
|
|
if (k <= 1) {
|
|
phi->op = IR_COPY;
|
|
phi->op1 = phi->op2;
|
|
phi->op2 = 1;
|
|
phi->inputs_count = 0;
|
|
} else {
|
|
phi->inputs_count = k + 1;
|
|
}
|
|
n2 = 1 + ((n + 1) >> 2);
|
|
k2 = 1 + ((k + 1) >> 2);
|
|
while (k2 != n2) {
|
|
(phi+k2)->optx = IR_NOP;
|
|
k2++;
|
|
}
|
|
phi += 1 + ((n + 1) >> 2);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
static void zend_jit_case_start(zend_jit_ctx *jit, int switch_b, int case_b, ir_ref switch_ref)
|
|
{
|
|
zend_basic_block *bb = &jit->ssa->cfg.blocks[switch_b];
|
|
const zend_op *opline = &jit->op_array->opcodes[bb->start + bb->len - 1];
|
|
|
|
if (opline->opcode == ZEND_SWITCH_LONG
|
|
|| opline->opcode == ZEND_SWITCH_STRING
|
|
|| opline->opcode == ZEND_MATCH) {
|
|
HashTable *jumptable = Z_ARRVAL_P(RT_CONSTANT(opline, opline->op2));
|
|
const zend_op *default_opline = ZEND_OFFSET_TO_OPLINE(opline, opline->extended_value);
|
|
int default_b = jit->ssa->cfg.map[default_opline - jit->op_array->opcodes];
|
|
zval *zv;
|
|
ir_ref list = IR_UNUSED, idx;
|
|
bool first = 1;
|
|
|
|
ZEND_HASH_FOREACH_VAL(jumptable, zv) {
|
|
const zend_op *target = ZEND_OFFSET_TO_OPLINE(opline, Z_LVAL_P(zv));
|
|
int b = jit->ssa->cfg.map[target - jit->op_array->opcodes];
|
|
|
|
if (b == case_b) {
|
|
if (!first) {
|
|
ir_END_list(list);
|
|
}
|
|
if (HT_IS_PACKED(jumptable)) {
|
|
idx = ir_CONST_LONG(zv - jumptable->arPacked);
|
|
} else {
|
|
idx = ir_CONST_LONG((Bucket*)zv - jumptable->arData);
|
|
}
|
|
ir_CASE_VAL(switch_ref, idx);
|
|
first = 0;
|
|
}
|
|
} ZEND_HASH_FOREACH_END();
|
|
if (default_b == case_b) {
|
|
if (!first) {
|
|
ir_END_list(list);
|
|
}
|
|
if (jit->ctx.ir_base[switch_ref].op3) {
|
|
/* op3 may contain a list of additional "default" path inputs for MATCH */
|
|
ir_ref ref = jit->ctx.ir_base[switch_ref].op3;
|
|
jit->ctx.ir_base[switch_ref].op3 = IS_UNDEF;
|
|
ZEND_ASSERT(jit->ctx.ir_base[ref].op == IR_END);
|
|
ir_ref end = ref;
|
|
while (jit->ctx.ir_base[end].op2) {
|
|
ZEND_ASSERT(jit->ctx.ir_base[end].op == IR_END);
|
|
end = jit->ctx.ir_base[end].op2;
|
|
}
|
|
jit->ctx.ir_base[end].op2 = list;
|
|
list = ref;
|
|
}
|
|
ir_CASE_DEFAULT(switch_ref);
|
|
}
|
|
if (list) {
|
|
ir_END_list(list);
|
|
ir_MERGE_list(list);
|
|
}
|
|
} else {
|
|
ZEND_UNREACHABLE();
|
|
}
|
|
}
|
|
|
|
static int zend_jit_bb_start(zend_jit_ctx *jit, int b)
|
|
{
|
|
zend_basic_block *bb;
|
|
int i, n, *p, pred;
|
|
ir_ref ref, bb_start;
|
|
|
|
ZEND_ASSERT(JIT_G(trigger) != ZEND_JIT_ON_HOT_TRACE);
|
|
ZEND_ASSERT(b < jit->ssa->cfg.blocks_count);
|
|
bb = &jit->ssa->cfg.blocks[b];
|
|
ZEND_ASSERT((bb->flags & ZEND_BB_REACHABLE) != 0);
|
|
n = bb->predecessors_count;
|
|
|
|
if (n == 0) {
|
|
/* pass */
|
|
ZEND_ASSERT(jit->ctx.control);
|
|
#if ZEND_DEBUG
|
|
ref = jit->ctx.control;
|
|
ir_insn *insn = &jit->ctx.ir_base[ref];
|
|
while (insn->op >= IR_CALL && insn->op <= IR_TRAP) {
|
|
ref = insn->op1;
|
|
insn = &jit->ctx.ir_base[ref];
|
|
}
|
|
ZEND_ASSERT(insn->op == IR_START);
|
|
ZEND_ASSERT(ref == 1);
|
|
#endif
|
|
bb_start = 1;
|
|
if (jit->ssa->cfg.flags & ZEND_FUNC_RECURSIVE_DIRECTLY) {
|
|
/* prvent END/BEGIN merging */
|
|
jit->ctx.control = ir_emit1(&jit->ctx, IR_BEGIN, ir_END());
|
|
bb_start = jit->ctx.control;
|
|
}
|
|
} else if (n == 1) {
|
|
ZEND_ASSERT(!jit->ctx.control);
|
|
pred = jit->ssa->cfg.predecessors[bb->predecessor_offset];
|
|
ref = jit->bb_edges[jit->bb_predecessors[b]];
|
|
if (ref == IR_UNUSED) {
|
|
if (!jit->ctx.control) {
|
|
ir_BEGIN(IR_UNUSED); /* unreachable block */
|
|
}
|
|
} else {
|
|
ir_op op = jit->ctx.ir_base[ref].op;
|
|
|
|
if (op == IR_IF) {
|
|
if (!jit->ctx.control) {
|
|
jit_IF_TRUE_FALSE_ex(jit, ref, b);
|
|
} else {
|
|
ir_ref entry_path = ir_END();
|
|
jit_IF_TRUE_FALSE_ex(jit, ref, b);
|
|
ir_MERGE_WITH(entry_path);
|
|
}
|
|
} else if (op == IR_SWITCH) {
|
|
zend_jit_case_start(jit, pred, b, ref);
|
|
} else {
|
|
if (!jit->ctx.control) {
|
|
ZEND_ASSERT(op == IR_END || op == IR_UNREACHABLE || op == IR_RETURN);
|
|
if ((jit->ssa->cfg.blocks[b].flags & ZEND_BB_RECV_ENTRY)
|
|
&& (jit->ssa->cfg.flags & ZEND_FUNC_RECURSIVE_DIRECTLY)) {
|
|
/* prvent END/BEGIN merging */
|
|
jit->ctx.control = ir_emit1(&jit->ctx, IR_BEGIN, ref);
|
|
} else {
|
|
ir_BEGIN(ref);
|
|
}
|
|
} else {
|
|
ir_MERGE_WITH(ref);
|
|
}
|
|
}
|
|
}
|
|
bb_start = jit->ctx.control;
|
|
} else {
|
|
int forward_edges_count = 0;
|
|
int back_edges_count = 0;
|
|
ir_ref *pred_refs;
|
|
ir_ref entry_path = IR_UNUSED;
|
|
ALLOCA_FLAG(use_heap);
|
|
|
|
ZEND_ASSERT(!jit->ctx.control);
|
|
if (jit->ctx.control) {
|
|
entry_path = ir_END();
|
|
}
|
|
pred_refs = (ir_ref *)do_alloca(sizeof(ir_ref) * n, use_heap);
|
|
for (i = 0, p = jit->ssa->cfg.predecessors + bb->predecessor_offset; i < n; p++, i++) {
|
|
pred = *p;
|
|
if (jit->bb_start_ref[pred]) {
|
|
/* forward edge */
|
|
forward_edges_count++;
|
|
ref = jit->bb_edges[jit->bb_predecessors[b] + i];
|
|
if (ref == IR_UNUSED) {
|
|
/* dead edge */
|
|
pred_refs[i] = IR_UNUSED;
|
|
} else {
|
|
ir_op op = jit->ctx.ir_base[ref].op;
|
|
|
|
if (op == IR_IF) {
|
|
jit_IF_TRUE_FALSE_ex(jit, ref, b);
|
|
pred_refs[i] = ir_END();
|
|
} else if (op == IR_SWITCH) {
|
|
zend_jit_case_start(jit, pred, b, ref);
|
|
pred_refs[i] = ir_END();
|
|
} else {
|
|
ZEND_ASSERT(op == IR_END || op == IR_UNREACHABLE || op == IR_RETURN);
|
|
pred_refs[i] = ref;
|
|
}
|
|
}
|
|
} else {
|
|
/* backward edge */
|
|
back_edges_count++;
|
|
pred_refs[i] = IR_UNUSED;
|
|
}
|
|
}
|
|
|
|
if (bb->flags & ZEND_BB_LOOP_HEADER) {
|
|
ZEND_ASSERT(back_edges_count != 0);
|
|
ZEND_ASSERT(forward_edges_count != 0);
|
|
ir_MERGE_N(n, pred_refs);
|
|
jit->ctx.ir_base[jit->ctx.control].op = IR_LOOP_BEGIN;
|
|
bb_start = jit->ctx.control;
|
|
if (entry_path) {
|
|
ir_MERGE_WITH(entry_path);
|
|
}
|
|
} else {
|
|
// ZEND_ASSERT(back_edges_count != 0);
|
|
/* edges from exceptional blocks may be counted as back edges */
|
|
ir_MERGE_N(n, pred_refs);
|
|
bb_start = jit->ctx.control;
|
|
if (entry_path) {
|
|
ir_MERGE_WITH(entry_path);
|
|
}
|
|
}
|
|
free_alloca(pred_refs, use_heap);
|
|
}
|
|
jit->b = b;
|
|
jit->bb_start_ref[b] = bb_start;
|
|
|
|
if ((bb->flags & ZEND_BB_ENTRY) || (bb->idom >= 0 && jit->bb_start_ref[bb->idom] < jit->ctx.fold_cse_limit)) {
|
|
jit->ctx.fold_cse_limit = bb_start;
|
|
}
|
|
|
|
return 1;
|
|
}
|
|
|
|
static int zend_jit_bb_end(zend_jit_ctx *jit, int b)
|
|
{
|
|
int succ;
|
|
zend_basic_block *bb;
|
|
|
|
ZEND_ASSERT(JIT_G(trigger) != ZEND_JIT_ON_HOT_TRACE);
|
|
if (jit->b != b) {
|
|
return 1;
|
|
}
|
|
|
|
bb = &jit->ssa->cfg.blocks[b];
|
|
ZEND_ASSERT(bb->successors_count != 0);
|
|
if (bb->successors_count == 1) {
|
|
succ = bb->successors[0];
|
|
} else {
|
|
const zend_op *opline = &jit->op_array->opcodes[bb->start + bb->len - 1];
|
|
|
|
/* Use only the following successor of SWITCH and FE_RESET_R */
|
|
ZEND_ASSERT(opline->opcode == ZEND_SWITCH_LONG
|
|
|| opline->opcode == ZEND_SWITCH_STRING
|
|
|| opline->opcode == ZEND_MATCH
|
|
|| opline->opcode == ZEND_FE_RESET_R);
|
|
succ = b + 1;
|
|
}
|
|
_zend_jit_add_predecessor_ref(jit, succ, b, ir_END());
|
|
jit->b = -1;
|
|
return 1;
|
|
}
|
|
|
|
static int jit_CMP_IP(zend_jit_ctx *jit, ir_op op, const zend_op *next_opline)
|
|
{
|
|
ir_ref ref;
|
|
|
|
#if 1
|
|
if (GCC_GLOBAL_REGS) {
|
|
ref = jit_IP32(jit);
|
|
} else {
|
|
ref = ir_LOAD_U32(jit_EX(opline));
|
|
}
|
|
ref = ir_CMP_OP(op, ref, ir_CONST_U32((uint32_t)(uintptr_t)next_opline));
|
|
#else
|
|
if (GCC_GLOBAL_REGS) {
|
|
ref = jit_IP(jit);
|
|
} else {
|
|
ref = ir_LOAD_A(jit_EX(opline));
|
|
}
|
|
ref = ir_CMP_OP(op, ref, ir_CONST_ADDR(next_opline));
|
|
#endif
|
|
return ref;
|
|
}
|
|
|
|
static int zend_jit_jmp_frameless(
|
|
zend_jit_ctx *jit,
|
|
const zend_op *opline,
|
|
const void *exit_addr,
|
|
zend_jmp_fl_result guard
|
|
) {
|
|
ir_ref ref, if_ref, cache_result, function_result, phi_result, cache_slot_ref;
|
|
zend_basic_block *bb;
|
|
|
|
// JIT: CACHED_PTR(opline->extended_value)
|
|
cache_slot_ref = ir_ADD_OFFSET(ir_LOAD_A(jit_EX(run_time_cache)), opline->extended_value);
|
|
cache_result = ir_LOAD_L(cache_slot_ref);
|
|
|
|
// JIT: if (UNEXPECTED(!result))
|
|
if_ref = ir_IF(cache_result);
|
|
ir_IF_FALSE_cold(if_ref);
|
|
zval *func_name_zv = RT_CONSTANT(opline, opline->op1);
|
|
function_result = ir_CALL_2(IR_LONG, ir_CONST_FC_FUNC(zend_jit_jmp_frameless_helper),
|
|
ir_CONST_ADDR(func_name_zv),
|
|
cache_slot_ref);
|
|
ir_MERGE_WITH_EMPTY_TRUE(if_ref);
|
|
|
|
phi_result = ir_PHI_2(IR_LONG, function_result, cache_result);
|
|
|
|
if (exit_addr) {
|
|
ir_GUARD(ir_EQ(phi_result, ir_CONST_LONG(guard)), ir_CONST_ADDR(exit_addr));
|
|
} else {
|
|
ZEND_ASSERT(jit->b >= 0);
|
|
bb = &jit->ssa->cfg.blocks[jit->b];
|
|
// JIT: if (result == ZEND_JMP_FL_HIT)
|
|
ref = jit_IF_ex(jit, ir_EQ(phi_result, ir_CONST_LONG(ZEND_JMP_FL_HIT)), bb->successors[0]);
|
|
_zend_jit_add_predecessor_ref(jit, bb->successors[0], jit->b, ref);
|
|
_zend_jit_add_predecessor_ref(jit, bb->successors[1], jit->b, ref);
|
|
jit->b = -1;
|
|
}
|
|
|
|
return 1;
|
|
}
|
|
|
|
static int zend_jit_cond_jmp(zend_jit_ctx *jit, const zend_op *next_opline, int target_block)
|
|
{
|
|
ir_ref ref;
|
|
zend_basic_block *bb;
|
|
|
|
ZEND_ASSERT(jit->b >= 0);
|
|
bb = &jit->ssa->cfg.blocks[jit->b];
|
|
|
|
ZEND_ASSERT(bb->successors_count == 2);
|
|
if (bb->successors[0] == bb->successors[1]) {
|
|
_zend_jit_add_predecessor_ref(jit, bb->successors[0], jit->b, ir_END());
|
|
jit->b = -1;
|
|
zend_jit_set_last_valid_opline(jit, next_opline);
|
|
return 1;
|
|
}
|
|
|
|
ref = jit_IF_ex(jit, jit_CMP_IP(jit, IR_NE, next_opline), target_block);
|
|
|
|
_zend_jit_add_predecessor_ref(jit, bb->successors[0], jit->b, ref);
|
|
_zend_jit_add_predecessor_ref(jit, bb->successors[1], jit->b, ref);
|
|
|
|
jit->b = -1;
|
|
zend_jit_set_last_valid_opline(jit, next_opline);
|
|
|
|
return 1;
|
|
}
|
|
|
|
static int zend_jit_set_cond(zend_jit_ctx *jit, const zend_op *next_opline, uint32_t var)
|
|
{
|
|
ir_ref ref;
|
|
|
|
ref = ir_ADD_U32(ir_ZEXT_U32(jit_CMP_IP(jit, IR_EQ, next_opline)), ir_CONST_U32(IS_FALSE));
|
|
|
|
// EX_VAR(var) = ...
|
|
ir_STORE(ir_ADD_OFFSET(jit_FP(jit), var + offsetof(zval, u1.type_info)), ref);
|
|
|
|
zend_jit_reset_last_valid_opline(jit);
|
|
return zend_jit_set_ip(jit, next_opline - 1);
|
|
}
|
|
|
|
/* PHP JIT handlers */
|
|
static void zend_jit_check_exception(zend_jit_ctx *jit)
|
|
{
|
|
ir_GUARD_NOT(ir_LOAD_A(jit_EG_exception(jit)),
|
|
jit_STUB_ADDR(jit, jit_stub_exception_handler));
|
|
}
|
|
|
|
static void zend_jit_check_exception_undef_result(zend_jit_ctx *jit, const zend_op *opline)
|
|
{
|
|
ir_GUARD_NOT(ir_LOAD_A(jit_EG_exception(jit)),
|
|
jit_STUB_ADDR(jit,
|
|
(opline->result_type & (IS_TMP_VAR|IS_VAR)) ? jit_stub_exception_handler_undef : jit_stub_exception_handler));
|
|
}
|
|
|
|
static void zend_jit_type_check_undef(zend_jit_ctx *jit,
|
|
ir_ref type,
|
|
uint32_t var,
|
|
const zend_op *opline,
|
|
bool check_exception,
|
|
bool in_cold_path,
|
|
bool undef_result)
|
|
{
|
|
ir_ref if_def = ir_IF(type);
|
|
|
|
if (!in_cold_path) {
|
|
ir_IF_FALSE_cold(if_def);
|
|
} else {
|
|
ir_IF_FALSE(if_def);
|
|
}
|
|
if (opline) {
|
|
jit_SET_EX_OPLINE(jit, opline);
|
|
}
|
|
ir_CALL_1(IR_VOID, ir_CONST_FC_FUNC(zend_jit_undefined_op_helper), ir_CONST_U32(var));
|
|
if (check_exception) {
|
|
if (undef_result) {
|
|
zend_jit_check_exception_undef_result(jit, opline);
|
|
} else {
|
|
zend_jit_check_exception(jit);
|
|
}
|
|
}
|
|
ir_MERGE_WITH_EMPTY_TRUE(if_def);
|
|
}
|
|
|
|
static ir_ref zend_jit_zval_check_undef(zend_jit_ctx *jit,
|
|
ir_ref ref,
|
|
uint32_t var,
|
|
const zend_op *opline,
|
|
bool check_exception)
|
|
{
|
|
ir_ref if_def, ref2;
|
|
|
|
if_def = ir_IF(jit_Z_TYPE_ref(jit, ref));
|
|
ir_IF_FALSE_cold(if_def);
|
|
|
|
if (opline) {
|
|
jit_SET_EX_OPLINE(jit, opline);
|
|
}
|
|
|
|
ir_CALL_1(IR_VOID, ir_CONST_FC_FUNC(zend_jit_undefined_op_helper), ir_CONST_U32(var));
|
|
|
|
if (check_exception) {
|
|
zend_jit_check_exception(jit);
|
|
}
|
|
|
|
ref2 = jit_EG(uninitialized_zval);
|
|
|
|
ir_MERGE_WITH_EMPTY_TRUE(if_def);
|
|
|
|
return ir_PHI_2(IR_ADDR, ref2, ref);
|
|
}
|
|
|
|
static void zend_jit_recv_entry(zend_jit_ctx *jit, int b)
|
|
{
|
|
zend_basic_block *bb = &jit->ssa->cfg.blocks[b];
|
|
int pred;
|
|
ir_ref ref;
|
|
|
|
ZEND_ASSERT(bb->predecessors_count > 0);
|
|
|
|
pred = jit->bb_predecessors[b];
|
|
ref = jit->bb_edges[pred];
|
|
|
|
ZEND_ASSERT(ref);
|
|
ZEND_ASSERT(jit->ctx.ir_base[ref].op == IR_END);
|
|
|
|
/* Insert a MERGE block with additional ENTRY input between predecessor and this one */
|
|
ir_ENTRY(ref, bb->start);
|
|
if (!GCC_GLOBAL_REGS) {
|
|
/* 2 is hardcoded reference to IR_PARAM */
|
|
ZEND_ASSERT(jit->ctx.ir_base[2].op == IR_PARAM);
|
|
ZEND_ASSERT(jit->ctx.ir_base[2].op3 == 1);
|
|
jit_STORE_FP(jit, 2);
|
|
}
|
|
|
|
ir_MERGE_WITH(ref);
|
|
jit->bb_edges[pred] = ir_END();
|
|
}
|
|
|
|
static void zend_jit_osr_entry(zend_jit_ctx *jit, int b)
|
|
{
|
|
zend_basic_block *bb = &jit->ssa->cfg.blocks[b];
|
|
ir_ref ref = ir_END();
|
|
|
|
/* Insert a MERGE block with additional ENTRY input between predecessor and this one */
|
|
ir_ENTRY(ref, bb->start);
|
|
if (!GCC_GLOBAL_REGS) {
|
|
/* 2 is hardcoded reference to IR_PARAM */
|
|
ZEND_ASSERT(jit->ctx.ir_base[2].op == IR_PARAM);
|
|
ZEND_ASSERT(jit->ctx.ir_base[2].op3 == 1);
|
|
jit_STORE_FP(jit, 2);
|
|
}
|
|
|
|
ir_MERGE_WITH(ref);
|
|
}
|
|
|
|
static ir_ref zend_jit_continue_entry(zend_jit_ctx *jit, ir_ref src, unsigned int label)
|
|
{
|
|
ir_ENTRY(src, label);
|
|
if (!GCC_GLOBAL_REGS) {
|
|
/* 2 is hardcoded reference to IR_PARAM */
|
|
ZEND_ASSERT(jit->ctx.ir_base[2].op == IR_PARAM);
|
|
ZEND_ASSERT(jit->ctx.ir_base[2].op3 == 1);
|
|
jit_STORE_FP(jit, 2);
|
|
}
|
|
return ir_END();
|
|
}
|
|
|
|
static int zend_jit_handler(zend_jit_ctx *jit, const zend_op *opline, int may_throw)
|
|
{
|
|
ir_ref ref;
|
|
const void *handler;
|
|
|
|
zend_jit_set_ip(jit, opline);
|
|
if (zend_jit_vm_kind == ZEND_VM_KIND_HYBRID) {
|
|
handler = zend_get_opcode_handler_func(opline);
|
|
} else {
|
|
handler = opline->handler;
|
|
}
|
|
if (GCC_GLOBAL_REGS) {
|
|
ir_CALL(IR_VOID, ir_CONST_FUNC(handler));
|
|
} else {
|
|
ref = jit_FP(jit);
|
|
ref = ir_CALL_1(IR_VOID, ir_CONST_FC_FUNC(handler), ref);
|
|
}
|
|
if (may_throw) {
|
|
zend_jit_check_exception(jit);
|
|
}
|
|
/* 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(jit, opline + 2);
|
|
break;
|
|
default:
|
|
zend_jit_set_last_valid_opline(jit, opline + 1);
|
|
break;
|
|
}
|
|
return 1;
|
|
}
|
|
|
|
static int zend_jit_tail_handler(zend_jit_ctx *jit, const zend_op *opline)
|
|
{
|
|
const void *handler;
|
|
ir_ref ref;
|
|
zend_basic_block *bb;
|
|
|
|
zend_jit_set_ip(jit, opline);
|
|
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 */
|
|
handler = opline->handler;
|
|
ir_TAILCALL(IR_VOID, ir_CONST_FUNC(handler));
|
|
} else {
|
|
handler = zend_get_opcode_handler_func(opline);
|
|
ir_CALL(IR_VOID, ir_CONST_FUNC(handler));
|
|
ref = ir_LOAD_A(jit_IP(jit));
|
|
ir_TAILCALL(IR_VOID, ref);
|
|
}
|
|
} else {
|
|
handler = opline->handler;
|
|
if (GCC_GLOBAL_REGS) {
|
|
ir_TAILCALL(IR_VOID, ir_CONST_FUNC(handler));
|
|
} else if ((jit->ssa->cfg.flags & ZEND_FUNC_RECURSIVE_DIRECTLY)
|
|
&& (opline->opcode == ZEND_CATCH
|
|
|| opline->opcode == ZEND_FAST_CALL
|
|
|| opline->opcode == ZEND_FAST_RET
|
|
|| opline->opcode == ZEND_MATCH_ERROR
|
|
|| opline->opcode == ZEND_THROW
|
|
|| opline->opcode == ZEND_VERIFY_NEVER_TYPE)) {
|
|
ref = jit_FP(jit);
|
|
ir_CALL_1(IR_I32, ir_CONST_FC_FUNC(handler), ref);
|
|
ir_RETURN(ir_CONST_I32(1));
|
|
} else {
|
|
ref = jit_FP(jit);
|
|
ir_TAILCALL_1(IR_I32, ir_CONST_FC_FUNC(handler), ref);
|
|
}
|
|
}
|
|
if (jit->b >= 0) {
|
|
bb = &jit->ssa->cfg.blocks[jit->b];
|
|
if (bb->successors_count > 0
|
|
&& (opline->opcode == ZEND_DO_FCALL
|
|
|| opline->opcode == ZEND_DO_UCALL
|
|
|| opline->opcode == ZEND_DO_FCALL_BY_NAME
|
|
|| opline->opcode == ZEND_INCLUDE_OR_EVAL
|
|
|| opline->opcode == ZEND_GENERATOR_CREATE
|
|
|| opline->opcode == ZEND_YIELD
|
|
|| opline->opcode == ZEND_YIELD_FROM
|
|
|| opline->opcode == ZEND_FAST_CALL)) {
|
|
/* Add a fake control edge from UNREACHABLE to the following ENTRY */
|
|
int succ;
|
|
|
|
if (bb->successors_count == 1) {
|
|
succ = bb->successors[0];
|
|
ZEND_ASSERT(jit->ssa->cfg.blocks[succ].flags & ZEND_BB_ENTRY);
|
|
} else {
|
|
/* Use only the following successor of FAST_CALL */
|
|
ZEND_ASSERT(opline->opcode == ZEND_FAST_CALL);
|
|
succ = jit->b + 1;
|
|
/* we need an entry */
|
|
jit->ssa->cfg.blocks[succ].flags |= ZEND_BB_ENTRY;
|
|
}
|
|
ref = jit->ctx.insns_count - 1;
|
|
ZEND_ASSERT(jit->ctx.ir_base[ref].op == IR_UNREACHABLE || jit->ctx.ir_base[ref].op == IR_RETURN);
|
|
ref = zend_jit_continue_entry(jit, ref, jit->ssa->cfg.blocks[succ].start);
|
|
_zend_jit_add_predecessor_ref(jit, succ, jit->b, ref);
|
|
}
|
|
jit->b = -1;
|
|
zend_jit_reset_last_valid_opline(jit);
|
|
}
|
|
return 1;
|
|
}
|
|
|
|
static int zend_jit_call(zend_jit_ctx *jit, const zend_op *opline, unsigned int next_block)
|
|
{
|
|
return zend_jit_tail_handler(jit, opline);
|
|
}
|
|
|
|
static int zend_jit_spill_store(zend_jit_ctx *jit, zend_jit_addr src, zend_jit_addr dst, uint32_t info, 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) {
|
|
jit_set_Z_LVAL(jit, dst, zend_jit_use_reg(jit, src));
|
|
if (set_type &&
|
|
(Z_REG(dst) != ZREG_FP ||
|
|
!JIT_G(current_frame) ||
|
|
STACK_MEM_TYPE(JIT_G(current_frame)->stack, EX_VAR_TO_NUM(Z_OFFSET(dst))) != IS_LONG)) {
|
|
jit_set_Z_TYPE_INFO(jit, dst, IS_LONG);
|
|
}
|
|
} else if ((info & MAY_BE_ANY) == MAY_BE_DOUBLE) {
|
|
jit_set_Z_DVAL(jit, dst, zend_jit_use_reg(jit, src));
|
|
if (set_type &&
|
|
(Z_REG(dst) != ZREG_FP ||
|
|
!JIT_G(current_frame) ||
|
|
STACK_MEM_TYPE(JIT_G(current_frame)->stack, EX_VAR_TO_NUM(Z_OFFSET(dst))) != IS_DOUBLE)) {
|
|
jit_set_Z_TYPE_INFO(jit, dst, IS_DOUBLE);
|
|
}
|
|
} else {
|
|
ZEND_UNREACHABLE();
|
|
}
|
|
return 1;
|
|
}
|
|
|
|
static int zend_jit_spill_store_inv(zend_jit_ctx *jit, zend_jit_addr src, zend_jit_addr dst, uint32_t info)
|
|
{
|
|
ZEND_ASSERT(Z_MODE(src) == IS_REG);
|
|
ZEND_ASSERT(Z_MODE(dst) == IS_MEM_ZVAL);
|
|
|
|
if ((info & MAY_BE_ANY) == MAY_BE_LONG) {
|
|
jit_set_Z_LVAL(jit, dst, zend_jit_use_reg(jit, src));
|
|
if (Z_REG(dst) != ZREG_FP || !JIT_G(current_frame)) {
|
|
jit_set_Z_TYPE_INFO(jit, dst, IS_LONG);
|
|
} else if (STACK_MEM_TYPE(JIT_G(current_frame)->stack, EX_VAR_TO_NUM(Z_OFFSET(dst))) != IS_LONG) {
|
|
/* invalidate memory type */
|
|
STACK_MEM_TYPE(JIT_G(current_frame)->stack, EX_VAR_TO_NUM(Z_OFFSET(dst))) = IS_UNKNOWN;
|
|
jit_set_Z_TYPE_INFO(jit, dst, IS_LONG);
|
|
}
|
|
} else if ((info & MAY_BE_ANY) == MAY_BE_DOUBLE) {
|
|
jit_set_Z_DVAL(jit, dst, zend_jit_use_reg(jit, src));
|
|
if (Z_REG(dst) != ZREG_FP || !JIT_G(current_frame)) {
|
|
jit_set_Z_TYPE_INFO(jit, dst, IS_DOUBLE);
|
|
} else if (STACK_MEM_TYPE(JIT_G(current_frame)->stack, EX_VAR_TO_NUM(Z_OFFSET(dst))) != IS_DOUBLE) {
|
|
/* invalidate memory type */
|
|
STACK_MEM_TYPE(JIT_G(current_frame)->stack, EX_VAR_TO_NUM(Z_OFFSET(dst))) = IS_UNKNOWN;
|
|
jit_set_Z_TYPE_INFO(jit, dst, IS_DOUBLE);
|
|
}
|
|
} else {
|
|
ZEND_UNREACHABLE();
|
|
}
|
|
return 1;
|
|
}
|
|
|
|
static int zend_jit_load_reg(zend_jit_ctx *jit, 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) {
|
|
zend_jit_def_reg(jit, dst, jit_Z_LVAL(jit, src));
|
|
} else if ((info & MAY_BE_ANY) == MAY_BE_DOUBLE) {
|
|
zend_jit_def_reg(jit, dst, jit_Z_DVAL(jit, src));
|
|
} else {
|
|
ZEND_UNREACHABLE();
|
|
}
|
|
return 1;
|
|
}
|
|
|
|
static int zend_jit_store_var(zend_jit_ctx *jit, uint32_t info, int var, int ssa_var, bool set_type)
|
|
{
|
|
zend_jit_addr src = ZEND_ADDR_REG(ssa_var);
|
|
zend_jit_addr dst = ZEND_ADDR_MEM_ZVAL(ZREG_FP, EX_NUM_TO_VAR(var));
|
|
|
|
return zend_jit_spill_store(jit, src, dst, info, set_type);
|
|
}
|
|
|
|
static int zend_jit_store_ref(zend_jit_ctx *jit, uint32_t info, int var, int32_t src, bool set_type)
|
|
{
|
|
zend_jit_addr dst = ZEND_ADDR_MEM_ZVAL(ZREG_FP, EX_NUM_TO_VAR(var));
|
|
|
|
if ((info & MAY_BE_ANY) == MAY_BE_LONG) {
|
|
jit_set_Z_LVAL(jit, dst, src);
|
|
if (set_type &&
|
|
(Z_REG(dst) != ZREG_FP ||
|
|
!JIT_G(current_frame) ||
|
|
STACK_MEM_TYPE(JIT_G(current_frame)->stack, EX_VAR_TO_NUM(Z_OFFSET(dst))) != IS_LONG)) {
|
|
jit_set_Z_TYPE_INFO(jit, dst, IS_LONG);
|
|
}
|
|
} else if ((info & MAY_BE_ANY) == MAY_BE_DOUBLE) {
|
|
jit_set_Z_DVAL(jit, dst, src);
|
|
if (set_type &&
|
|
(Z_REG(dst) != ZREG_FP ||
|
|
!JIT_G(current_frame) ||
|
|
STACK_MEM_TYPE(JIT_G(current_frame)->stack, EX_VAR_TO_NUM(Z_OFFSET(dst))) != IS_DOUBLE)) {
|
|
jit_set_Z_TYPE_INFO(jit, dst, IS_DOUBLE);
|
|
}
|
|
} else {
|
|
ZEND_UNREACHABLE();
|
|
}
|
|
return 1;
|
|
}
|
|
|
|
static ir_ref zend_jit_deopt_rload(zend_jit_ctx *jit, ir_type type, int32_t reg)
|
|
{
|
|
ir_ref ref = jit->ctx.control;
|
|
ir_insn *insn;
|
|
|
|
while (1) {
|
|
insn = &jit->ctx.ir_base[ref];
|
|
if (insn->op == IR_RLOAD && insn->op2 == reg) {
|
|
ZEND_ASSERT(insn->type == type);
|
|
return ref;
|
|
} else if (insn->op == IR_START) {
|
|
break;
|
|
}
|
|
ref = insn->op1;
|
|
}
|
|
return ir_RLOAD(type, reg);
|
|
}
|
|
|
|
static int zend_jit_store_const_long(zend_jit_ctx *jit, int var, zend_long val)
|
|
{
|
|
zend_jit_addr dst = ZEND_ADDR_MEM_ZVAL(ZREG_FP, EX_NUM_TO_VAR(var));
|
|
ir_ref src = ir_CONST_LONG(val);
|
|
|
|
if (jit->ra && jit->ra[var].ref == IR_NULL) {
|
|
zend_jit_def_reg(jit, ZEND_ADDR_REG(var), src);
|
|
}
|
|
jit_set_Z_LVAL(jit, dst, src);
|
|
jit_set_Z_TYPE_INFO(jit, dst, IS_LONG);
|
|
return 1;
|
|
}
|
|
|
|
static int zend_jit_store_const_double(zend_jit_ctx *jit, int var, double val)
|
|
{
|
|
zend_jit_addr dst = ZEND_ADDR_MEM_ZVAL(ZREG_FP, EX_NUM_TO_VAR(var));
|
|
ir_ref src = ir_CONST_DOUBLE(val);
|
|
|
|
if (jit->ra && jit->ra[var].ref == IR_NULL) {
|
|
zend_jit_def_reg(jit, ZEND_ADDR_REG(var), src);
|
|
}
|
|
jit_set_Z_DVAL(jit, dst, src);
|
|
jit_set_Z_TYPE_INFO(jit, dst, IS_DOUBLE);
|
|
return 1;
|
|
}
|
|
|
|
static int zend_jit_store_type(zend_jit_ctx *jit, int var, uint8_t type)
|
|
{
|
|
zend_jit_addr dst = ZEND_ADDR_MEM_ZVAL(ZREG_FP, EX_NUM_TO_VAR(var));
|
|
|
|
ZEND_ASSERT(type <= IS_DOUBLE);
|
|
jit_set_Z_TYPE_INFO(jit, dst, type);
|
|
return 1;
|
|
}
|
|
|
|
static int zend_jit_store_reg(zend_jit_ctx *jit, uint32_t info, int var, int8_t reg, bool in_mem, bool set_type)
|
|
{
|
|
zend_jit_addr src;
|
|
zend_jit_addr dst = ZEND_ADDR_MEM_ZVAL(ZREG_FP, EX_NUM_TO_VAR(var));
|
|
ir_type type;
|
|
|
|
if ((info & MAY_BE_ANY) == MAY_BE_LONG) {
|
|
type = IR_LONG;
|
|
src = zend_jit_deopt_rload(jit, type, reg);
|
|
if (jit->ra && jit->ra[var].ref == IR_NULL) {
|
|
zend_jit_def_reg(jit, ZEND_ADDR_REG(var), src);
|
|
} else if (!in_mem) {
|
|
jit_set_Z_LVAL(jit, dst, src);
|
|
if (set_type &&
|
|
(Z_REG(dst) != ZREG_FP ||
|
|
!JIT_G(current_frame) ||
|
|
STACK_MEM_TYPE(JIT_G(current_frame)->stack, EX_VAR_TO_NUM(Z_OFFSET(dst))) != IS_LONG)) {
|
|
jit_set_Z_TYPE_INFO(jit, dst, IS_LONG);
|
|
}
|
|
}
|
|
} else if ((info & MAY_BE_ANY) == MAY_BE_DOUBLE) {
|
|
type = IR_DOUBLE;
|
|
src = zend_jit_deopt_rload(jit, type, reg);
|
|
if (jit->ra && jit->ra[var].ref == IR_NULL) {
|
|
zend_jit_def_reg(jit, ZEND_ADDR_REG(var), src);
|
|
} else if (!in_mem) {
|
|
jit_set_Z_DVAL(jit, dst, src);
|
|
if (set_type &&
|
|
(Z_REG(dst) != ZREG_FP ||
|
|
!JIT_G(current_frame) ||
|
|
STACK_MEM_TYPE(JIT_G(current_frame)->stack, EX_VAR_TO_NUM(Z_OFFSET(dst))) != IS_DOUBLE)) {
|
|
jit_set_Z_TYPE_INFO(jit, dst, IS_DOUBLE);
|
|
}
|
|
}
|
|
} else {
|
|
ZEND_UNREACHABLE();
|
|
}
|
|
return 1;
|
|
}
|
|
|
|
static int zend_jit_store_spill_slot(zend_jit_ctx *jit, uint32_t info, int var, int8_t reg, int32_t offset, bool set_type)
|
|
{
|
|
zend_jit_addr src;
|
|
zend_jit_addr dst = ZEND_ADDR_MEM_ZVAL(ZREG_FP, EX_NUM_TO_VAR(var));
|
|
|
|
if ((info & MAY_BE_ANY) == MAY_BE_LONG) {
|
|
src = ir_LOAD_L(ir_ADD_OFFSET(ir_RLOAD_A(reg), offset));
|
|
if (jit->ra && jit->ra[var].ref == IR_NULL) {
|
|
zend_jit_def_reg(jit, ZEND_ADDR_REG(var), src);
|
|
} else {
|
|
jit_set_Z_LVAL(jit, dst, src);
|
|
if (set_type &&
|
|
(Z_REG(dst) != ZREG_FP ||
|
|
!JIT_G(current_frame) ||
|
|
STACK_MEM_TYPE(JIT_G(current_frame)->stack, EX_VAR_TO_NUM(Z_OFFSET(dst))) != IS_LONG)) {
|
|
jit_set_Z_TYPE_INFO(jit, dst, IS_LONG);
|
|
}
|
|
}
|
|
} else if ((info & MAY_BE_ANY) == MAY_BE_DOUBLE) {
|
|
src = ir_LOAD_D(ir_ADD_OFFSET(ir_RLOAD_A(reg), offset));
|
|
if (jit->ra && jit->ra[var].ref == IR_NULL) {
|
|
zend_jit_def_reg(jit, ZEND_ADDR_REG(var), src);
|
|
} else {
|
|
jit_set_Z_DVAL(jit, dst, src);
|
|
if (set_type &&
|
|
(Z_REG(dst) != ZREG_FP ||
|
|
!JIT_G(current_frame) ||
|
|
STACK_MEM_TYPE(JIT_G(current_frame)->stack, EX_VAR_TO_NUM(Z_OFFSET(dst))) != IS_DOUBLE)) {
|
|
jit_set_Z_TYPE_INFO(jit, dst, IS_DOUBLE);
|
|
}
|
|
}
|
|
} else {
|
|
ZEND_UNREACHABLE();
|
|
}
|
|
return 1;
|
|
}
|
|
|
|
static int zend_jit_store_var_type(zend_jit_ctx *jit, int var, uint32_t type)
|
|
{
|
|
zend_jit_addr dst = ZEND_ADDR_MEM_ZVAL(ZREG_FP, EX_NUM_TO_VAR(var));
|
|
|
|
jit_set_Z_TYPE_INFO(jit, dst, type);
|
|
return 1;
|
|
}
|
|
|
|
static int zend_jit_zval_try_addref(zend_jit_ctx *jit, zend_jit_addr var_addr)
|
|
{
|
|
ir_ref if_refcounted, end1;
|
|
|
|
if_refcounted = jit_if_REFCOUNTED(jit, var_addr);
|
|
ir_IF_FALSE(if_refcounted);
|
|
end1 = ir_END();
|
|
ir_IF_TRUE(if_refcounted);
|
|
jit_GC_ADDREF(jit, jit_Z_PTR(jit, var_addr));
|
|
ir_MERGE_WITH(end1);
|
|
return 1;
|
|
}
|
|
|
|
static int zend_jit_store_var_if_necessary(zend_jit_ctx *jit, 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(jit, src, dst, info, 1);
|
|
}
|
|
return 1;
|
|
}
|
|
|
|
static int zend_jit_store_var_if_necessary_ex(zend_jit_ctx *jit, 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);
|
|
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)) {
|
|
if (JIT_G(current_frame)) {
|
|
uint32_t mem_type = STACK_MEM_TYPE(JIT_G(current_frame)->stack, EX_VAR_TO_NUM(var));
|
|
|
|
if (mem_type != IS_UNKNOWN
|
|
&& (info & (MAY_BE_ANY|MAY_BE_REF|MAY_BE_UNDEF)) == (1 << mem_type)) {
|
|
set_type = 0;
|
|
}
|
|
} else {
|
|
set_type = 0;
|
|
}
|
|
}
|
|
}
|
|
return zend_jit_spill_store(jit, src, dst, info, set_type);
|
|
}
|
|
return 1;
|
|
}
|
|
|
|
static int zend_jit_load_var(zend_jit_ctx *jit, uint32_t info, int var, int ssa_var)
|
|
{
|
|
zend_jit_addr src = ZEND_ADDR_MEM_ZVAL(ZREG_FP, EX_NUM_TO_VAR(var));
|
|
zend_jit_addr dst = ZEND_ADDR_REG(ssa_var);
|
|
|
|
return zend_jit_load_reg(jit, src, dst, info);
|
|
}
|
|
|
|
static int zend_jit_invalidate_var_if_necessary(zend_jit_ctx *jit, uint8_t 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)) {
|
|
/* Invalidate operand type to prevent incorrect destuction by exception_handler_free_op1_op2() */
|
|
zend_jit_addr dst = ZEND_ADDR_MEM_ZVAL(ZREG_FP, op.var);
|
|
jit_set_Z_TYPE_INFO(jit, dst, IS_UNDEF);
|
|
}
|
|
return 1;
|
|
}
|
|
|
|
static int zend_jit_update_regs(zend_jit_ctx *jit, 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) {
|
|
zend_jit_def_reg(jit, dst, zend_jit_use_reg(jit, src));
|
|
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(jit, 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(jit, 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(jit, 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(jit, 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;
|
|
}
|
|
|
|
struct jit_observer_fcall_is_unobserved_data {
|
|
ir_ref if_unobserved;
|
|
ir_ref ir_end_inputs;
|
|
};
|
|
|
|
static struct jit_observer_fcall_is_unobserved_data jit_observer_fcall_is_unobserved_start(zend_jit_ctx *jit, const zend_function *func, ir_ref *observer_handler, ir_ref rx, ir_ref func_ref) {
|
|
ir_ref run_time_cache;
|
|
struct jit_observer_fcall_is_unobserved_data data = { .ir_end_inputs = IR_UNUSED };
|
|
if (func) {
|
|
ZEND_ASSERT((func->common.fn_flags & (ZEND_ACC_CALL_VIA_TRAMPOLINE | ZEND_ACC_GENERATOR)) == 0);
|
|
} else {
|
|
// JIT: if (function->common.fn_flags & (ZEND_ACC_CALL_VIA_TRAMPOLINE | ZEND_ACC_GENERATOR)) {
|
|
ZEND_ASSERT(rx != IR_UNUSED);
|
|
ir_ref if_trampoline_or_generator = ir_IF(ir_AND_U32(
|
|
ir_LOAD_U32(ir_ADD_OFFSET(func_ref, offsetof(zend_function, common.fn_flags))),
|
|
ir_CONST_U32(ZEND_ACC_CALL_VIA_TRAMPOLINE | ZEND_ACC_GENERATOR)));
|
|
ir_IF_TRUE(if_trampoline_or_generator);
|
|
ir_END_list(data.ir_end_inputs);
|
|
ir_IF_FALSE(if_trampoline_or_generator);
|
|
}
|
|
if (func && (func->common.fn_flags & ZEND_ACC_CLOSURE) == 0 && ZEND_MAP_PTR_IS_OFFSET(func->common.run_time_cache)) {
|
|
// JIT: ZEND_MAP_PTR_GET_IMM(func->common.runtime_cache)
|
|
run_time_cache = ir_LOAD_A(ir_ADD_OFFSET(ir_LOAD_A(jit_CG(map_ptr_base)), (uintptr_t)ZEND_MAP_PTR(func->common.run_time_cache)));
|
|
#ifndef ZTS
|
|
} else if (func && rx == IS_UNUSED) { // happens for internal functions only
|
|
ZEND_ASSERT(!ZEND_USER_CODE(func->type));
|
|
run_time_cache = ir_LOAD_A(ir_ADD_OFFSET(ir_CONST_ADDR(func), offsetof(zend_op_array, run_time_cache__ptr)));
|
|
#endif
|
|
} else {
|
|
// Closures may be duplicated and have a different runtime cache. Use the regular run_time_cache access pattern for these
|
|
if (func && ZEND_USER_CODE(func->type)) { // not a closure and definitely not an internal function
|
|
run_time_cache = ir_LOAD_A(jit_CALL(rx, run_time_cache));
|
|
} else {
|
|
// JIT: ZEND_MAP_PTR_GET(func->common.runtime_cache)
|
|
run_time_cache = ir_LOAD_A(ir_ADD_OFFSET(ir_LOAD_A(jit_CALL(rx, func)), offsetof(zend_op_array, run_time_cache__ptr)));
|
|
ir_ref if_odd = ir_IF(ir_AND_A(run_time_cache, ir_CONST_ADDR(1)));
|
|
ir_IF_TRUE(if_odd);
|
|
|
|
ir_ref run_time_cache2 = ir_LOAD_A(ir_ADD_A(run_time_cache, ir_LOAD_A(jit_CG(map_ptr_base))));
|
|
|
|
ir_ref if_odd_end = ir_END();
|
|
ir_IF_FALSE(if_odd);
|
|
|
|
// JIT: if (func->common.runtime_cache != NULL) {
|
|
ir_ref if_rt_cache = ir_IF(ir_EQ(run_time_cache, IR_NULL));
|
|
ir_IF_TRUE(if_rt_cache);
|
|
ir_END_list(data.ir_end_inputs);
|
|
ir_IF_FALSE(if_rt_cache);
|
|
|
|
ir_MERGE_WITH(if_odd_end);
|
|
run_time_cache = ir_PHI_2(IR_ADDR, run_time_cache, run_time_cache2);
|
|
}
|
|
}
|
|
// JIT: observer_handler = runtime_cache + ZEND_OBSERVER_HANDLE(function)
|
|
if (func) {
|
|
*observer_handler = ir_ADD_OFFSET(run_time_cache, ZEND_OBSERVER_HANDLE(func) * sizeof(void *));
|
|
} else {
|
|
// JIT: (func->type == ZEND_INTERNAL_FUNCTION ? zend_observer_fcall_internal_function_extension : zend_observer_fcall_op_array_extension) * sizeof(void *)
|
|
ir_ref tmp = ir_LOAD_U8(ir_ADD_OFFSET(func_ref, offsetof(zend_function, type)));
|
|
ir_ref if_internal_func = ir_IF(ir_AND_U8(tmp, ir_CONST_U8(ZEND_INTERNAL_FUNCTION)));
|
|
ir_IF_TRUE(if_internal_func);
|
|
|
|
ir_ref observer_handler_internal = ir_ADD_OFFSET(run_time_cache, zend_observer_fcall_internal_function_extension * sizeof(void *));
|
|
|
|
ir_ref if_internal_func_end = ir_END();
|
|
ir_IF_FALSE(if_internal_func);
|
|
|
|
ir_ref observer_handler_user = ir_ADD_OFFSET(run_time_cache, zend_observer_fcall_op_array_extension * sizeof(void *));
|
|
|
|
ir_MERGE_WITH(if_internal_func_end);
|
|
*observer_handler = ir_PHI_2(IR_ADDR, observer_handler_internal, observer_handler_user);
|
|
}
|
|
|
|
// JIT: if (*observer_handler == ZEND_OBSERVER_NONE_OBSERVED) {
|
|
data.if_unobserved = ir_IF(ir_EQ(ir_LOAD_A(*observer_handler), ir_CONST_ADDR(ZEND_OBSERVER_NONE_OBSERVED)));
|
|
ir_IF_FALSE(data.if_unobserved);
|
|
return data;
|
|
}
|
|
|
|
/* For frameless the true branch of if_unobserved is used and this function not called. */
|
|
static void jit_observer_fcall_is_unobserved_end(zend_jit_ctx *jit, struct jit_observer_fcall_is_unobserved_data *data) {
|
|
ir_END_list(data->ir_end_inputs);
|
|
ir_IF_TRUE(data->if_unobserved);
|
|
ir_END_list(data->ir_end_inputs);
|
|
ir_MERGE_list(data->ir_end_inputs);
|
|
}
|
|
|
|
static void jit_observer_fcall_begin(zend_jit_ctx *jit, ir_ref rx, ir_ref observer_handler) {
|
|
ir_CALL_2(IR_VOID, ir_CONST_FC_FUNC(zend_observer_fcall_begin_prechecked), rx, observer_handler);
|
|
}
|
|
|
|
static void jit_observer_fcall_end(zend_jit_ctx *jit, ir_ref rx, ir_ref res_ref) {
|
|
// JIT: if (execute_data == EG(current_observed_frame)) {
|
|
ir_ref has_end_observer = ir_IF(ir_EQ(rx, ir_LOAD_A(jit_EG(current_observed_frame))));
|
|
ir_IF_TRUE(has_end_observer);
|
|
ir_CALL_2(IR_VOID, ir_CONST_FC_FUNC(zend_observer_fcall_end_prechecked),
|
|
rx, res_ref);
|
|
ir_MERGE_WITH_EMPTY_FALSE(has_end_observer);
|
|
}
|
|
|
|
static int zend_jit_inc_dec(zend_jit_ctx *jit, 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)
|
|
{
|
|
ir_ref if_long = IR_UNUSED;
|
|
ir_ref op1_lval_ref = IR_UNUSED;
|
|
ir_ref ref;
|
|
ir_op op;
|
|
|
|
if (op1_info & ((MAY_BE_UNDEF|MAY_BE_ANY)-MAY_BE_LONG)) {
|
|
if_long = jit_if_Z_TYPE(jit, op1_addr, IS_LONG);
|
|
ir_IF_TRUE(if_long);
|
|
}
|
|
if (opline->opcode == ZEND_POST_INC || opline->opcode == ZEND_POST_DEC) {
|
|
op1_lval_ref = jit_Z_LVAL(jit, op1_addr);
|
|
jit_set_Z_LVAL(jit, res_addr, op1_lval_ref);
|
|
if (Z_MODE(res_addr) != IS_REG) {
|
|
jit_set_Z_TYPE_INFO(jit, res_addr, IS_LONG);
|
|
}
|
|
}
|
|
if (Z_MODE(op1_def_addr) == IS_MEM_ZVAL
|
|
&& Z_MODE(op1_addr) == IS_REG
|
|
&& !Z_LOAD(op1_addr)
|
|
&& !Z_STORE(op1_addr)) {
|
|
jit_set_Z_TYPE_INFO(jit, op1_def_addr, IS_LONG);
|
|
}
|
|
if (opline->opcode == ZEND_PRE_INC || opline->opcode == ZEND_POST_INC) {
|
|
op = may_overflow ? IR_ADD_OV : IR_ADD;
|
|
} else {
|
|
op = may_overflow ? IR_SUB_OV : IR_SUB;
|
|
}
|
|
if (!op1_lval_ref) {
|
|
op1_lval_ref = jit_Z_LVAL(jit, op1_addr);
|
|
}
|
|
ref = ir_BINARY_OP_L(op, op1_lval_ref, ir_CONST_LONG(1));
|
|
if (op1_def_info & MAY_BE_LONG) {
|
|
jit_set_Z_LVAL(jit, op1_def_addr, ref);
|
|
}
|
|
if (may_overflow &&
|
|
(((op1_def_info & (MAY_BE_ANY|MAY_BE_GUARD)) == (MAY_BE_LONG|MAY_BE_GUARD)) ||
|
|
((opline->result_type != IS_UNUSED && (res_info & (MAY_BE_ANY|MAY_BE_GUARD)) == (MAY_BE_LONG|MAY_BE_GUARD))))) {
|
|
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_REF(stack, EX_VAR_TO_NUM(opline->op1.var), ir_CONST_DOUBLE((double)ZEND_LONG_MAX + 1.0));
|
|
} else {
|
|
SET_STACK_REF(stack, EX_VAR_TO_NUM(opline->op1.var), ir_CONST_DOUBLE((double)ZEND_LONG_MIN - 1.0));
|
|
}
|
|
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_REF(stack, EX_VAR_TO_NUM(opline->result.var), ir_CONST_DOUBLE((double)ZEND_LONG_MAX + 1.0));
|
|
} else if (opline->opcode == ZEND_PRE_DEC) {
|
|
SET_STACK_TYPE(stack, EX_VAR_TO_NUM(opline->result.var), IS_DOUBLE, 0);
|
|
SET_STACK_REF(stack, EX_VAR_TO_NUM(opline->result.var), ir_CONST_DOUBLE((double)ZEND_LONG_MIN - 1.0));
|
|
} else if (opline->opcode == ZEND_POST_INC) {
|
|
SET_STACK_TYPE(stack, EX_VAR_TO_NUM(opline->result.var), IS_LONG, 0);
|
|
SET_STACK_REF(stack, EX_VAR_TO_NUM(opline->result.var), ir_CONST_LONG(ZEND_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_REF(stack, EX_VAR_TO_NUM(opline->result.var), ir_CONST_LONG(ZEND_LONG_MIN));
|
|
}
|
|
}
|
|
|
|
exit_point = zend_jit_trace_get_exit_point(opline + 1, 0);
|
|
exit_addr = zend_jit_trace_get_exit_addr(exit_point);
|
|
ir_GUARD_NOT(ir_OVERFLOW(ref), ir_CONST_ADDR(exit_addr));
|
|
|
|
if ((opline->opcode == ZEND_PRE_INC || opline->opcode == ZEND_PRE_DEC) &&
|
|
opline->result_type != IS_UNUSED) {
|
|
jit_set_Z_LVAL(jit, res_addr, ref);
|
|
if (Z_MODE(res_addr) != IS_REG) {
|
|
jit_set_Z_TYPE_INFO(jit, res_addr, IS_LONG);
|
|
}
|
|
}
|
|
|
|
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) {
|
|
ir_ref if_overflow;
|
|
ir_ref merge_inputs = IR_UNUSED;
|
|
|
|
if (((op1_def_info & (MAY_BE_ANY|MAY_BE_GUARD)) == (MAY_BE_DOUBLE|MAY_BE_GUARD))
|
|
|| (opline->result_type != IS_UNUSED && (res_info & (MAY_BE_ANY|MAY_BE_GUARD)) == (MAY_BE_DOUBLE|MAY_BE_GUARD))) {
|
|
int32_t exit_point;
|
|
const void *exit_addr;
|
|
zend_jit_trace_stack *stack;
|
|
uint32_t old_res_info = 0;
|
|
|
|
stack = JIT_G(current_frame)->stack;
|
|
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 || opline->opcode == ZEND_PRE_DEC) {
|
|
SET_STACK_TYPE(stack, EX_VAR_TO_NUM(opline->result.var), IS_LONG, 0);
|
|
}
|
|
}
|
|
exit_point = zend_jit_trace_get_exit_point(opline + 1, 0);
|
|
exit_addr = zend_jit_trace_get_exit_addr(exit_point);
|
|
if ((opline->opcode == ZEND_PRE_INC || opline->opcode == ZEND_PRE_DEC) &&
|
|
opline->result_type != IS_UNUSED) {
|
|
if_overflow = ir_IF(ir_OVERFLOW(ref));
|
|
ir_IF_FALSE_cold(if_overflow);
|
|
jit_set_Z_LVAL(jit, res_addr, ref);
|
|
if (Z_MODE(res_addr) != IS_REG) {
|
|
jit_set_Z_TYPE_INFO(jit, res_addr, IS_LONG);
|
|
}
|
|
jit_SIDE_EXIT(jit, ir_CONST_ADDR(exit_addr));
|
|
ir_IF_TRUE(if_overflow);
|
|
} else {
|
|
ir_GUARD(ir_OVERFLOW(ref), ir_CONST_ADDR(exit_addr));
|
|
}
|
|
if (opline->result_type != IS_UNUSED) {
|
|
SET_STACK_INFO(stack, EX_VAR_TO_NUM(opline->result.var), old_res_info);
|
|
}
|
|
} else {
|
|
if_overflow = ir_IF(ir_OVERFLOW(ref));
|
|
ir_IF_FALSE(if_overflow);
|
|
if ((opline->opcode == ZEND_PRE_INC || opline->opcode == ZEND_PRE_DEC) &&
|
|
opline->result_type != IS_UNUSED) {
|
|
jit_set_Z_LVAL(jit, res_addr, ref);
|
|
if (Z_MODE(res_addr) != IS_REG) {
|
|
jit_set_Z_TYPE_INFO(jit, res_addr, IS_LONG);
|
|
}
|
|
}
|
|
ir_END_list(merge_inputs);
|
|
|
|
/* overflow => cold path */
|
|
ir_IF_TRUE_cold(if_overflow);
|
|
}
|
|
|
|
if (opline->opcode == ZEND_PRE_INC || opline->opcode == ZEND_POST_INC) {
|
|
if (Z_MODE(op1_def_addr) == IS_REG) {
|
|
jit_set_Z_DVAL(jit, op1_def_addr, ir_CONST_DOUBLE((double)ZEND_LONG_MAX + 1.0));
|
|
} else {
|
|
#if SIZEOF_ZEND_LONG == 4
|
|
jit_set_Z_LVAL(jit, op1_def_addr, ir_CONST_LONG(0));
|
|
jit_set_Z_W2(jit, op1_def_addr, ir_CONST_U32(0x41e00000));
|
|
#else
|
|
jit_set_Z_LVAL(jit, op1_def_addr, ir_CONST_LONG(0x43e0000000000000));
|
|
#endif
|
|
jit_set_Z_TYPE_INFO(jit, op1_def_addr, IS_DOUBLE);
|
|
}
|
|
} else {
|
|
if (Z_MODE(op1_def_addr) == IS_REG) {
|
|
jit_set_Z_DVAL(jit, op1_def_addr, ir_CONST_DOUBLE((double)ZEND_LONG_MIN - 1.0));
|
|
} else {
|
|
#if SIZEOF_ZEND_LONG == 4
|
|
jit_set_Z_LVAL(jit, op1_def_addr, ir_CONST_LONG(0x00200000));
|
|
jit_set_Z_W2(jit, op1_def_addr, ir_CONST_U32(0xc1e00000));
|
|
#else
|
|
jit_set_Z_LVAL(jit, op1_def_addr, ir_CONST_LONG(0xc3e0000000000000));
|
|
#endif
|
|
jit_set_Z_TYPE_INFO(jit, op1_def_addr, IS_DOUBLE);
|
|
}
|
|
}
|
|
if ((opline->opcode == ZEND_PRE_INC || opline->opcode == ZEND_PRE_DEC) &&
|
|
opline->result_type != IS_UNUSED) {
|
|
if (opline->opcode == ZEND_PRE_INC) {
|
|
if (Z_MODE(res_addr) == IS_REG) {
|
|
jit_set_Z_DVAL(jit, res_addr, ir_CONST_DOUBLE((double)ZEND_LONG_MAX + 1.0));
|
|
} else {
|
|
#if SIZEOF_ZEND_LONG == 4
|
|
jit_set_Z_LVAL(jit, res_addr, ir_CONST_LONG(0));
|
|
jit_set_Z_W2(jit, res_addr, ir_CONST_U32(0x41e00000));
|
|
#else
|
|
jit_set_Z_LVAL(jit, res_addr, ir_CONST_LONG(0x43e0000000000000));
|
|
#endif
|
|
jit_set_Z_TYPE_INFO(jit, res_addr, IS_DOUBLE);
|
|
}
|
|
} else {
|
|
if (Z_MODE(res_addr) == IS_REG) {
|
|
jit_set_Z_DVAL(jit, res_addr, ir_CONST_DOUBLE((double)ZEND_LONG_MIN - 1.0));
|
|
} else {
|
|
#if SIZEOF_ZEND_LONG == 4
|
|
jit_set_Z_LVAL(jit, res_addr, ir_CONST_LONG(0x00200000));
|
|
jit_set_Z_W2(jit, res_addr, ir_CONST_U32(0xc1e00000));
|
|
#else
|
|
jit_set_Z_LVAL(jit, res_addr, ir_CONST_LONG(0xc3e0000000000000));
|
|
#endif
|
|
jit_set_Z_TYPE_INFO(jit, res_addr, IS_DOUBLE);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (merge_inputs) {
|
|
ir_END_list(merge_inputs);
|
|
ir_MERGE_list(merge_inputs);
|
|
}
|
|
} else {
|
|
if ((opline->opcode == ZEND_PRE_INC || opline->opcode == ZEND_PRE_DEC) &&
|
|
opline->result_type != IS_UNUSED) {
|
|
jit_set_Z_LVAL(jit, res_addr, ref);
|
|
if (Z_MODE(res_addr) != IS_REG) {
|
|
jit_set_Z_TYPE_INFO(jit, res_addr, IS_LONG);
|
|
}
|
|
}
|
|
}
|
|
if (op1_info & ((MAY_BE_ANY|MAY_BE_UNDEF)-MAY_BE_LONG)) {
|
|
ir_ref merge_inputs = ir_END();
|
|
|
|
/* !is_long => cold path */
|
|
ir_IF_FALSE_cold(if_long);
|
|
if (op1_info & ((MAY_BE_ANY|MAY_BE_UNDEF)-(MAY_BE_LONG|MAY_BE_DOUBLE))) {
|
|
jit_SET_EX_OPLINE(jit, opline);
|
|
if (op1_info & MAY_BE_UNDEF) {
|
|
ir_ref if_def;
|
|
|
|
if_def = jit_if_not_Z_TYPE(jit, op1_addr, IS_UNDEF);
|
|
ir_IF_FALSE_cold(if_def);
|
|
|
|
// zend_error_unchecked(E_WARNING, "Undefined variable $%S", CV_DEF_OF(EX_VAR_TO_NUM(opline->op1.var)));
|
|
ir_CALL_1(IR_VOID, ir_CONST_FC_FUNC(zend_jit_undefined_op_helper), ir_CONST_U32(opline->op1.var));
|
|
|
|
jit_set_Z_TYPE_INFO(jit, op1_def_addr, IS_NULL);
|
|
ir_MERGE_WITH_EMPTY_TRUE(if_def);
|
|
|
|
op1_info |= MAY_BE_NULL;
|
|
}
|
|
|
|
ref = jit_ZVAL_ADDR(jit, op1_addr);
|
|
|
|
if (op1_info & MAY_BE_REF) {
|
|
ir_ref if_ref, if_typed, func, ref2, arg2;
|
|
|
|
if_ref = jit_if_Z_TYPE_ref(jit, ref, ir_CONST_U8(IS_REFERENCE));
|
|
ir_IF_TRUE(if_ref);
|
|
ref2 = jit_Z_PTR_ref(jit, ref);
|
|
|
|
if_typed = jit_if_TYPED_REF(jit, ref2);
|
|
ir_IF_TRUE(if_typed);
|
|
|
|
if (RETURN_VALUE_USED(opline)) {
|
|
ZEND_ASSERT(Z_MODE(res_addr) != IS_REG);
|
|
arg2 = jit_ZVAL_ADDR(jit, res_addr);
|
|
} else {
|
|
arg2 = IR_NULL;
|
|
}
|
|
if (opline->opcode == ZEND_PRE_INC) {
|
|
func = ir_CONST_FC_FUNC(zend_jit_pre_inc_typed_ref);
|
|
} else if (opline->opcode == ZEND_PRE_DEC) {
|
|
func = ir_CONST_FC_FUNC(zend_jit_pre_dec_typed_ref);
|
|
} else if (opline->opcode == ZEND_POST_INC) {
|
|
func = ir_CONST_FC_FUNC(zend_jit_post_inc_typed_ref);
|
|
} else if (opline->opcode == ZEND_POST_DEC) {
|
|
func = ir_CONST_FC_FUNC(zend_jit_post_dec_typed_ref);
|
|
} else {
|
|
ZEND_UNREACHABLE();
|
|
}
|
|
|
|
ir_CALL_2(IR_VOID, func, ref2, arg2);
|
|
zend_jit_check_exception(jit);
|
|
ir_END_list(merge_inputs);
|
|
|
|
ir_IF_FALSE(if_typed);
|
|
ref2 = ir_ADD_OFFSET(ref2, offsetof(zend_reference, val));
|
|
ir_MERGE_WITH_EMPTY_FALSE(if_ref);
|
|
ref = ir_PHI_2(IR_ADDR, ref2, ref);
|
|
}
|
|
|
|
if (opline->opcode == ZEND_POST_INC || opline->opcode == ZEND_POST_DEC) {
|
|
jit_ZVAL_COPY(jit,
|
|
res_addr,
|
|
res_use_info,
|
|
ZEND_ADDR_REF_ZVAL(ref), op1_info, 1);
|
|
}
|
|
if (opline->opcode == ZEND_PRE_INC || opline->opcode == ZEND_POST_INC) {
|
|
if (opline->opcode == ZEND_PRE_INC && opline->result_type != IS_UNUSED) {
|
|
ir_ref arg2 = jit_ZVAL_ADDR(jit, res_addr);
|
|
ir_CALL_2(IR_VOID, ir_CONST_FC_FUNC(zend_jit_pre_inc), ref, arg2);
|
|
} else {
|
|
ir_CALL_1(IR_VOID, ir_CONST_FC_FUNC(increment_function), ref);
|
|
}
|
|
} else {
|
|
if (opline->opcode == ZEND_PRE_DEC && opline->result_type != IS_UNUSED) {
|
|
ir_ref arg2 = jit_ZVAL_ADDR(jit, res_addr);
|
|
ir_CALL_2(IR_VOID, ir_CONST_FC_FUNC(zend_jit_pre_dec), ref, arg2);
|
|
} else {
|
|
ir_CALL_1(IR_VOID, ir_CONST_FC_FUNC(decrement_function), ref);
|
|
}
|
|
}
|
|
if (may_throw) {
|
|
zend_jit_check_exception(jit);
|
|
}
|
|
} else {
|
|
ref = jit_Z_DVAL(jit, op1_addr);
|
|
if (opline->opcode == ZEND_POST_INC || opline->opcode == ZEND_POST_DEC) {
|
|
jit_set_Z_DVAL(jit, res_addr, ref);
|
|
jit_set_Z_TYPE_INFO(jit, res_addr, IS_DOUBLE);
|
|
}
|
|
if (opline->opcode == ZEND_PRE_INC || opline->opcode == ZEND_POST_INC) {
|
|
op = IR_ADD;
|
|
} else {
|
|
op = IR_SUB;
|
|
}
|
|
ref = ir_BINARY_OP_D(op, ref, ir_CONST_DOUBLE(1.0));
|
|
jit_set_Z_DVAL(jit, op1_def_addr, ref);
|
|
if ((opline->opcode == ZEND_PRE_INC || opline->opcode == ZEND_PRE_DEC) &&
|
|
opline->result_type != IS_UNUSED) {
|
|
jit_set_Z_DVAL(jit, res_addr, ref);
|
|
jit_set_Z_TYPE_INFO(jit, res_addr, IS_DOUBLE);
|
|
}
|
|
}
|
|
ir_END_list(merge_inputs);
|
|
ir_MERGE_list(merge_inputs);
|
|
}
|
|
if (!zend_jit_store_var_if_necessary_ex(jit, 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(jit, opline->result.var, res_addr, res_info)) {
|
|
return 0;
|
|
}
|
|
}
|
|
return 1;
|
|
}
|
|
|
|
static int zend_jit_math_long_long(zend_jit_ctx *jit,
|
|
const zend_op *opline,
|
|
uint8_t 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)
|
|
{
|
|
bool same_ops = zend_jit_same_addr(op1_addr, op2_addr);
|
|
ir_op op;
|
|
ir_ref op1, op2, ref, if_overflow = IR_UNUSED;
|
|
|
|
if (opcode == ZEND_ADD) {
|
|
op = may_overflow ? IR_ADD_OV : IR_ADD;
|
|
} else if (opcode == ZEND_SUB) {
|
|
op = may_overflow ? IR_SUB_OV : IR_SUB;
|
|
} else if (opcode == ZEND_MUL) {
|
|
op = may_overflow ? IR_MUL_OV : IR_MUL;
|
|
} else {
|
|
ZEND_UNREACHABLE();
|
|
}
|
|
op1 = jit_Z_LVAL(jit, op1_addr);
|
|
op2 = (same_ops) ? op1 : jit_Z_LVAL(jit, op2_addr);
|
|
ref = ir_BINARY_OP_L(op, op1, op2);
|
|
|
|
if (may_overflow) {
|
|
if (res_info & MAY_BE_GUARD) {
|
|
if ((res_info & MAY_BE_ANY) == MAY_BE_LONG) {
|
|
zend_jit_trace_stack *stack = JIT_G(current_frame)->stack;
|
|
uint32_t old_res_info;
|
|
int32_t exit_point;
|
|
const void *exit_addr;
|
|
|
|
if (opline->opcode == ZEND_ADD
|
|
&& Z_MODE(op2_addr) == IS_CONST_ZVAL && Z_LVAL_P(Z_ZV(op2_addr)) == 1) {
|
|
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_DOUBLE, 0);
|
|
SET_STACK_REF(stack, EX_VAR_TO_NUM(opline->result.var), ir_CONST_DOUBLE((double)ZEND_LONG_MAX + 1.0));
|
|
exit_point = zend_jit_trace_get_exit_point(opline + 1, 0);
|
|
SET_STACK_INFO(stack, EX_VAR_TO_NUM(opline->result.var), old_res_info);
|
|
} else if (opline->opcode == ZEND_SUB
|
|
&& Z_MODE(op2_addr) == IS_CONST_ZVAL && Z_LVAL_P(Z_ZV(op2_addr)) == 1) {
|
|
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_DOUBLE, 0);
|
|
SET_STACK_REF(stack, EX_VAR_TO_NUM(opline->result.var), ir_CONST_DOUBLE((double)ZEND_LONG_MIN - 1.0));
|
|
exit_point = zend_jit_trace_get_exit_point(opline + 1, 0);
|
|
SET_STACK_INFO(stack, EX_VAR_TO_NUM(opline->result.var), old_res_info);
|
|
} else {
|
|
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;
|
|
}
|
|
ir_GUARD_NOT(ir_OVERFLOW(ref), ir_CONST_ADDR(exit_addr));
|
|
may_overflow = 0;
|
|
} else if ((res_info & MAY_BE_ANY) == MAY_BE_DOUBLE) {
|
|
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;
|
|
}
|
|
ir_GUARD(ir_OVERFLOW(ref), ir_CONST_ADDR(exit_addr));
|
|
} else {
|
|
ZEND_UNREACHABLE();
|
|
}
|
|
} else {
|
|
if_overflow = ir_IF(ir_OVERFLOW(ref));
|
|
ir_IF_FALSE(if_overflow);
|
|
}
|
|
}
|
|
|
|
if ((res_info & MAY_BE_ANY) != MAY_BE_DOUBLE) {
|
|
jit_set_Z_LVAL(jit, res_addr, ref);
|
|
|
|
if (Z_MODE(res_addr) != IS_REG) {
|
|
if (!zend_jit_same_addr(op1_addr, res_addr)) {
|
|
if ((res_use_info & (MAY_BE_ANY|MAY_BE_UNDEF|MAY_BE_REF|MAY_BE_GUARD)) != MAY_BE_LONG) {
|
|
jit_set_Z_TYPE_INFO(jit, res_addr, IS_LONG);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (may_overflow) {
|
|
ir_ref fast_path = IR_UNUSED;
|
|
|
|
if ((res_info & MAY_BE_ANY) != MAY_BE_DOUBLE) {
|
|
fast_path = ir_END();
|
|
ir_IF_TRUE_cold(if_overflow);
|
|
}
|
|
if (opcode == ZEND_ADD) {
|
|
if (Z_MODE(op2_addr) == IS_CONST_ZVAL && Z_LVAL_P(Z_ZV(op2_addr)) == 1) {
|
|
if (Z_MODE(res_addr) == IS_REG) {
|
|
jit_set_Z_DVAL(jit, res_addr, ir_CONST_DOUBLE((double)ZEND_LONG_MAX + 1.0));
|
|
} else {
|
|
#if SIZEOF_ZEND_LONG == 4
|
|
jit_set_Z_LVAL(jit, res_addr, ir_CONST_LONG(0));
|
|
jit_set_Z_W2(jit, res_addr, ir_CONST_U32(0x41e00000));
|
|
#else
|
|
jit_set_Z_LVAL(jit, res_addr, ir_CONST_LONG(0x43e0000000000000));
|
|
#endif
|
|
jit_set_Z_TYPE_INFO(jit, res_addr, IS_DOUBLE);
|
|
}
|
|
if ((res_info & MAY_BE_ANY) != MAY_BE_DOUBLE) {
|
|
ir_MERGE_WITH(fast_path);
|
|
}
|
|
return 1;
|
|
}
|
|
op = IR_ADD;
|
|
} else if (opcode == ZEND_SUB) {
|
|
if (Z_MODE(op2_addr) == IS_CONST_ZVAL && Z_LVAL_P(Z_ZV(op2_addr)) == 1) {
|
|
if (Z_MODE(res_addr) == IS_REG) {
|
|
jit_set_Z_DVAL(jit, res_addr, ir_CONST_DOUBLE((double)ZEND_LONG_MIN - 1.0));
|
|
} else {
|
|
#if SIZEOF_ZEND_LONG == 4
|
|
jit_set_Z_LVAL(jit, res_addr, ir_CONST_LONG(0x00200000));
|
|
jit_set_Z_W2(jit, res_addr, ir_CONST_U32(0xc1e00000));
|
|
#else
|
|
jit_set_Z_LVAL(jit, res_addr, ir_CONST_LONG(0xc3e0000000000000));
|
|
#endif
|
|
jit_set_Z_TYPE_INFO(jit, res_addr, IS_DOUBLE);
|
|
}
|
|
if ((res_info & MAY_BE_ANY) != MAY_BE_DOUBLE) {
|
|
ir_MERGE_WITH(fast_path);
|
|
}
|
|
return 1;
|
|
}
|
|
op = IR_SUB;
|
|
} else if (opcode == ZEND_MUL) {
|
|
op = IR_MUL;
|
|
} else {
|
|
ZEND_UNREACHABLE();
|
|
}
|
|
#if 1
|
|
/* reload */
|
|
op1 = jit_Z_LVAL(jit, op1_addr);
|
|
op2 = (same_ops) ? op1 : jit_Z_LVAL(jit, op2_addr);
|
|
#endif
|
|
#if 1
|
|
/* disable CSE */
|
|
ir_ref old_cse_limit = jit->ctx.fold_cse_limit;
|
|
jit->ctx.fold_cse_limit = 0x7fffffff;
|
|
#endif
|
|
op1 = ir_INT2D(op1);
|
|
op2 = ir_INT2D(op2);
|
|
#if 1
|
|
jit->ctx.fold_cse_limit = old_cse_limit;
|
|
#endif
|
|
ref = ir_BINARY_OP_D(op, op1, op2);
|
|
jit_set_Z_DVAL(jit, res_addr, ref);
|
|
if (Z_MODE(res_addr) != IS_REG) {
|
|
jit_set_Z_TYPE_INFO(jit, res_addr, IS_DOUBLE);
|
|
}
|
|
if ((res_info & MAY_BE_ANY) != MAY_BE_DOUBLE) {
|
|
ir_MERGE_WITH(fast_path);
|
|
}
|
|
}
|
|
|
|
return 1;
|
|
}
|
|
|
|
static int zend_jit_math_long_double(zend_jit_ctx *jit,
|
|
uint8_t opcode,
|
|
zend_jit_addr op1_addr,
|
|
zend_jit_addr op2_addr,
|
|
zend_jit_addr res_addr,
|
|
uint32_t res_use_info)
|
|
{
|
|
ir_op op;
|
|
ir_ref op1, op2, ref;
|
|
|
|
if (opcode == ZEND_ADD) {
|
|
op = IR_ADD;
|
|
} else if (opcode == ZEND_SUB) {
|
|
op = IR_SUB;
|
|
} else if (opcode == ZEND_MUL) {
|
|
op = IR_MUL;
|
|
} else if (opcode == ZEND_DIV) {
|
|
op = IR_DIV;
|
|
} else {
|
|
ZEND_UNREACHABLE();
|
|
}
|
|
op1 = jit_Z_LVAL(jit, op1_addr);
|
|
op2 = jit_Z_DVAL(jit, op2_addr);
|
|
ref = ir_BINARY_OP_D(op, ir_INT2D(op1), op2);
|
|
jit_set_Z_DVAL(jit, res_addr, ref);
|
|
|
|
if (Z_MODE(res_addr) != IS_REG) {
|
|
if ((res_use_info & (MAY_BE_ANY|MAY_BE_UNDEF|MAY_BE_REF|MAY_BE_GUARD)) != MAY_BE_DOUBLE) {
|
|
jit_set_Z_TYPE_INFO(jit, res_addr, IS_DOUBLE);
|
|
}
|
|
}
|
|
return 1;
|
|
}
|
|
|
|
static int zend_jit_math_double_long(zend_jit_ctx *jit,
|
|
uint8_t opcode,
|
|
zend_jit_addr op1_addr,
|
|
zend_jit_addr op2_addr,
|
|
zend_jit_addr res_addr,
|
|
uint32_t res_use_info)
|
|
{
|
|
ir_op op;
|
|
ir_ref op1, op2, ref;
|
|
|
|
if (opcode == ZEND_ADD) {
|
|
op = IR_ADD;
|
|
} else if (opcode == ZEND_SUB) {
|
|
op = IR_SUB;
|
|
} else if (opcode == ZEND_MUL) {
|
|
op = IR_MUL;
|
|
} else if (opcode == ZEND_DIV) {
|
|
op = IR_DIV;
|
|
} else {
|
|
ZEND_UNREACHABLE();
|
|
}
|
|
op1 = jit_Z_DVAL(jit, op1_addr);
|
|
op2 = jit_Z_LVAL(jit, op2_addr);
|
|
ref = ir_BINARY_OP_D(op, op1, ir_INT2D(op2));
|
|
jit_set_Z_DVAL(jit, res_addr, ref);
|
|
|
|
if (Z_MODE(res_addr) != IS_REG) {
|
|
if (!zend_jit_same_addr(op1_addr, res_addr)) {
|
|
if ((res_use_info & (MAY_BE_ANY|MAY_BE_UNDEF|MAY_BE_REF|MAY_BE_GUARD)) != MAY_BE_DOUBLE) {
|
|
jit_set_Z_TYPE_INFO(jit, res_addr, IS_DOUBLE);
|
|
}
|
|
}
|
|
}
|
|
return 1;
|
|
}
|
|
|
|
static int zend_jit_math_double_double(zend_jit_ctx *jit,
|
|
uint8_t opcode,
|
|
zend_jit_addr op1_addr,
|
|
zend_jit_addr op2_addr,
|
|
zend_jit_addr res_addr,
|
|
uint32_t res_use_info)
|
|
{
|
|
bool same_ops = zend_jit_same_addr(op1_addr, op2_addr);
|
|
ir_op op;
|
|
ir_ref op1, op2, ref;
|
|
|
|
if (opcode == ZEND_ADD) {
|
|
op = IR_ADD;
|
|
} else if (opcode == ZEND_SUB) {
|
|
op = IR_SUB;
|
|
} else if (opcode == ZEND_MUL) {
|
|
op = IR_MUL;
|
|
} else if (opcode == ZEND_DIV) {
|
|
op = IR_DIV;
|
|
} else {
|
|
ZEND_UNREACHABLE();
|
|
}
|
|
op1 = jit_Z_DVAL(jit, op1_addr);
|
|
op2 = (same_ops) ? op1 : jit_Z_DVAL(jit, op2_addr);
|
|
ref = ir_BINARY_OP_D(op, op1, op2);
|
|
jit_set_Z_DVAL(jit, res_addr, ref);
|
|
|
|
if (Z_MODE(res_addr) != IS_REG) {
|
|
if (!zend_jit_same_addr(op1_addr, res_addr)) {
|
|
if ((res_use_info & (MAY_BE_ANY|MAY_BE_UNDEF|MAY_BE_REF|MAY_BE_GUARD)) != MAY_BE_DOUBLE) {
|
|
jit_set_Z_TYPE_INFO(jit, res_addr, IS_DOUBLE);
|
|
}
|
|
}
|
|
}
|
|
return 1;
|
|
}
|
|
|
|
static int zend_jit_math_helper(zend_jit_ctx *jit,
|
|
const zend_op *opline,
|
|
uint8_t opcode,
|
|
uint8_t op1_type,
|
|
znode_op op1,
|
|
zend_jit_addr op1_addr,
|
|
uint32_t op1_info,
|
|
uint8_t 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)
|
|
{
|
|
ir_ref if_op1_long = IR_UNUSED;
|
|
ir_ref if_op1_double = IR_UNUSED;
|
|
ir_ref if_op2_double = IR_UNUSED;
|
|
ir_ref if_op1_long_op2_long = IR_UNUSED;
|
|
ir_ref if_op1_long_op2_double = IR_UNUSED;
|
|
ir_ref if_op1_double_op2_double = IR_UNUSED;
|
|
ir_ref if_op1_double_op2_long = IR_UNUSED;
|
|
ir_ref slow_inputs = IR_UNUSED;
|
|
bool same_ops = zend_jit_same_addr(op1_addr, op2_addr);
|
|
ir_refs *end_inputs;
|
|
ir_refs *res_inputs;
|
|
|
|
ir_refs_init(end_inputs, 6);
|
|
ir_refs_init(res_inputs, 6);
|
|
|
|
if (Z_MODE(op1_addr) == IS_REG) {
|
|
if (!has_concrete_type(op2_info & MAY_BE_ANY) && jit->ra[Z_SSA_VAR(op1_addr)].ref == IR_NULL) {
|
|
/* Force load */
|
|
zend_jit_use_reg(jit, op1_addr);
|
|
}
|
|
} else if (Z_MODE(op2_addr) == IS_REG) {
|
|
if (!has_concrete_type(op1_info & MAY_BE_ANY) && jit->ra[Z_SSA_VAR(op2_addr)].ref == IR_NULL) {
|
|
/* Force load */
|
|
zend_jit_use_reg(jit, op2_addr);
|
|
}
|
|
}
|
|
|
|
if (Z_MODE(res_addr) == IS_REG) {
|
|
jit->delay_var = Z_SSA_VAR(res_addr);
|
|
jit->delay_refs = res_inputs;
|
|
}
|
|
|
|
if ((res_info & MAY_BE_GUARD) && (res_info & MAY_BE_LONG) && (op1_info & MAY_BE_LONG) && (op2_info & MAY_BE_LONG)) {
|
|
if (op1_info & (MAY_BE_ANY-MAY_BE_LONG)) {
|
|
if_op1_long = jit_if_Z_TYPE(jit, op1_addr, IS_LONG);
|
|
ir_IF_TRUE(if_op1_long);
|
|
}
|
|
if (!same_ops && (op2_info & (MAY_BE_ANY-MAY_BE_LONG))) {
|
|
if_op1_long_op2_long = jit_if_Z_TYPE(jit, op2_addr, IS_LONG);
|
|
ir_IF_TRUE(if_op1_long_op2_long);
|
|
}
|
|
if (!zend_jit_math_long_long(jit, opline, opcode, op1_addr, op2_addr, res_addr, res_info, res_use_info, may_overflow)) {
|
|
return 0;
|
|
}
|
|
ir_refs_add(end_inputs, ir_END());
|
|
if (if_op1_long) {
|
|
ir_IF_FALSE_cold(if_op1_long);
|
|
ir_END_list(slow_inputs);
|
|
}
|
|
if (if_op1_long_op2_long) {
|
|
ir_IF_FALSE_cold(if_op1_long_op2_long);
|
|
ir_END_list(slow_inputs);
|
|
}
|
|
} else 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_long = jit_if_Z_TYPE(jit, op1_addr, IS_LONG);
|
|
ir_IF_TRUE(if_op1_long);
|
|
}
|
|
if (!same_ops && (op2_info & (MAY_BE_ANY-MAY_BE_LONG))) {
|
|
if_op1_long_op2_long = jit_if_Z_TYPE(jit, op2_addr, IS_LONG);
|
|
ir_IF_FALSE_cold(if_op1_long_op2_long);
|
|
if (op2_info & MAY_BE_DOUBLE) {
|
|
if (op2_info & (MAY_BE_ANY-(MAY_BE_LONG|MAY_BE_DOUBLE))) {
|
|
if_op1_long_op2_double = jit_if_Z_TYPE(jit, op2_addr, IS_DOUBLE);
|
|
ir_IF_FALSE_cold(if_op1_long_op2_double);
|
|
ir_END_list(slow_inputs);
|
|
ir_IF_TRUE(if_op1_long_op2_double);
|
|
}
|
|
if (!zend_jit_math_long_double(jit, opcode, op1_addr, op2_addr, res_addr, res_use_info)) {
|
|
return 0;
|
|
}
|
|
ir_refs_add(end_inputs, ir_END());
|
|
} else {
|
|
ir_END_list(slow_inputs);
|
|
}
|
|
ir_IF_TRUE(if_op1_long_op2_long);
|
|
}
|
|
if (!zend_jit_math_long_long(jit, opline, opcode, op1_addr, op2_addr, res_addr, res_info, res_use_info, may_overflow)) {
|
|
return 0;
|
|
}
|
|
ir_refs_add(end_inputs, ir_END());
|
|
|
|
if (if_op1_long) {
|
|
ir_IF_FALSE_cold(if_op1_long);
|
|
}
|
|
|
|
if (op1_info & MAY_BE_DOUBLE) {
|
|
if (op1_info & (MAY_BE_ANY-(MAY_BE_LONG|MAY_BE_DOUBLE))) {
|
|
if_op1_double = jit_if_Z_TYPE(jit, op1_addr, IS_DOUBLE);
|
|
ir_IF_FALSE_cold(if_op1_double);
|
|
ir_END_list(slow_inputs);
|
|
ir_IF_TRUE(if_op1_double);
|
|
}
|
|
if (op2_info & MAY_BE_DOUBLE) {
|
|
if (!same_ops && (op2_info & (MAY_BE_ANY-MAY_BE_DOUBLE))) {
|
|
if_op1_double_op2_double = jit_if_Z_TYPE(jit, op2_addr, IS_DOUBLE);
|
|
ir_IF_TRUE(if_op1_double_op2_double);
|
|
}
|
|
if (!zend_jit_math_double_double(jit, opcode, op1_addr, op2_addr, res_addr, res_use_info)) {
|
|
return 0;
|
|
}
|
|
ir_refs_add(end_inputs, ir_END());
|
|
if (if_op1_double_op2_double) {
|
|
ir_IF_FALSE_cold(if_op1_double_op2_double);
|
|
}
|
|
}
|
|
if (!same_ops) {
|
|
if (op2_info & (MAY_BE_ANY-(MAY_BE_LONG|MAY_BE_DOUBLE))) {
|
|
if_op1_double_op2_long = jit_if_Z_TYPE(jit, op2_addr, IS_LONG);
|
|
ir_IF_FALSE_cold(if_op1_double_op2_long);
|
|
ir_END_list(slow_inputs);
|
|
ir_IF_TRUE(if_op1_double_op2_long);
|
|
}
|
|
if (!zend_jit_math_double_long(jit, opcode, op1_addr, op2_addr, res_addr, res_use_info)) {
|
|
return 0;
|
|
}
|
|
ir_refs_add(end_inputs, ir_END());
|
|
} else if (if_op1_double_op2_double) {
|
|
ir_END_list(slow_inputs);
|
|
}
|
|
} else if (if_op1_long) {
|
|
ir_END_list(slow_inputs);
|
|
}
|
|
} 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_op1_double = jit_if_Z_TYPE(jit, op1_addr, IS_DOUBLE);
|
|
ir_IF_FALSE_cold(if_op1_double);
|
|
ir_END_list(slow_inputs);
|
|
ir_IF_TRUE(if_op1_double);
|
|
}
|
|
if (op2_info & MAY_BE_DOUBLE) {
|
|
if (!same_ops && (op2_info & (MAY_BE_ANY-MAY_BE_DOUBLE))) {
|
|
if_op1_double_op2_double = jit_if_Z_TYPE(jit, op2_addr, IS_DOUBLE);
|
|
ir_IF_TRUE(if_op1_double_op2_double);
|
|
}
|
|
if (!zend_jit_math_double_double(jit, opcode, op1_addr, op2_addr, res_addr, res_use_info)) {
|
|
return 0;
|
|
}
|
|
ir_refs_add(end_inputs, ir_END());
|
|
if (if_op1_double_op2_double) {
|
|
ir_IF_FALSE_cold(if_op1_double_op2_double);
|
|
}
|
|
}
|
|
if (!same_ops && (op2_info & MAY_BE_LONG)) {
|
|
if (op2_info & (MAY_BE_ANY-(MAY_BE_DOUBLE|MAY_BE_LONG))) {
|
|
if_op1_double_op2_long = jit_if_Z_TYPE(jit, op2_addr, IS_LONG);
|
|
ir_IF_FALSE_cold(if_op1_double_op2_long);
|
|
ir_END_list(slow_inputs);
|
|
ir_IF_TRUE(if_op1_double_op2_long);
|
|
}
|
|
if (!zend_jit_math_double_long(jit, opcode, op1_addr, op2_addr, res_addr, res_use_info)) {
|
|
return 0;
|
|
}
|
|
ir_refs_add(end_inputs, ir_END());
|
|
} else if (if_op1_double_op2_double) {
|
|
ir_END_list(slow_inputs);
|
|
}
|
|
} 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_op2_double = jit_if_Z_TYPE(jit, op2_addr, IS_DOUBLE);
|
|
ir_IF_FALSE_cold(if_op2_double);
|
|
ir_END_list(slow_inputs);
|
|
ir_IF_TRUE(if_op2_double);
|
|
}
|
|
if (op1_info & MAY_BE_DOUBLE) {
|
|
if (!same_ops && (op1_info & (MAY_BE_ANY-MAY_BE_DOUBLE))) {
|
|
if_op1_double_op2_double = jit_if_Z_TYPE(jit, op1_addr, IS_DOUBLE);
|
|
ir_IF_TRUE(if_op1_double_op2_double);
|
|
}
|
|
if (!zend_jit_math_double_double(jit, opcode, op1_addr, op2_addr, res_addr, res_use_info)) {
|
|
return 0;
|
|
}
|
|
ir_refs_add(end_inputs, ir_END());
|
|
if (if_op1_double_op2_double) {
|
|
ir_IF_FALSE_cold(if_op1_double_op2_double);
|
|
}
|
|
}
|
|
if (!same_ops && (op1_info & MAY_BE_LONG)) {
|
|
if (op1_info & (MAY_BE_ANY-(MAY_BE_DOUBLE|MAY_BE_LONG))) {
|
|
if_op1_long_op2_double = jit_if_Z_TYPE(jit, op1_addr, IS_LONG);
|
|
ir_IF_FALSE_cold(if_op1_long_op2_double);
|
|
ir_END_list(slow_inputs);
|
|
ir_IF_TRUE(if_op1_long_op2_double);
|
|
}
|
|
if (!zend_jit_math_long_double(jit, opcode, op1_addr, op2_addr, res_addr, res_use_info)) {
|
|
return 0;
|
|
}
|
|
ir_refs_add(end_inputs, ir_END());
|
|
} else if (if_op1_double_op2_double) {
|
|
ir_END_list(slow_inputs);
|
|
}
|
|
}
|
|
|
|
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)))) {
|
|
ir_ref func, arg1, arg2, arg3;
|
|
|
|
if (slow_inputs) {
|
|
ir_MERGE_list(slow_inputs);
|
|
}
|
|
|
|
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_inv(jit, op1_addr, real_addr, op1_info)) {
|
|
return 0;
|
|
}
|
|
op1_addr = real_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_inv(jit, op2_addr, real_addr, op2_info)) {
|
|
return 0;
|
|
}
|
|
op2_addr = real_addr;
|
|
}
|
|
if (Z_MODE(res_addr) == IS_REG) {
|
|
arg1 = jit_ZVAL_ADDR(jit, ZEND_ADDR_MEM_ZVAL(ZREG_FP, res_var));
|
|
} else {
|
|
arg1 = jit_ZVAL_ADDR(jit, res_addr);
|
|
}
|
|
arg2 = jit_ZVAL_ADDR(jit, op1_addr);
|
|
arg3 = jit_ZVAL_ADDR(jit, op2_addr);
|
|
jit_SET_EX_OPLINE(jit, opline);
|
|
if (opcode == ZEND_ADD) {
|
|
func = ir_CONST_FC_FUNC(add_function);
|
|
} else if (opcode == ZEND_SUB) {
|
|
func = ir_CONST_FC_FUNC(sub_function);
|
|
} else if (opcode == ZEND_MUL) {
|
|
func = ir_CONST_FC_FUNC(mul_function);
|
|
} else if (opcode == ZEND_DIV) {
|
|
func = ir_CONST_FC_FUNC(div_function);
|
|
} else {
|
|
ZEND_UNREACHABLE();
|
|
}
|
|
ir_CALL_3(IR_VOID, func, arg1, arg2, arg3);
|
|
|
|
jit_FREE_OP(jit, op1_type, op1, op1_info, NULL);
|
|
jit_FREE_OP(jit, op2_type, op2, op2_info, NULL);
|
|
|
|
if (may_throw) {
|
|
if (opline->opcode == ZEND_ASSIGN_DIM_OP && (opline->op2_type & (IS_VAR|IS_TMP_VAR))) {
|
|
ir_GUARD_NOT(ir_LOAD_A(jit_EG_exception(jit)),
|
|
jit_STUB_ADDR(jit, jit_stub_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(jit, opline);
|
|
} else {
|
|
zend_jit_check_exception(jit);
|
|
}
|
|
}
|
|
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(jit, real_addr, res_addr, res_info)) {
|
|
return 0;
|
|
}
|
|
}
|
|
ir_refs_add(end_inputs, ir_END());
|
|
}
|
|
|
|
if (end_inputs->count) {
|
|
ir_MERGE_N(end_inputs->count, end_inputs->refs);
|
|
}
|
|
|
|
if (Z_MODE(res_addr) == IS_REG) {
|
|
ZEND_ASSERT(jit->delay_refs == res_inputs);
|
|
ZEND_ASSERT(end_inputs->count == res_inputs->count);
|
|
jit->delay_var = -1;
|
|
jit->delay_refs = NULL;
|
|
if (res_inputs->count == 1) {
|
|
zend_jit_def_reg(jit, res_addr, res_inputs->refs[0]);
|
|
} else {
|
|
ir_ref phi = ir_PHI_N((res_info & MAY_BE_LONG) ? IR_LONG : IR_DOUBLE, res_inputs->count, res_inputs->refs);
|
|
zend_jit_def_reg(jit, res_addr, phi);
|
|
}
|
|
}
|
|
|
|
return 1;
|
|
}
|
|
|
|
static int zend_jit_math(zend_jit_ctx *jit, 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));
|
|
|
|
if (!zend_jit_math_helper(jit, 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(jit, opline->result.var, res_addr, res_info)) {
|
|
return 0;
|
|
}
|
|
return 1;
|
|
}
|
|
|
|
static int zend_jit_add_arrays(zend_jit_ctx *jit, const zend_op *opline, uint32_t op1_info, zend_jit_addr op1_addr, uint32_t op2_info, zend_jit_addr op2_addr, zend_jit_addr res_addr)
|
|
{
|
|
ir_ref ref;
|
|
ir_ref arg1 = jit_Z_PTR(jit, op1_addr);
|
|
ir_ref arg2 = jit_Z_PTR(jit, op2_addr);
|
|
|
|
ref = ir_CALL_2(IR_ADDR, ir_CONST_FC_FUNC(zend_jit_add_arrays_helper), arg1, arg2);
|
|
jit_set_Z_PTR(jit, res_addr, ref);
|
|
jit_set_Z_TYPE_INFO(jit, res_addr, IS_ARRAY_EX);
|
|
jit_FREE_OP(jit, opline->op1_type, opline->op1, op1_info, opline);
|
|
jit_FREE_OP(jit, opline->op2_type, opline->op2, op2_info, opline);
|
|
return 1;
|
|
}
|
|
|
|
static int zend_jit_long_math_helper(zend_jit_ctx *jit,
|
|
const zend_op *opline,
|
|
uint8_t opcode,
|
|
uint8_t op1_type,
|
|
znode_op op1,
|
|
zend_jit_addr op1_addr,
|
|
uint32_t op1_info,
|
|
zend_ssa_range *op1_range,
|
|
uint8_t 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)
|
|
{
|
|
ir_ref ref = IR_UNUSED;
|
|
ir_ref if_long1 = IR_UNUSED;
|
|
ir_ref if_long2 = IR_UNUSED;
|
|
bool same_ops = zend_jit_same_addr(op1_addr, op2_addr);
|
|
ir_refs *res_inputs;
|
|
|
|
ir_refs_init(res_inputs, 2);
|
|
|
|
if (Z_MODE(op1_addr) == IS_REG
|
|
&& Z_LOAD(op1_addr)
|
|
&& jit->ra[Z_SSA_VAR(op1_addr)].ref == IR_NULL) {
|
|
/* Force load */
|
|
zend_jit_use_reg(jit, op1_addr);
|
|
}
|
|
if (Z_MODE(op2_addr) == IS_REG
|
|
&& Z_LOAD(op2_addr)
|
|
&& jit->ra[Z_SSA_VAR(op2_addr)].ref == IR_NULL) {
|
|
/* Force load */
|
|
zend_jit_use_reg(jit, op2_addr);
|
|
}
|
|
|
|
if (op1_info & ((MAY_BE_ANY|MAY_BE_UNDEF)-MAY_BE_LONG)) {
|
|
if_long1 = jit_if_Z_TYPE(jit, op1_addr, IS_LONG);
|
|
ir_IF_TRUE(if_long1);
|
|
}
|
|
if (!same_ops && (op2_info & ((MAY_BE_ANY|MAY_BE_UNDEF)-MAY_BE_LONG))) {
|
|
if_long2 = jit_if_Z_TYPE(jit, op2_addr, IS_LONG);
|
|
ir_IF_TRUE(if_long2);
|
|
}
|
|
|
|
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)) {
|
|
ref = ir_CONST_LONG(0);
|
|
} else {
|
|
zend_jit_invalidate_var_if_necessary(jit, op1_type, op1_addr, op1);
|
|
zend_jit_invalidate_var_if_necessary(jit, op2_type, op2_addr, op2);
|
|
jit_SET_EX_OPLINE(jit, opline);
|
|
ir_GUARD(IR_FALSE, jit_STUB_ADDR(jit, jit_stub_negative_shift));
|
|
if (Z_MODE(res_addr) == IS_REG) {
|
|
ref = ir_CONST_LONG(0); // dead code
|
|
}
|
|
}
|
|
} else {
|
|
ref = ir_SHL_L(jit_Z_LVAL(jit, op1_addr), ir_CONST_LONG(op2_lval));
|
|
}
|
|
} else {
|
|
ref = jit_Z_LVAL(jit, op2_addr);
|
|
if (!op2_range ||
|
|
op2_range->min < 0 ||
|
|
op2_range->max >= SIZEOF_ZEND_LONG * 8) {
|
|
|
|
ir_ref if_wrong, cold_path, ref2, if_ok;
|
|
ir_ref op1_ref = jit_Z_LVAL(jit, op1_addr);
|
|
|
|
if_wrong = ir_IF(ir_UGT(ref, ir_CONST_LONG((SIZEOF_ZEND_LONG * 8) - 1)));
|
|
ir_IF_TRUE_cold(if_wrong);
|
|
if_ok = ir_IF(ir_GE(ref, ir_CONST_LONG(0)));
|
|
ir_IF_FALSE(if_ok);
|
|
jit_SET_EX_OPLINE(jit, opline);
|
|
zend_jit_invalidate_var_if_necessary(jit, op1_type, op1_addr, op1);
|
|
zend_jit_invalidate_var_if_necessary(jit, op2_type, op2_addr, op2);
|
|
ir_IJMP(jit_STUB_ADDR(jit, jit_stub_negative_shift));
|
|
ir_IF_TRUE(if_ok);
|
|
ref2 = ir_CONST_LONG(0);
|
|
cold_path = ir_END();
|
|
ir_IF_FALSE(if_wrong);
|
|
ref = ir_SHL_L(op1_ref, ref);
|
|
ir_MERGE_WITH(cold_path);
|
|
ref = ir_PHI_2(IR_LONG, ref, ref2);
|
|
} else {
|
|
ref = ir_SHL_L(jit_Z_LVAL(jit, op1_addr), ref);
|
|
}
|
|
}
|
|
} else if (opcode == ZEND_SR) {
|
|
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)) {
|
|
ref = ir_SAR_L(
|
|
jit_Z_LVAL(jit, op1_addr),
|
|
ir_CONST_LONG((SIZEOF_ZEND_LONG * 8) - 1));
|
|
} else {
|
|
zend_jit_invalidate_var_if_necessary(jit, op1_type, op1_addr, op1);
|
|
zend_jit_invalidate_var_if_necessary(jit, op2_type, op2_addr, op2);
|
|
jit_SET_EX_OPLINE(jit, opline);
|
|
ir_GUARD(IR_FALSE, jit_STUB_ADDR(jit, jit_stub_negative_shift));
|
|
if (Z_MODE(res_addr) == IS_REG) {
|
|
ref = ir_CONST_LONG(0); // dead code
|
|
}
|
|
}
|
|
} else {
|
|
ref = ir_SAR_L(jit_Z_LVAL(jit, op1_addr), ir_CONST_LONG(op2_lval));
|
|
}
|
|
} else {
|
|
ref = jit_Z_LVAL(jit, op2_addr);
|
|
if (!op2_range ||
|
|
op2_range->min < 0 ||
|
|
op2_range->max >= SIZEOF_ZEND_LONG * 8) {
|
|
|
|
ir_ref if_wrong, cold_path, ref2, if_ok;
|
|
|
|
if_wrong = ir_IF(ir_UGT(ref, ir_CONST_LONG((SIZEOF_ZEND_LONG * 8) - 1)));
|
|
ir_IF_TRUE_cold(if_wrong);
|
|
if_ok = ir_IF(ir_GE(ref, ir_CONST_LONG(0)));
|
|
ir_IF_FALSE(if_ok);
|
|
jit_SET_EX_OPLINE(jit, opline);
|
|
zend_jit_invalidate_var_if_necessary(jit, op1_type, op1_addr, op1);
|
|
zend_jit_invalidate_var_if_necessary(jit, op2_type, op2_addr, op2);
|
|
ir_IJMP(jit_STUB_ADDR(jit, jit_stub_negative_shift));
|
|
ir_IF_TRUE(if_ok);
|
|
ref2 = ir_CONST_LONG((SIZEOF_ZEND_LONG * 8) - 1);
|
|
cold_path = ir_END();
|
|
ir_IF_FALSE(if_wrong);
|
|
ir_MERGE_WITH(cold_path);
|
|
ref = ir_PHI_2(IR_LONG, ref, ref2);
|
|
}
|
|
ref = ir_SAR_L(jit_Z_LVAL(jit, op1_addr), ref);
|
|
}
|
|
} 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(jit, op1_type, op1_addr, op1);
|
|
zend_jit_invalidate_var_if_necessary(jit, op2_type, op2_addr, op2);
|
|
jit_SET_EX_OPLINE(jit, opline);
|
|
ir_GUARD(IR_FALSE, jit_STUB_ADDR(jit, jit_stub_mod_by_zero));
|
|
if (Z_MODE(res_addr) == IS_REG) {
|
|
ref = ir_CONST_LONG(0); // dead code
|
|
}
|
|
} else if (zend_long_is_power_of_two(op2_lval) && op1_range && op1_range->min >= 0) {
|
|
ref = ir_AND_L(jit_Z_LVAL(jit, op1_addr), ir_CONST_LONG(op2_lval - 1));
|
|
} else {
|
|
ref = ir_MOD_L(jit_Z_LVAL(jit, op1_addr), ir_CONST_LONG(op2_lval));
|
|
}
|
|
} else {
|
|
ir_ref zero_path = 0;
|
|
ir_ref op1_ref = jit_Z_LVAL(jit, op1_addr);
|
|
|
|
ref = jit_Z_LVAL(jit, op2_addr);
|
|
if ((op2_type & (MAY_BE_UNDEF|MAY_BE_NULL|MAY_BE_FALSE)) || !op2_range || (op2_range->min <= 0 && op2_range->max >= 0)) {
|
|
ir_ref if_ok = ir_IF(ref);
|
|
ir_IF_FALSE(if_ok);
|
|
jit_SET_EX_OPLINE(jit, opline);
|
|
zend_jit_invalidate_var_if_necessary(jit, op1_type, op1_addr, op1);
|
|
zend_jit_invalidate_var_if_necessary(jit, op2_type, op2_addr, op2);
|
|
ir_IJMP(jit_STUB_ADDR(jit, jit_stub_mod_by_zero));
|
|
ir_IF_TRUE(if_ok);
|
|
}
|
|
|
|
/* Prevent overflow error/crash if op1 == LONG_MIN and op2 == -1 */
|
|
if (!op2_range || (op2_range->min <= -1 && op2_range->max >= -1)) {
|
|
ir_ref if_minus_one = ir_IF(ir_EQ(ref, ir_CONST_LONG(-1)));
|
|
ir_IF_TRUE_cold(if_minus_one);
|
|
zero_path = ir_END();
|
|
ir_IF_FALSE(if_minus_one);
|
|
}
|
|
ref = ir_MOD_L(op1_ref, ref);
|
|
|
|
if (zero_path) {
|
|
ir_MERGE_WITH(zero_path);
|
|
ref = ir_PHI_2(IR_LONG, ref, ir_CONST_LONG(0));
|
|
}
|
|
}
|
|
} else {
|
|
ir_op op;
|
|
ir_ref op1, op2;
|
|
|
|
if (opcode == ZEND_BW_OR) {
|
|
op = IR_OR;
|
|
} else if (opcode == ZEND_BW_AND) {
|
|
op = IR_AND;
|
|
} else if (opcode == ZEND_BW_XOR) {
|
|
op = IR_XOR;
|
|
} else {
|
|
ZEND_UNREACHABLE();
|
|
}
|
|
op1 = jit_Z_LVAL(jit, op1_addr);
|
|
op2 = (same_ops) ? op1 : jit_Z_LVAL(jit, op2_addr);
|
|
ref = ir_BINARY_OP_L(op, op1, op2);
|
|
}
|
|
|
|
if (ref) {
|
|
if (Z_MODE(res_addr) == IS_REG
|
|
&& ((op1_info & ((MAY_BE_ANY|MAY_BE_UNDEF)-MAY_BE_LONG))
|
|
|| (op2_info & ((MAY_BE_ANY|MAY_BE_UNDEF)-MAY_BE_LONG)))) {
|
|
jit->delay_var = Z_SSA_VAR(res_addr);
|
|
jit->delay_refs = res_inputs;
|
|
}
|
|
jit_set_Z_LVAL(jit, res_addr, ref);
|
|
if (Z_MODE(res_addr) != IS_REG) {
|
|
if (!zend_jit_same_addr(op1_addr, res_addr)) {
|
|
if ((res_use_info & (MAY_BE_ANY|MAY_BE_UNDEF|MAY_BE_REF|MAY_BE_GUARD)) != MAY_BE_LONG) {
|
|
jit_set_Z_TYPE_INFO(jit, 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))) {
|
|
ir_ref fast_path = ir_END();
|
|
ir_ref func, arg1, arg2, arg3;
|
|
|
|
if (if_long2 && if_long1) {
|
|
ir_ref ref;
|
|
ir_IF_FALSE_cold(if_long2);
|
|
ref = ir_END();
|
|
ir_IF_FALSE_cold(if_long1);
|
|
ir_MERGE_2(ref, ir_END());
|
|
} else if (if_long1) {
|
|
ir_IF_FALSE_cold(if_long1);
|
|
} else if (if_long2) {
|
|
ir_IF_FALSE_cold(if_long2);
|
|
}
|
|
|
|
if (op1_info & MAY_BE_UNDEF) {
|
|
ir_ref if_def, ref, ref2;
|
|
|
|
ref = jit_ZVAL_ADDR(jit, op1_addr);
|
|
if_def = jit_if_not_Z_TYPE(jit, op1_addr, IS_UNDEF);
|
|
ir_IF_FALSE_cold(if_def);
|
|
|
|
// zend_error_unchecked(E_WARNING, "Undefined variable $%S", CV_DEF_OF(EX_VAR_TO_NUM(opline->op1.var)));
|
|
ir_CALL_1(IR_VOID, ir_CONST_FC_FUNC(zend_jit_undefined_op_helper), ir_CONST_U32(opline->op1.var));
|
|
|
|
ref2 = jit_EG(uninitialized_zval);
|
|
ir_MERGE_WITH_EMPTY_TRUE(if_def);
|
|
ref = ir_PHI_2(IR_ADDR, ref2, ref);
|
|
op1_addr = ZEND_ADDR_REF_ZVAL(ref);
|
|
}
|
|
|
|
if (op2_info & MAY_BE_UNDEF) {
|
|
ir_ref if_def, ref, ref2;
|
|
|
|
ref = jit_ZVAL_ADDR(jit, op2_addr);
|
|
if_def = jit_if_not_Z_TYPE(jit, op2_addr, IS_UNDEF);
|
|
ir_IF_FALSE_cold(if_def);
|
|
|
|
// zend_error_unchecked(E_WARNING, "Undefined variable $%S", CV_DEF_OF(EX_VAR_TO_NUM(opline->op2.var)));
|
|
ir_CALL_1(IR_VOID, ir_CONST_FC_FUNC(zend_jit_undefined_op_helper), ir_CONST_U32(opline->op2.var));
|
|
|
|
ref2 = jit_EG(uninitialized_zval);
|
|
ir_MERGE_WITH_EMPTY_TRUE(if_def);
|
|
ref = ir_PHI_2(IR_ADDR, ref2, ref);
|
|
op2_addr = ZEND_ADDR_REF_ZVAL(ref);
|
|
}
|
|
|
|
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_inv(jit, op1_addr, real_addr, op1_info)) {
|
|
return 0;
|
|
}
|
|
op1_addr = real_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_inv(jit, op2_addr, real_addr, op2_info)) {
|
|
return 0;
|
|
}
|
|
op2_addr = real_addr;
|
|
}
|
|
if (Z_MODE(res_addr) == IS_REG) {
|
|
arg1 = jit_ZVAL_ADDR(jit, ZEND_ADDR_MEM_ZVAL(ZREG_FP, res_var));
|
|
} else {
|
|
arg1 = jit_ZVAL_ADDR(jit, res_addr);
|
|
}
|
|
arg2 = jit_ZVAL_ADDR(jit, op1_addr);
|
|
arg3 = jit_ZVAL_ADDR(jit, op2_addr);
|
|
jit_SET_EX_OPLINE(jit, opline);
|
|
if (opcode == ZEND_BW_OR) {
|
|
func = ir_CONST_FC_FUNC(bitwise_or_function);
|
|
} else if (opcode == ZEND_BW_AND) {
|
|
func = ir_CONST_FC_FUNC(bitwise_and_function);
|
|
} else if (opcode == ZEND_BW_XOR) {
|
|
func = ir_CONST_FC_FUNC(bitwise_xor_function);
|
|
} else if (opcode == ZEND_SL) {
|
|
func = ir_CONST_FC_FUNC(shift_left_function);
|
|
} else if (opcode == ZEND_SR) {
|
|
func = ir_CONST_FC_FUNC(shift_right_function);
|
|
} else if (opcode == ZEND_MOD) {
|
|
func = ir_CONST_FC_FUNC(mod_function);
|
|
} else {
|
|
ZEND_UNREACHABLE();
|
|
}
|
|
ir_CALL_3(IR_VOID, func, arg1, arg2, arg3);
|
|
|
|
if (op1_addr == res_addr && (op2_info & MAY_BE_RCN)) {
|
|
/* compound assignment may decrement "op2" refcount */
|
|
op2_info |= MAY_BE_RC1;
|
|
}
|
|
|
|
jit_FREE_OP(jit, op1_type, op1, op1_info, NULL);
|
|
jit_FREE_OP(jit, op2_type, op2, op2_info, NULL);
|
|
|
|
if (may_throw) {
|
|
if (opline->opcode == ZEND_ASSIGN_DIM_OP && (opline->op2_type & (IS_VAR|IS_TMP_VAR))) {
|
|
ir_GUARD_NOT(ir_LOAD_A(jit_EG_exception(jit)),
|
|
jit_STUB_ADDR(jit, jit_stub_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(jit, opline);
|
|
} else {
|
|
zend_jit_check_exception(jit);
|
|
}
|
|
}
|
|
|
|
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(jit, real_addr, res_addr, res_info)) {
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
ir_MERGE_2(fast_path, ir_END());
|
|
|
|
if (Z_MODE(res_addr) == IS_REG) {
|
|
ZEND_ASSERT(jit->delay_refs == res_inputs);
|
|
ZEND_ASSERT(res_inputs->count == 2);
|
|
jit->delay_var = -1;
|
|
jit->delay_refs = NULL;
|
|
if (res_inputs->count == 1) {
|
|
zend_jit_def_reg(jit, res_addr, res_inputs->refs[0]);
|
|
} else {
|
|
ir_ref phi = ir_PHI_N(IR_LONG, res_inputs->count, res_inputs->refs);
|
|
zend_jit_def_reg(jit, res_addr, phi);
|
|
}
|
|
}
|
|
}
|
|
|
|
return 1;
|
|
}
|
|
|
|
static int zend_jit_long_math(zend_jit_ctx *jit, 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_LONG) && (op2_info & MAY_BE_LONG));
|
|
|
|
if (!zend_jit_long_math_helper(jit, 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(jit, opline->result.var, res_addr, res_info)) {
|
|
return 0;
|
|
}
|
|
return 1;
|
|
}
|
|
|
|
static int zend_jit_concat_helper(zend_jit_ctx *jit,
|
|
const zend_op *opline,
|
|
uint8_t op1_type,
|
|
znode_op op1,
|
|
zend_jit_addr op1_addr,
|
|
uint32_t op1_info,
|
|
uint8_t op2_type,
|
|
znode_op op2,
|
|
zend_jit_addr op2_addr,
|
|
uint32_t op2_info,
|
|
zend_jit_addr res_addr,
|
|
int may_throw)
|
|
{
|
|
ir_ref if_op1_string = IR_UNUSED;
|
|
ir_ref if_op2_string = IR_UNUSED;
|
|
ir_ref fast_path = IR_UNUSED;
|
|
|
|
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_op1_string = jit_if_Z_TYPE(jit, op1_addr, IS_STRING);
|
|
ir_IF_TRUE(if_op1_string);
|
|
}
|
|
if (op2_info & ((MAY_BE_UNDEF|MAY_BE_ANY|MAY_BE_REF) - MAY_BE_STRING)) {
|
|
if_op2_string = jit_if_Z_TYPE(jit, op2_addr, IS_STRING);
|
|
ir_IF_TRUE(if_op2_string);
|
|
}
|
|
if (zend_jit_same_addr(op1_addr, res_addr)) {
|
|
ir_ref arg1 = jit_ZVAL_ADDR(jit, res_addr);
|
|
ir_ref arg2 = jit_ZVAL_ADDR(jit, op2_addr);
|
|
|
|
ir_CALL_2(IR_VOID, ir_CONST_FC_FUNC(zend_jit_fast_assign_concat_helper), arg1, arg2);
|
|
/* concatenation with itself may reduce refcount */
|
|
op2_info |= MAY_BE_RC1;
|
|
} else {
|
|
ir_ref arg1 = jit_ZVAL_ADDR(jit, res_addr);
|
|
ir_ref arg2 = jit_ZVAL_ADDR(jit, op1_addr);
|
|
ir_ref arg3 = jit_ZVAL_ADDR(jit, op2_addr);
|
|
|
|
if (op1_type == IS_CV || op1_type == IS_CONST) {
|
|
ir_CALL_3(IR_VOID, ir_CONST_FC_FUNC(zend_jit_fast_concat_helper), arg1, arg2, arg3);
|
|
} else {
|
|
ir_CALL_3(IR_VOID, ir_CONST_FC_FUNC(zend_jit_fast_concat_tmp_helper), arg1, arg2, arg3);
|
|
}
|
|
}
|
|
/* concatenation with empty string may increase refcount */
|
|
op2_info |= MAY_BE_RCN;
|
|
jit_FREE_OP(jit, op2_type, op2, op2_info, opline);
|
|
if (if_op1_string || if_op2_string) {
|
|
fast_path = ir_END();
|
|
}
|
|
}
|
|
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)) {
|
|
if (if_op1_string && if_op2_string) {
|
|
ir_IF_FALSE(if_op1_string);
|
|
ir_MERGE_WITH_EMPTY_FALSE(if_op2_string);
|
|
} else if (if_op1_string) {
|
|
ir_IF_FALSE_cold(if_op1_string);
|
|
} else if (if_op2_string) {
|
|
ir_IF_FALSE_cold(if_op2_string);
|
|
}
|
|
}
|
|
ir_ref arg1 = jit_ZVAL_ADDR(jit, res_addr);
|
|
ir_ref arg2 = jit_ZVAL_ADDR(jit, op1_addr);
|
|
ir_ref arg3 = jit_ZVAL_ADDR(jit, op2_addr);
|
|
|
|
jit_SET_EX_OPLINE(jit, opline);
|
|
ir_CALL_3(IR_VOID, ir_CONST_FC_FUNC(concat_function), arg1, arg2, arg3);
|
|
/* concatenation with empty string may increase refcount */
|
|
op1_info |= MAY_BE_RCN;
|
|
op2_info |= MAY_BE_RCN;
|
|
jit_FREE_OP(jit, op1_type, op1, op1_info, NULL);
|
|
jit_FREE_OP(jit, op2_type, op2, op2_info, NULL);
|
|
if (may_throw) {
|
|
if (opline->opcode == ZEND_ASSIGN_DIM_OP && (opline->op2_type & (IS_VAR|IS_TMP_VAR))) {
|
|
ir_GUARD_NOT(ir_LOAD_A(jit_EG_exception(jit)),
|
|
jit_STUB_ADDR(jit, jit_stub_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(jit, opline);
|
|
} else {
|
|
zend_jit_check_exception(jit);
|
|
}
|
|
}
|
|
if ((op1_info & MAY_BE_STRING) && (op2_info & MAY_BE_STRING)) {
|
|
ir_MERGE_WITH(fast_path);
|
|
}
|
|
}
|
|
return 1;
|
|
}
|
|
|
|
static int zend_jit_concat(zend_jit_ctx *jit, 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(jit, 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_assign_op(zend_jit_ctx *jit,
|
|
const zend_op *opline,
|
|
uint32_t op1_info,
|
|
zend_jit_addr op1_addr,
|
|
zend_ssa_range *op1_range,
|
|
uint32_t op1_def_info,
|
|
zend_jit_addr op1_def_addr,
|
|
uint32_t op1_mem_info,
|
|
uint32_t op2_info,
|
|
zend_jit_addr op2_addr,
|
|
zend_ssa_range *op2_range,
|
|
int may_overflow,
|
|
int may_throw)
|
|
{
|
|
int result = 1;
|
|
ir_ref slow_path = IR_UNUSED;
|
|
|
|
ZEND_ASSERT(opline->op1_type == IS_CV && opline->result_type == IS_UNUSED);
|
|
ZEND_ASSERT(!(op1_info & MAY_BE_UNDEF) && !(op2_info & MAY_BE_UNDEF));
|
|
|
|
if (op1_info & MAY_BE_REF) {
|
|
ir_ref ref, ref2, arg2, op1_noref_path;
|
|
ir_ref if_op1_ref = IR_UNUSED;
|
|
ir_ref if_op1_typed = IR_UNUSED;
|
|
binary_op_type binary_op = get_binary_op(opline->extended_value);
|
|
|
|
ref = jit_ZVAL_ADDR(jit, op1_addr);
|
|
if_op1_ref = jit_if_Z_TYPE_ref(jit, ref, ir_CONST_U8(IS_REFERENCE));
|
|
ir_IF_FALSE(if_op1_ref);
|
|
op1_noref_path = ir_END();
|
|
ir_IF_TRUE(if_op1_ref);
|
|
ref2 = jit_Z_PTR_ref(jit, ref);
|
|
|
|
if_op1_typed = jit_if_TYPED_REF(jit, ref2);
|
|
ir_IF_TRUE_cold(if_op1_typed);
|
|
|
|
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_inv(jit, op2_addr, real_addr, op2_info)) {
|
|
return 0;
|
|
}
|
|
arg2 = jit_ZVAL_ADDR(jit, real_addr);
|
|
} else {
|
|
arg2 = jit_ZVAL_ADDR(jit, op2_addr);
|
|
}
|
|
jit_SET_EX_OPLINE(jit, opline);
|
|
if ((opline->op2_type & (IS_TMP_VAR|IS_VAR))
|
|
&& (op2_info & (MAY_BE_STRING|MAY_BE_ARRAY|MAY_BE_OBJECT|MAY_BE_RESOURCE))) {
|
|
ir_CALL_3(IR_VOID, ir_CONST_FC_FUNC(zend_jit_assign_op_to_typed_ref_tmp),
|
|
ref2, arg2, ir_CONST_FC_FUNC(binary_op));
|
|
} else {
|
|
ir_CALL_3(IR_VOID, ir_CONST_FC_FUNC(zend_jit_assign_op_to_typed_ref),
|
|
ref2, arg2, ir_CONST_FC_FUNC(binary_op));
|
|
}
|
|
zend_jit_check_exception(jit);
|
|
slow_path = ir_END();
|
|
|
|
ir_IF_FALSE(if_op1_typed);
|
|
ref2 = ir_ADD_OFFSET(ref2, offsetof(zend_reference, val));
|
|
|
|
ir_MERGE_WITH(op1_noref_path);
|
|
ref = ir_PHI_2(IR_ADDR, ref2, ref);
|
|
ZEND_ASSERT(op1_addr == op1_def_addr);
|
|
op1_def_addr = op1_addr = ZEND_ADDR_REF_ZVAL(ref);
|
|
}
|
|
|
|
switch (opline->extended_value) {
|
|
case ZEND_ADD:
|
|
case ZEND_SUB:
|
|
case ZEND_MUL:
|
|
case ZEND_DIV:
|
|
result = zend_jit_math_helper(jit, 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_def_addr, op1_def_info, op1_mem_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(jit, 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_def_addr, op1_def_info, op1_mem_info, may_throw);
|
|
break;
|
|
case ZEND_CONCAT:
|
|
result = zend_jit_concat_helper(jit, opline, opline->op1_type, opline->op1, op1_addr, op1_info, opline->op2_type, opline->op2, op2_addr, op2_info, op1_def_addr, may_throw);
|
|
break;
|
|
default:
|
|
ZEND_UNREACHABLE();
|
|
}
|
|
|
|
if (!zend_jit_store_var_if_necessary_ex(jit, opline->op1.var, op1_def_addr, op1_def_info, op1_addr, op1_info)) {
|
|
return 0;
|
|
}
|
|
|
|
if (op1_info & MAY_BE_REF) {
|
|
ir_MERGE_WITH(slow_path);
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
static ir_ref jit_ZVAL_DEREF_ref(zend_jit_ctx *jit, ir_ref ref)
|
|
{
|
|
ir_ref if_ref, ref2;
|
|
|
|
if_ref = ir_IF(ir_EQ(jit_Z_TYPE_ref(jit, ref), ir_CONST_U8(IS_REFERENCE)));
|
|
ir_IF_TRUE(if_ref);
|
|
ref2 = ir_ADD_OFFSET(jit_Z_PTR_ref(jit, ref), offsetof(zend_reference, val));
|
|
ir_MERGE_WITH_EMPTY_FALSE(if_ref);
|
|
return ir_PHI_2(IR_ADDR, ref2, ref);
|
|
}
|
|
|
|
static zend_jit_addr jit_ZVAL_DEREF(zend_jit_ctx *jit, zend_jit_addr addr)
|
|
{
|
|
ir_ref ref = jit_ZVAL_ADDR(jit, addr);
|
|
ref = jit_ZVAL_DEREF_ref(jit, ref);
|
|
return ZEND_ADDR_REF_ZVAL(ref);
|
|
}
|
|
|
|
static ir_ref jit_ZVAL_INDIRECT_DEREF_ref(zend_jit_ctx *jit, ir_ref ref)
|
|
{
|
|
ir_ref if_ref, ref2;
|
|
|
|
if_ref = ir_IF(ir_EQ(jit_Z_TYPE_ref(jit, ref), ir_CONST_U8(IS_INDIRECT)));
|
|
ir_IF_TRUE(if_ref);
|
|
ref2 = jit_Z_PTR_ref(jit, ref);
|
|
ir_MERGE_WITH_EMPTY_FALSE(if_ref);
|
|
return ir_PHI_2(IR_ADDR, ref2, ref);
|
|
}
|
|
|
|
static zend_jit_addr jit_ZVAL_INDIRECT_DEREF(zend_jit_ctx *jit, zend_jit_addr addr)
|
|
{
|
|
ir_ref ref = jit_ZVAL_ADDR(jit, addr);
|
|
ref = jit_ZVAL_INDIRECT_DEREF_ref(jit, ref);
|
|
return ZEND_ADDR_REF_ZVAL(ref);
|
|
}
|
|
|
|
static int zend_jit_simple_assign(zend_jit_ctx *jit,
|
|
const zend_op *opline,
|
|
zend_jit_addr var_addr,
|
|
uint32_t var_info,
|
|
uint32_t var_def_info,
|
|
uint8_t val_type,
|
|
zend_jit_addr val_addr,
|
|
uint32_t val_info,
|
|
zend_jit_addr res_addr,
|
|
bool check_exception)
|
|
{
|
|
ir_ref end_inputs = IR_UNUSED;
|
|
|
|
if (Z_MODE(val_addr) == IS_CONST_ZVAL) {
|
|
zval *zv = Z_ZV(val_addr);
|
|
|
|
if (!res_addr) {
|
|
jit_ZVAL_COPY_CONST(jit,
|
|
var_addr,
|
|
var_info, var_def_info,
|
|
zv, 1);
|
|
} else {
|
|
jit_ZVAL_COPY_CONST(jit,
|
|
var_addr,
|
|
var_info, var_def_info,
|
|
zv, 1);
|
|
jit_ZVAL_COPY_CONST(jit,
|
|
res_addr,
|
|
-1, var_def_info,
|
|
zv, 1);
|
|
}
|
|
} else {
|
|
if (val_info & MAY_BE_UNDEF) {
|
|
ir_ref if_def, ret;
|
|
|
|
if_def = jit_if_not_Z_TYPE(jit, val_addr, IS_UNDEF);
|
|
ir_IF_FALSE_cold(if_def);
|
|
|
|
jit_set_Z_TYPE_INFO(jit, var_addr, IS_NULL);
|
|
if (res_addr) {
|
|
jit_set_Z_TYPE_INFO(jit, res_addr, IS_NULL);
|
|
}
|
|
jit_SET_EX_OPLINE(jit, opline);
|
|
|
|
ZEND_ASSERT(Z_MODE(val_addr) == IS_MEM_ZVAL);
|
|
// zend_error_unchecked(E_WARNING, "Undefined variable $%S", CV_DEF_OF(EX_VAR_TO_NUM(opline->op1.var)));
|
|
ret = ir_CALL_1(IR_I32, ir_CONST_FC_FUNC(zend_jit_undefined_op_helper), ir_CONST_U32(Z_OFFSET(val_addr)));
|
|
|
|
if (check_exception) {
|
|
ir_GUARD(ret, jit_STUB_ADDR(jit, jit_stub_exception_handler_undef));
|
|
}
|
|
|
|
ir_END_list(end_inputs);
|
|
ir_IF_TRUE(if_def);
|
|
}
|
|
if (val_info & MAY_BE_REF) {
|
|
if (val_type == IS_CV) {
|
|
ir_ref ref = jit_ZVAL_ADDR(jit, val_addr);
|
|
ref = jit_ZVAL_DEREF_ref(jit, ref);
|
|
val_addr = ZEND_ADDR_REF_ZVAL(ref);
|
|
} else {
|
|
ir_ref ref, type, if_ref, ref2, refcount, if_not_zero;
|
|
|
|
ref = jit_ZVAL_ADDR(jit, val_addr);
|
|
type = jit_Z_TYPE_ref(jit, ref);
|
|
if_ref = ir_IF(ir_EQ(type, ir_CONST_U8(IS_REFERENCE)));
|
|
|
|
ir_IF_TRUE_cold(if_ref);
|
|
ref = jit_Z_PTR_ref(jit, ref);
|
|
ref2 = ir_ADD_OFFSET(ref, offsetof(zend_reference, val));
|
|
if (!res_addr) {
|
|
jit_ZVAL_COPY(jit,
|
|
var_addr,
|
|
var_info,
|
|
ZEND_ADDR_REF_ZVAL(ref2), val_info, 1);
|
|
} else {
|
|
jit_ZVAL_COPY_2(jit,
|
|
res_addr,
|
|
var_addr,
|
|
var_info,
|
|
ZEND_ADDR_REF_ZVAL(ref2), val_info, 2);
|
|
}
|
|
|
|
refcount = jit_GC_DELREF(jit, ref);
|
|
if_not_zero = ir_IF(refcount);
|
|
ir_IF_FALSE(if_not_zero);
|
|
// TODO: instead of dtor() call and ADDREF above, we may call efree() and move addref at "true" path ???
|
|
// This is related to GH-10168 (keep this before GH-10168 is completely closed)
|
|
// jit_EFREE(jit, ref, sizeof(zend_reference), NULL, NULL);
|
|
jit_ZVAL_DTOR(jit, ref, val_info, opline);
|
|
ir_END_list(end_inputs);
|
|
ir_IF_TRUE(if_not_zero);
|
|
ir_END_list(end_inputs);
|
|
|
|
ir_IF_FALSE(if_ref);
|
|
}
|
|
}
|
|
|
|
if (!res_addr) {
|
|
jit_ZVAL_COPY(jit,
|
|
var_addr,
|
|
var_info,
|
|
val_addr, val_info, val_type == IS_CV);
|
|
} else {
|
|
jit_ZVAL_COPY_2(jit,
|
|
res_addr,
|
|
var_addr,
|
|
var_info,
|
|
val_addr, val_info, val_type == IS_CV ? 2 : 1);
|
|
}
|
|
}
|
|
|
|
if (end_inputs) {
|
|
ir_END_list(end_inputs);
|
|
ir_MERGE_list(end_inputs);
|
|
}
|
|
|
|
return 1;
|
|
}
|
|
|
|
static int zend_jit_assign_to_variable_call(zend_jit_ctx *jit,
|
|
const zend_op *opline,
|
|
zend_jit_addr __var_use_addr,
|
|
zend_jit_addr var_addr,
|
|
uint32_t __var_info,
|
|
uint32_t __var_def_info,
|
|
uint8_t val_type,
|
|
zend_jit_addr val_addr,
|
|
uint32_t val_info,
|
|
zend_jit_addr __res_addr,
|
|
bool __check_exception)
|
|
{
|
|
jit_stub_id func;
|
|
ir_ref undef_path = IR_UNUSED;
|
|
|
|
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;
|
|
}
|
|
|
|
jit_guard_not_Z_TYPE(jit, val_addr, IS_UNDEF, exit_addr);
|
|
} else {
|
|
ir_ref if_def;
|
|
|
|
ZEND_ASSERT(Z_MODE(val_addr) == IS_MEM_ZVAL && Z_REG(val_addr) == ZREG_FP);
|
|
if_def = ir_IF(jit_Z_TYPE(jit, val_addr));
|
|
ir_IF_FALSE_cold(if_def);
|
|
jit_SET_EX_OPLINE(jit, opline);
|
|
ir_CALL_1(IR_VOID, ir_CONST_FC_FUNC(zend_jit_undefined_op_helper), ir_CONST_U32(Z_OFFSET(val_addr)));
|
|
|
|
ir_CALL_2(IR_VOID, jit_STUB_FUNC_ADDR(jit, jit_stub_assign_const, IR_FASTCALL_FUNC),
|
|
jit_ZVAL_ADDR(jit, var_addr),
|
|
jit_EG(uninitialized_zval));
|
|
|
|
undef_path = ir_END();
|
|
ir_IF_TRUE(if_def);
|
|
}
|
|
}
|
|
|
|
if (!(val_info & (MAY_BE_STRING|MAY_BE_ARRAY|MAY_BE_OBJECT|MAY_BE_RESOURCE|MAY_BE_REF))) {
|
|
func = jit_stub_assign_tmp;
|
|
} else if (val_type == IS_CONST) {
|
|
func = jit_stub_assign_const;
|
|
} else if (val_type == IS_TMP_VAR) {
|
|
func = jit_stub_assign_tmp;
|
|
} else if (val_type == IS_VAR) {
|
|
if (!(val_info & MAY_BE_REF)) {
|
|
func = jit_stub_assign_tmp;
|
|
} else {
|
|
func = jit_stub_assign_var;
|
|
}
|
|
} else if (val_type == IS_CV) {
|
|
if (!(val_info & MAY_BE_REF)) {
|
|
func = jit_stub_assign_cv_noref;
|
|
} else {
|
|
func = jit_stub_assign_cv;
|
|
}
|
|
} else {
|
|
ZEND_UNREACHABLE();
|
|
}
|
|
|
|
if (opline) {
|
|
jit_SET_EX_OPLINE(jit, opline);
|
|
}
|
|
|
|
ir_CALL_2(IR_VOID, jit_STUB_FUNC_ADDR(jit, func, IR_FASTCALL_FUNC),
|
|
jit_ZVAL_ADDR(jit, var_addr),
|
|
jit_ZVAL_ADDR(jit, val_addr));
|
|
|
|
if (undef_path) {
|
|
ir_MERGE_WITH(undef_path);
|
|
}
|
|
|
|
return 1;
|
|
}
|
|
|
|
static int zend_jit_assign_to_variable(zend_jit_ctx *jit,
|
|
const zend_op *opline,
|
|
zend_jit_addr var_use_addr,
|
|
zend_jit_addr var_addr,
|
|
uint32_t var_info,
|
|
uint32_t var_def_info,
|
|
uint8_t val_type,
|
|
zend_jit_addr val_addr,
|
|
uint32_t val_info,
|
|
zend_jit_addr res_addr,
|
|
zend_jit_addr ref_addr,
|
|
bool check_exception)
|
|
{
|
|
ir_ref if_refcounted = IR_UNUSED;
|
|
ir_ref simple_inputs = IR_UNUSED;
|
|
bool done = 0;
|
|
zend_jit_addr real_res_addr = 0;
|
|
ir_refs *end_inputs;
|
|
ir_refs *res_inputs;
|
|
|
|
ir_refs_init(end_inputs, 6);
|
|
ir_refs_init(res_inputs, 6);
|
|
|
|
if (Z_MODE(val_addr) == IS_REG && jit->ra[Z_SSA_VAR(val_addr)].ref == IR_NULL) {
|
|
/* Force load */
|
|
zend_jit_use_reg(jit, val_addr);
|
|
}
|
|
|
|
if (Z_MODE(var_addr) == IS_REG) {
|
|
jit->delay_var = Z_SSA_VAR(var_addr);
|
|
jit->delay_refs = res_inputs;
|
|
if (Z_MODE(res_addr) == IS_REG) {
|
|
real_res_addr = res_addr;
|
|
res_addr = 0;
|
|
}
|
|
} else if (Z_MODE(res_addr) == IS_REG) {
|
|
jit->delay_var = Z_SSA_VAR(res_addr);
|
|
jit->delay_refs = res_inputs;
|
|
}
|
|
|
|
if ((var_info & MAY_BE_REF) || ref_addr) {
|
|
ir_ref ref = 0, if_ref = 0, ref2, arg2, if_typed, non_ref_path;
|
|
uintptr_t func;
|
|
|
|
if (!ref_addr) {
|
|
ref = jit_ZVAL_ADDR(jit, var_use_addr);
|
|
if_ref = jit_if_Z_TYPE_ref(jit, ref, ir_CONST_U8(IS_REFERENCE));
|
|
ir_IF_TRUE(if_ref);
|
|
ref2 = jit_Z_PTR_ref(jit, ref);
|
|
} else {
|
|
ref2 = jit_ZVAL_ADDR(jit, ref_addr);
|
|
}
|
|
if_typed = jit_if_TYPED_REF(jit, ref2);
|
|
ir_IF_TRUE_cold(if_typed);
|
|
jit_SET_EX_OPLINE(jit, opline);
|
|
if (Z_MODE(val_addr) == IS_REG) {
|
|
zend_jit_addr real_addr;
|
|
|
|
if (opline->opcode == ZEND_ASSIGN_DIM || opline->opcode == ZEND_ASSIGN_OBJ) {
|
|
real_addr = ZEND_ADDR_MEM_ZVAL(ZREG_FP, (opline+1)->op1.var);
|
|
} else {
|
|
ZEND_ASSERT(opline->opcode == ZEND_ASSIGN);
|
|
real_addr = ZEND_ADDR_MEM_ZVAL(ZREG_FP, opline->op2.var);
|
|
}
|
|
if (!zend_jit_spill_store_inv(jit, val_addr, real_addr, val_info)) {
|
|
return 0;
|
|
}
|
|
arg2 = jit_ZVAL_ADDR(jit, real_addr);
|
|
} else {
|
|
arg2 = jit_ZVAL_ADDR(jit, val_addr);
|
|
}
|
|
if (!res_addr) {
|
|
if (val_type == IS_CONST) {
|
|
func = (uintptr_t)zend_jit_assign_const_to_typed_ref;
|
|
} else if (val_type == IS_TMP_VAR) {
|
|
func = (uintptr_t)zend_jit_assign_tmp_to_typed_ref;
|
|
} else if (val_type == IS_VAR) {
|
|
func = (uintptr_t)zend_jit_assign_var_to_typed_ref;
|
|
} else if (val_type == IS_CV) {
|
|
func = (uintptr_t)zend_jit_assign_cv_to_typed_ref;
|
|
} else {
|
|
ZEND_UNREACHABLE();
|
|
}
|
|
ir_CALL_2(IR_ADDR, ir_CONST_FC_FUNC(func), ref2, arg2);
|
|
} else {
|
|
if (val_type == IS_CONST) {
|
|
func = (uintptr_t)zend_jit_assign_const_to_typed_ref2;
|
|
} else if (val_type == IS_TMP_VAR) {
|
|
func = (uintptr_t)zend_jit_assign_tmp_to_typed_ref2;
|
|
} else if (val_type == IS_VAR) {
|
|
func = (uintptr_t)zend_jit_assign_var_to_typed_ref2;
|
|
} else if (val_type == IS_CV) {
|
|
func = (uintptr_t)zend_jit_assign_cv_to_typed_ref2;
|
|
} else {
|
|
ZEND_UNREACHABLE();
|
|
}
|
|
ir_CALL_3(IR_ADDR, ir_CONST_FC_FUNC(func), ref2, arg2, jit_ZVAL_ADDR(jit, res_addr));
|
|
}
|
|
if (check_exception) {
|
|
zend_jit_check_exception(jit);
|
|
}
|
|
ir_refs_add(end_inputs, ir_END());
|
|
|
|
if (!ref_addr) {
|
|
ir_IF_FALSE(if_ref);
|
|
non_ref_path = ir_END();
|
|
ir_IF_FALSE(if_typed);
|
|
ref2 = ir_ADD_OFFSET(ref2, offsetof(zend_reference, val));
|
|
ir_MERGE_WITH(non_ref_path);
|
|
ref = ir_PHI_2(IR_ADDR, ref2, ref);
|
|
var_addr = var_use_addr = ZEND_ADDR_REF_ZVAL(ref);
|
|
} else {
|
|
ir_IF_FALSE(if_typed);
|
|
}
|
|
}
|
|
|
|
if (var_info & (MAY_BE_STRING|MAY_BE_ARRAY|MAY_BE_OBJECT|MAY_BE_RESOURCE)) {
|
|
ir_ref ref, counter, if_not_zero;
|
|
|
|
if (var_info & ((MAY_BE_ANY|MAY_BE_UNDEF)-(MAY_BE_OBJECT|MAY_BE_RESOURCE))) {
|
|
if_refcounted = jit_if_REFCOUNTED(jit, var_use_addr);
|
|
ir_IF_FALSE(if_refcounted);
|
|
ir_END_list(simple_inputs);
|
|
ir_IF_TRUE_cold(if_refcounted);
|
|
} else if (RC_MAY_BE_1(var_info)) {
|
|
done = 1;
|
|
}
|
|
ref = jit_Z_PTR(jit, var_use_addr);
|
|
if (RC_MAY_BE_1(var_info)) {
|
|
if (!zend_jit_simple_assign(jit, opline, var_addr, var_info, var_def_info, val_type, val_addr, val_info, res_addr, 0)) {
|
|
return 0;
|
|
}
|
|
counter = jit_GC_DELREF(jit, ref);
|
|
|
|
if_not_zero = ir_IF(counter);
|
|
ir_IF_FALSE(if_not_zero);
|
|
jit_ZVAL_DTOR(jit, ref, var_info, opline);
|
|
if (check_exception) {
|
|
zend_jit_check_exception(jit);
|
|
}
|
|
ir_refs_add(end_inputs, ir_END());
|
|
ir_IF_TRUE(if_not_zero);
|
|
if (RC_MAY_BE_N(var_info) && (var_info & (MAY_BE_ARRAY|MAY_BE_OBJECT)) != 0) {
|
|
ir_ref if_may_leak = jit_if_GC_MAY_NOT_LEAK(jit, ref);
|
|
ir_IF_FALSE(if_may_leak);
|
|
ir_CALL_1(IR_VOID, ir_CONST_FC_FUNC(gc_possible_root), ref);
|
|
|
|
if (Z_MODE(var_addr) == IS_REG || Z_MODE(res_addr) == IS_REG) {
|
|
ZEND_ASSERT(jit->delay_refs == res_inputs);
|
|
ZEND_ASSERT(res_inputs->count > 0);
|
|
ir_refs_add(res_inputs, res_inputs->refs[res_inputs->count - 1]);
|
|
}
|
|
if (check_exception && (val_info & MAY_BE_UNDEF)) {
|
|
zend_jit_check_exception(jit);
|
|
}
|
|
ir_refs_add(end_inputs, ir_END());
|
|
ir_IF_TRUE(if_may_leak);
|
|
}
|
|
if (Z_MODE(var_addr) == IS_REG || Z_MODE(res_addr) == IS_REG) {
|
|
ZEND_ASSERT(jit->delay_refs == res_inputs);
|
|
ZEND_ASSERT(res_inputs->count > 0);
|
|
ir_refs_add(res_inputs, res_inputs->refs[res_inputs->count - 1]);
|
|
}
|
|
if (check_exception && (val_info & MAY_BE_UNDEF)) {
|
|
zend_jit_check_exception(jit);
|
|
}
|
|
ir_refs_add(end_inputs, ir_END());
|
|
} else /* if (RC_MAY_BE_N(var_info)) */ {
|
|
jit_GC_DELREF(jit, ref);
|
|
if (var_info & (MAY_BE_ARRAY|MAY_BE_OBJECT)) {
|
|
ir_ref if_may_leak = jit_if_GC_MAY_NOT_LEAK(jit, ref);
|
|
ir_IF_FALSE(if_may_leak);
|
|
ir_CALL_1(IR_VOID, ir_CONST_FC_FUNC(gc_possible_root), ref);
|
|
ir_END_list(simple_inputs);
|
|
ir_IF_TRUE(if_may_leak);
|
|
}
|
|
ir_END_list(simple_inputs);
|
|
}
|
|
}
|
|
|
|
if (simple_inputs) {
|
|
ir_MERGE_list(simple_inputs);
|
|
}
|
|
|
|
if (!done) {
|
|
if (!zend_jit_simple_assign(jit, opline, var_addr, var_info, var_def_info, val_type, val_addr, val_info, res_addr, check_exception)) {
|
|
return 0;
|
|
}
|
|
if (end_inputs->count) {
|
|
ir_refs_add(end_inputs, ir_END());
|
|
}
|
|
}
|
|
|
|
if (end_inputs->count) {
|
|
ir_MERGE_N(end_inputs->count, end_inputs->refs);
|
|
}
|
|
|
|
if (Z_MODE(var_addr) == IS_REG || Z_MODE(res_addr) == IS_REG) {
|
|
ir_ref phi;
|
|
|
|
ZEND_ASSERT(jit->delay_refs == res_inputs);
|
|
ZEND_ASSERT(end_inputs->count == res_inputs->count || (end_inputs->count == 0 && res_inputs->count == 1));
|
|
jit->delay_var = -1;
|
|
jit->delay_refs = NULL;
|
|
if (res_inputs->count == 1) {
|
|
phi = res_inputs->refs[0];
|
|
} else {
|
|
phi = ir_PHI_N((var_def_info & MAY_BE_LONG & MAY_BE_LONG) ? IR_LONG : IR_DOUBLE,
|
|
res_inputs->count, res_inputs->refs);
|
|
}
|
|
if (Z_MODE(var_addr) == IS_REG) {
|
|
if ((var_info & (MAY_BE_REF|MAY_BE_STRING|MAY_BE_ARRAY|MAY_BE_OBJECT|MAY_BE_RESOURCE)) || ref_addr) {
|
|
phi = ir_emit2(&jit->ctx, IR_OPT(IR_COPY, jit->ctx.ir_base[phi].type), phi, 1);
|
|
}
|
|
zend_jit_def_reg(jit, var_addr, phi);
|
|
if (real_res_addr) {
|
|
if (var_def_info & MAY_BE_LONG) {
|
|
jit_set_Z_LVAL(jit, real_res_addr, jit_Z_LVAL(jit, var_addr));
|
|
} else {
|
|
jit_set_Z_DVAL(jit, real_res_addr, jit_Z_DVAL(jit, var_addr));
|
|
}
|
|
}
|
|
} else {
|
|
zend_jit_def_reg(jit, res_addr, phi);
|
|
}
|
|
}
|
|
|
|
return 1;
|
|
}
|
|
|
|
static int zend_jit_qm_assign(zend_jit_ctx *jit, 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(jit, 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(jit, opline, res_addr, res_use_info, res_info, opline->op1_type, op1_addr, op1_info, 0, 1)) {
|
|
return 0;
|
|
}
|
|
if (!zend_jit_store_var_if_necessary(jit, opline->result.var, res_addr, res_info)) {
|
|
return 0;
|
|
}
|
|
return 1;
|
|
}
|
|
|
|
static int zend_jit_assign(zend_jit_ctx *jit,
|
|
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,
|
|
zend_jit_addr ref_addr,
|
|
int may_throw)
|
|
{
|
|
ZEND_ASSERT(opline->op1_type == IS_CV);
|
|
|
|
if (op2_addr != op2_def_addr) {
|
|
if (!zend_jit_update_regs(jit, 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(jit, opline, op1_use_addr, op1_addr, op1_info, op1_def_info,
|
|
opline->op2_type, op2_addr, op2_info, res_addr, ref_addr, may_throw)) {
|
|
return 0;
|
|
}
|
|
if (Z_MODE(op1_addr) == IS_REG) {
|
|
if (Z_STORE(op1_addr)) {
|
|
if (!zend_jit_store_var_if_necessary_ex(jit, opline->op1.var, op1_addr, op1_def_info, op1_use_addr, op1_info)) {
|
|
return 0;
|
|
}
|
|
} else if ((op1_info & (MAY_BE_STRING|MAY_BE_ARRAY|MAY_BE_OBJECT|MAY_BE_RESOURCE))
|
|
&& Z_MODE(op1_use_addr) == IS_MEM_ZVAL
|
|
&& Z_REG(op1_use_addr) == ZREG_FP
|
|
&& EX_VAR_TO_NUM(Z_OFFSET(op1_use_addr)) < jit->current_op_array->last_var) {
|
|
/* We have to update type of CV because it may be captured by exception backtrace or released on RETURN */
|
|
if ((op1_def_info & MAY_BE_ANY) == MAY_BE_LONG) {
|
|
jit_set_Z_TYPE_INFO(jit, op1_use_addr, IS_LONG);
|
|
if (JIT_G(current_frame)) {
|
|
SET_STACK_TYPE(JIT_G(current_frame)->stack, EX_VAR_TO_NUM(Z_OFFSET(op1_use_addr)), IS_LONG, 1);
|
|
}
|
|
} else if ((op1_def_info & MAY_BE_ANY) == MAY_BE_DOUBLE) {
|
|
jit_set_Z_TYPE_INFO(jit, op1_use_addr, IS_DOUBLE);
|
|
if (JIT_G(current_frame)) {
|
|
SET_STACK_TYPE(JIT_G(current_frame)->stack, EX_VAR_TO_NUM(Z_OFFSET(op1_use_addr)), IS_DOUBLE, 1);
|
|
}
|
|
} else {
|
|
ZEND_UNREACHABLE();
|
|
}
|
|
}
|
|
}
|
|
if (opline->result_type != IS_UNUSED) {
|
|
if (!zend_jit_store_var_if_necessary(jit, opline->result.var, res_addr, res_info)) {
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
return 1;
|
|
}
|
|
|
|
static ir_op zend_jit_cmp_op(const zend_op *opline)
|
|
{
|
|
ir_op op;
|
|
|
|
switch (opline->opcode) {
|
|
case ZEND_IS_EQUAL:
|
|
case ZEND_IS_IDENTICAL:
|
|
case ZEND_CASE:
|
|
case ZEND_CASE_STRICT:
|
|
op = IR_EQ;
|
|
break;
|
|
case ZEND_IS_NOT_EQUAL:
|
|
case ZEND_IS_NOT_IDENTICAL:
|
|
op = IR_NE;
|
|
break;
|
|
case ZEND_IS_SMALLER:
|
|
op = IR_LT;
|
|
break;
|
|
case ZEND_IS_SMALLER_OR_EQUAL:
|
|
op = IR_LE;
|
|
break;
|
|
default:
|
|
ZEND_UNREACHABLE();
|
|
}
|
|
return op;
|
|
}
|
|
|
|
static ir_ref zend_jit_cmp_long_long(zend_jit_ctx *jit,
|
|
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,
|
|
uint8_t smart_branch_opcode,
|
|
uint32_t target_label,
|
|
uint32_t target_label2,
|
|
const void *exit_addr,
|
|
bool skip_comparison)
|
|
{
|
|
ir_ref ref;
|
|
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) {
|
|
jit_set_Z_TYPE_INFO(jit, 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) {
|
|
return jit_IF_ex(jit, IR_FALSE, result ? target_label : target_label2);
|
|
} else if (smart_branch_opcode == ZEND_JMPNZ ||
|
|
smart_branch_opcode == ZEND_JMPNZ_EX) {
|
|
return jit_IF_ex(jit, IR_TRUE, result ? target_label : target_label2);
|
|
} else {
|
|
ZEND_UNREACHABLE();
|
|
}
|
|
}
|
|
if (opline->opcode != ZEND_IS_IDENTICAL
|
|
&& opline->opcode != ZEND_IS_NOT_IDENTICAL
|
|
&& opline->opcode != ZEND_CASE_STRICT) {
|
|
return ir_END();
|
|
} else {
|
|
return IR_NULL; /* success */
|
|
}
|
|
}
|
|
|
|
ref = ir_CMP_OP(zend_jit_cmp_op(opline), jit_Z_LVAL(jit, op1_addr), jit_Z_LVAL(jit, op2_addr));
|
|
|
|
if (!smart_branch_opcode || smart_branch_opcode == ZEND_JMPNZ_EX || smart_branch_opcode == ZEND_JMPZ_EX) {
|
|
jit_set_Z_TYPE_INFO_ref(jit, jit_ZVAL_ADDR(jit, res_addr),
|
|
ir_ADD_U32(ir_ZEXT_U32(ref), ir_CONST_U32(IS_FALSE)));
|
|
}
|
|
if (exit_addr) {
|
|
if (smart_branch_opcode == ZEND_JMPZ || smart_branch_opcode == ZEND_JMPZ_EX) {
|
|
if (opline->opcode != ZEND_IS_NOT_IDENTICAL) {
|
|
ir_GUARD(ref, ir_CONST_ADDR(exit_addr));
|
|
} else {
|
|
ir_GUARD_NOT(ref, ir_CONST_ADDR(exit_addr));
|
|
}
|
|
} else {
|
|
if (opline->opcode != ZEND_IS_NOT_IDENTICAL) {
|
|
ir_GUARD_NOT(ref, ir_CONST_ADDR(exit_addr));
|
|
} else {
|
|
ir_GUARD(ref, ir_CONST_ADDR(exit_addr));
|
|
}
|
|
}
|
|
} else if (smart_branch_opcode) {
|
|
return jit_IF_ex(jit, ref,
|
|
(smart_branch_opcode == ZEND_JMPZ || smart_branch_opcode == ZEND_JMPZ_EX) ? target_label2 : target_label);
|
|
}
|
|
|
|
if (opline->opcode != ZEND_IS_IDENTICAL
|
|
&& opline->opcode != ZEND_IS_NOT_IDENTICAL
|
|
&& opline->opcode != ZEND_CASE_STRICT) {
|
|
return ir_END();
|
|
} else {
|
|
return IR_NULL; /* success */
|
|
}
|
|
}
|
|
|
|
static ir_ref zend_jit_cmp_long_double(zend_jit_ctx *jit, const zend_op *opline, zend_jit_addr op1_addr, zend_jit_addr op2_addr, zend_jit_addr res_addr, uint8_t smart_branch_opcode, uint32_t target_label, uint32_t target_label2, const void *exit_addr)
|
|
{
|
|
ir_ref ref = ir_CMP_OP(zend_jit_cmp_op(opline), ir_INT2D(jit_Z_LVAL(jit, op1_addr)), jit_Z_DVAL(jit, op2_addr));
|
|
|
|
if (!smart_branch_opcode || smart_branch_opcode == ZEND_JMPNZ_EX || smart_branch_opcode == ZEND_JMPZ_EX) {
|
|
jit_set_Z_TYPE_INFO_ref(jit, jit_ZVAL_ADDR(jit, res_addr),
|
|
ir_ADD_U32(ir_ZEXT_U32(ref), ir_CONST_U32(IS_FALSE)));
|
|
}
|
|
if (exit_addr) {
|
|
if (smart_branch_opcode == ZEND_JMPZ || smart_branch_opcode == ZEND_JMPZ_EX) {
|
|
ir_GUARD(ref, ir_CONST_ADDR(exit_addr));
|
|
} else {
|
|
ir_GUARD_NOT(ref, ir_CONST_ADDR(exit_addr));
|
|
}
|
|
} else if (smart_branch_opcode) {
|
|
return jit_IF_ex(jit, ref,
|
|
(smart_branch_opcode == ZEND_JMPZ || smart_branch_opcode == ZEND_JMPZ_EX) ? target_label2 : target_label);
|
|
}
|
|
return ir_END();
|
|
}
|
|
|
|
static ir_ref zend_jit_cmp_double_long(zend_jit_ctx *jit, const zend_op *opline, zend_jit_addr op1_addr, zend_jit_addr op2_addr, zend_jit_addr res_addr, uint8_t smart_branch_opcode, uint32_t target_label, uint32_t target_label2, const void *exit_addr)
|
|
{
|
|
ir_ref ref = ir_CMP_OP(zend_jit_cmp_op(opline), jit_Z_DVAL(jit, op1_addr), ir_INT2D(jit_Z_LVAL(jit, op2_addr)));
|
|
|
|
if (!smart_branch_opcode || smart_branch_opcode == ZEND_JMPNZ_EX || smart_branch_opcode == ZEND_JMPZ_EX) {
|
|
jit_set_Z_TYPE_INFO_ref(jit, jit_ZVAL_ADDR(jit, res_addr),
|
|
ir_ADD_U32(ir_ZEXT_U32(ref), ir_CONST_U32(IS_FALSE)));
|
|
}
|
|
if (exit_addr) {
|
|
if (smart_branch_opcode == ZEND_JMPZ || smart_branch_opcode == ZEND_JMPZ_EX) {
|
|
ir_GUARD(ref, ir_CONST_ADDR(exit_addr));
|
|
} else {
|
|
ir_GUARD_NOT(ref, ir_CONST_ADDR(exit_addr));
|
|
}
|
|
} else if (smart_branch_opcode) {
|
|
return jit_IF_ex(jit, ref,
|
|
(smart_branch_opcode == ZEND_JMPZ || smart_branch_opcode == ZEND_JMPZ_EX) ? target_label2 : target_label);
|
|
}
|
|
return ir_END();
|
|
}
|
|
|
|
static ir_ref zend_jit_cmp_double_double(zend_jit_ctx *jit, const zend_op *opline, zend_jit_addr op1_addr, zend_jit_addr op2_addr, zend_jit_addr res_addr, uint8_t smart_branch_opcode, uint32_t target_label, uint32_t target_label2, const void *exit_addr)
|
|
{
|
|
ir_ref ref = ir_CMP_OP(zend_jit_cmp_op(opline), jit_Z_DVAL(jit, op1_addr), jit_Z_DVAL(jit, op2_addr));
|
|
|
|
if (!smart_branch_opcode || smart_branch_opcode == ZEND_JMPNZ_EX || smart_branch_opcode == ZEND_JMPZ_EX) {
|
|
jit_set_Z_TYPE_INFO_ref(jit, jit_ZVAL_ADDR(jit, res_addr),
|
|
ir_ADD_U32(ir_ZEXT_U32(ref), ir_CONST_U32(IS_FALSE)));
|
|
}
|
|
if (exit_addr) {
|
|
if (smart_branch_opcode == ZEND_JMPZ || smart_branch_opcode == ZEND_JMPZ_EX) {
|
|
if (opline->opcode != ZEND_IS_NOT_IDENTICAL) {
|
|
ir_GUARD(ref, ir_CONST_ADDR(exit_addr));
|
|
} else {
|
|
ir_GUARD_NOT(ref, ir_CONST_ADDR(exit_addr));
|
|
}
|
|
} else {
|
|
if (opline->opcode != ZEND_IS_NOT_IDENTICAL) {
|
|
ir_GUARD_NOT(ref, ir_CONST_ADDR(exit_addr));
|
|
} else {
|
|
ir_GUARD(ref, ir_CONST_ADDR(exit_addr));
|
|
}
|
|
}
|
|
} else if (smart_branch_opcode) {
|
|
return jit_IF_ex(jit, ref,
|
|
(smart_branch_opcode == ZEND_JMPZ || smart_branch_opcode == ZEND_JMPZ_EX) ? target_label2 : target_label);
|
|
}
|
|
if (opline->opcode != ZEND_IS_IDENTICAL
|
|
&& opline->opcode != ZEND_IS_NOT_IDENTICAL
|
|
&& opline->opcode != ZEND_CASE_STRICT) {
|
|
return ir_END();
|
|
} else {
|
|
return IR_NULL; /* success */
|
|
}
|
|
}
|
|
|
|
static ir_ref zend_jit_cmp_slow(zend_jit_ctx *jit, ir_ref ref, const zend_op *opline, zend_jit_addr res_addr, uint8_t smart_branch_opcode, uint32_t target_label, uint32_t target_label2, const void *exit_addr)
|
|
{
|
|
ref = ir_CMP_OP(zend_jit_cmp_op(opline), ref, ir_CONST_I32(0));
|
|
|
|
if (!smart_branch_opcode || smart_branch_opcode == ZEND_JMPNZ_EX || smart_branch_opcode == ZEND_JMPZ_EX) {
|
|
jit_set_Z_TYPE_INFO_ref(jit, jit_ZVAL_ADDR(jit, res_addr),
|
|
ir_ADD_U32(ir_ZEXT_U32(ref), ir_CONST_U32(IS_FALSE)));
|
|
}
|
|
if (exit_addr) {
|
|
if (smart_branch_opcode == ZEND_JMPZ || smart_branch_opcode == ZEND_JMPZ_EX) {
|
|
ir_GUARD(ref, ir_CONST_ADDR(exit_addr));
|
|
} else {
|
|
ir_GUARD_NOT(ref, ir_CONST_ADDR(exit_addr));
|
|
}
|
|
} else if (smart_branch_opcode) {
|
|
return jit_IF_ex(jit, ref,
|
|
(smart_branch_opcode == ZEND_JMPZ || smart_branch_opcode == ZEND_JMPZ_EX) ? target_label2 : target_label);
|
|
}
|
|
|
|
return ir_END();
|
|
}
|
|
|
|
static int zend_jit_cmp(zend_jit_ctx *jit,
|
|
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,
|
|
uint8_t smart_branch_opcode,
|
|
uint32_t target_label,
|
|
uint32_t target_label2,
|
|
const void *exit_addr,
|
|
bool skip_comparison)
|
|
{
|
|
ir_ref ref = IR_UNUSED;
|
|
ir_ref if_op1_long = IR_UNUSED;
|
|
ir_ref if_op1_double = IR_UNUSED;
|
|
ir_ref if_op2_double = IR_UNUSED;
|
|
ir_ref if_op1_long_op2_long = IR_UNUSED;
|
|
ir_ref if_op1_long_op2_double = IR_UNUSED;
|
|
ir_ref if_op1_double_op2_double = IR_UNUSED;
|
|
ir_ref if_op1_double_op2_long = IR_UNUSED;
|
|
ir_ref slow_inputs = IR_UNUSED;
|
|
bool same_ops = zend_jit_same_addr(op1_addr, op2_addr);
|
|
bool 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))));
|
|
ir_refs *end_inputs;
|
|
|
|
ir_refs_init(end_inputs, 8);
|
|
|
|
if (Z_MODE(op1_addr) == IS_REG) {
|
|
if (!has_concrete_type(op2_info & MAY_BE_ANY) && jit->ra[Z_SSA_VAR(op1_addr)].ref == IR_NULL) {
|
|
/* Force load */
|
|
zend_jit_use_reg(jit, op1_addr);
|
|
}
|
|
} else if (Z_MODE(op2_addr) == IS_REG) {
|
|
if (!has_concrete_type(op1_info & MAY_BE_ANY) && jit->ra[Z_SSA_VAR(op2_addr)].ref == IR_NULL) {
|
|
/* Force load */
|
|
zend_jit_use_reg(jit, op2_addr);
|
|
}
|
|
}
|
|
|
|
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_long = jit_if_Z_TYPE(jit, op1_addr, IS_LONG);
|
|
ir_IF_TRUE(if_op1_long);
|
|
}
|
|
if (!same_ops && (op2_info & ((MAY_BE_ANY|MAY_BE_UNDEF)-MAY_BE_LONG))) {
|
|
if_op1_long_op2_long = jit_if_Z_TYPE(jit, op2_addr, IS_LONG);
|
|
ir_IF_FALSE_cold(if_op1_long_op2_long);
|
|
if (op2_info & MAY_BE_DOUBLE) {
|
|
if (op2_info & ((MAY_BE_ANY|MAY_BE_UNDEF)-(MAY_BE_LONG|MAY_BE_DOUBLE))) {
|
|
if_op1_long_op2_double = jit_if_Z_TYPE(jit, op2_addr, IS_DOUBLE);
|
|
ir_IF_FALSE_cold(if_op1_long_op2_double);
|
|
ir_END_list(slow_inputs);
|
|
ir_IF_TRUE(if_op1_long_op2_double);
|
|
}
|
|
ref = zend_jit_cmp_long_double(jit, opline, op1_addr, op2_addr, res_addr, smart_branch_opcode, target_label, target_label2, exit_addr);
|
|
if (!ref) {
|
|
return 0;
|
|
}
|
|
ir_refs_add(end_inputs, ref);
|
|
} else {
|
|
ir_END_list(slow_inputs);
|
|
}
|
|
ir_IF_TRUE(if_op1_long_op2_long);
|
|
}
|
|
ref = zend_jit_cmp_long_long(jit, opline, op1_range, op1_addr, op2_range, op2_addr, res_addr, smart_branch_opcode, target_label, target_label2, exit_addr, skip_comparison);
|
|
if (!ref) {
|
|
return 0;
|
|
}
|
|
ir_refs_add(end_inputs, ref);
|
|
|
|
if (if_op1_long) {
|
|
ir_IF_FALSE_cold(if_op1_long);
|
|
}
|
|
if (op1_info & MAY_BE_DOUBLE) {
|
|
if (op1_info & ((MAY_BE_ANY|MAY_BE_UNDEF)-(MAY_BE_LONG|MAY_BE_DOUBLE))) {
|
|
if_op1_double = jit_if_Z_TYPE(jit, op1_addr, IS_DOUBLE);
|
|
ir_IF_FALSE_cold(if_op1_double);
|
|
ir_END_list(slow_inputs);
|
|
ir_IF_TRUE(if_op1_double);
|
|
}
|
|
if (op2_info & MAY_BE_DOUBLE) {
|
|
if (!same_ops && (op2_info & ((MAY_BE_ANY|MAY_BE_UNDEF)-MAY_BE_DOUBLE))) {
|
|
if_op1_double_op2_double = jit_if_Z_TYPE(jit, op2_addr, IS_DOUBLE);
|
|
ir_IF_TRUE(if_op1_double_op2_double);
|
|
}
|
|
ref = zend_jit_cmp_double_double(jit, opline, op1_addr, op2_addr, res_addr, smart_branch_opcode, target_label, target_label2, exit_addr);
|
|
if (!ref) {
|
|
return 0;
|
|
}
|
|
ir_refs_add(end_inputs, ref);
|
|
if (if_op1_double_op2_double) {
|
|
ir_IF_FALSE_cold(if_op1_double_op2_double);
|
|
}
|
|
}
|
|
if (!same_ops) {
|
|
if (op2_info & ((MAY_BE_ANY|MAY_BE_UNDEF)-(MAY_BE_LONG|MAY_BE_DOUBLE))) {
|
|
if_op1_double_op2_long = jit_if_Z_TYPE(jit, op2_addr, IS_LONG);
|
|
ir_IF_FALSE_cold(if_op1_double_op2_long);
|
|
ir_END_list(slow_inputs);
|
|
ir_IF_TRUE(if_op1_double_op2_long);
|
|
}
|
|
ref = zend_jit_cmp_double_long(jit, opline, op1_addr, op2_addr, res_addr, smart_branch_opcode, target_label, target_label2, exit_addr);
|
|
if (!ref) {
|
|
return 0;
|
|
}
|
|
ir_refs_add(end_inputs, ref);
|
|
} else if (if_op1_double_op2_double) {
|
|
ir_END_list(slow_inputs);
|
|
}
|
|
} else if (if_op1_long) {
|
|
ir_END_list(slow_inputs);
|
|
}
|
|
} 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_op1_double = jit_if_Z_TYPE(jit, op1_addr, IS_DOUBLE);
|
|
ir_IF_FALSE_cold(if_op1_double);
|
|
ir_END_list(slow_inputs);
|
|
ir_IF_TRUE(if_op1_double);
|
|
}
|
|
if (op2_info & MAY_BE_DOUBLE) {
|
|
if (!same_ops && (op2_info & ((MAY_BE_ANY|MAY_BE_UNDEF)-MAY_BE_DOUBLE))) {
|
|
if_op1_double_op2_double = jit_if_Z_TYPE(jit, op2_addr, IS_DOUBLE);
|
|
ir_IF_TRUE(if_op1_double_op2_double);
|
|
}
|
|
ref = zend_jit_cmp_double_double(jit, opline, op1_addr, op2_addr, res_addr, smart_branch_opcode, target_label, target_label2, exit_addr);
|
|
if (!ref) {
|
|
return 0;
|
|
}
|
|
ir_refs_add(end_inputs, ref);
|
|
if (if_op1_double_op2_double) {
|
|
ir_IF_FALSE_cold(if_op1_double_op2_double);
|
|
}
|
|
}
|
|
if (!same_ops && (op2_info & MAY_BE_LONG)) {
|
|
if (op2_info & ((MAY_BE_ANY|MAY_BE_UNDEF)-(MAY_BE_DOUBLE|MAY_BE_LONG))) {
|
|
if_op1_double_op2_long = jit_if_Z_TYPE(jit, op2_addr, IS_LONG);
|
|
ir_IF_FALSE_cold(if_op1_double_op2_long);
|
|
ir_END_list(slow_inputs);
|
|
ir_IF_TRUE(if_op1_double_op2_long);
|
|
}
|
|
ref = zend_jit_cmp_double_long(jit, opline, op1_addr, op2_addr, res_addr, smart_branch_opcode, target_label, target_label2, exit_addr);
|
|
if (!ref) {
|
|
return 0;
|
|
}
|
|
ir_refs_add(end_inputs, ref);
|
|
} else if (if_op1_double_op2_double) {
|
|
ir_END_list(slow_inputs);
|
|
}
|
|
} 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_op2_double = jit_if_Z_TYPE(jit, op2_addr, IS_DOUBLE);
|
|
ir_IF_FALSE_cold(if_op2_double);
|
|
ir_END_list(slow_inputs);
|
|
ir_IF_TRUE(if_op2_double);
|
|
}
|
|
if (op1_info & MAY_BE_DOUBLE) {
|
|
if (!same_ops && (op1_info & ((MAY_BE_ANY|MAY_BE_UNDEF)-MAY_BE_DOUBLE))) {
|
|
if_op1_double_op2_double = jit_if_Z_TYPE(jit, op1_addr, IS_DOUBLE);
|
|
ir_IF_TRUE(if_op1_double_op2_double);
|
|
}
|
|
ref = zend_jit_cmp_double_double(jit, opline, op1_addr, op2_addr, res_addr, smart_branch_opcode, target_label, target_label2, exit_addr);
|
|
if (!ref) {
|
|
return 0;
|
|
}
|
|
ir_refs_add(end_inputs, ref);
|
|
if (if_op1_double_op2_double) {
|
|
ir_IF_FALSE_cold(if_op1_double_op2_double);
|
|
}
|
|
}
|
|
if (!same_ops && (op1_info & MAY_BE_LONG)) {
|
|
if (op1_info & ((MAY_BE_ANY|MAY_BE_UNDEF)-(MAY_BE_DOUBLE|MAY_BE_LONG))) {
|
|
if_op1_long_op2_double = jit_if_Z_TYPE(jit, op1_addr, IS_LONG);
|
|
ir_IF_FALSE_cold(if_op1_long_op2_double);
|
|
ir_END_list(slow_inputs);
|
|
ir_IF_TRUE(if_op1_long_op2_double);
|
|
}
|
|
ref = zend_jit_cmp_long_double(jit, opline, op1_addr, op2_addr, res_addr, smart_branch_opcode, target_label, target_label2, exit_addr);
|
|
if (!ref) {
|
|
return 0;
|
|
}
|
|
ir_refs_add(end_inputs, ref);
|
|
} else if (if_op1_double_op2_double) {
|
|
ir_END_list(slow_inputs);
|
|
}
|
|
}
|
|
|
|
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)))) {
|
|
ir_ref op1, op2, ref;
|
|
|
|
if (slow_inputs) {
|
|
ir_MERGE_list(slow_inputs);
|
|
}
|
|
jit_SET_EX_OPLINE(jit, opline);
|
|
|
|
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_inv(jit, op1_addr, real_addr, op1_info)) {
|
|
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_inv(jit, op2_addr, real_addr, op2_info)) {
|
|
return 0;
|
|
}
|
|
op2_addr = real_addr;
|
|
}
|
|
|
|
op1 = jit_ZVAL_ADDR(jit, op1_addr);
|
|
if (opline->op1_type == IS_CV && (op1_info & MAY_BE_UNDEF)) {
|
|
op1 = zend_jit_zval_check_undef(jit, op1, opline->op1.var, NULL, 0);
|
|
}
|
|
op2 = jit_ZVAL_ADDR(jit, op2_addr);
|
|
if (opline->op2_type == IS_CV && (op2_info & MAY_BE_UNDEF)) {
|
|
op2 = zend_jit_zval_check_undef(jit, op2, opline->op2.var, NULL, 0);
|
|
}
|
|
ref = ir_CALL_2(IR_I32, ir_CONST_FC_FUNC(zend_compare), op1, op2);
|
|
if (opline->opcode != ZEND_CASE) {
|
|
jit_FREE_OP(jit, opline->op1_type, opline->op1, op1_info, NULL);
|
|
}
|
|
jit_FREE_OP(jit, opline->op2_type, opline->op2, op2_info, NULL);
|
|
if (may_throw) {
|
|
zend_jit_check_exception_undef_result(jit, opline);
|
|
}
|
|
|
|
ref = zend_jit_cmp_slow(jit, ref, opline, res_addr, smart_branch_opcode, target_label, target_label2, exit_addr);
|
|
if (!ref) {
|
|
return 0;
|
|
}
|
|
ir_refs_add(end_inputs, ref);
|
|
}
|
|
|
|
if (end_inputs->count) {
|
|
uint32_t n = end_inputs->count;
|
|
|
|
if (smart_branch_opcode && !exit_addr) {
|
|
zend_basic_block *bb;
|
|
ir_ref ref;
|
|
uint32_t label = (smart_branch_opcode == ZEND_JMPZ || smart_branch_opcode == ZEND_JMPZ_EX) ?
|
|
target_label2 : target_label;
|
|
uint32_t label2 = (smart_branch_opcode == ZEND_JMPZ || smart_branch_opcode == ZEND_JMPZ_EX) ?
|
|
target_label : target_label2;
|
|
|
|
ZEND_ASSERT(jit->b >= 0);
|
|
bb = &jit->ssa->cfg.blocks[jit->b];
|
|
ZEND_ASSERT(bb->successors_count == 2);
|
|
|
|
if (UNEXPECTED(bb->successors[0] == bb->successors[1])) {
|
|
ir_ref merge_inputs = IR_UNUSED;
|
|
|
|
while (n) {
|
|
n--;
|
|
ir_IF_TRUE(end_inputs->refs[n]);
|
|
ir_END_list(merge_inputs);
|
|
ir_IF_FALSE(end_inputs->refs[n]);
|
|
ir_END_list(merge_inputs);
|
|
}
|
|
ir_MERGE_list(merge_inputs);
|
|
_zend_jit_add_predecessor_ref(jit, label, jit->b, ir_END());
|
|
} else if (n == 1) {
|
|
ref = end_inputs->refs[0];
|
|
_zend_jit_add_predecessor_ref(jit, bb->successors[0], jit->b, ref);
|
|
_zend_jit_add_predecessor_ref(jit, bb->successors[1], jit->b, ref);
|
|
} else {
|
|
ir_ref true_inputs = IR_UNUSED, false_inputs = IR_UNUSED;
|
|
|
|
while (n) {
|
|
n--;
|
|
ir_IF_TRUE(end_inputs->refs[n]);
|
|
ir_END_list(true_inputs);
|
|
ir_IF_FALSE(end_inputs->refs[n]);
|
|
ir_END_list(false_inputs);
|
|
}
|
|
ir_MERGE_list(true_inputs);
|
|
_zend_jit_add_predecessor_ref(jit, label, jit->b, ir_END());
|
|
ir_MERGE_list(false_inputs);
|
|
_zend_jit_add_predecessor_ref(jit, label2, jit->b, ir_END());
|
|
}
|
|
jit->b = -1;
|
|
} else {
|
|
ir_MERGE_N(n, end_inputs->refs);
|
|
}
|
|
} else if (smart_branch_opcode && !exit_addr) {
|
|
/* dead code */
|
|
_zend_jit_add_predecessor_ref(jit, target_label, jit->b, ir_END());
|
|
jit->b = -1;
|
|
}
|
|
|
|
return 1;
|
|
}
|
|
|
|
static int zend_jit_identical(zend_jit_ctx *jit,
|
|
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,
|
|
uint8_t smart_branch_opcode,
|
|
uint32_t target_label,
|
|
uint32_t target_label2,
|
|
const void *exit_addr,
|
|
bool skip_comparison)
|
|
{
|
|
bool always_false = 0, always_true = 0;
|
|
ir_ref ref = IR_UNUSED;
|
|
|
|
if (opline->op1_type == IS_CV && (op1_info & MAY_BE_UNDEF)) {
|
|
ir_ref op1 = jit_ZVAL_ADDR(jit, op1_addr);
|
|
op1 = zend_jit_zval_check_undef(jit, op1, opline->op1.var, opline, 0);
|
|
op1_info |= MAY_BE_NULL;
|
|
op1_addr = ZEND_ADDR_REF_ZVAL(op1);
|
|
}
|
|
if (opline->op2_type == IS_CV && (op2_info & MAY_BE_UNDEF)) {
|
|
ir_ref op2 = jit_ZVAL_ADDR(jit, op2_addr);
|
|
op2 = zend_jit_zval_check_undef(jit, op2, opline->op2.var, opline, 0);
|
|
op2_info |= MAY_BE_NULL;
|
|
op2_addr = ZEND_ADDR_REF_ZVAL(op2);
|
|
}
|
|
|
|
if ((op1_info & op2_info & MAY_BE_ANY) == 0) {
|
|
always_false = 1;
|
|
} 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) {
|
|
always_true = 1;
|
|
} 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))) {
|
|
always_true = 1;
|
|
} else {
|
|
always_false = 1;
|
|
}
|
|
}
|
|
|
|
if (always_true) {
|
|
if (opline->opcode != ZEND_CASE_STRICT) {
|
|
jit_FREE_OP(jit, opline->op1_type, opline->op1, op1_info, opline);
|
|
}
|
|
jit_FREE_OP(jit, opline->op2_type, opline->op2, op2_info, opline);
|
|
if (!smart_branch_opcode
|
|
|| smart_branch_opcode == ZEND_JMPZ_EX
|
|
|| smart_branch_opcode == ZEND_JMPNZ_EX) {
|
|
jit_set_Z_TYPE_INFO(jit, res_addr, opline->opcode != ZEND_IS_NOT_IDENTICAL ? IS_TRUE : IS_FALSE);
|
|
}
|
|
if (may_throw) {
|
|
zend_jit_check_exception(jit);
|
|
}
|
|
if (exit_addr) {
|
|
if (smart_branch_opcode == ZEND_JMPNZ || smart_branch_opcode == ZEND_JMPNZ_EX) {
|
|
jit_SIDE_EXIT(jit, ir_CONST_ADDR(exit_addr));
|
|
}
|
|
} else if (smart_branch_opcode) {
|
|
uint32_t label;
|
|
|
|
if (opline->opcode == ZEND_IS_NOT_IDENTICAL) {
|
|
label = (smart_branch_opcode == ZEND_JMPZ || smart_branch_opcode == ZEND_JMPZ_EX) ?
|
|
target_label : target_label2;
|
|
} else {
|
|
label = (smart_branch_opcode == ZEND_JMPZ || smart_branch_opcode == ZEND_JMPZ_EX) ?
|
|
target_label2 : target_label;
|
|
}
|
|
_zend_jit_add_predecessor_ref(jit, label, jit->b, ir_END());
|
|
jit->b = -1;
|
|
}
|
|
return 1;
|
|
} else if (always_false) {
|
|
if (opline->opcode != ZEND_CASE_STRICT) {
|
|
jit_FREE_OP(jit, opline->op1_type, opline->op1, op1_info, opline);
|
|
}
|
|
jit_FREE_OP(jit, opline->op2_type, opline->op2, op2_info, opline);
|
|
if (!smart_branch_opcode
|
|
|| smart_branch_opcode == ZEND_JMPZ_EX
|
|
|| smart_branch_opcode == ZEND_JMPNZ_EX) {
|
|
jit_set_Z_TYPE_INFO(jit, res_addr, opline->opcode != ZEND_IS_NOT_IDENTICAL ? IS_FALSE : IS_TRUE);
|
|
}
|
|
if (may_throw) {
|
|
zend_jit_check_exception(jit);
|
|
}
|
|
if (exit_addr) {
|
|
if (smart_branch_opcode == ZEND_JMPZ || smart_branch_opcode == ZEND_JMPZ_EX) {
|
|
jit_SIDE_EXIT(jit, ir_CONST_ADDR(exit_addr));
|
|
}
|
|
} else if (smart_branch_opcode) {
|
|
uint32_t label;
|
|
|
|
if (opline->opcode == ZEND_IS_NOT_IDENTICAL) {
|
|
label = (smart_branch_opcode == ZEND_JMPZ || smart_branch_opcode == ZEND_JMPZ_EX) ?
|
|
target_label2 : target_label;
|
|
} else {
|
|
label = (smart_branch_opcode == ZEND_JMPZ || smart_branch_opcode == ZEND_JMPZ_EX) ?
|
|
target_label : target_label2;
|
|
}
|
|
_zend_jit_add_predecessor_ref(jit, label, jit->b, ir_END());
|
|
jit->b = -1;
|
|
}
|
|
return 1;
|
|
}
|
|
|
|
if ((opline->op1_type & (IS_CV|IS_VAR)) && (op1_info & MAY_BE_REF)) {
|
|
ref = jit_ZVAL_ADDR(jit, op1_addr);
|
|
ref = jit_ZVAL_DEREF_ref(jit, ref);
|
|
op1_addr = ZEND_ADDR_REF_ZVAL(ref);
|
|
}
|
|
if ((opline->op2_type & (IS_CV|IS_VAR)) && (op2_info & MAY_BE_REF)) {
|
|
ref = jit_ZVAL_ADDR(jit, op2_addr);
|
|
ref = jit_ZVAL_DEREF_ref(jit, ref);
|
|
op2_addr = ZEND_ADDR_REF_ZVAL(ref);
|
|
}
|
|
|
|
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) {
|
|
ref = zend_jit_cmp_long_long(jit, opline, op1_range, op1_addr, op2_range, op2_addr, res_addr, smart_branch_opcode, target_label, target_label2, exit_addr, skip_comparison);
|
|
if (!ref) {
|
|
return 0;
|
|
}
|
|
} 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) {
|
|
ref = zend_jit_cmp_double_double(jit, opline, op1_addr, op2_addr, res_addr, smart_branch_opcode, target_label, target_label2, exit_addr);
|
|
if (!ref) {
|
|
return 0;
|
|
}
|
|
} 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_inv(jit, op1_addr, real_addr, op1_info)) {
|
|
return 0;
|
|
}
|
|
op1_addr = real_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_inv(jit, op2_addr, real_addr, op2_info)) {
|
|
return 0;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (Z_MODE(op1_addr) == IS_CONST_ZVAL && Z_TYPE_P(Z_ZV(op1_addr)) <= IS_TRUE) {
|
|
zval *val = Z_ZV(op1_addr);
|
|
|
|
ref = ir_EQ(jit_Z_TYPE(jit, op2_addr), ir_CONST_U8(Z_TYPE_P(val)));
|
|
} else if (Z_MODE(op2_addr) == IS_CONST_ZVAL && Z_TYPE_P(Z_ZV(op2_addr)) <= IS_TRUE) {
|
|
zval *val = Z_ZV(op2_addr);
|
|
|
|
ref = ir_EQ(jit_Z_TYPE(jit, op1_addr), ir_CONST_U8(Z_TYPE_P(val)));
|
|
} else {
|
|
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_inv(jit, op1_addr, real_addr, op1_info)) {
|
|
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_inv(jit, op2_addr, real_addr, op2_info)) {
|
|
return 0;
|
|
}
|
|
op2_addr = real_addr;
|
|
}
|
|
if (may_throw) {
|
|
jit_SET_EX_OPLINE(jit, opline);
|
|
}
|
|
|
|
ref = ir_CALL_2(IR_BOOL, ir_CONST_FC_FUNC(zend_is_identical),
|
|
jit_ZVAL_ADDR(jit, op1_addr),
|
|
jit_ZVAL_ADDR(jit, op2_addr));
|
|
}
|
|
|
|
if (!smart_branch_opcode || smart_branch_opcode == ZEND_JMPNZ_EX || smart_branch_opcode == ZEND_JMPZ_EX) {
|
|
if (opline->opcode == ZEND_IS_NOT_IDENTICAL) {
|
|
jit_set_Z_TYPE_INFO_ref(jit, jit_ZVAL_ADDR(jit, res_addr),
|
|
ir_SUB_U32(ir_CONST_U32(IS_TRUE), ir_ZEXT_U32(ref)));
|
|
} else {
|
|
jit_set_Z_TYPE_INFO_ref(jit, jit_ZVAL_ADDR(jit, res_addr),
|
|
ir_ADD_U32(ir_ZEXT_U32(ref), ir_CONST_U32(IS_FALSE)));
|
|
}
|
|
}
|
|
if (opline->opcode != ZEND_CASE_STRICT) {
|
|
jit_FREE_OP(jit, opline->op1_type, opline->op1, op1_info, NULL);
|
|
}
|
|
jit_FREE_OP(jit, opline->op2_type, opline->op2, op2_info, NULL);
|
|
if (may_throw) {
|
|
zend_jit_check_exception_undef_result(jit, opline);
|
|
}
|
|
if (exit_addr) {
|
|
if (smart_branch_opcode == ZEND_JMPZ || smart_branch_opcode == ZEND_JMPZ_EX) {
|
|
ir_GUARD(ref, ir_CONST_ADDR(exit_addr));
|
|
} else {
|
|
ir_GUARD_NOT(ref, ir_CONST_ADDR(exit_addr));
|
|
}
|
|
} else if (smart_branch_opcode) {
|
|
if (opline->opcode == ZEND_IS_NOT_IDENTICAL) {
|
|
/* swap labels */
|
|
uint32_t tmp = target_label;
|
|
target_label = target_label2;
|
|
target_label2 = tmp;
|
|
}
|
|
ref = jit_IF_ex(jit, ref,
|
|
(smart_branch_opcode == ZEND_JMPZ || smart_branch_opcode == ZEND_JMPZ_EX) ? target_label2 : target_label);
|
|
}
|
|
}
|
|
|
|
if (smart_branch_opcode && !exit_addr) {
|
|
zend_basic_block *bb;
|
|
|
|
ZEND_ASSERT(jit->b >= 0);
|
|
bb = &jit->ssa->cfg.blocks[jit->b];
|
|
ZEND_ASSERT(bb->successors_count == 2);
|
|
|
|
if (bb->successors_count == 2 && bb->successors[0] == bb->successors[1]) {
|
|
ir_IF_TRUE(ref);
|
|
ir_MERGE_WITH_EMPTY_FALSE(ref);
|
|
_zend_jit_add_predecessor_ref(jit, bb->successors[0], jit->b, ir_END());
|
|
} else {
|
|
ZEND_ASSERT(bb->successors_count == 2);
|
|
_zend_jit_add_predecessor_ref(jit, bb->successors[0], jit->b, ref);
|
|
_zend_jit_add_predecessor_ref(jit, bb->successors[1], jit->b, ref);
|
|
}
|
|
jit->b = -1;
|
|
}
|
|
|
|
return 1;
|
|
}
|
|
|
|
static int zend_jit_bool_jmpznz(zend_jit_ctx *jit, 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, uint8_t branch_opcode, const void *exit_addr)
|
|
{
|
|
uint32_t true_label = -1;
|
|
uint32_t false_label = -1;
|
|
bool set_bool = 0;
|
|
bool set_bool_not = 0;
|
|
bool always_true = 0, always_false = 0;
|
|
ir_ref ref, end_inputs = IR_UNUSED, true_inputs = IR_UNUSED, false_inputs = IR_UNUSED;
|
|
ir_type type = IR_UNUSED;
|
|
|
|
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) {
|
|
true_label = target_label2;
|
|
false_label = target_label;
|
|
} else if (branch_opcode == ZEND_JMPNZ) {
|
|
true_label = target_label;
|
|
false_label = target_label2;
|
|
} else if (branch_opcode == ZEND_JMPZ_EX) {
|
|
set_bool = 1;
|
|
true_label = target_label2;
|
|
false_label = target_label;
|
|
} else if (branch_opcode == ZEND_JMPNZ_EX) {
|
|
set_bool = 1;
|
|
true_label = target_label;
|
|
false_label = target_label2;
|
|
} else {
|
|
ZEND_UNREACHABLE();
|
|
}
|
|
|
|
if (opline->op1_type == IS_CV && (op1_info & MAY_BE_REF)) {
|
|
ref = jit_ZVAL_ADDR(jit, op1_addr);
|
|
ref = jit_ZVAL_DEREF_ref(jit, ref);
|
|
op1_addr = ZEND_ADDR_REF_ZVAL(ref);
|
|
}
|
|
|
|
if (Z_MODE(op1_addr) == IS_CONST_ZVAL) {
|
|
if (zend_is_true(Z_ZV(op1_addr))) {
|
|
always_true = 1;
|
|
} else {
|
|
always_false = 1;
|
|
}
|
|
} else 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 = 1;
|
|
} else if (!(op1_info & (MAY_BE_ANY-(MAY_BE_NULL|MAY_BE_FALSE)))) {
|
|
if (opline->op1_type == IS_CV && (op1_info & MAY_BE_UNDEF)) {
|
|
ref = jit_ZVAL_ADDR(jit, op1_addr);
|
|
zend_jit_zval_check_undef(jit, ref, opline->op1.var, opline, 0);
|
|
}
|
|
always_false = 1;
|
|
}
|
|
}
|
|
|
|
if (always_true) {
|
|
if (set_bool) {
|
|
jit_set_Z_TYPE_INFO(jit, res_addr, set_bool_not ? IS_FALSE : IS_TRUE);
|
|
}
|
|
jit_FREE_OP(jit, opline->op1_type, opline->op1, op1_info, opline);
|
|
if (may_throw) {
|
|
zend_jit_check_exception(jit);
|
|
}
|
|
if (true_label != (uint32_t)-1) {
|
|
ZEND_ASSERT(exit_addr == NULL);
|
|
_zend_jit_add_predecessor_ref(jit, true_label, jit->b, ir_END());
|
|
jit->b = -1;
|
|
}
|
|
return 1;
|
|
} else if (always_false) {
|
|
if (set_bool) {
|
|
jit_set_Z_TYPE_INFO(jit, res_addr, set_bool_not ? IS_TRUE : IS_FALSE);
|
|
}
|
|
jit_FREE_OP(jit, opline->op1_type, opline->op1, op1_info, opline);
|
|
if (may_throw) {
|
|
zend_jit_check_exception(jit);
|
|
}
|
|
if (false_label != (uint32_t)-1) {
|
|
ZEND_ASSERT(exit_addr == NULL);
|
|
_zend_jit_add_predecessor_ref(jit, false_label, jit->b, ir_END());
|
|
jit->b = -1;
|
|
}
|
|
return 1;
|
|
}
|
|
|
|
if (op1_info & (MAY_BE_UNDEF|MAY_BE_NULL|MAY_BE_FALSE|MAY_BE_TRUE)) {
|
|
type = jit_Z_TYPE(jit, op1_addr);
|
|
if (op1_info & (MAY_BE_UNDEF|MAY_BE_NULL|MAY_BE_FALSE)) {
|
|
ir_ref if_type = ir_IF(ir_LT(type, ir_CONST_U8(IS_TRUE)));
|
|
|
|
ir_IF_TRUE_cold(if_type);
|
|
|
|
if (op1_info & MAY_BE_UNDEF) {
|
|
zend_jit_type_check_undef(jit,
|
|
type,
|
|
opline->op1.var,
|
|
opline, 1, 0, 1);
|
|
}
|
|
if (set_bool) {
|
|
jit_set_Z_TYPE_INFO(jit, res_addr, set_bool_not ? IS_TRUE : IS_FALSE);
|
|
}
|
|
if (exit_addr) {
|
|
if (branch_opcode == ZEND_JMPNZ || branch_opcode == ZEND_JMPNZ_EX) {
|
|
ir_END_list(end_inputs);
|
|
} else {
|
|
jit_SIDE_EXIT(jit, ir_CONST_ADDR(exit_addr));
|
|
}
|
|
} else if (false_label != (uint32_t)-1) {
|
|
ir_END_list(false_inputs);
|
|
} else {
|
|
ir_END_list(end_inputs);
|
|
}
|
|
ir_IF_FALSE(if_type);
|
|
}
|
|
|
|
if (op1_info & MAY_BE_TRUE) {
|
|
ir_ref if_type = IR_UNUSED;
|
|
|
|
if (op1_info & (MAY_BE_ANY-(MAY_BE_NULL|MAY_BE_FALSE|MAY_BE_TRUE))) {
|
|
if_type = ir_IF(ir_EQ(type, ir_CONST_U8(IS_TRUE)));
|
|
|
|
ir_IF_TRUE(if_type);
|
|
}
|
|
if (set_bool) {
|
|
jit_set_Z_TYPE_INFO(jit, res_addr, set_bool_not ? IS_FALSE : IS_TRUE);
|
|
}
|
|
if (exit_addr) {
|
|
if (branch_opcode == ZEND_JMPZ || branch_opcode == ZEND_JMPZ_EX) {
|
|
ir_END_list(end_inputs);
|
|
} else {
|
|
jit_SIDE_EXIT(jit, ir_CONST_ADDR(exit_addr));
|
|
}
|
|
} else if (true_label != (uint32_t)-1) {
|
|
ir_END_list(true_inputs);
|
|
} else {
|
|
ir_END_list(end_inputs);
|
|
}
|
|
if (if_type) {
|
|
ir_IF_FALSE(if_type);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (op1_info & MAY_BE_LONG) {
|
|
ir_ref if_long = IR_UNUSED;
|
|
ir_ref ref;
|
|
|
|
if (op1_info & (MAY_BE_ANY-(MAY_BE_NULL|MAY_BE_FALSE|MAY_BE_TRUE|MAY_BE_LONG))) {
|
|
if (!type) {
|
|
type = jit_Z_TYPE(jit, op1_addr);
|
|
}
|
|
if_long = ir_IF(ir_EQ(type, ir_CONST_U8(IS_LONG)));
|
|
ir_IF_TRUE(if_long);
|
|
}
|
|
ref = jit_Z_LVAL(jit, op1_addr);
|
|
if (branch_opcode == ZEND_BOOL || branch_opcode == ZEND_BOOL_NOT) {
|
|
ref = ir_NE(ref, ir_CONST_LONG(0));
|
|
if (set_bool_not) {
|
|
jit_set_Z_TYPE_INFO_ref(jit, jit_ZVAL_ADDR(jit, res_addr),
|
|
ir_SUB_U32(ir_CONST_U32(IS_TRUE), ir_ZEXT_U32(ref)));
|
|
} else {
|
|
jit_set_Z_TYPE_INFO_ref(jit, jit_ZVAL_ADDR(jit, res_addr),
|
|
ir_ADD_U32(ir_ZEXT_U32(ref), ir_CONST_U32(IS_FALSE)));
|
|
}
|
|
ir_END_list(end_inputs);
|
|
} else if (exit_addr) {
|
|
if (set_bool) {
|
|
jit_set_Z_TYPE_INFO_ref(jit, jit_ZVAL_ADDR(jit, res_addr),
|
|
ir_ADD_U32(ir_ZEXT_U32(ir_NE(ref, ir_CONST_LONG(0))), ir_CONST_U32(IS_FALSE)));
|
|
}
|
|
if (branch_opcode == ZEND_JMPZ || branch_opcode == ZEND_JMPZ_EX) {
|
|
ir_GUARD(ref, ir_CONST_ADDR(exit_addr));
|
|
} else {
|
|
ir_GUARD_NOT(ref, ir_CONST_ADDR(exit_addr));
|
|
}
|
|
ir_END_list(end_inputs);
|
|
} else {
|
|
ir_ref if_val = ir_IF(ref);
|
|
ir_IF_TRUE(if_val);
|
|
if (set_bool) {
|
|
jit_set_Z_TYPE_INFO(jit, res_addr, set_bool_not ? IS_FALSE : IS_TRUE);
|
|
}
|
|
ir_END_list(true_inputs);
|
|
ir_IF_FALSE(if_val);
|
|
if (set_bool) {
|
|
jit_set_Z_TYPE_INFO(jit, res_addr, set_bool_not ? IS_TRUE : IS_FALSE);
|
|
}
|
|
ir_END_list(false_inputs);
|
|
}
|
|
if (if_long) {
|
|
ir_IF_FALSE(if_long);
|
|
}
|
|
}
|
|
|
|
if (op1_info & MAY_BE_DOUBLE) {
|
|
ir_ref if_double = IR_UNUSED;
|
|
ir_ref ref;
|
|
|
|
if (op1_info & (MAY_BE_ANY-(MAY_BE_NULL|MAY_BE_FALSE|MAY_BE_TRUE|MAY_BE_LONG|MAY_BE_DOUBLE))) {
|
|
if (!type) {
|
|
type = jit_Z_TYPE(jit, op1_addr);
|
|
}
|
|
if_double = ir_IF(ir_EQ(type, ir_CONST_U8(IS_DOUBLE)));
|
|
ir_IF_TRUE(if_double);
|
|
}
|
|
ref = ir_NE(jit_Z_DVAL(jit, op1_addr), ir_CONST_DOUBLE(0.0));
|
|
if (branch_opcode == ZEND_BOOL || branch_opcode == ZEND_BOOL_NOT) {
|
|
if (set_bool_not) {
|
|
jit_set_Z_TYPE_INFO_ref(jit, jit_ZVAL_ADDR(jit, res_addr),
|
|
ir_SUB_U32(ir_CONST_U32(IS_TRUE), ir_ZEXT_U32(ref)));
|
|
} else {
|
|
jit_set_Z_TYPE_INFO_ref(jit, jit_ZVAL_ADDR(jit, res_addr),
|
|
ir_ADD_U32(ir_ZEXT_U32(ref), ir_CONST_U32(IS_FALSE)));
|
|
}
|
|
ir_END_list(end_inputs);
|
|
} else if (exit_addr) {
|
|
if (set_bool) {
|
|
jit_set_Z_TYPE_INFO_ref(jit, jit_ZVAL_ADDR(jit, res_addr),
|
|
ir_ADD_U32(ir_ZEXT_U32(ref), ir_CONST_U32(IS_FALSE)));
|
|
}
|
|
if (branch_opcode == ZEND_JMPZ || branch_opcode == ZEND_JMPZ_EX) {
|
|
ir_GUARD(ref, ir_CONST_ADDR(exit_addr));
|
|
} else {
|
|
ir_GUARD_NOT(ref, ir_CONST_ADDR(exit_addr));
|
|
}
|
|
ir_END_list(end_inputs);
|
|
} else {
|
|
ir_ref if_val = ir_IF(ref);
|
|
ir_IF_TRUE(if_val);
|
|
if (set_bool) {
|
|
jit_set_Z_TYPE_INFO(jit, res_addr, set_bool_not ? IS_FALSE : IS_TRUE);
|
|
}
|
|
ir_END_list(true_inputs);
|
|
ir_IF_FALSE(if_val);
|
|
if (set_bool) {
|
|
jit_set_Z_TYPE_INFO(jit, res_addr, set_bool_not ? IS_TRUE : IS_FALSE);
|
|
}
|
|
ir_END_list(false_inputs);
|
|
}
|
|
if (if_double) {
|
|
ir_IF_FALSE(if_double);
|
|
}
|
|
}
|
|
|
|
if (op1_info & (MAY_BE_ANY - (MAY_BE_NULL|MAY_BE_FALSE|MAY_BE_TRUE|MAY_BE_LONG|MAY_BE_DOUBLE))) {
|
|
jit_SET_EX_OPLINE(jit, opline);
|
|
ref = ir_CALL_1(IR_BOOL, ir_CONST_FC_FUNC(zend_is_true), jit_ZVAL_ADDR(jit, op1_addr));
|
|
jit_FREE_OP(jit, opline->op1_type, opline->op1, op1_info, NULL);
|
|
if (may_throw) {
|
|
zend_jit_check_exception_undef_result(jit, opline);
|
|
}
|
|
if (branch_opcode == ZEND_BOOL || branch_opcode == ZEND_BOOL_NOT) {
|
|
if (set_bool_not) {
|
|
jit_set_Z_TYPE_INFO_ref(jit, jit_ZVAL_ADDR(jit, res_addr),
|
|
ir_SUB_U32(ir_CONST_U32(IS_TRUE), ir_ZEXT_U32(ref)));
|
|
} else {
|
|
jit_set_Z_TYPE_INFO_ref(jit, jit_ZVAL_ADDR(jit, res_addr),
|
|
ir_ADD_U32(ir_ZEXT_U32(ref), ir_CONST_U32(IS_FALSE)));
|
|
}
|
|
if (end_inputs) {
|
|
ir_END_list(end_inputs);
|
|
}
|
|
} else if (exit_addr) {
|
|
if (set_bool) {
|
|
jit_set_Z_TYPE_INFO_ref(jit, jit_ZVAL_ADDR(jit, res_addr),
|
|
ir_ADD_U32(ir_ZEXT_U32(ref), ir_CONST_U32(IS_FALSE)));
|
|
}
|
|
if (branch_opcode == ZEND_JMPZ || branch_opcode == ZEND_JMPZ_EX) {
|
|
ir_GUARD(ref, ir_CONST_ADDR(exit_addr));
|
|
} else {
|
|
ir_GUARD_NOT(ref, ir_CONST_ADDR(exit_addr));
|
|
}
|
|
if (end_inputs) {
|
|
ir_END_list(end_inputs);
|
|
}
|
|
} else {
|
|
ir_ref if_val = ir_IF(ref);
|
|
ir_IF_TRUE(if_val);
|
|
if (set_bool) {
|
|
jit_set_Z_TYPE_INFO(jit, res_addr, set_bool_not ? IS_FALSE : IS_TRUE);
|
|
}
|
|
ir_END_list(true_inputs);
|
|
ir_IF_FALSE(if_val);
|
|
if (set_bool) {
|
|
jit_set_Z_TYPE_INFO(jit, res_addr, set_bool_not ? IS_TRUE : IS_FALSE);
|
|
}
|
|
ir_END_list(false_inputs);
|
|
}
|
|
}
|
|
|
|
if (branch_opcode == ZEND_BOOL || branch_opcode == ZEND_BOOL_NOT || exit_addr) {
|
|
if (end_inputs) {
|
|
ir_MERGE_list(end_inputs);
|
|
}
|
|
} else {
|
|
_zend_jit_merge_smart_branch_inputs(jit, true_label, false_label, true_inputs, false_inputs);
|
|
}
|
|
|
|
return 1;
|
|
}
|
|
|
|
static int zend_jit_defined(zend_jit_ctx *jit, const zend_op *opline, uint8_t 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;
|
|
ir_ref ref, ref2, if_set, if_zero, if_set2;
|
|
ir_ref end_inputs = IR_UNUSED, true_inputs = IR_UNUSED, false_inputs = IR_UNUSED;
|
|
|
|
if (smart_branch_opcode && !exit_addr) {
|
|
if (smart_branch_opcode == ZEND_JMPZ) {
|
|
defined_label = target_label2;
|
|
undefined_label = target_label;
|
|
} else if (smart_branch_opcode == ZEND_JMPNZ) {
|
|
defined_label = target_label;
|
|
undefined_label = target_label2;
|
|
} else {
|
|
ZEND_UNREACHABLE();
|
|
}
|
|
} else {
|
|
res_addr = RES_ADDR();
|
|
}
|
|
|
|
// if (CACHED_PTR(opline->extended_value)) {
|
|
ref = ir_LOAD_A(ir_ADD_OFFSET(ir_LOAD_A(jit_EX(run_time_cache)), opline->extended_value));
|
|
|
|
if_set = ir_IF(ref);
|
|
|
|
ir_IF_FALSE_cold(if_set);
|
|
if_zero = ir_END();
|
|
|
|
ir_IF_TRUE(if_set);
|
|
if_set2 = ir_IF(ir_AND_A(ref, ir_CONST_ADDR(CACHE_SPECIAL)));
|
|
ir_IF_FALSE(if_set2);
|
|
|
|
if (exit_addr) {
|
|
if (smart_branch_opcode == ZEND_JMPNZ) {
|
|
jit_SIDE_EXIT(jit, ir_CONST_ADDR(exit_addr));
|
|
} else {
|
|
ir_END_list(end_inputs);
|
|
}
|
|
} else if (smart_branch_opcode) {
|
|
ir_END_list(true_inputs);
|
|
} else {
|
|
jit_set_Z_TYPE_INFO(jit, res_addr, IS_TRUE);
|
|
ir_END_list(end_inputs);
|
|
}
|
|
|
|
ir_IF_TRUE_cold(if_set2);
|
|
|
|
ref2 = jit_EG(zend_constants);
|
|
ref = ir_SHR_A(ref, ir_CONST_ADDR(1));
|
|
if (sizeof(void*) == 8) {
|
|
ref = ir_TRUNC_U32(ref);
|
|
}
|
|
ref2 = ir_EQ(ref, ir_LOAD_U32(ir_ADD_OFFSET(ir_LOAD_A(ref2), offsetof(HashTable, nNumOfElements))));
|
|
ref2 = ir_IF(ref2);
|
|
ir_IF_TRUE(ref2);
|
|
|
|
if (exit_addr) {
|
|
if (smart_branch_opcode == ZEND_JMPZ) {
|
|
jit_SIDE_EXIT(jit, ir_CONST_ADDR(exit_addr));
|
|
} else {
|
|
ir_END_list(end_inputs);
|
|
}
|
|
} else if (smart_branch_opcode) {
|
|
ir_END_list(false_inputs);
|
|
} else {
|
|
jit_set_Z_TYPE_INFO(jit, res_addr, IS_FALSE);
|
|
ir_END_list(end_inputs);
|
|
}
|
|
|
|
ir_IF_FALSE(ref2);
|
|
ir_MERGE_2(if_zero, ir_END());
|
|
|
|
jit_SET_EX_OPLINE(jit, opline);
|
|
ref2 = ir_NE(ir_CALL_1(IR_ADDR, ir_CONST_FC_FUNC(zend_jit_check_constant), ir_CONST_ADDR(zv)), IR_NULL);
|
|
if (exit_addr) {
|
|
if (smart_branch_opcode == ZEND_JMPZ) {
|
|
ir_GUARD(ref2, ir_CONST_ADDR(exit_addr));
|
|
} else {
|
|
ir_GUARD_NOT(ref2, ir_CONST_ADDR(exit_addr));
|
|
}
|
|
ir_END_list(end_inputs);
|
|
} else if (smart_branch_opcode) {
|
|
ref2 = ir_IF(ref2);
|
|
ir_IF_TRUE(ref2);
|
|
ir_END_list(true_inputs);
|
|
ir_IF_FALSE(ref2);
|
|
ir_END_list(false_inputs);
|
|
} else {
|
|
jit_set_Z_TYPE_INFO_ref(jit, jit_ZVAL_ADDR(jit, res_addr),
|
|
ir_ADD_U32(ir_ZEXT_U32(ref2), ir_CONST_U32(IS_FALSE)));
|
|
ir_END_list(end_inputs);
|
|
}
|
|
|
|
if (!smart_branch_opcode || exit_addr) {
|
|
if (end_inputs) {
|
|
ir_MERGE_list(end_inputs);
|
|
}
|
|
} else {
|
|
_zend_jit_merge_smart_branch_inputs(jit, defined_label, undefined_label, true_inputs, false_inputs);
|
|
}
|
|
|
|
return 1;
|
|
}
|
|
|
|
static int zend_jit_escape_if_undef(zend_jit_ctx *jit, int var, uint32_t flags, const zend_op *opline, int8_t reg)
|
|
{
|
|
zend_jit_addr reg_addr = ZEND_ADDR_REF_ZVAL(zend_jit_deopt_rload(jit, IR_ADDR, reg));
|
|
ir_ref if_def = ir_IF(jit_Z_TYPE(jit, reg_addr));
|
|
|
|
ir_IF_FALSE_cold(if_def);
|
|
|
|
if (flags & ZEND_JIT_EXIT_RESTORE_CALL) {
|
|
if (!zend_jit_save_call_chain(jit, -1)) {
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
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)) {
|
|
zend_jit_addr val_addr = ZEND_ADDR_MEM_ZVAL(ZREG_FP, (opline-1)->op1.var);
|
|
|
|
zend_jit_zval_try_addref(jit, val_addr);
|
|
}
|
|
|
|
jit_LOAD_IP_ADDR(jit, opline - 1);
|
|
ir_IJMP(jit_STUB_ADDR(jit, jit_stub_trace_escape));
|
|
|
|
ir_IF_TRUE(if_def);
|
|
|
|
return 1;
|
|
}
|
|
|
|
static int zend_jit_restore_zval(zend_jit_ctx *jit, int var, int8_t reg)
|
|
{
|
|
zend_jit_addr var_addr = ZEND_ADDR_MEM_ZVAL(ZREG_FP, var);
|
|
zend_jit_addr reg_addr = ZEND_ADDR_REF_ZVAL(zend_jit_deopt_rload(jit, IR_ADDR, reg));
|
|
|
|
// JIT: ZVAL_COPY_OR_DUP(EX_VAR(opline->result.var), &c->value); (no dup)
|
|
jit_ZVAL_COPY(jit, var_addr, MAY_BE_ANY, reg_addr, MAY_BE_ANY, 1);
|
|
return 1;
|
|
}
|
|
|
|
static zend_jit_addr zend_jit_guard_fetch_result_type(zend_jit_ctx *jit,
|
|
const zend_op *opline,
|
|
zend_jit_addr val_addr,
|
|
uint8_t type,
|
|
bool deref,
|
|
uint32_t flags,
|
|
bool op1_avoid_refcounting)
|
|
{
|
|
zend_jit_trace_stack *stack = JIT_G(current_frame)->stack;
|
|
int32_t exit_point;
|
|
const void *res_exit_addr = NULL;
|
|
ir_ref end1 = IR_UNUSED, ref1 = IR_UNUSED;
|
|
ir_ref ref = jit_ZVAL_ADDR(jit, val_addr);
|
|
uint32_t old_op1_info = 0;
|
|
uint32_t old_info;
|
|
ir_ref old_ref;
|
|
|
|
|
|
if (opline->op1_type & (IS_VAR|IS_TMP_VAR|IS_CV)) {
|
|
old_op1_info = STACK_INFO(stack, EX_VAR_TO_NUM(opline->op1.var));
|
|
if (op1_avoid_refcounting
|
|
|| ((opline->op1_type & (IS_VAR|IS_TMP_VAR))
|
|
&& STACK_FLAGS(stack, EX_VAR_TO_NUM(opline->op1.var)) & (ZREG_ZVAL_ADDREF|ZREG_THIS))) {
|
|
SET_STACK_REG(stack, EX_VAR_TO_NUM(opline->op1.var), ZREG_NONE);
|
|
}
|
|
}
|
|
old_info = STACK_INFO(stack, EX_VAR_TO_NUM(opline->result.var));
|
|
old_ref = STACK_REF(stack, EX_VAR_TO_NUM(opline->result.var));
|
|
CLEAR_STACK_REF(stack, EX_VAR_TO_NUM(opline->result.var));
|
|
SET_STACK_TYPE(stack, EX_VAR_TO_NUM(opline->result.var), IS_UNKNOWN, 1);
|
|
|
|
if (deref) {
|
|
ir_ref if_type;
|
|
|
|
if (type == IS_NULL && (opline->opcode == ZEND_FETCH_DIM_IS || opline->opcode == ZEND_FETCH_OBJ_IS)) {
|
|
if_type = ir_IF(ir_ULE(jit_Z_TYPE(jit, val_addr), ir_CONST_U8(type)));
|
|
} else {
|
|
if_type = jit_if_Z_TYPE(jit, val_addr, type);
|
|
}
|
|
ir_IF_TRUE(if_type);
|
|
end1 = ir_END();
|
|
ref1 = ref;
|
|
ir_IF_FALSE_cold(if_type);
|
|
|
|
SET_STACK_REF_EX(stack, EX_VAR_TO_NUM(opline->result.var), ref, ZREG_ZVAL_COPY);
|
|
exit_point = zend_jit_trace_get_exit_point(opline+1, flags);
|
|
res_exit_addr = zend_jit_trace_get_exit_addr(exit_point);
|
|
if (!res_exit_addr) {
|
|
return 0;
|
|
}
|
|
|
|
jit_guard_Z_TYPE(jit, val_addr, IS_REFERENCE, res_exit_addr);
|
|
ref = ir_ADD_OFFSET(jit_Z_PTR(jit, val_addr), offsetof(zend_reference, val));
|
|
val_addr = ZEND_ADDR_REF_ZVAL(ref);
|
|
}
|
|
|
|
SET_STACK_REF_EX(stack, EX_VAR_TO_NUM(opline->result.var), ref, ZREG_ZVAL_COPY);
|
|
exit_point = zend_jit_trace_get_exit_point(opline+1, flags);
|
|
res_exit_addr = zend_jit_trace_get_exit_addr(exit_point);
|
|
if (!res_exit_addr) {
|
|
return 0;
|
|
}
|
|
|
|
if (!deref && type == IS_NULL && (opline->opcode == ZEND_FETCH_DIM_IS || opline->opcode == ZEND_FETCH_OBJ_IS)) {
|
|
ir_GUARD(ir_ULE(jit_Z_TYPE(jit, val_addr), ir_CONST_U8(type)), ir_CONST_ADDR(res_exit_addr));
|
|
} else {
|
|
jit_guard_Z_TYPE(jit, val_addr, type, res_exit_addr);
|
|
}
|
|
|
|
if (deref) {
|
|
ir_MERGE_WITH(end1);
|
|
ref = ir_PHI_2(IR_ADDR, ref, ref1);
|
|
}
|
|
|
|
val_addr = ZEND_ADDR_REF_ZVAL(ref);
|
|
|
|
SET_STACK_REF(stack, EX_VAR_TO_NUM(opline->result.var), old_ref);
|
|
SET_STACK_INFO(stack, EX_VAR_TO_NUM(opline->result.var), old_info);
|
|
if (opline->op1_type & (IS_VAR|IS_TMP_VAR|IS_CV)) {
|
|
SET_STACK_INFO(stack, EX_VAR_TO_NUM(opline->op1.var), old_op1_info);
|
|
}
|
|
|
|
return val_addr;
|
|
}
|
|
|
|
static int zend_jit_fetch_constant(zend_jit_ctx *jit,
|
|
const zend_op *opline,
|
|
const zend_op_array *op_array,
|
|
zend_ssa *ssa,
|
|
const zend_ssa_op *ssa_op,
|
|
zend_jit_addr res_addr)
|
|
{
|
|
zval *zv = RT_CONSTANT(opline, opline->op2) + 1;
|
|
uint32_t res_info = RES_INFO();
|
|
ir_ref ref, ref2, if_set, if_special, not_set_path, special_path, fast_path;
|
|
|
|
// JIT: c = CACHED_PTR(opline->extended_value);
|
|
ref = ir_LOAD_A(ir_ADD_OFFSET(ir_LOAD_A(jit_EX(run_time_cache)), opline->extended_value));
|
|
|
|
// JIT: if (c != NULL)
|
|
if_set = ir_IF(ref);
|
|
|
|
if (!zend_jit_is_persistent_constant(zv, opline->op1.num)) {
|
|
// JIT: if (!IS_SPECIAL_CACHE_VAL(c))
|
|
ir_IF_FALSE_cold(if_set);
|
|
not_set_path = ir_END();
|
|
ir_IF_TRUE(if_set);
|
|
if_special = ir_IF(ir_AND_A(ref, ir_CONST_ADDR(CACHE_SPECIAL)));
|
|
ir_IF_TRUE_cold(if_special);
|
|
special_path = ir_END();
|
|
ir_IF_FALSE(if_special);
|
|
fast_path = ir_END();
|
|
ir_MERGE_2(not_set_path, special_path);
|
|
} else {
|
|
ir_IF_TRUE(if_set);
|
|
fast_path = ir_END();
|
|
ir_IF_FALSE_cold(if_set);
|
|
}
|
|
|
|
// JIT: zend_jit_get_constant(RT_CONSTANT(opline, opline->op2) + 1, opline->op1.num);
|
|
jit_SET_EX_OPLINE(jit, opline);
|
|
ref2 = ir_CALL_2(IR_ADDR, ir_CONST_FC_FUNC(zend_jit_get_constant),
|
|
ir_CONST_ADDR(zv),
|
|
ir_CONST_U32(opline->op1.num));
|
|
ir_GUARD(ref2, jit_STUB_ADDR(jit, jit_stub_exception_handler));
|
|
|
|
ir_MERGE_WITH(fast_path);
|
|
ref = ir_PHI_2(IR_ADDR, ref2, ref);
|
|
|
|
if ((res_info & MAY_BE_GUARD) && JIT_G(current_frame)) {
|
|
uint8_t type = concrete_type(res_info);
|
|
zend_jit_addr const_addr = ZEND_ADDR_REF_ZVAL(ref);
|
|
|
|
const_addr = zend_jit_guard_fetch_result_type(jit, opline, const_addr, type, 0, 0, 0);
|
|
if (!const_addr) {
|
|
return 0;
|
|
}
|
|
|
|
res_info &= ~MAY_BE_GUARD;
|
|
ssa->var_info[ssa_op->result_def].type &= ~MAY_BE_GUARD;
|
|
|
|
// JIT: ZVAL_COPY_OR_DUP(EX_VAR(opline->result.var), &c->value); (no dup)
|
|
jit_ZVAL_COPY(jit, res_addr, MAY_BE_ANY, const_addr, res_info, 1);
|
|
if (!zend_jit_store_var_if_necessary(jit, opline->result.var, res_addr, res_info)) {
|
|
return 0;
|
|
}
|
|
} else {
|
|
ir_ref const_addr = ZEND_ADDR_REF_ZVAL(ref);
|
|
|
|
// JIT: ZVAL_COPY_OR_DUP(EX_VAR(opline->result.var), &c->value); (no dup)
|
|
jit_ZVAL_COPY(jit, res_addr, MAY_BE_ANY, const_addr, MAY_BE_ANY, 1);
|
|
}
|
|
|
|
|
|
return 1;
|
|
}
|
|
|
|
static int zend_jit_type_check(zend_jit_ctx *jit, const zend_op *opline, uint32_t op1_info, uint8_t smart_branch_opcode, uint32_t target_label, uint32_t target_label2, const void *exit_addr)
|
|
{
|
|
uint32_t mask;
|
|
zend_jit_addr op1_addr = OP1_ADDR();
|
|
zend_jit_addr res_addr = 0;
|
|
uint32_t true_label = -1, false_label = -1;
|
|
ir_ref end_inputs = IR_UNUSED, true_inputs = IR_UNUSED, false_inputs = IR_UNUSED;
|
|
|
|
// TODO: support for is_resource() ???
|
|
ZEND_ASSERT(opline->extended_value != MAY_BE_RESOURCE);
|
|
|
|
if (smart_branch_opcode && !exit_addr) {
|
|
if (smart_branch_opcode == ZEND_JMPZ) {
|
|
true_label = target_label2;
|
|
false_label = target_label;
|
|
} else if (smart_branch_opcode == ZEND_JMPNZ) {
|
|
true_label = target_label;
|
|
false_label = target_label2;
|
|
} else {
|
|
ZEND_UNREACHABLE();
|
|
}
|
|
} else {
|
|
res_addr = RES_ADDR();
|
|
}
|
|
|
|
if (op1_info & MAY_BE_UNDEF) {
|
|
ir_ref if_def = IR_UNUSED;
|
|
|
|
if (op1_info & (MAY_BE_ANY|MAY_BE_REF)) {
|
|
if_def = jit_if_not_Z_TYPE(jit, op1_addr, IS_UNDEF);
|
|
ir_IF_FALSE_cold(if_def);
|
|
}
|
|
|
|
jit_SET_EX_OPLINE(jit, opline);
|
|
ir_CALL_1(IR_VOID, ir_CONST_FC_FUNC(zend_jit_undefined_op_helper), ir_CONST_U32(opline->op1.var));
|
|
zend_jit_check_exception_undef_result(jit, opline);
|
|
if (opline->extended_value & MAY_BE_NULL) {
|
|
if (exit_addr) {
|
|
if (smart_branch_opcode == ZEND_JMPNZ) {
|
|
jit_SIDE_EXIT(jit, ir_CONST_ADDR(exit_addr));
|
|
} else {
|
|
ir_END_list(end_inputs);
|
|
}
|
|
} else if (smart_branch_opcode) {
|
|
ir_END_list(true_inputs);
|
|
} else {
|
|
jit_set_Z_TYPE_INFO(jit, res_addr, IS_TRUE);
|
|
ir_END_list(end_inputs);
|
|
}
|
|
} else {
|
|
if (exit_addr) {
|
|
if (smart_branch_opcode == ZEND_JMPZ) {
|
|
jit_SIDE_EXIT(jit, ir_CONST_ADDR(exit_addr));
|
|
} else {
|
|
ir_END_list(end_inputs);
|
|
}
|
|
} else if (smart_branch_opcode) {
|
|
ir_END_list(false_inputs);
|
|
} else {
|
|
jit_set_Z_TYPE_INFO(jit, res_addr, IS_FALSE);
|
|
if (if_def) {
|
|
ir_END_list(end_inputs);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (if_def) {
|
|
ir_IF_TRUE(if_def);
|
|
op1_info |= MAY_BE_NULL;
|
|
}
|
|
}
|
|
|
|
if (op1_info & (MAY_BE_ANY|MAY_BE_REF)) {
|
|
mask = opline->extended_value;
|
|
if (!(op1_info & MAY_BE_GUARD) && !(op1_info & (MAY_BE_ANY - mask))) {
|
|
jit_FREE_OP(jit, opline->op1_type, opline->op1, op1_info, opline);
|
|
if (exit_addr) {
|
|
if (smart_branch_opcode == ZEND_JMPNZ) {
|
|
jit_SIDE_EXIT(jit, ir_CONST_ADDR(exit_addr));
|
|
} else if (end_inputs) {
|
|
ir_END_list(end_inputs);
|
|
}
|
|
} else if (smart_branch_opcode) {
|
|
ir_END_list(true_inputs);
|
|
} else {
|
|
jit_set_Z_TYPE_INFO(jit, res_addr, IS_TRUE);
|
|
ir_END_list(end_inputs);
|
|
}
|
|
} else if (!(op1_info & MAY_BE_GUARD) && !(op1_info & mask)) {
|
|
jit_FREE_OP(jit, opline->op1_type, opline->op1, op1_info, opline);
|
|
if (exit_addr) {
|
|
if (smart_branch_opcode == ZEND_JMPZ) {
|
|
jit_SIDE_EXIT(jit, ir_CONST_ADDR(exit_addr));
|
|
} else if (end_inputs) {
|
|
ir_END_list(end_inputs);
|
|
}
|
|
} else if (smart_branch_opcode) {
|
|
ir_END_list(false_inputs);
|
|
} else {
|
|
jit_set_Z_TYPE_INFO(jit, res_addr, IS_FALSE);
|
|
ir_END_list(end_inputs);
|
|
}
|
|
} else {
|
|
ir_ref ref;
|
|
bool invert = 0;
|
|
uint8_t type;
|
|
|
|
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;
|
|
case MAY_BE_ANY - MAY_BE_NULL: type = IS_NULL; invert = 1; break;
|
|
case MAY_BE_ANY - MAY_BE_FALSE: type = IS_FALSE; invert = 1; break;
|
|
case MAY_BE_ANY - MAY_BE_TRUE: type = IS_TRUE; invert = 1; break;
|
|
case MAY_BE_ANY - MAY_BE_LONG: type = IS_LONG; invert = 1; break;
|
|
case MAY_BE_ANY - MAY_BE_DOUBLE: type = IS_DOUBLE; invert = 1; break;
|
|
case MAY_BE_ANY - MAY_BE_STRING: type = IS_STRING; invert = 1; break;
|
|
case MAY_BE_ANY - MAY_BE_ARRAY: type = IS_ARRAY; invert = 1; break;
|
|
case MAY_BE_ANY - MAY_BE_OBJECT: type = IS_OBJECT; invert = 1; break;
|
|
case MAY_BE_ANY - MAY_BE_RESOURCE: type = IS_OBJECT; invert = 1; break;
|
|
default:
|
|
type = 0;
|
|
}
|
|
|
|
if (op1_info & MAY_BE_REF) {
|
|
ir_ref ref = jit_ZVAL_ADDR(jit, op1_addr);
|
|
ref = jit_ZVAL_DEREF_ref(jit, ref);
|
|
op1_addr = ZEND_ADDR_REF_ZVAL(ref);
|
|
}
|
|
if (type == 0) {
|
|
ref = ir_AND_U32(ir_SHL_U32(ir_CONST_U32(1), jit_Z_TYPE(jit, op1_addr)), ir_CONST_U32(mask));
|
|
if (!smart_branch_opcode) {
|
|
ref = ir_NE(ref, ir_CONST_U32(0));
|
|
}
|
|
} else if (invert) {
|
|
ref = ir_NE(jit_Z_TYPE(jit, op1_addr), ir_CONST_U8(type));
|
|
} else {
|
|
ref = ir_EQ(jit_Z_TYPE(jit, op1_addr), ir_CONST_U8(type));
|
|
}
|
|
|
|
jit_FREE_OP(jit, opline->op1_type, opline->op1, op1_info, opline);
|
|
|
|
if (exit_addr) {
|
|
if (smart_branch_opcode == ZEND_JMPZ) {
|
|
ir_GUARD(ref, ir_CONST_ADDR(exit_addr));
|
|
} else {
|
|
ir_GUARD_NOT(ref, ir_CONST_ADDR(exit_addr));
|
|
}
|
|
if (end_inputs) {
|
|
ir_END_list(end_inputs);
|
|
}
|
|
} else if (smart_branch_opcode) {
|
|
ir_ref if_val = ir_IF(ref);
|
|
ir_IF_TRUE(if_val);
|
|
ir_END_list(true_inputs);
|
|
ir_IF_FALSE(if_val);
|
|
ir_END_list(false_inputs);
|
|
} else {
|
|
jit_set_Z_TYPE_INFO_ref(jit, jit_ZVAL_ADDR(jit, res_addr),
|
|
ir_ADD_U32(ir_ZEXT_U32(ref), ir_CONST_U32(IS_FALSE)));
|
|
ir_END_list(end_inputs);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (!smart_branch_opcode || exit_addr) {
|
|
if (end_inputs) {
|
|
ir_MERGE_list(end_inputs);
|
|
} else if (exit_addr && !jit->ctx.control) {
|
|
ir_BEGIN(IR_UNUSED); /* unreachable block */
|
|
}
|
|
} else {
|
|
_zend_jit_merge_smart_branch_inputs(jit, true_label, false_label, true_inputs, false_inputs);
|
|
}
|
|
|
|
return 1;
|
|
}
|
|
|
|
static int zend_jit_isset_isempty_cv(zend_jit_ctx *jit, const zend_op *opline, uint32_t op1_info, zend_jit_addr op1_addr, uint8_t smart_branch_opcode, uint32_t target_label, uint32_t target_label2, const void *exit_addr)
|
|
{
|
|
zend_jit_addr res_addr = RES_ADDR();
|
|
uint32_t true_label = -1, false_label = -1;
|
|
ir_ref end_inputs = IR_UNUSED, true_inputs = IR_UNUSED, false_inputs = IR_UNUSED;
|
|
|
|
// TODO: support for empty() ???
|
|
ZEND_ASSERT(opline->extended_value != MAY_BE_RESOURCE);
|
|
|
|
if (smart_branch_opcode && !exit_addr) {
|
|
if (smart_branch_opcode == ZEND_JMPZ) {
|
|
true_label = target_label2;
|
|
false_label = target_label;
|
|
} else if (smart_branch_opcode == ZEND_JMPNZ) {
|
|
true_label = target_label;
|
|
false_label = target_label2;
|
|
} else {
|
|
ZEND_UNREACHABLE();
|
|
}
|
|
} else {
|
|
res_addr = RES_ADDR();
|
|
}
|
|
|
|
if (op1_info & MAY_BE_REF) {
|
|
ir_ref ref = jit_ZVAL_ADDR(jit, op1_addr);
|
|
ref = jit_ZVAL_DEREF_ref(jit, ref);
|
|
op1_addr = ZEND_ADDR_REF_ZVAL(ref);
|
|
}
|
|
|
|
if (!(op1_info & (MAY_BE_UNDEF|MAY_BE_NULL))) {
|
|
if (exit_addr) {
|
|
ZEND_ASSERT(smart_branch_opcode == ZEND_JMPZ);
|
|
} else if (smart_branch_opcode) {
|
|
ir_END_list(true_inputs);
|
|
} else {
|
|
jit_set_Z_TYPE_INFO(jit, res_addr, IS_TRUE);
|
|
ir_END_list(end_inputs);
|
|
}
|
|
} 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) {
|
|
ir_END_list(false_inputs);
|
|
} else {
|
|
jit_set_Z_TYPE_INFO(jit, res_addr, IS_FALSE);
|
|
ir_END_list(end_inputs);
|
|
}
|
|
} else {
|
|
ir_ref ref = ir_GT(jit_Z_TYPE(jit, op1_addr), ir_CONST_U8(IS_NULL));
|
|
if (exit_addr) {
|
|
if (smart_branch_opcode == ZEND_JMPNZ) {
|
|
ir_GUARD_NOT(ref, ir_CONST_ADDR(exit_addr));
|
|
} else {
|
|
ir_GUARD(ref, ir_CONST_ADDR(exit_addr));
|
|
}
|
|
} else if (smart_branch_opcode) {
|
|
ir_ref if_val = ir_IF(ref);
|
|
ir_IF_TRUE(if_val);
|
|
ir_END_list(true_inputs);
|
|
ir_IF_FALSE(if_val);
|
|
ir_END_list(false_inputs);
|
|
} else {
|
|
jit_set_Z_TYPE_INFO_ref(jit, jit_ZVAL_ADDR(jit, res_addr),
|
|
ir_ADD_U32(ir_ZEXT_U32(ref), ir_CONST_U32(IS_FALSE)));
|
|
ir_END_list(end_inputs);
|
|
}
|
|
}
|
|
|
|
if (!smart_branch_opcode || exit_addr) {
|
|
if (end_inputs) {
|
|
ir_MERGE_list(end_inputs);
|
|
}
|
|
} else {
|
|
_zend_jit_merge_smart_branch_inputs(jit, true_label, false_label, true_inputs, false_inputs);
|
|
}
|
|
|
|
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(zend_jit_ctx *jit, 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;
|
|
}
|
|
|
|
// JIT: if (EG(vm_stack_end) - EG(vm_stack_top) < used_stack)
|
|
ir_GUARD(
|
|
ir_UGE(
|
|
ir_SUB_A(ir_LOAD_A(jit_EG(vm_stack_end)), ir_LOAD_A(jit_EG(vm_stack_top))),
|
|
ir_CONST_ADDR(used_stack)),
|
|
ir_CONST_ADDR(exit_addr));
|
|
|
|
return 1;
|
|
}
|
|
|
|
static int zend_jit_free_trampoline(zend_jit_ctx *jit, int8_t func_reg)
|
|
{
|
|
// JIT: if (UNEXPECTED(func->common.fn_flags & ZEND_ACC_CALL_VIA_TRAMPOLINE))
|
|
ir_ref func = ir_RLOAD_A(func_reg);
|
|
ir_ref if_trampoline = ir_IF(ir_AND_U32(
|
|
ir_LOAD_U32(ir_ADD_OFFSET(func, offsetof(zend_function, common.fn_flags))),
|
|
ir_CONST_U32(ZEND_ACC_CALL_VIA_TRAMPOLINE)));
|
|
|
|
ir_IF_TRUE(if_trampoline);
|
|
ir_CALL_1(IR_VOID, ir_CONST_FC_FUNC(zend_jit_free_trampoline_helper), func);
|
|
ir_MERGE_WITH_EMPTY_FALSE(if_trampoline);
|
|
|
|
return 1;
|
|
}
|
|
|
|
static int zend_jit_push_call_frame(zend_jit_ctx *jit, const zend_op *opline, const zend_op_array *op_array, zend_function *func, bool is_closure, bool delayed_fetch_this, int checked_stack, ir_ref func_ref, ir_ref this_ref)
|
|
{
|
|
uint32_t used_stack;
|
|
ir_ref used_stack_ref = IR_UNUSED;
|
|
bool stack_check = 1;
|
|
ir_ref rx, ref, top, if_enough_stack, cold_path = IR_UNUSED;
|
|
|
|
ZEND_ASSERT(func_ref != IR_NULL);
|
|
if (func) {
|
|
used_stack = zend_vm_calc_used_stack(opline->extended_value, func);
|
|
if ((int)used_stack <= checked_stack) {
|
|
stack_check = 0;
|
|
}
|
|
used_stack_ref = ir_CONST_ADDR(used_stack);
|
|
} else {
|
|
ir_ref num_args_ref;
|
|
ir_ref if_internal_func = IR_UNUSED;
|
|
|
|
used_stack = (ZEND_CALL_FRAME_SLOT + opline->extended_value + ZEND_OBSERVER_ENABLED) * sizeof(zval);
|
|
used_stack_ref = ir_CONST_ADDR(used_stack);
|
|
|
|
if (!is_closure) {
|
|
used_stack_ref = ir_HARD_COPY_A(used_stack_ref); /* load constant once */
|
|
|
|
// JIT: if (EXPECTED(ZEND_USER_CODE(func->type))) {
|
|
ir_ref tmp = ir_LOAD_U8(ir_ADD_OFFSET(func_ref, offsetof(zend_function, type)));
|
|
if_internal_func = ir_IF(ir_AND_U8(tmp, ir_CONST_U8(1)));
|
|
ir_IF_FALSE(if_internal_func);
|
|
}
|
|
|
|
// JIT: used_stack += (func->op_array.last_var + func->op_array.T - MIN(func->op_array.num_args, num_args)) * sizeof(zval);
|
|
num_args_ref = ir_CONST_U32(opline->extended_value);
|
|
if (!is_closure) {
|
|
ref = ir_SUB_U32(
|
|
ir_SUB_U32(
|
|
ir_MIN_U32(
|
|
num_args_ref,
|
|
ir_LOAD_U32(ir_ADD_OFFSET(func_ref, offsetof(zend_function, op_array.num_args)))),
|
|
ir_LOAD_U32(ir_ADD_OFFSET(func_ref, offsetof(zend_function, op_array.last_var)))),
|
|
ir_LOAD_U32(ir_ADD_OFFSET(func_ref, offsetof(zend_function, op_array.T))));
|
|
} else {
|
|
ref = ir_SUB_U32(
|
|
ir_SUB_U32(
|
|
ir_MIN_U32(
|
|
num_args_ref,
|
|
ir_LOAD_U32(ir_ADD_OFFSET(func_ref, offsetof(zend_closure, func.op_array.num_args)))),
|
|
ir_LOAD_U32(ir_ADD_OFFSET(func_ref, offsetof(zend_closure, func.op_array.last_var)))),
|
|
ir_LOAD_U32(ir_ADD_OFFSET(func_ref, offsetof(zend_closure, func.op_array.T))));
|
|
}
|
|
ref = ir_MUL_U32(ref, ir_CONST_U32(sizeof(zval)));
|
|
if (sizeof(void*) == 8) {
|
|
ref = ir_SEXT_A(ref);
|
|
}
|
|
ref = ir_SUB_A(used_stack_ref, ref);
|
|
|
|
if (is_closure) {
|
|
used_stack_ref = ref;
|
|
} else {
|
|
ir_MERGE_WITH_EMPTY_TRUE(if_internal_func);
|
|
used_stack_ref = ir_PHI_2(IR_ADDR, ref, used_stack_ref);
|
|
}
|
|
}
|
|
|
|
zend_jit_start_reuse_ip(jit);
|
|
|
|
// JIT: if (UNEXPECTED(used_stack > (size_t)(((char*)EG(vm_stack_end)) - (char*)call))) {
|
|
jit_STORE_IP(jit, ir_LOAD_A(jit_EG(vm_stack_top)));
|
|
|
|
if (stack_check) {
|
|
// JIT: Check Stack Overflow
|
|
ref = ir_UGE(
|
|
ir_SUB_A(
|
|
ir_LOAD_A(jit_EG(vm_stack_end)),
|
|
jit_IP(jit)),
|
|
used_stack_ref);
|
|
|
|
if (JIT_G(trigger) == ZEND_JIT_ON_HOT_TRACE) {
|
|
bool may_be_trampoline = !func && (opline->opcode == ZEND_INIT_METHOD_CALL);
|
|
int32_t exit_point = zend_jit_trace_get_exit_point(opline,
|
|
may_be_trampoline ?
|
|
(ZEND_JIT_EXIT_TO_VM | ZEND_JIT_EXIT_METHOD_CALL) : ZEND_JIT_EXIT_TO_VM);
|
|
const void *exit_addr = zend_jit_trace_get_exit_addr(exit_point);
|
|
|
|
if (!exit_addr) {
|
|
return 0;
|
|
}
|
|
|
|
if (may_be_trampoline) {
|
|
jit->trace->exit_info[exit_point].poly_func_ref = func_ref;
|
|
jit->trace->exit_info[exit_point].poly_this_ref = this_ref;
|
|
}
|
|
|
|
ir_GUARD(ref, ir_CONST_ADDR(exit_addr));
|
|
} else {
|
|
if_enough_stack = ir_IF(ref);
|
|
ir_IF_FALSE_cold(if_enough_stack);
|
|
|
|
#ifdef _WIN32
|
|
if (0) {
|
|
#else
|
|
if (opline->opcode == ZEND_INIT_FCALL && func && func->type == ZEND_INTERNAL_FUNCTION) {
|
|
#endif
|
|
jit_SET_EX_OPLINE(jit, opline);
|
|
ref = ir_CALL_1(IR_ADDR, ir_CONST_FC_FUNC(zend_jit_int_extend_stack_helper), used_stack_ref);
|
|
} else {
|
|
if (!is_closure) {
|
|
ref = func_ref;
|
|
} else {
|
|
ref = ir_ADD_OFFSET(func_ref, offsetof(zend_closure, func));
|
|
}
|
|
jit_SET_EX_OPLINE(jit, opline);
|
|
ref = ir_CALL_2(IR_ADDR, ir_CONST_FC_FUNC(zend_jit_extend_stack_helper),
|
|
used_stack_ref, ref);
|
|
}
|
|
jit_STORE_IP(jit, ref);
|
|
|
|
cold_path = ir_END();
|
|
ir_IF_TRUE(if_enough_stack);
|
|
}
|
|
}
|
|
|
|
ref = jit_EG(vm_stack_top);
|
|
rx = jit_IP(jit);
|
|
#if !OPTIMIZE_FOR_SIZE
|
|
/* JIT: EG(vm_stack_top) = (zval*)((char*)call + used_stack);
|
|
* This vesions is longer but faster
|
|
* mov EG(vm_stack_top), %CALL
|
|
* lea size(%call), %tmp
|
|
* mov %tmp, EG(vm_stack_top)
|
|
*/
|
|
top = rx;
|
|
#else
|
|
/* JIT: EG(vm_stack_top) += used_stack;
|
|
* Use ir_emit() because ir_LOAD() makes load forwarding and doesn't allow load/store fusion
|
|
* mov EG(vm_stack_top), %CALL
|
|
* add $size, EG(vm_stack_top)
|
|
*/
|
|
top = jit->ctx.control = ir_emit2(&jit->ctx, IR_OPT(IR_LOAD, IR_ADDR), jit->ctx.control, ref);
|
|
#endif
|
|
ir_STORE(ref, ir_ADD_A(top, used_stack_ref));
|
|
|
|
// JIT: 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) {
|
|
// JIT: ZEND_SET_CALL_INFO(call, 0, call_info);
|
|
ir_STORE(jit_CALL(rx, This.u1.type_info), ir_CONST_U32(IS_UNDEF | ZEND_CALL_NESTED_FUNCTION));
|
|
}
|
|
#ifdef _WIN32
|
|
if (0) {
|
|
#else
|
|
if (opline->opcode == ZEND_INIT_FCALL && func && func->type == ZEND_INTERNAL_FUNCTION) {
|
|
#endif
|
|
if (cold_path) {
|
|
ir_MERGE_WITH(cold_path);
|
|
rx = jit_IP(jit);
|
|
}
|
|
|
|
// JIT: call->func = func;
|
|
ir_STORE(jit_CALL(rx, func), func_ref);
|
|
} else {
|
|
if (!is_closure) {
|
|
// JIT: call->func = func;
|
|
ir_STORE(jit_CALL(rx, func), func_ref);
|
|
} else {
|
|
// JIT: call->func = &closure->func;
|
|
ir_STORE(jit_CALL(rx, func), ir_ADD_OFFSET(func_ref, offsetof(zend_closure, func)));
|
|
}
|
|
if (cold_path) {
|
|
ir_MERGE_WITH(cold_path);
|
|
rx = jit_IP(jit);
|
|
}
|
|
}
|
|
if (opline->opcode == ZEND_INIT_METHOD_CALL) {
|
|
// JIT: Z_PTR(call->This) = obj;
|
|
ZEND_ASSERT(this_ref != IR_NULL);
|
|
ir_STORE(jit_CALL(rx, This.value.ptr), this_ref);
|
|
if (opline->op1_type == IS_UNUSED || delayed_fetch_this) {
|
|
// JIT: call->call_info |= ZEND_CALL_HAS_THIS;
|
|
ref = jit_CALL(rx, This.u1.type_info);
|
|
if (JIT_G(trigger) == ZEND_JIT_ON_HOT_TRACE) {
|
|
ir_STORE(ref, ir_CONST_U32( ZEND_CALL_HAS_THIS));
|
|
} else {
|
|
ir_STORE(ref, ir_OR_U32(ir_LOAD_U32(ref), ir_CONST_U32(ZEND_CALL_HAS_THIS)));
|
|
}
|
|
} else {
|
|
if (opline->op1_type == IS_CV) {
|
|
// JIT: GC_ADDREF(obj);
|
|
jit_GC_ADDREF(jit, this_ref);
|
|
}
|
|
|
|
// JIT: call->call_info |= ZEND_CALL_HAS_THIS | ZEND_CALL_RELEASE_THIS;
|
|
ref = jit_CALL(rx, This.u1.type_info);
|
|
if (JIT_G(trigger) == ZEND_JIT_ON_HOT_TRACE) {
|
|
ir_STORE(ref, ir_CONST_U32( ZEND_CALL_HAS_THIS | ZEND_CALL_RELEASE_THIS));
|
|
} else {
|
|
ir_STORE(ref,
|
|
ir_OR_U32(ir_LOAD_U32(ref),
|
|
ir_CONST_U32(ZEND_CALL_HAS_THIS | ZEND_CALL_RELEASE_THIS)));
|
|
}
|
|
}
|
|
} else if (opline->opcode == ZEND_INIT_STATIC_METHOD_CALL) {
|
|
// JIT: Z_CE(call->This) = called_scope;
|
|
ir_STORE(jit_CALL(rx, This), this_ref);
|
|
} else if (!is_closure) {
|
|
// JIT: Z_CE(call->This) = called_scope;
|
|
ir_STORE(jit_CALL(rx, This), IR_NULL);
|
|
} else {
|
|
ir_ref object_or_called_scope, call_info, call_info2, object, if_cond;
|
|
|
|
if (opline->op2_type == IS_CV) {
|
|
// JIT: GC_ADDREF(closure);
|
|
jit_GC_ADDREF(jit, func_ref);
|
|
}
|
|
|
|
// JIT: RX(object_or_called_scope) = closure->called_scope;
|
|
object_or_called_scope = ir_LOAD_A(ir_ADD_OFFSET(func_ref, offsetof(zend_closure, called_scope)));
|
|
|
|
// JIT: call_info = ZEND_CALL_NESTED_FUNCTION | ZEND_CALL_DYNAMIC | ZEND_CALL_CLOSURE |
|
|
// (closure->func->common.fn_flags & ZEND_ACC_FAKE_CLOSURE);
|
|
call_info = ir_OR_U32(
|
|
ir_AND_U32(
|
|
ir_LOAD_U32(ir_ADD_OFFSET(func_ref, offsetof(zend_closure, func.common.fn_flags))),
|
|
ir_CONST_U32(ZEND_ACC_FAKE_CLOSURE)),
|
|
ir_CONST_U32(ZEND_CALL_NESTED_FUNCTION | ZEND_CALL_DYNAMIC | ZEND_CALL_CLOSURE));
|
|
// JIT: if (Z_TYPE(closure->this_ptr) != IS_UNDEF) {
|
|
if_cond = ir_IF(ir_LOAD_U8(ir_ADD_OFFSET(func_ref, offsetof(zend_closure, this_ptr.u1.v.type))));
|
|
ir_IF_TRUE(if_cond);
|
|
|
|
// JIT: call_info |= ZEND_CALL_HAS_THIS;
|
|
call_info2 = ir_OR_U32(call_info, ir_CONST_U32(ZEND_CALL_HAS_THIS));
|
|
|
|
// JIT: object_or_called_scope = Z_OBJ(closure->this_ptr);
|
|
object = ir_LOAD_A(ir_ADD_OFFSET(func_ref, offsetof(zend_closure, this_ptr.value.ptr)));
|
|
|
|
ir_MERGE_WITH_EMPTY_FALSE(if_cond);
|
|
call_info = ir_PHI_2(IR_U32, call_info2, call_info);
|
|
object_or_called_scope = ir_PHI_2(IR_ADDR, object, object_or_called_scope);
|
|
|
|
// JIT: ZEND_SET_CALL_INFO(call, 0, call_info);
|
|
ref = jit_CALL(rx, This.u1.type_info);
|
|
ir_STORE(ref, ir_OR_U32(ir_LOAD_U32(ref), call_info));
|
|
|
|
// JIT: Z_PTR(call->This) = object_or_called_scope;
|
|
ir_STORE(jit_CALL(rx, This.value.ptr), object_or_called_scope);
|
|
|
|
// JIT: if (closure->func.op_array.run_time_cache__ptr)
|
|
if_cond = ir_IF(ir_LOAD_A(ir_ADD_OFFSET(func_ref, offsetof(zend_closure, func.op_array.run_time_cache__ptr))));
|
|
ir_IF_FALSE(if_cond);
|
|
|
|
// JIT: zend_jit_init_func_run_time_cache_helper(closure->func);
|
|
ir_CALL_1(IR_VOID, ir_CONST_FC_FUNC(zend_jit_init_func_run_time_cache_helper),
|
|
ir_ADD_OFFSET(func_ref, offsetof(zend_closure, func)));
|
|
|
|
ir_MERGE_WITH_EMPTY_TRUE(if_cond);
|
|
}
|
|
|
|
// JIT: ZEND_CALL_NUM_ARGS(call) = num_args;
|
|
ir_STORE(jit_CALL(rx, This.u2.num_args), ir_CONST_U32(opline->extended_value));
|
|
|
|
return 1;
|
|
}
|
|
|
|
static int zend_jit_init_fcall_guard(zend_jit_ctx *jit, uint32_t level, const zend_function *func, const zend_op *to_opline)
|
|
{
|
|
int32_t exit_point;
|
|
const void *exit_addr;
|
|
ir_ref call;
|
|
|
|
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);
|
|
call = ir_LOAD_A(jit_EX(call));
|
|
while (level > 0) {
|
|
// call = call->prev_execute_data
|
|
call = ir_LOAD_A(jit_CALL(call, 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;
|
|
|
|
// JIT: if (call->func.op_array.opcodes != opcodes) goto exit_addr;
|
|
ir_GUARD(
|
|
ir_EQ(
|
|
ir_LOAD_A(ir_ADD_OFFSET(ir_LOAD_A(jit_CALL(call, func)), offsetof(zend_op_array, opcodes))),
|
|
ir_CONST_ADDR(opcodes)),
|
|
ir_CONST_ADDR(exit_addr));
|
|
} else {
|
|
// JIT: if (call->func != func) goto exit_addr;
|
|
ir_GUARD(ir_EQ(ir_LOAD_A(jit_CALL(call, func)), ir_CONST_ADDR(func)), ir_CONST_ADDR(exit_addr));
|
|
}
|
|
|
|
return 1;
|
|
}
|
|
|
|
static int zend_jit_init_fcall(zend_jit_ctx *jit, 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, int checked_stack)
|
|
{
|
|
zend_func_info *info = ZEND_FUNC_INFO(op_array);
|
|
zend_call_info *call_info = NULL;
|
|
zend_function *func = NULL;
|
|
ir_ref func_ref = IR_UNUSED;
|
|
|
|
if (jit->delayed_call_level) {
|
|
if (!zend_jit_save_call_chain(jit, jit->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 && !call_info->is_prototype) {
|
|
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 */
|
|
func_ref = ir_CONST_ADDR(func);
|
|
} else if (func && op_array == &func->op_array) {
|
|
/* recursive call */
|
|
if (!(func->op_array.fn_flags & ZEND_ACC_IMMUTABLE)
|
|
|| zend_jit_prefer_const_addr_load(jit, (uintptr_t)func)) {
|
|
func_ref = ir_LOAD_A(jit_EX(func));
|
|
} else {
|
|
func_ref = ir_CONST_ADDR(func);
|
|
}
|
|
} else {
|
|
ir_ref if_func, cache_slot_ref, ref;
|
|
|
|
// JIT: if (CACHED_PTR(opline->result.num))
|
|
cache_slot_ref = ir_ADD_OFFSET(ir_LOAD_A(jit_EX(run_time_cache)), opline->result.num);
|
|
func_ref = ir_LOAD_A(cache_slot_ref);
|
|
if (JIT_G(trigger) == ZEND_JIT_ON_HOT_TRACE
|
|
&& func
|
|
&& (func->common.fn_flags & ZEND_ACC_IMMUTABLE)
|
|
&& opline->opcode != ZEND_INIT_FCALL) {
|
|
/* Called func may be changed because of recompilation. See ext/opcache/tests/jit/init_fcall_003.phpt */
|
|
if_func = ir_IF(ir_EQ(func_ref, ir_CONST_ADDR(func)));
|
|
} else {
|
|
if_func = ir_IF(func_ref);
|
|
}
|
|
ir_IF_FALSE_cold(if_func);
|
|
if (opline->opcode == ZEND_INIT_FCALL
|
|
&& func
|
|
&& func->type == ZEND_USER_FUNCTION
|
|
&& (func->op_array.fn_flags & ZEND_ACC_IMMUTABLE)) {
|
|
ref = ir_HARD_COPY_A(ir_CONST_ADDR(func)); /* load constant once */
|
|
ir_STORE(cache_slot_ref, ref);
|
|
ref = ir_CALL_1(IR_ADDR, ir_CONST_FC_FUNC(zend_jit_init_func_run_time_cache_helper), ref);
|
|
} else {
|
|
zval *zv = RT_CONSTANT(opline, opline->op2);
|
|
|
|
if (opline->opcode == ZEND_INIT_FCALL) {
|
|
ref = ir_CALL_2(IR_ADDR, ir_CONST_FC_FUNC(zend_jit_find_func_helper),
|
|
ir_CONST_ADDR(Z_STR_P(zv)),
|
|
cache_slot_ref);
|
|
} else if (opline->opcode == ZEND_INIT_FCALL_BY_NAME) {
|
|
ref = ir_CALL_2(IR_ADDR, ir_CONST_FC_FUNC(zend_jit_find_func_helper),
|
|
ir_CONST_ADDR(Z_STR_P(zv + 1)),
|
|
cache_slot_ref);
|
|
} else if (opline->opcode == ZEND_INIT_NS_FCALL_BY_NAME) {
|
|
ref = ir_CALL_2(IR_ADDR, ir_CONST_FC_FUNC(zend_jit_find_ns_func_helper),
|
|
ir_CONST_ADDR(zv),
|
|
cache_slot_ref);
|
|
} else {
|
|
ZEND_UNREACHABLE();
|
|
}
|
|
if (JIT_G(trigger) == ZEND_JIT_ON_HOT_TRACE) {
|
|
int32_t exit_point = zend_jit_trace_get_exit_point(opline,
|
|
func && (func->common.fn_flags & ZEND_ACC_IMMUTABLE) ? ZEND_JIT_EXIT_INVALIDATE : 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) {
|
|
ir_GUARD(ref, ir_CONST_ADDR(exit_addr));
|
|
} else if (func->type == ZEND_USER_FUNCTION
|
|
&& !(func->common.fn_flags & ZEND_ACC_IMMUTABLE)) {
|
|
const zend_op *opcodes = func->op_array.opcodes;
|
|
|
|
ir_GUARD(
|
|
ir_EQ(
|
|
ir_LOAD_A(ir_ADD_OFFSET(ref, offsetof(zend_op_array, opcodes))),
|
|
ir_CONST_ADDR(opcodes)),
|
|
ir_CONST_ADDR(exit_addr));
|
|
} else {
|
|
ir_GUARD(ir_EQ(ref, ir_CONST_ADDR(func)), ir_CONST_ADDR(exit_addr));
|
|
}
|
|
} else {
|
|
jit_SET_EX_OPLINE(jit, opline);
|
|
ir_GUARD(ref, jit_STUB_ADDR(jit, jit_stub_undefined_function));
|
|
}
|
|
}
|
|
ir_MERGE_WITH_EMPTY_TRUE(if_func);
|
|
func_ref = ir_PHI_2(IR_ADDR, ref, func_ref);
|
|
}
|
|
|
|
if (!zend_jit_push_call_frame(jit, opline, op_array, func, 0, 0, checked_stack, func_ref, IR_UNUSED)) {
|
|
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(jit, call_level)) {
|
|
return 0;
|
|
}
|
|
} else {
|
|
ZEND_ASSERT(call_level > 0);
|
|
jit->delayed_call_level = call_level;
|
|
delayed_call_chain = 1;
|
|
}
|
|
|
|
return 1;
|
|
}
|
|
|
|
static int zend_jit_init_method_call(zend_jit_ctx *jit,
|
|
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,
|
|
bool ce_is_instanceof,
|
|
bool on_this,
|
|
bool delayed_fetch_this,
|
|
zend_class_entry *trace_ce,
|
|
zend_jit_trace_rec *trace,
|
|
int checked_stack,
|
|
int8_t func_reg,
|
|
int8_t this_reg,
|
|
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;
|
|
ir_ref if_static = IR_UNUSED, cold_path, this_ref = IR_NULL, func_ref = IR_NULL;
|
|
|
|
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 && !call_info->is_prototype) {
|
|
func = call_info->callee_func;
|
|
}
|
|
}
|
|
|
|
if (polymorphic_side_trace) {
|
|
/* function is passed in r0 from parent_trace */
|
|
ZEND_ASSERT(func_reg >= 0 && this_reg >= 0);
|
|
func_ref = zend_jit_deopt_rload(jit, IR_ADDR, func_reg);
|
|
this_ref = zend_jit_deopt_rload(jit, IR_ADDR, this_reg);
|
|
} else {
|
|
ir_ref ref, ref2, if_found, fast_path, run_time_cache, this_ref2;
|
|
|
|
if (on_this) {
|
|
zend_jit_addr this_addr = ZEND_ADDR_MEM_ZVAL(ZREG_FP, offsetof(zend_execute_data, This));
|
|
this_ref = jit_Z_PTR(jit, this_addr);
|
|
} else {
|
|
if (op1_info & MAY_BE_REF) {
|
|
if (opline->op1_type == IS_CV) {
|
|
// JIT: ZVAL_DEREF(op1)
|
|
ir_ref ref = jit_ZVAL_ADDR(jit, op1_addr);
|
|
ref = jit_ZVAL_DEREF_ref(jit, ref);
|
|
op1_addr = ZEND_ADDR_REF_ZVAL(ref);
|
|
} else {
|
|
ir_ref if_ref;
|
|
|
|
/* Hack: Convert reference to regular value to simplify JIT code */
|
|
ZEND_ASSERT(Z_REG(op1_addr) == ZREG_FP);
|
|
|
|
if_ref = jit_if_Z_TYPE(jit, op1_addr, IS_REFERENCE);
|
|
ir_IF_TRUE(if_ref);
|
|
ir_CALL_1(IR_VOID, ir_CONST_FC_FUNC(zend_jit_unref_helper), jit_ZVAL_ADDR(jit, op1_addr));
|
|
|
|
ir_MERGE_WITH_EMPTY_FALSE(if_ref);
|
|
}
|
|
}
|
|
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;
|
|
}
|
|
ir_GUARD(ir_EQ(jit_Z_TYPE(jit, op1_addr), ir_CONST_U8(IS_OBJECT)),
|
|
ir_CONST_ADDR(exit_addr));
|
|
} else {
|
|
ir_ref if_object = jit_if_Z_TYPE(jit, op1_addr, IS_OBJECT);
|
|
|
|
ir_IF_FALSE_cold(if_object);
|
|
|
|
jit_SET_EX_OPLINE(jit, opline);
|
|
if ((opline->op1_type & (IS_VAR|IS_TMP_VAR)) && !delayed_fetch_this) {
|
|
ir_CALL_1(IR_VOID, ir_CONST_FC_FUNC(zend_jit_invalid_method_call_tmp),
|
|
jit_ZVAL_ADDR(jit, op1_addr));
|
|
} else {
|
|
ir_CALL_1(IR_VOID, ir_CONST_FC_FUNC(zend_jit_invalid_method_call),
|
|
jit_ZVAL_ADDR(jit, op1_addr));
|
|
}
|
|
ir_IJMP(jit_STUB_ADDR(jit, jit_stub_exception_handler));
|
|
ir_IF_TRUE(if_object);
|
|
}
|
|
}
|
|
|
|
this_ref = jit_Z_PTR(jit, op1_addr);
|
|
}
|
|
|
|
if (jit->delayed_call_level) {
|
|
if (!zend_jit_save_call_chain(jit, jit->delayed_call_level)) {
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
if (func) {
|
|
// JIT: fbc = CACHED_PTR(opline->result.num + sizeof(void*));
|
|
ref = ir_LOAD_A(ir_ADD_OFFSET(ir_LOAD_A(jit_EX(run_time_cache)), opline->result.num + sizeof(void*)));
|
|
|
|
if_found = ir_IF(ref);
|
|
ir_IF_TRUE(if_found);
|
|
fast_path = ir_END();
|
|
} else {
|
|
// JIT: if (CACHED_PTR(opline->result.num) == obj->ce)) {
|
|
run_time_cache = ir_LOAD_A(jit_EX(run_time_cache));
|
|
ref = ir_EQ(
|
|
ir_LOAD_A(ir_ADD_OFFSET(run_time_cache, opline->result.num)),
|
|
ir_LOAD_A(ir_ADD_OFFSET(this_ref, offsetof(zend_object, ce))));
|
|
if_found = ir_IF(ref);
|
|
ir_IF_TRUE(if_found);
|
|
|
|
// JIT: fbc = CACHED_PTR(opline->result.num + sizeof(void*));
|
|
ref = ir_LOAD_A(ir_ADD_OFFSET(run_time_cache, opline->result.num + sizeof(void*)));
|
|
fast_path = ir_END();
|
|
|
|
}
|
|
|
|
ir_IF_FALSE_cold(if_found);
|
|
jit_SET_EX_OPLINE(jit, opline);
|
|
|
|
if (!jit->ctx.fixed_call_stack_size) {
|
|
// JIT: alloca(sizeof(void*));
|
|
this_ref2 = ir_ALLOCA(ir_CONST_ADDR(0x10));
|
|
} else {
|
|
this_ref2 = ir_HARD_COPY_A(ir_RLOAD_A(IR_REG_SP));
|
|
}
|
|
ir_STORE(this_ref2, this_ref);
|
|
|
|
if ((opline->op1_type & (IS_VAR|IS_TMP_VAR)) && !delayed_fetch_this) {
|
|
ref2 = ir_CALL_3(IR_ADDR, ir_CONST_FC_FUNC(zend_jit_find_method_tmp_helper),
|
|
this_ref,
|
|
ir_CONST_ADDR(function_name),
|
|
this_ref2);
|
|
} else {
|
|
ref2 = ir_CALL_3(IR_ADDR, ir_CONST_FC_FUNC(zend_jit_find_method_helper),
|
|
this_ref,
|
|
ir_CONST_ADDR(function_name),
|
|
this_ref2);
|
|
}
|
|
|
|
this_ref2 = ir_LOAD_A(ir_RLOAD_A(IR_REG_SP));
|
|
if (!jit->ctx.fixed_call_stack_size) {
|
|
// JIT: revert alloca
|
|
ir_AFREE(ir_CONST_ADDR(0x10));
|
|
}
|
|
|
|
ir_GUARD(ref2, jit_STUB_ADDR(jit, jit_stub_exception_handler));
|
|
|
|
ir_MERGE_WITH(fast_path);
|
|
func_ref = ir_PHI_2(IR_ADDR, ref2, ref);
|
|
this_ref = ir_PHI_2(IR_ADDR, this_ref2, this_ref);
|
|
}
|
|
|
|
if ((!func || zend_jit_may_be_modified(func, op_array))
|
|
&& 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, func ? ZEND_JIT_EXIT_INVALIDATE : ZEND_JIT_EXIT_METHOD_CALL);
|
|
exit_addr = zend_jit_trace_get_exit_addr(exit_point);
|
|
if (!exit_addr) {
|
|
return 0;
|
|
}
|
|
|
|
jit->trace->exit_info[exit_point].poly_func_ref = func_ref;
|
|
jit->trace->exit_info[exit_point].poly_this_ref = this_ref;
|
|
|
|
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;
|
|
|
|
ir_GUARD(
|
|
ir_EQ(
|
|
ir_LOAD_A(ir_ADD_OFFSET(func_ref, offsetof(zend_op_array, opcodes))),
|
|
ir_CONST_ADDR(opcodes)),
|
|
ir_CONST_ADDR(exit_addr));
|
|
} else {
|
|
ir_GUARD(ir_EQ(func_ref, ir_CONST_ADDR(func)), ir_CONST_ADDR(exit_addr));
|
|
}
|
|
}
|
|
|
|
if (!func) {
|
|
// JIT: if (fbc->common.fn_flags & ZEND_ACC_STATIC) {
|
|
if_static = ir_IF(ir_AND_U32(
|
|
ir_LOAD_U32(ir_ADD_OFFSET(func_ref, offsetof(zend_function, common.fn_flags))),
|
|
ir_CONST_U32(ZEND_ACC_STATIC)));
|
|
ir_IF_TRUE_cold(if_static);
|
|
}
|
|
|
|
if (!func || (func->common.fn_flags & ZEND_ACC_STATIC) != 0) {
|
|
ir_ref ret;
|
|
|
|
if ((opline->op1_type & (IS_VAR|IS_TMP_VAR)) && !delayed_fetch_this) {
|
|
ret = ir_CALL_3(IR_ADDR, ir_CONST_FC_FUNC(zend_jit_push_static_method_call_frame_tmp),
|
|
this_ref,
|
|
func_ref,
|
|
ir_CONST_U32(opline->extended_value));
|
|
} else {
|
|
ret = ir_CALL_3(IR_ADDR, ir_CONST_FC_FUNC(zend_jit_push_static_method_call_frame),
|
|
this_ref,
|
|
func_ref,
|
|
ir_CONST_U32(opline->extended_value));
|
|
}
|
|
|
|
if ((opline->op1_type & (IS_VAR|IS_TMP_VAR) && !delayed_fetch_this)) {
|
|
ir_GUARD(ret, jit_STUB_ADDR(jit, jit_stub_exception_handler));
|
|
}
|
|
jit_STORE_IP(jit, ret);
|
|
}
|
|
|
|
if (!func) {
|
|
cold_path = ir_END();
|
|
ir_IF_FALSE(if_static);
|
|
}
|
|
|
|
if (!func || (func->common.fn_flags & ZEND_ACC_STATIC) == 0) {
|
|
if (!zend_jit_push_call_frame(jit, opline, NULL, func, 0, delayed_fetch_this, checked_stack, func_ref, this_ref)) {
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
if (!func) {
|
|
ir_MERGE_WITH(cold_path);
|
|
}
|
|
zend_jit_start_reuse_ip(jit);
|
|
|
|
if (zend_jit_needs_call_chain(call_info, b, op_array, ssa, ssa_op, opline, call_level, trace)) {
|
|
if (!zend_jit_save_call_chain(jit, call_level)) {
|
|
return 0;
|
|
}
|
|
} else {
|
|
ZEND_ASSERT(call_level > 0);
|
|
delayed_call_chain = 1;
|
|
jit->delayed_call_level = call_level;
|
|
}
|
|
|
|
if (trace
|
|
&& trace->op == ZEND_JIT_TRACE_END
|
|
&& trace->stop >= ZEND_JIT_TRACE_STOP_INTERPRETER) {
|
|
if (!zend_jit_set_ip(jit, opline + 1)) {
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
return 1;
|
|
}
|
|
|
|
static int zend_jit_init_static_method_call(zend_jit_ctx *jit,
|
|
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,
|
|
int checked_stack)
|
|
{
|
|
zend_func_info *info = ZEND_FUNC_INFO(op_array);
|
|
zend_call_info *call_info = NULL;
|
|
zend_class_entry *ce;
|
|
zend_function *func = NULL;
|
|
ir_ref func_ref, func_ref2, scope_ref, scope_ref2, if_cached, cold_path, ref;
|
|
ir_ref if_static = IR_UNUSED;
|
|
|
|
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 && !call_info->is_prototype) {
|
|
func = call_info->callee_func;
|
|
}
|
|
}
|
|
|
|
ce = zend_get_known_class(op_array, opline, opline->op1_type, opline->op1);
|
|
if (!func && ce) {
|
|
zval *zv = RT_CONSTANT(opline, opline->op2);
|
|
zend_string *method_name;
|
|
|
|
ZEND_ASSERT(Z_TYPE_P(zv) == IS_STRING);
|
|
method_name = Z_STR_P(zv);
|
|
zv = zend_hash_find(&ce->function_table, method_name);
|
|
if (zv) {
|
|
zend_function *fn = Z_PTR_P(zv);
|
|
|
|
if (fn->common.scope == op_array->scope
|
|
|| (fn->common.fn_flags & ZEND_ACC_PUBLIC)
|
|
|| ((fn->common.fn_flags & ZEND_ACC_PROTECTED)
|
|
&& op_array->scope
|
|
&& instanceof_function_slow(op_array->scope, fn->common.scope))) {
|
|
func = fn;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (jit->delayed_call_level) {
|
|
if (!zend_jit_save_call_chain(jit, jit->delayed_call_level)) {
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
// JIT: fbc = CACHED_PTR(opline->result.num + sizeof(void*));
|
|
func_ref = ir_LOAD_A(ir_ADD_OFFSET(ir_LOAD_A(jit_EX(run_time_cache)), opline->result.num + sizeof(void*)));
|
|
|
|
// JIT: if (fbc)
|
|
if_cached = ir_IF(func_ref);
|
|
ir_IF_FALSE_cold(if_cached);
|
|
|
|
jit_SET_EX_OPLINE(jit, opline);
|
|
scope_ref2 = ir_CALL_1(IR_ADDR, ir_CONST_FC_FUNC(zend_jit_find_class_helper), jit_FP(jit));
|
|
ir_GUARD(scope_ref2, jit_STUB_ADDR(jit, jit_stub_exception_handler));
|
|
|
|
func_ref2 = ir_CALL_2(IR_ADDR, ir_CONST_FC_FUNC(zend_jit_find_static_method_helper), jit_FP(jit), scope_ref2);
|
|
ir_GUARD(func_ref2, jit_STUB_ADDR(jit, jit_stub_exception_handler));
|
|
|
|
cold_path = ir_END();
|
|
|
|
ir_IF_TRUE(if_cached);
|
|
if (ce && (ce->ce_flags & ZEND_ACC_IMMUTABLE) && (ce->ce_flags & ZEND_ACC_LINKED)) {
|
|
scope_ref = ir_CONST_ADDR(ce);
|
|
} else {
|
|
scope_ref = ir_LOAD_A(ir_ADD_OFFSET(ir_LOAD_A(jit_EX(run_time_cache)), opline->result.num));
|
|
}
|
|
|
|
ir_MERGE_2(cold_path, ir_END());
|
|
func_ref = ir_PHI_2(IR_ADDR, func_ref2, func_ref);
|
|
scope_ref = ir_PHI_2(IR_ADDR, scope_ref2, scope_ref);
|
|
|
|
if ((!func || zend_jit_may_be_modified(func, op_array))
|
|
&& 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, func ? ZEND_JIT_EXIT_INVALIDATE : 0);
|
|
exit_addr = zend_jit_trace_get_exit_addr(exit_point);
|
|
if (!exit_addr) {
|
|
return 0;
|
|
}
|
|
|
|
// jit->trace->exit_info[exit_point].poly_func_ref = func_ref;
|
|
// jit->trace->exit_info[exit_point].poly_this_ref = scope_ref;
|
|
|
|
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;
|
|
|
|
ir_GUARD(
|
|
ir_EQ(
|
|
ir_LOAD_A(ir_ADD_OFFSET(func_ref, offsetof(zend_op_array, opcodes))),
|
|
ir_CONST_ADDR(opcodes)),
|
|
ir_CONST_ADDR(exit_addr));
|
|
} else {
|
|
ir_GUARD(ir_EQ(func_ref, ir_CONST_ADDR(func)), ir_CONST_ADDR(exit_addr));
|
|
}
|
|
}
|
|
|
|
if (!func || !(func->common.fn_flags & ZEND_ACC_STATIC)) {
|
|
if (!func) {
|
|
// JIT: if (fbc->common.fn_flags & ZEND_ACC_STATIC) {
|
|
if_static = ir_IF(ir_AND_U32(
|
|
ir_LOAD_U32(ir_ADD_OFFSET(func_ref, offsetof(zend_function, common.fn_flags))),
|
|
ir_CONST_U32(ZEND_ACC_STATIC)));
|
|
ir_IF_FALSE_cold(if_static);
|
|
}
|
|
|
|
ref = ir_CALL_3(IR_ADDR, ir_CONST_FC_FUNC(zend_jit_push_this_method_call_frame),
|
|
scope_ref,
|
|
func_ref,
|
|
ir_CONST_U32(opline->extended_value));
|
|
ir_GUARD(ref, jit_STUB_ADDR(jit, jit_stub_exception_handler));
|
|
jit_STORE_IP(jit, ref);
|
|
|
|
if (!func) {
|
|
cold_path = ir_END();
|
|
ir_IF_TRUE(if_static);
|
|
}
|
|
}
|
|
|
|
if (!func || (func->common.fn_flags & ZEND_ACC_STATIC)) {
|
|
if (opline->op1_type == IS_UNUSED
|
|
&& ((opline->op1.num & ZEND_FETCH_CLASS_MASK) == ZEND_FETCH_CLASS_PARENT ||
|
|
(opline->op1.num & ZEND_FETCH_CLASS_MASK) == ZEND_FETCH_CLASS_SELF)) {
|
|
if (op_array->fn_flags & ZEND_ACC_STATIC) {
|
|
scope_ref = ir_LOAD_A(jit_EX(This.value.ref));
|
|
} else {
|
|
scope_ref = ir_LOAD_A(ir_ADD_OFFSET(ir_LOAD_A(jit_EX(This.value.ref)), offsetof(zend_object, ce)));
|
|
}
|
|
}
|
|
if (!zend_jit_push_call_frame(jit, opline, op_array, func, 0, 0, checked_stack, func_ref, scope_ref)) {
|
|
return 0;
|
|
}
|
|
|
|
if (!func) {
|
|
ir_MERGE_2(cold_path, ir_END());
|
|
}
|
|
}
|
|
|
|
zend_jit_start_reuse_ip(jit);
|
|
if (zend_jit_needs_call_chain(call_info, b, op_array, ssa, ssa_op, opline, call_level, trace)) {
|
|
if (!zend_jit_save_call_chain(jit, call_level)) {
|
|
return 0;
|
|
}
|
|
} else {
|
|
ZEND_ASSERT(call_level > 0);
|
|
jit->delayed_call_level = call_level;
|
|
delayed_call_chain = 1;
|
|
}
|
|
|
|
return 1;
|
|
}
|
|
|
|
static int zend_jit_init_closure_call(zend_jit_ctx *jit,
|
|
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,
|
|
int checked_stack)
|
|
{
|
|
zend_function *func = NULL;
|
|
zend_jit_addr op2_addr = ZEND_ADDR_MEM_ZVAL(ZREG_FP, opline->op2.var);
|
|
ir_ref ref;
|
|
|
|
ref = jit_Z_PTR(jit, 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;
|
|
}
|
|
|
|
ir_GUARD(
|
|
ir_EQ(
|
|
ir_LOAD_A(ir_ADD_OFFSET(ref, offsetof(zend_object, ce))),
|
|
ir_CONST_ADDR(zend_ce_closure)),
|
|
ir_CONST_ADDR(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;
|
|
}
|
|
|
|
ir_GUARD(
|
|
ir_EQ(
|
|
ir_LOAD_A(ir_ADD_OFFSET(ref, offsetof(zend_closure, func.op_array.opcodes))),
|
|
ir_CONST_ADDR(opcodes)),
|
|
ir_CONST_ADDR(exit_addr));
|
|
}
|
|
|
|
if (jit->delayed_call_level) {
|
|
if (!zend_jit_save_call_chain(jit, jit->delayed_call_level)) {
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
if (!zend_jit_push_call_frame(jit, opline, NULL, func, 1, 0, checked_stack, ref, IR_UNUSED)) {
|
|
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(jit, call_level)) {
|
|
return 0;
|
|
}
|
|
} else {
|
|
ZEND_ASSERT(call_level > 0);
|
|
delayed_call_chain = 1;
|
|
jit->delayed_call_level = call_level;
|
|
}
|
|
|
|
if (trace
|
|
&& trace->op == ZEND_JIT_TRACE_END
|
|
&& trace->stop >= ZEND_JIT_TRACE_STOP_INTERPRETER) {
|
|
if (!zend_jit_set_ip(jit, opline + 1)) {
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
return 1;
|
|
}
|
|
|
|
static int zend_jit_send_val(zend_jit_ctx *jit, 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(jit)) {
|
|
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 {
|
|
ir_ref cond = ir_AND_U32(
|
|
ir_LOAD_U32(ir_ADD_OFFSET(ir_LOAD_A(jit_RX(func)), offsetof(zend_function, quick_arg_flags))),
|
|
ir_CONST_U32(mask));
|
|
|
|
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;
|
|
}
|
|
ir_GUARD_NOT(cond, ir_CONST_ADDR(exit_addr));
|
|
} else {
|
|
ir_ref if_pass_by_ref;
|
|
|
|
if_pass_by_ref = ir_IF(cond);
|
|
|
|
ir_IF_TRUE_cold(if_pass_by_ref);
|
|
if (Z_MODE(op1_addr) == IS_REG) {
|
|
/* set type to avoid zval_ptr_dtor() on uninitialized value */
|
|
zend_jit_addr addr = ZEND_ADDR_MEM_ZVAL(ZREG_FP, opline->op1.var);
|
|
jit_set_Z_TYPE_INFO(jit, addr, IS_UNDEF);
|
|
}
|
|
jit_SET_EX_OPLINE(jit, opline);
|
|
ir_IJMP(jit_STUB_ADDR(jit, jit_stub_throw_cannot_pass_by_ref));
|
|
|
|
ir_IF_FALSE(if_pass_by_ref);
|
|
}
|
|
}
|
|
}
|
|
|
|
arg_addr = ZEND_ADDR_MEM_ZVAL(ZREG_RX, opline->result.var);
|
|
|
|
if (opline->op1_type == IS_CONST) {
|
|
zval *zv = RT_CONSTANT(opline, opline->op1);
|
|
|
|
jit_ZVAL_COPY_CONST(jit,
|
|
arg_addr,
|
|
MAY_BE_ANY, MAY_BE_ANY,
|
|
zv, 1);
|
|
} else {
|
|
jit_ZVAL_COPY(jit,
|
|
arg_addr,
|
|
MAY_BE_ANY,
|
|
op1_addr, op1_info, 0);
|
|
}
|
|
|
|
return 1;
|
|
}
|
|
|
|
static int zend_jit_send_ref(zend_jit_ctx *jit, 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;
|
|
ir_ref ref_path = IR_UNUSED;
|
|
|
|
op1_addr = OP1_ADDR();
|
|
arg_addr = ZEND_ADDR_MEM_ZVAL(ZREG_RX, opline->result.var);
|
|
|
|
if (!zend_jit_reuse_ip(jit)) {
|
|
return 0;
|
|
}
|
|
|
|
if (opline->op1_type == IS_VAR) {
|
|
if (op1_info & MAY_BE_INDIRECT) {
|
|
op1_addr = jit_ZVAL_INDIRECT_DEREF(jit, op1_addr);
|
|
}
|
|
} else if (opline->op1_type == IS_CV) {
|
|
if (op1_info & MAY_BE_UNDEF) {
|
|
if (op1_info & (MAY_BE_ANY|MAY_BE_REF)) {
|
|
// JIT: if (Z_TYPE_P(op1) == IS_UNDEF)
|
|
ir_ref if_def = jit_if_not_Z_TYPE(jit, op1_addr, IS_UNDEF);
|
|
ir_IF_FALSE(if_def);
|
|
// JIT: ZVAL_NULL(op1)
|
|
jit_set_Z_TYPE_INFO(jit,op1_addr, IS_NULL);
|
|
ir_MERGE_WITH_EMPTY_TRUE(if_def);
|
|
}
|
|
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)) {
|
|
ir_ref ref, ref2;
|
|
|
|
if (op1_info & MAY_BE_REF) {
|
|
ir_ref if_ref;
|
|
|
|
// JIT: if (Z_TYPE_P(op1) == IS_UNDEF)
|
|
if_ref = jit_if_Z_TYPE(jit, op1_addr, IS_REFERENCE);
|
|
ir_IF_TRUE(if_ref);
|
|
// JIT: ref = Z_PTR_P(op1)
|
|
ref = jit_Z_PTR(jit, op1_addr);
|
|
// JIT: GC_ADDREF(ref)
|
|
jit_GC_ADDREF(jit, ref);
|
|
// JIT: ZVAL_REFERENCE(arg, ref)
|
|
jit_set_Z_PTR(jit, arg_addr, ref);
|
|
jit_set_Z_TYPE_INFO(jit, arg_addr, IS_REFERENCE_EX);
|
|
ref_path = ir_END();
|
|
ir_IF_FALSE(if_ref);
|
|
}
|
|
|
|
// JIT: ZVAL_NEW_REF(arg, varptr);
|
|
// JIT: ref = emalloc(sizeof(zend_reference));
|
|
ref = jit_EMALLOC(jit, sizeof(zend_reference), op_array, opline);
|
|
// JIT: GC_REFCOUNT(ref) = 2
|
|
jit_set_GC_REFCOUNT(jit, ref, 2);
|
|
// JIT: GC_TYPE(ref) = GC_REFERENCE
|
|
ir_STORE(ir_ADD_OFFSET(ref, offsetof(zend_reference, gc.u.type_info)), ir_CONST_U32(GC_REFERENCE));
|
|
ir_STORE(ir_ADD_OFFSET(ref, offsetof(zend_reference, sources.ptr)), IR_NULL);
|
|
ref2 = ir_ADD_OFFSET(ref, offsetof(zend_reference, val));
|
|
ref_addr = ZEND_ADDR_REF_ZVAL(ref2);
|
|
|
|
// JIT: ZVAL_COPY_VALUE(&ref->val, op1)
|
|
jit_ZVAL_COPY(jit,
|
|
ref_addr,
|
|
MAY_BE_ANY,
|
|
op1_addr, op1_info, 0);
|
|
|
|
// JIT: ZVAL_REFERENCE(arg, ref)
|
|
jit_set_Z_PTR(jit, op1_addr, ref);
|
|
jit_set_Z_TYPE_INFO(jit, op1_addr, IS_REFERENCE_EX);
|
|
|
|
// JIT: ZVAL_REFERENCE(arg, ref)
|
|
jit_set_Z_PTR(jit, arg_addr, ref);
|
|
jit_set_Z_TYPE_INFO(jit, arg_addr, IS_REFERENCE_EX);
|
|
}
|
|
|
|
if (ref_path) {
|
|
ir_MERGE_WITH(ref_path);
|
|
}
|
|
|
|
jit_FREE_OP(jit, opline->op1_type, opline->op1, op1_info, opline);
|
|
|
|
return 1;
|
|
}
|
|
|
|
static int zend_jit_send_var(zend_jit_ctx *jit, 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;
|
|
ir_ref end_inputs = IR_UNUSED;
|
|
|
|
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(jit)) {
|
|
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(jit, 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);
|
|
|
|
// JIT: if (RX->func->quick_arg_flags & mask)
|
|
ir_ref if_send_by_ref = ir_IF(ir_AND_U32(
|
|
ir_LOAD_U32(ir_ADD_OFFSET(ir_LOAD_A(jit_RX(func)), offsetof(zend_function, quick_arg_flags))),
|
|
ir_CONST_U32(mask)));
|
|
ir_IF_TRUE_cold(if_send_by_ref);
|
|
|
|
if (!zend_jit_send_ref(jit, opline, op_array, op1_info, 1)) {
|
|
return 0;
|
|
}
|
|
|
|
ir_END_list(end_inputs);
|
|
ir_IF_FALSE(if_send_by_ref);
|
|
}
|
|
} 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)) {
|
|
|
|
// JIT: ZVAL_COPY_VALUE(arg, op1)
|
|
jit_ZVAL_COPY(jit,
|
|
arg_addr,
|
|
MAY_BE_ANY,
|
|
op1_addr, op1_info, 0);
|
|
|
|
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;
|
|
}
|
|
|
|
// JIT: if (Z_TYPE_P(op1) != IS_REFERENCE)
|
|
ir_GUARD(ir_EQ(jit_Z_TYPE(jit, op1_addr), ir_CONST_U32(IS_REFERENCE)),
|
|
ir_CONST_ADDR(exit_addr));
|
|
}
|
|
}
|
|
return 1;
|
|
}
|
|
} else {
|
|
uint32_t mask = (ZEND_SEND_BY_REF|ZEND_SEND_PREFER_REF) << ((arg_num + 3) * 2);
|
|
ir_ref func, if_send_by_ref, if_prefer_ref;
|
|
|
|
// JIT: if (RX->func->quick_arg_flags & mask)
|
|
func = ir_LOAD_A(jit_RX(func));
|
|
if_send_by_ref = ir_IF(ir_AND_U32(
|
|
ir_LOAD_U32(ir_ADD_OFFSET(func, offsetof(zend_function, quick_arg_flags))),
|
|
ir_CONST_U32(mask)));
|
|
ir_IF_TRUE_cold(if_send_by_ref);
|
|
|
|
mask = ZEND_SEND_PREFER_REF << ((arg_num + 3) * 2);
|
|
|
|
// JIT: ZVAL_COPY_VALUE(arg, op1)
|
|
jit_ZVAL_COPY(jit,
|
|
arg_addr,
|
|
MAY_BE_ANY,
|
|
op1_addr, op1_info, 0);
|
|
|
|
if (op1_info & MAY_BE_REF) {
|
|
ir_ref if_ref = jit_if_Z_TYPE(jit, arg_addr, IS_REFERENCE);
|
|
ir_IF_TRUE(if_ref);
|
|
ir_END_list(end_inputs);
|
|
ir_IF_FALSE(if_ref);
|
|
}
|
|
|
|
// JIT: if (RX->func->quick_arg_flags & mask)
|
|
if_prefer_ref = ir_IF(ir_AND_U32(
|
|
ir_LOAD_U32(ir_ADD_OFFSET(func, offsetof(zend_function, quick_arg_flags))),
|
|
ir_CONST_U32(mask)));
|
|
ir_IF_TRUE(if_prefer_ref);
|
|
ir_END_list(end_inputs);
|
|
ir_IF_FALSE(if_prefer_ref);
|
|
|
|
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;
|
|
}
|
|
jit_SIDE_EXIT(jit, ir_CONST_ADDR(exit_addr));
|
|
} else {
|
|
jit_SET_EX_OPLINE(jit, opline);
|
|
ir_CALL_1(IR_VOID, ir_CONST_FC_FUNC(zend_jit_only_vars_by_reference),
|
|
jit_ZVAL_ADDR(jit, arg_addr));
|
|
zend_jit_check_exception(jit);
|
|
ir_END_list(end_inputs);
|
|
}
|
|
|
|
ir_IF_FALSE(if_send_by_ref);
|
|
}
|
|
} 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(jit, opline, op_array, op1_info, 0)) {
|
|
return 0;
|
|
}
|
|
return 1;
|
|
}
|
|
} else {
|
|
// JIT: if (RX->This.u1.type_info & ZEND_CALL_SEND_ARG_BY_REF)
|
|
ir_ref if_send_by_ref = ir_IF(ir_AND_U32(
|
|
ir_LOAD_U32(jit_RX(This.u1.type_info)),
|
|
ir_CONST_U32(ZEND_CALL_SEND_ARG_BY_REF)));
|
|
ir_IF_TRUE_cold(if_send_by_ref);
|
|
|
|
if (!zend_jit_send_ref(jit, opline, op_array, op1_info, 1)) {
|
|
return 0;
|
|
}
|
|
|
|
ir_END_list(end_inputs);
|
|
ir_IF_FALSE(if_send_by_ref);
|
|
}
|
|
}
|
|
|
|
if (op1_info & MAY_BE_UNDEF) {
|
|
ir_ref ref, if_def = IR_UNUSED;
|
|
|
|
if (op1_info & (MAY_BE_ANY|MAY_BE_REF)) {
|
|
if_def = jit_if_not_Z_TYPE(jit, op1_addr, IS_UNDEF);
|
|
ir_IF_FALSE_cold(if_def);
|
|
}
|
|
|
|
// JIT: zend_jit_undefined_op_helper(opline->op1.var)
|
|
jit_SET_EX_OPLINE(jit, opline);
|
|
ref = ir_CALL_1(IR_I32, ir_CONST_FC_FUNC(zend_jit_undefined_op_helper),
|
|
ir_CONST_U32(opline->op1.var));
|
|
|
|
// JIT: ZVAL_NULL(arg)
|
|
jit_set_Z_TYPE_INFO(jit, arg_addr, IS_NULL);
|
|
|
|
// JIT: check_exception
|
|
ir_GUARD(ref, jit_STUB_ADDR(jit, jit_stub_exception_handler));
|
|
|
|
if (op1_info & (MAY_BE_ANY|MAY_BE_REF)) {
|
|
ir_END_list(end_inputs);
|
|
ir_IF_TRUE(if_def);
|
|
} else {
|
|
if (end_inputs) {
|
|
ir_END_list(end_inputs);
|
|
ir_MERGE_list(end_inputs);
|
|
}
|
|
return 1;
|
|
}
|
|
}
|
|
|
|
if (opline->opcode == ZEND_SEND_VAR_NO_REF) {
|
|
// JIT: ZVAL_COPY_VALUE(arg, op1)
|
|
jit_ZVAL_COPY(jit,
|
|
arg_addr,
|
|
MAY_BE_ANY,
|
|
op1_addr, op1_info, 0);
|
|
if (op1_info & MAY_BE_REF) {
|
|
// JIT: if (Z_TYPE_P(arg) == IS_REFERENCE)
|
|
ir_ref if_ref = jit_if_Z_TYPE(jit, arg_addr, IS_REFERENCE);
|
|
ir_IF_TRUE(if_ref);
|
|
ir_END_list(end_inputs);
|
|
ir_IF_FALSE(if_ref);
|
|
}
|
|
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;
|
|
}
|
|
ir_GUARD(IR_FALSE, ir_CONST_ADDR(exit_addr));
|
|
} else {
|
|
jit_SET_EX_OPLINE(jit, opline);
|
|
ir_CALL_1(IR_VOID, ir_CONST_FC_FUNC(zend_jit_only_vars_by_reference),
|
|
jit_ZVAL_ADDR(jit, arg_addr));
|
|
zend_jit_check_exception(jit);
|
|
}
|
|
} else {
|
|
if (op1_info & MAY_BE_REF) {
|
|
if (opline->op1_type == IS_CV) {
|
|
ir_ref ref;
|
|
|
|
// JIT: ZVAL_DEREF(op1)
|
|
ref = jit_ZVAL_ADDR(jit, op1_addr);
|
|
ref = jit_ZVAL_DEREF_ref(jit, ref);
|
|
op1_addr = ZEND_ADDR_REF_ZVAL(ref);
|
|
|
|
// JIT: ZVAL_COPY(arg, op1)
|
|
jit_ZVAL_COPY(jit,
|
|
arg_addr,
|
|
MAY_BE_ANY,
|
|
op1_addr, op1_info, 1);
|
|
} else {
|
|
ir_ref if_ref, ref, ref2, refcount, if_not_zero, if_refcounted;
|
|
zend_jit_addr ref_addr;
|
|
|
|
// JIT: if (Z_TYPE_P(op1) == IS_REFERENCE)
|
|
if_ref = jit_if_Z_TYPE(jit, op1_addr, IS_REFERENCE);
|
|
ir_IF_TRUE_cold(if_ref);
|
|
|
|
// JIT: ref = Z_COUNTED_P(op1);
|
|
ref = jit_Z_PTR(jit, op1_addr);
|
|
ref2 = ir_ADD_OFFSET(ref, offsetof(zend_reference, val));
|
|
ref_addr = ZEND_ADDR_REF_ZVAL(ref2);
|
|
|
|
// JIT: ZVAL_COPY_VALUE(arg, op1);
|
|
jit_ZVAL_COPY(jit,
|
|
arg_addr,
|
|
MAY_BE_ANY,
|
|
ref_addr, op1_info, 0);
|
|
|
|
// JIT: if (GC_DELREF(ref) != 0)
|
|
refcount = jit_GC_DELREF(jit, ref);
|
|
if_not_zero = ir_IF(refcount);
|
|
ir_IF_TRUE(if_not_zero);
|
|
|
|
// JIT: if (Z_REFCOUNTED_P(arg)
|
|
if_refcounted = jit_if_REFCOUNTED(jit, arg_addr);
|
|
ir_IF_TRUE(if_refcounted);
|
|
// JIT: Z_ADDREF_P(arg)
|
|
jit_GC_ADDREF(jit, jit_Z_PTR(jit, arg_addr));
|
|
ir_END_list(end_inputs);
|
|
ir_IF_FALSE(if_refcounted);
|
|
ir_END_list(end_inputs);
|
|
|
|
ir_IF_FALSE(if_not_zero);
|
|
|
|
// JIT: efree(ref)
|
|
jit_EFREE(jit, ref, sizeof(zend_reference), op_array, opline);
|
|
ir_END_list(end_inputs);
|
|
|
|
ir_IF_FALSE(if_ref);
|
|
|
|
// JIT: ZVAL_COPY_VALUE(arg, op1);
|
|
jit_ZVAL_COPY(jit,
|
|
arg_addr,
|
|
MAY_BE_ANY,
|
|
op1_addr, op1_info, 0);
|
|
}
|
|
} else {
|
|
if (op1_addr != op1_def_addr) {
|
|
if (!zend_jit_update_regs(jit, 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;
|
|
}
|
|
}
|
|
|
|
// JIT: ZVAL_COPY_VALUE(arg, op1)
|
|
jit_ZVAL_COPY(jit,
|
|
arg_addr,
|
|
MAY_BE_ANY,
|
|
op1_addr, op1_info, opline->op1_type == IS_CV);
|
|
}
|
|
}
|
|
|
|
if (end_inputs) {
|
|
ir_END_list(end_inputs);
|
|
ir_MERGE_list(end_inputs);
|
|
}
|
|
|
|
return 1;
|
|
}
|
|
|
|
static int zend_jit_check_func_arg(zend_jit_ctx *jit, const zend_op *opline)
|
|
{
|
|
uint32_t arg_num = opline->op2.num;
|
|
ir_ref ref;
|
|
|
|
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);
|
|
// JIT: ZEND_ADD_CALL_FLAG(EX(call), ZEND_CALL_SEND_ARG_BY_REF);
|
|
if (jit->reuse_ip) {
|
|
ref = jit_IP(jit);
|
|
} else {
|
|
ref = ir_LOAD_A(jit_EX(call));
|
|
}
|
|
ref = jit_CALL(ref, This.u1.type_info);
|
|
ir_STORE(ref, ir_OR_U32(ir_LOAD_U32(ref), ir_CONST_U32(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);
|
|
// JIT: ZEND_DEL_CALL_FLAG(EX(call), ZEND_CALL_SEND_ARG_BY_REF);
|
|
if (jit->reuse_ip) {
|
|
ref = jit_IP(jit);
|
|
} else {
|
|
ref = ir_LOAD_A(jit_EX(call));
|
|
}
|
|
ref = jit_CALL(ref, This.u1.type_info);
|
|
ir_STORE(ref, ir_AND_U32(ir_LOAD_U32(ref), ir_CONST_U32(~ZEND_CALL_SEND_ARG_BY_REF)));
|
|
}
|
|
}
|
|
} else {
|
|
// JIT: 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);
|
|
ir_ref rx, if_ref, cold_path;
|
|
|
|
if (!zend_jit_reuse_ip(jit)) {
|
|
return 0;
|
|
}
|
|
|
|
rx = jit_IP(jit);
|
|
|
|
ref = ir_AND_U32(
|
|
ir_LOAD_U32(ir_ADD_OFFSET(ir_LOAD_A(jit_CALL(rx, func)), offsetof(zend_function, quick_arg_flags))),
|
|
ir_CONST_U32(mask));
|
|
if_ref = ir_IF(ref);
|
|
ir_IF_TRUE_cold(if_ref);
|
|
|
|
// JIT: ZEND_ADD_CALL_FLAG(EX(call), ZEND_CALL_SEND_ARG_BY_REF);
|
|
ref = jit_CALL(rx, This.u1.type_info);
|
|
ir_STORE(ref, ir_OR_U32(ir_LOAD_U32(ref), ir_CONST_U32(ZEND_CALL_SEND_ARG_BY_REF)));
|
|
|
|
cold_path = ir_END();
|
|
ir_IF_FALSE(if_ref);
|
|
|
|
// JIT: ZEND_DEL_CALL_FLAG(EX(call), ZEND_CALL_SEND_ARG_BY_REF);
|
|
ref = jit_CALL(rx, This.u1.type_info);
|
|
ir_STORE(ref, ir_AND_U32(ir_LOAD_U32(ref), ir_CONST_U32(~ZEND_CALL_SEND_ARG_BY_REF)));
|
|
|
|
ir_MERGE_WITH(cold_path);
|
|
}
|
|
|
|
return 1;
|
|
}
|
|
|
|
static int zend_jit_check_undef_args(zend_jit_ctx *jit, const zend_op *opline)
|
|
{
|
|
ir_ref call, if_may_have_undef, ret;
|
|
|
|
if (jit->reuse_ip) {
|
|
call = jit_IP(jit);
|
|
} else {
|
|
call = ir_LOAD_A(jit_EX(call));
|
|
}
|
|
|
|
if_may_have_undef = ir_IF(ir_AND_U8(
|
|
ir_LOAD_U8(ir_ADD_OFFSET(call, offsetof(zend_execute_data, This.u1.type_info) + 3)),
|
|
ir_CONST_U8(ZEND_CALL_MAY_HAVE_UNDEF >> 24)));
|
|
|
|
ir_IF_TRUE_cold(if_may_have_undef);
|
|
jit_SET_EX_OPLINE(jit, opline);
|
|
ret = ir_CALL_1(IR_I32, ir_CONST_FC_FUNC(zend_handle_undef_args), call);
|
|
ir_GUARD_NOT(ret, jit_STUB_ADDR(jit, jit_stub_exception_handler));
|
|
ir_MERGE_WITH_EMPTY_FALSE(if_may_have_undef);
|
|
|
|
return 1;
|
|
}
|
|
|
|
static int zend_jit_do_fcall(zend_jit_ctx *jit, 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;
|
|
uint32_t call_num_args = 0;
|
|
bool unknown_num_args = 0;
|
|
const void *exit_addr = NULL;
|
|
const zend_op *prev_opline;
|
|
ir_ref rx, func_ref = IR_UNUSED, if_user = IR_UNUSED, user_path = IR_UNUSED;
|
|
|
|
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 && !call_info->is_prototype) {
|
|
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 || 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 (!jit->reuse_ip) {
|
|
zend_jit_start_reuse_ip(jit);
|
|
// JIT: call = EX(call);
|
|
jit_STORE_IP(jit, ir_LOAD_A(jit_EX(call)));
|
|
}
|
|
rx = jit_IP(jit);
|
|
zend_jit_stop_reuse_ip(jit);
|
|
|
|
jit_SET_EX_OPLINE(jit, opline);
|
|
|
|
if (opline->opcode == ZEND_DO_FCALL || 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;
|
|
}
|
|
|
|
func_ref = ir_LOAD_A(jit_CALL(rx, func));
|
|
ir_GUARD_NOT(
|
|
ir_AND_U32(
|
|
ir_LOAD_U32(ir_ADD_OFFSET(func_ref, offsetof(zend_op_array, fn_flags))),
|
|
ir_CONST_U32(ZEND_ACC_DEPRECATED)),
|
|
ir_CONST_ADDR(exit_addr));
|
|
}
|
|
}
|
|
}
|
|
|
|
if (!jit->delayed_call_level) {
|
|
// JIT: EX(call) = call->prev_execute_data;
|
|
ir_STORE(jit_EX(call),
|
|
(call_level == 1) ? IR_NULL : ir_LOAD_A(jit_CALL(rx, prev_execute_data)));
|
|
}
|
|
delayed_call_chain = 0;
|
|
jit->delayed_call_level = 0;
|
|
|
|
// JIT: call->prev_execute_data = execute_data;
|
|
ir_STORE(jit_CALL(rx, prev_execute_data), jit_FP(jit));
|
|
|
|
if (!func) {
|
|
if (!func_ref) {
|
|
func_ref = ir_LOAD_A(jit_CALL(rx, func));
|
|
}
|
|
}
|
|
|
|
if (opline->opcode == ZEND_DO_FCALL || opline->opcode == ZEND_DO_FCALL_BY_NAME) {
|
|
if (!func) {
|
|
if (!trace) {
|
|
ir_ref if_deprecated, ret;
|
|
|
|
if_deprecated = ir_IF(ir_AND_U32(
|
|
ir_LOAD_U32(ir_ADD_OFFSET(func_ref, offsetof(zend_op_array, fn_flags))),
|
|
ir_CONST_U32(ZEND_ACC_DEPRECATED)));
|
|
ir_IF_TRUE_cold(if_deprecated);
|
|
|
|
if (GCC_GLOBAL_REGS) {
|
|
ret = ir_CALL(IR_BOOL, ir_CONST_FC_FUNC(zend_jit_deprecated_helper));
|
|
} else {
|
|
ret = ir_CALL_1(IR_BOOL, ir_CONST_FC_FUNC(zend_jit_deprecated_helper), rx);
|
|
}
|
|
ir_GUARD(ret, jit_STUB_ADDR(jit, jit_stub_exception_handler));
|
|
ir_MERGE_WITH_EMPTY_FALSE(if_deprecated);
|
|
}
|
|
} else if (func->common.fn_flags & ZEND_ACC_DEPRECATED) {
|
|
ir_ref ret;
|
|
|
|
if (GCC_GLOBAL_REGS) {
|
|
ret = ir_CALL(IR_BOOL, ir_CONST_FC_FUNC(zend_jit_deprecated_helper));
|
|
} else {
|
|
ret = ir_CALL_1(IR_BOOL, ir_CONST_FC_FUNC(zend_jit_deprecated_helper), rx);
|
|
}
|
|
ir_GUARD(ret, jit_STUB_ADDR(jit, jit_stub_exception_handler));
|
|
}
|
|
}
|
|
|
|
if (!func
|
|
&& opline->opcode != ZEND_DO_UCALL
|
|
&& opline->opcode != ZEND_DO_ICALL) {
|
|
ir_ref type_ref = ir_LOAD_U8(ir_ADD_OFFSET(func_ref, offsetof(zend_function, type)));
|
|
if_user = ir_IF(ir_EQ(type_ref, ir_CONST_U8(ZEND_USER_FUNCTION)));
|
|
ir_IF_TRUE(if_user);
|
|
}
|
|
|
|
if ((!func || func->type == ZEND_USER_FUNCTION)
|
|
&& opline->opcode != ZEND_DO_ICALL) {
|
|
bool recursive_call_through_jmp = 0;
|
|
|
|
// JIT: EX(call) = NULL;
|
|
ir_STORE(jit_CALL(rx, call), IR_NULL);
|
|
|
|
// JIT: EX(return_value) = RETURN_VALUE_USED(opline) ? EX_VAR(opline->result.var) : 0;
|
|
ir_STORE(jit_CALL(rx, return_value),
|
|
RETURN_VALUE_USED(opline) ?
|
|
jit_ZVAL_ADDR(jit, ZEND_ADDR_MEM_ZVAL(ZREG_FP, opline->result.var)) :
|
|
IR_NULL);
|
|
|
|
// JIT: EX_LOAD_RUN_TIME_CACHE(op_array);
|
|
if (!func || func->op_array.cache_size) {
|
|
ir_ref run_time_cache;
|
|
|
|
if (func && op_array == &func->op_array) {
|
|
/* recursive call */
|
|
run_time_cache = ir_LOAD_A(jit_EX(run_time_cache));
|
|
} else if (func
|
|
&& !(func->op_array.fn_flags & ZEND_ACC_CLOSURE)
|
|
&& ZEND_MAP_PTR_IS_OFFSET(func->op_array.run_time_cache)) {
|
|
run_time_cache = ir_LOAD_A(ir_ADD_OFFSET(ir_LOAD_A(jit_CG(map_ptr_base)),
|
|
(uintptr_t)ZEND_MAP_PTR(func->op_array.run_time_cache)));
|
|
} else if ((func && (func->op_array.fn_flags & ZEND_ACC_CLOSURE)) ||
|
|
(JIT_G(current_frame) &&
|
|
JIT_G(current_frame)->call &&
|
|
TRACE_FRAME_IS_CLOSURE_CALL(JIT_G(current_frame)->call))) {
|
|
/* Closures always use direct pointers */
|
|
ir_ref local_func_ref = func_ref ? func_ref : ir_LOAD_A(jit_CALL(rx, func));
|
|
|
|
run_time_cache = ir_LOAD_A(ir_ADD_OFFSET(local_func_ref, offsetof(zend_op_array, run_time_cache__ptr)));
|
|
} else {
|
|
ir_ref if_odd, run_time_cache2;
|
|
ir_ref local_func_ref = func_ref ? func_ref : ir_LOAD_A(jit_CALL(rx, func));
|
|
|
|
run_time_cache = ir_LOAD_A(ir_ADD_OFFSET(local_func_ref, offsetof(zend_op_array, run_time_cache__ptr)));
|
|
if_odd = ir_IF(ir_AND_A(run_time_cache, ir_CONST_ADDR(1)));
|
|
ir_IF_TRUE(if_odd);
|
|
|
|
run_time_cache2 = ir_LOAD_A(ir_ADD_A(run_time_cache, ir_LOAD_A(jit_CG(map_ptr_base))));
|
|
|
|
ir_MERGE_WITH_EMPTY_FALSE(if_odd);
|
|
run_time_cache = ir_PHI_2(IR_ADDR, run_time_cache2, run_time_cache);
|
|
}
|
|
|
|
ir_STORE(jit_CALL(rx, run_time_cache), run_time_cache);
|
|
}
|
|
|
|
// JIT: EG(current_execute_data) = execute_data = call;
|
|
ir_STORE(jit_EG(current_execute_data), rx);
|
|
jit_STORE_FP(jit, rx);
|
|
|
|
// JIT: 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);
|
|
zend_jit_addr var_addr = ZEND_ADDR_MEM_ZVAL(ZREG_FP, n);
|
|
|
|
jit_set_Z_TYPE_INFO_ex(jit, var_addr, ir_CONST_U32(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)) {
|
|
jit_LOAD_IP_ADDR(jit, func->op_array.opcodes + num_args);
|
|
} else {
|
|
if (!func_ref) {
|
|
func_ref = ir_LOAD_A(jit_CALL(rx, func));
|
|
}
|
|
ir_ref ip = ir_LOAD_A(ir_ADD_OFFSET(func_ref, offsetof(zend_op_array, opcodes)));
|
|
if (num_args) {
|
|
ip = ir_ADD_OFFSET(ip, num_args * sizeof(zend_op));
|
|
}
|
|
jit_LOAD_IP(jit, ip);
|
|
}
|
|
|
|
if (!trace && op_array == &func->op_array && call_num_args >= op_array->required_num_args) {
|
|
/* recursive call */
|
|
recursive_call_through_jmp = 1;
|
|
}
|
|
}
|
|
} else {
|
|
if (!trace || (trace->op == ZEND_JIT_TRACE_END
|
|
&& trace->stop >= ZEND_JIT_TRACE_STOP_INTERPRETER)) {
|
|
ir_ref ip;
|
|
|
|
if (zend_accel_in_shm(func->op_array.opcodes)) {
|
|
ip = ir_CONST_ADDR(func->op_array.opcodes);
|
|
} else {
|
|
if (!func_ref) {
|
|
func_ref = ir_LOAD_A(jit_CALL(rx, func));
|
|
}
|
|
ip = ir_LOAD_A(ir_ADD_OFFSET(func_ref, offsetof(zend_op_array, opcodes)));
|
|
}
|
|
jit_LOAD_IP(jit, ip);
|
|
}
|
|
if (GCC_GLOBAL_REGS) {
|
|
ir_CALL(IR_VOID, ir_CONST_FC_FUNC(zend_jit_copy_extra_args_helper));
|
|
} else {
|
|
ir_CALL_1(IR_VOID, ir_CONST_FC_FUNC(zend_jit_copy_extra_args_helper), jit_FP(jit));
|
|
}
|
|
}
|
|
} else {
|
|
ir_ref ip;
|
|
ir_ref merge_inputs = IR_UNUSED;
|
|
|
|
// JIT: opline = op_array->opcodes
|
|
if (func && zend_accel_in_shm(func->op_array.opcodes)) {
|
|
ip = ir_CONST_ADDR(func->op_array.opcodes);
|
|
} else {
|
|
if (!func_ref) {
|
|
func_ref = ir_LOAD_A(jit_CALL(rx, func));
|
|
}
|
|
ip = ir_LOAD_A(ir_ADD_OFFSET(func_ref, offsetof(zend_op_array, opcodes)));
|
|
}
|
|
jit_LOAD_IP(jit, ip);
|
|
|
|
// JIT: num_args = EX_NUM_ARGS();
|
|
ir_ref num_args, first_extra_arg;
|
|
|
|
num_args = ir_LOAD_U32(jit_EX(This.u2.num_args));
|
|
if (func) {
|
|
first_extra_arg = ir_CONST_U32(func->op_array.num_args);
|
|
} else {
|
|
// JIT: first_extra_arg = op_array->num_args;
|
|
ZEND_ASSERT(func_ref);
|
|
first_extra_arg = ir_LOAD_U32(ir_ADD_OFFSET(func_ref, offsetof(zend_op_array, num_args)));
|
|
}
|
|
|
|
// JIT: if (UNEXPECTED(num_args > first_extra_arg))
|
|
ir_ref if_extra_args = ir_IF(ir_GT(num_args, first_extra_arg));
|
|
ir_IF_TRUE_cold(if_extra_args);
|
|
if (GCC_GLOBAL_REGS) {
|
|
ir_CALL(IR_VOID, ir_CONST_FC_FUNC(zend_jit_copy_extra_args_helper));
|
|
} else {
|
|
ir_CALL_1(IR_VOID, ir_CONST_FC_FUNC(zend_jit_copy_extra_args_helper), jit_FP(jit));
|
|
}
|
|
ir_END_list(merge_inputs);
|
|
ir_IF_FALSE(if_extra_args);
|
|
if (!func || (func->op_array.fn_flags & ZEND_ACC_HAS_TYPE_HINTS) == 0) {
|
|
if (!func) {
|
|
// JIT: if (EXPECTED((op_array->fn_flags & ZEND_ACC_HAS_TYPE_HINTS) == 0))
|
|
ir_ref if_has_type_hints = ir_IF(ir_AND_U32(
|
|
ir_LOAD_U32(ir_ADD_OFFSET(func_ref, offsetof(zend_op_array, fn_flags))),
|
|
ir_CONST_U32(ZEND_ACC_HAS_TYPE_HINTS)));
|
|
ir_IF_TRUE(if_has_type_hints);
|
|
ir_END_list(merge_inputs);
|
|
ir_IF_FALSE(if_has_type_hints);
|
|
}
|
|
// JIT: opline += num_args;
|
|
|
|
ir_ref ref = ir_MUL_U32(num_args, ir_CONST_U32(sizeof(zend_op)));
|
|
|
|
if (sizeof(void*) == 8) {
|
|
ref = ir_ZEXT_A(ref);
|
|
}
|
|
|
|
if (GCC_GLOBAL_REGS) {
|
|
jit_STORE_IP(jit, ir_ADD_A(jit_IP(jit), ref));
|
|
} else {
|
|
ir_ref addr = jit_EX(opline);
|
|
|
|
ir_STORE(addr, ir_ADD_A(ir_LOAD_A(addr), ref));
|
|
}
|
|
}
|
|
|
|
ir_END_list(merge_inputs);
|
|
ir_MERGE_list(merge_inputs);
|
|
|
|
// JIT: if (EXPECTED((int)num_args < op_array->last_var)) {
|
|
ir_ref last_var;
|
|
|
|
if (func) {
|
|
last_var = ir_CONST_U32(func->op_array.last_var);
|
|
} else {
|
|
ZEND_ASSERT(func_ref);
|
|
last_var = ir_LOAD_U32(ir_ADD_OFFSET(func_ref, offsetof(zend_op_array, last_var)));
|
|
}
|
|
|
|
ir_ref idx = ir_SUB_U32(last_var, num_args);
|
|
ir_ref if_need = ir_IF(ir_GT(idx, ir_CONST_U32(0)));
|
|
ir_IF_TRUE(if_need);
|
|
|
|
// JIT: zval *var = EX_VAR_NUM(num_args);
|
|
if (sizeof(void*) == 8) {
|
|
num_args = ir_ZEXT_A(num_args);
|
|
}
|
|
ir_ref var_ref = ir_ADD_OFFSET(
|
|
ir_ADD_A(jit_FP(jit), ir_MUL_A(num_args, ir_CONST_ADDR(sizeof(zval)))),
|
|
(ZEND_CALL_FRAME_SLOT * sizeof(zval)) + offsetof(zval, u1.type_info));
|
|
|
|
ir_ref loop = ir_LOOP_BEGIN(ir_END());
|
|
var_ref = ir_PHI_2(IR_ADDR, var_ref, IR_UNUSED);
|
|
idx = ir_PHI_2(IR_U32, idx, IR_UNUSED);
|
|
ir_STORE(var_ref, ir_CONST_I32(IS_UNDEF));
|
|
ir_PHI_SET_OP(var_ref, 2, ir_ADD_OFFSET(var_ref, sizeof(zval)));
|
|
ir_ref idx2 = ir_SUB_U32(idx, ir_CONST_U32(1));
|
|
ir_PHI_SET_OP(idx, 2, idx2);
|
|
ir_ref if_not_zero = ir_IF(idx2);
|
|
ir_IF_TRUE(if_not_zero);
|
|
ir_MERGE_SET_OP(loop, 2, ir_LOOP_END());
|
|
ir_IF_FALSE(if_not_zero);
|
|
ir_MERGE_WITH_EMPTY_FALSE(if_need);
|
|
}
|
|
|
|
if (ZEND_OBSERVER_ENABLED && (!func || (func->common.fn_flags & (ZEND_ACC_CALL_VIA_TRAMPOLINE | ZEND_ACC_GENERATOR)) == 0)) {
|
|
ir_ref observer_handler;
|
|
ir_ref rx = jit_FP(jit);
|
|
struct jit_observer_fcall_is_unobserved_data unobserved_data = jit_observer_fcall_is_unobserved_start(jit, func, &observer_handler, rx, func_ref);
|
|
if (trace && (trace->op != ZEND_JIT_TRACE_END || trace->stop < ZEND_JIT_TRACE_STOP_INTERPRETER)) {
|
|
ZEND_ASSERT(trace[1].op == ZEND_JIT_TRACE_VM || trace[1].op == ZEND_JIT_TRACE_END);
|
|
jit_SET_EX_OPLINE(jit, trace[1].opline);
|
|
} else if (GCC_GLOBAL_REGS) {
|
|
// EX(opline) = opline
|
|
ir_STORE(jit_EX(opline), jit_IP(jit));
|
|
}
|
|
jit_observer_fcall_begin(jit, rx, observer_handler);
|
|
|
|
if (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;
|
|
}
|
|
} else {
|
|
exit_addr = NULL;
|
|
}
|
|
|
|
zend_jit_check_timeout(jit, NULL /* we're inside the called function */, exit_addr);
|
|
|
|
jit_observer_fcall_is_unobserved_end(jit, &unobserved_data);
|
|
}
|
|
|
|
if (trace) {
|
|
if (!func && (opline->opcode != ZEND_DO_UCALL)) {
|
|
user_path = ir_END();
|
|
}
|
|
} else {
|
|
zend_basic_block *bb;
|
|
|
|
do {
|
|
if (recursive_call_through_jmp) {
|
|
ir_ref begin, end;
|
|
ir_insn *insn;
|
|
|
|
/* attempt to convert direct recursive call into loop */
|
|
begin = jit->bb_start_ref[call_num_args];
|
|
ZEND_ASSERT(begin != IR_UNUSED);
|
|
insn = &jit->ctx.ir_base[begin];
|
|
if (insn->op == IR_BEGIN) {
|
|
end = ir_LOOP_END();
|
|
insn = &jit->ctx.ir_base[begin];
|
|
insn->op = IR_LOOP_BEGIN;
|
|
insn->inputs_count = 2;
|
|
insn->op2 = end;
|
|
break;
|
|
} else if ((insn->op == IR_MERGE || insn->op == IR_LOOP_BEGIN)
|
|
&& insn->inputs_count == 2) {
|
|
end = ir_LOOP_END();
|
|
insn = &jit->ctx.ir_base[begin];
|
|
insn->op = IR_LOOP_BEGIN;
|
|
insn->inputs_count = 3;
|
|
insn->op3 = end;
|
|
break;
|
|
} else if (insn->op == IR_LOOP_BEGIN && insn->inputs_count == 3) {
|
|
ZEND_ASSERT(jit->ctx.ir_base[insn->op3].op == IR_LOOP_END);
|
|
jit->ctx.ir_base[insn->op3].op = IR_END;
|
|
ir_MERGE_2(insn->op3, ir_END());
|
|
end = ir_LOOP_END();
|
|
insn = &jit->ctx.ir_base[begin];
|
|
insn->op3 = end;
|
|
break;
|
|
}
|
|
}
|
|
/* fallback to indirect JMP or RETURN */
|
|
if (GCC_GLOBAL_REGS) {
|
|
ir_TAILCALL(IR_VOID, ir_LOAD_A(jit_IP(jit)));
|
|
} else {
|
|
ir_RETURN(ir_CONST_I32(1));
|
|
}
|
|
} while (0);
|
|
|
|
bb = &jit->ssa->cfg.blocks[jit->b];
|
|
if (bb->successors_count > 0) {
|
|
int succ;
|
|
ir_ref ref;
|
|
|
|
ZEND_ASSERT(bb->successors_count == 1);
|
|
succ = bb->successors[0];
|
|
/* Add a fake control edge from UNREACHABLE/RETURN to the following ENTRY */
|
|
ref = jit->ctx.insns_count - 1;
|
|
ZEND_ASSERT(jit->ctx.ir_base[ref].op == IR_UNREACHABLE
|
|
|| jit->ctx.ir_base[ref].op == IR_RETURN
|
|
|| jit->ctx.ir_base[ref].op == IR_LOOP_END);
|
|
ZEND_ASSERT(jit->ssa->cfg.blocks[succ].flags & ZEND_BB_ENTRY);
|
|
ref = zend_jit_continue_entry(jit, ref, jit->ssa->cfg.blocks[succ].start);
|
|
if (func || (opline->opcode == ZEND_DO_UCALL)) {
|
|
_zend_jit_add_predecessor_ref(jit, succ, jit->b, ref);
|
|
jit->b = -1;
|
|
} else {
|
|
user_path = ref;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if ((!func || func->type == ZEND_INTERNAL_FUNCTION)
|
|
&& (opline->opcode != ZEND_DO_UCALL)) {
|
|
if (!func && (opline->opcode != ZEND_DO_ICALL)) {
|
|
ir_IF_FALSE(if_user);
|
|
}
|
|
|
|
// JIT: EG(current_execute_data) = execute_data;
|
|
ir_STORE(jit_EG(current_execute_data), rx);
|
|
|
|
bool may_have_observer = ZEND_OBSERVER_ENABLED && (!func || (func->common.fn_flags & (ZEND_ACC_CALL_VIA_TRAMPOLINE | ZEND_ACC_GENERATOR)) == 0);
|
|
if (may_have_observer) {
|
|
ir_ref observer_handler;
|
|
struct jit_observer_fcall_is_unobserved_data unobserved_data = jit_observer_fcall_is_unobserved_start(jit, func, &observer_handler, rx, func_ref ? func_ref : ir_LOAD_A(jit_CALL(rx, func)));
|
|
jit_observer_fcall_begin(jit, rx, observer_handler);
|
|
jit_observer_fcall_is_unobserved_end(jit, &unobserved_data);
|
|
}
|
|
|
|
// JIT: ZVAL_NULL(EX_VAR(opline->result.var));
|
|
ir_ref res_addr = IR_UNUSED, func_ptr;
|
|
|
|
if (RETURN_VALUE_USED(opline)) {
|
|
res_addr = ZEND_ADDR_MEM_ZVAL(ZREG_FP, opline->result.var);
|
|
} else {
|
|
/* CPU stack allocated temporary zval */
|
|
ir_ref ptr;
|
|
|
|
if (!jit->ctx.fixed_call_stack_size) {
|
|
// JIT: alloca(sizeof(void*));
|
|
ptr = ir_ALLOCA(ir_CONST_ADDR(sizeof(zval)));
|
|
} else {
|
|
ptr = ir_HARD_COPY_A(ir_RLOAD_A(IR_REG_SP));
|
|
}
|
|
res_addr = ZEND_ADDR_REF_ZVAL(ptr);
|
|
}
|
|
|
|
jit_set_Z_TYPE_INFO(jit, res_addr, IS_NULL);
|
|
|
|
zend_jit_reset_last_valid_opline(jit);
|
|
|
|
// JIT: (zend_execute_internal ? zend_execute_internal : fbc->internal_function.handler)(call, ret);
|
|
ir_ref res_ref = jit_ZVAL_ADDR(jit, res_addr);
|
|
if (zend_execute_internal) {
|
|
ir_CALL_2(IR_VOID, ir_CONST_FUNC(zend_execute_internal), rx, res_ref);
|
|
} else {
|
|
if (func) {
|
|
func_ptr = ir_CONST_FC_FUNC(func->internal_function.handler);
|
|
} else {
|
|
func_ptr = ir_LOAD_A(ir_ADD_OFFSET(func_ref, offsetof(zend_internal_function, handler)));
|
|
#if defined(IR_TARGET_X86)
|
|
func_ptr = ir_CAST_FC_FUNC(func_ptr);
|
|
#endif
|
|
}
|
|
ir_CALL_2(IR_VOID, func_ptr, rx, res_ref);
|
|
}
|
|
|
|
if (may_have_observer) {
|
|
jit_observer_fcall_end(jit, rx, res_ref);
|
|
}
|
|
|
|
/* When zend_interrupt_function is set, it gets called while
|
|
* the frame is still on top. This is less efficient than
|
|
* doing it later once it's popped off. There is code further
|
|
* down that handles when there isn't an interrupt function.
|
|
*/
|
|
if (zend_interrupt_function) {
|
|
// JIT: if (EG(vm_interrupt)) zend_fcall_interrupt(execute_data);
|
|
ir_ref if_interrupt = ir_IF(ir_LOAD_U8(jit_EG(vm_interrupt)));
|
|
ir_IF_TRUE_cold(if_interrupt);
|
|
ir_CALL_1(IR_VOID, ir_CONST_FC_FUNC(zend_fcall_interrupt), rx);
|
|
ir_MERGE_WITH_EMPTY_FALSE(if_interrupt);
|
|
}
|
|
|
|
// JIT: EG(current_execute_data) = execute_data;
|
|
ir_STORE(jit_EG(current_execute_data), jit_FP(jit));
|
|
|
|
// JIT: zend_vm_stack_free_args(call);
|
|
if (func && !unknown_num_args) {
|
|
for (i = 0; i < call_num_args; i++ ) {
|
|
if (zend_jit_needs_arg_dtor(func, i, call_info)) {
|
|
uint32_t offset = EX_NUM_TO_VAR(i);
|
|
zend_jit_addr var_addr = ZEND_ADDR_MEM_ZVAL(ZREG_RX, offset);
|
|
|
|
jit_ZVAL_PTR_DTOR(jit, var_addr, MAY_BE_ANY|MAY_BE_RC1|MAY_BE_RCN, 0, opline);
|
|
}
|
|
}
|
|
} else {
|
|
ir_CALL_1(IR_VOID, ir_CONST_FC_FUNC(zend_jit_vm_stack_free_args_helper), rx);
|
|
}
|
|
|
|
if (may_have_extra_named_params) {
|
|
// JIT: if (UNEXPECTED(ZEND_CALL_INFO(call) & ZEND_CALL_HAS_EXTRA_NAMED_PARAMS))
|
|
ir_ref if_has_named = ir_IF(ir_AND_U8(
|
|
ir_LOAD_U8(ir_ADD_OFFSET(rx, offsetof(zend_execute_data, This.u1.type_info) + 3)),
|
|
ir_CONST_U8(ZEND_CALL_HAS_EXTRA_NAMED_PARAMS >> 24)));
|
|
ir_IF_TRUE_cold(if_has_named);
|
|
|
|
ir_CALL_1(IR_VOID, ir_CONST_FC_FUNC(zend_free_extra_named_params),
|
|
ir_LOAD_A(jit_CALL(rx, extra_named_params)));
|
|
|
|
ir_MERGE_WITH_EMPTY_FALSE(if_has_named);
|
|
}
|
|
|
|
if (opline->opcode == ZEND_DO_FCALL) {
|
|
// TODO: optimize ???
|
|
// JIT: if (UNEXPECTED(ZEND_CALL_INFO(call) & ZEND_CALL_RELEASE_THIS))
|
|
ir_ref if_release_this = ir_IF(ir_AND_U8(
|
|
ir_LOAD_U8(ir_ADD_OFFSET(rx, offsetof(zend_execute_data, This.u1.type_info) + 2)),
|
|
ir_CONST_U8(ZEND_CALL_RELEASE_THIS >> 16)));
|
|
ir_IF_TRUE_cold(if_release_this);
|
|
|
|
// JIT: OBJ_RELEASE(Z_OBJ(RX->This));
|
|
jit_OBJ_RELEASE(jit, ir_LOAD_A(jit_CALL(rx, This.value.obj)));
|
|
|
|
ir_MERGE_WITH_EMPTY_FALSE(if_release_this);
|
|
}
|
|
|
|
|
|
ir_ref allocated_path = IR_UNUSED;
|
|
|
|
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) {
|
|
|
|
// JIT: zend_vm_stack_free_call_frame(call);
|
|
// JIT: if (UNEXPECTED(ZEND_CALL_INFO(call) & ZEND_CALL_ALLOCATED))
|
|
ir_ref if_allocated = ir_IF(ir_AND_U8(
|
|
ir_LOAD_U8(ir_ADD_OFFSET(rx, offsetof(zend_execute_data, This.u1.type_info) + 2)),
|
|
ir_CONST_U8(ZEND_CALL_ALLOCATED >> 16)));
|
|
ir_IF_TRUE_cold(if_allocated);
|
|
|
|
ir_CALL_1(IR_VOID, ir_CONST_FC_FUNC(zend_jit_free_call_frame), rx);
|
|
|
|
allocated_path = ir_END();
|
|
ir_IF_FALSE(if_allocated);
|
|
}
|
|
|
|
ir_STORE(jit_EG(vm_stack_top), rx);
|
|
|
|
if (allocated_path) {
|
|
ir_MERGE_WITH(allocated_path);
|
|
}
|
|
|
|
if (!RETURN_VALUE_USED(opline)) {
|
|
zend_class_entry *ce;
|
|
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)) {
|
|
ir_ref sp = ir_RLOAD_A(IR_REG_SP);
|
|
res_addr = ZEND_ADDR_REF_ZVAL(sp);
|
|
jit_ZVAL_PTR_DTOR(jit, res_addr, func_info, 1, opline);
|
|
}
|
|
if (!jit->ctx.fixed_call_stack_size) {
|
|
// JIT: revert alloca
|
|
ir_AFREE(ir_CONST_ADDR(sizeof(zval)));
|
|
}
|
|
}
|
|
|
|
// JIT: if (UNEXPECTED(EG(exception) != NULL)) {
|
|
ir_GUARD_NOT(ir_LOAD_A(jit_EG_exception(jit)),
|
|
jit_STUB_ADDR(jit, jit_stub_icall_throw));
|
|
|
|
/* If there isn't a zend_interrupt_function, the timeout is
|
|
* handled here because it's more efficient.
|
|
*/
|
|
if (!zend_interrupt_function) {
|
|
// TODO: Can we avoid checking for interrupts after each call ???
|
|
if (trace && jit->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;
|
|
}
|
|
|
|
zend_jit_check_timeout(jit, opline + 1, exit_addr);
|
|
}
|
|
|
|
if ((!trace || !func) && opline->opcode != ZEND_DO_ICALL) {
|
|
jit_LOAD_IP_ADDR(jit, opline + 1);
|
|
} else if (trace
|
|
&& trace->op == ZEND_JIT_TRACE_END
|
|
&& trace->stop >= ZEND_JIT_TRACE_STOP_INTERPRETER) {
|
|
jit_LOAD_IP_ADDR(jit, opline + 1);
|
|
}
|
|
}
|
|
|
|
if (user_path) {
|
|
ir_MERGE_WITH(user_path);
|
|
}
|
|
|
|
return 1;
|
|
}
|
|
|
|
static int zend_jit_constructor(zend_jit_ctx *jit, const zend_op *opline, const zend_op_array *op_array, zend_ssa *ssa, int call_level, int next_block)
|
|
{
|
|
ir_ref if_skip_constructor = jit_IF_ex(jit, jit_CMP_IP(jit, IR_NE, opline), next_block);
|
|
|
|
ir_IF_FALSE(if_skip_constructor);
|
|
|
|
if (JIT_G(opt_level) < ZEND_JIT_LEVEL_INLINE) {
|
|
if (!zend_jit_tail_handler(jit, opline)) {
|
|
return 0;
|
|
}
|
|
} else {
|
|
if (!zend_jit_do_fcall(jit, opline, op_array, ssa, call_level, next_block, NULL)) {
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
/* override predecessors of the next block */
|
|
ZEND_ASSERT(jit->ssa->cfg.blocks[next_block].predecessors_count == 1);
|
|
if (!jit->ctx.control) {
|
|
ZEND_ASSERT(jit->bb_edges[jit->bb_predecessors[next_block]]);
|
|
ir_IF_TRUE(if_skip_constructor);
|
|
ir_MERGE_2(jit->bb_edges[jit->bb_predecessors[next_block]], ir_END());
|
|
jit->bb_edges[jit->bb_predecessors[next_block]] = ir_END();
|
|
} else {
|
|
ZEND_ASSERT(!jit->bb_edges[jit->bb_predecessors[next_block]]);
|
|
/* merge current control path with the true branch of constructor skip condition */
|
|
ir_MERGE_WITH_EMPTY_TRUE(if_skip_constructor);
|
|
jit->bb_edges[jit->bb_predecessors[next_block]] = ir_END();
|
|
|
|
jit->b = -1;
|
|
}
|
|
|
|
return 1;
|
|
}
|
|
|
|
static int zend_jit_verify_arg_type(zend_jit_ctx *jit, const zend_op *opline, zend_arg_info *arg_info, bool check_exception)
|
|
{
|
|
zend_jit_addr res_addr = ZEND_ADDR_MEM_ZVAL(ZREG_FP, opline->result.var);
|
|
uint32_t type_mask = ZEND_TYPE_PURE_MASK(arg_info->type) & MAY_BE_ANY;
|
|
ir_ref ref, fast_path = IR_UNUSED;
|
|
|
|
ref = jit_ZVAL_ADDR(jit, res_addr);
|
|
if (JIT_G(trigger) == ZEND_JIT_ON_HOT_TRACE
|
|
&& JIT_G(current_frame)
|
|
&& JIT_G(current_frame)->prev) {
|
|
zend_jit_trace_stack *stack = JIT_G(current_frame)->stack;
|
|
uint8_t type = STACK_TYPE(stack, EX_VAR_TO_NUM(opline->result.var));
|
|
|
|
if (type != IS_UNKNOWN && (type_mask & (1u << type))) {
|
|
return 1;
|
|
}
|
|
}
|
|
|
|
if (ZEND_ARG_SEND_MODE(arg_info)) {
|
|
if (opline->opcode == ZEND_RECV_INIT) {
|
|
ref = jit_ZVAL_DEREF_ref(jit, ref);
|
|
} else {
|
|
ref = jit_Z_PTR_ref(jit, ref);
|
|
ref = ir_ADD_OFFSET(ref, offsetof(zend_reference, val));
|
|
}
|
|
}
|
|
|
|
if (type_mask != 0) {
|
|
if (is_power_of_two(type_mask)) {
|
|
uint32_t type_code = concrete_type(type_mask);
|
|
ir_ref if_ok = jit_if_Z_TYPE_ref(jit, ref, ir_CONST_U8(type_code));
|
|
ir_IF_TRUE(if_ok);
|
|
fast_path = ir_END();
|
|
ir_IF_FALSE_cold(if_ok);
|
|
} else {
|
|
ir_ref if_ok = ir_IF(ir_AND_U32(
|
|
ir_SHL_U32(ir_CONST_U32(1), jit_Z_TYPE_ref(jit, ref)),
|
|
ir_CONST_U32(type_mask)));
|
|
ir_IF_TRUE(if_ok);
|
|
fast_path = ir_END();
|
|
ir_IF_FALSE_cold(if_ok);
|
|
}
|
|
}
|
|
|
|
jit_SET_EX_OPLINE(jit, opline);
|
|
ref = ir_CALL_2(IR_BOOL, ir_CONST_FC_FUNC(zend_jit_verify_arg_slow),
|
|
ref, ir_CONST_ADDR(arg_info));
|
|
|
|
if (check_exception) {
|
|
ir_GUARD(ref, jit_STUB_ADDR(jit, jit_stub_exception_handler));
|
|
}
|
|
|
|
if (fast_path) {
|
|
ir_MERGE_WITH(fast_path);
|
|
}
|
|
|
|
return 1;
|
|
}
|
|
|
|
static int zend_jit_recv(zend_jit_ctx *jit, 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) {
|
|
if (JIT_G(trigger) == ZEND_JIT_ON_HOT_TRACE) {
|
|
if (!JIT_G(current_frame) ||
|
|
TRACE_FRAME_NUM_ARGS(JIT_G(current_frame)) < 0 ||
|
|
arg_num > TRACE_FRAME_NUM_ARGS(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);
|
|
|
|
if (!exit_addr) {
|
|
return 0;
|
|
}
|
|
ir_GUARD(ir_GE(ir_LOAD_U32(jit_EX(This.u2.num_args)), ir_CONST_U32(arg_num)),
|
|
ir_CONST_ADDR(exit_addr));
|
|
}
|
|
} else {
|
|
ir_ref if_ok =ir_IF(ir_GE(ir_LOAD_U32(jit_EX(This.u2.num_args)), ir_CONST_U32(arg_num)));
|
|
ir_IF_FALSE_cold(if_ok);
|
|
|
|
jit_SET_EX_OPLINE(jit, opline);
|
|
ir_CALL_1(IR_VOID, ir_CONST_FC_FUNC(zend_missing_arg_error), jit_FP(jit));
|
|
ir_IJMP(jit_STUB_ADDR(jit, jit_stub_exception_handler));
|
|
ir_IF_TRUE(if_ok);
|
|
}
|
|
}
|
|
|
|
if (arg_info) {
|
|
if (!zend_jit_verify_arg_type(jit, opline, arg_info, 1)) {
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
return 1;
|
|
}
|
|
|
|
static int zend_jit_recv_init(zend_jit_ctx *jit, const zend_op *opline, const zend_op_array *op_array, 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);
|
|
ir_ref ref, if_fail, skip_path = IR_UNUSED;
|
|
|
|
if (JIT_G(trigger) == ZEND_JIT_ON_HOT_TRACE
|
|
&& JIT_G(current_frame)
|
|
&& TRACE_FRAME_NUM_ARGS(JIT_G(current_frame)) >= 0) {
|
|
if (arg_num > TRACE_FRAME_NUM_ARGS(JIT_G(current_frame))) {
|
|
jit_ZVAL_COPY_CONST(jit,
|
|
res_addr,
|
|
-1, -1,
|
|
zv, 1);
|
|
}
|
|
} else {
|
|
if (JIT_G(trigger) != ZEND_JIT_ON_HOT_TRACE ||
|
|
(op_array->fn_flags & ZEND_ACC_HAS_TYPE_HINTS)) {
|
|
ir_ref if_skip = ir_IF(ir_GE(ir_LOAD_U32(jit_EX(This.u2.num_args)), ir_CONST_U32(arg_num)));
|
|
ir_IF_TRUE(if_skip);
|
|
skip_path = ir_END();
|
|
ir_IF_FALSE(if_skip);
|
|
}
|
|
jit_ZVAL_COPY_CONST(jit,
|
|
res_addr,
|
|
-1, -1,
|
|
zv, 1);
|
|
}
|
|
|
|
if (Z_CONSTANT_P(zv)) {
|
|
jit_SET_EX_OPLINE(jit, opline);
|
|
ref = ir_CALL_2(IR_I32, ir_CONST_FC_FUNC(zval_update_constant_ex),
|
|
jit_ZVAL_ADDR(jit, res_addr),
|
|
ir_LOAD_A(ir_ADD_OFFSET(ir_LOAD_A(jit_EX(func)), offsetof(zend_op_array, scope))));
|
|
|
|
if_fail = ir_IF(ref);
|
|
ir_IF_TRUE_cold(if_fail);
|
|
jit_ZVAL_PTR_DTOR(jit, res_addr, MAY_BE_ANY|MAY_BE_RC1|MAY_BE_RCN, 1, opline);
|
|
ir_IJMP(jit_STUB_ADDR(jit, jit_stub_exception_handler));
|
|
ir_IF_FALSE(if_fail);
|
|
}
|
|
|
|
if (skip_path) {
|
|
ir_MERGE_WITH(skip_path);
|
|
}
|
|
|
|
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(jit, opline, arg_info, may_throw)) {
|
|
return 0;
|
|
}
|
|
} while (0);
|
|
}
|
|
|
|
return 1;
|
|
}
|
|
|
|
static bool zend_jit_verify_return_type(zend_jit_ctx *jit, 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();
|
|
bool needs_slow_check = 1;
|
|
uint32_t type_mask = ZEND_TYPE_PURE_MASK(arg_info->type) & MAY_BE_ANY;
|
|
ir_ref fast_path = IR_UNUSED;
|
|
|
|
if (type_mask != 0) {
|
|
if (((op1_info & MAY_BE_ANY) & type_mask) == 0) {
|
|
/* pass */
|
|
} 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);
|
|
ir_ref if_ok = jit_if_Z_TYPE(jit, op1_addr, type_code);
|
|
|
|
ir_IF_TRUE(if_ok);
|
|
fast_path = ir_END();
|
|
ir_IF_FALSE_cold(if_ok);
|
|
} else {
|
|
ir_ref if_ok = ir_IF(ir_AND_U32(
|
|
ir_SHL_U32(ir_CONST_U32(1), jit_Z_TYPE(jit, op1_addr)),
|
|
ir_CONST_U32(type_mask)));
|
|
|
|
ir_IF_TRUE(if_ok);
|
|
fast_path = ir_END();
|
|
ir_IF_FALSE_cold(if_ok);
|
|
}
|
|
}
|
|
if (needs_slow_check) {
|
|
ir_ref ref;
|
|
|
|
jit_SET_EX_OPLINE(jit, opline);
|
|
ref = jit_ZVAL_ADDR(jit, op1_addr);
|
|
if (op1_info & MAY_BE_UNDEF) {
|
|
ref = zend_jit_zval_check_undef(jit, ref, opline->op1.var, NULL, 1);
|
|
}
|
|
|
|
ir_CALL_4(IR_VOID, ir_CONST_FC_FUNC(zend_jit_verify_return_slow),
|
|
ref,
|
|
ir_LOAD_A(jit_EX(func)),
|
|
ir_CONST_ADDR(arg_info),
|
|
ir_ADD_OFFSET(ir_LOAD_A(jit_EX(run_time_cache)), opline->op2.num));
|
|
|
|
zend_jit_check_exception(jit);
|
|
|
|
if (fast_path) {
|
|
ir_MERGE_WITH(fast_path);
|
|
}
|
|
}
|
|
|
|
return 1;
|
|
}
|
|
|
|
static int zend_jit_leave_frame(zend_jit_ctx *jit)
|
|
{
|
|
// JIT: EG(current_execute_data) = EX(prev_execute_data);
|
|
ir_STORE(jit_EG(current_execute_data), ir_LOAD_A(jit_EX(prev_execute_data)));
|
|
return 1;
|
|
}
|
|
|
|
static int zend_jit_free_cvs(zend_jit_ctx *jit)
|
|
{
|
|
// JIT: EG(current_execute_data) = EX(prev_execute_data);
|
|
ir_STORE(jit_EG(current_execute_data), ir_LOAD_A(jit_EX(prev_execute_data)));
|
|
|
|
// JIT: zend_free_compiled_variables(execute_data);
|
|
ir_CALL_1(IR_VOID, ir_CONST_FC_FUNC(zend_free_compiled_variables), jit_FP(jit));
|
|
return 1;
|
|
}
|
|
|
|
static int zend_jit_free_cv(zend_jit_ctx *jit, uint32_t info, uint32_t var)
|
|
{
|
|
if (info & (MAY_BE_STRING|MAY_BE_ARRAY|MAY_BE_OBJECT|MAY_BE_RESOURCE|MAY_BE_REF)) {
|
|
zend_jit_addr var_addr = ZEND_ADDR_MEM_ZVAL(ZREG_FP, EX_NUM_TO_VAR(var));
|
|
|
|
jit_ZVAL_PTR_DTOR(jit, var_addr, info, 1, NULL);
|
|
}
|
|
return 1;
|
|
}
|
|
|
|
static int zend_jit_free_op(zend_jit_ctx *jit, 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)) {
|
|
jit_ZVAL_PTR_DTOR(jit, ZEND_ADDR_MEM_ZVAL(ZREG_FP, var_offset), info, 0, opline);
|
|
}
|
|
return 1;
|
|
}
|
|
|
|
static int zend_jit_leave_func(zend_jit_ctx *jit,
|
|
const zend_op_array *op_array,
|
|
const zend_op *opline,
|
|
uint32_t op1_info,
|
|
bool left_frame,
|
|
zend_jit_trace_rec *trace,
|
|
zend_jit_trace_info *trace_info,
|
|
int indirect_var_access,
|
|
int may_throw)
|
|
{
|
|
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));
|
|
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 */
|
|
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)));
|
|
ir_ref call_info = IR_UNUSED, ref, cold_path = IR_UNUSED;
|
|
|
|
if (may_need_call_helper) {
|
|
if (!left_frame) {
|
|
left_frame = 1;
|
|
if (!zend_jit_leave_frame(jit)) {
|
|
return 0;
|
|
}
|
|
}
|
|
/* ZEND_CALL_FAKE_CLOSURE handled on slow path to eliminate check for ZEND_CALL_CLOSURE on fast path */
|
|
call_info = ir_LOAD_U32(jit_EX(This.u1.type_info));
|
|
ref = ir_AND_U32(call_info,
|
|
ir_CONST_U32(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) {
|
|
ir_ref if_slow = ir_IF(ref);
|
|
|
|
ir_IF_TRUE_cold(if_slow);
|
|
if (!GCC_GLOBAL_REGS) {
|
|
ref = ir_CALL_1(IR_I32, ir_CONST_FC_FUNC(zend_jit_leave_func_helper), jit_FP(jit));
|
|
} else {
|
|
ir_CALL(IR_VOID, ir_CONST_FC_FUNC(zend_jit_leave_func_helper));
|
|
}
|
|
|
|
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) {
|
|
ir_GUARD(jit_IP(jit), jit_STUB_ADDR(jit, jit_stub_trace_halt));
|
|
} else {
|
|
ir_GUARD(ir_GE(ref, ir_CONST_I32(0)), jit_STUB_ADDR(jit, jit_stub_trace_halt));
|
|
}
|
|
}
|
|
|
|
if (!GCC_GLOBAL_REGS) {
|
|
// execute_data = EG(current_execute_data)
|
|
jit_STORE_FP(jit, ir_LOAD_A(jit_EG(current_execute_data)));
|
|
}
|
|
cold_path = ir_END();
|
|
ir_IF_FALSE(if_slow);
|
|
} else {
|
|
ir_GUARD_NOT(ref, jit_STUB_ADDR(jit, jit_stub_leave_function_handler));
|
|
}
|
|
}
|
|
|
|
if ((op_array->fn_flags & (ZEND_ACC_CLOSURE|ZEND_ACC_FAKE_CLOSURE)) == ZEND_ACC_CLOSURE) {
|
|
if (!left_frame) {
|
|
left_frame = 1;
|
|
if (!zend_jit_leave_frame(jit)) {
|
|
return 0;
|
|
}
|
|
}
|
|
// JIT: OBJ_RELEASE(ZEND_CLOSURE_OBJECT(EX(func)));
|
|
jit_OBJ_RELEASE(jit, ir_ADD_OFFSET(ir_LOAD_A(jit_EX(func)), -sizeof(zend_object)));
|
|
} else if (may_need_release_this) {
|
|
ir_ref if_release, fast_path = IR_UNUSED;
|
|
|
|
if (!left_frame) {
|
|
left_frame = 1;
|
|
if (!zend_jit_leave_frame(jit)) {
|
|
return 0;
|
|
}
|
|
}
|
|
if (!JIT_G(current_frame) || !TRACE_FRAME_ALWAYS_RELEASE_THIS(JIT_G(current_frame))) {
|
|
// JIT: if (call_info & ZEND_CALL_RELEASE_THIS)
|
|
if (!call_info) {
|
|
call_info = ir_LOAD_U32(jit_EX(This.u1.type_info));
|
|
}
|
|
if_release = ir_IF(ir_AND_U32(call_info, ir_CONST_U32(ZEND_CALL_RELEASE_THIS)));
|
|
ir_IF_FALSE(if_release);
|
|
fast_path = ir_END();
|
|
ir_IF_TRUE(if_release);
|
|
}
|
|
// JIT: OBJ_RELEASE(execute_data->This))
|
|
jit_OBJ_RELEASE(jit, ir_LOAD_A(jit_EX(This.value.obj)));
|
|
if (fast_path) {
|
|
ir_MERGE_WITH(fast_path);
|
|
}
|
|
// TODO: avoid EG(excption) check for $this->foo() calls
|
|
may_throw = 1;
|
|
}
|
|
|
|
// JIT: EG(vm_stack_top) = (zval*)execute_data
|
|
ir_STORE(jit_EG(vm_stack_top), jit_FP(jit));
|
|
|
|
// JITL execute_data = EX(prev_execute_data)
|
|
jit_STORE_FP(jit, ir_LOAD_A(jit_EX(prev_execute_data)));
|
|
|
|
if (!left_frame) {
|
|
// JIT: EG(current_execute_data) = execute_data
|
|
ir_STORE(jit_EG(current_execute_data), jit_FP(jit));
|
|
}
|
|
|
|
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(jit);
|
|
} else {
|
|
if (GCC_GLOBAL_REGS) {
|
|
/* We add extra RLOAD and RSTORE to make fusion for persistent register
|
|
* mov (%FP), %IP
|
|
* add $0x1c, %IP
|
|
* The naive (commented) code leads to extra register allocation and move.
|
|
* mov (%FP), %tmp
|
|
* add $0x1c, %tmp
|
|
* mov %tmp, %FP
|
|
*/
|
|
#if 0
|
|
jit_STORE_IP(jit, ir_ADD_OFFSET(ir_LOAD_A(jit_EX(opline)), sizeof(zend_op)));
|
|
#else
|
|
jit_STORE_IP(jit, ir_LOAD_A(jit_EX(opline)));
|
|
jit_STORE_IP(jit, ir_ADD_OFFSET(jit_IP(jit), sizeof(zend_op)));
|
|
#endif
|
|
} else {
|
|
ir_ref ref = jit_EX(opline);
|
|
|
|
ir_STORE(ref, ir_ADD_OFFSET(ir_LOAD_A(ref), sizeof(zend_op)));
|
|
}
|
|
}
|
|
|
|
if (cold_path) {
|
|
ir_MERGE_WITH(cold_path);
|
|
}
|
|
|
|
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 */
|
|
// JIT: if (EG(exception))
|
|
ir_GUARD_NOT(ir_LOAD_A(jit_EG(exception)), jit_STUB_ADDR(jit, jit_stub_leave_throw));
|
|
}
|
|
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;
|
|
|
|
ir_ref if_eq = ir_IF(jit_CMP_IP(jit, IR_EQ, next_opline));
|
|
|
|
ir_IF_TRUE(if_eq);
|
|
ZEND_ASSERT(jit->trace_loop_ref);
|
|
ZEND_ASSERT(jit->ctx.ir_base[jit->trace_loop_ref].op2 == IR_UNUSED);
|
|
ir_MERGE_SET_OP(jit->trace_loop_ref, 2, ir_END());
|
|
ir_IF_FALSE(if_eq);
|
|
|
|
#ifdef ZEND_VM_HYBRID_JIT_RED_ZONE_SIZE
|
|
ir_TAILCALL(IR_VOID, ir_LOAD_A(jit_IP(jit)));
|
|
#else
|
|
ir_IJMP(jit_STUB_ADDR(jit, jit_stub_trace_escape));
|
|
#endif
|
|
} else {
|
|
ir_GUARD(jit_CMP_IP(jit, IR_EQ, next_opline), jit_STUB_ADDR(jit, jit_stub_trace_escape));
|
|
}
|
|
|
|
zend_jit_set_last_valid_opline(jit, 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))))) {
|
|
// JIT: if (EG(exception))
|
|
ir_GUARD_NOT(ir_LOAD_A(jit_EG(exception)), jit_STUB_ADDR(jit, jit_stub_leave_throw));
|
|
}
|
|
|
|
return 1;
|
|
} else {
|
|
// JIT: if (EG(exception))
|
|
ir_GUARD_NOT(ir_LOAD_A(jit_EG(exception)), jit_STUB_ADDR(jit, jit_stub_leave_throw));
|
|
// JIT: opline = EX(opline) + 1
|
|
if (GCC_GLOBAL_REGS) {
|
|
jit_STORE_IP(jit, ir_LOAD_A(jit_EX(opline)));
|
|
jit_STORE_IP(jit, ir_ADD_OFFSET(jit_IP(jit), sizeof(zend_op)));
|
|
} else {
|
|
ir_ref ref = jit_EX(opline);
|
|
|
|
ir_STORE(ref, ir_ADD_OFFSET(ir_LOAD_A(ref), sizeof(zend_op)));
|
|
}
|
|
}
|
|
|
|
if (GCC_GLOBAL_REGS) {
|
|
ir_TAILCALL(IR_VOID, ir_LOAD_A(jit_IP(jit)));
|
|
} else {
|
|
ir_RETURN(ir_CONST_I32(2)); // ZEND_VM_LEAVE
|
|
}
|
|
|
|
jit->b = -1;
|
|
|
|
return 1;
|
|
}
|
|
|
|
static void zend_jit_common_return(zend_jit_ctx *jit)
|
|
{
|
|
ZEND_ASSERT(jit->return_inputs);
|
|
ir_MERGE_list(jit->return_inputs);
|
|
}
|
|
|
|
static int zend_jit_return(zend_jit_ctx *jit, 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 = -1;
|
|
ir_ref return_value = IR_UNUSED, ref, refcount, if_return_value_used = IR_UNUSED;
|
|
|
|
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->return_inputs = IR_UNUSED;
|
|
if (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;
|
|
}
|
|
}
|
|
}
|
|
|
|
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_inv(jit, op1_addr, dst, op1_info)) {
|
|
return 0;
|
|
}
|
|
op1_addr = dst;
|
|
}
|
|
jit_observer_fcall_end(jit, jit_FP(jit), jit_ZVAL_ADDR(jit, op1_addr));
|
|
}
|
|
|
|
// JIT: if (!EX(return_value))
|
|
return_value = ir_LOAD_A(jit_EX(return_value));
|
|
ret_addr = ZEND_ADDR_REF_ZVAL(return_value);
|
|
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) {
|
|
if_return_value_used = ir_IF(return_value);
|
|
ir_IF_FALSE_cold(if_return_value_used);
|
|
}
|
|
if (return_value_used != 1) {
|
|
if (op1_info & ((MAY_BE_UNDEF|MAY_BE_ANY|MAY_BE_REF)-(MAY_BE_OBJECT|MAY_BE_RESOURCE))) {
|
|
ir_ref if_refcounted = jit_if_REFCOUNTED(jit, op1_addr);
|
|
ir_IF_FALSE(if_refcounted);
|
|
ir_END_list(jit->return_inputs);
|
|
ir_IF_TRUE(if_refcounted);
|
|
}
|
|
ref = jit_Z_PTR(jit, op1_addr);
|
|
refcount = jit_GC_DELREF(jit, ref);
|
|
|
|
if (RC_MAY_BE_1(op1_info)) {
|
|
if (RC_MAY_BE_N(op1_info)) {
|
|
ir_ref if_non_zero = ir_IF(refcount);
|
|
ir_IF_TRUE(if_non_zero);
|
|
ir_END_list(jit->return_inputs);
|
|
ir_IF_FALSE(if_non_zero);
|
|
}
|
|
jit_ZVAL_DTOR(jit, ref, op1_info, opline);
|
|
}
|
|
if (return_value_used == -1) {
|
|
ir_END_list(jit->return_inputs);
|
|
}
|
|
}
|
|
} else if (return_value_used == -1) {
|
|
if_return_value_used = ir_IF(return_value);
|
|
ir_IF_FALSE_cold(if_return_value_used);
|
|
ir_END_list(jit->return_inputs);
|
|
}
|
|
|
|
if (if_return_value_used) {
|
|
ir_IF_TRUE(if_return_value_used);
|
|
}
|
|
|
|
if (return_value_used == 0) {
|
|
if (jit->return_inputs) {
|
|
ZEND_ASSERT(JIT_G(trigger) == ZEND_JIT_ON_HOT_TRACE);
|
|
ir_END_list(jit->return_inputs);
|
|
ir_MERGE_list(jit->return_inputs);
|
|
jit->return_inputs = IR_UNUSED;
|
|
}
|
|
return 1;
|
|
}
|
|
|
|
if (opline->op1_type == IS_CONST) {
|
|
zval *zv = RT_CONSTANT(opline, opline->op1);
|
|
|
|
jit_ZVAL_COPY_CONST(jit, ret_addr, MAY_BE_ANY, MAY_BE_ANY, zv, 1);
|
|
} else if (opline->op1_type == IS_TMP_VAR) {
|
|
jit_ZVAL_COPY(jit, ret_addr, MAY_BE_ANY, op1_addr, op1_info, 0);
|
|
} else if (opline->op1_type == IS_CV) {
|
|
if (op1_info & MAY_BE_REF) {
|
|
ref = jit_ZVAL_ADDR(jit, op1_addr);
|
|
ref = jit_ZVAL_DEREF_ref(jit, ref);
|
|
op1_addr = ZEND_ADDR_REF_ZVAL(ref);
|
|
}
|
|
|
|
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) {
|
|
jit_ZVAL_COPY(jit, ret_addr, MAY_BE_ANY, op1_addr, op1_info, 1);
|
|
} else if (return_value_used != 1) {
|
|
jit_ZVAL_COPY(jit, ret_addr, MAY_BE_ANY, op1_addr, op1_info, 0);
|
|
// JIT: if (EXPECTED(!(EX_CALL_INFO() & ZEND_CALL_CODE))) ZVAL_NULL(retval_ptr);
|
|
jit_set_Z_TYPE_INFO(jit, op1_addr, IS_NULL);
|
|
} else {
|
|
jit_ZVAL_COPY(jit, ret_addr, MAY_BE_ANY, op1_addr, op1_info, 0);
|
|
}
|
|
} else {
|
|
jit_ZVAL_COPY(jit, ret_addr, MAY_BE_ANY, op1_addr, op1_info, 0);
|
|
}
|
|
} else {
|
|
if (op1_info & MAY_BE_REF) {
|
|
ir_ref if_ref, ref2, if_non_zero;
|
|
zend_jit_addr ref_addr;
|
|
|
|
if_ref = jit_if_Z_TYPE(jit, op1_addr, IS_REFERENCE);
|
|
ir_IF_TRUE_cold(if_ref);
|
|
|
|
// JIT: zend_refcounted *ref = Z_COUNTED_P(retval_ptr)
|
|
ref = jit_Z_PTR(jit, op1_addr);
|
|
|
|
// JIT: ZVAL_COPY_VALUE(return_value, &ref->value)
|
|
ref2 = ir_ADD_OFFSET(ref, offsetof(zend_reference, val));
|
|
ref_addr = ZEND_ADDR_REF_ZVAL(ref2);
|
|
jit_ZVAL_COPY(jit, ret_addr, MAY_BE_ANY, ref_addr, op1_info, 0);
|
|
ref2 = jit_GC_DELREF(jit, ref);
|
|
if_non_zero = ir_IF(ref2);
|
|
ir_IF_TRUE(if_non_zero);
|
|
|
|
// JIT: if (IS_REFCOUNTED())
|
|
ir_ref if_refcounted = jit_if_REFCOUNTED(jit, ret_addr);
|
|
ir_IF_FALSE(if_refcounted);
|
|
ir_END_list(jit->return_inputs);
|
|
ir_IF_TRUE(if_refcounted);
|
|
|
|
// JIT: ADDREF
|
|
ref2 = jit_Z_PTR(jit, ret_addr);
|
|
jit_GC_ADDREF(jit, ref2);
|
|
ir_END_list(jit->return_inputs);
|
|
|
|
ir_IF_FALSE(if_non_zero);
|
|
|
|
jit_EFREE(jit, ref, sizeof(zend_reference), op_array, opline);
|
|
ir_END_list(jit->return_inputs);
|
|
|
|
ir_IF_FALSE(if_ref);
|
|
}
|
|
jit_ZVAL_COPY(jit, ret_addr, MAY_BE_ANY, op1_addr, op1_info, 0);
|
|
}
|
|
|
|
if (JIT_G(trigger) == ZEND_JIT_ON_HOT_TRACE) {
|
|
if (jit->return_inputs) {
|
|
ir_END_list(jit->return_inputs);
|
|
ir_MERGE_list(jit->return_inputs);
|
|
jit->return_inputs = IR_UNUSED;
|
|
}
|
|
} else {
|
|
ir_END_list(jit->return_inputs);
|
|
jit->b = -1;
|
|
}
|
|
|
|
return 1;
|
|
}
|
|
|
|
static int zend_jit_bind_global(zend_jit_ctx *jit, 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));
|
|
ir_ref cache_slot_ref, idx_ref, num_used_ref, bucket_ref, ref, ref2;
|
|
ir_ref if_fit, if_reference, if_same_key, fast_path;
|
|
ir_ref slow_inputs = IR_UNUSED, end_inputs = IR_UNUSED;
|
|
|
|
// JIT: idx = (uintptr_t)CACHED_PTR(opline->extended_value) - 1;
|
|
cache_slot_ref = ir_ADD_OFFSET(ir_LOAD_A(jit_EX(run_time_cache)), opline->extended_value);
|
|
idx_ref = ir_SUB_A(ir_LOAD_A(cache_slot_ref), ir_CONST_ADDR(1));
|
|
|
|
// JIT: if (EXPECTED(idx < EG(symbol_table).nNumUsed * sizeof(Bucket)))
|
|
num_used_ref = ir_MUL_U32(ir_LOAD_U32(jit_EG(symbol_table.nNumUsed)),
|
|
ir_CONST_U32(sizeof(Bucket)));
|
|
if (sizeof(void*) == 8) {
|
|
num_used_ref = ir_ZEXT_A(num_used_ref);
|
|
}
|
|
if_fit = ir_IF(ir_ULT(idx_ref, num_used_ref));
|
|
ir_IF_FALSE_cold(if_fit);
|
|
ir_END_list(slow_inputs);
|
|
ir_IF_TRUE(if_fit);
|
|
|
|
// JIT: Bucket *p = (Bucket*)((char*)EG(symbol_table).arData + idx);
|
|
bucket_ref = ir_ADD_A(ir_LOAD_A(jit_EG(symbol_table.arData)), idx_ref);
|
|
if_reference = jit_if_Z_TYPE_ref(jit, bucket_ref, ir_CONST_U8(IS_REFERENCE));
|
|
ir_IF_FALSE_cold(if_reference);
|
|
ir_END_list(slow_inputs);
|
|
ir_IF_TRUE(if_reference);
|
|
|
|
// JIT: (EXPECTED(p->key == varname))
|
|
if_same_key = ir_IF(ir_EQ(ir_LOAD_A(ir_ADD_OFFSET(bucket_ref, offsetof(Bucket, key))), ir_CONST_ADDR(varname)));
|
|
ir_IF_FALSE_cold(if_same_key);
|
|
ir_END_list(slow_inputs);
|
|
ir_IF_TRUE(if_same_key);
|
|
|
|
// JIT: GC_ADDREF(Z_PTR(p->val))
|
|
ref = jit_Z_PTR_ref(jit, bucket_ref);
|
|
jit_GC_ADDREF(jit, ref);
|
|
|
|
fast_path = ir_END();
|
|
ir_MERGE_list(slow_inputs);
|
|
|
|
ref2 = ir_CALL_2(IR_ADDR, ir_CONST_FC_FUNC(zend_jit_fetch_global_helper),
|
|
ir_CONST_ADDR(varname),
|
|
cache_slot_ref);
|
|
|
|
ir_MERGE_WITH(fast_path);
|
|
ref = ir_PHI_2(IR_ADDR, ref2, ref);
|
|
|
|
if (op1_info & (MAY_BE_STRING|MAY_BE_ARRAY|MAY_BE_OBJECT|MAY_BE_RESOURCE|MAY_BE_REF)) {
|
|
ir_ref if_refcounted = IR_UNUSED, refcount, if_non_zero, if_may_not_leak;
|
|
|
|
if (op1_info & ((MAY_BE_ANY|MAY_BE_UNDEF) - (MAY_BE_OBJECT|MAY_BE_RESOURCE))) {
|
|
// JIT: if (UNEXPECTED(Z_REFCOUNTED_P(variable_ptr)))
|
|
if_refcounted = jit_if_REFCOUNTED(jit, op1_addr);
|
|
ir_IF_TRUE_cold(if_refcounted);
|
|
}
|
|
|
|
// JIT:zend_refcounted *garbage = Z_COUNTED_P(variable_ptr);
|
|
ref2 = jit_Z_PTR(jit, op1_addr);
|
|
|
|
// JIT: ZVAL_REF(variable_ptr, ref)
|
|
jit_set_Z_PTR(jit, op1_addr, ref);
|
|
jit_set_Z_TYPE_INFO(jit, op1_addr, IS_REFERENCE_EX);
|
|
|
|
// JIT: if (GC_DELREF(garbage) == 0)
|
|
refcount = jit_GC_DELREF(jit, ref2);
|
|
if_non_zero = ir_IF(refcount);
|
|
if (!(op1_info & (MAY_BE_REF|MAY_BE_ARRAY|MAY_BE_OBJECT))) {
|
|
ir_IF_TRUE(if_non_zero);
|
|
ir_END_list(end_inputs);
|
|
}
|
|
ir_IF_FALSE(if_non_zero);
|
|
|
|
jit_ZVAL_DTOR(jit, ref2, op1_info, opline);
|
|
if (op1_info & (MAY_BE_REF|MAY_BE_ARRAY|MAY_BE_OBJECT)) {
|
|
ir_END_list(end_inputs);
|
|
ir_IF_TRUE(if_non_zero);
|
|
|
|
// JIT: GC_ZVAL_CHECK_POSSIBLE_ROOT(variable_ptr)
|
|
if_may_not_leak = jit_if_GC_MAY_NOT_LEAK(jit, ref2);
|
|
ir_IF_TRUE(if_may_not_leak);
|
|
ir_END_list(end_inputs);
|
|
ir_IF_FALSE(if_may_not_leak);
|
|
ir_CALL_1(IR_VOID, ir_CONST_FC_FUNC(gc_possible_root), ref2);
|
|
}
|
|
if (op1_info & ((MAY_BE_ANY|MAY_BE_UNDEF) - (MAY_BE_OBJECT|MAY_BE_RESOURCE))) {
|
|
ir_END_list(end_inputs);
|
|
ir_IF_FALSE(if_refcounted);
|
|
}
|
|
}
|
|
|
|
if (op1_info & ((MAY_BE_ANY|MAY_BE_UNDEF) - (MAY_BE_OBJECT|MAY_BE_RESOURCE))) {
|
|
// JIT: ZVAL_REF(variable_ptr, ref)
|
|
jit_set_Z_PTR(jit, op1_addr, ref);
|
|
jit_set_Z_TYPE_INFO(jit, op1_addr, IS_REFERENCE_EX);
|
|
}
|
|
|
|
if (end_inputs) {
|
|
ir_END_list(end_inputs);
|
|
ir_MERGE_list(end_inputs);
|
|
}
|
|
|
|
return 1;
|
|
}
|
|
|
|
static int zend_jit_free(zend_jit_ctx *jit, 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) {
|
|
jit_SET_EX_OPLINE(jit, opline);
|
|
}
|
|
if (opline->opcode == ZEND_FE_FREE && (op1_info & (MAY_BE_OBJECT|MAY_BE_REF))) {
|
|
ir_ref ref, if_array, if_exists, end_inputs = IR_UNUSED;
|
|
|
|
if (op1_info & MAY_BE_ARRAY) {
|
|
if_array = jit_if_Z_TYPE(jit, op1_addr, IS_ARRAY);
|
|
ir_IF_TRUE(if_array);
|
|
ir_END_list(end_inputs);
|
|
ir_IF_FALSE(if_array);
|
|
}
|
|
ref = ir_LOAD_U32(ir_ADD_OFFSET(jit_FP(jit), opline->op1.var + offsetof(zval, u2.fe_iter_idx)));
|
|
if_exists = ir_IF(ir_EQ(ref, ir_CONST_U32(-1)));
|
|
ir_IF_TRUE(if_exists);
|
|
ir_END_list(end_inputs);
|
|
ir_IF_FALSE(if_exists);
|
|
|
|
ir_CALL_1(IR_VOID, ir_CONST_FC_FUNC(zend_hash_iterator_del), ref);
|
|
|
|
ir_END_list(end_inputs);
|
|
ir_MERGE_list(end_inputs);
|
|
}
|
|
|
|
jit_ZVAL_PTR_DTOR(jit, op1_addr, op1_info, 0, opline);
|
|
|
|
if (may_throw) {
|
|
zend_jit_check_exception(jit);
|
|
}
|
|
}
|
|
|
|
return 1;
|
|
}
|
|
|
|
static int zend_jit_echo(zend_jit_ctx *jit, 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);
|
|
|
|
jit_SET_EX_OPLINE(jit, opline);
|
|
ir_CALL_2(IR_VOID, ir_CONST_FUNC(zend_write),
|
|
ir_CONST_ADDR(str), ir_CONST_ADDR(len));
|
|
|
|
zend_jit_check_exception(jit);
|
|
}
|
|
} else {
|
|
zend_jit_addr op1_addr = OP1_ADDR();
|
|
ir_ref ref;
|
|
|
|
ZEND_ASSERT((op1_info & (MAY_BE_UNDEF|MAY_BE_ANY|MAY_BE_REF)) == MAY_BE_STRING);
|
|
|
|
jit_SET_EX_OPLINE(jit, opline);
|
|
|
|
ref = jit_Z_PTR(jit, op1_addr);
|
|
ir_CALL_2(IR_VOID, ir_CONST_FUNC(zend_write),
|
|
ir_ADD_OFFSET(ref, offsetof(zend_string, val)),
|
|
ir_LOAD_A(ir_ADD_OFFSET(ref, offsetof(zend_string, len))));
|
|
|
|
if (opline->op1_type & (IS_VAR|IS_TMP_VAR)) {
|
|
jit_ZVAL_PTR_DTOR(jit, op1_addr, op1_info, 0, opline);
|
|
}
|
|
|
|
zend_jit_check_exception(jit);
|
|
}
|
|
return 1;
|
|
}
|
|
|
|
static int zend_jit_strlen(zend_jit_ctx *jit, const zend_op *opline, uint32_t op1_info, zend_jit_addr op1_addr, zend_jit_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);
|
|
|
|
jit_set_Z_LVAL(jit, res_addr, ir_CONST_LONG(len));
|
|
if (Z_MODE(res_addr) != IS_REG) {
|
|
jit_set_Z_TYPE_INFO(jit, res_addr, IS_LONG);
|
|
} else if (!zend_jit_store_var_if_necessary(jit, opline->result.var, res_addr, MAY_BE_LONG)) {
|
|
return 0;
|
|
}
|
|
} else {
|
|
ir_ref ref;
|
|
|
|
ZEND_ASSERT((op1_info & (MAY_BE_UNDEF|MAY_BE_ANY|MAY_BE_REF)) == MAY_BE_STRING);
|
|
|
|
ref = jit_Z_PTR(jit, op1_addr);
|
|
ref = ir_LOAD_L(ir_ADD_OFFSET(ref, offsetof(zend_string, len)));
|
|
jit_set_Z_LVAL(jit, res_addr, ref);
|
|
|
|
if (Z_MODE(res_addr) == IS_REG) {
|
|
if (!zend_jit_store_var_if_necessary(jit, opline->result.var, res_addr, MAY_BE_LONG)) {
|
|
return 0;
|
|
}
|
|
} else {
|
|
jit_set_Z_TYPE_INFO(jit, res_addr, IS_LONG);
|
|
}
|
|
jit_FREE_OP(jit, opline->op1_type, opline->op1, op1_info, opline);
|
|
}
|
|
return 1;
|
|
}
|
|
|
|
static int zend_jit_count(zend_jit_ctx *jit, const zend_op *opline, uint32_t op1_info, zend_jit_addr op1_addr, zend_jit_addr res_addr, int may_throw)
|
|
{
|
|
if (opline->op1_type == IS_CONST) {
|
|
zval *zv;
|
|
zend_long count;
|
|
|
|
zv = RT_CONSTANT(opline, opline->op1);
|
|
ZEND_ASSERT(Z_TYPE_P(zv) == IS_ARRAY);
|
|
count = zend_hash_num_elements(Z_ARRVAL_P(zv));
|
|
|
|
jit_set_Z_LVAL(jit, res_addr, ir_CONST_LONG(count));
|
|
if (Z_MODE(res_addr) != IS_REG) {
|
|
jit_set_Z_TYPE_INFO(jit, res_addr, IS_LONG);
|
|
} else if (!zend_jit_store_var_if_necessary(jit, opline->result.var, res_addr, MAY_BE_LONG)) {
|
|
return 0;
|
|
}
|
|
} else {
|
|
ir_ref ref;
|
|
|
|
ZEND_ASSERT((op1_info & (MAY_BE_UNDEF|MAY_BE_ANY|MAY_BE_REF)) == MAY_BE_ARRAY);
|
|
// Note: See the implementation of ZEND_COUNT in Zend/zend_vm_def.h - arrays do not contain IS_UNDEF starting in php 8.1+.
|
|
|
|
ref = jit_Z_PTR(jit, op1_addr);
|
|
if (sizeof(void*) == 8) {
|
|
ref = ir_LOAD_U32(ir_ADD_OFFSET(ref, offsetof(HashTable, nNumOfElements)));
|
|
ref = ir_ZEXT_L(ref);
|
|
} else {
|
|
ref = ir_LOAD_L(ir_ADD_OFFSET(ref, offsetof(HashTable, nNumOfElements)));
|
|
}
|
|
jit_set_Z_LVAL(jit, res_addr, ref);
|
|
|
|
if (Z_MODE(res_addr) == IS_REG) {
|
|
if (!zend_jit_store_var_if_necessary(jit, opline->result.var, res_addr, MAY_BE_LONG)) {
|
|
return 0;
|
|
}
|
|
} else {
|
|
jit_set_Z_TYPE_INFO(jit, res_addr, IS_LONG);
|
|
}
|
|
jit_FREE_OP(jit, opline->op1_type, opline->op1, op1_info, opline);
|
|
}
|
|
|
|
if (may_throw) {
|
|
zend_jit_check_exception(jit);
|
|
}
|
|
return 1;
|
|
}
|
|
|
|
static int zend_jit_in_array(zend_jit_ctx *jit, const zend_op *opline, uint32_t op1_info, zend_jit_addr op1_addr, uint8_t 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);
|
|
ir_ref ref;
|
|
|
|
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);
|
|
|
|
// JIT: result = zend_hash_find_ex(ht, Z_STR_P(op1), OP1_TYPE == IS_CONST);
|
|
if (opline->op1_type != IS_CONST) {
|
|
ref = ir_CALL_2(IR_ADDR, ir_CONST_FC_FUNC(zend_hash_find),
|
|
ir_CONST_ADDR(ht),
|
|
jit_Z_PTR(jit, op1_addr));
|
|
} else {
|
|
zend_string *str = Z_STR_P(RT_CONSTANT(opline, opline->op1));
|
|
|
|
ref = ir_CALL_2(IR_ADDR, ir_CONST_FC_FUNC(zend_hash_find_known_hash),
|
|
ir_CONST_ADDR(ht), ir_CONST_ADDR(str));
|
|
}
|
|
|
|
if (exit_addr) {
|
|
if (smart_branch_opcode == ZEND_JMPZ) {
|
|
ir_GUARD(ref, ir_CONST_ADDR(exit_addr));
|
|
} else {
|
|
ir_GUARD_NOT(ref, ir_CONST_ADDR(exit_addr));
|
|
}
|
|
} else if (smart_branch_opcode) {
|
|
zend_basic_block *bb;
|
|
|
|
ZEND_ASSERT(jit->b >= 0);
|
|
bb = &jit->ssa->cfg.blocks[jit->b];
|
|
ZEND_ASSERT(bb->successors_count == 2);
|
|
ref = jit_IF_ex(jit, ref,
|
|
(smart_branch_opcode == ZEND_JMPZ) ? target_label2 : target_label);
|
|
_zend_jit_add_predecessor_ref(jit, bb->successors[0], jit->b, ref);
|
|
_zend_jit_add_predecessor_ref(jit, bb->successors[1], jit->b, ref);
|
|
jit->b = -1;
|
|
} else {
|
|
jit_set_Z_TYPE_INFO_ex(jit, res_addr,
|
|
ir_ADD_U32(ir_ZEXT_U32(ir_NE(ref, IR_NULL)), ir_CONST_U32(IS_FALSE)));
|
|
}
|
|
|
|
return 1;
|
|
}
|
|
|
|
static int zend_jit_rope(zend_jit_ctx *jit, const zend_op *opline, uint32_t op2_info)
|
|
{
|
|
uint32_t offset;
|
|
|
|
offset = (opline->opcode == ZEND_ROPE_INIT) ?
|
|
opline->result.var :
|
|
opline->op1.var + opline->extended_value * sizeof(zend_string*);
|
|
|
|
if (opline->op2_type == IS_CONST) {
|
|
zval *zv = RT_CONSTANT(opline, opline->op2);
|
|
zend_string *str;
|
|
|
|
ZEND_ASSERT(Z_TYPE_P(zv) == IS_STRING);
|
|
str = Z_STR_P(zv);
|
|
|
|
ir_STORE(ir_ADD_OFFSET(jit_FP(jit), offset), ir_CONST_ADDR(str));
|
|
} else {
|
|
zend_jit_addr op2_addr = OP2_ADDR();
|
|
ir_ref ref;
|
|
|
|
ZEND_ASSERT((op2_info & (MAY_BE_UNDEF|MAY_BE_ANY|MAY_BE_REF)) == MAY_BE_STRING);
|
|
|
|
ref = jit_Z_PTR(jit, op2_addr);
|
|
ir_STORE(ir_ADD_OFFSET(jit_FP(jit), offset), ref);
|
|
if (opline->op2_type == IS_CV) {
|
|
ir_ref if_refcounted, long_path;
|
|
|
|
if_refcounted = jit_if_REFCOUNTED(jit, op2_addr);
|
|
ir_IF_TRUE(if_refcounted);
|
|
jit_GC_ADDREF(jit, ref);
|
|
long_path = ir_END();
|
|
|
|
ir_IF_FALSE(if_refcounted);
|
|
ir_MERGE_WITH(long_path);
|
|
}
|
|
}
|
|
|
|
if (opline->opcode == ZEND_ROPE_END) {
|
|
zend_jit_addr res_addr = RES_ADDR();
|
|
ir_ref ref;
|
|
|
|
ref = ir_CALL_2(IR_ADDR, ir_CONST_FC_FUNC(zend_jit_rope_end),
|
|
ir_ADD_OFFSET(jit_FP(jit), opline->op1.var),
|
|
ir_CONST_U32(opline->extended_value));
|
|
|
|
jit_set_Z_PTR(jit, res_addr, ref);
|
|
jit_set_Z_TYPE_INFO(jit, res_addr, IS_STRING_EX);
|
|
}
|
|
|
|
return 1;
|
|
}
|
|
|
|
static int zend_jit_zval_copy_deref(zend_jit_ctx *jit, zend_jit_addr res_addr, zend_jit_addr val_addr, ir_ref type)
|
|
{
|
|
ir_ref if_refcounted, if_reference, if_refcounted2, ptr, val2, ptr2, type2;
|
|
ir_refs *merge_inputs, *types, *ptrs;
|
|
#if SIZEOF_ZEND_LONG == 4
|
|
ir_ref val = jit_ZVAL_ADDR(jit, val_addr);
|
|
ir_refs *values; /* we need this only for zval.w2 copy */
|
|
#endif
|
|
|
|
ir_refs_init(merge_inputs, 4);
|
|
ir_refs_init(types, 4);
|
|
ir_refs_init(ptrs, 4);
|
|
#if SIZEOF_ZEND_LONG == 4
|
|
ir_refs_init(values, 4);
|
|
#endif
|
|
|
|
// JIT: ptr = Z_PTR_P(val);
|
|
ptr = jit_Z_PTR(jit, val_addr);
|
|
|
|
// JIT: if (Z_OPT_REFCOUNTED_P(val)) {
|
|
if_refcounted = ir_IF(ir_AND_U32(type, ir_CONST_U32(Z_TYPE_FLAGS_MASK)));
|
|
ir_IF_FALSE_cold(if_refcounted);
|
|
ir_refs_add(merge_inputs, ir_END());
|
|
ir_refs_add(types, type);
|
|
ir_refs_add(ptrs, ptr);
|
|
#if SIZEOF_ZEND_LONG == 4
|
|
ir_refs_add(values, val);
|
|
#endif
|
|
|
|
ir_IF_TRUE(if_refcounted);
|
|
|
|
// JIT: if (UNEXPECTED(Z_OPT_ISREF_P(val))) {
|
|
if_reference = ir_IF(ir_EQ(type, ir_CONST_U32(IS_REFERENCE_EX)));
|
|
// if_reference = ir_IF(ir_EQ(ir_TRUNC_U8(type), ir_CONST_U8(IS_REFERENCE))); // TODO: fix IR to avoid need for extra register ???
|
|
ir_IF_TRUE(if_reference);
|
|
|
|
// JIT: val = Z_REFVAL_P(val);
|
|
val2 = ir_ADD_OFFSET(ptr, offsetof(zend_reference, val));
|
|
type2 = jit_Z_TYPE_INFO_ref(jit, val2);
|
|
ptr2 = jit_Z_PTR_ref(jit, val2);
|
|
|
|
// JIT: if (Z_OPT_REFCOUNTED_P(val)) {
|
|
if_refcounted2 = ir_IF(ir_AND_U32(type2, ir_CONST_U32(Z_TYPE_FLAGS_MASK)));
|
|
ir_IF_FALSE_cold(if_refcounted2);
|
|
ir_refs_add(merge_inputs, ir_END());
|
|
ir_refs_add(types, type2);
|
|
ir_refs_add(ptrs, ptr2);
|
|
#if SIZEOF_ZEND_LONG == 4
|
|
ir_refs_add(values, val2);
|
|
#endif
|
|
|
|
ir_IF_TRUE(if_refcounted2);
|
|
ir_MERGE_WITH_EMPTY_FALSE(if_reference);
|
|
type = ir_PHI_2(IR_U32, type2, type);
|
|
ptr = ir_PHI_2(IR_ADDR, ptr2, ptr);
|
|
#if SIZEOF_ZEND_LONG == 4
|
|
val = ir_PHI_2(IR_ADDR, val2, val);
|
|
#endif
|
|
|
|
// JIT: Z_ADDREF_P(val);
|
|
jit_GC_ADDREF(jit, ptr);
|
|
ir_refs_add(merge_inputs, ir_END());
|
|
ir_refs_add(types, type);
|
|
ir_refs_add(ptrs, ptr);
|
|
#if SIZEOF_ZEND_LONG == 4
|
|
ir_refs_add(values, val);
|
|
#endif
|
|
|
|
ir_MERGE_N(merge_inputs->count, merge_inputs->refs);
|
|
type = ir_PHI_N(IR_U32, types->count, types->refs);
|
|
ptr = ir_PHI_N(IR_ADDR, ptrs->count, ptrs->refs);
|
|
#if SIZEOF_ZEND_LONG == 4
|
|
val = ir_PHI_N(IR_ADDR, values->count, values->refs);
|
|
val_addr = ZEND_ADDR_REF_ZVAL(val);
|
|
#endif
|
|
|
|
// JIT: Z_PTR_P(res) = ptr;
|
|
jit_set_Z_PTR(jit, res_addr, ptr);
|
|
#if SIZEOF_ZEND_LONG == 4
|
|
jit_set_Z_W2(jit, res_addr, jit_Z_W2(jit, val_addr));
|
|
#endif
|
|
jit_set_Z_TYPE_INFO_ex(jit, res_addr, type);
|
|
|
|
return 1;
|
|
}
|
|
|
|
static int zend_jit_fetch_dimension_address_inner(zend_jit_ctx *jit,
|
|
const zend_op *opline,
|
|
uint32_t type,
|
|
uint32_t op1_info,
|
|
uint32_t op2_info,
|
|
zend_jit_addr op2_addr,
|
|
zend_ssa_range *op2_range,
|
|
uint8_t dim_type,
|
|
const void *found_exit_addr,
|
|
const void *not_found_exit_addr,
|
|
const void *exit_addr,
|
|
bool result_type_guard,
|
|
ir_ref ht_ref,
|
|
ir_refs *found_inputs,
|
|
ir_refs *found_vals,
|
|
ir_ref *end_inputs,
|
|
ir_ref *not_found_inputs)
|
|
{
|
|
zend_jit_addr res_addr = ZEND_ADDR_MEM_ZVAL(ZREG_FP, opline->result.var);
|
|
ir_ref ref = IR_UNUSED, cond, if_found;
|
|
ir_ref if_type = IS_UNUSED;
|
|
ir_refs *test_zval_inputs, *test_zval_values;
|
|
|
|
ir_refs_init(test_zval_inputs, 4);
|
|
ir_refs_init(test_zval_values, 4);
|
|
|
|
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) {
|
|
bool op2_loaded = 0;
|
|
bool packed_loaded = 0;
|
|
bool bad_packed_key = 0;
|
|
ir_ref if_packed = IS_UNDEF;
|
|
ir_ref h = IR_UNUSED;
|
|
ir_ref idx_not_found_inputs = IR_UNUSED;
|
|
|
|
if (op2_info & ((MAY_BE_ANY|MAY_BE_UNDEF) - MAY_BE_LONG)) {
|
|
// JIT: if (EXPECTED(Z_TYPE_P(dim) == IS_LONG))
|
|
if_type = jit_if_Z_TYPE(jit, op2_addr, IS_LONG);
|
|
ir_IF_TRUE(if_type);
|
|
}
|
|
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;
|
|
}
|
|
cond = ir_AND_U32(
|
|
ir_LOAD_U32(ir_ADD_OFFSET(ht_ref, offsetof(zend_array, u.flags))),
|
|
ir_CONST_U32(HASH_FLAG_PACKED));
|
|
if (op1_info & MAY_BE_ARRAY_PACKED) {
|
|
ir_GUARD(cond, ir_CONST_ADDR(exit_addr));
|
|
} else {
|
|
ir_GUARD_NOT(cond, ir_CONST_ADDR(exit_addr));
|
|
}
|
|
}
|
|
if (type == BP_VAR_W) {
|
|
// JIT: hval = Z_LVAL_P(dim);
|
|
h = jit_Z_LVAL(jit, 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;
|
|
}
|
|
h = ir_CONST_LONG(val);
|
|
} else {
|
|
if (!op2_loaded) {
|
|
// JIT: hval = Z_LVAL_P(dim);
|
|
h = jit_Z_LVAL(jit, op2_addr);
|
|
op2_loaded = 1;
|
|
}
|
|
packed_loaded = 1;
|
|
}
|
|
|
|
if (dim_type == IS_UNDEF && type == BP_VAR_W && packed_loaded) {
|
|
/* don't generate "fast" code for packed array */
|
|
packed_loaded = 0;
|
|
}
|
|
|
|
if (packed_loaded) {
|
|
// JIT: ZEND_HASH_INDEX_FIND(ht, hval, retval, num_undef);
|
|
if (op1_info & MAY_BE_ARRAY_NUMERIC_HASH) {
|
|
if_packed = ir_IF(
|
|
ir_AND_U32(
|
|
ir_LOAD_U32(ir_ADD_OFFSET(ht_ref, offsetof(zend_array, u.flags))),
|
|
ir_CONST_U32(HASH_FLAG_PACKED)));
|
|
ir_IF_TRUE(if_packed);
|
|
}
|
|
// JIT: if (EXPECTED((zend_ulong)(_h) < (zend_ulong)(_ht)->nNumUsed))
|
|
ref = ir_LOAD_U32(ir_ADD_OFFSET(ht_ref, offsetof(zend_array, nNumUsed)));
|
|
#if SIZEOF_ZEND_LONG == 8
|
|
if ((Z_MODE(op2_addr) == IS_CONST_ZVAL && val >= 0 && val <= UINT32_MAX)
|
|
|| (op2_range && op2_range->min >= 0 && op2_range->max <= UINT32_MAX)) {
|
|
/* comapre only the lower 32-bits to allow load fusion on x86_64 */
|
|
cond = ir_ULT(ir_TRUNC_U32(h), ref);
|
|
} else {
|
|
cond = ir_ULT(h, ir_ZEXT_L(ref));
|
|
}
|
|
#else
|
|
cond = ir_ULT(h, ref);
|
|
#endif
|
|
if (type == BP_JIT_IS) {
|
|
if (not_found_exit_addr) {
|
|
ir_GUARD(cond, ir_CONST_ADDR(not_found_exit_addr));
|
|
} else {
|
|
ir_ref if_fit = ir_IF(cond);
|
|
ir_IF_FALSE(if_fit);
|
|
ir_END_list(*end_inputs);
|
|
ir_IF_TRUE(if_fit);
|
|
}
|
|
} else if (JIT_G(trigger) == ZEND_JIT_ON_HOT_TRACE && type == BP_VAR_R) {
|
|
ir_GUARD(cond, ir_CONST_ADDR(exit_addr));
|
|
} else if (type == BP_VAR_IS && not_found_exit_addr) {
|
|
ir_GUARD(cond, ir_CONST_ADDR(not_found_exit_addr));
|
|
} else if (type == BP_VAR_RW && not_found_exit_addr) {
|
|
ir_GUARD(cond, ir_CONST_ADDR(not_found_exit_addr));
|
|
} else if (type == BP_VAR_IS && result_type_guard) {
|
|
ir_ref if_fit = ir_IF(cond);
|
|
ir_IF_FALSE(if_fit);
|
|
ir_END_list(*not_found_inputs);
|
|
ir_IF_TRUE(if_fit);
|
|
} else {
|
|
ir_ref if_fit = ir_IF(cond);
|
|
ir_IF_FALSE(if_fit);
|
|
ir_END_list(idx_not_found_inputs);
|
|
ir_IF_TRUE(if_fit);
|
|
}
|
|
// JIT: _ret = &_ht->arPacked[h];
|
|
ref = ir_MUL_L(h, ir_CONST_LONG(sizeof(zval)));
|
|
ref = ir_BITCAST_A(ref);
|
|
ref = ir_ADD_A(ir_LOAD_A(ir_ADD_OFFSET(ht_ref, offsetof(zend_array, arPacked))), ref);
|
|
if (type == BP_JIT_IS) {
|
|
ir_refs_add(test_zval_values, ref);
|
|
ir_refs_add(test_zval_inputs, ir_END());
|
|
}
|
|
}
|
|
}
|
|
switch (type) {
|
|
case BP_JIT_IS:
|
|
if (op1_info & MAY_BE_ARRAY_NUMERIC_HASH) {
|
|
if (if_packed) {
|
|
ir_IF_FALSE(if_packed);
|
|
if_packed = IR_UNUSED;
|
|
}
|
|
if (!op2_loaded) {
|
|
// JIT: hval = Z_LVAL_P(dim);
|
|
h = jit_Z_LVAL(jit, op2_addr);
|
|
op2_loaded = 1;
|
|
}
|
|
if (packed_loaded) {
|
|
ref = ir_CALL_2(IR_ADDR, ir_CONST_FC_FUNC(_zend_hash_index_find), ht_ref, h);
|
|
} else {
|
|
ref = ir_CALL_2(IR_ADDR, ir_CONST_FC_FUNC(zend_hash_index_find), ht_ref, h);
|
|
}
|
|
if (not_found_exit_addr) {
|
|
ir_GUARD(ref, ir_CONST_ADDR(not_found_exit_addr));
|
|
} else {
|
|
if_found = ir_IF(ref);
|
|
ir_IF_FALSE(if_found);
|
|
ir_END_list(*end_inputs);
|
|
ir_IF_TRUE(if_found);
|
|
}
|
|
ir_refs_add(test_zval_values, ref);
|
|
ir_refs_add(test_zval_inputs, ir_END());
|
|
} else if (!not_found_exit_addr && !packed_loaded) {
|
|
ir_END_list(*end_inputs);
|
|
}
|
|
break;
|
|
case BP_VAR_R:
|
|
case BP_VAR_IS:
|
|
case BP_VAR_UNSET:
|
|
if (packed_loaded) {
|
|
ir_ref type_ref = jit_Z_TYPE_ref(jit, ref);
|
|
|
|
if (result_type_guard) {
|
|
/* perform IS_UNDEF check only after result type guard (during deoptimization) */
|
|
} else if (JIT_G(trigger) == ZEND_JIT_ON_HOT_TRACE && type == BP_VAR_R) {
|
|
ir_GUARD(type_ref, ir_CONST_ADDR(exit_addr));
|
|
} else if (type == BP_VAR_IS && not_found_exit_addr) {
|
|
ir_GUARD(type_ref, ir_CONST_ADDR(not_found_exit_addr));
|
|
} else {
|
|
ir_ref if_def = ir_IF(type_ref);
|
|
ir_IF_FALSE(if_def);
|
|
ir_END_list(idx_not_found_inputs);
|
|
ir_IF_TRUE(if_def);
|
|
}
|
|
ir_refs_add(found_inputs, ir_END());
|
|
ir_refs_add(found_vals, ref);
|
|
}
|
|
if (op1_info & MAY_BE_ARRAY_NUMERIC_HASH) {
|
|
if (if_packed) {
|
|
ir_IF_FALSE(if_packed);
|
|
if_packed = IR_UNUSED;
|
|
}
|
|
if (!op2_loaded) {
|
|
// JIT: hval = Z_LVAL_P(dim);
|
|
h = jit_Z_LVAL(jit, op2_addr);
|
|
op2_loaded = 1;
|
|
}
|
|
if (packed_loaded) {
|
|
ref = ir_CALL_2(IR_ADDR, ir_CONST_FC_FUNC(_zend_hash_index_find), ht_ref, h);
|
|
} else {
|
|
ref = ir_CALL_2(IR_ADDR, ir_CONST_FC_FUNC(zend_hash_index_find), ht_ref, h);
|
|
}
|
|
if (JIT_G(trigger) == ZEND_JIT_ON_HOT_TRACE && type == BP_VAR_R) {
|
|
ir_GUARD(ref, ir_CONST_ADDR(exit_addr));
|
|
} else if (type == BP_VAR_IS && not_found_exit_addr) {
|
|
ir_GUARD(ref, ir_CONST_ADDR(not_found_exit_addr));
|
|
} else if (type == BP_VAR_IS && result_type_guard) {
|
|
if_found = ir_IF(ref);
|
|
ir_IF_FALSE(if_found);
|
|
ir_END_list(*not_found_inputs);
|
|
ir_IF_TRUE(if_found);
|
|
} else {
|
|
if_found = ir_IF(ref);
|
|
ir_IF_FALSE(if_found);
|
|
ir_END_list(idx_not_found_inputs);
|
|
ir_IF_TRUE(if_found);
|
|
}
|
|
ir_refs_add(found_inputs, ir_END());
|
|
ir_refs_add(found_vals, ref);
|
|
} else if (!packed_loaded) {
|
|
if (JIT_G(trigger) == ZEND_JIT_ON_HOT_TRACE && type == BP_VAR_R) {
|
|
jit_SIDE_EXIT(jit, ir_CONST_ADDR(exit_addr));
|
|
} else if (type == BP_VAR_IS && not_found_exit_addr) {
|
|
jit_SIDE_EXIT(jit, ir_CONST_ADDR(not_found_exit_addr));
|
|
} else if (type == BP_VAR_IS && result_type_guard) {
|
|
ir_END_list(*not_found_inputs);
|
|
} else {
|
|
ir_END_list(idx_not_found_inputs);
|
|
}
|
|
}
|
|
|
|
if (idx_not_found_inputs) {
|
|
ir_MERGE_list(idx_not_found_inputs);
|
|
switch (type) {
|
|
case BP_VAR_R:
|
|
ZEND_ASSERT(JIT_G(trigger) != ZEND_JIT_ON_HOT_TRACE);
|
|
// JIT: zend_error(E_WARNING,"Undefined array key " ZEND_LONG_FMT, hval);
|
|
// JIT: retval = &EG(uninitialized_zval);
|
|
jit_SET_EX_OPLINE(jit, opline);
|
|
if (Z_MODE(op2_addr) == IS_REG) {
|
|
if (!op2_loaded) {
|
|
// JIT: hval = Z_LVAL_P(dim);
|
|
h = jit_Z_LVAL(jit, op2_addr);
|
|
}
|
|
if (GCC_GLOBAL_REGS) {
|
|
ir_CALL_1(IR_VOID, ir_CONST_FC_FUNC(zend_jit_undefined_long_key_ex), h);
|
|
} else {
|
|
ir_CALL_2(IR_VOID, ir_CONST_FC_FUNC(zend_jit_undefined_long_key_ex), h, jit_FP(jit));
|
|
}
|
|
} else {
|
|
ir_CALL(IR_VOID, jit_STUB_FUNC_ADDR(jit, jit_stub_undefined_offset, IR_FASTCALL_FUNC));
|
|
}
|
|
ir_END_list(*end_inputs);
|
|
break;
|
|
case BP_VAR_IS:
|
|
case BP_VAR_UNSET:
|
|
if (!not_found_exit_addr) {
|
|
// JIT: retval = &EG(uninitialized_zval);
|
|
jit_set_Z_TYPE_INFO(jit, res_addr, IS_NULL);
|
|
ir_END_list(*end_inputs);
|
|
}
|
|
break;
|
|
default:
|
|
ZEND_UNREACHABLE();
|
|
}
|
|
}
|
|
break;
|
|
case BP_VAR_RW:
|
|
if (packed_loaded) {
|
|
if (not_found_exit_addr) {
|
|
ir_refs_add(found_inputs, ir_END());
|
|
ir_refs_add(found_vals, ref);
|
|
} else {
|
|
ir_ref if_def = ir_IF(jit_Z_TYPE_ref(jit, ref));
|
|
ir_IF_TRUE(if_def);
|
|
ir_refs_add(found_inputs, ir_END());
|
|
ir_refs_add(found_vals, ref);
|
|
ir_IF_FALSE_cold(if_def);
|
|
ir_END_list(idx_not_found_inputs);
|
|
}
|
|
}
|
|
if (!packed_loaded ||
|
|
!not_found_exit_addr ||
|
|
(op1_info & MAY_BE_ARRAY_NUMERIC_HASH)) {
|
|
if (if_packed) {
|
|
ir_IF_FALSE(if_packed);
|
|
if_packed = IR_UNUSED;
|
|
ir_END_list(idx_not_found_inputs);
|
|
} else if (!packed_loaded) {
|
|
ir_END_list(idx_not_found_inputs);
|
|
}
|
|
|
|
ir_MERGE_list(idx_not_found_inputs);
|
|
if (!op2_loaded) {
|
|
// JIT: hval = Z_LVAL_P(dim);
|
|
h = jit_Z_LVAL(jit, op2_addr);
|
|
}
|
|
if (packed_loaded) {
|
|
ref = ir_CALL_2(IR_ADDR, ir_CONST_FC_FUNC(zend_jit_hash_index_lookup_rw_no_packed),
|
|
ht_ref, h);
|
|
} else {
|
|
ref = ir_CALL_2(IR_ADDR, ir_CONST_FC_FUNC(zend_jit_hash_index_lookup_rw), ht_ref, h);
|
|
}
|
|
if (not_found_exit_addr) {
|
|
ir_GUARD(ref, ir_CONST_ADDR(not_found_exit_addr));
|
|
} else {
|
|
if_found = ir_IF(ref);
|
|
ir_IF_FALSE(if_found);
|
|
ir_END_list(*end_inputs);
|
|
ir_IF_TRUE(if_found);
|
|
}
|
|
ir_refs_add(found_inputs, ir_END());
|
|
ir_refs_add(found_vals, ref);
|
|
}
|
|
break;
|
|
case BP_VAR_W:
|
|
if (packed_loaded) {
|
|
ir_ref if_def = ir_IF(jit_Z_TYPE_ref(jit, ref));
|
|
ir_IF_TRUE_cold(if_def);
|
|
ir_refs_add(found_inputs, ir_END());
|
|
ir_refs_add(found_vals, ref);
|
|
ir_IF_FALSE(if_def);
|
|
ir_END_list(idx_not_found_inputs);
|
|
}
|
|
if (!(op1_info & MAY_BE_ARRAY_KEY_LONG) || (op1_info & MAY_BE_ARRAY_NUMERIC_HASH) || packed_loaded || bad_packed_key || dim_type == IS_UNDEF) {
|
|
if (if_packed) {
|
|
ir_IF_FALSE(if_packed);
|
|
if_packed = IR_UNUSED;
|
|
ir_END_list(idx_not_found_inputs);
|
|
} else if (!packed_loaded) {
|
|
ir_END_list(idx_not_found_inputs);
|
|
}
|
|
ir_MERGE_list(idx_not_found_inputs);
|
|
if (!op2_loaded) {
|
|
// JIT: hval = Z_LVAL_P(dim);
|
|
h = jit_Z_LVAL(jit, op2_addr);
|
|
}
|
|
ref = ir_CALL_2(IR_ADDR, ir_CONST_FC_FUNC(zend_hash_index_lookup), ht_ref, h);
|
|
ir_refs_add(found_inputs, ir_END());
|
|
ir_refs_add(found_vals, ref);
|
|
}
|
|
break;
|
|
default:
|
|
ZEND_UNREACHABLE();
|
|
}
|
|
}
|
|
|
|
if (op2_info & MAY_BE_STRING) {
|
|
ir_ref key;
|
|
|
|
if (if_type) {
|
|
ir_IF_FALSE(if_type);
|
|
if_type = IS_UNUSED;
|
|
}
|
|
|
|
if (op2_info & ((MAY_BE_ANY|MAY_BE_UNDEF) - (MAY_BE_LONG|MAY_BE_STRING))) {
|
|
// JIT: if (EXPECTED(Z_TYPE_P(dim) == IS_STRING))
|
|
if_type = jit_if_Z_TYPE(jit, op2_addr, IS_STRING);
|
|
ir_IF_TRUE(if_type);
|
|
}
|
|
|
|
// JIT: offset_key = Z_STR_P(dim);
|
|
key = jit_Z_PTR(jit, op2_addr);
|
|
|
|
// JIT: retval = zend_hash_find(ht, offset_key);
|
|
switch (type) {
|
|
case BP_JIT_IS:
|
|
if (opline->op2_type != IS_CONST) {
|
|
ir_ref if_num, end1, ref2;
|
|
|
|
if_num = ir_IF(
|
|
ir_ULE(
|
|
ir_LOAD_C(ir_ADD_OFFSET(key, offsetof(zend_string, val))),
|
|
ir_CONST_CHAR('9')));
|
|
ir_IF_TRUE_cold(if_num);
|
|
ref = ir_CALL_2(IR_ADDR, ir_CONST_FC_FUNC(zend_jit_symtable_find), ht_ref, key);
|
|
end1 = ir_END();
|
|
ir_IF_FALSE(if_num);
|
|
ref2 = ir_CALL_2(IR_ADDR, ir_CONST_FC_FUNC(zend_hash_find), ht_ref, key);
|
|
ir_MERGE_WITH(end1);
|
|
ref = ir_PHI_2(IR_ADDR, ref2, ref);
|
|
} else {
|
|
ref = ir_CALL_2(IR_ADDR, ir_CONST_FC_FUNC(zend_hash_find_known_hash), ht_ref, key);
|
|
}
|
|
if (not_found_exit_addr) {
|
|
ir_GUARD(ref, ir_CONST_ADDR(not_found_exit_addr));
|
|
} else {
|
|
if_found = ir_IF(ref);
|
|
ir_IF_FALSE(if_found);
|
|
ir_END_list(*end_inputs);
|
|
ir_IF_TRUE(if_found);
|
|
}
|
|
ir_refs_add(test_zval_values, ref);
|
|
ir_refs_add(test_zval_inputs, ir_END());
|
|
break;
|
|
case BP_VAR_R:
|
|
case BP_VAR_IS:
|
|
case BP_VAR_UNSET:
|
|
if (opline->op2_type != IS_CONST) {
|
|
ir_ref if_num, end1, ref2;
|
|
|
|
if_num = ir_IF(
|
|
ir_ULE(
|
|
ir_LOAD_C(ir_ADD_OFFSET(key, offsetof(zend_string, val))),
|
|
ir_CONST_CHAR('9')));
|
|
ir_IF_TRUE_cold(if_num);
|
|
ref = ir_CALL_2(IR_ADDR, ir_CONST_FC_FUNC(zend_jit_symtable_find), ht_ref, key);
|
|
end1 = ir_END();
|
|
ir_IF_FALSE(if_num);
|
|
ref2 = ir_CALL_2(IR_ADDR, ir_CONST_FC_FUNC(zend_hash_find), ht_ref, key);
|
|
ir_MERGE_WITH(end1);
|
|
ref = ir_PHI_2(IR_ADDR, ref2, ref);
|
|
} else {
|
|
ref = ir_CALL_2(IR_ADDR, ir_CONST_FC_FUNC(zend_hash_find_known_hash), ht_ref, key);
|
|
}
|
|
if (JIT_G(trigger) == ZEND_JIT_ON_HOT_TRACE && type == BP_VAR_R) {
|
|
ir_GUARD(ref, ir_CONST_ADDR(exit_addr));
|
|
} else if (type == BP_VAR_IS && not_found_exit_addr) {
|
|
ir_GUARD(ref, ir_CONST_ADDR(not_found_exit_addr));
|
|
} else if (type == BP_VAR_IS && result_type_guard) {
|
|
if_found = ir_IF(ref);
|
|
ir_IF_FALSE(if_found);
|
|
ir_END_list(*not_found_inputs);
|
|
ir_IF_TRUE(if_found);
|
|
} else {
|
|
if_found = ir_IF(ref);
|
|
switch (type) {
|
|
case BP_VAR_R:
|
|
ir_IF_FALSE_cold(if_found);
|
|
// JIT: zend_error(E_WARNING, "Undefined array key \"%s\"", ZSTR_VAL(offset_key));
|
|
jit_SET_EX_OPLINE(jit, opline);
|
|
ir_CALL(IR_VOID, jit_STUB_FUNC_ADDR(jit, jit_stub_undefined_key, IR_FASTCALL_FUNC));
|
|
ir_END_list(*end_inputs);
|
|
break;
|
|
case BP_VAR_IS:
|
|
case BP_VAR_UNSET:
|
|
ir_IF_FALSE(if_found);
|
|
// JIT: retval = &EG(uninitialized_zval);
|
|
jit_set_Z_TYPE_INFO(jit, res_addr, IS_NULL);
|
|
ir_END_list(*end_inputs);
|
|
break;
|
|
default:
|
|
ZEND_UNREACHABLE();
|
|
}
|
|
ir_IF_TRUE(if_found);
|
|
}
|
|
ir_refs_add(found_inputs, ir_END());
|
|
ir_refs_add(found_vals, ref);
|
|
break;
|
|
case BP_VAR_RW:
|
|
if (opline->op2_type != IS_CONST) {
|
|
ref = ir_CALL_2(IR_ADDR, ir_CONST_FC_FUNC(zend_jit_symtable_lookup_rw), ht_ref, key);
|
|
} else {
|
|
ref = ir_CALL_2(IR_ADDR, ir_CONST_FC_FUNC(zend_jit_hash_lookup_rw), ht_ref, key);
|
|
}
|
|
if (not_found_exit_addr) {
|
|
ir_GUARD(ref, ir_CONST_ADDR(not_found_exit_addr));
|
|
} else {
|
|
if_found = ir_IF(ref);
|
|
ir_IF_FALSE(if_found);
|
|
ir_END_list(*end_inputs);
|
|
ir_IF_TRUE(if_found);
|
|
}
|
|
ir_refs_add(found_inputs, ir_END());
|
|
ir_refs_add(found_vals, ref);
|
|
break;
|
|
case BP_VAR_W:
|
|
if (opline->op2_type != IS_CONST) {
|
|
ref = ir_CALL_2(IR_ADDR, ir_CONST_FC_FUNC(zend_jit_symtable_lookup_w), ht_ref, key);
|
|
} else {
|
|
ref = ir_CALL_2(IR_ADDR, ir_CONST_FC_FUNC(zend_hash_lookup), ht_ref, key);
|
|
}
|
|
ir_refs_add(found_inputs, ir_END());
|
|
ir_refs_add(found_vals, ref);
|
|
break;
|
|
default:
|
|
ZEND_UNREACHABLE();
|
|
}
|
|
}
|
|
|
|
if (op2_info & ((MAY_BE_ANY|MAY_BE_UNDEF) - (MAY_BE_LONG|MAY_BE_STRING))) {
|
|
if (if_type) {
|
|
ir_IF_FALSE_cold(if_type);
|
|
if_type = IS_UNDEF;
|
|
}
|
|
if (type != BP_VAR_RW) {
|
|
jit_SET_EX_OPLINE(jit, opline);
|
|
}
|
|
ref = jit_ZVAL_ADDR(jit, op2_addr);
|
|
switch (type) {
|
|
case BP_VAR_R:
|
|
ir_CALL_3(IR_VOID, ir_CONST_FC_FUNC(zend_jit_fetch_dim_r_helper),
|
|
ht_ref,
|
|
ref,
|
|
jit_ZVAL_ADDR(jit, res_addr));
|
|
ir_END_list(*end_inputs);
|
|
break;
|
|
case BP_JIT_IS:
|
|
ref = ir_CALL_2(IR_I32, ir_CONST_FC_FUNC(zend_jit_fetch_dim_isset_helper), ht_ref, ref);
|
|
if (not_found_exit_addr) {
|
|
ir_GUARD(ref, ir_CONST_ADDR(not_found_exit_addr));
|
|
ir_refs_add(found_inputs, ir_END());
|
|
} else if (found_exit_addr) {
|
|
ir_GUARD_NOT(ref, ir_CONST_ADDR(found_exit_addr));
|
|
ir_END_list(*end_inputs);
|
|
} else {
|
|
if_found = ir_IF(ref);
|
|
ir_IF_TRUE(if_found);
|
|
ir_refs_add(found_inputs, ir_END());
|
|
ir_IF_FALSE(if_found);
|
|
ir_END_list(*end_inputs);
|
|
}
|
|
break;
|
|
case BP_VAR_IS:
|
|
case BP_VAR_UNSET:
|
|
ir_CALL_3(IR_VOID, ir_CONST_FC_FUNC(zend_jit_fetch_dim_is_helper),
|
|
ht_ref,
|
|
ref,
|
|
jit_ZVAL_ADDR(jit, res_addr));
|
|
ir_END_list(*end_inputs);
|
|
break;
|
|
case BP_VAR_RW:
|
|
ref = ir_CALL_2(IR_ADDR, ir_CONST_FC_FUNC(zend_jit_fetch_dim_rw_helper), ht_ref, ref);
|
|
if_found = ir_IF(ref);
|
|
ir_IF_TRUE(if_found);
|
|
ir_refs_add(found_inputs, ir_END());
|
|
ir_refs_add(found_vals, ref);
|
|
ir_IF_FALSE(if_found);
|
|
ir_END_list(*end_inputs);
|
|
break;
|
|
case BP_VAR_W:
|
|
ref = ir_CALL_2(IR_ADDR, ir_CONST_FC_FUNC(zend_jit_fetch_dim_w_helper), ht_ref, ref);
|
|
if_found = ir_IF(ref);
|
|
ir_IF_TRUE(if_found);
|
|
ir_refs_add(found_inputs, ir_END());
|
|
ir_refs_add(found_vals, ref);
|
|
ir_IF_FALSE(if_found);
|
|
ir_END_list(*end_inputs);
|
|
break;
|
|
default:
|
|
ZEND_UNREACHABLE();
|
|
}
|
|
}
|
|
|
|
if (type == BP_JIT_IS
|
|
&& !(op2_info & (MAY_BE_ANY|MAY_BE_UNDEF))) {
|
|
/* dead code */
|
|
ir_END_list(*end_inputs);
|
|
} else if (type == BP_JIT_IS
|
|
&& (op1_info & MAY_BE_ARRAY)
|
|
&& (op2_info & (MAY_BE_LONG|MAY_BE_STRING))
|
|
&& test_zval_inputs->count) {
|
|
|
|
ir_MERGE_N(test_zval_inputs->count, test_zval_inputs->refs);
|
|
ref = ir_PHI_N(IR_ADDR, test_zval_values->count, test_zval_values->refs);
|
|
|
|
if (op1_info & MAY_BE_ARRAY_OF_REF) {
|
|
ref = jit_ZVAL_DEREF_ref(jit, ref);
|
|
}
|
|
cond = ir_GT(jit_Z_TYPE_ref(jit, ref), ir_CONST_U8(IS_NULL));
|
|
if (not_found_exit_addr) {
|
|
ir_GUARD(cond, ir_CONST_ADDR(not_found_exit_addr));
|
|
ir_refs_add(found_inputs, ir_END());
|
|
} else if (found_exit_addr) {
|
|
ir_GUARD_NOT(cond, ir_CONST_ADDR(found_exit_addr));
|
|
ir_END_list(*end_inputs);
|
|
} else {
|
|
ir_ref if_set = ir_IF(cond);
|
|
ir_IF_FALSE(if_set);
|
|
ir_END_list(*end_inputs);
|
|
ir_IF_TRUE(if_set);
|
|
ir_refs_add(found_inputs, ir_END());
|
|
}
|
|
}
|
|
|
|
return 1;
|
|
}
|
|
|
|
static int zend_jit_fetch_dim_read(zend_jit_ctx *jit,
|
|
const zend_op *opline,
|
|
zend_ssa *ssa,
|
|
const zend_ssa_op *ssa_op,
|
|
uint32_t op1_info,
|
|
zend_jit_addr op1_addr,
|
|
bool op1_avoid_refcounting,
|
|
uint32_t op2_info,
|
|
zend_jit_addr op2_addr,
|
|
zend_ssa_range *op2_range,
|
|
uint32_t res_info,
|
|
zend_jit_addr res_addr,
|
|
uint8_t dim_type)
|
|
{
|
|
zend_jit_addr orig_op1_addr;
|
|
const void *exit_addr = NULL;
|
|
const void *not_found_exit_addr = NULL;
|
|
bool result_type_guard = 0;
|
|
bool result_avoid_refcounting = 0;
|
|
uint32_t may_be_string = (opline->opcode != ZEND_FETCH_LIST_R) ? MAY_BE_STRING : 0;
|
|
int may_throw = 0;
|
|
ir_ref if_type = IR_UNUSED;
|
|
ir_ref end_inputs = IR_UNUSED;
|
|
ir_ref not_found_inputs = IR_UNUSED;
|
|
|
|
orig_op1_addr = OP1_ADDR();
|
|
|
|
if (opline->opcode != ZEND_FETCH_DIM_IS
|
|
&& JIT_G(trigger) == ZEND_JIT_ON_HOT_TRACE
|
|
&& !has_concrete_type(op1_info)) {
|
|
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) {
|
|
|
|
if (!(op2_info & ((MAY_BE_ANY|MAY_BE_UNDEF|MAY_BE_REF) - (MAY_BE_STRING|MAY_BE_LONG)))) {
|
|
result_type_guard = 1;
|
|
res_info &= ~MAY_BE_GUARD;
|
|
ssa->var_info[ssa_op->result_def].type &= ~MAY_BE_GUARD;
|
|
}
|
|
|
|
if ((opline->result_type & (IS_VAR|IS_TMP_VAR))
|
|
&& (opline->opcode == ZEND_FETCH_LIST_R
|
|
|| !(opline->op1_type & (IS_VAR|IS_TMP_VAR))
|
|
|| op1_avoid_refcounting)
|
|
&& (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 (opline->opcode == ZEND_FETCH_DIM_IS
|
|
&& !(res_info & MAY_BE_NULL)) {
|
|
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->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 (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);
|
|
}
|
|
|
|
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_EX(stack, EX_VAR_TO_NUM(opline->result.var), ZREG_NONE, ZREG_TYPE_ONLY);
|
|
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) {
|
|
ir_ref ref = jit_ZVAL_ADDR(jit, op1_addr);
|
|
ref = jit_ZVAL_DEREF_ref(jit, ref);
|
|
op1_addr = ZEND_ADDR_REF_ZVAL(ref);
|
|
}
|
|
|
|
if (op1_info & MAY_BE_ARRAY) {
|
|
ir_ref ht_ref, ref;
|
|
zend_jit_addr val_addr;
|
|
ir_refs *found_inputs, *found_vals;
|
|
|
|
ir_refs_init(found_inputs, 10);
|
|
ir_refs_init(found_vals, 10);
|
|
|
|
if (op1_info & ((MAY_BE_ANY|MAY_BE_UNDEF) - MAY_BE_ARRAY)) {
|
|
if (exit_addr && !(op1_info & (MAY_BE_OBJECT|may_be_string))) {
|
|
jit_guard_Z_TYPE(jit, op1_addr, IS_ARRAY, exit_addr);
|
|
} else {
|
|
if_type = jit_if_Z_TYPE(jit, op1_addr, IS_ARRAY);
|
|
ir_IF_TRUE(if_type);
|
|
}
|
|
}
|
|
|
|
ht_ref = jit_Z_PTR(jit, op1_addr);
|
|
|
|
if ((op2_info & ((MAY_BE_ANY|MAY_BE_UNDEF) - (MAY_BE_LONG|MAY_BE_STRING))) ||
|
|
(opline->opcode != ZEND_FETCH_DIM_IS && JIT_G(trigger) != ZEND_JIT_ON_HOT_TRACE)) {
|
|
may_throw = 1;
|
|
}
|
|
|
|
if (!zend_jit_fetch_dimension_address_inner(jit, opline,
|
|
(opline->opcode != ZEND_FETCH_DIM_IS) ? BP_VAR_R : BP_VAR_IS,
|
|
op1_info, op2_info, op2_addr, op2_range, dim_type, NULL, not_found_exit_addr, exit_addr,
|
|
result_type_guard, ht_ref, found_inputs, found_vals,
|
|
&end_inputs, ¬_found_inputs)) {
|
|
return 0;
|
|
}
|
|
|
|
if (found_inputs->count) {
|
|
ir_MERGE_N(found_inputs->count, found_inputs->refs);
|
|
ref = ir_PHI_N(IR_ADDR, found_vals->count, found_vals->refs);
|
|
val_addr = ZEND_ADDR_REF_ZVAL(ref);
|
|
|
|
if (result_type_guard) {
|
|
uint8_t type = concrete_type(res_info);
|
|
uint32_t flags = 0;
|
|
|
|
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;
|
|
}
|
|
|
|
val_addr = zend_jit_guard_fetch_result_type(jit, opline, val_addr, type,
|
|
(op1_info & MAY_BE_ARRAY_OF_REF) != 0, flags, op1_avoid_refcounting);
|
|
if (!val_addr) {
|
|
return 0;
|
|
}
|
|
|
|
if (not_found_inputs) {
|
|
ir_END_list(not_found_inputs);
|
|
ir_MERGE_list(not_found_inputs);
|
|
}
|
|
|
|
// ZVAL_COPY
|
|
jit_ZVAL_COPY(jit, res_addr, -1, val_addr, res_info, !result_avoid_refcounting);
|
|
if (Z_MODE(res_addr) != IS_REG) {
|
|
} else if (!zend_jit_store_var_if_necessary(jit, opline->result.var, res_addr, res_info)) {
|
|
return 0;
|
|
}
|
|
} else if (op1_info & MAY_BE_ARRAY_OF_REF) {
|
|
// ZVAL_COPY_DEREF
|
|
ir_ref type_info = jit_Z_TYPE_INFO(jit, val_addr);
|
|
if (!zend_jit_zval_copy_deref(jit, res_addr, val_addr, type_info)) {
|
|
return 0;
|
|
}
|
|
} else {
|
|
// ZVAL_COPY
|
|
jit_ZVAL_COPY(jit, res_addr, -1, val_addr, res_info, 1);
|
|
}
|
|
|
|
ir_END_list(end_inputs);
|
|
} else if (not_found_inputs) {
|
|
ir_MERGE_list(not_found_inputs);
|
|
jit_set_Z_TYPE_INFO(jit, res_addr, IS_NULL);
|
|
ir_END_list(end_inputs);
|
|
} else if (!end_inputs && jit->ctx.control) {
|
|
ir_END_list(end_inputs); /* dead code */
|
|
}
|
|
}
|
|
|
|
if (op1_info & ((MAY_BE_ANY|MAY_BE_UNDEF)-MAY_BE_ARRAY)) {
|
|
if (if_type) {
|
|
ir_IF_FALSE_cold(if_type);
|
|
if_type = IS_UNDEF;
|
|
}
|
|
|
|
if (opline->opcode != ZEND_FETCH_LIST_R && (op1_info & MAY_BE_STRING)) {
|
|
ir_ref str_ref;
|
|
|
|
may_throw = 1;
|
|
if (op1_info & ((MAY_BE_ANY|MAY_BE_UNDEF)-(MAY_BE_ARRAY|MAY_BE_STRING))) {
|
|
if (exit_addr && !(op1_info & MAY_BE_OBJECT)) {
|
|
jit_guard_Z_TYPE(jit, op1_addr, IS_STRING, exit_addr);
|
|
} else {
|
|
if_type = jit_if_Z_TYPE(jit, op1_addr, IS_STRING);
|
|
ir_IF_TRUE(if_type);
|
|
}
|
|
}
|
|
jit_SET_EX_OPLINE(jit, opline);
|
|
str_ref = jit_Z_PTR(jit, op1_addr);
|
|
if (opline->opcode != ZEND_FETCH_DIM_IS) {
|
|
ir_ref ref;
|
|
|
|
if ((op2_info & (MAY_BE_ANY|MAY_BE_UNDEF|MAY_BE_GUARD)) == MAY_BE_LONG) {
|
|
ref = ir_CALL_2(IR_ADDR, ir_CONST_FC_FUNC(zend_jit_fetch_dim_str_offset_r_helper),
|
|
str_ref, jit_Z_LVAL(jit, op2_addr));
|
|
} else {
|
|
ref = ir_CALL_2(IR_ADDR, ir_CONST_FC_FUNC(zend_jit_fetch_dim_str_r_helper),
|
|
str_ref, jit_ZVAL_ADDR(jit, op2_addr));
|
|
}
|
|
jit_set_Z_PTR(jit, res_addr, ref);
|
|
jit_set_Z_TYPE_INFO(jit, res_addr, IS_STRING);
|
|
} else {
|
|
ir_CALL_3(IR_VOID, ir_CONST_FC_FUNC(zend_jit_fetch_dim_str_is_helper),
|
|
str_ref,
|
|
jit_ZVAL_ADDR(jit, op2_addr),
|
|
jit_ZVAL_ADDR(jit, res_addr));
|
|
}
|
|
ir_END_list(end_inputs);
|
|
}
|
|
|
|
if (op1_info & MAY_BE_OBJECT) {
|
|
ir_ref arg2;
|
|
|
|
if (if_type) {
|
|
ir_IF_FALSE_cold(if_type);
|
|
if_type = IS_UNDEF;
|
|
}
|
|
|
|
may_throw = 1;
|
|
if (op1_info & ((MAY_BE_ANY|MAY_BE_UNDEF)-(MAY_BE_ARRAY|MAY_BE_OBJECT|may_be_string))) {
|
|
if (exit_addr) {
|
|
jit_guard_Z_TYPE(jit, op1_addr, IS_OBJECT, exit_addr);
|
|
} else {
|
|
if_type = jit_if_Z_TYPE(jit, op1_addr, IS_OBJECT);
|
|
ir_IF_TRUE(if_type);
|
|
}
|
|
}
|
|
|
|
jit_SET_EX_OPLINE(jit, opline);
|
|
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);
|
|
arg2 = ir_CONST_ADDR(Z_ZV(op2_addr)+1);
|
|
} else {
|
|
arg2 = jit_ZVAL_ADDR(jit, op2_addr);
|
|
}
|
|
|
|
if (opline->opcode != ZEND_FETCH_DIM_IS) {
|
|
ir_CALL_3(IR_VOID, ir_CONST_FC_FUNC(zend_jit_fetch_dim_obj_r_helper),
|
|
jit_ZVAL_ADDR(jit, op1_addr),
|
|
arg2,
|
|
jit_ZVAL_ADDR(jit, res_addr));
|
|
} else {
|
|
ir_CALL_3(IR_VOID, ir_CONST_FC_FUNC(zend_jit_fetch_dim_obj_is_helper),
|
|
jit_ZVAL_ADDR(jit, op1_addr),
|
|
arg2,
|
|
jit_ZVAL_ADDR(jit, res_addr));
|
|
}
|
|
|
|
ir_END_list(end_inputs);
|
|
}
|
|
|
|
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 (if_type) {
|
|
ir_IF_FALSE_cold(if_type);
|
|
if_type = IS_UNDEF;
|
|
}
|
|
|
|
if ((opline->opcode != ZEND_FETCH_DIM_IS && (op1_info & MAY_BE_UNDEF)) || (op2_info & MAY_BE_UNDEF)) {
|
|
jit_SET_EX_OPLINE(jit, opline);
|
|
if (opline->opcode != ZEND_FETCH_DIM_IS && (op1_info & MAY_BE_UNDEF)) {
|
|
may_throw = 1;
|
|
zend_jit_type_check_undef(jit, jit_Z_TYPE(jit, op1_addr), opline->op1.var, NULL, 0, 1, 0);
|
|
}
|
|
|
|
if (op2_info & MAY_BE_UNDEF) {
|
|
may_throw = 1;
|
|
zend_jit_type_check_undef(jit, jit_Z_TYPE(jit, op2_addr), opline->op2.var, NULL, 0, 1, 0);
|
|
}
|
|
}
|
|
|
|
if (opline->opcode != ZEND_FETCH_DIM_IS && opline->opcode != ZEND_FETCH_LIST_R) {
|
|
ir_ref ref;
|
|
|
|
may_throw = 1;
|
|
if ((op1_info & MAY_BE_UNDEF) || (op2_info & MAY_BE_UNDEF)) {
|
|
ref = jit_ZVAL_ADDR(jit, orig_op1_addr);
|
|
} else {
|
|
jit_SET_EX_OPLINE(jit, opline);
|
|
ref = jit_ZVAL_ADDR(jit, op1_addr);
|
|
}
|
|
ir_CALL_1(IR_VOID, ir_CONST_FC_FUNC(zend_jit_invalid_array_access), ref);
|
|
}
|
|
|
|
jit_set_Z_TYPE_INFO(jit, res_addr, IS_NULL);
|
|
ir_END_list(end_inputs);
|
|
}
|
|
}
|
|
|
|
if (end_inputs) {
|
|
ir_MERGE_list(end_inputs);
|
|
|
|
#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
|
|
|
|
if (opline->op2_type & (IS_TMP_VAR|IS_VAR)) {
|
|
if ((op2_info & MAY_HAVE_DTOR) && (op2_info & MAY_BE_RC1)) {
|
|
may_throw = 1;
|
|
}
|
|
jit_FREE_OP(jit, opline->op2_type, opline->op2, op2_info, opline);
|
|
}
|
|
if (opline->opcode != ZEND_FETCH_LIST_R && !op1_avoid_refcounting) {
|
|
if (opline->op1_type & (IS_TMP_VAR|IS_VAR)) {
|
|
if ((op1_info & MAY_HAVE_DTOR) && (op1_info & MAY_BE_RC1)) {
|
|
may_throw = 1;
|
|
}
|
|
jit_FREE_OP(jit, opline->op1_type, opline->op1, op1_info, opline);
|
|
}
|
|
}
|
|
|
|
if (may_throw) {
|
|
zend_jit_check_exception(jit);
|
|
}
|
|
} else if (op1_info & (MAY_BE_ANY|MAY_BE_UNDEF)) {
|
|
ir_BEGIN(IR_UNUSED); /* unreachable tail */
|
|
}
|
|
|
|
return 1;
|
|
}
|
|
|
|
static zend_jit_addr zend_jit_prepare_array_update(zend_jit_ctx *jit,
|
|
const zend_op *opline,
|
|
uint32_t op1_info,
|
|
zend_jit_addr op1_addr,
|
|
ir_ref *if_type,
|
|
ir_ref *ht_ref,
|
|
int *may_throw)
|
|
{
|
|
ir_ref ref = IR_UNUSED;
|
|
ir_ref array_reference_end = IR_UNUSED, array_reference_ref = IR_UNUSED;
|
|
ir_refs *array_inputs, *array_values;
|
|
|
|
ir_refs_init(array_inputs, 4);
|
|
ir_refs_init(array_values, 4);
|
|
|
|
ref = jit_ZVAL_ADDR(jit, op1_addr);
|
|
if (op1_info & MAY_BE_REF) {
|
|
ir_ref if_reference, if_array, end1, ref2;
|
|
|
|
*may_throw = 1;
|
|
if_reference = jit_if_Z_TYPE(jit, op1_addr, IS_REFERENCE);
|
|
ir_IF_FALSE(if_reference);
|
|
end1 = ir_END();
|
|
ir_IF_TRUE_cold(if_reference);
|
|
array_reference_ref = ir_ADD_OFFSET(jit_Z_PTR_ref(jit, ref), offsetof(zend_reference, val));
|
|
if_array = jit_if_Z_TYPE_ref(jit, array_reference_ref, ir_CONST_U8(IS_ARRAY));
|
|
ir_IF_TRUE(if_array);
|
|
array_reference_end = ir_END();
|
|
ir_IF_FALSE_cold(if_array);
|
|
if (opline->opcode != ZEND_FETCH_DIM_RW && opline->opcode != ZEND_ASSIGN_DIM_OP) {
|
|
jit_SET_EX_OPLINE(jit, opline);
|
|
}
|
|
ref2 = ir_CALL_1(IR_ADDR, ir_CONST_FC_FUNC(zend_jit_prepare_assign_dim_ref), ref);
|
|
ir_GUARD(ref2, jit_STUB_ADDR(jit, jit_stub_exception_handler_undef));
|
|
|
|
ir_MERGE_WITH(end1);
|
|
ref = ir_PHI_2(IR_ADDR, ref2, ref);
|
|
op1_addr = ZEND_ADDR_REF_ZVAL(ref);
|
|
}
|
|
|
|
if (op1_info & MAY_BE_ARRAY) {
|
|
ir_ref op1_ref = ref;
|
|
|
|
if (op1_info & ((MAY_BE_ANY|MAY_BE_UNDEF) - MAY_BE_ARRAY)) {
|
|
*if_type = jit_if_Z_TYPE(jit, op1_addr, IS_ARRAY);
|
|
ir_IF_TRUE(*if_type);
|
|
}
|
|
if (array_reference_end) {
|
|
ir_MERGE_WITH(array_reference_end);
|
|
op1_ref = ir_PHI_2(IR_ADDR, ref, array_reference_ref);
|
|
}
|
|
// JIT: SEPARATE_ARRAY()
|
|
ref = jit_Z_PTR_ref(jit, op1_ref);
|
|
if (RC_MAY_BE_N(op1_info)) {
|
|
if (RC_MAY_BE_1(op1_info)) {
|
|
ir_ref if_refcount_1 = ir_IF(ir_EQ(jit_GC_REFCOUNT(jit, ref), ir_CONST_U32(1)));
|
|
ir_IF_TRUE(if_refcount_1);
|
|
ir_refs_add(array_inputs, ir_END());
|
|
ir_refs_add(array_values, ref);
|
|
ir_IF_FALSE(if_refcount_1);
|
|
}
|
|
ref = ir_CALL_1(IR_ADDR, ir_CONST_FC_FUNC(zend_jit_zval_array_dup), op1_ref);
|
|
}
|
|
if (array_inputs->count || (op1_info & (MAY_BE_UNDEF|MAY_BE_NULL))) {
|
|
ir_refs_add(array_inputs, ir_END());
|
|
ir_refs_add(array_values, ref);
|
|
}
|
|
}
|
|
|
|
if (op1_info & (MAY_BE_UNDEF|MAY_BE_NULL)) {
|
|
if (*if_type) {
|
|
ir_IF_FALSE_cold(*if_type);
|
|
*if_type = IR_UNUSED;
|
|
}
|
|
if (op1_info & (MAY_BE_ANY-(MAY_BE_NULL|MAY_BE_ARRAY))) {
|
|
*if_type = ir_IF(ir_LE(jit_Z_TYPE(jit, op1_addr), ir_CONST_U8(IS_NULL)));
|
|
ir_IF_TRUE(*if_type);
|
|
}
|
|
if ((op1_info & MAY_BE_UNDEF)
|
|
&& (opline->opcode == ZEND_FETCH_DIM_RW || opline->opcode == ZEND_ASSIGN_DIM_OP)) {
|
|
ir_ref end1 = IR_UNUSED;
|
|
|
|
*may_throw = 1;
|
|
if (op1_info & MAY_BE_NULL) {
|
|
ir_ref if_def = ir_IF(jit_Z_TYPE(jit, op1_addr));
|
|
ir_IF_TRUE(if_def);
|
|
end1 = ir_END();
|
|
ir_IF_FALSE(if_def);
|
|
}
|
|
ir_CALL_1(IR_VOID, ir_CONST_FC_FUNC(zend_jit_undefined_op_helper), ir_CONST_U32(opline->op1.var));
|
|
if (end1) {
|
|
ir_MERGE_WITH(end1);
|
|
}
|
|
}
|
|
// JIT: ZVAL_ARR(container, zend_new_array(8));
|
|
ref = ir_CALL_1(IR_ADDR,
|
|
jit_STUB_FUNC_ADDR(jit, jit_stub_new_array, IR_FASTCALL_FUNC),
|
|
jit_ZVAL_ADDR(jit, op1_addr));
|
|
if (array_inputs->count) {
|
|
ir_refs_add(array_inputs, ir_END());
|
|
ir_refs_add(array_values, ref);
|
|
}
|
|
}
|
|
|
|
if (array_inputs->count) {
|
|
ir_MERGE_N(array_inputs->count, array_inputs->refs);
|
|
ref = ir_PHI_N(IR_ADDR, array_values->count, array_values->refs);
|
|
}
|
|
|
|
*ht_ref = ref;
|
|
return op1_addr;
|
|
}
|
|
|
|
static int zend_jit_fetch_dim(zend_jit_ctx *jit,
|
|
const zend_op *opline,
|
|
uint32_t op1_info,
|
|
zend_jit_addr op1_addr,
|
|
uint32_t op2_info,
|
|
zend_jit_addr op2_addr,
|
|
zend_ssa_range *op2_range,
|
|
zend_jit_addr res_addr,
|
|
uint8_t dim_type)
|
|
{
|
|
int may_throw = 0;
|
|
ir_ref end_inputs = IR_UNUSED;
|
|
ir_ref ref, if_type = IR_UNUSED, ht_ref;
|
|
|
|
if (opline->opcode == ZEND_FETCH_DIM_RW) {
|
|
jit_SET_EX_OPLINE(jit, opline);
|
|
}
|
|
|
|
op1_addr = zend_jit_prepare_array_update(jit, opline, op1_info, op1_addr, &if_type, &ht_ref, &may_throw);
|
|
|
|
if (op1_info & (MAY_BE_UNDEF|MAY_BE_NULL|MAY_BE_ARRAY)) {
|
|
ir_refs *found_inputs, *found_vals;
|
|
|
|
ir_refs_init(found_inputs, 8);
|
|
ir_refs_init(found_vals, 8);
|
|
|
|
if (opline->op2_type == IS_UNUSED) {
|
|
ir_ref if_ok;
|
|
|
|
may_throw = 1;
|
|
// JIT:var_ptr = zend_hash_next_index_insert(Z_ARRVAL_P(container), &EG(uninitialized_zval));
|
|
ref = ir_CALL_2(IR_ADDR, ir_CONST_FC_FUNC(zend_hash_next_index_insert),
|
|
ht_ref, jit_EG(uninitialized_zval));
|
|
|
|
// JIT: if (UNEXPECTED(!var_ptr)) {
|
|
if_ok = ir_IF(ref);
|
|
ir_IF_FALSE_cold(if_ok);
|
|
if (opline->opcode != ZEND_FETCH_DIM_RW) {
|
|
jit_SET_EX_OPLINE(jit, opline);
|
|
}
|
|
ir_CALL(IR_VOID, jit_STUB_FUNC_ADDR(jit, jit_stub_cannot_add_element, IR_FASTCALL_FUNC));
|
|
ir_END_list(end_inputs);
|
|
|
|
ir_IF_TRUE(if_ok);
|
|
jit_set_Z_PTR(jit, res_addr, ref);
|
|
jit_set_Z_TYPE_INFO(jit, res_addr, IS_INDIRECT);
|
|
|
|
ir_END_list(end_inputs);
|
|
} 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:
|
|
may_throw = 1;
|
|
type = BP_VAR_RW;
|
|
break;
|
|
case ZEND_FETCH_DIM_UNSET:
|
|
type = BP_VAR_UNSET;
|
|
break;
|
|
default:
|
|
ZEND_UNREACHABLE();
|
|
}
|
|
|
|
if (op2_info & ((MAY_BE_ANY|MAY_BE_UNDEF) - (MAY_BE_LONG|MAY_BE_STRING))) {
|
|
may_throw = 1;
|
|
}
|
|
if (!zend_jit_fetch_dimension_address_inner(jit, opline, type, op1_info,
|
|
op2_info, op2_addr, op2_range, dim_type, NULL, NULL, NULL,
|
|
0, ht_ref, found_inputs, found_vals, &end_inputs, NULL)) {
|
|
return 0;
|
|
}
|
|
|
|
if (type == BP_VAR_RW || (op2_info & ((MAY_BE_ANY|MAY_BE_UNDEF) - (MAY_BE_LONG|MAY_BE_STRING)))) {
|
|
if (end_inputs) {
|
|
ir_MERGE_list(end_inputs);
|
|
jit_set_Z_TYPE_INFO(jit, res_addr, IS_NULL);
|
|
end_inputs = ir_END();
|
|
}
|
|
} else if (!(op2_info & (MAY_BE_ANY|MAY_BE_UNDEF))) {
|
|
/* impossible dead path */
|
|
end_inputs = ir_END();
|
|
} else {
|
|
ZEND_ASSERT(end_inputs == IR_UNUSED);
|
|
}
|
|
|
|
if (found_inputs->count) {
|
|
ir_MERGE_N(found_inputs->count, found_inputs->refs);
|
|
ref = ir_PHI_N(IR_ADDR, found_vals->count, found_vals->refs);
|
|
jit_set_Z_PTR(jit, res_addr, ref);
|
|
jit_set_Z_TYPE_INFO(jit, res_addr, IS_INDIRECT);
|
|
ir_END_list(end_inputs);
|
|
}
|
|
|
|
}
|
|
}
|
|
|
|
if (op1_info & (MAY_BE_ANY-(MAY_BE_NULL|MAY_BE_ARRAY))) {
|
|
ir_ref arg2;
|
|
|
|
may_throw = 1;
|
|
|
|
if (if_type) {
|
|
ir_IF_FALSE(if_type);
|
|
if_type = IR_UNUSED;
|
|
}
|
|
|
|
if (opline->opcode != ZEND_FETCH_DIM_RW) {
|
|
jit_SET_EX_OPLINE(jit, opline);
|
|
}
|
|
|
|
if (opline->op2_type == IS_UNUSED) {
|
|
arg2 = IR_NULL;
|
|
} 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);
|
|
arg2 = ir_CONST_ADDR(Z_ZV(op2_addr) + 1);
|
|
} else {
|
|
arg2 = jit_ZVAL_ADDR(jit, op2_addr);
|
|
}
|
|
|
|
switch (opline->opcode) {
|
|
case ZEND_FETCH_DIM_W:
|
|
case ZEND_FETCH_LIST_W:
|
|
ir_CALL_3(IR_VOID, ir_CONST_FC_FUNC(zend_jit_fetch_dim_obj_w_helper),
|
|
jit_ZVAL_ADDR(jit, op1_addr),
|
|
arg2,
|
|
jit_ZVAL_ADDR(jit, res_addr));
|
|
break;
|
|
case ZEND_FETCH_DIM_RW:
|
|
ir_CALL_3(IR_VOID, ir_CONST_FC_FUNC(zend_jit_fetch_dim_obj_rw_helper),
|
|
jit_ZVAL_ADDR(jit, op1_addr),
|
|
arg2,
|
|
jit_ZVAL_ADDR(jit, res_addr));
|
|
break;
|
|
// case ZEND_FETCH_DIM_UNSET:
|
|
// | EXT_CALL zend_jit_fetch_dim_obj_unset_helper, r0
|
|
// break;
|
|
default:
|
|
ZEND_UNREACHABLE();
|
|
}
|
|
|
|
if (op1_info & (MAY_BE_UNDEF|MAY_BE_NULL|MAY_BE_ARRAY)) {
|
|
ir_END_list(end_inputs);
|
|
}
|
|
}
|
|
|
|
#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
|
|
|
|
if ((opline->op2_type & (IS_TMP_VAR|IS_VAR))
|
|
&& (op2_info & MAY_HAVE_DTOR)
|
|
&& (op2_info & MAY_BE_RC1)) {
|
|
may_throw = 1;
|
|
}
|
|
|
|
if (end_inputs) {
|
|
ir_MERGE_list(end_inputs);
|
|
}
|
|
|
|
jit_FREE_OP(jit, opline->op2_type, opline->op2, op2_info, opline);
|
|
|
|
if (may_throw) {
|
|
zend_jit_check_exception(jit);
|
|
}
|
|
|
|
return 1;
|
|
}
|
|
|
|
static int zend_jit_isset_isempty_dim(zend_jit_ctx *jit,
|
|
const zend_op *opline,
|
|
uint32_t op1_info,
|
|
zend_jit_addr op1_addr,
|
|
bool op1_avoid_refcounting,
|
|
uint32_t op2_info,
|
|
zend_jit_addr op2_addr,
|
|
zend_ssa_range *op2_range,
|
|
uint8_t dim_type,
|
|
int may_throw,
|
|
uint8_t smart_branch_opcode,
|
|
uint32_t target_label,
|
|
uint32_t target_label2,
|
|
const void *exit_addr)
|
|
{
|
|
zend_jit_addr res_addr;
|
|
ir_ref if_type = IR_UNUSED;
|
|
ir_ref false_inputs = IR_UNUSED, end_inputs = IR_UNUSED;
|
|
ir_refs *true_inputs;
|
|
|
|
ir_refs_init(true_inputs, 8);
|
|
|
|
// TODO: support for empty() ???
|
|
ZEND_ASSERT(!(opline->extended_value & ZEND_ISEMPTY));
|
|
|
|
res_addr = ZEND_ADDR_MEM_ZVAL(ZREG_FP, opline->result.var);
|
|
|
|
if (op1_info & MAY_BE_REF) {
|
|
ir_ref ref = jit_ZVAL_ADDR(jit, op1_addr);
|
|
ref = jit_ZVAL_DEREF_ref(jit, ref);
|
|
op1_addr = ZEND_ADDR_REF_ZVAL(ref);
|
|
}
|
|
|
|
if (op1_info & MAY_BE_ARRAY) {
|
|
const void *found_exit_addr = NULL;
|
|
const void *not_found_exit_addr = NULL;
|
|
ir_ref ht_ref;
|
|
|
|
if (op1_info & ((MAY_BE_ANY|MAY_BE_UNDEF) - MAY_BE_ARRAY)) {
|
|
if_type = jit_if_Z_TYPE(jit, op1_addr, IS_ARRAY);
|
|
ir_IF_TRUE(if_type);
|
|
}
|
|
|
|
ht_ref = jit_Z_PTR(jit, 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(jit, opline, BP_JIT_IS, op1_info,
|
|
op2_info, op2_addr, op2_range, dim_type, found_exit_addr, not_found_exit_addr, NULL,
|
|
0, ht_ref, true_inputs, NULL, &false_inputs, NULL)) {
|
|
return 0;
|
|
}
|
|
|
|
if (found_exit_addr) {
|
|
ir_MERGE_list(false_inputs);
|
|
return 1;
|
|
} else if (not_found_exit_addr) {
|
|
ir_MERGE_N(true_inputs->count, true_inputs->refs);
|
|
return 1;
|
|
}
|
|
}
|
|
|
|
if (op1_info & ((MAY_BE_ANY|MAY_BE_UNDEF)-MAY_BE_ARRAY)) {
|
|
if (if_type) {
|
|
ir_IF_FALSE(if_type);
|
|
if_type = IR_UNUSED;
|
|
}
|
|
|
|
if (op1_info & (MAY_BE_STRING|MAY_BE_OBJECT)) {
|
|
ir_ref ref, arg1, arg2, if_true;
|
|
|
|
jit_SET_EX_OPLINE(jit, opline);
|
|
arg1 = jit_ZVAL_ADDR(jit, 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);
|
|
arg2 = ir_CONST_ADDR(Z_ZV(op2_addr)+1);
|
|
} else {
|
|
arg2 = jit_ZVAL_ADDR(jit, op2_addr);
|
|
}
|
|
ref = ir_CALL_2(IR_I32, ir_CONST_FC_FUNC(zend_jit_isset_dim_helper), arg1, arg2);
|
|
if_true = ir_IF(ref);
|
|
ir_IF_TRUE(if_true);
|
|
ir_refs_add(true_inputs, ir_END());
|
|
ir_IF_FALSE(if_true);
|
|
ir_END_list(false_inputs);
|
|
} else {
|
|
if (op2_info & MAY_BE_UNDEF) {
|
|
ir_ref end1 = IR_UNUSED;
|
|
|
|
if (op2_info & MAY_BE_ANY) {
|
|
ir_ref if_def = ir_IF(jit_Z_TYPE(jit, op2_addr));
|
|
ir_IF_TRUE(if_def);
|
|
end1 = ir_END();
|
|
ir_IF_FALSE(if_def);
|
|
}
|
|
jit_SET_EX_OPLINE(jit, opline);
|
|
ir_CALL_1(IR_VOID, ir_CONST_FC_FUNC(zend_jit_undefined_op_helper), ir_CONST_U32(opline->op2.var));
|
|
if (end1) {
|
|
ir_MERGE_WITH(end1);
|
|
}
|
|
}
|
|
ir_END_list(false_inputs);
|
|
}
|
|
}
|
|
|
|
#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 (true_inputs->count) {
|
|
ir_MERGE_N(true_inputs->count, true_inputs->refs);
|
|
|
|
jit_FREE_OP(jit, opline->op2_type, opline->op2, op2_info, opline);
|
|
if (!op1_avoid_refcounting) {
|
|
jit_FREE_OP(jit, opline->op1_type, opline->op1, op1_info, opline);
|
|
}
|
|
if (may_throw) {
|
|
zend_jit_check_exception_undef_result(jit, opline);
|
|
}
|
|
if (!(opline->extended_value & ZEND_ISEMPTY)) {
|
|
if (exit_addr) {
|
|
if (smart_branch_opcode == ZEND_JMPNZ) {
|
|
jit_SIDE_EXIT(jit, ir_CONST_ADDR(exit_addr));
|
|
} else {
|
|
ir_END_list(end_inputs);
|
|
}
|
|
} else if (smart_branch_opcode) {
|
|
if (smart_branch_opcode == ZEND_JMPZ) {
|
|
_zend_jit_add_predecessor_ref(jit, target_label2, jit->b, ir_END());
|
|
} else if (smart_branch_opcode == ZEND_JMPNZ) {
|
|
_zend_jit_add_predecessor_ref(jit, target_label, jit->b, ir_END());
|
|
} else {
|
|
ZEND_UNREACHABLE();
|
|
}
|
|
} else {
|
|
jit_set_Z_TYPE_INFO(jit, res_addr, IS_TRUE);
|
|
ir_END_list(end_inputs);
|
|
}
|
|
} else {
|
|
ZEND_UNREACHABLE(); // TODO: support for empty()
|
|
}
|
|
}
|
|
|
|
ir_MERGE_list(false_inputs);
|
|
jit_FREE_OP(jit, opline->op2_type, opline->op2, op2_info, opline);
|
|
if (!op1_avoid_refcounting) {
|
|
jit_FREE_OP(jit, opline->op1_type, opline->op1, op1_info, opline);
|
|
}
|
|
if (may_throw) {
|
|
zend_jit_check_exception_undef_result(jit, opline);
|
|
}
|
|
if (!(opline->extended_value & ZEND_ISEMPTY)) {
|
|
if (exit_addr) {
|
|
if (smart_branch_opcode == ZEND_JMPZ) {
|
|
jit_SIDE_EXIT(jit, ir_CONST_ADDR(exit_addr));
|
|
} else {
|
|
ir_END_list(end_inputs);
|
|
}
|
|
} else if (smart_branch_opcode) {
|
|
if (smart_branch_opcode == ZEND_JMPZ) {
|
|
_zend_jit_add_predecessor_ref(jit, target_label, jit->b, ir_END());
|
|
} else if (smart_branch_opcode == ZEND_JMPNZ) {
|
|
_zend_jit_add_predecessor_ref(jit, target_label2, jit->b, ir_END());
|
|
} else {
|
|
ZEND_UNREACHABLE();
|
|
}
|
|
} else {
|
|
jit_set_Z_TYPE_INFO(jit, res_addr, IS_FALSE);
|
|
ir_END_list(end_inputs);
|
|
}
|
|
} else {
|
|
ZEND_UNREACHABLE(); // TODO: support for empty()
|
|
}
|
|
|
|
if (!exit_addr && smart_branch_opcode) {
|
|
jit->b = -1;
|
|
} else {
|
|
ir_MERGE_list(end_inputs);
|
|
}
|
|
|
|
return 1;
|
|
}
|
|
|
|
static int zend_jit_assign_dim(zend_jit_ctx *jit,
|
|
const zend_op *opline,
|
|
uint32_t op1_info,
|
|
zend_jit_addr op1_addr,
|
|
bool op1_indirect,
|
|
uint32_t op2_info,
|
|
zend_jit_addr op2_addr,
|
|
zend_ssa_range *op2_range,
|
|
uint32_t val_info,
|
|
zend_jit_addr op3_addr,
|
|
zend_jit_addr op3_def_addr,
|
|
zend_jit_addr res_addr,
|
|
uint8_t dim_type,
|
|
int may_throw)
|
|
{
|
|
ir_ref if_type = IR_UNUSED;
|
|
ir_ref end_inputs = IR_UNUSED, ht_ref;
|
|
|
|
if (op3_addr != op3_def_addr && op3_def_addr) {
|
|
if (!zend_jit_update_regs(jit, (opline+1)->op1.var, op3_addr, op3_def_addr, val_info)) {
|
|
return 0;
|
|
}
|
|
if (Z_MODE(op3_def_addr) == IS_REG && Z_MODE(op3_addr) != IS_REG) {
|
|
op3_addr = op3_def_addr;
|
|
}
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
jit_guard_not_Z_TYPE(jit, op3_addr, IS_UNDEF, exit_addr);
|
|
|
|
val_info &= ~MAY_BE_UNDEF;
|
|
}
|
|
|
|
op1_addr = zend_jit_prepare_array_update(jit, opline, op1_info, op1_addr, &if_type, &ht_ref, &may_throw);
|
|
|
|
if (op1_info & (MAY_BE_UNDEF|MAY_BE_NULL|MAY_BE_ARRAY)) {
|
|
if (opline->op2_type == IS_UNUSED) {
|
|
uint32_t var_info = MAY_BE_NULL;
|
|
ir_ref if_ok, ref;
|
|
zend_jit_addr var_addr;
|
|
|
|
// JIT: var_ptr = zend_hash_next_index_insert(Z_ARRVAL_P(container), &EG(uninitialized_zval));
|
|
ref = ir_CALL_2(IR_ADDR, ir_CONST_FC_FUNC(zend_hash_next_index_insert),
|
|
ht_ref, jit_EG(uninitialized_zval));
|
|
|
|
// JIT: if (UNEXPECTED(!var_ptr)) {
|
|
if_ok = ir_IF(ref);
|
|
ir_IF_FALSE_cold(if_ok);
|
|
|
|
// JIT: zend_throw_error(NULL, "Cannot add element to the array as the next element is already occupied");
|
|
jit_SET_EX_OPLINE(jit, opline);
|
|
ir_CALL(IR_VOID, jit_STUB_FUNC_ADDR(jit, jit_stub_cannot_add_element, IR_FASTCALL_FUNC));
|
|
|
|
ir_END_list(end_inputs);
|
|
|
|
ir_IF_TRUE(if_ok);
|
|
var_addr = ZEND_ADDR_REF_ZVAL(ref);
|
|
if (!zend_jit_simple_assign(jit, opline, var_addr, var_info, -1, (opline+1)->op1_type, op3_addr, val_info, res_addr, 0)) {
|
|
return 0;
|
|
}
|
|
} else {
|
|
uint32_t var_info = zend_array_element_type(op1_info, opline->op1_type, 0, 0);
|
|
zend_jit_addr var_addr;
|
|
ir_ref ref;
|
|
ir_refs *found_inputs, *found_values;
|
|
|
|
ir_refs_init(found_inputs, 8);
|
|
ir_refs_init(found_values, 8);
|
|
|
|
if (!zend_jit_fetch_dimension_address_inner(jit, opline, BP_VAR_W, op1_info,
|
|
op2_info, op2_addr, op2_range, dim_type, NULL, NULL, NULL,
|
|
0, ht_ref, found_inputs, found_values, &end_inputs, 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;
|
|
}
|
|
|
|
if (found_inputs->count) {
|
|
ir_MERGE_N(found_inputs->count, found_inputs->refs);
|
|
ref = ir_PHI_N(IR_ADDR, found_values->count, found_values->refs);
|
|
var_addr = ZEND_ADDR_REF_ZVAL(ref);
|
|
|
|
// JIT: value = zend_assign_to_variable(variable_ptr, value, OP_DATA_TYPE);
|
|
if (opline->op1_type == IS_VAR
|
|
&& Z_MODE(op3_addr) != IS_REG
|
|
&& opline->result_type == IS_UNUSED
|
|
&& (res_addr == 0 || Z_MODE(res_addr) != IS_REG)) {
|
|
if (!zend_jit_assign_to_variable_call(jit, 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(jit, opline, var_addr, var_addr, var_info, -1, (opline+1)->op1_type, op3_addr, val_info, res_addr, 0, 0)) {
|
|
return 0;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
ir_END_list(end_inputs);
|
|
}
|
|
|
|
if (op1_info & (MAY_BE_ANY-(MAY_BE_NULL|MAY_BE_ARRAY))) {
|
|
ir_ref arg2, arg4;
|
|
|
|
if (if_type) {
|
|
ir_IF_FALSE_cold(if_type);
|
|
if_type = IR_UNUSED;
|
|
}
|
|
|
|
jit_SET_EX_OPLINE(jit, opline);
|
|
|
|
if (opline->op2_type == IS_UNUSED) {
|
|
arg2 = IR_NULL;
|
|
} 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);
|
|
arg2 = ir_CONST_ADDR(Z_ZV(op2_addr) + 1);
|
|
} else {
|
|
arg2 = jit_ZVAL_ADDR(jit, op2_addr);
|
|
}
|
|
|
|
if (opline->result_type == IS_UNUSED) {
|
|
arg4 = IR_NULL;
|
|
} else {
|
|
arg4 = jit_ZVAL_ADDR(jit, res_addr);
|
|
}
|
|
ir_CALL_4(IR_VOID, ir_CONST_FC_FUNC(zend_jit_assign_dim_helper),
|
|
jit_ZVAL_ADDR(jit, op1_addr),
|
|
arg2,
|
|
jit_ZVAL_ADDR(jit, op3_addr),
|
|
arg4);
|
|
|
|
#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
|
|
|
|
jit_FREE_OP(jit, (opline+1)->op1_type, (opline+1)->op1, val_info, NULL);
|
|
|
|
ir_END_list(end_inputs);
|
|
}
|
|
|
|
#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
|
|
|
|
ir_MERGE_list(end_inputs);
|
|
jit_FREE_OP(jit, opline->op2_type, opline->op2, op2_info, opline);
|
|
|
|
if (!op1_indirect) {
|
|
jit_FREE_OP(jit, opline->op1_type, opline->op1, op1_info, opline);
|
|
}
|
|
|
|
if (may_throw) {
|
|
zend_jit_check_exception(jit);
|
|
}
|
|
|
|
return 1;
|
|
}
|
|
|
|
static int zend_jit_assign_dim_op(zend_jit_ctx *jit,
|
|
const zend_op *opline,
|
|
uint32_t op1_info,
|
|
uint32_t op1_def_info,
|
|
zend_jit_addr op1_addr,
|
|
bool op1_indirect,
|
|
uint32_t op2_info,
|
|
zend_jit_addr op2_addr,
|
|
zend_ssa_range *op2_range,
|
|
uint32_t op1_data_info,
|
|
zend_jit_addr op3_addr,
|
|
zend_ssa_range *op1_data_range,
|
|
uint8_t dim_type,
|
|
int may_throw)
|
|
{
|
|
zend_jit_addr var_addr = IS_UNUSED;
|
|
const void *not_found_exit_addr = NULL;
|
|
uint32_t var_info = MAY_BE_NULL;
|
|
ir_ref if_type = IS_UNUSED;
|
|
ir_ref end_inputs = IR_UNUSED, ht_ref;
|
|
bool emit_fast_path = 1;
|
|
|
|
ZEND_ASSERT(opline->result_type == IS_UNUSED);
|
|
|
|
if (may_throw) {
|
|
jit_SET_EX_OPLINE(jit, opline);
|
|
}
|
|
|
|
op1_addr = zend_jit_prepare_array_update(jit, opline, op1_info, op1_addr, &if_type, &ht_ref, &may_throw);
|
|
|
|
if (op1_info & (MAY_BE_UNDEF|MAY_BE_NULL|MAY_BE_ARRAY)) {
|
|
uint32_t var_def_info = zend_array_element_type(op1_def_info, opline->op1_type, 1, 0);
|
|
|
|
if (opline->op2_type == IS_UNUSED) {
|
|
var_info = MAY_BE_NULL;
|
|
ir_ref if_ok, ref;
|
|
|
|
// JIT: var_ptr = zend_hash_next_index_insert(Z_ARRVAL_P(container), &EG(uninitialized_zval));
|
|
ref = ir_CALL_2(IR_ADDR, ir_CONST_FC_FUNC(zend_hash_next_index_insert),
|
|
ht_ref, jit_EG(uninitialized_zval));
|
|
|
|
// JIT: if (UNEXPECTED(!var_ptr)) {
|
|
if_ok = ir_IF(ref);
|
|
ir_IF_FALSE_cold(if_ok);
|
|
|
|
// JIT: zend_throw_error(NULL, "Cannot add element to the array as the next element is already occupied");
|
|
ir_CALL(IR_VOID, jit_STUB_FUNC_ADDR(jit, jit_stub_cannot_add_element, IR_FASTCALL_FUNC));
|
|
|
|
ir_END_list(end_inputs);
|
|
|
|
ir_IF_TRUE(if_ok);
|
|
var_addr = ZEND_ADDR_REF_ZVAL(ref);
|
|
} else {
|
|
ir_ref ref;
|
|
ir_refs *found_inputs, *found_values;
|
|
|
|
ir_refs_init(found_inputs, 8);
|
|
ir_refs_init(found_values, 8);
|
|
|
|
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 (dim_type != IS_UNKNOWN
|
|
&& dim_type != IS_UNDEF
|
|
&& (op1_info & (MAY_BE_ANY|MAY_BE_UNDEF)) == MAY_BE_ARRAY
|
|
&& (op2_info & (MAY_BE_LONG|MAY_BE_STRING))
|
|
&& !(op2_info & ((MAY_BE_ANY|MAY_BE_UNDEF) - (MAY_BE_LONG|MAY_BE_STRING)))) {
|
|
int32_t exit_point = zend_jit_trace_get_exit_point(opline, 0);
|
|
not_found_exit_addr = zend_jit_trace_get_exit_addr(exit_point);
|
|
if (!not_found_exit_addr) {
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
if (!zend_jit_fetch_dimension_address_inner(jit, opline, BP_VAR_RW, op1_info,
|
|
op2_info, op2_addr, op2_range, dim_type, NULL, not_found_exit_addr, NULL,
|
|
0, ht_ref, found_inputs, found_values, &end_inputs, NULL)) {
|
|
return 0;
|
|
}
|
|
|
|
if (found_inputs->count) {
|
|
ir_MERGE_N(found_inputs->count, found_inputs->refs);
|
|
ref = ir_PHI_N(IR_ADDR, found_values->count, found_values->refs);
|
|
var_addr = ZEND_ADDR_REF_ZVAL(ref);
|
|
|
|
if (not_found_exit_addr && dim_type != IS_REFERENCE) {
|
|
jit_guard_Z_TYPE(jit, var_addr, dim_type, not_found_exit_addr);
|
|
var_info = (1 << dim_type) | (var_info & ~(MAY_BE_ANY|MAY_BE_UNDEF|MAY_BE_REF));
|
|
}
|
|
if (var_info & MAY_BE_REF) {
|
|
binary_op_type binary_op = get_binary_op(opline->extended_value);
|
|
ir_ref if_ref, if_typed, noref_path, ref_path, ref, reference, ref2, arg2;
|
|
|
|
ref = jit_ZVAL_ADDR(jit, var_addr);
|
|
if_ref = jit_if_Z_TYPE(jit, var_addr, IS_REFERENCE);
|
|
ir_IF_FALSE(if_ref);
|
|
noref_path = ir_END();
|
|
ir_IF_TRUE(if_ref);
|
|
|
|
reference = jit_Z_PTR_ref(jit, ref);
|
|
ref2 = ir_ADD_OFFSET(reference, offsetof(zend_reference, val));
|
|
if_typed = jit_if_TYPED_REF(jit, reference);
|
|
ir_IF_FALSE(if_typed);
|
|
ref_path = ir_END();
|
|
ir_IF_TRUE_cold(if_typed);
|
|
|
|
arg2 = jit_ZVAL_ADDR(jit, op3_addr);
|
|
ir_CALL_3(IR_VOID, ir_CONST_FC_FUNC(zend_jit_assign_op_to_typed_ref),
|
|
reference, arg2, ir_CONST_FC_FUNC(binary_op));
|
|
|
|
ir_END_list(end_inputs);
|
|
|
|
ir_MERGE_2(noref_path, ref_path);
|
|
ref = ir_PHI_2(IR_ADDR, ref, ref2);
|
|
var_addr = ZEND_ADDR_REF_ZVAL(ref);
|
|
}
|
|
} else {
|
|
emit_fast_path = 0;
|
|
}
|
|
}
|
|
|
|
if (emit_fast_path) {
|
|
uint8_t val_op_type = (opline+1)->op1_type;
|
|
|
|
if (val_op_type & (IS_TMP_VAR|IS_VAR)) {
|
|
/* prevent FREE_OP in the helpers */
|
|
val_op_type = IS_CV;
|
|
}
|
|
|
|
switch (opline->extended_value) {
|
|
case ZEND_ADD:
|
|
case ZEND_SUB:
|
|
case ZEND_MUL:
|
|
case ZEND_DIV:
|
|
if (!zend_jit_math_helper(jit, opline, opline->extended_value, IS_CV, opline->op1, var_addr, var_info, val_op_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(jit, opline, opline->extended_value,
|
|
IS_CV, opline->op1, var_addr, var_info, NULL,
|
|
val_op_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(jit, opline, IS_CV, opline->op1, var_addr, var_info, val_op_type, (opline+1)->op1, op3_addr, op1_data_info, var_addr,
|
|
may_throw)) {
|
|
return 0;
|
|
}
|
|
break;
|
|
default:
|
|
ZEND_UNREACHABLE();
|
|
}
|
|
|
|
ir_END_list(end_inputs);
|
|
}
|
|
}
|
|
|
|
if (op1_info & (MAY_BE_ANY-(MAY_BE_NULL|MAY_BE_ARRAY))) {
|
|
binary_op_type binary_op;
|
|
ir_ref arg2;
|
|
|
|
if (if_type) {
|
|
ir_IF_FALSE_cold(if_type);
|
|
if_type = IS_UNUSED;
|
|
}
|
|
|
|
if (opline->op2_type == IS_UNUSED) {
|
|
arg2 = IR_NULL;
|
|
} 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);
|
|
arg2 = ir_CONST_ADDR(Z_ZV(op2_addr) + 1);
|
|
} else {
|
|
arg2 = jit_ZVAL_ADDR(jit, op2_addr);
|
|
}
|
|
binary_op = get_binary_op(opline->extended_value);
|
|
ir_CALL_4(IR_VOID, ir_CONST_FC_FUNC(zend_jit_assign_dim_op_helper),
|
|
jit_ZVAL_ADDR(jit, op1_addr),
|
|
arg2,
|
|
jit_ZVAL_ADDR(jit, op3_addr),
|
|
ir_CONST_FC_FUNC(binary_op));
|
|
ir_END_list(end_inputs);
|
|
}
|
|
|
|
if (end_inputs) {
|
|
ir_MERGE_list(end_inputs);
|
|
}
|
|
|
|
jit_FREE_OP(jit, (opline+1)->op1_type, (opline+1)->op1, op1_data_info, NULL);
|
|
jit_FREE_OP(jit, opline->op2_type, opline->op2, op2_info, NULL);
|
|
if (!op1_indirect) {
|
|
jit_FREE_OP(jit, opline->op1_type, opline->op1, op1_info, NULL);
|
|
}
|
|
if (may_throw) {
|
|
zend_jit_check_exception(jit);
|
|
}
|
|
|
|
return 1;
|
|
}
|
|
|
|
static int zend_jit_fe_reset(zend_jit_ctx *jit, const zend_op *opline, uint32_t op1_info)
|
|
{
|
|
zend_jit_addr res_addr = ZEND_ADDR_MEM_ZVAL(ZREG_FP, opline->result.var);
|
|
|
|
// JIT: ZVAL_COPY(res, value);
|
|
if (opline->op1_type == IS_CONST) {
|
|
zval *zv = RT_CONSTANT(opline, opline->op1);
|
|
|
|
jit_ZVAL_COPY_CONST(jit, res_addr, MAY_BE_ANY, MAY_BE_ANY, zv, 1);
|
|
} else {
|
|
zend_jit_addr op1_addr = ZEND_ADDR_MEM_ZVAL(ZREG_FP, opline->op1.var);
|
|
|
|
jit_ZVAL_COPY(jit, res_addr, -1, op1_addr, op1_info, opline->op1_type == IS_CV);
|
|
}
|
|
|
|
// JIT: Z_FE_POS_P(res) = 0;
|
|
ir_STORE(ir_ADD_OFFSET(jit_FP(jit), opline->result.var + offsetof(zval, u2.fe_pos)), ir_CONST_U32(0));
|
|
|
|
return 1;
|
|
}
|
|
|
|
static int zend_jit_packed_guard(zend_jit_ctx *jit, 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);
|
|
zend_jit_addr addr = ZEND_ADDR_MEM_ZVAL(ZREG_FP, var);
|
|
ir_ref ref;
|
|
|
|
if (!exit_addr) {
|
|
return 0;
|
|
}
|
|
|
|
ref = ir_AND_U32(
|
|
ir_LOAD_U32(ir_ADD_OFFSET(jit_Z_PTR(jit, addr), offsetof(zend_array, u.flags))),
|
|
ir_CONST_U32(HASH_FLAG_PACKED));
|
|
if (op_info & MAY_BE_ARRAY_PACKED) {
|
|
ir_GUARD(ref, ir_CONST_ADDR(exit_addr));
|
|
} else {
|
|
ir_GUARD_NOT(ref, ir_CONST_ADDR(exit_addr));
|
|
}
|
|
|
|
return 1;
|
|
}
|
|
|
|
static int zend_jit_fe_fetch(zend_jit_ctx *jit, const zend_op *opline, uint32_t op1_info, uint32_t op2_info, unsigned int target_label, uint8_t exit_opcode, const void *exit_addr)
|
|
{
|
|
zend_jit_addr op1_addr = ZEND_ADDR_MEM_ZVAL(ZREG_FP, opline->op1.var);
|
|
ir_ref ref, ht_ref, hash_pos_ref, packed_pos_ref, hash_p_ref = IR_UNUSED, packed_p_ref = IR_UNUSED, if_packed = IR_UNUSED;
|
|
ir_ref if_def_hash = IR_UNUSED, if_def_packed = IR_UNUSED;
|
|
ir_ref exit_inputs = IR_UNUSED;
|
|
|
|
if (!MAY_BE_HASH(op1_info) && !MAY_BE_PACKED(op1_info)) {
|
|
/* empty array */
|
|
if (exit_addr) {
|
|
if (exit_opcode == ZEND_JMP) {
|
|
jit_SIDE_EXIT(jit, ir_CONST_ADDR(exit_addr));
|
|
}
|
|
} else {
|
|
zend_basic_block *bb;
|
|
|
|
ZEND_ASSERT(jit->b >= 0);
|
|
bb = &jit->ssa->cfg.blocks[jit->b];
|
|
_zend_jit_add_predecessor_ref(jit, bb->successors[0], jit->b, ir_END());
|
|
jit->b = -1;
|
|
}
|
|
return 1;
|
|
}
|
|
|
|
// JIT: array = EX_VAR(opline->op1.var);
|
|
// JIT: fe_ht = Z_ARRVAL_P(array);
|
|
ht_ref = jit_Z_PTR(jit, op1_addr);
|
|
|
|
if (op1_info & MAY_BE_PACKED_GUARD) {
|
|
if (!zend_jit_packed_guard(jit, opline, opline->op1.var, op1_info)) {
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
// JIT: pos = Z_FE_POS_P(array);
|
|
hash_pos_ref = packed_pos_ref = ir_LOAD_U32(ir_ADD_OFFSET(jit_FP(jit), opline->op1.var + offsetof(zval, u2.fe_pos)));
|
|
|
|
if (MAY_BE_HASH(op1_info)) {
|
|
ir_ref loop_ref, pos2_ref, p2_ref;
|
|
|
|
if (MAY_BE_PACKED(op1_info)) {
|
|
ref = ir_AND_U32(
|
|
ir_LOAD_U32(ir_ADD_OFFSET(ht_ref, offsetof(zend_array, u.flags))),
|
|
ir_CONST_U32(HASH_FLAG_PACKED));
|
|
if_packed = ir_IF(ref);
|
|
ir_IF_FALSE(if_packed);
|
|
}
|
|
|
|
// JIT: p = fe_ht->arData + pos;
|
|
if (sizeof(void*) == 8) {
|
|
ref = ir_ZEXT_A(hash_pos_ref);
|
|
} else {
|
|
ref = ir_BITCAST_A(hash_pos_ref);
|
|
}
|
|
hash_p_ref = ir_ADD_A(
|
|
ir_MUL_A(ref, ir_CONST_ADDR(sizeof(Bucket))),
|
|
ir_LOAD_A(ir_ADD_OFFSET(ht_ref, offsetof(zend_array, arData))));
|
|
|
|
loop_ref = ir_LOOP_BEGIN(ir_END());
|
|
hash_pos_ref = ir_PHI_2(IR_U32, hash_pos_ref, IR_UNUSED);
|
|
hash_p_ref = ir_PHI_2(IR_ADDR, hash_p_ref, IR_UNUSED);
|
|
|
|
// JIT: if (UNEXPECTED(pos >= fe_ht->nNumUsed)) {
|
|
ref = ir_ULT(hash_pos_ref,
|
|
ir_LOAD_U32(ir_ADD_OFFSET(ht_ref, offsetof(zend_array, nNumUsed))));
|
|
|
|
// JIT: ZEND_VM_SET_RELATIVE_OPCODE(opline, opline->extended_value);
|
|
// JIT: ZEND_VM_CONTINUE();
|
|
|
|
if (exit_addr) {
|
|
if (exit_opcode == ZEND_JMP) {
|
|
ir_GUARD(ref, ir_CONST_ADDR(exit_addr));
|
|
} else {
|
|
ir_ref if_fit = ir_IF(ref);
|
|
ir_IF_FALSE(if_fit);
|
|
ir_END_list(exit_inputs);
|
|
ir_IF_TRUE(if_fit);
|
|
}
|
|
} else {
|
|
ir_ref if_fit = ir_IF(ref);
|
|
ir_IF_FALSE(if_fit);
|
|
ir_END_list(exit_inputs);
|
|
ir_IF_TRUE(if_fit);
|
|
}
|
|
|
|
// JIT: pos++;
|
|
pos2_ref = ir_ADD_U32(hash_pos_ref, ir_CONST_U32(1));
|
|
|
|
// JIT: value_type = Z_TYPE_INFO_P(value);
|
|
// JIT: if (EXPECTED(value_type != IS_UNDEF)) {
|
|
if (!exit_addr || exit_opcode == ZEND_JMP) {
|
|
if_def_hash = ir_IF(jit_Z_TYPE_ref(jit, hash_p_ref));
|
|
ir_IF_FALSE(if_def_hash);
|
|
} else {
|
|
ir_GUARD_NOT(jit_Z_TYPE_ref(jit, hash_p_ref), ir_CONST_ADDR(exit_addr));
|
|
}
|
|
|
|
// JIT: p++;
|
|
p2_ref = ir_ADD_OFFSET(hash_p_ref, sizeof(Bucket));
|
|
|
|
ir_MERGE_SET_OP(loop_ref, 2, ir_LOOP_END());
|
|
ir_PHI_SET_OP(hash_pos_ref, 2, pos2_ref);
|
|
ir_PHI_SET_OP(hash_p_ref, 2, p2_ref);
|
|
|
|
if (MAY_BE_PACKED(op1_info)) {
|
|
ir_IF_TRUE(if_packed);
|
|
}
|
|
}
|
|
if (MAY_BE_PACKED(op1_info)) {
|
|
ir_ref loop_ref, pos2_ref, p2_ref;
|
|
|
|
// JIT: p = fe_ht->arPacked + pos;
|
|
if (sizeof(void*) == 8) {
|
|
ref = ir_ZEXT_A(packed_pos_ref);
|
|
} else {
|
|
ref = ir_BITCAST_A(packed_pos_ref);
|
|
}
|
|
packed_p_ref = ir_ADD_A(
|
|
ir_MUL_A(ref, ir_CONST_ADDR(sizeof(zval))),
|
|
ir_LOAD_A(ir_ADD_OFFSET(ht_ref, offsetof(zend_array, arPacked))));
|
|
|
|
loop_ref = ir_LOOP_BEGIN(ir_END());
|
|
packed_pos_ref = ir_PHI_2(IR_U32, packed_pos_ref, IR_UNUSED);
|
|
packed_p_ref = ir_PHI_2(IR_ADDR, packed_p_ref, IR_UNUSED);
|
|
|
|
// JIT: if (UNEXPECTED(pos >= fe_ht->nNumUsed)) {
|
|
ref = ir_ULT(packed_pos_ref,
|
|
ir_LOAD_U32(ir_ADD_OFFSET(ht_ref, offsetof(zend_array, nNumUsed))));
|
|
|
|
// JIT: ZEND_VM_SET_RELATIVE_OPCODE(opline, opline->extended_value);
|
|
// JIT: ZEND_VM_CONTINUE();
|
|
if (exit_addr) {
|
|
if (exit_opcode == ZEND_JMP) {
|
|
ir_GUARD(ref, ir_CONST_ADDR(exit_addr));
|
|
} else {
|
|
ir_ref if_fit = ir_IF(ref);
|
|
ir_IF_FALSE(if_fit);
|
|
ir_END_list(exit_inputs);
|
|
ir_IF_TRUE(if_fit);
|
|
}
|
|
} else {
|
|
ir_ref if_fit = ir_IF(ref);
|
|
ir_IF_FALSE(if_fit);
|
|
ir_END_list(exit_inputs);
|
|
ir_IF_TRUE(if_fit);
|
|
}
|
|
|
|
// JIT: pos++;
|
|
pos2_ref = ir_ADD_U32(packed_pos_ref, ir_CONST_U32(1));
|
|
|
|
// JIT: value_type = Z_TYPE_INFO_P(value);
|
|
// JIT: if (EXPECTED(value_type != IS_UNDEF)) {
|
|
if (!exit_addr || exit_opcode == ZEND_JMP) {
|
|
if_def_packed = ir_IF(jit_Z_TYPE_ref(jit, packed_p_ref));
|
|
ir_IF_FALSE(if_def_packed);
|
|
} else {
|
|
ir_GUARD_NOT(jit_Z_TYPE_ref(jit, packed_p_ref), ir_CONST_ADDR(exit_addr));
|
|
}
|
|
|
|
// JIT: p++;
|
|
p2_ref = ir_ADD_OFFSET(packed_p_ref, sizeof(zval));
|
|
|
|
ir_MERGE_SET_OP(loop_ref, 2, ir_LOOP_END());
|
|
ir_PHI_SET_OP(packed_pos_ref, 2, pos2_ref);
|
|
ir_PHI_SET_OP(packed_p_ref, 2, p2_ref);
|
|
}
|
|
|
|
if (!exit_addr || exit_opcode == ZEND_JMP) {
|
|
zend_jit_addr val_addr;
|
|
zend_jit_addr var_addr = ZEND_ADDR_MEM_ZVAL(ZREG_FP, opline->op2.var);
|
|
uint32_t val_info;
|
|
ir_ref p_ref = IR_UNUSED, hash_path = IR_UNUSED;
|
|
|
|
if (RETURN_VALUE_USED(opline)) {
|
|
zend_jit_addr res_addr = RES_ADDR();
|
|
|
|
if (MAY_BE_HASH(op1_info)) {
|
|
ir_ref key_ref = IR_UNUSED, if_key = IR_UNUSED, key_path = IR_UNUSED;
|
|
|
|
ZEND_ASSERT(if_def_hash);
|
|
ir_IF_TRUE(if_def_hash);
|
|
|
|
// JIT: Z_FE_POS_P(array) = pos + 1;
|
|
ir_STORE(ir_ADD_OFFSET(jit_FP(jit), opline->op1.var + offsetof(zval, u2.fe_pos)),
|
|
ir_ADD_U32(hash_pos_ref, ir_CONST_U32(1)));
|
|
|
|
if (op1_info & MAY_BE_ARRAY_KEY_STRING) {
|
|
key_ref = ir_LOAD_A(ir_ADD_OFFSET(hash_p_ref, offsetof(Bucket, key)));
|
|
}
|
|
if ((op1_info & MAY_BE_ARRAY_KEY_LONG)
|
|
&& (op1_info & MAY_BE_ARRAY_KEY_STRING)) {
|
|
// JIT: if (!p->key) {
|
|
if_key = ir_IF(key_ref);
|
|
ir_IF_TRUE(if_key);
|
|
}
|
|
if (op1_info & MAY_BE_ARRAY_KEY_STRING) {
|
|
ir_ref if_interned, interned_path;
|
|
|
|
// JIT: ZVAL_STR_COPY(EX_VAR(opline->result.var), p->key);
|
|
jit_set_Z_PTR(jit, res_addr, key_ref);
|
|
ref = ir_AND_U32(
|
|
ir_LOAD_U32(ir_ADD_OFFSET(key_ref, offsetof(zend_refcounted, gc.u.type_info))),
|
|
ir_CONST_U32(IS_STR_INTERNED));
|
|
if_interned = ir_IF(ref);
|
|
ir_IF_TRUE(if_interned);
|
|
|
|
jit_set_Z_TYPE_INFO(jit, res_addr, IS_STRING);
|
|
|
|
interned_path = ir_END();
|
|
ir_IF_FALSE(if_interned);
|
|
|
|
jit_GC_ADDREF(jit, key_ref);
|
|
jit_set_Z_TYPE_INFO(jit, res_addr, IS_STRING_EX);
|
|
|
|
ir_MERGE_WITH(interned_path);
|
|
|
|
if (op1_info & MAY_BE_ARRAY_KEY_LONG) {
|
|
key_path = ir_END();
|
|
}
|
|
}
|
|
if (op1_info & MAY_BE_ARRAY_KEY_LONG) {
|
|
if (op1_info & MAY_BE_ARRAY_KEY_STRING) {
|
|
ir_IF_FALSE(if_key);
|
|
}
|
|
// JIT: ZVAL_LONG(EX_VAR(opline->result.var), p->h);
|
|
ref = ir_LOAD_L(ir_ADD_OFFSET(hash_p_ref, offsetof(Bucket, h)));
|
|
jit_set_Z_LVAL(jit, res_addr, ref);
|
|
jit_set_Z_TYPE_INFO(jit, res_addr, IS_LONG);
|
|
|
|
if (op1_info & MAY_BE_ARRAY_KEY_STRING) {
|
|
ir_MERGE_WITH(key_path);
|
|
}
|
|
}
|
|
if (MAY_BE_PACKED(op1_info)) {
|
|
hash_path = ir_END();
|
|
} else {
|
|
p_ref = hash_p_ref;
|
|
}
|
|
}
|
|
if (MAY_BE_PACKED(op1_info)) {
|
|
ZEND_ASSERT(if_def_packed);
|
|
ir_IF_TRUE(if_def_packed);
|
|
|
|
// JIT: Z_FE_POS_P(array) = pos + 1;
|
|
ir_STORE(ir_ADD_OFFSET(jit_FP(jit), opline->op1.var + offsetof(zval, u2.fe_pos)),
|
|
ir_ADD_U32(packed_pos_ref, ir_CONST_U32(1)));
|
|
|
|
// JIT: ZVAL_LONG(EX_VAR(opline->result.var), pos);
|
|
if (sizeof(zend_long) == 8) {
|
|
packed_pos_ref = ir_ZEXT_L(packed_pos_ref);
|
|
} else {
|
|
packed_pos_ref = ir_BITCAST_L(packed_pos_ref);
|
|
}
|
|
jit_set_Z_LVAL(jit, res_addr, packed_pos_ref);
|
|
jit_set_Z_TYPE_INFO(jit, res_addr, IS_LONG);
|
|
|
|
if (MAY_BE_HASH(op1_info)) {
|
|
ir_MERGE_WITH(hash_path);
|
|
p_ref = ir_PHI_2(IR_ADDR, packed_p_ref, hash_p_ref);
|
|
} else {
|
|
p_ref = packed_p_ref;
|
|
}
|
|
}
|
|
} else {
|
|
ir_ref pos_ref = IR_UNUSED;
|
|
|
|
if (if_def_hash && if_def_packed) {
|
|
ir_IF_TRUE(if_def_hash);
|
|
ir_MERGE_WITH_EMPTY_TRUE(if_def_packed);
|
|
pos_ref = ir_PHI_2(IR_U32, hash_pos_ref, packed_pos_ref);
|
|
p_ref = ir_PHI_2(IR_ADDR, hash_p_ref, packed_p_ref);
|
|
} else if (if_def_hash) {
|
|
ir_IF_TRUE(if_def_hash);
|
|
pos_ref = hash_pos_ref;
|
|
p_ref = hash_p_ref;
|
|
} else if (if_def_packed) {
|
|
ir_IF_TRUE(if_def_packed);
|
|
pos_ref = packed_pos_ref;
|
|
p_ref = packed_p_ref;
|
|
} else {
|
|
ZEND_UNREACHABLE();
|
|
}
|
|
|
|
// JIT: Z_FE_POS_P(array) = pos + 1;
|
|
ir_STORE(ir_ADD_OFFSET(jit_FP(jit), opline->op1.var + offsetof(zval, u2.fe_pos)),
|
|
ir_ADD_U32(pos_ref, ir_CONST_U32(1)));
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
val_addr = ZEND_ADDR_REF_ZVAL(p_ref);
|
|
if (opline->op2_type == IS_CV) {
|
|
// JIT: zend_assign_to_variable(variable_ptr, value, IS_CV, EX_USES_STRICT_TYPES());
|
|
if (!zend_jit_assign_to_variable(jit, opline, var_addr, var_addr, op2_info, -1, IS_CV, val_addr, val_info, 0, 0, 1)) {
|
|
return 0;
|
|
}
|
|
} else {
|
|
// JIT: ZVAL_COPY(res, value);
|
|
jit_ZVAL_COPY(jit, var_addr, -1, val_addr, val_info, 1);
|
|
}
|
|
|
|
if (!exit_addr) {
|
|
zend_basic_block *bb;
|
|
|
|
ZEND_ASSERT(jit->b >= 0);
|
|
bb = &jit->ssa->cfg.blocks[jit->b];
|
|
_zend_jit_add_predecessor_ref(jit, bb->successors[1], jit->b, ir_END());
|
|
ZEND_ASSERT(exit_inputs);
|
|
if (!jit->ctx.ir_base[exit_inputs].op2) {
|
|
ref = exit_inputs;
|
|
} else {
|
|
ir_MERGE_list(exit_inputs);
|
|
ref = ir_END();
|
|
}
|
|
_zend_jit_add_predecessor_ref(jit, bb->successors[0], jit->b, ref);
|
|
jit->b = -1;
|
|
}
|
|
} else {
|
|
ZEND_ASSERT(exit_inputs);
|
|
ir_MERGE_list(exit_inputs);
|
|
}
|
|
|
|
return 1;
|
|
}
|
|
|
|
static int zend_jit_load_this(zend_jit_ctx *jit, uint32_t var)
|
|
{
|
|
zend_jit_addr this_addr = ZEND_ADDR_MEM_ZVAL(ZREG_FP, offsetof(zend_execute_data, This));
|
|
zend_jit_addr var_addr = ZEND_ADDR_MEM_ZVAL(ZREG_FP, var);
|
|
ir_ref ref = jit_Z_PTR(jit, this_addr);
|
|
|
|
jit_set_Z_PTR(jit, var_addr, ref);
|
|
jit_set_Z_TYPE_INFO(jit, var_addr, IS_OBJECT_EX);
|
|
jit_GC_ADDREF(jit, ref);
|
|
|
|
return 1;
|
|
}
|
|
|
|
static int zend_jit_fetch_this(zend_jit_ctx *jit, const zend_op *opline, const zend_op_array *op_array, bool check_only)
|
|
{
|
|
if (!op_array->scope ||
|
|
(op_array->fn_flags & ZEND_ACC_STATIC) ||
|
|
((op_array->fn_flags & (ZEND_ACC_CLOSURE|ZEND_ACC_IMMUTABLE)) == ZEND_ACC_CLOSURE)) {
|
|
if (JIT_G(trigger) == ZEND_JIT_ON_HOT_TRACE) {
|
|
if (!JIT_G(current_frame) ||
|
|
!TRACE_FRAME_IS_THIS_CHECKED(JIT_G(current_frame))) {
|
|
|
|
zend_jit_addr this_addr = ZEND_ADDR_MEM_ZVAL(ZREG_FP, offsetof(zend_execute_data, This));
|
|
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;
|
|
}
|
|
|
|
jit_guard_Z_TYPE(jit, this_addr, IS_OBJECT, exit_addr);
|
|
|
|
if (JIT_G(current_frame)) {
|
|
TRACE_FRAME_SET_THIS_CHECKED(JIT_G(current_frame));
|
|
}
|
|
}
|
|
} else {
|
|
zend_jit_addr this_addr = ZEND_ADDR_MEM_ZVAL(ZREG_FP, offsetof(zend_execute_data, This));
|
|
ir_ref if_object = jit_if_Z_TYPE(jit, this_addr, IS_OBJECT);
|
|
|
|
ir_IF_FALSE_cold(if_object);
|
|
jit_SET_EX_OPLINE(jit, opline);
|
|
ir_IJMP(jit_STUB_ADDR(jit, jit_stub_invalid_this));
|
|
|
|
ir_IF_TRUE(if_object);
|
|
}
|
|
}
|
|
|
|
if (!check_only) {
|
|
if (!zend_jit_load_this(jit, opline->result.var)) {
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
return 1;
|
|
}
|
|
|
|
static int zend_jit_class_guard(zend_jit_ctx *jit, const zend_op *opline, ir_ref obj_ref, 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;
|
|
}
|
|
|
|
ir_GUARD(ir_EQ(ir_LOAD_A(ir_ADD_OFFSET(obj_ref, offsetof(zend_object, ce))), ir_CONST_ADDR(ce)),
|
|
ir_CONST_ADDR(exit_addr));
|
|
|
|
return 1;
|
|
}
|
|
|
|
static int zend_jit_fetch_obj(zend_jit_ctx *jit,
|
|
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,
|
|
bool op1_indirect,
|
|
zend_class_entry *ce,
|
|
bool ce_is_instanceof,
|
|
bool on_this,
|
|
bool delayed_fetch_this,
|
|
bool op1_avoid_refcounting,
|
|
zend_class_entry *trace_ce,
|
|
zend_jit_addr res_addr,
|
|
uint8_t prop_type,
|
|
int may_throw)
|
|
{
|
|
zval *member;
|
|
zend_property_info *prop_info;
|
|
bool may_be_dynamic = 1;
|
|
zend_jit_addr prop_addr;
|
|
uint32_t res_info = RES_INFO();
|
|
ir_ref prop_type_ref = IR_UNUSED;
|
|
ir_ref obj_ref = IR_UNUSED;
|
|
ir_ref prop_ref = IR_UNUSED;
|
|
ir_ref end_inputs = IR_UNUSED;
|
|
ir_ref slow_inputs = IR_UNUSED;
|
|
ir_ref end_values = IR_UNUSED;
|
|
|
|
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(op_array, ce, Z_STR_P(member), on_this, op_array->filename);
|
|
|
|
if (on_this) {
|
|
zend_jit_addr this_addr = ZEND_ADDR_MEM_ZVAL(ZREG_FP, offsetof(zend_execute_data, This));
|
|
obj_ref = jit_Z_PTR(jit, 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) {
|
|
op1_addr = jit_ZVAL_INDIRECT_DEREF(jit, op1_addr);
|
|
}
|
|
if (op1_info & MAY_BE_REF) {
|
|
op1_addr = jit_ZVAL_DEREF(jit, op1_addr);
|
|
}
|
|
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;
|
|
}
|
|
jit_guard_Z_TYPE(jit, op1_addr, IS_OBJECT, exit_addr);
|
|
} else {
|
|
ir_ref if_obj = jit_if_Z_TYPE(jit, op1_addr, IS_OBJECT);
|
|
|
|
ir_IF_FALSE_cold(if_obj);
|
|
if (opline->opcode != ZEND_FETCH_OBJ_IS) {
|
|
ir_ref op1_ref = IR_UNUSED;
|
|
|
|
jit_SET_EX_OPLINE(jit, opline);
|
|
if (opline->opcode != ZEND_FETCH_OBJ_W && (op1_info & MAY_BE_UNDEF)) {
|
|
zend_jit_addr orig_op1_addr = OP1_ADDR();
|
|
ir_ref fast_path = IR_UNUSED;
|
|
|
|
if (op1_info & MAY_BE_ANY) {
|
|
ir_ref if_def = ir_IF(jit_Z_TYPE(jit, op1_addr));
|
|
ir_IF_TRUE(if_def);
|
|
fast_path = ir_END();
|
|
ir_IF_FALSE_cold(if_def);
|
|
}
|
|
ir_CALL_1(IR_VOID, ir_CONST_FC_FUNC(zend_jit_undefined_op_helper),
|
|
ir_CONST_U32(opline->op1.var));
|
|
if (fast_path) {
|
|
ir_MERGE_WITH(fast_path);
|
|
}
|
|
op1_ref = jit_ZVAL_ADDR(jit, orig_op1_addr);
|
|
} else {
|
|
op1_ref = jit_ZVAL_ADDR(jit, op1_addr);
|
|
}
|
|
if (opline->opcode == ZEND_FETCH_OBJ_W) {
|
|
ir_CALL_2(IR_VOID, ir_CONST_FC_FUNC(zend_jit_invalid_property_write),
|
|
op1_ref, ir_CONST_ADDR(Z_STRVAL_P(member)));
|
|
jit_set_Z_TYPE_INFO(jit, res_addr, _IS_ERROR);
|
|
} else {
|
|
ir_CALL_2(IR_VOID, ir_CONST_FC_FUNC(zend_jit_invalid_property_read),
|
|
op1_ref, ir_CONST_ADDR(Z_STRVAL_P(member)));
|
|
jit_set_Z_TYPE_INFO(jit, res_addr, IS_NULL);
|
|
}
|
|
} else {
|
|
jit_set_Z_TYPE_INFO(jit, res_addr, IS_NULL);
|
|
}
|
|
ir_END_list(end_inputs);
|
|
|
|
ir_IF_TRUE(if_obj);
|
|
}
|
|
}
|
|
obj_ref = jit_Z_PTR(jit, op1_addr);
|
|
}
|
|
|
|
ZEND_ASSERT(obj_ref);
|
|
if (!prop_info && trace_ce && (trace_ce->ce_flags & ZEND_ACC_IMMUTABLE)) {
|
|
prop_info = zend_get_known_property_info(op_array, trace_ce, Z_STR_P(member), on_this, op_array->filename);
|
|
if (prop_info) {
|
|
ce = trace_ce;
|
|
ce_is_instanceof = 0;
|
|
if (!(op1_info & MAY_BE_CLASS_GUARD)) {
|
|
if (on_this && JIT_G(current_frame)
|
|
&& TRACE_FRAME_IS_THIS_CLASS_CHECKED(JIT_G(current_frame))) {
|
|
ZEND_ASSERT(JIT_G(current_frame)->ce == ce);
|
|
} else if (zend_jit_class_guard(jit, opline, obj_ref, ce)) {
|
|
if (on_this && JIT_G(current_frame)) {
|
|
JIT_G(current_frame)->ce = ce;
|
|
TRACE_FRAME_SET_THIS_CLASS_CHECKED(JIT_G(current_frame));
|
|
}
|
|
} else {
|
|
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) {
|
|
ir_ref run_time_cache = ir_LOAD_A(jit_EX(run_time_cache));
|
|
ir_ref ref = ir_LOAD_A(ir_ADD_OFFSET(run_time_cache, opline->extended_value & ~ZEND_FETCH_OBJ_FLAGS));
|
|
ir_ref if_same = ir_IF(ir_EQ(ref,
|
|
ir_LOAD_A(ir_ADD_OFFSET(obj_ref, offsetof(zend_object, ce)))));
|
|
|
|
ir_IF_FALSE_cold(if_same);
|
|
ir_END_list(slow_inputs);
|
|
|
|
ir_IF_TRUE(if_same);
|
|
ir_ref offset_ref = ir_LOAD_A(
|
|
ir_ADD_OFFSET(run_time_cache, (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);
|
|
if (may_be_dynamic) {
|
|
ir_ref if_dynamic = ir_IF(ir_LT(offset_ref, ir_CONST_ADDR(ZEND_FIRST_PROPERTY_OFFSET)));
|
|
if (opline->opcode == ZEND_FETCH_OBJ_W) {
|
|
ir_IF_TRUE_cold(if_dynamic);
|
|
ir_END_list(slow_inputs);
|
|
} else {
|
|
ir_IF_TRUE_cold(if_dynamic);
|
|
jit_SET_EX_OPLINE(jit, opline);
|
|
|
|
if (opline->opcode != ZEND_FETCH_OBJ_IS) {
|
|
if (Z_MODE(res_addr) == IS_REG) {
|
|
ir_ref val_addr = ir_CALL_2(IR_ADDR, ir_CONST_FC_FUNC(zend_jit_fetch_obj_r_dynamic_ex),
|
|
obj_ref, offset_ref);
|
|
ir_END_PHI_list(end_values, val_addr);
|
|
} else {
|
|
ir_CALL_2(IR_VOID, ir_CONST_FC_FUNC(zend_jit_fetch_obj_r_dynamic),
|
|
obj_ref, offset_ref);
|
|
ir_END_list(end_inputs);
|
|
}
|
|
} else {
|
|
if (Z_MODE(res_addr) == IS_REG) {
|
|
ir_ref val_addr = ir_CALL_2(IR_ADDR, ir_CONST_FC_FUNC(zend_jit_fetch_obj_is_dynamic_ex),
|
|
obj_ref, offset_ref);
|
|
ir_END_PHI_list(end_values, val_addr);
|
|
} else {
|
|
ir_CALL_2(IR_VOID, ir_CONST_FC_FUNC(zend_jit_fetch_obj_is_dynamic),
|
|
obj_ref, offset_ref);
|
|
ir_END_list(end_inputs);
|
|
}
|
|
}
|
|
}
|
|
ir_IF_FALSE(if_dynamic);
|
|
}
|
|
prop_ref = ir_ADD_A(obj_ref, offset_ref);
|
|
prop_type_ref = jit_Z_TYPE_ref(jit, prop_ref);
|
|
ir_ref if_def = ir_IF(prop_type_ref);
|
|
ir_IF_FALSE_cold(if_def);
|
|
ir_END_list(slow_inputs);
|
|
ir_IF_TRUE(if_def);
|
|
prop_addr = ZEND_ADDR_REF_ZVAL(prop_ref);
|
|
if (opline->opcode == ZEND_FETCH_OBJ_W
|
|
&& (!ce || ce_is_instanceof || (ce->ce_flags & (ZEND_ACC_HAS_TYPE_HINTS|ZEND_ACC_TRAIT)))) {
|
|
uint32_t flags = opline->extended_value & ZEND_FETCH_OBJ_FLAGS;
|
|
|
|
ir_ref allowed_inputs = IR_UNUSED;
|
|
ir_ref forbidden_inputs = IR_UNUSED;
|
|
|
|
ir_ref prop_info_ref = ir_LOAD_A(
|
|
ir_ADD_OFFSET(run_time_cache, (opline->extended_value & ~ZEND_FETCH_OBJ_FLAGS) + sizeof(void*) * 2));
|
|
ir_ref if_has_prop_info = ir_IF(prop_info_ref);
|
|
|
|
ir_IF_TRUE_cold(if_has_prop_info);
|
|
|
|
ir_ref prop_flags = ir_LOAD_U32(ir_ADD_OFFSET(prop_info_ref, offsetof(zend_property_info, flags)));
|
|
ir_ref if_readonly_or_avis = ir_IF(ir_AND_U32(prop_flags, ir_CONST_U32(ZEND_ACC_READONLY|ZEND_ACC_PPP_SET_MASK)));
|
|
|
|
ir_IF_FALSE(if_readonly_or_avis);
|
|
ir_END_list(allowed_inputs);
|
|
|
|
ir_IF_TRUE_cold(if_readonly_or_avis);
|
|
|
|
ir_ref if_readonly = ir_IF(ir_AND_U32(prop_flags, ir_CONST_U32(ZEND_ACC_READONLY)));
|
|
ir_IF_TRUE(if_readonly);
|
|
ir_END_list(forbidden_inputs);
|
|
|
|
ir_IF_FALSE(if_readonly);
|
|
ir_ref has_avis_access = ir_CALL_1(IR_BOOL, ir_CONST_FC_FUNC(zend_asymmetric_property_has_set_access), prop_info_ref);
|
|
ir_ref if_avis_access = ir_IF(has_avis_access);
|
|
ir_IF_TRUE(if_avis_access);
|
|
ir_END_list(allowed_inputs);
|
|
|
|
ir_IF_FALSE(if_avis_access);
|
|
ir_END_list(forbidden_inputs);
|
|
|
|
ir_MERGE_list(forbidden_inputs);
|
|
|
|
ir_ref if_prop_obj = jit_if_Z_TYPE(jit, prop_addr, IS_OBJECT);
|
|
ir_IF_TRUE(if_prop_obj);
|
|
ref = jit_Z_PTR(jit, prop_addr);
|
|
jit_GC_ADDREF(jit, ref);
|
|
jit_set_Z_PTR(jit, res_addr, ref);
|
|
jit_set_Z_TYPE_INFO(jit, res_addr, IS_OBJECT_EX);
|
|
ir_END_list(end_inputs);
|
|
|
|
ir_IF_FALSE_cold(if_prop_obj);
|
|
|
|
jit_SET_EX_OPLINE(jit, opline);
|
|
if_readonly = ir_IF(ir_AND_U32(prop_flags, ir_CONST_U32(ZEND_ACC_READONLY)));
|
|
ir_IF_TRUE(if_readonly);
|
|
ir_CALL_1(IR_VOID, ir_CONST_FC_FUNC(zend_readonly_property_indirect_modification_error), prop_info_ref);
|
|
jit_set_Z_TYPE_INFO(jit, res_addr, _IS_ERROR);
|
|
ir_END_list(end_inputs);
|
|
|
|
ir_IF_FALSE(if_readonly);
|
|
ir_CALL_2(IR_VOID, ir_CONST_FC_FUNC(zend_asymmetric_visibility_property_modification_error),
|
|
prop_info_ref, ir_CONST_ADDR("indirectly modify"));
|
|
jit_set_Z_TYPE_INFO(jit, res_addr, _IS_ERROR);
|
|
ir_END_list(end_inputs);
|
|
|
|
ir_MERGE_list(allowed_inputs);
|
|
|
|
if (flags == ZEND_FETCH_DIM_WRITE) {
|
|
jit_SET_EX_OPLINE(jit, opline);
|
|
ir_CALL_2(IR_VOID, ir_CONST_FC_FUNC(zend_jit_check_array_promotion),
|
|
prop_ref, prop_info_ref);
|
|
ir_END_list(end_inputs);
|
|
ir_IF_FALSE(if_has_prop_info);
|
|
} else if (flags == ZEND_FETCH_REF) {
|
|
ir_CALL_3(IR_VOID, ir_CONST_FC_FUNC(zend_jit_create_typed_ref),
|
|
prop_ref,
|
|
prop_info_ref,
|
|
jit_ZVAL_ADDR(jit, res_addr));
|
|
ir_END_list(end_inputs);
|
|
ir_IF_FALSE(if_has_prop_info);
|
|
} else {
|
|
ZEND_ASSERT(flags == 0);
|
|
ir_MERGE_WITH_EMPTY_FALSE(if_has_prop_info);
|
|
}
|
|
}
|
|
} else {
|
|
prop_ref = ir_ADD_OFFSET(obj_ref, prop_info->offset);
|
|
prop_addr = ZEND_ADDR_REF_ZVAL(prop_ref);
|
|
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;
|
|
}
|
|
prop_type_ref = jit_Z_TYPE_INFO(jit, prop_addr);
|
|
ir_GUARD(prop_type_ref, ir_CONST_ADDR(exit_addr));
|
|
}
|
|
} else {
|
|
prop_type_ref = jit_Z_TYPE_INFO(jit, prop_addr);
|
|
ir_ref if_def = ir_IF(prop_type_ref);
|
|
ir_IF_FALSE_cold(if_def);
|
|
ir_END_list(slow_inputs);
|
|
ir_IF_TRUE(if_def);
|
|
}
|
|
if (opline->opcode == ZEND_FETCH_OBJ_W && (prop_info->flags & ZEND_ACC_READONLY)) {
|
|
ir_ref if_prop_obj = jit_if_Z_TYPE(jit, prop_addr, IS_OBJECT);
|
|
ir_IF_TRUE(if_prop_obj);
|
|
ir_ref ref = jit_Z_PTR(jit, prop_addr);
|
|
jit_GC_ADDREF(jit, ref);
|
|
jit_set_Z_PTR(jit, res_addr, ref);
|
|
jit_set_Z_TYPE_INFO(jit, res_addr, IS_OBJECT_EX);
|
|
ir_END_list(end_inputs);
|
|
|
|
ir_IF_FALSE_cold(if_prop_obj);
|
|
jit_SET_EX_OPLINE(jit, opline);
|
|
ir_CALL_1(IR_VOID, ir_CONST_FC_FUNC(zend_readonly_property_indirect_modification_error), ir_CONST_ADDR(prop_info));
|
|
jit_set_Z_TYPE_INFO(jit, res_addr, _IS_ERROR);
|
|
ir_END_list(end_inputs);
|
|
|
|
goto result_fetched;
|
|
} else if (opline->opcode == ZEND_FETCH_OBJ_W && (prop_info->flags & ZEND_ACC_PPP_SET_MASK)) {
|
|
/* Readonly properties which are also asymmetric are never mutable indirectly, which is
|
|
* handled by the previous branch. */
|
|
ir_ref has_access = ir_CALL_1(IR_BOOL, ir_CONST_FC_FUNC(zend_asymmetric_property_has_set_access), ir_CONST_ADDR(prop_info));
|
|
|
|
ir_ref if_access = ir_IF(has_access);
|
|
ir_IF_FALSE_cold(if_access);
|
|
|
|
ir_ref if_prop_obj = jit_if_Z_TYPE(jit, prop_addr, IS_OBJECT);
|
|
ir_IF_TRUE(if_prop_obj);
|
|
ir_ref ref = jit_Z_PTR(jit, prop_addr);
|
|
jit_GC_ADDREF(jit, ref);
|
|
jit_set_Z_PTR(jit, res_addr, ref);
|
|
jit_set_Z_TYPE_INFO(jit, res_addr, IS_OBJECT_EX);
|
|
ir_END_list(end_inputs);
|
|
|
|
ir_IF_FALSE_cold(if_prop_obj);
|
|
ir_CALL_2(IR_VOID, ir_CONST_FC_FUNC(zend_asymmetric_visibility_property_modification_error),
|
|
ir_CONST_ADDR(prop_info), ir_CONST_ADDR("indirectly modify"));
|
|
ir_END_list(end_inputs);
|
|
|
|
ir_IF_TRUE(if_access);
|
|
}
|
|
|
|
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_ARRAY) == 0) {
|
|
if (!prop_type_ref) {
|
|
prop_type_ref = jit_Z_TYPE_INFO(jit, prop_addr);
|
|
}
|
|
ir_ref if_null_or_flase = ir_IF(ir_LE(prop_type_ref, ir_CONST_U32(IR_FALSE)));
|
|
ir_IF_TRUE_cold(if_null_or_flase);
|
|
jit_SET_EX_OPLINE(jit, opline);
|
|
ir_CALL_2(IR_VOID, ir_CONST_FC_FUNC(zend_jit_check_array_promotion),
|
|
prop_ref, ir_CONST_ADDR(prop_info));
|
|
ir_END_list(end_inputs);
|
|
ir_IF_FALSE(if_null_or_flase);
|
|
}
|
|
} else if (flags == ZEND_FETCH_REF) {
|
|
ir_ref ref;
|
|
|
|
if (!prop_type_ref) {
|
|
prop_type_ref = jit_Z_TYPE_INFO(jit, prop_addr);
|
|
}
|
|
|
|
ir_ref if_reference = ir_IF(ir_EQ(prop_type_ref, ir_CONST_U32(IS_REFERENCE_EX)));
|
|
ir_IF_FALSE(if_reference);
|
|
if (ce && ce->ce_flags & ZEND_ACC_IMMUTABLE) {
|
|
ref = ir_CONST_ADDR(prop_info);
|
|
} else {
|
|
int prop_info_offset =
|
|
(((prop_info->offset - (sizeof(zend_object) - sizeof(zval))) / sizeof(zval)) * sizeof(void*));
|
|
|
|
ref = ir_LOAD_A(ir_ADD_OFFSET(obj_ref, offsetof(zend_object, ce)));
|
|
ref = ir_LOAD_A(ir_ADD_OFFSET(ref, offsetof(zend_class_entry, properties_info_table)));
|
|
ref = ir_LOAD_A(ir_ADD_OFFSET(ref, prop_info_offset));
|
|
}
|
|
ir_CALL_3(IR_VOID, ir_CONST_FC_FUNC(zend_jit_create_typed_ref),
|
|
prop_ref,
|
|
ref,
|
|
jit_ZVAL_ADDR(jit, res_addr));
|
|
ir_END_list(end_inputs);
|
|
ir_IF_TRUE(if_reference);
|
|
} else {
|
|
ZEND_UNREACHABLE();
|
|
}
|
|
}
|
|
}
|
|
|
|
if (opline->opcode == ZEND_FETCH_OBJ_W) {
|
|
ZEND_ASSERT(prop_ref);
|
|
jit_set_Z_PTR(jit, res_addr, prop_ref);
|
|
jit_set_Z_TYPE_INFO(jit, 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;
|
|
}
|
|
ir_END_list(end_inputs);
|
|
} else {
|
|
if (((res_info & MAY_BE_GUARD) && JIT_G(current_frame) && prop_info)
|
|
|| Z_MODE(res_addr) == IS_REG) {
|
|
ir_END_PHI_list(end_values, jit_ZVAL_ADDR(jit, prop_addr));
|
|
} else {
|
|
prop_type_ref = jit_Z_TYPE_INFO(jit, prop_addr);
|
|
|
|
if (!zend_jit_zval_copy_deref(jit, res_addr, prop_addr, prop_type_ref)) {
|
|
return 0;
|
|
}
|
|
ir_END_list(end_inputs);
|
|
}
|
|
}
|
|
|
|
result_fetched:
|
|
if (op1_avoid_refcounting) {
|
|
SET_STACK_REG(JIT_G(current_frame)->stack, EX_VAR_TO_NUM(opline->op1.var), ZREG_NONE);
|
|
}
|
|
|
|
if (JIT_G(trigger) != ZEND_JIT_ON_HOT_TRACE || !prop_info) {
|
|
ir_MERGE_list(slow_inputs);
|
|
jit_SET_EX_OPLINE(jit, opline);
|
|
|
|
if (opline->opcode == ZEND_FETCH_OBJ_W) {
|
|
ir_CALL_1(IR_VOID, ir_CONST_FC_FUNC(zend_jit_fetch_obj_w_slow), obj_ref);
|
|
ir_END_list(end_inputs);
|
|
} else if (opline->opcode != ZEND_FETCH_OBJ_IS) {
|
|
if (Z_MODE(res_addr) == IS_REG) {
|
|
ir_ref val_ref = ir_CALL_1(IR_ADDR, ir_CONST_FC_FUNC(zend_jit_fetch_obj_r_slow_ex), obj_ref);
|
|
ir_END_PHI_list(end_values, val_ref);
|
|
} else {
|
|
ir_CALL_1(IR_VOID, ir_CONST_FC_FUNC(zend_jit_fetch_obj_r_slow), obj_ref);
|
|
ir_END_list(end_inputs);
|
|
}
|
|
} else {
|
|
ir_CALL_1(IR_VOID, ir_CONST_FC_FUNC(zend_jit_fetch_obj_is_slow), obj_ref);
|
|
ir_END_list(end_inputs);
|
|
}
|
|
}
|
|
|
|
if (end_values) {
|
|
ir_ref val_ref = ir_PHI_list(end_values);
|
|
zend_jit_addr val_addr = ZEND_ADDR_REF_ZVAL(val_ref);
|
|
bool result_avoid_refcounting = 0;
|
|
|
|
ZEND_ASSERT(opline->opcode == ZEND_FETCH_OBJ_R
|
|
|| opline->opcode == ZEND_FETCH_OBJ_FUNC_ARG
|
|
|| opline->opcode == ZEND_FETCH_OBJ_IS);
|
|
ZEND_ASSERT(end_inputs == IR_UNUSED);
|
|
if ((res_info & MAY_BE_GUARD) && JIT_G(current_frame)) {
|
|
uint8_t type = concrete_type(res_info);
|
|
uint32_t flags = 0;
|
|
|
|
if ((opline->op1_type & (IS_VAR|IS_TMP_VAR))
|
|
&& !delayed_fetch_this
|
|
&& !op1_avoid_refcounting) {
|
|
flags = ZEND_JIT_EXIT_FREE_OP1;
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
val_addr = zend_jit_guard_fetch_result_type(jit, opline, val_addr, type,
|
|
1, flags, op1_avoid_refcounting);
|
|
if (!val_addr) {
|
|
return 0;
|
|
}
|
|
|
|
res_info &= ~MAY_BE_GUARD;
|
|
ssa->var_info[ssa_op->result_def].type &= ~MAY_BE_GUARD;
|
|
}
|
|
|
|
// ZVAL_COPY
|
|
jit_ZVAL_COPY(jit, res_addr, -1, val_addr, res_info, !result_avoid_refcounting);
|
|
|
|
if (!zend_jit_store_var_if_necessary(jit, opline->result.var, res_addr, res_info)) {
|
|
return 0;
|
|
}
|
|
} else {
|
|
ir_MERGE_list(end_inputs);
|
|
}
|
|
|
|
if (opline->op1_type != IS_UNUSED && !delayed_fetch_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();
|
|
ir_ref if_refcounted, ptr, refcount, if_non_zero;
|
|
ir_ref merge_inputs = IR_UNUSED;
|
|
|
|
if_refcounted = jit_if_REFCOUNTED(jit, orig_op1_addr);
|
|
ir_IF_FALSE( if_refcounted);
|
|
ir_END_list(merge_inputs);
|
|
ir_IF_TRUE( if_refcounted);
|
|
ptr = jit_Z_PTR(jit, orig_op1_addr);
|
|
refcount = jit_GC_DELREF(jit, ptr);
|
|
if_non_zero = ir_IF(refcount);
|
|
ir_IF_TRUE( if_non_zero);
|
|
ir_END_list(merge_inputs);
|
|
ir_IF_FALSE( if_non_zero);
|
|
jit_SET_EX_OPLINE(jit, opline);
|
|
ir_CALL_1(IR_VOID, ir_CONST_FC_FUNC(zend_jit_extract_helper), ptr);
|
|
ir_END_list(merge_inputs);
|
|
ir_MERGE_list(merge_inputs);
|
|
} else if (!op1_avoid_refcounting) {
|
|
if (on_this) {
|
|
op1_info &= ~MAY_BE_RC1;
|
|
}
|
|
jit_FREE_OP(jit, opline->op1_type, opline->op1, op1_info, 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|IS_TMP_VAR)) || on_this || op1_indirect)) {
|
|
may_throw = 0;
|
|
}
|
|
|
|
if (may_throw) {
|
|
zend_jit_check_exception(jit);
|
|
}
|
|
|
|
return 1;
|
|
}
|
|
|
|
static int zend_jit_assign_obj(zend_jit_ctx *jit,
|
|
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_jit_addr val_addr,
|
|
zend_jit_addr val_def_addr,
|
|
zend_jit_addr res_addr,
|
|
bool op1_indirect,
|
|
zend_class_entry *ce,
|
|
bool ce_is_instanceof,
|
|
bool on_this,
|
|
bool delayed_fetch_this,
|
|
zend_class_entry *trace_ce,
|
|
uint8_t prop_type,
|
|
int may_throw)
|
|
{
|
|
zval *member;
|
|
zend_string *name;
|
|
zend_property_info *prop_info;
|
|
zend_jit_addr prop_addr;
|
|
ir_ref obj_ref = IR_UNUSED;
|
|
ir_ref prop_ref = IR_UNUSED;
|
|
ir_ref delayed_end_input = IR_UNUSED;
|
|
ir_ref end_inputs = IR_UNUSED;
|
|
ir_ref slow_inputs = IR_UNUSED;
|
|
uint32_t res_info = RES_INFO();
|
|
|
|
if (val_addr != val_def_addr && val_def_addr) {
|
|
if (!zend_jit_update_regs(jit, (opline+1)->op1.var, val_addr, val_def_addr, val_info)) {
|
|
return 0;
|
|
}
|
|
if (Z_MODE(val_def_addr) == IS_REG && Z_MODE(val_addr) != IS_REG) {
|
|
val_addr = val_def_addr;
|
|
}
|
|
}
|
|
|
|
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(op_array, ce, name, on_this, op_array->filename);
|
|
|
|
if (on_this) {
|
|
zend_jit_addr this_addr = ZEND_ADDR_MEM_ZVAL(ZREG_FP, offsetof(zend_execute_data, This));
|
|
obj_ref = jit_Z_PTR(jit, this_addr);
|
|
} else {
|
|
if (opline->op1_type == IS_VAR
|
|
&& (op1_info & MAY_BE_INDIRECT)
|
|
&& Z_REG(op1_addr) == ZREG_FP) {
|
|
op1_addr = jit_ZVAL_INDIRECT_DEREF(jit, op1_addr);
|
|
}
|
|
if (op1_info & MAY_BE_REF) {
|
|
op1_addr = jit_ZVAL_DEREF(jit, op1_addr);
|
|
}
|
|
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;
|
|
}
|
|
jit_guard_Z_TYPE(jit, op1_addr, IS_OBJECT, exit_addr);
|
|
} else {
|
|
ir_ref if_obj = jit_if_Z_TYPE(jit, op1_addr, IS_OBJECT);
|
|
ir_IF_FALSE_cold(if_obj);
|
|
|
|
jit_SET_EX_OPLINE(jit, opline);
|
|
ir_CALL_2(IR_VOID, ir_CONST_FC_FUNC(zend_jit_invalid_property_assign),
|
|
jit_ZVAL_ADDR(jit, op1_addr),
|
|
ir_CONST_ADDR(ZSTR_VAL(name)));
|
|
|
|
if (RETURN_VALUE_USED(opline) && Z_MODE(res_addr) != IS_REG) {
|
|
jit_set_Z_TYPE_INFO(jit, res_addr, IS_NULL);
|
|
}
|
|
|
|
ir_END_list(end_inputs);
|
|
|
|
ir_IF_TRUE(if_obj);
|
|
}
|
|
}
|
|
obj_ref = jit_Z_PTR(jit, op1_addr);
|
|
}
|
|
|
|
ZEND_ASSERT(obj_ref);
|
|
if (!prop_info && trace_ce && (trace_ce->ce_flags & ZEND_ACC_IMMUTABLE)) {
|
|
prop_info = zend_get_known_property_info(op_array, trace_ce, name, on_this, op_array->filename);
|
|
if (prop_info) {
|
|
ce = trace_ce;
|
|
ce_is_instanceof = 0;
|
|
if (!(op1_info & MAY_BE_CLASS_GUARD)) {
|
|
if (on_this && JIT_G(current_frame)
|
|
&& TRACE_FRAME_IS_THIS_CLASS_CHECKED(JIT_G(current_frame))) {
|
|
ZEND_ASSERT(JIT_G(current_frame)->ce == ce);
|
|
} else if (zend_jit_class_guard(jit, opline, obj_ref, ce)) {
|
|
if (on_this && JIT_G(current_frame)) {
|
|
JIT_G(current_frame)->ce = ce;
|
|
TRACE_FRAME_SET_THIS_CLASS_CHECKED(JIT_G(current_frame));
|
|
}
|
|
} else {
|
|
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) {
|
|
ir_ref run_time_cache = ir_LOAD_A(jit_EX(run_time_cache));
|
|
ir_ref ref = ir_LOAD_A(ir_ADD_OFFSET(run_time_cache, opline->extended_value & ~ZEND_FETCH_OBJ_FLAGS));
|
|
ir_ref if_same = ir_IF(ir_EQ(ref, ir_LOAD_A(ir_ADD_OFFSET(obj_ref, offsetof(zend_object, ce)))));
|
|
|
|
ir_IF_FALSE_cold(if_same);
|
|
ir_END_list(slow_inputs);
|
|
|
|
ir_IF_TRUE(if_same);
|
|
ir_ref offset_ref = ir_LOAD_A(
|
|
ir_ADD_OFFSET(run_time_cache, (opline->extended_value & ~ZEND_FETCH_OBJ_FLAGS) + sizeof(void*)));
|
|
|
|
ir_ref if_dynamic = ir_IF(ir_LT(offset_ref, ir_CONST_ADDR(ZEND_FIRST_PROPERTY_OFFSET)));
|
|
ir_IF_TRUE_cold(if_dynamic);
|
|
ir_END_list(slow_inputs);
|
|
|
|
ir_IF_FALSE(if_dynamic);
|
|
prop_ref = ir_ADD_A(obj_ref, offset_ref);
|
|
ir_ref if_def = ir_IF(jit_Z_TYPE_ref(jit, prop_ref));
|
|
ir_IF_FALSE_cold(if_def);
|
|
ir_END_list(slow_inputs);
|
|
|
|
ir_IF_TRUE(if_def);
|
|
prop_addr = ZEND_ADDR_REF_ZVAL(prop_ref);
|
|
|
|
if (!ce || ce_is_instanceof || (ce->ce_flags & (ZEND_ACC_HAS_TYPE_HINTS|ZEND_ACC_TRAIT))) {
|
|
ir_ref arg3, arg4;
|
|
ir_ref prop_info_ref = ir_LOAD_A(
|
|
ir_ADD_OFFSET(run_time_cache, (opline->extended_value & ~ZEND_FETCH_OBJ_FLAGS) + sizeof(void*) * 2));
|
|
ir_ref if_has_prop_info = ir_IF(prop_info_ref);
|
|
ir_IF_TRUE_cold(if_has_prop_info);
|
|
|
|
if (Z_MODE(val_addr) == IS_REG) {
|
|
zend_jit_addr real_addr = ZEND_ADDR_MEM_ZVAL(ZREG_FP, (opline+1)->op1.var);
|
|
if (!zend_jit_spill_store_inv(jit, val_addr, real_addr, val_info)) {
|
|
return 0;
|
|
}
|
|
arg3 = jit_ZVAL_ADDR(jit, real_addr);
|
|
} else {
|
|
arg3 = jit_ZVAL_ADDR(jit, val_addr);
|
|
}
|
|
|
|
if (!RETURN_VALUE_USED(opline)) {
|
|
arg4 = IR_NULL;
|
|
} else if (Z_MODE(res_addr) == IS_REG) {
|
|
zend_jit_addr real_addr = ZEND_ADDR_MEM_ZVAL(ZREG_FP, opline->result.var);
|
|
arg4 = jit_ZVAL_ADDR(jit, real_addr);
|
|
} else {
|
|
arg4 = jit_ZVAL_ADDR(jit, res_addr);
|
|
}
|
|
// JIT: value = zend_assign_to_typed_prop(prop_info, property_val, value EXECUTE_DATA_CC);
|
|
jit_SET_EX_OPLINE(jit, opline);
|
|
ir_CALL_4(IR_VOID, ir_CONST_FC_FUNC(zend_jit_assign_to_typed_prop),
|
|
prop_ref,
|
|
prop_info_ref,
|
|
arg3,
|
|
arg4);
|
|
|
|
if ((opline+1)->op1_type == IS_CONST) {
|
|
// TODO: ???
|
|
// if (Z_TYPE_P(value) == orig_type) {
|
|
// CACHE_PTR_EX(cache_slot + 2, NULL);
|
|
}
|
|
|
|
if (RETURN_VALUE_USED(opline) && Z_MODE(res_addr) == IS_REG) {
|
|
zend_jit_addr real_addr = ZEND_ADDR_MEM_ZVAL(ZREG_FP, opline->result.var);
|
|
if (!zend_jit_load_reg(jit, real_addr, res_addr, res_info)) {
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
ir_END_list(end_inputs);
|
|
ir_IF_FALSE(if_has_prop_info);
|
|
}
|
|
} else {
|
|
prop_ref = ir_ADD_OFFSET(obj_ref, prop_info->offset);
|
|
prop_addr = ZEND_ADDR_REF_ZVAL(prop_ref);
|
|
/* With the exception of __clone(), readonly assignment always happens on IS_UNDEF, doding
|
|
* the fast path. Thus, the fast path is not useful. */
|
|
if (prop_info->flags & ZEND_ACC_READONLY) {
|
|
ZEND_ASSERT(slow_inputs == IR_UNUSED);
|
|
goto slow_path;
|
|
}
|
|
// Undefined property with potential magic __get()/__set() or lazy 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;
|
|
}
|
|
ir_GUARD(jit_Z_TYPE_INFO(jit, prop_addr), ir_CONST_ADDR(exit_addr));
|
|
} else {
|
|
ir_ref if_def = ir_IF(jit_Z_TYPE_INFO(jit, prop_addr));
|
|
ir_IF_FALSE_cold(if_def);
|
|
ir_END_list(slow_inputs);
|
|
ir_IF_TRUE(if_def);
|
|
}
|
|
if (ZEND_TYPE_IS_SET(prop_info->type)) {
|
|
ir_ref ref, arg3, arg4;
|
|
|
|
// JIT: value = zend_assign_to_typed_prop(prop_info, property_val, value EXECUTE_DATA_CC);
|
|
jit_SET_EX_OPLINE(jit, opline);
|
|
if (ce && ce->ce_flags & ZEND_ACC_IMMUTABLE) {
|
|
ref = ir_CONST_ADDR(prop_info);
|
|
} else {
|
|
int prop_info_offset =
|
|
(((prop_info->offset - (sizeof(zend_object) - sizeof(zval))) / sizeof(zval)) * sizeof(void*));
|
|
|
|
ref = ir_LOAD_A(ir_ADD_OFFSET(obj_ref, offsetof(zend_object, ce)));
|
|
ref = ir_LOAD_A(ir_ADD_OFFSET(ref, offsetof(zend_class_entry, properties_info_table)));
|
|
ref = ir_LOAD_A(ir_ADD_OFFSET(ref, prop_info_offset));
|
|
}
|
|
if (Z_MODE(val_addr) == IS_REG) {
|
|
zend_jit_addr real_addr = ZEND_ADDR_MEM_ZVAL(ZREG_FP, (opline+1)->op1.var);
|
|
if (!zend_jit_spill_store_inv(jit, val_addr, real_addr, val_info)) {
|
|
return 0;
|
|
}
|
|
arg3 = jit_ZVAL_ADDR(jit, real_addr);
|
|
} else {
|
|
arg3 = jit_ZVAL_ADDR(jit, val_addr);
|
|
}
|
|
if (!RETURN_VALUE_USED(opline)) {
|
|
arg4 = IR_NULL;
|
|
} else if (Z_MODE(res_addr) == IS_REG) {
|
|
zend_jit_addr real_addr = ZEND_ADDR_MEM_ZVAL(ZREG_FP, opline->result.var);
|
|
arg4 = jit_ZVAL_ADDR(jit, real_addr);
|
|
} else {
|
|
arg4 = jit_ZVAL_ADDR(jit, res_addr);
|
|
}
|
|
ir_CALL_4(IR_VOID, ir_CONST_FC_FUNC(zend_jit_assign_to_typed_prop),
|
|
prop_ref,
|
|
ref,
|
|
arg3,
|
|
arg4);
|
|
|
|
if (RETURN_VALUE_USED(opline) && Z_MODE(res_addr) == IS_REG) {
|
|
zend_jit_addr real_addr = ZEND_ADDR_MEM_ZVAL(ZREG_FP, opline->result.var);
|
|
if (!zend_jit_load_reg(jit, real_addr, res_addr, res_info)) {
|
|
return 0;
|
|
}
|
|
}
|
|
ir_END_list(end_inputs);
|
|
}
|
|
}
|
|
|
|
if (!prop_info || !ZEND_TYPE_IS_SET(prop_info->type)) {
|
|
if (Z_MODE(val_addr) != IS_REG
|
|
&& (res_addr == 0 || Z_MODE(res_addr) != IS_REG)
|
|
&& opline->result_type == IS_UNUSED) {
|
|
if (!zend_jit_assign_to_variable_call(jit, 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(jit, opline, prop_addr, prop_addr, -1, -1, (opline+1)->op1_type, val_addr, val_info, res_addr, 0, 0)) {
|
|
return 0;
|
|
}
|
|
}
|
|
if (end_inputs || slow_inputs) {
|
|
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))) {
|
|
/* skip FREE_OP_DATA() */
|
|
delayed_end_input = ir_END();
|
|
} else {
|
|
ir_END_list(end_inputs);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (slow_inputs) {
|
|
ir_ref arg3, arg5;
|
|
|
|
ir_MERGE_list(slow_inputs);
|
|
|
|
slow_path:
|
|
jit_SET_EX_OPLINE(jit, opline);
|
|
|
|
if (Z_MODE(val_addr) == IS_REG) {
|
|
zend_jit_addr real_addr = ZEND_ADDR_MEM_ZVAL(ZREG_FP, (opline+1)->op1.var);
|
|
if (!zend_jit_spill_store_inv(jit, val_addr, real_addr, val_info)) {
|
|
return 0;
|
|
}
|
|
arg3 = jit_ZVAL_ADDR(jit, real_addr);
|
|
} else {
|
|
arg3 = jit_ZVAL_ADDR(jit, val_addr);
|
|
}
|
|
if (!RETURN_VALUE_USED(opline)) {
|
|
arg5 = IR_NULL;
|
|
} else if (Z_MODE(res_addr) == IS_REG) {
|
|
zend_jit_addr real_addr = ZEND_ADDR_MEM_ZVAL(ZREG_FP, opline->result.var);
|
|
arg5 = jit_ZVAL_ADDR(jit, real_addr);
|
|
} else {
|
|
arg5 = jit_ZVAL_ADDR(jit, res_addr);
|
|
}
|
|
|
|
// JIT: value = zobj->handlers->write_property(zobj, name, value, CACHE_ADDR(opline->extended_value));
|
|
ir_ref run_time_cache = ir_LOAD_A(jit_EX(run_time_cache));
|
|
ir_CALL_5(IR_VOID, ir_CONST_FC_FUNC(zend_jit_assign_obj_helper),
|
|
obj_ref,
|
|
ir_CONST_ADDR(name),
|
|
arg3,
|
|
ir_ADD_OFFSET(run_time_cache, opline->extended_value & ~ZEND_FETCH_OBJ_FLAGS),
|
|
arg5);
|
|
|
|
if (RETURN_VALUE_USED(opline) && Z_MODE(res_addr) == IS_REG) {
|
|
zend_jit_addr real_addr = ZEND_ADDR_MEM_ZVAL(ZREG_FP, opline->result.var);
|
|
if (!zend_jit_load_reg(jit, real_addr, res_addr, res_info)) {
|
|
return 0;
|
|
}
|
|
}
|
|
ir_END_list(end_inputs);
|
|
}
|
|
|
|
if (end_inputs) {
|
|
ir_MERGE_list(end_inputs);
|
|
|
|
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;
|
|
}
|
|
jit_FREE_OP(jit, (opline+1)->op1_type, (opline+1)->op1, val_info, opline);
|
|
|
|
if (delayed_end_input) {
|
|
ir_MERGE_WITH(delayed_end_input);
|
|
}
|
|
}
|
|
|
|
if (opline->op1_type != IS_UNUSED && !delayed_fetch_this && !op1_indirect) {
|
|
jit_FREE_OP(jit, opline->op1_type, opline->op1, op1_info, opline);
|
|
}
|
|
|
|
if (may_throw) {
|
|
zend_jit_check_exception(jit);
|
|
}
|
|
|
|
return 1;
|
|
}
|
|
|
|
static int zend_jit_assign_obj_op(zend_jit_ctx *jit,
|
|
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_jit_addr val_addr,
|
|
zend_ssa_range *val_range,
|
|
bool op1_indirect,
|
|
zend_class_entry *ce,
|
|
bool ce_is_instanceof,
|
|
bool on_this,
|
|
bool delayed_fetch_this,
|
|
zend_class_entry *trace_ce,
|
|
uint8_t prop_type)
|
|
{
|
|
zval *member;
|
|
zend_string *name;
|
|
zend_property_info *prop_info;
|
|
zend_jit_addr prop_addr;
|
|
bool use_prop_guard = 0;
|
|
bool may_throw = 0;
|
|
binary_op_type binary_op = get_binary_op(opline->extended_value);
|
|
ir_ref obj_ref = IR_UNUSED;
|
|
ir_ref prop_ref = IR_UNUSED;
|
|
ir_ref end_inputs = IR_UNUSED;
|
|
ir_ref slow_inputs = IR_UNUSED;
|
|
|
|
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(op_array, ce, name, on_this, op_array->filename);
|
|
|
|
if (on_this) {
|
|
zend_jit_addr this_addr = ZEND_ADDR_MEM_ZVAL(ZREG_FP, offsetof(zend_execute_data, This));
|
|
obj_ref = jit_Z_PTR(jit, this_addr);
|
|
} else {
|
|
if (opline->op1_type == IS_VAR
|
|
&& (op1_info & MAY_BE_INDIRECT)
|
|
&& Z_REG(op1_addr) == ZREG_FP) {
|
|
op1_addr = jit_ZVAL_INDIRECT_DEREF(jit, op1_addr);
|
|
}
|
|
if (op1_info & MAY_BE_REF) {
|
|
op1_addr = jit_ZVAL_DEREF(jit, op1_addr);
|
|
}
|
|
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;
|
|
}
|
|
jit_guard_Z_TYPE(jit, op1_addr, IS_OBJECT, exit_addr);
|
|
} else {
|
|
ir_ref if_obj = jit_if_Z_TYPE(jit, op1_addr, IS_OBJECT);
|
|
ir_IF_FALSE_cold(if_obj);
|
|
|
|
jit_SET_EX_OPLINE(jit, opline);
|
|
ir_CALL_2(IR_VOID,
|
|
(op1_info & MAY_BE_UNDEF) ?
|
|
ir_CONST_FC_FUNC(zend_jit_invalid_property_assign_op) :
|
|
ir_CONST_FC_FUNC(zend_jit_invalid_property_assign),
|
|
jit_ZVAL_ADDR(jit, op1_addr),
|
|
ir_CONST_ADDR(ZSTR_VAL(name)));
|
|
|
|
may_throw = 1;
|
|
|
|
ir_END_list(end_inputs);
|
|
ir_IF_TRUE(if_obj);
|
|
}
|
|
}
|
|
obj_ref = jit_Z_PTR(jit, op1_addr);
|
|
}
|
|
|
|
ZEND_ASSERT(obj_ref);
|
|
if (!prop_info && trace_ce && (trace_ce->ce_flags & ZEND_ACC_IMMUTABLE)) {
|
|
prop_info = zend_get_known_property_info(op_array, trace_ce, name, on_this, op_array->filename);
|
|
if (prop_info) {
|
|
ce = trace_ce;
|
|
ce_is_instanceof = 0;
|
|
if (!(op1_info & MAY_BE_CLASS_GUARD)) {
|
|
if (on_this && JIT_G(current_frame)
|
|
&& TRACE_FRAME_IS_THIS_CLASS_CHECKED(JIT_G(current_frame))) {
|
|
ZEND_ASSERT(JIT_G(current_frame)->ce == ce);
|
|
} else if (zend_jit_class_guard(jit, opline, obj_ref, ce)) {
|
|
if (on_this && JIT_G(current_frame)) {
|
|
JIT_G(current_frame)->ce = ce;
|
|
TRACE_FRAME_SET_THIS_CLASS_CHECKED(JIT_G(current_frame));
|
|
}
|
|
} else {
|
|
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;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
use_prop_guard = (prop_type != IS_UNKNOWN
|
|
&& prop_type != IS_UNDEF
|
|
&& prop_type != IS_REFERENCE
|
|
&& (op1_info & (MAY_BE_ANY|MAY_BE_UNDEF)) == MAY_BE_OBJECT);
|
|
|
|
if (!prop_info) {
|
|
ir_ref run_time_cache = ir_LOAD_A(jit_EX(run_time_cache));
|
|
ir_ref ref = ir_LOAD_A(ir_ADD_OFFSET(run_time_cache, (opline+1)->extended_value & ~ZEND_FETCH_OBJ_FLAGS));
|
|
ir_ref if_same = ir_IF(ir_EQ(ref, ir_LOAD_A(ir_ADD_OFFSET(obj_ref, offsetof(zend_object, ce)))));
|
|
|
|
ir_IF_FALSE_cold(if_same);
|
|
ir_END_list(slow_inputs);
|
|
|
|
ir_IF_TRUE(if_same);
|
|
if (!ce || ce_is_instanceof || (ce->ce_flags & (ZEND_ACC_HAS_TYPE_HINTS|ZEND_ACC_TRAIT))) {
|
|
ir_ref prop_info_ref = ir_LOAD_A(
|
|
ir_ADD_OFFSET(run_time_cache, ((opline+1)->extended_value & ~ZEND_FETCH_OBJ_FLAGS) + sizeof(void*) * 2));
|
|
ir_ref if_has_prop_info = ir_IF(prop_info_ref);
|
|
ir_IF_TRUE_cold(if_has_prop_info);
|
|
ir_END_list(slow_inputs);
|
|
|
|
ir_IF_FALSE(if_has_prop_info);
|
|
}
|
|
ir_ref offset_ref = ir_LOAD_A(
|
|
ir_ADD_OFFSET(run_time_cache, ((opline+1)->extended_value & ~ZEND_FETCH_OBJ_FLAGS) + sizeof(void*)));
|
|
|
|
ir_ref if_dynamic = ir_IF(ir_LT(offset_ref, ir_CONST_ADDR(ZEND_FIRST_PROPERTY_OFFSET)));
|
|
ir_IF_TRUE_cold(if_dynamic);
|
|
ir_END_list(slow_inputs);
|
|
|
|
ir_IF_FALSE(if_dynamic);
|
|
|
|
prop_ref = ir_ADD_A(obj_ref, offset_ref);
|
|
if (!use_prop_guard) {
|
|
ir_ref if_def = ir_IF(jit_Z_TYPE_ref(jit, prop_ref));
|
|
ir_IF_FALSE_cold(if_def);
|
|
ir_END_list(slow_inputs);
|
|
|
|
ir_IF_TRUE(if_def);
|
|
}
|
|
prop_addr = ZEND_ADDR_REF_ZVAL(prop_ref);
|
|
} else {
|
|
prop_ref = ir_ADD_OFFSET(obj_ref, prop_info->offset);
|
|
prop_addr = ZEND_ADDR_REF_ZVAL(prop_ref);
|
|
|
|
if (ZEND_TYPE_IS_SET(prop_info->type) || !use_prop_guard) {
|
|
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;
|
|
}
|
|
ir_GUARD(jit_Z_TYPE_INFO(jit, prop_addr), ir_CONST_ADDR(exit_addr));
|
|
} else {
|
|
ir_ref if_def = ir_IF(jit_Z_TYPE_INFO(jit, prop_addr));
|
|
ir_IF_FALSE_cold(if_def);
|
|
ir_END_list(slow_inputs);
|
|
ir_IF_TRUE(if_def);
|
|
}
|
|
}
|
|
if (ZEND_TYPE_IS_SET(prop_info->type)) {
|
|
ir_ref if_ref, if_typed, noref_path, ref_path, reference, ref, arg2;
|
|
|
|
may_throw = 1;
|
|
|
|
jit_SET_EX_OPLINE(jit, opline);
|
|
|
|
if (Z_MODE(val_addr) == IS_REG) {
|
|
zend_jit_addr real_addr = ZEND_ADDR_MEM_ZVAL(ZREG_FP, (opline+1)->op1.var);
|
|
if (!zend_jit_spill_store_inv(jit, val_addr, real_addr, val_info)) {
|
|
return 0;
|
|
}
|
|
arg2 = jit_ZVAL_ADDR(jit, real_addr);
|
|
} else {
|
|
arg2 = jit_ZVAL_ADDR(jit, val_addr);
|
|
}
|
|
|
|
if_ref = jit_if_Z_TYPE(jit, prop_addr, IS_REFERENCE);
|
|
ir_IF_FALSE(if_ref);
|
|
noref_path = ir_END();
|
|
ir_IF_TRUE(if_ref);
|
|
|
|
reference = jit_Z_PTR(jit, prop_addr);
|
|
ref = ir_ADD_OFFSET(reference, offsetof(zend_reference, val));
|
|
if_typed = jit_if_TYPED_REF(jit, reference);
|
|
ir_IF_FALSE(if_typed);
|
|
ref_path = ir_END();
|
|
ir_IF_TRUE_cold(if_typed);
|
|
|
|
ir_CALL_3(IR_VOID, ir_CONST_FC_FUNC(zend_jit_assign_op_to_typed_ref),
|
|
reference,
|
|
arg2,
|
|
ir_CONST_FC_FUNC(binary_op));
|
|
|
|
ir_END_list(end_inputs);
|
|
|
|
ir_MERGE_2(noref_path, ref_path);
|
|
prop_ref = ir_PHI_2(IR_ADDR, prop_ref, ref);
|
|
prop_addr = ZEND_ADDR_REF_ZVAL(prop_ref);
|
|
|
|
// JIT: value = zend_assign_to_typed_prop(prop_info, property_val, value EXECUTE_DATA_CC);
|
|
if (ce && ce->ce_flags & ZEND_ACC_IMMUTABLE) {
|
|
ref = ir_CONST_ADDR(prop_info);
|
|
} else {
|
|
int prop_info_offset =
|
|
(((prop_info->offset - (sizeof(zend_object) - sizeof(zval))) / sizeof(zval)) * sizeof(void*));
|
|
|
|
ref = ir_LOAD_A(ir_ADD_OFFSET(obj_ref, offsetof(zend_object, ce)));
|
|
ref = ir_LOAD_A(ir_ADD_OFFSET(ref, offsetof(zend_class_entry, properties_info_table)));
|
|
ref = ir_LOAD_A(ir_ADD_OFFSET(ref, prop_info_offset));
|
|
}
|
|
|
|
ir_CALL_4(IR_VOID, ir_CONST_FC_FUNC(zend_jit_assign_op_to_typed_prop),
|
|
prop_ref,
|
|
ref,
|
|
arg2,
|
|
ir_CONST_FC_FUNC(binary_op));
|
|
|
|
ir_END_list(end_inputs);
|
|
}
|
|
}
|
|
|
|
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;
|
|
|
|
if (use_prop_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;
|
|
}
|
|
|
|
jit_guard_Z_TYPE(jit, prop_addr, prop_type, exit_addr);
|
|
var_info = (1 << prop_type) | (var_info & ~(MAY_BE_ANY|MAY_BE_UNDEF|MAY_BE_REF));
|
|
}
|
|
|
|
if (var_info & MAY_BE_REF) {
|
|
ir_ref if_ref, if_typed, noref_path, ref_path, reference, ref, arg2;
|
|
|
|
may_throw = 1;
|
|
|
|
if_ref = jit_if_Z_TYPE(jit, prop_addr, IS_REFERENCE);
|
|
ir_IF_FALSE(if_ref);
|
|
noref_path = ir_END();
|
|
ir_IF_TRUE(if_ref);
|
|
|
|
reference = jit_Z_PTR(jit, var_addr);
|
|
ref = ir_ADD_OFFSET(reference, offsetof(zend_reference, val));
|
|
if_typed = jit_if_TYPED_REF(jit, reference);
|
|
ir_IF_FALSE(if_typed);
|
|
ref_path = ir_END();
|
|
ir_IF_TRUE_cold(if_typed);
|
|
|
|
jit_SET_EX_OPLINE(jit, opline);
|
|
|
|
if (Z_MODE(val_addr) == IS_REG) {
|
|
zend_jit_addr real_addr = ZEND_ADDR_MEM_ZVAL(ZREG_FP, (opline+1)->op1.var);
|
|
if (!zend_jit_spill_store_inv(jit, val_addr, real_addr, val_info)) {
|
|
return 0;
|
|
}
|
|
arg2 = jit_ZVAL_ADDR(jit, real_addr);
|
|
} else {
|
|
arg2 = jit_ZVAL_ADDR(jit, val_addr);
|
|
}
|
|
ir_CALL_3(IR_VOID, ir_CONST_FC_FUNC(zend_jit_assign_op_to_typed_ref),
|
|
reference,
|
|
arg2,
|
|
ir_CONST_FC_FUNC(binary_op));
|
|
|
|
ir_END_list(end_inputs);
|
|
|
|
ir_MERGE_2(noref_path, ref_path);
|
|
prop_ref = ir_PHI_2(IR_ADDR, prop_ref, ref);
|
|
var_addr = ZEND_ADDR_REF_ZVAL(prop_ref);
|
|
|
|
var_info &= ~MAY_BE_REF;
|
|
}
|
|
|
|
uint8_t val_op_type = (opline+1)->op1_type;
|
|
if (val_op_type & (IS_TMP_VAR|IS_VAR)) {
|
|
/* prevent FREE_OP in the helpers */
|
|
val_op_type = IS_CV;
|
|
}
|
|
|
|
switch (opline->extended_value) {
|
|
case ZEND_ADD:
|
|
case ZEND_SUB:
|
|
case ZEND_MUL:
|
|
if ((var_info & (MAY_BE_STRING|MAY_BE_ARRAY|MAY_BE_OBJECT|MAY_BE_RESOURCE)) ||
|
|
(val_info & (MAY_BE_STRING|MAY_BE_ARRAY|MAY_BE_OBJECT|MAY_BE_RESOURCE))) {
|
|
if (opline->extended_value != ZEND_ADD ||
|
|
(var_info & MAY_BE_ANY) != MAY_BE_ARRAY ||
|
|
(val_info & MAY_BE_ANY) == MAY_BE_ARRAY) {
|
|
may_throw = 1;
|
|
}
|
|
}
|
|
if (!zend_jit_math_helper(jit, opline, opline->extended_value, IS_CV, opline->op1, var_addr, var_info, val_op_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:
|
|
if ((var_info & (MAY_BE_STRING|MAY_BE_DOUBLE|MAY_BE_ARRAY|MAY_BE_OBJECT|MAY_BE_RESOURCE)) ||
|
|
(val_info & (MAY_BE_STRING|MAY_BE_DOUBLE|MAY_BE_ARRAY|MAY_BE_OBJECT|MAY_BE_RESOURCE))) {
|
|
if ((var_info & MAY_BE_ANY) != MAY_BE_STRING ||
|
|
(val_info & MAY_BE_ANY) != MAY_BE_STRING) {
|
|
may_throw = 1;
|
|
}
|
|
}
|
|
goto long_math;
|
|
case ZEND_SL:
|
|
case ZEND_SR:
|
|
if ((var_info & (MAY_BE_STRING|MAY_BE_DOUBLE|MAY_BE_ARRAY|MAY_BE_OBJECT|MAY_BE_RESOURCE)) ||
|
|
(val_info & (MAY_BE_STRING|MAY_BE_DOUBLE|MAY_BE_ARRAY|MAY_BE_OBJECT|MAY_BE_RESOURCE))) {
|
|
may_throw = 1;
|
|
}
|
|
if (val_op_type != IS_CONST ||
|
|
Z_TYPE_P(RT_CONSTANT((opline+1), (opline+1)->op1)) != IS_LONG ||
|
|
Z_LVAL_P(RT_CONSTANT((opline+1), (opline+1)->op1)) < 0) {
|
|
may_throw = 1;
|
|
}
|
|
goto long_math;
|
|
case ZEND_MOD:
|
|
if ((var_info & (MAY_BE_STRING|MAY_BE_ARRAY|MAY_BE_OBJECT|MAY_BE_RESOURCE)) ||
|
|
(val_info & (MAY_BE_STRING|MAY_BE_ARRAY|MAY_BE_OBJECT|MAY_BE_RESOURCE))) {
|
|
may_throw = 1;
|
|
}
|
|
if (val_op_type != IS_CONST ||
|
|
Z_TYPE_P(RT_CONSTANT((opline+1), (opline+1)->op1)) != IS_LONG ||
|
|
Z_LVAL_P(RT_CONSTANT((opline+1), (opline+1)->op1)) == 0) {
|
|
may_throw = 1;
|
|
}
|
|
long_math:
|
|
if (!zend_jit_long_math_helper(jit, opline, opline->extended_value,
|
|
IS_CV, opline->op1, var_addr, var_info, NULL,
|
|
val_op_type, (opline+1)->op1, val_addr, val_info,
|
|
val_range,
|
|
0, var_addr, var_def_info, var_info, /* may throw */ 1)) {
|
|
return 0;
|
|
}
|
|
break;
|
|
case ZEND_CONCAT:
|
|
may_throw = 1;
|
|
if (!zend_jit_concat_helper(jit, opline, IS_CV, opline->op1, var_addr, var_info, val_op_type, (opline+1)->op1, val_addr, val_info, var_addr,
|
|
0)) {
|
|
return 0;
|
|
}
|
|
break;
|
|
default:
|
|
ZEND_UNREACHABLE();
|
|
}
|
|
if (end_inputs || slow_inputs) {
|
|
ir_END_list(end_inputs);
|
|
}
|
|
}
|
|
|
|
if (slow_inputs) {
|
|
ir_ref arg3;
|
|
|
|
ir_MERGE_list(slow_inputs);
|
|
|
|
may_throw = 1;
|
|
|
|
if (Z_MODE(val_addr) == IS_REG) {
|
|
zend_jit_addr real_addr = ZEND_ADDR_MEM_ZVAL(ZREG_FP, (opline+1)->op1.var);
|
|
if (!zend_jit_spill_store_inv(jit, val_addr, real_addr, val_info)) {
|
|
return 0;
|
|
}
|
|
arg3 = jit_ZVAL_ADDR(jit, real_addr);
|
|
} else {
|
|
arg3 = jit_ZVAL_ADDR(jit, val_addr);
|
|
}
|
|
jit_SET_EX_OPLINE(jit, opline);
|
|
ir_ref run_time_cache = ir_LOAD_A(jit_EX(run_time_cache));
|
|
ir_CALL_5(IR_VOID, ir_CONST_FC_FUNC(zend_jit_assign_obj_op_helper),
|
|
obj_ref,
|
|
ir_CONST_ADDR(name),
|
|
arg3,
|
|
ir_ADD_OFFSET(run_time_cache, (opline+1)->extended_value & ~ZEND_FETCH_OBJ_FLAGS),
|
|
ir_CONST_FC_FUNC(binary_op));
|
|
|
|
ir_END_list(end_inputs);
|
|
}
|
|
|
|
if (end_inputs) {
|
|
ir_MERGE_list(end_inputs);
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
// JIT: FREE_OP_DATA();
|
|
jit_FREE_OP(jit, (opline+1)->op1_type, (opline+1)->op1, val_info, opline);
|
|
|
|
if (opline->op1_type != IS_UNUSED && !delayed_fetch_this && !op1_indirect) {
|
|
if ((op1_info & MAY_HAVE_DTOR) && (op1_info & MAY_BE_RC1)) {
|
|
may_throw = 1;
|
|
}
|
|
jit_FREE_OP(jit, opline->op1_type, opline->op1, op1_info, opline);
|
|
}
|
|
|
|
if (may_throw) {
|
|
zend_jit_check_exception(jit);
|
|
}
|
|
|
|
return 1;
|
|
}
|
|
|
|
static int zend_jit_incdec_obj(zend_jit_ctx *jit,
|
|
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,
|
|
bool op1_indirect,
|
|
zend_class_entry *ce,
|
|
bool ce_is_instanceof,
|
|
bool on_this,
|
|
bool delayed_fetch_this,
|
|
zend_class_entry *trace_ce,
|
|
uint8_t prop_type)
|
|
{
|
|
zval *member;
|
|
zend_string *name;
|
|
zend_property_info *prop_info;
|
|
zend_jit_addr res_addr = 0;
|
|
zend_jit_addr prop_addr;
|
|
bool use_prop_guard = 0;
|
|
bool may_throw = 0;
|
|
uint32_t res_info = (opline->result_type != IS_UNDEF) ? RES_INFO() : 0;
|
|
ir_ref obj_ref = IR_UNUSED;
|
|
ir_ref prop_ref = IR_UNUSED;
|
|
ir_ref end_inputs = IR_UNUSED;
|
|
ir_ref slow_inputs = IR_UNUSED;
|
|
|
|
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(op_array, ce, name, on_this, op_array->filename);
|
|
|
|
if (on_this) {
|
|
zend_jit_addr this_addr = ZEND_ADDR_MEM_ZVAL(ZREG_FP, offsetof(zend_execute_data, This));
|
|
obj_ref = jit_Z_PTR(jit, this_addr);
|
|
} else {
|
|
if (opline->op1_type == IS_VAR
|
|
&& (op1_info & MAY_BE_INDIRECT)
|
|
&& Z_REG(op1_addr) == ZREG_FP) {
|
|
op1_addr = jit_ZVAL_INDIRECT_DEREF(jit, op1_addr);
|
|
}
|
|
if (op1_info & MAY_BE_REF) {
|
|
op1_addr = jit_ZVAL_DEREF(jit, op1_addr);
|
|
}
|
|
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;
|
|
}
|
|
jit_guard_Z_TYPE(jit, op1_addr, IS_OBJECT, exit_addr);
|
|
} else {
|
|
ir_ref if_obj = jit_if_Z_TYPE(jit, op1_addr, IS_OBJECT);
|
|
ir_IF_FALSE_cold(if_obj);
|
|
|
|
jit_SET_EX_OPLINE(jit, opline);
|
|
ir_CALL_2(IR_VOID, ir_CONST_FC_FUNC(zend_jit_invalid_property_incdec),
|
|
jit_ZVAL_ADDR(jit, op1_addr),
|
|
ir_CONST_ADDR(ZSTR_VAL(name)));
|
|
|
|
ir_IJMP(jit_STUB_ADDR(jit, jit_stub_exception_handler));
|
|
ir_IF_TRUE(if_obj);
|
|
}
|
|
}
|
|
obj_ref = jit_Z_PTR(jit, op1_addr);
|
|
}
|
|
|
|
ZEND_ASSERT(obj_ref);
|
|
if (!prop_info && trace_ce && (trace_ce->ce_flags & ZEND_ACC_IMMUTABLE)) {
|
|
prop_info = zend_get_known_property_info(op_array, trace_ce, name, on_this, op_array->filename);
|
|
if (prop_info) {
|
|
ce = trace_ce;
|
|
ce_is_instanceof = 0;
|
|
if (!(op1_info & MAY_BE_CLASS_GUARD)) {
|
|
if (on_this && JIT_G(current_frame)
|
|
&& TRACE_FRAME_IS_THIS_CLASS_CHECKED(JIT_G(current_frame))) {
|
|
ZEND_ASSERT(JIT_G(current_frame)->ce == ce);
|
|
} else if (zend_jit_class_guard(jit, opline, obj_ref, ce)) {
|
|
if (on_this && JIT_G(current_frame)) {
|
|
JIT_G(current_frame)->ce = ce;
|
|
TRACE_FRAME_SET_THIS_CLASS_CHECKED(JIT_G(current_frame));
|
|
}
|
|
} else {
|
|
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;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
use_prop_guard = (prop_type != IS_UNKNOWN
|
|
&& prop_type != IS_UNDEF
|
|
&& prop_type != IS_REFERENCE
|
|
&& (op1_info & (MAY_BE_ANY|MAY_BE_UNDEF)) == MAY_BE_OBJECT);
|
|
|
|
if (!prop_info) {
|
|
ir_ref run_time_cache = ir_LOAD_A(jit_EX(run_time_cache));
|
|
ir_ref ref = ir_LOAD_A(ir_ADD_OFFSET(run_time_cache, opline->extended_value & ~ZEND_FETCH_OBJ_FLAGS));
|
|
ir_ref if_same = ir_IF(ir_EQ(ref, ir_LOAD_A(ir_ADD_OFFSET(obj_ref, offsetof(zend_object, ce)))));
|
|
|
|
ir_IF_FALSE_cold(if_same);
|
|
ir_END_list(slow_inputs);
|
|
|
|
ir_IF_TRUE(if_same);
|
|
if (!ce || ce_is_instanceof || (ce->ce_flags & (ZEND_ACC_HAS_TYPE_HINTS|ZEND_ACC_TRAIT))) {
|
|
ir_ref prop_info_ref = ir_LOAD_A(
|
|
ir_ADD_OFFSET(run_time_cache, (opline->extended_value & ~ZEND_FETCH_OBJ_FLAGS) + sizeof(void*) * 2));
|
|
ir_ref if_has_prop_info = ir_IF(prop_info_ref);
|
|
ir_IF_TRUE_cold(if_has_prop_info);
|
|
ir_END_list(slow_inputs);
|
|
|
|
ir_IF_FALSE(if_has_prop_info);
|
|
}
|
|
ir_ref offset_ref = ir_LOAD_A(
|
|
ir_ADD_OFFSET(run_time_cache, (opline->extended_value & ~ZEND_FETCH_OBJ_FLAGS) + sizeof(void*)));
|
|
|
|
ir_ref if_dynamic = ir_IF(ir_LT(offset_ref, ir_CONST_ADDR(ZEND_FIRST_PROPERTY_OFFSET)));
|
|
ir_IF_TRUE_cold(if_dynamic);
|
|
ir_END_list(slow_inputs);
|
|
|
|
ir_IF_FALSE(if_dynamic);
|
|
|
|
prop_ref = ir_ADD_A(obj_ref, offset_ref);
|
|
if (!use_prop_guard) {
|
|
ir_ref if_def = ir_IF(jit_Z_TYPE_ref(jit, prop_ref));
|
|
ir_IF_FALSE_cold(if_def);
|
|
ir_END_list(slow_inputs);
|
|
|
|
ir_IF_TRUE(if_def);
|
|
}
|
|
prop_addr = ZEND_ADDR_REF_ZVAL(prop_ref);
|
|
} else {
|
|
prop_ref = ir_ADD_OFFSET(obj_ref, prop_info->offset);
|
|
prop_addr = ZEND_ADDR_REF_ZVAL(prop_ref);
|
|
|
|
if (ZEND_TYPE_IS_SET(prop_info->type) || !use_prop_guard) {
|
|
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;
|
|
}
|
|
ir_GUARD(jit_Z_TYPE_INFO(jit, prop_addr), ir_CONST_ADDR(exit_addr));
|
|
} else {
|
|
ir_ref if_def = ir_IF(jit_Z_TYPE_INFO(jit, prop_addr));
|
|
ir_IF_FALSE_cold(if_def);
|
|
ir_END_list(slow_inputs);
|
|
ir_IF_TRUE(if_def);
|
|
}
|
|
}
|
|
|
|
if (ZEND_TYPE_IS_SET(prop_info->type)) {
|
|
const void *func;
|
|
ir_ref ref;
|
|
|
|
may_throw = 1;
|
|
jit_SET_EX_OPLINE(jit, opline);
|
|
|
|
if (ce && ce->ce_flags & ZEND_ACC_IMMUTABLE) {
|
|
ref = ir_CONST_ADDR(prop_info);
|
|
} else {
|
|
int prop_info_offset =
|
|
(((prop_info->offset - (sizeof(zend_object) - sizeof(zval))) / sizeof(zval)) * sizeof(void*));
|
|
|
|
ref = ir_LOAD_A(ir_ADD_OFFSET(obj_ref, offsetof(zend_object, ce)));
|
|
ref = ir_LOAD_A(ir_ADD_OFFSET(ref, offsetof(zend_class_entry, properties_info_table)));
|
|
ref = ir_LOAD_A(ir_ADD_OFFSET(ref, prop_info_offset));
|
|
}
|
|
|
|
if (opline->result_type == IS_UNUSED) {
|
|
switch (opline->opcode) {
|
|
case ZEND_PRE_INC_OBJ:
|
|
case ZEND_POST_INC_OBJ:
|
|
func = zend_jit_inc_typed_prop;
|
|
break;
|
|
case ZEND_PRE_DEC_OBJ:
|
|
case ZEND_POST_DEC_OBJ:
|
|
func = zend_jit_dec_typed_prop;
|
|
break;
|
|
default:
|
|
ZEND_UNREACHABLE();
|
|
}
|
|
|
|
ir_CALL_2(IR_VOID, ir_CONST_FC_FUNC(func), prop_ref, ref);
|
|
} else {
|
|
switch (opline->opcode) {
|
|
case ZEND_PRE_INC_OBJ:
|
|
func = zend_jit_pre_inc_typed_prop;
|
|
break;
|
|
case ZEND_PRE_DEC_OBJ:
|
|
func = zend_jit_pre_dec_typed_prop;
|
|
break;
|
|
case ZEND_POST_INC_OBJ:
|
|
func = zend_jit_post_inc_typed_prop;
|
|
break;
|
|
case ZEND_POST_DEC_OBJ:
|
|
func = zend_jit_post_dec_typed_prop;
|
|
break;
|
|
default:
|
|
ZEND_UNREACHABLE();
|
|
}
|
|
ir_CALL_3(IR_VOID, ir_CONST_FC_FUNC(func),
|
|
prop_ref,
|
|
ref,
|
|
jit_ZVAL_ADDR(jit, res_addr));
|
|
}
|
|
ir_END_list(end_inputs);
|
|
}
|
|
}
|
|
|
|
if (!prop_info || !ZEND_TYPE_IS_SET(prop_info->type)) {
|
|
uint32_t var_info = MAY_BE_ANY|MAY_BE_REF|MAY_BE_RC1|MAY_BE_RCN;
|
|
zend_jit_addr var_addr = prop_addr;
|
|
ir_ref if_long = IR_UNUSED;
|
|
ir_ref if_overflow = IR_UNUSED;
|
|
|
|
if (use_prop_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;
|
|
}
|
|
|
|
jit_guard_Z_TYPE(jit, prop_addr, prop_type, exit_addr);
|
|
var_info = (1 << prop_type) | (var_info & ~(MAY_BE_ANY|MAY_BE_UNDEF|MAY_BE_REF));
|
|
}
|
|
|
|
if (var_info & MAY_BE_REF) {
|
|
const void *func;
|
|
ir_ref if_ref, if_typed, noref_path, ref_path, reference, ref;
|
|
|
|
if_ref = jit_if_Z_TYPE(jit, prop_addr, IS_REFERENCE);
|
|
ir_IF_FALSE(if_ref);
|
|
noref_path = ir_END();
|
|
ir_IF_TRUE(if_ref);
|
|
|
|
reference = jit_Z_PTR(jit, var_addr);
|
|
ref = ir_ADD_OFFSET(reference, offsetof(zend_reference, val));
|
|
if_typed = jit_if_TYPED_REF(jit, reference);
|
|
ir_IF_FALSE(if_typed);
|
|
ref_path = ir_END();
|
|
ir_IF_TRUE_cold(if_typed);
|
|
|
|
switch (opline->opcode) {
|
|
case ZEND_PRE_INC_OBJ:
|
|
func = zend_jit_pre_inc_typed_ref;
|
|
break;
|
|
case ZEND_PRE_DEC_OBJ:
|
|
func = zend_jit_pre_dec_typed_ref;
|
|
break;
|
|
case ZEND_POST_INC_OBJ:
|
|
func = zend_jit_post_inc_typed_ref;
|
|
break;
|
|
case ZEND_POST_DEC_OBJ:
|
|
func = zend_jit_post_dec_typed_ref;
|
|
break;
|
|
default:
|
|
ZEND_UNREACHABLE();
|
|
}
|
|
|
|
may_throw = 1;
|
|
jit_SET_EX_OPLINE(jit, opline);
|
|
ir_CALL_2(IR_VOID, ir_CONST_FC_FUNC(func),
|
|
reference,
|
|
(opline->result_type == IS_UNUSED) ? IR_NULL : jit_ZVAL_ADDR(jit, res_addr));
|
|
|
|
ir_END_list(end_inputs);
|
|
|
|
ir_MERGE_2(noref_path, ref_path);
|
|
prop_ref = ir_PHI_2(IR_ADDR, prop_ref, ref);
|
|
var_addr = ZEND_ADDR_REF_ZVAL(prop_ref);
|
|
|
|
var_info &= ~MAY_BE_REF;
|
|
}
|
|
|
|
if (var_info & MAY_BE_LONG) {
|
|
ir_ref addr, ref;
|
|
|
|
if (var_info & (MAY_BE_ANY - MAY_BE_LONG)) {
|
|
if_long = jit_if_Z_TYPE(jit, var_addr, IS_LONG);
|
|
ir_IF_TRUE(if_long);
|
|
}
|
|
|
|
addr = jit_ZVAL_ADDR(jit, var_addr);
|
|
ref = ir_LOAD_L(addr);
|
|
if (opline->opcode == ZEND_POST_INC_OBJ || opline->opcode == ZEND_POST_DEC_OBJ) {
|
|
if (opline->result_type != IS_UNUSED) {
|
|
jit_set_Z_LVAL(jit, res_addr, ref);
|
|
jit_set_Z_TYPE_INFO(jit, res_addr, IS_LONG);
|
|
}
|
|
}
|
|
if (opline->opcode == ZEND_PRE_INC_OBJ || opline->opcode == ZEND_POST_INC_OBJ) {
|
|
ref = ir_ADD_OV_L(ref, ir_CONST_LONG(1));
|
|
} else {
|
|
ref = ir_SUB_OV_L(ref, ir_CONST_LONG(1));
|
|
}
|
|
|
|
ir_STORE(addr, ref);
|
|
if_overflow = ir_IF(ir_OVERFLOW(ref));
|
|
ir_IF_FALSE(if_overflow);
|
|
|
|
if (opline->opcode == ZEND_PRE_INC_OBJ || opline->opcode == ZEND_PRE_DEC_OBJ) {
|
|
if (opline->result_type != IS_UNUSED) {
|
|
jit_set_Z_LVAL(jit, res_addr, ref);
|
|
jit_set_Z_TYPE_INFO(jit, res_addr, IS_LONG);
|
|
}
|
|
}
|
|
ir_END_list(end_inputs);
|
|
}
|
|
|
|
if (var_info & (MAY_BE_ANY - MAY_BE_LONG)) {
|
|
if (var_info & (MAY_BE_ARRAY|MAY_BE_OBJECT|MAY_BE_RESOURCE)) {
|
|
may_throw = 1;
|
|
}
|
|
if (if_long) {
|
|
ir_IF_FALSE_cold(if_long);
|
|
}
|
|
if (opline->opcode == ZEND_POST_INC_OBJ || opline->opcode == ZEND_POST_DEC_OBJ) {
|
|
jit_ZVAL_COPY(jit, res_addr, -1, var_addr, var_info, 1);
|
|
}
|
|
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) {
|
|
ir_CALL_2(IR_VOID, ir_CONST_FC_FUNC(zend_jit_pre_inc),
|
|
jit_ZVAL_ADDR(jit, var_addr),
|
|
jit_ZVAL_ADDR(jit, res_addr));
|
|
} else {
|
|
ir_CALL_1(IR_VOID, ir_CONST_FC_FUNC(increment_function),
|
|
jit_ZVAL_ADDR(jit, var_addr));
|
|
}
|
|
} else {
|
|
if (opline->opcode == ZEND_PRE_DEC_OBJ && opline->result_type != IS_UNUSED) {
|
|
ir_CALL_2(IR_VOID, ir_CONST_FC_FUNC(zend_jit_pre_dec),
|
|
jit_ZVAL_ADDR(jit, var_addr),
|
|
jit_ZVAL_ADDR(jit, res_addr));
|
|
} else {
|
|
ir_CALL_1(IR_VOID, ir_CONST_FC_FUNC(decrement_function),
|
|
jit_ZVAL_ADDR(jit, var_addr));
|
|
}
|
|
}
|
|
|
|
ir_END_list(end_inputs);
|
|
}
|
|
if (var_info & MAY_BE_LONG) {
|
|
ir_IF_TRUE_cold(if_overflow);
|
|
if (opline->opcode == ZEND_PRE_INC_OBJ || opline->opcode == ZEND_POST_INC_OBJ) {
|
|
#if SIZEOF_ZEND_LONG == 4
|
|
jit_set_Z_LVAL(jit, var_addr, ir_CONST_LONG(0));
|
|
jit_set_Z_W2(jit, var_addr, ir_CONST_U32(0x41e00000));
|
|
#else
|
|
jit_set_Z_LVAL(jit, var_addr, ir_CONST_LONG(0x43e0000000000000));
|
|
#endif
|
|
jit_set_Z_TYPE_INFO(jit, var_addr, IS_DOUBLE);
|
|
if (opline->opcode == ZEND_PRE_INC_OBJ && opline->result_type != IS_UNUSED) {
|
|
#if SIZEOF_ZEND_LONG == 4
|
|
jit_set_Z_LVAL(jit, res_addr, ir_CONST_LONG(0));
|
|
jit_set_Z_W2(jit, res_addr, ir_CONST_U32(0x41e00000));
|
|
#else
|
|
jit_set_Z_LVAL(jit, res_addr, ir_CONST_LONG(0x43e0000000000000));
|
|
#endif
|
|
jit_set_Z_TYPE_INFO(jit, res_addr, IS_DOUBLE);
|
|
}
|
|
} else {
|
|
#if SIZEOF_ZEND_LONG == 4
|
|
jit_set_Z_LVAL(jit, var_addr, ir_CONST_LONG(0x00200000));
|
|
jit_set_Z_W2(jit, var_addr, ir_CONST_U32(0xc1e00000));
|
|
#else
|
|
jit_set_Z_LVAL(jit, var_addr, ir_CONST_LONG(0xc3e0000000000000));
|
|
#endif
|
|
jit_set_Z_TYPE_INFO(jit, var_addr, IS_DOUBLE);
|
|
if (opline->opcode == ZEND_PRE_DEC_OBJ && opline->result_type != IS_UNUSED) {
|
|
#if SIZEOF_ZEND_LONG == 4
|
|
jit_set_Z_LVAL(jit, res_addr, ir_CONST_LONG(0x00200000));
|
|
jit_set_Z_W2(jit, res_addr, ir_CONST_U32(0xc1e00000));
|
|
#else
|
|
jit_set_Z_LVAL(jit, res_addr, ir_CONST_LONG(0xc3e0000000000000));
|
|
#endif
|
|
jit_set_Z_TYPE_INFO(jit, res_addr, IS_DOUBLE);
|
|
}
|
|
}
|
|
if (opline->result_type != IS_UNUSED
|
|
&& (opline->opcode == ZEND_PRE_INC_OBJ || opline->opcode == ZEND_PRE_DEC_OBJ)
|
|
&& prop_info
|
|
&& !ZEND_TYPE_IS_SET(prop_info->type)
|
|
&& (res_info & MAY_BE_GUARD)
|
|
&& (res_info & MAY_BE_LONG)) {
|
|
zend_jit_trace_stack *stack = JIT_G(current_frame)->stack;
|
|
uint32_t old_res_info = STACK_INFO(stack, EX_VAR_TO_NUM(opline->result.var));
|
|
int32_t exit_point;
|
|
const void *exit_addr;
|
|
|
|
SET_STACK_TYPE(stack, EX_VAR_TO_NUM(opline->result.var), IS_DOUBLE, 0);
|
|
exit_point = zend_jit_trace_get_exit_point(opline + 1, 0);
|
|
exit_addr = zend_jit_trace_get_exit_addr(exit_point);
|
|
if (!exit_addr) {
|
|
return 0;
|
|
}
|
|
SET_STACK_INFO(stack, EX_VAR_TO_NUM(opline->result.var), old_res_info);
|
|
ssa->var_info[ssa_op->result_def].type = res_info & ~MAY_BE_GUARD;
|
|
jit_SIDE_EXIT(jit, ir_CONST_ADDR(exit_addr));
|
|
} else {
|
|
ir_END_list(end_inputs);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (slow_inputs) {
|
|
const void *func;
|
|
|
|
ir_MERGE_list(slow_inputs);
|
|
|
|
// JIT: zend_jit_pre_inc_obj_helper(zobj, name, CACHE_ADDR(opline->extended_value), result);
|
|
switch (opline->opcode) {
|
|
case ZEND_PRE_INC_OBJ:
|
|
func = zend_jit_pre_inc_obj_helper;
|
|
break;
|
|
case ZEND_PRE_DEC_OBJ:
|
|
func = zend_jit_pre_dec_obj_helper;
|
|
break;
|
|
case ZEND_POST_INC_OBJ:
|
|
func = zend_jit_post_inc_obj_helper;
|
|
break;
|
|
case ZEND_POST_DEC_OBJ:
|
|
func = zend_jit_post_dec_obj_helper;
|
|
break;
|
|
default:
|
|
ZEND_UNREACHABLE();
|
|
}
|
|
|
|
may_throw = 1;
|
|
jit_SET_EX_OPLINE(jit, opline);
|
|
ir_ref run_time_cache = ir_LOAD_A(jit_EX(run_time_cache));
|
|
ir_CALL_4(IR_VOID, ir_CONST_FC_FUNC(func),
|
|
obj_ref,
|
|
ir_CONST_ADDR(name),
|
|
ir_ADD_OFFSET(run_time_cache, opline->extended_value & ~ZEND_FETCH_OBJ_FLAGS),
|
|
(opline->result_type == IS_UNUSED) ? IR_NULL : jit_ZVAL_ADDR(jit, res_addr));
|
|
|
|
ir_END_list(end_inputs);
|
|
}
|
|
|
|
if (end_inputs) {
|
|
ir_MERGE_list(end_inputs);
|
|
}
|
|
|
|
if ((opline->op1_type & (IS_VAR|IS_TMP_VAR)) && !delayed_fetch_this && !op1_indirect) {
|
|
if ((op1_info & MAY_HAVE_DTOR) && (op1_info & MAY_BE_RC1)) {
|
|
may_throw = 1;
|
|
}
|
|
jit_FREE_OP(jit, opline->op1_type, opline->op1, op1_info, opline);
|
|
}
|
|
|
|
if (may_throw) {
|
|
zend_jit_check_exception(jit);
|
|
}
|
|
|
|
return 1;
|
|
}
|
|
|
|
static int zend_jit_fetch_static_prop(zend_jit_ctx *jit, const zend_op *opline, const zend_op_array *op_array)
|
|
{
|
|
zend_jit_addr res_addr = RES_ADDR();
|
|
uint32_t cache_slot = opline->extended_value & ~ZEND_FETCH_OBJ_FLAGS;
|
|
uint32_t flags;
|
|
ir_ref ref, ref2, if_cached, fast_path, cold_path, prop_info_ref, if_typed, if_def;
|
|
int fetch_type;
|
|
zend_property_info *known_prop_info = NULL;
|
|
zend_class_entry *ce;
|
|
|
|
ce = zend_get_known_class(op_array, opline, opline->op2_type, opline->op2);
|
|
if (ce) {
|
|
zval *zv = RT_CONSTANT(opline, opline->op1);
|
|
zend_string *prop_name;
|
|
|
|
ZEND_ASSERT(Z_TYPE_P(zv) == IS_STRING);
|
|
prop_name = Z_STR_P(zv);
|
|
zv = zend_hash_find(&ce->properties_info, prop_name);
|
|
if (zv) {
|
|
zend_property_info *prop_info = Z_PTR_P(zv);
|
|
|
|
if (prop_info->flags & ZEND_ACC_STATIC) {
|
|
if (prop_info->ce == op_array->scope
|
|
|| (prop_info->flags & ZEND_ACC_PUBLIC)
|
|
|| ((prop_info->flags & ZEND_ACC_PROTECTED)
|
|
&& op_array->scope
|
|
&& instanceof_function_slow(op_array->scope, prop_info->ce))) {
|
|
known_prop_info = prop_info;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
switch (opline->opcode) {
|
|
case ZEND_FETCH_STATIC_PROP_R:
|
|
case ZEND_FETCH_STATIC_PROP_FUNC_ARG:
|
|
fetch_type = BP_VAR_R;
|
|
break;
|
|
case ZEND_FETCH_STATIC_PROP_IS:
|
|
fetch_type = BP_VAR_IS;
|
|
break;
|
|
case ZEND_FETCH_STATIC_PROP_W:
|
|
fetch_type = BP_VAR_W;
|
|
break;
|
|
case ZEND_FETCH_STATIC_PROP_RW:
|
|
fetch_type = BP_VAR_RW;
|
|
break;
|
|
case ZEND_FETCH_STATIC_PROP_UNSET:
|
|
fetch_type = BP_VAR_UNSET;
|
|
break;
|
|
EMPTY_SWITCH_DEFAULT_CASE();
|
|
}
|
|
|
|
// JIT: result = CACHED_PTR(cache_slot + sizeof(void *));
|
|
ref = ir_LOAD_A(
|
|
ir_ADD_OFFSET(ir_LOAD_A(jit_EX(run_time_cache)), cache_slot + sizeof(void*)));
|
|
|
|
// JIT: if (result)
|
|
if_cached = ir_IF(ref);
|
|
ir_IF_TRUE(if_cached);
|
|
|
|
if (fetch_type == BP_VAR_R || fetch_type == BP_VAR_RW) {
|
|
if (!known_prop_info || ZEND_TYPE_IS_SET(known_prop_info->type)) {
|
|
ir_ref merge = IR_UNUSED;
|
|
|
|
// JIT: if (UNEXPECTED(Z_TYPE_P(result) == IS_UNDEF)
|
|
if_typed = IR_UNUSED;
|
|
if_def = ir_IF(jit_Z_TYPE_ref(jit, ref));
|
|
ir_IF_FALSE_cold(if_def);
|
|
if (!known_prop_info) {
|
|
// JIT: if (ZEND_TYPE_IS_SET(property_info->type))
|
|
prop_info_ref = ir_LOAD_L(
|
|
ir_ADD_OFFSET(ir_LOAD_A(jit_EX(run_time_cache)), cache_slot + sizeof(void*) * 2));
|
|
if_typed = ir_IF(ir_AND_U32(
|
|
ir_LOAD_U32(ir_ADD_OFFSET(prop_info_ref, offsetof(zend_property_info, type.type_mask))),
|
|
ir_CONST_U32(_ZEND_TYPE_MASK)));
|
|
ir_IF_FALSE(if_typed);
|
|
ir_END_list(merge);
|
|
ir_IF_TRUE(if_typed);
|
|
}
|
|
// JIT: zend_throw_error(NULL, "Typed static property %s::$%s must not be accessed before initialization",
|
|
// ZSTR_VAL(property_info->ce->name),
|
|
// zend_get_unmangled_property_name(property_info->name));
|
|
jit_SET_EX_OPLINE(jit, opline);
|
|
ir_CALL(IR_VOID, ir_CONST_FC_FUNC(zend_jit_uninit_static_prop));
|
|
ir_IJMP(jit_STUB_ADDR(jit, jit_stub_exception_handler_undef));
|
|
|
|
ir_IF_TRUE(if_def);
|
|
if (!known_prop_info) {
|
|
ir_END_list(merge);
|
|
ir_MERGE_list(merge);
|
|
}
|
|
}
|
|
} else if (fetch_type == BP_VAR_W) {
|
|
flags = opline->extended_value & ZEND_FETCH_OBJ_FLAGS;
|
|
if (flags && (!known_prop_info || ZEND_TYPE_IS_SET(known_prop_info->type))) {
|
|
ir_ref merge = IR_UNUSED;
|
|
|
|
if (!known_prop_info) {
|
|
// JIT: if (ZEND_TYPE_IS_SET(property_info->type))
|
|
prop_info_ref = ir_LOAD_L(
|
|
ir_ADD_OFFSET(ir_LOAD_A(jit_EX(run_time_cache)), cache_slot + sizeof(void*) * 2));
|
|
if_typed = ir_IF(ir_AND_U32(
|
|
ir_LOAD_U32(ir_ADD_OFFSET(prop_info_ref, offsetof(zend_property_info, type.type_mask))),
|
|
ir_CONST_U32(_ZEND_TYPE_MASK)));
|
|
ir_IF_FALSE(if_typed);
|
|
ir_END_list(merge);
|
|
ir_IF_TRUE(if_typed);
|
|
} else {
|
|
prop_info_ref = ir_CONST_ADDR(known_prop_info);
|
|
}
|
|
|
|
// JIT: zend_handle_fetch_obj_flags(NULL, *retval, NULL, property_info, flags);
|
|
ir_ref if_ok = ir_IF(ir_CALL_5(IR_BOOL, ir_CONST_FUNC(zend_handle_fetch_obj_flags),
|
|
IR_NULL, ref, IR_NULL, prop_info_ref, ir_CONST_U32(flags)));
|
|
ir_IF_FALSE_cold(if_ok);
|
|
ir_IJMP(jit_STUB_ADDR(jit, jit_stub_exception_handler_undef));
|
|
ir_IF_TRUE(if_ok);
|
|
if (!known_prop_info) {
|
|
ir_END_list(merge);
|
|
ir_MERGE_list(merge);
|
|
}
|
|
}
|
|
}
|
|
|
|
fast_path = ir_END();
|
|
|
|
ir_IF_FALSE_cold(if_cached);
|
|
jit_SET_EX_OPLINE(jit, opline);
|
|
ref2 = ir_CALL_2(IR_ADDR, ir_CONST_FC_FUNC(zend_fetch_static_property), jit_FP(jit), ir_CONST_I32(fetch_type));
|
|
zend_jit_check_exception_undef_result(jit, opline);
|
|
cold_path = ir_END();
|
|
|
|
ir_MERGE_2(fast_path, cold_path);
|
|
ref = ir_PHI_2(IR_ADDR, ref, ref2);
|
|
|
|
if (fetch_type == BP_VAR_R || fetch_type == BP_VAR_IS) {
|
|
// JIT: ZVAL_COPY_DEREF(EX_VAR(opline->result.var), result);
|
|
if (!zend_jit_zval_copy_deref(jit, res_addr, ZEND_ADDR_REF_ZVAL(ref),
|
|
jit_Z_TYPE_INFO_ref(jit, ref))) {
|
|
return 0;
|
|
}
|
|
} else {
|
|
// JIT: ZVAL_INDIRECT(EX_VAR(opline->result.var), result);
|
|
jit_set_Z_PTR(jit, res_addr, ref);
|
|
jit_set_Z_TYPE_INFO(jit, res_addr, IS_INDIRECT);
|
|
}
|
|
|
|
return 1;
|
|
}
|
|
|
|
static int zend_jit_switch(zend_jit_ctx *jit, 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;
|
|
ir_refs *slow_inputs;
|
|
|
|
ir_refs_init(slow_inputs, 8);
|
|
|
|
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_known_hash(jumptable, Z_STR_P(zv));
|
|
}
|
|
} 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_known_hash(jumptable, Z_STR_P(zv));
|
|
}
|
|
} 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];
|
|
}
|
|
_zend_jit_add_predecessor_ref(jit, b, jit->b, ir_END());
|
|
jit->b = -1;
|
|
}
|
|
} else {
|
|
zend_ssa_op *ssa_op = ssa->ops ? &ssa->ops[opline - op_array->opcodes] : NULL;
|
|
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 *exit_addr;
|
|
const void *default_label = NULL;
|
|
zval *zv;
|
|
|
|
if (next_opline) {
|
|
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 (!default_label) {
|
|
return 0;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (opline->opcode == ZEND_SWITCH_LONG) {
|
|
if (op1_info & MAY_BE_LONG) {
|
|
const void *fallback_label = NULL;
|
|
|
|
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 (!fallback_label) {
|
|
return 0;
|
|
}
|
|
}
|
|
}
|
|
if (op1_info & MAY_BE_REF) {
|
|
ir_ref ref, if_long, fast_path, ref2;
|
|
|
|
ref = jit_ZVAL_ADDR(jit, op1_addr);
|
|
if_long = jit_if_Z_TYPE(jit, op1_addr, IS_LONG);
|
|
ir_IF_TRUE(if_long);
|
|
fast_path = ir_END();
|
|
ir_IF_FALSE_cold(if_long);
|
|
|
|
// JIT: ZVAL_DEREF(op)
|
|
if (fallback_label) {
|
|
jit_guard_Z_TYPE(jit, op1_addr, IS_REFERENCE, fallback_label);
|
|
} else {
|
|
ir_ref if_ref = jit_if_Z_TYPE(jit, op1_addr, IS_REFERENCE);
|
|
ir_IF_FALSE_cold(if_ref);
|
|
ir_refs_add(slow_inputs, ir_END());
|
|
ir_IF_TRUE(if_ref);
|
|
}
|
|
|
|
ref2 = ir_ADD_OFFSET(jit_Z_PTR(jit, op1_addr), offsetof(zend_reference, val));
|
|
op1_addr = ZEND_ADDR_REF_ZVAL(ref2);
|
|
|
|
if (fallback_label) {
|
|
jit_guard_Z_TYPE(jit, op1_addr, IS_LONG, fallback_label);
|
|
} else {
|
|
if_long = jit_if_Z_TYPE(jit, op1_addr, IS_LONG);
|
|
ir_IF_FALSE_cold(if_long);
|
|
ir_refs_add(slow_inputs, ir_END());
|
|
ir_IF_TRUE(if_long);
|
|
}
|
|
|
|
ir_MERGE_2(fast_path, ir_END());
|
|
ref = ir_PHI_2(IR_ADDR, ref, ref2);
|
|
op1_addr = ZEND_ADDR_REF_ZVAL(ref);
|
|
} else if (op1_info & ((MAY_BE_ANY|MAY_BE_UNDEF)-MAY_BE_LONG)) {
|
|
if (fallback_label) {
|
|
jit_guard_Z_TYPE(jit, op1_addr, IS_LONG, fallback_label);
|
|
} else {
|
|
ir_ref if_long = jit_if_Z_TYPE(jit, op1_addr, IS_LONG);
|
|
ir_IF_FALSE_cold(if_long);
|
|
ir_refs_add(slow_inputs, ir_END());
|
|
ir_IF_TRUE(if_long);
|
|
}
|
|
}
|
|
ir_ref ref = jit_Z_LVAL(jit, op1_addr);
|
|
|
|
if (!HT_IS_PACKED(jumptable)) {
|
|
ref = ir_CALL_2(IR_LONG, ir_CONST_FC_FUNC(zend_hash_index_find),
|
|
ir_CONST_ADDR(jumptable), ref);
|
|
ref = ir_SUB_L(ref, ir_CONST_LONG((uintptr_t)jumptable->arData));
|
|
/* Signed DIV by power of 2 may be optimized into SHR only for positive operands */
|
|
if (sizeof(Bucket) == 32) {
|
|
ref = ir_SHR_L(ref, ir_CONST_LONG(5));
|
|
} else {
|
|
ref = ir_DIV_L(ref, ir_CONST_LONG(sizeof(Bucket)));
|
|
}
|
|
}
|
|
ref = ir_SWITCH(ref);
|
|
|
|
if (next_opline) {
|
|
ir_ref continue_list = IR_UNUSED;
|
|
|
|
ZEND_HASH_FOREACH_VAL(jumptable, zv) {
|
|
ir_ref idx;
|
|
target = ZEND_OFFSET_TO_OPLINE(opline, Z_LVAL_P(zv));
|
|
|
|
if (HT_IS_PACKED(jumptable)) {
|
|
idx = ir_CONST_LONG(zv - jumptable->arPacked);
|
|
} else {
|
|
idx = ir_CONST_LONG((Bucket*)zv - jumptable->arData);
|
|
}
|
|
ir_CASE_VAL(ref, idx);
|
|
if (target == next_opline) {
|
|
ir_END_list(continue_list);
|
|
} else {
|
|
exit_point = zend_jit_trace_get_exit_point(target, 0);
|
|
exit_addr = zend_jit_trace_get_exit_addr(exit_point);
|
|
if (!exit_addr) {
|
|
return 0;
|
|
}
|
|
jit_SIDE_EXIT(jit, ir_CONST_ADDR(exit_addr));
|
|
}
|
|
} ZEND_HASH_FOREACH_END();
|
|
|
|
ir_CASE_DEFAULT(ref);
|
|
if (next_opline == default_opline) {
|
|
ir_END_list(continue_list);
|
|
} else {
|
|
jit_SIDE_EXIT(jit, ir_CONST_ADDR(default_label));
|
|
}
|
|
if (continue_list) {
|
|
ir_MERGE_list(continue_list);
|
|
} else {
|
|
ZEND_ASSERT(slow_inputs->count);
|
|
ir_MERGE_N(slow_inputs->count, slow_inputs->refs);
|
|
}
|
|
} else {
|
|
ZEND_HASH_FOREACH_VAL(jumptable, zv) {
|
|
target = ZEND_OFFSET_TO_OPLINE(opline, Z_LVAL_P(zv));
|
|
b = ssa->cfg.map[target - op_array->opcodes];
|
|
_zend_jit_add_predecessor_ref(jit, b, jit->b, ref);
|
|
} ZEND_HASH_FOREACH_END();
|
|
|
|
_zend_jit_add_predecessor_ref(jit, default_b, jit->b, ref);
|
|
if (slow_inputs->count) {
|
|
ir_MERGE_N(slow_inputs->count, slow_inputs->refs);
|
|
_zend_jit_add_predecessor_ref(jit, jit->b + 1, jit->b, ir_END());
|
|
}
|
|
jit->b = -1;
|
|
}
|
|
} else if (!next_opline) {
|
|
_zend_jit_add_predecessor_ref(jit, jit->b + 1, jit->b, ir_END());
|
|
jit->b = -1;
|
|
}
|
|
} else if (opline->opcode == ZEND_SWITCH_STRING) {
|
|
if (op1_info & MAY_BE_STRING) {
|
|
const void *fallback_label = NULL;
|
|
|
|
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 (!fallback_label) {
|
|
return 0;
|
|
}
|
|
}
|
|
}
|
|
if (op1_info & MAY_BE_REF) {
|
|
ir_ref ref, if_string, fast_path, ref2;
|
|
|
|
ref = jit_ZVAL_ADDR(jit, op1_addr);
|
|
if_string = jit_if_Z_TYPE(jit, op1_addr, IS_STRING);
|
|
ir_IF_TRUE(if_string);
|
|
fast_path = ir_END();
|
|
ir_IF_FALSE_cold(if_string);
|
|
|
|
// JIT: ZVAL_DEREF(op)
|
|
if (fallback_label) {
|
|
jit_guard_Z_TYPE(jit, op1_addr, IS_REFERENCE, fallback_label);
|
|
} else {
|
|
ir_ref if_ref = jit_if_Z_TYPE(jit, op1_addr, IS_STRING);
|
|
ir_IF_FALSE_cold(if_ref);
|
|
ir_refs_add(slow_inputs, ir_END());
|
|
ir_IF_TRUE(if_ref);
|
|
}
|
|
|
|
ref2 = ir_ADD_OFFSET(jit_Z_PTR(jit, op1_addr), offsetof(zend_reference, val));
|
|
op1_addr = ZEND_ADDR_REF_ZVAL(ref2);
|
|
|
|
if (fallback_label) {
|
|
jit_guard_Z_TYPE(jit, op1_addr, IS_LONG, fallback_label);
|
|
} else {
|
|
if_string = jit_if_Z_TYPE(jit, op1_addr, IS_STRING);
|
|
ir_IF_FALSE_cold(if_string);
|
|
ir_refs_add(slow_inputs, ir_END());
|
|
ir_IF_TRUE(if_string);
|
|
}
|
|
|
|
ir_MERGE_2(fast_path, ir_END());
|
|
ref = ir_PHI_2(IR_ADDR, ref, ref2);
|
|
op1_addr = ZEND_ADDR_REF_ZVAL(ref);
|
|
} else if (op1_info & ((MAY_BE_ANY|MAY_BE_UNDEF)-MAY_BE_STRING)) {
|
|
if (fallback_label) {
|
|
jit_guard_Z_TYPE(jit, op1_addr, IS_STRING, fallback_label);
|
|
} else {
|
|
ir_ref if_string = jit_if_Z_TYPE(jit, op1_addr, IS_STRING);
|
|
ir_IF_FALSE_cold(if_string);
|
|
ir_refs_add(slow_inputs, ir_END());
|
|
ir_IF_TRUE(if_string);
|
|
}
|
|
}
|
|
|
|
ir_ref ref = jit_Z_PTR(jit, op1_addr);
|
|
ref = ir_CALL_2(IR_LONG, ir_CONST_FC_FUNC(zend_hash_find),
|
|
ir_CONST_ADDR(jumptable), ref);
|
|
ref = ir_SUB_L(ref, ir_CONST_LONG((uintptr_t)jumptable->arData));
|
|
/* Signed DIV by power of 2 may be optimized into SHR only for positive operands */
|
|
if (sizeof(Bucket) == 32) {
|
|
ref = ir_SHR_L(ref, ir_CONST_LONG(5));
|
|
} else {
|
|
ref = ir_DIV_L(ref, ir_CONST_LONG(sizeof(Bucket)));
|
|
}
|
|
ref = ir_SWITCH(ref);
|
|
|
|
if (next_opline) {
|
|
ir_ref continue_list = IR_UNUSED;
|
|
|
|
ZEND_HASH_FOREACH_VAL(jumptable, zv) {
|
|
ir_ref idx;
|
|
target = ZEND_OFFSET_TO_OPLINE(opline, Z_LVAL_P(zv));
|
|
|
|
if (HT_IS_PACKED(jumptable)) {
|
|
idx = ir_CONST_LONG(zv - jumptable->arPacked);
|
|
} else {
|
|
idx = ir_CONST_LONG((Bucket*)zv - jumptable->arData);
|
|
}
|
|
ir_CASE_VAL(ref, idx);
|
|
if (target == next_opline) {
|
|
ir_END_list(continue_list);
|
|
} else {
|
|
exit_point = zend_jit_trace_get_exit_point(target, 0);
|
|
exit_addr = zend_jit_trace_get_exit_addr(exit_point);
|
|
if (!exit_addr) {
|
|
return 0;
|
|
}
|
|
jit_SIDE_EXIT(jit, ir_CONST_ADDR(exit_addr));
|
|
}
|
|
} ZEND_HASH_FOREACH_END();
|
|
|
|
ir_CASE_DEFAULT(ref);
|
|
if (next_opline == default_opline) {
|
|
ir_END_list(continue_list);
|
|
} else {
|
|
jit_SIDE_EXIT(jit, ir_CONST_ADDR(default_label));
|
|
}
|
|
if (continue_list) {
|
|
ir_MERGE_list(continue_list);
|
|
} else {
|
|
ZEND_ASSERT(slow_inputs->count);
|
|
ir_MERGE_N(slow_inputs->count, slow_inputs->refs);
|
|
}
|
|
} else {
|
|
ZEND_HASH_FOREACH_VAL(jumptable, zv) {
|
|
target = ZEND_OFFSET_TO_OPLINE(opline, Z_LVAL_P(zv));
|
|
b = ssa->cfg.map[target - op_array->opcodes];
|
|
_zend_jit_add_predecessor_ref(jit, b, jit->b, ref);
|
|
} ZEND_HASH_FOREACH_END();
|
|
_zend_jit_add_predecessor_ref(jit, default_b, jit->b, ref);
|
|
if (slow_inputs->count) {
|
|
ir_MERGE_N(slow_inputs->count, slow_inputs->refs);
|
|
_zend_jit_add_predecessor_ref(jit, jit->b + 1, jit->b, ir_END());
|
|
}
|
|
jit->b = -1;
|
|
}
|
|
} else if (!next_opline) {
|
|
_zend_jit_add_predecessor_ref(jit, jit->b + 1, jit->b, ir_END());
|
|
jit->b = -1;
|
|
}
|
|
} else if (opline->opcode == ZEND_MATCH) {
|
|
ir_ref if_type = IR_UNUSED, default_input_list = IR_UNUSED, ref = IR_UNUSED;
|
|
ir_ref continue_list = IR_UNUSED;
|
|
|
|
if (op1_info & (MAY_BE_LONG|MAY_BE_STRING)) {
|
|
ir_ref long_path = IR_UNUSED;
|
|
|
|
if (op1_info & MAY_BE_REF) {
|
|
op1_addr = jit_ZVAL_DEREF(jit, op1_addr);
|
|
}
|
|
if (op1_info & MAY_BE_LONG) {
|
|
if (op1_info & ((MAY_BE_ANY|MAY_BE_UNDEF)-MAY_BE_LONG)) {
|
|
if (op1_info & (MAY_BE_STRING|MAY_BE_UNDEF)) {
|
|
if_type = jit_if_Z_TYPE(jit, op1_addr, IS_LONG);
|
|
ir_IF_TRUE(if_type);
|
|
} else if (default_label) {
|
|
jit_guard_Z_TYPE(jit, op1_addr, IS_LONG, default_label);
|
|
} else if (next_opline) {
|
|
ir_ref if_type = jit_if_Z_TYPE(jit, op1_addr, IS_LONG);
|
|
ir_IF_FALSE(if_type);
|
|
ir_END_list(continue_list);
|
|
ir_IF_TRUE(if_type);
|
|
} else {
|
|
ir_ref if_type = jit_if_Z_TYPE(jit, op1_addr, IS_LONG);
|
|
ir_IF_FALSE(if_type);
|
|
ir_END_list(default_input_list);
|
|
ir_IF_TRUE(if_type);
|
|
}
|
|
}
|
|
ref = jit_Z_LVAL(jit, op1_addr);
|
|
ref = ir_CALL_2(IR_LONG, ir_CONST_FC_FUNC(zend_hash_index_find),
|
|
ir_CONST_ADDR(jumptable), ref);
|
|
if (op1_info & MAY_BE_STRING) {
|
|
long_path = ir_END();
|
|
}
|
|
}
|
|
if (op1_info & MAY_BE_STRING) {
|
|
if (if_type) {
|
|
ir_IF_FALSE(if_type);
|
|
if_type = IS_UNUSED;
|
|
}
|
|
if (op1_info & ((MAY_BE_ANY|MAY_BE_UNDEF)-(MAY_BE_LONG|MAY_BE_STRING))) {
|
|
if (op1_info & MAY_BE_UNDEF) {
|
|
if_type = jit_if_Z_TYPE(jit, op1_addr, IS_STRING);
|
|
ir_IF_TRUE(if_type);
|
|
} else if (default_label) {
|
|
jit_guard_Z_TYPE(jit, op1_addr, IS_STRING, default_label);
|
|
} else if (next_opline) {
|
|
ir_ref if_type = jit_if_Z_TYPE(jit, op1_addr, IS_STRING);
|
|
ir_IF_FALSE(if_type);
|
|
ir_END_list(continue_list);
|
|
ir_IF_TRUE(if_type);
|
|
} else {
|
|
ir_ref if_type = jit_if_Z_TYPE(jit, op1_addr, IS_STRING);
|
|
ir_IF_FALSE(if_type);
|
|
ir_END_list(default_input_list);
|
|
ir_IF_TRUE(if_type);
|
|
}
|
|
}
|
|
ir_ref ref2 = jit_Z_PTR(jit, op1_addr);
|
|
ref2 = ir_CALL_2(IR_LONG, ir_CONST_FC_FUNC(zend_hash_find),
|
|
ir_CONST_ADDR(jumptable), ref2);
|
|
if (op1_info & MAY_BE_LONG) {
|
|
ir_MERGE_WITH(long_path);
|
|
ref = ir_PHI_2(IR_LONG, ref2, ref);
|
|
} else {
|
|
ref = ref2;
|
|
}
|
|
}
|
|
|
|
ref = ir_SUB_L(ref, ir_CONST_LONG((uintptr_t)jumptable->arData));
|
|
/* Signed DIV by power of 2 may be optimized into SHR only for positive operands */
|
|
if (HT_IS_PACKED(jumptable)) {
|
|
ZEND_ASSERT(sizeof(zval) == 16);
|
|
ref = ir_SHR_L(ref, ir_CONST_LONG(4));
|
|
} else {
|
|
if (sizeof(Bucket) == 32) {
|
|
ref = ir_SHR_L(ref, ir_CONST_LONG(5));
|
|
} else {
|
|
ref = ir_DIV_L(ref, ir_CONST_LONG(sizeof(Bucket)));
|
|
}
|
|
}
|
|
ref = ir_SWITCH(ref);
|
|
|
|
if (next_opline) {
|
|
ZEND_HASH_FOREACH_VAL(jumptable, zv) {
|
|
ir_ref idx;
|
|
target = ZEND_OFFSET_TO_OPLINE(opline, Z_LVAL_P(zv));
|
|
|
|
if (HT_IS_PACKED(jumptable)) {
|
|
idx = ir_CONST_LONG(zv - jumptable->arPacked);
|
|
} else {
|
|
idx = ir_CONST_LONG((Bucket*)zv - jumptable->arData);
|
|
}
|
|
ir_CASE_VAL(ref, idx);
|
|
if (target == next_opline) {
|
|
ir_END_list(continue_list);
|
|
} else {
|
|
exit_point = zend_jit_trace_get_exit_point(target, 0);
|
|
exit_addr = zend_jit_trace_get_exit_addr(exit_point);
|
|
if (!exit_addr) {
|
|
return 0;
|
|
}
|
|
jit_SIDE_EXIT(jit, ir_CONST_ADDR(exit_addr));
|
|
}
|
|
} ZEND_HASH_FOREACH_END();
|
|
|
|
ir_CASE_DEFAULT(ref);
|
|
if (next_opline == default_opline) {
|
|
ir_END_list(continue_list);
|
|
} else {
|
|
jit_SIDE_EXIT(jit, ir_CONST_ADDR(default_label));
|
|
}
|
|
} else {
|
|
ZEND_HASH_FOREACH_VAL(jumptable, zv) {
|
|
target = ZEND_OFFSET_TO_OPLINE(opline, Z_LVAL_P(zv));
|
|
b = ssa->cfg.map[target - op_array->opcodes];
|
|
_zend_jit_add_predecessor_ref(jit, b, jit->b, ref);
|
|
} ZEND_HASH_FOREACH_END();
|
|
_zend_jit_add_predecessor_ref(jit, default_b, jit->b, ref);
|
|
}
|
|
} else if (!(op1_info & MAY_BE_UNDEF)) {
|
|
if (next_opline) {
|
|
if (next_opline == default_opline) {
|
|
ir_END_list(continue_list);
|
|
} else {
|
|
jit_SIDE_EXIT(jit, ir_CONST_ADDR(default_label));
|
|
}
|
|
} else {
|
|
_zend_jit_add_predecessor_ref(jit, default_b, jit->b, ir_END());
|
|
}
|
|
}
|
|
|
|
if (op1_info & MAY_BE_UNDEF) {
|
|
if (if_type) {
|
|
ir_IF_FALSE(if_type);
|
|
if_type = IS_UNUSED;
|
|
}
|
|
if (op1_info & (MAY_BE_ANY-(MAY_BE_LONG|MAY_BE_STRING))) {
|
|
if (default_label) {
|
|
jit_guard_Z_TYPE(jit, op1_addr, IS_UNDEF, default_label);
|
|
} else if (next_opline) {
|
|
ir_ref if_def = ir_IF(jit_Z_TYPE(jit, op1_addr));
|
|
ir_IF_TRUE(if_def);
|
|
ir_END_list(continue_list);
|
|
ir_IF_FALSE_cold(if_def);
|
|
} else {
|
|
ir_ref if_def = ir_IF(jit_Z_TYPE(jit, op1_addr));
|
|
ir_IF_TRUE(if_def);
|
|
ir_END_list(default_input_list);
|
|
ir_IF_FALSE_cold(if_def);
|
|
}
|
|
}
|
|
|
|
jit_SET_EX_OPLINE(jit, opline);
|
|
ir_CALL_1(IR_VOID, ir_CONST_FC_FUNC(zend_jit_undefined_op_helper),
|
|
ir_CONST_U32(opline->op1.var));
|
|
zend_jit_check_exception_undef_result(jit, opline);
|
|
if (default_label) {
|
|
jit_SIDE_EXIT(jit, ir_CONST_ADDR(default_label));
|
|
} else if (next_opline) {
|
|
ir_END_list(continue_list);
|
|
} else {
|
|
ir_END_list(default_input_list);
|
|
}
|
|
}
|
|
if (next_opline) {
|
|
ZEND_ASSERT(continue_list);
|
|
ir_MERGE_list(continue_list);
|
|
} else {
|
|
if (default_input_list) {
|
|
if (jit->ctx.ir_base[ref].op == IR_SWITCH) {
|
|
ZEND_ASSERT(jit->ctx.ir_base[ref].op3 == IR_UNUSED);
|
|
jit->ctx.ir_base[ref].op3 = default_input_list;
|
|
} else {
|
|
ir_MERGE_list(default_input_list);
|
|
_zend_jit_add_predecessor_ref(jit, default_b, jit->b, ir_END());
|
|
}
|
|
}
|
|
jit->b = -1;
|
|
}
|
|
} else {
|
|
ZEND_UNREACHABLE();
|
|
}
|
|
}
|
|
return 1;
|
|
}
|
|
|
|
static int zend_jit_start(zend_jit_ctx *jit, const zend_op_array *op_array, zend_ssa *ssa)
|
|
{
|
|
int i, count;
|
|
zend_basic_block *bb;
|
|
|
|
zend_jit_init_ctx(jit, (zend_jit_vm_kind == ZEND_VM_KIND_CALL) ? 0 : (IR_START_BR_TARGET|IR_ENTRY_BR_TARGET));
|
|
|
|
jit->ctx.spill_base = ZREG_FP;
|
|
|
|
jit->op_array = jit->current_op_array = op_array;
|
|
jit->ssa = ssa;
|
|
jit->bb_start_ref = zend_arena_calloc(&CG(arena), ssa->cfg.blocks_count * 2, sizeof(ir_ref));
|
|
jit->bb_predecessors = jit->bb_start_ref + ssa->cfg.blocks_count;
|
|
|
|
count = 0;
|
|
for (i = 0, bb = ssa->cfg.blocks; i < ssa->cfg.blocks_count; i++, bb++) {
|
|
jit->bb_predecessors[i] = count;
|
|
count += bb->predecessors_count;
|
|
}
|
|
jit->bb_edges = zend_arena_calloc(&CG(arena), count, sizeof(ir_ref));
|
|
|
|
if (!GCC_GLOBAL_REGS) {
|
|
ir_ref ref = ir_PARAM(IR_ADDR, "execute_data", 1);
|
|
jit_STORE_FP(jit, ref);
|
|
jit->ctx.flags |= IR_FASTCALL_FUNC;
|
|
}
|
|
|
|
return 1;
|
|
}
|
|
|
|
static void *zend_jit_finish(zend_jit_ctx *jit)
|
|
{
|
|
void *entry;
|
|
size_t size;
|
|
zend_string *str = NULL;
|
|
|
|
if (JIT_G(debug) & (ZEND_JIT_DEBUG_ASM|ZEND_JIT_DEBUG_GDB|ZEND_JIT_DEBUG_PERF|ZEND_JIT_DEBUG_PERF_DUMP|
|
|
ZEND_JIT_DEBUG_IR_SRC|ZEND_JIT_DEBUG_IR_AFTER_SCCP|ZEND_JIT_DEBUG_IR_AFTER_SCCP|
|
|
ZEND_JIT_DEBUG_IR_AFTER_SCHEDULE|ZEND_JIT_DEBUG_IR_AFTER_REGS|ZEND_JIT_DEBUG_IR_FINAL|ZEND_JIT_DEBUG_IR_CODEGEN)) {
|
|
if (jit->name) {
|
|
str = zend_string_copy(jit->name);
|
|
} else {
|
|
str = zend_jit_func_name(jit->op_array);
|
|
}
|
|
}
|
|
|
|
if (jit->op_array) {
|
|
/* Only for function JIT */
|
|
_zend_jit_fix_merges(jit);
|
|
#if defined(IR_TARGET_AARCH64)
|
|
} else if (jit->trace) {
|
|
jit->ctx.deoptimization_exits = jit->trace->exit_count;
|
|
jit->ctx.get_exit_addr = zend_jit_trace_get_exit_addr;
|
|
#endif
|
|
} else {
|
|
#if defined(IR_TARGET_X86) || defined(IR_TARGET_X64)
|
|
jit->ctx.flags |= IR_GEN_CACHE_DEMOTE;
|
|
#endif
|
|
}
|
|
|
|
entry = zend_jit_ir_compile(&jit->ctx, &size, str ? ZSTR_VAL(str) : NULL);
|
|
if (entry) {
|
|
if (JIT_G(debug) & (ZEND_JIT_DEBUG_ASM|ZEND_JIT_DEBUG_GDB|ZEND_JIT_DEBUG_PERF|ZEND_JIT_DEBUG_PERF_DUMP)) {
|
|
#ifdef HAVE_CAPSTONE
|
|
if (JIT_G(debug) & ZEND_JIT_DEBUG_ASM) {
|
|
if (str) {
|
|
ir_disasm_add_symbol(ZSTR_VAL(str), (uintptr_t)entry, size);
|
|
}
|
|
ir_disasm(str ? ZSTR_VAL(str) : "unknown",
|
|
entry, size,
|
|
(JIT_G(debug) & ZEND_JIT_DEBUG_ASM_ADDR) != 0,
|
|
&jit->ctx, stderr);
|
|
}
|
|
#endif
|
|
#ifndef _WIN32
|
|
if (str) {
|
|
if (JIT_G(debug) & ZEND_JIT_DEBUG_GDB) {
|
|
uintptr_t sp_offset = 0;
|
|
|
|
// ir_mem_unprotect(entry, size);
|
|
if (!(jit->ctx.flags & IR_FUNCTION)
|
|
&& zend_jit_vm_kind == ZEND_VM_KIND_HYBRID) {
|
|
#if !defined(ZEND_WIN32) && !defined(IR_TARGET_AARCH64)
|
|
sp_offset = zend_jit_hybrid_vm_sp_adj;
|
|
#else
|
|
sp_offset = sizeof(void*);
|
|
#endif
|
|
} else {
|
|
sp_offset = sizeof(void*);
|
|
}
|
|
ir_gdb_register(ZSTR_VAL(str), entry, size, sp_offset, 0);
|
|
// ir_mem_protect(entry, size);
|
|
}
|
|
|
|
if (JIT_G(debug) & (ZEND_JIT_DEBUG_PERF|ZEND_JIT_DEBUG_PERF_DUMP)) {
|
|
ir_perf_map_register(ZSTR_VAL(str), entry, size);
|
|
if (JIT_G(debug) & ZEND_JIT_DEBUG_PERF_DUMP) {
|
|
ir_perf_jitdump_register(ZSTR_VAL(str), entry, size);
|
|
}
|
|
}
|
|
}
|
|
#endif
|
|
}
|
|
|
|
if (jit->op_array) {
|
|
/* Only for function JIT */
|
|
const zend_op_array *op_array = jit->op_array;
|
|
zend_op *opline = (zend_op*)op_array->opcodes;
|
|
|
|
if (!(op_array->fn_flags & ZEND_ACC_HAS_TYPE_HINTS)) {
|
|
while (opline->opcode == ZEND_RECV) {
|
|
opline++;
|
|
}
|
|
}
|
|
opline->handler = entry;
|
|
|
|
if (jit->ctx.entries_count) {
|
|
/* For all entries */
|
|
int i = jit->ctx.entries_count;
|
|
do {
|
|
ir_insn *insn = &jit->ctx.ir_base[jit->ctx.entries[--i]];
|
|
op_array->opcodes[insn->op2].handler = (char*)entry + insn->op3;
|
|
} while (i != 0);
|
|
}
|
|
} else {
|
|
/* Only for tracing JIT */
|
|
zend_jit_trace_info *t = jit->trace;
|
|
zend_jit_trace_stack *stack;
|
|
uint32_t i;
|
|
|
|
if (t) {
|
|
for (i = 0; i < t->stack_map_size; i++) {
|
|
stack = t->stack_map + i;
|
|
if (stack->flags & ZREG_SPILL_SLOT) {
|
|
stack->reg = (jit->ctx.flags & IR_USE_FRAME_POINTER) ? IR_REG_FP : IR_REG_SP;
|
|
stack->ref = ir_get_spill_slot_offset(&jit->ctx, stack->ref);
|
|
}
|
|
}
|
|
}
|
|
|
|
zend_jit_trace_add_code(entry, size);
|
|
}
|
|
}
|
|
|
|
if (str) {
|
|
zend_string_release(str);
|
|
}
|
|
|
|
return entry;
|
|
}
|
|
|
|
static const void *zend_jit_trace_allocate_exit_group(uint32_t n)
|
|
{
|
|
const void *entry;
|
|
size_t size;
|
|
ir_code_buffer code_buffer;
|
|
|
|
code_buffer.start = dasm_buf;
|
|
code_buffer.end = dasm_end;
|
|
code_buffer.pos = *dasm_ptr;
|
|
|
|
entry = ir_emit_exitgroup(n, ZEND_JIT_EXIT_POINTS_PER_GROUP, zend_jit_stub_handlers[jit_stub_trace_exit],
|
|
&code_buffer, &size);
|
|
|
|
*dasm_ptr = code_buffer.pos;
|
|
|
|
if (entry) {
|
|
#ifdef HAVE_CAPSTONE
|
|
if (JIT_G(debug) & ZEND_JIT_DEBUG_ASM) {
|
|
uint32_t i;
|
|
char name[32];
|
|
|
|
for (i = 0; i < ZEND_JIT_EXIT_POINTS_PER_GROUP; i++) {
|
|
snprintf(name, sizeof(name), "jit$$trace_exit_%d", n + i);
|
|
ir_disasm_add_symbol(name, (uintptr_t)entry + (i * ZEND_JIT_EXIT_POINTS_SPACING), ZEND_JIT_EXIT_POINTS_SPACING);
|
|
}
|
|
}
|
|
#endif
|
|
}
|
|
|
|
return entry;
|
|
}
|
|
|
|
static int zend_jit_type_guard(zend_jit_ctx *jit, 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);
|
|
zend_jit_addr addr = ZEND_ADDR_MEM_ZVAL(ZREG_FP, var);
|
|
|
|
if (!exit_addr) {
|
|
return 0;
|
|
}
|
|
ir_GUARD(ir_EQ(jit_Z_TYPE(jit, addr), ir_CONST_U8(type)), ir_CONST_ADDR(exit_addr));
|
|
|
|
return 1;
|
|
}
|
|
|
|
static int zend_jit_scalar_type_guard(zend_jit_ctx *jit, const zend_op *opline, uint32_t var)
|
|
{
|
|
int32_t exit_point = zend_jit_trace_get_exit_point(opline, 0);
|
|
const void *exit_addr = zend_jit_trace_get_exit_addr(exit_point);
|
|
zend_jit_addr addr = ZEND_ADDR_MEM_ZVAL(ZREG_FP, var);
|
|
|
|
if (!exit_addr) {
|
|
return 0;
|
|
}
|
|
ir_GUARD(ir_LT(jit_Z_TYPE(jit, addr), ir_CONST_U8(IS_STRING)), ir_CONST_ADDR(exit_addr));
|
|
|
|
return 1;
|
|
}
|
|
|
|
static bool zend_jit_noref_guard(zend_jit_ctx *jit, const zend_op *opline, zend_jit_addr var_addr)
|
|
{
|
|
uint32_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;
|
|
}
|
|
ir_GUARD(ir_NE(jit_Z_TYPE(jit, var_addr), ir_CONST_U8(IS_REFERENCE)), ir_CONST_ADDR(exit_addr));
|
|
|
|
return 1;
|
|
}
|
|
|
|
static int zend_jit_trace_opline_guard(zend_jit_ctx *jit, 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;
|
|
}
|
|
|
|
ir_GUARD(jit_CMP_IP(jit, IR_EQ, opline), ir_CONST_ADDR(exit_addr));
|
|
zend_jit_set_last_valid_opline(jit, opline);
|
|
|
|
return 1;
|
|
}
|
|
|
|
static bool zend_jit_guard_reference(zend_jit_ctx *jit,
|
|
const zend_op *opline,
|
|
zend_jit_addr *var_addr_ptr,
|
|
zend_jit_addr *ref_addr_ptr,
|
|
bool add_ref_guard)
|
|
{
|
|
zend_jit_addr var_addr = *var_addr_ptr;
|
|
const void *exit_addr = NULL;
|
|
ir_ref ref;
|
|
|
|
if (add_ref_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;
|
|
}
|
|
|
|
ref = jit_Z_TYPE(jit, var_addr);
|
|
ir_GUARD(ir_EQ(ref, ir_CONST_U8(IS_REFERENCE)), ir_CONST_ADDR(exit_addr));
|
|
}
|
|
|
|
ref = jit_Z_PTR(jit, var_addr);
|
|
*ref_addr_ptr = ZEND_ADDR_REF_ZVAL(ref);
|
|
ref = ir_ADD_OFFSET(ref, offsetof(zend_reference, val));
|
|
var_addr = ZEND_ADDR_REF_ZVAL(ref);
|
|
*var_addr_ptr = var_addr;
|
|
|
|
return 1;
|
|
}
|
|
|
|
static bool zend_jit_fetch_reference(zend_jit_ctx *jit,
|
|
const zend_op *opline,
|
|
uint8_t var_type,
|
|
uint32_t *var_info_ptr,
|
|
zend_jit_addr *var_addr_ptr,
|
|
bool add_ref_guard,
|
|
bool add_type_guard)
|
|
{
|
|
zend_jit_addr var_addr = *var_addr_ptr;
|
|
uint32_t var_info = *var_info_ptr;
|
|
const void *exit_addr = NULL;
|
|
ir_ref ref;
|
|
|
|
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) {
|
|
ref = jit_Z_TYPE(jit, var_addr);
|
|
ir_GUARD(ir_EQ(ref, ir_CONST_U8(IS_REFERENCE)), ir_CONST_ADDR(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 */
|
|
ir_CALL_1(IR_VOID, ir_CONST_FC_FUNC(zend_jit_unref_helper),
|
|
jit_ZVAL_ADDR(jit, var_addr));
|
|
*var_addr_ptr = var_addr;
|
|
} else {
|
|
ref = jit_Z_PTR(jit, var_addr);
|
|
ref = ir_ADD_OFFSET(ref, offsetof(zend_reference, val));
|
|
var_addr = ZEND_ADDR_REF_ZVAL(ref);
|
|
*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)) {
|
|
ref = jit_Z_TYPE(jit, var_addr);
|
|
ir_GUARD(ir_EQ(ref, ir_CONST_U8(var_type)), ir_CONST_ADDR(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 bool zend_jit_fetch_indirect_var(zend_jit_ctx *jit, const zend_op *opline, uint8_t var_type, uint32_t *var_info_ptr, zend_jit_addr *var_addr_ptr, 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;
|
|
ir_ref ref = IR_UNUSED;
|
|
|
|
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;
|
|
}
|
|
jit_guard_Z_TYPE(jit, var_addr, IS_INDIRECT, exit_addr);
|
|
ref = jit_Z_PTR(jit, var_addr);
|
|
} else {
|
|
/* This LOAD of INDIRECT VAR, stored by the previous FETCH_(DIM/OBJ)_W,
|
|
* is eliminated by store forwarding (S2L) */
|
|
ref = jit_Z_PTR(jit, var_addr);
|
|
}
|
|
*var_info_ptr &= ~MAY_BE_INDIRECT;
|
|
var_addr = ZEND_ADDR_REF_ZVAL(ref);
|
|
*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;
|
|
}
|
|
|
|
jit_guard_Z_TYPE(jit, var_addr, 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 int zend_jit_trace_handler(zend_jit_ctx *jit, 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;
|
|
ir_ref ref;
|
|
|
|
zend_jit_set_ip(jit, opline);
|
|
if (GCC_GLOBAL_REGS) {
|
|
ir_CALL(IR_VOID, ir_CONST_FUNC(handler));
|
|
} else {
|
|
ref = jit_FP(jit);
|
|
ref = ir_CALL_1(IR_I32, ir_CONST_FC_FUNC(handler), ref);
|
|
}
|
|
if (may_throw
|
|
&& opline->opcode != ZEND_RETURN
|
|
&& opline->opcode != ZEND_RETURN_BY_REF) {
|
|
zend_jit_check_exception(jit);
|
|
}
|
|
|
|
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) {
|
|
|
|
ir_ref addr = jit_EG(current_execute_data);
|
|
|
|
jit_STORE_FP(jit, ir_LOAD_A(addr));
|
|
}
|
|
}
|
|
|
|
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 (trace->op != ZEND_JIT_TRACE_END ||
|
|
(trace->stop != ZEND_JIT_TRACE_STOP_RETURN &&
|
|
trace->stop < ZEND_JIT_TRACE_STOP_INTERPRETER)) {
|
|
/* this check may be handled by the following OPLINE guard or jmp [IP] */
|
|
ir_GUARD(ir_NE(jit_IP(jit), ir_CONST_ADDR(zend_jit_halt_op)),
|
|
jit_STUB_ADDR(jit, jit_stub_trace_halt));
|
|
}
|
|
} else if (GCC_GLOBAL_REGS) {
|
|
ir_GUARD(jit_IP(jit), jit_STUB_ADDR(jit, jit_stub_trace_halt));
|
|
} else {
|
|
ir_GUARD(ir_GE(ref, ir_CONST_I32(0)), jit_STUB_ADDR(jit, jit_stub_trace_halt));
|
|
}
|
|
} else if (opline->opcode == ZEND_GENERATOR_RETURN ||
|
|
opline->opcode == ZEND_YIELD ||
|
|
opline->opcode == ZEND_YIELD_FROM) {
|
|
ir_IJMP(jit_STUB_ADDR(jit, jit_stub_trace_halt));
|
|
ir_BEGIN(IR_UNUSED); /* unreachable block */
|
|
}
|
|
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)) {
|
|
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_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;
|
|
case ZEND_BIND_INIT_STATIC_OR_JMP:
|
|
if (opline->op1_type == IS_CV) {
|
|
old_info = STACK_INFO(stack, EX_VAR_TO_NUM(opline->op1.var));
|
|
SET_STACK_TYPE(stack, EX_VAR_TO_NUM(opline->op1.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;
|
|
case ZEND_BIND_INIT_STATIC_OR_JMP:
|
|
if (opline->op1_type == IS_CV) {
|
|
SET_STACK_INFO(stack, EX_VAR_TO_NUM(opline->op1.var), old_info);
|
|
}
|
|
break;
|
|
}
|
|
|
|
if (!exit_addr) {
|
|
return 0;
|
|
}
|
|
ir_GUARD(jit_CMP_IP(jit, IR_EQ, next_opline), ir_CONST_ADDR(exit_addr));
|
|
}
|
|
}
|
|
|
|
zend_jit_set_last_valid_opline(jit, trace->opline);
|
|
|
|
return 1;
|
|
}
|
|
|
|
static int zend_jit_deoptimizer_start(zend_jit_ctx *jit,
|
|
zend_string *name,
|
|
uint32_t trace_num,
|
|
uint32_t exit_num)
|
|
{
|
|
zend_jit_init_ctx(jit, (zend_jit_vm_kind == ZEND_VM_KIND_CALL) ? 0 : IR_START_BR_TARGET);
|
|
|
|
jit->ctx.spill_base = ZREG_FP;
|
|
|
|
jit->op_array = NULL;
|
|
jit->ssa = NULL;
|
|
jit->name = zend_string_copy(name);
|
|
|
|
jit->ctx.flags |= IR_SKIP_PROLOGUE;
|
|
|
|
return 1;
|
|
}
|
|
|
|
static int zend_jit_trace_start(zend_jit_ctx *jit,
|
|
const zend_op_array *op_array,
|
|
zend_ssa *ssa,
|
|
zend_string *name,
|
|
uint32_t trace_num,
|
|
zend_jit_trace_info *parent,
|
|
uint32_t exit_num)
|
|
{
|
|
zend_jit_init_ctx(jit, (zend_jit_vm_kind == ZEND_VM_KIND_CALL) ? 0 : IR_START_BR_TARGET);
|
|
|
|
jit->ctx.spill_base = ZREG_FP;
|
|
|
|
jit->op_array = NULL;
|
|
jit->current_op_array = op_array;
|
|
jit->ssa = ssa;
|
|
jit->name = zend_string_copy(name);
|
|
|
|
if (!GCC_GLOBAL_REGS) {
|
|
if (!parent) {
|
|
ir_ref ref = ir_PARAM(IR_ADDR, "execute_data", 1);
|
|
jit_STORE_FP(jit, ref);
|
|
jit->ctx.flags |= IR_FASTCALL_FUNC;
|
|
}
|
|
}
|
|
|
|
if (parent) {
|
|
jit->ctx.flags |= IR_SKIP_PROLOGUE;
|
|
}
|
|
|
|
if (parent) {
|
|
int i;
|
|
int parent_vars_count = parent->exit_info[exit_num].stack_size;
|
|
zend_jit_trace_stack *parent_stack = parent_vars_count == 0 ? NULL :
|
|
parent->stack_map +
|
|
parent->exit_info[exit_num].stack_offset;
|
|
|
|
/* prevent clobbering of registers used for deoptimization */
|
|
for (i = 0; i < parent_vars_count; i++) {
|
|
if (STACK_FLAGS(parent_stack, i) != ZREG_CONST
|
|
&& STACK_REG(parent_stack, i) != ZREG_NONE) {
|
|
int32_t reg = STACK_REG(parent_stack, i);
|
|
ir_type type;
|
|
|
|
if (STACK_FLAGS(parent_stack, i) == ZREG_ZVAL_COPY) {
|
|
type = IR_ADDR;
|
|
} else if (STACK_TYPE(parent_stack, i) == IS_LONG) {
|
|
type = IR_LONG;
|
|
} else if (STACK_TYPE(parent_stack, i) == IS_DOUBLE) {
|
|
type = IR_DOUBLE;
|
|
} else {
|
|
ZEND_UNREACHABLE();
|
|
}
|
|
if (ssa && ssa->vars[i].no_val) {
|
|
/* pass */
|
|
} else {
|
|
ir_ref ref = ir_RLOAD(type, reg);
|
|
|
|
if (STACK_FLAGS(parent_stack, i) & (ZREG_LOAD|ZREG_STORE)) {
|
|
/* op3 is used as a flag that the value is already stored in memory.
|
|
* In case the IR framework decides to spill the result of IR_LOAD,
|
|
* it doesn't have to store the value once again.
|
|
*
|
|
* See: insn->op3 check in ir_emit_rload()
|
|
*/
|
|
ir_set_op(&jit->ctx, ref, 3, EX_NUM_TO_VAR(i));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (parent && parent->exit_info[exit_num].flags & ZEND_JIT_EXIT_METHOD_CALL) {
|
|
ZEND_ASSERT(parent->exit_info[exit_num].poly_func_reg >= 0 && parent->exit_info[exit_num].poly_this_reg >= 0);
|
|
ir_RLOAD_A(parent->exit_info[exit_num].poly_func_reg);
|
|
ir_RLOAD_A(parent->exit_info[exit_num].poly_this_reg);
|
|
}
|
|
|
|
ir_STORE(jit_EG(jit_trace_num), ir_CONST_U32(trace_num));
|
|
|
|
return 1;
|
|
}
|
|
|
|
static int zend_jit_trace_begin_loop(zend_jit_ctx *jit)
|
|
{
|
|
return ir_LOOP_BEGIN(ir_END());
|
|
}
|
|
|
|
static void zend_jit_trace_gen_phi(zend_jit_ctx *jit, zend_ssa_phi *phi)
|
|
{
|
|
int dst_var = phi->ssa_var;
|
|
int src_var = phi->sources[0];
|
|
ir_ref ref;
|
|
|
|
ZEND_ASSERT(!(jit->ra[dst_var].flags & ZREG_LOAD));
|
|
ZEND_ASSERT(jit->ra[src_var].ref != IR_UNUSED && jit->ra[src_var].ref != IR_NULL);
|
|
|
|
ref = ir_PHI_2(
|
|
(jit->ssa->var_info[src_var].type & MAY_BE_LONG) ? IR_LONG : IR_DOUBLE,
|
|
zend_jit_use_reg(jit, ZEND_ADDR_REG(src_var)), IR_UNUSED);
|
|
|
|
src_var = phi->sources[1];
|
|
ZEND_ASSERT(jit->ra[src_var].ref == IR_NULL);
|
|
jit->ra[src_var].flags |= ZREG_FORWARD;
|
|
|
|
zend_jit_def_reg(jit, ZEND_ADDR_REG(dst_var), ref);
|
|
}
|
|
|
|
static int zend_jit_trace_end_loop(zend_jit_ctx *jit, int loop_ref, const void *timeout_exit_addr)
|
|
{
|
|
if (timeout_exit_addr) {
|
|
zend_jit_check_timeout(jit, NULL, timeout_exit_addr);
|
|
}
|
|
ZEND_ASSERT(jit->ctx.ir_base[loop_ref].op2 == IR_UNUSED);
|
|
ir_MERGE_SET_OP(loop_ref, 2, ir_LOOP_END());
|
|
return 1;
|
|
}
|
|
|
|
static int zend_jit_trace_return(zend_jit_ctx *jit, bool original_handler, const zend_op *opline)
|
|
{
|
|
if (GCC_GLOBAL_REGS) {
|
|
if (!original_handler) {
|
|
ir_TAILCALL(IR_VOID, ir_LOAD_A(jit_IP(jit)));
|
|
} else {
|
|
ir_TAILCALL(IR_VOID, zend_jit_orig_opline_handler(jit));
|
|
}
|
|
} else {
|
|
if (original_handler) {
|
|
ir_ref ref;
|
|
ir_ref addr = zend_jit_orig_opline_handler(jit);
|
|
|
|
#if defined(IR_TARGET_X86)
|
|
addr = ir_CAST_FC_FUNC(addr);
|
|
#endif
|
|
ref = ir_CALL_1(IR_I32, addr, jit_FP(jit));
|
|
if (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)) {
|
|
ir_RETURN(ref);
|
|
return 1;
|
|
}
|
|
}
|
|
ir_RETURN(ir_CONST_I32(2)); // ZEND_VM_LEAVE
|
|
}
|
|
return 1;
|
|
}
|
|
|
|
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 ir_patch(code, size, jmp_table_size, zend_jit_trace_get_exit_addr(exit_num), addr);
|
|
}
|
|
|
|
static int zend_jit_trace_link_to_root(zend_jit_ctx *jit, zend_jit_trace_info *t, const void *timeout_exit_addr)
|
|
{
|
|
const void *link_addr;
|
|
|
|
/* Skip prologue. */
|
|
ZEND_ASSERT(zend_jit_trace_prologue_size != (size_t)-1);
|
|
link_addr = (const void*)((const char*)t->code_start + zend_jit_trace_prologue_size);
|
|
|
|
if (timeout_exit_addr) {
|
|
zend_jit_check_timeout(jit, NULL, timeout_exit_addr);
|
|
}
|
|
ir_IJMP(ir_CONST_ADDR(link_addr));
|
|
|
|
return 1;
|
|
}
|
|
|
|
static 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) && (opline->opcode != ZEND_SEND_VAL_EX || opline->op2.num <= MAX_ARG_FLAG_NUM);
|
|
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:
|
|
return (opline->op1_type == IS_CV);
|
|
case ZEND_ASSIGN_OP:
|
|
if (opline->op1_type != IS_CV || opline->result_type != IS_UNUSED) {
|
|
return 0;
|
|
}
|
|
op1_info = OP1_INFO();
|
|
op2_info = OP2_INFO();
|
|
return zend_jit_supported_binary_op(opline->extended_value, op1_info, op2_info);
|
|
case ZEND_ADD:
|
|
case ZEND_SUB:
|
|
case ZEND_MUL:
|
|
op1_info = OP1_INFO();
|
|
op2_info = OP2_INFO();
|
|
if ((op1_info & MAY_BE_UNDEF) || (op2_info & MAY_BE_UNDEF)) {
|
|
return 0;
|
|
}
|
|
if (trace && trace->op1_type != IS_UNKNOWN) {
|
|
op1_info &= 1U << (trace->op1_type & ~(IS_TRACE_REFERENCE|IS_TRACE_INDIRECT|IS_TRACE_PACKED));
|
|
}
|
|
if (trace && trace->op2_type != IS_UNKNOWN) {
|
|
op2_info &= 1U << (trace->op2_type & ~(IS_TRACE_REFERENCE|IS_TRACE_INDIRECT|IS_TRACE_PACKED));
|
|
}
|
|
return !(op1_info & MAY_BE_UNDEF)
|
|
&& !(op2_info & MAY_BE_UNDEF)
|
|
&& (op1_info & (MAY_BE_LONG|MAY_BE_DOUBLE))
|
|
&& (op2_info & (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();
|
|
if (trace && trace->op1_type != IS_UNKNOWN) {
|
|
op1_info &= 1U << (trace->op1_type & ~(IS_TRACE_REFERENCE|IS_TRACE_INDIRECT|IS_TRACE_PACKED));
|
|
}
|
|
if (trace && trace->op2_type != IS_UNKNOWN) {
|
|
op2_info &= 1U << (trace->op2_type & ~(IS_TRACE_REFERENCE|IS_TRACE_INDIRECT|IS_TRACE_PACKED));
|
|
}
|
|
return (op1_info & MAY_BE_LONG)
|
|
&& (op2_info & 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_LONG)
|
|
&& !(op1_info & MAY_BE_REF);
|
|
case ZEND_STRLEN:
|
|
op1_info = OP1_INFO();
|
|
return (opline->op1_type & (IS_CV|IS_CONST))
|
|
&& (op1_info & (MAY_BE_ANY|MAY_BE_REF|MAY_BE_UNDEF)) == MAY_BE_STRING;
|
|
case ZEND_COUNT:
|
|
op1_info = OP1_INFO();
|
|
return (opline->op1_type & (IS_CV|IS_CONST))
|
|
&& (op1_info & (MAY_BE_ANY|MAY_BE_REF|MAY_BE_UNDEF)) == MAY_BE_ARRAY;
|
|
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;
|
|
}
|
|
}
|
|
ZEND_FALLTHROUGH;
|
|
case ZEND_BOOL:
|
|
case ZEND_BOOL_NOT:
|
|
case ZEND_JMPZ_EX:
|
|
case ZEND_JMPNZ_EX:
|
|
return 1;
|
|
case ZEND_FETCH_CONSTANT:
|
|
return 1;
|
|
case ZEND_ISSET_ISEMPTY_DIM_OBJ:
|
|
if ((opline->extended_value & ZEND_ISEMPTY)) {
|
|
return 0;
|
|
}
|
|
ZEND_FALLTHROUGH;
|
|
case ZEND_FETCH_DIM_R:
|
|
case ZEND_FETCH_DIM_IS:
|
|
case ZEND_FETCH_LIST_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) &&
|
|
(((op2_info & (MAY_BE_ANY|MAY_BE_UNDEF)) == MAY_BE_LONG) ||
|
|
((op2_info & (MAY_BE_ANY|MAY_BE_UNDEF)) == MAY_BE_STRING));
|
|
case ZEND_ASSIGN_DIM_OP:
|
|
if (opline->result_type != IS_UNUSED) {
|
|
return 0;
|
|
}
|
|
if (!zend_jit_supported_binary_op(opline->extended_value, MAY_BE_ANY, OP1_DATA_INFO())) {
|
|
return 0;
|
|
}
|
|
ZEND_FALLTHROUGH;
|
|
case ZEND_ASSIGN_DIM:
|
|
case ZEND_FETCH_DIM_W:
|
|
case ZEND_FETCH_DIM_RW:
|
|
case ZEND_FETCH_LIST_W:
|
|
op1_info = OP1_INFO();
|
|
op2_info = OP2_INFO();
|
|
if (trace) {
|
|
if (opline->op1_type == IS_CV) {
|
|
if ((opline->opcode == ZEND_ASSIGN_DIM
|
|
|| opline->opcode == ZEND_ASSIGN_DIM_OP)
|
|
&& (opline+1)->op1_type == IS_CV
|
|
&& (opline+1)->op1.var == opline->op1.var) {
|
|
/* skip $a[x] = $a; */
|
|
return 0;
|
|
}
|
|
} else if (opline->op1_type == IS_VAR) {
|
|
if (trace->op1_type == IS_UNKNOWN
|
|
|| !(trace->op1_type & IS_TRACE_INDIRECT)
|
|
|| opline->result_type != IS_UNUSED) {
|
|
return 0;
|
|
}
|
|
}
|
|
if (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);
|
|
}
|
|
} else {
|
|
if (opline->op1_type != IS_CV) {
|
|
return 0;
|
|
}
|
|
}
|
|
return ((op1_info & (MAY_BE_ANY|MAY_BE_UNDEF)) == MAY_BE_ARRAY) &&
|
|
(((op2_info & (MAY_BE_ANY|MAY_BE_UNDEF)) == MAY_BE_LONG) ||
|
|
((op2_info & (MAY_BE_ANY|MAY_BE_UNDEF)) == MAY_BE_STRING));
|
|
case ZEND_ASSIGN_OBJ_OP:
|
|
if (opline->result_type != IS_UNUSED) {
|
|
return 0;
|
|
}
|
|
if (!zend_jit_supported_binary_op(opline->extended_value, MAY_BE_ANY, OP1_DATA_INFO())) {
|
|
return 0;
|
|
}
|
|
ZEND_FALLTHROUGH;
|
|
case ZEND_FETCH_OBJ_R:
|
|
case ZEND_ASSIGN_OBJ:
|
|
if (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 0;
|
|
}
|
|
op1_info = OP1_INFO();
|
|
return opline->op1_type == IS_UNUSED || (op1_info & MAY_BE_OBJECT);
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static 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 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);
|
|
}
|
|
|
|
if (JIT_G(trigger) != ZEND_JIT_ON_HOT_TRACE) {
|
|
int def_block, use_block, b, use, j;
|
|
zend_basic_block *bb;
|
|
zend_ssa_phi *p;
|
|
bool ret = 1;
|
|
zend_worklist worklist;
|
|
ALLOCA_FLAG(use_heap)
|
|
|
|
/* Check if live range is split by ENTRY block */
|
|
if (ssa->vars[var].definition >= 0) {
|
|
def_block =ssa->cfg.map[ssa->vars[var].definition];
|
|
} else {
|
|
ZEND_ASSERT(ssa->vars[var].definition_phi);
|
|
def_block = ssa->vars[var].definition_phi->block;
|
|
}
|
|
|
|
ZEND_WORKLIST_ALLOCA(&worklist, ssa->cfg.blocks_count, use_heap);
|
|
|
|
if (ssa->vars[var].use_chain >= 0) {
|
|
use = ssa->vars[var].use_chain;
|
|
do {
|
|
use_block = ssa->cfg.map[use];
|
|
if (use_block != def_block) {
|
|
zend_worklist_push(&worklist, use_block);
|
|
}
|
|
use = zend_ssa_next_use(ssa->ops, var, use);
|
|
} while (use >= 0);
|
|
}
|
|
|
|
p = ssa->vars[var].phi_use_chain;
|
|
while (p) {
|
|
use_block = p->block;
|
|
if (use_block != def_block) {
|
|
bb = &ssa->cfg.blocks[use_block];
|
|
for (j = 0; j < bb->predecessors_count; j++) {
|
|
if (p->sources[j] == var) {
|
|
use_block = ssa->cfg.predecessors[bb->predecessor_offset + j];
|
|
if (use_block != def_block) {
|
|
zend_worklist_push(&worklist, use_block);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
p = zend_ssa_next_use_phi(ssa, var, p);
|
|
}
|
|
|
|
while (zend_worklist_len(&worklist) != 0) {
|
|
b = zend_worklist_pop(&worklist);
|
|
bb = &ssa->cfg.blocks[b];
|
|
if (bb->flags & (ZEND_BB_ENTRY|ZEND_BB_RECV_ENTRY)) {
|
|
ret = 0;
|
|
break;
|
|
}
|
|
for (j = 0; j < bb->predecessors_count; j++) {
|
|
b = ssa->cfg.predecessors[bb->predecessor_offset + j];
|
|
if (b != def_block) {
|
|
zend_worklist_push(&worklist, b);
|
|
}
|
|
}
|
|
}
|
|
|
|
ZEND_WORKLIST_FREE_ALLOCA(&worklist, use_heap);
|
|
|
|
return ret;
|
|
}
|
|
|
|
return 1;
|
|
}
|
|
|
|
static ir_ref jit_frameless_observer(zend_jit_ctx *jit, const zend_op *opline) {
|
|
// JIT: zend_observer_handler_is_unobserved(ZEND_OBSERVER_DATA(fbc))
|
|
ir_ref observer_handler;
|
|
zend_function *fbc = ZEND_FLF_FUNC(opline);
|
|
// Not need for runtime cache or generator checks here, we just need if_unobserved
|
|
ir_ref if_unobserved = jit_observer_fcall_is_unobserved_start(jit, fbc, &observer_handler, IR_UNUSED, IR_UNUSED).if_unobserved;
|
|
|
|
// Call zend_frameless_observed_call for the main logic.
|
|
ir_CALL_1(IR_VOID, ir_CONST_ADDR((size_t)zend_frameless_observed_call), jit_FP(jit));
|
|
|
|
ir_ref skip = ir_END();
|
|
ir_IF_TRUE(if_unobserved);
|
|
return skip;
|
|
}
|
|
|
|
static void jit_frameless_icall0(zend_jit_ctx *jit, const zend_op *opline)
|
|
{
|
|
jit_SET_EX_OPLINE(jit, opline);
|
|
|
|
void *function = ZEND_FLF_HANDLER(opline);
|
|
zend_jit_addr res_addr = RES_ADDR();
|
|
ir_ref res_ref = jit_ZVAL_ADDR(jit, res_addr);
|
|
jit_set_Z_TYPE_INFO(jit, res_addr, IS_NULL);
|
|
|
|
ir_ref skip_observer = IR_UNUSED;
|
|
if (ZEND_OBSERVER_ENABLED) {
|
|
skip_observer = jit_frameless_observer(jit, opline);
|
|
}
|
|
|
|
ir_CALL_1(IR_VOID, ir_CONST_ADDR((size_t)function), res_ref);
|
|
|
|
if (skip_observer != IR_UNUSED) {
|
|
ir_MERGE_WITH(skip_observer);
|
|
}
|
|
|
|
zend_jit_check_exception(jit);
|
|
}
|
|
|
|
static void jit_frameless_icall1(zend_jit_ctx *jit, const zend_op *opline, uint32_t op1_info)
|
|
{
|
|
jit_SET_EX_OPLINE(jit, opline);
|
|
|
|
/* Avoid dropping RC check in case op escapes. */
|
|
if (op1_info & MAY_BE_RC1) {
|
|
op1_info |= MAY_BE_RCN;
|
|
}
|
|
|
|
void *function = ZEND_FLF_HANDLER(opline);
|
|
zend_jit_addr res_addr = RES_ADDR();
|
|
zend_jit_addr op1_addr = OP1_ADDR();
|
|
ir_ref res_ref = jit_ZVAL_ADDR(jit, res_addr);
|
|
ir_ref op1_ref = jit_ZVAL_ADDR(jit, op1_addr);
|
|
jit_set_Z_TYPE_INFO(jit, res_addr, IS_NULL);
|
|
if (opline->op1_type == IS_CV && (op1_info & MAY_BE_UNDEF)) {
|
|
op1_ref = zend_jit_zval_check_undef(jit, op1_ref, opline->op1.var, opline, 1);
|
|
op1_info &= ~MAY_BE_UNDEF;
|
|
op1_info |= MAY_BE_NULL;
|
|
op1_addr = ZEND_ADDR_REF_ZVAL(op1_ref);
|
|
}
|
|
if (op1_info & MAY_BE_REF) {
|
|
op1_ref = jit_ZVAL_DEREF_ref(jit, op1_ref);
|
|
}
|
|
|
|
ir_ref skip_observer = IR_UNUSED;
|
|
if (ZEND_OBSERVER_ENABLED) {
|
|
skip_observer = jit_frameless_observer(jit, opline);
|
|
}
|
|
|
|
ir_CALL_2(IR_VOID, ir_CONST_ADDR((size_t)function), res_ref, op1_ref);
|
|
|
|
if (skip_observer != IR_UNUSED) {
|
|
ir_MERGE_WITH(skip_observer);
|
|
}
|
|
|
|
jit_FREE_OP(jit, opline->op1_type, opline->op1, op1_info, NULL);
|
|
zend_jit_check_exception(jit);
|
|
}
|
|
|
|
static void jit_frameless_icall2(zend_jit_ctx *jit, const zend_op *opline, uint32_t op1_info, uint32_t op2_info)
|
|
{
|
|
jit_SET_EX_OPLINE(jit, opline);
|
|
|
|
/* Avoid dropping RC check in case op escapes. */
|
|
if (op1_info & MAY_BE_RC1) {
|
|
op1_info |= MAY_BE_RCN;
|
|
}
|
|
if (op2_info & MAY_BE_RC1) {
|
|
op2_info |= MAY_BE_RCN;
|
|
}
|
|
|
|
void *function = ZEND_FLF_HANDLER(opline);
|
|
zend_jit_addr res_addr = RES_ADDR();
|
|
zend_jit_addr op1_addr = OP1_ADDR();
|
|
zend_jit_addr op2_addr = OP2_ADDR();
|
|
ir_ref res_ref = jit_ZVAL_ADDR(jit, res_addr);
|
|
ir_ref op1_ref = jit_ZVAL_ADDR(jit, op1_addr);
|
|
ir_ref op2_ref = jit_ZVAL_ADDR(jit, op2_addr);
|
|
jit_set_Z_TYPE_INFO(jit, res_addr, IS_NULL);
|
|
if (opline->op1_type == IS_CV && (op1_info & MAY_BE_UNDEF)) {
|
|
op1_ref = zend_jit_zval_check_undef(jit, op1_ref, opline->op1.var, opline, 1);
|
|
op1_info &= ~MAY_BE_UNDEF;
|
|
op1_info |= MAY_BE_NULL;
|
|
op1_addr = ZEND_ADDR_REF_ZVAL(op1_ref);
|
|
}
|
|
if (opline->op2_type == IS_CV && (op2_info & MAY_BE_UNDEF)) {
|
|
op2_ref = zend_jit_zval_check_undef(jit, op2_ref, opline->op2.var, opline, 1);
|
|
op2_info &= ~MAY_BE_UNDEF;
|
|
op2_info |= MAY_BE_NULL;
|
|
op2_addr = ZEND_ADDR_REF_ZVAL(op2_ref);
|
|
}
|
|
if (op1_info & MAY_BE_REF) {
|
|
op1_ref = jit_ZVAL_DEREF_ref(jit, op1_ref);
|
|
}
|
|
if (op2_info & MAY_BE_REF) {
|
|
op2_ref = jit_ZVAL_DEREF_ref(jit, op2_ref);
|
|
}
|
|
|
|
ir_ref skip_observer = IR_UNUSED;
|
|
if (ZEND_OBSERVER_ENABLED) {
|
|
skip_observer = jit_frameless_observer(jit, opline);
|
|
}
|
|
|
|
ir_CALL_3(IR_VOID, ir_CONST_ADDR((size_t)function), res_ref, op1_ref, op2_ref);
|
|
|
|
if (skip_observer != IR_UNUSED) {
|
|
ir_MERGE_WITH(skip_observer);
|
|
}
|
|
|
|
jit_FREE_OP(jit, opline->op1_type, opline->op1, op1_info, NULL);
|
|
/* Set OP1 to UNDEF in case FREE_OP2() throws. */
|
|
if ((opline->op1_type & (IS_VAR|IS_TMP_VAR)) != 0 && (opline->op2_type & (IS_VAR|IS_TMP_VAR)) != 0) {
|
|
jit_set_Z_TYPE_INFO(jit, op1_addr, IS_UNDEF);
|
|
}
|
|
jit_FREE_OP(jit, opline->op2_type, opline->op2, op2_info, NULL);
|
|
zend_jit_check_exception(jit);
|
|
}
|
|
|
|
static void jit_frameless_icall3(zend_jit_ctx *jit, const zend_op *opline, uint32_t op1_info, uint32_t op2_info, uint32_t op1_data_info)
|
|
{
|
|
jit_SET_EX_OPLINE(jit, opline);
|
|
|
|
/* Avoid dropping RC check in case op escapes. */
|
|
if (op1_info & MAY_BE_RC1) {
|
|
op1_info |= MAY_BE_RCN;
|
|
}
|
|
if (op2_info & MAY_BE_RC1) {
|
|
op2_info |= MAY_BE_RCN;
|
|
}
|
|
if (op1_data_info & MAY_BE_RC1) {
|
|
op1_data_info |= MAY_BE_RCN;
|
|
}
|
|
|
|
void *function = ZEND_FLF_HANDLER(opline);
|
|
uint8_t op_data_type = (opline + 1)->op1_type;
|
|
zend_jit_addr res_addr = RES_ADDR();
|
|
zend_jit_addr op1_addr = OP1_ADDR();
|
|
zend_jit_addr op2_addr = OP2_ADDR();
|
|
zend_jit_addr op3_addr = OP1_DATA_ADDR();
|
|
ir_ref res_ref = jit_ZVAL_ADDR(jit, res_addr);
|
|
ir_ref op1_ref = jit_ZVAL_ADDR(jit, op1_addr);
|
|
ir_ref op2_ref = jit_ZVAL_ADDR(jit, op2_addr);
|
|
ir_ref op3_ref = jit_ZVAL_ADDR(jit, op3_addr);
|
|
jit_set_Z_TYPE_INFO(jit, res_addr, IS_NULL);
|
|
if (opline->op1_type == IS_CV && (op1_info & MAY_BE_UNDEF)) {
|
|
op1_ref = zend_jit_zval_check_undef(jit, op1_ref, opline->op1.var, opline, 1);
|
|
op1_info &= ~MAY_BE_UNDEF;
|
|
op1_info |= MAY_BE_NULL;
|
|
op1_addr = ZEND_ADDR_REF_ZVAL(op1_ref);
|
|
}
|
|
if (opline->op2_type == IS_CV && (op2_info & MAY_BE_UNDEF)) {
|
|
op2_ref = zend_jit_zval_check_undef(jit, op2_ref, opline->op2.var, opline, 1);
|
|
op2_info &= ~MAY_BE_UNDEF;
|
|
op2_info |= MAY_BE_NULL;
|
|
op2_addr = ZEND_ADDR_REF_ZVAL(op2_ref);
|
|
}
|
|
if ((opline+1)->op1_type == IS_CV && (op1_data_info & MAY_BE_UNDEF)) {
|
|
op3_ref = zend_jit_zval_check_undef(jit, op3_ref, (opline+1)->op1.var, opline, 1);
|
|
op1_data_info &= ~MAY_BE_UNDEF;
|
|
op1_data_info |= MAY_BE_NULL;
|
|
op3_addr = ZEND_ADDR_REF_ZVAL(op3_ref);
|
|
}
|
|
if (op1_info & MAY_BE_REF) {
|
|
op1_ref = jit_ZVAL_DEREF_ref(jit, op1_ref);
|
|
}
|
|
if (op2_info & MAY_BE_REF) {
|
|
op2_ref = jit_ZVAL_DEREF_ref(jit, op2_ref);
|
|
}
|
|
if (op1_data_info & MAY_BE_REF) {
|
|
op3_ref = jit_ZVAL_DEREF_ref(jit, op3_ref);
|
|
}
|
|
|
|
ir_ref skip_observer = IR_UNUSED;
|
|
if (ZEND_OBSERVER_ENABLED) {
|
|
skip_observer = jit_frameless_observer(jit, opline);
|
|
}
|
|
|
|
ir_CALL_4(IR_VOID, ir_CONST_ADDR((size_t)function), res_ref, op1_ref, op2_ref, op3_ref);
|
|
|
|
if (skip_observer != IR_UNUSED) {
|
|
ir_MERGE_WITH(skip_observer);
|
|
}
|
|
|
|
jit_FREE_OP(jit, opline->op1_type, opline->op1, op1_info, NULL);
|
|
/* Set OP1 to UNDEF in case FREE_OP2() throws. */
|
|
if ((opline->op1_type & (IS_VAR|IS_TMP_VAR))
|
|
&& ((opline->op2_type & (IS_VAR|IS_TMP_VAR))
|
|
|| (op_data_type & (IS_VAR|IS_TMP_VAR)))) {
|
|
jit_set_Z_TYPE_INFO(jit, op1_addr, IS_UNDEF);
|
|
}
|
|
jit_FREE_OP(jit, opline->op2_type, opline->op2, op2_info, NULL);
|
|
/* If OP1 is a TMP|VAR, we don't need to set OP2 to UNDEF on free because
|
|
* zend_fetch_debug_backtrace aborts when it encounters the first UNDEF TMP|VAR. */
|
|
if (!(opline->op1_type & (IS_VAR|IS_TMP_VAR))
|
|
&& (opline->op2_type & (IS_VAR|IS_TMP_VAR)) != 0
|
|
&& (op_data_type & (IS_VAR|IS_TMP_VAR)) != 0) {
|
|
jit_set_Z_TYPE_INFO(jit, op2_addr, IS_UNDEF);
|
|
}
|
|
jit_FREE_OP(jit, (opline+1)->op1_type, (opline+1)->op1, op1_data_info, NULL);
|
|
zend_jit_check_exception(jit);
|
|
}
|
|
|
|
/*
|
|
* Local variables:
|
|
* tab-width: 4
|
|
* c-basic-offset: 4
|
|
* indent-tabs-mode: t
|
|
* End:
|
|
*/
|