Hi! As discussed here and in bugzilla, [[clang::musttail]] attribute in clang not just strongly asks for tail call or error, but changes behavior. To quote: https://clang.llvm.org/docs/AttributeReference.html#musttail "The lifetimes of all local variables and function parameters end immediately before the call to the function. This means that it is undefined behaviour to pass a pointer or reference to a local variable to the called function, which is not the case without the attribute. Clang will emit a warning in common cases where this happens."
The GCC behavior was just to error if we can't prove the musttail callee could not have dereferenced escaped pointers to local vars or parameters of the caller. That is still the case for variables with non-trivial destruction (even in clang), like vars with C++ non-trivial destructors or variables with cleanup attribute. The following patch changes the behavior to match that of clang, for all of [[clang::musttail]], [[gnu::musttail]] and __attribute__((musttail)). clang 20 actually added warning for some cases of it in https://github.com/llvm/llvm-project/pull/109255 but it is under -Wreturn-stack-address warning. Now, gcc doesn't have that warning, but -Wreturn-local-addr instead, and IMHO it is better to have this under new warnings, because this isn't about returning local address, but about passing it to a musttail call, or maybe escaping to a musttail call. And perhaps users will appreciate they can control it separately as well. The patch introduces 2 new warnings. -Wmusttail-local-addr which is turn on by default and warns for the always dumb cases of passing an address of a local variable or parameter to musttail call's argument. And then -Wmaybe-musttail-local-addr which is only diagnosed if -Wmusttail-local-addr was not diagnosed and diagnoses at most one (so that we don't emit 100s of warnings for one call if 100s of vars can escape) case where an address of a local var could have escaped to the musttail call. This is less severe, the code doesn't have to be obviously wrong, so the warning is only enabled in -Wextra. And I've adjusted also the documentation for this change and addition of new warnings. Bootstrapped/regtested on x86_64-linux and i686-linux (on top of the just posted patch), ok for trunk? 2025-03-25 Jakub Jelinek <ja...@redhat.com> PR ipa/119376 * common.opt (Wmusttail-local-addr, Wmaybe-musttail-local-addr): New. * tree-tailcall.cc (suitable_for_tail_call_opt_p): Don't fail for TREE_ADDRESSABLE PARM_DECLs for musttail calls if diag_musttail. Emit -Wmusttail-local-addr warnings. (maybe_error_musttail): Use gimple_location instead of directly accessing location member. (find_tail_calls): For musttail calls if diag_musttail, don't fail if address of local could escape to the call, instead emit -Wmaybe-musttail-local-addr warnings. Emit -Wmaybe-musttail-local-addr warnings also for address taken parameters. * common.opt.urls: Regenerate. * doc/extend.texi (musttail statement attribute): Clarify local variables without non-trivial destruction are considered out of scope before the tail call instruction. * doc/invoke.texi (-Wno-musttail-local-addr, -Wmaybe-musttail-local-addr): Document. * c-c++-common/musttail8.c: Expect a warning rather than error in one case. (f4): Add int * argument. * c-c++-common/musttail15.c: Don't disallow for C++98. * c-c++-common/musttail16.c: Likewise. * c-c++-common/musttail17.c: Likewise. * c-c++-common/musttail18.c: Likewise. * c-c++-common/musttail19.c: Likewise. Expect a warning rather than error in one case. (f4): Add int * argument. * c-c++-common/musttail20.c: Don't disallow for C++98. * c-c++-common/musttail21.c: Likewise. * c-c++-common/musttail28.c: New test. * c-c++-common/musttail29.c: New test. * c-c++-common/musttail30.c: New test. * c-c++-common/musttail31.c: New test. * g++.dg/ext/musttail1.C: New test. * g++.dg/ext/musttail2.C: New test. * g++.dg/ext/musttail3.C: New test. --- gcc/common.opt.jj 2025-03-11 09:18:21.589135804 +0100 +++ gcc/common.opt 2025-03-24 15:43:38.756165596 +0100 @@ -693,6 +693,14 @@ Does nothing. Preserved for backward com Wmissing-noreturn Common Warning Alias(Wsuggest-attribute=noreturn) +Wmusttail-local-addr +Common Var(warn_musttail_local_addr) Init(1) Warning +Warn about passing a pointer/reference to a local or temporary variable to a musttail call argument. + +Wmaybe-musttail-local-addr +Common Var(warn_maybe_musttail_local_addr) Warning EnabledBy(Wextra) +Warn about pointer/reference to a local or temporary variable possibly escaping to a musttail call. + Wodr Common Var(warn_odr_violations) Init(1) Warning Warn about some C++ One Definition Rule violations during link time optimization. --- gcc/tree-tailcall.cc.jj 2025-03-24 12:51:56.271628242 +0100 +++ gcc/tree-tailcall.cc 2025-03-24 17:25:34.864608837 +0100 @@ -206,14 +206,48 @@ suitable_for_tail_call_opt_p (gcall *cal /* ??? It is OK if the argument of a function is taken in some cases, but not in all cases. See PR15387 and PR19616. Revisit for 4.1. */ - for (param = DECL_ARGUMENTS (current_function_decl); - param; - param = DECL_CHAIN (param)) - if (TREE_ADDRESSABLE (param)) + if (!diag_musttail || !gimple_call_must_tail_p (call)) + for (param = DECL_ARGUMENTS (current_function_decl); + param; param = DECL_CHAIN (param)) + if (TREE_ADDRESSABLE (param)) + { + maybe_error_musttail (call, _("address of caller arguments taken"), + diag_musttail); + return false; + } + + if (diag_musttail + && gimple_call_must_tail_p (call) + && warn_musttail_local_addr) + for (unsigned int i = 0; i < gimple_call_num_args (call); i++) { - maybe_error_musttail (call, _("address of caller arguments taken"), - diag_musttail); - return false; + tree arg = gimple_call_arg (call, i); + if (!POINTER_TYPE_P (TREE_TYPE (arg))) + continue; + if (TREE_CODE (arg) == ADDR_EXPR) + { + arg = get_base_address (TREE_OPERAND (arg, 0)); + if (auto_var_in_fn_p (arg, current_function_decl)) + { + if (TREE_CODE (arg) == LABEL_DECL) + warning_at (gimple_location (call), OPT_Wmusttail_local_addr, + "address of label passed to %<musttail%> " + "call argument"); + else if (TREE_CODE (arg) == PARM_DECL) + warning_at (gimple_location (call), OPT_Wmusttail_local_addr, + "address of parameter %qD passed to " + "%<musttail%> call argument", arg); + else if (!DECL_ARTIFICIAL (arg) && DECL_NAME (arg)) + warning_at (gimple_location (call), OPT_Wmusttail_local_addr, + "address of automatic variable %qD passed to " + "%<musttail%> call argument", arg); + else + warning_at (gimple_location (call), OPT_Wmusttail_local_addr, + "address of local variable passed to " + "%<musttail%> call argument"); + suppress_warning (call, OPT_Wmaybe_musttail_local_addr); + } + } } return true; @@ -443,7 +477,7 @@ maybe_error_musttail (gcall *call, const { if (gimple_call_must_tail_p (call) && diag_musttail) { - error_at (call->location, "cannot tail-call: %s", err); + error_at (gimple_location (call), "cannot tail-call: %s", err); /* Avoid another error. ??? If there are multiple reasons why tail calls fail it might be useful to report them all to avoid whack-a-mole for the user. But currently there is too much @@ -727,6 +761,19 @@ find_tail_calls (basic_block bb, struct { if (!VAR_P (var)) { + if (diag_musttail && gimple_call_must_tail_p (call)) + { + auto opt = OPT_Wmaybe_musttail_local_addr; + if (!warning_suppressed_p (call, + opt)) + { + warning_at (gimple_location (call), opt, + "address of local variable can escape to " + "%<musttail%> call"); + suppress_warning (call, opt); + } + continue; + } if (local_live_vars) BITMAP_FREE (local_live_vars); maybe_error_musttail (call, @@ -739,6 +786,24 @@ find_tail_calls (basic_block bb, struct unsigned int *v = live_vars->get (DECL_UID (var)); if (bitmap_bit_p (local_live_vars, *v)) { + if (diag_musttail && gimple_call_must_tail_p (call)) + { + auto opt = OPT_Wmaybe_musttail_local_addr; + if (!warning_suppressed_p (call, opt)) + { + if (!DECL_ARTIFICIAL (var) && DECL_NAME (var)) + warning_at (gimple_location (call), opt, + "address of automatic variable %qD " + "can escape to %<musttail%> call", + var); + else + warning_at (gimple_location (call), opt, + "address of local variable can escape " + "to %<musttail%> call"); + suppress_warning (call, opt); + } + continue; + } BITMAP_FREE (local_live_vars); maybe_error_musttail (call, _("call invocation refers to locals"), @@ -748,6 +813,22 @@ find_tail_calls (basic_block bb, struct } } } + if (diag_musttail + && gimple_call_must_tail_p (call) + && !warning_suppressed_p (call, OPT_Wmaybe_musttail_local_addr)) + for (tree param = DECL_ARGUMENTS (current_function_decl); + param; param = DECL_CHAIN (param)) + if (may_be_aliased (param) + && (ref_maybe_used_by_stmt_p (call, param, false) + || call_may_clobber_ref_p (call, param, false))) + { + auto opt = OPT_Wmaybe_musttail_local_addr; + warning_at (gimple_location (call), opt, + "address of parameter %qD can escape to " + "%<musttail%> call", param); + suppress_warning (call, opt); + break; + } if (local_live_vars) BITMAP_FREE (local_live_vars); --- gcc/common.opt.urls.jj 2025-03-17 21:52:12.239087023 +0100 +++ gcc/common.opt.urls 2025-03-25 08:16:01.534591090 +0100 @@ -157,6 +157,12 @@ UrlSuffix(gcc/Warning-Options.html#index Wmissing-noreturn UrlSuffix(gcc/Warning-Options.html#index-Wmissing-noreturn) +Wmusttail-local-addr +UrlSuffix(gcc/Warning-Options.html#index-Wno-musttail-local-addr) + +Wmaybe-musttail-local-addr +UrlSuffix(gcc/Warning-Options.html#index-Wmaybe-musttail-local-addr) + Wodr UrlSuffix(gcc/Warning-Options.html#index-Wno-odr) --- gcc/doc/extend.texi.jj 2025-03-24 08:34:40.897113826 +0100 +++ gcc/doc/extend.texi 2025-03-24 17:01:12.382583006 +0100 @@ -9282,10 +9282,51 @@ __attribute__((musttail)) return bar(); If the compiler cannot generate a @code{musttail} tail call it will report 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 -improve the success of tail calls. +The user asserts for @code{musttail} tail calls that lifetime of automatic +variables, function parameters and temporaries (unless they have non-trivial +destruction) can end before the actual call instruction and that any access +to those from inside of the called function results is considered undefined +behavior. Enabling @option{-O1} or @option{-O2} can improve the success of +tail calls. + +@smallexample +int foo (int *); +void bar (int *); +struct S @{ S (); ~S (); int s; @}; + +int +baz (int *x) +@{ + if (*x == 1) + @{ + int a = 42; + /* The call will be tail called (would not be without the + attribute), dereferencing the pointer in the callee is + undefined behavior and there will be a warning emitted + for this by default (@option{-Wmusttail-local-addr}). */ + [[gnu::musttail]] return foo (&a); + @} + else if (*x == 2) + @{ + int a = 42; + bar (&a); + /* The call will be tail called (would not be without the + attribute), if bar stores the pointer anywhere, dereferencing + it in foo will be undefined behavior and there will be a warning + emitted for this with @option{-Wextra}, which implies + @option{-Wmaybe-musttail-local-addr}. */ + [[gnu::musttail]] return foo (nullptr); + @} + else + @{ + S s; + /* The s variable requires non-trivial destruction which ought + to be performed after the foo call returns, so this will + be rejected. */ + [[gnu::musttail]] return foo (&s.s); + @} +@} +@end smallexample @end table @node Attribute Syntax --- gcc/doc/invoke.texi.jj 2025-03-24 10:25:08.838095213 +0100 +++ gcc/doc/invoke.texi 2025-03-24 17:17:03.581591062 +0100 @@ -394,7 +394,8 @@ Objective-C and Objective-C++ Dialects}. -Wmemset-elt-size -Wmemset-transposed-args -Wmisleading-indentation -Wmissing-attributes -Wmissing-braces -Wmissing-field-initializers -Wmissing-format-attribute --Wmissing-include-dirs -Wmissing-noreturn -Wno-missing-profile +-Wmissing-include-dirs -Wmissing-noreturn -Wmusttail-local-addr +-Wmaybe-musttail-local-addr -Wno-missing-profile -Wno-multichar -Wmultistatement-macros -Wnonnull -Wnonnull-compare -Wnormalized=@r{[}none@r{|}id@r{|}nfc@r{|}nfkc@r{]} -Wnull-dereference -Wno-odr @@ -6967,6 +6968,55 @@ is only active when @option{-fdelete-nul which is enabled by optimizations in most targets. The precision of the warnings depends on the optimization options used. +@opindex Wno-musttail-local-addr +@opindex -Wmusttail-local-addr +@item -Wno-musttail-local-addr +Do not warn about passing a pointer (or in C++, a reference) to a +local variable or label to argument of a @code{musttail} call. Those +variables go out of scope before the tail call instruction. + +@opindex Wmaybe-musttail-local-addr +@opindex -Wno-maybe-musttail-local-addr +@item -Wmaybe-musttail-local-addr +Warn when address of a local variable can escape to a @code{musttail} +call, unless it goes out of scope already before the @code{musttail} +call. + +@smallexample +int foo (int *); + +int +bar (int *x) +@{ + if (x[0] == 1) + @{ + int a = 42; + foo (&a); + /* Without the @code{musttail} attribute this call would not + be tail called, because address of the @code{a} variable escapes + and the second foo call could dereference it. With the attribute + the local variables are assumed to go out of scope immediately + before the tail call instruction and the compiler warns about + this. */ + [[gnu::musttail]] return foo (nullptr); + @} + else + @{ + @{ + int a = 42; + foo (&a); + @} + /* The @code{a} variable isn't already in scope, so even when it + escaped, even without @code{musttail} attribute it would be + undefined behavior to dereference it and the compiler could + turn this into a tail call. No warning is diagnosed here. */ + [[gnu::musttail]] return foo (nullptr); + @} +@} +@end smallexample + +This warning is enabled by @option{-Wextra}. + @opindex Wnrvo @opindex Wno-nrvo @item -Wnrvo @r{(C++ and Objective-C++ only)} --- gcc/testsuite/c-c++-common/musttail8.c.jj 2024-09-04 20:09:14.656418328 +0200 +++ gcc/testsuite/c-c++-common/musttail8.c 2025-03-25 07:15:45.621064703 +0100 @@ -10,8 +10,9 @@ int f2(void) int f3(int *); -int f4(void) +int f4(int *p) { int x; - [[gnu::musttail]] return f3(&x); /* { dg-error "\(refers to locals|other reasons\)" } */ + (void) p; + [[gnu::musttail]] return f3(&x); /* { dg-warning "address of automatic variable 'x' passed to 'musttail' call argument" } */ } --- gcc/testsuite/c-c++-common/musttail15.c.jj 2025-03-18 18:51:09.887550337 +0100 +++ gcc/testsuite/c-c++-common/musttail15.c 2025-03-24 16:25:20.868063539 +0100 @@ -1,4 +1,4 @@ -/* { dg-do compile { target { musttail && { c || c++11 } } } } */ +/* { dg-do compile { target musttail } } */ /* { dg-additional-options "-fdelayed-branch" { target sparc*-*-* } } */ int __attribute__((noinline,noclone,noipa)) --- gcc/testsuite/c-c++-common/musttail16.c.jj 2025-03-18 18:51:09.887550337 +0100 +++ gcc/testsuite/c-c++-common/musttail16.c 2025-03-24 16:25:27.521972762 +0100 @@ -1,4 +1,4 @@ -/* { dg-do compile { target { musttail && { c || c++11 } } } } */ +/* { dg-do compile { target musttail } } */ struct box { char field[256]; int i; }; --- gcc/testsuite/c-c++-common/musttail17.c.jj 2025-03-18 18:51:09.887550337 +0100 +++ gcc/testsuite/c-c++-common/musttail17.c 2025-03-24 16:25:32.598903495 +0100 @@ -1,4 +1,4 @@ -/* { dg-do compile { target { musttail && { c || c++11 } } } } */ +/* { dg-do compile { target musttail } } */ struct box { char field[64]; int i; }; --- gcc/testsuite/c-c++-common/musttail18.c.jj 2025-03-18 18:51:09.887550337 +0100 +++ gcc/testsuite/c-c++-common/musttail18.c 2025-03-24 16:25:38.849818219 +0100 @@ -1,4 +1,4 @@ -/* { dg-do compile { target { musttail && { c || c++11 } } } } */ +/* { dg-do compile { target musttail } } */ /* { dg-additional-options "-fdelayed-branch" { target sparc*-*-* } } */ void __attribute__((noipa)) f() {} --- gcc/testsuite/c-c++-common/musttail19.c.jj 2025-03-18 21:22:20.981022951 +0100 +++ gcc/testsuite/c-c++-common/musttail19.c 2025-03-25 07:16:02.152836434 +0100 @@ -1,4 +1,4 @@ -/* { dg-do compile { target { musttail && { c || c++11 } } } } */ +/* { dg-do compile { target musttail } } */ float f1(void); @@ -10,8 +10,9 @@ int f2(void) int f3(int *); -int f4(void) +int f4(int *p) { int x; - __attribute__((musttail)) return f3(&x); /* { dg-error "\(refers to locals|other reasons\)" } */ + (void) p; + __attribute__((musttail)) return f3(&x); /* { dg-warning "address of automatic variable 'x' passed to 'musttail' call argument" } */ } --- gcc/testsuite/c-c++-common/musttail20.c.jj 2025-03-18 21:22:20.981022951 +0100 +++ gcc/testsuite/c-c++-common/musttail20.c 2025-03-25 07:17:29.025646504 +0100 @@ -1,4 +1,4 @@ -/* { dg-do compile { target { struct_musttail && { c || c++11 } } } } */ +/* { dg-do compile { target struct_musttail } } */ /* { dg-additional-options "-fdelayed-branch" { target sparc*-*-* } } */ struct str --- gcc/testsuite/c-c++-common/musttail21.c.jj 2025-03-18 18:51:09.887550337 +0100 +++ gcc/testsuite/c-c++-common/musttail21.c 2025-03-24 16:26:01.103514611 +0100 @@ -1,4 +1,4 @@ -/* { dg-do compile { target { c || c++11 } } } */ +/* { dg-do compile { target musttail } } */ void f(void) { __attribute__((musttail)) return; /* { dg-error "cannot tail-call.*return value must be a call" } */ --- gcc/testsuite/c-c++-common/musttail28.c.jj 2025-03-24 16:28:16.076667404 +0100 +++ gcc/testsuite/c-c++-common/musttail28.c 2025-03-24 16:50:35.196320248 +0100 @@ -0,0 +1,108 @@ +/* { dg-do compile { target { musttail && { c || c++11 } } } } */ + +int foo (int, void *); +int bar (int, int *); +struct S { int a, b, c; }; +struct T { int d; struct S e; }; + +int +baz (int x, void *y) +{ + [[gnu::musttail]] return bar (2, &x); /* { dg-warning "address of parameter 'x' passed to 'musttail' call argument" } */ +} + +int +qux (int x, void *y) +{ + __label__ lab; + lab:; + if (*(int *) y == 1) + [[gnu::musttail]] return foo (1, &&lab); /* { dg-warning "address of label passed to 'musttail' call argument" } */ + if (x == 1) + [[gnu::musttail]] return foo (3, 0); + else if (x == 2) + { + { + int a = 42; + bar (4, &a); + } + [[gnu::musttail]] return bar (5, 0); + } + else if (x == 3) + { + int a = 42; + bar (4, &a); + [[gnu::musttail]] return bar (6, 0); + } + else if (x == 4) + { + int a = 42; + [[gnu::musttail]] return bar (7, &a); /* { dg-warning "address of automatic variable 'a' passed to 'musttail' call argument" } */ + } + else if (x == 5) + { + struct T b; + [[gnu::musttail]] return bar (8, &b.e.b); /* { dg-warning "address of automatic variable 'b' passed to 'musttail' call argument" } */ + } + else if (x == 6) + { + struct T b; + bar (9, &b.e.a); + [[gnu::musttail]] return bar (10, 0); + } + else if (x == 7) + { + { + struct T b; + bar (9, &b.e.a); + } + [[gnu::musttail]] return bar (11, 0); + } + else if (x == 8) + { + { + int a = 42; + bar (4, &a); + } + [[gnu::musttail]] return foo (12, 0); + } + else if (x == 9) + { + int a = 42; + bar (4, &a); + [[gnu::musttail]] return foo (13, 0); + } + else if (x == 10) + { + int a = 42; + [[gnu::musttail]] return foo (14, &a); /* { dg-warning "address of automatic variable 'a' passed to 'musttail' call argument" } */ + } + else if (x == 11) + { + struct T b; + [[gnu::musttail]] return foo (15, &b.e.b); /* { dg-warning "address of automatic variable 'b' passed to 'musttail' call argument" } */ + } + else if (x == 12) + { + struct T b; + bar (9, &b.e.a); + [[gnu::musttail]] return foo (16, 0); + } + else if (x == 13) + { + { + struct T b; + bar (9, &b.e.a); + } + [[gnu::musttail]] return foo (17, 0); + } + return 0; +} + +int +corge (int x, void *y) +{ + if (*(int *) y == 1) + bar (18, &x); + [[gnu::musttail]] return bar (2, 0); +} --- gcc/testsuite/c-c++-common/musttail29.c.jj 2025-03-24 16:51:06.549890269 +0100 +++ gcc/testsuite/c-c++-common/musttail29.c 2025-03-24 17:29:04.734742858 +0100 @@ -0,0 +1,109 @@ +/* { dg-do compile { target { musttail && { c || c++11 } } } } */ +/* { dg-options "-O2 -Wmusttail-local-addr" } */ + +int foo (int, void *); +int bar (int, int *); +struct S { int a, b, c; }; +struct T { int d; struct S e; }; + +int +baz (int x, void *y) +{ + [[gnu::musttail]] return bar (2, &x); /* { dg-warning "address of parameter 'x' passed to 'musttail' call argument" } */ +} + +int +qux (int x, void *y) +{ + __label__ lab; + lab:; + if (*(int *) y == 1) + [[gnu::musttail]] return foo (1, &&lab); /* { dg-warning "address of label passed to 'musttail' call argument" } */ + if (x == 1) + [[gnu::musttail]] return foo (3, 0); + else if (x == 2) + { + { + int a = 42; + bar (4, &a); + } + [[gnu::musttail]] return bar (5, 0); + } + else if (x == 3) + { + int a = 42; + bar (4, &a); + [[gnu::musttail]] return bar (6, 0); + } + else if (x == 4) + { + int a = 42; + [[gnu::musttail]] return bar (7, &a); /* { dg-warning "address of automatic variable 'a' passed to 'musttail' call argument" } */ + } + else if (x == 5) + { + struct T b; + [[gnu::musttail]] return bar (8, &b.e.b); /* { dg-warning "address of automatic variable 'b' passed to 'musttail' call argument" } */ + } + else if (x == 6) + { + struct T b; + bar (9, &b.e.a); + [[gnu::musttail]] return bar (10, 0); + } + else if (x == 7) + { + { + struct T b; + bar (9, &b.e.a); + } + [[gnu::musttail]] return bar (11, 0); + } + else if (x == 8) + { + { + int a = 42; + bar (4, &a); + } + [[gnu::musttail]] return foo (12, 0); + } + else if (x == 9) + { + int a = 42; + bar (4, &a); + [[gnu::musttail]] return foo (13, 0); + } + else if (x == 10) + { + int a = 42; + [[gnu::musttail]] return foo (14, &a); /* { dg-warning "address of automatic variable 'a' passed to 'musttail' call argument" } */ + } + else if (x == 11) + { + struct T b; + [[gnu::musttail]] return foo (15, &b.e.b); /* { dg-warning "address of automatic variable 'b' passed to 'musttail' call argument" } */ + } + else if (x == 12) + { + struct T b; + bar (9, &b.e.a); + [[gnu::musttail]] return foo (16, 0); + } + else if (x == 13) + { + { + struct T b; + bar (9, &b.e.a); + } + [[gnu::musttail]] return foo (17, 0); + } + return 0; +} + +int +corge (int x, void *y) +{ + if (*(int *) y == 1) + bar (18, &x); + [[gnu::musttail]] return bar (2, 0); +} --- gcc/testsuite/c-c++-common/musttail30.c.jj 2025-03-24 16:51:30.638559918 +0100 +++ gcc/testsuite/c-c++-common/musttail30.c 2025-03-24 17:28:36.421129505 +0100 @@ -0,0 +1,109 @@ +/* { dg-do compile { target { musttail && { c || c++11 } } } } */ +/* { dg-options "-Wextra" } */ + +int foo (int, void *); +int bar (int, int *); +struct S { int a, b, c; }; +struct T { int d; struct S e; }; + +int +baz (int x, void *y) +{ + [[gnu::musttail]] return bar (2, &x); /* { dg-warning "address of parameter 'x' passed to 'musttail' call argument" } */ +} + +int +qux (int x, void *y) +{ + __label__ lab; + lab:; + if (*(int *) y == 1) + [[gnu::musttail]] return foo (1, &&lab); /* { dg-warning "address of label passed to 'musttail' call argument" } */ + if (x == 1) + [[gnu::musttail]] return foo (3, 0); + else if (x == 2) + { + { + int a = 42; + bar (4, &a); + } + [[gnu::musttail]] return bar (5, 0); + } + else if (x == 3) + { + int a = 42; + bar (4, &a); + [[gnu::musttail]] return bar (6, 0); /* { dg-warning "address of automatic variable 'a' can escape to 'musttail' call" } */ + } + else if (x == 4) + { + int a = 42; + [[gnu::musttail]] return bar (7, &a); /* { dg-warning "address of automatic variable 'a' passed to 'musttail' call argument" } */ + } + else if (x == 5) + { + struct T b; + [[gnu::musttail]] return bar (8, &b.e.b); /* { dg-warning "address of automatic variable 'b' passed to 'musttail' call argument" } */ + } + else if (x == 6) + { + struct T b; + bar (9, &b.e.a); + [[gnu::musttail]] return bar (10, 0); /* { dg-warning "address of automatic variable 'b' can escape to 'musttail' call" } */ + } + else if (x == 7) + { + { + struct T b; + bar (9, &b.e.a); + } + [[gnu::musttail]] return bar (11, 0); + } + else if (x == 8) + { + { + int a = 42; + bar (4, &a); + } + [[gnu::musttail]] return foo (12, 0); + } + else if (x == 9) + { + int a = 42; + bar (4, &a); + [[gnu::musttail]] return foo (13, 0); /* { dg-warning "address of automatic variable 'a' can escape to 'musttail' call" } */ + } + else if (x == 10) + { + int a = 42; + [[gnu::musttail]] return foo (14, &a); /* { dg-warning "address of automatic variable 'a' passed to 'musttail' call argument" } */ + } + else if (x == 11) + { + struct T b; + [[gnu::musttail]] return foo (15, &b.e.b); /* { dg-warning "address of automatic variable 'b' passed to 'musttail' call argument" } */ + } + else if (x == 12) + { + struct T b; + bar (9, &b.e.a); + [[gnu::musttail]] return foo (16, 0); /* { dg-warning "address of automatic variable 'b' can escape to 'musttail' call" } */ + } + else if (x == 13) + { + { + struct T b; + bar (9, &b.e.a); + } + [[gnu::musttail]] return foo (17, 0); + } + return 0; +} + +int +corge (int x, void *y) +{ + if (*(int *) y == 1) + bar (18, &x); + [[gnu::musttail]] return bar (2, 0); /* { dg-warning "address of parameter 'x' can escape to 'musttail' call" } */ +} --- gcc/testsuite/c-c++-common/musttail31.c.jj 2025-03-24 17:28:45.044011753 +0100 +++ gcc/testsuite/c-c++-common/musttail31.c 2025-03-24 17:29:24.356474904 +0100 @@ -0,0 +1,109 @@ +/* { dg-do compile { target { musttail && { c || c++11 } } } } */ +/* { dg-options "-O2 -Wmaybe-musttail-local-addr" } */ + +int foo (int, void *); +int bar (int, int *); +struct S { int a, b, c; }; +struct T { int d; struct S e; }; + +int +baz (int x, void *y) +{ + [[gnu::musttail]] return bar (2, &x); /* { dg-warning "address of parameter 'x' passed to 'musttail' call argument" } */ +} + +int +qux (int x, void *y) +{ + __label__ lab; + lab:; + if (*(int *) y == 1) + [[gnu::musttail]] return foo (1, &&lab); /* { dg-warning "address of label passed to 'musttail' call argument" } */ + if (x == 1) + [[gnu::musttail]] return foo (3, 0); + else if (x == 2) + { + { + int a = 42; + bar (4, &a); + } + [[gnu::musttail]] return bar (5, 0); + } + else if (x == 3) + { + int a = 42; + bar (4, &a); + [[gnu::musttail]] return bar (6, 0); /* { dg-warning "address of automatic variable 'a' can escape to 'musttail' call" } */ + } + else if (x == 4) + { + int a = 42; + [[gnu::musttail]] return bar (7, &a); /* { dg-warning "address of automatic variable 'a' passed to 'musttail' call argument" } */ + } + else if (x == 5) + { + struct T b; + [[gnu::musttail]] return bar (8, &b.e.b); /* { dg-warning "address of automatic variable 'b' passed to 'musttail' call argument" } */ + } + else if (x == 6) + { + struct T b; + bar (9, &b.e.a); + [[gnu::musttail]] return bar (10, 0); /* { dg-warning "address of automatic variable 'b' can escape to 'musttail' call" } */ + } + else if (x == 7) + { + { + struct T b; + bar (9, &b.e.a); + } + [[gnu::musttail]] return bar (11, 0); + } + else if (x == 8) + { + { + int a = 42; + bar (4, &a); + } + [[gnu::musttail]] return foo (12, 0); + } + else if (x == 9) + { + int a = 42; + bar (4, &a); + [[gnu::musttail]] return foo (13, 0); /* { dg-warning "address of automatic variable 'a' can escape to 'musttail' call" } */ + } + else if (x == 10) + { + int a = 42; + [[gnu::musttail]] return foo (14, &a); /* { dg-warning "address of automatic variable 'a' passed to 'musttail' call argument" } */ + } + else if (x == 11) + { + struct T b; + [[gnu::musttail]] return foo (15, &b.e.b); /* { dg-warning "address of automatic variable 'b' passed to 'musttail' call argument" } */ + } + else if (x == 12) + { + struct T b; + bar (9, &b.e.a); + [[gnu::musttail]] return foo (16, 0); /* { dg-warning "address of automatic variable 'b' can escape to 'musttail' call" } */ + } + else if (x == 13) + { + { + struct T b; + bar (9, &b.e.a); + } + [[gnu::musttail]] return foo (17, 0); + } + return 0; +} + +int +corge (int x, void *y) +{ + if (*(int *) y == 1) + bar (18, &x); + [[gnu::musttail]] return bar (2, 0); /* { dg-warning "address of parameter 'x' can escape to 'musttail' call" } */ +} --- gcc/testsuite/g++.dg/ext/musttail1.C.jj 2025-03-24 17:32:26.872982473 +0100 +++ gcc/testsuite/g++.dg/ext/musttail1.C 2025-03-24 17:44:23.137198626 +0100 @@ -0,0 +1,38 @@ +// PR ipa/119376 +// { dg-do compile { target { musttail && c++11 } } } +// { dg-options "-Wmaybe-musttail-local-addr" } + +int foo (int &); +int bar (int &&); +int corge (int *); + +int +baz (int &x) +{ + if (x == 1) + [[gnu::musttail]] return foo (x); + if (x == 2) + { + int a = 42; + [[gnu::musttail]] return foo (a); // { dg-warning "address of automatic variable 'a' passed to 'musttail' call argument" } + } + if (x == 3) + { + int a = 42; + foo (a); + [[gnu::musttail]] return foo (x); // { dg-warning "address of automatic variable 'a' can escape to 'musttail' call" } + } + return 0; +} + +int +qux (int &&x) +{ + [[gnu::musttail]] return bar (x + 1); // { dg-warning "address of local variable passed to 'musttail' call argument" } +} + +int +freddy (int x) +{ + [[gnu::musttail]] return foo (x); // { dg-warning "address of parameter 'x' passed to 'musttail' call argument" } +} --- gcc/testsuite/g++.dg/ext/musttail2.C.jj 2025-03-24 17:44:33.236060668 +0100 +++ gcc/testsuite/g++.dg/ext/musttail2.C 2025-03-24 17:44:50.476825149 +0100 @@ -0,0 +1,38 @@ +// PR ipa/119376 +// { dg-do compile { target { musttail && c++11 } } } +// { dg-options "-Wextra" } + +int foo (int &); +int bar (int &&); +int corge (int *); + +int +baz (int &x) +{ + if (x == 1) + [[clang::musttail]] return foo (x); + if (x == 2) + { + int a = 42; + [[clang::musttail]] return foo (a); // { dg-warning "address of automatic variable 'a' passed to 'musttail' call argument" } + } + if (x == 3) + { + int a = 42; + foo (a); + [[clang::musttail]] return foo (x); // { dg-warning "address of automatic variable 'a' can escape to 'musttail' call" } + } + return 0; +} + +int +qux (int &&x) +{ + [[clang::musttail]] return bar (x + 1); // { dg-warning "address of local variable passed to 'musttail' call argument" } +} + +int +freddy (int x) +{ + [[clang::musttail]] return foo (x); // { dg-warning "address of parameter 'x' passed to 'musttail' call argument" } +} --- gcc/testsuite/g++.dg/ext/musttail3.C.jj 2025-03-24 17:45:02.607659434 +0100 +++ gcc/testsuite/g++.dg/ext/musttail3.C 2025-03-24 17:45:29.278295095 +0100 @@ -0,0 +1,37 @@ +// PR ipa/119376 +// { dg-do compile { target { musttail && c++11 } } } + +int foo (int &); +int bar (int &&); +int corge (int *); + +int +baz (int &x) +{ + if (x == 1) + [[gnu::musttail]] return foo (x); + if (x == 2) + { + int a = 42; + [[gnu::musttail]] return foo (a); // { dg-warning "address of automatic variable 'a' passed to 'musttail' call argument" } + } + if (x == 3) + { + int a = 42; + foo (a); + [[gnu::musttail]] return foo (x); + } + return 0; +} + +int +qux (int &&x) +{ + [[gnu::musttail]] return bar (x + 1); // { dg-warning "address of local variable passed to 'musttail' call argument" } +} + +int +freddy (int x) +{ + [[gnu::musttail]] return foo (x); // { dg-warning "address of parameter 'x' passed to 'musttail' call argument" } +} Jakub