[llvm] [ICP] Introduce a hot function cutoff threshold for ICP (PR #208060)

Congzhe Cao via llvm-commits llvm-commits at lists.llvm.org
Tue Jul 28 21:15:31 PDT 2026


https://github.com/CongzheUalberta updated https://github.com/llvm/llvm-project/pull/208060

>From 65b39fd8972a94c96defa1afe73a8d5d6911a4cb Mon Sep 17 00:00:00 2001
From: Congzhe Cao <congzhe.cao at huawei.com>
Date: Tue, 7 Jul 2026 13:36:35 -0400
Subject: [PATCH 1/3] [ICP] Introduce a hot function cutoff threshold for ICP

Introduce a new hot function cutoff threshold specific to ICP
that can enable promotion of more indirect call targets. Currently
by default the threshold value is the same as `ProfileSummaryCutoffHot`.
---
 .../Instrumentation/IndirectCallPromotion.cpp | 11 ++-
 .../PGOProfile/icp_hot_func_threshold.ll      | 83 +++++++++++++++++++
 2 files changed, 92 insertions(+), 2 deletions(-)
 create mode 100644 llvm/test/Transforms/PGOProfile/icp_hot_func_threshold.ll

diff --git a/llvm/lib/Transforms/Instrumentation/IndirectCallPromotion.cpp b/llvm/lib/Transforms/Instrumentation/IndirectCallPromotion.cpp
index cc6361f03d89b..75ecf3581c464 100644
--- a/llvm/lib/Transforms/Instrumentation/IndirectCallPromotion.cpp
+++ b/llvm/lib/Transforms/Instrumentation/IndirectCallPromotion.cpp
@@ -160,6 +160,11 @@ static cl::list<std::string> ICPIgnoredBaseTypes(
         "binary could be different due to profiling limitations. Type info "
         "names are those string literals used in LLVM type metadata"));
 
+static cl::opt<int> HotFuncCutoffForICP(
+    "hot-func-cutoff-for-icp", cl::Hidden, cl::init(990000),
+    cl::desc("A count is hot for indirect call promotion if it exceeds "
+             "the minimum count to reach this percentile of total counts."
+             "Default value is the same as ProfileSummaryCutoffHot."));
 namespace {
 
 // The key is a vtable global variable, and the value is a map.
@@ -881,8 +886,10 @@ bool IndirectCallPromoter::processFunction(ProfileSummaryInfo *PSI) {
                           << TotalCount << "\n");
         continue;
       }
-      // Only pormote hot if ICPAllowHotOnly is true.
-      if (ICPAllowHotOnly && !PSI->isHotCount(TotalCount)) {
+      // Only promote hot if ICPAllowHotOnly is true. ICP has its own cutoff
+      // threshold for hotness.
+      if (ICPAllowHotOnly &&
+          !PSI->isHotCountNthPercentile(HotFuncCutoffForICP, TotalCount)) {
         LLVM_DEBUG(dbgs() << "Don't promote the non-hot candidate: TotalCount="
                           << TotalCount << "\n");
         continue;
diff --git a/llvm/test/Transforms/PGOProfile/icp_hot_func_threshold.ll b/llvm/test/Transforms/PGOProfile/icp_hot_func_threshold.ll
new file mode 100644
index 0000000000000..b36ea9a7ec396
--- /dev/null
+++ b/llvm/test/Transforms/PGOProfile/icp_hot_func_threshold.ll
@@ -0,0 +1,83 @@
+; RUN: opt < %s -passes=pgo-icall-prom -hot-func-cutoff-for-icp=200000 -pass-remarks=pgo-icall-prom 2>&1 | FileCheck %s --check-prefix=PASS-REMARK
+; RUN: opt < %s -passes=pgo-icall-prom -hot-func-cutoff-for-icp=100000 -pass-remarks=pgo-icall-prom 2>&1 | FileCheck %s --check-prefix=FAIL-REMARK
+
+; ICP has its own cutoff threshold for hotness that can be tuned.
+
+; PASS-REMARK: remark: <unknown>:0:0: Promote indirect call to func4 with count 5 out of 14
+; PASS-REMARK: remark: <unknown>:0:0: Promote indirect call to func2 with count 4 out of 9
+; PASS-REMARK: remark: <unknown>:0:0: Promote indirect call to func3 with count 3 out of 5
+
+; FAIL-REMARK-NOT: remark: <unknown>:0:0: Promote indirect call to func4
+; FAIL-REMARK-NOT: remark: <unknown>:0:0: Promote indirect call to func2
+; FAIL-REMARK-NOT: remark: <unknown>:0:0: Promote indirect call to func3
+
+target datalayout = "e-m:e-p270:32:32-p271:32:32-p272:64:64-i64:64-f80:128-n8:16:32:64-S128"
+target triple = "x86_64-unknown-linux-gnu"
+
+ at foo = common global ptr null, align 8
+
+define i32 @func1() {
+entry:
+  ret i32 0
+}
+
+define i32 @func2() {
+entry:
+  ret i32 1
+}
+
+define i32 @func3() {
+entry:
+  ret i32 2
+}
+
+define i32 @func4() {
+entry:
+  ret i32 3
+}
+
+define i32 @bar() {
+entry:
+  %tmp = load ptr, ptr @foo, align 8
+  %call = call i32 %tmp(), !prof !34
+  ret i32 %call
+}
+
+
+!llvm.module.flags = !{!0, !1, !2, !3, !4, !5}
+
+!0 = !{i32 1, !"wchar_size", i32 4}
+!1 = !{i32 7, !"PIC Level", i32 2}
+!2 = !{i32 7, !"PIE Level", i32 2}
+!3 = !{i32 7, !"uwtable", i32 2}
+!4 = !{i32 7, !"frame-pointer", i32 1}
+!5 = !{i32 1, !"ProfileSummary", !6}
+!6 = !{!7, !8, !9, !10, !11, !12, !13, !14, !15, !16}
+!7 = !{!"ProfileFormat", !"InstrProf"}
+!8 = !{!"TotalCount", i64 3}
+!9 = !{!"MaxCount", i64 1}
+!10 = !{!"MaxInternalCount", i64 1}
+!11 = !{!"MaxFunctionCount", i64 1}
+!12 = !{!"NumCounts", i64 7}
+!13 = !{!"NumFunctions", i64 4}
+!14 = !{!"IsPartialProfile", i64 0}
+!15 = !{!"PartialProfileRatio", double 0.000000e+00}
+!16 = !{!"DetailedSummary", !17}
+!17 = !{!18, !19, !20, !21, !22, !23, !24, !25, !26, !27, !28, !29, !30, !31, !32, !33}
+!18 = !{i32 10000, i64 16, i32 1}
+!19 = !{i32 100000, i64 15, i32 2}
+!20 = !{i32 200000, i64 14, i32 3}
+!21 = !{i32 300000, i64 13, i32 4}
+!22 = !{i32 400000, i64 12, i32 5}
+!23 = !{i32 500000, i64 11, i32 6}
+!24 = !{i32 600000, i64 10, i32 7}
+!25 = !{i32 700000, i64 9, i32 8}
+!26 = !{i32 800000, i64 8, i32 9}
+!27 = !{i32 900000, i64 7, i32 10}
+!28 = !{i32 950000, i64 6, i32 11}
+!29 = !{i32 990000, i64 5, i32 12}
+!30 = !{i32 999000, i64 4, i32 13}
+!31 = !{i32 999900, i64 3, i32 14}
+!32 = !{i32 999990, i64 2, i32 15}
+!33 = !{i32 999999, i64 1, i32 16}
+!34 = !{!"VP", i32 0, i64 14, i64 7651369219802541373, i64 5, i64 -4377547752858689819, i64 4, i64 -6929281286627296573, i64 3, i64 -2545542355363006406, i64 2}

>From 2bac88332c9bc81c531251641229eb3e841b56d9 Mon Sep 17 00:00:00 2001
From: Congzhe Cao <congzhe.cao at huawei.com>
Date: Wed, 15 Jul 2026 14:29:28 -0400
Subject: [PATCH 2/3] addressed reviewer's comments, make the default of
 HotFuncCutoffForICP to -1 and when unspecified, use the value of
 ProfileSummaryCutoffHot.

---
 .../Instrumentation/IndirectCallPromotion.cpp | 14 ++++++++---
 .../PGOProfile/icp_hot_func_threshold.ll      | 25 +++++++++++++++----
 2 files changed, 30 insertions(+), 9 deletions(-)

diff --git a/llvm/lib/Transforms/Instrumentation/IndirectCallPromotion.cpp b/llvm/lib/Transforms/Instrumentation/IndirectCallPromotion.cpp
index 75ecf3581c464..a3fcc212992c3 100644
--- a/llvm/lib/Transforms/Instrumentation/IndirectCallPromotion.cpp
+++ b/llvm/lib/Transforms/Instrumentation/IndirectCallPromotion.cpp
@@ -32,6 +32,7 @@
 #include "llvm/IR/ProfDataUtils.h"
 #include "llvm/IR/Value.h"
 #include "llvm/ProfileData/InstrProf.h"
+#include "llvm/ProfileData/ProfileCommon.h"
 #include "llvm/Support/Casting.h"
 #include "llvm/Support/CommandLine.h"
 #include "llvm/Support/Debug.h"
@@ -161,10 +162,11 @@ static cl::list<std::string> ICPIgnoredBaseTypes(
         "names are those string literals used in LLVM type metadata"));
 
 static cl::opt<int> HotFuncCutoffForICP(
-    "hot-func-cutoff-for-icp", cl::Hidden, cl::init(990000),
+    "hot-func-cutoff-for-icp", cl::Hidden, cl::init(-1),
     cl::desc("A count is hot for indirect call promotion if it exceeds "
              "the minimum count to reach this percentile of total counts."
-             "Default value is the same as ProfileSummaryCutoffHot."));
+             "Default value -1 means that if the flag is unspecified then "
+             "the value of ProfileSummaryCutoffHot will be used instead."));
 namespace {
 
 // The key is a vtable global variable, and the value is a map.
@@ -887,9 +889,13 @@ bool IndirectCallPromoter::processFunction(ProfileSummaryInfo *PSI) {
         continue;
       }
       // Only promote hot if ICPAllowHotOnly is true. ICP has its own cutoff
-      // threshold for hotness.
+      // threshold for hotness, which defaults to ProfileSummaryCutoffHot if
+      // unspecified.
       if (ICPAllowHotOnly &&
-          !PSI->isHotCountNthPercentile(HotFuncCutoffForICP, TotalCount)) {
+          !PSI->isHotCountNthPercentile(HotFuncCutoffForICP == -1
+                                            ? ProfileSummaryCutoffHot
+                                            : HotFuncCutoffForICP,
+                                        TotalCount)) {
         LLVM_DEBUG(dbgs() << "Don't promote the non-hot candidate: TotalCount="
                           << TotalCount << "\n");
         continue;
diff --git a/llvm/test/Transforms/PGOProfile/icp_hot_func_threshold.ll b/llvm/test/Transforms/PGOProfile/icp_hot_func_threshold.ll
index b36ea9a7ec396..b6b8a72caac31 100644
--- a/llvm/test/Transforms/PGOProfile/icp_hot_func_threshold.ll
+++ b/llvm/test/Transforms/PGOProfile/icp_hot_func_threshold.ll
@@ -1,5 +1,6 @@
 ; RUN: opt < %s -passes=pgo-icall-prom -hot-func-cutoff-for-icp=200000 -pass-remarks=pgo-icall-prom 2>&1 | FileCheck %s --check-prefix=PASS-REMARK
 ; RUN: opt < %s -passes=pgo-icall-prom -hot-func-cutoff-for-icp=100000 -pass-remarks=pgo-icall-prom 2>&1 | FileCheck %s --check-prefix=FAIL-REMARK
+; RUN: opt < %s -passes=pgo-icall-prom -pass-remarks=pgo-icall-prom 2>&1 | FileCheck %s --check-prefix=PASS-REMARK-DEFAULT
 
 ; ICP has its own cutoff threshold for hotness that can be tuned.
 
@@ -11,10 +12,21 @@
 ; FAIL-REMARK-NOT: remark: <unknown>:0:0: Promote indirect call to func2
 ; FAIL-REMARK-NOT: remark: <unknown>:0:0: Promote indirect call to func3
 
+; If ICP does not specify "-hot-func-cutoff-for-icp", then it falls back to the default ProfileSummaryCutoffHot value,
+; and will promote "%call2" as well.
+
+; PASS-REMARK-DEFAULT: remark: <unknown>:0:0: Promote indirect call to func4 with count 5 out of 14
+; PASS-REMARK-DEFAULT: remark: <unknown>:0:0: Promote indirect call to func2 with count 4 out of 9
+; PASS-REMARK-DEFAULT: remark: <unknown>:0:0: Promote indirect call to func3 with count 3 out of 5
+; PASS-REMARK-DEFAULT: remark: <unknown>:0:0: Promote indirect call to func4 with count 3 out of 5
+; PASS-REMARK-DEFAULT: remark: <unknown>:0:0: Promote indirect call to func2 with count 1 out of 2
+
+
 target datalayout = "e-m:e-p270:32:32-p271:32:32-p272:64:64-i64:64-f80:128-n8:16:32:64-S128"
 target triple = "x86_64-unknown-linux-gnu"
 
 @foo = common global ptr null, align 8
+ at baz = common global ptr null, align 8
 
 define i32 @func1() {
 entry:
@@ -39,7 +51,9 @@ entry:
 define i32 @bar() {
 entry:
   %tmp = load ptr, ptr @foo, align 8
+  %tmp2 = load ptr, ptr @baz, align 8
   %call = call i32 %tmp(), !prof !34
+  %call2 = call i32 %tmp2(), !prof !35
   ret i32 %call
 }
 
@@ -54,11 +68,11 @@ entry:
 !5 = !{i32 1, !"ProfileSummary", !6}
 !6 = !{!7, !8, !9, !10, !11, !12, !13, !14, !15, !16}
 !7 = !{!"ProfileFormat", !"InstrProf"}
-!8 = !{!"TotalCount", i64 3}
-!9 = !{!"MaxCount", i64 1}
-!10 = !{!"MaxInternalCount", i64 1}
-!11 = !{!"MaxFunctionCount", i64 1}
-!12 = !{!"NumCounts", i64 7}
+!8 = !{!"TotalCount", i64 136}
+!9 = !{!"MaxCount", i64 16}
+!10 = !{!"MaxInternalCount", i64 5}
+!11 = !{!"MaxFunctionCount", i64 16}
+!12 = !{!"NumCounts", i64 16}
 !13 = !{!"NumFunctions", i64 4}
 !14 = !{!"IsPartialProfile", i64 0}
 !15 = !{!"PartialProfileRatio", double 0.000000e+00}
@@ -81,3 +95,4 @@ entry:
 !32 = !{i32 999990, i64 2, i32 15}
 !33 = !{i32 999999, i64 1, i32 16}
 !34 = !{!"VP", i32 0, i64 14, i64 7651369219802541373, i64 5, i64 -4377547752858689819, i64 4, i64 -6929281286627296573, i64 3, i64 -2545542355363006406, i64 2}
+!35 = !{!"VP", i32 0, i64 5, i64 7651369219802541373, i64 3, i64 -4377547752858689819, i64 1}

>From 37deb63c1cc00baa37c6f8b1eba606c2f9ed695a Mon Sep 17 00:00:00 2001
From: Congzhe Cao <congzhe.cao at huawei.com>
Date: Wed, 29 Jul 2026 00:05:33 -0400
Subject: [PATCH 3/3] Further address reviewer's comments on July 29th.

---
 .../Instrumentation/IndirectCallPromotion.cpp  |  2 ++
 .../PGOProfile/icp_hot_func_threshold.ll       | 18 +++++++++++-------
 2 files changed, 13 insertions(+), 7 deletions(-)

diff --git a/llvm/lib/Transforms/Instrumentation/IndirectCallPromotion.cpp b/llvm/lib/Transforms/Instrumentation/IndirectCallPromotion.cpp
index a3fcc212992c3..f18287ba9de27 100644
--- a/llvm/lib/Transforms/Instrumentation/IndirectCallPromotion.cpp
+++ b/llvm/lib/Transforms/Instrumentation/IndirectCallPromotion.cpp
@@ -165,6 +165,8 @@ static cl::opt<int> HotFuncCutoffForICP(
     "hot-func-cutoff-for-icp", cl::Hidden, cl::init(-1),
     cl::desc("A count is hot for indirect call promotion if it exceeds "
              "the minimum count to reach this percentile of total counts."
+             "Note that this percentile is specified as "
+             "percentile * 10000 = HotFuncCutoffForICP."
              "Default value -1 means that if the flag is unspecified then "
              "the value of ProfileSummaryCutoffHot will be used instead."));
 namespace {
diff --git a/llvm/test/Transforms/PGOProfile/icp_hot_func_threshold.ll b/llvm/test/Transforms/PGOProfile/icp_hot_func_threshold.ll
index b6b8a72caac31..24339ec110b7c 100644
--- a/llvm/test/Transforms/PGOProfile/icp_hot_func_threshold.ll
+++ b/llvm/test/Transforms/PGOProfile/icp_hot_func_threshold.ll
@@ -4,17 +4,21 @@
 
 ; ICP has its own cutoff threshold for hotness that can be tuned.
 
+;; Cutoff of 200000 (20%) has a cutoff count of 14. The profile count of the VP metadata for %call reaches 14
+;; so it is promoted. The profile count of the VP metadata for %call2 is 5 and does not reach 14,
+;; so it is not promoted.
 ; PASS-REMARK: remark: <unknown>:0:0: Promote indirect call to func4 with count 5 out of 14
 ; PASS-REMARK: remark: <unknown>:0:0: Promote indirect call to func2 with count 4 out of 9
 ; PASS-REMARK: remark: <unknown>:0:0: Promote indirect call to func3 with count 3 out of 5
+; PASS-REMARK-NOT: remark: <unknown>:0:0: Promote indirect call to func4 with count 3 out of 5
+; PASS-REMARK-NOT: remark: <unknown>:0:0: Promote indirect call to func2 with count 1 out of 2
 
-; FAIL-REMARK-NOT: remark: <unknown>:0:0: Promote indirect call to func4
-; FAIL-REMARK-NOT: remark: <unknown>:0:0: Promote indirect call to func2
-; FAIL-REMARK-NOT: remark: <unknown>:0:0: Promote indirect call to func3
-
-; If ICP does not specify "-hot-func-cutoff-for-icp", then it falls back to the default ProfileSummaryCutoffHot value,
-; and will promote "%call2" as well.
+;; Cutoff of 100000 (10%) has a cutoff count of 15. Neither of the profile counts of %call and %call2
+;; reaches 15 so nothing is promoted.
+; FAIL-REMARK-NOT: Promote indirect call
 
+;; If ICP does not specify "-hot-func-cutoff-for-icp", then it falls back to the default ProfileSummaryCutoffHot value,
+;; and both "%call" and "%call2" will be promoted.
 ; PASS-REMARK-DEFAULT: remark: <unknown>:0:0: Promote indirect call to func4 with count 5 out of 14
 ; PASS-REMARK-DEFAULT: remark: <unknown>:0:0: Promote indirect call to func2 with count 4 out of 9
 ; PASS-REMARK-DEFAULT: remark: <unknown>:0:0: Promote indirect call to func3 with count 3 out of 5
@@ -95,4 +99,4 @@ entry:
 !32 = !{i32 999990, i64 2, i32 15}
 !33 = !{i32 999999, i64 1, i32 16}
 !34 = !{!"VP", i32 0, i64 14, i64 7651369219802541373, i64 5, i64 -4377547752858689819, i64 4, i64 -6929281286627296573, i64 3, i64 -2545542355363006406, i64 2}
-!35 = !{!"VP", i32 0, i64 5, i64 7651369219802541373, i64 3, i64 -4377547752858689819, i64 1}
+!35 = !{!"VP", i32 0, i64 5, i64 7651369219802541373, i64 3, i64 -4377547752858689819, i64 1}
\ No newline at end of file



More information about the llvm-commits mailing list