存疑
z=x+1;的中间代码生成
移植问题(已解决)
虚属性?
中间代码生成,修改
习题8.4 是否存在和这些规则一致的求值过程
9.1
9 10
12.2
13.3
14.6
15.4 15.5
重点
消除左递归
递归下降语法分析器
非递归下降语法分析器
句柄
判断是否为LL文法
LR文法的四种判断
1 | 关系:LR(0)<SLR(1)<LALR(1)<LR(1) |
1.判断LR(0)文法:
看项目中是否有归约-归约和移进-归约冲突。
如果无冲突则是LR(0)文法(如果是LR(0)文法则四种都是);如果有冲突则不是LR(0)文法。(就要向下判断)
2.判断SLR(1)文法:
a:DFA中存在冲突项目(归约-归约,归约-移进)
b:{a1,a2,…,an},FOLLOW(B1),FOLLOW(B2)两两互不相交,(交集=空集)时是SLR(1)项目。
【也就是说,同时满足两个条件才是SLR(1)文法】
若不是再向下判断。
3.判断LR(1)文法:
构造带向前搜索符的DFA,无归约-归约冲突则是LR(1)文法。
【此处意思是如果有向前搜索符还有冲突的话就不是LR(1)文法,就要再向下判断】
4.判断LALR(1)文法:
合并同心集后无(归约-归约)冲突(在之前的基础上)
(核相同,向前搜索符不同)
(B->a,a
B->a,a|b
同心集)
LR(0)项目集规范族的构造和分析的构造
1 | https://blog.csdn.net/m0_37154839/article/details/80316089 |
设计SDD
改写SDT
四元式序列
1 | https://blog.csdn.net/abc123lzf/article/details/103753507 |
中间代码生成
赋值语句的翻译
1 | newtemp( ):生成一个新的临时变量t,返回t的地址 |
数组引用的翻译
1 | L的综合属性 |
控制语句的翻译
1 | 继承属性 |
回填
非终结符B的综合属性
1 | B.truelist:指向一个包含跳转指令的列表,这些指令最终获得的目标标号就是当B为真时控制流应该转向的指令的标号 |
函数
1 | makelist( i ) |
1 | nextquad:即将生成的下一条指令的标号 |
什么是编译
编译:将高级语言翻译成汇编语言或机器语言的过程。
预处理器:把存储在不同文件中的源程序聚合在一起;把被称为宏的缩写语句转换为原始语句。
可重定位:在内存中存放的起始位置L不是固定的
加载器:修改可重定位地址;将修改后的指令和数据放到内存中适当的位置。
链接器:将多个可重定位的机器代码文件连接到一起;解决外部内存地址问题
编译系统的结构
词法分析概述
词法分析的主要任务:
1 | 从左向右逐行扫描源程序的字符,识别出各个单词,确定单词的类型。将识别出的单词转换成统一的机内表示——词法单元(token)形式。 |
语法分析概述
语法分析器从词法分析器输出的token序列中识别出各类短语,并构造语法分析树。
语义分析概述
主要任务:
任务一:收集标识符的属性信息
1 | : |
符号表:用来存放标识符的属性信息的数据结构
任务二:语义检查
1 | 变量或过程未经声明就使用; |
中间代码生成及编译器后端
常用的中间表示形式:
三地址码;语法结构树/语法树
三地址码
三地址码由类似于汇编语言的指令序列组成,每个指令最多有三个操作数。
三地址指令的表示
1 | 四元式(op,arg1,arg2,result) |
目标代码生成以源程序的中间表示形式作为输入,并把它映射到目标语言;目标代码生成的一个重要任务是为程序中使用的变量合理分配寄存器。
代码优化:为改进代码,所进行的等价程序变换,使其运行得更快一些,占用空间更小一些。
编译程序的生成
编译器的T形图
自展
移植
语言及其文法
基本概念
字母表
1 | 一个有穷符号集合。 |
字母表运算
1 | 乘积 |
串:字母表中符号的一个有穷序列
串的运算
1 | 连接,空串是连接运算的单位元 |
文法的定义
文法
$G=(V_T,V_N,P,S)$
$V_T$:终结符集合
1 | 终结符是文法所定义的语言的基本符号,有事也称为token |
$V_N$:非终结符集合
1 | 非终结符是用来表示语法成分的符号,有时也称为"语法变量" |
$V_T$与$V_N$不相交,二者相并统称为文法符号集。
P:产生式集合
1 | 描述了将终结符和非终结符组合成串的方法 |
S:开始符号
1 | 开始符号表示的是该文法中最大的语法成分 |
符号约定
终结符
非终结符
注
语言的定义
符号串$a0$经过n步推导出$a_n$,可简记为$a_0\Longrightarrow {}^{n}a_n $
句子是不包含非终结符的句型。
文法的分类
Chomsky文法分类体系
0型文法
$\alpha \longrightarrow \beta $
无限制文法,其中$\alpha$中至少包含一个非终结符。
由0型文法G生成的语言称为 0型语言
1型文法
$\alpha \longrightarrow \beta $
上下文有关文法,CSG
产生式的一般形式:
2型文法
$\alpha \longrightarrow \beta $
上下文无关文法,CFG
其中$\alpha\in V_N$
产生式的一般形式:
3型文法
$\alpha \longrightarrow \beta $
正则文法,RG
右线性文法:$A\longrightarrow wB或A\longrightarrow w$
左线性文法:$A\longrightarrow Bw或A\longrightarrow w$
CFG的分析树
给定一个句型,其分析树中的每一棵子树的边缘称为该句型的一个短语。
直接短语:高度为2的子树的边缘。
1 | 直接短语一定是某产生式的右部 |
二义性文法
1 | 如果一个文法可以为某个句子生成多颗分析树,则称这个文法是二义性的。 |
词法分析
正则表达式
正则定义
有穷自动机
最长子串匹配原则:当输入串的多个前缀与一个或多个模式匹配时,总是选择最长的前缀进行匹配
有穷自动机的分类
DFA确定的有穷自动机
NFA非确定的有穷自动机
正则文法与正则表达式与有穷自动机等价
带有空边的NFA与不带空边的NFA有等价性
从正则表达式到有穷自动机
先构造NFA,再从NFA到DFA。
根据RE构造NFA
假设正则表达式r1和r2对应的NFA分别为N(r1)和N(r2)
从NFA到DFA的转换
识别单词的DFA
词法分析阶段可检测错误的类型:
1 | 单词拼写错误 |
错误恢复策略
1 | 最简单的错误恢复策略:恐慌模式 |
语法分析
自顶向下分析概述
从分析树的顶部向底部方向构造分析树。
1 | 最左推导:总是选择每个句型的最左非终结符进行替换。 |
1 | 最右推导:总是选择每个句型的最右非终结符进行替换。 |
在自底向上的分析中,总是采用最左规约的方式,因此把最左规约称为规范规约,而最右推导相应的称为规范推导。
自顶向下选择最左推导。
自顶向下分析存在的问题
同一非终结符的多个候选式存在共同前缀,将导致回溯现象
左递归文法会使递归下降分析器陷入无限循环
消除直接左递归
消除直接左递归的一般形式
消除间接左递归
提取左公因子
预测分析
需要回溯的分析器叫不确定分析器。
1 | 预测分析:递归下降分析技术的一个特例,通过在输入中向前看固定个数符号来选择正确的A-产生式。 |
可以对某些文法构造出向前看k个输入符号的预测分析器,该类文法有时也称为LL(k) 文法类
预测分析法
LL(1)文法
S_文法
1 | 每个产生式的右部都以终结符开始 |
什么时候使用ε产生式
1 | 如果当前某非终结符A与当前输入符a不匹配时,若存在A→ε,可以通过检查a是否可以出现在 A的后面,来决定是否使用产生式 A→ε(若文法中无 A→ε ,则应报错) |
后继符号集
1 | 可能在某个句型中紧跟在A后边的终结符a的集合,记为FOLLOW(A) |
产生式的可选集
1 | 产生式A→β的可选集是指可以选用该产生式进行推导时对应的输入符号的集合,记为SELECT( A→β ) |
q_文法
1 | 每个产生式的右部或为ε ,或以终结符开始 |
串首终结符集
1 | 给定一个文法符号串α, α的串首终结符集FIRST(α)被定义为可以从α推导出的所有串首终结符构成的集合。如果α * ε,那么ε也在FIRST(α)中 |
LL(1)文法
1 | 当且仅当G的任意两个具有相同左部的产生式A → α | β 满足下面的条件: |
即同一非终结符的各个产生式的可选集互不相交
第一个L表示从左向右扫描输入,第二个L表示最左推导
follow(A)计算方法
预测分析表
预测分析
1 | 递归的方式:基于预测分析表对递归下降分析法进行扩展 |
递归的预测分析法
非递归的预测分析法
非递归的预测分析显式地维护一个栈结构,而不是通过递归调用的方式隐式地维护栈。这样的语法分析器可以模拟最左推导过程
预测分析步骤
1 | 1.构造文法 |
二义性判定
1 | 对于任一个上下文无关文法,不存在一个算法判断其是否为二义性,但能给出一个充分条件,满足则无二义性,不满足也未必有二义性 |
预测分析中的错误检测
两种情况下可以检测到错误
1 | 栈顶的终结符和当前输入符号不匹配 |
错误恢复
1 | 恐慌模式:忽略输入中的一些符号,直到输入中出现由设计者选定的同步词法单元(synchronizing token)集合中的某个词法单元;如果终结符在栈顶而不能匹配,一个简单的办法就是弹出此终结符 |
自底向上的分析
自底向上的语法分析采用最左归约方式(反向构造句子的最右推导)
移入-归约分析
工作过程
四种动作
1 | 移入:将下一个输入符号移到栈的顶端 |
存在问题
1 | 归约-归约冲突 |
归约-归约冲突
造成错误的原因:错误地识别了句柄
句柄:句型的最左直接短语
移入-归约冲突
LR分析法
是最大的、可以构造出相应移入-归约语法分析器的文法类
L: 对输入进行从左到右的扫描
R: 反向构造出一个最右推导序列
LR(k)分析
需要向前查看k个输入符号的LR分析
看PPT例题分析
LR(0) 分析
右部某位置标有圆点的产生式称为相应文法的一个LR(0)项目(简称为项目)
产生式A→ε 只生成一个项目A→ ·
增广文法
1 | 如果G 是一个以S为开始符号的文法,则G的增广文法 G' 就是在G中加上新开始符号S' 和产生式S' → S而得到的文法 |
引入这个新的开始产生式的目的是使得文法开始符号仅出现在一个产生式的左边,从而使得分析器只有一个接受状态
初始项目:S’→·S
接收项目:S’→S·
归约项目:·在末尾的项目
后继项目:同属于一个产生式的项目,但圆点的位置只相差一个符号, 则称后者是前者的后继项目
A→α· Xβ的后继项目是A→αX·β
可以把等价的项目组成一个项目集( I ) ,称为项目集闭包(Closure of Item Sets),每个项目集闭包对应着自动机的一个状态
eg:
LR(0)分析过程的冲突
移进归约冲突
在状态2时,当遇到*号时,不清楚应该移入还是归约。
归约归约冲突
状态二有两个归约和一个移入,移进/归约冲突和归约/归约冲突混合。
如果LR(0)分析表中没有语法分析动作冲突,那么给定的文法就称为LR(0)文法
不是所有CFG(上下文无关文法)都能用LR(0)方法进行分析,也就是说,CFG不总是LR(0)文法。
SLR 分析
LR(0)解决不了冲突的原因
1 | 句柄都是相对一个句型而言的,因此应该将句柄的识别放在句型这样一个上下文环境中考虑 |
SLR冲突
当状态2遇到=时,会有移入归约冲突。
存在问题
1 | SLR只是简单地考察下一个输入符号b是否属于与归约项目A→α相关联的FOLLOW(A),但b∈FOLLOW(A)只是归约α的一个必要条件,而非充分条件 |
LR(1)分析
在特定位置,A的后继符集合是FOLLOW(A)的子集,而SLR分析法则将其全部考虑了进去。
将一般形式为 [A→α·β, a]的项称为 LR(1) 项,其中A→αβ 是一个产生式,a 是一个终结符(这里将$视为一个特殊的终结符)它表示在当前状态下,A后面必须紧跟的终结符,称为该项的展望符(lookahead)
1 | LR(1) 中的1指的是项的第二个分量的长度 |
如果除展望符外,两个LR(1)项目集是相同的,则称这两个LR(1)项目集是同心的
LR(1)分析实际上是根据后继符集合的不同,将原始的LR(0)状态分裂成不同的LR(1)状态
LALR分析
1 | 寻找具有相同核心的LR (1) 项集,并将这些项集合并为一个项集。 所谓项集的核心就是其第一分量的集合 |
合并同心项集时产生归约-归约冲突的例子
合并同心项集不会产生移进-归约冲突
合并同心项集后,虽然不产生冲突,但可能会推迟错误的发现
LALR分析法可能会作多余的归约动作,但绝不会作错误的移进操作
LALR(1)的特点
1 | 形式上与LR(1)相同 |
二义性文法的LR分析
每个二义性文法都不是LR的
某些类型的二义性文法在语言的描述和实现中很有用
用优先级和结合性解决冲突
应该保守地使用二义性文法,并且必须在严格控制之下使用,因为稍有不慎就会导致语法分析器所识别的语言出现偏差
LR分析中的错误处理
语法错误的检测
1 | 当LR分析器在查询语法分析动作表并发现一个报错条目时,就检测到了一个语法错误 |
错误恢复策略
1 | 恐慌模式错误恢复 |
恐慌错误恢复
1 | 从栈顶向下扫描,直到发现某个状态si,它有一个对应于某个非终结符A的GOTO目标,可以认为从这个A推导出的串中包含错误 |
短语层次错误恢复
1 | 检查LR分析表中的每一个报错条目,并根据语言的使用方法来决定程序员所犯的何种错误最有可能引起这个语法错误 |
语法制导翻译
语法制导翻译使用CFG来引导对语言的翻译,是一种面向文法的翻译技术
基本思想
1 | 如何表示语义信息:为CFG中的文法符号设置语义属性,用来表示语法成分对应的语义信息 |
语法制导定义(Syntax-Directed Definitions, SDD)
1 | SDD是对CFG的推广 |
语法制导翻译方案 (Syntax-Directed Translation Scheme , SDT )
1 | SDT是在产生式右部嵌入了程序片段的CFG,这些程序片段称为语义动作。一个语义动作在产生式中的位置决定了这个动作的执行时间 |
语法制导定义SDD
概述
语法制导定义SDD是对CFG的推广
1 | 将每个文法符号和一个语义属性集合相关联 |
文法符号的属性
1 | 综合属性 (synthesized attribute) |
综合属性
1 | 在分析树结点 N上的非终结符A的综合属性只能通过 N的子结点或 N本身的属性值来定义 |
继承属性
1 | 在分析树结点 N上的非终结符A的继承属性只能通过 N的父结点、N的兄弟结点或 N本身的属性值来定义 |
属性文法
1 | 一个没有副作用的SDD有时也称为属性文法 |
SDD的求值顺序
语义规则建立了属性之间的依赖关系,在对语法分析树节点的一个属性求值之前,必须首先求出这个属性值所依赖的所有属性值
依赖图
1 | 依赖图是一个描述了分析树中结点属性间依赖关系的有向图 |
eg:
属性值计算顺序
1 | 这样的排序将一个有向图变成了一个线性排序,这个排序称为这个图的拓扑排序 |
1 | 对于只具有综合属性的SDD ,可以按照任何自底向上的顺序计算它们的值 |
S-属性定义与L-属性定义
仅仅使用综合属性的SDD称为S属性的SDD,或S-属性定义、S-SDD
如果一个SDD是S属性的,可以按照语法分析树节点的任何自底向上顺序来计算它的各个属性值
L-属性定义(也称为L属性的SDD或L-SDD)的直观含义:在一个产生式所关联的各属性之间,依赖图的边可以从左到右,但不能从右到左
正式定义
1 | 一个SDD是L-属性定义,当且仅当它的每个属性要么是一个综合属性,要么是满足如下条件的继承属性:假设存在一个产生式A→X1X2…Xn,其右部符号Xi (1<= i <= n)的继承属性仅依赖于下列属性: |
虚属性:就是一个动作,不干其他事情。
语法制导翻译方案SDT
语法制导翻译方案(SDT )是在产生式右部中嵌入了程序片段(称为语义动作)的CFG
SDD定义了各属性的计算方法(计算规则),SDT进一步明确了各属性的计算时机(计算顺序)
这两种情况下,SDT可在语法分析过程中实现
1 | 基本文法可以使用LR分析技术,且SDD是S属性的 |
S-SDD转换为SDT
将每个语义动作都放在产生式的最后
S-属性定义的SDT 实现
1 | 如果一个S-SDD的基本文法可以使用LR分析技术,那么它的SDT可以在LR语法分析过程中实现 |
语法分析器的扩展
1 | 为每个栈记录增加属性值字段,存放文法符号的综合属性值 |
将L-SDD转换为SDT
1 | 将计算某个非终结符号A的继承属性的动作插入到产生式右部中紧靠在A的本次出现之前的位置上 |
如果一个L-SDD的基本文法可以使用LL分析技术,那么它的SDT可以在LL或LR语法分析过程中实现
1 | 在非递归的预测分析过程中进行语义翻译 |
L-属性定义的自顶向下翻译
在非递归的预测分析过程中进行翻译
扩展语法分析栈
看例题
分析栈中的每一个记录都对应着一段执行代码
1 | 综合记录出栈时,要将综合属性值复制给后面特定的语义动作 |
在递归的预测分析过程中进行翻译
为每个非终结符A构造一个函数,A的每个继承属性对应该函数的一个形参,函数的返回值是A的综合属性值
对出现在A产生式右部中的每个文法符号的每个属性都设置一个局部变量
例题
算法
1 | 为每个非终结符A构造一个函数,A的每个继承属性对应该函数的一个形参,函数的返回值是A的综合属性值。对出现在A产生式中的每个文法符号的每个属性都设置一个局部变量 |
L-属性定义的自底向上翻译
给定一个以LL文法为基础的L-SDD,可以修改这个文法,并在LR语法分析过程中计算这个新文法之上的SDD
1 | 给定一个以LL文法为基础的L-属性定义,可以修改这个文法,并在LR语法分析过程中计算这个新文法之上的SDD |
中间代码生成
声明语句的翻译
主要任务
1 | 收集标识符的类型等属性信息,并为每一个名字分配一个相对地址 |
名字的类型和相对地址信息保存在相应的符号表记录中
类型表达式
基本类型是类型表达式
1 | integer |
可以为类型表达式命名,类型名也是类型表达式
将类型构造符(type constructor)作用于类型表达式可以构成新的类型表达式
数组构造符array
若T是类型表达式,则array ( I, T )是类型表达式( I是一个整数)
指针构造符pointer
若T 是类型表达式,则 pointer ( T ) 是类型表达式,它表示一个指针类型
笛卡尔乘积构造符X
若T1 和T2是类型表达式,则笛卡尔乘积T1 X T2 是类型表达式
函数构造符→
若T1、T2、…、Tn 和R是类型表达式,则T1T2 …Tn→ R是类型表达式
记录构造符record
若有标识符N1 、N2 、…、Nn 与类型表达式T1 、T2 、…、Tn , 则 record ( ( N1 X T1 ) X ( N2 X T2 )X …X ( Nn X Tn )) 是一个类型表达式
eg:
局部变量的存储分配
对于声明语句,语义分析的主要任务就是收集标识符的类型等属性信息,并为每一个名字分配一个相对地址
1 | 从类型表达式可以知道该类型在运行时刻所需的存储单元数量称为类型的宽度(width) |
名字的类型和相对地址信息保存在相应的符号表记录中
赋值语句的翻译
简单赋值语句的翻译
newtemp( ):生成一个新的临时变量t,返回t的地址
gen(code):生成三地址指令code
lookup(name):查询符号表返回name 对应的记录
增量翻译
1 | 在增量方法中,gen( )不仅要构造出一个新的三地址指令,还要将它添加到至今为止已生成的指令序列之后 |
ppt例子
数组引用的翻译
将数组引用翻译成三地址码时要解决的主要问题是确定数组元素的存放地址,也就是数组元素的寻址
符号表的组织——数组
控制语句的翻译
控制流语句
继承属性
1 | B.true:是一个地址,该地址用来存放当B为真时控制流转向的指令的标号 |
if-then-else
if-then
while-do
控制流语句SDT编写要点
1 | 分析每一个非终结符之前 |
布尔表达式的翻译
在跳转代码中,逻辑运算符&&、|| 和 ! 被翻译成跳转指令。运算符本身不出现在代码中,布尔表达式的值是通过代码序列中的位置来表示的
布尔表达式的SDT
SDT的通用实现方法
任何SDT都可以通过下面的方法实现
1 | 首先建立一棵语法分析树 |
例题
存疑,修改
回填
布尔表达式回填
基本思想
1 | 生成一个跳转指令时,暂时不指定该跳转指令的目标标号。这样的指令都被放入由跳转指令组成的列表中。同一个列表中的所有跳转指令具有相同的目标标号。等到 能够确定正确的目标标号时,才去填充这些指令的目标标号 |
非终结符B的综合属性
1 | B.truelist:指向一个包含跳转指令的列表,这些指令最终获得的目标标号就是当B为真时控制流应该转向的指令的标号 |
函数
1 | makelist( i ) |
例题
控制流语句回填
综合属性
1 | S.next1ist:指向一个包含跳转指令的列表,这些指令最终获得的目标标号就是按照运行顺序紧跟在S代码之后的指令的标号 |
例题
回填技术SDT编写要点
1 | 文法改造 |
switch语句的翻译
过程调用语句的翻译
需要一个队列q存放E1.addr 、E2.addr、…、 En.addr,以生成
例子
运行存储分配
存储组织
一个目标程序运行所需的存储空间主要包括
存储分配策略
1 | 对于那些在编译时刻就可以确定大小的数据对象,可以在编译时刻就为它们分配存储空间,这样的分配策略称为静态存储分配 |
活动记录
1 | 使用过程(或函数、方法)作为用户自定义动作的单元的语言,其编译器通常以过程为单位分配存储空间 |
活动记录的一般形式
静态存储分配
1 | 在静态存储分配中,编译器为每个过程确定其活动记录在目标程序中的位置 |
适合静态存储分配的语言必须满足以下条件
1 | 数组上下界必须是常数 |
满足这些条件的语言有BASIC和FORTRAN等
常用的静态存储分配方法:顺序分配法、层次分配法
顺序分配法
按照过程出现的先后顺序逐段分配存储空间
各过程的活动记录占用互不相交的存储空间
1 | 优点:处理上简单 |
层次分配法
通过对过程间的调用关系进行分析,凡属无相互调用关系的并列过程,尽量使其局部数据共享存储空间
层次分配算法
栈式存储分配
1 | 有些语言使用过程、函数或方法作为用户自定义动作的单元,几乎所有针对这些语言的编译器都把它们的(至少一部分的)运行时刻存储以栈的形式进行管理,称为栈式存储分配 |
活动树
1 | 用来描述程序运行期间控制进入和离开各个活动的情况的树称为活动树 |
1 | 每个活跃的活动都有一个位于控制栈中的活动记录 |
设计活动记录的一些原则
1 | 在调用者和被调用者之间传递的值一般被放在被调用者的活动记录的开始位置,这样它们可以尽可能地靠近调用者的活动记录 |
调用序列和返回序列
1 | 过程调用和过程返回都需要执行一些代码来管理活动记录栈,保存或恢复机器状态等 |
调用者和被调用者之间的任务划分
变长数据的存储分配
1 | 在现代程序设计语言中,在编译时刻不能确定大小的对象将被分配在堆区。但是,如果它们是过程的局部对象,也可以将它们分配在运行时刻栈中。尽量将对象放置在栈区的原因:可以避免对它们的空间进行垃圾回收,也就减少了相应的开销 |
非局部数据的访问
一个过程除了可以使用过程自身声明的局部数据以外,还可以使用过程外声明的非局部数据
1 | 全局数据 |
如何访问非局部数据
1 | 访问链(静态链) |
访问链
1 | 静态作用域规则:只要过程b的声明嵌套在过程a的声明中,过程b就可以访问过程a中声明的对象 |
嵌套深度
例子
访问链的建立
假设嵌套深度为nx的过程x调用嵌套深度为ny的过程y (x→y)
nx < ny的情况(外层调用内层)
1 | y一定是直接在x中定义的 (例如:s→q, q→p) ,因此,ny=nx +1 |
nx = ny的情况(本层调用本层 )
1 | 例如:递归调用 (q→q ) |
nx > ny的情况(内层调用外层,如: p→e )
1 | 调用者x必定嵌套在某个过程z中,而z中直接定义了被调用者y |
嵌套深度是在编译阶段通过静态分析就能确定的
display表
慕课没有
看例题,注意嵌套深度与树无关,在编译阶段就决定。
参数传递
形式参数(formal parameter):在过程定义中使用的参数
实际参数(actual parameter):在调用过程时使用的参数
形参和实参相关联的几种方法
1 | 传值(Call- by-Value) |
传值
1 | 把实参的值传递给相应的形参 |
传地址
1 | 把实参的地址传递给相应的形参 |
传值结果
1 | 传地址的一种变形 |
传名
1 | 相当于把被调用过程的过程体抄到调用出现的地方,但把其中出现的形参都替换成相应的实参 |
符号表
符号表是用于存放标识符的属性信息的数据结构
1 | 种属 (Kind) |
符号表的作用
1 | 辅助代码生成 |
例子
标识符的基本处理方法
1 | 当在某一层的声明语句中识别出一个标识符(id的定义性出现)时,以此标识符查相应于本层的符号表 |
符号表的建立?
代码优化
流图
基本块(Basic Block)是满足下列条件的最大的连续三地址指令序列
1 | 控制流只能从基本块的第一条指令进入该块。也就是说,没有跳转到基本块中间或末尾指令的转移指令 |
每个基本块由一组总是一起执行的指令组成
流图的每个结点是一个基本块
从基本块B到基本块C之间有一条边当且仅当基本块C的第一条指令可能紧跟在B的最后一条指令之后执行,此时称B是C的前驱(predecessor) , C是B的后继(successor)
优化的分类
机器无关优化:针对中间代码
机器相关优化:针对目标代码
局部代码优化:单个基本块范围内的优化
全局代码优化:面向多个基本块的优化
常用的优化方法
1 | 删除公共子表达式 |
删除公共子表达式
如果表达式x op y先前已被计算过,并且从先前的计算到现在,x op y中变量的值没有改变,那么x op y的这次出现就称为公共子表达式
局部公共子表达式;全局公共子表达式
例子
删除无用代码
复制传播
1 | 常用的公共子表达式消除算法和其它一些优化算法会引入一些复制语句(形如x = y的赋值语句) |
复制传播给删除无用代码带来机会
无用代码(死代码Dead-Code ) :其计算结果永远不会被使用的语句
例子
常量合并
如果在编译时刻推导出一个表达式的值是常量,就可以使用该常量来替代这个表达式。该技术被称为常量合并
代码移动
这个转换处理的是那些不管循环执行多少次都得到相同结果的表达式(即循环不变计算,loop-invariant computation) ,在进入循环之前就对它们求值
对于多重嵌套的循环,循环不变计算是相对于某个循环而言的。可能对于更加外层的循环,它就不是循环不变计算
强度削弱
用较快的操作代替较慢的操作,如用加代替乘
归纳变量
1 | 对于一个变量x ,如果存在一个正的或负的常数c使得每次x被赋值时它的值总增加c ,那么x就称为归纳变量(Induction Variable) |
删除归纳变量
1 | 在沿着循环运行时,如果有一组归纳变量的值的变化保持步调一致,常常可以将这组变量删除为只剩一个 |
基本块的优化
很多重要的局部优化技术首先把一个基本块转换成为一个无环有向图
基于基本块的 DAG 删除无用代码
1 | 从一个DAG上删除所有没有附加活跃变量(活跃变量是指其值可能会在以后被使用的变量)的根结点(即没有父结点的结点) 。重复应用这样的处理过程就可以从DAG中消除所有对应于无用代码的结点 |
数组元素赋值指令的表示
1 | 在a[j]=y时,有可能改变a[i]的值 |
根据基本块的DAG可以获得一些非常有用的信息
1 | 确定哪些变量的值在该基本块中赋值前被引用过 |
例子
数据流分析
一组用来获取有关数据如何沿着程序执行路径流动的相关信息的技术
在每一种数据流分析应用中,都会把每个程序点和一个数据流值关联起来
数据流分析的主要应用
1 | 到达-定值分析 (Reaching-Definition Analysis) |
数据流分析模式
1 | 语句的数据流模式 |
到达定值分析
定值
1 | 变量x的定值是(可能)将一个值赋给x的语句 |
到达定值(Reaching Definition)
1 | 如果存在一条从紧跟在x的定值d后面的点到达某一程序点p的路径,而且在此路径上d没有被“杀死” (如果在此路径上有对变量x的其它定值d′,则称定值d被定值d′“杀死”了) ,则称定值d到达程序点p |
到达定值分析的主要用途
循环不变计算的检测
1 | 如果循环中含有赋值x=y+z ,而y和z所有可能的定值都在循环外面(包括y或z是常数的特殊情况) ,那么y+z就是循环不变计算 |
常量合并
1 | 如果对变量x的某次使用只有一个定值可以到达,并且该定值把一个常量赋给x ,那么可以简单地把x替换为该常量 |
判定变量x在p点上是否未经定值就被引用
例子
到达定值的计算
例题
引用-定值链
活跃变量分析
活跃变量
1 | 对于变量x和程序点p,如果在流图中沿着从p开始的某条路径会引用变量x在p点的值,则称变量x在点p是活跃(live)的,否则称变量x在点p不活跃(dead) |
例子
活跃变量信息的主要用途
删除无用赋值
1 | 无用赋值:如果x在点p的定值在基本块内所有后继点都不被引用,且x在基本块出口之后又是不活跃的,那么x在点p的定值就是无用的 |
为基本块分配寄存器
1 | 如果所有寄存器都被占用,并且还需要申请一个寄存器,则应该考虑使用已经存放了死亡值的寄存器,因为这个值不需要保存到内存 |
可用表达式分析
可用表达式
1 | 如果从流图的首节点到达程序点 p的每条路径都对表达式x op y进行计算,并且从最后一个这样的计算到点p之间没有再次对x或y定值,那么表达式x op y在点 p是可用的(available) |
表达式可用的直观意义
1 | 在点 p上,x op y已经在之前被计算过,不需要重新计算 |
主要通途
消除全局公共子表达式
进行复制传播
可用表达式传递函数
可用表达式的数据流方程
注意此处初始化
流图中的循环
支配结点
1 | 如果从流图的入口结点到结点n的每条路径都经过结点d,则称结点d支配(dominate)结点n,记为d dom n |
寻找支配节点
回边
1 | 如果存在从结点n到d的有向边n→d,且d dom n,那么这条边称为回边 |
自然循环
自然循环的一个重要性质
1 | 如果两个自然循环的首结点不相同,则这两个循环要么互不相交,要么一个完全包含(嵌入)在另外一个里面 |
最内循环 (Innermost Loops): 不包含其它循环的循环
如果两个循环具有相同的首结点,那么很难说哪个是最内循环。此时把两个循环合并
全局优化
1 | 删除全局公共子表达式 |
删除全局公共子表达式
删除复制语句
对于复制语句s: x=y,如果在x的所有引用点都可以用对y的引用代替对x的引用(复制传播),那么可以删除复制语句 x=y
代码移动
循环不变计算检测算法
代码外提
循环不变计算语句 s : x = y + z 移动的条件
1 | (1) s所在的基本块是循环所有出口结点(有后继结点在循环外的结点)的支配结点 |
例子
作用于归纳变量的强度削弱
1 | 对于一个变量x ,如果存在一个正的或负的常量c ,使得每次x被赋值时,它的值总是增加c ,则称x为归纳变量 |
归纳变量的删除
1 | 对于在强度削弱算法中引入的复制语句j=t,如果在归纳变量j的所有引用点都可以用对t的引用代替对j的引用,并且j在循环的出口处不活跃,则可以删除复制语句j=t |
删除仅用于测试的归纳变量
代码生成
代码生成器的主要任务
指令选择
1 | 选择适当的目标机指令来实现中间表示(IR)语句 |
寄存器分配(allocation)和指派(assignment)
1 | 把哪个值放在哪个寄存器中 |
指令排序
1 | 按照什么顺序来安排指令的执行 |
一个简单的目标机模型
三地址机器模型
1 | 加载、保存、运算、跳转等操作 |
指令选择
过程调用和返回的目标代码
1 | 使用静态内存分配的方式 |
callee的活动记录在静态区中的起始位置
callee的目标代码在代码区中的起始位置
寄存器的选择
寄存器描述符和地址描述符
寄存器描述符
1 | 记录每个寄存器当前存放的是哪些变量的值 |
地址描述符
1 | 记录运行时每个名字的当前值存放在哪个或哪些位置 |
基本块的收尾处理
1 | 在基本块结束之前,基本块中使用的变量可能仅存放在某个寄存器中 |
管理寄存器和地址描述符
1 | 当代码生成算法生成加载、保存和其他指令时,它必须同时更新寄存器和地址描述符 |
寄存器选择函getReg
窥孔优化
窥孔(peephole)是程序上的一个小的滑动窗口
窥孔优化是指在优化的时候,检查目标指令的一个滑动窗口(即窥孔) ,并且只要有可能就在窥孔内用更快或更短的指令来替换窗口中的指令序列
也可以在中间代码生成之后直接应用窥孔优化来提高中间表示形式的质量
具有窥孔优化特点的程序变换的例子
1 | 冗余指令删除 |
习题总结
重点
已掌握
1 | 3.1 4.1 4.2 5.1(5.1.2重点) 6.1 6.2 7.1 7.2 7.3 7.4 7.5 (规范LR项集族) 7.6 7.7 7.8 7.9 |
未掌握
1 | 4.1.3的消除左递归方法 |
非重点
已掌握
1 | 1.2 2.1 8.2 8.4(是否存在一致的求值过程) |
未掌握
1 | 1.1 6.3 18.4 18.5 |
各种链