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

В настоящее время я сталкиваюсь со случайными ошибками с плавающей запятой при компиляции для целей x86 с использованием VC++ 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;
}

Обновлять

Ошибка подтверждено 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);
}

person Daniel Gehriger    schedule 24.10.2012    source источник
comment
static_cast литералы для ввода T?   -  person Steve-o    schedule 24.10.2012
comment
@ Стив-о: нет - даже если вы завернете все литералы в T(...), это не удастся. В любом случае это не обязательно, компилятор продвинет их до нужного типа (если сможет). Обратите внимание, что код компилируется без предупреждений даже при самом высоком уровне предупреждений.   -  person Daniel Gehriger    schedule 24.10.2012


Ответы (2)


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

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

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

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

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


ОБНОВЛЕНИЕ: исправлено в VS2013.

person Hans Passant    schedule 24.10.2012
comment
Ганс, спасибо за этот полезный анализ. Я последовал вашему совету и отправил отчет об ошибке - person Daniel Gehriger; 24.10.2012
comment
В продолжение вашего обходного пути: на самом деле достаточно переписать get_scaling_factor() и ввести переменную T result;, а оператор switch присвоит ей правильное возвращаемое значение, прежде чем возвращать его. Затем компилятор выдает правильный результат. Однако, поскольку я не знаю, что именно вызвало эту ошибку, я должен предположить, что она может укусить меня в любое время и в любом месте. По сути, это означает, что LTCG нельзя использовать для производственного кода. - person Daniel Gehriger; 24.10.2012
comment
Подтвержденный. Достаточно просто добавить локальную переменную и инициализировать ее, чтобы функция внезапно использовала xmm0 вместо FPU для возвращаемого значения в оптимизированном коде. Похоже, настоящая ошибка заключается в том, что оптимизатор теряет функцию, использующую FPU или SSE. - person Hans Passant; 24.10.2012

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

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

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

person MSalters    schedule 24.10.2012
comment
Спасибо за Ваш ответ. Компилятор наполовину встраивает get_scaling_factor(): он встраивает ветвь переключателя по умолчанию, но не оставшуюся часть. Литералы помещаются в стек с плавающей запятой (используя fld), но вызывающая функция ожидает, что они будут в xmm0. - person Daniel Gehriger; 24.10.2012