In C++ sometimes you have a deconstructor function which is "empty", like for an
example with unions or with arrays.  The front-end might not know it is empty 
either
so this should be done on during optimization.o
To implement it I added it to DCE where we mark if a statement is necessary or 
not.

Bootstrapped and tested on x86_64-linux-gnu with no regressions.

Changes since v1:
  * v2: Add support for __aeabi_atexit for arm-*eabi. Add extra comments.
        Add cxa_atexit-5.C testcase for -fPIC case.

        PR tree-optimization/19661

gcc/ChangeLog:

        * tree-ssa-dce.cc (is_cxa_atexit): New function.
        (is_removable_cxa_atexit_call): New function.
        (mark_stmt_if_obviously_necessary): Don't mark removable
        cxa_at_exit calls.
        (mark_all_reaching_defs_necessary_1): Likewise.
        (propagate_necessity): Likewise.

gcc/testsuite/ChangeLog:

        * g++.dg/tree-ssa/cxa_atexit-1.C: New test.
        * g++.dg/tree-ssa/cxa_atexit-2.C: New test.
        * g++.dg/tree-ssa/cxa_atexit-3.C: New test.
        * g++.dg/tree-ssa/cxa_atexit-4.C: New test.
        * g++.dg/tree-ssa/cxa_atexit-5.C: New test.
        * g++.dg/tree-ssa/cxa_atexit-6.C: New test.

Signed-off-by: Andrew Pinski <quic_apin...@quicinc.com>
---
 gcc/testsuite/g++.dg/tree-ssa/cxa_atexit-1.C | 20 +++++++
 gcc/testsuite/g++.dg/tree-ssa/cxa_atexit-2.C | 21 +++++++
 gcc/testsuite/g++.dg/tree-ssa/cxa_atexit-3.C | 19 +++++++
 gcc/testsuite/g++.dg/tree-ssa/cxa_atexit-4.C | 20 +++++++
 gcc/testsuite/g++.dg/tree-ssa/cxa_atexit-5.C | 39 +++++++++++++
 gcc/testsuite/g++.dg/tree-ssa/cxa_atexit-6.C | 24 ++++++++
 gcc/tree-ssa-dce.cc                          | 58 ++++++++++++++++++++
 7 files changed, 201 insertions(+)
 create mode 100644 gcc/testsuite/g++.dg/tree-ssa/cxa_atexit-1.C
 create mode 100644 gcc/testsuite/g++.dg/tree-ssa/cxa_atexit-2.C
 create mode 100644 gcc/testsuite/g++.dg/tree-ssa/cxa_atexit-3.C
 create mode 100644 gcc/testsuite/g++.dg/tree-ssa/cxa_atexit-4.C
 create mode 100644 gcc/testsuite/g++.dg/tree-ssa/cxa_atexit-5.C
 create mode 100644 gcc/testsuite/g++.dg/tree-ssa/cxa_atexit-6.C

diff --git a/gcc/testsuite/g++.dg/tree-ssa/cxa_atexit-1.C 
b/gcc/testsuite/g++.dg/tree-ssa/cxa_atexit-1.C
new file mode 100644
index 00000000000..1f5f431c7e4
--- /dev/null
+++ b/gcc/testsuite/g++.dg/tree-ssa/cxa_atexit-1.C
@@ -0,0 +1,20 @@
+/* { dg-do compile } */
+/* { dg-options "-O2 -fdump-tree-cddce1-details -fdump-tree-optimized" } */
+// { dg-require-effective-target cxa_atexit }
+/* PR tree-optimization/19661 */
+
+/* The call to axexit should be removed as A::~A() is a pure/const function 
call
+   and there is no visible effect if A::~A() call does not happen.  */
+
+struct A { 
+    A(); 
+    ~A() {} 
+}; 
+ 
+void foo () { 
+  static A a; 
+} 
+
+/* { dg-final { scan-tree-dump-times "Deleting : __cxxabiv1::__cxa_atexit" 1 
"cddce1" } } */
+/* { dg-final { scan-tree-dump-not "__cxa_atexit" "optimized" } } */
+
diff --git a/gcc/testsuite/g++.dg/tree-ssa/cxa_atexit-2.C 
b/gcc/testsuite/g++.dg/tree-ssa/cxa_atexit-2.C
new file mode 100644
index 00000000000..4d0656b455c
--- /dev/null
+++ b/gcc/testsuite/g++.dg/tree-ssa/cxa_atexit-2.C
@@ -0,0 +1,21 @@
+/* { dg-do compile { target c++11 } } */
+/* { dg-options "-O2 -fdump-tree-cddce1-details -fdump-tree-optimized" } */
+// { dg-require-effective-target cxa_atexit }
+/* PR tree-optimization/19661 */
+
+/* The call to axexit should be not removed as A::~A() as it marked with 
noipa.  */
+
+struct A { 
+    A(); 
+    ~A();
+}; 
+
+[[gnu::noipa]] A::~A() {}
+ 
+void foo () { 
+  static A a; 
+} 
+
+/* { dg-final { scan-tree-dump-not "Deleting : __cxxabiv1::__cxa_atexit" 
"cddce1" } } */
+/* { dg-final { scan-tree-dump-times "__cxa_atexit" 1 "optimized" } } */
+
diff --git a/gcc/testsuite/g++.dg/tree-ssa/cxa_atexit-3.C 
b/gcc/testsuite/g++.dg/tree-ssa/cxa_atexit-3.C
new file mode 100644
index 00000000000..03a19209661
--- /dev/null
+++ b/gcc/testsuite/g++.dg/tree-ssa/cxa_atexit-3.C
@@ -0,0 +1,19 @@
+/* { dg-do compile } */
+/* { dg-options "-O2 -fdump-tree-cddce1-details -fdump-tree-optimized" } */
+// { dg-require-effective-target cxa_atexit }
+/* PR tree-optimization/19661 */
+
+/* We should not remove the call to atexit as A::~A is unknown.  */
+
+struct A { 
+    A(); 
+    ~A();
+}; 
+
+void foo () { 
+  static A a; 
+} 
+
+/* { dg-final { scan-tree-dump-not "Deleting : __cxxabiv1::__cxa_atexit" 
"cddce1" } } */
+/* { dg-final { scan-tree-dump-times "__cxa_atexit" 1 "optimized" } } */
+
diff --git a/gcc/testsuite/g++.dg/tree-ssa/cxa_atexit-4.C 
b/gcc/testsuite/g++.dg/tree-ssa/cxa_atexit-4.C
new file mode 100644
index 00000000000..b85a7efd16b
--- /dev/null
+++ b/gcc/testsuite/g++.dg/tree-ssa/cxa_atexit-4.C
@@ -0,0 +1,20 @@
+/* { dg-do compile { target c++11 } } */
+/* { dg-options "-O2 -fdump-tree-cddce1-details -fdump-tree-optimized -w" } */
+// { dg-require-effective-target cxa_atexit }
+/* PR tree-optimization/19661 */
+
+/* The call to axexit should be removed as A::~A() is a pure/const function 
call
+   and there is no visible effect if A::~A() call does not happen.  */
+
+struct A { 
+    A(); 
+    [[gnu::pure]] ~A();
+}; 
+ 
+void foo () { 
+  static A a; 
+} 
+
+/* { dg-final { scan-tree-dump-times "Deleting : __cxxabiv1::__cxa_atexit" 1 
"cddce1" } } */
+/* { dg-final { scan-tree-dump-not "__cxa_atexit" "optimized" } } */
+
diff --git a/gcc/testsuite/g++.dg/tree-ssa/cxa_atexit-5.C 
b/gcc/testsuite/g++.dg/tree-ssa/cxa_atexit-5.C
new file mode 100644
index 00000000000..f96395df913
--- /dev/null
+++ b/gcc/testsuite/g++.dg/tree-ssa/cxa_atexit-5.C
@@ -0,0 +1,39 @@
+/* { dg-do compile { target c++20 } } */
+/* { dg-options "-O2 -fdump-tree-dce2-details -fdump-tree-optimized" } */
+// { dg-require-effective-target cxa_atexit }
+/* PR tree-optimization/19661 */
+
+/* The call to axexit should be removed as constant_init::~constant_init is a 
pure/const function call
+   and there is no visible effect if constant_init::~constant_init() call does 
not happen.  */
+/* This takes until DCE2 as constant_init::~constant_init is not figured out 
being pure/const until late. */
+
+extern "C" int puts(const char*);
+
+struct A
+{
+  constexpr A()  { }
+  ~A() { puts("bye"); }
+};
+
+namespace
+{
+  struct constant_init
+  {
+    union {
+      A obj;
+    };
+    constexpr constant_init() : obj() { }
+
+    ~constant_init() { /* do nothing, union member is not destroyed */ }
+  };
+
+
+  // Single-threaded fallback buffer.
+  constinit constant_init global;
+}
+
+extern "C" A* get() { return &global.obj; }
+
+/* { dg-final { scan-tree-dump-times "Deleting : __cxxabiv1::__cxa_atexit" 1 
"dce2" } } */
+/* { dg-final { scan-tree-dump-not "__cxa_atexit" "optimized" } } */
+
diff --git a/gcc/testsuite/g++.dg/tree-ssa/cxa_atexit-6.C 
b/gcc/testsuite/g++.dg/tree-ssa/cxa_atexit-6.C
new file mode 100644
index 00000000000..11e9d0c53db
--- /dev/null
+++ b/gcc/testsuite/g++.dg/tree-ssa/cxa_atexit-6.C
@@ -0,0 +1,24 @@
+/* { dg-do compile } */
+/* { dg-require-effective-target fpic } */
+/* { dg-options "-O2 -fdump-tree-cddce1-details -fdump-tree-optimized -fPIC" } 
*/
+// { dg-require-effective-target cxa_atexit }
+/* PR tree-optimization/19661 */
+
+/* The call to axexit should not be removed as A::~A() cannot be figured if it
+   is a pure/const function call as the function call g does not bind locally. 
*/
+
+__attribute__((noinline))
+void g() {}
+
+struct A { 
+    A(); 
+    ~A() { g(); } 
+}; 
+ 
+void foo () { 
+  static A a; 
+} 
+
+/* { dg-final { scan-tree-dump-not "Deleting : __cxxabiv1::__cxa_atexit" 
"cddce1" } } */
+/* { dg-final { scan-tree-dump-times "__cxa_atexit" 1 "optimized" } } */
+
diff --git a/gcc/tree-ssa-dce.cc b/gcc/tree-ssa-dce.cc
index 636c471d4c8..69249c73013 100644
--- a/gcc/tree-ssa-dce.cc
+++ b/gcc/tree-ssa-dce.cc
@@ -124,6 +124,55 @@ keep_all_vdefs_p ()
   return optimize_debug;
 }
 
+/* 1 if CALLEE is the function __cxa_atexit.
+   2 if CALLEE is the function __aeabi_atexit.
+   0 otherwise.   */
+
+static inline int
+is_cxa_atexit (const_tree callee)
+{
+  if (callee != NULL_TREE
+      && strcmp (IDENTIFIER_POINTER (DECL_NAME (callee)), "__cxa_atexit") == 0)
+    return 1;
+  if (callee != NULL_TREE
+      && strcmp (IDENTIFIER_POINTER (DECL_NAME (callee)), "__aeabi_atexit") == 
0)
+    return 2;
+  return 0;
+}
+
+/* True if STMT is a call to __cxa_atexit (or __aeabi_atexit)
+   and the function argument to that call is a const  or pure
+   non-looping function decl.  */
+
+static inline bool
+is_removable_cxa_atexit_call (gimple *stmt)
+{
+  tree callee = gimple_call_fndecl (stmt);
+  int functype = is_cxa_atexit (callee);
+  if (!functype)
+    return false;
+  if (gimple_call_num_args (stmt) != 3)
+    return false;
+
+  /* The function argument is the 1st argument for __cxa_atexit
+     or the 2nd argument for __eabi_atexit. */
+  tree arg = gimple_call_arg (stmt, functype == 2 ? 1 : 0);
+  if (TREE_CODE (arg) != ADDR_EXPR)
+    return false;
+  arg = TREE_OPERAND (arg, 0);
+  if (TREE_CODE (arg) != FUNCTION_DECL)
+    return false;
+  int flags = flags_from_decl_or_type (arg);
+
+  /* If the function is noreturn then it cannot be removed. */
+  if (flags & ECF_NORETURN)
+    return false;
+
+  /* The function needs to be const or pure and non looping.  */
+  return (flags & (ECF_CONST|ECF_PURE))
+         && !(flags & ECF_LOOPING_CONST_OR_PURE);
+}
+
 /* If STMT is not already marked necessary, mark it, and add it to the
    worklist if ADD_TO_WORKLIST is true.  */
 
@@ -251,6 +300,10 @@ mark_stmt_if_obviously_necessary (gimple *stmt, bool 
aggressive)
            && DECL_IS_REPLACEABLE_OPERATOR_NEW_P (callee))
          return;
 
+       /* For __cxa_atexit calls, don't mark as necessary right away. */
+       if (is_removable_cxa_atexit_call (stmt))
+         return;
+
        /* IFN_GOACC_LOOP calls are necessary in that they are used to
           represent parameter (i.e. step, bound) of a lowered OpenACC
           partitioned loop.  But this kind of partitioned loop might not
@@ -626,6 +679,8 @@ mark_all_reaching_defs_necessary_1 (ao_ref *ref 
ATTRIBUTE_UNUSED,
              || DECL_IS_OPERATOR_DELETE_P (callee))
          && gimple_call_from_new_or_delete (call))
        return false;
+      if (is_removable_cxa_atexit_call (call))
+       return false;
     }
 
   if (! gimple_clobber_p (def_stmt))
@@ -930,6 +985,9 @@ propagate_necessity (bool aggressive)
                  && gimple_call_from_new_or_delete (call))
                continue;
 
+             if (is_removable_cxa_atexit_call (call))
+               continue;
+
              /* Calls implicitly load from memory, their arguments
                 in addition may explicitly perform memory loads.  */
              mark_all_reaching_defs_necessary (call);
-- 
2.43.0

Reply via email to