ircmaxell / php-compiler
PHP的CFG编译器
Requires
- php: >=7.4
- ext-ffi: *
- ircmaxell/php-cfg: ^1.0
- ircmaxell/php-llvm: dev-master
- ircmaxell/php-optimizer: dev-master
- ircmaxell/php-types: ^1.0
Requires (Dev)
- friendsofphp/php-cs-fixer: *
- phan/phan: dev-master
- phpunit/phpunit: ^8.0
- pre/plugin: dev-develop
- yay/yay: dev-master#08c6490364e5f4c278c04d4eb331e0128290039d as 0.7.1
This package is auto-updated.
Last update: 2024-09-14 06:27:30 UTC
README
好吧,这个项目曾经是废弃的。它需要调用各种黑客技术来生成PHP扩展,或者PHP本身。
现在,由于 FFI在PHP 7.4中落地,各种疯狂的可能性是巨大的。
所以,我们就从这里开始吧 :)
安装
安装PHP 7.4,确保启用FFI扩展、OpenSSL扩展、mbstring扩展和zlib扩展(--with-ffi --with-openssl --enable-mbstring --with-zlib
)。
此外,您需要安装系统依赖项llvm-4.0
。在Ubuntu上
me@local:~$ sudo apt-get install llvm-4.0-dev clang-4.0
然后运行composer install
。
使用Docker
该项目附带一个工作和一个未工作的Dockerfile。Makefile使用一个相当旧的Ubuntu版本(16.04),一旦FFIMe为较新版本的glibc修复,它将切换到使用18.04(或更高版本)。
要构建,请使用make
me@local:~$ make build
这可能需要一些时间(可能长达10分钟)。它将安装一个Ubuntu容器,其中包含自定义编译的PHP-7.4以及您启动所需的一切。它还将安装所有依赖项,并运行预处理器。完成后,您可以运行测试
me@local:~$ make test
这将执行容器内所有单元测试。
要运行自己的代码或与编译器互动,您可以打开一个shell,使用make shell
me@local:~$ make shell root@662c59ae4527:/compiler# php bin/jit.php -r 'echo "Hello World\n";' Hello World
运行代码
使用此编译器主要有三种方式
VM - 虚拟机
此编译器模式实现了自己的PHP虚拟机,就像PHP一样。这实际上是一个循环中的巨型switch语句。
不,真的。它实际上是 一个巨大的switch语句...
实际上,这是一个非常慢的方法来运行您的PHP代码。嗯,它之所以慢,是因为它是用PHP编写的,而PHP已经在C编写的虚拟机上运行。
但如果我们能改变这个...
JIT - 原地编译
此编译器模式从PHP代码中生成机器代码。然后,它不使用上面的VM运行代码,而是调用机器代码。
运行速度非常快(比PHP 7.4快,不考虑编译时间)。
但它也花费很长时间进行编译(编译很慢,因为它是从PHP编译的)。
每次运行它时,它都会再次编译。
这带我们来到了最后一种模式
Compile - 预先编译
此编译器模式实际上生成原生机器代码,并将其输出到可执行文件。
这意味着,您可以取出PHP代码,生成一个独立的二进制文件。一个实现了 无虚拟机 的二进制文件。这意味着它在理论上是(至少)与原生C一样快。
嗯,这不是真的。但它非常快。
好的,说够了,我该如何尝试?
有四个CLI入口点,所有4个都与PHP CLI(某种程度上)类似
php bin/vm.php
- 在VM中运行代码php bin/jit.php
- 编译所有代码,然后运行它php bin/compile.php
- 编译所有代码,并输出.o
文件php bin/print.php
- 编译并输出CFG和生成的OpCodes(用于调试很有用)
执行代码
指定来自STDIN
的代码(这适用于所有4个入口点)
me@local:~$ echo '<?php echo "Hello World\n";' | php bin/vm.php Hello World
您也可以通过CLI的-r
参数指定
me@local:~$ php bin/jit.php -r 'echo "Hello World\n";' Hello World
您还可以指定一个文件
me@local:~$ echo '<?php echo "Hello World\n";' > test.php me@local:~$ php bin/vm.php test.php
当使用bin/compile.php
编译时,您还可以使用-o
指定“输出文件”(默认为输入文件,去掉.php
后缀)。这将生成一个可在您系统上执行的可执行二进制文件。
me@local:~$ echo '<?php echo "Hello World\n";' > test.php me@local:~$ php bin/compile.php -o other test.php me@local:~$ ./other Hello World
或者,使用默认设置
me@local:~$ echo '<?php echo "Hello World\n";' > test.php me@local:~$ php bin/compile.php test.php me@local:~$ ./test Hello World
代码审查
如果您传递-l
参数,它将不会执行代码,而是仅进行编译。这将允许您测试代码是否能够编译(提示:目前大多数都无法编译)。
调试
有时,您想查看正在发生的事情。如果是这样,请尝试使用bin/print.php
作为入口点。它将输出两种类型的信息。第一种是控制流图,第二种是编译后的操作码。
me@local:~$ php bin/print.php -r 'echo "Hello World\n";' Control Flow Graph: Block#1 Terminal_Echo expr: LITERAL<inferred:string>('Hello World ') Terminal_Return OpCodes: block_0: TYPE_ECHO(0, null, null) TYPE_RETURN_VOID(null, null, null)
未来工作
目前,这仅支持PHP的一个极其有限的子集。不支持任何动态内容。数组不受支持。对象属性和方法也不受支持。唯一受支持的内置函数是var_dump
和strlen
。
但这只是一个开始...
调试
由于这是前沿技术,可调试性至关重要。为此,bin/jit.php
和bin/compile.php
都接受一个-y
标志,这将输出一个调试文件对(默认为脚本的名称前缀,但您可以在标志后指定另一个前缀)。
me@local:~$ echo '<?php echo "Hello World\n";' > demo.php me@local:~$ php bin/compile.php -y demo.php # Produces: # demo - executable of the code # demo.bc - LLVM intermediary bytecode associated with the compiled code # demo.s - assembly generated by the compiled code
查看示例文件夹。
性能
那么,这个玩意儿有多快?好吧,让我们看看内部的基准测试。您可以使用make bench
运行它们,并得到以下输出(每个测试运行5次,取平均值的时间)。
查看基准测试文件夹。
这是在底层使用LLVM后的结果。因此,将LLVM迁移似乎是非常值得的,仅仅从性能角度来看。
要自己运行基准测试,您需要为每个要测试的PHP版本传递一系列ENV变量。例如,上面的图表是用以下方式生成的:
在没有opcache进行优化的情况下,bin/jit.php
实际上能够接近原生PHP的ack(3,9)和mandelbrot(没有opcache)对于7.3和7.4。它甚至能与PHP 8的实验性JIT编译器在ack(3,9)上竞争。对于ack(3,10),它甚至可能是最快的执行方法。
其他大多数测试实际上比使用bin/jit.php
编译器慢得多。这是因为测试本身比解析和编译文件的基本时间(目前约为0.2秒)慢。
注意,这是在PHP之上运行编译器。在某个时候,目标是让编译器能够编译自己,希望至少可以将编译时间减少几百分之一。
只需查看“编译时间”列(这是AOT编译器生成二进制文件的结果)与所有其他列之间的差异。这显示了这种编译方法的潜力。如果我们能解决bin/jit.php
示例中解析/编译的PHP开销,那么可能会非常出色...
所以,这里确实有很大的潜力... 邪恶的微笑