一、简介
v8 turbolizer 有助于我们分析 JIT turbofan 的优化方式以及优化过程。但我们常常对于 turbolizer 生成的 IR 图一知半解,不清楚具体符号所代表的意思。以下为笔者阅读相关代码后所做的笔记。
二、TurboFan Json 格式
-
--trace-turbo
参数将会生成一个 JSON 格式的数据。通过在 turbolizer 上加载该 JSON,可以得到一个这样的IR图:
- 其中,该 JSON 的格式如下:
{ "function": "opt_me", "sourcePosition": 109, "source": [js source], "phases": [ { "name": "Typed", "type": "graph", "data": { "nodes": [ [...], { "id": 20, "label": "FrameState[INTERPRETED_FRAME, 11, Ignore, 0x1a5acd4aa5e9 <SharedFunctionInfo opt_me>]", "title": "FrameState[INTERPRETED_FRAME, 11, Ignore, 0x1a5acd4aa5e9 <SharedFunctionInfo opt_me>]", "live": true, "properties": "Idempotent, NoRead, NoWrite, NoThrow, NoDeopt", "pos": 178, "opcode": "FrameState", "control": false, "opinfo": "5 v 0 eff 0 ctrl in, 1 v 0 eff 0 ctrl out", "type": "Internal" }, [...] ], "edges": [ { "source": 100, "target": 101, "index": 0, "type": "control" }, [...] ] } }, [...] ], "nodePositions": { [...] } }
简单的概括一下,就是:
- function: 函数名称
- sourcePosition:代码的起始位置。
- source: 当前 turboFan 优化的 JS 代码
- phases: turboFan 的各个优化阶段
- 优化阶段1
- name: 当前优化阶段的名称
- type:显示的形式,是
graph
IR 图还是 文本。 - data: 当前阶段真正存放的结点与边的数据。
- nodes: 结点数据
- 结点1
- id: 结点ID,通常是一个数字
- label:结点标签
- title:结点主题
- live: 当前结点是否是活结点,为 true / false
- properties:当前结点的属性
- pos:暂且不说
- opcode:当前结点的操作码,例如
End
- control:当前是否是控制结点,为 true / false
- opinfo:具体的结点信息,通常表示当前结点的ValueInputCount、EffectInputCount、ControlInputCount、ValueOutputCount、EffectOutputCount、ControlOutputCount。
表示方式如下:
“\<ValueInputCount\> v
\<EffectInputCount\> eff \<ControlInputCount\> ctrl in, \<ValueOutputCount\> v \<EffectOutputCount\> eff \<ControlOutputCount\> ctrl out"
例如:”0 v 1 eff 1 ctrl in, 0 v 1 eff 0 ctrl out”
- [其他结点]
- 结点1
- edges:边的数据
- 边1
- source:边的源节点 ID
- target:边的目标节点ID
- index:当前边连接到目标节点的哪个输入
- type:当前边的类型,例如 control、value、effect等等
- [其他边]
- 边1
- nodes: 结点数据
- [其他优化阶段]
- 优化阶段1
- nodePositions:每个结点在 JS 源码中所对应的代码位置
三、Node
a. 属性说明
以下是截取出的一个 Node 示例:
{
"id": 128,
"label": "LoadField[+16]",
"title": "LoadField[tagged base, 16, Internal, kRepTaggedPointer|kTypeAny, PointerWriteBarrier]",
"live": true,
"properties": "NoWrite, NoThrow, NoDeopt",
"pos": 388,
"opcode": "LoadField",
"control": false,
"opinfo": "1 v 1 eff 1 ctrl in, 1 v 1 eff 0 ctrl out",
"type": "Internal"
}
对应的结点如下:
一一对应以下便可以看出,其中的 id、label、title、properties、opinfo 以及 type 均显现在图中。
而 live、pos、opcode 以及 control 字段则是给 turbolizer.js 使用的。
注意到上图中的 “Inplace update in phase: Typed”,其中的 phase 则是 turbolizer.js 动态分析出的,不在 JSON 中记录。
b. 颜色
我们可以注意到,IR图中的结点都有颜色,其中颜色貌似符合某种规律。
通过查阅 turbolizer.js 以及 在线 turbolizer 的 css 代码,turbolizer 将结点分为了以下几种结点,并设置了不同的颜色加以区分:
- Control 结点:对于那些控制结点, 即 JSON 数据中 control 字段为 true 的结点,其颜色为黄色。
- Input 结点:那些 opcode 为 Parameter 或 Constant 结点,其颜色为浅蓝色。
- Live 结点(这其实不能算一类结点):即 live 字段为 true 的结点。其反向结点——DeadNode——的颜色会在原先颜色的基础上进行浅色化处理,例如以下图片。图片中的两个结点其类型相同,所不同的是左边的结点是 Dead,右边结点是 Live。
- JavaScript结点:那些 opcode 以 JS 开头的结点,其颜色为橙红色。
- Simplified 结点:那些 opcode 包含 Phi、Boolean、Number、String、Change、Object、Reference、Any、ToNumber、AnyToBoolean、Load、Store,但不是 JavaScript类型的结点。其颜色如下所示:
- Machine 结点:除了上述四种结点以外,剩余的结点。颜色如下所示:
四、Edge
Edge 中的 Type 共有五种,分别是 value、context、frame-state、effect、control 以及最后一个 unknown。
以下是这些边的一些例子:
a. value 边
对于该边:
{
"source": 80,
"target": 83,
"index": 4,
"type": "value"
}
其边的视觉效果如下:
可以看到,对于 Value 边来说,是一条实线。
b. context 边
对于该边:
{
"source": 4,
"target": 49,
"index": 3,
"type": "context"
}
视觉效果如下:
可以看到,Context边也是一条实线。但在当前这个例子中,由于 Context 边只会由 Parameter[%context#4]
结点发出,因此不会与 Value 边混淆。
这里需要注意一下,Context 边只会存在于某个 Context 结点发出的所有边,即不会出现结点既发出 Context 边又发出 Value 边的情况。
如果有还请指正。
c. frame-state 边
例子:
{
"source": 50,
"target": 49,
"index": 4,
"type": "frame-state"
}
视觉效果:
可以看到,对于一条 frame-state 边,其视觉效果是一条 疏虚线。
frame-state 边一定是由一个 FrameState 结点发出的。
上图的另一条虚线是密虚线,所不同的是虚线的疏密程度。
d. effect 边
例子:
{
"source": 114,
"target": 49,
"index": 5,
"type": "effect"
}
视觉效果:
即 effect 边的显示效果是 密虚线。
e. control 边
例子:
{
"source": 31,
"target": 49,
"index": 6,
"type": "control"
}
视觉效果:
注意:与 value 边相同,control 边的显示效果也是一条实线。这意味着单单只看 IR 图的话,是无法将 Control 边和 Value 边区分开的。
五、参考的源码
- v8/tools/turbolizer/build/turbolizer.js
- v8/src/compiler/graph-visualizer.cc