[llvm] f8d9f07 - [IR][IPO] Don't rewrite the signature of optnone functions (#211804)
via llvm-commits
llvm-commits at lists.llvm.org
Wed Aug 12 13:50:31 PDT 2026
Author: Joseph Huber
Date: 2026-08-12T15:50:21-05:00
New Revision: f8d9f07e837902050d5153002e319d1b901c2679
URL: https://github.com/llvm/llvm-project/commit/f8d9f07e837902050d5153002e319d1b901c2679
DIFF: https://github.com/llvm/llvm-project/commit/f8d9f07e837902050d5153002e319d1b901c2679.diff
LOG: [IR][IPO] Don't rewrite the signature of optnone functions (#211804)
Summary:
This PR unifies the behavior of attributes the prevent signature
requires, like `naked`, with `optnone`. Currently, `optnone` functions
are not restricted from taking plcae in IPO passes by design. However,
this means that in places the signature will still be rewritten, which
is likely not what users who make use of `optnone` desire. This PR will
allow `optnone` functions to still take place in IPO optimizations but
will disallow signatures being rewritten. This ensures the funciton
itself does not change beteen `-O0` and `-O2`.
The motivation behind this was GDB users observing debug info changes w/
and w/o LTO. Observed because LTO passes `lto<O2>` by default and
normally relies on `optnone` and `noinline` to preserve semantics.
This is the 'proper' fix to
https://github.com/llvm/llvm-project/pull/211790 which can land if the
pass maintainers believe this is incorrect.
Added:
llvm/test/Transforms/ArgumentPromotion/optnone.ll
llvm/test/Transforms/DeadArgElim/optnone.ll
llvm/test/Transforms/GlobalOpt/optnone.ll
Modified:
clang/test/CodeGenOpenCL/amdgpu-enqueue-kernel-linking.cl
llvm/docs/LangRef.md
llvm/docs/ReleaseNotes.md
llvm/include/llvm/IR/Function.h
llvm/lib/Transforms/IPO/ArgumentPromotion.cpp
llvm/lib/Transforms/IPO/DeadArgumentElimination.cpp
llvm/lib/Transforms/IPO/GlobalOpt.cpp
Removed:
################################################################################
diff --git a/clang/test/CodeGenOpenCL/amdgpu-enqueue-kernel-linking.cl b/clang/test/CodeGenOpenCL/amdgpu-enqueue-kernel-linking.cl
index bdb5918b00814..97446c61f9ec7 100644
--- a/clang/test/CodeGenOpenCL/amdgpu-enqueue-kernel-linking.cl
+++ b/clang/test/CodeGenOpenCL/amdgpu-enqueue-kernel-linking.cl
@@ -30,7 +30,7 @@
// CHECK-LABEL: define dso_local amdgpu_kernel void @test_kernel_first(
-// CHECK-LABEL: define internal fastcc void @static_invoker(ptr addrspace(1) noundef %outptr, ptr addrspace(1) noundef %argptr)
+// CHECK-LABEL: define internal void @static_invoker(ptr addrspace(1) noundef %outptr, ptr addrspace(1) noundef %argptr)
// CHECK: call i32 @__enqueue_kernel_basic(ptr addrspace(1) %{{[0-9]+}}, i32 %{{[0-9]+}}, ptr addrspace(5) %tmp, ptr addrspacecast (ptr addrspace(1) @__static_invoker_block_invoke_kernel.runtime.handle to ptr), ptr %{{.+}})
// CHECK: declare i32 @__enqueue_kernel_basic(ptr addrspace(1), i32, ptr addrspace(5), ptr, ptr) local_unnamed_addr
@@ -48,7 +48,7 @@
// CHECK-LABEL: define dso_local amdgpu_kernel void @test_kernel_second(ptr addrspace(1) noundef align 4 %outptr, ptr addrspace(1) noundef align 4 %argptr, ptr addrspace(1) noundef align 4 %
diff erence)
-// CHECK-LABEL: define internal fastcc void @static_invoker.5(ptr addrspace(1) noundef %outptr, ptr addrspace(1) noundef %argptr) unnamed_addr #{{[0-9]+}} {
+// CHECK-LABEL: define internal void @static_invoker.5(ptr addrspace(1) noundef %outptr, ptr addrspace(1) noundef %argptr) unnamed_addr #{{[0-9]+}} {
// CHECK: call i32 @__enqueue_kernel_basic(ptr addrspace(1) %{{[0-9]+}}, i32 %{{[0-9]+}}, ptr addrspace(5) %tmp, ptr addrspacecast (ptr addrspace(1) @__static_invoker_block_invoke_kernel.runtime.handle.3 to ptr), ptr %{{.+}})
diff --git a/llvm/docs/LangRef.md b/llvm/docs/LangRef.md
index 2ecd0f9adfc05..4a15ad08c0fdc 100644
--- a/llvm/docs/LangRef.md
+++ b/llvm/docs/LangRef.md
@@ -2551,6 +2551,8 @@ fn -> other_fn -> other_fn ; fn is norecurse
`optnone`
: This function attribute indicates that most optimization passes will skip
this function, with the exception of interprocedural optimization passes.
+ Interprocedural passes may still analyze this function, transform its body,
+ and refine its attributes, but they will not rewrite its signature.
Code generation defaults to the "fast" instruction selector.
This attribute cannot be used together with the `alwaysinline`
attribute; this attribute is also incompatible
diff --git a/llvm/docs/ReleaseNotes.md b/llvm/docs/ReleaseNotes.md
index 40e0303f77e86..785be1f827426 100644
--- a/llvm/docs/ReleaseNotes.md
+++ b/llvm/docs/ReleaseNotes.md
@@ -64,6 +64,11 @@ Makes programs 10x faster by doing Special New Thing.
### Changes to Interprocedural Optimizations
+- Interprocedural passes no longer rewrite the signature of functions marked
+ `optnone`, so their argument list, return type, and calling convention are
+ preserved. Interprocedural analysis and transformation of such functions is
+ otherwise unaffected.
+
- The IR Outliner has been removed, due to lack of a maintainer and the presence
of correctness issues.
diff --git a/llvm/include/llvm/IR/Function.h b/llvm/include/llvm/IR/Function.h
index 0238c9b352f5f..c1830c4b2f8d9 100644
--- a/llvm/include/llvm/IR/Function.h
+++ b/llvm/include/llvm/IR/Function.h
@@ -684,6 +684,13 @@ class LLVM_ABI Function : public GlobalObject, public ilist_node<Function> {
/// Do not optimize this function (-O0).
bool hasOptNone() const { return hasFnAttribute(Attribute::OptimizeNone); }
+ /// Determine whether interprocedural transforms may rewrite this function's
+ /// signature.
+ bool canChangeSignature() const {
+ return !hasFnAttribute(Attribute::Naked) &&
+ !hasFnAttribute(Attribute::NoIPA) && !hasOptNone();
+ }
+
/// Optimize this function for minimum size (-Oz).
bool hasMinSize() const { return hasFnAttribute(Attribute::MinSize); }
diff --git a/llvm/lib/Transforms/IPO/ArgumentPromotion.cpp b/llvm/lib/Transforms/IPO/ArgumentPromotion.cpp
index 9dfcd0926e4c7..51821dd7f23bb 100644
--- a/llvm/lib/Transforms/IPO/ArgumentPromotion.cpp
+++ b/llvm/lib/Transforms/IPO/ArgumentPromotion.cpp
@@ -807,10 +807,10 @@ static bool areTypesABICompatible(ArrayRef<Type *> Types, const Function &F,
/// calls the DoPromotion method.
static Function *promoteArguments(Function *F, FunctionAnalysisManager &FAM,
unsigned MaxElements, bool IsRecursive) {
- // Don't perform argument promotion for naked functions; otherwise we can end
- // up removing parameters that are seemingly 'not used' as they are referred
- // to in the assembly.
- if (F->hasFnAttribute(Attribute::Naked))
+ // Don't rewrite the signature of functions whose ABI must be preserved. For
+ // naked functions we can end up removing parameters that are seemingly 'not
+ // used' as they are referred to in the assembly.
+ if (!F->canChangeSignature())
return nullptr;
// Make sure that it is local to this module.
diff --git a/llvm/lib/Transforms/IPO/DeadArgumentElimination.cpp b/llvm/lib/Transforms/IPO/DeadArgumentElimination.cpp
index dcbf60ab55c72..277c7a11d7eea 100644
--- a/llvm/lib/Transforms/IPO/DeadArgumentElimination.cpp
+++ b/llvm/lib/Transforms/IPO/DeadArgumentElimination.cpp
@@ -104,10 +104,10 @@ bool DeadArgumentEliminationPass::deleteDeadVarargs(Function &F) {
if (F.hasAddressTaken())
return false;
- // Don't touch naked functions. The assembly might be using an argument, or
- // otherwise rely on the frame layout in a way that this analysis will not
- // see.
- if (F.hasFnAttribute(Attribute::Naked)) {
+ // Don't touch functions whose ABI must be preserved. For naked functions the
+ // assembly might be using an argument, or otherwise rely on the frame layout
+ // in a way that this analysis will not see.
+ if (!F.canChangeSignature()) {
return false;
}
@@ -252,10 +252,10 @@ bool DeadArgumentEliminationPass::removeDeadArgumentsFromCallers(Function &F) {
!F.getFunctionType()->isVarArg())
return false;
- // Don't touch naked functions. The assembly might be using an argument, or
- // otherwise rely on the frame layout in a way that this analysis will not
- // see.
- if (F.hasFnAttribute(Attribute::Naked))
+ // Don't touch functions whose ABI must be preserved. For naked functions the
+ // assembly might be using an argument, or otherwise rely on the frame layout
+ // in a way that this analysis will not see.
+ if (!F.canChangeSignature())
return false;
if (F.use_empty())
@@ -474,10 +474,10 @@ void DeadArgumentEliminationPass::surveyFunction(const Function &F) {
return;
}
- // Don't touch naked functions. The assembly might be using an argument, or
- // otherwise rely on the frame layout in a way that this analysis will not
- // see.
- if (F.hasFnAttribute(Attribute::Naked)) {
+ // Don't touch functions whose ABI must be preserved. For naked functions the
+ // assembly might be using an argument, or otherwise rely on the frame layout
+ // in a way that this analysis will not see.
+ if (!F.canChangeSignature()) {
markFrozen(F);
return;
}
diff --git a/llvm/lib/Transforms/IPO/GlobalOpt.cpp b/llvm/lib/Transforms/IPO/GlobalOpt.cpp
index 0618073387d97..f78b7169a6a26 100644
--- a/llvm/lib/Transforms/IPO/GlobalOpt.cpp
+++ b/llvm/lib/Transforms/IPO/GlobalOpt.cpp
@@ -1719,6 +1719,9 @@ static bool hasChangeableCCImpl(Function *F) {
if (CC != CallingConv::C && CC != CallingConv::X86_ThisCall)
return false;
+ if (!F->canChangeSignature())
+ return false;
+
if (F->isVarArg())
return false;
diff --git a/llvm/test/Transforms/ArgumentPromotion/optnone.ll b/llvm/test/Transforms/ArgumentPromotion/optnone.ll
new file mode 100644
index 0000000000000..1ccd1e3ccffaf
--- /dev/null
+++ b/llvm/test/Transforms/ArgumentPromotion/optnone.ll
@@ -0,0 +1,40 @@
+; NOTE: Assertions have been autogenerated by utils/update_test_checks.py UTC_ARGS: --function-signature
+; RUN: opt -passes=argpromotion -S < %s | FileCheck %s
+
+declare void @sink(i32)
+
+define internal void @optnone_promote(ptr %X) optnone noinline {
+; CHECK-LABEL: define {{[^@]+}}@optnone_promote
+; CHECK-SAME: (ptr [[X:%.*]]) #[[ATTR0:[0-9]+]] {
+; CHECK-NEXT: [[V:%.*]] = load i32, ptr [[X]], align 4
+; CHECK-NEXT: call void @sink(i32 [[V]])
+; CHECK-NEXT: ret void
+;
+ %v = load i32, ptr %X, align 4
+ call void @sink(i32 %v)
+ ret void
+}
+
+define internal void @promote(ptr %X) {
+; CHECK-LABEL: define {{[^@]+}}@promote
+; CHECK-SAME: (i32 [[X_0_VAL:%.*]]) {
+; CHECK-NEXT: call void @sink(i32 [[X_0_VAL]])
+; CHECK-NEXT: ret void
+;
+ %v = load i32, ptr %X, align 4
+ call void @sink(i32 %v)
+ ret void
+}
+
+define void @caller(ptr %Y, ptr %Z) {
+; CHECK-LABEL: define {{[^@]+}}@caller
+; CHECK-SAME: (ptr [[Y:%.*]], ptr [[Z:%.*]]) {
+; CHECK-NEXT: call void @optnone_promote(ptr [[Y]])
+; CHECK-NEXT: [[Z_VAL:%.*]] = load i32, ptr [[Z]], align 4
+; CHECK-NEXT: call void @promote(i32 [[Z_VAL]])
+; CHECK-NEXT: ret void
+;
+ call void @optnone_promote(ptr %Y)
+ call void @promote(ptr %Z)
+ ret void
+}
diff --git a/llvm/test/Transforms/DeadArgElim/optnone.ll b/llvm/test/Transforms/DeadArgElim/optnone.ll
new file mode 100644
index 0000000000000..1a2761b7f0650
--- /dev/null
+++ b/llvm/test/Transforms/DeadArgElim/optnone.ll
@@ -0,0 +1,31 @@
+; NOTE: Assertions have been autogenerated by utils/update_test_checks.py UTC_ARGS: --function-signature
+; RUN: opt -passes=deadargelim -S < %s | FileCheck %s
+
+define internal i32 @optnone_dead_arg(i32 %live, i32 %dead) optnone noinline {
+; CHECK-LABEL: define {{[^@]+}}@optnone_dead_arg
+; CHECK-SAME: (i32 [[LIVE:%.*]], i32 [[DEAD:%.*]]) #[[ATTR0:[0-9]+]] {
+; CHECK-NEXT: ret i32 [[LIVE]]
+;
+ ret i32 %live
+}
+
+define internal i32 @dead_arg(i32 %live, i32 %dead) {
+; CHECK-LABEL: define {{[^@]+}}@dead_arg
+; CHECK-SAME: (i32 [[LIVE:%.*]]) {
+; CHECK-NEXT: ret i32 [[LIVE]]
+;
+ ret i32 %live
+}
+
+define i32 @caller() {
+; CHECK-LABEL: define {{[^@]+}}@caller() {
+; CHECK-NEXT: [[A:%.*]] = call i32 @optnone_dead_arg(i32 1, i32 2)
+; CHECK-NEXT: [[B:%.*]] = call i32 @dead_arg(i32 3)
+; CHECK-NEXT: [[C:%.*]] = add i32 [[A]], [[B]]
+; CHECK-NEXT: ret i32 [[C]]
+;
+ %a = call i32 @optnone_dead_arg(i32 1, i32 2)
+ %b = call i32 @dead_arg(i32 3, i32 4)
+ %c = add i32 %a, %b
+ ret i32 %c
+}
diff --git a/llvm/test/Transforms/GlobalOpt/optnone.ll b/llvm/test/Transforms/GlobalOpt/optnone.ll
new file mode 100644
index 0000000000000..550a3313bf89a
--- /dev/null
+++ b/llvm/test/Transforms/GlobalOpt/optnone.ll
@@ -0,0 +1,18 @@
+; RUN: opt -passes=globalopt -S < %s | FileCheck %s
+
+; CHECK: define internal fastcc i32 @foo(
+define internal i32 @foo(i32 %x) noinline {
+ ret i32 %x
+}
+
+; CHECK: define internal i32 @foo_optnone(
+define internal i32 @foo_optnone(i32 %x) optnone noinline {
+ ret i32 %x
+}
+
+define i32 @bar() {
+ %r = call i32 @foo(i32 5)
+ %s = call i32 @foo_optnone(i32 5)
+ %res = add i32 %r, %s
+ ret i32 %res
+}
More information about the llvm-commits
mailing list