6
votes

Now, I know there are no guarantees for inlining, but...

Given the following:

struct Base {
    virtual int f() = 0;
};

struct Derived : public Base {
    virtual int f() final override {
        return 42;
    }
};

extern Base* b;

We have that:

int main() {
    return static_cast<Derived*>(b)->f();
}

Compiles down to:

main:
    movl    $42, %eax
    ret

Yet...

int main() {
    return (static_cast<Derived*>(b)->*(&Derived::f))();
}

Compiles down to:

main:
    pushl   %ebp
    movl    %esp, %ebp
    andl    $-16, %esp
    subl    $16, %esp
    movl    b, %eax
    movl    (%eax), %edx
    movl    %eax, (%esp)
    call    *(%edx)
    leave
    ret

Which is really saddening.

Why is that call to PMF not being inlined? The PMF is a constant expression!

3
Did you use -O2 or use default parameters?CS Pei
I tried with -O2 in both GCC and Clang. Same result.Gabriel Garcia

3 Answers

5
votes

The problem here is that in the first case type based devirtualization turns the indirect call into a direct call. When you add a member pointer, type based devirtualization can not be used (since it works by frontend passing down to optimization information about type and virtual method being called that is not trivially known from the in this case). GCC may be able to constant fold the actual access into virutal table by knowing that B is a class and knowing that its member can be called only after it has been constructed. At the moment it don't do such analysis.

I would suggest filling in enhancement request to GCC bugzilla.

1
votes

It is not always possible to inline pointer to function (unless the compiler is able to figure out what the pointer is actually pointing at, which is often difficult, so the compiler may "give up" before you expect it to).

EDIT

To expand on my answer: The first priority in all compilers is to generate CORRECT code (although sometimes this doesn't happen either!). Optimisation, such as inlining functions, is something the compiler will only do when "it is safe". The compiler may not quite "understand" that the above expression is indeed a constant expression, and therefore fall back to "let's do the safe thing" (which is to call via the virtual function table, rather than inline the function). Pointers to virtual member functions are quite a tricky subject in C++.

0
votes

This is indeed a bit annoying, but it is not surprising.

Most compilers transformations work by pattern matching: recognize a pattern and apply the transformation. So what is the reason that this may not be optimized ? Well, maybe simply that there is no transformation written based on this pattern.

Most specifically here, it is possible the issue may be about &Derived::f, there is a specific representation used by both gcc and clang for a pointer to virtual function: they use a pointer to a trampoline function that performs the virtual resolution. Therefore it might simply be that this trampoline function is one nesting too many for the compiler to see through.