#pragma once // // This file is distributed under the MIT License. See LICENSE.md for details. // #include #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/STLExtras.h" #include "revng/ADT/ZipMapIterator.h" #include "revng/Support/Assert.h" #include "revng/Support/ErrorList.h" #include "revng/TupleTree/TupleLikeTraits.h" #include "revng/TupleTree/TupleTree.h" #include "revng/TupleTree/TupleTreePath.h" template concept HasValueType = requires(T &&) { typename T::value_type; }; // clang-format off template concept HasPushBack = HasValueType && requires(T &&C, const typename T::value_type &V) { { C.push_back(V) }; }; template concept HasInsertOrAssign = HasValueType && requires(T &&C, const typename T::value_type &V) { { C.insert_or_assign(V) }; }; // clang-format on template void addToContainer(C &Container, const typename C::value_type &Value) { Container.push_back(Value); } template void addToContainer(C &Container, const typename C::value_type &Value) { Container.insert_or_assign(Value); } namespace revng::detail { template concept Set = StrictSpecializationOf; template concept SetOrKOC = Set || KeyedObjectContainer; } // namespace revng::detail template struct TupleTreeEntries {}; template using AllowedTupleTreeTypes = typename TupleTreeEntries::Types; template concept TupleTreeRootLike = StrictSpecializationOf, std::variant>; namespace detail { template struct CheckTypeIsCorrect { const AllowedTupleTreeTypes *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 AllowedTupleTreeTypes &Content) { CheckTypeIsCorrect Checker{ &Content }; callByPath(Checker, Path); return Checker.IsCorrect; } } // namespace detail template struct Change { public: using Variant = AllowedTupleTreeTypes; 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, EntryType Old, EntryType New) : Path(std::move(Path)), Old(std::move(Old)), New(std::move(New)) {} public: static Change createRemoval(TupleTreePath Path, Variant Old) { revng_check(detail::checkTypeIsCorrect(Path, Old)); return Change(std::move(Path), std::move(Old), std::nullopt); } static Change createAddition(TupleTreePath Path, Variant New) { revng_check(detail::checkTypeIsCorrect(Path, New)); return Change(std::move(Path), std::nullopt, std::move(New)); } static Change createChange(TupleTreePath Path, Variant Old, Variant 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 struct TupleTreeDiff { public: static llvm::Expected> deserialize(llvm::StringRef Input, ErrorList &EL) { return ::deserialize>(Input, &EL); } public: using Change = Change; std::vector 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, ErrorList &EL) const; }; /// TODO: use non-strict specialization after it's available. template T> struct llvm::yaml::MappingTraits { static void mapping(IO &IO, T &Info) { IO.mapOptional("Changes", Info.Changes); } }; /// TODO: use non-strict specialization after it's available. template T, typename X> struct llvm::yaml::SequenceElementTraits { // NOLINTNEXTLINE static const bool flow = false; }; namespace detail { template struct MapDiffVisitor { llvm::yaml::IO *Io; AllowedTupleTreeTypes *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 /// TODO: use non-strict specialization after it's available. template T> 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.emplace(); revng_assert(Entry.has_value()); ::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); if (!MaybePath.has_value()) { ::ErrorList *EL = static_cast<::ErrorList *>(IO.getContext()); std::string ErrorMessage = "Path " + SerializedPath + " is invalid"; EL->push_back(llvm::createStringError(llvm::inconvertibleErrorCode(), ErrorMessage)); } else { 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(const M &LHS, const M &RHS) { diffImpl(LHS, RHS); return Result; } private: template void diffTuple(const T &LHS, const 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 T> void diffImpl(const T &LHS, const 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(const T &LHS, const T &RHS) { diffTuple(LHS, RHS); } template void diffImpl(const T &LHS, const 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(const T &LHS, const T &RHS) { if (LHS != RHS) Result.change(Stack, LHS, RHS); } }; } // namespace tupletreediff::detail template TupleTreeDiff diff(const M &LHS, const 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 { template struct ApplyDiffVisitor { public: using Change = typename TupleTreeDiff::Change; const Change *C; ErrorList *EL; private: void generateError() { generateError(""); } void generateError(const llvm::StringRef Reason) { std::string Description = "Error in applying diff"; if (!Reason.empty()) Description += ": " + Reason.str(); std::optional StringPath = pathAsString(C->Path); if (StringPath != std::nullopt) Description += " on Path " + *StringPath; auto Error = llvm::createStringError(llvm::inconvertibleErrorCode(), Description); EL->push_back(std::move(Error)); } public: template void visitTupleElement(K &Element) { visit(Element); } template void visitContainerElement(KeyT, K &Element) { visit(Element); } template void visit(S &M) { // This visitor handles subtree additions/deletions. Here we either have a // New or Old key to add/remove. if (C->Old == std::nullopt && C->New == std::nullopt) { generateError("both 'Remove' and 'Add' are not present"); return; } if (C->Old != std::nullopt && C->New != std::nullopt) { generateError("both 'Remove' and 'Add' are not present"); return; } 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); if (OldSize - 1 != M.size()) generateError("subtree removal failed"); } else if (C->New != std::nullopt) { // TODO: assert not there already addToContainer(M, std::get(*C->New)); if (OldSize + 1 != M.size()) generateError("subtree addition failed"); } else { generateError("arrived at an impossible branch"); } } template void visit(S &M) { // This visitor handles key changes, so both Old and New are present. This // will check that the tree contains Old and then replace its contents with // New if (C->Old == std::nullopt || C->New == std::nullopt) { if (C->Old == std::nullopt) generateError("missing 'Remove' key"); if (C->New == std::nullopt) generateError("missing 'Add' key"); return; } auto &Old = std::get(*C->Old); auto &New = std::get(*C->New); if (Old != M) { generateError("'Remove' does not match the contents of the Tuple Tree"); return; } M = New; } }; } // namespace tupletreediff::detail template inline void TupleTreeDiff::apply(TupleTree &M, ErrorList &EL) const { for (const Change &C : Changes) { if (C.Path.size() == 0) { // Change failed to deserialize, skip it continue; } tupletreediff::detail::ApplyDiffVisitor ADV{ &C, &EL }; callByPath(ADV, C.Path, *M, EL, *pathAsString(C.Path)); } M.initializeReferences(); }