diff --git a/llvm/docs/HowToUpdateDebugInfo.rst b/llvm/docs/HowToUpdateDebugInfo.rst --- a/llvm/docs/HowToUpdateDebugInfo.rst +++ b/llvm/docs/HowToUpdateDebugInfo.rst @@ -387,6 +387,9 @@ # Test each pass and export the issues report into the JSON file. $ clang -Xclang -fverify-debuginfo-preserve -Xclang -fverify-debuginfo-preserve-export=sample.json -g -O2 sample.c +Please do note that there are some known false positives, for source locations +and debug intrinsic checking, so that will be addressed as a future work. + 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 @@ -25,6 +25,7 @@ using DebugFnMap = llvm::DenseMap; using DebugInstMap = llvm::DenseMap; +using DebugVarMap = llvm::DenseMap; using WeakInstValueMap = llvm::DenseMap; @@ -37,6 +38,8 @@ // This tracks value (instruction) deletion. If an instruction gets deleted, // WeakVH nulls itself. WeakInstValueMap InstToDelete; + // Maps variable into dbg users (#dbg values/declares for this variable). + DebugVarMap DIVariables; }; /// Map pass names to a per-pass DebugInfoPerPass instance. 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 @@ -304,18 +304,39 @@ // Collect the DISubprogram. auto *SP = F.getSubprogram(); DIPreservationMap[NameOfWrappedPass].DIFunctions.insert({F.getName(), SP}); - if (SP) + if (SP) { LLVM_DEBUG(dbgs() << " Collecting subprogram: " << *SP << '\n'); + for (const DINode *DN : SP->getRetainedNodes()) { + if (const auto *DV = dyn_cast(DN)) { + DIPreservationMap[NameOfWrappedPass].DIVariables[DV] = 0; + } + } + } for (BasicBlock &BB : F) { - // Collect debug locations (!dbg). - // TODO: Collect dbg.values. + // Collect debug locations (!dbg) and debug variable intrinsics. for (Instruction &I : BB) { // Skip PHIs. if (isa(I)) continue; - // Skip debug instructions. + // Collect dbg.values and dbg.declares. + if (auto *DVI = dyn_cast(&I)) { + if (!SP) + continue; + // Skip inlined variables. + if (I.getDebugLoc().getInlinedAt()) + continue; + // Skip undef values. + if (DVI->isUndef()) + continue; + + auto *Var = DVI->getVariable(); + DIPreservationMap[NameOfWrappedPass].DIVariables[Var]++; + continue; + } + + // Skip debug instructions other than dbg.value and dbg.declare. if (isa(&I)) continue; @@ -435,6 +456,39 @@ return Preserved; } +// This checks the preservation of original debug variable intrinsics. +static bool checkVars(const DebugVarMap &DIFunctionsBefore, + const DebugVarMap &DIFunctionsAfter, + StringRef NameOfWrappedPass, StringRef FileNameFromCU, + bool ShouldWriteIntoJSON, llvm::json::Array &Bugs) { + bool Preserved = true; + for (const auto &V : DIFunctionsBefore) { + auto VarIt = DIFunctionsAfter.find(V.first); + if (VarIt == DIFunctionsAfter.end()) + continue; + + unsigned NumOfDbgValsAfter = VarIt->second; + + if (V.second > NumOfDbgValsAfter) { + if (ShouldWriteIntoJSON) + Bugs.push_back(llvm::json::Object( + {{"metadata", "dbg-var-intrinsic"}, + {"name", V.first->getName()}, + {"fn-name", V.first->getScope()->getSubprogram()->getName()}, + {"action", "drop"}})); + else + dbg() << "WARNING: " << NameOfWrappedPass + << " drops dbg.value()/dbg.declare() for " << V.first->getName() + << " from " + << "function " << V.first->getScope()->getSubprogram()->getName() + << " (file " << FileNameFromCU << ")\n"; + Preserved = false; + } + } + + return Preserved; +} + // Write the json data into the specifed file. static void writeJSON(StringRef OrigDIVerifyBugsReportFilePath, StringRef FileNameFromCU, StringRef NameOfWrappedPass, @@ -484,18 +538,40 @@ auto *SP = F.getSubprogram(); DIPreservationAfter[NameOfWrappedPass].DIFunctions.insert( {F.getName(), SP}); - if (SP) + + if (SP) { LLVM_DEBUG(dbgs() << " Collecting subprogram: " << *SP << '\n'); + for (const DINode *DN : SP->getRetainedNodes()) { + if (const auto *DV = dyn_cast(DN)) { + DIPreservationAfter[NameOfWrappedPass].DIVariables[DV] = 0; + } + } + } for (BasicBlock &BB : F) { - // Collect debug locations (!dbg attachments). - // TODO: Collect dbg.values. + // Collect debug locations (!dbg) and debug variable intrinsics. for (Instruction &I : BB) { // Skip PHIs. if (isa(I)) continue; - // Skip debug instructions. + // Collect dbg.values and dbg.declares. + if (auto *DVI = dyn_cast(&I)) { + if (!SP) + continue; + // Skip inlined variables. + if (I.getDebugLoc().getInlinedAt()) + continue; + // Skip undef values. + if (DVI->isUndef()) + continue; + + auto *Var = DVI->getVariable(); + DIPreservationAfter[NameOfWrappedPass].DIVariables[Var]++; + continue; + } + + // Skip debug instructions other than dbg.value and dbg.declare. if (isa(&I)) continue; @@ -522,6 +598,9 @@ auto InstToDelete = DIPreservationAfter[NameOfWrappedPass].InstToDelete; + auto DIVarsBefore = DIPreservationMap[NameOfWrappedPass].DIVariables; + auto DIVarsAfter = DIPreservationAfter[NameOfWrappedPass].DIVariables; + bool ShouldWriteIntoJSON = !OrigDIVerifyBugsReportFilePath.empty(); llvm::json::Array Bugs; @@ -531,7 +610,11 @@ bool ResultForInsts = checkInstructions( DILocsBefore, DILocsAfter, InstToDelete, NameOfWrappedPass, FileNameFromCU, ShouldWriteIntoJSON, Bugs); - bool Result = ResultForFunc && ResultForInsts; + + bool ResultForVars = checkVars(DIVarsBefore, DIVarsAfter, NameOfWrappedPass, + FileNameFromCU, ShouldWriteIntoJSON, Bugs); + + bool Result = ResultForFunc && ResultForInsts && ResultForVars; StringRef ResultBanner = NameOfWrappedPass != "" ? NameOfWrappedPass : Banner; if (ShouldWriteIntoJSON && !Bugs.empty()) diff --git a/llvm/test/tools/llvm-original-di-preservation/Inputs/expected-sample.html b/llvm/test/tools/llvm-original-di-preservation/Inputs/expected-sample.html --- a/llvm/test/tools/llvm-original-di-preservation/Inputs/expected-sample.html +++ b/llvm/test/tools/llvm-original-di-preservation/Inputs/expected-sample.html @@ -127,5 +127,32 @@ No bugs found +
+
+ + + + + + + + + + + + +
Variable Location Bugs found by the Debugify
FileLLVM Pass NameVariableFunctionAction
No bugs found
+
+ + + + + + + + + + +
Summary of Variable Location Bugs
LLVM Pass NameNumber of bugs
No bugs found
\ No newline at end of file diff --git a/llvm/unittests/Transforms/Utils/DebugifyTest.cpp b/llvm/unittests/Transforms/Utils/DebugifyTest.cpp --- a/llvm/unittests/Transforms/Utils/DebugifyTest.cpp +++ b/llvm/unittests/Transforms/Utils/DebugifyTest.cpp @@ -6,8 +6,10 @@ // //===----------------------------------------------------------------------===// +#include "llvm/ADT/SmallVector.h" #include "llvm/AsmParser/Parser.h" #include "llvm/IR/DebugInfoMetadata.h" +#include "llvm/IR/IntrinsicInst.h" #include "llvm/IR/LegacyPassManager.h" #include "llvm/Support/SourceMgr.h" #include "llvm/Transforms/Utils/Debugify.h" @@ -41,6 +43,7 @@ return false; } + void getAnalysisUsage(AnalysisUsage &AU) const override { AU.setPreservesCFG(); } @@ -48,6 +51,31 @@ DebugInfoDrop() : FunctionPass(ID) {} }; +struct DebugValueDrop : public FunctionPass { + static char ID; + bool runOnFunction(Function &F) override { + SmallVector Dbgs; + for (BasicBlock &BB : F) { + // Remove dbg var intrinsics. + for (Instruction &I : BB) { + if (auto *DVI = dyn_cast(&I)) + Dbgs.push_back(DVI); + } + } + + for (auto &I : Dbgs) + I->eraseFromParent(); + + return false; + } + + void getAnalysisUsage(AnalysisUsage &AU) const override { + AU.setPreservesCFG(); + } + + DebugValueDrop() : FunctionPass(ID) {} +}; + struct DebugInfoDummyAnalysis : public FunctionPass { static char ID; bool runOnFunction(Function &F) override { @@ -63,6 +91,7 @@ } char DebugInfoDrop::ID = 0; +char DebugValueDrop::ID = 0; char DebugInfoDummyAnalysis::ID = 0; TEST(DebugInfoDrop, DropOriginalDebugInfo) { @@ -116,6 +145,59 @@ EXPECT_TRUE(StdOut.find(FinalResult) != std::string::npos); } +TEST(DebugValueDrop, DropOriginalDebugValues) { + 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) + )"); + + DebugValueDrop *P = new DebugValueDrop(); + + 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 WarningForVars = "WARNING: drops dbg.value()/dbg.declare() for"; + 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(WarningForVars) != std::string::npos); + EXPECT_TRUE(StdOut.find(FinalResult) != std::string::npos); +} + TEST(DebugInfoDummyAnalysis, PreserveOriginalDebugInfo) { LLVMContext C; std::unique_ptr M = parseIR(C, R"( @@ -160,10 +242,12 @@ std::string ErrorForSP = "ERROR: dropped DISubprogram of"; std::string WarningForLoc = "WARNING: dropped DILocation of"; + std::string WarningForVars = "WARNING: drops dbg.value()/dbg.declare() for"; 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(WarningForVars) == std::string::npos); EXPECT_TRUE(StdOut.find(FinalResult) != std::string::npos); } diff --git a/llvm/utils/llvm-original-di-preservation.py b/llvm/utils/llvm-original-di-preservation.py --- a/llvm/utils/llvm-original-di-preservation.py +++ b/llvm/utils/llvm-original-di-preservation.py @@ -23,10 +23,16 @@ self.action = action self.fn_name = fn_name +class DIVarBug: + def __init__(self, action, name, fn_name): + self.action = action + self.name = name + self.fn_name = fn_name + # Report the bugs in form of html. -def generate_html_report(di_location_bugs, di_subprogram_bugs, \ +def generate_html_report(di_location_bugs, di_subprogram_bugs, di_var_bugs, \ di_location_bugs_summary, di_sp_bugs_summary, \ - html_file): + di_var_bugs_summary, html_file): fileout = open(html_file, "w") html_header = """ @@ -145,7 +151,7 @@ at_least_one_bug_found = False - # Handle loction bugs. + # Handle fn bugs. for file, per_file_bugs in di_subprogram_bugs.items(): for llvm_pass, per_pass_bugs in per_file_bugs.items(): # No SP bugs for the pass. @@ -212,6 +218,89 @@ """ table_di_sp_sum += "\n" + # Create the table for Variable bugs. + table_title_di_var = "Variable Location Bugs found by the Debugify" + table_di_var = """ + + + """.format(table_title_di_var) + + header_di_var = ["File", "LLVM Pass Name", "Variable", "Function", "Action"] + + for column in header_di_var: + table_di_var += " \n".format(column.strip()) + table_di_var += " \n" + + at_least_one_bug_found = False + + # Handle var bugs. + for file, per_file_bugs in di_var_bugs.items(): + for llvm_pass, per_pass_bugs in per_file_bugs.items(): + # No SP bugs for the pass. + if len(per_pass_bugs) == 0: + continue + at_least_one_bug_found = True + row = [] + table_di_var += " \n" + # Get the bugs info. + for x in per_pass_bugs: + row.append(" \n") + row.append(file) + row.append(llvm_pass) + row.append(x.name) + row.append(x.fn_name) + row.append(x.action) + row.append(" \n") + # Dump the bugs info into the table. + for column in row: + # The same file-pass pair can have multiple bugs. + if (column == " \n" or column == " \n"): + table_di_var += column + continue + table_di_var += " \n".format(column.strip()) + table_di_var += " \n" + + if not at_least_one_bug_found: + table_di_var += """ + + + """ + table_di_var += "
{}
{0}
{0}
No bugs found
\n" + + # Create the summary table for the sp bugs. + table_title_di_var_sum = "Summary of Variable Location Bugs" + table_di_var_sum = """ + + + """.format(table_title_di_var_sum) + + header_di_var_sum = ["LLVM Pass Name", "Number of bugs"] + + for column in header_di_var_sum: + table_di_var_sum += " \n".format(column.strip()) + table_di_var_sum += " \n" + + # Print the summary. + row = [] + for llvm_pass, num in sorted(di_var_bugs_summary.items()): + row.append(" \n") + row.append(llvm_pass) + row.append(str(num)) + row.append(" \n") + for column in row: + if (column == " \n" or column == " \n"): + table_di_var_sum += column + continue + table_di_var_sum += " \n".format(column.strip()) + table_di_var_sum += " \n" + + if not at_least_one_bug_found: + table_di_var_sum += """ + + + """ + table_di_var_sum += "
{}
{0}
{0}
No bugs found
\n" + # Finish the html page. html_footer = """ """ @@ -227,6 +316,11 @@ fileout.writelines(table_di_sp) fileout.writelines(new_line) fileout.writelines(table_di_sp_sum) + fileout.writelines(new_line) + fileout.writelines(new_line) + fileout.writelines(table_di_var) + fileout.writelines(new_line) + fileout.writelines(table_di_var_sum) fileout.writelines(html_footer) fileout.close() @@ -288,10 +382,12 @@ # Use the defaultdict in order to make multidim dicts. di_location_bugs = defaultdict(lambda: defaultdict(dict)) di_subprogram_bugs = defaultdict(lambda: defaultdict(dict)) + di_variable_bugs = defaultdict(lambda: defaultdict(dict)) # Use the ordered dict to make a summary. di_location_bugs_summary = OrderedDict() di_sp_bugs_summary = OrderedDict() + di_var_bugs_summary = OrderedDict() # Map the bugs into the file-pass pairs. for bugs_per_pass in debug_info_bugs: @@ -302,6 +398,8 @@ di_loc_bugs = [] di_sp_bugs = [] + di_var_bugs = [] + for bug in bugs: bugs_metadata = bug["metadata"] if bugs_metadata == "DILocation": @@ -326,16 +424,28 @@ di_sp_bugs_summary[bugs_pass] += 1 else: di_sp_bugs_summary[bugs_pass] = 1 + elif bugs_metadata == "dbg-var-intrinsic": + action = bug["action"] + fn_name = bug["fn-name"] + name = bug["name"] + di_var_bugs.append(DIVarBug(action, name, fn_name)) + + # Fill the summary dict. + if bugs_pass in di_var_bugs_summary: + di_var_bugs_summary[bugs_pass] += 1 + else: + di_var_bugs_summary[bugs_pass] = 1 else: - print ("error: Only DILocation and DISubprogram are supported.") + print ("error: Unsupported metadata.") sys.exit(1) di_location_bugs[bugs_file][bugs_pass] = di_loc_bugs di_subprogram_bugs[bugs_file][bugs_pass] = di_sp_bugs + di_variable_bugs[bugs_file][bugs_pass] = di_var_bugs - generate_html_report(di_location_bugs, di_subprogram_bugs, \ + generate_html_report(di_location_bugs, di_subprogram_bugs, di_variable_bugs, \ di_location_bugs_summary, di_sp_bugs_summary, \ - opts.html_file) + di_var_bugs_summary, opts.html_file) if __name__ == "__main__": Main()