Skip to content
Open
Show file tree
Hide file tree
Changes from 6 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
49 changes: 48 additions & 1 deletion Zend/Optimizer/block_pass.c
Original file line number Diff line number Diff line change
Expand Up @@ -431,6 +431,53 @@ static void zend_optimize_block(zend_basic_block *block, zend_op_array *op_array
case ZEND_CASE:
case ZEND_CASE_STRICT:
case ZEND_COPY_TMP:
/* Check for printf optimization from `zend_compile_func_printf()`
* where the result of `printf()` is actually unused and remove the
* superflous COPY_TMP, STRLEN and FREE opcodes:
* T1 = COPY_TMP T0
* ECHO T0
* T2 = STRLEN T1
* FREE T2
*/
if (opline->op1_type == IS_TMP_VAR &&
opline + 1 < end && (opline + 1)->opcode == ZEND_ECHO &&
opline + 2 < end && (opline + 2)->opcode == ZEND_STRLEN &&
opline + 3 < end && (opline + 3)->opcode == ZEND_FREE) {

zend_op *echo_op = opline + 1;
zend_op *strlen_op = opline + 2;
zend_op *free_op = opline + 3;

/* Verify the pattern:
* - ECHO uses the same source as COPY_TMP
* - STRLEN uses the result of COPY_TMP
* - FREE uses the result of STRLEN
*/
if (echo_op->op1_type == IS_TMP_VAR &&
echo_op->op1.var == opline->op1.var &&
strlen_op->op1_type == IS_TMP_VAR &&
strlen_op->op1.var == opline->result.var &&
free_op->op1_type == IS_TMP_VAR &&
free_op->op1.var == strlen_op->result.var) {

/* Remove COPY_TMP, STRLEN, and FREE */
MAKE_NOP(opline);
MAKE_NOP(strlen_op);
MAKE_NOP(free_op);

/* Update source tracking */
if (opline->result_type == IS_TMP_VAR) {
VAR_SOURCE(opline->result) = NULL;
}
if (strlen_op->result_type == IS_TMP_VAR) {
VAR_SOURCE(strlen_op->result) = NULL;
}

++(*opt_count);
break;
}
}

if (opline->op1_type & (IS_TMP_VAR|IS_VAR)) {
/* Variable will be deleted later by FREE, so we can't optimize it */
Tsource[VAR_NUM(opline->op1.var)] = NULL;
Expand Down Expand Up @@ -538,7 +585,7 @@ static void zend_optimize_block(zend_basic_block *block, zend_op_array *op_array
}
}
break;

case ZEND_BOOL:
case ZEND_BOOL_NOT:
optimize_bool:
Expand Down
11 changes: 11 additions & 0 deletions Zend/Optimizer/pass1.c
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@
#include "zend_constants.h"
#include "zend_execute.h"
#include "zend_vm.h"
#include "zend_vm_opcodes.h"

#define TO_STRING_NOWARN(val) do { \
if (Z_TYPE_P(val) < IS_ARRAY) { \
Expand Down Expand Up @@ -264,6 +265,16 @@ void zend_optimizer_pass1(zend_op_array *op_array, zend_optimizer_ctx *ctx)
collect_constants = 0;
break;
}
case ZEND_DO_UCALL:
case ZEND_DO_FCALL:
case ZEND_DO_FCALL_BY_NAME:
case ZEND_FRAMELESS_ICALL_0:
case ZEND_FRAMELESS_ICALL_1:
case ZEND_FRAMELESS_ICALL_2:
case ZEND_FRAMELESS_ICALL_3:
/* don't collect constants after any UCALL/FCALL/FRAMELESS ICALL */
collect_constants = 0;
break;
case ZEND_STRLEN:
if (opline->op1_type == IS_CONST &&
zend_optimizer_eval_strlen(&result, &ZEND_OP1_LITERAL(opline)) == SUCCESS) {
Expand Down
48 changes: 48 additions & 0 deletions Zend/zend_compile.c
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
#include "zend_API.h"
#include "zend_exceptions.h"
#include "zend_interfaces.h"
#include "zend_types.h"
#include "zend_virtual_cwd.h"
#include "zend_multibyte.h"
#include "zend_language_scanner.h"
Expand Down Expand Up @@ -4954,6 +4955,51 @@ static zend_result zend_compile_func_sprintf(znode *result, zend_ast_list *args)
return SUCCESS;
}

static zend_result zend_compile_func_printf(znode *result, zend_ast_list *args) /* {{{ */
{
/* Special case: printf with a single constant string argument and no format specifiers.
* In this case, just emit ECHO and return the string length if needed. */
if (args->children == 1) {
zend_eval_const_expr(&args->child[0]);
if (args->child[0]->kind != ZEND_AST_ZVAL) {
return FAILURE;
}
zval *format_string = zend_ast_get_zval(args->child[0]);
if (Z_TYPE_P(format_string) != IS_STRING) {
return FAILURE;
}
/* Check if there are any format specifiers */
if (!memchr(Z_STRVAL_P(format_string), '%', Z_STRLEN_P(format_string))) {
/* No format specifiers - just emit ECHO and return string length */
znode format_node;
zend_compile_expr(&format_node, args->child[0]);
zend_emit_op(NULL, ZEND_ECHO, &format_node, NULL);

/* Return the string length as a constant if the result is used */
result->op_type = IS_CONST;
ZVAL_LONG(&result->u.constant, Z_STRLEN_P(format_string));
return SUCCESS;
}
}

/* Fall back to sprintf optimization for format strings with specifiers */
znode rope_result;
if (zend_compile_func_sprintf(&rope_result, args) != SUCCESS) {
return FAILURE;
}

/* printf() returns the amount of bytes written, so just an ECHO of the resulting sprintf()
* optimisation might not be enough. At this early stage we can't detect if the result is
* actually used, so we just emit the opcodes and cleanup if they are not used in the
* optimizers block pass later. */
znode copy;
zend_emit_op_tmp(&copy, ZEND_COPY_TMP, &rope_result, NULL);
zend_emit_op(NULL, ZEND_ECHO, &rope_result, NULL);
zend_emit_op_tmp(result, ZEND_STRLEN, &copy, NULL);

return SUCCESS;
}

static zend_result zend_compile_func_clone(znode *result, zend_ast_list *args)
{
znode arg_node;
Expand Down Expand Up @@ -5036,6 +5082,8 @@ static zend_result zend_try_compile_special_func_ex(znode *result, zend_string *
return zend_compile_func_array_key_exists(result, args);
} else if (zend_string_equals_literal(lcname, "sprintf")) {
return zend_compile_func_sprintf(result, args);
} else if (zend_string_equals_literal(lcname, "printf")) {
return zend_compile_func_printf(result, args);
} else if (zend_string_equals(lcname, ZSTR_KNOWN(ZEND_STR_CLONE))) {
return zend_compile_func_clone(result, args);
} else {
Expand Down