Patchwork Remove dead assignments to static local variables

login
register
mail settings
Submitter Bernd Schmidt
Date June 6, 2013, 1:42 p.m.
Message ID <51B091B5.2010207@codesourcery.com>
Download mbox | patch
Permalink /patch/249420/
State New
Headers show

Comments

Bernd Schmidt - June 6, 2013, 1:42 p.m.
There's a well-known benchmark which uselessly likes to declare local
variables as static. There exist at least two implementations to demote
these to normal register variables. See the discussion thread here:
  http://gcc.gnu.org/ml/gcc-patches/2008-07/msg00982.html
These days, however, we can skip most of the work as pointed out by
Andrew Pinski:
  http://gcc.gnu.org/ml/gcc-patches/2008-07/msg01035.html
PRE usually manages to eliminate all references to the static variable
except a final assignment. The only thing that's left to do is to
enhance DSE to recognize these as dead. So, the following patch is a
cut-down version of CodeSourcery's approach, originally written by
Nathan Froyd, modified to do exactly that.

Bootstrapped and tested on x86_64-linux, all languages except Ada. OK?


Bernd
Richard Guenther - June 6, 2013, 2:52 p.m.
On Thu, Jun 6, 2013 at 3:42 PM, Bernd Schmidt <bernds@codesourcery.com> wrote:
> There's a well-known benchmark which uselessly likes to declare local
> variables as static. There exist at least two implementations to demote
> these to normal register variables. See the discussion thread here:
>   http://gcc.gnu.org/ml/gcc-patches/2008-07/msg00982.html
> These days, however, we can skip most of the work as pointed out by
> Andrew Pinski:
>   http://gcc.gnu.org/ml/gcc-patches/2008-07/msg01035.html
> PRE usually manages to eliminate all references to the static variable
> except a final assignment. The only thing that's left to do is to
> enhance DSE to recognize these as dead. So, the following patch is a
> cut-down version of CodeSourcery's approach, originally written by
> Nathan Froyd, modified to do exactly that.
>
> Bootstrapped and tested on x86_64-linux, all languages except Ada. OK?

Just a few quick questions:

+      /* We cannot optimize away a static used in multiple functions (as
+        might happen in C++).  */
+      && !DECL_NONLOCAL(var)

it may also happen trivially with inlining.  Which means a local pass can never
"remove" vars safely.

In theory we have IPA reference which tries to figure out whether a local static
is read and/or written to (and from where).  It's of course quite early analysis
where FRE may not yet have optimized out all reads.

But the trivial dead local static store elimination would simply eliminate
all write-only and !TREE_ADDRESSABLE vars (and statements storing
to it).

For some reason this must be not enough so you write that local
analysis code.

Thus - I'm asking you to double-check a trivial implementation using
the IPA reference result and double-check the issue with inlining
introducing out-of-current-function uses.

Thanks,
Richard.

>
> Bernd
Bernd Schmidt - June 6, 2013, 3:10 p.m.
On 06/06/2013 04:52 PM, Richard Biener wrote:
> +      /* We cannot optimize away a static used in multiple functions (as
> +        might happen in C++).  */
> +      && !DECL_NONLOCAL(var)
> 
> it may also happen trivially with inlining.  Which means a local pass can never
> "remove" vars safely.

This is why the pass isn't run if cgraph_function_possibly_inlined_p.
Tested by remove-local-statics-14b.c.

> In theory we have IPA reference which tries to figure out whether a local static
> is read and/or written to (and from where).  It's of course quite early analysis
> where FRE may not yet have optimized out all reads.
> 
> But the trivial dead local static store elimination would simply eliminate
> all write-only and !TREE_ADDRESSABLE vars (and statements storing
> to it).
> 
> For some reason this must be not enough so you write that local
> analysis code.
> 
> Thus - I'm asking you to double-check a trivial implementation using
> the IPA reference result and double-check the issue with inlining
> introducing out-of-current-function uses.

I'm not sure what you're asking for here. The IPA passes seem to run
much before PRE, and if you need an example why that's too early, try
the remove-local-statics-7.c testcase.


Bernd
Richard Guenther - June 6, 2013, 3:19 p.m.
On Thu, Jun 6, 2013 at 5:10 PM, Bernd Schmidt <bernds@codesourcery.com> wrote:
> On 06/06/2013 04:52 PM, Richard Biener wrote:
>> +      /* We cannot optimize away a static used in multiple functions (as
>> +        might happen in C++).  */
>> +      && !DECL_NONLOCAL(var)
>>
>> it may also happen trivially with inlining.  Which means a local pass can never
>> "remove" vars safely.
>
> This is why the pass isn't run if cgraph_function_possibly_inlined_p.
> Tested by remove-local-statics-14b.c.

I see (how ugly ;)).  Does that cover versioning via IPA CP as well for example?

>> In theory we have IPA reference which tries to figure out whether a local static
>> is read and/or written to (and from where).  It's of course quite early analysis
>> where FRE may not yet have optimized out all reads.
>>
>> But the trivial dead local static store elimination would simply eliminate
>> all write-only and !TREE_ADDRESSABLE vars (and statements storing
>> to it).
>>
>> For some reason this must be not enough so you write that local
>> analysis code.
>>
>> Thus - I'm asking you to double-check a trivial implementation using
>> the IPA reference result and double-check the issue with inlining
>> introducing out-of-current-function uses.
>
> I'm not sure what you're asking for here. The IPA passes seem to run
> much before PRE, and if you need an example why that's too early, try
> the remove-local-statics-7.c testcase.

Yes, that requires PRE.  I'm sure there are cases your pass doesn't
get either.  I was asking whether the particular benchmark would be
optimized by the simple IPA reference method.

Richard.

>
> Bernd
>

Patch

commit ce5d3fe1bf7934dd551b7bf091f113f396e15d64
Author: Bernd Schmidt <bernds@codesourcery.com>
Date:   Wed Jun 5 15:04:56 2013 +0200

    Extend DSE to remove static local variables that are only set.
    
    Based on an earlier patch by Nathan Froyd and Andrew Stubbs.
    
    	gcc/
    	* cgraph.c (cgraph_node): Set ever_was_nested in the node and
    	its parent when creating a new node.
    	* cgraph.h (struct cgraph_node): New field ever_was_nested.
    	* tree-ssa-dse.c: Include "hashtab.h".
    	(struct rls_decl_info, struct rls_stmt_info): New.
    	(static_variables, defuse_statements, n_statics): New static
    	variables.
    	(rls_hash_decl_info, rls_eq_decl_info, rls_free_decl_info,
    	rls_hash_use_info, rls_eq_use_info, rls_free_use_info, rls_init,
    	rls_done, note_var_ref, mark_used, remove_local_statics,
    	find_static_nonvolatile_declarations, maybe_remove_stmt): New static
    	functions.
    	(tree_ssa_dse): Call remove_local_statics if appropriate.
    	* Makefile.in (tree-ssa-dse.o): Update dependencies.
    
    	gcc/cp/
    	* decl2.c (mark_used): Mark _DECLs as DECL_NONLOCAL if appropriate.
    
    	gcc/testsuite/
    	* g++.dg/remove-local-statics-1.C: New test.
    	* g++.dg/remove-local-statics-2.C: New test.
    	* gcc.dg/remove-local-statics-1.c: New file.
    	* gcc.dg/remove-local-statics-2.c: New file.
    	* gcc.dg/remove-local-statics-3.c: New file.
    	* gcc.dg/remove-local-statics-4.c: New file.
    	* gcc.dg/remove-local-statics-5.c: New file.
    	* gcc.dg/remove-local-statics-6.c: New file.
    	* gcc.dg/remove-local-statics-7.c: New file.
    	* gcc.dg/remove-local-statics-8.c: New file.
    	* gcc.dg/remove-local-statics-9.c: New file.
    	* gcc.dg/remove-local-statics-10.c: New file.
    	* gcc.dg/remove-local-statics-11.c: New file.
    	* gcc.dg/remove-local-statics-12.c: New file.
    	* gcc.dg/remove-local-statics-13.c: New test.
    	* gcc.dg/remove-local-statics-14.c: New test.
    	* gcc.dg/remove-local-statics-15.c: New test.
    	* gcc.dg/remove-local-statics-16.c: New test.
    	* gcc.dg/remove-local-statics-17.c: New test.
    	* gcc.dg/remove-local-statics-18.c: New test.
    	* gcc.dg/tree-ssa/ssa-dse-6.c: Ensure the local variables aren't
    	optimized away.

diff --git a/gcc/Makefile.in b/gcc/Makefile.in
index e95dd63..3acd9fb 100644
--- a/gcc/Makefile.in
+++ b/gcc/Makefile.in
@@ -2306,7 +2306,7 @@  tree-outof-ssa.o : tree-outof-ssa.c $(TREE_FLOW_H) $(CONFIG_H) $(SYSTEM_H) \
 tree-ssa-dse.o : tree-ssa-dse.c $(CONFIG_H) $(SYSTEM_H) coretypes.h \
    $(TM_H) $(GGC_H) $(TREE_H) $(TM_P_H) $(BASIC_BLOCK_H) \
    $(TREE_FLOW_H) $(TREE_PASS_H) domwalk.h $(FLAGS_H) \
-   $(GIMPLE_PRETTY_PRINT_H) langhooks.h
+   $(GIMPLE_PRETTY_PRINT_H) $(HASHTAB_H) langhooks.h
 tree-ssa-forwprop.o : tree-ssa-forwprop.c $(CONFIG_H) $(SYSTEM_H) coretypes.h \
    $(TM_H) $(TREE_H) $(TM_P_H) $(BASIC_BLOCK_H) $(CFGLOOP_H) \
    $(TREE_FLOW_H) $(TREE_PASS_H) $(DIAGNOSTIC_H) \
diff --git a/gcc/cgraph.c b/gcc/cgraph.c
index 445282a..9343e4c 100644
--- a/gcc/cgraph.c
+++ b/gcc/cgraph.c
@@ -531,8 +531,10 @@  cgraph_create_node (tree decl)
   if (DECL_CONTEXT (decl) && TREE_CODE (DECL_CONTEXT (decl)) == FUNCTION_DECL)
     {
       node->origin = cgraph_get_create_node (DECL_CONTEXT (decl));
+      node->origin->ever_was_nested = 1;
       node->next_nested = node->origin->nested;
       node->origin->nested = node;
+      node->ever_was_nested = 1;
     }
   return node;
 }
diff --git a/gcc/cgraph.h b/gcc/cgraph.h
index 276e568..a667f74 100644
--- a/gcc/cgraph.h
+++ b/gcc/cgraph.h
@@ -303,6 +303,8 @@  struct GTY(()) cgraph_node {
   /* Set once the function has been instantiated and its callee
      lists created.  */
   unsigned process : 1;
+  /* Set if the function is a nested function or has nested functions.  */
+  unsigned ever_was_nested : 1;
   /* How commonly executed the node is.  Initialized during branch
      probabilities pass.  */
   ENUM_BITFIELD (node_frequency) frequency : 2;
diff --git a/gcc/cp/decl2.c b/gcc/cp/decl2.c
index 5e7dbcd..8b346cd 100644
--- a/gcc/cp/decl2.c
+++ b/gcc/cp/decl2.c
@@ -4515,6 +4515,15 @@  mark_used (tree decl, tsubst_flags_t complain)
 
   /* Set TREE_USED for the benefit of -Wunused.  */
   TREE_USED (decl) = 1;
+  if (current_function_decl != NULL_TREE
+      && (TREE_CODE (decl) == VAR_DECL
+	  || TREE_CODE (decl) == PARM_DECL
+	  || TREE_CODE (decl) == FUNCTION_DECL))
+    {
+      tree context = decl_function_context (decl);
+      if (context != NULL_TREE && context != current_function_decl)
+	DECL_NONLOCAL (decl) = 1;
+    }
   if (DECL_CLONED_FUNCTION_P (decl))
     TREE_USED (DECL_CLONED_FUNCTION (decl)) = 1;
 
diff --git a/gcc/testsuite/g++.dg/remove-local-statics-1.C b/gcc/testsuite/g++.dg/remove-local-statics-1.C
new file mode 100644
index 0000000..c017892
--- /dev/null
+++ b/gcc/testsuite/g++.dg/remove-local-statics-1.C
@@ -0,0 +1,24 @@ 
+/* Verify that we do not eliminate a static variable in
+   main::Local::Foo.  */
+
+/* { dg-do compile } */
+/* { dg-options "-O2 -fdump-tree-dse2-details  -fdump-tree-dse1-details" } */
+
+int
+main (void)
+{
+   static int thestatic = 0;
+   struct Local {
+     __attribute__((__noinline__))
+     static void Foo () { thestatic = 1; }
+   };
+
+   thestatic = 2;
+   Local::Foo();
+
+   return thestatic++;
+}
+/* { dg-final { scan-tree-dump-times "static variables to consider" 0 "dse1" } } */
+/* { dg-final { scan-tree-dump-times "static variables to consider" 0 "dse2" } } */
+/* { dg-final { cleanup-tree-dump "dse1" } } */
+/* { dg-final { cleanup-tree-dump "dse2" } } */
diff --git a/gcc/testsuite/g++.dg/remove-local-statics-2.C b/gcc/testsuite/g++.dg/remove-local-statics-2.C
new file mode 100644
index 0000000..cb89f4d
--- /dev/null
+++ b/gcc/testsuite/g++.dg/remove-local-statics-2.C
@@ -0,0 +1,24 @@ 
+/* Verify that we do not eliminate a static variable in
+   main due to its use in Local::Foo.  */
+
+/* { dg-do compile } */
+/* { dg-options "-O2 -fdump-tree-dse2-details  -fdump-tree-dse1-details" } */
+
+int
+main (void)
+{
+   static int thestatic = 0;
+   struct Local {
+     __attribute__((__noinline__))
+     static int Foo () { return thestatic; }
+   };
+
+   thestatic = 2;
+   thestatic = Local::Foo();
+
+   return thestatic++;
+}
+/* { dg-final { scan-tree-dump-times "static variables to consider" 0 "dse1" } } */
+/* { dg-final { scan-tree-dump-times "static variables to consider" 0 "dse2" } } */
+/* { dg-final { cleanup-tree-dump "dse1" } } */
+/* { dg-final { cleanup-tree-dump "dse2" } } */
diff --git a/gcc/testsuite/gcc.dg/remove-local-statics-1.c b/gcc/testsuite/gcc.dg/remove-local-statics-1.c
new file mode 100644
index 0000000..e49409a
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/remove-local-statics-1.c
@@ -0,0 +1,16 @@ 
+/* Verify that we eliminate a static local variable where its uses
+   are dominated by a def.  */
+
+/* { dg-do compile } */
+/* { dg-options "-O2" } */
+/* { dg-final { scan-assembler-not "thestatic" } } */
+
+int
+test1 (int x)
+{
+  static int thestatic;
+
+  thestatic = x;
+
+  return thestatic + x;
+}
diff --git a/gcc/testsuite/gcc.dg/remove-local-statics-10.c b/gcc/testsuite/gcc.dg/remove-local-statics-10.c
new file mode 100644
index 0000000..e8038fe
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/remove-local-statics-10.c
@@ -0,0 +1,32 @@ 
+/* Verify that we do not eliminate a static local variable when it is
+   live on return from a function call that recursively calls the
+   function in which the variable is defined.  */
+
+/* { dg-do compile } */
+/* { dg-options "-O2" } */
+/* { dg-final { scan-assembler "thestatic" } } */
+
+int
+test2 (int x)
+{
+  if (x < 0)
+    return 0;
+  else
+    return test1 (x - 1);
+}
+
+int
+test1 (int x)
+{
+  static int thestatic;
+  int y;
+
+  thestatic = x;
+
+  y = test2 (x - 1);
+
+  y += thestatic;
+
+  return y + x;
+}
+  
diff --git a/gcc/testsuite/gcc.dg/remove-local-statics-11.c b/gcc/testsuite/gcc.dg/remove-local-statics-11.c
new file mode 100644
index 0000000..f9fbdb5
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/remove-local-statics-11.c
@@ -0,0 +1,16 @@ 
+/* Verify that we do not eliminate a static local variable when its
+   address is taken.  */
+
+/* { dg-do compile } */
+/* { dg-options "-O2" } */
+/* { dg-final { scan-assembler "thestatic" } } */
+
+int *
+test1 (int x)
+{
+  static int thestatic;
+
+  thestatic = x;
+
+  return &thestatic + x;
+}
diff --git a/gcc/testsuite/gcc.dg/remove-local-statics-12.c b/gcc/testsuite/gcc.dg/remove-local-statics-12.c
new file mode 100644
index 0000000..9e57fff
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/remove-local-statics-12.c
@@ -0,0 +1,20 @@ 
+/* Verify that we do not eliminate a static variable when it is declared
+   in a function that has nested functions.  */
+
+/* { dg-do compile } */
+/* { dg-options "-O2" } */
+/* { dg-final { scan-assembler "thestatic" } } */
+
+int test1 (int x)
+{
+  static int thestatic;
+
+  int nested_test1 (int x)
+  {
+    return x + thestatic;
+  }
+
+  thestatic = x;
+
+  return thestatic + x + nested_test1 (x);
+}
diff --git a/gcc/testsuite/gcc.dg/remove-local-statics-13.c b/gcc/testsuite/gcc.dg/remove-local-statics-13.c
new file mode 100644
index 0000000..3f8be1b
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/remove-local-statics-13.c
@@ -0,0 +1,24 @@ 
+/* We used to ICE on this test, because the call to BAR appeared to
+   define both static variables in FOO.  Verify that we no longer do
+   this.  */
+
+/* { dg-do compile } */
+/* { dg-options "-O2" } */
+/* { dg-final { scan-assembler "static1" } } */
+/* { dg-final { scan-assembler-not "static2" } } */
+
+int foo(int i) {
+  static int static1 = 0;
+  static int static2;
+
+  if (static2 = bar(i))
+    static1 = 1;
+  static2 = static1 + 30;
+
+  return static1 + static2;
+}
+
+int bar(int i) {
+  if (i) { foo(i-1); return 0; }
+  return 1;
+}
diff --git a/gcc/testsuite/gcc.dg/remove-local-statics-14.c b/gcc/testsuite/gcc.dg/remove-local-statics-14.c
new file mode 100644
index 0000000..f93160c
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/remove-local-statics-14.c
@@ -0,0 +1,29 @@ 
+/* Verify that we do eliminate a static local variable whose last use is
+   in a statement containing a call expression.  */
+
+/* { dg-do compile } */
+/* { dg-options "-O2" } */
+/* { dg-final { scan-assembler-not "thestatic" } } */
+
+int
+test2 (int x)
+{
+  if (x < 0)
+    return 0;
+  else
+    return test1 (x - 1);
+}
+
+__attribute__((noinline,noclone)) int
+test1 (int x)
+{
+  static int thestatic;
+  int y;
+
+  thestatic = x;
+
+  y = test2 (thestatic - 1);
+
+  return y + x;
+}
+  
diff --git a/gcc/testsuite/gcc.dg/remove-local-statics-14b.c b/gcc/testsuite/gcc.dg/remove-local-statics-14b.c
new file mode 100644
index 0000000..61587f4
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/remove-local-statics-14b.c
@@ -0,0 +1,29 @@ 
+/* Verify that we do not eliminate a static local variable if the function
+   containing it is inlined.  */
+
+/* { dg-do compile } */
+/* { dg-options "-O2" } */
+/* { dg-final { scan-assembler "thestatic" } } */
+
+int
+test2 (int x)
+{
+  if (x < 0)
+    return 0;
+  else
+    return test1 (x - 1);
+}
+
+inline int
+test1 (int x)
+{
+  static int thestatic;
+  int y;
+
+  thestatic = x;
+
+  y = test2 (thestatic - 1);
+
+  return y + x;
+}
+  
diff --git a/gcc/testsuite/gcc.dg/remove-local-statics-15.c b/gcc/testsuite/gcc.dg/remove-local-statics-15.c
new file mode 100644
index 0000000..87e1956
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/remove-local-statics-15.c
@@ -0,0 +1,19 @@ 
+/* Verify that we do not consider an array variable for local static
+   elimination.  */
+
+/* { dg-do compile } */
+/* { dg-options "-O2 -fdump-tree-dse2-details  -fdump-tree-dse1-details" } */
+
+int foo (void)
+{
+  static int a[1];
+
+  a[0] = 0;
+
+  return a[0];
+}
+
+/* { dg-final { scan-tree-dump-times "static variables to consider" 0 "dse1" } } */
+/* { dg-final { scan-tree-dump-times "static variables to consider" 0 "dse2" } } */
+/* { dg-final { cleanup-tree-dump "dse1" } } */
+/* { dg-final { cleanup-tree-dump "dse2" } } */
diff --git a/gcc/testsuite/gcc.dg/remove-local-statics-16.c b/gcc/testsuite/gcc.dg/remove-local-statics-16.c
new file mode 100644
index 0000000..c4fa24d
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/remove-local-statics-16.c
@@ -0,0 +1,22 @@ 
+/* Verify that we do not consider an structure variable for local static
+   elimination.  */
+
+/* { dg-do compile } */
+/* { dg-options "-O2 -fdump-tree-dse2-details  -fdump-tree-dse1-details" } */
+
+int foo (void)
+{
+  static struct {
+    int x;
+    int y;
+  } a;
+
+  a.x = 0;
+
+  return a.y;
+}
+
+/* { dg-final { scan-tree-dump-times "static variables to consider" 0 "dse1" } } */
+/* { dg-final { scan-tree-dump-times "static variables to consider" 0 "dse2" } } */
+/* { dg-final { cleanup-tree-dump "dse1" } } */
+/* { dg-final { cleanup-tree-dump "dse2" } } */
diff --git a/gcc/testsuite/gcc.dg/remove-local-statics-17.c b/gcc/testsuite/gcc.dg/remove-local-statics-17.c
new file mode 100644
index 0000000..d4e9b39
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/remove-local-statics-17.c
@@ -0,0 +1,20 @@ 
+/* Verify that we do not eliminate a static variable that is "defined"
+   by an asm that clobbers memory.  */
+
+/* { dg-do compile } */
+/* { dg-options "-O2 -fdump-tree-dse2-details  -fdump-tree-dse1-details" } */
+
+int foo (void)
+{
+  static int thestatic = 0;
+
+  __asm__ __volatile__ ("" : : : "memory");
+
+  thestatic++;
+
+  return thestatic;
+}
+/* { dg-final { scan-tree-dump-times "static variables to consider" 0 "dse1" } } */
+/* { dg-final { scan-tree-dump-times "static variables to consider" 0 "dse2" } } */
+/* { dg-final { cleanup-tree-dump "dse1" } } */
+/* { dg-final { cleanup-tree-dump "dse2" } } */
diff --git a/gcc/testsuite/gcc.dg/remove-local-statics-18.c b/gcc/testsuite/gcc.dg/remove-local-statics-18.c
new file mode 100644
index 0000000..56a46e1
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/remove-local-statics-18.c
@@ -0,0 +1,47 @@ 
+/* Verify that we do not eliminate the static variable `x' when `func'
+   is inlined and its return value become dead.  */
+
+/* { dg-do run } */
+/* { dg-output "" } */
+/* { dg-options "-O2" } */
+
+void abort(void);
+
+static int guard;
+static int func(int y)
+{
+  static int x;
+  if (guard == 0)
+    {
+      x = y;
+      guard = 1;
+    }
+  return x + y;
+}
+
+int __attribute__((noinline)) call1(int a)
+{
+  func(a);
+  return 0;
+}
+
+int __attribute__((noinline)) call2(int a)
+{
+  return func(a);
+}
+
+int global1 = 3;
+int global2 = 5;
+
+extern int printf (const char *, ...);
+
+int main()
+{
+  call1 (global1);
+  printf ("call2: %d\n", call2(global2));
+#if 0
+  if (call2 (global2) != 8)
+    abort ();
+#endif
+  return 0;
+}
diff --git a/gcc/testsuite/gcc.dg/remove-local-statics-2.c b/gcc/testsuite/gcc.dg/remove-local-statics-2.c
new file mode 100644
index 0000000..762c2c0
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/remove-local-statics-2.c
@@ -0,0 +1,19 @@ 
+/* Verify that we do not eliminate a static local variable when its uses
+   are not dominated by a def.  */
+
+/* { dg-do compile } */
+/* { dg-options "-O2" } */
+/* { dg-final { scan-assembler "first_time" } } */
+
+int
+test1 (int x)
+{
+  static int first_time;
+
+  if (x == 1)
+    first_time = 1;
+  else if (x > 0)
+    first_time = 2;
+
+  return first_time + x;
+}
diff --git a/gcc/testsuite/gcc.dg/remove-local-statics-3.c b/gcc/testsuite/gcc.dg/remove-local-statics-3.c
new file mode 100644
index 0000000..be7dd26
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/remove-local-statics-3.c
@@ -0,0 +1,16 @@ 
+/* Verify that we do not eliminate a static local variable whose uses
+   are dominated by a def when the variable is volatile.  */
+
+/* { dg-do compile } */
+/* { dg-options "-O2" } */
+/* { dg-final { scan-assembler "thestatic" } } */
+
+int
+test1 (int x)
+{
+  static volatile int thestatic;
+
+  thestatic = x;
+
+  return thestatic + x;
+}
diff --git a/gcc/testsuite/gcc.dg/remove-local-statics-4.c b/gcc/testsuite/gcc.dg/remove-local-statics-4.c
new file mode 100644
index 0000000..c3b9230
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/remove-local-statics-4.c
@@ -0,0 +1,15 @@ 
+/* Verify that we don't eliminate a global static variable.  */
+
+/* { dg-do compile } */
+/* { dg-options "-O2" } */
+/* { dg-final { scan-assembler "global_static" } } */
+
+static int global_static;
+
+int
+test1 (int x)
+{
+  global_static = x;
+
+  return global_static + x;
+}
diff --git a/gcc/testsuite/gcc.dg/remove-local-statics-5.c b/gcc/testsuite/gcc.dg/remove-local-statics-5.c
new file mode 100644
index 0000000..81e0c51
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/remove-local-statics-5.c
@@ -0,0 +1,24 @@ 
+/* Verify that we do not eliminate a static local variable whose uses
+   are dominated by a def when the function calls setjmp.  */
+
+/* { dg-do compile } */
+/* { dg-options "-O2" } */
+/* { dg-final { scan-assembler "thestatic" } } */
+
+#include <setjmp.h>
+
+int
+foo (int x)
+{
+  static int thestatic;
+  int retval;
+  jmp_buf env;
+
+  thestatic = x;
+
+  retval = thestatic + x;
+
+  setjmp (env);
+
+  return retval;
+}
diff --git a/gcc/testsuite/gcc.dg/remove-local-statics-6.c b/gcc/testsuite/gcc.dg/remove-local-statics-6.c
new file mode 100644
index 0000000..b04759f
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/remove-local-statics-6.c
@@ -0,0 +1,16 @@ 
+/* Verify that we do not eliminate a static local variable whose uses
+   are dominated by a def when the variable is addressed.  */
+
+/* { dg-do compile } */
+/* { dg-options "-O2" } */
+/* { dg-final { scan-assembler "thestatic" } } */
+
+int *
+test1 (int x)
+{
+  static int thestatic;
+
+  thestatic = x;
+
+  return &thestatic + x;
+}
diff --git a/gcc/testsuite/gcc.dg/remove-local-statics-7.c b/gcc/testsuite/gcc.dg/remove-local-statics-7.c
new file mode 100644
index 0000000..1177bdd
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/remove-local-statics-7.c
@@ -0,0 +1,19 @@ 
+/* Verify that we eliminate a static local variable where it is defined
+   along all paths leading to a use.  */
+
+/* { dg-do compile } */
+/* { dg-options "-O2" } */
+/* { dg-final { scan-assembler-not "thestatic" } } */
+
+int
+test1 (int x)
+{
+  static int thestatic;
+
+  if (x < 0)
+    thestatic = x;
+  else
+    thestatic = -x;
+
+  return thestatic + x;
+}
diff --git a/gcc/testsuite/gcc.dg/remove-local-statics-8.c b/gcc/testsuite/gcc.dg/remove-local-statics-8.c
new file mode 100644
index 0000000..e1b0825
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/remove-local-statics-8.c
@@ -0,0 +1,33 @@ 
+/* Verify that we eliminate a static local variable when it is dead on
+   return from a function call that recursively calls the function in
+   which the variable is defined.  */
+
+/* { dg-do compile } */
+/* { dg-options "-O2" } */
+/* { dg-final { scan-assembler-not "thestatic" } } */
+
+int test1 (int);
+int test2 (int);
+
+int
+test2 (int x)
+{
+  if (x < 0)
+    return 0;
+  else
+    return test1 (x - 1);
+}
+
+int
+test1 (int x)
+{
+  static int thestatic;
+  int y;
+
+  thestatic = x;
+
+  y = thestatic;
+
+  return y + x + test1 (x - 1) + test2 (x - 1);
+}
+  
diff --git a/gcc/testsuite/gcc.dg/remove-local-statics-9.c b/gcc/testsuite/gcc.dg/remove-local-statics-9.c
new file mode 100644
index 0000000..58cd325
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/remove-local-statics-9.c
@@ -0,0 +1,32 @@ 
+/* Verify that we eliminate a static local variable when it is live
+   on return from a function call that does not recursively call the
+   function in which the variable is defined.  */
+
+/* { dg-do compile } */
+/* { dg-options "-O2" } */
+/* { dg-final { scan-assembler-not "thestatic" } } */
+
+static int
+test2 (int x)
+{
+  if (x < 0)
+    return 0;
+  else
+    return x + test2 (x - 1);
+}
+
+int
+test1 (int x)
+{
+  static int thestatic;
+  int y;
+
+  thestatic = x;
+
+  y = test2 (x - 1);
+
+  y += thestatic;
+
+  return y + x;
+}
+  
diff --git a/gcc/testsuite/gcc.dg/tree-ssa/ssa-dse-6.c b/gcc/testsuite/gcc.dg/tree-ssa/ssa-dse-6.c
index 3d02006..fae3392 100644
--- a/gcc/testsuite/gcc.dg/tree-ssa/ssa-dse-6.c
+++ b/gcc/testsuite/gcc.dg/tree-ssa/ssa-dse-6.c
@@ -1,9 +1,12 @@ 
 /* { dg-do compile } */
 /* { dg-options "-O2 -fdump-tree-dse1" } */
 
+int *x1, *x2;
 int foo11 (int c)
 {
   static int local1, local2;
+  x1 = &local1;
+  x2 = &local2;
   local1 = 0;
   local2 += c;
   local1 = 2;
diff --git a/gcc/tree-ssa-dse.c b/gcc/tree-ssa-dse.c
index ad99ea9..5c4a32a 100644
--- a/gcc/tree-ssa-dse.c
+++ b/gcc/tree-ssa-dse.c
@@ -23,6 +23,7 @@  along with GCC; see the file COPYING3.  If not see
 #include "tm.h"
 #include "ggc.h"
 #include "tree.h"
+#include "hashtab.h"
 #include "tm_p.h"
 #include "basic-block.h"
 #include "gimple-pretty-print.h"
@@ -301,6 +302,310 @@  dse_enter_block (struct dom_walk_data *walk_data ATTRIBUTE_UNUSED,
     }
 }
 
+/* Sub-pass to remove unused assignments to local static variables that
+   are never read.  */
+
+/* Describe a potential candidate variable for the optimization.  */
+struct rls_decl_info
+{
+  /* The variable declaration.  */
+  tree var;
+
+  /* Whether we can optimize this variable.  */
+  bool optimizable_p;
+};
+
+/* Filled with 'struct rls_decl_info'; keyed off VAR.  */
+static htab_t static_variables;
+
+/* Describe a statement assigning to one of our candidate variables.  */
+struct rls_stmt_info
+{
+  /* Information about the variable.  */
+  struct rls_decl_info *info;
+
+  /* The statement in which we found a def or a use of the variable.  */
+  gimple stmt;
+};
+
+/* Filled with 'struct rls_stmt_info'; keyed off STMT.  */
+static htab_t defuse_statements;
+
+/* The number of static variables we found.  */
+static int n_statics;
+
+/* Parameters for the 'static_variables' hash table.  */
+
+static hashval_t
+rls_hash_decl_info (const void *x)
+{
+  return htab_hash_pointer
+    ((const void *) ((const struct rls_decl_info *) x)->var);
+}
+
+static int
+rls_eq_decl_info (const void *x, const void *y)
+{
+  const struct rls_decl_info *a = (const struct rls_decl_info *) x;
+  const struct rls_decl_info *b = (const struct rls_decl_info *) y;
+
+  return a->var == b->var;
+}
+
+static void
+rls_free_decl_info (void *info)
+{
+  free (info);
+}
+
+/* Parameters for the 'defuse_statements' hash table.  */
+
+static hashval_t
+rls_hash_use_info (const void *x)
+{
+  return htab_hash_pointer
+    ((const void *) ((const struct rls_stmt_info *) x)->stmt);
+}
+
+static int
+rls_eq_use_info (const void *x, const void *y)
+{
+  const struct rls_stmt_info *a = (const struct rls_stmt_info *) x;
+  const struct rls_stmt_info *b = (const struct rls_stmt_info *) y;
+
+  return a->stmt == b->stmt;
+}
+
+static void
+rls_free_use_info (void *info)
+{
+  struct rls_stmt_info *stmt_info = (struct rls_stmt_info *) info;
+
+  free (stmt_info);
+}
+
+/* Initialize data structures and statistics.  */
+
+static void
+rls_init (void)
+{
+  /* We expect relatively few static variables, hence the small
+     initial size for the hash table.  */
+  static_variables = htab_create (8, rls_hash_decl_info,
+                                  rls_eq_decl_info, rls_free_decl_info);
+
+  /* We expect quite a few statements.  */
+  defuse_statements = htab_create (128, rls_hash_use_info,
+                                   rls_eq_use_info, rls_free_use_info);
+
+  n_statics = 0;
+}
+
+/* Free data structures.  */
+
+static void
+rls_done (void)
+{
+  htab_delete (static_variables);
+  htab_delete (defuse_statements);
+}
+
+
+/* Doing the initial work to find static variables.  */
+
+/* Examine VAR, known to be a VAR_DECL, and determine whether it is a
+   static variable we could potentially optimize.  If so, stick in it in
+   the 'static_variables' hashtable.
+
+   STMT is the statement in which a definition or use of VAR occurs.
+   USE_P indicates whether VAR is used or defined in STMT.  Enter STMT
+   into 'defuse_statements' as well for use during dataflow
+   analysis.  */
+
+static void
+note_var_ref (tree var, gimple stmt, bool use_p)
+{
+  if (TREE_CODE (var) == VAR_DECL
+      /* We cannot optimize statics that were defined in another
+	 function.  The static may be non-optimizable in its original
+	 function, but optimizable when said function is inlined due to
+	 DCE of its uses.  (e.g. the only use was in a return statement
+	 and the function is inlined in a void context.)  */
+      && DECL_CONTEXT (var) == current_function_decl
+      /* We cannot optimize away a static used in multiple functions (as
+	 might happen in C++).  */
+      && !DECL_NONLOCAL(var)
+      && TREE_STATIC (var)
+      /* We cannot optimize away aggregate statics, as we would have to
+	 prove that definitions of every field of the aggregate dominate
+	 uses.  */
+      && !AGGREGATE_TYPE_P (TREE_TYPE (var))
+      /* GCC doesn't normally treat vectors as aggregates; we need to,
+	 though, since a user could use intrinsics to read/write
+	 particular fields of the vector, thereby treating it as an
+	 array.  */
+      && TREE_CODE (TREE_TYPE (var)) != VECTOR_TYPE
+      && !TREE_ADDRESSABLE (var)
+      && !TREE_THIS_VOLATILE (var))
+    {
+      struct rls_decl_info dummy;
+      void **slot;
+      struct rls_decl_info *info;
+
+      dummy.var = var;
+      slot = htab_find_slot (static_variables, &dummy, INSERT);
+      info = (struct rls_decl_info *)*slot;
+      if (info == NULL)
+        {
+          /* Found a use or a def of a new declaration.  */
+          info = XNEW (struct rls_decl_info);
+
+          info->var = var;
+          info->optimizable_p = !use_p;
+	  if (!use_p)
+	    n_statics++;
+          *slot = (void *) info;
+        }
+      if (use_p)
+	{
+	  if (info->optimizable_p)
+	    n_statics--;
+	  info->optimizable_p = false;
+	  return;
+	}
+
+      /* Enter the statement into DEFUSE_STATEMENTS.  */
+      {
+        struct rls_stmt_info dummy;
+        struct rls_stmt_info *stmt_info;
+
+        dummy.stmt = stmt;
+        slot = htab_find_slot (defuse_statements, &dummy, INSERT);
+
+        /* We should never insert the same statement into the
+           hashtable twice.  */
+        gcc_assert (*slot == NULL);
+
+        stmt_info = XNEW (struct rls_stmt_info);
+        stmt_info->info = info;
+        stmt_info->stmt = stmt;
+        if (dump_file)
+          {
+            fprintf (dump_file, "entering def ");
+            print_gimple_stmt (dump_file, stmt, 0, TDF_DETAILS | TDF_VOPS);
+          }
+        *slot = (void *) stmt_info;
+      }
+    }
+}
+
+/* Helper functions for walk_stmt_load_store_ops.  Used to detect uses
+   of static variables outside of assignments.  */
+
+static bool
+mark_used (gimple stmt ATTRIBUTE_UNUSED, tree t, void *data ATTRIBUTE_UNUSED)
+{
+  note_var_ref (t, stmt, true);
+  return true;
+}
+
+/* Grovel through all the statements in the program, looking for
+   SSA_NAMEs whose SSA_NAME_VAR is a VAR_DECL.  We look at both use and
+   def SSA_NAMEs.  */
+
+static void
+find_static_nonvolatile_declarations (void)
+{
+  basic_block bb;
+
+  FOR_EACH_BB (bb)
+    {
+      gimple_stmt_iterator i;
+
+      for (i = gsi_start_bb (bb); !gsi_end_p (i); gsi_next (&i))
+        {
+	  gimple stmt = gsi_stmt (i);
+
+	  if (gimple_code (stmt) == GIMPLE_ASM
+	      && gimple_asm_clobbers_memory_p (stmt))
+	    {
+	      /* Abort this optimization if an asm with a memory clobber is
+		 seen; it must be assumed to also read memory.  */
+	      n_statics = 0;
+	      return;
+	    }
+	  /* Static variables usually only occur in plain assignments
+	     that copy to or from a temporary.  */
+	  if (gimple_assign_single_p (stmt))
+	    {
+	      tree lhs = gimple_assign_lhs (stmt);
+	      tree rhs = gimple_assign_rhs1 (stmt);
+	      note_var_ref (lhs, stmt, false);
+	      note_var_ref (rhs, stmt, true);
+	      continue;
+	    }
+
+	  /* If they occur anywhere else, such as in function arguments,
+	     they must not be optimized away.  */
+	  walk_stmt_load_store_ops (stmt, NULL, mark_used, mark_used);
+        }
+    }
+}
+
+/* Traverse the 'defuse_statements' hash table.  For every use,
+   determine if the associated variable is defined along all paths
+   leading to said use.  Remove the associated variable from
+   'static_variables' if it is not.  */
+
+static int
+maybe_remove_stmt (void **slot, void *data ATTRIBUTE_UNUSED)
+{
+  struct rls_stmt_info *info = (struct rls_stmt_info *) *slot;
+
+  if (info->info->optimizable_p)
+    {
+      gimple_stmt_iterator bsi = gsi_for_stmt (info->stmt);
+
+      if (dump_file)
+	{
+	  fprintf (dump_file, "removing stmt ");
+	  print_gimple_stmt (dump_file, info->stmt, 0, 0);
+	  fprintf (dump_file, "\n");
+	}
+      reset_debug_uses (info->stmt);
+      unlink_stmt_vdef (info->stmt);
+      gsi_remove (&bsi, true);
+      release_defs (info->stmt);
+    }
+  return 1;
+}
+
+/* Remove local static variables that are only assigned to.  Return
+   end-of-pass TODO flags.  */
+static unsigned int
+remove_local_statics (void)
+{
+  rls_init ();
+
+  find_static_nonvolatile_declarations ();
+
+  /* Can we optimize anything?  */
+  if (n_statics != 0)
+    {
+      htab_traverse (defuse_statements, maybe_remove_stmt, NULL);
+      if (dump_file)
+        fprintf (dump_file, "removed %d static variables\n",
+                 n_statics);
+    }
+
+  rls_done ();
+
+  if (n_statics > 0)
+    return TODO_rebuild_alias | TODO_update_ssa;
+  else
+    return 0;
+}
+
 /* Main entry point.  */
 
 static unsigned int
@@ -350,6 +655,12 @@  tree_ssa_dse (void)
     
   /* For now, just wipe the post-dominator information.  */
   free_dominance_info (CDI_POST_DOMINATORS);
+
+  if (!cfun->calls_setjmp
+      && !cgraph_get_node (current_function_decl)->ever_was_nested
+      && !cgraph_function_possibly_inlined_p (current_function_decl))
+    return remove_local_statics ();
+
   return 0;
 }