[clang] [llvm] [Clang][Driver][LLVM] Add -fno-inline-functions-called-once; expose p… (PR #160343)

via llvm-commits llvm-commits at lists.llvm.org
Wed Sep 24 02:12:52 PDT 2025


https://github.com/Karthikdhondi updated https://github.com/llvm/llvm-project/pull/160343

>From 5e58c599d47c3a15be96e76bd6bcf279e01f75b6 Mon Sep 17 00:00:00 2001
From: Karthikdhondi <karthik.dhondi at gmail.com>
Date: Wed, 24 Sep 2025 14:31:35 +0530
Subject: [PATCH 1/2] [LLVM][IPO] Add NoInlineFuncCalledOnce and plumb
 -no-inline-functions-called-once via PassBuilder

Signed-off-by: Karthikdhondi <karthik.dhondi at gmail.com>
---
 .../Transforms/IPO/NoInlineFuncCalledOnce.h   | 18 ++++++
 llvm/lib/Passes/PassBuilderPipelines.cpp      | 19 ++++++
 llvm/lib/Transforms/IPO/CMakeLists.txt        |  1 +
 .../Transforms/IPO/NoInlineFuncCalledOnce.cpp | 62 +++++++++++++++++++
 4 files changed, 100 insertions(+)
 create mode 100644 llvm/include/llvm/Transforms/IPO/NoInlineFuncCalledOnce.h
 create mode 100644 llvm/lib/Transforms/IPO/NoInlineFuncCalledOnce.cpp

diff --git a/llvm/include/llvm/Transforms/IPO/NoInlineFuncCalledOnce.h b/llvm/include/llvm/Transforms/IPO/NoInlineFuncCalledOnce.h
new file mode 100644
index 0000000000000..41f782c02cd8c
--- /dev/null
+++ b/llvm/include/llvm/Transforms/IPO/NoInlineFuncCalledOnce.h
@@ -0,0 +1,18 @@
+#ifndef LLVM_TRANSFORMS_IPO_NOINLINEFUNCCALLEDONCE_H
+#define LLVM_TRANSFORMS_IPO_NOINLINEFUNCCALLEDONCE_H
+
+#include "llvm/IR/PassManager.h"
+#include "llvm/Support/CommandLine.h"
+
+namespace llvm {
+
+struct NoInlineFuncCalledOncePass
+    : public PassInfoMixin<NoInlineFuncCalledOncePass> {
+  PreservedAnalyses run(Module &M, ModuleAnalysisManager &);
+};
+
+// single definition of the control flag lives in the .cpp (declared here)
+extern cl::opt<bool> EnableNoInlineFuncCalledOnce;
+
+} // namespace llvm
+#endif
diff --git a/llvm/lib/Passes/PassBuilderPipelines.cpp b/llvm/lib/Passes/PassBuilderPipelines.cpp
index 30c6f06be139d..fe8dc6d810eda 100644
--- a/llvm/lib/Passes/PassBuilderPipelines.cpp
+++ b/llvm/lib/Passes/PassBuilderPipelines.cpp
@@ -66,6 +66,7 @@
 #include "llvm/Transforms/IPO/MemProfContextDisambiguation.h"
 #include "llvm/Transforms/IPO/MergeFunctions.h"
 #include "llvm/Transforms/IPO/ModuleInliner.h"
+#include "llvm/Transforms/IPO/NoInlineFuncCalledOnce.h"
 #include "llvm/Transforms/IPO/OpenMPOpt.h"
 #include "llvm/Transforms/IPO/PartialInlining.h"
 #include "llvm/Transforms/IPO/SCCP.h"
@@ -150,6 +151,12 @@
 
 using namespace llvm;
 
+namespace llvm {
+cl::opt<bool> EnableNoInlineFuncCalledOnce(
+    "no-inline-functions-called-once", cl::init(false), cl::Hidden,
+    cl::desc("Mark TU-local functions called exactly once as noinline"));
+} // namespace llvm
+
 static cl::opt<InliningAdvisorMode> UseInlineAdvisor(
     "enable-ml-inliner", cl::init(InliningAdvisorMode::Default), cl::Hidden,
     cl::desc("Enable ML policy for inliner. Currently trained for -Oz only"),
@@ -1274,6 +1281,9 @@ PassBuilder::buildModuleSimplificationPipeline(OptimizationLevel Level,
                  PGOOpt->Action == PGOOptions::SampleUse))
     MPM.addPass(PGOForceFunctionAttrsPass(PGOOpt->ColdOptType));
 
+  if (EnableNoInlineFuncCalledOnce)
+    MPM.addPass(NoInlineFuncCalledOncePass());
+
   MPM.addPass(AlwaysInlinerPass(/*InsertLifetimeIntrinsics=*/true));
 
   if (EnableModuleInliner)
@@ -1447,6 +1457,9 @@ PassBuilder::buildModuleOptimizationPipeline(OptimizationLevel Level,
   const bool LTOPreLink = isLTOPreLink(LTOPhase);
   ModulePassManager MPM;
 
+  if (EnableNoInlineFuncCalledOnce)
+    MPM.addPass(NoInlineFuncCalledOncePass());
+
   // Run partial inlining pass to partially inline functions that have
   // large bodies.
   if (RunPartialInlining)
@@ -1766,6 +1779,9 @@ PassBuilder::buildThinLTOPreLinkDefaultPipeline(OptimizationLevel Level) {
     return MPM;
   }
 
+  if (EnableNoInlineFuncCalledOnce)
+    MPM.addPass(NoInlineFuncCalledOncePass());
+
   // Run partial inlining pass to partially inline functions that have
   // large bodies.
   // FIXME: It isn't clear whether this is really the right place to run this
@@ -2012,6 +2028,9 @@ PassBuilder::buildLTODefaultPipeline(OptimizationLevel Level,
   // Lower variadic functions for supported targets prior to inlining.
   MPM.addPass(ExpandVariadicsPass(ExpandVariadicsMode::Optimize));
 
+  if (EnableNoInlineFuncCalledOnce)
+    MPM.addPass(NoInlineFuncCalledOncePass());
+
   // Note: historically, the PruneEH pass was run first to deduce nounwind and
   // generally clean up exception handling overhead. It isn't clear this is
   // valuable as the inliner doesn't currently care whether it is inlining an
diff --git a/llvm/lib/Transforms/IPO/CMakeLists.txt b/llvm/lib/Transforms/IPO/CMakeLists.txt
index 1c4ee0336d4db..50290513482ca 100644
--- a/llvm/lib/Transforms/IPO/CMakeLists.txt
+++ b/llvm/lib/Transforms/IPO/CMakeLists.txt
@@ -33,6 +33,7 @@ add_llvm_component_library(LLVMipo
   MemProfContextDisambiguation.cpp
   MergeFunctions.cpp
   ModuleInliner.cpp
+  NoInlineFuncCalledOnce.cpp
   OpenMPOpt.cpp
   PartialInlining.cpp
   SampleContextTracker.cpp
diff --git a/llvm/lib/Transforms/IPO/NoInlineFuncCalledOnce.cpp b/llvm/lib/Transforms/IPO/NoInlineFuncCalledOnce.cpp
new file mode 100644
index 0000000000000..44b56c3549791
--- /dev/null
+++ b/llvm/lib/Transforms/IPO/NoInlineFuncCalledOnce.cpp
@@ -0,0 +1,62 @@
+#include "llvm/Transforms/IPO/NoInlineFuncCalledOnce.h"
+#include "llvm/ADT/DenseMap.h"
+#include "llvm/ADT/DenseSet.h"
+#include "llvm/IR/Attributes.h"
+#include "llvm/IR/Function.h"
+#include "llvm/IR/InstIterator.h"
+#include "llvm/IR/Instructions.h"
+#include "llvm/IR/Module.h"
+#include "llvm/IR/PassManager.h"
+#include "llvm/Support/CommandLine.h"
+
+using namespace llvm;
+
+PreservedAnalyses NoInlineFuncCalledOncePass::run(Module &M,
+                                                  ModuleAnalysisManager &) {
+  DenseMap<Function *, unsigned> DirectCalls;
+  DenseSet<Function *> Recursive;
+
+  for (Function &F : M)
+    if (!F.isDeclaration() && (F.hasInternalLinkage() || F.hasPrivateLinkage()))
+      DirectCalls[&F] = 0;
+
+  for (Function &Caller : M) {
+    if (Caller.isDeclaration())
+      continue;
+    for (Instruction &I : instructions(Caller)) {
+      auto *CB = dyn_cast<CallBase>(&I);
+      if (!CB)
+        continue;
+      const Value *Op = CB->getCalledOperand()->stripPointerCasts();
+      if (auto *Callee = const_cast<Function *>(dyn_cast<Function>(Op))) {
+        if (!DirectCalls.count(Callee))
+          continue;
+        DirectCalls[Callee] += 1;
+        if (&Caller == Callee)
+          Recursive.insert(Callee);
+      }
+    }
+  }
+
+  bool Changed = false;
+  for (auto &KV : DirectCalls) {
+    Function *F = KV.first;
+    unsigned N = KV.second;
+
+    if (N != 1)
+      continue; // only called-once
+    if (Recursive.count(F))
+      continue; // skip recursion
+    if (F->hasAddressTaken())
+      continue; // skip address-taken
+    if (F->hasFnAttribute(Attribute::AlwaysInline))
+      continue;
+    if (F->hasFnAttribute(Attribute::NoInline))
+      continue;
+
+    F->addFnAttr(Attribute::NoInline);
+    Changed = true;
+  }
+
+  return Changed ? PreservedAnalyses::none() : PreservedAnalyses::all();
+}

>From eebc35eb54b31bbfaadb4ba69b3d4ea317edfbae Mon Sep 17 00:00:00 2001
From: Karthikdhondi <karthik.dhondi at gmail.com>
Date: Wed, 24 Sep 2025 14:33:23 +0530
Subject: [PATCH 2/2] [Clang][Driver] Add -fno-inline-functions-called-once;
 expose positive form in --help

Signed-off-by: Karthikdhondi <karthik.dhondi at gmail.com>
---
 clang/include/clang/Driver/Options.td         |  8 +++--
 clang/lib/Driver/ToolChains/Clang.cpp         |  6 ++++
 .../test/CodeGen/no-inline-func-called-once.c | 32 +++++++++++++++++++
 clang/test/Driver/clang_f_opts.c              |  3 --
 .../Driver/fno-inline-functions-called-once.c | 20 ++++++++++++
 5 files changed, 64 insertions(+), 5 deletions(-)
 create mode 100644 clang/test/CodeGen/no-inline-func-called-once.c
 create mode 100644 clang/test/Driver/fno-inline-functions-called-once.c

diff --git a/clang/include/clang/Driver/Options.td b/clang/include/clang/Driver/Options.td
index 16e1c396fedbe..6c6d5f8455d4f 100644
--- a/clang/include/clang/Driver/Options.td
+++ b/clang/include/clang/Driver/Options.td
@@ -6959,8 +6959,12 @@ defm merge_constants : BooleanFFlag<"merge-constants">, Group<clang_ignored_gcc_
 defm modulo_sched : BooleanFFlag<"modulo-sched">, Group<clang_ignored_gcc_optimization_f_Group>;
 defm modulo_sched_allow_regmoves : BooleanFFlag<"modulo-sched-allow-regmoves">,
     Group<clang_ignored_gcc_optimization_f_Group>;
-defm inline_functions_called_once : BooleanFFlag<"inline-functions-called-once">,
-    Group<clang_ignored_gcc_optimization_f_Group>;
+defm inline_functions_called_once
+  : BooleanFFlag<"inline-functions-called-once">,
+    Group<f_clang_Group>,
+    Visibility<[ClangOption, CC1Option]>,
+    HelpText<"Control inlining of TU-local functions called exactly once "
+             "(use -fno-inline-functions-called-once to inhibit it)">;
 def finline_limit_EQ : Joined<["-"], "finline-limit=">, Group<clang_ignored_gcc_optimization_f_Group>;
 defm finline_limit : BooleanFFlag<"inline-limit">, Group<clang_ignored_gcc_optimization_f_Group>;
 defm inline_small_functions : BooleanFFlag<"inline-small-functions">,
diff --git a/clang/lib/Driver/ToolChains/Clang.cpp b/clang/lib/Driver/ToolChains/Clang.cpp
index f67454ee517bd..78f2f209b7165 100644
--- a/clang/lib/Driver/ToolChains/Clang.cpp
+++ b/clang/lib/Driver/ToolChains/Clang.cpp
@@ -7266,6 +7266,12 @@ void Clang::ConstructJob(Compilation &C, const JobAction &JA,
 
   Args.AddLastArg(CmdArgs, options::OPT_finline_max_stacksize_EQ);
 
+  // Forward -fno-inline-functions-called-once to LLVM so the pass is enabled.
+  if (Args.hasArg(options::OPT_fno_inline_functions_called_once)) {
+    CmdArgs.push_back("-mllvm");
+    CmdArgs.push_back("-no-inline-functions-called-once");
+  }
+
   // FIXME: Find a better way to determine whether we are in C++20.
   bool HaveCxx20 =
       Std &&
diff --git a/clang/test/CodeGen/no-inline-func-called-once.c b/clang/test/CodeGen/no-inline-func-called-once.c
new file mode 100644
index 0000000000000..92cdb57297739
--- /dev/null
+++ b/clang/test/CodeGen/no-inline-func-called-once.c
@@ -0,0 +1,32 @@
+// REQUIRES: x86-registered-target
+// RUN: %clang -O1 -S -emit-llvm %s -fno-inline-functions-called-once -o - | FileCheck %s --check-prefix=NOINLINE
+
+// We verify three things:
+//   1) There is a surviving call to bad_function (so it wasn’t inlined).
+//   2) bad_function’s definition exists and carries an attribute group id.
+//   3) That attribute group includes 'noinline'.
+
+// The call is earlier in the IR than the callee/attributes, so use -DAG for the
+// first two checks to avoid order constraints, then pin the attributes match.
+
+// NOINLINE-DAG: call{{.*}} @bad_function{{.*}}
+// NOINLINE-DAG: define internal{{.*}} @bad_function{{.*}} #[[ATTR:[0-9]+]]
+// NOINLINE: attributes #[[ATTR]] = { {{.*}}noinline{{.*}} }
+
+volatile int G;
+
+static void bad_function(void) {
+  // Volatile side effect ensures the call can’t be DCE’d.
+  G++;
+}
+
+static void test(void) {
+  // Exactly one TU-local caller of bad_function.
+  bad_function();
+}
+
+int main(void) {
+  // Make the caller reachable so it survives global DCE.
+  test();
+  return 0;
+}
diff --git a/clang/test/Driver/clang_f_opts.c b/clang/test/Driver/clang_f_opts.c
index bdeb747aa66a3..39726ac78140c 100644
--- a/clang/test/Driver/clang_f_opts.c
+++ b/clang/test/Driver/clang_f_opts.c
@@ -277,7 +277,6 @@
 // RUN:     -fgcse-las                                                        \
 // RUN:     -fgcse-sm                                                         \
 // RUN:     -fipa-cp                                                          \
-// RUN:     -finline-functions-called-once                                    \
 // RUN:     -fmodulo-sched                                                    \
 // RUN:     -fmodulo-sched-allow-regmoves                                     \
 // RUN:     -fpeel-loops                                                      \
@@ -349,7 +348,6 @@
 // RUN: -fgcse-las                                                            \
 // RUN: -fgcse-sm                                                             \
 // RUN: -fipa-cp                                                              \
-// RUN: -finline-functions-called-once                                        \
 // RUN: -fmodulo-sched                                                        \
 // RUN: -fmodulo-sched-allow-regmoves                                         \
 // RUN: -fpeel-loops                                                          \
@@ -409,7 +407,6 @@
 // CHECK-WARNING-DAG: optimization flag '-fgcse-las' is not supported
 // CHECK-WARNING-DAG: optimization flag '-fgcse-sm' is not supported
 // CHECK-WARNING-DAG: optimization flag '-fipa-cp' is not supported
-// CHECK-WARNING-DAG: optimization flag '-finline-functions-called-once' is not supported
 // CHECK-WARNING-DAG: optimization flag '-fmodulo-sched' is not supported
 // CHECK-WARNING-DAG: optimization flag '-fmodulo-sched-allow-regmoves' is not supported
 // CHECK-WARNING-DAG: optimization flag '-fpeel-loops' is not supported
diff --git a/clang/test/Driver/fno-inline-functions-called-once.c b/clang/test/Driver/fno-inline-functions-called-once.c
new file mode 100644
index 0000000000000..d866f9f83358b
--- /dev/null
+++ b/clang/test/Driver/fno-inline-functions-called-once.c
@@ -0,0 +1,20 @@
+// REQUIRES: x86-registered-target
+
+// Check that -fno-inline-functions-called-once is forwarded to LLVM.
+// RUN: %clang -### -S %s -fno-inline-functions-called-once 2>&1 \
+// RUN:   | FileCheck %s --check-prefix=FWD
+// FWD: "-mllvm" "-no-inline-functions-called-once"
+
+// Check that the positive form does NOT forward anything to -mllvm.
+// RUN: %clang -### -S %s -finline-functions-called-once 2>&1 \
+// RUN:   | FileCheck %s --check-prefix=POS
+// POS-NOT: -mllvm
+// POS-NOT: -no-inline-functions-called-once
+
+// Help text should show both flags (order-independent).
+// RUN: %clang --help 2>&1 | FileCheck %s --check-prefix=HELP
+// HELP-DAG: -finline-functions-called-once
+// HELP-DAG: -fno-inline-functions-called-once
+
+int x;
+



More information about the llvm-commits mailing list