archlab 解题记录
临近期末,不如来点好玩的吧
实验说明
这个 lab 的实验说明就比较劝退,我先看了开头的 Part A 部分,大致意思是,课程设置了一种新的指令集:Y86-64
,相对于 x86
指令集精简了很多, 以用来进行实验,幸好的是,经过前两个 lab 的摧残,已经对汇编代码有抗性较好的认识了。
事前准备
首先使用 tar -zvf archlab-handout.tar
解压实验文件压缩包,然后运行
1 | cd sim |
编译工具链,期间遇到了 /usr/bin/ld: cannot find -ltk /usr/bin/ld: cannot find -ltcl
找不到两个库的错误,看了下这两个库应该是 gui 相关的,理论上来说可以直接在 makefile 里关掉,或者也可以下载安装
1 | apt install -y tk-dev tcl-dev |
Part A
进入正题,part a 主要要求我们使用 Y86 指令翻译一些 C 语言程序指令,其中需要翻译的 examples.c
如下:
1 | typedef struct ELE { |
sum_list
为了测试,我们首先编写运行框架
1 | 0 |
接下来,编写 sum
函数:
1 | sum: |
函数逻辑很简单,不多做解释,有几个要注意的点是:
- Y86 指令集只支持偏移寻址,即
instant(%reg)
- Y86 指令集不支持直接使用内存地址来进行算数运算
rsum_list
使用递归的方法遍历链表并且将其中的值相加
1 | rsum: |
需要注意的点:%rax 寄存器为调用者保存寄存器(caller saved register),如果要在本函数中继续使用,需要将其 push 入栈,然后再 pop。 代码中的%r10 寄存器理论上也是调用者保存的,但是由于接下来的代码中并没有需要其保存值使用的,因此并没有将其入栈。
2020/12/6 Updated
copy_block
用于从一个块中复制指定长度的内容到另一个块中,同样是使用循环结构,但是这一次需要加上条件判断,这里使用到的有:
subq $0, %rdx
:(pseudo)将%rdx 中的内容减去 0,设置 CF(最高位是否进位^是否是减法)、SF(结果是否为负数)、ZF(结果是否为 0),用于跳转使用(或者可以简单理解为cmp $0, %rdx
,然鹅 Y86 并没有支持)
jg body
:如果%rdx > 0,则跳转,是否跳转取决于~(SF^OF)&~ZF
下面贴上 Y86 代码:
1 | copy_block: |
至此,Part A 告一段落,下面进入 Part B
Part B
阅读题面:
Your task in Part B is to extend the SEQ processor to support the iaddq
.
很明显,这道题需要我们使用 HCL
语言来实现 iaddq
指令,即允许直接给一个寄存器加上一个立即数。参照 CSAPP 的图 4-18 我们可以做出 iaddq 的整个流程:
阶段 | Opq rA, rB |
iaddq V, rB |
---|---|---|
取指 | icode: ifun ← M1[PC] rA:rB←M1[PC+1] valP← PC+2 |
icode: ifun ← M1[PC] rA:rB←M1[PC+1] valC←M8[PC+2] valP←PC+10 |
译码 | valA←R[rA] valA←R[rA] |
valB←R[rB] |
执行 | valE←valB OP valA | valE←valB + valC |
访存 | ||
写回 | R[rB]←valE | R[rB]←valE |
更新 PC | PC←valP | PC←valP |
valC、valE、valP:分别代表运算所 fetch 的常数、alu 的运算结果、命令执行完后下一条命令的地址。
这么一看,iaddq 其实和 irmovq 指令是基本完全相同的,但是要指出以下两个不同点:
- iaddq 指令需要获取 rB 寄存器中的值参与运算,而 irmovq 则不需要。
- iaddq 指令属于运算指令,需要设置 Flag 的值改变,而 irmovq 不需要。
因此总的算下来,我们在seq-full.hcl
中需要区别于 IIRMOVQ 的只有两个地方:
1 | ################ Decode Stage ################################### |
1 | ## Select input B to ALU |
结果就是将立即数与寄存器中的值相加,存回寄存器中,按照实验指导书上的指令 pass 掉了所有的 benchmark,进入下一个阶段。
Part C
2021-01-07 Updated 来填坑了!之前一直不填这个坑一是因为期末考试,二是解决期末考试之后流水线看了挺久,但是发现其实要拿分和流水线(HCL 文件)中的内容修改关系不大,除去添加 iaddq 的部分,对于pipe-full.hcl
几乎不用做任何修改。
既然是利用流水线优化性能,那么就要谈到流水线处理器的几个基本优化方法:
- 避免寄存器冲突(提前读取减少 bubble)
- 减少分支跳转带来的惩罚(循环展开,以及利用
总是跳转
的预测原理)
源文件不再展示,这里使用了以上两个技巧得到的代码如下
1 | ncopy: |
- 将循环步数展开为
8
剩下的情况暴力处理。 - 大量使用了
iaddq
指令来减少常数写入、读出造成的损失。 - 把所有
jmp Done
的部分全部替换为ret
,可以减少分支预测惩罚以及跳转所需的额外指令。
最后得到的结果不能说太好但是也是中规中矩如下
Average CPE 7.86 Score 52.7/60.0
Part C 到此结束,总结一下,这个 lab 前两个阶段没有任何难度,有手就行,但是最后一个阶段至少对于我来说还是花了不少时间来查阅资料、阅读书籍,大约花费了一天整的时间来完成,收获不少,还是非常值得花费时间来做这个 lab 的。