TL:DR:加 1 翻转低位,保证,所以你可以使用lea/and。见下文。
您选择编写一个返回布尔整数的整个函数,而不是仅仅创建一个 FLAGS 条件(这是大多数代码所需要的:test $1, %dil 并且您已经完成;分支或 cmov 或 setnz 或 setz 或任何您实际想要做一个基于偶数的值)。
如果您要返回一个整数,那么您实际上不需要将条件放入 FLAGS 并退出,特别是如果您想要一个“宽”返回值。 x86 setcc 仅写入低字节是一种不方便的设计,大多数情况下需要额外的异或归零指令来创建更宽的 0 / 1 整数。 (我希望 AMD64 已经整理了设计并将 64 位模式的操作码的含义更改为 setcc r/m32,但他们没有。)
您选择了函数的语义以返回 1 for even;这与低位的值相反。 (即return (~x)&1;)您还选择使用 x86-64 System V 调用约定创建一个函数,这会增加调用约定的开销,该调用约定将 arg 放入与您传入的寄存器不同的寄存器中。
这个函数显然太琐碎了,不值得调用/返回开销;在现实生活中,您只需将其内联并优化到调用者中。因此将其优化为作为独立函数主要是一个愚蠢的练习,除了获得 0/ 的想法1 在与原始文件不同的寄存器中,而不破坏它。
如果我在https://codegolf.stackexchange.com/ 上写答案,我会按照this code-golf tip 并选择我的调用约定在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
# 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
2 条指令而不破坏输入
is_even:
lea 1(%rdi), %eax # flip the low bit
and $1, %eax # and isolate
ret
XOR 是不带进位的加法。 当进位为零时(除了 ADC 外,保证低位),给定位的结果与 XOR 和加法相同。检查 1 位“half adder”(无进位)的真值表/门等效项:“sum”输出实际上只是 XOR,进位输出只是 AND。
(XOR 与 1 翻转一点,与 NOT 相同。)
在这种情况下,我们不关心进位或任何高位(因为我们即将用& 1 核对这些位是相同的操作),所以我们可以使用 LEA 作为复制并添加翻转低位。
使用 XOR 而不是 ADD 或 SUB 对于 SIMD 很有用,其中 pxor 可以在比 Skylake 之前的 CPU 上的 paddb 或 psubb 更多的端口上运行。当你想将pcmpgtb 的无符号范围转换为有符号时,你想添加-128,但这与翻转每个字节的高位是一样的。
您可以使用它来翻转更高的位,例如lea 8(%rdi), %eax 将翻转 1<<3 位位置(并可能进入所有更高位)。我们知道该位的进位将为零,因为x + 0 不进位,并且8 的低 3 位都是 0。
(这个想法是后来 https://catonmat.net/low-level-bit-hacks 中一些更有趣的 bit-hacks 的核心)