V8 解析执行 JavaScript 流程简述
上图是自 17 年 V8 v5.9 (opens new window) 发布起至今 V8 的工作流程。相较于旧的架构,V8 引入了 Ignition
和 TurboFan
两个模块,前者是解析器,后者是优化编译器,他们是用来取代原来 Full-codegen 和 Crankshaft(这两者你可以通过 V8 的文章了解到) 的角色,以提供编译速度更快内存占用更小的运行时支持。
现时,V8 对 JavaScript 的解析执行流程如下。
# 词法分析与语法分析
词法分析与语法分析是编译原理中最基础的两个概念。
其中,词法分析用于将源代码的文本逐一分割开(即所谓 Tokenization),每一个字符各自被称为一个 token。语法分析(有的称为 Lexing,有的称为 Parsing)是在词法分析之上的,在词法分析后检查程序的语义是否有误,当语法有误时抛出异常。当 V8 的 Parser 将源代码经过这两步处理后,便输出抽象语法树(AST)。
# 抽象语法树
AST 是一种将源代码语法结构抽象描述的树状结构。你可以在 AST Explorer (opens new window) 中对 AST 进行探索,此处不深究。你只需要知道 V8 的 Parser 输出的就是 AST。
# Ignition
Ignition 负责将 AST 转换为字节码 Bytecode,解析并执行字节码,同时手机 TurboFan 优化编译所需要的信息,比如函数参数的类型等等。
假设现在有如下一段代码:
function add(x, y) {
return x + y
}
add(1, 2)
add(1, 2)
2
3
4
5
如果我们只是声明了函数 add,而没有调用,那就没有 Ignition 什么事了。如果声明后进行第一次调用 add(1, 2)
,Ignition 会将它编译成字节码并执行,但 Ignition 此时不认为 add 函数是一个热点函数,因此不需要优化。而如果 add 被多次调用,则 Ignition 会将其标记为热点函数,同时收集 add 函数的各种参数类型,为 TurboFan 的优化提供支持。TurboFan 会将字节码编译成优化后的机器码,已最大地提高代码的执行性能。
# TurboFan
正如前面所说,TurboFan 用来将字节码转换为优化的机器码。但通过文章开头的图上可以看到一个由 TurboFan 指向 Ignition 的 Deoptimize
箭头,说明优化后的机器码还可能会反过来让 Ignition 重新优化字节码,这个逆向的箭头怎么解释呢?
我们将前面的例子稍作改动:
function add(x, y) {
return x + y
}
add(1, 2)
add(1, 2)
add('1', '2')
2
3
4
5
6
最后一次调用 add 函数时传递的是两个字符串。这时,Ignition 原来收集的信息便可能出现错误,TurboFan 需要告诉 Ignition,“兄弟,你上次告诉我的信息不对哦”,然后 Ignition 根据反馈再生成字节码,这时如果 add 还是热点函数,TurboFan 再生成二次优化的机器码。
至此,V8 的整个解析执行 JavaScript 的流程已经讲完,想了解更多深入的知识及具体某步骤的细枝末节,建议阅读文末推荐文章。