如何使GCC在没有内置设备的情况下为big-endian存储生成bswap指令?

如何使GCC在没有内置设备的情况下为big-endian存储生成bswap指令?,c,gcc,x86,compiler-optimization,endianness,C,Gcc,X86,Compiler Optimization,Endianness,更新:这已在GCC 8.1中修复 我正在研究一个函数,它以big-endian格式将64位值存储到内存中。我希望我能编写可移植C99代码,在小型和大型终端平台上都能运行,并让现代x86编译器自动生成bswap指令,而无需任何内置或内部函数。因此,我从以下函数开始: #include <stdint.h> void encode_bigend_u64(uint64_t value, void *vdest) { uint8_t *bytes = (uint8_t *)vdes

更新:这已在GCC 8.1中修复

我正在研究一个函数,它以big-endian格式将64位值存储到内存中。我希望我能编写可移植C99代码,在小型和大型终端平台上都能运行,并让现代x86编译器自动生成
bswap
指令,而无需任何内置或内部函数。因此,我从以下函数开始:

#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;
}
但是海湾合作委员会。我尝试了几种不同的方法,但它们只会让事情变得更糟。我知道GCC可以使用按位and、shift和按位or检测字节交换,但为什么在写入字节时它不起作用呢


编辑:我找到了相应的。

这似乎起到了作用:

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-march=native的叮当声
带有
-O3
带有
-O3-march=native的gcc

使用clang 3.8.0和gcc 5.3.0进行测试(因此我不知道下面到底是什么处理器(对于
-march=native
),但我强烈怀疑最近的x86_64处理器)


如果您想要一个也适用于big-endian体系结构的函数,您可以使用中的答案来检测系统的endianness,并添加一个
If
。union和pointer casts版本均有效,并通过
gcc
clang
进行优化,从而产生完全相同的程序集(无分支):


(3-542第2A卷):

MOVBE在交换字节后移动数据

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

MOVBE指令用于在读取时交换字节 从存储器或写入存储器;从而为 将小端值转换为大端值格式,反之亦然


在asm输出位于


,自GNU C4.3起这显然是让gcc/clang生成代码的最可靠的方法,而这不会让代码变得糟糕

glibc提供了
htobe64
htole64
,以及类似的主机与BE和LE之间的交换或不交换功能,具体取决于机器的端部。有关详细信息,请参阅文档。手册页上说,它们是在2.9版(2008-11年发布)中添加到glibc的

如果您真的需要一个在不支持GNU C内置的编译器上编译良好的回退,那么@bolov答案中的代码可以用来实现一个编译良好的bswap。预处理器宏可用于选择是否交换(),以实现主机到be和主机到LE功能。当内置bswap或x86 asm不可用时,使用了bolov发现的掩码和移位习惯用法。gcc认识到这一点比仅仅改变要好


中的代码使用gcc编译到bswap,但不使用clang。IDK,如果他们的模式识别器都能识别出什么

// 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
//请注意,这是一个加载,而不是像问题中的代码那样的存储。
uint64_t be64_到_主机(无符号字符*数据){
返回

((uint64_t)data[7]我喜欢Peter的解决方案,但这里有一些你可以在Haswell上使用的东西。Haswell有
movbe
指令,它在那里是3个uops(不比正常加载或存储便宜),但在Atom/Silvermont()上更快:

将它与类似于
uint64\u t tmp=load\u bigend\u u64(数组[i])的内容一起使用;

您可以将其反转以生成
store\u bigend
函数,或者使用
bswap
修改寄存器中的值并让编译器加载/存储它


我将函数更改为返回
,因为我不清楚
vdest
的对齐方式

通常,功能由预处理器宏保护。我希望
\uuuumovbe\uuuu
用于
MOVBE
功能标志,但它不存在():

$gcc-march=native-dM-E-
trang with
-march=native
它变得更好:只有一个指令:
movbeq%rdi,(%rsi)
更新了我的答案,使用GNU C
内置bswap64
通过glibc函数
htobe64
在gcc和clang上编译成理想的代码。如果
movbe
可用,则序列可以减少到
movbeq%rdi,(%rsi)
。这可能是Clang和GCC的另一个优化缺陷。出于不相关的原因,我在RISC-V上研究了这个问题,Clang在RISC-V上检测各种序列(或不检测)取决于……看看是否包含了一些建议,在这个页面上也有几个建议。GCCC > = 8进行优化:<代码> STATICEXCAST 和 STD::/Cuff>看起来非常像C++。问题被标记为C。though@ThiefMaster是的,没有看到
C
标签。更正(悲伤的脸悲伤的脸)@当然。对于bigendian架构,函数应该只是一个副本。对于godbolt链接,使用
-march=haswell
。通常是haswell,但有几个星期是IvB。此外,发布“完整链接”对于代码,而不仅仅是头版。godbolt for stackoverflow的一半好处是为代码和编译选项生成永久链接,因此人们可以尝试使用其他编译器版本或编译选项。@bolov我想要的是一个既适用于小端平台又适用于大端平台的单一函数。很抱歉没有明确说明enough.您漏掉了逗号,所以这甚至无法编译。此外,您似乎在使用英特尔语法操作数顺序,而没有提及这一事实。我不建议使用内联asm中的
movbe
;您可能最终会
encode_bigend_u64(unsigned long, void*):
        bswapq  %rdi
        movq    %rdi, (%rsi)
        retq
encode_bigend_u64(unsigned long, void*):
        movbeq  %rdi, (%rsi)
        retq
encode_bigend_u64(unsigned long, void*):
        bswap   %rdi
        movq    %rdi, (%rsi)
        ret
encode_bigend_u64(unsigned long, void*):
        movbe   %rdi, (%rsi)
        ret
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));
}
#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
#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__
// 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
// 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;
}
$ 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
...