Extend modref by side-effect analysis

Message ID 20211110115528.GE97553@kam.mff.cuni.cz
State Committed
Commit 992644c3511acd58248db784f1ac43e2f053ebcc
Headers
Series Extend modref by side-effect analysis |

Commit Message

Jan Hubicka Nov. 10, 2021, 11:55 a.m. UTC
  Hi,
this patch makes modref to also collect info whether function has side
effects.  This allows pure/const function detection and also handling
functions which do store some memory in similar way as we handle
pure/consts now.

The code is symmetric to what ipa-pure-const does.  Modref is actually more
capable on proving that a given function is pure/const (since it understands
that non-pure function can be called when it only modifies data on stack)
so we could retire ipa-pure-const's pure-const discovery at some point.

However this patch only does the anlaysis - the consumers of this flag
will come next.

Bootstrapped/regtested x86_64-linux. I plan to commit it later today
if there are no complains.

gcc/ChangeLog:

	* ipa-modref.c: Include tree-eh.h
	(modref_summary::modref_summary): Initialize side_effects.
	(struct modref_summary_lto): New bool field side_effects.
	(modref_summary_lto::modref_summary_lto): Initialize side_effects.
	(modref_summary::dump): Dump side_effects.
	(modref_summary_lto::dump): Dump side_effects.
	(merge_call_side_effects): Merge side effects.
	(process_fnspec): Calls to non-const/pure or looping
	function is a side effect.
	(analyze_call): Self-recursion is a side-effect; handle
	special builtins.
	(analyze_load): Watch for volatile and throwing memory.
	(analyze_store): Likewise.
	(analyze_stmt): Watch for volatitle asm.
	(analyze_function): Handle side_effects.
	(modref_summaries::duplicate): Duplicate side_effects.
	(modref_summaries_lto::duplicate): Likewise.
	(modref_write): Stream side_effects.
	(read_section): Likewise.
	(update_signature): Update.
	(propagate_unknown_call): Handle side_effects.
	(modref_propagate_in_scc): Likewise.
	* ipa-modref.h (struct modref_summary): Add side_effects.
	* ipa-pure-const.c (special_builtin_state): Rename to ...
	(builtin_safe_for_const_function_p): ... this one.
	(check_call): Update.
	(finite_function_p): Break out from ...
	(propagate_pure_const): ... here
	* ipa-utils.h (finite_function): Declare.
  

Patch

diff --git a/gcc/ipa-modref.c b/gcc/ipa-modref.c
index 22efc06c583..d14f9e52f62 100644
--- a/gcc/ipa-modref.c
+++ b/gcc/ipa-modref.c
@@ -87,6 +87,7 @@  along with GCC; see the file COPYING3.  If not see
 #include "tree-ssanames.h"
 #include "attribs.h"
 #include "tree-cfg.h"
+#include "tree-eh.h"
 
 
 namespace {
@@ -273,7 +274,7 @@  static GTY(()) fast_function_summary <modref_summary_lto *, va_gc>
 
 modref_summary::modref_summary ()
   : loads (NULL), stores (NULL), retslot_flags (0), static_chain_flags (0),
-    writes_errno (false)
+    writes_errno (false), side_effects (false)
 {
 }
 
@@ -371,6 +372,7 @@  struct GTY(()) modref_summary_lto
   eaf_flags_t retslot_flags;
   eaf_flags_t static_chain_flags;
   bool writes_errno;
+  bool side_effects;
 
   modref_summary_lto ();
   ~modref_summary_lto ();
@@ -382,7 +384,7 @@  struct GTY(()) modref_summary_lto
 
 modref_summary_lto::modref_summary_lto ()
   : loads (NULL), stores (NULL), retslot_flags (0), static_chain_flags (0),
-    writes_errno (false)
+    writes_errno (false), side_effects (false)
 {
 }
 
@@ -615,6 +617,8 @@  modref_summary::dump (FILE *out)
     }
   if (writes_errno)
     fprintf (out, "  Writes errno\n");
+  if (side_effects)
+    fprintf (out, "  Side effects\n");
   if (arg_flags.length ())
     {
       for (unsigned int i = 0; i < arg_flags.length (); i++)
@@ -647,6 +651,8 @@  modref_summary_lto::dump (FILE *out)
   dump_lto_records (stores, out);
   if (writes_errno)
     fprintf (out, "  Writes errno\n");
+  if (side_effects)
+    fprintf (out, "  Side effects\n");
   if (arg_flags.length ())
     {
       for (unsigned int i = 0; i < arg_flags.length (); i++)
@@ -980,6 +986,12 @@  merge_call_side_effects (modref_summary *cur_summary,
 	  changed = true;
 	}
     }
+  if (!cur_summary->side_effects
+      && callee_summary->side_effects)
+    {
+      cur_summary->side_effects = true;
+      changed = true;
+    }
   return changed;
 }
 
@@ -1075,6 +1087,18 @@  process_fnspec (modref_summary *cur_summary,
 		gcall *call, bool ignore_stores)
 {
   attr_fnspec fnspec = gimple_call_fnspec (call);
+  int flags = gimple_call_flags (call);
+
+  if (!(flags & (ECF_CONST | ECF_NOVOPS))
+      || (flags & ECF_LOOPING_CONST_OR_PURE)
+      || (cfun->can_throw_non_call_exceptions
+	  && stmt_could_throw_p (cfun, call)))
+    {
+      if (cur_summary)
+	cur_summary->side_effects = true;
+      if (cur_summary_lto)
+	cur_summary_lto->side_effects = true;
+    }
   if (!fnspec.known_p ())
     {
       if (dump_file && gimple_call_builtin_p (call, BUILT_IN_NORMAL))
@@ -1212,6 +1236,10 @@  analyze_call (modref_summary *cur_summary, modref_summary_lto *cur_summary_lto,
   if (recursive_call_p (current_function_decl, callee))
     {
       recursive_calls->safe_push (stmt);
+      if (cur_summary)
+	cur_summary->side_effects = true;
+      if (cur_summary_lto)
+	cur_summary_lto->side_effects = true;
       if (dump_file)
 	fprintf (dump_file, " - Skipping recursive call.\n");
       return true;
@@ -1222,6 +1250,20 @@  analyze_call (modref_summary *cur_summary, modref_summary_lto *cur_summary_lto,
   /* Get the function symbol and its availability.  */
   enum availability avail;
   callee_node = callee_node->function_symbol (&avail);
+  bool looping;
+  if (builtin_safe_for_const_function_p (&looping, callee))
+    {
+      if (looping)
+	{
+	  if (cur_summary)
+	    cur_summary->side_effects = true;
+	  if (cur_summary_lto)
+	    cur_summary_lto->side_effects = true;
+	}
+      if (dump_file)
+	fprintf (dump_file, " - Bulitin is safe for const.\n");
+      return true;
+    }
   if (avail <= AVAIL_INTERPOSABLE)
     {
       if (dump_file)
@@ -1268,6 +1310,18 @@  analyze_load (gimple *, tree, tree op, void *data)
       fprintf (dump_file, "\n");
     }
 
+  if (TREE_THIS_VOLATILE (op)
+      || (cfun->can_throw_non_call_exceptions
+	  && tree_could_throw_p (op)))
+    {
+      if (dump_file)
+	fprintf (dump_file, " (volatile or can throw; marking side effects) ");
+      if (summary)
+	summary->side_effects = true;
+      if (summary_lto)
+	summary_lto->side_effects = true;
+    }
+
   if (!record_access_p (op))
     return false;
 
@@ -1296,6 +1350,18 @@  analyze_store (gimple *, tree, tree op, void *data)
       fprintf (dump_file, "\n");
     }
 
+  if (TREE_THIS_VOLATILE (op)
+      || (cfun->can_throw_non_call_exceptions
+	  && tree_could_throw_p (op)))
+    {
+      if (dump_file)
+	fprintf (dump_file, " (volatile or can throw; marking side effects) ");
+      if (summary)
+	summary->side_effects = true;
+      if (summary_lto)
+	summary_lto->side_effects = true;
+    }
+
   if (!record_access_p (op))
     return false;
 
@@ -1332,6 +1398,15 @@  analyze_stmt (modref_summary *summary, modref_summary_lto *summary_lto,
   switch (gimple_code (stmt))
    {
    case GIMPLE_ASM:
+      if (gimple_asm_volatile_p (as_a <gasm *> (stmt))
+	  || (cfun->can_throw_non_call_exceptions
+	      && stmt_could_throw_p (cfun, stmt)))
+	{
+	  if (summary)
+	    summary->side_effects = true;
+	  if (summary_lto)
+	    summary_lto->side_effects = true;
+	}
      /* If the ASM statement does not read nor write memory, there's nothing
 	to do.  Otherwise just give up.  */
      if (!gimple_asm_clobbers_memory_p (as_a <gasm *> (stmt)))
@@ -1363,7 +1438,14 @@  analyze_stmt (modref_summary *summary, modref_summary_lto *summary_lto,
       }
      return true;
    default:
-     /* Nothing to do for other types of statements.  */
+     if (cfun->can_throw_non_call_exceptions
+	 && stmt_could_throw_p (cfun, stmt))
+	{
+	  if (summary)
+	    summary->side_effects = true;
+	  if (summary_lto)
+	    summary_lto->side_effects = true;
+	}
      return true;
    }
 }
@@ -2606,6 +2688,7 @@  analyze_function (function *f, bool ipa)
 						    param_modref_max_refs,
 						    param_modref_max_accesses);
       summary->writes_errno = false;
+      summary->side_effects = false;
     }
   if (lto)
     {
@@ -2620,6 +2703,7 @@  analyze_function (function *f, bool ipa)
 				  param_modref_max_refs,
 				  param_modref_max_accesses);
       summary_lto->writes_errno = false;
+      summary_lto->side_effects = false;
     }
 
   analyze_parms (summary, summary_lto, ipa,
@@ -2690,6 +2774,12 @@  analyze_function (function *f, bool ipa)
       summaries_lto->remove (fnode);
       summary_lto = NULL;
     }
+  if (summary && !summary->global_memory_written_p () && !summary->side_effects
+      && !finite_function_p ())
+    summary->side_effects = true;
+  if (summary_lto && !summary_lto->side_effects && !finite_function_p ())
+    summary_lto->side_effects = true;
+
   if (ipa && !summary && !summary_lto)
     remove_modref_edge_summaries (fnode);
 
@@ -2886,6 +2976,7 @@  modref_summaries::duplicate (cgraph_node *, cgraph_node *dst,
 			 src_data->loads->max_accesses);
   dst_data->loads->copy_from (src_data->loads);
   dst_data->writes_errno = src_data->writes_errno;
+  dst_data->side_effects = src_data->side_effects;
   if (src_data->arg_flags.length ())
     dst_data->arg_flags = src_data->arg_flags.copy ();
   dst_data->retslot_flags = src_data->retslot_flags;
@@ -2913,6 +3004,7 @@  modref_summaries_lto::duplicate (cgraph_node *, cgraph_node *,
 			 src_data->loads->max_accesses);
   dst_data->loads->copy_from (src_data->loads);
   dst_data->writes_errno = src_data->writes_errno;
+  dst_data->side_effects = src_data->side_effects;
   if (src_data->arg_flags.length ())
     dst_data->arg_flags = src_data->arg_flags.copy ();
   dst_data->retslot_flags = src_data->retslot_flags;
@@ -3241,6 +3333,7 @@  modref_write ()
 
 	  struct bitpack_d bp = bitpack_create (ob->main_stream);
 	  bp_pack_value (&bp, r->writes_errno, 1);
+	  bp_pack_value (&bp, r->side_effects, 1);
 	  if (!flag_wpa)
 	    {
 	      for (cgraph_edge *e = cnode->indirect_calls;
@@ -3310,9 +3403,15 @@  read_section (struct lto_file_decl_data *file_data, const char *data,
 	modref_sum = optimization_summaries->get_create (node);
 
       if (modref_sum)
-	modref_sum->writes_errno = false;
+	{
+	  modref_sum->writes_errno = false;
+	  modref_sum->side_effects = false;
+	}
       if (modref_sum_lto)
-	modref_sum_lto->writes_errno = false;
+	{
+	  modref_sum_lto->writes_errno = false;
+	  modref_sum_lto->side_effects = false;
+	}
 
       gcc_assert (!modref_sum || (!modref_sum->loads
 				  && !modref_sum->stores));
@@ -3357,6 +3456,13 @@  read_section (struct lto_file_decl_data *file_data, const char *data,
 	  if (modref_sum_lto)
 	    modref_sum_lto->writes_errno = true;
 	}
+      if (bp_unpack_value (&bp, 1))
+	{
+	  if (modref_sum)
+	    modref_sum->side_effects = true;
+	  if (modref_sum_lto)
+	    modref_sum_lto->side_effects = true;
+	}
       if (!flag_ltrans)
 	{
 	  for (cgraph_edge *e = node->indirect_calls; e; e = e->next_callee)
@@ -3505,7 +3611,7 @@  update_signature (struct cgraph_node *node)
 
   map.reserve (max + 1);
   for (i = 0; i <= max; i++)
-    map.quick_push (-1);
+    map.quick_push (MODREF_UNKNOWN_PARM);
   FOR_EACH_VEC_SAFE_ELT (info->param_adjustments->m_adj_params, i, p)
     {
       int idx = info->param_adjustments->get_original_index (i);
@@ -3844,6 +3950,39 @@  propagate_unknown_call (cgraph_node *node,
   bool changed = false;
   class fnspec_summary *fnspec_sum = fnspec_summaries->get (e);
   auto_vec <modref_parm_map, 32> parm_map;
+  bool looping;
+
+  if (e->callee
+      && builtin_safe_for_const_function_p (&looping, e->callee->decl))
+    {
+      if (cur_summary && !cur_summary->side_effects)
+	{
+	  cur_summary->side_effects = true;
+	  changed = true;
+	}
+      if (cur_summary_lto && !cur_summary_lto->side_effects)
+	{
+	  cur_summary_lto->side_effects = true;
+	  changed = true;
+	}
+      return changed;
+    }
+
+  if (!(ecf_flags & (ECF_CONST | ECF_NOVOPS))
+      || (ecf_flags & ECF_LOOPING_CONST_OR_PURE))
+    {
+      if (cur_summary && !cur_summary->side_effects)
+	{
+	  cur_summary->side_effects = true;
+	  changed = true;
+	}
+      if (cur_summary_lto && !cur_summary_lto->side_effects)
+	{
+	  cur_summary_lto->side_effects = true;
+	  changed = true;
+	}
+    }
+
   if (fnspec_sum
       && compute_parm_map (e, &parm_map))
     {
@@ -4108,6 +4247,12 @@  modref_propagate_in_scc (cgraph_node *component_node)
 		  changed |= cur_summary->loads->merge
 				  (callee_summary->loads, &parm_map,
 				   &chain_map, !first);
+		  if (!cur_summary->side_effects
+		      && callee_summary->side_effects)
+		    {
+		      cur_summary->side_effects = true;
+		      changed = true;
+		    }
 		  if (!ignore_stores)
 		    {
 		      changed |= cur_summary->stores->merge
@@ -4126,6 +4271,12 @@  modref_propagate_in_scc (cgraph_node *component_node)
 		  changed |= cur_summary_lto->loads->merge
 				  (callee_summary_lto->loads, &parm_map,
 				   &chain_map, !first);
+		  if (!cur_summary_lto->side_effects
+		      && callee_summary_lto->side_effects)
+		    {
+		      cur_summary_lto->side_effects = true;
+		      changed = true;
+		    }
 		  if (!ignore_stores)
 		    {
 		      changed |= cur_summary_lto->stores->merge
diff --git a/gcc/ipa-modref.h b/gcc/ipa-modref.h
index 20170a65ded..0c3c81d1d43 100644
--- a/gcc/ipa-modref.h
+++ b/gcc/ipa-modref.h
@@ -34,6 +34,7 @@  struct GTY(()) modref_summary
   eaf_flags_t retslot_flags;
   eaf_flags_t static_chain_flags;
   bool writes_errno;
+  bool side_effects;
 
   modref_summary ();
   ~modref_summary ();
diff --git a/gcc/ipa-pure-const.c b/gcc/ipa-pure-const.c
index e5048092939..505ed4f8a3b 100644
--- a/gcc/ipa-pure-const.c
+++ b/gcc/ipa-pure-const.c
@@ -506,11 +506,10 @@  worse_state (enum pure_const_state_e *state, bool *looping,
   *looping = MAX (*looping, looping2);
 }
 
-/* Recognize special cases of builtins that are by themselves not pure or const
+/* Recognize special cases of builtins that are by themselves not const
    but function using them is.  */
-static bool
-special_builtin_state (enum pure_const_state_e *state, bool *looping,
-		       tree callee)
+bool
+builtin_safe_for_const_function_p (bool *looping, tree callee)
 {
   if (DECL_BUILT_IN_CLASS (callee) == BUILT_IN_NORMAL)
     switch (DECL_FUNCTION_CODE (callee))
@@ -532,11 +531,9 @@  special_builtin_state (enum pure_const_state_e *state, bool *looping,
       case BUILT_IN_DWARF_CFA:
       case BUILT_IN_RETURN_ADDRESS:
 	*looping = false;
-	*state = IPA_CONST;
 	return true;
       case BUILT_IN_PREFETCH:
 	*looping = true;
-	*state = IPA_CONST;
 	return true;
       default:
 	break;
@@ -594,17 +591,16 @@  check_call (funct_state local, gcall *call, bool ipa)
      graph.  */
   if (callee_t)
     {
-      enum pure_const_state_e call_state;
       bool call_looping;
 
       if (gimple_call_builtin_p (call, BUILT_IN_NORMAL)
 	  && !nonfreeing_call_p (call))
 	local->can_free = true;
 
-      if (special_builtin_state (&call_state, &call_looping, callee_t))
+      if (builtin_safe_for_const_function_p (&call_looping, callee_t))
 	{
 	  worse_state (&local->pure_const_state, &local->looping,
-		       call_state, call_looping,
+		       IPA_CONST, call_looping,
 		       NULL, NULL);
 	  return;
 	}
@@ -1007,6 +1003,51 @@  malloc_candidate_p (function *fun, bool ipa)
 
 #undef DUMP_AND_RETURN
 
+/* Return true if function is known to be finite.  */
+
+bool
+finite_function_p ()
+{
+  /* Const functions cannot have back edges (an
+     indication of possible infinite loop side
+     effect.  */
+  bool finite = true;
+  if (mark_dfs_back_edges ())
+    {
+      /* Preheaders are needed for SCEV to work.
+	 Simple latches and recorded exits improve chances that loop will
+	 proved to be finite in testcases such as in loop-15.c
+	 and loop-24.c  */
+      loop_optimizer_init (LOOPS_HAVE_PREHEADERS
+			   | LOOPS_HAVE_SIMPLE_LATCHES
+			   | LOOPS_HAVE_RECORDED_EXITS);
+      if (dump_file && (dump_flags & TDF_DETAILS))
+	flow_loops_dump (dump_file, NULL, 0);
+      if (mark_irreducible_loops ())
+	{
+	  if (dump_file)
+	    fprintf (dump_file, "    has irreducible loops\n");
+	  finite = false;
+	}
+      else
+	{
+	  scev_initialize ();
+	  for (auto loop : loops_list (cfun, 0))
+	    if (!finite_loop_p (loop))
+	      {
+		if (dump_file)
+		  fprintf (dump_file, "    cannot prove finiteness of "
+			   "loop %i\n", loop->num);
+		finite =false;
+		break;
+	      }
+	  scev_finalize ();
+	}
+      loop_optimizer_finalize ();
+    }
+  return finite;
+}
+
 /* This is the main routine for finding the reference patterns for
    global variables within a function FN.  */
 
@@ -1065,45 +1106,10 @@  analyze_function (struct cgraph_node *fn, bool ipa)
     }
 
 end:
-  if (l->pure_const_state != IPA_NEITHER)
-    {
-      /* Const functions cannot have back edges (an
-	 indication of possible infinite loop side
-	 effect.  */
-      if (mark_dfs_back_edges ())
-        {
-	  /* Preheaders are needed for SCEV to work.
-	     Simple latches and recorded exits improve chances that loop will
-	     proved to be finite in testcases such as in loop-15.c
-	     and loop-24.c  */
-	  loop_optimizer_init (LOOPS_HAVE_PREHEADERS
-			       | LOOPS_HAVE_SIMPLE_LATCHES
-			       | LOOPS_HAVE_RECORDED_EXITS);
-	  if (dump_file && (dump_flags & TDF_DETAILS))
-	    flow_loops_dump (dump_file, NULL, 0);
-	  if (mark_irreducible_loops ())
-	    {
-	      if (dump_file)
-	        fprintf (dump_file, "    has irreducible loops\n");
-	      l->looping = true;
-	    }
-	  else
-	    {
-	      scev_initialize ();
-	      for (auto loop : loops_list (cfun, 0))
-		if (!finite_loop_p (loop))
-		  {
-		    if (dump_file)
-		      fprintf (dump_file, "    cannot prove finiteness of "
-			       "loop %i\n", loop->num);
-		    l->looping =true;
-		    break;
-		  }
-	      scev_finalize ();
-	    }
-          loop_optimizer_finalize ();
-	}
-    }
+  if (l->pure_const_state != IPA_NEITHER
+      && !l->looping
+      && !finite_function_p ())
+    l->looping = true;
 
   if (dump_file && (dump_flags & TDF_DETAILS))
     fprintf (dump_file, "    checking previously known:");
@@ -1539,9 +1545,9 @@  propagate_pure_const (void)
 		      edge_looping = y_l->looping;
 		    }
 		}
-	      else if (special_builtin_state (&edge_state, &edge_looping,
-					      y->decl))
-		;
+	      else if (builtin_safe_for_const_function_p (&edge_looping,
+							   y->decl))
+		edge_state = IPA_CONST;
 	      else
 		state_from_flags (&edge_state, &edge_looping,
 				  flags_from_decl_or_type (y->decl),
diff --git a/gcc/ipa-utils.h b/gcc/ipa-utils.h
index 3cfaf2d2737..824780f562a 100644
--- a/gcc/ipa-utils.h
+++ b/gcc/ipa-utils.h
@@ -47,6 +47,10 @@  void ipa_merge_profiles (struct cgraph_node *dst,
 			 struct cgraph_node *src, bool preserve_body = false);
 bool recursive_call_p (tree, tree);
 
+/* In ipa-pure-const.c  */
+bool finite_function_p ();
+bool builtin_safe_for_const_function_p (bool *, tree);
+
 /* In ipa-profile.c  */
 bool ipa_propagate_frequency (struct cgraph_node *node);