Intro: Analsis of Framestates IR
Basic Framestates
反优化是 JIT
编译器的一个重要特征。deoptimization
将执行流从优化代码转移到解释器。思考,为了实现这样的转换,我们需要知道:
- 我们想从哪里继续解释执行
- 如何从优化代码的物理机器状态在延续点重构解释器运行所需
VM
状态。
第一,为了知道我们想要在哪里继续执行,我们必须保存对当前执行的 function
的 Bytecode
的引用以及一个字节码索引bci
(bytecode index
,为了让 Ignition
找到准确解释执行点)。第二,对于 VM
状态,可以对于没一个会产生 side effect
的 node
维护一个保存局部变量的变量槽,并映射到在 IR
中的具体值。当执行反优化时可以将 [name:value]
移植到它们在解释器自动机的对应物理位置。
在 IR Graph
中,需要跟踪可能对 VM
的状态产生 side effect
的节点以及相关联的数据,比如内存写入、方法调用等节点。这些会产生新的 VM
状态的节点我们统称为状态剥离节点。对于每个这样的节点,我们均需要保存虚拟机在执行这些 node
之后的状态信息。
状态剥离节点本身不能描述 VM
状态,于是我们引入了 Framestates
。这些新节点记录了倘若发生 deopt
时的 methods
和 bci
,并且存有变量槽。状态剥离节点(比如 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
,这使得 FrameState
在 IR
中是树状表示的。
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