前言
本文是V8引擎详解系列的第四篇,重点内容是关于V8是如何解释执行字节码的,关于字节码的执行在V8中所处的环节可以先看一下作者之前的V8引擎详解(三)——从字节码看V8的演变,本文会通过描述一个官方的例子来帮助大家理解,文末会有已经完成的系列文章的链接,本系列文章还在不断更新欢迎持续关注。
字节码的执行
简单来说V8引擎通常会经过一下流程:源码 --> AST --> 字节码 --> 二进制代码,源码通过 Parse 转成AST的过程已经在系列V8引擎详解(二)——AST 中描述过了,接下来会通过v8 BytecodeGenerator类根据抽象语法树将AST结构转换成字节码的结构,字节码是机器码的抽象,字节码的相关内容可以看一下从字节码看V8的演变, 而Ignition引擎对生成的字节码进行解释执行。
一、基于寄存器
Ignition引擎可以对字节码进行解释执行,那就是说他的功能类似于Java的JVM,本质上就是一个虚拟机。 虚拟机通常有两种分别是基于Stack(栈)的和基于Register(寄存器)的, 比如基于Stack的虚拟机有JVM,是一种比较广泛的实现方法,而我们V8引擎中的Ignition是基于Register的,也就是基于寄存器的虚拟机,通常基于Register的虚拟机比基于Stack的虚拟机执行的更快,但是指令相对较长。
Ignition是一个带有累加器(accumulator)的寄存器,我举一个小例子大家就明白了。 我们先抛开字节码看这段简单的代码如何计算出结果。
var x = 100;
var y = x +10;
x = x * y;
运用累加寄存器的大致运行流程如下:
这种就是我们基于寄存器虚拟机运行的一个大致流程,简单来说就是创建一块虚拟空间来保存参数、中间计算结果。
二、通过官方案例解读字节码
接下来会通过Google官方PPT上面的一段案例来解读V8上字节码的执行。我会逐图配上一些文字注解帮助大家阅读。
-
先将源码转换成字节码
-
进行函数 f 的初始化工作
-
将小整数 -100 存储到累加器中,LdaSmi 可以理解为一个定义好的handle函数 后面接的 #100 就是这个函数的参数
-
将a2中存储的150和累加器中的值求和,并将结果存于累加器 。
-
将累加器中存储的50保存到寄存器r0中,此时r0的值为50。
-
将寄存器a1也就是参数b的值存储到累加器中,此时累加器值为2。(a0、a1、a2 也是寄存器)
-
将寄存器r0中的值和累加器中的值求乘积,并将结果存于累加器。
-
将寄存器a0中的值和累加器中的值求和,并将结果存于累加器。
-
Return (包括上面的语句)本身都是定义好的handle函数,Return代表的就是将累加器中的值返回。
(随着v8版本的更新,不同的版本生成的字节码结构可能有细微差别,但是这些定义好的函数一般不会有太大变化)
通过这个例子相信大家可以大概了解字节码的执行,但是v8定义的关键字可不止例子中这一点(完整的放在附录中了),在学习的过程中开始很多关键字也确实不好理解,不过我们可以通过自己写一些简单的js代码然后生成字节码,因为我们已知js的执行结果,可以通过倒推的方式来理解字节码。
总结
本文主要带大家简单了解了v8是如何解释执行字节码的。
有人问我,作为一个前端学习这些东西到底有什么用?这些东西还没那么容易理解,有这个时间不如多刷两道题、看看面试问题、看看框架。
从短期来看学习V8引擎并不能明显的提高面试的能力,但是万丈高楼平地起,想成为一个优秀的前端程序员v8引擎的学习能帮助你更加深刻的理解js中的一些事情,比如通过对AST的学习更加了解了babel的工作原理,为什么一个页面第二次加载比第一次快(不仅仅是静态资源的缓存哦),为什么node选择了v8作为js引擎,如果不能深入的了解一下,也只是在复述别人得出的结论而已。
我在写本章的时候,想起来我面试被问的一道问题switch为什么比if else快,当时回家查了一下相关文章发现有人说switch跳表巴拉巴拉的,我专门转成了字节码看了一下,发现起码在正常的v8环境中switch比if else快纯属扯淡,从字节码上发现,绝大部分条件下两个几乎都是一样快,如果有兴趣可以自己玩一下。
V8字节码表
参考文章
docs.google.com/presentatio…
time.geekbang.org/column/arti…
系列文章
V8引擎详解(一)——概述
V8引擎详解(二)——AST
V8引擎详解(三)——从字节码看V8的演变
V8引擎详解(四)——字节码是如何执行的
V8引擎详解(五)——内联缓存
V8引擎详解(六)——内存结构
V8引擎详解(七)——垃圾回收机制
V8引擎详解(八)——消息队列
V8引擎详解(九)——协程&生成器函数