https://github.com/jhuber6 updated 
https://github.com/llvm/llvm-project/pull/211804

>From e50a09ba77934bd3d932160fdcc36480c23772fa Mon Sep 17 00:00:00 2001
From: Joseph Huber <[email protected]>
Date: Fri, 24 Jul 2026 09:10:47 -0500
Subject: [PATCH] [IR][IPO] Don't rewrite the signature of optnone functions

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`.
---
 .../amdgpu-enqueue-kernel-linking.cl          |  4 +--
 llvm/docs/LangRef.md                          |  1 +
 llvm/docs/ReleaseNotes.md                     |  4 +++
 llvm/include/llvm/IR/Function.h               |  7 ++++
 llvm/lib/Transforms/IPO/ArgumentPromotion.cpp |  8 ++---
 llvm/lib/Transforms/IPO/Attributor.cpp        |  6 ++++
 .../IPO/DeadArgumentElimination.cpp           | 24 ++++++-------
 llvm/lib/Transforms/IPO/GlobalOpt.cpp         |  3 ++
 .../Transforms/ArgumentPromotion/optnone.ll   | 36 +++++++++++++++++++
 llvm/test/Transforms/DeadArgElim/optnone.ll   | 30 ++++++++++++++++
 llvm/test/Transforms/GlobalOpt/optnone.ll     | 18 ++++++++++
 11 files changed, 123 insertions(+), 18 deletions(-)
 create mode 100644 llvm/test/Transforms/ArgumentPromotion/optnone.ll
 create mode 100644 llvm/test/Transforms/DeadArgElim/optnone.ll
 create mode 100644 llvm/test/Transforms/GlobalOpt/optnone.ll

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 %difference)
 
-// 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 d57f619042f22..431bebd067e3c 100644
--- a/llvm/docs/LangRef.md
+++ b/llvm/docs/LangRef.md
@@ -2551,6 +2551,7 @@ 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 will analyze this function but will not alter 
it.
     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 f8fce847b62f3..abe3ab42e3bb3 100644
--- a/llvm/docs/ReleaseNotes.md
+++ b/llvm/docs/ReleaseNotes.md
@@ -64,6 +64,10 @@ Makes programs 10x faster by doing Special New Thing.
 
 ### Changes to Interprocedural Optimizations
 
+- Interprocedural passes no longer rewrite the signature, calling convention, 
or
+  parameter and return attributes of functions marked `optnone`. 
Interprocedural
+  analysis of such functions is 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/Attributor.cpp 
b/llvm/lib/Transforms/IPO/Attributor.cpp
index 07d2435108ac7..a8addc8c65e2c 100644
--- a/llvm/lib/Transforms/IPO/Attributor.cpp
+++ b/llvm/lib/Transforms/IPO/Attributor.cpp
@@ -2921,6 +2921,12 @@ bool Attributor::isValidFunctionSignatureRewrite(
     return false;
 
   Function *Fn = Arg.getParent();
+  if (!Fn->canChangeSignature()) {
+    LLVM_DEBUG(dbgs() << "[Attributor] Cannot rewrite the signature of a "
+                         "function whose ABI must be preserved\n");
+    return false;
+  }
+
   auto CallSiteCanBeChanged = [Fn](AbstractCallSite ACS) {
     // Forbid the call site to cast the function return type. If we need to
     // rewrite these functions we need to re-create a cast for the new call 
site
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..36e97f402991c
--- /dev/null
+++ b/llvm/test/Transforms/ArgumentPromotion/optnone.ll
@@ -0,0 +1,36 @@
+; RUN: opt -passes=argpromotion -S < %s | FileCheck %s
+
+declare void @sink(i32)
+
+; CHECK-LABEL: define internal void @optnone_promote(ptr %X)
+; CHECK-NOT: DW_CC_nocall
+define internal void @optnone_promote(ptr %X) optnone noinline !dbg !4 {
+  %v = load i32, ptr %X, align 4
+  call void @sink(i32 %v)
+  ret void
+}
+
+; CHECK-LABEL: define internal void @promote(i32 %X.0.val)
+define internal void @promote(ptr %X) {
+  %v = load i32, ptr %X, align 4
+  call void @sink(i32 %v)
+  ret void
+}
+
+define void @caller(ptr %Y, ptr %Z) {
+  call void @optnone_promote(ptr %Y)
+  call void @promote(ptr %Z)
+  ret void
+}
+
+!llvm.dbg.cu = !{!0}
+!llvm.module.flags = !{!3}
+
+!0 = distinct !DICompileUnit(language: DW_LANG_C99, file: !1, producer: 
"clang", isOptimized: false, runtimeVersion: 0, emissionKind: FullDebug)
+!1 = !DIFile(filename: "optnone.c", directory: "/")
+!2 = !DISubroutineType(types: !5)
+!3 = !{i32 2, !"Debug Info Version", i32 3}
+!4 = distinct !DISubprogram(name: "optnone_promote", scope: !1, file: !1, 
line: 1, type: !2, scopeLine: 1, spFlags: DISPFlagDefinition, unit: !0)
+!5 = !{null, !6}
+!6 = !DIDerivedType(tag: DW_TAG_pointer_type, baseType: !7, size: 64)
+!7 = !DIBasicType(name: "int", size: 32, encoding: DW_ATE_signed)
diff --git a/llvm/test/Transforms/DeadArgElim/optnone.ll 
b/llvm/test/Transforms/DeadArgElim/optnone.ll
new file mode 100644
index 0000000000000..ae09a56c24d98
--- /dev/null
+++ b/llvm/test/Transforms/DeadArgElim/optnone.ll
@@ -0,0 +1,30 @@
+; RUN: opt -passes=deadargelim -S < %s | FileCheck %s
+
+; CHECK-LABEL: define internal i32 @optnone_dead_arg(i32 %live, i32 %dead)
+; CHECK-NOT: DW_CC_nocall
+define internal i32 @optnone_dead_arg(i32 %live, i32 %dead) optnone noinline 
!dbg !4 {
+  ret i32 %live
+}
+
+; CHECK-LABEL: define internal i32 @dead_arg(i32 %live)
+define internal i32 @dead_arg(i32 %live, i32 %dead) {
+  ret i32 %live
+}
+
+define i32 @caller() {
+  %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
+}
+
+!llvm.dbg.cu = !{!0}
+!llvm.module.flags = !{!3}
+
+!0 = distinct !DICompileUnit(language: DW_LANG_C99, file: !1, producer: 
"clang", isOptimized: false, runtimeVersion: 0, emissionKind: FullDebug)
+!1 = !DIFile(filename: "optnone.c", directory: "/")
+!2 = !DISubroutineType(types: !7)
+!3 = !{i32 2, !"Debug Info Version", i32 3}
+!4 = distinct !DISubprogram(name: "optnone_dead_arg", scope: !1, file: !1, 
line: 1, type: !2, scopeLine: 1, spFlags: DISPFlagDefinition, unit: !0)
+!7 = !{!8, !8, !8}
+!8 = !DIBasicType(name: "int", size: 32, encoding: DW_ATE_signed)
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
+}

_______________________________________________
cfe-commits mailing list
[email protected]
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits

Reply via email to