https://github.com/kubamracek updated https://github.com/llvm/llvm-project/pull/228074
>From 48175110a42c617791742c29cb26eab88dd652e5 Mon Sep 17 00:00:00 2001 From: Kuba Mracek <[email protected]> Date: Thu, 1 Oct 2026 11:57:38 +0100 Subject: [PATCH 1/5] [clang][Inliner] Add opt-in inliner hints for lambdas and main-file functions Clang now marks function definitions with "clang-lambda" (the function is a lambda) and "clang-main-file" (the function is defined in the main source file, as opposed to in a header-file) with the value of "inline-or-template" if it is declared inline or is a template instantiation. With -mllvm -inline-use-clang-hints (off by default), the inliner raises the thresholds for such callees, and makes lambdas, template/inline code defined in main source, and especially the combination of both. Also, allows sole-call bonus for the lambda-in-a-main-file situation (we can reasonably expect the original lambda to be completely unused after inlining, even across TUs). This makes this specific subset of typically linkonce_odr functions more likely to get inlined and optimized. No bonus applies to minsize callers or to cold call sites or callees. --- clang/lib/CodeGen/CodeGenModule.cpp | 16 +++++ clang/test/CodeGenCXX/inline-origin-hints.cpp | 29 +++++++++ llvm/lib/Analysis/InlineCost.cpp | 55 +++++++++++++++- llvm/test/Transforms/Inline/clang-hints.ll | 63 +++++++++++++++++++ 4 files changed, 161 insertions(+), 2 deletions(-) create mode 100644 clang/test/CodeGenCXX/inline-origin-hints.cpp create mode 100644 llvm/test/Transforms/Inline/clang-hints.ll diff --git a/clang/lib/CodeGen/CodeGenModule.cpp b/clang/lib/CodeGen/CodeGenModule.cpp index eec4d9e714abe6..8a0512d017323d 100644 --- a/clang/lib/CodeGen/CodeGenModule.cpp +++ b/clang/lib/CodeGen/CodeGenModule.cpp @@ -3325,6 +3325,22 @@ void CodeGenModule::SetLLVMFunctionAttributesForDefinition(const Decl *D, if (CodeGenOpts.DisableOutlining || D->hasAttr<NoOutlineAttr>()) B.addAttribute(llvm::Attribute::NoOutline); + // Hints for the optimizer (see -mllvm -inline-use-clang-hints) helping with + // linkonce_odr C++ functions: is this a lambda, is it defined in the main + // source file, is it declared inline or a template instantiation. + if (isLambdaCallOperator(dyn_cast<DeclContext>(D))) + B.addAttribute("clang-lambda"); + // D->getLocation(), for a template instantiation, is the location of the + // template's definition, not instantiation. + const SourceManager &SM = getContext().getSourceManager(); + if (SM.isInMainFile(D->getLocation())) { + const auto *FD = dyn_cast<FunctionDecl>(D); + bool InlineOrTemplate = + FD && (FD->isInlineSpecified() || FD->isTemplateInstantiation()); + B.addAttribute("clang-main-file", + InlineOrTemplate ? "inline-or-template" : ""); + } + F->addFnAttrs(B); llvm::MaybeAlign ExplicitAlignment; diff --git a/clang/test/CodeGenCXX/inline-origin-hints.cpp b/clang/test/CodeGenCXX/inline-origin-hints.cpp new file mode 100644 index 00000000000000..f73708a8309c02 --- /dev/null +++ b/clang/test/CodeGenCXX/inline-origin-hints.cpp @@ -0,0 +1,29 @@ +// RUN: rm -rf %t +// RUN: split-file %s %t +// RUN: %clang_cc1 -triple arm64-apple-macosx14.0.0 -std=c++20 -emit-llvm -o - %t/main.cpp | FileCheck %s + +//--- hdr.h +inline int hdr_inline(int x) { return x * 3; } +template <class F> int hdr_call(F f) { return f(); } + +//--- main.cpp +#include "hdr.h" + +int main_plain(int x) { return x + 1; } +inline int main_inline(int x) { return x * 7; } +template <class T> int main_tmpl(T x) { return hdr_call([&] { return x * 2; }); } + +int use(int x) { return main_plain(x) + main_inline(x) + main_tmpl(x) + hdr_inline(x); } + +// CHECK: define{{.*}} i32 @_Z10main_plaini({{.*}}) #[[PLAIN:[0-9]+]] +// CHECK: define{{.*}} i32 @_Z3usei({{.*}}) #[[PLAIN]] +// CHECK: define{{.*}} i32 @_Z11main_inlinei({{.*}}) #[[INL:[0-9]+]] +// CHECK: define{{.*}} i32 @_Z9main_tmplIiEiT_({{.*}}) #[[INL]] +// CHECK: define{{.*}} i32 @_Z10hdr_inlinei({{.*}}) #[[HDR:[0-9]+]] +// CHECK: define{{.*}} i32 @_ZZ9main_tmplIiEiT_ENKUlvE_clEv({{.*}}) #[[LAMBDA:[0-9]+]] + +// CHECK: attributes #[[PLAIN]] = { {{.*}}"clang-main-file" {{.*}} } +// CHECK: attributes #[[INL]] = { {{.*}}"clang-main-file"="inline-or-template"{{.*}} } +// CHECK: attributes #[[HDR]] = { +// CHECK-NOT: clang- +// CHECK: attributes #[[LAMBDA]] = { {{.*}}"clang-lambda" "clang-main-file"="inline-or-template"{{.*}} } diff --git a/llvm/lib/Analysis/InlineCost.cpp b/llvm/lib/Analysis/InlineCost.cpp index d418968a4a41c1..c823227f4fd429 100644 --- a/llvm/lib/Analysis/InlineCost.cpp +++ b/llvm/lib/Analysis/InlineCost.cpp @@ -183,6 +183,27 @@ 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")); + +static cl::opt<bool> UseClangHints( + "inline-use-clang-hints", cl::Hidden, cl::init(false), + cl::desc("Use clang's lambda/main-file hints to raise thresholds")); + +static cl::opt<int> ClangHintsLambdaBonus( + "clang-hints-lambda-bonus", cl::Hidden, cl::init(500), + cl::desc("Threshold bonus for lambdas")); + +static cl::opt<int> ClangHintsMainFileTemplateBonus( + "clang-hints-main-file-template-bonus", cl::Hidden, cl::init(150), + cl::desc("Threshold bonus for main-file inline/template functions")); + +static cl::opt<int> ClangHintsLambdaMainFileBonus( + "clang-hints-lambda-main-file-bonus", cl::Hidden, cl::init(3000), + cl::desc("Threshold bonus for lambdas in the main file")); + +static cl::opt<bool> ClangHintsLocalLambdas( + "clang-hints-local-lambdas", cl::Hidden, cl::init(true), + cl::desc("Treat main-file linkonce_odr lambdas as local functions")); + namespace llvm { std::optional<int> getStringFnAttrAsInt(const Attribute &Attr) { if (Attr.isValid()) { @@ -1247,10 +1268,20 @@ class InlineCostCallAnalyzer final : public CallAnalyzer { }; // Return true if CB is the sole call to local function Callee. +// +// With UseClangHints and ClangHintsLocalLambdas, a linkonce_odr function +// that's (1) a lambda, and (2) defined in the main source file, we count as +// local too. Ignoring very unusual setups (#include-ing a .cpp file), no other +// translation unit can reuse such lambda, so we expect inlining it turns the +// original dead. static bool isSoleCallToLocalFunction(const CallBase &CB, const Function &Callee) { - return Callee.hasLocalLinkage() && Callee.hasOneLiveUse() && - &Callee == CB.getCalledFunction(); + bool Local = Callee.hasLocalLinkage() || + (UseClangHints && ClangHintsLocalLambdas && + Callee.hasLinkOnceODRLinkage() && + Callee.hasFnAttribute("clang-lambda") && + Callee.hasFnAttribute("clang-main-file")); + return Local && Callee.hasOneLiveUse() && &Callee == CB.getCalledFunction(); } class InlineCostFeaturesAnalyzer final : public CallAnalyzer { @@ -2104,12 +2135,14 @@ void InlineCostCallAnalyzer::updateThreshold(CallBase &Call, Function &Callee) { int SingleBBBonusPercent = 50; int VectorBonusPercent = TTI.getInlinerVectorBonusPercent(); int LastCallToStaticBonus = TTI.getInliningLastCallToStaticBonus(); + bool ClangHintBonusAllowed = true; // Lambda to set all the above bonus and bonus percentages to 0. auto DisallowAllBonuses = [&]() { SingleBBBonusPercent = 0; VectorBonusPercent = 0; LastCallToStaticBonus = 0; + ClangHintBonusAllowed = false; }; // Use the OptMinSizeThreshold or OptSizeThreshold knob if they are available @@ -2122,6 +2155,7 @@ void InlineCostCallAnalyzer::updateThreshold(CallBase &Call, Function &Callee) { // call/return instructions. SingleBBBonusPercent = 0; VectorBonusPercent = 0; + ClangHintBonusAllowed = false; } else if (Caller->hasOptSize()) Threshold = MinIfValid(Threshold, Params.OptSizeThreshold); @@ -2194,6 +2228,23 @@ void InlineCostCallAnalyzer::updateThreshold(CallBase &Call, Function &Callee) { SingleBBBonus = Threshold * SingleBBBonusPercent / 100; VectorBonus = Threshold * VectorBonusPercent / 100; + // A lambda is usually specific to its call sites, very often there's only + // one call site, and inlining is especially benefitial: Captured variables + // turn back into the caller's locals. A function defined in the main source + // file (very typically) isn't used in other translation unit, inlining it + // cannot multiply copies across them (that's what linkonce_odr tries to + // prevent). Boost those situations. + if (UseClangHints && ClangHintBonusAllowed) { + bool IsLambda = Callee.hasFnAttribute("clang-lambda"); + Attribute MainFile = Callee.getFnAttribute("clang-main-file"); + if (IsLambda && MainFile.isValid()) + Threshold += ClangHintsLambdaMainFileBonus; + else if (IsLambda) + Threshold += ClangHintsLambdaBonus; + else if (MainFile.getValueAsString() == "inline-or-template") + Threshold += ClangHintsMainFileTemplateBonus; + } + // If there is only one call of the function, and it has internal linkage, // the cost of inlining it drops dramatically. It may seem odd to update // Cost in updateThreshold, but the bonus depends on the logic in this method. diff --git a/llvm/test/Transforms/Inline/clang-hints.ll b/llvm/test/Transforms/Inline/clang-hints.ll new file mode 100644 index 00000000000000..01b3719ce31f85 --- /dev/null +++ b/llvm/test/Transforms/Inline/clang-hints.ll @@ -0,0 +1,63 @@ +; RUN: opt -passes=inline -inline-threshold=0 -S < %s | FileCheck %s --check-prefix=OFF +; RUN: opt -passes=inline -inline-threshold=0 -inline-use-clang-hints -S < %s | FileCheck %s --check-prefix=HINTS +; RUN: opt -passes=inline -inline-threshold=0 -inline-use-clang-hints -clang-hints-lambda-main-file-bonus=0 -S < %s | FileCheck %s --check-prefix=LOCAL +; RUN: opt -passes=inline -inline-threshold=0 -inline-use-clang-hints -clang-hints-lambda-main-file-bonus=0 -clang-hints-local-lambdas=false -S < %s | FileCheck %s --check-prefix=NOLOCAL + +; OFF-LABEL: define i32 @caller( +; OFF-COUNT-2: call i32 @lambda( +; OFF-COUNT-2: call i32 @main_template( +; OFF-COUNT-2: call i32 @main_plain( +; OFF: call i32 @sole_main_inline( + +; HINTS-LABEL: define i32 @caller( +; HINTS-NOT: call i32 @lambda( +; HINTS-COUNT-2: call i32 @main_template( +; HINTS-COUNT-2: call i32 @main_plain( +; HINTS-NOT: call i32 @sole_main_inline( +; HINTS: ret i32 + +; LOCAL-LABEL: define i32 @caller( +; LOCAL-NOT: call i32 @sole_main_inline( +; LOCAL: ret i32 + +; NOLOCAL-LABEL: define i32 @caller( +; NOLOCAL: call i32 @sole_main_inline( +define i32 @caller(i32 %x) { + %a1 = call i32 @lambda(i32 %x) + %a2 = call i32 @lambda(i32 %a1) + %b1 = call i32 @main_template(i32 %a2) + %b2 = call i32 @main_template(i32 %b1) + %c1 = call i32 @main_plain(i32 %b2) + %c2 = call i32 @main_plain(i32 %c1) + %d = call i32 @sole_main_inline(i32 %c2) + ret i32 %d +} + +define linkonce_odr i32 @lambda(i32 %x) "clang-lambda" "function-inline-cost"="300" { + %r = add i32 %x, 1 + ret i32 %r +} + +define linkonce_odr i32 @main_template(i32 %x) "clang-main-file"="inline-or-template" "function-inline-cost"="300" { + %r = add i32 %x, 2 + ret i32 %r +} + +define linkonce_odr i32 @main_plain(i32 %x) "clang-main-file" "function-inline-cost"="300" { + %r = add i32 %x, 3 + ret i32 %r +} + +define linkonce_odr i32 @sole_main_inline(i32 %x) "clang-lambda" "clang-main-file" { + %v1 = mul i32 %x, 3 + %v2 = mul i32 %v1, 5 + %v3 = mul i32 %v2, 7 + %v4 = mul i32 %v3, 11 + %v5 = mul i32 %v4, 13 + %v6 = mul i32 %v5, 17 + %v7 = mul i32 %v6, 19 + %v8 = mul i32 %v7, 23 + %v9 = mul i32 %v8, 29 + %v10 = mul i32 %v9, 31 + ret i32 %v10 +} >From 80cf5803e1900bec70d4aa1eef594e2f2c9af3aa Mon Sep 17 00:00:00 2001 From: Kuba Mracek <[email protected]> Date: Mon, 5 Oct 2026 22:24:48 +0100 Subject: [PATCH 2/5] [clang][Inliner] Simplify module-local hints to a single "frontend-hint-likely-module-local" attribute --- clang/lib/CodeGen/CodeGenModule.cpp | 29 +++--- .../frontend-hint-likely-module-local.cpp | 56 +++++++++++ clang/test/CodeGenCXX/inline-origin-hints.cpp | 29 ------ llvm/lib/Analysis/InlineCost.cpp | 55 +---------- llvm/test/Transforms/Inline/clang-hints.ll | 63 ------------ .../frontend-hint-likely-module-local.ll | 95 +++++++++++++++++++ 6 files changed, 172 insertions(+), 155 deletions(-) create mode 100644 clang/test/CodeGenCXX/frontend-hint-likely-module-local.cpp delete mode 100644 clang/test/CodeGenCXX/inline-origin-hints.cpp delete mode 100644 llvm/test/Transforms/Inline/clang-hints.ll create mode 100644 llvm/test/Transforms/Inline/frontend-hint-likely-module-local.ll diff --git a/clang/lib/CodeGen/CodeGenModule.cpp b/clang/lib/CodeGen/CodeGenModule.cpp index 8a0512d017323d..65f98baea0b70c 100644 --- a/clang/lib/CodeGen/CodeGenModule.cpp +++ b/clang/lib/CodeGen/CodeGenModule.cpp @@ -93,6 +93,12 @@ static llvm::cl::opt<bool> LimitedCoverage( "limited-coverage-experimental", llvm::cl::Hidden, llvm::cl::desc("Emit limited coverage mapping information (experimental)")); +static llvm::cl::opt<bool> EmitModuleLocalHints( + "clang-emit-module-local-hints", llvm::cl::Hidden, + llvm::cl::init(false), + llvm::cl::desc("Mark inline and template functions defined in the main " + "source file with \"frontend-hint-likely-module-local\"")); + static const char AnnotationSection[] = "llvm.metadata"; static constexpr auto ErrnoTBAAMDName = "llvm.errno.tbaa"; @@ -3325,20 +3331,17 @@ void CodeGenModule::SetLLVMFunctionAttributesForDefinition(const Decl *D, if (CodeGenOpts.DisableOutlining || D->hasAttr<NoOutlineAttr>()) B.addAttribute(llvm::Attribute::NoOutline); - // Hints for the optimizer (see -mllvm -inline-use-clang-hints) helping with - // linkonce_odr C++ functions: is this a lambda, is it defined in the main - // source file, is it declared inline or a template instantiation. - if (isLambdaCallOperator(dyn_cast<DeclContext>(D))) - B.addAttribute("clang-lambda"); - // D->getLocation(), for a template instantiation, is the location of the - // template's definition, not instantiation. - const SourceManager &SM = getContext().getSourceManager(); - if (SM.isInMainFile(D->getLocation())) { + // An inline function or template instantiation defined in the main source + // file is (ignoring unusual setups like #include-ing a .cpp file) not + // referenced or emitted by any other translation unit. + if (EmitModuleLocalHints) { const auto *FD = dyn_cast<FunctionDecl>(D); - bool InlineOrTemplate = - FD && (FD->isInlineSpecified() || FD->isTemplateInstantiation()); - B.addAttribute("clang-main-file", - InlineOrTemplate ? "inline-or-template" : ""); + const SourceManager &SM = getContext().getSourceManager(); + // D->getLocation(), for a template instantiation, is the location of the + // template's definition, not instantiation. + bool InMainFile = SM.isInMainFile(D->getLocation()); + if (FD && (FD->isInlined() || FD->isTemplateInstantiation()) && InMainFile) + B.addAttribute("frontend-hint-likely-module-local"); } F->addFnAttrs(B); diff --git a/clang/test/CodeGenCXX/frontend-hint-likely-module-local.cpp b/clang/test/CodeGenCXX/frontend-hint-likely-module-local.cpp new file mode 100644 index 00000000000000..3bbea1ce840e28 --- /dev/null +++ b/clang/test/CodeGenCXX/frontend-hint-likely-module-local.cpp @@ -0,0 +1,56 @@ +// RUN: rm -rf %t +// RUN: split-file %s %t +// RUN: %clang_cc1 -triple arm64-apple-macosx14.0.0 -std=c++20 -emit-llvm -mllvm -clang-emit-module-local-hints -o %t/on.ll %t/main.cpp +// RUN: FileCheck %s < %t/on.ll +// RUN: FileCheck %s --check-prefix=PLAIN < %t/on.ll +// RUN: FileCheck %s --check-prefix=HDR < %t/on.ll +// RUN: FileCheck %s --check-prefix=HDRTMPL < %t/on.ll +// RUN: %clang_cc1 -triple arm64-apple-macosx14.0.0 -std=c++20 -emit-llvm -o - %t/main.cpp | FileCheck %s --check-prefix=OFF + +//--- hdr.h +inline int hdr_inline(int x) { return x * 3; } +template <class F> int hdr_call(F f) { return f(); } + +//--- main.cpp +#include "hdr.h" + +struct S { int m(int x) { return x - 1; } }; +int main_plain(int x) { return x + 1; } +inline int main_inline(int x) { return x * 7; } +template <class T> int main_tmpl(T x) { return hdr_call([&] { return x * 2; }); } + +int use(int x) { + return main_plain(x) + main_inline(x) + main_tmpl(x) + hdr_inline(x) + + S().m(x); +} + +// Inline functions, template instantiations and lambdas defined in main.cpp. +// CHECK-DAG: define{{.*}} i32 @_Z11main_inlinei({{.*}}) #[[INL:[0-9]+]] +// CHECK-DAG: define{{.*}} i32 @_Z9main_tmplIiEiT_({{.*}}) #[[TMPL:[0-9]+]] +// CHECK-DAG: define{{.*}} i32 @_ZZ9main_tmplIiEiT_ENKUlvE_clEv({{.*}}) #[[LAMBDA:[0-9]+]] +// CHECK-DAG: define{{.*}} i32 @_ZN1S1mEi({{.*}}) #[[MEMBER:[0-9]+]] +// CHECK-DAG: attributes #[[INL]] = { {{.*}}"frontend-hint-likely-module-local"{{.*}} } +// CHECK-DAG: attributes #[[TMPL]] = { {{.*}}"frontend-hint-likely-module-local"{{.*}} } +// CHECK-DAG: attributes #[[LAMBDA]] = { {{.*}}"frontend-hint-likely-module-local"{{.*}} } +// CHECK-DAG: attributes #[[MEMBER]] = { {{.*}}"frontend-hint-likely-module-local"{{.*}} } + +// Non-inline functions in main.cpp. +// PLAIN-DAG: define{{.*}} i32 @_Z10main_plaini({{.*}}) #[[PLAIN:[0-9]+]] +// PLAIN-DAG: define{{.*}} i32 @_Z3usei({{.*}}) #[[PLAIN]] +// PLAIN: attributes #[[PLAIN]] = { +// PLAIN-NOT: frontend-hint-likely-module-local +// PLAIN-SAME: } + +// Inline function defined in hdr.h. +// HDR: define{{.*}} i32 @_Z10hdr_inlinei({{.*}}) #[[HDR:[0-9]+]] +// HDR: attributes #[[HDR]] = { +// HDR-NOT: frontend-hint-likely-module-local +// HDR-SAME: } + +// Template defined in hdr.h, instantiated (with a main.cpp lambda) in main.cpp. +// HDRTMPL: define{{.*}} i32 @_Z8hdr_call{{.*}}) #[[HDRTMPL:[0-9]+]] +// HDRTMPL: attributes #[[HDRTMPL]] = { +// HDRTMPL-NOT: frontend-hint-likely-module-local +// HDRTMPL-SAME: } + +// OFF-NOT: frontend-hint-likely-module-local diff --git a/clang/test/CodeGenCXX/inline-origin-hints.cpp b/clang/test/CodeGenCXX/inline-origin-hints.cpp deleted file mode 100644 index f73708a8309c02..00000000000000 --- a/clang/test/CodeGenCXX/inline-origin-hints.cpp +++ /dev/null @@ -1,29 +0,0 @@ -// RUN: rm -rf %t -// RUN: split-file %s %t -// RUN: %clang_cc1 -triple arm64-apple-macosx14.0.0 -std=c++20 -emit-llvm -o - %t/main.cpp | FileCheck %s - -//--- hdr.h -inline int hdr_inline(int x) { return x * 3; } -template <class F> int hdr_call(F f) { return f(); } - -//--- main.cpp -#include "hdr.h" - -int main_plain(int x) { return x + 1; } -inline int main_inline(int x) { return x * 7; } -template <class T> int main_tmpl(T x) { return hdr_call([&] { return x * 2; }); } - -int use(int x) { return main_plain(x) + main_inline(x) + main_tmpl(x) + hdr_inline(x); } - -// CHECK: define{{.*}} i32 @_Z10main_plaini({{.*}}) #[[PLAIN:[0-9]+]] -// CHECK: define{{.*}} i32 @_Z3usei({{.*}}) #[[PLAIN]] -// CHECK: define{{.*}} i32 @_Z11main_inlinei({{.*}}) #[[INL:[0-9]+]] -// CHECK: define{{.*}} i32 @_Z9main_tmplIiEiT_({{.*}}) #[[INL]] -// CHECK: define{{.*}} i32 @_Z10hdr_inlinei({{.*}}) #[[HDR:[0-9]+]] -// CHECK: define{{.*}} i32 @_ZZ9main_tmplIiEiT_ENKUlvE_clEv({{.*}}) #[[LAMBDA:[0-9]+]] - -// CHECK: attributes #[[PLAIN]] = { {{.*}}"clang-main-file" {{.*}} } -// CHECK: attributes #[[INL]] = { {{.*}}"clang-main-file"="inline-or-template"{{.*}} } -// CHECK: attributes #[[HDR]] = { -// CHECK-NOT: clang- -// CHECK: attributes #[[LAMBDA]] = { {{.*}}"clang-lambda" "clang-main-file"="inline-or-template"{{.*}} } diff --git a/llvm/lib/Analysis/InlineCost.cpp b/llvm/lib/Analysis/InlineCost.cpp index c823227f4fd429..b7ce911ee2646c 100644 --- a/llvm/lib/Analysis/InlineCost.cpp +++ b/llvm/lib/Analysis/InlineCost.cpp @@ -183,27 +183,6 @@ 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")); - -static cl::opt<bool> UseClangHints( - "inline-use-clang-hints", cl::Hidden, cl::init(false), - cl::desc("Use clang's lambda/main-file hints to raise thresholds")); - -static cl::opt<int> ClangHintsLambdaBonus( - "clang-hints-lambda-bonus", cl::Hidden, cl::init(500), - cl::desc("Threshold bonus for lambdas")); - -static cl::opt<int> ClangHintsMainFileTemplateBonus( - "clang-hints-main-file-template-bonus", cl::Hidden, cl::init(150), - cl::desc("Threshold bonus for main-file inline/template functions")); - -static cl::opt<int> ClangHintsLambdaMainFileBonus( - "clang-hints-lambda-main-file-bonus", cl::Hidden, cl::init(3000), - cl::desc("Threshold bonus for lambdas in the main file")); - -static cl::opt<bool> ClangHintsLocalLambdas( - "clang-hints-local-lambdas", cl::Hidden, cl::init(true), - cl::desc("Treat main-file linkonce_odr lambdas as local functions")); - namespace llvm { std::optional<int> getStringFnAttrAsInt(const Attribute &Attr) { if (Attr.isValid()) { @@ -1269,18 +1248,14 @@ class InlineCostCallAnalyzer final : public CallAnalyzer { // Return true if CB is the sole call to local function Callee. // -// With UseClangHints and ClangHintsLocalLambdas, a linkonce_odr function -// that's (1) a lambda, and (2) defined in the main source file, we count as -// local too. Ignoring very unusual setups (#include-ing a .cpp file), no other -// translation unit can reuse such lambda, so we expect inlining it turns the -// original dead. +// A linkonce_odr function marked "frontend-hint-likely-module-local" counts as +// local too: no other module is expected to reference it, so we expect +// inlining the sole call to turn the original dead. static bool isSoleCallToLocalFunction(const CallBase &CB, const Function &Callee) { bool Local = Callee.hasLocalLinkage() || - (UseClangHints && ClangHintsLocalLambdas && - Callee.hasLinkOnceODRLinkage() && - Callee.hasFnAttribute("clang-lambda") && - Callee.hasFnAttribute("clang-main-file")); + (Callee.hasLinkOnceODRLinkage() && + Callee.hasFnAttribute("frontend-hint-likely-module-local")); return Local && Callee.hasOneLiveUse() && &Callee == CB.getCalledFunction(); } @@ -2135,14 +2110,12 @@ void InlineCostCallAnalyzer::updateThreshold(CallBase &Call, Function &Callee) { int SingleBBBonusPercent = 50; int VectorBonusPercent = TTI.getInlinerVectorBonusPercent(); int LastCallToStaticBonus = TTI.getInliningLastCallToStaticBonus(); - bool ClangHintBonusAllowed = true; // Lambda to set all the above bonus and bonus percentages to 0. auto DisallowAllBonuses = [&]() { SingleBBBonusPercent = 0; VectorBonusPercent = 0; LastCallToStaticBonus = 0; - ClangHintBonusAllowed = false; }; // Use the OptMinSizeThreshold or OptSizeThreshold knob if they are available @@ -2155,7 +2128,6 @@ void InlineCostCallAnalyzer::updateThreshold(CallBase &Call, Function &Callee) { // call/return instructions. SingleBBBonusPercent = 0; VectorBonusPercent = 0; - ClangHintBonusAllowed = false; } else if (Caller->hasOptSize()) Threshold = MinIfValid(Threshold, Params.OptSizeThreshold); @@ -2228,23 +2200,6 @@ void InlineCostCallAnalyzer::updateThreshold(CallBase &Call, Function &Callee) { SingleBBBonus = Threshold * SingleBBBonusPercent / 100; VectorBonus = Threshold * VectorBonusPercent / 100; - // A lambda is usually specific to its call sites, very often there's only - // one call site, and inlining is especially benefitial: Captured variables - // turn back into the caller's locals. A function defined in the main source - // file (very typically) isn't used in other translation unit, inlining it - // cannot multiply copies across them (that's what linkonce_odr tries to - // prevent). Boost those situations. - if (UseClangHints && ClangHintBonusAllowed) { - bool IsLambda = Callee.hasFnAttribute("clang-lambda"); - Attribute MainFile = Callee.getFnAttribute("clang-main-file"); - if (IsLambda && MainFile.isValid()) - Threshold += ClangHintsLambdaMainFileBonus; - else if (IsLambda) - Threshold += ClangHintsLambdaBonus; - else if (MainFile.getValueAsString() == "inline-or-template") - Threshold += ClangHintsMainFileTemplateBonus; - } - // If there is only one call of the function, and it has internal linkage, // the cost of inlining it drops dramatically. It may seem odd to update // Cost in updateThreshold, but the bonus depends on the logic in this method. diff --git a/llvm/test/Transforms/Inline/clang-hints.ll b/llvm/test/Transforms/Inline/clang-hints.ll deleted file mode 100644 index 01b3719ce31f85..00000000000000 --- a/llvm/test/Transforms/Inline/clang-hints.ll +++ /dev/null @@ -1,63 +0,0 @@ -; RUN: opt -passes=inline -inline-threshold=0 -S < %s | FileCheck %s --check-prefix=OFF -; RUN: opt -passes=inline -inline-threshold=0 -inline-use-clang-hints -S < %s | FileCheck %s --check-prefix=HINTS -; RUN: opt -passes=inline -inline-threshold=0 -inline-use-clang-hints -clang-hints-lambda-main-file-bonus=0 -S < %s | FileCheck %s --check-prefix=LOCAL -; RUN: opt -passes=inline -inline-threshold=0 -inline-use-clang-hints -clang-hints-lambda-main-file-bonus=0 -clang-hints-local-lambdas=false -S < %s | FileCheck %s --check-prefix=NOLOCAL - -; OFF-LABEL: define i32 @caller( -; OFF-COUNT-2: call i32 @lambda( -; OFF-COUNT-2: call i32 @main_template( -; OFF-COUNT-2: call i32 @main_plain( -; OFF: call i32 @sole_main_inline( - -; HINTS-LABEL: define i32 @caller( -; HINTS-NOT: call i32 @lambda( -; HINTS-COUNT-2: call i32 @main_template( -; HINTS-COUNT-2: call i32 @main_plain( -; HINTS-NOT: call i32 @sole_main_inline( -; HINTS: ret i32 - -; LOCAL-LABEL: define i32 @caller( -; LOCAL-NOT: call i32 @sole_main_inline( -; LOCAL: ret i32 - -; NOLOCAL-LABEL: define i32 @caller( -; NOLOCAL: call i32 @sole_main_inline( -define i32 @caller(i32 %x) { - %a1 = call i32 @lambda(i32 %x) - %a2 = call i32 @lambda(i32 %a1) - %b1 = call i32 @main_template(i32 %a2) - %b2 = call i32 @main_template(i32 %b1) - %c1 = call i32 @main_plain(i32 %b2) - %c2 = call i32 @main_plain(i32 %c1) - %d = call i32 @sole_main_inline(i32 %c2) - ret i32 %d -} - -define linkonce_odr i32 @lambda(i32 %x) "clang-lambda" "function-inline-cost"="300" { - %r = add i32 %x, 1 - ret i32 %r -} - -define linkonce_odr i32 @main_template(i32 %x) "clang-main-file"="inline-or-template" "function-inline-cost"="300" { - %r = add i32 %x, 2 - ret i32 %r -} - -define linkonce_odr i32 @main_plain(i32 %x) "clang-main-file" "function-inline-cost"="300" { - %r = add i32 %x, 3 - ret i32 %r -} - -define linkonce_odr i32 @sole_main_inline(i32 %x) "clang-lambda" "clang-main-file" { - %v1 = mul i32 %x, 3 - %v2 = mul i32 %v1, 5 - %v3 = mul i32 %v2, 7 - %v4 = mul i32 %v3, 11 - %v5 = mul i32 %v4, 13 - %v6 = mul i32 %v5, 17 - %v7 = mul i32 %v6, 19 - %v8 = mul i32 %v7, 23 - %v9 = mul i32 %v8, 29 - %v10 = mul i32 %v9, 31 - ret i32 %v10 -} diff --git a/llvm/test/Transforms/Inline/frontend-hint-likely-module-local.ll b/llvm/test/Transforms/Inline/frontend-hint-likely-module-local.ll new file mode 100644 index 00000000000000..de9aa101b2615b --- /dev/null +++ b/llvm/test/Transforms/Inline/frontend-hint-likely-module-local.ll @@ -0,0 +1,95 @@ +; RUN: opt -passes=inline -inline-threshold=0 -S < %s | FileCheck %s + +; A linkonce_odr function marked "frontend-hint-likely-module-local" gets the +; last-call-to-static bonus for its sole call, like a local function would. + +; CHECK-LABEL: define i32 @caller( +; CHECK-NOT: call i32 @hint_once( +; CHECK-COUNT-2: call i32 @hint_twice( +; CHECK: call i32 @nohint_once( +; CHECK: call i32 @hint_external_once( +; CHECK: ret i32 +define i32 @caller(i32 %x) { + %a = call i32 @hint_once(i32 %x) + %b1 = call i32 @hint_twice(i32 %a) + %b2 = call i32 @hint_twice(i32 %b1) + %c = call i32 @nohint_once(i32 %b2) + %d = call i32 @hint_external_once(i32 %c) + ret i32 %d +} + +define linkonce_odr i32 @hint_once(i32 %x) "frontend-hint-likely-module-local" { + %v1 = mul i32 %x, 3 + %v2 = mul i32 %v1, 5 + %v3 = mul i32 %v2, 7 + %v4 = mul i32 %v3, 11 + %v5 = mul i32 %v4, 13 + %v6 = mul i32 %v5, 17 + %v7 = mul i32 %v6, 19 + %v8 = mul i32 %v7, 23 + %v9 = mul i32 %v8, 29 + %v10 = mul i32 %v9, 31 + %v11 = mul i32 %v10, 37 + %v12 = mul i32 %v11, 41 + %v13 = mul i32 %v12, 43 + %v14 = mul i32 %v13, 47 + %v15 = mul i32 %v14, 53 + ret i32 %v15 +} + +define linkonce_odr i32 @hint_twice(i32 %x) "frontend-hint-likely-module-local" { + %v1 = mul i32 %x, 3 + %v2 = mul i32 %v1, 5 + %v3 = mul i32 %v2, 7 + %v4 = mul i32 %v3, 11 + %v5 = mul i32 %v4, 13 + %v6 = mul i32 %v5, 17 + %v7 = mul i32 %v6, 19 + %v8 = mul i32 %v7, 23 + %v9 = mul i32 %v8, 29 + %v10 = mul i32 %v9, 31 + %v11 = mul i32 %v10, 37 + %v12 = mul i32 %v11, 41 + %v13 = mul i32 %v12, 43 + %v14 = mul i32 %v13, 47 + %v15 = mul i32 %v14, 53 + ret i32 %v15 +} + +define linkonce_odr i32 @nohint_once(i32 %x) { + %v1 = mul i32 %x, 3 + %v2 = mul i32 %v1, 5 + %v3 = mul i32 %v2, 7 + %v4 = mul i32 %v3, 11 + %v5 = mul i32 %v4, 13 + %v6 = mul i32 %v5, 17 + %v7 = mul i32 %v6, 19 + %v8 = mul i32 %v7, 23 + %v9 = mul i32 %v8, 29 + %v10 = mul i32 %v9, 31 + %v11 = mul i32 %v10, 37 + %v12 = mul i32 %v11, 41 + %v13 = mul i32 %v12, 43 + %v14 = mul i32 %v13, 47 + %v15 = mul i32 %v14, 53 + ret i32 %v15 +} + +define i32 @hint_external_once(i32 %x) "frontend-hint-likely-module-local" { + %v1 = mul i32 %x, 3 + %v2 = mul i32 %v1, 5 + %v3 = mul i32 %v2, 7 + %v4 = mul i32 %v3, 11 + %v5 = mul i32 %v4, 13 + %v6 = mul i32 %v5, 17 + %v7 = mul i32 %v6, 19 + %v8 = mul i32 %v7, 23 + %v9 = mul i32 %v8, 29 + %v10 = mul i32 %v9, 31 + %v11 = mul i32 %v10, 37 + %v12 = mul i32 %v11, 41 + %v13 = mul i32 %v12, 43 + %v14 = mul i32 %v13, 47 + %v15 = mul i32 %v14, 53 + ret i32 %v15 +} >From 613186c72c4a731eab800a59e62ec9608d082fad Mon Sep 17 00:00:00 2001 From: Kuba Mracek <[email protected]> Date: Wed, 7 Oct 2026 22:02:05 +0100 Subject: [PATCH 3/5] [clang][Inliner] Fix module-local hint test to use per-prefix runs for shared attribute groups --- clang/lib/CodeGen/CodeGenModule.cpp | 3 +-- .../frontend-hint-likely-module-local.cpp | 27 ++++++++++++------- 2 files changed, 18 insertions(+), 12 deletions(-) diff --git a/clang/lib/CodeGen/CodeGenModule.cpp b/clang/lib/CodeGen/CodeGenModule.cpp index 65f98baea0b70c..ab63a8f29e202e 100644 --- a/clang/lib/CodeGen/CodeGenModule.cpp +++ b/clang/lib/CodeGen/CodeGenModule.cpp @@ -94,8 +94,7 @@ static llvm::cl::opt<bool> LimitedCoverage( llvm::cl::desc("Emit limited coverage mapping information (experimental)")); static llvm::cl::opt<bool> EmitModuleLocalHints( - "clang-emit-module-local-hints", llvm::cl::Hidden, - llvm::cl::init(false), + "clang-emit-module-local-hints", llvm::cl::Hidden, llvm::cl::init(false), llvm::cl::desc("Mark inline and template functions defined in the main " "source file with \"frontend-hint-likely-module-local\"")); diff --git a/clang/test/CodeGenCXX/frontend-hint-likely-module-local.cpp b/clang/test/CodeGenCXX/frontend-hint-likely-module-local.cpp index 3bbea1ce840e28..f9d03003cf556e 100644 --- a/clang/test/CodeGenCXX/frontend-hint-likely-module-local.cpp +++ b/clang/test/CodeGenCXX/frontend-hint-likely-module-local.cpp @@ -1,7 +1,10 @@ // RUN: rm -rf %t // RUN: split-file %s %t // RUN: %clang_cc1 -triple arm64-apple-macosx14.0.0 -std=c++20 -emit-llvm -mllvm -clang-emit-module-local-hints -o %t/on.ll %t/main.cpp -// RUN: FileCheck %s < %t/on.ll +// RUN: FileCheck %s --check-prefix=INL < %t/on.ll +// RUN: FileCheck %s --check-prefix=TMPL < %t/on.ll +// RUN: FileCheck %s --check-prefix=LAMBDA < %t/on.ll +// RUN: FileCheck %s --check-prefix=MEMBER < %t/on.ll // RUN: FileCheck %s --check-prefix=PLAIN < %t/on.ll // RUN: FileCheck %s --check-prefix=HDR < %t/on.ll // RUN: FileCheck %s --check-prefix=HDRTMPL < %t/on.ll @@ -25,14 +28,18 @@ int use(int x) { } // Inline functions, template instantiations and lambdas defined in main.cpp. -// CHECK-DAG: define{{.*}} i32 @_Z11main_inlinei({{.*}}) #[[INL:[0-9]+]] -// CHECK-DAG: define{{.*}} i32 @_Z9main_tmplIiEiT_({{.*}}) #[[TMPL:[0-9]+]] -// CHECK-DAG: define{{.*}} i32 @_ZZ9main_tmplIiEiT_ENKUlvE_clEv({{.*}}) #[[LAMBDA:[0-9]+]] -// CHECK-DAG: define{{.*}} i32 @_ZN1S1mEi({{.*}}) #[[MEMBER:[0-9]+]] -// CHECK-DAG: attributes #[[INL]] = { {{.*}}"frontend-hint-likely-module-local"{{.*}} } -// CHECK-DAG: attributes #[[TMPL]] = { {{.*}}"frontend-hint-likely-module-local"{{.*}} } -// CHECK-DAG: attributes #[[LAMBDA]] = { {{.*}}"frontend-hint-likely-module-local"{{.*}} } -// CHECK-DAG: attributes #[[MEMBER]] = { {{.*}}"frontend-hint-likely-module-local"{{.*}} } +// These may share an attribute group, so each is checked by a separate run. +// INL: define{{.*}} i32 @_Z11main_inlinei({{.*}}) #[[INL:[0-9]+]] +// INL: attributes #[[INL]] = { {{.*}}"frontend-hint-likely-module-local"{{.*}} } + +// TMPL: define{{.*}} i32 @_Z9main_tmplIiEiT_({{.*}}) #[[TMPL:[0-9]+]] +// TMPL: attributes #[[TMPL]] = { {{.*}}"frontend-hint-likely-module-local"{{.*}} } + +// LAMBDA: define{{.*}} i32 @_ZZ9main_tmplIiEiT_ENKUlvE_clEv({{.*}}) #[[LAMBDA:[0-9]+]] +// LAMBDA: attributes #[[LAMBDA]] = { {{.*}}"frontend-hint-likely-module-local"{{.*}} } + +// MEMBER: define{{.*}} i32 @_ZN1S1mEi({{.*}}) #[[MEMBER:[0-9]+]] +// MEMBER: attributes #[[MEMBER]] = { {{.*}}"frontend-hint-likely-module-local"{{.*}} } // Non-inline functions in main.cpp. // PLAIN-DAG: define{{.*}} i32 @_Z10main_plaini({{.*}}) #[[PLAIN:[0-9]+]] @@ -53,4 +60,4 @@ int use(int x) { // HDRTMPL-NOT: frontend-hint-likely-module-local // HDRTMPL-SAME: } -// OFF-NOT: frontend-hint-likely-module-local +// OFF-NOT: "frontend-hint-likely-module-local" >From 22a8e6afb4be70ce5de49801fbbe1b9aa2a4c3f1 Mon Sep 17 00:00:00 2001 From: Kuba Mracek <[email protected]> Date: Fri, 9 Oct 2026 20:06:48 +0100 Subject: [PATCH 4/5] [clang][Inliner] Rename "frontend-hint-likely-module-local" attribute to "likely-module-local" --- clang/lib/CodeGen/CodeGenModule.cpp | 4 ++-- ...-module-local.cpp => likely-module-local.cpp} | 16 ++++++++-------- llvm/docs/LangRef.md | 9 +++++++++ llvm/lib/Analysis/InlineCost.cpp | 8 ++++---- ...ly-module-local.ll => likely-module-local.ll} | 10 +++++----- 5 files changed, 28 insertions(+), 19 deletions(-) rename clang/test/CodeGenCXX/{frontend-hint-likely-module-local.cpp => likely-module-local.cpp} (80%) rename llvm/test/Transforms/Inline/{frontend-hint-likely-module-local.ll => likely-module-local.ll} (84%) diff --git a/clang/lib/CodeGen/CodeGenModule.cpp b/clang/lib/CodeGen/CodeGenModule.cpp index ab63a8f29e202e..a1722705676422 100644 --- a/clang/lib/CodeGen/CodeGenModule.cpp +++ b/clang/lib/CodeGen/CodeGenModule.cpp @@ -96,7 +96,7 @@ static llvm::cl::opt<bool> LimitedCoverage( static llvm::cl::opt<bool> EmitModuleLocalHints( "clang-emit-module-local-hints", llvm::cl::Hidden, llvm::cl::init(false), llvm::cl::desc("Mark inline and template functions defined in the main " - "source file with \"frontend-hint-likely-module-local\"")); + "source file with \"likely-module-local\"")); static const char AnnotationSection[] = "llvm.metadata"; static constexpr auto ErrnoTBAAMDName = "llvm.errno.tbaa"; @@ -3340,7 +3340,7 @@ void CodeGenModule::SetLLVMFunctionAttributesForDefinition(const Decl *D, // template's definition, not instantiation. bool InMainFile = SM.isInMainFile(D->getLocation()); if (FD && (FD->isInlined() || FD->isTemplateInstantiation()) && InMainFile) - B.addAttribute("frontend-hint-likely-module-local"); + B.addAttribute("likely-module-local"); } F->addFnAttrs(B); diff --git a/clang/test/CodeGenCXX/frontend-hint-likely-module-local.cpp b/clang/test/CodeGenCXX/likely-module-local.cpp similarity index 80% rename from clang/test/CodeGenCXX/frontend-hint-likely-module-local.cpp rename to clang/test/CodeGenCXX/likely-module-local.cpp index f9d03003cf556e..b8e9576d4856ca 100644 --- a/clang/test/CodeGenCXX/frontend-hint-likely-module-local.cpp +++ b/clang/test/CodeGenCXX/likely-module-local.cpp @@ -30,34 +30,34 @@ int use(int x) { // Inline functions, template instantiations and lambdas defined in main.cpp. // These may share an attribute group, so each is checked by a separate run. // INL: define{{.*}} i32 @_Z11main_inlinei({{.*}}) #[[INL:[0-9]+]] -// INL: attributes #[[INL]] = { {{.*}}"frontend-hint-likely-module-local"{{.*}} } +// INL: attributes #[[INL]] = { {{.*}}"likely-module-local"{{.*}} } // TMPL: define{{.*}} i32 @_Z9main_tmplIiEiT_({{.*}}) #[[TMPL:[0-9]+]] -// TMPL: attributes #[[TMPL]] = { {{.*}}"frontend-hint-likely-module-local"{{.*}} } +// TMPL: attributes #[[TMPL]] = { {{.*}}"likely-module-local"{{.*}} } // LAMBDA: define{{.*}} i32 @_ZZ9main_tmplIiEiT_ENKUlvE_clEv({{.*}}) #[[LAMBDA:[0-9]+]] -// LAMBDA: attributes #[[LAMBDA]] = { {{.*}}"frontend-hint-likely-module-local"{{.*}} } +// LAMBDA: attributes #[[LAMBDA]] = { {{.*}}"likely-module-local"{{.*}} } // MEMBER: define{{.*}} i32 @_ZN1S1mEi({{.*}}) #[[MEMBER:[0-9]+]] -// MEMBER: attributes #[[MEMBER]] = { {{.*}}"frontend-hint-likely-module-local"{{.*}} } +// MEMBER: attributes #[[MEMBER]] = { {{.*}}"likely-module-local"{{.*}} } // Non-inline functions in main.cpp. // PLAIN-DAG: define{{.*}} i32 @_Z10main_plaini({{.*}}) #[[PLAIN:[0-9]+]] // PLAIN-DAG: define{{.*}} i32 @_Z3usei({{.*}}) #[[PLAIN]] // PLAIN: attributes #[[PLAIN]] = { -// PLAIN-NOT: frontend-hint-likely-module-local +// PLAIN-NOT: likely-module-local // PLAIN-SAME: } // Inline function defined in hdr.h. // HDR: define{{.*}} i32 @_Z10hdr_inlinei({{.*}}) #[[HDR:[0-9]+]] // HDR: attributes #[[HDR]] = { -// HDR-NOT: frontend-hint-likely-module-local +// HDR-NOT: likely-module-local // HDR-SAME: } // Template defined in hdr.h, instantiated (with a main.cpp lambda) in main.cpp. // HDRTMPL: define{{.*}} i32 @_Z8hdr_call{{.*}}) #[[HDRTMPL:[0-9]+]] // HDRTMPL: attributes #[[HDRTMPL]] = { -// HDRTMPL-NOT: frontend-hint-likely-module-local +// HDRTMPL-NOT: likely-module-local // HDRTMPL-SAME: } -// OFF-NOT: "frontend-hint-likely-module-local" +// OFF-NOT: "likely-module-local" diff --git a/llvm/docs/LangRef.md b/llvm/docs/LangRef.md index 1e8342fd119be9..01d0f8f3a5f98c 100644 --- a/llvm/docs/LangRef.md +++ b/llvm/docs/LangRef.md @@ -2295,6 +2295,15 @@ define void @f() "no-sse" { ... } on function-pointer identity can break. So, any function annotated with `jumptable` must also be `unnamed_addr`. +`"likely-module-local"` +: This attribute indicates that the function is likely not referenced from + outside of the current module, even though its linkage does not guarantee + that. It is only meaningful on functions with `linkonce_odr` linkage and is + ignored otherwise. Optimizations may use it as a profitability hint. It is + just a hint; it imposes no requirements on the inliner or other + optimizations, and furthermore it must not be relied upon for correctness, + as the function may still be referenced from another module. + `memory(...)` : This attribute specifies the possible memory effects of the call-site or function. It allows specifying the possible access kinds (`none`, diff --git a/llvm/lib/Analysis/InlineCost.cpp b/llvm/lib/Analysis/InlineCost.cpp index b7ce911ee2646c..c6060013a9c207 100644 --- a/llvm/lib/Analysis/InlineCost.cpp +++ b/llvm/lib/Analysis/InlineCost.cpp @@ -1248,14 +1248,14 @@ class InlineCostCallAnalyzer final : public CallAnalyzer { // Return true if CB is the sole call to local function Callee. // -// A linkonce_odr function marked "frontend-hint-likely-module-local" counts as -// local too: no other module is expected to reference it, so we expect -// inlining the sole call to turn the original dead. +// A linkonce_odr function marked "likely-module-local" counts as local too: no +// other module is expected to reference it, so we expect inlining the sole call +// to turn the original dead. static bool isSoleCallToLocalFunction(const CallBase &CB, const Function &Callee) { bool Local = Callee.hasLocalLinkage() || (Callee.hasLinkOnceODRLinkage() && - Callee.hasFnAttribute("frontend-hint-likely-module-local")); + Callee.hasFnAttribute("likely-module-local")); return Local && Callee.hasOneLiveUse() && &Callee == CB.getCalledFunction(); } diff --git a/llvm/test/Transforms/Inline/frontend-hint-likely-module-local.ll b/llvm/test/Transforms/Inline/likely-module-local.ll similarity index 84% rename from llvm/test/Transforms/Inline/frontend-hint-likely-module-local.ll rename to llvm/test/Transforms/Inline/likely-module-local.ll index de9aa101b2615b..6f9e21993da8e8 100644 --- a/llvm/test/Transforms/Inline/frontend-hint-likely-module-local.ll +++ b/llvm/test/Transforms/Inline/likely-module-local.ll @@ -1,7 +1,7 @@ ; RUN: opt -passes=inline -inline-threshold=0 -S < %s | FileCheck %s -; A linkonce_odr function marked "frontend-hint-likely-module-local" gets the -; last-call-to-static bonus for its sole call, like a local function would. +; A linkonce_odr function marked "likely-module-local" gets the last-call-to- +; static bonus for its sole call, like a local function would. ; CHECK-LABEL: define i32 @caller( ; CHECK-NOT: call i32 @hint_once( @@ -18,7 +18,7 @@ define i32 @caller(i32 %x) { ret i32 %d } -define linkonce_odr i32 @hint_once(i32 %x) "frontend-hint-likely-module-local" { +define linkonce_odr i32 @hint_once(i32 %x) "likely-module-local" { %v1 = mul i32 %x, 3 %v2 = mul i32 %v1, 5 %v3 = mul i32 %v2, 7 @@ -37,7 +37,7 @@ define linkonce_odr i32 @hint_once(i32 %x) "frontend-hint-likely-module-local" { ret i32 %v15 } -define linkonce_odr i32 @hint_twice(i32 %x) "frontend-hint-likely-module-local" { +define linkonce_odr i32 @hint_twice(i32 %x) "likely-module-local" { %v1 = mul i32 %x, 3 %v2 = mul i32 %v1, 5 %v3 = mul i32 %v2, 7 @@ -75,7 +75,7 @@ define linkonce_odr i32 @nohint_once(i32 %x) { ret i32 %v15 } -define i32 @hint_external_once(i32 %x) "frontend-hint-likely-module-local" { +define i32 @hint_external_once(i32 %x) "likely-module-local" { %v1 = mul i32 %x, 3 %v2 = mul i32 %v1, 5 %v3 = mul i32 %v2, 7 >From b4e194e14fa7b825d227d24487177c550b5f8937 Mon Sep 17 00:00:00 2001 From: Kuba Mracek <[email protected]> Date: Mon, 26 Oct 2026 11:47:52 +0000 Subject: [PATCH 5/5] [clang][Inliner] Make likely_module_local an enum attribute, replace cl::opt with -fmodule-local-hints Turn the "likely-module-local" string attribute into the enum attribute likely_module_local (bitcode, CodeExtractor, LangRef, editor syntax files), and replace Clang's -mllvm -clang-emit-module-local-hints cl::opt with the cc1 option -fmodule-local-hints backed by CodeGenOptions::ModuleLocalHints. --- clang/include/clang/Basic/CodeGenOptions.def | 4 ++++ clang/include/clang/Options/Options.td | 6 ++++++ clang/lib/CodeGen/CodeGenModule.cpp | 9 ++------- clang/test/CodeGenCXX/likely-module-local.cpp | 18 +++++++++--------- llvm/docs/LangRef.md | 2 +- llvm/include/llvm/Bitcode/LLVMBitCodes.h | 1 + llvm/include/llvm/IR/Attributes.td | 4 ++++ llvm/lib/Analysis/InlineCost.cpp | 4 ++-- llvm/lib/Bitcode/Reader/BitcodeReader.cpp | 2 ++ llvm/lib/Bitcode/Writer/BitcodeWriter.cpp | 2 ++ llvm/lib/Transforms/Utils/CodeExtractor.cpp | 1 + llvm/test/Bitcode/attributes.ll | 6 ++++++ .../Transforms/Inline/likely-module-local.ll | 8 ++++---- llvm/utils/kate/llvm.xml | 1 + llvm/utils/vim/syntax/llvm.vim | 1 + .../vscode/llvm/syntaxes/ll.tmLanguage.yaml | 1 + 16 files changed, 47 insertions(+), 23 deletions(-) diff --git a/clang/include/clang/Basic/CodeGenOptions.def b/clang/include/clang/Basic/CodeGenOptions.def index c8ad0eb3f302e3..ff33f5be424ecd 100644 --- a/clang/include/clang/Basic/CodeGenOptions.def +++ b/clang/include/clang/Basic/CodeGenOptions.def @@ -544,6 +544,10 @@ ENUM_CODEGENOPT(WinControlFlowGuardMechanism, ControlFlowGuardMechanism, /// Adds attributes that prevent outlining (`-mno-outline`) CODEGENOPT(DisableOutlining, 1, 0, Benign) +/// Marks inline functions and template instantiations defined in the main +/// source file as likely_module_local (`-fmodule-local-hints`) +CODEGENOPT(ModuleLocalHints, 1, 0, Benign) + /// Controlls the emission of function name in PPA1 on z/OS. CODEGENOPT(ZOSPPA1Name, 1, 1, Benign) diff --git a/clang/include/clang/Options/Options.td b/clang/include/clang/Options/Options.td index 4419e3cf6f98c5..66cdce567cf5f5 100644 --- a/clang/include/clang/Options/Options.td +++ b/clang/include/clang/Options/Options.td @@ -3786,6 +3786,12 @@ def fexperimental_abi_lowering : Flag<["-"], "fexperimental-abi-lowering">, Group<f_Group>, Visibility<[CC1Option]>, MarshallingInfoFlag<CodeGenOpts<"ExperimentalABILowering">>; +def fmodule_local_hints : Flag<["-"], "fmodule-local-hints">, + Group<f_Group>, Visibility<[CC1Option]>, + HelpText<"Mark inline functions and template instantiations defined in the " + "main source file as likely_module_local">, + MarshallingInfoFlag<CodeGenOpts<"ModuleLocalHints">>; + def fmodules_embed_all_files : Joined<["-"], "fmodules-embed-all-files">, Visibility<[ClangOption, CC1Option, CLOption]>, HelpText<"Embed the contents of all files read by this compilation into " diff --git a/clang/lib/CodeGen/CodeGenModule.cpp b/clang/lib/CodeGen/CodeGenModule.cpp index a1722705676422..145506e36a3552 100644 --- a/clang/lib/CodeGen/CodeGenModule.cpp +++ b/clang/lib/CodeGen/CodeGenModule.cpp @@ -93,11 +93,6 @@ static llvm::cl::opt<bool> LimitedCoverage( "limited-coverage-experimental", llvm::cl::Hidden, llvm::cl::desc("Emit limited coverage mapping information (experimental)")); -static llvm::cl::opt<bool> EmitModuleLocalHints( - "clang-emit-module-local-hints", llvm::cl::Hidden, llvm::cl::init(false), - llvm::cl::desc("Mark inline and template functions defined in the main " - "source file with \"likely-module-local\"")); - static const char AnnotationSection[] = "llvm.metadata"; static constexpr auto ErrnoTBAAMDName = "llvm.errno.tbaa"; @@ -3333,14 +3328,14 @@ void CodeGenModule::SetLLVMFunctionAttributesForDefinition(const Decl *D, // An inline function or template instantiation defined in the main source // file is (ignoring unusual setups like #include-ing a .cpp file) not // referenced or emitted by any other translation unit. - if (EmitModuleLocalHints) { + if (CodeGenOpts.ModuleLocalHints) { const auto *FD = dyn_cast<FunctionDecl>(D); const SourceManager &SM = getContext().getSourceManager(); // D->getLocation(), for a template instantiation, is the location of the // template's definition, not instantiation. bool InMainFile = SM.isInMainFile(D->getLocation()); if (FD && (FD->isInlined() || FD->isTemplateInstantiation()) && InMainFile) - B.addAttribute("likely-module-local"); + B.addAttribute(llvm::Attribute::LikelyModuleLocal); } F->addFnAttrs(B); diff --git a/clang/test/CodeGenCXX/likely-module-local.cpp b/clang/test/CodeGenCXX/likely-module-local.cpp index b8e9576d4856ca..88e9dd03e68e86 100644 --- a/clang/test/CodeGenCXX/likely-module-local.cpp +++ b/clang/test/CodeGenCXX/likely-module-local.cpp @@ -1,6 +1,6 @@ // RUN: rm -rf %t // RUN: split-file %s %t -// RUN: %clang_cc1 -triple arm64-apple-macosx14.0.0 -std=c++20 -emit-llvm -mllvm -clang-emit-module-local-hints -o %t/on.ll %t/main.cpp +// RUN: %clang_cc1 -triple arm64-apple-macosx14.0.0 -std=c++20 -emit-llvm -fmodule-local-hints -o %t/on.ll %t/main.cpp // RUN: FileCheck %s --check-prefix=INL < %t/on.ll // RUN: FileCheck %s --check-prefix=TMPL < %t/on.ll // RUN: FileCheck %s --check-prefix=LAMBDA < %t/on.ll @@ -30,34 +30,34 @@ int use(int x) { // Inline functions, template instantiations and lambdas defined in main.cpp. // These may share an attribute group, so each is checked by a separate run. // INL: define{{.*}} i32 @_Z11main_inlinei({{.*}}) #[[INL:[0-9]+]] -// INL: attributes #[[INL]] = { {{.*}}"likely-module-local"{{.*}} } +// INL: attributes #[[INL]] = { {{.*}}likely_module_local{{.*}} } // TMPL: define{{.*}} i32 @_Z9main_tmplIiEiT_({{.*}}) #[[TMPL:[0-9]+]] -// TMPL: attributes #[[TMPL]] = { {{.*}}"likely-module-local"{{.*}} } +// TMPL: attributes #[[TMPL]] = { {{.*}}likely_module_local{{.*}} } // LAMBDA: define{{.*}} i32 @_ZZ9main_tmplIiEiT_ENKUlvE_clEv({{.*}}) #[[LAMBDA:[0-9]+]] -// LAMBDA: attributes #[[LAMBDA]] = { {{.*}}"likely-module-local"{{.*}} } +// LAMBDA: attributes #[[LAMBDA]] = { {{.*}}likely_module_local{{.*}} } // MEMBER: define{{.*}} i32 @_ZN1S1mEi({{.*}}) #[[MEMBER:[0-9]+]] -// MEMBER: attributes #[[MEMBER]] = { {{.*}}"likely-module-local"{{.*}} } +// MEMBER: attributes #[[MEMBER]] = { {{.*}}likely_module_local{{.*}} } // Non-inline functions in main.cpp. // PLAIN-DAG: define{{.*}} i32 @_Z10main_plaini({{.*}}) #[[PLAIN:[0-9]+]] // PLAIN-DAG: define{{.*}} i32 @_Z3usei({{.*}}) #[[PLAIN]] // PLAIN: attributes #[[PLAIN]] = { -// PLAIN-NOT: likely-module-local +// PLAIN-NOT: likely_module_local // PLAIN-SAME: } // Inline function defined in hdr.h. // HDR: define{{.*}} i32 @_Z10hdr_inlinei({{.*}}) #[[HDR:[0-9]+]] // HDR: attributes #[[HDR]] = { -// HDR-NOT: likely-module-local +// HDR-NOT: likely_module_local // HDR-SAME: } // Template defined in hdr.h, instantiated (with a main.cpp lambda) in main.cpp. // HDRTMPL: define{{.*}} i32 @_Z8hdr_call{{.*}}) #[[HDRTMPL:[0-9]+]] // HDRTMPL: attributes #[[HDRTMPL]] = { -// HDRTMPL-NOT: likely-module-local +// HDRTMPL-NOT: likely_module_local // HDRTMPL-SAME: } -// OFF-NOT: "likely-module-local" +// OFF-NOT: likely_module_local diff --git a/llvm/docs/LangRef.md b/llvm/docs/LangRef.md index 01d0f8f3a5f98c..78e71328d98e17 100644 --- a/llvm/docs/LangRef.md +++ b/llvm/docs/LangRef.md @@ -2295,7 +2295,7 @@ define void @f() "no-sse" { ... } on function-pointer identity can break. So, any function annotated with `jumptable` must also be `unnamed_addr`. -`"likely-module-local"` +`likely_module_local` : This attribute indicates that the function is likely not referenced from outside of the current module, even though its linkage does not guarantee that. It is only meaningful on functions with `linkonce_odr` linkage and is diff --git a/llvm/include/llvm/Bitcode/LLVMBitCodes.h b/llvm/include/llvm/Bitcode/LLVMBitCodes.h index 89e52bf02bb6e9..08393460d1de45 100644 --- a/llvm/include/llvm/Bitcode/LLVMBitCodes.h +++ b/llvm/include/llvm/Bitcode/LLVMBitCodes.h @@ -835,6 +835,7 @@ enum AttributeKindCodes { ATTR_KIND_FLATTEN = 108, ATTR_KIND_NOIPA = 109, ATTR_KIND_NOFREEOBJ = 110, + ATTR_KIND_LIKELY_MODULE_LOCAL = 111, }; enum ComdatSelectionKindCodes { diff --git a/llvm/include/llvm/IR/Attributes.td b/llvm/include/llvm/IR/Attributes.td index 354d72cf3355b9..76af42240f0350 100644 --- a/llvm/include/llvm/IR/Attributes.td +++ b/llvm/include/llvm/IR/Attributes.td @@ -217,6 +217,10 @@ def NoImplicitFloat : EnumAttr<"noimplicitfloat", IntersectPreserve, [FnAttr]>; /// inline=never. def NoInline : EnumAttr<"noinline", IntersectPreserve, [FnAttr]>; +/// Function is likely only referenced from the current module. +def LikelyModuleLocal + : EnumAttr<"likely_module_local", IntersectAnd, [FnAttr]>; + /// nooutline def NoOutline : EnumAttr<"nooutline", IntersectPreserve, [FnAttr]>; diff --git a/llvm/lib/Analysis/InlineCost.cpp b/llvm/lib/Analysis/InlineCost.cpp index c6060013a9c207..8b9b0ea1b9f89b 100644 --- a/llvm/lib/Analysis/InlineCost.cpp +++ b/llvm/lib/Analysis/InlineCost.cpp @@ -1248,14 +1248,14 @@ class InlineCostCallAnalyzer final : public CallAnalyzer { // Return true if CB is the sole call to local function Callee. // -// A linkonce_odr function marked "likely-module-local" counts as local too: no +// A linkonce_odr function marked likely_module_local counts as local too: no // other module is expected to reference it, so we expect inlining the sole call // to turn the original dead. static bool isSoleCallToLocalFunction(const CallBase &CB, const Function &Callee) { bool Local = Callee.hasLocalLinkage() || (Callee.hasLinkOnceODRLinkage() && - Callee.hasFnAttribute("likely-module-local")); + Callee.hasFnAttribute(Attribute::LikelyModuleLocal)); return Local && Callee.hasOneLiveUse() && &Callee == CB.getCalledFunction(); } diff --git a/llvm/lib/Bitcode/Reader/BitcodeReader.cpp b/llvm/lib/Bitcode/Reader/BitcodeReader.cpp index 0535d89057671c..990846ad90cec3 100644 --- a/llvm/lib/Bitcode/Reader/BitcodeReader.cpp +++ b/llvm/lib/Bitcode/Reader/BitcodeReader.cpp @@ -2344,6 +2344,8 @@ static Attribute::AttrKind getAttrFromCode(uint64_t Code) { return Attribute::NoOutline; case bitc::ATTR_KIND_NOIPA: return Attribute::NoIPA; + case bitc::ATTR_KIND_LIKELY_MODULE_LOCAL: + return Attribute::LikelyModuleLocal; } } diff --git a/llvm/lib/Bitcode/Writer/BitcodeWriter.cpp b/llvm/lib/Bitcode/Writer/BitcodeWriter.cpp index fed7408d7bf7f7..42f88709d9c389 100644 --- a/llvm/lib/Bitcode/Writer/BitcodeWriter.cpp +++ b/llvm/lib/Bitcode/Writer/BitcodeWriter.cpp @@ -1043,6 +1043,8 @@ static uint64_t getAttrKindEncoding(Attribute::AttrKind Kind) { return bitc::ATTR_KIND_NOOUTLINE; case Attribute::NoIPA: return bitc::ATTR_KIND_NOIPA; + case Attribute::LikelyModuleLocal: + return bitc::ATTR_KIND_LIKELY_MODULE_LOCAL; case Attribute::EndAttrKinds: llvm_unreachable("Can not encode end-attribute kinds marker."); case Attribute::None: diff --git a/llvm/lib/Transforms/Utils/CodeExtractor.cpp b/llvm/lib/Transforms/Utils/CodeExtractor.cpp index aad520d25212aa..aa70d0913ade9f 100644 --- a/llvm/lib/Transforms/Utils/CodeExtractor.cpp +++ b/llvm/lib/Transforms/Utils/CodeExtractor.cpp @@ -984,6 +984,7 @@ Function *CodeExtractor::constructFunctionDeclaration( case Attribute::HybridPatchable: case Attribute::NoRecurse: case Attribute::InlineHint: + case Attribute::LikelyModuleLocal: case Attribute::MinSize: case Attribute::NoCallback: case Attribute::NoDuplicate: diff --git a/llvm/test/Bitcode/attributes.ll b/llvm/test/Bitcode/attributes.ll index ae5e806a592107..882fabd789fdcd 100644 --- a/llvm/test/Bitcode/attributes.ll +++ b/llvm/test/Bitcode/attributes.ll @@ -607,6 +607,11 @@ define nofreeobj ptr @nofreeobj(ptr nofreeobj %p) { ret ptr %p } +; CHECK: define linkonce_odr void @likely_module_local() [[LIKELYMODULELOCAL:#[0-9]+]] +define linkonce_odr void @likely_module_local() likely_module_local { + ret void +} + ; CHECK: attributes #0 = { noreturn } ; CHECK: attributes #1 = { nounwind } ; CHECK: attributes #2 = { memory(none) } @@ -671,4 +676,5 @@ define nofreeobj ptr @nofreeobj(ptr nofreeobj %p) { ; CHECK: attributes [[OPTDEBUG]] = { optdebug } ; CHECK: attributes [[NODIVERGENCESOURCE]] = { nodivergencesource } ; CHECK: attributes [[NOIPA]] = { noipa } +; CHECK: attributes [[LIKELYMODULELOCAL]] = { likely_module_local } ; CHECK: attributes #[[NOBUILTIN]] = { nobuiltin } diff --git a/llvm/test/Transforms/Inline/likely-module-local.ll b/llvm/test/Transforms/Inline/likely-module-local.ll index 6f9e21993da8e8..0a2510b417cc92 100644 --- a/llvm/test/Transforms/Inline/likely-module-local.ll +++ b/llvm/test/Transforms/Inline/likely-module-local.ll @@ -1,6 +1,6 @@ ; RUN: opt -passes=inline -inline-threshold=0 -S < %s | FileCheck %s -; A linkonce_odr function marked "likely-module-local" gets the last-call-to- +; A linkonce_odr function marked likely_module_local gets the last-call-to- ; static bonus for its sole call, like a local function would. ; CHECK-LABEL: define i32 @caller( @@ -18,7 +18,7 @@ define i32 @caller(i32 %x) { ret i32 %d } -define linkonce_odr i32 @hint_once(i32 %x) "likely-module-local" { +define linkonce_odr i32 @hint_once(i32 %x) likely_module_local { %v1 = mul i32 %x, 3 %v2 = mul i32 %v1, 5 %v3 = mul i32 %v2, 7 @@ -37,7 +37,7 @@ define linkonce_odr i32 @hint_once(i32 %x) "likely-module-local" { ret i32 %v15 } -define linkonce_odr i32 @hint_twice(i32 %x) "likely-module-local" { +define linkonce_odr i32 @hint_twice(i32 %x) likely_module_local { %v1 = mul i32 %x, 3 %v2 = mul i32 %v1, 5 %v3 = mul i32 %v2, 7 @@ -75,7 +75,7 @@ define linkonce_odr i32 @nohint_once(i32 %x) { ret i32 %v15 } -define i32 @hint_external_once(i32 %x) "likely-module-local" { +define i32 @hint_external_once(i32 %x) likely_module_local { %v1 = mul i32 %x, 3 %v2 = mul i32 %v1, 5 %v3 = mul i32 %v2, 7 diff --git a/llvm/utils/kate/llvm.xml b/llvm/utils/kate/llvm.xml index 0291ed32b3f6c1..8200548838f105 100644 --- a/llvm/utils/kate/llvm.xml +++ b/llvm/utils/kate/llvm.xml @@ -94,6 +94,7 @@ <item> inlinehint </item> <item> inteldialect </item> <item> jumptable </item> + <item> likely_module_local </item> <item> local_unnamed_addr </item> <item> mustprogress </item> <item> naked </item> diff --git a/llvm/utils/vim/syntax/llvm.vim b/llvm/utils/vim/syntax/llvm.vim index bb805dff10a7b2..72f77ce23cc25f 100644 --- a/llvm/utils/vim/syntax/llvm.vim +++ b/llvm/utils/vim/syntax/llvm.vim @@ -109,6 +109,7 @@ syn keyword llvmKeyword \ inteldialect \ internal \ jumptable + \ likely_module_local \ linkonce \ linkonce_odr \ local_unnamed_addr diff --git a/llvm/utils/vscode/llvm/syntaxes/ll.tmLanguage.yaml b/llvm/utils/vscode/llvm/syntaxes/ll.tmLanguage.yaml index 6a6caccb350a5f..583ef73f2cc2cf 100644 --- a/llvm/utils/vscode/llvm/syntaxes/ll.tmLanguage.yaml +++ b/llvm/utils/vscode/llvm/syntaxes/ll.tmLanguage.yaml @@ -207,6 +207,7 @@ patterns: \\binteldialect\\b|\ \\binternal\\b|\ \\bjumptable\\b|\ + \\blikely_module_local\\b|\ \\blinkonce\\b|\ \\blinkonce_odr\\b|\ \\blocal_unnamed_addr\\b|\ _______________________________________________ cfe-commits mailing list [email protected] https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
