#pragma once // // This file is distributed under the MIT License. See LICENSE.md for details. // #include #include #include #include #include #include #include "llvm/ADT/ArrayRef.h" #include "llvm/ADT/SmallVector.h" #include "llvm/ADT/StringMap.h" #include "llvm/ADT/StringRef.h" #include "llvm/ADT/iterator_range.h" #include "llvm/Support/CommandLine.h" #include "llvm/Support/Error.h" #include "revng/ADT/STLExtras.h" #include "revng/Pipeline/CLOption.h" #include "revng/Pipeline/Container.h" #include "revng/Pipeline/ContainerSet.h" #include "revng/Pipeline/Context.h" #include "revng/Pipeline/Option.h" #include "revng/Pipeline/Target.h" #include "revng/Support/Assert.h" #include "revng/Support/Debug.h" #include "revng/Support/YAMLTraits.h" namespace pipeline { template concept HasName = requires() { { T::Name } -> convertible_to; }; // clang-format off template concept IsContainer = std::derived_from, ContainerBase>; template concept IsNotContainer = not IsContainer; template constexpr ReturnType invokableReturnTypeImpl(ReturnType (InvokableType::*F)(Args...)) { return ReturnType(); } template using invokableReturnType = decltype(invokableReturnTypeImpl(&InvokableType::run)); template constexpr bool invokableTypeReturnsError() { return std::is_same_v, llvm::Error>; } template concept ReturnsError = invokableTypeReturnsError(); /// A Invokable is a class with the following characteristics: /// /// * It must have a static constexpr field named Name that is a string /// describing its name. Mostly used for debug purposes. /// * a RetT run(T...) method where the first argument must be a Context& or /// const Context&, arguments after the first must be K& or const K& where /// K is derived from a container. /// /// Options after the last container can be of any type, but for each of /// them there must exists an entry in a constexpr tuple named Options that /// defines their name and default value. /// /// RetT can either be llvm::Error or void, if it is void then the invokable /// never fails. /// template concept Invokable = convertible_to> and HasName; // clang-format on /// TODO: Remove after updating to clang-format with concept support. struct ClangFormatPleaseDoNotBreakMyCode2; // clang-format off // clang-format on namespace detail { using StringArrayRef = llvm::ArrayRef; template auto &getContainer(ContainerSet &Containers, llvm::StringRef Name) { return llvm::cast(Containers[Name]); } template OptionType deserializeImpl(llvm::StringRef Value) { using ReturnType = OptionType; if constexpr (std::is_same_v) return Value.str(); else return llvm::cantFail(deserialize(Value)); } template OptionType getOption(const llvm::StringMap &Map) { auto Name = getOptionName(); if (auto Iter = Map.find(Name); Iter != Map.end()) { return deserializeImpl(Iter->second); } else if (CLOptionBase::hasOption(Name) and CLOptionBase::getOption(Name).isSet()) { const CLOptionBase &Option = CLOptionBase::getOption(Name); return deserializeImpl(Option.get()); } else { return getOptionDefault(); } } template auto invokeImpl(DeducedContextType &Ctx, InvokableType &Pipe, auto (InvokableType::*F)(DeducedContextType &, AllArgs...), ContainerSet &Containers, const StringArrayRef &ArgsNames, const std::tuple *, const std::tuple *, const std::integer_sequence &, const llvm::StringMap &OptionArgs, const std::integer_sequence &) { using namespace std; return (Pipe.*F)(Ctx, getContainer>(Containers, ArgsNames[S])..., getOption(OptionArgs)...); } template using Tuple = std::tuple; template using Cond_t = std::conditional_t; template using ContainerToTuple = Cond_t, Tuple, Tuple<>>; template using NonContainerTypeToTuple = Cond_t, Tuple<>, Tuple>; template std::tuple * concatImpl(const std::tuple *, const std::tuple *) { return nullptr; } template using ConcatImpl = std::decay_t(), std::declval()))>; template auto *concatMultiple(const First *, const Rest *...Others) { if constexpr (sizeof...(Rest) == 0) { return static_cast(nullptr); } else { auto *Recurred = concatMultiple(Others...); using TupleType = std::decay_t; using Concatted = ConcatImpl; return static_cast(nullptr); } } inline std::tuple<> *concatMultiple() { return nullptr; } template using Decay = std::decay_t; template using TupleConcat = Decay()...))>; template using FilterContainers = TupleConcat...>; template using FilterNonContainers = TupleConcat...>; template void createAndAppend(std::vector> &Out, ArgsT &&...Args) { using Wrapper = CLOptionWrapper>; Out.emplace_back(std::make_unique(std::forward(Args)...)); } template void createCLOption(std::vector> &Out, const std::integer_sequence &, llvm::cl::OptionCategory *Cat = nullptr) { using cat = llvm::cl::cat; (createAndAppend(Out, T::Name, getOptionName(), cat(*Cat)), ...); } template std::vector> createCLOptionsImpl(auto (T::*F)(DeducedContextType &, AllArgs...), llvm::cl::OptionCategory *Category = nullptr) { using OptionArgsTypes = detail::FilterNonContainers; constexpr size_t OptionArgsCount = std::tuple_size::value; constexpr auto OptionArgsIndexes = std::make_integer_sequence(); std::vector> Out; createCLOption(Out, OptionArgsIndexes, Category); return Out; } template void getOptionNamesFromIndexes(std::vector &Out, const std::integer_sequence &) { (Out.push_back(getOptionName().str()), ...); } template std::vector getOptionsNamesImpl(auto (T::*F)(CtxT &, Args...)) { using OptionArgsTypes = detail::FilterNonContainers; constexpr size_t OptionArgsCount = std::tuple_size::value; constexpr auto OptionArgsIndexes = std::make_integer_sequence(); std::vector Out; getOptionNamesFromIndexes(Out, OptionArgsIndexes); return Out; } template std::vector getOptionsNames() { return getOptionsNamesImpl(&T::run); } template void getOptionTypeFromIndexes(std::vector &Out, const std::integer_sequence &) { (Out.push_back(getTypeName().str()), ...); } template std::vector getOptionsTypesImpl(auto (T::*F)(CtxT &, Args...)) { using OptionArgsTypes = detail::FilterNonContainers; constexpr size_t OptionArgsCount = std::tuple_size::value; constexpr auto OptionArgsIndexes = std::make_integer_sequence(); std::vector Out; getOptionNamesFromIndexes(Out, OptionArgsIndexes); return Out; } template std::vector getOptionsTypes() { return getOptionsNamesImpl(&T::run); } template constexpr bool isNthTypeConst(size_t I) { if (I == 0) return std::is_const_v>; if constexpr (sizeof...(Rest) == 0) return false; else return isNthTypeConst(I - 1); } template constexpr bool isRunArgumentConstImpl(auto (InvokableType::*F)(Args...), size_t Index) { return isNthTypeConst(Index); } template constexpr bool isRunArgumentConst(size_t ArgumentIndex) { return isRunArgumentConstImpl(&InvokableType::run, ArgumentIndex); } } // namespace detail template std::vector> createCLOptions(llvm::cl::OptionCategory *Category = nullptr) { return detail::createCLOptionsImpl(&T::run, Category); } /// Invokes the F member function on the Pipe Pipe passing as nth argument the /// container with the name equal to the nth element of ArgsNames. template auto invokePipeFunction(Context &Ctx, InvokableType &Pipe, auto (InvokableType::*F)(ContextT &, Args...), ContainerSet &Containers, const llvm::ArrayRef &ArgsNames, const llvm::StringMap &OptionArgs) { using ContainersTypes = detail::FilterContainers; constexpr size_t ContainersCount = std::tuple_size::value; constexpr auto Indexes = std::make_integer_sequence(); revng_assert(ContainersCount == ArgsNames.size()); using OptionArgsTypes = detail::FilterNonContainers; constexpr size_t OptionArgsCount = std::tuple_size::value; constexpr auto OptionArgsIndexes = std::make_integer_sequence(); return detail::invokeImpl(Ctx, Pipe, F, Containers, ArgsNames, static_cast(nullptr), static_cast(nullptr), Indexes, OptionArgs, OptionArgsIndexes); } template concept Dumpable = requires(T D) { { D.dump(dbg, 0) }; }; template concept Printable = requires(InvokableType Pipe) { { Pipe.print(std::declval(), llvm::outs(), std::declval>()) }; }; class InvokableWrapperBase { public: virtual llvm::Error run(Context &Ctx, ContainerSet &Containers, const llvm::StringMap &Options = {}) = 0; virtual ~InvokableWrapperBase() = default; virtual std::vector getRunningContainersNames() const = 0; virtual std::string getName() const = 0; virtual void dump(std::ostream &OS, size_t Indents) const = 0; virtual bool isContainerArgumentConst(size_t ArgumentIndex) const = 0; virtual void print(const Context &Ctx, llvm::raw_ostream &OS, size_t Indents) const = 0; virtual std::vector getOptionsNames() const = 0; virtual std::vector getOptionsTypes() const = 0; }; /// A pipe must be type erased somehow to become compatible with a pipeline, /// a PipeWrapperImpl takes care of this issue, it can be constructed from /// any pipeline type, and it will expose the contract and run method of that /// enforcer. template class InvokableWrapperImpl : public InvokableWrapperBase { private: InvokableType ActualPipe; std::vector RunningContainersNames; public: InvokableWrapperImpl(InvokableType ActualPipe, std::vector RunningContainersNames) : ActualPipe(std::move(ActualPipe)), RunningContainersNames(std::move(RunningContainersNames)) {} InvokableWrapperImpl(const InvokableWrapperImpl &Other, std::vector RunningContainersNames) : ActualPipe(Other.ActualPipe), RunningContainersNames(std::move(RunningContainersNames)) {} ~InvokableWrapperImpl() override = default; public: std::string getName() const override { return InvokableType::Name; } public: llvm::Error run(Context &Ctx, ContainerSet &Containers, const llvm::StringMap &OptionArgs) override { if constexpr (invokableTypeReturnsError()) { return invokePipeFunction(Ctx, ActualPipe, &InvokableType::run, Containers, RunningContainersNames, OptionArgs); } else { invokePipeFunction(Ctx, ActualPipe, &InvokableType::run, Containers, RunningContainersNames, OptionArgs); } return llvm::Error::success(); } public: std::vector getRunningContainersNames() const override { return RunningContainersNames; } const InvokableType &getPipe() const { return ActualPipe; } std::vector getOptionsNames() const override { return detail::getOptionsNames(); } std::vector getOptionsTypes() const override { return detail::getOptionsTypes(); } public: void dump(std::ostream &OS, size_t Indentation) const override { indent(OS, Indentation); OS << getName() << "\n"; indent(OS, Indentation + 1); if (const auto &Names = getRunningContainersNames(); !Names.empty()) { OS << "Containers:\n"; for (const auto &Name : Names) { indent(OS, Indentation + 2); OS << Name; OS << "\n"; } } else { OS << "No containers.\n"; } if constexpr (Dumpable) ActualPipe.dump(OS, Indentation); } void print(const Context &Ctx, llvm::raw_ostream &OS, size_t Indentation) const override { indent(OS, Indentation); if constexpr (Printable) { const auto &Names = getRunningContainersNames(); ActualPipe.print(Ctx, OS, Names); } else { OS << "revng pipe "; OS << getName() << " "; for (const auto &Name : getRunningContainersNames()) OS << Name << " "; } } bool isContainerArgumentConst(size_t ArgumentIndex) const override { return detail::isRunArgumentConst(ArgumentIndex + 1); } }; /// This class is used to hide the unique ptr and expose a concrete class /// instead of pointers, as well as implementing dump and operator=, which /// is implemented as a clone. template class InvokableWrapper { public: template using WrapperT = typename BaseInterfaceT::template ImplType; using BaseInterface = BaseInterfaceT; private: std::unique_ptr Pipe; public: InvokableWrapper(const InvokableWrapper &Other, std::vector RunningContainersNames) : Pipe(Other.Pipe->clone(std::move(RunningContainersNames))) {} InvokableWrapper(const InvokableWrapper &Other) : Pipe(Other.Pipe->clone()) {} InvokableWrapper(InvokableWrapper &&Other) = default; InvokableWrapper &operator=(const InvokableWrapper &Other) { if (this == &Other) return *this; Pipe = Other.Pipe->clone(); return *this; } InvokableWrapper &operator=(InvokableWrapper &&Other) = default; ~InvokableWrapper() = default; public: template static InvokableWrapper make(std::vector RunningContainersNames) { using Wrapper = WrapperT; auto Ptr = std::make_unique(PipeType(), std::move(RunningContainersNames)); return InvokableWrapper(std::move(Ptr)); } template static InvokableWrapper make(PipeType Pipe, std::vector RunningContainersNames) { using Wrapper = WrapperT; auto Ptr = std::make_unique(std::move(Pipe), std::move(RunningContainersNames)); return InvokableWrapper(std::move(Ptr)); } template static InvokableWrapper bind(ContainerNames &&...Names) { auto NamesList = { std::forward(Names)... }; return make(std::move(NamesList)); } template static InvokableWrapper bind(PipeType &&E, ContainerNames &&...Names) { auto NamesList = { std::forward(Names)... }; return make(std::forward(E), std::move(NamesList)); } public: BaseInterface &operator*() { return *Pipe; } const BaseInterface &operator*() const { return *Pipe; } BaseInterface *operator->() { return Pipe.get(); } const BaseInterface *operator->() const { return Pipe.get(); } public: template void dump(OStream &OS, size_t Indentation = 0) const { Pipe->dump(OS, Indentation + 1); } void dump() const { dump(dbg); } private: InvokableWrapper(std::unique_ptr Pipe) : Pipe(std::move(Pipe)) {} }; } // namespace pipeline