[心得]编译原理知识整理

时间:2021-08-22 13:28:24

前言

不学龙书的码农不是靠谱的码农。就冲这句话,我真的把编译原理给速推了一把。

干货

分析把源程序分解成多个组成要素,并在这些要素之上加上语法结构。
综合根据中间表示和符号表中的信息来构造目标程序。
把声明如何完成一个计算任务的语言称为强制式语言。

编译器最基本的数学模型是有穷状态机FSM和正则表达式。它们用来描述词法单位(关键字,标识符)以及被编译器用来识别这些单位的算法。此外,上下文无关文法用于描述程序设计语言的语法结构。

标识符是一个字符串。所有的标识符都是名字,但有些名字也可以是表达式,变量指向存储中的某个特定位置。
一个函数通常有一个返回值,而一个过程不返回任何值。方法在OO语言中和特定类相关联。

声明告诉我们事务的类型,而定义告诉我们它们的值。
语法描述该语言的正确形式。语义定义程序的含义。

词法单元由各名字和属性值组成,这些词法单元也称为终结符号。

一个文法描述了程序的层次结构。文法的定义使用了称为终结符号的基本符号和称为非终结符号的变量符号。这些符号代表了语言的构造。

词法分析器从输入中逐个读取字符,并输出一个词法单元的流。

语法分析从一个文法开始符号推导出一个给定的终结符号串。推导的方法是反复将某个非终结符替换为它的某个产生式的体。
语法分析的结构是中间代码,具体有抽象语法树AST和三地址代码TAC。

词法分析器扫描源程序并输出一个由词法单元组成的序列。
为了判断下一个词素何处结束。常常预先扫描输入字符。
一个词法分析器的行为经常用一个状态转换图来描述。有穷状态机是状态转换图的形式化表示。

自底向上语法分析器基于LR(k)语法分析。L表示从左到右的扫描,R表示反向构造出一个最右推导序列,而K表示向前看k个输入符号。

上下文无关文法,一个文法描述了一个终结符号集合。另一个非终结符号集合和一组产生式。

一颗语法分析树是一个推导的图形表示。
如果一个文法的某些终结符号串有两个或多个最左推导/最右推导,那么这个文法就称为二义性文法。

如果对于一个文法,如果只需要查看下一个输入符号就可以选择正确的产生式来扩展一个给定的非终结符,那么这个文法就称为LL(1)的。

自底向上语法器一般如此:根据下一个符号(向前看符号)和栈中内容,选择是将下一个输入移入栈中,还是将栈顶部某些符号进行归纳。

在移入-规约分析过程中,栈中内容总是一个可行前缀。

语法制导定义SDD是一个上下文无关文法和属性以及规则的集合。

语法制导树简称SDT。

一个语法分析树结点上的继承属性只能依赖于它的父结点的继承属性,和位于它左边的兄弟节点的属性。

有向无环图简称DAG。

中间形式通常是一个图形表示方法和三地址代码的组合。

回填是一种为布尔表达式和语句进行一趟式代码生成的技术。其基本思想是维护多个由不完整的跳转指令组成的列表,当跳转目标已知后,填回该目标。

为了实现语言中的抽象概念,编译器与操作系统及目标机器协同,创建并管理了一个运行时环境,该环境有一个静态数据区,用于存放对象代码和在编译时刻创建的静态数据对象。同时它还有动态的栈区和堆区,用于管理在目标代码执行时创建和销毁的对象。

过程调用和返回通常称为控制栈的运行时刻栈管理,可以使用栈的原因是过程调用在时间上是嵌套的。

局部变量的存储空间可以在运行时刻栈中分配。每一个活跃的活动都在控制栈中有一个活动记录(帧)。

对不支持嵌套函数的语言,一个变量的位置要么是全局的,要么可以在运行时刻栈顶的活动记录中找到。对于带嵌套过程的语言而言,可以通过访问链来访问栈中的非局部数据。访问链是加在各个活动记录中的指针。显示表是一个和访问链联合使用的辅助数组,它不需要使用访问链链路的高效捷径。

堆是用来存放生命周期不确定或到期明确删除的存储区域。
best-fit策略(分配能够满足空间请求的最小可用“窗口”)可减少碎片。对于空间局部性而言,通过合并或者说接合相邻的窗口来减少碎片。

人工回收有2个问题。内存泄露或者空指针引用。

有两种寻找不可达对象的基本方法:要么截获一个对象从可达变成不可达的转换,要么周期性定位所有可达对象,并推导出其余对象是不可达的。

代码生成器有3个主要任务:指令选择、寄存器分配和指派,以及指令排序。

指令选择是为每个中间表示语句选择目标语言指令的过程。
寄存器分配是决定哪些IR值将会保存在寄存器中的过程。
图着色算法是一个在编译器中完成寄存器分配的有效技术。

Ershov数指出了如果不把任何临时值保存回内存中的指令序列。这些指令的目的是在寄存器中腾出空间,以保存另一个值。

大部分全局优化是基于数据流分析技术实现的。

全局公共子表达式:一个重要的优化方法是寻找同一个表达式在两个不同基本块中的计算过程。如果一个在另一个前面,可以把第1次计算该表达式的结果存起来,并复用。

复制传播是替换等价变量。

代码移动吧每次迭代取值相同的值移到循环之外。
归纳变量在循环执行时的不同迭代中的取值是一个线性序列。
一个数据流分析模式在程序的每个点上都定义了一个值。
到达定值的数据流框架的数据流值是程序中的语句集合。
另一个重要的数据流框架计算了在各个程序点上活跃的变量。

可用表达式就是之前已经计算过,且在最后一次计算之后,它的运算分量都没有被重新定值的表达式。

代码移动和全局公共子表达式消除可以被扩展为部分冗余消除。
如果在一个流图的入口结点开始对它进行深度优先搜索,会得到一个深度优先搜索树。结点的深度优先排序是这颗树的后序遍历的逆序。

在深度优先生成树上,边分为前进边,后退边和交叉边。生成树的一个重要性质是所有的交叉边都是从树的右边到达左边。另一个重要性质是,如果按照深度优先排序,只有后退边的头比它的尾排序更靠前。

回边就是其头结点支配尾结点的边。
如果生成树的每一个后退边都是一条回边,那么这个流图就是可以规约的。
一个自然循环是一个结点的集合。
不同于迭代方法的另一种数据流分析方法是沿着区域层次结构向上,然后再向下扫描,计算从各个区域的头到达该区域中各个结点的传递函数。

基于区域的分析技术的重要应用之一是寻找归纳变量的数据流框架。该框架试图找出循环区域中每个满足下面条件变量的公式。这些变量的值可表示为循环迭代次数的仿射(线性)函数。
被优化的代码调度利用了流水线特性。
通过使用附加位置存放数据,可以消除反依赖和输出依赖。
真依赖:先写后读
反依赖:读后写
输出依赖:先后写

一个do-all循环的迭代之间不存在依赖关系,因而可并行。
一个do-across循环存在从每个迭代到后续迭代的依赖关系。

知道了如何优化数据局部性,也就知道了并行性在哪里。

并行性覆盖率的重要性可用amdal定律表示:如果f是被并行化代码的比率,且如果并行版本在一个有p个核的机器运行且无任何通信或者并行化开销,那么此时加速比为:1/((1-f)+(f/p))

循环是并行化的主要目标,在使用数组的应用中更是如此。
循环中对数组的各个访问之间的依赖关系有限且符合一种正则表达式,这两个因素可获得较好的数据局部性。
对数组的访问都是假定是仿射的,这些数组的下标的表达式是循环下标的线性函数。

对迭代空间的一个关键操作是把定义该空间的各个循环重新排列。这就要求把一个多向体迭代空间投影到它的部分维上。

Fourier-Motskin算法把一个给定变量的上下界替换成为关于这些界限的不等式。
处理循环时需解决的一个中心问题是确定两个数组访问之间是否具有数据依赖关系。如果这些访问以及循环界限都是仿射的,迭代空间就可以被定义为一个多面体。而优化循环转化为一个特定的矩阵向量方程是否具有位于该多面体中的解。

如果该矩阵的秩达到最大值,那么当着循环迭代运行时,数据访问不会两次触及同一个元素。如果数组是按行(列)存放的,那么消除最后(最前)一行后得到的矩阵的秩反映这个访问是否具有良好的局部性。

为了确定是否存在依赖关系,必须求一个丢番图方程的整数解。解这个方程的关键技术是计算各个变量的系数的最大公约数GCD。

空间分划约束是说,如果不同迭代中的两个访问之间具有数据依赖关系(即他们访问了同一个元素),那么它们必须被映射到同一个处理器上。

用来并行化具有仿射数组访问的程序转换是7个基本转换的组合:
循环融合
循环裂变
重新索引(下标加常量)
比例变换(下标乘以常量)
反置(倒转下标)
交换(交换下标)
倾斜(扫描线不再和坐标轴同向)

并行运算同步:在一个程序步骤之间插入同步运算。
流水线化允许处理器共享数据。
时间分划约束
分块把一个循环嵌套中的每个循环都分划为两个循环。
条状挖掘和分块类似。

对跨越过程边界的信息进行跟踪的数据流分析标记为过程间分析。
程序中调用其它过程的程序点称为过程调用。
一个程序的调用图是二分图。
只要一个程序中没有递归,原则上可以把过程调用展开,然后进行过程内分析。

如果一个数据流分析得到的事实和程序中的位置相关。那称它为控制流相关。如果一个数据流分析得到的事实和过程调用的历史相关,那么它就是上下文相关。

上下文相关分析可以基于过程克隆或者基于摘要。

需要过程间分析技术的一个重要应用是检测软件的安全漏洞。

Datalog语言是if-then规则的简单表示方式。它用于高层次描述数据流分析。

二分决策图BDD是一种使用带根的DAG表示布尔函数的简洁方法。