https://gcc.gnu.org/g:eed9f52920aa362866eab84886dc47b0a38944b0
commit r17-4658-geed9f52920aa362866eab84886dc47b0a38944b0 Author: Jakub Jelinek <[email protected]> Date: Fri Sep 25 17:52:22 2026 +0200 c++: Implement C++26 P3769R1 - Clarification of placement new deallocation as a DR [PR125848] The following patch implements the P3769R1 paper as a DR, changes the algorithm to select placement deallocation function among those which were looked up. The delete cases mentioned in the paper work differently in GCC because we implement one possible resolution of still pending CWG901. 2026-09-25 Jakub Jelinek <[email protected]> P3769R1 PR c++/125848 * call.cc (build_op_delete_call_1): Implement C++26 P3769R1 - Clarification of placement new deallocation. Instead of calling instantiate_type to select placement deallocation function iterate over OVERLOAD if any, perform argument deduction for templates, ignore candidates with different parameter type lists or unsatisfied constraints and if only one candidate remains, use it, otherwise don't destruct on throw. Add placement_args argument, pass it down to build_op_delete_call call and use in template argument deduction. (build_op_delete_call): Add placement_args argument and pass it down to build_op_delete_call_1. (build_coroutine_op_delete_call): Remove placement argument, pass down NULL_TREE and NULL to build_op_delete_call_1 as placement and placement_args. * cp-tree.h (build_op_delete_call): Add placement_args argument. (build_coroutine_op_delete_call): Remove placement argument. * init.cc (build_new_1): Pass down *placement as placement_args to build_op_delete_call for placement new. (build_vec_delete_1): Adjust build_op_delete_call callers. (build_delete): Likewise. Remove {}s around single statement body. * coroutines.cc (build_coroutine_frame_delete_expr): Don't pass placement to build_coroutine_op_delete_call. * optimize.cc (build_delete_destructor_body): Adjust build_op_delete_call caller. * g++.dg/cpp26/placement-new1.C: New test. Reviewed-by: Jason Merrill <[email protected]> Diff: --- gcc/cp/call.cc | 84 ++++-- gcc/cp/coroutines.cc | 5 +- gcc/cp/cp-tree.h | 7 +- gcc/cp/init.cc | 64 ++--- gcc/cp/optimize.cc | 11 +- gcc/testsuite/g++.dg/cpp26/placement-new1.C | 427 ++++++++++++++++++++++++++++ 6 files changed, 529 insertions(+), 69 deletions(-) diff --git a/gcc/cp/call.cc b/gcc/cp/call.cc index ecf1c870d8be..eabc2342b062 100644 --- a/gcc/cp/call.cc +++ b/gcc/cp/call.cc @@ -8075,6 +8075,8 @@ usual_deallocation_fn_p (tree fn) usual deallocation should be chosen in preference to the single argument version in a class context. PLACEMENT is the corresponding placement new call, or NULL_TREE. + PLACEMENT_ARGS is a pointer to vector containing the original placement + arguments, or NULL. If this call to "operator delete" is being generated as part to deallocate memory allocated via a new-expression (as per [expr.new] @@ -8085,7 +8087,8 @@ usual_deallocation_fn_p (tree fn) static tree build_op_delete_call_1 (enum tree_code code, tree addr, tree size, bool global_p, bool coro_p, tree placement, - tree alloc_fn, tsubst_flags_t complain) + vec<tree, va_gc> *placement_args, tree alloc_fn, + tsubst_flags_t complain) { tree fn = NULL_TREE; tree fns, fnname, type, t; @@ -8127,28 +8130,66 @@ build_op_delete_call_1 (enum tree_code code, tree addr, tree size, if (placement) { - /* "A declaration of a placement deallocation function matches the - declaration of a placement allocation function if it has the same - number of parameters and, after parameter transformations (8.3.5), - all parameter types except the first are identical." - - So we build up the function type we want and ask instantiate_type - to get it for us. */ + /* For a placement allocation function, the matching deallocation + function is selected as follows: + + - Each candidate that is a function template is replaced by the + function template specializations (if any) generated using template + argument deduction with arguments as specified below. + - Each candidate whose parameter-type-list is not identical to that + of the allocation function, ignoring their respective first + parameters, is removed from the set of candidates. + - Each candidate whose associated constraints (if any) are not + satisfied is removed from the set of candidates. + - If exactly one function remains, that function is selected. + - Otherwise, no deallocation function is selected. */ t = FUNCTION_ARG_CHAIN (alloc_fn); - t = tree_cons (NULL_TREE, ptr_type_node, t); - t = build_function_type (void_type_node, t); + fn = NULL_TREE; + tree *args = NULL; + unsigned nargs = 0; + for (tree elt : lkp_range (MAYBE_BASELINK_FUNCTIONS (fns))) + { + if (TREE_CODE (elt) == TEMPLATE_DECL) + { + tree targs = make_tree_vec (DECL_NTPARMS (elt)); + if (args == NULL) + { + nargs = vec_safe_length (placement_args); + args = XALLOCAVEC (tree, nargs); + args[0] = addr; + for (unsigned i = 1; i < nargs; ++i) + args[i] = (*placement_args)[i]; + } + elt = fn_type_unification (elt, NULL_TREE, targs, args, + nargs, NULL_TREE, + DEDUCE_CALL, LOOKUP_NORMAL, + NULL, false, false); + if (elt == error_mark_node) + /* Instantiation failed. */ + continue; + } - fn = instantiate_type (t, fns, tf_none); - if (fn == error_mark_node) - return NULL_TREE; + if (!constraints_satisfied_p (elt)) + continue; - fn = MAYBE_BASELINK_FUNCTIONS (fn); + /* See if there's a match. */ + if (!compparms (t, FUNCTION_ARG_CHAIN (elt))) + continue; + + if (fn != NULL_TREE) + { + fn = NULL_TREE; + break; + } + else + fn = elt; + } /* "If the lookup finds the two-parameter form of a usual deallocation function (3.7.4.2) and that function, considered as a placement deallocation function, would have been selected as a match for the allocation function, the program is ill-formed." */ - if (second_parm_is_size_t (fn)) + if (fn && second_parm_is_size_t (fn)) { const char *const msg1 = G_("exception cleanup for this placement new selects " @@ -8372,7 +8413,7 @@ build_op_delete_call_1 (enum tree_code code, tree addr, tree size, if (excluded_destroying && DECL_NAMESPACE_SCOPE_P (alloc_fn)) return build_op_delete_call (code, addr, size, true, placement, - alloc_fn, complain); + placement_args, alloc_fn, complain); /* [expr.new] @@ -8406,21 +8447,24 @@ build_op_delete_call_1 (enum tree_code code, tree addr, tree size, tree build_op_delete_call (enum tree_code code, tree addr, tree size, bool global_p, - tree placement, tree alloc_fn, tsubst_flags_t complain) + tree placement, vec<tree, va_gc> *placement_args, + tree alloc_fn, tsubst_flags_t complain) { return build_op_delete_call_1 (code, addr, size, global_p, /*coro_p*/false, - placement, alloc_fn, complain); + placement, placement_args, alloc_fn, + complain); } /* Arguments as per build_op_delete_call_1 (). */ tree build_coroutine_op_delete_call (enum tree_code code, tree addr, tree size, - bool global_p, tree placement, tree alloc_fn, + bool global_p, tree alloc_fn, tsubst_flags_t complain) { return build_op_delete_call_1 (code, addr, size, global_p, /*coro_p*/true, - placement, alloc_fn, complain); + /*placement*/NULL_TREE, + /*placement_args*/NULL, alloc_fn, complain); } /* Issue diagnostics about a disallowed access of DECL, using DIAG_DECL diff --git a/gcc/cp/coroutines.cc b/gcc/cp/coroutines.cc index f4df662bbcb9..ce9c1b6bffab 100644 --- a/gcc/cp/coroutines.cc +++ b/gcc/cp/coroutines.cc @@ -4981,8 +4981,9 @@ build_coroutine_frame_delete_expr (tree coro_fp, tree frame_size, one with two parameters. */ tree del_coro_fr = build_coroutine_op_delete_call (DELETE_EXPR, frame_arg, frame_size, - /*global_p=*/false, /*placement=*/NULL, - /*alloc_fn=*/NULL, tf_warning_or_error); + /*global_p=*/false, + /*alloc_fn=*/NULL_TREE, + tf_warning_or_error); if (!del_coro_fr || del_coro_fr == error_mark_node) return error_mark_node; return del_coro_fr; diff --git a/gcc/cp/cp-tree.h b/gcc/cp/cp-tree.h index 154755fce821..947bf62ff982 100644 --- a/gcc/cp/cp-tree.h +++ b/gcc/cp/cp-tree.h @@ -7359,11 +7359,10 @@ 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, - tsubst_flags_t); + bool, tree, vec<tree, va_gc> *, + tree, tsubst_flags_t); extern tree build_coroutine_op_delete_call (enum tree_code, tree, tree, - bool, tree, tree, - tsubst_flags_t complain); + bool, tree, tsubst_flags_t); extern bool can_convert (tree, tree, tsubst_flags_t); extern bool can_convert_standard (tree, tree, tsubst_flags_t); extern bool can_convert_arg (tree, tree, tree, int, diff --git a/gcc/cp/init.cc b/gcc/cp/init.cc index 6bdc5fbbeb0b..3fefa9fc02d9 100644 --- a/gcc/cp/init.cc +++ b/gcc/cp/init.cc @@ -3898,14 +3898,12 @@ build_new_1 (vec<tree, va_gc> **placement, tree type, tree nelts, /* The Standard is unclear here, but the right thing to do is to use the same method for finding deallocation functions that we use for finding allocation functions. */ - cleanup = (build_op_delete_call - (dcode, - alloc_node, - size, - globally_qualified_p, - placement_allocation_fn_p ? alloc_call : NULL_TREE, - alloc_fn, - complain)); + cleanup + = build_op_delete_call (dcode, alloc_node, size, globally_qualified_p, + placement_allocation_fn_p + ? alloc_call : NULL_TREE, + placement_allocation_fn_p ? *placement : NULL, + alloc_fn, complain); if (cleanup && init_expr && !processing_template_decl) /* Ack! First we allocate the memory. Then we set our sentry @@ -4380,12 +4378,11 @@ build_vec_delete_1 (location_t loc, tree base, tree maxindex, tree type, virtual_size = size_binop (PLUS_EXPR, virtual_size, cookie_size); } - deallocate_expr = build_op_delete_call (VEC_DELETE_EXPR, - base_tbd, virtual_size, - use_global_delete & 1, - /*placement=*/NULL_TREE, - /*alloc_fn=*/NULL_TREE, - complain); + deallocate_expr + = build_op_delete_call (VEC_DELETE_EXPR, base_tbd, virtual_size, + use_global_delete & 1, /*placement=*/NULL_TREE, + /*placement_args=*/NULL, + /*alloc_fn=*/NULL_TREE, complain); } body = loop; @@ -5583,13 +5580,11 @@ build_delete (location_t loc, tree otype, tree addr, { head = get_internal_target_expr (build_headof (addr)); /* Delete the object. */ - do_delete = build_op_delete_call (DELETE_EXPR, - head, - cxx_sizeof_nowarn (type), - /*global_p=*/true, - /*placement=*/NULL_TREE, - /*alloc_fn=*/NULL_TREE, - complain); + do_delete + = build_op_delete_call (DELETE_EXPR, head, cxx_sizeof_nowarn (type), + /*global_p=*/true, /*placement=*/NULL_TREE, + /*placement_args=*/NULL, + /*alloc_fn=*/NULL_TREE, complain); /* Otherwise, treat this like a complete object destructor call. */ auto_delete = sfk_complete_destructor; @@ -5600,13 +5595,11 @@ build_delete (location_t loc, tree otype, tree addr, else if (!virtual_p) { /* Build the call. */ - do_delete = build_op_delete_call (DELETE_EXPR, - addr, - cxx_sizeof_nowarn (type), - /*global_p=*/false, - /*placement=*/NULL_TREE, - /*alloc_fn=*/NULL_TREE, - complain); + do_delete + = build_op_delete_call (DELETE_EXPR, addr, cxx_sizeof_nowarn (type), + /*global_p=*/false, /*placement=*/NULL_TREE, + /*placement_args=*/NULL, + /*alloc_fn=*/NULL_TREE, complain); /* Call the complete object destructor. */ auto_delete = sfk_complete_destructor; if (do_delete != error_mark_node) @@ -5616,15 +5609,12 @@ build_delete (location_t loc, tree otype, tree addr, } } else if (TYPE_GETS_REG_DELETE (type)) - { - /* Make sure we have access to the member op delete, even though - we'll actually be calling it from the destructor. */ - build_op_delete_call (DELETE_EXPR, addr, cxx_sizeof_nowarn (type), - /*global_p=*/false, - /*placement=*/NULL_TREE, - /*alloc_fn=*/NULL_TREE, - complain); - } + /* Make sure we have access to the member op delete, even though + we'll actually be calling it from the destructor. */ + build_op_delete_call (DELETE_EXPR, addr, cxx_sizeof_nowarn (type), + /*global_p=*/false, /*placement=*/NULL_TREE, + /*placement_args=*/NULL, /*alloc_fn=*/NULL_TREE, + complain); if (destroying_delete) /* The operator delete will call the destructor. */ diff --git a/gcc/cp/optimize.cc b/gcc/cp/optimize.cc index 31ee2aaccfb9..0afeefd57047 100644 --- a/gcc/cp/optimize.cc +++ b/gcc/cp/optimize.cc @@ -138,12 +138,11 @@ build_delete_destructor_body (tree delete_dtor, tree complete_dtor) tree virtual_size = cxx_sizeof (current_class_type); /* Call the delete function. */ - tree call_delete = build_op_delete_call (DELETE_EXPR, current_class_ptr, - virtual_size, - /*global_p=*/false, - /*placement=*/NULL_TREE, - /*alloc_fn=*/NULL_TREE, - tf_warning_or_error); + tree call_delete + = build_op_delete_call (DELETE_EXPR, current_class_ptr, virtual_size, + /*global_p=*/false, /*placement=*/NULL_TREE, + /*placement_args=*/NULL, /*alloc_fn=*/NULL_TREE, + tf_warning_or_error); tree op = get_callee_fndecl (call_delete); if (op && DECL_P (op) && destroying_delete_p (op)) diff --git a/gcc/testsuite/g++.dg/cpp26/placement-new1.C b/gcc/testsuite/g++.dg/cpp26/placement-new1.C new file mode 100644 index 000000000000..39c814f1938c --- /dev/null +++ b/gcc/testsuite/g++.dg/cpp26/placement-new1.C @@ -0,0 +1,427 @@ +// P3769R1 - Clarification of placement new deallocation +// { dg-do run { target c++11 } } + +namespace std +{ + using size_t = decltype (sizeof 0); +} + +struct A {}; +int v; +A a; +template <int I> +struct B {}; +B <0> b; +template <int I> +struct C {}; +C <0> c; +template <int I> +struct D {}; +D <0> d; +struct T { T () = default; T (int) { throw 1; } }; +union U { unsigned char c[sizeof (T)]; T t; } u; +void *foo (); +struct V { + V () = default; + V (int) { throw 2; } + void *operator new (std::size_t, A &) + { + if (v != 5) + __builtin_abort (); + ++v; + return foo (); + } + template <int = 0> + void operator delete (void *, A &) + { + if (v != 6) + __builtin_abort (); + v += 2; + } + void *operator new (std::size_t, B <0> &) + { + if (v != 8) + __builtin_abort (); + v += 4; + return foo (); + } + template <int I> + void operator delete (void *, B <I> &) + { + __builtin_abort (); + } + void operator delete (void *, B <0> &) + { + __builtin_abort (); + } + void *operator new (std::size_t, C <0> &) + { + if (v != 12) + __builtin_abort (); + v += 8; + return foo (); + } +#if __cpp_concepts >= 202002L + template <int I> + requires (I > 0) + void operator delete (void *, C <I> &) + { + __builtin_abort (); + } +#endif + void operator delete (void *, C <0> &) + { + if (v != 20) + __builtin_abort (); + v += 16; + } +}; +union W { unsigned char c[sizeof (V)]; V t; } w; + +void * +foo () +{ + return &w.c[0]; +} + +void * +operator new (std::size_t, A &) // #1 +{ + if (v != 0) + __builtin_abort (); + ++v; + return &u.c[0]; +} + +template <int = 0> +void +operator delete (void *, A &) // #2 +{ + if (v != 1) + __builtin_abort (); + v += 2; +} + +void * +operator new (std::size_t, B <0> &) // #3 +{ + if (v != 3) + __builtin_abort (); + v += 4; + return &u.c[0]; +} + +template <int I> +void +operator delete (void *, B <I> &) // #4 +{ + __builtin_abort (); +} + +void +operator delete (void *, B <0> &) // #5 +{ + __builtin_abort (); +} + +void * +operator new (std::size_t, C <0> &) // #6 +{ + if (v != 7) + __builtin_abort (); + v += 8; + return &u.c[0]; +} + +#if __cpp_concepts >= 202002L +template <int I> +requires (I > 0) +void +operator delete (void *, C <I> &) // #7 +{ + __builtin_abort (); +} +#endif + +void +operator delete (void *, C <0> &) // #8 +{ + if (v != 15) + __builtin_abort (); + v += 16; +} + +void * +operator new (std::size_t, double, int, float) +{ + if (v != 36) + __builtin_abort (); + v += 32; + return &u.c[0]; +} + +void +operator delete (void *, double, int, float, int = 0, double = 0.0, float = 0.0) +{ + __builtin_abort (); +} + +void * +operator new (std::size_t, double, int, long) +{ + if (v != 68) + __builtin_abort (); + v += 64; + return &u.c[0]; +} + +void +operator delete (void *, double, int, long, int = 0, double = 0.0, float = 0.0) +{ + __builtin_abort (); +} + +void +operator delete (void *, double, int, long) +{ + if (v != 132) + __builtin_abort (); + v += 128; +} + +void * +operator new (std::size_t, unsigned, B <0> &, B <0> &) +{ + if (v != 9) + __builtin_abort (); + ++v; + return &u.c[0]; +} + +template <typename T, typename U> +void operator delete (void *, unsigned, T &, U &) +{ + __builtin_abort (); +} + +template <typename T> +void operator delete (void *, unsigned, T &, T &) +{ + __builtin_abort (); +} + +template <int I> +void operator delete (void *, unsigned, B <I> &, B <I> &) +{ + __builtin_abort (); +} + +void * +operator new (std::size_t, unsigned long, B <0> &, B <0> &) +{ + if (v != 10) + __builtin_abort (); + v += 2; + return &u.c[0]; +} + +template <typename T, typename U> +void operator delete (void *, unsigned long, T &, U &) +{ + __builtin_abort (); +} + +template <typename T> +void operator delete (void *, unsigned long, T &, T &) +{ + __builtin_abort (); +} + +void * +operator new (std::size_t, unsigned long long, B <0> &, B <0> &) +{ + if (v != 12) + __builtin_abort (); + v += 4; + return &u.c[0]; +} + +template <typename T, typename U> +void operator delete (void *, unsigned long long, T &, U &) +{ + if (v != 16) + __builtin_abort (); + v += 8; +} + +void * +operator new (std::size_t, long long, B <0> &, B <0> &) +{ + if (v != 24) + __builtin_abort (); + v += 16; + return &u.c[0]; +} + +template <typename T, typename U> +void operator delete (void *, long long, T &, U &) = delete; + +template <typename T> +void operator delete (void *, long long, T &, T &) = delete; + +template <int I> +void operator delete (void *, long long, B <I> &, B <I> &) = delete; + +void * +operator new (std::size_t, D <0> &) +{ + if (v != 40) + __builtin_abort (); + v += 32; + return &u.c[0]; +} + +template <int I> +void operator delete (void *, D <I> &) +{ + if (v != 72) + __builtin_abort (); + v += 64; +} + +int +main () +{ + try + { + T *p = new (a) T (1); // OK, uses #1 and #2. + throw 1L; + } + catch (int) + { + if (v != 3) + __builtin_abort (); + } + try + { + T *p = new (b) T (1); // OK, uses #3. No deallocation function is selected (two candidates remain). + throw 2L; + } + catch (int) + { + if (v != 7) + __builtin_abort (); + } + try + { + T *p = new (c) T (1); // OK, uses #7 and #9. + throw 3L; + } + catch (int) + { + if (v != 31) + __builtin_abort (); + } + v = 5; + try + { + V *p = new (a) V (1); + throw 4L; + } + catch (int) + { + if (v != 8) + __builtin_abort (); + } + try + { + V *p = new (b) V (1); + throw 5L; + } + catch (int) + { + if (v != 12) + __builtin_abort (); + } + try + { + V *p = new (c) V (1); + throw 6L; + } + catch (int) + { + if (v != 36) + __builtin_abort (); + } + try + { + T *p = new (0.0, 0, 0.0f) T (1); + throw 7L; + } + catch (int) + { + if (v != 68) + __builtin_abort (); + } + try + { + T *p = new (0.0, 0, 0L) T (1); + throw 8L; + } + catch (int) + { + if (v != 260) + __builtin_abort (); + } + v = 9; + try + { + T *p = new (0U, b, b) T (1); + throw 9L; + } + catch (int) + { + if (v != 10) + __builtin_abort (); + } + try + { + T *p = new (0UL, b, b) T (1); + throw 10L; + } + catch (int) + { + if (v != 12) + __builtin_abort (); + } + try + { + T *p = new (0ULL, b, b) T (1); + throw 12L; + } + catch (int) + { + if (v != 24) + __builtin_abort (); + } + try + { + T *p = new (0LL, b, b) T (1); + throw 13L; + } + catch (int) + { + if (v != 40) + __builtin_abort (); + } + try + { + T *p = new (d) T (1); + throw 14L; + } + catch (int) + { + if (v != 136) + __builtin_abort (); + } +}
