检查数字是否为偶数

Dav*_*542 1 x86 assembly bit-manipulation x86-64

我正在研究低级位黑客,并想为每个黑客编写一个汇编程序。这是我检查数字是否偶数的方法:

is_even:
    # check if an integer is even. 
    # This is the same as seeing if its a multiple of two, i.e., & 1<<n - 1
    # rdi stores the number
    xor %eax, %eax
    test $0b1, %rdi
    setz %al
    ret

_start:
    mov $5, %rdi
    call is_even
Run Code Online (Sandbox Code Playgroud)

有什么方法可以改进上述内容或使其更具可读性?是否可以is_even使用 2 条指令而不是 3 条指令进行检查,因为第一条xor和第二条指令setz似乎可能会转换为一条(如果可能的话)。

Pet*_*des 5

TL:DR:加 1 会翻转低位,保证如此,因此您可以使用lea/ and。见下文。


您选择编写一个返回布尔整数的整个函数,而不是仅仅创建一个 FLAGS 条件(这是大多数代码所需要的:test $1, %dil您就完成了;branch 或 cmov 或 setnz 或 setz 或您实际想要执行的任何操作)值是偶数)。

如果您要返回一个整数,那么您实际上不需要将条件放入 FLAGS 中然后退出,特别是如果您想要一个“宽”返回值。x86setcc仅写入低字节是一种不方便的设计,大多数时候您想要创建更宽的 0 / 1 整数时需要额外的异或归零指令。(我希望 AMD64 能够整理设计并将 64 位模式的操作码的含义更改为 ,setcc r/m32但他们没有。)

1您选择了要返回偶数的函数的语义;这与低位的值相反。(即return (~x)&1;)您还选择使用 x86-64 System V 调用约定创建一个函数,从调用约定中强加开销,该调用约定在与您传入的寄存器不同的寄存器中获取 arg。

这个函数显然太微不足道了,不值得调用/返回开销;在现实生活中,您只需将其内联并优化到调用者中即可。 因此,将其作为独立函数进行优化基本上是一个愚蠢的做法,除了在与原始寄存器不同的寄存器中获取 0/1 而不破坏它的想法之外。

如果我在https://codegolf.stackexchange.com/上写答案,我会遵循此代码高尔夫技巧并选择我的调用约定以在 EAX 中传递 arg 并在 AL 中返回布尔值(就像gcc -m32 -mregparm=3那样)。或者在ZF中返回一个FLAGS条件。或者如果允许,选择我的返回语义,使 AL=0 表示偶数,AL=1 表示奇数。然后

# gcc 32-bit regparm calling convention
is_even:          # input in RAX, bool return value in AL
    not   %eax             # 2 bytes
    and   $1, %al          # 2 bytes
    ret
Run Code Online (Sandbox Code Playgroud)
# custom calling convention:
is_even:   # input in RDI
           # returns in ZF.  ZF=1 means even
    test  $1, %dil         # 4 bytes.  Would be 2 for AL, 3 for DL or CL (or BL)
    ret
Run Code Online (Sandbox Code Playgroud)

2条指令,不破坏输入

is_even:
    lea   1(%rdi), %eax          # flip the low bit
    and   $1, %eax               # and isolate
    ret
Run Code Online (Sandbox Code Playgroud)

XOR 是不带进位的加法。 当进位为零时(保证低位,ADC 除外),给定位的结果与异或和加法相同。检查 1 位“半加法器”(无进位输入)的真值表/门等效:“和”输出实际上只是 XOR,进位输出只是 AND。

(异或 1 次翻转,与非相同。)

在这种情况下,我们不关心进位或任何高位(因为我们要使用& 1相同的操作来核对这些位),因此我们可以使用 LEA 作为复制和添加翻转低位。

使用 XOR 代替 ADD 或 SUB 对于 SIMD 非常有用,SIMD可以在比Skylake 之前的 CPUpxor更多的端口上运行。当您想要将无符号范围移动到有符号或其他内容时,您需要添加 ,但这与翻转每个字节的高位是一样的。paddbpsubbpcmpgtb-128


您可以使用它来翻转较高位,例如lea 8(%rdi), %eax翻转1<<3位位置(并可能进位到所有较高位)。我们知道该位的进位将为零,因为它x + 0不进位,并且 的低 3 位8都为 0。

(这个想法是https://catonmat.net/low-level-bit-hacks中后来一些更有趣的位黑客的核心)