在C中使用内联汇编进行位奇偶校验?

时间:2017-05-10 04:15:19

标签: c assembly x86-64 inline-assembly

我正在尝试计算大量uint64的位奇偶校验。比特奇偶校验是指接受uint64的函数,如果设置的比特数是偶数则输出0,否则为1。

目前我正在使用以下功能(@Troyseph,发现here):

uint parity64(uint64 n){
  n ^= n >> 1;
  n ^= n >> 2;
  n = (n & 0x1111111111111111) * 0x1111111111111111;
  return (n >> 60) & 1;
}

相同的SO页面具有以下汇编例程(由@papadp提供):

.code

; bool CheckParity(size_t Result)
    CheckParity PROC
    mov     rax, 0
    add     rcx, 0
    jnp     jmp_over
    mov     rax, 1
jmp_over:
    ret
CheckParity ENDP

END

利用机器的parity flag。但我无法使用我的C程序(我知道没有组装)。

问题即可。如何在C源文件中包含上面(或类似)代码作为内联汇编,以便parity64()函数运行该代码?

(我在Intel Xeon Haswell上使用GCC和64位Ubuntu 14)

如果有任何帮助,parity64()函数将在以下例程中调用:

uint bindot(uint64* a, uint64* b, uint64 entries){
    uint parity = 0;

    for(uint i=0; i<entries; ++i)
      parity ^= parity64(a[i] & b[i]);  // Running sum!

    return parity;
}

(这应该是Z / 2Z场上两个向量的“点积”,即GF(2)。)

3 个答案:

答案 0 :(得分:8)

因为在处理位操作时C很糟糕,我建议使用gcc内置函数,在本例中为__builtin_parityl()。参见:

https://gcc.gnu.org/onlinedocs/gcc/Other-Builtins.html

答案 1 :(得分:2)

  

如何在C源文件中包含上面(或类似)代码作为内联汇编,以便parity64()函数运行该代码?

这是XY problem ...您认为您需要内联该程序集才能从中获益,因此您询问如何内联 ...但您不需要内嵌

不应该将汇编包含在你的C源代码中,因为在这种情况下你不需要,而且更好的选择(在可移植性和可维护性)是将两个源代码分开,单独编译它们并使用链接器链接它们

parity64.c中,您应该拥有可移植版本(带有名为bool CheckParity(size_t result)的包装器),您可以在非x86 / 64情况下默认使用该版本。

您可以将其编译为目标文件,如下所示:gcc -c parity64.c -o parity64.o

...然后将程序集生成的目标代码与C代码相关联:gcc bindot.c parity64.o -o bindot

parity64_x86.s中,您的问题可能包含以下汇编代码:

.code

; bool CheckParity(size_t Result)
    CheckParity PROC
    mov     rax, 0
    add     rcx, 0
    jnp     jmp_over
    mov     rax, 1
jmp_over:
    ret
CheckParity ENDP

END

您可以使用此命令使用parity64.o将此代码编译为另一个gcc目标文件对象代码:gcc -c parity64_x86.s -o parity64.o

...然后链接生成的对象代码:gcc bindot.c parity64.o -o bindot

同样,如果您想使用__builtin_parityl(正如hdantes answer所建议的那样),您可以(而且应该)再次将该代码分开(在同一个地方保留其他 gcc来自便携式代码的/ x86优化)。parity64_x86.c中您可能有:

bool CheckParity(size_t result) {
    return __builtin_parityl(result);
}

要编译它,您的命令将是:gcc -c parity64_x86.c -o parity64.o

...然后链接生成的对象代码:gcc bindot.c parity64.o -o bindot

另一方面,如果你想检查一下gcc会产生的汇编:gcc -S parity64_x86.c

你的程序集中的注释表明C中的等效函数原型是bool CheckParity(size_t Result),所以考虑到这一点,这里的bindot.c可能是这样的:

extern bool CheckParity(size_t Result);

uint64_t bindot(uint64_t *a, uint64_t *b, size_t entries){
    uint64_t parity = 0;

    for(size_t i = 0; i < entries; ++i)
        parity ^= a[i] & b[i];  // Running sum!

    return CheckParity(parity);
}

您可以将其构建并将其链接到上述parity64.o版本的任何,如下所示:gcc bindot.c parity64.o -o bindot ...

我强烈建议您在有空的时候阅读the manual for your compiler ...

答案 2 :(得分:1)

您必须使用扩展内联汇编(这是一个gcc扩展名)才能获得类似的效果。

您的parity64功能可以更改如下 -

uint parity64(uint64 n){
    uint result = 0;
    __asm__("addq $0, %0" : : "r"(n)  :);
    __asm__("jnp 1f");
    __asm__("movl $1, %0" : "=r"(result) : : );
    __asm__("1:");
    return result;
}

但正如@MichaelPetch评论的那样,奇偶校验标志仅在低8位上计算。因此,如果您的n小于255,这将适用于您。对于更大的数字,您将必须使用您在问题中提到的代码。

要使其工作在64位,您可以通过执行

将32位整数的奇偶校验折叠为单字节
n = (n >> 32) ^ n;
n = (n >> 16) ^ n;
n = (n >> 8) ^ n;

此代码必须只是在汇编之前的函数的开头。

您必须检查它对性能的影响。

我能得到的最优化的是

uint parity64(uint64 n){
    unsigned char result = 0;
    n = (n >> 32) ^ n;
    n = (n >> 16) ^ n;
    n = (n >> 8) ^ n;
    __asm__("test %1, %1 \n\t"
            "setp %0"
            : "+r"(result)
            : "r"(n)
            :
    );
    return result;
}