On Tue, 25 Mar 2025, Jakub Jelinek wrote: > Hi! > > The following testcases FAIL because musttail failures are diagnosed > not just in the tailc or musttail passes, but also during the tailr1 > and tailr2. > tailr1 pass is before IPA and in the testcases eh cleanup has not > cleaned up the IL sufficiently yet to make the musttail calls pass, > even tailr2 could be too early. > > The following patch does that only during the tailc pass, and if that > pass is not actually executed, during musttail pass. > To do it only in the tailc pass, I chose to pass a new bool flag, because > while we have the opt_tailcalls argument, it is actually passed by reference > to find_tail_calls and sometimes cleared during that. > musttail calls when the new DIAG_MUSTTAIL flag is not set are handled like > any other calls, we simply silently punt on those if they can't be turned > into tail calls. > > Furthermore, I had to tweak the musttail pass gate. Previously it was > !flag_optimize_sibling_calls && f->has_musttail. The problem is that > gate of tailr and tailc passes is > flag_optimize_sibling_calls != 0 && dbg_cnt (tail_call) > and furthermore, tailc pass is only in the normal optimization queue, > so only if not -O0 or -Og. So when one would use tail_call dbg_cnt > with some limit, or when e.g. using -foptimize-sibling-calls with -O0 or > -Og, nothing would actually diagnose invalid musttail calls or set tail call > flags on those if they are ok. I could insert a new PROP_ flag on whether > musttail has been handled by tailc pass, but given that we have the > cfun->has_musttail flag already and nothing after tailc/musttail passes uses > it, I think it is easier to just clear the flag when musttail failures are > diagnosed and correct ones have [[tail call]] flag added. Expansion will > then only look at the [[tail call]] flag, it could even at the [[must tail > call]] flag, but I don't see a point to check cfun->has_musttail. > > Bootstrapped/regtested on x86_64-linux and i686-linux, ok for trunk?
OK. Thanks, Richard. > 2025-03-25 Jakub Jelinek <ja...@redhat.com> > > PR ipa/119376 > * tree-tailcall.cc (suitable_for_tail_opt_p): Add DIAG_MUSTTAIL > argument, propagate it down to maybe_error_musttail. > (suitable_for_tail_call_opt_p): Likewise. > (maybe_error_musttail): Add DIAG_MUSTTAIL argument. Don't emit error > for gimple_call_must_tail_p calls if it is false. > (find_tail_calls): Add DIAG_MUSTTAIL argument, propagate it down to > maybe_error_musttail, suitable_for_tail_opt_p, > suitable_for_tail_call_opt_p and find_tail_calls calls. > (tree_optimize_tail_calls_1): Add DIAG_MUSTTAIL argument, propagate > it down to find_tail_calls and if set, clear cfun->has_musttail flag > at the end. Rename OPT_MUSTCALL argument to OPT_MUSTTAIL. > (execute_tail_calls): Pass true to DIAG_MUSTTAIL > tree_optimize_tail_calls_1 argument. > (pass_tail_recursion::execute): Pass false to DIAG_MUSTTAIL > tree_optimize_tail_calls_1 argument. > (pass_musttail::gate): Don't test flag_optimize_sibling_calls. > (pass_musttail::execute): Pass true to DIAG_MUSTTAIL > tree_optimize_tail_calls_1 argument. > > * g++.dg/torture/musttail1.C: New test. > * g++.dg/opt/musttail2.C: New test. > > --- gcc/tree-tailcall.cc.jj 2025-01-16 09:27:53.645909094 +0100 > +++ gcc/tree-tailcall.cc 2025-03-24 12:51:56.271628242 +0100 > @@ -139,18 +139,18 @@ static tree m_acc, a_acc; > > static bitmap tailr_arg_needs_copy; > > -static void maybe_error_musttail (gcall *call, const char *err); > +static void maybe_error_musttail (gcall *call, const char *err, bool); > > /* Returns false when the function is not suitable for tail call optimization > from some reason (e.g. if it takes variable number of arguments). CALL > is call to report for. */ > > static bool > -suitable_for_tail_opt_p (gcall *call) > +suitable_for_tail_opt_p (gcall *call, bool diag_musttail) > { > if (cfun->stdarg) > { > - maybe_error_musttail (call, _("caller uses stdargs")); > + maybe_error_musttail (call, _("caller uses stdargs"), diag_musttail); > return false; > } > > @@ -163,7 +163,7 @@ suitable_for_tail_opt_p (gcall *call) > tail call discovery happen. CALL is call to report error for. */ > > static bool > -suitable_for_tail_call_opt_p (gcall *call) > +suitable_for_tail_call_opt_p (gcall *call, bool diag_musttail) > { > tree param; > > @@ -171,7 +171,7 @@ suitable_for_tail_call_opt_p (gcall *cal > sibling call optimizations, but not tail recursion. */ > if (cfun->calls_alloca) > { > - maybe_error_musttail (call, _("caller uses alloca")); > + maybe_error_musttail (call, _("caller uses alloca"), diag_musttail); > return false; > } > > @@ -181,7 +181,8 @@ suitable_for_tail_call_opt_p (gcall *cal > if (targetm_common.except_unwind_info (&global_options) == UI_SJLJ > && current_function_has_exception_handlers ()) > { > - maybe_error_musttail (call, _("caller uses sjlj exceptions")); > + maybe_error_musttail (call, _("caller uses sjlj exceptions"), > + diag_musttail); > return false; > } > > @@ -190,7 +191,7 @@ suitable_for_tail_call_opt_p (gcall *cal > properly in the CFG so that this needn't be special cased. */ > if (cfun->calls_setjmp) > { > - maybe_error_musttail (call, _("caller uses setjmp")); > + maybe_error_musttail (call, _("caller uses setjmp"), diag_musttail); > return false; > } > > @@ -198,7 +199,8 @@ suitable_for_tail_call_opt_p (gcall *cal > that call __builtin_eh_return. */ > if (cfun->calls_eh_return) > { > - maybe_error_musttail (call, _("caller uses __builtin_eh_return")); > + maybe_error_musttail (call, _("caller uses __builtin_eh_return"), > + diag_musttail); > return false; > } > > @@ -209,7 +211,8 @@ suitable_for_tail_call_opt_p (gcall *cal > param = DECL_CHAIN (param)) > if (TREE_ADDRESSABLE (param)) > { > - maybe_error_musttail (call, _("address of caller arguments taken")); > + maybe_error_musttail (call, _("address of caller arguments taken"), > + diag_musttail); > return false; > } > > @@ -436,9 +439,9 @@ propagate_through_phis (tree var, edge e > errors. */ > > static void > -maybe_error_musttail (gcall *call, const char *err) > +maybe_error_musttail (gcall *call, const char *err, bool diag_musttail) > { > - if (gimple_call_must_tail_p (call)) > + if (gimple_call_must_tail_p (call) && diag_musttail) > { > error_at (call->location, "cannot tail-call: %s", err); > /* Avoid another error. ??? If there are multiple reasons why tail > @@ -461,12 +464,13 @@ static live_vars_map *live_vars; > static vec<bitmap_head> live_vars_vec; > > /* Finds tailcalls falling into basic block BB. The list of found tailcalls > is > - added to the start of RET. When ONLY_MUSTTAIL is set only handle musttail. > - Update OPT_TAILCALLS as output parameter. */ > + added to the start of RET. When ONLY_MUSTTAIL is set only handle > musttail. > + Update OPT_TAILCALLS as output parameter. If DIAG_MUSTTAIL, diagnose > + failures for musttail calls. */ > > static void > find_tail_calls (basic_block bb, struct tailcall **ret, bool only_musttail, > - bool &opt_tailcalls) > + bool &opt_tailcalls, bool diag_musttail) > { > tree ass_var = NULL_TREE, ret_var, func, param; > gimple *stmt; > @@ -524,7 +528,7 @@ find_tail_calls (basic_block bb, struct > { > maybe_error_musttail (call, > _("memory reference or volatile after " > - "call")); > + "call"), diag_musttail); > return; > } > ass_var = gimple_call_lhs (call); > @@ -561,15 +565,16 @@ find_tail_calls (basic_block bb, struct > edge_iterator ei; > /* Recurse to the predecessors. */ > FOR_EACH_EDGE (e, ei, bb->preds) > - find_tail_calls (e->src, ret, only_musttail, opt_tailcalls); > + find_tail_calls (e->src, ret, only_musttail, opt_tailcalls, > + diag_musttail); > > return; > } > > - if (!suitable_for_tail_opt_p (call)) > + if (!suitable_for_tail_opt_p (call, diag_musttail)) > return; > > - if (!suitable_for_tail_call_opt_p (call)) > + if (!suitable_for_tail_call_opt_p (call, diag_musttail)) > opt_tailcalls = false; > > /* If the LHS of our call is not just a simple register or local > @@ -587,13 +592,13 @@ find_tail_calls (basic_block bb, struct > && !is_gimple_reg (ass_var) > && !auto_var_in_fn_p (ass_var, cfun->decl)) > { > - maybe_error_musttail (call, _("return value in memory")); > + maybe_error_musttail (call, _("return value in memory"), > diag_musttail); > return; > } > > if (cfun->calls_setjmp) > { > - maybe_error_musttail (call, _("caller uses setjmp")); > + maybe_error_musttail (call, _("caller uses setjmp"), diag_musttail); > return; > } > > @@ -605,9 +610,10 @@ find_tail_calls (basic_block bb, struct > if (stmt == last_stmt) > maybe_error_musttail (call, > _("call may throw exception that does not " > - "propagate")); > + "propagate"), diag_musttail); > else > - maybe_error_musttail (call, _("code between call and return")); > + maybe_error_musttail (call, _("code between call and return"), > + diag_musttail); > return; > } > > @@ -639,7 +645,8 @@ find_tail_calls (basic_block bb, struct > && may_be_aliased (result_decl) > && ref_maybe_used_by_stmt_p (call, result_decl, false)) > { > - maybe_error_musttail (call, _("return value used after call")); > + maybe_error_musttail (call, _("return value used after call"), > + diag_musttail); > return; > } > > @@ -723,7 +730,8 @@ find_tail_calls (basic_block bb, struct > if (local_live_vars) > BITMAP_FREE (local_live_vars); > maybe_error_musttail (call, > - _("call invocation refers to locals")); > + _("call invocation refers to locals"), > + diag_musttail); > return; > } > else > @@ -733,7 +741,8 @@ find_tail_calls (basic_block bb, struct > { > BITMAP_FREE (local_live_vars); > maybe_error_musttail (call, > - _("call invocation refers to locals")); > + _("call invocation refers to locals"), > + diag_musttail); > return; > } > } > @@ -780,7 +789,8 @@ find_tail_calls (basic_block bb, struct > > if (gimple_code (stmt) != GIMPLE_ASSIGN) > { > - maybe_error_musttail (call, _("unhandled code after call")); > + maybe_error_musttail (call, _("unhandled code after call"), > + diag_musttail); > return; > } > > @@ -789,7 +799,8 @@ find_tail_calls (basic_block bb, struct > &tmp_m, &tmp_a, &ass_var, to_move_defs); > if (ret == FAIL || (ret == TRY_MOVE && !tail_recursion)) > { > - maybe_error_musttail (call, _("return value changed after call")); > + maybe_error_musttail (call, _("return value changed after call"), > + diag_musttail); > return; > } > else if (ret == TRY_MOVE) > @@ -864,7 +875,8 @@ find_tail_calls (basic_block bb, struct > if (!ok) > { > maybe_error_musttail (call, > - _("call and return value are different")); > + _("call and return value are different"), > + diag_musttail); > return; > } > } > @@ -874,7 +886,8 @@ find_tail_calls (basic_block bb, struct > if (!tail_recursion && (m || a)) > { > maybe_error_musttail (call, > - _("operations after non tail recursive call")); > + _("operations after non tail recursive call"), > + diag_musttail); > return; > } > > @@ -883,7 +896,7 @@ find_tail_calls (basic_block bb, struct > { > maybe_error_musttail (call, > _("tail recursion with pointers can only use " > - "additions")); > + "additions"), diag_musttail); > return; > } > > @@ -1258,11 +1271,13 @@ create_tailcall_accumulator (const char > } > > /* Optimizes tail calls in the function, turning the tail recursion > - into iteration. When ONLY_MUSTCALL is true only optimize mustcall > - marked calls. */ > + into iteration. When ONLY_MUSTTAIL is true only optimize musttail > + marked calls. When DIAG_MUSTTAIL, diagnose if musttail calls can't > + be tail call optimized. */ > > static unsigned int > -tree_optimize_tail_calls_1 (bool opt_tailcalls, bool only_mustcall) > +tree_optimize_tail_calls_1 (bool opt_tailcalls, bool only_musttail, > + bool diag_musttail) > { > edge e; > bool phis_constructed = false; > @@ -1277,7 +1292,8 @@ tree_optimize_tail_calls_1 (bool opt_tai > /* Only traverse the normal exits, i.e. those that end with return > statement. */ > if (safe_is_a <greturn *> (*gsi_last_bb (e->src))) > - find_tail_calls (e->src, &tailcalls, only_mustcall, opt_tailcalls); > + find_tail_calls (e->src, &tailcalls, only_musttail, opt_tailcalls, > + diag_musttail); > } > > if (live_vars) > @@ -1374,6 +1390,9 @@ tree_optimize_tail_calls_1 (bool opt_tai > if (tailr_arg_needs_copy) > BITMAP_FREE (tailr_arg_needs_copy); > > + if (diag_musttail) > + cfun->has_musttail = false; > + > if (changed) > return TODO_cleanup_cfg | TODO_update_ssa_only_virtuals; > return 0; > @@ -1388,7 +1407,7 @@ gate_tail_calls (void) > static unsigned int > execute_tail_calls (void) > { > - return tree_optimize_tail_calls_1 (true, false); > + return tree_optimize_tail_calls_1 (true, false, true); > } > > namespace { > @@ -1421,7 +1440,7 @@ public: > bool gate (function *) final override { return gate_tail_calls (); } > unsigned int execute (function *) final override > { > - return tree_optimize_tail_calls_1 (false, false); > + return tree_optimize_tail_calls_1 (false, false, false); > } > > }; // class pass_tail_recursion > @@ -1497,15 +1516,15 @@ public: > > /* opt_pass methods: */ > /* This pass is only used when the other tail call pass > - doesn't run to make [[musttail]] still work. But only > + doesn't run to make [[musttail]] still work. But only > run it when there is actually a musttail in the function. */ > bool gate (function *f) final override > { > - return !flag_optimize_sibling_calls && f->has_musttail; > + return f->has_musttail; > } > unsigned int execute (function *) final override > { > - return tree_optimize_tail_calls_1 (true, true); > + return tree_optimize_tail_calls_1 (true, true, true); > } > > }; // class pass_musttail > --- gcc/testsuite/g++.dg/torture/musttail1.C.jj 2025-03-24 > 12:55:09.092982548 +0100 > +++ gcc/testsuite/g++.dg/torture/musttail1.C 2025-03-24 12:56:24.154952628 > +0100 > @@ -0,0 +1,15 @@ > +// PR ipa/119376 > +// { dg-do compile { target musttail } } > +// { dg-additional-options "-ffat-lto-objects -fdump-tree-optimized" } > +/* { dg-final { scan-tree-dump-times " \[^\n\r]* = foo \\\(\[^\n\r]*\\\); > \\\[tail call\\\] \\\[must tail call\\\]" 1 "optimized" } } */ > + > +struct S { int s; }; > +int foo (int); > + > +int > +bar (int a) > +{ > + S b = {a}; > + b.s++; > + [[gnu::musttail]] return foo (a); > +} > --- gcc/testsuite/g++.dg/opt/musttail2.C.jj 2025-03-24 13:27:44.329204196 > +0100 > +++ gcc/testsuite/g++.dg/opt/musttail2.C 2025-03-24 13:28:08.975867389 > +0100 > @@ -0,0 +1,14 @@ > +// PR ipa/119376 > +// { dg-do compile { target musttail } } > +// { dg-options "-O2 -fno-early-inlining -fdump-tree-optimized" } > +// { dg-final { scan-tree-dump-times " \[^\n\r]* = foo \\\(\[^\n\r]*\\\); > \\\[tail call\\\] \\\[must tail call\\\]" 1 "optimized" } } > + > +struct S { S () {} }; > +char *foo (S); > + > +char * > +bar (S) > +{ > + S t; > + [[clang::musttail]] return foo (t); > +} > > 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)