Index: llvm/docs/HowToUpdateDebugInfo.rst =================================================================== --- llvm/docs/HowToUpdateDebugInfo.rst +++ llvm/docs/HowToUpdateDebugInfo.rst @@ -218,8 +218,8 @@ mutated to test debug info handling within that transformation. This is a simple way to test for proper debug info handling. -The ``debugify`` utility -^^^^^^^^^^^^^^^^^^^^^^^^ +The ``debugify`` utility pass +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ The ``debugify`` testing utility is just a pair of passes: ``debugify`` and ``check-debugify``. @@ -335,6 +335,22 @@ .. _MIRDebugify: +Test original debug info preservation in optimizations +------------------------------------------------------ + +The ``debugify`` utility pass can operate in a different mode, when it checks +the preservation of the original/real Debug Info metadata in optimizations. +Before a pass it collects the metadata, and after the pass it checks if it +preserved the Debug Info metadata. It could be run as follows: + +.. code-block:: bash + + # Run the pass by checking original Debug Info preservation. + $ opt -verify-debuginfo-preserve -pass-to-test sample.ll + + # Check the preservation of original Debug Info after each pass. + $ opt -verify-each-debuginfo-preserve -O2 sample.ll + Mutation testing for MIR-level transformations ---------------------------------------------- Index: llvm/include/llvm/Transforms/Utils/Debugify.h =================================================================== --- llvm/include/llvm/Transforms/Utils/Debugify.h +++ llvm/include/llvm/Transforms/Utils/Debugify.h @@ -1,4 +1,4 @@ -//===- Debugify.h - Attach synthetic debug info to everything -------------===// +//===- Debugify.h - Check debug info preservation in optimizations --------===// // // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. // See https://llvm.org/LICENSE.txt for license information. @@ -6,19 +6,41 @@ // //===----------------------------------------------------------------------===// /// -/// \file Interface to the `debugify` synthetic debug info testing utility. +/// \file Interface to the `debugify` synthetic/original debug info testing +/// utility. /// //===----------------------------------------------------------------------===// #ifndef LLVM_TRANSFORM_UTILS_DEBUGIFY_H #define LLVM_TRANSFORM_UTILS_DEBUGIFY_H +#include "llvm/ADT/DenseMap.h" #include "llvm/ADT/MapVector.h" #include "llvm/ADT/StringRef.h" #include "llvm/Bitcode/BitcodeWriterPass.h" #include "llvm/IR/IRPrintingPasses.h" #include "llvm/IR/LegacyPassManager.h" #include "llvm/IR/PassManager.h" +#include "llvm/IR/ValueHandle.h" + +using DebugFnMap = llvm::DenseMap; +using DebugInstMap = llvm::DenseMap; +using WeakInstValueMap = + llvm::DenseMap; + +/// Used to track the Debug Info Metadata information. +struct DebugInfoPerPass { + // This maps a function name to its associated DISubprogram. + DebugFnMap DIFunctions; + // This maps an instruction and the info about whether it has !dbg attached. + DebugInstMap DILocations; + // This tracks value (instruction) deletion. If an instruction gets deleted, + // WeakVH nulls itself. + WeakInstValueMap InstToDelete; +}; + +/// Map pass names to a per-pass DebugInfoPerPass instance. +using DebugInfoPerPassMap = llvm::MapVector; namespace llvm { class DIBuilder; @@ -40,16 +62,49 @@ /// Returns true if any change was made. bool stripDebugifyMetadata(Module &M); +/// Collect original debug information before a pass. +/// +/// \param M The module to collect debug information from. +/// \param Functions A range of functions to collect debug information from. +/// \param DIPreservationMap A map to collect the DI metadata. +/// \param Banner A prefix string to add to debug/error messages. +/// \param NameOfWrappedPass A name of a pass to add to debug/error messages. +bool collectDebugInfoMetadata(Module &M, + iterator_range Functions, + DebugInfoPerPassMap &DIPreservationMap, + StringRef Banner, StringRef NameOfWrappedPass); + +/// Check original debug information after a pass. +/// +/// \param M The module to collect debug information from. +/// \param Functions A range of functions to collect debug information from. +/// \param DIPreservationMap A map used to check collected the DI metadata. +/// \param Banner A prefix string to add to debug/error messages. +/// \param NameOfWrappedPass A name of a pass to add to debug/error messages. +bool checkDebugInfoMetadata(Module &M, + iterator_range Functions, + DebugInfoPerPassMap &DIPreservationMap, + StringRef Banner, StringRef NameOfWrappedPass); } // namespace llvm -llvm::ModulePass *createDebugifyModulePass(); -llvm::FunctionPass *createDebugifyFunctionPass(); +/// Used to check whether we track synthetic or original debug info. +enum class DebugifyMode { NoDebugify, SyntheticDebugInfo, OriginalDebugInfo }; + +llvm::ModulePass *createDebugifyModulePass( + enum DebugifyMode Mode = DebugifyMode::SyntheticDebugInfo, + llvm::StringRef NameOfWrappedPass = "", + DebugInfoPerPassMap *DIPreservationMap = nullptr); +llvm::FunctionPass *createDebugifyFunctionPass( + enum DebugifyMode Mode = DebugifyMode::SyntheticDebugInfo, + llvm::StringRef NameOfWrappedPass = "", + DebugInfoPerPassMap *DIPreservationMap = nullptr); struct NewPMDebugifyPass : public llvm::PassInfoMixin { llvm::PreservedAnalyses run(llvm::Module &M, llvm::ModuleAnalysisManager &AM); }; -/// Track how much `debugify` information has been lost. +/// Track how much `debugify` information (in the `synthetic` mode only) +/// has been lost. struct DebugifyStatistics { /// Number of missing dbg.values. unsigned NumDbgValuesMissing = 0; @@ -77,15 +132,17 @@ /// Map pass names to a per-pass DebugifyStatistics instance. using DebugifyStatsMap = llvm::MapVector; -llvm::ModulePass * -createCheckDebugifyModulePass(bool Strip = false, - llvm::StringRef NameOfWrappedPass = "", - DebugifyStatsMap *StatsMap = nullptr); +llvm::ModulePass *createCheckDebugifyModulePass( + bool Strip = false, llvm::StringRef NameOfWrappedPass = "", + DebugifyStatsMap *StatsMap = nullptr, + enum DebugifyMode Mode = DebugifyMode::SyntheticDebugInfo, + DebugInfoPerPassMap *DIPreservationMap = nullptr); -llvm::FunctionPass * -createCheckDebugifyFunctionPass(bool Strip = false, - llvm::StringRef NameOfWrappedPass = "", - DebugifyStatsMap *StatsMap = nullptr); +llvm::FunctionPass *createCheckDebugifyFunctionPass( + bool Strip = false, llvm::StringRef NameOfWrappedPass = "", + DebugifyStatsMap *StatsMap = nullptr, + enum DebugifyMode Mode = DebugifyMode::SyntheticDebugInfo, + DebugInfoPerPassMap *DIPreservationMap = nullptr); struct NewPMCheckDebugifyPass : public llvm::PassInfoMixin { @@ -98,8 +155,9 @@ /// NOTE: We support legacy custom pass manager only. /// TODO: Add New PM support for custom pass manager. class DebugifyCustomPassManager : public legacy::PassManager { - DebugifyStatsMap DIStatsMap; - bool EnableDebugifyEach = false; + DebugifyStatsMap *DIStatsMap = nullptr; + DebugInfoPerPassMap *DIPreservationMap = nullptr; + enum DebugifyMode Mode = DebugifyMode::NoDebugify; public: using super = legacy::PassManager; @@ -107,29 +165,34 @@ void add(Pass *P) override { // Wrap each pass with (-check)-debugify passes if requested, making // exceptions for passes which shouldn't see -debugify instrumentation. - bool WrapWithDebugify = EnableDebugifyEach && !P->getAsImmutablePass() && - !isIRPrintingPass(P) && !isBitcodeWriterPass(P); + bool WrapWithDebugify = + Mode != DebugifyMode::NoDebugify && + !P->getAsImmutablePass() && !isIRPrintingPass(P) && + !isBitcodeWriterPass(P); if (!WrapWithDebugify) { super::add(P); return; } - // Apply -debugify/-check-debugify before/after each pass and collect - // debug info loss statistics. + // Either apply -debugify/-check-debugify before/after each pass and collect + // debug info loss statistics, or collect and check original debug info in + // the optimizations. PassKind Kind = P->getPassKind(); StringRef Name = P->getPassName(); // TODO: Implement Debugify for LoopPass. switch (Kind) { case PT_Function: - super::add(createDebugifyFunctionPass()); + super::add(createDebugifyFunctionPass(Mode, Name, DIPreservationMap)); super::add(P); - super::add(createCheckDebugifyFunctionPass(true, Name, &DIStatsMap)); + super::add(createCheckDebugifyFunctionPass( + isSyntheticDebugInfo(), Name, DIStatsMap, Mode, DIPreservationMap)); break; case PT_Module: - super::add(createDebugifyModulePass()); + super::add(createDebugifyModulePass(Mode, Name, DIPreservationMap)); super::add(P); - super::add(createCheckDebugifyModulePass(true, Name, &DIStatsMap)); + super::add(createCheckDebugifyModulePass( + isSyntheticDebugInfo(), Name, DIStatsMap, Mode, DIPreservationMap)); break; default: super::add(P); @@ -137,9 +200,23 @@ } } - void enableDebugifyEach() { EnableDebugifyEach = true; } + // Used within DebugifyMode::SyntheticDebugInfo mode. + void setDIStatsMap(DebugifyStatsMap &StatMap) { DIStatsMap = &StatMap; } + // Used within DebugifyMode::OriginalDebugInfo mode. + void setDIPreservationMap(DebugInfoPerPassMap &PerPassMap) { + DIPreservationMap = &PerPassMap; + } + void setDebugifyMode(enum DebugifyMode M) { Mode = M; } + + bool isSyntheticDebugInfo() const { + return Mode == DebugifyMode::SyntheticDebugInfo; + } + bool isOriginalDebugInfoMode() const { + return Mode == DebugifyMode::OriginalDebugInfo; + } - const DebugifyStatsMap &getDebugifyStatsMap() const { return DIStatsMap; } + const DebugifyStatsMap &getDebugifyStatsMap() const { return *DIStatsMap; } + DebugInfoPerPassMap &getDebugInfoPerPassMap() { return *DIPreservationMap; } }; } // namespace llvm Index: llvm/lib/Transforms/Utils/Debugify.cpp =================================================================== --- llvm/lib/Transforms/Utils/Debugify.cpp +++ llvm/lib/Transforms/Utils/Debugify.cpp @@ -1,4 +1,4 @@ -//===- Debugify.cpp - Attach synthetic debug info to everything -----------===// +//===- Debugify.cpp - Check debug info preservation in optimizations ------===// // // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. // See https://llvm.org/LICENSE.txt for license information. @@ -6,8 +6,10 @@ // //===----------------------------------------------------------------------===// /// -/// \file This pass attaches synthetic debug info to everything. It can be used -/// to create targeted tests for debug info preservation. +/// \file In the `synthetic` mode, the `-debugify` attaches synthetic debug info +/// to everything. It can be used to create targeted tests for debug info +/// preservation. In addition, when using the `original` mode, it can check +/// original debug info preservation. The `synthetic` mode is default one. /// //===----------------------------------------------------------------------===// @@ -23,6 +25,8 @@ #include "llvm/Pass.h" #include "llvm/Support/CommandLine.h" +#define DEBUG_TYPE "debugify" + using namespace llvm; namespace { @@ -34,6 +38,8 @@ Locations, LocationsAndVariables }; + +// Used for the synthetic mode only. cl::opt DebugifyLevel( "debugify-level", cl::desc("Kind of debug info to add"), cl::values(clEnumValN(Level::Locations, "locations", "Locations only"), @@ -245,6 +251,209 @@ return Changed; } +bool llvm::collectDebugInfoMetadata(Module &M, + iterator_range Functions, + DebugInfoPerPassMap &DIPreservationMap, + StringRef Banner, + StringRef NameOfWrappedPass) { + LLVM_DEBUG(dbgs() << Banner << " (before) " << NameOfWrappedPass << '\n'); + + // Clear the map with the debug info before every single pass. + DIPreservationMap.clear(); + + if (!M.getNamedMetadata("llvm.dbg.cu")) { + dbg() << Banner << " Skipping module without debug info\n"; + return false; + } + + // Visit each instruction. + for (Function &F : Functions) { + if (isFunctionSkipped(F)) + continue; + + // Collect the DISubprogram. + auto *SP = F.getSubprogram(); + DIPreservationMap[NameOfWrappedPass].DIFunctions.insert({F.getName(), SP}); + if (SP) + LLVM_DEBUG(dbgs() << " Collecting subprogram: " << *SP << '\n'); + + for (BasicBlock &BB : F) { + // Collect debug locations (!dbg). + // TODO: Collect dbg.values. + for (Instruction &I : BB) { + // Skip PHIs. + if (isa(I)) + continue; + + // Skip debug instructions. + if (isa(&I)) + continue; + + LLVM_DEBUG(dbgs() << " Collecting info for inst: " << I << '\n'); + DIPreservationMap[NameOfWrappedPass].InstToDelete.insert({&I, &I}); + + const DILocation *Loc = I.getDebugLoc().get(); + bool HasLoc = Loc != nullptr; + DIPreservationMap[NameOfWrappedPass].DILocations.insert({&I, HasLoc}); + } + } + } + + return true; +} + +// This checks the preservation of original debug info attached to functions. +static bool checkFunctions(const DebugFnMap &DIFunctionsBefore, + const DebugFnMap &DIFunctionsAfter, + StringRef NameOfWrappedPass, + StringRef FileNameFromCU) { + bool Preserved = true; + for (const auto &F : DIFunctionsAfter) { + if (F.second) + continue; + auto SPIt = DIFunctionsBefore.find(F.first); + if (SPIt == DIFunctionsBefore.end()) { + dbg() << "ERROR: " << NameOfWrappedPass + << " did not generate DISubprogram for " << F.first << " from " + << FileNameFromCU << '\n'; + Preserved = false; + } else { + auto SP = SPIt->second; + if (!SP) + continue; + // If the function had the SP attached before the pass, consider it as + // a debug info bug. + dbg() << "ERROR: " << NameOfWrappedPass << " dropped DISubprogram of " + << F.first << " from " << FileNameFromCU << '\n'; + Preserved = false; + } + } + + return Preserved; +} + +// This checks the preservation of the original debug info attached to +// instructions. +static bool checkInstructions(const DebugInstMap &DILocsBefore, + const DebugInstMap &DILocsAfter, + const WeakInstValueMap &InstToDelete, + StringRef NameOfWrappedPass, + StringRef FileNameFromCU) { + bool Preserved = true; + for (const auto &L : DILocsAfter) { + if (L.second) + continue; + auto Instr = L.first; + + // In order to avoid pointer reuse/recycling, skip the values that might + // have been deleted during a pass. + auto WeakInstrPtr = InstToDelete.find(Instr); + if (WeakInstrPtr != InstToDelete.end() && !WeakInstrPtr->second) + continue; + + auto FnName = Instr->getFunction()->getName(); + auto BB = Instr->getParent(); + auto BBName = BB->hasName() ? BB->getName() : "no-name"; + + auto InstrIt = DILocsBefore.find(Instr); + if (InstrIt == DILocsBefore.end()) { + dbg() << "WARNING: " << NameOfWrappedPass + << " did not generate DILocation for " << *Instr + << " (BB: " << BBName << ", Fn: " << FnName + << ", File: " << FileNameFromCU << ")\n"; + Preserved = false; + } else { + if (!InstrIt->second) + continue; + // If the instr had the !dbg attached before the pass, consider it as + // a debug info issue. + dbg() << "WARNING: " << NameOfWrappedPass << " dropped DILocation of " + << *Instr << " (BB: " << BBName << ", Fn: " << FnName + << ", File: " << FileNameFromCU << ")\n"; + Preserved = false; + } + } + + return Preserved; +} + +bool llvm::checkDebugInfoMetadata(Module &M, + iterator_range Functions, + DebugInfoPerPassMap &DIPreservationMap, + StringRef Banner, + StringRef NameOfWrappedPass) { + LLVM_DEBUG(dbgs() << Banner << " (after) " << NameOfWrappedPass << '\n'); + + if (!M.getNamedMetadata("llvm.dbg.cu")) { + dbg() << Banner << " Skipping module without debug info\n"; + return false; + } + + // Map the debug info holding DIs after a pass. + DebugInfoPerPassMap DIPreservationAfter; + + // Visit each instruction. + for (Function &F : Functions) { + if (isFunctionSkipped(F)) + continue; + + // TODO: Collect metadata other than DISubprograms. + // Collect the DISubprogram. + auto *SP = F.getSubprogram(); + DIPreservationMap[NameOfWrappedPass].DIFunctions.insert({F.getName(), SP}); + if (SP) + LLVM_DEBUG(dbgs() << " Collecting subprogram: " << *SP << '\n'); + + for (BasicBlock &BB : F) { + // Collect debug locations (!dbg attachments). + // TODO: Collect dbg.values. + for (Instruction &I : BB) { + // Skip PHIs. + if (isa(I)) + continue; + + // Skip debug instructions. + if (isa(&I)) + continue; + + LLVM_DEBUG(dbgs() << " Collecting info for inst: " << I << '\n'); + + const DILocation *Loc = I.getDebugLoc().get(); + bool HasLoc = Loc != nullptr; + + DIPreservationAfter[NameOfWrappedPass].DILocations.insert({&I, HasLoc}); + } + } + } + + // TODO: The name of the module could be read better? + StringRef FileNameFromCU = + (cast(M.getNamedMetadata("llvm.dbg.cu")->getOperand(0))) + ->getFilename(); + + auto DIFunctionsBefore = DIPreservationMap[NameOfWrappedPass].DIFunctions; + auto DIFunctionsAfter = DIPreservationAfter[NameOfWrappedPass].DIFunctions; + + auto DILocsBefore = DIPreservationMap[NameOfWrappedPass].DILocations; + auto DILocsAfter = DIPreservationAfter[NameOfWrappedPass].DILocations; + + auto InstToDelete = DIPreservationAfter[NameOfWrappedPass].InstToDelete; + + bool Result = checkFunctions(DIFunctionsBefore, DIFunctionsAfter, + NameOfWrappedPass, FileNameFromCU) && + checkInstructions(DILocsBefore, DILocsAfter, InstToDelete, + NameOfWrappedPass, FileNameFromCU); + + StringRef ResultBanner = NameOfWrappedPass != "" ? NameOfWrappedPass : Banner; + if (Result) + dbg() << ResultBanner << ": PASS\n"; + else + dbg() << ResultBanner << ": FAIL\n"; + + LLVM_DEBUG(dbgs() << "\n\n"); + return Result; +} + namespace { /// Return true if a mis-sized diagnostic is issued for \p DVI. bool diagnoseMisSizedDbgValue(Module &M, DbgValueInst *DVI) { @@ -384,17 +593,30 @@ /// legacy module pass manager. struct DebugifyModulePass : public ModulePass { bool runOnModule(Module &M) override { - return applyDebugifyMetadata(M, M.functions(), - "ModuleDebugify: ", /*ApplyToMF*/ nullptr); + if (Mode == DebugifyMode::SyntheticDebugInfo) + return applyDebugifyMetadata(M, M.functions(), + "ModuleDebugify: ", /*ApplyToMF*/ nullptr); + return collectDebugInfoMetadata( + M, M.functions(), *DIPreservationMap, + "ModuleDebugify (original debuginfo): ", NameOfWrappedPass); } - DebugifyModulePass() : ModulePass(ID) {} + DebugifyModulePass(enum DebugifyMode Mode = DebugifyMode::SyntheticDebugInfo, + StringRef NameOfWrappedPass = "", + DebugInfoPerPassMap *DIPreservationMap = nullptr) + : ModulePass(ID), NameOfWrappedPass(NameOfWrappedPass), + DIPreservationMap(DIPreservationMap), Mode(Mode) {} void getAnalysisUsage(AnalysisUsage &AU) const override { AU.setPreservesAll(); } static char ID; // Pass identification. + +private: + StringRef NameOfWrappedPass; + DebugInfoPerPassMap *DIPreservationMap; + enum DebugifyMode Mode; }; /// FunctionPass for attaching synthetic debug info to instructions within a @@ -403,31 +625,53 @@ bool runOnFunction(Function &F) override { Module &M = *F.getParent(); auto FuncIt = F.getIterator(); - return applyDebugifyMetadata(M, make_range(FuncIt, std::next(FuncIt)), - "FunctionDebugify: ", /*ApplyToMF*/ nullptr); + if (Mode == DebugifyMode::SyntheticDebugInfo) + return applyDebugifyMetadata(M, make_range(FuncIt, std::next(FuncIt)), + "FunctionDebugify: ", /*ApplyToMF*/ nullptr); + return collectDebugInfoMetadata( + M, M.functions(), *DIPreservationMap, + "FunctionDebugify (original debuginfo): ", NameOfWrappedPass); } - DebugifyFunctionPass() : FunctionPass(ID) {} + DebugifyFunctionPass( + enum DebugifyMode Mode = DebugifyMode::SyntheticDebugInfo, + StringRef NameOfWrappedPass = "", + DebugInfoPerPassMap *DIPreservationMap = nullptr) + : FunctionPass(ID), NameOfWrappedPass(NameOfWrappedPass), + DIPreservationMap(DIPreservationMap), Mode(Mode) {} void getAnalysisUsage(AnalysisUsage &AU) const override { AU.setPreservesAll(); } static char ID; // Pass identification. + +private: + StringRef NameOfWrappedPass; + DebugInfoPerPassMap *DIPreservationMap; + enum DebugifyMode Mode; }; /// ModulePass for checking debug info inserted by -debugify, used with the /// legacy module pass manager. struct CheckDebugifyModulePass : public ModulePass { bool runOnModule(Module &M) override { - return checkDebugifyMetadata(M, M.functions(), NameOfWrappedPass, - "CheckModuleDebugify", Strip, StatsMap); + if (Mode == DebugifyMode::SyntheticDebugInfo) + return checkDebugifyMetadata(M, M.functions(), NameOfWrappedPass, + "CheckModuleDebugify", Strip, StatsMap); + return checkDebugInfoMetadata( + M, M.functions(), *DIPreservationMap, + "CheckModuleDebugify (original debuginfo): ", NameOfWrappedPass); } - CheckDebugifyModulePass(bool Strip = false, StringRef NameOfWrappedPass = "", - DebugifyStatsMap *StatsMap = nullptr) - : ModulePass(ID), Strip(Strip), NameOfWrappedPass(NameOfWrappedPass), - StatsMap(StatsMap) {} + CheckDebugifyModulePass( + bool Strip = false, StringRef NameOfWrappedPass = "", + DebugifyStatsMap *StatsMap = nullptr, + enum DebugifyMode Mode = DebugifyMode::SyntheticDebugInfo, + DebugInfoPerPassMap *DIPreservationMap = nullptr) + : ModulePass(ID), NameOfWrappedPass(NameOfWrappedPass), + StatsMap(StatsMap), DIPreservationMap(DIPreservationMap), Mode(Mode), + Strip(Strip) {} void getAnalysisUsage(AnalysisUsage &AU) const override { AU.setPreservesAll(); @@ -436,9 +680,11 @@ static char ID; // Pass identification. private: - bool Strip; StringRef NameOfWrappedPass; DebugifyStatsMap *StatsMap; + DebugInfoPerPassMap *DIPreservationMap; + enum DebugifyMode Mode; + bool Strip; }; /// FunctionPass for checking debug info inserted by -debugify-function, used @@ -447,16 +693,23 @@ bool runOnFunction(Function &F) override { Module &M = *F.getParent(); auto FuncIt = F.getIterator(); - return checkDebugifyMetadata(M, make_range(FuncIt, std::next(FuncIt)), - NameOfWrappedPass, "CheckFunctionDebugify", - Strip, StatsMap); + if (Mode == DebugifyMode::SyntheticDebugInfo) + return checkDebugifyMetadata(M, make_range(FuncIt, std::next(FuncIt)), + NameOfWrappedPass, "CheckFunctionDebugify", + Strip, StatsMap); + return checkDebugInfoMetadata( + M, make_range(FuncIt, std::next(FuncIt)), *DIPreservationMap, + "CheckFunctionDebugify (original debuginfo): ", NameOfWrappedPass); } - CheckDebugifyFunctionPass(bool Strip = false, - StringRef NameOfWrappedPass = "", - DebugifyStatsMap *StatsMap = nullptr) - : FunctionPass(ID), Strip(Strip), NameOfWrappedPass(NameOfWrappedPass), - StatsMap(StatsMap) {} + CheckDebugifyFunctionPass( + bool Strip = false, StringRef NameOfWrappedPass = "", + DebugifyStatsMap *StatsMap = nullptr, + enum DebugifyMode Mode = DebugifyMode::SyntheticDebugInfo, + DebugInfoPerPassMap *DIPreservationMap = nullptr) + : FunctionPass(ID), NameOfWrappedPass(NameOfWrappedPass), + StatsMap(StatsMap), DIPreservationMap(DIPreservationMap), Mode(Mode), + Strip(Strip) {} void getAnalysisUsage(AnalysisUsage &AU) const override { AU.setPreservesAll(); @@ -465,17 +718,32 @@ static char ID; // Pass identification. private: - bool Strip; StringRef NameOfWrappedPass; DebugifyStatsMap *StatsMap; + DebugInfoPerPassMap *DIPreservationMap; + enum DebugifyMode Mode; + bool Strip; }; } // end anonymous namespace -ModulePass *createDebugifyModulePass() { return new DebugifyModulePass(); } +ModulePass *createDebugifyModulePass(enum DebugifyMode Mode, + llvm::StringRef NameOfWrappedPass, + DebugInfoPerPassMap *DIPreservationMap) { + if (Mode == DebugifyMode::SyntheticDebugInfo) + return new DebugifyModulePass(); + assert(Mode == DebugifyMode::OriginalDebugInfo && "Must be original mode"); + return new DebugifyModulePass(Mode, NameOfWrappedPass, DIPreservationMap); +} -FunctionPass *createDebugifyFunctionPass() { - return new DebugifyFunctionPass(); +FunctionPass * +createDebugifyFunctionPass(enum DebugifyMode Mode, + llvm::StringRef NameOfWrappedPass, + DebugInfoPerPassMap *DIPreservationMap) { + if (Mode == DebugifyMode::SyntheticDebugInfo) + return new DebugifyFunctionPass(); + assert(Mode == DebugifyMode::OriginalDebugInfo && "Must be original mode"); + return new DebugifyFunctionPass(Mode, NameOfWrappedPass, DIPreservationMap); } PreservedAnalyses NewPMDebugifyPass::run(Module &M, ModuleAnalysisManager &) { @@ -484,16 +752,24 @@ return PreservedAnalyses::all(); } -ModulePass *createCheckDebugifyModulePass(bool Strip, - StringRef NameOfWrappedPass, - DebugifyStatsMap *StatsMap) { - return new CheckDebugifyModulePass(Strip, NameOfWrappedPass, StatsMap); +ModulePass *createCheckDebugifyModulePass( + bool Strip, StringRef NameOfWrappedPass, DebugifyStatsMap *StatsMap, + enum DebugifyMode Mode, DebugInfoPerPassMap *DIPreservationMap) { + if (Mode == DebugifyMode::SyntheticDebugInfo) + return new CheckDebugifyModulePass(Strip, NameOfWrappedPass, StatsMap); + assert(Mode == DebugifyMode::OriginalDebugInfo && "Must be original mode"); + return new CheckDebugifyModulePass(false, NameOfWrappedPass, nullptr, Mode, + DIPreservationMap); } -FunctionPass *createCheckDebugifyFunctionPass(bool Strip, - StringRef NameOfWrappedPass, - DebugifyStatsMap *StatsMap) { - return new CheckDebugifyFunctionPass(Strip, NameOfWrappedPass, StatsMap); +FunctionPass *createCheckDebugifyFunctionPass( + bool Strip, StringRef NameOfWrappedPass, DebugifyStatsMap *StatsMap, + enum DebugifyMode Mode, DebugInfoPerPassMap *DIPreservationMap) { + if (Mode == DebugifyMode::SyntheticDebugInfo) + return new CheckDebugifyFunctionPass(Strip, NameOfWrappedPass, StatsMap); + assert(Mode == DebugifyMode::OriginalDebugInfo && "Must be original mode"); + return new CheckDebugifyFunctionPass(false, NameOfWrappedPass, nullptr, Mode, + DIPreservationMap); } PreservedAnalyses NewPMCheckDebugifyPass::run(Module &M, Index: llvm/test/DebugInfo/debugify-each-original-dbginfo.ll =================================================================== --- /dev/null +++ llvm/test/DebugInfo/debugify-each-original-dbginfo.ll @@ -0,0 +1,96 @@ +; Check the preservation of original debug info. + +; RUN: opt -O2 -verify-each-debuginfo-preserve -S -o - < %s 2>&1 | FileCheck %s + +; CHECK: WARNING: Combine redundant instructions {{.*}} +; CHECK-NEXT: Combine redundant instructions: FAIL + +; ModuleID = 'simple.c' +source_filename = "simple.c" +target datalayout = "e-m:e-p270:32:32-p271:32:32-p272:64:64-i64:64-f80:128-n8:16:32:64-S128" +target triple = "x86_64-unknown-linux-gnu" + +; Function Attrs: nounwind uwtable +define dso_local i32 @fn1(i32 %arg1) !dbg !12 { +entry: + %retval = alloca i32, align 4 + %arg1.addr = alloca i32, align 4 + store i32 %arg1, i32* %arg1.addr, align 4 + call void @llvm.dbg.declare(metadata i32* %arg1.addr, metadata !16, metadata !DIExpression()), !dbg !21 + %0 = load i32, i32* %arg1.addr, align 4, !dbg !22 + %rem = srem i32 %0, 2, !dbg !24 + %tobool = icmp ne i32 %rem, 0, !dbg !24 + br i1 %tobool, label %if.then, label %if.end, !dbg !25 + +if.then: ; preds = %entry + %1 = load i32, i32* %arg1.addr, align 4, !dbg !24 + %inc = add nsw i32 %1, 1, !dbg !24 + store i32 %inc, i32* %arg1.addr, align 4, !dbg !24 + store i32 %inc, i32* %retval, align 4, !dbg !24 + br label %return, !dbg !24 + +if.end: ; preds = %entry + %2 = load i32, i32* %arg1.addr, align 4, !dbg !25 + store i32 %2, i32* %retval, align 4, !dbg !25 + br label %return, !dbg !25 + +return: ; preds = %if.end, %if.then + %3 = load i32, i32* %retval, align 4, !dbg !25 + ret i32 %3, !dbg !25 +} + +; Function Attrs: nounwind readnone speculatable willreturn +declare void @llvm.dbg.declare(metadata, metadata, metadata) + +; Function Attrs: nounwind uwtable +define dso_local i32 @fn2() !dbg !31 { +entry: + %local1 = alloca i32, align 4 + %0 = bitcast i32* %local1 to i8*, !dbg !36 + call void @llvm.dbg.declare(metadata i32* %local1, metadata !35, metadata !DIExpression()), !dbg !36 + %call = call i32 (...) @fn3(), !dbg !36 + store i32 %call, i32* %local1, align 4, !dbg !36 + %1 = load i32, i32* %local1, align 4, !dbg !36 + %call1 = call i32 @fn1(i32 %1), !dbg !36 + %2 = load i32, i32* %local1, align 4 + %add = add nsw i32 %2, %call1 + store i32 %add, i32* %local1, align 4 + %3 = load i32, i32* %local1, align 4, !dbg !36 + %4 = bitcast i32* %local1 to i8*, !dbg !36 + ret i32 %3, !dbg !36 +} + +declare !dbg !4 dso_local i32 @fn3(...) + +!llvm.dbg.cu = !{!0} +!llvm.module.flags = !{!8, !9, !10} +!llvm.ident = !{!11} + +!0 = distinct !DICompileUnit(language: DW_LANG_C99, file: !1, producer: "clang version 11.0.0", isOptimized: true, runtimeVersion: 0, emissionKind: FullDebug, enums: !2, retainedTypes: !3, splitDebugInlining: false, nameTableKind: None) +!1 = !DIFile(filename: "simple.c", directory: "/dir") +!2 = !{} +!3 = !{!4} +!4 = !DISubprogram(name: "fn3", scope: !1, file: !1, line: 1, type: !5, spFlags: DISPFlagOptimized, retainedNodes: !2) +!5 = !DISubroutineType(types: !6) +!6 = !{!7, null} +!7 = !DIBasicType(name: "int", size: 32, encoding: DW_ATE_signed) +!8 = !{i32 7, !"Dwarf Version", i32 4} +!9 = !{i32 2, !"Debug Info Version", i32 3} +!10 = !{i32 1, !"wchar_size", i32 4} +!11 = !{!"clang version 11.0.0"} +!12 = distinct !DISubprogram(name: "fn1", scope: !1, file: !1, line: 3, type: !13, scopeLine: 3, flags: DIFlagPrototyped | DIFlagAllCallsDescribed, spFlags: DISPFlagDefinition | DISPFlagOptimized, unit: !0, retainedNodes: !15) +!13 = !DISubroutineType(types: !14) +!14 = !{!7, !7} +!15 = !{!16} +!16 = !DILocalVariable(name: "arg1", arg: 1, scope: !12, file: !1, line: 3, type: !7) +!21 = !DILocation(line: 3, column: 13, scope: !12) +!22 = !DILocation(line: 4, column: 7, scope: !23) +!23 = distinct !DILexicalBlock(scope: !12, file: !1, line: 4, column: 7) +!24 = !DILocation(line: 4, column: 12, scope: !23) +!25 = !DILocation(line: 4, column: 7, scope: !12) +!31 = distinct !DISubprogram(name: "fn2", scope: !1, file: !1, line: 10, type: !32, scopeLine: 10, flags: DIFlagAllCallsDescribed, spFlags: DISPFlagDefinition | DISPFlagOptimized, unit: !0, retainedNodes: !34) +!32 = !DISubroutineType(types: !33) +!33 = !{!7} +!34 = !{!35} +!35 = !DILocalVariable(name: "local1", scope: !31, file: !1, line: 11, type: !7) +!36 = !DILocation(line: 11, column: 3, scope: !31) Index: llvm/test/DebugInfo/debugify-original-dbginfo.ll =================================================================== --- /dev/null +++ llvm/test/DebugInfo/debugify-original-dbginfo.ll @@ -0,0 +1,73 @@ +; Check the preservation of original debug info. + +; RUN: opt -verify-debuginfo-preserve -instcombine -S -o - < %s 2>&1 | FileCheck %s + +; CHECK: CheckModuleDebugify (original debuginfo): : PASS + +; ModuleID = 'test' +source_filename = "test.c" +target datalayout = "e-m:e-p270:32:32-p271:32:32-p272:64:64-i64:64-f80:128-n8:16:32:64-S128" +target triple = "x86_64-unknown-linux-gnu" + +; Function Attrs: norecurse nounwind readnone uwtable +define dso_local i32 @fn1(i32 %0) local_unnamed_addr !dbg !12 { + call void @llvm.dbg.value(metadata i32 %0, metadata !16, metadata !DIExpression()), !dbg !17 + %2 = and i32 %0, 1, !dbg !18 + %.0 = add nsw i32 %2, %0, !dbg !20 + ret i32 %.0, !dbg !21 +} + +; Function Attrs: nounwind uwtable +define dso_local i32 @fn2() local_unnamed_addr !dbg !22 { + %1 = tail call i32 (...) @fn3(), !dbg !27 + call void @llvm.dbg.value(metadata i32 %1, metadata !26, metadata !DIExpression()), !dbg !28 + call void @llvm.dbg.value(metadata i32 %1, metadata !16, metadata !DIExpression()), !dbg !29 + %2 = and i32 %1, 1, !dbg !31 + %reass.add = shl i32 %1, 1 + %3 = or i32 %2, %reass.add + call void @llvm.dbg.value(metadata i32 %3, metadata !26, metadata !DIExpression()), !dbg !28 + ret i32 %3, !dbg !32 +} + +declare !dbg !4 dso_local i32 @fn3(...) local_unnamed_addr + +; Function Attrs: nounwind readnone speculatable willreturn +declare void @llvm.dbg.value(metadata, metadata, metadata) + +!llvm.dbg.cu = !{!0} +!llvm.module.flags = !{!8, !9, !10} +!llvm.ident = !{!11} + +!0 = distinct !DICompileUnit(language: DW_LANG_C99, file: !1, producer: "clang version 11.0.0", isOptimized: true, runtimeVersion: 0, emissionKind: FullDebug, enums: !2, retainedTypes: !3, splitDebugInlining: false, nameTableKind: None) +!1 = !DIFile(filename: "test.c", directory: "/") +!2 = !{} +!3 = !{!4} +!4 = !DISubprogram(name: "fn3", scope: !1, file: !1, line: 1, type: !5, spFlags: DISPFlagOptimized, retainedNodes: !2) +!5 = !DISubroutineType(types: !6) +!6 = !{!7, null} +!7 = !DIBasicType(name: "int", size: 32, encoding: DW_ATE_signed) +!8 = !{i32 7, !"Dwarf Version", i32 4} +!9 = !{i32 2, !"Debug Info Version", i32 3} +!10 = !{i32 1, !"wchar_size", i32 4} +!11 = !{!"clang version 11.0.0"} +!12 = distinct !DISubprogram(name: "fn1", scope: !1, file: !1, line: 2, type: !13, scopeLine: 2, flags: DIFlagPrototyped | DIFlagAllCallsDescribed, spFlags: DISPFlagDefinition | DISPFlagOptimized, unit: !0, retainedNodes: !15) +!13 = !DISubroutineType(types: !14) +!14 = !{!7, !7} +!15 = !{!16} +!16 = !DILocalVariable(name: "arg1", arg: 1, scope: !12, file: !1, line: 2, type: !7) +!17 = !DILocation(line: 0, scope: !12) +!18 = !DILocation(line: 3, column: 12, scope: !19) +!19 = distinct !DILexicalBlock(scope: !12, file: !1, line: 3, column: 7) +!20 = !DILocation(line: 3, column: 7, scope: !12) +!21 = !DILocation(line: 6, column: 1, scope: !12) +!22 = distinct !DISubprogram(name: "fn2", scope: !1, file: !1, line: 7, type: !23, scopeLine: 7, flags: DIFlagAllCallsDescribed, spFlags: DISPFlagDefinition | DISPFlagOptimized, unit: !0, retainedNodes: !25) +!23 = !DISubroutineType(types: !24) +!24 = !{!7} +!25 = !{!26} +!26 = !DILocalVariable(name: "local1", scope: !22, file: !1, line: 8, type: !7) +!27 = !DILocation(line: 8, column: 16, scope: !22) +!28 = !DILocation(line: 0, scope: !22) +!29 = !DILocation(line: 0, scope: !12, inlinedAt: !30) +!30 = distinct !DILocation(line: 9, column: 13, scope: !22) +!31 = !DILocation(line: 3, column: 12, scope: !19, inlinedAt: !30) +!32 = !DILocation(line: 10, column: 3, scope: !22) Index: llvm/test/DebugInfo/debugify-original-no-dbg-info.ll =================================================================== --- /dev/null +++ llvm/test/DebugInfo/debugify-original-no-dbg-info.ll @@ -0,0 +1,23 @@ +; RUN: opt -verify-debuginfo-preserve -instcombine -S -o - < %s 2>&1 | FileCheck %s + +; CHECK: ModuleDebugify (original debuginfo): Skipping module without debug info +; CHECK-NEXT: CheckModuleDebugify (original debuginfo): Skipping module without debug info + +; ModuleID = 'no-dbg-info.c' +source_filename = "no-dbg-info.c" +target datalayout = "e-m:e-p270:32:32-p271:32:32-p272:64:64-i64:64-f80:128-n8:16:32:64-S128" +target triple = "x86_64-unknown-linux-gnu" + +; Function Attrs: nounwind uwtable +define dso_local i32 @fn() { + %1 = call i32 (...) @fn2() + ret i32 %1 +} + +declare dso_local i32 @fn2(...) + +!llvm.module.flags = !{!0} +!llvm.ident = !{!1} + +!0 = !{i32 1, !"wchar_size", i32 4} +!1 = !{!"clang version 11.0.0"} Index: llvm/tools/opt/opt.cpp =================================================================== --- llvm/tools/opt/opt.cpp +++ llvm/tools/opt/opt.cpp @@ -209,11 +209,21 @@ cl::desc( "Start the pipeline with debugify and end it with check-debugify")); +static cl::opt VerifyDebugInfoPreserve( + "verify-debuginfo-preserve", + cl::desc("Start the pipeline with collecting and end it with checking of " + "debug info preservation.")); + static cl::opt DebugifyEach( "debugify-each", cl::desc( "Start each pass with debugify and end it with check-debugify")); +static cl::opt VerifyEachDebugInfoPreserve( + "verify-each-debuginfo-preserve", + cl::desc("Start each pass with collecting and end it with checking of " + "debug info preservation.")); + static cl::opt DebugifyExport("debugify-export", cl::desc("Export per-pass debugify statistics to this file"), @@ -801,10 +811,19 @@ // about to build. If the -debugify-each option is set, wrap each pass with // the (-check)-debugify passes. DebugifyCustomPassManager Passes; - if (DebugifyEach) - Passes.enableDebugifyEach(); + DebugifyStatsMap DIStatsMap; + DebugInfoPerPassMap DIPreservationMap; + if (DebugifyEach) { + Passes.setDebugifyMode(DebugifyMode::SyntheticDebugInfo); + Passes.setDIStatsMap(DIStatsMap); + } else if (VerifyEachDebugInfoPreserve) { + Passes.setDebugifyMode(DebugifyMode::OriginalDebugInfo); + Passes.setDIPreservationMap(DIPreservationMap); + } - bool AddOneTimeDebugifyPasses = EnableDebugify && !DebugifyEach; + bool AddOneTimeDebugifyPasses = + (EnableDebugify && !DebugifyEach) || + (VerifyDebugInfoPreserve && !VerifyEachDebugInfoPreserve); Passes.add(new TargetLibraryInfoWrapperPass(TLII)); @@ -812,8 +831,17 @@ Passes.add(createTargetTransformInfoWrapperPass(TM ? TM->getTargetIRAnalysis() : TargetIRAnalysis())); - if (AddOneTimeDebugifyPasses) - Passes.add(createDebugifyModulePass()); + if (AddOneTimeDebugifyPasses) { + if (EnableDebugify) { + Passes.setDIStatsMap(DIStatsMap); + Passes.add(createDebugifyModulePass()); + } else if (VerifyDebugInfoPreserve) { + Passes.setDIPreservationMap(DIPreservationMap); + Passes.add(createDebugifyModulePass( + DebugifyMode::OriginalDebugInfo, "", + &(Passes.getDebugInfoPerPassMap()))); + } + } std::unique_ptr FPasses; if (OptLevelO0 || OptLevelO1 || OptLevelO2 || OptLevelOs || OptLevelOz || @@ -957,8 +985,14 @@ if (!NoVerify && !VerifyEach) Passes.add(createVerifierPass()); - if (AddOneTimeDebugifyPasses) - Passes.add(createCheckDebugifyModulePass(false)); + if (AddOneTimeDebugifyPasses) { + if (EnableDebugify) + Passes.add(createCheckDebugifyModulePass(false)); + else if (VerifyDebugInfoPreserve) + Passes.add(createCheckDebugifyModulePass( + false, "", nullptr, DebugifyMode::OriginalDebugInfo, + &(Passes.getDebugInfoPerPassMap()))); + } // In run twice mode, we want to make sure the output is bit-by-bit // equivalent if we run the pass manager again, so setup two buffers and