1
0
mirror of https://github.com/php/php-src.git synced 2026-03-24 00:02:20 +01:00

Optimize match(true) (#18423)

* 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`.

* Optimizer: Optimize `T = BOOL(X) + TYPE_CHECK(T, true)` to just `BOOL`

Resolves php/php-src#18411
This commit is contained in:
Tim Düsterhus
2025-04-29 21:39:12 +02:00
committed by GitHub
parent 3f03f7ed3d
commit 272abc2fb7
4 changed files with 162 additions and 1 deletions

View File

@@ -479,6 +479,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().

View File

@@ -470,7 +470,67 @@ 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);
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)
* 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);
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;
case ZEND_BOOL:
case ZEND_BOOL_NOT:
optimize_bool:
@@ -803,7 +863,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:

View File

@@ -0,0 +1,49 @@
--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--
<?php
$text = 'Bienvenue chez nous';
$result = match (true) {
!!preg_match('/Welcome/', $text), !!preg_match('/Hello/', $text) => 'en',
!!preg_match('/Bienvenue/', $text), !!preg_match('/Bonjour/', $text) => 'fr',
default => 'other',
};
var_dump($result);
?>
--EXPECTF--
$_main:
; (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 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"

View File

@@ -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--
<?php
$f = random_int(1, 2);
var_dump(!$f === true);
var_dump(!$f === false);
var_dump(!!$f === true);
var_dump(!!$f === false);
?>
--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)