不同的内在行为取决于 GCC 版本
Different intrinsics behaviour depending on GCC version
我对内在函数很陌生,我的代码在 GCC-7.4 和 GCC-8.3 中的行为不同
我的代码很简单
b.cpp:
#include <iostream>
#include <xmmintrin.h>
void foo(const float num, const float denom)
{
const __v4sf num4 = {
num,
num,
num,
num,
};
const __v4sf denom4 = {
denom,
denom,
denom,
denom,
};
float res_arr[] = {0, 0, 0, 0};
__v4sf *res = (__v4sf*)res_arr;
*res = num4 / denom4;
std::cout << res_arr[0] << std::endl;
std::cout << res_arr[1] << std::endl;
std::cout << res_arr[2] << std::endl;
std::cout << res_arr[3] << std::endl;
}
在b.cpp中,我们基本上只是从浮点变量构造两个__v4sf
并执行除法
b.h:
#ifndef B_H
#define B_H
void foo(const float num, const float denom);
#endif
a.cpp:
#include "b.h"
int main (void)
{
const float denominator = 1.0f;
const float numerator = 12.0f;
foo(numerator, denominator);
return 0;
}
这里我们只是从b.cpp
调用我们的函数
GCC 7.4 工作正常:
g++-7 -c b.cpp -o b.o && g++-7 a.cpp b.o -o a.out && ./a.out
12
12
12
12
但是 GCC 8.3 有问题
g++-8 -c b.cpp -o b.o && g++-8 a.cpp b.o -o a.out && ./a.out
inf
inf
inf
inf
所以我的问题是 - 为什么我使用不同版本的 GCC 会收到不同的结果?它是未定义的行为吗?
您在 gcc8 及更高版本中发现了一个错误,with/without 优化已启用。感谢 reporting it。
启用优化后,很容易看出 asm 在做什么,因为 __v4sf
东西被优化掉了:它只是标量除法并将结果打印 4 次。 (加上 4 次 flush cout 调用,因为您出于某种原因使用了 std::endl
。)
gcc7 正确地将其优化为 divss xmm0, xmm1
以执行 num / denom
。然后它转换为 double
,因为输出函数只采用 double
,而不是 float
,将其传递给 iostream
函数。 (GCC7 使用 -mtune=skylake
将 double
bit-pattern 保存在整数寄存器 r14
而不是内存中。GCC8 和更高版本只使用内存,这可能更有意义。)
gcc8 和后来的 divss xmm0, .LC0[rip]
,其中内存中的常量是 0
(+0.0
的 bit-pattern)。所以它将 num
除以零,忽略 denom
.
在 the Godbolt compiler explorer 上查看。
使用alignas(16) float res_arr[4];
消除__v4sf *res
的潜力under-alignment 无济于事。 (您通常不再需要 __attribute__((aligned(16)))
;C++11 引入了用于对齐的标准语法。)
我对内在函数很陌生,我的代码在 GCC-7.4 和 GCC-8.3 中的行为不同
我的代码很简单
b.cpp:
#include <iostream>
#include <xmmintrin.h>
void foo(const float num, const float denom)
{
const __v4sf num4 = {
num,
num,
num,
num,
};
const __v4sf denom4 = {
denom,
denom,
denom,
denom,
};
float res_arr[] = {0, 0, 0, 0};
__v4sf *res = (__v4sf*)res_arr;
*res = num4 / denom4;
std::cout << res_arr[0] << std::endl;
std::cout << res_arr[1] << std::endl;
std::cout << res_arr[2] << std::endl;
std::cout << res_arr[3] << std::endl;
}
在b.cpp中,我们基本上只是从浮点变量构造两个__v4sf
并执行除法
b.h:
#ifndef B_H
#define B_H
void foo(const float num, const float denom);
#endif
a.cpp:
#include "b.h"
int main (void)
{
const float denominator = 1.0f;
const float numerator = 12.0f;
foo(numerator, denominator);
return 0;
}
这里我们只是从b.cpp
调用我们的函数GCC 7.4 工作正常:
g++-7 -c b.cpp -o b.o && g++-7 a.cpp b.o -o a.out && ./a.out
12
12
12
12
但是 GCC 8.3 有问题
g++-8 -c b.cpp -o b.o && g++-8 a.cpp b.o -o a.out && ./a.out
inf
inf
inf
inf
所以我的问题是 - 为什么我使用不同版本的 GCC 会收到不同的结果?它是未定义的行为吗?
您在 gcc8 及更高版本中发现了一个错误,with/without 优化已启用。感谢 reporting it。
启用优化后,很容易看出 asm 在做什么,因为 __v4sf
东西被优化掉了:它只是标量除法并将结果打印 4 次。 (加上 4 次 flush cout 调用,因为您出于某种原因使用了 std::endl
。)
gcc7 正确地将其优化为 divss xmm0, xmm1
以执行 num / denom
。然后它转换为 double
,因为输出函数只采用 double
,而不是 float
,将其传递给 iostream
函数。 (GCC7 使用 -mtune=skylake
将 double
bit-pattern 保存在整数寄存器 r14
而不是内存中。GCC8 和更高版本只使用内存,这可能更有意义。)
gcc8 和后来的 divss xmm0, .LC0[rip]
,其中内存中的常量是 0
(+0.0
的 bit-pattern)。所以它将 num
除以零,忽略 denom
.
在 the Godbolt compiler explorer 上查看。
使用alignas(16) float res_arr[4];
消除__v4sf *res
的潜力under-alignment 无济于事。 (您通常不再需要 __attribute__((aligned(16)))
;C++11 引入了用于对齐的标准语法。)