v8字节码

Author Avatar
Peipei Wong 9月 21, 2020
  • 在其它设备中阅读本文章

对于字节码,之前有过听过,但是没有深入的了解。

高级语言中,cpp是直接转换成二进制,java作为一个跨平台的语言,其中就有字节码的概念(点这里),对于js,都知道的V8引擎,它是怎么工作的呢?

请先阅读参考文章:

image

文章中最重要的是这张图片了,v8会先将javascript翻译成字节码,再将字节码翻译成机器语言,如何查看生成的字节码呢?使用node --print-bytecode test.js,只要再原来的基础上加上–print-bytecode就可以了。

让我们写一个for循环

function for_loop(){
  for (let i = 0; i < 10; i++) {
    console.log(i)
  }
}

for_loop()

使用上面的命令,会打印出来很多很多东西,v8不仅仅会翻译你的代码,其中也会有一些自己包含的东西在里面,例如console,可以使用--print-bytecode-filter来过滤一下,你会得到

[generated bytecode for function: for_loop]
Parameter count 1
Frame size 24
   17 E> 0x1897b5c16f02 @    0 : a0                StackCheck
   36 S> 0x1897b5c16f03 @    1 : 0b                LdaZero
         0x1897b5c16f04 @    2 : 26 fb             Star r0
   41 S> 0x1897b5c16f06 @    4 : 0c 0a             LdaSmi [10]
   41 E> 0x1897b5c16f08 @    6 : 66 fb 00          TestLessThan r0, [0]
         0x1897b5c16f0b @    9 : 94 1c             JumpIfFalse [28] (0x1897b5c16f27 @ 37)
   23 E> 0x1897b5c16f0d @   11 : a0                StackCheck
   58 S> 0x1897b5c16f0e @   12 : 13 00 01          LdaGlobal [0], [1]
         0x1897b5c16f11 @   15 : 26 f9             Star r2
   66 E> 0x1897b5c16f13 @   17 : 28 f9 01 03       LdaNamedProperty r2, [1], [3]
         0x1897b5c16f17 @   21 : 26 fa             Star r1
   66 E> 0x1897b5c16f19 @   23 : 57 fa f9 fb 05    CallProperty1 r1, r2, r0, [5]
   48 S> 0x1897b5c16f1e @   28 : 25 fb             Ldar r0
         0x1897b5c16f20 @   30 : 4a 07             Inc [7]
         0x1897b5c16f22 @   32 : 26 fb             Star r0
         0x1897b5c16f24 @   34 : 85 1e 00          JumpLoop [30], [0] (0x1897b5c16f06 @ 4)
         0x1897b5c16f27 @   37 : 0d                LdaUndefined
   77 S> 0x1897b5c16f28 @   38 : a4                Return

根据参考文章中的带a的单词,一般是用来检查累加器,那么可以尝试理解一下:

StackCheck // 检查堆栈
LdaZero // 累加器置0,acc = 0
Star r0 // 将累加器的值赋给r0,r0 = 0
LdaSmi [10] // 累加器置为常数10,acc = 10
TestLessThan r0, [0] // 比较r0和acc
JumpIfFalse [28] (0x1897b5c16f27 @ 37) // 为false,根据后面的地址会跳到LdaUndefined这一行
StackCheck // 检查堆栈
LdaGlobal [0], [1] // 累计器置为全局变量console
Star r2 // 将累加器的值赋给r2, r2 = console
LdaNamedProperty r2, [1], [3] // 读取r2的属性赋值acc,本来理解的是后面的1、3指的是.log,log,但是我试了其他的字节码,这个指针会变,所以这一块我也不是奔清楚
Star r1 // r1 = acc, 也就是console.log
CallProperty1 r1, r2, r0, [5] // 这是调用输出,5不清楚代表的什么
Ldar r0 // acc = r0,
Inc [7] // acc++
Star r0 // r0 = acc
JumpLoop [30], [0] (0x1897b5c16f06 @ 4) // 循环,跳到LdaSmi [10]这一行
LdaUndefined // 累加器置为undefined
Return // 返回累加器中的值

在理解产生的字节码的时候,比较迷惑的是[数字], 不知道代表什么意思,LdaSmi [10]这个是参考文章中有,LdaNamedProperty r2, [1], [3]这个文章中也有,1和3代表索引,但是再尝试理解另一个函数的字节码时,也是console.log,但是索引就变了==

刚开始有一个错误理解,每种语言的字节码应该会大同小异,为此,我还找了一些java的字节码看,但是对比下来,用处不大。还是看不懂,一顿搜索,终于找到了一篇好文(下面参考文章的第一个),那个文章是为了对比let和var的效能问题,总算有了眉目。

借用维基中的一句话:「理解字节码以及理解Java编译器如何生成Java字节码与学习汇编知识对于C/C++程序员有一样的意义。」

参考文章: