V8 Optimize: FrameState

V8 Optimize: FrameState

Intro: Analsis of Framestates IR

Basic Framestates

反优化是 JIT 编译器的一个重要特征。deoptimization 将执行流从优化代码转移到解释器。思考,为了实现这样的转换,我们需要知道:

  • 我们想从哪里继续解释执行
  • 如何从优化代码的物理机器状态在延续点重构解释器运行所需 VM 状态。

第一,为了知道我们想要在哪里继续执行,我们必须保存对当前执行的 functionBytecode 的引用以及一个字节码索引bci(bytecode index,为了让 Ignition 找到准确解释执行点)。第二,对于 VM 状态,可以对于没一个会产生 side effectnode 维护一个保存局部变量的变量槽,并映射到在 IR 中的具体值。当执行反优化时可以将 [name:value] 移植到它们在解释器自动机的对应物理位置。

IR Graph 中,需要跟踪可能对 VM 的状态产生 side effect 的节点以及相关联的数据,比如内存写入、方法调用等节点。这些会产生新的 VM状态的节点我们统称为状态剥离节点。对于每个这样的节点,我们均需要保存虚拟机在执行这些 node 之后的状态信息。

状态剥离节点本身不能描述 VM 状态,于是我们引入了 Framestates。这些新节点记录了倘若发生 deopt 时的 methodsbci,并且存有变量槽。状态剥离节点(比如 StoreProperty ) 有一个描述 VM 状态的 FrameState 作为 input,然后将该节点执行之后的 VM 状态写入 FrameState ,换句话说就是记录了 side effect 。一个例子如下所示:在这个例子中,两个 Store 节点均存在副作用,因此存在 State 剥离,需要引入新的 FrameState 节点才能描述新的虚拟机状态。

JIT 优化器 emit 代码之前,我们需要将 deopt 信息与能够触发 deopt 的节点关联起来。为此,我们使用了在触发 deopt 之前的最后一个主状态剥离节点的 FrameState 信息。这意味着两个状态剥离节点之间的所有 JSOperator 均可以返回到同一个个 Bytecode ,并且共享同一组栈变量(同一个 FrameState )。因此,JIT 已经执行的一些指令可能在反优化之后由解释器重新执行一遍。重新执行除了会浪费点运算资源之外并不会有什么影响,因为我们已经确保了任何重新执行的指令都不会修改全局 State,或者说均不是状态剥离节点。

举个例子,如图5:图中的 Guard 节点作为一个边界检查,如果检查 fail 将会返回deopt。在这个例子中,如果要实现 deopt ,那我们将会使用 FrameState bci=18 节点里的 deopt 信息,对应的状态剥离节点为 StoreField b


如果遇到了分支合并,则需要在 Merge 节点上保留 FrameState 信息,如图6。如此一来,任何在 merge 后面的 deopt 节点均可以回溯到 merge 。否则的话,由于回溯 merge 会产生状态分支,IR 将无法描述 merge 之后的 VM 状态,Ignition 可能会执行产生错误的 side effect 的操作。

这个设计模型意味着任何可能导致deopt的节点都要与 FrameState 信息相关联。

嵌套 Framestates

一个 FrameState 节点可以精确地表示一个 method 作用域的 VM 状态,但是,一个 IR 节点的 VM 状态往往不能由一层 FrameState 表示完全,因此需要 FrameState 嵌套。如何实现呢?方法是在 IR 中通过让一个 FrameState 节点引用另一个 FrameState 节点(后者被称为 outer FrameState)。图7展示了一个例子:
需要注意的是,多个 inner FrameState 可以引用相同的 outer FrameState ,这使得 FrameStateIR 中是树状表示的。

Virtual Object

一些更高级优化甚至需要更多的信息来反应 VM 状态,比如 escape-analysis ,将会导致源文件中的某数据结构直接被优化掉,不能被实际分配。在这种情况下,如果进行 deopt ,则需要知道 JIT 优化掉了什么对象以及这些对象是什么值。如果有了这些信息,那么在 deopt 的时候就可以重新对消失的对象进行分配以及赋值了。

图8展示了 IR 中如何表示虚拟的对象(将要被 JIT 优化掉)。每一个虚拟对象都会被表示为 VirtualObject 节点,而且每一个引用这些虚拟对象的栈槽或者本地变量都会指向该节点。VirtualObject 节点真实的内容保存在 VirtualObjectState 节点,作为 FrameState的叶节点。

ps: 其实我不是很能理解为什么 FrameState 需要保留对 VirtualObjectState 的引用,而且obj.v节点可以产生新的 FrameState,图中并未体现出来。另外,根绝这个设计思想,可以推断 escape-analysis 在具体实现当中可能会把 HeapAlloc 节点转化为 VirtualObject,并且可能是通过 inner FrameState 是否存在对 outer FrameState 的某些栈插槽的更改来判断变量是否逃逸,后续的博客会整理出 V8 escape-analysis 的具体细节。

escape-analysis

[TODO]

Reference

【+】http://lafo.ssw.uni-linz.ac.at/papers/2013_VMIL_GraalIR.pdf

Comments

Your browser is out-of-date!

Update your browser to view this website correctly. Update my browser now

×