[新闻][JavaScript引擎] WebKit JavaScriptCore用新的B3编译器后端替代FTL JIT中的LLVM

[新闻][JavaScript引擎] WebKit JavaScriptCore用新的B3编译器后端替代FTL JIT中的LLVM

传送门:


Filip大大再次立功。苹果内部果然也开撕了嘛…

在这次更新后,JavaScriptCore仍旧会使用4层编译系统:

  • 第1层:解释器 LLInt
  • 第2层:简易非优化JIT编译器 Baseline JIT
  • 第3层:优化JIT编译器 DFG JIT
  • 第4层:高度优化JIT编译器 FTL JIT

只不过,这个FTL的意思变了:

  • 老:Fourth Tier LLVM = DFG++(高层优化) + LLVM(底层优化)
  • 新:Faster Than Light = DFG++(高层优化) + B3(底层优化)

…名字的更新当然是故意的。以前就有人开玩笑说FTL是faster than light,现在把LLVM扔掉了这名字就干脆也改过来了。

可以看到其实这个新的B3后端并不是把整个FTL JIT给重写了,而只是替换掉了其中负责底层优化的LLVM部分而已。


不得不说Filip大大带的JavaScriptCore团队还挺厉害的。去年10月开始写这个新的名为“B3”的后端,过了4个月就已经上了正轨了。

B3本质上是对LLVM的剪裁与特化,针对JavaScriptCore的需求而生。它与LLVM在相似的抽象层,B3 IR的构造API也与LLVM IR相似,这样FTL::Output就可以方便的从原本构造LLVM IR切换到构造B3 IR。换句话说,扔掉的是LLVM“臃肿”的实现,而保留下的是LLVM的优化的精髓。当然“臃肿”是相对FTL的应用场景而言的(?)。

这个B3后端最最核心的关注点就是减少内存开销。这再次展示了编译器中IR在内存里的布局对编译速度的影响——但主要关注AOT编译场景的编译器(例如GCC和LLVM)大多不够注重这一点。在FTL JIT里,LLVM编译出来的代码质量虽然不错,但是编译速度在许多运行时间不够长的场景里就显得太慢了,用户程序都跑完了LLVM还没编译完。

以前跟JRockit的JIT编译器开发聊天的时候,他们也提到JRockit的JIT编译器IR有过一次重要的更新,大幅减少了内存开销而并没有做什么别的特别优化,光是这样就让编译速度提升了50%而编译出来的代码质量与以前一样。JRockit减少编译器IR的内存开销主要是通过几种思路:

  • 减少数据结构中指针的使用,改为用更紧凑的整数ID/下标来表示引用关系;
  • 尽量用固定大小的结构表示常用信息,而把可变长并且不常用的信息挪到外部;
  • 尽量把数据紧凑的放在数组/vector里。

无独有偶,B3的思路和JRockit非常非常相似。这也是比较新的、干净的IR设计流行的做法。

B3为了减少内存开销,还把LLVM IR中的use-def/def-use双向链接中的def-use信息抛弃了。一个def无法直接找到自己的所有use,就无法直接完成“Replace All Uses With”操作;但要替换IR指令还是可以通过遍历一次IR图来做到。

不在IR里记录def-use信息是的有趣的取舍;HotSpot Client Compiler(C1)与Maxine C1X也采用了类似的设计,只为了SSA而记录use-def信息,但不记录def-use信息。这个设计在C1里却造成了一些很悲催的坑:C1 HIR的指令替换只能一条对一条的做——一条HIR指令可以被另外一条HIR指令所替代,但是一条新的指令却无法替代多条老的指令,因为不遍历一次整个IR就无法知道老指令有没有被其它地方用到(同时C1也无法让多条新指令替代一条老指令,不过那是另一个问题)。这设计导致C1某些优化被“永久禁用”,导致C1X的DiamondEliminator如此难看、而SCCPropagator实现起来太麻烦以致没人去实现它。

但C1 / C1X的HIR还是用单向链表连在一起的,而B3 IR是用数组(vector)为容器,如果要实现批量插入新IR节点那肯定得仔细处理才能让效率高。B3的解决方案是在一个pass里把需要对IR做的修改放进InsertionSet里,等到下次遍历整个IR图时再去批量插入IR。这对JavaScriptCore倒不是什么新概念——DFG自己其实早就有InsertionSet了。可见Filip大大还是不禁觉得自己的设计比LLVM好哇。

B3 IR里还藏了些原本LLVM没有但DFG已经有的私货,例如UpsilonValue。这是DFG所采用的特殊的SSA形式的组成部分,既然要扔掉LLVM,就干脆把B3的SSA形式也向DFG靠拢。


B3 IR里一些复合控制流的IR指令设计也是个有趣的点。用Graal的术语来说,这就是在IR层面上用更显式的方式来表达Guard。在IR层面上保留Guard的语义,而不急于将其lower到普通的if + deoptimization,对干净的实现某些优化(例如guard的提升与合并)还挺重要的。


B3目前采用的寄存器分配器是一个graph coloring算法的变种,叫做Iterated Register Coalescing(IRC)。LLVM默认的寄存器分配器则是一种linear scan算法的比较复杂的变种,叫做Greedy。Greedy虽然比原始的linear scan慢一些,但是总体来说还是比graph coloring系的算法快而效果相近。所以Filip大大也说会一边继续优化B3基于IRC的寄存器分配器,一边考虑把Greedy分配算法移植到JavaScriptCore里

LLVM的Greedy寄存器分配器近来还挺流行的。V8 TurboFan尝试过移植它(虽然目前被撤销了,但以后或许还会再放进来),IonMonkey实现了它。Hmm。


====================================================

同事Philip Reames也发表了对B3的看法:Quick thoughts on WebKit’s B3

显然,作为我们这边的重度LLVM用户和开发,Philip大大对B3颇有微词 >_<

====================================================

题图跟本文的主题有什么关系,大家知道不?^_^

(题图引用自戦闘妖精雪風,(c)2002 神林長平・早川書房/バンダイビジュアル・ビクターエンタテインメント・GONZO)

13 条评论