2009-02-01 16 views

Respuesta

8

Respuesta corta: apilar marcos.

Respuesta larga: cuando se llama a una función, los compiladores manipularán el puntero de la pila para permitir los datos locales, como las variables de función. Dado que su código está cambiando esp, el puntero de la pila, eso es lo que supongo que está sucediendo aquí. Hubiera pensado que GCC era lo suficientemente inteligente como para optimizar esto, donde en realidad no es necesario, pero es posible que no esté usando la optimización.

1

El uso de GCC 4.3.2 consigo esto para la función:

the_answer: 
movl $42, %eax 
ret 

... además que rodea chatarra, mediante la siguiente línea de comandos: echo 'int the_answer() { return 42; }' | gcc --omit-frame-pointer -S -x c -o - -

Qué versión está usando?

+0

4.0.1 (Apple Inc. compilación 5488). Supongo que es un error. –

+1

@Mike, no es un error. El código funciona bien ya que el addl invierte el subl. Es ineficiente, pero definitivamente no es un error. – paxdiablo

+0

Puede que no sea un error, podría ser que 4.3 sea más inteligente al descubrir qué instrucciones son seguras para eliminar. – flussence

4
_the_answer: 
    subl $12, %esp 
    movl $42, %eax 
    addl $12, %esp 
    ret 

El primer subl disminuye el puntero de la pila, para dejar espacio a las variables que pueden usarse en su función. Se puede usar una ranura para el puntero de marco, otra para sostener la dirección de retorno, por ejemplo. Dijiste que debería omitir el puntero del marco. Eso generalmente significa que omite cargas/tiendas para guardar/restaurar el puntero del marco. Pero a menudo el código aún reservará memoria para ello. La razón es que hace que el código que analiza la pila sea mucho más fácil. Es fácil dar al desplazamiento de la pila un ancho mínimo y así usted sabe que siempre puede acceder a FP + 0x12, para llegar al primer espacio variable local, incluso si omite guardar el puntero del marco.

Bueno, eax en x86 se utiliza para manejar el valor de retorno de la persona que llama, hasta donde yo sé. Y el último complemento simplemente destruye el marco creado previamente para su función.

El código que genera las instrucciones al inicio y al final de las funciones se denomina "epílogo" y "prólogo" de la función. Esto es lo que hace mi puerto cuando se tiene que crear el prólogo de una función en GCC (que es mucho más compleja para los puertos del mundo real que tienen la intención de ser lo más rápido y más versátil posible):

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)); 
    } 
} 

omití algunos código que trata con otros problemas, principalmente con decirle a GCC cuáles son las instrucciones importantes para el manejo de excepciones (es decir, dónde se almacena el puntero del marco, etc.). Bueno, los registros guardados en línea son los que la persona que llama no necesita guardar antes de una llamada. La función llamada se preocupa por guardar/restaurarlos según sea necesario. Como puede ver en las primeras líneas, siempre asignamos espacio para la dirección de retorno y el puntero del marco. Ese espacio es solo unos pocos bytes y no importará. Pero solo generamos las tiendas/cargas cuando sea necesario. Finalmente, tenga en cuenta que el puntero de marco "duro" es el registro de puntero de marco "real". Es necesario debido a algunas razones internas de gcc. El indicador "frame_pointer_needed" es establecido por GCC, siempre que pueda no omitiendo el almacenamiento del puntero de marco. En algunos casos, debe almacenarse, por ejemplo, cuando se usa alloca (cambia el stackpointer de forma dinámica). GCC se preocupa por todo eso.Tenga en cuenta que ha pasado un tiempo desde que escribí ese código, así que espero que los comentarios adicionales que agregué arriba no sean todos incorrectos :)

3

Alineación de la pila. En la entrada de la función, esp es -4 mod 16, debido a que la dirección de retorno ha sido presionada por call. Restando 12 lo vuelve a alinear. No hay una buena razón para tener la pila alineada a 16 bytes en x86 excepto en el código multimedia que usa mmx/sse/etc., Pero en algún lugar de la era 3.x, los desarrolladores de gcc decidieron que la pila siempre debería mantenerse alineada de todos modos, imponiendo sobrecarga de prólogo/epílogo, aumento en el tamaño de la pila y aumento de caché en todos los programas por el bien de unos pocos intereses especiales (que casualmente son algunas de mis áreas de interés, pero aún creo que fue injusto y malo decisión).

Normalmente, si habilita cualquier nivel de optimización, gcc eliminará el prólogo/epílogo inútil para la alineación de la pila para funciones de hoja (funciones que no hacen llamadas de función), pero volverá tan pronto como comience a hacer llamadas.

También puede solucionar el problema con -mpreferred-stack-boundary=2.

+0

+1 ¡esta debería ser la respuesta aceptada! Intenté un ejemplo similar utilizando GCC 4.8.x (MinGW) y, de forma predeterminada, en el procedimiento '_main', emite' andl $ -16,% esp' como parte del prólogo, que también alinea efectivamente el puntero de la pila a 16 bytes . Sin embargo, tal instrucción no aparece en la función llamada '_the_answer' – Amro

Cuestiones relacionadas