How TypePHP Achieves Native AOT Compilation for PHP: A Deep Dive into the Source Code
TypePHP compiles PHP source code into native machine code by first lowering the PHP AST into C++17 source files, then invoking a system C++ compiler (Clang or GCC) to produce standalone executables or shared libraries that run without the PHP interpreter.
TypePHP, developed by the Swoole team, implements a true ahead-of-time (AOT) compiler for PHP written entirely in PHP. Unlike JIT compilation that still operates within the Zend engine at runtime, TypePHP generates self-contained native binaries by bridging PHP's dynamic nature with C++'s static compilation model. This article examines the internal architecture, type mapping strategies, and build pipeline as implemented in the swoole/typephp repository.
The AOT Compilation Pipeline
TypePHP orchestrates native compilation through a six-stage pipeline managed primarily in src/Translator.php and src/compiler.php. Each stage transforms the source representation until it becomes executable machine code.
Stage 1: Parse and Validate
The process begins with Translator::prepare(), which parses all input files into an AST and constructs a global symbol model. This phase performs type checking and resolves declarations across the entire project before any code generation occurs.
Stage 2: Lowering to C++17
During Translator::convert(), the compiler walks the PHP AST and lowers each function, class, constant, and statement into C++17 code that utilizes the PHPX runtime API. Native scalar types undergo direct mapping: PHP int becomes int64_t, float becomes double, and bool becomes C++ bool. This transformation enables ABI-level calls without zval boxing for performance-critical paths.
Stage 3: Code Generation
The lowering phase emits *.cc source files to a build directory along with *.stub.php files for exported symbols. These C++ sources include embedded PHPX headers (phpx.h) that provide thin C++ wrappers around the Zend API, allowing generated code to call php::getFunction() and php::getClassEntrySafe() when interaction with the PHP runtime is required.
Stage 4: Compilation
Translator::compile() delegates to CompilerFactory to detect the platform-specific C++ compiler (Clang on macOS, GCC on Linux, or WASM compilers for WASI targets). The detected compiler builds object files from the generated C++ sources with user-specified optimization levels (-O3, etc.).
Stage 5: Linking
Finally, Translator::build() links the object files into the final artifact. The linker selection depends on the target platform—llvm-ld for WASI targets or standard ld for native Linux/macOS binaries—producing either an executable (.exe), shared library (.so or .dll), or PHP extension.
Stage 6: Execution (Optional)
When invoked with the -r or --run flag, TypePHP immediately executes the freshly linked binary, completing the edit-compile-run cycle in a single command.
Two-Phase Architecture: Prepare vs. Convert
TypePHP employs a deterministic two-phase design that separates symbol resolution from code generation, enabling cross-file optimization and self-hosting builds.
Prepare Phase builds the complete symbol model—including all functions, classes, and constants—without allocating runtime IDs. This global analysis allows the compiler to resolve every declaration before lowering begins, which is essential for handling circular dependencies and cross-file references.
Convert Phase uses the completed symbol model to lower each PHP file's body into C++ while preserving the original semantics. This separation guarantees that identical source code always yields identical C++ output, supporting aggressive caching and incremental builds.
The orchestration in src/compiler.php demonstrates this flow:
$files = $translator->prepare($translator->parseArgv($argv));
$sourceFiles = $translator->convert($files);
Native Type Mapping and ABI Optimization
TypePHP achieves near-C performance through aggressive type specialization via NativeTypeCompatibilityTrait located in src/TypeSystem/NativeTypeCompatibilityTrait.php.
When native scalar types are detected, the compiler emits direct ABI calls that bypass zval boxing entirely. The trait maps PHP return types to C++ equivalents, with special handling for edge cases:
protected function getReturnType(): string {
$type = $this->functionDef->returnType;
return $type === Type::STREAM ? Type::VAR : $type;
}
This mapping allows compiled functions to pass int64_t or double directly in CPU registers rather than allocating PHP variables on the heap, eliminating interpreter overhead for numeric computations.
Platform Abstraction and Compiler Detection
The compiler abstracts platform differences through PlatformFactory and CompilerFactory classes found in the src/Build directory.
Translator discovers the host OS and instantiates the appropriate platform handler:
$this->platform = PlatformFactory::create();
$this->cppCompiler = $this->platform instanceof Wasi
? $this->platform->getDefaultCompiler()
: CompilerFactory::detectCompilerName($this->cppCompiler);
This architecture allows TypePHP to target Linux, macOS, Windows, or WASI (WebAssembly System Interface) without modifications to the compilation logic. The factory pattern automatically detects available toolchains, selecting Clang on Darwin systems or GCC on Linux distributions.
Build Modes: Binary, Extension, and Library
TypePHP supports three output formats controlled via the -m or --mode CLI flag:
Binary Mode (-m bin) requires a main() function in the PHP source and produces a standalone executable that embeds the PHPX runtime. The resulting binary runs directly on the target CPU without requiring a system PHP installation.
Extension Mode (-m ext) generates a loadable PHP extension (.so on Linux, .dll on Windows) that can be loaded into standard PHP processes via php.ini or dl(). This mode allows selective optimization of performance-critical libraries while maintaining compatibility with existing PHP applications.
Library Mode (-m lib) creates a shared library accompanied by a .stub.php file, enabling compiled PHP code to be consumed by other TypePHP projects or C++ applications through a well-defined C interface.
Self-Hosting Capability
TypePHP is fully self-hosted: the compiler is written in PHP and can compile its own source code. The entry point bin/tpc.php implements a bootstrap process where it first compiles itself into a native tpc binary, then uses that binary to compile user projects.
This self-hosting loop validates the compiler's correctness and ensures the same AOT pipeline optimizes both the compiler infrastructure and end-user applications. Developers can build the compiler from source using:
php bin/tpc.php --dry # Generates C++ sources for inspection
Practical Code Examples
Compiling a Simple Script to Native Binary
Create a PHP file with an entry point:
<?php
// hello.php
function main(): void {
echo "Hello from native binary!\n";
}
Compile and execute:
php bin/tpc.php hello.php -O3 -o hello
./hello
The output produces a native executable that prints the greeting without loading the PHP interpreter.
Building a Loadable PHP Extension
Export functions for use in standard PHP environments:
<?php
// ext.php
function greet(string $name): string {
return "Hello, $name!";
}
Generate the shared object and test it:
php bin/tpc.php ext.php --mode=ext -o my_ext
php -d extension=./my_ext.so -r 'echo greet("World");'
Optimizing with Native Types
Enable direct C++ type mapping for arithmetic-intensive code:
<?php
use native_types;
function fib(int $n): int {
if ($n <= 2) return 1;
return fib($n-1) + fib($n-2);
}
function main(int $argc, array $argv): void {
$n = (int)($argv[1] ?? 30);
echo fib($n) . PHP_EOL;
}
Compile with maximum optimization:
php bin/tpc.php fib.php -O3 -o fib_bin
./fib_bin 35 # Executes significantly faster than interpreted PHP
Summary
- TypePHP implements AOT compilation by lowering PHP AST into C++17 source code through
Translator::convert()insrc/Translator.php. - Two-phase architecture separates symbol preparation from code generation, enabling cross-file optimization and deterministic builds.
- Native type mapping via
NativeTypeCompatibilityTraiteliminateszvaloverhead for scalar types, producing direct ABI calls. - Platform abstraction through
PlatformFactoryandCompilerFactorysupports Linux, macOS, Windows, and WASI targets. - Multiple build modes generate standalone binaries (
-m bin), PHP extensions (-m ext), or shared libraries (-m lib). - Self-hosting design allows the compiler to optimize its own codebase using the same pipeline it provides to users.
Frequently Asked Questions
How does TypePHP differ from PHP's built-in JIT compiler?
PHP's JIT compiles opcodes to machine code at runtime within the Zend engine, requiring the interpreter to remain resident in memory. TypePHP performs ahead-of-time compilation entirely before execution, producing standalone native binaries that run without the PHP interpreter or Zend engine loaded, except when explicitly calling back into PHP functions via the PHPX runtime.
What C++ standard does TypePHP target and why?
TypePHP targets C++17 as specified in the lowering phase of src/Translator.php. This standard provides modern C++ features like structured bindings, constexpr if, and improved type deduction that the PHPX runtime utilizes for efficient interoperability with the Zend API, while maintaining broad compiler support across Clang, GCC, and MSVC toolchains.
Can TypePHP compile any PHP code, or are there limitations?
TypePHP supports most PHP 7/8 syntax but requires explicit type declarations for functions intended for native execution to enable optimal code generation. Code using highly dynamic features like eval(), create_function(), or runtime class declaration modifications may be unsupported or fall back to slower runtime paths through the PHPX bridge. The compiler performs strict type checking during the prepare() phase to catch incompatibilities early.
How does the self-hosting process work in practice?
The self-hosting process begins with bin/tpc.php, which is PHP source code. When you run php bin/tpc.php, it interprets the compiler source, compiles the specified input files (potentially including itself), and generates a native binary named tpc. Subsequent invocations can use ./tpc instead of php bin/tpc.php, providing faster compilation times since the compiler itself runs as optimized native code rather than interpreted PHP.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →