Skip to content

[DebugInfo] getMergedLocation: match scopes based on their location #132286

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 8 commits into from
Apr 18, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
143 changes: 118 additions & 25 deletions llvm/lib/IR/DebugInfoMetadata.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -125,6 +125,98 @@ DILocation *DILocation::getMergedLocations(ArrayRef<DILocation *> Locs) {
return Merged;
}

static DILexicalBlockBase *cloneAndReplaceParentScope(DILexicalBlockBase *LBB,
DIScope *NewParent) {
TempMDNode ClonedScope = LBB->clone();
cast<DILexicalBlockBase>(*ClonedScope).replaceScope(NewParent);
return cast<DILexicalBlockBase>(
MDNode::replaceWithUniqued(std::move(ClonedScope)));
}

using LineColumn = std::pair<unsigned /* Line */, unsigned /* Column */>;

/// Returns the location of DILocalScope, if present, or a default value.
static LineColumn getLocalScopeLocationOr(DIScope *S, LineColumn Default) {
assert(isa<DILocalScope>(S) && "Expected DILocalScope.");

if (isa<DILexicalBlockFile>(S))
return Default;
if (auto *LB = dyn_cast<DILexicalBlock>(S))
return {LB->getLine(), LB->getColumn()};
if (auto *SP = dyn_cast<DISubprogram>(S))
return {SP->getLine(), 0u};

llvm_unreachable("Unhandled type of DILocalScope.");
}

// Returns the nearest matching scope inside a subprogram.
template <typename MatcherT>
static std::pair<DIScope *, LineColumn>
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<DISubprogram>(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<DISubprogram>(S2))
break;
}
return std::make_pair(nullptr, LineColumn(L2->getLine(), L2->getColumn()));
}

// Matches equal scopes.
struct EqualScopesMatcher {
SmallPtrSet<DIScope *, 8> 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<std::pair<DIFile *, LineColumn>, SmallSetVector<DIScope *, 8>,
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;
Expand Down Expand Up @@ -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<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());
// Find nearest common scope inside subprogram.
DIScope *Scope = getNearestMatchingScope<EqualScopesMatcher>(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<ScopeLocationsMatcher>(L1, L2);
Copy link
Contributor

@SLTozer SLTozer Apr 4, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I can imagine a small optimization where for this second search, we cap the search up through L1's scope chain at Scope, i.e. we never consider any parent scopes of Scope; but that's a minor point that may not be worth making the code less legible for and this patch doesn't noticeably affect performance, so it's not necessary to merge this.


// 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<DILexicalBlockBase>(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;
Expand Down Expand Up @@ -1187,10 +1282,8 @@ DILocalScope *DILocalScope::cloneScopeForSubprogram(
// cached result).
DIScope *UpdatedScope = CachedResult ? CachedResult : &NewSP;
for (DIScope *ScopeToUpdate : reverse(ScopeChain)) {
TempMDNode ClonedScope = ScopeToUpdate->clone();
cast<DILexicalBlockBase>(*ClonedScope).replaceScope(UpdatedScope);
UpdatedScope =
cast<DIScope>(MDNode::replaceWithUniqued(std::move(ClonedScope)));
UpdatedScope = cloneAndReplaceParentScope(
cast<DILexicalBlockBase>(ScopeToUpdate), UpdatedScope);
Cache[ScopeToUpdate] = UpdatedScope;
}

Expand Down
79 changes: 79 additions & 0 deletions llvm/test/DebugInfo/AArch64/merge-nested-block-loc.ll
Original file line number Diff line number Diff line change
@@ -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)
130 changes: 130 additions & 0 deletions llvm/test/DebugInfo/AArch64/merge-nested-block-loc2.ll
Original file line number Diff line number Diff line change
@@ -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)
Loading
Loading