Come rimuovere “noise” dall’output di GCC / clang assembly?

Voglio verificare l’output dell’assieme applicando boost::variant nel mio codice per vedere quali chiamate intermedie sono ottimizzate.

Quando compilo il seguente esempio (con GCC 5.3 usando g++ -O3 -std=c++14 -S ), sembra che il compilatore ottimizzi tutto e ritorni direttamente 100:

 (...) main: .LFB9320: .cfi_startproc movl $100, %eax ret .cfi_endproc (...) 

 #include  struct Foo { int get() { return 100; } }; struct Bar { int get() { return 999; } }; using Variant = boost::variant; int run(Variant v) { return boost::apply_visitor([](auto& x){return x.get();}, v); } int main() { Foo f; return run(f); } 

Tuttavia, l’output completo dell’assieme contiene molto più del precedente, il che a me sembra che non venga mai chiamato. C’è un modo per dire a GCC / clang di rimuovere tutto quel “rumore” e di emettere solo ciò che viene effettivamente chiamato quando viene eseguito il programma?


uscita di assembly completo:

  .file "main1.cpp" .section .rodata.str1.8,"aMS",@progbits,1 .align 8 .LC0: .string "/opt/boost/include/boost/variant/detail/forced_return.hpp" .section .rodata.str1.1,"aMS",@progbits,1 .LC1: .string "false" .section .text.unlikely._ZN5boost6detail7variant13forced_returnIvEET_v,"axG",@progbits,_ZN5boost6detail7variant13forced_returnIvEET_v,comdat .LCOLDB2: .section .text._ZN5boost6detail7variant13forced_returnIvEET_v,"axG",@progbits,_ZN5boost6detail7variant13forced_returnIvEET_v,comdat .LHOTB2: .p2align 4,,15 .weak _ZN5boost6detail7variant13forced_returnIvEET_v .type _ZN5boost6detail7variant13forced_returnIvEET_v, @function _ZN5boost6detail7variant13forced_returnIvEET_v: .LFB1197: .cfi_startproc subq $8, %rsp .cfi_def_cfa_offset 16 movl $_ZZN5boost6detail7variant13forced_returnIvEET_vE19__PRETTY_FUNCTION__, %ecx movl $49, %edx movl $.LC0, %esi movl $.LC1, %edi call __assert_fail .cfi_endproc .LFE1197: .size _ZN5boost6detail7variant13forced_returnIvEET_v, .-_ZN5boost6detail7variant13forced_returnIvEET_v .section .text.unlikely._ZN5boost6detail7variant13forced_returnIvEET_v,"axG",@progbits,_ZN5boost6detail7variant13forced_returnIvEET_v,comdat .LCOLDE2: .section .text._ZN5boost6detail7variant13forced_returnIvEET_v,"axG",@progbits,_ZN5boost6detail7variant13forced_returnIvEET_v,comdat .LHOTE2: .section .text.unlikely._ZN5boost6detail7variant13forced_returnIiEET_v,"axG",@progbits,_ZN5boost6detail7variant13forced_returnIiEET_v,comdat .LCOLDB3: .section .text._ZN5boost6detail7variant13forced_returnIiEET_v,"axG",@progbits,_ZN5boost6detail7variant13forced_returnIiEET_v,comdat .LHOTB3: .p2align 4,,15 .weak _ZN5boost6detail7variant13forced_returnIiEET_v .type _ZN5boost6detail7variant13forced_returnIiEET_v, @function _ZN5boost6detail7variant13forced_returnIiEET_v: .LFB9757: .cfi_startproc subq $8, %rsp .cfi_def_cfa_offset 16 movl $_ZZN5boost6detail7variant13forced_returnIiEET_vE19__PRETTY_FUNCTION__, %ecx movl $39, %edx movl $.LC0, %esi movl $.LC1, %edi call __assert_fail .cfi_endproc .LFE9757: .size _ZN5boost6detail7variant13forced_returnIiEET_v, .-_ZN5boost6detail7variant13forced_returnIiEET_v .section .text.unlikely._ZN5boost6detail7variant13forced_returnIiEET_v,"axG",@progbits,_ZN5boost6detail7variant13forced_returnIiEET_v,comdat .LCOLDE3: .section .text._ZN5boost6detail7variant13forced_returnIiEET_v,"axG",@progbits,_ZN5boost6detail7variant13forced_returnIiEET_v,comdat .LHOTE3: .section .text.unlikely,"ax",@progbits .LCOLDB4: .text .LHOTB4: .p2align 4,,15 .globl _Z3runN5boost7variantI3FooJ3BarEEE .type _Z3runN5boost7variantI3FooJ3BarEEE, @function _Z3runN5boost7variantI3FooJ3BarEEE: .LFB9310: .cfi_startproc subq $8, %rsp .cfi_def_cfa_offset 16 movl (%rdi), %eax cltd xorl %edx, %eax cmpl $19, %eax ja .L7 jmp *.L9(,%rax,8) .section .rodata .align 8 .align 4 .L9: .quad .L30 .quad .L10 .quad .L7 .quad .L7 .quad .L7 .quad .L7 .quad .L7 .quad .L7 .quad .L7 .quad .L7 .quad .L7 .quad .L7 .quad .L7 .quad .L7 .quad .L7 .quad .L7 .quad .L7 .quad .L7 .quad .L7 .quad .L7 .text .p2align 4,,10 .p2align 3 .L7: call _ZN5boost6detail7variant13forced_returnIiEET_v .p2align 4,,10 .p2align 3 .L30: movl $100, %eax .L8: addq $8, %rsp .cfi_remember_state .cfi_def_cfa_offset 8 ret .p2align 4,,10 .p2align 3 .L10: .cfi_restore_state movl $999, %eax jmp .L8 .cfi_endproc .LFE9310: .size _Z3runN5boost7variantI3FooJ3BarEEE, .-_Z3runN5boost7variantI3FooJ3BarEEE .section .text.unlikely .LCOLDE4: .text .LHOTE4: .globl _Z3runN5boost7variantI3FooI3BarEEE .set _Z3runN5boost7variantI3FooI3BarEEE,_Z3runN5boost7variantI3FooJ3BarEEE .section .text.unlikely .LCOLDB5: .section .text.startup,"ax",@progbits .LHOTB5: .p2align 4,,15 .globl main .type main, @function main: .LFB9320: .cfi_startproc movl $100, %eax ret .cfi_endproc .LFE9320: .size main, .-main .section .text.unlikely .LCOLDE5: .section .text.startup .LHOTE5: .section .rodata .align 32 .type _ZZN5boost6detail7variant13forced_returnIvEET_vE19__PRETTY_FUNCTION__, @object .size _ZZN5boost6detail7variant13forced_returnIvEET_vE19__PRETTY_FUNCTION__, 58 _ZZN5boost6detail7variant13forced_returnIvEET_vE19__PRETTY_FUNCTION__: .string "T boost::detail::variant::forced_return() [with T = void]" .align 32 .type _ZZN5boost6detail7variant13forced_returnIiEET_vE19__PRETTY_FUNCTION__, @object .size _ZZN5boost6detail7variant13forced_returnIiEET_vE19__PRETTY_FUNCTION__, 57 _ZZN5boost6detail7variant13forced_returnIiEET_vE19__PRETTY_FUNCTION__: .string "T boost::detail::variant::forced_return() [with T = int]" .ident "GCC: (Ubuntu 5.3.0-3ubuntu1~14.04) 5.3.0 20151204" .section .note.GNU-stack,"",@progbits 

.cfi direttive .cfi , le etichette inutilizzate e le righe di commento è un problema risolto: gli script dietro il compilatore di Matt Godbolt sono open source sul suo progetto github . Può persino eseguire l’evidenziazione dei colors per abbinare le linee di origine alle linee asm (usando le informazioni di debug).

Puoi impostarlo localmente in modo da poter alimentare i file che fanno parte del tuo progetto con tutti i percorsi #include e così via (usando -I/... ). E così puoi usarlo su un codice sorgente privato che non vuoi inviare su Internet.

CppCon2017 di Matt Godbolt parla “Qual è il mio compilatore fatto per me ultimamente? Unbolting del coperchio del compilatore “ mostra come usarlo (è piuttosto auto-esplicativo ma ha alcune caratteristiche chiare se leggi i documenti su github), e anche come leggere x86 asm , con una delicata introduzione a x86 asm stesso per i principianti assoluti, e guardare l’output del compilatore. Prosegue mostrando alcune ottimizzazioni del compilatore ordinate (ad es. Per dividere per una costante), e quale tipo di funzioni fornisce utili output di asm per guardare l’output del compilatore ottimizzato (function args, not int a = 123; ).


Con semplici gcc / clang (non g ++), -fno-asynchronous-unwind-tables evita .cfi direttive .cfi . Forse anche utile: -fno-exceptions -fno-rtti -masm=intel . Assicurati di omettere -g .

Copia / incolla questo per uso locale :

 g++ -fno-asynchronous-unwind-tables -fno-exceptions -fno-rtti -fverbose-asm \ -Wall -Wextra foo.cpp -O3 -masm=intel -S -o- | less 

Ma in realtà, consiglierei di usare Godbolt direttamente (online o installarlo localmente)! Puoi scorrere rapidamente tra le versioni di gcc e clang per vedere se vecchi o nuovi compilatori fanno qualcosa di stupido. (O cosa ICC fa, o anche cosa MSVC fa.) C’è anche ARM / ARM64 gcc 6.3 e vari gcc per PowerPC, MIPS, AVR, MSP430. (Può essere interessante vedere cosa succede su una macchina dove int è più largo di un registro, o non è a 32 bit. O su un RISC rispetto a x86).

Per C invece di C ++, usa -xc -std=gnu11 o qualcosa; il sito di explorer del compilatore fornisce solo g ++ / clang ++, non gcc / clang.


Opzioni utili del compilatore per rendere asm per il consumo umano :

  • Ricorda, il tuo codice deve solo compilare, non colbind: passare un puntatore a una funzione esterna come void ext(int*p) è un buon modo per evitare che qualcosa si estenda . Hai solo bisogno di un prototipo per questo, senza definizione, quindi il compilatore non può indicarlo o fare ipotesi su ciò che fa.

  • Ti consiglio di usare -O3 -Wall -Wextra -fverbose-asm -march=haswell ) per guardare il codice. ( -fverbose-asm può solo far apparire la fonte rumorosa, quando tutto ciò che si ottiene è temporaneo numerato come nome per gli operandi.) Quando si sta manipolando il codice sorgente per vedere come cambia l’asm, si vogliono sicuramente gli avvertimenti del compilatore abilitato. Non vuoi perdere tempo a grattarti la testa quando la spiegazione è che hai fatto qualcosa che merita un avvertimento nella fonte.

  • Per vedere come funziona la convenzione di chiamata, spesso si desidera guardare il chiamante e il destinatario senza inlining .

    Puoi usare __attribute__((noinline,noclone)) foo_t foo(bar_t x) { ... } su una definizione, o compilare con gcc -O3 -fno-inline-functions -fno-inline-functions-called-once -fno-inline-small-functions per disabilitare la gcc -O3 -fno-inline-functions -fno-inline-functions-called-once -fno-inline-small-functions di inlining. (Ma queste opzioni della riga di comando non disabilitano la clonazione di una funzione per la propagazione costante.) Vedi Dalla prospettiva del compilatore, come viene gestito il riferimento per l’array e, perché il passaggio per valore (non decadimento) non è consentito? per un esempio.

    Oppure, se vuoi solo vedere come le funzioni passano / ricevono argomenti di tipi diversi, puoi usare nomi diversi ma lo stesso prototipo in modo che il compilatore non abbia una definizione in linea. Funziona con qualsiasi compilatore.

  • -ffast-math otterrà molte funzioni libm in linea, alcune in una singola istruzione (specialmente con SSE4 disponibile per roundsd ). Alcuni saranno in linea con solo -fno-math-errno , o altre parti “più sicure” di -ffast-math , senza le parti che consentono al compilatore di arrotondare in modo diverso. Se hai il codice FP, sicuramente guardalo con / senza -ffast-math . Se non è ansible abilitare in sicurezza nessuno di -ffast-math nella propria build regolare, forse si otterrà un’idea per una modifica sicura che è ansible fare nel sorgente per consentire la stessa ottimizzazione senza -ffast-math .

  • -O3 -fno-tree-vectorize si ottimizzerà senza auto-vettorializzare , quindi puoi ottenere l’ottimizzazione completa senza se vuoi confrontarti con -O2 (che non abilita l’autovectorization su gcc, ma su clang).
  • clang srotola i loop di default, così -funroll-loops può essere utile in funzioni complesse . Puoi avere un’idea di “cosa ha fatto il compilatore” senza dover attraversare i loop srotolati. (gcc triggers -funroll-loops con -fprofile-use , ma non con -O3 ). (Questo è un suggerimento per il codice leggibile dall’uomo, non per il codice che girerebbe più velocemente.)
  • Sicuramente abilita un certo livello di ottimizzazione, a meno che tu non voglia specificamente sapere cosa -O0 fatto -O0 . Il suo requisito di “comportamento di debug prevedibile” fa sì che il compilatore memorizzi / ricarichi ogni cosa tra ogni istruzione C, così puoi modificare le variabili C con un debugger e persino “saltare” su una linea sorgente diversa all’interno della stessa funzione, e avere l’esecuzione continua come se tu fatto ciò nella fonte C. -O0 output è così rumoroso con gli store / ricaricamenti (e così lento) non solo per la mancanza di ottimizzazione, ma la de-ottimizzazione forzata per supportare il debugging .

Per ottenere un mix di sorgente e asm , usa gcc -Wa,-adhln -c -g foo.c | less gcc -Wa,-adhln -c -g foo.c | less per passare opzioni extra a as . (Altre discussioni su questo in un post del blog e un altro blog ). Nota che l’output di questo non è un input assembler valido, perché la sorgente C è lì direttamente, non come un commento assemblatore. Quindi non chiamarlo a. Un .lst potrebbe avere senso se si desidera salvarlo in un file.

L’evidenziazione dei colors di Godbolt ha uno scopo simile ed è ottima per aiutarti a vedere quando più istruzioni asm non contigue derivano dalla stessa linea sorgente. Non ho usato affatto il comando gcc per elencare, quindi IDK quanto bene e quanto sia facile per l’occhio vedere, in quel caso.

Mi piace l’alta densità del codice del riquadro asm di Godbolt, quindi non penso che mi piacerebbe avere le linee di origine mescolate. Almeno non per le funzioni semplici. Forse con una funzione che era troppo complessa per avere un controllo sulla struttura generale di ciò che fa Asm …


E ricorda, quando vuoi solo guardare l’asm, lascia fuori le costanti main() e compile-time . Volete vedere il codice per trattare una funzione arg in un registro, non per il codice dopo la propagazione costante, trasformarlo in return 42 , o almeno ottimizzare alcune cose.

La rimozione di static e / o inline dalle funzioni produrrà una definizione autonoma per loro, così come una definizione per tutti i chiamanti, quindi puoi semplicemente dare un’occhiata a ciò.

Non inserire il codice in una funzione denominata main() . gcc sa che main è speciale e presuppone che venga chiamato una sola volta, quindi lo contrassegna come “freddo” e lo ottimizza di meno.


L’altra cosa che puoi fare: se hai fatto un main() , puoi eseguirlo e usare un debugger. stepi ( si ) passi per istruzione. Vedi la parte inferiore del wiki del tag x86 per le istruzioni. Ma ricorda che il codice potrebbe essere ottimizzato via dopo averlo integrato in main con argomenti costanti in fase di compilazione.

__attribute__((noinline)) può essere d’aiuto, su una funzione che non vuoi essere inline. gcc creerà anche cloni di funzioni a propagazione costante, ovvero una versione speciale con uno degli args come costante, per i siti di chiamata che sanno di passare una costante. Il nome del simbolo sarà .clone.foo.constprop_1234 o qualcosa nell’output asm. Puoi usare __attribute__((noclone)) per disabilitare anche quello.).


Per esempio

Se vuoi vedere come il compilatore moltiplica due numeri interi: ho messo il seguente codice sul explorer del compilatore Godbolt per ottenere il gcc -O3 -march=haswell -fverbose-asm asm (da gcc -O3 -march=haswell -fverbose-asm ) per il modo sbagliato e il modo giusto per testare Questo.

 // the wrong way, which people often write when they're used to creating a runnable test-case with a main() and a printf // or worse, people will actually look at the asm for such a main() int constants() { int a = 10, b = 20; return a * b; } mov eax, 200 #, ret # compiles the same as return 200; not interesting // the right way: compiler doesn't know anything about the inputs // so we get asm like what would happen when this inlines into a bigger function. int variables(int a, int b) { return a * b; } mov eax, edi # D.2345, a imul eax, esi # D.2345, b ret 

(Questo mix di asm e C è stato realizzato a mano con il copia-incolla dell’output asm di Godbolt nel posto giusto. Trovo che sia un buon modo per mostrare come una funzione breve compila in risposte SO / rapporti / email di bug del compilatore.)

È sempre ansible esaminare l’assembly generato dal file object, anziché utilizzare l’output dell’assieme di compilatori. objdump viene in mente.

Si può anche dire a objdump di objdump l’origine con l’assembly, rendendo più facile capire quale riga di origine corrisponde a quali istruzioni. Sessione di esempio:

 $ cat test.cc int foo(int arg) { return arg * 42; } $ g++ -g -O3 -std=c++14 -c test.cc -o test.o && objdump -dS -M intel test.o test.o: file format elf64-x86-64 Disassembly of section .text: 0000000000000000 <_z3fooi>: int foo(int arg) { return arg + 1; 0: 8d 47 01 lea eax,[rdi+0x1] } 3: c3 ret 

Spiegazione dei flag objdump :

  • -d disassembla tutte le sezioni eseguibili
  • -S intermixes assembly con source ( -g richiesto durante la compilazione con g++ )
  • -M intel sceglie la syntax Intel su una brutta syntax AT & T ( opzionale )

Mi piace inserire etichette che posso facilmente annullare dall’output del componente objdump.

 int main() { asm volatile ("interesting_part_begin%=:":); do_something(); asm volatile ("interesting_part_end%=:":); } 

Non ho ancora avuto problemi con questo, ma asm volatile può essere molto difficile sull’ottimizzatore di un compilatore perché tende a lasciare intatto questo codice.