xref: /PHP-8.2/Zend/Optimizer/compact_literals.c (revision 6c4d24e4)
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 
20 /* pass 11
21  * - compact literals table
22  */
23 
24 #include "Optimizer/zend_optimizer.h"
25 #include "Optimizer/zend_optimizer_internal.h"
26 #include "zend_API.h"
27 #include "zend_constants.h"
28 #include "zend_execute.h"
29 #include "zend_vm.h"
30 #include "zend_extensions.h"
31 
32 #define DEBUG_COMPACT_LITERALS 0
33 
34 #define LITERAL_CLASS_CONST 1
35 #define LITERAL_STATIC_METHOD 2
36 #define LITERAL_STATIC_PROPERTY 3
37 
38 typedef struct _literal_info {
39 	uint8_t num_related;
40 } literal_info;
41 
42 #define LITERAL_INFO(n, related) do { \
43 		info[n].num_related = (related); \
44 	} while (0)
45 
type_num_classes(const zend_op_array * op_array,uint32_t arg_num)46 static size_t type_num_classes(const zend_op_array *op_array, uint32_t arg_num)
47 {
48 	zend_arg_info *arg_info;
49 	if (arg_num > 0) {
50 		if (!(op_array->fn_flags & ZEND_ACC_HAS_TYPE_HINTS)) {
51 			return 0;
52 		}
53 		if (EXPECTED(arg_num <= op_array->num_args)) {
54 			arg_info = &op_array->arg_info[arg_num-1];
55 		} else if (UNEXPECTED(op_array->fn_flags & ZEND_ACC_VARIADIC)) {
56 			arg_info = &op_array->arg_info[op_array->num_args];
57 		} else {
58 			return 0;
59 		}
60 	} else {
61 		arg_info = op_array->arg_info - 1;
62 	}
63 
64 	if (ZEND_TYPE_IS_COMPLEX(arg_info->type)) {
65 		if (ZEND_TYPE_HAS_LIST(arg_info->type)) {
66 			/* Intersection types cannot have nested list types */
67 			if (ZEND_TYPE_IS_INTERSECTION(arg_info->type)) {
68 				return ZEND_TYPE_LIST(arg_info->type)->num_types;
69 			}
70 			ZEND_ASSERT(ZEND_TYPE_IS_UNION(arg_info->type));
71 			size_t count = 0;
72 			zend_type *list_type;
73 
74 			ZEND_TYPE_LIST_FOREACH(ZEND_TYPE_LIST(arg_info->type), list_type) {
75 				if (ZEND_TYPE_IS_INTERSECTION(*list_type)) {
76 					count += ZEND_TYPE_LIST(*list_type)->num_types;
77 				} else {
78 					ZEND_ASSERT(!ZEND_TYPE_HAS_LIST(*list_type));
79 					count += 1;
80 				}
81 			} ZEND_TYPE_LIST_FOREACH_END();
82 			return count;
83 		}
84 		return 1;
85 	}
86 
87 	return 0;
88 }
89 
add_static_slot(HashTable * hash,zend_op_array * op_array,uint32_t op1,uint32_t op2,uint32_t kind,uint32_t * cache_size)90 static uint32_t add_static_slot(HashTable     *hash,
91                                 zend_op_array *op_array,
92                                 uint32_t       op1,
93                                 uint32_t       op2,
94                                 uint32_t       kind,
95                                 uint32_t       *cache_size)
96 {
97 	uint32_t ret;
98 	zval *class_name = &op_array->literals[op1];
99 	zval *prop_name = &op_array->literals[op2];
100 	zval *pos, tmp;
101 
102 	zend_string *key = zend_create_member_string(Z_STR_P(class_name), Z_STR_P(prop_name));
103 	ZSTR_H(key) = zend_string_hash_func(key);
104 	ZSTR_H(key) += kind;
105 
106 	pos = zend_hash_find(hash, key);
107 	if (pos) {
108 		ret = Z_LVAL_P(pos);
109 	} else {
110 		ret = *cache_size;
111 		*cache_size += (kind == LITERAL_STATIC_PROPERTY ? 3 : 2) * sizeof(void *);
112 		ZVAL_LONG(&tmp, ret);
113 		zend_hash_add(hash, key, &tmp);
114 	}
115 	zend_string_release_ex(key, 0);
116 	return ret;
117 }
118 
bias_key(zend_string * key,uint32_t bias)119 static inline void bias_key(zend_string *key, uint32_t bias)
120 {
121 	/* Add a bias to the hash so we can distinguish string keys
122 	 * that would otherwise be the same. */
123 	ZSTR_H(key) = zend_string_hash_val(key) + bias;
124 }
125 
create_str_cache_key(zval * literal,uint8_t num_related)126 static zend_string *create_str_cache_key(zval *literal, uint8_t num_related)
127 {
128 	ZEND_ASSERT(Z_TYPE_P(literal) == IS_STRING);
129 	if (num_related == 1) {
130 		return zend_string_copy(Z_STR_P(literal));
131 	}
132 
133 	/* Concatenate all the related literals for the cache key. */
134 	zend_string *key;
135 	if (num_related == 2) {
136 		ZEND_ASSERT(Z_TYPE_P(literal + 1) == IS_STRING);
137 		key = zend_string_concat2(
138 			Z_STRVAL_P(literal), Z_STRLEN_P(literal),
139 			Z_STRVAL_P(literal + 1), Z_STRLEN_P(literal + 1));
140 	} else if (num_related == 3) {
141 		ZEND_ASSERT(Z_TYPE_P(literal + 1) == IS_STRING && Z_TYPE_P(literal + 2) == IS_STRING);
142 		key = zend_string_concat3(
143 			Z_STRVAL_P(literal), Z_STRLEN_P(literal),
144 			Z_STRVAL_P(literal + 1), Z_STRLEN_P(literal + 1),
145 			Z_STRVAL_P(literal + 2), Z_STRLEN_P(literal + 2));
146 	} else {
147 		ZEND_ASSERT(0 && "Currently not needed");
148 	}
149 
150 	bias_key(key, num_related - 1);
151 	return key;
152 }
153 
zend_optimizer_compact_literals(zend_op_array * op_array,zend_optimizer_ctx * ctx)154 void zend_optimizer_compact_literals(zend_op_array *op_array, zend_optimizer_ctx *ctx)
155 {
156 	zend_op *opline, *end;
157 	int i, j, n, *map;
158 	uint32_t cache_size;
159 	zval zv, *pos;
160 	literal_info *info;
161 	int l_null = -1;
162 	int l_false = -1;
163 	int l_true = -1;
164 	int l_empty_arr = -1;
165 	HashTable hash;
166 	zend_string *key = NULL;
167 	void *checkpoint = zend_arena_checkpoint(ctx->arena);
168 	int *const_slot, *class_slot, *func_slot, *bind_var_slot, *property_slot, *method_slot;
169 
170 	if (op_array->last_literal) {
171 		info = (literal_info*)zend_arena_calloc(&ctx->arena, op_array->last_literal, sizeof(literal_info));
172 
173 	    /* Mark literals of specific types */
174 		opline = op_array->opcodes;
175 		end = opline + op_array->last;
176 		while (opline < end) {
177 			switch (opline->opcode) {
178 				case ZEND_INIT_FCALL_BY_NAME:
179 					LITERAL_INFO(opline->op2.constant, 2);
180 					break;
181 				case ZEND_INIT_NS_FCALL_BY_NAME:
182 					LITERAL_INFO(opline->op2.constant, 3);
183 					break;
184 				case ZEND_INIT_METHOD_CALL:
185 					if (opline->op1_type == IS_CONST) {
186 						LITERAL_INFO(opline->op1.constant, 1);
187 					}
188 					if (opline->op2_type == IS_CONST) {
189 						LITERAL_INFO(opline->op2.constant, 2);
190 					}
191 					break;
192 				case ZEND_INIT_STATIC_METHOD_CALL:
193 					if (opline->op1_type == IS_CONST) {
194 						LITERAL_INFO(opline->op1.constant, 2);
195 					}
196 					if (opline->op2_type == IS_CONST) {
197 						LITERAL_INFO(opline->op2.constant, 2);
198 					}
199 					break;
200 				case ZEND_CATCH:
201 					LITERAL_INFO(opline->op1.constant, 2);
202 					break;
203 				case ZEND_FETCH_CONSTANT:
204 					if (opline->op1.num & IS_CONSTANT_UNQUALIFIED_IN_NAMESPACE) {
205 						LITERAL_INFO(opline->op2.constant, 3);
206 					} else {
207 						LITERAL_INFO(opline->op2.constant, 2);
208 					}
209 					break;
210 				case ZEND_FETCH_CLASS_CONSTANT:
211 					if (opline->op1_type == IS_CONST) {
212 						LITERAL_INFO(opline->op1.constant, 2);
213 					}
214 					LITERAL_INFO(opline->op2.constant, 1);
215 					break;
216 				case ZEND_ASSIGN_STATIC_PROP:
217 				case ZEND_ASSIGN_STATIC_PROP_REF:
218 				case ZEND_FETCH_STATIC_PROP_R:
219 				case ZEND_FETCH_STATIC_PROP_W:
220 				case ZEND_FETCH_STATIC_PROP_RW:
221 				case ZEND_FETCH_STATIC_PROP_IS:
222 				case ZEND_FETCH_STATIC_PROP_UNSET:
223 				case ZEND_FETCH_STATIC_PROP_FUNC_ARG:
224 				case ZEND_UNSET_STATIC_PROP:
225 				case ZEND_ISSET_ISEMPTY_STATIC_PROP:
226 				case ZEND_PRE_INC_STATIC_PROP:
227 				case ZEND_PRE_DEC_STATIC_PROP:
228 				case ZEND_POST_INC_STATIC_PROP:
229 				case ZEND_POST_DEC_STATIC_PROP:
230 				case ZEND_ASSIGN_STATIC_PROP_OP:
231 					if (opline->op2_type == IS_CONST) {
232 						LITERAL_INFO(opline->op2.constant, 2);
233 					}
234 					if (opline->op1_type == IS_CONST) {
235 						LITERAL_INFO(opline->op1.constant, 1);
236 					}
237 					break;
238 				case ZEND_FETCH_CLASS:
239 				case ZEND_INSTANCEOF:
240 					if (opline->op2_type == IS_CONST) {
241 						LITERAL_INFO(opline->op2.constant, 2);
242 					}
243 					break;
244 				case ZEND_NEW:
245 					if (opline->op1_type == IS_CONST) {
246 						LITERAL_INFO(opline->op1.constant, 2);
247 					}
248 					break;
249 				case ZEND_DECLARE_CLASS:
250 				case ZEND_DECLARE_CLASS_DELAYED:
251 					LITERAL_INFO(opline->op1.constant, 2);
252 					if (opline->op2_type == IS_CONST) {
253 						LITERAL_INFO(opline->op2.constant, 1);
254 					}
255 					break;
256 				case ZEND_ISSET_ISEMPTY_DIM_OBJ:
257 				case ZEND_ASSIGN_DIM:
258 				case ZEND_UNSET_DIM:
259 				case ZEND_FETCH_DIM_R:
260 				case ZEND_FETCH_DIM_W:
261 				case ZEND_FETCH_DIM_RW:
262 				case ZEND_FETCH_DIM_IS:
263 				case ZEND_FETCH_DIM_FUNC_ARG:
264 				case ZEND_FETCH_DIM_UNSET:
265 				case ZEND_FETCH_LIST_R:
266 				case ZEND_FETCH_LIST_W:
267 				case ZEND_ASSIGN_DIM_OP:
268 					if (opline->op1_type == IS_CONST) {
269 						LITERAL_INFO(opline->op1.constant, 1);
270 					}
271 					if (opline->op2_type == IS_CONST) {
272 						if (Z_EXTRA(op_array->literals[opline->op2.constant]) == ZEND_EXTRA_VALUE) {
273 							LITERAL_INFO(opline->op2.constant, 2);
274 						} else {
275 							LITERAL_INFO(opline->op2.constant, 1);
276 						}
277 					}
278 					break;
279 				default:
280 					if (opline->op1_type == IS_CONST) {
281 						LITERAL_INFO(opline->op1.constant, 1);
282 					}
283 					if (opline->op2_type == IS_CONST) {
284 						LITERAL_INFO(opline->op2.constant, 1);
285 					}
286 					break;
287 			}
288 			opline++;
289 		}
290 
291 #if DEBUG_COMPACT_LITERALS
292 		{
293 			fprintf(stderr, "File %s func %s\n", op_array->filename->val,
294 					op_array->function_name ? op_array->function_name->val : "main");
295 			fprintf(stderr, "Literals table size %d\n", op_array->last_literal);
296 
297 			for (int i = 0; i < op_array->last_literal; i++) {
298 				zend_string *str = zval_get_string(op_array->literals + i);
299 				fprintf(stderr, "Literal %d, val (%zu):%s\n", i, ZSTR_LEN(str), ZSTR_VAL(str));
300 				zend_string_release(str);
301 			}
302 			fflush(stderr);
303 		}
304 #endif
305 
306 		/* Merge equal constants */
307 		j = 0;
308 		zend_hash_init(&hash, op_array->last_literal, NULL, NULL, 0);
309 		map = (int*)zend_arena_alloc(&ctx->arena, op_array->last_literal * sizeof(int));
310 		memset(map, 0, op_array->last_literal * sizeof(int));
311 		for (i = 0; i < op_array->last_literal; i++) {
312 			if (!info[i].num_related) {
313 				/* unset literal */
314 				zval_ptr_dtor_nogc(&op_array->literals[i]);
315 				continue;
316 			}
317 			switch (Z_TYPE(op_array->literals[i])) {
318 				case IS_NULL:
319 					ZEND_ASSERT(info[i].num_related == 1);
320 					if (l_null < 0) {
321 						l_null = j;
322 						if (i != j) {
323 							op_array->literals[j] = op_array->literals[i];
324 							info[j] = info[i];
325 						}
326 						j++;
327 					}
328 					map[i] = l_null;
329 					break;
330 				case IS_FALSE:
331 					ZEND_ASSERT(info[i].num_related == 1);
332 					if (l_false < 0) {
333 						l_false = j;
334 						if (i != j) {
335 							op_array->literals[j] = op_array->literals[i];
336 							info[j] = info[i];
337 						}
338 						j++;
339 					}
340 					map[i] = l_false;
341 					break;
342 				case IS_TRUE:
343 					ZEND_ASSERT(info[i].num_related == 1);
344 					if (l_true < 0) {
345 						l_true = j;
346 						if (i != j) {
347 							op_array->literals[j] = op_array->literals[i];
348 							info[j] = info[i];
349 						}
350 						j++;
351 					}
352 					map[i] = l_true;
353 					break;
354 				case IS_LONG:
355 					if (info[i].num_related == 1) {
356 						if ((pos = zend_hash_index_find(&hash, Z_LVAL(op_array->literals[i]))) != NULL) {
357 							map[i] = Z_LVAL_P(pos);
358 						} else {
359 							map[i] = j;
360 							ZVAL_LONG(&zv, j);
361 							zend_hash_index_add_new(&hash, Z_LVAL(op_array->literals[i]), &zv);
362 							if (i != j) {
363 								op_array->literals[j] = op_array->literals[i];
364 								info[j] = info[i];
365 							}
366 							j++;
367 						}
368 					} else {
369 						ZEND_ASSERT(info[i].num_related == 2);
370 						key = zend_string_init(Z_STRVAL(op_array->literals[i+1]), Z_STRLEN(op_array->literals[i+1]), 0);
371 						bias_key(key, 100 + info[i].num_related - 1);
372 						if ((pos = zend_hash_find(&hash, key)) != NULL) {
373 							ZEND_ASSERT(info[Z_LVAL_P(pos)].num_related == 2);
374 							map[i] = Z_LVAL_P(pos);
375 							zval_ptr_dtor_nogc(&op_array->literals[i+1]);
376 						} else {
377 							map[i] = j;
378 							ZVAL_LONG(&zv, j);
379 							zend_hash_add_new(&hash, key, &zv);
380 							if (i != j) {
381 								op_array->literals[j] = op_array->literals[i];
382 								info[j] = info[i];
383 								op_array->literals[j+1] = op_array->literals[i+1];
384 								info[j+1] = info[i+1];
385 							}
386 							j += 2;
387 						}
388 						zend_string_release_ex(key, 0);
389 						i++;
390 					}
391 					break;
392 				case IS_DOUBLE:
393 					ZEND_ASSERT(info[i].num_related == 1);
394 					key = zend_string_init((char*)&Z_DVAL(op_array->literals[i]), sizeof(double), 0);
395 					bias_key(key, 200);
396 					if ((pos = zend_hash_find(&hash, key))) {
397 						map[i] = Z_LVAL_P(pos);
398 					} else {
399 						map[i] = j;
400 						ZVAL_LONG(&zv, j);
401 						zend_hash_add_new(&hash, key, &zv);
402 						if (i != j) {
403 							op_array->literals[j] = op_array->literals[i];
404 							info[j] = info[i];
405 						}
406 						j++;
407 					}
408 					zend_string_release_ex(key, 0);
409 					break;
410 				case IS_STRING: {
411 					key = create_str_cache_key(&op_array->literals[i], info[i].num_related);
412 					if ((pos = zend_hash_find(&hash, key)) != NULL) {
413 						ZEND_ASSERT(Z_TYPE(op_array->literals[Z_LVAL_P(pos)]) == IS_STRING &&
414 							info[i].num_related == info[Z_LVAL_P(pos)].num_related);
415 						zend_string_release_ex(key, 0);
416 						map[i] = Z_LVAL_P(pos);
417 						zval_ptr_dtor_nogc(&op_array->literals[i]);
418 						n = info[i].num_related;
419 						while (n > 1) {
420 							i++;
421 							zval_ptr_dtor_nogc(&op_array->literals[i]);
422 							n--;
423 						}
424 					} else {
425 						map[i] = j;
426 						ZVAL_LONG(&zv, j);
427 						zend_hash_add_new(&hash, key, &zv);
428 						zend_string_release_ex(key, 0);
429 						if (i != j) {
430 							op_array->literals[j] = op_array->literals[i];
431 							info[j] = info[i];
432 						}
433 						j++;
434 						n = info[i].num_related;
435 						while (n > 1) {
436 							i++;
437 							if (i != j) op_array->literals[j] = op_array->literals[i];
438 							j++;
439 							n--;
440 						}
441 					}
442 					break;
443 				}
444 				case IS_ARRAY:
445 					ZEND_ASSERT(info[i].num_related == 1);
446 					if (zend_hash_num_elements(Z_ARRVAL(op_array->literals[i])) == 0) {
447 						if (l_empty_arr < 0) {
448 							l_empty_arr = j;
449 							if (i != j) {
450 								op_array->literals[j] = op_array->literals[i];
451 								info[j] = info[i];
452 							}
453 							j++;
454 						} else {
455 							zval_ptr_dtor_nogc(&op_array->literals[i]);
456 						}
457 						map[i] = l_empty_arr;
458 						break;
459 					}
460 					ZEND_FALLTHROUGH;
461 				default:
462 					/* don't merge other types */
463 					ZEND_ASSERT(info[i].num_related == 1);
464 					map[i] = j;
465 					if (i != j) {
466 						op_array->literals[j] = op_array->literals[i];
467 						info[j] = info[i];
468 					}
469 					j++;
470 					break;
471 			}
472 		}
473 
474 		/* Only clean "hash", as it will be reused in the loop below. */
475 		zend_hash_clean(&hash);
476 		op_array->last_literal = j;
477 
478 		const_slot = zend_arena_alloc(&ctx->arena, j * 6 * sizeof(int));
479 		memset(const_slot, -1, j * 6 * sizeof(int));
480 		class_slot = const_slot + j;
481 		func_slot = class_slot + j;
482 		bind_var_slot = func_slot + j;
483 		property_slot = bind_var_slot + j;
484 		method_slot = property_slot + j;
485 
486 		/* Update opcodes to use new literals table */
487 		cache_size = zend_op_array_extension_handles * sizeof(void*);
488 		opline = op_array->opcodes;
489 		end = opline + op_array->last;
490 		while (opline < end) {
491 			if (opline->op1_type == IS_CONST) {
492 				opline->op1.constant = map[opline->op1.constant];
493 			}
494 			if (opline->op2_type == IS_CONST) {
495 				opline->op2.constant = map[opline->op2.constant];
496 			}
497 			switch (opline->opcode) {
498 				case ZEND_RECV_INIT:
499 				case ZEND_RECV:
500 				case ZEND_RECV_VARIADIC:
501 				{
502 					size_t num_classes = type_num_classes(op_array, opline->op1.num);
503 					if (num_classes) {
504 						opline->extended_value = cache_size;
505 						cache_size += num_classes * sizeof(void *);
506 					}
507 					break;
508 				}
509 				case ZEND_VERIFY_RETURN_TYPE:
510 				{
511 					size_t num_classes = type_num_classes(op_array, 0);
512 					if (num_classes) {
513 						opline->op2.num = cache_size;
514 						cache_size += num_classes * sizeof(void *);
515 					}
516 					break;
517 				}
518 				case ZEND_ASSIGN_STATIC_PROP_OP:
519 					if (opline->op1_type == IS_CONST) {
520 						// op1 static property
521 						if (opline->op2_type == IS_CONST) {
522 							(opline+1)->extended_value = add_static_slot(&hash, op_array,
523 								opline->op2.constant,
524 								opline->op1.constant,
525 								LITERAL_STATIC_PROPERTY,
526 								&cache_size);
527 						} else {
528 							(opline+1)->extended_value = cache_size;
529 							cache_size += 3 * sizeof(void *);
530 						}
531 					} else if (opline->op2_type == IS_CONST) {
532 						// op2 class
533 						if (class_slot[opline->op2.constant] >= 0) {
534 							(opline+1)->extended_value = class_slot[opline->op2.constant];
535 						} else {
536 							(opline+1)->extended_value = cache_size;
537 							class_slot[opline->op2.constant] = cache_size;
538 							cache_size += sizeof(void *);
539 						}
540 					}
541 					break;
542 				case ZEND_ASSIGN_OBJ_OP:
543 					if (opline->op2_type == IS_CONST) {
544 						// op2 property
545 						if (opline->op1_type == IS_UNUSED &&
546 						    property_slot[opline->op2.constant] >= 0) {
547 							(opline+1)->extended_value = property_slot[opline->op2.constant];
548 						} else {
549 							(opline+1)->extended_value = cache_size;
550 							cache_size += 3 * sizeof(void *);
551 							if (opline->op1_type == IS_UNUSED) {
552 								property_slot[opline->op2.constant] = (opline+1)->extended_value;
553 							}
554 						}
555 					}
556 					break;
557 				case ZEND_ASSIGN_OBJ:
558 				case ZEND_ASSIGN_OBJ_REF:
559 				case ZEND_FETCH_OBJ_R:
560 				case ZEND_FETCH_OBJ_W:
561 				case ZEND_FETCH_OBJ_RW:
562 				case ZEND_FETCH_OBJ_IS:
563 				case ZEND_FETCH_OBJ_UNSET:
564 				case ZEND_FETCH_OBJ_FUNC_ARG:
565 				case ZEND_UNSET_OBJ:
566 				case ZEND_PRE_INC_OBJ:
567 				case ZEND_PRE_DEC_OBJ:
568 				case ZEND_POST_INC_OBJ:
569 				case ZEND_POST_DEC_OBJ:
570 					if (opline->op2_type == IS_CONST) {
571 						// op2 property
572 						if (opline->op1_type == IS_UNUSED &&
573 						    property_slot[opline->op2.constant] >= 0) {
574 							opline->extended_value = property_slot[opline->op2.constant] | (opline->extended_value & ZEND_FETCH_OBJ_FLAGS);
575 						} else {
576 							opline->extended_value = cache_size | (opline->extended_value & ZEND_FETCH_OBJ_FLAGS);
577 							cache_size += 3 * sizeof(void *);
578 							if (opline->op1_type == IS_UNUSED) {
579 								property_slot[opline->op2.constant] = opline->extended_value & ~ZEND_FETCH_OBJ_FLAGS;
580 							}
581 						}
582 					}
583 					break;
584 				case ZEND_ISSET_ISEMPTY_PROP_OBJ:
585 					if (opline->op2_type == IS_CONST) {
586 						// op2 property
587 						if (opline->op1_type == IS_UNUSED &&
588 						    property_slot[opline->op2.constant] >= 0) {
589 							opline->extended_value = property_slot[opline->op2.constant] | (opline->extended_value & ZEND_ISEMPTY);
590 						} else {
591 							opline->extended_value = cache_size | (opline->extended_value & ZEND_ISEMPTY);
592 							cache_size += 3 * sizeof(void *);
593 							if (opline->op1_type == IS_UNUSED) {
594 								property_slot[opline->op2.constant] = opline->extended_value & ~ZEND_ISEMPTY;
595 							}
596 						}
597 					}
598 					break;
599 				case ZEND_INIT_FCALL:
600 				case ZEND_INIT_FCALL_BY_NAME:
601 				case ZEND_INIT_NS_FCALL_BY_NAME:
602 					// op2 func
603 					if (func_slot[opline->op2.constant] >= 0) {
604 						opline->result.num = func_slot[opline->op2.constant];
605 					} else {
606 						opline->result.num = cache_size;
607 						cache_size += sizeof(void *);
608 						func_slot[opline->op2.constant] = opline->result.num;
609 					}
610 					break;
611 				case ZEND_INIT_METHOD_CALL:
612 					if (opline->op2_type == IS_CONST) {
613 						// op2 method
614 						if (opline->op1_type == IS_UNUSED &&
615 						    method_slot[opline->op2.constant] >= 0) {
616 							opline->result.num = method_slot[opline->op2.constant];
617 						} else {
618 							opline->result.num = cache_size;
619 							cache_size += 2 * sizeof(void *);
620 							if (opline->op1_type == IS_UNUSED) {
621 								method_slot[opline->op2.constant] = opline->result.num;
622 							}
623 						}
624 					}
625 					break;
626 				case ZEND_INIT_STATIC_METHOD_CALL:
627 					if (opline->op2_type == IS_CONST) {
628 						// op2 static method
629 						if (opline->op1_type == IS_CONST) {
630 							opline->result.num = add_static_slot(&hash, op_array,
631 								opline->op1.constant,
632 								opline->op2.constant,
633 								LITERAL_STATIC_METHOD,
634 								&cache_size);
635 						} else {
636 							opline->result.num = cache_size;
637 							cache_size += 2 * sizeof(void *);
638 						}
639 					} else if (opline->op1_type == IS_CONST) {
640 						// op1 class
641 						if (class_slot[opline->op1.constant] >= 0) {
642 							opline->result.num = class_slot[opline->op1.constant];
643 						} else {
644 							opline->result.num = cache_size;
645 							cache_size += sizeof(void *);
646 							class_slot[opline->op1.constant] = opline->result.num;
647 						}
648 					}
649 					break;
650 				case ZEND_DEFINED:
651 					// op1 const
652 					if (const_slot[opline->op1.constant] >= 0) {
653 						opline->extended_value = const_slot[opline->op1.constant];
654 					} else {
655 						opline->extended_value = cache_size;
656 						cache_size += sizeof(void *);
657 						const_slot[opline->op1.constant] = opline->extended_value;
658 					}
659 					break;
660 				case ZEND_FETCH_CONSTANT:
661 					// op2 const
662 					if (const_slot[opline->op2.constant] >= 0) {
663 						opline->extended_value = const_slot[opline->op2.constant];
664 					} else {
665 						opline->extended_value = cache_size;
666 						cache_size += sizeof(void *);
667 						const_slot[opline->op2.constant] = opline->extended_value;
668 					}
669 					break;
670 				case ZEND_FETCH_CLASS_CONSTANT:
671 					if (opline->op1_type == IS_CONST) {
672 						// op1/op2 class_const
673 						opline->extended_value = add_static_slot(&hash, op_array,
674 							opline->op1.constant,
675 							opline->op2.constant,
676 							LITERAL_CLASS_CONST,
677 							&cache_size);
678 					} else {
679 						opline->extended_value = cache_size;
680 						cache_size += 2 * sizeof(void *);
681 					}
682 					break;
683 				case ZEND_ASSIGN_STATIC_PROP:
684 				case ZEND_ASSIGN_STATIC_PROP_REF:
685 				case ZEND_FETCH_STATIC_PROP_R:
686 				case ZEND_FETCH_STATIC_PROP_W:
687 				case ZEND_FETCH_STATIC_PROP_RW:
688 				case ZEND_FETCH_STATIC_PROP_IS:
689 				case ZEND_FETCH_STATIC_PROP_UNSET:
690 				case ZEND_FETCH_STATIC_PROP_FUNC_ARG:
691 				case ZEND_UNSET_STATIC_PROP:
692 				case ZEND_ISSET_ISEMPTY_STATIC_PROP:
693 				case ZEND_PRE_INC_STATIC_PROP:
694 				case ZEND_PRE_DEC_STATIC_PROP:
695 				case ZEND_POST_INC_STATIC_PROP:
696 				case ZEND_POST_DEC_STATIC_PROP:
697 					if (opline->op1_type == IS_CONST) {
698 						// op1 static property
699 						if (opline->op2_type == IS_CONST) {
700 							opline->extended_value = add_static_slot(&hash, op_array,
701 								opline->op2.constant,
702 								opline->op1.constant,
703 								LITERAL_STATIC_PROPERTY,
704 								&cache_size) | (opline->extended_value & ZEND_FETCH_OBJ_FLAGS);
705 						} else {
706 							opline->extended_value = cache_size | (opline->extended_value & ZEND_FETCH_OBJ_FLAGS);
707 							cache_size += 3 * sizeof(void *);
708 						}
709 					} else if (opline->op2_type == IS_CONST) {
710 						// op2 class
711 						if (class_slot[opline->op2.constant] >= 0) {
712 							opline->extended_value = class_slot[opline->op2.constant] | (opline->extended_value & ZEND_FETCH_OBJ_FLAGS);
713 						} else {
714 							opline->extended_value = cache_size | (opline->extended_value & ZEND_FETCH_OBJ_FLAGS);
715 							class_slot[opline->op2.constant] = cache_size;
716 							cache_size += sizeof(void *);
717 						}
718 					}
719 					break;
720 				case ZEND_FETCH_CLASS:
721 				case ZEND_INSTANCEOF:
722 					if (opline->op2_type == IS_CONST) {
723 						// op2 class
724 						if (class_slot[opline->op2.constant] >= 0) {
725 							opline->extended_value = class_slot[opline->op2.constant];
726 						} else {
727 							opline->extended_value = cache_size;
728 							cache_size += sizeof(void *);
729 							class_slot[opline->op2.constant] = opline->extended_value;
730 						}
731 					}
732 					break;
733 				case ZEND_NEW:
734 					if (opline->op1_type == IS_CONST) {
735 						// op1 class
736 						if (class_slot[opline->op1.constant] >= 0) {
737 							opline->op2.num = class_slot[opline->op1.constant];
738 						} else {
739 							opline->op2.num = cache_size;
740 							cache_size += sizeof(void *);
741 							class_slot[opline->op1.constant] = opline->op2.num;
742 						}
743 					}
744 					break;
745 				case ZEND_CATCH:
746 					if (opline->op1_type == IS_CONST) {
747 						// op1 class
748 						if (class_slot[opline->op1.constant] >= 0) {
749 							opline->extended_value = class_slot[opline->op1.constant] | (opline->extended_value & ZEND_LAST_CATCH);
750 						} else {
751 							opline->extended_value = cache_size | (opline->extended_value & ZEND_LAST_CATCH);
752 							cache_size += sizeof(void *);
753 							class_slot[opline->op1.constant] = opline->extended_value & ~ZEND_LAST_CATCH;
754 						}
755 					}
756 					break;
757 				case ZEND_BIND_GLOBAL:
758 					// op2 bind var
759 					if (bind_var_slot[opline->op2.constant] >= 0) {
760 						opline->extended_value = bind_var_slot[opline->op2.constant];
761 					} else {
762 						opline->extended_value = cache_size;
763 						cache_size += sizeof(void *);
764 						bind_var_slot[opline->op2.constant] = opline->extended_value;
765 					}
766 					break;
767 				case ZEND_DECLARE_ANON_CLASS:
768 				case ZEND_DECLARE_CLASS_DELAYED:
769 					opline->extended_value = cache_size;
770 					cache_size += sizeof(void *);
771 					break;
772 				case ZEND_SEND_VAL:
773 				case ZEND_SEND_VAL_EX:
774 				case ZEND_SEND_VAR:
775 				case ZEND_SEND_VAR_EX:
776 				case ZEND_SEND_VAR_NO_REF:
777 				case ZEND_SEND_VAR_NO_REF_EX:
778 				case ZEND_SEND_REF:
779 				case ZEND_SEND_FUNC_ARG:
780 				case ZEND_CHECK_FUNC_ARG:
781 					if (opline->op2_type == IS_CONST) {
782 						opline->result.num = cache_size;
783 						cache_size += 2 * sizeof(void *);
784 					}
785 					break;
786 			}
787 			opline++;
788 		}
789 		op_array->cache_size = cache_size;
790 		zend_hash_destroy(&hash);
791 		zend_arena_release(&ctx->arena, checkpoint);
792 
793 		if (1) {
794 			opline = op_array->opcodes;
795 			while (1) {
796 				if (opline->opcode == ZEND_RECV_INIT) {
797 					zval *val = &op_array->literals[opline->op2.constant];
798 
799 					if (Z_TYPE_P(val) == IS_CONSTANT_AST) {
800 						/* Ensure zval is aligned to 8 bytes */
801 						op_array->cache_size = ZEND_MM_ALIGNED_SIZE_EX(op_array->cache_size, 8);
802 						Z_CACHE_SLOT_P(val) = op_array->cache_size;
803 						op_array->cache_size += sizeof(zval);
804 					}
805 				} else if (opline->opcode != ZEND_RECV) {
806 					break;
807 				}
808 				opline++;
809 			}
810 		}
811 
812 #if DEBUG_COMPACT_LITERALS
813 		{
814 			fprintf(stderr, "Optimized literals table size %d\n", op_array->last_literal);
815 
816 			for (int i = 0; i < op_array->last_literal; i++) {
817 				zend_string *str = zval_get_string(op_array->literals + i);
818 				fprintf(stderr, "Literal %d, val (%zu):%s\n", i, ZSTR_LEN(str), ZSTR_VAL(str));
819 				zend_string_release(str);
820 			}
821 			fflush(stderr);
822 		}
823 #endif
824 	}
825 }
826