@@ -1066,46 +1066,72 @@ static bool zend_dfa_try_to_replace_result(zend_op_array *op_array, zend_ssa *ss
1066
1066
return 0 ;
1067
1067
}
1068
1068
1069
+ static bool op_dominates (const zend_op_array * op_array , const zend_ssa * ssa , const zend_op * a , const zend_op * b )
1070
+ {
1071
+ uint32_t a_block = ssa -> cfg .map [a - op_array -> opcodes ];
1072
+ uint32_t b_block = ssa -> cfg .map [b - op_array -> opcodes ];
1073
+ if (a_block == b_block ) {
1074
+ return a < b ;
1075
+ } else {
1076
+ return dominates (ssa -> cfg .blocks , a_block , b_block );
1077
+ }
1078
+ }
1079
+
1080
+ static bool op_dominates_all_uses (const zend_op_array * op_array , const zend_ssa * ssa , int start ) {
1081
+ int use ;
1082
+ FOREACH_USE (ssa -> vars + ssa -> ops [start ].op1_def , use ) {
1083
+ if (!op_dominates (op_array , ssa , op_array -> opcodes + start , op_array -> opcodes + use )) {
1084
+ return false;
1085
+ }
1086
+ } FOREACH_USE_END ();
1087
+ return true;
1088
+ }
1089
+
1069
1090
/* Sets a flag on SEND ops when a copy can be a avoided. */
1070
- static void zend_dfa_optimize_send_copies (zend_op_array * op_array , zend_ssa * ssa , zend_call_info * * call_map )
1091
+ static void zend_dfa_optimize_send_copies (zend_op_array * op_array , const zend_ssa * ssa )
1071
1092
{
1072
- for (int v = 0 ; v < ssa -> vars_count ; v ++ ) {
1073
- int var = ssa -> vars [v ].var ;
1074
- if (var >= op_array -> last_var ) {
1093
+ /* func_get_args() etc could make the optimization observable */
1094
+ if (ssa -> cfg .flags & ZEND_FUNC_VARARG ) {
1095
+ return ;
1096
+ }
1097
+
1098
+ for (uint32_t i = 0 ; i < op_array -> last ; i ++ ) {
1099
+ const zend_op * opline = & op_array -> opcodes [i ];
1100
+ if ((opline -> opcode != ZEND_SEND_VAR && opline -> opcode != ZEND_SEND_VAR_EX ) || opline -> op2_type != IS_UNUSED || opline -> op1_type != IS_CV ) {
1101
+ continue ;
1102
+ }
1103
+
1104
+ /* NULL must not be visible in backtraces */
1105
+ int ssa_cv = ssa -> ops [i ].op1_use ;
1106
+ if (ssa -> vars [ssa_cv ].var < op_array -> num_args ) {
1075
1107
continue ;
1076
1108
}
1077
1109
1078
- uint32_t type = ssa -> var_info [ v ]. type ;
1079
- /* Unsetting a CV is always fine if it gets overwritten afterwards. Since type inference often infers
1080
- * very wide types, we are very loose in matching types. */
1110
+ /* Unsetting a CV is always fine if it gets overwritten afterwards.
1111
+ * Since type inference often infers very wide types, we are very loose in matching types. */
1112
+ uint32_t type = ssa -> var_info [ ssa_cv ]. type ;
1081
1113
if ((type & (MAY_BE_REF |MAY_BE_UNDEF )) || !(type & MAY_BE_RC1 ) || !(type & (MAY_BE_STRING |MAY_BE_ARRAY ))) {
1082
1114
continue ;
1083
1115
}
1084
1116
1085
- int use = ssa -> vars [v ].use_chain ;
1086
- if (use >= 0
1087
- && (op_array -> opcodes [use ].opcode == ZEND_SEND_VAR || op_array -> opcodes [use ].opcode == ZEND_SEND_VAR_EX ) // TODO
1088
- && op_array -> opcodes [use ].op2_type == IS_UNUSED ) {
1089
- int next_use = zend_ssa_next_use (ssa -> ops , v , use );
1090
-
1091
- /* The next use must be an assignment of the call result, immediately after the call such that the
1092
- * unset variable can never be observed.
1093
- * It is also safe to optimize if there are no indirect accesses through func_get_args() etc,
1094
- * no more uses, and it is not part of a loop. */
1095
- if ((next_use < 0
1096
- && var >= op_array -> num_args /* NULL must not be visible in backtraces */
1097
- && !(ssa -> cfg .flags & ZEND_FUNC_VARARG )
1098
- && !(ssa -> cfg .blocks [ssa -> cfg .map [use ]].flags & ZEND_BB_LOOP_HEADER )
1099
- && ssa -> cfg .blocks [ssa -> cfg .map [use ]].loop_header == -1 )
1100
- || (next_use >= 0
1101
- && op_array -> opcodes [next_use ].opcode == ZEND_ASSIGN
1102
- && ssa -> ops [next_use ].op1_use == v
1103
- && ssa -> ops [next_use ].op2_use >= 0
1104
- && call_map [use ]
1105
- && call_map [use ]-> caller_call_opline + 1 == op_array -> opcodes + next_use
1106
- && ssa -> ops [call_map [use ]-> caller_call_opline - op_array -> opcodes ].result_def == ssa -> ops [next_use ].op2_use )) {
1107
- ZEND_ASSERT (op_array -> opcodes [use ].extended_value == 0 );
1108
- op_array -> opcodes [use ].extended_value = 1 ;
1117
+ if (opline -> opcode == ZEND_SEND_VAR ) {
1118
+ /* Check if the call dominates the assignment and the assignment dominates all the future uses of this SSA variable */
1119
+ int next_use = ssa -> ops [i ].op1_use_chain ;
1120
+ if (next_use >= 0
1121
+ && op_array -> opcodes [next_use ].opcode == ZEND_ASSIGN
1122
+ && ssa -> ops [next_use ].op1_use == ssa_cv
1123
+ && ssa -> ops [next_use ].op2_use >= 0
1124
+ && op_dominates (op_array , ssa , opline , op_array -> opcodes + next_use )) {
1125
+ if (op_dominates_all_uses (op_array , ssa , next_use )) {
1126
+ op_array -> opcodes [i ].extended_value = 1 ;
1127
+ //fprintf(stderr, "yes optimize 1\n");
1128
+ }
1129
+ }
1130
+ } else /* ZEND_SEND_VAR_EX */ {
1131
+ ZEND_ASSERT (ssa -> ops [i ].op1_def != -1 );
1132
+ if (ssa -> vars [ssa -> ops [i ].op1_def ].no_val ) {
1133
+ op_array -> opcodes [i ].extended_value = 1 ;
1134
+ //fprintf(stderr, "yes optimize 2\n");
1109
1135
}
1110
1136
}
1111
1137
}
@@ -1169,9 +1195,9 @@ void zend_dfa_optimize_op_array(zend_op_array *op_array, zend_optimizer_ctx *ctx
1169
1195
#endif
1170
1196
}
1171
1197
1172
- /* Optimization should not be done on main because of globals. Pass depends on CFG & call graph. */
1173
- if (call_map && op_array -> function_name ) {
1174
- zend_dfa_optimize_send_copies (op_array , ssa , call_map );
1198
+ /* Optimization should not be done on main because of globals. */
1199
+ if (op_array -> function_name ) {
1200
+ zend_dfa_optimize_send_copies (op_array , ssa );
1175
1201
}
1176
1202
1177
1203
for (v = op_array -> last_var ; v < ssa -> vars_count ; v ++ ) {
0 commit comments