https://github.com/Karthikdhondi updated https://github.com/llvm/llvm-project/pull/160343
>From 5e58c599d47c3a15be96e76bd6bcf279e01f75b6 Mon Sep 17 00:00:00 2001 From: Karthikdhondi <[email protected]> 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 <[email protected]> --- .../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 <[email protected]> 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 <[email protected]> --- 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; + _______________________________________________ cfe-commits mailing list [email protected] https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
