Подтвердить что ты не робот

Возможная ошибка компилятора в Visual С++ 2012 (x86)?

В настоящее время я испытываю случайные ошибки с плавающей запятой при компиляции для целей x86 с использованием VС++ 11 (CTP Update 1). Ниже приведен краткий пример "test.cpp" ниже и скомпилируйте, используя:

cl /GL /O2 /EHsc test.cpp /link /MACHINE:X86

Выход должен быть 10 == 10, но он создает 10 == 0, когда /GL (оптимизация всей программы) включена. Проблема заключается в том, что get_scaling_factor() подталкивает результат к стеку с плавающей запятой, но вызывающая функция ожидает его в регистре SSE XMM0.

Вопрос: Я пропущу что-то очевидное или это действительно ошибка? Разумеется, тестовая программа не имеет смысла, так как это урезанный тестовый пример.

test.cpp:

#include <iostream>

template <typename T>
inline T get_scaling_factor(int units)
{
    switch (units)
    {
    case 0: return 1;  
    case 1: return 10;  
    case 2: return 100;  
    case 3: return 1000;  
    case 4: return 10000;  
    case 5: return 100000;  
    case 6: return 1000000;  
    case 7: return 10000000;  
    case 8: return 100000000;  
    case 9: return 1000000000; 
    default: return 1;
    }
}

template <int targetUnits, typename T>
inline T scale(T value, int sourceUnits)
{
    return value   * get_scaling_factor<T>(sourceUnits) 
                   / get_scaling_factor<T>(targetUnits);
}

__declspec(noinline)
double scale(double value, int units) 
{
    return scale<9>(value, units);
}

int main()
{
    std::cout << "10 = " << scale(1e9, 1) << std::endl;
}

Update

Проблема подтверждена Microsoft. Он даже влияет на прямой код следующим образом:

#include <stdio.h>
double test(int a)
{
    switch (a)
    {
    case 0: return 1.0;
    case 1: return 10.0;
    case 2: return 100.0;
    case 3: return 1000.0;
    case 4: return 10000.0;
    case 5: return 100000.0;
    case 6: return 1000000.0;
    case 7: return 10000000.0;
    case 8: return 100000000.0;
    case 9: return 1000000000.0;
    default: return 1.0;
    }
}

void main()
{
    int nine = 9;
    double x = test(nine);
    x /= test(7);
    int val = (int)x;
    if (val == 100)
        printf("pass");
    else 
        printf("fail, val is %d", val);
}
4b9b3361

Ответ 1

Да, это определенно ошибка оптимизатора кода, и я не мог воспроизвести ее. Ошибки оптимизатора обычно связаны с вложением, но это не так. Эта ошибка была введена в результате тяжелых изменений кода в VS2012, которые поддерживают новую функцию автоматического векторизации.

В двух словах функция get_scaling_factor() возвращает результат в стеке FPU. Генератор кода правильно испускает команду для извлечения из стека и сохраняет его в регистре XMM. Но неуправляемый оптимизатор полностью удаляет этот код, как будто он предполагает, что результат функции уже был сохранен в XMM0.

Обходной путь трудно найти, специализация функции шаблона для double не влияет. Отключение оптимизации с помощью #pragma optimize works:

#pragma optimize("", off)
__declspec(noinline)
double scale(double value, int units) 
{
    return scale<9>(value, units);
}
#pragma optimize("", on)

Ваш репрограммный код очень хорош, и у Microsoft не будет проблем с исправлением этой ошибки. Вы можете отправить отчет о обратной связи на сайте connect.microsoft.com, просто связавшись с этим вопросом. Или, если вы спешите, вы можете обратиться в службу поддержки Microsoft, хотя я бы предположил, что они предоставят вам обходное решение, которое поможет вам в пакете обновления.


UPDATE: исправлено в VS2013.

Ответ 2

/GL игнорирует стандартные вызовы по умолчанию. С помощью LTCG компилятор/компоновщик знает обо всем графике вызовов, чтобы он мог соответствовать вызывающему и вызываемому. Использование SSE-регистра в этом контексте не является странным.

Я не совсем уверен, что вы подразумеваете под "get_scaling_factor() толкает результат в стек с плавающей точкой". Вы имеете в виду, что компилятор не встраивает его? Я ожидаю, что компилятор сделает это, поскольку граф вызовов имеет только один вызывающий. (Мы знаем, что `get_scaling_factor (targetUnits) был встроен, поскольку это вызвало бы деление на ноль в противном случае)

Если компилятор действительно не выполняет inline get_scaling_factor(), то вы на самом деле обнаружили две ошибки: один отказ от наложения и один отказ пользовательского вызова.