Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 21 additions & 0 deletions src/Changes
Original file line number Diff line number Diff line change
@@ -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

Expand Down
44 changes: 38 additions & 6 deletions src/attribute.c
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand All @@ -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 {
Expand Down
4 changes: 4 additions & 0 deletions src/decls.c
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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
Expand Down
5 changes: 5 additions & 0 deletions src/decls.h
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
3 changes: 3 additions & 0 deletions src/func_def.c
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
5 changes: 4 additions & 1 deletion src/il.c
Original file line number Diff line number Diff line change
Expand Up @@ -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 */
Expand Down
1 change: 1 addition & 0 deletions tests/expectations/edg_x86_32_cp/regressions.log
Original file line number Diff line number Diff line change
@@ -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:
1 change: 1 addition & 0 deletions tests/expectations/edg_x86_64_cp/regressions.log
Original file line number Diff line number Diff line change
@@ -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:
Original file line number Diff line number Diff line change
Expand Up @@ -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__))
^
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,3 @@ fe_only -DTEST_NUMBER=1 --gnu_version=999999 -tused -e 200 --no_wrap --no_wrap_d
fnoinline_always_inline<Special>() // { dg-bogus "follows declaration" }
^

"Test_name.c", line 58: warning: routine is both "inline" and "noinline"
fnoinline_always_inline<Special>() // { dg-bogus "follows declaration" }
^

Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,3 @@ fe_only -DTEST_NUMBER=1 --gnu_version=999999 -tused -e 200 --no_wrap --no_wrap_d
fnoinline_always_inline<Special>() // { dg-bogus "follows declaration" }
^

"Test_name.c", line 112: warning: routine is both "inline" and "noinline"
fnoinline_always_inline<Special>() // { dg-bogus "follows declaration" }
^

Original file line number Diff line number Diff line change
@@ -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 ()
^

Original file line number Diff line number Diff line change
@@ -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))
^

Original file line number Diff line number Diff line change
@@ -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))
^

Original file line number Diff line number Diff line change
@@ -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))
^

Original file line number Diff line number Diff line change
@@ -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))
^

Original file line number Diff line number Diff line change
@@ -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))
^

54 changes: 54 additions & 0 deletions tests/tests/regressions/.gh-183-negative.rto/default.1.1.txt
Original file line number Diff line number Diff line change
@@ -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 <class T> inline T B<T>::m1(T x) { return x; } // Error.
^

"Test_name.c", line 46: error: routine is both "inline" and "noinline"
template <class T> NOINLINE T B<T>::m2(T x) { return x; } // Error.
^

13 errors detected in the compilation of "Test_name.c".
1 change: 1 addition & 0 deletions tests/tests/regressions/.gh-183-redecl.rto/default.1.1.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
fe_only -DTEST_NUMBER=1 --gnu_version=150000 --diag_error=inline_gnu_noinline_conflict --c++17 Test_name.c
1 change: 1 addition & 0 deletions tests/tests/regressions/.gh-183.rto/default.1.1.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
fe_only -DTEST_NUMBER=1 --gnu_version=150000 --diag_error=inline_gnu_noinline_conflict --c++17 Test_name.c
53 changes: 53 additions & 0 deletions tests/tests/regressions/gh-183-negative.sft.cpp
Original file line number Diff line number Diff line change
@@ -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 <class T> struct B {
NOINLINE T m1(T x);
inline T m2(T x);
};
template <class T> inline T B<T>::m1(T x) { return x; } // Error.
template <class T> NOINLINE T B<T>::m2(T x) { return x; } // Error.

int g() {
A a;
B<int> b; // Instantiating B<int>::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);
}
22 changes: 22 additions & 0 deletions tests/tests/regressions/gh-183-redecl.sft.cpp
Original file line number Diff line number Diff line change
@@ -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);
}
Loading
Loading