2009-02-01 20 views

risposta

8

Risposta breve: stack frames.

Risposta lunga: quando si chiama una funzione, i compilatori manipolano il puntatore dello stack per consentire dati locali come le variabili di funzione. Poiché il codice sta modificando esp, il puntatore dello stack, è quello che presumo stia accadendo qui. Avrei pensato che GCC fosse abbastanza intelligente da ottimizzarlo, laddove non è effettivamente necessario, ma potresti non utilizzare l'ottimizzazione.

1

Utilizzando GCC 4.3.2 ottengo questo per la funzione:

the_answer: 
movl $42, %eax 
ret 

... più spazzatura circostante, utilizzando la seguente riga di comando: echo 'int the_answer() { return 42; }' | gcc --omit-frame-pointer -S -x c -o - -

Quale versione stai usando?

+0

4.0.1 (Apple Inc. build 5488). Indovina che è un bug. –

+1

@Mike, non un bug. Il codice funziona bene poiché il sub è invertito dal addl. È inefficiente ma sicuramente non è un bug. – paxdiablo

+0

Potrebbe non essere un bug, potrebbe essere solo che 4.3 è più intelligente nel capire quali istruzioni sono sicure da rimuovere. – flussence

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

Il primo sub decrementa il puntatore dello stack, per creare spazio per le variabili che possono essere utilizzate nella funzione. Uno slot può essere utilizzato per il puntatore del frame, un altro per contenere l'indirizzo di ritorno, ad esempio. Hai detto che dovrebbe omettere il puntatore del frame. Questo di solito significa che omette i carichi/negozi per salvare/ripristinare il puntatore del frame. Ma spesso il codice riserva ancora memoria per questo. Il motivo è che rende il codice che analizza lo stack molto più facilmente. È facile dare all'offset dello stack una larghezza minima e quindi sai che puoi sempre accedere a FP + 0x12, per ottenere il primo slot di variabile locale, anche se ometti di salvare il puntatore del frame.

Bene, eax su x86 viene utilizzato per gestire il valore restituito al chiamante, per quanto ne so. E l'ultimo addl distrugge solo il frame creato in precedenza per la tua funzione.

Il codice che genera le istruzioni all'inizio e alla fine delle funzioni è chiamato "epilogo" e "prologo" della funzione. Ecco ciò che la mia porta fa quando si deve creare il prologo di una funzione in GCC (è modo più complesso per le porte del mondo reale che intendono essere il più veloce e versatile possibile):

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

ho omesso qualche codice che si occupa di altri problemi, in primo luogo dicendo a GCC quali sono le istruzioni importanti per la gestione delle eccezioni (cioè dove il puntatore del frame è archiviato e così via). Bene, i registri salvati con il callee sono quelli che il chiamante non ha bisogno di salvare prima di una chiamata. La funzione chiamata si preoccupa di salvarli/ripristinarli secondo necessità. Come vedi nelle prime righe, assegniamo sempre spazio per l'indirizzo di ritorno e il puntatore del frame. Quello spazio è solo pochi byte e non ha importanza. Ma noi generiamo solo i negozi/carichi quando necessario. Da notare infine che il puntatore del frame "hard" è il "vero" registro del puntatore del frame. È necessario a causa di alcune ragioni interne di gcc. Il flag "frame_pointer_needed" viene impostato da GCC, ogni volta che posso non omettere di memorizzare il frame-pointer. Per alcuni casi, deve essere memorizzato, ad esempio quando viene utilizzato alloca (modifica dinamicamente lo stackpointer). Il GCC si preoccupa di tutto questo.Nota che è passato del tempo da quando ho scritto quel codice, quindi spero che i commenti aggiuntivi che ho aggiunto sopra non siano tutti sbagliati :)

3

Allineamento pila. Alla voce di funzione, esp è -4 mod 16, a causa dell'indirizzo di ritorno che è stato spinto da call. Sottraendo 12 si riallinea. Non c'è una buona ragione per avere lo stack allineato a 16 byte su x86 tranne nel codice multimediale che utilizza mmx/sse/etc., Ma da qualche parte nell'era 3.x, gli sviluppatori di gcc hanno deciso che lo stack dovrebbe sempre essere comunque allineato, imponendo il sovraccarico di prologo/epilogo, l'aumento delle dimensioni dello stack e il conseguente aumento della cache thrashing su tutti i programmi a causa di alcuni interessi speciali (che incidentalmente capita di essere alcune delle mie aree di interesse, ma continuo a pensare che sia ingiusto e cattivo decisione).

Normalmente se si abilita qualsiasi livello di ottimizzazione, gcc rimuoverà l'inutile prologo/epilogo per l'allineamento dello stack per le funzioni foglia (funzioni che non effettuano chiamate di funzione), ma tornerà non appena si iniziano a effettuare chiamate.

È inoltre possibile risolvere il problema con -mpreferred-stack-boundary=2.

+0

+1 questa dovrebbe essere la risposta accettata! Ho provato un esempio simile usando GCC 4.8.x (MinGW), e di default nella procedura '_main' emette' andl $ -16,% esp' come parte del prologo, che allinea efficacemente il puntatore dello stack a 16 byte . Tuttavia, nessuna istruzione di questo tipo appare nella funzione chiamata '_the_answer' – Amro