diff mbox series

[C++] Implement P0722R3, destroying operator delete.

Message ID 20181113043948.9628-1-jason@redhat.com
State New
Headers show
Series [C++] Implement P0722R3, destroying operator delete. | expand

Commit Message

Jason Merrill Nov. 13, 2018, 4:39 a.m. UTC
A destroying operator delete takes responsibility for calling the destructor
for the object it is deleting; this is intended to be useful for sized
delete of a class allocated with a trailing buffer, where the compiler can't
know the size of the allocation, and so would pass the wrong size to the
non-destroying sized operator delete.

Tested x86_64-pc-linux-gnu, applying to trunk.  Can someone from the
libstdc++ team clean up my libsupc++ change if it should be formatted
differently?

gcc/c-family/
	* c-cppbuiltin.c (c_cpp_builtins): Define
	__cpp_impl_destroying_delete.
gcc/cp/
	* call.c (std_destroying_delete_t_p, destroying_delete_p): New.
	(aligned_deallocation_fn_p, usual_deallocation_fn_p): Use
	destroying_delete_p.
	(build_op_delete_call): Handle destroying delete.
	* decl2.c (coerce_delete_type): Handle destroying delete.
	* init.c (build_delete): Don't call dtor with destroying delete.
	* optimize.c (build_delete_destructor_body): Likewise.
libstdc++-v3/
	* libsupc++/new (std::destroying_delete_t): New.
---
 gcc/cp/cp-tree.h                              |  3 +-
 gcc/c-family/c-cppbuiltin.c                   |  1 +
 gcc/cp/call.c                                 | 45 +++++++++++++++++++
 gcc/cp/decl.c                                 |  2 +-
 gcc/cp/decl2.c                                | 32 ++++++++++---
 gcc/cp/init.c                                 |  8 +++-
 gcc/cp/optimize.c                             | 27 +++++++----
 .../g++.dg/cpp2a/destroying-delete1.C         | 41 +++++++++++++++++
 gcc/testsuite/g++.dg/cpp2a/feat-cxx2a.C       |  4 ++
 gcc/c-family/ChangeLog                        |  3 ++
 gcc/cp/ChangeLog                              |  9 ++++
 libstdc++-v3/ChangeLog                        |  4 ++
 libstdc++-v3/libsupc++/new                    | 12 +++++
 13 files changed, 174 insertions(+), 17 deletions(-)
 create mode 100644 gcc/testsuite/g++.dg/cpp2a/destroying-delete1.C


base-commit: e771eb36d40e1ad0345fa990cfe83405439e6159

Comments

Jonathan Wakely Nov. 13, 2018, 9:54 a.m. UTC | #1
On Tue, 13 Nov 2018 at 04:39, Jason Merrill wrote:
> Tested x86_64-pc-linux-gnu, applying to trunk.  Can someone from the
> libstdc++ team clean up my libsupc++ change if it should be formatted
> differently?

Looks fine to me, thanks.
diff mbox series

Patch

diff --git a/gcc/cp/cp-tree.h b/gcc/cp/cp-tree.h
index 9c4664c3aa7..c4d79c0cf7f 100644
--- a/gcc/cp/cp-tree.h
+++ b/gcc/cp/cp-tree.h
@@ -6127,6 +6127,7 @@  extern tree build_new_op			(location_t, enum tree_code,
 extern tree build_op_call			(tree, vec<tree, va_gc> **,
 						 tsubst_flags_t);
 extern bool aligned_allocation_fn_p		(tree);
+extern tree destroying_delete_p			(tree);
 extern bool usual_deallocation_fn_p		(tree);
 extern tree build_op_delete_call		(enum tree_code, tree, tree,
 						 bool, tree, tree,
@@ -6456,7 +6457,7 @@  extern void cplus_decl_attributes		(tree *, tree, int);
 extern void finish_anon_union			(tree);
 extern void cxx_post_compilation_parsing_cleanups (void);
 extern tree coerce_new_type			(tree, location_t);
-extern tree coerce_delete_type			(tree, location_t);
+extern void coerce_delete_type			(tree, location_t);
 extern void comdat_linkage			(tree);
 extern void determine_visibility		(tree);
 extern void constrain_class_visibility		(tree);
diff --git a/gcc/c-family/c-cppbuiltin.c b/gcc/c-family/c-cppbuiltin.c
index 8dd62158b62..7daa3e33990 100644
--- a/gcc/c-family/c-cppbuiltin.c
+++ b/gcc/c-family/c-cppbuiltin.c
@@ -980,6 +980,7 @@  c_cpp_builtins (cpp_reader *pfile)
 	  /* Set feature test macros for C++2a.  */
 	  cpp_define (pfile, "__cpp_conditional_explicit=201806");
 	  cpp_define (pfile, "__cpp_nontype_template_parameter_class=201806");
+	  cpp_define (pfile, "__cpp_impl_destroying_delete=201806");
 	}
       if (flag_concepts)
 	cpp_define (pfile, "__cpp_concepts=201507");
diff --git a/gcc/cp/call.c b/gcc/cp/call.c
index 6f401567c2e..b668e031d3c 100644
--- a/gcc/cp/call.c
+++ b/gcc/cp/call.c
@@ -6190,6 +6190,31 @@  aligned_allocation_fn_p (tree t)
   return (a && same_type_p (TREE_VALUE (a), align_type_node));
 }
 
+/* True if T is std::destroying_delete_t.  */
+
+static bool
+std_destroying_delete_t_p (tree t)
+{
+  return (TYPE_CONTEXT (t) == std_node
+	  && id_equal (TYPE_IDENTIFIER (t), "destroying_delete_t"));
+}
+
+/* A deallocation function with at least two parameters whose second parameter
+   type is of type std::destroying_delete_t is a destroying operator delete. A
+   destroying operator delete shall be a class member function named operator
+   delete. [ Note: Array deletion cannot use a destroying operator
+   delete. --end note ] */
+
+tree
+destroying_delete_p (tree t)
+{
+  tree a = TYPE_ARG_TYPES (TREE_TYPE (t));
+  if (!a || !TREE_CHAIN (a))
+    return NULL_TREE;
+  tree type = TREE_VALUE (TREE_CHAIN (a));
+  return std_destroying_delete_t_p (type) ? type : NULL_TREE;
+}
+
 /* Returns true iff T, an element of an OVERLOAD chain, is a usual deallocation
    function (3.7.4.2 [basic.stc.dynamic.deallocation]) with a parameter of
    std::align_val_t.  */
@@ -6207,6 +6232,8 @@  aligned_deallocation_fn_p (tree t)
     return false;
 
   tree a = FUNCTION_ARG_CHAIN (t);
+  if (destroying_delete_p (t))
+    a = TREE_CHAIN (a);
   if (same_type_p (TREE_VALUE (a), align_type_node)
       && TREE_CHAIN (a) == void_list_node)
     return true;
@@ -6242,6 +6269,8 @@  usual_deallocation_fn_p (tree t)
   tree chain = FUNCTION_ARG_CHAIN (t);
   if (!chain)
     return false;
+  if (destroying_delete_p (t))
+    chain = TREE_CHAIN (chain);
   if (chain == void_list_node
       || ((!global || flag_sized_deallocation)
 	  && second_parm_is_size_t (t)))
@@ -6307,6 +6336,7 @@  build_op_delete_call (enum tree_code code, tree addr, tree size,
     fns = lookup_name_nonclass (fnname);
 
   /* Strip const and volatile from addr.  */
+  tree oaddr = addr;
   addr = cp_convert (ptr_type_node, addr, complain);
 
   if (placement)
@@ -6484,9 +6514,24 @@  build_op_delete_call (enum tree_code code, tree addr, tree size,
 	}
       else
 	{
+	  tree destroying = destroying_delete_p (fn);
+	  if (destroying)
+	    {
+	      /* Strip const and volatile from addr but retain the type of the
+		 object.  */
+	      tree rtype = TREE_TYPE (TREE_TYPE (oaddr));
+	      rtype = cv_unqualified (rtype);
+	      rtype = TYPE_POINTER_TO (rtype);
+	      addr = cp_convert (rtype, oaddr, complain);
+	      destroying = build_functional_cast (destroying, NULL_TREE,
+						  complain);
+	    }
+
 	  tree ret;
 	  vec<tree, va_gc> *args = make_tree_vector ();
 	  args->quick_push (addr);
+	  if (destroying)
+	    args->quick_push (destroying);
 	  if (second_parm_is_size_t (fn))
 	    args->quick_push (size);
 	  if (aligned_deallocation_fn_p (fn))
diff --git a/gcc/cp/decl.c b/gcc/cp/decl.c
index cf934159395..42994055d5f 100644
--- a/gcc/cp/decl.c
+++ b/gcc/cp/decl.c
@@ -13401,7 +13401,7 @@  grok_op_properties (tree decl, bool complain)
 	}
 
       if (op_flags & OVL_OP_FLAG_DELETE)
-	TREE_TYPE (decl) = coerce_delete_type (TREE_TYPE (decl), loc);
+	coerce_delete_type (decl, loc);
       else
 	{
 	  DECL_IS_OPERATOR_NEW (decl) = 1;
diff --git a/gcc/cp/decl2.c b/gcc/cp/decl2.c
index a163558af54..13c156b947d 100644
--- a/gcc/cp/decl2.c
+++ b/gcc/cp/decl2.c
@@ -1739,10 +1739,11 @@  coerce_new_type (tree type, location_t loc)
   return type;
 }
 
-tree
-coerce_delete_type (tree type, location_t loc)
+void
+coerce_delete_type (tree decl, location_t loc)
 {
   int e = 0;
+  tree type = TREE_TYPE (decl);
   tree args = TYPE_ARG_TYPES (type);
 
   gcc_assert (TREE_CODE (type) == FUNCTION_TYPE);
@@ -1754,19 +1755,38 @@  coerce_delete_type (tree type, location_t loc)
 		void_type_node);
     }
 
+  tree ptrtype = ptr_type_node;
+  if (destroying_delete_p (decl))
+    {
+      if (DECL_CLASS_SCOPE_P (decl))
+	/* If the function is a destroying operator delete declared in class type
+	   C, the type of its first parameter shall be C*.  */
+	ptrtype = TYPE_POINTER_TO (DECL_CONTEXT (decl));
+      else
+	/* A destroying operator delete shall be a class member function named
+	   operator delete.  */
+	error_at (loc, "destroying operator delete must be a member function");
+      const ovl_op_info_t *op = IDENTIFIER_OVL_OP_INFO (DECL_NAME (decl));
+      if (op->flags & OVL_OP_FLAG_VEC)
+	error_at (loc, "operator delete[] cannot be a destroying delete");
+      if (!usual_deallocation_fn_p (decl))
+	error_at (loc, "destroying operator delete must be a usual "
+		  "deallocation function");
+    }
+
   if (!args || args == void_list_node
-      || !same_type_p (TREE_VALUE (args), ptr_type_node))
+      || !same_type_p (TREE_VALUE (args), ptrtype))
     {
       e = 2;
       if (args && args != void_list_node)
 	args = TREE_CHAIN (args);
       error_at (loc, "%<operator delete%> takes type %qT as first parameter",
-		ptr_type_node);
+		ptrtype);
     }
   switch (e)
   {
     case 2:
-      args = tree_cons (NULL_TREE, ptr_type_node, args);
+      args = tree_cons (NULL_TREE, ptrtype, args);
       /* Fall through.  */
     case 1:
       type = (cxx_copy_lang_qualifiers
@@ -1776,7 +1796,7 @@  coerce_delete_type (tree type, location_t loc)
     default:;
   }
 
-  return type;
+  TREE_TYPE (decl) = type;
 }
 
 /* DECL is a VAR_DECL for a vtable: walk through the entries in the vtable
diff --git a/gcc/cp/init.c b/gcc/cp/init.c
index a17e1608c80..5a314862c80 100644
--- a/gcc/cp/init.c
+++ b/gcc/cp/init.c
@@ -4782,6 +4782,7 @@  build_delete (tree otype, tree addr, special_function_kind auto_delete,
 
   tree head = NULL_TREE;
   tree do_delete = NULL_TREE;
+  bool destroying_delete = false;
 
   if (!deleting)
     {
@@ -4820,6 +4821,11 @@  build_delete (tree otype, tree addr, special_function_kind auto_delete,
 					complain);
       /* Call the complete object destructor.  */
       auto_delete = sfk_complete_destructor;
+      if (do_delete != error_mark_node)
+	{
+	  tree fn = get_callee_fndecl (do_delete);
+	  destroying_delete = destroying_delete_p (fn);
+	}
     }
   else if (TYPE_GETS_REG_DELETE (type))
     {
@@ -4832,7 +4838,7 @@  build_delete (tree otype, tree addr, special_function_kind auto_delete,
 			    complain);
     }
 
-  if (type_build_dtor_call (type))
+  if (!destroying_delete && type_build_dtor_call (type))
     expr = build_dtor_call (cp_build_fold_indirect_ref (addr),
 			    auto_delete, flags, complain);
   else
diff --git a/gcc/cp/optimize.c b/gcc/cp/optimize.c
index 3923a5fc6c4..da068b5931a 100644
--- a/gcc/cp/optimize.c
+++ b/gcc/cp/optimize.c
@@ -117,11 +117,6 @@  build_delete_destructor_body (tree delete_dtor, tree complete_dtor)
   tree parm = DECL_ARGUMENTS (delete_dtor);
   tree virtual_size = cxx_sizeof (current_class_type);
 
-  /* Call the corresponding complete destructor.  */
-  gcc_assert (complete_dtor);
-  tree call_dtor = build_cxx_call (complete_dtor, 1, &parm,
-				   tf_warning_or_error);
-
   /* Call the delete function.  */
   tree call_delete = build_op_delete_call (DELETE_EXPR, current_class_ptr,
 					   virtual_size,
@@ -130,10 +125,26 @@  build_delete_destructor_body (tree delete_dtor, tree complete_dtor)
 					   /*alloc_fn=*/NULL_TREE,
 					   tf_warning_or_error);
 
-  /* Operator delete must be called, whether or not the dtor throws.  */
-  add_stmt (build2 (TRY_FINALLY_EXPR, void_type_node, call_dtor, call_delete));
+  tree op = get_callee_fndecl (call_delete);
+  if (op && DECL_P (op) && destroying_delete_p (op))
+    {
+      /* The destroying delete will handle calling complete_dtor.  */
+      add_stmt (call_delete);
+    }
+  else
+    {
+      /* Call the corresponding complete destructor.  */
+      gcc_assert (complete_dtor);
+      tree call_dtor = build_cxx_call (complete_dtor, 1, &parm,
+				       tf_warning_or_error);
+
+      /* Operator delete must be called, whether or not the dtor throws.  */
+      add_stmt (build2 (TRY_FINALLY_EXPR, void_type_node,
+			call_dtor, call_delete));
+    }
 
-  /* Return the address of the object.  */
+  /* Return the address of the object.
+     ??? How is it useful to return an invalid address?  */
   if (targetm.cxx.cdtor_returns_this ())
     {
       tree val = DECL_ARGUMENTS (delete_dtor);
diff --git a/gcc/testsuite/g++.dg/cpp2a/destroying-delete1.C b/gcc/testsuite/g++.dg/cpp2a/destroying-delete1.C
new file mode 100644
index 00000000000..329588be7ab
--- /dev/null
+++ b/gcc/testsuite/g++.dg/cpp2a/destroying-delete1.C
@@ -0,0 +1,41 @@ 
+// { dg-do run { target c++2a } }
+
+#include <new>
+
+int adt, adl;
+struct A {
+  ~A() { ++adt; }
+  void operator delete (A *p, std::destroying_delete_t) {
+    ++adl;
+    if (adt) __builtin_abort();
+    p->~A();
+    ::operator delete (p);
+  }
+};
+
+struct B {
+  virtual ~B() {}
+  void operator delete(void*, std::size_t) { __builtin_abort(); }
+};
+
+int edel, edtor;
+struct E : B {
+  ~E() { ++edtor; }
+  void operator delete(E *p, std::destroying_delete_t) {
+    ++edel;
+    if (edtor) __builtin_abort();
+    p->~E();
+    ::operator delete(p);
+  }
+};
+int main() {
+  A* ap = new A;
+  delete ap;
+  if (adl != 1 || adt != 1)
+    __builtin_abort();
+
+  B* bp = new E;
+  delete bp; // 2: uses E::operator delete(E*, std::destroying_delete_t)
+  if (edel != 1 || edtor != 1)
+    __builtin_abort();
+}
diff --git a/gcc/testsuite/g++.dg/cpp2a/feat-cxx2a.C b/gcc/testsuite/g++.dg/cpp2a/feat-cxx2a.C
index 4289bfcfa52..dba77179b82 100644
--- a/gcc/testsuite/g++.dg/cpp2a/feat-cxx2a.C
+++ b/gcc/testsuite/g++.dg/cpp2a/feat-cxx2a.C
@@ -426,6 +426,10 @@ 
 # error "__cpp_nontype_template_parameter_class != 201806"
 #endif
 
+#if __cpp_impl_destroying_delete != 201806
+# error "__cpp_impl_destroying_delete != 201806"
+#endif
+
 #ifdef __has_cpp_attribute
 
 #  if ! __has_cpp_attribute(maybe_unused)
diff --git a/gcc/c-family/ChangeLog b/gcc/c-family/ChangeLog
index 42bb5ca450b..165f9b7efcc 100644
--- a/gcc/c-family/ChangeLog
+++ b/gcc/c-family/ChangeLog
@@ -1,5 +1,8 @@ 
 2018-11-12  Jason Merrill  <jason@redhat.com>
 
+	* c-cppbuiltin.c (c_cpp_builtins): Define
+	__cpp_impl_destroying_delete.
+
 	* c-cppbuiltin.c (c_cpp_builtins): Change __cpp_explicit_bool to
 	__cpp_conditional_explicit.
 
diff --git a/gcc/cp/ChangeLog b/gcc/cp/ChangeLog
index 2f15c08b3e4..5497a0829e3 100644
--- a/gcc/cp/ChangeLog
+++ b/gcc/cp/ChangeLog
@@ -1,5 +1,14 @@ 
 2018-11-12  Jason Merrill  <jason@redhat.com>
 
+	Implement P0722R3, destroying operator delete.
+	* call.c (std_destroying_delete_t_p, destroying_delete_p): New.
+	(aligned_deallocation_fn_p, usual_deallocation_fn_p): Use
+	destroying_delete_p.
+	(build_op_delete_call): Handle destroying delete.
+	* decl2.c (coerce_delete_type): Handle destroying delete.
+	* init.c (build_delete): Don't call dtor with destroying delete.
+	* optimize.c (build_delete_destructor_body): Likewise.
+
 	Implement P0780R2, pack expansion in lambda init-capture.
 	* parser.c (cp_parser_lambda_introducer): Parse pack init-capture.
 	* pt.c (tsubst_pack_expansion): Handle init-capture packs.
diff --git a/libstdc++-v3/ChangeLog b/libstdc++-v3/ChangeLog
index eafdd927e9c..d904b45f7df 100644
--- a/libstdc++-v3/ChangeLog
+++ b/libstdc++-v3/ChangeLog
@@ -1,3 +1,7 @@ 
+2018-11-12  Jason Merrill  <jason@redhat.com>
+
+	* libsupc++/new (std::destroying_delete_t): New.
+
 2018-11-12  Jonathan Wakely  <jwakely@redhat.com>
 
 	PR libstdc++/87963
diff --git a/libstdc++-v3/libsupc++/new b/libstdc++-v3/libsupc++/new
index 19bc1832541..91ebf3c2cd7 100644
--- a/libstdc++-v3/libsupc++/new
+++ b/libstdc++-v3/libsupc++/new
@@ -208,6 +208,18 @@  namespace std
 #endif // _GLIBCXX_HAVE_BUILTIN_LAUNDER
 #endif // C++17
 
+#if __cpp_impl_destroying_delete
+#define __cpp_lib_destroying_delete 201806L
+namespace std
+{
+  struct destroying_delete_t
+  {
+    explicit destroying_delete_t() = default;
+  };
+  inline constexpr destroying_delete_t destroying_delete{};
+}
+#endif // destroying delete
+
 #pragma GCC visibility pop
 
 #endif