Commit 793fd3dd authored by lxq's avatar lxq

beginning for stack allocation

parent 40437b6e
......@@ -19,7 +19,6 @@
- [示例 1:计算加法并输出](#示例-1计算加法并输出)
- [示例 2:调用函数访问数组](#示例-2调用函数访问数组)
## 0. 前言
经过前四个Lab,我们的cminus compiler已经可以生成有效的llvm IR。Lab5实验目的,需要你根据 IR 生成龙芯LA64架构下的可正常运行的汇编代码。
......@@ -31,7 +30,6 @@
3. 进行寄存器分配,优化性能(选做)。
4. 工作总结及功能汇报展示。
## 1. 实验框架
需要在现有框架的基础上,添加 `codegen` 模块,实现 IR 到汇编的转换;如果要优化性能,可以添加寄存器分配的模块。
......@@ -50,7 +48,6 @@
> 可以写一段 C 语言代码,在龙芯上用 `gcc -S` 生成汇编代码,观察汇编代码,思考如何实现。
### 完成代码
可以先使用[栈分配策略](#附录栈分配策略)存储变量,优先追求代码生成的完成度。
......@@ -74,15 +71,12 @@
+ 不进行专门的寄存器分配算法(图着色、线性分配等),所有变量都存储在栈上(详见[附录](#附录栈分配策略))。
+ 完成专门的寄存器分配算法
## 3. 运行与调试
### 龙芯服务器的登录
根据助教提供的账号,密码,采用 ssh 登录龙芯机器。
### 汇编代码的生成
编译过程:
......@@ -122,6 +116,7 @@ gdb test
```
> gdb 常用命令:
>
> ```
> b 20 # 在第 20 行设置断点
> r # 运行程序
......@@ -141,7 +136,6 @@ gdb test
评测脚本的使用说明见 [评测脚本.md](评测脚本.md)
## 4. 提交
### 目录结构
......@@ -177,7 +171,6 @@ gdb test
如果需要,可以添加新的文件,或者修改已有文件。
### 提交要求、评分标准
- 提交要求:
......@@ -191,6 +184,7 @@ gdb test
2023 年 3 月初,具体时间待定
- 评分标准:
- 实验分数组成如下:
测试用例的通过情况
实验报告
......@@ -202,8 +196,6 @@ gdb test
如有任何问题,可以通过邮件联系助教。
## 附录:栈分配策略
主要思想是:
......@@ -268,7 +260,6 @@ main:
在程序设置返回值之后,恢复返回地址、帧指针、栈指针的寄存器前,设置断点,可以观察到栈中的变量值:
> 地址以 4 字节为单位
```
......@@ -352,7 +343,6 @@ main:
jr $ra # 跳转到返回地址
```
在执行完 store 函数中的 `stptr` 后,恢复帧指针和栈指针前,栈的情况如下:
> 地址以 8 字节为单位
......
# 龙芯扩展实验ABI文档
- [龙芯扩展实验ABI文档](#龙芯扩展实验ABI文档)
- [0. 前言](#前言)
- [1. 基础指令介绍](#基础指令介绍)
......@@ -7,13 +8,13 @@
- [1.3 浮点数指令概述](#基础浮点数指令概述)
- [2. 寄存器使用约定](#寄存器使用约定)
- [3. 函数调用约定](#函数调用约定)
## 前言
## 前言
龙芯面向国家信息化建设需求,提供了高性能的处理器和基础软硬件解决方案,并提供独立自主的指令系统架构。龙芯架构LoongArch64是一种精简指令集(RISC)风格的指令系统架构,龙芯架构具有RISC指令架构的典型特征。它的指令长度固定且编码格式规整,绝大多数指令只有两个源操作数和一个目的操作数,采用`load/store`架构,即仅有`load/store`访存指令可以访问内存,其它指令的操作对象均是处理器核内部的寄存器或指令码中的立即数。
关于更多关于龙芯架构的详细介绍,也可以前往[龙芯官网](https://www.loongson.cn/)等查看相关内容。
## 基础指令介绍
### 指令编码格式
......@@ -25,10 +26,12 @@
0x120000664 <main+4>: st.d $r22,$r3,24
0x120000668 <main+8>: addi.d $r22,$r3,32
```
#### 基本数据类型
下表为基础指令涉及到的基本数据类型的大小与数据存放时对齐:
<table>
<table>
<tr>
<th>Type</th><th>Size</th><th>Alignment</th>
</tr>
......@@ -61,9 +64,6 @@
</tr>
</table>
#### 指令汇编助记格式
指令名后缀为`.B、.H、.W、.D、.BU、.HU、.WU、.DU`分别表示该指令操作的数据类型是有符号字节、有符号半字、有符号字、有符号双字、无符号字节、无符号半字、无符号字、无符号双字。不过这里有一种特殊情况,当操作数是有符号数还是无符号数不影响运算结果时,指令名中携带的后缀均不带U,但此时并不限制操作对象只能是有符号数。
......@@ -74,14 +74,17 @@
基础整数指令涉及的寄存器包括通用寄存器(General-purpose Register,简称GR)、程序计数器(Program Counter, 简称PC,它存放当前将要执行的指令所在的地址,一般我们不需要修改)。
##### 通用寄存器
通用寄存器GR有32个,记为r0-r31,其中第0号寄存器r0的值恒为0。GR的位宽记作GRLEN。LA32架构下GR的位宽是32比特,LA64架构下GR的位宽是64比特。基础整数指令中任一个寄存器操作数都可以采用32个GR中的任一个。唯一的例外是BL指令中隐含的目的寄存器一定是第1号寄存器r1。在标准的龙芯架构应用程序二进制接口 (Application Binary Interface,简称ABI)中,r1固定作为存放函数调用返回地址的寄存器。
### 基础整数指令概述
#### 数据传送指令
- 整型的数据传送,可以通过[OR、ORI](#AND,OR,NOR,XOR,ANDN,ORN),或者[LU12I.W](#LU12I.W)等指令实现。
- 样例:
- 我们可以通过执行如下命令,将\$r12的值赋给\$r13。
```
#$r13=$r12
#将$r12中数据与$0中的数据进行按位逻辑或运算,结果写入$r13
......@@ -89,6 +92,7 @@
```
- 如果我们想要传送超过12位的立即数,如让\$r13的`[31:0]`位内容变为56995821(0x365afed):
则可以通过以下指令,将该值赋给临时寄存器$r13;
```
#56991744,转换为十六进制即0x365a000
lu12i.w $r12,56991744>>12 #将寄存器$r12的[31:0]位,置为#0x365a000
......@@ -97,10 +101,8 @@
#### 算数运算指令
###### ADD.{W/D},SUB.{W/D}
- 格式:
- add.w rd,rj,rk
- add.d rd,rj,rk
......@@ -118,12 +120,15 @@
- 上述指令执行时不对溢出作特殊处理。
- 样例:
- 执行指令前, \$r13寄存器的值为`0x0000000000000003`,\$r12寄存器的值为 `0x0000000000000005`
```
sub.w $r14,$r13,$r12
```
执行后,\$r14 寄存器的值为`0xfffffffffffffffe`,即其`[31:0]`位数据等于`-2`
###### ADDI.{W/D}
- 格式:
- addi.w rd,rj,si12
- addi.d rd,rj,si12
......@@ -135,12 +140,15 @@
- 上述指令执行时不对溢出作特殊处理。
- 样例:
- 寄存器\$sp中的值`0x000000ffffff33f0`;执行指令
```
addi.d $fp,$sp,32
```
执行后,\$fp 寄存器的值为`0x000000ffffff3410`
##### LU12I.W
- 格式:
- lu12i.w rd,si20
- 概述:
......@@ -149,6 +157,7 @@
- 该指令与ORI指令一起,用于将超过12位的立即数装入通用寄存器中。
##### SLT
- 格式:
- slt rd,rj,rk
- 概述:
......@@ -157,6 +166,7 @@
- SLT比较的数据位宽与所执行机器的通用寄存器位宽一致。
##### SLTI
- 格式:
- slti rd,rj,si12
- 概述:
......@@ -165,6 +175,7 @@
- SLTI比较的数据位宽与所执行机器的通用寄存器位宽一致。
##### AND,OR,NOR,XOR,ANDN,ORN
- 格式:
- and rd, rj, rk
- or rd, rj, rk
......@@ -186,7 +197,8 @@
- ORN:
- ORN将通用寄存器rk中数据按位取反后再与通用寄存器rj中的数据进行按位逻辑或运算,结果写入通用寄存器rd中。
- 上述指令操作的数据位宽与所执行机器的通用寄存器位宽一致。
##### ANDI,ORI,XORI
##### ANDI,ORI,XORI
- 格式:
- andi rd, rj, ui12
- ori rd, rj, ui12
......@@ -199,11 +211,13 @@
- XORI:
- XORI将通用寄存器rj中数据与12比特立即数ui12零扩展之后的数据进行按位逻辑异或运算,结果写入通用寄存器rd中。
- 上述指令操作的数据位宽与所执行机器的通用寄存器位宽一致。
##### NOP
##### NOP
- 概述:
- NOP指令是`"andi,r0,r0,0"`的别名,其作用仅为占据4字节的指令码位置并让PC+4,除此之外不会改变任何软件可见的处理器状态。(注意\$r0寄存器的值始终为0)
##### MUL.W,MULH.W
- 格式:
- mul.w rd, rj, rk
- mulh.w rd, rj, rk
......@@ -212,7 +226,8 @@
- MUL.W将通用寄存器rj中[31:0]位数据与通用寄存器rk中[31:0]位数据相乘,乘积结果的[31:0]位数据符号扩展后写入通用寄存器rd的[63:0]位中。
- MULH.W:
- MULH.W将通用寄存器rj中[31:0]位数据与通用寄存器rk中[31:0]位数据视作有符号数相乘,乘积结果的[63:32]位数据符号扩展后写入通用寄存器rd的[63:0]位中。
##### MULW.D.W
##### MULW.D.W
- 格式:
- mulw.d.w, rj, rk
- 概述:
......@@ -220,11 +235,14 @@
- MULW.D.W将通用寄存器rj中[31:0]位数据与通用寄存器rk中[31:0]位数据视作有符号数相乘,乘积结果的[63:0]位数据写入通用寄存器rd中。
##### DIV.W,MOD.W
- 格式:
- div.w rd, rj, rk
- mod.w rd, rj, rk
- 概述:
- DIV.W:
- DIV.W将通用寄存器rj中[31:0]位数据除以通用寄存器rk中[31:0]位数据,所得的商符号扩展后写入通用寄存器rd中。
- MOD.W:
......@@ -237,6 +255,7 @@
#### 移位运算类指令
##### SLLI.W,SRLI.W,SRAI.W,ROTRIW
- 格式:
- slli.w rd, rj, ui5
- srli.w rd, rj, ui5
......@@ -254,17 +273,22 @@
- ROTRI.W将通用寄存器rj中[31:0]位数据循环右移,所得的移位结果符号扩展后写入通用寄存器rd中。
- 样例:
-\$r12寄存器在执行指令前,其内容为`0xfffffffffffffffa`,即`[31:0]`位数据的值等于-6。并执行移位指令:
```
slli.w $r12,$r12,1
```
执行后,\$r12 寄存器的值为`0xfffffffffffffff4`,即其`[31:0]`位数据的值等于-12,`[63:32]`位根据左移结果全置为1。
- 同上,若\$r12寄存器在执行指令前,其内容为 `0xfffffffffffffffa`,并执行移位指令:
```
srli.w $r12,$r12,1
```
执行后,\$r12 寄存器的值为`000000007ffffffd`,即逻辑右移后左边补0,`[63:32]`位根据移位结果全置为0。
##### SLL.W,SRL.W,SRA.W,ROTR.W
- 格式:
- sll.w rd, rj, rk
- srl.w rd, rj, rk
......@@ -282,6 +306,7 @@
- ROTR.W将通用寄存器rj中[31:0]位数据循环右移,所得的移位结果符号扩展后写入通用寄存器rd中。
##### SLLI.D,SRLI.D,SRAI.D,ROTRI.D
- 格式:
- slli.d rd, rj, ui6
- srli.d rd, rj, ui6
......@@ -299,6 +324,7 @@
- 上述移位指令的移位量均为指令码中6比特无符号立即数ui6。
##### SLL.D,SRL.D,SRA.D,ROTR.D
- 格式:
- sll.d rd, rj, rk
- srl.d rd, rj, rk
......@@ -315,11 +341,14 @@
- ROTR.D将通用寄存器rj中[63:0]位数据循环右移,所得的移位结果符号扩展后写入通用寄存器rd中。
- 上述移位指令的移位量均为通用寄存器rk中[5:0]位数据,且视作无符号数。
#### 转移指令
##### 示例
#### 转移指令
##### 示例
- [示例二](###示例2)给出了分支指令`B`的使用参考。
- [示例三](###示例3)给出了过程调用中`BL``JIRL`的使用参考。
##### B
##### B
- 格式:
- b offs26
- 概述:
......@@ -328,30 +357,46 @@
- 需要注意的是,该指令如果在写汇编时采用直接填入偏移值的方式,则汇编表示中的立即数应填入以字节为单位的偏移值,即指令码中offs26<<2。
##### BL
- 格式:
- bl offs26
- 概述:
- BL
- BL无条件跳转到目标地址处。同时将该指令的PC值加4的结果写入1号通用寄存器r1中。其跳转目标地址是将指令码中的26比特立即数offs26逻辑左移2位后再符号扩展,所得的偏移值加上该分支指令的PC。
- 1号通用寄存器r1作为返回地址寄存器ra。
- 该指令通常配合JIRL指令,完成函数调用。
- 需要注意的是,该指令如果在写汇编时采用直接填入偏移值的方式,则汇编表示中的立即数应填入以字节为单位的偏移值,即指令码中offs26<<2。
##### JIRL
##### JIRL
- 格式:
- jirl rd,rj,offs16
- 概述:
- JIRL
- JIRL无条件跳转到目标地址处。同时将该指令的PC值加4的结果写入通用寄存器rd中。其跳转目标地址是将指令码中的16比特立即数offs16逻辑左移2位后再符号扩展,所得的偏移值加上通用寄存器rj的值。
- 当rd等于0时,JIRL的功能即是一条普通的非调用间接跳转指令。 rd等于0,rj等于1且offs16等于0的JIRL常作为调用返回间接跳转使用。
- 需要注意的是,该指令如果在写汇编时采用直接填入偏移值的方式,则汇编表示中的立即数应填入以字节为单位的偏移值,即指令码中offs16<<2。
- 样例:
- 伪指令`jr $r1`是一条宏指令,它的完整表示为:
```
jirl $r0,$r1,0
```
其功能为跳转到寄存器$r1所保存的地址,该指令常用于过程返回。
##### BEQ,BNE,BLT[U],BGE[U]
##### BEQ,BNE,BLT[U],BGE[U]
- 格式:
- beq rj, rd, offs16
- bne rj, rd, offs16
- blt rj, rd, offs16
......@@ -360,6 +405,7 @@
- bgeu rj, rd, offs16
- 概述:
- BEQ
- BEQ将通用寄存器rj和通用寄存器rd的值进行比较,如果相等则跳转到目标地址,否则不跳转。
- BNE
......@@ -375,6 +421,7 @@
- 上述六条分支指令的跳转目标地址计算方式是将指令码中的16比特立即数offsl6逻辑左移2位后再符号扩展,所得的偏移值加上该分支指令的PC。不过需要注意的是,上述指令如果在写汇编时采用直接填入偏移值的方式,则汇编表示中的立即数应填入以字节为单位的偏移值,即指令码中offs16<<2。
##### BEQZ,BNEZ
- 格式:
- beqz rj, offs21
- bnez rj, offs21
......@@ -386,8 +433,11 @@
- 上述两条分支指令的跳转目标地址计算方式是将指令码中的21比特立即数offs21逻辑左移2位后再符号扩展,所得的偏移值加上该分支指令的PC。 不过需要注意的是,上述指令如果在写汇编时采用直接填入偏移值的方式,则汇编表示中的立即数应填入以字节为单位的偏移值,即指令码中offs21<<2。
#### 普通访存指令
##### LD.{B/H/W/D},ST.{B/H/W/D}
- 格式:
- ld.b rd, rj, si12
- ld.h rd, rj, si12
- ld.w rd, rj, si12
......@@ -398,6 +448,7 @@
- st.d rd, rj, si12
- 概述:
- LD.{B/H/W}从内存取回一个字节/半字/字的数据符号扩展后写入通用寄存器rd, LD.D从内存取回一个双字的数据写入通用寄存器rd。
- ST.{B/H/W/D}将通用寄存器rd中的[7:0]/[15:0]/[31:0]/[63:0]位数据写入内存中
......@@ -405,34 +456,53 @@
- 上述指令的访存地址计算方式是将通用寄存器rj中的值与符号扩展后的12比特立即数Si12相加求和。
#### 宏指令
##### LA.LOCAL
- 格式:
- la.local rd, label
- 概述:
- 将定义在模块内的符号,比如全局变量,加载到通用寄存器rd中。
### 基础浮点数指令
本章将介绍龙芯架构非特权子集基础部分中的浮点数指令。
#### 浮点数据类型
龙芯架构中的基础浮点数指令的功能定义遵循IEEE 754-2008标准。
##### 定点数据类型
部分浮点指令(如浮点转换指令)也会操作定点数据,包括字(Word,简记W,长度32b)、长字 (Longword,简记L,长度64b)。 字和长字数据类型均采用二进制补码的编码方式。
##### 寄存器
浮点数指令编程涉及到寄存器有浮点寄存器(Floating-point Register,简称FR)、条件标志寄存器 (Condition Flag Register,简称CFR)。
### 基础浮点数指令
本章将介绍龙芯架构非特权子集基础部分中的浮点数指令。
#### 浮点数据类型
龙芯架构中的基础浮点数指令的功能定义遵循IEEE 754-2008标准。
##### 定点数据类型
部分浮点指令(如浮点转换指令)也会操作定点数据,包括字(Word,简记W,长度32b)、长字 (Longword,简记L,长度64b)。 字和长字数据类型均采用二进制补码的编码方式。
##### 寄存器
浮点数指令编程涉及到寄存器有浮点寄存器(Floating-point Register,简称FR)、条件标志寄存器 (Condition Flag Register,简称CFR)。
- FR:
- 浮点寄存器共有32个,记为f0-f31,每一个都可以读写。仅当只实现操作单精度浮点数和字整数的浮点指令时,FR的位宽为32比特。通常情况下,FR的位宽为64比特,无论是LA32还是LA64架构。基础浮点数指令与浮点寄存器存在正交关系,即从架构角度而言,这些指令中任一个浮点寄存器操作数都可以采用32个FR中的任一个。
- 当浮点寄存器记录的是一个单精度浮点数或字整数时,数据总会出现在浮点寄存器的[31:0]位上,此时浮点寄存器的[63:32]位可以是任意值。
- CFR:
- 条件标志寄存器CFR共有8个,记为fcc0-fcc7,每一个都可以读写。CFR的位宽为1比特。浮点比较的结果将写入到条件标志寄存器中,当比较结果为真则置1,否则置0。浮点分支指令的判断条件来自于条件标志寄存器。
- 条件标志寄存器CFR共有8个,记为fcc0-fcc7,每一个都可以读写。CFR的位宽为1比特。浮点比较的结果将写入到条件标志寄存器中,当比较结果为真则置1,否则置0。浮点分支指令的判断条件来自于条件标志寄存器。
### 基础浮点指令概述
#### 样例
- [示例四](###示例4),给出了浮点搬运指令movgr2fr,浮点转换指令ffint、浮点比较指令fcmp等浮点指令的使用参考。
- [示例五](###示例5),给出了浮点数的赋值指令使用参考。
#### 浮点运算类指令
#### 浮点运算类指令
##### F{ADD/SUB/MUL/DIV}.{S/D}
- 格式:
- fadd.s fd, fj, fk
- fadd.d fd, fj, fk
......@@ -450,7 +520,9 @@
- 当操作数是单精度浮点数时,结果浮点寄存器的高32位可以是任意值。
#### 浮点比较指令
##### FCMP.cond.{S/D}
- 格式:
- fcmp.cond.s cc, fj, fk
- fcmp.cond.d cc, fj, fk
......@@ -496,55 +568,75 @@
</table>
#### 浮点转换指令(无)
##### FFINT.S.W,FTINT.W.S
- ffint.s.w fd, fj
- ftint.w.s fd, fj
- 概述:
- FFINT.S.W指令选择浮点寄存器fj中的整数型定点数转换为单精度浮点数, 得到的单精度浮点数写入到浮点寄存器fd中。
- FTINT.W.S指令选择浮点寄存器fj中的单精度浮点数转换为整数型定点数, 得到的整数型定点数写入到浮点寄存器fd中。
#### 浮点分支指令
##### BCEQZ,BCNEZ
- 格式:
- bceqz cj, offs21
- bcnez cj, offs21
- 概述:
- ffint.s.w fd, fj
- ftint.w.s fd, fj
- 概述:
- FFINT.S.W指令选择浮点寄存器fj中的整数型定点数转换为单精度浮点数, 得到的单精度浮点数写入到浮点寄存器fd中。
- FTINT.W.S指令选择浮点寄存器fj中的单精度浮点数转换为整数型定点数, 得到的整数型定点数写入到浮点寄存器fd中。
#### 浮点分支指令
##### BCEQZ,BCNEZ
- 格式:
- bceqz cj, offs21
- bcnez cj, offs21
- 概述:
- BCEQZ对条件标志寄存器cj的值进行判断,如果等于0则跳转至目标地址,否则不跳转。
- BCNEZ对条件标志寄存器cj的值进行判断,如果不等干0则跳转到目标地址,否则不跳转。
- 上述两条分支指令的跳转目标地址是将指令码中的21比特立即数。ffs21逻辑左移2位后再符号扩展所得的偏移值加上该分支指令的PC。不过需要注意的是,上述指令如果在写汇编时采用直接填入偏移值的方式,则汇编表示中的立即数应填入以字节为单位的偏移值,即指令码中offs21<<2。
#### 浮点搬运指令
##### FMOV.{S/D}
- 格式:
- fmov.s fd, fj
- fmov.d fd, fj
- 概述:
#### 浮点搬运指令
##### FMOV.{S/D}
- 格式:
- fmov.s fd, fj
- fmov.d fd, fj
- 概述:
- FMOV.{S/D}将浮点寄存器fj的值按单精度/双精度浮点数格式写入到浮点寄存器fd中,如果fj的值不是单精度/双精度浮点数格式,则结果不确定。
##### MOVGR2FR.{W/D},MOVGR2FRH.W
- 格式:
- movgr2fr.w fd, rj
- movgr2fr.d fd, rj
- movgr2frh.w fd, rj
- 概述:
- MOVGR2FR.W将通用寄存器rj的低32位值写入浮点寄存器fd的低32位中。若浮点寄存器位宽为64位,则fd的高32位值不确定。
- MOVGR2FRH.W将通用寄存器rj的低32位值写入浮点寄存器fd的高32位中,浮点寄存器fd的低32位值不变。
- MOVGR2FR.D将通用寄存器rj的64位值写入浮点寄存器fd。
##### MOVFR2GR.{S/D},MOVFRH2GR.S
##### MOVFR2GR.{S/D},MOVFRH2GR.S
- 格式:
- movfr2gr.s rd, fj
- movfr2gr.d rd, fj
- movfrh2gr.s rd, fj
- 概述:
- MOVFR2GR/MOVFRH2GR.S将浮点寄存器fj的低32位/高32位值符号扩展后写入通用寄存器rd。
- MOVFR2GR.D将浮点寄存器fj的64位值写入通用寄存器rd。
#### 浮点访存指令
##### FLD.{S/D},FST.{S/D}
#### 浮点访存指令
##### FLD.{S/D},FST.{S/D}
- 格式:
- fld.s fd, rj, si12
- fld.d fd, rj, si12
- fst.s fd, rj, si12
- fst.d fd, rj, si12
- 概述:
- FLD.S从内存取回一个字的数据写入浮点寄存器fd的低32位。若浮点寄存器位宽为64位,则fd的高32位值不确定。
- FLD.D从内存取回一个双字的数据写入浮点寄存器fd。
- FST.S将浮点寄存器fd中低32位字数据写入到内存中。
......@@ -558,7 +650,9 @@
- [参数传递](###参数传递)节给出了参数传递、调用返回值保存时的具体寄存器调用规范。也可参考[样例](###示例1)对寄存器的使用;
- 表中Name为寄存器名,Alias为别名,便于理解和记忆,在汇编程序中可使用寄存器名、也可使用对应的别名来指代相应寄存器。
- Preserved across calls表示函数调用过程中是否需要由调用者保存,Unused表示该寄存器不应该被汇编代码修改;Yes表示应该由调用者保存;No表示不需要由调用者保存。
<table>
<tr>
<th>Name</th><th>Alias</th><th>Usage</th><th>Preserved across calls</th>
......@@ -593,19 +687,23 @@
<tr>
<td>$r23-$r31</td><td>$s0-$s8</td><td>Subroutine register variables</td><td>Yes</td>
</tr>
</table>
</table>
- \$ra 是⼀个临时寄存器。控制流到达callee瞬间的 \$ra 的值是callee的返回地址,且这个值只会被备份在栈帧中。callee返回指令是 jirl \$r0,\$ra,0。
- \$tp ⽤于⽀持 TLS(thread-local storage),\$tp 由c库维护,故⽤户程序不应修改这个寄存器。
- \$sp 在整个程序执⾏过程中恒指向调⽤栈栈顶,是⼀个保存寄存器。控制流到达callee瞬间的\$sp的值被称作 \$sp on entry。
- \$fp 是⼀个保存寄存器,但如果过程的栈帧可变,编译器会使⽤这个寄存器作为 frame pointer(栈帧不变部分的地址加常数偏移量,也指存放该值的寄存器。 \$sp on entry是⼀个frame pointer。栈帧不变的函数的frame pointer是\$sp)。
- Unused指编译器在分配寄存器时,不会去分配该寄存器;或者约定作专⽤寄存器。
### 浮点寄存器调用约定
- 表中Name为寄存器名,Alias为别名,便于理解和记忆,在汇编程序中可使用寄存器名、也可使用对应的别名来指代相应寄存器。
- Preserved across calls表示函数调用过程中是否需要由调用者保存,Unused表示该寄存器不应该被汇编代码修改;Yes表示应该由调用者保存;No表示不需要由调用者保存。
<table>
<tr>
<th>Name</th><th>Alias</th><th>Usage</th><th>Preserved across calls</th>
......@@ -619,7 +717,7 @@
<tr>
<td>$f8-$f23</td><td>$ft0-$ft15</td><td>Temp Registers</td><td>No</td>
</tr>
</table>
</table>
## 函数调用约定
......@@ -628,29 +726,37 @@
- 整个函数中,堆栈从高地址增长到低地址。ABI使用两个寄存器来访问堆栈:指向帧底部的帧指针(\%fp),和堆栈指针(\%sp)指向帧的顶部。下图显示了框架的布局。正常情况下,帧指针用于寻址帧中的数据,例如传入参数和本地变量。
- 堆栈指针应该对⻬到⼀个128位的边界上作为函数⼊⼝。在堆栈上传递的第⼀个实参位于函数⼊⼝的堆栈指针偏移量为零的地⽅;后⾯的参数存储在更⾼的地址中。
![img1_4](img1_4.PNG)
### 函数调用过程
![img1_4](img1_4.PNG)
### 函数调用过程
函数调用的过程由调用者和被调用者共同完成。
#### 调用方调用
调用方的任务包括保存涉及跨调用过程中任何应由调用方保存的寄存器、将参数加载到适当的寄存器和堆栈位置,然后执行跳转指令。
```
bl function #bl指令:执行跳转,并将PC+4写入$ra
```
#### 被调用方进入
进入时,被调用方需要初始化其堆栈帧。此初始化由以下顺序完成:
```
addi.d $sp,$sp,-N #分配栈帧,注意堆栈指针需要对齐到16字节
st.d $ra,$sp,N-8 #将返回地址的内容压栈
addi.d $fp,$sp,N #赋给$fp当前的栈底值
...
```
这里的N指的是被调用方分配的栈帧大小。
#### 被调用方退出
一旦调用过程完成,被调用方将执行退出。这包括将结果放入对应寄存器,释放堆栈帧,并将控制返回调用方。
```
...
or $v0,$t0,$r0 #返回结果保存至$v0
......@@ -660,10 +766,13 @@ jr $ra #根据$ra的地址返回
```
#### 调用方还原
如果调用方在每次调用的基础上为参数分配堆栈空间,那么它负责返回时释放空间。
### 参数传递
#### 整形调用规范
- 基本整型调⽤规范提供了8个参数寄存器a0-a7⽤于参数传递,前两个参数寄存器a0-a1也⽤于返回值;若a0-a7数目不够时,使用栈进行数据传输。
- 若⼀个标量位宽GRLEN(即寄存器宽度,LA64下为64位),则它在单个参数寄存器中传递,若没有可⽤的寄存器,则在堆栈中传递;传递到堆栈上的标量会对⻬到类型对⻬(type alignment)和GRLEN中的较⼤者,但不会超过堆栈对⻬。当整型参数传⼊寄存器或堆栈时,⼩于GRLEN 位的整型标量根据其类型的符号扩展⾄32位,然后符号扩展为GRLEN位。当浮点型参数传⼊寄存器或堆栈时,⽐GRLEN位窄的浮点类型将被扩展为GRLEN位,⽽⾼位为未定义位。
- 通过引⽤传递的实参可以由被调⽤⽅修改。
......@@ -671,11 +780,11 @@ jr $ra #根据$ra的地址返回
- 函数(procedure)所依赖的数据必须位于函数栈帧范围之内。
#### 浮点调用规范
- 浮点参数寄存器共8个,为fa0-fa7,其中fa0和fa1也⽤于传递返回值。需要传递的值在任何可能的情况下都可以传递到浮点寄存器中,与整型参数寄存器a0-a7是否已经⽤完⽆关。
- 若⼀个浮点实数参数不超过FLEN位宽(FLEN指的是ABI中的浮点寄存器的宽度),并且⾄少有⼀个浮点参数寄存器可⽤,则将这个浮点实数参数传递到浮点参数寄存器中。否则,它将根据整型调⽤规范传递。当⼀个⽐FLEN位更窄的浮点参数在浮点寄存器中传递时,它会扩展(NaN-Boxed)到FLEN位。
- 返回值的传递⽅式与传递第⼀个同类型命名参数的⽅式相同。
### 函数值的返回
- 如果返回值类型是pointer或整型scalar type,则返回值经符号扩展或零扩展后放⼊ $v0 中。
......@@ -710,7 +819,9 @@ main: # 标记程序入口
addi.d $sp, $sp, 16 # 释放栈空间(必需)
jr $ra # 返回调用main函数的父函数(必需)
```
在执行完毕指令`addi.w $a0, $zero, 0`时,我们通过gdb查看当前函数的栈帧:
```
(gdb) i frame
Stack level 0, frame at 0xffffff3410:
......@@ -721,18 +832,22 @@ Stack level 0, frame at 0xffffff3410:
Saved registers:
pc at 0xffffff3408
```
可以看到当前\$sp寄存器的内容为0xffffff3400,\$fp的内容为0xffffff3410。
再来查看当前函数栈空间的内容:
```
(gdb) x/4x $sp #从$sp地址开始,向后显示4个单元按16进制格式输出的内容。
0xffffff3400: 0x00000000 0x00000000 0xf7e80774 0x000000ff #可以看到$sp+8 处的内容为0xfff7e80774,即保存的返回地址。
```
可见返回地址通过指令`st.d $ra, $sp, 8`被保存在栈空间中。
此时栈的状态如图。
![img_11](img1_10.PNG)
### 示例2
```c
int a;
int main() {
......@@ -742,6 +857,7 @@ int main() {
return 0;
}
```
```c
.text # 标记代码段
.comm a, 4 # 为符号a分配4字节长度的未初始化内存空间
......@@ -769,13 +885,13 @@ main: # 标记程序入口
addi.d $sp, $sp, 16 # 释放栈空间(必需)
jr $ra # 返回调用main函数的父函数(必需)
```
在执行完毕指令`stptr.w $t4, $t0, 0`时此时栈的状态如图。
![img_10](img1_10.PNG)
### 示例3
```c
int add(int a, int b) {
return a + b;
}
......@@ -787,8 +903,8 @@ int main() {
output(c);
return 0;
}
```
```c
.text # 标记代码段
.globl add # 标记 add 全局可见(必需)
......@@ -821,13 +937,15 @@ main:
ld.d $ra, $sp, 8 # 恢复返回地址(必需)
addi.d $sp, $sp, 16 # 释放栈空间(必需)
jr $ra # 返回调用main函数的父函数(必需)
```
在运行到add函数的`add.d $a0, $a0, $a1 # 计算 a0 + a1,函数返回值存储到 a0 中`处时,堆栈的状态如图。
![img_13](img1_13.PNG)
### 示例4
b和c在汇编程序中采用了不同的方式进行赋值,可供参考;变量b通过ffint将定点数格式的内容转换为浮点数格式,完成赋值;变量c通过lu12i.w指令进行了赋值。
```
int main () {
float b = 8;
......@@ -839,6 +957,7 @@ int main () {
return 0;
}
```
```
.text
.globl main
......@@ -865,9 +984,8 @@ main:
addi.d $sp, $sp, 32
jr $ra
```
###
###
### 在LA64下的编译与调试
......@@ -880,14 +998,10 @@ main:
在龙芯服务器麒麟系统下编译命令(假设汇编程序为test.s,生成可执行文件test)
`gcc src/io/io.c test.s -g -o test`
##### 不需要调试
`gcc src/io/io.c test.s -o test`
#### 调试
gdb调试
......
#ifndef CODEGEN_HPP
#define CODEGEN_HPP
#include "Function.h"
#include "Instruction.h"
#include "Module.h"
#include "Value.h"
#include "logging.hpp"
#include <map>
#define STACK_ALIGN(x) (((x / 16) + (x % 16 ? 1 : 0)) * 16)
using std::map;
using std::string;
using std::vector;
......@@ -24,6 +32,70 @@ class CodeGen {
void run();
private:
void IR2assem(Instruction &);
void IR2assem(ReturnInst *);
void IR2assem(LoadInst *);
void IR2assem(StoreInst *);
void IR2assem(BranchInst *) {}
void IR2assem(BinaryInst *) {}
void IR2assem(AllocaInst *) {}
void IR2assem(PhiInst *) {}
void IR2assem(CallInst *) {}
void IR2assem(GetElementPtrInst *);
void IR2assem(ZextInst *) {}
void IR2assem(FpToSiInst *) {}
void IR2assem(SiToFpInst *) {}
void stackMemAlloc();
void stackMemDealloc();
// load value `opk` to the specified register
// - for constant number, just load into reg
// - for global variables and pointers from alloca and GEP, read through
// address
// only use register a_id and t_
void value2reg(Value *, int id = 0);
// load the content in ptr to specified register.
// only use register a_id and t_
void ptrContent2reg(Value *, int id = 0);
int typeLen(Type *type) {
if (type->is_float_type())
return 4;
else if (type->is_integer_type()) {
if (static_cast<IntegerType *>(type)->get_num_bits() == 32)
return 4;
else
return 1;
} else if (type->is_pointer_type())
return 8;
else if (type->is_array_type()) {
auto arr_tp = static_cast<ArrayType *>(type);
int n = arr_tp->get_num_of_elements();
return n * typeLen(arr_tp->get_element_type());
} else
assert(false && "unexpected case while computing type-length");
}
string suffix(int len) {
switch (len) {
case 1:
return ".b";
case 2:
return ".h";
case 4:
return ".w";
case 8:
return ".d";
}
assert(false && "no such suffix");
}
std::map<Value *, unsigned int> off;
unsigned int N;
Function *cur_func;
Module *m;
vector<string> output;
};
......
// 局部变量究竟保存在哪里?
// alloca会返回一个指针,这个指针指向一块可用的内存区域
// 这个该如何体现在assemb上?
// NOTE: 参数类型需要额外考虑!
#include "codegen.hpp"
#include "Constant.h"
#include "GlobalVariable.h"
#include "Instruction.h"
#include "Type.h"
#include "Value.h"
#include <string>
// $r0 $zero constant 0
// $r1 $ra return address
// $r2 $tp thread pointer
......@@ -11,6 +25,8 @@
// $r22 $fp frame pointer
// $r23 - $r31 $s0 - $s8 static
using std::to_string;
class Reg {
public:
Reg(int index) : id(index) {}
......@@ -27,32 +43,256 @@ class Reg {
if (id == 3)
return "$sp";
if (4 <= id and id <= 11)
return "$a" + std::to_string(id - 4);
return "$a" + to_string(id - 4);
if (12 <= id and id <= 20)
return "$t" + std::to_string(id - 12);
return "$t" + to_string(id - 12);
if (id == 22)
return "$fp";
assert(false);
}
};
void CodeGen::run() {
void
CodeGen::run() {
// TODO: implement
// 以下内容生成 int main() { return 0; } 的汇编代码
output.push_back(".text");
for (auto &func : m->get_functions())
// global variables
for (auto &globl : m->get_global_variable()) {
auto type = globl.get_type()->get_pointer_element_type();
output.push_back(".comm " + globl.get_name() + ", " +
to_string(typeLen(type)));
}
// funtions
for (auto &func : m->get_functions()) {
if (not func.is_declaration()) {
cur_func = &func;
output.push_back("");
output.push_back(".globl " + func.get_name());
output.push_back(".type " + func.get_name() + ", @function");
output.push_back(func.get_name() + ":");
output.push_back("addi.d $sp, $sp, -16");
output.push_back("st.d $ra, $sp, 8");
output.push_back("addi.d $fp, $sp, 16");
// sp and fp balabala
stackMemAlloc();
for (auto &bb : func.get_basic_blocks()) {
if (&bb == func.get_entry_block())
output.push_back(func.get_name() + "_entry:");
else
output.push_back(func.get_name() + "_" +
bb.get_name().substr(5) + ":");
for (auto &instr : bb.get_instructions()) {
IR2assem(instr);
}
}
// restore the stack
stackMemDealloc();
} else {
/* output.push_back(".globl " + func.get_name());
* output.push_back(".type " + func.get_name() + ", @function"); */
}
}
}
void
CodeGen::ptrContent2reg(Value *ptr, int id) {
auto reg_name = "$a" + to_string(id);
if (dynamic_cast<GlobalVariable *>(ptr)) {
auto type = static_cast<GlobalVariable *>(ptr)
->get_type()
->get_pointer_element_type();
auto suff = suffix(typeLen(type));
output.push_back("la.local " + reg_name + ", " + ptr->get_name());
output.push_back("ldx" + suff + " " + reg_name + ", " + reg_name +
", $r0");
} else if (dynamic_cast<AllocaInst *>(ptr)) {
auto alloc_instr = static_cast<AllocaInst *>(ptr);
string suff = suffix(typeLen(alloc_instr->get_alloca_type()));
output.push_back("ld" + suff + " " + reg_name + ", $fp, -" +
to_string(off[ptr]));
} else if (dynamic_cast<GetElementPtrInst *>(ptr)) {
auto GEP_instr = static_cast<GetElementPtrInst *>(ptr);
auto suff = suffix(typeLen(GEP_instr->get_element_type()));
output.push_back("ld.d " + reg_name + ", $fp, -" + to_string(off[ptr]));
output.push_back("ldx" + suff + " " + reg_name + ", " + reg_name +
", $r0");
} else
assert(false && "unknown type");
}
void
CodeGen::value2reg(Value *v, int id) {
auto reg_name = "$a" + to_string(id);
if (dynamic_cast<Constant *>(v)) {
auto constant = static_cast<Constant *>(v);
if (dynamic_cast<ConstantInt *>(constant)) {
int k = static_cast<ConstantInt *>(constant)->get_value();
if ((k & 0xfff) != k) {
output.push_back("lui.w " + reg_name + ", " +
to_string(k >> 12));
output.push_back("ori " + reg_name + ", " + reg_name + ", " +
to_string(k & 0xfff));
} else
output.push_back("ori " + reg_name + ", $r0, " + to_string(k));
} else
assert(false && "wait for completion");
} else if (dynamic_cast<GlobalVariable *>(v)) {
output.push_back("la.local " + reg_name + ", " + v->get_name());
} else if (dynamic_cast<AllocaInst *>(v)) {
// auto alloc_instr = dynamic_cast<AllocaInst *>(v);
output.push_back("addi.d " + reg_name + ", $fp, -" + to_string(off[v]));
} else {
output.push_back("ld" + suffix(typeLen(v->get_type())) + " " +
reg_name + ", $fp, -" + to_string(off[v]));
}
}
output.push_back("addi.w $a0, $zero, 0");
output.push_back("ld.d $ra, $sp, 8");
output.push_back("addi.d $sp, $sp, 16");
void
CodeGen::stackMemDealloc() {
output.push_back("# epilog");
// 7: return value should be determined already!
// output.push_back("addi.w $a0, $zero, 0");
output.push_back(cur_func->get_name() + "_end:");
output.push_back("ld.d $ra, $fp, -8");
output.push_back("addi.d $sp, $sp, " + to_string(N));
output.push_back("jr $ra");
}
// the addr for opk is: fp - off[opk]
void
CodeGen::stackMemAlloc() {
// preserved for ra
N = 8;
off.clear();
for (auto &bb : cur_func->get_basic_blocks())
for (auto &instr : bb.get_instructions()) {
if (not instr.is_void()) {
if (instr.is_alloca()) {
auto alloc_instr = static_cast<AllocaInst *>(&instr);
N += typeLen(alloc_instr->get_alloca_type());
} else {
auto type = instr.get_type();
N += typeLen(type);
}
off[&instr] = N;
}
}
N = STACK_ALIGN(N);
output.push_back("# prolog");
output.push_back("addi.d $sp, $sp, -" + to_string(N));
output.push_back("addi.d $fp, $sp, " + to_string(N));
output.push_back("st.d $ra, $fp, -8");
}
void
CodeGen::IR2assem(GetElementPtrInst *instr) {
value2reg(instr->get_operand(0), 0);
Type *type = instr->get_operand(0)->get_type();
for (int i = 1; i < instr->get_num_operand(); i++) {
int size;
if (type->is_array_type()) {
size = type->get_array_element_type()->get_size();
type = type->get_array_element_type();
} else if (type->is_pointer_type()) {
size = type->get_size();
type = type->get_pointer_element_type();
} else
assert(false && "GEP translation error");
value2reg(instr->get_operand(i), 1);
output.push_back("mul.w $a1, $a1, " + to_string(size));
output.push_back("add.d $a0, $a0, $a1");
}
}
void
CodeGen::IR2assem(LoadInst *instr) {
// move the address to a0
ptrContent2reg(instr->get_lval());
string suff = suffix(typeLen(instr->get_load_type()));
string addr = "$fp, -" + to_string(off[instr]);
output.push_back("st" + suff + " $a0, " + addr);
}
void
CodeGen::IR2assem(StoreInst *instr) {
value2reg(instr->get_rval());
value2reg(instr->get_lval(), 1);
string suff = suffix(typeLen(instr->get_rval()->get_type()));
output.push_back("st" + suff + " $a0, $a1, 0");
}
void
CodeGen::IR2assem(ReturnInst *instr) {
if (not instr->is_void_ret()) {
auto value = instr->get_operand(0);
value2reg(value);
}
output.push_back("b " + cur_func->get_name() + "_end");
}
void
CodeGen::IR2assem(Instruction &instr) {
output.push_back("# " + instr.print());
switch (instr.get_instr_type()) {
case Instruction::ret:
IR2assem(static_cast<ReturnInst *>(&instr));
return;
case Instruction::br:
IR2assem(static_cast<BranchInst *>(&instr));
return;
// Standard binary operators
case Instruction::add:
break;
case Instruction::sub:
break;
case Instruction::mul:
break;
case Instruction::sdiv:
break;
// float binary operators
case Instruction::fadd:
break;
case Instruction::fsub:
break;
case Instruction::fmul:
break;
case Instruction::fdiv:
break;
// Memory operators
case Instruction::alloca:
IR2assem(static_cast<AllocaInst *>(&instr));
return;
case Instruction::load:
IR2assem(static_cast<LoadInst *>(&instr));
return;
case Instruction::store:
IR2assem(static_cast<StoreInst *>(&instr));
return;
// Other operators
case Instruction::cmp:
break;
case Instruction::fcmp:
break;
case Instruction::phi:
IR2assem(static_cast<PhiInst *>(&instr));
return;
case Instruction::call:
IR2assem(static_cast<CallInst *>(&instr));
return;
case Instruction::getelementptr:
IR2assem(static_cast<GetElementPtrInst *>(&instr));
return;
case Instruction::zext:
IR2assem(static_cast<ZextInst *>(&instr));
return;
case Instruction::fptosi:
IR2assem(static_cast<FpToSiInst *>(&instr));
return;
case Instruction::sitofp:
IR2assem(static_cast<SiToFpInst *>(&instr));
return;
}
// assert(false && "This ")
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment