[StaticDataLayout][MemProf] Annotate string literal hotness by making use of data access profiles. (#178333)
The change is gated under a new option
`memprof-annotate-string-literal-section-prefix` so we can flag-gate it
for rollout purposes.
A follow-up PR https://github.com/llvm/llvm-project/pull/178336/changes
updates the codegen pass to reconcile the hotness similar to the
reconciliation for other global variables.
diff --git a/llvm/lib/Transforms/Instrumentation/MemProfUse.cpp b/llvm/lib/Transforms/Instrumentation/MemProfUse.cpp
index 748c4cb..72d2c4d 100644
--- a/llvm/lib/Transforms/Instrumentation/MemProfUse.cpp
+++ b/llvm/lib/Transforms/Instrumentation/MemProfUse.cpp
@@ -92,6 +92,14 @@
"memprof-annotate-static-data-prefix", cl::init(false), cl::Hidden,
cl::desc("If true, annotate the static data section prefix"));
+// FIXME: This option is added for incremental rollout purposes.
+// After the option, string literal partitioning should be implied by
+// AnnotateStaticDataSectionPrefix above and this option should be cleaned up.
+static cl::opt<bool> AnnotateStringLiteralSectionPrefix(
+ "memprof-annotate-string-literal-section-prefix", cl::init(false),
+ cl::Hidden,
+ cl::desc("If true, annotate the string literal data section prefix"));
+
// Matching statistics
STATISTIC(NumOfMemProfMissing, "Number of functions without memory profile.");
STATISTIC(NumOfMemProfMismatch,
@@ -233,6 +241,21 @@
<< Reason << ".\n");
}
+// Computes the LLVM version of MD5 hash for the content of a string
+// literal.
+static std::optional<uint64_t>
+getStringContentHash(const GlobalVariable &GVar) {
+ auto *Initializer = GVar.getInitializer();
+ if (!Initializer)
+ return std::nullopt;
+ if (auto *C = dyn_cast<ConstantDataSequential>(Initializer))
+ if (C->isString()) {
+ // Note the hash computed for the literal would include the null byte.
+ return llvm::MD5Hash(C->getAsString());
+ }
+ return std::nullopt;
+}
+
// Structure for tracking info about matched allocation contexts for use with
// -memprof-print-match-info and -memprof-print-matched-alloc-stack.
struct AllocMatchInfo {
@@ -932,19 +955,27 @@
}
StringRef Name = GVar.getName();
+ SymbolHandleRef Handle = SymbolHandleRef(Name);
// Skip string literals as their mangled names don't stay stable across
// binary releases.
- // TODO: Track string content hash in the profiles and compute it inside the
- // compiler to categeorize the hotness string literals.
+ if (!AnnotateStringLiteralSectionPrefix)
+ if (Name.starts_with(".str"))
+ continue;
+
if (Name.starts_with(".str")) {
- LLVM_DEBUG(dbgs() << "Skip annotating string literal " << Name << "\n");
- continue;
+ std::optional<uint64_t> Hash = getStringContentHash(GVar);
+ if (!Hash) {
+ LLVM_DEBUG(dbgs() << "Cannot compute content hash for string literal "
+ << Name << "\n");
+ continue;
+ }
+ Handle = SymbolHandleRef(Hash.value());
}
// DataAccessProfRecord's get* methods will canonicalize the name under the
// hood before looking it up, so optimizer doesn't need to do it.
std::optional<DataAccessProfRecord> Record =
- DataAccessProf->getProfileRecord(Name);
+ DataAccessProf->getProfileRecord(Handle);
// Annotate a global variable as hot if it has non-zero sampled count, and
// annotate it as cold if it's seen in the profiled binary
// file but doesn't have any access sample.
@@ -956,7 +987,7 @@
Changed |= GVar.setSectionPrefix("hot");
LLVM_DEBUG(dbgs() << "Global variable " << Name
<< " is annotated as hot\n");
- } else if (DataAccessProf->isKnownColdSymbol(Name)) {
+ } else if (DataAccessProf->isKnownColdSymbol(Handle)) {
++NumOfMemProfColdGlobalVars;
Changed |= GVar.setSectionPrefix("unlikely");
Changed = true;
diff --git a/llvm/test/Transforms/PGOProfile/data-access-profile.ll b/llvm/test/Transforms/PGOProfile/data-access-profile.ll
index 205184b..7c062a9 100644
--- a/llvm/test/Transforms/PGOProfile/data-access-profile.ll
+++ b/llvm/test/Transforms/PGOProfile/data-access-profile.ll
@@ -1,5 +1,5 @@
; REQUIRES: asserts
-; asserts are required for -debug-only=<pass-name>
+; asserts are required for -debug-only=<pass-name> and -stats
; RUN: rm -rf %t && split-file %s %t && cd %t
@@ -10,11 +10,21 @@
;; Run optimizer pass on an IR module without IR functions, and test that global
;; variables in the module could be annotated (i.e., no early return),
; RUN: opt -passes='memprof-use<profile-filename=memprof.profdata>' -memprof-annotate-static-data-prefix \
-; RUN: -debug-only=memprof -stats -S funcless-module.ll -o - 2>&1 | FileCheck %s --check-prefixes=LOG,IR,STAT
+; RUN: -debug-only=memprof -stats -S funcless-module.ll -o - 2>&1 | FileCheck %s --check-prefixes=LOGCOMMON,IRCOMMON,IR,STAT
+
+;; Add '-memprof-annotate-string-literal-section-prefix' to RUN command above.
+; RUN: opt -passes='memprof-use<profile-filename=memprof.profdata>' -memprof-annotate-static-data-prefix \
+; RUN: -memprof-annotate-string-literal-section-prefix \
+; RUN: -debug-only=memprof -stats -S funcless-module.ll -o - 2>&1 | FileCheck %s --check-prefixes=LOGCOMMON,LOGSTR,IRCOMMON,IRSTR,STRSTAT
;; Run optimizer pass on the IR, and check the section prefix.
; RUN: opt -passes='memprof-use<profile-filename=memprof.profdata>' -memprof-annotate-static-data-prefix \
-; RUN: -debug-only=memprof -stats -S input.ll -o - 2>&1 | FileCheck %s --check-prefixes=LOG,IR,STAT
+; RUN: -debug-only=memprof -stats -S input.ll -o - 2>&1 | FileCheck %s --check-prefixes=LOGCOMMON,IR,STAT
+
+;; Add '-memprof-annotate-string-literal-section-prefix' to RUN command above.
+; RUN: opt -passes='memprof-use<profile-filename=memprof.profdata>' -memprof-annotate-static-data-prefix \
+; RUN: -memprof-annotate-string-literal-section-prefix \
+; RUN: -debug-only=memprof -stats -S input.ll -o - 2>&1 | FileCheck %s --check-prefixes=LOGCOMMON,LOGSTR,IRSTR,STRSTAT
;; Run memprof without providing memprof data. Test that IR has module flag
;; `EnableDataAccessProf` as 0.
@@ -26,40 +36,59 @@
; RUN: opt -passes='memprof-use<profile-filename=memprof.profdata>' \
; RUN: -debug-only=memprof -stats -S input.ll -o - | FileCheck %s --check-prefix=FLAGLESS --implicit-check-not="section_prefix"
-; LOG: Skip annotating string literal .str
-; LOG: Global variable var1 is annotated as hot
-; LOG: Global variable var2.llvm.125 is annotated as hot
-; LOG: Global variable bar is not annotated
-; LOG: Global variable foo is annotated as unlikely
-; LOG: Skip annotation for var3 due to explicit section name.
-; LOG: Skip annotation for var4 due to explicit section name.
-; LOG: Skip annotation for llvm.fake_var due to name starts with `llvm.`.
-; LOG: Skip annotation for qux due to linker declaration.
+; LOGSTR: Global variable .str is annotated as hot
+
+;; Common log lines
+; LOGCOMMON: Global variable var1 is annotated as hot
+; LOGCOMMON: Global variable var2.llvm.125 is annotated as hot
+; LOGCOMMON: Global variable bar is not annotated
+; LOGCOMMON: Global variable foo is annotated as unlikely
+; LOGCOMMON: Skip annotation for var3 due to explicit section name.
+; LOGCOMMON: Skip annotation for var4 due to explicit section name.
+; LOGCOMMON: Skip annotation for llvm.fake_var due to name starts with `llvm.`.
+; LOGCOMMON: Skip annotation for qux due to linker declaration.
+
+; LOGSTR: Global variable .str.llvm.98765 is annotated as unlikely
+; LOGSTR: Global variable .str.2 is not annotated
;; String literals are not annotated.
; IR: @.str = unnamed_addr constant [5 x i8] c"abcde"
; IR-NOT: section_prefix
-; IR: @var1 = global i32 123, !section_prefix !0
+; IR-SAME: {{$}}
+
+;; .str is hot
+; IRSTR: @.str = unnamed_addr constant [5 x i8] c"abcde", !section_prefix !0
+
+; IRCOMMON: @var1 = global i32 123, !section_prefix !0
;; @var.llvm.125 will be canonicalized to @var2 for profile look-up.
-; IR-NEXT: @var2.llvm.125 = global i64 0, !section_prefix !0
+; IRCOMMON-NEXT: @var2.llvm.125 = global i64 0, !section_prefix !0
;; @bar is not seen in hot symbol or known symbol set, so it won't get a section
;; prefix. Test this by testing that there is no section_prefix between @bar and
;; @foo.
-; IR-NEXT: @bar = global i16 3
-; IR-NOT: !section_prefix
+; IRCOMMON-NEXT: @bar = global i16 3
+; IRCOMMON-NOT: !section_prefix
+; IRCOMMON-SAME: {{$}}
;; @foo is unlikely.
-; IR-NEXT: @foo = global i8 2, !section_prefix !1
+; IRCOMMON-NEXT: @foo = global i8 2, !section_prefix !1
-; IR-NEXT: @var3 = constant [2 x i32] [i32 12345, i32 6789], section "sec1"
-; IR-NEXT: @var4 = constant [1 x i64] [i64 98765] #0
+; IRCOMMON-NEXT: @var3 = constant [2 x i32] [i32 12345, i32 6789], section "sec1"
+; IRCOMMON-NEXT: @var4 = constant [1 x i64] [i64 98765] #0
-; IR: @llvm.fake_var = global i32 123
-; IR-NOT: !section_prefix
-; IR: @qux = external global i64
-; IR-NOT: !section_prefix
+; IRCOMMON: @llvm.fake_var = global i32 123
+; IRCOMMON-NOT: !section_prefix
+; IRCOMMON-SAME: {{$}}
+; IRCOMMON: @qux = external global i64
+; IRCOMMON-NOT: !section_prefix
+; IRCOMMON-SAME: {{$}}
+
+;; @.str.llvm.98765 is unlikely and @.str.2 has no section prefix.
+; IRSTR: @.str.llvm.98765 = constant [5 x i8] c"Joins", align 1, !section_prefix !1
+; IRSTR: @.str.2 = constant [15 x i8] c"*ptr == nullptr", align 1
+; IRSTR-NOT: section_prefix
+; IRSTR-SAME: {{$}}
; IR: attributes #0 = { "rodata-section"="sec2" }
@@ -75,6 +104,11 @@
; STAT: 2 memprof - Number of global vars annotated with 'hot' section prefix.
; STAT: 1 memprof - Number of global vars with unknown hotness (no section prefix).
+; STRSTAT: 2 memprof - Number of global vars annotated with 'unlikely' section prefix.
+; STRSTAT: 2 memprof - Number of global vars with user-specified section (not annotated).
+; STRSTAT: 3 memprof - Number of global vars annotated with 'hot' section prefix.
+; STRSTAT: 2 memprof - Number of global vars with unknown hotness (no section prefix).
+
;--- memprof.yaml
---
DataAccessProfiles:
@@ -83,11 +117,14 @@
AccessCount: 1000
- Symbol: var2
AccessCount: 5
- - Hash: 101010
+ # 4211217683648370347 is the hash for 'abcde'
+ - Hash: 4211217683648370347
AccessCount: 145
KnownColdSymbols:
- foo
- KnownColdStrHashes: [ 999, 1001 ]
+ # 1698928196410683810 is the hash for 'Joins'
+ # 9184168289490324740 is the hash for 'ptr = nullptr'. This string doesn't exist in the IR.
+ KnownColdStrHashes: [ 1698928196410683810, 9184168289490324740 ]
...
;--- memprof-no-dap.yaml
---
@@ -114,13 +151,15 @@
@.str = unnamed_addr constant [5 x i8] c"abcde"
@var1 = global i32 123
-@var2.llvm.125 = global i64 0
+@var2.llvm.125 = global i64 0
@bar = global i16 3
@foo = global i8 2
@var3 = constant [2 x i32][i32 12345, i32 6789], section "sec1"
@var4 = constant [1 x i64][i64 98765] #0
@llvm.fake_var = global i32 123
@qux = external global i64
+@.str.llvm.98765 = constant [5 x i8] c"Joins", align 1
+@.str.2 = constant [15 x i8] c"*ptr == nullptr", align 1
define i32 @func() {
%a = load i32, ptr @var1
@@ -148,6 +187,8 @@
@var4 = constant [1 x i64][i64 98765] #0
@llvm.fake_var = global i32 123
@qux = external global i64
+@.str.llvm.98765 = constant [5 x i8] c"Joins", align 1
+@.str.2 = constant [15 x i8] c"*ptr == nullptr", align 1
attributes #0 = { "rodata-section"="sec2" }