Compilation pipeline
How the PHP compiler
turns code into machine code
Twelve well-defined phases compile your PHP or LFC source into native output. The compilation pipeline transforms PHP code into validated EIR, runs fixed-point IR optimization passes, then lowers it into optimized assembly for the selected target, then assembles and links it into native output. No Zend Engine, VM, interpreter, or opcode fallback is involved in the compiled path.
Classify
Every physical source file is classified independently from its path: `.lfc` starts in code mode with no `<?php` tags and always enables elephc extensions, while every other suffix keeps tagged-PHP behavior. With `--strict-php`, each PHP-mode AST is then audited for elephc-only constructs before any later pass runs.
Lexer
Character-by-character scanning of PHP source into keywords, operators, literals, and identifiers.
Parser
Pratt parser with binding powers builds a structured tree of expressions and statements.
Resolver
Builds the compile-time autoload registry from Composer metadata and SPL rules, lowers per-file PHP magic constants, runs the ifdef/--define conditional pass, pre-scans statically-resolvable include targets for declarations, folds compile-time include path expressions, lowers include_once and require_once runtime guards, runs static autoload file insertion for referenced classes, then resolves namespaces and use imports to fully qualified names — merging multiple files into one AST. Function-argument introspection (func_num_args, func_get_args) is desugared and the OPcache script manifest is baked before optimization begins.
Type Checker
Infers and validates types for every variable and expression. Catches errors before assembly is emitted.
Optimizer
Constant folding, constant propagation, control-flow pruning and normalization, and dead-code elimination with CFG-lite reachability. Conservative by design — rewrites only happen when the shape is statically provable.
Reachability
Drops unreachable functions, classes, methods, and injected prelude declarations before EIR lowering. Dynamic calls, eval, unserialize, and Reflection widen the retained surface conservatively.
EIR Lowering
The checked, optimized AST is lowered into elephc IR (EIR): a PHP-shaped SSA intermediate representation with explicit ownership, effects, and basic blocks. The module is validated before optimization and assembly.
EIR Optimization
A module-level fixed-point pipeline interleaves a cross-function small-function inliner with per-function passes: identity arithmetic folding, peephole rewrites, immutable-local classification, checked-integer sinking, constant folding, common-subexpression elimination, loop-invariant code motion, dead instruction and store elimination, and branch simplification. Re-validates after every pass in debug/test builds.
Register Allocation
A Poletto-Sarkar linear-scan allocator runs liveness analysis, builds live intervals, and assigns separate integer and float register pools so hot scalar values survive calls in callee-saved registers.
EIR Codegen
The validated, register-allocated EIR module is translated to annotated assembly for macOS ARM64, Linux ARM64, Linux x86_64, iOS ARM64, or the iOS ARM64 Simulator. Each line is commented explaining what and why, and a v2 source map is written alongside the `.s` file.
Link
The host assembler produces object code. Locked managed native packages, the cached runtime object, and bridge inputs become a typed link plan, then the linker produces a standalone executable, shared library, or static library for the selected target.
Code examples
Write PHP.
Compile to native.
200+ working PHP code examples ship with the compiler. Each one compiles to a standalone native binary and produces identical output to the PHP interpreter. From hello world to inheritance and exceptions — all compiled to native machine code.
All 200+ included examples
Documentation
Learn how a
PHP compiler works
13 guides covering every aspect of building a PHP compiler — from lexing and parsing, to type checking, code generation, and the ARM64 instruction set. No prior assembly or compiler knowledge required.
What is a compiler?
The big picture: source code in, binary out
How elephc works
The full pipeline walkthrough, step by step
The Lexer
How source text becomes a stream of tokens
The Parser
How tokens become an AST with Pratt parsing
The Type Checker
Static types, inference, and error detection
The Optimizer
Folding, propagation, pruning, normalization, dead-code elimination
The Codegen
How the AST becomes ARM64 assembly
The Runtime
Runtime routines: strings, arrays, exceptions, hash tables, I/O
Memory Model
Stack, heap, ref counting, copy-on-write, cycle collection
ARM64 Assembly
ARM64 primer for people who've never seen assembly
ARM64 Instructions
Quick reference for every instruction elephc uses
Architecture
Module map, file counts, conventions
Eval Runtime
How eval() mixes AOT lowering with the Magician interpreter bridge
"Every line of Rust that emits ARM64 assembly is annotated with an inline comment explaining what it does and why."
From stack frame setup to syscall invocation, from integer-to-string conversion to array memory layout.
If you've ever wondered what happens between echo "hello" and the CPU executing it,
follow the code from src/codegen/ and read the comments.