-
Notifications
You must be signed in to change notification settings - Fork 15.4k
[Clang][Driver][LLVM] Add -fno-inline-functions-called-once; expose p… #160343
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
[Clang][Driver][LLVM] Add -fno-inline-functions-called-once; expose p… #160343
Conversation
|
@llvm/pr-subscribers-llvm-transforms @llvm/pr-subscribers-clang-driver Author: kd0608 (Karthikdhondi) Changes…ositive form in --help This wires the Clang driver boolean flag and forwards the negative form to LLVM as '-mllvm -no-inline-functions-called-once'. The positive form is exposed in --help (no backend flag forwarded). Default behavior is unchanged unless the flag is passed. LLVM side: add the NoInlineFuncCalledOnce pass and thread the flag through PassBuilder pipelines. Tests:
Also drops obsolete 'unsupported flag' expectations in clang/test/Driver/clang_f_opts.c. Full diff: https://github.com/llvm/llvm-project/pull/160343.diff 9 Files Affected:
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;
+
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();
+}
|
|
@llvm/pr-subscribers-clang Author: kd0608 (Karthikdhondi) Changes…ositive form in --help This wires the Clang driver boolean flag and forwards the negative form to LLVM as '-mllvm -no-inline-functions-called-once'. The positive form is exposed in --help (no backend flag forwarded). Default behavior is unchanged unless the flag is passed. LLVM side: add the NoInlineFuncCalledOnce pass and thread the flag through PassBuilder pipelines. Tests:
Also drops obsolete 'unsupported flag' expectations in clang/test/Driver/clang_f_opts.c. Full diff: https://github.com/llvm/llvm-project/pull/160343.diff 9 Files Affected:
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;
+
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();
+}
|
boomanaiden154
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What is the use case for this?
jhuber6
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The LLVM changes and Clang flag changes should likely be separate
+1, please add motivation in the description |
…-called-once via PassBuilder Signed-off-by: Karthikdhondi <[email protected]>
…e form in --help Signed-off-by: Karthikdhondi <[email protected]>
caa85aa to
eebc35e
Compare
Thanks! I’ve split the change as suggested. |
Thanks, I’ve added the motivation in the description. |
Thanks, I’ve added the motivation in the description. |
|
While GCC has -finline-functions-called-once, we don't blindly copy every option it supports. Please provide clear, technical justification. The description looks AI-generated, making it difficult to assess its value.
I don't think you split the LLVM and Clang side changes. |
Apologies for not conveying better. We are working on a large application code base that was earlier compiled with GNU toolchain. Compiler option "-fno-inline-functions-called-once" was used. Now considering the benefits of LLVM toolchain, they want to have similar option with LLVM. Using attribute((no-inline)), requires making changes to application which is not a feasible option. So, I have enabled the pass in LLVM it worked fine, and thought to create a PR for the same. So that, it might be helpful for others too. By splitting the changes means, Do I need to create two different PRs for LLVM and Clang side changes? |
…ositive form in --help
This wires the Clang driver boolean flag and forwards the negative form to LLVM as '-mllvm -no-inline-functions-called-once'. The positive form is exposed in --help (no backend flag forwarded). Default behavior is unchanged unless the flag is passed.
LLVM side: add the NoInlineFuncCalledOnce pass and thread the flag through PassBuilder pipelines.
Tests:
Also drops obsolete 'unsupported flag' expectations in clang/test/Driver/clang_f_opts.c.
Motivation
-fno-inline-functions-called-once lets users keep TU-local, single-caller helpers as real calls even under -O2/-O3, preserving useful function boundaries without disabling other optimizations.
Why this helps
Scope & risk
Ecosystem parity