目录

第一章 整体流程

第二章 全局环境配置及初始化
2.1 全局模板
2.2 库函数
2.3 初始化

第三章 前端建立语法树
3.1 v8 编译中重要的类
3.2 compile 之前的查找
3.3 建立语法树

第四章 后端全代码生成(full codegenerator)

第五章 后端优化代码生成(crankshaft)
5.1 调用 crankshaft 的条件
5.2 Hydrogen
5.3 Lithium
5.4 寄存器分配

第六章 运行时监听(runtime profile)

第七章 LazyCompiler

第八章 inline cache
8.1 前提条件及实现方式
8.2 stubs 函数

第九章 性能分析(各流程所占时间比)

目录完

第一章 v8 之整体流程

  1. v8 在进入 main 函数之后首先做的就是参数分析,根据参数设 置 Flags。
  2. 然后创建一个基于栈分配的 HandleScope(在这里须得说明 在 v8 中任何一个对象都是需要 handle 来指向的,如果没有则这个对象 将很快被垃圾回收器回收掉。对象的释放意味做 handle 将没有用,因 此在每一个 v8 逻辑层次中都有一个 HandleScope 来管理该层次中的 所有 handle。释放一个scope, 则这个scope 中的所以 handle 就被释放掉了)。
  3. 创建一个新的执行环境 (即为 v8 执行 js 的环境,在创建该全 局执行环境的同时,创建全局的对象模板,函数模板,编译 built_in function(详细情况请见第二章 v8 之环境配置及初始化)。进入该新建 的全局执行环境中(所谓进入该环境变量就是设置当前 isolate中执行 的环境变量)。
  4. 然后就是编译执行*.js 用户代码:
    1. 首先进行语法分析,建立语法树。
    2. 首次执行采用fullCodegenerator编译最外层框架代码,并开始执行。
    3. 运行遇到还没有编译的 function 代码采用RuntimeLazyCompile,并用 inline cache 技术将其他同名的对 象指向该编译的代码。
    4. 若在运行到该同名对象时发生 CacheMiss(调用不匹配 ),则 采用 ICMissLazyCompile 对调用的对象或函数进行编译 (当然 这里还有其他 IC 处理情况出现,比如 ICCompare 等等 stubs function 的处理)。
    5. 在整个代码运行处理的过程中, v8 还会创建一个监听线程 (profile thread),该线程监听 function 的运行情况,收集类型信息,记录运行次数,记录 function 编译后的代码量等等,判断 该 function 是否为 hot function。(运行次数> 2, 类型信息收集 比例>15%,代码量是否有< 5*144,是否发生 IC_changed),达到要求后标记该函数为可优化,下次运行的时候采用RuntimeLazyRecompile,利用 Crankshaft优化代码,并替换原来的代码。同时还有监听优化过的代码判断其是否优化过头, 是否需要deoptimal。
    6. 在产生的优化代码中常常还要用到 on-stack replacement(该技术为不中断程序继续进行的情况下,进行代码替换)。
    7. 在Crankshaft生成优化代码的过程中,需要先建立语法树,进 行一次全代码生成,模拟该代码运行,判断分析哪些数据分配 到栈上,哪些变量分配到堆上,变量类型绑定等等,利用同一 个语法树生成静态单赋值表示的中间表示代码 Hydrogen(构建 图),同时将 function 可能调用的代码进行 inline(有充足类型信 息的被调函数),然后优化图(循环不变量外移,公共子表达式 消除)。 遍历图生成三地址形式的中间表示 Lithium ,进行寄存器分配,最后生成本地代码。
  1. 在整个过程中垃圾回收器都紧紧的追踪 v8 中所有的对象,参看其是否 still alive, 将其分类进行处理。在进行垃圾回收的时候要中 断程序的进行,每次只处理要回收的一部分。

第二章 全局环境配置及初始化

2.1 全局模板

v8 在运行时创建对象,函数都需要调用具体的模板方法来实现。 

在 v8 中有 2 个非常重要和常用的模板,分别是 objectTemplate 和 functionTemplate。 objectTemplate用来在运行时创建对象,向一个objectTemplate中添加 properties,就是向所有以该对象模板创建的对象中添加这些属性。继承自Template(继承自data)。Frend class functionTemplate。

functionTemplate用来在运行时创建函数,在一个 context 中一个 functionTemplate 只能创建一个函数,该函数的生存周期和 context 的生存周期相同。每个函数模板可以有属性,并且这些属性会在创建函 数的时候添加到该函数中。每个函数模板都有一个对应的实例模板, 用来以该函数为构造器创建对象实例。每个函数模板还有一个prototype 模板,用来创建函数的原型对象。functionTemplate的具体使用请参见 include/v8.h line 2109。另外functionTemplate还可以继承自另外一个函数模板,子函数模板继承父模板的属性,并且通过 proto可以访问父模板的原型对象。functionTemplate 继承自Template,friend class ObjectTemplate。

2.2 库函数

在v8中库函数时用javaScript 实现的,这样使得库函数的更新, 修改更加方便。 runtime.js 中,若调用这些库函数将会采 用 lazyCompile 的方式进行编译。

2.3 初始化

v8 的初始化代码也是使用initV8.js实现的,在apinatives.js 中调 用Instantiates函数,对函数、模板、模板实例进行初始化工作。
这些 js 代码将会在isolate->bootstrpper()->CreateEnvironmen(t ...)中的Gensis函数中进行编译成本地代码。

这些初始化的作用就是实现函数模板, 对象模板。
当然在初始化的过程中会调用到库函数,调用时将采 用 lazycompile 的方式进行编译。

初始化的作用其实是构造一个运行环境,在这个环境中一切javascript 所需的基本框架将会得到建立,包括库函数, js的模板,对象等。而这些 *.js处理的方式和整个v8对待用户的js程序是基本一致的,这些built_in.js 函数都通过 Genesis::ConfigureApiObject 再调用 Execution::Call 函数实现编译执行的。


第三章 v8之建立语法树

3.1 v8编译中重要的类

CompilerInfo: 封装一些在编译时间知道的信息,根据compiler-time时有的资源进行构造,需要用到ScriptDataImpl中的信息。

CompilerInfoWithZone: 和CompilerInfo类似,只是在构造的时候需要申请一块zone,并且进入该zone,在改累的对象exit的时候会释放这块zone。Zone的作用就是快速的进行小块的内存分配。Zone用来保存临时的数据结构,比如说抽象语法树,抽象语法树会在编译结束后被释放掉。

OptimizingCompiler: 在chrankshaft优化编译的三个阶段跟踪保存 状态(三个阶段分别是:建图,图优化,code 生成和 install)。

Compiler: v8的编译类,编译的基本策略就是将sourc code编译成匿名函数,给予参数就可被执行。如果这个source code包含其他函数(调用其他函数), 这些函数将会被编译,并且分配到该 source code中(作为其的一部分)。

CompilationCache: 保存编译过的script和evals的shared function infos。这些共享的函数信息通过source string作为关键来查找。

3.2 compile之前的查找

Compile cache包含几层sub-caches,每个子 cache 针对每代sub-cache包含一个对应的compilation cache tables。因为对scripts和evals同样的source code string将会产生不同的编译代码,v8应用不同的sub-caches来保存不同编译modes产生的代码,来防止检索到错误结果。

这些编译结果子cache,都继承自CompilationSubCache。CompileCacheScript是专门保存 scripts编译结果的子cache。CompileCacheEval是专门保存eval script编译结果的子cache。 CompileCacheRegEx是专门保存正则表达式编译结果的子cache。

v8 在对任一source code进行编译生成语法树之前,先要查找相应的compile cache,看该源代码是否已经编译过了,如果已经编译过了,可能就不需要再次编译了,直接导入就可以了。

在源代码执行到Compile::Compile(...)之时,首先通 过 compilation_cache-> lookupScript查看当前的源代码是否已经编译过,存放在cache中了,如果有,则直接导出就可。如果没有则要编译,且编译结束后要把结果存放在想对应的cache中。

3.3 建立语法树

在查找无结果之后,v8将对源代码进行分析,建立语法树。

v8在进行一系列环境配置之后(Scope,zone等配置),通过扫描器scanner,将源代码继续分析归类。分为语句(statement),和函数声明(functionDeclaration),在harmony mode下还允许有LetDeclaration, ConstDeclaration, ModuleDeclaration,ImportDeclartion,ExportDeclaration等模块元素。

Parser::ParseStatement(...)是语法分析的入口,在该函数里进行实质的语法分析。它是一个分拣器,分析出每个js语句的类型,然后针对不同的类型再进一步调用相关的分析函数。具体的流程为:首先分离出语句的关键字,因为在scanner处理后每个语句都会有相应的关键字指代,有很清楚的含义。比如在 js中声明变量var value;statement分析器将会将该变量声明语句传给Block* ParseVariableStatemen(t)方法进行分析,并将结果放入zone中, 并插入到AST树中的相应位置。

v8在语法分析时将语句分为

block
变量语句
空语句
表达式语句
if语句
Iteration语句
continue语句
break语句
return语句
with语句,
labelled语句 (这里须得注意, labels 只会在break,continue 语句中被使用,而这两种语句只有blocks,iteration,和switch语句中才合法。因此在其他语句中的labels可以简单的忽略掉。)
switch语句
throw语句
try语句
debugger语句

每一种类型的statement的处理方式基本类似,都是按照Ecma262第5版的标准进行分析的。进行匹配类似的分析,在递归的调用相应的处理方法。
在建立语法树中我们可以看到每个js函数的语法树都是显示函数名,及其引用明,然后便是函数中的变量声明,接着是boby模块,在boby模块中是call,赋值,初始化等等操作。

可以使用--print_ast打印所以的语法树。使用--trace_parse查看分析语法树建立的过程和花销的时间 。