Skip to content

Commit a2e266b

Browse files
[memprof] Add computeUndriftMap (#116478)
This patch adds computeUndriftMap, a function to compute mappings from source locations in the MemProf profile to source locations in the IR.
1 parent 314e9b1 commit a2e266b

File tree

3 files changed

+239
-0
lines changed

3 files changed

+239
-0
lines changed

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

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818

1919
namespace llvm {
2020
class Function;
21+
class IndexedInstrProfReader;
2122
class Module;
2223
class TargetLibraryInfo;
2324

@@ -66,6 +67,21 @@ namespace memprof {
6667
DenseMap<uint64_t, SmallVector<CallEdgeTy, 0>>
6768
extractCallsFromIR(Module &M, const TargetLibraryInfo &TLI);
6869

70+
struct LineLocationHash {
71+
uint64_t operator()(const LineLocation &Loc) const {
72+
return Loc.getHashCode();
73+
}
74+
};
75+
76+
using LocToLocMap =
77+
std::unordered_map<LineLocation, LineLocation, LineLocationHash>;
78+
79+
// Compute an undrifting map. The result is a map from caller GUIDs to an inner
80+
// map that maps source locations in the profile to those in the current IR.
81+
DenseMap<uint64_t, LocToLocMap>
82+
computeUndriftMap(Module &M, IndexedInstrProfReader *MemProfReader,
83+
const TargetLibraryInfo &TLI);
84+
6985
} // namespace memprof
7086
} // namespace llvm
7187

llvm/lib/Transforms/Instrumentation/MemProfiler.cpp

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@
4242
#include "llvm/Support/VirtualFileSystem.h"
4343
#include "llvm/TargetParser/Triple.h"
4444
#include "llvm/Transforms/Utils/BasicBlockUtils.h"
45+
#include "llvm/Transforms/Utils/LongestCommonSequence.h"
4546
#include "llvm/Transforms/Utils/ModuleUtils.h"
4647
#include <map>
4748
#include <set>
@@ -856,6 +857,37 @@ memprof::extractCallsFromIR(Module &M, const TargetLibraryInfo &TLI) {
856857
return Calls;
857858
}
858859

860+
DenseMap<uint64_t, LocToLocMap>
861+
memprof::computeUndriftMap(Module &M, IndexedInstrProfReader *MemProfReader,
862+
const TargetLibraryInfo &TLI) {
863+
DenseMap<uint64_t, LocToLocMap> UndriftMaps;
864+
865+
DenseMap<uint64_t, SmallVector<memprof::CallEdgeTy, 0>> CallsFromProfile =
866+
MemProfReader->getMemProfCallerCalleePairs();
867+
DenseMap<uint64_t, SmallVector<memprof::CallEdgeTy, 0>> CallsFromIR =
868+
extractCallsFromIR(M, TLI);
869+
870+
// Compute an undrift map for each CallerGUID.
871+
for (const auto &[CallerGUID, IRAnchors] : CallsFromIR) {
872+
auto It = CallsFromProfile.find(CallerGUID);
873+
if (It == CallsFromProfile.end())
874+
continue;
875+
const auto &ProfileAnchors = It->second;
876+
877+
LocToLocMap Matchings;
878+
longestCommonSequence<LineLocation, GlobalValue::GUID>(
879+
ProfileAnchors, IRAnchors, std::equal_to<GlobalValue::GUID>(),
880+
[&](LineLocation A, LineLocation B) { Matchings.try_emplace(A, B); });
881+
bool Inserted = UndriftMaps.try_emplace(CallerGUID, Matchings).second;
882+
883+
// The insertion must succeed because we visit each GUID exactly once.
884+
assert(Inserted);
885+
(void)Inserted;
886+
}
887+
888+
return UndriftMaps;
889+
}
890+
859891
static void
860892
readMemprof(Module &M, Function &F, IndexedInstrProfReader *MemProfReader,
861893
const TargetLibraryInfo &TLI,

llvm/unittests/Transforms/Instrumentation/MemProfUseTest.cpp

Lines changed: 191 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,11 @@
1111
#include "llvm/IR/LLVMContext.h"
1212
#include "llvm/IR/Module.h"
1313
#include "llvm/Passes/PassBuilder.h"
14+
#include "llvm/ProfileData/InstrProfReader.h"
15+
#include "llvm/ProfileData/InstrProfWriter.h"
1416
#include "llvm/ProfileData/MemProf.h"
1517
#include "llvm/Support/SourceMgr.h"
18+
#include "llvm/Testing/Support/Error.h"
1619
#include "llvm/Transforms/Instrumentation/MemProfiler.h"
1720

1821
#include "gmock/gmock.h"
@@ -21,9 +24,12 @@
2124
namespace {
2225
using namespace llvm;
2326
using namespace llvm::memprof;
27+
using testing::Contains;
28+
using testing::ElementsAre;
2429
using testing::FieldsAre;
2530
using testing::Pair;
2631
using testing::SizeIs;
32+
using testing::UnorderedElementsAre;
2733

2834
TEST(MemProf, ExtractDirectCallsFromIR) {
2935
// The following IR is generated from:
@@ -298,4 +304,189 @@ attributes #2 = { builtin allocsize(0) }
298304
ASSERT_THAT(FooCallSites, SizeIs(1));
299305
EXPECT_THAT(FooCallSites[0], Pair(FieldsAre(1U, 10U), 0));
300306
}
307+
308+
// Populate those fields returned by getHotColdSchema.
309+
MemInfoBlock makePartialMIB() {
310+
MemInfoBlock MIB;
311+
MIB.AllocCount = 1;
312+
MIB.TotalSize = 5;
313+
MIB.TotalLifetime = 10;
314+
MIB.TotalLifetimeAccessDensity = 23;
315+
return MIB;
316+
}
317+
318+
IndexedMemProfRecord
319+
makeRecordV2(std::initializer_list<::llvm::memprof::CallStackId> AllocFrames,
320+
std::initializer_list<::llvm::memprof::CallStackId> CallSiteFrames,
321+
const MemInfoBlock &Block, const memprof::MemProfSchema &Schema) {
322+
llvm::memprof::IndexedMemProfRecord MR;
323+
for (const auto &CSId : AllocFrames) {
324+
// We don't populate IndexedAllocationInfo::CallStack because we use it only
325+
// in Version1.
326+
MR.AllocSites.push_back({{}, CSId, Block, Schema});
327+
}
328+
for (const auto &CSId : CallSiteFrames)
329+
MR.CallSiteIds.push_back(CSId);
330+
return MR;
331+
}
332+
333+
static const auto Err = [](Error E) {
334+
FAIL() << E;
335+
consumeError(std::move(E));
336+
};
337+
338+
// Make sure that we can undrift direct calls.
339+
TEST(MemProf, ComputeUndriftingMap) {
340+
// Suppose that the source code has changed from:
341+
//
342+
// void bar();
343+
// void baz();
344+
// void zzz();
345+
//
346+
// void foo() {
347+
// /**/ bar(); // LineLocation(1, 8)
348+
// zzz(); // LineLocation(2, 3)
349+
// baz(); // LineLocation(3, 3)
350+
// }
351+
//
352+
// to:
353+
//
354+
// void bar();
355+
// void baz();
356+
//
357+
// void foo() {
358+
// bar(); // LineLocation(1, 3)
359+
// /**/ baz(); // LineLocation(2, 8)
360+
// }
361+
//
362+
// Notice that the calls to bar and baz have drifted while zzz has been
363+
// removed.
364+
StringRef IR = R"IR(
365+
define dso_local void @_Z3foov() #0 !dbg !10 {
366+
entry:
367+
call void @_Z3barv(), !dbg !13
368+
call void @_Z3bazv(), !dbg !14
369+
ret void, !dbg !15
370+
}
371+
372+
declare !dbg !16 void @_Z3barv() #1
373+
374+
declare !dbg !17 void @_Z3bazv() #1
375+
376+
attributes #0 = { mustprogress uwtable "min-legal-vector-width"="0" "no-trapping-math"="true" "stack-protector-buffer-size"="8" "target-cpu"="x86-64" "target-features"="+cmov,+cx8,+fxsr,+mmx,+sse,+sse2,+x87" "tune-cpu"="generic" }
377+
attributes #1 = { "no-trapping-math"="true" "stack-protector-buffer-size"="8" "target-cpu"="x86-64" "target-features"="+cmov,+cx8,+fxsr,+mmx,+sse,+sse2,+x87" "tune-cpu"="generic" }
378+
379+
!llvm.dbg.cu = !{!0}
380+
!llvm.module.flags = !{!2, !3, !4, !5, !6, !7, !8}
381+
!llvm.ident = !{!9}
382+
383+
!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)
384+
!1 = !DIFile(filename: "foobar.cc", directory: "/")
385+
!2 = !{i32 7, !"Dwarf Version", i32 5}
386+
!3 = !{i32 2, !"Debug Info Version", i32 3}
387+
!4 = !{i32 1, !"wchar_size", i32 4}
388+
!5 = !{i32 1, !"MemProfProfileFilename", !"memprof.profraw"}
389+
!6 = !{i32 8, !"PIC Level", i32 2}
390+
!7 = !{i32 7, !"PIE Level", i32 2}
391+
!8 = !{i32 7, !"uwtable", i32 2}
392+
!9 = !{!"clang"}
393+
!10 = distinct !DISubprogram(name: "foo", linkageName: "_Z3foov", scope: !1, file: !1, line: 4, type: !11, scopeLine: 4, flags: DIFlagPrototyped | DIFlagAllCallsDescribed, spFlags: DISPFlagDefinition | DISPFlagOptimized, unit: !0)
394+
!11 = !DISubroutineType(types: !12)
395+
!12 = !{}
396+
!13 = !DILocation(line: 5, column: 3, scope: !10)
397+
!14 = !DILocation(line: 6, column: 8, scope: !10)
398+
!15 = !DILocation(line: 7, column: 1, scope: !10)
399+
!16 = !DISubprogram(name: "bar", linkageName: "_Z3barv", scope: !1, file: !1, line: 1, type: !11, flags: DIFlagPrototyped, spFlags: DISPFlagOptimized)
400+
!17 = !DISubprogram(name: "baz", linkageName: "_Z3bazv", scope: !1, file: !1, line: 2, type: !11, flags: DIFlagPrototyped, spFlags: DISPFlagOptimized)
401+
)IR";
402+
403+
LLVMContext Ctx;
404+
SMDiagnostic SMErr;
405+
std::unique_ptr<Module> M = parseAssemblyString(IR, SMErr, Ctx);
406+
ASSERT_TRUE(M);
407+
408+
auto *F = M->getFunction("_Z3foov");
409+
ASSERT_NE(F, nullptr);
410+
411+
TargetLibraryInfoWrapperPass WrapperPass;
412+
auto &TLI = WrapperPass.getTLI(*F);
413+
auto Calls = extractCallsFromIR(*M, TLI);
414+
415+
uint64_t GUIDFoo = IndexedMemProfRecord::getGUID("_Z3foov");
416+
uint64_t GUIDBar = IndexedMemProfRecord::getGUID("_Z3barv");
417+
uint64_t GUIDBaz = IndexedMemProfRecord::getGUID("_Z3bazv");
418+
uint64_t GUIDZzz = IndexedMemProfRecord::getGUID("_Z3zzzv");
419+
420+
// Verify that extractCallsFromIR extracts caller-callee pairs as expected.
421+
EXPECT_THAT(Calls,
422+
UnorderedElementsAre(Pair(
423+
GUIDFoo, ElementsAre(Pair(LineLocation(1, 3), GUIDBar),
424+
Pair(LineLocation(2, 8), GUIDBaz)))));
425+
426+
llvm::InstrProfWriter Writer;
427+
std::unique_ptr<IndexedInstrProfReader> Reader;
428+
429+
const MemInfoBlock MIB = makePartialMIB();
430+
431+
Writer.setMemProfVersionRequested(memprof::Version3);
432+
Writer.setMemProfFullSchema(false);
433+
434+
ASSERT_THAT_ERROR(Writer.mergeProfileKind(InstrProfKind::MemProf),
435+
Succeeded());
436+
437+
const std::pair<memprof::FrameId, memprof::Frame> Frames[] = {
438+
// The call sites within foo.
439+
{0, {GUIDFoo, 1, 8, false}},
440+
{1, {GUIDFoo, 2, 3, false}},
441+
{2, {GUIDFoo, 3, 3, false}},
442+
// Line/column numbers below don't matter.
443+
{3, {GUIDBar, 9, 9, false}},
444+
{4, {GUIDZzz, 9, 9, false}},
445+
{5, {GUIDBaz, 9, 9, false}}};
446+
for (const auto &[FrameId, Frame] : Frames)
447+
Writer.addMemProfFrame(FrameId, Frame, Err);
448+
449+
const std::pair<memprof::CallStackId, SmallVector<memprof::FrameId>>
450+
CallStacks[] = {
451+
{0x111, {3, 0}}, // bar called by foo
452+
{0x222, {4, 1}}, // zzz called by foo
453+
{0x333, {5, 2}} // baz called by foo
454+
};
455+
for (const auto &[CSId, CallStack] : CallStacks)
456+
Writer.addMemProfCallStack(CSId, CallStack, Err);
457+
458+
const IndexedMemProfRecord IndexedMR = makeRecordV2(
459+
/*AllocFrames=*/{0x111, 0x222, 0x333},
460+
/*CallSiteFrames=*/{}, MIB, memprof::getHotColdSchema());
461+
Writer.addMemProfRecord(/*Id=*/0x9999, IndexedMR);
462+
463+
auto Profile = Writer.writeBuffer();
464+
465+
auto ReaderOrErr =
466+
IndexedInstrProfReader::create(std::move(Profile), nullptr);
467+
EXPECT_THAT_ERROR(ReaderOrErr.takeError(), Succeeded());
468+
Reader = std::move(ReaderOrErr.get());
469+
470+
// Verify that getMemProfCallerCalleePairs extracts caller-callee pairs as
471+
// expected.
472+
auto Pairs = Reader->getMemProfCallerCalleePairs();
473+
ASSERT_THAT(Pairs, SizeIs(4));
474+
ASSERT_THAT(
475+
Pairs,
476+
Contains(Pair(GUIDFoo, ElementsAre(Pair(LineLocation(1, 8), GUIDBar),
477+
Pair(LineLocation(2, 3), GUIDZzz),
478+
Pair(LineLocation(3, 3), GUIDBaz)))));
479+
480+
// Verify that computeUndriftMap identifies undrifting opportunities:
481+
//
482+
// Profile IR
483+
// (Line: 1, Column: 8) -> (Line: 1, Column: 3)
484+
// (Line: 3, Column: 3) -> (Line: 2, Column: 8)
485+
auto UndriftMap = computeUndriftMap(*M, Reader.get(), TLI);
486+
ASSERT_THAT(UndriftMap,
487+
UnorderedElementsAre(Pair(
488+
GUIDFoo, UnorderedElementsAre(
489+
Pair(LineLocation(1, 8), LineLocation(1, 3)),
490+
Pair(LineLocation(3, 3), LineLocation(2, 8))))));
491+
}
301492
} // namespace

0 commit comments

Comments
 (0)