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. Rust 框架分阶段指导
  2. PA5:寄存器分配

实验内容

PreviousPA5:寄存器分配Next图着色基本原理

Last updated 5 years ago

Was this helpful?

pa5中我们将完成编译器的后端,至此这个简单的编译器就完成了其所有的阶段。

一个典型的编译器后端分为以下三个步骤(排名分先后):

  1. 指令选择(instruction selection),为中间代码选择合适的汇编指令,这一阶段的汇编指令中仍然使用类似中间代码的虚拟寄存器

  2. 指令调度(instruction scheduling),通过恰当地安排指令的顺序,发掘流水线等硬件并行性的加速效果,减少访存的延迟的影响,充分利用分支延迟槽等

  3. 寄存器分配(register allocation),为汇编指令中涉及到的虚拟寄存器分配实际的寄存器,同时将无法分配的部分spill到栈上

可以看出第二步对于我们这个简单的编译器其实不是必要的,我们就不实现这个部分了(我们没有考虑mips的分支延迟槽,spim模拟器的默认选项下这个功能也是关闭的)。

值得注意的是,java版本的实验中先进行寄存器分配,后进行指令选择,也就是说寄存器分配的对象是中间代码而非带有虚拟寄存器的汇编代码。这样做也许可以减小一定工作量(存疑,因为我之前做实验的时候也没有觉得这=比我们的框架简单),然而这样并不便于处理平台相关的信息,例如实现调用约定和spill寄存器等,需要很生硬的在中间代码中强行加入这种并不"中间"的东西,同时也可能失去了一定的优化机会,这在后面会提到。

实验框架已经提供了指令选择,实现的很简单,基本上属于简单的macro expansion[1]方法,不过在mips这种risc指令集下,指令选择的空间不是很大,因此效果也不会和精心选择的结果相比差太多(相比于x86而言)。除macro expansion外,还有例如tree rewriting,dag rewriting等指令选择的方式,感兴趣的同学可以自己查阅资料。

寄存器分配分为局部寄存器分配和全局寄存器分配,局部寄存器分配在基本块层次进行,一般使用一些简单的贪心策略即可,效果一般不如全局寄存器分配;全局寄存器分配可以跨越基本块层次,来到函数层次,甚至跨越函数层次,进行过程间的寄存器分配。全局寄存器分配比较正统的方法一般是图着色和线性扫描,对更多方法感兴趣的同学可以自己查阅相关资料。

框架中已经提供了一个暴力的寄存器分配算法:最简单的寄存器分配,就是不进行寄存器分配,所有的虚拟寄存器都存放在栈中,只利用有限的几个寄存器进行运算,且运算的结果也立即写入内存。不难想象这样的实现是非常低效的。

本阶段的任务是函数层次的全局寄存器分配,我们将会根据论文[2]实现基于图着色的全局寄存器分配,并且要求实现mips调用约定[3]。下面讲解的内容在两篇课程教案[3]中有比较形象的解释,可供参考。讲解的主要目的还是希望大家能获得一个形象的认识,很多地方说的可能不是很精确,其实我个人也认为对代码实现不是很有指导价值。建议大家直接阅读论文[2],其中有相当详细的伪代码,大家可以用于对照实现。

参考资料:

[1]

[2]

[3]

[4]

[5]

Instruction Selection: Principles, Methods, and Applications
Iterated Register Coalescing
Lecture 14 Register Allocation & Spilling
Lecture 23 Register Allocation: Coalescing
MIPS Calling Convention
Register Allocation & Spilling via Graph Coloring