From d93f45b275914272b943ac6e536955103daebbc5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tim=20D=C3=BCsterhus?= Date: Thu, 24 Apr 2025 19:39:14 +0200 Subject: [PATCH 1/3] Optimizer: Optimize `IS_IDENTICAL` with true/false/null to `TYPE_CHECK` This optimization is already happening in the compiler for explicit `===` expressions, but not for `match()`, which also compiles to `IS_IDENTICAL`. --- Zend/Optimizer/block_pass.c | 30 ++++++++++++++++- ext/opcache/tests/match/005.phpt | 57 ++++++++++++++++++++++++++++++++ 2 files changed, 86 insertions(+), 1 deletion(-) create mode 100644 ext/opcache/tests/match/005.phpt diff --git a/Zend/Optimizer/block_pass.c b/Zend/Optimizer/block_pass.c index 2b6d71c385457..3f3e2e0e34602 100644 --- a/Zend/Optimizer/block_pass.c +++ b/Zend/Optimizer/block_pass.c @@ -470,7 +470,36 @@ static void zend_optimize_block(zend_basic_block *block, zend_op_array *op_array goto optimize_bool; } break; + case ZEND_IS_IDENTICAL: + if (opline->op1_type == IS_CONST && + opline->op2_type == IS_CONST) { + goto optimize_constant_binary_op; + } + if (opline->op1_type == IS_CONST && + (Z_TYPE(ZEND_OP1_LITERAL(opline)) <= IS_TRUE && Z_TYPE(ZEND_OP1_LITERAL(opline)) >= IS_NULL)) { + /* IS_IDENTICAL(TRUE, T) => TYPE_CHECK(T, TRUE) + * IS_IDENTICAL(FALSE, T) => TYPE_CHECK(T, FALSE) + * IS_IDENTICAL(NULL, T) => TYPE_CHECK(T, NULL) + */ + opline->opcode = ZEND_TYPE_CHECK; + opline->extended_value = (1 << Z_TYPE(ZEND_OP1_LITERAL(opline))); + COPY_NODE(opline->op1, opline->op2); + SET_UNUSED(opline->op2); + ++(*opt_count); + } else if (opline->op2_type == IS_CONST && + (Z_TYPE(ZEND_OP2_LITERAL(opline)) <= IS_TRUE && Z_TYPE(ZEND_OP2_LITERAL(opline)) >= IS_NULL)) { + /* IS_IDENTICAL(T, TRUE) => TYPE_CHECK(T, TRUE) + * IS_IDENTICAL(T, FALSE) => TYPE_CHECK(T, FALSE) + * IS_IDENTICAL(T, NULL) => TYPE_CHECK(T, NULL) + */ + opline->opcode = ZEND_TYPE_CHECK; + opline->extended_value = (1 << Z_TYPE(ZEND_OP2_LITERAL(opline))); + SET_UNUSED(opline->op2); + ++(*opt_count); + } + break; + case ZEND_BOOL: case ZEND_BOOL_NOT: optimize_bool: @@ -803,7 +832,6 @@ static void zend_optimize_block(zend_basic_block *block, zend_op_array *op_array case ZEND_SR: case ZEND_IS_SMALLER: case ZEND_IS_SMALLER_OR_EQUAL: - case ZEND_IS_IDENTICAL: case ZEND_IS_NOT_IDENTICAL: case ZEND_BOOL_XOR: case ZEND_BW_OR: diff --git a/ext/opcache/tests/match/005.phpt b/ext/opcache/tests/match/005.phpt new file mode 100644 index 0000000000000..f3bbafc59a738 --- /dev/null +++ b/ext/opcache/tests/match/005.phpt @@ -0,0 +1,57 @@ +--TEST-- +Match expression true +--INI-- +opcache.enable=1 +opcache.enable_cli=1 +opcache.opt_debug_level=0x20000 +zend_test.observer.enabled=0 +--EXTENSIONS-- +opcache +--FILE-- + 'en', + !!preg_match('/Bienvenue/', $text), !!preg_match('/Bonjour/', $text) => 'fr', + default => 'other', +}; + +var_dump($result); + +?> +--EXPECTF-- +$_main: + ; (lines=28, args=0, vars=2, tmps=3) + ; (after optimizer) + ; %s +0000 ASSIGN CV0($text) string("Bienvenue chez nous") +0001 T2 = FRAMELESS_ICALL_2(preg_match) string("/Welcome/") CV0($text) +0002 T3 = BOOL T2 +0003 T2 = TYPE_CHECK (true) T3 +0004 JMPNZ T2 0018 +0005 T4 = FRAMELESS_ICALL_2(preg_match) string("/Hello/") CV0($text) +0006 T3 = BOOL T4 +0007 T2 = TYPE_CHECK (true) T3 +0008 JMPNZ T2 0018 +0009 T4 = FRAMELESS_ICALL_2(preg_match) string("/Bienvenue/") CV0($text) +0010 T3 = BOOL T4 +0011 T2 = TYPE_CHECK (true) T3 +0012 JMPNZ T2 0020 +0013 T4 = FRAMELESS_ICALL_2(preg_match) string("/Bonjour/") CV0($text) +0014 T3 = BOOL T4 +0015 T2 = TYPE_CHECK (true) T3 +0016 JMPNZ T2 0020 +0017 JMP 0022 +0018 T2 = QM_ASSIGN string("en") +0019 JMP 0023 +0020 T2 = QM_ASSIGN string("fr") +0021 JMP 0023 +0022 T2 = QM_ASSIGN string("other") +0023 ASSIGN CV1($result) T2 +0024 INIT_FCALL 1 %d string("var_dump") +0025 SEND_VAR CV1($result) 1 +0026 DO_ICALL +0027 RETURN int(1) +string(2) "fr" From 8ffcee20704d3a69568eec93b9027557a6284552 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tim=20D=C3=BCsterhus?= Date: Thu, 24 Apr 2025 19:42:39 +0200 Subject: [PATCH 2/3] Optimizer: Optimize `T = BOOL(X) + TYPE_CHECK(T, true)` to just `BOOL` Resolves php/php-src#18411 --- Zend/Optimizer/block_pass.c | 31 ++++++++++++++ ext/opcache/tests/match/005.phpt | 46 +++++++++------------ ext/opcache/tests/opt/block_pass_007.phpt | 49 +++++++++++++++++++++++ 3 files changed, 99 insertions(+), 27 deletions(-) create mode 100644 ext/opcache/tests/opt/block_pass_007.phpt diff --git a/Zend/Optimizer/block_pass.c b/Zend/Optimizer/block_pass.c index 3f3e2e0e34602..96a0e81f03825 100644 --- a/Zend/Optimizer/block_pass.c +++ b/Zend/Optimizer/block_pass.c @@ -487,6 +487,7 @@ static void zend_optimize_block(zend_basic_block *block, zend_op_array *op_array COPY_NODE(opline->op1, opline->op2); SET_UNUSED(opline->op2); ++(*opt_count); + goto optimize_type_check; } else if (opline->op2_type == IS_CONST && (Z_TYPE(ZEND_OP2_LITERAL(opline)) <= IS_TRUE && Z_TYPE(ZEND_OP2_LITERAL(opline)) >= IS_NULL)) { /* IS_IDENTICAL(T, TRUE) => TYPE_CHECK(T, TRUE) @@ -497,6 +498,36 @@ static void zend_optimize_block(zend_basic_block *block, zend_op_array *op_array opline->extended_value = (1 << Z_TYPE(ZEND_OP2_LITERAL(opline))); SET_UNUSED(opline->op2); ++(*opt_count); + goto optimize_type_check; + } + break; + case ZEND_TYPE_CHECK: +optimize_type_check: + if (opline->extended_value == (1 << IS_TRUE) || opline->extended_value == (1 << IS_FALSE)) { + 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: + case ZEND_BOOL_NOT: + /* T = BOOL(X) + TYPE_CHECK(T, TRUE) -> BOOL(X), NOP + * T = BOOL(X) + TYPE_CHECK(T, FALSE) -> BOOL_NOT(X), NOP + * T = BOOL_NOT(X) + TYPE_CHECK(T, TRUE) -> BOOL_NOT(X), NOP + * T = BOOL_NOT(X) + TYPE_CHECK(T, FALSE) -> BOOL(X), NOP + */ + src->opcode = + ((src->opcode == ZEND_BOOL) == (opline->extended_value == (1 << IS_TRUE))) ? + ZEND_BOOL : ZEND_BOOL_NOT; + COPY_NODE(src->result, opline->result); + SET_VAR_SOURCE(src); + MAKE_NOP(opline); + ++(*opt_count); + break; + } + } + } } break; diff --git a/ext/opcache/tests/match/005.phpt b/ext/opcache/tests/match/005.phpt index f3bbafc59a738..5726336d53922 100644 --- a/ext/opcache/tests/match/005.phpt +++ b/ext/opcache/tests/match/005.phpt @@ -23,35 +23,27 @@ var_dump($result); ?> --EXPECTF-- $_main: - ; (lines=28, args=0, vars=2, tmps=3) + ; (lines=20, args=0, vars=2, tmps=1) ; (after optimizer) ; %s 0000 ASSIGN CV0($text) string("Bienvenue chez nous") 0001 T2 = FRAMELESS_ICALL_2(preg_match) string("/Welcome/") CV0($text) -0002 T3 = BOOL T2 -0003 T2 = TYPE_CHECK (true) T3 -0004 JMPNZ T2 0018 -0005 T4 = FRAMELESS_ICALL_2(preg_match) string("/Hello/") CV0($text) -0006 T3 = BOOL T4 -0007 T2 = TYPE_CHECK (true) T3 -0008 JMPNZ T2 0018 -0009 T4 = FRAMELESS_ICALL_2(preg_match) string("/Bienvenue/") CV0($text) -0010 T3 = BOOL T4 -0011 T2 = TYPE_CHECK (true) T3 -0012 JMPNZ T2 0020 -0013 T4 = FRAMELESS_ICALL_2(preg_match) string("/Bonjour/") CV0($text) -0014 T3 = BOOL T4 -0015 T2 = TYPE_CHECK (true) T3 -0016 JMPNZ T2 0020 -0017 JMP 0022 -0018 T2 = QM_ASSIGN string("en") -0019 JMP 0023 -0020 T2 = QM_ASSIGN string("fr") -0021 JMP 0023 -0022 T2 = QM_ASSIGN string("other") -0023 ASSIGN CV1($result) T2 -0024 INIT_FCALL 1 %d string("var_dump") -0025 SEND_VAR CV1($result) 1 -0026 DO_ICALL -0027 RETURN int(1) +0002 JMPNZ T2 0010 +0003 T2 = FRAMELESS_ICALL_2(preg_match) string("/Hello/") CV0($text) +0004 JMPNZ T2 0010 +0005 T2 = FRAMELESS_ICALL_2(preg_match) string("/Bienvenue/") CV0($text) +0006 JMPNZ T2 0012 +0007 T2 = FRAMELESS_ICALL_2(preg_match) string("/Bonjour/") CV0($text) +0008 JMPNZ T2 0012 +0009 JMP 0014 +0010 T2 = QM_ASSIGN string("en") +0011 JMP 0015 +0012 T2 = QM_ASSIGN string("fr") +0013 JMP 0015 +0014 T2 = QM_ASSIGN string("other") +0015 ASSIGN CV1($result) T2 +0016 INIT_FCALL 1 %d string("var_dump") +0017 SEND_VAR CV1($result) 1 +0018 DO_ICALL +0019 RETURN int(1) string(2) "fr" diff --git a/ext/opcache/tests/opt/block_pass_007.phpt b/ext/opcache/tests/opt/block_pass_007.phpt new file mode 100644 index 0000000000000..4f3d334406a42 --- /dev/null +++ b/ext/opcache/tests/opt/block_pass_007.phpt @@ -0,0 +1,49 @@ +--TEST-- +Block Pass 007: BOOL + TYPE_CHECK +--INI-- +opcache.enable=1 +opcache.enable_cli=1 +opcache.optimization_level=-1 +opcache.opt_debug_level=0x20000 +--EXTENSIONS-- +opcache +--FILE-- + +--EXPECTF-- +$_main: + ; (lines=22, args=0, vars=1, tmps=1) + ; (after optimizer) + ; %s +0000 INIT_FCALL 2 %d string("random_int") +0001 SEND_VAL int(1) 1 +0002 SEND_VAL int(2) 2 +0003 V1 = DO_ICALL +0004 ASSIGN CV0($f) V1 +0005 INIT_FCALL 1 %d string("var_dump") +0006 T1 = BOOL_NOT CV0($f) +0007 SEND_VAL T1 1 +0008 DO_ICALL +0009 INIT_FCALL 1 %d string("var_dump") +0010 T1 = BOOL CV0($f) +0011 SEND_VAL T1 1 +0012 DO_ICALL +0013 INIT_FCALL 1 %d string("var_dump") +0014 T1 = BOOL CV0($f) +0015 SEND_VAL T1 1 +0016 DO_ICALL +0017 INIT_FCALL 1 %d string("var_dump") +0018 T1 = BOOL_NOT CV0($f) +0019 SEND_VAL T1 1 +0020 DO_ICALL +0021 RETURN int(1) +bool(false) +bool(true) +bool(true) +bool(false) From 97be0cbe9ff3cf2408fe1cb25388c87a272e5137 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tim=20D=C3=BCsterhus?= Date: Tue, 29 Apr 2025 21:11:08 +0200 Subject: [PATCH 3/3] [skip ci] UPGRADING --- UPGRADING | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/UPGRADING b/UPGRADING index 40645a28ac600..c8dc05209516e 100644 --- a/UPGRADING +++ b/UPGRADING @@ -466,6 +466,10 @@ PHP 8.5 UPGRADE NOTES 14. Performance Improvements ======================================== +- Core: + . Remove OPcodes for identity comparisons against booleans, particularly + for the match(true) pattern. + - ReflectionProperty: . Improved performance of the following methods: getValue(), getRawValue(), isInitialized(), setValue(), setRawValue().