// Copyright Advanced Micro Devices, Inc., or its affiliates. // SPDX-License-Identifier: MIT #pragma once #include #include namespace rocRoller { namespace Serialization { template concept CVariant = requires { // clang-format off { std::variant_size::value } -> std::convertible_to; // clang-format on }; static_assert(CVariant>); static_assert(!CVariant); template concept CNamedVariant = requires(T const& t) { // clang-format off requires CVariant; { name(t) } -> std::convertible_to; // clang-format on }; template struct VariantTypeKeySpecifier { static std::string TypeKey() { return "type"; } }; /** * For a variant whose alternatives are not variant, returns name(v). * * If the alternative held in `v` is also a variant, returns a '.'-separated * path of the name of each alternative in the hierarchy. */ template std::string typePath(Var const& v); /** * Splits `input` into two pieces: * - The first is `input` up until the first occurrence of `delim`, * or the entire input if `delim` is not in `input`. * - The second is everything after the first occurrence of `delim`, * or an empty string if `delim` is not in `input`. */ inline std::tuple splitFirst(std::string const& delim, std::string const& input) { auto delimPos = input.find(delim); if(delimPos == std::string::npos) return {input, ""}; auto delimEnd = delimPos + delim.size(); return {input.substr(0, delimPos), input.substr(delimEnd)}; } /** * Serializes an instantiation of std::variant with a 'type' field that redirects to each alternative of the * variant. * * In addition to the CNamedVariant requirements, each alternative must be serializable. * * If the type field should be named something other than 'type', or if some alternatives are not serializable, * the subclass can reimplement `mapping` as appropriate, or specialize VariantTypeKeySpecifier. * * For a hierarchy of variants, this will automatically group the keys using typePath. This allows only one * key which contains the types of every variant alternative in path (eg. Edge.DataFlow.Offset). * * This mechanism uses the context mechanism of the serialization framework. The object passed is a std::string. * Each level of the hierarchy will consume the first token of the string (everthing up until the first `.`) * and pass the rest on to the next level. Once the string is empty, it will revert to EmptyContext. This also * allows the Adhoc dimension to use this for its `m_name` field. */ template struct DefaultVariantMappingTraits { using iot = IOTraits; using AlternativeFn = std::function; using AlternativeMap = std::map; static constexpr AlternativeMap GetAlternatives() { AlternativeMap rv; AddAlternatives<0>(rv); return rv; } const static AlternativeMap alternatives; template static constexpr auto GetAlternative() { using AltType = std::variant_alternative_t; auto typeName = name(DefaultConstruct::call()); return std::make_pair(typeName, DefaultConstruct::call); } template static void AddAlternatives(AlternativeMap& alts) { if constexpr(AltIdx < std::variant_size_v) { auto alt = GetAlternative(); AssertFatal( alts.count(alt.first) == 0, "Duplicate alternative names: ", alt.first); alts.insert(alt); AddAlternatives(alts); } } static void mapping(IO& io, T& exp, Context& ctx) { // Only Empty or String is supported. static_assert( std::same_as || std::same_as); std::string myTypePath; // std::string typeName; std::string typeKey = VariantTypeKeySpecifier::TypeKey(); std::optional remainingCtx; bool mapTypeName = true; if(iot::outputting(io)) { myTypePath = typePath(exp); } if constexpr(std::same_as) { if(iot::outputting(io)) AssertFatal(ctx == myTypePath); else myTypePath = ctx; } else { iot::mapRequired(io, typeKey.c_str(), myTypePath); } auto [typeName, rest] = splitFirst(".", myTypePath); if(!iot::outputting(io)) { exp = alternatives.at(typeName)(); } if(!rest.empty()) remainingCtx = rest; std::visit( [&io, &remainingCtx](auto& theExp) { using U = std::decay_t; if(remainingCtx) { MappingTraits::mapping(io, theExp, *remainingCtx); } else { MappingTraits::mapping(io, theExp); } }, exp); } static void mapping(IO& io, T& exp) { AssertFatal((std::same_as)); Context ctx; return mapping(io, exp, ctx); } }; template const typename DefaultVariantMappingTraits::AlternativeMap DefaultVariantMappingTraits::alternatives = DefaultVariantMappingTraits::GetAlternatives(); struct RemainingTypePathVisitor { template std::string operator()(Var const& v) { return typePath(v); } template requires(!CVariant) std::string operator()(T const&) { return ""; } }; template std::string typePath(Var const& v) { auto myName = name(v); auto rest = std::visit(RemainingTypePathVisitor{}, v); if(rest.empty()) return myName; return myName + "." + rest; } } }