php-src/ext/opcache/jit/zend_jit.c
yang yuhan f127e6581a
JIT: Align JIT stubs (#11149)
Fixed phpGH-11120

Signed-off-by: PeterYang12 <yuhan.yang@intel.com>
2023-05-02 12:34:49 +03:00

5183 lines
147 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 "main/php.h"
#include "main/SAPI.h"
#include "php_version.h"
#include <ZendAccelerator.h>
#include "zend_shared_alloc.h"
#include "Zend/zend_execute.h"
#include "Zend/zend_vm.h"
#include "Zend/zend_exceptions.h"
#include "Zend/zend_constants.h"
#include "Zend/zend_closures.h"
#include "Zend/zend_ini.h"
#include "Zend/zend_observer.h"
#include "zend_smart_str.h"
#include "jit/zend_jit.h"
#ifdef HAVE_JIT
#include "Optimizer/zend_func_info.h"
#include "Optimizer/zend_ssa.h"
#include "Optimizer/zend_inference.h"
#include "Optimizer/zend_call_graph.h"
#include "Optimizer/zend_dump.h"
#if ZEND_JIT_TARGET_X86
# include "jit/zend_jit_x86.h"
#elif ZEND_JIT_TARGET_ARM64
# include "jit/zend_jit_arm64.h"
#endif
#include "jit/zend_jit_internal.h"
#ifdef HAVE_PTHREAD_JIT_WRITE_PROTECT_NP
#include <pthread.h>
#endif
#ifdef ZTS
int jit_globals_id;
#else
zend_jit_globals jit_globals;
#endif
//#define CONTEXT_THREADED_JIT
#define ZEND_JIT_USE_RC_INFERENCE
#ifdef ZEND_JIT_USE_RC_INFERENCE
# define ZEND_SSA_RC_INFERENCE_FLAG ZEND_SSA_RC_INFERENCE
# define RC_MAY_BE_1(info) (((info) & (MAY_BE_RC1|MAY_BE_REF)) != 0)
# define RC_MAY_BE_N(info) (((info) & (MAY_BE_RCN|MAY_BE_REF)) != 0)
#else
# define ZEND_SSA_RC_INFERENCE_FLAG 0
# define RC_MAY_BE_1(info) 1
# define RC_MAY_BE_N(info) 1
#endif
#define JIT_PREFIX "JIT$"
#define JIT_STUB_PREFIX "JIT$$"
#define TRACE_PREFIX "TRACE-"
#define DASM_M_GROW(ctx, t, p, sz, need) \
do { \
size_t _sz = (sz), _need = (need); \
if (_sz < _need) { \
if (_sz < 16) _sz = 16; \
while (_sz < _need) _sz += _sz; \
(p) = (t *)erealloc((p), _sz); \
(sz) = _sz; \
} \
} while(0)
#define DASM_M_FREE(ctx, p, sz) efree(p)
#if ZEND_DEBUG
# define DASM_CHECKS 1
#endif
#include "dynasm/dasm_proto.h"
typedef struct _zend_jit_stub {
const char *name;
int (*stub)(dasm_State **Dst);
uint32_t offset;
uint32_t adjustment;
} zend_jit_stub;
#define JIT_STUB(name, offset, adjustment) \
{JIT_STUB_PREFIX #name, zend_jit_ ## name ## _stub, offset, adjustment}
zend_ulong zend_jit_profile_counter = 0;
int zend_jit_profile_counter_rid = -1;
int16_t zend_jit_hot_counters[ZEND_HOT_COUNTERS_COUNT];
const zend_op *zend_jit_halt_op = NULL;
static int zend_jit_vm_kind = 0;
#ifdef HAVE_PTHREAD_JIT_WRITE_PROTECT_NP
static int zend_write_protect = 1;
#endif
static void *dasm_buf = NULL;
static void *dasm_end = NULL;
static void **dasm_ptr = NULL;
static size_t dasm_size = 0;
static zend_long jit_bisect_pos = 0;
static const void *zend_jit_runtime_jit_handler = NULL;
static const void *zend_jit_profile_jit_handler = NULL;
static const void *zend_jit_func_hot_counter_handler = NULL;
static const void *zend_jit_loop_hot_counter_handler = NULL;
static const void *zend_jit_func_trace_counter_handler = NULL;
static const void *zend_jit_ret_trace_counter_handler = NULL;
static const void *zend_jit_loop_trace_counter_handler = NULL;
static int ZEND_FASTCALL zend_runtime_jit(void);
static int zend_jit_trace_op_len(const zend_op *opline);
static int zend_jit_trace_may_exit(const zend_op_array *op_array, const zend_op *opline);
static uint32_t zend_jit_trace_get_exit_point(const zend_op *to_opline, uint32_t flags);
static const void *zend_jit_trace_get_exit_addr(uint32_t n);
static void zend_jit_trace_add_code(const void *start, uint32_t size);
static bool zend_jit_needs_arg_dtor(const zend_function *func, uint32_t arg_num, zend_call_info *call_info);
#if ZEND_JIT_TARGET_ARM64
static zend_jit_trace_info *zend_jit_get_current_trace_info(void);
static uint32_t zend_jit_trace_find_exit_point(const void* addr);
#endif
static int zend_jit_assign_to_variable(dasm_State **Dst,
const zend_op *opline,
zend_jit_addr var_use_addr,
zend_jit_addr var_addr,
uint32_t var_info,
uint32_t var_def_info,
uint8_t val_type,
zend_jit_addr val_addr,
uint32_t val_info,
zend_jit_addr res_addr,
bool check_exception);
static bool dominates(const zend_basic_block *blocks, int a, int b) {
while (blocks[b].level > blocks[a].level) {
b = blocks[b].idom;
}
return a == b;
}
static bool zend_ssa_is_last_use(const zend_op_array *op_array, const zend_ssa *ssa, int var, int use)
{
int next_use;
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->cfg.blocks[ssa->cfg.map[use]].loop_header > 0
|| (ssa->cfg.blocks[ssa->cfg.map[use]].flags & ZEND_BB_LOOP_HEADER)) {
int b = ssa->cfg.map[use];
int prev_use = ssa->vars[var].use_chain;
while (prev_use >= 0 && prev_use != use) {
if (b != ssa->cfg.map[prev_use]
&& dominates(ssa->cfg.blocks, b, ssa->cfg.map[prev_use])
&& !zend_ssa_is_no_val_use(op_array->opcodes + prev_use, ssa->ops + prev_use, var)) {
return 0;
}
prev_use = zend_ssa_next_use(ssa->ops, var, prev_use);
}
}
next_use = zend_ssa_next_use(ssa->ops, var, use);
if (next_use < 0) {
return 1;
} else if (zend_ssa_is_no_val_use(op_array->opcodes + next_use, ssa->ops + next_use, var)) {
return 1;
}
return 0;
}
static bool zend_ival_is_last_use(const zend_lifetime_interval *ival, int use)
{
if (ival->flags & ZREG_LAST_USE) {
const zend_life_range *range = &ival->range;
while (range->next) {
range = range->next;
}
return range->end == use;
}
return 0;
}
static bool zend_is_commutative(uint8_t opcode)
{
return
opcode == ZEND_ADD ||
opcode == ZEND_MUL ||
opcode == ZEND_BW_OR ||
opcode == ZEND_BW_AND ||
opcode == ZEND_BW_XOR;
}
static int zend_jit_is_constant_cmp_long_long(const zend_op *opline,
zend_ssa_range *op1_range,
zend_jit_addr op1_addr,
zend_ssa_range *op2_range,
zend_jit_addr op2_addr,
bool *result)
{
zend_long op1_min;
zend_long op1_max;
zend_long op2_min;
zend_long op2_max;
if (op1_range) {
op1_min = op1_range->min;
op1_max = op1_range->max;
} else if (Z_MODE(op1_addr) == IS_CONST_ZVAL) {
ZEND_ASSERT(Z_TYPE_P(Z_ZV(op1_addr)) == IS_LONG);
op1_min = op1_max = Z_LVAL_P(Z_ZV(op1_addr));
} else {
return 0;
}
if (op2_range) {
op2_min = op2_range->min;
op2_max = op2_range->max;
} else if (Z_MODE(op2_addr) == IS_CONST_ZVAL) {
ZEND_ASSERT(Z_TYPE_P(Z_ZV(op2_addr)) == IS_LONG);
op2_min = op2_max = Z_LVAL_P(Z_ZV(op2_addr));
} else {
return 0;
}
switch (opline->opcode) {
case ZEND_IS_EQUAL:
case ZEND_IS_IDENTICAL:
case ZEND_CASE:
case ZEND_CASE_STRICT:
if (op1_min == op1_max && op2_min == op2_max && op1_min == op2_min) {
*result = 1;
return 1;
} else if (op1_max < op2_min || op1_min > op2_max) {
*result = 0;
return 1;
}
return 0;
case ZEND_IS_NOT_EQUAL:
case ZEND_IS_NOT_IDENTICAL:
if (op1_min == op1_max && op2_min == op2_max && op1_min == op2_min) {
*result = 0;
return 1;
} else if (op1_max < op2_min || op1_min > op2_max) {
*result = 1;
return 1;
}
return 0;
case ZEND_IS_SMALLER:
if (op1_max < op2_min) {
*result = 1;
return 1;
} else if (op1_min >= op2_max) {
*result = 0;
return 1;
}
return 0;
case ZEND_IS_SMALLER_OR_EQUAL:
if (op1_max <= op2_min) {
*result = 1;
return 1;
} else if (op1_min > op2_max) {
*result = 0;
return 1;
}
return 0;
default:
ZEND_UNREACHABLE();
}
return 0;
}
static int zend_jit_needs_call_chain(zend_call_info *call_info, uint32_t b, const zend_op_array *op_array, zend_ssa *ssa, const zend_ssa_op *ssa_op, const zend_op *opline, int call_level, zend_jit_trace_rec *trace)
{
int skip;
if (trace) {
zend_jit_trace_rec *p = trace;
ssa_op++;
while (1) {
if (p->op == ZEND_JIT_TRACE_VM) {
switch (p->opline->opcode) {
case ZEND_SEND_ARRAY:
case ZEND_SEND_USER:
case ZEND_SEND_UNPACK:
case ZEND_INIT_FCALL:
case ZEND_INIT_METHOD_CALL:
case ZEND_INIT_STATIC_METHOD_CALL:
case ZEND_INIT_FCALL_BY_NAME:
case ZEND_INIT_NS_FCALL_BY_NAME:
case ZEND_INIT_DYNAMIC_CALL:
case ZEND_NEW:
case ZEND_INIT_USER_CALL:
case ZEND_FAST_CALL:
case ZEND_JMP:
case ZEND_JMPZ:
case ZEND_JMPNZ:
case ZEND_JMPZ_EX:
case ZEND_JMPNZ_EX:
case ZEND_FE_RESET_R:
case ZEND_FE_RESET_RW:
case ZEND_JMP_SET:
case ZEND_COALESCE:
case ZEND_JMP_NULL:
case ZEND_ASSERT_CHECK:
case ZEND_CATCH:
case ZEND_DECLARE_ANON_CLASS:
case ZEND_FE_FETCH_R:
case ZEND_FE_FETCH_RW:
return 1;
case ZEND_DO_ICALL:
case ZEND_DO_UCALL:
case ZEND_DO_FCALL_BY_NAME:
case ZEND_DO_FCALL:
case ZEND_CALLABLE_CONVERT:
return 0;
case ZEND_SEND_VAL:
case ZEND_SEND_VAR:
case ZEND_SEND_VAL_EX:
case ZEND_SEND_VAR_EX:
case ZEND_SEND_FUNC_ARG:
case ZEND_SEND_REF:
case ZEND_SEND_VAR_NO_REF:
case ZEND_SEND_VAR_NO_REF_EX:
/* skip */
break;
default:
if (zend_may_throw(opline, ssa_op, op_array, ssa)) {
return 1;
}
}
ssa_op += zend_jit_trace_op_len(opline);
} else if (p->op == ZEND_JIT_TRACE_ENTER ||
p->op == ZEND_JIT_TRACE_BACK ||
p->op == ZEND_JIT_TRACE_END) {
return 1;
}
p++;
}
}
if (!call_info) {
const zend_op *end = op_array->opcodes + op_array->last;
opline++;
ssa_op++;
skip = (call_level == 1);
while (opline != end) {
if (!skip) {
if (zend_may_throw(opline, ssa_op, op_array, ssa)) {
return 1;
}
}
switch (opline->opcode) {
case ZEND_SEND_VAL:
case ZEND_SEND_VAR:
case ZEND_SEND_VAL_EX:
case ZEND_SEND_VAR_EX:
case ZEND_SEND_FUNC_ARG:
case ZEND_SEND_REF:
case ZEND_SEND_VAR_NO_REF:
case ZEND_SEND_VAR_NO_REF_EX:
skip = 0;
break;
case ZEND_SEND_ARRAY:
case ZEND_SEND_USER:
case ZEND_SEND_UNPACK:
case ZEND_INIT_FCALL:
case ZEND_INIT_METHOD_CALL:
case ZEND_INIT_STATIC_METHOD_CALL:
case ZEND_INIT_FCALL_BY_NAME:
case ZEND_INIT_NS_FCALL_BY_NAME:
case ZEND_INIT_DYNAMIC_CALL:
case ZEND_NEW:
case ZEND_INIT_USER_CALL:
case ZEND_FAST_CALL:
case ZEND_JMP:
case ZEND_JMPZ:
case ZEND_JMPNZ:
case ZEND_JMPZ_EX:
case ZEND_JMPNZ_EX:
case ZEND_FE_RESET_R:
case ZEND_FE_RESET_RW:
case ZEND_JMP_SET:
case ZEND_COALESCE:
case ZEND_JMP_NULL:
case ZEND_ASSERT_CHECK:
case ZEND_CATCH:
case ZEND_DECLARE_ANON_CLASS:
case ZEND_FE_FETCH_R:
case ZEND_FE_FETCH_RW:
return 1;
case ZEND_DO_ICALL:
case ZEND_DO_UCALL:
case ZEND_DO_FCALL_BY_NAME:
case ZEND_DO_FCALL:
case ZEND_CALLABLE_CONVERT:
end = opline;
if (end - op_array->opcodes >= ssa->cfg.blocks[b].start + ssa->cfg.blocks[b].len) {
/* INIT_FCALL and DO_FCALL in different BasicBlocks */
return 1;
}
return 0;
}
opline++;
ssa_op++;
}
return 1;
} else {
const zend_op *end = call_info->caller_call_opline;
/* end may be null if an opcode like EXIT is part of the argument list. */
if (!end || end - op_array->opcodes >= ssa->cfg.blocks[b].start + ssa->cfg.blocks[b].len) {
/* INIT_FCALL and DO_FCALL in different BasicBlocks */
return 1;
}
opline++;
ssa_op++;
skip = (call_level == 1);
while (opline != end) {
if (skip) {
switch (opline->opcode) {
case ZEND_SEND_VAL:
case ZEND_SEND_VAR:
case ZEND_SEND_VAL_EX:
case ZEND_SEND_VAR_EX:
case ZEND_SEND_FUNC_ARG:
case ZEND_SEND_REF:
case ZEND_SEND_VAR_NO_REF:
case ZEND_SEND_VAR_NO_REF_EX:
skip = 0;
break;
case ZEND_SEND_ARRAY:
case ZEND_SEND_USER:
case ZEND_SEND_UNPACK:
return 1;
}
} else {
if (zend_may_throw(opline, ssa_op, op_array, ssa)) {
return 1;
}
}
opline++;
ssa_op++;
}
return 0;
}
}
static uint32_t skip_valid_arguments(const zend_op_array *op_array, zend_ssa *ssa, const zend_call_info *call_info)
{
uint32_t num_args = 0;
zend_function *func = call_info->callee_func;
/* It's okay to handle prototypes here, because they can only increase the accepted arguments.
* Anything legal for the parent method is also legal for the parent method. */
while (num_args < call_info->num_args) {
zend_arg_info *arg_info = func->op_array.arg_info + num_args;
if (ZEND_TYPE_IS_SET(arg_info->type)) {
if (ZEND_TYPE_IS_ONLY_MASK(arg_info->type)) {
zend_op *opline = call_info->arg_info[num_args].opline;
zend_ssa_op *ssa_op = &ssa->ops[opline - op_array->opcodes];
uint32_t type_mask = ZEND_TYPE_PURE_MASK(arg_info->type);
if ((OP1_INFO() & (MAY_BE_ANY|MAY_BE_UNDEF)) & ~type_mask) {
break;
}
} else {
break;
}
}
num_args++;
}
return num_args;
}
static uint32_t zend_ssa_cv_info(const zend_op_array *op_array, zend_ssa *ssa, uint32_t var)
{
uint32_t j, info;
if (ssa->vars && ssa->var_info) {
info = ssa->var_info[var].type;
for (j = op_array->last_var; j < ssa->vars_count; j++) {
if (ssa->vars[j].var == var) {
info |= ssa->var_info[j].type;
}
}
} else {
info = MAY_BE_RC1 | MAY_BE_RCN | MAY_BE_REF | MAY_BE_ANY | MAY_BE_UNDEF |
MAY_BE_ARRAY_KEY_ANY | MAY_BE_ARRAY_OF_ANY | MAY_BE_ARRAY_OF_REF;
}
#ifdef ZEND_JIT_USE_RC_INFERENCE
/* Refcount may be increased by RETURN opcode */
if ((info & MAY_BE_RC1) && !(info & MAY_BE_RCN)) {
for (j = 0; j < ssa->cfg.blocks_count; j++) {
if ((ssa->cfg.blocks[j].flags & ZEND_BB_REACHABLE) &&
ssa->cfg.blocks[j].len > 0) {
const zend_op *opline = op_array->opcodes + ssa->cfg.blocks[j].start + ssa->cfg.blocks[j].len - 1;
if (opline->opcode == ZEND_RETURN) {
if (opline->op1_type == IS_CV && opline->op1.var == EX_NUM_TO_VAR(var)) {
info |= MAY_BE_RCN;
break;
}
}
}
}
}
#endif
return info;
}
static bool zend_jit_may_avoid_refcounting(const zend_op *opline, uint32_t op1_info)
{
switch (opline->opcode) {
case ZEND_FETCH_OBJ_FUNC_ARG:
if (!JIT_G(current_frame) ||
!JIT_G(current_frame)->call->func ||
!TRACE_FRAME_IS_LAST_SEND_BY_VAL(JIT_G(current_frame)->call)) {
return 0;
}
/* break missing intentionally */
case ZEND_FETCH_OBJ_R:
case ZEND_FETCH_OBJ_IS:
if ((op1_info & MAY_BE_OBJECT)
&& opline->op2_type == IS_CONST
&& Z_TYPE_P(RT_CONSTANT(opline, opline->op2)) == IS_STRING
&& Z_STRVAL_P(RT_CONSTANT(opline, opline->op2))[0] != '\0') {
return 1;
}
break;
case ZEND_FETCH_DIM_FUNC_ARG:
if (!JIT_G(current_frame) ||
!JIT_G(current_frame)->call->func ||
!TRACE_FRAME_IS_LAST_SEND_BY_VAL(JIT_G(current_frame)->call)) {
return 0;
}
/* break missing intentionally */
case ZEND_FETCH_DIM_R:
case ZEND_FETCH_DIM_IS:
return 1;
case ZEND_ISSET_ISEMPTY_DIM_OBJ:
if (!(opline->extended_value & ZEND_ISEMPTY)) {
return 1;
}
break;
}
return 0;
}
static bool zend_jit_is_persistent_constant(zval *key, uint32_t flags)
{
zval *zv;
zend_constant *c = NULL;
/* null/true/false are resolved during compilation, so don't check for them here. */
zv = zend_hash_find_known_hash(EG(zend_constants), Z_STR_P(key));
if (zv) {
c = (zend_constant*)Z_PTR_P(zv);
} else if (flags & IS_CONSTANT_UNQUALIFIED_IN_NAMESPACE) {
key++;
zv = zend_hash_find_known_hash(EG(zend_constants), Z_STR_P(key));
if (zv) {
c = (zend_constant*)Z_PTR_P(zv);
}
}
return c && (ZEND_CONSTANT_FLAGS(c) & CONST_PERSISTENT);
}
static zend_property_info* zend_get_known_property_info(const zend_op_array *op_array, zend_class_entry *ce, zend_string *member, bool on_this, zend_string *filename)
{
zend_property_info *info = NULL;
if ((on_this && (op_array->fn_flags & ZEND_ACC_TRAIT_CLONE)) ||
!ce ||
!(ce->ce_flags & ZEND_ACC_LINKED) ||
(ce->ce_flags & ZEND_ACC_TRAIT) ||
ce->create_object) {
return NULL;
}
if (!(ce->ce_flags & ZEND_ACC_IMMUTABLE)) {
if (ce->info.user.filename != filename) {
/* class declaration might be changed independently */
return NULL;
}
if (ce->parent) {
zend_class_entry *parent = ce->parent;
do {
if (parent->type == ZEND_INTERNAL_CLASS) {
break;
} else if (parent->info.user.filename != filename) {
/* some of parents class declarations might be changed independently */
/* TODO: this check may be not enough, because even
* in the same it's possible to conditionally define
* few classes with the same name, and "parent" may
* change from request to request.
*/
return NULL;
}
parent = parent->parent;
} while (parent);
}
}
info = (zend_property_info*)zend_hash_find_ptr(&ce->properties_info, member);
if (info == NULL ||
!IS_VALID_PROPERTY_OFFSET(info->offset) ||
(info->flags & ZEND_ACC_STATIC)) {
return NULL;
}
if (info->flags & ZEND_ACC_PUBLIC) {
return info;
} else if (on_this) {
if (ce == info->ce) {
return info;
} else if ((info->flags & ZEND_ACC_PROTECTED)
&& instanceof_function_slow(ce, info->ce)) {
return info;
}
}
return NULL;
}
static bool zend_may_be_dynamic_property(zend_class_entry *ce, zend_string *member, bool on_this, zend_string *filename)
{
zend_property_info *info;
if (!ce || (ce->ce_flags & ZEND_ACC_TRAIT)) {
return 1;
}
if (!(ce->ce_flags & ZEND_ACC_IMMUTABLE)) {
if (ce->info.user.filename != filename) {
/* class declaration might be changed independently */
return 1;
}
}
info = (zend_property_info*)zend_hash_find_ptr(&ce->properties_info, member);
if (info == NULL ||
!IS_VALID_PROPERTY_OFFSET(info->offset) ||
(info->flags & ZEND_ACC_STATIC)) {
return 1;
}
if (!(info->flags & ZEND_ACC_PUBLIC) &&
(!on_this || info->ce != ce)) {
return 1;
}
return 0;
}
#define OP_RANGE(ssa_op, opN) \
(((opline->opN##_type & (IS_TMP_VAR|IS_VAR|IS_CV)) && \
ssa->var_info && \
(ssa_op)->opN##_use >= 0 && \
ssa->var_info[(ssa_op)->opN##_use].has_range) ? \
&ssa->var_info[(ssa_op)->opN##_use].range : NULL)
#define OP1_RANGE() OP_RANGE(ssa_op, op1)
#define OP2_RANGE() OP_RANGE(ssa_op, op2)
#define OP1_DATA_RANGE() OP_RANGE(ssa_op + 1, op1)
#if ZEND_JIT_TARGET_X86
# include "dynasm/dasm_x86.h"
#elif ZEND_JIT_TARGET_ARM64
static int zend_jit_add_veneer(dasm_State *Dst, void *buffer, uint32_t ins, int *b, uint32_t *cp, ptrdiff_t offset);
# define DASM_ADD_VENEER zend_jit_add_veneer
# include "dynasm/dasm_arm64.h"
#endif
#include "jit/zend_jit_helpers.c"
#include "jit/zend_jit_disasm.c"
#ifndef _WIN32
# include "jit/zend_jit_gdb.h"
# include "jit/zend_jit_perf_dump.c"
#endif
#include "Zend/zend_cpuinfo.h"
#ifdef HAVE_VALGRIND
# include <valgrind/valgrind.h>
#endif
#ifdef HAVE_GCC_GLOBAL_REGS
# define GCC_GLOBAL_REGS 1
#else
# define GCC_GLOBAL_REGS 0
#endif
/* By default avoid JITing inline handlers if it does not seem profitable due to lack of
* type information. Disabling this option allows testing some JIT handlers in the
* presence of try/catch blocks, which prevent SSA construction. */
#ifndef PROFITABILITY_CHECKS
# define PROFITABILITY_CHECKS 1
#endif
#define BP_JIT_IS 6 /* Used for ISSET_ISEMPTY_DIM_OBJ. see BP_VAR_*defines in Zend/zend_compile.h */
typedef enum _sp_adj_kind {
SP_ADJ_NONE,
SP_ADJ_RET,
SP_ADJ_VM,
SP_ADJ_JIT,
SP_ADJ_ASSIGN,
SP_ADJ_LAST
} sp_adj_kind;
static int sp_adj[SP_ADJ_LAST];
/* The generated code may contain tautological comparisons, ignore them. */
#if defined(__clang__)
# pragma clang diagnostic push
# pragma clang diagnostic ignored "-Wtautological-compare"
# pragma clang diagnostic ignored "-Wstring-compare"
#endif
#if ZEND_JIT_TARGET_X86
# include "jit/zend_jit_vtune.c"
# include "jit/zend_jit_x86.c"
#elif ZEND_JIT_TARGET_ARM64
# include "jit/zend_jit_arm64.c"
#endif
#if defined(__clang__)
# pragma clang diagnostic pop
#endif
#if _WIN32
# include <Windows.h>
#else
# include <sys/mman.h>
# if !defined(MAP_ANONYMOUS) && defined(MAP_ANON)
# define MAP_ANONYMOUS MAP_ANON
# endif
#endif
ZEND_EXT_API void zend_jit_status(zval *ret)
{
zval stats;
array_init(&stats);
add_assoc_bool(&stats, "enabled", JIT_G(enabled));
add_assoc_bool(&stats, "on", JIT_G(on));
add_assoc_long(&stats, "kind", JIT_G(trigger));
add_assoc_long(&stats, "opt_level", JIT_G(opt_level));
add_assoc_long(&stats, "opt_flags", JIT_G(opt_flags));
if (dasm_buf) {
add_assoc_long(&stats, "buffer_size", (char*)dasm_end - (char*)dasm_buf);
add_assoc_long(&stats, "buffer_free", (char*)dasm_end - (char*)*dasm_ptr);
} else {
add_assoc_long(&stats, "buffer_size", 0);
add_assoc_long(&stats, "buffer_free", 0);
}
add_assoc_zval(ret, "jit", &stats);
}
static zend_string *zend_jit_func_name(const zend_op_array *op_array)
{
smart_str buf = {0};
if (op_array->function_name) {
if (op_array->scope) {
smart_str_appends(&buf, JIT_PREFIX);
smart_str_appendl(&buf, ZSTR_VAL(op_array->scope->name), ZSTR_LEN(op_array->scope->name));
smart_str_appends(&buf, "::");
smart_str_appendl(&buf, ZSTR_VAL(op_array->function_name), ZSTR_LEN(op_array->function_name));
smart_str_0(&buf);
return buf.s;
} else {
smart_str_appends(&buf, JIT_PREFIX);
smart_str_appendl(&buf, ZSTR_VAL(op_array->function_name), ZSTR_LEN(op_array->function_name));
smart_str_0(&buf);
return buf.s;
}
} else if (op_array->filename) {
smart_str_appends(&buf, JIT_PREFIX);
smart_str_appendl(&buf, ZSTR_VAL(op_array->filename), ZSTR_LEN(op_array->filename));
smart_str_0(&buf);
return buf.s;
} else {
return NULL;
}
}
#if ZEND_DEBUG
static void handle_dasm_error(int ret) {
switch (ret & 0xff000000u) {
case DASM_S_NOMEM:
fprintf(stderr, "DASM_S_NOMEM\n");
break;
case DASM_S_PHASE:
fprintf(stderr, "DASM_S_PHASE\n");
break;
case DASM_S_MATCH_SEC:
fprintf(stderr, "DASM_S_MATCH_SEC\n");
break;
case DASM_S_RANGE_I:
fprintf(stderr, "DASM_S_RANGE_I\n");
break;
case DASM_S_RANGE_SEC:
fprintf(stderr, "DASM_S_RANGE_SEC\n");
break;
case DASM_S_RANGE_LG:
fprintf(stderr, "DASM_S_RANGE_LG\n");
break;
case DASM_S_RANGE_PC:
fprintf(stderr, "DASM_S_RANGE_PC %d\n", ret & 0xffffffu);
break;
#ifdef DASM_S_RANGE_VREG
case DASM_S_RANGE_VREG:
fprintf(stderr, "DASM_S_RANGE_VREG\n");
break;
#endif
#ifdef DASM_S_UNDEF_L
case DASM_S_UNDEF_L:
fprintf(stderr, "DASM_S_UNDEF_L\n");
break;
#endif
#ifdef DASM_S_UNDEF_LG
case DASM_S_UNDEF_LG:
fprintf(stderr, "DASM_S_UNDEF_LG\n");
break;
#endif
#ifdef DASM_S_RANGE_REL
case DASM_S_RANGE_REL:
fprintf(stderr, "DASM_S_RANGE_REL\n");
break;
#endif
case DASM_S_UNDEF_PC:
fprintf(stderr, "DASM_S_UNDEF_PC %d\n", ret & 0xffffffu);
break;
default:
fprintf(stderr, "DASM_S_%0x\n", ret & 0xff000000u);
break;
}
ZEND_UNREACHABLE();
}
#endif
static void *dasm_link_and_encode(dasm_State **dasm_state,
const zend_op_array *op_array,
zend_ssa *ssa,
const zend_op *rt_opline,
zend_lifetime_interval **ra,
const char *name,
uint32_t trace_num,
uint32_t sp_offset,
uint32_t sp_adjustment)
{
size_t size;
int ret;
void *entry;
#if defined(HAVE_DISASM) || defined(HAVE_GDB) || defined(HAVE_PERFTOOLS) || defined(HAVE_VTUNE)
zend_string *str = NULL;
#endif
if (rt_opline && ssa && ssa->cfg.map) {
/* Create additional entry point, to switch from interpreter to JIT-ed
* code at run-time.
*/
int b = ssa->cfg.map[rt_opline - op_array->opcodes];
//#ifdef CONTEXT_THREADED_JIT
// if (!(ssa->cfg.blocks[b].flags & (ZEND_BB_START|ZEND_BB_RECV_ENTRY))) {
//#else
if (!(ssa->cfg.blocks[b].flags & (ZEND_BB_START|ZEND_BB_ENTRY|ZEND_BB_RECV_ENTRY))) {
//#endif
zend_jit_label(dasm_state, ssa->cfg.blocks_count + b);
zend_jit_prologue(dasm_state);
if (ra) {
int i;
zend_lifetime_interval *ival;
zend_life_range *range;
uint32_t pos = rt_opline - op_array->opcodes;
for (i = 0; i < ssa->vars_count; i++) {
ival = ra[i];
if (ival && ival->reg != ZREG_NONE) {
range = &ival->range;
if (pos >= range->start && pos <= range->end) {
if (!zend_jit_load_var(dasm_state, ssa->var_info[i].type, ssa->vars[i].var, ival->reg)) {
return NULL;
}
break;
}
range = range->next;
}
}
}
zend_jit_jmp(dasm_state, b);
}
}
ret = dasm_link(dasm_state, &size);
if (ret != DASM_S_OK) {
#if ZEND_DEBUG
handle_dasm_error(ret);
#endif
return NULL;
}
if ((void*)((char*)*dasm_ptr + size) > dasm_end) {
*dasm_ptr = dasm_end; //prevent further try
// TODO: jit_buffer_size overflow ???
return NULL;
}
#if ZEND_JIT_TARGET_ARM64
dasm_venners_size = 0;
#endif
ret = dasm_encode(dasm_state, *dasm_ptr);
if (ret != DASM_S_OK) {
#if ZEND_DEBUG
handle_dasm_error(ret);
#endif
return NULL;
}
#if ZEND_JIT_TARGET_ARM64
size += dasm_venners_size;
#endif
entry = *dasm_ptr;
*dasm_ptr = (void*)((char*)*dasm_ptr + ZEND_MM_ALIGNED_SIZE_EX(size, DASM_ALIGNMENT));
/* flush the hardware I-cache */
JIT_CACHE_FLUSH(entry, entry + size);
if (trace_num) {
zend_jit_trace_add_code(entry, dasm_getpclabel(dasm_state, 1));
}
if (op_array && ssa) {
int b;
for (b = 0; b < ssa->cfg.blocks_count; b++) {
//#ifdef CONTEXT_THREADED_JIT
// if (ssa->cfg.blocks[b].flags & (ZEND_BB_START|ZEND_BB_RECV_ENTRY)) {
//#else
if (ssa->cfg.blocks[b].flags & (ZEND_BB_START|ZEND_BB_ENTRY|ZEND_BB_RECV_ENTRY)) {
//#endif
zend_op *opline = op_array->opcodes + ssa->cfg.blocks[b].start;
int offset = dasm_getpclabel(dasm_state, ssa->cfg.blocks_count + b);
if (offset >= 0) {
opline->handler = (void*)(((char*)entry) + offset);
}
}
}
if (rt_opline && ssa && ssa->cfg.map) {
int b = ssa->cfg.map[rt_opline - op_array->opcodes];
zend_op *opline = (zend_op*)rt_opline;
int offset = dasm_getpclabel(dasm_state, ssa->cfg.blocks_count + b);
if (offset >= 0) {
opline->handler = (void*)(((char*)entry) + offset);
}
}
}
#if defined(HAVE_DISASM) || defined(HAVE_GDB) || defined(HAVE_PERFTOOLS) || defined(HAVE_VTUNE)
if (!name) {
if (JIT_G(debug) & (ZEND_JIT_DEBUG_ASM|ZEND_JIT_DEBUG_GDB|ZEND_JIT_DEBUG_PERF|ZEND_JIT_DEBUG_VTUNE|ZEND_JIT_DEBUG_PERF_DUMP)) {
str = zend_jit_func_name(op_array);
if (str) {
name = ZSTR_VAL(str);
}
}
#ifdef HAVE_DISASM
if (JIT_G(debug) & ZEND_JIT_DEBUG_ASM) {
zend_jit_disasm_add_symbol(name, (uintptr_t)entry, size);
zend_jit_disasm(
name,
(op_array && op_array->filename) ? ZSTR_VAL(op_array->filename) : NULL,
op_array,
&ssa->cfg,
entry,
size);
}
} else {
if (JIT_G(debug) & (ZEND_JIT_DEBUG_ASM_STUBS|ZEND_JIT_DEBUG_ASM)) {
zend_jit_disasm_add_symbol(name, (uintptr_t)entry, size);
if ((JIT_G(debug) & (trace_num ? ZEND_JIT_DEBUG_ASM : ZEND_JIT_DEBUG_ASM_STUBS)) != 0) {
zend_jit_disasm(
name,
(op_array && op_array->filename) ? ZSTR_VAL(op_array->filename) : NULL,
op_array,
ssa ? &ssa->cfg : NULL,
entry,
size);
}
}
# endif
}
#endif
#ifdef HAVE_GDB
if (JIT_G(debug) & ZEND_JIT_DEBUG_GDB) {
if (name) {
zend_jit_gdb_register(
name,
op_array,
entry,
size,
sp_adj[sp_offset],
sp_adj[sp_adjustment]);
}
}
#endif
#ifdef HAVE_PERFTOOLS
if (JIT_G(debug) & (ZEND_JIT_DEBUG_PERF|ZEND_JIT_DEBUG_PERF_DUMP)) {
if (name) {
zend_jit_perf_map_register(
name,
entry,
size);
if (JIT_G(debug) & ZEND_JIT_DEBUG_PERF_DUMP) {
zend_jit_perf_jitdump_register(
name,
entry,
size);
}
}
}
#endif
#ifdef HAVE_VTUNE
if (JIT_G(debug) & ZEND_JIT_DEBUG_VTUNE) {
if (name) {
zend_jit_vtune_register(
name,
entry,
size);
}
}
#endif
#if defined(HAVE_DISASM) || defined(HAVE_GDB) || defined(HAVE_PERFTOOLS) || defined(HAVE_VTUNE)
if (str) {
zend_string_release(str);
}
#endif
return entry;
}
static int zend_may_overflow(const zend_op *opline, const zend_ssa_op *ssa_op, const zend_op_array *op_array, zend_ssa *ssa)
{
int res;
zend_long op1_min, op1_max, op2_min, op2_max;
if (!ssa->ops || !ssa->var_info) {
return 1;
}
switch (opline->opcode) {
case ZEND_PRE_INC:
case ZEND_POST_INC:
res = ssa_op->op1_def;
if (res < 0
|| !ssa->var_info[res].has_range
|| ssa->var_info[res].range.overflow) {
if (!OP1_HAS_RANGE()) {
return 1;
}
op1_max = OP1_MAX_RANGE();
if (op1_max == ZEND_LONG_MAX) {
return 1;
}
}
return 0;
case ZEND_PRE_DEC:
case ZEND_POST_DEC:
res = ssa_op->op1_def;
if (res < 0
|| !ssa->var_info[res].has_range
|| ssa->var_info[res].range.underflow) {
if (!OP1_HAS_RANGE()) {
return 1;
}
op1_min = OP1_MIN_RANGE();
if (op1_min == ZEND_LONG_MIN) {
return 1;
}
}
return 0;
case ZEND_ADD:
res = ssa_op->result_def;
if (res < 0
|| !ssa->var_info[res].has_range
|| ssa->var_info[res].range.underflow) {
if (!OP1_HAS_RANGE() || !OP2_HAS_RANGE()) {
return 1;
}
op1_min = OP1_MIN_RANGE();
op2_min = OP2_MIN_RANGE();
if (zend_add_will_overflow(op1_min, op2_min)) {
return 1;
}
}
if (res < 0
|| !ssa->var_info[res].has_range
|| ssa->var_info[res].range.overflow) {
if (!OP1_HAS_RANGE() || !OP2_HAS_RANGE()) {
return 1;
}
op1_max = OP1_MAX_RANGE();
op2_max = OP2_MAX_RANGE();
if (zend_add_will_overflow(op1_max, op2_max)) {
return 1;
}
}
return 0;
case ZEND_SUB:
res = ssa_op->result_def;
if (res < 0
|| !ssa->var_info[res].has_range
|| ssa->var_info[res].range.underflow) {
if (!OP1_HAS_RANGE() || !OP2_HAS_RANGE()) {
return 1;
}
op1_min = OP1_MIN_RANGE();
op2_max = OP2_MAX_RANGE();
if (zend_sub_will_overflow(op1_min, op2_max)) {
return 1;
}
}
if (res < 0
|| !ssa->var_info[res].has_range
|| ssa->var_info[res].range.overflow) {
if (!OP1_HAS_RANGE() || !OP2_HAS_RANGE()) {
return 1;
}
op1_max = OP1_MAX_RANGE();
op2_min = OP2_MIN_RANGE();
if (zend_sub_will_overflow(op1_max, op2_min)) {
return 1;
}
}
return 0;
case ZEND_MUL:
res = ssa_op->result_def;
return (res < 0 ||
!ssa->var_info[res].has_range ||
ssa->var_info[res].range.underflow ||
ssa->var_info[res].range.overflow);
case ZEND_ASSIGN_OP:
if (opline->extended_value == ZEND_ADD) {
res = ssa_op->op1_def;
if (res < 0
|| !ssa->var_info[res].has_range
|| ssa->var_info[res].range.underflow) {
if (!OP1_HAS_RANGE() || !OP2_HAS_RANGE()) {
return 1;
}
op1_min = OP1_MIN_RANGE();
op2_min = OP2_MIN_RANGE();
if (zend_add_will_overflow(op1_min, op2_min)) {
return 1;
}
}
if (res < 0
|| !ssa->var_info[res].has_range
|| ssa->var_info[res].range.overflow) {
if (!OP1_HAS_RANGE() || !OP2_HAS_RANGE()) {
return 1;
}
op1_max = OP1_MAX_RANGE();
op2_max = OP2_MAX_RANGE();
if (zend_add_will_overflow(op1_max, op2_max)) {
return 1;
}
}
return 0;
} else if (opline->extended_value == ZEND_SUB) {
res = ssa_op->op1_def;
if (res < 0
|| !ssa->var_info[res].has_range
|| ssa->var_info[res].range.underflow) {
if (!OP1_HAS_RANGE() || !OP2_HAS_RANGE()) {
return 1;
}
op1_min = OP1_MIN_RANGE();
op2_max = OP2_MAX_RANGE();
if (zend_sub_will_overflow(op1_min, op2_max)) {
return 1;
}
}
if (res < 0
|| !ssa->var_info[res].has_range
|| ssa->var_info[res].range.overflow) {
if (!OP1_HAS_RANGE() || !OP2_HAS_RANGE()) {
return 1;
}
op1_max = OP1_MAX_RANGE();
op2_min = OP2_MIN_RANGE();
if (zend_sub_will_overflow(op1_max, op2_min)) {
return 1;
}
}
return 0;
} else if (opline->extended_value == ZEND_MUL) {
res = ssa_op->op1_def;
return (res < 0 ||
!ssa->var_info[res].has_range ||
ssa->var_info[res].range.underflow ||
ssa->var_info[res].range.overflow);
}
ZEND_FALLTHROUGH;
default:
return 1;
}
}
static int zend_jit_build_cfg(const zend_op_array *op_array, zend_cfg *cfg)
{
uint32_t flags;
flags = ZEND_CFG_STACKLESS | ZEND_CFG_NO_ENTRY_PREDECESSORS | ZEND_SSA_RC_INFERENCE_FLAG | ZEND_SSA_USE_CV_RESULTS | ZEND_CFG_RECV_ENTRY;
zend_build_cfg(&CG(arena), op_array, flags, cfg);
/* Don't JIT huge functions. Apart from likely being detrimental due to the amount of
* generated code, some of our analysis is recursive and will stack overflow with many
* blocks. */
if (cfg->blocks_count > 100000) {
return FAILURE;
}
zend_cfg_build_predecessors(&CG(arena), cfg);
/* Compute Dominators Tree */
zend_cfg_compute_dominators_tree(op_array, cfg);
/* Identify reducible and irreducible loops */
zend_cfg_identify_loops(op_array, cfg);
return SUCCESS;
}
static int zend_jit_op_array_analyze1(const zend_op_array *op_array, zend_script *script, zend_ssa *ssa)
{
if (zend_jit_build_cfg(op_array, &ssa->cfg) != SUCCESS) {
return FAILURE;
}
#if 0
/* TODO: debugger and profiler supports? */
if ((ssa->cfg.flags & ZEND_FUNC_HAS_EXTENDED_INFO)) {
return FAILURE;
}
#endif
/* TODO: move this to zend_cfg.c ? */
if (!op_array->function_name) {
ssa->cfg.flags |= ZEND_FUNC_INDIRECT_VAR_ACCESS;
}
if ((JIT_G(opt_level) >= ZEND_JIT_LEVEL_OPT_FUNC)
&& ssa->cfg.blocks
&& op_array->last_try_catch == 0
&& !(op_array->fn_flags & ZEND_ACC_GENERATOR)
&& !(ssa->cfg.flags & ZEND_FUNC_INDIRECT_VAR_ACCESS)) {
if (zend_build_ssa(&CG(arena), script, op_array, ZEND_SSA_RC_INFERENCE | ZEND_SSA_USE_CV_RESULTS, ssa) != SUCCESS) {
return FAILURE;
}
zend_ssa_compute_use_def_chains(&CG(arena), op_array, ssa);
zend_ssa_find_false_dependencies(op_array, ssa);
zend_ssa_find_sccs(op_array, ssa);
}
return SUCCESS;
}
static int zend_jit_op_array_analyze2(const zend_op_array *op_array, zend_script *script, zend_ssa *ssa, uint32_t optimization_level)
{
if ((JIT_G(opt_level) >= ZEND_JIT_LEVEL_OPT_FUNC)
&& ssa->cfg.blocks
&& op_array->last_try_catch == 0
&& !(op_array->fn_flags & ZEND_ACC_GENERATOR)
&& !(ssa->cfg.flags & ZEND_FUNC_INDIRECT_VAR_ACCESS)) {
if (zend_ssa_inference(&CG(arena), op_array, script, ssa,
optimization_level & ~ZEND_OPTIMIZER_NARROW_TO_DOUBLE) != SUCCESS) {
return FAILURE;
}
}
return SUCCESS;
}
static int zend_jit_add_range(zend_lifetime_interval **intervals, int var, uint32_t from, uint32_t to)
{
zend_lifetime_interval *ival = intervals[var];
if (!ival) {
ival = zend_arena_alloc(&CG(arena), sizeof(zend_lifetime_interval));
if (!ival) {
return FAILURE;
}
ival->ssa_var = var;
ival->reg = ZREG_NONE;
ival->flags = 0;
ival->range.start = from;
ival->range.end = to;
ival->range.next = NULL;
ival->hint = NULL;
ival->used_as_hint = NULL;
intervals[var] = ival;
} else if (ival->range.start > to + 1) {
zend_life_range *range = zend_arena_alloc(&CG(arena), sizeof(zend_life_range));
if (!range) {
return FAILURE;
}
range->start = ival->range.start;
range->end = ival->range.end;
range->next = ival->range.next;
ival->range.start = from;
ival->range.end = to;
ival->range.next = range;
} else if (ival->range.start == to + 1) {
ival->range.start = from;
} else {
zend_life_range *range = &ival->range;
zend_life_range *last = NULL;
do {
if (range->start > to + 1) {
break;
} else if (range->end + 1 >= from) {
if (range->start > from) {
range->start = from;
}
last = range;
range = range->next;
while (range) {
if (range->start > to + 1) {
break;
}
last->end = range->end;
range = range->next;
last->next = range;
}
if (to > last->end) {
last->end = to;
}
return SUCCESS;
}
last = range;
range = range->next;
} while (range);
range = zend_arena_alloc(&CG(arena), sizeof(zend_life_range));
if (!range) {
return FAILURE;
}
range->start = from;
range->end = to;
range->next = last->next;
last->next = range;
}
return SUCCESS;
}
static int zend_jit_begin_range(zend_lifetime_interval **intervals, int var, uint32_t block_start, uint32_t from)
{
if (block_start != from && intervals[var]) {
zend_life_range *range = &intervals[var]->range;
do {
if (from >= range->start && from <= range->end) {
if (range->start == block_start) {
range->start = from;
} else {
zend_life_range *r = zend_arena_alloc(&CG(arena), sizeof(zend_life_range));
if (!r) {
return FAILURE;
}
r->start = from;
r->end = range->end;
r->next = range->next;
range->end = block_start - 1;
range->next = r;
}
return SUCCESS;
}
range = range->next;
} while (range);
}
// dead store
return zend_jit_add_range(intervals, var, from, from);
}
static void zend_jit_insert_interval(zend_lifetime_interval **list, zend_lifetime_interval *ival)
{
while (1) {
if (*list == NULL) {
*list = ival;
ival->list_next = NULL;
return;
} else if (ival->range.start < (*list)->range.start) {
ival->list_next = *list;
*list = ival;
return;
}
list = &(*list)->list_next;
}
}
static int zend_jit_split_interval(zend_lifetime_interval *current, uint32_t pos, zend_lifetime_interval **list, zend_lifetime_interval **free)
{
zend_lifetime_interval *ival;
zend_life_range *range = &current->range;
zend_life_range *prev = NULL;
if (*free) {
ival = *free;
*free = ival->list_next;
} else {
ival = zend_arena_alloc(&CG(arena), sizeof(zend_lifetime_interval));
if (!ival) {
return FAILURE;
}
}
current->flags |= ZREG_STORE;
ival->ssa_var = current->ssa_var;
ival->reg = ZREG_NONE;
ival->flags |= ZREG_SPLIT | ZREG_LOAD;
ival->flags &= ~ZREG_STORE;
ival->hint = NULL;
do {
if (pos >= range->start && pos <= range->end) {
break;
}
prev = range;
range = range->next;
} while(range);
ZEND_ASSERT(range != NULL);
ival->range.start = pos;
ival->range.end = range->end;
ival->range.next = range->next;
if (pos == range->start) {
ZEND_ASSERT(prev != NULL);
prev->next = NULL;
} else {
range->end = pos - 1;
}
zend_jit_insert_interval(list, ival);
return SUCCESS;
}
static zend_lifetime_interval *zend_jit_sort_intervals(zend_lifetime_interval **intervals, int count)
{
zend_lifetime_interval *list, *last;
int i;
list = NULL;
i = 0;
while (i < count) {
list = intervals[i];
i++;
if (list) {
last = list;
last->list_next = NULL;
break;
}
}
while (i < count) {
zend_lifetime_interval *ival = intervals[i];
i++;
if (ival) {
if ((ival->range.start > last->range.start) ||
(ival->range.start == last->range.start &&
((!ival->hint && last->hint && last->hint != ival) ||
ival->range.end > last->range.end))) {
last->list_next = ival;
last = ival;
ival->list_next = NULL;
} else {
zend_lifetime_interval **p = &list;
while (1) {
if (*p == NULL) {
*p = last = ival;
ival->list_next = NULL;
break;
} else if ((ival->range.start < (*p)->range.start) ||
(ival->range.start == (*p)->range.start &&
((ival->hint && !(*p)->hint && ival->hint != *p) ||
ival->range.end < (*p)->range.end))) {
ival->list_next = *p;
*p = ival;
break;
}
p = &(*p)->list_next;
}
}
}
}
return list;
}
static ZEND_ATTRIBUTE_UNUSED void zend_jit_print_regset(zend_regset regset)
{
zend_reg reg;
int first = 1;
ZEND_REGSET_FOREACH(regset, reg) {
if (first) {
first = 0;
fprintf(stderr, "%s", zend_reg_name[reg]);
} else {
fprintf(stderr, ", %s", zend_reg_name[reg]);
}
} ZEND_REGSET_FOREACH_END();
}
static int *zend_jit_compute_block_order_int(zend_ssa *ssa, int n, int *block_order)
{
zend_basic_block *b = ssa->cfg.blocks + n;
tail_call:
*block_order = n;
block_order++;
n = b->children;
while (n >= 0) {
b = ssa->cfg.blocks + n;
if (b->next_child < 0) {
goto tail_call;
}
block_order = zend_jit_compute_block_order_int(ssa, n, block_order);
n = b->next_child;
}
return block_order;
}
static int zend_jit_compute_block_order(zend_ssa *ssa, int *block_order)
{
int *end = zend_jit_compute_block_order_int(ssa, 0, block_order);
return end - block_order;
}
static bool zend_jit_in_loop(zend_ssa *ssa, int header, zend_basic_block *b)
{
while (b->loop_header >= 0) {
if (b->loop_header == header) {
return 1;
}
b = ssa->cfg.blocks + b->loop_header;
}
return 0;
}
static void zend_jit_compute_loop_body(zend_ssa *ssa, int header, int n, zend_bitset loop_body)
{
zend_basic_block *b = ssa->cfg.blocks + n;
uint32_t i;
tail_call:
if (b->len) {
for (i = b->start; i < b->start + b->len; i++) {
zend_bitset_incl(loop_body, i);
}
}
n = b->children;
while (n >= 0) {
b = ssa->cfg.blocks + n;
if (zend_jit_in_loop(ssa, header, b)) {
if (b->next_child < 0) {
goto tail_call;
}
zend_jit_compute_loop_body(ssa, header, n, loop_body);
}
n = b->next_child;
}
}
static void zend_jit_add_hint(zend_lifetime_interval **intervals, int dst, int src)
{
if (intervals[dst]->range.start < intervals[src]->range.start) {
int tmp = src;
src = dst;
dst = tmp;
}
while (dst != src && intervals[dst]->hint) {
if (intervals[dst]->hint->range.start < intervals[src]->range.start) {
int tmp = src;
src = intervals[dst]->hint->ssa_var;
dst = tmp;
} else {
dst = intervals[dst]->hint->ssa_var;
}
}
if (dst != src) {
intervals[dst]->hint = intervals[src];
}
}
/* See "Linear Scan Register Allocation on SSA Form", Christian Wimmer and
Michael Franz, CGO'10 (2010), Figure 4. */
static int zend_jit_compute_liveness(const zend_op_array *op_array, zend_ssa *ssa, zend_bitset candidates, zend_lifetime_interval **list)
{
int set_size, i, j, k, l;
uint32_t n;
zend_bitset live, live_in, pi_vars, loop_body;
int *block_order;
zend_ssa_phi *phi;
zend_lifetime_interval **intervals;
size_t mem_size;
ALLOCA_FLAG(use_heap);
set_size = zend_bitset_len(ssa->vars_count);
mem_size =
ZEND_MM_ALIGNED_SIZE(ssa->vars_count * sizeof(zend_lifetime_interval*)) +
ZEND_MM_ALIGNED_SIZE((set_size * ssa->cfg.blocks_count) * ZEND_BITSET_ELM_SIZE) +
ZEND_MM_ALIGNED_SIZE(set_size * ZEND_BITSET_ELM_SIZE) +
ZEND_MM_ALIGNED_SIZE(set_size * ZEND_BITSET_ELM_SIZE) +
ZEND_MM_ALIGNED_SIZE(zend_bitset_len(op_array->last) * ZEND_BITSET_ELM_SIZE) +
ZEND_MM_ALIGNED_SIZE(ssa->cfg.blocks_count * sizeof(int));
intervals = do_alloca(mem_size, use_heap);
if (!intervals) {
*list = NULL;
return FAILURE;
}
live_in = (zend_bitset)((char*)intervals + ZEND_MM_ALIGNED_SIZE(ssa->vars_count * sizeof(zend_lifetime_interval*)));
live = (zend_bitset)((char*)live_in + ZEND_MM_ALIGNED_SIZE((set_size * ssa->cfg.blocks_count) * ZEND_BITSET_ELM_SIZE));
pi_vars = (zend_bitset)((char*)live + ZEND_MM_ALIGNED_SIZE(set_size * ZEND_BITSET_ELM_SIZE));
loop_body = (zend_bitset)((char*)pi_vars + ZEND_MM_ALIGNED_SIZE(set_size * ZEND_BITSET_ELM_SIZE));
block_order = (int*)((char*)loop_body + ZEND_MM_ALIGNED_SIZE(zend_bitset_len(op_array->last) * ZEND_BITSET_ELM_SIZE));
memset(intervals, 0, ssa->vars_count * sizeof(zend_lifetime_interval*));
zend_bitset_clear(live_in, set_size * ssa->cfg.blocks_count);
/* TODO: Provide a linear block order where all dominators of a block
* are before this block, and where all blocks belonging to the same loop
* are contiguous ???
*/
for (l = zend_jit_compute_block_order(ssa, block_order) - 1; l >= 0; l--) {
zend_basic_block *b;
i = block_order[l];
b = ssa->cfg.blocks + i;
/* live = UNION of successor.liveIn for each successor of b */
/* live.add(phi.inputOf(b)) for each phi of successors of b */
zend_bitset_clear(live, set_size);
for (j = 0; j < b->successors_count; j++) {
int succ = b->successors[j];
zend_bitset_union(live, live_in + set_size * succ, set_size);
zend_bitset_clear(pi_vars, set_size);
for (phi = ssa->blocks[succ].phis; phi; phi = phi->next) {
if (ssa->vars[phi->ssa_var].no_val) {
/* skip */
} else if (phi->pi >= 0) {
if (phi->pi == i && phi->sources[0] >= 0) {
if (zend_bitset_in(candidates, phi->sources[0])) {
zend_bitset_incl(live, phi->sources[0]);
}
zend_bitset_incl(pi_vars, phi->var);
}
} else if (!zend_bitset_in(pi_vars, phi->var)) {
for (k = 0; k < ssa->cfg.blocks[succ].predecessors_count; k++) {
if (ssa->cfg.predecessors[ssa->cfg.blocks[succ].predecessor_offset + k] == i) {
if (phi->sources[k] >= 0 && zend_bitset_in(candidates, phi->sources[k])) {
zend_bitset_incl(live, phi->sources[k]);
}
break;
}
}
}
}
}
/* addRange(var, b.from, b.to) for each var in live */
ZEND_BITSET_FOREACH(live, set_size, j) {
if (zend_bitset_in(candidates, j)) {
if (zend_jit_add_range(intervals, j, b->start, b->start + b->len - 1) != SUCCESS) {
goto failure;
}
}
} ZEND_BITSET_FOREACH_END();
/* for each operation op of b in reverse order */
for (n = b->start + b->len; n > b->start;) {
zend_ssa_op *op;
const zend_op *opline;
uint32_t num;
n--;
op = ssa->ops + n;
opline = op_array->opcodes + n;
if (UNEXPECTED(opline->opcode == ZEND_OP_DATA)) {
num = n - 1;
} else {
num = n;
}
/* for each output operand opd of op do */
/* setFrom(opd, op) */
/* live.remove(opd) */
if (op->op1_def >= 0 && zend_bitset_in(candidates, op->op1_def)) {
if (zend_jit_begin_range(intervals, op->op1_def, b->start, num) != SUCCESS) {
goto failure;
}
zend_bitset_excl(live, op->op1_def);
}
if (op->op2_def >= 0 && zend_bitset_in(candidates, op->op2_def)) {
if (zend_jit_begin_range(intervals, op->op2_def, b->start, num) != SUCCESS) {
goto failure;
}
zend_bitset_excl(live, op->op2_def);
}
if (op->result_def >= 0 && zend_bitset_in(candidates, op->result_def)) {
if (zend_jit_begin_range(intervals, op->result_def, b->start, num) != SUCCESS) {
goto failure;
}
zend_bitset_excl(live, op->result_def);
}
/* for each input operand opd of op do */
/* live.add(opd) */
/* addRange(opd, b.from, op) */
if (op->op1_use >= 0
&& zend_bitset_in(candidates, op->op1_use)
&& !zend_ssa_is_no_val_use(opline, op, op->op1_use)) {
zend_bitset_incl(live, op->op1_use);
if (zend_jit_add_range(intervals, op->op1_use, b->start, num) != SUCCESS) {
goto failure;
}
}
if (op->op2_use >= 0
&& zend_bitset_in(candidates, op->op2_use)
&& !zend_ssa_is_no_val_use(opline, op, op->op2_use)) {
zend_bitset_incl(live, op->op2_use);
if (zend_jit_add_range(intervals, op->op2_use, b->start, num) != SUCCESS) {
goto failure;
}
}
if (op->result_use >= 0
&& zend_bitset_in(candidates, op->result_use)
&& !zend_ssa_is_no_val_use(opline, op, op->result_use)) {
zend_bitset_incl(live, op->result_use);
if (zend_jit_add_range(intervals, op->result_use, b->start, num) != SUCCESS) {
goto failure;
}
}
}
/* live.remove(phi.output) for each phi of b */
for (phi = ssa->blocks[i].phis; phi; phi = phi->next) {
zend_bitset_excl(live, phi->ssa_var);
}
/* b.liveIn = live */
zend_bitset_copy(live_in + set_size * i, live, set_size);
}
for (i = ssa->cfg.blocks_count - 1; i >= 0; i--) {
zend_basic_block *b = ssa->cfg.blocks + i;
/* if b is loop header */
if ((b->flags & ZEND_BB_LOOP_HEADER)) {
live = live_in + set_size * i;
if (!zend_bitset_empty(live, set_size)) {
uint32_t set_size2 = zend_bitset_len(op_array->last);
zend_bitset_clear(loop_body, set_size2);
zend_jit_compute_loop_body(ssa, i, i, loop_body);
while (!zend_bitset_empty(loop_body, set_size2)) {
uint32_t from = zend_bitset_first(loop_body, set_size2);
uint32_t to = from;
do {
zend_bitset_excl(loop_body, to);
to++;
} while (zend_bitset_in(loop_body, to));
to--;
ZEND_BITSET_FOREACH(live, set_size, j) {
if (zend_jit_add_range(intervals, j, from, to) != SUCCESS) {
goto failure;
}
} ZEND_BITSET_FOREACH_END();
}
}
}
}
if (JIT_G(opt_flags) & ZEND_JIT_REG_ALLOC_GLOBAL) {
/* Register hinting (a cheap way for register coalescing) */
for (i = 0; i < ssa->vars_count; i++) {
if (intervals[i]) {
int src;
if (ssa->vars[i].definition_phi) {
zend_ssa_phi *phi = ssa->vars[i].definition_phi;
if (phi->pi >= 0) {
src = phi->sources[0];
if (intervals[src]) {
zend_jit_add_hint(intervals, i, src);
}
} else {
for (k = 0; k < ssa->cfg.blocks[phi->block].predecessors_count; k++) {
src = phi->sources[k];
if (src >= 0) {
if (ssa->vars[src].definition_phi
&& ssa->vars[src].definition_phi->pi >= 0
&& phi->block == ssa->vars[src].definition_phi->block) {
/* Skip zero-length interval for Pi variable */
src = ssa->vars[src].definition_phi->sources[0];
}
if (intervals[src]) {
zend_jit_add_hint(intervals, i, src);
}
}
}
}
}
}
}
for (i = 0; i < ssa->vars_count; i++) {
if (intervals[i] && !intervals[i]->hint) {
if (ssa->vars[i].definition >= 0) {
uint32_t line = ssa->vars[i].definition;
const zend_op *opline = op_array->opcodes + line;
switch (opline->opcode) {
case ZEND_QM_ASSIGN:
case ZEND_POST_INC:
case ZEND_POST_DEC:
if (ssa->ops[line].op1_use >= 0 &&
intervals[ssa->ops[line].op1_use] &&
(i == ssa->ops[line].op1_def ||
(i == ssa->ops[line].result_def &&
(ssa->ops[line].op1_def < 0 ||
!intervals[ssa->ops[line].op1_def])))) {
zend_jit_add_hint(intervals, i, ssa->ops[line].op1_use);
}
break;
case ZEND_SEND_VAR:
case ZEND_PRE_INC:
case ZEND_PRE_DEC:
if (i == ssa->ops[line].op1_def &&
ssa->ops[line].op1_use >= 0 &&
intervals[ssa->ops[line].op1_use]) {
zend_jit_add_hint(intervals, i, ssa->ops[line].op1_use);
}
break;
case ZEND_ASSIGN:
if (ssa->ops[line].op2_use >= 0 &&
intervals[ssa->ops[line].op2_use] &&
(i == ssa->ops[line].op2_def ||
(i == ssa->ops[line].op1_def &&
(ssa->ops[line].op2_def < 0 ||
!intervals[ssa->ops[line].op2_def])) ||
(i == ssa->ops[line].result_def &&
(ssa->ops[line].op2_def < 0 ||
!intervals[ssa->ops[line].op2_def]) &&
(ssa->ops[line].op1_def < 0 ||
!intervals[ssa->ops[line].op1_def])))) {
zend_jit_add_hint(intervals, i, ssa->ops[line].op2_use);
}
break;
case ZEND_SUB:
case ZEND_ADD:
case ZEND_MUL:
case ZEND_BW_OR:
case ZEND_BW_AND:
case ZEND_BW_XOR:
if (i == ssa->ops[line].result_def) {
if (ssa->ops[line].op1_use >= 0 &&
intervals[ssa->ops[line].op1_use] &&
ssa->ops[line].op1_use_chain < 0 &&
!ssa->vars[ssa->ops[line].op1_use].phi_use_chain &&
(ssa->var_info[i].type & MAY_BE_ANY) ==
(ssa->var_info[ssa->ops[line].op1_use].type & MAY_BE_ANY)) {
zend_jit_add_hint(intervals, i, ssa->ops[line].op1_use);
} else if (opline->opcode != ZEND_SUB &&
ssa->ops[line].op2_use >= 0 &&
intervals[ssa->ops[line].op2_use] &&
ssa->ops[line].op2_use_chain < 0 &&
!ssa->vars[ssa->ops[line].op2_use].phi_use_chain &&
(ssa->var_info[i].type & MAY_BE_ANY) ==
(ssa->var_info[ssa->ops[line].op2_use].type & MAY_BE_ANY)) {
zend_jit_add_hint(intervals, i, ssa->ops[line].op2_use);
}
}
break;
}
}
}
}
}
*list = zend_jit_sort_intervals(intervals, ssa->vars_count);
if (*list) {
zend_lifetime_interval *ival = *list;
while (ival) {
if (ival->hint) {
ival->hint->used_as_hint = ival;
}
ival = ival->list_next;
}
}
free_alloca(intervals, use_heap);
return SUCCESS;
failure:
*list = NULL;
free_alloca(intervals, use_heap);
return FAILURE;
}
static uint32_t zend_interval_end(zend_lifetime_interval *ival)
{
zend_life_range *range = &ival->range;
while (range->next) {
range = range->next;
}
return range->end;
}
static bool zend_interval_covers(zend_lifetime_interval *ival, uint32_t position)
{
zend_life_range *range = &ival->range;
do {
if (position >= range->start && position <= range->end) {
return 1;
}
range = range->next;
} while (range);
return 0;
}
static uint32_t zend_interval_intersection(zend_lifetime_interval *ival1, zend_lifetime_interval *ival2)
{
zend_life_range *r1 = &ival1->range;
zend_life_range *r2 = &ival2->range;
do {
if (r1->start <= r2->end) {
if (r2->start <= r1->end) {
return MAX(r1->start, r2->start);
} else {
r2 = r2->next;
}
} else {
r1 = r1->next;
}
} while (r1 && r2);
return 0xffffffff;
}
/* See "Optimized Interval Splitting in a Linear Scan Register Allocator",
Christian Wimmer VEE'05 (2005), Figure 4. Allocation without spilling */
static int zend_jit_try_allocate_free_reg(const zend_op_array *op_array, const zend_op **ssa_opcodes, zend_ssa *ssa, zend_lifetime_interval *current, zend_regset available, zend_regset *hints, zend_lifetime_interval *active, zend_lifetime_interval *inactive, zend_lifetime_interval **list, zend_lifetime_interval **free)
{
zend_lifetime_interval *it;
uint32_t freeUntilPos[ZREG_NUM];
uint32_t pos, pos2;
zend_reg i, reg, reg2;
zend_reg hint = ZREG_NONE;
zend_regset low_priority_regs;
zend_life_range *range;
if ((ssa->var_info[current->ssa_var].type & MAY_BE_ANY) == MAY_BE_DOUBLE) {
available = ZEND_REGSET_INTERSECTION(available, ZEND_REGSET_FP);
} else {
available = ZEND_REGSET_INTERSECTION(available, ZEND_REGSET_GP);
}
/* TODO: Allow usage of preserved registers ???
* Their values have to be stored in prologue and restored in epilogue
*/
available = ZEND_REGSET_DIFFERENCE(available, ZEND_REGSET_PRESERVED);
/* Set freeUntilPos of all physical registers to maxInt */
for (i = 0; i < ZREG_NUM; i++) {
freeUntilPos[i] = 0xffffffff;
}
/* for each interval it in active do */
/* freeUntilPos[it.reg] = 0 */
it = active;
if (ssa->vars[current->ssa_var].definition == current->range.start) {
while (it) {
if (current->range.start != zend_interval_end(it)) {
freeUntilPos[it->reg] = 0;
} else if (zend_jit_may_reuse_reg(
ssa_opcodes ? ssa_opcodes[current->range.start] : op_array->opcodes + current->range.start,
ssa->ops + current->range.start, ssa, current->ssa_var, it->ssa_var)) {
if (!ZEND_REGSET_IN(*hints, it->reg) &&
/* TODO: Avoid most often scratch registers. Find a better way ??? */
(!current->used_as_hint ||
!ZEND_REGSET_IN(ZEND_REGSET_LOW_PRIORITY, it->reg))) {
hint = it->reg;
}
} else {
freeUntilPos[it->reg] = 0;
}
it = it->list_next;
}
} else {
while (it) {
freeUntilPos[it->reg] = 0;
it = it->list_next;
}
}
if (current->hint) {
hint = current->hint->reg;
if (hint != ZREG_NONE && current->hint->used_as_hint == current) {
ZEND_REGSET_EXCL(*hints, hint);
}
}
if (hint == ZREG_NONE && ZEND_REGSET_IS_EMPTY(available)) {
return 0;
}
/* See "Linear Scan Register Allocation on SSA Form", Christian Wimmer and
Michael Franz, CGO'10 (2010), Figure 6. */
if (current->flags & ZREG_SPLIT) {
/* for each interval it in inactive intersecting with current do */
/* freeUntilPos[it.reg] = next intersection of it with current */
it = inactive;
while (it) {
uint32_t next = zend_interval_intersection(current, it);
//ZEND_ASSERT(next != 0xffffffff && !current->split);
if (next < freeUntilPos[it->reg]) {
freeUntilPos[it->reg] = next;
}
it = it->list_next;
}
}
/* Handle Scratch Registers */
/* TODO: Optimize ??? */
range = &current->range;
do {
uint32_t line = range->start;
uint32_t last_use_line = (uint32_t)-1;
zend_regset regset;
zend_reg reg;
if ((current->flags & ZREG_LAST_USE) && !range->next) {
last_use_line = range->end;
}
if (ssa->ops[line].op1_def == current->ssa_var ||
ssa->ops[line].op2_def == current->ssa_var ||
ssa->ops[line].result_def == current->ssa_var) {
regset = zend_jit_get_def_scratch_regset(
ssa_opcodes ? ssa_opcodes[line] : op_array->opcodes + line,
ssa->ops + line,
op_array, ssa, current->ssa_var, line == last_use_line);
ZEND_REGSET_FOREACH(regset, reg) {
if (line < freeUntilPos[reg]) {
freeUntilPos[reg] = line;
}
} ZEND_REGSET_FOREACH_END();
line++;
}
while (line <= range->end) {
regset = zend_jit_get_scratch_regset(
ssa_opcodes ? ssa_opcodes[line] : op_array->opcodes + line,
ssa->ops + line,
op_array, ssa, current->ssa_var, line == last_use_line);
ZEND_REGSET_FOREACH(regset, reg) {
if (line < freeUntilPos[reg]) {
freeUntilPos[reg] = line;
}
} ZEND_REGSET_FOREACH_END();
line++;
}
range = range->next;
} while (range);
#if 0
/* Coalescing */
if (ssa->vars[current->ssa_var].definition == current->start) {
zend_op *opline = op_array->opcodes + current->start;
int hint = -1;
switch (opline->opcode) {
case ZEND_ASSIGN:
hint = ssa->ops[current->start].op2_use;
case ZEND_QM_ASSIGN:
hint = ssa->ops[current->start].op1_use;
break;
case ZEND_ADD:
case ZEND_SUB:
case ZEND_MUL:
hint = ssa->ops[current->start].op1_use;
break;
case ZEND_ASSIGN_OP:
if (opline->extended_value == ZEND_ADD
|| opline->extended_value == ZEND_SUB
|| opline->extended_value == ZEND_MUL) {
hint = ssa->ops[current->start].op1_use;
}
break;
}
if (hint >= 0) {
}
}
#endif
if (hint != ZREG_NONE && freeUntilPos[hint] > zend_interval_end(current)) {
current->reg = hint;
if (current->used_as_hint) {
ZEND_REGSET_INCL(*hints, hint);
}
return 1;
}
if (ZEND_REGSET_IS_EMPTY(available)) {
return 0;
}
pos = 0; reg = ZREG_NONE;
pos2 = 0; reg2 = ZREG_NONE;
low_priority_regs = *hints;
if (current->used_as_hint) {
/* TODO: Avoid most often scratch registers. Find a better way ??? */
low_priority_regs = ZEND_REGSET_UNION(low_priority_regs, ZEND_REGSET_LOW_PRIORITY);
}
ZEND_REGSET_FOREACH(available, i) {
if (ZEND_REGSET_IN(low_priority_regs, i)) {
if (freeUntilPos[i] > pos2) {
reg2 = i;
pos2 = freeUntilPos[i];
}
} else if (freeUntilPos[i] > pos) {
reg = i;
pos = freeUntilPos[i];
}
} ZEND_REGSET_FOREACH_END();
if (reg == ZREG_NONE) {
if (reg2 != ZREG_NONE) {
reg = reg2;
pos = pos2;
reg2 = ZREG_NONE;
}
}
if (reg == ZREG_NONE) {
/* no register available without spilling */
return 0;
} else if (zend_interval_end(current) < pos) {
/* register available for the whole interval */
current->reg = reg;
if (current->used_as_hint) {
ZEND_REGSET_INCL(*hints, reg);
}
return 1;
#if 0
// TODO: allow low priority register usage
} else if (reg2 != ZREG_NONE && zend_interval_end(current) < pos2) {
/* register available for the whole interval */
current->reg = reg2;
if (current->used_as_hint) {
ZEND_REGSET_INCL(*hints, reg2);
}
return 1;
#endif
} else {
/* TODO: enable interval splitting ??? */
/* register available for the first part of the interval */
if (1 || zend_jit_split_interval(current, pos, list, free) != SUCCESS) {
return 0;
}
current->reg = reg;
if (current->used_as_hint) {
ZEND_REGSET_INCL(*hints, reg);
}
return 1;
}
}
/* See "Optimized Interval Splitting in a Linear Scan Register Allocator",
Christian Wimmer VEE'05 (2005), Figure 5. Allocation with spilling.
and "Linear Scan Register Allocation on SSA Form", Christian Wimmer and
Michael Franz, CGO'10 (2010), Figure 6. */
static int zend_jit_allocate_blocked_reg(void)
{
/* TODO: ??? */
return 0;
}
/* See "Optimized Interval Splitting in a Linear Scan Register Allocator",
Christian Wimmer VEE'10 (2005), Figure 2. */
static zend_lifetime_interval* zend_jit_linear_scan(const zend_op_array *op_array, const zend_op **ssa_opcodes, zend_ssa *ssa, zend_lifetime_interval *list)
{
zend_lifetime_interval *unhandled, *active, *inactive, *handled, *free;
zend_lifetime_interval *current, **p, *q;
uint32_t position;
zend_regset available = ZEND_REGSET_UNION(ZEND_REGSET_GP, ZEND_REGSET_FP);
zend_regset hints = ZEND_REGSET_EMPTY;
unhandled = list;
/* active = inactive = handled = free = {} */
active = inactive = handled = free = NULL;
while (unhandled != NULL) {
current = unhandled;
unhandled = unhandled->list_next;
position = current->range.start;
p = &active;
while (*p) {
uint32_t end = zend_interval_end(*p);
q = *p;
if (end < position) {
/* move ival from active to handled */
ZEND_REGSET_INCL(available, q->reg);
*p = q->list_next;
q->list_next = handled;
handled = q;
} else if (!zend_interval_covers(q, position)) {
/* move ival from active to inactive */
ZEND_REGSET_INCL(available, q->reg);
*p = q->list_next;
q->list_next = inactive;
inactive = q;
} else {
p = &q->list_next;
}
}
p = &inactive;
while (*p) {
uint32_t end = zend_interval_end(*p);
q = *p;
if (end < position) {
/* move ival from inactive to handled */
*p = q->list_next;
q->list_next = handled;
handled = q;
} else if (zend_interval_covers(q, position)) {
/* move ival from inactive to active */
ZEND_REGSET_EXCL(available, q->reg);
*p = q->list_next;
q->list_next = active;
active = q;
} else {
p = &q->list_next;
}
}
if (zend_jit_try_allocate_free_reg(op_array, ssa_opcodes, ssa, current, available, &hints, active, inactive, &unhandled, &free) ||
zend_jit_allocate_blocked_reg()) {
ZEND_REGSET_EXCL(available, current->reg);
current->list_next = active;
active = current;
} else {
current->list_next = free;
free = current;
}
}
/* move active to handled */
while (active) {
current = active;
active = active->list_next;
current->list_next = handled;
handled = current;
}
/* move inactive to handled */
while (inactive) {
current = inactive;
inactive = inactive->list_next;
current->list_next = handled;
handled = current;
}
return handled;
}
static void zend_jit_dump_lifetime_interval(const zend_op_array *op_array, const zend_ssa *ssa, const zend_lifetime_interval *ival)
{
zend_life_range *range;
int var_num = ssa->vars[ival->ssa_var].var;
fprintf(stderr, "#%d.", ival->ssa_var);
zend_dump_var(op_array, (var_num < op_array->last_var ? IS_CV : 0), var_num);
fprintf(stderr, ": %u-%u", ival->range.start, ival->range.end);
range = ival->range.next;
while (range) {
fprintf(stderr, ", %u-%u", range->start, range->end);
range = range->next;
}
if (ival->reg != ZREG_NONE) {
fprintf(stderr, " (%s)", zend_reg_name[ival->reg]);
}
if (ival->flags & ZREG_LAST_USE) {
fprintf(stderr, " last_use");
}
if (ival->flags & ZREG_LOAD) {
fprintf(stderr, " load");
}
if (ival->flags & ZREG_STORE) {
fprintf(stderr, " store");
}
if (ival->hint) {
fprintf(stderr, " hint");
if (ival->hint->ssa_var >= 0) {
var_num = ssa->vars[ival->hint->ssa_var].var;
fprintf(stderr, "=#%d.", ival->hint->ssa_var);
zend_dump_var(op_array, (var_num < op_array->last_var ? IS_CV : 0), var_num);
}
if (ival->hint->reg != ZREG_NONE) {
fprintf(stderr, " (%s)", zend_reg_name[ival->hint->reg]);
}
}
fprintf(stderr, "\n");
}
static zend_lifetime_interval** zend_jit_allocate_registers(const zend_op_array *op_array, zend_ssa *ssa)
{
void *checkpoint;
int set_size, candidates_count, i;
zend_bitset candidates = NULL;
zend_lifetime_interval *list, *ival;
zend_lifetime_interval **intervals;
ALLOCA_FLAG(use_heap);
if (!ssa->var_info) {
return NULL;
}
/* Identify SSA variables suitable for register allocation */
set_size = zend_bitset_len(ssa->vars_count);
candidates = ZEND_BITSET_ALLOCA(set_size, use_heap);
if (!candidates) {
return NULL;
}
candidates_count = 0;
zend_bitset_clear(candidates, set_size);
for (i = 0; i < ssa->vars_count; i++) {
if (zend_jit_may_be_in_reg(op_array, ssa, i)) {
zend_bitset_incl(candidates, i);
candidates_count++;
}
}
if (!candidates_count) {
free_alloca(candidates, use_heap);
return NULL;
}
checkpoint = zend_arena_checkpoint(CG(arena));
/* Find life-time intervals */
if (zend_jit_compute_liveness(op_array, ssa, candidates, &list) != SUCCESS) {
goto failure;
}
if (list) {
/* Set ZREG_LAST_USE flags */
ival = list;
while (ival) {
zend_life_range *range = &ival->range;
while (range->next) {
range = range->next;
}
if (zend_ssa_is_last_use(op_array, ssa, ival->ssa_var, range->end)) {
ival->flags |= ZREG_LAST_USE;
}
ival = ival->list_next;
}
}
if (list) {
if (JIT_G(debug) & ZEND_JIT_DEBUG_REG_ALLOC) {
fprintf(stderr, "Live Ranges \"%s\"\n", op_array->function_name ? ZSTR_VAL(op_array->function_name) : "[main]");
ival = list;
while (ival) {
zend_jit_dump_lifetime_interval(op_array, ssa, ival);
ival = ival->list_next;
}
fprintf(stderr, "\n");
}
/* Linear Scan Register Allocation */
list = zend_jit_linear_scan(op_array, NULL, ssa, list);
if (list) {
intervals = zend_arena_calloc(&CG(arena), ssa->vars_count, sizeof(zend_lifetime_interval*));
if (!intervals) {
goto failure;
}
ival = list;
while (ival != NULL) {
zend_lifetime_interval *next = ival->list_next;
ival->list_next = intervals[ival->ssa_var];
intervals[ival->ssa_var] = ival;
ival = next;
}
if (JIT_G(opt_flags) & ZEND_JIT_REG_ALLOC_GLOBAL) {
/* Naive SSA resolution */
for (i = 0; i < ssa->vars_count; i++) {
if (ssa->vars[i].definition_phi && !ssa->vars[i].no_val) {
zend_ssa_phi *phi = ssa->vars[i].definition_phi;
int k, src;
if (phi->pi >= 0) {
if (!ssa->vars[i].phi_use_chain
|| ssa->vars[i].phi_use_chain->block != phi->block) {
src = phi->sources[0];
if (intervals[i]) {
if (!intervals[src]) {
intervals[i]->flags |= ZREG_LOAD;
} else if (intervals[i]->reg != intervals[src]->reg) {
intervals[i]->flags |= ZREG_LOAD;
intervals[src]->flags |= ZREG_STORE;
}
} else if (intervals[src]) {
intervals[src]->flags |= ZREG_STORE;
}
}
} else {
int need_move = 0;
for (k = 0; k < ssa->cfg.blocks[phi->block].predecessors_count; k++) {
src = phi->sources[k];
if (src >= 0) {
if (ssa->vars[src].definition_phi
&& ssa->vars[src].definition_phi->pi >= 0
&& phi->block == ssa->vars[src].definition_phi->block) {
/* Skip zero-length interval for Pi variable */
src = ssa->vars[src].definition_phi->sources[0];
}
if (intervals[i]) {
if (!intervals[src]) {
need_move = 1;
} else if (intervals[i]->reg != intervals[src]->reg) {
need_move = 1;
}
} else if (intervals[src]) {
need_move = 1;
}
}
}
if (need_move) {
if (intervals[i]) {
intervals[i]->flags |= ZREG_LOAD;
}
for (k = 0; k < ssa->cfg.blocks[phi->block].predecessors_count; k++) {
src = phi->sources[k];
if (src >= 0) {
if (ssa->vars[src].definition_phi
&& ssa->vars[src].definition_phi->pi >= 0
&& phi->block == ssa->vars[src].definition_phi->block) {
/* Skip zero-length interval for Pi variable */
src = ssa->vars[src].definition_phi->sources[0];
}
if (intervals[src]) {
intervals[src]->flags |= ZREG_STORE;
}
}
}
}
}
}
}
/* Remove useless register allocation */
for (i = 0; i < ssa->vars_count; i++) {
if (intervals[i] &&
((intervals[i]->flags & ZREG_LOAD) ||
((intervals[i]->flags & ZREG_STORE) && ssa->vars[i].definition >= 0)) &&
ssa->vars[i].use_chain < 0) {
bool may_remove = 1;
zend_ssa_phi *phi = ssa->vars[i].phi_use_chain;
while (phi) {
if (intervals[phi->ssa_var] &&
!(intervals[phi->ssa_var]->flags & ZREG_LOAD)) {
may_remove = 0;
break;
}
phi = zend_ssa_next_use_phi(ssa, i, phi);
}
if (may_remove) {
intervals[i] = NULL;
}
}
}
/* Remove intervals used once */
for (i = 0; i < ssa->vars_count; i++) {
if (intervals[i] &&
(intervals[i]->flags & ZREG_LOAD) &&
(intervals[i]->flags & ZREG_STORE) &&
(ssa->vars[i].use_chain < 0 ||
zend_ssa_next_use(ssa->ops, i, ssa->vars[i].use_chain) < 0)) {
bool may_remove = 1;
zend_ssa_phi *phi = ssa->vars[i].phi_use_chain;
while (phi) {
if (intervals[phi->ssa_var] &&
!(intervals[phi->ssa_var]->flags & ZREG_LOAD)) {
may_remove = 0;
break;
}
phi = zend_ssa_next_use_phi(ssa, i, phi);
}
if (may_remove) {
intervals[i] = NULL;
}
}
}
}
if (JIT_G(debug) & ZEND_JIT_DEBUG_REG_ALLOC) {
fprintf(stderr, "Allocated Live Ranges \"%s\"\n", op_array->function_name ? ZSTR_VAL(op_array->function_name) : "[main]");
for (i = 0; i < ssa->vars_count; i++) {
ival = intervals[i];
while (ival) {
zend_jit_dump_lifetime_interval(op_array, ssa, ival);
ival = ival->list_next;
}
}
fprintf(stderr, "\n");
}
free_alloca(candidates, use_heap);
return intervals;
}
}
failure:
zend_arena_release(&CG(arena), checkpoint);
free_alloca(candidates, use_heap);
return NULL;
}
static bool zend_jit_next_is_send_result(const zend_op *opline)
{
if (opline->result_type == IS_TMP_VAR
&& (opline+1)->opcode == ZEND_SEND_VAL
&& (opline+1)->op1_type == IS_TMP_VAR
&& (opline+1)->op2_type != IS_CONST
&& (opline+1)->op1.var == opline->result.var) {
return 1;
}
return 0;
}
static bool zend_jit_supported_binary_op(uint8_t op, uint32_t op1_info, uint32_t op2_info)
{
if ((op1_info & MAY_BE_UNDEF) || (op2_info & MAY_BE_UNDEF)) {
return false;
}
switch (op) {
case ZEND_POW:
case ZEND_DIV:
// TODO: check for division by zero ???
return false;
case ZEND_ADD:
case ZEND_SUB:
case ZEND_MUL:
return (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:
return (op1_info & MAY_BE_LONG) && (op2_info & MAY_BE_LONG);
case ZEND_CONCAT:
return (op1_info & MAY_BE_STRING) && (op2_info & MAY_BE_STRING);
EMPTY_SWITCH_DEFAULT_CASE()
}
}
static int zend_jit(const zend_op_array *op_array, zend_ssa *ssa, const zend_op *rt_opline)
{
int b, i, end;
zend_op *opline;
dasm_State* dasm_state = NULL;
void *handler;
int call_level = 0;
void *checkpoint = NULL;
zend_lifetime_interval **ra = NULL;
bool is_terminated = 1; /* previous basic block is terminated by jump */
bool recv_emitted = 0; /* emitted at least one RECV opcode */
uint8_t smart_branch_opcode;
uint32_t target_label, target_label2;
uint32_t op1_info, op1_def_info, op2_info, res_info, res_use_info;
zend_jit_addr op1_addr, op1_def_addr, op2_addr, op2_def_addr, res_addr;
zend_class_entry *ce;
bool ce_is_instanceof;
bool on_this;
if (JIT_G(bisect_limit)) {
jit_bisect_pos++;
if (jit_bisect_pos >= JIT_G(bisect_limit)) {
if (jit_bisect_pos == JIT_G(bisect_limit)) {
fprintf(stderr, "Not JITing %s%s%s in %s:%d and after due to jit_bisect_limit\n",
op_array->scope ? ZSTR_VAL(op_array->scope->name) : "",
op_array->scope ? "::" : "",
op_array->function_name ? ZSTR_VAL(op_array->function_name) : "{main}",
ZSTR_VAL(op_array->filename), op_array->line_start);
}
return FAILURE;
}
}
if (JIT_G(opt_flags) & (ZEND_JIT_REG_ALLOC_LOCAL|ZEND_JIT_REG_ALLOC_GLOBAL)) {
checkpoint = zend_arena_checkpoint(CG(arena));
ra = zend_jit_allocate_registers(op_array, ssa);
}
/* mark hidden branch targets */
for (b = 0; b < ssa->cfg.blocks_count; b++) {
if (ssa->cfg.blocks[b].flags & ZEND_BB_REACHABLE &&
ssa->cfg.blocks[b].len > 1) {
opline = op_array->opcodes + ssa->cfg.blocks[b].start + ssa->cfg.blocks[b].len - 1;
if (opline->opcode == ZEND_DO_FCALL &&
(opline-1)->opcode == ZEND_NEW) {
ssa->cfg.blocks[ssa->cfg.blocks[b].successors[0]].flags |= ZEND_BB_TARGET;
}
}
}
dasm_init(&dasm_state, DASM_MAXSECTION);
dasm_setupglobal(&dasm_state, dasm_labels, zend_lb_MAX);
dasm_setup(&dasm_state, dasm_actions);
dasm_growpc(&dasm_state, ssa->cfg.blocks_count * 2 + 1);
zend_jit_align_func(&dasm_state);
for (b = 0; b < ssa->cfg.blocks_count; b++) {
if ((ssa->cfg.blocks[b].flags & ZEND_BB_REACHABLE) == 0) {
continue;
}
//#ifndef CONTEXT_THREADED_JIT
if (ssa->cfg.blocks[b].flags & ZEND_BB_ENTRY) {
if (ssa->cfg.blocks[b].flags & ZEND_BB_TARGET) {
/* pass */
} else if (JIT_G(opt_level) < ZEND_JIT_LEVEL_INLINE &&
ssa->cfg.blocks[b].len == 1 &&
(ssa->cfg.blocks[b].flags & ZEND_BB_EXIT) &&
op_array->opcodes[ssa->cfg.blocks[b].start].opcode != ZEND_JMP) {
/* don't generate code for BB with single opcode */
continue;
}
if (ssa->cfg.blocks[b].flags & ZEND_BB_FOLLOW) {
if (!is_terminated) {
zend_jit_jmp(&dasm_state, b);
}
}
zend_jit_label(&dasm_state, ssa->cfg.blocks_count + b);
zend_jit_prologue(&dasm_state);
} else
//#endif
if (ssa->cfg.blocks[b].flags & (ZEND_BB_START|ZEND_BB_RECV_ENTRY)) {
opline = op_array->opcodes + ssa->cfg.blocks[b].start;
if (ssa->cfg.flags & ZEND_CFG_RECV_ENTRY) {
if (opline->opcode == ZEND_RECV_INIT) {
if (opline == op_array->opcodes ||
(opline-1)->opcode != ZEND_RECV_INIT) {
if (recv_emitted) {
zend_jit_jmp(&dasm_state, b);
}
zend_jit_label(&dasm_state, ssa->cfg.blocks_count + b);
for (i = 1; (opline+i)->opcode == ZEND_RECV_INIT; i++) {
zend_jit_label(&dasm_state, ssa->cfg.blocks_count + b + i);
}
zend_jit_prologue(&dasm_state);
}
recv_emitted = 1;
} else if (opline->opcode == ZEND_RECV) {
if (!(op_array->fn_flags & ZEND_ACC_HAS_TYPE_HINTS)) {
/* skip */
continue;
} else if (recv_emitted) {
zend_jit_jmp(&dasm_state, b);
zend_jit_label(&dasm_state, ssa->cfg.blocks_count + b);
zend_jit_prologue(&dasm_state);
} else {
zend_arg_info *arg_info;
if (opline->op1.num <= op_array->num_args) {
arg_info = &op_array->arg_info[opline->op1.num - 1];
} else if (op_array->fn_flags & ZEND_ACC_VARIADIC) {
arg_info = &op_array->arg_info[op_array->num_args];
} else {
/* skip */
continue;
}
if (!ZEND_TYPE_IS_SET(arg_info->type)) {
/* skip */
continue;
}
zend_jit_label(&dasm_state, ssa->cfg.blocks_count + b);
zend_jit_prologue(&dasm_state);
recv_emitted = 1;
}
} else {
if (recv_emitted) {
zend_jit_jmp(&dasm_state, b);
} else if (JIT_G(opt_level) < ZEND_JIT_LEVEL_INLINE &&
ssa->cfg.blocks[b].len == 1 &&
(ssa->cfg.blocks[b].flags & ZEND_BB_EXIT)) {
/* don't generate code for BB with single opcode */
dasm_free(&dasm_state);
if (JIT_G(opt_flags) & (ZEND_JIT_REG_ALLOC_LOCAL|ZEND_JIT_REG_ALLOC_GLOBAL)) {
zend_arena_release(&CG(arena), checkpoint);
}
return SUCCESS;
}
zend_jit_label(&dasm_state, ssa->cfg.blocks_count + b);
zend_jit_prologue(&dasm_state);
recv_emitted = 1;
}
} else if (JIT_G(opt_level) < ZEND_JIT_LEVEL_INLINE &&
ssa->cfg.blocks[b].len == 1 &&
(ssa->cfg.blocks[b].flags & ZEND_BB_EXIT)) {
/* don't generate code for BB with single opcode */
dasm_free(&dasm_state);
if (JIT_G(opt_flags) & (ZEND_JIT_REG_ALLOC_LOCAL|ZEND_JIT_REG_ALLOC_GLOBAL)) {
zend_arena_release(&CG(arena), checkpoint);
}
return SUCCESS;
} else {
zend_jit_label(&dasm_state, ssa->cfg.blocks_count + b);
zend_jit_prologue(&dasm_state);
}
}
is_terminated = 0;
zend_jit_label(&dasm_state, b);
if (JIT_G(opt_level) < ZEND_JIT_LEVEL_INLINE) {
if ((ssa->cfg.blocks[b].flags & ZEND_BB_FOLLOW)
&& ssa->cfg.blocks[b].start != 0
&& (op_array->opcodes[ssa->cfg.blocks[b].start - 1].opcode == ZEND_NOP
|| op_array->opcodes[ssa->cfg.blocks[b].start - 1].opcode == ZEND_SWITCH_LONG
|| op_array->opcodes[ssa->cfg.blocks[b].start - 1].opcode == ZEND_SWITCH_STRING
|| op_array->opcodes[ssa->cfg.blocks[b].start - 1].opcode == ZEND_MATCH)) {
zend_jit_reset_last_valid_opline();
if (!zend_jit_set_ip(&dasm_state, op_array->opcodes + ssa->cfg.blocks[b].start)) {
goto jit_failure;
}
} else {
zend_jit_set_last_valid_opline(op_array->opcodes + ssa->cfg.blocks[b].start);
}
} else if (ssa->cfg.blocks[b].flags & ZEND_BB_TARGET) {
zend_jit_reset_last_valid_opline();
} else if (ssa->cfg.blocks[b].flags & (ZEND_BB_START|ZEND_BB_RECV_ENTRY|ZEND_BB_ENTRY)) {
zend_jit_set_last_valid_opline(op_array->opcodes + ssa->cfg.blocks[b].start);
}
if (ssa->cfg.blocks[b].flags & ZEND_BB_LOOP_HEADER) {
if (!zend_jit_check_timeout(&dasm_state, op_array->opcodes + ssa->cfg.blocks[b].start, NULL)) {
goto jit_failure;
}
}
if (!ssa->cfg.blocks[b].len) {
continue;
}
if ((JIT_G(opt_flags) & ZEND_JIT_REG_ALLOC_GLOBAL) && ra) {
zend_ssa_phi *phi = ssa->blocks[b].phis;
while (phi) {
zend_lifetime_interval *ival = ra[phi->ssa_var];
if (ival) {
if (ival->flags & ZREG_LOAD) {
ZEND_ASSERT(ival->reg != ZREG_NONE);
if (!zend_jit_load_var(&dasm_state, ssa->var_info[phi->ssa_var].type, ssa->vars[phi->ssa_var].var, ival->reg)) {
goto jit_failure;
}
} else if (ival->flags & ZREG_STORE) {
ZEND_ASSERT(ival->reg != ZREG_NONE);
if (!zend_jit_store_var(&dasm_state, ssa->var_info[phi->ssa_var].type, ssa->vars[phi->ssa_var].var, ival->reg, 1)) {
goto jit_failure;
}
}
}
phi = phi->next;
}
}
end = ssa->cfg.blocks[b].start + ssa->cfg.blocks[b].len - 1;
for (i = ssa->cfg.blocks[b].start; i <= end; i++) {
zend_ssa_op *ssa_op = ssa->ops ? &ssa->ops[i] : NULL;
opline = op_array->opcodes + i;
switch (opline->opcode) {
case ZEND_INIT_FCALL:
case ZEND_INIT_FCALL_BY_NAME:
case ZEND_INIT_NS_FCALL_BY_NAME:
case ZEND_INIT_METHOD_CALL:
case ZEND_INIT_DYNAMIC_CALL:
case ZEND_INIT_STATIC_METHOD_CALL:
case ZEND_INIT_USER_CALL:
case ZEND_NEW:
call_level++;
}
if (JIT_G(opt_level) >= ZEND_JIT_LEVEL_INLINE) {
switch (opline->opcode) {
case ZEND_PRE_INC:
case ZEND_PRE_DEC:
case ZEND_POST_INC:
case ZEND_POST_DEC:
if (opline->op1_type != IS_CV) {
break;
}
op1_info = OP1_INFO();
if (!(op1_info & MAY_BE_LONG)) {
break;
}
if (opline->result_type != IS_UNUSED) {
res_use_info = -1;
if (opline->result_type == IS_CV
&& ssa->vars
&& ssa_op->result_use >= 0
&& !ssa->vars[ssa_op->result_use].no_val) {
zend_jit_addr res_use_addr = RES_USE_REG_ADDR();
if (Z_MODE(res_use_addr) != IS_REG
|| Z_LOAD(res_use_addr)
|| Z_STORE(res_use_addr)) {
res_use_info = RES_USE_INFO();
}
}
res_info = RES_INFO();
res_addr = RES_REG_ADDR();
} else {
res_use_info = -1;
res_info = -1;
res_addr = 0;
}
op1_def_info = OP1_DEF_INFO();
if (!zend_jit_inc_dec(&dasm_state, opline,
op1_info, OP1_REG_ADDR(),
op1_def_info, OP1_DEF_REG_ADDR(),
res_use_info, res_info,
res_addr,
(op1_info & MAY_BE_LONG) && (op1_def_info & MAY_BE_DOUBLE) && zend_may_overflow(opline, ssa_op, op_array, ssa),
zend_may_throw(opline, ssa_op, op_array, ssa))) {
goto jit_failure;
}
goto done;
case ZEND_BW_OR:
case ZEND_BW_AND:
case ZEND_BW_XOR:
case ZEND_SL:
case ZEND_SR:
case ZEND_MOD:
if (PROFITABILITY_CHECKS && (!ssa->ops || !ssa->var_info)) {
break;
}
op1_info = OP1_INFO();
op2_info = OP2_INFO();
if ((op1_info & MAY_BE_UNDEF) || (op2_info & MAY_BE_UNDEF)) {
break;
}
if (!(op1_info & MAY_BE_LONG)
|| !(op2_info & MAY_BE_LONG)) {
break;
}
res_addr = RES_REG_ADDR();
if (Z_MODE(res_addr) != IS_REG
&& (i + 1) <= end
&& zend_jit_next_is_send_result(opline)) {
i++;
res_use_info = -1;
res_addr = ZEND_ADDR_MEM_ZVAL(ZREG_RX, (opline+1)->result.var);
if (!zend_jit_reuse_ip(&dasm_state)) {
goto jit_failure;
}
} else {
res_use_info = -1;
if (opline->result_type == IS_CV
&& ssa->vars
&& ssa_op->result_use >= 0
&& !ssa->vars[ssa_op->result_use].no_val) {
zend_jit_addr res_use_addr = RES_USE_REG_ADDR();
if (Z_MODE(res_use_addr) != IS_REG
|| Z_LOAD(res_use_addr)
|| Z_STORE(res_use_addr)) {
res_use_info = RES_USE_INFO();
}
}
}
if (!zend_jit_long_math(&dasm_state, opline,
op1_info, OP1_RANGE(), OP1_REG_ADDR(),
op2_info, OP2_RANGE(), OP2_REG_ADDR(),
res_use_info, RES_INFO(), res_addr,
zend_may_throw(opline, ssa_op, op_array, ssa))) {
goto jit_failure;
}
goto done;
case ZEND_ADD:
case ZEND_SUB:
case ZEND_MUL:
// case ZEND_DIV: // TODO: check for division by zero ???
if (PROFITABILITY_CHECKS && (!ssa->ops || !ssa->var_info)) {
break;
}
op1_info = OP1_INFO();
op2_info = OP2_INFO();
if ((op1_info & MAY_BE_UNDEF) || (op2_info & MAY_BE_UNDEF)) {
break;
}
if (opline->opcode == ZEND_ADD &&
(op1_info & (MAY_BE_ANY|MAY_BE_UNDEF)) == MAY_BE_ARRAY &&
(op2_info & (MAY_BE_ANY|MAY_BE_UNDEF)) == MAY_BE_ARRAY) {
/* pass */
} else if (!(op1_info & (MAY_BE_LONG|MAY_BE_DOUBLE)) ||
!(op2_info & (MAY_BE_LONG|MAY_BE_DOUBLE))) {
break;
}
res_addr = RES_REG_ADDR();
if (Z_MODE(res_addr) != IS_REG
&& (i + 1) <= end
&& zend_jit_next_is_send_result(opline)) {
i++;
res_use_info = -1;
res_addr = ZEND_ADDR_MEM_ZVAL(ZREG_RX, (opline+1)->result.var);
if (!zend_jit_reuse_ip(&dasm_state)) {
goto jit_failure;
}
} else {
res_use_info = -1;
if (opline->result_type == IS_CV
&& ssa->vars
&& ssa_op->result_use >= 0
&& !ssa->vars[ssa_op->result_use].no_val) {
zend_jit_addr res_use_addr = RES_USE_REG_ADDR();
if (Z_MODE(res_use_addr) != IS_REG
|| Z_LOAD(res_use_addr)
|| Z_STORE(res_use_addr)) {
res_use_info = RES_USE_INFO();
}
}
}
res_info = RES_INFO();
if (opline->opcode == ZEND_ADD &&
(op1_info & (MAY_BE_ANY|MAY_BE_UNDEF)) == MAY_BE_ARRAY &&
(op2_info & (MAY_BE_ANY|MAY_BE_UNDEF)) == MAY_BE_ARRAY) {
if (!zend_jit_add_arrays(&dasm_state, opline, op1_info, OP1_REG_ADDR(), op2_info, OP2_REG_ADDR(), res_addr)) {
goto jit_failure;
}
} else {
if (!zend_jit_math(&dasm_state, opline,
op1_info, OP1_REG_ADDR(),
op2_info, OP2_REG_ADDR(),
res_use_info, res_info, res_addr,
(op1_info & MAY_BE_LONG) && (op2_info & MAY_BE_LONG) && (res_info & MAY_BE_DOUBLE) && zend_may_overflow(opline, ssa_op, op_array, ssa),
zend_may_throw(opline, ssa_op, op_array, ssa))) {
goto jit_failure;
}
}
goto done;
case ZEND_CONCAT:
case ZEND_FAST_CONCAT:
if (PROFITABILITY_CHECKS && (!ssa->ops || !ssa->var_info)) {
break;
}
op1_info = OP1_INFO();
op2_info = OP2_INFO();
if ((op1_info & MAY_BE_UNDEF) || (op2_info & MAY_BE_UNDEF)) {
break;
}
if (!(op1_info & MAY_BE_STRING) ||
!(op2_info & MAY_BE_STRING)) {
break;
}
res_addr = RES_REG_ADDR();
if ((i + 1) <= end
&& zend_jit_next_is_send_result(opline)) {
i++;
res_addr = ZEND_ADDR_MEM_ZVAL(ZREG_RX, (opline+1)->result.var);
if (!zend_jit_reuse_ip(&dasm_state)) {
goto jit_failure;
}
}
if (!zend_jit_concat(&dasm_state, opline,
op1_info, op2_info, res_addr,
zend_may_throw(opline, ssa_op, op_array, ssa))) {
goto jit_failure;
}
goto done;
case ZEND_ASSIGN_OP:
if (opline->op1_type != IS_CV || opline->result_type != IS_UNUSED) {
break;
}
if (PROFITABILITY_CHECKS && (!ssa->ops || !ssa->var_info)) {
break;
}
op1_info = OP1_INFO();
op2_info = OP2_INFO();
if (!zend_jit_supported_binary_op(
opline->extended_value, op1_info, op2_info)) {
break;
}
op1_def_info = OP1_DEF_INFO();
if (!zend_jit_assign_op(&dasm_state, opline,
op1_info, op1_def_info, OP1_RANGE(),
op2_info, OP2_RANGE(),
(op1_info & MAY_BE_LONG) && (op2_info & MAY_BE_LONG) && (op1_def_info & MAY_BE_DOUBLE) && zend_may_overflow(opline, ssa_op, op_array, ssa),
zend_may_throw(opline, ssa_op, op_array, ssa))) {
goto jit_failure;
}
goto done;
case ZEND_ASSIGN_DIM_OP:
if (opline->op1_type != IS_CV || opline->result_type != IS_UNUSED) {
break;
}
if (PROFITABILITY_CHECKS && (!ssa->ops || !ssa->var_info)) {
break;
}
if (!zend_jit_supported_binary_op(
opline->extended_value, MAY_BE_ANY, OP1_DATA_INFO())) {
break;
}
if (!zend_jit_assign_dim_op(&dasm_state, opline,
OP1_INFO(), OP1_DEF_INFO(), OP1_REG_ADDR(), OP2_INFO(),
OP1_DATA_INFO(), OP1_DATA_RANGE(), IS_UNKNOWN,
zend_may_throw(opline, ssa_op, op_array, ssa))) {
goto jit_failure;
}
goto done;
case ZEND_ASSIGN_DIM:
if (opline->op1_type != IS_CV) {
break;
}
if (PROFITABILITY_CHECKS && (!ssa->ops || !ssa->var_info)) {
break;
}
if (!zend_jit_assign_dim(&dasm_state, opline,
OP1_INFO(), OP1_REG_ADDR(), OP2_INFO(), OP1_DATA_INFO(), IS_UNKNOWN,
zend_may_throw(opline, ssa_op, op_array, ssa))) {
goto jit_failure;
}
goto done;
case ZEND_PRE_INC_OBJ:
case ZEND_PRE_DEC_OBJ:
case ZEND_POST_INC_OBJ:
case ZEND_POST_DEC_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') {
break;
}
if (PROFITABILITY_CHECKS && (!ssa->ops || !ssa->var_info)) {
break;
}
ce = NULL;
ce_is_instanceof = 0;
on_this = 0;
if (opline->op1_type == IS_UNUSED) {
op1_info = MAY_BE_OBJECT|MAY_BE_RC1|MAY_BE_RCN;
ce = op_array->scope;
ce_is_instanceof = (ce->ce_flags & ZEND_ACC_FINAL) != 0;
op1_addr = 0;
on_this = 1;
} else {
op1_info = OP1_INFO();
if (!(op1_info & MAY_BE_OBJECT)) {
break;
}
op1_addr = OP1_REG_ADDR();
if (ssa->var_info && ssa->ops) {
zend_ssa_op *ssa_op = &ssa->ops[opline - op_array->opcodes];
if (ssa_op->op1_use >= 0) {
zend_ssa_var_info *op1_ssa = ssa->var_info + ssa_op->op1_use;
if (op1_ssa->ce && !op1_ssa->ce->create_object) {
ce = op1_ssa->ce;
ce_is_instanceof = op1_ssa->is_instanceof;
}
}
}
}
if (!zend_jit_incdec_obj(&dasm_state, opline, op_array, ssa, ssa_op,
op1_info, op1_addr,
0, ce, ce_is_instanceof, on_this, 0, NULL, IS_UNKNOWN)) {
goto jit_failure;
}
goto done;
case ZEND_ASSIGN_OBJ_OP:
if (opline->result_type != IS_UNUSED) {
break;
}
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') {
break;
}
if (PROFITABILITY_CHECKS && (!ssa->ops || !ssa->var_info)) {
break;
}
if (!zend_jit_supported_binary_op(
opline->extended_value, MAY_BE_ANY, OP1_DATA_INFO())) {
break;
}
ce = NULL;
ce_is_instanceof = 0;
on_this = 0;
if (opline->op1_type == IS_UNUSED) {
op1_info = MAY_BE_OBJECT|MAY_BE_RC1|MAY_BE_RCN;
ce = op_array->scope;
ce_is_instanceof = (ce->ce_flags & ZEND_ACC_FINAL) != 0;
op1_addr = 0;
on_this = 1;
} else {
op1_info = OP1_INFO();
if (!(op1_info & MAY_BE_OBJECT)) {
break;
}
op1_addr = OP1_REG_ADDR();
if (ssa->var_info && ssa->ops) {
zend_ssa_op *ssa_op = &ssa->ops[opline - op_array->opcodes];
if (ssa_op->op1_use >= 0) {
zend_ssa_var_info *op1_ssa = ssa->var_info + ssa_op->op1_use;
if (op1_ssa->ce && !op1_ssa->ce->create_object) {
ce = op1_ssa->ce;
ce_is_instanceof = op1_ssa->is_instanceof;
}
}
}
}
if (!zend_jit_assign_obj_op(&dasm_state, opline, op_array, ssa, ssa_op,
op1_info, op1_addr, OP1_DATA_INFO(), OP1_DATA_RANGE(),
0, ce, ce_is_instanceof, on_this, 0, NULL, IS_UNKNOWN)) {
goto jit_failure;
}
goto done;
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') {
break;
}
if (PROFITABILITY_CHECKS && (!ssa->ops || !ssa->var_info)) {
break;
}
ce = NULL;
ce_is_instanceof = 0;
on_this = 0;
if (opline->op1_type == IS_UNUSED) {
op1_info = MAY_BE_OBJECT|MAY_BE_RC1|MAY_BE_RCN;
ce = op_array->scope;
ce_is_instanceof = (ce->ce_flags & ZEND_ACC_FINAL) != 0;
op1_addr = 0;
on_this = 1;
} else {
op1_info = OP1_INFO();
if (!(op1_info & MAY_BE_OBJECT)) {
break;
}
op1_addr = OP1_REG_ADDR();
if (ssa->var_info && ssa->ops) {
zend_ssa_op *ssa_op = &ssa->ops[opline - op_array->opcodes];
if (ssa_op->op1_use >= 0) {
zend_ssa_var_info *op1_ssa = ssa->var_info + ssa_op->op1_use;
if (op1_ssa->ce && !op1_ssa->ce->create_object) {
ce = op1_ssa->ce;
ce_is_instanceof = op1_ssa->is_instanceof;
}
}
}
}
if (!zend_jit_assign_obj(&dasm_state, opline, op_array, ssa, ssa_op,
op1_info, op1_addr, OP1_DATA_INFO(),
0, ce, ce_is_instanceof, on_this, 0, NULL, IS_UNKNOWN,
zend_may_throw(opline, ssa_op, op_array, ssa))) {
goto jit_failure;
}
goto done;
case ZEND_ASSIGN:
if (opline->op1_type != IS_CV) {
break;
}
if (PROFITABILITY_CHECKS && (!ssa->ops || !ssa->var_info)) {
break;
}
op2_addr = OP2_REG_ADDR();
if (ra
&& ssa->ops[opline - op_array->opcodes].op2_def >= 0
&& !ssa->vars[ssa->ops[opline - op_array->opcodes].op2_def].no_val) {
op2_def_addr = OP2_DEF_REG_ADDR();
} else {
op2_def_addr = op2_addr;
}
op1_info = OP1_INFO();
if (ra && ssa->vars[ssa_op->op1_use].no_val) {
op1_info |= MAY_BE_UNDEF; // requres type assignment
}
if (opline->result_type == IS_UNUSED) {
res_addr = 0;
res_info = -1;
} else {
res_addr = RES_REG_ADDR();
res_info = RES_INFO();
if (Z_MODE(res_addr) != IS_REG
&& (i + 1) <= end
&& zend_jit_next_is_send_result(opline)
&& (!(op1_info & MAY_HAVE_DTOR) || !(op1_info & MAY_BE_RC1))) {
i++;
res_addr = ZEND_ADDR_MEM_ZVAL(ZREG_RX, (opline+1)->result.var);
if (!zend_jit_reuse_ip(&dasm_state)) {
goto jit_failure;
}
}
}
if (!zend_jit_assign(&dasm_state, opline,
op1_info, OP1_REG_ADDR(),
OP1_DEF_INFO(), OP1_DEF_REG_ADDR(),
OP2_INFO(), op2_addr, op2_def_addr,
res_info, res_addr,
zend_may_throw(opline, ssa_op, op_array, ssa))) {
goto jit_failure;
}
goto done;
case ZEND_QM_ASSIGN:
op1_addr = OP1_REG_ADDR();
if (ra
&& ssa->ops[opline - op_array->opcodes].op1_def >= 0
&& !ssa->vars[ssa->ops[opline - op_array->opcodes].op1_def].no_val) {
op1_def_addr = OP1_DEF_REG_ADDR();
} else {
op1_def_addr = op1_addr;
}
if (!zend_jit_qm_assign(&dasm_state, opline,
OP1_INFO(), op1_addr, op1_def_addr,
-1, RES_INFO(), RES_REG_ADDR())) {
goto jit_failure;
}
goto done;
case ZEND_INIT_FCALL:
case ZEND_INIT_FCALL_BY_NAME:
case ZEND_INIT_NS_FCALL_BY_NAME:
if (!zend_jit_init_fcall(&dasm_state, opline, b, op_array, ssa, ssa_op, call_level, NULL, 0)) {
goto jit_failure;
}
goto done;
case ZEND_SEND_VAL:
case ZEND_SEND_VAL_EX:
if (opline->op2_type == IS_CONST) {
/* Named parameters not supported in JIT (yet) */
break;
}
if (opline->opcode == ZEND_SEND_VAL_EX
&& opline->op2.num > MAX_ARG_FLAG_NUM) {
break;
}
if (!zend_jit_send_val(&dasm_state, opline,
OP1_INFO(), OP1_REG_ADDR())) {
goto jit_failure;
}
goto done;
case ZEND_SEND_REF:
if (opline->op2_type == IS_CONST) {
/* Named parameters not supported in JIT (yet) */
break;
}
if (!zend_jit_send_ref(&dasm_state, opline, op_array,
OP1_INFO(), 0)) {
goto jit_failure;
}
goto done;
case ZEND_SEND_VAR:
case ZEND_SEND_VAR_EX:
case ZEND_SEND_VAR_NO_REF:
case ZEND_SEND_VAR_NO_REF_EX:
case ZEND_SEND_FUNC_ARG:
if (opline->op2_type == IS_CONST) {
/* Named parameters not supported in JIT (yet) */
break;
}
if ((opline->opcode == ZEND_SEND_VAR_EX
|| opline->opcode == ZEND_SEND_VAR_NO_REF_EX)
&& opline->op2.num > MAX_ARG_FLAG_NUM) {
break;
}
op1_addr = OP1_REG_ADDR();
if (ra
&& ssa->ops[opline - op_array->opcodes].op1_def >= 0
&& !ssa->vars[ssa->ops[opline - op_array->opcodes].op1_def].no_val) {
op1_def_addr = OP1_DEF_REG_ADDR();
} else {
op1_def_addr = op1_addr;
}
if (!zend_jit_send_var(&dasm_state, opline, op_array,
OP1_INFO(), op1_addr, op1_def_addr)) {
goto jit_failure;
}
goto done;
case ZEND_CHECK_FUNC_ARG:
if (opline->op2_type == IS_CONST) {
/* Named parameters not supported in JIT (yet) */
break;
}
if (opline->op2.num > MAX_ARG_FLAG_NUM) {
break;
}
if (!zend_jit_check_func_arg(&dasm_state, opline)) {
goto jit_failure;
}
goto done;
case ZEND_CHECK_UNDEF_ARGS:
if (!zend_jit_check_undef_args(&dasm_state, opline)) {
goto jit_failure;
}
goto done;
case ZEND_DO_UCALL:
is_terminated = 1;
ZEND_FALLTHROUGH;
case ZEND_DO_ICALL:
case ZEND_DO_FCALL_BY_NAME:
case ZEND_DO_FCALL:
if (!zend_jit_do_fcall(&dasm_state, opline, op_array, ssa, call_level, b + 1, NULL)) {
goto jit_failure;
}
goto done;
case ZEND_IS_EQUAL:
case ZEND_IS_NOT_EQUAL:
case ZEND_IS_SMALLER:
case ZEND_IS_SMALLER_OR_EQUAL:
case ZEND_CASE: {
res_addr = RES_REG_ADDR();
if ((opline->result_type & IS_TMP_VAR)
&& (i + 1) <= end
&& ((opline+1)->opcode == ZEND_JMPZ
|| (opline+1)->opcode == ZEND_JMPNZ
|| (opline+1)->opcode == ZEND_JMPZ_EX
|| (opline+1)->opcode == ZEND_JMPNZ_EX)
&& (opline+1)->op1_type == IS_TMP_VAR
&& (opline+1)->op1.var == opline->result.var) {
i++;
smart_branch_opcode = (opline+1)->opcode;
target_label = ssa->cfg.blocks[b].successors[0];
target_label2 = ssa->cfg.blocks[b].successors[1];
/* For EX variant write into the result of EX opcode. */
if ((opline+1)->opcode == ZEND_JMPZ_EX
|| (opline+1)->opcode == ZEND_JMPNZ_EX) {
res_addr = OP_REG_ADDR(opline + 1, result_type, result, result_def);
}
} else {
smart_branch_opcode = 0;
target_label = target_label2 = (uint32_t)-1;
}
if (!zend_jit_cmp(&dasm_state, opline,
OP1_INFO(), OP1_RANGE(), OP1_REG_ADDR(),
OP2_INFO(), OP2_RANGE(), OP2_REG_ADDR(),
res_addr,
zend_may_throw(opline, ssa_op, op_array, ssa),
smart_branch_opcode, target_label, target_label2,
NULL, 0)) {
goto jit_failure;
}
goto done;
}
case ZEND_IS_IDENTICAL:
case ZEND_IS_NOT_IDENTICAL:
case ZEND_CASE_STRICT:
if ((opline->result_type & IS_TMP_VAR)
&& (i + 1) <= end
&& ((opline+1)->opcode == ZEND_JMPZ
|| (opline+1)->opcode == ZEND_JMPNZ)
&& (opline+1)->op1_type == IS_TMP_VAR
&& (opline+1)->op1.var == opline->result.var) {
i++;
smart_branch_opcode = (opline+1)->opcode;
target_label = ssa->cfg.blocks[b].successors[0];
target_label2 = ssa->cfg.blocks[b].successors[1];
} else {
smart_branch_opcode = 0;
target_label = target_label2 = (uint32_t)-1;
}
if (!zend_jit_identical(&dasm_state, opline,
OP1_INFO(), OP1_RANGE(), OP1_REG_ADDR(),
OP2_INFO(), OP2_RANGE(), OP2_REG_ADDR(),
RES_REG_ADDR(),
zend_may_throw(opline, ssa_op, op_array, ssa),
smart_branch_opcode, target_label, target_label2,
NULL, 0)) {
goto jit_failure;
}
goto done;
case ZEND_DEFINED:
if ((opline->result_type & IS_TMP_VAR)
&& (i + 1) <= end
&& ((opline+1)->opcode == ZEND_JMPZ
|| (opline+1)->opcode == ZEND_JMPNZ)
&& (opline+1)->op1_type == IS_TMP_VAR
&& (opline+1)->op1.var == opline->result.var) {
i++;
smart_branch_opcode = (opline+1)->opcode;
target_label = ssa->cfg.blocks[b].successors[0];
target_label2 = ssa->cfg.blocks[b].successors[1];
} else {
smart_branch_opcode = 0;
target_label = target_label2 = (uint32_t)-1;
}
if (!zend_jit_defined(&dasm_state, opline, smart_branch_opcode, target_label, target_label2, NULL)) {
goto jit_failure;
}
goto done;
case ZEND_TYPE_CHECK:
if (opline->extended_value == MAY_BE_RESOURCE) {
// TODO: support for is_resource() ???
break;
}
if ((opline->result_type & IS_TMP_VAR)
&& (i + 1) <= end
&& ((opline+1)->opcode == ZEND_JMPZ
|| (opline+1)->opcode == ZEND_JMPNZ)
&& (opline+1)->op1_type == IS_TMP_VAR
&& (opline+1)->op1.var == opline->result.var) {
i++;
smart_branch_opcode = (opline+1)->opcode;
target_label = ssa->cfg.blocks[b].successors[0];
target_label2 = ssa->cfg.blocks[b].successors[1];
} else {
smart_branch_opcode = 0;
target_label = target_label2 = (uint32_t)-1;
}
if (!zend_jit_type_check(&dasm_state, opline, OP1_INFO(), smart_branch_opcode, target_label, target_label2, NULL)) {
goto jit_failure;
}
goto done;
case ZEND_RETURN:
op1_info = OP1_INFO();
if ((PROFITABILITY_CHECKS && (!ssa->ops || !ssa->var_info))
|| op_array->type == ZEND_EVAL_CODE
// TODO: support for top-level code
|| !op_array->function_name
// TODO: support for IS_UNDEF ???
|| (op1_info & MAY_BE_UNDEF)) {
if (!zend_jit_tail_handler(&dasm_state, opline)) {
goto jit_failure;
}
} else {
int j;
bool left_frame = 0;
if (!zend_jit_return(&dasm_state, opline, op_array,
op1_info, OP1_REG_ADDR())) {
goto jit_failure;
}
if (jit_return_label >= 0) {
if (!zend_jit_jmp(&dasm_state, jit_return_label)) {
goto jit_failure;
}
goto done;
}
jit_return_label = ssa->cfg.blocks_count * 2;
if (!zend_jit_label(&dasm_state, jit_return_label)) {
goto jit_failure;
}
if (op_array->last_var > 100) {
/* To many CVs to unroll */
if (!zend_jit_free_cvs(&dasm_state)) {
goto jit_failure;
}
left_frame = 1;
}
if (!left_frame) {
for (j = 0 ; j < op_array->last_var; j++) {
uint32_t info = zend_ssa_cv_info(op_array, ssa, j);
if (info & (MAY_BE_STRING|MAY_BE_ARRAY|MAY_BE_OBJECT|MAY_BE_RESOURCE|MAY_BE_REF)) {
if (!left_frame) {
left_frame = 1;
if (!zend_jit_leave_frame(&dasm_state)) {
goto jit_failure;
}
}
if (!zend_jit_free_cv(&dasm_state, info, j)) {
goto jit_failure;
}
}
}
}
if (!zend_jit_leave_func(&dasm_state, op_array, opline, op1_info, left_frame,
NULL, NULL, (ssa->cfg.flags & ZEND_FUNC_INDIRECT_VAR_ACCESS) != 0, 1)) {
goto jit_failure;
}
}
goto done;
case ZEND_BOOL:
case ZEND_BOOL_NOT:
if (!zend_jit_bool_jmpznz(&dasm_state, opline,
OP1_INFO(), OP1_REG_ADDR(), RES_REG_ADDR(),
-1, -1,
zend_may_throw(opline, ssa_op, op_array, ssa),
opline->opcode, NULL)) {
goto jit_failure;
}
goto done;
case ZEND_JMPZ:
case ZEND_JMPNZ:
if (opline > op_array->opcodes + ssa->cfg.blocks[b].start &&
((opline-1)->result_type & (IS_SMART_BRANCH_JMPZ|IS_SMART_BRANCH_JMPNZ)) != 0) {
/* smart branch */
if (!zend_jit_cond_jmp(&dasm_state, opline + 1, ssa->cfg.blocks[b].successors[0])) {
goto jit_failure;
}
goto done;
}
ZEND_FALLTHROUGH;
case ZEND_JMPZ_EX:
case ZEND_JMPNZ_EX:
if (opline->result_type == IS_UNDEF) {
res_addr = 0;
} else {
res_addr = RES_REG_ADDR();
}
if (!zend_jit_bool_jmpznz(&dasm_state, opline,
OP1_INFO(), OP1_REG_ADDR(), res_addr,
ssa->cfg.blocks[b].successors[0], ssa->cfg.blocks[b].successors[1],
zend_may_throw(opline, ssa_op, op_array, ssa),
opline->opcode, NULL)) {
goto jit_failure;
}
goto done;
case ZEND_ISSET_ISEMPTY_CV:
if ((opline->extended_value & ZEND_ISEMPTY)) {
// TODO: support for empty() ???
break;
}
if ((opline->result_type & IS_TMP_VAR)
&& (i + 1) <= end
&& ((opline+1)->opcode == ZEND_JMPZ
|| (opline+1)->opcode == ZEND_JMPNZ)
&& (opline+1)->op1_type == IS_TMP_VAR
&& (opline+1)->op1.var == opline->result.var) {
i++;
smart_branch_opcode = (opline+1)->opcode;
target_label = ssa->cfg.blocks[b].successors[0];
target_label2 = ssa->cfg.blocks[b].successors[1];
} else {
smart_branch_opcode = 0;
target_label = target_label2 = (uint32_t)-1;
}
if (!zend_jit_isset_isempty_cv(&dasm_state, opline,
OP1_INFO(), OP1_REG_ADDR(),
smart_branch_opcode, target_label, target_label2,
NULL)) {
goto jit_failure;
}
goto done;
case ZEND_IN_ARRAY:
if (opline->op1_type == IS_VAR || opline->op1_type == IS_TMP_VAR) {
break;
}
op1_info = OP1_INFO();
if ((op1_info & (MAY_BE_ANY|MAY_BE_UNDEF|MAY_BE_REF)) != MAY_BE_STRING) {
break;
}
if ((opline->result_type & IS_TMP_VAR)
&& (i + 1) <= end
&& ((opline+1)->opcode == ZEND_JMPZ
|| (opline+1)->opcode == ZEND_JMPNZ)
&& (opline+1)->op1_type == IS_TMP_VAR
&& (opline+1)->op1.var == opline->result.var) {
i++;
smart_branch_opcode = (opline+1)->opcode;
target_label = ssa->cfg.blocks[b].successors[0];
target_label2 = ssa->cfg.blocks[b].successors[1];
} else {
smart_branch_opcode = 0;
target_label = target_label2 = (uint32_t)-1;
}
if (!zend_jit_in_array(&dasm_state, opline,
op1_info, OP1_REG_ADDR(),
smart_branch_opcode, target_label, target_label2,
NULL)) {
goto jit_failure;
}
goto done;
case ZEND_FETCH_DIM_R:
case ZEND_FETCH_DIM_IS:
case ZEND_FETCH_LIST_R:
if (PROFITABILITY_CHECKS && (!ssa->ops || !ssa->var_info)) {
break;
}
if (!zend_jit_fetch_dim_read(&dasm_state, opline, ssa, ssa_op,
OP1_INFO(), OP1_REG_ADDR(), 0,
OP2_INFO(), RES_INFO(), RES_REG_ADDR(), IS_UNKNOWN)) {
goto jit_failure;
}
goto done;
case ZEND_FETCH_DIM_W:
case ZEND_FETCH_DIM_RW:
// case ZEND_FETCH_DIM_UNSET:
case ZEND_FETCH_LIST_W:
if (PROFITABILITY_CHECKS && (!ssa->ops || !ssa->var_info)) {
break;
}
if (opline->op1_type != IS_CV) {
break;
}
if (!zend_jit_fetch_dim(&dasm_state, opline,
OP1_INFO(), OP1_REG_ADDR(), OP2_INFO(), RES_REG_ADDR(), IS_UNKNOWN)) {
goto jit_failure;
}
goto done;
case ZEND_ISSET_ISEMPTY_DIM_OBJ:
if ((opline->extended_value & ZEND_ISEMPTY)) {
// TODO: support for empty() ???
break;
}
if (PROFITABILITY_CHECKS && (!ssa->ops || !ssa->var_info)) {
break;
}
if ((opline->result_type & IS_TMP_VAR)
&& (i + 1) <= end
&& ((opline+1)->opcode == ZEND_JMPZ
|| (opline+1)->opcode == ZEND_JMPNZ)
&& (opline+1)->op1_type == IS_TMP_VAR
&& (opline+1)->op1.var == opline->result.var) {
i++;
smart_branch_opcode = (opline+1)->opcode;
target_label = ssa->cfg.blocks[b].successors[0];
target_label2 = ssa->cfg.blocks[b].successors[1];
} else {
smart_branch_opcode = 0;
target_label = target_label2 = (uint32_t)-1;
}
if (!zend_jit_isset_isempty_dim(&dasm_state, opline,
OP1_INFO(), OP1_REG_ADDR(), 0,
OP2_INFO(), IS_UNKNOWN,
zend_may_throw(opline, ssa_op, op_array, ssa),
smart_branch_opcode, target_label, target_label2,
NULL)) {
goto jit_failure;
}
goto done;
case ZEND_FETCH_OBJ_R:
case ZEND_FETCH_OBJ_IS:
case ZEND_FETCH_OBJ_W:
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') {
break;
}
ce = NULL;
ce_is_instanceof = 0;
on_this = 0;
if (opline->op1_type == IS_UNUSED) {
op1_info = MAY_BE_OBJECT|MAY_BE_RC1|MAY_BE_RCN;
op1_addr = 0;
ce = op_array->scope;
ce_is_instanceof = (ce->ce_flags & ZEND_ACC_FINAL) != 0;
on_this = 1;
} else {
op1_info = OP1_INFO();
if (!(op1_info & MAY_BE_OBJECT)) {
break;
}
op1_addr = OP1_REG_ADDR();
if (ssa->var_info && ssa->ops) {
zend_ssa_op *ssa_op = &ssa->ops[opline - op_array->opcodes];
if (ssa_op->op1_use >= 0) {
zend_ssa_var_info *op1_ssa = ssa->var_info + ssa_op->op1_use;
if (op1_ssa->ce && !op1_ssa->ce->create_object) {
ce = op1_ssa->ce;
ce_is_instanceof = op1_ssa->is_instanceof;
}
}
}
}
if (!zend_jit_fetch_obj(&dasm_state, opline, op_array, ssa, ssa_op,
op1_info, op1_addr, 0, ce, ce_is_instanceof, on_this, 0, 0, NULL,
IS_UNKNOWN,
zend_may_throw(opline, ssa_op, op_array, ssa))) {
goto jit_failure;
}
goto done;
case ZEND_BIND_GLOBAL:
if (!ssa->ops || !ssa->var_info) {
op1_info = MAY_BE_ANY|MAY_BE_REF;
} else {
op1_info = OP1_INFO();
}
if (!zend_jit_bind_global(&dasm_state, opline, op1_info)) {
goto jit_failure;
}
goto done;
case ZEND_RECV:
if (!zend_jit_recv(&dasm_state, opline, op_array)) {
goto jit_failure;
}
goto done;
case ZEND_RECV_INIT:
if (!zend_jit_recv_init(&dasm_state, opline, op_array,
(opline + 1)->opcode != ZEND_RECV_INIT,
zend_may_throw(opline, ssa_op, op_array, ssa))) {
goto jit_failure;
}
goto done;
case ZEND_FREE:
case ZEND_FE_FREE:
if (!zend_jit_free(&dasm_state, opline, OP1_INFO(),
zend_may_throw(opline, ssa_op, op_array, ssa))) {
goto jit_failure;
}
goto done;
case ZEND_ECHO:
op1_info = OP1_INFO();
if ((op1_info & (MAY_BE_UNDEF|MAY_BE_ANY|MAY_BE_REF)) != MAY_BE_STRING) {
break;
}
if (!zend_jit_echo(&dasm_state, opline, op1_info)) {
goto jit_failure;
}
goto done;
case ZEND_STRLEN:
op1_info = OP1_INFO();
if ((op1_info & (MAY_BE_UNDEF|MAY_BE_ANY|MAY_BE_REF)) != MAY_BE_STRING) {
break;
}
if (!zend_jit_strlen(&dasm_state, opline, op1_info, OP1_REG_ADDR(), RES_REG_ADDR())) {
goto jit_failure;
}
goto done;
case ZEND_COUNT:
op1_info = OP1_INFO();
if ((op1_info & (MAY_BE_UNDEF|MAY_BE_ANY|MAY_BE_REF)) != MAY_BE_ARRAY) {
break;
}
if (!zend_jit_count(&dasm_state, opline, op1_info, OP1_REG_ADDR(), RES_REG_ADDR(), zend_may_throw(opline, ssa_op, op_array, ssa))) {
goto jit_failure;
}
goto done;
case ZEND_FETCH_THIS:
if (!zend_jit_fetch_this(&dasm_state, opline, op_array, 0)) {
goto jit_failure;
}
goto done;
case ZEND_SWITCH_LONG:
case ZEND_SWITCH_STRING:
case ZEND_MATCH:
if (!zend_jit_switch(&dasm_state, opline, op_array, ssa, NULL, NULL)) {
goto jit_failure;
}
goto done;
case ZEND_VERIFY_RETURN_TYPE:
if (opline->op1_type == IS_UNUSED) {
/* Always throws */
break;
}
if (opline->op1_type == IS_CONST) {
/* TODO Different instruction format, has return value */
break;
}
if (op_array->fn_flags & ZEND_ACC_RETURN_REFERENCE) {
/* Not worth bothering with */
break;
}
if (OP1_INFO() & MAY_BE_REF) {
/* TODO May need reference unwrapping. */
break;
}
if (!zend_jit_verify_return_type(&dasm_state, opline, op_array, OP1_INFO())) {
goto jit_failure;
}
goto done;
case ZEND_FE_RESET_R:
op1_info = OP1_INFO();
if ((op1_info & (MAY_BE_ANY|MAY_BE_REF|MAY_BE_UNDEF)) != MAY_BE_ARRAY) {
break;
}
if (!zend_jit_fe_reset(&dasm_state, opline, op1_info)) {
goto jit_failure;
}
goto done;
case ZEND_FE_FETCH_R:
op1_info = OP1_INFO();
if ((op1_info & MAY_BE_ANY) != MAY_BE_ARRAY) {
break;
}
if (!zend_jit_fe_fetch(&dasm_state, opline, op1_info, OP2_INFO(),
ssa->cfg.blocks[b].successors[0], opline->opcode, NULL)) {
goto jit_failure;
}
goto done;
case ZEND_FETCH_CONSTANT:
if (!zend_jit_fetch_constant(&dasm_state, opline, op_array, ssa, ssa_op, RES_REG_ADDR())) {
goto jit_failure;
}
goto done;
case ZEND_INIT_METHOD_CALL:
if (opline->op2_type != IS_CONST
|| Z_TYPE_P(RT_CONSTANT(opline, opline->op2)) != IS_STRING) {
break;
}
ce = NULL;
ce_is_instanceof = 0;
on_this = 0;
if (opline->op1_type == IS_UNUSED) {
op1_info = MAY_BE_OBJECT|MAY_BE_RC1|MAY_BE_RCN;
op1_addr = 0;
ce = op_array->scope;
ce_is_instanceof = (ce->ce_flags & ZEND_ACC_FINAL) != 0;
on_this = 1;
} else {
op1_info = OP1_INFO();
if (!(op1_info & MAY_BE_OBJECT)) {
break;
}
op1_addr = OP1_REG_ADDR();
if (ssa->var_info && ssa->ops) {
zend_ssa_op *ssa_op = &ssa->ops[opline - op_array->opcodes];
if (ssa_op->op1_use >= 0) {
zend_ssa_var_info *op1_ssa = ssa->var_info + ssa_op->op1_use;
if (op1_ssa->ce && !op1_ssa->ce->create_object) {
ce = op1_ssa->ce;
ce_is_instanceof = op1_ssa->is_instanceof;
}
}
}
}
if (!zend_jit_init_method_call(&dasm_state, opline, b, op_array, ssa, ssa_op, call_level,
op1_info, op1_addr, ce, ce_is_instanceof, on_this, 0, NULL,
NULL, 0, 0)) {
goto jit_failure;
}
goto done;
case ZEND_ROPE_INIT:
case ZEND_ROPE_ADD:
case ZEND_ROPE_END:
op2_info = OP2_INFO();
if ((op2_info & (MAY_BE_UNDEF|MAY_BE_ANY|MAY_BE_REF)) != MAY_BE_STRING) {
break;
}
if (!zend_jit_rope(&dasm_state, opline, op2_info)) {
goto jit_failure;
}
goto done;
default:
break;
}
}
switch (opline->opcode) {
case ZEND_RECV_INIT:
case ZEND_BIND_GLOBAL:
if (opline == op_array->opcodes ||
opline->opcode != op_array->opcodes[i-1].opcode) {
/* repeatable opcodes */
if (!zend_jit_handler(&dasm_state, opline,
zend_may_throw(opline, ssa_op, op_array, ssa))) {
goto jit_failure;
}
}
zend_jit_set_last_valid_opline(opline+1);
break;
case ZEND_NOP:
case ZEND_OP_DATA:
case ZEND_SWITCH_LONG:
case ZEND_SWITCH_STRING:
case ZEND_MATCH:
break;
case ZEND_JMP:
if (JIT_G(opt_level) < ZEND_JIT_LEVEL_INLINE) {
const zend_op *target = OP_JMP_ADDR(opline, opline->op1);
if (!zend_jit_set_ip(&dasm_state, target)) {
goto jit_failure;
}
}
if (!zend_jit_jmp(&dasm_state, ssa->cfg.blocks[b].successors[0])) {
goto jit_failure;
}
is_terminated = 1;
break;
case ZEND_CATCH:
case ZEND_FAST_CALL:
case ZEND_FAST_RET:
case ZEND_GENERATOR_CREATE:
case ZEND_GENERATOR_RETURN:
case ZEND_RETURN_BY_REF:
case ZEND_RETURN:
case ZEND_EXIT:
case ZEND_MATCH_ERROR:
/* switch through trampoline */
case ZEND_YIELD:
case ZEND_YIELD_FROM:
if (!zend_jit_tail_handler(&dasm_state, opline)) {
goto jit_failure;
}
is_terminated = 1;
break;
/* stackless execution */
case ZEND_INCLUDE_OR_EVAL:
case ZEND_DO_FCALL:
case ZEND_DO_UCALL:
case ZEND_DO_FCALL_BY_NAME:
if (!zend_jit_call(&dasm_state, opline, b + 1)) {
goto jit_failure;
}
is_terminated = 1;
break;
case ZEND_JMPZ:
case ZEND_JMPNZ:
if (opline > op_array->opcodes + ssa->cfg.blocks[b].start &&
((opline-1)->result_type & (IS_SMART_BRANCH_JMPZ|IS_SMART_BRANCH_JMPNZ)) != 0) {
/* smart branch */
if (!zend_jit_cond_jmp(&dasm_state, opline + 1, ssa->cfg.blocks[b].successors[0])) {
goto jit_failure;
}
goto done;
}
ZEND_FALLTHROUGH;
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:
case ZEND_ASSERT_CHECK:
case ZEND_FE_FETCH_R:
case ZEND_FE_FETCH_RW:
if (!zend_jit_handler(&dasm_state, opline,
zend_may_throw(opline, ssa_op, op_array, ssa)) ||
!zend_jit_cond_jmp(&dasm_state, opline + 1, ssa->cfg.blocks[b].successors[0])) {
goto jit_failure;
}
break;
case ZEND_NEW:
if (!zend_jit_handler(&dasm_state, opline, 1)) {
return 0;
}
if (opline->extended_value == 0 && (opline+1)->opcode == ZEND_DO_FCALL) {
zend_class_entry *ce = NULL;
if (JIT_G(opt_level) >= ZEND_JIT_LEVEL_OPT_FUNC) {
if (ssa->ops && ssa->var_info) {
zend_ssa_var_info *res_ssa = &ssa->var_info[ssa->ops[opline - op_array->opcodes].result_def];
if (res_ssa->ce && !res_ssa->is_instanceof) {
ce = res_ssa->ce;
}
}
} else {
if (opline->op1_type == IS_CONST) {
zval *zv = RT_CONSTANT(opline, opline->op1);
if (Z_TYPE_P(zv) == IS_STRING) {
zval *lc = zv + 1;
ce = (zend_class_entry*)zend_hash_find_ptr(EG(class_table), Z_STR_P(lc));
}
}
}
i++;
if (!ce || !(ce->ce_flags & ZEND_ACC_LINKED) || ce->constructor) {
const zend_op *next_opline = opline + 1;
zend_jit_cond_jmp(&dasm_state, next_opline, ssa->cfg.blocks[b].successors[0]);
if (JIT_G(opt_level) < ZEND_JIT_LEVEL_INLINE) {
zend_jit_call(&dasm_state, next_opline, b + 1);
is_terminated = 1;
} else {
zend_jit_do_fcall(&dasm_state, next_opline, op_array, ssa, call_level, b + 1, NULL);
}
}
/* We skip over the DO_FCALL, so decrement call_level ourselves. */
call_level--;
}
break;
default:
if (!zend_jit_handler(&dasm_state, opline,
zend_may_throw(opline, ssa_op, op_array, ssa))) {
goto jit_failure;
}
if (i == end
&& (opline->result_type & (IS_SMART_BRANCH_JMPZ|IS_SMART_BRANCH_JMPNZ)) != 0) {
/* smart branch split across basic blocks */
if (!zend_jit_cond_jmp(&dasm_state, opline + 2, ssa->cfg.blocks[b+1].successors[0])) {
goto jit_failure;
}
if (!zend_jit_jmp(&dasm_state, ssa->cfg.blocks[b+1].successors[1])) {
goto jit_failure;
}
is_terminated = 1;
}
}
done:
switch (opline->opcode) {
case ZEND_DO_FCALL:
case ZEND_DO_ICALL:
case ZEND_DO_UCALL:
case ZEND_DO_FCALL_BY_NAME:
case ZEND_CALLABLE_CONVERT:
call_level--;
}
}
}
handler = dasm_link_and_encode(&dasm_state, op_array, ssa, rt_opline, ra, NULL, 0,
(zend_jit_vm_kind == ZEND_VM_KIND_HYBRID) ? SP_ADJ_VM : SP_ADJ_RET, SP_ADJ_JIT);
if (!handler) {
goto jit_failure;
}
dasm_free(&dasm_state);
if (JIT_G(opt_flags) & (ZEND_JIT_REG_ALLOC_LOCAL|ZEND_JIT_REG_ALLOC_GLOBAL)) {
zend_arena_release(&CG(arena), checkpoint);
}
return SUCCESS;
jit_failure:
if (dasm_state) {
dasm_free(&dasm_state);
}
if (JIT_G(opt_flags) & (ZEND_JIT_REG_ALLOC_LOCAL|ZEND_JIT_REG_ALLOC_GLOBAL)) {
zend_arena_release(&CG(arena), checkpoint);
}
return FAILURE;
}
static void zend_jit_collect_calls(zend_op_array *op_array, zend_script *script)
{
zend_func_info *func_info;
if (JIT_G(trigger) == ZEND_JIT_ON_FIRST_EXEC ||
JIT_G(trigger) == ZEND_JIT_ON_PROF_REQUEST ||
JIT_G(trigger) == ZEND_JIT_ON_HOT_COUNTERS) {
func_info = ZEND_FUNC_INFO(op_array);
} else {
func_info = zend_arena_calloc(&CG(arena), 1, sizeof(zend_func_info));
ZEND_SET_FUNC_INFO(op_array, func_info);
}
zend_analyze_calls(&CG(arena), script, ZEND_CALL_TREE, op_array, func_info);
}
static void zend_jit_cleanup_func_info(zend_op_array *op_array)
{
zend_func_info *func_info = ZEND_FUNC_INFO(op_array);
zend_call_info *caller_info, *callee_info;
if (func_info) {
caller_info = func_info->caller_info;
callee_info = func_info->callee_info;
if (JIT_G(trigger) == ZEND_JIT_ON_FIRST_EXEC ||
JIT_G(trigger) == ZEND_JIT_ON_PROF_REQUEST ||
JIT_G(trigger) == ZEND_JIT_ON_HOT_COUNTERS) {
func_info->num = 0;
func_info->flags &= ZEND_FUNC_JIT_ON_FIRST_EXEC
| ZEND_FUNC_JIT_ON_PROF_REQUEST
| ZEND_FUNC_JIT_ON_HOT_COUNTERS
| ZEND_FUNC_JIT_ON_HOT_TRACE;
memset(&func_info->ssa, 0, sizeof(zend_func_info) - offsetof(zend_func_info, ssa));
} else {
ZEND_SET_FUNC_INFO(op_array, NULL);
}
while (caller_info) {
if (caller_info->caller_op_array) {
zend_jit_cleanup_func_info(caller_info->caller_op_array);
}
caller_info = caller_info->next_caller;
}
while (callee_info) {
if (callee_info->callee_func && callee_info->callee_func->type == ZEND_USER_FUNCTION) {
zend_jit_cleanup_func_info(&callee_info->callee_func->op_array);
}
callee_info = callee_info->next_callee;
}
}
}
static int zend_real_jit_func(zend_op_array *op_array, zend_script *script, const zend_op *rt_opline)
{
zend_ssa ssa;
void *checkpoint;
zend_func_info *func_info;
if (*dasm_ptr == dasm_end) {
return FAILURE;
}
checkpoint = zend_arena_checkpoint(CG(arena));
/* Build SSA */
memset(&ssa, 0, sizeof(zend_ssa));
if (zend_jit_op_array_analyze1(op_array, script, &ssa) != SUCCESS) {
goto jit_failure;
}
if (JIT_G(opt_level) >= ZEND_JIT_LEVEL_OPT_FUNCS) {
zend_jit_collect_calls(op_array, script);
func_info = ZEND_FUNC_INFO(op_array);
func_info->call_map = zend_build_call_map(&CG(arena), func_info, op_array);
if (op_array->fn_flags & ZEND_ACC_HAS_RETURN_TYPE) {
zend_init_func_return_info(op_array, script, &func_info->return_info);
}
}
if (zend_jit_op_array_analyze2(op_array, script, &ssa, ZCG(accel_directives).optimization_level) != SUCCESS) {
goto jit_failure;
}
if (JIT_G(debug) & ZEND_JIT_DEBUG_SSA) {
zend_dump_op_array(op_array, ZEND_DUMP_HIDE_UNREACHABLE|ZEND_DUMP_RC_INFERENCE|ZEND_DUMP_SSA, "JIT", &ssa);
}
if (zend_jit(op_array, &ssa, rt_opline) != SUCCESS) {
goto jit_failure;
}
zend_jit_cleanup_func_info(op_array);
zend_arena_release(&CG(arena), checkpoint);
return SUCCESS;
jit_failure:
zend_jit_cleanup_func_info(op_array);
zend_arena_release(&CG(arena), checkpoint);
return FAILURE;
}
/* Run-time JIT handler */
static int ZEND_FASTCALL zend_runtime_jit(void)
{
zend_execute_data *execute_data = EG(current_execute_data);
zend_op_array *op_array = &EX(func)->op_array;
zend_op *opline = op_array->opcodes;
zend_jit_op_array_extension *jit_extension;
bool do_bailout = 0;
zend_shared_alloc_lock();
if (ZEND_FUNC_INFO(op_array)) {
SHM_UNPROTECT();
zend_jit_unprotect();
zend_try {
/* restore original opcode handlers */
if (!(op_array->fn_flags & ZEND_ACC_HAS_TYPE_HINTS)) {
while (opline->opcode == ZEND_RECV || opline->opcode == ZEND_RECV_INIT) {
opline++;
}
}
jit_extension = (zend_jit_op_array_extension*)ZEND_FUNC_INFO(op_array);
opline->handler = jit_extension->orig_handler;
/* perform real JIT for this function */
zend_real_jit_func(op_array, NULL, NULL);
} zend_catch {
do_bailout = true;
} zend_end_try();
zend_jit_protect();
SHM_PROTECT();
}
zend_shared_alloc_unlock();
if (do_bailout) {
zend_bailout();
}
/* JIT-ed code is going to be called by VM */
return 0;
}
void zend_jit_check_funcs(HashTable *function_table, bool is_method) {
zend_op *opline;
zend_function *func;
zend_op_array *op_array;
uintptr_t counter;
zend_jit_op_array_extension *jit_extension;
ZEND_HASH_MAP_REVERSE_FOREACH_PTR(function_table, func) {
if (func->type == ZEND_INTERNAL_FUNCTION) {
break;
}
op_array = &func->op_array;
opline = op_array->opcodes;
if (!(op_array->fn_flags & ZEND_ACC_HAS_TYPE_HINTS)) {
while (opline->opcode == ZEND_RECV || opline->opcode == ZEND_RECV_INIT) {
opline++;
}
}
if (opline->handler == zend_jit_profile_jit_handler) {
if (!RUN_TIME_CACHE(op_array)) {
continue;
}
counter = (uintptr_t)ZEND_COUNTER_INFO(op_array);
ZEND_COUNTER_INFO(op_array) = 0;
jit_extension = (zend_jit_op_array_extension*)ZEND_FUNC_INFO(op_array);
opline->handler = jit_extension->orig_handler;
if (((double)counter / (double)zend_jit_profile_counter) > JIT_G(prof_threshold)) {
zend_real_jit_func(op_array, NULL, NULL);
}
}
} ZEND_HASH_FOREACH_END();
}
void ZEND_FASTCALL zend_jit_hot_func(zend_execute_data *execute_data, const zend_op *opline)
{
zend_op_array *op_array = &EX(func)->op_array;
zend_jit_op_array_hot_extension *jit_extension;
uint32_t i;
bool do_bailout = 0;
zend_shared_alloc_lock();
jit_extension = (zend_jit_op_array_hot_extension*)ZEND_FUNC_INFO(op_array);
if (jit_extension) {
SHM_UNPROTECT();
zend_jit_unprotect();
zend_try {
for (i = 0; i < op_array->last; i++) {
op_array->opcodes[i].handler = jit_extension->orig_handlers[i];
}
/* perform real JIT for this function */
zend_real_jit_func(op_array, NULL, opline);
} zend_catch {
do_bailout = 1;
} zend_end_try();
zend_jit_protect();
SHM_PROTECT();
}
zend_shared_alloc_unlock();
if (do_bailout) {
zend_bailout();
}
/* JIT-ed code is going to be called by VM */
}
static void zend_jit_setup_hot_counters_ex(zend_op_array *op_array, zend_cfg *cfg)
{
if (JIT_G(hot_func)) {
zend_op *opline = op_array->opcodes;
if (!(op_array->fn_flags & ZEND_ACC_HAS_TYPE_HINTS)) {
while (opline->opcode == ZEND_RECV || opline->opcode == ZEND_RECV_INIT) {
opline++;
}
}
opline->handler = (const void*)zend_jit_func_hot_counter_handler;
}
if (JIT_G(hot_loop)) {
uint32_t i;
for (i = 0; i < cfg->blocks_count; i++) {
if ((cfg->blocks[i].flags & ZEND_BB_REACHABLE) &&
(cfg->blocks[i].flags & ZEND_BB_LOOP_HEADER)) {
op_array->opcodes[cfg->blocks[i].start].handler =
(const void*)zend_jit_loop_hot_counter_handler;
}
}
}
}
static int zend_jit_restart_hot_counters(zend_op_array *op_array)
{
zend_jit_op_array_hot_extension *jit_extension;
zend_cfg cfg;
uint32_t i;
jit_extension = (zend_jit_op_array_hot_extension*)ZEND_FUNC_INFO(op_array);
for (i = 0; i < op_array->last; i++) {
op_array->opcodes[i].handler = jit_extension->orig_handlers[i];
}
if (zend_jit_build_cfg(op_array, &cfg) != SUCCESS) {
return FAILURE;
}
zend_jit_setup_hot_counters_ex(op_array, &cfg);
return SUCCESS;
}
static int zend_jit_setup_hot_counters(zend_op_array *op_array)
{
zend_jit_op_array_hot_extension *jit_extension;
zend_cfg cfg;
uint32_t i;
ZEND_ASSERT(zend_jit_func_hot_counter_handler != NULL);
ZEND_ASSERT(zend_jit_loop_hot_counter_handler != NULL);
if (zend_jit_build_cfg(op_array, &cfg) != SUCCESS) {
return FAILURE;
}
jit_extension = (zend_jit_op_array_hot_extension*)zend_shared_alloc(sizeof(zend_jit_op_array_hot_extension) + (op_array->last - 1) * sizeof(void*));
if (!jit_extension) {
return FAILURE;
}
memset(&jit_extension->func_info, 0, sizeof(zend_func_info));
jit_extension->func_info.flags = ZEND_FUNC_JIT_ON_HOT_COUNTERS;
jit_extension->counter = &zend_jit_hot_counters[zend_jit_op_array_hash(op_array) & (ZEND_HOT_COUNTERS_COUNT - 1)];
for (i = 0; i < op_array->last; i++) {
jit_extension->orig_handlers[i] = op_array->opcodes[i].handler;
}
ZEND_SET_FUNC_INFO(op_array, (void*)jit_extension);
zend_jit_setup_hot_counters_ex(op_array, &cfg);
zend_shared_alloc_register_xlat_entry(op_array->opcodes, jit_extension);
return SUCCESS;
}
#include "jit/zend_jit_trace.c"
ZEND_EXT_API int zend_jit_op_array(zend_op_array *op_array, zend_script *script)
{
if (dasm_ptr == NULL) {
return FAILURE;
}
if (JIT_G(trigger) == ZEND_JIT_ON_FIRST_EXEC) {
zend_jit_op_array_extension *jit_extension;
zend_op *opline = op_array->opcodes;
if (CG(compiler_options) & ZEND_COMPILE_PRELOAD) {
ZEND_SET_FUNC_INFO(op_array, NULL);
zend_error(E_WARNING, "Preloading is incompatible with first-exec and profile triggered JIT");
return SUCCESS;
}
/* Set run-time JIT handler */
ZEND_ASSERT(zend_jit_runtime_jit_handler != NULL);
if (!(op_array->fn_flags & ZEND_ACC_HAS_TYPE_HINTS)) {
while (opline->opcode == ZEND_RECV || opline->opcode == ZEND_RECV_INIT) {
opline++;
}
}
jit_extension = (zend_jit_op_array_extension*)zend_shared_alloc(sizeof(zend_jit_op_array_extension));
if (!jit_extension) {
return FAILURE;
}
memset(&jit_extension->func_info, 0, sizeof(zend_func_info));
jit_extension->func_info.flags = ZEND_FUNC_JIT_ON_FIRST_EXEC;
jit_extension->orig_handler = (void*)opline->handler;
ZEND_SET_FUNC_INFO(op_array, (void*)jit_extension);
opline->handler = (const void*)zend_jit_runtime_jit_handler;
zend_shared_alloc_register_xlat_entry(op_array->opcodes, jit_extension);
return SUCCESS;
} else if (JIT_G(trigger) == ZEND_JIT_ON_PROF_REQUEST) {
zend_jit_op_array_extension *jit_extension;
zend_op *opline = op_array->opcodes;
if (CG(compiler_options) & ZEND_COMPILE_PRELOAD) {
ZEND_SET_FUNC_INFO(op_array, NULL);
zend_error(E_WARNING, "Preloading is incompatible with first-exec and profile triggered JIT");
return SUCCESS;
}
ZEND_ASSERT(zend_jit_profile_jit_handler != NULL);
if (op_array->function_name) {
if (!(op_array->fn_flags & ZEND_ACC_HAS_TYPE_HINTS)) {
while (opline->opcode == ZEND_RECV || opline->opcode == ZEND_RECV_INIT) {
opline++;
}
}
jit_extension = (zend_jit_op_array_extension*)zend_shared_alloc(sizeof(zend_jit_op_array_extension));
if (!jit_extension) {
return FAILURE;
}
memset(&jit_extension->func_info, 0, sizeof(zend_func_info));
jit_extension->func_info.flags = ZEND_FUNC_JIT_ON_PROF_REQUEST;
jit_extension->orig_handler = (void*)opline->handler;
ZEND_SET_FUNC_INFO(op_array, (void*)jit_extension);
opline->handler = (const void*)zend_jit_profile_jit_handler;
zend_shared_alloc_register_xlat_entry(op_array->opcodes, jit_extension);
}
return SUCCESS;
} else if (JIT_G(trigger) == ZEND_JIT_ON_HOT_COUNTERS) {
return zend_jit_setup_hot_counters(op_array);
} else if (JIT_G(trigger) == ZEND_JIT_ON_HOT_TRACE) {
return zend_jit_setup_hot_trace_counters(op_array);
} else if (JIT_G(trigger) == ZEND_JIT_ON_SCRIPT_LOAD) {
return zend_real_jit_func(op_array, script, NULL);
} else {
ZEND_UNREACHABLE();
}
}
ZEND_EXT_API int zend_jit_script(zend_script *script)
{
void *checkpoint;
zend_call_graph call_graph;
zend_func_info *info;
int i;
if (dasm_ptr == NULL || *dasm_ptr == dasm_end) {
return FAILURE;
}
checkpoint = zend_arena_checkpoint(CG(arena));
call_graph.op_arrays_count = 0;
zend_build_call_graph(&CG(arena), script, &call_graph);
zend_analyze_call_graph(&CG(arena), script, &call_graph);
if (JIT_G(trigger) == ZEND_JIT_ON_FIRST_EXEC ||
JIT_G(trigger) == ZEND_JIT_ON_PROF_REQUEST ||
JIT_G(trigger) == ZEND_JIT_ON_HOT_COUNTERS ||
JIT_G(trigger) == ZEND_JIT_ON_HOT_TRACE) {
for (i = 0; i < call_graph.op_arrays_count; i++) {
if (zend_jit_op_array(call_graph.op_arrays[i], script) != SUCCESS) {
goto jit_failure;
}
}
} else if (JIT_G(trigger) == ZEND_JIT_ON_SCRIPT_LOAD) {
for (i = 0; i < call_graph.op_arrays_count; i++) {
info = ZEND_FUNC_INFO(call_graph.op_arrays[i]);
if (info) {
if (zend_jit_op_array_analyze1(call_graph.op_arrays[i], script, &info->ssa) != SUCCESS) {
goto jit_failure;
}
info->flags = info->ssa.cfg.flags;
}
}
for (i = 0; i < call_graph.op_arrays_count; i++) {
info = ZEND_FUNC_INFO(call_graph.op_arrays[i]);
if (info) {
info->call_map = zend_build_call_map(&CG(arena), info, call_graph.op_arrays[i]);
if (call_graph.op_arrays[i]->fn_flags & ZEND_ACC_HAS_RETURN_TYPE) {
zend_init_func_return_info(call_graph.op_arrays[i], script, &info->return_info);
}
}
}
for (i = 0; i < call_graph.op_arrays_count; i++) {
info = ZEND_FUNC_INFO(call_graph.op_arrays[i]);
if (info) {
if (zend_jit_op_array_analyze2(call_graph.op_arrays[i], script, &info->ssa, ZCG(accel_directives).optimization_level) != SUCCESS) {
goto jit_failure;
}
info->flags = info->ssa.cfg.flags;
}
}
for (i = 0; i < call_graph.op_arrays_count; i++) {
info = ZEND_FUNC_INFO(call_graph.op_arrays[i]);
if (info) {
if (JIT_G(debug) & ZEND_JIT_DEBUG_SSA) {
zend_dump_op_array(call_graph.op_arrays[i], ZEND_DUMP_HIDE_UNREACHABLE|ZEND_DUMP_RC_INFERENCE|ZEND_DUMP_SSA, "JIT", &info->ssa);
}
if (zend_jit(call_graph.op_arrays[i], &info->ssa, NULL) != SUCCESS) {
goto jit_failure;
}
}
}
for (i = 0; i < call_graph.op_arrays_count; i++) {
ZEND_SET_FUNC_INFO(call_graph.op_arrays[i], NULL);
}
} else {
ZEND_UNREACHABLE();
}
zend_arena_release(&CG(arena), checkpoint);
if (JIT_G(trigger) == ZEND_JIT_ON_FIRST_EXEC
|| JIT_G(trigger) == ZEND_JIT_ON_PROF_REQUEST
|| JIT_G(trigger) == ZEND_JIT_ON_HOT_COUNTERS
|| JIT_G(trigger) == ZEND_JIT_ON_HOT_TRACE) {
zend_class_entry *ce;
zend_op_array *op_array;
ZEND_HASH_MAP_FOREACH_PTR(&script->class_table, ce) {
ZEND_HASH_MAP_FOREACH_PTR(&ce->function_table, op_array) {
if (!ZEND_FUNC_INFO(op_array)) {
void *jit_extension = zend_shared_alloc_get_xlat_entry(op_array->opcodes);
if (jit_extension) {
ZEND_SET_FUNC_INFO(op_array, jit_extension);
}
}
} ZEND_HASH_FOREACH_END();
} ZEND_HASH_FOREACH_END();
}
return SUCCESS;
jit_failure:
if (JIT_G(trigger) == ZEND_JIT_ON_SCRIPT_LOAD) {
for (i = 0; i < call_graph.op_arrays_count; i++) {
ZEND_SET_FUNC_INFO(call_graph.op_arrays[i], NULL);
}
}
zend_arena_release(&CG(arena), checkpoint);
return FAILURE;
}
ZEND_EXT_API void zend_jit_unprotect(void)
{
#ifdef HAVE_MPROTECT
if (!(JIT_G(debug) & (ZEND_JIT_DEBUG_GDB|ZEND_JIT_DEBUG_PERF_DUMP))) {
int opts = PROT_READ | PROT_WRITE;
#ifdef ZTS
#ifdef HAVE_PTHREAD_JIT_WRITE_PROTECT_NP
if (zend_write_protect) {
pthread_jit_write_protect_np(0);
}
#endif
opts |= PROT_EXEC;
#endif
if (mprotect(dasm_buf, dasm_size, opts) != 0) {
fprintf(stderr, "mprotect() failed [%d] %s\n", errno, strerror(errno));
}
}
#elif _WIN32
if (!(JIT_G(debug) & (ZEND_JIT_DEBUG_GDB|ZEND_JIT_DEBUG_PERF_DUMP))) {
DWORD old, new;
#ifdef ZTS
new = PAGE_EXECUTE_READWRITE;
#else
new = PAGE_READWRITE;
#endif
if (!VirtualProtect(dasm_buf, dasm_size, new, &old)) {
DWORD err = GetLastError();
char *msg = php_win32_error_to_msg(err);
fprintf(stderr, "VirtualProtect() failed [%u] %s\n", err, msg);
php_win32_error_msg_free(msg);
}
}
#endif
}
ZEND_EXT_API void zend_jit_protect(void)
{
#ifdef HAVE_MPROTECT
if (!(JIT_G(debug) & (ZEND_JIT_DEBUG_GDB|ZEND_JIT_DEBUG_PERF_DUMP))) {
#ifdef HAVE_PTHREAD_JIT_WRITE_PROTECT_NP
if (zend_write_protect) {
pthread_jit_write_protect_np(1);
}
#endif
if (mprotect(dasm_buf, dasm_size, PROT_READ | PROT_EXEC) != 0) {
fprintf(stderr, "mprotect() failed [%d] %s\n", errno, strerror(errno));
}
}
#elif _WIN32
if (!(JIT_G(debug) & (ZEND_JIT_DEBUG_GDB|ZEND_JIT_DEBUG_PERF_DUMP))) {
DWORD old;
if (!VirtualProtect(dasm_buf, dasm_size, PAGE_EXECUTE_READ, &old)) {
DWORD err = GetLastError();
char *msg = php_win32_error_to_msg(err);
fprintf(stderr, "VirtualProtect() failed [%u] %s\n", err, msg);
php_win32_error_msg_free(msg);
}
}
#endif
}
static void zend_jit_init_handlers(void)
{
if (zend_jit_vm_kind == ZEND_VM_KIND_HYBRID) {
zend_jit_runtime_jit_handler = dasm_labels[zend_lbhybrid_runtime_jit];
zend_jit_profile_jit_handler = dasm_labels[zend_lbhybrid_profile_jit];
zend_jit_func_hot_counter_handler = dasm_labels[zend_lbhybrid_func_hot_counter];
zend_jit_loop_hot_counter_handler = dasm_labels[zend_lbhybrid_loop_hot_counter];
zend_jit_func_trace_counter_handler = dasm_labels[zend_lbhybrid_func_trace_counter];
zend_jit_ret_trace_counter_handler = dasm_labels[zend_lbhybrid_ret_trace_counter];
zend_jit_loop_trace_counter_handler = dasm_labels[zend_lbhybrid_loop_trace_counter];
} else {
zend_jit_runtime_jit_handler = (const void*)zend_runtime_jit;
zend_jit_profile_jit_handler = (const void*)zend_jit_profile_helper;
zend_jit_func_hot_counter_handler = (const void*)zend_jit_func_counter_helper;
zend_jit_loop_hot_counter_handler = (const void*)zend_jit_loop_counter_helper;
zend_jit_func_trace_counter_handler = (const void*)zend_jit_func_trace_helper;
zend_jit_ret_trace_counter_handler = (const void*)zend_jit_ret_trace_helper;
zend_jit_loop_trace_counter_handler = (const void*)zend_jit_loop_trace_helper;
}
}
static int zend_jit_make_stubs(void)
{
dasm_State* dasm_state = NULL;
uint32_t i;
dasm_init(&dasm_state, DASM_MAXSECTION);
dasm_setupglobal(&dasm_state, dasm_labels, zend_lb_MAX);
for (i = 0; i < sizeof(zend_jit_stubs)/sizeof(zend_jit_stubs[0]); i++) {
dasm_setup(&dasm_state, dasm_actions);
zend_jit_align_stub(&dasm_state);
if (!zend_jit_stubs[i].stub(&dasm_state)) {
return 0;
}
if (!dasm_link_and_encode(&dasm_state, NULL, NULL, NULL, NULL, zend_jit_stubs[i].name, 0,
zend_jit_stubs[i].offset, zend_jit_stubs[i].adjustment)) {
return 0;
}
}
zend_jit_init_handlers();
dasm_free(&dasm_state);
return 1;
}
static void zend_jit_globals_ctor(zend_jit_globals *jit_globals)
{
memset(jit_globals, 0, sizeof(zend_jit_globals));
zend_jit_trace_init_caches();
}
#ifdef ZTS
static void zend_jit_globals_dtor(zend_jit_globals *jit_globals)
{
zend_jit_trace_free_caches(jit_globals);
}
#endif
static int zend_jit_parse_config_num(zend_long jit)
{
if (jit == 0) {
JIT_G(on) = 0;
return SUCCESS;
}
if (jit < 0) return FAILURE;
if (jit % 10 == 0 || jit % 10 > 5) return FAILURE;
JIT_G(opt_level) = jit % 10;
jit /= 10;
if (jit % 10 > 5) return FAILURE;
JIT_G(trigger) = jit % 10;
jit /= 10;
if (jit % 10 > 2) return FAILURE;
JIT_G(opt_flags) = jit % 10;
jit /= 10;
if (jit % 10 > 1) return FAILURE;
JIT_G(opt_flags) |= ((jit % 10) ? ZEND_JIT_CPU_AVX : 0);
if (jit / 10 != 0) return FAILURE;
JIT_G(on) = 1;
return SUCCESS;
}
ZEND_EXT_API int zend_jit_config(zend_string *jit, int stage)
{
if (stage != ZEND_INI_STAGE_STARTUP && !JIT_G(enabled)) {
if (stage == ZEND_INI_STAGE_RUNTIME) {
zend_error(E_WARNING, "Cannot change opcache.jit setting at run-time (JIT is disabled)");
}
return FAILURE;
}
if (ZSTR_LEN(jit) == 0
|| zend_string_equals_literal_ci(jit, "disable")) {
JIT_G(enabled) = 0;
JIT_G(on) = 0;
return SUCCESS;
} else if (zend_string_equals_literal_ci(jit, "0")
|| zend_string_equals_literal_ci(jit, "off")
|| zend_string_equals_literal_ci(jit, "no")
|| zend_string_equals_literal_ci(jit, "false")) {
JIT_G(enabled) = 1;
JIT_G(on) = 0;
return SUCCESS;
} else if (zend_string_equals_literal_ci(jit, "1")
|| zend_string_equals_literal_ci(jit, "on")
|| zend_string_equals_literal_ci(jit, "yes")
|| zend_string_equals_literal_ci(jit, "true")
|| zend_string_equals_literal_ci(jit, "tracing")) {
JIT_G(enabled) = 1;
JIT_G(on) = 1;
JIT_G(opt_level) = ZEND_JIT_LEVEL_OPT_FUNCS;
JIT_G(trigger) = ZEND_JIT_ON_HOT_TRACE;
JIT_G(opt_flags) = ZEND_JIT_REG_ALLOC_GLOBAL | ZEND_JIT_CPU_AVX;
return SUCCESS;
} else if (zend_string_equals_literal_ci(jit, "function")) {
JIT_G(enabled) = 1;
JIT_G(on) = 1;
JIT_G(opt_level) = ZEND_JIT_LEVEL_OPT_SCRIPT;
JIT_G(trigger) = ZEND_JIT_ON_SCRIPT_LOAD;
JIT_G(opt_flags) = ZEND_JIT_REG_ALLOC_GLOBAL | ZEND_JIT_CPU_AVX;
return SUCCESS;
} else {
char *end;
zend_long num = ZEND_STRTOL(ZSTR_VAL(jit), &end, 10);
if (end != ZSTR_VAL(jit) + ZSTR_LEN(jit) || zend_jit_parse_config_num(num) != SUCCESS) {
goto failure;
}
JIT_G(enabled) = 1;
return SUCCESS;
}
failure:
zend_error(E_WARNING, "Invalid \"opcache.jit\" setting. Should be \"disable\", \"on\", \"off\", \"tracing\", \"function\" or 4-digit number");
JIT_G(enabled) = 0;
JIT_G(on) = 0;
return FAILURE;
}
ZEND_EXT_API int zend_jit_debug_config(zend_long old_val, zend_long new_val, int stage)
{
if (stage != ZEND_INI_STAGE_STARTUP) {
if (((old_val ^ new_val) & ZEND_JIT_DEBUG_PERSISTENT) != 0) {
if (stage == ZEND_INI_STAGE_RUNTIME) {
zend_error(E_WARNING, "Some opcache.jit_debug bits cannot be changed after startup");
}
return FAILURE;
}
#ifdef HAVE_DISASM
if (new_val & (ZEND_JIT_DEBUG_ASM|ZEND_JIT_DEBUG_ASM_STUBS)) {
if (JIT_G(enabled) && !JIT_G(symbols) && !zend_jit_disasm_init()) {
// TODO: error reporting and cleanup ???
return FAILURE;
}
// TODO: symbols for JIT-ed code compiled before are missing ???
}
#endif
}
return SUCCESS;
}
ZEND_EXT_API void zend_jit_init(void)
{
#ifdef ZTS
jit_globals_id = ts_allocate_id(&jit_globals_id, sizeof(zend_jit_globals), (ts_allocate_ctor) zend_jit_globals_ctor, (ts_allocate_dtor) zend_jit_globals_dtor);
#else
zend_jit_globals_ctor(&jit_globals);
#endif
}
ZEND_EXT_API int zend_jit_check_support(void)
{
int i;
zend_jit_vm_kind = zend_vm_kind();
if (zend_jit_vm_kind != ZEND_VM_KIND_CALL &&
zend_jit_vm_kind != ZEND_VM_KIND_HYBRID) {
zend_error(E_WARNING, "JIT is compatible only with CALL and HYBRID VM. JIT disabled.");
JIT_G(enabled) = 0;
JIT_G(on) = 0;
return FAILURE;
}
if (zend_execute_ex != execute_ex) {
if (strcmp(sapi_module.name, "phpdbg") != 0) {
zend_error(E_WARNING, "JIT is incompatible with third party extensions that override zend_execute_ex(). JIT disabled.");
}
JIT_G(enabled) = 0;
JIT_G(on) = 0;
return FAILURE;
}
for (i = 0; i <= 256; i++) {
switch (i) {
/* JIT has no effect on these opcodes */
case ZEND_BEGIN_SILENCE:
case ZEND_END_SILENCE:
case ZEND_EXIT:
break;
default:
if (zend_get_user_opcode_handler(i) != NULL) {
zend_error(E_WARNING, "JIT is incompatible with third party extensions that setup user opcode handlers. JIT disabled.");
JIT_G(enabled) = 0;
JIT_G(on) = 0;
return FAILURE;
}
}
}
return SUCCESS;
}
ZEND_EXT_API int zend_jit_startup(void *buf, size_t size, bool reattached)
{
int ret;
zend_jit_halt_op = zend_get_halt_op();
if (zend_jit_setup() != SUCCESS) {
// TODO: error reporting and cleanup ???
return FAILURE;
}
zend_jit_profile_counter_rid = zend_get_op_array_extension_handle(ACCELERATOR_PRODUCT_NAME);
#ifdef HAVE_GDB
zend_jit_gdb_init();
#endif
#ifdef HAVE_PTHREAD_JIT_WRITE_PROTECT_NP
zend_write_protect = pthread_jit_write_protect_supported_np();
#endif
dasm_buf = buf;
dasm_size = size;
#ifdef HAVE_MPROTECT
#ifdef HAVE_PTHREAD_JIT_WRITE_PROTECT_NP
if (zend_write_protect) {
pthread_jit_write_protect_np(1);
}
#endif
if (JIT_G(debug) & (ZEND_JIT_DEBUG_GDB|ZEND_JIT_DEBUG_PERF_DUMP)) {
if (mprotect(dasm_buf, dasm_size, PROT_READ | PROT_WRITE | PROT_EXEC) != 0) {
fprintf(stderr, "mprotect() failed [%d] %s\n", errno, strerror(errno));
}
} else {
if (mprotect(dasm_buf, dasm_size, PROT_READ | PROT_EXEC) != 0) {
fprintf(stderr, "mprotect() failed [%d] %s\n", errno, strerror(errno));
}
}
#elif _WIN32
if (JIT_G(debug) & (ZEND_JIT_DEBUG_GDB|ZEND_JIT_DEBUG_PERF_DUMP)) {
DWORD old;
if (!VirtualProtect(dasm_buf, dasm_size, PAGE_EXECUTE_READWRITE, &old)) {
DWORD err = GetLastError();
char *msg = php_win32_error_to_msg(err);
fprintf(stderr, "VirtualProtect() failed [%u] %s\n", err, msg);
php_win32_error_msg_free(msg);
}
} else {
DWORD old;
if (!VirtualProtect(dasm_buf, dasm_size, PAGE_EXECUTE_READ, &old)) {
DWORD err = GetLastError();
char *msg = php_win32_error_to_msg(err);
fprintf(stderr, "VirtualProtect() failed [%u] %s\n", err, msg);
php_win32_error_msg_free(msg);
}
}
#endif
dasm_ptr = dasm_end = (void*)(((char*)dasm_buf) + size - sizeof(*dasm_ptr) * 2);
if (!reattached) {
zend_jit_unprotect();
*dasm_ptr = dasm_buf;
#if _WIN32
/* reserve space for global labels */
*dasm_ptr = (void**)*dasm_ptr + zend_lb_MAX;
#endif
zend_jit_protect();
}
#ifdef HAVE_DISASM
if (JIT_G(debug) & (ZEND_JIT_DEBUG_ASM|ZEND_JIT_DEBUG_ASM_STUBS)) {
if (!zend_jit_disasm_init()) {
// TODO: error reporting and cleanup ???
return FAILURE;
}
}
#endif
#ifdef HAVE_PERFTOOLS
if (JIT_G(debug) & ZEND_JIT_DEBUG_PERF_DUMP) {
zend_jit_perf_jitdump_open();
}
#endif
if (!reattached) {
zend_jit_unprotect();
ret = zend_jit_make_stubs();
#if _WIN32
/* save global labels */
memcpy(dasm_buf, dasm_labels, sizeof(void*) * zend_lb_MAX);
#endif
zend_jit_protect();
if (!ret) {
// TODO: error reporting and cleanup ???
return FAILURE;
}
} else {
#if _WIN32
/* restore global labels */
memcpy(dasm_labels, dasm_buf, sizeof(void*) * zend_lb_MAX);
zend_jit_init_handlers();
#endif
}
if (zend_jit_trace_startup(reattached) != SUCCESS) {
return FAILURE;
}
zend_jit_unprotect();
#if ZEND_JIT_TARGET_ARM64
/* reserve space for global labels veneers */
dasm_labels_veneers = *dasm_ptr;
*dasm_ptr = (void**)*dasm_ptr + ZEND_MM_ALIGNED_SIZE_EX(zend_lb_MAX, DASM_ALIGNMENT);
memset(dasm_labels_veneers, 0, sizeof(void*) * ZEND_MM_ALIGNED_SIZE_EX(zend_lb_MAX, DASM_ALIGNMENT));
#endif
/* save JIT buffer pos */
dasm_ptr[1] = dasm_ptr[0];
zend_jit_protect();
return SUCCESS;
}
ZEND_EXT_API void zend_jit_shutdown(void)
{
if (JIT_G(debug) & ZEND_JIT_DEBUG_SIZE) {
fprintf(stderr, "\nJIT memory usage: %td\n", (ptrdiff_t)((char*)*dasm_ptr - (char*)dasm_buf));
}
#ifdef HAVE_GDB
if (JIT_G(debug) & ZEND_JIT_DEBUG_GDB) {
zend_jit_gdb_unregister();
}
#endif
#ifdef HAVE_DISASM
zend_jit_disasm_shutdown();
#endif
#ifdef HAVE_PERFTOOLS
if (JIT_G(debug) & ZEND_JIT_DEBUG_PERF_DUMP) {
zend_jit_perf_jitdump_close();
}
#endif
#ifdef ZTS
ts_free_id(jit_globals_id);
#else
zend_jit_trace_free_caches(&jit_globals);
#endif
}
static void zend_jit_reset_counters(void)
{
int i;
for (i = 0; i < ZEND_HOT_COUNTERS_COUNT; i++) {
zend_jit_hot_counters[i] = ZEND_JIT_COUNTER_INIT;
}
}
ZEND_EXT_API void zend_jit_activate(void)
{
zend_jit_profile_counter = 0;
if (JIT_G(on)) {
if (JIT_G(trigger) == ZEND_JIT_ON_HOT_COUNTERS) {
zend_jit_reset_counters();
} else if (JIT_G(trigger) == ZEND_JIT_ON_HOT_TRACE) {
zend_jit_reset_counters();
zend_jit_trace_reset_caches();
}
}
}
ZEND_EXT_API void zend_jit_deactivate(void)
{
if (zend_jit_profile_counter) {
zend_class_entry *ce;
zend_shared_alloc_lock();
SHM_UNPROTECT();
zend_jit_unprotect();
zend_jit_check_funcs(EG(function_table), 0);
ZEND_HASH_MAP_REVERSE_FOREACH_PTR(EG(class_table), ce) {
if (ce->type == ZEND_INTERNAL_CLASS) {
break;
}
zend_jit_check_funcs(&ce->function_table, 1);
} ZEND_HASH_FOREACH_END();
zend_jit_protect();
SHM_PROTECT();
zend_shared_alloc_unlock();
zend_jit_profile_counter = 0;
}
}
static void zend_jit_restart_preloaded_op_array(zend_op_array *op_array)
{
zend_func_info *func_info = ZEND_FUNC_INFO(op_array);
if (!func_info) {
return;
}
if (func_info->flags & ZEND_FUNC_JIT_ON_HOT_TRACE) {
zend_jit_restart_hot_trace_counters(op_array);
} else if (func_info->flags & ZEND_FUNC_JIT_ON_HOT_COUNTERS) {
zend_jit_restart_hot_counters(op_array);
#if 0
// TODO: We have to restore handlers for some inner basic-blocks, but we didn't store them ???
} else if (func_info->flags & (ZEND_FUNC_JIT_ON_FIRST_EXEC|ZEND_FUNC_JIT_ON_PROF_REQUEST)) {
zend_op *opline = op_array->opcodes;
zend_jit_op_array_extension *jit_extension =
(zend_jit_op_array_extension*)func_info;
if (!(op_array->fn_flags & ZEND_ACC_HAS_TYPE_HINTS)) {
while (opline->opcode == ZEND_RECV || opline->opcode == ZEND_RECV_INIT) {
opline++;
}
}
if (func_info->flags & ZEND_FUNC_JIT_ON_FIRST_EXEC) {
opline->handler = (const void*)zend_jit_runtime_jit_handler;
} else {
opline->handler = (const void*)zend_jit_profile_jit_handler;
}
#endif
}
if (op_array->num_dynamic_func_defs) {
for (uint32_t i = 0; i < op_array->num_dynamic_func_defs; i++) {
zend_jit_restart_preloaded_op_array(op_array->dynamic_func_defs[i]);
}
}
}
static void zend_jit_restart_preloaded_script(zend_persistent_script *script)
{
zend_class_entry *ce;
zend_op_array *op_array;
zend_jit_restart_preloaded_op_array(&script->script.main_op_array);
ZEND_HASH_MAP_FOREACH_PTR(&script->script.function_table, op_array) {
zend_jit_restart_preloaded_op_array(op_array);
} ZEND_HASH_FOREACH_END();
ZEND_HASH_MAP_FOREACH_PTR(&script->script.class_table, ce) {
ZEND_HASH_MAP_FOREACH_PTR(&ce->function_table, op_array) {
if (op_array->type == ZEND_USER_FUNCTION) {
zend_jit_restart_preloaded_op_array(op_array);
}
} ZEND_HASH_FOREACH_END();
} ZEND_HASH_FOREACH_END();
}
ZEND_EXT_API void zend_jit_restart(void)
{
if (dasm_buf) {
zend_jit_unprotect();
#if ZEND_JIT_TARGET_ARM64
memset(dasm_labels_veneers, 0, sizeof(void*) * ZEND_MM_ALIGNED_SIZE_EX(zend_lb_MAX, DASM_ALIGNMENT));
#endif
/* restore JIT buffer pos */
dasm_ptr[0] = dasm_ptr[1];
zend_jit_trace_restart();
if (ZCSG(preload_script)) {
zend_jit_restart_preloaded_script(ZCSG(preload_script));
if (ZCSG(saved_scripts)) {
zend_persistent_script **p = ZCSG(saved_scripts);
while (*p) {
zend_jit_restart_preloaded_script(*p);
p++;
}
}
}
zend_jit_protect();
#ifdef HAVE_DISASM
if (JIT_G(debug) & (ZEND_JIT_DEBUG_ASM|ZEND_JIT_DEBUG_ASM_STUBS)) {
zend_jit_disasm_shutdown();
zend_jit_disasm_init();
}
#endif
}
}
#endif /* HAVE_JIT */