TypePHP 编译器 https://swoole.com/aot/
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 

13 KiB

AOT 编译器核心重构计划

背景

当前 AOT 编译器核心类承担了过多职责,尤其是 CompilerBaseTranslator 等类同时包含 AST 分发、类型推导、属性访问解析、调用解析、代码生成、诊断信息、上下文状态维护等逻辑。随着功能持续增加,这种结构会带来以下问题:

  • 封装性不足,修改一个语义点时容易影响多个代码路径。
  • 代码复用不足,同类逻辑在普通属性、静态属性、nullsafe、assignment、isset/empty/refval 等路径中重复实现。
  • 编译期检查容易出现绕过路径,例如某些动态 fallback 没有复用静态 resolver。
  • 单个类代码量过大,review、测试定位和长期维护成本持续升高。
  • 设计边界不清晰,类型系统、符号解析、属性访问、调用生成之间耦合过深。

本计划用于指导后续逐步重构,目标是改善架构质量,同时避免一次性大规模重写带来的行为回归风险。

核心原则

  1. 渐进式重构,禁止一次性重写核心编译流程。
  2. 优先抽离无状态或低状态依赖的纯逻辑,再抽离依赖编译上下文的逻辑。
  3. 每一步重构应尽量保持行为不变,行为变更必须单独说明并补测试。
  4. 新模块应围绕稳定领域建模,而不是简单按 AST 节点机械拆分。
  5. 优先使用小型 service/helper、明确 DTO、resolver、emitter;仅在边界稳定后再引入 Visitor、Strategy 等模式。
  6. 所有错误信息保持 PHP 风格,不暴露实现细节,不使用 “AOT 禁止” 这类表述。
  7. 每个阶段必须有 phpunit 或 phpt 回归验证,尤其是属性、类型、调用、继承、异常等高风险路径。

目标架构方向

CompilerBase

最终应收敛为编译上下文、公共工具、AST 调度入口和跨模块协作层,不再直接承载大量领域逻辑。

保留职责:

  • 当前文件、函数、类、方法上下文管理。
  • 临时变量、局部变量、作用域状态维护。
  • AST 顶层分发入口。
  • fatal/warning 的统一入口。
  • 与下层 resolver/emitter 的协作。

逐步移出职责:

  • 类型声明解析和类型兼容判断。
  • 属性可见性、属性 offset、typed property 检查。
  • 函数和方法调用解析。
  • 复杂表达式代码生成。
  • union/intersection/nullable runtime typecheck 生成。

Translator

保留文件级、类级、函数级编译流程控制,逐步减少具体语义检查和表达式生成逻辑。

保留职责:

  • 文件扫描和转换入口。
  • 类、函数、方法代码块生成流程。
  • class/function/constant/property metadata 注册。
  • 编译产物组织。

逐步移出职责:

  • 继承兼容检查的细节逻辑。
  • trait 属性冲突检查细节。
  • 参数、返回值、属性类型兼容判断。

模块拆分计划

1. TypeSystem

职责:

  • 类型声明解析。
  • PHP 类型到 AOT 内部类型映射。
  • nullable、union、intersection 类型展开。
  • 参数、返回值、属性、常量的类型兼容判断。
  • 静态类型与 runtime typecheck 的边界定义。
  • 类型字符串格式化。

建议子模块:

  • TypeResolver
  • TypeCompatibility
  • TypeCheckEmitter
  • TypeStringFormatter

设计要求:

  • union、intersection、nullable 在静态阶段仍可按 mixed/any 处理,但必须保留 runtime typecheck 信息。
  • selfparentstatic 等特殊类型名的解析规则必须集中,避免多个路径实现不一致。
  • 类型错误信息必须包含函数、方法、参数、属性等必要上下文。

2. SymbolResolver

职责:

  • 命名空间解析。
  • use alias 解析。
  • selfparentstatic 解析。
  • 类、接口、trait、函数、常量名称规范化。
  • 动态符号和静态可解析符号的边界判断。

建议接口:

  • resolveClassName(NodeAbstract $node): SymbolResolution
  • resolveFunctionName(NodeAbstract $node): SymbolResolution
  • resolveMethodScope(NodeAbstract $node): SymbolResolution
  • resolveClassConstScope(NodeAbstract $node): SymbolResolution

设计要求:

  • 不同调用路径不能重复实现 self/parent/static 规则。
  • 对无法静态确定的符号,应明确返回 dynamic 状态,而不是默默退化为字符串拼接。

3. PropertyAccessResolver

职责:

  • 对象属性访问解析。
  • 静态属性访问解析。
  • nullsafe 属性访问静态检查。
  • private/protected/public 可见性检查。
  • native property offset 查找。
  • typed property 写入 typecheck 信息生成入口。
  • static-vs-instance 属性误用检查。

建议接口:

  • resolveInstancePropertyAccess(PropertyAccessRequest $request): PropertyAccessResult
  • resolveStaticPropertyAccess(StaticPropertyAccessRequest $request): PropertyAccessResult
  • assertReadable(PropertyAccessResult $result): void
  • assertWritable(PropertyAccessResult $result): void
  • emitRead(PropertyAccessResult $result): string
  • emitWrite(PropertyAccessResult $result, string $value): string

需要统一覆盖的路径:

  • $obj->prop
  • $obj?->prop
  • Class::$prop
  • self::$prop
  • parent::$prop
  • static::$prop
  • isset($obj->prop)
  • empty($obj->prop)
  • refval($obj->prop)
  • 普通赋值、复合赋值、自增自减、unset。

第一优先级建议从本模块开始,因为近期问题集中在属性访问和可见性绕过,测试边界相对清晰。

4. CallResolver

职责:

  • 函数调用解析。
  • 对象方法调用解析。
  • 静态方法调用解析。
  • native call 与 dynamic call 选择。
  • named args、unpack、by-ref 参数处理。
  • closure 和动态 callable 退化规则。

建议接口:

  • resolveFunctionCall(CallRequest $request): CallResolution
  • resolveMethodCall(MethodCallRequest $request): CallResolution
  • resolveStaticCall(StaticCallRequest $request): CallResolution
  • emitCall(CallResolution $resolution): string

设计要求:

  • 静态 function 和内置 function 参数信息明确时,可以自动转引用。
  • 动态调用、closure、编译期无法获取参数 by-ref 信息时,必须要求显式 refval()
  • 使用 unpack 并追加尾部 named args 时,应退化为 dynamic call,不能走 native call。

5. ExpressionEmitter

职责:

  • 表达式级代码生成。
  • 将大型 parseExpr() 分发逻辑逐步拆分。
  • 复用 resolver 结果生成 C++ 代码。

建议按领域拆分,而不是一开始拆成大量 AST visitor:

  • AssignmentEmitter
  • PropertyEmitter
  • CallEmitter
  • ArrayEmitter
  • ControlExprEmitter
  • ObjectEmitter

设计要求:

  • 先保持现有 parseExpr() 作为调度入口。
  • 每次只迁移一组表达式,迁移后跑对应测试组。
  • 对表达式副作用、求值顺序、临时变量生成必须保守处理。

6. Diagnostic

职责:

  • 统一 fatal/warning 构造。
  • 提供上下文增强能力。
  • 保证错误消息风格接近 PHP。

建议能力:

  • 当前函数/方法名。
  • 参数名。
  • 属性名和类名。
  • 源码位置。
  • 声明位置与使用位置。

设计要求:

  • 错误信息不出现 “AOT” 作为行为主体。
  • 对用户可修复的问题,应包含准确符号名。
  • 编译期能发现的问题优先编译期 fatal,不应依赖运行时 typecheck 异常兜底。

阶段计划

阶段 1:属性访问解析模块化

目标:

  • 抽离 findNativeProperty()findNativeStaticProperty()canAccessProtectedProperty() 等属性访问解析逻辑。
  • 保持生成代码基本不变。
  • 建立统一的 PropertyAccessResult,承载属性声明、声明类、访问类、是否 native、offset、是否 dynamic 等信息。

范围:

  • 普通对象属性读取。
  • 静态属性读取。
  • nullsafe 属性静态检查。
  • 属性可见性检查。

当前进展:

  • 已建立 PropertyAccessResolverPropertyAccessResult 作为属性访问解析的第一层抽象。
  • CompilerBase::findNativeProperty() 保留为兼容入口,并委托 resolver 执行 native property 查找、static-vs-instance 检查和可见性检查。
  • CompilerBase::isSameClassName()isSameOrSubclassOf()canAccessProtectedProperty() 已委托 resolver,避免规则继续扩散。
  • 已添加 prepare/convert/idle 编译阶段状态;PropertyAccessResolver 只能在 convert 阶段创建和使用,避免预处理阶段误用不完整的类表状态。
  • 当前迁移保持生成代码不变,后续阶段再统一 read/write emitter。

验证:

  • phpunit/src/NativePropertyTest.php
  • phpunit/src/InheritanceErrorTest.php
  • object property 相关 phpt。
  • static property 相关 phpt。
  • nullsafe 相关 phpt。

阶段 2:属性写入路径统一

目标:

  • 所有属性写入路径先 resolve,再 emit。
  • typed property runtime typecheck 从散落逻辑收敛到属性写入模块。
  • 消除 assignment、compound assignment、inc/dec、unset 各自实现属性访问规则的问题。

范围:

  • $obj->prop = $value
  • $obj->prop += $value
  • $obj->prop++
  • unset($obj->prop)
  • Class::$prop = $value
  • ??= 相关属性路径。

验证:

  • typed property 相关 phpunit/phpt。
  • object property optimization 相关 phpt。
  • nullsafe 写上下文错误测试。
  • private/protected/static 属性错误测试。

阶段 3:类型系统模块化

目标:

  • 抽离类型声明解析、类型兼容、runtime typecheck 生成。
  • 明确静态类型推导和运行时 typecheck 的职责边界。
  • 减少参数、返回值、属性、常量各自重复处理类型规则。

范围:

  • parseTypeDecl()
  • buildTypeCheckFromNode()
  • 参数 typecheck。
  • 返回值 typecheck。
  • 属性 typecheck。
  • 常量类型处理。

验证:

  • union、intersection、nullable 类型测试。
  • 参数、返回值、属性 typecheck 测试。
  • namespace constant 测试。
  • constructor、void expression 相关测试。

阶段 4:调用解析模块化

目标:

  • 统一函数、方法、静态方法调用解析。
  • 明确 native call、dynamic call、closure call 的退化规则。
  • 集中处理 named args、unpack、by-ref 参数。

范围:

  • parseFuncCall()
  • parseMethodCall()
  • parseStaticCall()
  • call args 解析。
  • native/internal/user function 调用路径。

验证:

  • named args、unpack 相关 phpt。
  • by-ref 参数相关 phpt。
  • closure 相关 phpt。
  • parent/self/static call 相关 phpt。

阶段 5:表达式生成器拆分

目标:

  • parseExpr() 背后的大量表达式生成逻辑迁移到领域 emitter。
  • CompilerBase 保持调度和共享上下文能力。
  • 降低单文件和单类代码量。

范围:

  • AssignmentEmitter。
  • PropertyEmitter。
  • CallEmitter。
  • ArrayEmitter。
  • ControlExprEmitter。

验证:

  • 每迁移一个 emitter,跑对应测试组。
  • 最后跑核心 phpunit 和选定 phpt 回归集。

阶段 6:Translator 收敛

目标:

  • 将继承兼容、trait 合并、类成员校验等逻辑抽离成专门 checker。
  • Translator 聚焦编译流程组织。

建议子模块:

  • InheritanceChecker
  • TraitCompositionChecker
  • ClassMemberValidator
  • FunctionSignatureChecker

验证:

  • 继承错误 phpunit。
  • trait 相关 phpt。
  • interface/abstract/final/readonly 相关 phpt。

测试门禁

每个重构 PR 或阶段至少满足:

  • 相关 phpunit 必须通过。
  • 相关 phpt 必须通过。
  • 如果修改 C++/phpx,需要补 gtest 并通过对应测试。
  • 如果引入新编译期错误,需要新增固定 fixture 到 phpunit/code 或新增 phpt。
  • 不使用 file_put_contents() 临时生成源码作为新测试方式。

建议按模块维护最小回归集:

  • 属性模块:NativePropertyTestInheritanceErrorTest、object property、static property、nullsafe。
  • 类型模块:type_decl、type_hits、typed property、union/intersection/nullable。
  • 调用模块:function call、method call、parent_call、closure、named args、unpack、by-ref。
  • 控制流和表达式:ternary、match、goto、loop、array、coalesce、void expression。

风险控制

  • 不在同一个变更中同时做大规模文件移动和行为变更。
  • 每次迁移前先补当前行为的测试,尤其是历史 bug 对应路径。
  • 保留旧入口一段时间,通过 adapter 调用新模块,降低切换风险。
  • 对动态 PHP 语义保持保守,无法静态确定时不要过度优化。
  • 对 AOT 明确不兼容 PHP 历史包袱的地方,应在文档和错误信息中表达为语言规则,而不是实现限制。

推荐下一步

PropertyAccessResolver 开始。

理由:

  • 最近 bug 多集中在属性访问、可见性、typed property、nullsafe、static-vs-instance 路径。
  • 现有测试较容易扩展。
  • 属性访问是类型系统、优化器、调用生成之外相对独立的领域,适合先建立 resolver/result 模式。
  • 完成后可直接减少 CompilerBase 中的复杂分支,并为后续 ExpressionEmitter 拆分打基础。