Skip to content
Draft
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
50 changes: 50 additions & 0 deletions Zend/Optimizer/dfa_pass.c
Original file line number Diff line number Diff line change
Expand Up @@ -316,6 +316,54 @@ static inline bool can_elide_return_type_check(
return false;
}

static inline bool can_return_value_safely_be_coerced(
const zend_op_array *op_array, const zend_ssa *ssa, const zend_ssa_op *ssa_op, zend_op *opline
) {
const uint32_t return_type_mask = ZEND_TYPE_FULL_MASK(op_array->arg_info[-1].type);
const zend_ssa_var_info *use_info = &ssa->var_info[ssa_op->op1_use];

/* Type preference order: int -> float -> string -> bool */
/* Can always safely cast booleans to inter */
if (return_type_mask & MAY_BE_LONG) {
if (use_info->type & MAY_BE_BOOL) {
opline->opcode = ZEND_CAST_VALUE_FOR_RETURN_TYPE;
opline->extended_value = IS_LONG;
return true;
}
return false;
}
if (return_type_mask & MAY_BE_DOUBLE) {
/* Can always safely cast booleans, and integers to float */
if (use_info->type & (MAY_BE_LONG|MAY_BE_BOOL)) {
opline->opcode = ZEND_CAST_VALUE_FOR_RETURN_TYPE;
opline->extended_value = IS_DOUBLE;
return true;
}
return false;
}
/* Can always safely cast booleans, and integers to string,
* float value must not be NAN */
if (return_type_mask & MAY_BE_STRING) {
if (use_info->type & (MAY_BE_LONG|MAY_BE_BOOL)) {
opline->opcode = ZEND_CAST_VALUE_FOR_RETURN_TYPE;
opline->extended_value = IS_STRING;
return true;
}
return false;
}
return false;
if ((return_type_mask & MAY_BE_BOOL) == MAY_BE_BOOL) {
/* Can always safely cast integers and strings to bool,
* float value must not be NAN */
if (use_info->type & (MAY_BE_LONG|MAY_BE_STRING)) {
opline->opcode = ZEND_CAST_VALUE_FOR_RETURN_TYPE;
opline->extended_value = IS_FALSE;
return true;
}
return false;
}
}

static bool opline_supports_assign_contraction(
zend_op_array *op_array, zend_ssa *ssa, zend_op *opline, int src_var, uint32_t cv_var) {
if (opline->opcode == ZEND_NEW) {
Expand Down Expand Up @@ -1287,6 +1335,8 @@ void zend_dfa_optimize_op_array(zend_op_array *op_array, zend_optimizer_ctx *ctx

MAKE_NOP(opline);
remove_nops = 1;
} else if (can_return_value_safely_be_coerced(op_array, ssa, &ssa->ops[op_1], opline)) {
continue;
}
}
}
Expand Down
36 changes: 36 additions & 0 deletions Zend/zend_vm_def.h
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@
* php zend_vm_gen.php
*/

#include "zend_variables.h"

ZEND_VM_HELPER(zend_add_helper, ANY, ANY, zval *op_1, zval *op_2)
{
USE_OPLINE
Expand Down Expand Up @@ -4532,6 +4534,40 @@ ZEND_VM_COLD_CONST_HANDLER(124, ZEND_VERIFY_RETURN_TYPE, CONST|TMP|VAR|UNUSED|CV
}
}

ZEND_VM_COLD_CONST_HANDLER(45, ZEND_CAST_VALUE_FOR_RETURN_TYPE, TMP|VAR|CV, UNUSED)
{
USE_OPLINE
zval *retval_ptr = GET_OP1_ZVAL_PTR_UNDEF(BP_VAR_R);
ZEND_ASSERT(!Z_ISREF_P(retval_ptr));

switch (opline->extended_value) {
case IS_LONG: {
zend_long lval = zval_get_long(retval_ptr);
zval_ptr_dtor(retval_ptr);
ZVAL_LONG(retval_ptr, lval);
break;
}
case IS_DOUBLE: {
double dval = zval_get_double(retval_ptr);
zval_ptr_dtor(retval_ptr);
ZVAL_DOUBLE(retval_ptr, dval);
break;
}
case IS_STRING:
/* retval_ptr cannot be a refcounted value */
ZVAL_STR(retval_ptr, zval_get_string(retval_ptr));
break;
case IS_FALSE: { /* Used for booleans */
bool bval = i_zend_is_true(retval_ptr);
zval_ptr_dtor(retval_ptr);
ZVAL_BOOL(retval_ptr, bval);
break;
}
/* Default case assert? */
}
ZEND_VM_NEXT_OPCODE();
}

ZEND_VM_COLD_HANDLER(201, ZEND_VERIFY_NEVER_TYPE, UNUSED, UNUSED)
{
SAVE_OPLINE();
Expand Down
Loading
Loading