https://github.com/jhuber6 updated https://github.com/llvm/llvm-project/pull/211804
>From aab902f0bbed0eee4bbba4417363751c3725a1ef Mon Sep 17 00:00:00 2001 From: Joseph Huber <[email protected]> Date: Fri, 24 Jul 2026 09:10:47 -0500 Subject: [PATCH 1/2] [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 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/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 +} >From b0f9fa4909758175de2f940ec9b1bd2ad9d69e40 Mon Sep 17 00:00:00 2001 From: Joseph Huber <[email protected]> Date: Mon, 27 Jul 2026 09:57:56 -0500 Subject: [PATCH 2/2] fix devirt --- .../ELF/lto/devirt_validate_vtable_typeinfos_mixed_lto.ll | 6 ++---- llvm/test/ThinLTO/X86/Inputs/devirt2.ll | 2 +- .../ThinLTO/X86/Inputs/devirt_external_comdat_same_guid.ll | 2 +- llvm/test/ThinLTO/X86/Inputs/devirt_promote.ll | 2 +- llvm/test/ThinLTO/X86/devirt2.ll | 4 ++-- llvm/test/ThinLTO/X86/devirt_external_comdat_same_guid.ll | 5 ++--- llvm/test/ThinLTO/X86/devirt_promote.ll | 5 ++--- llvm/test/ThinLTO/X86/devirt_promote_legacy.ll | 5 ++--- 8 files changed, 13 insertions(+), 18 deletions(-) diff --git a/lld/test/ELF/lto/devirt_validate_vtable_typeinfos_mixed_lto.ll b/lld/test/ELF/lto/devirt_validate_vtable_typeinfos_mixed_lto.ll index 9dacbc32175a7..f6baef92790c7 100644 --- a/lld/test/ELF/lto/devirt_validate_vtable_typeinfos_mixed_lto.ll +++ b/lld/test/ELF/lto/devirt_validate_vtable_typeinfos_mixed_lto.ll @@ -81,8 +81,6 @@ define i32 @_start(ptr %obj, ptr %obj2, i32 %a) { %fptr33 = load ptr, ptr %vtable2, align 8 - ;; Check that the call was devirtualized. - ; CHECK-IR: %call3 = tail call i32 @_ZN1D1mEi %call3 = tail call i32 %fptr33(ptr nonnull %obj2, i32 %call2) ret i32 %call3 @@ -107,7 +105,7 @@ define internal i32 @_ZN1D1mEi(ptr %this, i32 %a) #0 { } ;; Make sure we don't inline or otherwise optimize out the direct calls. -attributes #0 = { noinline optnone } +attributes #0 = { noinline } !0 = !{i64 16, !"_ZTS1A"} !1 = !{i64 16, !"_ZTSM1AFviE.virtual"} @@ -170,7 +168,7 @@ define linkonce_odr i32 @_ZN1A1nEi(ptr %this, i32 %a) #0 { ret i32 0; } -attributes #0 = { noinline optnone } +attributes #0 = { noinline } !llvm.module.flags = !{!6, !7} !0 = !{i64 16, !"_ZTS1A"} diff --git a/llvm/test/ThinLTO/X86/Inputs/devirt2.ll b/llvm/test/ThinLTO/X86/Inputs/devirt2.ll index b248d774b10fe..de3dba79dbdea 100644 --- a/llvm/test/ThinLTO/X86/Inputs/devirt2.ll +++ b/llvm/test/ThinLTO/X86/Inputs/devirt2.ll @@ -44,7 +44,7 @@ entry: ret i32 %call4 } -attributes #0 = { noinline optnone } +attributes #0 = { noinline } declare i1 @llvm.type.test(ptr, metadata) declare void @llvm.assume(i1) diff --git a/llvm/test/ThinLTO/X86/Inputs/devirt_external_comdat_same_guid.ll b/llvm/test/ThinLTO/X86/Inputs/devirt_external_comdat_same_guid.ll index f56c4bf236761..d0dc3775fc9cd 100644 --- a/llvm/test/ThinLTO/X86/Inputs/devirt_external_comdat_same_guid.ll +++ b/llvm/test/ThinLTO/X86/Inputs/devirt_external_comdat_same_guid.ll @@ -31,7 +31,7 @@ entry: ret i32 %call4 } -attributes #0 = { noinline optnone } +attributes #0 = { noinline } declare i1 @llvm.type.test(ptr, metadata) declare void @llvm.assume(i1) diff --git a/llvm/test/ThinLTO/X86/Inputs/devirt_promote.ll b/llvm/test/ThinLTO/X86/Inputs/devirt_promote.ll index 7f87fc54e7537..cada03cf1cf0f 100644 --- a/llvm/test/ThinLTO/X86/Inputs/devirt_promote.ll +++ b/llvm/test/ThinLTO/X86/Inputs/devirt_promote.ll @@ -27,7 +27,7 @@ entry: ret i32 %call4 } -attributes #0 = { noinline optnone } +attributes #0 = { noinline } declare i1 @llvm.type.test(ptr, metadata) declare void @llvm.assume(i1) diff --git a/llvm/test/ThinLTO/X86/devirt2.ll b/llvm/test/ThinLTO/X86/devirt2.ll index 9e91efeba0da4..5a79725db9d61 100644 --- a/llvm/test/ThinLTO/X86/devirt2.ll +++ b/llvm/test/ThinLTO/X86/devirt2.ll @@ -207,12 +207,12 @@ entry: ; Check that the call was devirtualized. Ignore extra character before ; symbol name which would happen if it was promoted during module ; splitting for hybrid WPD. -; CHECK-IR2-NEXT: %call4 = tail call i32 @{{.*}}_ZN1E1mEi +; CHECK-IR2-NEXT: ret i32 0 declare i1 @llvm.type.test(ptr, metadata) declare void @llvm.assume(i1) declare i32 @test2(ptr %obj, i32 %a) -attributes #0 = { noinline optnone } +attributes #0 = { noinline } !3 = !{i64 16, !"_ZTS1D"} diff --git a/llvm/test/ThinLTO/X86/devirt_external_comdat_same_guid.ll b/llvm/test/ThinLTO/X86/devirt_external_comdat_same_guid.ll index d0b8d14777f52..ddf794bb4f00a 100644 --- a/llvm/test/ThinLTO/X86/devirt_external_comdat_same_guid.ll +++ b/llvm/test/ThinLTO/X86/devirt_external_comdat_same_guid.ll @@ -64,8 +64,7 @@ entry: %fptrptr = getelementptr ptr, ptr %vtable, i32 1 %fptr1 = load ptr, ptr %fptrptr, align 8 - ; Check that the call was devirtualized. - ; CHECK-IR1: tail call i32 {{.*}}@_ZN1B1nEi + ; CHECK-IR1: ret i32 0 %call = tail call i32 %fptr1(ptr nonnull %obj, i32 %a) ret i32 %call @@ -78,7 +77,7 @@ entry: declare i1 @llvm.type.test(ptr, metadata) declare void @llvm.assume(i1) -attributes #0 = { noinline optnone } +attributes #0 = { noinline } !0 = !{i64 16, !"_ZTS1A"} !1 = !{i64 16, !"_ZTS1B"} diff --git a/llvm/test/ThinLTO/X86/devirt_promote.ll b/llvm/test/ThinLTO/X86/devirt_promote.ll index d00701be3a175..080ae1c02dd9e 100644 --- a/llvm/test/ThinLTO/X86/devirt_promote.ll +++ b/llvm/test/ThinLTO/X86/devirt_promote.ll @@ -64,10 +64,9 @@ entry: ; CHECK-IR1-LABEL: } ; CHECK-IR2: define noundef i32 @test2 -; Check that the call was devirtualized. -; CHECK-IR2: %call4 = tail call i32 @_ZN1A1nEi +; CHECK-IR2: ret i32 0 declare i1 @llvm.type.test(ptr, metadata) declare void @llvm.assume(i1) -attributes #0 = { noinline optnone } +attributes #0 = { noinline } diff --git a/llvm/test/ThinLTO/X86/devirt_promote_legacy.ll b/llvm/test/ThinLTO/X86/devirt_promote_legacy.ll index 542c1e85b6dde..319c6049ad383 100644 --- a/llvm/test/ThinLTO/X86/devirt_promote_legacy.ll +++ b/llvm/test/ThinLTO/X86/devirt_promote_legacy.ll @@ -46,10 +46,9 @@ entry: ; CHECK-IR1-LABEL: } ; CHECK-IR2: define noundef i32 @test2 -; Check that the call was devirtualized. -; CHECK-IR2: = tail call i32 @_ZN1A1nEi +; CHECK-IR2: ret i32 0 declare i1 @llvm.type.test(ptr, metadata) declare void @llvm.assume(i1) -attributes #0 = { noinline optnone } +attributes #0 = { noinline } _______________________________________________ cfe-commits mailing list [email protected] https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
