diff --git a/Zend/Optimizer/dfa_pass.c b/Zend/Optimizer/dfa_pass.c index cfc6b27b3d218..2a8b4dc288e94 100644 --- a/Zend/Optimizer/dfa_pass.c +++ b/Zend/Optimizer/dfa_pass.c @@ -318,6 +318,53 @@ 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; + 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; + 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; + 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_BOOL; + 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) { @@ -1289,6 +1336,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; } } } diff --git a/ext/opcache/tests/opt/type_inference_class_consts1.phpt b/ext/opcache/tests/opt/type_inference_class_consts1.phpt index fb9be49a6f149..9410f378117cb 100644 --- a/ext/opcache/tests/opt/type_inference_class_consts1.phpt +++ b/ext/opcache/tests/opt/type_inference_class_consts1.phpt @@ -73,7 +73,7 @@ Test1::getStaticFoo: ; (after optimizer) ; %s 0000 T0 = FETCH_CLASS_CONSTANT (static) (exception) string("FOO") -0001 VERIFY_RETURN_TYPE T0 +0001 CAST (long) T0 0002 RETURN T0 LIVE RANGES: 0: 0001 - 0002 (tmp/var) diff --git a/ext/opcache/tests/opt/type_inference_class_consts4.phpt b/ext/opcache/tests/opt/type_inference_class_consts4.phpt index 03c0c1248bce1..a3243d8ad3e2f 100644 --- a/ext/opcache/tests/opt/type_inference_class_consts4.phpt +++ b/ext/opcache/tests/opt/type_inference_class_consts4.phpt @@ -57,7 +57,7 @@ Test4::getSelfA: ; (after optimizer) ; %s 0000 T0 = FETCH_CLASS_CONSTANT (self) (exception) string("A") -0001 VERIFY_RETURN_TYPE T0 +0001 CAST (long) T0 0002 RETURN T0 LIVE RANGES: 0: 0001 - 0002 (tmp/var) @@ -67,7 +67,7 @@ Test4::getSelfB: ; (after optimizer) ; %s 0000 T0 = FETCH_CLASS_CONSTANT (self) (exception) string("B") -0001 VERIFY_RETURN_TYPE T0 +0001 CAST (long) T0 0002 RETURN T0 LIVE RANGES: 0: 0001 - 0002 (tmp/var) @@ -77,7 +77,7 @@ Test4::getStaticA: ; (after optimizer) ; %s 0000 T0 = FETCH_CLASS_CONSTANT (static) (exception) string("A") -0001 VERIFY_RETURN_TYPE T0 +0001 CAST (long) T0 0002 RETURN T0 LIVE RANGES: 0: 0001 - 0002 (tmp/var) @@ -87,7 +87,7 @@ Test4::getStaticB: ; (after optimizer) ; %s 0000 T0 = FETCH_CLASS_CONSTANT (static) (exception) string("B") -0001 VERIFY_RETURN_TYPE T0 +0001 CAST (long) T0 0002 RETURN T0 LIVE RANGES: 0: 0001 - 0002 (tmp/var) diff --git a/ext/opcache/tests/opt/verify_return_type_to_cast.phpt b/ext/opcache/tests/opt/verify_return_type_to_cast.phpt new file mode 100644 index 0000000000000..61f3baaeeff3f --- /dev/null +++ b/ext/opcache/tests/opt/verify_return_type_to_cast.phpt @@ -0,0 +1,202 @@ +--TEST-- +Return type check converted to a cast +--INI-- +opcache.enable=1 +opcache.enable_cli=1 +opcache.optimization_level=-1 +opcache.opt_debug_level=0x20000 +opcache.preload= +--EXTENSIONS-- +opcache +--FILE-- + +--EXPECTF-- +$_main: + ; (lines=1, args=0, vars=0, tmps=0) + ; (after optimizer) + ; %s:1-58 +0000 RETURN int(1) + +strToBool: + ; (lines=3, args=1, vars=1, tmps=0) + ; (after optimizer) + ; %s:3-5 +0000 CV0($v) = RECV 1 +0001 VERIFY_RETURN_TYPE CV0($v) +0002 RETURN CV0($v) + +intToBool: + ; (lines=3, args=1, vars=1, tmps=0) + ; (after optimizer) + ; %s:6-8 +0000 CV0($v) = RECV 1 +0001 VERIFY_RETURN_TYPE CV0($v) +0002 RETURN CV0($v) + +floatToBool: + ; (lines=3, args=1, vars=1, tmps=0) + ; (after optimizer) + ; %s:9-11 +0000 CV0($v) = RECV 1 +0001 VERIFY_RETURN_TYPE CV0($v) +0002 RETURN CV0($v) + +strToInt: + ; (lines=3, args=1, vars=1, tmps=0) + ; (after optimizer) + ; %s:13-15 +0000 CV0($v) = RECV 1 +0001 VERIFY_RETURN_TYPE CV0($v) +0002 RETURN CV0($v) + +floatToInt: + ; (lines=3, args=1, vars=1, tmps=0) + ; (after optimizer) + ; %s:16-18 +0000 CV0($v) = RECV 1 +0001 VERIFY_RETURN_TYPE CV0($v) +0002 RETURN CV0($v) + +boolToInt: + ; (lines=3, args=1, vars=1, tmps=0) + ; (after optimizer) + ; %s:19-21 +0000 CV0($v) = RECV 1 +0001 CAST (long) CV0($v) +0002 RETURN CV0($v) + +strToFloat: + ; (lines=3, args=1, vars=1, tmps=0) + ; (after optimizer) + ; %s:23-25 +0000 CV0($v) = RECV 1 +0001 VERIFY_RETURN_TYPE CV0($v) +0002 RETURN CV0($v) + +intToFloat: + ; (lines=3, args=1, vars=1, tmps=0) + ; (after optimizer) + ; %s:26-28 +0000 CV0($v) = RECV 1 +0001 CAST (double) CV0($v) +0002 RETURN CV0($v) + +boolToFloat: + ; (lines=3, args=1, vars=1, tmps=0) + ; (after optimizer) + ; %s:29-31 +0000 CV0($v) = RECV 1 +0001 CAST (double) CV0($v) +0002 RETURN CV0($v) + +floatToString: + ; (lines=3, args=1, vars=1, tmps=0) + ; (after optimizer) + ; %s:33-35 +0000 CV0($v) = RECV 1 +0001 VERIFY_RETURN_TYPE CV0($v) +0002 RETURN CV0($v) + +intToString: + ; (lines=3, args=1, vars=1, tmps=0) + ; (after optimizer) + ; %s:36-38 +0000 CV0($v) = RECV 1 +0001 CAST (string) CV0($v) +0002 RETURN CV0($v) + +boolToString: + ; (lines=3, args=1, vars=1, tmps=0) + ; (after optimizer) + ; %s:39-41 +0000 CV0($v) = RECV 1 +0001 CAST (string) CV0($v) +0002 RETURN CV0($v) + +floatToUnion: + ; (lines=3, args=1, vars=1, tmps=0) + ; (after optimizer) + ; %s:43-45 +0000 CV0($v) = RECV 1 +0001 VERIFY_RETURN_TYPE CV0($v) +0002 RETURN CV0($v) + +intToUnion: + ; (lines=3, args=1, vars=1, tmps=0) + ; (after optimizer) + ; %s:46-48 +0000 CV0($v) = RECV 1 +0001 CAST (double) CV0($v) +0002 RETURN CV0($v) + +boolToUnion: + ; (lines=3, args=1, vars=1, tmps=0) + ; (after optimizer) + ; %s:49-51 +0000 CV0($v) = RECV 1 +0001 CAST (long) CV0($v) +0002 RETURN CV0($v) + +stringToUnion: + ; (lines=3, args=1, vars=1, tmps=0) + ; (after optimizer) + ; %s:52-54 +0000 CV0($v) = RECV 1 +0001 VERIFY_RETURN_TYPE CV0($v) +0002 RETURN CV0($v)