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