1 /*
2 +----------------------------------------------------------------------+
3 | Zend OPcache |
4 +----------------------------------------------------------------------+
5 | Copyright (c) 1998-2016 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 | http://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@zend.com> |
16 | Xinchen Hui <laruence@php.net> |
17 +----------------------------------------------------------------------+
18 */
19
20 /* pass 11
21 * - compact literals table
22 */
23
24 #include "php.h"
25 #include "Optimizer/zend_optimizer.h"
26 #include "Optimizer/zend_optimizer_internal.h"
27 #include "zend_API.h"
28 #include "zend_constants.h"
29 #include "zend_execute.h"
30 #include "zend_vm.h"
31
32 #define DEBUG_COMPACT_LITERALS 0
33
34 #define LITERAL_VALUE 0x0100
35 #define LITERAL_FUNC 0x0200
36 #define LITERAL_CLASS 0x0300
37 #define LITERAL_CONST 0x0400
38 #define LITERAL_CLASS_CONST 0x0500
39 #define LITERAL_STATIC_METHOD 0x0600
40 #define LITERAL_STATIC_PROPERTY 0x0700
41 #define LITERAL_METHOD 0x0800
42 #define LITERAL_PROPERTY 0x0900
43 #define LITERAL_GLOBAL 0x0A00
44
45 #define LITERAL_EX_CLASS 0x4000
46 #define LITERAL_EX_OBJ 0x2000
47 #define LITERAL_MAY_MERGE 0x1000
48 #define LITERAL_KIND_MASK 0x0f00
49 #define LITERAL_NUM_RELATED_MASK 0x000f
50 #define LITERAL_NUM_SLOTS_MASK 0x00f0
51 #define LITERAL_NUM_SLOTS_SHIFT 4
52
53 #define LITERAL_NUM_RELATED(info) (info & LITERAL_NUM_RELATED_MASK)
54 #define LITERAL_NUM_SLOTS(info) ((info & LITERAL_NUM_SLOTS_MASK) >> LITERAL_NUM_SLOTS_SHIFT)
55
56 typedef struct _literal_info {
57 uint32_t flags; /* bitmask (see defines above) */
58 union {
59 int num; /* variable number or class name literal number */
60 } u;
61 } literal_info;
62
63 #define LITERAL_FLAGS(kind, slots, related) \
64 ((kind) | ((slots) << LITERAL_NUM_SLOTS_SHIFT) | (related))
65
66 #define LITERAL_INFO(n, kind, merge, slots, related) do { \
67 info[n].flags = (((merge) ? LITERAL_MAY_MERGE : 0) | LITERAL_FLAGS(kind, slots, related)); \
68 } while (0)
69
70 #define LITERAL_INFO_CLASS(n, kind, merge, slots, related, _num) do { \
71 info[n].flags = (LITERAL_EX_CLASS | ((merge) ? LITERAL_MAY_MERGE : 0) | LITERAL_FLAGS(kind, slots, related)); \
72 info[n].u.num = (_num); \
73 } while (0)
74
75 #define LITERAL_INFO_OBJ(n, kind, merge, slots, related, _num) do { \
76 info[n].flags = (LITERAL_EX_OBJ | ((merge) ? LITERAL_MAY_MERGE : 0) | LITERAL_FLAGS(kind, slots, related)); \
77 info[n].u.num = (_num); \
78 } while (0)
79
80 static void optimizer_literal_obj_info(literal_info *info,
81 zend_uchar op_type,
82 znode_op op,
83 int constant,
84 uint32_t kind,
85 uint32_t slots,
86 uint32_t related,
87 zend_op_array *op_array)
88 {
89 /* For now we merge only $this object properties and methods.
90 * In general it's also possible to do it for any CV variable as well,
91 * but it would require complex dataflow and/or type analysis.
92 */
93 if (Z_TYPE(op_array->literals[constant]) == IS_STRING &&
94 op_type == IS_UNUSED) {
95 LITERAL_INFO_OBJ(constant, kind, 1, slots, related, op_array->this_var);
96 } else {
97 LITERAL_INFO(constant, kind, 0, slots, related);
98 }
99 }
100
101 static void optimizer_literal_class_info(literal_info *info,
102 zend_uchar op_type,
103 znode_op op,
104 int constant,
105 uint32_t kind,
106 uint32_t slots,
107 uint32_t related,
108 zend_op_array *op_array)
109 {
110 if (op_type == IS_CONST) {
111 LITERAL_INFO_CLASS(constant, kind, 1, slots, related, op.constant);
112 } else {
113 LITERAL_INFO(constant, kind, 0, slots, related);
114 }
115 }
116
117 void zend_optimizer_compact_literals(zend_op_array *op_array, zend_optimizer_ctx *ctx)
118 {
119 zend_op *opline, *end;
120 int i, j, n, *map, cache_size;
121 zval zv, *pos;
122 literal_info *info;
123 int l_null = -1;
124 int l_false = -1;
125 int l_true = -1;
126 HashTable hash;
127 zend_string *key = NULL;
128 void *checkpoint = zend_arena_checkpoint(ctx->arena);
129
130 if (op_array->last_literal) {
131 cache_size = 0;
132 info = (literal_info*)zend_arena_calloc(&ctx->arena, op_array->last_literal, sizeof(literal_info));
133
134 /* Mark literals of specific types */
135 opline = op_array->opcodes;
136 end = opline + op_array->last;
137 while (opline < end) {
138 switch (opline->opcode) {
139 case ZEND_INIT_FCALL:
140 LITERAL_INFO(opline->op2.constant, LITERAL_FUNC, 1, 1, 1);
141 break;
142 case ZEND_INIT_FCALL_BY_NAME:
143 LITERAL_INFO(opline->op2.constant, LITERAL_FUNC, 1, 1, 2);
144 break;
145 case ZEND_INIT_NS_FCALL_BY_NAME:
146 LITERAL_INFO(opline->op2.constant, LITERAL_FUNC, 1, 1, 3);
147 break;
148 case ZEND_INIT_METHOD_CALL:
149 if (ZEND_OP2_TYPE(opline) == IS_CONST) {
150 optimizer_literal_obj_info(
151 info,
152 opline->op1_type,
153 opline->op1,
154 opline->op2.constant,
155 LITERAL_METHOD, 2, 2,
156 op_array);
157 }
158 break;
159 case ZEND_INIT_STATIC_METHOD_CALL:
160 if (ZEND_OP1_TYPE(opline) == IS_CONST) {
161 LITERAL_INFO(opline->op1.constant, LITERAL_CLASS, 1, 1, 2);
162 }
163 if (ZEND_OP2_TYPE(opline) == IS_CONST) {
164 optimizer_literal_class_info(
165 info,
166 opline->op1_type,
167 opline->op1,
168 opline->op2.constant,
169 LITERAL_STATIC_METHOD, (ZEND_OP1_TYPE(opline) == IS_CONST) ? 1 : 2, 2,
170 op_array);
171 }
172 break;
173 case ZEND_CATCH:
174 LITERAL_INFO(opline->op1.constant, LITERAL_CLASS, 1, 1, 2);
175 break;
176 case ZEND_DEFINED:
177 LITERAL_INFO(opline->op1.constant, LITERAL_CONST, 1, 1, 2);
178 break;
179 case ZEND_FETCH_CONSTANT:
180 if (ZEND_OP1_TYPE(opline) == IS_UNUSED) {
181 if ((opline->extended_value & (IS_CONSTANT_IN_NAMESPACE|IS_CONSTANT_UNQUALIFIED)) == (IS_CONSTANT_IN_NAMESPACE|IS_CONSTANT_UNQUALIFIED)) {
182 LITERAL_INFO(opline->op2.constant, LITERAL_CONST, 1, 1, 5);
183 } else {
184 LITERAL_INFO(opline->op2.constant, LITERAL_CONST, 1, 1, 3);
185 }
186 } else {
187 if (ZEND_OP1_TYPE(opline) == IS_CONST) {
188 LITERAL_INFO(opline->op1.constant, LITERAL_CLASS, 1, 1, 2);
189 }
190 optimizer_literal_class_info(
191 info,
192 opline->op1_type,
193 opline->op1,
194 opline->op2.constant,
195 LITERAL_CLASS_CONST, (ZEND_OP1_TYPE(opline) == IS_CONST) ? 1 : 2, 1,
196 op_array);
197 }
198 break;
199 case ZEND_FETCH_R:
200 case ZEND_FETCH_W:
201 case ZEND_FETCH_RW:
202 case ZEND_FETCH_IS:
203 case ZEND_FETCH_UNSET:
204 case ZEND_FETCH_FUNC_ARG:
205 case ZEND_UNSET_VAR:
206 case ZEND_ISSET_ISEMPTY_VAR:
207 if (ZEND_OP2_TYPE(opline) == IS_UNUSED) {
208 if (ZEND_OP1_TYPE(opline) == IS_CONST) {
209 LITERAL_INFO(opline->op1.constant, LITERAL_VALUE, 1, 0, 1);
210 }
211 } else {
212 if (ZEND_OP2_TYPE(opline) == IS_CONST) {
213 LITERAL_INFO(opline->op2.constant, LITERAL_CLASS, 1, 1, 2);
214 }
215 if (ZEND_OP1_TYPE(opline) == IS_CONST) {
216 optimizer_literal_class_info(
217 info,
218 opline->op2_type,
219 opline->op2,
220 opline->op1.constant,
221 LITERAL_STATIC_PROPERTY, 2, 1,
222 op_array);
223 }
224 }
225 break;
226 case ZEND_FETCH_CLASS:
227 case ZEND_ADD_INTERFACE:
228 case ZEND_ADD_TRAIT:
229 case ZEND_INSTANCEOF:
230 if (ZEND_OP2_TYPE(opline) == IS_CONST) {
231 LITERAL_INFO(opline->op2.constant, LITERAL_CLASS, 1, 1, 2);
232 }
233 break;
234 case ZEND_NEW:
235 if (ZEND_OP1_TYPE(opline) == IS_CONST) {
236 LITERAL_INFO(opline->op1.constant, LITERAL_CLASS, 1, 1, 2);
237 }
238 break;
239 case ZEND_ASSIGN_OBJ:
240 case ZEND_FETCH_OBJ_R:
241 case ZEND_FETCH_OBJ_W:
242 case ZEND_FETCH_OBJ_RW:
243 case ZEND_FETCH_OBJ_IS:
244 case ZEND_FETCH_OBJ_UNSET:
245 case ZEND_FETCH_OBJ_FUNC_ARG:
246 case ZEND_UNSET_OBJ:
247 case ZEND_PRE_INC_OBJ:
248 case ZEND_PRE_DEC_OBJ:
249 case ZEND_POST_INC_OBJ:
250 case ZEND_POST_DEC_OBJ:
251 case ZEND_ISSET_ISEMPTY_PROP_OBJ:
252 if (ZEND_OP2_TYPE(opline) == IS_CONST) {
253 optimizer_literal_obj_info(
254 info,
255 opline->op1_type,
256 opline->op1,
257 opline->op2.constant,
258 LITERAL_PROPERTY, 2, 1,
259 op_array);
260 }
261 break;
262 case ZEND_ASSIGN_ADD:
263 case ZEND_ASSIGN_SUB:
264 case ZEND_ASSIGN_MUL:
265 case ZEND_ASSIGN_DIV:
266 case ZEND_ASSIGN_POW:
267 case ZEND_ASSIGN_MOD:
268 case ZEND_ASSIGN_SL:
269 case ZEND_ASSIGN_SR:
270 case ZEND_ASSIGN_CONCAT:
271 case ZEND_ASSIGN_BW_OR:
272 case ZEND_ASSIGN_BW_AND:
273 case ZEND_ASSIGN_BW_XOR:
274 if (ZEND_OP2_TYPE(opline) == IS_CONST) {
275 if (opline->extended_value == ZEND_ASSIGN_OBJ) {
276 optimizer_literal_obj_info(
277 info,
278 opline->op1_type,
279 opline->op1,
280 opline->op2.constant,
281 LITERAL_PROPERTY, 2, 1,
282 op_array);
283 } else {
284 LITERAL_INFO(opline->op2.constant, LITERAL_VALUE, 1, 0, 1);
285 }
286 }
287 break;
288 case ZEND_BIND_GLOBAL:
289 LITERAL_INFO(opline->op2.constant, LITERAL_GLOBAL, 0, 1, 1);
290 break;
291 case ZEND_RECV_INIT:
292 LITERAL_INFO(opline->op2.constant, LITERAL_VALUE, 0, 0, 1);
293 if (Z_CACHE_SLOT(op_array->literals[opline->op2.constant]) != -1) {
294 Z_CACHE_SLOT(op_array->literals[opline->op2.constant]) = cache_size;
295 cache_size += sizeof(void *);
296 }
297 break;
298 case ZEND_RECV:
299 case ZEND_RECV_VARIADIC:
300 case ZEND_VERIFY_RETURN_TYPE:
301 if (opline->op2.num != -1) {
302 opline->op2.num = cache_size;
303 cache_size += sizeof(void *);
304 }
305 default:
306 if (ZEND_OP1_TYPE(opline) == IS_CONST) {
307 LITERAL_INFO(opline->op1.constant, LITERAL_VALUE, 1, 0, 1);
308 }
309 if (ZEND_OP2_TYPE(opline) == IS_CONST) {
310 LITERAL_INFO(opline->op2.constant, LITERAL_VALUE, 1, 0, 1);
311 }
312 break;
313 }
314 opline++;
315 }
316
317 #if DEBUG_COMPACT_LITERALS
318 {
319 int i, use_copy;
320 fprintf(stderr, "File %s func %s\n", op_array->filename->val,
321 op_array->function_name ? op_array->function_name->val : "main");
322 fprintf(stderr, "Literlas table size %d\n", op_array->last_literal);
323
324 for (i = 0; i < op_array->last_literal; i++) {
325 zval zv;
326 ZVAL_COPY_VALUE(&zv, op_array->literals + i);
327 use_copy = zend_make_printable_zval(op_array->literals + i, &zv);
328 fprintf(stderr, "Literal %d, val (%d):%s\n", i, Z_STRLEN(zv), Z_STRVAL(zv));
329 if (use_copy) {
330 zval_dtor(&zv);
331 }
332 }
333 fflush(stderr);
334 }
335 #endif
336
337 /* Merge equal constants */
338 j = 0;
339 zend_hash_init(&hash, op_array->last_literal, NULL, NULL, 0);
340 map = (int*)zend_arena_alloc(&ctx->arena, op_array->last_literal * sizeof(int));
341 memset(map, 0, op_array->last_literal * sizeof(int));
342 for (i = 0; i < op_array->last_literal; i++) {
343 if (!info[i].flags) {
344 /* unsed literal */
345 zval_dtor(&op_array->literals[i]);
346 continue;
347 }
348 switch (Z_TYPE(op_array->literals[i])) {
349 case IS_NULL:
350 /* Only checking MAY_MERGE for IS_NULL here
351 * is because only IS_NULL can be default value for class type hinting(RECV_INIT). */
352 if ((info[i].flags & LITERAL_MAY_MERGE)) {
353 if (l_null < 0) {
354 l_null = j;
355 if (i != j) {
356 op_array->literals[j] = op_array->literals[i];
357 info[j] = info[i];
358 }
359 j++;
360 }
361 map[i] = l_null;
362 } else {
363 map[i] = j;
364 if (i != j) {
365 op_array->literals[j] = op_array->literals[i];
366 info[j] = info[i];
367 }
368 j++;
369 }
370 break;
371 case IS_FALSE:
372 if (l_false < 0) {
373 l_false = j;
374 if (i != j) {
375 op_array->literals[j] = op_array->literals[i];
376 info[j] = info[i];
377 }
378 j++;
379 }
380 map[i] = l_false;
381 break;
382 case IS_TRUE:
383 if (l_true < 0) {
384 l_true = j;
385 if (i != j) {
386 op_array->literals[j] = op_array->literals[i];
387 info[j] = info[i];
388 }
389 j++;
390 }
391 map[i] = l_true;
392 break;
393 case IS_LONG:
394 if ((pos = zend_hash_index_find(&hash, Z_LVAL(op_array->literals[i]))) != NULL) {
395 map[i] = Z_LVAL_P(pos);
396 } else {
397 map[i] = j;
398 ZVAL_LONG(&zv, j);
399 zend_hash_index_add_new(&hash, Z_LVAL(op_array->literals[i]), &zv);
400 if (i != j) {
401 op_array->literals[j] = op_array->literals[i];
402 info[j] = info[i];
403 }
404 j++;
405 }
406 break;
407 case IS_DOUBLE:
408 if ((pos = zend_hash_str_find(&hash, (char*)&Z_DVAL(op_array->literals[i]), sizeof(double))) != NULL) {
409 map[i] = Z_LVAL_P(pos);
410 } else {
411 map[i] = j;
412 ZVAL_LONG(&zv, j);
413 zend_hash_str_add(&hash, (char*)&Z_DVAL(op_array->literals[i]), sizeof(double), &zv);
414 if (i != j) {
415 op_array->literals[j] = op_array->literals[i];
416 info[j] = info[i];
417 }
418 j++;
419 }
420 break;
421 case IS_STRING:
422 case IS_CONSTANT:
423 if (info[i].flags & LITERAL_MAY_MERGE) {
424 if (info[i].flags & LITERAL_EX_OBJ) {
425 int key_len = MAX_LENGTH_OF_LONG + sizeof("->") - 1 + Z_STRLEN(op_array->literals[i]);
426 key = zend_string_alloc(key_len, 0);
427 ZSTR_LEN(key) = snprintf(ZSTR_VAL(key), ZSTR_LEN(key)-1, "%d->%s", info[i].u.num, Z_STRVAL(op_array->literals[i]));
428 } else if (info[i].flags & LITERAL_EX_CLASS) {
429 int key_len;
430 zval *class_name = &op_array->literals[(info[i].u.num < i) ? map[info[i].u.num] : info[i].u.num];
431 key_len = Z_STRLEN_P(class_name) + sizeof("::") - 1 + Z_STRLEN(op_array->literals[i]);
432 key = zend_string_alloc(key_len, 0);
433 memcpy(ZSTR_VAL(key), Z_STRVAL_P(class_name), Z_STRLEN_P(class_name));
434 memcpy(ZSTR_VAL(key) + Z_STRLEN_P(class_name), "::", sizeof("::") - 1);
435 memcpy(ZSTR_VAL(key) + Z_STRLEN_P(class_name) + sizeof("::") - 1,
436 Z_STRVAL(op_array->literals[i]),
437 Z_STRLEN(op_array->literals[i]) + 1);
438 } else {
439 key = zend_string_init(Z_STRVAL(op_array->literals[i]), Z_STRLEN(op_array->literals[i]), 0);
440 }
441 ZSTR_H(key) = zend_hash_func(ZSTR_VAL(key), ZSTR_LEN(key));
442 ZSTR_H(key) += info[i].flags;
443 }
444 if ((info[i].flags & LITERAL_MAY_MERGE) &&
445 (pos = zend_hash_find(&hash, key)) != NULL &&
446 Z_TYPE(op_array->literals[i]) == Z_TYPE(op_array->literals[Z_LVAL_P(pos)]) &&
447 info[i].flags == info[Z_LVAL_P(pos)].flags) {
448
449 zend_string_release(key);
450 map[i] = Z_LVAL_P(pos);
451 zval_dtor(&op_array->literals[i]);
452 n = LITERAL_NUM_RELATED(info[i].flags);
453 while (n > 1) {
454 i++;
455 zval_dtor(&op_array->literals[i]);
456 n--;
457 }
458 } else {
459 map[i] = j;
460 if (info[i].flags & LITERAL_MAY_MERGE) {
461 ZVAL_LONG(&zv, j);
462 zend_hash_add_new(&hash, key, &zv);
463 zend_string_release(key);
464 }
465 if (i != j) {
466 op_array->literals[j] = op_array->literals[i];
467 info[j] = info[i];
468 }
469 if (LITERAL_NUM_SLOTS(info[i].flags)) {
470 Z_CACHE_SLOT(op_array->literals[j]) = cache_size;
471 cache_size += LITERAL_NUM_SLOTS(info[i].flags) * sizeof(void*);
472 }
473 j++;
474 n = LITERAL_NUM_RELATED(info[i].flags);
475 while (n > 1) {
476 i++;
477 if (i != j) op_array->literals[j] = op_array->literals[i];
478 j++;
479 n--;
480 }
481 }
482 break;
483 default:
484 /* don't merge other types */
485 map[i] = j;
486 if (i != j) {
487 op_array->literals[j] = op_array->literals[i];
488 info[j] = info[i];
489 }
490 j++;
491 break;
492 }
493 }
494 zend_hash_destroy(&hash);
495 op_array->last_literal = j;
496 op_array->cache_size = cache_size;
497
498 /* Update opcodes to use new literals table */
499 opline = op_array->opcodes;
500 end = opline + op_array->last;
501 while (opline < end) {
502 if (ZEND_OP1_TYPE(opline) == IS_CONST) {
503 opline->op1.constant = map[opline->op1.constant];
504 }
505 if (ZEND_OP2_TYPE(opline) == IS_CONST) {
506 opline->op2.constant = map[opline->op2.constant];
507 }
508 opline++;
509 }
510 zend_arena_release(&ctx->arena, checkpoint);
511
512 #if DEBUG_COMPACT_LITERALS
513 {
514 int i, use_copy;
515 fprintf(stderr, "Optimized literlas table size %d\n", op_array->last_literal);
516
517 for (i = 0; i < op_array->last_literal; i++) {
518 zval zv;
519 ZVAL_COPY_VALUE(&zv, op_array->literals + i);
520 use_copy = zend_make_printable_zval(op_array->literals + i, &zv);
521 fprintf(stderr, "Literal %d, val (%d):%s\n", i, Z_STRLEN(zv), Z_STRVAL(zv));
522 if (use_copy) {
523 zval_dtor(&zv);
524 }
525 }
526 fflush(stderr);
527 }
528 #endif
529 }
530 }