typephp的编译原理
TypePHP 编译原理深度分析
一、整体定位
TypePHP 是一个 PHP → C++17 → 原生机器码 的 AOT(Ahead-Of-Time)编译器,由 Swoole 团队开发,完全用 PHP 编写且可自举(用 tpc 编译编译器自身的 PHP 源码得到原生二进制)。
与 OPcache(字节码缓存)和 PHP JIT(trace 编译)不同,TypePHP 不经过 Zend VM 解释执行,而是直接生成 CPU 原生指令。
二、核心编译流水线
从入口 src/compiler.php 的 main() 函数可以清晰看到五阶段流水线:
PHP 源码 + .stub.php + 可选 C/C++ 源码
│
▼
① prepare() —— 解析、校验、收集全部声明(符号表)
│
▼
② convert() —— 将函数体和常量表达式降级为 C++17
│
▼
③ compile() —— 原生 C++ 编译器 + PCH/对象缓存 → .o
│
▼
④ build() —— 链接 → 可执行文件 / PHP扩展 / 共享库 / WASI
│
▼
⑤ run() —— 可选:立即执行
三、阶段一:prepare(符号收集)
3.1 核心设计:两阶段分离
这是 TypePHP 最关键的架构决策。prepare 阶段只建立完整符号模型,不分配运行时 Cache ID,不解析常量表达式。常量和声明默认值只保留 AST,待全部项目符号就绪后再在 convert 阶段解析。
这一两阶段设计保证多文件构建和编译器自举过程具有确定性——避免前向引用导致的顺序依赖。
3.2 prepare 执行步骤(SourcePipelineTrait::prepare())
| 步骤 | 说明 |
|---|---|
| 文件收集 | 支持单文件、目录、project.yml 三种输入;过滤 ignore 路径 |
| 运行时校验 | 验证 PHP 8.4+ 头文件、PHPX 库、C++ 工具链 |
| Native class 发现 | 轻量预扫描,提前识别 #[Native] 类,解决前向引用 |
| 逐文件 prepareFile | 解析 AST → 名称解析 → 验证 → 收集声明 |
| Native global 发现 | 所有声明就绪后,解析 Native Object 全局变量的指针 ABI |
| 拓扑排序 | 按类继承/依赖关系排序文件 |
3.3 prepareFile 内部流程(Preprocessor::prepareFile())
源码 → php-parser 解析 AST
→ NameResolver(命名空间解析)
→ VoidCastValidationVisitor((void) 丢弃表达式校验)
→ Visitor(通用变换/警告)
→ ConstantExpressionValidationVisitor(常量表达式校验)
→ RuntimeAttributeFactoryLowering(运行时 Attribute 工厂降级)
→ 遍历顶层语句,收集:
├── Stmt_Namespace → prepareNamespace
├── Stmt_Class/Trait/Enum → prepareClass
├── Stmt_Interface → parseInterface
├── Stmt_Function → prepareFunction
├── Stmt_Use/GroupUse → 导入表
├── Stmt_Const → parseConstDef
└── Stmt_Expression → foundStrayCode(全局作用域禁止可执行语句)
处理后的 AST 保存在 $this->preparedFileAsts[$file] 中,供 convert 阶段复用。
四、阶段二:convert(C++ 代码生成)
4.1 finalizeDeclarationExpressions
在生成任何函数体之前,先遍历所有文件的已保存 AST,解析所有声明级常量表达式:
- 类常量值
- 属性默认值
- 函数参数默认值
- 全局常量
此时完整符号表已就绪,可以安全地解析跨文件常量引用。
4.2 逐文件 convertFile → doConvert
每个 PHP 文件生成一个对应的 .cpp 翻译单元:
重新解析 AST(与 prepare 相同的 Visitor 链)
→ 重置文件/命名空间/类/方法/函数上下文
→ 遍历顶层语句生成 C++:
├── Stmt_Namespace → parseNamespace(生成命名空间包裹)
├── Stmt_Class/Trait/Enum → parseClass(生成类实现 + 注册元数据)
├── Stmt_Function → parseFunction(生成函数体)
├── Stmt_Const → parseConstDef(生成常量)
└── Stmt_Interface → 仅校验,不生成实现
→ 生成类包装器(genClassWrapper):Zend class_entry 注册
→ 生成函数包装器(genFunctionWrapper):Zend 函数注册 + 参数检查
→ 生成 const 数据块(内嵌二进制数据)
→ 拼接 include 头文件
→ 写入 .cpp 文件
→ 生成 .stub.php(IDE 辅助/库导出声明)
4.3 全局产物
除了每个文件的 .cpp,convert 阶段还生成:
| 产物 | 说明 |
|---|---|
php_{name}_func_decl.h |
所有编译函数的 C++ 声明(extern php_xxx(...)) |
php_{name}_data_decl.h |
运行时数据声明(类表、函数表、字面量字符串表) |
| 扩展模块源文件 | get_module() 入口、Zend 模块注册、MINIT/RINIT 钩子 |
*.stub.php |
库模式下的 PHP 导入声明(供其他 PHP 项目引用) |
4.4 函数命名 ABI
所有编译后的 PHP 函数在 C++ 中以 php_ 前缀命名,命名空间分隔符 \ 转为 __:
namespace App\Service;
function calculate(int $x): int { ... }
→
extern int64_t php_App__Service__calculate(int64_t x);
五、阶段三:compile(C++ 编译)
5.1 预编译头(PCH)
PrecompiledHeaderManager 将公共头文件(phpx.h、typephp_helper.h、GMP/MPFR 等)预编译为 .gch/.pch,大幅加速增量编译。PCH 失败时自动降级为普通编译,不阻塞构建。
5.2 并行编译
- Unix/Linux/macOS:使用
pcntl_fork()多进程并行编译,由NativeBuilder::dispatchParallel()调度 - Windows:顺序编译(
compileSourceFile) - 支持
-j/--job指定并行度(默认 4)
5.3 编译单元构成
每个 .cpp 编译为 .o,此外还包括:
- PHPX 运行时源文件(
typephp_runtime.cc、typephp_main.cc) - CLI 进程标题支持(
php_cli_process_title.c、ps_title.c) - Windows 资源文件(
.res:图标、版本信息)
六、阶段四:build(链接)
NativeBuilder::link() 调用平台链接器,将所有 .o + .res + PHPX/libphp/第三方库链接为最终产物。
三种构建模式
| 模式 | 产物 | 入口 | 运行时依赖 |
|---|---|---|---|
bin(默认) |
原生可执行文件 | 全局 main() 函数 |
嵌入 libphp + PHPX |
ext |
PHP 扩展 .so/.dll |
get_module() |
宿主 PHP SAPI(php-fpm/CLI) |
lib |
共享库 + .stub.php |
导出符号表 | 嵌入 libphp + PHPX |
七、关键技术机制
7.1 原生类型系统(use native_types)
当文件声明 use native_types; 后,标量类型直接映射为 C++ 原生类型:
| PHP 类型 | C++ 类型 | 效果 |
|---|---|---|
int |
int64_t |
算术运算直接编译为 CPU 指令,无 ZendVM 调用 |
float |
double |
同上 |
bool |
bool |
同上 |
string |
php::String |
PHPX 封装的二进制安全字符串 |
array |
php::Array |
Zend 数组(有序哈希映射) |
object |
php::Object |
Zend 对象 |
这是性能提升的核心来源——数值密集代码可获得数量级加速。
7.2 强类型容器(std:: 前缀)
std::vector、std::array、std::map、std::ordered_map 的元素类型在编译期确定,直接映射为 C++ std::vector 等容器,最高比 PHP 数组快 10 倍,性能接近手写 C++。
7.3 SSA/e-SSA 优化器
Analysis\SsaBuilder 实现了基于 php-src Zend Optimizer 的 SSA(静态单赋值)机制,适配到 php-parser AST:
- 构建 CFG:从语句列表生成基本块
- 计算支配树:迭代算法
- 计算支配边界:用于 φ 函数放置
- 放置 φ 函数:在支配边界汇合点
- 变量重命名:遍历支配树转为 SSA 形式
特殊处理:
goto:创建非局部 CFG 边unset($var):标记为 "undefined" SSA 变量&$var引用:标记为 "escaped",函数调用后视为未知值
e-SSA 的 Pi 节点记录类型收窄(如 if ($x instanceof Foo) 后真分支中 $x 类型收窄为 Foo),支撑后续的类型传播优化。
7.4 编译期 Attribute 代码生成
在 class lowering 阶段消费内置 Attribute,零运行时开销生成类型安全方法:
| Attribute | 生成 |
|---|---|
#[Getter] |
public function getName(): T |
#[Setter] |
public function setName(T $name): void |
#[With] |
public function withName(T $name): static(克隆+修改) |
#[Constructor] |
将属性加入自动生成的 __construct() |
#[Printer] |
__toString() |
#[Arrayable] |
toArray() |
#[Immutable] |
编译期只读契约检查 |
#[ArrayDef] |
强类型数组结构元数据 |
7.5 混合 C++/PHP 互操作
通过 .stub.php 声明文件桥接:
// math.cpp
#include <phpx.h>
Int php_fast_sum(Int a, Int b) { return a + b; }
// math.stub.php —— 仅声明,函数体为空
function fast_sum(int $a, int $b): int {}
C++ 符号的 php_ 前缀属于 TypePHP callable ABI,stub 只提供类型元数据。
7.6 通用方法(Universal Methods)
在原生类型上直接调用方法($s->upper()、$arr->contains()、$big->mul(2)),静态类型已知时在编译期直接解析为 C/C++ 函数调用——无虚表查找、无反射、无运行时派发。
八、运行时架构
8.1 PHPX 运行时层
生成的 C++ 代码通过 PHPX(Swoole 开发的 C++ PHP 扩展框架)与 Zend runtime 互操作:
- 动态 PHP 值(
php::Var、php::Ref、php::Array、php::Object) - 内置函数调用(
php::fn::call("strlen", ...)) - 类注册、对象创建、属性访问
- 异常抛出与捕获
- 反射与对象元数据
8.2 双轨执行模型
用户函数/方法 → 编译为原生 C++ 函数,直接执行(不再走 Zend opcode)
内置函数/动态操作 → 通过 PHPX 调用 Zend runtime(fallback)
这意味着编译后的代码中,用户定义的函数是原生执行的,而 strlen()、array_map() 等内置函数以及动态特性(变量函数、$$var、反射)仍通过 Zend 执行。
九、自举(Bootstrap)
TypePHP 编译器本身完全用 PHP 编写,自举链路为:
PHP 解释器执行 bin/tpc.php(编译器源码)
→ 编译编译器自身的 src/ 目录
→ 生成原生 tpc 二进制
→ 用原生 tpc 再次编译自身(验证自举一致性)
自举测试是 CI 的重要环节,因为自举执行可能暴露 PHP-hosted 编译器不会经过的动态调用或 ABI 路径。
十、与同类方案的对比
| 特性 | TypePHP | KPHP | PeachPie | HHVM |
|---|---|---|---|---|
| 目标语言 | C++17 | C++ | C# / .NET | C++(HHBC) |
| 类型系统 | 渐进式(use native_types) |
强制静态类型 | .NET 类型系统 | 动态+JIT |
| PHP 兼容性 | 明确子集 | 严格子集 | 较完整 | 较完整(已停止) |
| 自举 | 是(纯PHP) | 否(C++写的编译器) | 否(C#写的编译器) | 否(C++写的编译器) |
| 运行时 | PHPX + libphp | 自建运行时 | .NET CLR | 自建 VM |
| 输出 | bin/ext/lib/WASI | 可执行文件 | .NET 程序集 | 字节码+JIT |
十一、未来方向:Backend-Neutral IR
当前 TypePHP 直接将 PHP AST 降级为 C++,这将语义分析、临时值管理、引用行为和 C++ 格式化耦合在一个阶段。docs/BACKEND_NEUTRAL_IR.md 规划了未来的中间表示:
PHP Parser AST
→ Bound AST(绑定语义信息:解析后的名称、作用域、类型、引用信息)
→ PHP Semantic IR(描述 PHP 行为,不含 C++ 实现细节)
→ Backend Lowering
├── C++/Zend Runtime IR → C++
├── JavaScript Runtime IR → JS
└── WASM Runtime IR → wasm/wat
核心原则:IR 必须描述 PHP 语义而非 C++ 实现,php::Var、zend_*、zval* 等属于 C++ 后端,不应出现在语义 IR 中。
总结
TypePHP 的编译原理可以概括为:
- 两阶段确定性构建:prepare 收集符号(不解析常量)→ convert 生成代码(符号就绪后解析常量),解决前向引用和多文件顺序问题
- AST 直接降级 C++:基于 nikic/php-parser,通过大量 Trait 分模块处理各类语法节点,生成 C++17 代码
- 渐进式原生类型:
use native_types标量直接映射 C++ 原生类型,std::容器映射 C++ STL,其余保持 PHP 动态语义 - PHPX 桥接 Zend:用户函数编译为原生代码,内置函数和动态特性通过 PHPX 调用 Zend runtime
- SSA 优化:移植 php-src 的 SSA/e-SSA 机制到 AST 层面,支撑类型传播和死代码消除
- 三模式输出:同一套代码可编译为独立可执行文件、PHP 扩展或共享库
- 纯 PHP 自举:编译器自身用 PHP 编写,可编译自身生成原生二进制
QQ二群 166427999
如果项目有技术瓶颈问题,请联系↓↓
QQ: 903464207
微信: zx903464207
浙公网安备 33010602011771号