高性能虚拟机解释器:DTC vs ITC(Indirect-Threaded Code)

时间:2021-10-13 14:00:31

最近重新看Google Chromium讲Ignition的一个ppt,最后提及JSC是Direct-threaded dispatch,而v8属于Indirect-threaded dispatch。


Contemporary JavaScript Engines
JavaScriptCore (Apple)
● Direct threaded (== bigger code and data, but fast).
● Register Machine.
● Custom assembler generating bytecode handlers in dispatch loop.
SpiderMonkey (Mozilla)
● Indirect threaded.
● Stack Machine.
● Interpreter implemented in C++ as either switch statement or goto table (depending on compiler).
Chakra (Microsoft)
● Register based bytecode and C++ based interpreter.
● Optimizing compiler can run concurrently with bytecode generation.


所谓解释器,根据解释执行的输入,有两种:AST与Bytecode。

但既然谈到虚拟机解释器,自然是有Bytecode的。

那么DTC和ITC又是指什么的?

根据一篇很老的论文,DTC指的是bytecode指令直接对应目标handler的跳转地址。而ITC则首先将这些handler的跳转地址存放到一个table中,间接跳转。(相当于CPU指令集间接寻址的概念,或者二级指针的概念),ITC的C语言实现依赖于一项被称为“computed goto”的GCC扩展特性。

可见对于ITC的概念来说,Bytecode并没有固定的二进制前缀编码。

还有一点比较特别的是,ITC每个bytecode的handler里面执行完毕后,不是像传统的解释器while(1){switch(code){...}}那样返回到while循环,而是直接Jump到下一个handler的入口地址。。。

由于每个handler的执行最后需要Jump到下一个handler的入口,这个Jump地址对于不同程序中的不同位置的bytecode而言,实际上都是不一样的。而传统的解释器里面,每个handler都是静态宿主语言(C/C++)写好的,可以重用。而ITC里面这些handler的代码实际上需要copy——如果不需要copy的话,那也只是因为输入bytecode program是固定的而已。


看了上面的描述,你可能感觉仍然有点迷糊,对,you should be。

对于v8的Ignition解释器引擎来说,假设一段程序输入是bytecode的数组,则每个handler的代码实际上是动态生成的(JIT,但是对于DTC/ITC的概念来说,并不要求handler是JIT生成的,C/C++静态实现的也可以),所以这个解释器的本质实际上是JIT,只不过它比正常的JIT性能差一点,差在什么地方呢?实际上主要还是在循环的执行上。其他地方不过是一个常数倍数而已。对于ITC来说,handler最后一句是Dispatch,这个Dispatch比较的神奇,它相当于跳转到IP++地址去执行。IP是指令地址寄存器,实际上对应于输入Bytecode program数组的下标索引。

ITC这种术语,可以让人想起QEMU。QEMU动态翻译目标CPU平台的二进制指令,然后逐个指令生成宿主平台的CPU指令执行。有个专门的属于称为“动态翻译”。假如把输入二进制指令当作Bytecode一样的话,QEMU实际上也是ITC模型。

至于Ignition提到JSC是Direct threaded (== bigger code and data, but fast). 这让人感觉很奇怪,实际上,JSC高性能解释器的原始术语称为LLint。(Low-level interpreter)

显然,LLint的bytecode handler也是JIT动态生成的,但它与v8 Ignition的区别是什么呢? 这里最主要的区别应该是,JSC LLint会把每个handler代码(JIT生成的)复制并拼接到一起,这样的话,就不需要ITC的handler最后的那个Dispatch。当然可以认为JSC DTC这种也是一种特殊的Dispatch。

未完待续。