m.s*_*.s. 56 c++ assembly gcc clang
我想检查boost::variant在我的代码中应用的程序集输出,以便查看哪些中间调用被优化掉了.
当我编译以下示例(使用GCC 5.3 g++ -O3 -std=c++14 -S)时,似乎编译器优化了所有内容并直接返回100:
(...)
main:
.LFB9320:
.cfi_startproc
movl $100, %eax
ret
.cfi_endproc
(...)
Run Code Online (Sandbox Code Playgroud)
#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);
}
Run Code Online (Sandbox Code Playgroud)
但是,完整的程序集输出包含的内容远远超过上面的摘录,对我而言,它看起来永远不会被调用.有没有办法告诉GCC/clang删除所有"噪音"并输出程序运行时实际调用的内容?
完整装配输出:
.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
Run Code Online (Sandbox Code Playgroud)
Pet*_*des 75
剥离.cfi指令,未使用的标签和注释行是一个已解决的问题:Matt Godbolt的编译器浏览器背后的脚本是其github项目的开源.它甚至可以进行颜色突出显示以将源行与asm行匹配(使用调试信息).
您可以在本地进行设置,这样您就可以使用所有#include路径(使用-I/...)来提供属于项目一部分的文件.因此,您可以将它用于您不希望通过Internet发送的私有源代码.
Matt Godbolt的CppCon2017演讲"我的编译器最近为我做了什么?Unbolting编译器的盖子" 展示了如何使用它(这是相当不言自明的,但如果你阅读GitHub上的文档有一些简洁的功能),以及如何阅读x86的汇编,用轻柔的介绍x86的ASM本身总的初学者,并查看编译器输出.他继续展示一些简洁的编译器优化(例如,用常数除法),以及哪种函数提供有用的asm输出以查看优化的编译器输出(函数args,而不是int a = 123;).
使用普通的gcc/clang(不是g ++),-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
Run Code Online (Sandbox Code Playgroud)
但实际上,我建议直接使用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:
请记住,您的代码只需编译,而不是链接:将指针传递给外部函数,这void ext(int*p)是阻止优化的好方法.你只需要一个原型,没有定义,所以编译器不能内联它或对它的作用做任何假设.
我建议使用-O3 -Wall -Wextra -fverbose-asm -march=haswell)来查看代码.(-fverbose-asm但是,当你得到的所有内容都被编号为temporaries作为操作数的名称时,可以让源看起来很吵.)当你正在摆弄源代码以查看它如何更改asm时,你肯定希望启用编译器警告.你不想浪费时间在asm上搔头,因为你的解释是你在源头做了一些值得警告的事情.
要查看调用约定的工作原理,通常需要查看调用者和被调用者而不进行内联.
您可以__attribute__((noinline,noclone)) foo_t foo(bar_t x) { ... }在定义上使用,也可以编译gcc -O3 -fno-inline-functions -fno-inline-functions-called-once -fno-inline-small-functions以禁用内联.(但是这些命令行选项不会禁用克隆用于常量传播的函数.)请参阅从编译器的角度来看,如何处理数组的引用,以及为什么不允许通过值(不是衰减)进行传递?举个例子.
或者,如果您只想查看函数如何传递/接收不同类型的args,您可以使用不同的名称但使用相同的原型,因此编译器没有内联定义.这适用于任何编译器.
-ffast-math将获得许多libm函数内联,一些到单个指令(特别是SSE4可用roundsd).有些内容将与仅仅-fno-math-errno或其他"更安全"的部分内联-ffast-math,而没有允许编译器以不同方式进行舍入的部分.如果您有FP代码,请务必查看/不使用-ffast-math.如果您无法安全地启用-ffast-math常规构建中的任何一个,那么您可能会想到可以在源代码中进行安全更改,以便在不进行相同优化的情况下进行相同的优化-ffast-math.
-O3 -fno-tree-vectorize将在没有自动矢量化的情况下进行优化,因此您无需进行比较即可获得完全优化-O2(不能在gcc上启用自动向量化,但在clang上启用自动向量化).-funroll-loops在复杂函数中非常有用.您可以在不必浏览展开的循环的情况下了解"编译器做了什么".(GCC能够-funroll-loops有-fprofile-use,但不是-O3).(这是对人类可读代码的建议,而不是对运行速度更快的代码的建议.)-O0做.它的"可预测的调试行为"要求使编译器在每个C语句之间存储/重新加载所有内容,因此您可以使用调试器修改C变量,甚至可以"跳转"到同一函数中的不同源代码行,并继续执行,就好像您一样在C源代码中做到了这一点. -O0存储/重载(以及如此慢)的输出是如此嘈杂,不仅仅是因为缺乏优化,而是强制去优化以支持调试.要获得源和asm的混合,请使用gcc -Wa,-adhln -c -g foo.c | less传递额外选项as.(在博客文章和另一篇博客中对此进行更多讨论.)请注意,此输出不是有效的汇编程序输入,因为C源是直接存在的,而不是汇编程序注释.所以不要称之为.s.一.lst,如果你想将它保存到一个文件可能是有意义的.
Godbolt的颜色突出显示有类似的用途,非常适合帮助您查看多个非连续的 asm指令来自同一源代码行.我根本没有使用过那个gcc列表命令,所以在这种情况下,IDK的表现如何,眼睛看起来有多容易.
我喜欢godbolt的asm窗格的高代码密度,所以我认为我不想让源代码行混合在一起.至少不是简单的函数.也许有一个太复杂的功能无法处理asm的整体结构......
请记住,当您只想查看asm时,请忽略main()编译时常量.您希望在寄存器中看到用于处理函数arg的代码,而不是在常量传播将其转换为代码之后的代码return 42,或者至少优化掉一些东西.
删除static和/或inline从函数中将为它们生成一个独立的定义,以及任何调用者的定义,因此您可以查看它.
不要将代码放在一个名为的函数中main().gcc知道这main是特殊的,并假设它只会被调用一次,因此它将其标记为"冷"并将其优化得更少.
你可以做的另一件事:如果你确实做了main(),你可以运行它并使用调试器. stepi(si)按指令逐步完成.有关说明,请参阅x86 标记wiki的底部.但请记住,在使用编译时常量args内联到main之后,代码可能会优化掉.
__attribute__((noinline))对于您不希望内联的函数可能有所帮助.gcc还将制作函数的常量传播克隆,即一个特殊版本,其中一个args作为常量,用于知道它们传递常数的调用点.符号名称将是.clone.foo.constprop_1234asm输出中的某个或某些内容.您也可以使用它__attribute__((noclone))来禁用它.)
如果你想看看编译器如何乘以两个整数:我将以下代码放在Godbolt编译器资源管理器上以获取asm(from 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
Run Code Online (Sandbox Code Playgroud)
(的ASM和C这样的搭配是通过复制粘贴从godbolt输出的ASM手工制作到正确的地方.我觉得这是展示一个简短的函数在SO答案/编译器错误报告/邮件如何编译一个好办法.)
Lea*_*ros 11
您始终可以从目标文件中查看生成的程序集,而不是使用编译器程序集输出.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
Run Code Online (Sandbox Code Playgroud)
objdump标志说明:
-d 反汇编所有可执行部分-S使用源混合汇编(-g编译时需要g++)-M intel选择英特尔语法而不是丑陋的AT&T语法(可选)我喜欢插入标签,我可以轻松地从objdump输出中grep.
int main() {
asm volatile ("interesting_part_begin%=:":);
do_something();
asm volatile ("interesting_part_end%=:":);
}
Run Code Online (Sandbox Code Playgroud)
我还没有遇到过这个问题,但是asm volatile在编译器的优化器上可能会非常困难,因为它往往会使这些代码保持不变.
| 归档时间: |
|
| 查看次数: |
11157 次 |
| 最近记录: |