22142 words
111 minutes
编译原理复习

引论#

什么是编译程序#

高级语言(源程序)-> compiler -> 目标程序(汇编语言)

java -> translator -> Intermediate Representation -> jvm

  • 编译系统的作用:
    • 翻译:支持高层的抽象,支持底层的硬件体系结构
    • 优化:更快的执行速度,更少的空间
    • 分析:程序理解

编译过程和编译程序结构#

c/c++#

源程序.c/.cpp -> preprocessor -> .i -> compiler(cc) -> .s -> assembler -> .o -> linker(ld) (+库代码.a) -> 机器代码

编译阶段的组合#

  • 前端
    • 词法分析
    • 语法分析
    • 语义分析
  • 中端
    • 中间代码生成
    • 代码优化
  • 后端
    • 代码优化
    • 目标代码生成

词法分析#

  • 从左至右扫描字符流的源程序,分解构成源程序的字符串,识别出一个个单词token(种别码,值)
  • 词法分析的结果是二元组:(token种别码,token值)
  • 词法分析的过程,其实就是对一个字符串进行模式匹配的过程
  • 词法规则文件.l -> flex编译器 -> 词法分析源程序lex.yy.c -> gcc -> 词法分析器

语法分析#

  • parsing,语法检查(规约或推导)
  • 识别短语,并构造语法分析树,还可以进一步简化为抽象语法树AST

语义分析#

  • 收集符号信息(种属/类型/长度/位置/值/作用域)
  • 语义检查(类型匹配,类型转换,引用消解)

中间(IR)代码生成#

目标代码生成#

  • 选择合适的指令,生成性能最高的代码
  • 优化寄存器的分配,让频繁访问的变量(如循环变量)放到寄存器里,因为访问寄存器比访问内存快100倍左右
  • 在不改变运行结果的情况下,对指令做重新排序,从而充分运用CPU内部的多个功能部件的并行计算能力

符号表管理与错误处理#

  • 符号表管理
    • 记录源程序中使用的各种符号名称
    • 收集符号的属性信息,类型,作用域,分配存储信息
    • 登录:扫描到说明语句就将标识符等级在符号表中
    • 查找:在执行语句查找标识符的属性,判断语义是否正确
  • 错误处理
    • 报告出错信息,排错
    • 恢复编译工作

解释程序和一些软件工具#

  • 编译:先翻译后执行,保存目标程序,一次翻译,多次执行
  • 解释:边翻译边执行,翻译一句就执行一句,翻译完毕也执行完毕。只保存源程序无需保证完成的目标程序。执行一次需要翻译一次。

PL/0编译系统#

规则#

  • 数据类型只有整数类型
  • 数据结构只支持简单变量和常熟
  • 所支持的数字为最长9位的十进制数
  • 标识符的有效长度位10
  • 标识符引用前要先声明
  • 过程无参数
  • 过程可嵌套,最多嵌套3层
  • 过程可递归调用
  • 内层过程可以引用包围它的外层过程的标识符

EBNF表示#

<程序> ::= <分程序>
<分程序> ::= [<常量说明部分>] [<变量说明部分>] [<过程说明部分>] <语句>
<常量说明部分> ::= CONST <常量定义> {, <常量定义>};
<常量定义> ::= <标识符> = <无符号整数>
<无符号整数> ::= <数字>{<数字>}
<变量说明部分> ::= VAR <标识符>{,<标识符>}
<标识符> ::= <字母> {<字母>|<数字>}
<过程说明部分> ::= <过程首部><分程序>{;<过程说明部分>};
<过程首部> ::= PROCEDURE <标识符> ;
<语句> ::= <赋值语句>|<条件语句>|<当型循环语句>|<过程调用语句>|<读语句>|<写语句>|<复合语句>|<空>
<赋值语句> ::= <标识符> := <表达式>
<复合语句> ::= BEGIN <语句> {;<语句>} END
<条件> ::= <表达式><关系运算符><表达式> | ODD <表达式>
<表达式> ::= [+|-] <项> {<加法运算符><项>}
<项> ::= <因子> {<乘法运算符><因子>}
<因子> ::= <标识符>|<无符号整数>|'('<表达式>')'
.....

编译工具链#

gcc选项功能目标文件
-E预处理,不编译.i
-S编译成汇编程序,不汇编.s
-c编译并汇编生成目标文件,不连接.o
-o指定生成目标文件的文件名
-m32按32位进行编译
-m64按64位进行编译
-D宏定义
-nostdinc排除标准C语言头文件搜索路径
-I指定头文件搜索路径
-nostdlib链接时排除系统相关的启动文件和库
-Wl将后面的参数传给链接器
-lc链接libc库文件

文法与语言#

文法的概念#

  • 语言的基本形式是句子,句子是由单词序列构成的,单词是由语言基本符号(字母或单字)组成
  • 还包含将这些成分组织起来的语言规则,如此法规则、语法规则等
  • 文法是阐述语法的一个工具,语句是语法的实例
  • 程序语言-形式语言
  • 形式语言是符号串集合
  • 字母表上的符号按一定的规则组成的所有符号串集合,其中每个符号串称为一个句子

符号和符号串#

  • 字母表: Σ\Sigma是非空有穷集合,其元素称为符号
  • 符号串: Σ\Sigma中的符号组成的有穷序列
    • 空串: ϵ\epsilon
  • 规则:符号串的组成规则
  • 符号串长度: α\alpha中含有符号的个数,记作α|\alpha|,其中ϵ=0|\epsilon| = 0
  • 符号串集合: 如果集合A的元素都是字母表Σ\Sigma上的符号串,则称集合A为Σ\Sigma上的符号串集合,简称串集
  • 符号串的运算
    • 连接: αβ=αβ\alpha \cdot \beta = \alpha \beta
    • 或: αβ\alpha | \beta,即α\alpha或者β\beta
    • 方幂: αn=αα...α\alpha ^n = \alpha \alpha ... \alpha
      • α0=ϵ\alpha^0 = \epsilon空符号串,就是什么符号也没有的符号串
    • 闭包: α1=α;α2=αα\alpha ^1 = \alpha; \alpha ^2 = \alpha \alpha
    • 正闭包: α+=α1α2...αn...\alpha ^+ = \alpha^1 | \alpha^2| ... | \alpha^n | ...
    • 星闭包: α=α0α1α2...αn...\alpha ^* = \alpha^0|\alpha^1 | \alpha^2| ... | \alpha^n | ...
  • 符号串的集合运算
    • 乘积: AB=xyxAyBAB = {xy | x \in A且y \in B}
    • 和: AB=A+B=xxAxBA \cup B = A + B = {x|x \in A 或 x \in B}
    • 方幂: An=AA..AA^n = AA..A
    • 闭包
      • 正闭包: A+=A1A2...An...A^+ = A^1 \cup A^2 \cup ... \cup A^n \cup ...
      • 星闭包: A=A0A1...An...A^* = A^0 \cup A^1 \cup ... \cup A^n \cup ...

文法和语言的形式化定义#

  • 定义:文法G定义位一个四元组(VN,VT,P,S)(V_N, V_T, P, S),记为G=(VN,VT,P,S)G=(V_N, V_T, P, S)
    • VNV_N是非空有穷集合,称为非终结符集,其元素称为非终结符
    • VTV_T是有穷集合,称为终结符集,其元素称为终结符
    • P是非空有穷集合,称为规则集,其元素是字母表VNVTV_N \cup V_T上的规则,VNVTV_N \cup V_T称为文法的字母表V,且VNVT=V_N \cap V_T = \varnothing
    • SVNS \in V_N,称为开始符
  • 直接推导
    • 如果P中包含αβ\alpha \rightarrow \beta,那么任意的串γαδ\gamma \alpha \delta可以推导出γβδ\gamma \beta \delta,把这种推导称为直接推导或一步推导,记作γαδ=>γβδ\gamma \alpha \delta => \gamma \beta \delta
  • 直接归约
    • 如果γαδ=>γβδ\gamma \alpha \delta => \gamma \beta \delta,也可以说γβδ\gamma \beta \delta规约到γαδ\gamma \alpha \delta。这种规约称为直接规约或一步归约
  • 多步推导
    • α=+>β\alpha =^+ > \beta
  • 多步归约
    • α=+>β\alpha =^+ > \betaβ\beta规约到α\alpha
  • 0步以上推导与归约
    • α=>β\alpha =^* > \beta
  • 句型与句子
    • G[S],有S=>βS = ^* > \beta,则称β\beta是文法G[S]的句型
    • βVT\beta \in V_T^*,则称β\beta是文法G的句子
  • 语言: 文法G的产生语言定义为文法G的句子集合,记为L(G),即L(G)=βS=>β,βVTL(G) = {\beta | S =^*>\beta, \beta \in V_T^*}
  • 文法的等价: 对于G1,G2G_1, G_2,如果L(G1)=L(G2)L(G_1) = L(G_2),则称文法G1,G2G_1, G_2等价

文法的类型#

  • 0型文法(短语文法,图灵机)
    • 如果一个文法的所有规则,左侧至少含有一个非终结符,则称此文法为0型文法,也称为短语文法
    • 产生式形如αβ\alpha \rightarrow \beta,其中α(VTVN)+\alpha \in (V_T \cup V_N)^+且至少含有一个非终结符,β(VTVN)\beta \in (V_T \cup V_N)^*
  • 1型文法(上下文有关文法,线性界限自动机)
    • 产生式形如αβ\alpha \rightarrow \beta,其中α<=β|\alpha| <= |\beta|,仅SϵS\rightarrow\epsilon例外
  • 2型文法(上下文无关文法)
    • AβP,AVN\forall A \rightarrow \beta \in P, A \in V_N,则文法是2型文法,β(VTVN)\beta \in (V_T \cup V_N)^*
    • 任意产生式左部均为一非终结符
  • 3型文法(正规文法,有限自动机)
    • 文法的产生式只能是两种形式之一
    • AϵaaBA,BVN,aVTA \rightarrow \epsilon | a | aB \qquad A,B \in V_N, a \in V_T右线性文法
    • AϵaBaA,BVN,aVTA \rightarrow \epsilon | a | Ba \qquad A, B \in V_N, a \in V_T左线性文法

上下文无关文法及其语法树#

  • 从一个句型到另一个句型的推导往往不唯一
  • 最左推导: 任何一步 α=>β\alpha => \beta都是对α\alpha中最左非终结符进行替换
  • 最右推导: 任何一步 α=>β\alpha => \beta都是对α\alpha中最右非终结符进行替换
  • 最右推导,也叫规范推导,由规范推导所得的句型,叫做规范句型(右句型)。规范推导的逆过程,叫做规范归约

语法树(推导树)#

  • 文法G的任何一个句型,根据其推导都能构造一个语法树,或推导树。它是一个满足下列条件的多叉树
    • 文法的开始符S为数的根节点
    • 对任意产生式Aα,α(VNVY)A \rightarrow \alpha, \alpha \in (V_N \cup V_Y)^*, α\alpha的各符号严格依生产时的次序依次为A的子节点;文法符号为其节点标记(节点名)
  • 语法的二义性
    • 如果文法G的某个句子存在至少两棵不同的语法树,则称文法G是二义性的
    • 如果文法是无二义性的,一个句子的语法树反映了该句子的全部推导过程
    • 如果文法是无二义性的,一个句子的最左(最右)推导是唯一的
  • 语言的二义性
    • 文法的二义性,并不等同于语言的二义性
    • 因为二义性文法G,可能存在与之等价的无二义性的文法G’,即L(G)=L(G’)
    • 如果一个语言不存在无二义性的文法,则称该语言是先天二义性的
    • 二义性问题是不可判定问题,不存在一个算法,能在有限步骤内,确切判定一个文法是否是二义的

句型的分析#

  • 假设文法G[S]是语言L的文法,即L(G) = L,则“符号串α\alpha是否符合语言L的语法问题”被等价的转化称为“推导或归约问题”,即 SααVTS \Rightarrow^* \alpha \alpha \in V_T ^\ast
  • 推导法 vs 归约法
  • 自顶向下 vs 自底向上

自顶向下分析法#

  • 从文法开始符号出发,反复使用规则,寻找匹配符号串(推导)的句型,直到推导出句子或穷尽规则也不能推导出。进行每步推导时,存在两个选择问题
    • 选择句型中哪一个非终结符进行推导
    • 选择非终结符的哪一个规则进行推导
  • 最左推导,琼剧规则
    • 推导过程中一旦出现符号串α\alpha: α\alpha是合法的句子
    • 穷尽规则,不存在α\alpha的推导过程: α\alpha是不合法的句子

自底向上的分析法#

  • 从输入符号串α\alpha开始,逐步进行“归约”,直至归约出文法的开始符号S,则输入串α\alpha是文法G定义的语言的句子,否则不是
    • 归约是推导的逆过程
    • 每步归约时,粗野你在如何选择句型α\alpha的子串β\beta进行归约的问题
  • 方案:按句柄归约-规范归约(移进-归约)
    • 简单优先分析法SLR(1)
    • LR分析法
  • 短语,直接短语和句柄
    • 文法G[S]有SαAδαβδS \Rightarrow^* \alpha A \delta \Rightarrow^* \alpha \beta \delta,且A=+>βA =+> \beta
      • β\beta是句型αβδ\alpha \beta \delta相对于非终结符A的短语
        • 任一子树的叶子节点符号串皆为短语
        • 特别的,SαAδ=>αβδS \Rightarrow^* \alpha A \delta => \alpha \beta \delta,即A=>βA => \beta
      • β\beta是句型αβδ\alpha \beta \delta相对于规则AβA \rightarrow \beta的直接短语(简单短语)
        • 若子树的根是子树所有叶节点的父亲,则叶节点符号串为简单短语;
        • 右句型的最左直接短语,称为该句型的句柄

补充说明-多余规则和expislon规则#

  • 文法不得含有有害规则、多余规则
    • 有害规则: A -> A
    • 不可达(用)规则: 不再任何产生式右部出现的非终结符及其规则
    • 不可终止规则: 从某非终结符开始,不可能推导出任意终结符来
  • 对文法G中的符号X是有用的,是指X至少出现在一个句子的推导过程中,即X必须同时满足以下两个条件
    • X必须在某个句型中出现,即存在α,βV\alpha, \beta \in V*,有SαXβS \Rightarrow^* \alpha X \beta(可达)
    • 必须能够从X推导出终结符号串,即存在wVTw \in V_T^*,使αXβw\alpha X \beta \Rightarrow^* w (可终结),否则X就是无用的
    • 含有无用符号的产生式称为无用产生式
  • 文法化简:消除有害规则,无用五号与无用规则
    • 构造能推导出中介符号串的非终结符集合VN1V_{N1}
      • 对P中每一个产生式AαA \rightarrow \alpha,若αVT\alpha \in V_T^*,则VN1=AV_{N1} \cup = A
      • 对P中每一个产生式BβB \rightarrow \beta,若β(VT+VN1)\beta \in (V_T + V_{N1})*,则VN1=BV_{N1} \cup = B
      • 重复步骤2,直至VN1V_{N1}不再扩大为止
    • 删除不再VN1V_{N1}中的所有非终结符的相关产生式

词法分析#

词法分析程序的设计#

  • 词法分析任务
    • 从左至右扫描文本格式的源程序,从基于字符理解的源程序中分离出符合源语言词法的但此符号
  • 词法分析器又称为扫描器scanner
  • Scanner的功能
    • 输入: 源程序
    • 输出: (单词种别,单词自身的值)
    • 种别: 关键字、标识符、字面量(常量)、运算符、界符
  • 输出的单词符号的表示形式
    • (单词种别,单词自身的值)
  • 单词种别通常用整数编码表示
    • 多用enum
    • 若一个种别只有一个单词符号,则种别编码就代表该单词符号。关键字、运算符和界符都是一符一种
    • 若一个种别有多个单词符号,则对于每个单词符号,给出种别编码和自身的值
      • 标识符单列一种;标识符自身的值表示成按及其字节划分的内部码
      • 常数按类型分种;常数的值则表示成标准的二进制形式
  • 为什么把词法分析作为一个独立的阶段
    • 结构简介、清晰和条理化,有利于集中考虑词法分析的一些枝节问题
    • 编译程序的效率会改进
    • 增强编译程序的可移植性

单词的形式化描述工具#

  • 基于生成观点、计算观点和识别观点,分别行程了正规文法、正规式和有穷自动机3种用于描述词法的工具
  • 正规文法:对文法G=(VN,VT,P,S)G=(V_N, V_T, P, S),如果任意AβP,AVNA \rightarrow \beta \in P, A \in V_N,且β\beta只能是aB或a或ϵ\epsilon,则称G属于右线性3型文法
  • 正规式(正则表达式), 及其表达的语言(正规集)
    • ϵ,\epsilon, \varnothing都是正规式,其正规集分别是ϵ{\epsilon}\varnothing
    • aΣ\forall a \in \Sigma,a是Σ\Sigma上的正规式,其正规集为{a}
    • 如果r和s都是Σ\Sigma上的正规式,则
      • (r)是正规式,它表示的正规集为L(r)
      • r|s是正规式,它表示的正规集为L(r)L(s)L(r) \cup L(s)
      • rsr \cdot s是正规式,它表示的正规集为L(r)L(s)L(r) \cdot L(s)
      • rr*是正规式,它表示的正规集为L(r)=L(r)L(r*) = L(r)*
    • 有限次使用上述步骤而定义的表达式仍是正规表达式,它们表示的符号串的集合是正规集
  • 所有此法结构一般都可以用正规式描述
  • 若两个正规式所表示的正规集相同,则称这两个正规式等价
正规式正规集
a{a}
a/b{a, b}
ab{ab}
(alb)*{a,b}*
a*{ϵ,a,aa,...\epsilon, a, aa, ...}
(a/b)*a{a,b}*{a}
(a/b)(a/b){aa, ab, ba, bb}
定律公示描述
交换律r/s = s/rl是可以交换的
结合律r/(s/t) = (r/s)/tl是可结合的
结合律r(st)=(rs)t连接是可结合的
分配律r(s/t)=rs/rt;(r/t)r=sr/tr连接对l是可分配的
恒等律ϵr=rϵ=r\epsilon r = r \epsilon = rϵ\epsilon是连接的恒等单位
存在律r*=(r/ϵ\epsilon)*闭包种一定包含ϵ\epsilon
幂等律r**=r*闭包中一定包含ϵ\epsilon
“或”的抽取律r/r = rl是可消重的
  • 正规式和正规文法的等价
    • 如果正规式r和文法G,有L(r) = L(G),则称正规式r和文法G是等价的
  • 正规式r \rightarrow 文法G
    • Σ\Sigma上正规式r,则等价文法G=(VN,VT,P,S)G=(V_N, V_T, P, S),其中VT=ΣV_T = \Sigma;从形如产生式SrS\rightarrow r开始,按照下表进行转换,直到全部符合正规文法之产生式为止。可得到P,VNP, V_N
正规式产生式文法产生式
规则1A->xyA->xB, B->y
规则2A->x*yA->xB,A->y,B->xB,B->y
规则3A->x/yA->x, A->y

有穷自动机#

  • 本质上和状态转换图相同,但有穷自动机只回答yes/no
  • 分为两类
    • 不确定的有穷自动机(NFA)
      • 输入符号包括ϵ\epsilon,一个符号可以标价在离开同一个状态的多条边上
    • 确定有穷自动机(DFA)
      • 输入符号不含ϵ\epsilon,每个状态以及每个符号,最多只有一条边
  • 两种自动机都识别正则语言
    • 对于每个可以用于正则表达式描述的语言,均可用某个NFA或DFA来识别,反之亦然

DFA形式化定义#

  • 确定有限自动机(DFA),M是一个五元组,M=(K,Σ,f,S,Z)M=(K, \Sigma, f, S, Z)
    • K: 有穷状态集
    • Σ\Sigma: 输入字母表(有穷), 输入符号集
    • f: 状态转换(移)函数,为K×ΣKK \times \Sigma \rightarrow K的单值部分映射,f(ki,a)=kjf(k_i, a) = k_j表示:当现行状态为kik_i,输入字符串为a时,将状态转换到下一个状态kjk_j,将kjk_j称为kik_i的一个后继状态
    • SKS \in K: 初始状态
    • ZKZ \subseteq K: 终态集,也称为可接受状态或结束状态
  • DFA的扩展状态转移函数
    • f:K×ΣKf': K \times \Sigma ^* \rightarrow K映射,设aΣ,βΣ,qKa \in \Sigma, \beta \in \Sigma^*, q \in K,则
    • f(q,aβ)=f(q,a),ifβ=ϵf'(q, a\beta) = f(q, a),\quad if \beta = \epsilon
    • f(q,aβ)=f(f(q,a),β),ifβ!=ϵf'(q, a\beta) = f'(f(q, a), \beta),\quad if \beta != \epsilon
  • DFA识别的语言
    • 设DFA M=(K,Σ,f,S,Z)M=(K, \Sigma, f, S, Z),如果αΣ,f(S,α)Z\alpha \in \Sigma ^*, f'(S, \alpha) \in Z,则称符号串α\alpha是DFA M所接受(或识别)的。DFA M所接受的符号串的集合记为L(M)
    • L(M)=ααΣ,f(S,α)ZL(M) = {\alpha | \alpha \in \Sigma ^*}, f'(S, \alpha) \in Z
    • Σ\Sigma上的一个符号串集VΣV \subseteq \Sigma ^*是正规的,如果存在一个Σ\Sigma上的DFA,使得V=L(M)

NFA的形式化定义#

  • 一个非确定有限自动机(NFA) M是一个五元式M=(K,Σ,f,S,Z)M=(K, \Sigma, f, S, Z)
    • K: 有穷状态集
    • Σ\Sigma: 输入字母表(有穷)
    • f: 状态转换(移)函数,为K×(Σϵ)P(K)K \times (\Sigma \cup {\epsilon}) \rightarrow P(K)的部分映射,P(K)表示K的幂集
    • SKS \in K: 初始状态
    • ZKZ \subseteq K: 终态集,也称为可接受状态或结束状态
  • DFA与NFA的区别
    • NFA弧上的标记可以是ϵ\epsilon
    • NFA同一个字母可能出现在同一状态射出的多条弧上
    • DFA是NFA的特例
  • NFA扩展状态转移函数
    • f(q,a)Move(I,a)f(I,s)f(q, a) \rightarrow Move(I, a) \rightarrow f'(I, s)
    • f:P(K)×ΣP(K)f': P(K) \times \Sigma^* \rightarrow P(K)映射。其中aΣ,s,βΣ,IKa\in \Sigma, \quad s, \beta \in \Sigma^*, I \subset K
    • Move(I,a)=qIf(q,a)Move(I, a) = \cup _{q \in I} f(q, a)
    • f(I,aβ)=Move(I,a),ifβ==ϵf'(I, a\beta) = Move(I, a), \quad if \beta == \epsilon
    • f(I,aβ)=f(Move(I,a),β),ifβ!=ϵf'(I, a\beta) = f'(Move(I, a), \beta), \quad if \beta != \epsilon
  • NFA识别的语言
    • 设NFA M=(K,Σ,f,S,Z)M=(K, \Sigma, f, S, Z),如果αΣ,f(S,α)Z!=\alpha \in \Sigma ^*, f'(S, \alpha) \cap Z != \varnothing,则称符号串α\alpha是DFA M所接受(或识别)的。DFA M所接受的符号串的集合记为L(M)
    • 自动机的等价: 对于任何两个有限自动机M和M’,如果L(M)=L(M’),则称M与M’等价
    • 对于每个NFA M存在一个DFA M’,使得L(M)=L(M’),反之亦然
    • DFA与NFA描述能力相同
  • 状态集I的转换运算
    • 设NFA M=(K,Σ,f,S,Z)M=(K, \Sigma, f, S, Z), IKI \subseteq K, aΣϵa \in \Sigma \cup {\epsilon},则Move(I, a)定义为Move(I,a)=qIf(q,a)Move(I, a) = \cup _{q \in I} f(q, a)
  • 状态集I的ϵ\epsilon闭包
    • ϵClosure(I)=I\epsilon - Closure(I) = I
    • ϵClosure(I)=Move(ϵClosure(I),ϵ)\epsilon- Closure(I) \cup = Move(\epsilon - Closure(I), \epsilon)
    • 重复上一个步骤,直到ϵClosure(I)\epsilon-Closure(I)不再扩大为止

NFA->DFA的转换算法(子集构造法)#

  • 输入: NFA N=(K,Σ,f,S0,Z)N=(K, \Sigma, f, S_0, Z)
  • 输出: 等价的DFA D=(K,Σ,g,S=ϵclosure(s0),F)D=(K, \Sigma, g, S=\epsilon-closure({s_0}), F), g(T,a)=ϵclosure(move(T,a))=Ug(T, a) = \epsilon-closure(move(T, a)) = U
  • 方法:
    • 一开始,ϵclosure(s0)\epsilon-closure({s_0})是D中唯一的状态,且它未加标记
while (在D中存在未标记的状态T){
标记T;
for (a \in \Sigma) {
U = \epsilon-closure(move(T, a));
Dtran[T, a] = U;
if (U不在D中) {
将U加入到D中,且不加标记
}
}
}

DFA的化简#

  • 消除无用状态(不可达,没有通路到达状态)
  • 合并等价状态
    • 一致性条件:p和q同时是可接受状态或不可接受状态
    • 蔓延性条件:对所有输入符号,p和q必须转换到等价的状态中
    • 方法:分割法-状态被分成不同子集,不同的子集不等价,同一子集等价

正规式和有穷自动机的等价性#

  • 对于Σ\Sigma上的NFA M,可以构造一个Σ\Sigma上的正规式r,使得L(r) = L(M)
  • 对于Σ\Sigma上的每一个正规式r,可以构造一个Σ\Sigma上的NFA M,使得L(M) = L(r)
  • 新增两个状态X和Y作为开始状态和接受状态,且将X经ϵ\epsilon指向M的所有开始状态,将M的所有接受状态经ϵ\epsilon指向Y,得到M’,显有L(M’) = L(M)

正规文法和有穷自动机的转换#

  • 正规文法G=(VN,VT,P,S)NFAMG=(V_N, V_T, P, S) \rightarrow NFA M
    • M的字母表 = G的终结符VTV_T
    • G的每个非终结符是M的一个状态
    • G的开始符S是M的开始状态S
    • 增加新状态Z,作为M的终态
    • M的状态转移函数f
      • 如果AaPA \rightarrow a \in P, 则f(A,a)=Zf(A, a) = Z
      • 如果AϵPA \rightarrow \epsilon \in P, 则f(A,ϵ)=Zf(A, \epsilon ) = Z
      • 如果AaBPA \rightarrow aB \in P, 则f(A,a)=Bf(A, a) = B
  • 有穷自动机M=(K,Σ,f,S,Z)M=(K, \Sigma, f, S, Z) \rightarrow 正则文法G=(K,Σ,P,S)G=(K, \Sigma, P, S)
    • 必要时确定化M(如果M含ϵ\epsilon转移)
    • G的产生式P由下列方法构造
      • f(B,a)=Cf(B, a) = C, 则BaCB \rightarrow aC
      • 对可接受态Z,增加产生式ZϵZ \rightarrow \epsilon
      • f(B,a)=Cf(B, a) = C, C为终态,且非始态,又无出边,可直接BaB\rightarrow a

词法分析程序的自动构造工具#

  • 构造词法分析程序的技术线路
    • 一句给定的源语言之单词集,设计其正规文法或正规式
    • 等价的转换成非确定有穷自动机NFA
    • 再通过子集法将其确定话,最终将确定有穷自动机最小化(DFA)
    • 最后依据最小化的DFA,设计词法分析程序

自顶向下语法分析#

  • 确定的自顶向下分析方法LL(1)
    • LL(1)文法的判定与构造
    • LL(1)方法的分析技术
      • 递归下降子程序法
      • 预测分析表: 每个产生式->首符号集

确定的自顶向下语法分析思想#

  • 从文法开始符号S推导出词串w的过程
  • 从S为根节点,从顶部向底部(叶节点)构造语法分析树
  • 每一步推导中,都需要左两个选择
    • 替换当前句型中那个非终结符
    • 用该非终结符的哪个候选式进行替换
  • 非终结符的选择:最左推导
    • 总是选择每个句型的最左非终结符进行替换
  • 候选产生式的选择
    • 根据输入流中的下一个终结符,选择最左非终结符的一个候选式
    • 确定的自顶向下:选择唯一可能推导出输入串w的规则进行推导
    • 不确定的自顶向下:穷举产生式(回溯:尝试->返回->再尝试)

FIRST集——串首(终结)符号集#

  • 设文法G=(VN,VT,P,S)G = (V_N, V_T, P, S),则FIRST(α)=aαaβ,aVT,α,βVFIRST(\alpha) = {a | \alpha \Rightarrow^* a \beta, a \in V_T, \alpha, \beta \in V*}
    • 特别地,αϵ\alpha \Rightarrow^* \epsilon,约定ϵFIRST(α)\epsilon \in FIRST(\alpha)
  • 同一非终结符的多个产生式,若右部的FIRST集无交集,则推导式确定的

FOLLOW(A)——非终结符的后继(终结)符号集#

  • 设文法G=(VN,VT,P,S)G = (V_N, V_T, P, S), AVNA \in V_N, 则FOLLOW(A)=aSαAβ,aVT,aFIRST(β),α,βVFOLLOW(A) = {a | S \Rightarrow^* \alpha A \beta, a \in V_T, a \in FIRST(\beta), \alpha, \beta \in V*}
  • 或者说,FOLLOW(A)=aS...Aa...,aVTFOLLOW(A) = {a | S \Rightarrow^* ...Aa..., a \in V_T}
  • S...AS\Rightarrow^*...A,或者上述模式中的βϵ\beta \Rightarrow^* \epsilon,则句末符FOLLOW(A)\sharp \in FOLLOW(A)
  • FOLLOW(A)是由任意句型中紧邻非终结符A之后出现的终结符a组成的集合
  • 如果对非终结符A,有一条空规则,则A的FOLLOW集合和A的非空右部的FIRST集合两两相交为空,可以使用确定的最左推导
  • #什么时候也要添加到FOLLOW集中
    • FOLLOW(S)\sharp \in FOLLOW(S),S为文法的开始符号
    • S...AS \Rightarrow^* ... A,则FOLLOW(A)\sharp \in FOLLOW(A),#为输入串的结束符
    • S...AβS \Rightarrow^* ... A\beta,且βϵ\beta \Rightarrow^* \epsilon,亦有FOLLOW(A)\sharp \in FOLLOW(A)
    • A...BA \Rightarrow^* ... B,或A...Bβ,βϵA \Rightarrow^* ... B \beta, \beta \Rightarrow^* \epsilon,则FOLLOW(A)FOLLOW(B)FOLLOW(A) \subseteq FOLLOW(B)

SELECT(Aα\rightarrow\alpha)-产生式的可选集#

设文法G=(VN,VT,P,S),AVN,AαPG = (V_N, V_T, P, S), A \in V_N, A \rightarrow \alpha \in P,则 SELECT(Aα)=FIRST(α),ifα!ϵ,else(First(α)ϵ)FOLLOW(A)SELECT(A \rightarrow \alpha) = FIRST(\alpha), if \alpha !\Rightarrow^* \epsilon, else (First(\alpha) - {\epsilon}) \cup FOLLOW(A) SELECT(Aϵ)=FOLLOW(A)SELECT(A \rightarrow \epsilon) = FOLLOW(A)

LL(1)文法#

  • 文法G是LL(1)的,当且仅当对每个VNV_N, A的两个不同产生式Aα,AβA \rightarrow \alpha, A \rightarrow \beta,满足SELECT(Aα)SELECT(Aβ)=SELECT(A\rightarrow\alpha) \cap SELECT(A \rightarrow \beta) = \varnothing, 其中α,β\alpha, \beta不能同时推导出ϵ\epsilon
  • LL(1)代表
    • Left to right parsing 从左到右分析token
    • Left most derivation 最左推导
    • 1: 只需向右看1个符号便可以决定选择哪个产生式进行推导
  • 确定的,无二义性的

LL(1)文法的判别#

  • 判别依据:根据上面的定义,文法G是LL(1)的,当且仅当任意两个左部相同的产生式其SELECT集的交集为空
  • 若存在非终结符的多个产生式,你必须计算SELECT()集
    • 判别αϵ\alpha \Rightarrow^* \epsilon,仅当α\alpha中所有非终结符全部可推导出空,且α\alpha无非空的终结符
    • 求产生式右部FIRST集,如产生式右部可推导出ϵ\epsilon,则还需计算产生式左部FOLLOW集

判断非终结符是否能推出空串#

  • ϵ\rightarrow \epsilon的非终结符,直接标记Yes,并划掉其所有产生式
  • 如果一个非终结符的所有产生式右侧至少有一个非ϵ\epsilon的终结符,它就不可能推导出ϵ\epsilon。直接标记No,并花掉其所有产生式。剩下的产生式中,但凡右侧有一个非ϵ\epsilon的终结符,也不可能推到出ϵ\epsilon。这样的产生式也划掉
  • 在剩下的产生式中,把所有能=>ϵ=>\epsilon的非终结符,替换成ϵ\epsilon,化简,结果=ϵ=\epsilon,标yes,否则no

FIRST(X)的算法#

XVX \in V

  • XVTX \in V_T, FIRST(X) = {X};
  • XϵX \rightarrow \epsilon, FIRST(X)={ϵ}FIRST(X) \cup = \{\epsilon\}
  • 对于所有形如Xa...X \rightarrow a...规则,且aVT,FIRST(X)=aa \in V_T, FIRST(X) \cup = {a}
  • 对于所有形如XY1Y2...YnX \rightarrow Y_1Y_2...Y_n规则,YiVNY_i \in V_N
    • 如果Y1ϵ,Y2ϵ,...,Yi1ϵ,(i<=n)Y_1 \Rightarrow^* \epsilon, Y_2 \Rightarrow^* \epsilon, ..., Y_{i-1} \Rightarrow^* \epsilon ,(i <= n),则FIRST(X)=(FIRST(Y1)FIRST(Y2)...FIRST(Yi)){ϵ}FIRST(X) \cup = (FIRST(Y_1)\cup FIRST(Y_2)...\cup FIRST(Y_i)) - \{\epsilon\}
    • 如果Y1ϵ,Y2ϵ,...,YnϵY_1 \Rightarrow^* \epsilon, Y_2 \Rightarrow^* \epsilon, ..., Y_n \Rightarrow^* \epsilon,则FIRST(X)=(FIRST(Y1)FIRST(Y2)...FIRST(Yi)){ϵ}FIRST(X) \cup = (FIRST(Y_1) \cup FIRST(Y_2) ... \cup FIRST(Y_i)) \cup \{\epsilon\}
  • 重复上一个步骤,直到FIRST()不再扩大为止

求FIRST(α\alpha)的算法#

αV,α=n,α=Y1Y2...Yn(VNVT)\alpha \in V^*, |\alpha| = n, \alpha = Y_1Y_2...Y_n \in (V_N \cup V_T)*,先置FIRST(α)=FIRST(\alpha) = \varnothing

  • 如果Y1VTY_1 \in V_T,则FIRST(α)={Y1}FIRST(\alpha) = \{Y_1\}
  • 如果Y1ϵ,Y2ϵ,...,Yi1ϵ,(1<i<=n)Y_1 \Rightarrow^* \epsilon, Y_2 \Rightarrow^* \epsilon, ..., Y_{i-1}\Rightarrow^* \epsilon, (1 < i <= n), 则FIRST(α)=(FIRST(Y1)FIRST(Y2)...FIRST(Yi))ϵFIRST(\alpha) = (FIRST(Y_1)\cup FIRST(Y_2)... \cup FIRST(Y_i)) - {\epsilon}
  • 如果Y1ϵ,...,YnϵY_1 \Rightarrow^* \epsilon, ..., Y_n \Rightarrow^* \epsilon, 则FIRST(α)=(FIRST(Y1)...FIRST(Yn)){ϵ}FIRST(\alpha) = (FIRST(Y_1)\cup ... \cup FIRST(Y_n)) \cup \{\epsilon\}

求FOLLOW(A)的算法#

AVNA \in V_N

  • FOLLOW(A)=FOLLOW(A) = \varnothing,置FOLLOW(S)={}FOLLOW(S) = \{ \sharp \}
  • 若有产生式BαAβ,BVNB \rightarrow \alpha A \beta, B \in V_N, 则FOLLOW(A)=(FIRST(β)ϵ)FOLLOW(A) \cup = (FIRST(\beta) - {\epsilon})
  • 若有产生式BαAB \rightarrow \alpha ABαAβB \rightarrow \alpha A \beta,且βϵ\beta \Rightarrow^* \epsilon,则FOLLOW(A)=FOLLOW(B)FOLLOW(A) \cup = FOLLOW(B)
  • 重复上面两个步骤,直到FOLLOW(A)不再扩大为止

某些非LL(1)文法到LL(1)文法的等价变换#

  • 非LL(1)文法
    • 若文法含有左公共因子,一定不是LL(1)文法
    • 若文法含有直接或间接左递归,一定不是LL(1)文法
  • 非LL(1)文法\rightarrowLL(1)文法的等价变换
    • 提取左公共因子
    • 消除左递归
      • 消除直接左递归法
      • 消除简介左递归法

提取左公共因子#

  • 对形如AαβαγA \rightarrow \alpha \beta | \alpha \gamma进行等价变换为Aα(βγ)A \rightarrow \alpha ( \beta | \gamma)
    • 进一步变换为AαA;AβγA \rightarrow \alpha A'; A' \rightarrow \beta | \gamma
  • 对形如Aαβ1αβ2...αβnA \rightarrow \alpha \beta_1 | \alpha \beta_2 | ... | \alpha \beta_n进行等价变换
    • AαA;Aβ1...βnA \rightarrow \alpha A'; A' \rightarrow \beta_1 |... | \beta_n
  • Aβ1β2...βnA' \rightarrow \beta_1 | \beta_2 | ... | \beta_n中仍含左公共因子,再次提取,直至所有的产生式不再有左公共因子
  • 文法中不含左公共因子只是LL(1)文法的必要条件

递归文法#

  • 对于文法G,形如AαAβA \rightarrow \alpha A \beta的规则称为文法G的直接递归规则。特别的,如果α=ϵ\alpha = \epsilon时候,则称为文法G的直接左递归规则。如果β=ϵ\beta = \epsilon时,则称文法G的直接右递归规则
  • 设文法G,如果存在推导AαλAμA \rightarrow \alpha \Rightarrow^* \lambda A \mu,则称规则AαA \rightarrow \alpha为文法G的间接递归规则。特别的,如果λ=ϵ\lambda = \epsilon时,称为文法G的间接左递归规则。如果μ=ϵ\mu = \epsilon时,则称文法G的间接右递归规则

消除直接左递归#

消除文法中一切左递归的算法(无有害规则,无空产生式)

VNV_N中的非终结符按任意顺序线性排列,A1,...,AnA_1, ..., A_n

for (i = 1; i <= n; i++) {
for (j = 1; j <= i-1; j++) {
若Aj的产生式为 A_j -> \dleta_1 | ... | \delta_k
则形如A_i -> A_j \gamma的产生式变为A_i -> \delta_1 \gamma | \delta_2 \gamma | ... | delta_k \gamma
}
消除A_i的直接左递归
}
删除无用产生式

LL(1)分析的实现-递归下降分析法#

  • LL(1)文法的(确定的自顶向下)分析方法
    • 递归下降的LL(1)分析法(递归的预测分析法)
    • (预测分析)表驱动的LL(1)分析法(非递归的预测分析法)

递归下降子程序法的基本思想#

  • 将每个非终结符编写称一个递归子程序,完成选择规则、推导和匹配的功能
  • 若非终结符A有多个产生式,则根据产生式的SELECT集选择相应的规则推导
  • 对规则AY1...YnA\rightarrow Y_1...Y_n,依次调用右部非终结符的子程序或匹配终结符,YiY_i为非终结符则调用YiY_i对应的子程序,若YiY_i为终结符,则与当前token匹配。匹配成功则调用词法分析程序取下一个token,匹配失败或当前token不属于任何SELECT集时,报告语法错误
  • 按照递归子程序法构造的语法分析程序是由一个总控子程序和一组非终结符对应的递归子程序组成的

LL(1)分析的实现-表驱动分析法#

  • 输入栈I: 以串末端为底,栈内为未匹配部分;初始状态为w#,w为要识别的串
  • 分析栈S: 推导过程产生的句型未匹配部分;初始状态为#S
  • 分析表M: 若aSELECT(Aα)a \in SELECT(A \rightarrow \alpha),则M[A,a]=(Aα)M[A, a] = (A\rightarrow \alpha)
  • 输出: w的最左推导,或报告语法错误

LL(1)中的出错处理#

  • 错误处理的两个任务
    • 报错
      • 栈顶的终结符与输入符号不匹配
      • 非终结符A位于栈顶,面临的输入符号位a,但分析表的表项M[A, a]位空
    • 错误恢复
      • 应急恢复
      • 短语层恢复

应急恢复#

  • 栈顶终结符与输入符号不匹配
  • 在空的M[A, a]指定同步符号,一旦遇到这种错误,就跳过输入符号,直到遇到同步符号位置
  • 当分析栈顶位A,一旦错误发生
    • 跳过输入串中的一些符号,直到遇到FOLLOW(A)中的符号,然后A退栈。相当于跳过A能推导出的所有符号,A的推导完成。
    • 跳过输入串中的一些符号,直到遇到FIRST(A)中的符号。相当于从A开始推导。

短语层恢复#

  • 每个非终结符,开始分析时,使用一个符号集合BeginSym;分析结束时,使用一个符号集合EndSym

自底向上语法分析#

LR分析概述#

  • LR(K)
    • L - left to right parsing
    • R - right most derivation in Reverse
    • K - look ahead K token(s)

LR(0)分析#

  • 将符号出啊的任意含有头符号的子串称为前缀。特别的,空串ϵ\epsilon为任意串的前缀
  • 设文法G[S],如果SRαAω=R>αβωS \Rightarrow^* _R \alpha A \omega =R> \alpha \beta \omega是句型αβω\alpha \beta \omega的规范推导,则αβ\alpha \beta称为可归前缀,αβ\alpha \beta的前缀称为活前缀
  • 可归前缀的后缀是句柄,句型的活前缀的右端不超过句柄末端
  • 增广文法
    • G[S]=(VN,VT,P,S)G[S] = (V_N, V_T, P, S)
    • G[S]=(VN{S},VT,P{SS},S)G[S'] = (V_N \cup \{S'\}, V_T, P \cup \{S' \rightarrow S\}, S')
    • VN{S}=V_N \cap \{S'\} = \varnothing
    • 则称G[S’]是G[S]的增广文法

LR(0)项目#

  • 移进项目: AαaβA \rightarrow \alpha \cdot a \beta
  • 待约项目: AαXβA \rightarrow \alpha \cdot X \beta
  • 归约项目: AA \rightarrow \cdot
  • 接受项目: Sα,SSS' \rightarrow \alpha \cdot, S' \rightarrow S \cdot

LR(0)项目的MOVE运算定义#

设I是文法G的LR(0)项目子集,则MOVE(I, X)定义如下 MOVE(I,X)={AαXβAαXβI}MOVE(I, X) = \{A \rightarrow \alpha X \cdot \beta | A \rightarrow \alpha \cdot X \beta \in I \}

LR(0)项目的CLOSURE运算定义#

  • 设I是文法G的LR(0)项目子集,则closure(I)定义如下
    • Iclosure(I)I \subset closure(I)
    • {BγAαBβclosure(I)}closure(I)\{B \rightarrow \cdot \gamma | A \rightarrow \alpha \cdot B \beta \in closure(I)\} \subset closure(I)
    • 重复上一个步骤,直到closure(I)不再扩大为止

LR(0)识别或前缀DFA M构造方法#

  • 设文法G=(VN,VT,P,S)G=(V_N, V_T, P, S),且已等价改写成文法G’的增广文法,则识别或前缀DFA M=(K,Σ,f,S,Z)M = (K, \Sigma, f, S, Z)
    • KρK \subseteq \rho(LR(0)项目集)
    • Σ=VNVT\Sigma = V_N \cup V_T
    • f(I,X)=closure(Move(I,X)),IK,XΣf(I, X) = closure(Move(I, X)), I \in K, X \in \Sigma
    • S=closure(SS)S = closure(S' \rightarrow \cdot S)
    • Z={qqK,q含有归约项目}Z = \{q | q \in K, q含有归约项目\}
  • 文法G的识别或前缀DFA M的状态集称为文法G的LR(0)项目集规范族

LR(0)分析表的构造#

  • 对每一个LR(0)项目,依据下列情况分别填分析表
    • 移进项目AαaβIK,f(IK,a)=IjA \rightarrow \alpha \cdot a \beta \in I_K, f(I_K, a) = I_j,置ACTION[k, a]为SjS_j
    • 归约项目AαIK,AαA \rightarrow \alpha \cdot \in I_K, A \rightarrow \alpha标号为i,置ACTION[k, a]为rir_i, a(VT{})a \in (V_T \cup \{ \sharp \})
    • 接受项目SSIKS' \rightarrow S \cdot \in I_K,则置ACTION[k, #]为acc
    • 待约项目AαXβIKA \rightarrow \alpha \cdot X \beta \in I_K,如果f(Ik,X)=Ij,XVNf(I_k, X) = I_j, X \in V_N,置GOTO[k, X]为j
  • 凡以上后没能填入分析表元素ACTION和GOTO的,置为ete_t(t为错误编号)
  • 移进-归约冲突:项目集中同时出现移进和归约项目
    • AαaβA \rightarrow \alpha \cdot a \beta
    • BγB \rightarrow \gamma \cdot
  • 归约-归约冲突:项目集中同时出现多个归约项目
    • AαA \rightarrow \alpha \cdot
    • BβB \rightarrow \beta \cdot

LR(0)文法#

  • 如果文法G的LR(0)项目集规范族不存在移进-归约冲突或归约-归约冲突的项目集,则文法G为LR(0)文法
    • 如果文法G是LR(0)文法,则G可采用LR(0)分析法
    • 如果文法G是LR(0)文法,则G是无二义性的
  • LR(0)分析表ACTION表中每格仅会是移进、归约和报错三种动作之一

SLR(1)分析#

  • 不是LR(0)文法时,可以采用简单地向后看1个输入符号的方法,解决移进-归约冲突或归约-归约冲突
  • 假设文法LR(0)项目集规范族有一个并存移进-归约冲突和归约-归约冲突的项目集IK={Aαaβ,Aγ,Bδ,...}I_K = \{ A \rightarrow \alpha \cdot a \beta, A \rightarrow \gamma \cdot, B \rightarrow \delta \cdot, ...\}
    • {a}FOLLOW(A)FOLLOW(B)=\{a\} \cap FOLLOW(A) \cap FOLLOW(B) = \varnothing,则冲突可解决
      • 如果下一符号ai移入符号集a_i \in 移入符号集,则移入
      • 如果下一符号aiFOLLOW(A)a_i \in FOLLOW(A),则用AγA \rightarrow \gamma归约
      • 如果下一符号aiFOLLOW(B)a_i \in FOLLOW(B),则用BδB \rightarrow \delta归约
  • SLR(1)分析表的构造
    • 移进项目AαaβIK,f(IK,a)=IjA \rightarrow \alpha \cdot a \beta \in I_K, f(I_K, a) = I_j,置ACTION[k, a]为SjS_j
    • 归约项目AαIK,AαA \rightarrow \alpha \cdot \in I_K, A \rightarrow \alpha标号为i,置ACTION[k, a]为rir_i, aFOLLOW(A)a \in FOLLOW(A)
    • 接受项目SSIKS' \rightarrow S \cdot \in I_K, 则置ACTION[k, #]为acc
    • 待约项目AαXβIKA \rightarrow \alpha \cdot X \beta \in I_K,如果f(IK,X)=Ij,XVNf(I_K, X)=I_j, X \in V_N,置GOTO[k, x]为j

SLR(1)文法#

  • 设文法G的LR(0)项目集规范族C中任意含有m个移进项目和n个归约项目的冲突项目集IKI_K的一般形式为
  • IK={A1α1a1β1,A2α2a2β2,...,Amαmamβm,B1γ1,B2γ2,...,Bnγn,...}I_K = \{A_1 \rightarrow \alpha_1 \cdot a_1 \beta_1, A_2 \rightarrow \alpha_2 \cdot a_2 \beta_2, ..., A_m \rightarrow \alpha_m \cdot a_m \beta_m, B_1 \rightarrow \gamma_1 \cdot, B_2 \rightarrow \gamma_2 \cdot, ..., B_n \rightarrow \gamma_n \cdot, ... \}
  • 如果移进符号集{a1,...,am}\{a_1, ..., a_m\}FOLLOW(B1),...,FOLLOW(Bn)FOLLOW(B_1), ..., FOLLOW(B_n)两两相交均为空集,则文法G称为SLR(1)文法
  • SLR(1)文法无二义性
  • LR(0)文法,一定也是SLR(1)文法

LR(1)分析#

  • SLR(1)分析法存在的问题
    • SLR只是简单地考察下一个输入符号b是否属于与归约项目AαA \rightarrow \alpha相关联的FOLLOW(A),但bFOLLOW(a)b \in FOLLOW(a)只是归约α\alpha的一个必要条件,而非充分条件
    • 对于产生式AαA \rightarrow \alpha的归约,在不同的使用位置,A会要求不同的后继符号
    • 在特定位置,A的后继符集合是FOLLOW(A)的子集
  • LR(1)的基本思想
    • AαBβIiA \rightarrow \alpha \cdot B \beta \in I_i,则BγIiB \rightarrow \cdot \gamma \in I_i
    • 在状态IjI_j:下个符号属于FOLLOW(B)而不属于FIRST(β\beta),即便归约到B,此路也不通
    • FIRST(β)FIRST(\beta)作为产生式BγB \rightarrow \gamma归约时向前查看的符号集合(向前搜索符号集,前瞻符号集),代替SLR(1)分析法中的FOLLOW(B),并将向前搜索符号集也放在LR(0)项目的后面
    • [Aαβ,a][A \rightarrow \alpha \cdot \beta, a],a称为向前搜索夫(展望符/前瞻符)——LR(1)项目

LR(1)项目#

  • 附加搜索符(VT{})(\in V_T \cup \{ \sharp \})的LR(0)项目称为LR(1)项目
    • [LR(0)项目,搜索符]
    • LR(0)项目-LR(1)项目的心
    • 对于同心的LR(1)项目简记:[LR(0)项目,搜索符1|搜索符2|…|搜索符m]
    • “搜索符1|…|搜索符m”称为搜索集
  • 形如[Aα,a][A \rightarrow \alpha \cdot, a]的项表示仅在下一个输入符号等于a时才可以按照AαA \rightarrow \alpha进行归约
  • 这样的a的集合总是FOLLOW(A)的子集,通常是真子集
  • 搜索符的继承与传播
    • [AαBβ,a],又有BγP,[Bγ,b],bFIRST(βα)[A \rightarrow \alpha \cdot B \beta, a], 又有B \rightarrow \gamma \in P, \Rightarrow [B \rightarrow \cdot \gamma, b], b \in FIRST(\beta \alpha)
    • β+ϵ\beta \Rightarrow^+ \epsilon时,此时b=a叫继承的搜索符,否则叫自生的搜索符

LR(1)项目集族的构造#

  • 设I是文法G的LR(1)项目子集,则MOVE1(I, X)定义如下
    • Move1(I,X)={[AαXβ,a][AαXβ,a]I}Move1(I, X) = \{[A \rightarrow \alpha X \cdot \beta, a] | [A \rightarrow \alpha \cdot X \beta, a] \in I\}
  • 设I是文法G的LR(1)项目子集,closure1(I)定义如下
    • Iclosure1(I)I \subset closure1(I)
    • {[Bγ,b][AαBβ,a]closure1(I),bFIRST(βa)}closure1(I)\{[B \rightarrow \cdot \gamma, b] | [A \rightarrow \alpha \cdot B \beta, a] \in closure1(I), b \in FIRST(\beta a)\} \subset closure1(I)
    • 重复上一个步骤,直到closure1(I)不再扩大为止

LR(1)分析表的构造#

LR(1)识别活前缀的DFA M构造#

  • 文法G=(VN,VT,P,S)G = (V_N, V_T, P, S),等价改写成文法G=(VN{S},VT,P{SS},S)G'=(V_N \cup \{S'\}, V_T, P \cup \{S' \rightarrow S\}, S'),其中Vn{S}=V_n \cup \{S'\} = \varnothing,则识别或前缀DFA M=(K,Σ,f,S,Z)M=(K, \Sigma, f, S, Z),其中
    • KρK \subseteq \rho(LR(1)项目集)
    • Σ=VNVT\Sigma = V_N \cup V_T
    • f(I,X)=closure(Move1(I,X)),IK,XΣf(I, X) = closure(Move1(I, X)), I \in K, X \in \Sigma
    • S=closure1([SS,])S = closure1([S' \rightarrow \cdot S, \sharp])
    • Z={qqK,q含有归约项目}Z = \{q | q \in K, q含有归约项目\}
  • 文法G的LR(1)识别或前缀DFA M的状态集称为文法G的LR(1)项目集规范族

LR(1)分析表的构造#

  • 文法G的LR(1)项目集规范族C={I0,...,In}C=\{I_0, ..., I_n\}
  • 对C中每个IiI_i构造得到状态i。状态i的语法分析动作按照下面的方法决定
    • if[Aαaβ,b]Ii&&f(Ii,a)=IjthenACTION[i,a]=Sjif [A \rightarrow \alpha \cdot a \beta, b] \in I_i \quad \And \And \quad f(I_i, a) = I_j \quad then\quad ACTION[i, a] = S_j
    • if[AαBβ,b]Ii&&f(Ii,B)=IjthenGOTO[i,B]=jif [A \rightarrow \alpha \cdot B \beta, b] \in I_i \quad \And \And \quad f(I_i, B) = I_j \quad then\quad GOTO[i, B] = j
    • if[Aα,a]Ii&&A!=SthenACTION[i,a]=rjif [A \rightarrow \alpha \cdot, a] \in I_i \quad \And \And \quad A != S' \quad then\quad ACTION[i, a] = r_j(j是产生式AαA \rightarrow \alpha的编号)
    • if[SS,]IithenACTION[i,]=accif [S' \rightarrow S \cdot, \sharp ] \in I_i \quad then\quad ACTION[i, \sharp ] = acc
  • 所有没有定义的条目都设置为ete_t,t是错误编号

LR(1)的冲突#

  • 设文法G的LR(1)项目集规范族C中任意含有m个移进项目和n个归约项目的冲突项目集IKI_K的一般形式为IK={[A1α1a!β1,S1],...,[Amαmamβm,Sm],[B1γ2,S1],...,[Bmγn,Sn],...},Ai,BjVn,aiVT,αi,βj,γj(VNVT),Si,SjI_K=\{[A_1 \rightarrow \alpha_1 \cdot a_! \beta_1, S_1 '], ..., [A_m \rightarrow \alpha_m \cdot a_m \beta_m, S_m'],[B_1 \rightarrow \gamma_2 \cdot , S_1], ..., [B_m \rightarrow \gamma_n \cdot, S_n], ...\}, A_i, B_j \in V_n, a_i \in V_T, \alpha_i, \beta_j, \gamma_j \in (V_N \cup V_T)*, S_i', S_j为搜索集
  • 如果移进符号集{a1,a2,...,αm}\{a_1, a_2, ..., \alpha_m\}和搜索集S1,...,SnS_1, ..., S_n两两相交均为空集,则文法G称为LR(1)文法
  • 结论
    • 如果文法G是LR(1)文法,则G可采用LR(1)分析法
    • 如果文法G是LR(1)文法,则G是无二义性的
    • 如果文法G是SLR(1)文法,则G一定是LR(1)

LALR(1)分析#

  • 如果采用同心项目集合并方法,进行合并后的文法G的LR(1)项目集规范组,没有LR(1)项目冲突,则称文法G为LALR(1)文法
  • 结论
    • 如果文法G是LALR(1)文法,则G可采用LALR(1)分析法
    • 如果文法G是LALR(1)文法,则G是无二义性的
    • 如果文法G是LALR(1)文法,则G一定是LR(1)
  • 特点
    • 形式上与LR(1)相同
    • 大小上与LR(0)/SLR相当
    • 分析能力介于SLR和LR(1)二者之间LR(0)<SLR(1)<LALR(1)<LR(1)LR(0) < SLR(1) < LALR(1) < LR(1)
    • 合并后的向前搜索符集合仍为FOLLOW集的子集

二义性文法的应用#

  • 特点
    • 任何一个二义性文法都不是LR的
    • 某些类型的二义性文法在语言的描述和实现中很有用

语法制导的语义计算#

基于属性文法的语义计算#

属性文法#

  • 在基础文法的基础上
    • 为每个文法符号(终结符或非终结符)配备若干相关属性,代表与文法符号相关信息,如类型、值、代码片段、符号表内容等
    • 对于文法的每个产生式都配备了一组属性的语义规则(动作),对属性进行计算和传递
  • 翻译模式(SDT): 明确语义计算的时机
    • 把{语义动作}插入到产生式右部恰当位置,按照分析的进程执行相应的语义计算

综合属性#

  • 计算规则: 通过自身属性和产生式右部属性值进行计算
  • 语法规则: 产生式左部符号的综合属性由自身的产生式右部符号的属性计算得出
  • 语法树: 父节点的综合属性由子节点的属性和父节点自身的属性计算得出
  • 自下而上传递信息
非终结符的综合属性#
  • 产生式的左部
  • 产生式的右部
终结符的综合属性#
  • 终结符可以具有综合属性
  • 由词法分析器提供
  • 固有属性
  • 没有计算规则

继承属性#

  • 计算规则: 根据自身属性和产生式自身左边符号的属性计算
  • 语法规则: 通过产生式左部符号的属性和右部符号的属性计算更右部符号的继承属性
  • 语法树: 根据父节点和兄长节点的属性计算子节点的继承属性
  • 自上而下传递信息

语义规则#

  • 右部符号的继承属性和左部符号的综合属性
    • 由本产生式提供计算规则。且规则中只能使用本产生式中文法符号的属性
  • 左部符号的继承属性和右部符号的综合属性
    • 由其它产生式提供语义规则来计算
  • 内容
    • 属性计算
    • 静态语义检查
      • 类型检查、控制流检查、唯一性检查、关联名检查
    • 符号表操作
    • 代码生成
    • 给出错误信息
    • 执行其它动作

属性的计算及注释分析树(含继承属性)#

  • 在语法树中,一个节点的继承属性由其父节点、其兄弟节点和其本身的某些属性确定
  • 不能在LR分析的同时构造带注释的语法树:先构造语法树,再计算属性

语义计算顺序#

  • 属性依赖
    • 对应于每个产生式AαA \rightarrow \alpha都有一套与之相关联的语义规则,每条规则的形式位b:=f(c1,...,cK)b := f(c_1, ..., c_K)
    • 属性b依赖于属性c1,...,ckc_1, ..., c_k
      • b是A的一个综合属性并且c1,...,ckc_1, ..., c_k是产生式右部文法符号的属性
      • b是产生式右边某个文法符号的一个继承属性并且c1,...,ckc_1, ..., c_k是A或产生式右边文法符号的属性
    • 过程调用也当属性
  • 语义规则建立了属性之间的依赖关系,再堆语法分析树节点的一个属性求值之前,必须首先计算出这个属性值所依赖的所有属性值

依赖图#

  • 依赖图是一个描述了分析树中节点属性间依赖关系的有向图
  • 分析树中每个标号为X的节点的每个属性a都对应着依赖图中的一个节点
  • 如果属性X.a的值依赖于属性Y.b的值,则依赖图中有一条从Y.b的节点指向X.a的节点的有向边
// 依赖图的构造算法
for 语法树中每一节点n do
for 节点n的文法符号的每一个属性a do
为a再依赖图中建立一个节点;
for 语法树中每一个节点n do
for 节点n所用产生式对应的每一个语义规则
b := f(c1, ..., ck) do
for i:= 1 to k do
从ci节点到b节点构造一条有向边
  • 一个依赖图的任何拓扑排序都给出一个语法树中节点的语义规则计算的有效顺序
  • 仅有综合属性的SDD,可以按照任何自底向上的顺序计算它们的值
  • 同时具有继承属性和综合属性的SDD,不能保证存在一个顺序来对各个节点上的属性进行求值
  • 给定一个SDD,很难判定是否存在一个其属性依赖图不含回路的分析树
  • 实践中,只使用能够保证对每棵语法分析树都存在一个属性求值顺序的SDD,因为它们不允许产生带有环的依赖图
  • 可以在自顶向下或自底向上语法分析的同时实现语义计算

S-属性文法与L-属性文法#

  • S-属性文法
    • 仅含有综合属性的属性文法
    • S属性文法可以在自底向上的语法分析过程中实现
  • L-属性文法
    • 当且仅当它的每个属性要么是一个综合属性,要么是满足如下条件的继承属性
    • 假设存在一个产生式AX1...XnA \rightarrow X_1...X_n,其右部符号Xi(1<=i<=n)X_i(1<=i<=n)的继承属性仅依赖于下列属性
      • A的继承属性
      • 产生式中XiX_i左边的符号X1,...,Xi1X_1,...,X_{i-1}的属性
      • XiX_i自身的属性,且XiX_i的全部属性不能再依赖图中形成环路
    • S-属性文法是L-属性文法的一个特例

基于S-属性文法的语义计算#

  • 采用LR分析,每当归约时,执行语义计算
  • e.g.
    • 抽象语法AST的构造
    • 类型检查

基于L-属性文法的语义计算#

树遍历法#

语法分析构建语法树,再深度优先,遍历语法树完成语义计算

while 还有未被计算的属性 do
VisitNode(S) /*S是开始符号*/
procedure VisidNode(N: Node);
begin
if N是一个非终结符 then /*假设其产生式为N->X1...Xm*/
for i:=1 to m do
if xi in Vn then /*Xi是非终结符*/
begin
计算Xi的所有能够计算的继承属性
VisitNode(Xi)
end;
计算N的所有能够计算的综合属性
end

基于翻译模式的语义计算#

翻译模式#

  • 把语义规则(也称语义动作),用花括号{}扩起来,插入到产生式右部的合适位置上。进一步细化了语义计算的时机。
  • S-翻译模式:仅含综合属性,语义动作集只有产生式u右端的末尾。常采用LR的自底向上的分析法,和S属性文法类似
  • L-翻译模式:包含综合属性,还可以包含继承属性
  • 满足下列条件
    • 产生式右部符号的继承属性,其语义计算必须位于该符号前,且其语义动作不访问右边符号的属性
    • 产生式左部符号的综合属性只有在它所引用的所有属性都计算出来后才能计算。属性计算通常放在产生式右端的末尾
    • 继承属性只能依赖于兄长的属性、父亲的继承属性、自身的属性
    • 属性间依赖图不能形成环路

基于S-翻译模式的语义计算#

  • 与S-属性文法类似
    • 基础文法是LR的:自底向上计算
    • 基础文法是LL的:自顶向下计算
  • 在LR分析的同时计算综合属性
  • 在分析栈中保存语法符号、状态和有关的综合属性值
  • 每当进行归约时,计算属性
  • 在LR分析框架的基础上,在分析栈增加附加域:语义栈(属性栈)
  • 将语义规则(动作)改写称具体可执行的栈操作

基于L-翻译模式的自顶向下计算#

  • 递归下降翻译器(语义计算分析)程序
    • 分析成语由一组递归子程序(函数)组成,每个非终结符对应一个子程序
    • 如果非终结符有多个产生式,根据当前符号和SELECT集决定用哪个产生式
    • 从左到右分析符号串,遇到终结符就匹配,遇到非终结符就调用相应的分析子程序
    • 要求基础文法是LL(1)的
  • 递归版
    • 检查基础文法是否是LL(1)文法
    • 构造LL(1)分析子程序
    • 对LL(1)递归下降分析器进行改造
      • 非终结符A的递归子程序 -> 函数 A.v = parseA(A.i)
      • A的每个继承属性对应该函数的一个形参,函数的返回值是A的综合属性值
      • 对出现在A产生式右部中的每个文法符号的每个属性都设置一个局部变量
      • 对应每个语义动作,将其代码赋值到语法分析器,并把对属性的引用改为对相应变量的引用
  • 非递归的自顶向下计算
    • 在LL(1)分析法基础上,增加属性列表栈
    • 从文法其实符号最左推导,根据下一符号和SELECT集选择产生式
    • 产生式右部和语义动作逆序进展
    • 综合属性计算得出结果后退栈

基于L-翻译模式的自底向上计算#

  • 从翻译模式中删除嵌入在产生式中间的语义动作
    • 把嵌入在产生式中的每个语义动作用不同的非终结符(如M,N,…代替)
    • 加入新的产生式Mϵ,...M \rightarrow \epsilon, ...
    • 在把原来的语义动作放在产生式Mϵ,...M \rightarrow \epsilon,...的末尾
    • 这样就不再有继承属性,相当于S-属性文法,可在自底向上的语法分析过程中完成语义计算
  • 继承属性的求值结果以综合属性值存放在语义栈中,对继承属性的访问变成对语义栈中某个综合属性的访问
  • 模拟继承属性的计算
  • 适用于所有的LL(1)文法和大部分LR文法

方法#

  • 改造翻译模式(使继承属性仅包含复写规则)
  • 将语义规则转换为栈操作
  • 根据基础文法,构造LR自动机
  • 根据LR自动机,构造LR分析表
  • 根据LR分析表,以及栈操作指令,分析
    • S操作的时候不做任何动作
    • 归约操作的时候,首先执行语义动作,然后再做弹栈操作

分析和翻译的自动生成工具-bison#

静态语义分析和中间代码生成#

  • 语义分析的任务
    • 语法上正确的程序,语义不一定正确
    • 有些错误只能在运行时才能发现,称为动态语义错误
    • 编译阶段只能对结构上正确的源程序进行静态语义检查
      • 类型检查
      • 唯一性检查
      • 控制流检查
      • 名字的上下文相关性检查
    • 静态语义是上下文有关的
    • 收集源程序中的符号信息,建立符号表

符号表#

  • 符号表的作用
    • 用来存放有关标识符(符号)的属性(语义)信息
    • 在编译的不同阶段都会用到
    • 作为目标代码生成阶段地址分配的依据
    • 用来体现作用域与可见性信息
    • 静态语义检查(名字检查/类型/控制流/唯一性)的依据
  • 符号的常见属性
    • 符号名
    • 类别:常量、变量、过程/函数、类的名称
    • 类型(种别): int, float, void, 过程/函数的返回类型等,决定存储格式和操作
    • 存储类别和存储分配信息,静态/动态,堆区/栈区,存储单元的代销,偏移量
    • 符号的作用域信息
    • 其它属性
      • 数组内情向量
      • 记录结构的成员信息
      • 函数及过程的形参
  • 符号表的操作
    • 创建符号表:进入一个作用域
    • 查询表项(名字/信息):引用标识符
      • 引用性出现 if (index < 100)
    • 插入表项:遇到新的标识符声明时
      • 定义行出现 int index;
    • 修改表项:获得新的语义值信息时
    • 删除表项:不可见或不再需要
    • 释放符号表空间:结束前或退出一个作用域
  • 符号表的数据结构
    • 一般的线性表
      • 数组,链表等
      • 有序表(红黑树等)
      • 二叉搜索树
      • 哈希表
  • 作用域与可见性
    • 嵌套的作用域(nested scopes)
    • 开作用域与闭作用域(相应于程序中特殊点)
      • 该点所在的作用域为当前作用域
      • 当前作用域与包含它的程序单元所构成的作用域称为开作用域(open scopes),及嵌套重叠的作用域
      • 不属于开作用域的作用域称为闭作用域(close scopes)
    • 常用的可见性规则(visibility rules)
      • 在程序的任何一点,只有在该点的开作用域中声明的名字才是可访问的
      • 若一个名字在多个开作用域中被声明,则引用该名字最近的声明作为该引用的解释
      • 新的声明只能出现在当前作用域
  • 作用域与符号表组织
    • 单符号表:所有作用域共用一个全局符号表
      • 所有嵌套的作用域公用一个全局符号表
      • 每个作用域有一个作用域号
      • 仅记录开作用域中的符号
      • 当某个作用域称为闭作用域时,从符号表中删除该作用域中所声明的名字
    • 多符号表:每个作用域都有各自的符号表
      • 每个作用域都有各自的符号表
      • 维护一个符号表的作用域栈,每个开作用域对应栈中的一个入口,当前的开作用域出现在该栈的栈顶
      • 当一个新的作用域开放时,新符号表将被创建,并将其入栈
      • 在当前作用域称为闭作用域时,从栈顶弹出相应的符号表

静态语义分析#

  • 静态语义:刻画程序在静态一致性或完整性方面的特征;仅当程序通过了静态语义检查,才能完成后续的中间代码生成和目标代码优化
  • 动态语义:刻画程序执行时的行为。比如除数为0,数组越界等错误,需要生成相应代码
  • 静态语义分析的主要任务
    • 类型检查(type check): 检查每个操作是否遵守语言类型系统的定义
    • 名字的作用域(scope)分析: 建立名字的定义和使用之间的联系
    • 控制流检查(control flow check): 控制流语句必须使控制转移到合法的地方
    • 唯一性检查(uniqueness check): 很多场合要求对象只能被定义一次
    • 名字的上下文相关性检查(name-related check)
  • 方法与技术: 基于语法制导的翻译模式
    • 语法制导——简单的类型检查系统(cheker)
      • 实现类型系统
      • 程序的结构是否匹配上下文所期望的类型
      • 为IR生成收集/建立必要的类型信息
  • 类型表达式
    • 基本类型是类型表达式
    • 将类型构造夫(type constructor)作用于类型表达式可以构建新的类型表达式
      • 数组构造array(I, T)
        • 若T是类型表达式,I是整数域,则array(I, T)也是类型表达式
      • 指针构造符pointer(T)
        • 若T是类型表达式,则pointer(T)是类型表达式,它表示指向类型为T的对象的指针
      • 积类型构造符T1 * T2 = <T1, T2>, <T1, ..., Tn>
        • 若T1,T2是类型表达式,则T1 * T2 = <T1, T2>为类型表达式
        • <T1, ..., Tn>, n> = 0;积类型表达式
        • <>, n=0
      • 过程类型构造符fun(T)
        • foo(int a; float b),则fun(T), T=<int, float>
        • 函数类型表达式,LLVM: i32(i32); {i32, i32} (i32)
      • 定义新的类型构造符

中间代码生成#

  • IR: 源程序的不同表示形式,也称为中间表示
    • 源程序和目标语言之间的桥梁(前端和后端),避开二者之间较大的语义跨度,使编译程序的逻辑结构更加简单明确。
    • 有利于进行于目标机器无关的优化
    • 有利于编译程序的重定向(移植)
  • 常见的中间表示的形式
    • TAC(Tree-Address code, 三地址码,四元式)
    • AST(Abstract Syntax Tree, 抽象语法树), DAG(有向无环图)
    • SSA(Static Single Assignment forn, 静态单赋值形式)
    • LLVM IR
    • P-code(特别用于Pasal语言实现)
    • Bytecode(java编译器的输出,java虚拟机的输入)
  • LLVM IR
    • LLVM IR是一种低级语言
    • 提供关于程序的高级信息,支持复杂分析与变换
    • 低级low level: 足以表示任意程序,允许被广泛优化
    • RISC风格的三地址码
    • SSA形式+无限的虚拟寄存器
    • 简单、低级的控制流结构: 指令: branch, return, unwind, invoke
    • load/store指令带类型化指针
  • 三地址码
    • 比较接近汇编语言的表示
    • AST/DAG的线性表达,更低级别上
    • TAC与四元式
    • TAC与SSA
    • TAC与LLVM IR
  • 常用的三地址码
    • 赋值语句x := y op z (op代表二元算数/逻辑运算) (op, y, z, x)
    • 赋值语句 x := op y (op代表一元运算) (op, y, -, x)
    • 复写语句 x := y (y的值赋值给x) (=, y, -, x)
    • 标号语句L: (定义标号L)
    • 无条件跳转语句 goto L (无条件跳转至标号L) (jmp, -, -, L)
    • 条件转移指令 if x goto L (条件为true时跳转值标号L) (jnz, x, -, L)
    • 条件跳转语句 if x rop y goto L (rop代表关系运算) (jrop, x, y, L) e.g. je/jne/jg/jge/jl/jle
    • 过程调用语句序列: p(x1, ..., xn), param x1, …, param xn, call p, n
    • 过程返回语句 return [y] (y可选,存放返回值) (return, y, -, -)
    • 下标赋值语句 x := y[i]x[i] := y (=[], y, i, x)和([]=, i, y, x)
    • 指针赋值语句 x := &y, x := *y, *x := y (=&, y, -, x), (=, y, -, x), (=, y, -, x)

赋值语句的翻译#

Sid:=AS \rightarrow id := A

  • 语义属性
    • id.place: id对应的存储为止
    • A.place: 用来存放的A的值的存储单元的地址
    • A.code: 对A求值的TAC语句序列
    • S.code: S的TAC语句序列
  • 语义函数/过程
    • gen(): 生成一条TAC语句
    • newtemp(): 在符号表中新建一个从未使用过的名字,并返回地址
    • ||: TAC语句的连接运算
S -> id := A { S.code := A.code || gen(id.place ':=' A.place) }
A -> id { A.place := id.place; A.code := "" }
A -> int { A.place := newtemp; A.code := gen(A.place ':=' int.val) }
A -> real { A.place := newtemp; A.cpde := gen(A.place ':=' real.val) }
A -> A1 + A2 { A.place := newtemp; A.code := A1.code || A2.code || gen(A.place ':=' A1.place '+' A2.place) }
A -> A1 * A2 { A.place = newtemp; A.code := A1.code || A2.code || gen(A.place ':=' A1.place '*' A2.place) }
A -> -A1 { A.place := newtemp; A.code = A1.code || gen(A.place ':=' 'uminus' A1.place) }
A -> (A1) { A.place := A.place; A.code =:= A1.code }

说明语句的翻译: L-翻译模式#

  • 语义属性
    • id.name: id的词法名字(符号表中的名字)
    • T.type: 类型属性
    • T.width,V.width: 数据宽度(字节数)
    • V.offset,L.offset: 列表中第一个变量的偏移地址
    • L.type: 变量列表呗申明的类型
    • L.width: 变量列表呗申明类型所栈的字节数
    • L.num: 变量列表中变量的个数
  • 语义函数/过程
    • enter(id.name, t, o): id.name 表项的type域置为t,offset域置为o
V -> T { L.type := T.type; L.offset := V.offset; L.width := T.width; } L; {V1.offset := V.offset + L.num * T.width; } V1 { V.type := make_product_3(T.type, L.num, V1.type); V.width := V1.width + L.num * T.width }
V -> \epsilon { V.type := <>; V.width := 0 }
T -> boolean { T.type := bool; T.width := 1 }
T -> int { T.type := int; T.width := 4 }
T -> real { T.type := real; T.width := 8 }
T -> array[num] of T1 { T.type := array(num.lexval, T1.type); T.width := num.val * T1.width }
T -> ^ T1 { T.type := pointer(T1.type); T.width := 4 }
L -> { L1.type := L.type; L1.offset := L.offset; L1.width := L.width; } L1, id { enter(id.name, L.type, L.offset + L1.num * L.width); L.num := L1.num + 1 }
L -> id { enter(id.name, L.type, L.offset); L.num := 1 }

数组#

  • 设A为n维数组,按行存放,每个元素宽度为w
    • IiI_i为第i维的下界
    • uiu_i为第i维的上界
    • nin_i为第i维可取值的个数ni=uiIi+1n_i = u_i - I_i + 1
    • base为A的第一个元素的相对地址
  • 元素A[i1,...,in]相对地址公式base+((((i1×n2+i2)×n3+i3)...)×nn+in)w((((I1×n2+I2)×n3+I3)...+In1)×nn+In)×wbase + ((((i_1 \times n_2 + i_2) \times n_3 + i_3) ... ) \times n_n + i_n) * w - ((((I_1 \times n_2 + I_2) \times n_3 + I_3)...+I_{n-1}) \times n_n + I_n) \times w
  • .arr: 数组名
  • .type: 类型
  • .addr: 元素的偏移地址
  • .code: TAC代码
S -> id := A {S.code := A.code || gen(id.place ':=' A.place) }
| L := A { S.code := A.code || gen(L.arr '[' addr ']' ':=' A.place) }
A -> L { A.place := newtemp; A.code := L.code || gen(A.place ':=' L.arr '[' L.addr ']') }
L -> id[A] { L.arr := id.name; L.type :=get_type(id).elem; L.addr := newtemp; L.code := A.code || gen(L.addr ':=' A.place '*' L.type.width) }
| L1[A] { L.arr := L1.arr; L.type := L1.type.elem; t := newtemp; L.addr := newtemp; L.code := L1.code || A.code || gen(t ':=' A.place '*' L.type.width) || gen(L.addr ':=' L1.addr '+' t) }

布尔表达式#

  • 用途
    • 用于逻辑盐酸,计算逻辑值
    • if/while/for语句的控制条件
  • 计算
    • 根据真值表直接计算;短路法
  • 带优化的翻译法(短路,避免不必要的求值)
    • E1 or E2解释成if E1 then true else E2
    • E1 and E2解释成if E1 then E2 else false
    • note E解释成if E then false else true
    • 被很多高级语言采用,用于条件表达式中布尔表达式的计算
  • 属性与语义计算的涉及
    • E.code, E.place
    • newtemp, gen()
  • 语义函数newlabel,返回一个新的符号标号
  • 对布尔表达式E,设置两个继承属性
    • E.true是E为‘真’时控制流转向的标号
    • E.false是E为‘假’时控制流转向的标号
E -> { E1.true := E.true; E1.false := newlabel } E1 or { E2.true := E.true; E2.false := E.false } E2 { E.code := E1.code || gen(E1.false ':') || E2.code }
E -> { E1.treue := newlabel; E1.false := E.false } E1 and { E2.true := E.true; E2.false := E.false } E2 { E.code := E1.code || gen(E1.true ':') || E2.code }
E -> not { E1.true := E.false; E1.false := E.true } E1 { E.code := E1.code }
E -> ({ E1.true := E.true; E1.false := E.false } E1) { E.code := E1.code }
E -> id1 rop id2 { E.code := gen ('if' id1.place rop.op id2.place 'goto' E.true) || gen('goto' E.false) }
E -> true { E.code := gen('goto' E.true) }
E -> false { E.code := gen('goto' E.false) }

and的时候修改E1.true,or的时候修改E1.false

控制语句的翻译#

  • 继承属性: S.next
S -> if { E.true := newlabel; E.false := S.next } E then { S1.next := S.next } S1 { S.code := E.code || gen(E.true ':') || S1.code }
S -> if { E.true := newlabel; E.false := newlabel} E then { S1.next := E.next } S1 else { S2.next := S.next } S2 { S.code := E.code || gen(E.true ':') || S1.code || gen('goto' S.next) || gen(E.flase ':') || S2.code}
S -> while { E.true := newlabel; E.false := S.next } E do { S1.next := newlabel } S1 { S.code := tgen(S1.next ':') || E.code || gen(E.true ':') || S1.code || gen('goto' S1.next) }
S -> { S1.next := newlabel } S1; { S2.next := S.next } S2 { S.code := S1.code || gen(S1.next) ':' || S2.code }
P -> D; { S.next := newlabel } S { gen(S.next ':') }

含有break语句的翻译模式#

P -> D; { S.next := newlabel; S.break := newlabel } S { gen(S.next ':') }
S -> if { E.true := newlabel; E.false := S.next } E then { S1.next := S.next; S1.break := S.break } S1 { S.code := e.code || gen(E.true ':') || S1.code }
S -> if { E.true := newlabel; E.false := newlabel } E then { S1.next := S.next; S1.break := S.break } S1 else { S2.next := S.next; S2.break := s.break } S2 { S.code := e.code || gen(E.true ':') || S1.code || gen('goto' S.next) gen(E.false ':') || S2.code }
S -> while { E.true := newlabel; E.false := S.next } E do { S1.next := newlabel; S1.break := S.next } S1 { S.code := gen(S1.next ':') || E.code || gen(E.true ':') || S1.code || gen('goto' S1.next) }
S -> { S1.next := newlabel; S1.break := S.break } S1; { S2.next := S.next; S2.break := S.break } S2 { S.code := S1.code || gen(S1.next ':') || S2.code }
S -> break; { S.code := gen('goto' S.break) }

拉链与代码回填#

生成一个跳转指令时,暂时不指定改跳转指令的目标标号。这样的指令都被放入由跳转指令组成的列表中。同一个列表中的所有跳转指令具有相同的目标标号。等到能够确定正确的目标标号时,才去填充这些指令的目标标号

  • 属性设计
  • E.truelist: “真链”,链表中的元素表示一系列跳转语句的地址,这些跳转语句的目标标号是体现布尔表达式E为“真”的标号
  • E.falselist: “假链”,链表中的元素表示一系列跳转语句的地址,这些跳转语句的目标标号是体现布尔表达式E为假的标号
  • S.nextlist: “next链”,链表中的元素表示一系列跳转语句的地址,这些跳转语句的目标是S之后的下条TAC语句的标号
  • S.breaklist: “break链”, 跳出直接包围S的while语句的下条TAC语句标号
  • M.gotostm: 处理到M时下一条待生成语句的标号
  • 语义计算
    • makelist(i): 创建一个只有节点i的列表,i是一条目标TAC语句的标号
    • merge(p1, p2): 连接两个链表p1和p2,将p2链接到p1后面,返回合并后的链首
    • backpatch(p, i): 将链表p中每个元素所指向的跳转语句的标号置为i
    • nextstm: 下一条TAC语句的地址
    • emit(...): 输出一条TAC语句,并使nextstm加1

采用拉链与回填技术的布尔表达式的翻译#

E -> E1 or M E2 { backpatch(E1.falselist, M.gotostm); E.truelist := merge(E1.truelist, E2.truelist); E.falselist := E2.falselist }
E -> E1 and M E2 { backpatch(E1.truelist, M.gotostm); E.falselist := merge(E1.falselist, E2.falselist); E.truelist := E2.truelist }
E -> not E1 { E.truelist := E1.falselist; E.falselist := E1.truelist }
M -> \epsilon { M.gotostm := nextstm }
E -> (E1) { E.truelist := E1.truelist; E.falselist := E1.falselist }
E -> id1 rop id2 { E.truelist := makelist(nextstm); E.falselist := makelist(nextstm+1); emit('if' id1.place rop.op id2.place 'goto'); emit('goto') }
E -> true { E.truelist := makelist(nextstm); emit('goto') }
E -> false { E.falselist := makelist(nextstm); emit('goto') }

分支语句的翻译#

S -> if E then M S1 { backpatch(E.truelist, M.gotostm); S.nextlist := merge(E.falselist, S1.nextlist) }
S -> if E then M1 S1 N else M2 S2 { backpatch(E.truelist, M1.gotostm); backpatch(E.falselist, M2.gotostm); S.nextlist := merge(S1.nextlist, merge(n.nextlist, S2.nextlist)) }
M -> \epsilon { M.gotostm := nextstm }
N -> \epsilon { N.nextlist := makelist(nextstm); emit('goto') }

循环、符合语句的翻译#

S -> while M1 E do M2 S1 { backpatch(S1.nextlist, M1.gotostm); backpatch(E.truelist, M2.gotostm); S.nextlist := E.falselist; emit('goto', M1.gotostm) }
S -> S1; M S2 { backpatch(S1.nextlist, M.gotostm); S.nextlist := S2.nextlist }
M -> \epsilon { M.gotostm := nextstm }

赋值、算数表达式的翻译#

S -> id := A { emit(id.place ':=' A.place); S.nextlist := '' }
A -> id { A.place := id.place }
A -> int { A.place := newtemp; emit(A.place ':=' int.val) }
A -> real { A.place := newtemp; emit(A.place ':=' real.val) }
A -> A1 + A2 { A.place := newtemp; emit(A.place ':=' A1.place '+' A2.place) }
A -> A1 * A2 { A.place := newtemp; emit(A.place ':=' A1.place '*' A2.place) }
A -> -A1 { A.place := newtemp; emit(A.place ':= uminus' A1.place) }
A -> (A1) { A.place := A1.place }

break语句#

P -> D; S M {backpatch(S.nextlist, M.gotostm); backpatch(S.breaklist, M.gotostm) }
S -> if E then M S1 { backpatch(E.truelist, M.gotostm); S.nextlist := merge(E.falselist, S1.nextlist); S.breaklist := S1.breaklist }
S -> if E then M1 S1 N else M2 S2 { backpatch(E.truelist, M1.gotostm); backpatch(E.falselist, M2.gotostm); S.nextlist := merge(S1.nextlist, merge(N.nextlist, S2.nextlist)); S.breaklist := merge(S1.breaklist, S2.breaklist) }
S -> while M1 E then M2 S1 { backpatch(S1.nextlist, M1.gotostm); backpatch(E.truelist, M2.gotostm); S.nextlist := merge(E.falselist, S1.breaklist); S.breaklist := ''; emit('goto', M1.gotostm) }
S -> S1; M S2 { backpatch(S1.nextlist, M.gotostm); S.nextlist := S2.nextlist; S.breaklist := merge(S1.breaklist, S2.breaklist) }
S -> break; { S.breaklist := makelist(nextstm); S.nextlist := ''; emit('goto'); }
M -> \epsilon { M.gotostm := nextstm }
N -> \epsilon { N.nextlist := makelist(nextstm); emit('goto') }

过程调用#

  • 简单过程调用的翻译
    • call p (a+b, a*b)
    • 计算a+b置于t中的代码 t := a + b
    • 计算a*b置于z中的代码 z := a * b
    • param t // 第一个实参地址
    • param z // 第二个实参地址
    • call p, 2
  • 属性
    • A.n: 参数个数
    • A.arglist: 实参地址的列表
  • 函数
    • makelist: 创建实参地址节点
    • append: 在实参表中添加节点
S -> call id ( A ) { S.code := A.code;
for A.arglist中的每一项p do
S.code := S.code || gen('param' p);
S.code := S.code || gen('call' id.place, A.n) }
A -> A1, E { A.n := A1.n + 1; A.arglist := append(A1.arglist, makelist(E.place)); A.code := A1.code || E.code }
A -> \epsilon { A.n := 0; A.arglist := ''; A.code := '' }

运行时存储组织#

运行时存储组织概述#

  • 任务:编译程序在生成代码时,负责明确各类对象(代码/数据)在逻辑地址空间式如何存放的,以及目标代码运行时,如何使用逻辑地址空间
  • 问题
    • 数据对象的表示。明确各种数据对象在目标机的表现形式
    • 表达式计算。明确如何正确有效的组织表达式的计算过程
    • 存储分配策略。如何存储不同作用域、不同生命周期的数据对象
    • 过程实现。如何实现过程/函数调用及参数传递
  • 数据对象的表示
    • char, bool: 1 byte, int: 4bytes
    • float: 4bytes(IEEE 754: 1符号位+7指数位+23尾数位)
    • 数组: 一块连续的存储区(按行/列存放)
    • 结构: 所有域(field)存放在一块连续的存储区
    • 对象: 实例对象参照结构,方法和成员函数放在其所属的代码区里
    • pointer: 1个字长。32位上4bytes,64位上8bytes
    • 大端 VS 小端
    • 对齐align
  • 表达式计算
    • 在栈区计算
      • 运算数/中间结果存放于当前活动记录(栈区)或通用寄存器中
    • 在运算数栈计算/协处理器
      • 某些目标机采用专门的运算数栈用于表达式计算
    • 使用了递归函数的表达式的计算通常在栈区
  • 存储分配
    • 静态存储分配
      • 全局变量和static的变量被分配在静态数据区
    • 堆式存储分配
      • 递归、可变数组、运行期间自由申请与释放空间
    • 栈式动态存储分配
      • 调用函数时,在栈顶为函数分配数据区
      • 返回时,从栈顶收回数据区
  • 函数调用与参数传递
    • 管理运行栈,分配/回收Active Record,保存/恢复机器状态,信息传递
    • 调用序列,返回序列
    • caller的责任,callee的责任

活动记录(栈帧)#

活动记录:存放函数一次执行所需要的信息的一段连续的存储区

sp临时变量低地址
动态数据区
局部变量
保存的机器状态
访问链
控制链
返回地址
fp实参高地址
  • 栈式存储分配策略
    • 当调用子程序时,在数据空间栈顶,给子程序分配所需的子程序过程活动记录
    • 当子程序返回时,从数据空间栈顶,收回分配给子程序所占用过程活动记录
    • 在允许递归调用时,一个子程序可能在数据空间中同时拥有多个过程活动记录

函数调用与参数传递#

  • 调用序列: 分配AR,填入信息
  • 返回序列: 回收AR,恢复机器状态
  • 参数传递: k<=4个参数用寄存器,余下放栈上
  • 返回值: 寄存器
  • 返回地址: call指令压入栈/通过寄存器传递更高效

代码优化和目标代码生成#

优化技术介绍#

  • 所谓代码优化,是指堆中间代码或目标代码进行等价变换,使得变换后的代码运行速度加快和存储空间减少
  • 优化分类
  • 优化的思路
  • 分析方法
  • 优化的顺序与重复

优化的分类#

  • 是否与机器有关
    • 机器无关(中间代码优化)
      • 机器无关的优化与硬件特征无关,比如把常数值在编译期间计算出来(常数折叠)
    • 机器有关(目标代码优化)
      • 机器相关的优化需要利用硬件特有的特征,比如SIMD指令可以在一条指令里完成多个数据的计算
  • 优化的范围
    • 窥孔优化(基本块内窗口范围内的优化)
    • 局部优化(基本块内优化)
    • 循环优化(可能涉及多个基本快,但在同一函数内)
    • 全局优化(函数内优化)
    • 过程间优化(跨函数优化)

优化的思路#

  • 提前计算常量
    • 常量折叠(合并)
    • 常量传播
      • 常量传播可能会导致更多的常量折叠(合并)
      • 如果一个被折叠的常量后续不再合并,也可能直接导致该常量的定义语句直接被删除——死代码删除
      • 常量传播可能导致分支判断条件是常量,从而导致分支的代码不需要执行,这种优化叫做稀疏有条件的常数传播(Sparse Conditional Constant Propagation)
  • 用更低的代价完成相同的计算
    • 代数化简(没有意义的操作删除,如x = x + 0
    • 强度削弱(乘除法转换成移位操作)
  • 消除重复的计算
    • 复写传播(Copy Propagation)
      • x = a + b; y = x; z = 2 * y -> z = 2 * x
    • 值编号(Value Numbering)
      • 相同的值用同一个变量
    • 公共子表达式删除(Common Subexpression Elimination)
    • 部分冗余消除
    • 化零为整,向量计算(如利用SIMD指令)
      • 循环向量化
      • 聚合体的标量替换
  • 针对循环,重点优化
    • 循环不变代码外提(Loop Invariant Code Motion)
    • 归纳变量强度削弱
      • for (int i = 1; i < 100; i++) j = i * 2; // 2*i可以替换称j+2,计算强度削弱
    • 归纳变量删除
    • 边界检查消除
      • 当引用一个数组成员的时候,同工厂要检查下标是否越界。在循环里面,如果每次都要检查的话,代价就会相当高(如左多个数组的向量运算的时候)。如果编译器能够确定,在循环中使用的数组下标(通常是循环变量或者基于循环变量的归纳变量)不会越界,那么就可以消除边界检查的代码,从而大大提高性能。
    • 循环展开
    • 循环向量化
    • 代码提升
      • 在某点之后总是被计算的表达式,将其移至总能被计算的最晚点
    • 循环重组
int a[100][100];
for(i = 0; i < 100; i++) {
for(j = 0; j < 100; j++) {
a[i][j] = b + a[i][j];
}
}
// a[i][j]的地址在前半部分不需要每次都在循环内计算,可以提到循环外
  • 减少过程调用的开销
    • 内联/内联扩展
      • 内联又被称为过程集成,就是把被调用函数的代码拷贝到调用者中,从而避免函数调用。内联扩展是更低级别的代码替代过程调用的机制
    • 叶例程优化
      • 叶子历程——不调用其它函数(过程)的函数(过程),可以对栈的使用做一些优化
    • 尾调用优化/尾递归删除
  • 对控制流的优化
    • 不可达代码消除(Unreacheable code elimination)
      • 通过控制流分析发现不可到达的代码,如return后的代码,可直接消除;if语句中的永久false分支
    • 死代码删除
      • 通过对数据流的分析,发现某个变量赋值了以后,后面根本没有再用到这个变量。这样的代码就是四变量,就可以删除。对计算解雇哦不起作用的指令
    • if简化
      • 删除if的无用分支或整个if结构
    • 循环简化
      • 化简循环,变成直线代码,从而增加了其它优化的机会,比如指令的流水线化
    • 循环反转
    • 拉直
    • 反分支

分析方法#

  • 控制流分析(CFA)
  • 数据流分析(DFA)
  • 依赖分析(DA)
  • 别名分析(AA)

优化的顺序与重复#

  • 重要性
    • 对所有语言都重要:循环优化
    • 面向对象语言:内联优化
    • 函数式语言:尾递归优化
  • 顺序:
    • 机器无关:早期
    • 机器有关:后期
    • 一个优化导致另一个优化
    • 同一个优化多遍运行

优化块、流图和循环#

基本块#

  • 基本块:一个顺序执行的语句序列,只有一个入口语句和一个出口语句。执行时只能从入口语句进入,从出口语句退出
  • 基本块是一个最大的不可分割的、连续的三地址指令序列,这个块中的指令要么全部执行,要么全部不执行
  • 基本块的入口
    • 程序的第一条语句
    • (条件/无条件)跳转语句的跳转目标语句
    • 条件跳转语句的下一条语句
  • 划分基本块的方法
    • 确定入口语句
    • 每一个入口语句对应一个基本块。从入口语句向后直到
      • 转移语句(是基本块的最后一条语句)
      • 停止语句
      • 下一个入口语句(不包括该句)之间的代码段
    • 凡不属于任何一个基本块的语句都是无用语句,将其全部删除

流图#

  • 流图的节点是一些基本块
  • 从基本块B到基本块C之间有一条边当且仅当基本块C的第一个指令可能紧跟在B的最后一条指令之后执行。
    • 此时称B是C的前去,C是B的后继
    • 确认这样的边的方式
      • 有一个从B的结尾跳转到C的开头的条件或无条件跳转语句
      • 按照原来的三地址语句序列中的顺序,C紧跟在B后,且B的结尾不是无条件跳转语句

常用的优化方法#

  • 删除公共子表达式
  • 复制传播
  • 常量合并/常量传播
  • 删除无用代码
  • 代码移动(外提)
  • 强度削弱
  • 删除归纳变量

删除公共子表达式(CSE)#

  • 如果表达式x op y先前已被计算过,并且从先前的计算到现在,x op y中的变量的值没有改变,那么x op y的这次出现就称为公共子表达式

删除无用代码#

  • 复制传播
  • 常用的公共子表达式消除算法和其它一些优化算法会引入一些复制语句(形如x=y的赋值语句)
  • 复制传播:在复制语句x=y之后尽可能用y代替x
  • 复制传播给删除无用代码带来机会
  • 无用代码(死代码):计算结果永远不会被使用的语句

基本归纳变量与归纳变量#

  • 对于一个变量i,如果存在一个正的或负的常数c使得每次x被赋值时它的值总增加c,那么i就称为基本归纳变量
    • i=i+c(c可为负数、常数、不变量)
    • j=k*i+c(k为不变量,可为负数)
    • j为归纳变量,并且与i同族
  • 删除归纳变量:基本归纳变量可由同族的某一归纳变量替换它

循环#

  • 在流程图G中,对于任意一个节点序列α\alpha,如果在节点序列之外存在一个节点指向节点序列中的节点V,或者节点序列中的节点V是程序首节点,则称节点V为节点序列α\alpha的入口节点
  • 自然循环是在程序流图中,具有下列性质的节点序列L
    • 是强连通子图(任意两节点之间必有通路,且通路上的节点都属于L)
    • 有且仅有一个入口节点
  • 在程序流图中,查找循环方法是基于流图中回边的
  • 回边的基础:必经节点(支配节点),毕经节点集(支配节点集)
    • 在流图中,对任意两个节点m和n,如果从首节点出发到达节点n的任一通路,都要经过节点m,则称节点m是节点n的毕竟节点或支配节点,记为m DOM n
    • 流图中节点n的所有必经节点集合,称为节点n的必经节点集,记为D(n)
计算所有节点n的必经节点集D(n)的算法#
  • 流图G=(N, E, n0), P(n)为节点n的所有前驱节点集
    • 置初值: D(n0){n0}D(n_0) \leftarrow \{n_0\},对于n(N{n0}),D(n)Nn \in (N - \{n_0\}), D(n) \leftarrow N
    • 对于n(N{n0})n \in (N - \{n_0\}),做下列计算
      • NEWD{n}(D(p)pP(n))NEW_D \leftarrow \{n\} \cup ( \cap D(p) | p \in P(n))
      • 如果NEWD!=D(n)NEW_D != D(n),则D(n)NEWDD(n) \leftarrow NEW_D
      • 重复上一个步骤,直到所有D(n)不再变化为止
    • d DOM n的充分必要条件是对于任意piP(n)p_i \in P(n),有dDOMpi,1<=i<=kd DOM p_i, 1 <= i <= k

自然循环:一种适合优化的循环#

  • 自然循环是满足以下性质的循环
    • 有唯一的入口节点,称为首节点。首节点支配循环中的所有节点,否则,它就不会成为循环的唯一入口
    • 循环中至少有一条返回首节点的路径,否则,控制就不可能从“循环”中直接回到循环头,也就无法构成循环
  • 自然循环的识别
    • 给定一个回边ndn \rightarrow d,该回边的自然循环为,以及所有可以不经过d而到达n的节点。d为该循环的首节点
  • 求回边vianndn \rightarrow d的自然循环(P(n)为节点n的所有前驱节点集)
  • 令流图G的一条回边ndn \rightarrow d,求d入口和n为出口的循环loop
    • loop{d,n},S{n}loop \leftarrow \{d, n\}, S \leftarrow \{n\}
    • S(P(q)qS)loopS \leftarrow (P(q)|q \in S) - loop
    • looploopSloop \leftarrow loop \cup S
    • 重复(2)(3),直到所有loop不再变化为止

数据流分析基础#

  • 一组用来收集程序执行路径上的数据流信息的技术
  • 数据流分析应用
    • 到达-定值分析
    • 活跃变量分析
    • 可用该表达式分析
  • 在每一种数据流分析应用中,都会把每个程序点和一个数据流值关联起来
  • 每条IR语句s将一个input state转换成一个新的output state
  • input/output数据流信息分别于s的前/后节点相关联
  • 语句s的转移函数
    • 正向分析: OUT[s]=fx(IN[s])OUT[s] = f_x (IN[s])
    • 反向分析: IN[s]=fs(OUT[s])IN[s] = f_s(OUT[s])
  • 基本块内的数据流
    • 设基本块B由语句s1, …, sn顺序组成,则必有
      • IN[si+1]=OUT[si]IN[s_{i+1}] = OUT[s_i],对于所有的i从0到n-1
    • IN[B]=IN[s1],OUT[B]=OUT[sn]IN[B] = IN[s_1], OUT[B]=OUT[s_n]
    • fBf_B: 基本块B的转移函数
      • 正向分析: OUT[B]=fB(IN[B])OUT[B] = f_B(IN[B]),fB=fsn...fs1f_B = f_{sn} \cdot ... \cdot f_{s1}
      • 反向分析: IN[B]=fB(OUT[B])IN[B] = f_B(OUT[B]), fB=fs1...fsnf_B = f_{s1} \cdot ... \cdot f_{sn}
  • 基本块间的数据流
    • 正向分析: IN[B]=Ppred(B)OUT[P]IN[B] = \cup _{P \in pred(B)} OUT[P]
    • 反向分析: OUT[B]=Ssucc(B)IN[S]OUT[B] = \cup _{S \in succ(B)} IN[S]

到达-定值分析#

  • 定值:变量v的定值(可能)将一个值赋给v的语句(v=...)
  • 到达定值
    • 存在一条从定值d到程序点p的路径,且在此路径上d没有被“杀死”,则称定值d到达程序点p
    • 即在此路径上没有其它定值d’对变量v重新定值
  • 转移函数fdf_d
    • 定值d: u = v + w
    • 它“产生”一个u的定值,并杀死其它对u的定值
    • fd(x)=gend(xkilld)f_d(x) = gen_d \cup ( x - kill_d)
    • gendgen_d: 语句d生成的定值集合{d}
    • killdkill_d: 语句d杀死的定值集合
  • 基本块的转移函数fBf_B
    • fB(x)=genB(xkillB)f_B(x) = gen_B \cup ( x - kill_B)
    • out[B]=fB(in[B])=genB(in[B]killB)out[B] = f_B(in[B]) = gen_B \cup (in[B]-kill_B)
    • in[B]=ppred(B)out[p]in[B] = \cup _{p \in pred(B)} out[p]

用途#

  • 循环不变计算的检测
    • x = y + z: y,z是常量,或y和z所有可能的定值都在循环外面
  • 常量传播/折叠
    • x = y + z: y|z只有一个定值可到达,并且该定值把一个常量赋值给y|z,那么可以简单地把y|z替换为该常量
  • 判定变量x在p点上是否未经定值就被引用

迭代算法#

输入: 流图G,并且已经计算了所有的genB,killBgen_B, kill_B

out[ENTRTY] = \emptyset
for (除ENTRY之外的每个基本块B) out[B] = \emptyset
while (某个out值发生了改变)
for (除ENTRY之外的每个基本块) {
in[B] = \cup _{p \in pred(B)} out[P]
out[B] = gen_B \cup (in[B] - kill_B)
}

UD链(Use-Definition Chain)-引用的定值链#

  • 点u引用(use) a,能到达点u的a的所有定值点的全体称为a在点u的引用-定值链
  • 基本块B中a的引用点u之前有a的定值,那么a的最后一次定值点是该ud链的唯一定值点: {d}
  • 基本块B中a的引用点之前没有a的定值,那么in[B]中a的所有定值点都能到达u,它们即A在点u的ud链

活跃变量数据流分析#

  • 活跃变量:对于程序中的变量v和某点p: 流图中存在一条从p开始的通路引用v在点p的值,则称v在点p是活跃的,否则是不活跃的
  • 用途
    • 删除无用赋值
      • 无用赋值:如果v在点p的定值在基本块内所有后继点都不被引用,且v在基本块出口之后又是不活跃的,那么v在点p的定值就是无用的
    • 为基本快分配寄存器
      • 如果所有寄存器都被占用,并且还需要申请一个寄存器,则应该考虑使用已经存放了死亡值的寄存器,因为这个值不需要保存到内存
      • 如果一个值在基本块结尾处就是死的就不必再结尾处保存这个值
  • 活跃变量数据流方程
    • out[B]=Ssucc(B)in[S]out[B] = \cup _{S \in succ(B)} in[S]
    • in[B]=useB(out[B]defB)in[B] = use_B \cup (out[B] - def_B)
    • 初始条件: in[EXIT]=in[EXIT] = \varnothing
    • useBuse_B: B中引用前未定值的变量集合
    • defBdef_B: B中定值前未引用的变量集合

算法#

输入:流图G,所有的useB,defBuse_B, def_B都已计算出

in[EXIT] = \emptyset
for (除EXIT之外的每个基本块B) in[B] = \emptyset
while (某个in值发生了改变)
for (除了EXIT之外的每个基本块) {
out[B] = \cup _{S \in succ(B)} in[S];
in[B] = use_B \cup (out[B] - def_B);
}

定值-引用链#

  • 设变量x有一个定值d,该定值所有能够到达的引用u的集合称为x在d处的定值-引用链,简称du链
  • 如果在求解活跃变量数据流方程中的out[B]时,将out[B]表示称从B的末尾处能够到达的引用的集合,那么,可以直接利用这些信息计算基本块B中每个变量x在其定值处的DU链
    • 如果B中x的定值之后有x的第一个定值d’,则d和d’之间x的所有引用构成d的DU链
    • 如果B中x的定值d之后没有x的新的定值,则B中d之后x的所有引用以及out[B]1
编译原理复习
https://blog.cassiusblack.top/posts/hust-cs-compile/
Author
Cassius Black
Published at
2025-06-24
License
CC BY-NC-SA 4.0