[llvm] [Attr] Add `noipa` function attribute (PR #203304)
J. Ryan Stinnett via llvm-commits
llvm-commits at lists.llvm.org
Thu Jun 11 08:57:10 PDT 2026
https://github.com/jryans updated https://github.com/llvm/llvm-project/pull/203304
>From 3063d323973126cbb760e657b236aec5cf28ace8 Mon Sep 17 00:00:00 2001
From: "J. Ryan Stinnett" <ryan at convolv.es>
Date: Thu, 11 Jun 2026 15:06:51 +0100
Subject: [PATCH 1/3] [Attr] Add `noipa` function attribute
This adds a `noipa` function attribute to LLVM IR. This new attribute
disables any interprocedural analysis that inspects the definition of
the function. Setting this attribute is equivalent to moving the
function definition to a separate, optimizer-opaque, module.
Revival of https://reviews.llvm.org/D101011
Discussed in https://discourse.llvm.org/t/noipa-continues/74411
LLVM portion of https://github.com/llvm/llvm-project/issues/40819
---
llvm/docs/LangRef.rst | 5 +++++
llvm/include/llvm/Bitcode/LLVMBitCodes.h | 1 +
llvm/include/llvm/IR/Attributes.td | 3 +++
llvm/include/llvm/IR/GlobalValue.h | 10 +++++++---
llvm/lib/Bitcode/Reader/BitcodeReader.cpp | 2 ++
llvm/lib/Bitcode/Writer/BitcodeWriter.cpp | 2 ++
llvm/lib/IR/Globals.cpp | 7 +++++++
llvm/lib/Transforms/IPO/FunctionAttrs.cpp | 12 ++++++------
llvm/lib/Transforms/Utils/CodeExtractor.cpp | 1 +
llvm/test/Bitcode/attributes.ll | 6 ++++++
llvm/unittests/IR/FunctionTest.cpp | 11 +++++++++++
llvm/utils/emacs/llvm-mode.el | 2 +-
llvm/utils/kate/llvm.xml | 1 +
llvm/utils/vim/syntax/llvm.vim | 1 +
llvm/utils/vscode/llvm/syntaxes/ll.tmLanguage.yaml | 1 +
15 files changed, 55 insertions(+), 10 deletions(-)
diff --git a/llvm/docs/LangRef.rst b/llvm/docs/LangRef.rst
index cf052513c5ef8..d2a1a32e2c6df 100644
--- a/llvm/docs/LangRef.rst
+++ b/llvm/docs/LangRef.rst
@@ -2390,6 +2390,11 @@ For example:
This attribute indicates that the inliner should never inline this
function in any situation. This attribute may not be used together
with the ``alwaysinline`` attribute.
+``noipa``
+ Disables any interprocedural analysis that inspects the definition of this
+ function. Equivalent to moving this function definition to a separate,
+ optimizer-opaque, module. Any attributes on the function are still respected
+ (as they would be if they remained on a function declaration in this module).
``nomerge``
This attribute indicates that calls to this function should never be merged
during optimization. For example, it will prevent tail merging otherwise
diff --git a/llvm/include/llvm/Bitcode/LLVMBitCodes.h b/llvm/include/llvm/Bitcode/LLVMBitCodes.h
index 95787c595dff7..5c2b681cc3d63 100644
--- a/llvm/include/llvm/Bitcode/LLVMBitCodes.h
+++ b/llvm/include/llvm/Bitcode/LLVMBitCodes.h
@@ -820,6 +820,7 @@ enum AttributeKindCodes {
ATTR_KIND_DENORMAL_FPENV = 106,
ATTR_KIND_NOOUTLINE = 107,
ATTR_KIND_FLATTEN = 108,
+ ATTR_KIND_NO_INTERPROCEDURAL_ANALYSIS = 109,
};
enum ComdatSelectionKindCodes {
diff --git a/llvm/include/llvm/IR/Attributes.td b/llvm/include/llvm/IR/Attributes.td
index 34032e341d85e..4e45100b54d38 100644
--- a/llvm/include/llvm/IR/Attributes.td
+++ b/llvm/include/llvm/IR/Attributes.td
@@ -218,6 +218,9 @@ def NoInline : EnumAttr<"noinline", IntersectPreserve, [FnAttr]>;
/// nooutline
def NoOutline : EnumAttr<"nooutline", IntersectPreserve, [FnAttr]>;
+/// Do not do interprocedural analysis or optimization including this function
+def NoIPA : EnumAttr<"noipa", IntersectPreserve, [FnAttr]>;
+
/// Function is called early and/or often, so lazy binding isn't worthwhile.
def NonLazyBind : EnumAttr<"nonlazybind", IntersectPreserve, [FnAttr]>;
diff --git a/llvm/include/llvm/IR/GlobalValue.h b/llvm/include/llvm/IR/GlobalValue.h
index 83e695cdd27d9..59424f201be84 100644
--- a/llvm/include/llvm/IR/GlobalValue.h
+++ b/llvm/include/llvm/IR/GlobalValue.h
@@ -136,7 +136,7 @@ class GlobalValue : public Constant {
/// Returns true if the definition of this global may be replaced by a
/// differently optimized variant of the same source level function at link
/// time.
- bool mayBeDerefined() const {
+ bool mayBeDerefinedOrNoIPA() const {
switch (getLinkage()) {
case WeakODRLinkage:
case LinkOnceODRLinkage:
@@ -155,7 +155,7 @@ class GlobalValue : public Constant {
// nobuiltin due to attributes at call-sites. To avoid applying IPO based
// on nobuiltin semantics, treat such function definitions as maybe
// derefined.
- return isInterposable() || isNobuiltinFnDef();
+ return isInterposable() || isNobuiltinFnDef() || isNoipaFnDef();
}
llvm_unreachable("Fully covered switch above!");
@@ -165,6 +165,10 @@ class GlobalValue : public Constant {
/// attribute.
LLVM_ABI bool isNobuiltinFnDef() const;
+ /// Returns true if the global is a function definition with the noipa
+ /// attribute.
+ LLVM_ABI bool isNoipaFnDef() const;
+
protected:
/// The intrinsic ID for this subclass (which must be a Function).
///
@@ -490,7 +494,7 @@ class GlobalValue : public Constant {
/// visible variant is *a* correct implementation of the original source
/// function; it just isn't the *only* correct implementation.
bool isDefinitionExact() const {
- return !mayBeDerefined();
+ return !mayBeDerefinedOrNoIPA();
}
/// Return true if this global has an exact defintion.
diff --git a/llvm/lib/Bitcode/Reader/BitcodeReader.cpp b/llvm/lib/Bitcode/Reader/BitcodeReader.cpp
index 87ae36afb4e65..04a62dccea179 100644
--- a/llvm/lib/Bitcode/Reader/BitcodeReader.cpp
+++ b/llvm/lib/Bitcode/Reader/BitcodeReader.cpp
@@ -2302,6 +2302,8 @@ static Attribute::AttrKind getAttrFromCode(uint64_t Code) {
return Attribute::DenormalFPEnv;
case bitc::ATTR_KIND_NOOUTLINE:
return Attribute::NoOutline;
+ case bitc::ATTR_KIND_NO_INTERPROCEDURAL_ANALYSIS:
+ return Attribute::NoIPA;
}
}
diff --git a/llvm/lib/Bitcode/Writer/BitcodeWriter.cpp b/llvm/lib/Bitcode/Writer/BitcodeWriter.cpp
index f4857461ca58e..f8f5c6953f94a 100644
--- a/llvm/lib/Bitcode/Writer/BitcodeWriter.cpp
+++ b/llvm/lib/Bitcode/Writer/BitcodeWriter.cpp
@@ -1008,6 +1008,8 @@ static uint64_t getAttrKindEncoding(Attribute::AttrKind Kind) {
return bitc::ATTR_KIND_DENORMAL_FPENV;
case Attribute::NoOutline:
return bitc::ATTR_KIND_NOOUTLINE;
+ case Attribute::NoIPA:
+ return bitc::ATTR_KIND_NO_INTERPROCEDURAL_ANALYSIS;
case Attribute::EndAttrKinds:
llvm_unreachable("Can not encode end-attribute kinds marker.");
case Attribute::None:
diff --git a/llvm/lib/IR/Globals.cpp b/llvm/lib/IR/Globals.cpp
index 805595a80a054..422517dd05843 100644
--- a/llvm/lib/IR/Globals.cpp
+++ b/llvm/lib/IR/Globals.cpp
@@ -334,6 +334,13 @@ bool GlobalValue::isNobuiltinFnDef() const {
return F->hasFnAttribute(Attribute::NoBuiltin);
}
+bool GlobalValue::isNoipaFnDef() const {
+ const Function *F = dyn_cast<Function>(this);
+ if (!F || F->empty())
+ return false;
+ return F->hasFnAttribute(Attribute::NoIPA);
+}
+
bool GlobalValue::isDeclaration() const {
// Globals are definitions if they have an initializer.
if (const GlobalVariable *GV = dyn_cast<GlobalVariable>(this))
diff --git a/llvm/lib/Transforms/IPO/FunctionAttrs.cpp b/llvm/lib/Transforms/IPO/FunctionAttrs.cpp
index 467fab519d32a..786ce066132e4 100644
--- a/llvm/lib/Transforms/IPO/FunctionAttrs.cpp
+++ b/llvm/lib/Transforms/IPO/FunctionAttrs.cpp
@@ -976,7 +976,7 @@ static void addArgumentReturnedAttrs(const SCCNodeSet &SCCNodes,
for (Function *F : SCCNodes) {
// We can infer and propagate function attributes only when we know that the
// definition we'll get at link time is *exactly* the definition we see now.
- // For more details, see GlobalValue::mayBeDerefined.
+ // For more details, see GlobalValue::mayBeDerefinedOrNoIPA.
if (!F->hasExactDefinition())
continue;
@@ -1250,7 +1250,7 @@ static void addArgumentAttrs(const SCCNodeSet &SCCNodes,
for (Function *F : SCCNodes) {
// We can infer and propagate function attributes only when we know that the
// definition we'll get at link time is *exactly* the definition we see now.
- // For more details, see GlobalValue::mayBeDerefined.
+ // For more details, see GlobalValue::mayBeDerefinedOrNoIPA.
if (!F->hasExactDefinition())
continue;
@@ -1503,7 +1503,7 @@ static void addNoAliasAttrs(const SCCNodeSet &SCCNodes,
// We can infer and propagate function attributes only when we know that the
// definition we'll get at link time is *exactly* the definition we see now.
- // For more details, see GlobalValue::mayBeDerefined.
+ // For more details, see GlobalValue::mayBeDerefinedOrNoIPA.
if (!F->hasExactDefinition())
return;
@@ -1620,7 +1620,7 @@ static void addNonNullAttrs(const SCCNodeSet &SCCNodes,
// We can infer and propagate function attributes only when we know that the
// definition we'll get at link time is *exactly* the definition we see now.
- // For more details, see GlobalValue::mayBeDerefined.
+ // For more details, see GlobalValue::mayBeDerefinedOrNoIPA.
if (!F->hasExactDefinition())
return;
@@ -1674,7 +1674,7 @@ static void addNoUndefAttrs(const SCCNodeSet &SCCNodes,
// We can infer and propagate function attributes only when we know that the
// definition we'll get at link time is *exactly* the definition we see now.
- // For more details, see GlobalValue::mayBeDerefined.
+ // For more details, see GlobalValue::mayBeDerefinedOrNoIPA.
if (!F->hasExactDefinition())
return;
@@ -2154,7 +2154,7 @@ static void addColdAttrs(const SCCNodeSet &SCCNodes,
static bool functionWillReturn(const Function &F) {
// We can infer and propagate function attributes only when we know that the
// definition we'll get at link time is *exactly* the definition we see now.
- // For more details, see GlobalValue::mayBeDerefined.
+ // For more details, see GlobalValue::mayBeDerefinedOrNoIPA.
if (!F.hasExactDefinition())
return false;
diff --git a/llvm/lib/Transforms/Utils/CodeExtractor.cpp b/llvm/lib/Transforms/Utils/CodeExtractor.cpp
index 7ffa99878cf74..39631d0c68f07 100644
--- a/llvm/lib/Transforms/Utils/CodeExtractor.cpp
+++ b/llvm/lib/Transforms/Utils/CodeExtractor.cpp
@@ -983,6 +983,7 @@ Function *CodeExtractor::constructFunctionDeclaration(
case Attribute::NoFree:
case Attribute::NoImplicitFloat:
case Attribute::NoInline:
+ case Attribute::NoIPA:
case Attribute::NoOutline:
case Attribute::NonLazyBind:
case Attribute::NoRedZone:
diff --git a/llvm/test/Bitcode/attributes.ll b/llvm/test/Bitcode/attributes.ll
index 21712fae7eecd..f696f2dd12323 100644
--- a/llvm/test/Bitcode/attributes.ll
+++ b/llvm/test/Bitcode/attributes.ll
@@ -592,6 +592,11 @@ define void @dead_on_return_sized(ptr dead_on_return(4) %p) {
ret void
}
+; CHECK: define void @noipa() [[NOIPA:#[0-9]+]]
+define void @noipa() noipa {
+ ret void
+}
+
; CHECK: attributes #0 = { noreturn }
; CHECK: attributes #1 = { nounwind }
; CHECK: attributes #2 = { memory(none) }
@@ -654,4 +659,5 @@ define void @dead_on_return_sized(ptr dead_on_return(4) %p) {
; CHECK: attributes [[SKIPPROFILE]] = { skipprofile }
; CHECK: attributes [[OPTDEBUG]] = { optdebug }
; CHECK: attributes [[NODIVERGENCESOURCE]] = { nodivergencesource }
+; CHECK: attributes [[NOIPA]] = { noipa }
; CHECK: attributes #[[NOBUILTIN]] = { nobuiltin }
diff --git a/llvm/unittests/IR/FunctionTest.cpp b/llvm/unittests/IR/FunctionTest.cpp
index 8ed76999dc13d..c7cd8bd43ecd7 100644
--- a/llvm/unittests/IR/FunctionTest.cpp
+++ b/llvm/unittests/IR/FunctionTest.cpp
@@ -644,4 +644,15 @@ TEST(FunctionTest, LLVMGetOrInsertFunction) {
EXPECT_EQ(FuncRef, FuncRef2);
}
+TEST(FunctionTest, NoIPAInexact) {
+ LLVMContext Ctx;
+ std::unique_ptr<Module> M = parseIR(Ctx, R"(
+ define void @foo() { bb1: ret void }
+ define void @bar() #0 { bb1: ret void }
+ attributes #0 = { noipa }
+ )");
+ EXPECT_TRUE(M->getFunction("foo")->isDefinitionExact());
+ EXPECT_FALSE(M->getFunction("bar")->isDefinitionExact());
+}
+
} // end namespace
diff --git a/llvm/utils/emacs/llvm-mode.el b/llvm/utils/emacs/llvm-mode.el
index 7dc1c28489839..6ee5a30e5e54c 100644
--- a/llvm/utils/emacs/llvm-mode.el
+++ b/llvm/utils/emacs/llvm-mode.el
@@ -35,7 +35,7 @@
`(,(regexp-opt
'("alwaysinline" "argmemonly" "allocsize" "builtin" "cold" "convergent" "dereferenceable" "dereferenceable_or_null" "hot" "immarg" "inaccessiblememonly"
"inaccessiblemem_or_argmemonly" "inalloca" "inlinehint" "jumptable" "minsize" "mustprogress" "naked" "nobuiltin" "nonnull" "nocapture"
- "nocallback" "nocf_check" "noduplicate" "noext" "nofree" "noimplicitfloat" "noinline" "nomerge" "nonlazybind" "noprofile" "noredzone" "noreturn"
+ "nocallback" "nocf_check" "noduplicate" "noext" "nofree" "noimplicitfloat" "noinline" "noipa" "nomerge" "nonlazybind" "noprofile" "noredzone" "noreturn"
"norecurse" "nosync" "noundef" "nounwind" "nosanitize_bounds" "nosanitize_coverage" "null_pointer_is_valid" "optdebug" "optforfuzzing" "optnone" "optsize" "preallocated" "readnone" "readonly" "returned" "returns_twice"
"shadowcallstack" "signext" "speculatable" "speculative_load_hardening" "ssp" "sspreq" "sspstrong" "safestack" "sanitize_address" "sanitize_alloc_token" "sanitize_hwaddress" "sanitize_memtag"
"sanitize_thread" "sanitize_memory" "strictfp" "swifterror" "uwtable" "vscale_range" "willreturn" "writeonly" "zeroext") 'symbols) . font-lock-constant-face)
diff --git a/llvm/utils/kate/llvm.xml b/llvm/utils/kate/llvm.xml
index c66547d9121ce..0291ed32b3f6c 100644
--- a/llvm/utils/kate/llvm.xml
+++ b/llvm/utils/kate/llvm.xml
@@ -104,6 +104,7 @@
<item> nofree </item>
<item> noimplicitfloat </item>
<item> noinline </item>
+ <item> noipa </item>
<item> nomerge </item>
<item> nooutline </item>
<item> noprofile </item>
diff --git a/llvm/utils/vim/syntax/llvm.vim b/llvm/utils/vim/syntax/llvm.vim
index d921e8bd1e401..bb805dff10a7b 100644
--- a/llvm/utils/vim/syntax/llvm.vim
+++ b/llvm/utils/vim/syntax/llvm.vim
@@ -132,6 +132,7 @@ syn keyword llvmKeyword
\ nofree
\ noimplicitfloat
\ noinline
+ \ noipa
\ nomerge
\ nooutline
\ nonlazybind
diff --git a/llvm/utils/vscode/llvm/syntaxes/ll.tmLanguage.yaml b/llvm/utils/vscode/llvm/syntaxes/ll.tmLanguage.yaml
index 06d2e43bb3c2e..6a6caccb350a5 100644
--- a/llvm/utils/vscode/llvm/syntaxes/ll.tmLanguage.yaml
+++ b/llvm/utils/vscode/llvm/syntaxes/ll.tmLanguage.yaml
@@ -229,6 +229,7 @@ patterns:
\\bnofree\\b|\
\\bnoimplicitfloat\\b|\
\\bnoinline\\b|\
+ \\bnoipa\\b|\
\\bnomerge\\b|\
\\bnooutline\\b|\
\\bnonlazybind\\b|\
>From 5da5ac739caabbf554b633c737c7f86c8c718973 Mon Sep 17 00:00:00 2001
From: "J. Ryan Stinnett" <ryan at convolv.es>
Date: Thu, 11 Jun 2026 16:30:15 +0100
Subject: [PATCH 2/3] Tweak formatting
---
llvm/include/llvm/IR/GlobalValue.h | 4 +---
1 file changed, 1 insertion(+), 3 deletions(-)
diff --git a/llvm/include/llvm/IR/GlobalValue.h b/llvm/include/llvm/IR/GlobalValue.h
index 59424f201be84..6e46eced45873 100644
--- a/llvm/include/llvm/IR/GlobalValue.h
+++ b/llvm/include/llvm/IR/GlobalValue.h
@@ -493,9 +493,7 @@ class GlobalValue : public Constant {
/// interposable (see \c isInterposable), since in such cases the currently
/// visible variant is *a* correct implementation of the original source
/// function; it just isn't the *only* correct implementation.
- bool isDefinitionExact() const {
- return !mayBeDerefinedOrNoIPA();
- }
+ bool isDefinitionExact() const { return !mayBeDerefinedOrNoIPA(); }
/// Return true if this global has an exact defintion.
bool hasExactDefinition() const {
>From 78a881c945f3d225df2a27db2cd9d45ab6526850 Mon Sep 17 00:00:00 2001
From: "J. Ryan Stinnett" <ryan at convolv.es>
Date: Thu, 11 Jun 2026 16:56:48 +0100
Subject: [PATCH 3/3] Clarify interaction with inlining
---
llvm/docs/LangRef.rst | 9 ++++++---
1 file changed, 6 insertions(+), 3 deletions(-)
diff --git a/llvm/docs/LangRef.rst b/llvm/docs/LangRef.rst
index d2a1a32e2c6df..8d6f436161a3b 100644
--- a/llvm/docs/LangRef.rst
+++ b/llvm/docs/LangRef.rst
@@ -2392,9 +2392,12 @@ For example:
with the ``alwaysinline`` attribute.
``noipa``
Disables any interprocedural analysis that inspects the definition of this
- function. Equivalent to moving this function definition to a separate,
- optimizer-opaque, module. Any attributes on the function are still respected
- (as they would be if they remained on a function declaration in this module).
+ function. This attribute is equivalent to moving this function definition to
+ a separate, optimizer-opaque, module. Any attributes on the function are
+ still respected (as they would be if they remained on a function declaration
+ in this module). This attribute does *not* control inlining, which is
+ treated as a separate dimension. Add the ``noinline`` attribute as well in
+ cases where inlining should additionally be disabled.
``nomerge``
This attribute indicates that calls to this function should never be merged
during optimization. For example, it will prevent tail merging otherwise
More information about the llvm-commits
mailing list