gcc 如何从-fverbose-asm 中选择对临时变量进行编号?

How does gcc choose to number temporary variables from -fverbose-asm?

有了这个简单的 c:

#define _XOPEN_SOURCE
#include <stdio.h>
#include <stdlib.h>
#include <alloca.h>

int main(){
    char *buf = alloca(600);
    snprintf(buf,600,"hi!, %d, %d, %d\n", 1,2,3);
    puts(buf);
}

生成于 $ cc -S -fverbose-asm a.c:
.

text
    .section    .rodata
.LC0:
    .string "hi!, %d, %d, %d\n"
    .text
    .globl  main
    .type   main, @function
main:
    pushq   %rbp    #
    movq    %rsp, %rbp  #,
    subq    , %rsp   #,
# a.c:7:    char *buf = alloca(600);
    movl    , %eax   #, tmp102
    subq    , %rax    #, tmp89
    addq    8, %rax  #, tmp90
    movl    , %ecx   #, tmp103
    movl    [=11=], %edx    #, tmp93
    divq    %rcx    # tmp103
    imulq   , %rax, %rax #, tmp92, tmp94
    subq    %rax, %rsp  # tmp94,
    movq    %rsp, %rax  #, tmp95
    addq    , %rax   #, tmp96
    shrq    , %rax    #, tmp97
    salq    , %rax    #, tmp98
    movq    %rax, -8(%rbp)  # tmp98, buf
# a.c:8:    snprintf(buf,600,"hi!, %d, %d, %d\n", 1,2,3);
   ...

gcc 根据什么决定对这些临时变量进行编号? (tmp102, tmp89, tmp90, ...)?

此外,有人可以解释一下,为什么 alloca 使用 %rax (addq 8, %rax) 而不是 %rsp (subq 8, %rsp) 来分配内存?这就是 alloca 的用途(根据手册页): alloca() 函数在 堆栈帧 中分配大小为 space 的字节 来电者。

How can have variables intermediate representation, when majority of them is immediate?

SSA (Static Single Assignment) internal representation of the program logic (like GCC's GIMPLE) 中,每个临时值都有一个单独的名称。当没有直接关联的 C 变量名称时,我假设数字来自自动编号的 SSA 变量。但我对 GCC 的内部结构不够熟悉,无法提供更多细节。如果您真的很好奇,您总是可以自己查看 GCC 源代码。但我相当有信心自动编号的 SSA vars 解释了它,并且完全有意义。

数字字面值实际上不会用 -fverbose-asm 获得任何名称。例如在优化的 GCC 输出 (from Godbolt) 中,我们将其视为将 args 放入寄存器的一部分:

...
        movl    , %r9d        #,
        movl    , %r8d        #,
        xorl    %eax, %eax      #
...

re: alloca: 它 最终抵消 RSP,subq %rax, %rsp,在将分配大小四舍五入到 a 之后16 的倍数。

此舍入保持堆栈对齐。 (请至少尝试 google 自己。当您缺少大量背景知识和概念时,您不能指望从头开始完全解释所有内容的答案。当您不了解细节时的东西,首先搜索被使用的技术术语。)

顺便说一句,gcc -O0 的 asm 效率低得惊人!它似乎使用 x / 16 * 16 而不是 x & 0xFFFF...F0 作为 将分配大小四舍五入到 16 的倍数的一部分。 (如果你用调试器单步执行,你可以看到 divimul 的顺序。)

我猜内置函数的固定逻辑序列是出于某种原因以这种方式编写的,并且在 -O0 GCC 没有通过它进行持续传播。但无论如何,这就是它使用 RAX 的原因。

也许 alloca 逻辑是用 GIMPLE 编写的,或者可能是 RTL 代码在某些转换通过之后才得到扩展。这可以解释为什么它的优化如此糟糕,即使它都是单个语句的一部分。 gcc -O0 对性能非常不利,但是 64 位 div 除以 16 与具有立即操作数的非常便宜的 and 相比非常糟糕。在 asm 中看到乘以 2 的幂作为立即数操作数也很奇怪;在正常情况下,编译器会将其优化为移位。

要查看不可怕的 asm,请查看启用优化后会发生什么,例如在 Godbolt. See also 上。然后它只是 sub 6, %rsp。但是它会在运行时浪费指令将指针对齐到 space(以保证 space 将按 16 字节对齐),即使 RSP 的对齐在那之后是静态已知的。

# GCC10.1 -O3 -fverbose-asm with alloca
...
        subq    6, %rsp           # reserve 600 + 16 bytes
        leaq    15(%rsp), %r12
        andq    $-16, %r12           # get a 16-byte aligned pointer into it
        movq    %r12, %rdi           # save the pointer for later instead of recalc before next call
        call    snprintf        #

愚蠢的编译器,%rsp 的对齐在那时是静态已知的,不需要 (x+15) & -16。请注意,-16 = 0xFFFFFFFFFFFFFFF0 在 64 位 2 的补码中,因此它是表达清除一些低位的 AND 掩码的便捷方式。

删除 alloca 并使用普通本地数组可提供更简单的代码:

# GCC10.1 -O3 with char buf[600]
        subq    6, %rsp
...
        movq    %rsp, %rdi
...
        call    snprintf        #