diff mbox

Fix 70199

Message ID 56E86591.1040904@redhat.com
State New
Headers show

Commit Message

Richard Henderson March 15, 2016, 7:42 p.m. UTC
On 03/15/2016 07:13 AM, Richard Biener wrote:
> On Tue, Mar 15, 2016 at 4:44 AM, Richard Henderson <rth@redhat.com> wrote:
>> The problem here is that
>>
>>    void* labels[] = {
>>      &&l0, &&l1, &&l2
>>    };
>>
>> gets gimplified to
>>
>>    labels = *.LC0;
>>
>> but .LC0 is not in the set of local decls, so that when copy_forbidden is
>> called during sra versioning we fail to forbid the copy.  We could set a
>> different flag, but I think it's easiest to just add the artificial decl to
>> where it can be seen.
>>
>> Ok?
>
> Hmm.  tree_output_constant_def uses the global constant pool (and not
> function-scope statics).  So while for the above case with local labels
> there can be no sharing and thus the decl is really "local" with non-local
> labels or with other random initializers you'd have the ctor decl in
> multiple local decl vectors.  Not sure if that's a problem, but at least
> if you'd have
>
>    void* labels[] = {
>      &&l0, &&l1, &&l2
>    };
>    void* labels2[] = {
>      &&l0, &&l1, &&l2
>    };
>
> you'll end up with the same constant pool decl in local-decls twice.

Yeah, but since the decl is TREE_STATIC, we'll ignore it for almost everything. 
  About the only thing I can figure that might go wrong is unused variable 
removal, where we'd remove the first copy but not look for duplicates, and so 
the variable stays in use when it isn't.  I don't *think* that can cause 
further problems.  It's not like we ever clear FORCED_LABEL even if the data 
referencing it goes away.

> It's also
> a bit pre-mature in the gimplifier as we only add to local-decls during
> BIND expr lowering.

Yeah, I suppose.  Though for a TREE_STATIC decl it doesn't make a difference 
that we didn't put it into any BIND_EXPR.

> I also wonder if outputting the constant pool decl far away from the labels
> might end up with invalid asm for some targets.

No.  The pointers involved here are full address space, not reduced 
displacement pc-relative.

> Well, I don't see any convenient way of fixing things here either but maybe
> we can do
>
>    if (walk_tree_without_duplicataes (&DECL_INITIAL (ctor),
> has_label_address_in_static_1, cfun->decl))
>      add_local_decl (cfun, ctor);
>
> to avoid adding the decl when it is not necessary.

Sure.  Patch 1 below.

> Having another struct function flag would be possible as well, or re-use
> has_nonlocal_label as clearly a global static is now refering to a local
> label (you'd lose optimization when 'labels' becomes unused of course).

On the other hand, the likelyhood of these labels (or the data referencing the 
labels) going away is slim.  Except for artificial test cases, the user is 
going to have taken these addresses and put them in an array for a reason.  The 
likelyhood of some stored FORCED_LABEL becoming non-forced is virtually nil.

Patch 2 below.  This second patch does have lower complexity, and doesn't have 
the duplicated entry issue you point out.

Thoughts?


r~

diff --git a/gcc/function.h b/gcc/function.h
index c4368cd..501ef68 100644
--- a/gcc/function.h
+++ b/gcc/function.h
@@ -328,6 +328,10 @@ struct GTY(()) function {
      from nested functions.  */
   unsigned int has_nonlocal_label : 1;
 
+  /* Nonzero if function being compiled has a forced label
+     placed into static storage.  */
+  unsigned int has_forced_label_in_static : 1;
+
   /* Nonzero if we've set cannot_be_copied_reason.  I.e. if
      (cannot_be_copied_set && !cannot_be_copied_reason), the function
      can in fact be copied.  */
diff --git a/gcc/gimplify.c b/gcc/gimplify.c
index b331e41..c60ac13 100644
--- a/gcc/gimplify.c
+++ b/gcc/gimplify.c
@@ -1414,7 +1414,10 @@ force_labels_r (tree *tp, int *walk_subtrees, void *data ATTRIBUTE_UNUSED)
   if (TYPE_P (*tp))
     *walk_subtrees = 0;
   if (TREE_CODE (*tp) == LABEL_DECL)
-    FORCED_LABEL (*tp) = 1;
+    {
+      FORCED_LABEL (*tp) = 1;
+      cfun->has_forced_label_in_static = 1;
+    }
 
   return NULL_TREE;
 }
diff --git a/gcc/testsuite/gcc.c-torture/compile/pr70199.c b/gcc/testsuite/gcc.c-torture/compile/pr70199.c
new file mode 100644
index 0000000..a4323f0
--- /dev/null
+++ b/gcc/testsuite/gcc.c-torture/compile/pr70199.c
@@ -0,0 +1,20 @@
+static volatile int v = 0;
+static
+void benchmark(long runs) {
+  void* labels[] = {
+    &&l0, &&l1, &&l2
+  };
+  for(unsigned int mask = 0x1F; mask > 0; mask >>= 1) {
+    unsigned lfsr = 0xACE1u;
+    long n = 10000000;
+    while(n > 0) {
+      l2: v;
+      l1: v;
+      goto *labels[lfsr & mask];
+      l0: n--;
+    }
+  }
+}
+int f(void) {
+  benchmark(10000000);
+}
diff --git a/gcc/tree-inline.c b/gcc/tree-inline.c
index d52e0c6..65ec2f9 100644
--- a/gcc/tree-inline.c
+++ b/gcc/tree-inline.c
@@ -3549,19 +3549,12 @@ copy_forbidden (struct function *fun, tree fndecl)
       goto fail;
     }
 
-  FOR_EACH_LOCAL_DECL (fun, ix, decl)
-    if (TREE_CODE (decl) == VAR_DECL
-	&& TREE_STATIC (decl)
-	&& !DECL_EXTERNAL (decl)
-	&& DECL_INITIAL (decl)
-	&& walk_tree_without_duplicates (&DECL_INITIAL (decl),
-					 has_label_address_in_static_1,
-					 fndecl))
-      {
-	reason = G_("function %q+F can never be copied because it saves "
-		    "address of local label in a static variable");
-	goto fail;
-      }
+  if (fun->has_forced_label_in_static)
+    {
+      reason = G_("function %q+F can never be copied because it saves "
+		  "address of local label in a static variable");
+      goto fail;
+    }
 
  fail:
   fun->cannot_be_copied_reason = reason;

Comments

Richard Biener March 16, 2016, 9:40 a.m. UTC | #1
On Tue, Mar 15, 2016 at 8:42 PM, Richard Henderson <rth@redhat.com> wrote:
> On 03/15/2016 07:13 AM, Richard Biener wrote:
>>
>> On Tue, Mar 15, 2016 at 4:44 AM, Richard Henderson <rth@redhat.com> wrote:
>>>
>>> The problem here is that
>>>
>>>    void* labels[] = {
>>>      &&l0, &&l1, &&l2
>>>    };
>>>
>>> gets gimplified to
>>>
>>>    labels = *.LC0;
>>>
>>> but .LC0 is not in the set of local decls, so that when copy_forbidden is
>>> called during sra versioning we fail to forbid the copy.  We could set a
>>> different flag, but I think it's easiest to just add the artificial decl
>>> to
>>> where it can be seen.
>>>
>>> Ok?
>>
>>
>> Hmm.  tree_output_constant_def uses the global constant pool (and not
>> function-scope statics).  So while for the above case with local labels
>> there can be no sharing and thus the decl is really "local" with non-local
>> labels or with other random initializers you'd have the ctor decl in
>> multiple local decl vectors.  Not sure if that's a problem, but at least
>> if you'd have
>>
>>    void* labels[] = {
>>      &&l0, &&l1, &&l2
>>    };
>>    void* labels2[] = {
>>      &&l0, &&l1, &&l2
>>    };
>>
>> you'll end up with the same constant pool decl in local-decls twice.
>
>
> Yeah, but since the decl is TREE_STATIC, we'll ignore it for almost
> everything.  About the only thing I can figure that might go wrong is unused
> variable removal, where we'd remove the first copy but not look for
> duplicates, and so the variable stays in use when it isn't.  I don't *think*
> that can cause further problems.  It's not like we ever clear FORCED_LABEL
> even if the data referencing it goes away.
>
>> It's also
>> a bit pre-mature in the gimplifier as we only add to local-decls during
>> BIND expr lowering.
>
>
> Yeah, I suppose.  Though for a TREE_STATIC decl it doesn't make a difference
> that we didn't put it into any BIND_EXPR.
>
>> I also wonder if outputting the constant pool decl far away from the
>> labels
>> might end up with invalid asm for some targets.
>
>
> No.  The pointers involved here are full address space, not reduced
> displacement pc-relative.
>
>> Well, I don't see any convenient way of fixing things here either but
>> maybe
>> we can do
>>
>>    if (walk_tree_without_duplicataes (&DECL_INITIAL (ctor),
>> has_label_address_in_static_1, cfun->decl))
>>      add_local_decl (cfun, ctor);
>>
>> to avoid adding the decl when it is not necessary.
>
>
> Sure.  Patch 1 below.
>
>> Having another struct function flag would be possible as well, or re-use
>> has_nonlocal_label as clearly a global static is now refering to a local
>> label (you'd lose optimization when 'labels' becomes unused of course).
>
>
> On the other hand, the likelyhood of these labels (or the data referencing
> the labels) going away is slim.  Except for artificial test cases, the user
> is going to have taken these addresses and put them in an array for a
> reason.  The likelyhood of some stored FORCED_LABEL becoming non-forced is
> virtually nil.
>
> Patch 2 below.  This second patch does have lower complexity, and doesn't
> have the duplicated entry issue you point out.

I like patch 2 more - btw, you need to add has_forced_label_in_static streaming
to lto-streamer-{in,out}.c, just look for has_nonlocal_label streaming.  Also
has_label_address_in_static_1 is now unused and should be removed.

Thanks,
Richard.

> Thoughts?
>
>
> r~
diff mbox

Patch

diff --git a/gcc/gimplify.c b/gcc/gimplify.c
index b331e41..cf50271 100644
--- a/gcc/gimplify.c
+++ b/gcc/gimplify.c
@@ -4016,6 +4016,14 @@  gimplify_init_constructor (tree *expr_p, gimple_seq *pre_p, gimple_seq *post_p,
 
 		walk_tree (&ctor, force_labels_r, NULL, NULL);
 		ctor = tree_output_constant_def (ctor);
+
+		/* If the ctor has a label in it, we need to remember the
+		   decl so that copy_forbidden can find it.  But for anything
+		   else we don't want to place the global variable on the
+		   local decls list.  */
+		if (has_label_address_in_static (ctor, cfun->decl))
+		  add_local_decl (cfun, ctor);
+
 		if (!useless_type_conversion_p (type, TREE_TYPE (ctor)))
 		  ctor = build1 (VIEW_CONVERT_EXPR, type, ctor);
 		TREE_OPERAND (*expr_p, 1) = ctor;
diff --git a/gcc/testsuite/gcc.c-torture/compile/pr70199.c b/gcc/testsuite/gcc.c-torture/compile/pr70199.c
new file mode 100644
index 0000000..a4323f0
--- /dev/null
+++ b/gcc/testsuite/gcc.c-torture/compile/pr70199.c
@@ -0,0 +1,20 @@ 
+static volatile int v = 0;
+static
+void benchmark(long runs) {
+  void* labels[] = {
+    &&l0, &&l1, &&l2
+  };
+  for(unsigned int mask = 0x1F; mask > 0; mask >>= 1) {
+    unsigned lfsr = 0xACE1u;
+    long n = 10000000;
+    while(n > 0) {
+      l2: v;
+      l1: v;
+      goto *labels[lfsr & mask];
+      l0: n--;
+    }
+  }
+}
+int f(void) {
+  benchmark(10000000);
+}
diff --git a/gcc/tree-inline.c b/gcc/tree-inline.c
index d52e0c6..cac2340 100644
--- a/gcc/tree-inline.c
+++ b/gcc/tree-inline.c
@@ -3522,6 +3522,17 @@  has_label_address_in_static_1 (tree *nodep, int *walk_subtrees, void *fnp)
   return NULL_TREE;
 }
 
+/* Determine if the DECL_INITIAL of DECL makes a reference to a label that
+   is local to FNDECL.  */
+
+bool
+has_label_address_in_static (tree decl, tree fndecl)
+{
+  return walk_tree_without_duplicates (&DECL_INITIAL (decl),
+				       has_label_address_in_static_1,
+				       fndecl) != NULL_TREE;
+}
+
 /* Determine if the function can be copied.  If so return NULL.  If
    not return a string describng the reason for failure.  */
 
@@ -3554,9 +3565,7 @@  copy_forbidden (struct function *fun, tree fndecl)
 	&& TREE_STATIC (decl)
 	&& !DECL_EXTERNAL (decl)
 	&& DECL_INITIAL (decl)
-	&& walk_tree_without_duplicates (&DECL_INITIAL (decl),
-					 has_label_address_in_static_1,
-					 fndecl))
+	&& has_label_address_in_static (decl, fndecl))
       {
 	reason = G_("function %q+F can never be copied because it saves "
 		    "address of local label in a static variable");
diff --git a/gcc/tree-inline.h b/gcc/tree-inline.h
index 4cc1f19..3b01091 100644
--- a/gcc/tree-inline.h
+++ b/gcc/tree-inline.h
@@ -217,6 +217,7 @@  extern tree remap_type (tree type, copy_body_data *id);
 extern gimple_seq copy_gimple_seq_and_replace_locals (gimple_seq seq);
 extern bool debug_find_tree (tree, tree);
 extern tree copy_fn (tree, tree&, tree&);
+extern bool has_label_address_in_static (tree decl, tree fndecl);
 extern const char *copy_forbidden (struct function *fun, tree fndecl);
 
 /* This is in tree-inline.c since the routine uses