diff --git a/llvm/lib/IR/DebugInfoMetadata.cpp b/llvm/lib/IR/DebugInfoMetadata.cpp index 12aba7d2bd123..e43f2cf6b0a70 100644 --- a/llvm/lib/IR/DebugInfoMetadata.cpp +++ b/llvm/lib/IR/DebugInfoMetadata.cpp @@ -13,7 +13,7 @@ #include "llvm/IR/DebugInfoMetadata.h" #include "LLVMContextImpl.h" #include "MetadataImpl.h" -#include "llvm/ADT/SmallPtrSet.h" +#include "llvm/ADT/SetVector.h" #include "llvm/ADT/StringSwitch.h" #include "llvm/BinaryFormat/Dwarf.h" #include "llvm/IR/DebugProgramInstruction.h" @@ -125,6 +125,98 @@ DILocation *DILocation::getMergedLocations(ArrayRef Locs) { return Merged; } +static DILexicalBlockBase *cloneAndReplaceParentScope(DILexicalBlockBase *LBB, + DIScope *NewParent) { + TempMDNode ClonedScope = LBB->clone(); + cast(*ClonedScope).replaceScope(NewParent); + return cast( + MDNode::replaceWithUniqued(std::move(ClonedScope))); +} + +using LineColumn = std::pair; + +/// Returns the location of DILocalScope, if present, or a default value. +static LineColumn getLocalScopeLocationOr(DIScope *S, LineColumn Default) { + assert(isa(S) && "Expected DILocalScope."); + + if (isa(S)) + return Default; + if (auto *LB = dyn_cast(S)) + return {LB->getLine(), LB->getColumn()}; + if (auto *SP = dyn_cast(S)) + return {SP->getLine(), 0u}; + + llvm_unreachable("Unhandled type of DILocalScope."); +} + +// Returns the nearest matching scope inside a subprogram. +template +static std::pair +getNearestMatchingScope(const DILocation *L1, const DILocation *L2) { + MatcherT Matcher; + + DIScope *S1 = L1->getScope(); + DIScope *S2 = L2->getScope(); + + LineColumn Loc1(L1->getLine(), L1->getColumn()); + for (; S1; S1 = S1->getScope()) { + Loc1 = getLocalScopeLocationOr(S1, Loc1); + Matcher.insert(S1, Loc1); + if (isa(S1)) + break; + } + + LineColumn Loc2(L2->getLine(), L2->getColumn()); + for (; S2; S2 = S2->getScope()) { + Loc2 = getLocalScopeLocationOr(S2, Loc2); + + if (DIScope *S = Matcher.match(S2, Loc2)) + return std::make_pair(S, Loc2); + + if (isa(S2)) + break; + } + return std::make_pair(nullptr, LineColumn(L2->getLine(), L2->getColumn())); +} + +// Matches equal scopes. +struct EqualScopesMatcher { + SmallPtrSet Scopes; + + void insert(DIScope *S, LineColumn Loc) { Scopes.insert(S); } + + DIScope *match(DIScope *S, LineColumn Loc) { + return Scopes.contains(S) ? S : nullptr; + } +}; + +// Matches scopes with the same location. +struct ScopeLocationsMatcher { + SmallMapVector, SmallSetVector, + 8> + Scopes; + + void insert(DIScope *S, LineColumn Loc) { + Scopes[{S->getFile(), Loc}].insert(S); + } + + DIScope *match(DIScope *S, LineColumn Loc) { + auto ScopesAtLoc = Scopes.find({S->getFile(), Loc}); + // No scope found with the given location. + if (ScopesAtLoc == Scopes.end()) + return nullptr; + + // Prefer S over other scopes with the same location. + if (ScopesAtLoc->second.contains(S)) + return S; + + if (!ScopesAtLoc->second.empty()) + return *ScopesAtLoc->second.begin(); + + llvm_unreachable("Scopes must not have empty entries."); + } +}; + DILocation *DILocation::getMergedLocation(DILocation *LocA, DILocation *LocB) { if (!LocA || !LocB) return nullptr; @@ -208,28 +300,31 @@ DILocation *DILocation::getMergedLocation(DILocation *LocA, DILocation *LocB) { if (L1->getScope()->getSubprogram() != L2->getScope()->getSubprogram()) return nullptr; - // Return the nearest common scope inside a subprogram. - auto GetNearestCommonScope = [](DIScope *S1, DIScope *S2) -> DIScope * { - SmallPtrSet Scopes; - for (; S1; S1 = S1->getScope()) { - Scopes.insert(S1); - if (isa(S1)) - break; - } - - for (; S2; S2 = S2->getScope()) { - if (Scopes.count(S2)) - return S2; - if (isa(S2)) - break; - } - - return nullptr; - }; - - auto Scope = GetNearestCommonScope(L1->getScope(), L2->getScope()); + // Find nearest common scope inside subprogram. + DIScope *Scope = getNearestMatchingScope(L1, L2).first; assert(Scope && "No common scope in the same subprogram?"); + // Try using the nearest scope with common location if files are different. + if (Scope->getFile() != L1->getFile() || L1->getFile() != L2->getFile()) { + auto [CommonLocScope, CommonLoc] = + getNearestMatchingScope(L1, L2); + + // If CommonLocScope is a DILexicalBlockBase, clone it and locate + // a new scope inside the nearest common scope to preserve + // lexical blocks structure. + if (auto *LBB = dyn_cast(CommonLocScope); + LBB && LBB != Scope) + CommonLocScope = cloneAndReplaceParentScope(LBB, Scope); + + Scope = CommonLocScope; + + // If files are still different, assume that L1 and L2 were "included" + // from CommonLoc. Use it as merged location. + if (Scope->getFile() != L1->getFile() || L1->getFile() != L2->getFile()) + return DILocation::get(C, CommonLoc.first, CommonLoc.second, + CommonLocScope, InlinedAt); + } + bool SameLine = L1->getLine() == L2->getLine(); bool SameCol = L1->getColumn() == L2->getColumn(); unsigned Line = SameLine ? L1->getLine() : 0; @@ -1187,10 +1282,8 @@ DILocalScope *DILocalScope::cloneScopeForSubprogram( // cached result). DIScope *UpdatedScope = CachedResult ? CachedResult : &NewSP; for (DIScope *ScopeToUpdate : reverse(ScopeChain)) { - TempMDNode ClonedScope = ScopeToUpdate->clone(); - cast(*ClonedScope).replaceScope(UpdatedScope); - UpdatedScope = - cast(MDNode::replaceWithUniqued(std::move(ClonedScope))); + UpdatedScope = cloneAndReplaceParentScope( + cast(ScopeToUpdate), UpdatedScope); Cache[ScopeToUpdate] = UpdatedScope; } diff --git a/llvm/test/DebugInfo/AArch64/merge-nested-block-loc.ll b/llvm/test/DebugInfo/AArch64/merge-nested-block-loc.ll new file mode 100644 index 0000000000000..bff705bfc1ad4 --- /dev/null +++ b/llvm/test/DebugInfo/AArch64/merge-nested-block-loc.ll @@ -0,0 +1,79 @@ +; RUN: opt -mtriple=aarch64-unknown-linux-gnu -S %s -passes=sroa -o - | FileCheck %s + +; In this test we want to ensure that the merged location of phi instruction +; belongs to the correct scope (DILexicalBlockFile), so that line number +; of that location belongs to the corresponding file. + +; Generated with clang from +; # 1 "1.c" 1 +; # 1 "1.c" 2 +; int foo(int a) { +; int i = 0; +; if ((a & 1) == 1) { +; a -= 1; +; # 1 "m.c" 1 +; # 40 "m.c" +; i += a; +; i -= 10*a; +; i *= a*a; +; # 6 "1.c" 2 +; } else { +; a += 3; +; # 1 "m.c" 1 +; # 40 "m.c" +; i += a; +; i -= 10*a; +; i *= a*a; +; # 9 "1.c" 2 +; } +; return i; +; } + +target datalayout = "e-m:e-p270:32:32-p271:32:32-p272:64:64-i8:8:32-i16:16:32-i64:64-i128:128-n32:64-S128-Fn32" +target triple = "aarch64-unknown-linux-gnu" + +define i32 @foo() !dbg !3 { +; CHECK: phi i32 {{.*}}, !dbg [[PHILOC:![0-9]+]] +; +entry: + %i = alloca i32, align 4 + br i1 false, label %if.then, label %if.else + +if.then: ; preds = %entry + store i32 1, ptr %i, align 4, !dbg !7 + br label %if.end + +if.else: ; preds = %entry + store i32 0, ptr %i, align 4, !dbg !12 + br label %if.end + +if.end: ; preds = %if.else, %if.then + %0 = load i32, ptr %i, align 4 + ret i32 0 +} + +!llvm.dbg.cu = !{!0} +!llvm.module.flags = !{!2} + +!0 = distinct !DICompileUnit(language: DW_LANG_C11, file: !1, producer: "clang version 21.0.0git", isOptimized: true, runtimeVersion: 0, emissionKind: FullDebug, splitDebugInlining: false, nameTableKind: None) +!1 = !DIFile(filename: "repro.c", directory: "") +!2 = !{i32 2, !"Debug Info Version", i32 3} +!3 = distinct !DISubprogram(name: "foo", scope: !4, file: !4, line: 1, type: !5, scopeLine: 1, flags: DIFlagPrototyped | DIFlagAllCallsDescribed, spFlags: DISPFlagDefinition | DISPFlagOptimized, unit: !0, retainedNodes: !6) +!4 = !DIFile(filename: "1.c", directory: "") +!5 = !DISubroutineType(types: !6) +!6 = !{} +!7 = !DILocation(line: 42, column: 3, scope: !8) +!8 = !DILexicalBlockFile(scope: !10, file: !9, discriminator: 0) +!9 = !DIFile(filename: "m.c", directory: "") +!10 = distinct !DILexicalBlock(scope: !11, file: !4, line: 3, column: 21) +!11 = distinct !DILexicalBlock(scope: !3, file: !4, line: 3, column: 7) +!12 = !DILocation(line: 42, column: 3, scope: !13) +!13 = !DILexicalBlockFile(scope: !14, file: !9, discriminator: 0) +!14 = distinct !DILexicalBlock(scope: !11, file: !4, line: 6, column: 9) + +; CHECK: [[SP:![0-9]+]] = distinct !DISubprogram(name: "foo", scope: [[FILE1:![0-9]+]], file: [[FILE1]], line: 1 +; CHECK: [[FILE1]] = !DIFile(filename: "1.c", directory: "") +; CHECK: [[PHILOC]] = !DILocation(line: 42, column: 3, scope: [[LBF:![0-9]+]]) +; CHECK: [[LBF]] = !DILexicalBlockFile(scope: [[LB:![0-9]+]], file: [[FILE2:![0-9]+]], discriminator: 0) +; CHECK: [[FILE2]] = !DIFile(filename: "m.c", directory: "") +; CHECK: [[LB]] = distinct !DILexicalBlock(scope: [[SP]], file: [[FILE1]], line: 3, column: 7) diff --git a/llvm/test/DebugInfo/AArch64/merge-nested-block-loc2.ll b/llvm/test/DebugInfo/AArch64/merge-nested-block-loc2.ll new file mode 100644 index 0000000000000..e5c9a32318b00 --- /dev/null +++ b/llvm/test/DebugInfo/AArch64/merge-nested-block-loc2.ll @@ -0,0 +1,130 @@ +; RUN: opt -mtriple=aarch64-unknown-linux-gnu -S %s -passes=sroa -o - | FileCheck %s + +; In this test we want to ensure that getMergedLocations uses common include +; location if incoming locations belong to different files. +; The location of phi instruction merged from locations of %mul3 and %mul10 +; should be the location of do-loop lexical block from y.c. + +; Generated with clang from +; +; main.c: +; int foo(int a) { +; int i = 0; +; if ((a & 1) == 1) { +; a -= 1; +; #define A +; #include "y.c" +; } else { +; a += 3; +; #undef A +; #include "y.c" +; } +; return i; +; } +; +; y.c: +; # 300 "y.c" 1 +; do { +; #ifdef A +; #include "z1.c" +; #else +; #include "z2.c" +; #endif +; } while (0); +; +; z1.c: +; # 100 "z1.c" 1 +; i += a; +; i -= 10*a; +; i *= a*a; +; +; z2.c: +; # 200 "z1.c" 1 +; i += a; +; i -= 10*a; +; i *= a*a; +; +; Preprocessed source: +; +; # 1 "main.c" +; int foo(int a) { +; int i = 0; +; if ((a & 1) == 1) { +; a -= 1; +; # 300 "y.c" 1 +; do { +; # 100 "z1.c" 1 +; i += a; +; i -= 10*a; +; i *= a*a; +; # 303 "y.c" 2 +; } while (0); +; # 7 "main.c" 2 +; } else { +; a += 3; +; # 300 "y.c" 1 +; do { +; # 200 "z2.c" 1 +; i += a; +; i -= 10*a; +; i *= a*a; +; # 305 "y.c" 2 +; } while (0); +; # 11 "main.c" 2 +; } +; return i; +; } + +target datalayout = "e-m:o-p270:32:32-p271:32:32-p272:64:64-i64:64-i128:128-n32:64-S128-Fn32" +target triple = "arm64-apple-macosx15.0.0" + +define i32 @foo() !dbg !3 { +; CHECK: phi i32 {{.*}}, !dbg [[PHILOC:![0-9]+]] +; +entry: + %i = alloca i32, align 4 + br i1 false, label %do.body, label %if.else + +do.body: ; preds = %entry + store i32 1, ptr %i, align 4, !dbg !6 + br label %if.end + +if.else: ; preds = %entry + store i32 0, ptr %i, align 4, !dbg !14 + br label %if.end + +if.end: ; preds = %if.else, %do.body + %0 = load i32, ptr %i, align 4 + ret i32 0 +} + +!llvm.dbg.cu = !{!0} +!llvm.module.flags = !{!2} + +!0 = distinct !DICompileUnit(language: DW_LANG_C11, file: !1, producer: "clang version 21.0.0git", isOptimized: false, runtimeVersion: 0, emissionKind: FullDebug, splitDebugInlining: false, nameTableKind: Apple, sysroot: "/") +!1 = !DIFile(filename: "main.c", directory: "") +!2 = !{i32 2, !"Debug Info Version", i32 3} +!3 = distinct !DISubprogram(name: "foo", scope: !1, file: !1, line: 1, type: !4, scopeLine: 1, flags: DIFlagPrototyped, spFlags: DISPFlagDefinition, unit: !0, retainedNodes: !5) +!4 = !DISubroutineType(types: !5) +!5 = !{} +!6 = !DILocation(line: 102, column: 3, scope: !7) +!7 = !DILexicalBlockFile(scope: !9, file: !8, discriminator: 0) +!8 = !DIFile(filename: "z1.c", directory: "") +!9 = distinct !DILexicalBlock(scope: !11, file: !10, line: 300, column: 4) +!10 = !DIFile(filename: "y.c", directory: "") +!11 = !DILexicalBlockFile(scope: !12, file: !10, discriminator: 0) +!12 = distinct !DILexicalBlock(scope: !13, file: !1, line: 3, column: 21) +!13 = distinct !DILexicalBlock(scope: !3, file: !1, line: 3, column: 7) +!14 = !DILocation(line: 202, column: 3, scope: !15) +!15 = !DILexicalBlockFile(scope: !17, file: !16, discriminator: 0) +!16 = !DIFile(filename: "z2.c", directory: "") +!17 = distinct !DILexicalBlock(scope: !18, file: !10, line: 300, column: 4) +!18 = !DILexicalBlockFile(scope: !19, file: !10, discriminator: 0) +!19 = distinct !DILexicalBlock(scope: !13, file: !1, line: 7, column: 9) + +; CHECK: [[FILE_MAIN:![0-9]+]] = !DIFile(filename: "main.c" +; CHECK: [[SP:![0-9]+]] = distinct !DISubprogram(name: "foo", scope: [[FILE_MAIN]], file: [[FILE_MAIN]], line: 1 +; CHECK: [[PHILOC]] = !DILocation(line: 300, column: 4, scope: [[BLOCK_Y:![0-9]+]]) +; CHECK-NEXT: [[BLOCK_Y]] = !DILexicalBlock(scope: [[BLOCK_MAIN:![0-9]+]], file: [[FILE_Y:![0-9]+]], line: 300, column: 4) +; CHECK-NEXT: [[FILE_Y]] = !DIFile(filename: "y.c" +; CHECK: [[BLOCK_MAIN]] = distinct !DILexicalBlock(scope: [[SP]], file: [[FILE_MAIN]], line: 3, column: 7) diff --git a/llvm/unittests/IR/MetadataTest.cpp b/llvm/unittests/IR/MetadataTest.cpp index 94cebb0406598..e710f7845df5e 100644 --- a/llvm/unittests/IR/MetadataTest.cpp +++ b/llvm/unittests/IR/MetadataTest.cpp @@ -85,10 +85,10 @@ class MetadataTest : public testing::Test { return DISubroutineType::getDistinct(Context, DINode::FlagZero, 0, getNode(nullptr)); } - DISubprogram *getSubprogram() { - return DISubprogram::getDistinct( - Context, nullptr, "", "", nullptr, 0, nullptr, 0, nullptr, 0, 0, - DINode::FlagZero, DISubprogram::SPFlagZero, nullptr); + DISubprogram *getSubprogram(DIFile *F = nullptr) { + return DISubprogram::getDistinct(Context, nullptr, "", "", F, 0, nullptr, 0, + nullptr, 0, 0, DINode::FlagZero, + DISubprogram::SPFlagZero, nullptr); } DIFile *getFile() { return DIFile::getDistinct(Context, "file.c", "/path/to/dir"); @@ -919,8 +919,9 @@ TEST_F(MDNodeTest, deleteTemporaryWithTrackingRef) { typedef MetadataTest DILocationTest; TEST_F(DILocationTest, Merge) { - DISubprogram *N = getSubprogram(); - DIScope *S = DILexicalBlock::get(Context, N, getFile(), 3, 4); + DIFile *F = getFile(); + DISubprogram *N = getSubprogram(F); + DIScope *S = DILexicalBlock::get(Context, N, F, 3, 4); { // Identical. @@ -932,6 +933,18 @@ TEST_F(DILocationTest, Merge) { EXPECT_EQ(N, M->getScope()); } + { + // Identical, inside DILexicalBlockFile. + auto *OtherF = DIFile::getDistinct(Context, "file1.c", "/path/to/dir"); + auto *LBF = DILexicalBlockFile::get(Context, S, OtherF, 0); + auto *A = DILocation::get(Context, 2, 7, LBF); + auto *B = DILocation::get(Context, 2, 7, LBF); + auto *M = DILocation::getMergedLocation(A, B); + EXPECT_EQ(2u, M->getLine()); + EXPECT_EQ(7u, M->getColumn()); + EXPECT_EQ(LBF, M->getScope()); + } + { // Identical, different scopes. auto *A = DILocation::get(Context, 2, 7, N); @@ -956,7 +969,22 @@ TEST_F(DILocationTest, Merge) { } { - // Different lines, same scopes. + // Same line, different column, same DILexicalBlockFile scope. + auto *OtherF = DIFile::getDistinct(Context, "file1.c", "/path/to/dir"); + auto *LBF = DILexicalBlockFile::get(Context, S, OtherF, 0); + auto *A = DILocation::get(Context, 2, 7, LBF); + auto *B = DILocation::get(Context, 2, 10, LBF); + auto *M0 = DILocation::getMergedLocation(A, B); + auto *M1 = DILocation::getMergedLocation(B, A); + for (auto *M : {M0, M1}) { + EXPECT_EQ(2u, M->getLine()); + EXPECT_EQ(0u, M->getColumn()); + EXPECT_EQ(LBF, M->getScope()); + } + } + + { + // Different lines, same DISubprogram scopes. auto *A = DILocation::get(Context, 1, 6, N); auto *B = DILocation::get(Context, 2, 7, N); auto *M = DILocation::getMergedLocation(A, B); @@ -965,6 +993,28 @@ TEST_F(DILocationTest, Merge) { EXPECT_EQ(N, M->getScope()); } + { + // Different lines, same DILexicalBlockFile scopes. + auto *OtherF = DIFile::getDistinct(Context, "file1.c", "/path/to/dir"); + auto *LBF = DILexicalBlockFile::get(Context, S, OtherF, 0); + auto *A = DILocation::get(Context, 1, 6, LBF); + auto *B = DILocation::get(Context, 2, 7, LBF); + auto *M = DILocation::getMergedLocation(A, B); + EXPECT_EQ(0u, M->getLine()); + EXPECT_EQ(0u, M->getColumn()); + EXPECT_EQ(LBF, M->getScope()); + } + + { + // Different lines, same DILexicalBlock scopes. + auto *A = DILocation::get(Context, 1, 6, S); + auto *B = DILocation::get(Context, 2, 7, S); + auto *M = DILocation::getMergedLocation(A, B); + EXPECT_EQ(0u, M->getLine()); + EXPECT_EQ(0u, M->getColumn()); + EXPECT_EQ(S, M->getScope()); + } + { // Twisty locations, all different, same function. auto *A = DILocation::get(Context, 1, 6, N); @@ -975,6 +1025,158 @@ TEST_F(DILocationTest, Merge) { EXPECT_EQ(N, M->getScope()); } + { + // Different files, same line numbers, same subprogram. + auto *F1 = DIFile::getDistinct(Context, "file1.c", "/path/to/dir"); + auto *F2 = DIFile::getDistinct(Context, "file2.c", "/path/to/dir"); + DISubprogram *N = getSubprogram(F1); + auto *LBF = DILexicalBlockFile::get(Context, N, F2, 0); + auto *A = DILocation::get(Context, 1, 6, N); + auto *B = DILocation::get(Context, 1, 6, LBF); + auto *M = DILocation::getMergedLocation(A, B); + EXPECT_EQ(0u, M->getLine()); + EXPECT_EQ(0u, M->getColumn()); + EXPECT_EQ(N, M->getScope()); + } + + { + // Different files, same line numbers. + auto *F1 = DIFile::getDistinct(Context, "file1.c", "/path/to/dir"); + auto *F2 = DIFile::getDistinct(Context, "file2.c", "/path/to/dir"); + DISubprogram *N = getSubprogram(F1); + auto *LB = DILexicalBlock::getDistinct(Context, N, F1, 4, 9); + auto *LBF = DILexicalBlockFile::get(Context, LB, F2, 0); + auto *A = DILocation::get(Context, 1, 6, LB); + auto *B = DILocation::get(Context, 1, 6, LBF); + auto *M = DILocation::getMergedLocation(A, B); + EXPECT_EQ(4u, M->getLine()); + EXPECT_EQ(9u, M->getColumn()); + EXPECT_EQ(LB, M->getScope()); + } + + { + // Different files, same line numbers, + // both locations have DILexicalBlockFile scopes. + auto *F1 = DIFile::getDistinct(Context, "file1.c", "/path/to/dir"); + auto *F2 = DIFile::getDistinct(Context, "file2.c", "/path/to/dir"); + auto *F3 = DIFile::getDistinct(Context, "file3.c", "/path/to/dir"); + DISubprogram *N = getSubprogram(F1); + auto *LB = DILexicalBlock::getDistinct(Context, N, F1, 4, 9); + auto *LBF1 = DILexicalBlockFile::get(Context, LB, F2, 0); + auto *LBF2 = DILexicalBlockFile::get(Context, LB, F3, 0); + auto *A = DILocation::get(Context, 1, 6, LBF1); + auto *B = DILocation::get(Context, 1, 6, LBF2); + auto *M = DILocation::getMergedLocation(A, B); + EXPECT_EQ(4u, M->getLine()); + EXPECT_EQ(9u, M->getColumn()); + EXPECT_EQ(LB, M->getScope()); + } + + { + // Same file, same line numbers, but different LBF objects. + // both locations have DILexicalBlockFile scope. + auto *F1 = DIFile::getDistinct(Context, "file1.c", "/path/to/dir"); + DISubprogram *N = getSubprogram(F1); + auto *LB1 = DILexicalBlock::getDistinct(Context, N, F1, 4, 9); + auto *LB2 = DILexicalBlock::getDistinct(Context, N, F1, 5, 9); + auto *F2 = DIFile::getDistinct(Context, "file2.c", "/path/to/dir"); + auto *LBF1 = DILexicalBlockFile::get(Context, LB1, F2, 0); + auto *LBF2 = DILexicalBlockFile::get(Context, LB2, F2, 0); + auto *A = DILocation::get(Context, 1, 6, LBF1); + auto *B = DILocation::get(Context, 1, 6, LBF2); + auto *M = DILocation::getMergedLocation(A, B); + EXPECT_EQ(1u, M->getLine()); + EXPECT_EQ(6u, M->getColumn()); + EXPECT_EQ(LBF1->getFile(), M->getScope()->getFile()); + EXPECT_EQ(N, M->getScope()->getScope()); + } + + { + // Merge locations A and B, where B is included in A's file + // at the same position as A's position. + auto *F1 = DIFile::getDistinct(Context, "file1.c", "/path/to/dir"); + DISubprogram *N = getSubprogram(F1); + auto *LB = DILexicalBlock::getDistinct(Context, N, F1, 4, 9); + auto *F2 = DIFile::getDistinct(Context, "file2.c", "/path/to/dir"); + auto *LBF = DILexicalBlockFile::get(Context, LB, F2, 0); + auto *A = DILocation::get(Context, 4, 9, LB); + auto *B = DILocation::get(Context, 1, 6, LBF); + auto *M = DILocation::getMergedLocation(A, B); + EXPECT_EQ(4u, M->getLine()); + EXPECT_EQ(9u, M->getColumn()); + EXPECT_EQ(LB, M->getScope()); + } + + { + // Different locations from different files included from the same block. + auto *F1 = DIFile::getDistinct(Context, "file1.c", "/path/to/dir"); + DISubprogram *N = getSubprogram(F1); + + auto *LBCommon = DILexicalBlock::getDistinct(Context, N, F1, 4, 9); + + auto *F2 = DIFile::getDistinct(Context, "file2.c", "/path/to/dir"); + LBCommon = DILexicalBlock::getDistinct(Context, LBCommon, F2, 5, 9); + + auto *F3 = DIFile::getDistinct(Context, "file3.c", "/path/to/dir"); + auto *LB1 = DILexicalBlock::getDistinct(Context, LBCommon, F3, 6, 9); + + auto *F4 = DIFile::getDistinct(Context, "file4.c", "/path/to/dir"); + auto *LB2 = DILexicalBlock::getDistinct(Context, LBCommon, F4, 7, 9); + + auto *F5 = DIFile::getDistinct(Context, "file5.c", "/path/to/dir"); + auto *LBF1 = DILexicalBlockFile::get(Context, LB1, F5, 0); + + auto *A = DILocation::get(Context, 8, 9, LB2); + auto *B = DILocation::get(Context, 9, 6, LBF1); + auto *M = DILocation::getMergedLocation(A, B); + EXPECT_EQ(5u, M->getLine()); + EXPECT_EQ(9u, M->getColumn()); + EXPECT_EQ(LBCommon, M->getScope()); + } + + { + // Different locations from different files having common include parent. + auto *LBCommon = DILexicalBlock::getDistinct(Context, N, F, 4, 1); + + // Different scopes. + DILexicalBlock *Block[2] = {}; + Block[0] = DILexicalBlock::get(Context, LBCommon, F, 10, 2); + Block[1] = DILexicalBlock::get(Context, LBCommon, F, 20, 3); + + // Includes of the same file. + auto *F1 = DIFile::getDistinct(Context, "file1.inc", "/path/to/dir"); + DILexicalBlock *Block1[2] = {}; + Block1[0] = DILexicalBlock::get( + Context, DILexicalBlockFile::get(Context, Block[0], F1, 0), F1, 30, 4); + Block1[1] = DILexicalBlock::get( + Context, DILexicalBlockFile::get(Context, Block[1], F1, 0), F1, 30, 4); + + // Different sub-includes. + DIFile *F2[2] = {}; + DILexicalBlock *Block2[2] = {}; + + F2[0] = DIFile::getDistinct(Context, "file2_a.inc", "/path/to/dir"); + Block2[0] = DILexicalBlock::get( + Context, DILexicalBlockFile::get(Context, Block1[0], F2[0], 0), F2[0], + 40, 5); + + F2[1] = DIFile::getDistinct(Context, "file2_b.inc", "/path/to/dir"); + Block2[1] = DILexicalBlock::get( + Context, DILexicalBlockFile::get(Context, Block1[1], F2[1], 0), F2[1], + 50, 6); + + auto *A = DILocation::get(Context, 41, 7, Block2[0]); + auto *B = DILocation::get(Context, 51, 8, Block2[1]); + auto *M = DILocation::getMergedLocation(A, B); + auto *MScope = dyn_cast(M->getScope()); + EXPECT_EQ(30u, M->getLine()); + EXPECT_EQ(4u, M->getColumn()); + EXPECT_EQ(Block1[0]->getFile(), MScope->getFile()); + EXPECT_EQ(Block1[0]->getLine(), MScope->getLine()); + EXPECT_EQ(Block1[0]->getColumn(), MScope->getColumn()); + EXPECT_EQ(LBCommon, MScope->getScope()); + } + { // Different function, same inlined-at. auto *F = getFile();