Permalink
Browse files

Implemented Request #76178 (Class constants are slow: they should be …

…inlined at runtime)

Run-time cache is used to eliminate recalculation of constant expression in RECV_INIT opcode (only non reference countable values are cached).
  • Loading branch information...
dstogov committed May 3, 2018
1 parent 5a31e14 commit 1a63fa6ec9b0bacbb726e60c3c212e7d97b518c6
Showing with 75 additions and 14 deletions.
  1. +10 −0 Zend/zend_opcode.c
  2. +4 −0 Zend/zend_types.h
  3. +21 −7 Zend/zend_vm_def.h
  4. +21 −7 Zend/zend_vm_execute.h
  5. +19 −0 ext/opcache/Optimizer/compact_literals.c
@@ -579,6 +579,16 @@ ZEND_API int pass_two(zend_op_array *op_array)
end = opline + op_array->last;
while (opline < end) {
switch (opline->opcode) {
case ZEND_RECV_INIT:
{
zval *val = CT_CONSTANT(opline->op2);
if (Z_TYPE_P(val) == IS_CONSTANT_AST) {
uint32_t slot = ZEND_MM_ALIGNED_SIZE_EX(op_array->cache_size, 8);
Z_CACHE_SLOT_P(val) = slot;
op_array->cache_size += sizeof(zval);
}
}
break;
case ZEND_FAST_CALL:
opline->op1.opline_num = op_array->try_catch_array[opline->op1.num].finally_op;
ZEND_PASS_TWO_UPDATE_JMP_TARGET(op_array, opline, opline->op1);
@@ -192,6 +192,7 @@ struct _zval_struct {
} u1;
union {
uint32_t next; /* hash collision chain */
uint32_t cache_slot; /* cache slot (for RECV_INIT) */
uint32_t opline_num; /* opline number (for FAST_CALL) */
uint32_t lineno; /* line number (for ast nodes) */
uint32_t num_args; /* arguments number for EX(This) */
@@ -404,6 +405,9 @@ static zend_always_inline zend_uchar zval_get_type(const zval* pz) {
#define Z_NEXT(zval) (zval).u2.next
#define Z_NEXT_P(zval_p) Z_NEXT(*(zval_p))

#define Z_CACHE_SLOT(zval) (zval).u2.cache_slot
#define Z_CACHE_SLOT_P(zval_p) Z_CACHE_SLOT(*(zval_p))

#define Z_LINENO(zval) (zval).u2.lineno
#define Z_LINENO_P(zval_p) Z_LINENO(*(zval_p))

@@ -4724,14 +4724,28 @@ ZEND_VM_HOT_HANDLER(64, ZEND_RECV_INIT, NUM, CONST, CACHE_SLOT)
arg_num = opline->op1.num;
param = _get_zval_ptr_cv_undef_BP_VAR_W(opline->result.var EXECUTE_DATA_CC);
if (arg_num > EX_NUM_ARGS()) {
ZVAL_COPY(param, RT_CONSTANT(opline, opline->op2));
if (Z_OPT_TYPE_P(param) == IS_CONSTANT_AST) {
SAVE_OPLINE();
if (UNEXPECTED(zval_update_constant_ex(param, EX(func)->op_array.scope) != SUCCESS)) {
zval_ptr_dtor_nogc(param);
ZVAL_UNDEF(param);
HANDLE_EXCEPTION();
zval *default_value = RT_CONSTANT(opline, opline->op2);

if (Z_OPT_TYPE_P(default_value) == IS_CONSTANT_AST) {
zval *cache_val = (zval*)CACHE_ADDR(Z_CACHE_SLOT_P(default_value));

/* we keep in cache only not refcounted values */
if (Z_TYPE_P(cache_val) != IS_UNDEF) {
ZVAL_COPY_VALUE(param, cache_val);
} else {
SAVE_OPLINE();
ZVAL_COPY(param, default_value);
if (UNEXPECTED(zval_update_constant_ex(param, EX(func)->op_array.scope) != SUCCESS)) {
zval_ptr_dtor_nogc(param);
ZVAL_UNDEF(param);
HANDLE_EXCEPTION();
}
if (!Z_REFCOUNTED_P(param)) {
ZVAL_COPY_VALUE(cache_val, param);
}
}
} else {
ZVAL_COPY(param, default_value);
}
}

@@ -2237,14 +2237,28 @@ static ZEND_VM_HOT ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_RECV_INIT_SPEC_CON
arg_num = opline->op1.num;
param = _get_zval_ptr_cv_undef_BP_VAR_W(opline->result.var EXECUTE_DATA_CC);
if (arg_num > EX_NUM_ARGS()) {
ZVAL_COPY(param, RT_CONSTANT(opline, opline->op2));
if (Z_OPT_TYPE_P(param) == IS_CONSTANT_AST) {
SAVE_OPLINE();
if (UNEXPECTED(zval_update_constant_ex(param, EX(func)->op_array.scope) != SUCCESS)) {
zval_ptr_dtor_nogc(param);
ZVAL_UNDEF(param);
HANDLE_EXCEPTION();
zval *default_value = RT_CONSTANT(opline, opline->op2);

if (Z_OPT_TYPE_P(default_value) == IS_CONSTANT_AST) {
zval *cache_val = (zval*)CACHE_ADDR(Z_CACHE_SLOT_P(default_value));

/* we keep in cache only not refcounted values */
if (Z_TYPE_P(cache_val) != IS_UNDEF) {
ZVAL_COPY_VALUE(param, cache_val);
} else {
SAVE_OPLINE();
ZVAL_COPY(param, default_value);
if (UNEXPECTED(zval_update_constant_ex(param, EX(func)->op_array.scope) != SUCCESS)) {
zval_ptr_dtor_nogc(param);
ZVAL_UNDEF(param);
HANDLE_EXCEPTION();
}
if (!Z_REFCOUNTED_P(param)) {
ZVAL_COPY_VALUE(cache_val, param);
}
}
} else {
ZVAL_COPY(param, default_value);
}
}

@@ -740,6 +740,25 @@ void zend_optimizer_compact_literals(zend_op_array *op_array, zend_optimizer_ctx
zend_hash_destroy(&hash);
zend_arena_release(&ctx->arena, checkpoint);

if (1) {

This comment has been minimized.

@lyrixx

lyrixx May 4, 2018

Hello, I don't know C, but it looks a bit odd. Why did you do that ?

This comment has been minimized.

@dstogov

dstogov May 4, 2018

Contributor

I thought about previous check for ZEND_RECV_INIT existence and elimination of this loop, but didn't implement this.

This comment has been minimized.

@smichaelsen

smichaelsen May 4, 2018

I think the question is not why the loop is there, but why it is wrapped by an if(1) condition. I don't know much about C either.

opline = op_array->opcodes;
while (1) {
if (opline->opcode == ZEND_RECV_INIT) {
zval *val = &op_array->literals[opline->op2.constant];

if (Z_TYPE_P(val) == IS_CONSTANT_AST) {
uint32_t slot = ZEND_MM_ALIGNED_SIZE_EX(op_array->cache_size, 8);

Z_CACHE_SLOT_P(val) = slot;
op_array->cache_size += sizeof(zval);
}
} else if (opline->opcode != ZEND_RECV) {
break;
}
opline++;
}
}

#if DEBUG_COMPACT_LITERALS
{
int i, use_copy;

0 comments on commit 1a63fa6

Please sign in to comment.