On Fri, 16 Jul 2021, Jan Hubicka wrote: > Hi, > this patch adds EAF_NOT_RETURNED flag which is determined by ipa-modref > and used both to improve its propagation (it can stop propagating flags > from call parameter to return value if EAF_NOT_RETURNED is earlier > determined for callee) and also to improve points-to constraints in > tree-ssa-structalias (since return value constrain does not need to > contain the parameters that are not returned. > > No true IPA propagatoin is done, but I will look into it incrementally > (there is general problem of lacking return functions). > > We now have 8 EAF flags so it is no longer possible to store them to > char datatype so I added eaf_flags_t. I also disabled some shortcuts in > ipa-moderef which ignored CONST functions since EAF_UNUSED and > EAF_NOT_RETURNED is useful there, too. > > The tree-ssa-structlias part is not very precise. I simply avoid adding > constraint copying callused to rhs if all parameters are > EAF_NOT_RETURNED. This is overly conservative, but if one just skips > not returned parameters in call used we will optimize out initialization > of memory that is read by the callee but does not escape or gets > returned. > > It would be more precise to push arguments to rhsc vector individually, > but I would like to do this incrementally since this results in more > constraints and pehraps we should be smart and produce them only if > there is a mix of not returned and returned parameters or so. > > Bootstrapped/regtested x86_64-linux, also ltobootstrapped with c++ only, > OK?
OK. Btw, there's some modref propagation correctness fix from Alex which needs looking at - https://gcc.gnu.org/pipermail/gcc-patches/2021-June/573137.html Thanks, Richard. > gcc/ChangeLog: > > 2021-07-16 Jan Hubicka <hubi...@ucw.cz> > > * ipa-modref.c (struct escape_entry): Use eaf_flags_t. > (dump_eaf_flags): Dump EAF_NOT_RETURNED > (eaf_flags_useful_p): Use eaf_fleags_t; handle const functions > and EAF_NOT_RETURNED. > (modref_summary::useful_p): Likewise. > (modref_summary_lto::useful_p): Likewise. > (struct) modref_summary_lto: Use eaf_fleags_t. > (deref_flags): Handle EAF_NOT_RETURNED. > (struct escape_point): Use min_flags. > (modref_lattice::init): Add EAF_NOT_RETURNED. > (merge_call_lhs_flags): Ignore EAF_NOT_RETURNED functions > (analyze_ssa_name_flags): Clear EAF_NOT_RETURNED on return; > handle call flags. > (analyze_parms): Also analyze const functions; update conition on > flags usefulness. > (modref_write): Update streaming. > (read_section): Update streaming. > (remap_arg_flags): Use eaf_flags_t. > (modref_merge_call_site_flags): Hanlde EAF_NOT_RETURNED. > * ipa-modref.h: (eaf_flags_t): New typedef. > (struct modref_summary): Use eaf_flags_t. > * tree-core.h (EAF_NOT_RETURNED): New constant. > * tree-ssa-structalias.c (handle_rhs_call): Hanlde EAF_NOT_RETURNED. > (handle_const_call): Handle EAF_UNUSED and EAF_NOT_RETURNED. > (handle_pure_call): Handle EAF_NOT_RETURNED. > > gcc/testsuite/ChangeLog: > > 2021-07-16 Jan Hubicka <hubi...@ucw.cz> > > * gcc.dg/tree-ssa/modref-6.c: New test. > > diff --git a/gcc/ipa-modref.c b/gcc/ipa-modref.c > index d5a8332fb55..734d7d066bc 100644 > --- a/gcc/ipa-modref.c > +++ b/gcc/ipa-modref.c > @@ -86,6 +86,7 @@ along with GCC; see the file COPYING3. If not see > #include "stringpool.h" > #include "tree-ssanames.h" > > + > namespace { > > /* We record fnspec specifiers for call edges since they depends on actual > @@ -135,7 +136,7 @@ struct escape_entry > /* Argument it escapes to. */ > unsigned int arg; > /* Minimal flags known about the argument. */ > - char min_flags; > + eaf_flags_t min_flags; > /* Does it escape directly or indirectly? */ > bool direct; > }; > @@ -155,6 +156,8 @@ dump_eaf_flags (FILE *out, int flags, bool newline = true) > fprintf (out, " nodirectescape"); > if (flags & EAF_UNUSED) > fprintf (out, " unused"); > + if (flags & EAF_NOT_RETURNED) > + fprintf (out, " not_returned"); > if (newline) > fprintf (out, "\n"); > } > @@ -278,12 +281,17 @@ modref_summary::~modref_summary () > /* Return true if FLAGS holds some useful information. */ > > static bool > -eaf_flags_useful_p (vec <unsigned char> &flags, int ecf_flags) > +eaf_flags_useful_p (vec <eaf_flags_t> &flags, int ecf_flags) > { > for (unsigned i = 0; i < flags.length (); i++) > - if (ecf_flags & ECF_PURE) > + if (ecf_flags & ECF_CONST) > { > - if (flags[i] & (EAF_UNUSED | EAF_DIRECT)) > + if (flags[i] & (EAF_UNUSED | EAF_NOT_RETURNED)) > + return true; > + } > + else if (ecf_flags & ECF_PURE) > + { > + if (flags[i] & (EAF_UNUSED | EAF_DIRECT | EAF_NOT_RETURNED)) > return true; > } > else > @@ -300,13 +308,15 @@ eaf_flags_useful_p (vec <unsigned char> &flags, int > ecf_flags) > bool > modref_summary::useful_p (int ecf_flags, bool check_flags) > { > - if (ecf_flags & (ECF_CONST | ECF_NOVOPS)) > + if (ecf_flags & ECF_NOVOPS) > return false; > if (arg_flags.length () && !check_flags) > return true; > if (check_flags && eaf_flags_useful_p (arg_flags, ecf_flags)) > return true; > arg_flags.release (); > + if (ecf_flags & ECF_CONST) > + return false; > if (loads && !loads->every_base) > return true; > if (ecf_flags & ECF_PURE) > @@ -325,7 +335,7 @@ struct GTY(()) modref_summary_lto > more verbose and thus more likely to hit the limits. */ > modref_records_lto *loads; > modref_records_lto *stores; > - auto_vec<unsigned char> GTY((skip)) arg_flags; > + auto_vec<eaf_flags_t> GTY((skip)) arg_flags; > bool writes_errno; > > modref_summary_lto (); > @@ -356,13 +366,15 @@ modref_summary_lto::~modref_summary_lto () > bool > modref_summary_lto::useful_p (int ecf_flags, bool check_flags) > { > - if (ecf_flags & (ECF_CONST | ECF_NOVOPS)) > + if (ecf_flags & ECF_NOVOPS) > return false; > if (arg_flags.length () && !check_flags) > return true; > if (check_flags && eaf_flags_useful_p (arg_flags, ecf_flags)) > return true; > arg_flags.release (); > + if (ecf_flags & ECF_CONST) > + return false; > if (loads && !loads->every_base) > return true; > if (ecf_flags & ECF_PURE) > @@ -1317,6 +1329,8 @@ deref_flags (int flags, bool ignore_stores) > if ((flags & EAF_NOESCAPE) || ignore_stores) > ret |= EAF_NOESCAPE; > } > + if (flags & EAF_NOT_RETURNED) > + ret |= EAF_NOT_RETURNED; > return ret; > } > > @@ -1332,7 +1346,7 @@ struct escape_point > int arg; > /* Flags already known about the argument (this can save us from recording > esape points if local analysis did good job already). */ > - char min_flags; > + eaf_flags_t min_flags; > /* Does value escape directly or indiretly? */ > bool direct; > }; > @@ -1366,7 +1380,7 @@ void > modref_lattice::init () > { > flags = EAF_DIRECT | EAF_NOCLOBBER | EAF_NOESCAPE | EAF_UNUSED > - | EAF_NODIRECTESCAPE; > + | EAF_NODIRECTESCAPE | EAF_NOT_RETURNED; > open = true; > known = false; > } > @@ -1539,6 +1553,9 @@ merge_call_lhs_flags (gcall *call, int arg, int index, > bool deref, > && (flags & ERF_RETURN_ARG_MASK) != arg) > return; > > + if (gimple_call_arg_flags (call, arg) & (EAF_NOT_RETURNED | EAF_UNUSED)) > + return; > + > /* If return value is SSA name determine its flags. */ > if (TREE_CODE (gimple_call_lhs (call)) == SSA_NAME) > { > @@ -1613,9 +1630,12 @@ analyze_ssa_name_flags (tree name, vec<modref_lattice> > &lattice, int depth, > if (greturn *ret = dyn_cast <greturn *> (use_stmt)) > { > if (gimple_return_retval (ret) == name) > - lattice[index].merge (~EAF_UNUSED); > + lattice[index].merge (~(EAF_UNUSED | EAF_NOT_RETURNED)); > else if (memory_access_to (gimple_return_retval (ret), name)) > - lattice[index].merge_direct_load (); > + { > + lattice[index].merge_direct_load (); > + lattice[index].merge (~EAF_NOT_RETURNED); > + } > } > /* Account for LHS store, arg loads and flags from callee function. */ > else if (gcall *call = dyn_cast <gcall *> (use_stmt)) > @@ -1666,7 +1686,8 @@ analyze_ssa_name_flags (tree name, vec<modref_lattice> > &lattice, int depth, > { > if (!(ecf_flags & (ECF_CONST | ECF_NOVOPS))) > { > - int call_flags = gimple_call_arg_flags (call, i); > + int call_flags = gimple_call_arg_flags (call, i) > + | EAF_NOT_RETURNED; > if (ignore_stores) > call_flags |= EAF_NOCLOBBER | EAF_NOESCAPE > | EAF_NODIRECTESCAPE; > @@ -1689,7 +1710,8 @@ analyze_ssa_name_flags (tree name, vec<modref_lattice> > &lattice, int depth, > else > { > int call_flags = deref_flags > - (gimple_call_arg_flags (call, i), ignore_stores); > + (gimple_call_arg_flags (call, i) > + | EAF_NOT_RETURNED, ignore_stores); > if (!record_ipa) > lattice[index].merge (call_flags); > else > @@ -1819,8 +1841,8 @@ analyze_parms (modref_summary *summary, > modref_summary_lto *summary_lto, > unsigned int count = 0; > int ecf_flags = flags_from_decl_or_type (current_function_decl); > > - /* For const functions we have nothing to gain by EAF flags. */ > - if (ecf_flags & (ECF_CONST | ECF_NOVOPS)) > + /* For novops functions we have nothing to gain by EAF flags. */ > + if (ecf_flags & ECF_NOVOPS) > return; > > for (tree parm = DECL_ARGUMENTS (current_function_decl); parm; > @@ -1863,7 +1885,11 @@ analyze_parms (modref_summary *summary, > modref_summary_lto *summary_lto, > /* For pure functions we have implicit NOCLOBBER > and NOESCAPE. */ > if (ecf_flags & ECF_PURE) > - flags &= ~(EAF_NOCLOBBER | EAF_NOESCAPE | EAF_NODIRECTESCAPE); > + flags &= (EAF_UNUSED | EAF_DIRECT | EAF_NOT_RETURNED); > + /* Only useful flags for const function are EAF_NOT_RETURNED and > + EAF_UNUSED. */ > + if (ecf_flags & ECF_CONST) > + flags &= (EAF_UNUSED | EAF_NOT_RETURNED); > > if (flags) > { > @@ -2518,7 +2544,7 @@ modref_write () > > streamer_write_uhwi (ob, r->arg_flags.length ()); > for (unsigned int i = 0; i < r->arg_flags.length (); i++) > - streamer_write_char_stream (ob->main_stream, r->arg_flags[i]); > + streamer_write_uhwi (ob, r->arg_flags[i]); > > write_modref_records (r->loads, ob); > write_modref_records (r->stores, ob); > @@ -2609,7 +2635,7 @@ read_section (struct lto_file_decl_data *file_data, > const char *data, > modref_sum_lto->arg_flags.reserve_exact (args); > for (unsigned int i = 0; i < args; i++) > { > - unsigned char flags = streamer_read_uchar (&ib); > + eaf_flags_t flags = streamer_read_uhwi (&ib); > if (modref_sum) > modref_sum->arg_flags.quick_push (flags); > if (modref_sum_lto) > @@ -2713,9 +2739,9 @@ modref_read (void) > /* Recompute arg_flags for param adjustments in INFO. */ > > static void > -remap_arg_flags (auto_vec <unsigned char> &arg_flags, clone_info *info) > +remap_arg_flags (auto_vec <eaf_flags_t> &arg_flags, clone_info *info) > { > - auto_vec<unsigned char> old = arg_flags.copy (); > + auto_vec<eaf_flags_t> old = arg_flags.copy (); > int max = -1; > size_t i; > ipa_adjusted_param *p; > @@ -3665,8 +3691,9 @@ modref_merge_call_site_flags (escape_summary *sum, > flags |= EAF_NOESCAPE | EAF_NOCLOBBER | EAF_NODIRECTESCAPE; > flags_lto |= EAF_NOESCAPE | EAF_NOCLOBBER | EAF_NODIRECTESCAPE; > } > - flags |= ee->min_flags; > - flags_lto |= ee->min_flags; > + /* Returning the value is already accounted to at local propagation. > */ > + flags |= ee->min_flags | EAF_NOT_RETURNED; > + flags_lto |= ee->min_flags | EAF_NOT_RETURNED; > if (!(flags & EAF_UNUSED) > && cur_summary && ee->parm_index < cur_summary->arg_flags.length ()) > { > diff --git a/gcc/ipa-modref.h b/gcc/ipa-modref.h > index 8af62b30d5e..498cc2414ac 100644 > --- a/gcc/ipa-modref.h > +++ b/gcc/ipa-modref.h > @@ -21,6 +21,7 @@ along with GCC; see the file COPYING3. If not see > #define IPA_MODREF_H > > typedef modref_tree <alias_set_type> modref_records; > +typedef unsigned short eaf_flags_t; > > /* Single function summary. */ > > @@ -29,7 +30,7 @@ struct GTY(()) modref_summary > /* Load and stores in function (transitively closed to all callees) */ > modref_records *loads; > modref_records *stores; > - auto_vec<unsigned char> GTY((skip)) arg_flags; > + auto_vec<eaf_flags_t> GTY((skip)) arg_flags; > bool writes_errno; > > modref_summary (); > diff --git a/gcc/testsuite/gcc.dg/tree-ssa/modref-6.c > b/gcc/testsuite/gcc.dg/tree-ssa/modref-6.c > new file mode 100644 > index 00000000000..a3ac23ce666 > --- /dev/null > +++ b/gcc/testsuite/gcc.dg/tree-ssa/modref-6.c > @@ -0,0 +1,37 @@ > +/* { dg-options "-O2 -fdump-tree-modref1 -fdump-tree-optimized" } */ > +/* { dg-do compile } */ > +int c; > +__attribute__ ((noinline)) > +int *test (int *b) > +{ > + c++; > + return *b ? &c : 0; > +} > +__attribute__ ((noinline, pure)) > +int *pure_test (int *b) > +{ > + return *b && c ? &c : 0; > +} > +__attribute__ ((noinline, const)) > +int *const_test (int *b) > +{ > + return b ? &c : 0; > +} > +void escape (int *); > + > +int test2() > +{ > + int a = 42; > + escape (test (&a)); > + escape (pure_test (&a)); > + escape (const_test (&a)); > + return a; > +} > +/* Flags for normal call. */ > +/* { dg-final { scan-tree-dump "parm 0 flags: direct noclobber noescape > nodirectescape not_returned" "modref1" } } */ > +/* Flags for pure call. */ > +/* { dg-final { scan-tree-dump "parm 0 flags: direct not_returned" "modref1" > } } */ > +/* Flags for const call. */ > +/* { dg-final { scan-tree-dump "parm 0 flags: unused not_returned" "modref1" > } } */ > +/* Overall we want to make "int a" non escaping. */ > +/* { dg-final { scan-tree-dump "return 42" "optimized" } } */ > diff --git a/gcc/tree-core.h b/gcc/tree-core.h > index e15e6c651f0..d2aa0bbbc5c 100644 > --- a/gcc/tree-core.h > +++ b/gcc/tree-core.h > @@ -114,6 +114,9 @@ struct die_struct; > referenced by it can escape. */ > #define EAF_NODIRECTESCAPE (1 << 4) > > +/* Nonzero if the argument does not escape to return value. */ > +#define EAF_NOT_RETURNED (1 << 8) > + > /* Call return flags. */ > /* Mask for the argument number that is returned. Lower two bits of > the return flags, encodes argument slots zero to three. */ > diff --git a/gcc/tree-ssa-structalias.c b/gcc/tree-ssa-structalias.c > index 7163438e23d..71894b38ff9 100644 > --- a/gcc/tree-ssa-structalias.c > +++ b/gcc/tree-ssa-structalias.c > @@ -4082,9 +4082,12 @@ handle_rhs_call (gcall *stmt, vec<ce_s> *results) > if (!(flags & EAF_DIRECT)) > make_transitive_closure_constraints (tem); > make_copy_constraint (uses, tem->id); > + /* TODO: This is overly conservative when some parameters are > + returned while others are not. */ > + if (!(flags & EAF_NOT_RETURNED)) > + returns_uses = true; > if (!(flags & (EAF_NOESCAPE | EAF_DIRECT))) > make_indirect_escape_constraint (tem); > - returns_uses = true; > } > else if (flags & (EAF_NOESCAPE | EAF_NODIRECTESCAPE)) > { > @@ -4098,6 +4101,8 @@ handle_rhs_call (gcall *stmt, vec<ce_s> *results) > if (!(flags & EAF_DIRECT)) > make_transitive_closure_constraints (tem); > make_copy_constraint (uses, tem->id); > + if (!(flags & EAF_NOT_RETURNED)) > + returns_uses = true; > make_copy_constraint (clobbers, tem->id); > /* Add *tem = nonlocal, do not add *tem = callused as > EAF_NOESCAPE parameters do not escape to other parameters > @@ -4111,7 +4116,6 @@ handle_rhs_call (gcall *stmt, vec<ce_s> *results) > process_constraint (new_constraint (lhs, rhs)); > if (!(flags & (EAF_NOESCAPE | EAF_DIRECT))) > make_indirect_escape_constraint (tem); > - returns_uses = true; > } > else > make_escape_constraint (arg); > @@ -4261,13 +4265,18 @@ handle_const_call (gcall *stmt, vec<ce_s> *results) > > /* May return offsetted arguments. */ > varinfo_t tem = NULL; > - if (gimple_call_num_args (stmt) != 0) > - { > - tem = new_var_info (NULL_TREE, "callarg", true); > - tem->is_reg_var = true; > - } > for (k = 0; k < gimple_call_num_args (stmt); ++k) > { > + int flags = gimple_call_arg_flags (stmt, k); > + > + /* If the argument is not used or not returned we can ignore it. */ > + if (flags & (EAF_UNUSED | EAF_NOT_RETURNED)) > + continue; > + if (!tem) > + { > + tem = new_var_info (NULL_TREE, "callarg", true); > + tem->is_reg_var = true; > + } > tree arg = gimple_call_arg (stmt, k); > auto_vec<ce_s> argc; > get_constraint_for_rhs (arg, &argc); > @@ -4298,6 +4307,7 @@ handle_pure_call (gcall *stmt, vec<ce_s> *results) > struct constraint_expr rhsc; > unsigned i; > varinfo_t uses = NULL; > + bool record_uses = false; > > /* Memory reached from pointer arguments is call-used. */ > for (i = 0; i < gimple_call_num_args (stmt); ++i) > @@ -4315,6 +4325,8 @@ handle_pure_call (gcall *stmt, vec<ce_s> *results) > make_transitive_closure_constraints (uses); > } > make_constraint_to (uses->id, arg); > + if (!(flags & EAF_NOT_RETURNED)) > + record_uses = true; > } > > /* The static chain is used as well. */ > @@ -4327,6 +4339,7 @@ handle_pure_call (gcall *stmt, vec<ce_s> *results) > make_transitive_closure_constraints (uses); > } > make_constraint_to (uses->id, gimple_call_chain (stmt)); > + record_uses = true; > } > > /* And if we applied NRV the address of the return slot. */ > @@ -4343,10 +4356,11 @@ handle_pure_call (gcall *stmt, vec<ce_s> *results) > auto_vec<ce_s> tmpc; > get_constraint_for_address_of (gimple_call_lhs (stmt), &tmpc); > make_constraints_to (uses->id, tmpc); > + record_uses = true; > } > > /* Pure functions may return call-used and nonlocal memory. */ > - if (uses) > + if (record_uses) > { > rhsc.var = uses->id; > rhsc.offset = 0; > -- Richard Biener <rguent...@suse.de> SUSE Software Solutions Germany GmbH, Maxfeldstrasse 5, 90409 Nuernberg, Germany; GF: Felix Imendörffer; HRB 36809 (AG Nuernberg)