V8 执行 JavaScript 的宏观过程
术语
V8::Isolate
一个 V8 的实例称为 V8::Isolate,每个实例中 JS Heap Memory 是隔离的
V8::Contexts
一个 V8 实例中有一个或多个 Context,这些称为 V8:Contexts,例如页面中的 iframe 就有自己的 Context,页面中 Chrome 扩展也有自己的 Context。在 DevTools 中可以看到当前 V8::Isolate 中有多少个 V8::Contexts
Pipeline
1. Loading
V8只是一个JS Engine,它不关心被执行的JS代码从哪来。在作为Chromium的一部分时,由Blink负责加载代码并将代码送到V8中执行。
2. Parsing
V8 的 Parsing 分为两部分:
Scanner
当代码被送入 V8 后,Scanner 负责将代码文本 tokenized,也就是编译架构中的 tokenizer,将程序文本切分为一个个 token。它接收字符流(stream of characters),返回token流(stream of token)。
Pre-parser & Full-Parser
V8 的 Parse 阶段是一个递归下降的解析过程,其中整个 Parse 阶段又分为两步:Pre-parser 与 Full-Parser,Pre-parser 阶段进行初步 Parse,主要用于初步解析与检查 JS 语法错误,它的结果将被 Full-Parser 阶段进一步解析。
整个 Parse 阶段接收 Scanner 返回的 token 流,返回AST。并不是一开始所有代码都会经历完整的 Parse 阶段,非 IIFE 或不在当前 Scope 内的将被标记为 Lazily Parsed,只有要执行时才会进行完整 Parse。
3. Interpreter
Parsing 过程结束后就到了 Interprete 阶段,这一过程由 V8 的 “Ignition” 完成,它是一个低级 register-based 解释器,接收AST,生成字节码流(stream of bytecodes)。
Ignition 生成的字节码处于比较“高级”的阶段,可以看作是 JS 的一个抽象,并非“低级”的machine bytecode,Ignition 的结果将作为 TurboFan 的输入。Ignition 并不是一个 AST 优化器或者 bytecode 优化器,而只是一个 bytecode 生成器,字节码优化过程将在后续流程中进行,Ignition 的主要目标是生成一个紧凑的,省内存的 AST 表达形式,也就是bytecode。
4. Execution
生成 bytecode 后到了 execution 阶段,这一步将执行前面生成的 bytecode。
JS 是一门非常动态的语言,同样的代码在不同的上下文中可能有不同含义。
例如a.b可能是访问对象a上的属性b,也可能是访问a上的getter b,也可能是原型链上的b,在执行过程过程中找到这个操作具体是什么含义是非常慢的过程。
一个常规优化手段是缓存本次访问结果,这样下次执行时如果访问的是同样类型的对象(shapes of object),就能使用缓存的内容,这个过程称为“inline caching”。
不过目前 V8 已经不再使用这样的常规手段进行对象类型访问缓存,这一过程现在是离线且数据驱动的,V8中有专门的地方存储这些缓存数据(shapes of object,aka “反馈向量”)。
5. Optimization
在执行过程中,有些代码可能会被多次执行,这些代码称为“热点代码”,此时就有必要优化它们,目前 TurboFan 是 V8 优化器,用于优化 Ignition 生成的 bytecode(优化过程需要之前的inline cache,或者说是shapes of object)。
TurboFan优化结束后会产生优化过的 bytecode,但它们不会被直接执行,而是送回 Execution 阶段执行,也就是说在 Execution 阶段遇到了热点代码,就将他们送到 TurboFan 中进行优化,优化完成后再送回 Execution 阶段来执行(整个过程都需要反馈向量的参与,因为 JS 是动态的,没有反馈向量无法确定不同上下文中同样的访问操作到底访问的是什么东西)。
Execution 过程中可能会需要反馈向量无法匹配的问题,例如访问a.b
200次的过程中前100次都是同样的上下文,但后100次变了,那么就需要抛弃掉前面的优化结果,重新进行优化。
GC
V8 的 GC 项目名为 “Orinoco”,它是 V8 的新一代 GC,用于检测所有可访问对象此时是否还存活着,即被使用。它是一个 Generational(new-space:新分配的 & old-space:长期活动的)、Increamental、Parallel、Concurrent(在 JS 执行过程中持续进行的,初特殊例子外不停止 JS 执行),同时它也与 Blink 的 GC(Oilpan) 紧密结合。
V8 的 GC 分为两部分:
- Minor GC:只在 new-space 中工作,用于将还活动着的对象移动到另一个space中,它的运行时间小于1ms
- Major GC:在所有 space 中工作,用于标记所有活动对象,更新引用,清除非活动区域(对象),运行Oilpan,它的运行时间小于10ms。
对象表达
V8 中 JS 对象的表达分为两类:
- inline:数字、字符串这类基础类型
- box:box是一个容器,表示除 inline 外的所有对象类型
WebAssembly
WASM 是不同于 JS 的静态低级可执行格式,因为与 JS 完全不同,所以它有独立的执行 Pipeline。
- 在执行前验证格式是否正确
- 使用 “LiftOff” 编译器对 WASM 进行编译
- TurboFan 对编译结果进行进一步优化