c++ - 如何从 GCC/clang 程序集输出中删除 "noise"?

标签 c++ gcc assembly 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/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

最佳答案

去除 .cfi 指令、未使用的标签和注释行是一个已解决的问题:Matt Godbolt's compiler explorer 背后的脚本在 its github project 上是开源的。它甚至可以进行颜色突出显示以将源代码行与汇编行匹配(使用调试信息)。
您可以在本地设置它,以便您可以使用所有 #include 路径等(使用 -I/... )提供属于您项目的文件。因此,您可以在不想通过 Internet 发送的私有(private)源代码上使用它。
马特Godbolt的CppCon2017谈话“What Has My Compiler Done for Me Lately? Unbolting the Compiler's Lid”展示了如何使用它(这是相当不言自明,但有,如果你阅读GitHub上的文档一些整洁的功能),并且还如何阅读的x86 ASM ,用轻柔介绍的x86汇编本身对于初学者,并查看编译器输出。他继续展示了一些简洁的编译器优化(例如除以常数),以及什么样的函数提供有用的 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

但实际上,我建议直接使用 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。 (或者,您可以在语言下拉列表中使用 C 模式,但它有不同的编译器选择,而且大多受到更多限制。它会重置您的源代码 Pane ,因此在 C 和 C++ 之间切换更像是一种折磨。)

用于制作 asm 供人类消费的有用编译器选项 :
  • 记住,你的代码只需要编译,而不是链接:传递一个指向外部函数的指针,如 void ext(int*p) 是阻止某些东西优化掉 0x251819211 的好方法。您只需要一个原型(prototype),没有定义,因此编译器无法内联它或对其功能做出任何假设。
  • 我建议使用 -O3 -Wall -Wextra -fverbose-asm -march=haswell ) 查看代码。 ( -fverbose-asm 只会让源代码看起来很嘈杂,但是,当您得到的只是临时编号作为操作数的名称时。)当您摆弄源代码以查看它如何更改 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 进行编译以禁用内联。 (但这些命令行选项不会禁用克隆用于常量传播的函数。)示例参见 From compiler perspective, how is reference for array dealt with, and, why passing by value(not decay) is not allowed?
    或者,如果您只想查看函数如何传递/接收不同类型的参数,您可以使用不同的名称但使用相同的原型(prototype),这样编译器就没有要内联的定义。这适用于任何编译器。
  • -ffast-math 将获得许多要内联的 libm 函数,其中一些是单个指令(特别是 SSE4 可用于 roundsd )。有些将仅与 -fno-math-errno-ffast-math 的其他“更安全”部分内联,而没有允许编译器以不同方式舍入的部分。如果你有 FP 代码,一定要看看它有/没有 -ffast-math 。如果您无法在常规构建中安全地启用 -ffast-math 中的任何一个,也许您会想到可以在源代码中进行安全更改以允许在没有 -ffast-math 的情况下进行相同的优化。
  • -O3 -fno-tree-vectorize 将在不自动矢量化的情况下进行优化 ,因此如果您想与 12518 上的自动矢量化进行比较,则无需自动矢量化 0x2518192142 即可获得完整的优化(但在 123243 上启用自动矢量化 123243 上的 1232333513 上的 cc
  • clang 默认展开循环,所以 -O2 在复杂函数 中很有用。您可以了解“编译器做了什么”,而无需费力地浏览展开的循环。 (gcc 启用 -fno-unroll-loops-funroll-loops ,但不启用 -fprofile-use )。 (这是对人类可读代码的建议,而不是对运行速度更快的代码的建议。)
  • 绝对启用某种程度的优化,除非您特别想知道 -O3 做了什么 。它的“可预测的调试行为”要求使编译器在每个 C 语句之间存储/重新加载所有内容,因此您可以使用调试器修改 C 变量,甚至可以“跳转”到同一函数中的不同源代码行,并继续执行,就像您一样在 C 源代码中做到了这一点。 -O0 输出在存储/重新加载时如此嘈杂(而且如此缓慢)不仅是由于缺乏优化,而且是 forced de-optimization to support debugging 。 (也是 related )。

  • 要混合使用源和汇编 ,请使用 -O0 将额外选项传递给 gcc -Wa,-adhln -c -g foo.c | less 。 (在 a blog postanother blog 中对此进行了更多讨论。)。请注意,此输出不是有效的汇编器输入,因为 C 源代码直接存在,而不是作为汇编器注释。所以不要称它为 as 。如果您想将其保存到文件中,则 .s 可能有意义。
    Godbolt 的颜色突出显示具有类似的目的,非常有助于您查看多个不连续的 asm 指令何时来自同一源代码行。我根本没有使用过那个 gcc 列表命令,所以 IDK 在这种情况下它做得有多好,以及它的眼睛有多容易看到。
    我喜欢 Godbolt 的 asm Pane 的高代码密度,所以我不认为我喜欢混入源代码行。至少对于简单的函数来说不是。也许有一个太复杂的函数,无法处理 asm 的整体结构......

    请记住,当您只想查看 asm 时, 会忽略 .lst 和编译时常量 。您希望看到处理寄存器中函数 arg 的代码,而不是常量传播将其转换为 main() 后的代码,或者至少优化掉一些东西。
    从函数中删除 return 42 和/或 static 将为它们生成一个独立的定义,以及对任何调用者的定义,因此您可以查看它。
    不要将您的代码放在名为 inline 的函数中。 gcc 知道 main() 是特殊的,并假设它只会被调用一次,因此它将其标记为“冷”并对其进行了较少的优化。

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

    例如
    如果您想查看编译器如何将两个整数相乘:我将以下代码 on the Godbolt compiler explorer 以错误的方式和正确的方式来获取 asm(来自 __attribute__((noclone)) )。
    // 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 输出复制粘贴到正确位置来手工制作的。我发现这是一个很好的方式来展示一个简短的函数如何在 SO 答案/编译器错误报告/电子邮件中编译。)

    关于c++ - 如何从 GCC/clang 程序集输出中删除 "noise"?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/38552116/

    相关文章:

    c++ - 使用 QtMobility 检测漫游(切换接入点)?

    c++ - cpp 的未知编译器标志/参数

    linux - 制作 :***no rule to create object"@GDB_OBJ @"`

    linux - 从字符串中去除空格

    assembly - 转置 SSE2/SSSE3 上的 8 个 16 位元素寄存器

    c++ - 如何检查我的输入文件是否已更新?

    c++ - 在 C++ 中以原始格式获取剪贴板数据

    linux - 如何在 Ubuntu 11.10 上安装 gcc 4.4.6

    编译后的 C 数据类型大小

    c++ - 当所有时间似乎都花在内存分配上时如何提高抽象工厂的性能