Commit 258a7391 authored by 甘文迪's avatar 甘文迪

lab5 publish

parent 556544ce
build /build/
.clangd
compile_commands.json
/.vscode/
/.cache/
test.c
test
*.s
# Lab5 实验文档
- [Lab5 实验文档](#lab5-实验文档)
- [0. 前言](#0-前言)
- [主要工作](#主要工作)
- [1. 实验框架](#1-实验框架)
- [2. 实验内容](#2-实验内容)
- [阅读龙芯的文档](#阅读龙芯的文档)
- [完成代码](#完成代码)
- [3. 运行与调试](#3-运行与调试)
- [龙芯服务器的登录](#龙芯服务器的登录)
- [汇编代码的生成](#汇编代码的生成)
- [调试生成的汇编](#调试生成的汇编)
- [测试](#测试)
- [4. 提交](#4-提交)
- [目录结构](#目录结构)
- [提交要求、评分标准](#提交要求评分标准)
- [附录:栈分配策略](#附录栈分配策略)
- [示例 1:计算加法并输出](#示例-1计算加法并输出)
- [示例 2:调用函数访问数组](#示例-2调用函数访问数组)
## 0. 前言
经过前四个Lab,我们的cminus compiler已经可以生成有效的llvm IR。Lab5实验目的,需要你根据 IR 生成龙芯LA64架构下的可正常运行的汇编代码。
### 主要工作
1. 阅读龙芯LA64的ABI,熟悉了解龙芯loongArch64(简称LA64)架构。
2. 完成 `codegen`,由 IR 生成汇编代码。
3. 进行寄存器分配,优化性能(选做)。
4. 工作总结及功能汇报展示。
## 1. 实验框架
需要在现有框架的基础上,添加 `codegen` 模块,实现 IR 到汇编的转换;如果要优化性能,可以添加寄存器分配的模块。
## 2. 实验内容
### 阅读龙芯的文档
+ [龙芯官方文档](https://github.com/loongson/LoongArch-Documentation/releases/latest/download/LoongArch-Vol1-v1.02-CN.pdf)
+ [龙芯 ABI 文档](龙芯扩展实验ABI/龙芯扩展实验ABI.md)
可以思考以下问题:
1. 当函数参数个数大于 8 个时,如何传递参数?
2. 浮点常量、全局变量是如何存储的?
> 可以写一段 C 语言代码,在龙芯上用 `gcc -S` 生成汇编代码,观察汇编代码,思考如何实现。
### 完成代码
可以先使用[栈分配策略](#附录栈分配策略)存储变量,优先追求代码生成的完成度。
也可以在完成基本的代码生成后,使用专门的寄存器分配算法,优化性能。
> 寄存器分配部分可以参考 [寄存器分配.md](寄存器分配.md)
评测、答辩将会考虑以下两个方面:
1. 代码生成的完成度(允许自由选择和组合):
+ 单个 main 函数的代码的汇编代码生成,主要包括算术指令、调用 `output` 函数,正常返回。
+ 单个 main 函数的代码的汇编代码生成,包括比较和跳转指令、局部数组访问。
+ 多个函数的代码的汇编代码生成,包括函数调用、返回。
+ 支持浮点数、全局变量等高级类型
> 允许自由拓展
2. 是否完成寄存器分配:
+ 不进行专门的寄存器分配算法(图着色、线性分配等),所有变量都存储在栈上(详见[附录](#附录栈分配策略))。
+ 完成专门的寄存器分配算法
## 3. 运行与调试
### 龙芯服务器的登录
根据助教提供的账号,密码,采用 ssh 登录龙芯机器。
### 汇编代码的生成
编译过程:
```bash
rm -rf build
mkdir build
cd build
cmake ..
make
```
编译后会产生 `cminusfc`可执行文件,使用 `cminusfc -S test.cminus` 编译 `test.cminus`源文件,生成 `test.s`汇编文件。
> 可以开启 `-mem2reg` 选项来简化 IR
学生可以本地运行生成汇编代码,然后传输到龙芯服务器,代码如下:
```bash
scp src/io/io.c username@202.38.75.246
scp test.s username@202.38.75.246
ssh username@202.38.75.246
# 以下命令在 ssh 会话中执行
gcc test.s io.c -o test
./test
echo $?
```
### 调试生成的汇编
在龙芯服务器,loongnix系统上将源代码编译为汇编代码命令如下,
```
gcc test.s io.c -Wa,--gdwarf2 -o test # 也可以利用静态链接库进行编译
gdb test
```
> gdb 常用命令:
> ```
> b 20 # 在第 20 行设置断点
> r # 运行程序
> n # 单步执行
> c # 继续执行
> i r # 查看寄存器
> ```
### 测试
测试分为两个部分:
- 功能测试:测试 `cminusfc` 是否能够正确生成汇编代码
- 性能测试:测试 `cminusfc` 生成的汇编代码的性能
测试样例、评测脚本见 `tests/5-bonus` 目录。
评测脚本的使用说明见 [评测脚本.md](评测脚本.md)
## 4. 提交
### 目录结构
与本次实验相关的目录结构如下:
```bash
.
├── CMakeLists.txt
├── Documentations
│   ├── ...
│   └── 5-bonus
│   ├── README.md # Lab5 实验文档说明(你在这里)
│   └── 寄存器分配引论17-19.pdf # 寄存器分配相关资料
├── include
│   ├── ...
│   └── codegen.hpp # 代码生成相关头文件
├── Reports
│   ├── ...
│   └── 5-bonus
│   └── report.md # lab5 需提交的实验报告(需要上交)
├── src
│   ├── ...
│   └── codegen
│   ├── CMakeLists.txt
│   └── codegen.cpp # 代码生成相关实现,包含简单的代码生成示例
└── tests
├── ...
└── 5-bonus
   ├── test.py # 助教提供的测试脚本
   └── testcases # 助教提供的测试样例
```
如果需要,可以添加新的文件,或者修改已有文件。
### 提交要求、评分标准
- 提交要求:
需要提交实验代码、实验报告。答辩时间待定
- 提交方式:
- 代码提交:在希冀课程平台上发布的作业中填写 GitLab 链接。
如果遇到在平台上提交异常的问题,请通过邮件联系助教,助教将收取截止日期之前,学生在 gitlab 仓库最近一次 commit 内容进行评测。
- 报告提交:`将 Reports/5/report.md` 导出成 pdf 文件
- 截止时间:
2023 年 3 月初,具体时间待定
- 评分标准:
- 实验分数组成如下:
测试用例的通过情况
实验报告
答辩情况
- 禁止执行恶意代码,违者本次实验0分处理
- 关于抄袭和雷同
经过助教和老师判定属于实验抄袭或雷同情况,所有参与方一律零分,不接受任何解释和反驳。
如有任何问题,可以通过邮件联系助教。
## 附录:栈分配策略
主要思想是:
把所有变量存储在内存中,仅当需要计算时,才将变量临时加载到寄存器上,完成后写回内存。
> 可以参考《寄存器分配引论》中的[相关章节](寄存器分配引论17-19.pdf)
### 示例 1:计算加法并输出
```c
int main(void) {
int a;
int b;
int c;
a = 17;
b = 34;
c = a + b;
output(c);
return 0;
}
```
以下是 gcc 生成的汇编代码(经过一定更改):
```mips
.text
.globl main
.type main, @function
main:
addi.d $sp, $sp, -32 # 更改栈指针
st.d $ra, $sp, 24 # 保存返回地址
st.d $fp, $sp, 16 # 保存帧指针
addi.d $fp, $sp, 32 # fp = sp + 32 当前函数栈帧底位置
# a = 17
addi.w $t0, $zero, 17 # t0 = 0x11
st.w $t0, $fp, -20 # fp-20 = t0,将值保存在栈上
# b = 34
addi.w $t0, $zero, 34 # t0 = 0x22
st.w $t0, $fp, -24 # fp-24 = t0
# c = a + b
ld.w $t1, $fp, -20 # t1 = fp-20
ld.w $t0, $fp, -24 # t0 = fp-24
add.w $t0, $t1, $t0 # t0 = t1 + t0
st.w $t0, $fp, -28 # fp-28 = t0
# output(c)
ldptr.w $t0, $fp, -28 # t0 = fp-28
or $a0, $t0, $zero # a0 = t0
bl output # 调用 output
# return 0
or $t0, $zero, $zero # t0 = 0
or $a0, $t0, $zero # 设置返回值 a0 = t0 在此处设置断点
ld.d $ra, $sp, 24 # 恢复返回地址
ld.d $fp, $sp, 16 # 恢复帧指针
addi.d $sp, $sp, 32 # 恢复栈指针
jr $ra # 跳转到返回地址
```
在程序设置返回值之后,恢复返回地址、帧指针、栈指针的寄存器前,设置断点,可以观察到栈中的变量值:
> 地址以 4 字节为单位
```
地址 内容 地址与寄存器的关系 描述
ffffff3190 0 fp
ffffff318c ff
ffffff3188 f7e7c774 sp+24 存储 main 函数的返回地址 fff7e7c774,位于 __libc_start_main+228
ffffff3184 0
ffffff3180 0 sp+16 存储原来的 fp = 0
ffffff317c 11 fp-20 存储 a
ffffff3178 22 fp-24 存储 b
ffffff3174 33 fp-28 存储 c = a + b
ffffff3170 f7fd4598 sp
```
### 示例 2:调用函数访问数组
```c
void store(int arr[], int index, int value) {
arr[index] = value;
}
int main(void) {
int arr[10];
store(arr, 5, 17);
}
```
```mips
.text
.globl store
.type store, @function
store:
addi.d $sp, $sp, -32 # 更改栈指针
st.d $fp, $sp, 24 # 保存帧指针
addi.d $fp, $sp, 32 # fp = sp + 32
st.d $a0, $fp, -24 # fp-24 = a0 保存第一个参数 arr
or $t1, $a1, $zero # t1 = a1
or $t0, $a2, $zero # t0 = a2
slli.w $t1, $t1, 0 # t1 = t1 << 0 因为 int 是 32 位,而寄存器是 64 位,gcc 选择保留低 32 位,如果寄存器高 32 位为 0 可以忽略这一步
st.w $t1, $fp, -28 # fp-28 = t1 保存第二个参数 index
slli.w $t0, $t0, 0
st.w $t0, $fp, -32 # fp-32 = t0 保存第三个参数 value
# arr[index] = value
ldptr.w $t0, $fp, -28 # t0 = fp-28 取出 index
slli.d $t0, $t0, 2 # t0 = t0 << 2
ld.d $t1, $fp, -24 # t1 = fp-24 取出 arr
add.d $t0, $t1, $t0 # t0 = t1 + t0
ld.w $t1, $fp, -32 # t1 = fp-32 取出 value
stptr.w $t1, $t0, 0 # t0[0] = t1 arr[index] = value 在此处设置断点
ld.d $fp, $sp, 24 # 恢复帧指针
addi.d $sp, $sp, 32 # 恢复栈指针
jr $ra # 跳转到返回地址
.globl main
.type main, @function
main:
addi.d $sp, $sp, -64 # 更改栈指针
st.d $ra, $sp, 56 # 保存返回地址
st.d $fp, $sp, 48 # 保存帧指针
addi.d $fp, $sp, 64 # fp = sp + 64
# store(arr, 5, 17);
addi.d $t0, $fp, -56 # t0 = fp-56 arr 的地址
addi.w $a2, $zero, 17 # a2 = 17 value = 0x11
addi.w $a1, $zero, 5 # a1 = 5 index = 5
or $a0, $t0, $zero # a0 = t0 arr
bl store # 调用 store
# return 0
or $t0, $zero, $zero # t0 = 0
or $a0, $t0, $zero # 设置返回值 a0 = t0
ld.d $ra, $sp, 56 # 恢复返回地址
ld.d $fp, $sp, 48 # 恢复帧指针
addi.d $sp, $sp, 64 # 恢复栈指针
jr $ra # 跳转到返回地址
```
在执行完 store 函数中的 `stptr` 后,恢复帧指针和栈指针前,栈的情况如下:
> 地址以 8 字节为单位
```
地址 内容 地址与寄存器的关系 描述
ffffff3190 0 原fp
ffffff3188 fff7e7c774 原sp+56 存储 main 函数的返回地址,位于 __libc_start_main+228
ffffff3180 0 原sp+48 存储 main 函数保存的 fp
ffffff3178 120000920 存储 arr[8], arr[9]
ffffff3170 fff7fd4598 存储 arr[6], arr[7]
ffffff3168 11ffff32f8 存储 arr[4], arr[5],可以看到 arr[5] 变成了 0x11
ffffff3160 fff7fd45c0 存储 arr[2], arr[3]
ffffff3158 fff7fd4618 原fp-56 存储 arr[0], arr[1]
ffffff3150 228 原sp fp
ffffff3148 ffffff3190 sp+24 存储 main 函数的 fp,是下面原fp 的地址
ffffff3140 fff7fc0ea8
ffffff3138 ffffff3158 fp-24 存储 arr 的地址,即下面 arr 的地址
ffffff3130 500000011 sp fp-32 存储 value 和 index value=0x11 位于 fp-32,index=5 位于 fp-28
```
此时 RA=120000820,为 main 函数中 `call store` 的下一条指令的地址
# 寄存器分配
寄存器分配算法类型:
1. 线性扫描
1. Poletto
2. lifetime hole
3. linear scan on SSA
2. 图着色
1. Chaitin
2. Graph Coloring on SSA
3. Other techniques
1. Rematerialization
2. pbqp
# Linear Scan
线性扫描由Poletto等人在1999年首次提出。
线性扫描分配寄存器前需要
1. 对基本块进行排序,给出一个线性序(Depth-First order较优)
2. 决定变量的活跃区间(活跃变量分析)
[活跃区间的定义]
变量v的活跃区间为[i,j]表示在任意i'<i或者j'>j的指令,v均不为活跃变量。但在[i,j]内,可能有部分指令处,v也不为活跃变量。
## Poletto
Poletto版本的线性扫描不是在SSA形式上的,需要进行Phi指令消除(同Lab4)
算法介绍如下:
1.前置条件:给定R个可用寄存器和一个活跃区间列表(包含所有变量的活跃区间),该列表以起点(即[i,j]中的i)递增的顺序排列。
2.伪代码:
```C++
//线性扫描算法
LinearScan:
active <- {}
//维护有序列表active,存储当前在寄存器中的变量的活跃区间,按终点([i,j]中的j)递增顺序排列
for each live interval i, in order of increasing start point
//遍历所有变量的活跃区间
ExpireOldIntervals(i)
//移除所有过期的区间
if length(active) = R then //表示此时所有寄存器均不空闲
SpillAtInterval(i)
//溢出一个当前活跃区间
else //存在空闲的寄存器
register[i] <- a register removed from pool of free registers
//为变量i赋予一个空闲的寄存器
add i to active, sorted by incresing end point
//将i的活跃区间加入active,并排序
//移除所有过期的区间:
//过期区间:终点在新区间起点之前的区间
ExpireOldIntervals(i):
for each interval j in active, in order of increasing end point
//遍历列表active
if endpoint[j] ≥ startpoint[i] then
//当找到一个未过期区间后退出循环
return
remove j from active
//在active中移除过期区间
add register[j] to pool of free registers
//释放过期区间对应的寄存器
//溢出一个当前活跃区间:
//决定要溢出哪个区间有多种选择方法,此处我们选择溢出终点最晚的区间(即列表active中最后的区间或者新区间)
SpillAtInterval(i):
spill <- last interval in active
//spill初始化为active中最后的区间
//比较列表active中最后的区间和新区间
if endpoint[spill] > endpoint[i] then //溢出active中最后的区间
register[i] <- register[spill]
//spill的寄存器给新区间所代表的变量
location[spill] <- new stack location
remove spill from active
add i to active, sorted by increasing end point
//spill移出active,i加入active
else //溢出新区间
location[i] <- new stack location
```
3.算法复杂度分析:变量个数为V,寄存器个数为R。当查找列表方式为二分查找时,复杂度为$O(V\log R)$,为线性查找时,复杂度为$O(VR)$。
更多介绍参考如下文章:
[http://web.cs.ucla.edu/~palsberg/course/cs132/linearscan.pdf](http://web.cs.ucla.edu/~palsberg/course/cs132/linearscan.pdf)
## Live Ranges with Lifetime Holes
Poletto版本的活跃变量区间是一大块的,它没有考虑到区间的lifetime hole,即 "不需要变量值的区间"。
以下两篇文章在非SSA形式上处理了带有hole的活跃变量区间
[https://dl.acm.org/doi/10.1145/277650.277714](https://dl.acm.org/doi/10.1145/277650.277714)
[https://dl.acm.org/doi/10.1145/1064979.1064998](https://dl.acm.org/doi/10.1145/1064979.1064998)
## Linear Scan on SSA
以上参考文献都在非SSA形式上工作,对于llvm ir,需要将phi指令手动转换成赋值语句,比较麻烦
这篇文章在SSA形式上进行线性扫描
[https://dl.acm.org/doi/10.1145/1772954.1772979](https://dl.acm.org/doi/10.1145/1772954.1772979)
其简要思想即为在代码生成阶段进行phi resolution
### 建议
不进行Phi指令消除,对SSA form的IR进行最简易的线性扫描(Poletto),在emitting machine code时插入move指令
# Graph Coloring
图着色寄存器分配是解决寄存器分配的主要方法,其生成的代码质量较线性扫描更高,但是也更加耗时。
在这种算法中,图的节点代表活跃变量区间,两个节点相连代表存在相互干扰的区间,即至少在一个程序点上同时存在的区间。然后,寄存器分配简化为图的着色问题,其中颜色(寄存器)被分配给节点,使得由边连接的两个节点不会得到相同的颜色。
使用活跃变量分析,可以建立一个interference graph干扰图。干扰图是一个无向图,其中的节点是程序的变量,用来模拟哪些变量不能被分配到同一个寄存器。
## Chaitin
经典算法,伪代码见
[https://dl.acm.org/doi/10.1145/872726.806984](https://dl.acm.org/doi/10.1145/872726.806984)
## Graph Coloring on SSA
SSA带来的特性能够简化图着色,参考
[https://dl.acm.org/doi/10.1007/11688839_20](https://dl.acm.org/doi/10.1007/11688839_20)
# Rematerialization
Chaitin等人讨论了几个提高溢出代码质量的想法。他们指出,某些数值可以在一条指令中**重新计算**,而且所需的操作数总是可以用来计算的
[https://dl.acm.org/doi/10.1145/143103.143143](https://dl.acm.org/doi/10.1145/143103.143143)
## PBPQ
LLVM的其中一个分配器,采用线性规划,参考
[https://dl.acm.org/doi/10.1007/11860990_21](https://dl.acm.org/doi/10.1007/11860990_21)
[https://dl.acm.org/doi/abs/10.1145/513829.513854](https://dl.acm.org/doi/abs/10.1145/513829.513854)
### 评测脚本
性能测试脚本位于tests/5-bonus/test_time.py,在实现自己的汇编代码生成功能前,可以使用脚本的 `--clang`选项,用clang完成编译和汇编。
下面用yourcmminus指代你完成的汇编代码生成项目。
脚本会对yourcminus或clang生成的可执行文件的**输出结果****运行时间**进行测试,性能测试结果会写入同目录下test_result文件,脚本的`--console`选项用来控制是否同时将性能测试结果打印到控制台。
**注**:默认要求yourcminus在生成汇编代码时使用的指令是`cminusfc file.cminus -mem2reg -S a.s`,你也可以根据你自己的实现更改脚本中的标有`###可修改###`的部分。
下面是开启`-console``-clang`时的生成结果示例 :
```bash
$ python3 test_time.py --console --clang
[1/13] 1-return.cminus: pass, costs 0.001559s
[2/13] 10-float.cminus: pass, costs 0.001864s
[3/13] 11-floatcall.cminus: pass, costs 0.001632s
[4/13] 12-global.cminus: pass, costs 0.001614s
[5/13] 13-complex.cminus: pass, costs 0.001423s
[6/13] 2-calculate.cminus: pass, costs 0.001770s
[7/13] 3-output.cminus: pass, costs 0.001356s
[8/13] 4-if.cminus: pass, costs 0.001618s
[9/13] 5-while.cminus: pass, costs 0.001637s
[10/13] 6-array.cminus: pass, costs 0.002292s
[11/13] 7-function.cminus: pass, costs 0.001389s
[12/13] 8-store.cminus: pass, costs 0.001849s
[13/13] 9-fibonacci.cminus: pass, costs 0.002904s
0 tests failed
total time is 0.022907666116952895s
avg time is 0.0017621281628425304s
13 tests finishes in time limit
testcase clang baseline
1-return.cminus 0.001559 0.000883
10-float.cminus 0.001864 0.001716
11-floatcall.cminus 0.001632 0.001657
12-global.cminus 0.001614 0.001491
13-complex.cminus 0.001423 0.001820
2-calculate.cminus 0.001770 0.001738
3-output.cminus 0.001356 0.001946
4-if.cminus 0.001618 0.001651
5-while.cminus 0.001637 0.002021
6-array.cminus 0.002292 0.002570
7-function.cminus 0.001389 0.001677
8-store.cminus 0.001849 0.001600
9-fibonacci.cminus 0.002904 0.004012
===============================================================
```
### 分数计算
+ 功能测试(40%)
从 IR 到龙芯汇编翻译,生成龙芯汇编代码,执行结果与预期结果一致。
得分为 通过测试用例数 / 总测试用例数 * 40
+ 性能测试(40%)
对于每个测试用例,将所有人的运行时间排序,得分为(总人数 - 排名 + 1) / 总人数 * 40,性能测试的分数将取所有测试用例的平均值。
+ 工作总结汇报(20%)
以 ppt 形式汇报,介绍项目的设计思想、学生自由发挥部分等内容。
# 龙芯扩展实验ABI文档
- [龙芯扩展实验ABI文档](#龙芯扩展实验ABI文档)
- [0. 前言](#前言)
- [1. 基础指令介绍](#基础指令介绍)
- [1.1 指令编码格式](#指令编码格式)
- [1.2 整数指令概述](#基础整数指令概述)
- [1.3 浮点数指令概述](#基础浮点数指令概述)
- [2. 寄存器使用约定](#寄存器使用约定)
- [3. 函数调用约定](#函数调用约定)
## 前言
龙芯面向国家信息化建设需求,提供了高性能的处理器和基础软硬件解决方案,并提供独立自主的指令系统架构。龙芯架构LoongArch64是一种精简指令集(RISC)风格的指令系统架构,龙芯架构具有RISC指令架构的典型特征。它的指令长度固定且编码格式规整,绝大多数指令只有两个源操作数和一个目的操作数,采用`load/store`架构,即仅有`load/store`访存指令可以访问内存,其它指令的操作对象均是处理器核内部的寄存器或指令码中的立即数。
关于更多关于龙芯架构的详细介绍,也可以前往[龙芯官网](https://www.loongson.cn/)等查看相关内容。
## 基础指令介绍
### 指令编码格式
龙芯架构中的所有指令均采用32位固定长度,且指令的地址都要求4字节边界对齐;如下,为某个汇编程序进入函数体时,首条汇编指令及后2条指令的地址,可见指令的地址按照4字节边界对齐:
```
==> 0x120000660 <main+0>: addi.d $r3,$r3,-32
0x120000664 <main+4>: st.d $r22,$r3,24
0x120000668 <main+8>: addi.d $r22,$r3,32
```
#### 基本数据类型
下表为基础指令涉及到的基本数据类型的大小与数据存放时对齐:
<table>
<tr>
<th>Type</th><th>Size</th><th>Alignment</th>
</tr>
<tr>
<td>unsigned/signed char</td><td>1</td><td>1</td>
</tr>
<tr>
<td>unsigned/signed short</td><td>2</td><td>2</td>
</tr>
<tr>
<td>unsigned/signed int</td><td>4</td><td>4</td>
</tr>
<tr>
<td>unsigned/signed long</td><td>8</td><td>8</td>
</tr>
<tr>
<td>unsigned/signed long long</td><td>8</td><td>8</td>
</tr>
<tr>
<td>pointer</td><td>8</td><td>8</td>
</tr>
<tr>
<td>float</td><td>4</td><td>4</td>
</tr>
<tr>
<td>double</td><td>8</td><td>8</td>
</tr>
<tr>
<td>long double</td><td>8</td><td>8</td>
</tr>
</table>
#### 指令汇编助记格式
指令名后缀为`.B、.H、.W、.D、.BU、.HU、.WU、.DU`分别表示该指令操作的数据类型是有符号字节、有符号半字、有符号字、有符号双字、无符号字节、无符号半字、无符号字、无符号双字。不过这里有一种特殊情况,当操作数是有符号数还是无符号数不影响运算结果时,指令名中携带的后缀均不带U,但此时并不限制操作对象只能是有符号数。
对于操作对象是浮点数类型的,或者更具体来说是那些指令名以“F”、“VF”和“XVF”开头的指令,其指令名后缀为`.H、.S、.D、.W、.L、.WU、.LU`分别表示该指令操作的数据类型是半精度浮点数、单精度浮点数、双精度浮点数、有符号字、有符号双字、无符号字、无符号双字。
#### 基础指令寄存器
基础整数指令涉及的寄存器包括通用寄存器(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
or $r13,$r12,$r0 #$r0中的值恒为0
```
- 如果我们想要传送超过12位的立即数,如让\$r13的`[31:0]`位内容变为56995821(0x365afed):
则可以通过以下指令,将该值赋给临时寄存器$r13;
```
#56991744,转换为十六进制即0x365a000
lu12i.w $r12,56991744>>12 #将寄存器$r12的[31:0]位,置为#0x365a000
ori $r13,$r12,4077 #将0x365a000与0x0000fed作或操作后,存入$r13
```
#### 算数运算指令
###### ADD.{W/D},SUB.{W/D}
- 格式:
- add.w rd,rj,rk
- add.d rd,rj,rk
- sub.w rd,rj,rk
- sub.d rd,rj,rk
- 概述:
- ADD.W:
- ADD.W将通用寄存器rj中的`[31:0]`位数据加上通用寄存器rk中的`[31:0]`位数据,所得结果的`[31:0]`位符号扩展后写入通用寄存器rd中。
- SUB.W:
- SUB.W将通用寄存器rj中的`[31:0]`位数据减去通用寄存器rk中的`[31:0]`位数据,所得结果的`[31:0]`位符号扩展后写入通用寄存器rd中。
- ADD.D:
- ADD.D将通用寄存器rj中的`[63:0]`位数据加上通用寄存器rk中的`[63:0]`位数据,所得结果的`[63:0]`位写入通用寄存器rd中。
- SUB.D:
- SUB.D将通用寄存器rj中的`[63:0]`位数据减去通用寄存器rk中的`[63:0]`位数据,所得结果的`[63:0]`位写入通用寄存器rd中。
- 上述指令执行时不对溢出作特殊处理。
- 样例:
- 执行指令前, \$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
- 概述:
- ADDI.W:
- ADDI.W将通用寄存器rj中的`[31:0]`位数据加上12比特立即数si12符号扩展后的32位数据,所得结果的`[31:0]`位符号扩展后写入通用寄存器rd中。
- ADDI.D:
- ADDI.D将通用寄存器rj中的`[63:0]`位数据加上12比特立即数si12符号扩展后的64位数据,所得结果的`[63:0]`位写入通用寄存器rd中。
- 上述指令执行时不对溢出作特殊处理。
- 样例:
- 寄存器\$sp中的值`0x000000ffffff33f0`;执行指令
```
addi.d $fp,$sp,32
```
执行后,\$fp 寄存器的值为`0x000000ffffff3410`
##### LU12I.W
- 格式:
- lu12i.w rd,si20
- 概述:
- LU12I.W:
- LU12I.W将20比特立即数si20位连接上12比特0,然后符号扩展后写入通用寄存器rd中。
- 该指令与ORI指令一起,用于将超过12位的立即数装入通用寄存器中。
##### SLT
- 格式:
- slt rd,rj,rk
- 概述:
- SLT:
- SLT将通用寄存器rj中的数据与通用寄存器rk中的数据视作有符号整数进行大小比较,如果前者小于后者,则将通用寄存器rd的值置为1,否则置为0。
- SLT比较的数据位宽与所执行机器的通用寄存器位宽一致。
##### SLTI
- 格式:
- slti rd,rj,si12
- 概述:
- SLTI:
- SLTI将通用寄存器rj中的数据与12比特立即数si12符号扩展后得到的数据视作有符号整数进行大小比较,如果前者小于后者,则将通用寄存器rd的值置为1,否则置为0。
- SLTI比较的数据位宽与所执行机器的通用寄存器位宽一致。
##### AND,OR,NOR,XOR,ANDN,ORN
- 格式:
- and rd, rj, rk
- or rd, rj, rk
- nor rd, rj, rk
- xor rd, rj, rk
- andn rd, rj, rk
- orn rd, rj, rk
- 概述:
- AND:
- AND将通用寄存器rj中数据与通用寄存器rk中的数据进行按位逻辑与运算,结果写入通用寄存器rd中。
- OR:
- OR将通用寄存器rj中数据与通用寄存器rk中的数据进行按位逻辑或运算,结果写入通用寄存器rd中。
- NOR:
- NOR将通用寄存器rj中数据与通用寄存器rk中的数据进行按位逻辑或非运算,结果写入通用寄存器rd中。
- XOR:
- XOR将通用寄存器rj中数据与通用寄存器rk中的数据进行按位逻辑异或运算,结果写入通用寄存器rd中。
- ANDN:
- ANDN将通用寄存器rk中数据按位取反后再与通用寄存器rj中的数据进行按位逻辑与运算,结果写入通用寄存器rd中。
- ORN:
- ORN将通用寄存器rk中数据按位取反后再与通用寄存器rj中的数据进行按位逻辑或运算,结果写入通用寄存器rd中。
- 上述指令操作的数据位宽与所执行机器的通用寄存器位宽一致。
##### ANDI,ORI,XORI
- 格式:
- andi rd, rj, ui12
- ori rd, rj, ui12
- xori rd, rj, ui12
- 概述:
- ANDI:
- ANDI将通用寄存器rj中数据与12比特立即数ui12零扩展之后的数据进行按位逻辑与运算,结果写入通用寄存器rd中。
- ORI:
- ORI将通用寄存器rj中数据与12比特立即数ui12零扩展之后的数据进行按位逻辑或运算,结果写入通用寄存器rd中。
- XORI:
- XORI将通用寄存器rj中数据与12比特立即数ui12零扩展之后的数据进行按位逻辑异或运算,结果写入通用寄存器rd中。
- 上述指令操作的数据位宽与所执行机器的通用寄存器位宽一致。
##### 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
- 概述:
- MUL.W:
- 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, rj, rk
- 概述:
- MULW.D.W:
- 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:
- MOD.W和MOD.WU将通用寄存器rj中[31:0]位数据除以通用寄存器rk中[31:0]位数据,所得的余数符号扩展后写入通用寄存器rd中。
- 在LoongArch64兼容的机器上,执行DIV.W和MOD.W指令时,如果通用寄存器rj和rk中的数值超过了32位有符号数的数值范围,则执行结果可以是无意义的任意值。
- DIV.W、 MOD.W进行除法操作时,操作数均视作有符号数。
- 求商/余数的指令对DIV.W/MOD.W运算的结果满足,余数与被除数的符号一致且余数的绝对值小于除数的绝对值。
- 当除数是0时,结果可以为任意值。
#### 移位运算类指令
##### SLLI.W,SRLI.W,SRAI.W,ROTRIW
- 格式:
- slli.w rd, rj, ui5
- srli.w rd, rj, ui5
- srai.w rd, rj, ui5
- rotri.w rd, rj, ui5
- 概述:
- 下述移位指令的移位量均为指令码中5比特无符号立即数ui5。
- SLLI.W:
- SLLI.W将通用寄存器rj中[31:0]位数据逻辑左移,所得的移位结果符号扩展后写入通用寄存器rd中。
- SRLI.W:
- SRLI.W将通用寄存器rj中[31:0]位数据逻辑右移,所得的移位结果符号扩展后写入通用寄存器rd中。
- SRAI.W:
- SRAI.W将通用寄存器rj中[31:0]位数据算数右移,所得的移位结果符号扩展后写入通用寄存器rd中。
- ROTRI.W:
- 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
- sra.w rd, rj, rk
- rotr.w rd, rj, rk
- 概述:
- 下述移位指令的移位量均为通用寄存器rk中[4:0]位数据,且视作无符号数。
- SLL.W:
- SLL.W将通用寄存器rj中[31:0]位数据逻辑左移,所得的移位结果符号扩展后写入通用寄存器rd中。
- SRL.W:
- SRL.W将通用寄存器rj中[31:0]位数据逻辑右移,所得的移位结果符号扩展后写入通用寄存器rd中。
- SRA.W:
- SRA.W将通用寄存器rj中[31:0]位数据算数右移,所得的移位结果符号扩展后写入通用寄存器rd中。
- ROTR.W:
- ROTR.W将通用寄存器rj中[31:0]位数据循环右移,所得的移位结果符号扩展后写入通用寄存器rd中。
##### SLLI.D,SRLI.D,SRAI.D,ROTRI.D
- 格式:
- slli.d rd, rj, ui6
- srli.d rd, rj, ui6
- srai.d rd, rj, ui6
- rotri.d rd, rj, ui6
- 概述:
- SLLI.D:
- SLLI.D将通用寄存器rj中[63:0]位数据逻辑左移,所得的移位结果符号扩展后写入通用寄存器rd中。
- SRLI.D:
- SRLI.D将通用寄存器rj中[63:0]位数据逻辑右移,所得的移位结果符号扩展后写入通用寄存器rd中。
- SRAI.D:
- SRAI.D将通用寄存器rj中[63:0]位数据算数右移,所得的移位结果符号扩展后写入通用寄存器rd中。
- ROTRI.D:
- ROTRI.D将通用寄存器rj中[63:0]位数据循环右移,所得的移位结果符号扩展后写入通用寄存器rd中。
- 上述移位指令的移位量均为指令码中6比特无符号立即数ui6。
##### SLL.D,SRL.D,SRA.D,ROTR.D
- 格式:
- sll.d rd, rj, rk
- srl.d rd, rj, rk
- sra.d rd, rj, rk
- rotr.d rd, rj, rk
- 概述:
- SLL.D:
- SLL.D将通用寄存器rj中[63:0]位数据逻辑左移,所得的移位结果符号扩展后写入通用寄存器rd中。
- SRL.D:
- SRL.D将通用寄存器rj中[63:0]位数据逻辑右移,所得的移位结果符号扩展后写入通用寄存器rd中。
- SRA.D:
- SRA.D将通用寄存器rj中[63:0]位数据算数右移,所得的移位结果符号扩展后写入通用寄存器rd中。
- ROTR.D:
- ROTR.D将通用寄存器rj中[63:0]位数据循环右移,所得的移位结果符号扩展后写入通用寄存器rd中。
- 上述移位指令的移位量均为通用寄存器rk中[5:0]位数据,且视作无符号数。
#### 转移指令
##### 示例
- [示例二](###示例2)给出了分支指令`B`的使用参考。
- [示例三](###示例3)给出了过程调用中`BL``JIRL`的使用参考。
##### B
- 格式:
- b offs26
- 概述:
- B
- B无条件跳转到目标地址处。其跳转目标地址是将指令码中的26比特立即数offs26逻辑左移2位后再符号扩展,所得的偏移值加上该分支指令的PC。
- 需要注意的是,该指令如果在写汇编时采用直接填入偏移值的方式,则汇编表示中的立即数应填入以字节为单位的偏移值,即指令码中offs26<<2。
##### BL
- 格式:
- bl offs26
- 概述:
- BL
- BL无条件跳转到目标地址处。同时将该指令的PC值加4的结果写入1号通用寄存器r1中。其跳转目标地址是将指令码中的26比特立即数offs26逻辑左移2位后再符号扩展,所得的偏移值加上该分支指令的PC。
- 1号通用寄存器r1作为返回地址寄存器ra。
- 该指令通常配合JIRL指令,完成函数调用。
- 需要注意的是,该指令如果在写汇编时采用直接填入偏移值的方式,则汇编表示中的立即数应填入以字节为单位的偏移值,即指令码中offs26<<2。
##### 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 rj, rd, offs16
- bne rj, rd, offs16
- blt rj, rd, offs16
- bge rj, rd, offs16
- bltu rj, rd, offs16
- bgeu rj, rd, offs16
- 概述:
- BEQ
- BEQ将通用寄存器rj和通用寄存器rd的值进行比较,如果相等则跳转到目标地址,否则不跳转。
- BNE
- BNE将通用寄存器rj和通用寄存器rd的值进行比较,如果不相等则跳转到目标地址,否则不跳转。
- BLT
- BLT将通用寄存器rj和通用寄存器rd的值视作有符号数进行比较,如果前者小于后者则跳转到目标地址,否则不跳转。
- BGE
- BGE将通用寄存器rj和通用寄存器rd的值视作有符号数进行比较,如果前者大于后者则跳转到目标地址,否则不跳转。
- BLTU
- BLTU将通用寄存器rj和通用寄存器rd的值视作无符号数进行比较,如果前者小于后者则跳转到目标地址,否则不跳转。
- BGEU
- BGEU将通用寄存器rj和通用寄存器rd的值视作无符号数进行比较,如果前者大于后者则跳转到目标地址,否则不跳转。
- 上述六条分支指令的跳转目标地址计算方式是将指令码中的16比特立即数offsl6逻辑左移2位后再符号扩展,所得的偏移值加上该分支指令的PC。不过需要注意的是,上述指令如果在写汇编时采用直接填入偏移值的方式,则汇编表示中的立即数应填入以字节为单位的偏移值,即指令码中offs16<<2。
##### BEQZ,BNEZ
- 格式:
- beqz rj, offs21
- bnez rj, offs21
- 概述:
- BEQZ
- BEQZ对通用寄存器rj的值进行判断,如果等于0则跳转到目标地址,否则不跳转。
- BNEZ
- BNEZ对通用寄存器rj的值进行判断,如果不等于0则跳转到目标地址,否则不跳转。
- 上述两条分支指令的跳转目标地址计算方式是将指令码中的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
- ld.d rd, rj, si12
- st.b rd, rj, si12
- st.h rd, rj, si12
- st.w rd, rj, si12
- 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]位数据写入内存中
- 上述指令的访存地址计算方式是将通用寄存器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)。
- 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。浮点分支指令的判断条件来自于条件标志寄存器。
### 基础浮点指令概述
#### 样例
- [示例四](###示例4),给出了浮点搬运指令movgr2fr,浮点转换指令ffint、浮点比较指令fcmp等浮点指令的使用参考。
- [示例五](###示例5),给出了浮点数的赋值指令使用参考。
#### 浮点运算类指令
##### F{ADD/SUB/MUL/DIV}.{S/D}
- 格式:
- fadd.s fd, fj, fk
- fadd.d fd, fj, fk
- fsub.s fd, fj, fk
- fsub.d fd, fj, fk
- fmul.s fd, fj, fk
- fmul.d fd, fj, fk
- fdiv.s fd, fj, fk
- fdiv.d fd, fj, fk
- 概述:
- FADD.{S/D}指令将浮点寄存器fj中的单精度/双精度浮点数加上浮点寄存器fk中的单精度/双精度浮点数,得到的单精度/双精度浮点数结果写入到浮点寄存器fd中。浮点加法运算遵循IEEE 754-2008标准 中addition (x,y)操作的规范。
- FSUB.{S/D}指令将浮点寄存器fj中的单精度/双精度浮点数减去浮点寄存器fk中的单精度/双精度浮点数,得到的单精度/双精度浮点数结果写入到浮点寄存器fd中。浮点减法运算遵循IEEE 754-2008标准中 subtraction(x,y)操作的规范。
- FMUL.{S/D}指令将浮点寄存器fj中的单精度/双精度浮点数乘以浮点寄存器fk中的单精度/精度浮点数,得到的单精度/双精度浮点数结果写入到浮点寄存器fd中。浮点乘法运算遵循IEEE 754-2008标准中 multiplication(x,y)操作的规范。
- FDIV.{S/D}指令将浮点寄存器fj中的单精度/双精度浮点数除以浮点寄存器fk中的单精度/双精度浮点数,得到的单精度/双精度浮点数结果写入到浮点寄存器fd中。浮点乘法运算遵循IEEE 754-2008标准中 division(x,y)操作的规范。
- 当操作数是单精度浮点数时,结果浮点寄存器的高32位可以是任意值。
#### 浮点比较指令
##### FCMP.cond.{S/D}
- 格式:
- fcmp.cond.s cc, fj, fk
- fcmp.cond.d cc, fj, fk
- 概述:
- 这是一条浮点比较指令,将比较结果写入状态码。其中助记符(cond)为编写汇编代码时的判断标志,状态码为可选的条件标志寄存器,比较条件和判断标准如下:
- <table>
<tr>
<th>助记符/Cond</th><th>状态码</th><th>含义</th><th>True Condition</th>
</tr>
<tr>
<td>CAF</td><td>0x0</td><td></td><td></td>
</tr>
<tr>
<td>CUN</td><td>0x8</td><td>无法比较</td><td>UN</td>
</tr>
<tr>
<td>CEQ</td><td>0x4</td><td>相等</td><td>EQ</td>
</tr>
<tr>
<td>CUEQ</td><td>0xC</td><td>相等或无法比较</td><td>UN EQ</td>
</tr>
<tr>
<td>CLT</td><td>0x2</td><td>小于</td><td> LT</td>
</tr>
<tr>
<td>CULT</td><td>0xA</td><td>小于或者无法比较</td><td>UN LT</td>
</tr>
<tr>
<td>CLE</td><td>0x6</td><td>小于等于</td><td>EQ LT</td>
</tr>
<tr>
<td>CULE</td><td>0xE</td><td>小于等于或无法比较</td><td>UN EQ LT</td>
</tr>
<tr>
<td>CNE</td><td>0x10</td><td>不等</td><td>GT LT</td>
</tr>
<tr>
<td>COR</td><td>0x14</td><td>有序</td><td>GT LT EQ</td>
</tr>
<tr>
<td>CUNE</td><td>0x18</td><td>无法比较或不等</td><td>UN GT LT</td>
</tr>
</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
- 概述:
- 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}将浮点寄存器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 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 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位字数据写入到内存中。
- FST.D将浮点寄存器fd中双字数据写入到内存中。
- 上述指令的访存地址计算方式是将通用寄存器rj中的值与符号扩展后的12比特立即数si12相加求和。
## 寄存器使用约定
### 整形寄存器调用约定
- [参数传递](###参数传递)节给出了参数传递、调用返回值保存时的具体寄存器调用规范。也可参考[样例](###示例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>
</tr>
<tr>
<td>$r0</td><td>$zero</td><td>Constant Zero</td><td>Unused</td>
</tr>
<tr>
<td>$r1</td><td>$ra</td><td>Return Address</td><td>No</td>
</tr>
<tr>
<td>$r2</td><td>$tp</td><td>TLS</td><td>Unused</td>
</tr>
<tr>
<td>$r3</td><td>$sp</td><td>Stack Pointer</td><td>Yes</td>
</tr>
<tr>
<td>$r4-$r11</td><td>$a0-$a7</td><td>Argument Registers</td><td>No</td>
</tr>
<tr>
<td>$r4-$r5</td><td>$v0-$v1</td><td>Return Value</td><td>No</td>
</tr>
<tr>
<td>$r12-$20</td><td>$t0-$t8</td><td>Temp Registers</td><td>No</td>
</tr>
<tr>
<td>$r21</td><td>$x</td><td>Reserved</td><td>Unused</td>
</tr>
<tr>
<td>$r22</td><td>$fp</td><td>Frame Pointer</td><td>Yes</td>
</tr>
<tr>
<td>$r23-$r31</td><td>$s0-$s8</td><td>Subroutine register variables</td><td>Yes</td>
</tr>
</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>
</tr>
<tr>
<td>$f0-$f7</td><td>$fa0-$fa7</td><td>Argument Registers</td><td>No</td>
</tr>
<tr>
<td>$f0-$f1</td><td>$fv0-$fv1</td><td>Return Value</td><td>No</td>
</tr>
<tr>
<td>$f8-$f23</td><td>$ft0-$ft15</td><td>Temp Registers</td><td>No</td>
</tr>
</table>
## 函数调用约定
### 栈帧布局
- 整个函数中,堆栈从高地址增长到低地址。ABI使用两个寄存器来访问堆栈:指向帧底部的帧指针(\%fp),和堆栈指针(\%sp)指向帧的顶部。下图显示了框架的布局。正常情况下,帧指针用于寻址帧中的数据,例如传入参数和本地变量。
- 堆栈指针应该对⻬到⼀个128位的边界上作为函数⼊⼝。在堆栈上传递的第⼀个实参位于函数⼊⼝的堆栈指针偏移量为零的地⽅;后⾯的参数存储在更⾼的地址中。
![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
ld.d $ra,$sp,N-8 # 恢复返回地址
addi.d $sp,$sp,N
jr $ra #根据$ra的地址返回
```
#### 调用方还原
如果调用方在每次调用的基础上为参数分配堆栈空间,那么它负责返回时释放空间。
### 参数传递
#### 整形调用规范
- 基本整型调⽤规范提供了8个参数寄存器a0-a7⽤于参数传递,前两个参数寄存器a0-a1也⽤于返回值;若a0-a7数目不够时,使用栈进行数据传输。
- 若⼀个标量位宽GRLEN(即寄存器宽度,LA64下为64位),则它在单个参数寄存器中传递,若没有可⽤的寄存器,则在堆栈中传递;传递到堆栈上的标量会对⻬到类型对⻬(type alignment)和GRLEN中的较⼤者,但不会超过堆栈对⻬。当整型参数传⼊寄存器或堆栈时,⼩于GRLEN 位的整型标量根据其类型的符号扩展⾄32位,然后符号扩展为GRLEN位。当浮点型参数传⼊寄存器或堆栈时,⽐GRLEN位窄的浮点类型将被扩展为GRLEN位,⽽⾼位为未定义位。
- 通过引⽤传递的实参可以由被调⽤⽅修改。
- 返回值的传递⽅式与第⼀个同类型命名参数的传递⽅式相同。如果这样的实参是通过引⽤传递的,则调⽤者为返回值分配内存,并将地址作为隐式的第⼀个参数传递。
- 函数(procedure)所依赖的数据必须位于函数栈帧范围之内。
#### 浮点调用规范
- 浮点参数寄存器共8个,为fa0-fa7,其中fa0和fa1也⽤于传递返回值。需要传递的值在任何可能的情况下都可以传递到浮点寄存器中,与整型参数寄存器a0-a7是否已经⽤完⽆关。
- 若⼀个浮点实数参数不超过FLEN位宽(FLEN指的是ABI中的浮点寄存器的宽度),并且⾄少有⼀个浮点参数寄存器可⽤,则将这个浮点实数参数传递到浮点参数寄存器中。否则,它将根据整型调⽤规范传递。当⼀个⽐FLEN位更窄的浮点参数在浮点寄存器中传递时,它会扩展(NaN-Boxed)到FLEN位。
- 返回值的传递⽅式与传递第⼀个同类型命名参数的⽅式相同。
### 函数值的返回
- 如果返回值类型是pointer或整型scalar type,则返回值经符号扩展或零扩展后放⼊ $v0 中。
- 如果返回值类型是float或double,则返回值在 $fv0 中。
## LA64汇编概述与示例
### 示例1
源程序
```c
int main() {
return 0;
}
```
汇编代码
```c
.text # 标记代码段
.globl main # 标记 main 全局可见(必需)
.type main, @function # 标记 main 是一个函数
main: # 标记程序入口
addi.d $sp, $sp, -16 # 分配栈空间(必需),-1616字节
st.d $ra, $sp, 8 # 保存返回地址(必需)
addi.d $fp, $sp, 16 # 设置帧指针
addi.w $a0, $zero, 0 # 设置返回值为 0
ld.d $ra, $sp, 8 # 恢复返回地址(必需)
addi.d $sp, $sp, 16 # 释放栈空间(必需)
jr $ra # 返回调用main函数的父函数(必需)
```
在执行完毕指令`addi.w $a0, $zero, 0`时,我们通过gdb查看当前函数的栈帧:
```
(gdb) i frame
Stack level 0, frame at 0xffffff3410:
pc = 0x120000674 in main (test.s:11); saved pc = 0xfff7e80774
source language asm.
Arglist at 0xffffff3410, args:
Locals at 0xffffff3410, Previous frame's sp is 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() {
a = 4;
if (a > 0)
return 1;
return 0;
}
```
```c
.text # 标记代码段
.comm a, 4 # 为符号a分配4字节长度的未初始化内存空间
.globl main # 标记 main 全局可见(必需)
.type main, @function # 标记 main 是一个函数
main: # 标记程序入口
addi.d $sp, $sp, -16 # 分配栈空间(必需),-1616字节
st.d $ra, $sp, 8 # 保存返回地址(必需)
addi.d $fp, $sp, 16 # 设置帧指针
.main_label_entry: # 分支判断入口
addi.w $t4, $zero, 4 # t4 = 4
addi.w $t2, $zero, 0 # t2 = 0
la.local $t0, a # a所处的内存地址加载入 t0
stptr.w $t4, $t0, 0 # t4 的数据保存入 t0 指向的地址中
blt $t2, $t4, .main_then0 # t2 t4 比较,如果 t2 < t4 则跳转到 main_then0
b .main_else1 # 否则跳转到 .main_else1
.main_else1:
addi.w $a0, $zero, 0 # 设置返回值为 0
b .main_label_return #跳转到 .main_label_return
.main_then0:
addi.w $a0, $zero, 1 # 设置返回值为 1
b .main_label_return #跳转到 .main_label_return
.main_label_return: # 标记分支出口
ld.d $ra, $sp, 8 # 恢复返回地址(必需)
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;
}
int main() {
int a = 1;
int b = 2;
int c = add(a, b);
output(c);
return 0;
}
```
```c
.text # 标记代码段
.globl add # 标记 add 全局可见(必需)
.type add, @function # 标记 main 是一个函数
add:
addi.d $sp, $sp, -16 # 分配栈空间(必需),-1616字节
st.d $ra, $sp, 8 # 将返回地址入栈,此处即bl add指令的下一条指令地址
addi.d $fp, $sp, 16 # 设置帧指针
add.d $a0, $a0, $a1 # 计算 a0 + a1,函数返回值存储到 a0
ld.d $ra, $sp, 8 # 恢复返回地址(必需)
addi.d $sp, $sp, 16 # 释放栈空间(必需)
jr $ra # 返回main函数(必需)
.globl main # 标记 main 全局可见(必需)
.type main, @function
main:
addi.d $sp, $sp, -16 # 分配栈空间(必需),-1616字节
st.d $ra, $sp, 8 # 将返回地址入栈
addi.d $fp, $sp, 16 # 设置帧指针
addi.w $a0, $zero, 1 # 设置第一个参数
addi.w $a1, $zero, 2 # 设置第二个参数
bl add # 调用 add 函数
bl output # 输出结果
addi.w $a0, $zero, 0 # 返回 0
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;
float c = 3.5;
if (b < c)
{
return 1;
}
return 0;
}
```
```
.text
.globl main
.type main, @function
main:
addi.d $sp, $sp, -32
st.d $ra, $sp, 24
addi.d $fp, $sp, 32
addi.w $t0,$zero,8 # 0x8的值赋给t0
movgr2fr.w $f0,$t0 # 将t0的内容赋给浮点寄存器f0
ffint.s.w $f0,$f0 # 将浮点寄存器f0中存放的定点数转换为浮点格式
lu12i.w $t1,1080033280>>12 #将t1的[31:0]位置为0x40600000,即浮点数3.5的单精度表示
st.w $t1,$fp,-12 #将t1的内容存放入$fp-12指向的内存处
fld.s $f1,$fp,-12 #将$fp-12指向的内存处内容读入f1中
fcmp.clt.s $fcc0,$f0,$f1 # 比较f0和f1的值;如果满足CLT(小于)条件,置条件标志位$fcc0为1
bceqz $fcc0,.L6 # 如果$fcc0等于0,则跳转到.L1处
addi.w $t2,$zero,1 # 0x1
b .L2
.L1:
or $t2,$zero,$zero
.L2:
or $a0,$t2,$zero
ld.d $ra, $sp, 24
addi.d $sp, $sp, 32
jr $ra
```
###
### 在LA64下的编译与调试
#### 编译
##### 需要调试
在龙芯服务器loongnix系统下编译命令(假设汇编程序为test.s,生成可执行文件test)
`gcc src/io/io.c test.s -Wa,--gdwarf2 -o test`
在龙芯服务器麒麟系统下编译命令(假设汇编程序为test.s,生成可执行文件test)
`gcc src/io/io.c test.s -g -o test`
##### 不需要调试
`gcc src/io/io.c test.s -o test`
#### 调试
gdb调试
`gdb test` 进入调试(test为可执行程序)
`i r #查看寄存器`
![img_8](img1_8.PNG)
`i r rx #查看寄存器rx数值(左侧0xfff7e80774是$ra的16进制值,右侧是它的10进制值)`
![img_7](img1_7.PNG)
`b XX:YY #在XX文件YY行打断点`
![img_5](img1_5.PNG)
`r #运行`
![img_6](img1_6.PNG)
`c #继续运行到下一断点`
![img_9](img1_9.PNG)
`n #逐行调试`
![img_11](img1_11.PNG)
`s #逐步调试`
![img_12](img1_12.PNG)
\ No newline at end of file
...@@ -5,26 +5,26 @@ ...@@ -5,26 +5,26 @@
## 目前已布置的实验 ## 目前已布置的实验
* [lab1](./Documentations/1-parser/) * [lab1](./Documentations/1-parser/)
+ DDL:2022-10-03 23:59:59 (UTC+8) + DDL:2021-10-06(~~10-03~~) 23:59:59 (UTC+8)
* [lab2](./Documentations/2-ir-gen-warmup/) * [lab2](./Documentations/2-ir-gen-warmup/)
+ DDL:2022-10-23 23:59:59 (UTC+8) + DDL:2021-10-22 23:59:59 (UTC+8)
* [lab3](./Documentations/3-ir-gen/) * [lab3](./Documentations/3-ir-gen/)
+ DDL:2022-11-13 23:59:59 (UTC+8) + DDL: 2021-11-21 23:59:59 (UTC+8)
* [lab4](./Documentations/4-ir-opt)
* [lab4.1](./Documentations/4.1-ssa/) + DDL:
+ DDL:2022-11-27 23:59:59 (UTC+8) + **阶段一**:2021/11/29 23:59:59 (UTC+8)
+ **阶段二**:2022/12/13 23:59:59 (UTC+8)
* [lab4.2](./Documentations/4.2-gvn/) * [lab5](./Documentations/5-bonus/)
+ DDL: 2023-01-14 23:59:59 (UTC+8) + DDL:
+ **建议报名期限**:2023/01/29
+ **实验提交**:2023 年 3 月初,具体时间待定
+ **答辩时间**:2023 年 3 月初,具体时间待定
## FAQ: How to merge upstream remote branches ## FAQ: How to merge upstream remote branches
In brief, you need another alias for upstream repository (we assume you are now in your local copy of forked repository on Gitlab): In brief, you need another alias for upstream repository (we assume you are now in your local copy of forked repository on Gitlab):
```shell ```shell
$ git remote add upstream git@202.38.79.174:compiler_staff/2022fall-compiler_cminus.git $ git remote add upstream http://211.86.152.198:8080/staff/2021fall-compiler_cminus.git
``` ```
Then try to merge remote commits to your local repository: Then try to merge remote commits to your local repository:
......
#ifndef CODEGEN_HPP
#define CODEGEN_HPP
#include "Module.h"
#include "logging.hpp"
using std::string;
using std::vector;
class CodeGen {
public:
CodeGen(Module *module) : m(module) {}
string print() {
string result;
for (auto line : output) {
if (line.find(":") == string::npos and line != "")
result += "\t"; // 添加缩进
result += line + "\n";
}
return result;
}
void run();
private:
Module *m;
vector<string> output;
};
#endif
...@@ -2,5 +2,6 @@ add_subdirectory(parser) ...@@ -2,5 +2,6 @@ add_subdirectory(parser)
add_subdirectory(common) add_subdirectory(common)
add_subdirectory(io) add_subdirectory(io)
add_subdirectory(lightir) add_subdirectory(lightir)
add_subdirectory(cminusfc)
add_subdirectory(optimization) add_subdirectory(optimization)
add_subdirectory(cminusfc)
add_subdirectory(codegen)
...@@ -6,15 +6,14 @@ add_executable( ...@@ -6,15 +6,14 @@ add_executable(
target_link_libraries( target_link_libraries(
cminusfc cminusfc
OP_lib
IR_lib IR_lib
common common
syntax syntax
OP_lib codegen
) )
install( install(
TARGETS cminusfc TARGETS cminusfc
RUNTIME DESTINATION bin RUNTIME DESTINATION bin
) )
#include "ActiveVars.hpp"
#include "ConstPropagation.hpp"
#include "DeadCode.h"
#include "Dominators.h" #include "Dominators.h"
#include "GVN.h" #include "GVN.h"
#include "LoopInvHoist.hpp"
#include "LoopSearch.hpp"
#include "Mem2Reg.hpp" #include "Mem2Reg.hpp"
#include "PassManager.hpp" #include "PassManager.hpp"
#include "cminusf_builder.hpp" #include "cminusf_builder.hpp"
#include "codegen.hpp"
#include "logging.hpp" #include "logging.hpp"
#include <fstream> #include <fstream>
...@@ -68,17 +74,13 @@ int main(int argc, char **argv) { ...@@ -68,17 +74,13 @@ int main(int argc, char **argv) {
std::cerr << argv[0] << ": input file " << input_path << " has unknown filetype!" << std::endl; std::cerr << argv[0] << ": input file " << input_path << " has unknown filetype!" << std::endl;
return -1; return -1;
} else { } else {
if (input_path.substr(pos) != ".cminus") { // if (input_path.substr(pos) != ".cminus") {
std::cerr << argv[0] << ": input file " << input_path << " has unknown filetype!" << std::endl; // std::cerr << argv[0] << ": input file " << input_path << " has unknown filetype!" << std::endl;
return -1; // return -1;
} // }
if (emit) {
target_path = input_path.substr(0, pos);
} else {
target_path = input_path.substr(0, pos); target_path = input_path.substr(0, pos);
} }
} }
}
auto s = parse(input_path.c_str()); auto s = parse(input_path.c_str());
auto a = AST(s); auto a = AST(s);
...@@ -93,13 +95,23 @@ int main(int argc, char **argv) { ...@@ -93,13 +95,23 @@ int main(int argc, char **argv) {
if (mem2reg) { if (mem2reg) {
PM.add_pass<Mem2Reg>(emit); PM.add_pass<Mem2Reg>(emit);
} }
if (gvn) if (gvn) {
PM.add_pass<DeadCode>(false); // remove some undef
PM.add_pass<GVN>(emit, dump_json); PM.add_pass<GVN>(emit, dump_json);
PM.add_pass<DeadCode>(false); // delete unused instructions created by GVN
}
PM.run(); PM.run();
auto IR = m->print(); auto IR = m->print();
CodeGen codegen(m.get());
codegen.run();
std::ofstream target_file(target_path + ".s");
target_file << codegen.print();
target_file.close();
std::ofstream output_stream; std::ofstream output_stream;
auto output_file = target_path + ".ll"; auto output_file = target_path + ".ll";
output_stream.open(output_file, std::ios::out); output_stream.open(output_file, std::ios::out);
...@@ -111,7 +123,7 @@ int main(int argc, char **argv) { ...@@ -111,7 +123,7 @@ int main(int argc, char **argv) {
std::string lib_path = " -L"s + argv[0]; std::string lib_path = " -L"s + argv[0];
auto idx = lib_path.rfind('/'); auto idx = lib_path.rfind('/');
if (idx != std::string::npos) if (idx != std::string::npos)
lib_path.erase(idx); lib_path.erase(lib_path.rfind('/'));
else else
lib_path.clear(); lib_path.clear();
auto cmd_str = "clang -O0 -w -no-pie "s + target_path + ".ll -o " + target_path + lib_path + " -lcminus_io"; auto cmd_str = "clang -O0 -w -no-pie "s + target_path + ".ll -o " + target_path + lib_path + " -lcminus_io";
......
add_library(codegen STATIC
codegen.cpp
)
target_link_libraries(common)
#include "codegen.hpp"
// $r0 $zero constant 0
// $r1 $ra return address
// $r2 $tp thread pointer
// $r3 $sp stack pointer
// $r4 - $r5 $a0 - $a1 argument, return value
// $r6 - $r11 $a2 - $a7 argument
// $r12 - $r20 $t0 - $t8 temporary
// $r21 saved
// $r22 $fp frame pointer
// $r23 - $r31 $s0 - $s8 static
class Reg {
public:
Reg(int index) : id(index) {}
int id;
string print() {
if (id == 0)
return "$zero";
if (id == 1)
return "$ra";
if (id == 2)
return "$tp";
if (id == 3)
return "$sp";
if (4 <= id and id <= 11)
return "$a" + std::to_string(id - 4);
if (12 <= id and id <= 20)
return "$t" + std::to_string(id - 12);
if (id == 22)
return "$fp";
assert(false);
}
};
void CodeGen::run() {
// TODO: implement
// 以下内容生成 int main() { return 0; } 的汇编代码
output.push_back(".text");
for (auto &func : m->get_functions())
if (not func.is_declaration()) {
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");
output.push_back("addi.w $a0, $zero, 0");
output.push_back("ld.d $ra, $sp, 8");
output.push_back("addi.d $sp, $sp, 16");
output.push_back("jr $ra");
}
}
import os
import sys
import timeit
import shutil
import argparse
import subprocess
test_dir = os.path.dirname(os.path.abspath(__file__)) # 当前文件夹路径 tests/5-bonus
cminus = os.path.join(test_dir, '../../build/cminus') # ===可修改===
testfile_dir = os.path.join(test_dir, './testcases')
output_file_name = os.path.join(test_dir, './test_result')
io = os.path.join(test_dir, '../../src/io/io.c')
total_failed_count = 0
class Outputer:
def __init__(self, console=False, filename="test_result") -> None:
self.console = console
self.fd = open(filename, "a")
def write(self, msg):
if self.console:
print(msg, end="")
sys.stdout.flush()
self.fd.write(msg)
self.fd.flush()
def __del__(self) -> None:
self.fd.close()
def eval(console=False, test_dir=testfile_dir, use_clang=False):
output_file = Outputer(console, output_file_name)
failed_count = 0
succ_count = 0
total_time = 0
single_begin = timeit.default_timer()
testfiles = os.listdir(testfile_dir)
testfiles.sort()
# 过滤出以.cminus结尾的file
testfiles = filter(lambda s: s.endswith('.cminus'), testfiles)
testfiles = list(testfiles)
test_count = len(testfiles)
testtime = []
for count, file_name in enumerate(testfiles):
start_time = timeit.default_timer()
testtime.append(-1)
# 超时,跳过
if start_time - total_start > 30 * 60 or start_time - single_begin > 30 * 60:
output_file.write(f"[{count+1}/{test_count}] " + file_name + ': skipped due to exceeded total time limit\n')
continue
# 未超时
output_file.write(f"[{count+1}/{test_count}] " + file_name + ': ')
filepath = os.path.join(testfile_dir, file_name)
outpath = os.path.join(testfile_dir, file_name[:-7] + '.out')
### 编译 ###
if not use_clang:
try:
# ===可修改===
compile_res = subprocess.run([cminus, filepath, '-mem2reg', '-S', 'a.s'],
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
timeout=300)
except subprocess.TimeoutExpired as _:
output_file.write('compile-1 timeout\n')
failed_count += 1
continue
except Exception:
output_file.write("compile-1 failed with an unexcept error\n")
failed_count += 1
continue
try:
compile_res = subprocess.run(['gcc', 'a.s', io, '-o', 'a.out'],
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
timeout=300)
except subprocess.TimeoutExpired as _:
output_file.write('compile-2 timeout\n')
failed_count += 1
continue
except Exception:
output_file.write("compile-2 failed with an unexcept error\n")
failed_count += 1
continue
else:
try:
cfilepath = filepath.replace(".cminus", ".c")
shutil.move(filepath, cfilepath)
compile_res = subprocess.run(["clang", cfilepath, io, "-o", "a.out"],
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
timeout=300)
shutil.move(cfilepath, filepath)
except subprocess.TimeoutExpired as _:
output_file.write('compile timeout\n')
failed_count += 1
continue
except Exception:
output_file.write("compile failed with an unexcept error\n")
failed_count += 1
continue
### 运行 ###
try:
input_option = None
inpath = os.path.join(testfile_dir, file_name[:-7] + '.in')
if os.path.exists(inpath): # testfile存在输入文件
with open(inpath, 'rb') as fin:
input_option = fin.read()
# 记录运行时间
start = timeit.default_timer()
for i in range(10):
exe_res = subprocess.run(['./a.out'],
input=input_option,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
timeout=100)
end = timeit.default_timer()
except subprocess.TimeoutExpired:
output_file.write("executable time limit exceeded\n")
failed_count += 1
continue
except Exception as _:
output_file.write('executable runtime error\n')
failed_count += 1
continue
# 输出
with open(outpath, 'r') as fout:
ref = fout.read().replace(' ', '').replace('\n', '')
try:
actual = exe_res.stdout.decode('utf-8').replace(' ', '').replace('\n', '').replace('\r', '') + str(
exe_res.returncode)
except UnicodeDecodeError:
output_file.write('executable output illegal characters\n')
failed_count += 1
continue
if ref == actual or use_clang:
time = (end - start) / 10
total_time += time
output_file.write(f'pass, costs {time:.6f}s\n')
succ_count += 1
testtime[count] = time
else:
output_file.write('output is different from standard answer, this may be caused by wrong return code\n'
) # 因为退出码也会作为输出的一部分,因此输出和答案不同可能是程序崩溃造成的
failed_count += 1
output_file.write(f"{failed_count} tests failed\n")
output_file.write(
f"total time is {total_time}s\navg time is {total_time/succ_count if succ_count>0 else 0}s\n{succ_count} tests finishes in time limit\n"
)
output_file.write('testcase')
output_file.write('\t\t\tyour_cminus')
for count, file_name in enumerate(testfiles):
output_file.write('{:<20}'.format(file_name))
output_file.write('\t\t %.6f' % testtime[count] if testtime[count] != -1 else '\t\t None ')
output_file.write("===================================================================\n")
if __name__ == "__main__":
total_start = timeit.default_timer()
parser = argparse.ArgumentParser(description="functional test")
parser.add_argument("--console", action="store_true", help="specify whether to output the result to console")
parser.add_argument("--clang", action="store_true", help="estimate runtime when compile with clang")
args = parser.parse_args()
eval(args.console, testfile_dir, use_clang=args.clang)
int main(void) { return 111; }
int main(void) {
float a;
float b;
float c;
a = 1.1;
b = 1.5;
c = 1.2;
outputFloat(a * b + c);
return 0;
}
/* float function call */
float mod(float x, float y) {
int div;
div = x / y;
return x - div * y;
}
int main(void) {
float a;
float b;
a = 11.2;
b = 2.2;
outputFloat(mod(a, b));
return 0;
}
int seed;
int randomLCG(void) {
seed = seed * 1103515245 + 12345;
return seed;
}
int randBin(void) {
if (randomLCG() > 0)
return 1;
else
return 0;
}
/* random walk */
int returnToZeroSteps(void) {
int x;
int steps;
x = 0;
steps = 0;
while (steps < 20) {
if (randBin())
x = x + 1;
else
x = x - 1;
steps = steps + 1;
if (x == 0)
return steps;
}
return 20;
}
int main(void) {
int i;
i = 0;
seed = 3407;
while (i < 20) {
output(returnToZeroSteps());
i = i + 1;
}
return 0;
}
4
2
2
4
8
2
2
2
2
2
6
2
10
8
4
2
20
2
2
8
0
/* 01 背包问题 */
int n;
int m;
int w[5];
int v[5];
int dp[66]; /* dp[n * 11 + size] 表示前 n 个物品放入容量为 size 的背包中的最大价值,初始化为 -1 */
int max(int a, int b) {
if (a > b)
return a;
else
return b;
}
/* 状态转移方程:
dp[n][size] = max(dp[n - 1][size], dp[n - 1][size - w[n]] + v[n])
边界条件:
dp[n][size <= 0] = 0
dp[0][size] = 0 */
int knapsack(int n, int size) {
int result;
if (size <= 0)
return 0;
if (n == 0)
return 0;
if (dp[n * 11 + size] >= 0)
return dp[n * 11 + size];
if (size < w[n - 1])
result = knapsack(n - 1, size);
else
result = max(knapsack(n - 1, size), knapsack(n - 1, size - w[n - 1]) + v[n - 1]);
dp[n * 11 + size] = result;
return result;
}
int main(void) {
int i;
i = 0;
n = 5;
m = 10;
w[0] = 2;
w[1] = 2;
w[2] = 6;
w[3] = 5;
w[4] = 4;
v[0] = 6;
v[1] = 3;
v[2] = 5;
v[3] = 4;
v[4] = 6;
while (i < 66) {
dp[i] = 0-1;
i = i + 1;
}
output(knapsack(n, m));
return 0;
}
15
0
\ No newline at end of file
int main(void) {
int a;
int b;
int c;
a = 23;
b = 25;
c = 4;
return a + b * c;
}
int main(void) {
output(11);
output(22222);
return 0;
}
int main(void) {
int a;
int b;
int c;
a = 11;
b = 22;
c = 33;
/* max value */
if (a > b) {
if (a > c)
output(a);
else
output(c);
} else {
if (c < b)
output(b);
else
output(c);
}
return 0;
}
int main(void) {
int n;
int i;
n = 10;
i = 0;
while (i < n) {
output(i);
i = i + 1;
}
return 0;
}
int main(void) {
int a[10];
int i;
i = 0;
a[0] = 11;
a[4] = 22;
a[9] = 33;
output(a[0]);
output(a[4]);
output(a[9]);
return 0;
}
int min(int a, int b) {
if (a <= b)
return a;
else
return b;
}
int main(void) {
int a;
int b;
int c;
a = 11;
b = 22;
c = 33;
output(min(a, b));
output(min(b, c));
output(min(c, a));
return 0;
}
int store(int arr[], int index, int value) {
arr[index] = value;
return value;
}
int main(void) {
int a[10];
int i;
int sum;
i = 0;
while (i < 10) {
store(a, i, i * 2);
i = i + 1;
}
sum = 0;
i = 0;
while (i < 10) {
sum = sum + a[i];
i = i + 1;
}
output(sum);
return 0;
}
int fibonacci(int n) {
if (n == 0)
return 0;
else if (n == 1)
return 1;
else
return fibonacci(n - 1) + fibonacci(n - 2);
}
int main(void) {
int n;
int i;
n = 10;
i = 0;
while (i < n) {
output(fibonacci(i));
i = i + 1;
}
return 0;
}
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