首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
社区首页 >问答首页 >如何让GCC在没有内置的情况下为大端存储生成bswap指令?

如何让GCC在没有内置的情况下为大端存储生成bswap指令?
EN

Stack Overflow用户
提问于 2016-04-08 10:41:57
回答 3查看 12.9K关注 0票数 22

更新:此问题已在GCC 8.1中修复。

我正在开发一个函数,该函数将64位的值以高字节顺序格式存储到内存中。我希望我可以编写可移植的C99代码,它可以在小端和大端平台上工作,并让现代x86编译器自动生成bswap指令,而不需要任何内置或内部。所以我从下面的函数开始:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
#include <stdint.h>

void
encode_bigend_u64(uint64_t value, void *vdest) {
    uint8_t *bytes = (uint8_t *)vdest;
    bytes[0] = value >> 56;
    bytes[1] = value >> 48;
    bytes[2] = value >> 40;
    bytes[3] = value >> 32;
    bytes[4] = value >> 24;
    bytes[5] = value >> 16;
    bytes[6] = value >> 8;
    bytes[7] = value;
}

这对于clang来说工作得很好,它将这个函数编译为:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
bswapq  %rdi
movq    %rdi, (%rsi)
retq

而是GCC fails to detect the byte swap。我尝试了几种不同的方法,但它们只会让事情变得更糟。我知道GCC可以使用逐位and、shift和byte or来检测字节交换,但是为什么它在写入字节时不起作用?

编辑:我找到了对应的GCC bug

EN

回答 3

Stack Overflow用户

回答已采纳

发布于 2016-04-11 14:58:18

这似乎起到了关键作用:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
void encode_bigend_u64(uint64_t value, void* dest)
{
  value =
      ((value & 0xFF00000000000000u) >> 56u) |
      ((value & 0x00FF000000000000u) >> 40u) |
      ((value & 0x0000FF0000000000u) >> 24u) |
      ((value & 0x000000FF00000000u) >>  8u) |
      ((value & 0x00000000FF000000u) <<  8u) |      
      ((value & 0x0000000000FF0000u) << 24u) |
      ((value & 0x000000000000FF00u) << 40u) |
      ((value & 0x00000000000000FFu) << 56u);
  memcpy(dest, &value, sizeof(uint64_t));
}

-O3重合

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
encode_bigend_u64(unsigned long, void*):
        bswapq  %rdi
        movq    %rdi, (%rsi)
        retq

-O3 -march=native重合

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
encode_bigend_u64(unsigned long, void*):
        movbeq  %rdi, (%rsi)
        retq

使用-O3的gcc

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
encode_bigend_u64(unsigned long, void*):
        bswap   %rdi
        movq    %rdi, (%rsi)
        ret

使用-O3 -march=native的gcc

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
encode_bigend_u64(unsigned long, void*):
        movbe   %rdi, (%rsi)
        ret

http://gcc.godbolt.org/上用CLAN3.8.0和gcc 5.3.0进行了测试(所以我不知道( -march=native)下面到底是什么处理器,但我强烈怀疑是最近的x86_64处理器)

如果您还想要一个适用于高字节顺序体系结构的函数,您可以使用here的答案来检测系统的字节顺序并添加一个if。联合和指针类型转换版本都可以工作,并由gccclang进行优化,从而产生完全相同的程序集(无分支)。Full code on godebolt

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
int is_big_endian(void)
{
    union {
        uint32_t i;
        char c[4];
    } bint = {0x01020304};

    return bint.c[0] == 1;
}

void encode_bigend_u64_union(uint64_t value, void* dest)
{
  if (!is_big_endian())
    //...
  memcpy(dest, &value, sizeof(uint64_t));
}

Intel® 64 and IA-32 Architectures Instruction Set Reference (3-542 Vol. 2A):

-交换字节后移动数据

对从第二个操作数(源操作数)复制的数据执行字节交换操作,并将结果存储在第一个操作数(目标操作数)中。..。

提供了MOVBE指令,用于在从内存读取或写入内存时交换字节;从而支持将小端值转换为大端格式,反之亦然。

票数 19
EN

Stack Overflow用户

发布于 2016-04-12 21:39:29

此答案中的所有函数都具有Godbolt Compiler Explorer上的asm输出

GNU C has a uint64_t __builtin_bswap64 (uint64_t x),从GNUC4.3开始。这显然是让gcc / clang生成不会让这个糟糕的代码的最可靠的方法。

glibc提供htobe64htole64以及类似的主机到BE和LE的函数,根据机器的字节顺序,这些函数可以交换或不交换。请参阅的文档。手册页上说,它们是在版本2.9 (发布于2008-11)中添加到glibc中的。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
#define _BSD_SOURCE             /* See feature_test_macros(7) */

#include <stdint.h>

#include <endian.h>
// ideal code with clang from 3.0 onwards, probably earlier
// ideal code with gcc from 4.4.7 onwards, probably earlier
uint64_t load_be64_endian_h(const uint64_t *be_src) { return be64toh(*be_src); }
    movq    (%rdi), %rax
    bswap   %rax

void store_be64_endian_h(uint64_t *be_dst, uint64_t data) { *be_dst = htobe64(data); }
    bswap   %rsi
    movq    %rsi, (%rdi)

// check that the compiler understands the data movement and optimizes away a double-conversion (which inline-asm `bswap` wouldn't)
// it does optimize away with gcc 4.9.3 and later, but not with gcc 4.9.0 (2x bswap)
// optimizes away with clang 3.7.0 and later, but not clang 3.6 or earlier (2x bswap)
uint64_t double_convert(uint64_t data) {
  uint64_t tmp;
  store_be64_endian_h(&tmp, data);
  return load_be64_endian_h(&tmp);
}
    movq    %rdi, %rax

即使在-O1 中,你也可以安全地从这些函数movbe中获得好的代码,当-march被设置为支持insn的CPU时,它们会使用

如果你的目标是GNU,但不是glibc,你可以借用glibc的定义(记住它是LGPLed代码):

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
#ifdef __GNUC__
# if __GNUC_PREREQ (4, 3)

static __inline unsigned int
__bswap_32 (unsigned int __bsx) { return __builtin_bswap32 (__bsx);  }

# elif __GNUC__ >= 2
    // ... some fallback stuff you only need if you're using an ancient gcc version, using inline asm for non-compile-time-constant args
# endif  // gcc version
#endif // __GNUC__

如果您确实需要一个可以在不支持GNU C内置的编译器上很好地编译的后备,那么@bolov答案中的代码可以用来实现一个编译得很好的bswap。预处理器宏可用于选择是否交换(like glibc does),以实现主机到be和主机到LE功能。当__builtin_bswap或x86 asm不可用时,bswap used by glibc使用了bolov发现的掩码和移位习惯用法。gcc更好地认识到这一点,而不仅仅是移动。

来自this Endian-agnostic coding blog post的代码编译为使用gcc的bswap,但使用而不是使用clang。如果有任何东西是他们的模式识别器都能识别的。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
// Note that this is a load, not a store like the code in the question.
uint64_t be64_to_host(unsigned char* data) {
    return
      ((uint64_t)data[7]<<0)  | ((uint64_t)data[6]<<8 ) |
      ((uint64_t)data[5]<<16) | ((uint64_t)data[4]<<24) |
      ((uint64_t)data[3]<<32) | ((uint64_t)data[2]<<40) |
      ((uint64_t)data[1]<<48) | ((uint64_t)data[0]<<56);
}

    ## gcc 5.3 -O3 -march=haswell
    movbe   (%rdi), %rax
    ret

    ## clang 3.8 -O3 -march=haswell
    movzbl  7(%rdi), %eax
    movzbl  6(%rdi), %ecx
    shlq    $8, %rcx
    orq     %rax, %rcx
    ... completely naive implementation

来自this answerhtonll编译为两个结合了shift/or的32位bswap。这一点很糟糕,但无论是对于gcc还是对于clang来说,都不是很糟糕。

我没有任何幸运的union { uint64_t a; uint8_t b[8]; }版本的OP的代码。clang仍然将其编译成64位的bswap,但我认为用gcc编译成的代码会更糟糕。(请参阅godbolt链接)。

票数 6
EN

Stack Overflow用户

发布于 2017-01-24 05:17:29

我喜欢Peter的解决方案,但这里还有一些你可以在Haswell上使用的东西。Haswell有movbe指令,它是3uops(不比bswap r64 +正常的加载或存储便宜),但在Atom / Silvermont (https://agner.org/optimize/)上更快:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
// AT&T syntax, compile without -masm=intel
inline
uint64_t load_bigend_u64(uint64_t value)
{
    __asm__ ("movbe %[src], %[dst]"   // x86-64 only
             :  [dst] "=r" (value)
             :  [src] "m" (value)
            );
    return value;
}

将它与uint64_t tmp = load_bigend_u64(array[i]);之类的东西一起使用

您可以颠倒这一点来创建store_bigend函数,或者使用bswap修改寄存器中的值,并让编译器加载/存储它。

我更改了函数以返回value,因为我不清楚vdest的对齐方式。

通常,功能由预处理器宏来保护。我希望__MOVBE__用于movbe特性标志,但它并不存在(this machine has the feature):

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
$ gcc -march=native -dM -E - < /dev/null | sort
...
#define __LWP__ 1
#define __LZCNT__ 1
#define __MMX__ 1
#define __MWAITX__ 1
#define __NO_INLINE__ 1
#define __ORDER_BIG_ENDIAN__ 4321
...
票数 2
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/36497605

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档
查看详情【社区公告】 技术创作特训营有奖征文