Hi,
this is updated patch for autodetection of EAF flags.  Still the goal is
to avoid fancy stuff and get besic logic in place (so no dataflow, no IPA
propagation, no attempts to handle trickier cases).  There is one new failure

./gcc/testsuite/gcc/gcc.sum:FAIL: gcc.dg/sso/t2.c   -Wno-scalar-storage-order 
-O1 -fno-inline  output pattern test
./gcc/testsuite/gcc/gcc.sum:FAIL: gcc.dg/sso/t2.c   -Wno-scalar-storage-order 
-O2  output pattern test
./gcc/testsuite/gcc/gcc.sum:FAIL: gcc.dg/sso/t2.c   -Wno-scalar-storage-order 
-Os  output pattern test

Which I blieve is bug exposed by detecting dump function to be EAF_DIRECT and
NOESCAPE (which it is) and packing/updacking the bitfields leads in one bit
difference. Still no idea why.

Patch seems to be quite effective on cc1plus turning:

Alias oracle query stats:
  refs_may_alias_p: 65808750 disambiguations, 75664890 queries
  ref_maybe_used_by_call_p: 153485 disambiguations, 66711204 queries
  call_may_clobber_ref_p: 22816 disambiguations, 28889 queries
  nonoverlapping_component_refs_p: 0 disambiguations, 36846 queries
  nonoverlapping_refs_since_match_p: 27271 disambiguations, 58917 must 
overlaps, 86958 queries
  aliasing_component_refs_p: 65808 disambiguations, 2067256 queries
  TBAA oracle: 25929211 disambiguations 60395141 queries
               12391384 are in alias set 0
               10783783 queries asked about the same object
               126 queries asked about the same alias set
               0 access volatile
               9598698 are dependent in the DAG
               1691939 are aritificially in conflict with void *

Modref stats:
  modref use: 14284 disambiguations, 53336 queries
  modref clobber: 1660281 disambiguations, 2130440 queries
  4311165 tbaa queries (2.023603 per modref query)
  685304 base compares (0.321673 per modref query)

PTA query stats:
  pt_solution_includes: 959190 disambiguations, 13169678 queries
  pt_solutions_intersect: 1050969 disambiguations, 13246686 queries

Alias oracle query stats:
  refs_may_alias_p: 66914578 disambiguations, 76692648 queries
  ref_maybe_used_by_call_p: 244077 disambiguations, 67732086 queries
  call_may_clobber_ref_p: 111475 disambiguations, 116613 queries
  nonoverlapping_component_refs_p: 0 disambiguations, 37091 queries
  nonoverlapping_refs_since_match_p: 27267 disambiguations, 59019 must 
overlaps, 87056 queries
  aliasing_component_refs_p: 65870 disambiguations, 2063394 queries
  TBAA oracle: 26024415 disambiguations 60579490 queries
               12450910 are in alias set 0
               10806673 queries asked about the same object
               125 queries asked about the same alias set
               0 access volatile
               9605837 are dependent in the DAG
               1691530 are aritificially in conflict with void *

Modref stats:
  modref use: 14272 disambiguations, 277680 queries
  modref clobber: 1669753 disambiguations, 7849135 queries
  4330162 tbaa queries (0.551674 per modref query)
  699241 base compares (0.089085 per modref query)

PTA query stats:
  pt_solution_includes: 1833920 disambiguations, 13846032 queries
  pt_solutions_intersect: 1093785 disambiguations, 13309954 queries

So almost twice as many pt_solution_includes disambiguations.
I will re-run the stats overnight to be sure that it is not independent
change (but both build was from almost same checkout).

Bootstrapped/regtested x86_64-linux, OK?
(I will analyze more the t2.c failure)

Honza

gcc/ChangeLog:

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

        * gimple.c: Include ipa-modref-tree.h and ipa-modref.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.
        (analyze_ssa_name_flags): Declare.
        (modref_summary::useful_p): Summary is also useful if arg flags are
        known.
        (dump_eaf_flags): New function.
        (modref_summary::dump): Use it.
        (get_modref_function_summary): Be read for current_function_decl
        being NULL.
        (memory_access_to): New function.
        (deref_flags): New function.
        (call_lhs_flags): New function.
        (analyze_parms): New function.
        (analyze_function): Use it.
        * ipa-modref.h (struct modref_summary): Add arg_flags.

gcc/testsuite/ChangeLog:

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

        * gcc.dg/torture/pta-ptrarith-1.c: Escape parametrs.

diff --git a/gcc/gimple.c b/gcc/gimple.c
index 1afed88e1f1..da90716aa23 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..bde626115ff 100644
--- a/gcc/ipa-modref.c
+++ b/gcc/ipa-modref.c
@@ -61,6 +61,14 @@ 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"
+
+static int analyze_ssa_name_flags (tree name, vec<int> *known_flags, int 
depth);
 
 /* We record fnspec specifiers for call edges since they depends on actual
    gimple statements.  */
@@ -186,6 +194,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 +365,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 +398,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 +437,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 +1103,315 @@ 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;
+}
+
+/* Consider statement 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;
+}
+
+/* Call statements may return their parameters.  Consider argument number
+   ARG of USE_STMT and determine flags that can needs to be cleared
+   in case pointer possibly indirectly references from ARG I is returned.
+   KNOWN_FLAGS and DEPTH are same as in analyze_ssa_name_flags.  */
+
+static int
+call_lhs_flags (gimple *use_stmt, int arg, vec<int> *known_flags, int depth)
+{
+  /* If there is no return value, no flags are affected.  */
+  if (!gimple_call_lhs (use_stmt))
+    return EAF_DIRECT | EAF_NOCLOBBER | EAF_NOESCAPE | EAF_UNUSED;
+
+  /* If we know that function returns given argument and it is not ARG
+     we can still be happy.  */
+  int flags = gimple_call_return_flags (as_a <gcall *> (use_stmt));
+  if ((flags & ERF_RETURNS_ARG)
+      && (flags & ERF_RETURN_ARG_MASK) != arg)
+    return EAF_DIRECT | EAF_NOCLOBBER | EAF_NOESCAPE | EAF_UNUSED;
+
+  /* If return value is SSA name determine its flags.  */
+  if (TREE_CODE (gimple_call_lhs (use_stmt)) == SSA_NAME)
+    return analyze_ssa_name_flags
+                      (gimple_call_lhs (use_stmt), known_flags,
+                       depth + 1);
+  /* In the case of memory store we can do nothing.  */
+  else
+    return 0;
+}
+
+/* Analyze EAF flags for SSA name NAME.
+   KNOWN_FLAGS is a cache for flags we already determined.
+   DEPTH is a recursion depth used to make debug output prettier.  */
+
+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 (is_gimple_debug (use_stmt))
+       continue;
+      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)
+                 {
+                   if (ecf_flags & (ECF_CONST | ECF_NOVOPS))
+                     flags &= ignore_stores
+                              ? 0
+                              : call_lhs_flags (use_stmt, i,
+                                                known_flags, depth);
+                   else
+                     {
+                       int call_flags = gimple_call_arg_flags (as_a <gcall *>
+                                                                (use_stmt), i);
+                       if (ignore_stores)
+                         call_flags |= EAF_NOCLOBBER | EAF_NOESCAPE;
+                       else
+                         call_flags &= call_lhs_flags (use_stmt, i,
+                                                       known_flags, depth);
+
+                       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);
+                   if (!ignore_stores)
+                     flags &= call_lhs_flags (use_stmt, i, known_flags,
+                                              depth);
+                 }
+           }
+         /* Only NOCLOBBER or DIRECT flags alone are not useful (see comments
+            in tree-ssa-alias.c).  Give up earlier.  */
+         if ((flags & ~(EAF_DIRECT | EAF_NOCLOBBER)) == 0)
+           flags = 0;
+       }
+      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 +1519,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 +1540,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