#pragma once // // This file is distributed under the MIT License. See LICENSE.md for details. // #include #include #include #include #include #include #include #include "llvm/ADT/ArrayRef.h" #include "llvm/ADT/STLExtras.h" #include "llvm/ADT/iterator_range.h" #include "revng/ADT/CompilationTime.h" #include "revng/ADT/Concepts.h" #include "revng/Support/Assert.h" #include "revng/Support/Debug.h" /// Tuple helpers to be used in type definitions template using make_tuple_t = decltype(std::make_tuple(std::declval())); template using tuple_cat_t = decltype(std::tuple_cat(std::declval(), std::declval())); // // always_true and always_false // // Since an assert in the `else` branch of an `if_constexpr` condition said // branch gets instantiated if it doesn't depend on a template, these provide // an easy way to "fake" dependence on an arbitrary template parameter. // // TODO: these will no longer be necessary after we switch to clang 17+ (defect // report 2518), don't forget to replace their usages with just `false`. template struct type_always_false { constexpr static bool value = false; }; template constexpr inline bool type_always_false_v = type_always_false::value; template struct value_always_false { constexpr static bool value = false; }; template constexpr inline bool value_always_false_v = value_always_false::value; template struct type_always_true { constexpr static bool value = false; }; template constexpr inline bool type_always_true_v = type_always_true::value; template struct value_always_true { constexpr static bool value = false; }; template constexpr inline bool value_always_true_v = value_always_true::value; //===----------------------------------------------------------------------===// // Extra additions to //===----------------------------------------------------------------------===// namespace revng { namespace detail { template using ReturnType = decltype(std::declval()(*std::declval())); template> class ProxyMappedIteratorImpl : public llvm::mapped_iterator { struct IteratorProxy { IteratorProxy(FuncReturnTy &&Value) : Temporary(std::move(Value)) {} FuncReturnTy *const operator->() { return &Temporary; } FuncReturnTy const *const operator->() const { return &Temporary; } private: FuncReturnTy Temporary; }; public: using llvm::mapped_iterator::mapped_iterator; using reference = std::decay_t; IteratorProxy operator->() { return llvm::mapped_iterator::operator*(); } IteratorProxy const operator->() const { return llvm::mapped_iterator::operator*(); } }; template using ItImpl = std::conditional_t>, ProxyMappedIteratorImpl, llvm::mapped_iterator>; } // namespace detail /// `revng::mapped_iterator` is a specialized version of /// `llvm::mapped_iterator`. /// /// It can act as an in-place replacement since it doesn't change the behavior /// in most cases. The main difference is the fact that when the iterator uses /// a temporary as a way of remembering its position its lifetime is /// explicitly prolonged to prevent it from being deleted prematurely (like /// inside the `operator->` call). template using mapped_iterator = revng::detail::ItImpl; // `map_iterator` - Provide a convenient way to create `mapped_iterator`s, // just like `make_pair` is useful for creating pairs... template inline auto map_iterator(ItTy I, FuncTy F) { return mapped_iterator(std::move(I), std::move(F)); }; template auto map_range(ContainerTy &&C, FuncTy F) { return llvm::make_range(map_iterator(C.begin(), F), map_iterator(C.end(), F)); } auto dereferenceIterator(auto Iter) { return llvm::map_iterator(Iter, [](const auto &Ptr) -> decltype(*Ptr) & { return *Ptr; }); } namespace detail { template using DIT = decltype(dereferenceIterator(std::declval())); } template using DereferenceIteratorType = revng::detail::DIT; template using DereferenceRangeType = llvm::iterator_range>; auto dereferenceRange(auto &&Range) { return llvm::make_range(dereferenceIterator(Range.begin()), dereferenceIterator(Range.end())); } template auto mapToValueIterator(Iterator It) { const auto GetSecond = [](auto &Pair) -> auto & { return Pair.second; }; return llvm::map_iterator(It, GetSecond); } template using MapToValueIteratorType = decltype(mapToValueIterator(std::declval())); } // namespace revng // // skip // namespace revng::detail { template inline auto skipImpl(IteratorType &&From, IteratorType &&To, size_t Front = 0, size_t Back = 0) -> llvm::iterator_range { std::ptrdiff_t TotalSkippedCount = Front + Back; if constexpr (std::forward_iterator) { // We cannot check on the input iterators because it's going to consume // them. if (std::distance(From, To) < TotalSkippedCount) { if constexpr (SafeMode) { revng_abort("Input range has fewer elements than the intended skip."); } else { // Quietly return an empty range if there are more skips requested than // the total number of elements the input range contains. return llvm::make_range(To, To); } } } std::decay_t Begin{ From }; std::advance(Begin, Front); std::decay_t End{ To }; std::advance(End, -(std::ptrdiff_t) Back); return llvm::make_range(std::move(Begin), std::move(End)); } template inline decltype(auto) skip(T &&From, T &&To, size_t Front = 0, size_t Back = 0) { return skipImpl(std::forward(From), std::forward(To), Front, Back); } template inline decltype(auto) skip_front(T &&From, T &&To, size_t SkippedCount = 1) { return skipImpl(std::forward(From), std::forward(To), SkippedCount, 0); } template inline decltype(auto) skip_back(T &&From, T &&To, size_t SkippedCount = 1) { return skipImpl(std::forward(From), std::forward(To), 0, SkippedCount); } } // namespace revng::detail template inline decltype(auto) skip(T &&Range, size_t Front = 0, size_t Back = 0) { return revng::detail::skip(Range.begin(), Range.end(), Front, Back); } template inline decltype(auto) skip_front(T &&Range, size_t SkippedCount = 1) { return revng::detail::skip_front(Range.begin(), Range.end(), SkippedCount); } template inline decltype(auto) skip_back(T &&Range, size_t SkippedCount = 1) { return revng::detail::skip_back(Range.begin(), Range.end(), SkippedCount); } // TODO: reimplement in terms of `std::views::adjacent` once that's available. template inline decltype(auto) zip_pairs(T &&Range) { return llvm::zip(revng::detail::skipImpl(Range.begin(), Range.end(), 0, 1), revng::detail::skipImpl(Range.begin(), Range.end(), 1, 0)); } // // slice // /// Copy into a std::array a slice of an llvm::ArrayRef template std::array slice(llvm::ArrayRef Old) { std::array Result; auto StartIt = Old.begin() + Start; std::copy(StartIt, StartIt + Size, Result.begin()); return Result; } /// Copy into a std::array a slice of a std::array template std::array slice(const std::array &Old) { std::array Result; auto StartIt = Old.begin() + Start; std::copy(StartIt, StartIt + Size, Result.begin()); return Result; } /// Simple helper function asserting a pointer is not a `nullptr` template inline T *notNull(T *Pointer) { revng_assert(Pointer != nullptr); return Pointer; } inline llvm::ArrayRef toArrayRef(llvm::StringRef Data) { auto Pointer = reinterpret_cast(Data.data()); return llvm::ArrayRef(Pointer, Data.size()); } template concept ArrayLike = requires(T &&V) { { V.data() } -> std::convertible_to; { V.size() } -> std::same_as; }; template concept DataBuffer = ArrayLike || ArrayLike; // // append // template concept HasReserve = requires(T &&V, size_t S) { { V.reserve(S) }; }; template concept HasRangeInsert = requires(T &&V) { { V.insert(V.end(), V.begin(), V.end()) }; }; template void append(FromType &&From, ToType &To) { // range-based insert is tremendously faster than any other method if constexpr (HasRangeInsert) { To.insert(To.end(), From.begin(), From.end()); return; } if constexpr (HasReserve) To.reserve(To.size() + From.size()); if constexpr (std::is_lvalue_reference_v) std::ranges::copy(From, std::inserter(To, To.end())); else std::ranges::move(From, std::inserter(To, To.end())); } /// Intersects two std::sets template std::set intersect(const std::set &First, const std::set &Last) { std::set Output; std::set_intersection(First.begin(), First.end(), Last.begin(), Last.end(), std::inserter(Output, Output.begin())); return Output; } inline void replaceAll(std::string &Input, const std::string &From, const std::string &To) { if (From.empty()) return; size_t Start = 0; while ((Start = Input.find(From, Start)) != std::string::npos) { Input.replace(Start, From.length(), To); Start += To.length(); } } // // `constexpr` versions of the llvm algorithm adaptors. // namespace revng { /// \note use `llvm::find` instead after it's made `constexpr`. template constexpr decltype(auto) find(R &&Range, const T &Value) { return std::find(std::begin(std::forward(Range)), std::end(std::forward(Range)), Value); } /// \note use `llvm::find_if` instead after it's made `constexpr`. template constexpr decltype(auto) find_if(R &&Range, CallableType &&Callable) { return std::find_if(std::begin(std::forward(Range)), std::end(std::forward(Range)), std::forward(Callable)); } /// \note use `llvm::find_if_not` instead after it's made `constexpr`. template constexpr decltype(auto) find_if_not(R &&Range, CallableType &&Callable) { return std::find_if_not(std::begin(std::forward(Range)), std::end(std::forward(Range)), std::forward(Callable)); } /// \note `std::find_last` is introduced in c++23, /// replace with the llvm version when it's available. template constexpr decltype(auto) find_last(R &&Range, const T &Value) { return std::find(std::rbegin(std::forward(Range)), std::rend(std::forward(Range)), Value); } /// \note `std::find_last_if` is introduced in c++23, /// replace with the llvm version when it's available. template constexpr decltype(auto) find_last_if(R &&Range, CallableType &&Callable) { return std::find_if(std::rbegin(std::forward(Range)), std::rend(std::forward(Range)), std::forward(Callable)); } /// \note `std::find_last_if_not` is introduced in c++23, /// replace with the llvm version when it's available. template constexpr decltype(auto) find_last_if_not(R &&Range, CallableType &&Callable) { return std::find_if_not(std::rbegin(std::forward(Range)), std::rend(std::forward(Range)), std::forward(Callable)); } /// \note use `llvm::is_contained` instead after it's made `constexpr`. template constexpr bool is_contained(R &&Range, const T &Value) { return revng::find(std::forward(Range), Value) != std::end(Range); } static_assert(is_contained(std::array{ 1, 2, 3 }, 2) == true); static_assert(is_contained(std::array{ 1, 2, 3 }, 4) == false); template constexpr bool any_of(Range &&R, C &&L) { auto Iterator = revng::find_if(std::forward(R), std::forward(L)); return Iterator != std::end(R); } } // namespace revng // // Some other useful small things // template constexpr auto takeAsTuple(RangeType &&R) { revng_assert(std::ranges::size(R) >= ElementCount); return compile_time::repeat([&R]() -> auto && { return *std::next(R.begin(), I); }); } // // Some views from the STL. // TODO: remove these after updating the libc++ version. // template [[nodiscard]] constexpr std::underlying_type::type to_underlying(EnumType Value) { return static_cast::type>(Value); } template auto as_rvalue(RangeType &&Range) { return llvm::make_range(std::make_move_iterator(Range.begin()), std::make_move_iterator(Range.end())); } namespace revng { namespace detail { template concept Impl = std::input_or_output_iterator && std::sentinel_for && std::is_constructible_v; template concept IteratorConstructible = std::ranges::range && Impl().begin()), decltype(std::declval().end())>; template struct ToImpl { template constexpr Container asContainer(Range &&Input) { // TODO: extend to support for more than just containers that // provide a double-iterator constructor. return Container(Input.begin(), Input.end()); } }; } // namespace detail // NOTE: the implementation here is very crude, but should be good enough until // we update to a version of `libc++` with `c++23` support. template constexpr detail::ToImpl to() { return detail::ToImpl(); }; } // namespace revng template Container> constexpr Container operator|(Range &&R, revng::detail::ToImpl T) { return T.asContainer(std::forward(R)); }