本文最后更新于:星期二, 三月 10日 2020, 5:09 下午

ChakraCore简介

结构:

1559462902531

基本执行流程:

当一个函数第一次执行时,ChakraCore 的语法解析器(parser)产生一个抽象语法树(AST)来代表这个函数的源码,随后AST 会被翻译为字节码(bytecode),这些字节码将由ChakraCore 解释器直接执行。在解释执行期间解释器会收集一些程序信息,如类型信息、调用次数,这些信息会被用来帮助JIT 编译器生成高度优化的机器码。当ChakraCore 在解释器中发现一个函数或者循环体(loop-body)被多次执行时,会将其送入后台JIT 编译队列为这个函数生成优化的代码。一旦这些优化代码准备就绪,ChakraCore 就会替换函数和循环的入口到这些新代码,之后的执行将远快于之前的解释执行。

ChakraCore 的后台JIT 编译器借助解释器生成的profile 数据来推断可能出现的模式,从而生成高度优化的本地代码。当得到JavaScript 代码的某些动态特性后,如果代码行为打破了基于profile 的预测,就会进行bailout,而bailout会放弃执行JIT代码转回使用解释器继续执行字节码,同时继续收集更多的配置文件数据。为了平衡JIT 编译时间与内存占用,ChakraCore 并不在一个函数每次释出时编译它,而是利用缓存下来的编译结果直到执行次数达到一定的门槛,之后才会迫使代码重新被JIT 编译并且抛弃旧的编译结果。

1559462547485

JIT编译器

ChakraCore 拥有两级JIT 编译器。在同一个后台线程中,ChakraCore 有一个完全JIT 编译器(Full JIT Compiler) 用来产生高度优化的代码;还有一个简单JIT 编译器(Simple JIT Compiler),这是一个有较少优化版本的完全JIT 编译器。在执行管线中,ChakraCore 首先将解释执行的函数换入简单JIT 编译器,然后才是完全JIT 编译。在大多数情况下,简单JIT 编译耗时少于完全JIT 编译,所以相比单级JIT,这种架构有助于更快启动app 和网页。多一个Simple JIT 的另外一个优势是,当”释出“发生时,函数的执行可以更快地从解释切换到简单JIT 编译,直到完全JIT 编译的代码就绪。简单JIT 编译的代码执行管线依然继续收集profile 信息以供完全JIT 编译器使用。

无论何时,只要有潜在的未被利用的硬件资源,ChakraCore 也可为后台JIT 编译器产生多个并行线程。存在多个后台JIT 编译线程时,ChakraCore 的简单JIT 编译和完全JIT 编译的工作都会被分摊到多个编译线程上进行跨线程编译。这有助于在总体上减少JIT 编译延迟。

1559463103759

垃圾回收

ChakraCore 拥有一个分代式标记清扫垃圾回收器,它支持并行、部分回收。当完全并行GC 被初始化,ChakraCore 的后台GC 会进行一个初始标记阶段,然后重新扫描(rescan)来找出在这个初始标记阶段被主线程修改的对象,随后再运行第二个标记阶段来标记重新扫描时被修改的对象。当第二个标记阶段扫描结束后,主线程暂停执行并启动最终的重新扫描(final rescan),之后最终的标记阶段(final marking pass)会被分解到主线程和已经在执行的GC 线程。最后清扫阶段由后台GC 线程完成,并且将无法到达的(unreachable)的对象重新加入分配池。

1559463144069

上面内容都来自ChakraCore的wiki文档

JIT pipeline

1559475003058

JIT编译的主要步骤:

  • IRBulilderPhase:根据bytecode生成IR(中间码)
  • InlinePhase: 检查是否可以内联某些内容
  • FGBuildPhase: 通过IR构建控制流程图CFG
  • GlobOptPhase: 全局优化器
    • SimpleJit
    • FullJit
  • LowererPhase: 减少IR 依赖机器的操作
  • RegAllocPhase

FullJit: (它通过了3次CFG,一次向后,一次向前,最后是另一次向后)

1559476654120

1559476662882

1559476589034

Backward pass

向后经过每个块。对于每个块, 向后进行指令信息搜集。为每个块收集其后续块的信息,在处理新块时合并每个后续块。可以执行一些简单的优化, 如指令重写、某些折叠优化等..

同时搜集一些未来优化时可能用上的信息。

Forward pass

向前经过每个块,对每个块的每条指令调用不同的方法来处理(用来处理某些指令的方法,比如switch语句)

在处理新块时合并每个前任块的信息,同时这里会生成真正优化的代码。

Symbol liveness and kill mechanism

1559479879332

1559479976387

这个机制很容易出现问题,错误的一个主要来源是JIT在需要时无法杀死某些信息

Forward pass : Loops

1559480428074

1559480445986

Deadstore pass

1559480624383

REFERENCE


JS-Engine      ChakraCore

本博客所有文章除特别声明外,均采用 CC BY-SA 3.0协议 。转载请注明出处!