diff mbox series

[v3] DCE __cxa_atexit calls where the function is pure/const [PR19661]

Message ID 20240504235826.3130451-1-quic_apinski@quicinc.com
State New
Headers show
Series [v3] DCE __cxa_atexit calls where the function is pure/const [PR19661] | expand

Commit Message

Andrew Pinski May 4, 2024, 11:58 p.m. UTC
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.
  * v3: Fix testcases for the __aeabi_atexit (forgot to do in the v2).

	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_apinski@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

Comments

Jeff Law May 7, 2024, 8:38 p.m. UTC | #1
On 5/4/24 5:58 PM, Andrew Pinski wrote:
> 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.
>    * v3: Fix testcases for the __aeabi_atexit (forgot to do in the v2).
> 
> 	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.
OK
jeff
diff mbox series

Patch

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..82ff3d2b778
--- /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|__aeabiv1::__aeabi_atexit)" 1 "cddce1" } } */
+/* { dg-final { scan-tree-dump-not "__cxa_atexit|__aeabi_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..726b6d7f156
--- /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|__aeabiv1::__aeabi_atexit)" "cddce1" } } */
+/* { dg-final { scan-tree-dump-times "(?:__cxa_atexit|__aeabi_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..42cc7ccb11b
--- /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|__aeabiv1::__aeabi_atexit)" "cddce1" } } */
+/* { dg-final { scan-tree-dump-times "(?:__cxa_atexit|__aeabi_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..591c1c0552a
--- /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|__aeabiv1::__aeabi_atexit)" 1 "cddce1" } } */
+/* { dg-final { scan-tree-dump-not "(?:__cxa_atexit|__aeabi_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..e59f70c1ed4
--- /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|__aeabiv1::__aeabi_atexit)" 1 "dce2" } } */
+/* { dg-final { scan-tree-dump-not "(?:__cxa_atexit|__aeabi_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..f6599a3c9f4
--- /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|__aeabiv1::__aeabi_atexit)" "cddce1" } } */
+/* { dg-final { scan-tree-dump-times "(?:__cxa_atexit|__aeabi_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);