xref: /PHP-8.4/Zend/Optimizer/compact_literals.c (revision 780a8280)
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_INIT_PARENT_PROPERTY_HOOK_CALL:
201 					LITERAL_INFO(opline->op1.constant, 1);
202 					break;
203 				case ZEND_CATCH:
204 					LITERAL_INFO(opline->op1.constant, 2);
205 					break;
206 				case ZEND_FETCH_CONSTANT:
207 					if (opline->op1.num & IS_CONSTANT_UNQUALIFIED_IN_NAMESPACE) {
208 						LITERAL_INFO(opline->op2.constant, 3);
209 					} else {
210 						LITERAL_INFO(opline->op2.constant, 2);
211 					}
212 					break;
213 				case ZEND_FETCH_CLASS_CONSTANT:
214 					if (opline->op1_type == IS_CONST) {
215 						LITERAL_INFO(opline->op1.constant, 2);
216 					}
217 					if (opline->op2_type == IS_CONST) {
218 						LITERAL_INFO(opline->op2.constant, 1);
219 					}
220 					break;
221 				case ZEND_ASSIGN_STATIC_PROP:
222 				case ZEND_ASSIGN_STATIC_PROP_REF:
223 				case ZEND_FETCH_STATIC_PROP_R:
224 				case ZEND_FETCH_STATIC_PROP_W:
225 				case ZEND_FETCH_STATIC_PROP_RW:
226 				case ZEND_FETCH_STATIC_PROP_IS:
227 				case ZEND_FETCH_STATIC_PROP_UNSET:
228 				case ZEND_FETCH_STATIC_PROP_FUNC_ARG:
229 				case ZEND_UNSET_STATIC_PROP:
230 				case ZEND_ISSET_ISEMPTY_STATIC_PROP:
231 				case ZEND_PRE_INC_STATIC_PROP:
232 				case ZEND_PRE_DEC_STATIC_PROP:
233 				case ZEND_POST_INC_STATIC_PROP:
234 				case ZEND_POST_DEC_STATIC_PROP:
235 				case ZEND_ASSIGN_STATIC_PROP_OP:
236 					if (opline->op2_type == IS_CONST) {
237 						LITERAL_INFO(opline->op2.constant, 2);
238 					}
239 					if (opline->op1_type == IS_CONST) {
240 						LITERAL_INFO(opline->op1.constant, 1);
241 					}
242 					break;
243 				case ZEND_FETCH_CLASS:
244 				case ZEND_INSTANCEOF:
245 					if (opline->op2_type == IS_CONST) {
246 						LITERAL_INFO(opline->op2.constant, 2);
247 					}
248 					break;
249 				case ZEND_NEW:
250 					if (opline->op1_type == IS_CONST) {
251 						LITERAL_INFO(opline->op1.constant, 2);
252 					}
253 					break;
254 				case ZEND_DECLARE_CLASS:
255 				case ZEND_DECLARE_CLASS_DELAYED:
256 					LITERAL_INFO(opline->op1.constant, 2);
257 					if (opline->op2_type == IS_CONST) {
258 						LITERAL_INFO(opline->op2.constant, 1);
259 					}
260 					break;
261 				case ZEND_ISSET_ISEMPTY_DIM_OBJ:
262 				case ZEND_ASSIGN_DIM:
263 				case ZEND_UNSET_DIM:
264 				case ZEND_FETCH_DIM_R:
265 				case ZEND_FETCH_DIM_W:
266 				case ZEND_FETCH_DIM_RW:
267 				case ZEND_FETCH_DIM_IS:
268 				case ZEND_FETCH_DIM_FUNC_ARG:
269 				case ZEND_FETCH_DIM_UNSET:
270 				case ZEND_FETCH_LIST_R:
271 				case ZEND_FETCH_LIST_W:
272 				case ZEND_ASSIGN_DIM_OP:
273 					if (opline->op1_type == IS_CONST) {
274 						LITERAL_INFO(opline->op1.constant, 1);
275 					}
276 					if (opline->op2_type == IS_CONST) {
277 						if (Z_EXTRA(op_array->literals[opline->op2.constant]) == ZEND_EXTRA_VALUE) {
278 							LITERAL_INFO(opline->op2.constant, 2);
279 						} else {
280 							LITERAL_INFO(opline->op2.constant, 1);
281 						}
282 					}
283 					break;
284 				default:
285 					if (opline->op1_type == IS_CONST) {
286 						LITERAL_INFO(opline->op1.constant, 1);
287 					}
288 					if (opline->op2_type == IS_CONST) {
289 						LITERAL_INFO(opline->op2.constant, 1);
290 					}
291 					break;
292 			}
293 			opline++;
294 		}
295 
296 #if DEBUG_COMPACT_LITERALS
297 		{
298 			fprintf(stderr, "File %s func %s\n", op_array->filename->val,
299 					op_array->function_name ? op_array->function_name->val : "main");
300 			fprintf(stderr, "Literals table size %d\n", op_array->last_literal);
301 
302 			for (int i = 0; i < op_array->last_literal; i++) {
303 				zend_string *str = zval_get_string(op_array->literals + i);
304 				fprintf(stderr, "Literal %d, val (%zu):%s\n", i, ZSTR_LEN(str), ZSTR_VAL(str));
305 				zend_string_release(str);
306 			}
307 			fflush(stderr);
308 		}
309 #endif
310 
311 		/* Merge equal constants */
312 		j = 0;
313 		zend_hash_init(&hash, op_array->last_literal, NULL, NULL, 0);
314 		map = (int*)zend_arena_alloc(&ctx->arena, op_array->last_literal * sizeof(int));
315 		memset(map, 0, op_array->last_literal * sizeof(int));
316 		for (i = 0; i < op_array->last_literal; i++) {
317 			if (!info[i].num_related) {
318 				/* unset literal */
319 				zval_ptr_dtor_nogc(&op_array->literals[i]);
320 				continue;
321 			}
322 			switch (Z_TYPE(op_array->literals[i])) {
323 				case IS_NULL:
324 					ZEND_ASSERT(info[i].num_related == 1);
325 					if (l_null < 0) {
326 						l_null = j;
327 						if (i != j) {
328 							op_array->literals[j] = op_array->literals[i];
329 							info[j] = info[i];
330 						}
331 						j++;
332 					}
333 					map[i] = l_null;
334 					break;
335 				case IS_FALSE:
336 					ZEND_ASSERT(info[i].num_related == 1);
337 					if (l_false < 0) {
338 						l_false = j;
339 						if (i != j) {
340 							op_array->literals[j] = op_array->literals[i];
341 							info[j] = info[i];
342 						}
343 						j++;
344 					}
345 					map[i] = l_false;
346 					break;
347 				case IS_TRUE:
348 					ZEND_ASSERT(info[i].num_related == 1);
349 					if (l_true < 0) {
350 						l_true = j;
351 						if (i != j) {
352 							op_array->literals[j] = op_array->literals[i];
353 							info[j] = info[i];
354 						}
355 						j++;
356 					}
357 					map[i] = l_true;
358 					break;
359 				case IS_LONG:
360 					if (info[i].num_related == 1) {
361 						if ((pos = zend_hash_index_find(&hash, Z_LVAL(op_array->literals[i]))) != NULL) {
362 							map[i] = Z_LVAL_P(pos);
363 						} else {
364 							map[i] = j;
365 							ZVAL_LONG(&zv, j);
366 							zend_hash_index_add_new(&hash, Z_LVAL(op_array->literals[i]), &zv);
367 							if (i != j) {
368 								op_array->literals[j] = op_array->literals[i];
369 								info[j] = info[i];
370 							}
371 							j++;
372 						}
373 					} else {
374 						ZEND_ASSERT(info[i].num_related == 2);
375 						key = zend_string_init(Z_STRVAL(op_array->literals[i+1]), Z_STRLEN(op_array->literals[i+1]), 0);
376 						bias_key(key, 100 + info[i].num_related - 1);
377 						if ((pos = zend_hash_find(&hash, key)) != NULL) {
378 							ZEND_ASSERT(info[Z_LVAL_P(pos)].num_related == 2);
379 							map[i] = Z_LVAL_P(pos);
380 							zval_ptr_dtor_nogc(&op_array->literals[i+1]);
381 						} else {
382 							map[i] = j;
383 							ZVAL_LONG(&zv, j);
384 							zend_hash_add_new(&hash, key, &zv);
385 							if (i != j) {
386 								op_array->literals[j] = op_array->literals[i];
387 								info[j] = info[i];
388 								op_array->literals[j+1] = op_array->literals[i+1];
389 								info[j+1] = info[i+1];
390 							}
391 							j += 2;
392 						}
393 						zend_string_release_ex(key, 0);
394 						i++;
395 					}
396 					break;
397 				case IS_DOUBLE:
398 					ZEND_ASSERT(info[i].num_related == 1);
399 					key = zend_string_init((char*)&Z_DVAL(op_array->literals[i]), sizeof(double), 0);
400 					bias_key(key, 200);
401 					if ((pos = zend_hash_find(&hash, key))) {
402 						map[i] = Z_LVAL_P(pos);
403 					} else {
404 						map[i] = j;
405 						ZVAL_LONG(&zv, j);
406 						zend_hash_add_new(&hash, key, &zv);
407 						if (i != j) {
408 							op_array->literals[j] = op_array->literals[i];
409 							info[j] = info[i];
410 						}
411 						j++;
412 					}
413 					zend_string_release_ex(key, 0);
414 					break;
415 				case IS_STRING: {
416 					key = create_str_cache_key(&op_array->literals[i], info[i].num_related);
417 					if ((pos = zend_hash_find(&hash, key)) != NULL) {
418 						ZEND_ASSERT(Z_TYPE(op_array->literals[Z_LVAL_P(pos)]) == IS_STRING &&
419 							info[i].num_related == info[Z_LVAL_P(pos)].num_related);
420 						zend_string_release_ex(key, 0);
421 						map[i] = Z_LVAL_P(pos);
422 						zval_ptr_dtor_nogc(&op_array->literals[i]);
423 						n = info[i].num_related;
424 						while (n > 1) {
425 							i++;
426 							zval_ptr_dtor_nogc(&op_array->literals[i]);
427 							n--;
428 						}
429 					} else {
430 						map[i] = j;
431 						ZVAL_LONG(&zv, j);
432 						zend_hash_add_new(&hash, key, &zv);
433 						zend_string_release_ex(key, 0);
434 						if (i != j) {
435 							op_array->literals[j] = op_array->literals[i];
436 							info[j] = info[i];
437 						}
438 						j++;
439 						n = info[i].num_related;
440 						while (n > 1) {
441 							i++;
442 							if (i != j) op_array->literals[j] = op_array->literals[i];
443 							j++;
444 							n--;
445 						}
446 					}
447 					break;
448 				}
449 				case IS_ARRAY:
450 					ZEND_ASSERT(info[i].num_related == 1);
451 					if (zend_hash_num_elements(Z_ARRVAL(op_array->literals[i])) == 0) {
452 						if (l_empty_arr < 0) {
453 							l_empty_arr = j;
454 							if (i != j) {
455 								op_array->literals[j] = op_array->literals[i];
456 								info[j] = info[i];
457 							}
458 							j++;
459 						} else {
460 							zval_ptr_dtor_nogc(&op_array->literals[i]);
461 						}
462 						map[i] = l_empty_arr;
463 						break;
464 					}
465 					ZEND_FALLTHROUGH;
466 				default:
467 					/* don't merge other types */
468 					ZEND_ASSERT(info[i].num_related == 1);
469 					map[i] = j;
470 					if (i != j) {
471 						op_array->literals[j] = op_array->literals[i];
472 						info[j] = info[i];
473 					}
474 					j++;
475 					break;
476 			}
477 		}
478 
479 		/* Only clean "hash", as it will be reused in the loop below. */
480 		zend_hash_clean(&hash);
481 		op_array->last_literal = j;
482 
483 		const_slot = zend_arena_alloc(&ctx->arena, j * 6 * sizeof(int));
484 		memset(const_slot, -1, j * 6 * sizeof(int));
485 		class_slot = const_slot + j;
486 		func_slot = class_slot + j;
487 		bind_var_slot = func_slot + j;
488 		property_slot = bind_var_slot + j;
489 		method_slot = property_slot + j;
490 
491 		/* Update opcodes to use new literals table */
492 		cache_size = zend_op_array_extension_handles * sizeof(void*);
493 		opline = op_array->opcodes;
494 		end = opline + op_array->last;
495 		while (opline < end) {
496 			if (opline->op1_type == IS_CONST) {
497 				opline->op1.constant = map[opline->op1.constant];
498 			}
499 			if (opline->op2_type == IS_CONST) {
500 				opline->op2.constant = map[opline->op2.constant];
501 			}
502 			switch (opline->opcode) {
503 				case ZEND_RECV_INIT:
504 				case ZEND_RECV:
505 				case ZEND_RECV_VARIADIC:
506 				{
507 					size_t num_classes = type_num_classes(op_array, opline->op1.num);
508 					if (num_classes) {
509 						opline->extended_value = cache_size;
510 						cache_size += num_classes * sizeof(void *);
511 					}
512 					break;
513 				}
514 				case ZEND_VERIFY_RETURN_TYPE:
515 				{
516 					size_t num_classes = type_num_classes(op_array, 0);
517 					if (num_classes) {
518 						opline->op2.num = cache_size;
519 						cache_size += num_classes * sizeof(void *);
520 					}
521 					break;
522 				}
523 				case ZEND_ASSIGN_STATIC_PROP_OP:
524 					if (opline->op1_type == IS_CONST) {
525 						// op1 static property
526 						if (opline->op2_type == IS_CONST) {
527 							(opline+1)->extended_value = add_static_slot(&hash, op_array,
528 								opline->op2.constant,
529 								opline->op1.constant,
530 								LITERAL_STATIC_PROPERTY,
531 								&cache_size);
532 						} else {
533 							(opline+1)->extended_value = cache_size;
534 							cache_size += 3 * sizeof(void *);
535 						}
536 					} else if (opline->op2_type == IS_CONST) {
537 						// op2 class
538 						if (class_slot[opline->op2.constant] >= 0) {
539 							(opline+1)->extended_value = class_slot[opline->op2.constant];
540 						} else {
541 							(opline+1)->extended_value = cache_size;
542 							class_slot[opline->op2.constant] = cache_size;
543 							cache_size += sizeof(void *);
544 						}
545 					}
546 					break;
547 				case ZEND_ASSIGN_OBJ_OP:
548 					if (opline->op2_type == IS_CONST) {
549 						// op2 property
550 						if (opline->op1_type == IS_UNUSED &&
551 						    property_slot[opline->op2.constant] >= 0) {
552 							(opline+1)->extended_value = property_slot[opline->op2.constant];
553 						} else {
554 							(opline+1)->extended_value = cache_size;
555 							cache_size += 3 * sizeof(void *);
556 							if (opline->op1_type == IS_UNUSED) {
557 								property_slot[opline->op2.constant] = (opline+1)->extended_value;
558 							}
559 						}
560 					}
561 					break;
562 				case ZEND_ASSIGN_OBJ:
563 				case ZEND_ASSIGN_OBJ_REF:
564 				case ZEND_FETCH_OBJ_R:
565 				case ZEND_FETCH_OBJ_W:
566 				case ZEND_FETCH_OBJ_RW:
567 				case ZEND_FETCH_OBJ_IS:
568 				case ZEND_FETCH_OBJ_UNSET:
569 				case ZEND_FETCH_OBJ_FUNC_ARG:
570 				case ZEND_UNSET_OBJ:
571 				case ZEND_PRE_INC_OBJ:
572 				case ZEND_PRE_DEC_OBJ:
573 				case ZEND_POST_INC_OBJ:
574 				case ZEND_POST_DEC_OBJ:
575 					if (opline->op2_type == IS_CONST) {
576 						// op2 property
577 						if (opline->op1_type == IS_UNUSED &&
578 						    property_slot[opline->op2.constant] >= 0) {
579 							opline->extended_value = property_slot[opline->op2.constant] | (opline->extended_value & ZEND_FETCH_OBJ_FLAGS);
580 						} else {
581 							opline->extended_value = cache_size | (opline->extended_value & ZEND_FETCH_OBJ_FLAGS);
582 							cache_size += 3 * sizeof(void *);
583 							if (opline->op1_type == IS_UNUSED) {
584 								property_slot[opline->op2.constant] = opline->extended_value & ~ZEND_FETCH_OBJ_FLAGS;
585 							}
586 						}
587 					}
588 					break;
589 				case ZEND_ISSET_ISEMPTY_PROP_OBJ:
590 					if (opline->op2_type == IS_CONST) {
591 						// op2 property
592 						if (opline->op1_type == IS_UNUSED &&
593 						    property_slot[opline->op2.constant] >= 0) {
594 							opline->extended_value = property_slot[opline->op2.constant] | (opline->extended_value & ZEND_ISEMPTY);
595 						} else {
596 							opline->extended_value = cache_size | (opline->extended_value & ZEND_ISEMPTY);
597 							cache_size += 3 * sizeof(void *);
598 							if (opline->op1_type == IS_UNUSED) {
599 								property_slot[opline->op2.constant] = opline->extended_value & ~ZEND_ISEMPTY;
600 							}
601 						}
602 					}
603 					break;
604 				case ZEND_INIT_FCALL:
605 				case ZEND_INIT_FCALL_BY_NAME:
606 				case ZEND_INIT_NS_FCALL_BY_NAME:
607 					// op2 func
608 					if (func_slot[opline->op2.constant] >= 0) {
609 						opline->result.num = func_slot[opline->op2.constant];
610 					} else {
611 						opline->result.num = cache_size;
612 						cache_size += sizeof(void *);
613 						func_slot[opline->op2.constant] = opline->result.num;
614 					}
615 					break;
616 				case ZEND_INIT_METHOD_CALL:
617 					if (opline->op2_type == IS_CONST) {
618 						// op2 method
619 						if (opline->op1_type == IS_UNUSED &&
620 						    method_slot[opline->op2.constant] >= 0) {
621 							opline->result.num = method_slot[opline->op2.constant];
622 						} else {
623 							opline->result.num = cache_size;
624 							cache_size += 2 * sizeof(void *);
625 							if (opline->op1_type == IS_UNUSED) {
626 								method_slot[opline->op2.constant] = opline->result.num;
627 							}
628 						}
629 					}
630 					break;
631 				case ZEND_INIT_STATIC_METHOD_CALL:
632 					if (opline->op2_type == IS_CONST) {
633 						// op2 static method
634 						if (opline->op1_type == IS_CONST) {
635 							opline->result.num = add_static_slot(&hash, op_array,
636 								opline->op1.constant,
637 								opline->op2.constant,
638 								LITERAL_STATIC_METHOD,
639 								&cache_size);
640 						} else {
641 							opline->result.num = cache_size;
642 							cache_size += 2 * sizeof(void *);
643 						}
644 					} else if (opline->op1_type == IS_CONST) {
645 						// op1 class
646 						if (class_slot[opline->op1.constant] >= 0) {
647 							opline->result.num = class_slot[opline->op1.constant];
648 						} else {
649 							opline->result.num = cache_size;
650 							cache_size += sizeof(void *);
651 							class_slot[opline->op1.constant] = opline->result.num;
652 						}
653 					}
654 					break;
655 				case ZEND_DEFINED:
656 					// op1 const
657 					if (const_slot[opline->op1.constant] >= 0) {
658 						opline->extended_value = const_slot[opline->op1.constant];
659 					} else {
660 						opline->extended_value = cache_size;
661 						cache_size += sizeof(void *);
662 						const_slot[opline->op1.constant] = opline->extended_value;
663 					}
664 					break;
665 				case ZEND_FETCH_CONSTANT:
666 					// op2 const
667 					if (const_slot[opline->op2.constant] >= 0) {
668 						opline->extended_value = const_slot[opline->op2.constant];
669 					} else {
670 						opline->extended_value = cache_size;
671 						cache_size += sizeof(void *);
672 						const_slot[opline->op2.constant] = opline->extended_value;
673 					}
674 					break;
675 				case ZEND_FETCH_CLASS_CONSTANT:
676 					if (opline->op1_type == IS_CONST
677 						&& opline->op2_type == IS_CONST
678 						&& Z_TYPE(op_array->literals[opline->op2.constant]) == IS_STRING) {
679 						// op1/op2 class_const
680 						opline->extended_value = add_static_slot(&hash, op_array,
681 							opline->op1.constant,
682 							opline->op2.constant,
683 							LITERAL_CLASS_CONST,
684 							&cache_size);
685 					} else {
686 						opline->extended_value = cache_size;
687 						cache_size += 2 * sizeof(void *);
688 					}
689 					break;
690 				case ZEND_ASSIGN_STATIC_PROP:
691 				case ZEND_ASSIGN_STATIC_PROP_REF:
692 				case ZEND_FETCH_STATIC_PROP_R:
693 				case ZEND_FETCH_STATIC_PROP_W:
694 				case ZEND_FETCH_STATIC_PROP_RW:
695 				case ZEND_FETCH_STATIC_PROP_IS:
696 				case ZEND_FETCH_STATIC_PROP_UNSET:
697 				case ZEND_FETCH_STATIC_PROP_FUNC_ARG:
698 				case ZEND_UNSET_STATIC_PROP:
699 				case ZEND_ISSET_ISEMPTY_STATIC_PROP:
700 				case ZEND_PRE_INC_STATIC_PROP:
701 				case ZEND_PRE_DEC_STATIC_PROP:
702 				case ZEND_POST_INC_STATIC_PROP:
703 				case ZEND_POST_DEC_STATIC_PROP:
704 					if (opline->op1_type == IS_CONST) {
705 						// op1 static property
706 						if (opline->op2_type == IS_CONST) {
707 							opline->extended_value = add_static_slot(&hash, op_array,
708 								opline->op2.constant,
709 								opline->op1.constant,
710 								LITERAL_STATIC_PROPERTY,
711 								&cache_size) | (opline->extended_value & ZEND_FETCH_OBJ_FLAGS);
712 						} else {
713 							opline->extended_value = cache_size | (opline->extended_value & ZEND_FETCH_OBJ_FLAGS);
714 							cache_size += 3 * sizeof(void *);
715 						}
716 					} else if (opline->op2_type == IS_CONST) {
717 						// op2 class
718 						if (class_slot[opline->op2.constant] >= 0) {
719 							opline->extended_value = class_slot[opline->op2.constant] | (opline->extended_value & ZEND_FETCH_OBJ_FLAGS);
720 						} else {
721 							opline->extended_value = cache_size | (opline->extended_value & ZEND_FETCH_OBJ_FLAGS);
722 							class_slot[opline->op2.constant] = cache_size;
723 							cache_size += sizeof(void *);
724 						}
725 					}
726 					break;
727 				case ZEND_FETCH_CLASS:
728 				case ZEND_INSTANCEOF:
729 					if (opline->op2_type == IS_CONST) {
730 						// op2 class
731 						if (class_slot[opline->op2.constant] >= 0) {
732 							opline->extended_value = class_slot[opline->op2.constant];
733 						} else {
734 							opline->extended_value = cache_size;
735 							cache_size += sizeof(void *);
736 							class_slot[opline->op2.constant] = opline->extended_value;
737 						}
738 					}
739 					break;
740 				case ZEND_NEW:
741 					if (opline->op1_type == IS_CONST) {
742 						// op1 class
743 						if (class_slot[opline->op1.constant] >= 0) {
744 							opline->op2.num = class_slot[opline->op1.constant];
745 						} else {
746 							opline->op2.num = cache_size;
747 							cache_size += sizeof(void *);
748 							class_slot[opline->op1.constant] = opline->op2.num;
749 						}
750 					}
751 					break;
752 				case ZEND_CATCH:
753 					if (opline->op1_type == IS_CONST) {
754 						// op1 class
755 						if (class_slot[opline->op1.constant] >= 0) {
756 							opline->extended_value = class_slot[opline->op1.constant] | (opline->extended_value & ZEND_LAST_CATCH);
757 						} else {
758 							opline->extended_value = cache_size | (opline->extended_value & ZEND_LAST_CATCH);
759 							cache_size += sizeof(void *);
760 							class_slot[opline->op1.constant] = opline->extended_value & ~ZEND_LAST_CATCH;
761 						}
762 					}
763 					break;
764 				case ZEND_BIND_GLOBAL:
765 					// op2 bind var
766 					if (bind_var_slot[opline->op2.constant] >= 0) {
767 						opline->extended_value = bind_var_slot[opline->op2.constant];
768 					} else {
769 						opline->extended_value = cache_size;
770 						cache_size += sizeof(void *);
771 						bind_var_slot[opline->op2.constant] = opline->extended_value;
772 					}
773 					break;
774 				case ZEND_DECLARE_ANON_CLASS:
775 				case ZEND_DECLARE_CLASS_DELAYED:
776 				case ZEND_JMP_FRAMELESS:
777 					opline->extended_value = cache_size;
778 					cache_size += sizeof(void *);
779 					break;
780 				case ZEND_SEND_VAL:
781 				case ZEND_SEND_VAL_EX:
782 				case ZEND_SEND_VAR:
783 				case ZEND_SEND_VAR_EX:
784 				case ZEND_SEND_VAR_NO_REF:
785 				case ZEND_SEND_VAR_NO_REF_EX:
786 				case ZEND_SEND_REF:
787 				case ZEND_SEND_FUNC_ARG:
788 				case ZEND_CHECK_FUNC_ARG:
789 					if (opline->op2_type == IS_CONST) {
790 						opline->result.num = cache_size;
791 						cache_size += 2 * sizeof(void *);
792 					}
793 					break;
794 			}
795 			opline++;
796 		}
797 		op_array->cache_size = cache_size;
798 		zend_hash_destroy(&hash);
799 		zend_arena_release(&ctx->arena, checkpoint);
800 
801 		if (1) {
802 			opline = op_array->opcodes;
803 			while (1) {
804 				if (opline->opcode == ZEND_RECV_INIT) {
805 					zval *val = &op_array->literals[opline->op2.constant];
806 
807 					if (Z_TYPE_P(val) == IS_CONSTANT_AST) {
808 						/* Ensure zval is aligned to 8 bytes */
809 						op_array->cache_size = ZEND_MM_ALIGNED_SIZE_EX(op_array->cache_size, 8);
810 						Z_CACHE_SLOT_P(val) = op_array->cache_size;
811 						op_array->cache_size += sizeof(zval);
812 					}
813 				} else if (opline->opcode != ZEND_RECV) {
814 					break;
815 				}
816 				opline++;
817 			}
818 		}
819 
820 #if DEBUG_COMPACT_LITERALS
821 		{
822 			fprintf(stderr, "Optimized literals table size %d\n", op_array->last_literal);
823 
824 			for (int i = 0; i < op_array->last_literal; i++) {
825 				zend_string *str = zval_get_string(op_array->literals + i);
826 				fprintf(stderr, "Literal %d, val (%zu):%s\n", i, ZSTR_LEN(str), ZSTR_VAL(str));
827 				zend_string_release(str);
828 			}
829 			fflush(stderr);
830 		}
831 #endif
832 	}
833 }
834