https://github.com/ketjandr updated https://github.com/llvm/llvm-project/pull/215173
>From a542d2b6f99d3aed57c4b26adfe282ab936093fb Mon Sep 17 00:00:00 2001 From: Kenzo <[email protected]> Date: Sun, 9 Aug 2026 22:19:40 -0400 Subject: [PATCH 1/3] [Clang] Diagnose conflict between always_inline/noinline attributes --- clang/include/clang/Basic/Attr.td | 2 + clang/lib/CIR/CodeGen/CIRGenStmt.cpp | 9 +++- clang/lib/CodeGen/CGStmt.cpp | 14 ++++-- clang/lib/Sema/SemaDecl.cpp | 7 ++- .../CodeGen/attr-noinline-always-inline.cpp | 49 +++++++++++++++++++ .../attr-noinline-always-inline-conflict.cpp | 37 ++++++++++++++ 6 files changed, 110 insertions(+), 8 deletions(-) create mode 100644 clang/test/CodeGen/attr-noinline-always-inline.cpp create mode 100644 clang/test/SemaCXX/attr-noinline-always-inline-conflict.cpp diff --git a/clang/include/clang/Basic/Attr.td b/clang/include/clang/Basic/Attr.td index d874ccb1b8653..0bf81c312640f 100644 --- a/clang/include/clang/Basic/Attr.td +++ b/clang/include/clang/Basic/Attr.td @@ -2423,6 +2423,8 @@ def NoInline : DeclOrStmtAttr { let SimpleHandler = 1; } +def : MutualExclusions<[AlwaysInline, NoInline]>; + def NoOutline : DeclOrStmtAttr { let Spellings = [Clang<"no_outline">]; let Subjects = SubjectList<[Function, ObjCMethod, Block], ErrorDiag>; diff --git a/clang/lib/CIR/CodeGen/CIRGenStmt.cpp b/clang/lib/CIR/CodeGen/CIRGenStmt.cpp index 38b65f16197eb..48ef88cef9621 100644 --- a/clang/lib/CIR/CodeGen/CIRGenStmt.cpp +++ b/clang/lib/CIR/CodeGen/CIRGenStmt.cpp @@ -90,8 +90,8 @@ mlir::LogicalResult CIRGenFunction::emitCompoundStmtWithoutScope( mlir::LogicalResult CIRGenFunction::emitAttributedStmt(const AttributedStmt &s) { - bool noinline = false; - bool alwaysinline = false; + bool noinline = inNoInlineAttributedStmt; + bool alwaysinline = inAlwaysInlineAttributedStmt; const CallExpr *musttail = nullptr; for (const Attr *attr : s.getAttrs()) { @@ -108,9 +108,11 @@ CIRGenFunction::emitAttributedStmt(const AttributedStmt &s) { break; case attr::NoInline: noinline = true; + alwaysinline = false; break; case attr::AlwaysInline: alwaysinline = true; + noinline = false; break; case attr::MustTail: { const Stmt *sub = s.getSubStmt(); @@ -131,6 +133,9 @@ CIRGenFunction::emitAttributedStmt(const AttributedStmt &s) { } } + assert(!(alwaysinline && noinline) && + "alwaysinline and noinline are mutually exclusive"); + SaveAndRestore save_noinline(inNoInlineAttributedStmt, noinline); SaveAndRestore save_alwaysinline(inAlwaysInlineAttributedStmt, alwaysinline); diff --git a/clang/lib/CodeGen/CGStmt.cpp b/clang/lib/CodeGen/CGStmt.cpp index 73f6c3c4aff1d..828edd109a9ac 100644 --- a/clang/lib/CodeGen/CGStmt.cpp +++ b/clang/lib/CodeGen/CGStmt.cpp @@ -779,10 +779,10 @@ void CodeGenFunction::EmitLabelStmt(const LabelStmt &S) { } void CodeGenFunction::EmitAttributedStmt(const AttributedStmt &S) { - bool nomerge = false; - bool noinline = false; - bool alwaysinline = false; - bool noconvergent = false; + bool nomerge = InNoMergeAttributedStmt; + bool noinline = InNoInlineAttributedStmt; + bool alwaysinline = InAlwaysInlineAttributedStmt; + bool noconvergent = InNoConvergentAttributedStmt; StringRef amdgpuAVMode; HLSLControlFlowHintAttr::Spelling flattenOrBranch = HLSLControlFlowHintAttr::SpellingNotCalculated; @@ -798,9 +798,11 @@ void CodeGenFunction::EmitAttributedStmt(const AttributedStmt &S) { break; case attr::NoInline: noinline = true; + alwaysinline = false; break; case attr::AlwaysInline: alwaysinline = true; + noinline = false; break; case attr::NoConvergent: noconvergent = true; @@ -829,6 +831,10 @@ void CodeGenFunction::EmitAttributedStmt(const AttributedStmt &S) { } break; } } + + assert(!(alwaysinline && noinline) && + "alwaysinline and noinline are mutually exclusive"); + SaveAndRestore save_nomerge(InNoMergeAttributedStmt, nomerge); SaveAndRestore save_noinline(InNoInlineAttributedStmt, noinline); SaveAndRestore save_alwaysinline(InAlwaysInlineAttributedStmt, alwaysinline); diff --git a/clang/lib/Sema/SemaDecl.cpp b/clang/lib/Sema/SemaDecl.cpp index c5dcdee7dc5dd..ccb080af6cb26 100644 --- a/clang/lib/Sema/SemaDecl.cpp +++ b/clang/lib/Sema/SemaDecl.cpp @@ -3389,11 +3389,14 @@ void Sema::mergeDeclAttributes(NamedDecl *New, Decl *Old, if (isa<UsedAttr>(I) || isa<RetainAttr>(I)) continue; - if (isa<InferredNoReturnAttr>(I)) { + // Don't propagate inferred noreturn or conflicting inline attributes to + // explicit specializations. + if (isa<InferredNoReturnAttr>(I) || isa<AlwaysInlineAttr>(I) || + isa<NoInlineAttr>(I)) { if (auto *FD = dyn_cast<FunctionDecl>(New); FD && FD->getTemplateSpecializationKind() == TSK_ExplicitSpecialization) - continue; // Don't propagate inferred noreturn attributes to explicit + continue; } if (mergeDeclAttribute(*this, New, I, LocalAMK)) diff --git a/clang/test/CodeGen/attr-noinline-always-inline.cpp b/clang/test/CodeGen/attr-noinline-always-inline.cpp new file mode 100644 index 0000000000000..208bf13a1d1cb --- /dev/null +++ b/clang/test/CodeGen/attr-noinline-always-inline.cpp @@ -0,0 +1,49 @@ +// RUN: %clang_cc1 -emit-llvm -o - %s | FileCheck %s + +template <typename T> +[[clang::noinline]] void foo(T) {} + +// Explicit specialization should not inherit noinline +template <> +[[clang::always_inline]] void foo<int>(int) {} + +void caller() { + foo<float>(4.2f); // expect noinline on function + foo<int>(42); // expect alwaysinline on function +} +// CHECK: define {{.*}}void @_Z3fooIiEvT_({{.*}}) #[[ALWAYSINLINE:[0-9]+]] +// CHECK: define {{.*}}void @_Z3fooIfEvT_({{.*}}) #[[NOINLINE:[0-9]+]] + +// Inner function should not clobber non-conflicting attributes +void inner_fn(); + +void outer_fn() { + [[clang::noinline]] + { + [[clang::nomerge]] // unrelated to inling + inner_fn(); + } +} +// CHECK: call void @_Z8inner_fnv() #[[NOINLINE_NOMERGE:[0-9]+]] + +// Inner function should clobber a conflicting attribute +void inner_fn2(); + +void outer_fn2() { + [[clang::noinline]] + { + [[clang::always_inline]] + inner_fn2(); + } +} +// CHECK: call void @_Z9inner_fn2v() #[[ALWAYSINLINE_ONLY:[0-9]+]] + +// CHECK: attributes #[[ALWAYSINLINE]] = { +// CHECK-SAME: alwaysinline +// CHECK-NOT: noinline +// CHECK: attributes #[[NOINLINE]] = { +// CHECK-SAME: noinline +// CHECK-NOT: alwaysinline + +// CHECK: attributes #[[NOINLINE_NOMERGE]] = { noinline nomerge } +// CHECK: attributes #[[ALWAYSINLINE_ONLY]] = { alwaysinline } diff --git a/clang/test/SemaCXX/attr-noinline-always-inline-conflict.cpp b/clang/test/SemaCXX/attr-noinline-always-inline-conflict.cpp new file mode 100644 index 0000000000000..cd50bf41e6c1f --- /dev/null +++ b/clang/test/SemaCXX/attr-noinline-always-inline-conflict.cpp @@ -0,0 +1,37 @@ +// RUN: %clang_cc1 %s -std=c++17 -fsyntax-only -verify + +void foo() {} + +// Statement attributes are mutually exclusive +void caller() { + [[clang::noinline, clang::always_inline]] foo(); // expected-error {{'clang::always_inline' and 'clang::noinline' attributes are not compatible}} \ + // expected-note {{conflicting attribute is here}} + + [[clang::always_inline, clang::noinline]] foo(); // expected-error {{'clang::noinline' and 'clang::always_inline' attributes are not compatible}} \ + // expected-note {{conflicting attribute is here}} +} + +// Attributes on redeclared functions are mutually exclusive +[[clang::noinline]] void redecl_fn(); // expected-note {{conflicting attribute is here}} +[[clang::always_inline]] void redecl_fn() {} // expected-error {{'clang::always_inline' and 'clang::noinline' attributes are not compatible}} + +[[clang::always_inline]] void redecl_fn2(); // expected-note {{conflicting attribute is here}} +[[clang::noinline]] void redecl_fn2() {} // expected-error {{'clang::noinline' and 'clang::always_inline' attributes are not compatible}} + +// Attributes on the same declaration are mutually exclusive +[[clang::noinline, clang::always_inline]] void decl_fn(); // expected-error {{'clang::always_inline' and 'clang::noinline' attributes are not compatible}} \ + // expected-note {{conflicting attribute is here}} + +// Explicit specialization should not inherit inline attributes +template <typename T> +[[clang::noinline]] void tmpl_fn(T); + +template <> +[[clang::always_inline]] void tmpl_fn(int); // no error expected + +// Check different spellings +[[gnu::noinline]] void spelling_fn(); // expected-note {{conflicting attribute is here}} +[[gnu::always_inline]] void spelling_fn() {} // expected-error {{'gnu::always_inline' and 'gnu::noinline' attributes are not compatible}} + +__attribute__((noinline)) void spelling_fn2(); // expected-note {{conflicting attribute is here}} +__attribute__((always_inline)) void spelling_fn2() {} // expected-error {{'always_inline' and 'noinline' attributes are not compatible}} >From 8df5d6cb8d607e44feb90f89ab4cd2f13046b9c9 Mon Sep 17 00:00:00 2001 From: Kenzo <[email protected]> Date: Mon, 10 Aug 2026 20:07:15 -0400 Subject: [PATCH 2/3] [Clang] Add tests for CIRGen inlining, add more CGStmt inling tests --- .../CodeGen/callsite-inline-attributes.cpp | 8 ++++ .../CodeGen/attr-noinline-always-inline.cpp | 37 ++++++++++++------- clang/test/Sema/attr-noinline.cpp | 34 +++++++++++++++++ .../attr-noinline-always-inline-conflict.cpp | 37 ------------------- 4 files changed, 66 insertions(+), 50 deletions(-) delete mode 100644 clang/test/SemaCXX/attr-noinline-always-inline-conflict.cpp diff --git a/clang/test/CIR/CodeGen/callsite-inline-attributes.cpp b/clang/test/CIR/CodeGen/callsite-inline-attributes.cpp index 06422d36539c6..b4b2c2386cf6e 100644 --- a/clang/test/CIR/CodeGen/callsite-inline-attributes.cpp +++ b/clang/test/CIR/CodeGen/callsite-inline-attributes.cpp @@ -49,6 +49,14 @@ void caller() { // CIR: cir.call %{{.*}}() {inline_kind = 1 : i32} // LLVM: call void %{{.*}}() #[[NOINLINE]] } + + [[clang::noinline]] + { + [[clang::always_inline]] + callee(); + // CIR: cir.call @_Z6calleev() {inline_kind = 2 : i32} + // LLVM: call void @_Z6calleev() #[[ALWAYSINLINE]] + } } // LLVM: attributes #[[ALWAYSINLINE]] = { alwaysinline } diff --git a/clang/test/CodeGen/attr-noinline-always-inline.cpp b/clang/test/CodeGen/attr-noinline-always-inline.cpp index 208bf13a1d1cb..35d3132468a00 100644 --- a/clang/test/CodeGen/attr-noinline-always-inline.cpp +++ b/clang/test/CodeGen/attr-noinline-always-inline.cpp @@ -1,4 +1,4 @@ -// RUN: %clang_cc1 -emit-llvm -o - %s | FileCheck %s +// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -emit-llvm -o - %s | FileCheck %s template <typename T> [[clang::noinline]] void foo(T) {} @@ -18,25 +18,36 @@ void caller() { void inner_fn(); void outer_fn() { - [[clang::noinline]] - { - [[clang::nomerge]] // unrelated to inling - inner_fn(); - } + [[clang::noinline]] + { + [[clang::nomerge]] // unrelated to inlining + inner_fn(); + } } // CHECK: call void @_Z8inner_fnv() #[[NOINLINE_NOMERGE:[0-9]+]] -// Inner function should clobber a conflicting attribute void inner_fn2(); void outer_fn2() { - [[clang::noinline]] - { - [[clang::always_inline]] - inner_fn2(); - } + [[clang::nomerge]] + { + [[clang::noinline]] // unrelated to nomerge + inner_fn2(); + } +} +// CHECK: call void @_Z9inner_fn2v() #[[NOINLINE_NOMERGE]] + +// Inner function should clobber a conflicting attribute +void inner_fn3(); + +void outer_fn3() { + [[clang::noinline]] + { + [[clang::always_inline]] + inner_fn3(); + } } -// CHECK: call void @_Z9inner_fn2v() #[[ALWAYSINLINE_ONLY:[0-9]+]] +// CHECK: call void @_Z9inner_fn3v() #[[ALWAYSINLINE_ONLY:[0-9]+]] // CHECK: attributes #[[ALWAYSINLINE]] = { // CHECK-SAME: alwaysinline diff --git a/clang/test/Sema/attr-noinline.cpp b/clang/test/Sema/attr-noinline.cpp index d6d48321d6604..b16ee660602d0 100644 --- a/clang/test/Sema/attr-noinline.cpp +++ b/clang/test/Sema/attr-noinline.cpp @@ -119,3 +119,37 @@ void use() { qux<3>(0); // #QUX_INST variadic_qux<0, 1, 2>(0); // #QUX_VARIADIC_INST } + +// Statement attributes are mutually exclusive +void caller() { + [[clang::noinline, clang::always_inline]] bar(); // expected-error {{'clang::always_inline' and 'clang::noinline' attributes are not compatible}} \ + // expected-note {{conflicting attribute is here}} + + [[clang::always_inline, clang::noinline]] bar(); // expected-error {{'clang::noinline' and 'clang::always_inline' attributes are not compatible}} \ + // expected-note {{conflicting attribute is here}} +} + +// Attributes on redeclared functions are mutually exclusive +[[clang::noinline]] void redecl_fn(); // expected-note {{conflicting attribute is here}} +[[clang::always_inline]] void redecl_fn() {} // expected-error {{'clang::always_inline' and 'clang::noinline' attributes are not compatible}} + +[[clang::always_inline]] void redecl_fn2(); // expected-note {{conflicting attribute is here}} +[[clang::noinline]] void redecl_fn2() {} // expected-error {{'clang::noinline' and 'clang::always_inline' attributes are not compatible}} + +// Attributes on the same declaration are mutually exclusive +[[clang::noinline, clang::always_inline]] void decl_fn(); // expected-error {{'clang::always_inline' and 'clang::noinline' attributes are not compatible}} \ + // expected-note {{conflicting attribute is here}} + +// Explicit specialization should not inherit inline attributes +template <typename T> +[[clang::noinline]] void tmpl_fn(T); + +template <> +[[clang::always_inline]] void tmpl_fn(int); // no error expected + +// Check different spellings +[[gnu::noinline]] void spelling_fn(); // expected-note {{conflicting attribute is here}} +[[gnu::always_inline]] void spelling_fn() {} // expected-error {{'gnu::always_inline' and 'gnu::noinline' attributes are not compatible}} + +__attribute__((noinline)) void spelling_fn2(); // expected-note {{conflicting attribute is here}} +__attribute__((always_inline)) void spelling_fn2() {} // expected-error {{'always_inline' and 'noinline' attributes are not compatible}} diff --git a/clang/test/SemaCXX/attr-noinline-always-inline-conflict.cpp b/clang/test/SemaCXX/attr-noinline-always-inline-conflict.cpp deleted file mode 100644 index cd50bf41e6c1f..0000000000000 --- a/clang/test/SemaCXX/attr-noinline-always-inline-conflict.cpp +++ /dev/null @@ -1,37 +0,0 @@ -// RUN: %clang_cc1 %s -std=c++17 -fsyntax-only -verify - -void foo() {} - -// Statement attributes are mutually exclusive -void caller() { - [[clang::noinline, clang::always_inline]] foo(); // expected-error {{'clang::always_inline' and 'clang::noinline' attributes are not compatible}} \ - // expected-note {{conflicting attribute is here}} - - [[clang::always_inline, clang::noinline]] foo(); // expected-error {{'clang::noinline' and 'clang::always_inline' attributes are not compatible}} \ - // expected-note {{conflicting attribute is here}} -} - -// Attributes on redeclared functions are mutually exclusive -[[clang::noinline]] void redecl_fn(); // expected-note {{conflicting attribute is here}} -[[clang::always_inline]] void redecl_fn() {} // expected-error {{'clang::always_inline' and 'clang::noinline' attributes are not compatible}} - -[[clang::always_inline]] void redecl_fn2(); // expected-note {{conflicting attribute is here}} -[[clang::noinline]] void redecl_fn2() {} // expected-error {{'clang::noinline' and 'clang::always_inline' attributes are not compatible}} - -// Attributes on the same declaration are mutually exclusive -[[clang::noinline, clang::always_inline]] void decl_fn(); // expected-error {{'clang::always_inline' and 'clang::noinline' attributes are not compatible}} \ - // expected-note {{conflicting attribute is here}} - -// Explicit specialization should not inherit inline attributes -template <typename T> -[[clang::noinline]] void tmpl_fn(T); - -template <> -[[clang::always_inline]] void tmpl_fn(int); // no error expected - -// Check different spellings -[[gnu::noinline]] void spelling_fn(); // expected-note {{conflicting attribute is here}} -[[gnu::always_inline]] void spelling_fn() {} // expected-error {{'gnu::always_inline' and 'gnu::noinline' attributes are not compatible}} - -__attribute__((noinline)) void spelling_fn2(); // expected-note {{conflicting attribute is here}} -__attribute__((always_inline)) void spelling_fn2() {} // expected-error {{'always_inline' and 'noinline' attributes are not compatible}} >From 78140c7f8bd3ddc0c756402cb1c6087541bf7e83 Mon Sep 17 00:00:00 2001 From: Kenzo <[email protected]> Date: Tue, 11 Aug 2026 19:54:21 -0400 Subject: [PATCH 3/3] [Clang] Add tests for bare template specializations, and noconvergent/alwaysinline attributes --- .../CodeGen/attr-noinline-always-inline.cpp | 42 ++++++++++++++++--- 1 file changed, 37 insertions(+), 5 deletions(-) diff --git a/clang/test/CodeGen/attr-noinline-always-inline.cpp b/clang/test/CodeGen/attr-noinline-always-inline.cpp index 35d3132468a00..ee7fa86a85dd7 100644 --- a/clang/test/CodeGen/attr-noinline-always-inline.cpp +++ b/clang/test/CodeGen/attr-noinline-always-inline.cpp @@ -1,17 +1,22 @@ -// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -emit-llvm -o - %s | FileCheck %s +// RUN: %clang_cc1 -O1 -disable-llvm-passes -triple x86_64-unknown-linux-gnu -emit-llvm -o - %s | FileCheck %s template <typename T> [[clang::noinline]] void foo(T) {} -// Explicit specialization should not inherit noinline +// Explicit specializations should not inherit noinline template <> [[clang::always_inline]] void foo<int>(int) {} +template <> +void foo<long>(long) {} + void caller() { foo<float>(4.2f); // expect noinline on function foo<int>(42); // expect alwaysinline on function + foo<long>(42L); // expect no noinline attribute } // CHECK: define {{.*}}void @_Z3fooIiEvT_({{.*}}) #[[ALWAYSINLINE:[0-9]+]] +// CHECK: define {{.*}}void @_Z3fooIlEvT_({{.*}}) #[[BARE:[0-9]+]] // CHECK: define {{.*}}void @_Z3fooIfEvT_({{.*}}) #[[NOINLINE:[0-9]+]] // Inner function should not clobber non-conflicting attributes @@ -37,21 +42,48 @@ void outer_fn2() { } // CHECK: call void @_Z9inner_fn2v() #[[NOINLINE_NOMERGE]] -// Inner function should clobber a conflicting attribute +[[clang::convergent]] void inner_fn3(); void outer_fn3() { - [[clang::noinline]] + [[clang::always_inline]] { - [[clang::always_inline]] + [[clang::noconvergent]] // unrelated to inlining inner_fn3(); } } // CHECK: call void @_Z9inner_fn3v() #[[ALWAYSINLINE_ONLY:[0-9]+]] +[[clang::convergent]] +void inner_fn4(); + +void outer_fn4() { + [[clang::noconvergent]] + { + [[clang::always_inline]] // unrelated to noconvergent + inner_fn4(); + } +} +// CHECK: call void @_Z9inner_fn4v() #[[ALWAYSINLINE_ONLY]] + +// Inner function should clobber a conflicting attribute +void inner_fn5(); + +void outer_fn5() { + [[clang::noinline]] + { + [[clang::always_inline]] + inner_fn5(); + } +} +// CHECK: call void @_Z9inner_fn5v() #[[ALWAYSINLINE_ONLY]] + // CHECK: attributes #[[ALWAYSINLINE]] = { // CHECK-SAME: alwaysinline // CHECK-NOT: noinline +// CHECK: attributes #[[BARE]] = { +// CHECK-NOT: alwaysinline +// CHECK-NOT: noinline // CHECK: attributes #[[NOINLINE]] = { // CHECK-SAME: noinline // CHECK-NOT: alwaysinline _______________________________________________ cfe-commits mailing list [email protected] https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
