From a6bb6b07f76c4431cb5a2a520ac33f7c970b80c1 Mon Sep 17 00:00:00 2001 From: Jason Merrill Date: Mon, 12 Nov 2018 23:40:01 -0500 Subject: [PATCH] Implement P0722R3, destroying operator delete. 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. 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. From-SVN: r266053 --- gcc/c-family/ChangeLog | 3 ++ gcc/c-family/c-cppbuiltin.c | 1 + gcc/cp/ChangeLog | 9 ++++ gcc/cp/call.c | 45 +++++++++++++++++++ gcc/cp/cp-tree.h | 3 +- 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 ++ 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/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 + * 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/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/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 + 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/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 *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/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 **, 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/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, "% 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 + +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/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 + + * libsupc++/new (std::destroying_delete_t): New. + 2018-11-12 Jonathan Wakely 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 -- 2.30.2