Skip to content

Commit e189d61

Browse files
[memprof] Add extractCallsFromIR (#115218)
This patch adds extractCallsFromIR, a function to extract calls from the IR, which will be used to undrift call site locations in the MemProf profile. In a nutshell, the MemProf undrifting works as follows: - Extract call site locations from the IR. - Extract call site locations from the MemProf profile. - Undrift the call site locations with longestCommonSequence. This patch implements the first bullet point above. Specifically, given the IR, the new function returns a map from caller GUIDs to lists of corresponding call sites. For example: Given: foo() { f1(); f2(); f3(); } extractCallsFromIR returns: Caller: foo -> {{(Line 1, Column 3), Callee: f1}, {(Line 2, Column 3), Callee: f2}, {(Line 2, Column 9), Callee: f3}} where the line numbers, relative to the beginning of the caller, and column numbers are sorted in the ascending order. The value side of the map -- the list of call sites -- can be directly passed to longestCommonSequence. To facilitate the review process, I've only implemented basic features in extractCallsFromIR in this patch. - The new function extracts calls from the LLVM "call" instructions only. It does not look into the inline stack. - It does not recognize or treat heap allocation functions in any special way. I will address these missing features in subsequent patches.
1 parent 02668f6 commit e189d61

File tree

4 files changed

+184
-0
lines changed

4 files changed

+184
-0
lines changed

llvm/include/llvm/Transforms/Instrumentation/MemProfiler.h

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,38 @@ class MemProfUsePass : public PassInfoMixin<MemProfUsePass> {
5757
IntrusiveRefCntPtr<vfs::FileSystem> FS;
5858
};
5959

60+
namespace memprof {
61+
62+
struct LineLocation {
63+
LineLocation(uint32_t L, uint32_t D) : LineOffset(L), Column(D) {}
64+
65+
bool operator<(const LineLocation &O) const {
66+
return LineOffset < O.LineOffset ||
67+
(LineOffset == O.LineOffset && Column < O.Column);
68+
}
69+
70+
bool operator==(const LineLocation &O) const {
71+
return LineOffset == O.LineOffset && Column == O.Column;
72+
}
73+
74+
bool operator!=(const LineLocation &O) const {
75+
return LineOffset != O.LineOffset || Column != O.Column;
76+
}
77+
78+
uint64_t getHashCode() const { return ((uint64_t)Column << 32) | LineOffset; }
79+
80+
uint32_t LineOffset;
81+
uint32_t Column;
82+
};
83+
84+
// A pair of a call site location and its corresponding callee GUID.
85+
using CallEdgeTy = std::pair<LineLocation, uint64_t>;
86+
87+
// Extract all calls from the IR. Arrange them in a map from caller GUIDs to a
88+
// list of call sites, each of the form {LineLocation, CalleeGUID}.
89+
DenseMap<uint64_t, SmallVector<CallEdgeTy, 0>> extractCallsFromIR(Module &M);
90+
91+
} // namespace memprof
6092
} // namespace llvm
6193

6294
#endif

llvm/lib/Transforms/Instrumentation/MemProfiler.cpp

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -795,6 +795,53 @@ struct AllocMatchInfo {
795795
bool Matched = false;
796796
};
797797

798+
DenseMap<uint64_t, SmallVector<CallEdgeTy, 0>>
799+
memprof::extractCallsFromIR(Module &M) {
800+
DenseMap<uint64_t, SmallVector<CallEdgeTy, 0>> Calls;
801+
802+
auto GetOffset = [](const DILocation *DIL) {
803+
return (DIL->getLine() - DIL->getScope()->getSubprogram()->getLine()) &
804+
0xffff;
805+
};
806+
807+
for (Function &F : M) {
808+
if (F.isDeclaration())
809+
continue;
810+
811+
for (auto &BB : F) {
812+
for (auto &I : BB) {
813+
const DILocation *DIL = I.getDebugLoc();
814+
if (!DIL)
815+
continue;
816+
817+
if (!isa<CallBase>(&I) || isa<IntrinsicInst>(&I))
818+
continue;
819+
820+
auto *CB = dyn_cast<CallBase>(&I);
821+
auto *CalledFunction = CB->getCalledFunction();
822+
// Disregard indirect calls and intrinsics.
823+
if (!CalledFunction || CalledFunction->isIntrinsic())
824+
continue;
825+
826+
StringRef CalleeName = CalledFunction->getName();
827+
uint64_t CallerGUID =
828+
IndexedMemProfRecord::getGUID(DIL->getSubprogramLinkageName());
829+
uint64_t CalleeGUID = IndexedMemProfRecord::getGUID(CalleeName);
830+
LineLocation Loc = {GetOffset(DIL), DIL->getColumn()};
831+
Calls[CallerGUID].emplace_back(Loc, CalleeGUID);
832+
}
833+
}
834+
}
835+
836+
// Sort each call list by the source location.
837+
for (auto &[CallerGUID, CallList] : Calls) {
838+
llvm::sort(CallList);
839+
CallList.erase(llvm::unique(CallList), CallList.end());
840+
}
841+
842+
return Calls;
843+
}
844+
798845
static void
799846
readMemprof(Module &M, Function &F, IndexedInstrProfReader *MemProfReader,
800847
const TargetLibraryInfo &TLI,

llvm/unittests/Transforms/Instrumentation/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ set(LLVM_LINK_COMPONENTS
88
)
99

1010
add_llvm_unittest(InstrumentationTests
11+
MemProfUseTest.cpp
1112
PGOInstrumentationTest.cpp
1213
)
1314

Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,104 @@
1+
//===- MemProfUseTest.cpp - MemProf use tests -----------------------------===//
2+
//
3+
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4+
// See https://llvm.org/LICENSE.txt for license information.
5+
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6+
//
7+
//===----------------------------------------------------------------------===//
8+
9+
#include "llvm/AsmParser/Parser.h"
10+
#include "llvm/IR/LLVMContext.h"
11+
#include "llvm/IR/Module.h"
12+
#include "llvm/ProfileData/MemProf.h"
13+
#include "llvm/Support/SourceMgr.h"
14+
#include "llvm/Transforms/Instrumentation/MemProfiler.h"
15+
16+
#include "gmock/gmock.h"
17+
#include "gtest/gtest.h"
18+
19+
namespace {
20+
using namespace llvm;
21+
using namespace llvm::memprof;
22+
using testing::FieldsAre;
23+
using testing::Pair;
24+
using testing::SizeIs;
25+
26+
TEST(MemProf, ExtractDirectCallsFromIR) {
27+
// The following IR is generated from:
28+
//
29+
// void f1();
30+
// void f2();
31+
// void f3();
32+
//
33+
// void foo() {
34+
// f1();
35+
// f2(); f3();
36+
// }
37+
StringRef IR = R"IR(
38+
define dso_local void @_Z3foov() !dbg !10 {
39+
entry:
40+
call void @_Z2f1v(), !dbg !13
41+
call void @_Z2f2v(), !dbg !14
42+
call void @_Z2f3v(), !dbg !15
43+
ret void, !dbg !16
44+
}
45+
46+
declare !dbg !17 void @_Z2f1v()
47+
48+
declare !dbg !18 void @_Z2f2v()
49+
50+
declare !dbg !19 void @_Z2f3v()
51+
52+
!llvm.dbg.cu = !{!0}
53+
!llvm.module.flags = !{!2, !3, !4, !5, !6, !7, !8}
54+
!llvm.ident = !{!9}
55+
56+
!0 = distinct !DICompileUnit(language: DW_LANG_C_plus_plus_14, file: !1, producer: "clang", isOptimized: true, runtimeVersion: 0, emissionKind: LineTablesOnly, splitDebugInlining: false, debugInfoForProfiling: true, nameTableKind: None)
57+
!1 = !DIFile(filename: "foobar.cc", directory: "/")
58+
!2 = !{i32 7, !"Dwarf Version", i32 5}
59+
!3 = !{i32 2, !"Debug Info Version", i32 3}
60+
!4 = !{i32 1, !"wchar_size", i32 4}
61+
!5 = !{i32 1, !"MemProfProfileFilename", !"memprof.profraw"}
62+
!6 = !{i32 8, !"PIC Level", i32 2}
63+
!7 = !{i32 7, !"PIE Level", i32 2}
64+
!8 = !{i32 7, !"uwtable", i32 2}
65+
!9 = !{!"clang"}
66+
!10 = distinct !DISubprogram(name: "foo", linkageName: "_Z3foov", scope: !1, file: !1, line: 5, type: !11, scopeLine: 5, flags: DIFlagPrototyped | DIFlagAllCallsDescribed, spFlags: DISPFlagDefinition | DISPFlagOptimized, unit: !0)
67+
!11 = !DISubroutineType(types: !12)
68+
!12 = !{}
69+
!13 = !DILocation(line: 6, column: 3, scope: !10)
70+
!14 = !DILocation(line: 7, column: 3, scope: !10)
71+
!15 = !DILocation(line: 7, column: 9, scope: !10)
72+
!16 = !DILocation(line: 8, column: 1, scope: !10)
73+
!17 = !DISubprogram(name: "f1", linkageName: "_Z2f1v", scope: !1, file: !1, line: 1, type: !11, flags: DIFlagPrototyped, spFlags: DISPFlagOptimized)
74+
!18 = !DISubprogram(name: "f2", linkageName: "_Z2f2v", scope: !1, file: !1, line: 2, type: !11, flags: DIFlagPrototyped, spFlags: DISPFlagOptimized)
75+
!19 = !DISubprogram(name: "f3", linkageName: "_Z2f3v", scope: !1, file: !1, line: 3, type: !11, flags: DIFlagPrototyped, spFlags: DISPFlagOptimized)
76+
)IR";
77+
78+
LLVMContext Ctx;
79+
SMDiagnostic Err;
80+
std::unique_ptr<Module> M = parseAssemblyString(IR, Err, Ctx);
81+
ASSERT_TRUE(M);
82+
83+
auto Calls = extractCallsFromIR(*M);
84+
85+
// Expect exactly one caller.
86+
ASSERT_THAT(Calls, SizeIs(1));
87+
88+
auto It = Calls.begin();
89+
ASSERT_NE(It, Calls.end());
90+
91+
const auto &[CallerGUID, CallSites] = *It;
92+
EXPECT_EQ(CallerGUID, IndexedMemProfRecord::getGUID("_Z3foov"));
93+
ASSERT_THAT(CallSites, SizeIs(3));
94+
95+
// Verify that call sites show up in the ascending order of their source
96+
// locations.
97+
EXPECT_THAT(CallSites[0],
98+
Pair(FieldsAre(1U, 3U), IndexedMemProfRecord::getGUID("_Z2f1v")));
99+
EXPECT_THAT(CallSites[1],
100+
Pair(FieldsAre(2U, 3U), IndexedMemProfRecord::getGUID("_Z2f2v")));
101+
EXPECT_THAT(CallSites[2],
102+
Pair(FieldsAre(2U, 9U), IndexedMemProfRecord::getGUID("_Z2f3v")));
103+
}
104+
} // namespace

0 commit comments

Comments
 (0)