diff --git a/docs/hhvm-review.md b/docs/hhvm-review.md new file mode 100644 index 00000000..3998309b --- /dev/null +++ b/docs/hhvm-review.md @@ -0,0 +1,569 @@ +# HHVM/HHBBC 编译器 Review:全程序分析、类型系统与优化管道 + +> 2026-06-11 · 基于 /home/swoole/workspace/cpp/hhvm 源码审查 + +--- + +## 一、架构概览 + +HHVM 的编译体系分为三层: + +| 层级 | 组件 | 职责 | +|------|------|------| +| 前端 | HackC (hphp/hack) | Hack/PHP 源码 → HHAS (Hack Assembly) | +| 中端 | HHBBC (hphp/hhbbc) | HHBC bytecode → 优化后 HHBC bytecode | +| 后端 | JIT (hphp/runtime/vm/jit) | HHBC → x86-64 机器码 (运行时 tracing JIT) | + +HHBBC(~80k 行 C++)是一个**全程序字节码优化器**,基于不动点迭代分析。核心文件: + +| 文件 | 行数 | 用途 | +|------|------|------| +| `interp.cpp` | 6,685 | 抽象解释器(forward dataflow) | +| `index.cpp` | 30,132 | 全程序索引:类型信息、依赖追踪、增量重分析 | +| `type-system.h` | 1,610 | 类型 lattice 定义(trep + specialization) | +| `type-system.cpp` | 8,451 | 类型操作实现(meet/join/subtype/union) | +| `dce.cpp` | 3,102 | 类型感知的死代码消除(局部 + 全局) | +| `analyze.cpp` | 2,293 | 函数级数据流分析驱动 | +| `optimize.cpp` | 962 | 类型感知优化 pass | +| `cfg-opts.cpp` | ~300 | CFG 优化(不可达块删除、异常边简化) | + +与 AOT Compiler 的对比: + +| 维度 | HHBBC | AOT Compiler | +|------|-------|-------------| +| 编译目标 | HHBC → 优化 HHBC → JIT 机器码 | PHP → C++ → 二进制 | +| 分析级别 | 字节码级(全程序) | AST 级(单文件) | +| IR 形式 | php::Func (factored CFG + Bytecode) | PHP-Parser AST | +| 类型推理 | trep + specialization + 不动点迭代 | SSA + manual type annotations | +| 优化范围 | 全程序(跨文件/跨函数) | 单函数 + 类层次 | +| 调用图 | 动态构建(Index + assumption) | 静态(classExtends + classMethodOverride) | +| 并发 | 函数级并行分析 | 串行执行 | +| 运行时 | HHVM(JIT + refcounting GC) | phpx(RAII 包装 Zend API) | +| 代码量 | ~80k 行 C++(仅 HHBBC) | ~15k 行 PHP | + +--- + +## 二、可借鉴的创新设计 + +### 2.1 全程序不动点分析(Whole-Program Fixed-Point) + +**文件**: `hphp/hhbbc/README`, `analyze.cpp`, `index.cpp` + +HHBBC 的核心算法:在**全程序范围**内进行迭代收敛分析。 + +``` +算法流程: +1. 初始化 work list = 所有函数/类 +2. 并行分析每个 work unit(只读 Index,产出一个新结果) +3. 单线程:将新结果合并到 Index +4. 如果 Index 有更新 → 把依赖该信息的函数加回 work list +5. 重复直到 Index 不动点(信息不再变化) +6. 最终并行优化 pass +``` + +关键设计原则: +- Index 中的信息**只能收缩**(shrinking types:变得更精确) +- 函数内分析的类型**只能增长**(growing types:在分析上下文中累积) +- Index 信息**永远不会错误**(soundness guaratee)——只可能不够精确 + +**AOT 借鉴优先级: P0** + +当前 AOT 是单文件串行编译,没有跨文件/跨函数分析。可以借鉴: + +1. **Index 结构**:存储每个函数的返回类型、参数类型、属性类型等推断结果 +2. **依赖追踪**:记录每次查询("函数 F 查询了类 C 的方法 M 的返回类型"),在 C::M 类型更新时将 F 加回 work list +3. **迭代收敛**:初始化所有函数为 "未知" → 逐轮分析 → 直至类型信息不再变化 + +实现建议: +```php +// Preprocessor 中构建 +class AnalysisIndex { + // 函数返回类型(每轮迭代中收缩) + public array $returnTypes = []; // funcName => TypeInfo + + // 依赖图:funcName => [被依赖的 funcName, ...] + public array $dependencies = []; + + // 函数入参状态(从调用方收集) + public array $paramTypes = []; // funcName => [argIdx => TypeInfo] + + // 公共静态属性状态 + public array $publicStaticProps = []; // className::prop => TypeInfo +} +``` + +然后按拓扑顺序反复分析函数,直到 `$returnTypes` 和 `$paramTypes` 不变。 + +--- + +### 2.2 Trep 类型格(Bitset Type Lattice) + +**文件**: `hphp/hhbbc/type-system.h`, `type-system-bits.h`, `type-system-detail.h` + +HHBBC 的类型系统是整个优化器的基石,设计精巧: + +**Base 机制 — trep (type representation):** +```cpp +// 每个"基本类型"是一个 bit +BUninit, BInitNull, BFalse, BTrue, BInt, BDbl, +BCls, BLazyCls, BFunc, BClsMeth, BEnumClassLabel, +BObj, BRes, BRFunc, BRClsMeth, +BSStr, BCStr, // 不可数/可数字符串 +BSVec, BCVec, BSVecE, BCVecE, BSVecN, BCVecN, // 数组的 counted × empty 维度 +// ... Dict, Keyset 同理 +``` + +类型通过 bitset 组合表示联合类型。例如 `Int|String` 就是 `BInt|BStr`。 + +**Specialization(特化)——类型附带的额外信息:** +``` +Int=n — 已知常量整数值 +Dbl=n — 已知常量浮点值 +{S,C}Str=s — 已知常量字符串值 +Obj{<}=c — 已知类类型(精确或子类) +Arr(T1,T2,...) — 数组形状已知(packed array) +Arr([T1:T2]) — 数组键值类型已知 +``` + +**关键创新:** +- `counted` / `uncounted` 维度:字符串和数组区分是否引用计数,允许编译器针对不可数(static/常量)值做更激进优化 +- `empty` / `non-empty` 维度:数组区分空/非空,消除冗余的 empty 检查 +- Monotonic lattice:类型只能向一个方向变化(Index 中的类型只能收缩,分析中的类型只能增长) + +**AOT 借鉴优先级: P0** + +当前 AOT 使用 `TYPE_INT`, `TYPE_STRING`, `TYPE_ARRAY` 等离散常量,没有联合类型、不可数标记等细粒度信息。可以: + +1. 用 bitset 表示联合类型(`int|string` 而不是 `mixed`) +2. 添加 `Immutable` 标记(编译期可证明不可变的值) +3. 添加 `NonEmpty` 标记(编译期可证明非空的数组) +4. 构建类型格(lattice),定义 meet(⊓)和 join(⊔)操作 + +```php +// 当前 +public const TYPE_INT = 1; + +// 建议:bitset +class Type { + const BINT = 1 << 0; + const BSTRING = 1 << 1; + const BBOOL = 1 << 2; + const BFLOAT = 1 << 3; + // ... + const BIMMUTABLE = 1 << 16; // 不可变标记 + const BNONEMPTY = 1 << 17; // 非空标记 + + // union type: int|string = BINT | BSTRING + // meet: narrower = more specific + // join: wider = more general +} +``` + +--- + +### 2.3 抽象解释器(Abstract Interpreter) + +**文件**: `hphp/hhbbc/interp.cpp` (6,685 行), `interp.h` + +HHBBC 使用**抽象解释器**进行函数级的类型推导。这与传统的 SSA + constraint solving 不同: + +``` +算法(analyze_func): +1. 初始化 entry block 的输入状态(参数类型 = Index 提供的保守假设) +2. Work list ← entry blocks +3. While work list not empty: + a. 取出一个 block + b. 在 block 上逐指令运行抽象解释器 + c. 遇到可能抛异常的指令 → 传播当前状态到异常边 + d. 遇到分支指令 → 传播分支后状态到 taken 边 + e. Block 可能 fallthrough → 传播最终状态到 fallthrough 边 + f. 如果目标 block 的输入状态改变了 → 加入 work list +``` + +抽象解释器的关键特性: +- **状态传播**: 每条指令后计算新的 `State`(locals + eval stack 类型信息) +- **Factored CFG**: 异常边被单独建模(`FactoredExitBlock`),普通指令不会因为 "可能抛异常" 而打断基本块 +- **类型特化**: 分支条件自动收窄类型(如 `if (is_int($x))` → then 分支中 `$x` 收窄为 `Int`) +- **常量传播**: 内置常量折叠和值跟踪 + +**AOT 借鉴优先级: P1** + +当前 AOT 的 `SsaTypeOptimizer` 做了一定程度的类型收窄,但: +- 没有 factored CFG 概念(异常不参与数据流) +- 没有抽象解释器框架(每条指令定义 `step(state) → new_state`) +- 没有迭代数据流分析 + +可以在 SSA 之上实现轻量级的抽象解释: +```php +class AbstractInterpreter { + // 对每个 SSA 基本块运行抽象解释 + function analyzeBlock(Block $block, State $inputState): State { + foreach ($block->instructions as $instr) { + $inputState = $this->step($instr, $inputState); + } + return $inputState; + } + + // 每条指令定义如何变换状态 + function step(Instruction $instr, State $state): State { + // switch on instruction type + // return new State with updated types + } +} +``` + +--- + +### 2.4 类型感知死代码消除(Type-Aware DCE) + +**文件**: `hphp/hhbbc/dce.cpp` (3,102 行) + +HHBBC 的 DCE 不同于传统的 liveness-based DCE——它**结合类型分析**来发现更多死代码: + +``` +两种 DCE: +1. Local DCE — 单个基本块内 + - 向后遍历 block + - 维护 "反向栈":标记哪些 eval stack slot 未来会被使用 + - 未被使用的栈 slot → 产生该 slot 的指令可被删除 + - 未被使用的 local store → 消除 + +2. Global DCE — 跨基本块 + - 对 locals 做 liveness 分析 + - 允许跨 block 消除死 store +``` + +关键设计:**类型感知**—DCE 需要知道每条指令的类型才能正确判断。例如: +- 如果 `$x` 在某条路径上类型为 `Bottom`(不可达),使用 `$x` 的代码可能是 dead code +- 如果 `$x` 是 counted 类型,相关 inc/dec ref 操作不能被消除 + +**AOT 借鉴优先级: P2** + +当前 AOT 的 DCE 基本依赖 GCC/Clang 的 `-O2`。可以增加编译期的 local DCE: +- 消除对未使用局部变量的赋值 +- 消除无副作用的纯计算(如果结果未被使用) +- 利用类型信息判断操作是否可能有副作用 + +--- + +### 2.5 DataType 编码:3-of-7 纠错码 + +**文件**: `hphp/runtime/base/datatype.h` + +HHVM 的运行时类型标签使用极巧妙的位编码: + +```cpp +// DataType 是 uint8_t +// - bit 0 (LSB): countedness — 0 = 确定不可数 +// - bits 1-7: 3-of-7 纠错码 — 恰好 3 个 bit 为 1 + +// 类型检测变为简单的位操作: +// 检查是否是 Vec 或 Dict: dt <= KindOfVec +// 检查是否有 persistent 版本: dt <= KindOfString +// 检查是否是 null/uninit: dt >= KindOfUninit +``` + +3-of-7 编码的特性: +- 恰好 3 个 bit 为 1 的 8-bit 值共 C(7,3) = 35 个(每对 persistent/counted 共用同一个 3-of-7 码) +- 任一类型的检测:`(dt & type_mask) == type_tag` 两指令完成 +- unsigned LT/GT 比较实现高效类型分组检测 + +**AOT 借鉴优先级: P4** + +这对 AOT 编译器的**生成代码质量**有启发——但主要在 phpx 层。可以考虑给 phpx 的 `Variant` 类型标签使用更高效的编码,优化 `is_int()`/`is_string()` 等运行时类型检查。 + +--- + +### 2.6 RepoAuthType:字节码空间高效类型存储 + +**文件**: `hphp/runtime/base/repo-auth-type.h`, `repo-auth-type-tags.h` + +HHBBC 将分析得到的类型信息编码为 `RepoAuthType`,嵌入字节码流(`AssertRAT` 指令),供 JIT 使用。 + +设计要点: +- 紧凑编码(`CompactTaggedPtr`):类型 tag + 可选指针(类名/数组形状)打包在一个指针宽度 +- 覆盖从 `Uninit`(最精确)到 `Cell`(最宽泛)的完整格 +- `SubObj` / `SubCls` 标签支持子类关系 +- 数组形状特殊化(packed array 的精确类型) + +**AOT 借鉴优先级: P3** + +当前 AOT 的类型标注通过 C++ 类型系统(`int64_t`, `php::string`, `php::array`)表示。对于 typed property,可以利用类似 RAT 的思想生成更精确的 C++ 类型声明。 + +--- + +### 2.7 Index 依赖追踪 + +**文件**: `hphp/hhbbc/index.cpp` (30,132 行) + +Index 不仅是类型信息的存储,更核心的是**依赖追踪机制**: + +``` +依赖类型(DependencyKind): +- ReturnTy — 函数返回类型 +- ConstVal — 常量值 +- ClsConst — 类常量 +- PropType — 属性类型 +- PublicSProp — 公共静态属性类型(特别重要!) +``` + +当 Index 中某个函数的返回类型更新时,所有查询过该返回类型的函数都会被标记为需要重新分析。 + +**公共静态属性特殊处理**: 公共静态属性可以被任何函数修改,因此 Index 追踪所有的 mutation 操作。当分析发现某静态属性从未被修改时,可以做更激进的常量传播。 + +**AOT 借鉴优先级: P1** + +与 2.1 的全程序分析配套——Index 依赖追踪是实现迭代分析的基础。在 AOT 中: + +```php +class Index { + // 返回类型:funcName => Type + // 依赖:funcName => [depends_on_funcName, ...] + // 脏标记:funcName => bool(需要重新分析) +} +``` + +Preprocessor 扫描所有文件后,构建初始 Index,然后迭代分析直到收敛。 + +--- + +### 2.8 Factored CFG(异常因子化的控制流图) + +**文件**: `hphp/hhbbc/cfg.h`, `parse.cpp` + +HHBBC 的控制流图将异常边**因子化**:不在每个可能抛异常的指令处终止基本块,而是让基本块尽可能大。 + +``` +传统 CFG: + instr1 ; block 1 + instr2(may_throw) ; block 1 在此终止(因为可能抛异常) + --- + instr3 ; block 2 + +Factored CFG: + instr1 ; block 1 + instr2(may_throw) + instr3 + ; block 1 包含多条指令 + ; 异常边从 factored exit edge 连接到异常 handler +``` + +好处: +- 更大的基本块 → 数据流分析更高效(更少的 block 边界) +- 类型信息可以排除异常可能性 → 在优化阶段可以删除异常边 +- JIT 阶段更容易做指令调度 + +**AOT 借鉴优先级: P4** + +对于 AOT 编译器(翻译到 C++ 而非直接生成机器码),CFG 主要由 GCC 处理。但在函数着色(function coloring)和 SSA 分析中,更精确的异常建模可以参考。 + +--- + +### 2.9 并行分析(Parallel Analysis) + +**文件**: `hphp/hhbbc/parallel.cpp` (69 行), `README` + +HHBBC 的不动点迭代中,每个 work unit 的分析可以**完全并行**: + +``` +线程安全模型: +- 分析阶段:只能读 Index(内部线程安全)+ 读 php 元数据(不可变) +- 合并阶段:单线程更新 Index(不需要锁) +``` + +这利用了 "Index 信息永远不会错误" 的特性——即使两个线程基于不同版本的 Index 进行分析,合并结果也不会产生错误信息。 + +**AOT 借鉴优先级: P3** + +与 KPHP 的流水线并行类似。在 AOT 中,可以并行分析独立函数/类。需要注意的是 Index 合并必须串行(或使用 lock-free 结构)。 + +--- + +### 2.10 Public Static Property 优化 + +**文件**: `hphp/hhbbc/index.cpp` + +HHBBC 追踪每个公共静态属性在整个程序中的 mutation: + +``` +- 初始状态:保守假设(可能被任何函数修改) +- 每轮分析中,记录哪些函数修改了哪些 static prop +- 如果一个 static prop 在分析中从未被修改 → 可以常量折叠 +- 如果一个 static prop 只被赋值一种类型 → 类型可以收窄 +``` + +这比简单的 "是否有写入" 分析更精确,因为它是**全程序**分析,能看到跨文件的 mutation。 + +**AOT 借鉴优先级: P2** + +当前 AOT 中,公共静态属性总是使用 `Variant`(mixed 类型)。通过全程序分析,可以将只在内部赋值的静态属性优化为精确 C++ 类型。 + +--- + +## 三、工具链分析 + +### 3.1 测试基础设施 + +HHVM 有庞大的测试体系: + +| 层级 | 目录 | 数量 | 用途 | +|------|------|------|------| +| Quick test | `hphp/test/quick/` | 865 个 .php | 快速回归测试 | +| Slow test | `hphp/test/slow/` | 7,927 个 .php | 全面功能/性能测试 | +| Zend test | `hphp/test/zend/` | ~4,500 个 | PHP 兼容性测试(来自 php-src) | +| Ext test | `hphp/test/ext/` | ~800 个 | 扩展功能测试 | +| Server test | `hphp/test/server/` | ~100 个 | HTTP/RPC 集成测试 | +| HHBBC unit test | `hphp/hhbbc/test/` | 3 个 C++ 文件 | 编译器内部测试 | +| **总计** | | **~14,675 个** | | + +测试运行器特点: +- Quick vs Slow 分层:Quick(~1 秒运行)用于 pre-commit,Slow(~10 分钟)用于 CI +- 支持多种运行模式:interp / JIT / hhbbc + JIT / RepoAuthoritative +- Zend 测试:直接复用 php-src 官方测试,验证 PHP 兼容性 + +**AOT 借鉴:** +- Quick/Slow 分层测试策略——将现有 `tests/aot/` 按运行时间分类 +- 直接复用 php-src 官方 PHPT 测试——验证 AOT 编译器的 PHP 行为兼容性 +- HHBBC 内部单元测试太少(仅 3 个),不应效仿——AOT 的 PHPUnit 测试覆盖更好 + +### 3.2 Hack 类型检查器 + +HHVM 的 Hack 语言有完整的**静态类型检查器**(`hphp/hack/`),与编译器独立运行: + +- 编译期类型标注(`int`, `string`, `vec`, `dict`, `shape(...)`) +- 渐进类型(gradual typing):可以从无标注逐步迁移 +- IDE 集成(LSP 协议支持) +- 类型覆盖率追踪 + +**AOT 借鉴:** +- 当前 AOT 使用 `@phpstan-type` 等注解,可以集成 phpstan 做编译前的类型检查 +- 类型覆盖率是一个有用的度量:X% 的函数/变量有精确的类型标注 + +### 3.3 Tracing / Debug 基础设施 + +HHVM 有丰富的调试和追踪: +- `TRACE_SET_MOD(hhbbc)` — 模块级条件日志(编译期开关) +- `hphp/tools/` — 多种分析工具(字节码查看器、profiler 等) +- `debug.cpp` — 类型/状态的可读化打印 + +**AOT 借鉴:** +- 当前 `-vv` 详细输出可以借鉴 TRACE_MODULE 方式,按模块过滤日志 +- 类型分析结果的可视化可以辅助调试优化器 + +### 3.4 RepoAuthoritative 模式 + +HHVM 支持**编译一次,部署多次**的模式: + +``` +源码 → HHBBC 全程序分析 → 优化后字节码 Repo → 多进程直接加载 Repo +``` + +- Repo 中存储**预分析的类型信息**(RepoAuthType) +- 运行时不需要再次做类型推断 +- 字节码级别的 interning (string/class/function id) +- 多进程共享同一个 Repo(mmap) + +**AOT 借鉴: P4** + +当前 AOT 直接生成 `.cc` 文件并编译为二进制,已经在 "编译一次,部署多次" 的路径上。但 Repo 的 mmap 共享思想可以用于多进程环境下的常量池共享(而非每个进程独立加载)。 + +--- + +## 四、类型系统兼容性分析 + +### 4.1 Hack vs PHP vs AOT Compiler + +| 特性 | PHP 8.2 | Hack | AOT Compiler | +|------|---------|------|-------------| +| 基础类型 | mixed, int, string, float, bool, array, null, void, never | int, string, float, bool, null, void, noreturn, mixed, dynamic, nonnull, nothing | 同 PHP | +| 联合类型 | `int\|string` | `int\|string` (但实践中不鼓励) | 支持 | +| 交叉类型 | `X&Y` (8.1+) | 通过 `where` 约束 | 不支持 | +| 泛型 | 无 | `vec`, `dict`, `class Box` | `std::vector` (C++ 原生) | +| 数组类型 | `array` | `vec`, `dict`, `keyset` | `php::array` | +| Shapes | 无 | `shape('x' => int, 'y' => string)` | 无 (建议用 object) | +| 枚举 | enum (8.1) | enum, enum class (带 label) | 支持 PHP 8.1 enum | +| 可空类型 | `?int` | `?int` (仅函数参数) | 支持 | +| nothing / bottom | 无 | 有 (空函数返回, unreachable) | 无 | +| dynamic | 无 | 有 (选择性放弃类型检查) | 无 | + +### 4.2 类型格的关键差异 + +Hack/HHBBC 的类型系统比 PHP 丰富得多: + +``` +HHBBC 类型格: + + Cell (any value) + | + InitCell (not Uninit) + | | + Prim Boxed types (Obj, Res, ...) + | + InitPrim (not null) + | | | +Num Bool Str/ArrKey +| | +Int Dbl + +底部: Bottom (无值 - unreachable) +顶部: Cell (任何值) +``` + +特性: +- **Bottom**: 表示不可达路径的类型,可以消除 dead code +- **Counted/Uncounted**: 静态字符串 vs 运行时分配字符串,不同生命周期 +- **Array shapes**: `dict<'name' => string, 'age' => int>` 是独立类型 +- **Wait handle**: `WaitH` 表示异步结果的类型 + +### 4.3 Hack 的渐进类型设计 + +Hack 从 PHP 演进而来,支持渐进迁移: +- `mixed` — 任何类型(等同于无标注 PHP) +- `dynamic` — 任何类型,且不报类型错误(更宽松的 mixed) +- `<<__Soft>>` — 软类型提示(运行时不强制检查) + +这种设计允许大型代码库逐步添加类型标注,避免 "全有或全无"。 + +### 4.4 与 AOT 编译器的语法差异 + +| 特性 | HHBBC 输入 (HHAS) | AOT Compiler | +|------|------|-------------| +| 基础 PHP 版本 | Hack (PHP 5.6 分支) | PHP 8.2+ | +| 类型标注 | 强制(Hack 语言要求) | 可选 (phpstan 注解) | +| 泛型 | `vec`, `dict`, 自定义泛型类 | 无原生泛型 | +| Lambda / 闭包 | `$x ==> $x + 1` (short lambda) | PHP 闭包 (`function($x) { return $x + 1; }`) | +| async / await | 原生支持 (WaitHandle) | 无 | +| Shapes | `shape('x' => int)` | 无 | +| Enum class | 支持(带 label 的 enum class) | 仅 PHP 8.1 enum | +| XHP | HTML 模板语法 | 无 | +| Case types | `case type T = int \| string` | 无 | + +--- + +## 五、总结与优先级建议 + +| 优先级 | 技术 | 难度 | 收益 | 说明 | +|--------|------|------|------|------| +| **P0** | 全程序不动点分析 | 极高 | 极高 | 跨文件类型推导、返回类型收窄、消除伪动态调用 | +| **P0** | Trep 类型格 | 中 | 极高 | 精确联合类型、不可数标记、非空标记,提升所有优化的精度 | +| **P1** | 抽象解释器 | 高 | 高 | 替代/增强当前 SSA 分析,支持分支类型收窄、常量传播 | +| **P1** | Index 依赖追踪 | 高 | 高 | 全程序分析的基础,增量编译 | +| **P2** | 类型感知 DCE | 中 | 中 | 消除更多死代码,减小生成 C++ 代码体积 | +| **P2** | Public Static Prop 优化 | 中 | 中 | 全局静态属性类型精确化 | +| **P3** | RepoAuthType 存储 | 低 | 中 | 产物中嵌入精确类型信息(对 C++ 生成阶段意义有限) | +| **P3** | 并行分析 | 高 | 中 | 大型项目编译提速 | +| **P4** | DataType 编码优化 | 低 | 低 | 对 phpx Variant 类型检查微优化 | +| **P4** | Factored CFG | 高 | 低 | C++ 编译器已处理 CFG 优化 | +| **P5** | Repo mmap 共享 | 高 | 低 | 特定部署场景优化 | + +### 关键认识 + +1. **HHBBC 的全程序分析是其最大的差异化优势**——AOT 编译器最大的架构差距就在于此。单文件分析无法看到跨文件的类型信息,导致很多调用被迫使用动态分发。 + +2. **类型系统是优化的核心引擎**——HHBBC 的 trep + specialization + monotonic lattice 投入巨大(约 12k 行),但这正是所有优化 pass 的质量基础。 + +3. **依赖追踪是实现增量分析的关键**——Index 自动记录的 "谁查询了什么" 信息,使得只需重新分析受影响的函数,避免全程序每次重新分析。 + +4. **factored CFG 和 DataType 编码更偏 JIT 场景**——这些设计为运行时 JIT 优化,AOT 编译器生成 C++ 源码,由 GCC/Clang 处理这些底层优化,不应重复投资。 + +5. **测试基础设施值得学习**——Quick/Slow 分层、直接复用 php-src 测试、多运行模式,对 AOT 编译器的测试 CI 建设有直接参考价值。 diff --git a/docs/kphp-review.md b/docs/kphp-review.md new file mode 100644 index 00000000..12aef345 --- /dev/null +++ b/docs/kphp-review.md @@ -0,0 +1,451 @@ +# KPHP 编译器 Review:设计、优化、工具链与语法兼容性分析 + +> 2026-06-11 · 基于 /home/swoole/workspace/cpp/kphp 源码审查 + +--- + +## 一、架构概览 + +| 维度 | KPHP | AOT Compiler | +|------|------|-------------| +| 编译目标 | PHP → C++ → 二进制 | PHP → C++ → 二进制 | +| IR 形式 | 自定义 vertex (op_*) 树 | PHP-Parser AST → C++ 字符串 | +| 类型推理 | 迭代收敛型图推断(收敛于泛化方向) | SSA + 手动类型注释 | +| 中间优化 | 多 pass AST 重写(~60+ pipe) | 直接 AST → C++ 翻译 + 少量优化 | +| 运行时 | 自研(allocator/string/array/mixed) | phpx(C++ RAII 包装 Zend API) | +| 并发模型 | 单线程 + reactor/epoll | 依赖 Zend/TSRM | +| 线程安全 | 无(内存分配器无锁) | Zend TSRM | +| 代码量(编译器) | ~18k 行 pipe 代码 | ~6k 行 CompilerBase | + +--- + +## 二、可借鉴的创新设计 + +### 2.1 Rewrite Rules DSL(模式匹配优化规则) + +**文件**: `compiler/rewrite-rules/early_opt.rules` + +声明式 DSL 描述 AST 重写,编译期生成 C++ 优化代码。规则格式为 `(pattern) => (replacement)`,支持条件子句和 C++ 表达式嵌入: + +```lisp +;; strlen 常量折叠 +(op_func_call {"strlen"} arg:(op_string)) + => (op_int_const { std::to_string(arg->str_val.size()) }) + +;; explode 索引直接访问 → 特化版本 +(op_index (op_func_call {"explode"} delim s) k:(op_int_const)) + => (op_func_call {"_explode_nth"} delim s k) + +;; ("" . $x) → (string)$x — 消除无意义拼接 +(op_concat (op_string {""}) x) => (op_conv_string x) + +;; 子串类型优化:conv(substr(...)) → conv(_tmp_substr(...)) +(op_conv_int x) if let x2 { to_tmp_string_expr(x) } => (op_conv_int x2) +``` + +**AOT 借鉴优先级: P0** + +当前 `FuncCallOptimizer` 的 `strlen`/`count` 等优化是硬编码的。引入类似规则引擎可以: +- 声明式添加优化,降低维护成本 +- 通过 `if let` 条件做局部模式变量绑定 +- 规则文件与编译器分离,热加载可行 + +实现建议:PHP 层面实现一套 `RewriteRule` 类,在 `FuncCallOptimizer` 中加载并匹配。 + +--- + +### 2.2 Smart instanceof / Smart Casts(类型收窄) + +**文件**: `compiler/pipes/transform-to-smart-instanceof.cpp` + +`if ($x instanceof A)` 之后,`$x` 在 if 体内自动重命名为 `instance_cast($x)`。核心创新在于**在类型推断之前**做变量拆分: + +```php +// PHP 源码 +if ($x instanceof A) { + $x->methodOfA(); // $x 自动变为 instance_cast($x) +} + +// 反向守卫模式 +if (!($x instanceof A)) return; +// 此后 $x 全函数范围替换为 instance_cast($x) +``` + +同时处理 `catch (SomeClass $e)` 中同名变量在不同 catch 块间的重命名,防止 assumption 混淆。 + +**AOT 借鉴优先级: P0** + +当前 `SsaTypeOptimizer` 只做 int/float/string 基本类型收窄。可以增加对象类型收窄: +1. 在 SSA builder 中识别 `instanceof` 守卫 +2. 在 then/else 分支中替换为目标子类类型 +3. 结合现有的 `stableObjects` 机制进行 devirtualization + +--- + +### 2.3 流水线并行编译 + +**文件**: `compiler/compiler.cpp` + +编译过程以函数为粒度,通过 `operator>>` 链接管道,多线程并行处理: + +```cpp +SchedulerConstructor{scheduler} + >> PipeC{} + >> PipeC{} + >> PipeC{} + >> PassC{} + /* ... 60+ pipes */; +``` + +三种管道类型: +- **PipeC\**: 通用转换,输入→输出 +- **PassC\**: 函数级变换,遍历所有 AST 顶点 +- **SyncC\**: 同步点,所有输入处理完毕才输出 + +不同函数可在不同阶段同时处理,全局存储使用线程安全或无锁结构。 + +**AOT 借鉴优先级: P3** + +当前 `Preprocessor` → `CompilerBase` 是串行执行。对于大型项目可引入函数级并行: +- 类/函数粒度独立编译 +- `SyncC` 同步点用于合并全局符号表 + +--- + +### 2.4 Switch 拆分(状态机变换) + +**文件**: `compiler/pipes/split-switch.cpp` + +将 switch 的每个 case 分支提取为独立函数,用状态变量驱动: + +```cpp +// 每个 case 变成: +int case_state = 0; +auto case_res = switch_func_N(&case_state); +if (case_state == 1) return case_res; // 正常返回 +if (case_state == -1) break; // break 语义 +``` + +`break N` 和 `continue N` 被转换为状态变量 `-1` 设置 + return,与之前 AOT 实现的 `_brk_flag` / `_cnt_flag` 方案思路一致。 + +**AOT 借鉴优先级: P2** + +大型 switch 可以拆分为独立函数,降低单函数复杂度,使 GCC 内联/优化更有空间。 + +--- + +### 2.5 常量不可变标记与 init-once + +**文件**: `compiler/pipes/collect-const-vars.cpp`、`runtime-common/core/memory-resource/` + +编译期常量数组/字符串使用特殊的 refcount 标记: + +```cpp +ExtraRefCnt::for_global_const // 不可变,修改时触发 COW +ExtraRefCnt::for_instance_cache // 跨请求共享,不可修改 +``` + +这些常量存放在 data section,服务器启动时初始化一次,后续请求只读使用。任何修改操作自动触发 COW。 + +**AOT 借鉴优先级: P1** + +当前 AOT 已将常量数组提升为 static 变量,但可以引入更细粒度的不可变标记机制,减少不必要的 COW 拷贝(当编译期可证明变量从未被修改时)。 + +--- + +### 2.6 函数特化(多版本生成) + +**文件**: `compiler/pipes/early-optimization.cpp` + +在类型推断之前根据参数进行函数特化: + +- `microtime()` → `_microtime_float()` 或 `_microtime_string()`(根据参数 true/false) +- `list() + explode()` → `_explode_tupleN()`(精确 N 元组类型) +- `explode()[N]` → `_explode_nth()`(O(1) 直接访问第 N 个元素) +- `substr()` 在函数参数位置 → `_tmp_substr()`(避免字符串拷贝) + +关键是**特殊化版本返回更精确的类型**。例如 `microtime()` 返回 `mixed`,而 `_microtime_float()` 返回 `float`。 + +**AOT 借鉴优先级: P1** + +`FuncCallOptimizer` 目前只做常量折叠,可以扩展为**多版本特化**: + +```php +// 当前 +$result = strlen($s); // 返回 mixed/int + +// 优化后 +$result = _strlen_string($s); // 编译期确定返回 int +``` + +--- + +### 2.7 Class Assumptions:先验类型预测 + +**文件**: `compiler/class-assumptions.cpp` + +解决**类型推断和调用图构建的循环依赖**: + +``` +$obj->method() 需要 $obj 的类型才能绑定 method() + 但类型推断需要完整调用图 + → Assumption 打破循环 +``` + +Assumption 来源: +- `@param ClassName $x` — 参数类型 +- `@return ClassName` — 返回类型 +- `@var ClassName` — 局部变量 +- 构造函数调用 `new ClassName()` → 直接得到类型 + +Assumption 在类型推断**之前**进行,用于绑定调用图。类型推断**之后**进行校验——若不匹配则报错。 + +**AOT 借鉴优先级: P2** + +对于 method call devirtualization:assumption 比纯 SSA 分析更早可用,可作为 devirtualization 的第一阶段(在 SSA 不可用时回退)。 + +--- + +### 2.8 虚拟方法自动生成 + +**文件**: `compiler/pipes/generate-virtual-methods.cpp` + +当方法被子类覆盖时,基类方法自动成为分发器: + +```cpp +ReturnType f$Base$$method(instance_var, args...) { + if (instance_var.ce() == Child1::ce) + return f$Child1$$method(instance_cast(instance_var), args...); + if (instance_var.ce() == Child2::ce) + return f$Child2$$method(instance_cast(instance_var), args...); + // ... fallback to self + return f$Base$$method$$Base(instance_var, args...); +} +``` + +同时做 PHP 7.4+ 类型变体检查(参数逆变、返回协变)。 + +**AOT 借鉴优先级: P2** + +当前 devirtualization plan 中的 "runtime exact-type guard" 与此思路一致。可借鉴其自动生成全部分发分支 + variance 检查。 + +--- + +### 2.9 性能检查注解(Performance Inspections) + +**文件**: `docs/kphp-language/best-practices/performance-inspections.md` + +编译期性能分析,通过注解激活: + +```php +/** @kphp-warn-performance implicit-array-cast */ +function businessLogic() { ... } +``` + +支持的检查项: +- `implicit-array-cast` — 检测 `array` → `array` 隐式转换(昂贵拷贝) +- `array-merge-into` — 检测可通过 `array_merge_into` 优化的合并 +- `array-reserve` — 检测可预分配大小的数组 +- `constant-execution-in-loop` — 检测循环中的常量表达式 + +注解通过调用链传播到所有可达函数。 + +**AOT 借鉴优先级: P3** + +与函数着色类似,可作为一种编译期静态分析插件。`implicit-array-cast` 对类型化数组的性能影响极大,值得单独检测。 + +--- + +### 2.10 池式内存分配器 + +**文件**: `runtime-common/core/memory-resource/unsynchronized_pool_resource.h` + +- 预分配固定大小 buffer +- 小块 (<16KB): slab 分配,按大小分级(`free_chunks_[chunk_id]`),O(1) 分配/释放 +- 大块 (≥16KB): 红黑树管理(`huge_pieces_`),支持碎片整理 +- **每个请求结束后硬重置**(`hard_reset()`),无需逐个释放 +- 支持 OOM handling memory 预留 + +**AOT 借鉴优先级: P4** + +当前依赖 Zend MM。对于长时间运行的 CLI 模式,pool allocator 可显著降低碎片和分配开销。但需要替换整个内存管理层,工程量大。 + +--- + +## 三、工具链分析 + +### 3.1 测试基础设施 + +KPHP 有三层测试体系: + +| 层级 | 目录 | 用途 | +|------|------|------| +| PHPT 测试 | `tests/phpt/` (75+ 子目录) | PHP 行为兼容性测试 | +| C++ 单元测试 | `tests/cpp/compiler/` `tests/cpp/runtime/` `tests/cpp/server/` | 编译器/运行时/服务器组件测试 | +| Python 集成测试 | `tests/python/tests/` | HTTP/RPC/多进程集成测试 | + +测试运行器 `tests/kphp_tester.py` 支持: +- 标签机制(`@ok`, `@kphp_should_fail`, `@kphp_should_warn` 等) +- PHP 版本选择(`@php7.4`, `@php8`) +- 多进程并行执行(基于 ThreadPool) +- TCP server 管理 +- k2 模式(组件编译)兼容 +- 增量编译支持(nocc 分布式编译) + +**AOT 借鉴**: +- 当前 AOT 只有 `phpunit/` (PHPUnit) 和 `tests/aot/` (PHPT) 两层,缺少编译器内部单元测试和集成测试 +- 标签机制比纯 PHPT 更灵活——可以标记预期编译失败、预期警告等 +- Python 测试 runner 提供了更好的 CI 集成能力 + +### 3.2 基准测试框架 + +**文件**: `tests/benchmarks/` + +使用 Go 编写的 `ktest` 工具做 KPHP vs PHP 性能对比: + +``` +$ KPHP_ROOT=/path/to/repo/kphp ./ktest bench-vs-php tests/benchmarks/ +``` + +基准测试覆盖: +- `BenchmarkBasic.php` — 基础操作 +- `BenchmarkConcat.php` — 字符串拼接 +- `BenchmarkExplode.php` — explode 性能 +- `BenchmarkMultiSwitch.php` — 大型 switch +- `BenchmarkTmpString.php` — 临时字符串优化效果 +- `BenchmarkJson.php` / `BenchmarkFFI.php` — 特定功能 + +**AOT 借鉴**: +- 可以建立类似的 AOT vs PHP 基准对比套件 +- 特别关注 AOT 编译器声称优化的场景(如 typed property access、devirtualized calls) + +### 3.3 IDE 集成 + +KPHP 提供 **kphpstorm** IDE 插件(`docs/kphp-language/kphpstorm-ide-plugin/`),支持: +- `@kphp-*` 注解语法高亮 +- 类型标注补全 +- KPHP 特有类型的提示 + +**AOT 借鉴**: +- 当前 AOT 编译器使用 `@phpstan-*` 等注解,可考虑提供 VSCode/JetBrains 插件 + +### 3.4 增量编译 + +KPHP 仅重新编译变更的文件(基于 CRC64 哈希): + +```cpp +// 每个生成文件开头 +//crc64 +//crc64_with_comments +``` + +比较这些哈希与上次生成结果,确定需要重编译的文件,包括所有依赖此文件的上游文件。 + +**AOT 借鉴**: +- 当前 `build/` 目录全量重新生成,可引入类似的增量机制加速大型项目迭代 + +--- + +## 四、语法兼容性分析 + +### 4.1 KPHP 支持的 PHP 版本 + +KPHP 瞄准 **PHP 7.4** 语言级别,部分 8.0/8.1 特性正在添加。 + +### 4.2 不支持的特性(架构原因) + +| 特性 | 原因 | +|------|------| +| 动态函数/方法调用 (`call_user_func`) | 编译期无法解析符号 | +| `eval()` | 编译期不可知 | +| 动态类/函数声明 | 符号表必须在编译期完整 | +| Reflection | 需要运行时元数据 | +| Mock (PHPUnit) | 依赖 Reflection + 动态重定义 | +| 数组内部指针 (`reset`/`current`/`next`) | 不符合引用语义 | +| PHP 扩展互操作 | 自研运行时替代 | + +### 4.3 不支持的特性(未实现) + +| 特性 | 状态 | +|------|------| +| 嵌套 `list()` | 未实现 | +| 生成器 (`yield`) | 未实现 | +| 匿名类 | 未实现 | +| Group use declarations | 未实现 | +| finally | 未实现 | +| `func_get_args` | 未实现 | +| 引用(除 foreach by ref 和引用参数外) | 部分支持 | +| 接口在父链中出现多次 | 不支持 | +| `insteadof` / traits 重命名 | 不支持 | + +### 4.4 KPHP 特有注解 + +```php +// 函数注解 +@kphp-inline // 强制内联(GCC inline) +@kphp-flatten // 激进内联所有 callee +@kphp-required // 强制编译(用于字符串回调) +@kphp-sync // 禁止成为 resumable +@kphp-no-return // 永不返回(优化 CFG) +@kphp-pure-function // 纯函数(常量数组可调用) +@kphp-warn-unused-result // 未使用返回值时报错 +@kphp-should-not-throw // 禁止抛异常 +@kphp-throws {Class} // 受检异常 +@kphp-generic T1, T2 // 泛型函数 +@kphp-color {color} // 能力标注 +@kphp-warn-performance {...} // 性能检查 +@kphp-disable-warnings {...} // 抑制特定警告 +@kphp-profile // 嵌入 profiler + +// 类注解 +@kphp-serializable // 可序列化 +@kphp-immutable-class // 不可变类 +@kphp-json {attr}={value} // JSON 配置 +``` + +### 4.5 与 AOT 编译器的语法差异 + +| 特性 | KPHP | AOT Compiler | +|------|------|-------------| +| 基础 PHP 版本 | 7.4 | 8.2+ | +| 枚举 | 不支持 | 支持 (PHP 8.1 enum) | +| 命名参数 | 不支持 | 支持 | +| Match 表达式 | 不支持 | 支持 | +| 联合类型 | 部分支持 | 支持 | +| Nullsafe `?->` | 不支持 | 支持 | +| 属性提升 (constructor promotion) | 不支持 | 支持 | +| `list()` 解构 | 部分 | 完整支持 | +| `break N` / `continue N` | 部分支持 | 已支持 | +| 类型化数组 | 自定义语法 `array` | 无(使用 phpstan 标注) | +| 泛型函数 | `@kphp-generic` | 无 | +| 元组 / Shapes | 自定义语法 | 无 | +| FFI | 支持(自定义 FFI) | 无 | + +### 4.6 类型系统的关键差异 + +KPHP 的类型系统比 PHP 严格得多: +- **不允许类型混用**: `f(42); f("string")` 对同一个 `$arg` 是编译错误 +- **数组类型化**: `array` vs `array` 是不同的类型,转换需要显式或隐式 cast +- **mixed 代价高昂**: 16 字节 tagged union + switch-case 分发 +- **泛型函数**: 通过 `@kphp-generic` 实现编译期特化,类似 C++ template +- **变量分裂**: 同一变量名可能在不同的 CFG 路径上分裂为不同名称(如 `$x` → `$x$v1`) + +--- + +## 五、总结与优先级建议 + +| 优先级 | 技术 | 难度 | 收益 | 说明 | +|--------|------|------|------|------| +| **P0** | Rewrite Rules DSL | 中 | 高 | 声明式优化规则,可扩展性极强 | +| **P0** | Smart instanceof casts | 低 | 高 | 直接提升对象类型收窄 + devirtualization | +| **P1** | 函数特化(多版本) | 中 | 高 | 更精确的返回类型,消除 mixed 污染 | +| **P1** | 不可变常量标记 | 低 | 中 | 减少 COW,已有常量提升基础 | +| **P2** | Switch 拆分 | 中 | 中 | 已有多级 break 基础,特定场景优化 | +| **P2** | Class Assumptions | 高 | 高 | 需要 PHPDoc 解析基础设施 | +| **P2** | 虚拟方法自动生成 | 中 | 高 | 与 devirtualization plan 互补 | +| **P3** | 性能检查注解 | 低 | 中 | 辅助开发者发现隐藏性能问题 | +| **P3** | 流水线并行 | 高 | 中 | 大型项目编译提速 | +| **P3** | 函数着色 | 低 | 低 | 辅助安全/IO 审计 | +| **P4** | 增量编译 | 中 | 中 | 开发体验优化 | +| **P5** | Resumable 状态机 | 高 | 中 | 需实际 async 需求 | +| **P5** | Pool allocator | 极高 | 高 | 需替换整个内存管理 | diff --git a/docs/peachpie-review.md b/docs/peachpie-review.md new file mode 100644 index 00000000..b2c7c971 --- /dev/null +++ b/docs/peachpie-review.md @@ -0,0 +1,525 @@ +# PeachPie 编译器 Review:Roslyn 集成、跨语言互操作与类型系统 + +> 2026-06-11 · 基于 /home/swoole/workspace/cpp/peachpie 源码审查 + +--- + +## 一、架构概览 + +PeachPie 是一个**基于 Roslyn(Microsoft .NET 编译器平台)的 PHP-to-.NET 编译器**,约 710 个 C# 源文件、272k 行代码。 + +### 编译管道 + +``` +PHP 源码 + → PhpSyntaxTree (Roslyn SyntaxTree for PHP — Syntax/) + → SemanticModel + Symbols (Roslyn symbol system — Semantics/, Symbols/) + → BoundControlFlowGraph (CFG with typed IR — Semantics/Graph/, FlowAnalysis/) + → CIL Bytecode (EMIT — CodeGen/, Emitter/) + → .NET Assembly (.dll / .exe) +``` + +| 阶段 | 组件 | 职责 | +|------|------|------| +| 语法解析 | `Syntax/` (PhpSyntaxTree, NodesFactory) | PHP → Roslyn SyntaxTree | +| 语义绑定 | `Semantics/` (SemanticsBinder, BoundExpression) | 名称解析、方法绑定、类型推断 | +| 符号系统 | `Symbols/` (SourceTypeSymbol, PEMethodSymbol...) | 类型/方法/属性的 Roslyn 符号表 | +| 数据流分析 | `FlowAnalysis/` (FlowState, TypeRefMask, ExpressionAnalysis) | 类型推断、不可达代码检测、条件收窄 | +| CFG 优化 | `FlowAnalysis/Passes/` (TransformationRewriter) | CFG 重写、常量化、死代码消除 | +| 代码生成 | `CodeGen/` (CodeGenerator, GhostMethodBuilder) | IR → CIL 指令 | +| 程序集输出 | `Emitter/` (PEModuleBuilder) | CIL → PE 文件 (.dll/.exe) | + +### 核心文件 + +| 文件 | 行数 | 用途 | +|------|------|------| +| `CodeGen/Graph/BoundExpression.cs` | 5,742 | 核心 IR 节点定义与 CIL 发射 | +| `CodeGen/CodeGenerator.Emit.cs` | 4,396 | CIL 指令生成 | +| `FlowAnalysis/ExpressionAnalysis.cs` | 2,911 | 表达式级类型分析 | +| `Semantics/BoundExpression.cs` | 2,721 | 语义绑定表达式 | +| `Runtime/Operators.cs` | 2,573 | PHP 运算符运行时实现 | +| `Runtime/PhpString.cs` | 2,047 | PHP 字符串值类型 | +| `CodeGen/VariableReference.cs` | 1,863 | 变量引用与地址分析 | +| `Symbols/Source/SourceTypeSymbol.cs` | 1,795 | 源码类型符号 | +| `Runtime/Conversions.cs` | 1,603 | 类型转换运行时 | + +### 与 AOT Compiler 对比 + +| 维度 | PeachPie | AOT Compiler | +|------|----------|-------------| +| 编译目标 | PHP → CIL → .NET Assembly | PHP → C++ → 二进制 | +| 编译器框架 | Roslyn (C# compiler-as-a-library) | 自研 PHP AST → C++ string | +| IR 形式 | BoundControlFlowGraph (Roslyn 模式) | PHP-Parser AST 节点 | +| 类型推理 | FlowState + TypeRefMask bitset | SSA + 手动类型注释 | +| 符号系统 | Roslyn Symbol hierarchy (完整) | 简化版 ClassDef/FunctionDef | +| 输出 | .NET PE 文件 (cross-platform) | 原生二进制 (Linux/Mac/Windows) | +| 运行时 | Peachpie.Runtime (PhpValue, PhpArray, PhpString) | phpx (C++ RAII 包装 Zend API) | +| 跨语言互操作 | 一等公民 — PHP ⇄ C# 双向调用 | 仅 FFI (swoole_cc/cpp 扩展加载) | +| 并行编译 | Parallel.ForEach 函数级并行 | 串行执行 | +| 代码量 | ~272k 行 C# (含运行时) | ~15k 行 PHP | +| MSBuild 集成 | 完整 SDK (`dotnet build`) | 无 | + +--- + +## 二、可借鉴的创新设计 + +### 2.1 基于 Roslyn 的编译器架构 + +**文件**: `Peachpie.CodeAnalysis/` 全部 + +PeachPie 最核心的设计决策是**完全基于 Microsoft Roslyn 编译器平台**构建。这意味着: + +- **复用 Roslyn 的符号系统**:`TypeSymbol`, `MethodSymbol`, `NamedTypeSymbol` 等标准 Roslyn 类型 +- **复用 Roslyn 的元数据发射**:`PEModuleBuilder`, `PEAssemblyBuilder` 直接生成 PE 文件 +- **复用 Roslyn 的诊断体系**:`DiagnosticBag`, 标准化的 Error/Warning 机制 +- **MSBuild 原生集成**:PHP 项目是标准的 .NET 项目(`.csproj` 风格),`dotnet build` 即可编译 + +**AOT 借鉴优先级: P2** + +当前 AOT 从零搭建了所有编译器基础设施。PeachPie 的思路可以作为参考,但不适合直接移植(AOT 的目标是生成 C++ 而非 CIL)。可以借鉴的是: + +- 将编译器分解为 **Syntax → Semantic → IR → CodeGen** 的标准阶段,每阶段有清晰的接口 +- Preprocessor(符号收集)分离为独立的 Analyzer 阶段,类似 Roslyn 的 Compilation 概念 +- 统一定义 `Diagnostic` 类型,而非散布在各处的 `fatalError()` / `SyntaxError` + +--- + +### 2.2 TypeRefMask:64-bit 类型 Bitset + +**文件**: `FlowAnalysis/TypeRef/TypeRefMask.cs` + +PeachPie 使用 `ulong` (64-bit) 作为类型掩码: + +```csharp +public struct TypeRefMask { + ulong _mask; + // bits 0-61: 类型索引(最多 62 种不同类型) + // bit 62: IncludesSubclasses(类型可能包含子类) + // bit 63: IsRef(值是引用/别名) +} +``` + +特性: +- **O(1) 类型比较**: `(mask & type_bit) != 0` 即可检测类型 +- **联合类型**: `mask1 | mask2` = 包含两种类型 +- **类型收窄**: `mask & ~excluded_type_bit` = 排除某种类型 +- **IsRef 标记**: 追踪值是否被引用赋值,影响别名分析 +- **IncludesSubclasses**: 区分 `exactly Class` vs `Class or subclass` + +每个 `TypeRefContext` 维护一个类型注册表,将具体的 .NET 类型 (如 `System.Int64`, `Pchp.Core.PhpString`) 映射到 bit index。 + +**AOT 借鉴优先级: P0** + +这与 HHVM 的 trep 思路一致——使用 bitset 表示类型。AOT 的 `TYPE_INT`, `TYPE_STRING` 等离散常量可以被替换为 bitset: + +```php +class TypeMask { + const BINT = 1 << 0; + const BFLOAT = 1 << 1; + const BSTRING = 1 << 2; + const BBOOL = 1 << 3; + const BARRAY = 1 << 4; + const BOBJECT = 1 << 5; + // 扩展标记 + const BEMPTY = 1 << 60; // 空值标记 + const BSUBCLASS = 1 << 61; // 允许子类 + const BREFERENCE = 1 << 62; // 引用标记 +} +``` + +优势:`int|string` = `BINT|BSTRING`,类型收窄 = `& ~excluded_bits`。 + +--- + +### 2.3 FlowState + Worklist 数据流分析 + +**文件**: `FlowAnalysis/FlowState.cs`, `FlowAnalysis/Worklist.cs` + +PeachPie 使用经典的**数据流 worklist 算法**进行类型推断: + +```csharp +class FlowState { + TypeRefMask[] _varsType; // 每个变量的类型掩码 + ulong _initializedMask; // 变量是否被初始化 + HashSet _notes; // 附加信息(如函数返回点) +} +``` + +**Merge 操作**: 当两条 CFG 路径汇合时,`FlowState(state1, state2)` 构造函数计算: +- 类型掩码的 **union**(所有可能类型) +- 初始化掩码的 **union**(任一分支初始化就算初始化) +- Notes 的 **intersection**(两条路径都有的信息才保留) + +Worklist 按拓扑顺序处理 block,遇到状态变化时重新入队。 + +**AOT 借鉴优先级: P1** + +当前 AOT 的 SSA 分析做了一定程度的类型推导,但缺少: +- **结构化 FlowState**:统一的变量类型状态表示 +- **标准 merge 操作**:join point 处的类型合并 +- **Worklist 迭代**:达到不动点的迭代分析框架 + +--- + +### 2.4 ConditionBranch 感知的类型收窄 + +**文件**: `FlowAnalysis/ConditionBranch.cs`, `FlowAnalysis/AnalysisFacts.cs` + +PeachPie 的类型分析**感知当前上下文的条件分支**: + +```csharp +enum ConditionBranch { + AnyResult = 0, // 普通求值 + ToTrue = +1, // 表达式结果为 true 的分支 + ToFalse = -1, // 表达式结果为 false 的分支 +} +``` + +在条件表达式中,分析器携带分支方向传播类型信息: + +```csharp +// if ($x instanceof MyClass) { ... } +// 在 ToTrue 分支中: +// $x 的类型收窄,排除不可能是 MyClass 的类型 +// 在 ToFalse 分支中: +// $x 的类型收窄,排除 MyClass + +// if (is_int($x)) { ... } +// 在 ToTrue 分支中: +// $x 的类型收窄为 int +``` + +`AnalysisFacts.HandleSpecialFunctionCall()` 注册了 `is_int`, `is_string`, `is_array`, `is_callable`, `function_exists`, `class_exists` 等类型检查函数,在分支中自动收窄变量类型。 + +**AOT 借鉴优先级: P1** + +当前 `SsaTypeOptimizer` 做了一定程度的 instanceof 收窄,但: +- 不支持 `is_int()` / `is_string()` 等内置类型检查函数收窄 +- 不支持 `class_exists()` / `function_exists()` 等存在性检查常量折叠 +- 可以直接借鉴 `AnalysisFacts` 的 "已知类型检查函数注册表" 模式 + +--- + +### 2.5 PhpValue Tagged Union 设计 + +**文件**: `Peachpie.Runtime/PhpValue.cs` + +PeachPie 的运行时值类型使用精妙的 C# tagged union: + +```csharp +[StructLayout(LayoutKind.Sequential)] +public readonly partial struct PhpValue { + readonly PhpTypeCode _type; // 1 byte 类型标签 + + // 显式布局联合:两个字段占用同一内存 + [StructLayout(LayoutKind.Explicit)] + struct ValueField { + [FieldOffset(0)] public bool @bool; + [FieldOffset(0)] public long @long; + [FieldOffset(0)] public double @double; + } + + [StructLayout(LayoutKind.Explicit)] + struct ObjectField { + [FieldOffset(0)] public object @object; + [FieldOffset(0)] public string @string; + [FieldOffset(0)] public PhpString.Blob blob; + [FieldOffset(0)] public PhpArray array; + [FieldOffset(0)] public PhpAlias alias; + } + + readonly ValueField _value; // 值类型存储 + readonly ObjectField _obj; // 引用类型存储 +} +``` + +内存布局:`PhpValue` = `PhpTypeCode` (1 byte) + padding + `ValueField` (8 bytes) + `ObjectField` (8 bytes,指针) ≈ 24 bytes。 + +这种设计的优势: +- **readonly struct** — 无 GC 开销,可在栈上分配 +- **显式联合** — 值类型和引用类型共享空间,紧凑 +- **PhpAlias 机制** — 通过 `PhpAlias` 抽象实现 PHP 引用(写时复制),而非直接复制值 +- **MutableString** — 区分不可变 string 和可写 MutableString(用于字符串拼接优化) + +**AOT 借鉴优先级: P3** + +当前 AOT 使用 `Variant`(基于 Zend `zval`)作为动态类型。可以借鉴: +- PhpString 的 MutableString 分离(string builder pattern) +- PhpAlias 的引用语义抽象 +- 但整体替换 `Variant` 工程量巨大,优先级低 + +--- + +### 2.6 GhostMethodBuilder:PHP 方法 ⇄ C# 方法适配 + +**文件**: `CodeGen/GhostMethodBuilder.cs` + +PeachPie 最独特的功能是**自动生成 ghost stub 方法**,使 PHP 方法可被 C# 直接调用: + +```csharp +// 为 PHP 方法生成 C# 可调用的包装器: +// - 处理参数类型转换(PhpValue → CLR type) +// - 处理返回类型转换(CLR type → PhpValue) +// - 构建 PhpContext 传递 +// - 支持 explicit interface override +static MethodSymbol CreateGhostOverload( + MethodSymbol original, NamedTypeSymbol containingtype, + PEModuleBuilder module, DiagnosticBag diagnostic, + TypeSymbol ghostreturn, ImmutableArray ghostparams, + bool phphidden = false, MethodSymbol explicitOverride = null) +``` + +Ghost 方法使得: +- C# 调用 PHP 方法时自动获得类型安全的接口 +- PHP 实现 C# 接口(IMethod, INotifyPropertyChanged 等) +- PHP 类可以作为 .NET 泛型参数 + +**AOT 借鉴优先级: P4** + +当前 AOT 不支持 PHP 调用 C++(反之亦然)。如果未来需要双向互操作层,ghost stub 模式值得参考。 + +--- + +### 2.7 DelayedTransformations:并行安全的延迟变换 + +**文件**: `FlowAnalysis/Passes/DelayedTransformations.cs` + +在并行分析阶段,某些变换(如标记不可达函数、将条件函数升级为无条件函数)不能直接修改共享状态。PeachPie 使用**延迟变换**模式: + +```csharp +class DelayedTransformations { + ConcurrentBag UnreachableRoutines; + ConcurrentBag UnreachableTypes; + ConcurrentBag FunctionsMarkedAsUnconditional; + // 并行分析期间线程安全地收集 + // 分析完成后串行 Apply() +} +``` + +分析线程只将待变换的对象放入 `ConcurrentBag`,分析结束后由单线程调用 `Apply()`。 + +**AOT 借鉴优先级: P2** + +AOT 目前是串行的,不需要这个。但如果将来引入并行编译(参见 KPHP review 2.3),延迟变换是线程安全的基础模式。 + +--- + +### 2.8 MSBuild 原生集成(Peachpie.NET.Sdk) + +**文件**: `Peachpie.NET.Sdk/` + +PeachPie 不只是一个编译器——它是一个**完整的 .NET SDK**: + +``` +dotnet new classlibrary -o MyPhpLib # 创建 PHP 类库项目 +dotnet build # 编译 PHP → .NET DLL +dotnet run # 运行编译后的程序 +dotnet publish # 发布为独立应用 +``` + +通过 MSBuild targets/props 实现: +- `build/peachpie.targets` — 定义编译任务 +- `Peachpie.NET.Sdk.nuspec` — NuGet 包定义 +- `BuildTask.cs` — MSBuild 编译任务 + +这意味着 PHP 项目可以无缝使用 .NET 生态:NuGet 包引用、项目引用、条件编译、多目标框架等。 + +**AOT 借鉴优先级: P2** + +AOT 目前使用 `php bin/compiler.php ` 命令行。可以借鉴: +- 为 AOT 编译器创建一个 Composer plugin 或 CLI phar 包 +- 定义 `project.yml` 的 JSON Schema(类似 `.csproj`) +- 支持 `composer build` 或 `php-aot build` 统一入口 + +--- + +### 2.9 可延迟求值的 AnalysisFacts + +**文件**: `FlowAnalysis/AnalysisFacts.cs` + +PeachPie 在编译期对大量 PHP 运行时函数进行常量求值: + +| 函数 | 求值策略 | +|------|---------| +| `function_exists(X)` | 检查 PE assembly 中是否存在符号 X → 折叠为 `true` | +| `class_exists(X)` | 检查 PE assembly 中是否存在类型 X → 折叠为 `true`/`false` | +| `method_exists(X, M)` | 检查类型 X 中是否存在方法 M → 折叠为 `true`/`false` | +| `defined(CONST)` | 检查常量是否存在 → 折叠为 `true`/`false` | +| `is_callable(F)` | 检查 F 是否无条件声明 → 折叠为 `true` | +| `dirname(__FILE__)` | 编译期路径运算 → `__DIR__` | +| `basename(__FILE__)` | 编译期文件名提取 → 字符串常量 | + +这些求值利用了 PeachPie 的 "PE assembly" 概念——已经编译好的 .NET 程序集包含了完整的类型/方法/常量元数据,可以**在编译时查询**。 + +**AOT 借鉴优先级: P1** + +当前 `FuncCallOptimizer` 只做最基础的常量折叠(`strlen("abc")` → `3`)。可以扩展为对 `function_exists`, `class_exists`, `defined` 等反射型函数的编译期求值——前提是在 Preprocessor 中建立了完整的符号表。 + +--- + +### 2.10 条件声明检测与不可达代码消除 + +**文件**: `FlowAnalysis/Passes/DelayedTransformations.cs`, `FlowAnalysis/Passes/TransformationRewriter.cs` + +PeachPie 能检测**条件声明**(`if (condition) { function foo() {} }`)并优化: + +- 如果分析证明条件恒为 true → 函数标记为无条件声明 +- 如果分析证明条件恒为 false → 函数/类标记为不可达(Unreachable),不编译 + +这允许在 PHP 中写出类似 C 条件编译的模式: + +```php +if (PHP_VERSION_ID >= 80000) { + function newFeature() { ... } // 低版本下不编译 +} +``` + +**AOT 借鉴优先级: P2** + +当前 AOT 编译所有扫描到的函数,无论是否可达。对于存在多个 PHP 版本兼容代码的项目,条件声明消除可以减少编译产物大小。 + +--- + +## 三、工具链分析 + +### 3.1 测试基础设施 + +PeachPie 有 529 个测试文件(PHP 文件),分布在功能目录中: + +| 目录 | 内容 | +|------|------| +| `tests/arrays/` | 数组操作测试(含 `lazy_copy` 子目录) | +| `tests/classes/` | 类/对象测试 | +| `tests/functions/` | 函数调用测试 | +| `tests/generators/` | Generator/yield 测试 | +| `tests/strings/` | 字符串操作测试 | +| `tests/operators/` | 运算符测试 | +| `tests/transformations/` | 编译器转换/优化测试 | +| `tests/constants/` | 常量测试 | +| `tests/constructs/` | 语言结构测试 | +| `tests/traits/` | Trait 测试 | +| `tests/reflection/` | Reflection 测试 | +| `tests/spl/` | SPL 测试 | +| `tests/bcmath/` `tests/hash/` `tests/pcre/` | 扩展测试 | +| `tests/pdo/` `tests/ftp/` `tests/openssl/` | 数据库/网络扩展 | +| `tests/gd/` `tests/xml/` `tests/zip/` | 图形/XML/ZIP 扩展 | +| `tests/web/` `tests/scripting/` | Web/脚本集成测试 | + +测试运行方式:通过 .NET 测试框架(xUnit/NUnit)执行编译后的程序集。 + +**AOT 借鉴:** +- `tests/transformations/` 目录专门测试编译器优化——AOT 可以建立类似的优化正确性测试 +- `tests/arrays/lazy_copy/` 专门测试写时复制行为——AOT 也可以建立 COW 相关的专项测试 + +### 3.2 Visual Studio 深度集成 + +PeachPie 提供完整的 IDE 体验: +- **Visual Studio Extension** — 项目管理、智能感知、调试、性能分析 +- **VS Code / Rider 支持** — 通过 OmniSharp / LSP +- **NuGet 包管理** — PHP 库可以作为 NuGet 包发布和引用 + +**AOT 借鉴:** +- 可以提供 VSCode 插件(Task 集成 + 项目模板) +- 当前 AOT 的 `project.yml` 可以用 JSON Schema 提供 IDE 自动补全 + +### 3.3 命令行工具链 + +```bash +# PeachPie 的 CLI 体验 +dotnet peach build # 编译 PHP 项目 +dotnet peach run # 运行编译后程序 +dotnet peach publish # 独立发布 +dotnet peach add # 添加依赖 +``` + +**AOT 借鉴:** +```bash +# 可以设计类似的 CLI +php-aot build # 编译项目 +php-aot run # 编译并运行 +php-aot new # 创建新项目 +``` + +--- + +## 四、类型系统与互操作分析 + +### 4.1 PeachPie 类型映射 + +| PHP 类型 | PeachPie 运行时类型 | .NET CLR 类型 | +|----------|-------------------|---------------| +| null | `PhpTypeCode.Null` | `null` (任何引用类型) | +| bool | `PhpTypeCode.Boolean` | `bool` | +| int | `PhpTypeCode.Long` | `long` | +| float | `PhpTypeCode.Double` | `double` | +| string | `PhpTypeCode.String` / `MutableString` | `string` / `PhpString` | +| array | `PhpTypeCode.PhpArray` | `PhpArray` | +| object | `PhpTypeCode.Object` | `object` (具体类) | +| reference | `PhpTypeCode.Alias` | `PhpAlias` | + +### 4.2 PHP ⇄ C# 互操作 + +PeachPie 的双向互操作是其最突出的差异化优势: + +**C# 调用 PHP:** +```csharp +// PHP 类编译后变成 .NET 类,C# 可以直接 new 和调用 +var phpObj = new MyPhpClass(ctx); +phpObj.someMethod(arg1, arg2); +``` + +**PHP 调用 C#:** +```php +// PHP 中可以直接使用 .NET 类型 +$list = new \System\Collections\Generic\List; +$list->Add(42); +``` + +互操作的实现依赖于: +- `GhostMethodBuilder` 生成适配方法 +- `ConversionsExtensions` 处理 PhpValue ↔ CLR type 自动转换 +- `DynamicOperationFactory` 处理动态方法调用转发 + +### 4.3 与 AOT 编译器的语法差异 + +| 特性 | PeachPie | AOT Compiler | +|------|----------|-------------| +| 基础 PHP 版本 | 8.0+ (目标) | 8.2+ | +| 类型标注 | 可选(逐渐丰富) | 可选 (phpstan 注解) | +| 命名空间 | 标准 PHP | 标准 PHP | +| 泛型 | 无 | 无 | +| C# 互操作 | 完整(一等公民) | 仅 FFI 扩展 | +| .NET 生态系统 | 完全兼容 | 不相关 | +| MSBuild 集成 | 完整 | 无 | +| 反射 | 部分支持 | 不支持 | +| yield/generator | 支持 | 支持 | + +--- + +## 五、总结与优先级建议 + +| 优先级 | 技术 | 难度 | 收益 | 说明 | +|--------|------|------|------|------| +| **P0** | TypeRefMask bitset 类型系统 | 中 | 极高 | 联合类型、类型收窄、不可空标记——所有优化 pass 的基础 | +| **P1** | ConditionBranch 类型收窄 | 低 | 高 | `is_int()`/`is_string()` 等检查函数自动收窄,当前 SSA 未覆盖 | +| **P1** | AnalysisFacts 编译期求值 | 中 | 高 | `function_exists`/`class_exists`/`defined` 编译期折叠 | +| **P1** | FlowState worklist 分析框架 | 中 | 高 | 结构化变量类型状态、标准 merge 操作 | +| **P2** | Roslyn 式编译器分层 | 高 | 中 | Syntax→Semantic→IR→CodeGen 清晰分层,当前 Preprocessor/CompilerBase 职责混杂 | +| **P2** | 条件声明检测 | 低 | 中 | 消除不可达函数/类,减小编译产物 | +| **P2** | MSBuild 集成 / CLI 工具统一 | 低 | 中 | 标准化项目配置、CI 友好 | +| **P3** | PhpValue tagged union | 高 | 中 | 紧凑内存布局,但替换 Variant/zval 工程量大 | +| **P3** | DelayedTransformations | 低 | 低 | 仅在并行编译时有意义 | +| **P4** | GhostMethodBuilder 互操作 | 极高 | 低 | 需要 .NET 或类似 FFI 运行时 | +| **P4** | IDE 深度集成 | 高 | 低 | VSCode 插件投资回报有限 | + +### 关键认识 + +1. **PeachPie 最大的优势在于 .NET 生态集成**——这是架构选择带来的天然优势,而非单项技术创新。AOT 可以借鉴其 "编译器 SDK + 标准化 CLI" 的思路,但不需要移植具体技术。 + +2. **TypeRefMask bitset 是最直接可移植的设计**——用 64-bit 位集表示类型,同时支持联合类型、子类标记、引用标记。这是 HHBBC 的 trep 和 KPHP 的类型分析的共同特征,说明 bitset 类型格是 PHP 编译器的最佳实践。 + +3. **ConditionBranch 模式是低成本高收益的增强**——在条件分支分析中携带 "期望结果" 信息,使类型检查函数能自动收窄变量类型。AOT 的 SSA 分析可以立即采用。 + +4. **GhostMethodBuilder 揭示了互操作的通用模式**——生成 adapter/thunk 方法桥接两种语言的调用约定。虽然 AOT 的目标是 C++ 而非 .NET,但如果有跨语言调用需求(如 PHP 调用 C 扩展),此模式可复用。 + +5. **PeachPie 约 530 个测试**明显少于 KPHP (75+ 目录) 和 HHVM (14,675),说明其成熟度相对较低。但其测试按功能模块和优化类型分类的方式值得参考。