# 循环展开 ## 1 假设 关于源代码的形式,做如下的假设: - 除了循环外,源代码中不含其他分支或跳转指令(比如if语句、goto语句、函数调用等) - 不区分浮点运算和整数运算 - 仅包含形如`for i = 0 to n - 1`的循环,其中循环变量`i`在循环体内不会被修改,`n`是变量 进一步假设源代码中仅包含以下要素: - 标签:形如`label: [instruction]`,其中`label`是标签,`[instruction]`是某条指令 - 功能型指令: - 加载:形如`load reg1, offset(reg2)`,其中`reg`是寄存器,`offset`是某个整数 - 存储:形如`store reg1, offset(reg2)`,其中`reg`是寄存器,`offset`是某个整数 - 加法:形如`add reg1, reg2, reg3`,其中`reg1`、`reg2`、`reg3`是寄存器 - 移动:形如`move reg1, reg2`,其中`reg1`、`reg2`是寄存器 - 加立即数:形如`addi reg1, reg2, imm`,其中`reg1`、`reg2`是寄存器,`imm`是某个整数 - 空指令:形如`nop` - 条件跳转指令: - 不相等时跳转:形如`bne reg1, reg2, label`,其中`reg1`、`reg2`是寄存器,`label`是标签 - 相等时跳转:形如`beq reg1, reg2, label`,其中`reg1`、`reg2`是寄存器,`label`是标签 关于寄存器的使用,做如下假设: - 物理寄存器有32个 - 存储器中有一块专用区域作为虚拟寄存器的交换空间: - 交换空间是一块起始地址为0的连续存储空间 - 交换空间足够大 关于指令排序的约束,做如下假设: - 5阶段流水线 - 分支延迟1周期,延迟槽中的指令无条件执行 - (其他假设由实现者自行补充在这里) ## 2 程序结构 整个程序的描述如下: - 输入:源代码的中间表示 - 输出:经过循环展开和指令重排后的汇编代码 - 约束:输出代码应当能够直接被CPU执行,并且与输入代码等效 我们将整个程序划分为四个模块: - 循环展开:将代码中的循环展开若干次,展开的次数应当作为参数输入 - 寄存器管理:将代码中使用的虚拟寄存器号转化为物理寄存器号,适当加入与存储器交换的指令 - 指令重排:将指令重新排列,以消除指令之间的冲突,并**尽可能地**提高执行效率 - 代码生成:将经过处理的中间表示转化为输出代码 ### 2.1 中间表示的形式 - 一个程序是一个列表,其中每一项或者是一个功能型指令、或者是一个循环 - 仅考虑特定形式的for循环,因此一个循环可以用如下结构表示: - 循环变量:一个寄存器 - 循环次数:一个寄存器 - 循环体:一个程序,即一列功能型指令或循环(允许循环的嵌套) 形式化的表达是这样的: ``` program ::= [item, item, ..., item] item ::= function | loop loop ::= [iterator, times, program] ``` 具体的实现在`ir/ir.py`中,可以阅读一下 ### 2.2 循环展开模块 循环展开模块的作用是将一个中间表示中的循环展开指定次数,输出也是一个中间表示 实现时注意边界条件,展开后的循环在跳出前的最后一次循环体可能有不一样的形式 实现者可以自行决定嵌套的循环如何展开: - 展开最内层的循环 - 从内向外展开所有循环 - 将嵌套的循环展开成单层的循环 ### 2.3 寄存器管理模块 在循环展开模块中,通常需要使用更多的寄存器,我们允许指令访问的寄存器号可以是任意的,虚拟寄存器模块负责将这些虚拟寄存器号转化为物理寄存器号 当物理寄存器不够用时,需要将寄存器中的数据移到存储器中,在适当的时候调回(参考虚拟内存中的调页) 寄存器管理模块的输入输出都是一个中间表示,输入中允许访问任意的虚拟寄存器号,输出中仅允许访问有限的物理寄存器号 ### 2.4 指令重排 指令重排模块的作用是将指令重新排列,使其成为满足假设的程序,输入输出都是一个中间表示 在正确的基础上,实现者可以自行决定优化到什么程度(可以先无脑插入nop) ### 2.5 代码生成模块 代码生成模块的作用是将一个中间表示转化为对应的汇编代码,并输出到指定文件 ## 2 其他事项