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

Странное поведение с включенными оптимизациями

У меня есть этот небольшой фрагмент кода (это минимальный рабочий пример проблемы, который у меня есть):

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

void xorBuffer(unsigned char* dst, unsigned char* src, int len)
{
    while (len != 0)
    {
        *dst ^= *src;
        dst++;
        src++;
        len--;
    }
}

int main()
{
    unsigned char* a = malloc(32);
    unsigned char* b = malloc(32);
    int t;

    memset(a, 0xAA, 32);
    memset(b, 0xBB, 32);

    xorBuffer(a, b, 32);

    printf("result = ");
    for (t = 0; t < 32; t++) printf("%.2x", a[t]);
    printf("\n");

    return 0;
}

Этот код должен выполнять эксклюзивные или два 32-байтовых буфера памяти (концептуально это должно делать a = a ^ b). Так как 0xAA ^ 0xBB = 0x11, он должен печатать "11" тридцать два раза.

Моя проблема заключается в том, что когда я компилирую это в MinGW-GCC (Windows), это отлично работает в режиме отладки (без оптимизации), но с SIGILL наступает через середину цикла xorBuffer, когда оптимизация начинается с -O3. Кроме того, если я поставлю printf в оскорбительном цикле, он будет работать отлично. Я подозреваю, что повреждение стека, но я просто не вижу, что я делаю неправильно здесь.

Попытка отладки с GDB с включенными оптимизациями - это потерянная причина, так как все GDB показывает, что "переменная оптимизирована" для каждой переменной (и, конечно, если я попытаюсь распечатать переменную, она внезапно начнет работать).

Кто-нибудь знает, что здесь происходит? Я слишком долго занимался этой проблемой, и мне действительно нужно исправить это правильно, чтобы двигаться дальше. Я предполагаю, что мне не хватает какого-либо фундаментального знания указателя C, но для меня код выглядит правильно. Это может быть из инкремента буфера, но, насколько я знаю, sizeof(unsigned char) == 1, поэтому он должен проходить каждый байт один за другим.

Для чего это стоит, код работает даже с оптимизациями на GCC на моем Linux-сервере.

Итак... какая сделка здесь? Спасибо!

В соответствии с запросом выводится сборка всей программы:

С -O2: clicky

С -O3: clicky

Я наблюдаю это поведение на GCC 4.6.2 (работает с MinGW)

4b9b3361

Ответ 1

Из моего комментария:

Убедитесь, что компилятор имеет правильную информацию о целевой архитектуре. Кажется, из чтения вывода -O3, что компилятор настраивает оптимизацию SIMD, что делает его более параллельным с использованием векторных инструкций (например, movdqa). Если целевой процессор не соответствует 100% тому, на что исходит код компилятора, вы можете получить незаконные инструкции.

Ответ 2

Я добавляю это как расширение ответа Unwind (который я принимаю, когда он меня на правильном пути).

После просеивания оптимизированного кода я заметил инструкции AVX. Сначала я подумал, что это не должно вызывать проблемы, учитывая, что мой процессор поддерживает набор инструкций AVX. Однако выясняется, что существуют две разные версии AVX: AVX1 и AVX2. И хотя мой процессор поддерживает только AVX1, gcc без разбора использует коды операций AVX2, если процессор поддерживает любую из двух версий (llvm сделал ту же ошибку, есть ошибка отчеты об этом). Это, насколько я могу себе представить, неправильную работу и ошибку компилятора.

В результате получается код AVX2 в системе AVX1, что, очевидно, приводит к незаконной инструкции. Это объясняет многие вещи: от кода, который не работает на входах размером менее 32 байт (из-за ширины регистра 256 бит), коду, работающему на моем Linux-боксе, который, оказывается, является виртуальной машиной с поддержкой ЦП, ограниченным SSE3.

Исправление состоит в том, чтобы отключить -O3 и вернуться к -O2, где gcc не будет использовать самые жесткие инструкции SIMD для оптимизации простого кода или использовать ключевое слово volatile, которое заставит его пройти байт буферов на байт, кропотливо, так:

*(unsigned char volatile *)dst ^= *(unsigned char volatile *)src;

Это, конечно, очень медленный и, вероятно, хуже, чем просто использование -O2 (игнорирование последствий для всей программы), но его можно обойти, пройдя через int int int и заполнив в конце, что достаточно хорошо с точки зрения скорости.

Другим хорошим решением является обновление до версии gcc, которая не имеет этой ошибки (эта версия может еще не существовать, я не проверял).

EDIT: окончательное решение заключается в том, чтобы выдать флаг -mno-avx в GCC, тем самым отключив любые и все коды операций AVX, полностью отрицая ошибку без изменений кода (и ее можно легко удалить после исправления версии компилятора доступно.)

Какая извращенная ошибка компилятора.