https://github.com/kubamracek updated https://github.com/llvm/llvm-project/pull/228074
>From de12181704ec2d1b41ed60d3cebe628629a96135 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 273634a09f1caa..50275215aa9066 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 2ddb265df82e969aba974a760edf464fb24e2f53 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 50275215aa9066..6b90d0c4578fdd 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 749110a51efc49df6ce6da82f9c1c7714aa5b253 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 a3c516c5e03423d79b9ae405fe6e0e19a4967037 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 c088a72334b4e7..1d68a28c2587ed 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 6b90d0c4578fdd..fbbad054573bcd 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 97652c546619c1f03208978b719b3509d3f16a77 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 1d68a28c2587ed..447d703e74ff32 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 88bd8568bb288b..23c6f0f63c9fa4 100644 --- a/llvm/include/llvm/Bitcode/LLVMBitCodes.h +++ b/llvm/include/llvm/Bitcode/LLVMBitCodes.h @@ -836,6 +836,7 @@ enum AttributeKindCodes { ATTR_KIND_NOIPA = 109, ATTR_KIND_NOFREEOBJ = 110, ATTR_KIND_SANITIZE_CONCURRENCY = 111, + ATTR_KIND_LIKELY_MODULE_LOCAL = 112, }; enum ComdatSelectionKindCodes { diff --git a/llvm/include/llvm/IR/Attributes.td b/llvm/include/llvm/IR/Attributes.td index b0e8aeef8e7652..9fdbffb995de81 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 fbbad054573bcd..9dd9df45c4a507 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 6078165df76fd4..a2eb7c3f5a9116 100644 --- a/llvm/lib/Bitcode/Reader/BitcodeReader.cpp +++ b/llvm/lib/Bitcode/Reader/BitcodeReader.cpp @@ -2346,6 +2346,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 6b5f7b7a75cfa9..da4dac1030ce26 100644 --- a/llvm/lib/Bitcode/Writer/BitcodeWriter.cpp +++ b/llvm/lib/Bitcode/Writer/BitcodeWriter.cpp @@ -1041,6 +1041,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 5395a362fffb6b..1233c805908a5f 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 9cf0bda60f2050..8c87dbc4770dcb 100644 --- a/llvm/test/Bitcode/attributes.ll +++ b/llvm/test/Bitcode/attributes.ll @@ -612,6 +612,11 @@ define void @f_sanitize_concurrency() sanitize_concurrency { ret void } +; 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) } @@ -677,4 +682,5 @@ define void @f_sanitize_concurrency() sanitize_concurrency { ; CHECK: attributes [[NODIVERGENCESOURCE]] = { nodivergencesource } ; CHECK: attributes [[NOIPA]] = { noipa } ; CHECK: attributes [[SANITIZE_CONCURRENCY]] = { sanitize_concurrency } +; 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 0392fd51fc3337412e8589109bf4a84061fe3abe 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 450e3a35d6f1be..42cc82598e8b54 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
