如何使这种动态位范围代码GCC兼容64位编译器?

时间:2019-05-04 16:00:13

标签: c gcc bit-manipulation 64-bit bitwise-operators

我正在尝试针对linux,GCC和64位使用进行更新,并保留在github Ken Silverman's Paint N Draw 3D C软件中。我得到了他的允许,但他忙于帮助。我不想做得很糟糕,我也不是一位精打细算的专家,所以我想在上传之前修复主要部分。

在他的代码pnd3d.c中,他使用了一个名为bitmal_t *的结构,该结构包含一个malloc(我认为他的元素mal表示malloc的大小)和一个将体素距离表示为无符号int的大小(在2009年,这是一个32位int级联集合中的32位)。因此,基本上,距离是沿扩展位链的(1)上多少位的函数。对于碰撞,他会上下寻找零和一。

这是他的bitmal_t:

    //buf: cast to: octv_t* or surf_t*
    //bit: 1 bit per sizeof(buf[0]); 0=free, 1=occupied
typedef struct bit { void *buf; unsigned int mal, *bit, ind, num, siz; } bitmal_t;

这是他的测距代码,该代码在位范围内上下寻找1或0。我发布了他的原件,而不是我那笨拙的无效版本。

这里是重现它所需的所有代码段。

static __forceinline int dntil0 (unsigned int *lptr, int z, int zsiz)
{
    //   //This line does the same thing (but slow & brute force)
    //while ((z < zsiz) && (lptr[z>>5]&(1<<KMOD32(z)))) z++; return(z);
    int i;
        //WARNING: zsiz must be multiple of 32!
    i = (lptr[z>>5]|((1<<KMOD32(z))-1)); z &= ~31;
    while (i == 0xffffffff)
    {
        z += 32; if (z >= zsiz) return(zsiz);
        i = lptr[z>>5];
    }
    return(bsf(~i)+z);
}

static __forceinline int uptil0 (unsigned int *lptr, int z)
{
    //   //This line does the same thing (but slow & brute force)
    //while ((z > 0) && (lptr[(z-1)>>5]&(1<<KMOD32(z-1)))) z--; return(z);
    int i;
    if (!z) return(0); //Prevent possible crash
    i = (lptr[(z-1)>>5]|(-1<<KMOD32(z))); z &= ~31;
    while (i == 0xffffffff)
    {
        z -= 32; if (z < 0) return(0);
        i = lptr[z>>5];
    }
    return(bsr(~i)+z+1);
}

static __forceinline int dntil1 (unsigned int *lptr, int z, int zsiz)
{
    //   //This line does the same thing (but slow & brute force)
    //while ((z < zsiz) && (!(lptr[z>>5]&(1<<KMOD32(z))))) z++; return(z);
    int i;
        //WARNING: zsiz must be multiple of 32!
    i = (lptr[z>>5]&(-1<<KMOD32(z))); z &= ~31;
    while (!i)
    {
        z += 32; if (z >= zsiz) return(zsiz);
        i = lptr[z>>5];
    }
    return(bsf(i)+z);
}

static __forceinline int uptil1 (unsigned int *lptr, int z)
{
    //   //This line does the same thing (but slow & brute force)
    //while ((z > 0) && (!(lptr[(z-1)>>5]&(1<<KMOD32(z-1))))) z--; return(z);
    int i;
    if (!z) return(0); //Prevent possible crash
    i = (lptr[(z-1)>>5]&((1<<KMOD32(z))-1)); z &= ~31;
    while (!i)
    {
        z -= 32; if (z < 0) return(0);
        i = lptr[z>>5];
    }
    return(bsr(i)+z+1);
}

这是他设置为1和0的范围:

//Set all bits in vbit from (x,y,z0) to (x,y,z1-1) to 0's
#ifndef _WIN64

static __forceinline void setzrange0 (void *vptr, int z0, int z1)
{
    int z, ze, *iptr = (int *)vptr;
    if (!((z0^z1)&~31)) { iptr[z0>>5] &= ((~(-1<<z0))|(-1<<z1)); return; }
    z = (z0>>5); ze = (z1>>5);
    iptr[z] &=~(-1<<z0); for(z++;z<ze;z++) iptr[z] = 0;
    iptr[z] &= (-1<<z1);
}

    //Set all bits in vbit from (x,y,z0) to (x,y,z1-1) to 1's
static __forceinline void setzrange1 (void *vptr, int z0, int z1)
{
    int z, ze, *iptr = (int *)vptr;
    if (!((z0^z1)&~31)) { iptr[z0>>5] |= ((~(-1<<z1))&(-1<<z0)); return; }
    z = (z0>>5); ze = (z1>>5);
    iptr[z] |= (-1<<z0); for(z++;z<ze;z++) iptr[z] = -1;
    iptr[z] |=~(-1<<z1);
}

#else

static __forceinline void setzrange0 (void *vptr, __int64 z0, __int64 z1)
{
    unsigned __int64 z, ze, *iptr = (unsigned __int64 *)vptr;
    if (!((z0^z1)&~63)) { iptr[z0>>6] &= ((~(LL(-1)<<z0))|(LL(-1)<<z1)); return; }
    z = (z0>>6); ze = (z1>>6);
    iptr[z] &=~(LL(-1)<<z0); for(z++;z<ze;z++) iptr[z] = LL(0);
    iptr[z] &= (LL(-1)<<z1);
}

    //Set all bits in vbit from (x,y,z0) to (x,y,z1-1) to 1's
static __forceinline void setzrange1 (void *vptr, __int64 z0, __int64 z1)
{
    unsigned __int64 z, ze, *iptr = (unsigned __int64 *)vptr;
    if (!((z0^z1)&~63)) { iptr[z0>>6] |= ((~(LL(-1)<<z1))&(LL(-1)<<z0)); return; }
    z = (z0>>6); ze = (z1>>6);
    iptr[z] |= (LL(-1)<<z0); for(z++;z<ze;z++) iptr[z] = LL(-1);
    iptr[z] |=~(LL(-1)<<z1);
}

#endif

1 个答案:

答案 0 :(得分:2)

写一些通过原始测试的单元测试!

首先,SSE2是x86-64的基线,因此,您一定要使用它,而不仅仅是64位整数。

GCC(与MSVC不同)不假定严格混叠,因此设置了位范围功能(根据WIN64是否将传入的指针强制转换为有符号的int*(!!)或uint64_t*指针)可能需要使用-fno-strict-aliasing进行编译,以使指针广播定义良好。

您可以将设置/清除位范围函数的循环部分替换为memset(gcc可以内联),或者如果您希望大小通常较小(例如小于200字节或因此,不值得调用libc memset的开销)


我认为第一块中的dntil0函数只是前0或前1位(向前或向后)的位搜索循环。

使用SSE2内部函数从头开始重写它们_mm_cmpeq_epi8 / _mm_movemask_epi8找到不是全0或全1位的第一个字节,然后使用{ {1}}或bsf

请参见glibc source code for SSE2 memchr或任何更简单的SSE2优化实现,以了解如何进行字节搜索。或使用glibc bsr作为比较等于的示例,但这很简单:寻找非零memmem(表示匹配),而不是寻找一个非零的_mm_movemask_epi8()。结果为!= 0xffff(全为),表明存在不匹配。在该位掩码上使用bsfbsr查找SIMD向量中的字节索引。

因此,您总共在每个函数中使用BSR或BSF两次:一次在SIMD向量中查找字节索引,再一次在目标字节中查找位索引。

对于位扫描功能,请使用GCC __builtin_clz__builtin_ctz查找第一个1位。 Bit twiddling: which bit is set?

要搜索第一个零而不是第一个零,请按位求反,例如__builtin_ctz( ~p[idx] ),其中punsigned char*到您的搜索缓冲区中(您正在使用{{1} } on),而_mm_loadu_si128()是该16字节窗口内的偏移量。 (您对idx结果得出的__builtin_ctz()进行了计算,该结果超出了矢量循环。)


原件的工作方式:

movemask以32位循环(z -= 32的大小,因为它是在假定它将为x86 Windows或x86-64 Windows编译的情况下编写的。)

int正在将位索引转换为lptr[z>>5];索引。因此,它只是一次遍历缓冲区1 int

当它找到一个4字节的元素int时,它发现了一个!= 0xFFFFFFFF包含不为1的位;即它包含了我们正在寻找的东西。因此,它使用intbsf进行位扫描并找到该bsr 中该位的位置。
它将其添加到int(此z开头的位位置)。

这与我上面描述的算法完全相同,但是一次实现一个整数,而不是一次实现16个字节。

它实际上应该使用intuint32_t进行位操作,而不是用符号unsigned int签名,但是显然可以在MSVC上正常工作。

int这是大小检查,如果未找到任何位,则退出循环。