【问题标题】:Why can't gcc devirtualize this function call?为什么 gcc 不能去虚拟化这个函数调用?
【发布时间】:2018-08-01 01:25:32
【问题描述】:
#include <cstdio>
#include <cstdlib>
struct Interface {
    virtual void f() = 0;
};

struct Impl1: Interface {
    void f() override {
        std::puts("foo");
    }
};

// or __attribute__ ((visibility ("hidden")))/anonymous namespace
static Interface* const ptr = new Impl1 ;

int main() {
    ptr->f();
}

当用g++-7 -O3 -flto -fdevirtualize-at-ltrans -fipa-pta -fuse-linker-plugin编译时,上面的ptr-&gt;f()调用不能被去虚拟化。

似乎没有外部库可以修改ptr。这是 GCC 优化器的缺陷,还是因为某些其他来源在这种情况下使去虚拟化不可用?

Godbolt link

更新: 好像是clang-7 with -flto -O3 -fwhole-program-vtables -fvisibility=hidden is the only compiler+flags (as in 2018/03) that can devirtualize this program

【问题讨论】:

  • 您想解决的实际问题是什么?你为什么做这个?也许对实际问题有更好或其他的解决方案?请花点时间read about the XY problem
  • 如果将ptr 放入匿名命名空间会发生什么?
  • @Bathsheba 在 Godbolt 上试过。仍然是虚拟通话。
  • 很奇怪。本来以为 gcc 在这种情况下会应用 -fdevirtualize-at-ltrans :它是简单的标准 C++。好问题。
  • 使用这样的匿名命名空间似乎可以启用去虚拟化:godbolt.org/g/exzQrC

标签: c++ gcc compiler-optimization


【解决方案1】:

我刚刚做了另一个实验,就像在 Omnifarious answer 中一样。但是这次我让指针指向一个静态对象:

Impl1 x;
static Interface* const ptr = &x ;

GCC do devirtualize the function call, -O2 就足够了。我没有看到 C++ 标准中的任何规则会使指向静态存储的指针与指向动态存储的指针不同。

ptr指向的地址允许更改对象。因此编译器必须跟踪该地址发生的事情,以了解对象的实际动态类型。所以我的观点是优化器实现者可能认为在实际程序中跟踪堆上发生的事情太难了,所以编译器就不会这样做。

【讨论】:

  • 这应该是评论,而不是答案。答案应该足够详细地解决以下问题:优化是否合法?在编译器中实现优化有多难?为什么 GCC 不执行优化?值得实施吗?
  • @HadiBrais 老实说,我犹豫是否将它放在对 Omnifarious 答案的评论中,因为它的答案被这个实验无效。另一方面,这不是一个适当的评论。当我能够证明之前的答案缺少某些东西而我仍然无法提供完美的答案时,我已经养成了给出部分答案的习惯。
  • 一般情况下,您可以将其作为评论发布在问题下方。如果它足够好,其他人会告诉您将其推广为答案。
  • @HadiBrais 我发现评论无法阅读,我更喜欢 wiki 的讨论页面之类的东西,这样可以通过协作找到解决方案。 (那是海里的一瓶)。
  • 这里有两种技术可以协作获得一个好的/更好的答案:1-通过在 cmets 部分中讨论问题 2-通过建议对现有答案的编辑。如果这是一个非常有趣的问题,可以将其发布在 Reddit 或 Hacker News 上,以激发冗长的讨论并吸引更广泛的受众。
【解决方案2】:

如果将 ptr 移到 main 函数中,结果会非常有说服力,并强烈暗示 gcc 不希望对指针进行去虚拟化。

disassembly for this 表明如果“已初始化静态标志”为假,它会初始化静态然后直接跳回到虚函数调用,即使在这之间可能没有发生任何事情。

这告诉我 gcc 天生就相信任何类型的全局持久指针都必须始终被视为指向未知类型的指针。

事实上,情况比这还要糟糕。如果添加局部变量,那么在创建局部变量和调用f 之间是否发生对静态指针上的f 的调用很重要。显示f 插入案例的程序集在这里:Another godbolt link;并且很容易自己在网站上重新安排它,看看一旦没有插入另一个调用,程序集如何变成 f 的内联。

因此,gcc 必须假设指针所指的实际类型可能会在控制流出于任何原因离开函数时发生变化。并且是否声明为const 是无关紧要的。如果它的地址被占用或其他任何事情,它也无关紧要。

clang 做同样的事情。这对我来说似乎过于谨慎,但我不是编译器编写者。

【讨论】:

  • 这应该是评论,而不是答案。答案应该足够详细地解决以下问题:优化是否合法?在编译器中实现优化有多难?为什么 GCC 不执行优化?值得实施吗?
  • @HadiBrais - 评论太大了。也是唯一能给你真正答案的人。我建议为错过的优化机会提交错误报告。我自己填写了这些内容,他们被认真对待。
  • 感谢您花时间提交错误报告。我同意这是获得答案的唯一方法。您可以根据此更新此答案。
猜你喜欢
  • 2019-08-23
  • 2019-06-06
  • 1970-01-01
  • 2016-09-22
  • 1970-01-01
  • 1970-01-01
  • 2015-08-27
  • 1970-01-01
  • 2017-06-04
相关资源
最近更新 更多