[llvm] [memprof] Add extractCallsFromIR (PR #115218)
via llvm-commits
llvm-commits at lists.llvm.org
Wed Nov 6 13:50:14 PST 2024
llvmbot wrote:
<!--LLVM PR SUMMARY COMMENT-->
@llvm/pr-subscribers-llvm-transforms
Author: Kazu Hirata (kazutakahirata)
<details>
<summary>Changes</summary>
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.
---
Full diff: https://github.com/llvm/llvm-project/pull/115218.diff
4 Files Affected:
- (modified) llvm/include/llvm/Transforms/Instrumentation/MemProfiler.h (+35)
- (modified) llvm/lib/Transforms/Instrumentation/MemProfiler.cpp (+47)
- (modified) llvm/unittests/Transforms/Instrumentation/CMakeLists.txt (+1)
- (added) llvm/unittests/Transforms/Instrumentation/MemProfUseTest.cpp (+108)
``````````diff
diff --git a/llvm/include/llvm/Transforms/Instrumentation/MemProfiler.h b/llvm/include/llvm/Transforms/Instrumentation/MemProfiler.h
index f92c6b4775a2a2..076a2785bbaa77 100644
--- a/llvm/include/llvm/Transforms/Instrumentation/MemProfiler.h
+++ b/llvm/include/llvm/Transforms/Instrumentation/MemProfiler.h
@@ -57,6 +57,41 @@ class MemProfUsePass : public PassInfoMixin<MemProfUsePass> {
IntrusiveRefCntPtr<vfs::FileSystem> FS;
};
+namespace memprof {
+
+struct LineLocation {
+ LineLocation(uint32_t L, uint32_t D) : LineOffset(L), Column(D) {}
+
+ void print(raw_ostream &OS) const;
+ void dump() const;
+
+ bool operator<(const LineLocation &O) const {
+ return LineOffset < O.LineOffset ||
+ (LineOffset == O.LineOffset && Column < O.Column);
+ }
+
+ bool operator==(const LineLocation &O) const {
+ return LineOffset == O.LineOffset && Column == O.Column;
+ }
+
+ bool operator!=(const LineLocation &O) const {
+ return LineOffset != O.LineOffset || Column != O.Column;
+ }
+
+ uint64_t getHashCode() const { return ((uint64_t)Column << 32) | LineOffset; }
+
+ uint32_t LineOffset;
+ uint32_t Column;
+};
+
+// A pair of a call site location and its corresponding callee GUID.
+using CallEdgeTy = std::pair<LineLocation, uint64_t>;
+
+// Extract all calls from the IR. Arrange them in a map from caller GUIDs to a
+// list of call sites, each of the form {LineLocation, CalleeGUID}.
+DenseMap<uint64_t, SmallVector<CallEdgeTy, 0>> extractCallsFromIR(Module &M);
+
+} // namespace memprof
} // namespace llvm
#endif
diff --git a/llvm/lib/Transforms/Instrumentation/MemProfiler.cpp b/llvm/lib/Transforms/Instrumentation/MemProfiler.cpp
index 70bee30fd151f6..fef11d9ffe306f 100644
--- a/llvm/lib/Transforms/Instrumentation/MemProfiler.cpp
+++ b/llvm/lib/Transforms/Instrumentation/MemProfiler.cpp
@@ -795,6 +795,53 @@ struct AllocMatchInfo {
bool Matched = false;
};
+DenseMap<uint64_t, SmallVector<CallEdgeTy, 0>>
+memprof::extractCallsFromIR(Module &M) {
+ DenseMap<uint64_t, SmallVector<CallEdgeTy, 0>> Calls;
+
+ auto GetOffset = [](const DILocation *DIL) {
+ return (DIL->getLine() - DIL->getScope()->getSubprogram()->getLine()) &
+ 0xffff;
+ };
+
+ for (Function &F : M) {
+ if (F.isDeclaration())
+ continue;
+
+ for (auto &BB : F) {
+ for (auto &I : BB) {
+ const DILocation *DIL = I.getDebugLoc();
+ if (!DIL)
+ continue;
+
+ if (!isa<CallBase>(&I) || isa<IntrinsicInst>(&I))
+ continue;
+
+ auto *CB = dyn_cast<CallBase>(&I);
+ auto *CalledFunction = CB->getCalledFunction();
+ if (!CalledFunction || CalledFunction->isIntrinsic())
+ continue;
+
+ StringRef CalleeName = CalledFunction->getName();
+ uint64_t CallerGUID =
+ IndexedMemProfRecord::getGUID(DIL->getSubprogramLinkageName());
+ uint64_t CalleeGUID = IndexedMemProfRecord::getGUID(CalleeName);
+ LineLocation Loc = {GetOffset(DIL), DIL->getColumn()};
+ Calls[CallerGUID].emplace_back(Loc, CalleeGUID);
+ }
+ }
+ }
+
+ // Sort each call list by the source location.
+ for (auto &KV : Calls) {
+ auto &Calls = KV.second;
+ llvm::sort(Calls);
+ Calls.erase(llvm::unique(Calls), Calls.end());
+ }
+
+ return Calls;
+}
+
static void
readMemprof(Module &M, Function &F, IndexedInstrProfReader *MemProfReader,
const TargetLibraryInfo &TLI,
diff --git a/llvm/unittests/Transforms/Instrumentation/CMakeLists.txt b/llvm/unittests/Transforms/Instrumentation/CMakeLists.txt
index 1f249b0049d062..80fac2353be416 100644
--- a/llvm/unittests/Transforms/Instrumentation/CMakeLists.txt
+++ b/llvm/unittests/Transforms/Instrumentation/CMakeLists.txt
@@ -8,6 +8,7 @@ set(LLVM_LINK_COMPONENTS
)
add_llvm_unittest(InstrumentationTests
+ MemProfUseTest.cpp
PGOInstrumentationTest.cpp
)
diff --git a/llvm/unittests/Transforms/Instrumentation/MemProfUseTest.cpp b/llvm/unittests/Transforms/Instrumentation/MemProfUseTest.cpp
new file mode 100644
index 00000000000000..21c7537852c4df
--- /dev/null
+++ b/llvm/unittests/Transforms/Instrumentation/MemProfUseTest.cpp
@@ -0,0 +1,108 @@
+//===- MemProfUseTest.cpp - MemProf use tests -----------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+#include "llvm/AsmParser/Parser.h"
+#include "llvm/IR/LLVMContext.h"
+#include "llvm/IR/Module.h"
+#include "llvm/ProfileData/MemProf.h"
+#include "llvm/Support/SourceMgr.h"
+#include "llvm/Transforms/Instrumentation/MemProfiler.h"
+
+#include "gmock/gmock.h"
+#include "gtest/gtest.h"
+
+namespace {
+using namespace llvm;
+
+TEST(MemProf, ExtractDirectCallsFromIR) {
+ // The following IR is generated from:
+ //
+ // void f1();
+ // void f2();
+ // void f3();
+ //
+ // void foo() {
+ // f1();
+ // f2(); f3();
+ // }
+ StringRef IR = R"IR(
+define dso_local void @_Z3foov() !dbg !10 {
+entry:
+ call void @_Z2f1v(), !dbg !13
+ call void @_Z2f2v(), !dbg !14
+ call void @_Z2f3v(), !dbg !15
+ ret void, !dbg !16
+}
+
+declare !dbg !17 void @_Z2f1v()
+
+declare !dbg !18 void @_Z2f2v()
+
+declare !dbg !19 void @_Z2f3v()
+
+!llvm.dbg.cu = !{!0}
+!llvm.module.flags = !{!2, !3, !4, !5, !6, !7, !8}
+!llvm.ident = !{!9}
+
+!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)
+!1 = !DIFile(filename: "foobar.cc", directory: "/")
+!2 = !{i32 7, !"Dwarf Version", i32 5}
+!3 = !{i32 2, !"Debug Info Version", i32 3}
+!4 = !{i32 1, !"wchar_size", i32 4}
+!5 = !{i32 1, !"MemProfProfileFilename", !"memprof.profraw"}
+!6 = !{i32 8, !"PIC Level", i32 2}
+!7 = !{i32 7, !"PIE Level", i32 2}
+!8 = !{i32 7, !"uwtable", i32 2}
+!9 = !{!"clang"}
+!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)
+!11 = !DISubroutineType(types: !12)
+!12 = !{}
+!13 = !DILocation(line: 6, column: 3, scope: !10)
+!14 = !DILocation(line: 7, column: 3, scope: !10)
+!15 = !DILocation(line: 7, column: 9, scope: !10)
+!16 = !DILocation(line: 8, column: 1, scope: !10)
+!17 = !DISubprogram(name: "f1", linkageName: "_Z2f1v", scope: !1, file: !1, line: 1, type: !11, flags: DIFlagPrototyped, spFlags: DISPFlagOptimized)
+!18 = !DISubprogram(name: "f2", linkageName: "_Z2f2v", scope: !1, file: !1, line: 2, type: !11, flags: DIFlagPrototyped, spFlags: DISPFlagOptimized)
+!19 = !DISubprogram(name: "f3", linkageName: "_Z2f3v", scope: !1, file: !1, line: 3, type: !11, flags: DIFlagPrototyped, spFlags: DISPFlagOptimized)
+)IR";
+
+ LLVMContext Ctx;
+ SMDiagnostic Err;
+ std::unique_ptr<Module> M = parseAssemblyString(IR, Err, Ctx);
+ ASSERT_TRUE(M);
+
+ auto Calls = memprof::extractCallsFromIR(*M);
+
+ // Expect exactly one caller.
+ ASSERT_THAT(Calls, testing::SizeIs(1));
+
+ auto It = Calls.begin();
+ ASSERT_NE(It, Calls.end());
+
+ const auto &[CallerGUID, CallSites] = *It;
+ EXPECT_EQ(CallerGUID, memprof::IndexedMemProfRecord::getGUID("_Z3foov"));
+ ASSERT_THAT(CallSites, testing::SizeIs(3));
+
+ // Verify that call sites show up in the ascending order of their source
+ // locations.
+ EXPECT_EQ(CallSites[0].first.LineOffset, 1U);
+ EXPECT_EQ(CallSites[0].first.Column, 3U);
+ EXPECT_EQ(CallSites[0].second,
+ memprof::IndexedMemProfRecord::getGUID("_Z2f1v"));
+
+ EXPECT_EQ(CallSites[1].first.LineOffset, 2U);
+ EXPECT_EQ(CallSites[1].first.Column, 3U);
+ EXPECT_EQ(CallSites[1].second,
+ memprof::IndexedMemProfRecord::getGUID("_Z2f2v"));
+
+ EXPECT_EQ(CallSites[2].first.LineOffset, 2U);
+ EXPECT_EQ(CallSites[2].first.Column, 9U);
+ EXPECT_EQ(CallSites[2].second,
+ memprof::IndexedMemProfRecord::getGUID("_Z2f3v"));
+}
+} // namespace
``````````
</details>
https://github.com/llvm/llvm-project/pull/115218
More information about the llvm-commits
mailing list