https://gcc.gnu.org/bugzilla/show_bug.cgi?id=108181

            Bug ID: 108181
           Summary: [missed optimization] Call to virtual function under
                    runtime index should be optimized into jump with an
                    offset
           Product: gcc
           Version: 13.0
            Status: UNCONFIRMED
          Severity: normal
          Priority: P3
         Component: c++
          Assignee: unassigned at gcc dot gnu.org
          Reporter: m.cencora at gmail dot com
  Target Milestone: ---

Given code below compiled with g++ 11 or newer, compiler should be able to
optimize 'get' into similar code as manually-optimized 'get_opt'.

g++ -std=c++20 -O2

#include <bit>
#include <cstdint>

struct foo
{
    virtual constexpr int& get0() noexcept = 0;
    virtual constexpr int& get1() noexcept  = 0;
    virtual constexpr int& get2() noexcept  = 0;
    virtual constexpr int& get3() noexcept  = 0;
    virtual constexpr int& get4() noexcept  = 0;
    virtual constexpr int& get5() noexcept  = 0;
    virtual constexpr int& get6() noexcept  = 0;
    virtual constexpr int& get7() noexcept  = 0;
    virtual constexpr int& get8() noexcept  = 0;
    virtual constexpr int& get9() noexcept  = 0;
};


template <typename T, unsigned idx>
constexpr auto memPtr = nullptr;

template <typename T>
constexpr auto memPtr<T, 0> = &T::get0;

template <typename T>
constexpr auto memPtr<T, 1> = &T::get1;

template <typename T>
constexpr auto memPtr<T, 2> = &T::get2;

template <typename T>
constexpr auto memPtr<T, 3> = &T::get3;

template <typename T>
constexpr auto memPtr<T, 4> = &T::get4;

template <typename T>
constexpr auto memPtr<T, 5> = &T::get5;

template <typename T>
constexpr auto memPtr<T, 6> = &T::get6;

template <typename T>
constexpr auto memPtr<T, 7> = &T::get7;

template <typename T>
constexpr auto memPtr<T, 8> = &T::get8;

template <typename T>
constexpr auto memPtr<T, 9> = &T::get9;


int& get(unsigned idx, foo* f) noexcept
{
    switch (idx)
    {
        case 0:
            return (f->*memPtr<foo, 0>)();
        case 1:
            return (f->*memPtr<foo, 1>)();
        case 2:
            return (f->*memPtr<foo, 2>)();
        case 3:
            return (f->*memPtr<foo, 3>)();
        case 4:
            return (f->*memPtr<foo, 4>)();
        case 5:
            return (f->*memPtr<foo, 5>)();
        case 6:
            return (f->*memPtr<foo, 6>)();
        case 7:
            return (f->*memPtr<foo, 7>)();
        case 8:
            return (f->*memPtr<foo, 8>)();
        case 9:
            return (f->*memPtr<foo, 9>)();
        default:
            __builtin_unreachable();
    }
}

int& get_opt(unsigned idx, foo* f) noexcept
{
     // assuming System V x64 ABI
    struct RawMemPtr
    {
        std::uintptr_t v[2];
    };

    using PtrType = int& (foo::*)() noexcept;

    const RawMemPtr rawPtr{{sizeof(void*) * idx + 1, 0}};
    const auto ptr = std::bit_cast<PtrType>(rawPtr);

    return (f->*ptr)();
}

Reply via email to