2016-04-18 13 views
16

kiedy mogę skompilować następujący kod z gcc 6 -O3 -std=c++14, mam ładne i puste main:Czy to błąd w optymalizatorze gcc?

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

Ale odkomentowanie ostatnią linię w głównym "przerwy" optymalizacja:

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]> 

Code

#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 może zoptymalizować obie wersje.

Jakieś pomysły, co się dzieje? Czy trafiam w jakiś limit optymalizacji? Jeśli tak, czy można go skonfigurować?

+0

Wyglądam jak błąd. –

+0

Powtarzalny w GCC (Ubuntu 5.3.1-14ubuntu2) 5.3.1 20160413 – Zeta

+0

Nie nazwałbym tego błędem, chyba że wygeneruje niepoprawny kod lub mniej zoptymalizowany kod niż wygenerowany przez niższy poziom optymalizacji. –

Odpowiedz

5

Przeprowadziłem g++ z -fdump-ipa-inline, aby uzyskać więcej informacji o tym, dlaczego funkcje są lub nie są wbudowane.

Dla testcase z funkcji main() i trzech obiektów utworzonych uzyskałem:

(...) 
    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 
    (...) 

Ten kod błędu jest ustawiony w 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; 
} 

Następnie Odkryłem, że najmniejszą zmianą w celu wstawienia tych funkcji jest dodanie deklaracji:

int main() __attribute__((hot)); 

Nie byłem w stanie znaleźć w kodzie, dlaczego int main() nie jest uważany za gorący, ale prawdopodobnie powinno to pozostać dla innego pytania.

Bardziej interesująca jest druga część warunkowej, którą wkleiłem powyżej. Zamiarem było nie wstawiać, gdy kod się zwiększy, a Ty wytworzyłeś przykład, gdy kod kurczy się po całkowitym wstawieniu.

Myślę, że zasługuje to na zgłoszenie na GCC's bugzilla, ale nie jestem pewien, czy można go nazwać błędem - oszacowanie wpływu inline jest heurystyczne i jako takie powinno działać poprawnie w większości przypadków, nie wszystkie z nich.

+0

Myślę, że to ciekawy eksperyment do uruchomienia [creduce] (https: //embed.cs.utah .edu/creduce /), aby uzyskać minimalny przykład, który nie jest wbudowany. –

+0

Domyślam się, że 'main()' nie jest uważany za gorący, ponieważ jest niezdefiniowanym zachowaniem, aby można go było nazwać jawnie lub przez wskaźnik (C++ 11 3.6.1/3: "Główna funkcja nie będzie używana wewnątrz program"). Tak więc gcc "wie", że 'main()' może być wywołane tylko raz. –

+0

@MichaelBurr Masz całkowitą rację co do niezdefiniowanego zachowania, ale ponieważ nie mogłem znaleźć tego, co sprawia, że ​​'main()' jest zimne w źródle GCC to wciąż "nie wiem" dla mnie :) Nie widziałem żadnych specjalnych przypadków wewnątrz ' maybe_hot_p', więc prawdopodobnie pochodzi z samego heurystycznego ciepła/zimna. –