> > 
> > Yep, i am not arguing for eliminating special case of memcpy (because we
> > have the additional info that it only copies pointers from *src to
> > *dest).
> > 
> > However I find current definition of EAF_NOESCAPE bit hard to handle in
> > modref, since naturally it is quite reliable to track all uses of ssa
> > name that correspond to parameters, but it is harder to track where
> > values read from pointed-to memory can eventually go.
> 
> Yeah - also the fnspec of memcpy _is_ wrong at the moment ...

Yep.
> > 
> > For 6) I determine flags of LHS and merge them in
> 
> guess you could track a SSA lattice "based on parameter N" which
> would need to be a mask (thus only track up to 32 parameters?)

Yes, I can do that if we relax NOESCAPE this way.
> 
> > For 7) I clear NOESCAPE if rhs is name itself
> > and UNUSED + NOESCAPE if rhs is derefernece from name.
> > 
> > For 8) I do nothing.  Here the names are non-pointers that I track
> > because of earlier dereference.
> > 
> > 
> > 
> > So I think 7) can be relaxed.  Main problem is hoever that we often see 1)
> > and then 3) or 7) on LHS that makes us punt very often.
> > 
> > The fact that pointer directly does not escape but pointed to memory can
> > seems still very useful since one does not need to add *ptr to points-to
> > sets. But I will try relaxing 7).
> > 
> > If we allow values escaping to other parameters and itself, I think I
> > can relax 3) if base of the store is default def of PARM_DECL.
> 
> I think the important part is to get things correct.  Maybe it's worth

Indeed :)
> to add write/read flags where the argument _does_ escape in case the
> function itself is otherwise pure/const.  For PTA that doesn't make
> a difference (and fnspec was all about PTA ...) but for alias-analysis
> it does.

I detect them independently (UNUSED/NOCLOBBER flags which is not perfect
since we do not have OUTPUT flag like in fnspecs), but currently they
are unused since we do not track "pure/const except for known
exceptions".  This is not hard to add.
> 
> > > 
> > > I wonder if we should teach the GIMPLE FE to parse 'fn spec'
> > > so we can write unit tests for the attribute ... or maybe simply
> > > add this to the __GIMPLE spec string.
> > 
> > May be nice and also describe carefully that NOESCAPE and NOCLOBBER also
> > reffers to indirect references.  Current description
> > "Nonzero if the argument does not escape."
> > reads to me that it is about ptr itself, not about *ptr and also it does
> > not speak of the escaping to return value etc.
> 
> Well, if 'ptr' escapes then obvoiously all memory reachable from 'ptr'
> escapes - escaping is always transitive.

Yes, but if values pointed to by ptr escapes, ptr itself does not need
to escape.  This is easy to detect (and is common case of THIS pointer)
but we have no way to express it via EAF flags.
> 
> And as escaping is in the context of the caller sth escaping to the
> caller itself (via return) can hardly be considered escaping (again
> this was designed for PTA ...).
> 
> I guess it makes sense to be able to separate escaping from the rest.
I think current definition (escaping via return is not an escape) is
also OK for modref propagation.  We may have EAF_NORETURN that says that
value never escapes to return value that would be also easy to detect.

This is kind of minimal patch for the EAF flags discovery.  It works
only in local ipa-modref and gives up on cyclic SSA graphs.  Adding
propagation is easy and proper IPA mode needs collecting call sites+arg
pairs that affect the answer.

It passes testuite except for sso/t2.c testcase where it affects bit of first
dumped structure R1.
We correctly determine that it is noescape/noclobber from the dump function and
it seems that this triggers kind of strange SRA.  I will look into it.

I am running full bootstrap/regtest.

On tramp3d the effect is not great, but it does something.

PTA query stats:
  pt_solution_includes: 397269 disambiguations, 606922 queries
  pt_solutions_intersect: 138302 disambiguations, 416884 queries

to

PTA query stats:
  pt_solution_includes: 401540 disambiguations, 609093 queries
  pt_solutions_intersect: 138616 disambiguations, 417174 queries

2020-11-09  Jan Hubicka  <hubi...@ucw.cz>

        * builtins.c (builtin_fnspec): Fix fnspecs of memcpy and friends.
        * gimple.c: Include ipa-modref-tree.h and ipa-mdoref.h.
        (gimple_call_arg_flags): Use modref to determine flags.
        * ipa-modref.c: Include gimple-ssa.h, tree-phinodes.h,
        tree-ssa-operands.h, stringpool.h and tree-ssanames.h.
        (modref_summary::useful_p): Summary is also useful if EAF flags are
        known.
        (dump_eaf_flags): New.
        (modref_summary::dump): Dump EAF flags.
        (get_modref_function_summary): Be ready for
        current_function_decl == NULL.
        (memory_access_to): New function.
        (deref_flags): New function.
        (analyze_ssa_name_flags): New function.
        (analyze_parms): New function.
        (analyze_function): New function.
        * ipa-modref.h (struct modref_summary): Add arg_flags.

gcc/testsuite/ChangeLog:

2020-11-09  Jan Hubicka  <hubi...@ucw.cz>

        * gcc.dg/torture/pta-ptrarith-1.c: Make parameter escape.

diff --git a/gcc/builtins.c b/gcc/builtins.c
index da25343beb1..c55f54e2aef 100644
--- a/gcc/builtins.c
+++ b/gcc/builtins.c
@@ -12939,16 +12939,16 @@ builtin_fnspec (tree callee)
         argument.  */
       case BUILT_IN_STRCAT:
       case BUILT_IN_STRCAT_CHK:
-       return "1cW R ";
+       return "1cW r ";
       case BUILT_IN_STRNCAT:
       case BUILT_IN_STRNCAT_CHK:
-       return "1cW R3";
+       return "1cW r3";
       case BUILT_IN_STRCPY:
       case BUILT_IN_STRCPY_CHK:
-       return "1cO R ";
+       return "1cO r ";
       case BUILT_IN_STPCPY:
       case BUILT_IN_STPCPY_CHK:
-       return ".cO R ";
+       return ".cO r ";
       case BUILT_IN_STRNCPY:
       case BUILT_IN_MEMCPY:
       case BUILT_IN_MEMMOVE:
@@ -12957,15 +12957,15 @@ builtin_fnspec (tree callee)
       case BUILT_IN_STRNCPY_CHK:
       case BUILT_IN_MEMCPY_CHK:
       case BUILT_IN_MEMMOVE_CHK:
-       return "1cO3R3";
+       return "1cO3r3";
       case BUILT_IN_MEMPCPY:
       case BUILT_IN_MEMPCPY_CHK:
-       return ".cO3R3";
+       return ".cO3r3";
       case BUILT_IN_STPNCPY:
       case BUILT_IN_STPNCPY_CHK:
-       return ".cO3R3";
+       return ".cO3r3";
       case BUILT_IN_BCOPY:
-       return ".cR3O3";
+       return ".cr3O3";
       case BUILT_IN_BZERO:
        return ".cO2";
       case BUILT_IN_MEMCMP:
diff --git a/gcc/gimple.c b/gcc/gimple.c
index 1afed88e1f1..72a0d05580a 100644
--- a/gcc/gimple.c
+++ b/gcc/gimple.c
@@ -46,6 +46,8 @@ along with GCC; see the file COPYING3.  If not see
 #include "asan.h"
 #include "langhooks.h"
 #include "attr-fnspec.h"
+#include "ipa-modref-tree.h"
+#include "ipa-modref.h"
 
 
 /* All the tuples have their operand vector (if present) at the very bottom
@@ -1532,24 +1534,45 @@ int
 gimple_call_arg_flags (const gcall *stmt, unsigned arg)
 {
   attr_fnspec fnspec = gimple_call_fnspec (stmt);
-
-  if (!fnspec.known_p ())
-    return 0;
-
   int flags = 0;
 
-  if (!fnspec.arg_specified_p (arg))
-    ;
-  else if (!fnspec.arg_used_p (arg))
-    flags = EAF_UNUSED;
-  else
+  if (fnspec.known_p ())
     {
-      if (fnspec.arg_direct_p (arg))
-       flags |= EAF_DIRECT;
-      if (fnspec.arg_noescape_p (arg))
-       flags |= EAF_NOESCAPE;
-      if (fnspec.arg_readonly_p (arg))
-       flags |= EAF_NOCLOBBER;
+      if (!fnspec.arg_specified_p (arg))
+       ;
+      else if (!fnspec.arg_used_p (arg))
+       flags = EAF_UNUSED;
+      else
+       {
+         if (fnspec.arg_direct_p (arg))
+           flags |= EAF_DIRECT;
+         if (fnspec.arg_noescape_p (arg))
+           flags |= EAF_NOESCAPE;
+         if (fnspec.arg_readonly_p (arg))
+           flags |= EAF_NOCLOBBER;
+       }
+    }
+  tree callee = gimple_call_fndecl (stmt);
+  if (callee)
+    {
+      cgraph_node *node = cgraph_node::get (callee);
+      modref_summary *summary = node ? get_modref_function_summary (node)
+                               : NULL;
+
+      if (summary && summary->arg_flags.length () > arg)
+       {
+         int modref_flags = summary->arg_flags [arg];
+
+         /* We have possibly optimized out load.  Be conservative here.  */
+         if (!node->binds_to_current_def_p ())
+           {
+             if ((modref_flags & EAF_UNUSED) && !(flags & EAF_UNUSED))
+               modref_flags &= ~EAF_UNUSED;
+             if ((modref_flags & EAF_DIRECT) && !(flags & EAF_DIRECT))
+               modref_flags &= ~EAF_DIRECT;
+           }
+         flags |= modref_flags;
+       }
     }
   return flags;
 }
diff --git a/gcc/ipa-modref.c b/gcc/ipa-modref.c
index 3f46bebed3c..6df4c8fbf68 100644
--- a/gcc/ipa-modref.c
+++ b/gcc/ipa-modref.c
@@ -61,6 +61,12 @@ along with GCC; see the file COPYING3.  If not see
 #include "ipa-fnsummary.h"
 #include "attr-fnspec.h"
 #include "symtab-clones.h"
+#include "gimple-ssa.h"
+#include "tree-phinodes.h"
+#include "tree-ssa-operands.h"
+#include "ssa-iterators.h"
+#include "stringpool.h"
+#include "tree-ssanames.h"
 
 /* We record fnspec specifiers for call edges since they depends on actual
    gimple statements.  */
@@ -186,6 +192,8 @@ modref_summary::useful_p (int ecf_flags)
 {
   if (ecf_flags & (ECF_CONST | ECF_NOVOPS))
     return false;
+  if (arg_flags.length ())
+    return true;
   if (loads && !loads->every_base)
     return true;
   if (ecf_flags & ECF_PURE)
@@ -355,6 +363,22 @@ dump_lto_records (modref_records_lto *tt, FILE *out)
     }
 }
 
+/* Dump EAF flags.  */
+
+static void
+dump_eaf_flags (FILE *out, int flags)
+{
+  if (flags & EAF_DIRECT)
+    fprintf (out, " direct");
+  if (flags & EAF_NOCLOBBER)
+    fprintf (out, " noclobber");
+  if (flags & EAF_NOESCAPE)
+    fprintf (out, " noescape");
+  if (flags & EAF_UNUSED)
+    fprintf (out, " unused");
+  fprintf (out, "\n");
+}
+
 /* Dump summary.  */
 
 void
@@ -372,6 +396,15 @@ modref_summary::dump (FILE *out)
     }
   if (writes_errno)
     fprintf (out, "  Writes errno\n");
+  if (arg_flags.length ())
+    {
+      for (unsigned int i = 0; i < arg_flags.length (); i++)
+       if (arg_flags[i])
+         {
+           fprintf (out, "  parm %i flags:", i);
+           dump_eaf_flags (out, arg_flags[i]);
+         }
+    }
 }
 
 /* Dump summary.  */
@@ -402,7 +435,8 @@ get_modref_function_summary (cgraph_node *func)
      function.  */
   enum availability avail;
   func = func->function_or_virtual_thunk_symbol
-            (&avail, cgraph_node::get (current_function_decl));
+                (&avail, current_function_decl ?
+                         cgraph_node::get (current_function_decl) : NULL);
   if (avail <= AVAIL_INTERPOSABLE)
     return NULL;
 
@@ -1067,6 +1101,266 @@ remove_summary (bool lto, bool nolto, bool ipa)
             " - modref done with result: not tracked.\n");
 }
 
+/* Return true if OP accesses memory pointed to by SSA_NAME.  */
+
+bool
+memory_access_to (tree op, tree ssa_name)
+{
+  tree base = get_base_address (op);
+  if (!base)
+    return false;
+  if (TREE_CODE (base) != MEM_REF && TREE_CODE (base) != TARGET_MEM_REF)
+    return false;
+  return TREE_OPERAND (base, 0) == ssa_name;
+}
+
+/* We have val = *arg. 
+   return EAF flags of ARG that can be determined from EAF flags of VAL
+   (which are known to be FLAGS).  If IGNORE_STORES is true we can ignore
+   all stores to VAL, i.e. when handling noreturn function.  */
+
+static int
+deref_flags (int flags, bool ignore_stores)
+{
+  int ret = 0;
+  if (flags & EAF_UNUSED)
+    ret |= EAF_DIRECT | EAF_NOCLOBBER | EAF_NOESCAPE;
+  else
+    {
+      if ((flags & EAF_NOCLOBBER) || ignore_stores)
+       ret |= EAF_NOCLOBBER;
+      if ((flags & EAF_NOESCAPE) || ignore_stores)
+       ret |= EAF_NOESCAPE;
+    }
+  return ret;
+}
+
+/* Analyze EAF flags for SSA name NAME.
+   KNOWN_FLAGS is a cache for flags we already determined.  */
+
+static int
+analyze_ssa_name_flags (tree name, vec<int> *known_flags, int depth)
+{
+  imm_use_iterator ui;
+  gimple *use_stmt;
+  int flags = EAF_DIRECT | EAF_NOCLOBBER | EAF_NOESCAPE | EAF_UNUSED;
+
+  /* See if value is already computed.  */
+  if ((*known_flags)[SSA_NAME_VERSION (name)])
+    {
+      /* Punt on cycles for now, so we do not need dataflow.  */
+      if ((*known_flags)[SSA_NAME_VERSION (name)] == 1)
+       {
+         if (dump_file)
+           fprintf (dump_file,
+                    "%*sGiving up on a cycle in SSA graph\n", depth * 4, "");
+         return 0;
+       }
+      return (*known_flags)[SSA_NAME_VERSION (name)] - 2;
+    }
+  /* Recursion guard.  */
+  (*known_flags)[SSA_NAME_VERSION (name)] = 1;
+
+  if (dump_file)
+    {
+      fprintf (dump_file,
+              "%*sAnalyzing flags of ssa name: ", depth * 4, "");
+      print_generic_expr (dump_file, name);
+      fprintf (dump_file, "\n");
+    }
+
+  FOR_EACH_IMM_USE_STMT (use_stmt, ui, name)
+    {
+      if (flags == 0)
+       {
+         BREAK_FROM_IMM_USE_STMT (ui);
+       }
+      if (dump_file)
+       {
+         fprintf (dump_file, "%*s  Analyzing stmt:", depth * 4, "");
+         print_gimple_stmt (dump_file, use_stmt, 0);
+       }
+
+      /* Gimple return may load the return value.  */
+      if (gimple_code (use_stmt) == GIMPLE_RETURN)
+       {
+         if (memory_access_to (gimple_return_retval
+                                  (as_a <greturn *> (use_stmt)), name))
+           flags &= ~EAF_UNUSED;
+       }
+      /* Account for LHS store, arg loads and flags from callee function.  */
+      else if (is_gimple_call (use_stmt))
+       {
+         tree callee = gimple_call_fndecl (use_stmt);
+
+         /* Recursion would require bit of propagation; give up for now.  */
+         if (callee && recursive_call_p (current_function_decl, callee))
+           flags = 0;
+         else
+           {
+             int ecf_flags = gimple_call_flags (use_stmt);
+             bool ignore_stores = ignore_stores_p (current_function_decl,
+                                                   ecf_flags);
+
+             /* Handle *name = func (...).  */
+             if (gimple_call_lhs (use_stmt)
+                 && memory_access_to (gimple_call_lhs (use_stmt), name))
+               flags &= ~(EAF_UNUSED | EAF_NOCLOBBER);
+
+             /* Handle all function parameters.  */
+             for (unsigned i = 0; i < gimple_call_num_args (use_stmt); i++)
+               /* Name is directly passed to the callee.  */
+               if (gimple_call_arg (use_stmt, i) == name
+                   && !(ecf_flags & (ECF_CONST | ECF_NOVOPS)))
+                 {
+                   int call_flags = gimple_call_arg_flags (as_a <gcall *>
+                                                                (use_stmt), i);
+                   if (ignore_stores)
+                     call_flags |= EAF_NOCLOBBER | EAF_NOESCAPE;
+
+                   flags &= call_flags;
+                 }
+               /* Name is dereferenced and passed to a callee.  */
+               else if (memory_access_to (gimple_call_arg (use_stmt, i), name))
+                 {
+                   if (ecf_flags & (ECF_CONST | ECF_NOVOPS))
+                     flags &= ~EAF_UNUSED;
+                   else
+                     flags &= deref_flags (gimple_call_arg_flags
+                                               (as_a <gcall *> (use_stmt), i),
+                                           ignore_stores);
+                 }
+           }
+       }
+      else if (gimple_assign_load_p (use_stmt))
+       {
+         /* Memory to memory copy.  */
+         if (gimple_store_p (use_stmt))
+           {
+             /* Handle *name = *exp.  */
+             if (memory_access_to (gimple_assign_lhs (use_stmt), name))
+               flags &= ~(EAF_UNUSED | EAF_NOCLOBBER);
+
+             /* Handle *lhs = *name.
+
+                We do not track memory locations, so assume that value
+                is used arbitrarily.  */
+             if (memory_access_to (gimple_assign_rhs1 (use_stmt), name))
+               flags = 0;
+           }
+         /* Handle lhs = *name.  */
+         else if (memory_access_to (gimple_assign_rhs1 (use_stmt), name))
+           flags &= deref_flags (analyze_ssa_name_flags
+                                     (gimple_assign_lhs (use_stmt),
+                                      known_flags, depth + 1), false);
+       }
+      else if (gimple_store_p (use_stmt))
+       {
+         /* Handle *lhs = name.  */
+         if (is_gimple_assign (use_stmt)
+             && gimple_assign_rhs1 (use_stmt) == name)
+           {
+             if (dump_file)
+               fprintf (dump_file, "%*s  ssa name saved to memory\n",
+                        depth * 4, "");
+             flags = 0;
+           }
+         /* Handle *name = exp.  */
+         else if (is_gimple_assign (use_stmt)
+                  && memory_access_to (gimple_assign_lhs (use_stmt), name))
+           flags &= ~(EAF_UNUSED | EAF_NOCLOBBER);
+         /* ASM statements etc.  */
+         else if (!is_gimple_assign (use_stmt))
+           {
+             if (dump_file)
+               fprintf (dump_file, "%*s  Unhandled store\n",
+                        depth * 4, "");
+             flags = 0;
+           }
+       }
+      else if (is_gimple_assign (use_stmt))
+       {
+         enum tree_code code = gimple_assign_rhs_code (use_stmt);
+
+         /* See if operation is a merge as considered by
+            tree-ssa-structalias.c:find_func_aliases.  */
+         if (!truth_value_p (code)
+             && code != POINTER_DIFF_EXPR
+             && (code != POINTER_PLUS_EXPR
+                 || gimple_assign_rhs1 (use_stmt) == name))
+           flags &= analyze_ssa_name_flags
+                              (gimple_assign_lhs (use_stmt), known_flags,
+                               depth + 1);
+       }
+      else if (gimple_code (use_stmt) == GIMPLE_PHI)
+       {
+         flags &= analyze_ssa_name_flags
+                            (gimple_phi_result (use_stmt), known_flags,
+                             depth + 1);
+       }
+      /* Conditions are not considered escape points
+        by tree-ssa-structalias.  */
+      else if (gimple_code (use_stmt) == GIMPLE_COND)
+       ;
+      else
+       {
+         if (dump_file)
+           fprintf (dump_file, "%*s  Unhandled stmt\n", depth * 4, "");
+         flags = 0;
+       }
+
+      if (dump_file)
+       {
+         fprintf (dump_file, "%*s  current flags of ", depth * 4, "");
+         print_generic_expr (dump_file, name);
+         dump_eaf_flags (dump_file, flags);
+       }
+    }
+  if (dump_file)
+    {
+      fprintf (dump_file, "%*sflags of ssa name ", depth * 4, "");
+      print_generic_expr (dump_file, name);
+      dump_eaf_flags (dump_file, flags);
+    }
+  (*known_flags)[SSA_NAME_VERSION (name)] = flags + 2;
+  return flags;
+}
+
+/* Determine EAF flags for function parameters.  */
+
+static void
+analyze_parms (modref_summary *summary)
+{
+  unsigned int parm_index = 0;
+  unsigned int count = 0;
+
+  for (tree parm = DECL_ARGUMENTS (current_function_decl); parm;
+       parm = TREE_CHAIN (parm))
+    count++;
+
+  if (!count)
+    return;
+
+  auto_vec<int> known_flags;
+  known_flags.safe_grow_cleared (num_ssa_names);
+
+  for (tree parm = DECL_ARGUMENTS (current_function_decl); parm; parm_index++,
+       parm = TREE_CHAIN (parm))
+    {
+      tree name = ssa_default_def (cfun, parm);
+      if (!name)
+       continue;
+      int flags = analyze_ssa_name_flags (name, &known_flags, 0);
+
+      if (flags)
+       {
+         if (parm_index >= summary->arg_flags.length ())
+           summary->arg_flags.safe_grow_cleared (count);
+         summary->arg_flags [parm_index] = flags;
+       }
+    }
+}
+
 /* Analyze function F.  IPA indicates whether we're running in local mode
    (false) or the IPA mode (true).  */
 
@@ -1174,6 +1468,10 @@ analyze_function (function *f, bool ipa)
                                  param_modref_max_accesses);
       summary_lto->writes_errno = false;
     }
+
+  if (!ipa)
+    analyze_parms (summary);
+
   int ecf_flags = flags_from_decl_or_type (current_function_decl);
   auto_vec <gimple *, 32> recursive_calls;
 
@@ -1191,8 +1489,9 @@ analyze_function (function *f, bool ipa)
              || ((!summary || !summary->useful_p (ecf_flags))
                  && (!summary_lto || !summary_lto->useful_p (ecf_flags))))
            {
-             remove_summary (lto, nolto, ipa);
-             return;
+             collapse_loads (summary, summary_lto);
+             collapse_stores (summary, summary_lto);
+             break;
            }
        }
     }
diff --git a/gcc/ipa-modref.h b/gcc/ipa-modref.h
index 31ceffa8d34..8fa05aaf7fb 100644
--- a/gcc/ipa-modref.h
+++ b/gcc/ipa-modref.h
@@ -29,6 +29,7 @@ struct GTY(()) modref_summary
   /* Load and stores in function (transitively closed to all callees)  */
   modref_records *loads;
   modref_records *stores;
+  auto_vec<int> GTY((skip)) arg_flags;
 
   modref_summary ();
   ~modref_summary ();
diff --git a/gcc/testsuite/gcc.dg/torture/pta-ptrarith-1.c 
b/gcc/testsuite/gcc.dg/torture/pta-ptrarith-1.c
index 99a548840df..85b68068b12 100644
--- a/gcc/testsuite/gcc.dg/torture/pta-ptrarith-1.c
+++ b/gcc/testsuite/gcc.dg/torture/pta-ptrarith-1.c
@@ -6,11 +6,14 @@ struct Foo {
   int *p;
 };
 
+struct Foo *ff;
+
 void __attribute__((noinline))
 foo (void *p)
 {
   struct Foo *f = (struct Foo *)p - 1;
   *f->p = 0;
+  ff = f;
 }
 
 int bar (void)

Reply via email to