这是关于“在 WebAssembly 上实现快速 JS”的三部分系列的最后一篇帖子。
- 直接(单遍)编译器风格:从每个字节码(JS 和 CacheIR)到 WebAssembly 直接实现编译器是 straightforward 的,如 SpiderMonkey 已有基线编译器实现,可将其移植到生成 WebAssembly 字节码,但此方法存在问题,如 Wasm 平台常禁止运行时代码生成,可能无时间“预热”生成 Wasm 字节码,且 JIT 开发和调试困难,无先进调试能力。
- 将 SpiderMonkey JIT 移植到 Wasm:此方法已被采用,通过在 Wasm 模块内“内联”生成 Wasm 字节码并使用特殊的主机调用接口添加新可调用函数,但存在诸多限制。
- 新希望:单一语义源:回到 PBL,已有为 IC 存根的 CacheIR 主体和调用它们的 JS 字节码开发的解释器,能忠实维护 SpiderMonkey 基线执行的不变量,且 PBL 可在原生构建中进行开发,可将其语义用于编译器开发,如 GraalVM 利用此原理从解释器代码中导出编译器。
- 第一个 Futamura 投影:通过将函数中的常量作为参数,可导出一个新函数,Futamura 描述了三个投影级别,weval 是 WebAssembly 部分求值器,可导出 WebAssembly 模块快照内解释器的第一个 Futamura 投影。
- 实际的第一个 Futamura 投影通过上下文特化:常量传播看似可实现第一个 Futamura 投影,但对于包含多个操作码的程序会变得困难,因为存在循环和无法确定常量的情况,需要通过上下文敏感分析来解决,如使
pc
本身成为上下文,在更新pc
时更新上下文,使分析能在不同上下文中保持常量值,从而实现代码复制和优化。 - weval 变换:weval 变换是 weval 执行的第一个 Futamura 投影的核心,它在进行常量传播分析的同时进行代码变换,通过处理 SSA 基础的 IR 并将其编译回 Wasm 函数字节码,算法不依赖于特定的解释器,具有一定的通用性和正确性,同时留下了扩展的空间。
- 使用 weval:“快照中的函数特化”抽象:作为解释器作者,与 weval 工具的交互通过“函数特化”抽象实现,在 Wizer 工具的基础上,weval 在 Wasm 快照中找到要特化的字节码和解释器函数,进行特化并添加新函数,请求是异步的,在快照处理完成后才会填充函数指针。
- 通过保留语义的特化保证正确性:为使用 weval,解释器只需添加
update_context
内在函数并发出特化请求,weval 编译的代码和解释器代码使用相同的操作码实现,通过锁步执行模式可测试 weval 的正确性。 - 优化代码生成:抽象化的解释器状态:通过提供内在函数,解释器可将管理的 guest-language 状态更高效地映射到寄存器或其他快速存储,weval 提供了针对“本地”和“操作数栈”的内在函数,提高了 SpiderMonkey 的性能。
- 其他优化和细节:weval 工具具有顶级缓存,可避免重复计算;添加特殊处理以消除 LLVM 影子栈的残留;可清理部分求值产生的模式;处理
switch
语句以生成 Wasm 级别的切换操作码。 - 结果:注释开销和运行时加速:在 SpiderMonkey 中添加 weval 支持的 PR 代码增量较小,运行时加速明显,总体几何平均加速为 2.77 倍,最高可达 4.39 倍,weval 的编译和优化起到了重要作用。与 GraalVM 相比,weval 目标为 WebAssembly,完全是 AOT 的,对解释器要求较低,但不支持某些机制。与其他 AOT 编译器相比,weval 提供了与 SpiderMonkey 引擎的完全兼容性。
- 结论:JIT 的原理推导:weval 是一个有趣的项目,通过将字节码执行的语义问题与代码生成问题分离,利用 PBL 解释器和 Wasm 的特性,实现了 JavaScript 的 AOT 编译器,证明了通过自动推导可以简化复杂系统的实现,还有很多工作可做。
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。