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