提问者:小点点

为什么GCC发出重复的`ret'?


在下面的C++示例中,我定义了一个计算树最左边路径高度的函数。

struct TreeNode {
  int value{};
  TreeNode *l = nullptr;
  TreeNode *r = nullptr;
};

int getLeftHeight(TreeNode *root) {
  int height = 0;
  while (root != nullptr) {
    root = root->l;
    height++;
  }
  return height;
}

在用GCC 9.3或10.1编译时(使用-o3),我得到以下x86。

getLeftHeight(TreeNode*):
        xor     eax, eax
        test    rdi, rdi
        je      .L4
.L3:
        mov     rdi, QWORD PTR [rdi+8]
        add     eax, 1
        test    rdi, rdi
        jne     .L3
        ret
.L4:
        ret

但是,当使用Clang 10进行编译时,如下所示,没有一个重复的ret

getLeftHeight(TreeNode*):
        xor     eax, eax
        test    rdi, rdi
        je      .LBB0_3
.LBB0_1:
        mov     rdi, qword ptr [rdi + 8]
        add     eax, 1
        test    rdi, rdi
        jne     .LBB0_1
.LBB0_3:
        ret

海合会为什么要这样做?

据我所知,ret将需要额外的一个字节来进行编码。 在这种情况下,由于发出的机器代码的对齐/填充,这可能无关紧要,但如果情况并非如此,并且这种重复发生在大型代码库中的所有地方,那么许多字节的机器代码最终可能只是重复的rets。

这种重复的特殊情况似乎也很容易发现和优化掉。


共1个答案

匿名用户

这似乎是GCC中错过的一个优化。

有一个bug#71923,其中有一个非常类似的例子,标记为“Missing-Optimization”。

话虽如此,但对性能的影响是微不足道的--主要是对代码大小的影响。 此外,当第二个ret被对齐时(现代x86目标通常就是这种情况),消除第一个ret通常不会对代码大小产生影响,但是如果消除,从循环中掉下来将导致在ret之前解码额外的nop指令,这会使代码路径变得更慢。

无论如何,总是欢迎向GCC发出拉请求。 您可以使用-fdump-passs查看优化传递列表,并使用-fdump-tree-...标志转储RTL/Gimple树(或者在Godbolt上查看)。 主要的问题是,现有的优化程序是否应该处理这个问题,或者是否需要添加一个新的优化程序(我不是GCC方面的专家,所以不能建议这样或那样的方法)。