V8有了全新的超快速非优化JS编译器,性能提高5-15%( 二 )


缺少 IR 意味着编译器的优化机会有限 , 只能做一些非常本地的小幅度优化 。 这也意味着我们必须将整个实现分别移植到我们支持的每种架构上 , 因为这里没有架构无关的中间阶段 。 但事实证明这些都不是问题:快速编译器是简单编译器 , 因此代码很容易移植;并且 Sparkplug 不需要大量优化 , 因为我们稍后会在管道中提供优化效果很出色的编译器 。
从技术上讲 , 我们目前对字节码进行了两次 pass——一次用来发现循环 , 第二次生成实际代码 。 不过 , 我们最终的计划是摆脱第一个 。
解析器兼容框架
向现有的成熟 JavaScript VM 添加新的编译器是一项艰巨的任务 。 除了标准执行之外 , 你还需要支持各种各样的事情;V8 有一个调试器、一个 stack-walking CPU profiler、针对异常的堆栈跟踪、集成到升级、堆栈替换以优化代码实现热循环……实在很多 。
Sparkplug 巧妙地简化了所有这些问题 , 具体方法就是保持一个“与解析器兼容的堆栈框架” 。
稍微解释下 。 堆栈框架(Stack frame)是代码执行存储函数状态的方式 。 每当你调用一个新函数时 , 它都会为该函数的局部变量创建一个新的堆栈框架 。 一个堆栈框架由一个框架指针(标记其开始)和一个堆栈指针(标记其结束)定义:
V8有了全新的超快速非优化JS编译器,性能提高5-15%
本文插图
堆栈框架 , 带有堆栈和框架指针
看到这里 , 很多读者会表示抗议:“这张图不对啊 , 堆栈明显是朝着相反的方向的!” 。 别急 , 我为你做了一个按钮:
当一个函数被调用时 , 返回地址被推入这个堆栈;该函数返回时会弹出它 , 来知道该返回到何处 。 然后 , 当该函数创建一个新框架时 , 它将旧的框架指针保存在堆栈上 , 并将新的框架指针设置为指向它自己的堆栈框架的起始 。 因此 , 这个堆栈有了一个框架指针链 , 每个框架指针都指向前一个框架的起始:
V8有了全新的超快速非优化JS编译器,性能提高5-15%
本文插图
多个调用的堆栈框架
严格来说这只是一个约定 , 后面是生成的代码 , 它不是必需的 。 不过这是一种相当常见的方式;唯一真正中断的一次是堆栈框架完全清除的时候 , 或者可以改用调试边表(side-table)遍历堆栈框架的时候 。
这是针对所有函数类型的常规堆栈布局;然后是关于如何传递参数 , 以及函数如何在其框架中存储值的约定 。 在 V8 中 , 我们有针对 JavaScript 框架的约定 , 即在调用函数之前将参数(包括接收器)以相反的顺序推入堆栈 , 并且堆栈上的前几个槽为:被调用的当前函数;被调用的上下文;以及传递的参数数量 。 这是我们的“标准”JS 框架布局: