今天我们来了解一下JavaScriptCore中的JIT机制。
一、JIT基本概念
JIT(Just In Time)编译器:是指程序逻辑以代码(或字节码)形式下发到目标机(如客户端)上,在系统即将运行此逻辑的前一刻,目标机系统上的编译器才将这些代码编译成机器指令,然后再交给系统执行。因为它的编译发生成运行前一刻,刚刚能赶得上执行,所以叫做Just In Time编译器.
谈到JIT,经常有同学把它与解释器(Interpreter)混淆,下面首先看一下这两个概念的区别:
解释器(Interpreter)和JIT的区别
虚拟机执行一段程序,一般有两种方式:解释执行和先编译再执行。
-
解释执行:虚拟机读取程序字节码,取出其中的“虚拟机指令”,由解释器逐条进行解释执行
- “虚拟机指令”可以理解为一种
DSL(Domain Specific Language),它作为一种领域专用数据结构,包含了虚拟机运行程序所需的所有数据信息(如:操作符、操作数等)
- “虚拟机指令”可以理解为一种
-
先编译再执行,根据编译的时机不同,又可以分为
AOT和JIT:-
AOT:Ahead of Time,即开发者先将程序编译成机器码,再将由机器码构成的二进制程序下发到客户端运行。JavaScriptCore目前不支持AOT- 一个支持
AOT的虚拟机的例子是Dart VM,它可以执行事先编译成机器码的Dart程序。
-
JIT:Just in Time,虚拟机读取程序字节码,在真正运行代码逻辑前,先将他们编译成“机器指令”序列,再执行这些机器指令JIT编译后,待运行的方法就已经是机器指令了- 一般对于比较“热”的方法可以在运行时动态调整
JIT的级别,根据调用现场情况开启相应的优化
-
二、JavaScriptCore中的解释器和JIT
JavaScriptCore中的解释器(LLInt)和JIT都可以执行JavaScript代码编译成的字节码(bytecode)[1]。而其中JIT又根据优化级别的不同分为三种:Baseline JIT、DFG JIT和FTL JIT。
下面具体讲一下这四种模式的主要特点。
JavaScriptCore执行代码的四种模式
1. LLInt解释器模式
-
LLInt是用跨平台的汇编语言(offlineasm)实现的 -
逐条解释执行
JSC虚机指令 -
JS代码的执行总是从LLInt模式开始
由
LLInt切换到Baseline JIT的条件(满足任意一条即可):
- 方法中任意一个语句执行次数超过
100次- 方法被调用了超过
6次
虚拟机由解释器模式向
JIT模式切换时,解释器会将当前字节码偏移传给JIT,JIT只编译此字节码偏移能够到达的代码分支
OSR(On Stack Replacement):是一种可以在程序运行时动态切换其内部方法具体实现的技术
- 方法切换可以在任意一条语句结束后
- 是虚拟机在解释器和各
JIT模式间无缝切换的关键技术保障
2. Baseline JIT模式
- 只是做了简单的“机器码化”,减小了解释器按指令
dispatch的开销,代码(指令序列)本身并未做任何优化。
- 切换到
DFG JIT的条件:
- 方法中任意语句执行次数超过
1000- 方法被调用次数超过
66次
3. DFG JIT模式(Data Flow Graph)
DFG JIT 主要流程:
-
DFG会把字节码转成DFG CPS形式CPS(Continuation-Passing Style)CPS表示这样一种形式[3]:一个函数f除了它自身的参数外,总是有一个额外的参数continuation。continuation也是一个函数,当f完成了自己的返回值计算之后,不是返回,而是将此返回值作为continuation的参数,调用continuation。所以CPS形式的函数从形式上看它不会return,当它要return的时候会将所有的参数传递给continuation,让continuation继续去执行。CPS的优点是让如下的信息显式化:过程返回(调用一个continuation),中间值(具有显式的名称),求值顺序,尾调用(采用相同的continuation调用一个过程)。CPS有利于后续通过profiling来预测数据类型,这些“数据类型预测”能减少后续生成代码时需添加的类型检查逻辑。
-
DFG启用了多种常规的编译优化- 寄存器分配
- 控制流图简化
- 公共子表达式消除
- 无用代码消除
- 稀疏有条件的常量传播
- 编译时计算常量数据,并将它传播到相关代码中,达到整体简化代码的目的
-
DFG JIT编译器将CPS形式的代码优化后编译成机器码
例:如下的 foo 方法:
function foo(a, b) { return a + b + 42; }
- 经过
LLInt和Baseline JIT的多次运行后,profiler收集到了很多a和b的运行时类型信息,如果发现都是int,则生成的机器指令可以是:先判断是否是int,如果是则跳转到类型固化为int的机器指令代码,否则OSR exit回Baseline JIT。
4. FTL JIT模式
FTL JIT主要流程:(复用了DFG的大部分流程)
- 之前是使用的
LLVM后端,后来改成了B3,上图还是旧的框架图
- 复用了
DFG的大部分流程,将原DFG流程中的DFG后端替换为新的FTL流程:CPS转SSASSA转LLVM IRLLVM IR的编译优化IR转机器码
- 使用了
LLVM后端,引入了更多的编译优化(类似C程序的极致优化)
参考资料
- [1] webkit.org/blog/9329/a…
- [2] liveoverflow.com/just-in-tim…
- [3] zhuanlan.zhihu.com/p/263420069
- [4] webkit.org/blog/3362/i…