2016-04-18 19 views
16

Quando compilo seguente codice con gcc 6 -O3 -std=c++14, ricevo bella e vuota main:Si tratta di un bug in gcc optimizer?

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

Ma decommentando ultima riga principale ottimizzazione "pause":

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

Codice

#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 può ottimizzare entrambe le versioni.

Qualche idea cosa sta succedendo? Sto colpendo qualche limite di ottimizzazione? Se è così, è configurabile?

+0

Sembra un insetto per me. –

+0

Riproducibile in GCC (Ubuntu 5.3.1-14ubuntu2) 5.3.1 20160413 – Zeta

+0

Non lo chiamerei un bug a meno che non generi codice errato o codice meno ottimizzato di quello generato da un livello di ottimizzazione inferiore. –

risposta

5

Ho eseguito g++ con -fdump-ipa-inline per ottenere ulteriori informazioni sul motivo per cui le funzioni sono o non sono in linea.

Per il testcase con la funzione main() e tre oggetti creati ho ottenuto:

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

Questo codice di errore si trova in 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; 
} 

Poi ho scoperto, che il più piccolo cambiamento per rendere g ++ in linea queste funzioni è quello di aggiungere una dichiarazione:

int main() __attribute__((hot)); 

non ero in grado di trovare nel codice perché int main() non è considerato caldo, ma probabilmente questo dovrebbe essere lasciato per un'altra domanda.

Più interessante è la seconda parte del condizionale che ho incollato sopra. L'intento era quello di non essere in linea quando il codice crescerà e hai prodotto un esempio quando il codice si restringe dopo l'inlining completo.

Penso che questo meriti di essere segnalato su GCC's bugzilla, ma non sono sicuro se si può chiamare un bug - la stima dell'impatto in linea è un euristico e come tale si prevede che funzioni correttamente nella maggior parte dei casi, non tutti di loro.

+0

Penso che sia un esperimento interessante eseguire un [credule] (https: //embed.cs.utah .edu/creduce /) per ottenere un esempio minimo che non riesce in linea. –

+0

Direi che 'main()' non è considerato caldo perché è un comportamento indefinito per essere chiamato esplicitamente o tramite un puntatore (C++ 11 3.6.1/3: "La funzione main non deve essere utilizzata all'interno di un programma"). Quindi gcc 'sa' che 'main()' può essere chiamato una sola volta. –

+0

@MichaelBurr Hai perfettamente ragione riguardo al comportamento indefinito, ma dato che non sono riuscito a trovare ciò che rende 'main()' freddo nella sorgente GCC è ancora "non so" per me :) Non ho visto casi speciali all'interno ' forse_hot_p', quindi probabilmente deriva dall'euristica caldo/freddo stesso. –

Problemi correlati