Мотивация для бесполезного пролога в gcc-скомпилированном main (), его отключение? - PullRequest
0 голосов
/ 30 апреля 2018

С учетом следующего минимального теста:

void exit(int);

int main() { 
    exit(0);
}

GCC 4.9 и более поздние версии с 32-разрядной целью x86 выдают что-то вроде:

main:
        leal    4(%esp), %ecx
        andl    $-16, %esp
        pushl   -4(%ecx)
        pushl   %ebp
        movl    %esp, %ebp
        pushl   %ecx
        subl    $4, %esp
        subl    $12, %esp
        pushl   $0
        call    exit

Обратите внимание на извилистый код выравнивания стека. Однако функция, переименованная во что угодно, кроме main, дает (гораздо более разумное):

xmain:
        pushl   %ebp
        movl    %esp, %ebp
        subl    $8, %esp
        subl    $12, %esp
        pushl   $0
        call    exit

Различия еще более выражены с -O. Как main ничего не меняется; переименован, это дает:

xmain:
        subl    $24, %esp
        pushl   $0
        call    exit

При ответе на этот вопрос было замечено:

Как избавиться от звонка __x86.get_pc_thunk.ax

Задокументировано ли это поведение (и его мотивация) где-нибудь, и есть ли способ его подавить? GCC имеет специфичные для цели x86 опции для установки предпочтительного / предполагаемого выравнивания входящего и исходящего стека и включения / отключения выравнивания для произвольных функций, но они, похоже, не учитываются для main.

Ответы [ 2 ]

0 голосов
/ 30 апреля 2018
main:
        leal    4(%esp), %ecx
        andl    $-16, %esp
        pushl   -4(%ecx)
        pushl   %ebp
        movl    %esp, %ebp
        pushl   %ecx
        subl    $4, %esp

В вышеприведенном разделе реплицируется вызывающий фрейм стека, который, поскольку вы не определили никаких аргументов для main (), состоит только из адреса возврата -4 (% ecx) и указателя фрейма в выровненный стек размером 16 байт; таким образом, моя WAG состоит в том, что это должно соответствовать средам выполнения (crt0.s), которые не выравнивают стек должным образом.

Толчок% ebp был чем-то вроде дешевой распродажи - он устанавливает последовательный обратный след через crt0.s, несмотря на этот батут.

Это просто "нормальный" вызов выхода с правильно выровненным стеком ...

subl    $12, %esp
pushl   $0
call    exit
0 голосов
/ 30 апреля 2018

Этот ответ основан на исходном погружении. Я не знаю, каковы были намерения или мотивы разработчиков. Весь задействованный код, похоже, датируется 2008-м годом, после моего собственного времени работы над GCC, но достаточно давно, чтобы воспоминания людей, вероятно, стали размытыми. (GCC 4.9 был выпущен в 2014 году; вы пошли дальше?) Если я прав насчет того, когда этот код был введен, неуклюжее выравнивание стека для main должно начаться в версии 4.4.)

Серверная часть GCC x86, по-видимому, была закодирована, чтобы делать сверхконсервативные предположения о выравнивании стека при входе в main, независимо от параметров командной строки. Функция ix86_minimum_incoming_stack_boundary вызывается для вычисления ожидаемого выравнивания стека при вводе для каждой функции и последней вещи, которую она делает ...

12523   /* Stack at entrance of main is aligned by runtime.  We use the
12524      smallest incoming stack boundary. */
12525   if (incoming_stack_boundary > MAIN_STACK_BOUNDARY
12526       && DECL_NAME (current_function_decl)
12527       && MAIN_NAME_P (DECL_NAME (current_function_decl))
12528       && DECL_FILE_SCOPE_P (current_function_decl))
12529     incoming_stack_boundary = MAIN_STACK_BOUNDARY;
12530 
12531   return incoming_stack_boundary;

... переопределяет ожидаемое выравнивание стека на консервативную константу, MAIN_STACK_BOUNDARY, если компилируемая функция имеет значение main. MAIN_STACK_BOUNDARY равно 128 (битам) при компиляции 64-битного кода и 32 при компиляции 32-битного кода. Насколько я могу судить, нет ручки командной строки, которая заставила бы его ожидать, что стек будет более выровнен, чем при входе в main. Я могу убедить его пропустить выравнивание стека для main, сказав, что дополнительное выравнивание не требуется, компиляция вашей тестовой программы с помощью -m32 -mpreferred-stack-boundary=2 дает мне

main:
        pushl   $0
        call    exit

с GCC 7.3.


Манипуляции только для записи %ecx кажутся ошибкой пропущенной оптимизации. Они приходят из этой части ix86_expand_prologue:

13695       /* Grab the argument pointer.  */
13696       t = plus_constant (Pmode, stack_pointer_rtx, m->fs.sp_offset);
13697       insn = emit_insn (gen_rtx_SET (crtl->drap_reg, t));
13698       RTX_FRAME_RELATED_P (insn) = 1;
13699       m->fs.cfa_reg = crtl->drap_reg;
13700       m->fs.cfa_offset = 0;
13701
13702       /* Align the stack.  */
13703       insn = emit_insn (ix86_gen_andsp (stack_pointer_rtx,
13704                                         stack_pointer_rtx,
13705                                         GEN_INT (-align_bytes)));
13706       RTX_FRAME_RELATED_P (insn) = 1;
13707 

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

Я полагаю, что разработчики GCC по крайней мере прослушивают отчет об ошибке по этому поводу, но они могут обоснованно считать его низким приоритетом, потому что это инструкции, которые выполняются только один раз за время существования всей программы. на самом деле они только мертвы, когда main не использует его аргументы, и они случаются только в традиционном 32-битном ABI, который, как мне кажется, в настоящее время считается целью второго класса.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...