@@ -1066,6 +1066,51 @@ 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
+ /* 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 )
1071
+ {
1072
+ for (int v = 0 ; v < ssa -> vars_count ; v ++ ) {
1073
+ int var = ssa -> vars [v ].var ;
1074
+ if (var >= op_array -> last_var ) {
1075
+ continue ;
1076
+ }
1077
+
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. */
1081
+ if ((type & (MAY_BE_REF |MAY_BE_UNDEF )) || !(type & MAY_BE_RC1 ) || !(type & (MAY_BE_STRING |MAY_BE_ARRAY ))) {
1082
+ continue ;
1083
+ }
1084
+
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 ;
1109
+ }
1110
+ }
1111
+ }
1112
+ }
1113
+
1069
1114
void zend_dfa_optimize_op_array (zend_op_array * op_array , zend_optimizer_ctx * ctx , zend_ssa * ssa , zend_call_info * * call_map )
1070
1115
{
1071
1116
if (ctx -> debug_level & ZEND_DUMP_BEFORE_DFA_PASS ) {
@@ -1124,6 +1169,11 @@ void zend_dfa_optimize_op_array(zend_op_array *op_array, zend_optimizer_ctx *ctx
1124
1169
#endif
1125
1170
}
1126
1171
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 );
1175
+ }
1176
+
1127
1177
for (v = op_array -> last_var ; v < ssa -> vars_count ; v ++ ) {
1128
1178
1129
1179
op_1 = ssa -> vars [v ].definition ;
0 commit comments