c, c++: Support musttail attribute even using __attribute__ form [PR116545]
Checks
Context |
Check |
Description |
linaro-tcwg-bot/tcwg_gcc_build--master-arm |
success
|
Build passed
|
linaro-tcwg-bot/tcwg_simplebootstrap_build--master-arm-bootstrap |
success
|
Build passed
|
linaro-tcwg-bot/tcwg_simplebootstrap_build--master-aarch64-bootstrap |
success
|
Build passed
|
linaro-tcwg-bot/tcwg_gcc_build--master-aarch64 |
success
|
Build passed
|
Commit Message
Hi!
Apparently some programs in the wild use
#if __has_attribute(musttail)
__attribute__((musttail)) return foo ();
#else
return foo ();
#endif
clang supports musttail both as a standard attribute ([[clang::musttail]]
which we also support for compatibility) and the above worked just
fine with GCC 14 which had __has_attribute(musttail) 0. Now that it is
0, this doesn't compile anymore.
So, either we need to ensure that __has_attribute(musttail) is 0
and just __has_c{,pp}_attribute({gnu,clang}::musttail) are non-zero,
or IMHO better we just make it work in the attribute form, especially for
C < C23 I can see why some projects would prefer that form.
While [[gnu::musttail]] is rejected as an error in C11 etc. before GCC 15,
rather than just handled as an unknown attribute.
I view this as both a regression and compatibility issue.
The patch handles it in similar spots to fallthrough/assume attributes
inside of __attribute__.
While working on it, I've noticed we weren't diagnosing arguments to the
clang::musttail attribute (fixed by the c-attribs.cc hunk) and newly
on the __attribute__ form attribute (in that case the arguments aren't just
skipped, they are always parsed and because we don't call decl_attributes
etc., it wouldn't be diagnosed without a manual check).
Bootstrapped/regtested on x86_64-linux and i686-linux, ok for trunk?
2025-03-13 Jakub Jelinek <jakub@redhat.com>
PR c/116545
gcc/
* doc/extend.texi (musttail statement attribute): Document
that musttail GNU attribute can be used as well.
gcc/c-family/
* c-attribs.cc (c_common_clang_attributes): Add musttail.
gcc/c/
* c-parser.cc (c_parser_declaration_or_fndef): Parse
__attribute__((musttail)) return.
(c_parser_handle_musttail): Diagnose attribute arguments.
(c_parser_statement_after_labels): Parse
__attribute__((musttail)) return.
gcc/cp/
* parser.cc (cp_parser_expression_statement): Parse
__attribute__((musttail)) return.
gcc/testsuite/
* c-c++-common/musttail15.c: New test.
* c-c++-common/musttail16.c: New test.
* c-c++-common/musttail17.c: New test.
* c-c++-common/musttail18.c: New test.
* c-c++-common/musttail19.c: New test.
* c-c++-common/musttail20.c: New test.
* c-c++-common/musttail21.c: New test.
* c-c++-common/musttail22.c: New test.
* c-c++-common/musttail23.c: New test.
* c-c++-common/musttail24.c: New test.
* g++.dg/musttail7.C: New test.
* g++.dg/musttail8.C: New test.
* g++.dg/musttail12.C: New test.
* g++.dg/musttail13.C: New test.
Jakub
Comments
On 3/13/25 11:27 AM, Jakub Jelinek wrote:
> Hi!
>
> Apparently some programs in the wild use
> #if __has_attribute(musttail)
> __attribute__((musttail)) return foo ();
> #else
> return foo ();
> #endif
> clang supports musttail both as a standard attribute ([[clang::musttail]]
> which we also support for compatibility) and the above worked just
> fine with GCC 14 which had __has_attribute(musttail) 0. Now that it is
> 0, this doesn't compile anymore.
> So, either we need to ensure that __has_attribute(musttail) is 0
> and just __has_c{,pp}_attribute({gnu,clang}::musttail) are non-zero,
> or IMHO better we just make it work in the attribute form, especially for
> C < C23 I can see why some projects would prefer that form.
> While [[gnu::musttail]] is rejected as an error in C11 etc. before GCC 15,
> rather than just handled as an unknown attribute.
> I view this as both a regression and compatibility issue.
> The patch handles it in similar spots to fallthrough/assume attributes
> inside of __attribute__.
>
> While working on it, I've noticed we weren't diagnosing arguments to the
> clang::musttail attribute (fixed by the c-attribs.cc hunk) and newly
> on the __attribute__ form attribute (in that case the arguments aren't just
> skipped, they are always parsed and because we don't call decl_attributes
> etc., it wouldn't be diagnosed without a manual check).
>
> Bootstrapped/regtested on x86_64-linux and i686-linux, ok for trunk?
>
> 2025-03-13 Jakub Jelinek <jakub@redhat.com>
>
> PR c/116545
> gcc/
> * doc/extend.texi (musttail statement attribute): Document
> that musttail GNU attribute can be used as well.
> gcc/c-family/
> * c-attribs.cc (c_common_clang_attributes): Add musttail.
> gcc/c/
> * c-parser.cc (c_parser_declaration_or_fndef): Parse
> __attribute__((musttail)) return.
> (c_parser_handle_musttail): Diagnose attribute arguments.
> (c_parser_statement_after_labels): Parse
> __attribute__((musttail)) return.
> gcc/cp/
> * parser.cc (cp_parser_expression_statement): Parse
> __attribute__((musttail)) return.
Parsing a jump-statement under cp_parser_expression_statement just
because it happens to start with __attribute is pretty strange.
How about changing cp_parser_std_attribute_spec_seq in
cp_parser_statement to cp_parser_attributes_opt?
Jason
On Thu, Mar 13, 2025 at 01:45:43PM -0400, Jason Merrill wrote:
> On 3/13/25 11:27 AM, Jakub Jelinek wrote:
> Parsing a jump-statement under cp_parser_expression_statement just because
> it happens to start with __attribute is pretty strange.
It is true that it is pretty strange, but that is where we handle the
empty declarations with GNU attributes (or shall those be called attribute
declarations) as well. Or are empty statements with GNU attribute
expression-statement with no expression?
> How about changing cp_parser_std_attribute_spec_seq in cp_parser_statement
> to cp_parser_attributes_opt?
I'd be afraid that would be quite significant change of behavior everywhere,
something that C doesn't allow (like mixing std and GNU attributes in any
orders or [[]] __attribute__(()) [[]][[]] __attribute__(())
expression-statement). Or it would allow __attribute__(()) on while, do,
for, if, switch, ..., again something that wasn't accepted before.
In any case, calling cp_parser_jump_expression from cp_parser_statement
directly rather than from cp_parser_expression_statement is easily possible
just by doing
else if (cp_next_tokens_can_be_gnu_attribute_p (parser))
{
unsigned int n = cp_parser_skip_gnu_attributes_opt (parser, 1);
if (cp_lexer_nth_token_is_keyword (parser->lexer, n, RID_RETURN))
{
tree attr = cp_parser_gnu_attributes_opt (parser);
for (tree a = lookup_attribute ("musttail", attr);
a; a = lookup_attribute ("musttail", TREE_CHAIN (a)))
if (TREE_VALUE (a))
error ("%qs attribute does not take any arguments",
"musttail");
statement = cp_parser_jump_statement (parser, attr);
if (attr != NULL_TREE && any_nonignored_attribute_p (attr))
warning_at (loc, OPT_Wattributes,
"attributes at the beginning of statement are "
"ignored");
return statement;
}
}
after
else if (token->type == CPP_EOF)
{
cp_parser_error (parser, "expected statement");
return;
}
Or the GNU attributes on empty statement stuff could move there as well.
Jakub
On 3/13/25 3:16 PM, Jakub Jelinek wrote:
> On Thu, Mar 13, 2025 at 01:45:43PM -0400, Jason Merrill wrote:
>> On 3/13/25 11:27 AM, Jakub Jelinek wrote:
>> Parsing a jump-statement under cp_parser_expression_statement just because
>> it happens to start with __attribute is pretty strange.
>
> It is true that it is pretty strange, but that is where we handle the
> empty declarations with GNU attributes (or shall those be called attribute
> declarations) as well. Or are empty statements with GNU attribute
> expression-statement with no expression?
Yes, empty statements are technically expression-statements.
>> How about changing cp_parser_std_attribute_spec_seq in cp_parser_statement
>> to cp_parser_attributes_opt?
>
> I'd be afraid that would be quite significant change of behavior everywhere,
> something that C doesn't allow (like mixing std and GNU attributes in any
> orders or [[]] __attribute__(()) [[]][[]] __attribute__(())
> expression-statement). Or it would allow __attribute__(()) on while, do,
> for, if, switch, ..., again something that wasn't accepted before.
Do you think those changes are undesirable? We've previously had to fix
cases where we were failing to support mixing of std and GNU attributes.
> In any case, calling cp_parser_jump_expression from cp_parser_statement
> directly rather than from cp_parser_expression_statement is easily possible
> just by doing
> else if (cp_next_tokens_can_be_gnu_attribute_p (parser))
> {
> unsigned int n = cp_parser_skip_gnu_attributes_opt (parser, 1);
> if (cp_lexer_nth_token_is_keyword (parser->lexer, n, RID_RETURN))
> {
> tree attr = cp_parser_gnu_attributes_opt (parser);
> for (tree a = lookup_attribute ("musttail", attr);
> a; a = lookup_attribute ("musttail", TREE_CHAIN (a)))
> if (TREE_VALUE (a))
> error ("%qs attribute does not take any arguments",
> "musttail");
> statement = cp_parser_jump_statement (parser, attr);
> if (attr != NULL_TREE && any_nonignored_attribute_p (attr))
> warning_at (loc, OPT_Wattributes,
> "attributes at the beginning of statement are "
> "ignored");
> return statement;
> }
> }
> after
> else if (token->type == CPP_EOF)
> {
> cp_parser_error (parser, "expected statement");
> return;
> }
>
> Or the GNU attributes on empty statement stuff could move there as well.
Yes, it seems desirable to move all the attribute handling out of
cp_parser_expression_statement; in the grammar, the attributes aren't
part of the expression-statement, and it's odd to handle fallthrough in
both places.
Jason
On Thu, 13 Mar 2025, Jason Merrill wrote:
> > I'd be afraid that would be quite significant change of behavior everywhere,
> > something that C doesn't allow (like mixing std and GNU attributes in any
> > orders or [[]] __attribute__(()) [[]][[]] __attribute__(())
> > expression-statement). Or it would allow __attribute__(()) on while, do,
> > for, if, switch, ..., again something that wasn't accepted before.
>
> Do you think those changes are undesirable? We've previously had to fix cases
> where we were failing to support mixing of std and GNU attributes.
Mixing attributes is problematic because of different semantics for
appertainment. For example, in
void func() ATTRS;
if ATTRS are standard attributes then they are part of the function
declarator and appertain to the function type, but if they are GNU
attributes then they are not part of the declarator, but rather appertain
to the declaration. This means completely different parts of the C parser
handle the different kinds of attributes in what superficially looks like
the same position (standard attributes handled in parsing a declarator,
GNU ones in parsing the declaration after the declarator - with more
complicated declarators such as functions returning pointers to arrays,
the locations can end up physically separated) and there would be no
coherent syntax or semantics for appertainment for mixed attributes there.
> On Thu, 13 Mar 2025, Jason Merrill wrote:
>
>>> I'd be afraid that would be quite significant change of behavior everywhere,
>>> something that C doesn't allow (like mixing std and GNU attributes in any
>>> orders or [[]] __attribute__(()) [[]][[]] __attribute__(())
>>> expression-statement). Or it would allow __attribute__(()) on while, do,
>>> for, if, switch, ..., again something that wasn't accepted before.
>>
>> Do you think those changes are undesirable? We've previously had to fix cases
>> where we were failing to support mixing of std and GNU attributes.
On 3/13/25 4:10 PM, Jakub Jelinek wrote:
> I know, but we've never allowed GNU attributes on most of those, neither
> does clang, we don't allow it in C and with the exception of
> fallthrough/assume on empty statement and musttail on return we currently
> even don't have any uses for it. If we start accepting it, we'd then
> need to support it forever.
But, as you say, we already started accepting it for
fallthrough/assume/musttail; deciding to allow it in that case and not
in others seems weirdly inconsistent.
> Moving the GNU attribute assume/fallthrough handling on empty statement handling
> from cp_parser_expression_statement would regress
> int bar (int x) { return x; }
>
> void
> foo (void)
> {
> if (__attribute__(()); true)
> ;
> if (__attribute__((assume (bar (1)))); true)
> ;
> // if (__attribute__((fallthrough)); true)
> // ;
> }
> accepted by g++ 13 and 14 (fallthrough commented out, that is diagnosed
> with error).
So in an init-statement we can use GNU attributes but not standard
attributes? Another strange inconsistency.
On 3/13/25 4:10 PM, Joseph Myers wrote:
> Mixing attributes is problematic because of different semantics for
> appertainment. For example, in
>
> void func() ATTRS;
>
> if ATTRS are standard attributes then they are part of the function
> declarator and appertain to the function type, but if they are GNU
> attributes then they are not part of the declarator, but rather appertain
> to the declaration. This means completely different parts of the C parser
> handle the different kinds of attributes in what superficially looks like
> the same position (standard attributes handled in parsing a declarator,
> GNU ones in parsing the declaration after the declarator - with more
> complicated declarators such as functions returning pointers to arrays,
> the locations can end up physically separated) and there would be no
> coherent syntax or semantics for appertainment for mixed attributes there.
That's true within a declaration-statement, but at the beginning of a
declaration-statement, both appertain to any declarations. And at the
beginning of any other statement, both appertain to the statement. So I
don't think this concern applies to this case.
Jason
@@ -10241,18 +10241,22 @@ have to optimize it to just @code{return
@cindex @code{musttail} statement attribute
@item musttail
-The @code{gnu::musttail} or @code{clang::musttail} attribute
-can be applied to a @code{return} statement with a return-value expression
-that is a function call. It asserts that the call must be a tail call that
-does not allocate extra stack space, so it is safe to use tail recursion
-to implement long running loops.
+The @code{gnu::musttail} or @code{clang::musttail} standard attribute
+or @code{musttail} GNU attribute can be applied to a @code{return} statement
+with a return-value expression that is a function call. It asserts that the
+call must be a tail call that does not allocate extra stack space, so it is
+safe to use tail recursion to implement long running loops.
@smallexample
[[gnu::musttail]] return foo();
@end smallexample
+@smallexample
+__attribute__((musttail)) return bar();
+@end smallexample
+
If the compiler cannot generate a @code{musttail} tail call it will report
-an error. On some targets tail calls may never be supported.
+an error. On some targets tail calls may never be supported.
Tail calls cannot reference locals in memory, which may affect
builds without optimization when passing small structures, or passing
or returning large structures. Enabling @option{-O1} or @option{-O2} can
@@ -651,7 +651,9 @@ const struct scoped_attribute_specs c_co
/* Attributes also recognized in the clang:: namespace. */
const struct attribute_spec c_common_clang_attributes[] = {
{ "flag_enum", 0, 0, false, true, false, false,
- handle_flag_enum_attribute, NULL }
+ handle_flag_enum_attribute, NULL },
+ { "musttail", 0, 0, false, false, false,
+ false, handle_musttail_attribute, NULL }
};
const struct scoped_attribute_specs c_common_clang_attribute_table =
@@ -1820,6 +1820,7 @@ static void c_parser_objc_at_dynamic_dec
static bool c_parser_objc_diagnose_bad_element_prefix
(c_parser *, struct c_declspecs *);
static location_t c_parser_parse_rtl_body (c_parser *, char *);
+static tree c_parser_handle_musttail (c_parser *, tree, attr_state &);
#if ENABLE_ANALYZER
@@ -2519,6 +2520,26 @@ c_parser_declaration_or_fndef (c_parser
c_finish_oacc_routine (oacc_routine_data, NULL_TREE, false);
return result;
}
+ else if (specs->typespec_kind == ctsk_none
+ && c_parser_next_token_is_keyword (parser, RID_RETURN))
+ {
+ attr_state astate = {};
+ specs->attrs = c_parser_handle_musttail (parser, specs->attrs, astate);
+ if (astate.musttail_p)
+ {
+ if (specs->attrs)
+ {
+ auto_urlify_attributes sentinel;
+ warning_at (c_parser_peek_token (parser)->location,
+ OPT_Wattributes,
+ "attribute %<musttail%> mixed with other attributes "
+ "on %<return%> statement");
+ }
+ c_parser_statement_after_labels (parser, NULL, NULL_TREE, NULL,
+ astate);
+ return result;
+ }
+ }
/* Provide better error recovery. Note that a type name here is usually
better diagnosed as a redeclaration. */
@@ -7373,8 +7394,12 @@ c_parser_handle_musttail (c_parser *pars
{
if (c_parser_next_token_is_keyword (parser, RID_RETURN))
{
- if (lookup_attribute ("gnu", "musttail", std_attrs))
+ if (tree a = lookup_attribute ("gnu", "musttail", std_attrs))
{
+ for (; a; a = lookup_attribute ("gnu", "musttail", TREE_CHAIN (a)))
+ if (TREE_VALUE (a))
+ error ("%qs attribute does not take any arguments",
+ "musttail");
std_attrs = remove_attribute ("gnu", "musttail", std_attrs);
attr.musttail_p = true;
}
@@ -8237,7 +8262,8 @@ c_parser_statement_after_labels (c_parse
case RID_ATTRIBUTE:
{
/* Allow '__attribute__((fallthrough));' or
- '__attribute__((assume(cond)));'. */
+ '__attribute__((assume(cond)));' or
+ '__attribute__((musttail))) return'. */
tree attrs = c_parser_gnu_attributes (parser);
bool has_assume = lookup_attribute ("assume", attrs);
if (has_assume)
@@ -8252,6 +8278,20 @@ c_parser_statement_after_labels (c_parse
has_assume = false;
}
}
+ gcc_assert (!astate.musttail_p);
+ attrs = c_parser_handle_musttail (parser, attrs, astate);
+ if (astate.musttail_p)
+ {
+ if (attrs)
+ {
+ auto_urlify_attributes sentinel;
+ warning_at (c_parser_peek_token (parser)->location,
+ OPT_Wattributes,
+ "attribute %<musttail%> mixed with other "
+ "attributes on %<return%> statement");
+ }
+ goto restart;
+ }
if (attribute_fallthrough_p (attrs))
{
if (c_parser_next_token_is (parser, CPP_SEMICOLON))
@@ -13577,6 +13577,22 @@ cp_parser_expression_statement (cp_parse
statement. */
if (cp_lexer_next_token_is_not (parser->lexer, CPP_SEMICOLON))
{
+ /* If the next token is return, check for musttail attribute. */
+ tree a;
+ if (cp_lexer_next_token_is_keyword (parser->lexer, RID_RETURN)
+ && (a = lookup_attribute ("musttail", attr)))
+ {
+ for (; a; a = lookup_attribute ("musttail", TREE_CHAIN (a)))
+ if (TREE_VALUE (a))
+ error ("%qs attribute does not take any arguments",
+ "musttail");
+ statement = cp_parser_jump_statement (parser, attr);
+ if (attr != NULL_TREE && any_nonignored_attribute_p (attr))
+ warning_at (loc, OPT_Wattributes,
+ "attributes at the beginning of statement are "
+ "ignored");
+ return statement;
+ }
statement = cp_parser_expression (parser);
if (statement == error_mark_node
&& !cp_parser_uncommitted_to_tentative_parse_p (parser))
@@ -0,0 +1,14 @@
+/* { dg-do compile { target { musttail && { c || c++11 } } } } */
+/* { dg-additional-options "-fdelayed-branch" { target sparc*-*-* } } */
+
+int __attribute__((noinline,noclone,noipa))
+callee (int i)
+{
+ return i * i;
+}
+
+int __attribute__((noinline,noclone,noipa))
+caller (int i)
+{
+ __attribute__((musttail)) return callee (i + 1);
+}
@@ -0,0 +1,33 @@
+/* { dg-do compile { target { musttail && { c || c++11 } } } } */
+
+struct box { char field[256]; int i; };
+
+int __attribute__((noinline,noclone,noipa))
+test_2_callee (int i, struct box b)
+{
+ if (b.field[0])
+ return 5;
+ return i * i;
+}
+
+int __attribute__((noinline,noclone,noipa))
+test_2_caller (int i)
+{
+ struct box b;
+ __attribute__((musttail)) return test_2_callee (i + 1, b); /* { dg-error "cannot tail-call: " } */
+}
+
+extern void setjmp (void);
+void
+test_3 (void)
+{
+ __attribute__((musttail)) return setjmp (); /* { dg-error "cannot tail-call: " } */
+}
+
+extern float f7(void);
+
+int
+test_6 (void)
+{
+ __attribute__((musttail)) return f7(); /* { dg-error "cannot tail-call: " } */
+}
@@ -0,0 +1,17 @@
+/* { dg-do compile { target { musttail && { c || c++11 } } } } */
+
+struct box { char field[64]; int i; };
+
+struct box __attribute__((noinline,noclone,noipa))
+returns_struct (int i)
+{
+ struct box b;
+ b.i = i * i;
+ return b;
+}
+
+int __attribute__((noinline,noclone))
+test_1 (int i)
+{
+ __attribute__((musttail)) return returns_struct (i * 5).i; /* { dg-error "cannot tail-call: " } */
+}
@@ -0,0 +1,14 @@
+/* { dg-do compile { target { musttail && { c || c++11 } } } } */
+/* { dg-additional-options "-fdelayed-branch" { target sparc*-*-* } } */
+
+void __attribute__((noipa)) f() {}
+
+void f2()
+{
+ __attribute__((__musttail__)) return f2();
+}
+
+void f3()
+{
+ __attribute__((__musttail__)) return f();
+}
@@ -0,0 +1,17 @@
+/* { dg-do compile { target { musttail && { c || c++11 } } } } */
+
+float f1(void);
+
+int f2(void)
+{
+ __attribute__((musttail)) return f1 (); /* { dg-error "changed after call" } */
+}
+
+
+int f3(int *);
+
+int f4(void)
+{
+ int x;
+ __attribute__((musttail)) return f3(&x); /* { dg-error "\(refers to locals|other reasons\)" } */
+}
@@ -0,0 +1,15 @@
+/* { dg-do compile { target { struct_musttail && { c || c++11 } } } } */
+/* { dg-additional-options "-fdelayed-branch" { target sparc*-*-* } } */
+
+struct str
+{
+ int a, b;
+};
+struct str
+cstruct (int x)
+{
+ if (x < 10)
+ L:
+ __attribute__((musttail)) return cstruct (x + 1); /* { dg-warning "'musttail' attribute ignored" "" { target c } } */
+ return ((struct str){ x, 0 });
+}
@@ -0,0 +1,5 @@
+/* { dg-do compile { target { c || c++11 } } } */
+void f(void)
+{
+ __attribute__((musttail)) return; /* { dg-error "cannot tail-call.*return value must be a call" } */
+}
@@ -0,0 +1,90 @@
+/* PR tree-optimization/118430 */
+/* { dg-do compile { target musttail } } */
+/* { dg-options "-O2 -fdump-tree-optimized" } */
+/* { dg-final { scan-tree-dump-times " \[^\n\r]* = bar \\\(\[^\n\r]\*\\\); \\\[tail call\\\] \\\[must tail call\\\]" 1 "optimized" } } */
+/* { dg-final { scan-tree-dump-times " \[^\n\r]* = freddy \\\(\[^\n\r]\*\\\); \\\[tail call\\\] \\\[must tail call\\\]" 1 "optimized" } } */
+/* { dg-final { scan-tree-dump-not " (?:bar|freddy) \\\(\[^\n\r]\*\\\); \\\[tail call\\\]" "optimized" } } */
+
+__attribute__ ((noipa)) void
+foo (int x)
+{
+ (void) x;
+}
+
+__attribute__ ((noinline)) int
+bar (int x)
+{
+ foo (x);
+ return 1;
+}
+
+__attribute__ ((noinline)) int
+baz (int *x)
+{
+ foo (*x);
+ return 2;
+}
+
+__attribute__((noipa)) int
+qux (int x)
+{
+ {
+ int v;
+ foo (x);
+ baz (&v);
+ }
+ __attribute__((musttail))
+ return bar (x);
+}
+
+__attribute__((noipa)) int
+corge (int x)
+{
+ {
+ int v;
+ foo (x);
+ baz (&v);
+ }
+ return bar (x) + 1;
+}
+
+__attribute__ ((noinline)) float
+freddy (int x)
+{
+ foo (x);
+ return 1.75f;
+}
+
+__attribute__((noipa)) float
+garply (int x)
+{
+ {
+ int v;
+ foo (x);
+ baz (&v);
+ }
+ __attribute__((musttail))
+ return freddy (x);
+}
+
+__attribute__((noipa)) float
+quux (int x)
+{
+ {
+ int v;
+ foo (x);
+ baz (&v);
+ }
+ return freddy (x) + 0.25f;
+}
+
+int v;
+
+int
+main ()
+{
+ qux (v);
+ corge (v);
+ garply (v);
+ quux (v);
+}
@@ -0,0 +1,45 @@
+/* { dg-do compile } */
+/* { dg-options "-W -Wall" } */
+
+void bar (void);
+
+void
+foo (int x)
+{
+ __attribute__((musttail)); /* { dg-warning "empty declaration" "" { target c } } */
+ /* { dg-warning "attributes at the beginning of statement are ignored" "" { target c++ } .-1 } */
+ if (x == 1)
+ __attribute__((musttail (1))) return bar (); /* { dg-error "'musttail' attribute does not take any arguments" } */
+ if (x == 2)
+ __attribute__((musttail (1, "", 3))) return bar (); /* { dg-error "'musttail' attribute does not take any arguments" } */
+ if (x == 3)
+ [[gnu::musttail (1)]] return bar (); /* { dg-error "'musttail' attribute does not take any arguments" } */
+ /* { dg-error "expected" "" { target c } .-1 } */
+ if (x == 4)
+ [[gnu::musttail (1, "", 3)]] return bar (); /* { dg-error "'musttail' attribute does not take any arguments" } */
+ /* { dg-error "expected" "" { target c } .-1 } */
+ if (x == 3)
+ [[clang::musttail (1)]] return bar (); /* { dg-error "'musttail' attribute does not take any arguments" } */
+ /* { dg-error "expected" "" { target c } .-1 } */
+ if (x == 4)
+ [[clang::musttail (1, "", 3)]] return bar (); /* { dg-error "'musttail' attribute does not take any arguments" } */
+ /* { dg-error "expected" "" { target c } .-1 } */
+ if (x == 5)
+ __attribute__((fallthrough, musttail)) return bar (); /* { dg-warning "attribute 'musttail' mixed with other attributes on 'return' statement" "" { target c } } */
+ /* { dg-warning "attributes at the beginning of statement are ignored" "" { target c++ } .-1 } */
+
+ if (x == 6)
+ [[fallthrough]] [[gnu::musttail]] return bar (); /* { dg-warning "'fallthrough' attribute ignored" "" { target c } } */
+ /* { dg-warning "attributes at the beginning of statement are ignored" "" { target c++ } .-1 } */
+ if (x == 7)
+ [[clang::musttail, fallthrough]] return bar (); /* { dg-warning "'fallthrough' attribute ignored" "" { target c } } */
+ /* { dg-warning "attributes at the beginning of statement are ignored" "" { target c++ } .-1 } */
+ if (x == 8)
+ __attribute__((musttail, musttail)) return bar ();
+ if (x == 9)
+ [[gnu::musttail, gnu::musttail]] return bar ();
+ if (x == 10)
+ [[clang::musttail]] [[clang::musttail]] return bar ();
+ if (x == 11)
+ [[clang::musttail]] [[gnu::musttail]] return bar ();
+}
@@ -0,0 +1,21 @@
+/* { dg-do compile } */
+/* { dg-options "" } */
+
+#if !__has_attribute (musttail)
+#error missing musttail attribute
+#endif
+#ifdef __cplusplus
+#if !__has_cpp_attribute (gnu::musttail)
+#error missing gnu::musttail attribute
+#endif
+#if !__has_cpp_attribute (clang::musttail)
+#error missing clang::musttail attribute
+#endif
+#else
+#if !__has_c_attribute (gnu::musttail)
+#error missing gnu::musttail attribute
+#endif
+#if !__has_c_attribute (clang::musttail)
+#error missing clang::musttail attribute
+#endif
+#endif
@@ -0,0 +1,60 @@
+/* { dg-do compile { target { struct_musttail } } } */
+/* { dg-require-effective-target external_musttail } */
+/* A lot of architectures will not build this due to PR115606 and PR115607 */
+/* { dg-options "-std=gnu++11" } */
+/* { dg-additional-options "-fdelayed-branch" { target sparc*-*-* } } */
+
+class Foo {
+public:
+ int a, b;
+ Foo(int a, int b) : a(a), b(b) {}
+};
+
+Foo __attribute__((noinline,noclone,noipa))
+callee (int i)
+{
+ return Foo(i, i+1);
+}
+
+Foo __attribute__((noinline,noclone,noipa))
+caller (int i)
+{
+ __attribute__((__musttail__)) return callee (i + 1);
+}
+
+template<typename T>
+T __attribute__((noinline,noclone,noipa)) foo (T i)
+{
+ return i + 1;
+}
+
+int
+caller2 (int k)
+{
+ __attribute__((__musttail__)) return foo<int>(1);
+}
+
+template<typename T>
+T caller3 (T v)
+{
+ __attribute__((__musttail__)) return foo<T>(v);
+}
+
+int call3(int i)
+{
+ __attribute__((__musttail__)) return caller3<int>(i + 1);
+}
+
+struct Bar {
+ int a;
+ Bar(int a) : a(a) {}
+ Bar operator+(Bar o) { return Bar(a + o.a); }
+};
+
+#if __OPTIMIZE__ >= 1
+Bar
+caller4 (Bar k)
+{
+ __attribute__((__musttail__)) return caller3<Bar>(Bar(99));
+}
+#endif
@@ -0,0 +1,10 @@
+/* { dg-do compile { target { musttail } } } */
+/* { dg-options "-std=gnu++11" } */
+/* { dg-additional-options "-fdelayed-branch" { target sparc*-*-* } } */
+
+extern void foo();
+
+void f() noexcept
+{
+ __attribute__((musttail)) return foo(); /* { dg-error "call may throw exception that does not propagate" } */
+}
@@ -0,0 +1,40 @@
+/* { dg-do compile { target { musttail } } } */
+/* { dg-options "-std=gnu++11" } */
+/* { dg-additional-options "-fdelayed-branch" { target sparc*-*-* } } */
+
+template <class T> T f();
+
+double g() { __attribute__((musttail)) return f<int>(); } /* { dg-error "cannot tail-call" } */
+
+template <class T>
+__attribute__((noinline, noclone, noipa))
+T g1() { __attribute__((musttail)) return f<T>(); } /* { dg-error "target is not able" "" { target { ! external_musttail } } } */
+
+template <class T>
+__attribute__((noinline, noclone, noipa))
+T g2() { __attribute__((musttail)) return f<T>(); } /* { dg-error "target is not able" "" { target { ! external_musttail } } } */
+
+template <class T>
+__attribute__((noinline, noclone, noipa))
+/* Would work with -O1. */
+T g3() { __attribute__((musttail)) return f<T>(); } /* { dg-error "cannot tail-call" } */
+
+template <class T>
+__attribute__((noinline, noclone, noipa))
+T g4() { __attribute__((musttail)) return f<double>(); } /* { dg-error "cannot tail-call" } */
+
+class C
+{
+ double x;
+public:
+ C(double x) : x(x) {}
+ ~C() { asm("":::"memory"); }
+};
+
+int main()
+{
+ g1<int>();
+ g2<double>();
+ g3<C>();
+ g4<int>();
+}
@@ -0,0 +1,33 @@
+/* { dg-do compile { target { musttail } } } */
+/* { dg-options "-std=gnu++11" } */
+/* { dg-additional-options "-fdelayed-branch" { target sparc*-*-* } } */
+
+template <class T> T f();
+
+class C
+{
+ double x;
+public:
+ C(double x) : x(x) {}
+ ~C() { asm("":::"memory"); }
+ operator int() { return x; }
+};
+
+template <class T>
+__attribute__((noinline, noclone, noipa))
+T g5() { __attribute__((musttail)) return f<C> (); } /* { dg-error "cannot tail-call" } */
+
+C h();
+
+__attribute__((noinline, noclone, noipa))
+int g6() { __attribute__((__musttail__)) return h (); } /* { dg-error "cannot tail-call" } */
+
+__attribute__((noinline, noclone, noipa))
+C g7() { __attribute__((musttail)) return h (); } /* { dg-error "cannot tail-call" } */
+
+int main()
+{
+ g5<int> ();
+ g6 ();
+ g7 ();
+}