assembly 如何消除GCC/clang汇编输出中的“噪音”?

bqjvbblv  于 2023-10-19  发布在  其他
关注(0)|答案(3)|浏览(124)

我想检查在代码中应用boost::variant的汇编输出,以查看哪些中间调用被优化掉了。
当我编译下面的例子时(使用GCC 5.3使用g++ -O3 -std=c++14 -S),似乎编译器优化了所有内容并直接返回100:

  1. (...)
  2. main:
  3. .LFB9320:
  4. .cfi_startproc
  5. movl $100, %eax
  6. ret
  7. .cfi_endproc
  8. (...)
  1. #include <boost/variant.hpp>
  2. struct Foo
  3. {
  4. int get() { return 100; }
  5. };
  6. struct Bar
  7. {
  8. int get() { return 999; }
  9. };
  10. using Variant = boost::variant<Foo, Bar>;
  11. int run(Variant v)
  12. {
  13. return boost::apply_visitor([](auto& x){return x.get();}, v);
  14. }
  15. int main()
  16. {
  17. Foo f;
  18. return run(f);
  19. }

然而,完整的程序集输出包含的内容远不止上面的摘录,在我看来,它似乎从未被调用过。有没有办法告诉GCC/clang删除所有的“噪音”,只输出程序运行时实际调用的内容?

完整汇编输出:

  1. .file "main1.cpp"
  2. .section .rodata.str1.8,"aMS",@progbits,1
  3. .align 8
  4. .LC0:
  5. .string "/opt/boost/include/boost/variant/detail/forced_return.hpp"
  6. .section .rodata.str1.1,"aMS",@progbits,1
  7. .LC1:
  8. .string "false"
  9. .section .text.unlikely._ZN5boost6detail7variant13forced_returnIvEET_v,"axG",@progbits,_ZN5boost6detail7variant13forced_returnIvEET_v,comdat
  10. .LCOLDB2:
  11. .section .text._ZN5boost6detail7variant13forced_returnIvEET_v,"axG",@progbits,_ZN5boost6detail7variant13forced_returnIvEET_v,comdat
  12. .LHOTB2:
  13. .p2align 4,,15
  14. .weak _ZN5boost6detail7variant13forced_returnIvEET_v
  15. .type _ZN5boost6detail7variant13forced_returnIvEET_v, @function
  16. _ZN5boost6detail7variant13forced_returnIvEET_v:
  17. .LFB1197:
  18. .cfi_startproc
  19. subq $8, %rsp
  20. .cfi_def_cfa_offset 16
  21. movl $_ZZN5boost6detail7variant13forced_returnIvEET_vE19__PRETTY_FUNCTION__, %ecx
  22. movl $49, %edx
  23. movl $.LC0, %esi
  24. movl $.LC1, %edi
  25. call __assert_fail
  26. .cfi_endproc
  27. .LFE1197:
  28. .size _ZN5boost6detail7variant13forced_returnIvEET_v, .-_ZN5boost6detail7variant13forced_returnIvEET_v
  29. .section .text.unlikely._ZN5boost6detail7variant13forced_returnIvEET_v,"axG",@progbits,_ZN5boost6detail7variant13forced_returnIvEET_v,comdat
  30. .LCOLDE2:
  31. .section .text._ZN5boost6detail7variant13forced_returnIvEET_v,"axG",@progbits,_ZN5boost6detail7variant13forced_returnIvEET_v,comdat
  32. .LHOTE2:
  33. .section .text.unlikely._ZN5boost6detail7variant13forced_returnIiEET_v,"axG",@progbits,_ZN5boost6detail7variant13forced_returnIiEET_v,comdat
  34. .LCOLDB3:
  35. .section .text._ZN5boost6detail7variant13forced_returnIiEET_v,"axG",@progbits,_ZN5boost6detail7variant13forced_returnIiEET_v,comdat
  36. .LHOTB3:
  37. .p2align 4,,15
  38. .weak _ZN5boost6detail7variant13forced_returnIiEET_v
  39. .type _ZN5boost6detail7variant13forced_returnIiEET_v, @function
  40. _ZN5boost6detail7variant13forced_returnIiEET_v:
  41. .LFB9757:
  42. .cfi_startproc
  43. subq $8, %rsp
  44. .cfi_def_cfa_offset 16
  45. movl $_ZZN5boost6detail7variant13forced_returnIiEET_vE19__PRETTY_FUNCTION__, %ecx
  46. movl $39, %edx
  47. movl $.LC0, %esi
  48. movl $.LC1, %edi
  49. call __assert_fail
  50. .cfi_endproc
  51. .LFE9757:
  52. .size _ZN5boost6detail7variant13forced_returnIiEET_v, .-_ZN5boost6detail7variant13forced_returnIiEET_v
  53. .section .text.unlikely._ZN5boost6detail7variant13forced_returnIiEET_v,"axG",@progbits,_ZN5boost6detail7variant13forced_returnIiEET_v,comdat
  54. .LCOLDE3:
  55. .section .text._ZN5boost6detail7variant13forced_returnIiEET_v,"axG",@progbits,_ZN5boost6detail7variant13forced_returnIiEET_v,comdat
  56. .LHOTE3:
  57. .section .text.unlikely,"ax",@progbits
  58. .LCOLDB4:
  59. .text
  60. .LHOTB4:
  61. .p2align 4,,15
  62. .globl _Z3runN5boost7variantI3FooJ3BarEEE
  63. .type _Z3runN5boost7variantI3FooJ3BarEEE, @function
  64. _Z3runN5boost7variantI3FooJ3BarEEE:
  65. .LFB9310:
  66. .cfi_startproc
  67. subq $8, %rsp
  68. .cfi_def_cfa_offset 16
  69. movl (%rdi), %eax
  70. cltd
  71. xorl %edx, %eax
  72. cmpl $19, %eax
  73. ja .L7
  74. jmp *.L9(,%rax,8)
  75. .section .rodata
  76. .align 8
  77. .align 4
  78. .L9:
  79. .quad .L30
  80. .quad .L10
  81. .quad .L7
  82. .quad .L7
  83. .quad .L7
  84. .quad .L7
  85. .quad .L7
  86. .quad .L7
  87. .quad .L7
  88. .quad .L7
  89. .quad .L7
  90. .quad .L7
  91. .quad .L7
  92. .quad .L7
  93. .quad .L7
  94. .quad .L7
  95. .quad .L7
  96. .quad .L7
  97. .quad .L7
  98. .quad .L7
  99. .text
  100. .p2align 4,,10
  101. .p2align 3
  102. .L7:
  103. call _ZN5boost6detail7variant13forced_returnIiEET_v
  104. .p2align 4,,10
  105. .p2align 3
  106. .L30:
  107. movl $100, %eax
  108. .L8:
  109. addq $8, %rsp
  110. .cfi_remember_state
  111. .cfi_def_cfa_offset 8
  112. ret
  113. .p2align 4,,10
  114. .p2align 3
  115. .L10:
  116. .cfi_restore_state
  117. movl $999, %eax
  118. jmp .L8
  119. .cfi_endproc
  120. .LFE9310:
  121. .size _Z3runN5boost7variantI3FooJ3BarEEE, .-_Z3runN5boost7variantI3FooJ3BarEEE
  122. .section .text.unlikely
  123. .LCOLDE4:
  124. .text
  125. .LHOTE4:
  126. .globl _Z3runN5boost7variantI3FooI3BarEEE
  127. .set _Z3runN5boost7variantI3FooI3BarEEE,_Z3runN5boost7variantI3FooJ3BarEEE
  128. .section .text.unlikely
  129. .LCOLDB5:
  130. .section .text.startup,"ax",@progbits
  131. .LHOTB5:
  132. .p2align 4,,15
  133. .globl main
  134. .type main, @function
  135. main:
  136. .LFB9320:
  137. .cfi_startproc
  138. movl $100, %eax
  139. ret
  140. .cfi_endproc
  141. .LFE9320:
  142. .size main, .-main
  143. .section .text.unlikely
  144. .LCOLDE5:
  145. .section .text.startup
  146. .LHOTE5:
  147. .section .rodata
  148. .align 32
  149. .type _ZZN5boost6detail7variant13forced_returnIvEET_vE19__PRETTY_FUNCTION__, @object
  150. .size _ZZN5boost6detail7variant13forced_returnIvEET_vE19__PRETTY_FUNCTION__, 58
  151. _ZZN5boost6detail7variant13forced_returnIvEET_vE19__PRETTY_FUNCTION__:
  152. .string "T boost::detail::variant::forced_return() [with T = void]"
  153. .align 32
  154. .type _ZZN5boost6detail7variant13forced_returnIiEET_vE19__PRETTY_FUNCTION__, @object
  155. .size _ZZN5boost6detail7variant13forced_returnIiEET_vE19__PRETTY_FUNCTION__, 57
  156. _ZZN5boost6detail7variant13forced_returnIiEET_vE19__PRETTY_FUNCTION__:
  157. .string "T boost::detail::variant::forced_return() [with T = int]"
  158. .ident "GCC: (Ubuntu 5.3.0-3ubuntu1~14.04) 5.3.0 20151204"
  159. .section .note.GNU-stack,"",@progbits
q5lcpyga

q5lcpyga1#

去掉.cfi指令、未使用的标签和注解行是一个解决的问题:Matt Godbolt的编译器资源管理器背后的脚本在its github project上是开源的。它甚至可以做颜色突出显示,以匹配源代码行到asm行(使用调试信息)。
您可以在本地设置它,这样您就可以为它提供包含所有#include路径等的项目文件(使用-I/...)。所以你可以把它用在你不想通过互联网发送出去的私人源代码上。

Matt Godbolt的CppCon 2017演讲“What Has My Compiler Done for Me Lately? Unbolting the Compiler's Lid”展示了如何使用它(它非常不言自明,但如果你阅读github上的文档,它有一些整洁的功能),以及如何阅读x86 asm,并为初学者介绍了x86 asm本身,以及查看编译器输出。他继续展示了一些整洁的编译器优化(例如,用于除以常数),以及什么样的函数给予有用的asm输出来查看优化的编译器输出(函数args,而不是int a = 123;)。

在Godbolt编译器资源管理器上,如果您想取消选中指令的过滤器选项,使用-g0 -fno-asynchronous-unwind-tables可能会很有用,例如。因为你想在编译器输出中看到.section.p2align的东西。默认情况下,将-g添加到您的选项中,以获取用于颜色突出显示匹配的源代码和asm行的调试信息,但这意味着每个堆栈操作都有.cfi指令,每个源代码行都有.loc指令。
使用普通的gcc/clang(而不是g++),-fno-asynchronous-unwind-tables避免了.cfi指令。也可能有用:-fno-exceptions -fno-rtti-masm=intel。请确保忽略-g

复制/粘贴此文件以供本地使用

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

或者-Os可以更可读,例如使用div来除以非2的幂常数,而不是乘法逆,即使这对性能来说要差得多,也只是小一点,如果有的话。

在Godbolt上使用-g0,如果你想取消选中“filter directives”来查看它错误过滤的部分和内容,或者全局变量。它的默认值-g可能会使事情变得混乱,所以-g0抵消了这一点。(-g0是默认值,没有命令行选项。)也可以使用-fno-asynchronous-unwind-tables,也可以使用-fno-exceptions -fno-rtti

但实际上,我建议直接使用Godbolt(在线或本地设置)!你可以在gcc和clang的版本之间快速切换,看看旧的或新的编译器是否做了一些愚蠢的事情。(或者ICC做什么,甚至MSVC做什么。)甚至有ARM /ARM 64 gcc 6.3,以及用于PowerPC、MIPS、AVR、MSP430的各种gcc。(看看在int比寄存器宽或者不是32位的机器上会发生什么可能很有趣。或者在RISC上,x86)。
对于C而不是C++,您可以使用-xc -std=gnu11来避免将语言下拉列表翻转为C,这会重置您的源窗格和编译器选择,并且有一组不同的编译器可用。

用于生成供人类使用的asm的有用编译器选项
***记住,你的代码只需要编译,而不是链接:传递一个指向外部函数的指针,比如void ext(void*p),是一个很好的方法来阻止某些东西被优化掉。你只需要一个原型,没有定义,所以编译器不能内联它或对它做什么做任何假设。(或者inline asm like Benchmark::DoNotOptimize可以强制编译器在寄存器中具体化一个值,或者忘记它是一个已知的常量,如果你足够了解GNU C内联asm语法,可以使用约束来理解你对编译器的要求的影响。

  • 我推荐使用-O3 -Wall -Wextra -fverbose-asm -march=haswell来查看代码。(-fverbose-asm只会让源代码看起来很吵,当你得到的只是编号的临时变量作为操作数的名称时。)当你摆弄源代码以查看它如何改变asm时,你肯定希望启用编译器警告。当解释是你做了一些值得在源代码中警告的事情时,你不想浪费时间在asm上挠头。
  • 要了解调用约定是如何工作的,您通常需要查看调用方和被调用方,而不需要内联

您可以在定义上使用__attribute__((noipa)) foo_t foo(bar_t x) { ... },或使用gcc -O3 -fno-inline-functions -fno-inline-functions-called-once -fno-inline-small-functions编译以禁用内联。(但是这些命令行选项不会禁止克隆函数进行常量传播。noipa =无术中分析。它甚至比__attribute__((noinline,noclone))更强。)参见From compiler perspective, how is reference for array dealt with, and, why passing by value(not decay) is not allowed?以获得示例。
或者,如果你只是想看看函数如何传递/接收不同类型的参数,你可以使用不同的名称,但使用相同的原型,这样编译器就没有内联的定义。这适用于任何编译器。如果没有定义,函数对于优化器来说只是一个黑盒子,只受调用约定/ ABI的控制。

  • -ffast-math将得到许多libm函数内联,一些到一个单一的指令(特别是)。SSE 4可用于roundsd)。有些将只与-fno-math-errno-ffast-math的其他“更安全”的部分内联,而不包含允许编译器以不同方式舍入的部分。如果你有FP代码,一定要看看它是否有-ffast-math。如果你不能在你的常规构建中安全地启用任何-ffast-math,也许你会想到一个安全的想法,你可以在源代码中进行修改,以允许在没有-ffast-math的情况下进行同样的优化。
    *-O3 -fno-tree-vectorize将在不自动矢量化的情况下进行优化,因此如果您想与-O2(在gcc 11和更早版本上不启用自动矢量化,但在所有clang上启用)进行比较,则可以在不自动矢量化的情况下进行完全优化。
    -Os(优化大小和速度)可以帮助保持代码更紧凑,这意味着更少的代码需要理解。clang的-Oz优化了大小,即使它影响了速度,甚至使用push 1/pop rax代替mov eax, 1,所以这只对code golf有意义。

甚至-Og(最小优化)也可能是您想要查看的,这取决于您的目标。-O0充满了存储/重新加载噪音,这使得它更难跟随,unless you use register vars。唯一的好处是每个C语句都编译成一个单独的指令块,这使得-fverbose-asm能够使用实际的C变量名。

  • clang默认展开循环,因此**-fno-unroll-loops在复杂函数中非常有用**。您可以了解“编译器做了什么”,而不必费力地遍历展开的循环。(gcc支持-funroll-loops-fprofile-use,但不支持-O3)。(这是对人类可读代码的建议,而不是对运行速度更快的代码的建议。
    *一定要启用某种程度的优化,除非你特别想知道-O0做了什么。它的“可预测的调试行为”要求使编译器存储/重新加载每个C语句之间的所有内容,因此您可以使用调试器修改C变量,甚至可以“跳转”到同一函数中的不同源代码行,并继续执行,就像在C源代码中那样。-O0输出是如此嘈杂与商店/重新加载(和如此缓慢)不仅从缺乏优化,但forced de-optimization to support debugging。(相关)。
    要混合使用source和asm,请使用gcc -Wa,-adhln -c -g foo.c | less将额外的选项传递给as。(在a blog postanother blog中有更多讨论。请注意,this的输出不是有效的汇编程序输入,因为C源代码直接在那里,而不是作为汇编程序注解。所以不要叫它.s。如果您想将其保存到文件中,则.lst可能是有意义的。

Godbolt的颜色突出显示也有类似的用途,它非常有助于您查看多个 * 非连续 * asm指令何时来自同一个源代码行。我根本没有使用过gcc listing命令,所以IDK它做得有多好,在这种情况下,眼睛看起来有多容易。
我喜欢godbolt的asm面板的高代码密度,所以我不认为我会喜欢混合的源代码行。至少不是简单的功能。也许是一个太复杂的函数,无法处理asm的整体结构。
记住,当你只想看asm时,省略main()和编译时常量。您希望看到处理寄存器中的函数arg的代码,而不是常量传播将其转换为return 42之后的代码,或者至少优化掉一些东西。
从函数中删除static和/或inline将为它们生成一个独立的定义,以及任何调用者的定义,因此您可以查看它。

不要将代码放在名为main()的函数中。gcc知道main是特殊的,并假设它只会被调用一次,因此它将其标记为“冷”,并减少优化。

你可以做的另一件事是:如果你确实做了一个main(),你可以运行它并使用调试器。stepisi)按指令步进。请参阅x86tag wiki的底部了解说明。但请记住,代码可能会在使用编译时常量参数内联到main后进行优化。
__attribute__((noinline))可能会对你不想内联的函数有帮助。gcc还将对函数进行恒定传播克隆,即一个特殊的版本,其中一个args作为常量,用于知道它们正在传递常量的调用站点。符号名将是.clone.foo.constprop_1234或asm输出中的其他名称。您也可以使用__attribute__((noclone))来禁用它。

举例说明

如果你想看看编译器如何将两个整数相乘:我把下面的代码放在Godbolt编译器资源管理器上,以获取asm(来自gcc -O3 -march=haswell -fverbose-asm)的错误方法和正确方法来测试它。

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

(This asm和C的混合是通过将godbolt的asm输出复制粘贴到正确的位置来手工制作的。我发现这是一个很好的方式来展示一个短函数如何在SO答案/编译器错误报告/电子邮件中编译。

展开查看全部
vd8tlhqk

vd8tlhqk2#

您始终可以从对象文件查看生成的程序集,而不是使用编译器程序集输出。objdump在脑海中浮现。
您甚至可以告诉objdump混合使用源代码和汇编程序,这样就可以更容易地找出哪一行源代码对应于哪一条指令。示例会话:

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

objdump标志:

  • -d反汇编所有可执行部分
  • -S混合汇编和源代码(在使用g++编译时需要-g
  • -M intel选择intel语法而不是丑陋的AT&T语法(* 可选 *)
展开查看全部
kqqjbcuj

kqqjbcuj3#

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

  1. int main() {
  2. asm volatile ("interesting_part_begin%=:":);
  3. do_something();
  4. asm volatile ("interesting_part_end%=:":);
  5. }

我还没有遇到过这样的问题,但是asm volatile对编译器的优化器来说可能非常困难,因为它倾向于保持这样的代码不变。

相关问题