初识 JavaScriptCore JIT

Posted 码工笔记

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了初识 JavaScriptCore JIT相关的知识,希望对你有一定的参考价值。

今天我们来了解一下 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 会把字节码转成 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 转 SSA

    • SSA 转 LLVM IR

    • LLVM IR 的编译优化

    • IR 转机器码

  • 使用了 LLVM 后端,引入了更多的编译优化(类似 C 程序的极致优化)

参考资料

  • [1] https://webkit.org/blog/9329/a-new-bytecode-format-for-javascriptcore/

  • [2] https://liveoverflow.com/just-in-time-compiler-in-javascriptcore-browser-0x03/

  • [3] https://zhuanlan.zhihu.com/p/263420069

  • [4] https://webkit.org/blog/3362/introducing-the-webkit-ftl-jit/


以上是关于初识 JavaScriptCore JIT的主要内容,如果未能解决你的问题,请参考以下文章

在 JavaScriptCore 中获取默认导出

JavaScriptCore 外部数组

iOS js oc相互调用(JavaScriptCore)

Swift Playgrounds 中的 JavaScriptCore

iOS9 上的 JavaScriptCore 崩溃

转载 iOS js oc相互调用(JavaScriptCore)