Сборка Intel ICC 2018 и GCC 8: различие между началом и окончанием стека - PullRequest
0 голосов
/ 22 мая 2018

icc и gcc производят несколько разные сборки, в частности инициализация стека и очистка функции (основной).

Инициализация:

icc:

push   %rbp
mov    %rsp,%rbp
and    $0xffffffffffffff80,%rsp
sub    $0x80,%rsp

gcc:

push   %rbp
mov    %rsp,%rbp
sub    $0x10,%rsp

Окончание:

icc:

xor    %eax,%eax
mov    %rbp,%rsp
pop    %rbp
retq
nopl   0x0(%rax)

gcc:

mov    $0x0,%eax
leaveq
retq

Может ли кто-нибудь объяснить эти различия?Я могу понять код gcc, но icc более загадочный.Разница фактическая или произвольная?(например, повторная инициализация %rax при очистке).

Код C ++ и Makefile (для воспроизведения):

#include <iostream>
using namespace std;
int main()
{
    int x = 3;
    x *= x;
    cout << x << endl;
    x = 3;
    int y = x + x;
    int z = x + 3;
    cout << (x * y) + (z / z) << endl;
}

, а затем Makefile:

build: code.cpp
    icpc code.cpp -o code_i
    objdump -d code_i > code.icc
    g++ code.cpp -o code_g
    objdump -d code_g > code.gcc
    diff code.icc code.gcc > code.diff

Полные файлы: на github

1 Ответ

0 голосов
/ 23 мая 2018

повторная инициализация% rax при очистке.

Оба компилятора обнуляют EAX, потому что в C ++ (и C99) int main() имеет неявное return 0; в нижней части функции.

gcc ищет только оптимизацию глазка xor-zeroing при -O2 и выше, , но вы компилируете по умолчанию gcc -O0 (режим отладки /нет оптимизации / даже не храните переменные в регистрах между операторами.) ICC (и clang) используют обнуление xor даже при -O0.

Оба mov $0, %eax и xor %eax,%eax "инициализируют" RAX, т.е.сломать любую зависимость от старого значения.mov $0, %eax - неэффективный способ.

(или без -g ICC может по умолчанию установить значение -O2, но это не меняет выбор пролога / эпилога. Он по-прежнему устанавливает режим быстрой математики ивызывает специальные функции Intel для инициализации в верхней части main. Вы можете более легко просмотреть вывод asm компилятора в проводнике компилятора Godbolt . Он неявно передает -g, поэтому ICC там определенно по умолчанию равен -O0.)


push   %rbp
mov    %rsp,%rbp

Это создает кадр стека с RBP.GCC -O1 и выше включает -fomit-frame-pointer, поэтому gcc не будет тратить впустую инструкции на это в обычной функции.

ICC все еще создает кадр стека в main, потому что он хочет выровнять стек с помощью128. (И кадр стека - это самый простой способ восстановить стек в конце main после неизвестного смещения, поэтому main может вернуться).


# ICC stack over-alignment code:
and   $0xffffffffffffff80,%rsp   # round RSP down to the next multiple of 128
sub   $0x80,%rsp                 # and reserve 128 bytes
      # missed optimization: add $-0x80, %rsp could use an imm8 instead of imm32

Я не знаю, почему ICC выравнивает стек в main.128 - это размер красной зоны в x86-64 SysV ABI, но это может быть совпадением.Это означало бы, что материал, добавленный в main, не будет беспокоиться о пересечении страниц для местных жителей в красной зоне.(Размер строки кэша составляет 64 ББ, размер страницы - 4 КБ).

ABI System V x86-64 гарантирует только 16-байтовое выравнивание стека, поэтому будущие вызовы функций не сохранят 128-байтовое выравнивание.(GCC не выравнивает стек, потому что main уже вызывается с 16-байтовым выравниванием стека.)

Если бы вы выбрали любое другое имя функции вместо main, вы бы нея не вижу много странных вещей.


sub    $0x10,%rsp

GCC резервирует 16 байтов стекового пространства для int x,y,z (и поддерживает выравнивание стека 16 байт после push rbp).int занимает 4 байта в x86-64 SysV ABI. GCC хранит их в памяти, потому что вы скомпилировали с отключенной оптимизацией.

Если бы вы скомпилировали с -O2, g ++ сохранил бы переменную в регистрах и использовал только sub $8, %rsp для выравниваниястек на 16 после ввода функции (вместо нажатия чего-либо).

Или с -mtune=haswell или чем-то, я думаю, что недавний gcc мог бы push %rax вместо использования sub для выравнивания стека.


leave против mov %rbp,%rsp / pop %rbp.

GCC предпочитает leave для удаления кадра стека, если RSP еще не указывает насохраненное значение RBP.(В противном случае он просто использует pop rbp).

leave - 3 мопа, согласно тестированию Agner Fog на процессорах Intel, но это может включать в себя стековую синхронизацию при тестировании спина к спине.Я не проверил себя.mov / pop - всего 2 мопа.

leave мне кажется хорошим выбором для оптимизации;IDK, если в этом случае выбор настроек Intel остался от более старых процессоров, где многопользовательские инструкции могли бы более легко вызвать проблемы с декодированием, или если они действительно протестировали и обнаружили, что для Haswell / Skylake лучше всего две отдельные инструкции.


В конце концов, что такое nopl 0x0(%rax)?... Я думал, что retq была последней инструкцией main.

RET является последней инструкцией в main.Long-NOP является частью дополнения между функциями из директивы .p2align 4 компилятора.

...