On Wed, 15 Jan 2025, Jakub Jelinek wrote: > Hi! > > When we have return somefn (whatever); where somefn is normally tail > callable and IPA-VRP determines somefn returns a singleton range, VRP > just changes the IL to > somefn (whatever); > return 42; > (or whatever the value in that range is). The introduction of IPA-VRP > return value tracking then effectively regresses the tail call optimization. > This is even more important if the call is [[gnu::musttail]]. > > So, the following patch queries IPA-VRP whether a function returns singleton > range and if so and the value returned is identical to that, marks the > call as [tail call] anyway. If expansion decides it can't use the tail > call, we'll still expand the return 42; or similar statement, and if it > decides it can use the tail call, that part will be ignored and we'll emit > normal tail call.
Interesting idea. I'd have said that for [[gnu::musttail]] we want to disable the IPA transform instead? But I can see that when the user wrote somefn (whatever); return 42; the handling would enable tail-calling, but your patch only handles it in the [[musttail]] failure path. Anyway, I think we want to guard IPA transforms on [[gnu::musttail]] return value which should be more robust? Richard. > Bootstrapped/regtested on x86_64-linux and i686-linux, ok for trunk? > > 2025-01-15 Jakub Jelinek <ja...@redhat.com> > Andrew Pinski <quic_apin...@quicinc.com> > > PR tree-optimization/118430 > * tree-tailcall.cc: Include gimple-range.h, alloc-pool.h, sreal.h, > symbol-summary.h, ipa-cp.h and ipa-prop.h. > (find_tail_calls): If ass_var is NULL and ret_var is not, check if > IPA-VRP has not found singleton return range for it. In that case, > don't punt if ret_var is the only value in that range. Adjust the > maybe_error_musttail message otherwise to diagnose different value > being returned from the caller and callee rather than using return > slot. Formatting fixes. > > * c-c++-common/musttail14.c: New test. > > --- gcc/tree-tailcall.cc.jj 2025-01-02 11:23:18.407490465 +0100 > +++ gcc/tree-tailcall.cc 2025-01-14 16:03:13.169294430 +0100 > @@ -45,6 +45,12 @@ along with GCC; see the file COPYING3. > #include "ipa-utils.h" > #include "tree-ssa-live.h" > #include "diagnostic-core.h" > +#include "gimple-range.h" > +#include "alloc-pool.h" > +#include "sreal.h" > +#include "symbol-summary.h" > +#include "ipa-cp.h" > +#include "ipa-prop.h" > > /* The file implements the tail recursion elimination. It is also used to > analyze the tail calls in general, passing the results to the rtl level > @@ -483,7 +489,7 @@ find_tail_calls (basic_block bb, struct > { > if (dump_file) > fprintf (dump_file, "Basic block %d has extra exit edges\n", > - bb->index); > + bb->index); > return; > } > if (!cfun->has_musttail) > @@ -517,7 +523,8 @@ find_tail_calls (basic_block bb, struct > if (bad_stmt) > { > maybe_error_musttail (call, > - _("memory reference or volatile after call")); > + _("memory reference or volatile after " > + "call")); > return; > } > ass_var = gimple_call_lhs (call); > @@ -597,10 +604,10 @@ find_tail_calls (basic_block bb, struct > { > if (stmt == last_stmt) > maybe_error_musttail (call, > - _("call may throw exception that does not > propagate")); > + _("call may throw exception that does not " > + "propagate")); > else > - maybe_error_musttail (call, > - _("code between call and return")); > + maybe_error_musttail (call, _("code between call and return")); > return; > } > > @@ -715,7 +722,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")); > + maybe_error_musttail (call, > + _("call invocation refers to locals")); > return; > } > else > @@ -724,7 +732,8 @@ find_tail_calls (basic_block bb, struct > if (bitmap_bit_p (local_live_vars, *v)) > { > BITMAP_FREE (local_live_vars); > - maybe_error_musttail (call, _("call invocation refers to > locals")); > + maybe_error_musttail (call, > + _("call invocation refers to locals")); > return; > } > } > @@ -833,15 +842,39 @@ find_tail_calls (basic_block bb, struct > && (ret_var != ass_var > && !(is_empty_type (TREE_TYPE (ret_var)) && !ass_var))) > { > - maybe_error_musttail (call, _("call uses return slot")); > - return; > + bool ok = false; > + value_range val; > + tree valr; > + /* If IPA-VRP proves called function always returns a singleton range, > + the return value is replaced by the only value in that range. > + For tail call purposes, pretend such replacement didn't happen. */ > + if (ass_var == NULL_TREE > + && !tail_recursion > + && TREE_CONSTANT (ret_var)) > + if (tree type = gimple_range_type (call)) > + if (tree callee = gimple_call_fndecl (call)) > + if ((INTEGRAL_TYPE_P (type) || SCALAR_FLOAT_TYPE_P (type)) > + && useless_type_conversion_p (TREE_TYPE (TREE_TYPE (callee)), > + type) > + && useless_type_conversion_p (TREE_TYPE (ret_var), type) > + && ipa_return_value_range (val, callee) > + && val.singleton_p (&valr) > + && operand_equal_p (ret_var, valr, 0)) > + ok = true; > + if (!ok) > + { > + maybe_error_musttail (call, > + _("call and return value are different")); > + return; > + } > } > > /* If this is not a tail recursive call, we cannot handle addends or > multiplicands. */ > if (!tail_recursion && (m || a)) > { > - maybe_error_musttail (call, _("operations after non tail recursive > call")); > + maybe_error_musttail (call, > + _("operations after non tail recursive call")); > return; > } > > @@ -849,7 +882,8 @@ find_tail_calls (basic_block bb, struct > if (m && POINTER_TYPE_P (TREE_TYPE (DECL_RESULT (current_function_decl)))) > { > maybe_error_musttail (call, > - _("tail recursion with pointers can only use additions")); > + _("tail recursion with pointers can only use " > + "additions")); > return; > } > > --- gcc/testsuite/c-c++-common/musttail14.c.jj 2025-01-14 > 16:22:06.623276122 +0100 > +++ gcc/testsuite/c-c++-common/musttail14.c 2025-01-14 16:23:17.963267912 > +0100 > @@ -0,0 +1,89 @@ > +/* PR tree-optimization/118430 */ > +/* { dg-do compile { target musttail } } */ > +/* { dg-options "-O2 -fdump-tree-optimized" } */ > +/* { dg-final { scan-tree-dump-times " bar \\\(\[^\n\r]\*\\\); \\\[tail > call\\\] \\\[must tail call\\\]" 1 "optimized" } } */ > +/* { dg-final { scan-tree-dump-times " freddy \\\(\[^\n\r]\*\\\); \\\[tail > call\\\] \\\[must tail call\\\]" 1 "optimized" } } */ > + > +__attribute__ ((noipa)) void > +foo (int x) > +{ > + (void) x; > +} > + > +__attribute__ ((noinline)) int > +bar (int x) > +{ > + foo (x); > + return 1; > +} > + > +__attribute__ ((noinline)) int > +baz (int *x) > +{ > + foo (*x); > + return 2; > +} > + > +__attribute__((noipa)) int > +qux (int x) > +{ > + { > + int v; > + foo (x); > + baz (&v); > + } > + [[gnu::musttail]] > + return bar (x); > +} > + > +__attribute__((noipa)) int > +corge (int x) > +{ > + { > + int v; > + foo (x); > + baz (&v); > + } > + return bar (x) + 1; > +} > + > +__attribute__ ((noinline)) float > +freddy (int x) > +{ > + foo (x); > + return 1.75f; > +} > + > +__attribute__((noipa)) float > +garply (int x) > +{ > + { > + int v; > + foo (x); > + baz (&v); > + } > + [[gnu::musttail]] > + return freddy (x); > +} > + > +__attribute__((noipa)) float > +quux (int x) > +{ > + { > + int v; > + foo (x); > + baz (&v); > + } > + return freddy (x) + 0.25f; > +} > + > +int v; > + > +int > +main () > +{ > + qux (v); > + corge (v); > + garply (v); > + quux (v); > +} > > 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)