Je souhaite inspecter la sortie Assembly de l'application de boost::variant
dans mon code afin de voir quels appels intermédiaires sont optimisés.
Lorsque je compile l'exemple suivant (avec GCC 5.3 en utilisant g++ -O3 -std=c++14 -S
), il semble que le compilateur optimise tout et renvoie directement 100:
(...)
main:
.LFB9320:
.cfi_startproc
movl $100, %eax
ret
.cfi_endproc
(...)
#include <boost/variant.hpp>
struct Foo
{
int get() { return 100; }
};
struct Bar
{
int get() { return 999; }
};
using Variant = boost::variant<Foo, Bar>;
int run(Variant v)
{
return boost::apply_visitor([](auto& x){return x.get();}, v);
}
int main()
{
Foo f;
return run(f);
}
Cependant, la sortie complète de l'assemblage contient beaucoup plus que l'extrait ci-dessus, qui, à mes yeux, ne semble jamais être appelé. Existe-t-il un moyen de dire à GCC/clang de supprimer tout ce "bruit" et de simplement afficher ce qui est réellement appelé lorsque le programme est exécuté?
sortie complète de l'assemblage:
.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
Supprimer les directives .cfi
, Les étiquettes inutilisées et les lignes de commentaires est un problème résolu: les scripts derrière Explorateur du compilateur de Matt Godbolt sont open source sur son projet github . Il peut même mettre en évidence les couleurs pour faire correspondre les lignes source aux lignes asm (en utilisant les informations de débogage).
Vous pouvez le configurer localement afin de lui donner des fichiers qui font partie de votre projet avec tous les chemins #include
Et ainsi de suite (en utilisant -I/...
). Et vous pouvez donc l'utiliser sur du code source privé que vous ne voulez pas envoyer sur Internet.
Conférence CppCon2017 de Matt Godbolt "Qu'est-ce que mon compilateur a fait pour moi récemment? Déboulonner le couvercle du compilateur" montre comment l'utiliser (c'est assez explicite mais a quelques fonctionnalités intéressantes si vous lisez les documents sur github), et aussi comment lire x86 asm , avec une introduction en douceur à x86 asm lui-même pour les débutants, et regarder la sortie du compilateur. Il continue en montrant des optimisations de compilateur soignées (par exemple pour la division par une constante), et quel type de fonctions donnent une sortie asm utile pour regarder la sortie de compilateur optimisée (args de fonction, pas int a = 123;
).
Avec gcc/clang simple (pas g ++), -fno-asynchronous-unwind-tables
Évite les directives .cfi
. Peut-être aussi utile: -fno-exceptions -fno-rtti
-masm=intel
. Assurez-vous d'omettre -g
.
Copiez/collez ceci pour une utilisation locale :
g++ -fno-asynchronous-unwind-tables -fno-exceptions -fno-rtti -fverbose-asm \
-Wall -Wextra foo.cpp -O3 -masm=intel -S -o- | less
Mais vraiment, je recommanderais d'utiliser Godbolt directement (en ligne ou de le configurer localement)! Vous pouvez rapidement basculer entre les versions de gcc et de clang pour voir si les anciens ou les nouveaux compilateurs font quelque chose de stupide. (Ou ce que fait ICC, ou même ce que fait MSVC.) Il y a même ARM/ARM64 gcc 6.3, et divers gcc pour PowerPC, MIPS, AVR, MSP430. (Il peut être intéressant de voir ce que se produit sur une machine où int
est plus large qu'un registre, ou n'est pas 32 bits. Ou sur un RISC vs x86).
Pour C au lieu de C++, utilisez -xc -std=gnu11
Ou quelque chose; le site de l'explorateur du compilateur fournit uniquement g ++/clang ++, pas gcc/clang. (Ou vous pouvez utiliser le mode C dans la liste déroulante du langage, mais cela a une sélection différente de compilateurs qui est généralement plus limitée. Et cela réinitialise votre volet source, il est donc plus difficile de basculer entre C et C++.)
Options de compilation utiles pour créer asm pour la consommation humaine :
Rappelez-vous, votre code n'a qu'à compiler, pas à lier: passer un pointeur vers une fonction externe comme void ext(int*p)
est un bon moyen d'empêcher quelque chose de s'optimiser . Vous n'avez besoin que d'un prototype pour cela, sans définition, de sorte que le compilateur ne peut pas l'inclure ou faire des hypothèses sur ce qu'il fait.
Je recommanderais d'utiliser -O3 -Wall -Wextra -fverbose-asm -march=haswell
) Pour consulter le code. (-fverbose-asm
Peut simplement rendre la source bruyante, cependant, lorsque tout ce que vous obtenez est numéroté en tant que noms pour les opérandes.) Lorsque vous tripotez la source pour voir comment elle modifie l'asm, vous definitely souhaite que les avertissements du compilateur soient activés. Vous ne voulez pas perdre de temps à vous gratter la tête lorsque vous expliquez que vous avez fait quelque chose qui mérite un avertissement dans la source.
Pour voir comment fonctionne la convention d'appel, , vous voulez souvent regarder l'appelant et l'appelé sans les insérer .
Vous pouvez utiliser __attribute__((noinline,noclone)) foo_t foo(bar_t x) { ... }
sur une définition, ou compiler avec gcc -O3 -fno-inline-functions -fno-inline-functions-called-once -fno-inline-small-functions
Pour désactiver l'inline. (Mais ces options de ligne de commande ne désactivent pas le clonage d'une fonction pour une propagation constante.) Voir Du point de vue du compilateur, comment la référence pour le tableau est-elle traitée, et pourquoi le passage par la valeur (pas la décroissance) n'est pas autorisé? pour un exemple.
Ou si vous voulez simplement voir comment les fonctions passent/reçoivent des arguments de différents types, vous pouvez utiliser des noms différents mais le même prototype afin que le compilateur n'ait pas de définition à aligner. Cela fonctionne avec n'importe quel compilateur.
-ffast-math
Obtiendra de nombreuses fonctions libm en ligne, certaines en une seule instruction (en particulier avec SSE4 disponible pour roundsd
). Certains s'aligneront avec juste -fno-math-errno
, Ou d'autres parties "plus sûres" de -ffast-math
, Sans les parties qui permettent au compilateur d'arrondir différemment. Si vous avez du code FP, regardez-le avec/sans -ffast-math
. Si vous ne pouvez pas activer en toute sécurité -ffast-math
Dans votre build normal, peut-être vous aurez une idée d'un changement sûr que vous pouvez faire dans la source pour permettre la même optimisation sans -ffast-math
.
-O3 -fno-tree-vectorize
Optimisera sans vectorisation automatique , vous pouvez donc obtenir une optimisation complète sans si vous voulez comparer avec -O2
(qui n'active pas l'autovectorisation sur gcc, mais le fait sur clang).-funroll-loops
peut être utile dans les fonctions complexes . Vous pouvez avoir une idée de "ce que le compilateur a fait" sans avoir à parcourir les boucles déroulées. (gcc active -funroll-loops
avec -fprofile-use
, mais pas avec -O3
). (Il s'agit d'une suggestion de code lisible par l'homme, pas de code qui s'exécuterait plus rapidement.)-O0
A fait . Son exigence de "comportement de débogage prévisible" fait que le compilateur stocke/recharge tout entre chaque instruction C, vous pouvez donc modifier les variables C avec un débogueur et même "sauter" vers une ligne source différente dans la même fonction, et faire continuer l'exécution comme si vous a fait cela dans la source C. La sortie de -O0
Est si bruyante avec les magasins/rechargements (et si lente) non seulement par manque d'optimisation, mais désoptimisation forcée pour prendre en charge le débogage . Pour obtenir un mélange de source et d'asm , utilisez gcc -Wa,-adhln -c -g foo.c | less
pour passer des options supplémentaires à as
. (Plus de discussion à ce sujet dans n article de blog , et n autre blog .). Notez que la sortie de ceci n'est pas une entrée d'assembleur valide, car la source C est là directement, pas en tant que commentaire d'assembleur. Alors ne l'appelez pas un .s
. Un .lst
Peut être utile si vous souhaitez l'enregistrer dans un fichier.
Le surlignage des couleurs de Godbolt sert un objectif similaire et est excellent pour vous aider à voir lorsque plusieurs instructions non contiguës asm proviennent de la même ligne source. Je n'ai pas du tout utilisé cette commande de listage gcc, donc IDK à quel point elle fonctionne bien et à quel point il est facile à voir pour les yeux, dans ce cas.
J'aime la haute densité de code du volet asm de godbolt, donc je ne pense pas que j'aimerais avoir des lignes sources mélangées. Du moins pas pour des fonctions simples. Peut-être avec une fonction qui était trop complexe pour avoir une idée de la structure globale de ce que fait l'asm ...
Et rappelez-vous, lorsque vous souhaitez simplement regarder l'asm, omettez la main()
et les constantes de compilation . Vous voulez voir le code pour traiter une fonction arg dans un registre, pas pour le code après que la propagation constante le transforme en return 42
, Ou au moins optimise certaines choses.
La suppression de static
et/ou inline
des fonctions produira une définition autonome pour elles, ainsi qu'une définition pour tous les appelants, vous pouvez donc simplement regarder cela.
Ne mettez pas votre code dans une fonction appelée main()
. gcc sait que main
est spécial et suppose qu'il ne sera appelé qu'une seule fois, donc il le marque comme "froid" et l'optimise moins.
L'autre chose que vous pouvez faire: si vous avez créé une main()
, vous pouvez l'exécuter et utiliser un débogueur. stepi
(si
) étapes par instruction. Voir le bas du x86tag wiki pour les instructions. Mais rappelez-vous que le code peut s'optimiser après s'être inséré dans main avec des arguments constants au moment de la compilation.
__attribute__((noinline))
peut aider, sur une fonction que vous ne souhaitez pas inclure. gcc créera également des clones de fonctions à propagation constante, c'est-à-dire une version spéciale avec l'un des arguments comme constante, pour les sites d'appel qui savent qu'ils passent une constante. Le nom du symbole sera .clone.foo.constprop_1234
Ou quelque chose dans la sortie asm. Vous pouvez également utiliser __attribute__((noclone))
pour désactiver cela.).
Si vous voulez voir comment le compilateur multiplie deux entiers: je mets le code suivant sur l'explorateur du compilateur Godbolt pour obtenir l'asm (de gcc -O3 -march=haswell -fverbose-asm
) Pour la mauvaise et la bonne façon pour tester cela.
// 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
(Ce mélange d'asm et de C a été conçu à la main en copiant-collant la sortie asm de godbolt au bon endroit. Je trouve que c'est un bon moyen de montrer comment une fonction courte se compile dans SO réponses/rapports de bogue/e-mails du compilateur.)
Vous pouvez toujours regarder l'assembly généré à partir du fichier objet, au lieu d'utiliser la sortie d'assembly des compilateurs. objdump
me vient à l'esprit.
Vous pouvez même dire à objdump
de mélanger la source avec Assembly, ce qui permet de déterminer plus facilement quelle ligne source correspond à quelles instructions. Exemple de session:
$ 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
Explication des drapeaux objdump
:
-d
démonte toutes les sections exécutables-S
mélange l'assemblage avec la source (-g
requis lors de la compilation avec g++
)-M intel
choisit la syntaxe Intel plutôt que la laide syntaxe AT&T (facultatif)J'aime insérer des étiquettes que je peux facilement extraire de la sortie objdump.
int main() {
asm volatile ("interesting_part_begin%=:":);
do_something();
asm volatile ("interesting_part_end%=:":);
}
Je n'ai pas encore eu de problème avec ça, mais asm volatile
peut être très difficile pour l'optimiseur d'un compilateur car il a tendance à laisser ce code intact.