Это ошибка в gcc-оптимизаторе?

Когда я компилирую следующий код с gcc 6 -O3 -std=c++14, я получаю приятный и пустой main:

Dump of assembler code for function main():
   0x00000000004003e0 <+0>:     xor    %eax,%eax
   0x00000000004003e2 <+2>:     retq 

Но раскомментирование последней строки в главной оптимизации "разрывов":

Dump of assembler code for function main():
   0x00000000004005f0 <+0>:     sub    $0x78,%rsp
   0x00000000004005f4 <+4>:     lea    0x40(%rsp),%rdi
   0x00000000004005f9 <+9>:     movq   $0x400838,0x10(%rsp)
   0x0000000000400602 <+18>:    movb   $0x0,0x18(%rsp)
   0x0000000000400607 <+23>:    mov    %fs:0x28,%rax
   0x0000000000400610 <+32>:    mov    %rax,0x68(%rsp)
   0x0000000000400615 <+37>:    xor    %eax,%eax
   0x0000000000400617 <+39>:    movl   $0x0,(%rsp)
   0x000000000040061e <+46>:    movq   $0x400838,0x30(%rsp)
   0x0000000000400627 <+55>:    movb   $0x0,0x38(%rsp)
   0x000000000040062c <+60>:    movl   $0x0,0x20(%rsp)
   0x0000000000400634 <+68>:    movq   $0x400838,0x50(%rsp)
   0x000000000040063d <+77>:    movb   $0x0,0x58(%rsp)
   0x0000000000400642 <+82>:    movl   $0x0,0x40(%rsp)
   0x000000000040064a <+90>:    callq  0x400790 <ErasedObject::~ErasedObject()>
   0x000000000040064f <+95>:    lea    0x20(%rsp),%rdi
   0x0000000000400654 <+100>:   callq  0x400790 <ErasedObject::~ErasedObject()>
   0x0000000000400659 <+105>:   mov    %rsp,%rdi
   0x000000000040065c <+108>:   callq  0x400790 <ErasedObject::~ErasedObject()>
   0x0000000000400661 <+113>:   mov    0x68(%rsp),%rdx
   0x0000000000400666 <+118>:   xor    %fs:0x28,%rdx
   0x000000000040066f <+127>:   jne    0x400678 <main()+136>
   0x0000000000400671 <+129>:   xor    %eax,%eax
   0x0000000000400673 <+131>:   add    $0x78,%rsp
   0x0000000000400677 <+135>:   retq   
   0x0000000000400678 <+136>:   callq  0x4005c0 <[email protected]>

код

#include <type_traits>
#include <new>

namespace
{
struct ErasedTypeVTable
{
   using destructor_t = void (*)(void *obj);

   destructor_t dtor;
};

template <typename T>
void dtor(void *obj)
{
   return static_cast<T *>(obj)->~T();
}

template <typename T>
static const ErasedTypeVTable erasedTypeVTable = {
   &dtor<T>
};
}

struct ErasedObject
{
   std::aligned_storage<sizeof(void *)>::type storage;
   const ErasedTypeVTable& vtbl;
   bool flag = false;

   template <typename T, typename S = typename std::decay<T>::type>
   ErasedObject(T&& obj)
   : vtbl(erasedTypeVTable<S>)
   {
      static_assert(sizeof(T) <= sizeof(storage) && alignof(T) <= alignof(decltype(storage)), "");
      new (object()) S(std::forward<T>(obj));
   }

   ErasedObject(ErasedObject&& other) = default;

   ~ErasedObject()
   {
      if (flag)
      {
         ::operator delete(object());
      }
      else
      {
         vtbl.dtor(object());
      }
   }

   void *object()
   {
      return reinterpret_cast<char *>(&storage);
   }
};

struct myType
{
   int a;
};

int main()
{
   ErasedObject c1(myType{});
   ErasedObject c2(myType{});
   //ErasedObject c3(myType{});
}

clang может оптимизировать обе версии.

Есть идеи, что происходит? Я нажимаю некоторый лимит оптимизации? Если да, то он настраивается?

Ответы

Ответ 1

Я запустил g++ с помощью -fdump-ipa-inline, чтобы получить дополнительную информацию о том, почему функции являются или нет.

Для тестового теста с функцией main() и тремя созданными объектами я получил:

  (...)
  150 Deciding on inlining of small functions.  Starting with size 35.
  151 Enqueueing calls in void {anonymous}::dtor(void*) [with T = myType]/40.
  152 Enqueueing calls in int main()/35.
  153   not inlinable: int main()/35 -> ErasedObject::~ErasedObject()/33, call is unlikely and code size would grow
  154   not inlinable: int main()/35 -> ErasedObject::~ErasedObject()/33, call is unlikely and code size would grow
  155   not inlinable: int main()/35 -> ErasedObject::~ErasedObject()/33, call is unlikely and code size would grow
  (...)

Этот код ошибки установлен в gcc/gcc/ipa-inline.c:

  else if (!e->maybe_hot_p ()
       && (growth >= MAX_INLINE_INSNS_SINGLE
       || growth_likely_positive (callee, growth)))
{
      e->inline_failed = CIF_UNLIKELY_CALL;
      want_inline = false;
}

Затем я обнаружил, что наименьшее изменение, чтобы заставить g++ встроить эти функции, заключается в добавлении объявления:

int main() __attribute__((hot));

Мне не удалось найти код, почему int main() не считается горячим, но, вероятно, это нужно оставить для другого вопроса.

Более интересна вторая часть условного значения, вставленного выше. Цель заключалась в том, чтобы не включать, когда код будет расти, и вы создали пример, когда код сжимается после полной вставки.

Я думаю, что это заслуживает того, чтобы сообщалось о GCC bugzilla, но я не уверен, если вы можете назвать это ошибкой - оценка встроенного воздействия - это эвристический и как ожидается, в большинстве случаев они будут работать правильно, не все из них.