qlibs/reflect

Binary size when using reflect::for_each

SpintroniK opened this issue · 11 comments

This isn't really an issue, but I was wondering if it would be possible to improve the for_each function.
I've noticed that this example: https://godbolt.org/z/sqvn9bh1j shows how to overload the operator<< to print a struct's variables names and values using reflect::for_each.

Looking at the output from gcc, I see this : .string "constexpr std::string_view reflect::v1_0_8::detail::function_name() [with auto V = ref<const int>{ext<foo>.foo::a}; std::string_view = std::basic_string_view<char>]" twice, because the struct has two members.

My guess is that this library kind of started from there : https://www.reddit.com/r/cpp/comments/18bv197/c20_60_loc_constexpr_get_name_for_members_no.
My question is: can we avoid calling get_name<0>(...), get_name<1>(...) and pass all the struct's members directly as non-type template parameters in order to get a single line in the final binary?

I don't know if that makes sense, so I tried to make sure this is actually possible. This is (a very bad example of) what I came up with: https://godbolt.org/z/hxeKno1Pv.
Would it be an improvement to add that sort of thing in your implementation?
Would that actually reduce the overall binary size (thinking about embedded systems here)?

Thanks for pointing it out, defo an issue and thanks for your suggestions, it's important to fix that. Will take a look at keep updates in this thread.

Yeah, the implementation is broken ATM, the whole array can be precomputed at compile time with lazy fields. Going to fix that. Thanks again for pointing it out.

No worries. Looking forward to seeing how it works under the hood!

Have been playing around and managed to remove the bloat and optimize the code - right now for enum_name but the same applies to others. Full example here - https://t.co/6RvagrWa00

Looks awesome! Thanks for sharing your progress. I'm guessing you won't be using mph in the final implementation?

Sorry, gone a bit too wide with the example. I don't think name_to_enum belongs in reflection either way but likely reflect will end up with some policies to trade-off run-time performance/binary size or compilation-times as different solutions have different properities.

No worries. Having some policies is a pretty good idea. It's one of the features I like the most with sml.

type_name and member_name have been addressed by eeb8606, enum_name is still WIP

That is pretty good indeed!
Since you mentioned the potential addition of policies, do you think it would be possible to "customize" the member struct?
For instance, if I only want the name and value of my aggregate's members, would that be possible to have a policy that allows to keep only those fields in the member struct?

for_each as well as enum_name now have been improved with minimal size and optimized performance.
for_each API has been changed to allow getting only require fields

Full example here - > https://godbolt.org/z/fcdGTT7ff

For the following examples

#include <iostream>
int main() {
  reflect::for_each([](auto I) {
    std::cout
      << reflect::type_name(f) << '.'                   // foo, foo
      << reflect::member_name<I>(f) << ':'              // a  , b
      << reflect::type_name(reflect::get<I>(f)) << '='  // int, E
      << reflect::get<I>(f) << '('                      // 42 , B
      << reflect::size_of<I>(f)                         // 4  , 4
      << reflect::align_of<I>(f)                        // 4  , 4
      << reflect::offset_of<I>(f) << ')'                // 0  , 4
      << '\n';
  }, f);
}

It produces (with -Os)

main:
        push    rbx
        mov     edx, 3
        mov     esi, OFFSET FLAT:reflect::v1_0_9::type_name<foo>()::{lambda()#1}::operator()() const::name
        mov     edi, OFFSET FLAT:_ZSt4cout
        call    std::basic_ostream<char, std::char_traits<char> >& std::__ostream_insert<char, std::char_traits<char> >(std::basic_ostream<char, std::char_traits<char> >&, char const*, long)
        mov     esi, 46
        mov     rdi, rax
        call    std::basic_ostream<char, std::char_traits<char> >& std::operator<< <std::char_traits<char> >(std::basic_ostream<char, std::char_traits<char> >&, char)
        mov     edx, 1
        mov     esi, OFFSET FLAT:reflect::v1_0_9::member_name<0ul, foo>(foo const&)::{lambda()#1}::operator()() const::name
        mov     rdi, rax
        call    std::basic_ostream<char, std::char_traits<char> >& std::__ostream_insert<char, std::char_traits<char> >(std::basic_ostream<char, std::char_traits<char> >&, char const*, long)
        mov     esi, 58
        mov     rdi, rax
        call    std::basic_ostream<char, std::char_traits<char> >& std::operator<< <std::char_traits<char> >(std::basic_ostream<char, std::char_traits<char> >&, char)
        mov     edx, 3
        mov     esi, OFFSET FLAT:reflect::v1_0_9::type_name<int>()::{lambda()#1}::operator()() const::name
        mov     rdi, rax
        call    std::basic_ostream<char, std::char_traits<char> >& std::__ostream_insert<char, std::char_traits<char> >(std::basic_ostream<char, std::char_traits<char> >&, char const*, long)
        mov     esi, 61
        mov     rdi, rax
        call    std::basic_ostream<char, std::char_traits<char> >& std::operator<< <std::char_traits<char> >(std::basic_ostream<char, std::char_traits<char> >&, char)
        mov     esi, 42
        mov     rdi, rax
        call    std::basic_ostream<char, std::char_traits<char> >::operator<<(int)
        mov     esi, 40
        mov     rdi, rax
        call    std::basic_ostream<char, std::char_traits<char> >& std::operator<< <std::char_traits<char> >(std::basic_ostream<char, std::char_traits<char> >&, char)
        mov     esi, 4
        mov     rdi, rax
        call    std::basic_ostream<char, std::char_traits<char> >& std::basic_ostream<char, std::char_traits<char> >::_M_insert<unsigned long>(unsigned long)
        mov     esi, 4
        mov     rdi, rax
        call    std::basic_ostream<char, std::char_traits<char> >& std::basic_ostream<char, std::char_traits<char> >::_M_insert<unsigned long>(unsigned long)
        xor     esi, esi
        mov     rdi, rax
        call    std::basic_ostream<char, std::char_traits<char> >& std::basic_ostream<char, std::char_traits<char> >::_M_insert<unsigned long>(unsigned long)
        mov     esi, 41
        mov     rdi, rax
        call    std::basic_ostream<char, std::char_traits<char> >& std::operator<< <std::char_traits<char> >(std::basic_ostream<char, std::char_traits<char> >&, char)
        mov     esi, 10
        mov     rdi, rax
        call    std::basic_ostream<char, std::char_traits<char> >& std::operator<< <std::char_traits<char> >(std::basic_ostream<char, std::char_traits<char> >&, char)
        mov     edx, 3
        mov     esi, OFFSET FLAT:reflect::v1_0_9::type_name<foo>()::{lambda()#1}::operator()() const::name
        mov     edi, OFFSET FLAT:_ZSt4cout
        call    std::basic_ostream<char, std::char_traits<char> >& std::__ostream_insert<char, std::char_traits<char> >(std::basic_ostream<char, std::char_traits<char> >&, char const*, long)
        mov     esi, 46
        mov     rdi, rax
        call    std::basic_ostream<char, std::char_traits<char> >& std::operator<< <std::char_traits<char> >(std::basic_ostream<char, std::char_traits<char> >&, char)
        mov     edx, 1
        mov     esi, OFFSET FLAT:reflect::v1_0_9::member_name<1ul, foo>(foo const&)::{lambda()#1}::operator()() const::name
        mov     rdi, rax
        call    std::basic_ostream<char, std::char_traits<char> >& std::__ostream_insert<char, std::char_traits<char> >(std::basic_ostream<char, std::char_traits<char> >&, char const*, long)
        mov     esi, 58
        mov     rdi, rax
        call    std::basic_ostream<char, std::char_traits<char> >& std::operator<< <std::char_traits<char> >(std::basic_ostream<char, std::char_traits<char> >&, char)
        mov     edx, 1
        mov     esi, OFFSET FLAT:reflect::v1_0_9::type_name<E>()::{lambda()#1}::operator()() const::name
        mov     rdi, rax
        call    std::basic_ostream<char, std::char_traits<char> >& std::__ostream_insert<char, std::char_traits<char> >(std::basic_ostream<char, std::char_traits<char> >&, char const*, long)
        mov     esi, 61
        mov     rdi, rax
        call    std::basic_ostream<char, std::char_traits<char> >& std::operator<< <std::char_traits<char> >(std::basic_ostream<char, std::char_traits<char> >&, char)
        mov     edi, 1
        mov     rbx, rax
        call    std::basic_string_view<char, std::char_traits<char> > reflect::v1_0_9::enum_name<E, reflect::v1_0_9::fixed_string<char, 0ul>{}, -1, 128>(E)
        mov     rdi, rbx
        mov     rsi, rdx
        mov     rdx, rax
        call    std::basic_ostream<char, std::char_traits<char> >& std::__ostream_insert<char, std::char_traits<char> >(std::basic_ostream<char, std::char_traits<char> >&, char const*, long)
        mov     esi, 40
        mov     rdi, rax
        call    std::basic_ostream<char, std::char_traits<char> >& std::operator<< <std::char_traits<char> >(std::basic_ostream<char, std::char_traits<char> >&, char)
        mov     esi, 4
        mov     rdi, rax
        call    std::basic_ostream<char, std::char_traits<char> >& std::basic_ostream<char, std::char_traits<char> >::_M_insert<unsigned long>(unsigned long)
        mov     esi, 4
        mov     rdi, rax
        call    std::basic_ostream<char, std::char_traits<char> >& std::basic_ostream<char, std::char_traits<char> >::_M_insert<unsigned long>(unsigned long)
        mov     esi, 4
        mov     rdi, rax
        call    std::basic_ostream<char, std::char_traits<char> >& std::basic_ostream<char, std::char_traits<char> >::_M_insert<unsigned long>(unsigned long)
        mov     esi, 41
        mov     rdi, rax
        call    std::basic_ostream<char, std::char_traits<char> >& std::operator<< <std::char_traits<char> >(std::basic_ostream<char, std::char_traits<char> >&, char)
        mov     esi, 10
        mov     rdi, rax
        call    std::basic_ostream<char, std::char_traits<char> >& std::operator<< <std::char_traits<char> >(std::basic_ostream<char, std::char_traits<char> >&, char)
        xor     eax, eax
        pop     rbx
        ret
reflect::v1_0_9::member_name<1ul, foo>(foo const&)::{lambda()#1}::operator()() const::name:
        .byte   98
reflect::v1_0_9::member_name<0ul, foo>(foo const&)::{lambda()#1}::operator()() const::name:
        .byte   97
reflect::v1_0_9::enum_name<E, reflect::v1_0_9::fixed_string<char, 0ul>{}, -1, 128>(E)::{lambda<auto $N0>()#1}::operator()<1u>() const::{lambda()#1}::operator()() const::name2:
        .byte   66
reflect::v1_0_9::enum_name<E, reflect::v1_0_9::fixed_string<char, 0ul>{}, -1, 128>(E)::{lambda<auto $N0>()#1}::operator()<0u>() const::{lambda()#1}::operator()() const::name2:
        .byte   65
reflect::v1_0_9::enum_name<E, reflect::v1_0_9::fixed_string<char, 0ul>{}, -1, 128>(E)::{lambda<auto $N0>()#1}::operator()<4294967295u>() const::{lambda()#1}::operator()() const::name2:
        .byte   40
        .byte   69
        .byte   41
        .byte   52
        .byte   50
        .byte   57
        .byte   52
        .byte   57
        .byte   54
        .byte   55
        .byte   50
        .byte   57
        .byte   53
reflect::v1_0_9::type_name<E>()::{lambda()#1}::operator()() const::name:
        .byte   69
reflect::v1_0_9::type_name<foo>()::{lambda()#1}::operator()() const::name:
        .byte   102
        .byte   111
        .byte   111
reflect::v1_0_9::type_name<int>()::{lambda()#1}::operator()() const::name:
        .byte   105
        .byte   110
        .byte   116

Closing for now as fixes will be released in the next version. Feel free to reopen and/or comment if there are any follow-ups. Thanks again for raising the issue and constructive comments.