【问题标题】:Doubts about assembly code translated by gcc for implementation of N factorial by C对由 gcc 翻译的汇编代码以用 C 实现 N 阶乘的怀疑
【发布时间】:2021-09-27 16:30:19
【问题描述】:

我对分解阶乘函数感到困惑。

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

【问题讨论】:

  • 仅供参考,功能不正确。它又乘以x
  • 那些正在设置和访问参数x和局部变量result
  • OT: long result =x; --> long result =1;
  • OT:您在long 中计算但返回int
  • 很奇怪,看起来 GCC7(与您的 asm godbolt.org/z/jMhjsvfdM 匹配,后来 GCC 省略了 rep 前缀)正在以非常复杂的方式计算 RCX 中的循环结束条件。即使在-O2,这也不是仅启用部分优化 (-O1) 的结果,尽管早期的 GCC 没有这样做。例如GCC 4.9 -O1 只是按书面方式编译。 godbolt.org/z/1PPndxo4v。您可以将其报告为 GCC 的 bugzilla 上的一个错过优化错误

标签: c assembly x86-64


【解决方案1】:

(问题的更新更改了 C 和 asm,删除了问题仍然询问的 movq %rax, %rdx,但否则会使答案的第一部分无效。请参阅编辑历史记录或点击此答案中的 Godbolt 链接看看这部分指的是什么。)

movq %rax, %rdx 正在复制符号扩展 x(32 位 int 到 64 位 long),用于表达式 result * x 表达式的循环中隐式执行 @ 987654332@。请注意,它避免了每次通过循环时都像 C 抽象机那样重做符号扩展。 (与 GCC5 及更早版本不同,它或多或少地按照书面形式进行编译,只有正常的转换 like do{}while loop structure。)

它以符号扩展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's 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、-march=skylake-avx512 之前没有 SIMD qword 乘法,并且从多个 pmuludq 操作中合成它很慢)。

【讨论】:

  • 感谢您的精彩解释,仔细阅读后我已经明白leaq、leal和subq指令的作用,但是它是通过这种方式计算一个常数值1的。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2023-01-31
  • 2021-08-16
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-03-13
相关资源
最近更新 更多