From 13ace6664fad8b4d0277d16690674f4e1f176642 Mon Sep 17 00:00:00 2001 From: Daniel Malea Date: Wed, 8 May 2013 20:44:14 +0000 Subject: Add DebugIR pass -- emits IR file and replace source lines with IR lines in MD - requires existing debug information to be present - fixes up file name and line number information in metadata - emits a "-debug.ll" succinct IR file (without !dbg metadata or debug intrinsics) that can be read by a debugger - initialize pass in opt tool to enable the "-debug-ir" flag - lit tests to follow git-svn-id: https://llvm.org/svn/llvm-project/llvm/trunk@181467 91177308-0d34-0410-b5e6-96231b3b80d8 --- include/llvm/DebugInfo.h | 3 + include/llvm/InitializePasses.h | 1 + include/llvm/Transforms/Instrumentation.h | 4 + lib/IR/DebugInfo.cpp | 7 + lib/Transforms/Instrumentation/CMakeLists.txt | 1 + lib/Transforms/Instrumentation/DebugIR.cpp | 246 ++++++++++++++++++++++++++ tools/opt/opt.cpp | 1 + 7 files changed, 263 insertions(+) create mode 100644 lib/Transforms/Instrumentation/DebugIR.cpp diff --git a/include/llvm/DebugInfo.h b/include/llvm/DebugInfo.h index 1c7edc971b..5f92ca22ec 100644 --- a/include/llvm/DebugInfo.h +++ b/include/llvm/DebugInfo.h @@ -166,6 +166,9 @@ namespace llvm { public: explicit DIScope(const MDNode *N = 0) : DIDescriptor (N) {} + /// Set the filename by allocating a new string MDNode for + /// it and attaching it to the underlying node. + void setFilename(StringRef Name, LLVMContext &Context); StringRef getFilename() const; StringRef getDirectory() const; }; diff --git a/include/llvm/InitializePasses.h b/include/llvm/InitializePasses.h index 5b2cd603c3..072c2e4895 100644 --- a/include/llvm/InitializePasses.h +++ b/include/llvm/InitializePasses.h @@ -100,6 +100,7 @@ void initializeDAEPass(PassRegistry&); void initializeDAHPass(PassRegistry&); void initializeDCEPass(PassRegistry&); void initializeDSEPass(PassRegistry&); +void initializeDebugIRPass(PassRegistry&); void initializeDeadInstEliminationPass(PassRegistry&); void initializeDeadMachineInstructionElimPass(PassRegistry&); void initializeDependenceAnalysisPass(PassRegistry&); diff --git a/include/llvm/Transforms/Instrumentation.h b/include/llvm/Transforms/Instrumentation.h index 4aae200dd0..142b40f1ea 100644 --- a/include/llvm/Transforms/Instrumentation.h +++ b/include/llvm/Transforms/Instrumentation.h @@ -78,6 +78,10 @@ FunctionPass *createThreadSanitizerPass(StringRef BlacklistFile = StringRef()); // checking on loads, stores, and other memory intrinsics. FunctionPass *createBoundsCheckingPass(); +/// createDebugIRPass - Create and return a pass that modifies a module's +/// debug metadata to point back to IR instead of the original source file +ModulePass *createDebugIRPass(StringRef FilenamePostfix); + } // End llvm namespace #endif diff --git a/lib/IR/DebugInfo.cpp b/lib/IR/DebugInfo.cpp index 3be5e0f849..38fc90f865 100644 --- a/lib/IR/DebugInfo.cpp +++ b/lib/IR/DebugInfo.cpp @@ -695,6 +695,13 @@ DIArray DISubprogram::getVariables() const { return DIArray(); } +void DIScope::setFilename(StringRef Name, LLVMContext &Context) { + if (!DbgNode) + return; + MDString *MDName(MDString::get(Context, Name)); + const_cast(getNodeField(DbgNode, 1))->replaceOperandWith(0, MDName); +} + StringRef DIScope::getFilename() const { if (!DbgNode) return StringRef(); diff --git a/lib/Transforms/Instrumentation/CMakeLists.txt b/lib/Transforms/Instrumentation/CMakeLists.txt index 1c9e053679..aa265a49ed 100644 --- a/lib/Transforms/Instrumentation/CMakeLists.txt +++ b/lib/Transforms/Instrumentation/CMakeLists.txt @@ -2,6 +2,7 @@ add_llvm_library(LLVMInstrumentation AddressSanitizer.cpp BlackList.cpp BoundsChecking.cpp + DebugIR.cpp EdgeProfiling.cpp GCOVProfiling.cpp MemorySanitizer.cpp diff --git a/lib/Transforms/Instrumentation/DebugIR.cpp b/lib/Transforms/Instrumentation/DebugIR.cpp new file mode 100644 index 0000000000..62a9b8abd8 --- /dev/null +++ b/lib/Transforms/Instrumentation/DebugIR.cpp @@ -0,0 +1,246 @@ +//===--- DebugIR.cpp - Transform debug metadata to allow debugging IR -----===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// +// +// A Module transform pass that emits a succinct version of the IR and replaces +// the source file metadata to allow debuggers to step through the IR. +// +// The location where the IR file is emitted is the same as the directory +// operand of the !llvm.dbg.cu metadata node present in the input module. The +// file name is constructed from the original file name by stripping the +// extension and replacing it with "-debug.ll" or the Postfix string specified +// at construction. +// +// FIXME: instead of replacing debug metadata, additional metadata should be +// used to point capable debuggers to the IR file without destroying the +// mapping to the original source file. +// +// FIXME: this pass should not depend on the existance of debug metadata in +// the module as it does now. Instead, it should use DIBuilder to create the +// required metadata. +// +//===----------------------------------------------------------------------===// + +#include + +#include "llvm/ADT/ArrayRef.h" +#include "llvm/ADT/SmallSet.h" +#include "llvm/DebugInfo.h" +#include "llvm/DIBuilder.h" +#include "llvm/IR/AsmWriter.h" +#include "llvm/IR/Instruction.h" +#include "llvm/IR/IntrinsicInst.h" +#include "llvm/IR/Module.h" +#include "llvm/Pass.h" +#include "llvm/Transforms/Instrumentation.h" +#include "llvm/Support/Debug.h" +#include "llvm/Support/ToolOutputFile.h" +#include "llvm/Support/raw_ostream.h" +using namespace llvm; + +namespace { + +/// Returns true if Node's name contains the string "llvm.dbg" +bool isDebugNamedMetadata(const NamedMDNode *Node) { + return Node->getName().str().find("llvm.dbg") != std::string::npos; +} + +/// Returns true if Inst is a call to llvm.dbg.value or llvm.dbg.declare +bool isDebugIntrinsic(const IntrinsicInst *Inst) { + Intrinsic::ID id = Inst->getIntrinsicID(); + return id == Intrinsic::dbg_value || id == Intrinsic::dbg_declare; +} + +/// An AssemblyWriter which generates a succinct representation of the module +/// (without debug intrinsics or metadata) suitable for debugging. As IR +/// instructions are printed, !dbg metadata nodes are added (or updated) +/// to point to the corresponding line in the generated IR file instead +/// of the original source file. The input module must have been constructed +/// with debug metadata (i.e. clang -g). +class IRDebugInfoHelper : public llvm::AssemblyWriter { + /// Directory of debug metadata + const DebugInfoFinder &Finder; + + /// Flags to control the verbosity of the generated IR file + bool hideDebugIntrinsics; + bool hideDebugMetadata; + + /// Set to track metadata nodes to be printed (used only when + /// printDebugMetadata == false) + SmallSet NonDebugNodes; + +public: + IRDebugInfoHelper( + formatted_raw_ostream &o, const Module *M, + AssemblyAnnotationWriter *AAW, const DebugInfoFinder &Finder, + bool hideDebugIntrinsics = true, bool hideDebugMetadata = true) + : AssemblyWriter(o, M, AAW), Finder(Finder), + hideDebugIntrinsics(hideDebugIntrinsics), + hideDebugMetadata(hideDebugMetadata) {} + +private: + virtual void printInstruction(const Instruction &I) { + DebugLoc Loc(I.getDebugLoc()); + + if (hideDebugMetadata) + removeDebugMetadata(const_cast(I)); + + AssemblyWriter::printInstruction(I); + Out.flush(); + // Adjust line number by 1 because we have not yet printed the \n + unsigned Line = Out.getLine() + 1; + + DebugLoc NewLoc; + if (!Loc.isUnknown()) + // I had a previous debug location: re-use the DebugLoc + NewLoc = DebugLoc::get(Line, /* FIXME: support columns */ 0, + Loc.getScope(I.getContext()), + Loc.getInlinedAt(I.getContext())); + else if (MDNode *scope = findFunctionMD(I.getParent()->getParent())) + // I had no previous debug location, but M has some debug information + NewLoc = DebugLoc::get(Line, 0, scope, /*FIXME: inlined instructions*/ 0); + else + // Neither I nor M has any debug information -- nothing to do here. + // FIXME: support debugging of undecorated IR (generated by clang without + // the -g option) + return; + + if (hideDebugMetadata) + saveNonDebugMetadata(I); + + addDebugLocation(const_cast(I), NewLoc); + } + + virtual void printInstructionLine(const Instruction &I) { + if (hideDebugIntrinsics) + if (const IntrinsicInst *II = dyn_cast(&I)) + if (isDebugIntrinsic(II)) + return; + AssemblyWriter::printInstructionLine(I); + } + + virtual void writeMDNode(unsigned Slot, const MDNode *Node) { + if (hideDebugMetadata == false || isDebugMetadata(Node) == false) + AssemblyWriter::writeMDNode(Slot, Node); + } + + virtual void printNamedMDNode(const NamedMDNode *NMD) { + if (hideDebugMetadata == false || isDebugNamedMetadata(NMD) == false) + AssemblyWriter::printNamedMDNode(NMD); + } + + /// Returns the MDNode that corresponds with F + MDNode *findFunctionMD(const Function *F) { + for (DebugInfoFinder::iterator i = Finder.subprogram_begin(), + e = Finder.subprogram_end(); + i != e; ++i) { + DISubprogram S(*i); + if (S.getFunction() == F) + return *i; + } + // cannot find F -- likely means there is no debug information + return 0; + } + + /// Saves all non-debug metadata attached to I + void saveNonDebugMetadata(const Instruction &I) { + typedef SmallVector, 4> MDNodeVector; + MDNodeVector Others; + I.getAllMetadataOtherThanDebugLoc(Others); + for (MDNodeVector::iterator i = Others.begin(), e = Others.end(); i != e; + ++i) + NonDebugNodes.insert(i->second); + } + + /// Returns true if Node was not saved as non-debug metadata with + /// saveNonDebugMetadata(), false otherwise. + bool isDebugMetadata(const MDNode *Node) { + return NonDebugNodes.count(Node) == 0; + } + + void removeDebugMetadata(Instruction &I) { + if (I.getMetadata(LLVMContext::MD_dbg)) + I.setMetadata(LLVMContext::MD_dbg, 0); + } + + void addDebugLocation(Instruction &I, DebugLoc Loc) { + MDNode *MD = Loc.getAsMDNode(I.getContext()); + I.setMetadata(LLVMContext::MD_dbg, MD); + } +}; + +class DebugIR : public ModulePass { + std::string Postfix; + std::string Filename; + DebugInfoFinder Finder; + +public: + static char ID; + + DebugIR() : ModulePass(ID), Postfix("-debug.ll") {} + + /// Customize the postfix string used to replace the extension of the + /// original filename that appears in the !llvm.dbg.cu metadata node. + DebugIR(StringRef postfix) : ModulePass(ID), Postfix(postfix) {} + +private: + // Modify the filename embedded in the Compilation-Unit debug information of M + bool replaceFilename(Module &M) { + bool changed = false; + + // Sanity check -- if llvm.dbg.cu node exists, the DebugInfoFinder + // better have found at least one CU! + if (M.getNamedMetadata("llvm.dbg.cu")) + assert(Finder.compile_unit_count() > 0 && + "Found no compile units but llvm.dbg.cu node exists"); + + for (DebugInfoFinder::iterator i = Finder.compile_unit_begin(), + e = Finder.compile_unit_end(); + i != e; ++i) { + DICompileUnit CU(*i); + Filename = CU.getFilename(); + + // Replace extension with postfix + size_t dot = Filename.find_last_of("."); + if (dot != std::string::npos) + Filename.erase(dot); + Filename += Postfix; + + CU.setFilename(Filename, M.getContext()); + changed = true; + } + return changed; + } + + void writeAndUpdateDebugIRFile(Module *M) { + std::string error; + tool_output_file OutFile(Filename.c_str(), error); + OutFile.keep(); + formatted_raw_ostream OS; + OS.setStream(OutFile.os(), false); + + IRDebugInfoHelper W(OS, M, 0, Finder); + W.printModule(M); + } + + bool runOnModule(Module &M) { + Finder.processModule(M); + bool changed = replaceFilename(M); + if (changed) + writeAndUpdateDebugIRFile(&M); + return changed; + } +}; + +} // anonymous namespace + +char DebugIR::ID = 0; +INITIALIZE_PASS(DebugIR, "debug-ir", "Enable debugging IR", false, false) + ModulePass *llvm::createDebugIRPass(StringRef FilenamePostfix) { + return new DebugIR(FilenamePostfix); +} diff --git a/tools/opt/opt.cpp b/tools/opt/opt.cpp index e385d7f577..57b03b4c48 100644 --- a/tools/opt/opt.cpp +++ b/tools/opt/opt.cpp @@ -567,6 +567,7 @@ int main(int argc, char **argv) { // Initialize passes PassRegistry &Registry = *PassRegistry::getPassRegistry(); initializeCore(Registry); + initializeDebugIRPass(Registry); initializeScalarOpts(Registry); initializeObjCARCOpts(Registry); initializeVectorization(Registry); -- cgit v1.2.3