如果有或没有x32 ABI,如何为x86_64定义[u] int_fastN_t类型?

时间:2016-04-30 22:51:23

标签: c stdint linux-x32-abi

x32 ABI指定了为x86_64架构生成的代码的32位指针。它结合了x86_64架构(包括64位CPU寄存器)的优点和减少的32位指针开销。

<stdint.h>标头定义了typedef int_fast8_tint_fast16_tint_fast32_tint_fast64_t(以及相应的无符号类型uint_fast8_t等),每个都是:

  

一种整数类型,通常最快   至少具有指定宽度的整数类型

脚注:

  

不保证指定类型对所有目的都是最快的;   如果实施没有明确的理由选择一种类型   另外,它会简单地选择一些满足的整数类型   签名和宽度要求。

(引自N1570 C11 draft。)

问题是,无论是否使用x32 ABI,如何为x86_64架构定义[u]int_fast16_t[u]int_fast32_t类型?是否有指定这些类型的x32文档?它们是否应与32位x86定义(均为32位)兼容,或者,由于x32可以访问64位CPU寄存器,它们是否应该具有相同的大小,有或没有x32 ABI? (请注意,无论x32 ABI是否正在使用,x86_64都有64位寄存器。)

这是一个测试程序(取决于特定于gcc的__x86_64__宏):

#include <stdio.h>
#include <stdint.h>
#include <limits.h>

int main(void) {
#if defined __x86_64__ && SIZE_MAX == 0xFFFFFFFF
    puts("This is x86_64 with the x32 ABI");
#elif defined __x86_64__ && SIZE_MAX > 0xFFFFFFFF
    puts("This is x86_64 without the x32 ABI");
#else
    puts("This is not x86_64");
#endif
    printf("uint_fast8_t  is %2zu bits\n", CHAR_BIT * sizeof (uint_fast8_t));
    printf("uint_fast16_t is %2zu bits\n", CHAR_BIT * sizeof (uint_fast16_t));
    printf("uint_fast32_t is %2zu bits\n", CHAR_BIT * sizeof (uint_fast32_t));
    printf("uint_fast64_t is %2zu bits\n", CHAR_BIT * sizeof (uint_fast64_t));
}

当我用gcc -m64编译时,输出为:

This is x86_64 without the x32 ABI
uint_fast8_t  is  8 bits
uint_fast16_t is 64 bits
uint_fast32_t is 64 bits
uint_fast64_t is 64 bits

当我用gcc -mx32编译时,输出为:

This is x86_64 with the x32 ABI
uint_fast8_t  is  8 bits
uint_fast16_t is 32 bits
uint_fast32_t is 32 bits
uint_fast64_t is 64 bits

(除第一行外,它将输出与gcc -m32匹配,后者生成32位x86代码)。

这是glibc中的错误(定义{{​​1}}标头),还是遵循一些x32 ABI要求? x32 ABI documentx86_64 ABI document中没有<stdint.h>类型的引用,但可能还有其他内容指定了它。

有人可能认为fast16和fast32类型应该是64位或x32,因为64位寄存器是可用的;会对当前行为更有意义吗?

(我已经对原始问题进行了大量编辑,该问题仅涉及x32 ABI。现在问题是x86_64是否有x32。)

3 个答案:

答案 0 :(得分:1)

一般来说,您会期望32位整数类型比x86-64 CPU上的64位整数类型快一些。部分是因为它们使用较少的内存,但也因为64位指令需要额外的前缀字节超过其32位对应物。 32位除法指令明显快于64位指令,但其他指令执行延迟相同。

将它们加载到64位寄存器时通常不需要扩展32位。虽然在这种情况下CPU会自动对值进行零扩展,但这通常只是一个好处,因为它可以避免部分寄存器停顿。加载到寄存器上部的内容不如整个寄存器被修改的事实重要。寄存器上半部分的内容无关紧要,因为当它们用于保存32位类型时,它们通常仅用于32位指令,这些指令仅适用于寄存器的低32位部分。

当使用x32和x86-64 ABI时,int_fast32_t类型的大小之间的不一致可能是指针是64位宽的最佳证明。每当向指针添加32位整数时,就需要对其进行扩展,这在使用x86-64 ABI时更容易发生。

要考虑的另一个因素是x32 ABI的重点是通过使用较小的类型来获得更好的性能。任何受益于指针和相关类型较小的应用程序也应该受益于int_fast32_t更小。

答案 1 :(得分:0)

我编译了以下示例代码,以检查生成的代码,以获得具有不同整数类型的简单求和:

#include <stdint.h>

typedef int16_t INT;
//typedef int32_t INT;
//typedef int64_t INT;

INT foo()
{
    volatile INT a = 1, b = 2;
    return a + b;
}

然后我反汇编了每个整数类型生成的代码。编译命令为gcc -Ofast -mx32 -c test.c。请注意,在完整的64位模式下,生成的代码几乎相同,因为我的代码中没有指针(只有%rsp而不是%esp)。

int16_t会发出:

00000000 <foo>:
   0:   b8 01 00 00 00          mov    $0x1,%eax
   5:   ba 02 00 00 00          mov    $0x2,%edx
   a:   67 66 89 44 24 fc       mov    %ax,-0x4(%esp)
  10:   67 66 89 54 24 fe       mov    %dx,-0x2(%esp)
  16:   67 0f b7 54 24 fc       movzwl -0x4(%esp),%edx
  1c:   67 0f b7 44 24 fe       movzwl -0x2(%esp),%eax
  22:   01 d0                   add    %edx,%eax
  24:   c3                      retq   

使用int32_t

00000000 <foo>:
   0:   67 c7 44 24 f8 01 00 00 00  movl   $0x1,-0x8(%esp)
   9:   67 c7 44 24 fc 02 00 00 00  movl   $0x2,-0x4(%esp)
  12:   67 8b 54 24 f8              mov    -0x8(%esp),%edx
  17:   67 8b 44 24 fc              mov    -0x4(%esp),%eax
  1c:   01 d0                       add    %edx,%eax
  1e:   c3                          retq   

使用int64_t

00000000 <foo>:
   0:   67 48 c7 44 24 f0 01 00 00 00   movq   $0x1,-0x10(%esp)
   a:   67 48 c7 44 24 f8 02 00 00 00   movq   $0x2,-0x8(%esp)
  14:   67 48 8b 54 24 f0               mov    -0x10(%esp),%rdx
  1a:   67 48 8b 44 24 f8               mov    -0x8(%esp),%rax
  20:   48 01 d0                        add    %rdx,%rax
  23:   c3                              retq   

现在,我没有声称确切地知道编译器为什么生成了这个代码(可能volatile关键字与非寄存器大小整数类型组合不是最佳选择?)。但是根据生成的代码,我们可以得出以下结论:

  1. 最慢的类型是int16_t。它需要额外的指令来移动值。
  2. 最快的类型是int32_t。虽然32位和64位版本具有相同数量的指令,但32位代码的字节数较短,因此它将更加缓存,因此速度更快。
  3. 因此,快速类型的自然选择是:

    1. 对于int_fast16_t,请选择int32_t
    2. 对于int_fast32_t,请选择int32_t
    3. 对于int_fast64_t,请选择int64_t(还有其他内容)。

答案 2 :(得分:-3)

坚韧。我们来看看int_fast8_t。如果开发人员使用大型数组来存储大量8位有符号整数,那么由于缓存,int8_t将是最快的。我声明使用大型int_fast8_t数组可能是一个坏主意。

您需要使用大型代码库,并且如果使用int_fast8_t签名,则系统地替换int8_t和signed char以及plain char。然后使用int_fast8_t的不同typedef对代码进行基准测试,并测量最快的代码。

请注意,未定义的行为将会发生变化。例如,如果类型为int8_t,则赋值255将给出-1的结果,否则为255。