对应书本第四章内容。通过对Y86-64的ISA构造,熟悉对x86-64指令集。
实验分为三个部分,
第一部分是熟悉C到ys汇编。
第二部分是修改线性的SEQ,增加iaddq这个指令。
第三部分是最核心,也是最困难的开放部分,优化代码以及体系结构。
本机使用win10 +wsl2.0 + ubuntu18.04完成实验。
点击查看我的全部代码
CSAPP LAB
关于CSAPP cannot find -ltcl -ltk的问题
[读书笔记]CSAPP:ArchLab
通俗解说CSAPP的archlab partC
csapp-Archlab
在sim/misc 文件夹中运行./yas sum.ys
在sim/misc ./yis sum.yo
根据y86-code/asum.ys的例子中可以总结出ys文件的通常结构。
# Execution begins at address 0
.pos 0
irmovq stack, %rsp # Set up stack pointer
call main # Execute main program
halt # Terminate program
# 内存区域,存放数据/链表之类
...
# END
main:
irmovq $4,%rsi #参数准备
call myFunc # myFunc( 4)
ret
# long myFunc(long i)
# i in %rdi
sum:
... #程序具体内容
ret # Return
# Stack starts here and grows to lower addresses.
# 这里自定义栈开始地址
.pos 0x200
stack:
任务:模拟examples.c完成三个函数的翻译:从c语言到Y86-64的汇编语言。
examples.c中,定义了一个链表。
/* linked list element */
typedef struct ELE {
long val;
struct ELE *next;
} *list_ptr;
同时给了测试数据:
# Sample linked list
.align 8
ele1:
.quad 0x00a
.quad ele2
ele2:
.quad 0x0b0
.quad ele3
ele3:
.quad 0xc00
.quad 0
/* sum_list - Sum the elements of a linked list */
long sum_list(list_ptr ls)
{
long val = 0;
while (ls) {
val += ls->val;
ls = ls->next;
}
return val;
}
对链表进行迭代求和。
做一个出色的copycat。
sum_list.ys
# Execution begins at address 0
.pos 0
irmovq stack, %rsp # Set up stack pointer
call main # Execute main program
halt # Terminate program
# 内存区域,存放数据/链表之类
# Sample linked list
.align 8
ele1:
.quad 0x00a
.quad ele2
ele2:
.quad 0x0b0
.quad ele3
ele3:
.quad 0xc00
.quad 0
# END
main:
irmovq ele1,%rdi #参数准备
call sum_list # sum_list(ele1)
ret
# long sum_list(long i)
# ele1 in %rdi
sum_list:
xorq %rax,%rax #val=0
loop:
mrmovq (%rdi),%r8 #读取node.val值到寄存器r8
addq %r8,%rax #将结果加到return val中
mrmovq 8(%rdi),%rdi
jmp test #无条件跳转到test
test:
andq %rdi,%rdi # set CC。这个操作我有点迷惑,用andq来设置CC
jne loop
ret # Return
# Stack starts here and grows to lower addresses.
# 这里自定义栈开始地址
.pos 0x200
stack:
进行测试:
./yas sum_list.ys
和./yis sum_list.yo
可以查看结果%rax: 0x0000000000000000 0x0000000000000cba
,没有问题。
/* rsum_list - Recursive version of sum_list */
long rsum_list(list_ptr ls)
{
if (!ls)
return 0;
else {
long val = ls->val;
long rest = rsum_list(ls->next);
return val + rest;
}
}
是一个递归的版本。
总体结构和sum_list一致。需改rsum_list。这里用栈的方式先存储了node.val,然后到达了ptr的末尾的时候,才开始相加addq %rbx, %rax
。
rsum_list.ys
# Execution begins at address 0
.pos 0
irmovq stack, %rsp # Set up stack pointer
call main # Execute main program
halt # Terminate program
# 内存区域,存放数据/链表之类
# Sample linked list
.align 8
ele1:
.quad 0x00a
.quad ele2
ele2:
.quad 0x0b0
.quad ele3
ele3:
.quad 0xc00
.quad 0
# END
main:
irmovq ele1,%rdi #参数准备
call rsum_list # rsum_list(ele1)
ret
# long rsum_list(long i)
# ele1 in %rdi
rsum_list:
pushq %rbx #自递归,需要保存目前的结果。其实rbx就是存了 上一个node.val
xorq %rax,%rax #return val=0
andq %rdi, %rdi
je finish
mrmovq (%rdi), %rbx #当前的node.val
mrmovq 8(%rdi), %rdi #node = node->next
call rsum_list
addq %rbx, %rax #在这里仍然会继续往下执行。
finish:
popq %rbx #
ret # Return
# Stack starts here and grows to lower addresses.
# 这里自定义栈开始地址
.pos 0x200
stack:
测试可行。
/* copy_block - Copy src to dest and return xor checksum of src */
long copy_block(long *src, long *dest, long len)
{
long result = 0;
while (len > 0) {
long val = *src++; //两个语句:long val = *src;src++;
*dest++ = val;//两个语句:*dest = val;dest++
result ^= val;//update checksum
len--;
}
return result;
}
.align 8
# Source block
src:
.quad 0x00a
.quad 0x0b0
.quad 0xc00
# Destination block
dest:
.quad 0x111
.quad 0x222
.quad 0x333
慢慢做。
copy_block.ys
# Execution begins at address 0
.pos 0
irmovq stack, %rsp # Set up stack pointer
call main # Execute main program
halt # Terminate program
# 内存区域,存放数据/链表之类
.align 8
# Source block
src:
.quad 0x00a
.quad 0x0b0
.quad 0xc00
# Destination block
dest:
.quad 0x111
.quad 0x222
.quad 0x333
#END
main:
irmovq src,%rdi #param1
irmovq dest,%rsi #param2
irmovq $3,%rdx #param3
call copy_block # copy_block(src,dest,)
ret
# long copy_block(long *src, long *dest, long len)
# src in %rdi
# dest in %rsi
# 3 in %rdx
copy_block:
pushq %rbx
pushq %r9
pushq %r10
xorq %rax,%rax #result=0
irmovq $8,%r9
irmovq $1,%r10
jmp test
loop:
mrmovq (%rdi),%rbx #long val = *src;
addq %r9,%rdi #src++;
rmmovq %rbx,(%rsi) #*dest = val;
addq %r9,%rsi #dest++;
xorq %rbx,%rax #update checksum
subq %r10,%rdx #len--
test:
andq %rdx,%rdx #set CC
jne loop #Stop when len = 0
popq %r10
popq %r9
popq %rbx
ret
# Stack starts here and grows to lower addresses.
.pos 0x200
stack:
值得注意的点:
在sim/seq文件夹。
任务:拓展SEQ处理器,即修改seq-full.hcl文件以支持iaddq。
阶段 | iaddq V,rB |
---|---|
取指Fetch | icode:ifun <- M1[PC] rA:rB <- M1[PC+1] D <- M8[ PC+2 ] valP <- PC+10 |
译码Decode | valB <- R[rB] |
执行Execute | valE <- valB + valC Set CC |
访存Memory | |
写回 | R[rB] <- valE |
更新PC Program Counter Update |
PC <- valP |
书本的P280详细介绍了五个阶段的过程。
符号表已经增加了iaddq
# Instruction code for iaddq instruction
wordsig IIADDQ 'I_IADDQ'
所以我们按照顺序修改五个阶段即可。
这个阶段和irmovq V,rB是一模一样的。所以它怎样,iaddq就怎样。
#有效指令
bool instr_valid = icode in
{ INOP, IHALT, IRRMOVQ, IIRMOVQ, IRMMOVQ, IMRMOVQ,
IOPQ, IJXX, ICALL, IRET, IPUSHQ, IPOPQ,IIADDQ };
#需要寄存器
bool need_regids =
icode in { IRRMOVQ, IOPQ, IPUSHQ, IPOPQ,
IIRMOVQ, IRMMOVQ, IMRMOVQ ,IIADDQ};
#需要常量
bool need_valC =
icode in { IIRMOVQ, IRMMOVQ, IMRMOVQ, IJXX, ICALL ,IIADDQ};
要读寄存器rB的值。
指定srcB
## What register should be used as the B source?
word srcB = [
icode in { IOPQ, IRMMOVQ, IMRMOVQ,IIADDQ } : rB;
icode in { IPUSHQ, IPOPQ, ICALL, IRET } : RRSP;
1 : RNONE; # Don't need register
];
valE <- valB + valC
即aluA = valC;aluB = valB ;
## Select input A to ALU
word aluA = [
icode in { IRRMOVQ, IOPQ } : valA;
icode in { IIRMOVQ, IRMMOVQ, IMRMOVQ,IIADDQ } : valC;
icode in { ICALL, IPUSHQ } : -8;
icode in { IRET, IPOPQ } : 8;
# Other instructions don't need ALU
];
## Select input B to ALU
word aluB = [
icode in { IRMMOVQ, IMRMOVQ, IOPQ, ICALL,
IPUSHQ, IRET, IPOPQ,IIADDQ } : valB;
icode in { IRRMOVQ, IIRMOVQ } : 0;
# Other instructions don't need ALU
];
这里我们是默认使用了add,所以不需要修改alufun
但我们希望iaddq可以更新CC
## Should the condition codes be updated?
bool set_cc = icode in { IOPQ, IIADDQ};
没有。
R[rB] <- valE
目标寄存器是rB。这里和irmovq也是一样的。
## What register should be used as the E destination?
word dstE = [
icode in { IRRMOVQ } && Cnd : rB;
icode in { IIRMOVQ, IOPQ,IIADDQ} : rB;
icode in { IPUSHQ, IPOPQ, ICALL, IRET } : RRSP;
1 : RNONE; # Don't write any register
];
默认就会更新为valP,此处不用修改。
- 根据
seq-full.hcl
文件构建新的仿真器。注意指定一下tcl和tk.make VERSION=full
- 在小的Y86-64程序中测试你的方法
./ssim -t ../y86-code/asumi.yo
如果失败了,还要重新修改你的实现
- 使用基准程序来测试你的方法
(cd ../y86-code; make testssim)
- 一旦可以正确执行基准测试程序,则应在
../ptest
中运行大量的回归测试测试除了
iaddq
以外的所有指令(cd ../ptest; make SIM=../seq/ssim)
测试我们实现的
iaddq
指令(cd ../ptest; make SIM=../seq/ssim TFLAGS=-i)
至此PART B完成。
但需要承认的是,这里可参考元素过多,其实对五个阶段还并不是很熟悉就可以完成。
在sim/pipe。
任务:修改ncopy.ys and pipe-full.hcl两个文件,使得ncopy.ys跑得越快越好。
ncopy.c
/*
* ncopy - copy src to dst, returning number of positive ints
* contained in src array.
*/
word_t ncopy(word_t *src, word_t *dst, word_t len)
{
word_t count = 0;
word_t val;
while (len > 0) {
val = *src++; //val = *src; src++;
*dst++ = val; //*dst = val; dst++;
if (val > 0)
count++;
len--;
}
return count;
}
ncopy.ys
##################################################################
# Do not modify this portion
# Function prologue.
# %rdi = src, %rsi = dst, %rdx = len
ncopy:
##################################################################
# You can modify this portion
# Loop header
xorq %rax,%rax # count = 0;
andq %rdx,%rdx # len <= 0?
jle Done # if so, goto Done:
Loop:
mrmovq (%rdi), %r10 # read val from src
rmmovq %r10, (%rsi) # and store it to dst
andq %r10, %r10 # val <= 0?
jle Npos # if so, goto Npos:
irmovq $1, %r10
addq %r10, %rax # count++
Npos:
irmovq $1, %r10
subq %r10, %rdx # len--
irmovq $8, %r10
addq %r10, %rdi # src++
addq %r10, %rsi # dst++
andq %rdx,%rdx # len > 0?
jg Loop # if so, goto Loop:
##################################################################
# Do not modify the following section of code
# Function epilogue.
Done:
ret
##################################################################
# Keep the following label at the end of your function
End:
#/* $end ncopy-ys */
../misc/yas ncopy.ys
生成ncopy.yo
./check-len.pl < ncopy.yo
检测长度
make drivers
生成ncopy.ys的测试程序
make psim VERSION=full
生成新的psim
./psim -t sdriver.yo
测试small 4-element array
./psim -t ldriver.yo
测试larger 63-element array
./correctness.pl
测试不同Block length下code range是否符合
./benchmark.pl
评分
可以用./correctness.pl
测试ncopy
函数的正确性,然后使用./benchmark.pl
来测试函数的性能,希望CPE越小越好。初始CPE为15.18,大于10.5为0分,小于7.5为满分60。
if the simulated code requires C cycles(时钟周期) to copy a block of N(数组大小) elements, then the CPE is C=N.
We will therefore evaluate the performance of your function by computing the average of the CPEs for blocks ranging from 1 to 64 elements.
##################################################################
# You can modify this portion
# Loop header
xorq %rax,%rax # count = 0;
andq %rdx,%rdx # len <= 0?
jle Done # if so, goto Done:
Loop:
mrmovq (%rdi), %r10 # read val from src...
rmmovq %r10, (%rsi) # ...and store it to dst
andq %r10, %r10 # val <= 0?
jle Npos # if so, goto Npos:
iaddq $1, %rax # count++
Npos:
iaddq $-1, %rdx # len--
iaddq $8, %rdi # src++
iaddq $8, %rsi # dst++
andq %rdx,%rdx # len > 0?
jg Loop # if so, goto Loop:
##################################################################
此时是12.70,仍然没有分。
xorq %rax,%rax # count = 0;
删掉,因为是单次程序,rax初始化就是0andq %rdx,%rdx
这个操作再去设置符号位。但是要移动一下顺序,使得iaddq $-1, %rdx
和jg Loop
临接##################################################################
# You can modify this portion
# Loop header
andq %rdx,%rdx # len <= 0?
jle Done # if so, goto Done:
Loop:
mrmovq (%rdi), %r10 # read val from src...
rmmovq %r10, (%rsi) # ...and store it to dst
andq %r10, %r10 # val <= 0?
jle Npos # if so, goto Npos:
iaddq $1, %rax # count++
Npos:
iaddq $8, %rdi # src++
iaddq $8, %rsi # dst++
iaddq $-1, %rdx # len--
jg Loop # if so, goto Loop:
##################################################################
到这里了就要开始看书本第五章了。先去看。
好的看完回来了。
通常展开k小于16,但现在有代码长度限制(9是极限)。看了几篇博文都说展开到7 8 9 都是差不多的。
我们就挑一个吉利的数字8,做8x8展开。
//原版本
word_t ncopy1(word_t *src, word_t *dst, word_t len)
{
word_t count = 0;
word_t val;
while (len > 0) {
val = *src;
src++;
*dst = val;
dst++;
if (val > 0)
count++;
len--;
}
return count;
}
//新版本 8x8循环展开
word_t ncopy1(word_t *src, word_t *dst, word_t len)
{
word_t count = 0;
word_t acc0;
word_t acc1;
word_t acc2;
word_t acc3;
word_t acc4;
word_t acc5;
word_t acc6;
word_t acc7;
while (len - 8 > 0) {
acc0 = *(src);
acc1 = *(src+1);
acc2 = *(src+2);
acc3 = *(src+3);
acc4 = *(src+4);
acc5 = *(src+5);
acc6 = *(src+6);
acc7 = *(src+7);
if (... > 0) //八个都判断是否添加
count++;
*(dst) = acc0; //八个都赋值
...
len-=8;
src+=8;
dst+=8;
}
//处理剩余的
while (len > 0) {
val = *src;
src++;
*dst = val;
dst++;
if (val > 0)
count++;
len--;
}
return count;
}
所以,
ncopy.ys
##################################################################
# You can modify this portion
# Loop header
andq %rdx,%rdx # len <= 0?
jle Done # 如果一开始输入的len就<=0,直接结束
jmp test
Loop8x8:
#取地址
mrmovq 0(%rdi), %r8
mrmovq 8(%rdi), %r9
mrmovq 16(%rdi), %r10
mrmovq 24(%rdi), %r11
mrmovq 32(%rdi), %r12
mrmovq 40(%rdi), %r13
mrmovq 48(%rdi), %r14
mrmovq 56(%rdi), %rbx
#赋值
rmmovq %r8, 0(%rsi)
rmmovq %r9, 8(%rsi)
rmmovq %r10, 16(%rsi)
rmmovq %r11, 24(%rsi)
rmmovq %r12, 32(%rsi)
rmmovq %r13, 40(%rsi)
rmmovq %r14, 48(%rsi)
rmmovq %rbx, 56(%rsi)
#判断是否可以count+1
judge0:
andq %r8, %r8
jle judge1
iaddq $1, %rax
judge1:
andq %r9, %r9
jle judge2
iaddq $1, %rax
judge2:
andq %r10, %r10
jle judge3
iaddq $1, %rax
judge3:
andq %r11, %r11
jle judge4
iaddq $1, %rax
judge4:
andq %r12, %r12
jle judge5
iaddq $1, %rax
judge5:
andq %r13, %r13
jle judge6
iaddq $1, %rax
judge6:
andq %r14, %r14
jle judge7
iaddq $1, %rax
judge7:
andq %rbx, %rbx
jle step8x8
iaddq $1, %rax
step8x8:
iaddq $64,%rdi
iaddq $64,%rsi
test:
#这里需要判断长度是否有8
iaddq $-8, %rdx
jg Loop8x8 #长度没有8,有则8x8循环拓展。否则1x1慢慢迭代
iaddq $8, %rdx #要把减去的8加回去才能循环
Loop1x1:
mrmovq (%rdi), %r10 # read val from src
rmmovq %r10, (%rsi) # and store it to dst
andq %r10, %r10 # val <= 0? 这里已经是guarded-do了
jle Npos1x1 # if so, goto Npos:
iaddq $1, %rax # count++
Npos1x1:
iaddq $8, %rdi # src++
iaddq $8, %rsi # dst++
iaddq $-1, %rdx # len--
jg Loop1x1 # if so, goto Loop:
##################################################################
现在CPE是8.73,CPE是35.3/60.0 。
我们看到 当不使用8x8的时候,居然要这么大开销。每当剩余长度很短的时候,又会使用线性迭代的方式,比较吃亏。
ncopy
0 12
1 31 31.00
2 43 21.50
3 52 17.33
4 64 16.00
5 73 14.60
6 85 14.17
7 94 13.43
8 106 13.25
有一种思路,就是:剩余多少个,就用几路展开。这样就舒服了。
但是如何快速判断现在剩余多少个呢?可以用二分查找。
另外,由于代码是有数字限制。所以最好是嵌套的方式,就不用copy那么多代码。根据reference还可以戳气泡来使得流水线更高效,让人敬佩的方案。
但进一步完善我就不做了。若果有日发现这部分很重要会再来研究。
欢迎留言交流。