Skip to content

Commit

Permalink
[MemProf] Optionally print or record the profiled sizes of allocations (
Browse files Browse the repository at this point in the history
llvm#98248)

This is the first step in being able to track the total profiled sizes
of allocations successfully marked as cold.

Under a new option -memprof-report-hinted-sizes:
- For unambiguous (non-context-sensitive) allocations, print the
  profiled size and the allocation coldness, along with a hash of the
  allocation's location (to allow for deduplication across modules or
  inline instances).
- For context sensitive allocations, add the size as a 3rd operand on
  the MIB metadata. A follow on patch will propagate this through to the
  thin link where the sizes will be reported for each context after
  cloning.
  • Loading branch information
teresajohnson authored Jul 10, 2024
1 parent 3c8b18b commit 8c1bd67
Show file tree
Hide file tree
Showing 6 changed files with 76 additions and 20 deletions.
12 changes: 9 additions & 3 deletions llvm/include/llvm/Analysis/MemoryProfileInfo.h
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,10 @@ MDNode *getMIBStackNode(const MDNode *MIB);
/// Returns the allocation type from an MIB metadata node.
AllocationType getMIBAllocType(const MDNode *MIB);

/// Returns the total size from an MIB metadata node, or 0 if it was not
/// recorded.
uint64_t getMIBTotalSize(const MDNode *MIB);

/// Returns the string to use in attributes with the given type.
std::string getAllocTypeAttributeString(AllocationType Type);

Expand All @@ -54,10 +58,11 @@ class CallStackTrie {
// Allocation types for call context sharing the context prefix at this
// node.
uint8_t AllocTypes;
uint64_t TotalSize;
// Map of caller stack id to the corresponding child Trie node.
std::map<uint64_t, CallStackTrieNode *> Callers;
CallStackTrieNode(AllocationType Type)
: AllocTypes(static_cast<uint8_t>(Type)) {}
CallStackTrieNode(AllocationType Type, uint64_t TotalSize)
: AllocTypes(static_cast<uint8_t>(Type)), TotalSize(TotalSize) {}
};

// The node for the allocation at the root.
Expand Down Expand Up @@ -90,7 +95,8 @@ class CallStackTrie {
/// matching via a debug location hash), expected to be in order from the
/// allocation call down to the bottom of the call stack (i.e. callee to
/// caller order).
void addCallStack(AllocationType AllocType, ArrayRef<uint64_t> StackIds);
void addCallStack(AllocationType AllocType, ArrayRef<uint64_t> StackIds,
uint64_t TotalSize = 0);

/// Add the call stack context along with its allocation type from the MIB
/// metadata to the Trie.
Expand Down
44 changes: 34 additions & 10 deletions llvm/lib/Analysis/MemoryProfileInfo.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,10 @@ cl::opt<unsigned> MemProfMinAveLifetimeAccessDensityHotThreshold(
cl::desc("The minimum TotalLifetimeAccessDensity / AllocCount for an "
"allocation to be considered hot"));

cl::opt<bool> MemProfReportHintedSizes(
"memprof-report-hinted-sizes", cl::init(false), cl::Hidden,
cl::desc("Report total allocation sizes of hinted allocations"));

AllocationType llvm::memprof::getAllocType(uint64_t TotalLifetimeAccessDensity,
uint64_t AllocCount,
uint64_t TotalLifetime) {
Expand Down Expand Up @@ -74,13 +78,13 @@ MDNode *llvm::memprof::buildCallstackMetadata(ArrayRef<uint64_t> CallStack,
}

MDNode *llvm::memprof::getMIBStackNode(const MDNode *MIB) {
assert(MIB->getNumOperands() == 2);
assert(MIB->getNumOperands() >= 2);
// The stack metadata is the first operand of each memprof MIB metadata.
return cast<MDNode>(MIB->getOperand(0));
}

AllocationType llvm::memprof::getMIBAllocType(const MDNode *MIB) {
assert(MIB->getNumOperands() == 2);
assert(MIB->getNumOperands() >= 2);
// The allocation type is currently the second operand of each memprof
// MIB metadata. This will need to change as we add additional allocation
// types that can be applied based on the allocation profile data.
Expand All @@ -94,6 +98,12 @@ AllocationType llvm::memprof::getMIBAllocType(const MDNode *MIB) {
return AllocationType::NotCold;
}

uint64_t llvm::memprof::getMIBTotalSize(const MDNode *MIB) {
if (MIB->getNumOperands() < 3)
return 0;
return mdconst::dyn_extract<ConstantInt>(MIB->getOperand(2))->getZExtValue();
}

std::string llvm::memprof::getAllocTypeAttributeString(AllocationType Type) {
switch (Type) {
case AllocationType::NotCold:
Expand Down Expand Up @@ -125,7 +135,8 @@ bool llvm::memprof::hasSingleAllocType(uint8_t AllocTypes) {
}

void CallStackTrie::addCallStack(AllocationType AllocType,
ArrayRef<uint64_t> StackIds) {
ArrayRef<uint64_t> StackIds,
uint64_t TotalSize) {
bool First = true;
CallStackTrieNode *Curr = nullptr;
for (auto StackId : StackIds) {
Expand All @@ -135,9 +146,10 @@ void CallStackTrie::addCallStack(AllocationType AllocType,
if (Alloc) {
assert(AllocStackId == StackId);
Alloc->AllocTypes |= static_cast<uint8_t>(AllocType);
Alloc->TotalSize += TotalSize;
} else {
AllocStackId = StackId;
Alloc = new CallStackTrieNode(AllocType);
Alloc = new CallStackTrieNode(AllocType, TotalSize);
}
Curr = Alloc;
continue;
Expand All @@ -147,10 +159,11 @@ void CallStackTrie::addCallStack(AllocationType AllocType,
if (Next != Curr->Callers.end()) {
Curr = Next->second;
Curr->AllocTypes |= static_cast<uint8_t>(AllocType);
Curr->TotalSize += TotalSize;
continue;
}
// Otherwise add a new caller node.
auto *New = new CallStackTrieNode(AllocType);
auto *New = new CallStackTrieNode(AllocType, TotalSize);
Curr->Callers[StackId] = New;
Curr = New;
}
Expand All @@ -167,16 +180,19 @@ void CallStackTrie::addCallStack(MDNode *MIB) {
assert(StackId);
CallStack.push_back(StackId->getZExtValue());
}
addCallStack(getMIBAllocType(MIB), CallStack);
addCallStack(getMIBAllocType(MIB), CallStack, getMIBTotalSize(MIB));
}

static MDNode *createMIBNode(LLVMContext &Ctx,
std::vector<uint64_t> &MIBCallStack,
AllocationType AllocType) {
AllocationType AllocType, uint64_t TotalSize) {
std::vector<Metadata *> MIBPayload(
{buildCallstackMetadata(MIBCallStack, Ctx)});
MIBPayload.push_back(
MDString::get(Ctx, getAllocTypeAttributeString(AllocType)));
if (TotalSize)
MIBPayload.push_back(ValueAsMetadata::get(
ConstantInt::get(Type::getInt64Ty(Ctx), TotalSize)));
return MDNode::get(Ctx, MIBPayload);
}

Expand All @@ -190,8 +206,8 @@ bool CallStackTrie::buildMIBNodes(CallStackTrieNode *Node, LLVMContext &Ctx,
// Trim context below the first node in a prefix with a single alloc type.
// Add an MIB record for the current call stack prefix.
if (hasSingleAllocType(Node->AllocTypes)) {
MIBNodes.push_back(
createMIBNode(Ctx, MIBCallStack, (AllocationType)Node->AllocTypes));
MIBNodes.push_back(createMIBNode(
Ctx, MIBCallStack, (AllocationType)Node->AllocTypes, Node->TotalSize));
return true;
}

Expand Down Expand Up @@ -227,7 +243,8 @@ bool CallStackTrie::buildMIBNodes(CallStackTrieNode *Node, LLVMContext &Ctx,
// non-cold allocation type.
if (!CalleeHasAmbiguousCallerContext)
return false;
MIBNodes.push_back(createMIBNode(Ctx, MIBCallStack, AllocationType::NotCold));
MIBNodes.push_back(createMIBNode(Ctx, MIBCallStack, AllocationType::NotCold,
Node->TotalSize));
return true;
}

Expand All @@ -238,6 +255,13 @@ bool CallStackTrie::buildAndAttachMIBMetadata(CallBase *CI) {
auto &Ctx = CI->getContext();
if (hasSingleAllocType(Alloc->AllocTypes)) {
addAllocTypeAttribute(Ctx, CI, (AllocationType)Alloc->AllocTypes);
if (MemProfReportHintedSizes) {
assert(Alloc->TotalSize);
errs() << "Total size for allocation with location hash " << AllocStackId
<< " and single alloc type "
<< getAllocTypeAttributeString((AllocationType)Alloc->AllocTypes)
<< ": " << Alloc->TotalSize << "\n";
}
return false;
}
std::vector<uint64_t> MIBCallStack;
Expand Down
10 changes: 7 additions & 3 deletions llvm/lib/IR/Verifier.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -4941,10 +4941,14 @@ void Verifier::visitMemProfMetadata(Instruction &I, MDNode *MD) {
MDNode *StackMD = dyn_cast<MDNode>(MIB->getOperand(0));
visitCallStackMetadata(StackMD);

// Check that remaining operands are MDString.
Check(llvm::all_of(llvm::drop_begin(MIB->operands()),
// Check that remaining operands, except possibly the last, are MDString.
Check(llvm::all_of(MIB->operands().drop_front().drop_back(),
[](const MDOperand &Op) { return isa<MDString>(Op); }),
"Not all !memprof MemInfoBlock operands 1 to N are MDString", MIB);
"Not all !memprof MemInfoBlock operands 1 to N-1 are MDString", MIB);
// The last operand might be the total profiled size so can be an integer.
auto &LastOperand = MIB->operands().back();
Check(isa<MDString>(LastOperand) || mdconst::hasa<ConstantInt>(LastOperand),
"Last !memprof MemInfoBlock operand not MDString or int", MIB);
}
}

Expand Down
11 changes: 9 additions & 2 deletions llvm/lib/Transforms/Instrumentation/MemProfiler.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -161,6 +161,8 @@ static cl::opt<bool>
"context in this module's profiles"),
cl::Hidden, cl::init(false));

extern cl::opt<bool> MemProfReportHintedSizes;

// Instrumentation statistics
STATISTIC(NumInstrumentedReads, "Number of instrumented reads");
STATISTIC(NumInstrumentedWrites, "Number of instrumented writes");
Expand Down Expand Up @@ -712,7 +714,12 @@ static AllocationType addCallStack(CallStackTrie &AllocTrie,
auto AllocType = getAllocType(AllocInfo->Info.getTotalLifetimeAccessDensity(),
AllocInfo->Info.getAllocCount(),
AllocInfo->Info.getTotalLifetime());
AllocTrie.addCallStack(AllocType, StackIds);
uint64_t TotalSize = 0;
if (MemProfReportHintedSizes) {
TotalSize = AllocInfo->Info.getTotalSize();
assert(TotalSize);
}
AllocTrie.addCallStack(AllocType, StackIds, TotalSize);
return AllocType;
}

Expand Down Expand Up @@ -1055,4 +1062,4 @@ PreservedAnalyses MemProfUsePass::run(Module &M, ModuleAnalysisManager &AM) {
}

return PreservedAnalyses::none();
}
}
15 changes: 15 additions & 0 deletions llvm/test/Transforms/PGOProfile/memprof.ll
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,9 @@
;; give both memprof and pgo metadata.
; RUN: opt < %s -passes='pgo-instr-use,memprof-use<profile-filename=%t.pgomemprofdata>' -pgo-test-profile-file=%t.pgomemprofdata -pgo-warn-missing-function -S 2>&1 | FileCheck %s --check-prefixes=MEMPROF,ALL,PGO

;; Check that the total sizes are reported if requested.
; RUN: opt < %s -passes='memprof-use<profile-filename=%t.memprofdata>' -pgo-warn-missing-function -S -memprof-report-hinted-sizes 2>&1 | FileCheck %s --check-prefixes=TOTALSIZES

; MEMPROFMATCHINFO: MemProf notcold context with id 1093248920606587996 has total profiled size 10 is matched
; MEMPROFMATCHINFO: MemProf notcold context with id 5725971306423925017 has total profiled size 10 is matched
; MEMPROFMATCHINFO: MemProf notcold context with id 6792096022461663180 has total profiled size 10 is matched
Expand Down Expand Up @@ -331,6 +334,18 @@ for.end: ; preds = %for.cond
; MEMPROF: ![[C10]] = !{i64 2061451396820446691}
; MEMPROF: ![[C11]] = !{i64 1544787832369987002}

;; For non-context sensitive allocations that get attributes we emit a message
;; with the allocation hash, type, and size in bytes.
; TOTALSIZES: Total size for allocation with location hash 6792096022461663180 and single alloc type notcold: 10
; TOTALSIZES: Total size for allocation with location hash 15737101490731057601 and single alloc type cold: 10
;; For context sensitive allocations the size in bytes is included on the MIB
;; metadata.
; TOTALSIZES: !"cold", i64 10}
; TOTALSIZES: !"cold", i64 10}
; TOTALSIZES: !"notcold", i64 10}
; TOTALSIZES: !"cold", i64 20}
; TOTALSIZES: !"notcold", i64 10}


; MEMPROFNOCOLINFO: #[[A1]] = { builtin allocsize(0) "memprof"="notcold" }
; MEMPROFNOCOLINFO: #[[A2]] = { builtin allocsize(0) "memprof"="cold" }
Expand Down
4 changes: 2 additions & 2 deletions llvm/test/Verifier/memprof-metadata-bad.ll
Original file line number Diff line number Diff line change
Expand Up @@ -43,8 +43,8 @@ declare dso_local noalias noundef ptr @malloc(i64 noundef)
!6 = !{i64 0}
!7 = !{!8}
; CHECK: call stack metadata should have at least 1 operand
; CHECK: Not all !memprof MemInfoBlock operands 1 to N are MDString
!8 = !{!0, !"default", i64 0}
; CHECK: Not all !memprof MemInfoBlock operands 1 to N-1 are MDString
!8 = !{!0, !"default", i64 0, i64 5}
!9 = !{i64 123}
; CHECK: call stack metadata operand should be constant integer
!10 = !{!"wrongtype"}
Expand Down

0 comments on commit 8c1bd67

Please sign in to comment.