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
