XCache is a fast, stable PHP opcode cacher that has been proven and is now running on production servers under high load. https://xcache.lighttpd.net/
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

617 lines
14 KiB

  1. #if 0
  2. # define XCACHE_DEBUG
  3. #endif
  4. #include "utils.h"
  5. #include "optimizer.h"
  6. /* the "vector" stack */
  7. #include "stack.h"
  8. #include "xcache_globals.h"
  9. #ifdef XCACHE_DEBUG
  10. # include "processor.h"
  11. # include "const_string.h"
  12. # include "ext/standard/php_var.h"
  13. #endif
  14. #ifdef IS_CV
  15. # define XCACHE_IS_CV IS_CV
  16. #else
  17. # define XCACHE_IS_CV 16
  18. #endif
  19. #ifdef ZEND_ENGINE_2_4
  20. # undef Z_OP_CONSTANT
  21. /* Z_OP_CONSTANT is used before pass_two is applied */
  22. # define Z_OP_CONSTANT(op) op_array->literals[op.constant].constant
  23. #endif
  24. typedef int bbid_t;
  25. enum {
  26. BBID_INVALID = -1
  27. };
  28. /* {{{ basic block */
  29. typedef struct _bb_t {
  30. bbid_t id;
  31. zend_bool used;
  32. zend_bool alloc;
  33. zend_op *opcodes;
  34. int count;
  35. int size;
  36. bbid_t fall;
  37. #ifdef ZEND_ENGINE_2
  38. bbid_t catch;
  39. #endif
  40. int opnum; /* opnum after joining basic block */
  41. } bb_t;
  42. /* }}} */
  43. /* basic blocks */
  44. typedef xc_stack_t bbs_t;
  45. /* op array helper functions */
  46. static int op_array_convert_switch(zend_op_array *op_array) /* {{{ */
  47. {
  48. int i;
  49. if (op_array->brk_cont_array == NULL) {
  50. return SUCCESS;
  51. }
  52. for (i = 0; i < op_array->last; i ++) {
  53. zend_op *opline = &op_array->opcodes[i];
  54. zend_brk_cont_element *jmp_to;
  55. zend_bool can_convert = 1;
  56. int array_offset, nest_levels, original_nest_levels;
  57. switch (opline->opcode) {
  58. case ZEND_BRK:
  59. case ZEND_CONT:
  60. break;
  61. #ifdef ZEND_GOTO
  62. case ZEND_GOTO:
  63. #endif
  64. continue;
  65. default:
  66. continue;
  67. }
  68. if (Z_OP_TYPE(opline->op2) != IS_CONST
  69. || Z_OP_CONSTANT(opline->op2).type != IS_LONG) {
  70. return FAILURE;
  71. }
  72. nest_levels = Z_OP_CONSTANT(opline->op2).value.lval;
  73. original_nest_levels = nest_levels;
  74. array_offset = Z_OP(opline->op1).opline_num;
  75. do {
  76. if (array_offset == -1) {
  77. /* this is a runtime error in ZE
  78. zend_error(E_ERROR, "Cannot break/continue %d level%s", original_nest_levels, (original_nest_levels == 1) ? "" : "s");
  79. */
  80. return FAILURE;
  81. }
  82. jmp_to = &op_array->brk_cont_array[array_offset];
  83. if (nest_levels > 1) {
  84. zend_op *brk_opline = &op_array->opcodes[jmp_to->brk];
  85. switch (brk_opline->opcode) {
  86. case ZEND_SWITCH_FREE:
  87. case ZEND_FREE:
  88. #ifdef EXT_TYPE_FREE_ON_RETURN
  89. if (!(brk_opline->extended_value & EXT_TYPE_FREE_ON_RETURN))
  90. #endif
  91. {
  92. can_convert = 0;
  93. }
  94. break;
  95. }
  96. }
  97. array_offset = jmp_to->parent;
  98. } while (--nest_levels > 0);
  99. if (can_convert) {
  100. /* rewrite to jmp */
  101. switch (opline->opcode) {
  102. case ZEND_BRK:
  103. Z_OP(opline->op1).opline_num = jmp_to->brk;
  104. break;
  105. case ZEND_CONT:
  106. Z_OP(opline->op1).opline_num = jmp_to->cont;
  107. break;
  108. }
  109. Z_OP_TYPE(opline->op2) = IS_UNUSED;
  110. opline->opcode = ZEND_JMP;
  111. }
  112. }
  113. return SUCCESS;
  114. }
  115. /* }}} */
  116. /* {{{ op_flowinfo helper func */
  117. enum {
  118. XC_OPNUM_INVALID = -1,
  119. };
  120. typedef struct {
  121. int jmpout_op1;
  122. int jmpout_op2;
  123. int jmpout_ext;
  124. zend_bool fall;
  125. } op_flowinfo_t;
  126. static void op_flowinfo_init(op_flowinfo_t *fi)
  127. {
  128. fi->jmpout_op1 = fi->jmpout_op2 = fi->jmpout_ext = XC_OPNUM_INVALID;
  129. fi->fall = 0;
  130. }
  131. /* }}} */
  132. static int op_get_flowinfo(op_flowinfo_t *fi, zend_op *opline) /* {{{ */
  133. {
  134. op_flowinfo_init(fi);
  135. /* break=will fall */
  136. switch (opline->opcode) {
  137. #ifdef ZEND_HANDLE_EXCEPTION
  138. case ZEND_HANDLE_EXCEPTION:
  139. #endif
  140. case ZEND_RETURN:
  141. case ZEND_EXIT:
  142. return SUCCESS; /* no fall */
  143. case ZEND_JMP:
  144. fi->jmpout_op1 = Z_OP(opline->op1).opline_num;
  145. return SUCCESS; /* no fall */
  146. case ZEND_JMPZNZ:
  147. fi->jmpout_op2 = Z_OP(opline->op2).opline_num;
  148. fi->jmpout_ext = (int) opline->extended_value;
  149. return SUCCESS; /* no fall */
  150. case ZEND_JMPZ:
  151. case ZEND_JMPNZ:
  152. case ZEND_JMPZ_EX:
  153. case ZEND_JMPNZ_EX:
  154. #ifdef ZEND_JMP_SET
  155. case ZEND_JMP_SET:
  156. #endif
  157. #ifdef ZEND_JMP_NO_CTOR
  158. case ZEND_JMP_NO_CTOR:
  159. #endif
  160. #ifdef ZEND_NEW
  161. case ZEND_NEW:
  162. #endif
  163. #ifdef ZEND_FE_RESET
  164. case ZEND_FE_RESET:
  165. #endif
  166. case ZEND_FE_FETCH:
  167. fi->jmpout_op2 = Z_OP(opline->op2).opline_num;
  168. break;
  169. #ifdef ZEND_CATCH
  170. case ZEND_CATCH:
  171. fi->jmpout_ext = (int) opline->extended_value;
  172. break;
  173. #endif
  174. default:
  175. return FAILURE;
  176. }
  177. fi->fall = 1;
  178. return SUCCESS;
  179. }
  180. /* }}} */
  181. #ifdef XCACHE_DEBUG
  182. static void op_snprint(char *buf, int size, zend_uchar op_type, znode_op *op) /* {{{ */
  183. {
  184. switch (op_type) {
  185. case IS_CONST:
  186. {
  187. zval result;
  188. zval *zv = &Z_OP_CONSTANT(*op);
  189. TSRMLS_FETCH();
  190. /* TODO: update for PHP 6 */
  191. php_start_ob_buffer(NULL, 0, 1 TSRMLS_CC);
  192. php_var_export(&zv, 1 TSRMLS_CC);
  193. php_ob_get_buffer(&result TSRMLS_CC);
  194. php_end_ob_buffer(0, 0 TSRMLS_CC);
  195. snprintf(buf, size, Z_STRVAL(result));
  196. zval_dtor(&result);
  197. }
  198. break;
  199. case IS_TMP_VAR:
  200. snprintf(buf, size, "t@%d", Z_OP(*op).var);
  201. break;
  202. case XCACHE_IS_CV:
  203. case IS_VAR:
  204. snprintf(buf, size, "v@%d", Z_OP(*op).var);
  205. break;
  206. case IS_UNUSED:
  207. if (Z_OP(*op).opline_num) {
  208. snprintf(buf, size, "u#%d", Z_OP(op).opline_num);
  209. }
  210. else {
  211. snprintf(buf, size, "-");
  212. }
  213. break;
  214. default:
  215. snprintf(buf, size, "%d %d", op->op_type, Z_OP(op).var);
  216. }
  217. }
  218. /* }}} */
  219. static void op_print(int line, zend_op *first, zend_op *end) /* {{{ */
  220. {
  221. zend_op *opline;
  222. for (opline = first; opline < end; opline ++) {
  223. char buf_r[20];
  224. char buf_1[20];
  225. char buf_2[20];
  226. op_snprint(buf_r, sizeof(buf_r), Z_OP_TYPE(opline->result), &opline->result);
  227. op_snprint(buf_1, sizeof(buf_1), Z_OP_TYPE(opline->op1), &opline->op1);
  228. op_snprint(buf_2, sizeof(buf_2), Z_OP_TYPE(opline->op2), &opline->op2);
  229. fprintf(stderr,
  230. "%3d %3d"
  231. " %-25s%-5s%-20s%-20s%5lu\r\n"
  232. , opline->lineno, opline - first + line
  233. , xc_get_opcode(opline->opcode), buf_r, buf_1, buf_2, opline->extended_value);
  234. }
  235. }
  236. /* }}} */
  237. #endif
  238. /*
  239. * basic block functions
  240. */
  241. static bb_t *bb_new_ex(zend_op *opcodes, int count) /* {{{ */
  242. {
  243. bb_t *bb = (bb_t *) ecalloc(sizeof(bb_t), 1);
  244. bb->fall = BBID_INVALID;
  245. #ifdef ZEND_ENGINE_2
  246. bb->catch = BBID_INVALID;
  247. #endif
  248. if (opcodes) {
  249. bb->alloc = 0;
  250. bb->size = bb->count = count;
  251. bb->opcodes = opcodes;
  252. }
  253. else {
  254. bb->alloc = 1;
  255. bb->size = bb->count = 8;
  256. bb->opcodes = ecalloc(sizeof(zend_op), bb->size);
  257. }
  258. return bb;
  259. }
  260. /* }}} */
  261. #define bb_new() bb_new_ex(NULL, 0)
  262. static void bb_destroy(bb_t *bb) /* {{{ */
  263. {
  264. if (bb->alloc) {
  265. efree(bb->opcodes);
  266. }
  267. efree(bb);
  268. }
  269. /* }}} */
  270. #ifdef XCACHE_DEBUG
  271. static void bb_print(bb_t *bb, zend_op *opcodes) /* {{{ */
  272. {
  273. int line = bb->opcodes - opcodes;
  274. op_flowinfo_t fi;
  275. zend_op *last = bb->opcodes + bb->count - 1;
  276. bbid_t catchbbid;
  277. #ifdef ZEND_ENGINE_2
  278. catchbbid = BBID_INVALID;
  279. #else
  280. catchbbid = bb->catch;
  281. #endif
  282. op_get_flowinfo(&fi, last);
  283. fprintf(stderr,
  284. "\r\n==== #%-3d cnt:%-3d lno:%-3d"
  285. " %c%c"
  286. " op1:%-3d op2:%-3d ext:%-3d fal:%-3d cat:%-3d %s ====\r\n"
  287. , bb->id, bb->count, bb->alloc ? -1 : line
  288. , bb->used ? 'U' : ' ', bb->alloc ? 'A' : ' '
  289. , fi.jmpout_op1, fi.jmpout_op2, fi.jmpout_ext, bb->fall, catchbbid, xc_get_opcode(last->opcode)
  290. );
  291. op_print(line, bb->opcodes, last + 1);
  292. }
  293. /* }}} */
  294. #endif
  295. static bb_t *bbs_get(bbs_t *bbs, int n) /* {{{ */
  296. {
  297. return (bb_t *) xc_stack_get(bbs, n);
  298. }
  299. /* }}} */
  300. static int bbs_count(bbs_t *bbs) /* {{{ */
  301. {
  302. return xc_stack_count(bbs);
  303. }
  304. /* }}} */
  305. static void bbs_destroy(bbs_t *bbs) /* {{{ */
  306. {
  307. bb_t *bb;
  308. while (bbs_count(bbs)) {
  309. bb = (bb_t *) xc_stack_pop(bbs);
  310. bb_destroy(bb);
  311. }
  312. xc_stack_destroy(bbs);
  313. }
  314. /* }}} */
  315. #ifdef XCACHE_DEBUG
  316. static void bbs_print(bbs_t *bbs, zend_op *opcodes) /* {{{ */
  317. {
  318. int i;
  319. for (i = 0; i < xc_stack_count(bbs); i ++) {
  320. bb_print(bbs_get(bbs, i), opcodes);
  321. }
  322. }
  323. /* }}} */
  324. #endif
  325. #define bbs_init(bbs) xc_stack_init_ex(bbs, 16)
  326. static bb_t *bbs_add_bb(bbs_t *bbs, bb_t *bb) /* {{{ */
  327. {
  328. bb->id = (bbid_t) xc_stack_count(bbs);
  329. xc_stack_push(bbs, (void *) bb);
  330. return bb;
  331. }
  332. /* }}} */
  333. static bb_t *bbs_new_bb_ex(bbs_t *bbs, zend_op *opcodes, int count) /* {{{ */
  334. {
  335. return bbs_add_bb(bbs, bb_new_ex(opcodes, count));
  336. }
  337. /* }}} */
  338. static int bbs_build_from(bbs_t *bbs, zend_op_array *op_array, int count) /* {{{ */
  339. {
  340. int i, start;
  341. bb_t *pbb;
  342. bbid_t id;
  343. op_flowinfo_t fi;
  344. zend_op *opline;
  345. ALLOCA_FLAG(use_heap_bbids)
  346. ALLOCA_FLAG(use_heap_catchbbids)
  347. ALLOCA_FLAG(use_heap_markbbhead)
  348. bbid_t *bbids = my_do_alloca(count * sizeof(bbid_t), use_heap_bbids);
  349. #ifdef ZEND_ENGINE_2
  350. bbid_t *catchbbids = my_do_alloca(count * sizeof(bbid_t), use_heap_catchbbids);
  351. #endif
  352. zend_bool *markbbhead = my_do_alloca(count * sizeof(zend_bool), use_heap_markbbhead);
  353. /* {{{ mark jmpin/jumpout */
  354. memset(markbbhead, 0, count * sizeof(zend_bool));
  355. markbbhead[0] = 1;
  356. for (i = 0; i < count; i ++) {
  357. if (op_get_flowinfo(&fi, &op_array->opcodes[i]) == SUCCESS) {
  358. if (fi.jmpout_op1 != XC_OPNUM_INVALID) {
  359. markbbhead[fi.jmpout_op1] = 1;
  360. }
  361. if (fi.jmpout_op2 != XC_OPNUM_INVALID) {
  362. markbbhead[fi.jmpout_op2] = 1;
  363. }
  364. if (fi.jmpout_ext != XC_OPNUM_INVALID) {
  365. markbbhead[fi.jmpout_ext] = 1;
  366. }
  367. if (i + 1 < count) {
  368. markbbhead[i + 1] = 1;
  369. }
  370. }
  371. }
  372. #ifdef ZEND_ENGINE_2
  373. /* mark try start */
  374. for (i = 0; i < op_array->last_try_catch; i ++) {
  375. markbbhead[op_array->try_catch_array[i].try_op] = 1;
  376. }
  377. #endif
  378. /* }}} */
  379. /* {{{ fill op lines with newly allocated id */
  380. for (i = 0; i < count; i ++) {
  381. bbids[i] = BBID_INVALID;
  382. }
  383. id = -1;
  384. for (i = 0; i < count; i ++) {
  385. if (markbbhead[i]) {
  386. id ++;
  387. }
  388. bbids[i] = id;
  389. TRACE("bbids[%d] = %d", i, id);
  390. }
  391. /* }}} */
  392. #ifdef ZEND_ENGINE_2
  393. /* {{{ fill op lines with catch id */
  394. for (i = 0; i < count; i ++) {
  395. catchbbids[i] = BBID_INVALID;
  396. }
  397. for (i = 0; i < op_array->last_try_catch; i ++) {
  398. int j;
  399. zend_try_catch_element *e = &op_array->try_catch_array[i];
  400. for (j = e->try_op; j < e->catch_op; j ++) {
  401. catchbbids[j] = bbids[e->catch_op];
  402. }
  403. }
  404. #ifdef XCACHE_DEBUG
  405. for (i = 0; i < count; i ++) {
  406. TRACE("catchbbids[%d] = %d", i, catchbbids[i]);
  407. }
  408. #endif
  409. /* }}} */
  410. #endif
  411. /* {{{ create basic blocks */
  412. start = 0;
  413. id = 0;
  414. /* loop over to deal with the last block */
  415. for (i = 1; i <= count; i ++) {
  416. if (i < count && id == bbids[i]) {
  417. continue;
  418. }
  419. opline = op_array->opcodes + start;
  420. pbb = bbs_new_bb_ex(bbs, opline, i - start);
  421. #ifdef ZEND_ENGINE_2
  422. pbb->catch = catchbbids[start];
  423. #endif
  424. /* last */
  425. opline = pbb->opcodes + pbb->count - 1;
  426. if (op_get_flowinfo(&fi, opline) == SUCCESS) {
  427. if (fi.jmpout_op1 != XC_OPNUM_INVALID) {
  428. Z_OP(opline->op1).opline_num = bbids[fi.jmpout_op1];
  429. assert(Z_OP(opline->op1).opline_num != BBID_INVALID);
  430. }
  431. if (fi.jmpout_op2 != XC_OPNUM_INVALID) {
  432. Z_OP(opline->op2).opline_num = bbids[fi.jmpout_op2];
  433. assert(Z_OP(opline->op2).opline_num != BBID_INVALID);
  434. }
  435. if (fi.jmpout_ext != XC_OPNUM_INVALID) {
  436. opline->extended_value = bbids[fi.jmpout_ext];
  437. assert(opline->extended_value != BBID_INVALID);
  438. }
  439. if (fi.fall && i + 1 < count) {
  440. pbb->fall = bbids[i + 1];
  441. TRACE("fall %d", pbb->fall);
  442. assert(pbb->fall != BBID_INVALID);
  443. }
  444. }
  445. if (i >= count) {
  446. break;
  447. }
  448. start = i;
  449. id = bbids[i];
  450. }
  451. /* }}} */
  452. my_free_alloca(markbbhead, use_heap_markbbhead);
  453. #ifdef ZEND_ENGINE_2
  454. my_free_alloca(catchbbids, use_heap_catchbbids);
  455. #endif
  456. my_free_alloca(bbids, use_heap_bbids);
  457. return SUCCESS;
  458. }
  459. /* }}} */
  460. static void bbs_restore_opnum(bbs_t *bbs, zend_op_array *op_array) /* {{{ */
  461. {
  462. int i;
  463. #ifdef ZEND_ENGINE_2
  464. bbid_t lasttrybbid;
  465. bbid_t lastcatchbbid;
  466. #endif
  467. for (i = 0; i < bbs_count(bbs); i ++) {
  468. op_flowinfo_t fi;
  469. bb_t *bb = bbs_get(bbs, i);
  470. zend_op *last = bb->opcodes + bb->count - 1;
  471. if (op_get_flowinfo(&fi, last) == SUCCESS) {
  472. if (fi.jmpout_op1 != XC_OPNUM_INVALID) {
  473. Z_OP(last->op1).opline_num = bbs_get(bbs, fi.jmpout_op1)->opnum;
  474. assert(Z_OP(last->op1).opline_num != BBID_INVALID);
  475. }
  476. if (fi.jmpout_op2 != XC_OPNUM_INVALID) {
  477. Z_OP(last->op2).opline_num = bbs_get(bbs, fi.jmpout_op2)->opnum;
  478. assert(Z_OP(last->op2).opline_num != BBID_INVALID);
  479. }
  480. if (fi.jmpout_ext != XC_OPNUM_INVALID) {
  481. last->extended_value = bbs_get(bbs, fi.jmpout_ext)->opnum;
  482. assert(last->extended_value != BBID_INVALID);
  483. }
  484. }
  485. }
  486. #ifdef ZEND_ENGINE_2
  487. lasttrybbid = BBID_INVALID;
  488. lastcatchbbid = BBID_INVALID;
  489. op_array->last_try_catch = 0;
  490. for (i = 0; i < bbs_count(bbs); i ++) {
  491. bb_t *bb = bbs_get(bbs, i);
  492. if (lastcatchbbid != bb->catch) {
  493. if (lasttrybbid != BBID_INVALID && lastcatchbbid != BBID_INVALID) {
  494. int try_catch_offset = op_array->last_try_catch ++;
  495. op_array->try_catch_array = erealloc(op_array->try_catch_array, sizeof(zend_try_catch_element) * op_array->last_try_catch);
  496. op_array->try_catch_array[try_catch_offset].try_op = bbs_get(bbs, lasttrybbid)->opnum;
  497. op_array->try_catch_array[try_catch_offset].catch_op = bbs_get(bbs, lastcatchbbid)->opnum;
  498. }
  499. lasttrybbid = i;
  500. lastcatchbbid = bb->catch;
  501. }
  502. }
  503. /* it is impossible to have last bb catched */
  504. #endif
  505. }
  506. /* }}} */
  507. /*
  508. * optimize
  509. */
  510. static int xc_optimize_op_array(zend_op_array *op_array TSRMLS_DC) /* {{{ */
  511. {
  512. bbs_t bbs;
  513. if (op_array->type != ZEND_USER_FUNCTION) {
  514. return 0;
  515. }
  516. #ifdef XCACHE_DEBUG
  517. # if 0
  518. TRACE("optimize file: %s", op_array->filename);
  519. xc_dprint_zend_op_array(op_array, 0 TSRMLS_CC);
  520. # endif
  521. op_print(0, op_array->opcodes, op_array->opcodes + op_array->last);
  522. #endif
  523. if (op_array_convert_switch(op_array) == SUCCESS) {
  524. bbs_init(&bbs);
  525. if (bbs_build_from(&bbs, op_array, op_array->last) == SUCCESS) {
  526. int i;
  527. #ifdef XCACHE_DEBUG
  528. bbs_print(&bbs, op_array->opcodes);
  529. #endif
  530. /* TODO: calc opnum after basic block move */
  531. for (i = 0; i < bbs_count(&bbs); i ++) {
  532. bb_t *bb = bbs_get(&bbs, i);
  533. bb->opnum = bb->opcodes - op_array->opcodes;
  534. }
  535. bbs_restore_opnum(&bbs, op_array);
  536. }
  537. bbs_destroy(&bbs);
  538. }
  539. #ifdef XCACHE_DEBUG
  540. # if 0
  541. TRACE("%s", "after compiles");
  542. xc_dprint_zend_op_array(op_array, 0 TSRMLS_CC);
  543. # endif
  544. op_print(0, op_array->opcodes, op_array->opcodes + op_array->last);
  545. #endif
  546. return 0;
  547. }
  548. /* }}} */
  549. void xc_optimizer_op_array_handler(zend_op_array *op_array) /* {{{ */
  550. {
  551. TSRMLS_FETCH();
  552. if (XG(optimizer)) {
  553. xc_optimize_op_array(op_array TSRMLS_CC);
  554. }
  555. }
  556. /* }}} */