2016-04-18 9 views
16

Wenn ich mit gcc 6 -O3 -std=c++14 folgenden Code zu kompilieren, bekomme ich schön und leer main:Ist das ein Fehler im Gcc Optimizer?

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

Aber uncommenting letzte Zeile in main "Pausen" Optimierung:

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 kann beide Versionen optimieren.

Irgendwelche Ideen, was ist los? Triff ich ein Optimierungslimit? Wenn ja, ist es konfigurierbar?

+0

Aussehen wie ein Fehler für mich. –

+0

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

+0

Ich würde es nicht einen Fehler nennen, es sei denn, es erzeugt falschen Code oder weniger optimierten Code als generiert von einer niedrigeren Optimierungsstufe. –

Antwort

5

Ich lief g++ mit -fdump-ipa-inline, um mehr Informationen darüber zu erhalten, warum Funktionen inline sind oder nicht.

Für den Testfall mit main() Funktion und drei Objekte erstellt Ich habe:

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

Dieser Fehlercode in gcc/gcc/IPV-inline.c gesetzt:

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; 
} 

Dann Ich entdeckte, dass die kleinste Änderung, um g ++ inline diese Funktionen zu machen ist, eine Deklaration hinzuzufügen:

Ich war nicht in der Lage zu finden, in Code, warum int main() ist nicht heiß, aber wahrscheinlich sollte dies für eine andere Frage verlassen werden.

Interessanter ist der zweite Teil der bedingten I eingefügt oben. Die Absicht war, nicht inline zu sein, wenn der Code wächst, und Sie erzeugten ein Beispiel, wenn der Code nach dem vollständigen Inlining schrumpft.

Ich denke, dies verdient auf GCC's bugzilla gemeldet werden, aber ich bin mir nicht sicher, ob Sie es einen Fehler nennen können - Schätzung der Inline-Auswirkungen ist eine Heuristik und als solche wird erwartet, dass sie in den meisten Fällen korrekt funktioniert, nicht alle von ihnen.

+0

Ich denke, es ist ein interessantes Experiment, um eine [creduce] (https: //embed.cs.utah .edu/creduce /) um ein minimales Beispiel zu erhalten, das nicht inline ist. –

+0

Ich nehme an, dass 'main()' nicht als hot betrachtet wird, da es undefiniertes Verhalten ist, dass es explizit oder über einen Zeiger aufgerufen wird (C++ 11 3.6.1/3: "Die Funktion main darf nicht verwendet werden ein Programm"). Gcc "weiß", dass 'main()' nur einmal aufgerufen werden kann. –

+0

@MichaelBurr Sie haben völlig Recht mit dem undefinierten Verhalten, aber da ich nicht finden konnte, was main() 'cold in der GCC-Quelle macht, ist es immer noch" weiß nicht "für mich :) Ich sah keine speziellen Fälle in' may_hot_p', kommt also wahrscheinlich von der Heuristik selbst. –