Index: llvm/include/llvm/IR/Metadata.h =================================================================== --- llvm/include/llvm/IR/Metadata.h +++ llvm/include/llvm/IR/Metadata.h @@ -1038,6 +1038,31 @@ return cast(N.release()->replaceWithDistinctImpl()); } + /// Print in tree shape. + /// + /// Prints definition of \c this in tree shape. + /// + /// If \c M is provided, metadata nodes will be numbered canonically; + /// otherwise, pointer addresses are substituted. + /// @{ + void printTree(raw_ostream &OS, const Module *M = nullptr) const; + void printTree(raw_ostream &OS, ModuleSlotTracker &MST, + const Module *M = nullptr) const; + /// @} + + /// User-friendly dump in tree shape. + /// + /// If \c M is provided, metadata nodes will be numbered canonically; + /// otherwise, pointer addresses are substituted. + /// + /// Note: this uses an explicit overload instead of default arguments so that + /// the nullptr version is easy to call from a debugger. + /// + /// @{ + void dumpTree() const; + void dumpTree(const Module *M) const; + /// @} + private: MDNode *replaceWithPermanentImpl(); MDNode *replaceWithUniquedImpl(); Index: llvm/lib/IR/AsmWriter.cpp =================================================================== --- llvm/lib/IR/AsmWriter.cpp +++ llvm/lib/IR/AsmWriter.cpp @@ -23,6 +23,7 @@ #include "llvm/ADT/Optional.h" #include "llvm/ADT/STLExtras.h" #include "llvm/ADT/SetVector.h" +#include "llvm/ADT/SmallPtrSet.h" #include "llvm/ADT/SmallString.h" #include "llvm/ADT/SmallVector.h" #include "llvm/ADT/StringExtras.h" @@ -1292,6 +1293,12 @@ static AsmWriterContext EmptyCtx(nullptr, nullptr); return EmptyCtx; } + + /// A callback that will be triggered when the underlying printer + /// prints a Metadata as operand. + virtual void onWriteMetadataAsOperand(const Metadata *) {} + + virtual ~AsmWriterContext() {} }; } // end anonymous namespace @@ -1636,6 +1643,7 @@ WriteAsOperandInternal(Out, V, WriterCtx); } else { WriteAsOperandInternal(Out, MD, WriterCtx); + WriterCtx.onWriteMetadataAsOperand(MD); } if (mi + 1 != me) Out << ", "; @@ -1736,6 +1744,7 @@ return; } WriteAsOperandInternal(Out, MD, WriterCtx); + WriterCtx.onWriteMetadataAsOperand(MD); } void MDFieldPrinter::printMetadata(StringRef Name, const Metadata *MD, @@ -4658,22 +4667,87 @@ printAsOperandImpl(*this, O, PrintType, MST); } +/// Recursive version of printMetadataImpl. +static void printMetadataImplRec(raw_ostream &ROS, const Metadata &MD, + AsmWriterContext &WriterCtx) { + formatted_raw_ostream OS(ROS); + WriteAsOperandInternal(OS, &MD, WriterCtx, /* FromValue */ true); + + auto *N = dyn_cast(&MD); + if (!N || isa(MD) || isa(MD)) + return; + + OS << " = "; + WriteMDNodeBodyInternal(OS, N, WriterCtx); +} + +namespace { +struct MDTreeAsmWriterContext : public AsmWriterContext { + unsigned Level; + // {Level, Printed string} + using EntryTy = std::pair; + SmallVector Buffer; + + // Used to break the cycle in case there is any. + SmallPtrSet Visited; + + raw_ostream &MainOS; + + MDTreeAsmWriterContext(TypePrinting *TP, SlotTracker *ST, const Module *M, + raw_ostream &OS, const Metadata *InitMD) + : AsmWriterContext(TP, ST, M), Level(0U), Visited({InitMD}), MainOS(OS) {} + + void onWriteMetadataAsOperand(const Metadata *MD) override { + if (Visited.count(MD)) + return; + Visited.insert(MD); + + std::string Str; + raw_string_ostream SS(Str); + ++Level; + // A placeholder entry to memorize the correct + // position in buffer. + Buffer.emplace_back(std::make_pair(Level, "")); + unsigned InsertIdx = Buffer.size() - 1; + + printMetadataImplRec(SS, *MD, *this); + Buffer[InsertIdx].second = std::move(SS.str()); + --Level; + } + + ~MDTreeAsmWriterContext() { + for (const auto &Entry : Buffer) { + MainOS << "\n"; + unsigned NumIndent = Entry.first * 2U; + MainOS.indent(NumIndent) << Entry.second; + } + } +}; +} // end anonymous namespace + static void printMetadataImpl(raw_ostream &ROS, const Metadata &MD, ModuleSlotTracker &MST, const Module *M, - bool OnlyAsOperand) { + bool OnlyAsOperand, bool PrintAsTree = false) { formatted_raw_ostream OS(ROS); TypePrinting TypePrinter(M); - AsmWriterContext WriterCtx(&TypePrinter, MST.getMachine(), M); - WriteAsOperandInternal(OS, &MD, WriterCtx, /* FromValue */ true); + std::unique_ptr WriterCtx; + if (PrintAsTree && !OnlyAsOperand) + WriterCtx = std::make_unique( + &TypePrinter, MST.getMachine(), M, OS, &MD); + else + WriterCtx = + std::make_unique(&TypePrinter, MST.getMachine(), M); + + WriteAsOperandInternal(OS, &MD, *WriterCtx, /* FromValue */ true); auto *N = dyn_cast(&MD); if (OnlyAsOperand || !N || isa(MD) || isa(MD)) return; OS << " = "; - WriteMDNodeBodyInternal(OS, N, WriterCtx); + WriteMDNodeBodyInternal(OS, N, *WriterCtx); } void Metadata::printAsOperand(raw_ostream &OS, const Module *M) const { @@ -4697,6 +4771,18 @@ printMetadataImpl(OS, *this, MST, M, /* OnlyAsOperand */ false); } +void MDNode::printTree(raw_ostream &OS, const Module *M) const { + ModuleSlotTracker MST(M, true); + printMetadataImpl(OS, *this, MST, M, /* OnlyAsOperand */ false, + /*PrintAsTree=*/true); +} + +void MDNode::printTree(raw_ostream &OS, ModuleSlotTracker &MST, + const Module *M) const { + printMetadataImpl(OS, *this, MST, M, /* OnlyAsOperand */ false, + /*PrintAsTree=*/true); +} + void ModuleSummaryIndex::print(raw_ostream &ROS, bool IsForDebug) const { SlotTracker SlotTable(this); formatted_raw_ostream OS(ROS); @@ -4748,6 +4834,15 @@ dbgs() << '\n'; } +LLVM_DUMP_METHOD +void MDNode::dumpTree() const { dumpTree(nullptr); } + +LLVM_DUMP_METHOD +void MDNode::dumpTree(const Module *M) const { + printTree(dbgs(), M); + dbgs() << '\n'; +} + // Allow printing of ModuleSummaryIndex from the debugger. LLVM_DUMP_METHOD void ModuleSummaryIndex::dump() const { print(dbgs(), /*IsForDebug=*/true); } Index: llvm/unittests/IR/MetadataTest.cpp =================================================================== --- llvm/unittests/IR/MetadataTest.cpp +++ llvm/unittests/IR/MetadataTest.cpp @@ -423,6 +423,67 @@ ModuleSlotTracker MST(&M); EXPECT_PRINTER_EQ("!0 = distinct !{}", N0->print(OS, MST)); } + +TEST_F(MDNodeTest, PrintTree) { + DILocalScope *Scope = getSubprogram(); + DIFile *File = getFile(); + DINode::DIFlags Flags = static_cast(7); + { + DIType *Type = getDerivedType(); + auto *Var = DILocalVariable::get(Context, Scope, "foo", File, + /*LineNo=*/8, Type, /*ArgNo=*/2, Flags, + /*Align=*/8, nullptr); + std::string Expected; + { + raw_string_ostream SS(Expected); + Var->print(SS); + // indent level 1 + Scope->print((SS << "\n").indent(2)); + File->print((SS << "\n").indent(2)); + Type->print((SS << "\n").indent(2)); + // indent level 2 + auto *BaseType = cast(Type)->getBaseType(); + BaseType->print((SS << "\n").indent(4)); + } + + EXPECT_PRINTER_EQ(Expected, Var->printTree(OS)); + } + + { + // Test if printTree works correctly when there is + // a cycle in the MDNode and its dependencies. + // + // We're trying to create type like this: + // struct LinkedList { + // LinkedList *Head; + // }; + auto *StructTy = cast(getCompositeType()); + DIType *PointerTy = DIDerivedType::getDistinct( + Context, dwarf::DW_TAG_pointer_type, "", nullptr, 0, nullptr, StructTy, + 1, 2, 0, None, DINode::FlagZero); + StructTy->replaceElements(MDTuple::get(Context, PointerTy)); + + auto *Var = DILocalVariable::get(Context, Scope, "foo", File, + /*LineNo=*/8, StructTy, /*ArgNo=*/2, Flags, + /*Align=*/8, nullptr); + std::string Expected; + { + raw_string_ostream SS(Expected); + Var->print(SS); + // indent level 1 + Scope->print((SS << "\n").indent(2)); + File->print((SS << "\n").indent(2)); + StructTy->print((SS << "\n").indent(2)); + // indent level 2 + StructTy->getRawElements()->print((SS << "\n").indent(4)); + // indent level 3 + auto Elements = StructTy->getElements(); + Elements[0]->print((SS << "\n").indent(6)); + } + + EXPECT_PRINTER_EQ(Expected, Var->printTree(OS)); + } +} #undef EXPECT_PRINTER_EQ TEST_F(MDNodeTest, NullOperand) {