CSAPP 第三章家庭作业

CSAPP 第三章家庭作业

参考了

《深入理解计算机系统》第三版]: https://blog.csdn.net/weixin_47089544/article/details/124302414
深入理解计算机系统(第三版)作业题答案(第三章)]: (https://www.cnblogs.com/machao/p/8471809.html

深入理解计算机系统: http://doraemonzzz.com/2021/04/08/深入理解计算机系统 第3章 习题解析 Part1/
CSAPP-3e-Solutions/chapter3/3.59: https://dreamanddead.github.io/CSAPP-3e-Solutions/chapter3/3.59/
非常感谢

3.58 一个函数原型为decode2(long x,long y,long z)

题目

long decode2(long x,long y,long z);

GCC产生如下汇编代码:

1 decode2:
2	subq	%rdx,%rsi
3	imulq	%rsi,%rdi
4	movq	%rsi,%rax
5	salq	$63,%rax
6	sarq	$63,%rax 
7	xorq	%rdi,%rax
8	ret

参数x、y和z通过寄存器%rdi、%rsi%rdx传递。代码将返回值存放在寄存器%rax中。
写出等价于上述汇编代码的decode2的C代码。

过程

先看看汇编代码:

1 decode2:
2	subq	%rdx,%rsi//y=y-z
3	imulq	%rsi,%rdi//x=x*y
4	movq	%rsi,%rax//res=y
5	salq	$63,%rax//res<<=63
6	sarq	$63,%rax //res>>=63
7	xorq	%rdi,%rax//res=res^x
8	ret//return res

得到

decode2(long x,long y,long z){
    long res=y-z;
    return (res*x)^(res<<63>>63);
}

3.59 stored_prod计算两个64位有符号值x和y 的 128位乘积

题目

下面的代码计算两个64位有符号值x和y 的 128位乘积,并将结果存储在内存中:

typedef __int128 int128_t;
void store_prod(int128_t *dest,int64_t x,int64_t y) {
	*dest =  x* (int128_t) y;
}

GCC产出下面的汇编代码来实现计算:

1 store_prod :
2	movq	%rdx,%rax
3	cqto
4	movq	%rsi,%rcx
5	sarq	$63,%rcx
6	imulq	%rax,%rcx
7	imulq	%rsi,%rdx
8	addq	%rdx,%rcx
9	mulq	%rsi
10	addq	%rcx,%rdx
11	movq	%rax,(%rdi)
12	movq	%rdx,8(%rdi)
13	ret

为了满足在64位机器上实现128位运算所需的多精度计算,这段代码用了三个乘法。描述用来计算乘积的算法,对汇编代码加注释,说明它是如何实现你的算法的。提示:在把参数x和y扩展到128位时,它们可以重写为

\[x=2^{64}\cdot x_h+x_l\\ y=2^{64}\cdot y_h+y_l\\ \]

这里xh,xl,yh,yl都是64位值。类似地,128位的乘积可以写成

\[p=2^{64}\cdot p_h + p_l \]

这里ph和pl是64位值。请解释这段代码是如何用xh,xl,yh,yl来计算ph和pl的。

过程

需要看P1333.5.5 特殊的算术操作,介绍了汇编代码中imulqmulq等指令,和题目中的函数。

下标h和l表示高位(符号位)和低位,之所以可以这么写是因为符号拓展,以4位二进制int为例:1111的补码数,为-1.将其进行符号拓展后为1111 1111,其值也为-1,但这里可以将1111 1111写为高位1111的补码数 *2^4 + 低位1111的无符号数:
即-1 *2^4 + 15 = -1.

原理:%rdx和%rax的二进制连起来表示这个数,既然连起来了,符号位就跑到了%rdx的最高位了,除符号位权值为负外,其余位的权值均为正。所以,高位寄存器%rdx当做补码数,低位寄存器%rax当做无符号数。因为符号位现在在高位寄存器那儿呢,所以高位寄存器当做补码数了;而低位寄存器的每一位的权值现在都是正的了,所以低位寄存器要当做无符号数。

首先看公式,

\[\begin{aligned} x \times y & = 扩展到128位后的x与y相乘\\ &=\left(2^{64} \cdot x_{h}+x_{l}\right) \times \left(2^{64} \cdot y_{h}+y_{l}\right)\\ &= 2^{128}. (x_h y_h) + 2^{64}.(x_h y_l + x_l y_h) + x_l y_l \\ &第一项2^{128}. (x_h y_h)溢出,截断后全为0,忽略\\ &第二项,(x_hy_l+x_ly_h)这个数值 是需要放在高位寄存器中的\\&(因为这一项乘以的数为2^{64},假设x_hy_l 分别是-1和UMAX, \\&仅仅是它俩的乘积都会使得高位寄存器溢出(考虑补码数和无符号数的表示范围就能想到),如果溢出,放入高位寄存器时会自行截断。 \\&第三项x_ly_l直接使用双寄存器来保存结果. \end{aligned} \]

然后看汇编代码

//dest in %rdi, x in %rsi, y in %rdx
1 store_prod:
2     movq   %rdx, %rax   # %rax = y
3     cqto                #转换q为16字节长,即4字符号拓展到8字,假如y的符号位为1,那么%rdx所有位都是1(此时值是-1),否则,%rdx全为0(此时值是0).%rdx = yh
4     movq   %rsi, %rcx   # %rcx = x
5     sarq   $63,  %rcx   # 将%rcx向右移63位,跟%rdx的含义一样,二进制位要么全是1,要么是0,%rcx = xh.
6     imulq  %rax, %rcx   # %rcx = y * xh
7     imulq  %rsi, %rdx   # %rdx = x * yh
8     addq   %rdx, %rcx   # %rcx = y * xh + x * yh,计算了第二项
9     mulq   %rsi         # 无符号计算 xl*yl,mulq命令会将xl*yl的128位结果的高64位放在%rdx,低位放在%rax,计算了第三项.
10    addq   %rcx, %rdx   # 将第二项计算结果加到%rdx
11    movq   %rax, (%rdi) # 将%rax的值放到dest的低位
12    movq   %rdx, 8(%rdi)# 将%rdx的值放到dest的高位
13    ret

重点讲一下6-8行,发现这里代码计算的是

\[(x_hy+xy_h) \]

而数学公式要求的不一样,之所以汇编要如此计算,是利用了相同的位级向量,无论用无符号数乘法还是补码乘法,其结果的截断的位级表示肯定是一样的。

但这里有点不一样,给定x'和y'两个位级向量,固定将x'看作补码数,而将y'分别看作补码数和无符号数,那么x与y的两种乘积的截断的位级表示是一样的。接下来用个小例子来证明该结论。(注意代码是将乘积的截断的位级表示看作补码数的)

假设整数类型为3位,x'和y'分别为111111,x的值为-1,而y的值分别为-1,7.
首先看-1 * -1 = 1,那么位级表示为001
再看-1 * 7 = -7,那么位级表示为1001,截断后为001
证毕。

考虑下第9行是否会溢出,

\[无符号数最大为2^{64}−1,所以两个无符号数的乘积最大为(2^{64}-1)^2 \\ 等于2^{128}+1-2^{65}.而128位的补码数的最大范围为2^{127}-1,\\ 而(2^{128}+1-2^{65})-(2^{127}-1) = 2^{127}+2-2^{65} > 0,所以可能溢出。 \]

3.60 考虑下面的汇编代码

题目

long loop(long x, int n) x in % rdi,n in %esi

loop:
	movl	%esi,%ecx
	movl	$1,% edx
    mov1	$0,% eax
	jmp	.L26
.L3:
	movq	%rdi,%r8
    andq	%rdx,%r8
    orq		%r8,%rax
	salq	%c1,%rdx
.L2:
	testq	%rdx,%rdx
	jne	.L3
    rep; ret

以上代码是编译以下整体形式的C代码产生的:

long loop(1ong x,int n)
{
	long result=___;
	long mask;
    for(mask=__;mask__;mask=__){
		result |=____;
	}
	return result;
}

你的任务是填写这个C代码中缺失的部分,得到一个程序等价于产生的汇编代码。回想一下,这个函数的结果是在寄存器%rax中返回的。你会发现以下工作很有帮助:检查循环之前、之中和之后的汇编代码,形成一个寄存器和程序变量之间一致的映射。
A.哪个寄存器保存着程序值x、n、result和mask?
B.result和mask的初始值是什么?
C.mask的测试条件是什么?
D.mask是如何被修改的?
E.result是如何被修改的
F.填写这段C代码中所有缺失的部分。

过程

首先看汇编代码x in %rdi,n in %esi

loop:
	movl	%esi,%ecx	#%ecx放入n
	movl	$1,%edx		#%edx放入1
    mov1	$0,%eax		#%eax放入0
	jmp	.L2				#跳转到标号L2
.L3:					
	movq	%rdi,%r8	#%r8放入x
    andq	%rdx,%r8	#%r8=%r8&%rdx,即&%edx中的1,相当于判断x最低位是1还是0
    orq		%r8,%rax	#%rax=%rax|%r8,即%eax中的0与%r8或运算,若x最低位是1得1,否则0
	salq	%c1,%rdx	#%rdx<<=%rcx的字节(%c1为%rcx最低8位,即n的低8位),即
.L2:					#
	testq	%rdx,%rdx	#检验%rdx&%rdx
	jne	.L3				#jne即不等/ZF非零时跳转,即%rdx非零就跳转到标号L3
    rep; ret

因为x in %rdi,n in %esi,那么判断结束的部分使用了%rdx,所以mask存放在%rdx中,而返回的result肯定在%rax中,所以得到result初始值为0,而mask为1,判断结束部分是非零时跳转,所以判断条件为mask!=0,而循环修改值是将mask左移n位,汇编代码中低8位是为了保护位移,接下来是result,是mask先与%r8中的x与运算,得到结果与%rax进行或运算。

long loop(long x,int n)
{
	long result=0;
	long mask;
    for(mask=1;mask!=0;mask=mask<<n){
		result |=mask&x;
	}
	return result;
}

3.61 修改cread_alt

题目

在3.6.6节,我们查看了下面的代码,作为使用条件数据传送的一种选择:

long cread(long *xp){
	return(xp?*xp:0);
}

我们给出了使用条件传送指令的一个尝试实现,但是认为它是不合法的,因为它试图从一个空地址读数据。
写一个C函数cread_alt,它与cread有一样的行为,除了它可以被编译成使用条件数据传送。当编译时,产生的代码应该使用条件传送指令而不是某种跳转指令。

过程

条件表达式和赋值的通用形式:v=test-expr?then-expr:else-expr;,基于条件传送的代码会对then-exprelse-expr都求值,可抽象为如下:

v=then-expr;
ve=else-expr;
t=test-expr;
if(!t)
    v=ve;

所以cread函数编译后得到:

# long cread(long *xp)
# xp in %rdi
cread:
  movq (%rdi), %rax	#v=then-expr,即使判断条件为false此行还是间接引用了空指针
  testq %rdi, %rdi	#检验t
  movl $0, %edx		#ve=0
  cmove %rdx, %rax	#若t==0,进行传送操作,v=ve
  ret				#return v

为了避免对空指针的间接引用,将判断条件改为对xp求反,规避then-expr,else-expr中对空指针的计算。

  • 8
    点赞
  • 55
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值