[llvm] [Inliner] Add option (default off) to inline all calls regardless of the cost (PR #152365)

Justin Fargnoli via llvm-commits llvm-commits at lists.llvm.org
Wed Aug 6 11:51:09 PDT 2025


https://github.com/justinfargnoli created https://github.com/llvm/llvm-project/pull/152365

Add a default off option to the inline cost calculation to always inline all viable calls regardless of the cost/benefit and cost/threshold calculations. 

>From 24f4a6ab9878d292d9b9d93fdc3b1696e28c8b4d Mon Sep 17 00:00:00 2001
From: Justin Fargnoli <jfargnoli at nvidia.com>
Date: Wed, 6 Aug 2025 18:39:31 +0000
Subject: [PATCH 1/2] [Inliner] Add option (default off) to inline all calls
 regardless of the cost

---
 llvm/lib/Analysis/InlineCost.cpp              |   7 ++
 .../Inline/inline-all-viable-calls.ll         | 116 ++++++++++++++++++
 2 files changed, 123 insertions(+)
 create mode 100644 llvm/test/Transforms/Inline/inline-all-viable-calls.ll

diff --git a/llvm/lib/Analysis/InlineCost.cpp b/llvm/lib/Analysis/InlineCost.cpp
index 22f4d08448a22..6c28306896e18 100644
--- a/llvm/lib/Analysis/InlineCost.cpp
+++ b/llvm/lib/Analysis/InlineCost.cpp
@@ -180,6 +180,10 @@ static cl::opt<bool> DisableGEPConstOperand(
     "disable-gep-const-evaluation", cl::Hidden, cl::init(false),
     cl::desc("Disables evaluation of GetElementPtr with constant operands"));
 
+static cl::opt<bool> InlineAllViableCalls(
+    "inline-all-viable-calls", cl::Hidden, cl::init(false),
+    cl::desc("Inline all viable calls, even if they exceed the inlining "
+             "threshold"));
 namespace llvm {
 std::optional<int> getStringFnAttrAsInt(const Attribute &Attr) {
   if (Attr.isValid()) {
@@ -3272,6 +3276,9 @@ InlineCost llvm::getInlineCost(
     return llvm::InlineCost::getNever(UserDecision->getFailureReason());
   }
 
+  if (InlineAllViableCalls && isInlineViable(*Callee).isSuccess())
+    return llvm::InlineCost::getAlways("inline all viable calls");
+
   LLVM_DEBUG(llvm::dbgs() << "      Analyzing call of " << Callee->getName()
                           << "... (caller:" << Call.getCaller()->getName()
                           << ")\n");
diff --git a/llvm/test/Transforms/Inline/inline-all-viable-calls.ll b/llvm/test/Transforms/Inline/inline-all-viable-calls.ll
new file mode 100644
index 0000000000000..7c1554b2d7bd9
--- /dev/null
+++ b/llvm/test/Transforms/Inline/inline-all-viable-calls.ll
@@ -0,0 +1,116 @@
+; NOTE: Assertions have been autogenerated by utils/update_test_checks.py UTC_ARGS: --version 5
+; RUN: opt -passes=inline -inline-threshold=0 -inline-all-viable-calls -S < %s | FileCheck %s
+
+; Test that -inline-all-viable-calls inlines all viable calls, but not noinline/optnone.
+
+define i32 @callee_simple(i32 %x) {
+  %1 = add i32 %x, 1
+  %2 = mul i32 %1, 2
+  %3 = sub i32 %2, 1
+  %4 = add i32 %3, 3
+  %5 = mul i32 %4, 2
+  %6 = sub i32 %5, 2
+  %7 = add i32 %6, 1
+  ret i32 %7
+}
+
+; Check that user decisions are respected.
+define i32 @callee_alwaysinline(i32 %x) alwaysinline {
+  %sub = sub i32 %x, 3
+  ret i32 %sub
+}
+
+define i32 @callee_noinline(i32 %x) noinline {
+  %div = sdiv i32 %x, 2
+  ret i32 %div
+}
+
+define i32 @callee_optnone(i32 %x) optnone noinline {
+  %rem = srem i32 %x, 2
+  ret i32 %rem
+}
+
+define i32 @caller(i32 %a) {
+; CHECK-LABEL: define i32 @caller(
+; CHECK-SAME: i32 [[A:%.*]]) {
+; CHECK-NEXT:    [[TMP7:%.*]] = add i32 [[A]], 1
+; CHECK-NEXT:    [[TMP8:%.*]] = mul i32 [[TMP7]], 2
+; CHECK-NEXT:    [[TMP3:%.*]] = sub i32 [[TMP8]], 1
+; CHECK-NEXT:    [[TMP4:%.*]] = add i32 [[TMP3]], 3
+; CHECK-NEXT:    [[TMP5:%.*]] = mul i32 [[TMP4]], 2
+; CHECK-NEXT:    [[TMP6:%.*]] = sub i32 [[TMP5]], 2
+; CHECK-NEXT:    [[ADD_I:%.*]] = add i32 [[TMP6]], 1
+; CHECK-NEXT:    [[SUB_I:%.*]] = sub i32 [[ADD_I]], 3
+; CHECK-NEXT:    [[TMP1:%.*]] = call i32 @callee_noinline(i32 [[SUB_I]])
+; CHECK-NEXT:    [[TMP2:%.*]] = call i32 @callee_optnone(i32 [[TMP1]])
+; CHECK-NEXT:    [[SUM:%.*]] = add i32 [[TMP2]], [[TMP1]]
+; CHECK-NEXT:    ret i32 [[SUM]]
+;
+  %1 = call i32 @callee_simple(i32 %a)
+  %2 = call i32 @callee_alwaysinline(i32 %1)
+  %3 = call i32 @callee_noinline(i32 %2)
+  %4 = call i32 @callee_optnone(i32 %3)
+  %sum = add i32 %4, %3
+  ret i32 %sum
+}
+
+; Check that non-viable calls are not inlined
+
+; Test recursive function is not inlined
+define i32 @recursive(i32 %n) {
+entry:
+  %cmp = icmp eq i32 %n, 0
+  br i1 %cmp, label %base, label %recurse
+
+base:
+  ret i32 0
+
+recurse:
+  %dec = sub i32 %n, 1
+  %rec = call i32 @recursive(i32 %dec)
+  %add = add i32 %rec, 1
+  ret i32 %add
+}
+
+define i32 @call_recursive(i32 %x) {
+; CHECK-LABEL: define i32 @call_recursive(
+; CHECK-SAME: i32 [[X:%.*]]) {
+; CHECK-NEXT:    [[R:%.*]] = call i32 @recursive(i32 [[X]])
+; CHECK-NEXT:    ret i32 [[R]]
+;
+  %r = call i32 @recursive(i32 %x)
+  ret i32 %r
+}
+
+; Test indirectbr prevents inlining
+define void @has_indirectbr(ptr %ptr, i32 %cond) {
+entry:
+  switch i32 %cond, label %default [
+  i32 0, label %target0
+  i32 1, label %target1
+  ]
+
+target0:
+  br label %end
+
+target1:
+  br label %end
+
+default:
+  br label %end
+
+end:
+  indirectbr ptr %ptr, [label %target0, label %target1]
+  ret void
+}
+
+define void @call_indirectbr(ptr %p, i32 %c) {
+; CHECK-LABEL: define void @call_indirectbr(
+; CHECK-SAME: ptr [[P:%.*]], i32 [[C:%.*]]) {
+; CHECK-NEXT:    call void @has_indirectbr(ptr [[P]], i32 [[C]])
+; CHECK-NEXT:    ret void
+;
+  call void @has_indirectbr(ptr %p, i32 %c)
+  ret void
+}
+

>From 7297b2a9c0f3beb4f2a51ed9d72fbaeef9961ae1 Mon Sep 17 00:00:00 2001
From: Justin Fargnoli <jfargnoli at nvidia.com>
Date: Wed, 6 Aug 2025 18:44:39 +0000
Subject: [PATCH 2/2] Update test comment

---
 llvm/test/Transforms/Inline/inline-all-viable-calls.ll | 3 +--
 1 file changed, 1 insertion(+), 2 deletions(-)

diff --git a/llvm/test/Transforms/Inline/inline-all-viable-calls.ll b/llvm/test/Transforms/Inline/inline-all-viable-calls.ll
index 7c1554b2d7bd9..2104a30f76db9 100644
--- a/llvm/test/Transforms/Inline/inline-all-viable-calls.ll
+++ b/llvm/test/Transforms/Inline/inline-all-viable-calls.ll
@@ -1,8 +1,7 @@
 ; NOTE: Assertions have been autogenerated by utils/update_test_checks.py UTC_ARGS: --version 5
 ; RUN: opt -passes=inline -inline-threshold=0 -inline-all-viable-calls -S < %s | FileCheck %s
 
-; Test that -inline-all-viable-calls inlines all viable calls, but not noinline/optnone.
-
+; Check that viable calls that are beyond the cost threshold are still inlined.
 define i32 @callee_simple(i32 %x) {
   %1 = add i32 %x, 1
   %2 = mul i32 %1, 2



More information about the llvm-commits mailing list