From 80ceda5a967c2108b609e9525b81ae2d3c8a1fe5 Mon Sep 17 00:00:00 2001 From: Tyson Andre Date: Wed, 13 Nov 2019 19:25:28 -0500 Subject: [PATCH] Optimize `false === is_string($x)` in opcache This optimization acts on opcodes that are guaranteed to return booleans. (It's based on the below case for ZEND_BOOL and ZEND_BOOL_NOT) Code such as `return is_string($this->prop) === false;` will benefit from it when opcache is enabled. A contrived example of a worst-case scenario is below. ```php // Before this PR: Took 0.459 seconds // After this PR: Takes 0.362 seconds loop_is_float(0, 20000000); function loop_is_float($a, $b) { $values = []; for ($i = $a; $i < $b; $i++) { $values[false === is_float($i)] = true; } return $values; } ``` This PR uses bit operations, so that this optimization will also work for `false !== is_string($x)` and any future combinations generated for ZEND_TYPE_CHECK. Similar to GH-4906 (can be merged independently) --- Zend/Optimizer/block_pass.c | 131 ++++++++++++++++++++++++++++++++++++ 1 file changed, 131 insertions(+) diff --git a/Zend/Optimizer/block_pass.c b/Zend/Optimizer/block_pass.c index 61c8f420aa43f..8eb4e99074d75 100644 --- a/Zend/Optimizer/block_pass.c +++ b/Zend/Optimizer/block_pass.c @@ -441,6 +441,137 @@ static void zend_optimize_block(zend_basic_block *block, zend_op_array *op_array } break; + case ZEND_TYPE_CHECK: + /* Optimize checks such as e === true, e !== true, e !== false, etc. when e is from an opcode that only results in booleans */ + /* If opcache combines ZEND_TYPE_CHECKs into is_true_or_string($x) in the future, this is designed to properly handle that. */ + if (((opline->extended_value & MAY_BE_FALSE) != 0) ^ ((opline->extended_value & MAY_BE_TRUE) != 0)) { + if (opline->op1_type == IS_TMP_VAR && + !zend_bitset_in(used_ext, VAR_NUM(opline->op1.var))) { + src = VAR_SOURCE(opline->op1); + if (src) { + switch (src->opcode) { + case ZEND_BOOL_NOT: + VAR_SOURCE(opline->op1) = NULL; + COPY_NODE(opline->op1, src->op1); + opline->opcode = (opline->extended_value & MAY_BE_FALSE) ? ZEND_BOOL : ZEND_BOOL_NOT; + MAKE_NOP(src); + ++(*opt_count); + goto optimize_bool; + case ZEND_BOOL: + VAR_SOURCE(opline->op1) = NULL; + COPY_NODE(opline->op1, src->op1); + opline->opcode = (opline->extended_value & MAY_BE_FALSE) ? ZEND_BOOL_NOT : ZEND_BOOL; + MAKE_NOP(src); + ++(*opt_count); + goto optimize_bool; + case ZEND_IS_EQUAL: + if (opline->extended_value & MAY_BE_FALSE) { + src->opcode = ZEND_IS_NOT_EQUAL; + } + COPY_NODE(src->result, opline->result); + SET_VAR_SOURCE(src); + MAKE_NOP(opline); + ++(*opt_count); + break; + case ZEND_IS_NOT_EQUAL: + if (opline->extended_value & MAY_BE_FALSE) { + src->opcode = ZEND_IS_EQUAL; + } + COPY_NODE(src->result, opline->result); + SET_VAR_SOURCE(src); + MAKE_NOP(opline); + ++(*opt_count); + break; + case ZEND_IS_IDENTICAL: + if (opline->extended_value & MAY_BE_FALSE) { + src->opcode = ZEND_IS_NOT_IDENTICAL; + } + COPY_NODE(src->result, opline->result); + SET_VAR_SOURCE(src); + MAKE_NOP(opline); + ++(*opt_count); + break; + case ZEND_IS_NOT_IDENTICAL: + if (opline->extended_value & MAY_BE_FALSE) { + src->opcode = ZEND_IS_IDENTICAL; + } + COPY_NODE(src->result, opline->result); + SET_VAR_SOURCE(src); + MAKE_NOP(opline); + ++(*opt_count); + break; + case ZEND_TYPE_CHECK: + if (opline->extended_value & MAY_BE_FALSE) { + if (src->extended_value == MAY_BE_RESOURCE || src->extended_value == (MAY_BE_ANY - MAY_BE_RESOURCE)) { + /* is_resource() is a special case - it returns false if the resource is closed. Don't convert to/from it. */ + break; + } + src->extended_value = MAY_BE_ANY - src->extended_value; + } + COPY_NODE(src->result, opline->result); + SET_VAR_SOURCE(src); + MAKE_NOP(opline); + ++(*opt_count); + break; + case ZEND_IS_SMALLER: + if (opline->extended_value & MAY_BE_FALSE) { + zend_uchar tmp_type; + uint32_t tmp; + + src->opcode = ZEND_IS_SMALLER_OR_EQUAL; + tmp_type = src->op1_type; + src->op1_type = src->op2_type; + src->op2_type = tmp_type; + tmp = src->op1.num; + src->op1.num = src->op2.num; + src->op2.num = tmp; + } + COPY_NODE(src->result, opline->result); + SET_VAR_SOURCE(src); + MAKE_NOP(opline); + ++(*opt_count); + break; + case ZEND_IS_SMALLER_OR_EQUAL: + if (opline->extended_value & MAY_BE_FALSE) { + zend_uchar tmp_type; + uint32_t tmp; + + src->opcode = ZEND_IS_SMALLER; + tmp_type = src->op1_type; + src->op1_type = src->op2_type; + src->op2_type = tmp_type; + tmp = src->op1.num; + src->op1.num = src->op2.num; + src->op2.num = tmp; + } + COPY_NODE(src->result, opline->result); + SET_VAR_SOURCE(src); + MAKE_NOP(opline); + ++(*opt_count); + break; + case ZEND_ISSET_ISEMPTY_CV: + case ZEND_ISSET_ISEMPTY_VAR: + case ZEND_ISSET_ISEMPTY_DIM_OBJ: + case ZEND_ISSET_ISEMPTY_PROP_OBJ: + case ZEND_ISSET_ISEMPTY_STATIC_PROP: + case ZEND_INSTANCEOF: + case ZEND_DEFINED: + case ZEND_IN_ARRAY: + case ZEND_ARRAY_KEY_EXISTS: + if (opline->extended_value & MAY_BE_FALSE) { + break; + } + COPY_NODE(src->result, opline->result); + SET_VAR_SOURCE(src); + MAKE_NOP(opline); + ++(*opt_count); + break; + } + } + } + } + break; + case ZEND_BOOL: case ZEND_BOOL_NOT: optimize_bool: