Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Virtual function compiler optimization c++

class Base 
{
public:
    virtual void fnc(size_t nm) 
    {
        // do some work here
    }

    void process()
    {
        for(size_t i = 0; i < 1000; i++)
        {
            fnc(i);
        }
    }
}  

Can and will the c++ compiler optimize calls to the fnc function from the process funtion, considering its going to be the same function every time it's invoked inside the loop ? Or is it gonna fetch the function adress from the vtable every time the function is invoked ?

like image 311
Igor Mitrovic Avatar asked May 04 '17 18:05

Igor Mitrovic


2 Answers

I checked an example on godbolt.org. the result is that NO, none of the compiler optimise that.

Here's the test source:

class Base 
{
public:
// made it pure virtual to decrease clutter
    virtual void fnc(int nm) =0;
    void process()
    {
        for(int i = 0; i < 1000; i++)
        {
            fnc(i);
        }
    }
};

void test(Base* b ) {
    return b->process();
}

and the generated asm:

test(Base*):
        push    rbp       ; setup function call 
        push    rbx
        mov     rbp, rdi  ; Base* rbp 
        xor     ebx, ebx  ; int ebx=0;
        sub     rsp, 8    ; advance stack ptr
.L2:
        mov     rax, QWORD PTR [rbp+0]  ; read 8 bytes from our Base*
                                        ; rax now contains vtable ptr 
        mov     esi, ebx                ; int parameter for fnc
        add     ebx, 1                  ; i++
        mov     rdi, rbp                ; (Base*) this parameter for fnc
        call    [QWORD PTR [rax]]       ; read vtable and call fnc
        cmp     ebx, 1000               ; back to the top of the loop 
        jne     .L2
        add     rsp, 8                  ; reset stack ptr and return
        pop     rbx
        pop     rbp
        ret

as you can see it reads the vtable on each call. I guess it's because the compiler can't prove that you don't change the vtable inside the function call (e.g. if you call placement new or something silly), so, technically, the virtual function call could change between iterations.

like image 110
Joseph Ireland Avatar answered Oct 19 '22 14:10

Joseph Ireland


Usually, compilers are allowed to optimize anything that doesn't change the observable behavior of a program. There are some exceptions, such as eliding non-trivial copy constructors when returning from a function, but it can be assumed that any change in expected code generation that does not change the output or the side effects of a program in the C++ Abstract Machine can be done by the compiler.

So, can devirtualizing a function change the observable behavior? According to this article, yes.

Relevant passage:

[...] optimizer will have to assume that [virtual function] might change the vptr in passed object. [...]

void A::foo() { // virtual 
 static_assert(sizeof(A) == sizeof(Derived)); 
 new(this) Derived; 
}

This is call of placement new operator - it doesn’t allocate new memory, it just creates a new object in the provided location. So, by constructing a Derived object in the place where an object of type A was living, we change the vptr to point to Derived’s vtable. Is this code even legal? C++ Standard says yes."

Therefore, if the compiler does not have access to the definition of the virtual function (and know the concrete type of *this at compile type), then this optimization is risky.

According to this same article, you use -fstrict-vtable-pointers on Clang to allow this optimization, at the risk of making your code less C++ Standard complying.

like image 34
KABoissonneault Avatar answered Oct 19 '22 13:10

KABoissonneault