GCC对简单类进行去虚拟化

GCC de-virtualization of simple class

本文关键字:虚拟化 简单 GCC      更新时间:2023-10-16

以下代码不会被gcc破坏。我能做些什么来说服gcc放弃机会吗?

struct B /* final */ {
    virtual int foo() { return 3; }
};
struct C {
    B& b;
    __attribute__((noinline))
    C( B& b ) : b(b) {
    }
    int foo() {
        return b.foo();
    }
};
int main() {
    B b;
    C c(b);
    int res = c.foo();
    return res;
}

我天真地认为这将是一种机会化(至少是推测性的)和内联。

在构造函数是另一个编译单元的实际代码中,编译器将无法看到构造函数的主体(因此是noinline属性)。模仿一些现实世界的要求也不是最终的。

当编译器在编译时知道对象的类型时,就会发生虚拟化。在这里,您有C::C的noinline,这使得main无法知道在构造过程中,什么类型的对象最终会变成C::b。

在构造函数是另一个编译单元的实际代码中,编译器将无法看到构造函数的主体(因此是noinline属性)。模仿一些现实世界的要求也不是最终的。

为了去虚拟化,编译器通常需要能够证明类层次结构是密封的。如果对构造函数的调用在单独的翻译单元中,编译器无法证明这一点。然而,使用链接时优化可以在翻译单元之间为优化器提供信息,这可以更容易地证明有关类层次结构和引用的事实。

下面是一个使用clang的例子。

b.hpp

#ifndef B_H
#define B_H
struct B {
  virtual int foo();
};
#endif

b.cpp

#include "b.h"
int B::foo() { return 3; };

c.hpp

#ifndef C_H
#define C_H
#include "b.h"
struct C {
  B& b;
  C(B& b);
  int foo();
};
#endif

c.cpp

#include "c.h"
C::C(B& b) : b(b) {}
int C::foo() {
    return b.foo();
}

main.cpp

#include <iostream>
#include "b.h"
#include "c.h"
int main(const int argc, const char* argv[argc]) {
  B b;
  C c(b);
  std::cout << c.foo() << std::endl;
  return 0;
}

由于优化器对C::C的调用站点一无所知(构造函数),它对CCD_ 2的运行时类型一无所知。所以,它不能去虚拟化CCD_ 3。

C: :foo

_ZN1C3fooEv:                            # @_ZN1C3fooEv
    .cfi_startproc
# BB#0:
    movq    (%rdi), %rdi
    movq    (%rdi), %rax
    jmpq    *(%rax)                 # TAILCALL  <== pointer call

但是,为优化器提供链接时间信息(-flto)允许它证明类层次结构与调用站点是密封的。

B: :foo

0000000000400960 <_ZN1B3fooEv>:
  400960:   b8 03 00 00 00          mov    $0x3,%eax
  400965:   c3                      retq   
  400966:   66 2e 0f 1f 84 00 00    nopw   %cs:0x0(%rax,%rax,1)
  40096d:   00 00 00 

主要

0000000000400970 <main>:
  400970:   41 56                   push   %r14
  400972:   53                      push   %rbx
  400973:   50                      push   %rax
  400974:   48 c7 04 24 78 0a 40    movq   $0x400a78,(%rsp)
  40097b:   00 
  40097c:   48 8d 3c 24             lea    (%rsp),%rdi
  400980:   e8 db ff ff ff          callq  400960 <_ZN1B3fooEv> # <== direct call