On Thu, 27 Mar 2025, Jakub Jelinek wrote:

> Hi!
> 
> The following (first) testcase is accepted by clang (if clang::musttail)
> and rejected by gcc, because we discover the call is noreturn and then bail
> out because we don't want noreturn tailcalls.
> The general reason not to support noreturn tail calls is for cases like
> abort where we want nicer backtrace, but if user asks explicitly to
> musttail a call which either is explicitly noreturn or is implicitly
> determined to be noreturn, I don't see a reason why we couldn't do that.
> Both for tail calls and tail recursions.
> 
> An alternative would be to keep rejecting musttail to explicit noreturn,
> but not actually implicitly mark anything as noreturn if it has any musttail
> calls.  But it is unclear how we could do that, such marking is I think done
> typically before IPA and e.g. for LTO we won't know whether some other TU
> could have musttail calls to it.  And keeping around both explicit and
> implicit noreturn bits would be ugly.  Well, I guess we could differentiate
> between presence of noreturn/_Noreturn attributes and just ECF_NORETURN
> without those, but then tailc would still need to support it, just error out
> if it was explicit.
> 
> Bootstrapped/regtested on x86_64-linux and i686-linux, ok for trunk?

OK.

> 2025-03-27  Jakub Jelinek  <ja...@redhat.com>
> 
>       PR tree-optimization/119483
>       * tree-tailcall.cc (find_tail_calls): Handle noreturn musttail
>       calls.
>       (eliminate_tail_call): Likewise.
>       (tree_optimize_tail_calls_1): If cfun->has_musttail and
>       diag_musttail, handle also basic blocks with no successors
>       with noreturn musttail calls.
>       * calls.cc (can_implement_as_sibling_call_p): Allow ECF_NORETURN
>       calls if they are musttail calls.
> 
>       * c-c++-common/pr119483-1.c: New test.
>       * c-c++-common/pr119483-2.c: New test.
> 
> --- gcc/tree-tailcall.cc.jj   2025-03-25 09:36:31.502487137 +0100
> +++ gcc/tree-tailcall.cc      2025-03-27 16:18:30.017898249 +0100
> @@ -484,7 +484,8 @@ find_tail_calls (basic_block bb, struct
>    size_t idx;
>    tree var;
>  
> -  if (!single_succ_p (bb))
> +  if (!single_succ_p (bb)
> +      && (EDGE_COUNT (bb->succs) || !cfun->has_musttail || !diag_musttail))
>      {
>        /* If there is an abnormal edge assume it's the only extra one.
>        Tolerate that case so that we can give better error messages
> @@ -605,7 +606,7 @@ find_tail_calls (basic_block bb, struct
>    /* If the call might throw an exception that wouldn't propagate out of
>       cfun, we can't transform to a tail or sibling call (82081).  */
>    if ((stmt_could_throw_p (cfun, stmt)
> -       && !stmt_can_throw_external (cfun, stmt)) || !single_succ_p (bb))
> +       && !stmt_can_throw_external (cfun, stmt)) || EDGE_COUNT (bb->succs) > 
> 1)
>    {
>      if (stmt == last_stmt)
>        maybe_error_musttail (call,
> @@ -760,10 +761,12 @@ find_tail_calls (basic_block bb, struct
>    a = NULL_TREE;
>    auto_bitmap to_move_defs;
>    auto_vec<gimple *> to_move_stmts;
> +  bool is_noreturn
> +    = EDGE_COUNT (bb->succs) == 0 && gimple_call_noreturn_p (call);
>  
>    abb = bb;
>    agsi = gsi;
> -  while (1)
> +  while (!is_noreturn)
>      {
>        tree tmp_a = NULL_TREE;
>        tree tmp_m = NULL_TREE;
> @@ -844,7 +847,22 @@ find_tail_calls (basic_block bb, struct
>      }
>  
>    /* See if this is a tail call we can handle.  */
> -  ret_var = gimple_return_retval (as_a <greturn *> (stmt));
> +  if (is_noreturn)
> +    {
> +      tree rettype = TREE_TYPE (TREE_TYPE (current_function_decl));
> +      tree calltype = TREE_TYPE (gimple_call_fntype (call));
> +      if (!VOID_TYPE_P (rettype)
> +       && !useless_type_conversion_p (rettype, calltype))
> +     {
> +       maybe_error_musttail (call,
> +                             _("call and return value are different"),
> +                             diag_musttail);
> +       return;
> +     }
> +      ret_var = NULL_TREE;
> +    }
> +  else
> +    ret_var = gimple_return_retval (as_a <greturn *> (stmt));
>  
>    /* We may proceed if there either is no return value, or the return value
>       is identical to the call's return or if the return decl is an empty type
> @@ -1153,24 +1171,32 @@ eliminate_tail_call (struct tailcall *t,
>       gsi_prev (&gsi2);
>      }
>  
> -  /* Number of executions of function has reduced by the tailcall.  */
> -  e = single_succ_edge (gsi_bb (t->call_gsi));
> +  if (gimple_call_noreturn_p (as_a <gcall *> (stmt)))
> +    {
> +      e = make_edge (gsi_bb (t->call_gsi), first, EDGE_FALLTHRU);
> +      e->probability = profile_probability::always ();
> +    }
> +  else
> +    {
> +      /* Number of executions of function has reduced by the tailcall.  */
> +      e = single_succ_edge (gsi_bb (t->call_gsi));
>  
> -  profile_count count = e->count ();
> +      profile_count count = e->count ();
>  
> -  /* When profile is inconsistent and the recursion edge is more frequent
> -     than number of executions of functions, scale it down, so we do not end
> -     up with 0 executions of entry block.  */
> -  if (count >= ENTRY_BLOCK_PTR_FOR_FN (cfun)->count)
> -    count = ENTRY_BLOCK_PTR_FOR_FN (cfun)->count.apply_scale (7, 8);
> -  decrease_profile (EXIT_BLOCK_PTR_FOR_FN (cfun), count);
> -  decrease_profile (ENTRY_BLOCK_PTR_FOR_FN (cfun), count);
> -  if (e->dest != EXIT_BLOCK_PTR_FOR_FN (cfun))
> -    decrease_profile (e->dest, count);
> -
> -  /* Replace the call by a jump to the start of function.  */
> -  e = redirect_edge_and_branch (single_succ_edge (gsi_bb (t->call_gsi)),
> -                             first);
> +      /* When profile is inconsistent and the recursion edge is more frequent
> +      than number of executions of functions, scale it down, so we do not
> +      end up with 0 executions of entry block.  */
> +      if (count >= ENTRY_BLOCK_PTR_FOR_FN (cfun)->count)
> +     count = ENTRY_BLOCK_PTR_FOR_FN (cfun)->count.apply_scale (7, 8);
> +      decrease_profile (EXIT_BLOCK_PTR_FOR_FN (cfun), count);
> +      decrease_profile (ENTRY_BLOCK_PTR_FOR_FN (cfun), count);
> +      if (e->dest != EXIT_BLOCK_PTR_FOR_FN (cfun))
> +     decrease_profile (e->dest, count);
> +
> +      /* Replace the call by a jump to the start of function.  */
> +      e = redirect_edge_and_branch (single_succ_edge (gsi_bb (t->call_gsi)),
> +                                 first);
> +    }
>    gcc_assert (e);
>    PENDING_STMT (e) = NULL;
>  
> @@ -1295,6 +1321,18 @@ tree_optimize_tail_calls_1 (bool opt_tai
>       find_tail_calls (e->src, &tailcalls, only_musttail, opt_tailcalls,
>                        diag_musttail);
>      }
> +  if (cfun->has_musttail && diag_musttail)
> +    {
> +      basic_block bb;
> +      FOR_EACH_BB_FN (bb, cfun)
> +     if (EDGE_COUNT (bb->succs) == 0)
> +       if (gimple *c = last_nondebug_stmt (bb))
> +         if (is_gimple_call (c)
> +             && gimple_call_must_tail_p (as_a <gcall *> (c))
> +             && gimple_call_noreturn_p (as_a <gcall *> (c)))
> +           find_tail_calls (bb, &tailcalls, only_musttail, opt_tailcalls,
> +                            diag_musttail);
> +    }
>  
>    if (live_vars)
>      {
> --- gcc/calls.cc.jj   2025-01-02 11:23:09.062620925 +0100
> +++ gcc/calls.cc      2025-03-27 16:01:49.034485816 +0100
> @@ -2568,7 +2568,7 @@ can_implement_as_sibling_call_p (tree ex
>        maybe_complain_about_tail_call (exp, _("callee returns twice"));
>        return false;
>      }
> -  if (flags & ECF_NORETURN)
> +  if ((flags & ECF_NORETURN) && !CALL_EXPR_MUST_TAIL_CALL (exp))
>      {
>        maybe_complain_about_tail_call (exp, _("callee does not return"));
>        return false;
> --- gcc/testsuite/c-c++-common/pr119483-1.c.jj        2025-03-27 
> 16:06:07.291979915 +0100
> +++ gcc/testsuite/c-c++-common/pr119483-1.c   2025-03-27 16:06:48.418421612 
> +0100
> @@ -0,0 +1,29 @@
> +/* PR tree-optimization/119483 */
> +/* { dg-do compile { target musttail } } */
> +/* { dg-options "-O2 -fdump-tree-optimized" } */
> +/* { dg-final { scan-tree-dump-times "bar\[.a-z0-9]* \\\(\[^\n\r]*\\\); 
> \\\[tail call\\\] \\\[must tail call\\\]" 1 "optimized" } } */
> +/* { dg-final { scan-tree-dump-times "baz \\\(\[^\n\r]*\\\); \\\[tail 
> call\\\] \\\[must tail call\\\]" 1 "optimized" } } */
> +
> +[[gnu::noreturn]] extern void foo (void);
> +
> +[[gnu::noinline]] static int
> +bar (int x)
> +{
> +  (void) x;
> +  foo ();
> +  return 0;
> +}
> +
> +[[gnu::noipa]] int
> +baz (int x)
> +{
> +  return x + 42;
> +}
> +
> +int
> +qux (int x)
> +{
> +  if (x == 1)
> +    [[gnu::musttail]] return bar (1);
> +  [[gnu::musttail]] return baz (x);
> +}
> --- gcc/testsuite/c-c++-common/pr119483-2.c.jj        2025-03-27 
> 16:20:02.751639870 +0100
> +++ gcc/testsuite/c-c++-common/pr119483-2.c   2025-03-27 16:21:10.885715305 
> +0100
> @@ -0,0 +1,12 @@
> +/* PR tree-optimization/119483 */
> +/* { dg-do compile { target musttail } } */
> +/* { dg-options "-O2" } */
> +
> +[[noreturn]] int
> +foo (int x)
> +{
> +  if (x > 10)
> +    [[gnu::musttail]] return foo (x - 1);    /* { dg-warning "function 
> declared 'noreturn' has a 'return' statement" } */
> +  for (;;)
> +    ;
> +}
> 
>       Jakub
> 
> 

-- 
Richard Biener <rguent...@suse.de>
SUSE Software Solutions Germany GmbH,
Frankenstrasse 146, 90461 Nuernberg, Germany;
GF: Ivo Totev, Andrew McDonald, Werner Knoblich; (HRB 36809, AG Nuernberg)

Reply via email to