
The CMake flag has been on by default for a month without any issues. This makes the feature support in LLVM unconditional (but does not enable the feature by default).
1640 lines
64 KiB
C++
1640 lines
64 KiB
C++
//===- 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;
|
|
|
|
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;
|
|
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)
|
|
)");
|
|
|
|
// 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);
|
|
}
|
|
|
|
TEST(BasicBlockDbgInfoTest, SplitBasicBlockBefore) {
|
|
LLVMContext C;
|
|
std::unique_ptr<Module> M = parseIR(C, R"---(
|
|
define dso_local void @func() #0 !dbg !10 {
|
|
%1 = alloca i32, align 4
|
|
tail call void @llvm.dbg.declare(metadata ptr %1, metadata !14, metadata !DIExpression()), !dbg !16
|
|
store i32 2, ptr %1, align 4, !dbg !16
|
|
ret void, !dbg !17
|
|
}
|
|
|
|
declare void @llvm.dbg.declare(metadata, metadata, metadata) #0
|
|
|
|
attributes #0 = { nocallback nofree nosync nounwind speculatable willreturn memory(none) }
|
|
|
|
!llvm.dbg.cu = !{!0}
|
|
!llvm.module.flags = !{!2, !3, !4, !5, !6, !7, !8}
|
|
!llvm.ident = !{!9}
|
|
|
|
!0 = distinct !DICompileUnit(language: DW_LANG_C11, file: !1, producer: "dummy", isOptimized: false, runtimeVersion: 0, emissionKind: FullDebug, splitDebugInlining: false, nameTableKind: None)
|
|
!1 = !DIFile(filename: "dummy", directory: "dummy")
|
|
!2 = !{i32 7, !"Dwarf Version", i32 5}
|
|
!3 = !{i32 2, !"Debug Info Version", i32 3}
|
|
!4 = !{i32 1, !"wchar_size", i32 4}
|
|
!5 = !{i32 8, !"PIC Level", i32 2}
|
|
!6 = !{i32 7, !"PIE Level", i32 2}
|
|
!7 = !{i32 7, !"uwtable", i32 2}
|
|
!8 = !{i32 7, !"frame-pointer", i32 2}
|
|
!9 = !{!"dummy"}
|
|
!10 = distinct !DISubprogram(name: "func", scope: !1, file: !1, line: 1, type: !11, scopeLine: 1, spFlags: DISPFlagDefinition, unit: !0, retainedNodes: !13)
|
|
!11 = !DISubroutineType(types: !12)
|
|
!12 = !{null}
|
|
!13 = !{}
|
|
!14 = !DILocalVariable(name: "a", scope: !10, file: !1, line: 2, type: !15)
|
|
!15 = !DIBasicType(name: "int", size: 32, encoding: DW_ATE_signed)
|
|
!16 = !DILocation(line: 2, column: 6, scope: !10)
|
|
!17 = !DILocation(line: 3, column: 2, scope: !10)
|
|
)---");
|
|
ASSERT_TRUE(M);
|
|
|
|
Function *F = M->getFunction("func");
|
|
|
|
BasicBlock &BB = F->getEntryBlock();
|
|
auto I = std::prev(BB.end(), 2); // store i32 2, ptr %1.
|
|
BB.splitBasicBlockBefore(I, "before");
|
|
|
|
BasicBlock &BBBefore = F->getEntryBlock();
|
|
auto I2 = std::prev(BBBefore.end()); // br label %1 (new).
|
|
ASSERT_TRUE(I2->hasDbgRecords());
|
|
}
|
|
|
|
TEST(BasicBlockDbgInfoTest, DropSourceAtomOnSplit) {
|
|
LLVMContext C;
|
|
std::unique_ptr<Module> M = parseIR(C, R"---(
|
|
define dso_local void @func() !dbg !10 {
|
|
%1 = alloca i32, align 4
|
|
ret void, !dbg !DILocation(line: 3, column: 2, scope: !10, atomGroup: 1, atomRank: 1)
|
|
}
|
|
|
|
!llvm.dbg.cu = !{!0}
|
|
!llvm.module.flags = !{!2, !3}
|
|
|
|
!0 = distinct !DICompileUnit(language: DW_LANG_C11, file: !1, producer: "dummy", isOptimized: false, runtimeVersion: 0, emissionKind: FullDebug, splitDebugInlining: false, nameTableKind: None)
|
|
!1 = !DIFile(filename: "dummy", directory: "dummy")
|
|
!2 = !{i32 7, !"Dwarf Version", i32 5}
|
|
!3 = !{i32 2, !"Debug Info Version", i32 3}
|
|
!10 = distinct !DISubprogram(name: "func", scope: !1, file: !1, line: 1, type: !11, scopeLine: 1, spFlags: DISPFlagDefinition, unit: !0, retainedNodes: !13, keyInstructions: true)
|
|
!11 = !DISubroutineType(types: !12)
|
|
!12 = !{null}
|
|
!13 = !{}
|
|
!14 = !DILocalVariable(name: "a", scope: !10, file: !1, line: 2, type: !15)
|
|
!15 = !DIBasicType(name: "int", size: 32, encoding: DW_ATE_signed)
|
|
)---");
|
|
ASSERT_TRUE(M);
|
|
|
|
Function *F = M->getFunction("func");
|
|
|
|
// Test splitBasicBlockBefore.
|
|
{
|
|
BasicBlock &BB = F->back();
|
|
// Split at `ret void`.
|
|
BasicBlock *Before =
|
|
BB.splitBasicBlockBefore(std::prev(BB.end(), 1), "before");
|
|
const DebugLoc &BrToAfterDL = Before->getTerminator()->getDebugLoc();
|
|
ASSERT_TRUE(BrToAfterDL);
|
|
EXPECT_EQ(BrToAfterDL->getAtomGroup(), 0u);
|
|
|
|
BasicBlock *After = Before->getSingleSuccessor();
|
|
ASSERT_TRUE(After);
|
|
const DebugLoc &OrigTerminatorDL = After->getTerminator()->getDebugLoc();
|
|
ASSERT_TRUE(OrigTerminatorDL);
|
|
EXPECT_EQ(OrigTerminatorDL->getAtomGroup(), 1u);
|
|
}
|
|
|
|
// Test splitBasicBlock.
|
|
{
|
|
BasicBlock &BB = F->back();
|
|
// Split at `ret void`.
|
|
BasicBlock *After = BB.splitBasicBlock(std::prev(BB.end(), 1), "before");
|
|
|
|
const DebugLoc &OrigTerminatorDL = After->getTerminator()->getDebugLoc();
|
|
ASSERT_TRUE(OrigTerminatorDL);
|
|
EXPECT_EQ(OrigTerminatorDL->getAtomGroup(), 1u);
|
|
|
|
BasicBlock *Before = After->getSinglePredecessor();
|
|
ASSERT_TRUE(Before);
|
|
const DebugLoc &BrToAfterDL = Before->getTerminator()->getDebugLoc();
|
|
ASSERT_TRUE(BrToAfterDL);
|
|
EXPECT_EQ(BrToAfterDL->getAtomGroup(), 0u);
|
|
}
|
|
}
|
|
|
|
TEST(BasicBlockDbgInfoTest, MarkerOperations) {
|
|
LLVMContext C;
|
|
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();
|
|
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(
|
|
llvm::make_pointer_range(Marker2->getDbgRecordRange()));
|
|
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());
|
|
}
|
|
|
|
TEST(BasicBlockDbgInfoTest, HeadBitOperations) {
|
|
LLVMContext C;
|
|
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();
|
|
|
|
// 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->getIterator());
|
|
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);
|
|
}
|
|
|
|
TEST(BasicBlockDbgInfoTest, InstrDbgAccess) {
|
|
LLVMContext C;
|
|
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();
|
|
|
|
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);
|
|
}
|
|
|
|
/* 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 {
|
|
M = parseIR(C, SpliceTestIR.c_str());
|
|
|
|
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());
|
|
}
|
|
|
|
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(
|
|
llvm::make_pointer_range(I->getDbgRecordRange()));
|
|
|
|
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;
|
|
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();
|
|
|
|
// 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);
|
|
}
|
|
|
|
// 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;
|
|
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();
|
|
|
|
// 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->getIterator());
|
|
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);
|
|
}
|
|
|
|
// Test instruction removal and re-insertion, this time with one
|
|
// DbgVariableRecord that should hop up one instruction.
|
|
TEST(BasicBlockDbgInfoTest, RemoveInstAndReinsertForOneDbgVariableRecord) {
|
|
LLVMContext C;
|
|
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();
|
|
|
|
// 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->getIterator());
|
|
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);
|
|
}
|
|
|
|
// 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;
|
|
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();
|
|
|
|
// 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);
|
|
}
|
|
|
|
// 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;
|
|
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();
|
|
|
|
// 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();
|
|
}
|
|
|
|
// What if we moveBefore end() -- there might be no debug-info there, in which
|
|
// case we shouldn't crash.
|
|
TEST(BasicBlockDbgInfoTest, DbgMoveToEnd) {
|
|
LLVMContext C;
|
|
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();
|
|
|
|
// 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());
|
|
}
|
|
|
|
TEST(BasicBlockDbgInfoTest, CloneTrailingRecordsToEmptyBlock) {
|
|
LLVMContext C;
|
|
std::unique_ptr<Module> M = parseIR(C, R"(
|
|
define i16 @foo(i16 %a) !dbg !6 {
|
|
entry:
|
|
%b = add i16 %a, 0
|
|
#dbg_value(i16 %b, !9, !DIExpression(), !11)
|
|
ret i16 0, !dbg !11
|
|
}
|
|
|
|
!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)
|
|
)");
|
|
ASSERT_TRUE(M);
|
|
|
|
Function *F = M->getFunction("foo");
|
|
BasicBlock &BB = F->getEntryBlock();
|
|
// Start with no trailing records.
|
|
ASSERT_FALSE(BB.getTrailingDbgRecords());
|
|
|
|
BasicBlock::iterator Ret = std::prev(BB.end());
|
|
BasicBlock::iterator B = std::prev(Ret);
|
|
|
|
// Delete terminator which has debug records: we now get trailing records.
|
|
Ret->eraseFromParent();
|
|
EXPECT_TRUE(BB.getTrailingDbgRecords());
|
|
|
|
BasicBlock *NewBB = BasicBlock::Create(C, "NewBB", F);
|
|
NewBB->splice(NewBB->end(), &BB, B, BB.end());
|
|
|
|
// The trailing records should've been absorbed into NewBB.
|
|
EXPECT_FALSE(BB.getTrailingDbgRecords());
|
|
EXPECT_TRUE(NewBB->getTrailingDbgRecords());
|
|
if (DbgMarker *Trailing = NewBB->getTrailingDbgRecords()) {
|
|
EXPECT_EQ(llvm::range_size(Trailing->getDbgRecordRange()), 1u);
|
|
// Drop the trailing records now, to prevent a cleanup assertion.
|
|
Trailing->eraseFromParent();
|
|
NewBB->deleteTrailingDbgRecords();
|
|
}
|
|
}
|
|
|
|
} // End anonymous namespace.
|