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 的倍数的一部分。 (如果你用调试器单步执行,你可以看到 div
和 imul
的顺序。)
我猜内置函数的固定逻辑序列是出于某种原因以这种方式编写的,并且在 -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 #
有了这个简单的 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 的倍数的一部分。 (如果你用调试器单步执行,你可以看到 div
和 imul
的顺序。)
我猜内置函数的固定逻辑序列是出于某种原因以这种方式编写的,并且在 -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 #