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)