Skip to content

Commit 6462fad

Browse files
authored
[DebugInfo] getMergedLocation: match scopes based on their location (#132286)
getMergedLocation uses a common parent scope of the two input locations for an output location. It doesn't consider the case when the common parent scope is from a file other than L1's and L2's files. In that case, it produces a merged location with an erroneous scope (#122846). In some cases, such as #125780 (comment), L1, L2 having a common parent scope from another file indicate that the code at L1 and L2 is included from the same source location. With this commit, getMergedLocation detects that L1, L2, or their common parent scope files are different. If so, it assumes that L1 and L2 were included from some source location, and tries to attach the output location to a scope with the nearest common source location with regard to L1 and L2. If the nearest common location is also from another file, getMergedLocation returns it as a merged location, assuming that L1 and L2 belong to files that were both included in the nearest common location. Fixes #122846.
1 parent c890b73 commit 6462fad

File tree

4 files changed

+536
-32
lines changed

4 files changed

+536
-32
lines changed

llvm/lib/IR/DebugInfoMetadata.cpp

+118-25
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@
1313
#include "llvm/IR/DebugInfoMetadata.h"
1414
#include "LLVMContextImpl.h"
1515
#include "MetadataImpl.h"
16-
#include "llvm/ADT/SmallPtrSet.h"
16+
#include "llvm/ADT/SetVector.h"
1717
#include "llvm/ADT/StringSwitch.h"
1818
#include "llvm/BinaryFormat/Dwarf.h"
1919
#include "llvm/IR/DebugProgramInstruction.h"
@@ -125,6 +125,98 @@ DILocation *DILocation::getMergedLocations(ArrayRef<DILocation *> Locs) {
125125
return Merged;
126126
}
127127

128+
static DILexicalBlockBase *cloneAndReplaceParentScope(DILexicalBlockBase *LBB,
129+
DIScope *NewParent) {
130+
TempMDNode ClonedScope = LBB->clone();
131+
cast<DILexicalBlockBase>(*ClonedScope).replaceScope(NewParent);
132+
return cast<DILexicalBlockBase>(
133+
MDNode::replaceWithUniqued(std::move(ClonedScope)));
134+
}
135+
136+
using LineColumn = std::pair<unsigned /* Line */, unsigned /* Column */>;
137+
138+
/// Returns the location of DILocalScope, if present, or a default value.
139+
static LineColumn getLocalScopeLocationOr(DIScope *S, LineColumn Default) {
140+
assert(isa<DILocalScope>(S) && "Expected DILocalScope.");
141+
142+
if (isa<DILexicalBlockFile>(S))
143+
return Default;
144+
if (auto *LB = dyn_cast<DILexicalBlock>(S))
145+
return {LB->getLine(), LB->getColumn()};
146+
if (auto *SP = dyn_cast<DISubprogram>(S))
147+
return {SP->getLine(), 0u};
148+
149+
llvm_unreachable("Unhandled type of DILocalScope.");
150+
}
151+
152+
// Returns the nearest matching scope inside a subprogram.
153+
template <typename MatcherT>
154+
static std::pair<DIScope *, LineColumn>
155+
getNearestMatchingScope(const DILocation *L1, const DILocation *L2) {
156+
MatcherT Matcher;
157+
158+
DIScope *S1 = L1->getScope();
159+
DIScope *S2 = L2->getScope();
160+
161+
LineColumn Loc1(L1->getLine(), L1->getColumn());
162+
for (; S1; S1 = S1->getScope()) {
163+
Loc1 = getLocalScopeLocationOr(S1, Loc1);
164+
Matcher.insert(S1, Loc1);
165+
if (isa<DISubprogram>(S1))
166+
break;
167+
}
168+
169+
LineColumn Loc2(L2->getLine(), L2->getColumn());
170+
for (; S2; S2 = S2->getScope()) {
171+
Loc2 = getLocalScopeLocationOr(S2, Loc2);
172+
173+
if (DIScope *S = Matcher.match(S2, Loc2))
174+
return std::make_pair(S, Loc2);
175+
176+
if (isa<DISubprogram>(S2))
177+
break;
178+
}
179+
return std::make_pair(nullptr, LineColumn(L2->getLine(), L2->getColumn()));
180+
}
181+
182+
// Matches equal scopes.
183+
struct EqualScopesMatcher {
184+
SmallPtrSet<DIScope *, 8> Scopes;
185+
186+
void insert(DIScope *S, LineColumn Loc) { Scopes.insert(S); }
187+
188+
DIScope *match(DIScope *S, LineColumn Loc) {
189+
return Scopes.contains(S) ? S : nullptr;
190+
}
191+
};
192+
193+
// Matches scopes with the same location.
194+
struct ScopeLocationsMatcher {
195+
SmallMapVector<std::pair<DIFile *, LineColumn>, SmallSetVector<DIScope *, 8>,
196+
8>
197+
Scopes;
198+
199+
void insert(DIScope *S, LineColumn Loc) {
200+
Scopes[{S->getFile(), Loc}].insert(S);
201+
}
202+
203+
DIScope *match(DIScope *S, LineColumn Loc) {
204+
auto ScopesAtLoc = Scopes.find({S->getFile(), Loc});
205+
// No scope found with the given location.
206+
if (ScopesAtLoc == Scopes.end())
207+
return nullptr;
208+
209+
// Prefer S over other scopes with the same location.
210+
if (ScopesAtLoc->second.contains(S))
211+
return S;
212+
213+
if (!ScopesAtLoc->second.empty())
214+
return *ScopesAtLoc->second.begin();
215+
216+
llvm_unreachable("Scopes must not have empty entries.");
217+
}
218+
};
219+
128220
DILocation *DILocation::getMergedLocation(DILocation *LocA, DILocation *LocB) {
129221
if (!LocA || !LocB)
130222
return nullptr;
@@ -208,28 +300,31 @@ DILocation *DILocation::getMergedLocation(DILocation *LocA, DILocation *LocB) {
208300
if (L1->getScope()->getSubprogram() != L2->getScope()->getSubprogram())
209301
return nullptr;
210302

211-
// Return the nearest common scope inside a subprogram.
212-
auto GetNearestCommonScope = [](DIScope *S1, DIScope *S2) -> DIScope * {
213-
SmallPtrSet<DIScope *, 8> Scopes;
214-
for (; S1; S1 = S1->getScope()) {
215-
Scopes.insert(S1);
216-
if (isa<DISubprogram>(S1))
217-
break;
218-
}
219-
220-
for (; S2; S2 = S2->getScope()) {
221-
if (Scopes.count(S2))
222-
return S2;
223-
if (isa<DISubprogram>(S2))
224-
break;
225-
}
226-
227-
return nullptr;
228-
};
229-
230-
auto Scope = GetNearestCommonScope(L1->getScope(), L2->getScope());
303+
// Find nearest common scope inside subprogram.
304+
DIScope *Scope = getNearestMatchingScope<EqualScopesMatcher>(L1, L2).first;
231305
assert(Scope && "No common scope in the same subprogram?");
232306

307+
// Try using the nearest scope with common location if files are different.
308+
if (Scope->getFile() != L1->getFile() || L1->getFile() != L2->getFile()) {
309+
auto [CommonLocScope, CommonLoc] =
310+
getNearestMatchingScope<ScopeLocationsMatcher>(L1, L2);
311+
312+
// If CommonLocScope is a DILexicalBlockBase, clone it and locate
313+
// a new scope inside the nearest common scope to preserve
314+
// lexical blocks structure.
315+
if (auto *LBB = dyn_cast<DILexicalBlockBase>(CommonLocScope);
316+
LBB && LBB != Scope)
317+
CommonLocScope = cloneAndReplaceParentScope(LBB, Scope);
318+
319+
Scope = CommonLocScope;
320+
321+
// If files are still different, assume that L1 and L2 were "included"
322+
// from CommonLoc. Use it as merged location.
323+
if (Scope->getFile() != L1->getFile() || L1->getFile() != L2->getFile())
324+
return DILocation::get(C, CommonLoc.first, CommonLoc.second,
325+
CommonLocScope, InlinedAt);
326+
}
327+
233328
bool SameLine = L1->getLine() == L2->getLine();
234329
bool SameCol = L1->getColumn() == L2->getColumn();
235330
unsigned Line = SameLine ? L1->getLine() : 0;
@@ -1187,10 +1282,8 @@ DILocalScope *DILocalScope::cloneScopeForSubprogram(
11871282
// cached result).
11881283
DIScope *UpdatedScope = CachedResult ? CachedResult : &NewSP;
11891284
for (DIScope *ScopeToUpdate : reverse(ScopeChain)) {
1190-
TempMDNode ClonedScope = ScopeToUpdate->clone();
1191-
cast<DILexicalBlockBase>(*ClonedScope).replaceScope(UpdatedScope);
1192-
UpdatedScope =
1193-
cast<DIScope>(MDNode::replaceWithUniqued(std::move(ClonedScope)));
1285+
UpdatedScope = cloneAndReplaceParentScope(
1286+
cast<DILexicalBlockBase>(ScopeToUpdate), UpdatedScope);
11941287
Cache[ScopeToUpdate] = UpdatedScope;
11951288
}
11961289

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
; RUN: opt -mtriple=aarch64-unknown-linux-gnu -S %s -passes=sroa -o - | FileCheck %s
2+
3+
; In this test we want to ensure that the merged location of phi instruction
4+
; belongs to the correct scope (DILexicalBlockFile), so that line number
5+
; of that location belongs to the corresponding file.
6+
7+
; Generated with clang from
8+
; # 1 "1.c" 1
9+
; # 1 "1.c" 2
10+
; int foo(int a) {
11+
; int i = 0;
12+
; if ((a & 1) == 1) {
13+
; a -= 1;
14+
; # 1 "m.c" 1
15+
; # 40 "m.c"
16+
; i += a;
17+
; i -= 10*a;
18+
; i *= a*a;
19+
; # 6 "1.c" 2
20+
; } else {
21+
; a += 3;
22+
; # 1 "m.c" 1
23+
; # 40 "m.c"
24+
; i += a;
25+
; i -= 10*a;
26+
; i *= a*a;
27+
; # 9 "1.c" 2
28+
; }
29+
; return i;
30+
; }
31+
32+
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"
33+
target triple = "aarch64-unknown-linux-gnu"
34+
35+
define i32 @foo() !dbg !3 {
36+
; CHECK: phi i32 {{.*}}, !dbg [[PHILOC:![0-9]+]]
37+
;
38+
entry:
39+
%i = alloca i32, align 4
40+
br i1 false, label %if.then, label %if.else
41+
42+
if.then: ; preds = %entry
43+
store i32 1, ptr %i, align 4, !dbg !7
44+
br label %if.end
45+
46+
if.else: ; preds = %entry
47+
store i32 0, ptr %i, align 4, !dbg !12
48+
br label %if.end
49+
50+
if.end: ; preds = %if.else, %if.then
51+
%0 = load i32, ptr %i, align 4
52+
ret i32 0
53+
}
54+
55+
!llvm.dbg.cu = !{!0}
56+
!llvm.module.flags = !{!2}
57+
58+
!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)
59+
!1 = !DIFile(filename: "repro.c", directory: "")
60+
!2 = !{i32 2, !"Debug Info Version", i32 3}
61+
!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)
62+
!4 = !DIFile(filename: "1.c", directory: "")
63+
!5 = !DISubroutineType(types: !6)
64+
!6 = !{}
65+
!7 = !DILocation(line: 42, column: 3, scope: !8)
66+
!8 = !DILexicalBlockFile(scope: !10, file: !9, discriminator: 0)
67+
!9 = !DIFile(filename: "m.c", directory: "")
68+
!10 = distinct !DILexicalBlock(scope: !11, file: !4, line: 3, column: 21)
69+
!11 = distinct !DILexicalBlock(scope: !3, file: !4, line: 3, column: 7)
70+
!12 = !DILocation(line: 42, column: 3, scope: !13)
71+
!13 = !DILexicalBlockFile(scope: !14, file: !9, discriminator: 0)
72+
!14 = distinct !DILexicalBlock(scope: !11, file: !4, line: 6, column: 9)
73+
74+
; CHECK: [[SP:![0-9]+]] = distinct !DISubprogram(name: "foo", scope: [[FILE1:![0-9]+]], file: [[FILE1]], line: 1
75+
; CHECK: [[FILE1]] = !DIFile(filename: "1.c", directory: "")
76+
; CHECK: [[PHILOC]] = !DILocation(line: 42, column: 3, scope: [[LBF:![0-9]+]])
77+
; CHECK: [[LBF]] = !DILexicalBlockFile(scope: [[LB:![0-9]+]], file: [[FILE2:![0-9]+]], discriminator: 0)
78+
; CHECK: [[FILE2]] = !DIFile(filename: "m.c", directory: "")
79+
; CHECK: [[LB]] = distinct !DILexicalBlock(scope: [[SP]], file: [[FILE1]], line: 3, column: 7)
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,130 @@
1+
; RUN: opt -mtriple=aarch64-unknown-linux-gnu -S %s -passes=sroa -o - | FileCheck %s
2+
3+
; In this test we want to ensure that getMergedLocations uses common include
4+
; location if incoming locations belong to different files.
5+
; The location of phi instruction merged from locations of %mul3 and %mul10
6+
; should be the location of do-loop lexical block from y.c.
7+
8+
; Generated with clang from
9+
;
10+
; main.c:
11+
; int foo(int a) {
12+
; int i = 0;
13+
; if ((a & 1) == 1) {
14+
; a -= 1;
15+
; #define A
16+
; #include "y.c"
17+
; } else {
18+
; a += 3;
19+
; #undef A
20+
; #include "y.c"
21+
; }
22+
; return i;
23+
; }
24+
;
25+
; y.c:
26+
; # 300 "y.c" 1
27+
; do {
28+
; #ifdef A
29+
; #include "z1.c"
30+
; #else
31+
; #include "z2.c"
32+
; #endif
33+
; } while (0);
34+
;
35+
; z1.c:
36+
; # 100 "z1.c" 1
37+
; i += a;
38+
; i -= 10*a;
39+
; i *= a*a;
40+
;
41+
; z2.c:
42+
; # 200 "z1.c" 1
43+
; i += a;
44+
; i -= 10*a;
45+
; i *= a*a;
46+
;
47+
; Preprocessed source:
48+
;
49+
; # 1 "main.c"
50+
; int foo(int a) {
51+
; int i = 0;
52+
; if ((a & 1) == 1) {
53+
; a -= 1;
54+
; # 300 "y.c" 1
55+
; do {
56+
; # 100 "z1.c" 1
57+
; i += a;
58+
; i -= 10*a;
59+
; i *= a*a;
60+
; # 303 "y.c" 2
61+
; } while (0);
62+
; # 7 "main.c" 2
63+
; } else {
64+
; a += 3;
65+
; # 300 "y.c" 1
66+
; do {
67+
; # 200 "z2.c" 1
68+
; i += a;
69+
; i -= 10*a;
70+
; i *= a*a;
71+
; # 305 "y.c" 2
72+
; } while (0);
73+
; # 11 "main.c" 2
74+
; }
75+
; return i;
76+
; }
77+
78+
target datalayout = "e-m:o-p270:32:32-p271:32:32-p272:64:64-i64:64-i128:128-n32:64-S128-Fn32"
79+
target triple = "arm64-apple-macosx15.0.0"
80+
81+
define i32 @foo() !dbg !3 {
82+
; CHECK: phi i32 {{.*}}, !dbg [[PHILOC:![0-9]+]]
83+
;
84+
entry:
85+
%i = alloca i32, align 4
86+
br i1 false, label %do.body, label %if.else
87+
88+
do.body: ; preds = %entry
89+
store i32 1, ptr %i, align 4, !dbg !6
90+
br label %if.end
91+
92+
if.else: ; preds = %entry
93+
store i32 0, ptr %i, align 4, !dbg !14
94+
br label %if.end
95+
96+
if.end: ; preds = %if.else, %do.body
97+
%0 = load i32, ptr %i, align 4
98+
ret i32 0
99+
}
100+
101+
!llvm.dbg.cu = !{!0}
102+
!llvm.module.flags = !{!2}
103+
104+
!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: "/")
105+
!1 = !DIFile(filename: "main.c", directory: "")
106+
!2 = !{i32 2, !"Debug Info Version", i32 3}
107+
!3 = distinct !DISubprogram(name: "foo", scope: !1, file: !1, line: 1, type: !4, scopeLine: 1, flags: DIFlagPrototyped, spFlags: DISPFlagDefinition, unit: !0, retainedNodes: !5)
108+
!4 = !DISubroutineType(types: !5)
109+
!5 = !{}
110+
!6 = !DILocation(line: 102, column: 3, scope: !7)
111+
!7 = !DILexicalBlockFile(scope: !9, file: !8, discriminator: 0)
112+
!8 = !DIFile(filename: "z1.c", directory: "")
113+
!9 = distinct !DILexicalBlock(scope: !11, file: !10, line: 300, column: 4)
114+
!10 = !DIFile(filename: "y.c", directory: "")
115+
!11 = !DILexicalBlockFile(scope: !12, file: !10, discriminator: 0)
116+
!12 = distinct !DILexicalBlock(scope: !13, file: !1, line: 3, column: 21)
117+
!13 = distinct !DILexicalBlock(scope: !3, file: !1, line: 3, column: 7)
118+
!14 = !DILocation(line: 202, column: 3, scope: !15)
119+
!15 = !DILexicalBlockFile(scope: !17, file: !16, discriminator: 0)
120+
!16 = !DIFile(filename: "z2.c", directory: "")
121+
!17 = distinct !DILexicalBlock(scope: !18, file: !10, line: 300, column: 4)
122+
!18 = !DILexicalBlockFile(scope: !19, file: !10, discriminator: 0)
123+
!19 = distinct !DILexicalBlock(scope: !13, file: !1, line: 7, column: 9)
124+
125+
; CHECK: [[FILE_MAIN:![0-9]+]] = !DIFile(filename: "main.c"
126+
; CHECK: [[SP:![0-9]+]] = distinct !DISubprogram(name: "foo", scope: [[FILE_MAIN]], file: [[FILE_MAIN]], line: 1
127+
; CHECK: [[PHILOC]] = !DILocation(line: 300, column: 4, scope: [[BLOCK_Y:![0-9]+]])
128+
; CHECK-NEXT: [[BLOCK_Y]] = !DILexicalBlock(scope: [[BLOCK_MAIN:![0-9]+]], file: [[FILE_Y:![0-9]+]], line: 300, column: 4)
129+
; CHECK-NEXT: [[FILE_Y]] = !DIFile(filename: "y.c"
130+
; CHECK: [[BLOCK_MAIN]] = distinct !DILexicalBlock(scope: [[SP]], file: [[FILE_MAIN]], line: 3, column: 7)

0 commit comments

Comments
 (0)