https://github.com/kubamracek updated https://github.com/llvm/llvm-project/pull/228074
>From 1e2ff35e01b775183e9f155493235d2426114fab Mon Sep 17 00:00:00 2001 From: Kuba Mracek <[email protected]> Date: Thu, 1 Oct 2026 11:57:38 +0100 Subject: [PATCH 1/6] [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 | 54 +++++++++++++++- llvm/test/Transforms/Inline/clang-hints.ll | 63 +++++++++++++++++++ 4 files changed, 160 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 ce084487a69820..9162b8a7507798 100644 --- a/clang/lib/CodeGen/CodeGenModule.cpp +++ b/clang/lib/CodeGen/CodeGenModule.cpp @@ -3328,6 +3328,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 4423ab63ae19ba..cd5c307dfb7030 100644 --- a/llvm/lib/Analysis/InlineCost.cpp +++ b/llvm/lib/Analysis/InlineCost.cpp @@ -92,6 +92,26 @@ static cl::opt<int> InstrCost("inline-instr-cost", cl::Hidden, cl::init(5), cl::desc("Cost of a single instruction when inlining")); +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()) { @@ -1158,10 +1178,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 { @@ -2016,12 +2046,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 @@ -2034,6 +2066,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); @@ -2106,6 +2139,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 7e6a09beb37843c796f68e5a830c7324a484a5a1 Mon Sep 17 00:00:00 2001 From: Kuba Mracek <[email protected]> Date: Mon, 5 Oct 2026 22:24:48 +0100 Subject: [PATCH 2/6] [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 | 54 +---------- llvm/test/Transforms/Inline/clang-hints.ll | 63 ------------ .../frontend-hint-likely-module-local.ll | 95 +++++++++++++++++++ 6 files changed, 172 insertions(+), 154 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 9162b8a7507798..54b6a74acd7ac9 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"; @@ -3328,20 +3334,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 cd5c307dfb7030..3db133ef747d80 100644 --- a/llvm/lib/Analysis/InlineCost.cpp +++ b/llvm/lib/Analysis/InlineCost.cpp @@ -92,26 +92,6 @@ static cl::opt<int> InstrCost("inline-instr-cost", cl::Hidden, cl::init(5), cl::desc("Cost of a single instruction when inlining")); -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()) { @@ -1179,18 +1159,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(); } @@ -2046,14 +2022,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 @@ -2066,7 +2040,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); @@ -2139,23 +2112,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 b31c18269b07fe7bfa4c639ffd62bf81c1cc8671 Mon Sep 17 00:00:00 2001 From: Kuba Mracek <[email protected]> Date: Wed, 7 Oct 2026 22:02:05 +0100 Subject: [PATCH 3/6] [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 54b6a74acd7ac9..4ce2260311a298 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 7e9161394cab16eb17e57564b3057c442273f24e Mon Sep 17 00:00:00 2001 From: Kuba Mracek <[email protected]> Date: Fri, 9 Oct 2026 20:06:48 +0100 Subject: [PATCH 4/6] [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 4ce2260311a298..5288c035e80c89 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"; @@ -3343,7 +3343,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 7e53bbe029476f..e30422d86836ac 100644 --- a/llvm/docs/LangRef.md +++ b/llvm/docs/LangRef.md @@ -2297,6 +2297,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 3db133ef747d80..af0cdbf4e53b47 100644 --- a/llvm/lib/Analysis/InlineCost.cpp +++ b/llvm/lib/Analysis/InlineCost.cpp @@ -1159,14 +1159,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 dc6958953e8da1248395cab60a48731ce0e6c783 Mon Sep 17 00:00:00 2001 From: Kuba Mracek <[email protected]> Date: Mon, 26 Oct 2026 11:47:52 +0000 Subject: [PATCH 5/6] [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 4275eba62bac30..0a10f90d6c96b8 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 5288c035e80c89..fc54b7f28346b1 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"; @@ -3336,14 +3331,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 e30422d86836ac..443b5a781a73f7 100644 --- a/llvm/docs/LangRef.md +++ b/llvm/docs/LangRef.md @@ -2297,7 +2297,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 af0cdbf4e53b47..d41eb8a7982fc7 100644 --- a/llvm/lib/Analysis/InlineCost.cpp +++ b/llvm/lib/Analysis/InlineCost.cpp @@ -1159,14 +1159,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 e04e792d70cf54..7447af650b5d69 100644 --- a/llvm/lib/Bitcode/Writer/BitcodeWriter.cpp +++ b/llvm/lib/Bitcode/Writer/BitcodeWriter.cpp @@ -1039,6 +1039,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 06725e70f8aaf5..6a69fcc7936d89 100644 --- a/llvm/lib/Transforms/Utils/CodeExtractor.cpp +++ b/llvm/lib/Transforms/Utils/CodeExtractor.cpp @@ -907,6 +907,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|\ >From df33b115cb790d3a484b303ed990a0192fe4a860 Mon Sep 17 00:00:00 2001 From: Kuba Mracek <[email protected]> Date: Sat, 31 Oct 2026 22:26:53 +0000 Subject: [PATCH 6/6] Add driver test, preprocessed source test --- clang/include/clang/Options/Options.td | 11 ++++++----- clang/lib/CodeGen/CodeGenModule.cpp | 2 ++ clang/lib/Driver/ToolChains/Clang.cpp | 2 ++ clang/test/CodeGenCXX/likely-module-local.cpp | 12 ++++++++++++ clang/test/Driver/fmodule-local-hints.c | 9 +++++++++ 5 files changed, 31 insertions(+), 5 deletions(-) create mode 100644 clang/test/Driver/fmodule-local-hints.c diff --git a/clang/include/clang/Options/Options.td b/clang/include/clang/Options/Options.td index 0a10f90d6c96b8..d6e0ccbb387c4a 100644 --- a/clang/include/clang/Options/Options.td +++ b/clang/include/clang/Options/Options.td @@ -3786,11 +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">>; +defm module_local_hints : BoolFOption<"module-local-hints", + CodeGenOpts<"ModuleLocalHints">, DefaultFalse, + PosFlag<SetTrue, [], [ClangOption, CC1Option], + "Mark inline functions and template instantiations defined in the " + "main source file as likely_module_local">, + NegFlag<SetFalse>>; def fmodules_embed_all_files : Joined<["-"], "fmodules-embed-all-files">, Visibility<[ClangOption, CC1Option, CLOption]>, diff --git a/clang/lib/CodeGen/CodeGenModule.cpp b/clang/lib/CodeGen/CodeGenModule.cpp index fc54b7f28346b1..9483f98b265826 100644 --- a/clang/lib/CodeGen/CodeGenModule.cpp +++ b/clang/lib/CodeGen/CodeGenModule.cpp @@ -3336,6 +3336,8 @@ void CodeGenModule::SetLLVMFunctionAttributesForDefinition(const Decl *D, const SourceManager &SM = getContext().getSourceManager(); // D->getLocation(), for a template instantiation, is the location of the // template's definition, not instantiation. + // TODO: Also consider template instantiations where a template argument is + // a class declared in the main file. bool InMainFile = SM.isInMainFile(D->getLocation()); if (FD && (FD->isInlined() || FD->isTemplateInstantiation()) && InMainFile) B.addAttribute(llvm::Attribute::LikelyModuleLocal); diff --git a/clang/lib/Driver/ToolChains/Clang.cpp b/clang/lib/Driver/ToolChains/Clang.cpp index 1bce61d2cfbeaa..f8937f710a4de3 100644 --- a/clang/lib/Driver/ToolChains/Clang.cpp +++ b/clang/lib/Driver/ToolChains/Clang.cpp @@ -8611,6 +8611,8 @@ void Clang::ConstructJob(Compilation &C, const JobAction &JA, options::OPT_fno_keep_persistent_storage_variables); Args.addOptInFlag(CmdArgs, options::OPT_fkeep_inline_functions, options::OPT_fno_keep_inline_functions); + Args.addOptInFlag(CmdArgs, options::OPT_fmodule_local_hints, + options::OPT_fno_module_local_hints); Args.addOptInFlag(CmdArgs, options::OPT_fcomplete_member_pointers, options::OPT_fno_complete_member_pointers); if (Arg *A = Args.getLastArg(options::OPT_cxx_static_destructors_EQ)) diff --git a/clang/test/CodeGenCXX/likely-module-local.cpp b/clang/test/CodeGenCXX/likely-module-local.cpp index 88e9dd03e68e86..3cdace5a83ac41 100644 --- a/clang/test/CodeGenCXX/likely-module-local.cpp +++ b/clang/test/CodeGenCXX/likely-module-local.cpp @@ -10,6 +10,18 @@ // 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 +// Preprocessed source: the line markers (as emitted by -E) still attribute +// hdr.h's code to hdr.h, so the result must be the same. +// RUN: %clang_cc1 -triple arm64-apple-macosx14.0.0 -std=c++20 -E -o %t/main.ii %t/main.cpp +// RUN: %clang_cc1 -triple arm64-apple-macosx14.0.0 -std=c++20 -emit-llvm -fmodule-local-hints -x c++-cpp-output -o %t/pp.ll %t/main.ii +// RUN: FileCheck %s --check-prefix=INL < %t/pp.ll +// RUN: FileCheck %s --check-prefix=TMPL < %t/pp.ll +// RUN: FileCheck %s --check-prefix=LAMBDA < %t/pp.ll +// RUN: FileCheck %s --check-prefix=MEMBER < %t/pp.ll +// RUN: FileCheck %s --check-prefix=PLAIN < %t/pp.ll +// RUN: FileCheck %s --check-prefix=HDR < %t/pp.ll +// RUN: FileCheck %s --check-prefix=HDRTMPL < %t/pp.ll + //--- hdr.h inline int hdr_inline(int x) { return x * 3; } template <class F> int hdr_call(F f) { return f(); } diff --git a/clang/test/Driver/fmodule-local-hints.c b/clang/test/Driver/fmodule-local-hints.c new file mode 100644 index 00000000000000..4ecee74f142d9b --- /dev/null +++ b/clang/test/Driver/fmodule-local-hints.c @@ -0,0 +1,9 @@ +// RUN: %clang -### -c -fmodule-local-hints %s 2>&1 | FileCheck %s --check-prefix=ON +// RUN: %clang -### -c -fno-module-local-hints -fmodule-local-hints %s 2>&1 | FileCheck %s --check-prefix=ON +// RUN: %clang -### -c -fmodule-local-hints -fno-module-local-hints %s 2>&1 | FileCheck %s --check-prefix=OFF +// RUN: %clang -### -c %s 2>&1 | FileCheck %s --check-prefix=OFF + +// ON: "-cc1" +// ON-SAME: "-fmodule-local-hints" +// OFF-NOT: "-fmodule-local-hints" +// OFF-NOT: "-fno-module-local-hints" _______________________________________________ cfe-commits mailing list [email protected] https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
