Yes, kind of. As your methods are defined inline, they can be sometimes inlined.
Neither Clang nor GCC create a specialized B::ManyFoo(int)
, though.
I've amended the code to prevent unsuitable optimizations, and illustrate some behaviour:
struct A {
virtual int Foo() = 0;
virtual int ManyFoo(int N) {
int res = 0;
for (int i = 0; i < N; ++i) res += Foo();
return res;
}
};
struct B : A {
virtual int Foo() { return 3; }
};
B force_code_generation() { return {}; }
int dynamic_dispatch(A& object) { return object.ManyFoo(2); }
int static_dispatch (B value) { return value .ManyFoo(2); }
At a non-virtual callsite where the compiler is able to do static dispatch,
ManyFoo()
and Foo()
may be completely flattened away. GCC 8.2 with -O2 is able to evaluate the function at compile time:
mov eax, 6
ret
But Clang doesn't seem to do that optimization. It merely inlines the ManyFoo(2)
call, which uses virtual calls to invoke Foo()
. Pseudo-code:
static_dispatch(B* rdi):
push rbp
push rbx
push rax
rbx = rdi
rax = *rbx // load vtable
eax = call rax[0](rdi) // first Foo() call
ebp = eax
rax = *rbx // load vtable
rdi = rbx // move this pointer to rdi
eax = call rax[0](rdi)
eax += ebp // add the Foo() results
rsp += 8 // discard saved rax
pop rbx
pop rbp
return eax
With dynamic dispatch these optimizations are not generally possible. Clang adds no special optimizations and simply uses ordinary virtual calls. However, GCC 8.2 adds guards at the virtual callsites to optionally inline the virtual function. Here's the generated assembly rewritten as pseudo-code and reordered for clarity:
dynamic_dispatch(A& rdi):
rax = *rdi // load vtable from object
rdx = rax[8] // load ManyFoo(int) vtable entry
// check if ManyFoo(int) method is A::ManyFoo(int)
if (rdx != &A::ManyFoo(int)) {
// fallback for virtual ManyFoo(2) call, and return
esi = 2
goto rdx // tailcall
}
// We are now in the specialized A::ManyFoo(2) version.
// The loop for N=2 is unrolled.
push rbp
push rbx
rsp -= 8
// first Foo() call:
// check if Foo() is B::Foo(), else fall back to virtual call
ebx = 3 // result of the first B::Foo() call if it is inlined
rax = rax[0] // load Foo() vtable entry
if (rax != &B::Foo()) {
// fallback for first virtual Foo() call
rbp = rdi
eax = call rax(rdi)
ebx = eax // save result of first call
// second Foo() call:
// check again if Foo() is B::Foo()
// Can "this" even change its type???
rax = *rbp
rax = rax[0]
if (rax != &B::Foo()) {
// fallback for second virtual Foo() call
rdi = rbp
eax = call rax(rdi)
goto end
}
}
eax = 3 // result of second B::Foo() call
end:
// add the result of the calls and return
eax += ebx
rsp += 8
pop rbx
pop rbp
return eax
Neither Clang nor GCC change the generated code depending on whether B
is final
.
Source: view the assembly on the Godbolt Compiler Explorer.
ManyFoo
onB
and insert it intoB
's vtable.class C : public B
that redefinesFoo
but notManyFoo
? When the compiler processes C, it would use the B's version, which is then wrong.virtual Foo() = 0;
is not a valid syntax