c++ - 返回元组时 GCC/Clang x86_64 C++ ABI 不匹配?

标签 c++ tuples x86-64 abi compiler-bug

尝试 to optimize return values on x86_64 时,我注意到一件奇怪的事情。即,给定代码:

#include <cstdint>
#include <tuple>
#include <utility>

using namespace std;

constexpr uint64_t a = 1u;
constexpr uint64_t b = 2u;

pair<uint64_t, uint64_t> f() { return {a, b}; }
tuple<uint64_t, uint64_t> g() { return tuple<uint64_t, uint64_t>{a, b}; }

Clang 3.8 outputs f 的这个汇编代码:

movl $1, %eax
movl $2, %edx
retq

这对于 g:

movl $2, %eax
movl $1, %edx
retq

看起来最佳。然而,当 compiled with GCC 6.1 ,虽然为 f 生成的程序集与 Clang 输出的相同,但为 g 生成的程序集是:

movq %rdi, %rax
movq $2, (%rdi)
movq $1, 8(%rdi)
ret

看起来返回值的类型被 GCC 归类为 MEMORY,但被 Clang 归类为 INTEGER。我可以确认将 Clang 代码与 GCC 代码链接这样的代码会导致段错误(Clang 调用 GCC 编译的 g() 写入到 %rdi 碰巧指向的任何地方)并返回一个无效值(GCC 调用 Clang 编译的 g())。哪个编译器有问题?

相关:

另见

最佳答案

ABI 规定参数值是根据特定算法分类的。这里相关的是:

  1. If the size of the aggregate exceeds a single eightbyte, each is classified separately. Each eightbyte gets initialized to class NO_CLASS.

  2. Each field of an object is classified recursively so that always two fields are considered. The resulting class is calculated according to the classes of the fields in the eightbyte:

在这种情况下,每个字段(对于一个元组或一对)都是 uint64_t 类型,因此占据了整个“八字节”。那么,在每八字节中要考虑的“两个字段”是“NO_CLASS”(按照 3)和 uint64_t 字段,它被归类为 INTEGER。

还有,与参数相关的传递:

If a C++ object has either a non-trivial copy constructor or a non-trivial destructor, it is passed by invisible reference (the object is replaced in the parameter list by a pointer that has class INTEGER)

不满足这些要求的对象必须有地址,因此需要在内存中,这就是存在上述要求的原因。返回值也是如此,尽管这似乎在规范中被省略了(可能是偶然的)。

最后还有:

(c) If the size of the aggregate exceeds two eightbytes and the first eight-byte isn’t SSE or any other eightbyte isn’t SSEUP, the whole argument is passed in memory.

这显然不适用于这里;聚合的大小正好是两个八字节。

在返回值时,文本说:

  1. Classify the return type with the classification algorithm

这意味着,如上所述,元组应归类为 INTEGER。那么:

  1. If the class is INTEGER, the next available register of the sequence %rax, %rdx is used.

这很清楚。

唯一仍然悬而未决的问题是这些类型是否是非平凡复制可构造/可破坏的。如上所述,这种类型的值不能在寄存器中传递或返回,即使规范似乎没有认识到返回值的问题。但是,我们可以很容易地证明元组和对都是可平凡复制构造和可平凡破坏的,使用以下程序:

测试程序:

#include <utility>
#include <cstdint>
#include <tuple>
#include <iostream>

using namespace std;

int main(int argc, char **argv)
{
    cout << "pair is trivial? : " << is_trivial<pair<uint64_t, uint64_t> >::value << endl;
    cout << "pair is trivially_copy_constructible? : " << is_trivially_copy_constructible<pair<uint64_t, uint64_t> >::value << endl;
    cout << "pair is standard_layout? : " << is_standard_layout<pair<uint64_t, uint64_t> >::value << endl;
    cout << "pair is pod? : " << is_pod<pair<uint64_t, uint64_t> >::value << endl;
    cout << "pair is trivially_destructable? : " << is_trivially_destructible<pair<uint64_t, uint64_t> >::value << endl;
    cout << "pair is trivially_move_constructible? : " << is_trivially_move_constructible<pair<uint64_t, uint64_t> >::value << endl;

    cout << "tuple is trivial? : " << is_trivial<tuple<uint64_t, uint64_t> >::value << endl;
    cout << "tuple is trivially_copy_constructible? : " << is_trivially_copy_constructible<tuple<uint64_t, uint64_t> >::value << endl;
    cout << "tuple is standard_layout? : " << is_standard_layout<tuple<uint64_t, uint64_t> >::value << endl;
    cout << "tuple is pod? : " << is_pod<tuple<uint64_t, uint64_t> >::value << endl;
    cout << "tuple is trivially_destructable? : " << is_trivially_destructible<tuple<uint64_t, uint64_t> >::value << endl;
    cout << "tuple is trivially_move_constructible? : " << is_trivially_move_constructible<tuple<uint64_t, uint64_t> >::value << endl;
    return 0;
}

使用 GCC 或 Clang 编译时的输出:

pair is trivial? : 0
pair is trivially_copy_constructible? : 1
pair is standard_layout? : 1
pair is pod? : 0
pair is trivially_destructable? : 1
pair is trivially_move_constructible? : 1
tuple is trivial? : 0
tuple is trivially_copy_constructible? : 1
tuple is standard_layout? : 0
tuple is pod? : 0
tuple is trivially_destructable? : 1
tuple is trivially_move_constructible? : 0

这意味着 GCC 弄错了。返回值应该在 %rax,%rdx 中传递。

(类型之间的主要显着差异是 pair 是标准布局并且可以简单地移动构造,而 tuple 不是,因此 GCC 可能总是返回例如,通过指针进行非平凡移动可构造的值)。

关于c++ - 返回元组时 GCC/Clang x86_64 C++ ABI 不匹配?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/37457443/

相关文章:

c++ - Qt 容器如何作为参数传递给共享库的函数?

c++链表反转 - 指针不前进

c++ - 我如何通过在 C++ 中单击它来将文件夹名称发送到函数

haskell - 元组声明进入无限循环

c++ - 为什么 std::tuple<int> 不能简单复制?

assembly - 将 scanf 与 x86-64 GAS 组件结合使用

c - 在 uint128_t 整数上下文中对十六进制字符串的操作

c++ - 哪个 DLL 有 PathCchAppend?

python - 将元组列表转换为 numpy 数组并 reshape 它?

c++ - 8 个后续字节的测试未转换为单个比较指令