1 //===- unittests/IR/DroppedVariableStatsIRTest.cpp ------------------------===// 2 // 3 // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. 4 // See https://llvm.org/LICENSE.txt for license information. 5 // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception 6 // 7 //===----------------------------------------------------------------------===// 8 9 #include "llvm/AsmParser/Parser.h" 10 #include "llvm/IR/Function.h" 11 #include "llvm/IR/InstIterator.h" 12 #include "llvm/IR/LegacyPassManager.h" 13 #include "llvm/IR/Module.h" 14 #include "llvm/Pass.h" 15 #include "llvm/PassRegistry.h" 16 #include "llvm/Passes/StandardInstrumentations.h" 17 #include "llvm/Support/SourceMgr.h" 18 #include "gtest/gtest.h" 19 #include <gtest/gtest.h> 20 #include <llvm/ADT/SmallString.h> 21 #include <llvm/IR/LLVMContext.h> 22 #include <llvm/IR/Module.h> 23 #include <llvm/IR/PassInstrumentation.h> 24 #include <llvm/IR/PassManager.h> 25 #include <llvm/IR/PassTimingInfo.h> 26 #include <llvm/Support/raw_ostream.h> 27 28 using namespace llvm; 29 namespace llvm { 30 void initializePassTest1Pass(PassRegistry &); 31 32 static std::unique_ptr<Module> parseIR(LLVMContext &C, const char *IR) { 33 SMDiagnostic Err; 34 std::unique_ptr<Module> Mod = parseAssemblyString(IR, Err, C); 35 if (!Mod) 36 Err.print("AbstractCallSiteTests", errs()); 37 return Mod; 38 } 39 } // namespace llvm 40 41 namespace { 42 43 // This test ensures that if a #dbg_value and an instruction that exists in the 44 // same scope as that #dbg_value are both deleted as a result of an optimization 45 // pass, debug information is considered not dropped. 46 TEST(DroppedVariableStatsIR, BothDeleted) { 47 PassInstrumentationCallbacks PIC; 48 PassInstrumentation PI(&PIC); 49 50 LLVMContext C; 51 52 const char *IR = 53 R"( 54 ; Function Attrs: mustprogress nounwind ssp uwtable(sync) 55 define noundef range(i32 -2147483647, -2147483648) i32 @_Z3fooi(i32 noundef %x) local_unnamed_addr #0 !dbg !9 { 56 entry: 57 #dbg_value(i32 %x, !15, !DIExpression(), !16) 58 %add = add nsw i32 %x, 1, !dbg !17 59 ret i32 0 60 } 61 !llvm.dbg.cu = !{!0} 62 !llvm.module.flags = !{!3} 63 !llvm.ident = !{!8} 64 !0 = distinct !DICompileUnit(language: DW_LANG_C_plus_plus_14, file: !1, producer: "clang", isOptimized: true, runtimeVersion: 0, emissionKind: FullDebug, splitDebugInlining: false, nameTableKind: Apple, sysroot: "/") 65 !1 = !DIFile(filename: "/tmp/code.cpp", directory: "/") 66 !3 = !{i32 2, !"Debug Info Version", i32 3} 67 !8 = !{!"clang"} 68 !9 = distinct !DISubprogram(name: "foo", linkageName: "_Z3fooi", scope: !10, file: !10, line: 1, type: !11, scopeLine: 1, unit: !0, retainedNodes: !14) 69 !10 = !DIFile(filename: "/tmp/code.cpp", directory: "") 70 !11 = !DISubroutineType(types: !12) 71 !12 = !{!13, !13} 72 !13 = !DIBasicType(name: "int", size: 32, encoding: DW_ATE_signed) 73 !14 = !{!15} 74 !15 = !DILocalVariable(name: "x", arg: 1, scope: !9, file: !10, line: 1, type: !13) 75 !16 = !DILocation(line: 0, scope: !9) 76 !17 = !DILocation(line: 2, column: 11, scope: !9))"; 77 78 std::unique_ptr<llvm::Module> M = parseIR(C, IR); 79 ASSERT_TRUE(M); 80 81 DroppedVariableStatsIR Stats(true); 82 Stats.runBeforePass("", llvm::Any(const_cast<const llvm::Module *>(M.get()))); 83 84 // This loop simulates an IR pass that drops debug information. 85 for (auto &F : *M) { 86 for (auto &I : instructions(&F)) { 87 I.dropDbgRecords(); 88 I.eraseFromParent(); 89 break; 90 } 91 break; 92 } 93 Stats.runAfterPass("Test", 94 llvm::Any(const_cast<const llvm::Module *>(M.get()))); 95 ASSERT_EQ(Stats.getPassDroppedVariables(), false); 96 } 97 98 // This test ensures that if a #dbg_value is dropped after an optimization pass, 99 // but an instruction that shares the same scope as the #dbg_value still exists, 100 // debug information is conisdered dropped. 101 TEST(DroppedVariableStatsIR, DbgValLost) { 102 PassInstrumentationCallbacks PIC; 103 PassInstrumentation PI(&PIC); 104 105 LLVMContext C; 106 107 const char *IR = 108 R"( 109 ; Function Attrs: mustprogress nounwind ssp uwtable(sync) 110 define noundef range(i32 -2147483647, -2147483648) i32 @_Z3fooi(i32 noundef %x) local_unnamed_addr #0 !dbg !9 { 111 entry: 112 #dbg_value(i32 %x, !15, !DIExpression(), !16) 113 %add = add nsw i32 %x, 1, !dbg !17 114 ret i32 0 115 } 116 !llvm.dbg.cu = !{!0} 117 !llvm.module.flags = !{!3} 118 !llvm.ident = !{!8} 119 !0 = distinct !DICompileUnit(language: DW_LANG_C_plus_plus_14, file: !1, producer: "clang", isOptimized: true, runtimeVersion: 0, emissionKind: FullDebug, splitDebugInlining: false, nameTableKind: Apple, sysroot: "/") 120 !1 = !DIFile(filename: "/tmp/code.cpp", directory: "/") 121 !3 = !{i32 2, !"Debug Info Version", i32 3} 122 !8 = !{!"clang"} 123 !9 = distinct !DISubprogram(name: "foo", linkageName: "_Z3fooi", scope: !10, file: !10, line: 1, type: !11, scopeLine: 1, unit: !0, retainedNodes: !14) 124 !10 = !DIFile(filename: "/tmp/code.cpp", directory: "") 125 !11 = !DISubroutineType(types: !12) 126 !12 = !{!13, !13} 127 !13 = !DIBasicType(name: "int", size: 32, encoding: DW_ATE_signed) 128 !14 = !{!15} 129 !15 = !DILocalVariable(name: "x", arg: 1, scope: !9, file: !10, line: 1, type: !13) 130 !16 = !DILocation(line: 0, scope: !9) 131 !17 = !DILocation(line: 2, column: 11, scope: !9))"; 132 133 std::unique_ptr<llvm::Module> M = parseIR(C, IR); 134 ASSERT_TRUE(M); 135 136 DroppedVariableStatsIR Stats(true); 137 Stats.runBeforePass("", llvm::Any(const_cast<const llvm::Module *>(M.get()))); 138 139 // This loop simulates an IR pass that drops debug information. 140 for (auto &F : *M) { 141 for (auto &I : instructions(&F)) { 142 I.dropDbgRecords(); 143 break; 144 } 145 break; 146 } 147 Stats.runAfterPass("Test", 148 llvm::Any(const_cast<const llvm::Module *>(M.get()))); 149 ASSERT_EQ(Stats.getPassDroppedVariables(), true); 150 } 151 152 // This test ensures that if a #dbg_value is dropped after an optimization pass, 153 // but an instruction that has an unrelated scope as the #dbg_value still 154 // exists, debug information is conisdered not dropped. 155 TEST(DroppedVariableStatsIR, UnrelatedScopes) { 156 PassInstrumentationCallbacks PIC; 157 PassInstrumentation PI(&PIC); 158 159 LLVMContext C; 160 161 const char *IR = 162 R"( 163 ; Function Attrs: mustprogress nounwind ssp uwtable(sync) 164 define noundef range(i32 -2147483647, -2147483648) i32 @_Z3fooi(i32 noundef %x) local_unnamed_addr #0 !dbg !9 { 165 entry: 166 #dbg_value(i32 %x, !15, !DIExpression(), !16) 167 %add = add nsw i32 %x, 1, !dbg !17 168 ret i32 0 169 } 170 !llvm.dbg.cu = !{!0} 171 !llvm.module.flags = !{!3} 172 !llvm.ident = !{!8} 173 !0 = distinct !DICompileUnit(language: DW_LANG_C_plus_plus_14, file: !1, producer: "clang", isOptimized: true, runtimeVersion: 0, emissionKind: FullDebug, splitDebugInlining: false, nameTableKind: Apple, sysroot: "/") 174 !1 = !DIFile(filename: "/tmp/code.cpp", directory: "/") 175 !3 = !{i32 2, !"Debug Info Version", i32 3} 176 !8 = !{!"clang"} 177 !9 = distinct !DISubprogram(name: "foo", linkageName: "_Z3fooi", scope: !10, file: !10, line: 1, type: !11, scopeLine: 1, unit: !0, retainedNodes: !14) 178 !10 = !DIFile(filename: "/tmp/code.cpp", directory: "") 179 !11 = !DISubroutineType(types: !12) 180 !12 = !{!13, !13} 181 !13 = !DIBasicType(name: "int", size: 32, encoding: DW_ATE_signed) 182 !14 = !{!15} 183 !15 = !DILocalVariable(name: "x", arg: 1, scope: !9, file: !10, line: 1, type: !13) 184 !16 = !DILocation(line: 0, scope: !9) 185 !17 = !DILocation(line: 2, column: 11, scope: !18) 186 !18 = distinct !DISubprogram(name: "bar", linkageName: "_Z3bari", scope: !10, file: !10, line: 11, type: !11, scopeLine: 1, unit: !0, retainedNodes: !14))"; 187 188 std::unique_ptr<llvm::Module> M = parseIR(C, IR); 189 ASSERT_TRUE(M); 190 191 DroppedVariableStatsIR Stats(true); 192 Stats.runBeforePass("", llvm::Any(const_cast<const llvm::Module *>(M.get()))); 193 194 // This loop simulates an IR pass that drops debug information. 195 for (auto &F : *M) { 196 for (auto &I : instructions(&F)) { 197 I.dropDbgRecords(); 198 break; 199 } 200 break; 201 } 202 Stats.runAfterPass("Test", 203 llvm::Any(const_cast<const llvm::Module *>(M.get()))); 204 ASSERT_EQ(Stats.getPassDroppedVariables(), false); 205 } 206 207 // This test ensures that if a #dbg_value is dropped after an optimization pass, 208 // but an instruction that has a scope which is a child of the #dbg_value scope 209 // still exists, debug information is conisdered dropped. 210 TEST(DroppedVariableStatsIR, ChildScopes) { 211 PassInstrumentationCallbacks PIC; 212 PassInstrumentation PI(&PIC); 213 214 LLVMContext C; 215 216 const char *IR = 217 R"( 218 ; Function Attrs: mustprogress nounwind ssp uwtable(sync) 219 define noundef range(i32 -2147483647, -2147483648) i32 @_Z3fooi(i32 noundef %x) local_unnamed_addr #0 !dbg !9 { 220 entry: 221 #dbg_value(i32 %x, !15, !DIExpression(), !16) 222 %add = add nsw i32 %x, 1, !dbg !17 223 ret i32 0 224 } 225 !llvm.dbg.cu = !{!0} 226 !llvm.module.flags = !{!3} 227 !llvm.ident = !{!8} 228 !0 = distinct !DICompileUnit(language: DW_LANG_C_plus_plus_14, file: !1, producer: "clang", isOptimized: true, runtimeVersion: 0, emissionKind: FullDebug, splitDebugInlining: false, nameTableKind: Apple, sysroot: "/") 229 !1 = !DIFile(filename: "/tmp/code.cpp", directory: "/") 230 !3 = !{i32 2, !"Debug Info Version", i32 3} 231 !8 = !{!"clang"} 232 !9 = distinct !DISubprogram(name: "foo", linkageName: "_Z3fooi", scope: !10, file: !10, line: 1, type: !11, scopeLine: 1, unit: !0, retainedNodes: !14) 233 !10 = !DIFile(filename: "/tmp/code.cpp", directory: "") 234 !11 = !DISubroutineType(types: !12) 235 !12 = !{!13, !13} 236 !13 = !DIBasicType(name: "int", size: 32, encoding: DW_ATE_signed) 237 !14 = !{!15} 238 !15 = !DILocalVariable(name: "x", arg: 1, scope: !9, file: !10, line: 1, type: !13) 239 !16 = !DILocation(line: 0, scope: !9) 240 !17 = !DILocation(line: 2, column: 11, scope: !18) 241 !18 = distinct !DILexicalBlock(scope: !9, file: !10, line: 10, column: 28))"; 242 243 std::unique_ptr<llvm::Module> M = parseIR(C, IR); 244 ASSERT_TRUE(M); 245 246 DroppedVariableStatsIR Stats(true); 247 Stats.runBeforePass("", llvm::Any(const_cast<const llvm::Module *>(M.get()))); 248 249 // This loop simulates an IR pass that drops debug information. 250 for (auto &F : *M) { 251 for (auto &I : instructions(&F)) { 252 I.dropDbgRecords(); 253 break; 254 } 255 break; 256 } 257 Stats.runAfterPass("Test", 258 llvm::Any(const_cast<const llvm::Module *>(M.get()))); 259 ASSERT_EQ(Stats.getPassDroppedVariables(), true); 260 } 261 262 // This test ensures that if a #dbg_value is dropped after an optimization pass, 263 // but an instruction that has a scope which is a child of the #dbg_value scope 264 // still exists, and the #dbg_value is inlined at another location, debug 265 // information is conisdered not dropped. 266 TEST(DroppedVariableStatsIR, InlinedAt) { 267 PassInstrumentationCallbacks PIC; 268 PassInstrumentation PI(&PIC); 269 270 LLVMContext C; 271 272 const char *IR = 273 R"(; Function Attrs: mustprogress nounwind ssp uwtable(sync) 274 define noundef range(i32 -2147483647, -2147483648) i32 @_Z3fooi(i32 noundef %x) local_unnamed_addr #0 !dbg !9 { 275 entry: 276 #dbg_value(i32 %x, !15, !DIExpression(), !16) 277 %add = add nsw i32 %x, 1, !dbg !17 278 ret i32 0 279 } 280 !llvm.dbg.cu = !{!0} 281 !llvm.module.flags = !{!3} 282 !llvm.ident = !{!8} 283 !0 = distinct !DICompileUnit(language: DW_LANG_C_plus_plus_14, file: !1, producer: "clang", isOptimized: true, runtimeVersion: 0, emissionKind: FullDebug, splitDebugInlining: false, nameTableKind: Apple, sysroot: "/") 284 !1 = !DIFile(filename: "/tmp/code.cpp", directory: "/") 285 !3 = !{i32 2, !"Debug Info Version", i32 3} 286 !8 = !{!"clang"} 287 !9 = distinct !DISubprogram(name: "foo", linkageName: "_Z3fooi", scope: !10, file: !10, line: 1, type: !11, scopeLine: 1, unit: !0, retainedNodes: !14) 288 !10 = !DIFile(filename: "/tmp/code.cpp", directory: "") 289 !11 = !DISubroutineType(types: !12) 290 !12 = !{!13, !13} 291 !13 = !DIBasicType(name: "int", size: 32, encoding: DW_ATE_signed) 292 !14 = !{!15} 293 !15 = !DILocalVariable(name: "x", arg: 1, scope: !9, file: !10, line: 1, type: !13) 294 !16 = !DILocation(line: 0, scope: !9, inlinedAt: !19) 295 !17 = !DILocation(line: 2, column: 11, scope: !18) 296 !18 = distinct !DILexicalBlock(scope: !9, file: !10, line: 10, column: 28) 297 !19 = !DILocation(line: 3, column: 2, scope: !9))"; 298 299 std::unique_ptr<llvm::Module> M = parseIR(C, IR); 300 ASSERT_TRUE(M); 301 302 DroppedVariableStatsIR Stats(true); 303 Stats.runBeforePass("", llvm::Any(const_cast<const llvm::Module *>(M.get()))); 304 305 // This loop simulates an IR pass that drops debug information. 306 for (auto &F : *M) { 307 for (auto &I : instructions(&F)) { 308 I.dropDbgRecords(); 309 break; 310 } 311 break; 312 } 313 Stats.runAfterPass("Test", 314 llvm::Any(const_cast<const llvm::Module *>(M.get()))); 315 ASSERT_EQ(Stats.getPassDroppedVariables(), false); 316 } 317 318 // This test ensures that if a #dbg_value is dropped after an optimization pass, 319 // but an instruction that has a scope which is a child of the #dbg_value scope 320 // still exists, and the #dbg_value and the instruction are inlined at another 321 // location, debug information is conisdered dropped. 322 TEST(DroppedVariableStatsIR, InlinedAtShared) { 323 PassInstrumentationCallbacks PIC; 324 PassInstrumentation PI(&PIC); 325 326 LLVMContext C; 327 328 const char *IR = 329 R"(; Function Attrs: mustprogress nounwind ssp uwtable(sync) 330 define noundef range(i32 -2147483647, -2147483648) i32 @_Z3fooi(i32 noundef %x) local_unnamed_addr #0 !dbg !9 { 331 entry: 332 #dbg_value(i32 %x, !15, !DIExpression(), !16) 333 %add = add nsw i32 %x, 1, !dbg !17 334 ret i32 0 335 } 336 !llvm.dbg.cu = !{!0} 337 !llvm.module.flags = !{!3} 338 !llvm.ident = !{!8} 339 !0 = distinct !DICompileUnit(language: DW_LANG_C_plus_plus_14, file: !1, producer: "clang", isOptimized: true, runtimeVersion: 0, emissionKind: FullDebug, splitDebugInlining: false, nameTableKind: Apple, sysroot: "/") 340 !1 = !DIFile(filename: "/tmp/code.cpp", directory: "/") 341 !3 = !{i32 2, !"Debug Info Version", i32 3} 342 !8 = !{!"clang"} 343 !9 = distinct !DISubprogram(name: "foo", linkageName: "_Z3fooi", scope: !10, file: !10, line: 1, type: !11, scopeLine: 1, unit: !0, retainedNodes: !14) 344 !10 = !DIFile(filename: "/tmp/code.cpp", directory: "") 345 !11 = !DISubroutineType(types: !12) 346 !12 = !{!13, !13} 347 !13 = !DIBasicType(name: "int", size: 32, encoding: DW_ATE_signed) 348 !14 = !{!15} 349 !15 = !DILocalVariable(name: "x", arg: 1, scope: !9, file: !10, line: 1, type: !13) 350 !16 = !DILocation(line: 0, scope: !9, inlinedAt: !19) 351 !17 = !DILocation(line: 2, column: 11, scope: !18, inlinedAt: !19) 352 !18 = distinct !DILexicalBlock(scope: !9, file: !10, line: 10, column: 28) 353 !19 = !DILocation(line: 3, column: 2, scope: !9))"; 354 355 std::unique_ptr<llvm::Module> M = parseIR(C, IR); 356 ASSERT_TRUE(M); 357 358 DroppedVariableStatsIR Stats(true); 359 Stats.runBeforePass("", llvm::Any(const_cast<const llvm::Module *>(M.get()))); 360 361 // This loop simulates an IR pass that drops debug information. 362 for (auto &F : *M) { 363 for (auto &I : instructions(&F)) { 364 I.dropDbgRecords(); 365 break; 366 } 367 break; 368 } 369 Stats.runAfterPass("Test", 370 llvm::Any(const_cast<const llvm::Module *>(M.get()))); 371 ASSERT_EQ(Stats.getPassDroppedVariables(), true); 372 } 373 374 // This test ensures that if a #dbg_value is dropped after an optimization pass, 375 // but an instruction that has a scope which is a child of the #dbg_value scope 376 // still exists, and the instruction is inlined at a location that is the 377 // #dbg_value's inlined at location, debug information is conisdered dropped. 378 TEST(DroppedVariableStatsIR, InlinedAtChild) { 379 PassInstrumentationCallbacks PIC; 380 PassInstrumentation PI(&PIC); 381 382 LLVMContext C; 383 384 const char *IR = 385 R"(; Function Attrs: mustprogress nounwind ssp uwtable(sync) 386 define noundef range(i32 -2147483647, -2147483648) i32 @_Z3fooi(i32 noundef %x) local_unnamed_addr #0 !dbg !9 { 387 entry: 388 #dbg_value(i32 %x, !15, !DIExpression(), !16) 389 %add = add nsw i32 %x, 1, !dbg !17 390 ret i32 0 391 } 392 !llvm.dbg.cu = !{!0} 393 !llvm.module.flags = !{!3} 394 !llvm.ident = !{!8} 395 !0 = distinct !DICompileUnit(language: DW_LANG_C_plus_plus_14, file: !1, producer: "clang", isOptimized: true, runtimeVersion: 0, emissionKind: FullDebug, splitDebugInlining: false, nameTableKind: Apple, sysroot: "/") 396 !1 = !DIFile(filename: "/tmp/code.cpp", directory: "/") 397 !3 = !{i32 2, !"Debug Info Version", i32 3} 398 !8 = !{!"clang"} 399 !9 = distinct !DISubprogram(name: "foo", linkageName: "_Z3fooi", scope: !10, file: !10, line: 1, type: !11, scopeLine: 1, unit: !0, retainedNodes: !14) 400 !10 = !DIFile(filename: "/tmp/code.cpp", directory: "") 401 !11 = !DISubroutineType(types: !12) 402 !12 = !{!13, !13} 403 !13 = !DIBasicType(name: "int", size: 32, encoding: DW_ATE_signed) 404 !14 = !{!15} 405 !15 = !DILocalVariable(name: "x", arg: 1, scope: !9, file: !10, line: 1, type: !13) 406 !16 = !DILocation(line: 0, scope: !9, inlinedAt: !19) 407 !17 = !DILocation(line: 2, column: 11, scope: !18, inlinedAt: !20) 408 !18 = distinct !DILexicalBlock(scope: !9, file: !10, line: 10, column: 28) 409 !19 = !DILocation(line: 3, column: 2, scope: !9); 410 !20 = !DILocation(line: 4, column: 5, scope: !18, inlinedAt: !19))"; 411 412 std::unique_ptr<llvm::Module> M = parseIR(C, IR); 413 ASSERT_TRUE(M); 414 415 DroppedVariableStatsIR Stats(true); 416 Stats.runBeforePass("", llvm::Any(const_cast<const llvm::Module *>(M.get()))); 417 418 // This loop simulates an IR pass that drops debug information. 419 for (auto &F : *M) { 420 for (auto &I : instructions(&F)) { 421 I.dropDbgRecords(); 422 break; 423 } 424 break; 425 } 426 Stats.runAfterPass("Test", 427 llvm::Any(const_cast<const llvm::Module *>(M.get()))); 428 ASSERT_EQ(Stats.getPassDroppedVariables(), true); 429 } 430 431 } // end anonymous namespace 432