| //===-- LVCompare.cpp -----------------------------------------------------===// |
| // |
| // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. |
| // See https://llvm.org/LICENSE.txt for license information. |
| // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception |
| // |
| //===----------------------------------------------------------------------===// |
| // |
| // This implements the LVCompare class. |
| // |
| //===----------------------------------------------------------------------===// |
| |
| #include "llvm/DebugInfo/LogicalView/Core/LVCompare.h" |
| #include "llvm/DebugInfo/LogicalView/Core/LVOptions.h" |
| #include "llvm/DebugInfo/LogicalView/Core/LVReader.h" |
| #include <tuple> |
| |
| using namespace llvm; |
| using namespace llvm::logicalview; |
| |
| #define DEBUG_TYPE "Compare" |
| |
| namespace { |
| |
| enum class LVCompareItem { Scope, Symbol, Type, Line, Total }; |
| enum class LVCompareIndex { Header, Expected, Missing, Added }; |
| using LVCompareEntry = std::tuple<const char *, unsigned, unsigned, unsigned>; |
| using LVCompareInfo = std::map<LVCompareItem, LVCompareEntry>; |
| LVCompareInfo Results = { |
| {LVCompareItem::Line, LVCompareEntry("Lines", 0, 0, 0)}, |
| {LVCompareItem::Scope, LVCompareEntry("Scopes", 0, 0, 0)}, |
| {LVCompareItem::Symbol, LVCompareEntry("Symbols", 0, 0, 0)}, |
| {LVCompareItem::Type, LVCompareEntry("Types", 0, 0, 0)}, |
| {LVCompareItem::Total, LVCompareEntry("Total", 0, 0, 0)}}; |
| static LVCompareInfo::iterator IterTotal = Results.end(); |
| |
| constexpr unsigned getHeader() { |
| return static_cast<unsigned>(LVCompareIndex::Header); |
| } |
| constexpr unsigned getExpected() { |
| return static_cast<unsigned>(LVCompareIndex::Expected); |
| } |
| constexpr unsigned getMissing() { |
| return static_cast<unsigned>(LVCompareIndex::Missing); |
| } |
| constexpr unsigned getAdded() { |
| return static_cast<unsigned>(LVCompareIndex::Added); |
| } |
| |
| LVCompare *CurrentComparator = nullptr; |
| |
| void zeroResults() { |
| // In case the same reader instance is used. |
| for (LVCompareInfo::reference Entry : Results) { |
| std::get<getExpected()>(Entry.second) = 0; |
| std::get<getMissing()>(Entry.second) = 0; |
| std::get<getAdded()>(Entry.second) = 0; |
| } |
| IterTotal = Results.find(LVCompareItem::Total); |
| assert(IterTotal != Results.end()); |
| } |
| |
| LVCompareInfo::iterator getResultsEntry(LVElement *Element) { |
| LVCompareItem Kind; |
| if (Element->getIsLine()) |
| Kind = LVCompareItem::Line; |
| else if (Element->getIsScope()) |
| Kind = LVCompareItem::Scope; |
| else if (Element->getIsSymbol()) |
| Kind = LVCompareItem::Symbol; |
| else |
| Kind = LVCompareItem::Type; |
| |
| // Used to update the expected, missing or added entry for the given kind. |
| LVCompareInfo::iterator Iter = Results.find(Kind); |
| assert(Iter != Results.end()); |
| return Iter; |
| } |
| |
| void updateExpected(LVElement *Element) { |
| LVCompareInfo::iterator Iter = getResultsEntry(Element); |
| // Update total for expected. |
| ++std::get<getExpected()>(IterTotal->second); |
| // Update total for specific element kind. |
| ++std::get<getExpected()>(Iter->second); |
| } |
| |
| void updateMissingOrAdded(LVElement *Element, LVComparePass Pass) { |
| LVCompareInfo::iterator Iter = getResultsEntry(Element); |
| if (Pass == LVComparePass::Missing) { |
| ++std::get<getMissing()>(IterTotal->second); |
| ++std::get<getMissing()>(Iter->second); |
| } else { |
| ++std::get<getAdded()>(IterTotal->second); |
| ++std::get<getAdded()>(Iter->second); |
| } |
| } |
| |
| } // namespace |
| |
| LVCompare &LVCompare::getInstance() { |
| static LVCompare DefaultComparator(outs()); |
| return CurrentComparator ? *CurrentComparator : DefaultComparator; |
| } |
| |
| void LVCompare::setInstance(LVCompare *Comparator) { |
| CurrentComparator = Comparator; |
| } |
| |
| LVCompare::LVCompare(raw_ostream &OS) : OS(OS) { |
| PrintLines = options().getPrintLines(); |
| PrintSymbols = options().getPrintSymbols(); |
| PrintTypes = options().getPrintTypes(); |
| PrintScopes = |
| options().getPrintScopes() || PrintLines || PrintSymbols || PrintTypes; |
| } |
| |
| Error LVCompare::execute(LVReader *ReferenceReader, LVReader *TargetReader) { |
| setInstance(this); |
| // In the case of added elements, the 'Reference' reader will be modified; |
| // those elements will be added to it. Update the current reader instance. |
| LVReader::setInstance(ReferenceReader); |
| |
| auto PrintHeader = [this](LVScopeRoot *LHS, LVScopeRoot *RHS) { |
| LLVM_DEBUG({ |
| dbgs() << "[Reference] " << LHS->getName() << "\n" |
| << "[Target] " << RHS->getName() << "\n"; |
| }); |
| OS << "\nReference: " << formattedName(LHS->getName()) << "\n" |
| << "Target: " << formattedName(RHS->getName()) << "\n"; |
| }; |
| |
| // We traverse the given scopes tree ('Reference' and 'Target') twice. |
| // The first time we look for missing items from the 'Reference' and the |
| // second time we look for items added to the 'Target'. |
| // The comparison test includes the name, lexical level, type, source |
| // location, etc. |
| LVScopeRoot *ReferenceRoot = ReferenceReader->getScopesRoot(); |
| LVScopeRoot *TargetRoot = TargetReader->getScopesRoot(); |
| ReferenceRoot->setIsInCompare(); |
| TargetRoot->setIsInCompare(); |
| |
| // Reset possible previous results. |
| zeroResults(); |
| |
| if (options().getCompareContext()) { |
| // Perform a logical view comparison as a whole unit. We start at the |
| // root reference; at each scope an equal test is applied to its children. |
| // If a difference is found, the current path is marked as missing. |
| auto CompareViews = [this](LVScopeRoot *LHS, LVScopeRoot *RHS) -> Error { |
| LHS->markMissingParents(RHS, /*TraverseChildren=*/true); |
| if (LHS->getIsMissingLink() && options().getReportAnyView()) { |
| // As we are printing a missing tree, enable formatting. |
| options().setPrintFormatting(); |
| OS << "\nMissing Tree:\n"; |
| if (Error Err = LHS->doPrint(/*Split=*/false, /*Match=*/false, |
| /*Print=*/true, OS)) |
| return Err; |
| options().resetPrintFormatting(); |
| } |
| |
| return Error::success(); |
| }; |
| |
| // If the user has requested printing details for the comparison, we |
| // disable the indentation and the added/missing tags ('+'/'-'), as the |
| // details are just a list of elements. |
| options().resetPrintFormatting(); |
| |
| PrintHeader(ReferenceRoot, TargetRoot); |
| Reader = ReferenceReader; |
| if (Error Err = CompareViews(ReferenceRoot, TargetRoot)) |
| return Err; |
| FirstMissing = true; |
| ReferenceRoot->report(LVComparePass::Missing); |
| |
| PrintHeader(TargetRoot, ReferenceRoot); |
| Reader = TargetReader; |
| if (Error Err = CompareViews(TargetRoot, ReferenceRoot)) |
| return Err; |
| FirstMissing = true; |
| TargetRoot->report(LVComparePass::Added); |
| |
| options().setPrintFormatting(); |
| |
| // Display a summary with the elements missing and/or added. |
| printSummary(); |
| } else { |
| // Perform logical elements comparison. An equal test is apply to each |
| // element. If a difference is found, the reference element is marked as |
| // 'missing'. |
| // The final comparison result will show the 'Reference' scopes tree, |
| // having both missing and added elements. |
| using LVScopeLink = std::map<LVScope *, LVScope *>; |
| LVScopeLink ScopeLinks; |
| auto CompareReaders = [&](LVReader *LHS, LVReader *RHS, LVElements &Set, |
| LVComparePass Pass) -> Error { |
| auto FindMatch = [&](auto &References, auto &Targets, |
| const char *Category) -> Error { |
| LVElements Elements; |
| for (LVElement *Reference : References) { |
| // Report elements that can be printed; ignore logical elements that |
| // have qualifiers. |
| if (Reference->getIncludeInPrint()) { |
| if (Pass == LVComparePass::Missing) |
| updateExpected(Reference); |
| Reference->setIsInCompare(); |
| LVElement *CurrentTarget = nullptr; |
| if (std::any_of(Targets.begin(), Targets.end(), |
| [&](auto Target) -> bool { |
| CurrentTarget = Target; |
| return Reference->equals(Target); |
| })) { |
| if (Pass == LVComparePass::Missing && Reference->getIsScope()) { |
| // If the elements being compared are scopes and are a match, |
| // they are recorded, to be used when creating the augmented |
| // tree, as insertion points for the "added" items. |
| ScopeLinks.emplace(static_cast<LVScope *>(CurrentTarget), |
| static_cast<LVScope *>(Reference)); |
| } |
| } else { |
| // Element is missing or added. |
| Pass == LVComparePass::Missing ? Reference->setIsMissing() |
| : Reference->setIsAdded(); |
| Elements.push_back(Reference); |
| updateMissingOrAdded(Reference, Pass); |
| // Record missing/added element. |
| addPassEntry(Reader, Reference, Pass); |
| } |
| } |
| } |
| if (Pass == LVComparePass::Added) |
| // Record all the current missing elements for this category. |
| Set.insert(Set.end(), Elements.begin(), Elements.end()); |
| if (options().getReportList()) { |
| if (Elements.size()) { |
| OS << "\n(" << Elements.size() << ") " |
| << (Pass == LVComparePass::Missing ? "Missing" : "Added") << " " |
| << Category << ":\n"; |
| for (const LVElement *Element : Elements) { |
| if (Error Err = Element->doPrint(/*Split=*/false, /*Match=*/false, |
| /*Print=*/true, OS)) |
| return Err; |
| } |
| } |
| } |
| |
| return Error::success(); |
| }; |
| |
| // First compare the scopes, so they will be inserted at the front of |
| // the missing elements list. When they are moved, their children are |
| // moved as well and no additional work is required. |
| if (options().getCompareScopes()) |
| if (Error Err = FindMatch(LHS->getScopes(), RHS->getScopes(), "Scopes")) |
| return Err; |
| if (options().getCompareSymbols()) |
| if (Error Err = |
| FindMatch(LHS->getSymbols(), RHS->getSymbols(), "Symbols")) |
| return Err; |
| if (options().getCompareTypes()) |
| if (Error Err = FindMatch(LHS->getTypes(), RHS->getTypes(), "Types")) |
| return Err; |
| if (options().getCompareLines()) |
| if (Error Err = FindMatch(LHS->getLines(), RHS->getLines(), "Lines")) |
| return Err; |
| |
| return Error::success(); |
| }; |
| |
| // If the user has requested printing details for the comparison, we |
| // disable the indentation and the added/missing tags ('+'/'-'), as the |
| // details are just a list of elements. |
| options().resetPrintFormatting(); |
| |
| PrintHeader(ReferenceRoot, TargetRoot); |
| // Include the root in the expected count. |
| updateExpected(ReferenceRoot); |
| |
| LVElements ElementsToAdd; |
| Reader = ReferenceReader; |
| if (Error Err = CompareReaders(ReferenceReader, TargetReader, ElementsToAdd, |
| LVComparePass::Missing)) |
| return Err; |
| Reader = TargetReader; |
| if (Error Err = CompareReaders(TargetReader, ReferenceReader, ElementsToAdd, |
| LVComparePass::Added)) |
| return Err; |
| |
| LLVM_DEBUG({ |
| dbgs() << "\nReference/Target Scope links:\n"; |
| for (LVScopeLink::const_reference Entry : ScopeLinks) |
| dbgs() << "Source: " << hexSquareString(Entry.first->getOffset()) << " " |
| << "Destination: " << hexSquareString(Entry.second->getOffset()) |
| << "\n"; |
| dbgs() << "\n"; |
| }); |
| |
| // Add the 'missing' elements from the 'Target' into the 'Reference'. |
| // First insert the missing scopes, as they include any missing children. |
| LVScope *Parent = nullptr; |
| for (LVElement *Element : ElementsToAdd) { |
| LLVM_DEBUG({ |
| dbgs() << "Element to Insert: " << hexSquareString(Element->getOffset()) |
| << ", Parent: " |
| << hexSquareString(Element->getParentScope()->getOffset()) |
| << "\n"; |
| }); |
| // Skip already inserted elements. They were inserted, if their parents |
| // were missing. When inserting them, all the children are moved. |
| if (Element->getHasMoved()) |
| continue; |
| |
| // We need to find an insertion point in the reference scopes tree. |
| Parent = Element->getParentScope(); |
| if (ScopeLinks.find(Parent) != ScopeLinks.end()) { |
| LVScope *InsertionPoint = ScopeLinks[Parent]; |
| LLVM_DEBUG({ |
| dbgs() << "Inserted at: " |
| << hexSquareString(InsertionPoint->getOffset()) << "\n"; |
| }); |
| if (Parent->removeElement(Element)) { |
| // Be sure we have a current compile unit. |
| getReader().setCompileUnit(InsertionPoint->getCompileUnitParent()); |
| InsertionPoint->addElement(Element); |
| Element->updateLevel(InsertionPoint, /*Moved=*/true); |
| } |
| } |
| } |
| |
| options().setPrintFormatting(); |
| |
| // Display the augmented reference scopes tree. |
| if (options().getReportAnyView()) |
| if (Error Err = ReferenceReader->doPrint()) |
| return Err; |
| |
| LLVM_DEBUG({ |
| dbgs() << "\nModified Reference Reader"; |
| if (Error Err = ReferenceReader->doPrint()) |
| return Err; |
| dbgs() << "\nModified Target Reader"; |
| if (Error Err = TargetReader->doPrint()) |
| return Err; |
| }); |
| |
| // Display a summary with the elements missing and/or added. |
| printSummary(); |
| } |
| |
| return Error::success(); |
| } |
| |
| void LVCompare::printCurrentStack() { |
| for (const LVScope *Scope : ScopeStack) { |
| Scope->printAttributes(OS); |
| OS << Scope->lineNumberAsString(/*ShowZero=*/true) << " " << Scope->kind() |
| << " " << formattedName(Scope->getName()) << "\n"; |
| } |
| } |
| |
| void LVCompare::printItem(LVElement *Element, LVComparePass Pass) { |
| // Record expected, missing, added. |
| updateExpected(Element); |
| updateMissingOrAdded(Element, Pass); |
| |
| // Record missing/added element. |
| if (Element->getIsMissing()) |
| addPassEntry(Reader, Element, Pass); |
| |
| if ((!PrintLines && Element->getIsLine()) || |
| (!PrintScopes && Element->getIsScope()) || |
| (!PrintSymbols && Element->getIsSymbol()) || |
| (!PrintTypes && Element->getIsType())) |
| return; |
| |
| if (Element->getIsMissing()) { |
| if (FirstMissing) { |
| OS << "\n"; |
| FirstMissing = false; |
| } |
| |
| StringRef Kind = Element->kind(); |
| StringRef Name = |
| Element->getIsLine() ? Element->getPathname() : Element->getName(); |
| StringRef Status = (Pass == LVComparePass::Missing) ? "Missing" : "Added"; |
| OS << Status << " " << Kind << " '" << Name << "'"; |
| if (Element->getLineNumber() > 0) |
| OS << " at line " << Element->getLineNumber(); |
| OS << "\n"; |
| |
| if (options().getReportList()) { |
| printCurrentStack(); |
| Element->printAttributes(OS); |
| OS << Element->lineNumberAsString(/*ShowZero=*/true) << " " << Kind << " " |
| << Name << "\n"; |
| } |
| } |
| } |
| |
| void LVCompare::printSummary() const { |
| if (!options().getPrintSummary()) |
| return; |
| std::string Separator = std::string(40, '-'); |
| auto PrintSeparator = [&]() { OS << Separator << "\n"; }; |
| auto PrintHeadingRow = [&](const char *T, const char *U, const char *V, |
| const char *W) { |
| OS << format("%-9s%9s %9s %9s\n", T, U, V, W); |
| }; |
| auto PrintDataRow = [&](const char *T, unsigned U, unsigned V, unsigned W) { |
| OS << format("%-9s%9d %9d %9d\n", T, U, V, W); |
| }; |
| |
| OS << "\n"; |
| PrintSeparator(); |
| PrintHeadingRow("Element", "Expected", "Missing", "Added"); |
| PrintSeparator(); |
| for (LVCompareInfo::reference Entry : Results) { |
| if (Entry.first == LVCompareItem::Total) |
| PrintSeparator(); |
| PrintDataRow(std::get<getHeader()>(Entry.second), |
| std::get<getExpected()>(Entry.second), |
| std::get<getMissing()>(Entry.second), |
| std::get<getAdded()>(Entry.second)); |
| } |
| } |
| |
| void LVCompare::print(raw_ostream &OS) const { OS << "LVCompare\n"; } |