【问题标题】:Different intrinsics behaviour depending on GCC version不同的内在行为取决于 GCC 版本
【发布时间】:2019-10-24 15:56:10
【问题描述】:

我对内在函数还很陌生,我在使用 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 会收到不同的结果?它是未定义的行为吗?

【问题讨论】:

  • 您正在使用特定于编译器的功能。根据定义,它是未定义的行为。 C++ 标准中“未定义”的含义本质上是“C++ 标准没有对发生的事情指定任何约束”。该标准当然没有指定您可能使用的任何编译器特定的内在函数。如果您想了解发生了什么,请阅读 gcc 文档 - 可能适用于您正在使用的两个版本。
  • @Peter 这不是未定义的,它的implementation defined(因为__v4sf starts with __)。主要区别,这基本上意味着标准方面的问题,并说“它被定义为:做任何实现(在本例中为 gcc)想要它做的事情。”
  • 这看起来像是 gcc 中的一个错误(您可以通过删除一些 const 来解决它)。请向 gcc 的 bugzilla 报告,以便修复,谢谢。
  • @MarcGlisse 我创建了错误报告gcc.gnu.org/bugzilla/show_bug.cgi?id=90810(不确定我的格式是否正确)。等待答复
  • 请注意,您的代码至少由于对齐原因被破坏:__v4sf 假定 16 字节对齐,而 res_arr 仅保证 4。

标签: c++ gcc undefined-behavior intrinsics


【解决方案1】:

您在 gcc8 及更高版本中发现了一个错误,该错误在启用/未启用优化的情况下发生。感谢reporting it

启用优化后,很容易看到 asm 正在做什么,因为 __v4sf 东西优化了:它只是标量除法并打印 4 次结果。 (加上 4 次刷新 cout 的调用,因为您出于某种原因使用了 std::endl。)

gcc7 正确地将其优化为divss xmm0, xmm1 以执行num / denom。然后它转换为double,因为输出函数只接受double,而不是float,将其传递给iostream函数。 (GCC7 将double 位模式保存在整数寄存器r14 而不是内存中,-mtune=skylake。GCC8 及更高版本只使用内存,这可能更有意义。)

gcc8 及更高版本执行divss xmm0, .LC0[rip],其中内存中的常量为0+0.0 的位模式)。所以它将num 除以零,忽略denom

查看the Godbolt compiler explorer

使用alignas(16) float res_arr[4]; 消除__v4sf *res 的潜在欠对齐无济于事。 (您通常不再需要__attribute__((aligned(16)));C++11 引入了对齐的标准语法。)


【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2017-05-04
    • 2013-02-20
    • 1970-01-01
    相关资源
    最近更新 更多