禁用优化的 c alloca 函数的奇怪汇编代码 - gcc 使用 DIV 和 IMUL 为常数 16,并转换?

Posted

技术标签:

【中文标题】禁用优化的 c alloca 函数的奇怪汇编代码 - gcc 使用 DIV 和 IMUL 为常数 16,并转换?【英文标题】:Strange assembly code for c alloca function with optimization disabled - gcc uses DIV and IMUL by a constant 16, and shifts? 【发布时间】:2021-05-23 03:39:03 【问题描述】:

我在 c 中有这个简单的代码

#include <stdio.h>
#include <alloca.h>

int main()

    char* buffer = (char*)alloca(600);
    snprintf(buffer, 600, "Hello %d %d %d\n", 1, 2, 3);

    return 0;

我希望为 alloca 函数生成的汇编代码只会递减堆栈指针(一个子指令),并且可能会进行一些对齐(一个和指令),但生成的汇编代码非常复杂,甚至比你的效率低​​下'期待。

这是objdump -d main.o的输出,在gcc -c的输出上(没有优化,所以默认-O0

    0000000000400596 <main>:
  400596:   55                      push   %rbp
  400597:   48 89 e5                mov    %rsp,%rbp
  40059a:   48 83 ec 10             sub    $0x10,%rsp
  40059e:   b8 10 00 00 00          mov    $0x10,%eax
  4005a3:   48 83 e8 01             sub    $0x1,%rax
  4005a7:   48 05 60 02 00 00       add    $0x260,%rax
  4005ad:   b9 10 00 00 00          mov    $0x10,%ecx
  4005b2:   ba 00 00 00 00          mov    $0x0,%edx
  4005b7:   48 f7 f1                div    %rcx
  4005ba:   48 6b c0 10             imul   $0x10,%rax,%rax
  4005be:   48 29 c4                sub    %rax,%rsp
  4005c1:   48 89 e0                mov    %rsp,%rax
  4005c4:   48 83 c0 0f             add    $0xf,%rax
  4005c8:   48 c1 e8 04             shr    $0x4,%rax
  4005cc:   48 c1 e0 04             shl    $0x4,%rax
  4005d0:   48 89 45 f8             mov    %rax,-0x8(%rbp)
  4005d4:   48 8b 45 f8             mov    -0x8(%rbp),%rax
  4005d8:   41 b9 03 00 00 00       mov    $0x3,%r9d
  4005de:   41 b8 02 00 00 00       mov    $0x2,%r8d
  4005e4:   b9 01 00 00 00          mov    $0x1,%ecx
  4005e9:   ba a8 06 40 00          mov    $0x4006a8,%edx
  4005ee:   be 58 02 00 00          mov    $0x258,%esi
  4005f3:   48 89 c7                mov    %rax,%rdi
  4005f6:   b8 00 00 00 00          mov    $0x0,%eax
  4005fb:   e8 a0 fe ff ff          callq  4004a0 <snprintf@plt>
  400600:   b8 00 00 00 00          mov    $0x0,%eax
  400605:   c9                      leaveq 
  400606:   c3                      retq   
  400607:   66 0f 1f 84 00 00 00    nopw   0x0(%rax,%rax,1)
  40060e:   00 00 

知道生成的汇编代码的目的是什么吗?我正在使用 gcc 8.3.1。

【问题讨论】:

如果没有优化,gcc 会为alloca 生成非常糟糕的asm,使用divimul 而不是and $-16, %reg 将分配大小四舍五入为16 的倍数。 编译的时候有没有开启调试模式? 为什么您期望优化而不启用优化? ;) 是的,优化未启用。但这似乎是过早的悲观,即使在调试模式下,为什么不生成and $-16, %reg @tadman: 一般情况下-O0 是正确的,但这不是的一个实例。其中大部分是单个语句char* buffer = alloca(600); 的代码,并且 asm 比它需要的或预期的更复杂和冗长,这本身就比add $15, %reg 等明显的舍入习语更难理解/and $-16, %reg/sub %reg, %rsp. 【参考方案1】:

当然,通常的调试模式/反优化行为是将每个 C 语句编译到一个单独的块中,非register 变量实际上在内存中。 (Why does clang produce inefficient asm with -O0 (for this simple floating point sum)?)。

但是,是的,这超出了“未优化”的范围。没有理智的人会期望 GCC 的指令序列(或 GIMPLE 或 RTL 逻辑,无论它扩展的任何阶段)对于 alloca 逻辑通过 compile-time-constant power 2 涉及 div,而不是一个班次或只是一个AND。 x /= 16; 不会编译成 div 如果你自己用 C 源代码编写,即使是 gcc -O0

通常 GCC 会尽可能多地对常量表达式进行编译时求值,例如 x = 5 * 6 在运行时不会使用 imul。但是它扩展其alloca 逻辑的时间点必须在该点之后,可能很晚(在大多数其他通过之后)来解释所有那些错过的优化。因此,它不会从在 C 源逻辑上运行的相同传递中受益。

它做了两件事:

将分配大小向上舍入(将其放入寄存器后的常量 600到 16 的倍数,方法是:((16ULL - 1) + x) / 16 * 16。一个理智的编译器至少会使用右移/左移,如果不将其优化为(x+15) &amp; -16。但不幸的是,GCC 使用了 16 倍的 divimul,尽管它是 2 的恒定幂。

将分配空间的最终地址四舍五入为 16 的倍数(尽管它已经是因为 RSP 开始 16 字节对齐并且分配大小被四舍五入。)它使用 @ 987654339@ 比 div/imul 效率高得多(特别是对于 Ice Lake 之前的 Intel 上的 64 位操作数大小),但仍然比 and $-16, %rax 效率低。做已经毫无意义的工作当然是愚蠢的。

那么当然要把指针存入char* buffer

在下一条语句的 asm 块中,将其作为 sprintf 的 arg 重新加载(效率低下到 RAX 中,而不是直接到 RDI,对于 gcc -O0 很典型),同时设置寄存器 args。


所以这很糟糕,但在大多数转换(“优化”)通道已经运行之后,alloca 的固定逻辑的后期扩展很合理地解释了这一点。注意-O0doesn't literally mean "no optimization",只是表示“编译快,调试一致”。


相关:

How does gcc choose to number temporary variables from -fverbose-asm? - 另一个关于 -O0 alloca asm 的讨论,同样的猜测是在 GIMPLE 通道的后期,甚至在 RTL 中扩展它。还有 alloca / snprintf 的优化 asm,这要简单得多。事实上,这几乎是重复的;这个问题也确实询问了 alloca 代码。

doing seemingly un-needed ops (crackme) - 我非常轻松地评论了基本相同的 asm(对于 32 位模式),但主要是在讨论手动混淆的 asm。

How does GCC implement variable-length arrays? 显示了这个糟糕代码的 32 位版本,但没有评论它有多糟糕。

【讨论】:

以上是关于禁用优化的 c alloca 函数的奇怪汇编代码 - gcc 使用 DIV 和 IMUL 为常数 16,并转换?的主要内容,如果未能解决你的问题,请参考以下文章

C:malloc/calloc/realloc/alloca内存分配函数

试图了解 x86 上 alloca() 函数的汇编实现

如何在 gcc 中禁用编译器优化?

了解 GCC 的 alloca() 对齐和看似错过的优化

构造一个指向 alloca 的函数指针会导致链接器错误?

在 C 中包装 alloca 函数