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.
 
 

17 KiB

专利申请技术交底书:一种混合态编程语言的编译执行方法及系统

本文档为专利申请技术交底草案,面向专利代理人说明技术方案。文中“本发明”指“一种混合态编程语言的编译执行方法及系统”。本文档不构成法律意见,正式权利要求应由专利代理人结合检索结果进一步撰写。

1. 技术应用产品

本发明应用于 Swoole-Compiler PHP AOT 编译器。Swoole-Compiler 并非简单地将 PHP 完全静态化,也不是传统意义上的纯解释器或纯即时编译器,而是一种“混合态编程语言编译执行系统”。

在该系统中,同一份 PHP 程序可以同时包含:

  • 可被静态分析和预先编译的静态态代码;
  • 需要保留 PHP 动态语义的动态态代码;
  • 静态态与动态态之间的边界转换代码;
  • 可作为 PHP 扩展被动态运行时调用的入口;
  • 可作为独立二进制程序执行的入口。

该系统使动态语言具备静态语言的性能优势,同时保留动态语言的灵活优势。

2. 术语解释

术语 中文说明
混合态 同一程序中同时存在静态编译态和动态解释态,并可在两种状态之间切换
静态态 编译器可在编译期确定类型、函数、方法、属性或控制路径的状态
动态态 需要在运行时依据 PHP 语义解析类型、函数、方法或属性的状态
AOT Ahead-Of-Time,预先编译
VM Virtual Machine,虚拟机,这里指 PHP Zend VM
Native 函数 被 AOT 编译为 C++ 函数的 PHP 函数或方法
动态调用 运行时根据函数名、方法名、对象类型或回调对象查找并调用目标
静态直连 编译期确定调用目标后生成 C++ 直接函数调用
ArgInfo 参数信息,包括类型、默认值、引用、变长参数等
符号表 编译器记录函数、类、接口、trait、常量、属性、方法等信息的数据结构
包装函数 将 Zend VM 调用入口转换为 Native 函数调用的桥接函数
回退 当静态分析不能保证语义正确时,退回动态运行时路径

3. 技术背景及现有技术方案

3.1 大的技术背景

传统编程语言通常可分为两类。

第一类是静态编译语言,例如 C、C++、Go、Rust。它们在运行前完成编译,类型、函数签名和内存布局大多在编译期确定,因此运行性能高。但这类语言通常开发灵活性较弱,动态加载、动态方法调用、运行时修改数据结构等能力有限。

第二类是动态解释语言,例如 PHP、Python、Ruby、JavaScript。它们在运行时解析变量类型、函数调用、对象属性和方法分派,因此开发效率高、表达灵活。但这类语言通常需要虚拟机、解释器、动态类型判断和运行时查找,性能不如静态编译语言。

现有 JIT 技术试图在运行时将热点代码编译为机器码,但 JIT 仍依赖运行时采样、类型反馈和热点判断,且难以在部署前得到完全确定的本地代码。传统 AOT 技术则倾向于将程序全部静态化,但面对动态语言的变量函数、动态方法、魔术方法、反射、动态属性、闭包和回调时容易丢失语言语义。

3.2 小的技术背景

Swoole-Compiler 面向 PHP 程序。PHP 程序具有以下动态特征:

  • 变量可以保存任意类型;
  • 函数和方法可以通过字符串或数组形式动态调用;
  • 对象可通过 __call()__get()__set() 等魔术方法处理动态行为;
  • 命名空间、use 别名、trait、继承、方法重写会影响真实调用目标;
  • 参数支持默认值、命名参数、变长参数、引用参数;
  • PHP 内置函数和用户函数可混合调用;
  • 程序既可能作为 PHP 扩展运行,也可能作为独立二进制程序运行。

如果编译器强行将所有 PHP 代码转换为静态 C++ 调用,会破坏上述动态语义。如果完全保留动态解释执行,则无法获得 AOT 的性能优势。

因此,需要一种新的混合态编程语言设计方案:在可静态确定的区域进入静态态,在不可静态确定的区域进入动态态,并通过统一边界机制实现两种状态的互通。

3.3 最相近的现有技术方案

现有技术包括:

  1. 纯解释执行方案
    PHP 源码由 Zend VM 解释执行。该方案兼容性强,但每次函数调用、属性访问、数组操作都依赖运行时动态机制。

  2. 纯静态编译方案
    将源程序整体转换为 C/C++ 或机器码。该方案性能高,但对于动态语言特性支持困难,通常需要限制大量语法或改变语言语义。

  3. JIT 即时编译方案
    运行时根据热点路径和类型反馈进行编译。该方案可提升热点性能,但仍需要解释器协作,且编译结果依赖运行时状态。

  4. 手写扩展或 FFI 方案
    通过 C/C++ 扩展或外部函数接口让动态语言调用静态代码。该方案可以局部提升性能,但静态代码与动态代码之间缺乏统一语言级编译模型。

4. 现有技术缺点及本发明目的

4.1 现有技术缺点

现有方案存在以下问题:

  1. 纯解释执行无法充分利用静态类型和本地代码性能。
  2. 纯静态编译难以兼容动态语言的动态调用、魔术方法、动态属性和反射等特性。
  3. JIT 依赖运行时热点和类型反馈,部署前难以得到稳定可预测的编译产物。
  4. 手写扩展需要开发者显式维护动态语言与静态语言之间的接口、类型转换和生命周期。
  5. 动态语言中的函数、类、属性和方法查找通常依赖字符串,重复查找成本较高。
  6. 传统编译器往往以“能否完全静态化”为判断标准,缺少一种表达同一程序内多种执行状态共存的语言级模型。

4.2 本发明目的

本发明的目的在于提供一种混合态编程语言的编译执行方法及系统,使同一动态语言程序能够被划分为静态态和动态态:

  • 在静态态中,编译器生成 C++ 直接调用、强类型变量、强类型容器和本地代码;
  • 在动态态中,系统保留 PHP VM 的动态查找、动态调用、动态属性、回调和通用 zval 语义;
  • 在两种状态之间,通过 ArgInfo、包装函数、符号缓存、类型转换和动态回退机制实现互通。

由此实现静态语言的性能优势和动态语言的灵活优势兼得。

5. 本发明技术方案

5.1 混合态语言模型

本发明提出“混合态”语言模型。该模型不是简单地把动态语言编译成静态语言,而是在编译器和运行时共同支持下,将程序中的语句、表达式、函数、方法、变量和调用划分为不同状态。

混合态至少包括:

  1. 静态函数态:函数定义、参数类型和返回类型可确定,生成 C++ Native 函数。
  2. 静态对象态:对象变量的类可确定,方法调用可直连到 C++ 函数。
  3. 静态数据态:变量可映射为 C++ 原生类型或 C++ 模版容器。
  4. 动态值态:变量使用 php::Var、zval 或 PHP 对象保存,保留动态类型语义。
  5. 动态调用态:函数名、方法名或对象类型无法确定时,通过 PHP 运行时调用。
  6. 边界桥接态:静态态与动态态之间进行参数提取、类型转换、返回值写回和符号缓存。

5.2 系统组成

图 1:混合态编程语言系统组成图

PHP 源码 / 项目配置
        |
        v
预处理与符号提取模块
        |
        v
依赖排序与语义分析模块
        |
        v
混合态判定模块
        |
        +--> 静态态代码生成模块
        |       |
        |       +--> C++ 函数直连
        |       +--> C++ 原生类型
        |       +--> C++ 模版容器
        |
        +--> 动态态代码生成模块
        |       |
        |       +--> PHP VM 动态调用
        |       +--> zval / php::Var 动态值
        |       +--> 魔术方法和回调
        |
        +--> 边界桥接模块
                |
                +--> ArgInfo 参数转换
                +--> Zend 包装函数
                +--> 符号缓存
                +--> 动态回退
        |
        v
C++ 源码与扩展注册代码
        |
        v
PHP 扩展或可执行二进制

5.3 方法流程

图 2:混合态编译执行流程图

步骤 S1:读取 PHP 源文件和项目配置;
步骤 S2:解析抽象语法树;
步骤 S3:提取函数、类、接口、trait、常量、属性、方法等符号;
步骤 S4:根据符号使用关系对文件进行依赖排序;
步骤 S5:解析函数和方法的 ArgInfo;
步骤 S6:对每个表达式或调用点判断是否可进入静态态;
步骤 S7:若目标、类型和参数可确定,则生成 C++ 静态直连代码;
步骤 S8:若存在动态语义风险,则生成动态态调用代码;
步骤 S9:为静态态与动态态边界生成参数转换、返回值转换和包装函数;
步骤 S10:为函数、类、方法、属性生成运行时缓存映射;
步骤 S11:编译 C++ 代码,生成 PHP 扩展或可执行程序;
步骤 S12:运行时根据生成代码执行静态直连或动态回退路径。

5.4 预处理与符号提取

编译器在正式生成 C++ 代码前,先扫描 PHP 源码,提取:

  • 函数定义;
  • 类定义;
  • 接口定义;
  • trait 定义;
  • 类属性;
  • 类方法;
  • 类常量;
  • 全局常量;
  • 命名空间和 use 别名;
  • 函数、类、方法、常量的使用关系。

随后构建符号依赖图,并对源文件进行拓扑排序。这样可提高静态分析成功率,避免因为文件顺序导致函数或类暂不可见。

5.5 ArgInfo 统一参数模型

本发明使用 ArgInfo 描述函数和方法参数,包括:

参数名称;
参数类型;
对象类名;
是否引用参数;
是否变长参数;
是否可空;
默认值;
是否为 UnsafePtr;
是否为构造器属性提升参数。

ArgInfo 同时服务于两个方向:

  1. AOT 内部直连调用:编译器根据 ArgInfo 重排命名参数、填充默认值、合并变长参数并生成 C++ 直接调用。
  2. Zend 运行时入口调用:当编译后函数作为 PHP 扩展函数被 PHP VM 调用时,包装函数根据 ArgInfo 从调用栈读取参数并转换类型。

该设计使静态态和动态态共享同一套参数语义。

5.6 静态态代码生成

当编译器能够确定调用目标时,生成 C++ 直接调用。

示例:

function add(int $a, int $b): int
{
    return $a + $b;
}

add(1, 2);

生成类似:

php_add(php::toInt(1L), php::toInt(2L));

对于对象方法:

$obj->run($arg);

如果 $obj 的类可确定,且不存在方法重写或动态魔术方法风险,则生成:

php_Class_run(obj, converted_arg);

静态态还包括:

  • intfloatbool 等原生类型;
  • std::arraystd::vector 等 C++ 模版容器;
  • 可确定类的对象属性访问;
  • 可确定函数的默认参数和变长参数处理;
  • 编译期可确定的常量和类常量访问。

5.7 动态态代码生成

当出现以下情况时,编译器进入动态态:

  • 函数名来自变量;
  • 方法名来自变量;
  • 对象真实类型无法确定;
  • 类可能存在 __call() 魔术方法;
  • 方法被子类重写,静态直连可能改变动态分派语义;
  • 需要调用 PHP 内置动态函数;
  • 回调、闭包或占位符表达式无法完全静态确定;
  • 变量使用通用 mixed 或动态数组语义。

动态态生成类似:

php::call(function_ptr, arg_list);

或:

object.call(method_ptr, arg_list);

动态态保持 PHP VM 的函数查找、方法分派、zval 类型和动态回调语义。

5.8 静态直连与动态回退判定

本发明不要求所有代码都静态化,而是在每个调用点执行判定。

图 3:调用点状态判定流程

调用表达式
    |
    v
函数名或方法名是否为字面量?
    |
    +-- 否 --> 动态态调用
    |
    +-- 是
          |
          v
是否可在符号表中找到 Native 目标?
          |
          +-- 否 --> 动态态调用
          |
          +-- 是
                |
                v
对象类或函数签名是否可确定?
                |
                +-- 否 --> 动态态调用
                |
                +-- 是
                      |
                      v
是否存在魔术方法、重写、动态回调等风险?
                      |
                      +-- 是 --> 动态态调用
                      |
                      +-- 否 --> 静态态直连

该机制使语言运行时不是单一状态,而是按代码位置自动选择最佳状态。

5.9 边界桥接机制

静态态和动态态之间通过边界桥接机制互通。

5.9.1 动态调用静态函数

当 PHP VM 调用编译后的函数时,进入 Zend 包装函数:

Zend 调用入口
    |
    v
读取调用栈参数
    |
    v
根据 ArgInfo 转换参数
    |
    v
调用 Native 函数
    |
    v
将返回值写回 return_value

5.9.2 静态代码调用动态函数

当 AOT 代码无法确定目标时,生成动态调用:

C++ 静态代码
    |
    v
构造 PHP 动态参数列表
    |
    v
查找函数或方法
    |
    v
调用 PHP VM 动态路径
    |
    v
返回 php::Var 动态值

5.9.3 静态数据流入动态态

例如强类型容器赋值给普通 PHP 变量时,自动转换为 PHP Array:

$arr = $vector;

生成:

arr = php::toArray(vector);

5.9.4 动态对象流入静态态

当从动态数组或函数返回值取出对象,而后续需要静态方法调用时,可通过编译期类型声明函数或类型转换函数告知编译器对象类名,从而进入静态对象态。

5.10 符号缓存机制

为减少动态态运行时字符串查找开销,本发明为符号建立整数编号和缓存数组:

zend_class_entry *class_map[N];
zend_function *func_map[M];
uint32_t property_map[K];

首次查找时根据字符串名称取得 Zend 结构指针并缓存;后续调用通过整数 ID 快速访问。

示例:

zend_function *get_func(int id, const php::Str &name) {
    if (func_map[id] == nullptr) {
        func_map[id] = php::getFunction(name);
    }
    return func_map[id];
}

该机制使动态态仍然比纯解释路径更高效。

5.11 双运行形态

本发明支持两种运行形态:

  1. 扩展形态:编译结果是 PHP 扩展,可加载到 PHP-FPM 或 CLI 中,由 PHP VM 调用。
  2. 二进制形态:编译结果是可执行程序,由 main() 函数作为入口,可直接运行。

这两种形态共享同一套混合态编译模型。

6. 关键点和欲保护点

  1. 提出一种混合态编程语言模型,使同一动态语言程序中同时存在静态态和动态态。
  2. 在编译器中按函数、方法、变量、表达式和调用点自动判定静态态或动态态。
  3. 对可确定目标生成 C++ 静态直连代码,对不可确定目标自动回退到 PHP 动态调用代码。
  4. 使用 ArgInfo 作为静态态和动态态共享的参数语义模型。
  5. 生成 Zend 包装函数,使动态运行时可以调用 AOT 生成的 Native 函数。
  6. 在 AOT 静态代码中构造动态参数列表,使静态代码可以调用 PHP 动态函数、动态方法或回调。
  7. 使用符号缓存机制减少动态态运行时的函数、类、方法、属性字符串查找开销。
  8. 支持扩展形态和二进制形态两种运行产物,均基于同一混合态语言模型。
  9. 在静态态中支持原生类型和 C++ 模版容器,在动态态中保留 zval、php::Var、PHP Array 和 PHP 对象语义。

7. 与现有技术相比的优点

与传统静态编译语言相比,本发明保留了动态语言的动态调用、动态类型、动态对象和回调能力,不要求程序完全静态化。

与传统动态解释语言相比,本发明可在编译期确定的区域生成 C++ 直接调用、原生类型和强类型容器,显著减少运行时查找和动态类型开销。

与 JIT 相比,本发明在部署前生成稳定的编译产物,不依赖运行时热点采样和类型反馈,适合需要稳定部署、保护源码和提升性能的生产环境。

与手写扩展相比,本发明由编译器自动生成包装函数、参数转换、符号缓存和动态回退路径,降低开发者在 PHP 与 C++ 之间手动桥接的成本。

8. 可选实施方式

本发明不限于 PHP,也可用于 Python、JavaScript、Ruby、Lua 等动态语言。目标静态语言不限于 C++,也可为 Rust、Go、C 或 LLVM 中间表示。

混合态判定粒度可以是函数级、基本块级、语句级或表达式级。符号缓存也可使用哈希表、数组、句柄表或运行时内联缓存实现。

9. 保密说明

本文档涉及 Swoole-Compiler 的整体编译模型、静态直连与动态回退策略、ArgInfo 边界桥接机制和符号缓存方案。正式申请前建议作为内部技术资料管理。