xref: /llvm-project/llvm/unittests/IR/DroppedVariableStatsIRTest.cpp (revision 12903fb3c73ad549c89585097f24d8b9952d849c)
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