Skip to content

In place string ops #117

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
105 changes: 105 additions & 0 deletions Zend/Optimizer/dfa_pass.c
Original file line number Diff line number Diff line change
Expand Up @@ -1090,6 +1090,103 @@ static bool zend_dfa_try_to_replace_result(zend_op_array *op_array, zend_ssa *ss
return 0;
}

static bool dominates(const zend_basic_block *blocks, int a, int b) {
while (blocks[b].level > blocks[a].level) {
b = blocks[b].idom;
}
return a == b;
}

static bool zend_ssa_is_last_use(const zend_op_array *op_array, const zend_ssa *ssa, int var, int use)
{
int next_use;

if (ssa->vars[var].phi_use_chain) {
zend_ssa_phi *phi = ssa->vars[var].phi_use_chain;
do {
if (!ssa->vars[phi->ssa_var].no_val) {
return 0;
}
phi = zend_ssa_next_use_phi(ssa, var, phi);
} while (phi);
}

if (ssa->cfg.blocks[ssa->cfg.map[use]].loop_header > 0
|| (ssa->cfg.blocks[ssa->cfg.map[use]].flags & ZEND_BB_LOOP_HEADER)) {
int b = ssa->cfg.map[use];
int prev_use = ssa->vars[var].use_chain;
int def_block;

if (ssa->vars[var].definition >= 0) {
def_block =ssa->cfg.map[ssa->vars[var].definition];
} else {
ZEND_ASSERT(ssa->vars[var].definition_phi);
def_block = ssa->vars[var].definition_phi->block;
}
if (dominates(ssa->cfg.blocks, def_block,
(ssa->cfg.blocks[b].flags & ZEND_BB_LOOP_HEADER) ? b : ssa->cfg.blocks[b].loop_header)) {
return 0;
}

while (prev_use >= 0 && prev_use != use) {
if (b != ssa->cfg.map[prev_use]
&& dominates(ssa->cfg.blocks, b, ssa->cfg.map[prev_use])
&& !zend_ssa_is_no_val_use(op_array->opcodes + prev_use, ssa->ops + prev_use, var)) {
return 0;
}
prev_use = zend_ssa_next_use(ssa->ops, var, prev_use);
}
}

next_use = zend_ssa_next_use(ssa->ops, var, use);
if (next_use < 0) {
return 1;
} else if (zend_ssa_is_no_val_use(op_array->opcodes + next_use, ssa->ops + next_use, var)) {
return 1;
}
return 0;
}

/* Sets a flag on SEND ops when a copy can be a avoided. */
static void zend_dfa_optimize_send_copies(zend_op_array *op_array, zend_ssa *ssa)
{
/* func_get_args(), indirect accesses and exceptions could make the optimization observable.
* The latter two cases are already tested before applying the DFA pass. */
ZEND_ASSERT(!op_array->last_try_catch);
ZEND_ASSERT(!(ssa->cfg.flags & ZEND_FUNC_INDIRECT_VAR_ACCESS));
if (ssa->cfg.flags & ZEND_FUNC_VARARG) {
return;
}

for (uint32_t i = 0; i < op_array->last; i++) {
zend_op *opline = op_array->opcodes + i;
if ((opline->opcode != ZEND_SEND_VAR && opline->opcode != ZEND_SEND_VAR_EX)
|| opline->op2_type != IS_UNUSED
|| opline->op1_type != IS_CV) {
continue;
}

// TODO: prevent argument modification

zend_ssa_op *ssa_op = ssa->ops + i;

int ssa_cv = ssa_op->op1_use;

uint32_t type = ssa->var_info[ssa_cv].type;
if ((type & (MAY_BE_REF|MAY_BE_UNDEF|MAY_BE_ANY)) != MAY_BE_STRING || !(type & MAY_BE_RC1)) {
continue;
}

zend_ssa_var *ssa_var = ssa->vars + ssa_cv;
//printf("flags %d %d\n", ssa_var->no_val, ssa_var->alias);
//printf("is last use %d\n", zend_ssa_is_last_use(op_array, ssa, ssa_cv, i));
if (!ssa_var->alias && zend_ssa_is_last_use(op_array, ssa, ssa_cv, i)) { // TODO: don't copy-paste this function pls
//printf("hello %p\n", ssa_var);
opline->extended_value = 1;//TODO: no magic number pls
}
}
}

void zend_dfa_optimize_op_array(zend_op_array *op_array, zend_optimizer_ctx *ctx, zend_ssa *ssa, zend_call_info **call_map)
{
if (ctx->debug_level & ZEND_DUMP_BEFORE_DFA_PASS) {
Expand Down Expand Up @@ -1148,6 +1245,14 @@ void zend_dfa_optimize_op_array(zend_op_array *op_array, zend_optimizer_ctx *ctx
#endif
}

/* Optimization should not be done on main because of globals. */
if (op_array->function_name) {
zend_dfa_optimize_send_copies(op_array, ssa);
#if ZEND_DEBUG_DFA
ssa_verify_integrity(op_array, ssa, "after optimize send copies");
#endif
}

for (v = op_array->last_var; v < ssa->vars_count; v++) {

op_1 = ssa->vars[v].definition;
Expand Down
43 changes: 33 additions & 10 deletions Zend/zend_operators.c
Original file line number Diff line number Diff line change
Expand Up @@ -3059,7 +3059,22 @@ ZEND_API char* ZEND_FASTCALL zend_str_toupper_dup_ex(const char *source, size_t
}
/* }}} */

ZEND_API zend_string* ZEND_FASTCALL zend_string_tolower_ex(zend_string *str, bool persistent) /* {{{ */
static zend_string* ZEND_FASTCALL zend_string_alloc_or_reuse(zend_string *str, bool persistent, bool in_place, const unsigned char *p)
{
if (in_place) {
//printf("in place!\n");
ZEND_ASSERT(!persistent);
zend_string_forget_hash_val(str);
GC_ADDREF(str);
return str;
} else {
zend_string *res = zend_string_alloc(ZSTR_LEN(str), persistent);
memcpy(ZSTR_VAL(res), ZSTR_VAL(str), p - (unsigned char *) ZSTR_VAL(str));
return res;
}
}

ZEND_API zend_string* ZEND_FASTCALL zend_string_tolower_ex2(zend_string *str, bool persistent, bool in_place) /* {{{ */
{
size_t length = ZSTR_LEN(str);
unsigned char *p = (unsigned char *) ZSTR_VAL(str);
Expand All @@ -3070,8 +3085,7 @@ ZEND_API zend_string* ZEND_FASTCALL zend_string_tolower_ex(zend_string *str, boo
while (p + BLOCKCONV_STRIDE <= end) {
BLOCKCONV_LOAD(p);
if (BLOCKCONV_FOUND()) {
zend_string *res = zend_string_alloc(length, persistent);
memcpy(ZSTR_VAL(res), ZSTR_VAL(str), p - (unsigned char *) ZSTR_VAL(str));
zend_string *res = zend_string_alloc_or_reuse(str, persistent, in_place, p);
unsigned char *q = (unsigned char*) ZSTR_VAL(res) + (p - (unsigned char*) ZSTR_VAL(str));

/* Lowercase the chunk we already compared. */
Expand All @@ -3091,8 +3105,7 @@ ZEND_API zend_string* ZEND_FASTCALL zend_string_tolower_ex(zend_string *str, boo

while (p < end) {
if (*p != zend_tolower_ascii(*p)) {
zend_string *res = zend_string_alloc(length, persistent);
memcpy(ZSTR_VAL(res), ZSTR_VAL(str), p - (unsigned char*) ZSTR_VAL(str));
zend_string *res = zend_string_alloc_or_reuse(str, persistent, in_place, p);

unsigned char *q = (unsigned char*) ZSTR_VAL(res) + (p - (unsigned char*) ZSTR_VAL(str));
while (p < end) {
Expand All @@ -3108,7 +3121,13 @@ ZEND_API zend_string* ZEND_FASTCALL zend_string_tolower_ex(zend_string *str, boo
}
/* }}} */

ZEND_API zend_string* ZEND_FASTCALL zend_string_toupper_ex(zend_string *str, bool persistent) /* {{{ */
ZEND_API zend_string* ZEND_FASTCALL zend_string_tolower_ex(zend_string *str, bool persistent) /* {{{ */
{
return zend_string_tolower_ex2(str, persistent, false);
}
/* }}} */

ZEND_API zend_string* ZEND_FASTCALL zend_string_toupper_ex2(zend_string *str, bool persistent, bool in_place) /* {{{ */
{
size_t length = ZSTR_LEN(str);
unsigned char *p = (unsigned char *) ZSTR_VAL(str);
Expand All @@ -3119,8 +3138,7 @@ ZEND_API zend_string* ZEND_FASTCALL zend_string_toupper_ex(zend_string *str, boo
while (p + BLOCKCONV_STRIDE <= end) {
BLOCKCONV_LOAD(p);
if (BLOCKCONV_FOUND()) {
zend_string *res = zend_string_alloc(length, persistent);
memcpy(ZSTR_VAL(res), ZSTR_VAL(str), p - (unsigned char *) ZSTR_VAL(str));
zend_string *res = zend_string_alloc_or_reuse(str, persistent, in_place, p);
unsigned char *q = (unsigned char *) ZSTR_VAL(res) + (p - (unsigned char *) ZSTR_VAL(str));

/* Uppercase the chunk we already compared. */
Expand All @@ -3140,8 +3158,7 @@ ZEND_API zend_string* ZEND_FASTCALL zend_string_toupper_ex(zend_string *str, boo

while (p < end) {
if (*p != zend_toupper_ascii(*p)) {
zend_string *res = zend_string_alloc(length, persistent);
memcpy(ZSTR_VAL(res), ZSTR_VAL(str), p - (unsigned char*) ZSTR_VAL(str));
zend_string *res = zend_string_alloc_or_reuse(str, persistent, in_place, p);

unsigned char *q = (unsigned char *) ZSTR_VAL(res) + (p - (unsigned char *) ZSTR_VAL(str));
while (p < end) {
Expand All @@ -3157,6 +3174,12 @@ ZEND_API zend_string* ZEND_FASTCALL zend_string_toupper_ex(zend_string *str, boo
}
/* }}} */

ZEND_API zend_string* ZEND_FASTCALL zend_string_toupper_ex(zend_string *str, bool persistent) /* {{{ */
{
return zend_string_toupper_ex2(str, persistent, false);
}
/* }}} */

ZEND_API int ZEND_FASTCALL zend_binary_strcmp(const char *s1, size_t len1, const char *s2, size_t len2) /* {{{ */
{
int retval;
Expand Down
6 changes: 4 additions & 2 deletions Zend/zend_operators.h
Original file line number Diff line number Diff line change
Expand Up @@ -473,13 +473,15 @@ ZEND_API char* ZEND_FASTCALL zend_str_toupper_dup(const char *source, siz
ZEND_API char* ZEND_FASTCALL zend_str_tolower_dup_ex(const char *source, size_t length);
ZEND_API char* ZEND_FASTCALL zend_str_toupper_dup_ex(const char *source, size_t length);
ZEND_API zend_string* ZEND_FASTCALL zend_string_tolower_ex(zend_string *str, bool persistent);
ZEND_API zend_string* ZEND_FASTCALL zend_string_tolower_ex2(zend_string *str, bool persistent, bool in_place);
ZEND_API zend_string* ZEND_FASTCALL zend_string_toupper_ex(zend_string *str, bool persistent);
ZEND_API zend_string* ZEND_FASTCALL zend_string_toupper_ex2(zend_string *str, bool persistent, bool in_place);

static zend_always_inline zend_string* zend_string_tolower(zend_string *str) {
return zend_string_tolower_ex(str, false);
return zend_string_tolower_ex2(str, false, false);
}
static zend_always_inline zend_string* zend_string_toupper(zend_string *str) {
return zend_string_toupper_ex(str, false);
return zend_string_toupper_ex2(str, false, false);
}

ZEND_API int ZEND_FASTCALL zend_binary_zval_strcmp(zval *s1, zval *s2);
Expand Down
5 changes: 5 additions & 0 deletions Zend/zend_types.h
Original file line number Diff line number Diff line change
Expand Up @@ -1579,4 +1579,9 @@ static zend_always_inline bool zend_may_modify_arg_in_place(const zval *arg)
return Z_REFCOUNTED_P(arg) && !(GC_FLAGS(Z_COUNTED_P(arg)) & (GC_IMMUTABLE | GC_PERSISTENT)) && Z_REFCOUNT_P(arg) == 1;
}

static zend_always_inline bool zend_may_modify_string_in_place(const zend_string *arg)
{
return !(GC_FLAGS(arg) & (GC_IMMUTABLE | GC_PERSISTENT)) && GC_REFCOUNT(arg) == 1;
}

#endif /* ZEND_TYPES_H */
37 changes: 35 additions & 2 deletions Zend/zend_vm_def.h
Original file line number Diff line number Diff line change
Expand Up @@ -10256,7 +10256,7 @@ ZEND_VM_C_LABEL(fetch_dim_r_index_undef):
ZEND_VM_NEXT_OPCODE_CHECK_EXCEPTION();
}

ZEND_VM_HOT_TYPE_SPEC_HANDLER(ZEND_SEND_VAR, op->op2_type == IS_UNUSED && (op1_info & (MAY_BE_UNDEF|MAY_BE_REF)) == 0, ZEND_SEND_VAR_SIMPLE, CV|VAR, NUM)
ZEND_VM_HOT_TYPE_SPEC_HANDLER(ZEND_SEND_VAR, op->op2_type == IS_UNUSED && !op->extended_value && (op1_info & (MAY_BE_UNDEF|MAY_BE_REF)) == 0, ZEND_SEND_VAR_SIMPLE, CV|VAR, NUM)
{
USE_OPLINE
zval *varptr, *arg;
Expand All @@ -10273,7 +10273,21 @@ ZEND_VM_HOT_TYPE_SPEC_HANDLER(ZEND_SEND_VAR, op->op2_type == IS_UNUSED && (op1_i
ZEND_VM_NEXT_OPCODE();
}

ZEND_VM_HOT_TYPE_SPEC_HANDLER(ZEND_SEND_VAR_EX, op->op2_type == IS_UNUSED && op->op2.num <= MAX_ARG_FLAG_NUM && (op1_info & (MAY_BE_UNDEF|MAY_BE_REF)) == 0, ZEND_SEND_VAR_EX_SIMPLE, CV|VAR, UNUSED|NUM)
ZEND_VM_HOT_TYPE_SPEC_HANDLER(ZEND_SEND_VAR, op->extended_value /* extended_value implies OP2 UNUSED and OP1 not UNDEF or REF */, ZEND_SEND_VAR_SIMPLE_EXT, CV, NUM)
{
USE_OPLINE
zval *varptr, *arg;

varptr = GET_OP1_ZVAL_PTR_UNDEF(BP_VAR_R);
arg = ZEND_CALL_VAR(EX(call), opline->result.var);

ZVAL_COPY_VALUE(arg, varptr);
ZVAL_EMPTY_STRING(varptr);

ZEND_VM_NEXT_OPCODE();
}

ZEND_VM_HOT_TYPE_SPEC_HANDLER(ZEND_SEND_VAR_EX, op->op2_type == IS_UNUSED && !op->extended_value && op->op2.num <= MAX_ARG_FLAG_NUM && (op1_info & (MAY_BE_UNDEF|MAY_BE_REF)) == 0, ZEND_SEND_VAR_EX_SIMPLE, CV|VAR, UNUSED|NUM)
{
USE_OPLINE
zval *varptr, *arg;
Expand All @@ -10295,6 +10309,25 @@ ZEND_VM_HOT_TYPE_SPEC_HANDLER(ZEND_SEND_VAR_EX, op->op2_type == IS_UNUSED && op-
ZEND_VM_NEXT_OPCODE();
}

ZEND_VM_HOT_TYPE_SPEC_HANDLER(ZEND_SEND_VAR_EX, op->extended_value && op->op2.num <= MAX_ARG_FLAG_NUM /* extended_value implies OP2 UNUSED and OP1 not UNDEF or REF */, ZEND_SEND_VAR_EX_SIMPLE_EXT, CV, UNUSED|NUM)
{
USE_OPLINE
zval *varptr, *arg;
uint32_t arg_num = opline->op2.num;

if (QUICK_ARG_SHOULD_BE_SENT_BY_REF(EX(call)->func, arg_num)) {
ZEND_VM_DISPATCH_TO_HANDLER(ZEND_SEND_REF);
}

varptr = GET_OP1_ZVAL_PTR_UNDEF(BP_VAR_R);
arg = ZEND_CALL_VAR(EX(call), opline->result.var);

ZVAL_COPY_VALUE(arg, varptr);
ZVAL_EMPTY_STRING(varptr);

ZEND_VM_NEXT_OPCODE();
}

ZEND_VM_HOT_TYPE_SPEC_HANDLER(ZEND_SEND_VAL, op->op1_type == IS_CONST && op->op2_type == IS_UNUSED && !Z_REFCOUNTED_P(RT_CONSTANT(op, op->op1)), ZEND_SEND_VAL_SIMPLE, CONST, NUM)
{
USE_OPLINE
Expand Down
Loading
Loading