escape_analysis.c 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524
  1. /*
  2. +----------------------------------------------------------------------+
  3. | Zend OPcache, Escape Analysis |
  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. +----------------------------------------------------------------------+
  17. */
  18. #include "Optimizer/zend_optimizer.h"
  19. #include "Optimizer/zend_optimizer_internal.h"
  20. #include "zend_bitset.h"
  21. #include "zend_cfg.h"
  22. #include "zend_ssa.h"
  23. #include "zend_inference.h"
  24. #include "zend_dump.h"
  25. /*
  26. * T. Kotzmann and H. Mossenbock. Escape analysis in the context of dynamic
  27. * compilation and deoptimization. In Proceedings of the International
  28. * Conference on Virtual Execution Environments, pages 111-120, Chicago,
  29. * June 2005
  30. */
  31. static zend_always_inline void union_find_init(int *parent, int *size, int count) /* {{{ */
  32. {
  33. int i;
  34. for (i = 0; i < count; i++) {
  35. parent[i] = i;
  36. size[i] = 1;
  37. }
  38. }
  39. /* }}} */
  40. static zend_always_inline int union_find_root(int *parent, int i) /* {{{ */
  41. {
  42. int p = parent[i];
  43. while (i != p) {
  44. p = parent[p];
  45. parent[i] = p;
  46. i = p;
  47. p = parent[i];
  48. }
  49. return i;
  50. }
  51. /* }}} */
  52. static zend_always_inline void union_find_unite(int *parent, int *size, int i, int j) /* {{{ */
  53. {
  54. int r1 = union_find_root(parent, i);
  55. int r2 = union_find_root(parent, j);
  56. if (r1 != r2) {
  57. if (size[r1] < size[r2]) {
  58. parent[r1] = r2;
  59. size[r2] += size[r1];
  60. } else {
  61. parent[r2] = r1;
  62. size[r1] += size[r2];
  63. }
  64. }
  65. }
  66. /* }}} */
  67. static int zend_build_equi_escape_sets(int *parent, zend_op_array *op_array, zend_ssa *ssa) /* {{{ */
  68. {
  69. zend_ssa_var *ssa_vars = ssa->vars;
  70. int ssa_vars_count = ssa->vars_count;
  71. zend_ssa_phi *p;
  72. int i, j;
  73. int *size;
  74. ALLOCA_FLAG(use_heap)
  75. size = do_alloca(sizeof(int) * ssa_vars_count, use_heap);
  76. if (!size) {
  77. return FAILURE;
  78. }
  79. union_find_init(parent, size, ssa_vars_count);
  80. for (i = 0; i < ssa_vars_count; i++) {
  81. if (ssa_vars[i].definition_phi) {
  82. p = ssa_vars[i].definition_phi;
  83. if (p->pi >= 0) {
  84. union_find_unite(parent, size, i, p->sources[0]);
  85. } else {
  86. for (j = 0; j < ssa->cfg.blocks[p->block].predecessors_count; j++) {
  87. union_find_unite(parent, size, i, p->sources[j]);
  88. }
  89. }
  90. } else if (ssa_vars[i].definition >= 0) {
  91. int def = ssa_vars[i].definition;
  92. zend_ssa_op *op = ssa->ops + def;
  93. zend_op *opline = op_array->opcodes + def;
  94. if (op->op1_def >= 0) {
  95. if (op->op1_use >= 0) {
  96. if (opline->opcode != ZEND_ASSIGN) {
  97. union_find_unite(parent, size, op->op1_def, op->op1_use);
  98. }
  99. }
  100. if (opline->opcode == ZEND_ASSIGN && op->op2_use >= 0) {
  101. union_find_unite(parent, size, op->op1_def, op->op2_use);
  102. }
  103. }
  104. if (op->op2_def >= 0) {
  105. if (op->op2_use >= 0) {
  106. union_find_unite(parent, size, op->op2_def, op->op2_use);
  107. }
  108. }
  109. if (op->result_def >= 0) {
  110. if (op->result_use >= 0) {
  111. if (opline->opcode != ZEND_QM_ASSIGN) {
  112. union_find_unite(parent, size, op->result_def, op->result_use);
  113. }
  114. }
  115. if (opline->opcode == ZEND_QM_ASSIGN && op->op1_use >= 0) {
  116. union_find_unite(parent, size, op->result_def, op->op1_use);
  117. }
  118. if (opline->opcode == ZEND_ASSIGN && op->op2_use >= 0) {
  119. union_find_unite(parent, size, op->result_def, op->op2_use);
  120. }
  121. if (opline->opcode == ZEND_ASSIGN && op->op1_def >= 0) {
  122. union_find_unite(parent, size, op->result_def, op->op1_def);
  123. }
  124. }
  125. }
  126. }
  127. for (i = 0; i < ssa_vars_count; i++) {
  128. parent[i] = union_find_root(parent, i);
  129. }
  130. free_alloca(size, use_heap);
  131. return SUCCESS;
  132. }
  133. /* }}} */
  134. static int is_allocation_def(zend_op_array *op_array, zend_ssa *ssa, int def, int var, const zend_script *script) /* {{{ */
  135. {
  136. zend_ssa_op *ssa_op = ssa->ops + def;
  137. zend_op *opline = op_array->opcodes + def;
  138. if (ssa_op->result_def == var) {
  139. switch (opline->opcode) {
  140. case ZEND_INIT_ARRAY:
  141. return 1;
  142. case ZEND_NEW:
  143. /* objects with destructors should escape */
  144. if (opline->op1_type == IS_CONST) {
  145. zend_class_entry *ce = zend_optimizer_get_class_entry(
  146. script, Z_STR_P(CRT_CONSTANT(opline->op1)+1));
  147. uint32_t forbidden_flags =
  148. /* These flags will always cause an exception */
  149. ZEND_ACC_IMPLICIT_ABSTRACT_CLASS | ZEND_ACC_EXPLICIT_ABSTRACT_CLASS
  150. | ZEND_ACC_INTERFACE | ZEND_ACC_TRAIT;
  151. if (ce && !ce->parent && !ce->create_object && !ce->constructor &&
  152. !ce->destructor && !ce->__get && !ce->__set &&
  153. !(ce->ce_flags & forbidden_flags) &&
  154. (ce->ce_flags & ZEND_ACC_CONSTANTS_UPDATED)) {
  155. return 1;
  156. }
  157. }
  158. break;
  159. case ZEND_QM_ASSIGN:
  160. if (opline->op1_type == IS_CONST
  161. && Z_TYPE_P(CRT_CONSTANT(opline->op1)) == IS_ARRAY) {
  162. return 1;
  163. }
  164. if (opline->op1_type == IS_CV && (OP1_INFO() & MAY_BE_ARRAY)) {
  165. return 1;
  166. }
  167. break;
  168. case ZEND_ASSIGN:
  169. if (opline->op1_type == IS_CV && (OP1_INFO() & MAY_BE_ARRAY)) {
  170. return 1;
  171. }
  172. break;
  173. }
  174. } else if (ssa_op->op1_def == var) {
  175. switch (opline->opcode) {
  176. case ZEND_ASSIGN:
  177. if (opline->op2_type == IS_CONST
  178. && Z_TYPE_P(CRT_CONSTANT(opline->op2)) == IS_ARRAY) {
  179. return 1;
  180. }
  181. if (opline->op2_type == IS_CV && (OP2_INFO() & MAY_BE_ARRAY)) {
  182. return 1;
  183. }
  184. break;
  185. case ZEND_ASSIGN_DIM:
  186. if (OP1_INFO() & (MAY_BE_UNDEF | MAY_BE_NULL | MAY_BE_FALSE)) {
  187. /* implicit object/array allocation */
  188. return 1;
  189. }
  190. break;
  191. }
  192. }
  193. return 0;
  194. }
  195. /* }}} */
  196. static int is_local_def(zend_op_array *op_array, zend_ssa *ssa, int def, int var, const zend_script *script) /* {{{ */
  197. {
  198. zend_ssa_op *op = ssa->ops + def;
  199. zend_op *opline = op_array->opcodes + def;
  200. if (op->result_def == var) {
  201. switch (opline->opcode) {
  202. case ZEND_INIT_ARRAY:
  203. case ZEND_ADD_ARRAY_ELEMENT:
  204. case ZEND_QM_ASSIGN:
  205. case ZEND_ASSIGN:
  206. return 1;
  207. case ZEND_NEW:
  208. /* objects with destructors should escape */
  209. if (opline->op1_type == IS_CONST) {
  210. zend_class_entry *ce = zend_optimizer_get_class_entry(
  211. script, Z_STR_P(CRT_CONSTANT(opline->op1)+1));
  212. if (ce && !ce->create_object && !ce->constructor &&
  213. !ce->destructor && !ce->__get && !ce->__set && !ce->parent) {
  214. return 1;
  215. }
  216. }
  217. break;
  218. }
  219. } else if (op->op1_def == var) {
  220. switch (opline->opcode) {
  221. case ZEND_ASSIGN:
  222. case ZEND_ASSIGN_DIM:
  223. case ZEND_ASSIGN_OBJ:
  224. case ZEND_ASSIGN_OBJ_REF:
  225. case ZEND_ASSIGN_DIM_OP:
  226. case ZEND_ASSIGN_OBJ_OP:
  227. case ZEND_PRE_INC_OBJ:
  228. case ZEND_PRE_DEC_OBJ:
  229. case ZEND_POST_INC_OBJ:
  230. case ZEND_POST_DEC_OBJ:
  231. return 1;
  232. }
  233. }
  234. return 0;
  235. }
  236. /* }}} */
  237. static int is_escape_use(zend_op_array *op_array, zend_ssa *ssa, int use, int var) /* {{{ */
  238. {
  239. zend_ssa_op *ssa_op = ssa->ops + use;
  240. zend_op *opline = op_array->opcodes + use;
  241. if (ssa_op->op1_use == var) {
  242. switch (opline->opcode) {
  243. case ZEND_ASSIGN:
  244. /* no_val */
  245. break;
  246. case ZEND_QM_ASSIGN:
  247. if (opline->op1_type == IS_CV) {
  248. if (OP1_INFO() & MAY_BE_OBJECT) {
  249. /* object aliasing */
  250. return 1;
  251. }
  252. }
  253. break;
  254. case ZEND_ISSET_ISEMPTY_DIM_OBJ:
  255. case ZEND_ISSET_ISEMPTY_PROP_OBJ:
  256. case ZEND_FETCH_DIM_R:
  257. case ZEND_FETCH_OBJ_R:
  258. case ZEND_FETCH_DIM_IS:
  259. case ZEND_FETCH_OBJ_IS:
  260. break;
  261. case ZEND_ASSIGN_OP:
  262. return 1;
  263. case ZEND_ASSIGN_DIM_OP:
  264. case ZEND_ASSIGN_OBJ_OP:
  265. case ZEND_ASSIGN_STATIC_PROP_OP:
  266. case ZEND_ASSIGN_DIM:
  267. case ZEND_ASSIGN_OBJ:
  268. case ZEND_ASSIGN_OBJ_REF:
  269. break;
  270. case ZEND_PRE_INC_OBJ:
  271. case ZEND_PRE_DEC_OBJ:
  272. case ZEND_POST_INC_OBJ:
  273. case ZEND_POST_DEC_OBJ:
  274. break;
  275. case ZEND_INIT_ARRAY:
  276. case ZEND_ADD_ARRAY_ELEMENT:
  277. if (opline->extended_value & ZEND_ARRAY_ELEMENT_REF) {
  278. return 1;
  279. }
  280. if (OP1_INFO() & MAY_BE_OBJECT) {
  281. /* object aliasing */
  282. return 1;
  283. }
  284. /* reference dependencies processed separately */
  285. break;
  286. case ZEND_OP_DATA:
  287. if ((opline-1)->opcode != ZEND_ASSIGN_DIM
  288. && (opline-1)->opcode != ZEND_ASSIGN_OBJ) {
  289. return 1;
  290. }
  291. if (OP1_INFO() & MAY_BE_OBJECT) {
  292. /* object aliasing */
  293. return 1;
  294. }
  295. opline--;
  296. ssa_op--;
  297. if (opline->op1_type != IS_CV
  298. || (OP1_INFO() & MAY_BE_REF)
  299. || (ssa_op->op1_def >= 0 && ssa->vars[ssa_op->op1_def].alias)) {
  300. /* assignment into escaping structure */
  301. return 1;
  302. }
  303. /* reference dependencies processed separately */
  304. break;
  305. default:
  306. return 1;
  307. }
  308. }
  309. if (ssa_op->op2_use == var) {
  310. switch (opline->opcode) {
  311. case ZEND_ASSIGN:
  312. if (opline->op1_type != IS_CV
  313. || (OP1_INFO() & MAY_BE_REF)
  314. || (ssa_op->op1_def >= 0 && ssa->vars[ssa_op->op1_def].alias)) {
  315. /* assignment into escaping variable */
  316. return 1;
  317. }
  318. if (opline->op2_type == IS_CV || opline->result_type != IS_UNUSED) {
  319. if (OP2_INFO() & MAY_BE_OBJECT) {
  320. /* object aliasing */
  321. return 1;
  322. }
  323. }
  324. break;
  325. default:
  326. return 1;
  327. }
  328. }
  329. if (ssa_op->result_use == var) {
  330. switch (opline->opcode) {
  331. case ZEND_ASSIGN:
  332. case ZEND_QM_ASSIGN:
  333. case ZEND_INIT_ARRAY:
  334. case ZEND_ADD_ARRAY_ELEMENT:
  335. break;
  336. default:
  337. return 1;
  338. }
  339. }
  340. return 0;
  341. }
  342. /* }}} */
  343. int zend_ssa_escape_analysis(const zend_script *script, zend_op_array *op_array, zend_ssa *ssa) /* {{{ */
  344. {
  345. zend_ssa_var *ssa_vars = ssa->vars;
  346. int ssa_vars_count = ssa->vars_count;
  347. int i, root, use;
  348. int *ees;
  349. bool has_allocations;
  350. int num_non_escaped;
  351. ALLOCA_FLAG(use_heap)
  352. if (!ssa_vars) {
  353. return SUCCESS;
  354. }
  355. has_allocations = 0;
  356. for (i = op_array->last_var; i < ssa_vars_count; i++) {
  357. if (ssa_vars[i].definition >= 0
  358. && (ssa->var_info[i].type & (MAY_BE_ARRAY|MAY_BE_OBJECT))
  359. && is_allocation_def(op_array, ssa, ssa_vars[i].definition, i, script)) {
  360. has_allocations = 1;
  361. break;
  362. }
  363. }
  364. if (!has_allocations) {
  365. return SUCCESS;
  366. }
  367. /* 1. Build EES (Equi-Escape Sets) */
  368. ees = do_alloca(sizeof(int) * ssa_vars_count, use_heap);
  369. if (!ees) {
  370. return FAILURE;
  371. }
  372. if (zend_build_equi_escape_sets(ees, op_array, ssa) != SUCCESS) {
  373. return FAILURE;
  374. }
  375. /* 2. Identify Allocations */
  376. num_non_escaped = 0;
  377. for (i = op_array->last_var; i < ssa_vars_count; i++) {
  378. root = ees[i];
  379. if (ssa_vars[root].escape_state > ESCAPE_STATE_NO_ESCAPE) {
  380. /* already escape. skip */
  381. } else if (ssa_vars[i].alias && (ssa->var_info[i].type & MAY_BE_REF)) {
  382. if (ssa_vars[root].escape_state == ESCAPE_STATE_NO_ESCAPE) {
  383. num_non_escaped--;
  384. }
  385. ssa_vars[root].escape_state = ESCAPE_STATE_GLOBAL_ESCAPE;
  386. } else if (ssa_vars[i].definition >= 0
  387. && (ssa->var_info[i].type & (MAY_BE_ARRAY|MAY_BE_OBJECT))) {
  388. if (!is_local_def(op_array, ssa, ssa_vars[i].definition, i, script)) {
  389. if (ssa_vars[root].escape_state == ESCAPE_STATE_NO_ESCAPE) {
  390. num_non_escaped--;
  391. }
  392. ssa_vars[root].escape_state = ESCAPE_STATE_GLOBAL_ESCAPE;
  393. } else if (ssa_vars[root].escape_state == ESCAPE_STATE_UNKNOWN
  394. && is_allocation_def(op_array, ssa, ssa_vars[i].definition, i, script)) {
  395. ssa_vars[root].escape_state = ESCAPE_STATE_NO_ESCAPE;
  396. num_non_escaped++;
  397. }
  398. }
  399. }
  400. /* 3. Mark escaped EES */
  401. if (num_non_escaped) {
  402. for (i = 0; i < ssa_vars_count; i++) {
  403. if (ssa_vars[i].use_chain >= 0) {
  404. root = ees[i];
  405. if (ssa_vars[root].escape_state == ESCAPE_STATE_NO_ESCAPE) {
  406. FOREACH_USE(ssa_vars + i, use) {
  407. if (is_escape_use(op_array, ssa, use, i)) {
  408. ssa_vars[root].escape_state = ESCAPE_STATE_GLOBAL_ESCAPE;
  409. num_non_escaped--;
  410. if (num_non_escaped == 0) {
  411. i = ssa_vars_count;
  412. }
  413. break;
  414. }
  415. } FOREACH_USE_END();
  416. }
  417. }
  418. }
  419. }
  420. /* 4. Process referential dependencies */
  421. if (num_non_escaped) {
  422. bool changed;
  423. do {
  424. changed = 0;
  425. for (i = 0; i < ssa_vars_count; i++) {
  426. if (ssa_vars[i].use_chain >= 0) {
  427. root = ees[i];
  428. if (ssa_vars[root].escape_state == ESCAPE_STATE_NO_ESCAPE) {
  429. FOREACH_USE(ssa_vars + i, use) {
  430. zend_ssa_op *op = ssa->ops + use;
  431. zend_op *opline = op_array->opcodes + use;
  432. int enclosing_root;
  433. if (opline->opcode == ZEND_OP_DATA &&
  434. ((opline-1)->opcode == ZEND_ASSIGN_DIM ||
  435. (opline-1)->opcode == ZEND_ASSIGN_OBJ ||
  436. (opline-1)->opcode == ZEND_ASSIGN_OBJ_REF) &&
  437. op->op1_use == i &&
  438. (op-1)->op1_use >= 0) {
  439. enclosing_root = ees[(op-1)->op1_use];
  440. } else if ((opline->opcode == ZEND_INIT_ARRAY ||
  441. opline->opcode == ZEND_ADD_ARRAY_ELEMENT) &&
  442. op->op1_use == i &&
  443. op->result_def >= 0) {
  444. enclosing_root = ees[op->result_def];
  445. } else {
  446. continue;
  447. }
  448. if (ssa_vars[enclosing_root].escape_state == ESCAPE_STATE_UNKNOWN ||
  449. ssa_vars[enclosing_root].escape_state > ssa_vars[root].escape_state) {
  450. if (ssa_vars[enclosing_root].escape_state == ESCAPE_STATE_UNKNOWN) {
  451. ssa_vars[root].escape_state = ESCAPE_STATE_GLOBAL_ESCAPE;
  452. } else {
  453. ssa_vars[root].escape_state = ssa_vars[enclosing_root].escape_state;
  454. }
  455. if (ssa_vars[root].escape_state == ESCAPE_STATE_GLOBAL_ESCAPE) {
  456. num_non_escaped--;
  457. if (num_non_escaped == 0) {
  458. changed = 0;
  459. } else {
  460. changed = 1;
  461. }
  462. break;
  463. } else {
  464. changed = 1;
  465. }
  466. }
  467. } FOREACH_USE_END();
  468. }
  469. }
  470. }
  471. } while (changed);
  472. }
  473. /* 5. Propagate values of escape sets to variables */
  474. for (i = 0; i < ssa_vars_count; i++) {
  475. root = ees[i];
  476. if (i != root) {
  477. ssa_vars[i].escape_state = ssa_vars[root].escape_state;
  478. }
  479. }
  480. free_alloca(ees, use_heap);
  481. return SUCCESS;
  482. }
  483. /* }}} */