Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Introduction to Swoole's AOT Compiler

Introduction to Swoole's AOT Compiler

@Larvel Live Japan Pre-Party

Avatar for Albert Chen

Albert Chen

May 24, 2026

More Decks by Albert Chen

Other Decks in Programming

Transcript

  1. THE TRUE NATIVE PHP From interpreter to native system language

    Introduction to Swoole's AOT Compiler A paradigm shift for PHP — from interpreted scripts to native binaries. PRESENTED BY Albert Chen target native binary runtime libphp.so + libphpx.so speedup up to 150×
  2. // SPEAKER Albert Chen Enjoy building high-concurrency architectures with PHP.

    Software Architect, Taiwan Laravel Artisan Swoole Enthusiast Creator of Hypervel x / @albert_cht 02 / 24 · SPEAKER hypervel.org HYPERVEL / CREATOR $ /usr/bin/ whoami
  3. // MYTH CHECK · 01 Is PHP really slow? Stock

    interpreters only — no JIT in the lineup. 1B NESTED-LOOP ITERATIONS · SECONDS (LOWER IS BETTER) 0 … 32s PHP php 8 · interpreter 10.76s Ruby cruby · interpreter 27.92s Python cpython 3.13 31.59s ~3× Stock PHP outruns CPython by ~3× and CRuby by ~2.6× on this workload — the interpreter itself is well-tuned. // 1B nested loops · benjdd.com/languages2 03 / 24 · PHP IS NOT SLOW stock interpreters
  4. // MYTH CHECK · 02 Add JIT — PHP keeps

    up. PHP JIT lands right next to PyPy — ~4.5× faster than the interpreter. 1B NESTED-LOOP ITERATIONS · SECONDS (LOWER IS BETTER) 0 … 32s PyPy python · jit 2.30s PHP JIT php 8 · opcache.jit=on 2.41s PHP php 8 · interpreter 10.76s Ruby YJIT miniruby --yjit 11.02s Ruby cruby · interpreter 27.92s Python cpython 3.13 31.59s ~4.5× PHP JIT closes the gap with PyPy and trounces every interpreter — among dynamic languages, PHP is fast. // 1B nested loops · benjdd.com/languages2 04 / 24 · WITH JIT opcache.jit=on
  5. // MYTH CHECK · 02 …but next to compiled languages,

    the gap is huge. This is why nobody uses PHP for CPU-bound workloads. SAME WORKLOAD · SECONDS 0 … 12s Rust --release 0.515s C gcc -O2 0.519s Go go build 1.05s PHP JIT opcache.jit=on 2.41s PHP interpreter 10.76s ~20× Even with JIT enabled, PHP is still ~5× slower than C/Rust. The interpreter is roughly 20× off. // 1B nested loops · benjdd.com/languages2 05 / 24 · THE REAL GAP interpreted vs compiled
  6. // PIPELINES Two ways to run a .php file. Same

    source, very different end states. // TRADITIONAL PHP interpreter loop SOURCE .php files FILE I/O ↓ ↓ RUN ZendVM — interpret cost per-opcode dispatch source readable — ships as plaintext .php PARSE Lexer · Parser ↓ TREE Abstract Syntax Tree ↓ COMPILE Zend Opcodes OPCACHE CACHES THIS SEGMENT // SWOOLE AOT native binary SOURCE .php files ↓ ANALYZE Parser + PhpAot Analyzer ↓ CODEGEN C++ via PHPX API ↓ COMPILE g++ / clang → .o ↓ LINK native executable cost cpu instructions only source protected — ships as native binary 07 / 24 · TWO PIPELINES interpret · vs · compile
  7. // PRODUCT · CONTEXT Swoole Compiler — a commercial PHP

    source-protection product. Same category as ionCube, Zend Guard, PHP Screw — not free, not open source. // THE MARKET · COMMERCIAL PHP PROTECTORS ionCube PHP encoder · loader-based Zend Guard Zend's own encoder · legacy PHP Screw Open-license obfuscator Swoole Compiler VM-layer hardening + SCCP All paid products. All sell the same promise: your customers can run your PHP without reading it. // HARDENING PASSES 01 Identifier Erase what humans read. Variable-name mangling Function-name mangling 02 Structure Hide what static analysis sees. Control-flow obfuscation Control-flow flattening Junk-code injection SCCP optimization 03 Runtime Wrap the executable in a vault. Bytecode encryption VM-protection layer 08 / 24 · PRODUCT CONTEXT commercial · paid
  8. // WHY THIS STILL MATTERS In the AI era, obfuscation

    alone isn't a moat anymore. The cost of de-obfuscation collapsed. The cost of building it didn't. ~2010 A specialist's job. DEFENDER'S ADVANTAGE 2026 A weekend prompt. DEFENDER'S ADVANTAGE // shipped.bin ENCRYPTED // raw payload · ELF / opcache blob 7f 45 4c 46 02 01 01 00 00 00 00 00 00 00 00 00 03 00 3e 00 01 00 00 00 50 1c 04 00 00 00 00 00 40 00 00 00 00 00 00 00 a8 9d 12 00 00 00 00 00 00 00 00 00 40 00 38 00 0d 00 40 00 23 00 22 00 06 00 00 00 04 00 00 00 40 00 00 00 00 00 00 00 e8 ff 7d a3 c1 b2 0e 4f 9d 71 6a 5c 4d 22 b9 18 a3 7f 1d 0c 6e f0 33 21 0a be 4d 7c 8e 91 d2 5b 5c 12 9a c4 6f 30 e1 22 7d 4b 8c 09 a1 b3 d6 11 b8 17 9c 4e a0 22 7d 8e 11 c3 0a 6b f9 4d 22 18 // llm · reverse unwrap → infer AI // recovered.php RESTORED } return $total; } 09 / 24 · THREAT MODEL obfuscation → AI → defeated
  9. // ARCHITECTURE Inside the PhpAot compiler. AOT-generated C++ calls into

    Zend Engine at runtime (zval / GC / ref-count via PHPX / Zend API). The final executable embeds Zend VM — fallback include/require runs inside it. PARSING & ANALYSIS COMPILE & LINK PHP source .php php-parser AST generation Abstract Syntax Tree (AST) PhpAot analyzer decides path AOT supported? yes (AOT) C++ code generator via PHPX API Native C++ files .cpp no · fallback include / require normal PHP load Zend compiler opcode array opcode → embedded Zend VM C++ compiler g++ / clang Object files .o System linker ld Final executable native code (AOT) embedded Zend VM (libphp.so) for fallback paths opcode → Zend VM exec libphp.so Zend runtime libphpx.so PHPX bridge php / runtime aot path generated c++ system tooling 11 / 24 · ARCHITECTURE aot → native code + embedded zend vm · fallback → embedded zend vm
  10. // BRIDGE · PHPX 2.0 PHPX — write C++ that

    looks like PHP. A Zend API wrapper, redesigned in 2025. Same dynamic feel, full GC, zero manual memory. // WHAT IT IS A header-only C++ library mirroring PHP's standard library. Built by the Swoole team in 2016 — fully redesigned for 2.0 in 2025. var keyword — assign any type, just like PHP. Operator overloading that follows PHP semantics. Automatic GC + refcounting — no leaks, no overruns. sizeof(Variant) == sizeof(zval) — 16 bytes, ABI-identical. var a = "hello world"; var b = 2025; var c = a; // shallow copy b += 100; a.append(b); // "hello world2125" // bit ops, comparisons — all PHP rules var d = b << 3; if (a == c) { ... } // list & associative arrays var e = {"php", "swoole", "is", "best"}; var f = { {"key", "value"}, {"key2", 2024.08} }; // dynamic typing in c++ 12 / 24 · PHPX php-flavoured c++ · gc · 16-byte variant
  11. // PHPX · API SURFACE Call PHP from C++ —

    no boilerplate. Functions, objects, constants — the entire stdlib is one include away. Call any PHP function directly — the entire stdlib. Built-in classes auto-generated by reflection. Predefined constants usable as-is. References via C++ & / * — familiar pointer semantics. // INSTALL composer create-project \ swoole/phpx-ext myext // call any PHP function var rs = file_get_contents("/tmp/file.txt"); var_dump(rs); // hashing var data = random_bytes(1024); var h1 = sha1(data); var h2 = hash("sha1", data); // objects — built-in classes Redis redis{}; redis.connect("127.0.0.1", 6379); redis.set("phpx_key", "hello phpx"); // predefined constants var v = PHP_VERSION; var o = PHP_OS; // example.cpp · PHPX 2.0 13 / 24 · PHPX IN PRACTICE stdlib · objects · refs
  12. // ROLE OF PHPX PHPX is the target language the

    AOT compiler emits. Your .php is rewritten into PHPX .cpp — line for line — then handed to the C++ compiler. main.php <?php use native_types; function main() { ini_set("precision", 17); $rounds = (int) file_get_contents( "./rounds.txt", true); $stop = $rounds + 2; $begin = microtime(true); $pi = 1.0; for ($i = 2; $i <= $stop; $i++) { $x = -1.0 + 2.0 * ($i & 0x1); $pi += $x / (2 * $i - 1); } $pi *= 4.0; print $pi; } 01 source // human-written CODEGEN main.cpp · PHPX #include <phpx.h> void php_main() { php::call("ini_set", {"precision", "17"}); php::Int rounds = (php::call("file_get_contents", {"./rounds.txt", true})).toInt(); php::Int stop = rounds + 2; php::Variant begin = php::call("microtime", {true}); php::Float pi = 1.0; php::Int i = 2; for (; i <= stop; i++) { x = -1.0 + 2.0 * (i & 1); pi += x / ((2) * (i) - (1)); } pi *= 4.0; php::echo(pi); } 02 phpx c++ // machine-emitted G++ / CLANG a.out · ELF // stripped · 39 KB 7f 45 4c 46 02 01 01 00 00 00 00 00 00 00 00 00 02 00 3e 00 01 00 00 00 b0 1c 40 00 00 00 00 00 40 00 00 00 00 00 00 00 a8 4f 00 00 00 00 00 00 00 00 00 00 40 00 38 00 // disasm — _php_main: push rbp mov rbp, rsp call php_call@PLT vmulsd xmm0, xmm0, xmm1 ret 03 native // no VM · pure cpu // 1:1 every PHP construct has a PHPX equivalent // RUNTIME linked against libphp.so + libphpx.so // YOU never write PHPX by hand — the compiler does 14 / 24 · PHPX IN AOT .php → .cpp → native
  13. // PRIOR ART Other PHP compilers fork the language. Swoole

    AOT doesn't. PROJECT MECHANISM ECOSYSTEM REALITY HHVM JIT → eventually a separate language (Hack) ✕ Diverged from PHP. Not Zend-compatible anymore. KPHP Transpiles a subset of PHP to C++ ✕ A separate PHP implementation. No PHP extensions. Swoole AOT Reuses ZendPHP runtime + PHPX ABI bridge ✓ Composer, all built-ins, all native extensions just work. // THE TRICK Swoole AOT doesn't reinvent the wheel. It throws out the opcode handler — not the runtime — and links straight against libphp.so . That's why every PHP extension keeps working. 15 / 24 · HHVM · KPHP · SWOOLE 100% extension compatibility
  14. // RULE · 01 Goodbye floating code — main() is

    the entry point. Top-level scripts have no place in a compiled binary; everything lives inside a function. The AOT compiler refuses unbounded top-level code. For bin mode, you supply a main() as the system entry. The signature mirrors C: int $argc, array $argv . // GOOD NEWS The PHP standard library is fully available inside main() . echo , var_dump , the lot. <?php // AOT entry point — must exist in bin mode function main(int $argc, array $argv): void { echo "Hello World!\n"; var_dump(PHP_VERSION); } app.php 16 / 24 · ENTRY POINT function main(): void
  15. // RULE · 02 No dynamic variable names — symbols

    are resolved at compile time. Variables become C++ memory addresses — $$name has nothing to bind to. extract() mints local symbols at runtime and can't be analysed. Migrate to explicit array keys or object properties. // TRADE-OFF You lose runtime symbol magic. You gain a compiler that can prove what your code touches. // ❌ AOT can't resolve runtime-named locals $name = "foo"; $$name = "bar"; extract(['a' => 1]); // ✅ Use an explicit map instead $data = []; $name = "foo"; $data[$name] = "bar"; // rule of thumb 17 / 24 · STATIC SYMBOLS no $$ · no extract()
  16. // RULE · 03 / 04 Async & control flow

    — trade VM tricks for native primitives. yield generators rely on a ZendVM state machine — gone. Use Swoole/Fiber real coroutines (Channels) for streaming. break 2 / continue 3 have no C++ counterpart. Reach for goto labels — or throw to escape. // ❌ generator — depends on the VM function getNumbers() { for ($i = 0; $i < 5; $i++) yield $i; } // ✅ Swoole coroutine + channel use Swoole\Coroutine\Channel; $chan = new Channel(5); Swoole\Coroutine::run(function () use ($chan) { for ($i = 0; $i < 5; $i++) $chan->push($i); }); // ❌ break 2 has no C++ equivalent // ✅ goto a label that the linker can resolve foreach ($arr1 as $a) foreach ($arr2 as $b) if ($a === $b) goto END_LOOP; // preferred patterns 18 / 24 · CONSTRAINTS yield → coroutines · break N → goto
  17. // RULE · 05 Strict signatures — and refval() for

    explicit references. Compiled functions act like C++ — wrong arity crashes the binary. For runtime- resolved closures, the compiler can't infer &$ref ; it defaults to by- value. Wrap arguments in refval(…) to pass by reference explicitly. // MENTAL MODEL Treat call sites like a C ABI: arity is a contract, references are an opt-in. $fn = getClosure(); $a = 1; $b = 2; $c = 0; // ❌ Arity mismatch — undefined behaviour // $fn($a, $b, $c, "extra"); // ❌ Compiler can't tell — defaults to by-value $fn($a, $b, $c); // ✅ refval() — explicit reference $fn($a, $b, refval($c)); // closures 19 / 24 · CALL CONVENTIONS refval() · pointer passing
  18. // BENCHMARK · 01 Recursive fib(40) — 135× faster, same

    source. ~/swoole-aot — bash $ ./cli.php examples/fib.php 40 102334155 Time: 14.816216945648193 $ ./fib examples/fib.php 40 102334155 Time: 0.10961484909057617 php cli → native binary 135 function fib(int $n): int { if ($n == 1 || $n == 2) { return 1; } else { return fib($n - 1) + fib($n - 2); } } function main(int $argc, array $argv): void { $n = $argv[2]; $begin = microtime(true); echo fib($n) . "\n"; echo "Time: " . (microtime(true) - $begin) . "\n"; } examples/fib.php × 20 / 24 · BENCHMARK · FIBONACCI recursive · -O3
  19. // BENCHMARK · 02 100 M iterations of π —

    use native_types wins. ~/swoole-aot — bash $ ./cli.php examples/pi.php int(100000002) 3.141592643589326 time: 2.2408719062805176 // after AOT compile, same loop: $ ./pi int(100000002) 3.141592643589326 time: 0.094 native_types · raw C ints · no zval dance 23 use native_types; function main() { ini_set("precision", 17); $rounds = (int) file_get_contents("./rounds.txt", tr $stop = $rounds + 2; var_dump($stop); $begin = microtime(true); $x = 1.0; $pi = 1.0; for ($i = 2; $i <= $stop; $i++) { $x = -1.0 + 2.0 * ($i & 0x1); $pi += $x / (2 * $i - 1); } $pi *= 4.0; examples/pi.php × 21 / 24 · BENCHMARK · π use native_types · -O3
  20. // SHIPPING One command. 39 KB binary. Source protection comes

    along for the ride. ~/projects/workerman — zsh $ ./swoole-compiler projects/workerman/src/ -o workerman [1/4] parsing 142 .php files … [2/4] codegen → 86 .cpp files [3/4] g++ -O3 -c (linking against libphp.so) [4/4] ld → ./workerman ✓ build complete · 0.94s $ ls -lh workerman -rwxr-xr-x 1 albert staff 377K workerman $ strip workerman && ls -lh workerman -rwxr-xr-x 1 albert staff 39K workerman $ ./workerman start // AFTER BUILD 377 KB // AFTER STRIP 39 KB // ALSO FREE Native compilation makes the source unrecoverable — protection without a separate obfuscator. // VALIDATED Workerman compiles to a single binary with only minor patches. 22 / 24 · BUILD & SHIP stripped · 39 KB
  21. // PROGRESSIVE ADOPTION Don't have a long-running process? Compile to

    a .so. Same Nginx + PHP-FPM stack — with a hot path that runs at C++ speed. Nginx → PHP-FPM → myapp.so Web request · standard pool · AOT-compiled hot path // 01 · BUILD -m ext emits a standard PHP dynamic library. // 02 · LOAD Drop one line in php.ini — like Redis or PDO. // 03 · USE Hot functions execute as native machine code, no VM dispatch. # 1. Build the extension $ ./bin/compiler.php examples/extension -O2 -m ext -o myap # 2. Enable in php.ini extension=myapp.so # 3. Call from any controller <?php $start = microtime(true); $result = myapp_heavy_calculation(1_000_000); echo "done in " . (microtime(true) - $start) . "s"; build & load 23 / 24 · EXT MODE nginx + php-fpm + .so
  22. // ROADMAP Redefining what PHP can be. WHEN STAGE TARGET

    & POSITIONING 2026.05 Preview First public preview — including direct calls into Python from PHP. 2026.10 Beta Most known issues resolved; ready for non-critical workloads. 2027.05 General Availability Production-ready — PHP as a static, compiled, system-grade language. Swoole-Compiler v4 isn't just an optimizer. It's the moment PHP becomes a statically-compiled language. 24 / 24 · ROADMAP preview · beta · GA
  23. END / Q&A 24 / 24 · native php is

    shipping Thanks for listening Thank you. Questions welcome. ask about anything benchmarks · codegen · PHPX · roadmap · licensing speaker Albert Chen project swoole · hypervel contact @albert · github.com/swoole >_