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

>From fa3a7569b18bea14bb5ee449d64b6825c9e61f79 Mon Sep 17 00:00:00 2001
From: Joseph Huber <[email protected]>
Date: Fri, 24 Jul 2026 09:10:47 -0500
Subject: [PATCH] [LLVM] Respect `optnone` in `promote-args` and `deadargelim`

Summary:
These passes can rewrite  the signatures, and already have checks the
prevent us modifying things like external or naked functions for
correctness. The`optnone` attribute deliberately allows some
optimizations to take place, but I think that rewriting function
signatures and changing calling conventions is unexpected behavior.

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.
---
 .../amdgpu-enqueue-kernel-linking.cl          |  4 +--
 llvm/include/llvm/IR/GlobalValue.h            | 10 ++++--
 llvm/lib/IR/Globals.cpp                       |  7 ++++
 .../Transforms/ArgumentPromotion/optnone.ll   | 36 +++++++++++++++++++
 llvm/test/Transforms/Attributor/nonnull.ll    |  2 +-
 llvm/test/Transforms/DeadArgElim/optnone.ll   | 30 ++++++++++++++++
 llvm/test/Transforms/FunctionAttrs/nonnull.ll | 13 +++----
 llvm/test/Transforms/GlobalOpt/optnone.ll     | 21 +++++++++++
 8 files changed, 108 insertions(+), 15 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 a61c5530c8a73..9b690e1e1b43b 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/include/llvm/IR/GlobalValue.h 
b/llvm/include/llvm/IR/GlobalValue.h
index d7b0f1f25f929..f6a2f6a5d6663 100644
--- a/llvm/include/llvm/IR/GlobalValue.h
+++ b/llvm/include/llvm/IR/GlobalValue.h
@@ -153,9 +153,9 @@ class GlobalValue : public Constant {
     case PrivateLinkage:
       // Optimizations may assume builtin semantics for functions defined as
       // 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();
+      // on nobuiltin or optnone semantics, treat such function definitions as
+      // maybe derefined.
+      return isInterposable() || isNobuiltinFnDef() || isOptNoneFnDef();
     }
 
     llvm_unreachable("Fully covered switch above!");
@@ -169,6 +169,10 @@ class GlobalValue : public Constant {
   /// attribute.
   LLVM_ABI bool isNoipaFnDef() const;
 
+  /// Returns true if the global is a function definition with the optnone
+  /// attribute.
+  LLVM_ABI bool isOptNoneFnDef() const;
+
 protected:
   /// The intrinsic ID for this subclass (which must be a Function).
   ///
diff --git a/llvm/lib/IR/Globals.cpp b/llvm/lib/IR/Globals.cpp
index 3428f662e9120..53351f38a4afa 100644
--- a/llvm/lib/IR/Globals.cpp
+++ b/llvm/lib/IR/Globals.cpp
@@ -405,6 +405,13 @@ bool GlobalValue::isNoipaFnDef() const {
   return F->hasFnAttribute(Attribute::NoIPA);
 }
 
+bool GlobalValue::isOptNoneFnDef() const {
+  const Function *F = dyn_cast<Function>(this);
+  if (!F || F->isDeclaration())
+    return false;
+  return F->hasFnAttribute(Attribute::OptimizeNone);
+}
+
 bool GlobalValue::isDeclaration() const {
   // Globals are definitions if they have an initializer.
   if (const GlobalVariable *GV = dyn_cast<GlobalVariable>(this))
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/Attributor/nonnull.ll 
b/llvm/test/Transforms/Attributor/nonnull.ll
index 0aa0e72d403ab..9ff81966ad1d0 100644
--- a/llvm/test/Transforms/Attributor/nonnull.ll
+++ b/llvm/test/Transforms/Attributor/nonnull.ll
@@ -1057,7 +1057,7 @@ define internal void @optnone(ptr dereferenceable(4) %a) 
optnone noinline {
 ;
 ; CHECK: Function Attrs: noinline optnone
 ; CHECK-LABEL: define {{[^@]+}}@optnone
-; CHECK-SAME: (ptr noundef nonnull dereferenceable(4) [[A:%.*]]) 
#[[ATTR12:[0-9]+]] {
+; CHECK-SAME: (ptr dereferenceable(4) [[A:%.*]]) #[[ATTR12:[0-9]+]] {
 ; CHECK-NEXT:    call void @use_i32_ptr(ptr nofree noundef nonnull 
captures(none) [[A]])
 ; CHECK-NEXT:    ret void
 ;
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/FunctionAttrs/nonnull.ll 
b/llvm/test/Transforms/FunctionAttrs/nonnull.ll
index 5b160438fb4ea..69822490dc341 100644
--- a/llvm/test/Transforms/FunctionAttrs/nonnull.ll
+++ b/llvm/test/Transforms/FunctionAttrs/nonnull.ll
@@ -1090,15 +1090,10 @@ define internal void @naked(ptr dereferenceable(4) %a) 
naked {
 }
 ; Avoid nonnull as we do not touch optnone
 define internal void @optnone(ptr dereferenceable(4) %a) optnone noinline {
-; FNATTRS-LABEL: define internal void @optnone(
-; FNATTRS-SAME: ptr dereferenceable(4) [[A:%.*]]) #[[ATTR12:[0-9]+]] {
-; FNATTRS-NEXT:    call void @use_i32_ptr(ptr [[A]])
-; FNATTRS-NEXT:    ret void
-;
-; ATTRIBUTOR-LABEL: define internal void @optnone(
-; ATTRIBUTOR-SAME: ptr nonnull dereferenceable(4) [[A:%.*]]) 
#[[ATTR12:[0-9]+]] {
-; ATTRIBUTOR-NEXT:    call void @use_i32_ptr(ptr [[A]])
-; ATTRIBUTOR-NEXT:    ret void
+; COMMON-LABEL: define internal void @optnone(
+; COMMON-SAME: ptr dereferenceable(4) [[A:%.*]]) #[[ATTR12:[0-9]+]] {
+; COMMON-NEXT:    call void @use_i32_ptr(ptr [[A]])
+; COMMON-NEXT:    ret void
 ;
   call void @use_i32_ptr(ptr %a)
   ret void
diff --git a/llvm/test/Transforms/GlobalOpt/optnone.ll 
b/llvm/test/Transforms/GlobalOpt/optnone.ll
new file mode 100644
index 0000000000000..5b6ec84d1c806
--- /dev/null
+++ b/llvm/test/Transforms/GlobalOpt/optnone.ll
@@ -0,0 +1,21 @@
+; RUN: opt -passes=globalopt -S < %s | FileCheck %s
+
+; GlobalOpt should not change the calling convention of an optnone function,
+; since that rewrites its ABI in a way optnone is meant to prevent.
+
+; 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