PHP编译优化--常量折叠

本文基于的PHP8 commit为:14806e0824ecd598df74cac855868422e44aea53

有如下代码:

1
2
3
<?php

echo 1 + 2 + 3;

对应的opcode为:

1
2
L3    #0     ECHO                    6
L4 #1 RETURN<-1> 1

我们可以分析出,这个echo表达式对应的AST大概如下:

1
2
3
4
            ZEND_ECHO
+
+ 3
1 2

所以,可以看到,PHP代码生成的时候,很轻松的进行优化了:

1
2
3
        ZEND_ECHO
+
3 3

最后就会优化为:

1
2
    ZEND_ECHO
6

所以生成的opcode只有一条。

那么,我们再来看一个PHP目前没有优化的例子:

1
2
3
4
5
<?php

$x = 1;

echo $x + 2 + 3;

对应的opcode如下:

1
2
3
4
5
L3    #0     ASSIGN                  $x                   1
L5 #1 ADD $x 2 ~1
L5 #2 ADD ~1 3 ~2
L5 #3 ECHO ~2
L6 #4 RETURN<-1> 1

可以看到,这里没有进行优化,理论上来说,对常量进行折叠的话,可以减少一条opcode。那么为什么PHP内核它没有对这种情况进行优化呢?我们先来看一看这条语句对应的AST

1
2
3
4
            ZEND_ECHO
+
+ 3
x 2

可以发现,如果对AST进行深度遍历的话,是先进行x + 2,而x是一个变量,折叠不了,所以就没有优化到了,具体的代码是这样的(在函数zend_compile_binary_op里面):

1
2
3
4
5
6
7
8
9
10
if (left_node.op_type == IS_CONST && right_node.op_type == IS_CONST) {
if (zend_try_ct_eval_binary_op(&result->u.constant, opcode,
&left_node.u.constant, &right_node.u.constant)
) {
result->op_type = IS_CONST;
zval_ptr_dtor(&left_node.u.constant);
zval_ptr_dtor(&right_node.u.constant);
return;
}
}

我们发现,折叠的情况只有是当左右节点都为IS_CONST类型的时候,才会生效。

那么,面对这种情况,理论上我们可以怎么解决呢?我们可以对这个AST进行旋转,得到:

1
2
3
        ZEND_ECHO
+ +
x 2 3

然后,我们就可以优化为:

1
2
3
        ZEND_ECHO
+ 5
x

既然,PHP没有做这方面的优化工作,那么,我们写代码的时候,就可以稍微注意一下了。常量尽可能的往左边靠拢,例如1 + 2 + x这样。

后续我们的yaphp会使用LLVM来对这方面进行优化。