Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Compiler devirtualization, not too smart?

I wrote this short program to see how devirtualization would work. The compiler should be able to deduce the correct type:

#include <iostream>
using std::cout;
using std::endl;

class Base 
{
public:
    void foo() { cout << "Base::foo" << endl; }
    virtual void bar() { cout << "Base::bar" << endl; }
    virtual ~Base() = default;
};

class Child : public Base 
{
public:
    void foo() { cout << "Child::foo" << endl; }
    void bar() { cout << "Child::bar" << endl; }
};

int main()
{
    Base* obj = new Child;
    obj->foo();
    obj->bar();
    delete obj;
}

Compiled with -O2 -std=c++11 using gcc 5.3 and clang 3.7 via https://gcc.godbolt.org/.

What turned out is that neither compiler was able to optimize everything - gcc inlines foo() and makes virtual call to bar() while clang makes call to foo() and devirtualizes and inlines call to bar().

Meanwhile, if instead I call obj->bar(); and then obj->foo();, the compilers have no problem in optimizing - clang inlines both calls and gcc makes normal call to bar() instead of virtual one and inlines foo().

Can anyone explain this behavior?

like image 515
cailinscath Avatar asked Dec 20 '15 11:12

cailinscath


1 Answers

It's probably because the compiler thinks that inlining does not help because the cout is too expensive compared to the overhead of the function call. If you replace it with something simpler, e.g. an assigment to a member, it will get inlined. See below for the output of

#include <iostream>
using std::cout;
using std::endl;

class Base 
{
public:
    void foo() { i = 1; }
    virtual void bar() { i = 2; }
    virtual ~Base() = default;

    int i = 0;
};

class Child : public Base 
{
public:
    void foo() { i = 3; }
    void bar() { i = 4; }
};

int main()
{
    Base* obj = new Child;
    obj->foo();
    obj->bar();
    std::cout << obj->i << std::endl;
    //delete obj;
}

Assembly:

Base::bar():
        movl    $2, 8(%rdi)
        ret
Child::bar():
        movl    $4, 8(%rdi)
        ret
Base::~Base():
        ret
Child::~Child():
        ret
Child::~Child():
        jmp     operator delete(void*)
Base::~Base():
        jmp     operator delete(void*)
main:
        subq    $8, %rsp
        movl    $16, %edi
        call    operator new(unsigned long)
        movl    $4, %esi
        movl    std::cout, %edi
        call    std::basic_ostream<char, std::char_traits<char> >::operator<<(int)
        movq    %rax, %rdi
        call    std::basic_ostream<char, std::char_traits<char> >& std::endl<char, std::char_traits<char> >(std::basic_ostream<char, std::char_traits<char> >&)
        xorl    %eax, %eax
        addq    $8, %rsp
        ret
        subq    $8, %rsp
        movl    std::__ioinit, %edi
        call    std::ios_base::Init::Init()
        movl    $__dso_handle, %edx
        movl    std::__ioinit, %esi
        movl    std::ios_base::Init::~Init(), %edi
        addq    $8, %rsp
        jmp     __cxa_atexit
like image 91
Jens Avatar answered Nov 10 '22 13:11

Jens