繁体   English   中英

为什么gcc / clang使用两个128位xmm寄存器来传递单个值?

[英]Why does gcc/clang use two 128bit xmm registers to pass a single value?

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

#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;
}

现在,在我的脑海里,调用squaremain应该把价值axmm0从而使square功能可以做mulps xmm0, xmm0并用它做。

这不是我用clang或gcc编译时会发生的事情。 取而代之的是,前8个字节的a被放置在xmm0并在接下来的8个字节xmm1 ,使square功能复杂得多,因为它需要打补丁的东西回来了。

知道为什么吗?

注意:这是-O3优化。

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

编译器只是试图遵循System V应用程序二进制接口AMD64架构处理器补充,第3.2.3节参数传递所规定的调用约定。

相关要点是:

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成员的存在没有任何影响,你甚至可以删除它。

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

只为clang说话:

看起来这是一个问题,只是编译器启发式的一个不幸的副作用。

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

clang查看你的函数并发现它需要16个字节的类型签名参数,然后使用启发式选择它认为最好的类型。 它有利于对{ double, double } <4 x float>进行{ double, double }解释(这样可以在你的情况下提供最高效率),因为双精度在对齐方面更宽松。

我不是clang internals的专家,所以我可能会非常错误,但看起来并不是一个特别好的方法。 如果您需要优化版本,则可能必须使用指针转换而不是联合来获取它。

我怀疑的代码导致了问题:

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;
    ...
}

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM