Skip to content

Commit 0334b75

Browse files
committed
Implicit REG operand / result
1 parent b2a3690 commit 0334b75

15 files changed

Lines changed: 67068 additions & 34410 deletions

Zend/Optimizer/dfa_pass.c

Lines changed: 194 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@
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+
10371168
void 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

16651859
void zend_optimize_dfa(zend_op_array *op_array, zend_optimizer_ctx *ctx)

Zend/Optimizer/zend_dump.c

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -163,6 +163,8 @@ ZEND_API void zend_dump_var(const zend_op_array *op_array, uint8_t var_type, uin
163163
fprintf(stderr, "V%d", var_num);
164164
} else if ((var_type & (IS_VAR|IS_TMP_VAR)) == IS_TMP_VAR) {
165165
fprintf(stderr, "T%d", var_num);
166+
} else if (var_type == IS_REG) {
167+
fprintf(stderr, "REG");
166168
} else {
167169
fprintf(stderr, "X%d", var_num);
168170
}
@@ -464,7 +466,7 @@ ZEND_API void zend_dump_op(const zend_op_array *op_array, const zend_basic_block
464466
uint32_t n = 0;
465467

466468
if (!ssa_op || ssa_op->result_use < 0) {
467-
if (opline->result_type & (IS_CV|IS_VAR|IS_TMP_VAR)) {
469+
if (opline->result_type & (IS_CV|IS_VAR|IS_TMP_VAR|IS_REG)) {
468470
if (ssa_op && ssa_op->result_def >= 0) {
469471
int ssa_var_num = ssa_op->result_def;
470472
zend_dump_ssa_var(op_array, ssa, ssa_var_num, opline->result_type, EX_VAR_TO_NUM(opline->result.var), dump_flags);
@@ -639,7 +641,7 @@ ZEND_API void zend_dump_op(const zend_op_array *op_array, const zend_basic_block
639641

640642
if (opline->op1_type == IS_CONST) {
641643
zend_dump_const(CRT_CONSTANT(opline->op1));
642-
} else if (opline->op1_type & (IS_CV|IS_VAR|IS_TMP_VAR)) {
644+
} else if (opline->op1_type & (IS_CV|IS_VAR|IS_TMP_VAR|IS_REG)) {
643645
if (ssa_op) {
644646
int ssa_var_num = ssa_op->op1_use;
645647
if (ssa_var_num >= 0) {
@@ -700,7 +702,7 @@ ZEND_API void zend_dump_op(const zend_op_array *op_array, const zend_basic_block
700702
} else {
701703
zend_dump_const(op);
702704
}
703-
} else if (opline->op2_type & (IS_CV|IS_VAR|IS_TMP_VAR)) {
705+
} else if (opline->op2_type & (IS_CV|IS_VAR|IS_TMP_VAR|IS_REG)) {
704706
if (ssa_op) {
705707
int ssa_var_num = ssa_op->op2_use;
706708
if (ssa_var_num >= 0) {
@@ -752,7 +754,7 @@ ZEND_API void zend_dump_op(const zend_op_array *op_array, const zend_basic_block
752754
fprintf(stderr, " jmpnz");
753755
#endif
754756
} else if (ssa_op && ssa_op->result_use >= 0) {
755-
if (opline->result_type & (IS_CV|IS_VAR|IS_TMP_VAR)) {
757+
if (opline->result_type & (IS_CV|IS_VAR|IS_TMP_VAR|IS_REG)) {
756758
if (ssa_op) {
757759
int ssa_var_num = ssa_op->result_use;
758760
if (ssa_var_num >= 0) {

Zend/Optimizer/zend_optimizer.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@
4141
#define ZEND_OPTIMIZER_PASS_14 (1<<13) /* DCE (dead code elimination) */
4242
#define ZEND_OPTIMIZER_PASS_15 (1<<14) /* (unsafe) Collect constants */
4343
#define ZEND_OPTIMIZER_PASS_16 (1<<15) /* Inline functions */
44+
#define ZEND_OPTIMIZER_PASS_17 (1<<18) /* Reg operands */
4445

4546
#define ZEND_OPTIMIZER_IGNORE_OVERLOADING (1<<16) /* (unsafe) Ignore possibility of operator overloading */
4647

@@ -65,6 +66,7 @@
6566
#define ZEND_DUMP_AFTER_PASS_12 ZEND_OPTIMIZER_PASS_12
6667
#define ZEND_DUMP_AFTER_PASS_13 ZEND_OPTIMIZER_PASS_13
6768
#define ZEND_DUMP_AFTER_PASS_14 ZEND_OPTIMIZER_PASS_14
69+
#define ZEND_DUMP_AFTER_PASS_17 (1<<30)
6870

6971
#define ZEND_DUMP_BEFORE_OPTIMIZER (1<<16)
7072
#define ZEND_DUMP_AFTER_OPTIMIZER (1<<17)

Zend/zend_compile.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -864,6 +864,7 @@ ZEND_STATIC_ASSERT(ZEND_MM_ALIGNED_SIZE(sizeof(zval)) == sizeof(zval),
864864
#define IS_TMP_VAR (1<<1)
865865
#define IS_VAR (1<<2)
866866
#define IS_CV (1<<3) /* Compiled variable */
867+
#define IS_REG (1<<4) /* Implicit register operand. IS_TMP_VAR semantics. */
867868

868869
/* Used for result.type of smart branch instructions */
869870
#define IS_SMART_BRANCH_JMPZ (1<<4)

Zend/zend_execute.c

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -115,6 +115,7 @@
115115
#define _VAR_CODE 2
116116
#define _UNUSED_CODE 3
117117
#define _CV_CODE 4
118+
#define _REG_CODE 5
118119

119120
typedef int (ZEND_FASTCALL *incdec_t)(zval *);
120121

@@ -5891,6 +5892,8 @@ static zend_always_inline zend_execute_data *_zend_vm_stack_push_call_frame(uint
58915892
#define UNDEF_RESULT() do { \
58925893
if (opline->result_type & (IS_VAR | IS_TMP_VAR)) { \
58935894
ZVAL_UNDEF(EX_VAR(opline->result.var)); \
5895+
} else if (opline->result_type == IS_REG) { \
5896+
ZVAL_UNDEF(&reg); \
58945897
} \
58955898
} while (0)
58965899

Zend/zend_operators.c

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2904,9 +2904,8 @@ ZEND_API bool ZEND_FASTCALL zend_is_true(const zval *op) /* {{{ */
29042904
}
29052905
/* }}} */
29062906

2907-
ZEND_API bool ZEND_FASTCALL zend_object_is_true(const zval *op) /* {{{ */
2907+
ZEND_API bool ZEND_FASTCALL zend_object_is_true(zend_object *zobj) /* {{{ */
29082908
{
2909-
zend_object *zobj = Z_OBJ_P(op);
29102909
zval tmp;
29112910
if (zobj->handlers->cast_object(zobj, &tmp, _IS_BOOL) == SUCCESS) {
29122911
return Z_TYPE(tmp) == IS_TRUE;

Zend/zend_operators.h

Lines changed: 50 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -392,7 +392,7 @@ static zend_always_inline bool try_convert_to_string(zval *op) {
392392

393393

394394
ZEND_API bool ZEND_FASTCALL zend_is_true(const zval *op);
395-
ZEND_API bool ZEND_FASTCALL zend_object_is_true(const zval *op);
395+
ZEND_API bool ZEND_FASTCALL zend_object_is_true(zend_object *zobj);
396396

397397
static zend_always_inline bool i_zend_is_true(const zval *op)
398398
{
@@ -430,7 +430,7 @@ static zend_always_inline bool i_zend_is_true(const zval *op)
430430
if (EXPECTED(Z_OBJ_HT_P(op)->cast_object == zend_std_cast_object_tostring)) {
431431
result = 1;
432432
} else {
433-
result = zend_object_is_true(op);
433+
result = zend_object_is_true(Z_OBJ_P(op));
434434
}
435435
break;
436436
case IS_RESOURCE:
@@ -794,6 +794,30 @@ overflow: ZEND_ATTRIBUTE_COLD_LABEL
794794
#endif
795795
}
796796

797+
static zend_always_inline zval fast_long_add_function_reg(zval *op1, zval *op2)
798+
{
799+
#if ZEND_USE_ASM_ARITHMETIC && defined(__x86_64__)
800+
zval result;
801+
__asm__ goto(
802+
"movq (%1), %%rax\n\t"
803+
"addq (%2), %%rax\n\t"
804+
"jo %l3\n\t"
805+
"movq %%rax, %0\n\t"
806+
: "=r"(result.value)
807+
: "r"(&op1->value),
808+
"r"(&op2->value)
809+
: "rax","cc", "memory"
810+
: overflow);
811+
result.u1.type_info = IS_LONG;
812+
return result;
813+
overflow: ZEND_ATTRIBUTE_COLD_LABEL
814+
ZVAL_DOUBLE(&result, (double) Z_LVAL_P(op1) + (double) Z_LVAL_P(op2));
815+
return result;
816+
#else
817+
# error TODO
818+
#endif
819+
}
820+
797821
static zend_always_inline void fast_long_sub_function(zval *result, zval *op1, zval *op2)
798822
{
799823
#if ZEND_USE_ASM_ARITHMETIC && defined(__i386__) && !(4 == __GNUC__ && 8 == __GNUC_MINOR__)
@@ -898,6 +922,30 @@ overflow: ZEND_ATTRIBUTE_COLD_LABEL
898922
#endif
899923
}
900924

925+
static zend_always_inline zval fast_long_sub_function_reg(zval *op1, zval *op2)
926+
{
927+
#if ZEND_USE_ASM_ARITHMETIC && defined(__x86_64__)
928+
zval result;
929+
__asm__ goto(
930+
"movq (%1), %%rax\n\t"
931+
"subq (%2), %%rax\n\t"
932+
"jo %l3\n\t"
933+
"movq %%rax, %0\n\t"
934+
: "=r"(result.value)
935+
: "r"(&op1->value),
936+
"r"(&op2->value)
937+
: "rax","cc", "memory"
938+
: overflow);
939+
result.u1.type_info = IS_LONG;
940+
return result;
941+
overflow: ZEND_ATTRIBUTE_COLD_LABEL
942+
ZVAL_DOUBLE(&result, (double) Z_LVAL_P(op1) - (double) Z_LVAL_P(op2));
943+
return result;
944+
#else
945+
# error TODO
946+
#endif
947+
}
948+
901949
static zend_always_inline bool zend_fast_equal_strings(zend_string *s1, zend_string *s2)
902950
{
903951
if (s1 == s2) {

0 commit comments

Comments
 (0)