diff --git a/src/Changes b/src/Changes index 0734f50f32..21d6643a55 100644 --- a/src/Changes +++ b/src/Changes @@ -1,5 +1,26 @@ Version 7.1 +10/9/26 [GH #183] +GNU compatibility: "inline" combined with "noinline" on a definition + +In GNU C++ modes, the front end warned that a routine is both "inline" and +"noinline" when a function definition was declared "inline" and had the GNU +"noinline" attribute, but GCC accepts that combination silently (it is used, +e.g., by Boost through "BOOST_NOINLINE inline"). For example: + + inline __attribute__((noinline)) int f(int x) { // No warning now. + return x; + } + +In C modes, where GCC warns about the combination in all of those cases, the +behavior is unchanged. + +Also, instantiating a function template (or a member function of a class +template) that is both "inline" and "noinline" no longer repeats the warning +"At end of source" for each instance, only the template declarations are +checked. + + 10/9/26 [GH #27] Clearer diagnostic when auto is deduced from an incomplete type diff --git a/src/attribute.c b/src/attribute.c index 86c8b0a627..546439345e 100644 --- a/src/attribute.c +++ b/src/attribute.c @@ -6059,6 +6059,37 @@ that entity. #endif /* GNU_NAKED_ATTRIBUTE_ALLOWED || MICROSOFT_EXTENSIONS_ALLOWED */ +static a_boolean gnu_accepts_noinline_on_inline_routine(an_attribute_ptr ap) +/* +ap is a GNU "noinline" attribute being applied to an inline routine. Return +TRUE if GCC accepts that combination silently in the context of ap. For +example: + + inline __attribute__((noinline)) int f() { return 0; } // No warning. + +This idiom is used to ensure a noinline function in a header file does not +cause linker errors. GCC in C mode will warn on this while it does not in C++ +*/ +{ + a_boolean result = FALSE; + a_decl_parse_state *dps = (a_decl_parse_state*)ap->assoc_info; + + if (!C_mode()) { + if (dps == NULL) { + /* The attribute is not being applied as part of a declaration, which + means it is being copied to a template instance. */ + result = TRUE; + } else { + result = ap->on_primary_declaration && + (dps->dso_flags & + (DSO_INLINE | DSO_CONSTEXPR | DSO_CONSTEVAL)) != 0 && + !dps->inline_on_earlier_decl; + } /* if */ + } /* if */ + return result; +} /* gnu_accepts_noinline_on_inline_routine */ + + static char* apply_noinline_attr(an_attribute_ptr ap, char *entity, an_il_entry_kind entity_kind) @@ -6069,15 +6100,16 @@ and return the entity. { if (entity_kind == iek_routine) { a_routine_ptr rp = (a_routine_ptr)entity; + a_boolean has_always_inline = FALSE; +#if GNU_EXTENSIONS_ALLOWED + has_always_inline = find_attribute(ak_always_inline, + rp->source_corresp.attributes) != NULL; +#endif /* GNU_EXTENSIONS_ALLOWED */ rp->never_inline = TRUE; if (rp->is_inline && is_gcc_attribute(ap) && (!rp->source_corresp.is_class_member || - rp->defined_outside_of_parent -#if GNU_EXTENSIONS_ALLOWED - || find_attribute(ak_always_inline, rp->source_corresp.attributes) - != NULL -#endif /* GNU_EXTENSIONS_ALLOWED */ - )) { + rp->defined_outside_of_parent || has_always_inline) && + (has_always_inline || !gnu_accepts_noinline_on_inline_routine(ap))) { pos_warning(ec_inline_gnu_noinline_conflict, &ap->position); } /* if */ } else { diff --git a/src/decls.c b/src/decls.c index a04a449ccb..d2dd710d00 100644 --- a/src/decls.c +++ b/src/decls.c @@ -314,6 +314,7 @@ be restored). dps->has_direct_initializer = FALSE; dps->first_decl = FALSE; dps->first_decl_of_predeclared_entity = FALSE; + dps->inline_on_earlier_decl = FALSE; dps->override_okay = FALSE; dps->initializer_is_expr_list = FALSE; dps->no_special_cli_class_type_check = FALSE; @@ -10374,6 +10375,9 @@ skip_overloading:; is_nothrow_type(rtp)) { routine_ptr->never_throws = TRUE; } /* if */ + /* Record whether the routine was inline before this declaration (used when + applying the GNU "noinline" attribute). */ + dps->inline_on_earlier_decl = routine_ptr->is_inline; if (func_info->is_inline) set_inline_flag(routine_ptr, TRUE); if (use_std_c99_inlining && !idlb.is_block_extern_decl) { /* In C99 mode the definition_for_inlining_only flag is set only if that is diff --git a/src/decls.h b/src/decls.h index e5889995fc..cd7f3ffed9 100644 --- a/src/decls.h +++ b/src/decls.h @@ -1061,6 +1061,11 @@ typedef struct a_decl_parse_state { /* TRUE if this is the first declaration of a variable or function that was predeclared by the front end. */ + a_bit_field inline_on_earlier_decl:1; + /* TRUE if this declaration redeclares a function that + was already inline before this declaration (e.g., + because an earlier declaration specified + "inline"). */ a_bit_field redeclares_tag:1; /* TRUE if the specifiers include a tag name (like "struct S" or "enum E") that must match a prior diff --git a/src/func_def.c b/src/func_def.c index 39b801bc8a..c28d7e0519 100644 --- a/src/func_def.c +++ b/src/func_def.c @@ -2531,6 +2531,9 @@ member declaration (allowed in some Microsoft modes only). specification, and issue one if needed. */ issue_no_exception_support_diag_on_throw_spec(func_info); } /* if */ + /* Record whether the routine was inline before this declaration (used when + applying the GNU "noinline" attribute). */ + dps->inline_on_earlier_decl = rp->is_inline; if (func_info->is_inline) { if (!rp->is_inline) { set_inline_flag(rp, TRUE); diff --git a/src/il.c b/src/il.c index 662be45c4e..2e8b2e23a6 100644 --- a/src/il.c +++ b/src/il.c @@ -3284,12 +3284,15 @@ set). #endif /* MICROSOFT_EXTENSIONS_ALLOWED */ #if GNU_EXTENSIONS_ALLOWED } else if (rp->never_inline && gnu_mode && + (!rp->is_template_function || rp->is_specialized || + rp->is_prototype_instantiation) && (!rp->source_corresp.is_class_member || rp->defined_outside_of_parent || find_attribute(ak_always_inline, rp->source_corresp.attributes) != NULL)) { /* Was explicitly marked "noinline" previously and is now explicitly - marked inline. */ + marked inline. (An instance generated from a template is not + checked: the declarations of the template itself were.) */ pos_warning(ec_inline_gnu_noinline_conflict, &error_position); #endif /* GNU_EXTENSIONS_ALLOWED */ } /* if */ diff --git a/tests/expectations/edg_x86_32_cp/regressions.log b/tests/expectations/edg_x86_32_cp/regressions.log index feb1c4efff..1a25c2dacb 100644 --- a/tests/expectations/edg_x86_32_cp/regressions.log +++ b/tests/expectations/edg_x86_32_cp/regressions.log @@ -1,3 +1,4 @@ +gh-183-redecl.sft.cpp:fp:-DTEST_NUMBER=1 --c++17:REQUIREMENTS NOT MET: modules/crash-processing-imported-explicit-specialization/test.mft.cpp:cp:-DTEST_NUMBER=1:FAIL: pragma-il-class-no-lowering.sft.cpp:fp:-DTEST_NUMBER=1:REQUIREMENTS NOT MET: pragma-il-class.sft.cpp:fp:-DTEST_NUMBER=1:REQUIREMENTS NOT MET: diff --git a/tests/expectations/edg_x86_64_cp/regressions.log b/tests/expectations/edg_x86_64_cp/regressions.log index feb1c4efff..1a25c2dacb 100644 --- a/tests/expectations/edg_x86_64_cp/regressions.log +++ b/tests/expectations/edg_x86_64_cp/regressions.log @@ -1,3 +1,4 @@ +gh-183-redecl.sft.cpp:fp:-DTEST_NUMBER=1 --c++17:REQUIREMENTS NOT MET: modules/crash-processing-imported-explicit-specialization/test.mft.cpp:cp:-DTEST_NUMBER=1:FAIL: pragma-il-class-no-lowering.sft.cpp:fp:-DTEST_NUMBER=1:REQUIREMENTS NOT MET: pragma-il-class.sft.cpp:fp:-DTEST_NUMBER=1:REQUIREMENTS NOT MET: diff --git a/tests/tests/imported/gnu/cpp/.Wattributes-3.rto/default.1.1.txt b/tests/tests/imported/gnu/cpp/.Wattributes-3.rto/default.1.1.txt index 59ba6825bf..831a059c4f 100644 --- a/tests/tests/imported/gnu/cpp/.Wattributes-3.rto/default.1.1.txt +++ b/tests/tests/imported/gnu/cpp/.Wattributes-3.rto/default.1.1.txt @@ -3,10 +3,6 @@ fe_only -DTEST_NUMBER=1 --gnu_version=999999 -tused -e 200 --no_wrap --no_wrap_d ATTR ((__always_inline__)) ^ -"Test_name.c", line 53: warning: routine is both "inline" and "noinline" - inline ATTR ((__noinline__)) - ^ - "Test_name.c", line 58: warning: the "always_inline" attribute is ignored on non-inline functions ATTR ((__always_inline__)) ^ diff --git a/tests/tests/imported/gnu/cpp/ext/.attr-noinline-2.rto/default.1.1.txt b/tests/tests/imported/gnu/cpp/ext/.attr-noinline-2.rto/default.1.1.txt index 3065877c46..572be3122b 100644 --- a/tests/tests/imported/gnu/cpp/ext/.attr-noinline-2.rto/default.1.1.txt +++ b/tests/tests/imported/gnu/cpp/ext/.attr-noinline-2.rto/default.1.1.txt @@ -3,7 +3,3 @@ fe_only -DTEST_NUMBER=1 --gnu_version=999999 -tused -e 200 --no_wrap --no_wrap_d fnoinline_always_inline() // { dg-bogus "follows declaration" } ^ -"Test_name.c", line 58: warning: routine is both "inline" and "noinline" - fnoinline_always_inline() // { dg-bogus "follows declaration" } - ^ - diff --git a/tests/tests/imported/gnu/cpp/ext/.attr-noinline.rto/default.1.1.txt b/tests/tests/imported/gnu/cpp/ext/.attr-noinline.rto/default.1.1.txt index d01a441358..84d12af81f 100644 --- a/tests/tests/imported/gnu/cpp/ext/.attr-noinline.rto/default.1.1.txt +++ b/tests/tests/imported/gnu/cpp/ext/.attr-noinline.rto/default.1.1.txt @@ -3,7 +3,3 @@ fe_only -DTEST_NUMBER=1 --gnu_version=999999 -tused -e 200 --no_wrap --no_wrap_d fnoinline_always_inline() // { dg-bogus "follows declaration" } ^ -"Test_name.c", line 112: warning: routine is both "inline" and "noinline" - fnoinline_always_inline() // { dg-bogus "follows declaration" } - ^ - diff --git a/tests/tests/imported/gnu/cpp/gcov/.gcov-10.rto/default.1.1.txt b/tests/tests/imported/gnu/cpp/gcov/.gcov-10.rto/default.1.1.txt index 1d93edd1c7..aea020f183 100644 --- a/tests/tests/imported/gnu/cpp/gcov/.gcov-10.rto/default.1.1.txt +++ b/tests/tests/imported/gnu/cpp/gcov/.gcov-10.rto/default.1.1.txt @@ -1,5 +1 @@ eccp -DTEST_NUMBER=1 --gnu_version=999999 -tused -e 200 --no_wrap --no_wrap_diagnostics Test_name.c -"Test_name.c", line 9: warning: routine is both "inline" and "noinline" - inline int __attribute__ ((noinline)) Foo () - ^ - diff --git a/tests/tests/imported/gnu/cpp/ipa/.nothrow-1.rto/default.1.1.txt b/tests/tests/imported/gnu/cpp/ipa/.nothrow-1.rto/default.1.1.txt index c7124254be..1c625c94ce 100644 --- a/tests/tests/imported/gnu/cpp/ipa/.nothrow-1.rto/default.1.1.txt +++ b/tests/tests/imported/gnu/cpp/ipa/.nothrow-1.rto/default.1.1.txt @@ -1,5 +1 @@ fe_only -DTEST_NUMBER=1 --gnu_version=999999 -tused -e 200 --no_wrap --no_wrap_diagnostics Test_name.c -"Test_name.c", line 11: warning: routine is both "inline" and "noinline" - __attribute__ ((noinline)) - ^ - diff --git a/tests/tests/imported/gnu/cpp/ipa/.pure-const-1.rto/default.1.1.txt b/tests/tests/imported/gnu/cpp/ipa/.pure-const-1.rto/default.1.1.txt index c7124254be..1c625c94ce 100644 --- a/tests/tests/imported/gnu/cpp/ipa/.pure-const-1.rto/default.1.1.txt +++ b/tests/tests/imported/gnu/cpp/ipa/.pure-const-1.rto/default.1.1.txt @@ -1,5 +1 @@ fe_only -DTEST_NUMBER=1 --gnu_version=999999 -tused -e 200 --no_wrap --no_wrap_diagnostics Test_name.c -"Test_name.c", line 11: warning: routine is both "inline" and "noinline" - __attribute__ ((noinline)) - ^ - diff --git a/tests/tests/imported/gnu/cpp/ipa/.pure-const-2.rto/default.1.1.txt b/tests/tests/imported/gnu/cpp/ipa/.pure-const-2.rto/default.1.1.txt index 9c427a82a9..1c625c94ce 100644 --- a/tests/tests/imported/gnu/cpp/ipa/.pure-const-2.rto/default.1.1.txt +++ b/tests/tests/imported/gnu/cpp/ipa/.pure-const-2.rto/default.1.1.txt @@ -1,5 +1 @@ fe_only -DTEST_NUMBER=1 --gnu_version=999999 -tused -e 200 --no_wrap --no_wrap_diagnostics Test_name.c -"Test_name.c", line 10: warning: routine is both "inline" and "noinline" - __attribute__ ((noinline)) - ^ - diff --git a/tests/tests/imported/gnu/cpp/ipa/.pure-const-3.rto/default.1.1.txt b/tests/tests/imported/gnu/cpp/ipa/.pure-const-3.rto/default.1.1.txt index 700648045d..1c625c94ce 100644 --- a/tests/tests/imported/gnu/cpp/ipa/.pure-const-3.rto/default.1.1.txt +++ b/tests/tests/imported/gnu/cpp/ipa/.pure-const-3.rto/default.1.1.txt @@ -1,9 +1 @@ fe_only -DTEST_NUMBER=1 --gnu_version=999999 -tused -e 200 --no_wrap --no_wrap_diagnostics Test_name.c -"./ipa/pure-const-3.h", line 7: warning: routine is both "inline" and "noinline" - __attribute__ ((noinline)) - ^ - -"./ipa/pure-const-3.h", line 15: warning: routine is both "inline" and "noinline" - __attribute__ ((noinline)) - ^ - diff --git a/tests/tests/imported/gnu/cpp/ipa/.pure-const-3b.rto/default.1.1.txt b/tests/tests/imported/gnu/cpp/ipa/.pure-const-3b.rto/default.1.1.txt index 700648045d..1c625c94ce 100644 --- a/tests/tests/imported/gnu/cpp/ipa/.pure-const-3b.rto/default.1.1.txt +++ b/tests/tests/imported/gnu/cpp/ipa/.pure-const-3b.rto/default.1.1.txt @@ -1,9 +1 @@ fe_only -DTEST_NUMBER=1 --gnu_version=999999 -tused -e 200 --no_wrap --no_wrap_diagnostics Test_name.c -"./ipa/pure-const-3.h", line 7: warning: routine is both "inline" and "noinline" - __attribute__ ((noinline)) - ^ - -"./ipa/pure-const-3.h", line 15: warning: routine is both "inline" and "noinline" - __attribute__ ((noinline)) - ^ - diff --git a/tests/tests/regressions/.gh-183-negative.rto/default.1.1.txt b/tests/tests/regressions/.gh-183-negative.rto/default.1.1.txt new file mode 100644 index 0000000000..c07a918ed1 --- /dev/null +++ b/tests/tests/regressions/.gh-183-negative.rto/default.1.1.txt @@ -0,0 +1,54 @@ +fe_only -DTEST_NUMBER=1 --gnu_version=150000 --diag_error=inline_gnu_noinline_conflict --c++17 Test_name.c +"Test_name.c", line 9: error: routine is both "inline" and "noinline" + inline NOINLINE int f1(int x); // Error: not a definition. + ^ + +"Test_name.c", line 10: error: routine is both "inline" and "noinline" + inline int f1(int x) { return x; } // Error: inline after noinline. + ^ + +"Test_name.c", line 12: error: routine is both "inline" and "noinline" + inline int f2(int x) NOINLINE; // Error: not a definition. + ^ + +"Test_name.c", line 15: error: routine is both "inline" and "noinline" + inline int f3(int x) { return x; } // Error: inline after noinline. + ^ + +"Test_name.c", line 18: error: routine is both "inline" and "noinline" + inline NOINLINE int f4(int x) { return x; } // Error: inline after + ^ + +"Test_name.c", line 22: error: routine is both "inline" and "noinline" + NOINLINE int f5(int x) { return x; } // Error: noinline after inline. + ^ + +"Test_name.c", line 25: error: routine is both "inline" and "noinline" + inline NOINLINE int f6(int x) { return x; } // Error: noinline after + ^ + +"Test_name.c", line 29: error: routine is both "inline" and "noinline" + constexpr NOINLINE int f7(int x) { return x; } // Error: noinline after + ^ + +"Test_name.c", line 37: error: routine is both "inline" and "noinline" + inline NOINLINE int A::m1(int x) { return x; } // Error. + ^ + +"Test_name.c", line 38: error: routine is both "inline" and "noinline" + inline NOINLINE int A::m2(int x) { return x; } // Error (only once). + ^ + +"Test_name.c", line 39: error: routine is both "inline" and "noinline" + inline NOINLINE int A::m3(int x) { return x; } // Error. + ^ + +"Test_name.c", line 45: error: routine is both "inline" and "noinline" + template inline T B::m1(T x) { return x; } // Error. + ^ + +"Test_name.c", line 46: error: routine is both "inline" and "noinline" + template NOINLINE T B::m2(T x) { return x; } // Error. + ^ + +13 errors detected in the compilation of "Test_name.c". diff --git a/tests/tests/regressions/.gh-183-redecl.rto/default.1.1.txt b/tests/tests/regressions/.gh-183-redecl.rto/default.1.1.txt new file mode 100644 index 0000000000..7bb2ae7885 --- /dev/null +++ b/tests/tests/regressions/.gh-183-redecl.rto/default.1.1.txt @@ -0,0 +1 @@ +fe_only -DTEST_NUMBER=1 --gnu_version=150000 --diag_error=inline_gnu_noinline_conflict --c++17 Test_name.c diff --git a/tests/tests/regressions/.gh-183.rto/default.1.1.txt b/tests/tests/regressions/.gh-183.rto/default.1.1.txt new file mode 100644 index 0000000000..7bb2ae7885 --- /dev/null +++ b/tests/tests/regressions/.gh-183.rto/default.1.1.txt @@ -0,0 +1 @@ +fe_only -DTEST_NUMBER=1 --gnu_version=150000 --diag_error=inline_gnu_noinline_conflict --c++17 Test_name.c diff --git a/tests/tests/regressions/gh-183-negative.sft.cpp b/tests/tests/regressions/gh-183-negative.sft.cpp new file mode 100644 index 0000000000..9337e98e67 --- /dev/null +++ b/tests/tests/regressions/gh-183-negative.sft.cpp @@ -0,0 +1,53 @@ +//type:fn +//options_all:--gnu_version=150000 --diag_error=inline_gnu_noinline_conflict +//options:--c++17 +// Cases in which GCC still diagnoses "inline" combined with "noinline" in C++ +// (see gh-183.sft.cpp for the cases in which it does not): declarations that +// are not definitions, and redeclarations that add one to the other. +#define NOINLINE __attribute__((__noinline__)) + +inline NOINLINE int f1(int x); // Error: not a definition. +inline int f1(int x) { return x; } // Error: inline after noinline. + +inline int f2(int x) NOINLINE; // Error: not a definition. + +NOINLINE int f3(int x); +inline int f3(int x) { return x; } // Error: inline after noinline. + +NOINLINE int f4(int x); +inline NOINLINE int f4(int x) { return x; } // Error: inline after + // noinline (only once). + +inline int f5(int x); +NOINLINE int f5(int x) { return x; } // Error: noinline after inline. + +inline int f6(int x); +inline NOINLINE int f6(int x) { return x; } // Error: noinline after + // inline. + +constexpr int f7(int x); +constexpr NOINLINE int f7(int x) { return x; } // Error: noinline after + // inline. + +struct A { + inline NOINLINE int m1(int x); // No error in the class. + NOINLINE int m2(int x); + inline int m3(int x); +}; +inline NOINLINE int A::m1(int x) { return x; } // Error. +inline NOINLINE int A::m2(int x) { return x; } // Error (only once). +inline NOINLINE int A::m3(int x) { return x; } // Error. + +template struct B { + NOINLINE T m1(T x); + inline T m2(T x); +}; +template inline T B::m1(T x) { return x; } // Error. +template NOINLINE T B::m2(T x) { return x; } // Error. + +int g() { + A a; + B b; // Instantiating B::m1 and m2 adds no further errors. + return f1(1) + f3(1) + f4(1) + f5(1) + f6(1) + f7(1) + a.m1(1) + a.m2(1) + + a.m3(1) + b.m1(1) + b.m2(1); +} diff --git a/tests/tests/regressions/gh-183-redecl.sft.cpp b/tests/tests/regressions/gh-183-redecl.sft.cpp new file mode 100644 index 0000000000..0a2c939f78 --- /dev/null +++ b/tests/tests/regressions/gh-183-redecl.sft.cpp @@ -0,0 +1,22 @@ +//type:fp +//options_all:--gnu_version=150000 --diag_error=inline_gnu_noinline_conflict +//options:--c++17 +//require:BACK_END_IS_CP_GEN_BE 0 +// Like gh-183.sft.cpp: GCC does not diagnose an "inline" definition with the +// "noinline" attribute that follows a declaration without "inline". (The +// C++-generating back end puts out the earlier declaration with "inline", so +// its generated code would legitimately be diagnosed; hence the requirement.) +#define NOINLINE __attribute__((__noinline__)) + +int f(int x); +inline NOINLINE int f(int x) { return x; } + +struct A { + int m(int x); +}; +inline NOINLINE int A::m(int x) { return x; } + +int g() { + A a; + return f(1) + a.m(1); +} diff --git a/tests/tests/regressions/gh-183.sft.cpp b/tests/tests/regressions/gh-183.sft.cpp new file mode 100644 index 0000000000..70d554395f --- /dev/null +++ b/tests/tests/regressions/gh-183.sft.cpp @@ -0,0 +1,41 @@ +//type:fp +//options_all:--gnu_version=150000 --diag_error=inline_gnu_noinline_conflict +//options:--c++17 +// GCC does not diagnose "inline" combined with "noinline" on a C++ function +// definition (unless an earlier declaration already made the function +// inline). Boost.System and others use the idiom "BOOST_NOINLINE inline". +// (See gh-183-redecl.sft.cpp for definitions that follow a declaration +// without "inline".) +#define NOINLINE __attribute__((__noinline__)) + +inline __attribute__((noinline)) int f(int x) { return x; } +int g() { return f(1); } + +NOINLINE inline int f2(int x) { return x; } +static inline NOINLINE int f4(int x) { return x; } +[[gnu::noinline]] inline int f5(int x) { return x; } +constexpr NOINLINE int f6(int x) { return x; } +namespace N { inline NOINLINE int f7(int x) { return x; } } + +struct A { + NOINLINE int m2(int x) { return x; } // Implicitly inline. + template T m3(T x); + friend inline NOINLINE int fr(A) { return 0; } +}; +template inline NOINLINE T A::m3(T x) { return x; } + +template inline NOINLINE T t1(T x) { return x; } + +template struct B { + T m(T x); +}; +template inline NOINLINE T B::m(T x) { return x; } + +auto lam = [](int x) NOINLINE { return x; }; + +int h() { + A a; + B b; + return f2(1) + f4(1) + f5(1) + f6(1) + N::f7(1) + a.m2(1) + a.m3(1) + + fr(a) + t1(1) + t1(1L) + b.m(1) + lam(1); +}