|
| 1 | +//===- MemProfilerTest.cpp - MemProfiler unit 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/Transforms/Instrumentation/MemProfiler.h" |
| 10 | +#include "llvm/ADT/StringRef.h" |
| 11 | +#include "llvm/Analysis/TargetLibraryInfo.h" |
| 12 | +#include "llvm/AsmParser/Parser.h" |
| 13 | +#include "llvm/IR/Attributes.h" |
| 14 | +#include "llvm/IR/Metadata.h" |
| 15 | +#include "llvm/IR/Module.h" |
| 16 | +#include "llvm/IR/PassManager.h" |
| 17 | +#include "llvm/Passes/PassBuilder.h" |
| 18 | +#include "llvm/ProfileData/InstrProfReader.h" |
| 19 | +#include "llvm/ProfileData/MemProf.h" |
| 20 | +#include "llvm/ProfileData/MemProfData.inc" |
| 21 | +#include "llvm/Support/Error.h" |
| 22 | +#include "llvm/Support/SourceMgr.h" |
| 23 | + |
| 24 | +#include "gmock/gmock.h" |
| 25 | +#include "gtest/gtest.h" |
| 26 | + |
| 27 | +extern llvm::cl::opt<bool> ClMemProfMatchHotColdNew; |
| 28 | + |
| 29 | +namespace llvm { |
| 30 | +namespace memprof { |
| 31 | +namespace { |
| 32 | + |
| 33 | +using ::testing::Return; |
| 34 | +using ::testing::SizeIs; |
| 35 | + |
| 36 | +struct MemProfilerTest : public ::testing::Test { |
| 37 | + LLVMContext Context; |
| 38 | + std::unique_ptr<Module> M; |
| 39 | + |
| 40 | + MemProfilerTest() { ClMemProfMatchHotColdNew = true; } |
| 41 | + |
| 42 | + void parseAssembly(const StringRef IR) { |
| 43 | + SMDiagnostic Error; |
| 44 | + M = parseAssemblyString(IR, Error, Context); |
| 45 | + std::string ErrMsg; |
| 46 | + raw_string_ostream OS(ErrMsg); |
| 47 | + Error.print("", OS); |
| 48 | + |
| 49 | + // A failure here means that the test itself is buggy. |
| 50 | + if (!M) |
| 51 | + report_fatal_error(OS.str().c_str()); |
| 52 | + } |
| 53 | +}; |
| 54 | + |
| 55 | +// A mock memprof reader we can inject into the function we are testing. |
| 56 | +class MockMemProfReader : public IndexedMemProfReader { |
| 57 | +public: |
| 58 | + MOCK_METHOD(Expected<MemProfRecord>, getMemProfRecord, |
| 59 | + (const uint64_t FuncNameHash), (const, override)); |
| 60 | + |
| 61 | + // A helper function to create mock records from frames. |
| 62 | + static MemProfRecord makeRecord(ArrayRef<ArrayRef<Frame>> AllocFrames) { |
| 63 | + MemProfRecord Record; |
| 64 | + MemInfoBlock Info; |
| 65 | + // Mimic values which will be below the cold threshold. |
| 66 | + Info.AllocCount = 1, Info.TotalSize = 550; |
| 67 | + Info.TotalLifetime = 1000 * 1000, Info.TotalLifetimeAccessDensity = 1; |
| 68 | + for (const auto &Callstack : AllocFrames) { |
| 69 | + AllocationInfo AI; |
| 70 | + AI.Info = PortableMemInfoBlock(Info, getHotColdSchema()); |
| 71 | + AI.CallStack = std::vector(Callstack.begin(), Callstack.end()); |
| 72 | + Record.AllocSites.push_back(AI); |
| 73 | + } |
| 74 | + return Record; |
| 75 | + } |
| 76 | +}; |
| 77 | + |
| 78 | +TEST_F(MemProfilerTest, AnnotatesCall) { |
| 79 | + parseAssembly(R"IR( |
| 80 | + target datalayout = "e-m:e-p270:32:32-p271:32:32-p272:64:64-i64:64-f80:128-n8:16:32:64-S128" |
| 81 | + target triple = "x86_64-unknown-linux-gnu" |
| 82 | +
|
| 83 | + define void @_Z3foov() !dbg !10 { |
| 84 | + entry: |
| 85 | + %c1 = call {ptr, i64} @__size_returning_new(i64 32), !dbg !13 |
| 86 | + %c2 = call {ptr, i64} @__size_returning_new_aligned(i64 32, i64 8), !dbg !14 |
| 87 | + %c3 = call {ptr, i64} @__size_returning_new_hot_cold(i64 32, i8 254), !dbg !15 |
| 88 | + %c4 = call {ptr, i64} @__size_returning_new_aligned_hot_cold(i64 32, i64 8, i8 254), !dbg !16 |
| 89 | + ret void |
| 90 | + } |
| 91 | +
|
| 92 | + declare {ptr, i64} @__size_returning_new(i64) |
| 93 | + declare {ptr, i64} @__size_returning_new_aligned(i64, i64) |
| 94 | + declare {ptr, i64} @__size_returning_new_hot_cold(i64, i8) |
| 95 | + declare {ptr, i64} @__size_returning_new_aligned_hot_cold(i64, i64, i8) |
| 96 | +
|
| 97 | + !llvm.dbg.cu = !{!0} |
| 98 | + !llvm.module.flags = !{!2, !3} |
| 99 | +
|
| 100 | + !0 = distinct !DICompileUnit(language: DW_LANG_C_plus_plus_14, file: !1) |
| 101 | + !1 = !DIFile(filename: "mock_file.cc", directory: "mock_dir") |
| 102 | + !2 = !{i32 7, !"Dwarf Version", i32 5} |
| 103 | + !3 = !{i32 2, !"Debug Info Version", i32 3} |
| 104 | + !10 = distinct !DISubprogram(name: "foo", linkageName: "_Z3foov", scope: !1, file: !1, line: 4, type: !11, scopeLine: 4, unit: !0, retainedNodes: !12) |
| 105 | + !11 = !DISubroutineType(types: !12) |
| 106 | + !12 = !{} |
| 107 | + !13 = !DILocation(line: 5, column: 10, scope: !10) |
| 108 | + !14 = !DILocation(line: 6, column: 10, scope: !10) |
| 109 | + !15 = !DILocation(line: 7, column: 10, scope: !10) |
| 110 | + !16 = !DILocation(line: 8, column: 10, scope: !10) |
| 111 | + )IR"); |
| 112 | + |
| 113 | + auto *F = M->getFunction("_Z3foov"); |
| 114 | + ASSERT_NE(F, nullptr); |
| 115 | + |
| 116 | + TargetLibraryInfoWrapperPass WrapperPass; |
| 117 | + auto &TLI = WrapperPass.getTLI(*F); |
| 118 | + |
| 119 | + auto Guid = Function::getGUID("_Z3foov"); |
| 120 | + // All the allocation sites are in foo(). |
| 121 | + MemProfRecord MockRecord = |
| 122 | + MockMemProfReader::makeRecord({{Frame(Guid, 1, 10, false)}, |
| 123 | + {Frame(Guid, 2, 10, false)}, |
| 124 | + {Frame(Guid, 3, 10, false)}, |
| 125 | + {Frame(Guid, 4, 10, false)}}); |
| 126 | + // Set up mocks for the reader. |
| 127 | + MockMemProfReader Reader; |
| 128 | + EXPECT_CALL(Reader, getMemProfRecord(Guid)).WillOnce(Return(MockRecord)); |
| 129 | + |
| 130 | + MemProfUsePass Pass("/unused/profile/path"); |
| 131 | + std::map<uint64_t, MemProfUsePass::AllocMatchInfo> Unused; |
| 132 | + Pass.readMemprof(*F, Reader, TLI, Unused); |
| 133 | + |
| 134 | + // Since we only have a single type of behaviour for each allocation site, we |
| 135 | + // only get function attributes. |
| 136 | + std::vector<llvm::Attribute> CallsiteAttrs; |
| 137 | + for (const auto &BB : *F) { |
| 138 | + for (const auto &I : BB) { |
| 139 | + if (auto *CI = dyn_cast<CallInst>(&I)) { |
| 140 | + if (!CI->getCalledFunction()->getName().starts_with( |
| 141 | + "__size_returning_new")) |
| 142 | + continue; |
| 143 | + Attribute Attr = CI->getFnAttr("memprof"); |
| 144 | + // The attribute will be invalid if it didn't find one named memprof. |
| 145 | + ASSERT_TRUE(Attr.isValid()); |
| 146 | + CallsiteAttrs.push_back(Attr); |
| 147 | + } |
| 148 | + } |
| 149 | + } |
| 150 | + |
| 151 | + // We match all the variants including ones with the hint since we set |
| 152 | + // ClMemProfMatchHotColdNew to true. |
| 153 | + EXPECT_THAT(CallsiteAttrs, SizeIs(4)); |
| 154 | +} |
| 155 | + |
| 156 | +} // namespace |
| 157 | +} // namespace memprof |
| 158 | +} // namespace llvm |
0 commit comments