[英]Doubts about assembly code translated by gcc for implementation of N factorial by C
我对阶乘函数的反汇编感到困惑。
代码
long factorial(int x)
{
long result = 1;
while (x > 1)
{
result = result * x;
x= x - 1;
}
return result;
}
我使用 gcc 命令反汇编阶乘函数gcc -S -O1 test.c
factorial:
.LFB0:
cmpl $1, %edi
jle .L2
movslq %edi, %rdx
leaq -1(%rdx), %rcx
leal -2(%rdi), %eax
subq %rax, %rcx
movl $1, %eax
.L3:
imulq %rdx, %rax
subq $1, %rdx
cmpq %rcx, %rdx
jne .L3
.L2:
movl $1, %eax
rep ret
我不明白下面的代码是做什么的,有人可以帮助我吗?
movq %rax, %rdx
leaq -1(%rax), %rcx
leal -2(%rdi), %esi
subq %rsi, %rcx
(对问题的更新更改了 C 和 asm,删除了问题仍然询问的movq %rax, %rdx
,但否则会使答案的第一部分无效。请参阅编辑历史记录或遵循此答案中的 Godbolt 链接请参阅本节所指的内容。)
movq %rax, %rdx
正在制作符号扩展x
(32 位int
到 64 位long
)的副本,用于在表达式result * x
隐式执行(long)x
表达式的循环中。 请注意,它避免了每次循环都像 C 抽象机那样重做符号扩展。 (与 GCC5 及更早版本不同,它们或多或少地按照编写的方式编译,只有像 do{}while 循环结构这样的普通转换。)
它以符号扩展x
2 个副本开始的事实是因为您的 C 以result=x
开头。 这是您的阶乘实现中的一个错误,因为您没有执行x--
,但编译器只是在实现您编写的内容。 实际上使用x--
会生成其他奇怪的代码( https://godbolt.org/z/345K6hbas ),例如leal -3(%rdi), %edi
/ addq $1, %rdi
仅与lea -2(%rdi), %edi
以防 LEA 产生 0xFFFFFFFF (-1) 并且 qword +1 进入高 32 位。 但这不可能发生,因为较早的 cmp/jcc 会提前返回x-1 <= 1
,因此 rdi-3+1 是另一个错过的优化。
其他 3 条指令(lea/lea/sub)是 GCC 是愚蠢的,我认为以复杂的方式计算常量1
作为 RCX 中的循环终止条件,以与 RDX 进行比较。 这是一个遗漏的优化错误,您可以报告GCC 的 bugzilla,因为它仍然发生在 -O2 ( https://godbolt.org/z/achGeePYb ) 的当前主干夜间构建中。
我猜测提升符号扩展导致创建此逻辑为时已晚,优化传递无法将其分类为合理的东西,或者以他们不能/不可以的方式。
顺便说一句,这看起来像 GCC7,因为它与您的 asm https://godbolt.org/z/jMhjsvfdM匹配。 后来的 GCC 省略了 rep 前缀(但否则会造成同样的混乱),早期的 GCC 要么使 asm 略有不同,要么(gcc5 及更早版本)直接进入循环而没有先做那么多。 但是他们每次循环迭代都会重做x
符号扩展(从 32 位int
到 64 位long
)。
即使在-O2
也会发生这种情况,因此这不是仅启用部分优化 (-O1) 的结果。 GCC8 和更早的在-O3
处自动矢量化,但这可能没有利润,这可能是 GCC9 和后来停止这样做的原因。 (x86 在 AVX-512 之前没有 SIMD qword 乘法, -march=skylake-avx512
,并且从多个pmuludq
操作中合成它很慢)。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.