_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 заботится обо всем этом. Обратите внимание, что прошло некоторое время с тех пор, как я написал этот код, поэтому я надеюсь, что дополнительные комментарии, которые я добавил выше, не все не так:)