Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

remove boost demangle #2405

Open
psychocoderHPC opened this issue Oct 1, 2024 · 13 comments · May be fixed by #2472
Open

remove boost demangle #2405

psychocoderHPC opened this issue Oct 1, 2024 · 13 comments · May be fixed by #2472

Comments

@psychocoderHPC
Copy link
Member

psychocoderHPC commented Oct 1, 2024

template<typename T>
inline const std::string demangled = boost::core::demangle(typeid(T).name());

We aim to remove boost completely. With clang 15+ and gcc 11+ we can remove this code with the following code.
These versions support std::source_location.

#include <source_location>
#include <string>
#include <string_view>
    /// \file
    /// use source_location to derive the demangled type name
    /// based on:
    /// https://www.reddit.com/r/cpp/comments/lfi6jt/finally_a_possibly_portable_way_to_convert_types/?utm_source=share&utm_medium=web3x&utm_name=web3xcss&utm_term=1&utm_content=share_button

    struct DummyType
    {
    };

    template<typename T>
    inline auto EmbedTypeIntoSignature()
    {
        return std::string_view{std::source_location::current().function_name()};
    }

    template<typename T>
    struct Demangled
    {
        static auto name()
        {
            constexpr size_t testSignatureLength = sizeof("DummyType") - 1;
            auto const DummySignature = EmbedTypeIntoSignature<DummyType>();
            // count char's until the type name starts
            auto const startPosition = DummySignature.find("DummyType");
            // count char's after the type information by removing type name information and pre information
            auto const tailLength = DummySignature.size() - startPosition - testSignatureLength;
            auto const EmbeddingSignature = EmbedTypeIntoSignature<T>();
            auto const typeLength = EmbeddingSignature.size() - startPosition - tailLength;
            return EmbeddingSignature.substr(startPosition, typeLength);
        }
    };

    template<typename T>
    auto demangled = Demangled<T>::name();
@psychocoderHPC psychocoderHPC added this to the 2.0.0 milestone Oct 1, 2024
@fwyzard
Copy link
Contributor

fwyzard commented Oct 1, 2024

Can it be constexpr ?

@fwyzard
Copy link
Contributor

fwyzard commented Oct 1, 2024

In general, I do not share the "remove boost at all costs" goal, so to me the new implementation needs to be at least as good as the old one, or possibly better.

@SimeonEhrig
Copy link
Member

std::source_location::current() is actual pretty well. I think it is more a question if we want remove the compilers, which does not support std::source_location::current().

@SimeonEhrig
Copy link
Member

SimeonEhrig commented Oct 1, 2024

@fwyzard If you want, you can test it here: https://godbolt.org/z/P6MsrKTKv

Update: a more advanced example with namespace: https://godbolt.org/z/159Gs313h

@SimeonEhrig
Copy link
Member

Can it be constexpr ?

Yes. My minimal example produces only 15 lines assembler including printing: https://godbolt.org/z/YhxKx6n3W
(looks like there is a small bug with the string termination symbol \0).

@fwyzard
Copy link
Contributor

fwyzard commented Oct 1, 2024

Can it be constexpr ?

Yes: https://godbolt.org/z/cbKdEPz4q .

@psychocoderHPC
Copy link
Member Author

Can it be constexpr ?

It was first constexpr but in some places, it produced some issues.

@psychocoderHPC
Copy link
Member Author

In general, I do not share the "remove boost at all costs" goal, so to me the new implementation needs to be at least as good as the old one, or possibly better.

I understand, the motivation was always that boost is not testing nvcc and HIP and this produced in the past many issues.
Nevertheless this implementation is doing what it should.
Removing boost will reduce the test complexity a lot.

@fwyzard
Copy link
Contributor

fwyzard commented Oct 2, 2024

Note that GCC and clang produce different output in some cases, for example for std::string:

gcc

the name is: std::__cxx11::basic_string<char>

clang

the name is: std::basic_string<char>

But maybe the old one was also doing this ?

@fwyzard
Copy link
Contributor

fwyzard commented Oct 2, 2024

Ah, looks like the two give a different output for inline namespaces.

inline namespace nested {
    struct Type {};
}

int main(){
    std::cout << "the name is: " << demangled<Type> << '\n';
}

Gives:

gcc

the name is: nested::Type

clang

the name is: Type

@fwyzard
Copy link
Contributor

fwyzard commented Feb 1, 2025

Coming back to this, I found a few more issues with this approach:

  • MSVC includes class, struct or enum in the type name, so using struct DummyType to detect where the type name ends up in the compiler generated string gives incorrect results; using a fundamental type like double works around this.
  • nvc++ (derived from the PGI compiler) does not return the full function type signature in std::source_location::current().function_name(), only the C-style function name, e.g. EmbedTypeIntoSignature; using __PRETTY_FUNCTION__ returns a viable string.
  • as we already discussed, the full demangled function type signature gets embedded in the binary, not just the type name, which kind of adds some bloat to the binary; I found a blog post with an interesting technique on how to solve this: https://rodusek.com/posts/2021/03/09/getting-an-unmangled-type-name-at-compile-time/ .

@fwyzard
Copy link
Contributor

fwyzard commented Feb 1, 2025

Here is an example, putting it all together: https://godbolt.org/z/1xT5Ejszx .

/*
 * Compile-time demangled type name, based on
 *   https://www.reddit.com/r/cpp/comments/lfi6jt/finally_a_possibly_portable_way_to_convert_types/
 * and
 *   https://rodusek.com/posts/2021/03/09/getting-an-unmangled-type-name-at-compile-time/
 */

#include <array>
#include <iostream>
#include <source_location>
#include <string_view>
#include <utility>

namespace
{
    // Use a fundamental type to detect the prefix and suffix, because MSVC includes the class, struct, or union
    // keyword in the type signature.
    using test_pattern_type = double;

    constexpr std::string_view test_pattern_name("double");

    // Generate a function name with full type signature that includes the user-provided type name.
    // Use the compiler-specific extensions for known compilers because nvc++ does not include the full type signature
    // in std::source_location::current().function_name() .
    template<typename T>
    consteval std::string_view get_function_name()
    {
#if defined(__GNUC__) || defined(__clang__) || defined(__PGI) || defined(__NVCOMPILER)
        // gcc, clang, PGI, nvc++
        return __PRETTY_FUNCTION__;
#elif defined(_MSC_VER)
        // MSVC
        return __FUNCSIG__;
#else
        // unknown compiler, use the c++20 standard approach
        return std::source_location::current().function_name();
#endif
    }

    consteval size_t prefix_size()
    {
        return get_function_name<test_pattern_type>().find(test_pattern_name);
    }

    consteval size_t suffix_size()
    {
        return get_function_name<test_pattern_type>().size() - prefix_size() - test_pattern_name.size();
    }

    template<typename T>
    consteval auto demangle_as_substr()
    {
        constexpr std::string_view text = get_function_name<T>();
        constexpr size_t start = prefix_size();
        constexpr size_t end = text.size() - suffix_size();
        static_assert(start < end);
        constexpr size_t length = end - start;
        return text.substr(start, length);
    }

    template<std::size_t... Idxs>
    consteval auto substring_as_array(std::string_view str, std::index_sequence<Idxs...>)
    {
        // Add null termination
        return std::array{str[Idxs]..., '\0'};
    }

    template<typename T>
    consteval auto demangle_as_array()
    {
        constexpr std::string_view name = demangle_as_substr<T>();
        return substring_as_array(name, std::make_index_sequence<name.size()>{});
    }

    template<typename T>
    inline constexpr auto storage = demangle_as_array<T>();

    template<typename T>
    consteval std::string_view demangle()
    {
        return std::string_view{storage<T>.data(), storage<T>.size()};
    }

} // namespace

template<typename T>
inline constexpr std::string_view const demangled = demangle<T>();

/******************************************************************************/

using Data = std::array<int, 42>;

namespace test {
    template<typename X>
    struct Type{};
}

int main(void)
{
    std::puts(demangled<int>.data());
    std::puts(demangled<Data>.data());
    std::puts(demangled<test::Type<void>>.data());
}

GCC

int
std::array<int, 42>
test::Type<void>

Clang

int
std::array<int, 42>
test::Type<void>

MSVC

int
class std::array<int,42>
struct test::Type<void>

NVC++

int
std::array<int, 42UL>
test::Type<void>

@fwyzard
Copy link
Contributor

fwyzard commented Feb 1, 2025

I've put it up here: https://github.com/fwyzard/demangle :-)

@fwyzard fwyzard linked a pull request Feb 1, 2025 that will close this issue
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging a pull request may close this issue.

3 participants