#pragma once // // This file is distributed under the MIT License. See LICENSE.md for details. // #include #include #include #include #include #include "llvm/ADT/StringRef.h" #include "llvm/Support/WithColor.h" #include "llvm/Support/raw_os_ostream.h" #include "llvm/Support/raw_ostream.h" #include "revng/ADT/KeyedObjectContainer.h" #include "revng/ADT/ZipMapIterator.h" #include "revng/Support/Assert.h" #include "revng/TupleTree/TupleTree.h" #include "revng/TupleTree/TupleTreePath.h" template struct is_std_vector : std::false_type {}; template struct is_std_vector> : std::true_type {}; template concept HasPushBack = is_std_vector::value; template concept HasInsertOrAssign = not HasPushBack; template void addToContainer(C &Container, const typename C::value_type &Value) { Container.insert_or_assign(Value); } template void addToContainer(C &Container, const typename C::value_type &Value) { Container.push_back(Value); } template struct TupleTreeEntries; template using TupleTreeEntriesT = typename TupleTreeEntries::Types; namespace detail { template struct CheckTypeIsCorrect { using Variant = TupleTreeEntriesT; const Variant *Alternatives; bool IsCorrect = false; template void visitTupleElement() { using tuple_element = typename std::tuple_element::type; visit(); } template void visitContainerElement(KeyT Key) {} template void visit() { check(); } template void visit() { check(); } template void check() { IsCorrect = std::holds_alternative(*Alternatives); } }; template bool checkTypeIsCorrect(const TupleTreePath &Path, const TupleTreeEntriesT &Content) { detail::CheckTypeIsCorrect Checker{ &Content }; callByPath(Checker, Path); return Checker.IsCorrect; } } // namespace detail template struct Change { public: using EntryType = std::optional>; public: TupleTreePath Path; EntryType Old = std::nullopt; EntryType New = std::nullopt; public: Change() = default; using TupleTreeType = T; explicit Change(TupleTreePath Path, std::optional> Old, std::optional> New) : Path(std::move(Path)), Old(std::move(Old)), New(std::move(New)) {} public: static Change createRemoval(TupleTreePath Path, TupleTreeEntriesT Old) { revng_check(detail::checkTypeIsCorrect(Path, Old)); return Change(std::move(Path), std::move(Old), std::nullopt); } static Change createAddition(TupleTreePath Path, TupleTreeEntriesT New) { revng_check(detail::checkTypeIsCorrect(Path, New)); return Change(std::move(Path), std::nullopt, std::move(New)); } static Change createChange(TupleTreePath Path, TupleTreeEntriesT Old, TupleTreeEntriesT New) { revng_check(detail::checkTypeIsCorrect(Path, New)); revng_check(detail::checkTypeIsCorrect(Path, Old)); return Change(std::move(Path), std::move(Old), std::move(New)); } }; template using ChangesVector = std::vector>; template struct TupleTreeDiff { public: using Change = Change; ChangesVector Changes; // TODO: invalidated instances public: TupleTreeDiff invert() const { TupleTreeDiff Result = *this; for (Change &C : Result.Changes) { std::swap(C.Old, C.New); } return Result; } public: template void add(const TupleTreePath &Path, ToAdd What) { Changes.push_back(Change::createAddition(Path, What)); } template void remove(const TupleTreePath &Path, ToRemove What) { Changes.push_back(Change::createRemoval(Path, What)); } template void change(const TupleTreePath &Path, const ToChange &From, const ToChange &To) { Changes.push_back(Change::createChange(Path, From, To)); } public: void dump(llvm::raw_ostream &OutputStream) const; void dump() const { llvm::raw_os_ostream OutputStream(dbg); dump(OutputStream); } void apply(TupleTree &M) const; }; template concept DiffSpecialization = is_specialization_v; template concept ChangeSpecialization = is_specialization_v; template struct llvm::yaml::MappingTraits { static void mapping(IO &IO, T &Info) { IO.mapOptional("Changes", Info.Changes); } }; template struct llvm::yaml::SequenceElementTraits { // NOLINTNEXTLINE static const bool flow = false; }; namespace detail { template struct MapDiffVisitor { llvm::yaml::IO *Io; TupleTreeEntriesT *Change; const char *MappingName; template void visitTupleElement() { using tuple_element = typename std::tuple_element::type; visit(); } template void visitContainerElement(KeyT Key) {} template void visit() { dump(); } template void visit() { dump(); } template void dump() { if (Io->outputting()) { Io->mapRequired(MappingName, std::get(*Change)); } else { T Content; Io->mapRequired(MappingName, Content); *Change = std::move(Content); } } }; } // namespace detail template struct llvm::yaml::MappingTraits { using EntryType = typename T::EntryType; using Model = typename T::TupleTreeType; static void writeEntry(IO &IO, T &Info, const char *Name, EntryType &Entry) { if (not Entry.has_value()) return; ::detail::MapDiffVisitor Visitor{ &IO, &*Entry, Name }; callByPath(Visitor, Info.Path); } static void readEntry(IO &IO, T &Info, const char *Name, EntryType &Entry) { const auto &Keys = IO.keys(); if (llvm::find(Keys, Name) == Keys.end()) return; Entry = false; ::detail::MapDiffVisitor Visitor{ &IO, &*Entry, Name }; callByPath(Visitor, Info.Path); } static void mapSingleEntry(IO &IO, T &Info, const char *Name, EntryType &Entry) { if (IO.outputting()) writeEntry(IO, Info, Name, Entry); else readEntry(IO, Info, Name, Entry); } static void mapping(IO &IO, T &Info) { if (IO.outputting()) { std::string SerializedPath = *pathAsString(Info.Path); IO.mapRequired("Path", SerializedPath); } else { std::string SerializedPath; IO.mapRequired("Path", SerializedPath); auto MaybePath = stringAsPath(SerializedPath); revng_assert(MaybePath.has_value()); Info.Path = std::move(*MaybePath); } mapSingleEntry(IO, Info, "Add", Info.New); mapSingleEntry(IO, Info, "Remove", Info.Old); } }; // // diff // namespace tupletreediff::detail { template struct Diff { TupleTreePath Stack; TupleTreeDiff Result; TupleTreeDiff diff(M &LHS, M &RHS) { diffImpl(LHS, RHS); return Result; } private: template void diffTuple(T &LHS, T &RHS) { if constexpr (I < std::tuple_size_v) { Stack.push_back(size_t(I)); diffImpl(get(LHS), get(RHS)); Stack.pop_back(); // Recur diffTuple(LHS, RHS); } } template void diffImpl(T &LHS, T &RHS) { LHS.upcast([&](auto &LHSUpcasted) { RHS.upcast([&](auto &RHSUpcasted) { using LHSType = std::remove_cvref_t; using RHSType = std::remove_cvref_t; if constexpr (std::is_same_v) { diffImpl(LHSUpcasted, RHSUpcasted); } else { Result.change(Stack, LHS, RHS); } }); }); } template void diffImpl(T &LHS, T &RHS) { diffTuple(LHS, RHS); } template void diffImpl(T &LHS, T &RHS) { for (auto [LHSElement, RHSElement] : zipmap_range(LHS, RHS)) { if (LHSElement == nullptr) { // Added Result.add(Stack, *RHSElement); } else if (RHSElement == nullptr) { // Removed Result.remove(Stack, *LHSElement); } else { // Identical using value_type = typename T::value_type; Stack.push_back(KeyedObjectTraits::key(*LHSElement)); diffImpl(*LHSElement, *RHSElement); Stack.pop_back(); } } } template void diffImpl(T &LHS, T &RHS) { if (LHS != RHS) Result.change(Stack, LHS, RHS); } }; } // namespace tupletreediff::detail template TupleTreeDiff diff(M &LHS, M &RHS) { return tupletreediff::detail::Diff().diff(LHS, RHS); } // // TupleTreeDiff::dump // template inline void TupleTreeDiff::dump(llvm::raw_ostream &OutputStream) const { serialize(OutputStream, *this); } // // TupleTreeDiff::apply // namespace tupletreediff::detail { // clang-format off template concept StringRefConvertible = requires(T A) { llvm::StringRef(A); }; template concept TwineConvertible = requires(T A) { llvm::Twine(A); }; template concept StringLike = requires(T A) { A.str(); }; template concept IterableAndNotStdString = Iterable and not std::is_same_v and not StringRefConvertible and not TwineConvertible and not StringLike; // clang-format on template concept NotIterableOrStdString = not IterableAndNotStdString; template struct ApplyDiffVisitor { using Change = typename TupleTreeDiff::Change; const Change *C; template void visitTupleElement(K &Element) { visit(Element); } template void visitContainerElement(KeyT, K &Element) { visit(Element); } template void visit(S &M) { revng_assert((C->Old == std::nullopt) != (C->New == std::nullopt)); using value_type = typename S::value_type; using KOT = KeyedObjectTraits; using key_type = decltype(KOT::key(std::declval())); size_t OldSize = M.size(); if (C->Old != std::nullopt) { key_type Key = KOT::key(std::get(*C->Old)); auto End = M.end(); auto CompareKeys = [Key](value_type &V) { return KOT::key(V) == Key; }; auto FirstToDelete = std::remove_if(M.begin(), End, CompareKeys); M.erase(FirstToDelete, End); revng_assert(OldSize == M.size() + 1); } else if (C->New != std::nullopt) { // TODO: assert not there already addToContainer(M, std::get(*C->New)); revng_assert(OldSize == M.size() - 1); } else { revng_abort(); } } template void visit(S &M) { revng_assert(C->Old != std::nullopt and C->New != std::nullopt); auto &Old = std::get(*C->Old); auto &New = std::get(*C->New); revng_check(Old == M); M = New; } }; } // namespace tupletreediff::detail template inline void TupleTreeDiff::apply(TupleTree &M) const { TupleTreePath LastPath; for (const Change &C : Changes) { tupletreediff::detail::ApplyDiffVisitor ADV{ &C }; callByPath(ADV, C.Path, *M); } M.initializeReferences(); }