GCC не будет оптимизировать целочисленное арифметическое выражение

int f(int x, int y) {
    return 20 * (x - 10) + 50 * (x + 5);
}

int f_expected(int x, int y) {
    return 70 * x + 50;
}

сгенерированный код:

f(int, int):
        lea     eax, [rdi-50+rdi*4]
        add     edi, 5
        imul    edi, edi, 50
        lea     eax, [rdi+rax*4]
        ret
f_expected(int, int):
        imul    eax, edi, 70
        add     eax, 50
        ret

Я ожидаю, что f будет скомпилирован в f_expected. Я попробовал -O3 и -Ofast в GCC 7. Какой именно флаг мне нужен (если есть)? clang и icc выдают ожидаемый код под -O3.

Для справки, clang code:

f(int, int):
        imul    eax, edi, 70
        add     eax, 50
        ret

f_expected(int, int):
        imul    eax, edi, 70
        add     eax, 50
        ret

person Gabriel Garcia    schedule 31.03.2017    source источник
comment
Я ожидаю, что f будет скомпилирован в f_expected Почему?   -  person    schedule 01.04.2017
comment
У меня есть другой код, где этот тип оптимизации доказуемо (я профилировал, оптимизировал вручную и т. д.) приводит к более быстрому выполнению. Кроме того, у clang нет проблем с этим. Является ли GCC особенно плохим для таких типов оптимизации, или я просто использую его неправильно?   -  person Gabriel Garcia    schedule 01.04.2017
comment
Вы не можете ожидать, что два разных компилятора создадут один и тот же оптимизированный (или неоптимизированный) код.   -  person    schedule 01.04.2017
comment
Несколько загадочным образом GCC делает это правильно, если вы используете -fwrapv — да, флаг, который запрещает некоторые оптимизации.   -  person harold    schedule 01.04.2017
comment
@гарольд фантастический. Это то, что я ищу. Хотите добавить его в качестве ответа, чтобы я мог его принять?   -  person Gabriel Garcia    schedule 01.04.2017
comment
Желательно нет, так как это далеко не вся история. Поскольку это совершенно случайное открытие, это решение не должно было сработать. Это скорее свидетельство ошибки в GCC, чем решение.   -  person harold    schedule 01.04.2017
comment
@harold Я не очень разбираюсь в GCC и разработке компиляторов, но формулировка docs звучит прекрасно: This flag enables some optimizations and disables others. Без особого фона это кажется мне естественным (либо: A мы предполагаем какое-то математическое поведение / нет, либо B мы предполагаем одно из двух разных математических действий; я не уверен, в чем здесь дело, очевидно, что B) указывает скорее на тип включения некоторого; отключить другие вещи)   -  person sascha    schedule 01.04.2017
comment
@sascha да, но эта оптимизация действительна в любом случае, ее действительно не следует отключать. Я подозреваю, что он даже на самом деле не отключен, но какая-то другая переработка каким-то образом мешает и разрушает шаблон, поэтому оптимизация, которая должна заботиться об этом, больше не распознает выражение, но я не в GCC внутренности..   -  person harold    schedule 01.04.2017
comment
@harold Хорошо, спасибо за понимание. Интересно посмотреть, что сегодня делают компиляторы. Я, вероятно, испугался бы такого рода оптимизации здесь, когда мне пришлось бы внедрять криптографию с учетом атак по сторонним каналам (что означает: действителен с точки зрения результата, но не всегда изменение поведения может быть желательным). Но это тоже не мой опыт, и тогда опции компилятора в целом кажутся страшными.   -  person sascha    schedule 01.04.2017
comment
Рассмотрим этот код в GCC (godbolt.org/g/Mm7l0m) и Clang (godbolt.org/g/cTtbRx).   -  person Gabriel Garcia    schedule 01.04.2017
comment
Попробуйте -30678338 с -fsanitize=undefined. Оптимизация действительна, но может быть выполнена только на низком уровне. Иногда обсуждают возможность автоматического включения -fwrapv во время последних проходов оптимизации.   -  person Marc Glisse    schedule 04.11.2018


Ответы (1)


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

$ gcc tmp.c -S -o- -O2
    ...
    leal    -50(%rdi,%rdi,4), %eax
    movl    $50, %edx
    addl    $5, %edi
    imull   %edx, %edi
    leal    (%rdi,%rax,4), %eax
    ret
$ gcc tmp.c -S -o- -O2 -fwrapv
    ...
    movl    %edi, %eax
    movl    $70, %edx
    imull   %edx, %eax
    addl    $50, %eax
    ret

Теперь Clang может каким-то образом выяснить, что UB отсутствует в исходном коде, так что это может быть недостающая оптимизация в GCC (о чем я рекомендую вам сообщить по адресу их Bugzilla).

person yugr    schedule 02.11.2018