如何从GCC / clang程序集输出中删除“噪音”?

我想检查在我的代码中应用boost::variant的程序集输出,以查看哪些中间调用被优化了。

当我编译下面的例子(GCC 5.3使用g++ -O3 -std=c++14 -S )时,好像编译器优化了所有东西,直接返回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); } 

但是,完整的程序集输出包含的内容比上面摘录的要多得多,对我来说这看起来是从来没有被调用的。 有没有办法告诉GCC /铿锵删除所有的“噪音”,只是输出程序运行时实际调用?


全装配输出:

  .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指令,未使用的标签和注释行是一个解决的问题: Matt Godbolt编译器资源管理器背后的脚本是其github项目的开源代码。 它甚至可以做颜色突出显示来匹配源代码行到汇编行(使用调试信息)。

你可以在本地设置它,这样你就可以使用所有的#include路径等等(使用-I/... )为它提供文件。 所以你可以使用私人的源代码,你不想通过互联网发送。

Matt在CppCon2017上做了一个演讲:“我的编译器最近为我做了什么? 解开编译器的盖子“ , 展示如何使用它 ,以及如何读取x86 asm ,并为初学者进行温和的介绍。 他显示了一些简洁的编译器优化(例如,用一个常量除),以及什么样的函数可以提供有用的asm输出来查看它。


用简单的gcc / clang, -fno-asynchronous-unwind-tables避免了.cfi指令。 可能也有用: -fno-exceptions -fno-rtti -masm=intel 。 确保省略-g

复制/粘贴本地使用

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

但是真的,我建议直接使用Godbolt(在线或在本地设置)! 你可以在gcc和clang的版本之间快速翻转,看看旧的或新的编译器是否做了一些愚蠢的事情。 (或者ICC做什么,甚至MSVC做什么。甚至有ARM / ARM64 gcc 6.3,以及用于PowerPC,MIPS,AVR,MSP430的各种gcc。 (有趣的是,看看int是否比寄存器更宽,或者不是32位,或者RISC与x86上有什么关系。

对于C而不是C ++,使用-xc -std=gnu11或其他东西; 编译器资源管理器网站只提供g ++ / clang ++,而不是gcc / clang。


有用的编译器选项用于为人类消费提供asm

  • 我建议使用-O3 -Wall -Wextra -fverbose-asm -march=haswell )来查看代码。 ( -fverbose-asm可以使源代码看起来很嘈杂,但是当你所得到的都是作为操作数名称的临时编号时)。当你摆弄源代码来看它是如何改变asm的,你一定要编译器警告启用。 当你解释说你做了一些值得警告的事情的时候,你不想浪费时间在脑海里摸索。

  • -ffast-math会得到许多libm函数来内联,有些可能只有一个指令(尤其是SSE4可用于roundsd )。 有些内联只是-fno-math-errno或者其他“更安全”的部分,而没有允许编译器四舍五入的部分。 如果你有FP代码,一定要看看它有没有-ffast-math 。 如果你不能在日常的构建中安全地启用任何一种-ffast-math ,也许你会想到一个安全的变化,你可以在源代码中进行安全的更改,以便在没有-ffast-math情况下进行相同的优化。

  • -O3 -fno-tree-vectorize将会在没有自动-O3 -fno-tree-vectorize情况下进行优化,如果你想和-O2进行比较(它不能在gcc上启用自动插件,但是在clang IIRC上)。
  • clang默认展开循环,所以-funroll-loops在复杂函数中可能很有用。 你可以感觉到“编译器做了什么”,而不必通过展开的循环。 (gcc使用-fprofile-use -funroll-loops ,但不使用-O3 )。 (这是对人类可读代码的建议,而不是对运行速度更快的代码。)
  • 绝对启用某种程度的优化,除非你特别想知道-O0做了什么 。 其“可预测的调试行为”要求使得编译器可以在每个C语句之间存储/重新加载所有内容,因此您可以使用调试器修改C变量,甚至在同一个函数中跳转到不同的源代码行,并继续执行在C源代码中做到了这一点。 -O0输出对于存储/重载(以及如此缓慢)非常嘈杂,不仅因为缺乏优化,而且强制去优化以支持调试 。

要混合使用源代码和asm ,请使用gcc -Wa,-adhln -c -g foo.c | less gcc -Wa,-adhln -c -g foo.c | less通过额外的选择as 。 (在博客文章和另一个博客中更多地讨论这个)。 请注意,这个输出不是有效的汇编输入,因为C源代码是直接存在的,而不是汇编注释。 所以不要说它是一个。 .lst可能是有意义的,如果你想保存到一个文件。

Godbolt的颜色突出显示也有类似的用途,在帮助您看到多个非连续的 asm指令来自同一源代码行时非常有用。 我根本没有使用过gcc的列表命令,所以在这种情况下,IDK的功能有多好,眼睛看起来多么容易。

我喜欢godbolt的asm面板的高代码密度,所以我不认为我需要混合源代码行。至少不是简单的功能。 也许有一个太复杂的函数来处理asm的整体结构…


记住,当你只想看看asm时,就省略了main()和编译时常量 。 你想看看处理一个寄存器中的函数arg的代码,而不是常量传播将代码转换return 42的代码,或者至少优化掉一些东西。

从函数中去除static和/或inline将为它们产生一个独立的定义,以及任何调用者的定义,所以你可以看看这个定义。

不要把你的代码放在一个名为main()的函数中 。 海湾合作委员会知道, main是特殊的,并假定它只会被调用一次,所以它标记为“冷”,优化少。


另一件事你可以做:如果你做了一个main() ,你可以运行它并使用一个调试器。 stepisi )按指令步骤。 有关说明,请参阅x86 标记wiki的底部。 但是请记住,代码可能会在使用编译时常量参数内联到main之后进行优化。

__attribute__((noinline))可能有助于您不想内联的函数。 gcc也会做常量传播克隆的函数,也就是一个特殊的版本,其中一个参数是一个常数,对于那些知道他们正在传递一个常量的调用站点。 符号名称将是.clone.foo.constprop_1234或asm输出中的某个东西。 你也可以使用__attribute__((noclone))来禁用它。)。


例如 ,如果你想看看编译器如何乘以两个整数:我把下面的代码放在Godbolt上 ,以错误的方式得到asm(从gcc -O3 -march=haswell -fverbose-asm )测试这个。

 // 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 

(asm和C的混合是通过将godbolt的asm输出复制到正确的地方来手工制作的)

您始终可以从对象文件中查看生成的程序集,而不是使用编译器程序集输出。 objdump在脑海中。

你甚至可以告诉objdump混合源程序集,使得更容易找出什么源代码行对应于什么指令。 示例会话:

 $ 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 

objdump标志的解释:

  • -d反汇编所有可执行部分
  • -S与源代码汇编(用g++编译时需要-g
  • -M intel在丑陋的AT&T语法中选择了intel语法( 可选

我喜欢插入标签,我可以很容易地grep出objdump输出。

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

我还没有遇到过这个问题,但是在编译器的优化器中, asm volatile会非常困难,因为它往往会使这些代码不变。