Skip to content

Use ropes for strings concatenation (disables do_operation(ZEND_CONCAT)). #1195

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

Closed
wants to merge 2 commits into from
Closed
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
55 changes: 42 additions & 13 deletions Zend/zend_ast.c
Original file line number Diff line number Diff line change
Expand Up @@ -223,6 +223,19 @@ ZEND_API void zend_ast_evaluate(zval *result, zend_ast *ast, zend_class_entry *s
zval_dtor(&op2);
break;
}
case ZEND_AST_CONCAT_LIST:
{
uint32_t i;
zend_ast_list *list = zend_ast_get_list(ast);

ZVAL_EMPTY_STRING(result);
for (i = 0; i < list->children; ++i) {
zend_ast_evaluate(&op1, list->child[i], scope);
concat_function(result, result, &op1);
zval_dtor(&op1);
}
break;
}
case ZEND_AST_GREATER:
case ZEND_AST_GREATER_EQUAL:
{
Expand Down Expand Up @@ -650,18 +663,27 @@ static void zend_ast_export_list(smart_str *str, zend_ast_list *list, int separa
}
}

static void zend_ast_export_encaps_list(smart_str *str, char quote, zend_ast_list *list, int indent)
static void zend_ast_export_concat_list(smart_str *str, char quote, zend_ast_list *list, int indent)
{
uint32_t i = 0;
zend_ast *ast;

if (list->attr != ZEND_CONCAT) {
smart_str_appendc(str, quote);
}
while (i < list->children) {
ast = list->child[i];
if (ast->kind == ZEND_AST_ZVAL) {
zval *zv = zend_ast_get_zval(ast);

ZEND_ASSERT(Z_TYPE_P(zv) == IS_STRING);
if (list->attr == ZEND_CONCAT) {
smart_str_appendc(str, '\'');
}
zend_ast_export_qstr(str, quote, Z_STR_P(zv));
if (list->attr == ZEND_CONCAT) {
smart_str_appendc(str, '\'');
}
} else if (ast->kind == ZEND_AST_VAR &&
ast->child[0]->kind == ZEND_AST_ZVAL &&
(i + 1 == list->children ||
Expand All @@ -671,11 +693,21 @@ static void zend_ast_export_encaps_list(smart_str *str, char quote, zend_ast_lis
zend_ast_get_zval(list->child[i + 1]))))) {
zend_ast_export_ex(str, ast, 0, indent);
} else {
smart_str_appendc(str, '{');
if (list->attr != ZEND_CONCAT) {
smart_str_appendc(str, '{');
}
zend_ast_export_ex(str, ast, 0, indent);
smart_str_appendc(str, '}');
if (list->attr != ZEND_CONCAT) {
smart_str_appendc(str, '}');
}
}
i++;
if (++i < list->children && list->attr == ZEND_CONCAT) {
smart_str_appends(str, " . ");
}
}

if (list->attr != ZEND_CONCAT) {
smart_str_appendc(str, quote);
}
}

Expand Down Expand Up @@ -1000,10 +1032,8 @@ static void zend_ast_export_ex(smart_str *str, zend_ast *ast, int priority, int
zend_ast_export_list(str, (zend_ast_list*)ast, 1, 20, indent);
smart_str_appendc(str, ']');
break;
case ZEND_AST_ENCAPS_LIST:
smart_str_appendc(str, '"');
zend_ast_export_encaps_list(str, '"', (zend_ast_list*)ast, indent);
smart_str_appendc(str, '"');
case ZEND_AST_CONCAT_LIST:
zend_ast_export_concat_list(str, '"', (zend_ast_list*)ast, indent);
break;
case ZEND_AST_STMT_LIST:
case ZEND_AST_TRAIT_ADAPTATIONS:
Expand Down Expand Up @@ -1107,13 +1137,13 @@ static void zend_ast_export_ex(smart_str *str, zend_ast *ast, int priority, int
case ZEND_AST_SILENCE:
PREFIX_OP("@", 240, 241);
case ZEND_AST_SHELL_EXEC:
smart_str_appendc(str, '`');
if (ast->child[0]->kind == ZEND_AST_ENCAPS_LIST) {
zend_ast_export_encaps_list(str, '`', (zend_ast_list*)ast->child[0], indent);
if (ast->child[0]->kind == ZEND_AST_CONCAT_LIST) {
zend_ast_export_concat_list(str, '`', (zend_ast_list*)ast->child[0], indent);
} else {
smart_str_appendc(str, '`');
zend_ast_export_ex(str, ast->child[0], 0, indent);
smart_str_appendc(str, '`');
}
smart_str_appendc(str, '`');
break;
case ZEND_AST_CLONE:
PREFIX_OP("clone ", 270, 271);
Expand Down Expand Up @@ -1239,7 +1269,6 @@ static void zend_ast_export_ex(smart_str *str, zend_ast *ast, int priority, int
case ZEND_MOD: BINARY_OP(" % ", 210, 210, 211);
case ZEND_SL: BINARY_OP(" << ", 190, 190, 191);
case ZEND_SR: BINARY_OP(" >> ", 190, 190, 191);
case ZEND_CONCAT: BINARY_OP(" . ", 200, 200, 201);
case ZEND_BW_OR: BINARY_OP(" | ", 140, 140, 141);
case ZEND_BW_AND: BINARY_OP(" & ", 160, 160, 161);
case ZEND_BW_XOR: BINARY_OP(" ^ ", 150, 150, 151);
Expand Down
8 changes: 6 additions & 2 deletions Zend/zend_ast.h
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ enum _zend_ast_kind {
ZEND_AST_ARG_LIST = 1 << ZEND_AST_IS_LIST_SHIFT,
ZEND_AST_LIST,
ZEND_AST_ARRAY,
ZEND_AST_ENCAPS_LIST,
ZEND_AST_CONCAT_LIST,
ZEND_AST_EXPR_LIST,
ZEND_AST_STMT_LIST,
ZEND_AST_IF,
Expand Down Expand Up @@ -267,5 +267,9 @@ static zend_always_inline zend_ast *zend_ast_create_assign_op(uint32_t opcode, z
static zend_always_inline zend_ast *zend_ast_create_cast(uint32_t type, zend_ast *op0) {
return zend_ast_create_ex(ZEND_AST_CAST, type, op0);
}

static zend_always_inline zend_ast *zend_ast_create_concat_list(uint32_t opcode, zend_ast *op0) {
zend_ast *ast = zend_ast_create_list(1, ZEND_AST_CONCAT_LIST, op0);
ast->attr = opcode;
return ast;
}
#endif
150 changes: 112 additions & 38 deletions Zend/zend_compile.c
Original file line number Diff line number Diff line change
Expand Up @@ -6231,55 +6231,129 @@ void zend_compile_resolve_class_name(znode *result, zend_ast *ast) /* {{{ */
}
/* }}} */

void zend_compile_encaps_list(znode *result, zend_ast *ast) /* {{{ */
static zend_op *zend_compile_rope_add(znode *result, uint32_t num, znode *elem_node) /* {{{ */
{
zend_ast_list *list = zend_ast_get_list(ast);
uint32_t i;
zend_op *opline = get_next_op(CG(active_op_array));

ZEND_ASSERT(list->children > 0);
if (num == 0) {
result->op_type = IS_TMP_VAR;
result->u.op.var = -1;
opline->opcode = ZEND_ROPE_INIT;
SET_UNUSED(opline->op1);
} else {
opline->opcode = ZEND_ROPE_ADD;
SET_NODE(opline->op1, result);
}
SET_NODE(opline->op2, elem_node);
SET_NODE(opline->result, result);
opline->extended_value = num;
return opline;
}
/* }}} */

result->op_type = IS_TMP_VAR;
result->u.op.var = get_temporary_variable(CG(active_op_array));
static void zend_compile_concat_list(znode *result, zend_ast *ast) /* {{{ */
{
uint32_t i, j;
uint32_t rope_init_lineno = -1;
zend_op *opline = NULL, *init_opline;
znode elem_node, last_const_node;
zend_ast_list *list = zend_ast_get_list(ast);

for (i = 0; i < list->children; ++i) {
zend_ast *elem_ast = list->child[i];
znode elem_node;
zend_op *opline;
ZEND_ASSERT(list->children > 0);

zend_compile_expr(&elem_node, elem_ast);
j = 0;
last_const_node.op_type = IS_UNUSED;
for (i = 0; i < list->children; i++) {
zend_compile_expr(&elem_node, list->child[i]);

if (elem_ast->kind == ZEND_AST_ZVAL) {
zval *zv = &elem_node.u.constant;
ZEND_ASSERT(Z_TYPE_P(zv) == IS_STRING);
if (elem_node.op_type == IS_CONST) {
convert_to_string(&elem_node.u.constant);

if (Z_STRLEN_P(zv) > 1) {
opline = get_next_op(CG(active_op_array));
opline->opcode = ZEND_ADD_STRING;
} else if (Z_STRLEN_P(zv) == 1) {
char ch = *Z_STRVAL_P(zv);
zend_string_release(Z_STR_P(zv));
ZVAL_LONG(zv, ch);

opline = get_next_op(CG(active_op_array));
opline->opcode = ZEND_ADD_CHAR;
if (Z_STRLEN(elem_node.u.constant) == 0) {
zval_ptr_dtor(&elem_node.u.constant);
} else if (last_const_node.op_type == IS_CONST) {
concat_function(&last_const_node.u.constant, &last_const_node.u.constant, &elem_node.u.constant);
zval_ptr_dtor(&elem_node.u.constant);
} else {
/* String can be empty after a variable at the end of a heredoc */
zend_string_release(Z_STR_P(zv));
continue;
last_const_node.op_type = IS_CONST;
ZVAL_COPY_VALUE(&last_const_node.u.constant, &elem_node.u.constant);
}
continue;
} else {
opline = get_next_op(CG(active_op_array));
opline->opcode = ZEND_ADD_VAR;
ZEND_ASSERT(elem_node.op_type != IS_CONST);
if (j == 0) {
rope_init_lineno = get_next_op_number(CG(active_op_array));
}
if (last_const_node.op_type == IS_CONST) {
zend_compile_rope_add(result, j++, &last_const_node);
last_const_node.op_type = IS_UNUSED;
}
opline = zend_compile_rope_add(result, j++, &elem_node);
}
}

if (i == 0) {
SET_UNUSED(opline->op1);
if (j == 0) {
result->op_type = IS_CONST;
if (last_const_node.op_type == IS_CONST) {
ZVAL_COPY_VALUE(&result->u.constant, &last_const_node.u.constant);
} else {
ZVAL_EMPTY_STRING(&result->u.constant);
/* empty string */
}
return;
} else if (last_const_node.op_type == IS_CONST) {
opline = zend_compile_rope_add(result, j++, &last_const_node);
}
init_opline = CG(active_op_array)->opcodes + rope_init_lineno;
if (j == 1) {
if (opline->op2_type == IS_CONST) {
GET_NODE(result, opline->op2);
MAKE_NOP(opline);
} else {
SET_NODE(opline->op1, result);
opline->opcode = ZEND_CAST;
opline->extended_value = IS_STRING;
opline->op1_type = opline->op2_type;
opline->op1 = opline->op2;
opline->result_type = IS_TMP_VAR;
opline->result.var = get_temporary_variable(CG(active_op_array));
SET_UNUSED(opline->op2);
GET_NODE(result, opline->result);
}
} else if (j == 2) {
opline->opcode = ZEND_FAST_CONCAT;
opline->extended_value = 0;
opline->op1_type = init_opline->op2_type;
opline->op1 = init_opline->op2;
opline->result_type = IS_TMP_VAR;
opline->result.var = get_temporary_variable(CG(active_op_array));
MAKE_NOP(init_opline);
GET_NODE(result, opline->result);
} else {
uint32_t var;

init_opline->extended_value = j;
opline->opcode = ZEND_ROPE_END;
opline->result.var = get_temporary_variable(CG(active_op_array));
var = opline->op1.var = get_temporary_variable(CG(active_op_array));
GET_NODE(result, opline->result);

/* Allocates the necessary number of zval slots to keep the rope */
i = ((j * sizeof(zend_string*)) + (sizeof(zval) - 1)) / sizeof(zval);
while (i > 1) {
get_temporary_variable(CG(active_op_array));
i--;
}
/* Update all the previous opcodes to use the same variable */
while (opline != init_opline) {
opline--;
if (opline->opcode == ZEND_ROPE_ADD &&
opline->result.var == -1) {
opline->op1.var = var;
opline->result.var = var;
} else if (opline->opcode == ZEND_ROPE_INIT &&
opline->result.var == -1) {
opline->result.var = var;
}
}
SET_NODE(opline->op2, &elem_node);
SET_NODE(opline->result, result);
}
}
/* }}} */
Expand All @@ -6304,7 +6378,7 @@ zend_bool zend_is_allowed_in_const_expr(zend_ast_kind kind) /* {{{ */
return kind == ZEND_AST_ZVAL || kind == ZEND_AST_BINARY_OP
|| kind == ZEND_AST_GREATER || kind == ZEND_AST_GREATER_EQUAL
|| kind == ZEND_AST_AND || kind == ZEND_AST_OR
|| kind == ZEND_AST_UNARY_OP
|| kind == ZEND_AST_UNARY_OP || kind == ZEND_AST_CONCAT_LIST
|| kind == ZEND_AST_UNARY_PLUS || kind == ZEND_AST_UNARY_MINUS
|| kind == ZEND_AST_CONDITIONAL || kind == ZEND_AST_DIM
|| kind == ZEND_AST_ARRAY || kind == ZEND_AST_ARRAY_ELEM
Expand Down Expand Up @@ -6731,8 +6805,8 @@ void zend_compile_expr(znode *result, zend_ast *ast) /* {{{ */
case ZEND_AST_RESOLVE_CLASS_NAME:
zend_compile_resolve_class_name(result, ast);
return;
case ZEND_AST_ENCAPS_LIST:
zend_compile_encaps_list(result, ast);
case ZEND_AST_CONCAT_LIST:
zend_compile_concat_list(result, ast);
return;
case ZEND_AST_MAGIC_CONST:
zend_compile_magic_const(result, ast);
Expand Down
8 changes: 5 additions & 3 deletions Zend/zend_language_parser.y
Original file line number Diff line number Diff line change
Expand Up @@ -847,7 +847,6 @@ expr_without_variable:
| expr '|' expr { $$ = zend_ast_create_binary_op(ZEND_BW_OR, $1, $3); }
| expr '&' expr { $$ = zend_ast_create_binary_op(ZEND_BW_AND, $1, $3); }
| expr '^' expr { $$ = zend_ast_create_binary_op(ZEND_BW_XOR, $1, $3); }
| expr '.' expr { $$ = zend_ast_create_binary_op(ZEND_CONCAT, $1, $3); }
| expr '+' expr { $$ = zend_ast_create_binary_op(ZEND_ADD, $1, $3); }
| expr '-' expr { $$ = zend_ast_create_binary_op(ZEND_SUB, $1, $3); }
| expr '*' expr { $$ = zend_ast_create_binary_op(ZEND_MUL, $1, $3); }
Expand All @@ -856,6 +855,9 @@ expr_without_variable:
| expr '%' expr { $$ = zend_ast_create_binary_op(ZEND_MOD, $1, $3); }
| expr T_SL expr { $$ = zend_ast_create_binary_op(ZEND_SL, $1, $3); }
| expr T_SR expr { $$ = zend_ast_create_binary_op(ZEND_SR, $1, $3); }
| expr '.' expr { $$ = ($$->kind == ZEND_AST_CONCAT_LIST)?
zend_ast_list_add($1, $3):
zend_ast_list_add(zend_ast_create_concat_list(ZEND_CONCAT, $1), $3); }
| '+' expr %prec T_INC { $$ = zend_ast_create(ZEND_AST_UNARY_PLUS, $2); }
| '-' expr %prec T_INC { $$ = zend_ast_create(ZEND_AST_UNARY_MINUS, $2); }
| '!' expr { $$ = zend_ast_create_ex(ZEND_AST_UNARY_OP, ZEND_BOOL_NOT, $2); }
Expand Down Expand Up @@ -1151,9 +1153,9 @@ encaps_list:
| encaps_list T_ENCAPSED_AND_WHITESPACE
{ $$ = zend_ast_list_add($1, $2); }
| encaps_var
{ $$ = zend_ast_create_list(1, ZEND_AST_ENCAPS_LIST, $1); }
{ $$ = zend_ast_create_concat_list(0, $1); }
| T_ENCAPSED_AND_WHITESPACE encaps_var
{ $$ = zend_ast_create_list(2, ZEND_AST_ENCAPS_LIST, $1, $2); }
{ $$ = zend_ast_list_add(zend_ast_create_concat_list(0, $1), $2); }
;

encaps_var:
Expand Down
1 change: 1 addition & 0 deletions Zend/zend_opcode.c
Original file line number Diff line number Diff line change
Expand Up @@ -861,6 +861,7 @@ ZEND_API binary_op_type get_binary_op(int opcode)
case ZEND_SR:
case ZEND_ASSIGN_SR:
return (binary_op_type) shift_right_function;
case ZEND_FAST_CONCAT:
case ZEND_CONCAT:
case ZEND_ASSIGN_CONCAT:
return (binary_op_type) concat_function;
Expand Down
Loading