【问题标题】:Why does gcc/clang use two 128bit xmm registers to pass a single value?为什么 gcc/clang 使用两个 128 位 xmm 寄存器来传递单个值?
【发布时间】:2014-04-21 09:00:51
【问题描述】:

所以我偶然发现了一些我想了解的东西,因为它让我很头疼。我有以下代码:

#include <stdio.h>
#include <smmintrin.h>

typedef union {
    struct { float x, y, z, w; } v;
    __m128 m;
} vec;

vec __attribute__((noinline)) square(vec a)
{
    vec x = { .m = _mm_mul_ps(a.m, a.m) };
    return x;
}

int main(int argc, char *argv[])
{
    float f = 4.9;
    vec a = (vec){f, f, f, f};
    vec res = square(a); // ?
    printf("%f %f %f %f\n", res.v.x, res.v.y, res.v.z, res.v.w);
    return 0;
}

现在,在我看来,在main 中对square 的调用应该将a 的值放入xmm0 中,以便square 函数可以执行mulps xmm0, xmm0 并完成它。

这不是我使用 clang 或 gcc 编译时发生的情况。相反,a 的前 8 个字节放在 xmm0 中,接下来的 8 个字节放在 xmm1 中,这使得 square 函数更加复杂,因为它需要修补一些东西。

知道为什么吗?

注意:这是使用 -O3 优化。

经过进一步研究,似乎与联合类型有关。如果函数采用直接 __m128,则生成的代码将期望单个寄存器 (xmm0) 中的值。但是考虑到它们都应该适合 xmm0,我不明白为什么在使用 vec 类型时它被分成两个半使用的寄存器..

【问题讨论】:

  • 你玩过其他命令行选项吗?总是值得尝试打开/关闭一些东西,看看它如何影响生成的代码。
  • 我会试试的,有什么建议吗?有这么多开关..
  • 我不熟悉clang,但是在GCC 上有一堆x86 特定的options,很多都与SSE 有关。另外,优化和调试标志呢?
  • 老实说,我很惊讶它没有使用四个寄存器,因为它会通过四个浮点数。由于您使用的是 gcc,我建议您使用 vector extensions 而不是具有四个浮点数的结构。
  • 对我来说,它看起来根本不像是在 xmm0 中传递参数。如果您使用直接的 __m128 或什至包含单个 __m128 的结构,那么它将按照 ABI 在 xmm0 中传递它。但我认为 ABI 坚持要求联合在堆栈上传递(尽管我很可能错了)。

标签: c++ c assembly clang sse


【解决方案1】:

编译器只是试图遵循System V Application Binary Interface AMD64 Architecture Processor Supplement, section 3.2.3 Parameter Passing指定的调用约定。

相关点是:

We first define a number of classes to classify arguments. The
classes are corresponding to AMD64 register classes and defined as:

SSE The class consists of types that fit into a vector register.

SSEUP The class consists of types that fit into a vector register and can
be passed and returned in the upper bytes of it.

The size of each argument gets rounded up to eightbytes.
The basic types are assigned their natural classes:
Arguments of types float, double, _Decimal32, _Decimal64 and __m64 are
in class SSE.

The classification of aggregate (structures and arrays) and union types
works as follows:

If the size of the aggregate exceeds a single eightbyte, each is
classified separately. 

应用上述规则意味着嵌入结构的x, yz, w 对被分别归类为SSE 类,这反过来意味着它们必须在两个单独的寄存器中传递。在这种情况下m 成员的存在没有任何影响,您甚至可以将其删除。

【讨论】:

  • 这似乎是答案。我想也许第一点会优先,因为它适合向量寄存器。很有趣。
【解决方案2】:

编辑:在第二次阅读时,我不太确定为什么会发生这种情况,但我更确定这就是发生的地方。我不认为这个答案是正确的,但我会留下它,因为它可能会有所帮助。

只为clang说话:

这似乎只是编译器启发式的不幸副作用。

从clang(文件CGRecordLayoutBuilder.cpp,函数CGRecordLowering::lowerUnion)的简要介绍看来,llvm 在内部并不代表联合类型,并且函数的类型不会根据内部的用途而改变函数。

clang 查看您的函数,发现它需要 16 个字节的参数作为类型签名,然后使用启发式方法来选择它认为最好的类型。它倾向于 { double, double } 解释而不是 &lt;4 x float&gt;(这将在您的情况下提供最高效率),因为双精度在对齐方面更为宽松。

我不是 clang 内部结构方面的专家,所以我可能会大错特错,但看起来并没有什么特别好的方法可以解决这个问题。如果您想要优化版本,您可能必须使用指针转换而不是联合来获得它。

我怀疑导致问题的代码:

void CGRecordLowering::lowerUnion() {
    ...
    // Conditionally update our storage type if we've got a new "better" one.
    if (!StorageType ||
        getAlignment(FieldType) >  getAlignment(StorageType) ||
        (getAlignment(FieldType) == getAlignment(StorageType) &&
        getSize(FieldType) > getSize(StorageType)))
      StorageType = FieldType;
    ...
}

【讨论】:

    猜你喜欢
    • 2011-01-14
    • 1970-01-01
    • 2017-10-16
    • 2012-01-30
    • 1970-01-01
    • 2019-04-28
    • 1970-01-01
    • 2019-11-16
    相关资源
    最近更新 更多