void zend_optimize_func_calls(zend_op_array *op_array, zend_optimizer_ctx *ctx) { zend_op *opline = op_array->opcodes; zend_op *end = opline + op_array->last; int call = 0; void *checkpoint; optimizer_call_info *call_stack; if (op_array->last < 2) { return; } checkpoint = zend_arena_checkpoint(ctx->arena); call_stack = zend_arena_calloc(&ctx->arena, op_array->last / 2, sizeof(optimizer_call_info)); while (opline < end) { switch (opline->opcode) { case ZEND_INIT_FCALL_BY_NAME: case ZEND_INIT_NS_FCALL_BY_NAME: case ZEND_INIT_STATIC_METHOD_CALL: case ZEND_INIT_METHOD_CALL: case ZEND_INIT_FCALL: case ZEND_NEW: call_stack[call].func = zend_optimizer_get_called_func( ctx->script, op_array, opline, 0); call_stack[call].try_inline = opline->opcode != ZEND_NEW; /* break missing intentionally */ case ZEND_INIT_DYNAMIC_CALL: case ZEND_INIT_USER_CALL: call_stack[call].opline = opline; call++; break; case ZEND_DO_FCALL: case ZEND_DO_ICALL: case ZEND_DO_UCALL: case ZEND_DO_FCALL_BY_NAME: call--; if (call_stack[call].func && call_stack[call].opline) { zend_op *fcall = call_stack[call].opline; if (fcall->opcode == ZEND_INIT_FCALL) { /* nothing to do */ } else if (fcall->opcode == ZEND_INIT_FCALL_BY_NAME) { fcall->opcode = ZEND_INIT_FCALL; fcall->op1.num = zend_vm_calc_used_stack(fcall->extended_value, call_stack[call].func); Z_CACHE_SLOT(op_array->literals[fcall->op2.constant + 1]) = Z_CACHE_SLOT(op_array->literals[fcall->op2.constant]); literal_dtor(&ZEND_OP2_LITERAL(fcall)); fcall->op2.constant = fcall->op2.constant + 1; opline->opcode = zend_get_call_op(fcall, call_stack[call].func); } else if (fcall->opcode == ZEND_INIT_NS_FCALL_BY_NAME) { fcall->opcode = ZEND_INIT_FCALL; fcall->op1.num = zend_vm_calc_used_stack(fcall->extended_value, call_stack[call].func); Z_CACHE_SLOT(op_array->literals[fcall->op2.constant + 1]) = Z_CACHE_SLOT(op_array->literals[fcall->op2.constant]); literal_dtor(&op_array->literals[fcall->op2.constant]); literal_dtor(&op_array->literals[fcall->op2.constant + 2]); fcall->op2.constant = fcall->op2.constant + 1; opline->opcode = zend_get_call_op(fcall, call_stack[call].func); } else if (fcall->opcode == ZEND_INIT_STATIC_METHOD_CALL || fcall->opcode == ZEND_INIT_METHOD_CALL || fcall->opcode == ZEND_NEW) { /* We don't have specialized opcodes for this, do nothing */ } else { ZEND_ASSERT(0); } if ((ZEND_OPTIMIZER_PASS_16 & ctx->optimization_level) && call_stack[call].try_inline) { zend_try_inline_call(op_array, fcall, opline, call_stack[call].func); } } call_stack[call].func = NULL; call_stack[call].opline = NULL; call_stack[call].try_inline = 0; break; case ZEND_FETCH_FUNC_ARG: case ZEND_FETCH_STATIC_PROP_FUNC_ARG: case ZEND_FETCH_OBJ_FUNC_ARG: case ZEND_FETCH_DIM_FUNC_ARG: if (call_stack[call - 1].func) { if (ARG_SHOULD_BE_SENT_BY_REF(call_stack[call - 1].func, (opline->extended_value & ZEND_FETCH_ARG_MASK))) { opline->extended_value &= ZEND_FETCH_TYPE_MASK; if (opline->opcode != ZEND_FETCH_STATIC_PROP_FUNC_ARG) { opline->opcode -= 9; } else { opline->opcode = ZEND_FETCH_STATIC_PROP_W; } } else { if (opline->opcode == ZEND_FETCH_DIM_FUNC_ARG && opline->op2_type == IS_UNUSED) { /* FETCH_DIM_FUNC_ARG supports UNUSED op2, while FETCH_DIM_R does not. * Performing the replacement would create an invalid opcode. */ call_stack[call - 1].try_inline = 0; break; } opline->extended_value &= ZEND_FETCH_TYPE_MASK; if (opline->opcode != ZEND_FETCH_STATIC_PROP_FUNC_ARG) { opline->opcode -= 12; } else { opline->opcode = ZEND_FETCH_STATIC_PROP_R; } } } break; case ZEND_SEND_VAL_EX: if (call_stack[call - 1].func) { if (ARG_MUST_BE_SENT_BY_REF(call_stack[call - 1].func, opline->op2.num)) { /* We won't convert it into_DO_FCALL to emit error at run-time */ call_stack[call - 1].opline = NULL; } else { opline->opcode = ZEND_SEND_VAL; } } break; case ZEND_SEND_VAR_EX: if (call_stack[call - 1].func) { if (ARG_SHOULD_BE_SENT_BY_REF(call_stack[call - 1].func, opline->op2.num)) { opline->opcode = ZEND_SEND_REF; } else { opline->opcode = ZEND_SEND_VAR; } } break; case ZEND_SEND_VAR_NO_REF_EX: if (call_stack[call - 1].func) { if (ARG_MUST_BE_SENT_BY_REF(call_stack[call - 1].func, opline->op2.num)) { opline->opcode = ZEND_SEND_VAR_NO_REF; } else if (ARG_MAY_BE_SENT_BY_REF(call_stack[call - 1].func, opline->op2.num)) { opline->opcode = ZEND_SEND_VAL; } else { opline->opcode = ZEND_SEND_VAR; } } break; case ZEND_SEND_UNPACK: case ZEND_SEND_USER: case ZEND_SEND_ARRAY: call_stack[call - 1].try_inline = 0; break; default: break; } opline++; } zend_arena_release(&ctx->arena, checkpoint); }
void zend_optimize_func_calls(zend_op_array *op_array, zend_optimizer_ctx *ctx) { zend_op *opline = op_array->opcodes; zend_op *end = opline + op_array->last; int call = 0; void *checkpoint; optimizer_call_info *call_stack; if (op_array->last < 2) { return; } checkpoint = zend_arena_checkpoint(ctx->arena); call_stack = zend_arena_calloc(&ctx->arena, op_array->last / 2, sizeof(optimizer_call_info)); while (opline < end) { switch (opline->opcode) { case ZEND_INIT_FCALL_BY_NAME: case ZEND_INIT_NS_FCALL_BY_NAME: if (ZEND_OP2_IS_CONST_STRING(opline)) { zend_function *func; zval *function_name = &ZEND_OP2_LITERAL(opline) + 1; if ((func = zend_hash_find_ptr(&ctx->script->function_table, Z_STR_P(function_name))) != NULL) { call_stack[call].func = func; } } call_stack[call].opline = opline; call++; break; case ZEND_INIT_STATIC_METHOD_CALL: if (ZEND_OP2_IS_CONST_STRING(opline)) { zend_class_entry *ce = NULL; if (ZEND_OP1_IS_CONST_STRING(opline)) { zend_string *class_name = Z_STR_P(&ZEND_OP1_LITERAL(opline) + 1); ce = zend_hash_find_ptr(&ctx->script->class_table, class_name); } else if (opline->op1_type == IS_UNUSED && op_array->scope && !(op_array->scope->ce_flags & ZEND_ACC_TRAIT) && (opline->op1.num & ZEND_FETCH_CLASS_MASK) == ZEND_FETCH_CLASS_SELF) { ce = op_array->scope; } if (ce) { zend_string *func_name = Z_STR_P(&ZEND_OP2_LITERAL(opline) + 1); call_stack[call].func = zend_hash_find_ptr(&ce->function_table, func_name); } } /* break missing intentionally */ case ZEND_NEW: case ZEND_INIT_DYNAMIC_CALL: case ZEND_INIT_METHOD_CALL: case ZEND_INIT_FCALL: case ZEND_INIT_USER_CALL: call_stack[call].opline = opline; call++; break; case ZEND_DO_FCALL: case ZEND_DO_ICALL: case ZEND_DO_UCALL: case ZEND_DO_FCALL_BY_NAME: call--; if (call_stack[call].func && call_stack[call].opline) { zend_op *fcall = call_stack[call].opline; if (fcall->opcode == ZEND_INIT_FCALL_BY_NAME) { fcall->opcode = ZEND_INIT_FCALL; fcall->op1.num = zend_vm_calc_used_stack(fcall->extended_value, call_stack[call].func); Z_CACHE_SLOT(op_array->literals[fcall->op2.constant + 1]) = Z_CACHE_SLOT(op_array->literals[fcall->op2.constant]); literal_dtor(&ZEND_OP2_LITERAL(fcall)); fcall->op2.constant = fcall->op2.constant + 1; opline->opcode = zend_get_call_op(ZEND_INIT_FCALL, call_stack[call].func); } else if (fcall->opcode == ZEND_INIT_NS_FCALL_BY_NAME) { fcall->opcode = ZEND_INIT_FCALL; fcall->op1.num = zend_vm_calc_used_stack(fcall->extended_value, call_stack[call].func); Z_CACHE_SLOT(op_array->literals[fcall->op2.constant + 1]) = Z_CACHE_SLOT(op_array->literals[fcall->op2.constant]); literal_dtor(&op_array->literals[fcall->op2.constant]); literal_dtor(&op_array->literals[fcall->op2.constant + 2]); fcall->op2.constant = fcall->op2.constant + 1; opline->opcode = zend_get_call_op(ZEND_INIT_FCALL, call_stack[call].func); } else if (fcall->opcode == ZEND_INIT_STATIC_METHOD_CALL) { /* We don't have specialized opcodes for this, do nothing */ } else { ZEND_ASSERT(0); } } call_stack[call].func = NULL; call_stack[call].opline = NULL; break; case ZEND_FETCH_FUNC_ARG: case ZEND_FETCH_STATIC_PROP_FUNC_ARG: case ZEND_FETCH_OBJ_FUNC_ARG: case ZEND_FETCH_DIM_FUNC_ARG: if (call_stack[call - 1].func) { if (ARG_SHOULD_BE_SENT_BY_REF(call_stack[call - 1].func, (opline->extended_value & ZEND_FETCH_ARG_MASK))) { opline->extended_value &= ZEND_FETCH_TYPE_MASK; if (opline->opcode != ZEND_FETCH_STATIC_PROP_FUNC_ARG) { opline->opcode -= 9; } else { opline->opcode = ZEND_FETCH_STATIC_PROP_W; } } else { opline->extended_value &= ZEND_FETCH_TYPE_MASK; if (opline->opcode != ZEND_FETCH_STATIC_PROP_FUNC_ARG) { opline->opcode -= 12; } else { opline->opcode = ZEND_FETCH_STATIC_PROP_R; } } } break; case ZEND_SEND_VAL_EX: if (call_stack[call - 1].func) { if (ARG_MUST_BE_SENT_BY_REF(call_stack[call - 1].func, opline->op2.num)) { /* We won't convert it into_DO_FCALL to emit error at run-time */ call_stack[call - 1].opline = NULL; } else { opline->opcode = ZEND_SEND_VAL; } } break; case ZEND_SEND_VAR_EX: if (call_stack[call - 1].func) { if (ARG_SHOULD_BE_SENT_BY_REF(call_stack[call - 1].func, opline->op2.num)) { opline->opcode = ZEND_SEND_REF; } else { opline->opcode = ZEND_SEND_VAR; } } break; case ZEND_SEND_VAR_NO_REF: if (!(opline->extended_value & ZEND_ARG_COMPILE_TIME_BOUND) && call_stack[call - 1].func) { if (ARG_SHOULD_BE_SENT_BY_REF(call_stack[call - 1].func, opline->op2.num)) { opline->extended_value |= ZEND_ARG_COMPILE_TIME_BOUND | ZEND_ARG_SEND_BY_REF; } else { opline->opcode = ZEND_SEND_VAR; opline->extended_value = 0; } } break; #if 0 case ZEND_SEND_REF: if (opline->extended_value != ZEND_ARG_COMPILE_TIME_BOUND && call_stack[call - 1].func) { /* We won't handle run-time pass by reference */ call_stack[call - 1].opline = NULL; } break; #endif case ZEND_SEND_UNPACK: call_stack[call - 1].func = NULL; call_stack[call - 1].opline = NULL; break; default: break; } opline++; } zend_arena_release(&ctx->arena, checkpoint); }