xref: /PHP-8.2/Zend/Optimizer/compact_vars.c (revision a18df90a)
1 /*
2    +----------------------------------------------------------------------+
3    | Zend Engine, Removing unused variables                               |
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: Nikita Popov <nikic@php.net>                                |
16    +----------------------------------------------------------------------+
17 */
18 
19 #include "Optimizer/zend_optimizer_internal.h"
20 #include "zend_bitset.h"
21 #include "zend_observer.h"
22 
23 /* This pass removes all CVs and temporaries that are completely unused. It does *not* merge any CVs or TMPs.
24  * This pass does not operate on SSA form anymore. */
zend_optimizer_compact_vars(zend_op_array * op_array)25 void zend_optimizer_compact_vars(zend_op_array *op_array) {
26 	int i;
27 
28 	ALLOCA_FLAG(use_heap1);
29 	ALLOCA_FLAG(use_heap2);
30 	uint32_t used_vars_len = zend_bitset_len(op_array->last_var + op_array->T);
31 	zend_bitset used_vars = ZEND_BITSET_ALLOCA(used_vars_len, use_heap1);
32 	uint32_t *vars_map = do_alloca((op_array->last_var + op_array->T) * sizeof(uint32_t), use_heap2);
33 	uint32_t num_cvs, num_tmps;
34 
35 	/* Determine which CVs are used */
36 	zend_bitset_clear(used_vars, used_vars_len);
37 	for (i = 0; i < op_array->last; i++) {
38 		zend_op *opline = &op_array->opcodes[i];
39 		if (opline->op1_type & (IS_CV|IS_VAR|IS_TMP_VAR)) {
40 			zend_bitset_incl(used_vars, VAR_NUM(opline->op1.var));
41 		}
42 		if (opline->op2_type & (IS_CV|IS_VAR|IS_TMP_VAR)) {
43 			zend_bitset_incl(used_vars, VAR_NUM(opline->op2.var));
44 		}
45 		if (opline->result_type & (IS_CV|IS_VAR|IS_TMP_VAR)) {
46 			zend_bitset_incl(used_vars, VAR_NUM(opline->result.var));
47 			if (opline->opcode == ZEND_ROPE_INIT) {
48 				uint32_t num = ((opline->extended_value * sizeof(zend_string*)) + (sizeof(zval) - 1)) / sizeof(zval);
49 				while (num > 1) {
50 					num--;
51 					zend_bitset_incl(used_vars, VAR_NUM(opline->result.var) + num);
52 				}
53 			}
54 		}
55 	}
56 
57 	num_cvs = 0;
58 	for (i = 0; i < op_array->last_var; i++) {
59 		if (zend_bitset_in(used_vars, i)) {
60 			vars_map[i] = num_cvs++;
61 		} else {
62 			vars_map[i] = (uint32_t) -1;
63 		}
64 	}
65 
66 	num_tmps = 0;
67 	for (i = op_array->last_var; i < op_array->last_var + op_array->T; i++) {
68 		if (zend_bitset_in(used_vars, i)) {
69 			vars_map[i] = num_cvs + num_tmps++;
70 		} else {
71 			vars_map[i] = (uint32_t) -1;
72 		}
73 	}
74 
75 	free_alloca(used_vars, use_heap1);
76 	if (num_cvs == op_array->last_var && num_tmps == op_array->T) {
77 		free_alloca(vars_map, use_heap2);
78 		return;
79 	}
80 
81 	ZEND_ASSERT(num_cvs <= op_array->last_var);
82 	ZEND_ASSERT(num_tmps <= op_array->T);
83 
84 	/* Update CV and TMP references in opcodes */
85 	for (i = 0; i < op_array->last; i++) {
86 		zend_op *opline = &op_array->opcodes[i];
87 		if (opline->op1_type & (IS_CV|IS_VAR|IS_TMP_VAR)) {
88 			opline->op1.var = NUM_VAR(vars_map[VAR_NUM(opline->op1.var)]);
89 		}
90 		if (opline->op2_type & (IS_CV|IS_VAR|IS_TMP_VAR)) {
91 			opline->op2.var = NUM_VAR(vars_map[VAR_NUM(opline->op2.var)]);
92 		}
93 		if (opline->result_type & (IS_CV|IS_VAR|IS_TMP_VAR)) {
94 			opline->result.var = NUM_VAR(vars_map[VAR_NUM(opline->result.var)]);
95 		}
96 	}
97 
98 	/* Update CV name table */
99 	if (num_cvs != op_array->last_var) {
100 		if (num_cvs) {
101 			zend_string **names = safe_emalloc(sizeof(zend_string *), num_cvs, 0);
102 			for (i = 0; i < op_array->last_var; i++) {
103 				if (vars_map[i] != (uint32_t) -1) {
104 					names[vars_map[i]] = op_array->vars[i];
105 				} else {
106 					zend_string_release_ex(op_array->vars[i], 0);
107 				}
108 			}
109 			efree(op_array->vars);
110 			op_array->vars = names;
111 		} else {
112 			for (i = 0; i < op_array->last_var; i++) {
113 				zend_string_release_ex(op_array->vars[i], 0);
114 			}
115 			efree(op_array->vars);
116 			op_array->vars = NULL;
117 		}
118 		op_array->last_var = num_cvs;
119 	}
120 
121 	op_array->T = num_tmps + ZEND_OBSERVER_ENABLED; // reserve last temporary for observers if enabled
122 
123 	free_alloca(vars_map, use_heap2);
124 }
125