Почему эта простая функция не виртуализируется? - PullRequest
5 голосов
/ 02 апреля 2019

Рассмотрим следующий код:

struct A {
    virtual A& operator+=(const A& other) noexcept = 0;
};

void foo_inner(int *p) noexcept { *p += *p; }
void foo_virtual_inner(A *p) noexcept { *p += *p; }

void foo(int *p) noexcept
{
    return foo_inner(p);
}

struct Aint : public A {
    int i;
    A& operator+=(const A& other) noexcept override final
    { 
// No devirtualization of foo_virtual with:
        i += dynamic_cast<const Aint&>(other).i; 
// ... nor with:
//      i += reinterpret_cast<const Aint&>(other).i; 
        return *this;
    }
};

void foo_virtual(Aint *p) noexcept
{
    return foo_virtual_inner(p);
}

Насколько я могу судить, и foo(), и foo_virtual() должны компилироваться в один и тот же объектный код.Компилятор имеет всю информацию, необходимую для де-виртуализации вызова на operator+= в foo_virtual_inner(), когда он вызывается с foo_virtual.Но - ни GCC 8.3, ни MSVC 19.10, ни clang 8 не делают этого .Естественно, я использовал флаг максимальной оптимизации (-O3 или /Ox).

Почему?Это ошибка, или я что-то упустил?


clang 8 вывод:

foo(int*):                               # @foo(int*)
        shl     dword ptr [rdi]
        ret
foo_virtual(Aint*):                  # @foo_virtual(Aint*)
        mov     rax, qword ptr [rdi]
        mov     rax, qword ptr [rax]
        mov     rsi, rdi
        jmp     rax                     # TAILCALL

GCC 8.3 вывод:

foo(int*):
        sal     DWORD PTR [rdi]
        ret
foo_virtual(Aint*):
        mov     rax, QWORD PTR [rdi]
        mov     rax, QWORD PTR [rax]
        cmp     rax, OFFSET FLAT:Aint::operator+=(A const&)
        jne     .L19
        push    rbx
        xor     ecx, ecx
        mov     edx, OFFSET FLAT:typeinfo for Aint
        mov     esi, OFFSET FLAT:typeinfo for A
        mov     rbx, rdi
        call    __dynamic_cast
        test    rax, rax
        je      .L20
        mov     eax, DWORD PTR [rax+8]
        add     DWORD PTR [rbx+8], eax
        pop     rbx
        ret
.L19:
        mov     rsi, rdi
        jmp     rax
foo_virtual(Aint*) [clone .cold.1]:
.L20:
        call    __cxa_bad_cast

MSVC 19.10 вывод:

p$ = 8
void foo(int * __ptr64) PROC                                    ; foo
        mov     eax, DWORD PTR [rcx]
        add     eax, eax
        mov     DWORD PTR [rcx], eax
        ret     0
void foo(int * __ptr64) ENDP                                    ; foo

p$ = 8
void foo_virtual(Aint * __ptr64) PROC                  ; foo_virtual
        mov     rax, QWORD PTR [rcx]
        mov     rdx, rcx
        rex_jmp QWORD PTR [rax]
void foo_virtual(Aint * __ptr64) ENDP 

PS - Чем объясняется весь этот бизнес typeinfo в скомпилированном коде под GCC?

1 Ответ

1 голос
/ 04 апреля 2019

GCC предполагает, что Aint * p указывает на экземпляр Aint * p (но не думает, что это обязательно произойдет), и поэтому он спекулятивно отклоняет вызов оператора + =, а проверка typeinfo является встроенной копией.-fno-devirtualize-specultively приводит к тому же коду, что и Clang и MSVC.

_Z11foo_virtualP4Aint:
.LFB4:
        .cfi_startproc
        movq    (%rdi), %rax
        movq    %rdi, %rsi
        movq    (%rax), %rax
        jmp     *%rax
...