6

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!

Gabriel Garcia
  • 1,000
  • 10
  • 11

3 Answers3

5

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.

Avi
  • 21,182
  • 26
  • 82
  • 121
Jan Hubička
  • 609
  • 6
  • 5
1

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++.

Mats Petersson
  • 126,704
  • 14
  • 140
  • 227
  • @AlanStokes: And your explanation is? – Mats Petersson Sep 29 '13 at 15:41
  • The compiler does know the type. I am telling it with `static_cast`. It is safe. I am telling it with `final` and a constant-expression PMF. Am I missing something? (Again, I know there are no guarantees for inlining, but this seems like such a simple optimization opportunity) – Gabriel Garcia Sep 29 '13 at 16:02
  • I would expect it to "unfold" the call to constant PMF to a regular method call. It *does* do this when not in a virtual inheritance context, for contrast. – Gabriel Garcia Sep 29 '13 at 16:08
  • I believe the compiler is "taking the safe option", because you are making it complex with a function pointer. One reason may of course be that there simply isn't much need to optimise this type of construct - after all, pointer to virtual member functions is a bit of a tautology - you are calling a function that is already supposed to be called via an indirection, and then making a second indirection. Compiler optimisations are done (primarily) on stuff that is common in the compiled code... – Mats Petersson Sep 29 '13 at 16:16
  • 1
    @Gabriel Garcia: Casting the object pointer to a specific pointer type does not tell the compiler the specific type of the object. In fact it tells the compiler absolutely nothing. The fact that the compiler was able to optimize the first call has nothing to do with your cast. With or without cast, the first call is still a virtual call. The only reason it was inlined is that the compiler was able to determine the dynamic type of the object at compile time. You can remove your `static_cast` from the first call, and you will see that the compiler still inlines the first call. – AnT stands with Russia Sep 29 '13 at 20:21
  • 1
    @Gabriel Garcia: The second call has more complicated semantics. It depends on two "variables" instead of one: 1) the dynamic type of the object, 2) the pointer-to-member value. It is true that in your example both can be easily determined at compile time. But apparently the compiler saw it as something too complicated to optimize fully. – AnT stands with Russia Sep 29 '13 at 20:27
  • @AndreyT if I remove the `static_cast` from the first call, it does not get inlined. Why would it? The actual type of `b` could be something else besides `Derived` belonging to a different translation unit. The reason the call was devirtualized (and subsequently inlined) was because of the `final` qualifier on `Derived::f`. Casting the pointer does tell the compiler a darn lot. – Gabriel Garcia Sep 30 '13 at 03:50
  • See Jan Hubicka's answer. It explains why this doesn't work [and it's basically "There is no code in GCC to do this"]. – Mats Petersson Sep 30 '13 at 07:38
0

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.

Matthieu M.
  • 287,565
  • 48
  • 449
  • 722