Различное внутреннее поведение в зависимости от версии GCC - PullRequest
6 голосов
/ 10 июня 2019

Я довольно плохо знаком с внутренностями и столкнулся с разным поведением моего кода в 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? Это неопределенное поведение?

1 Ответ

4 голосов
/ 10 июня 2019

Вы обнаружили ошибку в gcc8 и более поздних версиях, которая происходит при включенной / без оптимизации. Спасибо за сообщение .

С включенной оптимизацией легко увидеть, что делает асм, потому что оптимизируется __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.

Проверьте это на проводнике компилятора Godbolt .

Использование alignas(16) float res_arr[4]; для устранения потенциального смещения __v4sf *res не помогает. (Как правило, вам больше не нужен __attribute__((aligned(16))); в C ++ 11 введен стандартный синтаксис для выравнивания.)


...