2929#include "zend_call_graph.h"
3030#include "zend_inference.h"
3131#include "zend_dump.h"
32+ #include "zend_observer.h"
3233
3334#ifndef ZEND_DEBUG_DFA
3435# define ZEND_DEBUG_DFA ZEND_DEBUG
@@ -1034,6 +1035,136 @@ static bool zend_dfa_try_to_replace_result(zend_op_array *op_array, zend_ssa *ss
10341035 return false;
10351036}
10361037
1038+ #define SET_REG_OP_chain_op1 op1_use_chain
1039+ #define SET_REG_OP_chain_op2 op2_use_chain
1040+ #define SET_REG_OP_chain_result res_use_chain
1041+ #define SET_REG_OP (ssa_op , using_op , opline , var , op ) do { \
1042+ zend_ssa_op *_ssa_op = (ssa_op); \
1043+ zend_ssa_op *_using_op = (using_op); \
1044+ zend_op *_opline = (opline); \
1045+ zend_ssa_var *_var = (var); \
1046+ _opline->result_type = IS_REG; \
1047+ (_opline+1)->op ## _type = IS_REG; \
1048+ _ssa_op->result_def = -1; \
1049+ _using_op->op ## _use = -1; \
1050+ ZEND_ASSERT(_using_op->SET_REG_OP_chain_ ## op == -1); \
1051+ _var->use_chain = -1; \
1052+ _var->definition = -1; \
1053+ } while (0)
1054+
1055+ static inline bool zend_supports_reg_result (uint8_t opcode )
1056+ {
1057+ return zend_get_opcode_flags (opcode ) & ZEND_VM_EXT_RETVAL_REG ;
1058+ }
1059+
1060+ static inline int zend_supports_reg_op (uint8_t opcode )
1061+ {
1062+ uint64_t flags = zend_get_opcode_flags (opcode );
1063+
1064+ if ((ZEND_VM_OP1_FLAGS (flags ) & ZEND_VM_OP_REG )) {
1065+ return 1 ;
1066+ }
1067+ if ((ZEND_VM_OP2_FLAGS (flags ) & ZEND_VM_OP_REG )) {
1068+ return 2 ;
1069+ }
1070+
1071+ return 0 ;
1072+ }
1073+
1074+ /* Update op/result type to IS_REG when possible.
1075+ *
1076+ * Right now we support only pairs of subsequent oplines where the first one
1077+ * defines a TMP 'result', the second one uses it via 'op1' or 'op2',
1078+ * and the SSA var is used only once.
1079+ * Chains of INIT_ARRAY/ADD_ARRAY_ELEMENT are not supported (uses 'result').
1080+ * ZEND_VERIFY_RETURN_TYPE is not supported (defines 'op1'). */
1081+ static bool zend_dfa_reg_op_optimize_op (zend_op_array * op_array , uint32_t i , zend_ssa * ssa )
1082+ {
1083+ if (i >= op_array -> last ) {
1084+ return false;
1085+ }
1086+
1087+ zend_op * opline = & op_array -> opcodes [i ];
1088+
1089+ if (opline -> result_type != IS_TMP_VAR ) {
1090+ return false;
1091+ }
1092+
1093+ bool reg_result = zend_supports_reg_result (opline -> opcode );
1094+ int reg_op = zend_supports_reg_op ((opline + 1 )-> opcode );
1095+ if (!reg_result || !reg_op ) {
1096+ return false;
1097+ }
1098+
1099+ zend_ssa_op * ssa_op = & ssa -> ops [i ];
1100+ ZEND_ASSERT (ssa_op -> result_def >= 0 );
1101+ zend_ssa_var * var = & ssa -> vars [ssa_op -> result_def ];
1102+ int use = var -> use_chain ;
1103+ if (use != i + 1 ) {
1104+ return false;
1105+ }
1106+ if (zend_ssa_next_use (ssa -> ops , ssa_op -> result_def , use ) >= 0 ) {
1107+ /* used more than once */
1108+ return false;
1109+ }
1110+
1111+ /* Do not optimize if opline uses the var, as this would create an op type
1112+ * inconsistency */
1113+ if ((ssa_op -> op1_use >= 0 && ssa -> vars [ssa_op -> op1_use ].var == var -> var )
1114+ || (ssa_op -> op2_use >= 0 && ssa -> vars [ssa_op -> op2_use ].var == var -> var )
1115+ || (ssa_op -> result_use >= 0 && ssa -> vars [ssa_op -> result_use ].var == var -> var )) {
1116+ return false;
1117+ }
1118+
1119+ zend_ssa_op * using_op = & ssa -> ops [use ];
1120+
1121+ /* Do not optimize if opline+1 defines the var, as this would create an op
1122+ * type inconsistency */
1123+ if ((using_op -> op1_def >= 0 && ssa -> vars [using_op -> op1_def ].var == var -> var )
1124+ || (using_op -> op2_def >= 0 && ssa -> vars [using_op -> op2_def ].var == var -> var )
1125+ || (using_op -> result_def >= 0 && ssa -> vars [using_op -> result_def ].var == var -> var )) {
1126+ return false;
1127+ }
1128+
1129+ switch (reg_op ) {
1130+ case 1 :
1131+ if (using_op -> op1_use != ssa_op -> result_def ) {
1132+ return false;
1133+ }
1134+ SET_REG_OP (ssa_op , using_op , opline , var , op1 );
1135+ break ;
1136+ case 2 :
1137+ if (using_op -> op2_use != ssa_op -> result_def ) {
1138+ return false;
1139+ }
1140+ SET_REG_OP (ssa_op , using_op , opline , var , op2 );
1141+ break ;
1142+ default :
1143+ ZEND_UNREACHABLE ();
1144+ }
1145+
1146+ return true;
1147+ }
1148+
1149+ static void zend_dfa_reg_op_optimize_op_array (zend_op_array * op_array , zend_optimizer_ctx * ctx , zend_ssa * ssa )
1150+ {
1151+ if (ZEND_OBSERVER_ENABLED ) {
1152+ // TODO
1153+ return ;
1154+ }
1155+ #if ZEND_DEBUG_DFA
1156+ ssa_verify_integrity (op_array , ssa , "before reg op" );
1157+ #endif
1158+
1159+ for (uint32_t i = 0 , last = op_array -> last - 1 ; i < last ; i ++ ) {
1160+ zend_dfa_reg_op_optimize_op (op_array , i , ssa );
1161+ }
1162+
1163+ #if ZEND_DEBUG_DFA
1164+ ssa_verify_integrity (op_array , ssa , "after reg op" );
1165+ #endif
1166+ }
1167+
10371168void zend_dfa_optimize_op_array (zend_op_array * op_array , zend_optimizer_ctx * ctx , zend_ssa * ssa , zend_call_info * * call_map )
10381169{
10391170 if (ctx -> debug_level & ZEND_DUMP_BEFORE_DFA_PASS ) {
@@ -1649,6 +1780,10 @@ void zend_dfa_optimize_op_array(zend_op_array *op_array, zend_optimizer_ctx *ctx
16491780 ssa_verify_integrity (op_array , ssa , "after dfa" );
16501781#endif
16511782
1783+ if (ZEND_OPTIMIZER_PASS_17 & ctx -> optimization_level ) {
1784+ zend_dfa_reg_op_optimize_op_array (op_array , ctx , ssa );
1785+ }
1786+
16521787 if (remove_nops ) {
16531788 zend_ssa_remove_nops (op_array , ssa , ctx );
16541789#if ZEND_DEBUG_DFA
@@ -1660,6 +1795,65 @@ void zend_dfa_optimize_op_array(zend_op_array *op_array, zend_optimizer_ctx *ctx
16601795 if (ctx -> debug_level & ZEND_DUMP_AFTER_DFA_PASS ) {
16611796 zend_dump_op_array (op_array , ZEND_DUMP_SSA , "after dfa pass" , ssa );
16621797 }
1798+
1799+ #if ZEND_DEBUG && 0
1800+ for (uint32_t i = 0 ; op_array -> last && i < op_array -> last - 1 ; i ++ ) {
1801+ fprintf (stderr , "considering op\n" );
1802+ const zend_op * opline = & op_array -> opcodes [i ];
1803+ if (opline -> result_type != IS_TMP_VAR ) {
1804+ continue ;
1805+ }
1806+ if (zend_is_smart_branch (opline )
1807+ && ((opline + 1 )-> opcode == ZEND_JMPZ || (opline + 1 )-> opcode == ZEND_JMPNZ )
1808+ && (opline + 1 )-> op1_type == IS_TMP_VAR && (opline + 1 )-> op1 .var == opline -> result .var ) {
1809+ continue ;
1810+ }
1811+ if (opline -> opcode == ZEND_DO_UCALL || opline -> opcode == ZEND_DO_FCALL
1812+ || opline -> opcode == ZEND_DO_ICALL ) {
1813+ continue ;
1814+ }
1815+ const zend_ssa_op * ssa_op = & ssa -> ops [i ];
1816+ if (ssa_op -> result_def == -1 ) {
1817+ continue ;
1818+ }
1819+ const zend_ssa_var * var = & ssa -> vars [ssa_op -> result_def ];
1820+ int use = var -> use_chain ;
1821+ if (use != i + 1 ) {
1822+ continue ;
1823+ }
1824+ if (zend_ssa_next_use (ssa -> ops , ssa_op -> result_def , use ) >= 0 ) {
1825+ /* Variable is used more than once */
1826+ continue ;
1827+ }
1828+ const zend_ssa_op * using_op = & ssa -> ops [use ];
1829+ const char * operand ;
1830+ if (using_op -> op1_use == ssa_op -> result_def ) {
1831+ operand = "op1" ;
1832+ } else if (using_op -> op2_use == ssa_op -> result_def ) {
1833+ operand = "op2" ;
1834+ } else {
1835+ operand = "res" ;
1836+ }
1837+ const zend_string * fname = op_array -> function_name ;
1838+ if (fname == NULL ) {
1839+ fname = op_array -> filename ;
1840+ }
1841+ const zend_string * cname ;
1842+ if (op_array -> scope ) {
1843+ cname = op_array -> scope -> name ;
1844+ } else {
1845+ cname = NULL ;
1846+ }
1847+ fprintf (stderr , "reg-op candidate: %s::%s %zu %s %s %s\n" ,
1848+ cname ? ZSTR_VAL (cname ) : NULL ,
1849+ ZSTR_VAL (fname ),
1850+ opline - op_array -> opcodes ,
1851+ zend_get_opcode_name (opline -> opcode ),
1852+ zend_get_opcode_name ((opline + 1 )-> opcode ),
1853+ operand );
1854+ }
1855+ #endif
1856+
16631857}
16641858
16651859void zend_optimize_dfa (zend_op_array * op_array , zend_optimizer_ctx * ctx )
0 commit comments