第8周课下作业1(补)
(蓝墨云上未提交,课下补的)
1 完成家庭作业4.47,4.48,4.49
2 相应代码反汇编成X86-64汇编
3 把上述X86-64汇编翻译成Y86汇编,并给出相应机器码
4.47
用指针引用数组元素实现冒泡排序:
void bubble_a(long *data,long count)
{
long i,last,t;
for(last = count-1;last>0;last--)
{
for(i = 0;i<last;i++)
{
if(*(data+i+1)<*(data+i))
{
t=*(data+i+1);
*(data+i+1)=*(data+i);
*(data+i)=t;
}
}
}
}
测试结果:
这个函数和测试代码组成的Y86-64程序:
首先通过分步编译,得到对应的X86汇编程序,再将x86(Bubble_sort.s) 手动翻译为Y86(Bubble_sort.ys)(代码在这里),注意y86与x86的不同之处(见书上P252):
- Y86中要将常数加载到寄存器再进行计算,它在算术指令中不能使用立即数。
- 要实现从内存读取一个数值并将其与一个寄存器相加,Y86需要两条指令,先将内存中的数装到寄存器里。
- Y86的整数操作指令会设置条件码,不需要testq指令,直接使用跳转指令。
- 由于Y86-64指令集中所以操作都以8个字节为单位,所以在转换“movl,addl”这些四字节指令时要注意进行符号拓展。
4.48
修改4.47的代码要求不使用跳转,最多使用三次条件传送。
我重新复习并详读了P145——用条件传送实现条件分支。
- 条件传送的核心是:计算一个条件操作的两种结果,然后再根据条件是否满足从中选取一个。
- X86-64上可用的条件传送指令具体见P147图3-18
- 处理器执行条件传送指令:读源值(内存或寄存器),检查条件码,然后要么更新目的寄存器,要么保持不变。
对于这道题,要实现6~11行冒泡排序的测试与交换,且求不使用跳转,最多使用三次条件传送。
- 首先必须要看懂X86汇编代码,找到测试与交换这段C程序对应的x86汇编代码,通过我对汇编代码一行行的努力仔细分析,最终找到了对应汇编代码:
leaq 8(%rdi,%rax,8), %rsi
movq (%rsi), %rcx #从内存装入dada[i]到%rcx
leaq (%rdi,%rax,8), %rdx
movq (%rdx), %r8 #从内存装入dada[i+1]到%r8
cmpq %r8, %rcx
jge .L3 #如果%r8>=%rcx,即dada[i+1]>=dada[i],跳转到.L3改变i值进入下一次循环。
movq %r8, (%rsi)
movq %rcx, (%rdx) #这里是两数交换的汇编代码,只需将寄存器里的值交叉传回内存,不需要借助第三方。
显然这里用了一次跳转,根据条件传送指令使用规则,可以这样改写:
- 不管前面怎么写,最终是一定要从寄存器传回内存。
- 考虑用条件传送cmovnge(有符号<),当%r8<%rcx,即dada[i+1]<dada[i]时,从内存交叉传到寄存器。
- 再从寄存器对应传回内存。
- 这样使用了两次条件传送。
leaq 8(%rdi,%rax,8), %rsi
movq (%rsi), %rcx
leaq (%rdi,%rax,8), %rdx
movq (%rdx), %r8
cmpq %r8, %rcx
cmovnge (%rsi),%r8
cmpq %r8, %rcx
cmovnge (%rdx),%rcx
movq %r8, (%rdx)
movq %rcx, (%rsi)
4.49
修改4.47的代码要求不使用跳转,最多使用一次条件传送。
这里的分析与4.48同理。需要考虑的是如何将上一题中两次条件传送合并为一次条件传送。
我注意到两次条件传送的条件都是同一个,可以这样考虑,虽然这是在汇编程序里,实现两数交换的方式是,是在寄存器和内存之间交叉传送。
但我们不妨考虑在高级语言里实现两数交换的方式,即借助第三个变量%r9,这样只需在最后一步交换时,判断是否传送,即仅一次条件传送。
leaq 8(%rdi,%rax,8), %rsi
movq (%rsi), %rcx
leaq (%rdi,%rax,8), %rdx
movq (%rdx), %r8
movq %rcx, %r9
movq %r8, %rcx
cmpq %r8, %r9
cmovnge %r9,%r8
movq %r8, (%rdx)
movq %9, (%rsi)