1818#include "zend_bitset.h"
1919#include "zend_observer.h"
2020
21+ /* Types a CV may have, excluding array kinds. */
22+ #define CV_TYPE_KINDS (MAY_BE_UNDEF|MAY_BE_ANY|MAY_BE_REF)
23+ /* Type kinds that are not refcounted. */
24+ #define CV_TYPE_KINDS_NO_RC (MAY_BE_UNDEF|MAY_BE_NULL|MAY_BE_BOOL|MAY_BE_LONG|MAY_BE_DOUBLE)
25+
26+ /* Fills in `rc_cvs` with the CV numbers that may be refcounted, based on SSA info (if available). */
27+ static void compute_rc_cvs (
28+ const zend_op_array * op_array , const zend_ssa * ssa , zend_bitset rc_cvs )
29+ {
30+ if (!ssa || !ssa -> var_info ) {
31+ /* No type information: every CV has to be assumed refcounted. */
32+ zend_bitset_fill (rc_cvs , zend_bitset_len (op_array -> last_var ));
33+ return ;
34+ }
35+
36+ zend_bitset_clear (rc_cvs , zend_bitset_len (op_array -> last_var ));
37+
38+ /* TODO: Argument slots right now have to be always destroyed because
39+ * when ZEND_RECV fails, they can be refcounted (e.g. passing an array to an int parameter).
40+ * This can be improved by making the ZEND_RECV op destroy the argument iself. */
41+ for (uint32_t i = 0 ; i < MIN ((uint32_t ) op_array -> last_var , op_array -> num_args ); i ++ ) {
42+ zend_bitset_incl (rc_cvs , i );
43+ }
44+ for (int i = 0 ; i < ssa -> vars_count ; i ++ ) {
45+ int cv = ssa -> vars [i ].var ;
46+
47+ if (cv < 0 || cv >= op_array -> last_var || zend_bitset_in (rc_cvs , cv )) {
48+ continue ;
49+ }
50+ if (ssa -> var_info [i ].type & CV_TYPE_KINDS & ~CV_TYPE_KINDS_NO_RC ) {
51+ zend_bitset_incl (rc_cvs , cv );
52+ }
53+ }
54+
55+ /* ZEND_RETURN_BY_REF turns its op1 into a reference without being recorded as
56+ * such in the SSA. */
57+ /* TODO: should this be recorded by type inference and SSA construction via a new def? */
58+ if (op_array -> fn_flags & ZEND_ACC_RETURN_REFERENCE ) {
59+ for (uint32_t i = 0 ; i < op_array -> last ; i ++ ) {
60+ const zend_op * opline = & op_array -> opcodes [i ];
61+
62+ if (opline -> opcode == ZEND_RETURN_BY_REF && opline -> op1_type == IS_CV ) {
63+ zend_bitset_incl (rc_cvs , EX_VAR_TO_NUM (opline -> op1 .var ));
64+ }
65+ }
66+ }
67+ }
68+
2169/* This pass removes all CVs and temporaries that are completely unused. It does *not* merge any CVs or TMPs.
22- * This pass does not operate on SSA form anymore. */
23- void zend_optimizer_compact_vars (zend_op_array * op_array ) {
70+ *
71+ * It also sorts the refcounted CVs so they appear at the front, to fill in `op_array->last_var_to_free`.
72+ * This improves call frame cleanup performance by skipping (in bulk) the CVs that are not refcounted. */
73+ void zend_optimizer_compact_vars (zend_op_array * op_array , const zend_ssa * ssa ) {
2474 int i ;
2575
2676 ALLOCA_FLAG (use_heap1 );
2777 ALLOCA_FLAG (use_heap2 );
78+ ALLOCA_FLAG (use_heap3 );
2879 uint32_t used_vars_len = zend_bitset_len (op_array -> last_var + op_array -> T );
2980 zend_bitset used_vars = ZEND_BITSET_ALLOCA (used_vars_len , use_heap1 );
3081 uint32_t * vars_map = do_alloca ((op_array -> last_var + op_array -> T ) * sizeof (uint32_t ), use_heap2 );
31- uint32_t num_cvs , num_tmps ;
82+ uint32_t rc_cvs_len = zend_bitset_len (op_array -> last_var );
83+ zend_bitset rc_cvs = ZEND_BITSET_ALLOCA (rc_cvs_len , use_heap3 );
3284
3385 /* Determine which CVs are used */
3486 zend_bitset_clear (used_vars , used_vars_len );
@@ -52,16 +104,36 @@ void zend_optimizer_compact_vars(zend_op_array *op_array) {
52104 }
53105 }
54106
55- num_cvs = 0 ;
107+ compute_rc_cvs (op_array , ssa , rc_cvs );
108+
109+ uint32_t num_cvs = 0 ;
110+ uint32_t last_var_to_free = 0 ;
56111 for (i = 0 ; i < op_array -> last_var ; i ++ ) {
57- if (zend_bitset_in (used_vars , i )) {
58- vars_map [i ] = num_cvs ++ ;
59- } else {
112+ /* Parameters have a fixed position because other components depend on that ordering. */
113+ if (i < op_array -> num_args ) {
114+ ZEND_ASSERT (zend_bitset_in (used_vars , i )
115+ && "A parameter CV is written by its RECV and cannot be unused" );
116+ } else if (!zend_bitset_in (used_vars , i ) || !zend_bitset_in (rc_cvs , i )) {
60117 vars_map [i ] = (uint32_t ) -1 ;
118+ continue ;
119+ }
120+ vars_map [i ] = num_cvs ++ ;
121+ if (zend_bitset_in (rc_cvs , i )) {
122+ /* Parameters keep their slot even when they are not refcounted,
123+ * so take the highest refcounted slot rather than the group size. */
124+ last_var_to_free = vars_map [i ] + 1 ;
125+ }
126+ }
127+
128+ /* The CVs that can never hold a refcounted value go last, after `last_var_to_free`. */
129+ for (i = op_array -> num_args ; i < op_array -> last_var ; i ++ ) {
130+ if (vars_map [i ] == (uint32_t ) -1 && zend_bitset_in (used_vars , i )) {
131+ vars_map [i ] = num_cvs ++ ;
61132 }
62133 }
134+ ZEND_ASSERT (last_var_to_free <= num_cvs );
63135
64- num_tmps = 0 ;
136+ uint32_t num_tmps = 0 ;
65137 for (i = op_array -> last_var ; i < op_array -> last_var + op_array -> T ; i ++ ) {
66138 if (zend_bitset_in (used_vars , i )) {
67139 vars_map [i ] = num_cvs + num_tmps ++ ;
@@ -71,9 +143,24 @@ void zend_optimizer_compact_vars(zend_op_array *op_array) {
71143 }
72144
73145 free_alloca (used_vars , use_heap1 );
146+ free_alloca (rc_cvs , use_heap3 );
147+
148+ op_array -> last_var_to_free = last_var_to_free ;
149+
150+ /* Nothing was removed and no CV moved: the rewrite below would be a no-op.
151+ * Check if anything got moved, as the number of vars could still be the same in that case. */
74152 if (num_cvs == op_array -> last_var && num_tmps == op_array -> T ) {
75- free_alloca (vars_map , use_heap2 );
76- return ;
153+ bool identity = true;
154+ for (i = 0 ; i < op_array -> last_var ; i ++ ) {
155+ if (vars_map [i ] != (uint32_t ) i ) {
156+ identity = false;
157+ break ;
158+ }
159+ }
160+ if (identity ) {
161+ free_alloca (vars_map , use_heap2 );
162+ return ;
163+ }
77164 }
78165
79166 ZEND_ASSERT (num_cvs <= op_array -> last_var );
@@ -93,8 +180,8 @@ void zend_optimizer_compact_vars(zend_op_array *op_array) {
93180 }
94181 }
95182
96- /* Update CV name table */
97- if ( num_cvs != op_array -> last_var ) {
183+ /* Update CV name table, either because of CVs being removed or being moved. */
184+ {
98185 if (num_cvs ) {
99186 zend_string * * names = safe_emalloc (sizeof (zend_string * ), num_cvs , 0 );
100187 for (i = 0 ; i < op_array -> last_var ; i ++ ) {
0 commit comments