#pragma once // // This file is distributed under the MIT License. See LICENSE.md for details. // #include #include #include "llvm/Support/WithColor.h" #include "revng/ADT/KeyedObjectContainer.h" #include "revng/ADT/ZipMapIterator.h" #include "revng/Model/TupleTree.h" template struct is_std_vector : std::false_type {}; template struct is_std_vector> : std::true_type {}; template constexpr bool has_push_back_v = is_std_vector::value; template using enable_if_has_push_back_t = std::enable_if_t, K>; template constexpr bool has_insert_or_assign_v = not has_push_back_v; namespace detail { template using ei_hioa_t = std::enable_if_t, K>; } template using enable_if_has_insert_or_assign_t = detail::ei_hioa_t; template enable_if_has_insert_or_assign_t addToContainer(C &Container, const typename C::value_type &Value) { Container.insert_or_assign(Value); } template enable_if_has_push_back_t addToContainer(C &Container, const typename C::value_type &Value) { Container.push_back(Value); } template struct TupleTreeDiff { struct Change { KeyIntVector Path; void *Old; void *New; }; std::vector Changes; // TODO: invalidated instances TupleTreeDiff invert() const { TupleTreeDiff Result = *this; for (Change &C : Result.Changes) { std::swap(C.Old, C.New); } return Result; } void add(const KeyIntVector &Path, void *What) { Changes.push_back({ Path, nullptr, What }); } void remove(const KeyIntVector &Path, void *What) { Changes.push_back({ Path, What, nullptr }); } void change(const KeyIntVector &Path, void *From, void *To) { Changes.push_back({ Path, From, To }); } void dump() const; void apply(T &M) const; }; // // diff // namespace tupletreediff::detail { template struct Diff { KeyIntVector Stack; TupleTreeDiff Result; TupleTreeDiff diff(M &LHS, M &RHS) { diffImpl(LHS, RHS); return Result; } private: template enable_if_tuple_end_t diffTuple(T &LHS, T &RHS) {} template enable_if_not_tuple_end_t diffTuple(T &LHS, T &RHS) { using child_type = typename std::tuple_element::type; Stack.push_back(I); diffImpl(get(LHS), get(RHS)); Stack.pop_back(); // Recur diffTuple(LHS, RHS); } template enable_if_has_tuple_size_t diffImpl(T &LHS, T &RHS) { diffTuple(LHS, RHS); } template enable_if_is_sorted_container_t diffImpl(T &LHS, T &RHS) { using value_type = typename T::value_type; using KOT = KeyedObjectTraits; using key_type = decltype(KOT::key(std::declval())); 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 KT = KeyTraits; const auto &KeyInts = KT::toInts(KOT::key(*LHSElement)); std::copy(KeyInts.begin(), KeyInts.end(), std::back_inserter(Stack)); diffImpl(*LHSElement, *RHSElement); // Delete key from the stack Stack.resize(Stack.size() - KeyTraits::IntsCount); } } } template enable_if_is_unsorted_container_t diffImpl(T &LHS, T &RHS) { using value_type = typename T::value_type; using KOT = KeyedObjectTraits; using key_type = decltype(KOT::key(std::declval())); std::map LHSMap, RHSMap; for (value_type &Element : LHS) LHSMap[KOT::key(Element)] = ∈ for (value_type &Element : RHS) RHSMap[KOT::key(Element)] = ∈ for (auto [LHSElement, RHSElement] : zipmap_range(LHSMap, RHSMap)) { if (LHSElement == nullptr) { // Added Result.add(Stack, RHSElement->second); } else if (RHSElement == nullptr) { // Removed Result.remove(Stack, LHSElement->second); } else { // Identical const auto &KeysInt = KeyTraits::toInts(LHSElement->first); std::copy(KeysInt.begin(), KeysInt.end(), std::back_inserter(Stack)); diffImpl(*LHSElement->second, *RHSElement->second); Stack.resize(Stack.size() - KeyTraits::IntsCount); } } } template std::enable_if_t or has_tuple_size_v)> 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 // namespace tupletreediff::detail { template enable_if_has_yaml_t stream(T *M, S &Stream) { using namespace llvm::yaml; Output Out(Stream); EmptyContext Ctx; yamlize(Out, *M, true, Ctx); } template enable_if_has_not_yaml_t stream(T *M, S &Stream) { Stream << *M; } template void dumpWithPrefixAndColor(llvm::StringRef Prefix, llvm::raw_ostream::Colors Color, T *M) { std::string Buffer; llvm::WithColor Stream(llvm::outs()); Stream.changeColor(Color); { llvm::raw_string_ostream StringStream(Buffer); stream(M, StringStream); } auto [LHS, RHS] = llvm::StringRef(Buffer).split('\n'); while (RHS.size() != 0) { Stream << Prefix << LHS << "\n"; std::tie(LHS, RHS) = RHS.split('\n'); } Stream << Prefix << LHS << "\n"; } struct DumpDiffVisitor { void *Old, *New; template void visitTupleElement() { using tuple_element = typename std::tuple_element::type; visit(); } template void visitContainerElement(KeyT Key) {} template enable_if_is_container_t visit() { revng_assert((Old != nullptr) != (New != nullptr)); dump(); } template enable_if_is_not_container_t visit() { revng_assert(Old != nullptr and New != nullptr); dump(); } template void dump() { if (Old != nullptr) { dumpWithPrefixAndColor("-", llvm::raw_ostream::RED, reinterpret_cast(Old)); } if (New != nullptr) { dumpWithPrefixAndColor("+", llvm::raw_ostream::GREEN, reinterpret_cast(New)); } } }; } // namespace tupletreediff::detail template inline void TupleTreeDiff::dump() const { using namespace tupletreediff::detail; KeyIntVector LastPath; for (const Change &C : Changes) { if (LastPath != C.Path) { std::string NewPath = pathAsString(C.Path); llvm::outs() << "--- " << NewPath << "\n"; llvm::outs() << "+++ " << NewPath << "\n"; LastPath = C.Path; } DumpDiffVisitor PV2{ C.Old, C.New }; callByPath(PV2, C.Path); } } // // TupleTreeDiff::apply // namespace tupletreediff::detail { 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 std::enable_if_t and !std::is_same_v> visit(S &M) { revng_assert((C->Old == nullptr) != (C->New == nullptr)); 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 != nullptr) { key_type Key = KOT::key(*reinterpret_cast(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 != nullptr) { // TODO: assert not there already addToContainer(M, *reinterpret_cast(C->New)); revng_assert(OldSize == M.size() - 1); } else { revng_abort(); } } template std::enable_if_t and !std::is_same_v)> visit(S &M) { revng_assert(C->Old != nullptr and C->New != nullptr); auto *Old = reinterpret_cast(C->Old); auto *New = reinterpret_cast(C->New); revng_check(*Old == M); M = *New; } }; } // namespace tupletreediff::detail template inline void TupleTreeDiff::apply(T &M) const { KeyIntVector LastPath; for (const Change &C : Changes) { tupletreediff::detail::ApplyDiffVisitor ADV{ &C }; callByPath(ADV, C.Path, M); } }