题目描述:
下面的代码计算两个
64
64
64 位有符号值
x
x
x 和
y
y
y 的
128
128
128 位乘积,并将结果储存在内存中:
typedef __int128 int128_t;
void store_prod(int128_t *dest, int64_t x, int64_t y) {
*dest = x * (int128_t) y;
}
G C C GCC GCC 编译器产出了下面的汇编代码来实现计算:
store_prod:
movq %rdx, %rax
cqto
movq %rsi, %rcs
sarq $63, %rcx
imulq %rax, %rcx
imulq %rsi, %rdx
addq %rdx, %rcx
mulq %rsi
addq %rcx, %rdx
movq %rax, (%rdi)
movq %rdx, 8(%rdi)
为了满足在 64 64 64 位机器上实现128位运算所需的多精度计算,这段代码用了三个乘法。描述用来计算乘积的算法,对汇编代码加注释,说明它是如何实现你的算法的。提示:在参数 x x x 和 参数 y y y 拓展 128 128 128 位时,他们可以重写为 x = 2 64 × x h + x l x = 2^{64}\times x_h + x_l x=264×xh+xl 和 y = 2 64 × y h + y l y = 2^{64} \times y_h+y_l y=264×yh+yl 这里 x h , x l , y h , y l x_h,x_l,y_h,y_l xh,xl,yh,yl 都是 64 64 64 位值。请解释这段代码是如何用 x h , x l , y h , y l x_h,x_l,y_h,y_l xh,xl,yh,yl 来计算 p h p_h ph 和 p l p_l pl 的。
分析:
说实话第一次接触
C
C
C 语言底层的汇编逻辑挺不适应的,这些寄存器跑到在这儿有哪儿真的很麻烦。不过最终发现其实我对拓展运算的不熟悉导致的。
因为
64
64
64 位机器,所以一个寄存器最大也就
64
64
64 位,这导致如果要拓展到
128
128
128 位的运算,得借助两个寄存器。最初我十分不解 sarq $63,%rcx
的作用是什么,真的脑袋完全无法想到。前面书本中介绍的拓展就是只有
c
p
t
o
cpto
cpto (
8
8
8 字节拓展到
16
16
16 字节),以及
c
l
t
q
cltq
cltq 这两种符号拓展。
既然是符号拓展,注意到这右移运算也是算术右移,好了,真相大白,在这之前还有一句 movq %rsi, %rcx
即此时
%
r
c
x
\%rcx
%rcx 执行了算术右移后相当于是对
x
x
x 进行了符号拓展,而
%
r
c
x
\%rcx
%rcx 存的就是
x
h
x_h
xh 。
好了,理解了这个右移后,就知道在执行 imulq %rax,%rcx
,前每个寄存器存的数据的实际意义了,如下表所示:
%rdx | %rax | %rcx | %rsi |
---|---|---|---|
y h y_h yh | y l y_l yl | x h x_h xh | x l x_l xl |
此时现理解数学上的
128
128
128 位乘法,定义
×
128
t
\times_{128}^{t}
×128t 运算符为
128
128
128 为的补码乘法运算。根据第二章可知
x
×
128
t
y
=
x
×
y
(
m
o
d
2
128
)
x \times_{128}^t y=x\times y \pmod{2^{128}}
x×128ty=x×y(mod2128) 。所以
x
×
128
t
y
=
x
×
y
(
m
o
d
2
128
)
=
(
x
h
×
2
64
+
x
l
)
(
y
h
×
2
64
+
y
l
)
(
m
o
d
2
128
)
x \times_{128}^t y=x\times y \pmod{2^{128}} = (x_h\times 2^{64}+x_l)(y_h\times 2^{64}+y_l)\pmod{2^{128}}
x×128ty=x×y(mod2128)=(xh×264+xl)(yh×264+yl)(mod2128)
展开可得
x
×
128
t
y
=
(
x
h
×
y
l
+
x
l
×
y
h
+
⌊
x
l
×
y
l
2
64
⌋
(
m
o
d
2
64
)
)
×
2
64
+
(
x
l
×
y
l
(
m
o
d
2
64
)
)
x \times_{128}^t y = (x_h \times y_l + x_l \times y_h + \lfloor\frac{x_l\times y_l}{2^{64}}\rfloor \pmod{2^{64}}) \times 2^{64} + (x_l\times y_l\pmod{2^{64}})
x×128ty=(xh×yl+xl×yh+⌊264xl×yl⌋(mod264))×264+(xl×yl(mod264))
所以乘积结果 p = p h × 2 64 + p l p = p_h\times2^{64}+p_l p=ph×264+pl 的 p h = x h × y l + x l × y h + ⌊ x l × y l 2 64 ⌋ ( m o d 2 64 ) p_h = x_h \times y_l + x_l \times y_h + \lfloor\frac{x_l\times y_l}{2^{64}}\rfloor \pmod{2^{64}} ph=xh×yl+xl×yh+⌊264xl×yl⌋(mod264) ,而 p l = x l × y l ( m o d 2 64 ) p_l = x_l\times y_l\pmod{2^{64}} pl=xl×yl(mod264) 。
这时再来看下面的乘法逻辑,我们也使用表来看看
%rdx | %rax | %rcx | %rsi | |
---|---|---|---|---|
y h y_h yh | y l y_l yl | x h x_h xh | x l x_l xl | |
imulq %rax, %rcx | y h y_h yh | y l y_l yl | x h × 64 t y l x_h\times_{64}^ty_l xh×64tyl | x l x_l xl |
imulq %rsi, %rdx | x l × 64 t y h x_l\times_{64}^ty_h xl×64tyh | y l y_l yl | x h × 64 t y l x_h\times_{64}^ty_l xh×64tyl | x l x_l xl |
addq %rdx, %rcx | x l × 64 t y h x_l\times_{64}^ty_h xl×64tyh | y l y_l yl | ( x h × 64 t y l ) + 64 t ( x l × 64 t y h ) x_h\times_{64}^ty_l) +_{64}^t (x_l\times_{64}^ty_h) xh×64tyl)+64t(xl×64tyh) | x l x_l xl |
mulq %rsi | ⌊ x l × y l 2 64 ⌋ \lfloor\frac{x_l\times y_l}{2^{64}}\rfloor ⌊264xl×yl⌋ | y l × 64 t x l y_l\times_{64}^t x_l yl×64txl | ( x h × 64 t y l ) + 64 t ( x l × 64 t y h ) x_h\times_{64}^ty_l) +_{64}^t (x_l\times_{64}^ty_h) xh×64tyl)+64t(xl×64tyh) | x l x_l xl |
addq %rcx, %rdx | ( x h × 64 t y l ) + 64 t ( x l × 64 t y h ) + 64 t ⌊ x l × y l 2 64 ⌋ (x_h\times_{64}^ty_l) +_{64}^t (x_l\times_{64}^ty_h) +_{64}^t \lfloor\frac{x_l\times y_l}{2^{64}}\rfloor (xh×64tyl)+64t(xl×64tyh)+64t⌊264xl×yl⌋ | y l × 64 t x l y_l\times_{64}^t x_l yl×64txl | ( x h × 64 t y l ) + 64 t ( x l × 64 t y h ) (x_h\times_{64}^ty_l) +_{64}^t (x_l\times_{64}^ty_h) (xh×64tyl)+64t(xl×64tyh) | x l x_l xl |
这里比较反常的就是 mulq %rsi
,之前我一直没注意到
m
u
l
q
mulq
mulq 无符号拓展就导致原本的
%
r
d
x
\%rdx
%rdx 寄存器里的数据就只剩下
⌊
x
l
×
y
l
2
64
⌋
\lfloor\frac{x_l\times y_l}{2^{64}}\rfloor
⌊264xl×yl⌋ 。所以一直不理解后面还要执行 addq %rcx, %rdx
。
最后再把低位的 p l p_l pl 赋值到内存中 R [ % r d i ] R[\%rdi] R[%rdi] 中,把 p h p_h ph 赋值到 8 + R [ % r d i ] 8 + R[\%rdi] 8+R[%rdi] 中去。
所以这就是这道题的完整解释了。中间有很多废话,不过这都是我在思考中不理解的部分。
欢迎各位大犇来指出我的错误和交流!