图的结构

图就是由边(edge)连接的节点(vertax),任何一个二分的关系都可以通过图来表示。
编译流程:中间代码
IR,也就是中间代码(Intermediate Representation,有时也称 Intermediate Code,IC)
从概念层面看,IR 可以分为 HIR(Higher IR)、MIR(Middle IR)和 LIR(Lower IR),这几种高、中、低的中间代码的形式。

前端主要做的是词法、语法和语义的分析
中端会做相关的优化工作
最后后端的部分就生成了目标代码

  • 重点的任务是寄存器分配和指令的选择和排序。

中端:

高:HIR 主要用于基于源语言做的一些分析和变换,比较典型的例子就是用于语义分析的的 AST(Abstract Syntax Parser)语法树。
中: MIR 则是独立于源语言和 CPU 架构来做一些分析和优化,比较典型的例子就是三地址代码 TAC(Three Address Code)和程序依赖图 PDG(Program Dependency Graph),它们主要用于分析和优化算法的部分
低:LIR 这一层则是依赖于 CPU 架构做优化和代码生成,其中比较典型的例子就是有向无环图 DAG(Directed Ayclic Graph),它的主要目的是帮助生成目标代码。

在编译流程中分析的这一段里,有一个重要的分析就是控制流分析(CFA,Control Flow Analysis)和数据流分析(DFA, Data Flow Analysis)
节点之海(SoN,Sea of Node)
在编译流程中优化的这一段里,也有几个重要的概念,分别是通用优化、对象优化和函数式优化。比如循环优化就属于通用的优化;内联和逃逸属于关于对象的优化。对于函数式编程而言,比较重要的就是之前,我们在说到迭代和递归的时候,提到过的循环和尾递归的优化。

中端优化:分析和优化通过

IR 的结构你可能可以看出,“中间代表”可能是比“中间代码”更准确的一种描述。因为 IR 更多的是以一种“数据结构”的形式表现,也就是我们说的线性列表、树、图这些数据结构,而不是“代码”。

TurboFan 用到的节点之海 (SoN,Sea of Node)
SoN 结构:格式和表达
整个的 SoN 只有节点和边构成。除了值以外,所有的运算也都是用节点来表示的。在 TurboFan 的可视化工具 Turbolizer 中,使用了不同颜色来区分不同类型的节点。

SoN 的格式:静态单赋值
SoN 是符合静态单赋值(SSA,static single assignment)格式的。
静态单赋值:每个变量只有唯一的赋值。
比如下面的例子里,第一个操作是 x 等于 3 乘以 7,之后我们给 x 加 3。这时,x 变量就出现了两次。所以 TurboFan 在创建图的时候会给本地变量做重命名,在改完之后就是 3 这个值的节点同时有两个操作符指向它,一个是乘以 7 的结果,一个是结果再加上 3。在 SoN 中没有本地变量的概念,所以变量被节点取代了。

数据流表达
在数据流中,节点用来表达一切运算,包括常量、参数、数学运算、加载、存储、调用等等。
关于数据流的边,有两个核心概念,一是实线边表达了数据流的方向,这个决定了输入和输出的关系,下面实线表达的就是运算输出的结果。二是虚线边影响了数据流中的相关数据的读写状态,下面虚线边表示运算读写状态的指定。
控制流表达
从数据流的角度来看,节点和边表达了一切。而在控制流中,这种说法也是同样成立的。
控制中的节点包括开始、分支、循环、合并以及结束等等,都是通过节点来表示。
在 Turbolizer 的可视化图中,用黄色节点代表了控制流的节点。下面是几个单纯看数据流的例子,我们可以看到从最简单的只包含开始结束的直线程序,到分支到循环都可以通过节点之海来表达。

为了区分不同类型的边,可以通过黑色实线、灰色实线和虚线边分别代表控制流、数据流和影响关系。

再加上合并,可以看到一个更完整的例子。这里你会看到一个 phi 指令,这个指令是做什么的呢?它会根据分支判断后的实际情况来确定 x 的值。因为我们说过,在 SSA(静态单赋值)的规则中,变量只能赋值一次,那么我们在遇到分支后的合并,就必须用到 phi。

分析:变量类型和范围
中间代码的重要作用是分析和优化,为什么要做变量类型和范围的分析呢?这里主要要解决三个问题。第一,我们说 JavaScript 是动态而不是静态类型的,所以虽然值有固定的数据类型,可变量是没有固定的数据类型的。
举个例子,我们可以说 var a = 25,25 本身是不变的数字类型,但是如果我们说 a + “years old”, 那么变量 a 的类型就变成了字符串。第二,所有的数学运算都是基于 64 比特的,然而在实际的应用中,大多数的程序其实用不了这么多比特,大多都小于 32 比特,并且这里还有很多如 NaN、Infinity、-Infinity、-0.0 等特殊的值。第三,是为了支持如 asm.js 这样的 JS 子集中的注释,以及将上一点提到的如 NaN、Infinity 这些特殊值截断成 0 整数这样的需求。

极客时间《Jvascript进阶实战课》学习笔记 Day18

豪猪
4 声望4 粉丝

undefined