typephp的编译原理


TypePHP 编译原理深度分析

一、整体定位

TypePHP 是一个 PHP → C++17 → 原生机器码 的 AOT(Ahead-Of-Time)编译器,由 Swoole 团队开发,完全用 PHP 编写且可自举(用 tpc 编译编译器自身的 PHP 源码得到原生二进制)。

与 OPcache(字节码缓存)和 PHP JIT(trace 编译)不同,TypePHP 不经过 Zend VM 解释执行,而是直接生成 CPU 原生指令。


二、核心编译流水线

从入口 src/compiler.phpmain() 函数可以清晰看到五阶段流水线:

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.htypephp_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.cctypephp_main.cc
  • CLI 进程标题支持(php_cli_process_title.cps_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::vectorstd::arraystd::mapstd::ordered_map 的元素类型在编译期确定,直接映射为 C++ std::vector 等容器,最高比 PHP 数组快 10 倍,性能接近手写 C++。

7.3 SSA/e-SSA 优化器

Analysis\SsaBuilder 实现了基于 php-src Zend Optimizer 的 SSA(静态单赋值)机制,适配到 php-parser AST:

  1. 构建 CFG:从语句列表生成基本块
  2. 计算支配树:迭代算法
  3. 计算支配边界:用于 φ 函数放置
  4. 放置 φ 函数:在支配边界汇合点
  5. 变量重命名:遍历支配树转为 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::Varphp::Refphp::Arrayphp::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::Varzend_*zval* 等属于 C++ 后端,不应出现在语义 IR 中。


总结

TypePHP 的编译原理可以概括为:

  1. 两阶段确定性构建:prepare 收集符号(不解析常量)→ convert 生成代码(符号就绪后解析常量),解决前向引用和多文件顺序问题
  2. AST 直接降级 C++:基于 nikic/php-parser,通过大量 Trait 分模块处理各类语法节点,生成 C++17 代码
  3. 渐进式原生类型use native_types 标量直接映射 C++ 原生类型,std:: 容器映射 C++ STL,其余保持 PHP 动态语义
  4. PHPX 桥接 Zend:用户函数编译为原生代码,内置函数和动态特性通过 PHPX 调用 Zend runtime
  5. SSA 优化:移植 php-src 的 SSA/e-SSA 机制到 AST 层面,支撑类型传播和死代码消除
  6. 三模式输出:同一套代码可编译为独立可执行文件、PHP 扩展或共享库
  7. 纯 PHP 自举:编译器自身用 PHP 编写,可编译自身生成原生二进制

posted on 2026-08-26 14:01  zh7314  阅读(8)  评论(0)    收藏  举报