diff mbox series

tailc: Don't fail musttail calls if they use or could use local arguments, instead warn [PR119376]

Message ID Z+JccoSNxYWZIPz5@tucnak
State New
Headers show
Series tailc: Don't fail musttail calls if they use or could use local arguments, instead warn [PR119376] | expand

Commit Message

Jakub Jelinek March 25, 2025, 7:34 a.m. UTC
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  <jakub@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.


	Jakub

Comments

Jason Merrill March 25, 2025, 3:06 p.m. UTC | #1
On 3/25/25 3:34 AM, Jakub Jelinek wrote:
> 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.

I don't think this is a significantly different case from 
-Wreturn-local-addr; in both cases we are passing a local address out at 
the same time as the stack frame goes away, the only difference is 
whether it's passed by return or argument.  I don't think that 
difference justifies using a different flag.

If others agree with you I don't mind going along, just wanted to make 
my case.

> 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.

I agree that this should be a different flag.

Jason
Jakub Jelinek March 25, 2025, 3:21 p.m. UTC | #2
On Tue, Mar 25, 2025 at 11:06:05AM -0400, Jason Merrill wrote:
> > 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.
> 
> I don't think this is a significantly different case from
> -Wreturn-local-addr; in both cases we are passing a local address out at the
> same time as the stack frame goes away, the only difference is whether it's
> passed by return or argument.  I don't think that difference justifies using
> a different flag.
> 
> If others agree with you I don't mind going along, just wanted to make my
> case.

Both those warnings are enabled by default, so the exact naming of the
warning flags matters only to users who would like to silence it for some
strange reason (and they will get the option in the warning printed, so it
is a cut and paste in that case for them).
Having two different ones allows it to be disabled separately.
And while both are related, the -Wreturn-local-addr name doesn't match what
this new warning warns about, so it would need to be explained in the
documentation that it isn't just about returning local addresses but also about
passing local addresses to musttail which is related.

Anyway, I can live with it if others agree on -Wreturn-local-addr plus
-Wmaybe-musttail-local-addr (or some other name).

	Jakub
Alexander Monakov March 25, 2025, 4:43 p.m. UTC | #3
Hello,

FWIW I think Clang made a mistake in bending semantics in a way that is clearly
misaligned with the general design of C and C++, where a language-native, so to
speak, solution was available: introduce a scope for the local variables to
indicate that they cannot escape to the intended tailcall:

void foo(int v)
{
  {
    int a;
    capture(&a);
  }
  tailcall(v); // this cannot refer to 'a', even though it escaped earlier
}

I think this would be easily teachable to users who need [[tailcall]].

I wonder if Clang folks would be open to a dialogue about undoing this
misdesign. I'd rather not see it propagated into GCC.

Thanks.
Alexander
Jakub Jelinek March 25, 2025, 4:58 p.m. UTC | #4
On Tue, Mar 25, 2025 at 07:43:28PM +0300, Alexander Monakov wrote:
> FWIW I think Clang made a mistake in bending semantics in a way that is clearly
> misaligned with the general design of C and C++, where a language-native, so to
> speak, solution was available: introduce a scope for the local variables to
> indicate that they cannot escape to the intended tailcall:
> 
> void foo(int v)
> {
>   {
>     int a;
>     capture(&a);
>   }
>   tailcall(v); // this cannot refer to 'a', even though it escaped earlier
> }
> 
> I think this would be easily teachable to users who need [[tailcall]].
> 
> I wonder if Clang folks would be open to a dialogue about undoing this
> misdesign. I'd rather not see it propagated into GCC.

I'm not excited about it either, but they had introduced the attribute with
this behavior already 3.5 years ago and it is used in the wild, we should
have complained before that did that :(.
And having significantly different behavior for attribute with the same name
(especially [[clang::musttail]], but also the __attribute__((musttail)) form
which is supposed to be gnu::musttail but they handle it like
clang::musttail) would be confusing to users as well.

For the
void bar (int *);
void
foo (int *x)
{
  int a;
  [[clang::musttail]] return bar (&a);
}
case they now (newly) warn by default and the posted patch warns by default
too, for the other case I've put the warning into -Wextra but am open to
move it to -Wall.
Adding compound statement with the locals is definitely what users can then
do to silence that warning; unfortunately one can't do that with function
arguments if their addresses escape.

	Jakub
Andi Kleen March 25, 2025, 5:26 p.m. UTC | #5
On Tue, Mar 25, 2025 at 07:43:28PM +0300, Alexander Monakov wrote:
> Hello,
> 
> FWIW I think Clang made a mistake in bending semantics in a way that is clearly
> misaligned with the general design of C and C++, where a language-native, so to
> speak, solution was available: introduce a scope for the local variables to
> indicate that they cannot escape to the intended tailcall:
> 
> void foo(int v)
> {
>   {
>     int a;
>     capture(&a);
>   }
>   tailcall(v); // this cannot refer to 'a', even though it escaped earlier
> }

This is not a universal fix? e.g. consider a case like

void foo(int v)
{
   int a;
   capture(&a); 
   if (condition)
     return tailcall(v);
   // do something with a
}

-Andix>
Alexander Monakov March 25, 2025, 5:45 p.m. UTC | #6
On Tue, 25 Mar 2025, Andi Kleen wrote:

> On Tue, Mar 25, 2025 at 07:43:28PM +0300, Alexander Monakov wrote:
> > Hello,
> > 
> > FWIW I think Clang made a mistake in bending semantics in a way that is clearly
> > misaligned with the general design of C and C++, where a language-native, so to
> > speak, solution was available: introduce a scope for the local variables to
> > indicate that they cannot escape to the intended tailcall:
> > 
> > void foo(int v)
> > {
> >   {
> >     int a;
> >     capture(&a);
> >   }
> >   tailcall(v); // this cannot refer to 'a', even though it escaped earlier
> > }
> 
> This is not a universal fix? e.g. consider a case like
> 
> void foo(int v)
> {
>    int a;
>    capture(&a); 
>    if (condition)
>      return tailcall(v);
>    // do something with a
> }

This can be rewritten as

void foo(int v)
{
  {
    int a;
    capture(&a);
    if (condition)
      goto tail_position;
    // do something with a
  }
tail_position:
  tailcall(v);
}

or with 'do { ... if (...) break; ...} while (0)' when one prefers that to goto.

Alexander
Andi Kleen March 25, 2025, 6:07 p.m. UTC | #7
> This can be rewritten as
> 
> void foo(int v)
> {
>   {
>     int a;
>     capture(&a);
>     if (condition)
>       goto tail_position;
>     // do something with a
>   }
> tail_position:
>   tailcall(v);
> }
> 
> or with 'do { ... if (...) break; ...} while (0)' when one prefers that to goto.

This could get really ugly in more complex functions though with large
scale transformation needed. Not sure that is something I would recommend
to anyone.

I don't know if the clang people considered such a case, but I can see
a point for their semantics.


-Andi
Jakub Jelinek April 1, 2025, 10:48 a.m. UTC | #8
Hi!

On Tue, Mar 25, 2025 at 08:34:10AM +0100, Jakub Jelinek wrote:
> 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.

I'd like to ping the
https://gcc.gnu.org/pipermail/gcc-patches/2025-March/679182.html
patch.
I know it is quite controversial and if clang wouldn't be the first
to implement this I'd certainly not go that way; I am willing to change
the warning option names or move the maybe one from -Wextra to -Wall
if there is agreement on that.  Unfortunately there seems to be
quite a lot of code in the wild that uses this attribute already
and without this patch GCC 15 will simply not be able to compile that
(whether it is firefox (skia etc.), protobuf, ...).

The only other option is IMHO to drop the musttail attribute support
for GCC 15 or name it differently with different semantics.
But not sure projects in the wild will like to annotate their calls
with two different musttail like attributes if they satisfy behavior
of both.

	Jakub
Andi Kleen April 1, 2025, 3:57 p.m. UTC | #9
> I'd like to ping the
> https://gcc.gnu.org/pipermail/gcc-patches/2025-March/679182.html
> patch.
> I know it is quite controversial and if clang wouldn't be the first
> to implement this I'd certainly not go that way; I am willing to change
> the warning option names or move the maybe one from -Wextra to -Wall
> if there is agreement on that.  Unfortunately there seems to be
> quite a lot of code in the wild that uses this attribute already
> and without this patch GCC 15 will simply not be able to compile that
> (whether it is firefox (skia etc.), protobuf, ...).

FWIW the patch looks good to me.
> 
> The only other option is IMHO to drop the musttail attribute support
> for GCC 15 or name it differently with different semantics.
> But not sure projects in the wild will like to annotate their calls
> with two different musttail like attributes if they satisfy behavior
> of both.

Projects that are serious about the performance will also need
to use __attribute__((no_callee_saved_registers)) and that is different
than the clang equivalent. So some ifdefery is needed anyways.

-Andi
Richard Biener April 2, 2025, 8:32 a.m. UTC | #10
On Tue, 1 Apr 2025, Jakub Jelinek wrote:

> Hi!
> 
> On Tue, Mar 25, 2025 at 08:34:10AM +0100, Jakub Jelinek wrote:
> > 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.
> 
> I'd like to ping the
> https://gcc.gnu.org/pipermail/gcc-patches/2025-March/679182.html
> patch.
> I know it is quite controversial and if clang wouldn't be the first
> to implement this I'd certainly not go that way; I am willing to change
> the warning option names or move the maybe one from -Wextra to -Wall
> if there is agreement on that.  Unfortunately there seems to be
> quite a lot of code in the wild that uses this attribute already
> and without this patch GCC 15 will simply not be able to compile that
> (whether it is firefox (skia etc.), protobuf, ...).
> 
> The only other option is IMHO to drop the musttail attribute support
> for GCC 15 or name it differently with different semantics.
> But not sure projects in the wild will like to annotate their calls
> with two different musttail like attributes if they satisfy behavior
> of both.

OK.

I wonder if we can amend the documentation to suggest to end lifetime
of variables explicitly by proper scoping?

Thanks,
Richard.
diff mbox series

Patch

--- 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" }
+}