| //===- llvm/unittest/IR/BasicBlockTest.cpp - BasicBlock 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/IR/BasicBlock.h" |
| #include "llvm/IR/DebugInfo.h" |
| #include "llvm/ADT/STLExtras.h" |
| #include "llvm/AsmParser/Parser.h" |
| #include "llvm/IR/Function.h" |
| #include "llvm/IR/IRBuilder.h" |
| #include "llvm/IR/Instruction.h" |
| #include "llvm/IR/Instructions.h" |
| #include "llvm/IR/LLVMContext.h" |
| #include "llvm/IR/Module.h" |
| #include "llvm/IR/NoFolder.h" |
| #include "llvm/IR/Verifier.h" |
| #include "llvm/Support/SourceMgr.h" |
| #include "gmock/gmock-matchers.h" |
| #include "gtest/gtest.h" |
| #include <memory> |
| |
| using namespace llvm; |
| |
| extern cl::opt<bool> UseNewDbgInfoFormat; |
| |
| static std::unique_ptr<Module> parseIR(LLVMContext &C, const char *IR) { |
| SMDiagnostic Err; |
| std::unique_ptr<Module> Mod = parseAssemblyString(IR, Err, C); |
| if (!Mod) |
| Err.print("BasicBlockDbgInfoTest", errs()); |
| return Mod; |
| } |
| |
| namespace { |
| |
| // We can occasionally moveAfter an instruction so that it moves to the |
| // position that it already resides at. This is fine -- but gets complicated |
| // with dbg.value intrinsics. By moving an instruction, we can end up changing |
| // nothing but the location of debug-info intrinsics. That has to be modelled |
| // by DbgVariableRecords, the dbg.value replacement. |
| TEST(BasicBlockDbgInfoTest, InsertAfterSelf) { |
| LLVMContext C; |
| UseNewDbgInfoFormat = true; |
| |
| std::unique_ptr<Module> M = parseIR(C, R"( |
| define i16 @f(i16 %a) !dbg !6 { |
| call void @llvm.dbg.value(metadata i16 %a, metadata !9, metadata !DIExpression()), !dbg !11 |
| %b = add i16 %a, 1, !dbg !11 |
| call void @llvm.dbg.value(metadata i16 %b, metadata !9, metadata !DIExpression()), !dbg !11 |
| %c = add i16 %b, 1, !dbg !11 |
| ret i16 0, !dbg !11 |
| } |
| declare void @llvm.dbg.value(metadata, metadata, metadata) #0 |
| attributes #0 = { nounwind readnone speculatable willreturn } |
| |
| !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: "foo", linkageName: "foo", 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: "1", 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) |
| )"); |
| |
| // Convert the module to "new" form debug-info. |
| M->convertToNewDbgValues(); |
| // Fetch the entry block. |
| BasicBlock &BB = M->getFunction("f")->getEntryBlock(); |
| |
| Instruction *Inst1 = &*BB.begin(); |
| Instruction *Inst2 = &*std::next(BB.begin()); |
| Instruction *RetInst = &*std::next(Inst2->getIterator()); |
| EXPECT_TRUE(Inst1->hasDbgRecords()); |
| EXPECT_TRUE(Inst2->hasDbgRecords()); |
| EXPECT_FALSE(RetInst->hasDbgRecords()); |
| |
| // If we move Inst2 to be after Inst1, then it comes _immediately_ after. Were |
| // we in dbg.value form we would then have: |
| // dbg.value |
| // %b = add |
| // %c = add |
| // dbg.value |
| // Check that this is replicated by DbgVariableRecords. |
| Inst2->moveAfter(Inst1); |
| |
| // Inst1 should only have one DbgVariableRecord on it. |
| EXPECT_TRUE(Inst1->hasDbgRecords()); |
| auto Range1 = Inst1->getDbgRecordRange(); |
| EXPECT_EQ(std::distance(Range1.begin(), Range1.end()), 1u); |
| // Inst2 should have none. |
| EXPECT_FALSE(Inst2->hasDbgRecords()); |
| // While the return inst should now have one on it. |
| EXPECT_TRUE(RetInst->hasDbgRecords()); |
| auto Range2 = RetInst->getDbgRecordRange(); |
| EXPECT_EQ(std::distance(Range2.begin(), Range2.end()), 1u); |
| |
| M->convertFromNewDbgValues(); |
| |
| UseNewDbgInfoFormat = false; |
| } |
| |
| TEST(BasicBlockDbgInfoTest, MarkerOperations) { |
| LLVMContext C; |
| UseNewDbgInfoFormat = true; |
| |
| std::unique_ptr<Module> M = parseIR(C, R"( |
| define i16 @f(i16 %a) !dbg !6 { |
| call void @llvm.dbg.value(metadata i16 %a, metadata !9, metadata !DIExpression()), !dbg !11 |
| %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) #0 |
| attributes #0 = { nounwind readnone speculatable willreturn } |
| |
| !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: "foo", linkageName: "foo", 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: "1", 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) |
| )"); |
| |
| // Fetch the entry block, |
| BasicBlock &BB = M->getFunction("f")->getEntryBlock(); |
| // Convert the module to "new" form debug-info. |
| M->convertToNewDbgValues(); |
| EXPECT_EQ(BB.size(), 2u); |
| |
| // Fetch out our two markers, |
| Instruction *Instr1 = &*BB.begin(); |
| Instruction *Instr2 = Instr1->getNextNode(); |
| DbgMarker *Marker1 = Instr1->DebugMarker; |
| DbgMarker *Marker2 = Instr2->DebugMarker; |
| // There's no TrailingDbgRecords marker allocated yet. |
| DbgMarker *EndMarker = nullptr; |
| |
| // Check that the "getMarker" utilities operate as expected. |
| EXPECT_EQ(BB.getMarker(Instr1->getIterator()), Marker1); |
| EXPECT_EQ(BB.getMarker(Instr2->getIterator()), Marker2); |
| EXPECT_EQ(BB.getNextMarker(Instr1), Marker2); |
| EXPECT_EQ(BB.getNextMarker(Instr2), EndMarker); // Is nullptr. |
| |
| // There should be two DbgVariableRecords, |
| EXPECT_EQ(Marker1->StoredDbgRecords.size(), 1u); |
| EXPECT_EQ(Marker2->StoredDbgRecords.size(), 1u); |
| |
| // Unlink them and try to re-insert them through the basic block. |
| DbgRecord *DVR1 = &*Marker1->StoredDbgRecords.begin(); |
| DbgRecord *DVR2 = &*Marker2->StoredDbgRecords.begin(); |
| DVR1->removeFromParent(); |
| DVR2->removeFromParent(); |
| EXPECT_TRUE(Marker1->StoredDbgRecords.empty()); |
| EXPECT_TRUE(Marker2->StoredDbgRecords.empty()); |
| |
| // This should appear in Marker1. |
| BB.insertDbgRecordBefore(DVR1, BB.begin()); |
| EXPECT_EQ(Marker1->StoredDbgRecords.size(), 1u); |
| EXPECT_EQ(DVR1, &*Marker1->StoredDbgRecords.begin()); |
| |
| // This should attach to Marker2. |
| BB.insertDbgRecordAfter(DVR2, &*BB.begin()); |
| EXPECT_EQ(Marker2->StoredDbgRecords.size(), 1u); |
| EXPECT_EQ(DVR2, &*Marker2->StoredDbgRecords.begin()); |
| |
| // Now, how about removing instructions? That should cause any |
| // DbgVariableRecords to "fall down". |
| Instr1->removeFromParent(); |
| Marker1 = nullptr; |
| // DbgVariableRecords should now be in Marker2. |
| EXPECT_EQ(BB.size(), 1u); |
| EXPECT_EQ(Marker2->StoredDbgRecords.size(), 2u); |
| // They should also be in the correct order. |
| SmallVector<DbgRecord *, 2> DVRs; |
| for (DbgRecord &DVR : Marker2->getDbgRecordRange()) |
| DVRs.push_back(&DVR); |
| EXPECT_EQ(DVRs[0], DVR1); |
| EXPECT_EQ(DVRs[1], DVR2); |
| |
| // If we remove the end instruction, the DbgVariableRecords should fall down |
| // into the trailing marker. |
| EXPECT_EQ(BB.getTrailingDbgRecords(), nullptr); |
| Instr2->removeFromParent(); |
| EXPECT_TRUE(BB.empty()); |
| EndMarker = BB.getTrailingDbgRecords(); |
| ASSERT_NE(EndMarker, nullptr); |
| EXPECT_EQ(EndMarker->StoredDbgRecords.size(), 2u); |
| // Again, these should arrive in the correct order. |
| |
| DVRs.clear(); |
| for (DbgRecord &DVR : EndMarker->getDbgRecordRange()) |
| DVRs.push_back(&DVR); |
| EXPECT_EQ(DVRs[0], DVR1); |
| EXPECT_EQ(DVRs[1], DVR2); |
| |
| // Inserting a normal instruction at the beginning: shouldn't dislodge the |
| // DbgVariableRecords. It's intended to not go at the start. |
| Instr1->insertBefore(BB, BB.begin()); |
| EXPECT_EQ(EndMarker->StoredDbgRecords.size(), 2u); |
| Instr1->removeFromParent(); |
| |
| // Inserting at end(): should dislodge the DbgVariableRecords, if they were |
| // dbg.values then they would sit "above" the new instruction. |
| Instr1->insertBefore(BB, BB.end()); |
| EXPECT_EQ(Instr1->DebugMarker->StoredDbgRecords.size(), 2u); |
| // We should de-allocate the trailing marker when something is inserted |
| // at end(). |
| EXPECT_EQ(BB.getTrailingDbgRecords(), nullptr); |
| |
| // Remove Instr1: now the DbgVariableRecords will fall down again, |
| Instr1->removeFromParent(); |
| EndMarker = BB.getTrailingDbgRecords(); |
| EXPECT_EQ(EndMarker->StoredDbgRecords.size(), 2u); |
| |
| // Inserting a terminator, however it's intended, should dislodge the |
| // trailing DbgVariableRecords, as it's the clear intention of the caller that |
| // this be the final instr in the block, and DbgVariableRecords aren't allowed |
| // to live off the end forever. |
| Instr2->insertBefore(BB, BB.begin()); |
| EXPECT_EQ(Instr2->DebugMarker->StoredDbgRecords.size(), 2u); |
| EXPECT_EQ(BB.getTrailingDbgRecords(), nullptr); |
| |
| // Teardown, |
| Instr1->insertBefore(BB, BB.begin()); |
| |
| UseNewDbgInfoFormat = false; |
| } |
| |
| TEST(BasicBlockDbgInfoTest, HeadBitOperations) { |
| LLVMContext C; |
| UseNewDbgInfoFormat = true; |
| |
| std::unique_ptr<Module> 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 %a, metadata !9, metadata !DIExpression()), !dbg !11 |
| %c = add i16 %a, 1, !dbg !11 |
| %d = add i16 %a, 1, !dbg !11 |
| ret i16 0, !dbg !11 |
| } |
| declare void @llvm.dbg.value(metadata, metadata, metadata) #0 |
| attributes #0 = { nounwind readnone speculatable willreturn } |
| |
| !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: "foo", linkageName: "foo", 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: "1", 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) |
| )"); |
| |
| // Test that the movement of debug-data when using moveBefore etc and |
| // insertBefore etc are governed by the "head" bit of iterators. |
| BasicBlock &BB = M->getFunction("f")->getEntryBlock(); |
| // Convert the module to "new" form debug-info. |
| M->convertToNewDbgValues(); |
| |
| // Test that the head bit behaves as expected: it should be set when the |
| // code wants the _start_ of the block, but not otherwise. |
| EXPECT_TRUE(BB.getFirstInsertionPt().getHeadBit()); |
| BasicBlock::iterator BeginIt = BB.begin(); |
| EXPECT_TRUE(BeginIt.getHeadBit()); |
| // If you launder the instruction pointer through dereferencing and then |
| // get the iterator again with getIterator, the head bit is lost. This is |
| // deliberate: if you're calling getIterator, then you're requesting an |
| // iterator for the position of _this_ instruction, not "the start of this |
| // block". |
| BasicBlock::iterator BeginIt2 = BeginIt->getIterator(); |
| EXPECT_FALSE(BeginIt2.getHeadBit()); |
| |
| // Fetch some instruction pointers. |
| Instruction *BInst = &*BeginIt; |
| Instruction *CInst = BInst->getNextNode(); |
| Instruction *DInst = CInst->getNextNode(); |
| // CInst should have debug-info. |
| ASSERT_TRUE(CInst->DebugMarker); |
| EXPECT_FALSE(CInst->DebugMarker->StoredDbgRecords.empty()); |
| |
| // If we move "c" to the start of the block, just normally, then the |
| // DbgVariableRecords should fall down to "d". |
| CInst->moveBefore(BB, BeginIt2); |
| EXPECT_TRUE(!CInst->DebugMarker || |
| CInst->DebugMarker->StoredDbgRecords.empty()); |
| ASSERT_TRUE(DInst->DebugMarker); |
| EXPECT_FALSE(DInst->DebugMarker->StoredDbgRecords.empty()); |
| |
| // Wheras if we move D to the start of the block with moveBeforePreserving, |
| // the DbgVariableRecords should move with it. |
| DInst->moveBeforePreserving(BB, BB.begin()); |
| EXPECT_FALSE(DInst->DebugMarker->StoredDbgRecords.empty()); |
| EXPECT_EQ(&*BB.begin(), DInst); |
| |
| // Similarly, moveAfterPreserving "D" to "C" should move DbgVariableRecords |
| // with "D". |
| DInst->moveAfterPreserving(CInst); |
| EXPECT_FALSE(DInst->DebugMarker->StoredDbgRecords.empty()); |
| |
| // (move back to the start...) |
| DInst->moveBeforePreserving(BB, BB.begin()); |
| |
| // Current order of insts: "D -> C -> B -> Ret". DbgVariableRecords on "D". |
| // If we move "C" to the beginning of the block, it should go before the |
| // DbgVariableRecords. They'll stay on "D". |
| CInst->moveBefore(BB, BB.begin()); |
| EXPECT_TRUE(!CInst->DebugMarker || |
| CInst->DebugMarker->StoredDbgRecords.empty()); |
| EXPECT_FALSE(DInst->DebugMarker->StoredDbgRecords.empty()); |
| EXPECT_EQ(&*BB.begin(), CInst); |
| EXPECT_EQ(CInst->getNextNode(), DInst); |
| |
| // Move back. |
| CInst->moveBefore(BInst); |
| EXPECT_EQ(&*BB.begin(), DInst); |
| |
| // Current order of insts: "D -> C -> B -> Ret". DbgVariableRecords on "D". |
| // Now move CInst to the position of DInst, but using getIterator instead of |
| // BasicBlock::begin. This signals that we want the "C" instruction to be |
| // immediately before "D", with any DbgVariableRecords on "D" now moving to |
| // "C". It's the equivalent of moving an instruction to the position between a |
| // run of dbg.values and the next instruction. |
| CInst->moveBefore(BB, DInst->getIterator()); |
| // CInst gains the DbgVariableRecords. |
| EXPECT_TRUE(!DInst->DebugMarker || |
| DInst->DebugMarker->StoredDbgRecords.empty()); |
| EXPECT_FALSE(CInst->DebugMarker->StoredDbgRecords.empty()); |
| EXPECT_EQ(&*BB.begin(), CInst); |
| |
| UseNewDbgInfoFormat = false; |
| } |
| |
| TEST(BasicBlockDbgInfoTest, InstrDbgAccess) { |
| LLVMContext C; |
| UseNewDbgInfoFormat = true; |
| |
| std::unique_ptr<Module> 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 %a, metadata !9, metadata !DIExpression()), !dbg !11 |
| %c = add i16 %a, 1, !dbg !11 |
| %d = add i16 %a, 1, !dbg !11 |
| ret i16 0, !dbg !11 |
| } |
| declare void @llvm.dbg.value(metadata, metadata, metadata) #0 |
| attributes #0 = { nounwind readnone speculatable willreturn } |
| |
| !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: "foo", linkageName: "foo", 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: "1", 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) |
| )"); |
| |
| // Check that DbgVariableRecords can be accessed from Instructions without |
| // digging into the depths of DbgMarkers. |
| BasicBlock &BB = M->getFunction("f")->getEntryBlock(); |
| // Convert the module to "new" form debug-info. |
| M->convertToNewDbgValues(); |
| |
| Instruction *BInst = &*BB.begin(); |
| Instruction *CInst = BInst->getNextNode(); |
| Instruction *DInst = CInst->getNextNode(); |
| |
| ASSERT_FALSE(BInst->DebugMarker); |
| ASSERT_TRUE(CInst->DebugMarker); |
| ASSERT_EQ(CInst->DebugMarker->StoredDbgRecords.size(), 1u); |
| DbgRecord *DVR1 = &*CInst->DebugMarker->StoredDbgRecords.begin(); |
| ASSERT_TRUE(DVR1); |
| EXPECT_FALSE(BInst->hasDbgRecords()); |
| |
| // Clone DbgVariableRecords from one inst to another. Other arguments to clone |
| // are tested in DbgMarker test. |
| auto Range1 = BInst->cloneDebugInfoFrom(CInst); |
| EXPECT_EQ(BInst->DebugMarker->StoredDbgRecords.size(), 1u); |
| DbgRecord *DVR2 = &*BInst->DebugMarker->StoredDbgRecords.begin(); |
| EXPECT_EQ(std::distance(Range1.begin(), Range1.end()), 1u); |
| EXPECT_EQ(&*Range1.begin(), DVR2); |
| EXPECT_NE(DVR1, DVR2); |
| |
| // We should be able to get a range over exactly the same information. |
| auto Range2 = BInst->getDbgRecordRange(); |
| EXPECT_EQ(Range1.begin(), Range2.begin()); |
| EXPECT_EQ(Range1.end(), Range2.end()); |
| |
| // We should be able to query if there are DbgVariableRecords, |
| EXPECT_TRUE(BInst->hasDbgRecords()); |
| EXPECT_TRUE(CInst->hasDbgRecords()); |
| EXPECT_FALSE(DInst->hasDbgRecords()); |
| |
| // Dropping should be easy, |
| BInst->dropDbgRecords(); |
| EXPECT_FALSE(BInst->hasDbgRecords()); |
| EXPECT_EQ(BInst->DebugMarker->StoredDbgRecords.size(), 0u); |
| |
| // And we should be able to drop individual DbgVariableRecords. |
| CInst->dropOneDbgRecord(DVR1); |
| EXPECT_FALSE(CInst->hasDbgRecords()); |
| EXPECT_EQ(CInst->DebugMarker->StoredDbgRecords.size(), 0u); |
| |
| UseNewDbgInfoFormat = false; |
| } |
| |
| /* Let's recall the big illustration from BasicBlock::spliceDebugInfo: |
| |
| Dest |
| | |
| this-block: A----A----A ====A----A----A----A---A---A |
| Src-block ++++B---B---B---B:::C |
| | | |
| First Last |
| |
| in all it's glory. Depending on the bit-configurations for the iterator head |
| / tail bits on the three named iterators, there are eight ways for a splice to |
| occur. To save the amount of thinking needed to pack this into one unit test, |
| just test the same IR eight times with difference splices. The IR shall be |
| thus: |
| |
| define i16 @f(i16 %a) !dbg !6 { |
| entry: |
| call void @llvm.dbg.value(metadata i16 %a, metadata !9, metadata !DIExpression()), !dbg !11 |
| %b = add i16 %a, 1, !dbg !11 |
| call void @llvm.dbg.value(metadata i16 %b, metadata !9, metadata !DIExpression()), !dbg !11 |
| br label %exit, !dbg !11 |
| |
| exit: |
| call void @llvm.dbg.value(metadata i16 0, metadata !9, metadata !DIExpression()), !dbg !11 |
| %c = add i16 %b, 1, !dbg !11 |
| ret i16 0, !dbg !11 |
| } |
| |
| The iterators will be: |
| Dest: exit block, "c" instruction. |
| First: entry block, "b" instruction. |
| Last: entry block, branch instruction. |
| |
| The numbered configurations will be: |
| |
| | Dest-Head | First-Head | Last-tail |
| ----+----------------+----------------+------------ |
| 0 | false | false | false |
| 1 | true | false | false |
| 2 | false | true | false |
| 3 | true | true | false |
| 4 | false | false | true |
| 5 | true | false | true |
| 6 | false | true | true |
| 7 | true | true | true |
| |
| Each numbered test scenario will also have a short explanation indicating what |
| this bit configuration represents. |
| */ |
| |
| static const std::string SpliceTestIR = R"( |
| define i16 @f(i16 %a) !dbg !6 { |
| call void @llvm.dbg.value(metadata i16 %a, metadata !9, metadata !DIExpression()), !dbg !11 |
| %b = add i16 %a, 1, !dbg !11 |
| call void @llvm.dbg.value(metadata i16 %b, metadata !9, metadata !DIExpression()), !dbg !11 |
| br label %exit, !dbg !11 |
| |
| exit: |
| call void @llvm.dbg.value(metadata i16 0, metadata !9, metadata !DIExpression()), !dbg !11 |
| %c = add i16 %b, 1, !dbg !11 |
| ret i16 0, !dbg !11 |
| } |
| declare void @llvm.dbg.value(metadata, metadata, metadata) #0 |
| attributes #0 = { nounwind readnone speculatable willreturn } |
| |
| !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: "foo", linkageName: "foo", 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: "1", 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) |
| )"; |
| |
| class DbgSpliceTest : public ::testing::Test { |
| protected: |
| LLVMContext C; |
| std::unique_ptr<Module> M; |
| BasicBlock *BBEntry, *BBExit; |
| BasicBlock::iterator Dest, First, Last; |
| Instruction *BInst, *Branch, *CInst; |
| DbgVariableRecord *DVRA, *DVRB, *DVRConst; |
| |
| void SetUp() override { |
| UseNewDbgInfoFormat = true; |
| M = parseIR(C, SpliceTestIR.c_str()); |
| M->convertToNewDbgValues(); |
| |
| BBEntry = &M->getFunction("f")->getEntryBlock(); |
| BBExit = BBEntry->getNextNode(); |
| |
| Dest = BBExit->begin(); |
| First = BBEntry->begin(); |
| Last = BBEntry->getTerminator()->getIterator(); |
| BInst = &*First; |
| Branch = &*Last; |
| CInst = &*Dest; |
| |
| DVRA = |
| cast<DbgVariableRecord>(&*BInst->DebugMarker->StoredDbgRecords.begin()); |
| DVRB = cast<DbgVariableRecord>( |
| &*Branch->DebugMarker->StoredDbgRecords.begin()); |
| DVRConst = |
| cast<DbgVariableRecord>(&*CInst->DebugMarker->StoredDbgRecords.begin()); |
| } |
| |
| void TearDown() override { UseNewDbgInfoFormat = false; } |
| |
| bool InstContainsDbgVariableRecord(Instruction *I, DbgVariableRecord *DVR) { |
| for (DbgRecord &D : I->getDbgRecordRange()) { |
| if (&D == DVR) { |
| // Confirm too that the links between the records are correct. |
| EXPECT_EQ(DVR->Marker, I->DebugMarker); |
| EXPECT_EQ(I->DebugMarker->MarkedInstr, I); |
| return true; |
| } |
| } |
| return false; |
| } |
| |
| bool CheckDVROrder(Instruction *I, |
| SmallVector<DbgVariableRecord *> CheckVals) { |
| SmallVector<DbgRecord *> Vals; |
| for (DbgRecord &D : I->getDbgRecordRange()) |
| Vals.push_back(&D); |
| |
| EXPECT_EQ(Vals.size(), CheckVals.size()); |
| if (Vals.size() != CheckVals.size()) |
| return false; |
| |
| for (unsigned int I = 0; I < Vals.size(); ++I) { |
| EXPECT_EQ(Vals[I], CheckVals[I]); |
| // Provide another expectation failure to let us localise what goes wrong, |
| // by returning a flag to the caller. |
| if (Vals[I] != CheckVals[I]) |
| return false; |
| } |
| return true; |
| } |
| }; |
| |
| TEST_F(DbgSpliceTest, DbgSpliceTest0) { |
| Dest.setHeadBit(false); |
| First.setHeadBit(false); |
| Last.setTailBit(false); |
| |
| /* |
| define i16 @f(i16 %a) !dbg !6 { |
| BBEntry entry: |
| DVRA call void @llvm.dbg.value(metadata i16 %a, metadata !9, metadata |
| !DIExpression()), !dbg !11 First %b = add i16 %a, 1, !dbg !11 DVRB call |
| void @llvm.dbg.value(metadata i16 %b, metadata !9, metadata !DIExpression()), |
| !dbg !11 Last br label %exit, !dbg !11 |
| |
| BBExit exit: |
| DVRConst call void @llvm.dbg.value(metadata i16 0, metadata !9, metadata |
| !DIExpression()), !dbg !11 Dest %c = add i16 %b, 1, !dbg !11 ret i16 0, |
| !dbg !11 |
| } |
| |
| Splice from First, not including leading dbg.value, to Last, including the |
| trailing dbg.value. Place at Dest, between the constant dbg.value and %c. |
| %b, and the following dbg.value, should move, to: |
| |
| define i16 @f(i16 %a) !dbg !6 { |
| BBEntry entry: |
| DVRA call void @llvm.dbg.value(metadata i16 %a, metadata !9, metadata |
| !DIExpression()), !dbg !11 Last br label %exit, !dbg !11 |
| |
| BBExit exit: |
| DVRConst call void @llvm.dbg.value(metadata i16 0, metadata !9, metadata |
| !DIExpression()), !dbg !11 First %b = add i16 %a, 1, !dbg !11 DVRB call |
| void @llvm.dbg.value(metadata i16 %b, metadata !9, metadata !DIExpression()), |
| !dbg !11 Dest %c = add i16 %b, 1, !dbg !11 ret i16 0, !dbg !11 |
| } |
| |
| |
| */ |
| BBExit->splice(Dest, BBEntry, First, Last); |
| EXPECT_EQ(BInst->getParent(), BBExit); |
| EXPECT_EQ(CInst->getParent(), BBExit); |
| EXPECT_EQ(Branch->getParent(), BBEntry); |
| |
| // DVRB: should be on Dest, in exit block. |
| EXPECT_TRUE(InstContainsDbgVariableRecord(CInst, DVRB)); |
| |
| // DVRA, should have "fallen" onto the branch, remained in entry block. |
| EXPECT_TRUE(InstContainsDbgVariableRecord(Branch, DVRA)); |
| |
| // DVRConst should be on the moved %b instruction. |
| EXPECT_TRUE(InstContainsDbgVariableRecord(BInst, DVRConst)); |
| } |
| |
| TEST_F(DbgSpliceTest, DbgSpliceTest1) { |
| Dest.setHeadBit(true); |
| First.setHeadBit(false); |
| Last.setTailBit(false); |
| |
| /* |
| define i16 @f(i16 %a) !dbg !6 { |
| BBEntry entry: |
| DVRA call void @llvm.dbg.value(metadata i16 %a, metadata !9, metadata |
| !DIExpression()), !dbg !11 First %b = add i16 %a, 1, !dbg !11 DVRB call |
| void @llvm.dbg.value(metadata i16 %b, metadata !9, metadata !DIExpression()), |
| !dbg !11 Last br label %exit, !dbg !11 |
| |
| BBExit exit: |
| DVRConst call void @llvm.dbg.value(metadata i16 0, metadata !9, metadata |
| !DIExpression()), !dbg !11 Dest %c = add i16 %b, 1, !dbg !11 ret i16 0, |
| !dbg !11 |
| } |
| |
| Splice from First, not including leading dbg.value, to Last, including the |
| trailing dbg.value. Place at the head of Dest, i.e. at the very start of |
| BBExit, before any debug-info there. Becomes: |
| |
| define i16 @f(i16 %a) !dbg !6 { |
| BBEntry entry: |
| DVRA call void @llvm.dbg.value(metadata i16 %a, metadata !9, metadata |
| !DIExpression()), !dbg !11 Last br label %exit, !dbg !11 |
| |
| BBExit exit: |
| First %b = add i16 %a, 1, !dbg !11 |
| DVRB call void @llvm.dbg.value(metadata i16 %b, metadata !9, metadata |
| !DIExpression()), !dbg !11 DVRConst call void @llvm.dbg.value(metadata i16 0, |
| metadata !9, metadata !DIExpression()), !dbg !11 Dest %c = add i16 %b, 1, |
| !dbg !11 ret i16 0, !dbg !11 |
| } |
| |
| |
| */ |
| BBExit->splice(Dest, BBEntry, First, Last); |
| EXPECT_EQ(BInst->getParent(), BBExit); |
| EXPECT_EQ(CInst->getParent(), BBExit); |
| EXPECT_EQ(Branch->getParent(), BBEntry); |
| |
| // DVRB: should be on CInst, in exit block. |
| EXPECT_TRUE(InstContainsDbgVariableRecord(CInst, DVRB)); |
| |
| // DVRA, should have "fallen" onto the branch, remained in entry block. |
| EXPECT_TRUE(InstContainsDbgVariableRecord(Branch, DVRA)); |
| |
| // DVRConst should be behind / after the moved instructions, remain on CInst. |
| EXPECT_TRUE(InstContainsDbgVariableRecord(CInst, DVRConst)); |
| |
| // Order of DVRB and DVRConst should be thus: |
| EXPECT_TRUE(CheckDVROrder(CInst, {DVRB, DVRConst})); |
| } |
| |
| TEST_F(DbgSpliceTest, DbgSpliceTest2) { |
| Dest.setHeadBit(false); |
| First.setHeadBit(true); |
| Last.setTailBit(false); |
| |
| /* |
| define i16 @f(i16 %a) !dbg !6 { |
| BBEntry entry: |
| DVRA call void @llvm.dbg.value(metadata i16 %a, metadata !9, metadata |
| !DIExpression()), !dbg !11 First %b = add i16 %a, 1, !dbg !11 DVRB call |
| void @llvm.dbg.value(metadata i16 %b, metadata !9, metadata !DIExpression()), |
| !dbg !11 Last br label %exit, !dbg !11 |
| |
| BBExit exit: |
| DVRConst call void @llvm.dbg.value(metadata i16 0, metadata !9, metadata |
| !DIExpression()), !dbg !11 Dest %c = add i16 %b, 1, !dbg !11 ret i16 0, |
| !dbg !11 |
| } |
| |
| Splice from head of First, which includes the leading dbg.value, to Last, |
| including the trailing dbg.value. Place in front of Dest, but after any |
| debug-info there. Becomes: |
| |
| define i16 @f(i16 %a) !dbg !6 { |
| BBEntry entry: |
| Last br label %exit, !dbg !11 |
| |
| BBExit exit: |
| DVRConst call void @llvm.dbg.value(metadata i16 0, metadata !9, metadata |
| !DIExpression()), !dbg !11 DVRA call void @llvm.dbg.value(metadata i16 %a, |
| metadata !9, metadata !DIExpression()), !dbg !11 First %b = add i16 %a, 1, |
| !dbg !11 DVRB call void @llvm.dbg.value(metadata i16 %b, metadata !9, |
| metadata !DIExpression()), !dbg !11 Dest %c = add i16 %b, 1, !dbg !11 ret |
| i16 0, !dbg !11 |
| } |
| |
| |
| */ |
| BBExit->splice(Dest, BBEntry, First, Last); |
| EXPECT_EQ(BInst->getParent(), BBExit); |
| EXPECT_EQ(CInst->getParent(), BBExit); |
| |
| // DVRB: should be on CInst, in exit block. |
| EXPECT_TRUE(InstContainsDbgVariableRecord(CInst, DVRB)); |
| |
| // DVRA, should have transferred with the spliced instructions, remains on |
| // the "b" inst. |
| EXPECT_TRUE(InstContainsDbgVariableRecord(BInst, DVRA)); |
| |
| // DVRConst should be ahead of the moved instructions, ahead of BInst. |
| EXPECT_TRUE(InstContainsDbgVariableRecord(BInst, DVRConst)); |
| |
| // Order of DVRA and DVRConst should be thus: |
| EXPECT_TRUE(CheckDVROrder(BInst, {DVRConst, DVRA})); |
| } |
| |
| TEST_F(DbgSpliceTest, DbgSpliceTest3) { |
| Dest.setHeadBit(true); |
| First.setHeadBit(true); |
| Last.setTailBit(false); |
| |
| /* |
| define i16 @f(i16 %a) !dbg !6 { |
| BBEntry entry: |
| DVRA call void @llvm.dbg.value(metadata i16 %a, metadata !9, metadata |
| !DIExpression()), !dbg !11 First %b = add i16 %a, 1, !dbg !11 DVRB call |
| void @llvm.dbg.value(metadata i16 %b, metadata !9, metadata !DIExpression()), |
| !dbg !11 Last br label %exit, !dbg !11 |
| |
| BBExit exit: |
| DVRConst call void @llvm.dbg.value(metadata i16 0, metadata !9, metadata |
| !DIExpression()), !dbg !11 Dest %c = add i16 %b, 1, !dbg !11 ret i16 0, |
| !dbg !11 |
| } |
| |
| Splice from head of First, which includes the leading dbg.value, to Last, |
| including the trailing dbg.value. Place at head of Dest, before any |
| debug-info there. Becomes: |
| |
| define i16 @f(i16 %a) !dbg !6 { |
| BBEntry entry: |
| Last br label %exit, !dbg !11 |
| |
| BBExit exit: |
| DVRA call void @llvm.dbg.value(metadata i16 %a, metadata !9, metadata |
| !DIExpression()), !dbg !11 First %b = add i16 %a, 1, !dbg !11 DVRB call |
| void @llvm.dbg.value(metadata i16 %b, metadata !9, metadata !DIExpression()), |
| !dbg !11 DVRConst call void @llvm.dbg.value(metadata i16 0, metadata !9, |
| metadata !DIExpression()), !dbg !11 Dest %c = add i16 %b, 1, !dbg !11 ret |
| i16 0, !dbg !11 |
| } |
| |
| */ |
| BBExit->splice(Dest, BBEntry, First, Last); |
| EXPECT_EQ(BInst->getParent(), BBExit); |
| EXPECT_EQ(CInst->getParent(), BBExit); |
| |
| // DVRB: should be on CInst, in exit block. |
| EXPECT_TRUE(InstContainsDbgVariableRecord(CInst, DVRB)); |
| |
| // DVRA, should have transferred with the spliced instructions, remains on |
| // the "b" inst. |
| EXPECT_TRUE(InstContainsDbgVariableRecord(BInst, DVRA)); |
| |
| // DVRConst should be behind the moved instructions, ahead of CInst. |
| EXPECT_TRUE(InstContainsDbgVariableRecord(CInst, DVRConst)); |
| |
| // Order of DVRB and DVRConst should be thus: |
| EXPECT_TRUE(CheckDVROrder(CInst, {DVRB, DVRConst})); |
| } |
| |
| TEST_F(DbgSpliceTest, DbgSpliceTest4) { |
| Dest.setHeadBit(false); |
| First.setHeadBit(false); |
| Last.setTailBit(true); |
| |
| /* |
| define i16 @f(i16 %a) !dbg !6 { |
| BBEntry entry: |
| DVRA call void @llvm.dbg.value(metadata i16 %a, metadata !9, metadata |
| !DIExpression()), !dbg !11 First %b = add i16 %a, 1, !dbg !11 DVRB call |
| void @llvm.dbg.value(metadata i16 %b, metadata !9, metadata !DIExpression()), |
| !dbg !11 Last br label %exit, !dbg !11 |
| |
| BBExit exit: |
| DVRConst call void @llvm.dbg.value(metadata i16 0, metadata !9, metadata |
| !DIExpression()), !dbg !11 Dest %c = add i16 %b, 1, !dbg !11 ret i16 0, |
| !dbg !11 |
| } |
| |
| Splice from First, not including the leading dbg.value, to Last, but NOT |
| including the trailing dbg.value because the tail bit is set. Place at Dest, |
| after any debug-info there. Becomes: |
| |
| define i16 @f(i16 %a) !dbg !6 { |
| BBEntry entry: |
| DVRA call void @llvm.dbg.value(metadata i16 %a, metadata !9, metadata |
| !DIExpression()), !dbg !11 DVRB call void @llvm.dbg.value(metadata i16 %b, |
| metadata !9, metadata !DIExpression()), !dbg !11 Last br label %exit, !dbg |
| !11 |
| |
| BBExit exit: |
| DVRConst call void @llvm.dbg.value(metadata i16 0, metadata !9, metadata |
| !DIExpression()), !dbg !11 First %b = add i16 %a, 1, !dbg !11 Dest %c = |
| add i16 %b, 1, !dbg !11 ret i16 0, !dbg !11 |
| } |
| |
| */ |
| BBExit->splice(Dest, BBEntry, First, Last); |
| EXPECT_EQ(BInst->getParent(), BBExit); |
| EXPECT_EQ(CInst->getParent(), BBExit); |
| EXPECT_EQ(Branch->getParent(), BBEntry); |
| |
| // DVRB: should be on Branch as before, remain in entry block. |
| EXPECT_TRUE(InstContainsDbgVariableRecord(Branch, DVRB)); |
| |
| // DVRA, should have remained in entry block, falls onto Branch inst. |
| EXPECT_TRUE(InstContainsDbgVariableRecord(Branch, DVRA)); |
| |
| // DVRConst should be ahead of the moved instructions, BInst. |
| EXPECT_TRUE(InstContainsDbgVariableRecord(BInst, DVRConst)); |
| |
| // Order of DVRA and DVRA should be thus: |
| EXPECT_TRUE(CheckDVROrder(Branch, {DVRA, DVRB})); |
| } |
| |
| TEST_F(DbgSpliceTest, DbgSpliceTest5) { |
| Dest.setHeadBit(true); |
| First.setHeadBit(false); |
| Last.setTailBit(true); |
| |
| /* |
| define i16 @f(i16 %a) !dbg !6 { |
| BBEntry entry: |
| DVRA call void @llvm.dbg.value(metadata i16 %a, metadata !9, metadata |
| !DIExpression()), !dbg !11 First %b = add i16 %a, 1, !dbg !11 DVRB call |
| void @llvm.dbg.value(metadata i16 %b, metadata !9, metadata !DIExpression()), |
| !dbg !11 Last br label %exit, !dbg !11 |
| |
| BBExit exit: |
| DVRConst call void @llvm.dbg.value(metadata i16 0, metadata !9, metadata |
| !DIExpression()), !dbg !11 Dest %c = add i16 %b, 1, !dbg !11 ret i16 0, |
| !dbg !11 |
| } |
| |
| Splice from First, not including the leading dbg.value, to Last, but NOT |
| including the trailing dbg.value because the tail bit is set. Place at head |
| of Dest, before any debug-info there. Becomes: |
| |
| define i16 @f(i16 %a) !dbg !6 { |
| BBEntry entry: |
| DVRA call void @llvm.dbg.value(metadata i16 %a, metadata !9, metadata |
| !DIExpression()), !dbg !11 DVRB call void @llvm.dbg.value(metadata i16 %b, |
| metadata !9, metadata !DIExpression()), !dbg !11 Last br label %exit, !dbg |
| !11 |
| |
| BBExit exit: |
| First %b = add i16 %a, 1, !dbg !11 |
| DVRConst call void @llvm.dbg.value(metadata i16 0, metadata !9, metadata |
| !DIExpression()), !dbg !11 Dest %c = add i16 %b, 1, !dbg !11 ret i16 0, |
| !dbg !11 |
| } |
| |
| */ |
| BBExit->splice(Dest, BBEntry, First, Last); |
| EXPECT_EQ(BInst->getParent(), BBExit); |
| EXPECT_EQ(CInst->getParent(), BBExit); |
| EXPECT_EQ(Branch->getParent(), BBEntry); |
| |
| // DVRB: should be on Branch as before, remain in entry block. |
| EXPECT_TRUE(InstContainsDbgVariableRecord(Branch, DVRB)); |
| |
| // DVRA, should have remained in entry block, falls onto Branch inst. |
| EXPECT_TRUE(InstContainsDbgVariableRecord(Branch, DVRA)); |
| |
| // DVRConst should be behind of the moved instructions, on CInst. |
| EXPECT_TRUE(InstContainsDbgVariableRecord(CInst, DVRConst)); |
| |
| // Order of DVRA and DVRB should be thus: |
| EXPECT_TRUE(CheckDVROrder(Branch, {DVRA, DVRB})); |
| } |
| |
| TEST_F(DbgSpliceTest, DbgSpliceTest6) { |
| Dest.setHeadBit(false); |
| First.setHeadBit(true); |
| Last.setTailBit(true); |
| |
| /* |
| define i16 @f(i16 %a) !dbg !6 { |
| BBEntry entry: |
| DVRA call void @llvm.dbg.value(metadata i16 %a, metadata !9, metadata |
| !DIExpression()), !dbg !11 First %b = add i16 %a, 1, !dbg !11 DVRB call |
| void @llvm.dbg.value(metadata i16 %b, metadata !9, metadata !DIExpression()), |
| !dbg !11 Last br label %exit, !dbg !11 |
| |
| BBExit exit: |
| DVRConst call void @llvm.dbg.value(metadata i16 0, metadata !9, metadata |
| !DIExpression()), !dbg !11 Dest %c = add i16 %b, 1, !dbg !11 ret i16 0, |
| !dbg !11 |
| } |
| |
| Splice from First, including the leading dbg.value, to Last, but NOT |
| including the trailing dbg.value because the tail bit is set. Place at Dest, |
| after any debug-info there. Becomes: |
| |
| define i16 @f(i16 %a) !dbg !6 { |
| BBEntry entry: |
| DVRB call void @llvm.dbg.value(metadata i16 %b, metadata !9, metadata |
| !DIExpression()), !dbg !11 Last br label %exit, !dbg !11 |
| |
| BBExit exit: |
| DVRConst call void @llvm.dbg.value(metadata i16 0, metadata !9, metadata |
| !DIExpression()), !dbg !11 DVRA call void @llvm.dbg.value(metadata i16 %a, |
| metadata !9, metadata !DIExpression()), !dbg !11 First %b = add i16 %a, 1, |
| !dbg !11 Dest %c = add i16 %b, 1, !dbg !11 ret i16 0, !dbg !11 |
| } |
| |
| */ |
| BBExit->splice(Dest, BBEntry, First, Last); |
| EXPECT_EQ(BInst->getParent(), BBExit); |
| EXPECT_EQ(CInst->getParent(), BBExit); |
| EXPECT_EQ(Branch->getParent(), BBEntry); |
| |
| // DVRB: should be on Branch as before, remain in entry block. |
| EXPECT_TRUE(InstContainsDbgVariableRecord(Branch, DVRB)); |
| |
| // DVRA, should have transferred to BBExit, on B inst. |
| EXPECT_TRUE(InstContainsDbgVariableRecord(BInst, DVRA)); |
| |
| // DVRConst should be ahead of the moved instructions, on BInst. |
| EXPECT_TRUE(InstContainsDbgVariableRecord(BInst, DVRConst)); |
| |
| // Order of DVRA and DVRConst should be thus: |
| EXPECT_TRUE(CheckDVROrder(BInst, {DVRConst, DVRA})); |
| } |
| |
| TEST_F(DbgSpliceTest, DbgSpliceTest7) { |
| Dest.setHeadBit(true); |
| First.setHeadBit(true); |
| Last.setTailBit(true); |
| |
| /* |
| define i16 @f(i16 %a) !dbg !6 { |
| BBEntry entry: |
| DVRA call void @llvm.dbg.value(metadata i16 %a, metadata !9, metadata |
| !DIExpression()), !dbg !11 First %b = add i16 %a, 1, !dbg !11 DVRB call |
| void @llvm.dbg.value(metadata i16 %b, metadata !9, metadata !DIExpression()), |
| !dbg !11 Last br label %exit, !dbg !11 |
| |
| BBExit exit: |
| DVRConst call void @llvm.dbg.value(metadata i16 0, metadata !9, metadata |
| !DIExpression()), !dbg !11 Dest %c = add i16 %b, 1, !dbg !11 ret i16 0, |
| !dbg !11 |
| } |
| |
| Splice from First, including the leading dbg.value, to Last, but NOT |
| including the trailing dbg.value because the tail bit is set. Place at head |
| of Dest, before any debug-info there. Becomes: |
| |
| define i16 @f(i16 %a) !dbg !6 { |
| BBEntry entry: |
| DVRB call void @llvm.dbg.value(metadata i16 %b, metadata !9, metadata |
| !DIExpression()), !dbg !11 Last br label %exit, !dbg !11 |
| |
| BBExit exit: |
| DVRA call void @llvm.dbg.value(metadata i16 %a, metadata !9, metadata |
| !DIExpression()), !dbg !11 First %b = add i16 %a, 1, !dbg !11 DVRConst call |
| void @llvm.dbg.value(metadata i16 0, metadata !9, metadata !DIExpression()), |
| !dbg !11 Dest %c = add i16 %b, 1, !dbg !11 ret i16 0, !dbg !11 |
| } |
| |
| */ |
| BBExit->splice(Dest, BBEntry, First, Last); |
| EXPECT_EQ(BInst->getParent(), BBExit); |
| EXPECT_EQ(CInst->getParent(), BBExit); |
| EXPECT_EQ(Branch->getParent(), BBEntry); |
| |
| // DVRB: should be on Branch as before, remain in entry block. |
| EXPECT_TRUE(InstContainsDbgVariableRecord(Branch, DVRB)); |
| |
| // DVRA, should have transferred to BBExit, on B inst. |
| EXPECT_TRUE(InstContainsDbgVariableRecord(BInst, DVRA)); |
| |
| // DVRConst should be after of the moved instructions, on CInst. |
| EXPECT_TRUE(InstContainsDbgVariableRecord(CInst, DVRConst)); |
| } |
| |
| // But wait, there's more! What if you splice a range that is empty, but |
| // implicitly contains debug-info? In the dbg.value design for debug-info, |
| // this would be an explicit range, but in DbgVariableRecord debug-info, it |
| // isn't. Check that if we try to do that, with differing head-bit values, that |
| // DbgVariableRecords are transferred. |
| // Test with empty transfers to Dest, with head bit set and not set. |
| |
| TEST_F(DbgSpliceTest, DbgSpliceEmpty0) { |
| Dest.setHeadBit(false); |
| First.setHeadBit(false); |
| Last.setHeadBit(false); |
| /* |
| define i16 @f(i16 %a) !dbg !6 { |
| BBEntry entry: |
| DVRA call void @llvm.dbg.value(metadata i16 %a, metadata !9, metadata |
| !DIExpression()), !dbg !11 First %b = add i16 %a, 1, !dbg !11 DVRB call |
| void @llvm.dbg.value(metadata i16 %b, metadata !9, metadata !DIExpression()), |
| !dbg !11 Last br label %exit, !dbg !11 |
| |
| BBExit exit: |
| DVRConst call void @llvm.dbg.value(metadata i16 0, metadata !9, metadata |
| !DIExpression()), !dbg !11 Dest %c = add i16 %b, 1, !dbg !11 ret i16 0, |
| !dbg !11 |
| } |
| |
| Splice from BBEntry.getFirstInsertionPt to First -- this implicitly is a |
| splice of DVRA, but the iterators are pointing at the same instruction. The |
| only difference is the setting of the head bit. Becomes; |
| |
| define i16 @f(i16 %a) !dbg !6 { |
| First %b = add i16 %a, 1, !dbg !11 |
| DVRB call void @llvm.dbg.value(metadata i16 %b, metadata !9, metadata |
| !DIExpression()), !dbg !11 Last br label %exit, !dbg !11 |
| |
| BBExit exit: |
| DVRConst call void @llvm.dbg.value(metadata i16 0, metadata !9, metadata |
| !DIExpression()), !dbg !11 DVRA call void @llvm.dbg.value(metadata i16 %a, |
| metadata !9, metadata !DIExpression()), !dbg !11 Dest %c = add i16 %b, 1, |
| !dbg !11 ret i16 0, !dbg !11 |
| } |
| |
| */ |
| BBExit->splice(Dest, BBEntry, BBEntry->getFirstInsertionPt(), First); |
| EXPECT_EQ(BInst->getParent(), BBEntry); |
| EXPECT_EQ(CInst->getParent(), BBExit); |
| EXPECT_EQ(Branch->getParent(), BBEntry); |
| |
| // DVRB: should be on Branch as before, remain in entry block. |
| EXPECT_TRUE(InstContainsDbgVariableRecord(Branch, DVRB)); |
| |
| // DVRA, should have transferred to BBExit, on C inst. |
| EXPECT_TRUE(InstContainsDbgVariableRecord(CInst, DVRA)); |
| |
| // DVRConst should be ahead of the moved DbgVariableRecord, on CInst. |
| EXPECT_TRUE(InstContainsDbgVariableRecord(CInst, DVRConst)); |
| |
| // Order of DVRA and DVRConst should be thus: |
| EXPECT_TRUE(CheckDVROrder(CInst, {DVRConst, DVRA})); |
| } |
| |
| TEST_F(DbgSpliceTest, DbgSpliceEmpty1) { |
| Dest.setHeadBit(true); |
| First.setHeadBit(false); |
| Last.setHeadBit(false); |
| /* |
| define i16 @f(i16 %a) !dbg !6 { |
| BBEntry entry: |
| DVRA call void @llvm.dbg.value(metadata i16 %a, metadata !9, metadata |
| !DIExpression()), !dbg !11 First %b = add i16 %a, 1, !dbg !11 DVRB call |
| void @llvm.dbg.value(metadata i16 %b, metadata !9, metadata !DIExpression()), |
| !dbg !11 Last br label %exit, !dbg !11 |
| |
| BBExit exit: |
| DVRConst call void @llvm.dbg.value(metadata i16 0, metadata !9, metadata |
| !DIExpression()), !dbg !11 Dest %c = add i16 %b, 1, !dbg !11 ret i16 0, |
| !dbg !11 |
| } |
| |
| Splice from BBEntry.getFirstInsertionPt to First -- this implicitly is a |
| splice of DVRA, but the iterators are pointing at the same instruction. The |
| only difference is the setting of the head bit. Insert at head of Dest, |
| i.e. before DVRConst. Becomes; |
| |
| define i16 @f(i16 %a) !dbg !6 { |
| First %b = add i16 %a, 1, !dbg !11 |
| DVRB call void @llvm.dbg.value(metadata i16 %b, metadata !9, metadata |
| !DIExpression()), !dbg !11 Last br label %exit, !dbg !11 |
| |
| BBExit exit: |
| DVRA call void @llvm.dbg.value(metadata i16 %a, metadata !9, metadata |
| !DIExpression()), !dbg !11 DVRConst call void @llvm.dbg.value(metadata i16 0, |
| metadata !9, metadata !DIExpression()), !dbg !11 Dest %c = add i16 %b, 1, |
| !dbg !11 ret i16 0, !dbg !11 |
| } |
| |
| */ |
| BBExit->splice(Dest, BBEntry, BBEntry->getFirstInsertionPt(), First); |
| EXPECT_EQ(BInst->getParent(), BBEntry); |
| EXPECT_EQ(CInst->getParent(), BBExit); |
| EXPECT_EQ(Branch->getParent(), BBEntry); |
| |
| // DVRB: should be on Branch as before, remain in entry block. |
| EXPECT_TRUE(InstContainsDbgVariableRecord(Branch, DVRB)); |
| |
| // DVRA, should have transferred to BBExit, on C inst. |
| EXPECT_TRUE(InstContainsDbgVariableRecord(CInst, DVRA)); |
| |
| // DVRConst should be ahead of the moved DbgVariableRecord, on CInst. |
| EXPECT_TRUE(InstContainsDbgVariableRecord(CInst, DVRConst)); |
| |
| // Order of DVRA and DVRConst should be thus: |
| EXPECT_TRUE(CheckDVROrder(CInst, {DVRA, DVRConst})); |
| } |
| |
| // If we splice new instructions into a block with trailing DbgVariableRecords, |
| // then the trailing DbgVariableRecords should get flushed back out. |
| TEST(BasicBlockDbgInfoTest, DbgSpliceTrailing) { |
| LLVMContext C; |
| UseNewDbgInfoFormat = true; |
| |
| std::unique_ptr<Module> M = parseIR(C, R"( |
| define i16 @f(i16 %a) !dbg !6 { |
| entry: |
| call void @llvm.dbg.value(metadata i16 %a, metadata !9, metadata !DIExpression()), !dbg !11 |
| br label %exit |
| |
| exit: |
| %b = add i16 %a, 1, !dbg !11 |
| ret i16 0, !dbg !11 |
| } |
| declare void @llvm.dbg.value(metadata, metadata, metadata) #0 |
| attributes #0 = { nounwind readnone speculatable willreturn } |
| |
| !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: "foo", linkageName: "foo", 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: "1", 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) |
| )"); |
| |
| BasicBlock &Entry = M->getFunction("f")->getEntryBlock(); |
| BasicBlock &Exit = *Entry.getNextNode(); |
| M->convertToNewDbgValues(); |
| |
| // Begin by forcing entry block to have dangling DbgVariableRecord. |
| Entry.getTerminator()->eraseFromParent(); |
| ASSERT_NE(Entry.getTrailingDbgRecords(), nullptr); |
| EXPECT_TRUE(Entry.empty()); |
| |
| // Now transfer the entire contents of the exit block into the entry. |
| Entry.splice(Entry.end(), &Exit, Exit.begin(), Exit.end()); |
| |
| // The trailing DbgVariableRecord should have been placed at the front of |
| // what's been spliced in. |
| Instruction *BInst = &*Entry.begin(); |
| ASSERT_TRUE(BInst->DebugMarker); |
| EXPECT_EQ(BInst->DebugMarker->StoredDbgRecords.size(), 1u); |
| |
| UseNewDbgInfoFormat = false; |
| } |
| |
| // When we remove instructions from the program, adjacent DbgVariableRecords |
| // coalesce together into one DbgMarker. In "old" dbg.value mode you could |
| // re-insert the removed instruction back into the middle of a sequence of |
| // dbg.values. Test that this can be replicated correctly by DbgVariableRecords |
| TEST(BasicBlockDbgInfoTest, RemoveInstAndReinsert) { |
| LLVMContext C; |
| UseNewDbgInfoFormat = true; |
| |
| std::unique_ptr<Module> M = parseIR(C, R"( |
| define i16 @f(i16 %a) !dbg !6 { |
| entry: |
| %qux = sub i16 %a, 0 |
| call void @llvm.dbg.value(metadata i16 %a, metadata !9, metadata !DIExpression()), !dbg !11 |
| %foo = add i16 %a, %a |
| call void @llvm.dbg.value(metadata i16 0, metadata !9, metadata !DIExpression()), !dbg !11 |
| ret i16 1 |
| } |
| 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: "foo", linkageName: "foo", 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: "1", 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) |
| )"); |
| |
| BasicBlock &Entry = M->getFunction("f")->getEntryBlock(); |
| M->convertToNewDbgValues(); |
| |
| // Fetch the relevant instructions from the converted function. |
| Instruction *SubInst = &*Entry.begin(); |
| ASSERT_TRUE(isa<BinaryOperator>(SubInst)); |
| Instruction *AddInst = SubInst->getNextNode(); |
| ASSERT_TRUE(isa<BinaryOperator>(AddInst)); |
| Instruction *RetInst = AddInst->getNextNode(); |
| ASSERT_TRUE(isa<ReturnInst>(RetInst)); |
| |
| // add and sub should both have one DbgVariableRecord on add and ret. |
| EXPECT_FALSE(SubInst->hasDbgRecords()); |
| EXPECT_TRUE(AddInst->hasDbgRecords()); |
| EXPECT_TRUE(RetInst->hasDbgRecords()); |
| auto R1 = AddInst->getDbgRecordRange(); |
| EXPECT_EQ(std::distance(R1.begin(), R1.end()), 1u); |
| auto R2 = RetInst->getDbgRecordRange(); |
| EXPECT_EQ(std::distance(R2.begin(), R2.end()), 1u); |
| |
| // The Supported (TM) code sequence for removing then reinserting insts |
| // after another instruction: |
| std::optional<DbgVariableRecord::self_iterator> Pos = |
| AddInst->getDbgReinsertionPosition(); |
| AddInst->removeFromParent(); |
| |
| // We should have a re-insertion position. |
| ASSERT_TRUE(Pos); |
| // Both DbgVariableRecords should now be attached to the ret inst. |
| auto R3 = RetInst->getDbgRecordRange(); |
| EXPECT_EQ(std::distance(R3.begin(), R3.end()), 2u); |
| |
| // Re-insert and re-insert. |
| AddInst->insertAfter(SubInst); |
| Entry.reinsertInstInDbgRecords(AddInst, Pos); |
| // We should be back into a position of having one DbgVariableRecord on add |
| // and ret. |
| EXPECT_FALSE(SubInst->hasDbgRecords()); |
| EXPECT_TRUE(AddInst->hasDbgRecords()); |
| EXPECT_TRUE(RetInst->hasDbgRecords()); |
| auto R4 = AddInst->getDbgRecordRange(); |
| EXPECT_EQ(std::distance(R4.begin(), R4.end()), 1u); |
| auto R5 = RetInst->getDbgRecordRange(); |
| EXPECT_EQ(std::distance(R5.begin(), R5.end()), 1u); |
| |
| UseNewDbgInfoFormat = false; |
| } |
| |
| // Test instruction removal and re-insertion, this time with one |
| // DbgVariableRecord that should hop up one instruction. |
| TEST(BasicBlockDbgInfoTest, RemoveInstAndReinsertForOneDbgVariableRecord) { |
| LLVMContext C; |
| UseNewDbgInfoFormat = true; |
| |
| std::unique_ptr<Module> M = parseIR(C, R"( |
| define i16 @f(i16 %a) !dbg !6 { |
| entry: |
| %qux = sub i16 %a, 0 |
| call void @llvm.dbg.value(metadata i16 %a, metadata !9, metadata !DIExpression()), !dbg !11 |
| %foo = add i16 %a, %a |
| ret i16 1 |
| } |
| 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: "foo", linkageName: "foo", 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: "1", 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) |
| )"); |
| |
| BasicBlock &Entry = M->getFunction("f")->getEntryBlock(); |
| M->convertToNewDbgValues(); |
| |
| // Fetch the relevant instructions from the converted function. |
| Instruction *SubInst = &*Entry.begin(); |
| ASSERT_TRUE(isa<BinaryOperator>(SubInst)); |
| Instruction *AddInst = SubInst->getNextNode(); |
| ASSERT_TRUE(isa<BinaryOperator>(AddInst)); |
| Instruction *RetInst = AddInst->getNextNode(); |
| ASSERT_TRUE(isa<ReturnInst>(RetInst)); |
| |
| // There should be one DbgVariableRecord. |
| EXPECT_FALSE(SubInst->hasDbgRecords()); |
| EXPECT_TRUE(AddInst->hasDbgRecords()); |
| EXPECT_FALSE(RetInst->hasDbgRecords()); |
| auto R1 = AddInst->getDbgRecordRange(); |
| EXPECT_EQ(std::distance(R1.begin(), R1.end()), 1u); |
| |
| // The Supported (TM) code sequence for removing then reinserting insts: |
| std::optional<DbgVariableRecord::self_iterator> Pos = |
| AddInst->getDbgReinsertionPosition(); |
| AddInst->removeFromParent(); |
| |
| // No re-insertion position as there were no DbgVariableRecords on the ret. |
| ASSERT_FALSE(Pos); |
| // The single DbgVariableRecord should now be attached to the ret inst. |
| EXPECT_TRUE(RetInst->hasDbgRecords()); |
| auto R2 = RetInst->getDbgRecordRange(); |
| EXPECT_EQ(std::distance(R2.begin(), R2.end()), 1u); |
| |
| // Re-insert and re-insert. |
| AddInst->insertAfter(SubInst); |
| Entry.reinsertInstInDbgRecords(AddInst, Pos); |
| // We should be back into a position of having one DbgVariableRecord on the |
| // AddInst. |
| EXPECT_FALSE(SubInst->hasDbgRecords()); |
| EXPECT_TRUE(AddInst->hasDbgRecords()); |
| EXPECT_FALSE(RetInst->hasDbgRecords()); |
| auto R3 = AddInst->getDbgRecordRange(); |
| EXPECT_EQ(std::distance(R3.begin(), R3.end()), 1u); |
| |
| UseNewDbgInfoFormat = false; |
| } |
| |
| // Similar to the above, what if we splice into an empty block with debug-info, |
| // with debug-info at the start of the moving range, that we intend to be |
| // transferred. The dbg.value of %a should remain at the start, but come ahead |
| // of the i16 0 dbg.value. |
| TEST(BasicBlockDbgInfoTest, DbgSpliceToEmpty1) { |
| LLVMContext C; |
| UseNewDbgInfoFormat = true; |
| |
| std::unique_ptr<Module> M = parseIR(C, R"( |
| define i16 @f(i16 %a) !dbg !6 { |
| entry: |
| call void @llvm.dbg.value(metadata i16 %a, metadata !9, metadata !DIExpression()), !dbg !11 |
| br label %exit |
| |
| exit: |
| call void @llvm.dbg.value(metadata i16 0, metadata !9, metadata !DIExpression()), !dbg !11 |
| %b = add i16 %a, 1, !dbg !11 |
| call void @llvm.dbg.value(metadata i16 1, metadata !9, metadata !DIExpression()), !dbg !11 |
| ret i16 0, !dbg !11 |
| } |
| declare void @llvm.dbg.value(metadata, metadata, metadata) #0 |
| attributes #0 = { nounwind readnone speculatable willreturn } |
| |
| !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: "foo", linkageName: "foo", 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: "1", 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) |
| )"); |
| |
| Function &F = *M->getFunction("f"); |
| BasicBlock &Entry = F.getEntryBlock(); |
| BasicBlock &Exit = *Entry.getNextNode(); |
| M->convertToNewDbgValues(); |
| |
| // Begin by forcing entry block to have dangling DbgVariableRecord. |
| Entry.getTerminator()->eraseFromParent(); |
| ASSERT_NE(Entry.getTrailingDbgRecords(), nullptr); |
| EXPECT_TRUE(Entry.empty()); |
| |
| // Now transfer the entire contents of the exit block into the entry. This |
| // includes both dbg.values. |
| Entry.splice(Entry.end(), &Exit, Exit.begin(), Exit.end()); |
| |
| // We should now have two dbg.values on the first instruction, and they |
| // should be in the correct order of %a, then 0. |
| Instruction *BInst = &*Entry.begin(); |
| ASSERT_TRUE(BInst->hasDbgRecords()); |
| EXPECT_EQ(BInst->DebugMarker->StoredDbgRecords.size(), 2u); |
| SmallVector<DbgVariableRecord *, 2> DbgVariableRecords; |
| for (DbgRecord &DVR : BInst->getDbgRecordRange()) |
| DbgVariableRecords.push_back(cast<DbgVariableRecord>(&DVR)); |
| |
| EXPECT_EQ(DbgVariableRecords[0]->getVariableLocationOp(0), F.getArg(0)); |
| Value *SecondDVRValue = DbgVariableRecords[1]->getVariableLocationOp(0); |
| ASSERT_TRUE(isa<ConstantInt>(SecondDVRValue)); |
| EXPECT_EQ(cast<ConstantInt>(SecondDVRValue)->getZExtValue(), 0ull); |
| |
| // No trailing DbgVariableRecords in the entry block now. |
| EXPECT_EQ(Entry.getTrailingDbgRecords(), nullptr); |
| |
| UseNewDbgInfoFormat = false; |
| } |
| |
| // Similar test again, but this time: splice the contents of exit into entry, |
| // with the intention of leaving the first dbg.value (i16 0) behind. |
| TEST(BasicBlockDbgInfoTest, DbgSpliceToEmpty2) { |
| LLVMContext C; |
| UseNewDbgInfoFormat = true; |
| |
| std::unique_ptr<Module> M = parseIR(C, R"( |
| define i16 @f(i16 %a) !dbg !6 { |
| entry: |
| call void @llvm.dbg.value(metadata i16 %a, metadata !9, metadata !DIExpression()), !dbg !11 |
| br label %exit |
| |
| exit: |
| call void @llvm.dbg.value(metadata i16 0, metadata !9, metadata !DIExpression()), !dbg !11 |
| %b = add i16 %a, 1, !dbg !11 |
| call void @llvm.dbg.value(metadata i16 1, metadata !9, metadata !DIExpression()), !dbg !11 |
| ret i16 0, !dbg !11 |
| } |
| declare void @llvm.dbg.value(metadata, metadata, metadata) #0 |
| attributes #0 = { nounwind readnone speculatable willreturn } |
| |
| !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: "foo", linkageName: "foo", 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: "1", 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) |
| )"); |
| |
| Function &F = *M->getFunction("f"); |
| BasicBlock &Entry = F.getEntryBlock(); |
| BasicBlock &Exit = *Entry.getNextNode(); |
| M->convertToNewDbgValues(); |
| |
| // Begin by forcing entry block to have dangling DbgVariableRecord. |
| Entry.getTerminator()->eraseFromParent(); |
| ASSERT_NE(Entry.getTrailingDbgRecords(), nullptr); |
| EXPECT_TRUE(Entry.empty()); |
| |
| // Now transfer into the entry block -- fetching the first instruction with |
| // begin and then calling getIterator clears the "head" bit, meaning that the |
| // range to move will not include any leading DbgVariableRecords. |
| Entry.splice(Entry.end(), &Exit, Exit.begin()->getIterator(), Exit.end()); |
| |
| // We should now have one dbg.values on the first instruction, %a. |
| Instruction *BInst = &*Entry.begin(); |
| ASSERT_TRUE(BInst->hasDbgRecords()); |
| EXPECT_EQ(BInst->DebugMarker->StoredDbgRecords.size(), 1u); |
| SmallVector<DbgVariableRecord *, 2> DbgVariableRecords; |
| for (DbgRecord &DVR : BInst->getDbgRecordRange()) |
| DbgVariableRecords.push_back(cast<DbgVariableRecord>(&DVR)); |
| |
| EXPECT_EQ(DbgVariableRecords[0]->getVariableLocationOp(0), F.getArg(0)); |
| // No trailing DbgVariableRecords in the entry block now. |
| EXPECT_EQ(Entry.getTrailingDbgRecords(), nullptr); |
| |
| // We should have nothing left in the exit block... |
| EXPECT_TRUE(Exit.empty()); |
| // ... except for some dangling DbgVariableRecords. |
| EXPECT_NE(Exit.getTrailingDbgRecords(), nullptr); |
| EXPECT_FALSE(Exit.getTrailingDbgRecords()->empty()); |
| Exit.getTrailingDbgRecords()->eraseFromParent(); |
| Exit.deleteTrailingDbgRecords(); |
| |
| UseNewDbgInfoFormat = false; |
| } |
| |
| // What if we moveBefore end() -- there might be no debug-info there, in which |
| // case we shouldn't crash. |
| TEST(BasicBlockDbgInfoTest, DbgMoveToEnd) { |
| LLVMContext C; |
| UseNewDbgInfoFormat = true; |
| |
| std::unique_ptr<Module> M = parseIR(C, R"( |
| define i16 @f(i16 %a) !dbg !6 { |
| entry: |
| br label %exit |
| |
| exit: |
| ret i16 0, !dbg !11 |
| } |
| declare void @llvm.dbg.value(metadata, metadata, metadata) #0 |
| attributes #0 = { nounwind readnone speculatable willreturn } |
| |
| !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: "foo", linkageName: "foo", 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: "1", 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) |
| )"); |
| |
| Function &F = *M->getFunction("f"); |
| BasicBlock &Entry = F.getEntryBlock(); |
| BasicBlock &Exit = *Entry.getNextNode(); |
| M->convertToNewDbgValues(); |
| |
| // Move the return to the end of the entry block. |
| Instruction *Br = Entry.getTerminator(); |
| Instruction *Ret = Exit.getTerminator(); |
| EXPECT_EQ(Entry.getTrailingDbgRecords(), nullptr); |
| Ret->moveBefore(Entry, Entry.end()); |
| Br->eraseFromParent(); |
| |
| // There should continue to not be any debug-info anywhere. |
| EXPECT_EQ(Entry.getTrailingDbgRecords(), nullptr); |
| EXPECT_EQ(Exit.getTrailingDbgRecords(), nullptr); |
| EXPECT_FALSE(Ret->hasDbgRecords()); |
| |
| UseNewDbgInfoFormat = false; |
| } |
| |
| } // End anonymous namespace. |