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

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  <ja...@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  <ja...@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  <ja...@redhat.com>
+
+       * libsupc++/new (std::destroying_delete_t): New.
+
 2018-11-12  Jonathan Wakely  <jwak...@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

base-commit: e771eb36d40e1ad0345fa990cfe83405439e6159
-- 
2.17.2

Reply via email to