请回顾前文第12章第2节的那个函数。请设想一下,能否用x86的汇编指令打造一个无分支的版本?
int my_abs (int i)
{
if (i<0)
return -i;
else
return i;
};
答案当然是肯定的。
首先我们来看看,如果我们采用GCC 4.9来编译的话,会出现什么情况。
指令清单45.1 x64下的GCC 4.9优化
my_abs:
mov edx, edi
mov eax, edi
sar edx, 31
; EDX is 0xFFFFFFFF here if sign of input value is minus
; EDX is 0 if sign of input value is plus (including 0)
; the following two instructions have effect only if EDX is 0xFFFFFFFF
; or idle if EDX is 0
xor eax, edx
sub eax, edx
ret
以下是其实现过程的描述:
SAR算术右[1]移:算术右移31位,我们知道算术右移是带符号的操作。有符号数的最高有效位MSB就是其符号位。也就是说,如果MSB的值为1(原操作数是负数),那么右移31位后,不管原来的数是什么,得到的结果都会是0xffffffff。当然如果MSB的值为0(原操作数是正数或零),那么最后的结果就会是零。执行完SAR后,返回值被存放在EDX寄存器中。后面就是XOR指令。当原操作数是负数时,这条指令就变成了“XOR{寄存器},0xffffffff”,相当于逐位求非的逻辑非运算;其后的SUB指令会完成补码运算的“减去负一(加一)”运算、求得绝对值,因为此时EDX寄存器的值是0xffffffff——十进制的“-1”。有关两个数的逻辑运算和递增运算详解,请参阅本书第30章。
因此,当源操作数是负数的时候,最后两条运算指令会对源操作数进行处理。在符号位的值为零,即零和正数的情况下,最后两条运算指令不会调整源操作数的值。
有关本例的详细算法,请查阅参考书目[War02,pp.2-4]的相关介绍。GCC或许借鉴了现有的成熟算法,或许这正是它自己推演的计算结果。
GCC 4.9 for ARM64的编译方式和GCC for x64的编译方式几乎相同,只是它使用的是自己CPU平台的64位寄存器而已。由于ARM64可以通过参数调节符ASR在别的指令里完成位移运算,因此GCC 4.9 for ARM64比GCC for x64分配的指令少。
指令清单45.2 ARM64下的GCC 4.9优化
my_abs:
; sign-extend input 32-bit value to X0 64-bit register:
sxtw x0, w0
eor x1, x0, x0, asr 63
; X1=X0^(X0>>63) (shift is arithmetical)
sub x0, x1, x0, asr 63
; X0=X1-(X0>>63)=X0^(X0>>63)-(X0>>63) (all shifts are arithmetical)
ret
[1] 原文为左移。