Почему в моем выводе gcc есть дополнительные инструкции? - PullRequest
4 голосов
/ 01 февраля 2009

GCC компилирует (используя gcc --omit-frame-pointer -s):

    int the_answer() { return 42; }

в

            .Text
    .globl _the_answer
    _the_answer:
        subl    $12, %esp
        movl    $42, %eax
        addl    $12, %esp
        ret
       .subsections_via_symbols

Что здесь делает константа $ 12 и что такое регистр% esp?

Ответы [ 4 ]

8 голосов
/ 01 февраля 2009

Краткий ответ: стек кадров.

Длинный ответ: когда вы вызываете функцию, компиляторы будут манипулировать указателем стека для учета локальных данных, таких как переменные функции. Так как ваш код меняет esp, указатель стека, это то, что я предполагаю, что происходит здесь. Я бы подумал, что GCC достаточно умен, чтобы оптимизировать это там, где это на самом деле не требуется, но вы, возможно, не используете оптимизацию.

4 голосов
/ 01 февраля 2009
_the_answer:
    subl    $12, %esp
    movl    $42, %eax
    addl    $12, %esp
    ret

Первый subl уменьшает указатель стека, чтобы освободить место для переменных, которые могут использоваться в вашей функции. Один слот может использоваться для указателя кадра, другой - для хранения адреса возврата, например. Вы сказали, что он должен опустить указатель кадра. Обычно это означает, что он не загружает / сохраняет для сохранения / восстановления указателя кадра. Но часто код все еще резервирует память для этого. Причина в том, что он делает код, который анализирует стек, намного проще. Легко задать минимальное значение смещения стека, поэтому вы всегда можете получить доступ к FP + 0x12, чтобы получить первый слот локальной переменной, даже если вы не сохраняете указатель кадра.

Ну, eax на x86 используется для обработки возвращаемого значения вызывающей стороне, насколько я знаю. И последний addl просто уничтожает ранее созданный фрейм для вашей функции.

Код, который генерирует инструкции в начале и конце функций, называется «эпилог» и «пролог» функции. Вот что делает мой порт, когда ему нужно создать пролог функции в GCC (это намного сложнее для реальных портов, которые намереваются быть максимально быстрыми и универсальными):

void eco32_prologue(void) {
    int i, j;
    /* reserve space for all callee saved registers, and 2 additional ones:
     * for the frame pointer and return address */
    int regs_saved = registers_to_be_saved() + 2;
    int stackptr_off = (regs_saved * 4 + get_frame_size());

    /* decrement the stack pointer */
    emit_move_insn(stack_pointer_rtx, 
                   gen_rtx_MINUS(SImode, stack_pointer_rtx, 
                                 GEN_INT(stackptr_off)));

    /* save return adress, if we need to */
    if(eco32_ra_ever_killed()) {
        /* note: reg 31 is return address register */
        emit_move_insn(gen_rtx_MEM(SImode, 
                           plus_constant(stack_pointer_rtx, 
                                         -4 + stackptr_off)),  
                       gen_rtx_REG(SImode, 31));
    }

    /* save the frame pointer, if it is needed */
    if(frame_pointer_needed) {
        emit_move_insn(gen_rtx_MEM(SImode, 
                           plus_constant(stack_pointer_rtx, 
                                         -8 + stackptr_off)), 
                       hard_frame_pointer_rtx);
    }

    /* save callee save registers */
    for(i=0, j=3; i<FIRST_PSEUDO_REGISTER; i++) {
        /* if we ever use the register, and if it's not used in calls
         * (would be saved already) and it's not a special register */
        if(df_regs_ever_live_p(i) && 
           !call_used_regs[i] && !fixed_regs[i]) {
            emit_move_insn(gen_rtx_MEM(SImode, 
                               plus_constant(stack_pointer_rtx, 
                                             -4 * j + stackptr_off)), 
                           gen_rtx_REG(SImode, i));
            j++;
        }
    }

    /* set the new frame pointer, if it is needed now */
    if(frame_pointer_needed) {
        emit_move_insn(hard_frame_pointer_rtx, 
                       plus_constant(stack_pointer_rtx, stackptr_off));
    }
}

Я пропустил некоторый код, который имеет дело с другими проблемами, в первую очередь с сообщением GCC, какие инструкции важны для обработки исключений (то есть, где хранится указатель кадра и т. Д.). Ну, регистры, сохраненные вызываемым абонентом, - это те, которые вызывающему абоненту не нужно сохранять перед вызовом. Вызываемая функция заботится о сохранении / восстановлении их по мере необходимости. Как видно из первых строк, мы всегда выделяем место для адреса возврата и указателя кадра. Это пространство занимает всего несколько байтов и не имеет значения. Но мы генерируем магазины / загружаем только при необходимости. В заключение отметим, что «жесткий» указатель кадра является «реальным» регистром указателя кадра. Это необходимо из-за некоторых внутренних причин gcc. Флаг "frame_pointer_needed" устанавливается GCC, всякий раз, когда я могу не опустить сохранение указателя кадра. В некоторых случаях его необходимо сохранить, например, когда используется alloca (динамически изменяет указатель стека). GCC заботится обо всем этом. Обратите внимание, что прошло некоторое время с тех пор, как я написал этот код, поэтому я надеюсь, что дополнительные комментарии, которые я добавил выше, не все не так:)

3 голосов
/ 30 марта 2011

Выравнивание стека. При входе в функцию esp равен -4 mod 16 из-за того, что адрес возврата был нажат call. Вычитая 12 повторно выравнивает его. Нет веской причины для выравнивания стека на 16 байтов в x86, за исключением мультимедийного кода, использующего mmx / sse / и т. Д., Но где-то в эпоху 3.x разработчики gcc решили, что в любом случае стек всегда должен быть выровнен накладные расходы на пролог / эпилог, увеличенный размер стека и, как следствие, увеличенный объем кэш-памяти во всех программах ради нескольких специальных целей (что, кстати, случайно является одной из моих сфер интересов, но я все еще думаю, что это было несправедливо и плохо решение).

Обычно, если вы включите какой-либо уровень оптимизации, gcc удалит бесполезный пролог / эпилог для выравнивания стека для конечных функций (функций, которые не вызывают вызовов функций), но вернется, как только вы начнете делать вызовы.

Вы также можете решить проблему с помощью -mpreferred-stack-boundary=2.

1 голос
/ 01 февраля 2009

Используя GCC 4.3.2 я получаю это для функции:

the_answer:
movl    $42, %eax
ret

... плюс окружающий мусор, используя следующую командную строку: echo 'int the_answer() { return 42; }' | gcc --omit-frame-pointer -S -x c -o - -

Какую версию вы используете?

...