ircmaxell/php-compiler

PHP的CFG编译器

dev-master 2019-05-05 11:48 UTC

This package is auto-updated.

Last update: 2024-09-14 06:27:30 UTC


README

CircleCI

好吧,这个项目曾经是废弃的。它需要调用各种黑客技术来生成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_dumpstrlen

但这只是一个开始...

调试

由于这是前沿技术,可调试性至关重要。为此,bin/jit.phpbin/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开销,那么可能会非常出色...

所以,这里确实有很大的潜力... 邪恶的微笑