js源码字节码机器码的执行过程

前言

源码:高级语言
字节码:中间代码
机器码:最终执行的代码

在这里插入图片描述
基于栈和基于寄存器,这是两种动态语言的执行方式,对于js而言是基于栈的,对于lua而言它是基于寄存器的,虽然我们在lua源码中也可以看到栈的踪迹,但源码的绝大部分的操作都是基于寄存器的,这里只是提一下,不做深究。
基于栈啥意思?比如说有一个代码,c = a+b;那就是a入栈,c入栈,操作符+入栈,执行加法,最后结果出栈,对于js而言他有很多操作符,加减乘除循环体函数体等,在执行他们的时候,都是入栈出栈
LdaSmi [1]
LdaSmi [1] 将常量 1 加载到累加器中
在这里插入图片描述
Star r0
接下来,Star r0 将当前在累加器中的值 1 存储在寄存器 r0 中
在这里插入图片描述
LdaNamedProperty a0, [0], [4]
LdaNamedProperty 将 a0 的命名属性加载到累加器中。ai 指向 incrementX() 的第 i 个参数。在这个例子中,我们在 a0 上查找一个命名属性,这是 incrementX() 的第一个参数。该属性名由常量 0 确定。LdaNamedProperty 使用 0 在单独的表中查找名称:

  • length: 1
    0: 0x2ddf8db91611 <String[1]: x>
    可以看到,0 映射到了 x。因此这行字节码的意思是加载 obj.x。

那么值为 4 的操作数是干什么的呢? 它是函数 incrementX() 的反馈向量的索引。反馈向量包含用于性能优化的 runtime 信息。

现在寄存器看起来是这样的:
在这里插入图片描述
Add r0, [6]
最后一条指令将 r0 加到累加器,结果是 43。 6 是反馈向量的另一个索引
在这里插入图片描述
Return
Return 返回累加器中的值。返回语句是函数 incrementX() 的结束。此时 incrementX() 的调用者可以在累加器中获得值 43,并可以进一步处理此值。

乍一看,V8 的字节码看起来非常奇怪,特别是当我们打印出所有的额外信息。但是一旦你知道 Ignition 是一个带有累加器寄存器的寄存器,你就可以分析出大多数字节码都干了什么
到此为止这些代码你是不是都能看懂?没错,你应该可以看懂字节码,字节码的描述就是操作符和操作数,操作数是存放在寄存器里的,一旦执行虚拟机,入栈,和操作符发生左右就会产生运算,最终将结果出栈,放到指定寄存器里
那么问题来了?字节码终归是字节码,他能描述运算逻辑,能预先算出寄存器的偏移,有的时候,看字节码,我们会有点闷逼,就是寄存器的地址偏移问题,不要问为什么偏移这么多,这些偏移,都是虚拟机生成字节码预先计算好的,寄存器的地址比较简单,0x00000000-0x11111111,我们的字节码都是一条条指令,字节码描述了整个代码的逻辑运算,所以字节码的每一条语句还要转成机器码

内容

谷歌浏览器内置v8引擎,v8引擎内置js虚拟机,js虚拟机负责对js语言进行解析和执行,如果是苹果浏览器的话,那么它内置的jsc引擎。
下图有两种方式执行源码,左图是编译器主要针对的是c语言和c++语言,他们是静态语言,一次编译直接生成机器码,后边直接执行即可,所以代码执行效率特别高,但跨平台比较差:右图是解释器主要针对的脚本语言,如js,python等,他们是边解释边执行,效率上比较低,但跨平台性比较强,只要内置js引擎,都可以执行
在这里插入图片描述
js虚拟机的执行过程如下:
在这里插入图片描述
解析器(parser):内置词法分析和语法分析
词法分析(lexical analysis):主要是将字符流(char stream) 转换成标记流(token stream),字符流就是我们一行一行的代码,token是指语法上不能再分的、最小的单个字符或者字符串
在这里插入图片描述


var name = "ivweb"
//转成token后为

[
    {
"type": "Keyword",
"value": "var"
    },
    {
"type": "Identifier",
"value": "name"
    },
    {
"type": "Punctuator",
"value": "="
    },
    {
"type": "String",
"value": "\"ivweb\""
    },
    {
"type": "Punctuator",
"value": ";"
    }
]

语法分析:将前面生成的token流根据语法规则,形成一个有元素层级嵌套的语法规则树,这个树就是AST。在此过程中,如果源代码不符合语法规则,则会终止,并抛出“语法错误”
在这里插入图片描述
解释器(Ignition)生成字节码:
在这里插入图片描述
字节码是机器码的抽象,可以看作是小型的构建块,这些构建块组合到一起构成任何JavaScript功能。字节码比机器码占用更小的内存,这也是为什么V8使用字节码的一个很重要的原因。字节码不能够直接在处理器上运行,需要通过解释器将其转换为机器码后才能执行
在这里插入图片描述
通过上图可以看出,Ignition把前一步得到的AST通过字节码生成器经过一些列的优化生成字节码。
在这个过程中:

Register Optimizer: 主要是避免寄存器不必要的加载和存储;

Peephole Optimizer: 寻找直接码中可以复用的部分,并进行合并;

Dead-code Elimination: 删除无用的代码,减少字节码的大小

通过上面三个过程的优化进一步减小字节码的大小并提高性能,最后Ignition执行优化后的字节码

执行代码及优化
在这里插入图片描述
Ignition执行上一步生成的字节码,并记录代码运行的次数等信息,如果同一段代码执行了很多次,就会被标记为 “HotSpot”(热点代码),然后把这段代码发送给 编译器TurboFan,然后TurboFan把它编译为更高效的机器码储存起来,等到下次再执行到这段代码时,就会用现在的机器码替换原来的字节码进行执行,这样大大提升了代码的执行效率。
另外,当TurboFan判断一段代码不再为热点代码的时候,会执行去优化的过程,把优化的机器码丢掉,然后执行过程回到Ignition

小结:源码是高级语言的语法,我们程序员可以看懂,但是机器看不懂,所以需要转换成机器码,js虚拟机的作用就是将源码转成机器码,它首先将源码通过解析器的词法分析器生成一个个标记流,这是不能再分割的最小单词,然后再通过解析器的语法分析器拿着上一步的一个一个小单词生成一棵语法树AST,如果我们的语法有问题,到这一步就会抛出错误了,到此为止解析器的工作就结束了,下面开始解释器的工作了,解释器的主要工作就是把这棵语法树变成字节码,我们说的一边解释边执行就是边解释字节码为机器码再执行,当然这里还会有一些优化,就是将一些经常执行的字节码设定为热点代码,就是开辟一块内存来存储这些热点字节码生成的机器码,下一次执行的时候就直接执行了,字节码所占内存相对于机器码要小很多,这也就是为啥不一次性都转成机器码的原因所在!!!我们一般写完代码,js会有一个加载的过程,对于加载来的源码会一次性将这些源码翻译成字节码,然后再边解释成机器码边执行,所以对于一些预先将源码解析成字节码的文件,可以加快代码的加载速度,但并不能加快执行速度也就是解释速度,字节码的设计看最上面的那张图就能够明显的感觉到,字节码就是一些模仿CPU物理执行或者机器码执行的逻辑,无非就是一些寄存器的累加,这样在翻译的时候也会加快翻译速度

  • 1
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值