public inbox for gcc-patches@gcc.gnu.org
 help / color / mirror / Atom feed
From: Jason Merrill <jason@redhat.com>
To: Jakub Jelinek <jakub@redhat.com>
Cc: gcc-patches@gcc.gnu.org, Jonathan Wakely <jwakely@redhat.com>,
	Marek Polacek <polacek@redhat.com>
Subject: Re: [C++ PATCH] PR c++/91369 - Implement P0784R7: constexpr new
Date: Tue, 01 Oct 2019 21:56:00 -0000	[thread overview]
Message-ID: <688ebe68-e83f-9559-a1c7-883758b2bd74@redhat.com> (raw)
In-Reply-To: <20190927203104.GY15914@tucnak>

On 9/27/19 4:31 PM, Jakub Jelinek wrote:
> Hi!
> 
> The following patch attempts to implement P0784R7, which includes constexpr
> destructors and constexpr new/delete/new[]/delete[].
> 
> ::operator new is allowed during constexpr evaluation and returns address of
> an artificial VAR_DECL with special name.  At this point we don't really
> know the type of the heap storage, just size.  Later on when we encounter
> cast to the corresponding pointer type, we change the name of the var and
> type to match the type from the new expression (for new[] we need to do
> further stuff as at the point where build_new_1 is called, we might not know
> the exact array size, but we shall know that during the constexpr
> evaluation, and cookie handling also complicates it a little bit).
> When we first store into such heap objects, a ctor is created for them on
> the fly.  Finally, ::operator delete marks those heap VAR_DECLs as deleted
> and cxx_eval_outermost_constant_expr checks if everything that has been
> allocated has been also deallocated and verifies addresses of those heap
> vars aren't leaking into the return value.
> 
> Bootstrapped/regtested on x86_64-linux and i686-linux, ok for trunk?
> 
> 2019-09-27  Jakub Jelinek  <jakub@redhat.com>
> 
> 	PR c++/91369 - Implement P0784R7: constexpr new
> c-family/
> 	* c-cppbuiltin.c (c_cpp_builtins): Predefine
> 	__cpp_constexpr_dynamic_alloc=201907 for -std=c++2a.
> cp/
> 	* cp-tree.h (enum cp_tree_index): Add CPTI_HEAP_UNINIT_IDENTIFIER,
> 	CPTI_HEAP_IDENTIFIER and CPTI_HEAP_DELETED_IDENTIFIER.
> 	(heap_uninit_identifier, heap_identifier, heap_deleted_identifier):
> 	Define.
> 	(type_has_constexpr_destructor, cxx_constant_dtor): Declare.
> 	* class.c (type_maybe_constexpr_default_constructor): Make static.
> 	(type_maybe_constexpr_destructor, type_has_constexpr_destructor): New
> 	functions.
> 	(finalize_literal_type_property): For c++2a, don't clear
> 	CLASSTYPE_LITERAL_P for types without trivial destructors unless they
> 	have non-constexpr destructors.
> 	(explain_non_literal_class): For c++2a, complain about non-constexpr
> 	destructors rather than about non-trivial destructors.
> 	* constexpr.c: Include stor-layout.h.
> 	(struct constexpr_ctx): Add heap_vars field.
> 	(cxx_eval_call_expression): For c++2a allow calls to replaceable
> 	global allocation functions, for new return address of a heap uninit
> 	var, for delete record its deletion.
> 	(initialized_type): Handle destructors for c++2a.
> 	(cxx_fold_indirect_ref): Also handle array fields in structures.
> 	(non_const_var_error): Add auto_diagnostic_group sentinel.  Emit
> 	special diagnostics for heap variables.
> 	(cxx_eval_store_expression): Create ctor for heap variables on the
> 	first write.  Formatting fix.  Handle const_object_being_modified
> 	with array type.
> 	(cxx_eval_loop_expr): Initialize jump_target if NULL.
> 	(cxx_eval_constant_expression) <case CLEANUP_STMT>: If not skipping
> 	upon entry to body, run cleanup with the same *jump_target as it
> 	started to run the cleanup even if the body returns, breaks or
> 	continues.
> 	<case NOP_EXPR>: Formatting fix.  On cast of replaceable global
> 	allocation function to some pointer type, adjust the type of
> 	the heap variable and change name from heap_uninit_identifier
> 	to heap_identifier.
> 	(find_heap_var_refs): New function.
> 	(cxx_eval_outermost_constant_expr): Add constexpr_dtor argument,
> 	handle evaluation of constexpr dtors and add tracking of heap
> 	variables.  Use tf_no_cleanup for get_target_expr_with_sfinae.
> 	(cxx_constant_value): Adjust cxx_eval_outermost_constant_expr caller.
> 	(cxx_constant_dtor): New function.
> 	(maybe_constant_value, fold_non_dependent_expr_template,
> 	maybe_constant_init_1): Adjust cxx_eval_outermost_constant_expr
> 	callers.
> 	(potential_constant_expression_1): Ignore clobbers.  Allow
> 	COND_EXPR_IS_VEC_DELETE for c++2a.  Allow CLEANUP_STMT.
> 	* decl.c (initialize_predefined_identifiers): Add heap identifiers.
> 	(cp_finish_decl): Don't clear TREE_READONLY for constexpr variables
> 	with non-trivial, but constexpr destructors.
> 	(register_dtor_fn): For constexpr variables with constexpr non-trivial
> 	destructors call cxx_constant_dtor instead of adding destructor calls
> 	at runtime.
> 	(expand_static_init): For constexpr variables with constexpr
> 	non-trivial destructors call cxx_maybe_build_cleanup.
> 	(grokdeclarator): Allow constexpr destructors for c++2a.  Formatting
> 	fix.
> 	(cxx_maybe_build_cleanup): For constexpr variables with constexpr
> 	non-trivial destructors call cxx_constant_dtor instead of adding
> 	destructor calls at runtime.
> 	* init.c: Include stor-layout.h.
> 	(build_new_1): For c++2a and new[], add cast around the alloc call
> 	to help constexpr evaluation figure out the type of the heap storage.
> 	(build_vec_delete_1): Set DECL_INITIAL of tbase and emit a DECL_EXPR
> 	for it instead of initializing an uninitialized variable.
> 	* method.c: Include intl.h.
> 	(SFK_CTOR_P, SFK_DTOR_P, SFK_ASSIGN_P, SFK_COPY_P, SFK_MOVE_P): Move
> 	definitions earlier.
> 	(process_subob_fn): Add sfk argument, adjust non-constexpr call
> 	diagnostics based on it.
> 	(walk_field_subobs): Formatting fixes.  Adjust process_subob_fn caller.
> 	(synthesized_method_base_walk): Likewise.
> 	(synthesized_method_walk): Set *constexpr_p to true for dtors in c++2a.
> 	Fix up DR number in comment.
> 	(implicitly_declare_fn): Formatting fix.
> 	* typeck2.c (store_init_value): Don't call cp_fully_fold_init on
> 	initializers of automatic non-constexpr variables in constexpr
> 	functions.
> testsuite/
> 	* g++.dg/cpp0x/constexpr-delete2.C: Adjust expected diagnostics for
> 	c++2a.
> 	* g++.dg/cpp0x/locations1.C: Only expect constexpr ~S() diagnostics
> 	in c++17_down, adjust expected wording.
> 	* g++.dg/cpp1y/constexpr-new.C: Only expect diagnostics in c++17_down.
> 	* g++.dg/cpp2a/constexpr-dtor1.C: New test.
> 	* g++.dg/cpp2a/constexpr-dtor2.C: New test.
> 	* g++.dg/cpp2a/constexpr-dtor3.C: New test.
> 	* g++.dg/cpp2a/constexpr-new1.C: New test.
> 	* g++.dg/cpp2a/constexpr-new2.C: New test.
> 	* g++.dg/cpp2a/constexpr-new3.C: New test.
> 	* g++.dg/cpp2a/constexpr-new4.C: New test.
> 	* g++.dg/cpp2a/feat-cxx2a.C: Add __cpp_constinit and
> 	__cpp_constexpr_dynamic_alloc tests.  Tweak __cpp_* tests for c++2a
> 	features to use style like older features, including #ifdef test.
> 	* g++.dg/ext/is_literal_type3.C: New test.
> 
> --- gcc/c-family/c-cppbuiltin.c.jj	2019-09-26 21:34:21.188923996 +0200
> +++ gcc/c-family/c-cppbuiltin.c	2019-09-27 18:25:41.346059343 +0200
> @@ -989,6 +989,7 @@ c_cpp_builtins (cpp_reader *pfile)
>   	  cpp_define (pfile, "__cpp_constinit=201907");
>   	  cpp_define (pfile, "__cpp_nontype_template_parameter_class=201806");
>   	  cpp_define (pfile, "__cpp_impl_destroying_delete=201806");
> +	  cpp_define (pfile, "__cpp_constexpr_dynamic_alloc=201907");
>   	}
>         if (flag_concepts)
>   	cpp_define (pfile, "__cpp_concepts=201507");
> --- gcc/cp/cp-tree.h.jj	2019-09-27 12:22:54.042880699 +0200
> +++ gcc/cp/cp-tree.h	2019-09-27 18:25:40.903065957 +0200
> @@ -172,6 +172,9 @@ enum cp_tree_index
>       CPTI_VALUE_IDENTIFIER,
>       CPTI_FUN_IDENTIFIER,
>       CPTI_CLOSURE_IDENTIFIER,
> +    CPTI_HEAP_UNINIT_IDENTIFIER,
> +    CPTI_HEAP_IDENTIFIER,
> +    CPTI_HEAP_DELETED_IDENTIFIER,
>   
>       CPTI_LANG_NAME_C,
>       CPTI_LANG_NAME_CPLUSPLUS,
> @@ -310,6 +313,9 @@ extern GTY(()) tree cp_global_trees[CPTI
>   #define value_identifier		cp_global_trees[CPTI_VALUE_IDENTIFIER]
>   #define fun_identifier			cp_global_trees[CPTI_FUN_IDENTIFIER]
>   #define closure_identifier		cp_global_trees[CPTI_CLOSURE_IDENTIFIER]
> +#define heap_uninit_identifier		cp_global_trees[CPTI_HEAP_UNINIT_IDENTIFIER]
> +#define heap_identifier			cp_global_trees[CPTI_HEAP_IDENTIFIER]
> +#define heap_deleted_identifier		cp_global_trees[CPTI_HEAP_DELETED_IDENTIFIER]
>   #define lang_name_c			cp_global_trees[CPTI_LANG_NAME_C]
>   #define lang_name_cplusplus		cp_global_trees[CPTI_LANG_NAME_CPLUSPLUS]
>   
> @@ -6324,6 +6330,7 @@ extern bool vbase_has_user_provided_move
>   extern tree default_init_uninitialized_part (tree);
>   extern bool trivial_default_constructor_is_constexpr (tree);
>   extern bool type_has_constexpr_default_constructor (tree);
> +extern bool type_has_constexpr_destructor	(tree);
>   extern bool type_has_virtual_destructor		(tree);
>   extern bool classtype_has_move_assign_or_move_ctor_p (tree, bool user_declared);
>   extern bool classtype_has_non_deleted_move_ctor (tree);
> @@ -7729,6 +7736,7 @@ extern bool require_constant_expression
>   extern bool require_rvalue_constant_expression (tree);
>   extern bool require_potential_rvalue_constant_expression (tree);
>   extern tree cxx_constant_value			(tree, tree = NULL_TREE);
> +extern void cxx_constant_dtor			(tree, tree);
>   extern tree cxx_constant_init			(tree, tree = NULL_TREE);
>   extern tree maybe_constant_value		(tree, tree = NULL_TREE, bool = false);
>   extern tree maybe_constant_init			(tree, tree = NULL_TREE, bool = false);
> --- gcc/cp/class.c.jj	2019-09-26 21:34:21.434920308 +0200
> +++ gcc/cp/class.c	2019-09-27 18:25:40.904065942 +0200
> @@ -206,6 +206,7 @@ static int empty_base_at_nonzero_offset_
>   static tree end_of_base (tree);
>   static tree get_vcall_index (tree, tree);
>   static bool type_maybe_constexpr_default_constructor (tree);
> +static bool type_maybe_constexpr_destructor (tree);
>   static bool field_poverlapping_p (tree);
>   
>   /* Return a COND_EXPR that executes TRUE_STMT if this execution of the
> @@ -5242,7 +5243,7 @@ type_has_constexpr_default_constructor (
>      without forcing a lazy declaration (which might cause undesired
>      instantiations).  */
>   
> -bool
> +static bool
>   type_maybe_constexpr_default_constructor (tree t)
>   {
>     if (CLASS_TYPE_P (t) && CLASSTYPE_LAZY_DEFAULT_CTOR (t)
> @@ -5252,6 +5253,34 @@ type_maybe_constexpr_default_constructor
>     return type_has_constexpr_default_constructor (t);
>   }
>   
> +/* Returns true iff class T has a constexpr destructor.  */
> +
> +bool
> +type_has_constexpr_destructor (tree t)
> +{
> +  tree fns;
> +
> +  if (CLASSTYPE_LAZY_DESTRUCTOR (t))
> +    /* Non-trivial, we need to check subobject destructors.  */
> +    lazily_declare_fn (sfk_destructor, t);
> +  fns = CLASSTYPE_DESTRUCTOR (t);
> +  return (fns && DECL_DECLARED_CONSTEXPR_P (fns));
> +}
> +
> +/* Returns true iff class T has a constexpr destructor or has an
> +   implicitly declared destructor that we can't tell if it's constexpr
> +   without forcing a lazy declaration (which might cause undesired
> +   instantiations).  */
> +
> +static bool
> +type_maybe_constexpr_destructor (tree t)
> +{
> +  if (CLASS_TYPE_P (t) && CLASSTYPE_LAZY_DESTRUCTOR (t))
> +    /* Assume it's constexpr.  */
> +    return true;
> +  return type_has_constexpr_destructor (t);
> +}
> +
>   /* Returns true iff class TYPE has a virtual destructor.  */
>   
>   bool
> @@ -5503,8 +5532,11 @@ finalize_literal_type_property (tree t)
>   {
>     tree fn;
>   
> -  if (cxx_dialect < cxx11
> -      || TYPE_HAS_NONTRIVIAL_DESTRUCTOR (t))
> +  if (cxx_dialect < cxx11)
> +    CLASSTYPE_LITERAL_P (t) = false;
> +  else if (CLASSTYPE_LITERAL_P (t)
> +	   && TYPE_HAS_NONTRIVIAL_DESTRUCTOR (t)
> +	   && (cxx_dialect < cxx2a || !type_maybe_constexpr_destructor (t)))
>       CLASSTYPE_LITERAL_P (t) = false;
>     else if (CLASSTYPE_LITERAL_P (t) && LAMBDA_TYPE_P (t))
>       CLASSTYPE_LITERAL_P (t) = (cxx_dialect >= cxx17);
> @@ -5558,8 +5590,12 @@ explain_non_literal_class (tree t)
>       inform (UNKNOWN_LOCATION,
>   	    "  %qT is a closure type, which is only literal in "
>   	    "C++17 and later", t);
> -  else if (TYPE_HAS_NONTRIVIAL_DESTRUCTOR (t))
> +  else if (cxx_dialect < cxx2a && TYPE_HAS_NONTRIVIAL_DESTRUCTOR (t))
>       inform (UNKNOWN_LOCATION, "  %q+T has a non-trivial destructor", t);
> +  else if (TYPE_HAS_NONTRIVIAL_DESTRUCTOR (t)
> +	   && !type_maybe_constexpr_destructor (t))
> +    inform (UNKNOWN_LOCATION, "  %q+T does not have %<constexpr%> destructor",
> +	    t);
>     else if (CLASSTYPE_NON_AGGREGATE (t)
>   	   && !TYPE_HAS_TRIVIAL_DFLT (t)
>   	   && !LAMBDA_TYPE_P (t)
> --- gcc/cp/constexpr.c.jj	2019-09-27 20:33:37.600208356 +0200
> +++ gcc/cp/constexpr.c	2019-09-27 20:38:38.203710246 +0200
> @@ -34,6 +34,7 @@ along with GCC; see the file COPYING3.
>   #include "gimple-fold.h"
>   #include "timevar.h"
>   #include "fold-const-call.h"
> +#include "stor-layout.h"
>   
>   static bool verify_constant (tree, bool, bool *, bool *);
>   #define VERIFY_CONSTANT(X)						\
> @@ -1031,6 +1032,9 @@ struct constexpr_ctx {
>        on simple constants or location wrappers) encountered during current
>        cxx_eval_outermost_constant_expr call.  */
>     HOST_WIDE_INT *constexpr_ops_count;
> +  /* Heap VAR_DECLs created during the evaluation of the outermost constant
> +     expression.  */
> +  vec<tree> *heap_vars;
>   
>     /* Whether we should error on a non-constant expression or fail quietly.  */
>     bool quiet;
> @@ -1666,6 +1670,58 @@ cxx_eval_call_expression (const constexp
>   					   lval, non_constant_p, overflow_p);
>     if (!DECL_DECLARED_CONSTEXPR_P (fun))
>       {
> +      if (cxx_dialect >= cxx2a
> +	  && IDENTIFIER_NEWDEL_OP_P (DECL_NAME (fun))
> +	  && CP_DECL_CONTEXT (fun) == global_namespace)
> +	{
> +	  const int nargs = call_expr_nargs (t);
> +	  tree arg0 = NULL_TREE;
> +	  for (int i = 0; i < nargs; ++i)
> +	    {
> +	      tree arg = CALL_EXPR_ARG (t, i);
> +	      arg = cxx_eval_constant_expression (ctx, arg, false,
> +						  non_constant_p, overflow_p);
> +	      VERIFY_CONSTANT (arg);
> +	      if (i == 0)
> +		arg0 = arg;
> +	    }
> +	  gcc_assert (arg0);
> +	  if (IDENTIFIER_NEW_OP_P (DECL_NAME (fun)))
> +	    {
> +	      tree type = build_array_type_nelts (char_type_node,
> +						  tree_to_uhwi (arg0));
> +	      tree var = build_decl (loc, VAR_DECL, heap_uninit_identifier,
> +				     type);
> +	      DECL_ARTIFICIAL (var) = 1;
> +	      TREE_STATIC (var) = 1;
> +	      ctx->heap_vars->safe_push (var);
> +	      return fold_convert (ptr_type_node, build_address (var));
> +	    }
> +	  else
> +	    {
> +	      STRIP_NOPS (arg0);
> +	      if (TREE_CODE (arg0) == ADDR_EXPR
> +		  && VAR_P (TREE_OPERAND (arg0, 0)))
> +		{
> +		  tree var = TREE_OPERAND (arg0, 0);
> +		  if (DECL_NAME (var) == heap_uninit_identifier
> +		      || DECL_NAME (var) == heap_identifier)
> +		    {
> +		      DECL_NAME (var) = heap_deleted_identifier;
> +		      ctx->values->remove (var);
> +		      return void_node;
> +		    }
> +		  else if (DECL_NAME (var) == heap_deleted_identifier)
> +		    {
> +		      if (!ctx->quiet)
> +			error_at (loc, "deallocation of already deallocated "
> +				       "storage");
> +		      *non_constant_p = true;
> +		      return t;
> +		    }

Don't we need an error for trying to deallocate something that wasn't 
allocated within the constexpr evaluation?

> +		}
> +	    }
> +	}
>         if (!ctx->quiet)
>   	{
>   	  if (!lambda_static_thunk_p (fun))
> @@ -2998,8 +3054,8 @@ base_field_constructor_elt (vec<construc
>   }
>   
>   /* Some of the expressions fed to the constexpr mechanism are calls to
> -   constructors, which have type void.  In that case, return the type being
> -   initialized by the constructor.  */
> +   constructors or destructors, which have type void.  In that case,
> +   return the type being initialized by the constructor.  */
>   
>   static tree
>   initialized_type (tree t)
> @@ -3011,8 +3067,10 @@ initialized_type (tree t)
>       {
>         /* A constructor call has void type, so we need to look deeper.  */
>         tree fn = get_function_named_in_call (t);
> -      if (fn && TREE_CODE (fn) == FUNCTION_DECL
> -	  && DECL_CXX_CONSTRUCTOR_P (fn))
> +      if (fn
> +	  && TREE_CODE (fn) == FUNCTION_DECL
> +	  && (DECL_CXX_CONSTRUCTOR_P (fn)
> +	      || (cxx_dialect >= cxx2a && DECL_CXX_DESTRUCTOR_P (fn))))
>   	type = DECL_CONTEXT (fn);

Why is this needed?  A destructor doesn't initialize anything, so 
returning void seems appropriate.

>       }
>     else if (TREE_CODE (t) == COMPOUND_EXPR)
> @@ -3434,11 +3492,24 @@ cxx_fold_indirect_ref (location_t loc, t
>   	{
>   	  tree field = TYPE_FIELDS (optype);
>   	  for (; field; field = DECL_CHAIN (field))
> -	    if (TREE_CODE (field) == FIELD_DECL
> -		&& TREE_TYPE (field) != error_mark_node
> -		&& integer_zerop (byte_position (field))
> -		&& similar_type_p (TREE_TYPE (field), type))
> +	    if (TREE_CODE (field) != FIELD_DECL
> +		|| TREE_TYPE (field) == error_mark_node
> +		|| !integer_zerop (byte_position (field)))
> +	      continue;
> +	    else if (similar_type_p (TREE_TYPE (field), type))
>   	      return fold_build3 (COMPONENT_REF, type, op, field, NULL_TREE);
> +	    else if (TREE_CODE (TREE_TYPE (field)) == ARRAY_TYPE
> +		     && similar_type_p (TREE_TYPE (TREE_TYPE (field)), type))
> +	      {
> +		tree type_domain = TYPE_DOMAIN (TREE_TYPE (field));
> +		tree min_val = size_zero_node;
> +		if (type_domain && TYPE_MIN_VALUE (type_domain))
> +		  min_val = TYPE_MIN_VALUE (type_domain);
> +		op = fold_build3 (COMPONENT_REF, TREE_TYPE (field),
> +				  op, field, NULL_TREE);
> +		return build4_loc (loc, ARRAY_REF, type, op, min_val,
> +				   NULL_TREE, NULL_TREE);
> +	      }
>   	}
>       }
>     else if (TREE_CODE (sub) == POINTER_PLUS_EXPR
> @@ -3521,12 +3592,44 @@ cxx_fold_indirect_ref (location_t loc, t
>   	    {
>   	      tree field = TYPE_FIELDS (op00type);
>   	      for (; field; field = DECL_CHAIN (field))
> -		if (TREE_CODE (field) == FIELD_DECL
> -		    && TREE_TYPE (field) != error_mark_node
> -		    && tree_int_cst_equal (byte_position (field), op01)
> -		    && similar_type_p (TREE_TYPE (field), type))
> +		if (TREE_CODE (field) != FIELD_DECL
> +		    || TREE_TYPE (field) == error_mark_node)
> +		  continue;
> +		else if (tree_int_cst_equal (byte_position (field), op01)
> +			 && similar_type_p (TREE_TYPE (field), type))
>   		  return fold_build3 (COMPONENT_REF, type, op00,
>   				      field, NULL_TREE);
> +		else if (TREE_CODE (TREE_TYPE (field)) == ARRAY_TYPE
> +			 && tree_int_cst_le (byte_position (field), op01)
> +			 && similar_type_p (TREE_TYPE (TREE_TYPE (field)),
> +					    type))
> +		  {
> +		    tree type_domain = TYPE_DOMAIN (TREE_TYPE (field));
> +		    tree min_val = size_zero_node;
> +		    tree max_val = NULL_TREE;
> +		    if (type_domain && TYPE_MIN_VALUE (type_domain))
> +		      min_val = TYPE_MIN_VALUE (type_domain);
> +		    if (type_domain && TYPE_MAX_VALUE (type_domain))
> +		      max_val = TYPE_MAX_VALUE (type_domain);
> +		    offset_int off = wi::to_offset (op01);
> +		    off -= wi::to_offset (byte_position (field));
> +		    offset_int el_sz = wi::to_offset (TYPE_SIZE_UNIT (type));
> +		    offset_int remainder;
> +		    off = wi::divmod_trunc (off, el_sz, SIGNED, &remainder);
> +		    if (remainder == 0
> +			&& TREE_CODE (min_val) == INTEGER_CST
> +			&& (max_val == NULL_TREE
> +			    || (TREE_CODE (max_val) == INTEGER_CST
> +				&& off <= wi::to_offset (max_val))))
> +		      {
> +			off = off + wi::to_offset (min_val);
> +			op00 = fold_build3 (COMPONENT_REF, TREE_TYPE (field),
> +					    op00, field, NULL_TREE);
> +			op01 = wide_int_to_tree (sizetype, off);
> +			return build4_loc (loc, ARRAY_REF, type, op00, op01,
> +					   NULL_TREE, NULL_TREE);
> +		      }
> +		  }

I think we want to factor this function more, so we don't have the same 
code in multiple places for handling an array, and an array member, and 
a pointer to array.  Do you want to take a look at bug 71504 while 
you're touching this code?

> @@ -3645,7 +3748,23 @@ cxx_eval_indirect_ref (const constexpr_c
>   static void
>   non_const_var_error (tree r)
>   {
> +  auto_diagnostic_group d;
>     tree type = TREE_TYPE (r);
> +  if (DECL_NAME (r) == heap_uninit_identifier
> +      || DECL_NAME (r) == heap_identifier)
> +    {
> +      error ("the content of uninitialized storage is not usable "
> +	     "in a constant expression");
> +      inform (DECL_SOURCE_LOCATION (r), "allocated here");
> +      return;
> +    }
> +  if (DECL_NAME (r) == heap_deleted_identifier)
> +    {
> +      error ("use of allocated storage after deallocation in a "
> +	     "constant expression");
> +      inform (DECL_SOURCE_LOCATION (r), "allocated here");
> +      return;
> +    }
>     error ("the value of %qD is not usable in a constant "
>   	 "expression", r);
>     /* Avoid error cascade.  */
> @@ -3892,6 +4011,15 @@ cxx_eval_store_expression (const constex
>       valp = ctx->values->get (object);
>     else
>       valp = NULL;
> +  if (!valp
> +      && VAR_P (object)
> +      && DECL_NAME (object) == heap_identifier)
> +    {
> +      tree ctor = build_constructor (type, NULL);
> +      CONSTRUCTOR_NO_CLEARING (ctor) = true;
> +      ctx->values->put (object, ctor);
> +      valp = ctx->values->get (object);
> +    }

Instead of this, how about giving the object NULL_TREE value when we 
create it in cxx_eval_call_expression?

>     if (!valp)
>       {
>         /* A constant-expression cannot modify objects from outside the
> @@ -3905,7 +4033,7 @@ cxx_eval_store_expression (const constex
>     bool no_zero_init = true;
>   
>     releasing_vec ctors;
> -  while (!refs->is_empty())
> +  while (!refs->is_empty ())
>       {
>         if (*valp == NULL_TREE)
>   	{
> @@ -4046,7 +4174,9 @@ cxx_eval_store_expression (const constex
>     if (const_object_being_modified)
>       {
>         bool fail = false;
> -      if (!CLASS_TYPE_P (TREE_TYPE (const_object_being_modified)))
> +      tree const_objtype
> +	= strip_array_types (TREE_TYPE (const_object_being_modified));
> +      if (!CLASS_TYPE_P (const_objtype))

This looks like an unrelated bugfix; you might commit it (and the 
others) separately if that's convenient.

>   	fail = true;
>         else
>   	{
> @@ -4365,6 +4495,12 @@ cxx_eval_loop_expr (const constexpr_ctx
>   		    tree *jump_target)
>   {
>     constexpr_ctx new_ctx = *ctx;
> +  tree local_target;
> +  if (!jump_target)
> +    {
> +      local_target = NULL_TREE;
> +      jump_target = &local_target;
> +    }
>   
>     tree body, cond = NULL_TREE, expr = NULL_TREE;
>     int count = 0;
> @@ -4907,14 +5043,21 @@ cxx_eval_constant_expression (const cons
>         break;
>   
>       case CLEANUP_STMT:
> -      r = cxx_eval_constant_expression (ctx, CLEANUP_BODY (t), lval,
> +      {
> +	tree initial_jump_target = jump_target ? *jump_target : NULL_TREE;
> +	r = cxx_eval_constant_expression (ctx, CLEANUP_BODY (t), lval,
> +					  non_constant_p, overflow_p,
> +					  jump_target);
> +	if (!CLEANUP_EH_ONLY (t) && !*non_constant_p)
> +	  /* Also evaluate the cleanup.  If we weren't skipping at the
> +	     start of the CLEANUP_BODY, change jump_target temporarily
> +	     to &initial_jump_target, so that even a return or break or
> +	     continue in the body doesn't skip the cleanup.  */

This also looks like an unrelated bugfix.

> +	  cxx_eval_constant_expression (ctx, CLEANUP_EXPR (t), true,
>   					non_constant_p, overflow_p,
> -					jump_target);
> -      if (!CLEANUP_EH_ONLY (t) && !*non_constant_p)
> -	/* Also evaluate the cleanup.  */
> -	cxx_eval_constant_expression (ctx, CLEANUP_EXPR (t), true,
> -				      non_constant_p, overflow_p,
> -				      jump_target);
> +					jump_target ? &initial_jump_target
> +					: NULL);
> +      }
>         break;
>   
>         /* These differ from cxx_eval_unary_expression in that this doesn't
> @@ -5203,8 +5346,7 @@ cxx_eval_constant_expression (const cons
>   	if (VOID_TYPE_P (type))
>   	  return void_node;
>   
> -	if (TREE_CODE (op) == PTRMEM_CST
> -	    && !TYPE_PTRMEM_P (type))
> +	if (TREE_CODE (op) == PTRMEM_CST && !TYPE_PTRMEM_P (type))
>   	  op = cplus_expand_constant (op);
>   
>   	if (TREE_CODE (op) == PTRMEM_CST && tcode == NOP_EXPR)
> @@ -5258,6 +5400,81 @@ cxx_eval_constant_expression (const cons
>   	      }
>   	  }
>   
> +	if (INDIRECT_TYPE_P (type)
> +	    && TREE_CODE (op) == NOP_EXPR
> +	    && TREE_TYPE (op) == ptr_type_node
> +	    && TREE_CODE (TREE_OPERAND (op, 0)) == ADDR_EXPR
> +	    && VAR_P (TREE_OPERAND (TREE_OPERAND (op, 0), 0))
> +	    && DECL_NAME (TREE_OPERAND (TREE_OPERAND (op, 0),
> +					0)) == heap_uninit_identifier)
> +	  {
> +	    tree var = TREE_OPERAND (TREE_OPERAND (op, 0), 0);
> +	    tree var_type = TREE_TYPE (type);
> +	    tree cookie_type = NULL_TREE;
> +	    bool array_p = false;
> +	    HOST_WIDE_INT cookie_size = 0;
> +	    if (TREE_CODE (var_type) == ARRAY_TYPE
> +		&& TYPE_DOMAIN (var_type) == NULL_TREE)
> +	      {
> +		var_type = TREE_TYPE (var_type);
> +		array_p = true;
> +	      }
> +	    else if (TREE_CODE (var_type) == RECORD_TYPE
> +		     && TYPE_NAME (var_type) == NULL_TREE)
> +	      if (tree fld1 = TYPE_FIELDS (var_type))
> +		if (TREE_CODE (fld1) == FIELD_DECL
> +		    && DECL_NAME (fld1) == NULL_TREE
> +		    && DECL_ARTIFICIAL (fld1)
> +		    && TREE_CODE (TREE_TYPE (fld1)) == ARRAY_TYPE
> +		    && COMPLETE_TYPE_P (TREE_TYPE (fld1)))
> +		  if (tree fld2 = DECL_CHAIN (fld1))
> +		    if (TREE_CODE (fld2) == FIELD_DECL
> +			&& DECL_NAME (fld2) == NULL_TREE
> +			&& DECL_ARTIFICIAL (fld2)
> +			&& TREE_CODE (TREE_TYPE (fld2)) == ARRAY_TYPE
> +			&& TYPE_DOMAIN (TREE_TYPE (fld2)) == NULL_TREE
> +			&& DECL_CHAIN (fld2) == NULL_TREE)

Maybe give the struct a magic name so you don't need to do as much 
checking of the FIELD_DECLs?

> +		      {
> +			var_type = TREE_TYPE (TREE_TYPE (fld2));
> +			array_p = true;
> +			cookie_type = TREE_TYPE (fld1);
> +			cookie_size = int_size_in_bytes (TREE_TYPE (fld1));
> +		      }
> +	    HOST_WIDE_INT sz1 = int_size_in_bytes (var_type);
> +	    HOST_WIDE_INT sz2 = int_size_in_bytes (TREE_TYPE (var));
> +	    if (sz1 <= sz2 && cookie_size <= sz2)
> +	      {
> +		DECL_NAME (var) = heap_identifier;
> +		if (array_p && sz1 > 0)
> +		  {
> +		    sz2 -= cookie_size;
> +		    sz2 /= sz1;
> +		    tree idx_type = build_index_type (size_int (sz2 - 1));
> +		    var_type = build_cplus_array_type (var_type, idx_type);
> +		    if (cookie_type)
> +		      {
> +			location_t loc = cp_expr_loc_or_input_loc (t);
> +			tree vtype = cxx_make_type (RECORD_TYPE);
> +			tree fld1 = build_decl (loc, FIELD_DECL, NULL_TREE,
> +						cookie_type);
> +			tree fld2 = build_decl (loc, FIELD_DECL, NULL_TREE,
> +						var_type);
> +			DECL_FIELD_CONTEXT (fld1) = vtype;
> +			DECL_FIELD_CONTEXT (fld2) = vtype;
> +			DECL_ARTIFICIAL (fld1) = true;
> +			DECL_ARTIFICIAL (fld2) = true;
> +			TYPE_FIELDS (vtype) = fld1;
> +			DECL_CHAIN (fld1) = fld2;
> +			layout_type (vtype);
> +			var_type = vtype;

So here you're completing the type of the array member of the struct.

> +		TREE_TYPE (var) = var_type;
> +		TREE_TYPE (TREE_OPERAND (op, 0))
> +		  = build_pointer_type (var_type);
> +	      }
> +	  }

Let's factor out all of this code, too.

> +
>   	if (op == oldop && tcode != UNARY_PLUS_EXPR)
>   	  /* We didn't fold at the top so we could check for ptr-int
>   	     conversion.  */
> @@ -5499,6 +5716,7 @@ instantiate_cx_fn_r (tree *tp, int *walk
>   
>     return NULL_TREE;
>   }
> +
>   static void
>   instantiate_constexpr_fns (tree t)
>   {
> @@ -5507,17 +5725,36 @@ instantiate_constexpr_fns (tree t)
>     input_location = loc;
>   }
>   
> +/* Look for heap variables in the expression *TP.  */
> +
> +static tree
> +find_heap_var_refs (tree *tp, int *walk_subtrees, void */*data*/)
> +{
> +  if (VAR_P (*tp)
> +      && (DECL_NAME (*tp) == heap_uninit_identifier
> +	  || DECL_NAME (*tp) == heap_identifier
> +	  || DECL_NAME (*tp) == heap_deleted_identifier))
> +    return *tp;
> +
> +  if (TYPE_P (*tp))
> +    *walk_subtrees = 0;
> +  return NULL_TREE;
> +}
> +
>   /* ALLOW_NON_CONSTANT is false if T is required to be a constant expression.
>      STRICT has the same sense as for constant_value_1: true if we only allow
>      conforming C++ constant expressions, or false if we want a constant value
>      even if it doesn't conform.
>      MANIFESTLY_CONST_EVAL is true if T is manifestly const-evaluated as
> -   per P0595 even when ALLOW_NON_CONSTANT is true.  */
> +   per P0595 even when ALLOW_NON_CONSTANT is true.
> +   CONSTEXPR_DTOR is true when evaluating the dtor of a constexpr variable.
> +   OBJECT must be non-NULL in that case.  */

>   static tree
>   cxx_eval_outermost_constant_expr (tree t, bool allow_non_constant,
>   				  bool strict = true,
>   				  bool manifestly_const_eval = false,
> +				  bool constexpr_dtor = false,
>   				  tree object = NULL_TREE)
>   {
>     auto_timevar time (TV_CONSTEXPR);
> @@ -5525,16 +5762,23 @@ cxx_eval_outermost_constant_expr (tree t
>     bool non_constant_p = false;
>     bool overflow_p = false;
>     hash_map<tree,tree> map;
> +  auto_vec<tree, 16> heap_vars;
>     HOST_WIDE_INT constexpr_ctx_count = 0;
>   
>     constexpr_ctx ctx = { NULL, &map, NULL, NULL, NULL, NULL,
> -			&constexpr_ctx_count, allow_non_constant, strict,
> -			manifestly_const_eval || !allow_non_constant };
> +			&constexpr_ctx_count, &heap_vars, allow_non_constant,
> +			strict, manifestly_const_eval || !allow_non_constant };

As we add more stuff to constexpr_ctx, creating new ones on the stack 
becomes more and more expensive.  We should really split off the parts 
that change frequently: Maybe just ctor/object, maybe also 
call/save_exprs/...?

>     tree type = initialized_type (t);
>     tree r = t;
>     if (VOID_TYPE_P (type))
> -    return t;
> +    {
> +      if (TREE_CODE (t) == BIND_EXPR && constexpr_dtor)
> +	/* Used for destructors of array elements.  */
> +	type = TREE_TYPE (object);
> +      else
> +	return t;
> +    } >     if (AGGREGATE_TYPE_P (type) || VECTOR_TYPE_P (type))
>       {
>         /* In C++14 an NSDMI can participate in aggregate initialization,
> @@ -5544,8 +5788,22 @@ cxx_eval_outermost_constant_expr (tree t
>   	 update ctx.values for the VAR_DECL.  We use the same strategy
>   	 for C++11 constexpr constructors that refer to the object being
>   	 initialized.  */
> -      ctx.ctor = build_constructor (type, NULL);
> -      CONSTRUCTOR_NO_CLEARING (ctx.ctor) = true;
> +      if (constexpr_dtor)
> +	{
> +	  gcc_assert (object && VAR_P (object));
> +	  gcc_assert (DECL_DECLARED_CONSTEXPR_P (object));
> +	  gcc_assert (DECL_INITIALIZED_BY_CONSTANT_EXPRESSION_P (object));
> +	  ctx.ctor = unshare_expr (DECL_INITIAL (object));
> +	  TREE_READONLY (ctx.ctor) = false;
> +	  /* Temporarily force decl_really_constant_value to return false
> +	     for it, we want to use ctx.ctor for the current value instead.  */
> +	  DECL_INITIALIZED_BY_CONSTANT_EXPRESSION_P (object) = false;
> +	}
> +      else
> +	{
> +	  ctx.ctor = build_constructor (type, NULL);
> +	  CONSTRUCTOR_NO_CLEARING (ctx.ctor) = true;
> +	}
>         if (!object)
>   	{
>   	  if (TREE_CODE (t) == TARGET_EXPR)
> @@ -5569,13 +5827,15 @@ cxx_eval_outermost_constant_expr (tree t
>     r = cxx_eval_constant_expression (&ctx, r,
>   				    false, &non_constant_p, &overflow_p);
>   
> -  verify_constant (r, allow_non_constant, &non_constant_p, &overflow_p);
> +  if (!constexpr_dtor)
> +    verify_constant (r, allow_non_constant, &non_constant_p, &overflow_p);
> +  else
> +    DECL_INITIALIZED_BY_CONSTANT_EXPRESSION_P (object) = true;
>   
>     /* Mutable logic is a bit tricky: we want to allow initialization of
>        constexpr variables with mutable members, but we can't copy those
>        members to another constexpr variable.  */
> -  if (TREE_CODE (r) == CONSTRUCTOR
> -      && CONSTRUCTOR_MUTABLE_POISON (r))
> +  if (TREE_CODE (r) == CONSTRUCTOR && CONSTRUCTOR_MUTABLE_POISON (r))
>       {
>         if (!allow_non_constant)
>   	error ("%qE is not a constant expression because it refers to "
> @@ -5583,8 +5843,7 @@ cxx_eval_outermost_constant_expr (tree t
>         non_constant_p = true;
>       }
>   
> -  if (TREE_CODE (r) == CONSTRUCTOR
> -      && CONSTRUCTOR_NO_CLEARING (r))
> +  if (TREE_CODE (r) == CONSTRUCTOR && CONSTRUCTOR_NO_CLEARING (r))
>       {
>         if (!allow_non_constant)
>   	error ("%qE is not a constant expression because it refers to "
> @@ -5593,6 +5852,32 @@ cxx_eval_outermost_constant_expr (tree t
>         non_constant_p = true;
>       }
>   
> +  if (!heap_vars.is_empty ())
> +    {
> +      tree heap_var = cp_walk_tree_without_duplicates (&r, find_heap_var_refs,
> +						       NULL);

Doesn't verify_constant already complain about remaining references to 
allocated objects?

> +      unsigned int i;
> +      if (heap_var)
> +	{
> +	  if (!allow_non_constant && !non_constant_p)
> +	    error_at (DECL_SOURCE_LOCATION (heap_var),
> +		      "%qE is not a constant expression because it refers to "
> +		      "a result of %<operator new%>", t);
> +	  r = t;
> +	  non_constant_p = true;
> +	}
> +      FOR_EACH_VEC_ELT (heap_vars, i, heap_var)
> +	if (DECL_NAME (heap_var) != heap_deleted_identifier)
> +	  {
> +	    if (!allow_non_constant && !non_constant_p)
> +	      error_at (DECL_SOURCE_LOCATION (heap_var),
> +			"%qE is not a constant expression because allocated "
> +			"storage has not been deallocated", t);
> +	    r = t;
> +	    non_constant_p = true;
> +	  }
> +    }
> +
>     /* Technically we should check this for all subexpressions, but that
>        runs into problems with our internal representation of pointer
>        subtraction and the 5.19 rules are still in flux.  */
> @@ -5618,6 +5903,8 @@ cxx_eval_outermost_constant_expr (tree t
>   
>     if (non_constant_p && !allow_non_constant)
>       return error_mark_node;
> +  else if (constexpr_dtor)
> +    return r;
>     else if (non_constant_p && TREE_CONSTANT (r))
>       {
>         /* If __builtin_is_constant_evaluated () was evaluated to true
> @@ -5625,7 +5912,7 @@ cxx_eval_outermost_constant_expr (tree t
>   	 punt.  */
>         if (manifestly_const_eval)
>   	return cxx_eval_outermost_constant_expr (t, true, strict,
> -						 false, object);
> +						 false, false, object);
>         /* This isn't actually constant, so unset TREE_CONSTANT.
>   	 Don't clear TREE_CONSTANT on ADDR_EXPR, as the middle-end requires
>   	 it to be set if it is invariant address, even when it is not
> @@ -5653,7 +5940,7 @@ cxx_eval_outermost_constant_expr (tree t
>   	return t;
>         else if (TREE_CODE (t) != CONSTRUCTOR)
>   	{
> -	  r = get_target_expr (r);
> +	  r = get_target_expr_sfinae (r, tf_warning_or_error | tf_no_cleanup);
>   	  TREE_CONSTANT (r) = true;
>   	}
>       }
> @@ -5668,7 +5955,16 @@ cxx_eval_outermost_constant_expr (tree t
>   tree
>   cxx_constant_value (tree t, tree decl)
>   {
> -  return cxx_eval_outermost_constant_expr (t, false, true, true, decl);
> +  return cxx_eval_outermost_constant_expr (t, false, true, true, false, decl);
> +}
> +
> +/* Like cxx_constant_value, but used for evaluation of constexpr destructors
> +   of constexpr variables.  The actual initializer of DECL is not modified.  */
> +
> +void
> +cxx_constant_dtor (tree t, tree decl)
> +{
> +  cxx_eval_outermost_constant_expr (t, false, true, true, true, decl);
>   }
>   
>   /* Helper routine for fold_simple function.  Either return simplified
> @@ -5772,14 +6068,14 @@ maybe_constant_value (tree t, tree decl,
>       return t;
>   
>     if (manifestly_const_eval)
> -    return cxx_eval_outermost_constant_expr (t, true, true, true, decl);
> +    return cxx_eval_outermost_constant_expr (t, true, true, true, false, decl);
>   
>     if (cv_cache == NULL)
>       cv_cache = hash_map<tree, tree>::create_ggc (101);
>     if (tree *cached = cv_cache->get (t))
>       return *cached;
>   
> -  r = cxx_eval_outermost_constant_expr (t, true, true, false, decl);
> +  r = cxx_eval_outermost_constant_expr (t, true, true, false, false, decl);
>     gcc_checking_assert (r == t
>   		       || CONVERT_EXPR_P (t)
>   		       || TREE_CODE (t) == VIEW_CONVERT_EXPR
> @@ -5841,7 +6137,7 @@ fold_non_dependent_expr_template (tree t
>   
>         tree r = cxx_eval_outermost_constant_expr (t, true, true,
>   						 manifestly_const_eval,
> -						 NULL_TREE);
> +						 false, NULL_TREE);
>         /* cp_tree_equal looks through NOPs, so allow them.  */
>         gcc_checking_assert (r == t
>   			   || CONVERT_EXPR_P (t)
> @@ -5945,7 +6241,7 @@ maybe_constant_init_1 (tree t, tree decl
>     else
>       t = cxx_eval_outermost_constant_expr (t, allow_non_constant,
>   					  /*strict*/false,
> -					  manifestly_const_eval, decl);
> +					  manifestly_const_eval, false, decl);
>     if (TREE_CODE (t) == TARGET_EXPR)
>       {
>         tree init = TARGET_EXPR_INITIAL (t);
> @@ -6239,7 +6535,12 @@ potential_constant_expression_1 (tree t,
>   		if (!DECL_DECLARED_CONSTEXPR_P (fun)
>   		    /* Allow any built-in function; if the expansion
>   		       isn't constant, we'll deal with that then.  */
> -		    && !fndecl_built_in_p (fun))
> +		    && !fndecl_built_in_p (fun)
> +		    /* In C++2a, replaceable global allocation functions
> +		       are constant expressions.  */
> +		    && (cxx_dialect < cxx2a
> +			|| !IDENTIFIER_NEWDEL_OP_P (DECL_NAME (fun))
> +			|| CP_DECL_CONTEXT (fun) != global_namespace))

This is the second occurrence of this three-line test for a constexpr 
(de)allocation function, let's factor it out.

>   		  {
>   		    if (flags & tf_error)
>   		      {
> @@ -6468,6 +6769,9 @@ potential_constant_expression_1 (tree t,
>   	goto fail;
>         if (!RECUR (TREE_OPERAND (t, 0), any))
>   	return false;
> +      /* Just ignore clobbers.  */
> +      if (TREE_CLOBBER_P (TREE_OPERAND (t, 1)))
> +	return true;
>         if (!RECUR (TREE_OPERAND (t, 1), rval))
>   	return false;
>         return true;
> @@ -6937,7 +7241,7 @@ potential_constant_expression_1 (tree t,
>        return true;
>   
>       case COND_EXPR:
> -      if (COND_EXPR_IS_VEC_DELETE (t))
> +      if (COND_EXPR_IS_VEC_DELETE (t) && cxx_dialect < cxx2a)
>   	{
>   	  if (flags & tf_error)
>   	    error_at (loc, "%<delete[]%> is not a constant expression");
> @@ -6983,6 +7287,12 @@ potential_constant_expression_1 (tree t,
>         return true;
>   
>       case CLEANUP_STMT:
> +      if (!RECUR (CLEANUP_BODY (t), any))
> +	return false;
> +      if (!CLEANUP_EH_ONLY (t) && !RECUR (CLEANUP_EXPR (t), any))
> +	return false;
> +      return true;
> +
>       case EMPTY_CLASS_EXPR:
>       case PREDICT_EXPR:
>         return false;
> --- gcc/cp/decl.c.jj	2019-09-26 21:34:21.673916726 +0200
> +++ gcc/cp/decl.c	2019-09-27 18:25:40.898066032 +0200
> @@ -4146,6 +4146,9 @@ initialize_predefined_identifiers (void)
>       {"value", &value_identifier, cik_normal},
>       {"_FUN", &fun_identifier, cik_normal},
>       {"__closure", &closure_identifier, cik_normal},
> +    {"heap uninit", &heap_uninit_identifier, cik_normal},
> +    {"heap ", &heap_identifier, cik_normal},
> +    {"heap deleted", &heap_deleted_identifier, cik_normal},
>       {NULL, NULL, cik_normal}
>     };
>   
> @@ -7430,7 +7433,11 @@ cp_finish_decl (tree decl, tree init, bo
>   	    TREE_READONLY (decl) = 1;
>   
>   	  /* Likewise if it needs destruction.  */
> -	  if (TYPE_HAS_NONTRIVIAL_DESTRUCTOR (type))
> +	  if (TYPE_HAS_NONTRIVIAL_DESTRUCTOR (type)
> +	      && (cxx_dialect < cxx2a
> +		  || !DECL_DECLARED_CONSTEXPR_P (decl)
> +		  || !type_has_constexpr_destructor
> +						(strip_array_types (type))))

This could use a decl_maybe_constant_destruction predicate.

>   	    TREE_READONLY (decl) = 0;
>   	}
>   
> @@ -8319,6 +8326,27 @@ register_dtor_fn (tree decl)
>     if (TYPE_HAS_TRIVIAL_DESTRUCTOR (type))
>       return void_node;
>   
> +  if (cxx_dialect >= cxx2a
> +      && DECL_DECLARED_CONSTEXPR_P (decl)
> +      && DECL_INITIALIZED_BY_CONSTANT_EXPRESSION_P (decl)
> +      && type_has_constexpr_destructor (strip_array_types (type)))

...which we'd use again here.

> +    {
> +      int flags = LOOKUP_NORMAL|LOOKUP_NONVIRTUAL|LOOKUP_DESTRUCTOR;
> +      tree addr, call;
> +
> +      if (TREE_CODE (type) == ARRAY_TYPE)
> +	addr = decl;
> +      else
> +	addr = build_address (decl);
> +
> +      call = build_delete (TREE_TYPE (addr), addr,
> +			   sfk_complete_destructor, flags, 0,
> +			   tf_warning_or_error);
> +      if (call != error_mark_node)
> +	cxx_constant_dtor (call, decl);
> +      return void_node;

Why not use the result of build_cleanup?

> +    }
> +
>     /* If we're using "__cxa_atexit" (or "__cxa_thread_atexit" or
>        "__aeabi_atexit"), and DECL is a class object, we can just pass the
>        destructor to "__cxa_atexit"; we don't have to build a temporary
> @@ -8432,11 +8460,15 @@ register_dtor_fn (tree decl)
>   static void
>   expand_static_init (tree decl, tree init)
>   {
> +  tree type = TREE_TYPE (decl);
>     gcc_assert (VAR_P (decl));
>     gcc_assert (TREE_STATIC (decl));
>   
>     /* Some variables require no dynamic initialization.  */
> -  if (TYPE_HAS_TRIVIAL_DESTRUCTOR (TREE_TYPE (decl)))
> +  if (TYPE_HAS_TRIVIAL_DESTRUCTOR (TREE_TYPE (decl))
> +      || (cxx_dialect >= cxx2a
> +	  && DECL_DECLARED_CONSTEXPR_P (decl)
> +	  && type_has_constexpr_destructor (strip_array_types (type))))
>       {
>         /* Make sure the destructor is callable.  */
>         cxx_maybe_build_cleanup (decl, tf_warning_or_error);
> @@ -12702,12 +12734,13 @@ grokdeclarator (const cp_declarator *dec
>   			      "a destructor cannot be %<concept%>");
>                       return error_mark_node;
>                     }
> -                if (constexpr_p)
> -                  {
> -                    error_at (declspecs->locations[ds_constexpr],
> -			      "a destructor cannot be %<constexpr%>");
> -                    return error_mark_node;
> -                  }
> +		if (constexpr_p && cxx_dialect < cxx2a)
> +		  {
> +		    error_at (declspecs->locations[ds_constexpr],
> +			      "%<constexpr%> destructors only available"
> +			      " with %<-std=c++2a%> or %<-std=gnu++2a%>");
> +		    return error_mark_node;
> +		  }
>   	      }
>   	    else if (sfk == sfk_constructor && friendp && !ctype)
>   	      {
> @@ -12744,10 +12777,11 @@ grokdeclarator (const cp_declarator *dec
>   	      }
>   
>   	    /* Tell grokfndecl if it needs to set TREE_PUBLIC on the node.  */
> -	    function_context = (ctype != NULL_TREE) ?
> -	      decl_function_context (TYPE_MAIN_DECL (ctype)) : NULL_TREE;
> -	    publicp = (! friendp || ! staticp)
> -	      && function_context == NULL_TREE;
> +	    function_context
> +	      = (ctype != NULL_TREE
> +		 ? decl_function_context (TYPE_MAIN_DECL (ctype)) : NULL_TREE);
> +	    publicp = ((! friendp || ! staticp)
> +		       && function_context == NULL_TREE);
>   
>   	    decl = grokfndecl (ctype, type,
>   			       TREE_CODE (unqualified_id) != TEMPLATE_ID_EXPR
> @@ -16752,6 +16786,12 @@ cxx_maybe_build_cleanup (tree decl, tsub
>   	cleanup = error_mark_node;
>         else if (TYPE_HAS_TRIVIAL_DESTRUCTOR (type))
>   	/* Discard the call.  */;
> +      else if (cxx_dialect >= cxx2a
> +	       && VAR_P (decl)
> +	       && DECL_DECLARED_CONSTEXPR_P (decl)
> +	       && DECL_INITIALIZED_BY_CONSTANT_EXPRESSION_P (decl)
> +	       && type_has_constexpr_destructor (strip_array_types (type)))
> +	cxx_constant_dtor (call, decl);
>         else if (cleanup)
>   	cleanup = cp_build_compound_expr (cleanup, call, complain);
>         else
> --- gcc/cp/init.c.jj	2019-09-26 21:34:21.598917851 +0200
> +++ gcc/cp/init.c	2019-09-27 19:15:56.473042238 +0200
> @@ -33,6 +33,7 @@ along with GCC; see the file COPYING3.
>   #include "stringpool.h"
>   #include "attribs.h"
>   #include "asan.h"
> +#include "stor-layout.h"
>   
>   static bool begin_init_stmts (tree *, tree *);
>   static tree finish_init_stmts (bool, tree, tree);
> @@ -3332,6 +3333,48 @@ build_new_1 (vec<tree, va_gc> **placemen
>   	}
>       }
>   
> +  tree alloc_call_call = extract_call_expr (alloc_call);
> +  tree alloc_call_fndecl = NULL_TREE;
> +  if (alloc_call_call != error_mark_node)
> +    alloc_call_fndecl = cp_get_callee_fndecl_nofold (alloc_call_call);
> +  if (array_p
> +      && cxx_dialect >= cxx2a
> +      && (current_function_decl == NULL_TREE
> +	  || DECL_DECLARED_CONSTEXPR_P (current_function_decl))
> +      && alloc_call_fndecl
> +      && IDENTIFIER_NEW_OP_P (DECL_NAME (alloc_call_fndecl))
> +      && CP_DECL_CONTEXT (alloc_call_fndecl) == global_namespace)
> +    {
> +      /* Help the constexpr code to find the right type for the heap variable
> +	 by adding a NOP_EXPR around alloc_call.
> +	 If not using cookies, use array type, otherwise structure with
> +	 two array types.  */

i.e.

struct {
   size_t cookie[N];
   elt array[];
};

I guess you want an array for the cookie rather than a non-array size_t 
data member to handle ARM cookies that also include the element size. 
Please include this in the comment.

> +      tree atype = build_cplus_array_type (elt_type, NULL_TREE);
> +      if (cookie_size)
> +	{
> +	  gcc_assert (tree_fits_uhwi_p (cookie_size));
> +	  unsigned HOST_WIDE_INT sz = tree_to_uhwi (cookie_size);
> +	  sz /= int_size_in_bytes (sizetype);
> +	  tree atype2 = build_index_type (size_int (sz - 1));
> +	  atype2 = build_cplus_array_type (sizetype, atype2);
> +	  tree atype3 = cxx_make_type (RECORD_TYPE);
> +	  tree fld1 = build_decl (input_location, FIELD_DECL, NULL_TREE,
> +				  atype2);
> +	  tree fld2 = build_decl (input_location, FIELD_DECL, NULL_TREE,
> +				  atype);
> +	  DECL_FIELD_CONTEXT (fld1) = atype3;
> +	  DECL_FIELD_CONTEXT (fld2) = atype3;
> +	  DECL_ARTIFICIAL (fld1) = true;
> +	  DECL_ARTIFICIAL (fld2) = true;
> +	  TYPE_FIELDS (atype3) = fld1;
> +	  DECL_CHAIN (fld1) = fld2;
> +	  layout_type (atype3);
> +	  atype = atype3;
> +	}
> +      pointer_type = build_pointer_type (atype);
> +      alloc_call = build_nop (pointer_type, alloc_call);
> +    }

All of this could be factored out into a function called something like 
maybe_wrap_new_for_constexpr.

> +
>     /* In the simple case, we can stop now.  */
>     pointer_type = build_pointer_type (type);
>     if (!cookie_size && !is_initialized)
> @@ -3905,17 +3948,11 @@ build_vec_delete_1 (tree base, tree maxi
>   			     fold_convert (sizetype, maxindex));
>   
>     tbase = create_temporary_var (ptype);
> -  tbase_init
> -    = cp_build_modify_expr (input_location, tbase, NOP_EXPR,
> -			    fold_build_pointer_plus_loc (input_location,
> -							 fold_convert (ptype,
> -								       base),
> -							 virtual_size),
> -			    complain);
> -  if (tbase_init == error_mark_node)
> -    return error_mark_node;
> -  controller = build3 (BIND_EXPR, void_type_node, tbase,
> -		       NULL_TREE, NULL_TREE);
> +  DECL_INITIAL (tbase)
> +    = fold_build_pointer_plus_loc (input_location, fold_convert (ptype, base),
> +				   virtual_size);
> +  tbase_init = build_stmt (input_location, DECL_EXPR, tbase);
> +  controller = build3 (BIND_EXPR, void_type_node, tbase, NULL_TREE, NULL_TREE);
>     TREE_SIDE_EFFECTS (controller) = 1;
>   
>     body = build1 (EXIT_EXPR, void_type_node,
> --- gcc/cp/method.c.jj	2019-09-26 21:34:21.331921851 +0200
> +++ gcc/cp/method.c	2019-09-27 18:25:40.902065972 +0200
> @@ -30,6 +30,7 @@ along with GCC; see the file COPYING3.
>   #include "cgraph.h"
>   #include "varasm.h"
>   #include "toplev.h"
> +#include "intl.h"
>   #include "common/common-target.h"
>   
>   static void do_build_copy_assign (tree);
> @@ -1237,12 +1238,24 @@ is_xible (enum tree_code code, tree to,
>     return !!expr;
>   }
>   
> +/* Categorize various special_function_kinds.  */
> +#define SFK_CTOR_P(sfk) \
> +  ((sfk) >= sfk_constructor && (sfk) <= sfk_move_constructor)
> +#define SFK_DTOR_P(sfk) \
> +  ((sfk) == sfk_destructor || (sfk) == sfk_virtual_destructor)
> +#define SFK_ASSIGN_P(sfk) \
> +  ((sfk) == sfk_copy_assignment || (sfk) == sfk_move_assignment)
> +#define SFK_COPY_P(sfk) \
> +  ((sfk) == sfk_copy_constructor || (sfk) == sfk_copy_assignment)
> +#define SFK_MOVE_P(sfk) \
> +  ((sfk) == sfk_move_constructor || (sfk) == sfk_move_assignment)
> +
>   /* Subroutine of synthesized_method_walk.  Update SPEC_P, TRIVIAL_P and
>      DELETED_P or give an error message MSG with argument ARG.  */
>   
>   static void
> -process_subob_fn (tree fn, tree *spec_p, bool *trivial_p,
> -		  bool *deleted_p, bool *constexpr_p,
> +process_subob_fn (tree fn, special_function_kind sfk, tree *spec_p,
> +		  bool *trivial_p, bool *deleted_p, bool *constexpr_p,
>   		  bool diag, tree arg, bool dtor_from_ctor = false)
>   {
>     if (!fn || fn == error_mark_node)
> @@ -1283,24 +1296,15 @@ process_subob_fn (tree fn, tree *spec_p,
>         if (diag)
>   	{
>   	  inform (DECL_SOURCE_LOCATION (fn),
> -		  "defaulted constructor calls non-%<constexpr%> %qD", fn);
> +		  SFK_DTOR_P (sfk)
> +		  ? G_("destructor calls non-%<constexpr%> %qD")

Not "defaulted"?

> +		  : G_("defaulted constructor calls non-%<constexpr%> %qD"),
> +		  fn);
>   	  explain_invalid_constexpr_fn (fn);
>   	}
>       }
>   }
>   
> -/* Categorize various special_function_kinds.  */
> -#define SFK_CTOR_P(sfk) \
> -  ((sfk) >= sfk_constructor && (sfk) <= sfk_move_constructor)
> -#define SFK_DTOR_P(sfk) \
> -  ((sfk) == sfk_destructor || (sfk) == sfk_virtual_destructor)
> -#define SFK_ASSIGN_P(sfk) \
> -  ((sfk) == sfk_copy_assignment || (sfk) == sfk_move_assignment)
> -#define SFK_COPY_P(sfk) \
> -  ((sfk) == sfk_copy_constructor || (sfk) == sfk_copy_assignment)
> -#define SFK_MOVE_P(sfk) \
> -  ((sfk) == sfk_move_constructor || (sfk) == sfk_move_assignment)
> -
>   /* Subroutine of synthesized_method_walk to allow recursion into anonymous
>      aggregates.  If DTOR_FROM_CTOR is true, we're walking subobject destructors
>      called from a synthesized constructor, in which case we don't consider
> @@ -1318,8 +1322,7 @@ walk_field_subobs (tree fields, special_
>       {
>         tree mem_type, argtype, rval;
>   
> -      if (TREE_CODE (field) != FIELD_DECL
> -	  || DECL_ARTIFICIAL (field))
> +      if (TREE_CODE (field) != FIELD_DECL || DECL_ARTIFICIAL (field))
>   	continue;
>   
>         /* Variant members only affect deletedness.  In particular, they don't
> @@ -1457,7 +1460,7 @@ walk_field_subobs (tree fields, special_
>   
>         rval = locate_fn_flags (mem_type, fnname, argtype, flags, complain);
>   
> -      process_subob_fn (rval, spec_p, trivial_p, deleted_p,
> +      process_subob_fn (rval, sfk, spec_p, trivial_p, deleted_p,
>   			constexpr_p, diag, field, dtor_from_ctor);
>       }
>   }
> @@ -1510,23 +1513,23 @@ synthesized_method_base_walk (tree binfo
>         && DECL_CONTEXT (*inheriting_ctor) == DECL_CONTEXT (rval))
>       *inheriting_ctor = DECL_CLONED_FUNCTION (rval);
>   
> -  process_subob_fn (rval, spec_p, trivial_p, deleted_p,
> +  process_subob_fn (rval, sfk, spec_p, trivial_p, deleted_p,
>   		    constexpr_p, diag, BINFO_TYPE (base_binfo));
> -  if (SFK_CTOR_P (sfk) &&
> -      (!BINFO_VIRTUAL_P (base_binfo)
> -       || TYPE_HAS_NONTRIVIAL_DESTRUCTOR (BINFO_TYPE (base_binfo))))
> +  if (SFK_CTOR_P (sfk)
> +      && (!BINFO_VIRTUAL_P (base_binfo)
> +	  || TYPE_HAS_NONTRIVIAL_DESTRUCTOR (BINFO_TYPE (base_binfo))))
>       {
>         /* In a constructor we also need to check the subobject
>   	 destructors for cleanup of partially constructed objects.  */
>         tree dtor = locate_fn_flags (base_binfo, complete_dtor_identifier,
>   				   NULL_TREE, flags,
>   				   diag ? tf_warning_or_error : tf_none);
> -	  /* Note that we don't pass down trivial_p; the subobject
> -	     destructors don't affect triviality of the constructor.  Nor
> -	     do they affect constexpr-ness (a constant expression doesn't
> -	     throw) or exception-specification (a throw from one of the
> -	     dtors would be a double-fault).  */
> -      process_subob_fn (dtor, NULL, NULL, deleted_p, NULL, false,
> +      /* Note that we don't pass down trivial_p; the subobject
> +	 destructors don't affect triviality of the constructor.  Nor
> +	 do they affect constexpr-ness (a constant expression doesn't
> +	 throw) or exception-specification (a throw from one of the
> +	 dtors would be a double-fault).  */
> +      process_subob_fn (dtor, sfk, NULL, NULL, deleted_p, NULL, false,
>   			BINFO_TYPE (base_binfo), /*dtor_from_ctor*/true);
>       }
>   
> @@ -1608,7 +1611,8 @@ synthesized_method_walk (tree ctype, spe
>   	member is a constexpr function.  */
>     if (constexpr_p)
>       *constexpr_p = (SFK_CTOR_P (sfk)
> -		    || (SFK_ASSIGN_P (sfk) && cxx_dialect >= cxx14));
> +		    || (SFK_ASSIGN_P (sfk) && cxx_dialect >= cxx14)
> +		    || (SFK_DTOR_P (sfk) && cxx_dialect >= cxx2a));
>   
>     bool expected_trivial = type_has_trivial_fn (ctype, sfk);
>     if (trivial_p)
> @@ -1704,8 +1708,8 @@ synthesized_method_walk (tree ctype, spe
>     else if (vec_safe_is_empty (vbases))
>       /* No virtual bases to worry about.  */;
>     else if (ABSTRACT_CLASS_TYPE_P (ctype) && cxx_dialect >= cxx14
> -	   /* DR 1658 specifis that vbases of abstract classes are
> -	      ignored for both ctors and dtors.  Except DR 2338
> +	   /* DR 1658 specifies that vbases of abstract classes are
> +	      ignored for both ctors and dtors.  Except DR 2336
>   	      overrides that skipping when determing the eh-spec of a
>   	      virtual destructor.  */
>   	   && sfk != sfk_virtual_destructor)
> @@ -2046,7 +2050,8 @@ implicitly_declare_fn (special_function_
>       constexpr_p = false;
>     /* A trivial copy/move constructor is also a constexpr constructor,
>        unless the class has virtual bases (7.1.5p4).  */
> -  else if (trivial_p && cxx_dialect >= cxx11
> +  else if (trivial_p
> +	   && cxx_dialect >= cxx11
>   	   && (kind == sfk_copy_constructor
>   	       || kind == sfk_move_constructor)
>   	   && !CLASSTYPE_VBASECLASSES (type))
> --- gcc/cp/typeck2.c.jj	2019-09-26 21:34:26.299847376 +0200
> +++ gcc/cp/typeck2.c	2019-09-27 18:25:40.899066017 +0200
> @@ -902,7 +902,13 @@ store_init_value (tree decl, tree init,
>   	    value = oldval;
>   	}
>       }
> -  value = cp_fully_fold_init (value);
> +  /* Don't fold initializers of automatic variables in constexpr functions,
> +     that might fold away something that needs to be diagnosed at constexpr
> +     evaluation time.  */
> +  if (!current_function_decl
> +      || !DECL_DECLARED_CONSTEXPR_P (current_function_decl)
> +      || TREE_STATIC (decl))
> +    value = cp_fully_fold_init (value);
>   
>     /* Handle aggregate NSDMI in non-constant initializers, too.  */
>     value = replace_placeholders (value, decl);
> --- gcc/testsuite/g++.dg/cpp0x/constexpr-delete2.C.jj	2016-03-05 07:46:49.554128302 +0100
> +++ gcc/testsuite/g++.dg/cpp0x/constexpr-delete2.C	2019-09-27 20:26:06.317971681 +0200
> @@ -5,8 +5,9 @@ struct A { ~A(); };
>   constexpr int f(int i) { return i; }
>   constexpr int g(A* ap)
>   {
> -  return f((delete[] ap, 42)); // { dg-message "" }
> +  return f((delete[] ap, 42)); // { dg-message "" "" { target c++17_down } }
>   }
>   
>   A a;
>   constexpr int i = g(&a);	// { dg-error "" }
> +				// { dg-message "in 'constexpr' expansion of" "" { target c++2a } .-1 }
> --- gcc/testsuite/g++.dg/cpp0x/locations1.C.jj	2019-09-26 21:34:22.205908750 +0200
> +++ gcc/testsuite/g++.dg/cpp0x/locations1.C	2019-09-27 18:25:41.346059343 +0200
> @@ -11,7 +11,7 @@ struct S
>   {
>     virtual S();  // { dg-error "3:constructors cannot be declared .virtual." }
>     constexpr int s = 1;  // { dg-error "3:non-static data member .s. declared .constexpr." }
> -  constexpr ~S();  // { dg-error "3:a destructor cannot be .constexpr." }
> +  constexpr ~S();  // { dg-error "3:'constexpr' destructors only available with" "" { target c++17_down } }
>   };
>   
>   typedef constexpr int my_int;  // { dg-error "9:.constexpr. cannot appear in a typedef declaration" }
> --- gcc/testsuite/g++.dg/cpp1y/constexpr-new.C.jj	2019-09-26 21:34:22.281907610 +0200
> +++ gcc/testsuite/g++.dg/cpp1y/constexpr-new.C	2019-09-27 18:25:41.303059984 +0200
> @@ -4,7 +4,7 @@ constexpr int *f4(bool b) {
>     if (b) {
>       return nullptr;
>     } else {
> -    return new int{42}; // { dg-error "call to non-.constexpr." }
> +    return new int{42}; // { dg-error "call to non-.constexpr." "" { target c++17_down } }
>     }
>   }
>   static_assert(f4(true) == nullptr, "");
> --- gcc/testsuite/g++.dg/cpp2a/constexpr-dtor1.C.jj	2019-09-27 18:25:41.320059731 +0200
> +++ gcc/testsuite/g++.dg/cpp2a/constexpr-dtor1.C	2019-09-27 18:25:41.320059731 +0200
> @@ -0,0 +1,9 @@
> +// P0784R7
> +// { dg-do compile { target c++11 } }
> +
> +struct S
> +{
> +  constexpr S () : s (0) {}
> +  constexpr ~S () {}	// { dg-error "'constexpr' destructors only available with" "" { target c++17_down } }
> +  int s;
> +};
> --- gcc/testsuite/g++.dg/cpp2a/constexpr-dtor2.C.jj	2019-09-27 18:25:41.303059984 +0200
> +++ gcc/testsuite/g++.dg/cpp2a/constexpr-dtor2.C	2019-09-27 18:25:41.303059984 +0200
> @@ -0,0 +1,66 @@
> +// P0784R7
> +// { dg-do compile { target c++2a } }
> +
> +struct S
> +{
> +  constexpr S () : r (4), s (3) { --r; s -= 2; }
> +  constexpr ~S () { if (s == 1) s = 0; else asm (""); if (s == 0 && r == 3) r = 0; else asm (""); }
> +  int r, s;
> +};
> +struct T : public S
> +{
> +  constexpr T () : t (2) {}
> +  int t;
> +  S u;
> +};
> +struct U : public S
> +{
> +  constexpr U (int x) : u (x) {}
> +  constexpr ~U () = default;
> +  int u;
> +  S v;
> +};
> +
> +constexpr S a;
> +constexpr T b;
> +constexpr U c = 3;
> +static_assert (a.s == 1 && a.r == 3);
> +static_assert (b.s == 1 && b.r == 3 && b.t == 2 && b.u.s == 1 && b.u.r == 3);
> +static_assert (c.s == 1 && c.r == 3 && c.u == 3 && c.v.s == 1 && c.v.r == 3);
> +
> +void
> +foo ()
> +{
> +  static constexpr S d;
> +  static constexpr T e;
> +  static constexpr U f = 4;
> +  static_assert (d.s == 1 && d.r == 3);
> +  static_assert (e.s == 1 && e.r == 3 && e.t == 2 && e.u.s == 1 && e.u.r == 3);
> +  static_assert (f.s == 1 && f.r == 3 && f.u == 4 && f.v.s == 1 && f.v.r == 3);
> +  if (1)
> +    {
> +      constexpr S g;
> +      constexpr T h;
> +      constexpr U i = 5;
> +      static_assert (g.s == 1 && g.r == 3);
> +      static_assert (h.s == 1 && h.r == 3 && h.t == 2 && h.u.s == 1 && h.u.r == 3);
> +      static_assert (i.s == 1 && i.r == 3 && i.u == 5 && i.v.s == 1 && i.v.r == 3);
> +    }
> +}
> +
> +constexpr bool
> +bar ()
> +{
> +  S j;
> +  T k;
> +  U l = 6;
> +  if (j.s != 1 || j.r != 3)
> +    return false;
> +  if (k.s != 1 || k.r != 3 || k.t != 2 || k.u.s != 1 || k.u.r != 3)
> +    return false;
> +  if (l.s != 1 || l.r != 3 || l.u != 6 || l.v.s != 1 || l.v.r != 3)
> +    return false;
> +  return true;
> +}
> +
> +static_assert (bar ());
> --- gcc/testsuite/g++.dg/cpp2a/constexpr-dtor3.C.jj	2019-09-27 18:25:41.303059984 +0200
> +++ gcc/testsuite/g++.dg/cpp2a/constexpr-dtor3.C	2019-09-27 18:25:41.303059984 +0200
> @@ -0,0 +1,185 @@
> +// P0784R7
> +// { dg-do compile { target c++2a } }
> +
> +struct S
> +{
> +  constexpr S () : s (0) {}
> +  constexpr ~S () {}
> +  int s;
> +};
> +struct T	// { dg-message "'T' is not literal because" }
> +{		// { dg-message "'T' does not have 'constexpr' destructor" "" { target *-*-* } .-1 }
> +  constexpr T () : t (0) {}
> +  ~T () {}	// { dg-message "destructor calls non-'constexpr' 'T::~T\\(\\)'" }
> +  int t;
> +};
> +struct U : public S
> +{
> +  constexpr U () : u (0) {}
> +  constexpr ~U () = default;	// { dg-error "explicitly defaulted function 'constexpr U::~U\\(\\)' cannot be declared 'constexpr' because the implicit declaration is not 'constexpr'" }
> +  int u;
> +  T t;
> +};
> +struct V : virtual public S
> +{
> +  V () : v (0) {}
> +  constexpr ~V () = default;	// { dg-error "explicitly defaulted function 'constexpr V::~V\\(\\)' cannot be declared 'constexpr' because the implicit declaration is not 'constexpr'" }
> +  int v;
> +};
> +struct W0
> +{
> +  constexpr W0 () : w (0) {}
> +  constexpr W0 (int x) : w (x) {}
> +  constexpr ~W0 () { if (w == 5) asm (""); w = 3; }
> +  int w;
> +};
> +struct W1
> +{
> +  constexpr W1 () : w (0) {}
> +  constexpr W1 (int x) : w (x) {}
> +  constexpr ~W1 () { if (w == 5) asm (""); w = 3; }	// { dg-error "inline assembly is not a constant expression" }
> +							// { dg-message "only unevaluated inline assembly is allowed in a 'constexpr' function" "" { target *-*-* } .-1 }
> +  int w;
> +};
> +struct W2
> +{
> +  constexpr W2 () : w (0) {}
> +  constexpr W2 (int x) : w (x) {}
> +  constexpr ~W2 () { if (w == 5) asm (""); w = 3; }	// { dg-error "inline assembly is not a constant expression" }
> +							// { dg-message "only unevaluated inline assembly is allowed in a 'constexpr' function" "" { target *-*-* } .-1 }
> +  int w;
> +};
> +struct W3
> +{
> +  constexpr W3 () : w (0) {}
> +  constexpr W3 (int x) : w (x) {}
> +  constexpr ~W3 () { if (w == 5) asm (""); w = 3; }	// { dg-error "inline assembly is not a constant expression" }
> +							// { dg-message "only unevaluated inline assembly is allowed in a 'constexpr' function" "" { target *-*-* } .-1 }
> +  int w;
> +};
> +struct W4
> +{
> +  constexpr W4 () : w (0) {}
> +  constexpr W4 (int x) : w (x) {}
> +  constexpr ~W4 () { if (w == 5) asm (""); w = 3; }	// { dg-error "inline assembly is not a constant expression" }
> +							// { dg-message "only unevaluated inline assembly is allowed in a 'constexpr' function" "" { target *-*-* } .-1 }
> +  int w;
> +};
> +struct W5
> +{
> +  constexpr W5 () : w (0) {}
> +  constexpr W5 (int x) : w (x) {}
> +  constexpr ~W5 () { if (w == 5) asm (""); w = 3; }	// { dg-error "inline assembly is not a constant expression" }
> +							// { dg-message "only unevaluated inline assembly is allowed in a 'constexpr' function" "" { target *-*-* } .-1 }
> +  int w;
> +};
> +struct W6
> +{
> +  constexpr W6 () : w (0) {}
> +  constexpr W6 (int x) : w (x) {}
> +  constexpr ~W6 () { if (w == 5) asm (""); w = 3; }	// { dg-error "inline assembly is not a constant expression" }
> +							// { dg-message "only unevaluated inline assembly is allowed in a 'constexpr' function" "" { target *-*-* } .-1 }
> +  int w;
> +};
> +struct W7
> +{
> +  constexpr W7 () : w (0) {}
> +  constexpr W7 (int x) : w (x) {}
> +  constexpr ~W7 () { if (w == 5) asm (""); w = 3; }	// { dg-error "inline assembly is not a constant expression" }
> +							// { dg-message "only unevaluated inline assembly is allowed in a 'constexpr' function" "" { target *-*-* } .-1 }
> +  int w;
> +};
> +struct W8
> +{
> +  constexpr W8 () : w (0) {}
> +  constexpr W8 (int x) : w (x) {}
> +  constexpr ~W8 () { if (w == 5) asm (""); w = 3; }	// { dg-error "inline assembly is not a constant expression" }
> +							// { dg-message "only unevaluated inline assembly is allowed in a 'constexpr' function" "" { target *-*-* } .-1 }
> +  int w;
> +};
> +struct X : public T
> +{
> +  constexpr X () : x (0) {}
> +  constexpr ~X () = default;	// { dg-error "explicitly defaulted function 'constexpr X::~X\\(\\)' cannot be declared 'constexpr' because the implicit declaration is not 'constexpr'" }
> +  int x;
> +};
> +constexpr S s;
> +constexpr T t;	// { dg-error "the type 'const T' of 'constexpr' variable 't' is not literal" }
> +constexpr W0 w1;
> +constexpr W0 w2 = 12;
> +constexpr W1 w3 = 5;	// { dg-message "in 'constexpr' expansion of" }
> +constexpr W0 w4[3] = { 1, 2, 3 };
> +constexpr W2 w5[3] = { 4, 5, 6 };	// { dg-message "in 'constexpr' expansion of" }
> +
> +void
> +f1 ()
> +{
> +  constexpr S s2;
> +  constexpr W0 w6;
> +  constexpr W0 w7 = 12;
> +  constexpr W3 w8 = 5;	// { dg-message "in 'constexpr' expansion of" }
> +  constexpr W0 w9[3] = { 1, 2, 3 };
> +  constexpr W4 w10[3] = { 4, 5, 6 };	// { dg-message "in 'constexpr' expansion of" }
> +}
> +
> +constexpr int
> +f2 ()
> +{
> +  constexpr S s3;
> +  constexpr W0 w11;
> +  constexpr W0 w12 = 12;
> +  constexpr W5 w13 = 5;	// { dg-message "in 'constexpr' expansion of" }
> +  constexpr W0 w14[3] = { 1, 2, 3 };
> +  constexpr W6 w15[3] = { 4, 5, 6 };	// { dg-message "in 'constexpr' expansion of" }
> +  return 0;
> +}
> +
> +constexpr int
> +f3 ()
> +{
> +  S s3;
> +  W0 w11;
> +  W0 w12 = 12;
> +  W0 w14[3] = { 1, 2, 3 };
> +  return 0;
> +}
> +
> +constexpr int x3 = f3 ();
> +
> +constexpr int
> +f4 ()
> +{
> +  W7 w13 = 5;
> +  return 0;
> +}
> +
> +constexpr int x4 = f4 ();	// { dg-message "in 'constexpr' expansion of" }
> +
> +constexpr int
> +f5 ()
> +{
> +  W8 w15[3] = { 4, 5, 6 };	// { dg-message "in 'constexpr' expansion of" }
> +  return 0;
> +}
> +
> +constexpr int x5 = f5 ();	// { dg-message "in 'constexpr' expansion of" }
> +
> +void
> +f6 ()
> +{
> +  constexpr T t2;	// { dg-error "the type 'const T' of 'constexpr' variable 't2' is not literal" }
> +}
> +
> +constexpr int
> +f7 ()
> +{
> +  constexpr T t3;	// { dg-error "the type 'const T' of 'constexpr' variable 't3' is not literal" }
> +  return 0;
> +}
> +
> +constexpr int
> +f8 ()
> +{
> +  T t4;			// { dg-error "variable 't4' of non-literal type 'T' in 'constexpr' function" }
> +  return 0;
> +}
> --- gcc/testsuite/g++.dg/cpp2a/constexpr-new1.C.jj	2019-09-27 18:25:41.320059731 +0200
> +++ gcc/testsuite/g++.dg/cpp2a/constexpr-new1.C	2019-09-27 20:06:50.047298452 +0200
> @@ -0,0 +1,39 @@
> +// P0784R7
> +// { dg-do compile { target c++2a } }
> +
> +struct S { constexpr S () : s (5) {} constexpr S (int x) : s (x) {} int s; };
> +
> +constexpr bool
> +foo ()
> +{
> +  int r = 0;
> +  S *p = new S ();
> +  p->s += 3;
> +  r += p->s;
> +  delete p;
> +  p = new S (12);
> +  p->s = p->s * 2;
> +  r += p->s;
> +  delete p;
> +  int *q = new int;
> +  *q = 25;
> +  r += *q;
> +  delete q;
> +  q = new int (1);
> +  r += *q;
> +  if (!q)
> +    return false;
> +  delete q;
> +  q = new int[5]{1,2,3,4,5};
> +  r += q[0] + q[4];
> +  delete[] q;
> +  q = new int[4];
> +  q[0] = 6;
> +  q[1] = 7;
> +  q[3] = 8;
> +  r += q[0] + q[1] + q[3];
> +  delete[] q;
> +  return r == 5 + 3 + 2 * 12 + 25 + 1 + 1 + 5 + 6 + 7 + 8;
> +}
> +constexpr bool a = foo ();
> +static_assert (a);
> --- gcc/testsuite/g++.dg/cpp2a/constexpr-new2.C.jj	2019-09-27 18:25:41.320059731 +0200
> +++ gcc/testsuite/g++.dg/cpp2a/constexpr-new2.C	2019-09-27 20:23:16.107524585 +0200
> @@ -0,0 +1,21 @@
> +// P0784R7
> +// { dg-do compile { target c++2a } }
> +
> +template <int N>
> +constexpr bool
> +foo (const char (&x)[N])
> +{
> +  int **p = new int *[N];
> +  for (int i = 0; i < N; i++)
> +    p[i] = new int (x[i]);
> +  for (int i = 0; i < N; i++)
> +    if (*p[i] != x[i])
> +      return false;
> +  for (int i = 0; i < N; ++i)
> +    delete p[i];
> +  delete[] p;
> +  return true;
> +}
> +
> +constexpr bool a = foo ("foobar");
> +static_assert (a);
> --- gcc/testsuite/g++.dg/cpp2a/constexpr-new3.C.jj	2019-09-27 18:25:41.320059731 +0200
> +++ gcc/testsuite/g++.dg/cpp2a/constexpr-new3.C	2019-09-27 18:25:41.320059731 +0200
> @@ -0,0 +1,63 @@
> +// P0784R7
> +// { dg-do compile { target c++2a } }
> +
> +constexpr int *
> +f1 ()
> +{
> +  return new int (2);		// { dg-error "is not a constant expression because it refers to a result of 'operator new'" }
> +}
> +
> +constexpr auto v1 = f1 ();
> +
> +constexpr bool
> +f2 ()
> +{
> +  int *p = new int (3);		// { dg-error "is not a constant expression because allocated storage has not been deallocated" }
> +  return false;
> +}
> +
> +constexpr auto v2 = f2 ();
> +
> +constexpr bool
> +f3 ()
> +{
> +  int *p = new int (3);
> +  int *q = p;
> +  delete p;
> +  delete q;			// { dg-error "deallocation of already deallocated storage" }
> +  return false;
> +}
> +
> +constexpr auto v3 = f3 ();	// { dg-message "in 'constexpr' expansion of" }
> +
> +constexpr bool
> +f4 (int *p)
> +{
> +  delete p;			// { dg-error "call to non-'constexpr' function" }
> +  return false;
> +}
> +
> +int q;
> +constexpr auto v4 = f4 (&q);	// { dg-message "in 'constexpr' expansion of" }
> +
> +constexpr bool
> +f5 ()
> +{
> +  int *p = new int;		// { dg-message "allocated here" }
> +  return *p == 1;
> +}
> +
> +constexpr auto v5 = f5 ();	// { dg-error "the content of uninitialized storage is not usable in a constant expression" }
> +				// { dg-message "in 'constexpr' expansion of" "" { target *-*-* } .-1 }
> +
> +constexpr bool
> +f6 ()
> +{
> +  int *p = new int (2);		// { dg-message "allocated here" }
> +  int *q = p;
> +  delete p;
> +  return *q == 2;
> +}
> +
> +constexpr auto v6 = f6 ();	// { dg-error "use of allocated storage after deallocation in a constant expression" }
> +				// { dg-message "in 'constexpr' expansion of" "" { target *-*-* } .-1  }
> --- gcc/testsuite/g++.dg/cpp2a/constexpr-new4.C.jj	2019-09-27 18:25:41.319059745 +0200
> +++ gcc/testsuite/g++.dg/cpp2a/constexpr-new4.C	2019-09-27 20:07:33.928641928 +0200
> @@ -0,0 +1,29 @@
> +// P0784R7
> +// { dg-do compile { target c++2a } }
> +
> +struct S
> +{
> +  constexpr S () : s (0) { s++; }
> +  constexpr S (int x) : s (x) { s += 2; }
> +  constexpr ~S () { if (s != 35) asm (""); s = 5; }
> +  int s;
> +};
> +
> +constexpr bool
> +foo ()
> +{
> +  S *p = new S (7);
> +  if (p->s != 9) return false;
> +  p->s = 35;
> +  delete p;
> +  p = new S[3] { 11, 13, 15 };
> +  if (p[0].s != 13 || p[1].s != 15 || p[2].s != 17) return false;
> +  p[0].s = 35;
> +  p[2].s = 35;
> +  p[1].s = 35;
> +  delete[] p;
> +  return true;
> +}
> +
> +constexpr bool a = foo ();
> +static_assert (a);
> --- gcc/testsuite/g++.dg/cpp2a/feat-cxx2a.C.jj	2019-09-26 21:34:22.341906710 +0200
> +++ gcc/testsuite/g++.dg/cpp2a/feat-cxx2a.C	2019-09-27 18:25:41.319059745 +0200
> @@ -430,16 +430,34 @@
>   
>   // C++20 features
>   
> -#if __cpp_conditional_explicit != 201806
> -# error "__cpp_conditional_explicit != 201806"
> +#ifndef __cpp_conditional_explicit
> +#  error "__cpp_conditional_explicit"
> +#elif __cpp_conditional_explicit != 201806
> +#  error "__cpp_conditional_explicit != 201806"
>   #endif
>   
> -#if __cpp_nontype_template_parameter_class != 201806
> -# error "__cpp_nontype_template_parameter_class != 201806"
> +#ifndef __cpp_nontype_template_parameter_class
> +#  error "__cpp_nontype_template_parameter_class"
> +#elif __cpp_nontype_template_parameter_class != 201806
> +#  error "__cpp_nontype_template_parameter_class != 201806"
>   #endif
>   
> -#if __cpp_impl_destroying_delete != 201806
> -# error "__cpp_impl_destroying_delete != 201806"
> +#ifndef __cpp_impl_destroying_delete
> +#  error "__cpp_impl_destroying_delete"
> +#elif __cpp_impl_destroying_delete != 201806
> +#  error "__cpp_impl_destroying_delete != 201806"
> +#endif
> +
> +#ifndef __cpp_constinit
> +#  error "__cpp_constinit"
> +#elif __cpp_constinit != 201907
> +#  error "__cpp_constinit != 201907"
> +#endif
> +
> +#ifndef __cpp_constexpr_dynamic_alloc
> +#  error "__cpp_constexpr_dynamic_alloc"
> +#elif __cpp_constexpr_dynamic_alloc != 201907
> +#  error "__cpp_constexpr_dynamic_alloc != 201907"
>   #endif
>   
>   #ifdef __has_cpp_attribute
> @@ -484,8 +502,6 @@
>   #  error "__has_cpp_attribute"
>   #endif
>   
> -// C++2A features:
> -
>   #ifndef __cpp_char8_t
>   #  error "__cpp_char8_t"
>   #elif __cpp_char8_t != 201811
> --- gcc/testsuite/g++.dg/ext/is_literal_type3.C.jj	2019-09-27 18:25:40.904065942 +0200
> +++ gcc/testsuite/g++.dg/ext/is_literal_type3.C	2019-09-27 18:25:40.904065942 +0200
> @@ -0,0 +1,26 @@
> +// { dg-do compile { target c++11 } }
> +
> +struct S {
> +  constexpr S () : n{} { }
> +  ~S () { n = 1; }
> +  int n;
> +};
> +
> +static_assert(!__is_literal_type(S), "");
> +
> +#ifdef __cpp_constexpr_dynamic_alloc
> +struct T {
> +  constexpr T () : n{} { }
> +  constexpr ~T () { n = 1; }
> +  int n;
> +};
> +
> +static_assert(__is_literal_type(T), "");
> +
> +struct U : public T {
> +  constexpr U () : u{} { }
> +  int u;
> +};
> +
> +static_assert(__is_literal_type(U), "");
> +#endif
> 
> 	Jakub
> 

  reply	other threads:[~2019-10-01 21:56 UTC|newest]

Thread overview: 13+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2019-09-27 20:31 Jakub Jelinek
2019-10-01 21:56 ` Jason Merrill [this message]
2019-10-02 11:36   ` Jakub Jelinek
2019-10-02 15:22     ` Jason Merrill
2019-10-03 18:25   ` [C++ PATCH] Improve cxx_fold_indirect_ref (PR c++/71504) Jakub Jelinek
2019-10-03 19:57     ` Jason Merrill
2019-10-03 20:57       ` [C++ PATCH] Improve cxx_fold_indirect_ref (PR c++/71504, take 2) Jakub Jelinek
2019-10-04  4:44         ` Jason Merrill
2019-10-03 18:38   ` [C++ PATCH] PR c++/91369 - Implement P0784R7: constexpr new Jakub Jelinek
2019-10-03 20:07     ` Jason Merrill
2019-10-04 17:50       ` Jakub Jelinek
2019-10-04 19:34         ` Jason Merrill
2019-10-05  7:39           ` Jakub Jelinek

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=688ebe68-e83f-9559-a1c7-883758b2bd74@redhat.com \
    --to=jason@redhat.com \
    --cc=gcc-patches@gcc.gnu.org \
    --cc=jakub@redhat.com \
    --cc=jwakely@redhat.com \
    --cc=polacek@redhat.com \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for read-only IMAP folder(s) and NNTP newsgroup(s).