> Am 02.04.2025 um 18:10 schrieb Jakub Jelinek <ja...@redhat.com>:
>
> Hi!
>
> The following testcases FAIL, because EH cleanup is performed only before
> IPA and then right before musttail pass.
> At -O2 etc. (except for -O0/-Og) we handle musttail calls in the tailc
> pass though, and we can fail at that point because the calls might appear
> to throw internal exceptions which just don't do anything interesting
> (perhaps have debug statements or clobber statements in them) before they
> continue with resume of the exception (i.e. throw it externally).
>
> As Richi said in the PR (and I agree) that moving passes is risky at this
> point, the following patch instead teaches the tail{r,c} and musttail
> passes to deal with such extra EDGE_EH edges.
>
> It is fairly simple thing, if we see an EDGE_EH edge from the call we
> just look up where it lands and if there are no
> non-debug/non-clobber/non-label statements before resx which throws
> externally, such edge can be ignored for tail call optimization or
> tail recursion. At other spots I just need to avoid using
> single_succ/single_succ_edge because the bb might have another edge -
> EDGE_EH.
>
> To make this less risky, this is done solely for the musttail calls for now.
>
> Bootstrapped/regtested on x86_64-linux and i686-linux, ok for trunk?
Ok
Richard
>
> 2025-04-02 Jakub Jelinek <ja...@redhat.com>
>
> PR tree-optimization/119491
> * tree-tailcall.cc (single_non_eh_succ_edge): New function.
> (independent_of_stmt_p): Use single_non_eh_succ_edge (bb)->dest
> instead of single_succ (bb).
> (empty_eh_cleanup): New function.
> (find_tail_calls): Diagnose throwing of exceptions which do not
> propagate only if there are no EDGE_EH successor edges. If there are
> and the call is musttail, use empty_eh_cleanup to find if the cleanup
> is not empty. If not or the call is not musttail, use different
> diagnostics. Set is_noreturn even if there are successor edges. Use
> single_non_eh_succ_edge (abb) instead of single_succ_edge (abb). Punt
> on internal noreturn calls.
> (decrease_profile): Don't assert 0 or 1 successor edges.
> (eliminate_tail_call): Use
> single_non_eh_succ_edge (gsi_bb (t->call_gsi)) instead of
> single_succ_edge (gsi_bb (t->call_gsi)).
> (tree_optimize_tail_calls_1): Also look into basic blocks with
> single succ edge which is EDGE_EH for noreturn musttail calls.
>
> * g++.dg/opt/musttail3.C: New test.
> * g++.dg/opt/musttail4.C: New test.
> * g++.dg/opt/musttail5.C: New test.
>
> --- gcc/tree-tailcall.cc.jj 2025-04-01 16:47:30.373502796 +0200
> +++ gcc/tree-tailcall.cc 2025-04-02 09:02:35.572760732 +0200
> @@ -219,6 +219,23 @@ suitable_for_tail_call_opt_p (gcall *cal
> return true;
> }
>
> +/* Return single successor edge ignoring EDGE_EH edges. */
> +
> +static edge
> +single_non_eh_succ_edge (basic_block bb)
> +{
> + edge e, ret = NULL;
> + edge_iterator ei;
> + FOR_EACH_EDGE (e, ei, bb->succs)
> + if ((e->flags & EDGE_EH) == 0)
> + {
> + gcc_assert (ret == NULL);
> + ret = e;
> + }
> + gcc_assert (ret);
> + return ret;
> +}
> +
> /* Checks whether the expression EXPR in stmt AT is independent of the
> statement pointed to by GSI (in a sense that we already know EXPR's value
> at GSI). We use the fact that we are only called from the chain of
> @@ -245,7 +262,7 @@ independent_of_stmt_p (tree expr, gimple
> /* Mark the blocks in the chain leading to the end. */
> at_bb = gimple_bb (at);
> call_bb = gimple_bb (gsi_stmt (gsi));
> - for (bb = call_bb; bb != at_bb; bb = single_succ (bb))
> + for (bb = call_bb; bb != at_bb; bb = single_non_eh_succ_edge (bb)->dest)
> bb->aux = &bb->aux;
> bb->aux = &bb->aux;
>
> @@ -289,7 +306,7 @@ independent_of_stmt_p (tree expr, gimple
> }
>
> /* Unmark the blocks. */
> - for (bb = call_bb; bb != at_bb; bb = single_succ (bb))
> + for (bb = call_bb; bb != at_bb; bb = single_non_eh_succ_edge (bb)->dest)
> bb->aux = NULL;
> bb->aux = NULL;
>
> @@ -462,6 +479,33 @@ maybe_error_musttail (gcall *call, const
> }
> }
>
> +/* Return true if there is no real work performed in the exception
> + path starting at BB and it will in the end result in external exception.
> + Search at most CNT basic blocks (so that we don't need to do trivial
> + loop discovery). */
> +static bool
> +empty_eh_cleanup (basic_block bb, int cnt)
> +{
> + if (EDGE_COUNT (bb->succs) > 1)
> + return false;
> +
> + for (gimple_stmt_iterator gsi = gsi_after_labels (bb); !gsi_end_p (gsi);
> + gsi_next (&gsi))
> + {
> + gimple *g = gsi_stmt (gsi);
> + if (is_gimple_debug (g) || gimple_clobber_p (g))
> + continue;
> + if (is_gimple_resx (g) && stmt_can_throw_external (cfun, g))
> + return true;
> + return false;
> + }
> + if (!single_succ_p (bb))
> + return false;
> + if (cnt == 1)
> + return false;
> + return empty_eh_cleanup (single_succ (bb), cnt - 1);
> +}
> +
> /* Argument for compute_live_vars/live_vars_at_stmt and what compute_live_vars
> returns. Computed lazily, but just once for the function. */
> static live_vars_map *live_vars;
> @@ -612,14 +656,36 @@ find_tail_calls (basic_block bb, struct
> if ((stmt_could_throw_p (cfun, stmt)
> && !stmt_can_throw_external (cfun, stmt)) || EDGE_COUNT (bb->succs) >
> 1)
> {
> - if (stmt == last_stmt)
> - maybe_error_musttail (call,
> - _("call may throw exception that does not "
> - "propagate"), diag_musttail);
> - else
> - maybe_error_musttail (call, _("code between call and return"),
> - diag_musttail);
> - return;
> + if (stmt != last_stmt)
> + {
> + maybe_error_musttail (call, _("code between call and return"),
> + diag_musttail);
> + return;
> + }
> +
> + edge e;
> + edge_iterator ei;
> + FOR_EACH_EDGE (e, ei, bb->succs)
> + if (e->flags & EDGE_EH)
> + break;
> +
> + if (!e)
> + {
> + maybe_error_musttail (call,
> + _("call may throw exception that does not "
> + "propagate"), diag_musttail);
> + return;
> + }
> +
> + if (!gimple_call_must_tail_p (call)
> + || !empty_eh_cleanup (e->dest, 20)
> + || EDGE_COUNT (bb->succs) > 2)
> + {
> + maybe_error_musttail (call,
> + _("call may throw exception caught locally "
> + "or perform cleanups"), diag_musttail);
> + return;
> + }
> }
>
> /* If the function returns a value, then at present, the tail call
> @@ -763,8 +829,7 @@ 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);
> + bool is_noreturn = gimple_call_noreturn_p (call);
>
> abb = bb;
> agsi = gsi;
> @@ -776,8 +841,9 @@ find_tail_calls (basic_block bb, struct
>
> while (gsi_end_p (agsi))
> {
> - ass_var = propagate_through_phis (ass_var, single_succ_edge (abb));
> - abb = single_succ (abb);
> + edge e = single_non_eh_succ_edge (abb);
> + ass_var = propagate_through_phis (ass_var, e);
> + abb = e->dest;
> agsi = gsi_start_bb (abb);
> }
>
> @@ -851,6 +917,11 @@ find_tail_calls (basic_block bb, struct
> /* See if this is a tail call we can handle. */
> if (is_noreturn)
> {
> + if (gimple_call_internal_p (call))
> + {
> + maybe_error_musttail (call, _("internal call"), diag_musttail);
> + return;
> + }
> tree rettype = TREE_TYPE (TREE_TYPE (current_function_decl));
> tree calltype = TREE_TYPE (gimple_call_fntype (call));
> if (!VOID_TYPE_P (rettype)
> @@ -1112,11 +1183,6 @@ static void
> decrease_profile (basic_block bb, profile_count count)
> {
> bb->count = bb->count - count;
> - if (!single_succ_p (bb))
> - {
> - gcc_assert (!EDGE_COUNT (bb->succs));
> - return;
> - }
> }
>
> /* Eliminates tail call described by T. TMP_VARS is a list of
> @@ -1181,7 +1247,7 @@ eliminate_tail_call (struct tailcall *t,
> else
> {
> /* Number of executions of function has reduced by the tailcall. */
> - e = single_succ_edge (gsi_bb (t->call_gsi));
> + e = single_non_eh_succ_edge (gsi_bb (t->call_gsi));
>
> profile_count count = e->count ();
>
> @@ -1196,8 +1262,7 @@ eliminate_tail_call (struct tailcall *t,
> 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);
> + e = redirect_edge_and_branch (e, first);
> }
> gcc_assert (e);
> PENDING_STMT (e) = NULL;
> @@ -1362,7 +1427,9 @@ tree_optimize_tail_calls_1 (bool opt_tai
> {
> basic_block bb;
> FOR_EACH_BB_FN (bb, cfun)
> - if (EDGE_COUNT (bb->succs) == 0)
> + if (EDGE_COUNT (bb->succs) == 0
> + || (single_succ_p (bb)
> + && (single_succ_edge (bb)->flags & EDGE_EH)))
> if (gimple *c = last_nondebug_stmt (bb))
> if (is_gimple_call (c)
> && gimple_call_must_tail_p (as_a <gcall *> (c))
> --- gcc/testsuite/g++.dg/opt/musttail3.C.jj 2025-04-01 18:47:10.474945080
> +0200
> +++ gcc/testsuite/g++.dg/opt/musttail3.C 2025-04-01 18:49:27.063068029
> +0200
> @@ -0,0 +1,41 @@
> +// PR tree-optimization/119491
> +// { dg-do compile { target { external_musttail && c++11 } } }
> +// { dg-options "-O2" }
> +
> +struct A {
> + struct B {};
> + A () {}
> +};
> +void qux ();
> +unsigned char v;
> +A w;
> +void foo (A);
> +
> +template <typename T>
> +[[gnu::always_inline]] static inline void
> +bar (int &)
> +{
> +}
> +
> +[[gnu::always_inline]] static inline void
> +baz (int *)
> +{
> + int r = 0;
> + bar<int> (r);
> +}
> +
> +[[gnu::always_inline]] inline void
> +corge (A)
> +{
> + if (v)
> + qux ();
> + [[gnu::musttail]] return foo (w);
> +}
> +
> +void
> +freddy (A)
> +{
> + int t;
> + baz (&t);
> + [[gnu::musttail]] return corge (A{});
> +}
> --- gcc/testsuite/g++.dg/opt/musttail4.C.jj 2025-04-01 19:10:56.389350911
> +0200
> +++ gcc/testsuite/g++.dg/opt/musttail4.C 2025-04-01 19:28:18.285020409
> +0200
> @@ -0,0 +1,35 @@
> +// { dg-do compile { target { external_musttail && c++11 } } }
> +// { dg-options "-O2 -fexceptions" }
> +
> +struct S { ~S (); };
> +volatile int v;
> +struct T { ~T () { v = v + 1; } };
> +struct U { ~U () {} };
> +int foo ();
> +
> +int
> +bar () noexcept
> +{
> + [[gnu::musttail]] return foo (); // { dg-error "cannot tail-call: call
> may throw exception that does not propagate" }
> +}
> +
> +int
> +baz ()
> +{
> + S s;
> + [[gnu::musttail]] return foo (); // { dg-error "cannot tail-call: other
> reasons" }
> +}
> +
> +int
> +qux ()
> +{
> + T t;
> + [[gnu::musttail]] return foo (); // { dg-error "cannot tail-call: other
> reasons" }
> +}
> +
> +int
> +corge ()
> +{
> + U u;
> + [[gnu::musttail]] return foo ();
> +}
> --- gcc/testsuite/g++.dg/opt/musttail5.C.jj 2025-04-01 19:14:50.981127712
> +0200
> +++ gcc/testsuite/g++.dg/opt/musttail5.C 2025-04-01 19:11:25.249954382
> +0200
> @@ -0,0 +1,41 @@
> +// PR tree-optimization/119491
> +// { dg-do compile { target { external_musttail && c++11 } } }
> +// { dg-options "-O2" }
> +
> +struct A {
> + struct B {};
> + A () {}
> +};
> +void qux ();
> +unsigned char v;
> +A w;
> +[[noreturn]] void foo (A);
> +
> +template <typename T>
> +[[gnu::always_inline]] static inline void
> +bar (int &)
> +{
> +}
> +
> +[[gnu::always_inline]] static inline void
> +baz (int *)
> +{
> + int r = 0;
> + bar<int> (r);
> +}
> +
> +[[gnu::always_inline]] inline void
> +corge (A)
> +{
> + if (v)
> + qux ();
> + [[gnu::musttail]] return foo (w);
> +}
> +
> +void
> +freddy (A)
> +{
> + int t;
> + baz (&t);
> + [[gnu::musttail]] return corge (A{});
> +}
>
> Jakub
>