x86-64位切换一个数的第n位

14 浏览
0 Comments

x86-64位切换一个数的第n位

我看到了类似于在第i个位置切换一个位如何设置、清除和切换单个位?的问题,但我想知道在x86-64汇编中是否有一种好的方法来切换第i个位置的位?我尝试用C语言编写它,并查看汇编代码,但我不太明白为什么有些东西存在。\nC代码:\n

unsigned long toggle(unsigned long num, unsigned long bit)
{
  num ^= 1 << bit;
  return num;
}
int main()
{
  printf("%ld\n", toggle(100, 60));
  return 0;
}

\n从GDB中获取的Toggle函数汇编代码:\n


push rbp
mov rbp, rsp
mov QWORD PTR [rbp-0x8],rdi
mov QWORD PTR [rbp-0x10],rsi
mov rax, QWORD PTR [rbp-0x10]
mov edx, 0x1
mov ecx, eax
shl edx, cl
mov eax, edx
cdqe
xor QWORD PTR [rbp-0x8],rax
mov rax, QWORD PTR [rbp-0x8]
pop rbp
ret

\n有人可以帮我解释一下汇编层面上发生了什么,这样我就能更好地理解,并自己编写x86-64的切换函数了吗?

0
0 Comments

x86-64位切换数字中的第n位的出现原因是一个用户在询问是否有一个好的方法在x86-64汇编语言中切换第i位的问题。解决方法是使用x86的BTC(Bit Test and Complement)指令来实现(同时将CF设置为位的旧值),该指令在所有现代CPU上运行效率高。

解决方法是使用以下代码:

toggle:
    mov  rax, rdi
    btc  rax, rsi
    ret

如果在C语言中正确地编写了toggle函数的话。

需要注意的是,不要在内存操作数上使用btc指令,因为位字符串指令具有疯狂的CISC语义,其中位索引不限于地址模式选择的双字内。但是对于寄存器操作数,移位计数与变量计数移位一样被掩码处理。

如果要重复使用相同的1ULL << bit和多个输入,则在AMD CPU上,btc比xor慢,值得在寄存器中生成掩码并使用xor。甚至在Intel CPU上,这也值得为了在内存中切换位(内存目标的xor要比内存目标的btc好)。

对于数组中的多个元素,可以使用SSE2的pxor。可以使用以下代码生成掩码:

pcmpeqd  xmm0, xmm0        ; -1 all bits set
psrlq    xmm0, 63          ;  1 just a single bit set
movd     xmm1, esi
psllq    xmm0, xmm1        ; 1<

然后在循环内部,对于在xmm1中的数据,可以使用以下代码来反转每个qword元素中的位:

pxor     xmm1, xmm0        ; flip bit in each qword element

由于编译时没有进行优化,并且使用了有符号的int常量,所以出现了一些不必要的指令。如果想要得到更好的代码,可以使用`-O3 -march=native`编译选项。

如果将toggle函数正确编写为:

uint64_t toggle64(uint64_t num, uint32_t bit) {
  num ^= 1ULL << bit;
  return num;
}

GCC和Clang仍然没有使用btc指令,但情况并不糟糕。有趣的是,MSVC确实发现了btc peephole,但浪费了一个MOV指令。使用uint64_t位避免了额外的MOV指令。

最后,需要注意的是在使用有符号的int常量`1 << bit`时,GCC会生成一个32位的移位操作,并且使用了cdqe来进行符号扩展。可以通过将代码编写为`num ^= 1ULL << bit;`来避免这个问题。

总之,通过使用x86的BTC指令,可以在x86-64汇编语言中高效地切换数字的第n位。同时,对于多个输入,可以使用SSE2的pxor指令。如果要获得更好的代码,可以使用优化选项进行编译。

0