c++: implement P2564, consteval needs to propagate up [PR107687]

Message ID 20230823194904.1925591-1-polacek@redhat.com
State Unresolved
Headers
Series c++: implement P2564, consteval needs to propagate up [PR107687] |

Checks

Context Check Description
snail/gcc-patch-check warning Git am fail log

Commit Message

Marek Polacek Aug. 23, 2023, 7:49 p.m. UTC
  Bootstrapped/regtested on x86_64-pc-linux-gnu, ok for trunk?

-- >8 --

This patch implements P2564, described at <wg21.link/p2564>, whereby
certain functions are promoted to consteval.  For example:

  consteval int id(int i) { return i; }

  template <typename T>
  constexpr int f(T t)
  {
    return t + id(t); // id causes f<int> to be promoted to consteval
  }

  void g(int i)
  {
    f (3);
  }

now compiles.  Previously the code was ill-formed: we would complain
that 't' in 'f' is not a constant expression.  Since 'f' is now
consteval, it means that the call to id(t) is in an immediate context,
so doesn't have to produce a constant -- this is how we allow consteval
functions composition.  But making 'f<int>' consteval also means that
the call to 'f' in 'g' must yield a constant; failure to do so results
in an error.  I made the effort to have cc1plus explain to us what's
going on.  For example, calling f(i) produces this neat diagnostic:

q.C: In function 'void g(int)':
q.C:11:5: error: call to consteval function 'f<int>(i)' is not a constant expression
   11 |   f (i);
      |   ~~^~~
q.C:6:16: note: 'constexpr int f(T) [with T = int]' was promoted to an immediate function because its body contains an immediate-escalating expression 'id(t)'
    6 |   return t + id(t);
      |              ~~^~~

which hopefully makes it clear what's going on.

Implementing this proposal has been tricky.  One problem was delayed
instantiation: instantiating a function can set off a domino effect
where one call promotes a function to consteval but that then means
that another function should also be promoted, etc.  I previously
thought that I could get past that by implementing the propagation in
cp_gimplify_expr at which point we have already instantiated everything
via instantiate_pending_templates.  But I realized that we don't
gimplify e.g.

  static auto p = &id;

and so we'd never detect taking the address of a consteval function.
Therefore this patch instantiates immediate-escalating functions
beforehand.  And as usual, there were a lot of other things to
handle.  It's not just calls to consteval functions that we must
detect, we also have to look for id-expressions that denote an
immediate function.

I discovered two crashes:
<https://gcc.gnu.org/PR110997>, ICE-on-valid with NSDMI
<https://gcc.gnu.org/PR111106>, missing ; causes an ICE
which this patch doesn't address, but adds a dg-ice test for the former.

I left one FIXME in the patch because I'm unclear on how to properly fix
the modules problem.

	PR c++/107687

gcc/c-family/ChangeLog:

	* c-cppbuiltin.cc (c_cpp_builtins): Update __cpp_consteval.

gcc/cp/ChangeLog:

	* call.cc (immediate_invocation_p): No longer static.
	(immediate_escalating_function_p): New.
	(maybe_promote_function_to_consteval): New.
	(build_over_call): Set ADDR_EXPR_DENOTES_CALL_P.  Maybe promote
	current_function_decl to consteval.
	* constexpr.cc (instantiate_cx_fn_r): No longer static.
	* cp-gimplify.cc (struct find_escalating_expr_t): New.
	(find_escalating_expr_r): New.
	(maybe_explain_promoted_consteval): New.
	(maybe_escalate_decl_and_cfun): New.
	(cp_fold_r) <case PTRMEM_CST>: Handle promoting functions to consteval.
	<case CALL_EXPR>: New case, handle promoting functions to consteval.
	<case ADDR_EXPR>: Handle promoting functions to consteval.
	* cp-tree.h (ADDR_EXPR_DENOTES_CALL_P): Define.
	(immediate_invocation_p): Declare.
	(immediate_escalating_function_p): Declare.
	(maybe_promote_function_to_consteval): Declare.
	(instantiate_constexpr_fns): Declare.
	* typeck.cc (cp_build_addr_expr_1): SET_EXPR_LOCATION on constexpr
	functions as well.

libstdc++-v3/ChangeLog:

	* testsuite/20_util/integer_comparisons/greater_equal_neg.cc: Adjust
	expected diagnostic.
	* testsuite/20_util/integer_comparisons/greater_neg.cc: Likewise.
	* testsuite/20_util/integer_comparisons/less_equal_neg.cc: Likewise.
	* testsuite/20_util/optional/monadic/or_else_neg.cc: Likewise.
	* testsuite/23_containers/array/creation/3_neg.cc: Likewise.
	* testsuite/23_containers/span/first_neg.cc: Likewise.
	* testsuite/23_containers/span/last_neg.cc: Likewise.
	* testsuite/23_containers/span/subspan_neg.cc: Likewise.

gcc/testsuite/ChangeLog:

	* g++.dg/cpp0x/constexpr-inst1.C: Add dg-error.
	* g++.dg/cpp23/consteval-if10.C: Remove dg-error.
	* g++.dg/cpp23/consteval-if2.C: Likewise.
	* g++.dg/cpp23/feat-cxx2b.C: Adjust expected value of __cpp_consteval.
	* g++.dg/cpp26/feat-cxx26.C: Likewise.
	* g++.dg/cpp2a/feat-cxx2a.C: Likewise.
	* g++.dg/cpp2a/consteval-prop1.C: New test.
	* g++.dg/cpp2a/consteval-prop10.C: New test.
	* g++.dg/cpp2a/consteval-prop11.C: New test.
	* g++.dg/cpp2a/consteval-prop12.C: New test.
	* g++.dg/cpp2a/consteval-prop13.C: New test.
	* g++.dg/cpp2a/consteval-prop14.C: New test.
	* g++.dg/cpp2a/consteval-prop15.C: New test.
	* g++.dg/cpp2a/consteval-prop16.C: New test.
	* g++.dg/cpp2a/consteval-prop17.C: New test.
	* g++.dg/cpp2a/consteval-prop2.C: New test.
	* g++.dg/cpp2a/consteval-prop3.C: New test.
	* g++.dg/cpp2a/consteval-prop4.C: New test.
	* g++.dg/cpp2a/consteval-prop5.C: New test.
	* g++.dg/cpp2a/consteval-prop6.C: New test.
	* g++.dg/cpp2a/consteval-prop7.C: New test.
	* g++.dg/cpp2a/consteval-prop8.C: New test.
	* g++.dg/cpp2a/consteval-prop9.C: New test.
---
 gcc/c-family/c-cppbuiltin.cc                  |   2 +-
 gcc/cp/call.cc                                |  74 +++++-
 gcc/cp/constexpr.cc                           |   2 +-
 gcc/cp/cp-gimplify.cc                         | 223 ++++++++++++++++--
 gcc/cp/cp-tree.h                              |   9 +
 gcc/cp/typeck.cc                              |   4 +-
 gcc/testsuite/g++.dg/cpp0x/constexpr-inst1.C  |   5 +-
 gcc/testsuite/g++.dg/cpp23/consteval-if10.C   |   7 +-
 gcc/testsuite/g++.dg/cpp23/consteval-if2.C    |  10 +-
 gcc/testsuite/g++.dg/cpp23/feat-cxx2b.C       |   4 +-
 gcc/testsuite/g++.dg/cpp26/feat-cxx26.C       |   4 +-
 gcc/testsuite/g++.dg/cpp2a/consteval-prop1.C  | 149 ++++++++++++
 gcc/testsuite/g++.dg/cpp2a/consteval-prop10.C |   9 +
 gcc/testsuite/g++.dg/cpp2a/consteval-prop11.C |  48 ++++
 gcc/testsuite/g++.dg/cpp2a/consteval-prop12.C |  30 +++
 gcc/testsuite/g++.dg/cpp2a/consteval-prop13.C |  23 ++
 gcc/testsuite/g++.dg/cpp2a/consteval-prop14.C |  72 ++++++
 gcc/testsuite/g++.dg/cpp2a/consteval-prop15.C |  81 +++++++
 gcc/testsuite/g++.dg/cpp2a/consteval-prop16.C |  73 ++++++
 gcc/testsuite/g++.dg/cpp2a/consteval-prop17.C |  32 +++
 gcc/testsuite/g++.dg/cpp2a/consteval-prop2.C  |  54 +++++
 gcc/testsuite/g++.dg/cpp2a/consteval-prop3.C  |  31 +++
 gcc/testsuite/g++.dg/cpp2a/consteval-prop4.C  |  32 +++
 gcc/testsuite/g++.dg/cpp2a/consteval-prop5.C  |  27 +++
 gcc/testsuite/g++.dg/cpp2a/consteval-prop6.C  |  58 +++++
 gcc/testsuite/g++.dg/cpp2a/consteval-prop7.C  |  76 ++++++
 gcc/testsuite/g++.dg/cpp2a/consteval-prop8.C  |  71 ++++++
 gcc/testsuite/g++.dg/cpp2a/consteval-prop9.C  |  67 ++++++
 gcc/testsuite/g++.dg/cpp2a/feat-cxx2a.C       |   4 +-
 .../integer_comparisons/greater_equal_neg.cc  |  24 +-
 .../integer_comparisons/greater_neg.cc        |  24 +-
 .../integer_comparisons/less_equal_neg.cc     |  24 +-
 .../20_util/optional/monadic/or_else_neg.cc   |  10 +-
 .../23_containers/array/creation/3_neg.cc     |  14 +-
 .../testsuite/23_containers/span/first_neg.cc |   4 +-
 .../testsuite/23_containers/span/last_neg.cc  |   4 +-
 .../23_containers/span/subspan_neg.cc         |  12 +-
 37 files changed, 1306 insertions(+), 91 deletions(-)
 create mode 100644 gcc/testsuite/g++.dg/cpp2a/consteval-prop1.C
 create mode 100644 gcc/testsuite/g++.dg/cpp2a/consteval-prop10.C
 create mode 100644 gcc/testsuite/g++.dg/cpp2a/consteval-prop11.C
 create mode 100644 gcc/testsuite/g++.dg/cpp2a/consteval-prop12.C
 create mode 100644 gcc/testsuite/g++.dg/cpp2a/consteval-prop13.C
 create mode 100644 gcc/testsuite/g++.dg/cpp2a/consteval-prop14.C
 create mode 100644 gcc/testsuite/g++.dg/cpp2a/consteval-prop15.C
 create mode 100644 gcc/testsuite/g++.dg/cpp2a/consteval-prop16.C
 create mode 100644 gcc/testsuite/g++.dg/cpp2a/consteval-prop17.C
 create mode 100644 gcc/testsuite/g++.dg/cpp2a/consteval-prop2.C
 create mode 100644 gcc/testsuite/g++.dg/cpp2a/consteval-prop3.C
 create mode 100644 gcc/testsuite/g++.dg/cpp2a/consteval-prop4.C
 create mode 100644 gcc/testsuite/g++.dg/cpp2a/consteval-prop5.C
 create mode 100644 gcc/testsuite/g++.dg/cpp2a/consteval-prop6.C
 create mode 100644 gcc/testsuite/g++.dg/cpp2a/consteval-prop7.C
 create mode 100644 gcc/testsuite/g++.dg/cpp2a/consteval-prop8.C
 create mode 100644 gcc/testsuite/g++.dg/cpp2a/consteval-prop9.C


base-commit: 0cfc9c953d0221ec3971a25e6509ebe1041f142e
  

Comments

Jason Merrill Aug. 29, 2023, 7:26 p.m. UTC | #1
On 8/23/23 15:49, Marek Polacek wrote:
> Bootstrapped/regtested on x86_64-pc-linux-gnu, ok for trunk?
> 
> -- >8 --
> 
> This patch implements P2564, described at <wg21.link/p2564>, whereby
> certain functions are promoted to consteval.  For example:

Great, thanks!

>    consteval int id(int i) { return i; }
> 
>    template <typename T>
>    constexpr int f(T t)
>    {
>      return t + id(t); // id causes f<int> to be promoted to consteval
>    }
> 
>    void g(int i)
>    {
>      f (3);
>    }
> 
> now compiles.  Previously the code was ill-formed: we would complain
> that 't' in 'f' is not a constant expression.  Since 'f' is now
> consteval, it means that the call to id(t) is in an immediate context,
> so doesn't have to produce a constant -- this is how we allow consteval
> functions composition.  But making 'f<int>' consteval also means that
> the call to 'f' in 'g' must yield a constant; failure to do so results
> in an error.  I made the effort to have cc1plus explain to us what's
> going on.  For example, calling f(i) produces this neat diagnostic:
> 
> q.C: In function 'void g(int)':
> q.C:11:5: error: call to consteval function 'f<int>(i)' is not a constant expression
>     11 |   f (i);
>        |   ~~^~~
> q.C:6:16: note: 'constexpr int f(T) [with T = int]' was promoted to an immediate function because its body contains an immediate-escalating expression 'id(t)'
>      6 |   return t + id(t);
>        |              ~~^~~
> 
> which hopefully makes it clear what's going on.
> 
> Implementing this proposal has been tricky.  One problem was delayed
> instantiation: instantiating a function can set off a domino effect
> where one call promotes a function to consteval but that then means
> that another function should also be promoted, etc.

What I expected was that we would instantiate when we see a call, i.e. 
immediate_invocation_p would instantiate its argument if it's an 
immediate-escalating function.  But...

> I previously thought that I could get past that by implementing the propagation in
> cp_gimplify_expr at which point we have already instantiated everything
> via instantiate_pending_templates.

...this sounds like a clever way to avoid the problems we tend to see 
with eager instantiation (e.g. constexpr-inst1.C).  It still seems 
promising to me.

> But I realized that we don't gimplify e.g.
> 
>    static auto p = &id;

Indeed, just cp_fully_fold_init them, at least until we build the global 
init function.

> and so we'd never detect taking the address of a consteval function.
> Therefore this patch instantiates immediate-escalating functions
> beforehand.

Relatedly, in one of the testcases you have

> +template <typename T>
> +constexpr int f2(T);
> +
> +// ??? clang++ emits
> +// error: immediate function 'f2<char>' used before it is defined
> +// error: immediate function 'f2<int>' used before it is defined
> +// but at this point we don't know that f2 will be updated to consteval?
> +auto a2 = &f2<char>;
> +auto b2 = &f2<int>;
> +
> +template <typename T>
> +constexpr int f2(T t) {
> +    return id(t);

This is a case where we can't immediately resolve the question anyway, 
we need to remember where we've seen bare references to 
immediate-escalating functions outside of other immediate-escalating (or 
already immediate) functions, and complain later if they became 
consteval when instantiated.

I don't see why this is a significant obstacle to doing escalation late.

> 	* cp-tree.h (ADDR_EXPR_DENOTES_CALL_P): Define.

Is this flag just for rejecting consteval-prop10.C?  I think we should 
not try to reject it.  This came up previously in the context of my 
patch for CWG2631, at which point I argued to CWG that it should be 
allowed, but nobody responded.  I'll ping that thread now, but in the 
mean time let's not go out of our way to reject this testcase that seems 
to me pretty clearly allowed by the current wording: that expression 
invokes an immediate function, so it's an immediate invocation.

> diff --git a/gcc/cp/call.cc b/gcc/cp/call.cc
> index 673ec91d60e..31f78b71fe1 100644
> --- a/gcc/cp/call.cc
> +++ b/gcc/cp/call.cc
> @@ -9735,7 +9735,9 @@ build_trivial_dtor_call (tree instance, bool no_ptr_deref)
>   
>   /* Return true if in an immediate function context, or an unevaluated operand,
>      or a default argument/member initializer, or a subexpression of an immediate
> -   invocation.  */
> +   invocation.
> +   ??? P2564 says that "a subexpression of a manifestly constant-evaluated
> +   expression or conversion" is also an immediate function context.  */

Yes, that was to allow the consteval-prop2.C

   static_assert(is_not(5, is_even));

Since the operand of static_assert is manifestly constant-evaluated, 
it's OK that we name consteval is_even there.

We currently use this function to trigger evaluating immediate 
evaluations, and later (through immediate_invocation_p) to trigger 
errors, and other constant-evaluation happens between those two, so in 
most cases it's OK that this function does not identify that situation.

But there's another example in the paper, that this patch doesn't handle:

consteval int g(int p) { return p; }
template<typename T> constexpr auto f(T) { return g; }
int r = f(1)(2);      // proposed ok
int s = f(1)(2) + r;  // error

Here f(1) looks like an immediate invocation but is not itself constant, 
and GCC with this patch tries to immediately evaluate f(1) and gives an 
error.  But since it's in a manifestly constant-evaluated expression, it 
isn't actually an immediate invocation, and giving an error is wrong.

Maybe we want to stop doing immediate evaluation in build_over_call (and 
bot_replace) at all, and instead build the call as normal and perhaps 
error later in cp_fold_r?  If I just disable the immediate_invocation_p 
blocks in build_over_call the testcase above works as the comments 
indicate.  Maybe removing the bot_replace code would also help with 110997?

> +/* Return true if FN is an immediate-escalating function.  */
> +
> +bool
> +immediate_escalating_function_p (tree fn)
> +{
> +  if (!fn)
> +    return false;
> +
> +  gcc_checking_assert (TREE_CODE (fn) == FUNCTION_DECL);

I think we might want to have a flag for this, like I did with 
-fnew-ttp-matching.  Say, -fimmediate-escalation?

> +	  /* [expr.const]p16 "An expression or conversion is
> +	     immediate-escalating if it is not initially in an immediate
> +	     function context and it is either
> +	      -- an immediate invocation that is not a constant expression and
> +	      is not a subexpression of an immediate invocation."
> +
> +	      If we are in an immediate-escalating function, the
> +	      immediate-escalating expression or conversion makes it an
> +	      immediate function.  So CALL does not need to produce a constant
> +	      expression.  ??? It's ugly to call cxx_constant_value twice in
> +	      some cases.  */
> +	  if (cxx_constant_value (call, obj_arg, tf_none) == error_mark_node
> +	      && maybe_promote_function_to_consteval (current_function_decl))
> +	    return orig_call;
>   	  call = cxx_constant_value (call, obj_arg, complain);

As discussed above, I think promoting or complaining here is now wrong 
because we might be in a manifestly constant-evaluated expression.  At 
this point I think there's probably no point even trying to do 
evaluation here, we can just wait until cp_fold_r (or the later 
escalation time?).

> diff --git a/gcc/cp/cp-gimplify.cc b/gcc/cp/cp-gimplify.cc
> index 206e791fcfd..902c9d54741 100644
> --- a/gcc/cp/cp-gimplify.cc
> +++ b/gcc/cp/cp-gimplify.cc
> > +/* Figure out if DECL should be promoted to consteval and if so, 
maybe also
> +   promote the function we are in currently.  CALL is the CALL_EXPR of DECL,
> +   or NULL_TREE, if we're taking the address of a function.  This function
> +   will likely instantiate DECL.  */
> +
> +static void
> +maybe_escalate_decl_and_cfun (tree decl, tree call = NULL_TREE)
> +{
> +  if (cxx_dialect <= cxx17 || cp_unevaluated_operand)
> +    return;

Return early if cfun is not immediate-escalating?

> +  /* Compiler-generated functions don't seem like good candidates for
> +     promoting.  */
> +  if (DECL_ARTIFICIAL (decl) || DECL_ABSTRACT_P (decl))

Why DECL_ABSTRACT_P?  I think that will be set on pre-cloning constructors.

> +    return;
> +
> +  /* What we're calling is not a consteval function but it may become
> +     one.  This requires recursing; DECL may be promoted to consteval
> +     because it contains an escalating expression E, but E itself may
> +     have to be promoted first, etc.  */
> +  if (!DECL_IMMEDIATE_FUNCTION_P (decl)
> +      && immediate_escalating_function_p (decl))
> +    {
> +      /* PSET holds the set of functions we have already perused for
> +	 immediate-escalating expressions.  */
> +      static hash_set<tree> pset;

It will also hold pointers to all the function-internal expressions that 
we ever walk through, which seems like a problem.  IMO it would be 
better to have a persistent pset that just tracks the functions, in 
addition to the usual tree-walk pset that gets thrown away each time. 
That is, if we need to do this walk at all; see the next comment.

> +      find_escalating_expr_t data = { decl, &pset };
> +      /* We can't delay instantiating any further.  We need to see the
> +	 whole tree to decide whether DECL is consteval.
> +	 ??? Consider adding a sentinel to instantiate_constexpr_fns so
> +	 that we don't escalate while we instantiate while we escalate...
> +	 which seems dodgy.

It doesn't seem that dodgy to me.  But if we're doing escalation at 
finish_function time rather than trying to defer to EOF we shouldn't 
need to do this tree walk at all, only instantiation, and the 
instantiation will deal with its own escalation (or not).  And if we do 
end up deferring to EOF, we shouldn't need to do any instantiation 
because as you mentioned in the intro we will have already done 
instantiate_pending_templates.

> +	 FIXME Instantiating a defaulted ctor breaks modules (ICE due to
> +	 cp_function_chain being null).  */

Sounds like this needs more investigation.

Incidentally, defaulted functions aren't instantiated, they're 
synthesized.  Which instantiate_constexpr_fns deals with, though it 
seems a bit odd to do a "walk" over a single node, maybe we should 
factor that bit out?

> +  /* In turn, maybe promote the function we find ourselves in...  */
> +  if (DECL_IMMEDIATE_FUNCTION_P (decl)
> +      /* ...but not if the call to DECL was constant; that is the
> +	 "an immediate invocation that is not a constant expression"
> +	 case.  We do this here and not in find_escalating_expr_r,
> +	 because DECL could have already been consteval and we'd
> +	 never call f_e_e_r.  */

It seems unfortunate to call cxx_constant_value here and then again 
immediately in cp_fold_r in the case that call is indeed an immediate 
invocation.

> +      && (!call || cxx_constant_value (call, tf_none) == error_mark_node))
> +    maybe_promote_function_to_consteval (current_function_decl);
> +}
> +
> @@ -1046,27 +1204,64 @@ cp_fold_r (tree *stmt_p, int *walk_subtrees, void *data_)
>     switch (code)
>       {
>       case PTRMEM_CST:
> -      if (TREE_CODE (PTRMEM_CST_MEMBER (stmt)) == FUNCTION_DECL
> -	  && DECL_IMMEDIATE_FUNCTION_P (PTRMEM_CST_MEMBER (stmt)))
> +      if (TREE_CODE (PTRMEM_CST_MEMBER (stmt)) == FUNCTION_DECL)
>   	{
> -	  if (!data->pset.add (stmt))
> -	    error_at (PTRMEM_CST_LOCATION (stmt),
> -		      "taking address of an immediate function %qD",
> -		      PTRMEM_CST_MEMBER (stmt));
> -	  stmt = *stmt_p = build_zero_cst (TREE_TYPE (stmt));
> -	  break;
> +	  tree decl = PTRMEM_CST_MEMBER (stmt);
> +	  maybe_escalate_decl_and_cfun (decl);
> +	  /* Taking the address of a consteval function is not permitted.  */
> +	  if (immediate_invocation_p (decl))
> +	    {
> +	      if (!data->pset.add (stmt))
> +		{
> +		  error_at (PTRMEM_CST_LOCATION (stmt),
> +			    "taking address of an immediate function %qD",
> +			    decl);
> +		  maybe_explain_promoted_consteval (PTRMEM_CST_LOCATION (stmt),
> +						    decl);
> +		}
> +	      stmt = *stmt_p = build_zero_cst (TREE_TYPE (stmt));
> +	    }

Can we share most of this code between PTRMEM_CST and ADDR_EXPR?

> +    case CALL_EXPR:
> +      if (tree fn = CALL_EXPR_FN (stmt))
> +	if (TREE_CODE (fn) == ADDR_EXPR
> +	    && ADDR_EXPR_DENOTES_CALL_P (fn)
> +	    && TREE_CODE (TREE_OPERAND (fn, 0)) == FUNCTION_DECL)
> +	  {
> +	    tree decl = TREE_OPERAND (fn, 0);
> +	    maybe_escalate_decl_and_cfun (decl, stmt);
> +	    if (immediate_invocation_p (decl))
> +	      {
> +		tree e = cxx_constant_value (stmt, tf_none);
> +		if (e == error_mark_node)
> +		  {
> +		    const location_t loc = cp_expr_loc_or_input_loc (stmt);
> +		    error_at (loc, "call to consteval function %qE is not a "
> +			      "constant expression", stmt);
> +		    maybe_explain_promoted_consteval (loc, decl);

I think here we also want the usual cxx_constant_value errors explaining 
why the call is not constant.

> diff --git a/gcc/cp/cp-tree.h b/gcc/cp/cp-tree.h
> index eb901683b6d..36d76a98233 100644
> --- a/gcc/cp/cp-tree.h
> +++ b/gcc/cp/cp-tree.h
> @@ -4784,6 +4784,11 @@ get_vec_init_expr (tree t)
>   #define PTRMEM_OK_P(NODE) \
>     TREE_LANG_FLAG_0 (TREE_CHECK3 ((NODE), ADDR_EXPR, OFFSET_REF, SCOPE_REF))
>   
> +/* True if this ADDR_EXPR denotes a function call; that is, it's
> +   fn() rather than &fn.  */
> +#define ADDR_EXPR_DENOTES_CALL_P(NODE) \
> +  (ADDR_EXPR_CHECK(NODE)->base.protected_flag)

As mentioned above, I don't think we need this.

> diff --git a/gcc/cp/typeck.cc b/gcc/cp/typeck.cc
> index d5c0c85ed51..03b642fefae 100644
> --- a/gcc/cp/typeck.cc
> +++ b/gcc/cp/typeck.cc
> @@ -7248,7 +7248,9 @@ cp_build_addr_expr_1 (tree arg, bool strict_lvalue, tsubst_flags_t complain)
>        set for possible later diagnostics.  */
>     if (TREE_CODE (val) == ADDR_EXPR
>         && TREE_CODE (TREE_OPERAND (val, 0)) == FUNCTION_DECL
> -      && DECL_IMMEDIATE_FUNCTION_P (TREE_OPERAND (val, 0)))
> +      && (DECL_IMMEDIATE_FUNCTION_P (TREE_OPERAND (val, 0))
> +	  /* A constexpr function may be promoted to consteval.  */
> +	  || DECL_DECLARED_CONSTEXPR_P (TREE_OPERAND (val, 0))))

Hmm, why don't we just do this for all functions?

>       SET_EXPR_LOCATION (val, input_location);
>   
>     return val;
> diff --git a/gcc/testsuite/g++.dg/cpp0x/constexpr-inst1.C b/gcc/testsuite/g++.dg/cpp0x/constexpr-inst1.C
> index 3ce513d6e25..7b3e2db6c8a 100644
> --- a/gcc/testsuite/g++.dg/cpp0x/constexpr-inst1.C
> +++ b/gcc/testsuite/g++.dg/cpp0x/constexpr-inst1.C
> @@ -1,6 +1,9 @@
> +// This used to...
>   // Test that we don't uselessly instantiate f<A> immediately while parsing g.
>   // Doing so is permitted by the standard, but has no benefit and breaks code
>   // unnecessarily.
> +// ...but due to P2564 we actually do instantiate f, because we need to figure
> +// out if it should be promoted to consteval.  :-(

This is an example that would work better if we can wait until EOF to do 
escalation.

> diff --git a/gcc/testsuite/g++.dg/cpp2a/consteval-prop1.C b/gcc/testsuite/g++.dg/cpp2a/consteval-prop1.C
> new file mode 100644
> index 00000000000..2ff16b88aa1
> --- /dev/null
> +++ b/gcc/testsuite/g++.dg/cpp2a/consteval-prop1.C
> @@ -0,0 +1,149 @@
> +// P2564R3
> +// { dg-do compile { target c++20 } }
> +// Some of these were cribbed from clang's cxx2b-consteval-propagate.cpp.
> +
> +consteval int id(int i) { return i; }
> +
> +template<typename T>
> +constexpr int
> +f4 (T t)
> +{
> +  auto p = id;
> +  (void) p;
> +  return t;
> +}
> +
> +// ??? We promote f4 to consteval but clang++ doesn't seem to.
> +auto p6 = &f4<int>; // { dg-error "taking address of an immediate function" }

I imagine clang doesn't promote because it optimizes away the unused 
reference to id.  That seems like a clang bug.

> +static_assert (f4 (42) == 42);
> +
> +// Constructors.
> +consteval int zero (int)
> +{
> +  return 0;
> +}
> +
> +struct A {
> +  // A::A(auto) promoted to consteval.
> +  constexpr A(auto i) { zero (i); }
> +};
> +
> +constexpr void
> +f5 (auto i)
> +{
> +  A a{i};
> +}

Maybe also a non-template function that gets an error?

> +struct C {
> +  // C::C(int) promoted to consteval.
> +  consteval C (int) {};

Looks explicitly declared consteval, not promoted.  :)

> +struct Y {
> +  int y;
> +  int x = id (y);
> +  consteval Y (int i) : y (id (i)) {}
> +};
> +
> +Y y1(1);
> +Y y2(g); // { dg-error "not usable" }

How about a variant of this where Y(int) is promoted to consteval?

> +auto l1 = [](int i) constexpr {
> +  int t = id (i);
> +  return id (0);
> +};
> +
> +int (*p)(int) = l1; // { dg-error "returns address of immediate function" }

What if the lambda isn't explicitly constexpr?

> diff --git a/gcc/testsuite/g++.dg/cpp2a/consteval-prop10.C b/gcc/testsuite/g++.dg/cpp2a/consteval-prop10.C
> new file mode 100644
> index 00000000000..00af77042cb
> --- /dev/null
> +++ b/gcc/testsuite/g++.dg/cpp2a/consteval-prop10.C
> @@ -0,0 +1,9 @@
> +// P2564R3
> +// { dg-do compile { target c++20 } }
> +
> +consteval int foo ()  { return 42; }
> +// Even though the standard doesn't define "invocation", this is one.
> +// But the user actually wrote '&' so presumably we should error.  If
> +// we decide to accept this, move the ADDR_EXPR_DENOTES_CALL_P setting
> +// to build_cxx_call.
> +int bar () { return (*(&foo)) (); } // { dg-error "taking address" }

As above, I think we can/should accept this, so I don't see the purpose 
of ADDR_EXPR_DENOTES_CALL_P.

> diff --git a/gcc/testsuite/g++.dg/cpp2a/consteval-prop15.C b/gcc/testsuite/g++.dg/cpp2a/consteval-prop15.C
> new file mode 100644
> index 00000000000..f52ba07ec6c
> --- /dev/null
> +++ b/gcc/testsuite/g++.dg/cpp2a/consteval-prop15.C
> @@ -0,0 +1,81 @@
> +// P2564R3
> +// { dg-do compile { target c++20 } }
> +// { dg-options "-Wno-c++23-extensions" }
> +
> +consteval int id (int i) { return i; }
...
> +constexpr int
> +f6 (auto)
> +{
> +  // This call is a constant expression, so don't promote f6.
> +  return f4 (42);
> +}
> +
> +constexpr int
> +f7 (auto i)
> +{
> +  if consteval {
> +    auto p = &id;
> +    (void) p;

What if we return id(i) in the if consteval?

> +  }
> +  return i;
> +}
> +
> +constexpr int
> +f8 (auto i)
> +{
> +  if not consteval {
> +    (void) 0;
> +  } else {
> +    auto p = &id;
> +    (void) p;

And here.

> diff --git a/gcc/testsuite/g++.dg/cpp2a/consteval-prop17.C b/gcc/testsuite/g++.dg/cpp2a/consteval-prop17.C
> new file mode 100644
> index 00000000000..846e5603fee
> --- /dev/null
> +++ b/gcc/testsuite/g++.dg/cpp2a/consteval-prop17.C
> @@ -0,0 +1,32 @@
> +// P2564R3
> +// { dg-do compile { target c++20 } }
> +// Test default arguments.
> +
> +consteval int id (int i) { return i; }
> +
> +template<typename>
> +constexpr int
> +f1 (int i = id (42))
> +{
> +  return i;
> +}
> +
> +int non_const;
> +
> +template<typename>
> +constexpr int
> +f2 (int i = id (non_const))
> +{
> +  return i;
> +}
> +
> +void
> +g (int i)
> +{
> +  f1<int> (42);
> +  f1<int> (i);
> +  f1<int> ();
> +  f2<int> (42);
> +  f2<int> (i);
> +  f2<int> (); // { dg-error "not usable" }
> +}

Also test that a function f3(auto) that calls f2<int>() gets promoted?

> diff --git a/gcc/testsuite/g++.dg/cpp2a/consteval-prop2.C b/gcc/testsuite/g++.dg/cpp2a/consteval-prop2.C
> new file mode 100644
> index 00000000000..a5803736d37
> --- /dev/null
> +++ b/gcc/testsuite/g++.dg/cpp2a/consteval-prop2.C
> @@ -0,0 +1,54 @@
> +// P2564R3
> +// { dg-do compile { target c++20 } }
> +// Testcase from P2564R3.
> +
> +consteval int id(int i) { return i; }
> +constexpr char id(char c) { return c; }
> +
> +template<class T>
> +constexpr int f(T t) {
> +  return t + id(t);
> +}
> +
> +auto a = &f<char>;              // OK, f<char> is not an immediate function
> +auto b = &f<int>;               // { dg-error "taking address of an immediate function" }
> +
> +static_assert(f(3) == 6);       // OK
> +
> +template<class T>
> +constexpr int g(T t) {          // g<int> is not an immediate function
> +  return t + id(42);            // because id(42) is already a constant
> +}

I don't see any uses of g to test the comment?

> +template<class T, class F>
> +constexpr bool is_not(T t, F f) {
> +  return not f(t);
> +}
> +
> +consteval bool is_even(int i) { return i % 2 == 0; }
> +
> +static_assert(is_not(5, is_even));      // OK
> +
> +int x = 0;
> +
> +template<class T>
> +constexpr T h(T t = id(x)) {    // h<int> is not an immediate function
> +    return t;
> +}
> +
> +template<class T>
> +constexpr T hh() {              // hh<int> is an immediate function
> +  return h<T>();		// { dg-error "not usable in a constant expression" }

Why is there an error on this line?

> +}
> +
> +int i = hh<int>();              // error: hh<int>() is an immediate-escalating expression
> +                                // outside of an immediate-escalating function

...and not on this line?

> +struct A {
> +  int x;
> +  int y = id(x);
> +};
> +
> +template<class T>
> +constexpr int k(int) {          // k<int> is not an immediate function because A(42) is a
> +  return A(42).y;               // constant expression and thus not immediate-escalating
> +}

Needs use(s) of k to test the comment.

> diff --git a/gcc/testsuite/g++.dg/cpp2a/consteval-prop3.C b/gcc/testsuite/g++.dg/cpp2a/consteval-prop3.C
> new file mode 100644
> index 00000000000..35665304652
> --- /dev/null
> +++ b/gcc/testsuite/g++.dg/cpp2a/consteval-prop3.C
> @@ -0,0 +1,31 @@
> +// P2564R3
> +// { dg-do compile { target c++20 } }
> +// Cribbed from clang's cxx2b-consteval-propagate.cpp.
> +
> +consteval int id(int i) { return i; }
> +
> +template <typename T>
> +constexpr int f(T t);
> +
> +auto a1 = &f<char>;
> +auto b1 = &f<int>;
> +
> +template <typename T>
> +constexpr int f(T t) {
> +    return id(0);
> +}
> +
> +template <typename T>
> +constexpr int f2(T);
> +
> +// ??? clang++ emits
> +// error: immediate function 'f2<char>' used before it is defined
> +// error: immediate function 'f2<int>' used before it is defined
> +// but at this point we don't know that f2 will be updated to consteval?

Right, as mentioned above it seems that we need to keep track of forward 
references to immediate-escalating functions in case they become 
consteval.  If we're promoting early, only ones that aren't defined yet.

> +auto a2 = &f2<char>;
> +auto b2 = &f2<int>;
> +
> +template <typename T>
> +constexpr int f2(T t) {
> +    return id(t);
> +}
> diff --git a/gcc/testsuite/g++.dg/cpp2a/consteval-prop6.C b/gcc/testsuite/g++.dg/cpp2a/consteval-prop6.C
> new file mode 100644
> index 00000000000..8cc08c7f6d8
> --- /dev/null
> +++ b/gcc/testsuite/g++.dg/cpp2a/consteval-prop6.C
> @@ -0,0 +1,58 @@
> +// P2564R3
> +// { dg-do compile { target c++20 } }
> +// From cxx2b-consteval-propagate.cpp.
> +
> +void side_effect();
> +
> +consteval int
> +f (int x)
> +{
> +  if (!x)
> +    side_effect(); // { dg-error "call to non-.constexpr. function" }
> +  return x;
> +}
> +
> +struct SS {
> +  int y = f(1);
> +  int x = f(0);
> +  SS();
> +};
> +SS::SS(){}

Maybe a dg-message here, assuming the context for the above error 
mentions this line?

> +consteval int
> +f2 (int x)
> +{
> +  if (!__builtin_is_constant_evaluated ())
> +    side_effect();
> +  return x;
> +}
> +
> +struct S2 {
> +  int x = f2(0);
> +  constexpr S2();
> +};
> +
> +constexpr S2::S2(){}
> +S2 s = {};
> +constinit S2 s2 = {};
> +
> +struct S3 {
> +  int x = f2(0);
> +  S3();
> +};
> +S3::S3(){}
> +
> +consteval int undef (int x); // { dg-warning "never defined" }
> +
> +struct X {
> +  int a = sizeof(undef(0));
> +  int x = undef(0);

No diagnostic on this line?

> +
> +  X() = default; // { dg-error "used before its definition" }
> +};
> +
> +void
> +test ()
> +{
> +  [[maybe_unused]] X x;
> +}
> diff --git a/gcc/testsuite/g++.dg/cpp2a/consteval-prop8.C b/gcc/testsuite/g++.dg/cpp2a/consteval-prop8.C
> new file mode 100644
> index 00000000000..41c47992ef7
> --- /dev/null
> +++ b/gcc/testsuite/g++.dg/cpp2a/consteval-prop8.C
> @@ -0,0 +1,71 @@
> +// P2564R3
> +// { dg-do compile { target c++20 } }
> +// { dg-options "-Wno-c++23-extensions" }
> +
> +consteval int zero (int)
> +{
> +  return 0;
> +}
> +
> +struct A {
> +  // A::A(auto) promoted to consteval.
> +  constexpr A(auto i) { zero (i); }
> +};
> +
> +// 'f1<int>' is an immediate function because its body contains a call to an
> +// immediate constructor 'A<int>' and that call is not a constant expression
> +constexpr void
> +f1 (auto i)
> +{
> +  A a{i};
> +}
> +
> +// 'f2<int>' is an immediate function because its body contains a call to an
> +// immediate constructor 'A<int>' and that call is not a constant expression
> +constexpr void
> +f2 (auto i)
> +{
> +  A a{i};
> +}
> +
> +// ??? This doesn't give error when inline/constexpr and not called.

Is that still true with the current patch?  If so, it should be fixed. 
In any case, it should be tested.

> +void
> +f3 (int i)
> +{
> +  A a{i}; // { dg-error "not a constant expression" }
> +}
> diff --git a/libstdc++-v3/testsuite/20_util/optional/monadic/or_else_neg.cc b/libstdc++-v3/testsuite/20_util/optional/monadic/or_else_neg.cc
> index 16e94864f3b..329f3a6cc33 100644
> --- a/libstdc++-v3/testsuite/20_util/optional/monadic/or_else_neg.cc
> +++ b/libstdc++-v3/testsuite/20_util/optional/monadic/or_else_neg.cc
> @@ -8,11 +8,11 @@ test01()
>   {
>     std::optional<int> o;
>     o.or_else([&] { return o; }); // OK
> -  o.or_else([] { return std::optional<short>(); }); // { dg-error "here" }
> -  o.or_else([] { return 1; }); // { dg-error "here" }
> -  std::move(o).or_else([] { return std::optional<short>(); }); // { dg-error "here" }
> -  std::move(o).or_else([] { return 1; }); // { dg-error "here" }
> -}
> +  o.or_else([] { return std::optional<short>(); });
> +  o.or_else([] { return 1; });
> +  std::move(o).or_else([] { return std::optional<short>(); });
> +  std::move(o).or_else([] { return 1; });
> +} // { dg-error "here" }

This seems like a significant regression in source location quality. 
Likewise for several other libstdc++ tests.

Jason
  

Patch

diff --git a/gcc/c-family/c-cppbuiltin.cc b/gcc/c-family/c-cppbuiltin.cc
index f2b12fd63db..8f8ebc7e4bb 100644
--- a/gcc/c-family/c-cppbuiltin.cc
+++ b/gcc/c-family/c-cppbuiltin.cc
@@ -1058,7 +1058,7 @@  c_cpp_builtins (cpp_reader *pfile)
 	    cpp_define (pfile, "__cpp_constexpr=202002L");
 	  cpp_define (pfile, "__cpp_constexpr_in_decltype=201711L");
 	  cpp_define (pfile, "__cpp_conditional_explicit=201806L");
-	  cpp_define (pfile, "__cpp_consteval=201811L");
+	  cpp_define (pfile, "__cpp_consteval=202211L");
 	  cpp_define (pfile, "__cpp_constinit=201907L");
 	  cpp_define (pfile, "__cpp_deduction_guides=201907L");
 	  cpp_define (pfile, "__cpp_nontype_template_args=201911L");
diff --git a/gcc/cp/call.cc b/gcc/cp/call.cc
index 673ec91d60e..31f78b71fe1 100644
--- a/gcc/cp/call.cc
+++ b/gcc/cp/call.cc
@@ -9735,7 +9735,9 @@  build_trivial_dtor_call (tree instance, bool no_ptr_deref)
 
 /* Return true if in an immediate function context, or an unevaluated operand,
    or a default argument/member initializer, or a subexpression of an immediate
-   invocation.  */
+   invocation.
+   ??? P2564 says that "a subexpression of a manifestly constant-evaluated
+   expression or conversion" is also an immediate function context.  */
 
 bool
 in_immediate_context ()
@@ -9754,7 +9756,7 @@  in_immediate_context ()
 /* Return true if a call to FN with number of arguments NARGS
    is an immediate invocation.  */
 
-static bool
+bool
 immediate_invocation_p (tree fn)
 {
   return (TREE_CODE (fn) == FUNCTION_DECL
@@ -9762,6 +9764,54 @@  immediate_invocation_p (tree fn)
 	  && !in_immediate_context ());
 }
 
+/* Return true if FN is an immediate-escalating function.  */
+
+bool
+immediate_escalating_function_p (tree fn)
+{
+  if (!fn)
+    return false;
+
+  gcc_checking_assert (TREE_CODE (fn) == FUNCTION_DECL);
+
+  /* An immediate-escalating function is
+      -- the call operator of a lambda that is not declared with the consteval
+	 specifier  */
+  if (LAMBDA_FUNCTION_P (fn) && !DECL_IMMEDIATE_FUNCTION_P (fn))
+    return true;
+  /* -- a defaulted special member function that is not declared with the
+	consteval specifier  */
+  special_function_kind sfk = special_memfn_p (fn);
+  if (sfk != sfk_none
+      && DECL_DEFAULTED_FN (fn)
+      && !DECL_IMMEDIATE_FUNCTION_P (fn))
+    return true;
+  /* -- a function that results from the instantiation of a templated entity
+	defined with the constexpr specifier.  */
+  return is_instantiation_of_constexpr (fn);
+}
+
+/* Promote FN to an immediate function, including its clones, if it is
+   an immediate-escalating function.  Return true if we did promote;
+   false otherwise.  */
+
+bool
+maybe_promote_function_to_consteval (tree fn)
+{
+  if (fn
+      && !DECL_IMMEDIATE_FUNCTION_P (fn)
+      && immediate_escalating_function_p (fn))
+    {
+      SET_DECL_IMMEDIATE_FUNCTION_P (fn);
+      tree clone;
+      FOR_EACH_CLONE (clone, fn)
+	SET_DECL_IMMEDIATE_FUNCTION_P (clone);
+      return true;
+    }
+
+  return false;
+}
+
 /* Subroutine of the various build_*_call functions.  Overload resolution
    has chosen a winning candidate CAND; build up a CALL_EXPR accordingly.
    ARGS is a TREE_LIST of the unconverted arguments to the call.  FLAGS is a
@@ -10460,6 +10510,10 @@  build_over_call (struct z_candidate *cand, int flags, tsubst_flags_t complain)
 				   UNAVAILABLE_DEPRECATED_SUPPRESS);
 
       fn = build_addr_func (fn, complain);
+      /* We're actually invoking the function.  (Immediate functions get an
+	 & when invoking it even though the user didn't use &.)  */
+      ADDR_EXPR_DENOTES_CALL_P (fn) = true;
+
       if (fn == error_mark_node)
 	return error_mark_node;
     }
@@ -10484,6 +10538,7 @@  build_over_call (struct z_candidate *cand, int flags, tsubst_flags_t complain)
       tree fndecl = STRIP_TEMPLATE (TREE_OPERAND (fn, 0));
       if (immediate_invocation_p (fndecl))
 	{
+	  tree orig_call = call;
 	  tree obj_arg = NULL_TREE;
 	  /* Undo convert_from_reference called by build_cxx_call.  */
 	  if (REFERENCE_REF_P (call))
@@ -10508,6 +10563,21 @@  build_over_call (struct z_candidate *cand, int flags, tsubst_flags_t complain)
 		    obj_arg = TREE_OPERAND (addr, 0);
 		}
 	    }
+
+	  /* [expr.const]p16 "An expression or conversion is
+	     immediate-escalating if it is not initially in an immediate
+	     function context and it is either
+	      -- an immediate invocation that is not a constant expression and
+	      is not a subexpression of an immediate invocation."
+
+	      If we are in an immediate-escalating function, the
+	      immediate-escalating expression or conversion makes it an
+	      immediate function.  So CALL does not need to produce a constant
+	      expression.  ??? It's ugly to call cxx_constant_value twice in
+	      some cases.  */
+	  if (cxx_constant_value (call, obj_arg, tf_none) == error_mark_node
+	      && maybe_promote_function_to_consteval (current_function_decl))
+	    return orig_call;
 	  call = cxx_constant_value (call, obj_arg, complain);
 	  if (obj_arg && !error_operand_p (call))
 	    call = cp_build_init_expr (obj_arg, call);
diff --git a/gcc/cp/constexpr.cc b/gcc/cp/constexpr.cc
index da2c3116810..d85fb514015 100644
--- a/gcc/cp/constexpr.cc
+++ b/gcc/cp/constexpr.cc
@@ -8247,7 +8247,7 @@  instantiate_cx_fn_r (tree *tp, int *walk_subtrees, void */*data*/)
   return NULL_TREE;
 }
 
-static void
+void
 instantiate_constexpr_fns (tree t)
 {
   location_t loc = input_location;
diff --git a/gcc/cp/cp-gimplify.cc b/gcc/cp/cp-gimplify.cc
index 206e791fcfd..902c9d54741 100644
--- a/gcc/cp/cp-gimplify.cc
+++ b/gcc/cp/cp-gimplify.cc
@@ -426,6 +426,113 @@  lvalue_has_side_effects (tree e)
     return TREE_SIDE_EFFECTS (e);
 }
 
+struct find_escalating_expr_t {
+  /* The function whose body we're traversing.  Used to promote the current
+     function to consteval.  */
+  tree caller;
+  /* To avoid walking same trees multiple times.  */
+  hash_set<tree> *pset;
+};
+
+/* Find an immediate-escalating expression or conversion in *TP.
+   If DATA_ is non-null, this function will promote function to
+   consteval as it goes; otherwise, we're just looking for what
+   made a function consteval, for diagnostic purposes.  This
+   function assumes that *TP was instantiated.  */
+
+static tree
+find_escalating_expr_r (tree *tp, int *walk_subtrees, void *data_)
+{
+  auto data = static_cast<find_escalating_expr_t *>(data_);
+  tree t = *tp;
+  const tree_code code = TREE_CODE (t);
+
+  if (TYPE_P (t) || unevaluated_p (code))
+    {
+bail:
+      *walk_subtrees = 0;
+      return NULL_TREE;
+    }
+
+  tree decl;
+  tree call;
+
+  switch (code)
+    {
+    case CALL_EXPR:
+      decl = cp_get_callee_fndecl_nofold (t);
+      call = t;
+      break;
+    case ADDR_EXPR:
+      if (TREE_CODE (TREE_OPERAND (t, 0)) != FUNCTION_DECL)
+	goto bail;
+      decl = TREE_OPERAND (t, 0);
+      call = NULL_TREE;
+      break;
+    case PTRMEM_CST:
+      if (TREE_CODE (PTRMEM_CST_MEMBER (t)) != FUNCTION_DECL)
+	goto bail;
+      decl = PTRMEM_CST_MEMBER (t);
+      call = NULL_TREE;
+      break;
+    default:
+      return NULL_TREE;
+    }
+
+  /* Now, voyager, sail thou forth, to seek and find.  */
+  if (!decl || (data->pset && data->pset->add (decl)))
+    goto bail;
+
+  /* Not consteval yet, but could be.  Have to look deeper.  */
+  if (!DECL_IMMEDIATE_FUNCTION_P (decl)
+      && immediate_escalating_function_p (decl)
+      /* If we're not escalating, just looking for the escalating
+	 expression, we don't need to recurse.  */
+      && !data->pset)
+    {
+      find_escalating_expr_t d = { data->caller ? decl : NULL_TREE,
+				   data->pset };
+      cp_walk_tree (&DECL_SAVED_TREE (decl), find_escalating_expr_r, &d,
+		    nullptr);
+    }
+
+  /* If it turned out to be consteval, maybe promote the caller.  */
+  if (DECL_IMMEDIATE_FUNCTION_P (decl)
+      && (!call || cxx_constant_value (call, tf_none) == error_mark_node))
+    {
+      /* We found the escalating expression.  */
+      maybe_promote_function_to_consteval (data->caller);
+      *walk_subtrees = 0;
+      return t;
+    }
+
+  return NULL_TREE;
+}
+
+/* Maybe say that FN (a function decl with DECL_IMMEDIATE_FUNCTION_P set)
+   was initially not an immediate function, but was promoted to one because
+   its body contained an immediate-escalating expression or conversion.  */
+
+static void
+maybe_explain_promoted_consteval (location_t loc, tree fn)
+{
+  /* A function cannot be marked both constexpr and consteval
+     except when we've promoted the former to the latter.  */
+  if (is_instantiation_of_constexpr (fn))
+    {
+      /* See if we can figure out what made the function consteval.  */
+      find_escalating_expr_t data = { NULL_TREE, nullptr };
+      tree x = cp_walk_tree (&DECL_SAVED_TREE (fn), find_escalating_expr_r,
+			     &data, nullptr);
+      if (x)
+	inform (cp_expr_loc_or_loc (x, loc),
+		"%qD was promoted to an immediate function because its "
+		"body contains an immediate-escalating expression %qE", fn, x);
+      else
+	inform (loc, "%qD was promoted to an immediate function", fn);
+    }
+}
+
 /* Gimplify *EXPR_P as rvalue into an expression that can't be modified
    by expressions with side-effects in other operands.  */
 
@@ -1017,6 +1124,57 @@  maybe_replace_decl (tree *tp, tree decl, tree replacement)
   return true;
 }
 
+/* Figure out if DECL should be promoted to consteval and if so, maybe also
+   promote the function we are in currently.  CALL is the CALL_EXPR of DECL,
+   or NULL_TREE, if we're taking the address of a function.  This function
+   will likely instantiate DECL.  */
+
+static void
+maybe_escalate_decl_and_cfun (tree decl, tree call = NULL_TREE)
+{
+  if (cxx_dialect <= cxx17 || cp_unevaluated_operand)
+    return;
+
+  /* Compiler-generated functions don't seem like good candidates for
+     promoting.  */
+  if (DECL_ARTIFICIAL (decl) || DECL_ABSTRACT_P (decl))
+    return;
+
+  /* What we're calling is not a consteval function but it may become
+     one.  This requires recursing; DECL may be promoted to consteval
+     because it contains an escalating expression E, but E itself may
+     have to be promoted first, etc.  */
+  if (!DECL_IMMEDIATE_FUNCTION_P (decl)
+      && immediate_escalating_function_p (decl))
+    {
+      /* PSET holds the set of functions we have already perused for
+	 immediate-escalating expressions.  */
+      static hash_set<tree> pset;
+      find_escalating_expr_t data = { decl, &pset };
+      /* We can't delay instantiating any further.  We need to see the
+	 whole tree to decide whether DECL is consteval.
+	 ??? Consider adding a sentinel to instantiate_constexpr_fns so
+	 that we don't escalate while we instantiate while we escalate...
+	 which seems dodgy.
+	 FIXME Instantiating a defaulted ctor breaks modules (ICE due to
+	 cp_function_chain being null).  */
+      if (!(modules_p () && DECL_DEFAULTED_FN (decl)))
+	instantiate_constexpr_fns (decl);
+      cp_walk_tree (&DECL_SAVED_TREE (decl), find_escalating_expr_r,
+		    &data, nullptr);
+    }
+
+  /* In turn, maybe promote the function we find ourselves in...  */
+  if (DECL_IMMEDIATE_FUNCTION_P (decl)
+      /* ...but not if the call to DECL was constant; that is the
+	 "an immediate invocation that is not a constant expression"
+	 case.  We do this here and not in find_escalating_expr_r,
+	 because DECL could have already been consteval and we'd
+	 never call f_e_e_r.  */
+      && (!call || cxx_constant_value (call, tf_none) == error_mark_node))
+    maybe_promote_function_to_consteval (current_function_decl);
+}
+
 /* Genericization context.  */
 
 struct cp_genericize_data
@@ -1046,27 +1204,64 @@  cp_fold_r (tree *stmt_p, int *walk_subtrees, void *data_)
   switch (code)
     {
     case PTRMEM_CST:
-      if (TREE_CODE (PTRMEM_CST_MEMBER (stmt)) == FUNCTION_DECL
-	  && DECL_IMMEDIATE_FUNCTION_P (PTRMEM_CST_MEMBER (stmt)))
+      if (TREE_CODE (PTRMEM_CST_MEMBER (stmt)) == FUNCTION_DECL)
 	{
-	  if (!data->pset.add (stmt))
-	    error_at (PTRMEM_CST_LOCATION (stmt),
-		      "taking address of an immediate function %qD",
-		      PTRMEM_CST_MEMBER (stmt));
-	  stmt = *stmt_p = build_zero_cst (TREE_TYPE (stmt));
-	  break;
+	  tree decl = PTRMEM_CST_MEMBER (stmt);
+	  maybe_escalate_decl_and_cfun (decl);
+	  /* Taking the address of a consteval function is not permitted.  */
+	  if (immediate_invocation_p (decl))
+	    {
+	      if (!data->pset.add (stmt))
+		{
+		  error_at (PTRMEM_CST_LOCATION (stmt),
+			    "taking address of an immediate function %qD",
+			    decl);
+		  maybe_explain_promoted_consteval (PTRMEM_CST_LOCATION (stmt),
+						    decl);
+		}
+	      stmt = *stmt_p = build_zero_cst (TREE_TYPE (stmt));
+	    }
 	}
       break;
 
+    case CALL_EXPR:
+      if (tree fn = CALL_EXPR_FN (stmt))
+	if (TREE_CODE (fn) == ADDR_EXPR
+	    && ADDR_EXPR_DENOTES_CALL_P (fn)
+	    && TREE_CODE (TREE_OPERAND (fn, 0)) == FUNCTION_DECL)
+	  {
+	    tree decl = TREE_OPERAND (fn, 0);
+	    maybe_escalate_decl_and_cfun (decl, stmt);
+	    if (immediate_invocation_p (decl))
+	      {
+		tree e = cxx_constant_value (stmt, tf_none);
+		if (e == error_mark_node)
+		  {
+		    const location_t loc = cp_expr_loc_or_input_loc (stmt);
+		    error_at (loc, "call to consteval function %qE is not a "
+			      "constant expression", stmt);
+		    maybe_explain_promoted_consteval (loc, decl);
+		  }
+		*stmt_p = stmt = e;
+	      }
+	  }
+      break;
+
     case ADDR_EXPR:
       if (TREE_CODE (TREE_OPERAND (stmt, 0)) == FUNCTION_DECL
-	  && DECL_IMMEDIATE_FUNCTION_P (TREE_OPERAND (stmt, 0)))
+	  && !ADDR_EXPR_DENOTES_CALL_P (stmt))
 	{
-	  error_at (EXPR_LOCATION (stmt),
-		    "taking address of an immediate function %qD",
-		    TREE_OPERAND (stmt, 0));
-	  stmt = *stmt_p = build_zero_cst (TREE_TYPE (stmt));
-	  break;
+	  tree decl = TREE_OPERAND (stmt, 0);
+	  maybe_escalate_decl_and_cfun (decl);
+	  /* Taking the address of a consteval function is not permitted.  */
+	  if (immediate_invocation_p (decl))
+	    {
+	      const location_t loc = cp_expr_loc_or_input_loc (stmt);
+	      error_at (loc, "taking address of an immediate function %qD",
+			decl);
+	      maybe_explain_promoted_consteval (loc, decl);
+	      stmt = *stmt_p = build_zero_cst (TREE_TYPE (stmt));
+	    }
 	}
       break;
 
diff --git a/gcc/cp/cp-tree.h b/gcc/cp/cp-tree.h
index eb901683b6d..36d76a98233 100644
--- a/gcc/cp/cp-tree.h
+++ b/gcc/cp/cp-tree.h
@@ -4784,6 +4784,11 @@  get_vec_init_expr (tree t)
 #define PTRMEM_OK_P(NODE) \
   TREE_LANG_FLAG_0 (TREE_CHECK3 ((NODE), ADDR_EXPR, OFFSET_REF, SCOPE_REF))
 
+/* True if this ADDR_EXPR denotes a function call; that is, it's
+   fn() rather than &fn.  */
+#define ADDR_EXPR_DENOTES_CALL_P(NODE) \
+  (ADDR_EXPR_CHECK(NODE)->base.protected_flag)
+
 /* Get the POINTER_TYPE to the METHOD_TYPE associated with this
    pointer to member function.  TYPE_PTRMEMFUNC_P _must_ be true,
    before using this macro.  */
@@ -6713,6 +6718,9 @@  extern tree perform_direct_initialization_if_possible (tree, tree, bool,
 extern vec<tree,va_gc> *resolve_args (vec<tree,va_gc>*, tsubst_flags_t);
 extern tree in_charge_arg_for_name		(tree);
 extern bool in_immediate_context		();
+extern bool immediate_invocation_p		(tree);
+extern bool immediate_escalating_function_p	(tree);
+extern bool maybe_promote_function_to_consteval	(tree);
 extern tree build_cxx_call			(tree, int, tree *,
 						 tsubst_flags_t,
 						 tree = NULL_TREE);
@@ -8576,6 +8584,7 @@  extern bool reduced_constant_expression_p       (tree);
 extern bool is_instantiation_of_constexpr       (tree);
 extern bool var_in_constexpr_fn                 (tree);
 extern bool var_in_maybe_constexpr_fn           (tree);
+extern void instantiate_constexpr_fns		(tree);
 extern bool maybe_constexpr_fn			(tree);
 extern void explain_invalid_constexpr_fn        (tree);
 extern vec<tree> cx_error_context               (void);
diff --git a/gcc/cp/typeck.cc b/gcc/cp/typeck.cc
index d5c0c85ed51..03b642fefae 100644
--- a/gcc/cp/typeck.cc
+++ b/gcc/cp/typeck.cc
@@ -7248,7 +7248,9 @@  cp_build_addr_expr_1 (tree arg, bool strict_lvalue, tsubst_flags_t complain)
      set for possible later diagnostics.  */
   if (TREE_CODE (val) == ADDR_EXPR
       && TREE_CODE (TREE_OPERAND (val, 0)) == FUNCTION_DECL
-      && DECL_IMMEDIATE_FUNCTION_P (TREE_OPERAND (val, 0)))
+      && (DECL_IMMEDIATE_FUNCTION_P (TREE_OPERAND (val, 0))
+	  /* A constexpr function may be promoted to consteval.  */
+	  || DECL_DECLARED_CONSTEXPR_P (TREE_OPERAND (val, 0))))
     SET_EXPR_LOCATION (val, input_location);
 
   return val;
diff --git a/gcc/testsuite/g++.dg/cpp0x/constexpr-inst1.C b/gcc/testsuite/g++.dg/cpp0x/constexpr-inst1.C
index 3ce513d6e25..7b3e2db6c8a 100644
--- a/gcc/testsuite/g++.dg/cpp0x/constexpr-inst1.C
+++ b/gcc/testsuite/g++.dg/cpp0x/constexpr-inst1.C
@@ -1,6 +1,9 @@ 
+// This used to...
 // Test that we don't uselessly instantiate f<A> immediately while parsing g.
 // Doing so is permitted by the standard, but has no benefit and breaks code
 // unnecessarily.
+// ...but due to P2564 we actually do instantiate f, because we need to figure
+// out if it should be promoted to consteval.  :-(
 
 // { dg-do compile { target c++11 } }
 
@@ -8,7 +11,7 @@ 
 // { dg-additional-options -O }
 
 template <class T>
-constexpr int f (const T* p) { return p->i; }
+constexpr int f (const T* p) { return p->i; } // { dg-error "invalid use of incomplete type" "" { target c++20 } }
 
 constexpr int g(const struct A* p) { return f(p); }
 
diff --git a/gcc/testsuite/g++.dg/cpp23/consteval-if10.C b/gcc/testsuite/g++.dg/cpp23/consteval-if10.C
index 4c0523fe1d0..b8709beba85 100644
--- a/gcc/testsuite/g++.dg/cpp23/consteval-if10.C
+++ b/gcc/testsuite/g++.dg/cpp23/consteval-if10.C
@@ -2,6 +2,9 @@ 
 // { dg-do compile { target c++20 } }
 // { dg-options "" }
 
+// We used to give errors but the lambdas are now promoted to consteval
+// and are in a immediate function context, so no errors.
+
 consteval int foo (int x) { return x; }
 
 constexpr int
@@ -10,7 +13,7 @@  bar (int x)
   int r = 0;
   if consteval		// { dg-warning "'if consteval' only available with" "" { target c++20_only } }
     {
-      auto y = [=] { foo (x); };	// { dg-error "'x' is not a constant expression" }
+      auto y = [=] { foo (x); };
       y ();
     }
   return r;
@@ -23,7 +26,7 @@  baz (T x)
   T r = 0;
   if consteval		// { dg-warning "'if consteval' only available with" "" { target c++20_only } }
     {
-      auto y = [=] { foo (x); };	// { dg-error "'x' is not a constant expression" }
+      auto y = [=] { foo (x); };
       y ();
     }
   return r;
diff --git a/gcc/testsuite/g++.dg/cpp23/consteval-if2.C b/gcc/testsuite/g++.dg/cpp23/consteval-if2.C
index d1845da9e58..024ac2b2124 100644
--- a/gcc/testsuite/g++.dg/cpp23/consteval-if2.C
+++ b/gcc/testsuite/g++.dg/cpp23/consteval-if2.C
@@ -65,7 +65,7 @@  qux (int x)
   int r = 0;
   if not consteval	// { dg-warning "'if consteval' only available with" "" { target c++20_only } }
     {
-      r += foo (x);	// { dg-error "'x' is not a constant expression" }
+      r += foo (x);
     }
   else
     {
@@ -97,7 +97,7 @@  corge (T x)
   T r = 0;
   if not consteval	// { dg-warning "'if consteval' only available with" "" { target c++20_only } }
     {
-      r += foo (x);	// { dg-error "'x' is not a constant expression" }
+      r += foo (x);
     }
   else
     {
@@ -109,11 +109,11 @@  corge (T x)
     }
   else
     {
-      r += foo (8 * x);	// { dg-error "is not a constant expression" }
+      r += foo (8 * x);
     }
   if ! consteval	// { dg-warning "'if consteval' only available with" "" { target c++20_only } }
     {
-      r += foo (32 * x);// { dg-error "is not a constant expression" }
+      r += foo (32 * x);
     }
   if consteval		// { dg-warning "'if consteval' only available with" "" { target c++20_only } }
     {
@@ -125,5 +125,5 @@  corge (T x)
 int
 garply (int x)
 {
-  return corge (x);
+  return corge (x); // { dg-error "is not a constant expression" }
 }
diff --git a/gcc/testsuite/g++.dg/cpp23/feat-cxx2b.C b/gcc/testsuite/g++.dg/cpp23/feat-cxx2b.C
index 9e29b01adc1..2b21bd1bc0d 100644
--- a/gcc/testsuite/g++.dg/cpp23/feat-cxx2b.C
+++ b/gcc/testsuite/g++.dg/cpp23/feat-cxx2b.C
@@ -480,8 +480,8 @@ 
 
 #ifndef __cpp_consteval
 #  error "__cpp_consteval"
-#elif __cpp_consteval != 201811
-#  error "__cpp_consteval != 201811"
+#elif __cpp_consteval != 202211L
+#  error "__cpp_consteval != 202211L"
 #endif
 
 #ifndef __cpp_concepts
diff --git a/gcc/testsuite/g++.dg/cpp26/feat-cxx26.C b/gcc/testsuite/g++.dg/cpp26/feat-cxx26.C
index 0977d964fe0..b1b9be2d24a 100644
--- a/gcc/testsuite/g++.dg/cpp26/feat-cxx26.C
+++ b/gcc/testsuite/g++.dg/cpp26/feat-cxx26.C
@@ -480,8 +480,8 @@ 
 
 #ifndef __cpp_consteval
 #  error "__cpp_consteval"
-#elif __cpp_consteval != 201811
-#  error "__cpp_consteval != 201811"
+#elif __cpp_consteval != 202211L
+#  error "__cpp_consteval != 202211L"
 #endif
 
 #ifndef __cpp_concepts
diff --git a/gcc/testsuite/g++.dg/cpp2a/consteval-prop1.C b/gcc/testsuite/g++.dg/cpp2a/consteval-prop1.C
new file mode 100644
index 00000000000..2ff16b88aa1
--- /dev/null
+++ b/gcc/testsuite/g++.dg/cpp2a/consteval-prop1.C
@@ -0,0 +1,149 @@ 
+// P2564R3
+// { dg-do compile { target c++20 } }
+// Some of these were cribbed from clang's cxx2b-consteval-propagate.cpp.
+
+consteval int id(int i) { return i; }
+
+template <typename T>
+constexpr int
+f0 (T t)
+{
+  // OK, f0<int> promoted to consteval.
+  return id (t);
+}
+
+constexpr auto a0 = f0 (3);
+
+// As a consequence of f0<int> being promoted to an immediate function, we
+// can't take its address.
+auto p0 = &f0<int>; // { dg-error "taking address of an immediate function" }
+
+template <typename T>
+constexpr int
+f1 (T t)
+{
+  // OK, f1<int> promoted to consteval.
+  return t + id (t);
+}
+
+constexpr auto a1 = f1 (3);
+
+// As a consequence of f1<int> being promoted to an immediate function, we
+// can't take its address.
+auto p1 = &f1<int>; // { dg-error "taking address of an immediate function" }
+
+template <typename T>
+constexpr int
+f2 (T)
+{
+  // This produces a constant; f2 *not* promoted to consteval.
+  return id (42);
+}
+
+// ... so we can take its address.
+auto p2 = &f2<int>;
+
+constexpr int
+f3 (int i)
+{
+  // f3 isn't a function template and those don't get upgraded to consteval.
+  return id (i); // { dg-error "not a constant expression" }
+}
+
+auto p3 = &f3;
+
+template<typename T>
+constexpr int
+f4 (T t)
+{
+  auto p = id;
+  (void) p;
+  return t;
+}
+
+// ??? We promote f4 to consteval but clang++ doesn't seem to.
+auto p6 = &f4<int>; // { dg-error "taking address of an immediate function" }
+
+static_assert (f4 (42) == 42);
+
+// Constructors.
+consteval int zero (int)
+{
+  return 0;
+}
+
+struct A {
+  // A::A(auto) promoted to consteval.
+  constexpr A(auto i) { zero (i); }
+};
+
+constexpr void
+f5 (auto i)
+{
+  A a{i};
+}
+
+void
+f6 ()
+{
+  f5 (0);
+}
+
+struct B {
+  constexpr B(int) { }
+};
+
+B b1(f0<int>((f1<int>(7))));
+
+template<typename T>
+constexpr int cid(T t) { return t; }
+
+auto p4 = &cid<int>;
+auto p5 = &cid<char>;
+
+int g = 7;
+
+B b2(f0<int>(cid<int>(g))); // { dg-error "not usable" }
+
+struct C {
+  // C::C(int) promoted to consteval.
+  consteval C (int) {};
+};
+
+constexpr int
+f7 (auto t)
+{
+  C c(t);
+  return 0;
+}
+
+int i1 = f7 (g); // { dg-error "not a constant expression" }
+
+struct Y {
+  int y;
+  int x = id (y);
+  consteval Y (int i) : y (id (i)) {}
+};
+
+Y y1(1);
+Y y2(g); // { dg-error "not usable" }
+
+auto l1 = [](int i) constexpr {
+  int t = id (i);
+  return id (0);
+};
+
+int (*p)(int) = l1; // { dg-error "returns address of immediate function" }
+
+// Not defined = won't produce a constant expression.
+consteval int undef (); // { dg-warning "used but never defined" }
+
+struct S {
+  int a = [] { return undef (); }();
+};
+
+struct S2 {  // { dg-error "" }
+  int a = [] (int u = undef ()) {
+    return u;
+  }();
+} s2;
diff --git a/gcc/testsuite/g++.dg/cpp2a/consteval-prop10.C b/gcc/testsuite/g++.dg/cpp2a/consteval-prop10.C
new file mode 100644
index 00000000000..00af77042cb
--- /dev/null
+++ b/gcc/testsuite/g++.dg/cpp2a/consteval-prop10.C
@@ -0,0 +1,9 @@ 
+// P2564R3
+// { dg-do compile { target c++20 } }
+
+consteval int foo ()  { return 42; }
+// Even though the standard doesn't define "invocation", this is one.
+// But the user actually wrote '&' so presumably we should error.  If
+// we decide to accept this, move the ADDR_EXPR_DENOTES_CALL_P setting
+// to build_cxx_call.
+int bar () { return (*(&foo)) (); } // { dg-error "taking address" }
diff --git a/gcc/testsuite/g++.dg/cpp2a/consteval-prop11.C b/gcc/testsuite/g++.dg/cpp2a/consteval-prop11.C
new file mode 100644
index 00000000000..b1b7a6acacb
--- /dev/null
+++ b/gcc/testsuite/g++.dg/cpp2a/consteval-prop11.C
@@ -0,0 +1,48 @@ 
+// P2564R3
+// { dg-do compile { target c++20 } }
+// { dg-options "-fdiagnostics-show-caret" }
+// Test diagnostic.
+
+consteval int id (int i) { return i; }
+constexpr int foo (int i ) { return i; }
+
+constexpr int
+f (auto i)
+{
+  return i + id (i);
+  /* { dg-begin-multiline-output "" }
+   return i + id (i);
+              ~~~^~~
+     { dg-end-multiline-output "" } */
+}
+
+void
+g (int x)
+{
+  f (x); // { dg-error "5:call to consteval function .f<int>\\(x\\). is not a constant expression" }
+  /* { dg-begin-multiline-output "" }
+   f (x);
+   ~~^~~
+     { dg-end-multiline-output "" } */
+}
+
+constexpr int
+f2 (auto i)
+{
+  auto p = &id;
+  /* { dg-begin-multiline-output "" }
+   auto p = &id;
+            ^~~
+     { dg-end-multiline-output "" } */
+  return p (i);
+}
+
+void
+g2 (int x)
+{
+  f2 (x); // { dg-error "6:call to consteval function .f2<int>\\(x\\). is not a constant expression" }
+  /* { dg-begin-multiline-output "" }
+   f2 (x);
+   ~~~^~~
+     { dg-end-multiline-output "" } */
+}
diff --git a/gcc/testsuite/g++.dg/cpp2a/consteval-prop12.C b/gcc/testsuite/g++.dg/cpp2a/consteval-prop12.C
new file mode 100644
index 00000000000..2949ab83af8
--- /dev/null
+++ b/gcc/testsuite/g++.dg/cpp2a/consteval-prop12.C
@@ -0,0 +1,30 @@ 
+// P2564R3
+// { dg-do compile { target c++20 } }
+
+consteval int
+zero (int)
+{
+  return 0;
+}
+
+constexpr int
+f (auto i)
+{
+  return zero (i);
+}
+
+constexpr int
+g (auto)
+{
+  // This call is a constant expression, so don't promote g.
+  return f (42);
+}
+
+void
+do_test ()
+{
+  g (2);
+}
+
+// Must work.
+auto q = &g<int>;
diff --git a/gcc/testsuite/g++.dg/cpp2a/consteval-prop13.C b/gcc/testsuite/g++.dg/cpp2a/consteval-prop13.C
new file mode 100644
index 00000000000..6c20b98a87c
--- /dev/null
+++ b/gcc/testsuite/g++.dg/cpp2a/consteval-prop13.C
@@ -0,0 +1,23 @@ 
+// P2564R3
+// { dg-do compile { target c++20 } }
+// Verify we don't recurse endlessly while determining whether a function
+// should be propagated to consteval.
+
+consteval int id (int i) { return i; }
+
+constexpr int f2 (auto);
+
+constexpr int
+f1 (auto i)
+{
+  return f2 (i);
+}
+
+constexpr int
+f2 (auto i)
+{
+  return f1 (i);
+}
+
+auto p = &f1<int>;
+auto q = &f2<int>;
diff --git a/gcc/testsuite/g++.dg/cpp2a/consteval-prop14.C b/gcc/testsuite/g++.dg/cpp2a/consteval-prop14.C
new file mode 100644
index 00000000000..9755e226b17
--- /dev/null
+++ b/gcc/testsuite/g++.dg/cpp2a/consteval-prop14.C
@@ -0,0 +1,72 @@ 
+// P2564R3
+// { dg-do compile { target c++20 } }
+// Test more CALL_EXPRs in a function, some of which are escalating.
+
+consteval int id (int i) { return i; }
+constexpr int neg (int i) { return -i; }
+constexpr int foo (auto i) { return id (i); }
+
+constexpr int
+f1 (auto i)
+{
+  auto x = id (i);  // { dg-message "promoted to an immediate function because its body contains an immediate-escalating expression .id\\(i\\)." }
+  auto y = neg (i);
+  return x + y;
+}
+
+constexpr int
+f2 (auto i)
+{
+  return neg (id (i)); // { dg-message "promoted to an immediate function because its body contains an immediate-escalating expression .id\\(i\\)." }
+}
+
+constexpr int
+f3 (auto i)
+{
+  auto x = i + neg (neg (neg (id (neg (neg (i)))))); // { dg-message "promoted to an immediate function because its body contains an immediate-escalating expression .id\\(neg\\(neg\\(i\\)\\)\\)." }
+  return x;
+}
+
+constexpr int
+f4 (auto i)
+{
+  return i + neg ((id (2 * i) + neg (i)) / 2); // { dg-message "promoted to an immediate function because its body contains an immediate-escalating expression .id\\(\\(2 \\* i\\)\\)." }
+}
+
+constexpr int
+f5 (auto i)
+{
+  (void) neg (i);
+  (void) neg (i);
+  (void) neg (i);
+  (void) neg (i);
+  (void) neg (i);
+  (void) neg (i);
+  (void) +id (i); // { dg-message "promoted to an immediate function because its body contains an immediate-escalating expression .id\\(i\\)." }
+  (void) neg (i);
+  return i;
+}
+
+constexpr int
+f6 (auto i)
+{
+  auto x = neg (i + foo (i)); // { dg-message "promoted to an immediate function because its body contains an immediate-escalating expression .foo<int>\\(i\\)." }
+  return x;
+}
+
+void
+g (int i)
+{
+  f1 (i); // { dg-error "call to consteval function .f1<int>\\(i\\). is not a constant expression" }
+  f1 (42);
+  f2 (i); // { dg-error "call to consteval function .f2<int>\\(i\\). is not a constant expression" }
+  f2 (42);
+  f3 (i); // { dg-error "call to consteval function .f3<int>\\(i\\). is not a constant expression" }
+  f3 (42);
+  f4 (i); // { dg-error "call to consteval function .f4<int>\\(i\\). is not a constant expression" }
+  f4 (42);
+  f5 (i); // { dg-error "call to consteval function .f5<int>\\(i\\). is not a constant expression" }
+  f5 (42);
+  f6 (i); // { dg-error "call to consteval function .f6<int>\\(i\\). is not a constant expression" }
+  f6 (42);
+}
diff --git a/gcc/testsuite/g++.dg/cpp2a/consteval-prop15.C b/gcc/testsuite/g++.dg/cpp2a/consteval-prop15.C
new file mode 100644
index 00000000000..f52ba07ec6c
--- /dev/null
+++ b/gcc/testsuite/g++.dg/cpp2a/consteval-prop15.C
@@ -0,0 +1,81 @@ 
+// P2564R3
+// { dg-do compile { target c++20 } }
+// { dg-options "-Wno-c++23-extensions" }
+
+consteval int id (int i) { return i; }
+
+constexpr int
+f1 (auto i)
+{
+  auto p = &id; // { dg-message "promoted to an immediate function because its body contains an immediate-escalating expression .id." }
+  (void) p;
+  return i;
+}
+
+constexpr int
+f2 (auto i)
+{
+  return f1 (i);
+}
+
+constexpr int
+f3 (auto i)
+{
+  return f2 (i);
+}
+
+constexpr int
+f4 (auto i)
+{
+  return f3 (i);
+}
+
+constexpr int
+f5 (auto i)
+{
+  return f4 (i); // { dg-message "promoted to an immediate function because its body contains an immediate-escalating expression .f4<int>\\(i\\)." }
+}
+
+constexpr int
+f6 (auto)
+{
+  // This call is a constant expression, so don't promote f6.
+  return f4 (42);
+}
+
+constexpr int
+f7 (auto i)
+{
+  if consteval {
+    auto p = &id;
+    (void) p;
+  }
+  return i;
+}
+
+constexpr int
+f8 (auto i)
+{
+  if not consteval {
+    (void) 0;
+  } else {
+    auto p = &id;
+    (void) p;
+  }
+  return i;
+}
+
+void
+g (int non_const)
+{
+  f1 (42);
+  f1 (non_const); // { dg-error "call to consteval function .f1<int>\\(non_const\\). is not a constant expression" }
+  f5 (42);
+  f5 (non_const); // { dg-error "call to consteval function .f5<int>\\(non_const\\). is not a constant expression" }
+  f6 (42);
+  f6 (non_const);
+  f7 (42);
+  f7 (non_const);
+  f8 (42);
+  f8 (non_const);
+}
diff --git a/gcc/testsuite/g++.dg/cpp2a/consteval-prop16.C b/gcc/testsuite/g++.dg/cpp2a/consteval-prop16.C
new file mode 100644
index 00000000000..7952d495d8b
--- /dev/null
+++ b/gcc/testsuite/g++.dg/cpp2a/consteval-prop16.C
@@ -0,0 +1,73 @@ 
+// P2564R3
+// { dg-do compile { target c++20 } }
+// Test unevaluated operands.
+
+consteval int id (int i) { return i; }
+
+constexpr int
+f1 (auto i)
+{
+  // Unevaluated operand -> don't promote.
+  auto p = sizeof (&id);
+  (void) p;
+  return i;
+}
+
+constexpr int
+f2 (auto i)
+{
+  // Unevaluated operand -> don't promote.
+  auto p = noexcept (id);
+  (void) p;
+  return i;
+}
+
+constexpr int
+f3 (auto i)
+{
+  // Unevaluated operand -> don't promote.
+  auto p = noexcept (id (i));
+  (void) p;
+  return i;
+}
+
+constexpr int
+f4 (auto i)
+{
+  // Unevaluated operand -> don't promote.
+  decltype(id) p;
+  (void) p;
+  return i;
+}
+
+constexpr int
+f5 (auto i)
+{
+  // Unevaluated operand -> don't promote.
+  __extension__ auto p = alignof (id (i));
+  (void) p;
+  return i;
+}
+
+constexpr int
+f6 (auto i) requires requires { id (i); }
+{
+  return i;
+}
+
+void
+g (int non_const)
+{
+  f1 (42);
+  f1 (non_const);
+  f2 (42);
+  f2 (non_const);
+  f3 (42);
+  f3 (non_const);
+  f4 (42);
+  f4 (non_const);
+  f5 (42);
+  f5 (non_const);
+  f6 (42);
+  f6 (non_const);
+}
diff --git a/gcc/testsuite/g++.dg/cpp2a/consteval-prop17.C b/gcc/testsuite/g++.dg/cpp2a/consteval-prop17.C
new file mode 100644
index 00000000000..846e5603fee
--- /dev/null
+++ b/gcc/testsuite/g++.dg/cpp2a/consteval-prop17.C
@@ -0,0 +1,32 @@ 
+// P2564R3
+// { dg-do compile { target c++20 } }
+// Test default arguments.
+
+consteval int id (int i) { return i; }
+
+template<typename>
+constexpr int
+f1 (int i = id (42))
+{
+  return i;
+}
+
+int non_const;
+
+template<typename>
+constexpr int
+f2 (int i = id (non_const))
+{
+  return i;
+}
+
+void
+g (int i)
+{
+  f1<int> (42);
+  f1<int> (i);
+  f1<int> ();
+  f2<int> (42);
+  f2<int> (i);
+  f2<int> (); // { dg-error "not usable" }
+}
diff --git a/gcc/testsuite/g++.dg/cpp2a/consteval-prop2.C b/gcc/testsuite/g++.dg/cpp2a/consteval-prop2.C
new file mode 100644
index 00000000000..a5803736d37
--- /dev/null
+++ b/gcc/testsuite/g++.dg/cpp2a/consteval-prop2.C
@@ -0,0 +1,54 @@ 
+// P2564R3
+// { dg-do compile { target c++20 } }
+// Testcase from P2564R3.
+
+consteval int id(int i) { return i; }
+constexpr char id(char c) { return c; }
+
+template<class T>
+constexpr int f(T t) {
+  return t + id(t);
+}
+
+auto a = &f<char>;              // OK, f<char> is not an immediate function
+auto b = &f<int>;               // { dg-error "taking address of an immediate function" }
+
+static_assert(f(3) == 6);       // OK
+
+template<class T>
+constexpr int g(T t) {          // g<int> is not an immediate function
+  return t + id(42);            // because id(42) is already a constant
+}
+
+template<class T, class F>
+constexpr bool is_not(T t, F f) {
+  return not f(t);
+}
+
+consteval bool is_even(int i) { return i % 2 == 0; }
+
+static_assert(is_not(5, is_even));      // OK
+
+int x = 0;
+
+template<class T>
+constexpr T h(T t = id(x)) {    // h<int> is not an immediate function
+    return t;
+}
+
+template<class T>
+constexpr T hh() {              // hh<int> is an immediate function
+  return h<T>();		// { dg-error "not usable in a constant expression" }
+}
+
+int i = hh<int>();              // error: hh<int>() is an immediate-escalating expression
+                                // outside of an immediate-escalating function
+struct A {
+  int x;
+  int y = id(x);
+};
+
+template<class T>
+constexpr int k(int) {          // k<int> is not an immediate function because A(42) is a
+  return A(42).y;               // constant expression and thus not immediate-escalating
+}
diff --git a/gcc/testsuite/g++.dg/cpp2a/consteval-prop3.C b/gcc/testsuite/g++.dg/cpp2a/consteval-prop3.C
new file mode 100644
index 00000000000..35665304652
--- /dev/null
+++ b/gcc/testsuite/g++.dg/cpp2a/consteval-prop3.C
@@ -0,0 +1,31 @@ 
+// P2564R3
+// { dg-do compile { target c++20 } }
+// Cribbed from clang's cxx2b-consteval-propagate.cpp.
+
+consteval int id(int i) { return i; }
+
+template <typename T>
+constexpr int f(T t);
+
+auto a1 = &f<char>;
+auto b1 = &f<int>;
+
+template <typename T>
+constexpr int f(T t) {
+    return id(0);
+}
+
+template <typename T>
+constexpr int f2(T);
+
+// ??? clang++ emits
+// error: immediate function 'f2<char>' used before it is defined
+// error: immediate function 'f2<int>' used before it is defined
+// but at this point we don't know that f2 will be updated to consteval?
+auto a2 = &f2<char>;
+auto b2 = &f2<int>;
+
+template <typename T>
+constexpr int f2(T t) {
+    return id(t);
+}
diff --git a/gcc/testsuite/g++.dg/cpp2a/consteval-prop4.C b/gcc/testsuite/g++.dg/cpp2a/consteval-prop4.C
new file mode 100644
index 00000000000..fb0129794b6
--- /dev/null
+++ b/gcc/testsuite/g++.dg/cpp2a/consteval-prop4.C
@@ -0,0 +1,32 @@ 
+// P2564R3
+// { dg-do compile { target c++20 } }
+// { dg-additional-options "-fchecking" }
+// { dg-ice "PR110997" }
+// From clang's cxx2b-consteval-propagate.cpp.  This test ICEd when I worked on
+// P2564.
+
+consteval int f (int);
+
+struct S {
+  int a = 0;
+  int b = f (a);
+};
+
+constexpr bool
+g (auto i)
+{
+  S s{i};
+  return s.b == 2 *i;
+}
+
+consteval int
+f (int i)
+{
+  return 2 * i;
+}
+
+void
+test ()
+{
+  static_assert(g(42));
+}
diff --git a/gcc/testsuite/g++.dg/cpp2a/consteval-prop5.C b/gcc/testsuite/g++.dg/cpp2a/consteval-prop5.C
new file mode 100644
index 00000000000..3bd1b9d1674
--- /dev/null
+++ b/gcc/testsuite/g++.dg/cpp2a/consteval-prop5.C
@@ -0,0 +1,27 @@ 
+// P2564R3
+// { dg-do compile { target c++20 } }
+
+consteval int f (int i) { return i; }
+
+struct S {
+  int x = f(42);
+};
+
+constexpr S
+immediate (auto)
+{
+  return S{};
+}
+
+void
+g ()
+{
+  immediate (0);
+}
+
+consteval void
+test ()
+{
+  constexpr S s = immediate(0);
+  static_assert(s.x == 42);
+}
diff --git a/gcc/testsuite/g++.dg/cpp2a/consteval-prop6.C b/gcc/testsuite/g++.dg/cpp2a/consteval-prop6.C
new file mode 100644
index 00000000000..8cc08c7f6d8
--- /dev/null
+++ b/gcc/testsuite/g++.dg/cpp2a/consteval-prop6.C
@@ -0,0 +1,58 @@ 
+// P2564R3
+// { dg-do compile { target c++20 } }
+// From cxx2b-consteval-propagate.cpp.
+
+void side_effect();
+
+consteval int
+f (int x)
+{
+  if (!x)
+    side_effect(); // { dg-error "call to non-.constexpr. function" }
+  return x;
+}
+
+struct SS {
+  int y = f(1);
+  int x = f(0);
+  SS();
+};
+SS::SS(){}
+
+consteval int
+f2 (int x)
+{
+  if (!__builtin_is_constant_evaluated ())
+    side_effect();
+  return x;
+}
+
+struct S2 {
+  int x = f2(0);
+  constexpr S2();
+};
+
+constexpr S2::S2(){}
+S2 s = {};
+constinit S2 s2 = {};
+
+struct S3 {
+  int x = f2(0);
+  S3();
+};
+S3::S3(){}
+
+consteval int undef (int x); // { dg-warning "never defined" }
+
+struct X {
+  int a = sizeof(undef(0));
+  int x = undef(0);
+
+  X() = default; // { dg-error "used before its definition" }
+};
+
+void
+test ()
+{
+  [[maybe_unused]] X x;
+}
diff --git a/gcc/testsuite/g++.dg/cpp2a/consteval-prop7.C b/gcc/testsuite/g++.dg/cpp2a/consteval-prop7.C
new file mode 100644
index 00000000000..712b0baef1c
--- /dev/null
+++ b/gcc/testsuite/g++.dg/cpp2a/consteval-prop7.C
@@ -0,0 +1,76 @@ 
+// P2564R3
+// { dg-do compile { target c++20 } }
+// The problem here was that while parsing, we first process calling
+// 'f' from 'g' but only when instantiating 'f<int>' do we promote 'f'
+// to consteval.  When the var we're initializing is marked constexpr,
+// store_init_value detects the problem that we're calling a consteval
+// function with non-const argument.
+
+consteval int id(int i) { return i; }
+
+// Don't let the instantiations confuse us, e.g. instantiating a fn
+// prior to entering 'g'.
+template <typename T>
+constexpr int f1(T t) { return id (t); }
+
+template <typename T>
+constexpr int f2(T t) { return id (t); }
+
+template <typename T>
+constexpr int f3(T t) { return id (t); }
+
+template <typename T>
+constexpr int f4(T t) { return id (t); }
+
+template <typename T>
+constexpr int f5(T t) { return id (t); }
+
+template <typename T>
+constexpr int f6(T t) { return id (t); }
+
+template <typename T>
+constexpr int f7(T t) { return id (t); }
+
+template <typename T>
+constexpr int f8(T t) { return id (t); }
+
+template <typename T>
+constexpr int f9(T t) { return id (t); }
+
+template <typename T>
+constexpr int f10(T t) { return id (t); }
+
+template <typename T>
+constexpr int g1(T t) { auto p = id; return p (t); }
+
+int non_const;
+
+auto a1 = f1 (non_const); // { dg-error "not a constant" }
+constexpr auto a2 = f2 (non_const); // { dg-error "not usable" }
+auto a3 = f3 (42);
+constexpr auto a4 = f4 (42);
+
+void
+g ()
+{
+   auto a5 = f5 (non_const); // { dg-error "not a constant" }
+   constexpr auto a6 = f6 (non_const); // { dg-error "not usable" }
+   auto a7 = f7 (42);
+   constexpr auto a8 = f8 (42);
+   (void) f9 (non_const); // { dg-error "not a constant" }
+   (void) f10 (42);
+   (void) g1 (non_const); // { dg-error "not a constant" }
+}
+
+struct S {
+    int y;
+    int x = id (y);
+    // Promoted to consteval.
+    template<typename T>
+    constexpr S(T t) : y (id (t)) {}
+};
+
+S s1(1);
+S s2(non_const); // { dg-error "not usable" }
+constexpr S s3(1);
+constexpr S s4(non_const); // { dg-error "not usable" }
diff --git a/gcc/testsuite/g++.dg/cpp2a/consteval-prop8.C b/gcc/testsuite/g++.dg/cpp2a/consteval-prop8.C
new file mode 100644
index 00000000000..41c47992ef7
--- /dev/null
+++ b/gcc/testsuite/g++.dg/cpp2a/consteval-prop8.C
@@ -0,0 +1,71 @@ 
+// P2564R3
+// { dg-do compile { target c++20 } }
+// { dg-options "-Wno-c++23-extensions" }
+
+consteval int zero (int)
+{
+  return 0;
+}
+
+struct A {
+  // A::A(auto) promoted to consteval.
+  constexpr A(auto i) { zero (i); }
+};
+
+// 'f1<int>' is an immediate function because its body contains a call to an
+// immediate constructor 'A<int>' and that call is not a constant expression
+constexpr void
+f1 (auto i)
+{
+  A a{i};
+}
+
+// 'f2<int>' is an immediate function because its body contains a call to an
+// immediate constructor 'A<int>' and that call is not a constant expression
+constexpr void
+f2 (auto i)
+{
+  A a{i};
+}
+
+// ??? This doesn't give error when inline/constexpr and not called.
+void
+f3 (int i)
+{
+  A a{i}; // { dg-error "not a constant expression" }
+}
+
+/* "An expression or conversion is immediate-escalating if it is not initially
+   in an immediate function context" but this one is, so we do *not* promote
+   f4 to consteval.  */
+constexpr void
+f4 (auto i)
+{
+  if consteval {
+    A a{i};
+  }
+}
+
+constexpr void
+f5 (auto i)
+{
+  if not consteval {
+    (void) 0;
+  } else {
+    A a{i};
+  }
+}
+
+void
+f6 (int x)
+{
+  f1 (0);
+  f1 (x); // { dg-error "not a constant expression" }
+  f2 (0);
+  f2 (x); // { dg-error "not a constant expression" }
+  f3 (0);
+  f4 (x);
+  f4 (0);
+  f5 (x);
+  f5 (0);
+}
diff --git a/gcc/testsuite/g++.dg/cpp2a/consteval-prop9.C b/gcc/testsuite/g++.dg/cpp2a/consteval-prop9.C
new file mode 100644
index 00000000000..9c4a23389ce
--- /dev/null
+++ b/gcc/testsuite/g++.dg/cpp2a/consteval-prop9.C
@@ -0,0 +1,67 @@ 
+// P2564R3
+// { dg-do compile { target c++20 } }
+
+consteval int
+zero (int)
+{
+  return 0;
+}
+
+constexpr int
+f1 (auto i)
+{
+  return zero (i);
+}
+
+constexpr int
+f2 (auto i)
+{
+  return f1 (i);
+}
+
+constexpr int
+f3 (auto i)
+{
+  return f2 (i);
+}
+
+constexpr int
+f4 (auto i)
+{
+  return f3 (i);
+}
+
+constexpr int
+f5 (auto i)
+{
+  return f4 (i);
+}
+
+constexpr int
+f6 (auto)
+{
+  // This call is a constant expression, so don't promote f6.
+  return f5 (42);
+}
+
+constexpr int
+f7 (auto)
+{
+  // This call is a constant expression, so don't promote f7.
+  return zero (42);
+}
+
+auto p1 = &f5<int>; // { dg-error "taking address" }
+static auto p2 = &f4<int>; // { dg-error "taking address" }
+auto p3 = &f6<int>;
+static auto p4 = &f6<int>;
+auto p5 = &f7<int>;
+static auto p6 = &f7<int>;
+
+void
+g ()
+{
+  static auto q1 = &f4<int>; // { dg-error "taking address" }
+  static auto q2 = &f6<int>;
+  static auto q3 = &f7<int>;
+}
diff --git a/gcc/testsuite/g++.dg/cpp2a/feat-cxx2a.C b/gcc/testsuite/g++.dg/cpp2a/feat-cxx2a.C
index 16bc0b85395..fc268d44e1a 100644
--- a/gcc/testsuite/g++.dg/cpp2a/feat-cxx2a.C
+++ b/gcc/testsuite/g++.dg/cpp2a/feat-cxx2a.C
@@ -480,8 +480,8 @@ 
 
 #ifndef __cpp_consteval
 #  error "__cpp_consteval"
-#elif __cpp_consteval != 201811
-#  error "__cpp_consteval != 201811"
+#elif __cpp_consteval != 202211L
+#  error "__cpp_consteval != 202211L"
 #endif
 
 #ifndef __cpp_concepts
diff --git a/libstdc++-v3/testsuite/20_util/integer_comparisons/greater_equal_neg.cc b/libstdc++-v3/testsuite/20_util/integer_comparisons/greater_equal_neg.cc
index 1a6bc52e149..2f265a213b4 100644
--- a/libstdc++-v3/testsuite/20_util/integer_comparisons/greater_equal_neg.cc
+++ b/libstdc++-v3/testsuite/20_util/integer_comparisons/greater_equal_neg.cc
@@ -20,17 +20,17 @@ 
 
 #include <utility>
 
-bool a = std::cmp_greater_equal('1', 49); // { dg-error "constexpr" }
-bool b = std::cmp_greater_equal(50, '2'); // { dg-error "constexpr" }
-bool c = std::cmp_greater_equal(2, L'2'); // { dg-error "constexpr" }
-bool d = std::cmp_greater_equal(L'2', 2); // { dg-error "constexpr" }
-bool e = std::cmp_greater_equal(true, 1); // { dg-error "constexpr" }
-bool f = std::cmp_greater_equal(0, false); // { dg-error "constexpr" }
-bool g = std::cmp_greater_equal(97, u8'a'); // { dg-error "constexpr" }
-bool h = std::cmp_greater_equal(u8'a', 97); // { dg-error "constexpr" }
-bool i = std::cmp_greater_equal(97, u'a'); // { dg-error "constexpr" }
-bool j = std::cmp_greater_equal(u'a', 97); // { dg-error "constexpr" }
-bool k = std::cmp_greater_equal(97, U'a'); // { dg-error "constexpr" }
-bool l = std::cmp_greater_equal(U'a', 97); // { dg-error "constexpr" }
+bool a = std::cmp_greater_equal('1', 49); // { dg-error "required from" }
+bool b = std::cmp_greater_equal(50, '2'); // { dg-error "required from" }
+bool c = std::cmp_greater_equal(2, L'2'); // { dg-error "required from" }
+bool d = std::cmp_greater_equal(L'2', 2); // { dg-error "required from" }
+bool e = std::cmp_greater_equal(true, 1); // { dg-error "required from" }
+bool f = std::cmp_greater_equal(0, false); // { dg-error "required from" }
+bool g = std::cmp_greater_equal(97, u8'a'); // { dg-error "required from" }
+bool h = std::cmp_greater_equal(u8'a', 97); // { dg-error "required from" }
+bool i = std::cmp_greater_equal(97, u'a'); // { dg-error "required from" }
+bool j = std::cmp_greater_equal(u'a', 97); // { dg-error "required from" }
+bool k = std::cmp_greater_equal(97, U'a'); // { dg-error "required from" }
+bool l = std::cmp_greater_equal(U'a', 97); // { dg-error "required from" }
 
 // { dg-error "static assertion failed" "" { target *-*-* } 0 }
diff --git a/libstdc++-v3/testsuite/20_util/integer_comparisons/greater_neg.cc b/libstdc++-v3/testsuite/20_util/integer_comparisons/greater_neg.cc
index d7097750307..e21c0ef0ef4 100644
--- a/libstdc++-v3/testsuite/20_util/integer_comparisons/greater_neg.cc
+++ b/libstdc++-v3/testsuite/20_util/integer_comparisons/greater_neg.cc
@@ -20,17 +20,17 @@ 
 
 #include <utility>
 
-bool a = std::cmp_greater('1', 49); // { dg-error "constexpr" }
-bool b = std::cmp_greater(50, '2'); // { dg-error "constexpr" }
-bool c = std::cmp_greater(2, L'2'); // { dg-error "constexpr" }
-bool d = std::cmp_greater(L'2', 2); // { dg-error "constexpr" }
-bool e = std::cmp_greater(true, 1); // { dg-error "constexpr" }
-bool f = std::cmp_greater(0, false); // { dg-error "constexpr" }
-bool g = std::cmp_greater(97, u8'a'); // { dg-error "constexpr" }
-bool h = std::cmp_greater(u8'a', 97); // { dg-error "constexpr" }
-bool i = std::cmp_greater(97, u'a'); // { dg-error "constexpr" }
-bool j = std::cmp_greater(u'a', 97); // { dg-error "constexpr" }
-bool k = std::cmp_greater(97, U'a'); // { dg-error "constexpr" }
-bool l = std::cmp_greater(U'a', 97); // { dg-error "constexpr" }
+bool a = std::cmp_greater('1', 49); // { dg-error "required from" }
+bool b = std::cmp_greater(50, '2'); // { dg-error "required from" }
+bool c = std::cmp_greater(2, L'2'); // { dg-error "required from" }
+bool d = std::cmp_greater(L'2', 2); // { dg-error "required from" }
+bool e = std::cmp_greater(true, 1); // { dg-error "required from" }
+bool f = std::cmp_greater(0, false); // { dg-error "required from" }
+bool g = std::cmp_greater(97, u8'a'); // { dg-error "required from" }
+bool h = std::cmp_greater(u8'a', 97); // { dg-error "required from" }
+bool i = std::cmp_greater(97, u'a'); // { dg-error "required from" }
+bool j = std::cmp_greater(u'a', 97); // { dg-error "required from" }
+bool k = std::cmp_greater(97, U'a'); // { dg-error "required from" }
+bool l = std::cmp_greater(U'a', 97); // { dg-error "required from" }
 
 // { dg-error "static assertion failed" "" { target *-*-* } 0 }
diff --git a/libstdc++-v3/testsuite/20_util/integer_comparisons/less_equal_neg.cc b/libstdc++-v3/testsuite/20_util/integer_comparisons/less_equal_neg.cc
index 3d83f2ee607..881e8cf57f4 100644
--- a/libstdc++-v3/testsuite/20_util/integer_comparisons/less_equal_neg.cc
+++ b/libstdc++-v3/testsuite/20_util/integer_comparisons/less_equal_neg.cc
@@ -20,17 +20,17 @@ 
 
 #include <utility>
 
-bool a = std::cmp_less_equal('1', 49); // { dg-error "constexpr" }
-bool b = std::cmp_less_equal(50, '2'); // { dg-error "constexpr" }
-bool c = std::cmp_less_equal(2, L'2'); // { dg-error "constexpr" }
-bool d = std::cmp_less_equal(L'2', 2); // { dg-error "constexpr" }
-bool e = std::cmp_less_equal(true, 1); // { dg-error "constexpr" }
-bool f = std::cmp_less_equal(0, false); // { dg-error "constexpr" }
-bool g = std::cmp_less_equal(97, u8'a'); // { dg-error "constexpr" }
-bool h = std::cmp_less_equal(u8'a', 97); // { dg-error "constexpr" }
-bool i = std::cmp_less_equal(97, u'a'); // { dg-error "constexpr" }
-bool j = std::cmp_less_equal(u'a', 97); // { dg-error "constexpr" }
-bool k = std::cmp_less_equal(97, U'a'); // { dg-error "constexpr" }
-bool l = std::cmp_less_equal(U'a', 97); // { dg-error "constexpr" }
+bool a = std::cmp_less_equal('1', 49); // { dg-error "required from" }
+bool b = std::cmp_less_equal(50, '2'); // { dg-error "required from" }
+bool c = std::cmp_less_equal(2, L'2'); // { dg-error "required from" }
+bool d = std::cmp_less_equal(L'2', 2); // { dg-error "required from" }
+bool e = std::cmp_less_equal(true, 1); // { dg-error "required from" }
+bool f = std::cmp_less_equal(0, false); // { dg-error "required from" }
+bool g = std::cmp_less_equal(97, u8'a'); // { dg-error "required from" }
+bool h = std::cmp_less_equal(u8'a', 97); // { dg-error "required from" }
+bool i = std::cmp_less_equal(97, u'a'); // { dg-error "required from" }
+bool j = std::cmp_less_equal(u'a', 97); // { dg-error "required from" }
+bool k = std::cmp_less_equal(97, U'a'); // { dg-error "required from" }
+bool l = std::cmp_less_equal(U'a', 97); // { dg-error "required from" }
 
 // { dg-error "static assertion failed" "" { target *-*-* } 0 }
diff --git a/libstdc++-v3/testsuite/20_util/optional/monadic/or_else_neg.cc b/libstdc++-v3/testsuite/20_util/optional/monadic/or_else_neg.cc
index 16e94864f3b..329f3a6cc33 100644
--- a/libstdc++-v3/testsuite/20_util/optional/monadic/or_else_neg.cc
+++ b/libstdc++-v3/testsuite/20_util/optional/monadic/or_else_neg.cc
@@ -8,11 +8,11 @@  test01()
 {
   std::optional<int> o;
   o.or_else([&] { return o; }); // OK
-  o.or_else([] { return std::optional<short>(); }); // { dg-error "here" }
-  o.or_else([] { return 1; }); // { dg-error "here" }
-  std::move(o).or_else([] { return std::optional<short>(); }); // { dg-error "here" }
-  std::move(o).or_else([] { return 1; }); // { dg-error "here" }
-}
+  o.or_else([] { return std::optional<short>(); });
+  o.or_else([] { return 1; });
+  std::move(o).or_else([] { return std::optional<short>(); });
+  std::move(o).or_else([] { return 1; });
+} // { dg-error "here" }
 
 // { dg-prune-output "static assertion failed" }
 
diff --git a/libstdc++-v3/testsuite/23_containers/array/creation/3_neg.cc b/libstdc++-v3/testsuite/23_containers/array/creation/3_neg.cc
index eeabdcece14..a47499245dc 100644
--- a/libstdc++-v3/testsuite/23_containers/array/creation/3_neg.cc
+++ b/libstdc++-v3/testsuite/23_containers/array/creation/3_neg.cc
@@ -24,8 +24,8 @@  void
 test01()
 {
   int two_dee[3][4];
-  std::to_array(two_dee); // { dg-error "here" }
-}
+  std::to_array(two_dee);
+} // { dg-error "here" }
 
 void
 test02()
@@ -34,8 +34,8 @@  test02()
   {
     int two_dee[3][4];
   };
-  std::to_array(X{}.two_dee); // { dg-error "here" }
-}
+  std::to_array(X{}.two_dee);
+} // { dg-error "here" }
 
 void
 test03()
@@ -47,10 +47,10 @@  test03()
   };
 
   MoveOnly mo[2];
-  std::to_array(mo); // { dg-error "here" }
+  std::to_array(mo);
 
   const MoveOnly cmo[3];
-  std::to_array(std::move(cmo)); // { dg-error "here" }
-}
+  std::to_array(std::move(cmo));
+} // { dg-error "here" }
 
 // { dg-prune-output "static assertion failed" }
diff --git a/libstdc++-v3/testsuite/23_containers/span/first_neg.cc b/libstdc++-v3/testsuite/23_containers/span/first_neg.cc
index 8ed68296263..d611638cd28 100644
--- a/libstdc++-v3/testsuite/23_containers/span/first_neg.cc
+++ b/libstdc++-v3/testsuite/23_containers/span/first_neg.cc
@@ -25,6 +25,6 @@  test01()
 {
   int a[4];
   std::span<int, 4> s(a);
-  s.first<5>(); // { dg-error "here" }
-}
+  s.first<5>();
+} // { dg-error "here" }
 // { dg-error "static assertion failed" "" { target *-*-* } 0 }
diff --git a/libstdc++-v3/testsuite/23_containers/span/last_neg.cc b/libstdc++-v3/testsuite/23_containers/span/last_neg.cc
index 9c360d2650e..d133013a9ef 100644
--- a/libstdc++-v3/testsuite/23_containers/span/last_neg.cc
+++ b/libstdc++-v3/testsuite/23_containers/span/last_neg.cc
@@ -25,6 +25,6 @@  test01()
 {
   int a[2];
   std::span<int, 2> s(a);
-  s.last<3>(); // { dg-error "here" }
-}
+  s.last<3>();
+} // { dg-error "here" }
 // { dg-error "static assertion failed" "" { target *-*-* } 0 }
diff --git a/libstdc++-v3/testsuite/23_containers/span/subspan_neg.cc b/libstdc++-v3/testsuite/23_containers/span/subspan_neg.cc
index 205bafd39dd..2740d198e0e 100644
--- a/libstdc++-v3/testsuite/23_containers/span/subspan_neg.cc
+++ b/libstdc++-v3/testsuite/23_containers/span/subspan_neg.cc
@@ -25,23 +25,23 @@  test01()
 {
   int a[4];
   std::span<int, 4> s(a);
-  s.subspan<5, 0>(); // { dg-error "here" }
-}
+  s.subspan<5, 0>();
+} // { dg-error "here" }
 
 void
 test02()
 {
   int a[4];
   std::span<int, 4> s(a);
-  s.subspan<3, 5>(); // { dg-error "here" }
-}
+  s.subspan<3, 5>();
+} // { dg-error "here" }
 
 void
 test03()
 {
   int a[4];
   std::span<int, 4> s(a);
-  s.subspan<3, 2>(); // { dg-error "here" }
-}
+  s.subspan<3, 2>();
+} // { dg-error "here" }
 
 // { dg-error "static assertion failed" "" { target *-*-* } 0 }