Compile PHP into a Native Binary Without an Interpreter
About fifteen years ago, Facebook tried to solve the performance problems of their monolith with the HPHPc compiler, which converted PHP to C++. Later, they abandoned this approach in favor of the HHVM virtual machine, and PHP 8 introduced a built-in JIT. It seemed like the idea of a pure Ahead-Of-Time compiler for PHP had gone down in history for good. The Swoole team decided to revisit it and released the TypePHP project.
This is a full-fledged AOT compiler written in PHP itself. It translates PHP 8.4+ source code into C++17, then compiles it via GCC or Clang into native machine code. No opcodes at runtime, no JIT warmup, and no interpreter in the runtime.
The compiler is fully self-hosting. The tpc utility builds itself from PHP sources, without any C glue code inside the compiler itself.
How TypePHP Works
Compilation happens in two stages. First, the analyzer parses the project code, collects metadata about classes and functions, and builds a complete symbol table. In the second stage, function bodies are translated into C++17. In hot paths, the compiler generates fast static C++, while for dynamic constructs like reflection or system function calls, it connects to the Zend runtime through the PHPX layer.
The output can be one of three formats:
- Executable binary (
bin). A standalone file for console utilities and daemons. It requires an entry pointmain(). - PHP extension (
ext). A regular.soor.dllmodule that can be loaded into standardphp.ini. - Dynamic library (
lib). A binary file with generated.stub.phpfiles for calling from other projects. - WASI component for running in a WebAssembly environment.
What Changes in Syntax
TypePHP doesn't try to support all the dynamic tricks of standard PHP. The creators bet on strict typing where maximum speed is needed.
Native Scalar Types
The use native_types directive tells the compiler to map int, float, and bool directly to C++ types (int64_t, double, bool). A variable with such a type can no longer suddenly change type in the middle of a function's execution. But the processor runs pure machine instructions without unpacking zval.
<?php
use native_types;
function fib(int $n): int
{
if ($n == 1 || $n == 2) {
return 1;
}
return fib($n - 1) + fib($n - 2);
}
function main(int $argc, array $argv): void
{
$n = (int)$argv[1];
$begin = microtime(true);
echo fib($n) . "\n";
echo "Time: " . (microtime(true) - $begin) . "\n";
}
It's built with a single command:
bin/tpc.php fib.php -O3 -o fib
./fib 35
Strictly Typed Containers
Regular PHP arrays are universal but memory-hungry and slow due to hash tables. TypePHP adds std::vector, std::array, std::map, and std::ordered_map structures.
<?php
use native_types;
function main(): void
{
$vector = std::vector(Type::Int);
$vector[] = 10;
$vector[] = 20;
$vector[] = 30;
$sum = 0;
foreach ($vector as $val) {
$sum += $val;
}
echo "Sum: " . $sum . "\n";
$map = std::ordered_map(Type::String, Type::Int);
$map["alpha"] = 1;
$map["beta"] = 2;
}
In the developers' tests, a loop updating elements in std::array took 6.4 seconds versus 67.6 seconds for a regular PHP array with JIT enabled. Speed essentially matched handwritten C++ vector (6.2 seconds).
Methods on Primitives
Instead of a bunch of functions like strlen(), strtoupper(), or in_array(), you can call methods directly on base types. The compiler resolves such calls at build time and converts them to direct C function calls without virtual table overhead:
<?php
function main(): void
{
$str = "hello world";
echo $str->upper() . "\n";
echo $str->substr(0, 5) . "\n";
$items = [1, 3, 5, 7];
var_dump($items->contains(3));
}
Template Code Generation via Attributes
To avoid writing dozens of getters and setters by hand, TypePHP processes custom attributes during compilation:
<?php
#[Printer(fields: ['id', 'name'])]
#[Arrayable(fields: ['id', 'name'])]
final class User
{
#[Constructor, Getter, With]
public int $id;
#[Constructor, Getter, Setter]
public string $name = 'guest';
}
function main(): void
{
$user = new User(1);
$user->setName('Ivan');
$copy = $user->withId(2);
echo $user->getId() . "\n"; // 1
echo $copy->getId() . "\n"; // 2
echo $user . "\n"; // User(id=1, name=Ivan)
}
The #[With] attribute generates a method that clones the object, changes the field, and returns a new instance. This is convenient for immutable DTOs.
Direct C++ Integration
If some algorithm is missing in PHP, you can write it in C++ and place it nearby. Linking happens via a stub file with an empty body:
// math.cpp
#include <phpx.h>
using namespace php;
Int php_fast_sum(Int a, Int b) {
return a + b;
}
<?php
// math.stub.php
function fast_sum(int $a, int $b): int {}
<?php
// main.php
function main(): void
{
echo fast_sum(10, 20) . "\n";
}
Performance in Benchmarks
According to the authors' measurements on standard synthetic tests from the php-src repository (bench.php and micro_bench.php with the -O3 flag):
bench.phpcompletes in 0.603 s versus 5.034 s for the standard interpreter (roughly 8x speedup);micro_bench.phpexecutes in 2.021 s versus 13.045 s (6.5x speedup).
The numbers are expected for AOT compilation of math and branching. In real web applications, most time goes to I/O and database operations, so the improvement will be more modest there. But for computational tasks and background workers, the difference is noticeable.
Limitations and Trade-offs
You can't yet port an existing Laravel or Symfony project to TypePHP. There are a number of strict rules:
- Executable code in global scope is forbidden—all code must live inside functions or methods.
- PHP 8.4 or 8.5 with the compiled
libphp.solibrary (embed SAPI) is required to build a binary. - You need GCC 9+ with C++17 support, CMake, and GMP/MPFR libraries for precise calculations.
- Some dynamic language features, like free type switching or complex references, are intentionally not supported.
The project.yml configuration file helps manage dependencies and optimization flags in large projects:
name: myapp
mode: bin
php-version: "8.5"
optimize: 2
job: 8
build-dir: build
cxx-std: c++17
sources:
- src
- cpp-src
link-libs:
- curl
Who Will Benefit from This Project
TypePHP is under active development. It has fewer than a thousand stars on GitHub so far, but the project is backed by the experienced Swoole team.
It makes sense to try the project if you need to:
- Build a lightweight CLI utility or microservice as a single binary without needing to install PHP on the target server.
- Hide application source code when delivering to a client on-premise, since binaries are significantly harder to decompile than bytecode.
- Write a PHP extension with heavy computations without diving deep into C and Zend API.
- Speed up isolated computational modules, like parsers, data packers, or scoring algorithms.
To get started, just clone the repository, build a test script via bin/tpc.php app.php, and look at the generated C++ in the build directory.
Powiązane projekty