Skip to content

Commit ed3c386

Browse files
[memprof] Add extractCallsFromIR
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 f85be26 commit ed3c386

File tree

4 files changed

+191
-0
lines changed

4 files changed

+191
-0
lines changed

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

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,41 @@ 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+
void print(raw_ostream &OS) const;
66+
void dump() const;
67+
68+
bool operator<(const LineLocation &O) const {
69+
return LineOffset < O.LineOffset ||
70+
(LineOffset == O.LineOffset && Column < O.Column);
71+
}
72+
73+
bool operator==(const LineLocation &O) const {
74+
return LineOffset == O.LineOffset && Column == O.Column;
75+
}
76+
77+
bool operator!=(const LineLocation &O) const {
78+
return LineOffset != O.LineOffset || Column != O.Column;
79+
}
80+
81+
uint64_t getHashCode() const { return ((uint64_t)Column << 32) | LineOffset; }
82+
83+
uint32_t LineOffset;
84+
uint32_t Column;
85+
};
86+
87+
// A pair of a call site location and its corresponding callee GUID.
88+
using CallEdgeTy = std::pair<LineLocation, uint64_t>;
89+
90+
// Extract all calls from the IR. Arrange them in a map from caller GUIDs to a
91+
// list of call sites, each of the form {LineLocation, CalleeGUID}.
92+
DenseMap<uint64_t, SmallVector<CallEdgeTy, 0>> extractCallsFromIR(Module &M);
93+
94+
} // namespace memprof
6095
} // namespace llvm
6196

6297
#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+
if (!CalledFunction || CalledFunction->isIntrinsic())
823+
continue;
824+
825+
StringRef CalleeName = CalledFunction->getName();
826+
uint64_t CallerGUID =
827+
IndexedMemProfRecord::getGUID(DIL->getSubprogramLinkageName());
828+
uint64_t CalleeGUID = IndexedMemProfRecord::getGUID(CalleeName);
829+
LineLocation Loc = {GetOffset(DIL), DIL->getColumn()};
830+
Calls[CallerGUID].emplace_back(Loc, CalleeGUID);
831+
}
832+
}
833+
}
834+
835+
// Sort each call list by the source location.
836+
for (auto &KV : Calls) {
837+
auto &Calls = KV.second;
838+
llvm::sort(Calls);
839+
Calls.erase(llvm::unique(Calls), Calls.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: 108 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,108 @@
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+
22+
TEST(MemProf, ExtractDirectCallsFromIR) {
23+
// The following IR is generated from:
24+
//
25+
// void f1();
26+
// void f2();
27+
// void f3();
28+
//
29+
// void foo() {
30+
// f1();
31+
// f2(); f3();
32+
// }
33+
StringRef IR = R"IR(
34+
define dso_local void @_Z3foov() !dbg !10 {
35+
entry:
36+
call void @_Z2f1v(), !dbg !13
37+
call void @_Z2f2v(), !dbg !14
38+
call void @_Z2f3v(), !dbg !15
39+
ret void, !dbg !16
40+
}
41+
42+
declare !dbg !17 void @_Z2f1v()
43+
44+
declare !dbg !18 void @_Z2f2v()
45+
46+
declare !dbg !19 void @_Z2f3v()
47+
48+
!llvm.dbg.cu = !{!0}
49+
!llvm.module.flags = !{!2, !3, !4, !5, !6, !7, !8}
50+
!llvm.ident = !{!9}
51+
52+
!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)
53+
!1 = !DIFile(filename: "foobar.cc", directory: "/")
54+
!2 = !{i32 7, !"Dwarf Version", i32 5}
55+
!3 = !{i32 2, !"Debug Info Version", i32 3}
56+
!4 = !{i32 1, !"wchar_size", i32 4}
57+
!5 = !{i32 1, !"MemProfProfileFilename", !"memprof.profraw"}
58+
!6 = !{i32 8, !"PIC Level", i32 2}
59+
!7 = !{i32 7, !"PIE Level", i32 2}
60+
!8 = !{i32 7, !"uwtable", i32 2}
61+
!9 = !{!"clang"}
62+
!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)
63+
!11 = !DISubroutineType(types: !12)
64+
!12 = !{}
65+
!13 = !DILocation(line: 6, column: 3, scope: !10)
66+
!14 = !DILocation(line: 7, column: 3, scope: !10)
67+
!15 = !DILocation(line: 7, column: 9, scope: !10)
68+
!16 = !DILocation(line: 8, column: 1, scope: !10)
69+
!17 = !DISubprogram(name: "f1", linkageName: "_Z2f1v", scope: !1, file: !1, line: 1, type: !11, flags: DIFlagPrototyped, spFlags: DISPFlagOptimized)
70+
!18 = !DISubprogram(name: "f2", linkageName: "_Z2f2v", scope: !1, file: !1, line: 2, type: !11, flags: DIFlagPrototyped, spFlags: DISPFlagOptimized)
71+
!19 = !DISubprogram(name: "f3", linkageName: "_Z2f3v", scope: !1, file: !1, line: 3, type: !11, flags: DIFlagPrototyped, spFlags: DISPFlagOptimized)
72+
)IR";
73+
74+
LLVMContext Ctx;
75+
SMDiagnostic Err;
76+
std::unique_ptr<Module> M = parseAssemblyString(IR, Err, Ctx);
77+
ASSERT_TRUE(M);
78+
79+
auto Calls = memprof::extractCallsFromIR(*M);
80+
81+
// Expect exactly one caller.
82+
ASSERT_THAT(Calls, testing::SizeIs(1));
83+
84+
auto It = Calls.begin();
85+
ASSERT_NE(It, Calls.end());
86+
87+
const auto &[CallerGUID, CallSites] = *It;
88+
EXPECT_EQ(CallerGUID, memprof::IndexedMemProfRecord::getGUID("_Z3foov"));
89+
ASSERT_THAT(CallSites, testing::SizeIs(3));
90+
91+
// Verify that call sites show up in the ascending order of their source
92+
// locations.
93+
EXPECT_EQ(CallSites[0].first.LineOffset, 1U);
94+
EXPECT_EQ(CallSites[0].first.Column, 3U);
95+
EXPECT_EQ(CallSites[0].second,
96+
memprof::IndexedMemProfRecord::getGUID("_Z2f1v"));
97+
98+
EXPECT_EQ(CallSites[1].first.LineOffset, 2U);
99+
EXPECT_EQ(CallSites[1].first.Column, 3U);
100+
EXPECT_EQ(CallSites[1].second,
101+
memprof::IndexedMemProfRecord::getGUID("_Z2f2v"));
102+
103+
EXPECT_EQ(CallSites[2].first.LineOffset, 2U);
104+
EXPECT_EQ(CallSites[2].first.Column, 9U);
105+
EXPECT_EQ(CallSites[2].second,
106+
memprof::IndexedMemProfRecord::getGUID("_Z2f3v"));
107+
}
108+
} // namespace

0 commit comments

Comments
 (0)