The Decaf Book
  • Introduction
  • 语言规范
  • Java 框架分阶段指导
    • 本章导引
    • PA1-A:语法分析器的自动构造
      • 阶段任务
      • 词法分析
      • 抽象语法树
      • 文法分析
    • PA1-B:基于 LL(1) 的语法分析器半自动构造
      • 阶段任务
      • LL(1) 文法分析
      • 错误恢复
    • PA2:语义分析
      • 阶段任务
      • 类型、符号和作用域
      • 访问者模式
      • 符号表构建
      • 类型检查
    • PA3:中间代码生成
      • 阶段任务
      • TAC 程序
      • 面向对象机制
      • 控制流的翻译
    • PA4:中间代码优化
      • 实验内容
      • 基本块
      • 数据流分析概述
      • 数据流优化概述
      • 公共表达式提取
      • 复写传播
      • 常量传播
      • 死代码消除
  • Scala 框架分阶段指导
    • 本章导引
    • PA1:语法分析器的自动构造
      • 阶段任务
      • 词法分析
      • 抽象语法树
      • 文法分析
      • 访问者模式
    • PA2:语义分析
      • 阶段任务
      • 类型、符号和作用域
      • 符号表构建
      • 类型检查
    • PA3:中间代码生成
      • 阶段任务
      • TAC 程序
      • 面向对象机制
      • 控制流的翻译
    • PA3-JVM:JVM 字节码生成
      • 阶段任务
      • JVM 字节码简介
      • 翻译过程
    • PA4:中间代码优化
      • 实验内容
      • 基本块
      • 数据流分析概述
      • 数据流优化概述
      • 公共表达式提取
      • 复写传播
      • 常量传播
      • 死代码消除
  • Rust 框架分阶段指导
    • 本章导引
    • PA1-A:语法分析器的自动构造
      • 实验内容
      • lalr1使用指导
        • 编写lexer
        • impl块的可选属性
        • 产生式和语法动作
        • 解决冲突
        • 一个完整的例子
      • 抽象语法树
      • 框架中部分实现的解释
      • 文件结构
    • PA1-B:基于 LL(1) 的语法分析器半自动构造
      • 实验内容
      • lalr1使用指导
      • 错误恢复
      • 文件结构
    • PA2:语义分析
      • 实验内容
      • 语义分析
      • 符号表
      • visitor模式
    • PA3:中间代码生成
      • 实验内容
      • 中间代码
      • 中间代码中的类型信息
      • 运行时存储布局
      • 面向对象机制
      • tacvm简述
    • PA4:中间代码优化
      • 基本块
      • 数据流分析概述
      • 数据流优化概述
      • 公共表达式提取
      • 复写传播
      • 常量传播
      • 死代码消除
    • PA5:寄存器分配
      • 实验内容
      • 图着色基本原理
      • 着色算法
      • 预着色节点
      • 干涉图节点合并
      • 调用约定
Powered by GitBook
On this page

Was this helpful?

  1. Scala 框架分阶段指导
  2. PA4:中间代码优化

基本块

在数据流分析和优化之前需要先将tac划分成基本块:大家应该是默认地接受了这个设定,其实这不是必要的。之后提到的所有的数据流分析和优化的算法都可以直接在tac语句级别进行,对于每条语句都可以计算出相应的集合,并且在语句间进行传播。将tac划分为基本块可以看做是一种加速手段:每个基本块从进入到退出,对于数据流信息的处理是固定的,而因为这些分析和优化方法都是迭代的算法,划分为基本块就可以避免每个迭代步重复计算了。

很多参考文献中为了简化算法的描述,会假定每个基本块都由单条语句组成。从这也可以看出划分基本块是不必要的,不过在实际的编译器中为了编译速度考虑,一般还是要进行划分基本块的操作。

原理课中对于基本块的定义如下:

  • 程序中一个顺序执行的语句序列

  • 只有一个入口语句和一个出口语句

  • 除入口语句外其他语句均不可以带标号

  • 除出口语句外其他语句均不能是转移或停止语句

也就是说,除非执行过程中发生意料之外的错误,一个基本块总能够从开头执行到结尾,且不可能从除开头外的任何一个中间点开始执行。

基本块的划分方法是先标记"首语句",也就是可以成为基本块的第一条语句的语句。它可以是:

  • 程序的第一条语句

  • 转移语句的目标语句

  • 紧跟在条件转移语句后面的语句

容易证明基本块就是被首语句划分的一系列语句块。因此一个直白的划分基本块的方法是先标记所有首语句,然后遍历一遍语句,将两条首语句之间的语句划分给一个基本块。不过实现的时候没有必要分成这样两趟的过程,只需要一趟遍历就可以划分出基本块。具体可以参考框架已经提供好的代码。

事实上为了获取基本块的划分,不一定要像现在的实验框架和课堂中讲的一样对线性的ir进行划分,也可以直接从ast构造出cfg,只是会让这个翻译阶段(pa3)麻烦一些而已。也许明年的框架中就没有划分基本块这一步了。

基本块之间的连接关系由基本块的最后一条语句决定:

  • 返回/停止:没有出边

  • 无条件转移:一条出边指向转移的标号所在的基本块

  • 条件转移:一条出边指向转移的标号所在的基本块,一条出边指向下一个基本块

实现的时候,以下几个点需要注意:

  1. 这个"最后一条语句"没有被包含在基本块的tac链表中,它的其它信息(例如返回值,条件)保存到了表示出边的enum中

  2. 在遇到tac中的条件转移语句时,不是直接把它对应到条件转移,而是判断条件是否是一个常量,如果是则把它转化成无条件转移

  3. tac语句中没有"停止指令",但是有对功能为停止的intrinsic函数的调用,对它按照停止语句处理

容易看出,因为有第二条的"优化"的存在(其实主要目的不是优化,而是让本阶段的后续实现更简单),如果在本阶段通过图的可达性来检查一个函数是否不返回就结束,可以对pa2中提到的循环条件为常量true且没有作用于它的break的循环做出正确的判断。事实上,在之前的某个版本中,我基本实现了java规范要求的这类错误的检测,但是结果只能在划分基本块这个阶段才能给出,而且需要依赖pa3中进行一定程度的常量折叠,但是其它的框架都希望能在pa2这个阶段就检查出这类错误,所以我也只能简化成现在这个样子了。

按照现在的实现,可以保证pa4中处理的所有函数都满足不会不返回就结束(不仅限于返回值非空的函数,而是所有函数),tacopt::bb::simplify函数依赖于这个条件,如果违反的话可能会出现下标越界。这样的设计是完全合适的,按照rust的错误处理的理念,这样的不可恢复,而且是因为输入没有满足约定而导致的错误,就应该panic。

Previous实验内容Next数据流分析概述

Last updated 5 years ago

Was this helpful?