Index: llvm/trunk/docs/ReleaseNotes.rst =================================================================== --- llvm/trunk/docs/ReleaseNotes.rst +++ llvm/trunk/docs/ReleaseNotes.rst @@ -49,6 +49,11 @@ the name used in the `def X : Target` definition to the call to `RegisterTarget`. +* The ``Debugify`` pass was added to ``opt`` to facilitate testing of debug + info preservation. This pass attaches synthetic ``DILocations`` and + ``DIVariables`` to the instructions in a ``Module``. The ``CheckDebugify`` + pass determines how much of the metadata is lost. + * Note.. .. NOTE Index: llvm/trunk/test/DebugInfo/debugify.ll =================================================================== --- llvm/trunk/test/DebugInfo/debugify.ll +++ llvm/trunk/test/DebugInfo/debugify.ll @@ -0,0 +1,65 @@ +; RUN: opt -debugify -S -o - < %s | FileCheck %s + +; RUN: opt -debugify -debugify -S -o - < %s 2>&1 | \ +; RUN: FileCheck %s -check-prefix=CHECK-REPEAT + +; RUN: opt -debugify -check-debugify -S -o - < %s | \ +; RUN: FileCheck %s -implicit-check-not="CheckDebugify: FAIL" + +; RUN: opt -debugify -strip -check-debugify -S -o - < %s | \ +; RUN: FileCheck %s -check-prefix=CHECK-FAIL + +; CHECK-LABEL: define void @foo +define void @foo() { +; CHECK: ret void, !dbg ![[RET1:.*]] + ret void +} + +; CHECK-LABEL: define i32 @bar +define i32 @bar() { +; CHECK: call void @foo(), !dbg ![[CALL1:.*]] + call void @foo() + +; CHECK: add i32 0, 1, !dbg ![[ADD1:.*]] + %sum = add i32 0, 1 + +; CHECK: ret i32 0, !dbg ![[RET2:.*]] + ret i32 0 +} + +; CHECK-DAG: !llvm.dbg.cu = !{![[CU:.*]]} +; CHECK-DAG: !llvm.debugify = !{![[NUM_INSTS:.*]], ![[NUM_VARS:.*]]} + +; CHECK-DAG: ![[CU]] = distinct !DICompileUnit(language: DW_LANG_C, file: {{.*}}, producer: "debugify", isOptimized: true, runtimeVersion: 0, emissionKind: FullDebug, enums: {{.*}}) +; CHECK-DAG: !DIFile(filename: "", directory: "/") +; CHECK-DAG: distinct !DISubprogram(name: "foo", linkageName: "foo", scope: null, file: {{.*}}, line: 1, type: {{.*}}, isLocal: false, isDefinition: true, scopeLine: 1, isOptimized: true, unit: {{.*}}, variables: {{.*}}) +; CHECK-DAG: distinct !DISubprogram(name: "bar", linkageName: "bar", scope: null, file: {{.*}}, line: 2, type: {{.*}}, isLocal: false, isDefinition: true, scopeLine: 2, isOptimized: true, unit: {{.*}}, variables: {{.*}}) + +; --- DILocations +; CHECK-DAG: ![[RET1]] = !DILocation(line: 1, column: 1 +; CHECK-DAG: ![[CALL1]] = !DILocation(line: 2, column: 1 +; CHECK-DAG: ![[ADD1]] = !DILocation(line: 3, column: 1 +; CHECK-DAG: ![[RET2]] = !DILocation(line: 4, column: 1 + +; --- DILocalVariables +; CHECK-DAG: ![[TY32:.*]] = !DIBasicType(name: "ty32", size: 32, encoding: DW_ATE_unsigned) +; CHECK-DAG: !DILocalVariable(name: "1", scope: {{.*}}, file: {{.*}}, line: 3, type: ![[TY32]]) + +; --- Metadata counts +; CHECK-DAG: ![[NUM_INSTS]] = !{i32 4} +; CHECK-DAG: ![[NUM_VARS]] = !{i32 1} + +; --- Repeat case +; CHECK-REPEAT: Debugify: Skipping module with debug info + +; --- Failure case +; CHECK-FAIL: ERROR: Instruction with empty DebugLoc -- ret void +; CHECK-FAIL: ERROR: Instruction with empty DebugLoc -- call void @foo() +; CHECK-FAIL: ERROR: Instruction with empty DebugLoc -- {{.*}} add i32 0, 1 +; CHECK-FAIL: ERROR: Instruction with empty DebugLoc -- ret i32 0 +; CHECK-FAIL: WARNING: Missing line 1 +; CHECK-FAIL: WARNING: Missing line 2 +; CHECK-FAIL: WARNING: Missing line 3 +; CHECK-FAIL: WARNING: Missing line 4 +; CHECK-FAIL: ERROR: Missing variable 1 +; CHECK-FAIL: CheckDebugify: FAIL Index: llvm/trunk/test/Transforms/Mem2Reg/PromoteMemToRegister.ll =================================================================== --- llvm/trunk/test/Transforms/Mem2Reg/PromoteMemToRegister.ll +++ llvm/trunk/test/Transforms/Mem2Reg/PromoteMemToRegister.ll @@ -1,5 +1,8 @@ ; Simple sanity check testcase. Both alloca's should be eliminated. -; RUN: opt < %s -mem2reg -S | not grep alloca +; RUN: opt < %s -debugify -mem2reg -check-debugify -S | FileCheck %s + +; CHECK-NOT: alloca +; CHECK: CheckDebugify: PASS define double @testfunc(i32 %i, double %j) { %I = alloca i32 ; [#uses=4] Index: llvm/trunk/tools/opt/CMakeLists.txt =================================================================== --- llvm/trunk/tools/opt/CMakeLists.txt +++ llvm/trunk/tools/opt/CMakeLists.txt @@ -25,6 +25,7 @@ add_llvm_tool(opt AnalysisWrappers.cpp BreakpointPrinter.cpp + Debugify.cpp GraphPrinters.cpp NewPMDriver.cpp PassPrinters.cpp Index: llvm/trunk/tools/opt/Debugify.cpp =================================================================== --- llvm/trunk/tools/opt/Debugify.cpp +++ llvm/trunk/tools/opt/Debugify.cpp @@ -0,0 +1,212 @@ +//===- Debugify.cpp - Attach synthetic debug info to everything -----------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// +/// +/// \file This pass attaches synthetic debug info to everything. It can be used +/// to create targeted tests for debug info preservation. +/// +//===----------------------------------------------------------------------===// + +#include "llvm/ADT/BitVector.h" +#include "llvm/ADT/StringExtras.h" +#include "llvm/IR/BasicBlock.h" +#include "llvm/IR/Constants.h" +#include "llvm/IR/DIBuilder.h" +#include "llvm/IR/DebugInfo.h" +#include "llvm/IR/Function.h" +#include "llvm/IR/GlobalVariable.h" +#include "llvm/IR/InstIterator.h" +#include "llvm/IR/Instruction.h" +#include "llvm/IR/Instructions.h" +#include "llvm/IR/IntrinsicInst.h" +#include "llvm/IR/Module.h" +#include "llvm/IR/Type.h" +#include "llvm/Pass.h" +#include "llvm/Support/raw_ostream.h" +#include "llvm/Transforms/IPO.h" + +using namespace llvm; + +namespace { + +bool applyDebugifyMetadata(Module &M) { + // Skip modules with debug info. + if (M.getNamedMetadata("llvm.dbg.cu")) { + errs() << "Debugify: Skipping module with debug info\n"; + return false; + } + + DIBuilder DIB(M); + LLVMContext &Ctx = M.getContext(); + + // Get a DIType which corresponds to Ty. + DenseMap TypeCache; + auto getCachedDIType = [&](Type *Ty) -> DIType * { + uint64_t Size = M.getDataLayout().getTypeAllocSizeInBits(Ty); + DIType *&DTy = TypeCache[Size]; + if (!DTy) { + std::string Name = "ty" + utostr(Size); + DTy = DIB.createBasicType(Name, Size, dwarf::DW_ATE_unsigned); + } + return DTy; + }; + + unsigned NextLine = 1; + unsigned NextVar = 1; + auto File = DIB.createFile(M.getName(), "/"); + auto CU = + DIB.createCompileUnit(dwarf::DW_LANG_C, DIB.createFile(M.getName(), "/"), + "debugify", /*isOptimized=*/true, "", 0); + + // Visit each instruction. + for (Function &F : M) { + if (F.isDeclaration()) + continue; + + auto SPType = DIB.createSubroutineType(DIB.getOrCreateTypeArray(None)); + bool IsLocalToUnit = F.hasPrivateLinkage() || F.hasInternalLinkage(); + auto SP = + DIB.createFunction(CU, F.getName(), F.getName(), File, NextLine, SPType, + IsLocalToUnit, F.hasExactDefinition(), NextLine, + DINode::FlagZero, /*isOptimized=*/true); + F.setSubprogram(SP); + for (BasicBlock &BB : F) { + // Attach debug locations. + for (Instruction &I : BB) + I.setDebugLoc(DILocation::get(Ctx, NextLine++, 1, SP)); + + // Attach debug values. + for (Instruction &I : BB) { + // Skip void-valued instructions. + if (I.getType()->isVoidTy()) + continue; + + // Skip the terminator instruction and any just-inserted intrinsics. + if (isa(&I) || isa(&I)) + break; + + std::string Name = utostr(NextVar++); + const DILocation *Loc = I.getDebugLoc().get(); + auto LocalVar = DIB.createAutoVariable(SP, Name, File, Loc->getLine(), + getCachedDIType(I.getType()), + /*AlwaysPreserve=*/true); + DIB.insertDbgValueIntrinsic(&I, LocalVar, DIB.createExpression(), Loc, + BB.getTerminator()); + } + } + DIB.finalizeSubprogram(SP); + } + DIB.finalize(); + + // Track the number of distinct lines and variables. + NamedMDNode *NMD = M.getOrInsertNamedMetadata("llvm.debugify"); + auto *IntTy = Type::getInt32Ty(Ctx); + auto addDebugifyOperand = [&](unsigned N) { + NMD->addOperand(MDNode::get( + Ctx, ValueAsMetadata::getConstant(ConstantInt::get(IntTy, N)))); + }; + addDebugifyOperand(NextLine - 1); // Original number of lines. + addDebugifyOperand(NextVar - 1); // Original number of variables. + return true; +} + +void checkDebugifyMetadata(Module &M) { + // Skip modules without debugify metadata. + NamedMDNode *NMD = M.getNamedMetadata("llvm.debugify"); + if (!NMD) + return; + + auto getDebugifyOperand = [&](unsigned Idx) -> unsigned { + return mdconst::extract(NMD->getOperand(Idx)->getOperand(0)) + ->getZExtValue(); + }; + unsigned OriginalNumLines = getDebugifyOperand(0); + unsigned OriginalNumVars = getDebugifyOperand(1); + bool HasErrors = false; + + // Find missing lines. + BitVector MissingLines{OriginalNumLines, true}; + for (Function &F : M) { + for (Instruction &I : instructions(F)) { + if (isa(&I)) + continue; + + auto DL = I.getDebugLoc(); + if (DL) { + MissingLines.reset(DL.getLine() - 1); + continue; + } + + outs() << "ERROR: Instruction with empty DebugLoc -- "; + I.print(outs()); + outs() << "\n"; + HasErrors = true; + } + } + for (unsigned Idx : MissingLines.set_bits()) + outs() << "WARNING: Missing line " << Idx + 1 << "\n"; + + // Find missing variables. + BitVector MissingVars{OriginalNumVars, true}; + for (Function &F : M) { + for (Instruction &I : instructions(F)) { + auto *DVI = dyn_cast(&I); + if (!DVI) + continue; + + unsigned Var = ~0U; + (void)to_integer(DVI->getVariable()->getName(), Var, 10); + assert(Var <= OriginalNumVars && "Unexpected name for DILocalVariable"); + MissingVars.reset(Var - 1); + } + } + for (unsigned Idx : MissingVars.set_bits()) + outs() << "ERROR: Missing variable " << Idx + 1 << "\n"; + HasErrors |= MissingVars.count() > 0; + + outs() << "CheckDebugify: " << (HasErrors ? "FAIL" : "PASS") << "\n"; +} + +/// Attach synthetic debug info to everything. +struct DebugifyPass : public ModulePass { + bool runOnModule(Module &M) override { return applyDebugifyMetadata(M); } + + DebugifyPass() : ModulePass(ID) {} + + void getAnalysisUsage(AnalysisUsage &AU) const override { + AU.setPreservesAll(); + } + + static char ID; // Pass identification. +}; + +/// Check debug info inserted by -debugify for completeness. +struct CheckDebugifyPass : public ModulePass { + bool runOnModule(Module &M) override { + checkDebugifyMetadata(M); + return false; + } + + CheckDebugifyPass() : ModulePass(ID) {} + + void getAnalysisUsage(AnalysisUsage &AU) const override { + AU.setPreservesAll(); + } + + static char ID; // Pass identification. +}; + +} // end anonymous namespace + +char DebugifyPass::ID = 0; +static RegisterPass X("debugify", + "Attach debug info to everything"); + +char CheckDebugifyPass::ID = 0; +static RegisterPass Y("check-debugify", + "Check debug info from -debugify");