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);
