Fix pure/const discovery WRT interposition part 2

Jan Hubicka hubicka@ucw.cz
Sat Apr 16 16:47:00 GMT 2016


Hi,
this patch updates ipa-pure-const.c to only propagate PURE flag across
calls that does not bind to local defs and are not explicitly declared const.
This gets memory state into shape that the callee produced by other compiler
and still accessing memory is safe.

We need similar logic for -fnon-call-exceptions which I will do incrementally.
We also want to track if the original unoptimized body did access memory but
that needs frontend changes because memory accesses may get folded away during
parsing.

Bootstrapped/regtested x86_64-linux, will commit it shortly.

Honza

	PR ipa/70018
	* cgraph.c (cgraph_set_const_flag_1): Only set as pure if
	function does not bind to current def.
	* ipa-pure-const.c (worse_state): Add FROM and TO parameters;
	handle conservatively calls to functions that does not need to bind
	to current def.
	(check_call): Update call of worse_state.
	(ignore_edge_for_nothrow): Update.
	(ignore_edge_for_pure_const): Likewise.
	(propagate_pure_const): Update calls to worse_state.
	(skip_function_for_local_pure_const): Reformat comments.

	* g++.dg/ipa/pure-const-1.C: New testcase.
	* g++.dg/ipa/pure-const-2.C: New testcase.
	* g++.dg/ipa/pure-const-3.C: New testcase.
Index: cgraph.c
===================================================================
--- cgraph.c	(revision 235063)
+++ cgraph.c	(working copy)
@@ -2393,7 +2393,35 @@ cgraph_set_const_flag_1 (cgraph_node *no
       if (DECL_STATIC_DESTRUCTOR (node->decl))
 	DECL_STATIC_DESTRUCTOR (node->decl) = 0;
     }
-  TREE_READONLY (node->decl) = data != NULL;
+
+  /* Consider function:
+
+     bool a(int *p)
+     {
+       return *p==*p;
+     }
+
+     During early optimization we will turn this into:
+
+     bool a(int *p)
+     {
+       return true;
+     }
+
+     Now if this function will be detected as CONST however when interposed it
+     may end up being just pure.  We always must assume the worst scenario here.
+   */
+  if (TREE_READONLY (node->decl))
+    ;
+  else if (node->binds_to_current_def_p ())
+    TREE_READONLY (node->decl) = data != NULL;
+  else
+    {
+      if (dump_file && (dump_flags & TDF_DETAILS))
+	fprintf (dump_file, "Dropping state to PURE because function does "
+		 "not bind to current def.\n");
+      DECL_PURE_P (node->decl) = data != NULL;
+    }
   DECL_LOOPING_CONST_OR_PURE_P (node->decl) = ((size_t)data & 2) != 0;
   return false;
 }
Index: ipa-pure-const.c
===================================================================
--- ipa-pure-const.c	(revision 235063)
+++ ipa-pure-const.c	(working copy)
@@ -440,12 +440,40 @@ better_state (enum pure_const_state_e *s
 }
 
 /* Merge STATE and STATE2 and LOOPING and LOOPING2 and store
-   into STATE and LOOPING worse of the two variants.  */
+   into STATE and LOOPING worse of the two variants.
+   N is the actual node called.  */
 
 static inline void
 worse_state (enum pure_const_state_e *state, bool *looping,
-	     enum pure_const_state_e state2, bool looping2)
-{
+	     enum pure_const_state_e state2, bool looping2,
+	     struct symtab_node *from,
+	     struct symtab_node *to)
+{
+  /* Consider function:
+
+     bool a(int *p)
+     {
+       return *p==*p;
+     }
+
+     During early optimization we will turn this into:
+
+     bool a(int *p)
+     {
+       return true;
+     }
+
+     Now if this function will be detected as CONST however when interposed it
+     may end up being just pure.  We always must assume the worst scenario here.
+   */
+  if (*state == IPA_CONST && state2 == IPA_CONST
+      && to && !TREE_READONLY (to->decl) && !to->binds_to_current_def_p (from))
+    {
+      if (dump_file && (dump_flags & TDF_DETAILS))
+	fprintf (dump_file, "Dropping state to PURE because call to %s may not "
+		 "bind to current def.\n", to->name ());
+      state2 = IPA_PURE;
+    }
   *state = MAX (*state, state2);
   *looping = MAX (*looping, looping2);
 }
@@ -546,7 +574,8 @@ check_call (funct_state local, gcall *ca
       if (special_builtin_state (&call_state, &call_looping, callee_t))
 	{
 	  worse_state (&local->pure_const_state, &local->looping,
-		       call_state, call_looping);
+		       call_state, call_looping,
+		       NULL, NULL);
 	  return;
 	}
       /* When bad things happen to bad functions, they cannot be const
@@ -617,7 +646,7 @@ check_call (funct_state local, gcall *ca
 			 == (ECF_NORETURN | ECF_NOTHROW))
 			|| (!flag_exceptions && (flags & ECF_NORETURN)));
       worse_state (&local->pure_const_state, &local->looping,
-		   call_state, call_looping);
+		   call_state, call_looping, NULL, NULL);
     }
   /* Direct functions calls are handled by IPA propagation.  */
 }
@@ -1134,7 +1161,8 @@ ignore_edge_for_nothrow (struct cgraph_e
     return true;
 
   enum availability avail;
-  cgraph_node *n = e->callee->function_or_virtual_thunk_symbol (&avail);
+  cgraph_node *n = e->callee->function_or_virtual_thunk_symbol (&avail,
+							        e->caller);
   return (avail <= AVAIL_INTERPOSABLE || TREE_NOTHROW (n->decl));
 }
 
@@ -1170,7 +1198,7 @@ static bool
 ignore_edge_for_pure_const (struct cgraph_edge *e)
 {
   enum availability avail;
-  e->callee->function_or_virtual_thunk_symbol (&avail);
+  e->callee->function_or_virtual_thunk_symbol (&avail, e->caller);
   return (avail <= AVAIL_INTERPOSABLE);
 }
 
@@ -1232,18 +1260,25 @@ propagate_pure_const (void)
 		     pure_const_names[w_l->pure_const_state],
 		     w_l->looping);
 
-	  /* First merge in function body properties.  */
+	  /* First merge in function body properties.
+	     We are safe to pass NULL as FROM and TO because we will take care
+	     of possible interposition when walking callees.  */
 	  worse_state (&pure_const_state, &looping,
-		       w_l->pure_const_state, w_l->looping);
+		       w_l->pure_const_state, w_l->looping,
+		       NULL, NULL);
 	  if (pure_const_state == IPA_NEITHER)
 	    break;
 
-	  /* For interposable nodes we can not assume anything.  */
+	  /* For interposable nodes we can not assume anything.
+	     FIXME: It should be safe to remove this conditional and allow
+	     interposable functions with non-interposable aliases next
+	     stage 1.  */
 	  if (w->get_availability () == AVAIL_INTERPOSABLE)
 	    {
 	      worse_state (&pure_const_state, &looping,
 			   w_l->state_previously_known,
-			   w_l->looping_previously_known);
+			   w_l->looping_previously_known,
+			   NULL, NULL);
 	      if (dump_file && (dump_flags & TDF_DETAILS))
 		{
 		  fprintf (dump_file,
@@ -1268,7 +1303,8 @@ propagate_pure_const (void)
 	    {
 	      enum availability avail;
 	      struct cgraph_node *y = e->callee->
-				function_or_virtual_thunk_symbol (&avail);
+				function_or_virtual_thunk_symbol (&avail,
+								  e->caller);
 	      enum pure_const_state_e edge_state = IPA_CONST;
 	      bool edge_looping = false;
 
@@ -1318,7 +1354,7 @@ propagate_pure_const (void)
 			    w_l->state_previously_known,
 			    w_l->looping_previously_known);
 	      worse_state (&pure_const_state, &looping,
-			   edge_state, edge_looping);
+			   edge_state, edge_looping, e->caller, e->callee);
 	      if (pure_const_state == IPA_NEITHER)
 	        break;
 	    }
@@ -1340,7 +1376,7 @@ propagate_pure_const (void)
 			    w_l->state_previously_known,
 			    w_l->looping_previously_known);
 	      worse_state (&pure_const_state, &looping,
-			   edge_state, edge_looping);
+			   edge_state, edge_looping, NULL, NULL);
 	      if (pure_const_state == IPA_NEITHER)
 	        break;
 	    }
@@ -1378,7 +1414,7 @@ propagate_pure_const (void)
 			    w_l->state_previously_known,
 			    w_l->looping_previously_known);
 	      worse_state (&pure_const_state, &looping,
-			   ref_state, ref_looping);
+			   ref_state, ref_looping, NULL, NULL);
 	      if (pure_const_state == IPA_NEITHER)
 		break;
 	    }
@@ -1407,7 +1443,8 @@ propagate_pure_const (void)
 	    {
 	      enum availability avail;
 	      struct cgraph_node *y = e->callee->
-				function_or_virtual_thunk_symbol (&avail);
+				function_or_virtual_thunk_symbol (&avail,
+								  e->caller);
 
 	      if (avail > AVAIL_INTERPOSABLE)
 		can_free = get_function_state (y)->can_free;
@@ -1552,7 +1589,8 @@ propagate_nothrow (void)
 		    continue;
 
 		  struct cgraph_node *y = e->callee->
-				    function_or_virtual_thunk_symbol (&avail);
+				    function_or_virtual_thunk_symbol (&avail,
+								      e->caller);
 
 		  /* We can use info about the callee only if we know it can
 		     not be interposed.  */
@@ -1664,8 +1702,9 @@ make_pass_ipa_pure_const (gcc::context *
 static bool
 skip_function_for_local_pure_const (struct cgraph_node *node)
 {
-  /* Because we do not schedule pass_fixup_cfg over whole program after early optimizations
-     we must not promote functions that are called by already processed functions.  */
+  /* Because we do not schedule pass_fixup_cfg over whole program after early
+     optimizations we must not promote functions that are called by already
+     processed functions.  */
 
   if (function_called_by_processed_nodes_p ())
     {
@@ -1676,7 +1715,8 @@ skip_function_for_local_pure_const (stru
   if (node->get_availability () <= AVAIL_INTERPOSABLE)
     {
       if (dump_file)
-        fprintf (dump_file, "Function is not available or interposable; not analyzing.\n");
+        fprintf (dump_file,
+		 "Function is not available or interposable; not analyzing.\n");
       return true;
     }
   return false;
Index: testsuite/g++.dg/ipa/pure-const-1.C
===================================================================
--- testsuite/g++.dg/ipa/pure-const-1.C	(revision 0)
+++ testsuite/g++.dg/ipa/pure-const-1.C	(working copy)
@@ -0,0 +1,22 @@
+/* { dg-do compile } */
+/* { dg-options "-O2 -fdump-tree-optimized"  } */
+int *ptr;
+static int barvar;
+
+/* We can not detect A to be const because it may be interposed by unoptimized
+   body.  */
+inline
+__attribute__ ((noinline))
+int a(void)
+{
+  return *ptr == *ptr;
+}
+main()
+{
+  int aa;
+  ptr = &barvar;
+  aa=!a();
+  ptr = 0;
+  return aa;
+}
+/* { dg-final { scan-tree-dump "barvar"  "optimized"  } } */
Index: testsuite/g++.dg/ipa/pure-const-2.C
===================================================================
--- testsuite/g++.dg/ipa/pure-const-2.C	(revision 0)
+++ testsuite/g++.dg/ipa/pure-const-2.C	(working copy)
@@ -0,0 +1,26 @@
+/* { dg-do compile } */
+/* { dg-options "-O2 -fdump-tree-optimized"  } */
+int *ptr;
+static int barvar;
+/* We can not detect A to be const because it may be interposed by unoptimized
+   body.  */
+inline
+__attribute__ ((noinline))
+int a(void)
+{
+  return *ptr == *ptr;
+}
+__attribute__ ((noinline))
+static int b(void)
+{
+  return a();
+}
+main()
+{
+  int aa;
+  ptr = &barvar;
+  aa=!b();
+  ptr = 0;
+  return aa;
+}
+/* { dg-final { scan-tree-dump "barvar"  "optimized"  } } */
Index: testsuite/g++.dg/ipa/pure-const-3.C
===================================================================
--- testsuite/g++.dg/ipa/pure-const-3.C	(revision 0)
+++ testsuite/g++.dg/ipa/pure-const-3.C	(working copy)
@@ -0,0 +1,32 @@
+/* { dg-do compile } */
+/* { dg-options "-O2 -fdump-tree-optimized"  } */
+int *ptr;
+static int barvar;
+static int b(int a);
+/* We can not detect A to be const because it may be interposed by unoptimized
+   body.  */
+inline
+__attribute__ ((noinline))
+int a(int a)
+{
+  if (a>0)
+    return b(a-1);
+  return *ptr == *ptr;
+}
+inline
+__attribute__ ((noinline))
+static int b(int p)
+{
+  if (p<0)
+    return a(p+1);
+  return 1;
+}
+main()
+{
+  int aa;
+  ptr = &barvar;
+  aa=!b(3);
+  ptr = 0;
+  return aa;
+}
+/* { dg-final { scan-tree-dump "barvar"  "optimized"  } } */



More information about the Gcc-patches mailing list