699 changes: 699 additions & 0 deletions lldb/test/Shell/SymbolFile/DWARF/Inputs/inlined-file0-line0-col0.yaml

Large diffs are not rendered by default.

34 changes: 34 additions & 0 deletions lldb/test/Shell/SymbolFile/DWARF/inlined-file0-line0-col0.test
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
# RUN: yaml2obj %S/Inputs/inlined-file0-line0-col0.yaml -o %t
# RUN: %lldb %t -s %s -o exit | FileCheck %s

# 1 void abort(void);
# 2 int g1 = 4, g2 = 6;
# 3
# 4 inline __attribute__((always_inline)) void bar(int q) {
# 5 if (q > 5)
# 6 abort();
# 7 }
# 8
# 9 inline __attribute__((always_inline)) void foo(int q) {
# 10 bar(q);
# 11 }
# 12
# 13 int main() {
# 14 foo(g1);
# 15 foo(g2);
# 16 return 0;
# 17 }

# The input object file contains a single abort invocation for the two inlined
# instances of foo() in main() at line 0. As the file, line and column numbers
# are all 0, file and line number information would be missing for foo and main
# in the lookup information.
#
# A line number 0 is not printed for main in this case, but the same holds
# for a non-inlined location with line number 0.

# CHECK: Summary: inlined-file0-line0-col0.test.tmp`main + 30 [inlined] bar + 4 at inlined-file0-line0-col0.c:6:5
# CHECK-NEXT: inlined-file0-line0-col0.test.tmp`main + 26 [inlined] foo at inlined-file0-line0-col0.c:10:3
# CHECK-NEXT: inlined-file0-line0-col0.test.tmp`main + 26 at inlined-file0-line0-col0.c

image lookup -a 0x1e
160 changes: 110 additions & 50 deletions llvm/lib/IR/DebugInfoMetadata.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
#include "llvm/IR/DebugInfoMetadata.h"
#include "LLVMContextImpl.h"
#include "MetadataImpl.h"
#include "llvm/ADT/SmallPtrSet.h"
#include "llvm/ADT/SmallSet.h"
#include "llvm/ADT/StringSwitch.h"
#include "llvm/BinaryFormat/Dwarf.h"
Expand Down Expand Up @@ -114,63 +115,122 @@ const DILocation *DILocation::getMergedLocation(const DILocation *LocA,
return LocA;

LLVMContext &C = LocA->getContext();
SmallDenseMap<std::pair<DILocalScope *, DILocation *>,
std::pair<unsigned, unsigned>, 4>
Locations;

DIScope *S = LocA->getScope();
DILocation *L = LocA->getInlinedAt();
unsigned Line = LocA->getLine();
unsigned Col = LocA->getColumn();

// Walk from the current source locaiton until the file scope;
// then, do the same for the inlined-at locations.
auto AdvanceToParentLoc = [&S, &L, &Line, &Col]() {
S = S->getScope();
if (!S && L) {
Line = L->getLine();
Col = L->getColumn();
S = L->getScope();
L = L->getInlinedAt();
}
};

while (S) {
if (auto *LS = dyn_cast<DILocalScope>(S))
Locations.try_emplace(std::make_pair(LS, L), std::make_pair(Line, Col));
AdvanceToParentLoc();
using LocVec = SmallVector<const DILocation *>;
LocVec ALocs;
LocVec BLocs;
SmallDenseMap<std::pair<const DISubprogram *, const DILocation *>, unsigned,
4>
ALookup;

// Walk through LocA and its inlined-at locations, populate them in ALocs and
// save the index for the subprogram and inlined-at pair, which we use to find
// a matching starting location in LocB's chain.
for (auto [L, I] = std::make_pair(LocA, 0U); L; L = L->getInlinedAt(), I++) {
ALocs.push_back(L);
auto Res = ALookup.try_emplace(
{L->getScope()->getSubprogram(), L->getInlinedAt()}, I);
assert(Res.second && "Multiple <SP, InlinedAt> pairs in a location chain?");
(void)Res;
}

// Walk the source locations of LocB until a match with LocA is found.
S = LocB->getScope();
L = LocB->getInlinedAt();
Line = LocB->getLine();
Col = LocB->getColumn();
while (S) {
if (auto *LS = dyn_cast<DILocalScope>(S)) {
auto MatchLoc = Locations.find(std::make_pair(LS, L));
if (MatchLoc != Locations.end()) {
// If the lines match, keep the line, but set the column to '0'
// If the lines don't match, pick a "line 0" location but keep
// the current scope and inlined-at.
bool SameLine = Line == MatchLoc->second.first;
bool SameCol = Col == MatchLoc->second.second;
Line = SameLine ? Line : 0;
Col = SameLine && SameCol ? Col : 0;
break;
}
}
AdvanceToParentLoc();
LocVec::reverse_iterator ARIt = ALocs.rend();
LocVec::reverse_iterator BRIt = BLocs.rend();

// Populate BLocs and look for a matching starting location, the first
// location with the same subprogram and inlined-at location as in LocA's
// chain. Since the two locations have the same inlined-at location we do
// not need to look at those parts of the chains.
for (auto [L, I] = std::make_pair(LocB, 0U); L; L = L->getInlinedAt(), I++) {
BLocs.push_back(L);

if (ARIt != ALocs.rend())
// We have already found a matching starting location.
continue;

auto IT = ALookup.find({L->getScope()->getSubprogram(), L->getInlinedAt()});
if (IT == ALookup.end())
continue;

// The + 1 is to account for the &*rev_it = &(it - 1) relationship.
ARIt = LocVec::reverse_iterator(ALocs.begin() + IT->second + 1);
BRIt = LocVec::reverse_iterator(BLocs.begin() + I + 1);

// If we have found a matching starting location we do not need to add more
// locations to BLocs, since we will only look at location pairs preceding
// the matching starting location, and adding more elements to BLocs could
// invalidate the iterator that we initialized here.
break;
}

if (!S) {
// If the two locations are irreconsilable, pick any scope,
// and return a "line 0" location.
Line = Col = 0;
S = LocA->getScope();
// Merge the two locations if possible, using the supplied
// inlined-at location for the created location.
auto MergeLocPair = [&C](const DILocation *L1, const DILocation *L2,
DILocation *InlinedAt) -> DILocation * {
if (L1 == L2)
return DILocation::get(C, L1->getLine(), L1->getColumn(), L1->getScope(),
InlinedAt);

// If the locations originate from different subprograms we can't produce
// a common location.
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<DIScope *, 8> Scopes;
for (; S1; S1 = S1->getScope()) {
Scopes.insert(S1);
if (isa<DISubprogram>(S1))
break;
}

for (; S2; S2 = S2->getScope()) {
if (Scopes.count(S2))
return S2;
if (isa<DISubprogram>(S2))
break;
}

return nullptr;
};

auto Scope = GetNearestCommonScope(L1->getScope(), L2->getScope());
assert(Scope && "No common scope in the same subprogram?");

bool SameLine = L1->getLine() == L2->getLine();
bool SameCol = L1->getColumn() == L2->getColumn();
unsigned Line = SameLine ? L1->getLine() : 0;
unsigned Col = SameLine && SameCol ? L1->getColumn() : 0;

return DILocation::get(C, Line, Col, Scope, InlinedAt);
};

DILocation *Result = ARIt != ALocs.rend() ? (*ARIt)->getInlinedAt() : nullptr;

// If we have found a common starting location, walk up the inlined-at chains
// and try to produce common locations.
for (; ARIt != ALocs.rend() && BRIt != BLocs.rend(); ++ARIt, ++BRIt) {
DILocation *Tmp = MergeLocPair(*ARIt, *BRIt, Result);

if (!Tmp)
// We have walked up to a point in the chains where the two locations
// are irreconsilable. At this point Result contains the nearest common
// location in the inlined-at chains of LocA and LocB, so we break here.
break;

Result = Tmp;
}

return DILocation::get(C, Line, Col, S, L);
if (Result)
return Result;

// We ended up with LocA and LocB as irreconsilable locations. Produce a
// location at 0:0 with one of the locations' scope. The function has
// historically picked A's scope, and a nullptr inlined-at location, so that
// behavior is mimicked here but I am not sure if this is always the correct
// way to handle this.
return DILocation::get(C, 0, 0, LocA->getScope(), nullptr);
}

std::optional<unsigned>
Expand Down
222 changes: 222 additions & 0 deletions llvm/test/DebugInfo/MIR/X86/merge-inline-loc1.mir
Original file line number Diff line number Diff line change
@@ -0,0 +1,222 @@
# RUN: llc -mtriple=x86_64-pc-linux %s -run-pass=branch-folder -o - | FileCheck %s

--- |
; ModuleID = 'case1.c'
source_filename = "case1.c"
target datalayout = "e-m:e-p270:32:32-p271:32:32-p272:64:64-i64:64-f80:128-n8:16:32:64-S128"
target triple = "x86_64-unknown-linux-gnu"

@q1 = dso_local local_unnamed_addr global i32 1, align 4
@q2 = dso_local local_unnamed_addr global i32 4, align 4
@q3 = dso_local local_unnamed_addr global i32 2, align 4
@g1 = dso_local local_unnamed_addr global i32 0, align 4
@g2 = dso_local local_unnamed_addr global i32 0, align 4
@g3 = dso_local local_unnamed_addr global i32 0, align 4

; Function Attrs: nounwind uwtable
define dso_local i32 @multiple_inl_one_loc() local_unnamed_addr #0 !dbg !9 {
entry:
%0 = load i32, ptr @q1, align 4, !dbg !12, !tbaa !13
%cmp.i = icmp sgt i32 %0, 3, !dbg !17
br i1 %cmp.i, label %if.then.i, label %inl1.exit, !dbg !20

if.then.i: ; preds = %entry
tail call void @abort() #2, !dbg !21
unreachable, !dbg !21

inl1.exit: ; preds = %entry
%mul.i = mul nsw i32 %0, 152, !dbg !22
%add.i = add nsw i32 %mul.i, 100, !dbg !23
store i32 %add.i, ptr @g1, align 4, !dbg !24, !tbaa !13
%1 = load i32, ptr @q2, align 4, !dbg !25, !tbaa !13
%cmp.i3 = icmp sgt i32 %1, 3, !dbg !26
br i1 %cmp.i3, label %if.then.i4, label %inl1.exit7, !dbg !28

if.then.i4: ; preds = %inl1.exit
tail call void @abort() #2, !dbg !29
unreachable, !dbg !29

inl1.exit7: ; preds = %inl1.exit
%mul.i5 = mul nsw i32 %1, 152, !dbg !30
%add.i6 = add nsw i32 %mul.i5, 200, !dbg !31
store i32 %add.i6, ptr @g2, align 4, !dbg !32, !tbaa !13
%2 = load i32, ptr @q3, align 4, !dbg !33, !tbaa !13
%cmp.i8 = icmp sgt i32 %2, 3, !dbg !34
br i1 %cmp.i8, label %if.then.i9, label %inl1.exit12, !dbg !36

if.then.i9: ; preds = %inl1.exit7
tail call void @abort() #2, !dbg !37
unreachable, !dbg !37

inl1.exit12: ; preds = %inl1.exit7
%mul.i10 = mul nsw i32 %2, 152, !dbg !38
%add.i11 = add nsw i32 %mul.i10, 300, !dbg !39
store i32 %add.i11, ptr @q3, align 4, !dbg !40, !tbaa !13
ret i32 0, !dbg !41
}

; Function Attrs: noreturn nounwind
declare !dbg !42 void @abort() local_unnamed_addr #1

attributes #0 = { nounwind uwtable "frame-pointer"="none" "min-legal-vector-width"="0" "no-trapping-math"="true" "stack-protector-buffer-size"="8" "target-cpu"="x86-64" "target-features"="+cx8,+fxsr,+mmx,+sse,+sse2,+x87" "tune-cpu"="generic" }
attributes #1 = { noreturn nounwind "frame-pointer"="none" "no-trapping-math"="true" "stack-protector-buffer-size"="8" "target-cpu"="x86-64" "target-features"="+cx8,+fxsr,+mmx,+sse,+sse2,+x87" "tune-cpu"="generic" }
attributes #2 = { noreturn nounwind }

!llvm.dbg.cu = !{!0}
!llvm.module.flags = !{!2, !3, !4, !5, !6, !7}
!llvm.ident = !{!8}

!0 = distinct !DICompileUnit(language: DW_LANG_C11, file: !1, producer: "clang version 16.0.0.prerel", isOptimized: true, runtimeVersion: 0, emissionKind: LineTablesOnly, splitDebugInlining: false, nameTableKind: None)
!1 = !DIFile(filename: "case1.c", directory: "/", checksumkind: CSK_MD5, checksum: "36d7da9616644fc8b011f5c49108ab31")
!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 = !{!"clang version 16.0.0.prerel"}
!9 = distinct !DISubprogram(name: "multiple_inl_one_loc", scope: !1, file: !1, line: 13, type: !10, scopeLine: 14, flags: DIFlagAllCallsDescribed, spFlags: DISPFlagDefinition | DISPFlagOptimized, unit: !0, retainedNodes: !11)
!10 = !DISubroutineType(types: !11)
!11 = !{}
!12 = !DILocation(line: 15, column: 13, scope: !9)
!13 = !{!14, !14, i64 0}
!14 = !{!"int", !15, i64 0}
!15 = !{!"omnipotent char", !16, i64 0}
!16 = !{!"Simple C/C++ TBAA"}
!17 = !DILocation(line: 5, column: 9, scope: !18, inlinedAt: !19)
!18 = distinct !DISubprogram(name: "inl1", scope: !1, file: !1, line: 3, type: !10, scopeLine: 4, flags: DIFlagPrototyped | DIFlagAllCallsDescribed, spFlags: DISPFlagLocalToUnit | DISPFlagDefinition | DISPFlagOptimized, unit: !0, retainedNodes: !11)
!19 = distinct !DILocation(line: 15, column: 8, scope: !9)
!20 = !DILocation(line: 5, column: 7, scope: !18, inlinedAt: !19)
!21 = !DILocation(line: 6, column: 5, scope: !18, inlinedAt: !19)
!22 = !DILocation(line: 7, column: 12, scope: !18, inlinedAt: !19)
!23 = !DILocation(line: 7, column: 18, scope: !18, inlinedAt: !19)
!24 = !DILocation(line: 15, column: 6, scope: !9)
!25 = !DILocation(line: 16, column: 13, scope: !9)
!26 = !DILocation(line: 5, column: 9, scope: !18, inlinedAt: !27)
!27 = distinct !DILocation(line: 16, column: 8, scope: !9)
!28 = !DILocation(line: 5, column: 7, scope: !18, inlinedAt: !27)
!29 = !DILocation(line: 6, column: 5, scope: !18, inlinedAt: !27)
!30 = !DILocation(line: 7, column: 12, scope: !18, inlinedAt: !27)
!31 = !DILocation(line: 7, column: 18, scope: !18, inlinedAt: !27)
!32 = !DILocation(line: 16, column: 6, scope: !9)
!33 = !DILocation(line: 17, column: 13, scope: !9)
!34 = !DILocation(line: 5, column: 9, scope: !18, inlinedAt: !35)
!35 = distinct !DILocation(line: 17, column: 8, scope: !9)
!36 = !DILocation(line: 5, column: 7, scope: !18, inlinedAt: !35)
!37 = !DILocation(line: 6, column: 5, scope: !18, inlinedAt: !35)
!38 = !DILocation(line: 7, column: 12, scope: !18, inlinedAt: !35)
!39 = !DILocation(line: 7, column: 18, scope: !18, inlinedAt: !35)
!40 = !DILocation(line: 17, column: 6, scope: !9)
!41 = !DILocation(line: 18, column: 3, scope: !9)
!42 = !DISubprogram(name: "abort", scope: !43, file: !43, line: 514, type: !10, flags: DIFlagPrototyped, spFlags: DISPFlagOptimized, retainedNodes: !11)
!43 = !DIFile(filename: "/usr/include/stdlib.h", directory: "", checksumkind: CSK_MD5, checksum: "f7a1412d75d9e3df251dfc21b02d59ef")

...
---
name: multiple_inl_one_loc
alignment: 16
tracksRegLiveness: true
tracksDebugUserValues: true
frameInfo:
stackSize: 8
offsetAdjustment: -8
maxAlignment: 1
adjustsStack: true
hasCalls: true
maxCallFrameSize: 0
machineFunctionInfo: {}
body: |
bb.0.entry:
successors: %bb.1(0x00000800), %bb.2(0x7ffff800)
frame-setup PUSH64r undef $rax, implicit-def $rsp, implicit $rsp
frame-setup CFI_INSTRUCTION def_cfa_offset 16
renamable $eax = MOV32rm $rip, 1, $noreg, @q1, $noreg, debug-location !12 :: (dereferenceable load (s32) from @q1, !tbaa !13)
CMP32ri8 renamable $eax, 4, implicit-def $eflags, debug-location !17
JCC_1 %bb.2, 12, implicit killed $eflags, debug-location !20
JMP_1 %bb.1, debug-location !20
bb.1.if.then.i:
successors:
CALL64pcrel32 target-flags(x86-plt) @abort, csr_64, implicit $rsp, implicit $ssp, implicit-def $rsp, implicit-def $ssp, debug-location !21
bb.2.inl1.exit:
successors: %bb.3(0x00000800), %bb.4(0x7ffff800)
liveins: $eax
renamable $eax = nsw IMUL32rri killed renamable $eax, 152, implicit-def dead $eflags, debug-location !22
renamable $eax = nsw ADD32ri8 killed renamable $eax, 100, implicit-def dead $eflags, debug-location !23
MOV32mr $rip, 1, $noreg, @g1, $noreg, killed renamable $eax, debug-location !24 :: (store (s32) into @g1, !tbaa !13)
renamable $eax = MOV32rm $rip, 1, $noreg, @q2, $noreg, debug-location !25 :: (dereferenceable load (s32) from @q2, !tbaa !13)
CMP32ri8 renamable $eax, 4, implicit-def $eflags, debug-location !26
JCC_1 %bb.4, 12, implicit killed $eflags, debug-location !28
JMP_1 %bb.3, debug-location !28
bb.3.if.then.i4:
successors:
CALL64pcrel32 target-flags(x86-plt) @abort, csr_64, implicit $rsp, implicit $ssp, implicit-def $rsp, implicit-def $ssp, debug-location !29
bb.4.inl1.exit7:
successors: %bb.5(0x00000800), %bb.6(0x7ffff800)
liveins: $eax
renamable $eax = nsw IMUL32rri killed renamable $eax, 152, implicit-def dead $eflags, debug-location !30
renamable $eax = nsw ADD32ri killed renamable $eax, 200, implicit-def dead $eflags, debug-location !31
MOV32mr $rip, 1, $noreg, @g2, $noreg, killed renamable $eax, debug-location !32 :: (store (s32) into @g2, !tbaa !13)
renamable $eax = MOV32rm $rip, 1, $noreg, @q3, $noreg, debug-location !33 :: (dereferenceable load (s32) from @q3, !tbaa !13)
CMP32ri8 renamable $eax, 4, implicit-def $eflags, debug-location !34
JCC_1 %bb.6, 12, implicit killed $eflags, debug-location !36
JMP_1 %bb.5, debug-location !36
bb.5.if.then.i9:
successors:
CALL64pcrel32 target-flags(x86-plt) @abort, csr_64, implicit $rsp, implicit $ssp, implicit-def $rsp, implicit-def $ssp, debug-location !37
bb.6.inl1.exit12:
liveins: $eax
renamable $eax = nsw IMUL32rri killed renamable $eax, 152, implicit-def dead $eflags, debug-location !38
renamable $eax = nsw ADD32ri killed renamable $eax, 300, implicit-def dead $eflags, debug-location !39
MOV32mr $rip, 1, $noreg, @q3, $noreg, killed renamable $eax, debug-location !40 :: (store (s32) into @q3, !tbaa !13)
$eax = MOV32r0 implicit-def dead $eflags, debug-location !41
$rcx = frame-destroy POP64r implicit-def $rsp, implicit $rsp, debug-location !41
frame-destroy CFI_INSTRUCTION def_cfa_offset 8, debug-location !41
RET 0, $eax, debug-location !41
...

# In this case we have a abort call block folded from one single location in
# three inlined instances of inl1():
#
# 1 | #include <stdlib.h>
# 2 |
# 3 | static inline int inl1(int q, int n)
# 4 | {
# 5 | if (q > 3)
# 6 | abort();
# 7 | return q * 152 + n;
# 8 | }
# 9 |
# 10 | int q1 = 1, q2 = 4, q3 = 2;
# 11 | int g1, g2, g3;
# 12 |
# 13 | int multiple_inl_one_loc()
# 14 | {
# 15 | g1 = inl1(q1, 100);
# 16 | g2 = inl1(q2, 200);
# 17 | q3 = inl1(q3, 300);
# 18 | return 0;
# 19 | }
#
# We should produce a merged location describing that the abort call is located
# at line 6 in inl1() inlined at line 0.

# CHECK-DAG: [[INLINER:![0-9]+]] = distinct !DISubprogram(name: "multiple_inl_one_loc"
# CHECK-DAG: [[INLINEE:![0-9]+]] = distinct !DISubprogram(name: "inl1"

# CHECK-NOT: CALL64pcrel32
# CHECK: CALL64pcrel32 target-flags(x86-plt) @abort, {{.*}} debug-location !DILocation(line: 6, column: 5, scope: [[INLINEE]], inlinedAt: !DILocation(line: 0, scope: [[INLINER]]))
# CHECK-NOT: CALL64pcrel32
339 changes: 339 additions & 0 deletions llvm/test/DebugInfo/MIR/X86/merge-inline-loc2.mir

Large diffs are not rendered by default.

187 changes: 187 additions & 0 deletions llvm/test/DebugInfo/MIR/X86/merge-inline-loc3.mir
Original file line number Diff line number Diff line change
@@ -0,0 +1,187 @@
# RUN: llc -mtriple=x86_64-pc-linux %s -run-pass=branch-folder -o - | FileCheck %s

--- |
; ModuleID = 'case3.c'
source_filename = "case3.c"
target datalayout = "e-m:e-p270:32:32-p271:32:32-p272:64:64-i64:64-f80:128-n8:16:32:64-S128"
target triple = "x86_64-unknown-linux-gnu"

@q1 = dso_local local_unnamed_addr global i32 1, align 4
@q2 = dso_local local_unnamed_addr global i32 6, align 4
@g1 = dso_local local_unnamed_addr global i32 0, align 4
@g2 = dso_local local_unnamed_addr global i32 0, align 4

; Function Attrs: nounwind uwtable
define dso_local i32 @multiple_inl_funcs() local_unnamed_addr #0 !dbg !9 {
entry:
%0 = load i32, ptr @q1, align 4, !dbg !12, !tbaa !13
%cmp.i = icmp sgt i32 %0, 3, !dbg !17
br i1 %cmp.i, label %if.then.i, label %inl3.exit, !dbg !20

if.then.i: ; preds = %entry
tail call void @abort() #2, !dbg !21
unreachable, !dbg !21

inl3.exit: ; preds = %entry
%mul.i = mul nsw i32 %0, 152, !dbg !22
%add.i = add nsw i32 %mul.i, 100, !dbg !23
store i32 %add.i, ptr @g1, align 4, !dbg !24, !tbaa !13
%1 = load i32, ptr @q2, align 4, !dbg !25, !tbaa !13
%cmp.i2 = icmp sgt i32 %1, 5, !dbg !26
br i1 %cmp.i2, label %if.then.i3, label %inl4.exit, !dbg !29

if.then.i3: ; preds = %inl3.exit
tail call void @abort() #2, !dbg !30
unreachable, !dbg !30

inl4.exit: ; preds = %inl3.exit
%mul.i4 = mul nsw i32 %1, %1, !dbg !31
%add.i5 = add nuw nsw i32 %mul.i4, 200, !dbg !32
store i32 %add.i5, ptr @g2, align 4, !dbg !33, !tbaa !13
ret i32 0, !dbg !34
}

; Function Attrs: noreturn nounwind
declare !dbg !35 void @abort() local_unnamed_addr #1

attributes #0 = { nounwind uwtable "frame-pointer"="none" "min-legal-vector-width"="0" "no-trapping-math"="true" "stack-protector-buffer-size"="8" "target-cpu"="x86-64" "target-features"="+cx8,+fxsr,+mmx,+sse,+sse2,+x87" "tune-cpu"="generic" }
attributes #1 = { noreturn nounwind "frame-pointer"="none" "no-trapping-math"="true" "stack-protector-buffer-size"="8" "target-cpu"="x86-64" "target-features"="+cx8,+fxsr,+mmx,+sse,+sse2,+x87" "tune-cpu"="generic" }
attributes #2 = { noreturn nounwind }

!llvm.dbg.cu = !{!0}
!llvm.module.flags = !{!2, !3, !4, !5, !6, !7}
!llvm.ident = !{!8}

!0 = distinct !DICompileUnit(language: DW_LANG_C11, file: !1, producer: "clang version 16.0.0.prerel", isOptimized: true, runtimeVersion: 0, emissionKind: LineTablesOnly, splitDebugInlining: false, nameTableKind: None)
!1 = !DIFile(filename: "case3.c", directory: "/", checksumkind: CSK_MD5, checksum: "53e9893099480164de1f5ee265c0cf01")
!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 = !{!"clang version 16.0.0.prerel"}
!9 = distinct !DISubprogram(name: "multiple_inl_funcs", scope: !1, file: !1, line: 18, type: !10, scopeLine: 19, flags: DIFlagAllCallsDescribed, spFlags: DISPFlagDefinition | DISPFlagOptimized, unit: !0, retainedNodes: !11)
!10 = !DISubroutineType(types: !11)
!11 = !{}
!12 = !DILocation(line: 20, column: 13, scope: !9)
!13 = !{!14, !14, i64 0}
!14 = !{!"int", !15, i64 0}
!15 = !{!"omnipotent char", !16, i64 0}
!16 = !{!"Simple C/C++ TBAA"}
!17 = !DILocation(line: 4, column: 9, scope: !18, inlinedAt: !19)
!18 = distinct !DISubprogram(name: "inl3", scope: !1, file: !1, line: 3, type: !10, scopeLine: 3, flags: DIFlagPrototyped | DIFlagAllCallsDescribed, spFlags: DISPFlagLocalToUnit | DISPFlagDefinition | DISPFlagOptimized, unit: !0, retainedNodes: !11)
!19 = distinct !DILocation(line: 20, column: 8, scope: !9)
!20 = !DILocation(line: 4, column: 7, scope: !18, inlinedAt: !19)
!21 = !DILocation(line: 5, column: 5, scope: !18, inlinedAt: !19)
!22 = !DILocation(line: 6, column: 12, scope: !18, inlinedAt: !19)
!23 = !DILocation(line: 6, column: 18, scope: !18, inlinedAt: !19)
!24 = !DILocation(line: 20, column: 6, scope: !9)
!25 = !DILocation(line: 21, column: 13, scope: !9)
!26 = !DILocation(line: 10, column: 9, scope: !27, inlinedAt: !28)
!27 = distinct !DISubprogram(name: "inl4", scope: !1, file: !1, line: 9, type: !10, scopeLine: 9, flags: DIFlagPrototyped | DIFlagAllCallsDescribed, spFlags: DISPFlagLocalToUnit | DISPFlagDefinition | DISPFlagOptimized, unit: !0, retainedNodes: !11)
!28 = distinct !DILocation(line: 21, column: 8, scope: !9)
!29 = !DILocation(line: 10, column: 7, scope: !27, inlinedAt: !28)
!30 = !DILocation(line: 11, column: 5, scope: !27, inlinedAt: !28)
!31 = !DILocation(line: 12, column: 12, scope: !27, inlinedAt: !28)
!32 = !DILocation(line: 12, column: 16, scope: !27, inlinedAt: !28)
!33 = !DILocation(line: 21, column: 6, scope: !9)
!34 = !DILocation(line: 22, column: 3, scope: !9)
!35 = !DISubprogram(name: "abort", scope: !36, file: !36, line: 514, type: !10, flags: DIFlagPrototyped, spFlags: DISPFlagOptimized, retainedNodes: !11)
!36 = !DIFile(filename: "/usr/include/stdlib.h", directory: "", checksumkind: CSK_MD5, checksum: "f7a1412d75d9e3df251dfc21b02d59ef")

...
---
name: multiple_inl_funcs
alignment: 16
tracksRegLiveness: true
tracksDebugUserValues: true
frameInfo:
stackSize: 8
offsetAdjustment: -8
maxAlignment: 1
adjustsStack: true
hasCalls: true
maxCallFrameSize: 0
machineFunctionInfo: {}
body: |
bb.0.entry:
successors: %bb.1(0x00000800), %bb.2(0x7ffff800)
frame-setup PUSH64r undef $rax, implicit-def $rsp, implicit $rsp
frame-setup CFI_INSTRUCTION def_cfa_offset 16
renamable $eax = MOV32rm $rip, 1, $noreg, @q1, $noreg, debug-location !12 :: (dereferenceable load (s32) from @q1, !tbaa !13)
CMP32ri8 renamable $eax, 4, implicit-def $eflags, debug-location !17
JCC_1 %bb.2, 12, implicit killed $eflags, debug-location !20
JMP_1 %bb.1, debug-location !20
bb.1.if.then.i:
successors:
CALL64pcrel32 target-flags(x86-plt) @abort, csr_64, implicit $rsp, implicit $ssp, implicit-def $rsp, implicit-def $ssp, debug-location !21
bb.2.inl3.exit:
successors: %bb.3(0x00000800), %bb.4(0x7ffff800)
liveins: $eax
renamable $eax = nsw IMUL32rri killed renamable $eax, 152, implicit-def dead $eflags, debug-location !22
renamable $eax = nsw ADD32ri8 killed renamable $eax, 100, implicit-def dead $eflags, debug-location !23
MOV32mr $rip, 1, $noreg, @g1, $noreg, killed renamable $eax, debug-location !24 :: (store (s32) into @g1, !tbaa !13)
renamable $eax = MOV32rm $rip, 1, $noreg, @q2, $noreg, debug-location !25 :: (dereferenceable load (s32) from @q2, !tbaa !13)
CMP32ri8 renamable $eax, 6, implicit-def $eflags, debug-location !26
JCC_1 %bb.4, 12, implicit killed $eflags, debug-location !29
JMP_1 %bb.3, debug-location !29
bb.3.if.then.i3:
successors:
CALL64pcrel32 target-flags(x86-plt) @abort, csr_64, implicit $rsp, implicit $ssp, implicit-def $rsp, implicit-def $ssp, debug-location !30
bb.4.inl4.exit:
liveins: $eax
renamable $eax = nsw IMUL32rr killed renamable $eax, renamable $eax, implicit-def dead $eflags, debug-location !31
renamable $eax = nuw nsw ADD32ri killed renamable $eax, 200, implicit-def dead $eflags, debug-location !32
MOV32mr $rip, 1, $noreg, @g2, $noreg, killed renamable $eax, debug-location !33 :: (store (s32) into @g2, !tbaa !13)
$eax = MOV32r0 implicit-def dead $eflags, debug-location !34
$rcx = frame-destroy POP64r implicit-def $rsp, implicit $rsp, debug-location !34
frame-destroy CFI_INSTRUCTION def_cfa_offset 8, debug-location !34
RET 0, $eax, debug-location !34
...

# In this case we get a single abort call originated from two separate
# inlined functions:
#
# 1 | #include <stdlib.h>
# 2 |
# 3 | static inline int inl3(int q, int n) {
# 4 | if (q > 3)
# 5 | abort();
# 6 | return q * 152 + n;
# 7 | }
# 8 |
# 9 | static inline int inl4(int q, int n) {
# 10 | if (q > 5)
# 11 | abort();
# 12 | return q * q + n;
# 13 | }
# 14 |
# 15 | int q1 = 1, q2 = 6;
# 16 | int g1, g2;
# 17 |
# 18 | int multiple_inl_funcs()
# 19 | {
# 20 | g1 = inl3(q1, 100);
# 21 | g2 = inl4(q2, 200);
# 22 | return 0;
# 23 | }
#
# We should produce a location at line 0 in the most common scope,
# multiple_inl_funcs(), without any inline information.

# CHECK: [[INLINER:![0-9]+]] = distinct !DISubprogram(name: "multiple_inl_funcs"

# CHECK-NOT: CALL64pcrel32
# CHECK: CALL64pcrel32 target-flags(x86-plt) @abort, {{.*}} debug-location !DILocation(line: 0, scope: [[INLINER]])
# CHECK-NOT: CALL64pcrel32
166 changes: 166 additions & 0 deletions llvm/test/DebugInfo/MIR/X86/merge-inline-loc4.mir
Original file line number Diff line number Diff line change
@@ -0,0 +1,166 @@
# RUN: llc -mtriple=x86_64-pc-linux %s -run-pass=branch-folder -o - | FileCheck %s

--- |
; ModuleID = 'case4.c'
source_filename = "case4.c"
target datalayout = "e-m:e-p270:32:32-p271:32:32-p272:64:64-i64:64-f80:128-n8:16:32:64-S128"
target triple = "x86_64-unknown-linux-gnu"

@q1 = dso_local local_unnamed_addr global i32 1, align 4
@q2 = dso_local local_unnamed_addr global i32 6, align 4
@g1 = dso_local local_unnamed_addr global i32 0, align 4

; Function Attrs: nounwind uwtable
define dso_local i32 @merge_inl_and_non_inl() local_unnamed_addr #0 !dbg !9 {
entry:
%0 = load i32, ptr @q1, align 4, !dbg !12, !tbaa !13
%cmp.i = icmp sgt i32 %0, 3, !dbg !17
br i1 %cmp.i, label %if.then.i, label %inl5.exit, !dbg !20

if.then.i: ; preds = %entry
tail call void @abort() #2, !dbg !21
unreachable, !dbg !21

inl5.exit: ; preds = %entry
%mul.i = mul nsw i32 %0, 152, !dbg !22
%add.i = add nsw i32 %mul.i, 100, !dbg !23
store i32 %add.i, ptr @g1, align 4, !dbg !24, !tbaa !13
%1 = load i32, ptr @q2, align 4, !dbg !25, !tbaa !13
%cmp = icmp sgt i32 %1, 5, !dbg !26
br i1 %cmp, label %if.then, label %if.end, !dbg !25

if.then: ; preds = %inl5.exit
tail call void @abort() #2, !dbg !27
unreachable, !dbg !27

if.end: ; preds = %inl5.exit
ret i32 0, !dbg !28
}

; Function Attrs: noreturn nounwind
declare !dbg !29 void @abort() local_unnamed_addr #1

attributes #0 = { nounwind uwtable "min-legal-vector-width"="0" "no-trapping-math"="true" "stack-protector-buffer-size"="8" "target-cpu"="x86-64" "target-features"="+cx8,+fxsr,+mmx,+sse,+sse2,+x87" "tune-cpu"="generic" }
attributes #1 = { noreturn nounwind "no-trapping-math"="true" "stack-protector-buffer-size"="8" "target-cpu"="x86-64" "target-features"="+cx8,+fxsr,+mmx,+sse,+sse2,+x87" "tune-cpu"="generic" }
attributes #2 = { noreturn nounwind }

!llvm.dbg.cu = !{!0}
!llvm.module.flags = !{!2, !3, !4, !5, !6, !7}
!llvm.ident = !{!8}

!0 = distinct !DICompileUnit(language: DW_LANG_C11, file: !1, producer: "clang version 16.0.0.prerel", isOptimized: true, runtimeVersion: 0, emissionKind: LineTablesOnly, splitDebugInlining: false, nameTableKind: None)
!1 = !DIFile(filename: "case4.c", directory: "/", checksumkind: CSK_MD5, checksum: "6ffe3d1878ac05f79e8c3a30566e1b2a")
!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 = !{!"clang version 16.0.0.prerel"}
!9 = distinct !DISubprogram(name: "merge_inl_and_non_inl", scope: !1, file: !1, line: 12, type: !10, scopeLine: 13, flags: DIFlagAllCallsDescribed, spFlags: DISPFlagDefinition | DISPFlagOptimized, unit: !0, retainedNodes: !11)
!10 = !DISubroutineType(types: !11)
!11 = !{}
!12 = !DILocation(line: 14, column: 13, scope: !9)
!13 = !{!14, !14, i64 0}
!14 = !{!"int", !15, i64 0}
!15 = !{!"omnipotent char", !16, i64 0}
!16 = !{!"Simple C/C++ TBAA"}
!17 = !DILocation(line: 4, column: 9, scope: !18, inlinedAt: !19)
!18 = distinct !DISubprogram(name: "inl5", scope: !1, file: !1, line: 3, type: !10, scopeLine: 3, flags: DIFlagPrototyped | DIFlagAllCallsDescribed, spFlags: DISPFlagLocalToUnit | DISPFlagDefinition | DISPFlagOptimized, unit: !0, retainedNodes: !11)
!19 = distinct !DILocation(line: 14, column: 8, scope: !9)
!20 = !DILocation(line: 4, column: 7, scope: !18, inlinedAt: !19)
!21 = !DILocation(line: 5, column: 5, scope: !18, inlinedAt: !19)
!22 = !DILocation(line: 6, column: 12, scope: !18, inlinedAt: !19)
!23 = !DILocation(line: 6, column: 18, scope: !18, inlinedAt: !19)
!24 = !DILocation(line: 14, column: 6, scope: !9)
!25 = !DILocation(line: 15, column: 7, scope: !9)
!26 = !DILocation(line: 15, column: 10, scope: !9)
!27 = !DILocation(line: 16, column: 5, scope: !9)
!28 = !DILocation(line: 17, column: 3, scope: !9)
!29 = !DISubprogram(name: "abort", scope: !30, file: !30, line: 514, type: !10, flags: DIFlagPrototyped, spFlags: DISPFlagOptimized, retainedNodes: !11)
!30 = !DIFile(filename: "/usr/include/stdlib.h", directory: "", checksumkind: CSK_MD5, checksum: "f7a1412d75d9e3df251dfc21b02d59ef")

...
---
name: merge_inl_and_non_inl
alignment: 16
tracksRegLiveness: true
tracksDebugUserValues: true
frameInfo:
stackSize: 8
offsetAdjustment: -8
maxAlignment: 1
adjustsStack: true
hasCalls: true
maxCallFrameSize: 0
machineFunctionInfo: {}
body: |
bb.0.entry:
successors: %bb.1(0x00000800), %bb.2(0x7ffff800)
frame-setup PUSH64r undef $rax, implicit-def $rsp, implicit $rsp
frame-setup CFI_INSTRUCTION def_cfa_offset 16
renamable $eax = MOV32rm $rip, 1, $noreg, @q1, $noreg, debug-location !12 :: (dereferenceable load (s32) from @q1, !tbaa !13)
CMP32ri8 renamable $eax, 4, implicit-def $eflags, debug-location !17
JCC_1 %bb.2, 12, implicit killed $eflags, debug-location !20
JMP_1 %bb.1, debug-location !20
bb.1.if.then.i:
successors:
CALL64pcrel32 target-flags(x86-plt) @abort, csr_64, implicit $rsp, implicit $ssp, implicit-def $rsp, implicit-def $ssp, debug-location !21
bb.2.inl5.exit:
successors: %bb.3(0x00000800), %bb.4(0x7ffff800)
liveins: $eax
renamable $eax = nsw IMUL32rri killed renamable $eax, 152, implicit-def dead $eflags, debug-location !22
renamable $eax = nsw ADD32ri8 killed renamable $eax, 100, implicit-def dead $eflags, debug-location !23
MOV32mr $rip, 1, $noreg, @g1, $noreg, killed renamable $eax, debug-location !24 :: (store (s32) into @g1, !tbaa !13)
CMP32mi8 $rip, 1, $noreg, @q2, $noreg, 6, implicit-def $eflags, debug-location !26 :: (dereferenceable load (s32) from @q2, !tbaa !13)
JCC_1 %bb.4, 12, implicit killed $eflags, debug-location !25
JMP_1 %bb.3, debug-location !25
bb.3.if.then:
successors:
CALL64pcrel32 target-flags(x86-plt) @abort, csr_64, implicit $rsp, implicit $ssp, implicit-def $rsp, implicit-def $ssp, debug-location !27
bb.4.if.end:
$eax = MOV32r0 implicit-def dead $eflags, debug-location !28
$rcx = frame-destroy POP64r implicit-def $rsp, implicit $rsp, debug-location !28
frame-destroy CFI_INSTRUCTION def_cfa_offset 8, debug-location !28
RET 0, $eax, debug-location !28
...

# In this case we get a single abort call originating from an inlined instance
# of inl5() and the function that is inlined in:
#
# 1 | #include <stdlib.h>
# 2 |
# 3 | static inline int inl5(int q, int n) {
# 4 | if (q > 3)
# 5 | abort();
# 6 | return q * 152 + n;
# 7 | }
# 8 |
# 9 | int q1 = 1, q2 = 6;
# 10 | int g1;
# 11 |
# 12 | int merge_inl_and_non_inl()
# 13 | {
# 14 | g1 = inl5(q1, 100);
# 15 | if (q2 > 5)
# 16 | abort();
# 17 | return 0;
# 18 | }
#
# We should produce a location at line 0 in the most common scope,
# merge_inl_and_non_inl(), without any inline information.

# CHECK: [[INLINER:![0-9]+]] = distinct !DISubprogram(name: "merge_inl_and_non_inl"

# CHECK-NOT: CALL64pcrel32
# CHECK: CALL64pcrel32 target-flags(x86-plt) @abort, {{.*}} debug-location !DILocation(line: 0, scope: [[INLINER]])
# CHECK-NOT: CALL64pcrel32
15 changes: 10 additions & 5 deletions llvm/test/DebugInfo/X86/merge_inlined_loc.ll
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,8 @@
; locations with the same source line but different scopes should be merged to
; a line zero location at the nearest common scope and inlining. The location
; of the single call to "common" (the two calls are collapsed together by
; BranchFolding) should be attributed to line zero inside the wrapper2 inlined
; scope within f1.
; BranchFolding) should be attributed to line 2 inside the wrapper inlined
; scope within wrapper2 at line 0 inlined within f1 at line 13.

; void common();
; inline void wrapper() { common(); }
Expand All @@ -22,10 +22,12 @@
; }
; void f1() { wrapper2(); }

; Ensure there is only one inlined_subroutine (for wrapper2, none for wrapper)
; & that its address range includes the call to 'common'.
; Ensure there are two inlined_subroutine (for wrapper and wrapper2).

; CHECK: jmp _Z6commonv
; CHECK: .loc 1 2 25 epilogue_begin
; CHECK-NEXT: popq %rax
; CHECK-NEXT: .cfi_def_cfa_offset 8
; CHECK-NEXT: jmp _Z6commonv
; CHECK-NEXT: [[LABEL:.*]]:

; CHECK: .section .debug_info
Expand All @@ -35,6 +37,9 @@
; CHECK: DW_TAG_inlined_subroutine
; CHECK-NOT: {{DW_TAG\|End Of Children}}
; CHECK: [[LABEL]]-{{.*}} DW_AT_high_pc
; CHECK: .byte 13 # DW_AT_call_line
; CHECK: DW_TAG_inlined_subroutine
; CHECK: .byte 0 # DW_AT_call_line
; CHECK-NOT: DW_TAG


Expand Down
153 changes: 153 additions & 0 deletions llvm/unittests/IR/MetadataTest.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1089,6 +1089,159 @@ TEST_F(DILocationTest, Merge) {
EXPECT_EQ(SPI, M->getScope());
EXPECT_EQ(nullptr, M->getInlinedAt());
}

// Merge a location in C, which is inlined-at in B that is inlined in A,
// with a location in A that has the same scope, line and column as B's
// inlined-at location.
{
auto *FA = getFile();
auto *FB = getFile();
auto *FC = getFile();

auto *SPA = DISubprogram::getDistinct(Context, FA, "a", "a", FA, 0, nullptr,
0, nullptr, 0, 0, DINode::FlagZero,
DISubprogram::SPFlagZero, nullptr);

auto *SPB = DISubprogram::getDistinct(Context, FB, "b", "b", FB, 0, nullptr,
0, nullptr, 0, 0, DINode::FlagZero,
DISubprogram::SPFlagZero, nullptr);

auto *SPC = DISubprogram::getDistinct(Context, FC, "c", "c", FC, 0, nullptr,
0, nullptr, 0, 0, DINode::FlagZero,
DISubprogram::SPFlagZero, nullptr);

auto *A = DILocation::get(Context, 3, 2, SPA);
auto *B = DILocation::get(Context, 2, 4, SPB, A);
auto *C = DILocation::get(Context, 13, 2, SPC, B);
auto *M = DILocation::getMergedLocation(A, C);
EXPECT_EQ(3u, M->getLine());
EXPECT_EQ(2u, M->getColumn());
EXPECT_TRUE(isa<DILocalScope>(M->getScope()));
EXPECT_EQ(SPA, M->getScope());
EXPECT_EQ(nullptr, M->getInlinedAt());
}

// Two inlined locations with the same scope, line and column
// in the same inlined-at function at different line and column.
{
auto *FA = getFile();
auto *FB = getFile();
auto *FC = getFile();

auto *SPA = DISubprogram::getDistinct(Context, FA, "a", "a", FA, 0, nullptr,
0, nullptr, 0, 0, DINode::FlagZero,
DISubprogram::SPFlagZero, nullptr);

auto *SPB = DISubprogram::getDistinct(Context, FB, "b", "b", FB, 0, nullptr,
0, nullptr, 0, 0, DINode::FlagZero,
DISubprogram::SPFlagZero, nullptr);

auto *SPC = DISubprogram::getDistinct(Context, FC, "c", "c", FC, 0, nullptr,
0, nullptr, 0, 0, DINode::FlagZero,
DISubprogram::SPFlagZero, nullptr);

auto *A = DILocation::get(Context, 10, 20, SPA);
auto *B1 = DILocation::get(Context, 3, 2, SPB, A);
auto *B2 = DILocation::get(Context, 4, 5, SPB, A);
auto *C1 = DILocation::get(Context, 2, 4, SPC, B1);
auto *C2 = DILocation::get(Context, 2, 4, SPC, B2);

auto *M = DILocation::getMergedLocation(C1, C2);
EXPECT_EQ(2u, M->getLine());
EXPECT_EQ(4u, M->getColumn());
EXPECT_EQ(SPC, M->getScope());
ASSERT_NE(nullptr, M->getInlinedAt());

auto *I1 = M->getInlinedAt();
EXPECT_EQ(0u, I1->getLine());
EXPECT_EQ(0u, I1->getColumn());
EXPECT_EQ(SPB, I1->getScope());
EXPECT_EQ(A, I1->getInlinedAt());
}

// Two locations, different line/column and scope in the same subprogram,
// inlined at the same place. This should result in a 0:0 location with
// the nearest common scope in the inlined function.
{
auto *FA = getFile();
auto *FI = getFile();

auto *SPA = DISubprogram::getDistinct(Context, FA, "a", "a", FA, 0, nullptr,
0, nullptr, 0, 0, DINode::FlagZero,
DISubprogram::SPFlagZero, nullptr);

auto *SPI = DISubprogram::getDistinct(Context, FI, "i", "i", FI, 0, nullptr,
0, nullptr, 0, 0, DINode::FlagZero,
DISubprogram::SPFlagZero, nullptr);

// Nearest common scope for the two locations in a.
auto *SPAScope1 = DILexicalBlock::getDistinct(Context, SPA, FA, 4, 9);

// Scope for the first location in a.
auto *SPAScope2 =
DILexicalBlock::getDistinct(Context, SPAScope1, FA, 10, 12);

// Scope for the second location in a.
auto *SPAScope3 =
DILexicalBlock::getDistinct(Context, SPAScope1, FA, 20, 8);
auto *SPAScope4 =
DILexicalBlock::getDistinct(Context, SPAScope3, FA, 21, 12);

auto *I = DILocation::get(Context, 3, 8, SPI);
auto *A1 = DILocation::get(Context, 12, 7, SPAScope2, I);
auto *A2 = DILocation::get(Context, 21, 15, SPAScope4, I);
auto *M = DILocation::getMergedLocation(A1, A2);
EXPECT_EQ(0u, M->getLine());
EXPECT_EQ(0u, M->getColumn());
EXPECT_TRUE(isa<DILocalScope>(M->getScope()));
EXPECT_EQ(SPAScope1, M->getScope());
EXPECT_EQ(I, M->getInlinedAt());
}

// Regression test to catch a case where an iterator was invalidated due to
// handling the chain of inlined-at locations after the nearest common
// location for the two arguments were found.
{
auto *FA = getFile();
auto *FB = getFile();
auto *FI = getFile();

auto *SPA = DISubprogram::getDistinct(Context, FA, "a", "a", FA, 0, nullptr,
0, nullptr, 0, 0, DINode::FlagZero,
DISubprogram::SPFlagZero, nullptr);

auto *SPB = DISubprogram::getDistinct(Context, FB, "b", "b", FB, 0, nullptr,
0, nullptr, 0, 0, DINode::FlagZero,
DISubprogram::SPFlagZero, nullptr);

auto *SPI = DISubprogram::getDistinct(Context, FI, "i", "i", FI, 0, nullptr,
0, nullptr, 0, 0, DINode::FlagZero,
DISubprogram::SPFlagZero, nullptr);

auto *SPAScope1 = DILexicalBlock::getDistinct(Context, SPA, FA, 4, 9);
auto *SPAScope2 = DILexicalBlock::getDistinct(Context, SPA, FA, 8, 3);

DILocation *InlinedAt = nullptr;

// Create a chain of inlined-at locations.
for (int i = 0; i < 256; i++) {
InlinedAt = DILocation::get(Context, 3 + i, 8 + i, SPI, InlinedAt);
}

auto *A1 = DILocation::get(Context, 5, 9, SPAScope1, InlinedAt);
auto *A2 = DILocation::get(Context, 9, 8, SPAScope2, InlinedAt);
auto *B = DILocation::get(Context, 10, 3, SPB, A1);
auto *M1 = DILocation::getMergedLocation(B, A2);
EXPECT_EQ(0u, M1->getLine());
EXPECT_EQ(0u, M1->getColumn());
EXPECT_TRUE(isa<DILocalScope>(M1->getScope()));
EXPECT_EQ(SPA, M1->getScope());
EXPECT_EQ(InlinedAt, M1->getInlinedAt());

// Test the other argument order for good measure.
auto *M2 = DILocation::getMergedLocation(A2, B);
EXPECT_EQ(M1, M2);
}
}

TEST_F(DILocationTest, getDistinct) {
Expand Down