Skip to content

Commit 272abc2

Browse files
authored
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 #18411
1 parent 3f03f7e commit 272abc2

File tree

4 files changed

+162
-1
lines changed

4 files changed

+162
-1
lines changed

UPGRADING

+4
Original file line numberDiff line numberDiff line change
@@ -479,6 +479,10 @@ PHP 8.5 UPGRADE NOTES
479479
14. Performance Improvements
480480
========================================
481481

482+
- Core:
483+
. Remove OPcodes for identity comparisons against booleans, particularly
484+
for the match(true) pattern.
485+
482486
- ReflectionProperty:
483487
. Improved performance of the following methods: getValue(), getRawValue(),
484488
isInitialized(), setValue(), setRawValue().

Zend/Optimizer/block_pass.c

+60-1
Original file line numberDiff line numberDiff line change
@@ -470,7 +470,67 @@ static void zend_optimize_block(zend_basic_block *block, zend_op_array *op_array
470470
goto optimize_bool;
471471
}
472472
break;
473+
case ZEND_IS_IDENTICAL:
474+
if (opline->op1_type == IS_CONST &&
475+
opline->op2_type == IS_CONST) {
476+
goto optimize_constant_binary_op;
477+
}
473478

479+
if (opline->op1_type == IS_CONST &&
480+
(Z_TYPE(ZEND_OP1_LITERAL(opline)) <= IS_TRUE && Z_TYPE(ZEND_OP1_LITERAL(opline)) >= IS_NULL)) {
481+
/* IS_IDENTICAL(TRUE, T) => TYPE_CHECK(T, TRUE)
482+
* IS_IDENTICAL(FALSE, T) => TYPE_CHECK(T, FALSE)
483+
* IS_IDENTICAL(NULL, T) => TYPE_CHECK(T, NULL)
484+
*/
485+
opline->opcode = ZEND_TYPE_CHECK;
486+
opline->extended_value = (1 << Z_TYPE(ZEND_OP1_LITERAL(opline)));
487+
COPY_NODE(opline->op1, opline->op2);
488+
SET_UNUSED(opline->op2);
489+
++(*opt_count);
490+
goto optimize_type_check;
491+
} else if (opline->op2_type == IS_CONST &&
492+
(Z_TYPE(ZEND_OP2_LITERAL(opline)) <= IS_TRUE && Z_TYPE(ZEND_OP2_LITERAL(opline)) >= IS_NULL)) {
493+
/* IS_IDENTICAL(T, TRUE) => TYPE_CHECK(T, TRUE)
494+
* IS_IDENTICAL(T, FALSE) => TYPE_CHECK(T, FALSE)
495+
* IS_IDENTICAL(T, NULL) => TYPE_CHECK(T, NULL)
496+
*/
497+
opline->opcode = ZEND_TYPE_CHECK;
498+
opline->extended_value = (1 << Z_TYPE(ZEND_OP2_LITERAL(opline)));
499+
SET_UNUSED(opline->op2);
500+
++(*opt_count);
501+
goto optimize_type_check;
502+
}
503+
break;
504+
case ZEND_TYPE_CHECK:
505+
optimize_type_check:
506+
if (opline->extended_value == (1 << IS_TRUE) || opline->extended_value == (1 << IS_FALSE)) {
507+
if (opline->op1_type == IS_TMP_VAR &&
508+
!zend_bitset_in(used_ext, VAR_NUM(opline->op1.var))) {
509+
src = VAR_SOURCE(opline->op1);
510+
511+
if (src) {
512+
switch (src->opcode) {
513+
case ZEND_BOOL:
514+
case ZEND_BOOL_NOT:
515+
/* T = BOOL(X) + TYPE_CHECK(T, TRUE) -> BOOL(X), NOP
516+
* T = BOOL(X) + TYPE_CHECK(T, FALSE) -> BOOL_NOT(X), NOP
517+
* T = BOOL_NOT(X) + TYPE_CHECK(T, TRUE) -> BOOL_NOT(X), NOP
518+
* T = BOOL_NOT(X) + TYPE_CHECK(T, FALSE) -> BOOL(X), NOP
519+
*/
520+
src->opcode =
521+
((src->opcode == ZEND_BOOL) == (opline->extended_value == (1 << IS_TRUE))) ?
522+
ZEND_BOOL : ZEND_BOOL_NOT;
523+
COPY_NODE(src->result, opline->result);
524+
SET_VAR_SOURCE(src);
525+
MAKE_NOP(opline);
526+
++(*opt_count);
527+
break;
528+
}
529+
}
530+
}
531+
}
532+
break;
533+
474534
case ZEND_BOOL:
475535
case ZEND_BOOL_NOT:
476536
optimize_bool:
@@ -803,7 +863,6 @@ static void zend_optimize_block(zend_basic_block *block, zend_op_array *op_array
803863
case ZEND_SR:
804864
case ZEND_IS_SMALLER:
805865
case ZEND_IS_SMALLER_OR_EQUAL:
806-
case ZEND_IS_IDENTICAL:
807866
case ZEND_IS_NOT_IDENTICAL:
808867
case ZEND_BOOL_XOR:
809868
case ZEND_BW_OR:

ext/opcache/tests/match/005.phpt

+49
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
--TEST--
2+
Match expression true
3+
--INI--
4+
opcache.enable=1
5+
opcache.enable_cli=1
6+
opcache.opt_debug_level=0x20000
7+
zend_test.observer.enabled=0
8+
--EXTENSIONS--
9+
opcache
10+
--FILE--
11+
<?php
12+
13+
$text = 'Bienvenue chez nous';
14+
15+
$result = match (true) {
16+
!!preg_match('/Welcome/', $text), !!preg_match('/Hello/', $text) => 'en',
17+
!!preg_match('/Bienvenue/', $text), !!preg_match('/Bonjour/', $text) => 'fr',
18+
default => 'other',
19+
};
20+
21+
var_dump($result);
22+
23+
?>
24+
--EXPECTF--
25+
$_main:
26+
; (lines=20, args=0, vars=2, tmps=1)
27+
; (after optimizer)
28+
; %s
29+
0000 ASSIGN CV0($text) string("Bienvenue chez nous")
30+
0001 T2 = FRAMELESS_ICALL_2(preg_match) string("/Welcome/") CV0($text)
31+
0002 JMPNZ T2 0010
32+
0003 T2 = FRAMELESS_ICALL_2(preg_match) string("/Hello/") CV0($text)
33+
0004 JMPNZ T2 0010
34+
0005 T2 = FRAMELESS_ICALL_2(preg_match) string("/Bienvenue/") CV0($text)
35+
0006 JMPNZ T2 0012
36+
0007 T2 = FRAMELESS_ICALL_2(preg_match) string("/Bonjour/") CV0($text)
37+
0008 JMPNZ T2 0012
38+
0009 JMP 0014
39+
0010 T2 = QM_ASSIGN string("en")
40+
0011 JMP 0015
41+
0012 T2 = QM_ASSIGN string("fr")
42+
0013 JMP 0015
43+
0014 T2 = QM_ASSIGN string("other")
44+
0015 ASSIGN CV1($result) T2
45+
0016 INIT_FCALL 1 %d string("var_dump")
46+
0017 SEND_VAR CV1($result) 1
47+
0018 DO_ICALL
48+
0019 RETURN int(1)
49+
string(2) "fr"
+49
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
--TEST--
2+
Block Pass 007: BOOL + TYPE_CHECK
3+
--INI--
4+
opcache.enable=1
5+
opcache.enable_cli=1
6+
opcache.optimization_level=-1
7+
opcache.opt_debug_level=0x20000
8+
--EXTENSIONS--
9+
opcache
10+
--FILE--
11+
<?php
12+
$f = random_int(1, 2);
13+
14+
var_dump(!$f === true);
15+
var_dump(!$f === false);
16+
var_dump(!!$f === true);
17+
var_dump(!!$f === false);
18+
?>
19+
--EXPECTF--
20+
$_main:
21+
; (lines=22, args=0, vars=1, tmps=1)
22+
; (after optimizer)
23+
; %s
24+
0000 INIT_FCALL 2 %d string("random_int")
25+
0001 SEND_VAL int(1) 1
26+
0002 SEND_VAL int(2) 2
27+
0003 V1 = DO_ICALL
28+
0004 ASSIGN CV0($f) V1
29+
0005 INIT_FCALL 1 %d string("var_dump")
30+
0006 T1 = BOOL_NOT CV0($f)
31+
0007 SEND_VAL T1 1
32+
0008 DO_ICALL
33+
0009 INIT_FCALL 1 %d string("var_dump")
34+
0010 T1 = BOOL CV0($f)
35+
0011 SEND_VAL T1 1
36+
0012 DO_ICALL
37+
0013 INIT_FCALL 1 %d string("var_dump")
38+
0014 T1 = BOOL CV0($f)
39+
0015 SEND_VAL T1 1
40+
0016 DO_ICALL
41+
0017 INIT_FCALL 1 %d string("var_dump")
42+
0018 T1 = BOOL_NOT CV0($f)
43+
0019 SEND_VAL T1 1
44+
0020 DO_ICALL
45+
0021 RETURN int(1)
46+
bool(false)
47+
bool(true)
48+
bool(true)
49+
bool(false)

0 commit comments

Comments
 (0)