Why don't compilers devirtualize calls for a final class when inlining?

319 Views Asked by At
struct base {
    virtual void vcall() = 0;
};

struct foo final : base {
    void vcall() final;
};

void call_base(base& b) {
    b.vcall();
}

void call_foo(foo& f) {
    call_base(f);
}

void call_foo_directly(foo& f) {
    f.vcall();
}

clang 16 produces:

call_base(base&):
        mov     rax, qword ptr [rdi]
        jmp     qword ptr [rax]
call_foo(foo&):
        mov     rax, qword ptr [rdi]
        jmp     qword ptr [rax]
call_foo_directly(foo&):
        jmp     foo::vcall()@PLT

GCC and MSVC produce the same result, so it's not a problem limited to clang. Shouldn't it be possible for call_foo to contain a non-virtual call to foo::vcall() too? Is this a missed optimization, or is it possible for the call to be virtual?

See live example on Compiler Explorer.

1

There are 1 best solutions below

11
SrPanda On

The compiler does try, but there needs to be something to inline, if a function has no implementation it's just a empty call and that's what gets compiled; adding final just prevents the use of override later. To compile it with optimization volatile is kinda required so everything isn't optimized away.

Run this in bodbolt.

struct base {
    volatile int num = 111;
    virtual void vcall() = 0;
};

struct foo final : base {
    void vcall() {
        num += 222;
    };
};

void call_base(base& b) {
    b.vcall();
}
void call_foo(foo& f) {
    call_base(f);
}

void call_foo_directly(foo& f) {
    f.vcall();
}

void main_func(void) {
    foo val;
    call_foo(val);
    call_foo_directly(val);
}

This is the clang-15 with -O3 partial disassembly (same with -O2); vs couldn't inline call_foo.

main_func():                          # @main_func()
        mov     dword ptr [rsp - 8], 111
        add     dword ptr [rsp - 8], 222
        add     dword ptr [rsp - 8], 222
        ret