从 g++ 输出中删除不需要的汇编程序语句
Remove needless assembler statements from g++ output
我正在调查本地二进制文件的一些问题。我注意到 g++ 创建了很多对我来说似乎没有必要的 ASM 输出。 -O0
示例:
Derived::Derived():
pushq %rbp
movq %rsp, %rbp
subq , %rsp <--- just need 8 bytes for the movq to -8(%rbp), why -16?
movq %rdi, -8(%rbp)
movq -8(%rbp), %rax
movq %rax, %rdi <--- now we have moved rdi onto itself.
call Base::Base()
leaq 16+vtable for Derived(%rip), %rdx
movq -8(%rbp), %rax <--- effectively %edi, does not point into this area of the stack
movq %rdx, (%rax) <--- thus this wont change -8(%rbp)
movq -8(%rbp), %rax <--- so this statement is unnecessary
movl 12, 12(%rax)
nop
leave
ret
选项-O1 -fno-inline -fno-elide-constructors -fno-omit-frame-pointer
:
Derived::Derived():
pushq %rbp
movq %rsp, %rbp
pushq %rbx
subq , %rsp <--- reserve some stack space and never use it.
movq %rdi, %rbx
call Base::Base()
leaq 16+vtable for Derived(%rip), %rax
movq %rax, (%rbx)
movl 12, 12(%rbx)
addq , %rsp <--- release unused stack space.
popq %rbx
popq %rbp
ret
此代码用于 Derived
的构造函数,该构造函数调用 Base
基本构造函数,然后覆盖位置 0 处的 vtable 指针,并将常量值设置为其持有的 int 成员Base
包含什么。
问题:
- 我能否通过尽可能少的优化来翻译我的程序并摆脱这些东西?我必须设置哪些选项?或者编译器无法使用
-O0
或 -O1
检测到这些情况并且没有办法绕过它们的原因是什么?
- 为什么会生成
subq , %rsp
语句?您不能优化输入或输出一开始就没有意义的语句。为什么编译器会生成它呢?即使使用 O0,寄存器分配算法也不应该为不存在的东西生成代码。那为什么要这样做呢?
is there a reason the compiler cannot detect these cases with -O0
or -O1
正是因为您告诉编译器不要这样做。这些是 optimisation 级别,需要关闭或关闭以进行正确调试。您还在为 run-time.
权衡编译时间
您正在以错误的方式查看 telescope,请查看当您进行 up 优化时编译器将为您做的很棒的优化。
我在您的 -O1
输出中没有看到任何明显遗漏的优化。当然,除了将 RBP 设置为帧指针,但您使用 -fno-omit-frame-pointer
非常清楚,您知道为什么 GCC 没有优化它。
The function has no local variables
您的函数是一个非静态 class 成员函数,因此 它有一个隐式参数:this
in rdi
。由于 -O0
,哪个 g++ 溢出到堆栈。函数参数算作局部变量。
How does a cyclic move without an effect improve the debugging experience. Please elaborate.
改进C/C++ 调试:调试信息格式只能描述 C 变量相对于 RSP 或 RBP 的位置,而不是它当前所在的寄存器。此外,您可以使用调试器 修改 任何变量并继续,获得预期的结果,就好像您在 C++ 抽象机中那样做一样。每个语句都被编译成一个单独的 asm 块,寄存器中没有任何值(有趣的事实:除了 register int foo
:该关键字确实影响调试模式代码生成)。
Why does clang produce inefficient asm with -O0 (for this simple floating point sum)? 也适用于 G++ 和其他编译器。
Which options would I have to set?
如果您正在阅读/调试 asm,请至少使用 -Og
或更高版本 来禁用调试模式溢出所有语句之间的行为-O0
。最好是 -O2
或 -O3
除非你喜欢看到比完全优化更多的错过的优化。但是 -Og
或 -O1
将进行寄存器分配并进行合理的循环(底部有条件分支),以及各种简单的优化。虽然还不是异或归零的标准窥视孔
解释了如何编写带有 args 和 return 值的函数,这样您就可以编写不会优化掉的函数。
加载到 RAX 然后 movq %rax, %rdi
只是 -O0
的副作用。 GCC 花费很少的时间来优化程序逻辑的 GIMPLE and/or RTL 内部表示(在发出 x86 asm 之前),它甚至没有注意到它可以首先加载到 RDI 中。 -O0
的部分要点是快速编译,以及一致的调试。
Why is the subq , %rsp
statement generated at all?
因为ABI要求call
指令前16字节栈对齐,而这个函数做了偶数个8字节push
es . (call
本身推送一个 return 地址)。它会在 -O1
没有 -fno-omit-frame-pointer
时消失,因为你没有强迫 g++ 到 push/pop RBP 以及它实际需要的调用保留寄存器。
有趣的事实:clang 通常会使用虚拟 push %rcx
/pop
或其他东西,具体取决于 -mtune
选项,而不是 8 字节子。
如果它是叶函数,g++ 将只使用 RSP 下面的红色区域供当地人使用,即使在 -O0
。 Why is there no "sub rsp" instruction in this function prologue and why are function parameters stored at negative rbp offsets?
在未优化的代码中,G++ 分配它从未使用过的额外 16 个字节的情况并不少见。即使有时在启用优化的情况下,当以 16 字节为目标时,g++ 也会将其堆栈分配大小向上舍入太多。这是一个错过优化的错误。例如Memory allocation and addressing in Assembly
我正在调查本地二进制文件的一些问题。我注意到 g++ 创建了很多对我来说似乎没有必要的 ASM 输出。 -O0
示例:
Derived::Derived():
pushq %rbp
movq %rsp, %rbp
subq , %rsp <--- just need 8 bytes for the movq to -8(%rbp), why -16?
movq %rdi, -8(%rbp)
movq -8(%rbp), %rax
movq %rax, %rdi <--- now we have moved rdi onto itself.
call Base::Base()
leaq 16+vtable for Derived(%rip), %rdx
movq -8(%rbp), %rax <--- effectively %edi, does not point into this area of the stack
movq %rdx, (%rax) <--- thus this wont change -8(%rbp)
movq -8(%rbp), %rax <--- so this statement is unnecessary
movl 12, 12(%rax)
nop
leave
ret
选项-O1 -fno-inline -fno-elide-constructors -fno-omit-frame-pointer
:
Derived::Derived():
pushq %rbp
movq %rsp, %rbp
pushq %rbx
subq , %rsp <--- reserve some stack space and never use it.
movq %rdi, %rbx
call Base::Base()
leaq 16+vtable for Derived(%rip), %rax
movq %rax, (%rbx)
movl 12, 12(%rbx)
addq , %rsp <--- release unused stack space.
popq %rbx
popq %rbp
ret
此代码用于 Derived
的构造函数,该构造函数调用 Base
基本构造函数,然后覆盖位置 0 处的 vtable 指针,并将常量值设置为其持有的 int 成员Base
包含什么。
问题:
- 我能否通过尽可能少的优化来翻译我的程序并摆脱这些东西?我必须设置哪些选项?或者编译器无法使用
-O0
或-O1
检测到这些情况并且没有办法绕过它们的原因是什么? - 为什么会生成
subq , %rsp
语句?您不能优化输入或输出一开始就没有意义的语句。为什么编译器会生成它呢?即使使用 O0,寄存器分配算法也不应该为不存在的东西生成代码。那为什么要这样做呢?
is there a reason the compiler cannot detect these cases with
-O0
or-O1
正是因为您告诉编译器不要这样做。这些是 optimisation 级别,需要关闭或关闭以进行正确调试。您还在为 run-time.
权衡编译时间您正在以错误的方式查看 telescope,请查看当您进行 up 优化时编译器将为您做的很棒的优化。
我在您的 -O1
输出中没有看到任何明显遗漏的优化。当然,除了将 RBP 设置为帧指针,但您使用 -fno-omit-frame-pointer
非常清楚,您知道为什么 GCC 没有优化它。
The function has no local variables
您的函数是一个非静态 class 成员函数,因此 它有一个隐式参数:this
in rdi
。由于 -O0
,哪个 g++ 溢出到堆栈。函数参数算作局部变量。
How does a cyclic move without an effect improve the debugging experience. Please elaborate.
改进C/C++ 调试:调试信息格式只能描述 C 变量相对于 RSP 或 RBP 的位置,而不是它当前所在的寄存器。此外,您可以使用调试器 修改 任何变量并继续,获得预期的结果,就好像您在 C++ 抽象机中那样做一样。每个语句都被编译成一个单独的 asm 块,寄存器中没有任何值(有趣的事实:除了 register int foo
:该关键字确实影响调试模式代码生成)。
Why does clang produce inefficient asm with -O0 (for this simple floating point sum)? 也适用于 G++ 和其他编译器。
Which options would I have to set?
如果您正在阅读/调试 asm,请至少使用 -Og
或更高版本 来禁用调试模式溢出所有语句之间的行为-O0
。最好是 -O2
或 -O3
除非你喜欢看到比完全优化更多的错过的优化。但是 -Og
或 -O1
将进行寄存器分配并进行合理的循环(底部有条件分支),以及各种简单的优化。虽然还不是异或归零的标准窥视孔
加载到 RAX 然后 movq %rax, %rdi
只是 -O0
的副作用。 GCC 花费很少的时间来优化程序逻辑的 GIMPLE and/or RTL 内部表示(在发出 x86 asm 之前),它甚至没有注意到它可以首先加载到 RDI 中。 -O0
的部分要点是快速编译,以及一致的调试。
Why is the
subq , %rsp
statement generated at all?
因为ABI要求call
指令前16字节栈对齐,而这个函数做了偶数个8字节push
es . (call
本身推送一个 return 地址)。它会在 -O1
没有 -fno-omit-frame-pointer
时消失,因为你没有强迫 g++ 到 push/pop RBP 以及它实际需要的调用保留寄存器。
有趣的事实:clang 通常会使用虚拟 push %rcx
/pop
或其他东西,具体取决于 -mtune
选项,而不是 8 字节子。
如果它是叶函数,g++ 将只使用 RSP 下面的红色区域供当地人使用,即使在 -O0
。 Why is there no "sub rsp" instruction in this function prologue and why are function parameters stored at negative rbp offsets?
在未优化的代码中,G++ 分配它从未使用过的额外 16 个字节的情况并不少见。即使有时在启用优化的情况下,当以 16 字节为目标时,g++ 也会将其堆栈分配大小向上舍入太多。这是一个错过优化的错误。例如Memory allocation and addressing in Assembly