Skip to content

[Optimization] Don't generate bad_variant_access logic for exhaustive visit using overloaded #74

@bhamiltoncx

Description

@bhamiltoncx

Using clang from trunk with -std=c++17 -Oz, the following code uses overloaded with an exhaustive set of lambdas to visit all cases of the variant:

https://godbolt.org/z/k-r72L

#include <https://raw.githubusercontent.com/mpark/variant/single-header/master/variant.hpp>
#include <functional>
#include <string>
#include <stdio.h>

namespace {
template <class... Ts>
struct overloaded : Ts... {
using Ts::operator()...;
};
template <class... Ts>
overloaded(Ts...) -> overloaded<Ts...>;
}

enum class Foo { kValue };
enum class Bar { kValue };
using Callback = std::function<std::string(void)>;

using SumType = mpark::variant<Foo, Bar, Callback>;

void DoStuff(SumType s) {
  mpark::visit(overloaded{
      [](Foo f) { printf("Foo!\n");},
      [](Bar b) { printf("Bar!\n");},
      [](const Callback& c) { printf("Callback: %s\n", c().c_str()); },
  },
  s);
} 

Even though the lambdas exhaustively handle all cases, MPark.Variant still generates error handling logic to throw bad_variant_access exceptions (which as far as I can tell should never happen).

I think it'd be a nice optimization to drop the error handling entirely if the visitation is exhaustive.

Here's the current state of the optimized assembly in case this gets fixed:

DoStuff(mpark::variant<Foo, Bar, std::function<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > ()> >): # @DoStuff(mpark::variant<Foo, Bar, std::function<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > ()> >)
        push    r14
        push    rbx
        sub     rsp, 40
        mov     rbx, rdi
        cmp     byte ptr [rdi + 32], -1
        lea     rdi, [rsp + 7]
        sete    byte ptr [rdi]
        push    1
        pop     rsi
        call    mpark::detail::any(std::initializer_list<bool>)
        test    al, al
        jne     .LBB0_8
        movzx   ecx, byte ptr [rbx + 32]
        cmp     rcx, 255
        push    -1
        pop     rax
        cmovne  rax, rcx
        cmp     rax, 2
        je      .LBB0_6
        cmp     rax, 1
        je      .LBB0_5
        mov     edi, offset .Lstr
        jmp     .LBB0_4
.LBB0_5:
        mov     edi, offset .Lstr.4
.LBB0_4:
        call    puts
        jmp     .LBB0_7
.LBB0_6:
        lea     r14, [rsp + 8]
        mov     rdi, r14
        mov     rsi, rbx
        call    std::function<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > ()>::operator()() const
        mov     rsi, qword ptr [r14]
        mov     edi, offset .L.str.3
        xor     eax, eax
        call    printf
        mov     rdi, r14
        call    std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >::~basic_string() [base object destructor]
.LBB0_7:
        add     rsp, 40
        pop     rbx
        pop     r14
        ret
.LBB0_8:
        call    mpark::throw_bad_variant_access()
mpark::detail::any(std::initializer_list<bool>): # @mpark::detail::any(std::initializer_list<bool>)
        xor     eax, eax
.LBB1_1:                                # =>This Inner Loop Header: Depth=1
        cmp     rsi, rax
        je      .LBB1_4
        cmp     byte ptr [rdi + rax], 0
        lea     rax, [rax + 1]
        je      .LBB1_1
        mov     al, 1
        ret
.LBB1_4:
        xor     eax, eax
        ret
mpark::throw_bad_variant_access():  # @mpark::throw_bad_variant_access()
        push    rax
        push    8
        pop     rdi
        call    __cxa_allocate_exception
        mov     qword ptr [rax], offset vtable for mpark::bad_variant_access+16
        mov     esi, offset typeinfo for mpark::bad_variant_access
        mov     edx, offset std::exception::~exception() [base object destructor]
        mov     rdi, rax
        call    __cxa_throw
mpark::bad_variant_access::~bad_variant_access() [deleting destructor]:      # @mpark::bad_variant_access::~bad_variant_access() [deleting destructor]
        push    rbx
        mov     rbx, rdi
        call    std::exception::~exception() [base object destructor]
        mov     rdi, rbx
        pop     rbx
        jmp     operator delete(void*)                  # TAILCALL
mpark::bad_variant_access::what() const:  # @mpark::bad_variant_access::what() const
        mov     eax, offset .L.str
        ret
std::function<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > ()>::operator()() const: # @std::function<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > ()>::operator()() const
        push    rbx
        cmp     qword ptr [rsi + 16], 0
        je      .LBB5_2
        mov     rbx, rdi
        call    qword ptr [rsi + 24]
        mov     rax, rbx
        pop     rbx
        ret
.LBB5_2:
        call    std::__throw_bad_function_call()
typeinfo name for mpark::bad_variant_access:
        .asciz  "N5mpark18bad_variant_accessE"

typeinfo for mpark::bad_variant_access:
        .quad   vtable for __cxxabiv1::__si_class_type_info+16
        .quad   typeinfo name for mpark::bad_variant_access
        .quad   typeinfo for std::exception

vtable for mpark::bad_variant_access:
        .quad   0
        .quad   typeinfo for mpark::bad_variant_access
        .quad   std::exception::~exception() [base object destructor]
        .quad   mpark::bad_variant_access::~bad_variant_access() [deleting destructor]
        .quad   mpark::bad_variant_access::what() const

.L.str:
        .asciz  "bad_variant_access"

.L.str.3:
        .asciz  "Callback: %s\n"

.Lstr:
        .asciz  "Foo!"

.Lstr.4:
        .asciz  "Bar!"

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions