diff --git a/llvm/docs/HowToUpdateDebugInfo.rst b/llvm/docs/HowToUpdateDebugInfo.rst --- a/llvm/docs/HowToUpdateDebugInfo.rst +++ b/llvm/docs/HowToUpdateDebugInfo.rst @@ -229,8 +229,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``. @@ -346,6 +346,21 @@ .. _MIRDebugify: +Test original debug info preservation in optimizations +------------------------------------------------------ + +In addition to automatically generating debug info, the checks provided by +the ``debugify`` utility pass can also be used to test the preservation of +pre-existing 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 ---------------------------------------------- diff --git a/llvm/include/llvm/Transforms/Utils/Debugify.h b/llvm/include/llvm/Transforms/Utils/Debugify.h --- a/llvm/include/llvm/Transforms/Utils/Debugify.h +++ b/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_TRANSFORMS_UTILS_DEBUGIFY_H #define LLVM_TRANSFORMS_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,14 +62,49 @@ /// Returns true if any change was made. bool stripDebugifyMetadata(Module &M); -llvm::ModulePass *createDebugifyModulePass(); -llvm::FunctionPass *createDebugifyFunctionPass(); +/// 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 + +/// 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; @@ -75,23 +132,26 @@ /// Map pass names to a per-pass DebugifyStatistics instance. using DebugifyStatsMap = llvm::MapVector; -void exportDebugifyStats(StringRef Path, const DebugifyStatsMap &Map); - -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 { llvm::PreservedAnalyses run(llvm::Module &M, llvm::ModuleAnalysisManager &AM); }; +namespace llvm { +void exportDebugifyStats(StringRef Path, const DebugifyStatsMap &Map); + struct DebugifyEachInstrumentation { DebugifyStatsMap StatsMap; @@ -103,8 +163,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; @@ -112,29 +173,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); @@ -142,9 +208,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 diff --git a/llvm/lib/Transforms/Utils/Debugify.cpp b/llvm/lib/Transforms/Utils/Debugify.cpp --- a/llvm/lib/Transforms/Utils/Debugify.cpp +++ b/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. /// //===----------------------------------------------------------------------===// @@ -24,6 +26,8 @@ #include "llvm/Pass.h" #include "llvm/Support/CommandLine.h" +#define DEBUG_TYPE "debugify" + using namespace llvm; namespace { @@ -35,6 +39,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"), @@ -199,16 +205,33 @@ return true; } -static bool applyDebugify(Function &F) { +static bool +applyDebugify(Function &F, + enum DebugifyMode Mode = DebugifyMode::SyntheticDebugInfo, + DebugInfoPerPassMap *DIPreservationMap = nullptr, + StringRef NameOfWrappedPass = "") { 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); + assert(DIPreservationMap); + return collectDebugInfoMetadata(M, M.functions(), *DIPreservationMap, + "FunctionDebugify (original debuginfo)", + NameOfWrappedPass); } -static bool applyDebugify(Module &M) { - return applyDebugifyMetadata(M, M.functions(), - "ModuleDebugify: ", /*ApplyToMF=*/nullptr); +static bool +applyDebugify(Module &M, + enum DebugifyMode Mode = DebugifyMode::SyntheticDebugInfo, + DebugInfoPerPassMap *DIPreservationMap = nullptr, + StringRef NameOfWrappedPass = "") { + if (Mode == DebugifyMode::SyntheticDebugInfo) + return applyDebugifyMetadata(M, M.functions(), + "ModuleDebugify: ", /*ApplyToMF*/ nullptr); + return collectDebugInfoMetadata(M, M.functions(), *DIPreservationMap, + "ModuleDebugify (original debuginfo)", + NameOfWrappedPass); } bool llvm::stripDebugifyMetadata(Module &M) { @@ -256,6 +279,211 @@ 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(); + DIPreservationAfter[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 ResultForFunc = checkFunctions(DIFunctionsBefore, DIFunctionsAfter, + NameOfWrappedPass, FileNameFromCU); + bool ResultForInsts = + checkInstructions(DILocsBefore, DILocsAfter, InstToDelete, + NameOfWrappedPass, FileNameFromCU); + bool Result = ResultForFunc && ResultForInsts; + + 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) { @@ -394,43 +622,74 @@ /// ModulePass for attaching synthetic debug info to everything, used with the /// legacy module pass manager. struct DebugifyModulePass : public ModulePass { - bool runOnModule(Module &M) override { return applyDebugify(M); } + bool runOnModule(Module &M) override { + return applyDebugify(M, Mode, DIPreservationMap, 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 /// single function, used with the legacy module pass manager. struct DebugifyFunctionPass : public FunctionPass { - bool runOnFunction(Function &F) override { return applyDebugify(F); } + bool runOnFunction(Function &F) override { + return applyDebugify(F, Mode, DIPreservationMap, 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(); @@ -439,9 +698,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 @@ -450,16 +711,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(); @@ -468,9 +736,11 @@ static char ID; // Pass identification. private: - bool Strip; StringRef NameOfWrappedPass; DebugifyStatsMap *StatsMap; + DebugInfoPerPassMap *DIPreservationMap; + enum DebugifyMode Mode; + bool Strip; }; } // end anonymous namespace @@ -496,12 +766,23 @@ } } -ModulePass *llvm::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 *llvm::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 &) { @@ -510,16 +791,24 @@ return PreservedAnalyses::all(); } -ModulePass *llvm::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 * -llvm::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, diff --git a/llvm/test/DebugInfo/debugify-original-no-dbg-info.ll b/llvm/test/DebugInfo/debugify-original-no-dbg-info.ll new file mode 100644 --- /dev/null +++ b/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"} diff --git a/llvm/tools/opt/opt.cpp b/llvm/tools/opt/opt.cpp --- a/llvm/tools/opt/opt.cpp +++ b/llvm/tools/opt/opt.cpp @@ -219,6 +219,16 @@ 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 VerifyEachDebugInfoPreserve( + "verify-each-debuginfo-preserve", + cl::desc("Start each pass with collecting and end it with checking of " + "debug info preservation.")); + static cl::opt PrintBreakpoints("print-breakpoints-for-testing", cl::desc("Print select breakpoints location for testing")); @@ -813,10 +823,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)); @@ -824,8 +843,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 || @@ -969,8 +997,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 diff --git a/llvm/unittests/Transforms/Utils/CMakeLists.txt b/llvm/unittests/Transforms/Utils/CMakeLists.txt --- a/llvm/unittests/Transforms/Utils/CMakeLists.txt +++ b/llvm/unittests/Transforms/Utils/CMakeLists.txt @@ -1,6 +1,7 @@ set(LLVM_LINK_COMPONENTS Analysis AsmParser + BitWriter Core Support TransformUtils @@ -13,6 +14,7 @@ CloningTest.cpp CodeExtractorTest.cpp CodeMoverUtilsTest.cpp + DebugifyTest.cpp FunctionComparatorTest.cpp IntegerDivisionTest.cpp LocalTest.cpp diff --git a/llvm/unittests/Transforms/Utils/DebugifyTest.cpp b/llvm/unittests/Transforms/Utils/DebugifyTest.cpp new file mode 100644 --- /dev/null +++ b/llvm/unittests/Transforms/Utils/DebugifyTest.cpp @@ -0,0 +1,180 @@ +//===- DebugifyTest.cpp - Debugify unit tests -----------------------------===// +// +// 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 +// +//===----------------------------------------------------------------------===// + +#include "llvm/AsmParser/Parser.h" +#include "llvm/IR/DebugInfoMetadata.h" +#include "llvm/IR/LegacyPassManager.h" +#include "llvm/Support/SourceMgr.h" +#include "llvm/Transforms/Utils/Debugify.h" +#include "gtest/gtest.h" + +using namespace llvm; + +static std::unique_ptr parseIR(LLVMContext &C, const char *IR) { + SMDiagnostic Err; + std::unique_ptr Mod = parseAssemblyString(IR, Err, C); + if (!Mod) + Err.print("DebugifyTest", errs()); + return Mod; +} + +namespace llvm { +void initializeDebugInfoDropPass(PassRegistry &); +void initializeDebugInfoDummyAnalysisPass(PassRegistry &); + +namespace { +struct DebugInfoDrop : public FunctionPass { + static char ID; + bool runOnFunction(Function &F) override { + // Drop DISubprogram. + F.setSubprogram(nullptr); + for (BasicBlock &BB : F) { + // Remove debug locations. + for (Instruction &I : BB) + I.setDebugLoc(DebugLoc()); + } + + return false; + } + void getAnalysisUsage(AnalysisUsage &AU) const override { + AU.setPreservesCFG(); + } + + DebugInfoDrop() : FunctionPass(ID) {} +}; + +struct DebugInfoDummyAnalysis : public FunctionPass { + static char ID; + bool runOnFunction(Function &F) override { + // Do nothing, so debug info stays untouched. + return false; + } + void getAnalysisUsage(AnalysisUsage &AU) const override { + AU.setPreservesAll(); + } + + DebugInfoDummyAnalysis() : FunctionPass(ID) {} +}; +} + +char DebugInfoDrop::ID = 0; +char DebugInfoDummyAnalysis::ID = 0; + +TEST(DebugInfoDrop, DropOriginalDebugInfo) { + LLVMContext C; + std::unique_ptr M = parseIR(C, R"( + define i16 @f(i16 %a) !dbg !6 { + %b = add i16 %a, 1, !dbg !11 + call void @llvm.dbg.value(metadata i16 %b, metadata !9, metadata !DIExpression()), !dbg !11 + ret i16 0, !dbg !11 + } + declare void @llvm.dbg.value(metadata, metadata, metadata) + + !llvm.dbg.cu = !{!0} + !llvm.module.flags = !{!5} + + !0 = distinct !DICompileUnit(language: DW_LANG_C, file: !1, producer: "debugify", isOptimized: true, runtimeVersion: 0, emissionKind: FullDebug, enums: !2) + !1 = !DIFile(filename: "t.ll", directory: "/") + !2 = !{} + !5 = !{i32 2, !"Debug Info Version", i32 3} + !6 = distinct !DISubprogram(name: "f", linkageName: "f", scope: null, file: !1, line: 1, type: !7, scopeLine: 1, spFlags: DISPFlagDefinition | DISPFlagOptimized, unit: !0, retainedNodes: !8) + !7 = !DISubroutineType(types: !2) + !8 = !{!9} + !9 = !DILocalVariable(name: "b", scope: !6, file: !1, line: 1, type: !10) + !10 = !DIBasicType(name: "ty16", size: 16, encoding: DW_ATE_unsigned) + !11 = !DILocation(line: 1, column: 1, scope: !6) + )"); + + DebugInfoDrop *P = new DebugInfoDrop(); + + DebugInfoPerPassMap DIPreservationMap; + DebugifyCustomPassManager Passes; + Passes.setDIPreservationMap(DIPreservationMap); + Passes.add(createDebugifyModulePass(DebugifyMode::OriginalDebugInfo, "", + &(Passes.getDebugInfoPerPassMap()))); + Passes.add(P); + Passes.add(createCheckDebugifyModulePass(false, "", nullptr, + DebugifyMode::OriginalDebugInfo, + &(Passes.getDebugInfoPerPassMap()))); + + testing::internal::CaptureStderr(); + Passes.run(*M); + + std::string StdOut = testing::internal::GetCapturedStderr(); + + std::string ErrorForSP = "ERROR: dropped DISubprogram of"; + std::string WarningForLoc = "WARNING: dropped DILocation of"; + std::string FinalResult = "CheckModuleDebugify (original debuginfo): FAIL"; + + EXPECT_TRUE(StdOut.find(ErrorForSP) != std::string::npos); + EXPECT_TRUE(StdOut.find(WarningForLoc) != std::string::npos); + EXPECT_TRUE(StdOut.find(FinalResult) != std::string::npos); +} + +TEST(DebugInfoDummyAnalysis, PreserveOriginalDebugInfo) { + LLVMContext C; + std::unique_ptr M = parseIR(C, R"( + define i32 @g(i32 %b) !dbg !6 { + %c = add i32 %b, 1, !dbg !11 + call void @llvm.dbg.value(metadata i32 %c, metadata !9, metadata !DIExpression()), !dbg !11 + ret i32 1, !dbg !11 + } + declare void @llvm.dbg.value(metadata, metadata, metadata) + + !llvm.dbg.cu = !{!0} + !llvm.module.flags = !{!5} + + !0 = distinct !DICompileUnit(language: DW_LANG_C, file: !1, producer: "debugify", isOptimized: true, runtimeVersion: 0, emissionKind: FullDebug, enums: !2) + !1 = !DIFile(filename: "test.ll", directory: "/") + !2 = !{} + !5 = !{i32 2, !"Debug Info Version", i32 3} + !6 = distinct !DISubprogram(name: "f", linkageName: "f", scope: null, file: !1, line: 1, type: !7, scopeLine: 1, spFlags: DISPFlagDefinition | DISPFlagOptimized, unit: !0, retainedNodes: !8) + !7 = !DISubroutineType(types: !2) + !8 = !{!9} + !9 = !DILocalVariable(name: "c", scope: !6, file: !1, line: 1, type: !10) + !10 = !DIBasicType(name: "ty32", size: 32, encoding: DW_ATE_unsigned) + !11 = !DILocation(line: 1, column: 1, scope: !6) + )"); + + DebugInfoDummyAnalysis *P = new DebugInfoDummyAnalysis(); + + DebugInfoPerPassMap DIPreservationMap; + DebugifyCustomPassManager Passes; + Passes.setDIPreservationMap(DIPreservationMap); + Passes.add(createDebugifyModulePass(DebugifyMode::OriginalDebugInfo, "", + &(Passes.getDebugInfoPerPassMap()))); + Passes.add(P); + Passes.add(createCheckDebugifyModulePass(false, "", nullptr, + DebugifyMode::OriginalDebugInfo, + &(Passes.getDebugInfoPerPassMap()))); + + testing::internal::CaptureStderr(); + Passes.run(*M); + + std::string StdOut = testing::internal::GetCapturedStderr(); + + std::string ErrorForSP = "ERROR: dropped DISubprogram of"; + std::string WarningForLoc = "WARNING: dropped DILocation of"; + std::string FinalResult = "CheckModuleDebugify (original debuginfo): PASS"; + + EXPECT_TRUE(StdOut.find(ErrorForSP) == std::string::npos); + EXPECT_TRUE(StdOut.find(WarningForLoc) == std::string::npos); + EXPECT_TRUE(StdOut.find(FinalResult) != std::string::npos); +} + +} // end namespace llvm + +INITIALIZE_PASS_BEGIN(DebugInfoDrop, "debuginfodroppass", "debuginfodroppass", + false, false) +INITIALIZE_PASS_END(DebugInfoDrop, "debuginfodroppass", "debuginfodroppass", false, + false) + +INITIALIZE_PASS_BEGIN(DebugInfoDummyAnalysis, "debuginfodummyanalysispass", + "debuginfodummyanalysispass", false, false) +INITIALIZE_PASS_END(DebugInfoDummyAnalysis, "debuginfodummyanalysispass", + "debuginfodummyanalysispass", false, false)