optimize_func_calls.c 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356
  1. /*
  2. +----------------------------------------------------------------------+
  3. | Zend OPcache |
  4. +----------------------------------------------------------------------+
  5. | Copyright (c) The PHP Group |
  6. +----------------------------------------------------------------------+
  7. | This source file is subject to version 3.01 of the PHP license, |
  8. | that is bundled with this package in the file LICENSE, and is |
  9. | available through the world-wide-web at the following url: |
  10. | https://www.php.net/license/3_01.txt |
  11. | If you did not receive a copy of the PHP license and are unable to |
  12. | obtain it through the world-wide-web, please send a note to |
  13. | license@php.net so we can mail you a copy immediately. |
  14. +----------------------------------------------------------------------+
  15. | Authors: Dmitry Stogov <dmitry@php.net> |
  16. | Xinchen Hui <laruence@php.net> |
  17. +----------------------------------------------------------------------+
  18. */
  19. /* pass 4
  20. * - optimize INIT_FCALL_BY_NAME to DO_FCALL
  21. */
  22. #include "Optimizer/zend_optimizer.h"
  23. #include "Optimizer/zend_optimizer_internal.h"
  24. #include "zend_API.h"
  25. #include "zend_constants.h"
  26. #include "zend_execute.h"
  27. #include "zend_vm.h"
  28. #define ZEND_OP1_IS_CONST_STRING(opline) \
  29. (opline->op1_type == IS_CONST && \
  30. Z_TYPE(op_array->literals[(opline)->op1.constant]) == IS_STRING)
  31. #define ZEND_OP2_IS_CONST_STRING(opline) \
  32. (opline->op2_type == IS_CONST && \
  33. Z_TYPE(op_array->literals[(opline)->op2.constant]) == IS_STRING)
  34. typedef struct _optimizer_call_info {
  35. zend_function *func;
  36. zend_op *opline;
  37. bool is_prototype;
  38. bool try_inline;
  39. uint32_t func_arg_num;
  40. } optimizer_call_info;
  41. static void zend_delete_call_instructions(zend_op *opline)
  42. {
  43. int call = 0;
  44. while (1) {
  45. switch (opline->opcode) {
  46. case ZEND_INIT_FCALL_BY_NAME:
  47. case ZEND_INIT_NS_FCALL_BY_NAME:
  48. case ZEND_INIT_STATIC_METHOD_CALL:
  49. case ZEND_INIT_METHOD_CALL:
  50. case ZEND_INIT_FCALL:
  51. if (call == 0) {
  52. MAKE_NOP(opline);
  53. return;
  54. }
  55. ZEND_FALLTHROUGH;
  56. case ZEND_NEW:
  57. case ZEND_INIT_DYNAMIC_CALL:
  58. case ZEND_INIT_USER_CALL:
  59. call--;
  60. break;
  61. case ZEND_DO_FCALL:
  62. case ZEND_DO_ICALL:
  63. case ZEND_DO_UCALL:
  64. case ZEND_DO_FCALL_BY_NAME:
  65. call++;
  66. break;
  67. case ZEND_SEND_VAL:
  68. case ZEND_SEND_VAR:
  69. if (call == 0) {
  70. if (opline->op1_type == IS_CONST) {
  71. MAKE_NOP(opline);
  72. } else if (opline->op1_type == IS_CV) {
  73. opline->opcode = ZEND_CHECK_VAR;
  74. opline->extended_value = 0;
  75. opline->result.var = 0;
  76. } else {
  77. opline->opcode = ZEND_FREE;
  78. opline->extended_value = 0;
  79. opline->result.var = 0;
  80. }
  81. }
  82. break;
  83. }
  84. opline--;
  85. }
  86. }
  87. static void zend_try_inline_call(zend_op_array *op_array, zend_op *fcall, zend_op *opline, zend_function *func)
  88. {
  89. if (func->type == ZEND_USER_FUNCTION
  90. && !(func->op_array.fn_flags & (ZEND_ACC_ABSTRACT|ZEND_ACC_HAS_TYPE_HINTS))
  91. /* TODO: function copied from trait may be inconsistent ??? */
  92. && !(func->op_array.fn_flags & (ZEND_ACC_TRAIT_CLONE))
  93. && fcall->extended_value >= func->op_array.required_num_args
  94. && func->op_array.opcodes[func->op_array.num_args].opcode == ZEND_RETURN) {
  95. zend_op *ret_opline = func->op_array.opcodes + func->op_array.num_args;
  96. if (ret_opline->op1_type == IS_CONST) {
  97. uint32_t i, num_args = func->op_array.num_args;
  98. num_args += (func->op_array.fn_flags & ZEND_ACC_VARIADIC) != 0;
  99. if (fcall->opcode == ZEND_INIT_STATIC_METHOD_CALL
  100. && !(func->op_array.fn_flags & ZEND_ACC_STATIC)) {
  101. /* Don't inline static call to instance method. */
  102. return;
  103. }
  104. for (i = 0; i < num_args; i++) {
  105. /* Don't inline functions with by-reference arguments. This would require
  106. * correct handling of INDIRECT arguments. */
  107. if (ZEND_ARG_SEND_MODE(&func->op_array.arg_info[i])) {
  108. return;
  109. }
  110. }
  111. if (fcall->extended_value < func->op_array.num_args) {
  112. /* don't inline functions with named constants in default arguments */
  113. i = fcall->extended_value;
  114. do {
  115. if (Z_TYPE_P(CRT_CONSTANT_EX(&func->op_array, &func->op_array.opcodes[i], func->op_array.opcodes[i].op2)) == IS_CONSTANT_AST) {
  116. return;
  117. }
  118. i++;
  119. } while (i < func->op_array.num_args);
  120. }
  121. if (RETURN_VALUE_USED(opline)) {
  122. zval zv;
  123. ZVAL_COPY(&zv, CRT_CONSTANT_EX(&func->op_array, ret_opline, ret_opline->op1));
  124. opline->opcode = ZEND_QM_ASSIGN;
  125. opline->op1_type = IS_CONST;
  126. opline->op1.constant = zend_optimizer_add_literal(op_array, &zv);
  127. SET_UNUSED(opline->op2);
  128. } else {
  129. MAKE_NOP(opline);
  130. }
  131. zend_delete_call_instructions(opline-1);
  132. }
  133. }
  134. }
  135. /* arg_num is 1-based here, to match SEND encoding. */
  136. static bool has_known_send_mode(const optimizer_call_info *info, uint32_t arg_num)
  137. {
  138. if (!info->func) {
  139. return false;
  140. }
  141. /* For prototype functions we should not make assumptions about arguments that are not part of
  142. * the signature: And inheriting method can add an optional by-ref argument. */
  143. return !info->is_prototype
  144. || arg_num <= info->func->common.num_args
  145. || (info->func->common.fn_flags & ZEND_ACC_VARIADIC);
  146. }
  147. void zend_optimize_func_calls(zend_op_array *op_array, zend_optimizer_ctx *ctx)
  148. {
  149. zend_op *opline = op_array->opcodes;
  150. zend_op *end = opline + op_array->last;
  151. int call = 0;
  152. void *checkpoint;
  153. optimizer_call_info *call_stack;
  154. if (op_array->last < 2) {
  155. return;
  156. }
  157. checkpoint = zend_arena_checkpoint(ctx->arena);
  158. call_stack = zend_arena_calloc(&ctx->arena, op_array->last / 2, sizeof(optimizer_call_info));
  159. while (opline < end) {
  160. switch (opline->opcode) {
  161. case ZEND_INIT_FCALL_BY_NAME:
  162. case ZEND_INIT_NS_FCALL_BY_NAME:
  163. case ZEND_INIT_STATIC_METHOD_CALL:
  164. case ZEND_INIT_METHOD_CALL:
  165. case ZEND_INIT_FCALL:
  166. case ZEND_NEW:
  167. /* The argument passing optimizations are valid for prototypes as well,
  168. * as inheritance cannot change between ref <-> non-ref arguments. */
  169. call_stack[call].func = zend_optimizer_get_called_func(
  170. ctx->script, op_array, opline, &call_stack[call].is_prototype);
  171. call_stack[call].try_inline =
  172. !call_stack[call].is_prototype && opline->opcode != ZEND_NEW;
  173. ZEND_FALLTHROUGH;
  174. case ZEND_INIT_DYNAMIC_CALL:
  175. case ZEND_INIT_USER_CALL:
  176. call_stack[call].opline = opline;
  177. call_stack[call].func_arg_num = (uint32_t)-1;
  178. call++;
  179. break;
  180. case ZEND_DO_FCALL:
  181. case ZEND_DO_ICALL:
  182. case ZEND_DO_UCALL:
  183. case ZEND_DO_FCALL_BY_NAME:
  184. case ZEND_CALLABLE_CONVERT:
  185. call--;
  186. if (call_stack[call].func && call_stack[call].opline) {
  187. zend_op *fcall = call_stack[call].opline;
  188. if (fcall->opcode == ZEND_INIT_FCALL) {
  189. /* nothing to do */
  190. } else if (fcall->opcode == ZEND_INIT_FCALL_BY_NAME) {
  191. fcall->opcode = ZEND_INIT_FCALL;
  192. fcall->op1.num = zend_vm_calc_used_stack(fcall->extended_value, call_stack[call].func);
  193. literal_dtor(&ZEND_OP2_LITERAL(fcall));
  194. fcall->op2.constant = fcall->op2.constant + 1;
  195. if (opline->opcode != ZEND_CALLABLE_CONVERT) {
  196. opline->opcode = zend_get_call_op(fcall, call_stack[call].func);
  197. }
  198. } else if (fcall->opcode == ZEND_INIT_NS_FCALL_BY_NAME) {
  199. fcall->opcode = ZEND_INIT_FCALL;
  200. fcall->op1.num = zend_vm_calc_used_stack(fcall->extended_value, call_stack[call].func);
  201. literal_dtor(&op_array->literals[fcall->op2.constant]);
  202. literal_dtor(&op_array->literals[fcall->op2.constant + 2]);
  203. fcall->op2.constant = fcall->op2.constant + 1;
  204. if (opline->opcode != ZEND_CALLABLE_CONVERT) {
  205. opline->opcode = zend_get_call_op(fcall, call_stack[call].func);
  206. }
  207. } else if (fcall->opcode == ZEND_INIT_STATIC_METHOD_CALL
  208. || fcall->opcode == ZEND_INIT_METHOD_CALL
  209. || fcall->opcode == ZEND_NEW) {
  210. /* We don't have specialized opcodes for this, do nothing */
  211. } else {
  212. ZEND_UNREACHABLE();
  213. }
  214. if ((ZEND_OPTIMIZER_PASS_16 & ctx->optimization_level)
  215. && call_stack[call].try_inline
  216. && opline->opcode != ZEND_CALLABLE_CONVERT) {
  217. zend_try_inline_call(op_array, fcall, opline, call_stack[call].func);
  218. }
  219. }
  220. call_stack[call].func = NULL;
  221. call_stack[call].opline = NULL;
  222. call_stack[call].try_inline = 0;
  223. call_stack[call].func_arg_num = (uint32_t)-1;
  224. break;
  225. case ZEND_FETCH_FUNC_ARG:
  226. case ZEND_FETCH_STATIC_PROP_FUNC_ARG:
  227. case ZEND_FETCH_OBJ_FUNC_ARG:
  228. case ZEND_FETCH_DIM_FUNC_ARG:
  229. if (call_stack[call - 1].func_arg_num != (uint32_t)-1
  230. && has_known_send_mode(&call_stack[call - 1], call_stack[call - 1].func_arg_num)) {
  231. if (ARG_SHOULD_BE_SENT_BY_REF(call_stack[call - 1].func, call_stack[call - 1].func_arg_num)) {
  232. if (opline->opcode != ZEND_FETCH_STATIC_PROP_FUNC_ARG) {
  233. opline->opcode -= 9;
  234. } else {
  235. opline->opcode = ZEND_FETCH_STATIC_PROP_W;
  236. }
  237. } else {
  238. if (opline->opcode == ZEND_FETCH_DIM_FUNC_ARG
  239. && opline->op2_type == IS_UNUSED) {
  240. /* FETCH_DIM_FUNC_ARG supports UNUSED op2, while FETCH_DIM_R does not.
  241. * Performing the replacement would create an invalid opcode. */
  242. call_stack[call - 1].try_inline = 0;
  243. break;
  244. }
  245. if (opline->opcode != ZEND_FETCH_STATIC_PROP_FUNC_ARG) {
  246. opline->opcode -= 12;
  247. } else {
  248. opline->opcode = ZEND_FETCH_STATIC_PROP_R;
  249. }
  250. }
  251. }
  252. break;
  253. case ZEND_SEND_VAL_EX:
  254. if (opline->op2_type == IS_CONST) {
  255. call_stack[call - 1].try_inline = 0;
  256. break;
  257. }
  258. if (has_known_send_mode(&call_stack[call - 1], opline->op2.num)) {
  259. if (ARG_MUST_BE_SENT_BY_REF(call_stack[call - 1].func, opline->op2.num)) {
  260. /* We won't convert it into_DO_FCALL to emit error at run-time */
  261. call_stack[call - 1].opline = NULL;
  262. } else {
  263. opline->opcode = ZEND_SEND_VAL;
  264. }
  265. }
  266. break;
  267. case ZEND_CHECK_FUNC_ARG:
  268. if (opline->op2_type == IS_CONST) {
  269. call_stack[call - 1].try_inline = 0;
  270. call_stack[call - 1].func_arg_num = (uint32_t)-1;
  271. break;
  272. }
  273. if (has_known_send_mode(&call_stack[call - 1], opline->op2.num)) {
  274. call_stack[call - 1].func_arg_num = opline->op2.num;
  275. MAKE_NOP(opline);
  276. }
  277. break;
  278. case ZEND_SEND_VAR_EX:
  279. case ZEND_SEND_FUNC_ARG:
  280. if (opline->op2_type == IS_CONST) {
  281. call_stack[call - 1].try_inline = 0;
  282. break;
  283. }
  284. if (has_known_send_mode(&call_stack[call - 1], opline->op2.num)) {
  285. call_stack[call - 1].func_arg_num = (uint32_t)-1;
  286. if (ARG_SHOULD_BE_SENT_BY_REF(call_stack[call - 1].func, opline->op2.num)) {
  287. opline->opcode = ZEND_SEND_REF;
  288. } else {
  289. opline->opcode = ZEND_SEND_VAR;
  290. }
  291. }
  292. break;
  293. case ZEND_SEND_VAR_NO_REF_EX:
  294. if (opline->op2_type == IS_CONST) {
  295. call_stack[call - 1].try_inline = 0;
  296. break;
  297. }
  298. if (has_known_send_mode(&call_stack[call - 1], opline->op2.num)) {
  299. if (ARG_MUST_BE_SENT_BY_REF(call_stack[call - 1].func, opline->op2.num)) {
  300. opline->opcode = ZEND_SEND_VAR_NO_REF;
  301. } else if (ARG_MAY_BE_SENT_BY_REF(call_stack[call - 1].func, opline->op2.num)) {
  302. opline->opcode = ZEND_SEND_VAL;
  303. } else {
  304. opline->opcode = ZEND_SEND_VAR;
  305. }
  306. }
  307. break;
  308. case ZEND_SEND_VAL:
  309. case ZEND_SEND_VAR:
  310. case ZEND_SEND_REF:
  311. if (opline->op2_type == IS_CONST) {
  312. call_stack[call - 1].try_inline = 0;
  313. break;
  314. }
  315. break;
  316. case ZEND_SEND_UNPACK:
  317. case ZEND_SEND_USER:
  318. case ZEND_SEND_ARRAY:
  319. call_stack[call - 1].try_inline = 0;
  320. break;
  321. default:
  322. break;
  323. }
  324. opline++;
  325. }
  326. zend_arena_release(&ctx->arena, checkpoint);
  327. }