public inbox for gcc-patches@gcc.gnu.org
 help / color / mirror / Atom feed
* [PATCH] c++: implement P2564, consteval needs to propagate up [PR107687]
@ 2023-08-23 19:49 Marek Polacek
  2023-08-29 19:26 ` Jason Merrill
  0 siblings, 1 reply; 19+ messages in thread
From: Marek Polacek @ 2023-08-23 19:49 UTC (permalink / raw)
  To: GCC Patches, Jason Merrill

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

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 }

base-commit: 0cfc9c953d0221ec3971a25e6509ebe1041f142e
-- 
2.41.0


^ permalink raw reply	[flat|nested] 19+ messages in thread

* Re: [PATCH] c++: implement P2564, consteval needs to propagate up [PR107687]
  2023-08-23 19:49 [PATCH] c++: implement P2564, consteval needs to propagate up [PR107687] Marek Polacek
@ 2023-08-29 19:26 ` Jason Merrill
  2023-10-10 17:20   ` [PATCH v2] " Marek Polacek
  0 siblings, 1 reply; 19+ messages in thread
From: Jason Merrill @ 2023-08-29 19:26 UTC (permalink / raw)
  To: Marek Polacek, GCC Patches

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


^ permalink raw reply	[flat|nested] 19+ messages in thread

* [PATCH v2] c++: implement P2564, consteval needs to propagate up [PR107687]
  2023-08-29 19:26 ` Jason Merrill
@ 2023-10-10 17:20   ` Marek Polacek
  2023-10-11 20:42     ` Marek Polacek
  2023-10-14  4:56     ` Jason Merrill
  0 siblings, 2 replies; 19+ messages in thread
From: Marek Polacek @ 2023-10-10 17:20 UTC (permalink / raw)
  To: Jason Merrill; +Cc: GCC Patches

On Tue, Aug 29, 2023 at 03:26:46PM -0400, Jason Merrill wrote:
> 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!

Thanks for looking into this.  It's kept me occupied for quite a while.
 
> >    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.

This v2 does the propagation much later.
 
> > 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.

This is now handled differently, as described below.
 
> > 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.

This now works correctly by remembering the references until EOF.
 
> I don't see why this is a significant obstacle to doing escalation late.

I've described some of this in the patch description.
 
> > 	* 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.

(This flag was introduced in my earlier patch.)
 
> > 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.

This test is now handled correctly (consteval-prop19.C).
 
> 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?

This has now been resolved in an earlier patch.
 
> > +/* 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?

Added in v2.
 
> > +	  /* [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?).

This code is gone now.
 
> > 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?

I think I can't, we may have to promote c_f_d.
 
> > +  /* 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.

Ah, I threw it in just because.  Now removed.
 
> > +    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.

The whole hash_set is gone; I'm using DECL_ESCALATED_P which I needed
for maybe_explain_promoted_consteval as well.
 
> > +      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.

v2 doesn't do any extra instantiation anymore.
 
> > +	 FIXME Instantiating a defaulted ctor breaks modules (ICE due to
> > +	 cp_function_chain being null).  */
> 
> Sounds like this needs more investigation.

No longer a problem.
 
> 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?

I'm not using instantiate_constexpr_fns in v2 anymore so I didn't touch it.

> > +  /* 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.

True.  This was easy to fix though -- maybe_escalate_decl_and_cfun now saves
the evaluated tree in *evalp.
 
> > +      && (!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?

Yes, done.
 
> > +    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.

Added.
 
> > 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.

Resolved earlier.

> > 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?

Unclear.  I've dropped the DECL_IMMEDIATE_FUNCTION_P check.
 
> >       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.

This test no longer needs any modification.
 
> > 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.

OK.
 
> > +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?

Added.
 
> > +struct C {
> > +  // C::C(int) promoted to consteval.
> > +  consteval C (int) {};
> 
> Looks explicitly declared consteval, not promoted.  :)

Oop, comment removed.
 
> > +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?

Added.
 
> > +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?

Added.  The result is the same, and clang++ agrees.

> > 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.

Resolved earlier.
 
> > 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?

Test added.  In that case we are not supposed to promote the function.
 
> > +  }
> > +  return i;
> > +}
> > +
> > +constexpr int
> > +f8 (auto i)
> > +{
> > +  if not consteval {
> > +    (void) 0;
> > +  } else {
> > +    auto p = &id;
> > +    (void) p;
> 
> And here.

Also added, and we also handle this as expected.

> > 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?

Done.
 
> > 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?

Added.
 
> > +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?

So now we say here
error: the value of 'x' is not usable in a constant expression
 
> > +}
> > +
> > +int i = hh<int>();              // error: hh<int>() is an immediate-escalating expression
> > +                                // outside of an immediate-escalating function
> 
> ...and not on this line?

And here we now say:
error: call to consteval function 'hh<int>()' is not a constant expression
error: 'constexpr T hh() [with T = int]' called in a constant expression

> > +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.

True, and that revealed what I think is a bug in the standard.
In the test I'm saying:

// ??? [expr.const]#example-9 says:
//   k<int> is not an immediate function because A(42) is a
//   constant expression and thus not immediate-escalating
// But I think the call to id(x) is *not* a constant expression and thus
// it is an immediate-escalating expression.  Therefore k<int> *is*
// an immediate function.  So we get the error below.  clang++ agrees.
 
id(x) is not a constant expression because x isn't constant.

> > 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.

This seems to work correctly now.
 
> > +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?

Now we say here:
error: call to consteval function 'f(0)' is not a constant expression
 
> > +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?

No :(.  We don't show that line even when there's no propagation
taking place.
 
> > +
> > +  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.

This now works correctly, tests added.
 
> > +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.

These are all gone in v2.  I'm ecstatic.

So.  I think we want to refrain from instantiating things early
given how many problems that caused.  On the other hand, stashing
all the immediate-escalating decls into immediate_escalating_decls
and walking their bodies isn't going to be cheap.  I've checked
how big the vectors can get, but our testsuite isn't the best litmus
test because it's mostly smallish testcases without many #includes.
The worst offender is uninit-pr105562.C with

(gdb) p immediate_escalating_decls->length()
$2 = 2204
(gdb) p deferred_escalating_exprs->length()
$3 = 501

Compiling uninit-pr105562.C with g++13 and g++14 with this patch:
real 7.51                 real 7.67
user 7.32                 user 7.49
sys 0.15                  sys 0.14

I've made sure not to walk the same bodies twice.  But there's room
for further optimization; I suppose we could escalate instantiated
functions right away rather than putting them into
immediate_escalating_decls and waiting till later.  I'm not certain
if I can just look at DECL_TEMPLATE_INSTANTIATED.  I suppose some
functions cannot possibly be promoted because they don't contain
any CALL_EXPRs.  So we may be able to rule them out while doing
cp_fold_r early.  If a function is trivial, can it ever be promoted?
And so on.

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:

w.C:11:11: error: call to consteval function 'f<int>(i)' is not a constant expression
   11 |         f (i);
      |         ~~^~~
w.C:11:11: error: 'i' is not a constant expression
w.C:6:22: 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); // id causes f<int> to be promoted to consteval
      |                    ~~^~~

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.

In v1, I addressed the delayed instantiation problem by instantiating
trees early, so that we can escalate functions right away.  That caused
a number of problems, and in certain cases, like consteval-prop3.C, it
can't work, because we need to wait till EOF to see the definition of
the function anyway.  Overeager instantiation tends to cause diagnostic
problems too.

In v2, I attempted to move the escalation to the gimplifier, at which
point all templates have been instantiated.  That attempt flopped,
however, because once we've gimplified a function, its body is discarded
and as a consequence, you can no longer evaluate a call to that function
which is required for escalating, which needs to decide if a call is
a constant expression or not.

Therefore, we have to perform the escalation before gimplifying, but
after instantiate_pending_templates.  That's not easy because we have
no way to walk all the trees.  In the v2 patch, I use two vectors: one
to store function decls that may become consteval, and another to
remember references to immediate-escalating functions.  Unfortunately
the latter must also stash functions that call immediate-escalating
functions.  Consider:

  int g(int i)
  {
    f<int>(i); // f is immediate-escalating
  }

where g itself is not immediate-escalating, but we have to make sure
that if f gets promoted to consteval, we give an error.

A new option, -fno-immediate-escalation, is provided to suppress
escalating functions.

v2 also adds a new flag, DECL_ESCALATED_P, so that we don't escalate
a function multiple times, and so that we can distinguish between
explicitly consteval functions and functions that have been promoted
to consteval.

	PR c++/107687
	PR c++/110997

gcc/c-family/ChangeLog:

	* c-cppbuiltin.cc (c_cpp_builtins): Update __cpp_consteval.
	* c.opt (fimmediate-escalation): New option.

gcc/cp/ChangeLog:

	* call.cc (in_immediate_context): No longer static.
	* constexpr.cc (cxx_eval_conditional_expression): Adjust the call to
	cp_fold_immediate.
	* cp-gimplify.cc (immediate_escalating_decls,
	deferred_escalating_exprs): New vecs.
	(enum fold_flags): Add ff_escalating.
	(immediate_escalating_function_p): New.
	(maybe_promote_function_to_consteval): New.
	(maybe_store_cfun_for_late_checking): New.
	(find_escalating_expr_r): New.
	(maybe_explain_promoted_consteval): New.
	(maybe_escalate_decl_and_cfun): New.
	(cp_gimplify_expr) <case CALL_EXPR>: Assert we've handled all
	immediate invocations.
	(taking_address_of_imm_fn_error): New.
	(cp_fold_immediate_r): Merge ADDR_EXPR and PTRMEM_CST cases.  Implement
	P2564 - promoting functions to consteval.
	<case CALL_EXPR>: Implement P2564 - promoting functions to consteval.
	(cp_fold_immediate): New bool parameter, use it.  Return true if we
	emitted errors.
	(maybe_store_immediate_escalating_fn): New.
	(process_pending_immediate_escalating_fns): New.
	(check_immediate_escalating_refs): New.
	* cp-tree.h (struct lang_decl_fn): Add escalated_p bit-field.
	(DECL_ESCALATED_P): New.
	(immediate_invocation_p): Declare.
	(cp_fold_immediate): Adjust declaration.
	(check_immediate_escalating_refs): Declare.
	(maybe_store_immediate_escalating_fn): Declare.
	(process_pending_immediate_escalating_fns): Declare.
	* decl.cc (finish_function): Call maybe_store_immediate_escalating_fn.
	* decl2.cc (c_parse_final_cleanups): Call
	process_pending_immediate_escalating_fns and
	check_immediate_escalating_refs.
	* module.cc (trees_out::lang_decl_bools): New comment.
	(trees_in::lang_decl_bools): New comment.
	* typeck.cc (cp_build_addr_expr_1): Don't check
	DECL_IMMEDIATE_FUNCTION_P.

gcc/ChangeLog:

	* doc/invoke.texi: Document -fno-immediate-escalation.

libstdc++-v3/ChangeLog:

	* testsuite/18_support/comparisons/categories/zero_neg.cc: Add
	dg-prune-output.
	* testsuite/std/format/string_neg.cc: Add dg-error.

gcc/testsuite/ChangeLog:

	* 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/consteval-memfn1.C: Add dg-error.
	* g++.dg/cpp2a/consteval11.C: Likewise.
	* g++.dg/cpp2a/consteval3.C: Adjust dg-error.
	* g++.dg/cpp2a/consteval34.C: Add dg-error.
	* g++.dg/cpp2a/consteval9.C: Likewise.
	* g++.dg/cpp2a/feat-cxx2a.C: Adjust expected value of __cpp_consteval.
	* g++.dg/cpp2a/spaceship-synth9.C: Adjust dg-error.
	* 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-prop18.C: New test.
	* g++.dg/cpp2a/consteval-prop19.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/c-family/c.opt                            |   4 +
 gcc/cp/call.cc                                |   2 +-
 gcc/cp/constexpr.cc                           |   2 +-
 gcc/cp/cp-gimplify.cc                         | 434 ++++++++++++++++--
 gcc/cp/cp-tree.h                              |  17 +-
 gcc/cp/decl.cc                                |   5 +-
 gcc/cp/decl2.cc                               |  10 +-
 gcc/cp/module.cc                              |   4 +
 gcc/cp/typeck.cc                              |   6 +-
 gcc/doc/invoke.texi                           |   8 +
 gcc/testsuite/g++.dg/cpp23/consteval-if10.C   |   7 +-
 gcc/testsuite/g++.dg/cpp23/consteval-if2.C    |  14 +-
 gcc/testsuite/g++.dg/cpp23/feat-cxx2b.C       |   4 +-
 gcc/testsuite/g++.dg/cpp26/feat-cxx26.C       |   4 +-
 gcc/testsuite/g++.dg/cpp2a/consteval-memfn1.C |   3 +
 gcc/testsuite/g++.dg/cpp2a/consteval-prop1.C  | 169 +++++++
 gcc/testsuite/g++.dg/cpp2a/consteval-prop10.C |  41 ++
 gcc/testsuite/g++.dg/cpp2a/consteval-prop11.C |  49 ++
 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 |  78 ++++
 gcc/testsuite/g++.dg/cpp2a/consteval-prop15.C | 107 +++++
 gcc/testsuite/g++.dg/cpp2a/consteval-prop16.C |  73 +++
 gcc/testsuite/g++.dg/cpp2a/consteval-prop17.C |  17 +
 gcc/testsuite/g++.dg/cpp2a/consteval-prop18.C |  20 +
 gcc/testsuite/g++.dg/cpp2a/consteval-prop19.C |   7 +
 gcc/testsuite/g++.dg/cpp2a/consteval-prop2.C  |  88 ++++
 gcc/testsuite/g++.dg/cpp2a/consteval-prop3.C  |  27 ++
 gcc/testsuite/g++.dg/cpp2a/consteval-prop4.C  |  30 ++
 gcc/testsuite/g++.dg/cpp2a/consteval-prop5.C  |  27 ++
 gcc/testsuite/g++.dg/cpp2a/consteval-prop6.C  |  59 +++
 gcc/testsuite/g++.dg/cpp2a/consteval-prop7.C  |  76 +++
 gcc/testsuite/g++.dg/cpp2a/consteval-prop8.C  |  82 ++++
 gcc/testsuite/g++.dg/cpp2a/consteval-prop9.C  |  67 +++
 gcc/testsuite/g++.dg/cpp2a/consteval11.C      |  18 +
 gcc/testsuite/g++.dg/cpp2a/consteval3.C       |   4 +-
 gcc/testsuite/g++.dg/cpp2a/consteval34.C      |   8 +
 gcc/testsuite/g++.dg/cpp2a/consteval9.C       |   2 +
 gcc/testsuite/g++.dg/cpp2a/feat-cxx2a.C       |   4 +-
 gcc/testsuite/g++.dg/cpp2a/spaceship-synth9.C |   2 +-
 .../comparisons/categories/zero_neg.cc        |   1 +
 .../testsuite/std/format/string_neg.cc        |   2 +-
 43 files changed, 1563 insertions(+), 74 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-prop18.C
 create mode 100644 gcc/testsuite/g++.dg/cpp2a/consteval-prop19.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

diff --git a/gcc/c-family/c-cppbuiltin.cc b/gcc/c-family/c-cppbuiltin.cc
index 8904ac85015..3c8eaed7ad3 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/c-family/c.opt b/gcc/c-family/c.opt
index 44b9c862c14..41b20e71005 100644
--- a/gcc/c-family/c.opt
+++ b/gcc/c-family/c.opt
@@ -1874,6 +1874,10 @@ fhuge-objects
 C++ ObjC++ WarnRemoved
 No longer supported.
 
+fimmediate-escalation
+C++ ObjC++ Var(flag_immediate_escalation) Init(1)
+Implement P2564 for consteval propagation.
+
 fimplement-inlines
 C++ ObjC++ Var(flag_implement_inlines) Init(1)
 Export functions even if they can be inlined.
diff --git a/gcc/cp/call.cc b/gcc/cp/call.cc
index e8dafbd8ba6..4101857da11 100644
--- a/gcc/cp/call.cc
+++ b/gcc/cp/call.cc
@@ -9726,7 +9726,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
diff --git a/gcc/cp/constexpr.cc b/gcc/cp/constexpr.cc
index 0f948db7c2d..a6ddaebce48 100644
--- a/gcc/cp/constexpr.cc
+++ b/gcc/cp/constexpr.cc
@@ -3854,7 +3854,7 @@ cxx_eval_conditional_expression (const constexpr_ctx *ctx, tree t,
   if (ctx->manifestly_const_eval != mce_true
       && !in_immediate_context ()
       && cp_fold_immediate (&TREE_OPERAND (t, zero_p ? 1 : 2),
-			    ctx->manifestly_const_eval))
+			    ctx->manifestly_const_eval, /*escalate_p=*/false))
     {
       *non_constant_p = true;
       return t;
diff --git a/gcc/cp/cp-gimplify.cc b/gcc/cp/cp-gimplify.cc
index bdf6e5f98ff..deda09a805b 100644
--- a/gcc/cp/cp-gimplify.cc
+++ b/gcc/cp/cp-gimplify.cc
@@ -43,6 +43,15 @@ along with GCC; see the file COPYING3.  If not see
 #include "omp-general.h"
 #include "opts.h"
 
+/* A vec of FUNCTION_DECLs.  They are immediate-escalating functions and may
+   become consteval */
+static GTY(()) vec<tree, va_gc> *immediate_escalating_decls;
+/* Keep track of forward references to immediate-escalating functions in
+   case they become consteval.  This vector contains ADDR_EXPRs and
+   PTRMEM_CSTs; it also stores FUNCTION_DECLs that had an escalating
+   function call in them, to check that they can be evaluated to a constant.  */
+static GTY(()) vec<tree, va_gc> *deferred_escalating_exprs;
+
 /* Flags for cp_fold and cp_fold_r.  */
 
 enum fold_flags {
@@ -55,6 +64,8 @@ enum fold_flags {
   ff_mce_false = 1 << 1,
   /* Whether we're being called from cp_fold_immediate.  */
   ff_fold_immediate = 1 << 2,
+  /* Whether we're escalating immediate-escalating functions.  */
+  ff_escalating = 1 << 3,
 };
 
 using fold_flags_t = int;
@@ -428,6 +439,176 @@ lvalue_has_side_effects (tree e)
     return TREE_SIDE_EFFECTS (e);
 }
 
+/* Return true if FN is an immediate-escalating function.  */
+
+static bool
+immediate_escalating_function_p (tree fn)
+{
+  if (!fn || !flag_immediate_escalation)
+    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.  */
+
+static 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);
+      DECL_ESCALATED_P (fn) = true;
+      tree clone;
+      FOR_EACH_CLONE (clone, fn)
+	{
+	  SET_DECL_IMMEDIATE_FUNCTION_P (clone);
+	  DECL_ESCALATED_P (clone) = true;
+	}
+      return true;
+    }
+
+  return false;
+}
+
+/* Remember that the current function declaration contains a call to
+   a function that might be promoted to consteval later.  */
+
+static void
+maybe_store_cfun_for_late_checking ()
+{
+  if (!current_function_decl
+      || !flag_immediate_escalation
+      || immediate_escalating_function_p (current_function_decl))
+    return;
+
+  if (deferred_escalating_exprs
+      /* Don't put duplicates into the vec so that we don't walk
+	 a function multiple times.  Most likely the last one
+	 added will be the same.  */
+      && (deferred_escalating_exprs->last () == current_function_decl
+	  || deferred_escalating_exprs->contains (current_function_decl)))
+    return;
+
+  vec_safe_push (deferred_escalating_exprs, current_function_decl);
+}
+
+/* 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)
+{
+  /* The function whose body we're traversing.  Used to promote the current
+     function to consteval.  */
+  tree caller = data ? *static_cast<tree *>(data) : NULL_TREE;
+  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)
+    goto bail;
+
+  /* Not consteval yet, but could be.  Have to look deeper.  */
+  if (!DECL_IMMEDIATE_FUNCTION_P (decl)
+      && immediate_escalating_function_p (decl)
+      && !DECL_ESCALATED_P (decl))
+    {
+      /* Set before the actual walk to avoid endless recursion.  */
+      DECL_ESCALATED_P (decl) = true;
+      cp_walk_tree (&DECL_SAVED_TREE (decl), find_escalating_expr_r,
+		    caller ? &decl : nullptr, 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 (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)
+{
+  if (DECL_ESCALATED_P (fn))
+    {
+      /* See if we can figure out what made the function consteval.  */
+      tree x = cp_walk_tree (&DECL_SAVED_TREE (fn), find_escalating_expr_r,
+			     NULL_TREE, 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.  */
 
@@ -485,6 +666,41 @@ cp_gimplify_arg (tree *arg_p, gimple_seq *pre_p, location_t call_location,
 
 }
 
+/* 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.
+   EVALP is where we may store the result of cxx_constant_value so that we
+   don't have to evaluate the same tree again in cp_fold_immediate_r.  */
+
+static void
+maybe_escalate_decl_and_cfun (tree decl, tree call, tree *evalp)
+{
+  /* Compiler-generated functions don't seem like good candidates for
+     promoting.  */
+  if (cp_unevaluated_operand || DECL_ARTIFICIAL (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)
+      && !DECL_ESCALATED_P (decl))
+    cp_walk_tree (&DECL_SAVED_TREE (decl), find_escalating_expr_r,
+		  &decl, 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.  */
+      && (*evalp = cxx_constant_value (call, tf_none),
+	  *evalp == error_mark_node))
+    maybe_promote_function_to_consteval (current_function_decl);
+}
+
 /* Do C++-specific gimplification.  Args are as for gimplify_expr.  */
 
 int
@@ -746,7 +962,9 @@ cp_gimplify_expr (tree *expr_p, gimple_seq *pre_p, gimple_seq *post_p)
       if (ret != GS_ERROR)
 	{
 	  tree decl = cp_get_callee_fndecl_nofold (*expr_p);
-	  if (decl && fndecl_built_in_p (decl, BUILT_IN_FRONTEND))
+	  if (!decl)
+	    break;
+	  if (fndecl_built_in_p (decl, BUILT_IN_FRONTEND))
 	    switch (DECL_FE_FUNCTION_CODE (decl))
 	      {
 	      case CP_BUILT_IN_IS_CONSTANT_EVALUATED:
@@ -771,6 +989,9 @@ cp_gimplify_expr (tree *expr_p, gimple_seq *pre_p, gimple_seq *post_p)
 	      default:
 		break;
 	      }
+	  else
+	    /* All consteval functions should have been processed by now.  */
+	    gcc_checking_assert (!immediate_invocation_p (decl));
 	}
       break;
 
@@ -1031,6 +1252,20 @@ struct cp_genericize_data
   bool handle_invisiref_parm_p;
 };
 
+/* Emit an error about taking the address of an immediate function.
+   EXPR is the whole expression; DECL is the immediate function.  */
+
+static void
+taking_address_of_imm_fn_error (tree expr, tree decl)
+{
+  auto_diagnostic_group d;
+  const location_t loc = (TREE_CODE (expr) == PTRMEM_CST
+			  ? PTRMEM_CST_LOCATION (expr)
+			  : EXPR_LOCATION (expr));
+  error_at (loc, "taking address of an immediate function %qD", decl);
+  maybe_explain_promoted_consteval (loc, decl);
+}
+
 /* A subroutine of cp_fold_r to handle immediate functions.  */
 
 static tree
@@ -1041,16 +1276,17 @@ cp_fold_immediate_r (tree *stmt_p, int *walk_subtrees, void *data_)
   /* The purpose of this is not to emit errors for mce_unknown.  */
   const tsubst_flags_t complain = (data->flags & ff_mce_false
 				   ? tf_error : tf_none);
+  const tree_code code = TREE_CODE (stmt);
 
   /* No need to look into types or unevaluated operands.
      NB: This affects cp_fold_r as well.  */
-  if (TYPE_P (stmt) || unevaluated_p (TREE_CODE (stmt)))
+  if (TYPE_P (stmt) || unevaluated_p (code))
     {
       *walk_subtrees = 0;
       return NULL_TREE;
     }
 
-  switch (TREE_CODE (stmt))
+  switch (code)
     {
     /* Unfortunately we must handle code like
 	 false ? bar () : 42
@@ -1073,20 +1309,42 @@ cp_fold_immediate_r (tree *stmt_p, int *walk_subtrees, void *data_)
 	 from cp_fold_r and we must let it recurse on the expression with
 	 cp_fold.  */
       break;
+
     case PTRMEM_CST:
-      if (TREE_CODE (PTRMEM_CST_MEMBER (stmt)) == FUNCTION_DECL
-	  && DECL_IMMEDIATE_FUNCTION_P (PTRMEM_CST_MEMBER (stmt)))
-	{
-	  if (!data->pset.add (stmt) && (complain & tf_error))
-	    {
-	      error_at (PTRMEM_CST_LOCATION (stmt),
-			"taking address of an immediate function %qD",
-			PTRMEM_CST_MEMBER (stmt));
-	      *stmt_p = build_zero_cst (TREE_TYPE (stmt));
-	    }
-	  return error_mark_node;
-	}
-      break;
+    case ADDR_EXPR:
+      {
+	tree decl = (code == PTRMEM_CST
+		     ? PTRMEM_CST_MEMBER (stmt)
+		     : TREE_OPERAND (stmt, 0));
+	if (TREE_CODE (decl) != FUNCTION_DECL)
+	  break;
+	if (code == ADDR_EXPR && ADDR_EXPR_DENOTES_CALL_P (stmt))
+	  break;
+	if (immediate_invocation_p (decl))
+	  {
+	    if (maybe_promote_function_to_consteval (current_function_decl))
+	      break;
+	    if (complain & tf_error)
+	      {
+		/* ??? Why don't we use data->pset for ADDR_EXPR too?  */
+		if (code == ADDR_EXPR || !data->pset.add (stmt))
+		  {
+		    taking_address_of_imm_fn_error (stmt, decl);
+		    *stmt_p = build_zero_cst (TREE_TYPE (stmt));
+		  }
+		/* If we're giving hard errors, continue the walk rather than
+		   bailing out after the first error.  */
+		break;
+	      }
+	    return error_mark_node;
+	  }
+	/* Not consteval yet, but could become one, in which case it's invalid
+	   to take its address.  */
+	else if (!(data->flags & ff_fold_immediate)
+		 && immediate_escalating_function_p (decl))
+	  vec_safe_push (deferred_escalating_exprs, stmt);
+	break;
+      }
 
     /* Expand immediate invocations.  */
     case CALL_EXPR:
@@ -1094,33 +1352,54 @@ cp_fold_immediate_r (tree *stmt_p, int *walk_subtrees, void *data_)
       if (tree fn = cp_get_callee (stmt))
 	if (TREE_CODE (fn) != ADDR_EXPR || ADDR_EXPR_DENOTES_CALL_P (fn))
 	  if (tree fndecl = cp_get_fndecl_from_callee (fn, /*fold*/false))
-	    if (DECL_IMMEDIATE_FUNCTION_P (fndecl))
-	      {
-		stmt = cxx_constant_value (stmt, complain);
-		if (stmt == error_mark_node)
-		  {
-		    if (complain & tf_error)
-		      *stmt_p = error_mark_node;
-		    return error_mark_node;
-		  }
-		*stmt_p = stmt;
-	      }
-      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))
-	{
-	  if (complain & tf_error)
 	    {
-	      error_at (EXPR_LOCATION (stmt),
-			"taking address of an immediate function %qD",
-			TREE_OPERAND (stmt, 0));
-	      *stmt_p = build_zero_cst (TREE_TYPE (stmt));
+	      tree eval = NULL_TREE;
+	      if (data->flags & ff_escalating)
+		maybe_escalate_decl_and_cfun (fndecl, stmt, &eval);
+
+	      /* [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 STMT does not need to produce
+		 a constant expression.  */
+	      if (immediate_invocation_p (fndecl))
+		{
+		  tree e = eval ? eval : cxx_constant_value (stmt, tf_none);
+		  if (e == error_mark_node)
+		    {
+		      if (maybe_promote_function_to_consteval
+			  (current_function_decl))
+			break;
+		      if (complain & tf_error)
+			{
+			  auto_diagnostic_group d;
+			  location_t loc = cp_expr_loc_or_input_loc (stmt);
+			  error_at (loc, "call to consteval function %qE is "
+				    "not a constant expression", stmt);
+			  /* Explain why it's not a constant expression.  */
+			  *stmt_p = cxx_constant_value (stmt, complain);
+			  maybe_explain_promoted_consteval (loc, fndecl);
+			  /* Don't return error_mark_node, it would stop our
+			     tree walk.  */
+			  break;
+			}
+		      return error_mark_node;
+		    }
+		  /* We've evaluated the consteval function call.  */
+		  *stmt_p = e;
+		}
+	      /* We've encountered a function call that may turn out to be
+		 consteval later.  Store its caller so that we can ensure
+		 that the call is a constant expression.  */
+	      else if (!(data->flags & ff_fold_immediate)
+		       && immediate_escalating_function_p (fndecl))
+		maybe_store_cfun_for_late_checking ();
 	    }
-	  return error_mark_node;
-	}
       break;
 
     default:
@@ -1132,10 +1411,11 @@ cp_fold_immediate_r (tree *stmt_p, int *walk_subtrees, void *data_)
 
 /* A wrapper around cp_fold_immediate_r.  Return true if we found
    a non-constant immediate function, or taking the address of an
-   immediate function.  */
+   immediate function.  If ESCALATE_P, tell cp_fold_immediate_r to
+   escalate immediate-escalating functions.  */
 
 bool
-cp_fold_immediate (tree *tp, mce_value manifestly_const_eval)
+cp_fold_immediate (tree *tp, mce_value manifestly_const_eval, bool escalate_p)
 {
   if (cxx_dialect <= cxx17)
     return false;
@@ -1143,9 +1423,13 @@ cp_fold_immediate (tree *tp, mce_value manifestly_const_eval)
   fold_flags_t flags = ff_fold_immediate;
   if (manifestly_const_eval == mce_false)
     flags |= ff_mce_false;
+  if (escalate_p)
+    flags |= ff_escalating;
 
   cp_fold_data data (flags);
-  return !!cp_walk_tree_without_duplicates (tp, cp_fold_immediate_r, &data);
+  int save_errorcount = errorcount;
+  tree r = cp_walk_tree_without_duplicates (tp, cp_fold_immediate_r, &data);
+  return r != NULL_TREE || errorcount > save_errorcount;
 }
 
 /* Perform any pre-gimplification folding of C++ front end trees to
@@ -1290,6 +1574,68 @@ cp_fold_function (tree fndecl)
   cp_walk_tree (&DECL_SAVED_TREE (fndecl), cp_fold_r, &data, NULL);
 }
 
+/* FN is not a consteval function, but may become one.  Remember to
+   escalate it after all pending templates have been instantiated.  */
+
+void
+maybe_store_immediate_escalating_fn (tree fn)
+{
+  if (cxx_dialect >= cxx20
+      && flag_immediate_escalation
+      && !DECL_IMMEDIATE_FUNCTION_P (fn)
+      && immediate_escalating_function_p (fn))
+    vec_safe_push (immediate_escalating_decls, fn);
+}
+
+/* We've stashed immediate-escalating functions.  Now see if they indeed
+   ought to be promoted to consteval.  */
+
+void
+process_pending_immediate_escalating_fns ()
+{
+  /* This will be null for -fno-immediate-escalation.  */
+  if (!immediate_escalating_decls)
+    return;
+
+  while (!immediate_escalating_decls->is_empty ())
+    {
+      tree fndecl = immediate_escalating_decls->pop ();
+      temp_override<tree> cfd (current_function_decl, fndecl);
+      cp_fold_immediate (&DECL_SAVED_TREE (fndecl), mce_false,
+			 /*escalate_p=*/true);
+    }
+}
+
+/* We've escalated every function that could have been promoted to
+   consteval.  Check that we are not taking the address of a consteval
+   function.  */
+
+void
+check_immediate_escalating_refs ()
+{
+  if (!deferred_escalating_exprs)
+    return;
+
+  while (!deferred_escalating_exprs->is_empty ())
+    {
+      tree ref = deferred_escalating_exprs->pop ();
+      if (TREE_CODE (ref) == FUNCTION_DECL)
+	/* We saw a function call to an immediate-escalating function in
+	   the body of REF.  Check that it's a constant if it was promoted
+	   to consteval.  */
+	cp_fold_immediate (&DECL_SAVED_TREE (ref), mce_false,
+			   /*escalate_p=*/false);
+      else
+	{
+	  tree decl = (TREE_CODE (ref) == PTRMEM_CST
+		       ? PTRMEM_CST_MEMBER (ref)
+		       : TREE_OPERAND (ref, 0));
+	  if (DECL_IMMEDIATE_FUNCTION_P (decl))
+	    taking_address_of_imm_fn_error (ref, decl);
+	}
+    }
+}
+
 /* Turn SPACESHIP_EXPR EXPR into GENERIC.  */
 
 static tree genericize_spaceship (tree expr)
diff --git a/gcc/cp/cp-tree.h b/gcc/cp/cp-tree.h
index 6e34952da99..f866f259f4e 100644
--- a/gcc/cp/cp-tree.h
+++ b/gcc/cp/cp-tree.h
@@ -2938,8 +2938,9 @@ struct GTY(()) lang_decl_fn {
   unsigned maybe_deleted : 1;
   unsigned coroutine_p : 1;
   unsigned implicit_constexpr : 1;
+  unsigned escalated_p : 1;
 
-  unsigned spare : 9;
+  unsigned spare : 8;
 
   /* 32-bits padding on 64-bit host.  */
 
@@ -3391,6 +3392,14 @@ struct GTY(()) lang_decl {
 #define DECL_MAYBE_DELETED(NODE) \
   (LANG_DECL_FN_CHECK (NODE)->maybe_deleted)
 
+/* Nonzero for FUNCTION_DECL means that this function's body has been
+   checked for immediate-escalating expressions and maybe promoted.  It
+   does *not* mean the function is consteval.  It must not be set in
+   a function that was marked consteval by the user, so that we can
+   distinguish between explicitly consteval functions and promoted consteval
+   functions.  */
+#define DECL_ESCALATED_P(NODE) (LANG_DECL_FN_CHECK (NODE)->escalated_p)
+
 /* True (in a FUNCTION_DECL) if NODE is a virtual function that is an
    invalid overrider for a function from a base class.  Once we have
    complained about an invalid overrider we avoid complaining about it
@@ -6736,6 +6745,7 @@ 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 tree build_cxx_call			(tree, int, tree *,
 						 tsubst_flags_t,
 						 tree = NULL_TREE);
@@ -8379,7 +8389,10 @@ extern tree process_stmt_assume_attribute	(tree, tree, location_t);
 extern bool simple_empty_class_p		(tree, tree, tree_code);
 extern tree fold_builtin_source_location	(const_tree);
 extern tree get_source_location_impl_type	();
-extern bool cp_fold_immediate			(tree *, mce_value);
+extern bool cp_fold_immediate			(tree *, mce_value, bool);
+extern void check_immediate_escalating_refs	();
+extern void maybe_store_immediate_escalating_fn	(tree);
+extern void process_pending_immediate_escalating_fns ();
 
 /* in name-lookup.cc */
 extern tree strip_using_decl                    (tree);
diff --git a/gcc/cp/decl.cc b/gcc/cp/decl.cc
index 255c4026bdb..5480e4167ab 100644
--- a/gcc/cp/decl.cc
+++ b/gcc/cp/decl.cc
@@ -18342,7 +18342,10 @@ finish_function (bool inline_p)
   if (!processing_template_decl
       && !DECL_IMMEDIATE_FUNCTION_P (fndecl)
       && !DECL_OMP_DECLARE_REDUCTION_P (fndecl))
-    cp_fold_function (fndecl);
+    {
+      cp_fold_function (fndecl);
+      maybe_store_immediate_escalating_fn (fndecl);
+    }
 
   /* Set up the named return value optimization, if we can.  Candidate
      variables are selected in check_return_expr.  */
diff --git a/gcc/cp/decl2.cc b/gcc/cp/decl2.cc
index 344e19ec98b..a67bdb4db6f 100644
--- a/gcc/cp/decl2.cc
+++ b/gcc/cp/decl2.cc
@@ -5294,7 +5294,15 @@ c_parse_final_cleanups (void)
   if (static_init_fini_fns[true])
     for (auto iter : *static_init_fini_fns[true])
       iter.second = nreverse (iter.second);
-  
+
+  /* Now we've instantiated all templates.  Now we can escalate the functions
+     we squirreled away earlier.  */
+  if (cxx_dialect >= cxx20)
+    {
+      process_pending_immediate_escalating_fns ();
+      check_immediate_escalating_refs ();
+    }
+
   /* Then, do the Objective-C stuff.  This is where all the
      Objective-C module stuff gets generated (symtab,
      class/protocol/selector lists etc).  This must be done after C++
diff --git a/gcc/cp/module.cc b/gcc/cp/module.cc
index e3fb2299d93..02c50ac6a11 100644
--- a/gcc/cp/module.cc
+++ b/gcc/cp/module.cc
@@ -5685,6 +5685,8 @@ trees_out::lang_decl_bools (tree t)
       WB (lang->u.fn.has_dependent_explicit_spec_p);
       WB (lang->u.fn.immediate_fn_p);
       WB (lang->u.fn.maybe_deleted);
+      /* We do not stream lang->u.implicit_constexpr nor
+	 lang->u.escalated_p.  */
       goto lds_min;
 
     case lds_decomp:  /* lang_decl_decomp.  */
@@ -5753,6 +5755,8 @@ trees_in::lang_decl_bools (tree t)
       RB (lang->u.fn.has_dependent_explicit_spec_p);
       RB (lang->u.fn.immediate_fn_p);
       RB (lang->u.fn.maybe_deleted);
+      /* We do not stream lang->u.implicit_constexpr nor
+	 lang->u.escalated_p.  */
       goto lds_min;
 
     case lds_decomp:  /* lang_decl_decomp.  */
diff --git a/gcc/cp/typeck.cc b/gcc/cp/typeck.cc
index 8132bd7fccc..71e6db896af 100644
--- a/gcc/cp/typeck.cc
+++ b/gcc/cp/typeck.cc
@@ -7244,11 +7244,9 @@ cp_build_addr_expr_1 (tree arg, bool strict_lvalue, tsubst_flags_t complain)
 			      complain);
     }
 
-  /* For addresses of immediate functions ensure we have EXPR_LOCATION
-     set for possible later diagnostics.  */
+  /* Ensure we have EXPR_LOCATION 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)))
+      && TREE_CODE (TREE_OPERAND (val, 0)) == FUNCTION_DECL)
     SET_EXPR_LOCATION (val, input_location);
 
   return val;
diff --git a/gcc/doc/invoke.texi b/gcc/doc/invoke.texi
index 7c5f81d9783..7aa5d620da2 100644
--- a/gcc/doc/invoke.texi
+++ b/gcc/doc/invoke.texi
@@ -219,6 +219,7 @@ in the following sections.
 -fno-elide-constructors
 -fno-enforce-eh-specs
 -fno-gnu-keywords
+-fno-immediate-escalation
 -fno-implicit-templates
 -fno-implicit-inline-templates
 -fno-implement-inlines
@@ -3364,6 +3365,13 @@ word as an identifier.  You can use the keyword @code{__typeof__} instead.
 This option is implied by the strict ISO C++ dialects: @option{-ansi},
 @option{-std=c++98}, @option{-std=c++11}, etc.
 
+@opindex fno-immediate-escalation
+@opindex fimmediate-escalation
+@item -fno-immediate-escalation
+Do not enable immediate function escalation whereby certain functions
+can be promoted to consteval, as specified in P2564R3.  This option is
+turned on by default; it is only effective in C++20 mode or later.
+
 @opindex fimplicit-constexpr
 @item -fimplicit-constexpr
 Make inline functions implicitly constexpr, if they satisfy the
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 b2c5472b7de..3b258711ce6 100644
--- a/gcc/testsuite/g++.dg/cpp23/consteval-if2.C
+++ b/gcc/testsuite/g++.dg/cpp23/consteval-if2.C
@@ -33,7 +33,7 @@ baz (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);	// { dg-error "not a constant expression" }
     }
   else
     {
@@ -45,11 +45,11 @@ baz (int x)
     }
   else
     {
-      r += foo (8 * x);	// { dg-error "'x' is not a constant expression" }
+      r += foo (8 * x);	// { dg-error "is not a constant expression" }
     }
   if ! consteval	// { dg-warning "'if consteval' only available with" "" { target c++20_only } }
     {
-      r += foo (32 * x);// { dg-error "'x' is not a constant expression" }
+      r += foo (32 * x);// { dg-error "not a constant expression" }
     }
   if consteval		// { dg-warning "'if consteval' only available with" "" { target c++20_only } }
     {
@@ -98,7 +98,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
     {
@@ -110,11 +110,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 } }
     {
@@ -126,5 +126,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-memfn1.C b/gcc/testsuite/g++.dg/cpp2a/consteval-memfn1.C
index 63f4f1d526a..edf8c4a139d 100644
--- a/gcc/testsuite/g++.dg/cpp2a/consteval-memfn1.C
+++ b/gcc/testsuite/g++.dg/cpp2a/consteval-memfn1.C
@@ -19,10 +19,13 @@ template<class>
 void VerifyHash(fixed_string s) {
   s.size(0); // { dg-bogus "" }
   s.size(-1); // { dg-message "expansion of" }
+// { dg-error "call to consteval function" "" { target *-*-* } .-1 }
   s.size_static(0); // { dg-bogus "" }
   s.size_static(-1); // { dg-message "expansion of" }
+// { dg-error "call to consteval function" "" { target *-*-* } .-1 }
   fixed_string::size_static(0); // { dg-bogus "" }
   fixed_string::size_static(-1); // { dg-message "expansion of" }
+// { dg-error "call to consteval function" "" { target *-*-* } .-1 }
   s(); // { dg-bogus "" }
 }
 
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..5e7b208113f
--- /dev/null
+++ b/gcc/testsuite/g++.dg/cpp2a/consteval-prop1.C
@@ -0,0 +1,169 @@
+// 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); // { dg-message "immediate-escalating expression .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); // { dg-message "immediate-escalating expression .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; // { dg-message "immediate-escalating expression .id." }
+  (void) p;
+  return t;
+}
+
+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};
+}
+
+constexpr void
+f5_nt (int i)
+{
+  A a{i}; // { dg-error "call to consteval function|not a constant" }
+}
+
+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; // { dg-message ".int g. is not const" }
+
+B b2(f0<int>(cid<int>(g))); // { dg-error "call to consteval function|not usable" }
+
+struct C {
+  consteval C (int) {};
+};
+
+constexpr int
+f7 (auto t)
+{
+  C c(t); // { dg-message "immediate-escalating expression .c.C::C\\(t\\)." }
+  return 0;
+}
+
+int i1 = f7 (g); // { dg-error "call to consteval function|not usable" }
+
+struct Y {
+  int y;
+  int x = id (y);
+  consteval Y (int i) : y (id (i)) {}
+};
+
+Y y1(1);
+Y y2(g); // { dg-error "call to consteval function|not usable" }
+
+struct Y2 {
+  int y;
+  int x = id (y);
+  constexpr Y2 (auto i) : y (id (i)) {}
+};
+
+Y2 y3(1);
+Y2 y4(g); // { dg-error "call to consteval function|not usable" }
+
+auto l1 = [](int i) constexpr {
+  int t = id (i);
+  return id (0);
+};
+
+int (*pl1)(int) = l1; // { dg-error "call to consteval function|returns address of immediate function" }
+
+auto l2 = [](int i) {
+  int t = id (i);
+  return id (0);
+};
+
+int (*pl2)(int) = l2; // { dg-error "call to consteval function|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 "used before its definition" }
+  int a = [] (int u = undef ()) {
+    return u;
+  }();
+} s2; // { dg-error "call to consteval function" }
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..4e33e6e3d0e
--- /dev/null
+++ b/gcc/testsuite/g++.dg/cpp2a/consteval-prop10.C
@@ -0,0 +1,41 @@
+// 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; // { dg-message ".int non_const. is not const" }
+
+template<typename>
+constexpr int
+f2 (int i = id (non_const))
+{
+  return i;
+}
+
+constexpr int
+f3 (auto)
+{
+  return f2<int>(); // { dg-message "contains an immediate-escalating expression .id\\(non_const\\)." }
+}
+
+auto a = &f3<int>; // { dg-error "taking address of an immediate function" }
+
+void
+g (int i)
+{
+  f1<int> (42);
+  f1<int> (i);
+  f1<int> ();
+  f2<int> (42);
+  f2<int> (i);
+  f2<int> (); // { dg-error "call to consteval function .id\\(non_const\\). is not a constant expression" }
+// { dg-error ".non_const. is not usable in a constant expression" "" { target *-*-* } .-1 }
+}
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..aca9675cd53
--- /dev/null
+++ b/gcc/testsuite/g++.dg/cpp2a/consteval-prop11.C
@@ -0,0 +1,49 @@
+// 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
+foobar (auto i)
+{
+  return i + id (i);
+  /* { dg-begin-multiline-output "" }
+   return i + id (i);
+              ~~~^~~
+     { dg-end-multiline-output "" } */
+}
+
+void
+g (int x)
+{
+  foobar (x); // { dg-error "10:call to consteval function .foobar<int>\\(x\\). is not a constant expression" }
+// { dg-error ".x. is not a constant expression" "" { target *-*-* } .-1 }
+  /* { dg-begin-multiline-output "" }
+foobar (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|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..cdc1f6dc862
--- /dev/null
+++ b/gcc/testsuite/g++.dg/cpp2a/consteval-prop14.C
@@ -0,0 +1,78 @@
+// 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\\(\\(i \\* 2\\)\\)." }
+}
+
+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" }
+// { dg-error ".i. is not a constant expression" "" { target *-*-* } .-1 }
+  f1 (42);
+  f2 (i); // { dg-error "call to consteval function .f2<int>\\(i\\). is not a constant expression" }
+// { dg-error ".i. is not a constant expression" "" { target *-*-* } .-1 }
+  f2 (42);
+  f3 (i); // { dg-error "call to consteval function .f3<int>\\(i\\). is not a constant expression" }
+// { dg-error ".i. is not a constant expression" "" { target *-*-* } .-1 }
+  f3 (42);
+  f4 (i); // { dg-error "call to consteval function .f4<int>\\(i\\). is not a constant expression" }
+// { dg-error ".i. is not a constant expression" "" { target *-*-* } .-1 }
+  f4 (42);
+  f5 (i); // { dg-error "call to consteval function .f5<int>\\(i\\). is not a constant expression" }
+// { dg-error ".i. is not a constant expression" "" { target *-*-* } .-1 }
+  f5 (42);
+  f6 (i); // { dg-error "call to consteval function .f6<int>\\(i\\). is not a constant expression" }
+// { dg-error ".i. is not a constant expression" "" { target *-*-* } .-1 }
+  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..3341c510a9f
--- /dev/null
+++ b/gcc/testsuite/g++.dg/cpp2a/consteval-prop15.C
@@ -0,0 +1,107 @@
+// 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;
+}
+
+constexpr int
+f9 (auto i)
+{
+  if consteval {
+    return id(i);
+  }
+  return i;
+}
+
+constexpr int
+f10 (auto i)
+{
+  if not consteval {
+    (void) 0;
+  } else {
+    return id(i);
+  }
+  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" }
+// { dg-error ".non_const. is not a constant expression" "" { target *-*-* } .-1 }
+  f5 (42);
+  f5 (non_const); // { dg-error "call to consteval function .f5<int>\\(non_const\\). is not a constant expression" }
+// { dg-error ".non_const. is not a constant expression" "" { target *-*-* } .-1 }
+  f6 (42);
+  f6 (non_const);
+  f7 (42);
+  f7 (non_const);
+  f8 (42);
+  f8 (non_const);
+  f9 (42);
+  f9 (non_const);
+  f10 (42);
+  f10 (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..47ec9b60b6c
--- /dev/null
+++ b/gcc/testsuite/g++.dg/cpp2a/consteval-prop17.C
@@ -0,0 +1,17 @@
+// P2564R3
+// { dg-do compile { target c++20 } }
+// { dg-options "-fno-immediate-escalation" }
+
+consteval int id(int i) { return i; }
+
+constexpr int
+f (auto i)
+{
+  return id (i); // { dg-error "not a constant expression" }
+}
+
+int
+g ()
+{
+  return f (42);
+}
diff --git a/gcc/testsuite/g++.dg/cpp2a/consteval-prop18.C b/gcc/testsuite/g++.dg/cpp2a/consteval-prop18.C
new file mode 100644
index 00000000000..a18106f8e0f
--- /dev/null
+++ b/gcc/testsuite/g++.dg/cpp2a/consteval-prop18.C
@@ -0,0 +1,20 @@
+// P2564R3
+// { dg-do compile { target c++20 } }
+
+consteval int id(int i) { return i; }
+
+constexpr int
+f (auto t)
+{
+  return t + id (t);
+}
+
+constexpr int
+f2 (auto t)
+{
+  return t + f(t); // { dg-message "immediate-escalating expression .f<int>\\(t\\)." }
+}
+
+int z; // { dg-message "not const" }
+auto y1 = f2 (42);
+auto y2 = f2 (z); // { dg-error "value of .z. is not usable in a constant expression|call to consteval function" }
diff --git a/gcc/testsuite/g++.dg/cpp2a/consteval-prop19.C b/gcc/testsuite/g++.dg/cpp2a/consteval-prop19.C
new file mode 100644
index 00000000000..3ceb05e41f4
--- /dev/null
+++ b/gcc/testsuite/g++.dg/cpp2a/consteval-prop19.C
@@ -0,0 +1,7 @@
+// P2564R3
+// { dg-do compile { target c++20 } }
+
+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;  // { dg-error "call to consteval function|returns address of immediate function" }
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..eddc45b54b8
--- /dev/null
+++ b/gcc/testsuite/g++.dg/cpp2a/consteval-prop2.C
@@ -0,0 +1,88 @@
+// 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);		// { dg-message "immediate-escalating expression .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 "the value of .x. is not usable in a constant expression" }
+// { dg-message "immediate-escalating expression .id\\(x\\)." "" { target *-*-* } .-1 }
+}
+
+int i = hh<int>();              // { dg-error "call to consteval function|called in a constant expression" }
+				// error: hh<int>() is an immediate-escalating expression
+                                // outside of an immediate-escalating function
+struct A {
+  int x;
+  int y = id(x);
+};
+
+// ??? [expr.const]#example-9 says:
+//   k<int> is not an immediate function because A(42) is a
+//   constant expression and thus not immediate-escalating
+// But I think the call to id(x) is *not* a constant expression and thus
+// it is an immediate-escalating expression.  Therefore k<int> *is*
+// an immediate function.  So we get the error below.  clang++ agrees.
+template<class T>
+constexpr int k(int) {
+  return A(42).y;
+}
+
+int
+test (int i)
+{
+  int r = g (42) + g(i);
+  int t = k<int>(42)
+	    + k<int>(i); // { dg-error "call to consteval function|not a constant" }
+  return r + t;
+}
+
+// Just like above, but make the call to id(x) actually a constant.
+struct A2 {
+  static constexpr int x = 42;
+  int y = id(x);
+};
+
+template<class T>
+constexpr int k2(int) {
+  return A2(42).y;
+}
+
+int
+test2 (int i)
+{
+  return k2<int>(42) + k2<int>(i);
+}
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..f181cb32942
--- /dev/null
+++ b/gcc/testsuite/g++.dg/cpp2a/consteval-prop3.C
@@ -0,0 +1,27 @@
+// 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);
+
+auto a2 = &f2<char>; // { dg-error "taking address" }
+auto b2 = &f2<int>; // { dg-error "taking address" }
+
+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..3a2e09b17b0
--- /dev/null
+++ b/gcc/testsuite/g++.dg/cpp2a/consteval-prop4.C
@@ -0,0 +1,30 @@
+// P2564R3
+// { dg-do compile { target c++20 } }
+// 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..93ed398d9bf
--- /dev/null
+++ b/gcc/testsuite/g++.dg/cpp2a/consteval-prop6.C
@@ -0,0 +1,59 @@
+// 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(){} // { dg-error "call to consteval function" }
+
+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 "modification of .x. is not a constant expression" }
+};
+
+void
+test ()
+{
+  [[maybe_unused]] X x; // { dg-error "call to consteval function" }
+// { dg-message "promoted to an immediate function" "" { target *-*-* } .-1 }
+}
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..118cf576f14
--- /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 "call to consteval function|not usable" }
+constexpr auto a2 = f2 (non_const); // { dg-error "not a constant|not usable" }
+auto a3 = f3 (42);
+constexpr auto a4 = f4 (42);
+
+void
+g ()
+{
+   auto a5 = f5 (non_const); // { dg-error "not a constant|not usable" }
+   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|not usable" }
+   (void) f10 (42);
+   (void) g1 (non_const); // { dg-error "not a constant|not usable" }
+}
+
+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 "call to consteval function|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..080fc76f26e
--- /dev/null
+++ b/gcc/testsuite/g++.dg/cpp2a/consteval-prop8.C
@@ -0,0 +1,82 @@
+// 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};
+}
+
+void
+f3 (int i)
+{
+  A a{i}; // { dg-error "not a constant expression" }
+}
+
+inline void
+f7 (int i)
+{
+  A a{i}; // { dg-error "not a constant expression" }
+}
+
+constexpr void
+f8 (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/consteval11.C b/gcc/testsuite/g++.dg/cpp2a/consteval11.C
index 091127eabbf..f5de24bdf77 100644
--- a/gcc/testsuite/g++.dg/cpp2a/consteval11.C
+++ b/gcc/testsuite/g++.dg/cpp2a/consteval11.C
@@ -7,9 +7,11 @@ constexpr int a = bar (1);
 constexpr int b = bar (2);		// { dg-message "in 'constexpr' expansion of" }
 constexpr int c = 0 ? bar (3) : 1;
 const int d = bar (4);			// { dg-message "in 'constexpr' expansion of" }
+// { dg-error "call to consteval function" "" { target *-*-* } .-1 }
 const int e = 0 ? bar (5) : 1;
 int f = bar (1);
 int g = bar (6);			// { dg-message "in 'constexpr' expansion of" }
+// { dg-error "call to consteval function" "" { target *-*-* } .-1 }
 int h = 0 ? bar (7) : 1;
 
 void
@@ -19,25 +21,35 @@ foo ()
   constexpr int b = bar (2);		// { dg-message "in 'constexpr' expansion of" }
   constexpr int c = 0 ? bar (3) : 1;
   const int d = bar (4);		// { dg-message "in 'constexpr' expansion of" }
+// { dg-error "call to consteval function" "" { target *-*-* } .-1 }
   const int e = 0 ? bar (5) : 1;
   int f = bar (1);
   int g = bar (6);			// { dg-message "in 'constexpr' expansion of" }
+// { dg-error "call to consteval function" "" { target *-*-* } .-1 }
   int h = 0 ? bar (7) : 1;		// { dg-message "in 'constexpr' expansion of" }
+// { dg-error "call to consteval function" "" { target *-*-* } .-1 }
   h += 0 ? bar (8) : 1;			// { dg-message "in 'constexpr' expansion of" }
+// { dg-error "call to consteval function" "" { target *-*-* } .-1 }
   if (0)
     bar (9);				// { dg-message "in 'constexpr' expansion of" }
+// { dg-error "call to consteval function" "" { target *-*-* } .-1 }
   else
     bar (10);				// { dg-message "in 'constexpr' expansion of" }
+// { dg-error "call to consteval function" "" { target *-*-* } .-1 }
   if (1)
     bar (11);				// { dg-message "in 'constexpr' expansion of" }
+// { dg-error "call to consteval function" "" { target *-*-* } .-1 }
   else
     bar (12);				// { dg-message "in 'constexpr' expansion of" }
+// { dg-error "call to consteval function" "" { target *-*-* } .-1 }
   if constexpr (0)
     bar (13);
   else
     bar (14);				// { dg-message "in 'constexpr' expansion of" }
+// { dg-error "call to consteval function" "" { target *-*-* } .-1 }
   if constexpr (1)
     bar (15);				// { dg-message "in 'constexpr' expansion of" }
+// { dg-error "call to consteval function" "" { target *-*-* } .-1 }
   else
     bar (16);
 }
@@ -120,18 +132,24 @@ quux ()
 {
   if (0)
     bar ((T) 2);				// { dg-message "in 'constexpr' expansion of" }
+// { dg-error "call to consteval function" "" { target *-*-* } .-1 }
   else
     bar ((T) 3);				// { dg-message "in 'constexpr' expansion of" }
+// { dg-error "call to consteval function" "" { target *-*-* } .-1 }
   if (1)
     bar ((T) 4);				// { dg-message "in 'constexpr' expansion of" }
+// { dg-error "call to consteval function" "" { target *-*-* } .-1 }
   else
     bar ((T) 5);				// { dg-message "in 'constexpr' expansion of" }
+// { dg-error "call to consteval function" "" { target *-*-* } .-1 }
   if constexpr (0)
     bar ((T) 6);
   else
     bar ((T) 7);				// { dg-message "in 'constexpr' expansion of" }
+// { dg-error "call to consteval function" "" { target *-*-* } .-1 }
   if constexpr (1)
     bar ((T) 8);				// { dg-message "in 'constexpr' expansion of" }
+// { dg-error "call to consteval function" "" { target *-*-* } .-1 }
   else
     bar ((T) 9);
 }
diff --git a/gcc/testsuite/g++.dg/cpp2a/consteval3.C b/gcc/testsuite/g++.dg/cpp2a/consteval3.C
index 9efac8c8eae..1199e9db623 100644
--- a/gcc/testsuite/g++.dg/cpp2a/consteval3.C
+++ b/gcc/testsuite/g++.dg/cpp2a/consteval3.C
@@ -16,8 +16,8 @@ consteval auto [ b, c ] = S ();		// { dg-error "structured binding declaration c
 int f5 (consteval int x) { return x; }	// { dg-error "a parameter cannot be declared 'consteval'" }
 consteval int f6 (int x) { return x; }
 int d = 6;		// { dg-message "'int d' is not const" }
-int e = f6 (d);		// { dg-error "the value of 'd' is not usable in a constant expression" }
-constexpr int f7 (int x) { return f6 (x); }	// { dg-error "'x' is not a constant expression" }
+int e = f6 (d);		// { dg-error "the value of 'd' is not usable in a constant expression|call to consteval function" }
+constexpr int f7 (int x) { return f6 (x); }	// { dg-error "'x' is not a constant expression|call to consteval function" }
 constexpr int f = f7 (5);
 using fnptr = int (int);
 fnptr *g = f6;		// { dg-error "taking address of an immediate function 'consteval int f6\\(int\\)'" }
diff --git a/gcc/testsuite/g++.dg/cpp2a/consteval34.C b/gcc/testsuite/g++.dg/cpp2a/consteval34.C
index d5e2d1dc5d2..75d4caa622e 100644
--- a/gcc/testsuite/g++.dg/cpp2a/consteval34.C
+++ b/gcc/testsuite/g++.dg/cpp2a/consteval34.C
@@ -6,6 +6,7 @@ constexpr int
 foo (bool b)
 {
   return b ? bar (3) : 2; // { dg-message "in .constexpr. expansion" }
+// { dg-error "call to consteval function" "" { target *-*-* } .-1 }
 }
 
 static_assert (foo (false) == 2);
@@ -21,13 +22,20 @@ void
 g ()
 {
   __extension__ int a1[bar(3)]; // { dg-message "in .constexpr. expansion" }
+// { dg-error "call to consteval function" "" { target *-*-* } .-1 }
   int a2[sizeof (bar(3))];
 
   int a3 = false ? (1 + bar (8)) : 1; // { dg-message "in .constexpr. expansion" }
+// { dg-error "call to consteval function" "" { target *-*-* } .-1 }
   a3 += false ? (1 + bar (8)) : 1; // { dg-message "in .constexpr. expansion" }
+// { dg-error "call to consteval function" "" { target *-*-* } .-1 }
 
   __extension__ int a4 = false ?: (1 + bar (8)); // { dg-message "in .constexpr. expansion" }
+// { dg-error "call to consteval function" "" { target *-*-* } .-1 }
   __extension__ int a5 = true ?: (1 + bar (8)); // { dg-message "in .constexpr. expansion" }
+// { dg-error "call to consteval function" "" { target *-*-* } .-1 }
   int a6 = bar (2) ? 1 : 2; // { dg-message "in .constexpr. expansion" }
+// { dg-error "call to consteval function" "" { target *-*-* } .-1 }
   int a7 = bar (2) - 1 ? 1 : 2; // { dg-message "in .constexpr. expansion" }
+// { dg-error "call to consteval function" "" { target *-*-* } .-1 }
 }
diff --git a/gcc/testsuite/g++.dg/cpp2a/consteval9.C b/gcc/testsuite/g++.dg/cpp2a/consteval9.C
index aa75ba37849..a03226d17c6 100644
--- a/gcc/testsuite/g++.dg/cpp2a/consteval9.C
+++ b/gcc/testsuite/g++.dg/cpp2a/consteval9.C
@@ -13,6 +13,7 @@ template <int N>
 void qux ()
 {
   int a = bar (N);	// { dg-message "in 'constexpr' expansion of 'bar\\(2\\)'" }
+// { dg-error "call to consteval function" "" { target *-*-* } .-1 }
 }
 
 // This function is not instantiated so NDR.
@@ -30,3 +31,4 @@ baz ()
 }
 
 int a = bar (2);	// { dg-message "in 'constexpr' expansion of 'bar\\(2\\)'" }
+// { dg-error "call to consteval function" "" { target *-*-* } .-1 }
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/gcc/testsuite/g++.dg/cpp2a/spaceship-synth9.C b/gcc/testsuite/g++.dg/cpp2a/spaceship-synth9.C
index 33b547d2b50..ecb46b016a6 100644
--- a/gcc/testsuite/g++.dg/cpp2a/spaceship-synth9.C
+++ b/gcc/testsuite/g++.dg/cpp2a/spaceship-synth9.C
@@ -22,6 +22,6 @@ struct Z: Y<int>
 int main()
 {
   X<char>() == X<char>();	// { dg-error "no match" }
-  X<int> x; x == x;		// { dg-error "x' is not usable in a constant expression" }
+  X<int> x; x == x;		// { dg-error "x' is not usable in a constant expression|call to consteval function" }
   Y<int>()  == Y<int>();	// { dg-warning "nodiscard" }
 }
diff --git a/libstdc++-v3/testsuite/18_support/comparisons/categories/zero_neg.cc b/libstdc++-v3/testsuite/18_support/comparisons/categories/zero_neg.cc
index 9d2115b3f4f..82f7cd54fba 100644
--- a/libstdc++-v3/testsuite/18_support/comparisons/categories/zero_neg.cc
+++ b/libstdc++-v3/testsuite/18_support/comparisons/categories/zero_neg.cc
@@ -52,3 +52,4 @@ test01()
 
 // { dg-prune-output "reinterpret_cast.* is not a constant expression" }
 // { dg-prune-output "cast from 'void.' is not allowed" }
+// { dg-prune-output "not a constant expression" }
diff --git a/libstdc++-v3/testsuite/std/format/string_neg.cc b/libstdc++-v3/testsuite/std/format/string_neg.cc
index 7a60ef8cf0e..69bcc736cff 100644
--- a/libstdc++-v3/testsuite/std/format/string_neg.cc
+++ b/libstdc++-v3/testsuite/std/format/string_neg.cc
@@ -2,5 +2,5 @@
 
 #include <format>
 
-auto s = std::format(" {9} ");
+auto s = std::format(" {9} "); // { dg-error "call to consteval function" }
 // { dg-error "invalid.arg.id" "" { target *-*-* } 0 }

base-commit: 5bb6a8766ec83e20550e1b59f2accb1ef25e3369
-- 
2.41.0


^ permalink raw reply	[flat|nested] 19+ messages in thread

* Re: [PATCH v2] c++: implement P2564, consteval needs to propagate up [PR107687]
  2023-10-10 17:20   ` [PATCH v2] " Marek Polacek
@ 2023-10-11 20:42     ` Marek Polacek
  2023-10-14  4:56     ` Jason Merrill
  1 sibling, 0 replies; 19+ messages in thread
From: Marek Polacek @ 2023-10-11 20:42 UTC (permalink / raw)
  To: Jason Merrill; +Cc: GCC Patches

On Tue, Oct 10, 2023 at 01:20:09PM -0400, Marek Polacek wrote:
> @@ -3364,6 +3365,13 @@ word as an identifier.  You can use the keyword @code{__typeof__} instead.
>  This option is implied by the strict ISO C++ dialects: @option{-ansi},
>  @option{-std=c++98}, @option{-std=c++11}, etc.
>  
> +@opindex fno-immediate-escalation
> +@opindex fimmediate-escalation
> +@item -fno-immediate-escalation
> +Do not enable immediate function escalation whereby certain functions
> +can be promoted to consteval, as specified in P2564R3.  This option is
> +turned on by default; it is only effective in C++20 mode or later.
> +
>  @opindex fimplicit-constexpr
>  @item -fimplicit-constexpr
>  Make inline functions implicitly constexpr, if they satisfy the

I realized it would be useful to extend this doc text, so now it reads:

@opindex fno-immediate-escalation
@opindex fimmediate-escalation
@item -fno-immediate-escalation
Do not enable immediate function escalation whereby certain functions
can be promoted to consteval, as specified in P2564R3.  For example:

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

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

void g(int i)
@{
  f (3);
@}
@end example

compiles in C++20: @code{f} is an immediate-escalating function (due to
the @code{auto} it is a function template and is declared @code{constexpr})
and @code{id(t)} is an immediate-escalating expression, so @code{f} is
promoted to @code{consteval}.  Consequently, the call to @code{id(t)}
is in an immediate context, so doesn't have to produce a constant (that
is the mechanism allowing consteval function composition).  However,
with @option{-fno-immediate-escalation}, @code{f} is not promoted to
@code{consteval}, and since the call to consteval function @code{id(t)}
is not a constant expression, the compiler rejects the code.

This option is turned on by default; it is only effective in C++20 mode
or later.


^ permalink raw reply	[flat|nested] 19+ messages in thread

* Re: [PATCH v2] c++: implement P2564, consteval needs to propagate up [PR107687]
  2023-10-10 17:20   ` [PATCH v2] " Marek Polacek
  2023-10-11 20:42     ` Marek Polacek
@ 2023-10-14  4:56     ` Jason Merrill
  2023-11-02 15:28       ` [PATCH v3] " Marek Polacek
  1 sibling, 1 reply; 19+ messages in thread
From: Jason Merrill @ 2023-10-14  4:56 UTC (permalink / raw)
  To: Marek Polacek; +Cc: GCC Patches

On 10/10/23 13:20, Marek Polacek wrote:
> Thanks for looking into this.  It's kept me occupied for quite a while.

Thanks, nice work.

> On Tue, Aug 29, 2023 at 03:26:46PM -0400, Jason Merrill wrote:
>> On 8/23/23 15:49, Marek Polacek wrote:
>>> +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.
> 
> True, and that revealed what I think is a bug in the standard.
> In the test I'm saying:
> 
> // ??? [expr.const]#example-9 says:
> //   k<int> is not an immediate function because A(42) is a
> //   constant expression and thus not immediate-escalating
> // But I think the call to id(x) is *not* a constant expression and thus
> // it is an immediate-escalating expression.  Therefore k<int> *is*
> // an immediate function.  So we get the error below.  clang++ agrees.
>   
> id(x) is not a constant expression because x isn't constant.

Not when considering id(x) by itself, but in the evaluation of A(42), 
the member x has just been initialized to constant 42.  And A(42) is 
constant-evaluated because "An aggregate initialization is an immediate 
invocation if it evaluates a default member initializer that has a 
subexpression that is an immediate-escalating expression."

I assume clang doesn't handle this passage properly yet because it was 
added during core review of the paper, for parity between aggregate 
initialization and constructor escalation.

This can be a follow-up patch.

> So.  I think we want to refrain from instantiating things early
> given how many problems that caused.  On the other hand, stashing
> all the immediate-escalating decls into immediate_escalating_decls
> and walking their bodies isn't going to be cheap.  I've checked
> how big the vectors can get, but our testsuite isn't the best litmus
> test because it's mostly smallish testcases without many #includes.
> The worst offender is uninit-pr105562.C with
> 
> (gdb) p immediate_escalating_decls->length()
> $2 = 2204
> (gdb) p deferred_escalating_exprs->length()
> $3 = 501
> 
> Compiling uninit-pr105562.C with g++13 and g++14 with this patch:
> real 7.51                 real 7.67
> user 7.32                 user 7.49
> sys 0.15                  sys 0.14
> 
> I've made sure not to walk the same bodies twice.  But there's room
> for further optimization; I suppose we could escalate instantiated
> functions right away rather than putting them into
> immediate_escalating_decls and waiting till later.

Absolutely; if we see a call to a known consteval function, we should 
escalate...immediately.  As the patch seems to do already?

> I'm not certain
> if I can just look at DECL_TEMPLATE_INSTANTIATED.

I'm not sure what you mean, but a constexpr function being instantiated 
doesn't necessarily imply that everything it calls has been 
instantiated, so we might not know yet if it needs to escalate.

> I suppose some
> functions cannot possibly be promoted because they don't contain
> any CALL_EXPRs.  So we may be able to rule them out while doing
> cp_fold_r early.

Yes.  Or, the only immediate-escalating functions referenced have 
already been checked.

We can also do some escalation during constexpr evaluation: all the 
functions involved need to be instantiated for the evaluation, and if we 
encounter an immediate-escalating expression while evaluating a call to 
an immediate-escalating function, we can promote it then.  Though we 
can't necessarily mark it as not needing promotion, as there might be 
i-e exprs in branches that the particular evaluation doesn't take.

> If a function is trivial, can it ever be promoted?

This might be a question for CWG.  clang thinks so:

struct A {
   consteval A() = default;
   consteval A(const A&) = default;
   int i;
};
struct B : A { }; // B constructors promote to consteval
B b; // ok, constant-initialization
B b2(b); // error, immediate invocation isn't constant

> And so on.
> 
> 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:
> 
> w.C:11:11: error: call to consteval function 'f<int>(i)' is not a constant expression
>     11 |         f (i);
>        |         ~~^~~
> w.C:11:11: error: 'i' is not a constant expression
> w.C:6:22: 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); // id causes f<int> to be promoted to consteval
>        |                    ~~^~~
> 
> 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.
> 
> In v1, I addressed the delayed instantiation problem by instantiating
> trees early, so that we can escalate functions right away.  That caused
> a number of problems, and in certain cases, like consteval-prop3.C, it
> can't work, because we need to wait till EOF to see the definition of
> the function anyway.

Indeed.  I briefly thought that maybe we could just poison consteval 
functions (declared or escalated) so that the back-end complains if we 
try to emit a reference to them, but that wouldn't catch optimized-away 
references.

> Overeager instantiation tends to cause diagnostic
> problems too.

e.g. constexpr-inst1.C.  Though I notice that clang and EDG do the eager 
instantiation for that testcase even in C++11 mode.

Relatedly, for

template <class T> consteval T id (T t) { return t; }
template <class T> constexpr T f(T t);
void g(int i) { f(42); }
template <class T> constexpr T f(T t) { return id(t); }

clang doesn't try to constant-evaluate f(42) because f wasn't defined 
yet at that point (as required by 
https://eel.is/c++draft/expr.const#5.4), just gives an error.

And if f is explicitly declared consteval, we also reject it, so it 
seems surprising to be more permissive with promoted consteval.

This also doesn't need to be resolved now.

> Therefore, we have to perform the escalation before gimplifying, but
> after instantiate_pending_templates.  That's not easy because we have
> no way to walk all the trees.  In the v2 patch, I use two vectors: one
> to store function decls that may become consteval and another to > remember references to immediate-escalating functions.  Unfortunately
> the latter must also stash functions that call immediate-escalating
> functions.  Consider:
> 
>    int g(int i)
>    {
>      f<int>(i); // f is immediate-escalating
>    }
> 
> where g itself is not immediate-escalating, but we have to make sure
> that if f gets promoted to consteval, we give an error.

I'm not sure you need the former list: if you immediately promote when 
you see a known immediate invocation, an i-e fn you might want to 
promote will be one that calls other i-e fns, so it'll also be on the 
second list.

> A new option, -fno-immediate-escalation, is provided to suppress
> escalating functions.
> 
> v2 also adds a new flag, DECL_ESCALATED_P, so that we don't escalate
> a function multiple times, and so that we can distinguish between
> explicitly consteval functions and functions that have been promoted
> to consteval.

That name suggests to me that it's been promoted, not just that we've 
checked it for promotion; maybe 
DECL_ESCALATION_{CHECKED,RESOLVED,KNOWN}_P?

> @@ -55,6 +64,8 @@ enum fold_flags {
>     ff_mce_false = 1 << 1,
>     /* Whether we're being called from cp_fold_immediate.  */
>     ff_fold_immediate = 1 << 2,
> +  /* Whether we're escalating immediate-escalating functions.  */
> +  ff_escalating = 1 << 3,

If we always escalate when we see a known immediate invocation, this 
means recurse.  And maybe we could use at_eof for that?

>   };
>   
>   using fold_flags_t = int;
> @@ -428,6 +439,176 @@ lvalue_has_side_effects (tree e)
>       return TREE_SIDE_EFFECTS (e);
>   }
>   
> +/* Return true if FN is an immediate-escalating function.  */
> +
> +static bool
> +immediate_escalating_function_p (tree fn)
> +{
> +  if (!fn || !flag_immediate_escalation)
> +    return false;
> +
> +  gcc_checking_assert (TREE_CODE (fn) == FUNCTION_DECL);

Maybe check DECL_IMMEDIATE_FUNCTION_P early, rather than multiple times 
below...

> +  /* 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.  */
> +
> +static bool
> +maybe_promote_function_to_consteval (tree fn)
> +{
> +  if (fn
> +      && !DECL_IMMEDIATE_FUNCTION_P (fn)

...and in all the callers?

> +      && immediate_escalating_function_p (fn))
> +    {
> +      SET_DECL_IMMEDIATE_FUNCTION_P (fn);
> +      DECL_ESCALATED_P (fn) = true;
> +      tree clone;
> +      FOR_EACH_CLONE (clone, fn)
> +	{
> +	  SET_DECL_IMMEDIATE_FUNCTION_P (clone);
> +	  DECL_ESCALATED_P (clone) = true;
> +	}
> +      return true;
> +    }
> +
> +  return false;
> +}
> +
> +/* Remember that the current function declaration contains a call to
> +   a function that might be promoted to consteval later.  */
> +
> +static void
> +maybe_store_cfun_for_late_checking ()
> +{
> +  if (!current_function_decl
> +      || !flag_immediate_escalation
> +      || immediate_escalating_function_p (current_function_decl))
> +    return;
> +
> +  if (deferred_escalating_exprs
> +      /* Don't put duplicates into the vec so that we don't walk
> +	 a function multiple times.  Most likely the last one
> +	 added will be the same.  */
> +      && (deferred_escalating_exprs->last () == current_function_decl
> +	  || deferred_escalating_exprs->contains (current_function_decl)))

Doing ->contains on a vec is slow, better to use a hash_set.

> +    return;
> +
> +  vec_safe_push (deferred_escalating_exprs, current_function_decl);
> +}
> +
> +/* 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)

Can this merge with cp_fold_immediate_r?  They seem to be doing 
essentially the same thing.

> @@ -485,6 +666,41 @@ cp_gimplify_arg (tree *arg_p, gimple_seq *pre_p, location_t call_location,
>   
>   }
>   
> +/* 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.
> +   EVALP is where we may store the result of cxx_constant_value so that we
> +   don't have to evaluate the same tree again in cp_fold_immediate_r.  */
> +
> +static void
> +maybe_escalate_decl_and_cfun (tree decl, tree call, tree *evalp)
> +{
> +  /* Compiler-generated functions don't seem like good candidates for
> +     promoting.  */
> +  if (cp_unevaluated_operand || DECL_ARTIFICIAL (decl))

As discussed above, we probably don't want to exclude 
implicitly-declared special member functions.

> @@ -1073,20 +1309,42 @@ cp_fold_immediate_r (tree *stmt_p, int *walk_subtrees, void *data_)
>   	 from cp_fold_r and we must let it recurse on the expression with
>   	 cp_fold.  */
>         break;
> +
>       case PTRMEM_CST:
> -      if (TREE_CODE (PTRMEM_CST_MEMBER (stmt)) == FUNCTION_DECL
> -	  && DECL_IMMEDIATE_FUNCTION_P (PTRMEM_CST_MEMBER (stmt)))
> -	{
> -	  if (!data->pset.add (stmt) && (complain & tf_error))
> -	    {
> -	      error_at (PTRMEM_CST_LOCATION (stmt),
> -			"taking address of an immediate function %qD",
> -			PTRMEM_CST_MEMBER (stmt));
> -	      *stmt_p = build_zero_cst (TREE_TYPE (stmt));
> -	    }
> -	  return error_mark_node;
> -	}
> -      break;
> +    case ADDR_EXPR:
> +      {
> +	tree decl = (code == PTRMEM_CST
> +		     ? PTRMEM_CST_MEMBER (stmt)
> +		     : TREE_OPERAND (stmt, 0));
> +	if (TREE_CODE (decl) != FUNCTION_DECL)
> +	  break;
> +	if (code == ADDR_EXPR && ADDR_EXPR_DENOTES_CALL_P (stmt))
> +	  break;
> +	if (immediate_invocation_p (decl))
> +	  {
> +	    if (maybe_promote_function_to_consteval (current_function_decl))
> +	      break;
> +	    if (complain & tf_error)
> +	      {
> +		/* ??? Why don't we use data->pset for ADDR_EXPR too?  */

Does doing that affect the testsuite?  My guess is that it was done for 
PTRMEM_CST to avoid duplicate errors, and isn't needed for ADDR_EXPR, 
but it also shouldn't hurt for ADDR_EXPR.

> +		if (code == ADDR_EXPR || !data->pset.add (stmt))
> +		  {
> +		    taking_address_of_imm_fn_error (stmt, decl);
> +		    *stmt_p = build_zero_cst (TREE_TYPE (stmt));
> +		  }
> +		/* If we're giving hard errors, continue the walk rather than
> +		   bailing out after the first error.  */
> +		break;
> +	      }
> +	    return error_mark_node;
> +	  }
> +	/* Not consteval yet, but could become one, in which case it's invalid
> +	   to take its address.  */
> +	else if (!(data->flags & ff_fold_immediate)

Why check ff_fold_immediate?  Don't we want to do deferred checking of 
immediate invocations in discarded branches?

> +		 && immediate_escalating_function_p (decl))
> +	  vec_safe_push (deferred_escalating_exprs, stmt);
> +	break;
> +      }
>   
>       /* Expand immediate invocations.  */
>       case CALL_EXPR:
> @@ -1094,33 +1352,54 @@ cp_fold_immediate_r (tree *stmt_p, int *walk_subtrees, void *data_)
>         if (tree fn = cp_get_callee (stmt))
>   	if (TREE_CODE (fn) != ADDR_EXPR || ADDR_EXPR_DENOTES_CALL_P (fn))
>   	  if (tree fndecl = cp_get_fndecl_from_callee (fn, /*fold*/false))
> -	    if (DECL_IMMEDIATE_FUNCTION_P (fndecl))
> -	      {
> -		stmt = cxx_constant_value (stmt, complain);
> -		if (stmt == error_mark_node)
> -		  {
> -		    if (complain & tf_error)
> -		      *stmt_p = error_mark_node;
> -		    return error_mark_node;
> -		  }
> -		*stmt_p = stmt;
> -	      }
> -      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))
> -	{
> -	  if (complain & tf_error)
>   	    {
> -	      error_at (EXPR_LOCATION (stmt),
> -			"taking address of an immediate function %qD",
> -			TREE_OPERAND (stmt, 0));
> -	      *stmt_p = build_zero_cst (TREE_TYPE (stmt));
> +	      tree eval = NULL_TREE;
> +	      if (data->flags & ff_escalating)
> +		maybe_escalate_decl_and_cfun (fndecl, stmt, &eval);
> +
> +	      /* [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 STMT does not need to produce
> +		 a constant expression.  */
> +	      if (immediate_invocation_p (fndecl))
> +		{
> +		  tree e = eval ? eval : cxx_constant_value (stmt, tf_none);
> +		  if (e == error_mark_node)
> +		    {
> +		      if (maybe_promote_function_to_consteval
> +			  (current_function_decl))
> +			break;
> +		      if (complain & tf_error)
> +			{
> +			  auto_diagnostic_group d;
> +			  location_t loc = cp_expr_loc_or_input_loc (stmt);
> +			  error_at (loc, "call to consteval function %qE is "
> +				    "not a constant expression", stmt);
> +			  /* Explain why it's not a constant expression.  */
> +			  *stmt_p = cxx_constant_value (stmt, complain);
> +			  maybe_explain_promoted_consteval (loc, fndecl);
> +			  /* Don't return error_mark_node, it would stop our
> +			     tree walk.  */
> +			  break;
> +			}
> +		      return error_mark_node;
> +		    }
> +		  /* We've evaluated the consteval function call.  */
> +		  *stmt_p = e;
> +		}
> +	      /* We've encountered a function call that may turn out to be
> +		 consteval later.  Store its caller so that we can ensure
> +		 that the call is a constant expression.  */
> +	      else if (!(data->flags & ff_fold_immediate)

Likewise.

It also seems odd that the ADDR_EXPR case calls vec_safe_push 
(deferred_escalating_exprs, while the CALL_EXPR case calls 
maybe_store_cfun_for_late_checking, why the different handling?

> +		       && immediate_escalating_function_p (fndecl))
> +		maybe_store_cfun_for_late_checking ();
>   	    }
> -	  return error_mark_node;
> -	}
>         break;
>   
>       default:
> diff --git a/gcc/cp/module.cc b/gcc/cp/module.cc
> index e3fb2299d93..02c50ac6a11 100644
> --- a/gcc/cp/module.cc
> +++ b/gcc/cp/module.cc
> @@ -5685,6 +5685,8 @@ trees_out::lang_decl_bools (tree t)
>         WB (lang->u.fn.has_dependent_explicit_spec_p);
>         WB (lang->u.fn.immediate_fn_p);
>         WB (lang->u.fn.maybe_deleted);
> +      /* We do not stream lang->u.implicit_constexpr nor
> +	 lang->u.escalated_p.  */

Won't this mean not getting maybe_explain_promoted_consteval for 
imported functions?

Jason


^ permalink raw reply	[flat|nested] 19+ messages in thread

* [PATCH v3] c++: implement P2564, consteval needs to propagate up [PR107687]
  2023-10-14  4:56     ` Jason Merrill
@ 2023-11-02 15:28       ` Marek Polacek
  2023-11-02 15:32         ` Marek Polacek
  2023-11-03 17:51         ` Jason Merrill
  0 siblings, 2 replies; 19+ messages in thread
From: Marek Polacek @ 2023-11-02 15:28 UTC (permalink / raw)
  To: Jason Merrill; +Cc: GCC Patches

On Sat, Oct 14, 2023 at 12:56:11AM -0400, Jason Merrill wrote:
> On 10/10/23 13:20, Marek Polacek wrote:
> > Thanks for looking into this.  It's kept me occupied for quite a while.
> 
> Thanks, nice work.
> 
> > On Tue, Aug 29, 2023 at 03:26:46PM -0400, Jason Merrill wrote:
> > > On 8/23/23 15:49, Marek Polacek wrote:
> > > > +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.
> > 
> > True, and that revealed what I think is a bug in the standard.
> > In the test I'm saying:
> > 
> > // ??? [expr.const]#example-9 says:
> > //   k<int> is not an immediate function because A(42) is a
> > //   constant expression and thus not immediate-escalating
> > // But I think the call to id(x) is *not* a constant expression and thus
> > // it is an immediate-escalating expression.  Therefore k<int> *is*
> > // an immediate function.  So we get the error below.  clang++ agrees.
> > id(x) is not a constant expression because x isn't constant.
> 
> Not when considering id(x) by itself, but in the evaluation of A(42), the
> member x has just been initialized to constant 42.  And A(42) is
> constant-evaluated because "An aggregate initialization is an immediate
> invocation if it evaluates a default member initializer that has a
> subexpression that is an immediate-escalating expression."
> 
> I assume clang doesn't handle this passage properly yet because it was added
> during core review of the paper, for parity between aggregate initialization
> and constructor escalation.
> 
> This can be a follow-up patch.

I see.  So the fix will be to, for the aggregate initialization case, pass
the whole A(42).y thing to cxx_constant_eval, not just id(x).
 
> > So.  I think we want to refrain from instantiating things early
> > given how many problems that caused.  On the other hand, stashing
> > all the immediate-escalating decls into immediate_escalating_decls
> > and walking their bodies isn't going to be cheap.  I've checked
> > how big the vectors can get, but our testsuite isn't the best litmus
> > test because it's mostly smallish testcases without many #includes.
> > The worst offender is uninit-pr105562.C with
> > 
> > (gdb) p immediate_escalating_decls->length()
> > $2 = 2204
> > (gdb) p deferred_escalating_exprs->length()
> > $3 = 501
> > 
> > Compiling uninit-pr105562.C with g++13 and g++14 with this patch:
> > real 7.51                 real 7.67
> > user 7.32                 user 7.49
> > sys 0.15                  sys 0.14
> > 
> > I've made sure not to walk the same bodies twice.  But there's room
> > for further optimization; I suppose we could escalate instantiated
> > functions right away rather than putting them into
> > immediate_escalating_decls and waiting till later.
> 
> Absolutely; if we see a call to a known consteval function, we should
> escalate...immediately.  As the patch seems to do already?

Right, I'm not sure what I was thinking.
 
> > I'm not certain
> > if I can just look at DECL_TEMPLATE_INSTANTIATED.
> 
> I'm not sure what you mean, but a constexpr function being instantiated
> doesn't necessarily imply that everything it calls has been instantiated, so
> we might not know yet if it needs to escalate.

I was pondering exactly that but you are of course correct here.
 
> > I suppose some
> > functions cannot possibly be promoted because they don't contain
> > any CALL_EXPRs.  So we may be able to rule them out while doing
> > cp_fold_r early.
> 
> Yes.  Or, the only immediate-escalating functions referenced have already
> been checked.
> 
> We can also do some escalation during constexpr evaluation: all the
> functions involved need to be instantiated for the evaluation, and if we
> encounter an immediate-escalating expression while evaluating a call to an
> immediate-escalating function, we can promote it then.  Though we can't
> necessarily mark it as not needing promotion, as there might be i-e exprs in
> branches that the particular evaluation doesn't take.

I've tried but I didn't get anywhere.  The patch was basically

--- a/gcc/cp/constexpr.cc
+++ b/gcc/cp/constexpr.cc
@@ -2983,7 +2983,13 @@ cxx_eval_call_expression (const constexpr_ctx *ctx, tree t,
   } fb (new_call.bindings);

   if (*non_constant_p)
-    return t;
+    {
+      if (cxx_dialect >= cxx20
+	  && ctx->manifestly_const_eval == mce_false
+	  && DECL_IMMEDIATE_FUNCTION_P (fun))
+	maybe_promote_function_to_consteval (current_function_decl);
+      return t;
+    }

   /* We can't defer instantiating the function any longer.  */
   if (!DECL_INITIAL (fun)

but since I have to check mce_false, it didn't do anything useful
in practice (that is, it wouldn't escalate anything in my tests).

> > If a function is trivial, can it ever be promoted?
> 
> This might be a question for CWG.  clang thinks so:
> 
> struct A {
>   consteval A() = default;
>   consteval A(const A&) = default;
>   int i;
> };
> struct B : A { }; // B constructors promote to consteval
> B b; // ok, constant-initialization
> B b2(b); // error, immediate invocation isn't constant

Ah, here we elide the calls so there's nothing to promote.  And I see
that clang++ gives errors for both b and b2.  I left this be for now.
 
> > In v1, I addressed the delayed instantiation problem by instantiating
> > trees early, so that we can escalate functions right away.  That caused
> > a number of problems, and in certain cases, like consteval-prop3.C, it
> > can't work, because we need to wait till EOF to see the definition of
> > the function anyway.
> 
> Indeed.  I briefly thought that maybe we could just poison consteval
> functions (declared or escalated) so that the back-end complains if we try
> to emit a reference to them, but that wouldn't catch optimized-away
> references.
> 
> > Overeager instantiation tends to cause diagnostic
> > problems too.
> 
> e.g. constexpr-inst1.C.  Though I notice that clang and EDG do the eager
> instantiation for that testcase even in C++11 mode.
> 
> Relatedly, for
> 
> template <class T> consteval T id (T t) { return t; }
> template <class T> constexpr T f(T t);
> void g(int i) { f(42); }
> template <class T> constexpr T f(T t) { return id(t); }
> 
> clang doesn't try to constant-evaluate f(42) because f wasn't defined yet at
> that point (as required by https://eel.is/c++draft/expr.const#5.4), just
> gives an error.
> 
> And if f is explicitly declared consteval, we also reject it, so it seems
> surprising to be more permissive with promoted consteval.

Agreed.
 
> This also doesn't need to be resolved now.

I thought about this and I think the best approach would be to poison
f with an attribute, and give an error if we do escalate to consteval.
Hopefully such an attribute could be used elsewhere, too.

> > Therefore, we have to perform the escalation before gimplifying, but
> > after instantiate_pending_templates.  That's not easy because we have
> > no way to walk all the trees.  In the v2 patch, I use two vectors: one
> > to store function decls that may become consteval and another to > remember references to immediate-escalating functions.  Unfortunately
> > the latter must also stash functions that call immediate-escalating
> > functions.  Consider:
> > 
> >    int g(int i)
> >    {
> >      f<int>(i); // f is immediate-escalating
> >    }
> > 
> > where g itself is not immediate-escalating, but we have to make sure
> > that if f gets promoted to consteval, we give an error.
> 
> I'm not sure you need the former list: if you immediately promote when you
> see a known immediate invocation, an i-e fn you might want to promote will
> be one that calls other i-e fns, so it'll also be on the second list.

True, I now only use one hash_set.
 
> > A new option, -fno-immediate-escalation, is provided to suppress
> > escalating functions.
> > 
> > v2 also adds a new flag, DECL_ESCALATED_P, so that we don't escalate
> > a function multiple times, and so that we can distinguish between
> > explicitly consteval functions and functions that have been promoted
> > to consteval.
> 
> That name suggests to me that it's been promoted, not just that we've
> checked it for promotion; maybe DECL_ESCALATION_{CHECKED,RESOLVED,KNOWN}_P?

Fair enough, renamed to DECL_ESCALATION_CHECKED_P.
 
> > @@ -55,6 +64,8 @@ enum fold_flags {
> >     ff_mce_false = 1 << 1,
> >     /* Whether we're being called from cp_fold_immediate.  */
> >     ff_fold_immediate = 1 << 2,
> > +  /* Whether we're escalating immediate-escalating functions.  */
> > +  ff_escalating = 1 << 3,
> 
> If we always escalate when we see a known immediate invocation, this means
> recurse.  And maybe we could use at_eof for that?

Yes.  Though, at_eof == 1 doesn't imply that all templates have been
instantiated, only that we got to c_parse_final_cleanups.  Rather than
keeping the ff_ flag, I've updated at_eof to mean that 2 signals that
all templates have been instantiated, and 3 that we're into cgraph
territory.  (Might should use an enum rather than magic numbers.)
 
> >   };
> >   using fold_flags_t = int;
> > @@ -428,6 +439,176 @@ lvalue_has_side_effects (tree e)
> >       return TREE_SIDE_EFFECTS (e);
> >   }
> > +/* Return true if FN is an immediate-escalating function.  */
> > +
> > +static bool
> > +immediate_escalating_function_p (tree fn)
> > +{
> > +  if (!fn || !flag_immediate_escalation)
> > +    return false;
> > +
> > +  gcc_checking_assert (TREE_CODE (fn) == FUNCTION_DECL);
> 
> Maybe check DECL_IMMEDIATE_FUNCTION_P early, rather than multiple times
> below...
> 
> > +  /* 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.  */
> > +
> > +static bool
> > +maybe_promote_function_to_consteval (tree fn)
> > +{
> > +  if (fn
> > +      && !DECL_IMMEDIATE_FUNCTION_P (fn)
> 
> ...and in all the callers?

Ah, beautiful.  And likewise for DECL_ESCALATION_CHECKED_P.
 
> > +static void
> > +maybe_store_cfun_for_late_checking ()
> > +{
> > +  if (!current_function_decl
> > +      || !flag_immediate_escalation
> > +      || immediate_escalating_function_p (current_function_decl))
> > +    return;
> > +
> > +  if (deferred_escalating_exprs
> > +      /* Don't put duplicates into the vec so that we don't walk
> > +	 a function multiple times.  Most likely the last one
> > +	 added will be the same.  */
> > +      && (deferred_escalating_exprs->last () == current_function_decl
> > +	  || deferred_escalating_exprs->contains (current_function_decl)))
> 
> Doing ->contains on a vec is slow, better to use a hash_set.

Sure, we have iterators so we can walk the hash set.  Changed.
 
> > +    return;
> > +
> > +  vec_safe_push (deferred_escalating_exprs, current_function_decl);
> > +}
> > +
> > +/* 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)
> 
> Can this merge with cp_fold_immediate_r?  They seem to be doing essentially
> the same thing.

I did not do it, sorry.  I thought it would not make things more readable
as I don't see a whole lot of code to share.
 
> > @@ -485,6 +666,41 @@ cp_gimplify_arg (tree *arg_p, gimple_seq *pre_p, location_t call_location,
> >   }
> > +/* 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.
> > +   EVALP is where we may store the result of cxx_constant_value so that we
> > +   don't have to evaluate the same tree again in cp_fold_immediate_r.  */
> > +
> > +static void
> > +maybe_escalate_decl_and_cfun (tree decl, tree call, tree *evalp)
> > +{
> > +  /* Compiler-generated functions don't seem like good candidates for
> > +     promoting.  */
> > +  if (cp_unevaluated_operand || DECL_ARTIFICIAL (decl))
> 
> As discussed above, we probably don't want to exclude implicitly-declared
> special member functions.

Done (but removing the DECL_ARTIFICIAL check).
 
> > @@ -1073,20 +1309,42 @@ cp_fold_immediate_r (tree *stmt_p, int *walk_subtrees, void *data_)
> >   	 from cp_fold_r and we must let it recurse on the expression with
> >   	 cp_fold.  */
> >         break;
> > +
> >       case PTRMEM_CST:
> > -      if (TREE_CODE (PTRMEM_CST_MEMBER (stmt)) == FUNCTION_DECL
> > -	  && DECL_IMMEDIATE_FUNCTION_P (PTRMEM_CST_MEMBER (stmt)))
> > -	{
> > -	  if (!data->pset.add (stmt) && (complain & tf_error))
> > -	    {
> > -	      error_at (PTRMEM_CST_LOCATION (stmt),
> > -			"taking address of an immediate function %qD",
> > -			PTRMEM_CST_MEMBER (stmt));
> > -	      *stmt_p = build_zero_cst (TREE_TYPE (stmt));
> > -	    }
> > -	  return error_mark_node;
> > -	}
> > -      break;
> > +    case ADDR_EXPR:
> > +      {
> > +	tree decl = (code == PTRMEM_CST
> > +		     ? PTRMEM_CST_MEMBER (stmt)
> > +		     : TREE_OPERAND (stmt, 0));
> > +	if (TREE_CODE (decl) != FUNCTION_DECL)
> > +	  break;
> > +	if (code == ADDR_EXPR && ADDR_EXPR_DENOTES_CALL_P (stmt))
> > +	  break;
> > +	if (immediate_invocation_p (decl))
> > +	  {
> > +	    if (maybe_promote_function_to_consteval (current_function_decl))
> > +	      break;
> > +	    if (complain & tf_error)
> > +	      {
> > +		/* ??? Why don't we use data->pset for ADDR_EXPR too?  */
> 
> Does doing that affect the testsuite?  My guess is that it was done for
> PTRMEM_CST to avoid duplicate errors, and isn't needed for ADDR_EXPR, but it
> also shouldn't hurt for ADDR_EXPR.

No visible change in the testsuite, so now we treat them the same.
 
> > +		if (code == ADDR_EXPR || !data->pset.add (stmt))
> > +		  {
> > +		    taking_address_of_imm_fn_error (stmt, decl);
> > +		    *stmt_p = build_zero_cst (TREE_TYPE (stmt));
> > +		  }
> > +		/* If we're giving hard errors, continue the walk rather than
> > +		   bailing out after the first error.  */
> > +		break;
> > +	      }
> > +	    return error_mark_node;
> > +	  }
> > +	/* Not consteval yet, but could become one, in which case it's invalid
> > +	   to take its address.  */
> > +	else if (!(data->flags & ff_fold_immediate)
> 
> Why check ff_fold_immediate?  Don't we want to do deferred checking of
> immediate invocations in discarded branches?

On reflection, I was papering over a different problem here.  

Inserting a new element to a hash table while we're traversing it is
dangerous because a new element might mean calling hash_table::expand
which creates a new table, std::moves the elements, and calls ggc_free
on the old table.  Then iterating to the next element is going to crash
because it has been freed.

We were trying to add a new element while being called from
process_pending_immediate_escalating_fns.  This should not happen.
The problem was that the DECL_ESCALATION_CHECKED_P flag may not have
been properly set for functions without any CALL_EXPRs in them, and that
immediate_escalating_function_p was not checking it, either.

The code is just

+   else if (immediate_escalating_function_p (decl))
+     remember_escalating_expr (stmt);
 
now.

The problem triggered in Wfree-nonheap-object-3.C.  Unfortunately, I could
not reduce it very far so I don't have a test suitable for the testsuite.
The tricky thing is that the problem only manifests when we expand() a hash
table which is unlikely to happen in a small test and I don't see a --param
I could use.

> > +		 && immediate_escalating_function_p (decl))
> > +	  vec_safe_push (deferred_escalating_exprs, stmt);
> > +	break;
> > +      }
> >       /* Expand immediate invocations.  */
> >       case CALL_EXPR:
> > @@ -1094,33 +1352,54 @@ cp_fold_immediate_r (tree *stmt_p, int *walk_subtrees, void *data_)
> >         if (tree fn = cp_get_callee (stmt))
> >   	if (TREE_CODE (fn) != ADDR_EXPR || ADDR_EXPR_DENOTES_CALL_P (fn))
> >   	  if (tree fndecl = cp_get_fndecl_from_callee (fn, /*fold*/false))
> > -	    if (DECL_IMMEDIATE_FUNCTION_P (fndecl))
> > -	      {
> > -		stmt = cxx_constant_value (stmt, complain);
> > -		if (stmt == error_mark_node)
> > -		  {
> > -		    if (complain & tf_error)
> > -		      *stmt_p = error_mark_node;
> > -		    return error_mark_node;
> > -		  }
> > -		*stmt_p = stmt;
> > -	      }
> > -      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))
> > -	{
> > -	  if (complain & tf_error)
> >   	    {
> > -	      error_at (EXPR_LOCATION (stmt),
> > -			"taking address of an immediate function %qD",
> > -			TREE_OPERAND (stmt, 0));
> > -	      *stmt_p = build_zero_cst (TREE_TYPE (stmt));
> > +	      tree eval = NULL_TREE;
> > +	      if (data->flags & ff_escalating)
> > +		maybe_escalate_decl_and_cfun (fndecl, stmt, &eval);
> > +
> > +	      /* [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 STMT does not need to produce
> > +		 a constant expression.  */
> > +	      if (immediate_invocation_p (fndecl))
> > +		{
> > +		  tree e = eval ? eval : cxx_constant_value (stmt, tf_none);
> > +		  if (e == error_mark_node)
> > +		    {
> > +		      if (maybe_promote_function_to_consteval
> > +			  (current_function_decl))
> > +			break;
> > +		      if (complain & tf_error)
> > +			{
> > +			  auto_diagnostic_group d;
> > +			  location_t loc = cp_expr_loc_or_input_loc (stmt);
> > +			  error_at (loc, "call to consteval function %qE is "
> > +				    "not a constant expression", stmt);
> > +			  /* Explain why it's not a constant expression.  */
> > +			  *stmt_p = cxx_constant_value (stmt, complain);
> > +			  maybe_explain_promoted_consteval (loc, fndecl);
> > +			  /* Don't return error_mark_node, it would stop our
> > +			     tree walk.  */
> > +			  break;
> > +			}
> > +		      return error_mark_node;
> > +		    }
> > +		  /* We've evaluated the consteval function call.  */
> > +		  *stmt_p = e;
> > +		}
> > +	      /* We've encountered a function call that may turn out to be
> > +		 consteval later.  Store its caller so that we can ensure
> > +		 that the call is a constant expression.  */
> > +	      else if (!(data->flags & ff_fold_immediate)
> 
> Likewise.

Also fixed.
 
> It also seems odd that the ADDR_EXPR case calls vec_safe_push
> (deferred_escalating_exprs, while the CALL_EXPR case calls
> maybe_store_cfun_for_late_checking, why the different handling?

maybe_store_cfun_for_late_checking saves current_function_decl
so that we can check:

void g (int i) {
  fn (i); // error if fn promotes to consteval
}

> > +		       && immediate_escalating_function_p (fndecl))
> > +		maybe_store_cfun_for_late_checking ();
> >   	    }
> > -	  return error_mark_node;
> > -	}
> >         break;
> >       default:
> > diff --git a/gcc/cp/module.cc b/gcc/cp/module.cc
> > index e3fb2299d93..02c50ac6a11 100644
> > --- a/gcc/cp/module.cc
> > +++ b/gcc/cp/module.cc
> > @@ -5685,6 +5685,8 @@ trees_out::lang_decl_bools (tree t)
> >         WB (lang->u.fn.has_dependent_explicit_spec_p);
> >         WB (lang->u.fn.immediate_fn_p);
> >         WB (lang->u.fn.maybe_deleted);
> > +      /* We do not stream lang->u.implicit_constexpr nor
> > +	 lang->u.escalated_p.  */
> 
> Won't this mean not getting maybe_explain_promoted_consteval for imported
> functions?

I suppose so.  Let's just stream it, then.

Bootstrapped/regtested on x86_64-pc-linux-gnu and powerpc64le-unknown-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:

w.C:11:11: error: call to consteval function 'f<int>(i)' is not a constant expression
   11 |         f (i);
      |         ~~^~~
w.C:11:11: error: 'i' is not a constant expression
w.C:6:22: 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); // id causes f<int> to be promoted to consteval
      |                    ~~^~~

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.

In v1, I addressed the delayed instantiation problem by instantiating
trees early, so that we can escalate functions right away.  That caused
a number of problems, and in certain cases, like consteval-prop3.C, it
can't work, because we need to wait till EOF to see the definition of
the function anyway.  Overeager instantiation tends to cause diagnostic
problems too.

In v2, I attempted to move the escalation to the gimplifier, at which
point all templates have been instantiated.  That attempt flopped,
however, because once we've gimplified a function, its body is discarded
and as a consequence, you can no longer evaluate a call to that function
which is required for escalating, which needs to decide if a call is
a constant expression or not.

Therefore, we have to perform the escalation before gimplifying, but
after instantiate_pending_templates.  That's not easy because we have
no way to walk all the trees.  In the v2 patch, I use two vectors: one
to store function decls that may become consteval, and another to
remember references to immediate-escalating functions.  Unfortunately
the latter must also stash functions that call immediate-escalating
functions.  Consider:

  int g(int i)
  {
    f<int>(i); // f is immediate-escalating
  }

where g itself is not immediate-escalating, but we have to make sure
that if f gets promoted to consteval, we give an error.

A new option, -fno-immediate-escalation, is provided to suppress
escalating functions.

v2 also adds a new flag, DECL_ESCALATED_P, so that we don't escalate
a function multiple times, and so that we can distinguish between
explicitly consteval functions and functions that have been promoted
to consteval.

In v3, I removed one of the new vectors and changed the other one
to a hash set.  This version also contains numerous cleanups.

	PR c++/107687
	PR c++/110997

gcc/c-family/ChangeLog:

	* c-cppbuiltin.cc (c_cpp_builtins): Update __cpp_consteval.
	* c.opt (fimmediate-escalation): New option.

gcc/cp/ChangeLog:

	* call.cc (in_immediate_context): No longer static.
	* constexpr.cc (cxx_eval_call_expression): Adjust assert.
	* cp-gimplify.cc (deferred_escalating_exprs): New vec.
	(remember_escalating_expr): New.
	(enum fold_flags): Remove ff_fold_immediate.
	(immediate_escalating_function_p): New.
	(promote_function_to_consteval): New.
	(maybe_promote_function_to_consteval): New.
	(maybe_store_cfun_for_late_checking): New.
	(find_escalating_expr_r): New.
	(maybe_explain_promoted_consteval): New.
	(maybe_escalate_decl_and_cfun): New.
	(cp_gimplify_expr) <case CALL_EXPR>: Assert we've handled all
	immediate invocations.
	(taking_address_of_imm_fn_error): New.
	(cp_fold_immediate_r): Merge ADDR_EXPR and PTRMEM_CST cases.  Implement
	P2564 - promoting functions to consteval.
	<case CALL_EXPR>: Implement P2564 - promoting functions to consteval.
	(cp_fold_immediate): Return true if any errors were emitted.
	(maybe_store_immediate_escalating_fn): New.
	(process_pending_immediate_escalating_fns): New.
	(check_immediate_escalating_refs): New.
	* cp-tree.h (struct lang_decl_fn): Add escalated_p bit-field.
	(DECL_ESCALATION_CHECKED_P): New.
	(immediate_invocation_p): Declare.
	(check_immediate_escalating_refs): Likewise.
	(maybe_store_immediate_escalating_fn): Likewise.
	(process_pending_immediate_escalating_fns): Likewise.
	* decl.cc (finish_function): Call maybe_store_immediate_escalating_fn.
	* decl2.cc (c_parse_final_cleanups): Set at_eof to 2 after all
	templates have been instantiated; and to 3 at the end of the function.
	Call process_pending_immediate_escalating_fns and
	check_immediate_escalating_refs.
	* error.cc (dump_template_bindings): Check at_eof against an updated
	value.
	* module.cc (trees_out::lang_decl_bools): Stream escalated_p.
	(trees_in::lang_decl_bools): Likewise.
	* pt.cc (push_tinst_level_loc): Set at_eof to 3, not 2.
	* typeck.cc (cp_build_addr_expr_1): Don't check
	DECL_IMMEDIATE_FUNCTION_P.

gcc/ChangeLog:

	* doc/invoke.texi: Document -fno-immediate-escalation.

libstdc++-v3/ChangeLog:

	* testsuite/18_support/comparisons/categories/zero_neg.cc: Add
	dg-prune-output.
	* testsuite/std/format/string_neg.cc: Add dg-error.

gcc/testsuite/ChangeLog:

	* 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/consteval-memfn1.C: Add dg-error.
	* g++.dg/cpp2a/consteval11.C: Likewise.
	* g++.dg/cpp2a/consteval3.C: Adjust dg-error.
	* g++.dg/cpp2a/consteval34.C: Add dg-error.
	* g++.dg/cpp2a/consteval9.C: Likewise.
	* g++.dg/cpp2a/feat-cxx2a.C: Adjust expected value of __cpp_consteval.
	* g++.dg/cpp2a/spaceship-synth9.C: Adjust dg-error.
	* 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-prop18.C: New test.
	* g++.dg/cpp2a/consteval-prop19.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/c-family/c.opt                            |   4 +
 gcc/cp/call.cc                                |   2 +-
 gcc/cp/constexpr.cc                           |   4 +-
 gcc/cp/cp-gimplify.cc                         | 438 ++++++++++++++++--
 gcc/cp/cp-tree.h                              |  18 +-
 gcc/cp/decl.cc                                |   5 +-
 gcc/cp/decl2.cc                               |  20 +-
 gcc/cp/error.cc                               |   2 +-
 gcc/cp/module.cc                              |   4 +
 gcc/cp/pt.cc                                  |   2 +-
 gcc/cp/typeck.cc                              |   6 +-
 gcc/doc/invoke.texi                           |  34 ++
 gcc/testsuite/g++.dg/cpp23/consteval-if10.C   |   7 +-
 gcc/testsuite/g++.dg/cpp23/consteval-if2.C    |  14 +-
 gcc/testsuite/g++.dg/cpp23/feat-cxx2b.C       |   4 +-
 gcc/testsuite/g++.dg/cpp26/feat-cxx26.C       |   4 +-
 gcc/testsuite/g++.dg/cpp2a/consteval-memfn1.C |   3 +
 gcc/testsuite/g++.dg/cpp2a/consteval-prop1.C  | 169 +++++++
 gcc/testsuite/g++.dg/cpp2a/consteval-prop10.C |  41 ++
 gcc/testsuite/g++.dg/cpp2a/consteval-prop11.C |  49 ++
 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 |  78 ++++
 gcc/testsuite/g++.dg/cpp2a/consteval-prop15.C | 107 +++++
 gcc/testsuite/g++.dg/cpp2a/consteval-prop16.C |  73 +++
 gcc/testsuite/g++.dg/cpp2a/consteval-prop17.C |  17 +
 gcc/testsuite/g++.dg/cpp2a/consteval-prop18.C |  20 +
 gcc/testsuite/g++.dg/cpp2a/consteval-prop19.C |   7 +
 gcc/testsuite/g++.dg/cpp2a/consteval-prop2.C  |  90 ++++
 gcc/testsuite/g++.dg/cpp2a/consteval-prop3.C  |  27 ++
 gcc/testsuite/g++.dg/cpp2a/consteval-prop4.C  |  30 ++
 gcc/testsuite/g++.dg/cpp2a/consteval-prop5.C  |  27 ++
 gcc/testsuite/g++.dg/cpp2a/consteval-prop6.C  |  59 +++
 gcc/testsuite/g++.dg/cpp2a/consteval-prop7.C  |  76 +++
 gcc/testsuite/g++.dg/cpp2a/consteval-prop8.C  |  82 ++++
 gcc/testsuite/g++.dg/cpp2a/consteval-prop9.C  |  67 +++
 gcc/testsuite/g++.dg/cpp2a/consteval11.C      |  18 +
 gcc/testsuite/g++.dg/cpp2a/consteval3.C       |   4 +-
 gcc/testsuite/g++.dg/cpp2a/consteval34.C      |   8 +
 gcc/testsuite/g++.dg/cpp2a/consteval36.C      |  26 +-
 gcc/testsuite/g++.dg/cpp2a/consteval9.C       |   2 +
 gcc/testsuite/g++.dg/cpp2a/feat-cxx2a.C       |   4 +-
 gcc/testsuite/g++.dg/cpp2a/spaceship-synth9.C |   2 +-
 .../comparisons/categories/zero_neg.cc        |   1 +
 .../testsuite/std/format/string_neg.cc        |   2 +-
 46 files changed, 1618 insertions(+), 94 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-prop18.C
 create mode 100644 gcc/testsuite/g++.dg/cpp2a/consteval-prop19.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

diff --git a/gcc/c-family/c-cppbuiltin.cc b/gcc/c-family/c-cppbuiltin.cc
index 8904ac85015..3c8eaed7ad3 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/c-family/c.opt b/gcc/c-family/c.opt
index 29d3d789a49..007a8c1c61b 100644
--- a/gcc/c-family/c.opt
+++ b/gcc/c-family/c.opt
@@ -1878,6 +1878,10 @@ fhuge-objects
 C++ ObjC++ WarnRemoved
 No longer supported.
 
+fimmediate-escalation
+C++ ObjC++ Var(flag_immediate_escalation) Init(1)
+Implement P2564 for consteval propagation.
+
 fimplement-inlines
 C++ ObjC++ Var(flag_implement_inlines) Init(1)
 Export functions even if they can be inlined.
diff --git a/gcc/cp/call.cc b/gcc/cp/call.cc
index 2eb54b5b6ed..8799de6e28f 100644
--- a/gcc/cp/call.cc
+++ b/gcc/cp/call.cc
@@ -9708,7 +9708,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
diff --git a/gcc/cp/constexpr.cc b/gcc/cp/constexpr.cc
index c05760e6789..2e9f1c014ac 100644
--- a/gcc/cp/constexpr.cc
+++ b/gcc/cp/constexpr.cc
@@ -3128,11 +3128,11 @@ cxx_eval_call_expression (const constexpr_ctx *ctx, tree t,
 	/* OK */;
       else if (!DECL_SAVED_TREE (fun))
 	{
-	  /* When at_eof >= 2, cgraph has started throwing away
+	  /* When at_eof >= 3, cgraph has started throwing away
 	     DECL_SAVED_TREE, so fail quietly.  FIXME we get here because of
 	     late code generation for VEC_INIT_EXPR, which needs to be
 	     completely reconsidered.  */
-	  gcc_assert (at_eof >= 2 && ctx->quiet);
+	  gcc_assert (at_eof >= 3 && ctx->quiet);
 	  *non_constant_p = true;
 	}
       else if (tree copy = get_fundef_copy (new_call.fundef))
diff --git a/gcc/cp/cp-gimplify.cc b/gcc/cp/cp-gimplify.cc
index 9375a116f06..69bafa69b7e 100644
--- a/gcc/cp/cp-gimplify.cc
+++ b/gcc/cp/cp-gimplify.cc
@@ -43,6 +43,21 @@ along with GCC; see the file COPYING3.  If not see
 #include "omp-general.h"
 #include "opts.h"
 
+/* Keep track of forward references to immediate-escalating functions in
+   case they become consteval.  This vector contains ADDR_EXPRs and
+   PTRMEM_CSTs; it also stores FUNCTION_DECLs that had an escalating
+   function call in them, to check that they can be evaluated to a constant,
+   and immediate-escalating functions that may become consteval.  */
+static GTY(()) hash_set<tree> *deferred_escalating_exprs;
+
+static void
+remember_escalating_expr (tree t)
+{
+  if (!deferred_escalating_exprs)
+    deferred_escalating_exprs = hash_set<tree>::create_ggc (37);
+  deferred_escalating_exprs->add (t);
+}
+
 /* Flags for cp_fold and cp_fold_r.  */
 
 enum fold_flags {
@@ -53,8 +68,6 @@ enum fold_flags {
      definitely not in a manifestly constant-evaluated
      context.  */
   ff_mce_false = 1 << 1,
-  /* Whether we're being called from cp_fold_immediate.  */
-  ff_fold_immediate = 1 << 2,
 };
 
 using fold_flags_t = int;
@@ -428,6 +441,173 @@ lvalue_has_side_effects (tree e)
     return TREE_SIDE_EFFECTS (e);
 }
 
+/* Return true if FN is an immediate-escalating function.  */
+
+static bool
+immediate_escalating_function_p (tree fn)
+{
+  if (!fn || !flag_immediate_escalation)
+    return false;
+
+  gcc_checking_assert (TREE_CODE (fn) == FUNCTION_DECL);
+
+  if (DECL_IMMEDIATE_FUNCTION_P (fn) || DECL_ESCALATION_CHECKED_P (fn))
+    return false;
+
+  /* An immediate-escalating function is
+      -- the call operator of a lambda that is not declared with the consteval
+	 specifier  */
+  if (LAMBDA_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))
+    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.  */
+
+static void
+promote_function_to_consteval (tree fn)
+{
+  SET_DECL_IMMEDIATE_FUNCTION_P (fn);
+  DECL_ESCALATION_CHECKED_P (fn) = true;
+  tree clone;
+  FOR_EACH_CLONE (clone, fn)
+    {
+      SET_DECL_IMMEDIATE_FUNCTION_P (clone);
+      DECL_ESCALATION_CHECKED_P (clone) = true;
+    }
+}
+
+/* Promote FN to an immediate function, including its clones, if it is
+   an immediate-escalating function.  Return true if we did promote;
+   false otherwise.  */
+
+static bool
+maybe_promote_function_to_consteval (tree fn)
+{
+  if (immediate_escalating_function_p (fn))
+    {
+      promote_function_to_consteval (fn);
+      return true;
+    }
+
+  return false;
+}
+
+/* Remember that the current function declaration contains a call to
+   a function that might be promoted to consteval later.  */
+
+static void
+maybe_store_cfun_for_late_checking ()
+{
+  if (flag_immediate_escalation
+      && current_function_decl
+      && !immediate_escalating_function_p (current_function_decl))
+    remember_escalating_expr (current_function_decl);
+}
+
+/* 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)
+{
+  /* The function whose body we're traversing.  Used to promote the current
+     function to consteval.  */
+  tree caller = data ? *static_cast<tree *>(data) : NULL_TREE;
+  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
+	  || ADDR_EXPR_DENOTES_CALL_P (t))
+	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)
+    goto bail;
+
+  /* Not consteval yet, but could be.  Have to look deeper.  */
+  if (immediate_escalating_function_p (decl))
+    {
+      /* Set before the actual walk to avoid endless recursion.  */
+      DECL_ESCALATION_CHECKED_P (decl) = true;
+      cp_walk_tree (&DECL_SAVED_TREE (decl), find_escalating_expr_r,
+		    caller ? &decl : nullptr, 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.  */
+      if (caller)
+	promote_function_to_consteval (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)
+{
+  if (DECL_ESCALATION_CHECKED_P (fn))
+    {
+      /* See if we can figure out what made the function consteval.  */
+      tree x = cp_walk_tree (&DECL_SAVED_TREE (fn), find_escalating_expr_r,
+			     NULL_TREE, 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.  */
 
@@ -485,6 +665,40 @@ cp_gimplify_arg (tree *arg_p, gimple_seq *pre_p, location_t call_location,
 
 }
 
+/* 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.
+   EVALP is where we may store the result of cxx_constant_value so that we
+   don't have to evaluate the same tree again in cp_fold_immediate_r.  */
+
+static void
+maybe_escalate_decl_and_cfun (tree decl, tree call, tree *evalp)
+{
+  if (cp_unevaluated_operand)
+    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 (immediate_escalating_function_p (decl))
+    {
+      cp_walk_tree (&DECL_SAVED_TREE (decl), find_escalating_expr_r,
+		    &decl, nullptr);
+      DECL_ESCALATION_CHECKED_P (decl) = true;
+    }
+
+  /* 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.  */
+      && (*evalp = cxx_constant_value (call, tf_none),
+	  *evalp == error_mark_node))
+    maybe_promote_function_to_consteval (current_function_decl);
+}
+
 /* Do C++-specific gimplification.  Args are as for gimplify_expr.  */
 
 int
@@ -746,7 +960,9 @@ cp_gimplify_expr (tree *expr_p, gimple_seq *pre_p, gimple_seq *post_p)
       if (ret != GS_ERROR)
 	{
 	  tree decl = cp_get_callee_fndecl_nofold (*expr_p);
-	  if (decl && fndecl_built_in_p (decl, BUILT_IN_FRONTEND))
+	  if (!decl)
+	    break;
+	  if (fndecl_built_in_p (decl, BUILT_IN_FRONTEND))
 	    switch (DECL_FE_FUNCTION_CODE (decl))
 	      {
 	      case CP_BUILT_IN_IS_CONSTANT_EVALUATED:
@@ -771,6 +987,9 @@ cp_gimplify_expr (tree *expr_p, gimple_seq *pre_p, gimple_seq *post_p)
 	      default:
 		break;
 	      }
+	  else
+	    /* All consteval functions should have been processed by now.  */
+	    gcc_checking_assert (!immediate_invocation_p (decl));
 	}
       break;
 
@@ -1031,6 +1250,20 @@ struct cp_genericize_data
   bool handle_invisiref_parm_p;
 };
 
+/* Emit an error about taking the address of an immediate function.
+   EXPR is the whole expression; DECL is the immediate function.  */
+
+static void
+taking_address_of_imm_fn_error (tree expr, tree decl)
+{
+  auto_diagnostic_group d;
+  const location_t loc = (TREE_CODE (expr) == PTRMEM_CST
+			  ? PTRMEM_CST_LOCATION (expr)
+			  : EXPR_LOCATION (expr));
+  error_at (loc, "taking address of an immediate function %qD", decl);
+  maybe_explain_promoted_consteval (loc, decl);
+}
+
 /* A subroutine of cp_fold_r to handle immediate functions.  */
 
 static tree
@@ -1041,31 +1274,51 @@ cp_fold_immediate_r (tree *stmt_p, int *walk_subtrees, void *data_)
   /* The purpose of this is not to emit errors for mce_unknown.  */
   const tsubst_flags_t complain = (data->flags & ff_mce_false
 				   ? tf_error : tf_none);
+  const tree_code code = TREE_CODE (stmt);
 
   /* No need to look into types or unevaluated operands.
      NB: This affects cp_fold_r as well.  */
-  if (TYPE_P (stmt) || unevaluated_p (TREE_CODE (stmt)))
+  if (TYPE_P (stmt) || unevaluated_p (code))
     {
       *walk_subtrees = 0;
       return NULL_TREE;
     }
 
-  switch (TREE_CODE (stmt))
+  switch (code)
     {
     case PTRMEM_CST:
-      if (TREE_CODE (PTRMEM_CST_MEMBER (stmt)) == FUNCTION_DECL
-	  && DECL_IMMEDIATE_FUNCTION_P (PTRMEM_CST_MEMBER (stmt)))
-	{
-	  if (!data->pset.add (stmt) && (complain & tf_error))
-	    {
-	      error_at (PTRMEM_CST_LOCATION (stmt),
-			"taking address of an immediate function %qD",
-			PTRMEM_CST_MEMBER (stmt));
-	      *stmt_p = build_zero_cst (TREE_TYPE (stmt));
-	    }
-	  return error_mark_node;
-	}
-      break;
+    case ADDR_EXPR:
+      {
+	tree decl = (code == PTRMEM_CST
+		     ? PTRMEM_CST_MEMBER (stmt)
+		     : TREE_OPERAND (stmt, 0));
+	if (TREE_CODE (decl) != FUNCTION_DECL)
+	  break;
+	if (code == ADDR_EXPR && ADDR_EXPR_DENOTES_CALL_P (stmt))
+	  break;
+	if (immediate_invocation_p (decl))
+	  {
+	    if (maybe_promote_function_to_consteval (current_function_decl))
+	      break;
+	    if (complain & tf_error)
+	      {
+		if (!data->pset.add (stmt))
+		  {
+		    taking_address_of_imm_fn_error (stmt, decl);
+		    *stmt_p = build_zero_cst (TREE_TYPE (stmt));
+		  }
+		/* If we're giving hard errors, continue the walk rather than
+		   bailing out after the first error.  */
+		break;
+	      }
+	    return error_mark_node;
+	  }
+	/* Not consteval yet, but could become one, in which case it's invalid
+	   to take its address.  */
+	else if (immediate_escalating_function_p (decl))
+	  remember_escalating_expr (stmt);
+	break;
+      }
 
     /* Expand immediate invocations.  */
     case CALL_EXPR:
@@ -1073,33 +1326,54 @@ cp_fold_immediate_r (tree *stmt_p, int *walk_subtrees, void *data_)
       if (tree fn = cp_get_callee (stmt))
 	if (TREE_CODE (fn) != ADDR_EXPR || ADDR_EXPR_DENOTES_CALL_P (fn))
 	  if (tree fndecl = cp_get_fndecl_from_callee (fn, /*fold*/false))
-	    if (DECL_IMMEDIATE_FUNCTION_P (fndecl))
-	      {
-		stmt = cxx_constant_value (stmt, complain);
-		if (stmt == error_mark_node)
-		  {
-		    if (complain & tf_error)
-		      *stmt_p = error_mark_node;
-		    return error_mark_node;
-		  }
-		*stmt_p = stmt;
-	      }
-      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))
-	{
-	  if (complain & tf_error)
 	    {
-	      error_at (EXPR_LOCATION (stmt),
-			"taking address of an immediate function %qD",
-			TREE_OPERAND (stmt, 0));
-	      *stmt_p = build_zero_cst (TREE_TYPE (stmt));
+	      tree eval = NULL_TREE;
+	      /* Escalate once all templates have been instantiated.  */
+	      if (at_eof > 1)
+		maybe_escalate_decl_and_cfun (fndecl, stmt, &eval);
+
+	      /* [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 STMT does not need to produce
+		 a constant expression.  */
+	      if (immediate_invocation_p (fndecl))
+		{
+		  tree e = eval ? eval : cxx_constant_value (stmt, tf_none);
+		  if (e == error_mark_node)
+		    {
+		      if (maybe_promote_function_to_consteval
+			  (current_function_decl))
+			break;
+		      if (complain & tf_error)
+			{
+			  auto_diagnostic_group d;
+			  location_t loc = cp_expr_loc_or_input_loc (stmt);
+			  error_at (loc, "call to consteval function %qE is "
+				    "not a constant expression", stmt);
+			  /* Explain why it's not a constant expression.  */
+			  *stmt_p = cxx_constant_value (stmt, complain);
+			  maybe_explain_promoted_consteval (loc, fndecl);
+			  /* Don't return error_mark_node, it would stop our
+			     tree walk.  */
+			  break;
+			}
+		      return error_mark_node;
+		    }
+		  /* We've evaluated the consteval function call.  */
+		  *stmt_p = e;
+		}
+	      /* We've encountered a function call that may turn out to be
+		 consteval later.  Store its caller so that we can ensure
+		 that the call is a constant expression.  */
+	      else if (immediate_escalating_function_p (fndecl))
+		maybe_store_cfun_for_late_checking ();
 	    }
-	  return error_mark_node;
-	}
       break;
 
     default:
@@ -1111,7 +1385,8 @@ cp_fold_immediate_r (tree *stmt_p, int *walk_subtrees, void *data_)
 
 /* A wrapper around cp_fold_immediate_r.  Return true if we found
    a non-constant immediate function, or taking the address of an
-   immediate function.  */
+   immediate function.  If ESCALATE_P, tell cp_fold_immediate_r to
+   escalate immediate-escalating functions.  */
 
 bool
 cp_fold_immediate (tree *tp, mce_value manifestly_const_eval)
@@ -1119,12 +1394,14 @@ cp_fold_immediate (tree *tp, mce_value manifestly_const_eval)
   if (cxx_dialect <= cxx17)
     return false;
 
-  fold_flags_t flags = ff_fold_immediate;
+  fold_flags_t flags = ff_none;
   if (manifestly_const_eval == mce_false)
     flags |= ff_mce_false;
 
   cp_fold_data data (flags);
-  return !!cp_walk_tree_without_duplicates (tp, cp_fold_immediate_r, &data);
+  int save_errorcount = errorcount;
+  tree r = cp_walk_tree_without_duplicates (tp, cp_fold_immediate_r, &data);
+  return r != NULL_TREE || errorcount > save_errorcount;
 }
 
 /* Perform any pre-gimplification folding of C++ front end trees to
@@ -1302,6 +1579,75 @@ cp_fold_function (tree fndecl)
   cp_walk_tree (&DECL_SAVED_TREE (fndecl), cp_fold_r, &data, NULL);
 }
 
+/* FN is not a consteval function, but may become one.  Remember to
+   escalate it after all pending templates have been instantiated.  */
+
+void
+maybe_store_immediate_escalating_fn (tree fn)
+{
+  if (cxx_dialect >= cxx20
+      && flag_immediate_escalation
+      && immediate_escalating_function_p (fn))
+    remember_escalating_expr (fn);
+}
+
+/* We've stashed immediate-escalating functions.  Now see if they indeed
+   ought to be promoted to consteval.  */
+
+void
+process_pending_immediate_escalating_fns ()
+{
+  /* This will be null for -fno-immediate-escalation.  */
+  if (!deferred_escalating_exprs)
+    return;
+
+  for (auto e : *deferred_escalating_exprs)
+    if (TREE_CODE (e) == FUNCTION_DECL)
+      {
+	if (!DECL_ESCALATION_CHECKED_P (e))
+	  {
+	    temp_override<tree> cfd (current_function_decl, e);
+	    cp_fold_immediate (&DECL_SAVED_TREE (e), mce_false);
+	  }
+	if (DECL_IMMEDIATE_FUNCTION_P (e))
+	  deferred_escalating_exprs->remove (e);
+      }
+}
+
+/* We've escalated every function that could have been promoted to
+   consteval.  Check that we are not taking the address of a consteval
+   function.  */
+
+void
+check_immediate_escalating_refs ()
+{
+  /* This will be null for -fno-immediate-escalation.  */
+  if (!deferred_escalating_exprs)
+    return;
+
+  for (auto ref : *deferred_escalating_exprs)
+    {
+      if (TREE_CODE (ref) == FUNCTION_DECL)
+	/* We saw a function call to an immediate-escalating function in
+	   the body of REF.  Check that it's a constant if it was promoted
+	   to consteval.  */
+	{
+	  temp_override<tree> cfd (current_function_decl, ref);
+	  cp_fold_immediate (&DECL_SAVED_TREE (ref), mce_false);
+	}
+      else
+	{
+	  tree decl = (TREE_CODE (ref) == PTRMEM_CST
+		       ? PTRMEM_CST_MEMBER (ref)
+		       : TREE_OPERAND (ref, 0));
+	  if (DECL_IMMEDIATE_FUNCTION_P (decl))
+	    taking_address_of_imm_fn_error (ref, decl);
+	}
+    }
+
+  deferred_escalating_exprs = nullptr;
+}
+
 /* Turn SPACESHIP_EXPR EXPR into GENERIC.  */
 
 static tree genericize_spaceship (tree expr)
diff --git a/gcc/cp/cp-tree.h b/gcc/cp/cp-tree.h
index 98b29e9cf81..d4ec3c3e7c1 100644
--- a/gcc/cp/cp-tree.h
+++ b/gcc/cp/cp-tree.h
@@ -2938,8 +2938,9 @@ struct GTY(()) lang_decl_fn {
   unsigned maybe_deleted : 1;
   unsigned coroutine_p : 1;
   unsigned implicit_constexpr : 1;
+  unsigned escalated_p : 1;
 
-  unsigned spare : 9;
+  unsigned spare : 8;
 
   /* 32-bits padding on 64-bit host.  */
 
@@ -3391,6 +3392,14 @@ struct GTY(()) lang_decl {
 #define DECL_MAYBE_DELETED(NODE) \
   (LANG_DECL_FN_CHECK (NODE)->maybe_deleted)
 
+/* Nonzero for FUNCTION_DECL means that this function's body has been
+   checked for immediate-escalating expressions and maybe promoted.  It
+   does *not* mean the function is consteval.  It must not be set in
+   a function that was marked consteval by the user, so that we can
+   distinguish between explicitly consteval functions and promoted consteval
+   functions.  */
+#define DECL_ESCALATION_CHECKED_P(NODE) (LANG_DECL_FN_CHECK (NODE)->escalated_p)
+
 /* True (in a FUNCTION_DECL) if NODE is a virtual function that is an
    invalid overrider for a function from a base class.  Once we have
    complained about an invalid overrider we avoid complaining about it
@@ -5849,7 +5858,8 @@ extern GTY(()) vec<tree, va_gc> *keyed_classes;
 
 \f
 /* Nonzero if we're done parsing and into end-of-file activities.
-   Two if we're done with front-end processing.  */
+   2 if all templates have been instantiated.
+   3 if we're done with front-end processing.  */
 
 extern int at_eof;
 
@@ -6741,6 +6751,7 @@ 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 tree build_cxx_call			(tree, int, tree *,
 						 tsubst_flags_t,
 						 tree = NULL_TREE);
@@ -8384,6 +8395,9 @@ extern bool simple_empty_class_p		(tree, tree, tree_code);
 extern tree fold_builtin_source_location	(const_tree);
 extern tree get_source_location_impl_type	();
 extern bool cp_fold_immediate			(tree *, mce_value);
+extern void check_immediate_escalating_refs	();
+extern void maybe_store_immediate_escalating_fn	(tree);
+extern void process_pending_immediate_escalating_fns ();
 
 /* in name-lookup.cc */
 extern tree strip_using_decl                    (tree);
diff --git a/gcc/cp/decl.cc b/gcc/cp/decl.cc
index 16af59de696..2fa1a7400e8 100644
--- a/gcc/cp/decl.cc
+++ b/gcc/cp/decl.cc
@@ -18343,7 +18343,10 @@ finish_function (bool inline_p)
   if (!processing_template_decl
       && !DECL_IMMEDIATE_FUNCTION_P (fndecl)
       && !DECL_OMP_DECLARE_REDUCTION_P (fndecl))
-    cp_fold_function (fndecl);
+    {
+      cp_fold_function (fndecl);
+      maybe_store_immediate_escalating_fn (fndecl);
+    }
 
   /* Set up the named return value optimization, if we can.  Candidate
      variables are selected in check_return_expr.  */
diff --git a/gcc/cp/decl2.cc b/gcc/cp/decl2.cc
index 0aa1e355972..6df4d8adf2c 100644
--- a/gcc/cp/decl2.cc
+++ b/gcc/cp/decl2.cc
@@ -169,7 +169,9 @@ typedef hash_map<unsigned/*Priority*/, tree/*List*/,
    one for init.  The fini table is only ever used when !cxa_atexit.  */
 static GTY(()) priority_map_t *static_init_fini_fns[2];
 
-/* Nonzero if we're done parsing and into end-of-file activities.  */
+/* Nonzero if we're done parsing and into end-of-file activities.
+   2 if all templates have been instantiated.
+   3 if we're done with front-end processing.  */
 
 int at_eof;
 
@@ -4981,6 +4983,7 @@ c_parse_final_cleanups (void)
   tree decl;
 
   locus_at_end_of_parsing = input_location;
+  /* We're done parsing.  */
   at_eof = 1;
 
   /* Bad parse errors.  Just forget about it.  */
@@ -5246,6 +5249,9 @@ c_parse_final_cleanups (void)
 	reconsider = true;
     }
 
+  /* All templates have been instantiated.  */
+  at_eof = 2;
+
   void *module_cookie = finish_module_processing (parse_in);
 
   lower_var_init ();
@@ -5288,7 +5294,15 @@ c_parse_final_cleanups (void)
   if (static_init_fini_fns[true])
     for (auto iter : *static_init_fini_fns[true])
       iter.second = nreverse (iter.second);
-  
+
+  /* Now we've instantiated all templates.  Now we can escalate the functions
+     we squirreled away earlier.  */
+  if (cxx_dialect >= cxx20)
+    {
+      process_pending_immediate_escalating_fns ();
+      check_immediate_escalating_refs ();
+    }
+
   /* Then, do the Objective-C stuff.  This is where all the
      Objective-C module stuff gets generated (symtab,
      class/protocol/selector lists etc).  This must be done after C++
@@ -5370,7 +5384,7 @@ c_parse_final_cleanups (void)
   timevar_start (TV_PHASE_PARSING);
 
   /* Indicate that we're done with front end processing.  */
-  at_eof = 2;
+  at_eof = 3;
 }
 
 /* Perform any post compilation-proper cleanups for the C++ front-end.
diff --git a/gcc/cp/error.cc b/gcc/cp/error.cc
index 0ed69bca6fc..bc022c8abaa 100644
--- a/gcc/cp/error.cc
+++ b/gcc/cp/error.cc
@@ -478,7 +478,7 @@ dump_template_bindings (cxx_pretty_printer *pp, tree parms, tree args,
 
   /* Don't try to do this once cgraph starts throwing away front-end
      information.  */
-  if (at_eof >= 2)
+  if (at_eof >= 3)
     return;
 
   FOR_EACH_VEC_SAFE_ELT (typenames, i, t)
diff --git a/gcc/cp/module.cc b/gcc/cp/module.cc
index c1c8c226bc1..72946f1b065 100644
--- a/gcc/cp/module.cc
+++ b/gcc/cp/module.cc
@@ -5683,6 +5683,8 @@ trees_out::lang_decl_bools (tree t)
       WB (lang->u.fn.has_dependent_explicit_spec_p);
       WB (lang->u.fn.immediate_fn_p);
       WB (lang->u.fn.maybe_deleted);
+      WB (lang->u.fn.escalated_p);
+      /* We do not stream lang->u.fn.implicit_constexpr.  */
       goto lds_min;
 
     case lds_decomp:  /* lang_decl_decomp.  */
@@ -5751,6 +5753,8 @@ trees_in::lang_decl_bools (tree t)
       RB (lang->u.fn.has_dependent_explicit_spec_p);
       RB (lang->u.fn.immediate_fn_p);
       RB (lang->u.fn.maybe_deleted);
+      RB (lang->u.fn.escalated_p);
+      /* We do not stream lang->u.fn.implicit_constexpr.  */
       goto lds_min;
 
     case lds_decomp:  /* lang_decl_decomp.  */
diff --git a/gcc/cp/pt.cc b/gcc/cp/pt.cc
index 86c95b278ba..515b00f9b01 100644
--- a/gcc/cp/pt.cc
+++ b/gcc/cp/pt.cc
@@ -11130,7 +11130,7 @@ push_tinst_level_loc (tree tldcl, tree targs, location_t loc)
   if (tinst_depth >= max_tinst_depth)
     {
       /* Tell error.cc not to try to instantiate any templates.  */
-      at_eof = 2;
+      at_eof = 3;
       fatal_error (input_location,
 		   "template instantiation depth exceeds maximum of %d"
 		   " (use %<-ftemplate-depth=%> to increase the maximum)",
diff --git a/gcc/cp/typeck.cc b/gcc/cp/typeck.cc
index 49afbd8fb5e..380826ca1e9 100644
--- a/gcc/cp/typeck.cc
+++ b/gcc/cp/typeck.cc
@@ -7228,11 +7228,9 @@ cp_build_addr_expr_1 (tree arg, bool strict_lvalue, tsubst_flags_t complain)
 			      complain);
     }
 
-  /* For addresses of immediate functions ensure we have EXPR_LOCATION
-     set for possible later diagnostics.  */
+  /* Ensure we have EXPR_LOCATION 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)))
+      && TREE_CODE (TREE_OPERAND (val, 0)) == FUNCTION_DECL)
     SET_EXPR_LOCATION (val, input_location);
 
   return val;
diff --git a/gcc/doc/invoke.texi b/gcc/doc/invoke.texi
index 6e776a0faa1..ae349782862 100644
--- a/gcc/doc/invoke.texi
+++ b/gcc/doc/invoke.texi
@@ -219,6 +219,7 @@ in the following sections.
 -fno-elide-constructors
 -fno-enforce-eh-specs
 -fno-gnu-keywords
+-fno-immediate-escalation
 -fno-implicit-templates
 -fno-implicit-inline-templates
 -fno-implement-inlines
@@ -3375,6 +3376,39 @@ word as an identifier.  You can use the keyword @code{__typeof__} instead.
 This option is implied by the strict ISO C++ dialects: @option{-ansi},
 @option{-std=c++98}, @option{-std=c++11}, etc.
 
+@opindex fno-immediate-escalation
+@opindex fimmediate-escalation
+@item -fno-immediate-escalation
+Do not enable immediate function escalation whereby certain functions
+can be promoted to consteval, as specified in P2564R3.  For example:
+
+@example
+consteval int id(int i) @{ return i; @}
+
+constexpr int f(auto t)
+@{
+  return t + id(t); // id causes f<int> to be promoted to consteval
+@}
+
+void g(int i)
+@{
+  f (3);
+@}
+@end example
+
+compiles in C++20: @code{f} is an immediate-escalating function (due to
+the @code{auto} it is a function template and is declared @code{constexpr})
+and @code{id(t)} is an immediate-escalating expression, so @code{f} is
+promoted to @code{consteval}.  Consequently, the call to @code{id(t)}
+is in an immediate context, so doesn't have to produce a constant (that
+is the mechanism allowing consteval function composition).  However,
+with @option{-fno-immediate-escalation}, @code{f} is not promoted to
+@code{consteval}, and since the call to consteval function @code{id(t)}
+is not a constant expression, the compiler rejects the code.
+
+This option is turned on by default; it is only effective in C++20 mode
+or later.
+
 @opindex fimplicit-constexpr
 @item -fimplicit-constexpr
 Make inline functions implicitly constexpr, if they satisfy the
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 b2c5472b7de..3b258711ce6 100644
--- a/gcc/testsuite/g++.dg/cpp23/consteval-if2.C
+++ b/gcc/testsuite/g++.dg/cpp23/consteval-if2.C
@@ -33,7 +33,7 @@ baz (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);	// { dg-error "not a constant expression" }
     }
   else
     {
@@ -45,11 +45,11 @@ baz (int x)
     }
   else
     {
-      r += foo (8 * x);	// { dg-error "'x' is not a constant expression" }
+      r += foo (8 * x);	// { dg-error "is not a constant expression" }
     }
   if ! consteval	// { dg-warning "'if consteval' only available with" "" { target c++20_only } }
     {
-      r += foo (32 * x);// { dg-error "'x' is not a constant expression" }
+      r += foo (32 * x);// { dg-error "not a constant expression" }
     }
   if consteval		// { dg-warning "'if consteval' only available with" "" { target c++20_only } }
     {
@@ -98,7 +98,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
     {
@@ -110,11 +110,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 } }
     {
@@ -126,5 +126,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-memfn1.C b/gcc/testsuite/g++.dg/cpp2a/consteval-memfn1.C
index 63f4f1d526a..edf8c4a139d 100644
--- a/gcc/testsuite/g++.dg/cpp2a/consteval-memfn1.C
+++ b/gcc/testsuite/g++.dg/cpp2a/consteval-memfn1.C
@@ -19,10 +19,13 @@ template<class>
 void VerifyHash(fixed_string s) {
   s.size(0); // { dg-bogus "" }
   s.size(-1); // { dg-message "expansion of" }
+// { dg-error "call to consteval function" "" { target *-*-* } .-1 }
   s.size_static(0); // { dg-bogus "" }
   s.size_static(-1); // { dg-message "expansion of" }
+// { dg-error "call to consteval function" "" { target *-*-* } .-1 }
   fixed_string::size_static(0); // { dg-bogus "" }
   fixed_string::size_static(-1); // { dg-message "expansion of" }
+// { dg-error "call to consteval function" "" { target *-*-* } .-1 }
   s(); // { dg-bogus "" }
 }
 
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..5e7b208113f
--- /dev/null
+++ b/gcc/testsuite/g++.dg/cpp2a/consteval-prop1.C
@@ -0,0 +1,169 @@
+// 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); // { dg-message "immediate-escalating expression .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); // { dg-message "immediate-escalating expression .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; // { dg-message "immediate-escalating expression .id." }
+  (void) p;
+  return t;
+}
+
+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};
+}
+
+constexpr void
+f5_nt (int i)
+{
+  A a{i}; // { dg-error "call to consteval function|not a constant" }
+}
+
+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; // { dg-message ".int g. is not const" }
+
+B b2(f0<int>(cid<int>(g))); // { dg-error "call to consteval function|not usable" }
+
+struct C {
+  consteval C (int) {};
+};
+
+constexpr int
+f7 (auto t)
+{
+  C c(t); // { dg-message "immediate-escalating expression .c.C::C\\(t\\)." }
+  return 0;
+}
+
+int i1 = f7 (g); // { dg-error "call to consteval function|not usable" }
+
+struct Y {
+  int y;
+  int x = id (y);
+  consteval Y (int i) : y (id (i)) {}
+};
+
+Y y1(1);
+Y y2(g); // { dg-error "call to consteval function|not usable" }
+
+struct Y2 {
+  int y;
+  int x = id (y);
+  constexpr Y2 (auto i) : y (id (i)) {}
+};
+
+Y2 y3(1);
+Y2 y4(g); // { dg-error "call to consteval function|not usable" }
+
+auto l1 = [](int i) constexpr {
+  int t = id (i);
+  return id (0);
+};
+
+int (*pl1)(int) = l1; // { dg-error "call to consteval function|returns address of immediate function" }
+
+auto l2 = [](int i) {
+  int t = id (i);
+  return id (0);
+};
+
+int (*pl2)(int) = l2; // { dg-error "call to consteval function|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 "used before its definition" }
+  int a = [] (int u = undef ()) {
+    return u;
+  }();
+} s2; // { dg-error "call to consteval function" }
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..4e33e6e3d0e
--- /dev/null
+++ b/gcc/testsuite/g++.dg/cpp2a/consteval-prop10.C
@@ -0,0 +1,41 @@
+// 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; // { dg-message ".int non_const. is not const" }
+
+template<typename>
+constexpr int
+f2 (int i = id (non_const))
+{
+  return i;
+}
+
+constexpr int
+f3 (auto)
+{
+  return f2<int>(); // { dg-message "contains an immediate-escalating expression .id\\(non_const\\)." }
+}
+
+auto a = &f3<int>; // { dg-error "taking address of an immediate function" }
+
+void
+g (int i)
+{
+  f1<int> (42);
+  f1<int> (i);
+  f1<int> ();
+  f2<int> (42);
+  f2<int> (i);
+  f2<int> (); // { dg-error "call to consteval function .id\\(non_const\\). is not a constant expression" }
+// { dg-error ".non_const. is not usable in a constant expression" "" { target *-*-* } .-1 }
+}
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..aca9675cd53
--- /dev/null
+++ b/gcc/testsuite/g++.dg/cpp2a/consteval-prop11.C
@@ -0,0 +1,49 @@
+// 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
+foobar (auto i)
+{
+  return i + id (i);
+  /* { dg-begin-multiline-output "" }
+   return i + id (i);
+              ~~~^~~
+     { dg-end-multiline-output "" } */
+}
+
+void
+g (int x)
+{
+  foobar (x); // { dg-error "10:call to consteval function .foobar<int>\\(x\\). is not a constant expression" }
+// { dg-error ".x. is not a constant expression" "" { target *-*-* } .-1 }
+  /* { dg-begin-multiline-output "" }
+foobar (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|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..cdc1f6dc862
--- /dev/null
+++ b/gcc/testsuite/g++.dg/cpp2a/consteval-prop14.C
@@ -0,0 +1,78 @@
+// 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\\(\\(i \\* 2\\)\\)." }
+}
+
+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" }
+// { dg-error ".i. is not a constant expression" "" { target *-*-* } .-1 }
+  f1 (42);
+  f2 (i); // { dg-error "call to consteval function .f2<int>\\(i\\). is not a constant expression" }
+// { dg-error ".i. is not a constant expression" "" { target *-*-* } .-1 }
+  f2 (42);
+  f3 (i); // { dg-error "call to consteval function .f3<int>\\(i\\). is not a constant expression" }
+// { dg-error ".i. is not a constant expression" "" { target *-*-* } .-1 }
+  f3 (42);
+  f4 (i); // { dg-error "call to consteval function .f4<int>\\(i\\). is not a constant expression" }
+// { dg-error ".i. is not a constant expression" "" { target *-*-* } .-1 }
+  f4 (42);
+  f5 (i); // { dg-error "call to consteval function .f5<int>\\(i\\). is not a constant expression" }
+// { dg-error ".i. is not a constant expression" "" { target *-*-* } .-1 }
+  f5 (42);
+  f6 (i); // { dg-error "call to consteval function .f6<int>\\(i\\). is not a constant expression" }
+// { dg-error ".i. is not a constant expression" "" { target *-*-* } .-1 }
+  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..3341c510a9f
--- /dev/null
+++ b/gcc/testsuite/g++.dg/cpp2a/consteval-prop15.C
@@ -0,0 +1,107 @@
+// 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;
+}
+
+constexpr int
+f9 (auto i)
+{
+  if consteval {
+    return id(i);
+  }
+  return i;
+}
+
+constexpr int
+f10 (auto i)
+{
+  if not consteval {
+    (void) 0;
+  } else {
+    return id(i);
+  }
+  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" }
+// { dg-error ".non_const. is not a constant expression" "" { target *-*-* } .-1 }
+  f5 (42);
+  f5 (non_const); // { dg-error "call to consteval function .f5<int>\\(non_const\\). is not a constant expression" }
+// { dg-error ".non_const. is not a constant expression" "" { target *-*-* } .-1 }
+  f6 (42);
+  f6 (non_const);
+  f7 (42);
+  f7 (non_const);
+  f8 (42);
+  f8 (non_const);
+  f9 (42);
+  f9 (non_const);
+  f10 (42);
+  f10 (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..47ec9b60b6c
--- /dev/null
+++ b/gcc/testsuite/g++.dg/cpp2a/consteval-prop17.C
@@ -0,0 +1,17 @@
+// P2564R3
+// { dg-do compile { target c++20 } }
+// { dg-options "-fno-immediate-escalation" }
+
+consteval int id(int i) { return i; }
+
+constexpr int
+f (auto i)
+{
+  return id (i); // { dg-error "not a constant expression" }
+}
+
+int
+g ()
+{
+  return f (42);
+}
diff --git a/gcc/testsuite/g++.dg/cpp2a/consteval-prop18.C b/gcc/testsuite/g++.dg/cpp2a/consteval-prop18.C
new file mode 100644
index 00000000000..a18106f8e0f
--- /dev/null
+++ b/gcc/testsuite/g++.dg/cpp2a/consteval-prop18.C
@@ -0,0 +1,20 @@
+// P2564R3
+// { dg-do compile { target c++20 } }
+
+consteval int id(int i) { return i; }
+
+constexpr int
+f (auto t)
+{
+  return t + id (t);
+}
+
+constexpr int
+f2 (auto t)
+{
+  return t + f(t); // { dg-message "immediate-escalating expression .f<int>\\(t\\)." }
+}
+
+int z; // { dg-message "not const" }
+auto y1 = f2 (42);
+auto y2 = f2 (z); // { dg-error "value of .z. is not usable in a constant expression|call to consteval function" }
diff --git a/gcc/testsuite/g++.dg/cpp2a/consteval-prop19.C b/gcc/testsuite/g++.dg/cpp2a/consteval-prop19.C
new file mode 100644
index 00000000000..3ceb05e41f4
--- /dev/null
+++ b/gcc/testsuite/g++.dg/cpp2a/consteval-prop19.C
@@ -0,0 +1,7 @@
+// P2564R3
+// { dg-do compile { target c++20 } }
+
+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;  // { dg-error "call to consteval function|returns address of immediate function" }
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..30129a4a266
--- /dev/null
+++ b/gcc/testsuite/g++.dg/cpp2a/consteval-prop2.C
@@ -0,0 +1,90 @@
+// 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);		// { dg-message "immediate-escalating expression .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 "the value of .x. is not usable in a constant expression" }
+// { dg-message "immediate-escalating expression .id\\(x\\)." "" { target *-*-* } .-1 }
+}
+
+int i = hh<int>();              // { dg-error "call to consteval function|called in a constant expression" }
+				// error: hh<int>() is an immediate-escalating expression
+                                // outside of an immediate-escalating function
+struct A {
+  int x;
+  int y = id(x);
+};
+
+// [expr.const]#example-9 says:
+//   k<int> is not an immediate function because A(42) is a
+//   constant expression and thus not immediate-escalating
+// In the evaluation of A(42), the member x has just been initialized
+// to constant 42.  And A(42) is constant-evaluated because "An aggregate
+// initialization is an immediate invocation if it evaluates a default
+// member initializer that has a subexpression that is an
+// immediate-escalating expression."
+template<class T>
+constexpr int k(int) {
+  return A(42).y;
+}
+
+int
+test (int i)
+{
+  int r = g (42) + g(i);
+  int t = k<int>(42)
+	    + k<int>(i); // { dg-bogus "call to|constant" "" { xfail *-*-* } }
+  return r + t;
+}
+
+// Just like above, but make the call to id(x) actually a constant.
+struct A2 {
+  static constexpr int x = 42;
+  int y = id(x);
+};
+
+template<class T>
+constexpr int k2(int) {
+  return A2(42).y;
+}
+
+int
+test2 (int i)
+{
+  return k2<int>(42) + k2<int>(i);
+}
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..f181cb32942
--- /dev/null
+++ b/gcc/testsuite/g++.dg/cpp2a/consteval-prop3.C
@@ -0,0 +1,27 @@
+// 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);
+
+auto a2 = &f2<char>; // { dg-error "taking address" }
+auto b2 = &f2<int>; // { dg-error "taking address" }
+
+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..3a2e09b17b0
--- /dev/null
+++ b/gcc/testsuite/g++.dg/cpp2a/consteval-prop4.C
@@ -0,0 +1,30 @@
+// P2564R3
+// { dg-do compile { target c++20 } }
+// 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..93ed398d9bf
--- /dev/null
+++ b/gcc/testsuite/g++.dg/cpp2a/consteval-prop6.C
@@ -0,0 +1,59 @@
+// 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(){} // { dg-error "call to consteval function" }
+
+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 "modification of .x. is not a constant expression" }
+};
+
+void
+test ()
+{
+  [[maybe_unused]] X x; // { dg-error "call to consteval function" }
+// { dg-message "promoted to an immediate function" "" { target *-*-* } .-1 }
+}
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..118cf576f14
--- /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 "call to consteval function|not usable" }
+constexpr auto a2 = f2 (non_const); // { dg-error "not a constant|not usable" }
+auto a3 = f3 (42);
+constexpr auto a4 = f4 (42);
+
+void
+g ()
+{
+   auto a5 = f5 (non_const); // { dg-error "not a constant|not usable" }
+   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|not usable" }
+   (void) f10 (42);
+   (void) g1 (non_const); // { dg-error "not a constant|not usable" }
+}
+
+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 "call to consteval function|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..080fc76f26e
--- /dev/null
+++ b/gcc/testsuite/g++.dg/cpp2a/consteval-prop8.C
@@ -0,0 +1,82 @@
+// 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};
+}
+
+void
+f3 (int i)
+{
+  A a{i}; // { dg-error "not a constant expression" }
+}
+
+inline void
+f7 (int i)
+{
+  A a{i}; // { dg-error "not a constant expression" }
+}
+
+constexpr void
+f8 (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/consteval11.C b/gcc/testsuite/g++.dg/cpp2a/consteval11.C
index 091127eabbf..f5de24bdf77 100644
--- a/gcc/testsuite/g++.dg/cpp2a/consteval11.C
+++ b/gcc/testsuite/g++.dg/cpp2a/consteval11.C
@@ -7,9 +7,11 @@ constexpr int a = bar (1);
 constexpr int b = bar (2);		// { dg-message "in 'constexpr' expansion of" }
 constexpr int c = 0 ? bar (3) : 1;
 const int d = bar (4);			// { dg-message "in 'constexpr' expansion of" }
+// { dg-error "call to consteval function" "" { target *-*-* } .-1 }
 const int e = 0 ? bar (5) : 1;
 int f = bar (1);
 int g = bar (6);			// { dg-message "in 'constexpr' expansion of" }
+// { dg-error "call to consteval function" "" { target *-*-* } .-1 }
 int h = 0 ? bar (7) : 1;
 
 void
@@ -19,25 +21,35 @@ foo ()
   constexpr int b = bar (2);		// { dg-message "in 'constexpr' expansion of" }
   constexpr int c = 0 ? bar (3) : 1;
   const int d = bar (4);		// { dg-message "in 'constexpr' expansion of" }
+// { dg-error "call to consteval function" "" { target *-*-* } .-1 }
   const int e = 0 ? bar (5) : 1;
   int f = bar (1);
   int g = bar (6);			// { dg-message "in 'constexpr' expansion of" }
+// { dg-error "call to consteval function" "" { target *-*-* } .-1 }
   int h = 0 ? bar (7) : 1;		// { dg-message "in 'constexpr' expansion of" }
+// { dg-error "call to consteval function" "" { target *-*-* } .-1 }
   h += 0 ? bar (8) : 1;			// { dg-message "in 'constexpr' expansion of" }
+// { dg-error "call to consteval function" "" { target *-*-* } .-1 }
   if (0)
     bar (9);				// { dg-message "in 'constexpr' expansion of" }
+// { dg-error "call to consteval function" "" { target *-*-* } .-1 }
   else
     bar (10);				// { dg-message "in 'constexpr' expansion of" }
+// { dg-error "call to consteval function" "" { target *-*-* } .-1 }
   if (1)
     bar (11);				// { dg-message "in 'constexpr' expansion of" }
+// { dg-error "call to consteval function" "" { target *-*-* } .-1 }
   else
     bar (12);				// { dg-message "in 'constexpr' expansion of" }
+// { dg-error "call to consteval function" "" { target *-*-* } .-1 }
   if constexpr (0)
     bar (13);
   else
     bar (14);				// { dg-message "in 'constexpr' expansion of" }
+// { dg-error "call to consteval function" "" { target *-*-* } .-1 }
   if constexpr (1)
     bar (15);				// { dg-message "in 'constexpr' expansion of" }
+// { dg-error "call to consteval function" "" { target *-*-* } .-1 }
   else
     bar (16);
 }
@@ -120,18 +132,24 @@ quux ()
 {
   if (0)
     bar ((T) 2);				// { dg-message "in 'constexpr' expansion of" }
+// { dg-error "call to consteval function" "" { target *-*-* } .-1 }
   else
     bar ((T) 3);				// { dg-message "in 'constexpr' expansion of" }
+// { dg-error "call to consteval function" "" { target *-*-* } .-1 }
   if (1)
     bar ((T) 4);				// { dg-message "in 'constexpr' expansion of" }
+// { dg-error "call to consteval function" "" { target *-*-* } .-1 }
   else
     bar ((T) 5);				// { dg-message "in 'constexpr' expansion of" }
+// { dg-error "call to consteval function" "" { target *-*-* } .-1 }
   if constexpr (0)
     bar ((T) 6);
   else
     bar ((T) 7);				// { dg-message "in 'constexpr' expansion of" }
+// { dg-error "call to consteval function" "" { target *-*-* } .-1 }
   if constexpr (1)
     bar ((T) 8);				// { dg-message "in 'constexpr' expansion of" }
+// { dg-error "call to consteval function" "" { target *-*-* } .-1 }
   else
     bar ((T) 9);
 }
diff --git a/gcc/testsuite/g++.dg/cpp2a/consteval3.C b/gcc/testsuite/g++.dg/cpp2a/consteval3.C
index 9efac8c8eae..1199e9db623 100644
--- a/gcc/testsuite/g++.dg/cpp2a/consteval3.C
+++ b/gcc/testsuite/g++.dg/cpp2a/consteval3.C
@@ -16,8 +16,8 @@ consteval auto [ b, c ] = S ();		// { dg-error "structured binding declaration c
 int f5 (consteval int x) { return x; }	// { dg-error "a parameter cannot be declared 'consteval'" }
 consteval int f6 (int x) { return x; }
 int d = 6;		// { dg-message "'int d' is not const" }
-int e = f6 (d);		// { dg-error "the value of 'd' is not usable in a constant expression" }
-constexpr int f7 (int x) { return f6 (x); }	// { dg-error "'x' is not a constant expression" }
+int e = f6 (d);		// { dg-error "the value of 'd' is not usable in a constant expression|call to consteval function" }
+constexpr int f7 (int x) { return f6 (x); }	// { dg-error "'x' is not a constant expression|call to consteval function" }
 constexpr int f = f7 (5);
 using fnptr = int (int);
 fnptr *g = f6;		// { dg-error "taking address of an immediate function 'consteval int f6\\(int\\)'" }
diff --git a/gcc/testsuite/g++.dg/cpp2a/consteval34.C b/gcc/testsuite/g++.dg/cpp2a/consteval34.C
index d5e2d1dc5d2..75d4caa622e 100644
--- a/gcc/testsuite/g++.dg/cpp2a/consteval34.C
+++ b/gcc/testsuite/g++.dg/cpp2a/consteval34.C
@@ -6,6 +6,7 @@ constexpr int
 foo (bool b)
 {
   return b ? bar (3) : 2; // { dg-message "in .constexpr. expansion" }
+// { dg-error "call to consteval function" "" { target *-*-* } .-1 }
 }
 
 static_assert (foo (false) == 2);
@@ -21,13 +22,20 @@ void
 g ()
 {
   __extension__ int a1[bar(3)]; // { dg-message "in .constexpr. expansion" }
+// { dg-error "call to consteval function" "" { target *-*-* } .-1 }
   int a2[sizeof (bar(3))];
 
   int a3 = false ? (1 + bar (8)) : 1; // { dg-message "in .constexpr. expansion" }
+// { dg-error "call to consteval function" "" { target *-*-* } .-1 }
   a3 += false ? (1 + bar (8)) : 1; // { dg-message "in .constexpr. expansion" }
+// { dg-error "call to consteval function" "" { target *-*-* } .-1 }
 
   __extension__ int a4 = false ?: (1 + bar (8)); // { dg-message "in .constexpr. expansion" }
+// { dg-error "call to consteval function" "" { target *-*-* } .-1 }
   __extension__ int a5 = true ?: (1 + bar (8)); // { dg-message "in .constexpr. expansion" }
+// { dg-error "call to consteval function" "" { target *-*-* } .-1 }
   int a6 = bar (2) ? 1 : 2; // { dg-message "in .constexpr. expansion" }
+// { dg-error "call to consteval function" "" { target *-*-* } .-1 }
   int a7 = bar (2) - 1 ? 1 : 2; // { dg-message "in .constexpr. expansion" }
+// { dg-error "call to consteval function" "" { target *-*-* } .-1 }
 }
diff --git a/gcc/testsuite/g++.dg/cpp2a/consteval36.C b/gcc/testsuite/g++.dg/cpp2a/consteval36.C
index 9c470e4b7d7..8e27f2e33c6 100644
--- a/gcc/testsuite/g++.dg/cpp2a/consteval36.C
+++ b/gcc/testsuite/g++.dg/cpp2a/consteval36.C
@@ -6,17 +6,17 @@ consteval int id (int i) { return i; }
 void
 g (int i)
 {
-  1 ? 1 : ((1 ? 1 : 1), id (i)); // { dg-error "'i' is not a constant expression" }
-  1 ? 1 : ((1 ? 1 : 1), id (i), 1); // { dg-error "'i' is not a constant expression" }
-  1 ? 1 : ((i ? 1 : 1), id (i), 1); // { dg-error "'i' is not a constant expression" }
-  1 ? 1 : ((1 ? i : 1), id (i), 1); // { dg-error "'i' is not a constant expression" }
-  1 ? 1 : ((1 ? 1 : i), id (i), 1); // { dg-error "'i' is not a constant expression" }
-  1 ? 1 : ((i ? -i : i), id (i), 1); // { dg-error "'i' is not a constant expression" }
-  1 ? 1 : ((1 ? 1 : id (i)), id (42), 1); // { dg-error "'i' is not a constant expression" }
-  1 ? 1 : ((1 ? 1 : id (42)), id (i)); // { dg-error "'i' is not a constant expression" }
-  1 ? 1 : ((1 ? 1 : id (42)), id (i), 1); // { dg-error "'i' is not a constant expression" }
-  id (i) ? 1 : ((1 ? 1 : 1), id (i)); // { dg-error "'i' is not a constant expression" }
-  1 ? 1 : ((1 ? 1 : id (i)), id (i)); // { dg-error "'i' is not a constant expression" }
-  1 ? id (i) : ((1 ? 1 : id (i)), id (i)); // { dg-error "'i' is not a constant expression" }
-  1 ? 1 : ((id (i) ? 1 : 1), id (i)); // { dg-error "'i' is not a constant expression" }
+  1 ? 1 : ((1 ? 1 : 1), id (i)); // { dg-error "call to consteval function|'i' is not a constant expression" }
+  1 ? 1 : ((1 ? 1 : 1), id (i), 1); // { dg-error "call to consteval function|'i' is not a constant expression" }
+  1 ? 1 : ((i ? 1 : 1), id (i), 1); // { dg-error "call to consteval function|'i' is not a constant expression" }
+  1 ? 1 : ((1 ? i : 1), id (i), 1); // { dg-error "call to consteval function|'i' is not a constant expression" }
+  1 ? 1 : ((1 ? 1 : i), id (i), 1); // { dg-error "call to consteval function|'i' is not a constant expression" }
+  1 ? 1 : ((i ? -i : i), id (i), 1); // { dg-error "call to consteval function|'i' is not a constant expression" }
+  1 ? 1 : ((1 ? 1 : id (i)), id (42), 1); // { dg-error "call to consteval function|'i' is not a constant expression" }
+  1 ? 1 : ((1 ? 1 : id (42)), id (i)); // { dg-error "call to consteval function|'i' is not a constant expression" }
+  1 ? 1 : ((1 ? 1 : id (42)), id (i), 1); // { dg-error "call to consteval function|'i' is not a constant expression" }
+  id (i) ? 1 : ((1 ? 1 : 1), id (i)); // { dg-error "call to consteval function|'i' is not a constant expression" }
+  1 ? 1 : ((1 ? 1 : id (i)), id (i)); // { dg-error "call to consteval function|'i' is not a constant expression" }
+  1 ? id (i) : ((1 ? 1 : id (i)), id (i)); // { dg-error "call to consteval function|'i' is not a constant expression" }
+  1 ? 1 : ((id (i) ? 1 : 1), id (i)); // { dg-error "call to consteval function|'i' is not a constant expression" }
 }
diff --git a/gcc/testsuite/g++.dg/cpp2a/consteval9.C b/gcc/testsuite/g++.dg/cpp2a/consteval9.C
index aa75ba37849..a03226d17c6 100644
--- a/gcc/testsuite/g++.dg/cpp2a/consteval9.C
+++ b/gcc/testsuite/g++.dg/cpp2a/consteval9.C
@@ -13,6 +13,7 @@ template <int N>
 void qux ()
 {
   int a = bar (N);	// { dg-message "in 'constexpr' expansion of 'bar\\(2\\)'" }
+// { dg-error "call to consteval function" "" { target *-*-* } .-1 }
 }
 
 // This function is not instantiated so NDR.
@@ -30,3 +31,4 @@ baz ()
 }
 
 int a = bar (2);	// { dg-message "in 'constexpr' expansion of 'bar\\(2\\)'" }
+// { dg-error "call to consteval function" "" { target *-*-* } .-1 }
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/gcc/testsuite/g++.dg/cpp2a/spaceship-synth9.C b/gcc/testsuite/g++.dg/cpp2a/spaceship-synth9.C
index 33b547d2b50..ecb46b016a6 100644
--- a/gcc/testsuite/g++.dg/cpp2a/spaceship-synth9.C
+++ b/gcc/testsuite/g++.dg/cpp2a/spaceship-synth9.C
@@ -22,6 +22,6 @@ struct Z: Y<int>
 int main()
 {
   X<char>() == X<char>();	// { dg-error "no match" }
-  X<int> x; x == x;		// { dg-error "x' is not usable in a constant expression" }
+  X<int> x; x == x;		// { dg-error "x' is not usable in a constant expression|call to consteval function" }
   Y<int>()  == Y<int>();	// { dg-warning "nodiscard" }
 }
diff --git a/libstdc++-v3/testsuite/18_support/comparisons/categories/zero_neg.cc b/libstdc++-v3/testsuite/18_support/comparisons/categories/zero_neg.cc
index 9d2115b3f4f..82f7cd54fba 100644
--- a/libstdc++-v3/testsuite/18_support/comparisons/categories/zero_neg.cc
+++ b/libstdc++-v3/testsuite/18_support/comparisons/categories/zero_neg.cc
@@ -52,3 +52,4 @@ test01()
 
 // { dg-prune-output "reinterpret_cast.* is not a constant expression" }
 // { dg-prune-output "cast from 'void.' is not allowed" }
+// { dg-prune-output "not a constant expression" }
diff --git a/libstdc++-v3/testsuite/std/format/string_neg.cc b/libstdc++-v3/testsuite/std/format/string_neg.cc
index 7a60ef8cf0e..69bcc736cff 100644
--- a/libstdc++-v3/testsuite/std/format/string_neg.cc
+++ b/libstdc++-v3/testsuite/std/format/string_neg.cc
@@ -2,5 +2,5 @@
 
 #include <format>
 
-auto s = std::format(" {9} ");
+auto s = std::format(" {9} "); // { dg-error "call to consteval function" }
 // { dg-error "invalid.arg.id" "" { target *-*-* } 0 }

base-commit: f432a594fe6d3a0de1330ba69200d158e6248083
-- 
2.41.0


^ permalink raw reply	[flat|nested] 19+ messages in thread

* Re: [PATCH v3] c++: implement P2564, consteval needs to propagate up [PR107687]
  2023-11-02 15:28       ` [PATCH v3] " Marek Polacek
@ 2023-11-02 15:32         ` Marek Polacek
  2023-11-03 17:51         ` Jason Merrill
  1 sibling, 0 replies; 19+ messages in thread
From: Marek Polacek @ 2023-11-02 15:32 UTC (permalink / raw)
  To: Jason Merrill; +Cc: GCC Patches

On Thu, Nov 02, 2023 at 11:28:43AM -0400, Marek Polacek wrote:
> On Sat, Oct 14, 2023 at 12:56:11AM -0400, Jason Merrill wrote:
> > As discussed above, we probably don't want to exclude implicitly-declared
> > special member functions.
> 
> Done (but removing the DECL_ARTIFICIAL check).

s/but/by/


^ permalink raw reply	[flat|nested] 19+ messages in thread

* Re: [PATCH v3] c++: implement P2564, consteval needs to propagate up [PR107687]
  2023-11-02 15:28       ` [PATCH v3] " Marek Polacek
  2023-11-02 15:32         ` Marek Polacek
@ 2023-11-03 17:51         ` Jason Merrill
  2023-11-06 22:34           ` [PATCH v4] " Marek Polacek
  1 sibling, 1 reply; 19+ messages in thread
From: Jason Merrill @ 2023-11-03 17:51 UTC (permalink / raw)
  To: Marek Polacek; +Cc: GCC Patches

On 11/2/23 11:28, Marek Polacek wrote:
> On Sat, Oct 14, 2023 at 12:56:11AM -0400, Jason Merrill wrote:
>> On 10/10/23 13:20, Marek Polacek wrote:
>>> On Tue, Aug 29, 2023 at 03:26:46PM -0400, Jason Merrill wrote:
>>>> On 8/23/23 15:49, Marek Polacek wrote:
>>>>> +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.
>>>
>>> True, and that revealed what I think is a bug in the standard.
>>> In the test I'm saying:
>>>
>>> // ??? [expr.const]#example-9 says:
>>> //   k<int> is not an immediate function because A(42) is a
>>> //   constant expression and thus not immediate-escalating
>>> // But I think the call to id(x) is *not* a constant expression and thus
>>> // it is an immediate-escalating expression.  Therefore k<int> *is*
>>> // an immediate function.  So we get the error below.  clang++ agrees.
>>> id(x) is not a constant expression because x isn't constant.
>>
>> Not when considering id(x) by itself, but in the evaluation of A(42), the
>> member x has just been initialized to constant 42.  And A(42) is
>> constant-evaluated because "An aggregate initialization is an immediate
>> invocation if it evaluates a default member initializer that has a
>> subexpression that is an immediate-escalating expression."
>>
>> I assume clang doesn't handle this passage properly yet because it was added
>> during core review of the paper, for parity between aggregate initialization
>> and constructor escalation.
>>
>> This can be a follow-up patch.
> 
> I see.  So the fix will be to, for the aggregate initialization case, pass
> the whole A(42).y thing to cxx_constant_eval, not just id(x).

Well, A(42) is the immediate invocation, the .y is not part of it.

>>> I suppose some
>>> functions cannot possibly be promoted because they don't contain
>>> any CALL_EXPRs.  So we may be able to rule them out while doing
>>> cp_fold_r early.
>>
>> Yes.  Or, the only immediate-escalating functions referenced have already
>> been checked.

It looks like you haven't pursued this yet?  One implementation thought: 
maybe_store_cfun... could stop skipping immediate_escalating_function_p 
(current_function_decl), and after we're done folding if the current 
function isn't in the hash_set we can go ahead and set 
DECL_ESCALATION_CHECKED_P?

>> We can also do some escalation during constexpr evaluation: all the
>> functions involved need to be instantiated for the evaluation, and if we
>> encounter an immediate-escalating expression while evaluating a call to an
>> immediate-escalating function, we can promote it then.  Though we can't
>> necessarily mark it as not needing promotion, as there might be i-e exprs in
>> branches that the particular evaluation doesn't take.
> 
> I've tried but I didn't get anywhere.  The patch was basically
> 
> --- a/gcc/cp/constexpr.cc
> +++ b/gcc/cp/constexpr.cc
> @@ -2983,7 +2983,13 @@ cxx_eval_call_expression (const constexpr_ctx *ctx, tree t,
>     } fb (new_call.bindings);
> 
>     if (*non_constant_p)
> -    return t;
> +    {
> +      if (cxx_dialect >= cxx20
> +	  && ctx->manifestly_const_eval == mce_false
> +	  && DECL_IMMEDIATE_FUNCTION_P (fun))
> +	maybe_promote_function_to_consteval (current_function_decl);
> +      return t;
> +    }
> 
>     /* We can't defer instantiating the function any longer.  */
>     if (!DECL_INITIAL (fun)
> 
> but since I have to check mce_false, it didn't do anything useful
> in practice (that is, it wouldn't escalate anything in my tests).

Makes sense.

>>> @@ -55,6 +64,8 @@ enum fold_flags {
>>>      ff_mce_false = 1 << 1,
>>>      /* Whether we're being called from cp_fold_immediate.  */
>>>      ff_fold_immediate = 1 << 2,
>>> +  /* Whether we're escalating immediate-escalating functions.  */
>>> +  ff_escalating = 1 << 3,
>>
>> If we always escalate when we see a known immediate invocation, this means
>> recurse.  And maybe we could use at_eof for that?
> 
> Yes.  Though, at_eof == 1 doesn't imply that all templates have been
> instantiated, only that we got to c_parse_final_cleanups.  Rather than
> keeping the ff_ flag, I've updated at_eof to mean that 2 signals that
> all templates have been instantiated, and 3 that we're into cgraph
> territory.  (Might should use an enum rather than magic numbers.)

Sounds good.

>>>    };
>>>    using fold_flags_t = int;
>>> @@ -428,6 +439,176 @@ lvalue_has_side_effects (tree e)
>>>        return TREE_SIDE_EFFECTS (e);
>>>    }
>>> +/* Return true if FN is an immediate-escalating function.  */
>>> +
>>> +static bool
>>> +immediate_escalating_function_p (tree fn)
>>> +{
>>> +  if (!fn || !flag_immediate_escalation)
>>> +    return false;
>>> +
>>> +  gcc_checking_assert (TREE_CODE (fn) == FUNCTION_DECL);
>>
>> Maybe check DECL_IMMEDIATE_FUNCTION_P early, rather than multiple times
>> below...
>>
>>> +  /* 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.  */
>>> +
>>> +static bool
>>> +maybe_promote_function_to_consteval (tree fn)
>>> +{
>>> +  if (fn
>>> +      && !DECL_IMMEDIATE_FUNCTION_P (fn)
>>
>> ...and in all the callers?
> 
> Ah, beautiful.  And likewise for DECL_ESCALATION_CHECKED_P.

Agreed, but the function comment should mention that it returns false 
for an immediate-escalating function that is no longer a candidate for 
escalation.  And maybe the function name should be something like 
unchecked_immediate_escalating_function_p to clarify that it implements 
something different from the term in the standard?

>>> +    return;
>>> +
>>> +  vec_safe_push (deferred_escalating_exprs, current_function_decl);
>>> +}
>>> +
>>> +/* 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)
>>
>> Can this merge with cp_fold_immediate_r?  They seem to be doing essentially
>> the same thing.
> 
> I did not do it, sorry.  I thought it would not make things more readable
> as I don't see a whole lot of code to share.

The code is significantly different, sure, but both are walking the tree 
to look for immediate-escalating expressions.  Why do we need two of those?

>>> +		if (code == ADDR_EXPR || !data->pset.add (stmt))
>>> +		  {
>>> +		    taking_address_of_imm_fn_error (stmt, decl);
>>> +		    *stmt_p = build_zero_cst (TREE_TYPE (stmt));
>>> +		  }
>>> +		/* If we're giving hard errors, continue the walk rather than
>>> +		   bailing out after the first error.  */
>>> +		break;
>>> +	      }
>>> +	    return error_mark_node;
>>> +	  }
>>> +	/* Not consteval yet, but could become one, in which case it's invalid
>>> +	   to take its address.  */
>>> +	else if (!(data->flags & ff_fold_immediate)
>>
>> Why check ff_fold_immediate?  Don't we want to do deferred checking of
>> immediate invocations in discarded branches?
> 
> On reflection, I was papering over a different problem here.
> 
> Inserting a new element to a hash table while we're traversing it is
> dangerous because a new element might mean calling hash_table::expand
> which creates a new table, std::moves the elements, and calls ggc_free
> on the old table.  Then iterating to the next element is going to crash
> because it has been freed.
> 
> We were trying to add a new element while being called from
> process_pending_immediate_escalating_fns.  This should not happen.
> The problem was that the DECL_ESCALATION_CHECKED_P flag may not have
> been properly set for functions without any CALL_EXPRs in them, and that
> immediate_escalating_function_p was not checking it, either.
> 
> The code is just
> 
> +   else if (immediate_escalating_function_p (decl))
> +     remember_escalating_expr (stmt);
>   
> now.
> 
> The problem triggered in Wfree-nonheap-object-3.C.  Unfortunately, I could
> not reduce it very far so I don't have a test suitable for the testsuite.
> The tricky thing is that the problem only manifests when we expand() a hash
> table which is unlikely to happen in a small test and I don't see a --param
> I could use.

You could save the value of deferred_escalating_exprs->elements() before 
the loop and if it changes, abort in checking mode or goto back before 
the loop when not checking?

>> It also seems odd that the ADDR_EXPR case calls vec_safe_push
>> (deferred_escalating_exprs, while the CALL_EXPR case calls
>> maybe_store_cfun_for_late_checking, why the different handling?
> 
> maybe_store_cfun_for_late_checking saves current_function_decl
> so that we can check:
> 
> void g (int i) {
>    fn (i); // error if fn promotes to consteval
> }

Yes, but why don't we want the same handling for ADDR_EXPR?

Jason


^ permalink raw reply	[flat|nested] 19+ messages in thread

* [PATCH v4] c++: implement P2564, consteval needs to propagate up [PR107687]
  2023-11-03 17:51         ` Jason Merrill
@ 2023-11-06 22:34           ` Marek Polacek
  2023-11-14  2:06             ` Jason Merrill
  0 siblings, 1 reply; 19+ messages in thread
From: Marek Polacek @ 2023-11-06 22:34 UTC (permalink / raw)
  To: Jason Merrill; +Cc: GCC Patches

On Fri, Nov 03, 2023 at 01:51:07PM -0400, Jason Merrill wrote:
> On 11/2/23 11:28, Marek Polacek wrote:
> > On Sat, Oct 14, 2023 at 12:56:11AM -0400, Jason Merrill wrote:
> > > On 10/10/23 13:20, Marek Polacek wrote:
> > > > On Tue, Aug 29, 2023 at 03:26:46PM -0400, Jason Merrill wrote:
> > > > > On 8/23/23 15:49, Marek Polacek wrote:
> > > > > > +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.
> > > > 
> > > > True, and that revealed what I think is a bug in the standard.
> > > > In the test I'm saying:
> > > > 
> > > > // ??? [expr.const]#example-9 says:
> > > > //   k<int> is not an immediate function because A(42) is a
> > > > //   constant expression and thus not immediate-escalating
> > > > // But I think the call to id(x) is *not* a constant expression and thus
> > > > // it is an immediate-escalating expression.  Therefore k<int> *is*
> > > > // an immediate function.  So we get the error below.  clang++ agrees.
> > > > id(x) is not a constant expression because x isn't constant.
> > > 
> > > Not when considering id(x) by itself, but in the evaluation of A(42), the
> > > member x has just been initialized to constant 42.  And A(42) is
> > > constant-evaluated because "An aggregate initialization is an immediate
> > > invocation if it evaluates a default member initializer that has a
> > > subexpression that is an immediate-escalating expression."
> > > 
> > > I assume clang doesn't handle this passage properly yet because it was added
> > > during core review of the paper, for parity between aggregate initialization
> > > and constructor escalation.
> > > 
> > > This can be a follow-up patch.
> > 
> > I see.  So the fix will be to, for the aggregate initialization case, pass
> > the whole A(42).y thing to cxx_constant_eval, not just id(x).
> 
> Well, A(42) is the immediate invocation, the .y is not part of it.

Ah right, like the comment above says.

> > > > I suppose some
> > > > functions cannot possibly be promoted because they don't contain
> > > > any CALL_EXPRs.  So we may be able to rule them out while doing
> > > > cp_fold_r early.
> > > 
> > > Yes.  Or, the only immediate-escalating functions referenced have already
> > > been checked.
> 
> It looks like you haven't pursued this yet?  One implementation thought:

Oops, I'd forgotten to address that.

> maybe_store_cfun... could stop skipping immediate_escalating_function_p
> (current_function_decl), and after we're done folding if the current
> function isn't in the hash_set we can go ahead and set
> DECL_ESCALATION_CHECKED_P?

Clever, I see what you mean.  IOW, we store c_f_d iff the function contains
an i-e expr.  If not, it can't possibly become consteval.  I've added that
into cp_fold_function, and it seems to work well...

...except it revealed a different problem: cp_fold_r -> cp_fold will, since
https://gcc.gnu.org/pipermail/gcc-patches/2016-March/443993.html, remove
UNARY_PLUS_EXPR, leading us into this problem:

  // stmt = +id(i)
  cp_fold (...);
  // stmt = id(i)

and the subsequent tree walk walks the CALL_EXPR's operands, so
cp_fold_immediate_r will never see the CALL_EXPR, so we miss an i-e expr.

Perhaps a better solution than the kludge I added would be to only call
cp_fold_immediate_r after cp_fold.  Or potentially before /and/ after if
cp_fold changes the expression?

> > > We can also do some escalation during constexpr evaluation: all the
> > > functions involved need to be instantiated for the evaluation, and if we
> > > encounter an immediate-escalating expression while evaluating a call to an
> > > immediate-escalating function, we can promote it then.  Though we can't
> > > necessarily mark it as not needing promotion, as there might be i-e exprs in
> > > branches that the particular evaluation doesn't take.
> > 
> > I've tried but I didn't get anywhere.  The patch was basically
> > 
> > --- a/gcc/cp/constexpr.cc
> > +++ b/gcc/cp/constexpr.cc
> > @@ -2983,7 +2983,13 @@ cxx_eval_call_expression (const constexpr_ctx *ctx, tree t,
> >     } fb (new_call.bindings);
> > 
> >     if (*non_constant_p)
> > -    return t;
> > +    {
> > +      if (cxx_dialect >= cxx20
> > +	  && ctx->manifestly_const_eval == mce_false
> > +	  && DECL_IMMEDIATE_FUNCTION_P (fun))
> > +	maybe_promote_function_to_consteval (current_function_decl);
> > +      return t;
> > +    }
> > 
> >     /* We can't defer instantiating the function any longer.  */
> >     if (!DECL_INITIAL (fun)
> > 
> > but since I have to check mce_false, it didn't do anything useful
> > in practice (that is, it wouldn't escalate anything in my tests).
> 
> Makes sense.
> 
> > > > @@ -55,6 +64,8 @@ enum fold_flags {
> > > >      ff_mce_false = 1 << 1,
> > > >      /* Whether we're being called from cp_fold_immediate.  */
> > > >      ff_fold_immediate = 1 << 2,
> > > > +  /* Whether we're escalating immediate-escalating functions.  */
> > > > +  ff_escalating = 1 << 3,
> > > 
> > > If we always escalate when we see a known immediate invocation, this means
> > > recurse.  And maybe we could use at_eof for that?
> > 
> > Yes.  Though, at_eof == 1 doesn't imply that all templates have been
> > instantiated, only that we got to c_parse_final_cleanups.  Rather than
> > keeping the ff_ flag, I've updated at_eof to mean that 2 signals that
> > all templates have been instantiated, and 3 that we're into cgraph
> > territory.  (Might should use an enum rather than magic numbers.)
> 
> Sounds good.
> 
> > > >    };
> > > >    using fold_flags_t = int;
> > > > @@ -428,6 +439,176 @@ lvalue_has_side_effects (tree e)
> > > >        return TREE_SIDE_EFFECTS (e);
> > > >    }
> > > > +/* Return true if FN is an immediate-escalating function.  */
> > > > +
> > > > +static bool
> > > > +immediate_escalating_function_p (tree fn)
> > > > +{
> > > > +  if (!fn || !flag_immediate_escalation)
> > > > +    return false;
> > > > +
> > > > +  gcc_checking_assert (TREE_CODE (fn) == FUNCTION_DECL);
> > > 
> > > Maybe check DECL_IMMEDIATE_FUNCTION_P early, rather than multiple times
> > > below...
> > > 
> > > > +  /* 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.  */
> > > > +
> > > > +static bool
> > > > +maybe_promote_function_to_consteval (tree fn)
> > > > +{
> > > > +  if (fn
> > > > +      && !DECL_IMMEDIATE_FUNCTION_P (fn)
> > > 
> > > ...and in all the callers?
> > 
> > Ah, beautiful.  And likewise for DECL_ESCALATION_CHECKED_P.
> 
> Agreed, but the function comment should mention that it returns false for an
> immediate-escalating function that is no longer a candidate for escalation.
> And maybe the function name should be something like
> unchecked_immediate_escalating_function_p to clarify that it implements
> something different from the term in the standard?

I've added unchecked_immediate_escalating_function_p, but I've kept
immediate_escalating_function_p; I like having functions that correspond
1:1 to terms defined in the standard.
 
> > > > +    return;
> > > > +
> > > > +  vec_safe_push (deferred_escalating_exprs, current_function_decl);
> > > > +}
> > > > +
> > > > +/* 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)
> > > 
> > > Can this merge with cp_fold_immediate_r?  They seem to be doing essentially
> > > the same thing.
> > 
> > I did not do it, sorry.  I thought it would not make things more readable
> > as I don't see a whole lot of code to share.
> 
> The code is significantly different, sure, but both are walking the tree to
> look for immediate-escalating expressions.  Why do we need two of those?

I've merged find_escalating_expr_r into cp_fold_immediate_r in this patch.
It involves a goto and a new ff_ flag but overall it should be a win.
 
> > > > +		if (code == ADDR_EXPR || !data->pset.add (stmt))
> > > > +		  {
> > > > +		    taking_address_of_imm_fn_error (stmt, decl);
> > > > +		    *stmt_p = build_zero_cst (TREE_TYPE (stmt));
> > > > +		  }
> > > > +		/* If we're giving hard errors, continue the walk rather than
> > > > +		   bailing out after the first error.  */
> > > > +		break;
> > > > +	      }
> > > > +	    return error_mark_node;
> > > > +	  }
> > > > +	/* Not consteval yet, but could become one, in which case it's invalid
> > > > +	   to take its address.  */
> > > > +	else if (!(data->flags & ff_fold_immediate)
> > > 
> > > Why check ff_fold_immediate?  Don't we want to do deferred checking of
> > > immediate invocations in discarded branches?
> > 
> > On reflection, I was papering over a different problem here.
> > 
> > Inserting a new element to a hash table while we're traversing it is
> > dangerous because a new element might mean calling hash_table::expand
> > which creates a new table, std::moves the elements, and calls ggc_free
> > on the old table.  Then iterating to the next element is going to crash
> > because it has been freed.
> > 
> > We were trying to add a new element while being called from
> > process_pending_immediate_escalating_fns.  This should not happen.
> > The problem was that the DECL_ESCALATION_CHECKED_P flag may not have
> > been properly set for functions without any CALL_EXPRs in them, and that
> > immediate_escalating_function_p was not checking it, either.
> > 
> > The code is just
> > 
> > +   else if (immediate_escalating_function_p (decl))
> > +     remember_escalating_expr (stmt);
> > now.
> > 
> > The problem triggered in Wfree-nonheap-object-3.C.  Unfortunately, I could
> > not reduce it very far so I don't have a test suitable for the testsuite.
> > The tricky thing is that the problem only manifests when we expand() a hash
> > table which is unlikely to happen in a small test and I don't see a --param
> > I could use.
> 
> You could save the value of deferred_escalating_exprs->elements() before the
> loop and if it changes, abort in checking mode or goto back before the loop
> when not checking?

Right, that should work, but I don't think we want to ever add a new
element during process_pending_immediate_escalating_fns.  I've added a new
assert:

  gcc_checking_assert (at_eof <= 1);
 
> > > It also seems odd that the ADDR_EXPR case calls vec_safe_push
> > > (deferred_escalating_exprs, while the CALL_EXPR case calls
> > > maybe_store_cfun_for_late_checking, why the different handling?
> > 
> > maybe_store_cfun_for_late_checking saves current_function_decl
> > so that we can check:
> > 
> > void g (int i) {
> >    fn (i); // error if fn promotes to consteval
> > }
> 
> Yes, but why don't we want the same handling for ADDR_EXPR?

The handling can't be exactly the same due to global vars like

  auto p1 = &f5<int>;

...but it's wrong to only save the ADDR_EXPR if it's enclosed in
a function, because the ADDR_EXPR could be inside a consteval if
block, in which case I think we're not supposed to error.  Tested
in consteval-prop20.C.  Thanks,

Bootstrapped/regtested on x86_64-pc-linux-gnu.

-- >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:

w.C:11:11: error: call to consteval function 'f<int>(i)' is not a constant expression
   11 |         f (i);
      |         ~~^~~
w.C:11:11: error: 'i' is not a constant expression
w.C:6:22: 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); // id causes f<int> to be promoted to consteval
      |                    ~~^~~

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.

In v1, I addressed the delayed instantiation problem by instantiating
trees early, so that we can escalate functions right away.  That caused
a number of problems, and in certain cases, like consteval-prop3.C, it
can't work, because we need to wait till EOF to see the definition of
the function anyway.  Overeager instantiation tends to cause diagnostic
problems too.

In v2, I attempted to move the escalation to the gimplifier, at which
point all templates have been instantiated.  That attempt flopped,
however, because once we've gimplified a function, its body is discarded
and as a consequence, you can no longer evaluate a call to that function
which is required for escalating, which needs to decide if a call is
a constant expression or not.

Therefore, we have to perform the escalation before gimplifying, but
after instantiate_pending_templates.  That's not easy because we have
no way to walk all the trees.  In the v2 patch, I use two vectors: one
to store function decls that may become consteval, and another to
remember references to immediate-escalating functions.  Unfortunately
the latter must also stash functions that call immediate-escalating
functions.  Consider:

  int g(int i)
  {
    f<int>(i); // f is immediate-escalating
  }

where g itself is not immediate-escalating, but we have to make sure
that if f gets promoted to consteval, we give an error.

A new option, -fno-immediate-escalation, is provided to suppress
escalating functions.

v2 also adds a new flag, DECL_ESCALATED_P, so that we don't escalate
a function multiple times, and so that we can distinguish between
explicitly consteval functions and functions that have been promoted
to consteval.

In v3, I removed one of the new vectors and changed the other one
to a hash set.  This version also contains numerous cleanups.

v4 merges find_escalating_expr_r into cp_fold_immediate_r.  It also
adds a new optimization in cp_fold_function.

	PR c++/107687
	PR c++/110997

gcc/c-family/ChangeLog:

	* c-cppbuiltin.cc (c_cpp_builtins): Update __cpp_consteval.
	* c-opts.cc (c_common_post_options): Pre-C++20, unset
	flag_immediate_escalation.
	* c.opt (fimmediate-escalation): New option.

gcc/cp/ChangeLog:

	* call.cc (in_immediate_context): No longer static.
	* constexpr.cc (cxx_eval_call_expression): Adjust assert.
	* cp-gimplify.cc (deferred_escalating_exprs): New vec.
	(remember_escalating_expr): New.
	(enum fold_flags): Rename ff_fold_immediate to ff_escalating.
	(struct cp_fold_data): New delegating ctor.
	(immediate_escalating_function_p): New.
	(unchecked_immediate_escalating_function_p): New.
	(promote_function_to_consteval): New.
	(maybe_promote_function_to_consteval): New.
	(maybe_store_cfun_for_late_checking): New.
	(maybe_explain_promoted_consteval): New.
	(maybe_escalate_decl_and_cfun): New.
	(cp_gimplify_expr) <case CALL_EXPR>: Assert we've handled all
	immediate invocations.
	(taking_address_of_imm_fn_error): New.
	(cp_fold_immediate_r): Merge ADDR_EXPR and PTRMEM_CST cases.  Implement
	P2564 - promoting functions to consteval.
	<case CALL_EXPR>: Implement P2564 - promoting functions to consteval.
	(cp_fold_immediate): Return true if any errors were emitted.
	(cp_fold_r): If a CALL_EXPR is wrapped in UNARY_PLUS_EXPR, call
	cp_fold_immediate_r on the CALL_EXPR.
	(cp_fold_function): Set DECL_ESCALATION_CHECKED_P if
	deferred_escalating_exprs does not contain current_function_decl.
	(maybe_store_immediate_escalating_fn): New.
	(process_pending_immediate_escalating_fns): New.
	(check_immediate_escalating_refs): New.
	* cp-tree.h (struct lang_decl_fn): Add escalated_p bit-field.
	(DECL_ESCALATION_CHECKED_P): New.
	(immediate_invocation_p): Declare.
	(check_immediate_escalating_refs): Likewise.
	(maybe_store_immediate_escalating_fn): Likewise.
	(process_pending_immediate_escalating_fns): Likewise.
	* decl.cc (finish_function): Call maybe_store_immediate_escalating_fn.
	* decl2.cc (c_parse_final_cleanups): Set at_eof to 2 after all
	templates have been instantiated; and to 3 at the end of the function.
	Call process_pending_immediate_escalating_fns and
	check_immediate_escalating_refs.
	* error.cc (dump_template_bindings): Check at_eof against an updated
	value.
	* module.cc (trees_out::lang_decl_bools): Stream escalated_p.
	(trees_in::lang_decl_bools): Likewise.
	* pt.cc (push_tinst_level_loc): Set at_eof to 3, not 2.
	* typeck.cc (cp_build_addr_expr_1): Don't check
	DECL_IMMEDIATE_FUNCTION_P.

gcc/ChangeLog:

	* doc/invoke.texi: Document -fno-immediate-escalation.

libstdc++-v3/ChangeLog:

	* testsuite/18_support/comparisons/categories/zero_neg.cc: Add
	dg-prune-output.
	* testsuite/std/format/string_neg.cc: Add dg-error.

gcc/testsuite/ChangeLog:

	* 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/consteval-memfn1.C: Add dg-error.
	* g++.dg/cpp2a/consteval11.C: Likewise.
	* g++.dg/cpp2a/consteval3.C: Adjust dg-error.
	* g++.dg/cpp2a/consteval34.C: Add dg-error.
	* g++.dg/cpp2a/consteval9.C: Likewise.
	* g++.dg/cpp2a/feat-cxx2a.C: Adjust expected value of __cpp_consteval.
	* g++.dg/cpp2a/spaceship-synth9.C: Adjust dg-error.
	* 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-prop18.C: New test.
	* g++.dg/cpp2a/consteval-prop19.C: New test.
	* g++.dg/cpp2a/consteval-prop20.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/c-family/c-opts.cc                        |   5 +
 gcc/c-family/c.opt                            |   4 +
 gcc/cp/call.cc                                |   2 +-
 gcc/cp/constexpr.cc                           |   4 +-
 gcc/cp/cp-gimplify.cc                         | 447 ++++++++++++++++--
 gcc/cp/cp-tree.h                              |  18 +-
 gcc/cp/decl.cc                                |   5 +-
 gcc/cp/decl2.cc                               |  20 +-
 gcc/cp/error.cc                               |   2 +-
 gcc/cp/module.cc                              |   4 +
 gcc/cp/pt.cc                                  |   2 +-
 gcc/cp/typeck.cc                              |   6 +-
 gcc/doc/invoke.texi                           |  34 ++
 gcc/testsuite/g++.dg/cpp23/consteval-if10.C   |   7 +-
 gcc/testsuite/g++.dg/cpp23/consteval-if2.C    |  14 +-
 gcc/testsuite/g++.dg/cpp23/feat-cxx2b.C       |   4 +-
 gcc/testsuite/g++.dg/cpp26/feat-cxx26.C       |   4 +-
 gcc/testsuite/g++.dg/cpp2a/consteval-memfn1.C |   3 +
 gcc/testsuite/g++.dg/cpp2a/consteval-prop1.C  | 169 +++++++
 gcc/testsuite/g++.dg/cpp2a/consteval-prop10.C |  41 ++
 gcc/testsuite/g++.dg/cpp2a/consteval-prop11.C |  49 ++
 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 |  78 +++
 gcc/testsuite/g++.dg/cpp2a/consteval-prop15.C | 107 +++++
 gcc/testsuite/g++.dg/cpp2a/consteval-prop16.C |  73 +++
 gcc/testsuite/g++.dg/cpp2a/consteval-prop17.C |  17 +
 gcc/testsuite/g++.dg/cpp2a/consteval-prop18.C |  20 +
 gcc/testsuite/g++.dg/cpp2a/consteval-prop19.C |   7 +
 gcc/testsuite/g++.dg/cpp2a/consteval-prop2.C  |  90 ++++
 gcc/testsuite/g++.dg/cpp2a/consteval-prop20.C |  21 +
 gcc/testsuite/g++.dg/cpp2a/consteval-prop3.C  |  27 ++
 gcc/testsuite/g++.dg/cpp2a/consteval-prop4.C  |  30 ++
 gcc/testsuite/g++.dg/cpp2a/consteval-prop5.C  |  27 ++
 gcc/testsuite/g++.dg/cpp2a/consteval-prop6.C  |  59 +++
 gcc/testsuite/g++.dg/cpp2a/consteval-prop7.C  |  76 +++
 gcc/testsuite/g++.dg/cpp2a/consteval-prop8.C  |  82 ++++
 gcc/testsuite/g++.dg/cpp2a/consteval-prop9.C  |  67 +++
 gcc/testsuite/g++.dg/cpp2a/consteval11.C      |  18 +
 gcc/testsuite/g++.dg/cpp2a/consteval3.C       |   4 +-
 gcc/testsuite/g++.dg/cpp2a/consteval34.C      |   8 +
 gcc/testsuite/g++.dg/cpp2a/consteval36.C      |  26 +-
 gcc/testsuite/g++.dg/cpp2a/consteval9.C       |   2 +
 gcc/testsuite/g++.dg/cpp2a/feat-cxx2a.C       |   4 +-
 gcc/testsuite/g++.dg/cpp2a/spaceship-synth9.C |   2 +-
 .../comparisons/categories/zero_neg.cc        |   1 +
 .../testsuite/std/format/string_neg.cc        |   2 +-
 48 files changed, 1650 insertions(+), 97 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-prop18.C
 create mode 100644 gcc/testsuite/g++.dg/cpp2a/consteval-prop19.C
 create mode 100644 gcc/testsuite/g++.dg/cpp2a/consteval-prop2.C
 create mode 100644 gcc/testsuite/g++.dg/cpp2a/consteval-prop20.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

diff --git a/gcc/c-family/c-cppbuiltin.cc b/gcc/c-family/c-cppbuiltin.cc
index 8904ac85015..3c8eaed7ad3 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/c-family/c-opts.cc b/gcc/c-family/c-opts.cc
index 87da6c180cd..d74c016d742 100644
--- a/gcc/c-family/c-opts.cc
+++ b/gcc/c-family/c-opts.cc
@@ -1124,6 +1124,11 @@ c_common_post_options (const char **pfilename)
   if (cxx_dialect >= cxx20 || flag_concepts_ts)
     flag_concepts = 1;
 
+  /* -fimmediate-escalation has no effect when immediate functions are not
+     supported.  */
+  if (flag_immediate_escalation && cxx_dialect < cxx20)
+    flag_immediate_escalation = 0;
+
   if (num_in_fnames > 1)
     error ("too many filenames given; type %<%s %s%> for usage",
 	   progname, "--help");
diff --git a/gcc/c-family/c.opt b/gcc/c-family/c.opt
index 359f071e632..051d4e1bc7d 100644
--- a/gcc/c-family/c.opt
+++ b/gcc/c-family/c.opt
@@ -1882,6 +1882,10 @@ fhuge-objects
 C++ ObjC++ WarnRemoved
 No longer supported.
 
+fimmediate-escalation
+C++ ObjC++ Var(flag_immediate_escalation) Init(1)
+Implement P2564 for consteval propagation.
+
 fimplement-inlines
 C++ ObjC++ Var(flag_implement_inlines) Init(1)
 Export functions even if they can be inlined.
diff --git a/gcc/cp/call.cc b/gcc/cp/call.cc
index 4516677bcab..b6f3243d642 100644
--- a/gcc/cp/call.cc
+++ b/gcc/cp/call.cc
@@ -9708,7 +9708,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
diff --git a/gcc/cp/constexpr.cc b/gcc/cp/constexpr.cc
index c05760e6789..2e9f1c014ac 100644
--- a/gcc/cp/constexpr.cc
+++ b/gcc/cp/constexpr.cc
@@ -3128,11 +3128,11 @@ cxx_eval_call_expression (const constexpr_ctx *ctx, tree t,
 	/* OK */;
       else if (!DECL_SAVED_TREE (fun))
 	{
-	  /* When at_eof >= 2, cgraph has started throwing away
+	  /* When at_eof >= 3, cgraph has started throwing away
 	     DECL_SAVED_TREE, so fail quietly.  FIXME we get here because of
 	     late code generation for VEC_INIT_EXPR, which needs to be
 	     completely reconsidered.  */
-	  gcc_assert (at_eof >= 2 && ctx->quiet);
+	  gcc_assert (at_eof >= 3 && ctx->quiet);
 	  *non_constant_p = true;
 	}
       else if (tree copy = get_fundef_copy (new_call.fundef))
diff --git a/gcc/cp/cp-gimplify.cc b/gcc/cp/cp-gimplify.cc
index 9375a116f06..fa44ea83bf8 100644
--- a/gcc/cp/cp-gimplify.cc
+++ b/gcc/cp/cp-gimplify.cc
@@ -43,6 +43,21 @@ along with GCC; see the file COPYING3.  If not see
 #include "omp-general.h"
 #include "opts.h"
 
+/* Keep track of forward references to immediate-escalating functions in
+   case they become consteval.  This vector contains ADDR_EXPRs and
+   PTRMEM_CSTs; it also stores FUNCTION_DECLs that had an escalating
+   function call in them, to check that they can be evaluated to a constant,
+   and immediate-escalating functions that may become consteval.  */
+static GTY(()) hash_set<tree> *deferred_escalating_exprs;
+
+static void
+remember_escalating_expr (tree t)
+{
+  if (!deferred_escalating_exprs)
+    deferred_escalating_exprs = hash_set<tree>::create_ggc (37);
+  deferred_escalating_exprs->add (t);
+}
+
 /* Flags for cp_fold and cp_fold_r.  */
 
 enum fold_flags {
@@ -53,8 +68,9 @@ enum fold_flags {
      definitely not in a manifestly constant-evaluated
      context.  */
   ff_mce_false = 1 << 1,
-  /* Whether we're being called from cp_fold_immediate.  */
-  ff_fold_immediate = 1 << 2,
+  /* Whether cp_fold_immediate_r is looking for immediate-escalating
+     expressions.  */
+  ff_escalating = 1 << 2,
 };
 
 using fold_flags_t = int;
@@ -63,7 +79,11 @@ struct cp_fold_data
 {
   hash_set<tree> pset;
   fold_flags_t flags;
-  cp_fold_data (fold_flags_t flags): flags (flags) {}
+  /* The function whose body we're traversing.  Used to promote the current
+     function to consteval.  */
+  tree caller;
+  cp_fold_data (fold_flags_t flags, tree t): flags (flags), caller (t) {}
+  cp_fold_data (fold_flags_t flags): cp_fold_data (flags, NULL_TREE) {}
 };
 
 /* Forward declarations.  */
@@ -72,6 +92,7 @@ static tree cp_genericize_r (tree *, int *, void *);
 static tree cp_fold_r (tree *, int *, void *);
 static void cp_genericize_tree (tree*, bool);
 static tree cp_fold (tree, fold_flags_t);
+static tree cp_fold_immediate_r (tree *, int *, void *);
 
 /* Genericize a TRY_BLOCK.  */
 
@@ -428,6 +449,107 @@ lvalue_has_side_effects (tree e)
     return TREE_SIDE_EFFECTS (e);
 }
 
+/* Return true if FN is an immediate-escalating function.  */
+
+static bool
+immediate_escalating_function_p (tree fn)
+{
+  if (!fn || !flag_immediate_escalation)
+    return false;
+
+  gcc_checking_assert (TREE_CODE (fn) == FUNCTION_DECL);
+
+  if (DECL_IMMEDIATE_FUNCTION_P (fn))
+    return false;
+
+  /* An immediate-escalating function is
+      -- the call operator of a lambda that is not declared with the consteval
+	 specifier  */
+  if (LAMBDA_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))
+    return true;
+  /* -- a function that results from the instantiation of a templated entity
+	defined with the constexpr specifier.  */
+  return is_instantiation_of_constexpr (fn);
+}
+
+/* Return true if FN is an immediate-escalating function that has not been
+   checked for escalating expressions..  */
+
+static bool
+unchecked_immediate_escalating_function_p (tree fn)
+{
+  return (immediate_escalating_function_p (fn)
+	  && !DECL_ESCALATION_CHECKED_P (fn));
+}
+
+/* Promote FN to an immediate function, including its clones.  */
+
+static void
+promote_function_to_consteval (tree fn)
+{
+  SET_DECL_IMMEDIATE_FUNCTION_P (fn);
+  DECL_ESCALATION_CHECKED_P (fn) = true;
+  tree clone;
+  FOR_EACH_CLONE (clone, fn)
+    {
+      SET_DECL_IMMEDIATE_FUNCTION_P (clone);
+      DECL_ESCALATION_CHECKED_P (clone) = true;
+    }
+}
+
+/* Promote FN to an immediate function, including its clones, if it is
+   an immediate-escalating function.  Return true if we did promote;
+   false otherwise.  */
+
+static bool
+maybe_promote_function_to_consteval (tree fn)
+{
+  if (unchecked_immediate_escalating_function_p (fn))
+    {
+      promote_function_to_consteval (fn);
+      return true;
+    }
+
+  return false;
+}
+
+/* Remember that the current function declaration contains a call to
+   a function that might be promoted to consteval later.  */
+
+static void
+maybe_store_cfun_for_late_checking ()
+{
+  if (flag_immediate_escalation && current_function_decl)
+    remember_escalating_expr (current_function_decl);
+}
+
+/* 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)
+{
+  if (DECL_ESCALATION_CHECKED_P (fn))
+    {
+      /* See if we can figure out what made the function consteval.  */
+      cp_fold_data data (ff_escalating);
+      tree x = cp_walk_tree (&DECL_SAVED_TREE (fn), cp_fold_immediate_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.  */
 
@@ -485,6 +607,41 @@ cp_gimplify_arg (tree *arg_p, gimple_seq *pre_p, location_t call_location,
 
 }
 
+/* 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.
+   EVALP is where we may store the result of cxx_constant_value so that we
+   don't have to evaluate the same tree again in cp_fold_immediate_r.  */
+
+static void
+maybe_escalate_decl_and_cfun (tree decl, tree call, tree *evalp)
+{
+  if (cp_unevaluated_operand)
+    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 (unchecked_immediate_escalating_function_p (decl))
+    {
+      cp_fold_data data (ff_escalating, decl);
+      cp_walk_tree (&DECL_SAVED_TREE (decl), cp_fold_immediate_r,
+		    &data, nullptr);
+      DECL_ESCALATION_CHECKED_P (decl) = true;
+    }
+
+  /* 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 cp_fold_immediate_r,
+	 because DECL could have already been consteval and we'd
+	 never call cp_fold_immediate_r.  */
+      && (*evalp = cxx_constant_value (call, tf_none),
+	  *evalp == error_mark_node))
+    maybe_promote_function_to_consteval (current_function_decl);
+}
+
 /* Do C++-specific gimplification.  Args are as for gimplify_expr.  */
 
 int
@@ -746,7 +903,9 @@ cp_gimplify_expr (tree *expr_p, gimple_seq *pre_p, gimple_seq *post_p)
       if (ret != GS_ERROR)
 	{
 	  tree decl = cp_get_callee_fndecl_nofold (*expr_p);
-	  if (decl && fndecl_built_in_p (decl, BUILT_IN_FRONTEND))
+	  if (!decl)
+	    break;
+	  if (fndecl_built_in_p (decl, BUILT_IN_FRONTEND))
 	    switch (DECL_FE_FUNCTION_CODE (decl))
 	      {
 	      case CP_BUILT_IN_IS_CONSTANT_EVALUATED:
@@ -771,6 +930,9 @@ cp_gimplify_expr (tree *expr_p, gimple_seq *pre_p, gimple_seq *post_p)
 	      default:
 		break;
 	      }
+	  else
+	    /* All consteval functions should have been processed by now.  */
+	    gcc_checking_assert (!immediate_invocation_p (decl));
 	}
       break;
 
@@ -1031,6 +1193,20 @@ struct cp_genericize_data
   bool handle_invisiref_parm_p;
 };
 
+/* Emit an error about taking the address of an immediate function.
+   EXPR is the whole expression; DECL is the immediate function.  */
+
+static void
+taking_address_of_imm_fn_error (tree expr, tree decl)
+{
+  auto_diagnostic_group d;
+  const location_t loc = (TREE_CODE (expr) == PTRMEM_CST
+			  ? PTRMEM_CST_LOCATION (expr)
+			  : EXPR_LOCATION (expr));
+  error_at (loc, "taking address of an immediate function %qD", decl);
+  maybe_explain_promoted_consteval (loc, decl);
+}
+
 /* A subroutine of cp_fold_r to handle immediate functions.  */
 
 static tree
@@ -1041,65 +1217,129 @@ cp_fold_immediate_r (tree *stmt_p, int *walk_subtrees, void *data_)
   /* The purpose of this is not to emit errors for mce_unknown.  */
   const tsubst_flags_t complain = (data->flags & ff_mce_false
 				   ? tf_error : tf_none);
+  const tree_code code = TREE_CODE (stmt);
+  tree decl;
+  tree call = NULL_TREE;
 
   /* No need to look into types or unevaluated operands.
      NB: This affects cp_fold_r as well.  */
-  if (TYPE_P (stmt) || unevaluated_p (TREE_CODE (stmt)))
+  if (TYPE_P (stmt) || unevaluated_p (code))
     {
       *walk_subtrees = 0;
       return NULL_TREE;
     }
 
-  switch (TREE_CODE (stmt))
+  switch (code)
     {
     case PTRMEM_CST:
-      if (TREE_CODE (PTRMEM_CST_MEMBER (stmt)) == FUNCTION_DECL
-	  && DECL_IMMEDIATE_FUNCTION_P (PTRMEM_CST_MEMBER (stmt)))
-	{
-	  if (!data->pset.add (stmt) && (complain & tf_error))
-	    {
-	      error_at (PTRMEM_CST_LOCATION (stmt),
-			"taking address of an immediate function %qD",
-			PTRMEM_CST_MEMBER (stmt));
-	      *stmt_p = build_zero_cst (TREE_TYPE (stmt));
-	    }
-	  return error_mark_node;
-	}
-      break;
+    case ADDR_EXPR:
+      {
+	decl = (code == PTRMEM_CST ? PTRMEM_CST_MEMBER (stmt)
+		: TREE_OPERAND (stmt, 0));
+	if (TREE_CODE (decl) != FUNCTION_DECL)
+	  break;
+	if (code == ADDR_EXPR && ADDR_EXPR_DENOTES_CALL_P (stmt))
+	  break;
+	if (data->flags & ff_escalating)
+	  goto escalate;
+	if (immediate_invocation_p (decl))
+	  {
+	    if (maybe_promote_function_to_consteval (current_function_decl))
+	      break;
+	    if (complain & tf_error)
+	      {
+		if (!data->pset.add (stmt))
+		  {
+		    taking_address_of_imm_fn_error (stmt, decl);
+		    *stmt_p = build_zero_cst (TREE_TYPE (stmt));
+		  }
+		/* If we're giving hard errors, continue the walk rather than
+		   bailing out after the first error.  */
+		break;
+	      }
+	    return error_mark_node;
+	  }
+	/* Not consteval yet, but could become one, in which case it's invalid
+	   to take its address.  */
+	else if (unchecked_immediate_escalating_function_p (decl))
+	  {
+	    /* auto p = &f<int>; in the global scope won't be ensconced in
+	       a function we could store for later at this point.  */
+	    if (!current_function_decl)
+	      remember_escalating_expr (stmt);
+	    else
+	      maybe_store_cfun_for_late_checking ();
+	  }
+	break;
+      }
 
     /* Expand immediate invocations.  */
     case CALL_EXPR:
     case AGGR_INIT_EXPR:
       if (tree fn = cp_get_callee (stmt))
 	if (TREE_CODE (fn) != ADDR_EXPR || ADDR_EXPR_DENOTES_CALL_P (fn))
-	  if (tree fndecl = cp_get_fndecl_from_callee (fn, /*fold*/false))
-	    if (DECL_IMMEDIATE_FUNCTION_P (fndecl))
-	      {
-		stmt = cxx_constant_value (stmt, complain);
-		if (stmt == error_mark_node)
-		  {
-		    if (complain & tf_error)
-		      *stmt_p = error_mark_node;
-		    return error_mark_node;
-		  }
-		*stmt_p = stmt;
-	      }
-      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))
-	{
-	  if (complain & tf_error)
+	  if ((decl = cp_get_fndecl_from_callee (fn, /*fold*/false)))
 	    {
-	      error_at (EXPR_LOCATION (stmt),
-			"taking address of an immediate function %qD",
-			TREE_OPERAND (stmt, 0));
-	      *stmt_p = build_zero_cst (TREE_TYPE (stmt));
+	      if (data->flags & ff_escalating)
+		{
+		  call = stmt;
+		  goto escalate;
+		}
+
+	      tree eval = NULL_TREE;
+	      /* Escalate once all templates have been instantiated.  */
+	      if (at_eof > 1)
+		maybe_escalate_decl_and_cfun (decl, stmt, &eval);
+
+	      /* [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 STMT does not need to produce
+		 a constant expression.  */
+	      if (immediate_invocation_p (decl))
+		{
+		  tree e = eval ? eval : cxx_constant_value (stmt, tf_none);
+		  if (e == error_mark_node)
+		    {
+		      if (maybe_promote_function_to_consteval
+			  (current_function_decl))
+			break;
+		      if (complain & tf_error)
+			{
+			  auto_diagnostic_group d;
+			  location_t loc = cp_expr_loc_or_input_loc (stmt);
+			  error_at (loc, "call to consteval function %qE is "
+				    "not a constant expression", stmt);
+			  /* Explain why it's not a constant expression.  */
+			  *stmt_p = cxx_constant_value (stmt, complain);
+			  maybe_explain_promoted_consteval (loc, decl);
+			  /* Don't return error_mark_node, it would stop our
+			     tree walk.  */
+			  break;
+			}
+		      return error_mark_node;
+		    }
+		  /* We've evaluated the consteval function call.  */
+		  *stmt_p = e;
+		}
+	      /* We've encountered a function call that may turn out to be
+		 consteval later.  Store its caller so that we can ensure
+		 that the call is a constant expression.  */
+	      else if (unchecked_immediate_escalating_function_p (decl))
+		{
+		  /* Make sure we're not inserting new elements while walking
+		     the deferred_escalating_exprs hash table; if we are, it's
+		     likely that a function wasn't properly marked checked for
+		     i-e expressions.  */
+		  gcc_checking_assert (at_eof <= 1);
+		  maybe_store_cfun_for_late_checking ();
+		}
 	    }
-	  return error_mark_node;
-	}
       break;
 
     default:
@@ -1107,11 +1347,35 @@ cp_fold_immediate_r (tree *stmt_p, int *walk_subtrees, void *data_)
     }
 
   return NULL_TREE;
+
+escalate:
+  /* Not consteval yet, but could be.  Have to look deeper.  */
+  if (unchecked_immediate_escalating_function_p (decl))
+    {
+      /* Set before the actual walk to avoid endless recursion.  */
+      DECL_ESCALATION_CHECKED_P (decl) = true;
+      cp_fold_data d (ff_escalating, data->caller ? decl : NULL_TREE);
+      cp_walk_tree (&DECL_SAVED_TREE (decl), cp_fold_immediate_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.  */
+      if (data->caller)
+	promote_function_to_consteval (data->caller);
+      *walk_subtrees = 0;
+      return stmt;
+    }
+
+  return NULL_TREE;
 }
 
 /* A wrapper around cp_fold_immediate_r.  Return true if we found
    a non-constant immediate function, or taking the address of an
-   immediate function.  */
+   immediate function.  If ESCALATE_P, tell cp_fold_immediate_r to
+   escalate immediate-escalating functions.  */
 
 bool
 cp_fold_immediate (tree *tp, mce_value manifestly_const_eval)
@@ -1119,12 +1383,14 @@ cp_fold_immediate (tree *tp, mce_value manifestly_const_eval)
   if (cxx_dialect <= cxx17)
     return false;
 
-  fold_flags_t flags = ff_fold_immediate;
+  fold_flags_t flags = ff_none;
   if (manifestly_const_eval == mce_false)
     flags |= ff_mce_false;
 
   cp_fold_data data (flags);
-  return !!cp_walk_tree_without_duplicates (tp, cp_fold_immediate_r, &data);
+  int save_errorcount = errorcount;
+  tree r = cp_walk_tree_without_duplicates (tp, cp_fold_immediate_r, &data);
+  return r != NULL_TREE || errorcount > save_errorcount;
 }
 
 /* Perform any pre-gimplification folding of C++ front end trees to
@@ -1174,7 +1440,15 @@ cp_fold_r (tree *stmt_p, int *walk_subtrees, void *data_)
 	  *walk_subtrees = 0;
 	  /* Don't return yet, still need the cp_fold below.  */
 	}
-      cp_fold_immediate_r (stmt_p, walk_subtrees, data);
+
+      /* For +foo(), the cp_fold below will remove the +, and the subsequent
+	 tree walk would go straight down to the CALL_EXPR's operands, meaning
+	 that cp_fold_immediate_r would never see the CALL_EXPR.  Ew :(.  */
+      if (code == UNARY_PLUS_EXPR
+	  && TREE_CODE (TREE_OPERAND (stmt, 0)) == CALL_EXPR)
+	cp_fold_immediate_r (&TREE_OPERAND (stmt, 0), walk_subtrees, data);
+      else
+	cp_fold_immediate_r (stmt_p, walk_subtrees, data);
     }
 
   *stmt_p = stmt = cp_fold (*stmt_p, data->flags);
@@ -1300,6 +1574,81 @@ cp_fold_function (tree fndecl)
      pass ff_mce_false.  */
   cp_fold_data data (ff_genericize | ff_mce_false);
   cp_walk_tree (&DECL_SAVED_TREE (fndecl), cp_fold_r, &data, NULL);
+
+  /* This is merely an optimization: if FNDECL has no i-e expressions,
+     we'll not save c_f_d, and we can safely say that FNDECL will not
+     be promoted to consteval.  */
+  if (deferred_escalating_exprs
+      && !deferred_escalating_exprs->contains (current_function_decl))
+    DECL_ESCALATION_CHECKED_P (fndecl) = true;
+}
+
+/* FN is not a consteval function, but may become one.  Remember to
+   escalate it after all pending templates have been instantiated.  */
+
+void
+maybe_store_immediate_escalating_fn (tree fn)
+{
+  if (flag_immediate_escalation
+      && unchecked_immediate_escalating_function_p (fn))
+    remember_escalating_expr (fn);
+}
+
+/* We've stashed immediate-escalating functions.  Now see if they indeed
+   ought to be promoted to consteval.  */
+
+void
+process_pending_immediate_escalating_fns ()
+{
+  /* This will be null for -fno-immediate-escalation.  */
+  if (!deferred_escalating_exprs)
+    return;
+
+  for (auto e : *deferred_escalating_exprs)
+    if (TREE_CODE (e) == FUNCTION_DECL)
+      {
+	if (!DECL_ESCALATION_CHECKED_P (e))
+	  {
+	    temp_override<tree> cfd (current_function_decl, e);
+	    cp_fold_immediate (&DECL_SAVED_TREE (e), mce_false);
+	  }
+	if (DECL_IMMEDIATE_FUNCTION_P (e))
+	  deferred_escalating_exprs->remove (e);
+      }
+}
+
+/* We've escalated every function that could have been promoted to
+   consteval.  Check that we are not taking the address of a consteval
+   function.  */
+
+void
+check_immediate_escalating_refs ()
+{
+  /* This will be null for -fno-immediate-escalation.  */
+  if (!deferred_escalating_exprs)
+    return;
+
+  for (auto ref : *deferred_escalating_exprs)
+    {
+      if (TREE_CODE (ref) == FUNCTION_DECL)
+	/* We saw a function call to an immediate-escalating function in
+	   the body of REF.  Check that it's a constant if it was promoted
+	   to consteval.  */
+	{
+	  temp_override<tree> cfd (current_function_decl, ref);
+	  cp_fold_immediate (&DECL_SAVED_TREE (ref), mce_false);
+	}
+      else
+	{
+	  tree decl = (TREE_CODE (ref) == PTRMEM_CST
+		       ? PTRMEM_CST_MEMBER (ref)
+		       : TREE_OPERAND (ref, 0));
+	  if (DECL_IMMEDIATE_FUNCTION_P (decl))
+	    taking_address_of_imm_fn_error (ref, decl);
+	}
+    }
+
+  deferred_escalating_exprs = nullptr;
 }
 
 /* Turn SPACESHIP_EXPR EXPR into GENERIC.  */
diff --git a/gcc/cp/cp-tree.h b/gcc/cp/cp-tree.h
index 98b29e9cf81..d4ec3c3e7c1 100644
--- a/gcc/cp/cp-tree.h
+++ b/gcc/cp/cp-tree.h
@@ -2938,8 +2938,9 @@ struct GTY(()) lang_decl_fn {
   unsigned maybe_deleted : 1;
   unsigned coroutine_p : 1;
   unsigned implicit_constexpr : 1;
+  unsigned escalated_p : 1;
 
-  unsigned spare : 9;
+  unsigned spare : 8;
 
   /* 32-bits padding on 64-bit host.  */
 
@@ -3391,6 +3392,14 @@ struct GTY(()) lang_decl {
 #define DECL_MAYBE_DELETED(NODE) \
   (LANG_DECL_FN_CHECK (NODE)->maybe_deleted)
 
+/* Nonzero for FUNCTION_DECL means that this function's body has been
+   checked for immediate-escalating expressions and maybe promoted.  It
+   does *not* mean the function is consteval.  It must not be set in
+   a function that was marked consteval by the user, so that we can
+   distinguish between explicitly consteval functions and promoted consteval
+   functions.  */
+#define DECL_ESCALATION_CHECKED_P(NODE) (LANG_DECL_FN_CHECK (NODE)->escalated_p)
+
 /* True (in a FUNCTION_DECL) if NODE is a virtual function that is an
    invalid overrider for a function from a base class.  Once we have
    complained about an invalid overrider we avoid complaining about it
@@ -5849,7 +5858,8 @@ extern GTY(()) vec<tree, va_gc> *keyed_classes;
 
 \f
 /* Nonzero if we're done parsing and into end-of-file activities.
-   Two if we're done with front-end processing.  */
+   2 if all templates have been instantiated.
+   3 if we're done with front-end processing.  */
 
 extern int at_eof;
 
@@ -6741,6 +6751,7 @@ 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 tree build_cxx_call			(tree, int, tree *,
 						 tsubst_flags_t,
 						 tree = NULL_TREE);
@@ -8384,6 +8395,9 @@ extern bool simple_empty_class_p		(tree, tree, tree_code);
 extern tree fold_builtin_source_location	(const_tree);
 extern tree get_source_location_impl_type	();
 extern bool cp_fold_immediate			(tree *, mce_value);
+extern void check_immediate_escalating_refs	();
+extern void maybe_store_immediate_escalating_fn	(tree);
+extern void process_pending_immediate_escalating_fns ();
 
 /* in name-lookup.cc */
 extern tree strip_using_decl                    (tree);
diff --git a/gcc/cp/decl.cc b/gcc/cp/decl.cc
index 4a07c7e879b..be73e2339d3 100644
--- a/gcc/cp/decl.cc
+++ b/gcc/cp/decl.cc
@@ -18343,7 +18343,10 @@ finish_function (bool inline_p)
   if (!processing_template_decl
       && !DECL_IMMEDIATE_FUNCTION_P (fndecl)
       && !DECL_OMP_DECLARE_REDUCTION_P (fndecl))
-    cp_fold_function (fndecl);
+    {
+      cp_fold_function (fndecl);
+      maybe_store_immediate_escalating_fn (fndecl);
+    }
 
   /* Set up the named return value optimization, if we can.  Candidate
      variables are selected in check_return_expr.  */
diff --git a/gcc/cp/decl2.cc b/gcc/cp/decl2.cc
index 0aa1e355972..c7b50c5781a 100644
--- a/gcc/cp/decl2.cc
+++ b/gcc/cp/decl2.cc
@@ -169,7 +169,9 @@ typedef hash_map<unsigned/*Priority*/, tree/*List*/,
    one for init.  The fini table is only ever used when !cxa_atexit.  */
 static GTY(()) priority_map_t *static_init_fini_fns[2];
 
-/* Nonzero if we're done parsing and into end-of-file activities.  */
+/* Nonzero if we're done parsing and into end-of-file activities.
+   2 if all templates have been instantiated.
+   3 if we're done with front-end processing.  */
 
 int at_eof;
 
@@ -4981,6 +4983,7 @@ c_parse_final_cleanups (void)
   tree decl;
 
   locus_at_end_of_parsing = input_location;
+  /* We're done parsing.  */
   at_eof = 1;
 
   /* Bad parse errors.  Just forget about it.  */
@@ -5246,6 +5249,9 @@ c_parse_final_cleanups (void)
 	reconsider = true;
     }
 
+  /* All templates have been instantiated.  */
+  at_eof = 2;
+
   void *module_cookie = finish_module_processing (parse_in);
 
   lower_var_init ();
@@ -5288,7 +5294,15 @@ c_parse_final_cleanups (void)
   if (static_init_fini_fns[true])
     for (auto iter : *static_init_fini_fns[true])
       iter.second = nreverse (iter.second);
-  
+
+  /* Now we've instantiated all templates.  Now we can escalate the functions
+     we squirreled away earlier.  */
+  if (flag_immediate_escalation)
+    {
+      process_pending_immediate_escalating_fns ();
+      check_immediate_escalating_refs ();
+    }
+
   /* Then, do the Objective-C stuff.  This is where all the
      Objective-C module stuff gets generated (symtab,
      class/protocol/selector lists etc).  This must be done after C++
@@ -5370,7 +5384,7 @@ c_parse_final_cleanups (void)
   timevar_start (TV_PHASE_PARSING);
 
   /* Indicate that we're done with front end processing.  */
-  at_eof = 2;
+  at_eof = 3;
 }
 
 /* Perform any post compilation-proper cleanups for the C++ front-end.
diff --git a/gcc/cp/error.cc b/gcc/cp/error.cc
index 785909c362a..3b1b5de5ea4 100644
--- a/gcc/cp/error.cc
+++ b/gcc/cp/error.cc
@@ -478,7 +478,7 @@ dump_template_bindings (cxx_pretty_printer *pp, tree parms, tree args,
 
   /* Don't try to do this once cgraph starts throwing away front-end
      information.  */
-  if (at_eof >= 2)
+  if (at_eof >= 3)
     return;
 
   FOR_EACH_VEC_SAFE_ELT (typenames, i, t)
diff --git a/gcc/cp/module.cc b/gcc/cp/module.cc
index c1c8c226bc1..72946f1b065 100644
--- a/gcc/cp/module.cc
+++ b/gcc/cp/module.cc
@@ -5683,6 +5683,8 @@ trees_out::lang_decl_bools (tree t)
       WB (lang->u.fn.has_dependent_explicit_spec_p);
       WB (lang->u.fn.immediate_fn_p);
       WB (lang->u.fn.maybe_deleted);
+      WB (lang->u.fn.escalated_p);
+      /* We do not stream lang->u.fn.implicit_constexpr.  */
       goto lds_min;
 
     case lds_decomp:  /* lang_decl_decomp.  */
@@ -5751,6 +5753,8 @@ trees_in::lang_decl_bools (tree t)
       RB (lang->u.fn.has_dependent_explicit_spec_p);
       RB (lang->u.fn.immediate_fn_p);
       RB (lang->u.fn.maybe_deleted);
+      RB (lang->u.fn.escalated_p);
+      /* We do not stream lang->u.fn.implicit_constexpr.  */
       goto lds_min;
 
     case lds_decomp:  /* lang_decl_decomp.  */
diff --git a/gcc/cp/pt.cc b/gcc/cp/pt.cc
index 324f6f01555..e33adb6def3 100644
--- a/gcc/cp/pt.cc
+++ b/gcc/cp/pt.cc
@@ -11130,7 +11130,7 @@ push_tinst_level_loc (tree tldcl, tree targs, location_t loc)
   if (tinst_depth >= max_tinst_depth)
     {
       /* Tell error.cc not to try to instantiate any templates.  */
-      at_eof = 2;
+      at_eof = 3;
       fatal_error (input_location,
 		   "template instantiation depth exceeds maximum of %d"
 		   " (use %<-ftemplate-depth=%> to increase the maximum)",
diff --git a/gcc/cp/typeck.cc b/gcc/cp/typeck.cc
index 49afbd8fb5e..380826ca1e9 100644
--- a/gcc/cp/typeck.cc
+++ b/gcc/cp/typeck.cc
@@ -7228,11 +7228,9 @@ cp_build_addr_expr_1 (tree arg, bool strict_lvalue, tsubst_flags_t complain)
 			      complain);
     }
 
-  /* For addresses of immediate functions ensure we have EXPR_LOCATION
-     set for possible later diagnostics.  */
+  /* Ensure we have EXPR_LOCATION 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)))
+      && TREE_CODE (TREE_OPERAND (val, 0)) == FUNCTION_DECL)
     SET_EXPR_LOCATION (val, input_location);
 
   return val;
diff --git a/gcc/doc/invoke.texi b/gcc/doc/invoke.texi
index 80bb1efac40..c1f6cfecfec 100644
--- a/gcc/doc/invoke.texi
+++ b/gcc/doc/invoke.texi
@@ -219,6 +219,7 @@ in the following sections.
 -fno-elide-constructors
 -fno-enforce-eh-specs
 -fno-gnu-keywords
+-fno-immediate-escalation
 -fno-implicit-templates
 -fno-implicit-inline-templates
 -fno-implement-inlines
@@ -3379,6 +3380,39 @@ word as an identifier.  You can use the keyword @code{__typeof__} instead.
 This option is implied by the strict ISO C++ dialects: @option{-ansi},
 @option{-std=c++98}, @option{-std=c++11}, etc.
 
+@opindex fno-immediate-escalation
+@opindex fimmediate-escalation
+@item -fno-immediate-escalation
+Do not enable immediate function escalation whereby certain functions
+can be promoted to consteval, as specified in P2564R3.  For example:
+
+@example
+consteval int id(int i) @{ return i; @}
+
+constexpr int f(auto t)
+@{
+  return t + id(t); // id causes f<int> to be promoted to consteval
+@}
+
+void g(int i)
+@{
+  f (3);
+@}
+@end example
+
+compiles in C++20: @code{f} is an immediate-escalating function (due to
+the @code{auto} it is a function template and is declared @code{constexpr})
+and @code{id(t)} is an immediate-escalating expression, so @code{f} is
+promoted to @code{consteval}.  Consequently, the call to @code{id(t)}
+is in an immediate context, so doesn't have to produce a constant (that
+is the mechanism allowing consteval function composition).  However,
+with @option{-fno-immediate-escalation}, @code{f} is not promoted to
+@code{consteval}, and since the call to consteval function @code{id(t)}
+is not a constant expression, the compiler rejects the code.
+
+This option is turned on by default; it is only effective in C++20 mode
+or later.
+
 @opindex fimplicit-constexpr
 @item -fimplicit-constexpr
 Make inline functions implicitly constexpr, if they satisfy the
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 b2c5472b7de..3b258711ce6 100644
--- a/gcc/testsuite/g++.dg/cpp23/consteval-if2.C
+++ b/gcc/testsuite/g++.dg/cpp23/consteval-if2.C
@@ -33,7 +33,7 @@ baz (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);	// { dg-error "not a constant expression" }
     }
   else
     {
@@ -45,11 +45,11 @@ baz (int x)
     }
   else
     {
-      r += foo (8 * x);	// { dg-error "'x' is not a constant expression" }
+      r += foo (8 * x);	// { dg-error "is not a constant expression" }
     }
   if ! consteval	// { dg-warning "'if consteval' only available with" "" { target c++20_only } }
     {
-      r += foo (32 * x);// { dg-error "'x' is not a constant expression" }
+      r += foo (32 * x);// { dg-error "not a constant expression" }
     }
   if consteval		// { dg-warning "'if consteval' only available with" "" { target c++20_only } }
     {
@@ -98,7 +98,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
     {
@@ -110,11 +110,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 } }
     {
@@ -126,5 +126,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-memfn1.C b/gcc/testsuite/g++.dg/cpp2a/consteval-memfn1.C
index 46eed13446d..ca923519f98 100644
--- a/gcc/testsuite/g++.dg/cpp2a/consteval-memfn1.C
+++ b/gcc/testsuite/g++.dg/cpp2a/consteval-memfn1.C
@@ -20,10 +20,13 @@ template<class>
 void VerifyHash(fixed_string s) {
   s.size(0); // { dg-bogus "" }
   s.size(-1); // { dg-message "expansion of" }
+// { dg-error "call to consteval function" "" { target *-*-* } .-1 }
   s.size_static(0); // { dg-bogus "" }
   s.size_static(-1); // { dg-message "expansion of" }
+// { dg-error "call to consteval function" "" { target *-*-* } .-1 }
   fixed_string::size_static(0); // { dg-bogus "" }
   fixed_string::size_static(-1); // { dg-message "expansion of" }
+// { dg-error "call to consteval function" "" { target *-*-* } .-1 }
   s(); // { dg-bogus "" }
 }
 
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..5e7b208113f
--- /dev/null
+++ b/gcc/testsuite/g++.dg/cpp2a/consteval-prop1.C
@@ -0,0 +1,169 @@
+// 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); // { dg-message "immediate-escalating expression .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); // { dg-message "immediate-escalating expression .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; // { dg-message "immediate-escalating expression .id." }
+  (void) p;
+  return t;
+}
+
+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};
+}
+
+constexpr void
+f5_nt (int i)
+{
+  A a{i}; // { dg-error "call to consteval function|not a constant" }
+}
+
+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; // { dg-message ".int g. is not const" }
+
+B b2(f0<int>(cid<int>(g))); // { dg-error "call to consteval function|not usable" }
+
+struct C {
+  consteval C (int) {};
+};
+
+constexpr int
+f7 (auto t)
+{
+  C c(t); // { dg-message "immediate-escalating expression .c.C::C\\(t\\)." }
+  return 0;
+}
+
+int i1 = f7 (g); // { dg-error "call to consteval function|not usable" }
+
+struct Y {
+  int y;
+  int x = id (y);
+  consteval Y (int i) : y (id (i)) {}
+};
+
+Y y1(1);
+Y y2(g); // { dg-error "call to consteval function|not usable" }
+
+struct Y2 {
+  int y;
+  int x = id (y);
+  constexpr Y2 (auto i) : y (id (i)) {}
+};
+
+Y2 y3(1);
+Y2 y4(g); // { dg-error "call to consteval function|not usable" }
+
+auto l1 = [](int i) constexpr {
+  int t = id (i);
+  return id (0);
+};
+
+int (*pl1)(int) = l1; // { dg-error "call to consteval function|returns address of immediate function" }
+
+auto l2 = [](int i) {
+  int t = id (i);
+  return id (0);
+};
+
+int (*pl2)(int) = l2; // { dg-error "call to consteval function|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 "used before its definition" }
+  int a = [] (int u = undef ()) {
+    return u;
+  }();
+} s2; // { dg-error "call to consteval function" }
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..4e33e6e3d0e
--- /dev/null
+++ b/gcc/testsuite/g++.dg/cpp2a/consteval-prop10.C
@@ -0,0 +1,41 @@
+// 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; // { dg-message ".int non_const. is not const" }
+
+template<typename>
+constexpr int
+f2 (int i = id (non_const))
+{
+  return i;
+}
+
+constexpr int
+f3 (auto)
+{
+  return f2<int>(); // { dg-message "contains an immediate-escalating expression .id\\(non_const\\)." }
+}
+
+auto a = &f3<int>; // { dg-error "taking address of an immediate function" }
+
+void
+g (int i)
+{
+  f1<int> (42);
+  f1<int> (i);
+  f1<int> ();
+  f2<int> (42);
+  f2<int> (i);
+  f2<int> (); // { dg-error "call to consteval function .id\\(non_const\\). is not a constant expression" }
+// { dg-error ".non_const. is not usable in a constant expression" "" { target *-*-* } .-1 }
+}
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..aca9675cd53
--- /dev/null
+++ b/gcc/testsuite/g++.dg/cpp2a/consteval-prop11.C
@@ -0,0 +1,49 @@
+// 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
+foobar (auto i)
+{
+  return i + id (i);
+  /* { dg-begin-multiline-output "" }
+   return i + id (i);
+              ~~~^~~
+     { dg-end-multiline-output "" } */
+}
+
+void
+g (int x)
+{
+  foobar (x); // { dg-error "10:call to consteval function .foobar<int>\\(x\\). is not a constant expression" }
+// { dg-error ".x. is not a constant expression" "" { target *-*-* } .-1 }
+  /* { dg-begin-multiline-output "" }
+foobar (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|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..cdc1f6dc862
--- /dev/null
+++ b/gcc/testsuite/g++.dg/cpp2a/consteval-prop14.C
@@ -0,0 +1,78 @@
+// 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\\(\\(i \\* 2\\)\\)." }
+}
+
+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" }
+// { dg-error ".i. is not a constant expression" "" { target *-*-* } .-1 }
+  f1 (42);
+  f2 (i); // { dg-error "call to consteval function .f2<int>\\(i\\). is not a constant expression" }
+// { dg-error ".i. is not a constant expression" "" { target *-*-* } .-1 }
+  f2 (42);
+  f3 (i); // { dg-error "call to consteval function .f3<int>\\(i\\). is not a constant expression" }
+// { dg-error ".i. is not a constant expression" "" { target *-*-* } .-1 }
+  f3 (42);
+  f4 (i); // { dg-error "call to consteval function .f4<int>\\(i\\). is not a constant expression" }
+// { dg-error ".i. is not a constant expression" "" { target *-*-* } .-1 }
+  f4 (42);
+  f5 (i); // { dg-error "call to consteval function .f5<int>\\(i\\). is not a constant expression" }
+// { dg-error ".i. is not a constant expression" "" { target *-*-* } .-1 }
+  f5 (42);
+  f6 (i); // { dg-error "call to consteval function .f6<int>\\(i\\). is not a constant expression" }
+// { dg-error ".i. is not a constant expression" "" { target *-*-* } .-1 }
+  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..3341c510a9f
--- /dev/null
+++ b/gcc/testsuite/g++.dg/cpp2a/consteval-prop15.C
@@ -0,0 +1,107 @@
+// 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;
+}
+
+constexpr int
+f9 (auto i)
+{
+  if consteval {
+    return id(i);
+  }
+  return i;
+}
+
+constexpr int
+f10 (auto i)
+{
+  if not consteval {
+    (void) 0;
+  } else {
+    return id(i);
+  }
+  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" }
+// { dg-error ".non_const. is not a constant expression" "" { target *-*-* } .-1 }
+  f5 (42);
+  f5 (non_const); // { dg-error "call to consteval function .f5<int>\\(non_const\\). is not a constant expression" }
+// { dg-error ".non_const. is not a constant expression" "" { target *-*-* } .-1 }
+  f6 (42);
+  f6 (non_const);
+  f7 (42);
+  f7 (non_const);
+  f8 (42);
+  f8 (non_const);
+  f9 (42);
+  f9 (non_const);
+  f10 (42);
+  f10 (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..47ec9b60b6c
--- /dev/null
+++ b/gcc/testsuite/g++.dg/cpp2a/consteval-prop17.C
@@ -0,0 +1,17 @@
+// P2564R3
+// { dg-do compile { target c++20 } }
+// { dg-options "-fno-immediate-escalation" }
+
+consteval int id(int i) { return i; }
+
+constexpr int
+f (auto i)
+{
+  return id (i); // { dg-error "not a constant expression" }
+}
+
+int
+g ()
+{
+  return f (42);
+}
diff --git a/gcc/testsuite/g++.dg/cpp2a/consteval-prop18.C b/gcc/testsuite/g++.dg/cpp2a/consteval-prop18.C
new file mode 100644
index 00000000000..a18106f8e0f
--- /dev/null
+++ b/gcc/testsuite/g++.dg/cpp2a/consteval-prop18.C
@@ -0,0 +1,20 @@
+// P2564R3
+// { dg-do compile { target c++20 } }
+
+consteval int id(int i) { return i; }
+
+constexpr int
+f (auto t)
+{
+  return t + id (t);
+}
+
+constexpr int
+f2 (auto t)
+{
+  return t + f(t); // { dg-message "immediate-escalating expression .f<int>\\(t\\)." }
+}
+
+int z; // { dg-message "not const" }
+auto y1 = f2 (42);
+auto y2 = f2 (z); // { dg-error "value of .z. is not usable in a constant expression|call to consteval function" }
diff --git a/gcc/testsuite/g++.dg/cpp2a/consteval-prop19.C b/gcc/testsuite/g++.dg/cpp2a/consteval-prop19.C
new file mode 100644
index 00000000000..3ceb05e41f4
--- /dev/null
+++ b/gcc/testsuite/g++.dg/cpp2a/consteval-prop19.C
@@ -0,0 +1,7 @@
+// P2564R3
+// { dg-do compile { target c++20 } }
+
+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;  // { dg-error "call to consteval function|returns address of immediate function" }
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..30129a4a266
--- /dev/null
+++ b/gcc/testsuite/g++.dg/cpp2a/consteval-prop2.C
@@ -0,0 +1,90 @@
+// 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);		// { dg-message "immediate-escalating expression .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 "the value of .x. is not usable in a constant expression" }
+// { dg-message "immediate-escalating expression .id\\(x\\)." "" { target *-*-* } .-1 }
+}
+
+int i = hh<int>();              // { dg-error "call to consteval function|called in a constant expression" }
+				// error: hh<int>() is an immediate-escalating expression
+                                // outside of an immediate-escalating function
+struct A {
+  int x;
+  int y = id(x);
+};
+
+// [expr.const]#example-9 says:
+//   k<int> is not an immediate function because A(42) is a
+//   constant expression and thus not immediate-escalating
+// In the evaluation of A(42), the member x has just been initialized
+// to constant 42.  And A(42) is constant-evaluated because "An aggregate
+// initialization is an immediate invocation if it evaluates a default
+// member initializer that has a subexpression that is an
+// immediate-escalating expression."
+template<class T>
+constexpr int k(int) {
+  return A(42).y;
+}
+
+int
+test (int i)
+{
+  int r = g (42) + g(i);
+  int t = k<int>(42)
+	    + k<int>(i); // { dg-bogus "call to|constant" "" { xfail *-*-* } }
+  return r + t;
+}
+
+// Just like above, but make the call to id(x) actually a constant.
+struct A2 {
+  static constexpr int x = 42;
+  int y = id(x);
+};
+
+template<class T>
+constexpr int k2(int) {
+  return A2(42).y;
+}
+
+int
+test2 (int i)
+{
+  return k2<int>(42) + k2<int>(i);
+}
diff --git a/gcc/testsuite/g++.dg/cpp2a/consteval-prop20.C b/gcc/testsuite/g++.dg/cpp2a/consteval-prop20.C
new file mode 100644
index 00000000000..f1bb08e2dba
--- /dev/null
+++ b/gcc/testsuite/g++.dg/cpp2a/consteval-prop20.C
@@ -0,0 +1,21 @@
+// P2564R3
+// { dg-do compile { target c++20 } }
+// { dg-options "-Wno-c++23-extensions" }
+
+consteval int id(int i) { return i; }
+
+constexpr int
+f (auto i)
+{
+  return id (i);
+}
+
+void
+g ()
+{
+  auto p = &f<int>; // { dg-error "taking address" }
+  decltype(&f<int>) x;
+  if consteval {
+    auto q = &f<int>;
+  }
+}
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..f181cb32942
--- /dev/null
+++ b/gcc/testsuite/g++.dg/cpp2a/consteval-prop3.C
@@ -0,0 +1,27 @@
+// 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);
+
+auto a2 = &f2<char>; // { dg-error "taking address" }
+auto b2 = &f2<int>; // { dg-error "taking address" }
+
+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..3a2e09b17b0
--- /dev/null
+++ b/gcc/testsuite/g++.dg/cpp2a/consteval-prop4.C
@@ -0,0 +1,30 @@
+// P2564R3
+// { dg-do compile { target c++20 } }
+// 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..93ed398d9bf
--- /dev/null
+++ b/gcc/testsuite/g++.dg/cpp2a/consteval-prop6.C
@@ -0,0 +1,59 @@
+// 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(){} // { dg-error "call to consteval function" }
+
+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 "modification of .x. is not a constant expression" }
+};
+
+void
+test ()
+{
+  [[maybe_unused]] X x; // { dg-error "call to consteval function" }
+// { dg-message "promoted to an immediate function" "" { target *-*-* } .-1 }
+}
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..118cf576f14
--- /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 "call to consteval function|not usable" }
+constexpr auto a2 = f2 (non_const); // { dg-error "not a constant|not usable" }
+auto a3 = f3 (42);
+constexpr auto a4 = f4 (42);
+
+void
+g ()
+{
+   auto a5 = f5 (non_const); // { dg-error "not a constant|not usable" }
+   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|not usable" }
+   (void) f10 (42);
+   (void) g1 (non_const); // { dg-error "not a constant|not usable" }
+}
+
+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 "call to consteval function|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..080fc76f26e
--- /dev/null
+++ b/gcc/testsuite/g++.dg/cpp2a/consteval-prop8.C
@@ -0,0 +1,82 @@
+// 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};
+}
+
+void
+f3 (int i)
+{
+  A a{i}; // { dg-error "not a constant expression" }
+}
+
+inline void
+f7 (int i)
+{
+  A a{i}; // { dg-error "not a constant expression" }
+}
+
+constexpr void
+f8 (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/consteval11.C b/gcc/testsuite/g++.dg/cpp2a/consteval11.C
index 05cecea4502..c2ee3c7a82a 100644
--- a/gcc/testsuite/g++.dg/cpp2a/consteval11.C
+++ b/gcc/testsuite/g++.dg/cpp2a/consteval11.C
@@ -8,9 +8,11 @@ constexpr int a = bar (1);
 constexpr int b = bar (2);		// { dg-message "in 'constexpr' expansion of" }
 constexpr int c = 0 ? bar (3) : 1;
 const int d = bar (4);			// { dg-message "in 'constexpr' expansion of" }
+// { dg-error "call to consteval function" "" { target *-*-* } .-1 }
 const int e = 0 ? bar (5) : 1;
 int f = bar (1);
 int g = bar (6);			// { dg-message "in 'constexpr' expansion of" }
+// { dg-error "call to consteval function" "" { target *-*-* } .-1 }
 int h = 0 ? bar (7) : 1;
 
 void
@@ -20,25 +22,35 @@ foo ()
   constexpr int b = bar (2);		// { dg-message "in 'constexpr' expansion of" }
   constexpr int c = 0 ? bar (3) : 1;
   const int d = bar (4);		// { dg-message "in 'constexpr' expansion of" }
+// { dg-error "call to consteval function" "" { target *-*-* } .-1 }
   const int e = 0 ? bar (5) : 1;
   int f = bar (1);
   int g = bar (6);			// { dg-message "in 'constexpr' expansion of" }
+// { dg-error "call to consteval function" "" { target *-*-* } .-1 }
   int h = 0 ? bar (7) : 1;		// { dg-message "in 'constexpr' expansion of" }
+// { dg-error "call to consteval function" "" { target *-*-* } .-1 }
   h += 0 ? bar (8) : 1;			// { dg-message "in 'constexpr' expansion of" }
+// { dg-error "call to consteval function" "" { target *-*-* } .-1 }
   if (0)
     bar (9);				// { dg-message "in 'constexpr' expansion of" }
+// { dg-error "call to consteval function" "" { target *-*-* } .-1 }
   else
     bar (10);				// { dg-message "in 'constexpr' expansion of" }
+// { dg-error "call to consteval function" "" { target *-*-* } .-1 }
   if (1)
     bar (11);				// { dg-message "in 'constexpr' expansion of" }
+// { dg-error "call to consteval function" "" { target *-*-* } .-1 }
   else
     bar (12);				// { dg-message "in 'constexpr' expansion of" }
+// { dg-error "call to consteval function" "" { target *-*-* } .-1 }
   if constexpr (0)
     bar (13);
   else
     bar (14);				// { dg-message "in 'constexpr' expansion of" }
+// { dg-error "call to consteval function" "" { target *-*-* } .-1 }
   if constexpr (1)
     bar (15);				// { dg-message "in 'constexpr' expansion of" }
+// { dg-error "call to consteval function" "" { target *-*-* } .-1 }
   else
     bar (16);
 }
@@ -121,18 +133,24 @@ quux ()
 {
   if (0)
     bar ((T) 2);				// { dg-message "in 'constexpr' expansion of" }
+// { dg-error "call to consteval function" "" { target *-*-* } .-1 }
   else
     bar ((T) 3);				// { dg-message "in 'constexpr' expansion of" }
+// { dg-error "call to consteval function" "" { target *-*-* } .-1 }
   if (1)
     bar ((T) 4);				// { dg-message "in 'constexpr' expansion of" }
+// { dg-error "call to consteval function" "" { target *-*-* } .-1 }
   else
     bar ((T) 5);				// { dg-message "in 'constexpr' expansion of" }
+// { dg-error "call to consteval function" "" { target *-*-* } .-1 }
   if constexpr (0)
     bar ((T) 6);
   else
     bar ((T) 7);				// { dg-message "in 'constexpr' expansion of" }
+// { dg-error "call to consteval function" "" { target *-*-* } .-1 }
   if constexpr (1)
     bar ((T) 8);				// { dg-message "in 'constexpr' expansion of" }
+// { dg-error "call to consteval function" "" { target *-*-* } .-1 }
   else
     bar ((T) 9);
 }
diff --git a/gcc/testsuite/g++.dg/cpp2a/consteval3.C b/gcc/testsuite/g++.dg/cpp2a/consteval3.C
index 9efac8c8eae..1199e9db623 100644
--- a/gcc/testsuite/g++.dg/cpp2a/consteval3.C
+++ b/gcc/testsuite/g++.dg/cpp2a/consteval3.C
@@ -16,8 +16,8 @@ consteval auto [ b, c ] = S ();		// { dg-error "structured binding declaration c
 int f5 (consteval int x) { return x; }	// { dg-error "a parameter cannot be declared 'consteval'" }
 consteval int f6 (int x) { return x; }
 int d = 6;		// { dg-message "'int d' is not const" }
-int e = f6 (d);		// { dg-error "the value of 'd' is not usable in a constant expression" }
-constexpr int f7 (int x) { return f6 (x); }	// { dg-error "'x' is not a constant expression" }
+int e = f6 (d);		// { dg-error "the value of 'd' is not usable in a constant expression|call to consteval function" }
+constexpr int f7 (int x) { return f6 (x); }	// { dg-error "'x' is not a constant expression|call to consteval function" }
 constexpr int f = f7 (5);
 using fnptr = int (int);
 fnptr *g = f6;		// { dg-error "taking address of an immediate function 'consteval int f6\\(int\\)'" }
diff --git a/gcc/testsuite/g++.dg/cpp2a/consteval34.C b/gcc/testsuite/g++.dg/cpp2a/consteval34.C
index 068827ba516..7562f403f74 100644
--- a/gcc/testsuite/g++.dg/cpp2a/consteval34.C
+++ b/gcc/testsuite/g++.dg/cpp2a/consteval34.C
@@ -7,6 +7,7 @@ constexpr int
 foo (bool b)
 {
   return b ? bar (3) : 2; // { dg-message "in .constexpr. expansion" }
+// { dg-error "call to consteval function" "" { target *-*-* } .-1 }
 }
 
 static_assert (foo (false) == 2);
@@ -22,13 +23,20 @@ void
 g ()
 {
   __extension__ int a1[bar(3)]; // { dg-message "in .constexpr. expansion" }
+// { dg-error "call to consteval function" "" { target *-*-* } .-1 }
   int a2[sizeof (bar(3))];
 
   int a3 = false ? (1 + bar (8)) : 1; // { dg-message "in .constexpr. expansion" }
+// { dg-error "call to consteval function" "" { target *-*-* } .-1 }
   a3 += false ? (1 + bar (8)) : 1; // { dg-message "in .constexpr. expansion" }
+// { dg-error "call to consteval function" "" { target *-*-* } .-1 }
 
   __extension__ int a4 = false ?: (1 + bar (8)); // { dg-message "in .constexpr. expansion" }
+// { dg-error "call to consteval function" "" { target *-*-* } .-1 }
   __extension__ int a5 = true ?: (1 + bar (8)); // { dg-message "in .constexpr. expansion" }
+// { dg-error "call to consteval function" "" { target *-*-* } .-1 }
   int a6 = bar (2) ? 1 : 2; // { dg-message "in .constexpr. expansion" }
+// { dg-error "call to consteval function" "" { target *-*-* } .-1 }
   int a7 = bar (2) - 1 ? 1 : 2; // { dg-message "in .constexpr. expansion" }
+// { dg-error "call to consteval function" "" { target *-*-* } .-1 }
 }
diff --git a/gcc/testsuite/g++.dg/cpp2a/consteval36.C b/gcc/testsuite/g++.dg/cpp2a/consteval36.C
index 9c470e4b7d7..8e27f2e33c6 100644
--- a/gcc/testsuite/g++.dg/cpp2a/consteval36.C
+++ b/gcc/testsuite/g++.dg/cpp2a/consteval36.C
@@ -6,17 +6,17 @@ consteval int id (int i) { return i; }
 void
 g (int i)
 {
-  1 ? 1 : ((1 ? 1 : 1), id (i)); // { dg-error "'i' is not a constant expression" }
-  1 ? 1 : ((1 ? 1 : 1), id (i), 1); // { dg-error "'i' is not a constant expression" }
-  1 ? 1 : ((i ? 1 : 1), id (i), 1); // { dg-error "'i' is not a constant expression" }
-  1 ? 1 : ((1 ? i : 1), id (i), 1); // { dg-error "'i' is not a constant expression" }
-  1 ? 1 : ((1 ? 1 : i), id (i), 1); // { dg-error "'i' is not a constant expression" }
-  1 ? 1 : ((i ? -i : i), id (i), 1); // { dg-error "'i' is not a constant expression" }
-  1 ? 1 : ((1 ? 1 : id (i)), id (42), 1); // { dg-error "'i' is not a constant expression" }
-  1 ? 1 : ((1 ? 1 : id (42)), id (i)); // { dg-error "'i' is not a constant expression" }
-  1 ? 1 : ((1 ? 1 : id (42)), id (i), 1); // { dg-error "'i' is not a constant expression" }
-  id (i) ? 1 : ((1 ? 1 : 1), id (i)); // { dg-error "'i' is not a constant expression" }
-  1 ? 1 : ((1 ? 1 : id (i)), id (i)); // { dg-error "'i' is not a constant expression" }
-  1 ? id (i) : ((1 ? 1 : id (i)), id (i)); // { dg-error "'i' is not a constant expression" }
-  1 ? 1 : ((id (i) ? 1 : 1), id (i)); // { dg-error "'i' is not a constant expression" }
+  1 ? 1 : ((1 ? 1 : 1), id (i)); // { dg-error "call to consteval function|'i' is not a constant expression" }
+  1 ? 1 : ((1 ? 1 : 1), id (i), 1); // { dg-error "call to consteval function|'i' is not a constant expression" }
+  1 ? 1 : ((i ? 1 : 1), id (i), 1); // { dg-error "call to consteval function|'i' is not a constant expression" }
+  1 ? 1 : ((1 ? i : 1), id (i), 1); // { dg-error "call to consteval function|'i' is not a constant expression" }
+  1 ? 1 : ((1 ? 1 : i), id (i), 1); // { dg-error "call to consteval function|'i' is not a constant expression" }
+  1 ? 1 : ((i ? -i : i), id (i), 1); // { dg-error "call to consteval function|'i' is not a constant expression" }
+  1 ? 1 : ((1 ? 1 : id (i)), id (42), 1); // { dg-error "call to consteval function|'i' is not a constant expression" }
+  1 ? 1 : ((1 ? 1 : id (42)), id (i)); // { dg-error "call to consteval function|'i' is not a constant expression" }
+  1 ? 1 : ((1 ? 1 : id (42)), id (i), 1); // { dg-error "call to consteval function|'i' is not a constant expression" }
+  id (i) ? 1 : ((1 ? 1 : 1), id (i)); // { dg-error "call to consteval function|'i' is not a constant expression" }
+  1 ? 1 : ((1 ? 1 : id (i)), id (i)); // { dg-error "call to consteval function|'i' is not a constant expression" }
+  1 ? id (i) : ((1 ? 1 : id (i)), id (i)); // { dg-error "call to consteval function|'i' is not a constant expression" }
+  1 ? 1 : ((id (i) ? 1 : 1), id (i)); // { dg-error "call to consteval function|'i' is not a constant expression" }
 }
diff --git a/gcc/testsuite/g++.dg/cpp2a/consteval9.C b/gcc/testsuite/g++.dg/cpp2a/consteval9.C
index 051a3d4e355..ad882d51c9b 100644
--- a/gcc/testsuite/g++.dg/cpp2a/consteval9.C
+++ b/gcc/testsuite/g++.dg/cpp2a/consteval9.C
@@ -14,6 +14,7 @@ template <int N>
 void qux ()
 {
   int a = bar (N);	// { dg-message "in 'constexpr' expansion of 'bar\\(2\\)'" }
+// { dg-error "call to consteval function" "" { target *-*-* } .-1 }
 }
 
 // This function is not instantiated so NDR.
@@ -31,3 +32,4 @@ baz ()
 }
 
 int a = bar (2);	// { dg-message "in 'constexpr' expansion of 'bar\\(2\\)'" }
+// { dg-error "call to consteval function" "" { target *-*-* } .-1 }
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/gcc/testsuite/g++.dg/cpp2a/spaceship-synth9.C b/gcc/testsuite/g++.dg/cpp2a/spaceship-synth9.C
index 33b547d2b50..ecb46b016a6 100644
--- a/gcc/testsuite/g++.dg/cpp2a/spaceship-synth9.C
+++ b/gcc/testsuite/g++.dg/cpp2a/spaceship-synth9.C
@@ -22,6 +22,6 @@ struct Z: Y<int>
 int main()
 {
   X<char>() == X<char>();	// { dg-error "no match" }
-  X<int> x; x == x;		// { dg-error "x' is not usable in a constant expression" }
+  X<int> x; x == x;		// { dg-error "x' is not usable in a constant expression|call to consteval function" }
   Y<int>()  == Y<int>();	// { dg-warning "nodiscard" }
 }
diff --git a/libstdc++-v3/testsuite/18_support/comparisons/categories/zero_neg.cc b/libstdc++-v3/testsuite/18_support/comparisons/categories/zero_neg.cc
index 9d2115b3f4f..82f7cd54fba 100644
--- a/libstdc++-v3/testsuite/18_support/comparisons/categories/zero_neg.cc
+++ b/libstdc++-v3/testsuite/18_support/comparisons/categories/zero_neg.cc
@@ -52,3 +52,4 @@ test01()
 
 // { dg-prune-output "reinterpret_cast.* is not a constant expression" }
 // { dg-prune-output "cast from 'void.' is not allowed" }
+// { dg-prune-output "not a constant expression" }
diff --git a/libstdc++-v3/testsuite/std/format/string_neg.cc b/libstdc++-v3/testsuite/std/format/string_neg.cc
index 7a60ef8cf0e..69bcc736cff 100644
--- a/libstdc++-v3/testsuite/std/format/string_neg.cc
+++ b/libstdc++-v3/testsuite/std/format/string_neg.cc
@@ -2,5 +2,5 @@
 
 #include <format>
 
-auto s = std::format(" {9} ");
+auto s = std::format(" {9} "); // { dg-error "call to consteval function" }
 // { dg-error "invalid.arg.id" "" { target *-*-* } 0 }

base-commit: 91d7b036ebd650494f96828cfeacb6d90e7bd376
-- 
2.41.0


^ permalink raw reply	[flat|nested] 19+ messages in thread

* Re: [PATCH v4] c++: implement P2564, consteval needs to propagate up [PR107687]
  2023-11-06 22:34           ` [PATCH v4] " Marek Polacek
@ 2023-11-14  2:06             ` Jason Merrill
  2023-11-23 16:46               ` [PATCH v5] " Marek Polacek
  0 siblings, 1 reply; 19+ messages in thread
From: Jason Merrill @ 2023-11-14  2:06 UTC (permalink / raw)
  To: Marek Polacek; +Cc: GCC Patches

On 11/6/23 17:34, Marek Polacek wrote:
> On Fri, Nov 03, 2023 at 01:51:07PM -0400, Jason Merrill wrote:
>> On 11/2/23 11:28, Marek Polacek wrote:
>>> On Sat, Oct 14, 2023 at 12:56:11AM -0400, Jason Merrill wrote:
>>>> On 10/10/23 13:20, Marek Polacek wrote:
>>>>> I suppose some
>>>>> functions cannot possibly be promoted because they don't contain
>>>>> any CALL_EXPRs.  So we may be able to rule them out while doing
>>>>> cp_fold_r early.
>>>>
>>>> Yes.  Or, the only immediate-escalating functions referenced have already
>>>> been checked.
>>
>> It looks like you haven't pursued this yet?  One implementation thought:
> 
> Oops, I'd forgotten to address that.
> 
>> maybe_store_cfun... could stop skipping immediate_escalating_function_p
>> (current_function_decl), and after we're done folding if the current
>> function isn't in the hash_set we can go ahead and set
>> DECL_ESCALATION_CHECKED_P?
> 
> Clever, I see what you mean.  IOW, we store c_f_d iff the function contains
> an i-e expr.  If not, it can't possibly become consteval.  I've added that
> into cp_fold_function, and it seems to work well...
> 
> ...except it revealed a different problem: cp_fold_r -> cp_fold will, since
> https://gcc.gnu.org/pipermail/gcc-patches/2016-March/443993.html, remove
> UNARY_PLUS_EXPR, leading us into this problem:
> 
>    // stmt = +id(i)
>    cp_fold (...);
>    // stmt = id(i)
> 
> and the subsequent tree walk walks the CALL_EXPR's operands, so
> cp_fold_immediate_r will never see the CALL_EXPR, so we miss an i-e expr.
> 
> Perhaps a better solution than the kludge I added would be to only call
> cp_fold_immediate_r after cp_fold.  Or potentially before /and/ after if
> cp_fold changes the expression?

Or walk everything with cp_fold_immediate_r before walking again with 
cp_fold_r?

>>>> It also seems odd that the ADDR_EXPR case calls vec_safe_push
>>>> (deferred_escalating_exprs, while the CALL_EXPR case calls
>>>> maybe_store_cfun_for_late_checking, why the different handling?
>>>
>>> maybe_store_cfun_for_late_checking saves current_function_decl
>>> so that we can check:
>>>
>>> void g (int i) {
>>>     fn (i); // error if fn promotes to consteval
>>> }
>>
>> Yes, but why don't we want the same handling for ADDR_EXPR?
> 
> The handling can't be exactly the same due to global vars like
> 
>    auto p1 = &f5<int>;
> 
> ...but it's wrong to only save the ADDR_EXPR if it's enclosed in
> a function, because the ADDR_EXPR could be inside a consteval if
> block, in which case I think we're not supposed to error.  Tested
> in consteval-prop20.C.  Thanks,

And we don't need the !current_function_decl handling for CALL_EXPR?

The only significant difference I see between &f and f() for escalation 
is that the latter might be an immediate invocation.  Once we've 
determined that it's not, so we are in fact looking at an 
immediate-escalating expression, I'd expect the promotion handling to be 
identical.

> +  /* Whether cp_fold_immediate_r is looking for immediate-escalating
> +     expressions.  */

Isn't that always what it's doing?

The uses of ff_escalating in maybe_explain_promoted_consteval and 
maybe_escalate_decl_and_cfun seem to have different purposes that I'm 
having trouble following.

For the former, it seems to control returning the offending expression 
rather than error_mark_node.  Why don't we always do that?

For the latter, it seems to control recursion, which seems redundant 
with the recursion in that latter function itself.  And the use of the 
flag seems redundant with at_eof.

> +/* Remember that the current function declaration contains a call to
> +   a function that might be promoted to consteval later.  */
> +
> +static void
> +maybe_store_cfun_for_late_checking ()

This name could say more about escalation?  Maybe 
...for_escalation_checking?

Or, better, merge this with maybe_store_immediate_escalating_fn?

> +/* 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.
> +   EVALP is where we may store the result of cxx_constant_value so that we
> +   don't have to evaluate the same tree again in cp_fold_immediate_r.  */
> +
> +static void
> +maybe_escalate_decl_and_cfun (tree decl, tree call, tree *evalp)
> +{
> +  if (cp_unevaluated_operand)
> +    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 (unchecked_immediate_escalating_function_p (decl))
> +    {
> +      cp_fold_data data (ff_escalating, decl);
> +      cp_walk_tree (&DECL_SAVED_TREE (decl), cp_fold_immediate_r,
> +		    &data, nullptr);
> +      DECL_ESCALATION_CHECKED_P (decl) = true;

Why recurse both here and in cp_fold_immediate_r?

> +    }
> +
> +  /* 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 cp_fold_immediate_r,
> +	 because DECL could have already been consteval and we'd
> +	 never call cp_fold_immediate_r.  */
> +      && (*evalp = cxx_constant_value (call, tf_none),
> +	  *evalp == error_mark_node))

Why check this both here and immediately after the call in 
cp_fold_immediate_r?

> @@ -1041,65 +1217,129 @@ cp_fold_immediate_r (tree *stmt_p, int *walk_subtrees, void *data_)
>     /* The purpose of this is not to emit errors for mce_unknown.  */
>     const tsubst_flags_t complain = (data->flags & ff_mce_false
>   				   ? tf_error : tf_none);
> +  const tree_code code = TREE_CODE (stmt);
> +  tree decl;
> +  tree call = NULL_TREE;
>   
>     /* No need to look into types or unevaluated operands.
>        NB: This affects cp_fold_r as well.  */
> -  if (TYPE_P (stmt) || unevaluated_p (TREE_CODE (stmt)))
> +  if (TYPE_P (stmt) || unevaluated_p (code))
>       {
>         *walk_subtrees = 0;
>         return NULL_TREE;
>       }
>   
> -  switch (TREE_CODE (stmt))
> +  switch (code)
>       {
>       case PTRMEM_CST:
> -      if (TREE_CODE (PTRMEM_CST_MEMBER (stmt)) == FUNCTION_DECL
> -	  && DECL_IMMEDIATE_FUNCTION_P (PTRMEM_CST_MEMBER (stmt)))
> -	{
> -	  if (!data->pset.add (stmt) && (complain & tf_error))
> -	    {
> -	      error_at (PTRMEM_CST_LOCATION (stmt),
> -			"taking address of an immediate function %qD",
> -			PTRMEM_CST_MEMBER (stmt));
> -	      *stmt_p = build_zero_cst (TREE_TYPE (stmt));
> -	    }
> -	  return error_mark_node;
> -	}
> -      break;
> +    case ADDR_EXPR:
> +      {
> +	decl = (code == PTRMEM_CST ? PTRMEM_CST_MEMBER (stmt)
> +		: TREE_OPERAND (stmt, 0));
> +	if (TREE_CODE (decl) != FUNCTION_DECL)
> +	  break;
> +	if (code == ADDR_EXPR && ADDR_EXPR_DENOTES_CALL_P (stmt))
> +	  break;
> +	if (data->flags & ff_escalating)
> +	  goto escalate;
> +	if (immediate_invocation_p (decl))
> +	  {
> +	    if (maybe_promote_function_to_consteval (current_function_decl))
> +	      break;
> +	    if (complain & tf_error)
> +	      {
> +		if (!data->pset.add (stmt))
> +		  {
> +		    taking_address_of_imm_fn_error (stmt, decl);
> +		    *stmt_p = build_zero_cst (TREE_TYPE (stmt));
> +		  }
> +		/* If we're giving hard errors, continue the walk rather than
> +		   bailing out after the first error.  */
> +		break;
> +	      }
> +	    return error_mark_node;
> +	  }
> +	/* Not consteval yet, but could become one, in which case it's invalid
> +	   to take its address.  */
> +	else if (unchecked_immediate_escalating_function_p (decl))
> +	  {
> +	    /* auto p = &f<int>; in the global scope won't be ensconced in
> +	       a function we could store for later at this point.  */
> +	    if (!current_function_decl)
> +	      remember_escalating_expr (stmt);
> +	    else
> +	      maybe_store_cfun_for_late_checking ();
> +	  }
> +	break;
> +      }
>   
>       /* Expand immediate invocations.  */
>       case CALL_EXPR:
>       case AGGR_INIT_EXPR:
>         if (tree fn = cp_get_callee (stmt))
>   	if (TREE_CODE (fn) != ADDR_EXPR || ADDR_EXPR_DENOTES_CALL_P (fn))
> -	  if (tree fndecl = cp_get_fndecl_from_callee (fn, /*fold*/false))
> -	    if (DECL_IMMEDIATE_FUNCTION_P (fndecl))
> -	      {
> -		stmt = cxx_constant_value (stmt, complain);
> -		if (stmt == error_mark_node)
> -		  {
> -		    if (complain & tf_error)
> -		      *stmt_p = error_mark_node;
> -		    return error_mark_node;
> -		  }
> -		*stmt_p = stmt;
> -	      }
> -      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))
> -	{
> -	  if (complain & tf_error)
> +	  if ((decl = cp_get_fndecl_from_callee (fn, /*fold*/false)))
>   	    {
> -	      error_at (EXPR_LOCATION (stmt),
> -			"taking address of an immediate function %qD",
> -			TREE_OPERAND (stmt, 0));
> -	      *stmt_p = build_zero_cst (TREE_TYPE (stmt));
> +	      if (data->flags & ff_escalating)
> +		{
> +		  call = stmt;
> +		  goto escalate;
> +		}
> +
> +	      tree eval = NULL_TREE;
> +	      /* Escalate once all templates have been instantiated.  */
> +	      if (at_eof > 1)
> +		maybe_escalate_decl_and_cfun (decl, stmt, &eval);
> +
> +	      /* [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 STMT does not need to produce
> +		 a constant expression.  */
> +	      if (immediate_invocation_p (decl))
> +		{
> +		  tree e = eval ? eval : cxx_constant_value (stmt, tf_none);
> +		  if (e == error_mark_node)
> +		    {
> +		      if (maybe_promote_function_to_consteval
> +			  (current_function_decl))
> +			break;
> +		      if (complain & tf_error)
> +			{
> +			  auto_diagnostic_group d;
> +			  location_t loc = cp_expr_loc_or_input_loc (stmt);
> +			  error_at (loc, "call to consteval function %qE is "
> +				    "not a constant expression", stmt);
> +			  /* Explain why it's not a constant expression.  */
> +			  *stmt_p = cxx_constant_value (stmt, complain);
> +			  maybe_explain_promoted_consteval (loc, decl);
> +			  /* Don't return error_mark_node, it would stop our
> +			     tree walk.  */
> +			  break;
> +			}
> +		      return error_mark_node;
> +		    }
> +		  /* We've evaluated the consteval function call.  */
> +		  *stmt_p = e;
> +		}
> +	      /* We've encountered a function call that may turn out to be
> +		 consteval later.  Store its caller so that we can ensure
> +		 that the call is a constant expression.  */
> +	      else if (unchecked_immediate_escalating_function_p (decl))
> +		{
> +		  /* Make sure we're not inserting new elements while walking
> +		     the deferred_escalating_exprs hash table; if we are, it's
> +		     likely that a function wasn't properly marked checked for
> +		     i-e expressions.  */
> +		  gcc_checking_assert (at_eof <= 1);
> +		  maybe_store_cfun_for_late_checking ();
> +		}
>   	    }
> -	  return error_mark_node;
> -	}
>         break;
>   
>       default:
> @@ -1107,11 +1347,35 @@ cp_fold_immediate_r (tree *stmt_p, int *walk_subtrees, void *data_)
>       }
>   
>     return NULL_TREE;
> +
> +escalate:
> +  /* Not consteval yet, but could be.  Have to look deeper.  */
> +  if (unchecked_immediate_escalating_function_p (decl))
> +    {
> +      /* Set before the actual walk to avoid endless recursion.  */
> +      DECL_ESCALATION_CHECKED_P (decl) = true;
> +      cp_fold_data d (ff_escalating, data->caller ? decl : NULL_TREE);

It seems like the "caller" field is in place of setting 
current_function_decl to decl while walking its trees.  Some other 
places instead override current_function_decl and call 
cp_fold_immediate.  Can we unify on the latter pattern, and factor it 
into a separate function?

> +      cp_walk_tree (&DECL_SAVED_TREE (decl), cp_fold_immediate_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.  */
> +      if (data->caller)
> +	promote_function_to_consteval (data->caller);
> +      *walk_subtrees = 0;
> +      return stmt;
> +    }

Why different promotion code depending on whether we're recursing?

> +/* We've stashed immediate-escalating functions.  Now see if they indeed
> +   ought to be promoted to consteval.  */
> +
> +void
> +process_pending_immediate_escalating_fns ()
> +{
> +  /* This will be null for -fno-immediate-escalation.  */
> +  if (!deferred_escalating_exprs)
> +    return;
> +
> +  for (auto e : *deferred_escalating_exprs)
> +    if (TREE_CODE (e) == FUNCTION_DECL)
> +      {
> +	if (!DECL_ESCALATION_CHECKED_P (e))
> +	  {
> +	    temp_override<tree> cfd (current_function_decl, e);
> +	    cp_fold_immediate (&DECL_SAVED_TREE (e), mce_false);
> +	  }
> +	if (DECL_IMMEDIATE_FUNCTION_P (e))
> +	  deferred_escalating_exprs->remove (e);
> +      }
> +}
> +
> +/* We've escalated every function that could have been promoted to
> +   consteval.  Check that we are not taking the address of a consteval
> +   function.  */
> +
> +void
> +check_immediate_escalating_refs ()
> +{
> +  /* This will be null for -fno-immediate-escalation.  */
> +  if (!deferred_escalating_exprs)
> +    return;
> +
> +  for (auto ref : *deferred_escalating_exprs)
> +    {
> +      if (TREE_CODE (ref) == FUNCTION_DECL)
> +	/* We saw a function call to an immediate-escalating function in
> +	   the body of REF.  Check that it's a constant if it was promoted
> +	   to consteval.  */
> +	{
> +	  temp_override<tree> cfd (current_function_decl, ref);
> +	  cp_fold_immediate (&DECL_SAVED_TREE (ref), mce_false);

Shouldn't we skip functions here since we just processed them in the 
function above?

Jason


^ permalink raw reply	[flat|nested] 19+ messages in thread

* [PATCH v5] c++: implement P2564, consteval needs to propagate up [PR107687]
  2023-11-14  2:06             ` Jason Merrill
@ 2023-11-23 16:46               ` Marek Polacek
  2023-11-30 23:34                 ` Jason Merrill
  0 siblings, 1 reply; 19+ messages in thread
From: Marek Polacek @ 2023-11-23 16:46 UTC (permalink / raw)
  To: Jason Merrill; +Cc: GCC Patches

On Mon, Nov 13, 2023 at 09:06:09PM -0500, Jason Merrill wrote:
> On 11/6/23 17:34, Marek Polacek wrote:
> > On Fri, Nov 03, 2023 at 01:51:07PM -0400, Jason Merrill wrote:
> > > On 11/2/23 11:28, Marek Polacek wrote:
> > > > On Sat, Oct 14, 2023 at 12:56:11AM -0400, Jason Merrill wrote:
> > > > > On 10/10/23 13:20, Marek Polacek wrote:
> > > > > > I suppose some
> > > > > > functions cannot possibly be promoted because they don't contain
> > > > > > any CALL_EXPRs.  So we may be able to rule them out while doing
> > > > > > cp_fold_r early.
> > > > > 
> > > > > Yes.  Or, the only immediate-escalating functions referenced have already
> > > > > been checked.
> > > 
> > > It looks like you haven't pursued this yet?  One implementation thought:
> > 
> > Oops, I'd forgotten to address that.
> > 
> > > maybe_store_cfun... could stop skipping immediate_escalating_function_p
> > > (current_function_decl), and after we're done folding if the current
> > > function isn't in the hash_set we can go ahead and set
> > > DECL_ESCALATION_CHECKED_P?
> > 
> > Clever, I see what you mean.  IOW, we store c_f_d iff the function contains
> > an i-e expr.  If not, it can't possibly become consteval.  I've added that
> > into cp_fold_function, and it seems to work well...
> > 
> > ...except it revealed a different problem: cp_fold_r -> cp_fold will, since
> > https://gcc.gnu.org/pipermail/gcc-patches/2016-March/443993.html, remove
> > UNARY_PLUS_EXPR, leading us into this problem:
> > 
> >    // stmt = +id(i)
> >    cp_fold (...);
> >    // stmt = id(i)
> > 
> > and the subsequent tree walk walks the CALL_EXPR's operands, so
> > cp_fold_immediate_r will never see the CALL_EXPR, so we miss an i-e expr.
> > 
> > Perhaps a better solution than the kludge I added would be to only call
> > cp_fold_immediate_r after cp_fold.  Or potentially before /and/ after if
> > cp_fold changes the expression?
> 
> Or walk everything with cp_fold_immediate_r before walking again with
> cp_fold_r?

Maybe, but I might run into complexity issues again unless I add a new
pset into cp_fold_data.  I did something else: only re-run
cp_fold_immediate_r if cp_fold turned an expression into a CALL_EXPR.
 
> > > > > It also seems odd that the ADDR_EXPR case calls vec_safe_push
> > > > > (deferred_escalating_exprs, while the CALL_EXPR case calls
> > > > > maybe_store_cfun_for_late_checking, why the different handling?
> > > > 
> > > > maybe_store_cfun_for_late_checking saves current_function_decl
> > > > so that we can check:
> > > > 
> > > > void g (int i) {
> > > >     fn (i); // error if fn promotes to consteval
> > > > }
> > > 
> > > Yes, but why don't we want the same handling for ADDR_EXPR?
> > 
> > The handling can't be exactly the same due to global vars like
> > 
> >    auto p1 = &f5<int>;
> > 
> > ...but it's wrong to only save the ADDR_EXPR if it's enclosed in
> > a function, because the ADDR_EXPR could be inside a consteval if
> > block, in which case I think we're not supposed to error.  Tested
> > in consteval-prop20.C.  Thanks,
> 
> And we don't need the !current_function_decl handling for CALL_EXPR?

I don't think so: we should see the call when cp_fold_function gets to
__static_i_and_d.  I added a comment.
 
> The only significant difference I see between &f and f() for escalation is
> that the latter might be an immediate invocation.  Once we've determined
> that it's not, so we are in fact looking at an immediate-escalating
> expression, I'd expect the promotion handling to be identical.

I'm not certain if I understand the immediate invocation remark but in
v5 I've tried to unify both as much as possible.

> > +  /* Whether cp_fold_immediate_r is looking for immediate-escalating
> > +     expressions.  */
> 
> Isn't that always what it's doing?
> 
> The uses of ff_escalating in maybe_explain_promoted_consteval and
> maybe_escalate_decl_and_cfun seem to have different purposes that I'm having
> trouble following.
> 
> For the former, it seems to control returning the offending expression
> rather than error_mark_node.  Why don't we always do that?
> 
> For the latter, it seems to control recursion, which seems redundant with
> the recursion in that latter function itself.  And the use of the flag seems
> redundant with at_eof.
 
You're absolutely correct, the code was a knotty mess.  In v5, I tried to
untangle it to make it something more straightforward.

I still need a new ff_ flag to signal that we can return immediately
after seeing an i-e expr.

> > +/* Remember that the current function declaration contains a call to
> > +   a function that might be promoted to consteval later.  */
> > +
> > +static void
> > +maybe_store_cfun_for_late_checking ()
> 
> This name could say more about escalation?  Maybe
> ...for_escalation_checking?
> 
> Or, better, merge this with maybe_store_immediate_escalating_fn?

I got rid of the former function.
 
> > +/* 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.
> > +   EVALP is where we may store the result of cxx_constant_value so that we
> > +   don't have to evaluate the same tree again in cp_fold_immediate_r.  */
> > +
> > +static void
> > +maybe_escalate_decl_and_cfun (tree decl, tree call, tree *evalp)
> > +{
> > +  if (cp_unevaluated_operand)
> > +    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 (unchecked_immediate_escalating_function_p (decl))
> > +    {
> > +      cp_fold_data data (ff_escalating, decl);
> > +      cp_walk_tree (&DECL_SAVED_TREE (decl), cp_fold_immediate_r,
> > +		    &data, nullptr);
> > +      DECL_ESCALATION_CHECKED_P (decl) = true;
> 
> Why recurse both here and in cp_fold_immediate_r?

Gone now.
 
> > +    }
> > +
> > +  /* 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 cp_fold_immediate_r,
> > +	 because DECL could have already been consteval and we'd
> > +	 never call cp_fold_immediate_r.  */
> > +      && (*evalp = cxx_constant_value (call, tf_none),
> > +	  *evalp == error_mark_node))
> 
> Why check this both here and immediately after the call in
> cp_fold_immediate_r?

This should no longer happen.
 
> > +escalate:
> > +  /* Not consteval yet, but could be.  Have to look deeper.  */
> > +  if (unchecked_immediate_escalating_function_p (decl))
> > +    {
> > +      /* Set before the actual walk to avoid endless recursion.  */
> > +      DECL_ESCALATION_CHECKED_P (decl) = true;
> > +      cp_fold_data d (ff_escalating, data->caller ? decl : NULL_TREE);
> 
> It seems like the "caller" field is in place of setting
> current_function_decl to decl while walking its trees.  Some other places
> instead override current_function_decl and call cp_fold_immediate.  Can we
> unify on the latter pattern, and factor it into a separate function?

Done -- see find_escalating_expr.
 
> > +      cp_walk_tree (&DECL_SAVED_TREE (decl), cp_fold_immediate_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.  */
> > +      if (data->caller)
> > +	promote_function_to_consteval (data->caller);
> > +      *walk_subtrees = 0;
> > +      return stmt;
> > +    }
> 
> Why different promotion code depending on whether we're recursing?

Gone now, and hopefully less unwieldy.
 
> > +/* We've stashed immediate-escalating functions.  Now see if they indeed
> > +   ought to be promoted to consteval.  */
> > +
> > +void
> > +process_pending_immediate_escalating_fns ()
> > +{
> > +  /* This will be null for -fno-immediate-escalation.  */
> > +  if (!deferred_escalating_exprs)
> > +    return;
> > +
> > +  for (auto e : *deferred_escalating_exprs)
> > +    if (TREE_CODE (e) == FUNCTION_DECL)
> > +      {
> > +	if (!DECL_ESCALATION_CHECKED_P (e))
> > +	  {
> > +	    temp_override<tree> cfd (current_function_decl, e);
> > +	    cp_fold_immediate (&DECL_SAVED_TREE (e), mce_false);
> > +	  }
> > +	if (DECL_IMMEDIATE_FUNCTION_P (e))
> > +	  deferred_escalating_exprs->remove (e);
> > +      }
> > +}
> > +
> > +/* We've escalated every function that could have been promoted to
> > +   consteval.  Check that we are not taking the address of a consteval
> > +   function.  */
> > +
> > +void
> > +check_immediate_escalating_refs ()
> > +{
> > +  /* This will be null for -fno-immediate-escalation.  */
> > +  if (!deferred_escalating_exprs)
> > +    return;
> > +
> > +  for (auto ref : *deferred_escalating_exprs)
> > +    {
> > +      if (TREE_CODE (ref) == FUNCTION_DECL)
> > +	/* We saw a function call to an immediate-escalating function in
> > +	   the body of REF.  Check that it's a constant if it was promoted
> > +	   to consteval.  */
> > +	{
> > +	  temp_override<tree> cfd (current_function_decl, ref);
> > +	  cp_fold_immediate (&DECL_SAVED_TREE (ref), mce_false);
> 
> Shouldn't we skip functions here since we just processed them in the
> function above?

I think so.  Done.

Thanks a lot.

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:

w.C:11:11: error: call to consteval function 'f<int>(i)' is not a constant expression
   11 |         f (i);
      |         ~~^~~
w.C:11:11: error: 'i' is not a constant expression
w.C:6:22: 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); // id causes f<int> to be promoted to consteval
      |                    ~~^~~

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.

In v1, I addressed the delayed instantiation problem by instantiating
trees early, so that we can escalate functions right away.  That caused
a number of problems, and in certain cases, like consteval-prop3.C, it
can't work, because we need to wait till EOF to see the definition of
the function anyway.  Overeager instantiation tends to cause diagnostic
problems too.

In v2, I attempted to move the escalation to the gimplifier, at which
point all templates have been instantiated.  That attempt flopped,
however, because once we've gimplified a function, its body is discarded
and as a consequence, you can no longer evaluate a call to that function
which is required for escalating, which needs to decide if a call is
a constant expression or not.

Therefore, we have to perform the escalation before gimplifying, but
after instantiate_pending_templates.  That's not easy because we have
no way to walk all the trees.  In the v2 patch, I use two vectors: one
to store function decls that may become consteval, and another to
remember references to immediate-escalating functions.  Unfortunately
the latter must also stash functions that call immediate-escalating
functions.  Consider:

  int g(int i)
  {
    f<int>(i); // f is immediate-escalating
  }

where g itself is not immediate-escalating, but we have to make sure
that if f gets promoted to consteval, we give an error.

A new option, -fno-immediate-escalation, is provided to suppress
escalating functions.

v2 also adds a new flag, DECL_ESCALATION_CHECKED_P, so that we don't
escalate a function multiple times, and so that we can distinguish between
explicitly consteval functions and functions that have been promoted
to consteval.

In v3, I removed one of the new vectors and changed the other one
to a hash set.  This version also contains numerous cleanups.

v4 merges find_escalating_expr_r into cp_fold_immediate_r.  It also
adds a new optimization in cp_fold_function.

v5 greatly simplifies the code.

	PR c++/107687
	PR c++/110997

gcc/c-family/ChangeLog:

	* c-cppbuiltin.cc (c_cpp_builtins): Update __cpp_consteval.
	* c-opts.cc (c_common_post_options): Pre-C++20, unset
	flag_immediate_escalation.
	* c.opt (fimmediate-escalation): New option.

gcc/cp/ChangeLog:

	* call.cc (in_immediate_context): No longer static.
	* constexpr.cc (cxx_eval_call_expression): Adjust assert.
	* cp-gimplify.cc (deferred_escalating_exprs): New vec.
	(remember_escalating_expr): New.
	(enum fold_flags): Rename ff_fold_immediate to ff_find_escalating_expr.
	(immediate_escalating_function_p): New.
	(unchecked_immediate_escalating_function_p): New.
	(promote_function_to_consteval): New.
	(maybe_promote_function_to_consteval): New.
	(cp_fold_immediate): Move above.  Return true if any errors were
	emitted.
	(find_escalating_expr): New.
	(maybe_explain_promoted_consteval): New.
	(cp_gimplify_expr) <case CALL_EXPR>: Assert we've handled all
	immediate invocations.
	(taking_address_of_imm_fn_error): New.
	(cp_fold_immediate_r): Merge ADDR_EXPR and PTRMEM_CST cases.  Implement
	P2564 - promoting functions to consteval.
	<case CALL_EXPR>: Implement P2564 - promoting functions to consteval.
	(cp_fold_r): If an expression turns into a CALL_EXPR after cp_fold,
	call cp_fold_immediate_r on the CALL_EXPR.
	(cp_fold_function): Set DECL_ESCALATION_CHECKED_P if
	deferred_escalating_exprs does not contain current_function_decl.
	(maybe_store_immediate_escalating_fn): New.
	(process_pending_immediate_escalating_fns): New.
	(check_immediate_escalating_refs): New.
	* cp-tree.h (struct lang_decl_fn): Add escalated_p bit-field.
	(DECL_ESCALATION_CHECKED_P): New.
	(immediate_invocation_p): Declare.
	(check_immediate_escalating_refs): Likewise.
	(maybe_store_immediate_escalating_fn): Likewise.
	(process_pending_immediate_escalating_fns): Likewise.
	* decl.cc (finish_function): Call maybe_store_immediate_escalating_fn.
	* decl2.cc (c_parse_final_cleanups): Set at_eof to 2 after all
	templates have been instantiated; and to 3 at the end of the function.
	Call process_pending_immediate_escalating_fns and
	check_immediate_escalating_refs.
	* error.cc (dump_template_bindings): Check at_eof against an updated
	value.
	* module.cc (trees_out::lang_decl_bools): Stream escalated_p.
	(trees_in::lang_decl_bools): Likewise.
	* pt.cc (push_tinst_level_loc): Set at_eof to 3, not 2.
	* typeck.cc (cp_build_addr_expr_1): Don't check
	DECL_IMMEDIATE_FUNCTION_P.

gcc/ChangeLog:

	* doc/invoke.texi: Document -fno-immediate-escalation.

libstdc++-v3/ChangeLog:

	* testsuite/18_support/comparisons/categories/zero_neg.cc: Add
	dg-prune-output.
	* testsuite/std/format/string_neg.cc: Add dg-error.

gcc/testsuite/ChangeLog:

	* 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/consteval-memfn1.C: Add dg-error.
	* g++.dg/cpp2a/consteval11.C: Likewise.
	* g++.dg/cpp2a/consteval3.C: Adjust dg-error.
	* g++.dg/cpp2a/consteval34.C: Add dg-error.
	* g++.dg/cpp2a/consteval9.C: Likewise.
	* g++.dg/cpp2a/feat-cxx2a.C: Adjust expected value of __cpp_consteval.
	* g++.dg/cpp2a/spaceship-synth9.C: Adjust dg-error.
	* 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-prop18.C: New test.
	* g++.dg/cpp2a/consteval-prop19.C: New test.
	* g++.dg/cpp2a/consteval-prop20.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/c-family/c-opts.cc                        |   5 +
 gcc/c-family/c.opt                            |   4 +
 gcc/cp/call.cc                                |   2 +-
 gcc/cp/constexpr.cc                           |   4 +-
 gcc/cp/cp-gimplify.cc                         | 409 +++++++++++++++---
 gcc/cp/cp-tree.h                              |  21 +-
 gcc/cp/decl.cc                                |   5 +-
 gcc/cp/decl2.cc                               |  20 +-
 gcc/cp/error.cc                               |   2 +-
 gcc/cp/module.cc                              |   4 +
 gcc/cp/pt.cc                                  |   2 +-
 gcc/cp/typeck.cc                              |   6 +-
 gcc/doc/invoke.texi                           |  34 ++
 gcc/testsuite/g++.dg/cpp23/consteval-if10.C   |   7 +-
 gcc/testsuite/g++.dg/cpp23/consteval-if2.C    |  14 +-
 gcc/testsuite/g++.dg/cpp23/feat-cxx2b.C       |   4 +-
 gcc/testsuite/g++.dg/cpp26/feat-cxx26.C       |   4 +-
 gcc/testsuite/g++.dg/cpp2a/consteval-memfn1.C |   3 +
 gcc/testsuite/g++.dg/cpp2a/consteval-prop1.C  | 169 ++++++++
 gcc/testsuite/g++.dg/cpp2a/consteval-prop10.C |  41 ++
 gcc/testsuite/g++.dg/cpp2a/consteval-prop11.C |  49 +++
 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 |  78 ++++
 gcc/testsuite/g++.dg/cpp2a/consteval-prop15.C | 107 +++++
 gcc/testsuite/g++.dg/cpp2a/consteval-prop16.C |  73 ++++
 gcc/testsuite/g++.dg/cpp2a/consteval-prop17.C |  17 +
 gcc/testsuite/g++.dg/cpp2a/consteval-prop18.C |  20 +
 gcc/testsuite/g++.dg/cpp2a/consteval-prop19.C |   7 +
 gcc/testsuite/g++.dg/cpp2a/consteval-prop2.C  |  90 ++++
 gcc/testsuite/g++.dg/cpp2a/consteval-prop20.C |  21 +
 gcc/testsuite/g++.dg/cpp2a/consteval-prop3.C  |  27 ++
 gcc/testsuite/g++.dg/cpp2a/consteval-prop4.C  |  30 ++
 gcc/testsuite/g++.dg/cpp2a/consteval-prop5.C  |  27 ++
 gcc/testsuite/g++.dg/cpp2a/consteval-prop6.C  |  59 +++
 gcc/testsuite/g++.dg/cpp2a/consteval-prop7.C  |  76 ++++
 gcc/testsuite/g++.dg/cpp2a/consteval-prop8.C  |  82 ++++
 gcc/testsuite/g++.dg/cpp2a/consteval-prop9.C  |  67 +++
 gcc/testsuite/g++.dg/cpp2a/consteval11.C      |  18 +
 gcc/testsuite/g++.dg/cpp2a/consteval3.C       |   4 +-
 gcc/testsuite/g++.dg/cpp2a/consteval34.C      |   8 +
 gcc/testsuite/g++.dg/cpp2a/consteval36.C      |  26 +-
 gcc/testsuite/g++.dg/cpp2a/consteval9.C       |   2 +
 gcc/testsuite/g++.dg/cpp2a/feat-cxx2a.C       |   4 +-
 gcc/testsuite/g++.dg/cpp2a/spaceship-synth9.C |   2 +-
 .../comparisons/categories/zero_neg.cc        |   1 +
 .../testsuite/std/format/string_neg.cc        |   2 +-
 48 files changed, 1596 insertions(+), 116 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-prop18.C
 create mode 100644 gcc/testsuite/g++.dg/cpp2a/consteval-prop19.C
 create mode 100644 gcc/testsuite/g++.dg/cpp2a/consteval-prop2.C
 create mode 100644 gcc/testsuite/g++.dg/cpp2a/consteval-prop20.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

diff --git a/gcc/c-family/c-cppbuiltin.cc b/gcc/c-family/c-cppbuiltin.cc
index 56c4d637186..74c0b8de2ff 100644
--- a/gcc/c-family/c-cppbuiltin.cc
+++ b/gcc/c-family/c-cppbuiltin.cc
@@ -1059,7 +1059,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/c-family/c-opts.cc b/gcc/c-family/c-opts.cc
index 10403c03bd6..d68a4548c4b 100644
--- a/gcc/c-family/c-opts.cc
+++ b/gcc/c-family/c-opts.cc
@@ -1138,6 +1138,11 @@ c_common_post_options (const char **pfilename)
   if (cxx_dialect >= cxx20 || flag_concepts_ts)
     flag_concepts = 1;
 
+  /* -fimmediate-escalation has no effect when immediate functions are not
+     supported.  */
+  if (flag_immediate_escalation && cxx_dialect < cxx20)
+    flag_immediate_escalation = 0;
+
   if (num_in_fnames > 1)
     error ("too many filenames given; type %<%s %s%> for usage",
 	   progname, "--help");
diff --git a/gcc/c-family/c.opt b/gcc/c-family/c.opt
index 3848f378de1..c0c323a5f17 100644
--- a/gcc/c-family/c.opt
+++ b/gcc/c-family/c.opt
@@ -1890,6 +1890,10 @@ fhuge-objects
 C++ ObjC++ WarnRemoved
 No longer supported.
 
+fimmediate-escalation
+C++ ObjC++ Var(flag_immediate_escalation) Init(1)
+Implement P2564 for consteval propagation.
+
 fimplement-inlines
 C++ ObjC++ Var(flag_implement_inlines) Init(1)
 Export functions even if they can be inlined.
diff --git a/gcc/cp/call.cc b/gcc/cp/call.cc
index 81b104f4b40..26aa19a2415 100644
--- a/gcc/cp/call.cc
+++ b/gcc/cp/call.cc
@@ -9744,7 +9744,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
diff --git a/gcc/cp/constexpr.cc b/gcc/cp/constexpr.cc
index 344107d494b..078dc875c14 100644
--- a/gcc/cp/constexpr.cc
+++ b/gcc/cp/constexpr.cc
@@ -3128,11 +3128,11 @@ cxx_eval_call_expression (const constexpr_ctx *ctx, tree t,
 	/* OK */;
       else if (!DECL_SAVED_TREE (fun))
 	{
-	  /* When at_eof >= 2, cgraph has started throwing away
+	  /* When at_eof >= 3, cgraph has started throwing away
 	     DECL_SAVED_TREE, so fail quietly.  FIXME we get here because of
 	     late code generation for VEC_INIT_EXPR, which needs to be
 	     completely reconsidered.  */
-	  gcc_assert (at_eof >= 2 && ctx->quiet);
+	  gcc_assert (at_eof >= 3 && ctx->quiet);
 	  *non_constant_p = true;
 	}
       else if (tree copy = get_fundef_copy (new_call.fundef))
diff --git a/gcc/cp/cp-gimplify.cc b/gcc/cp/cp-gimplify.cc
index 795c811471d..97fa317ea32 100644
--- a/gcc/cp/cp-gimplify.cc
+++ b/gcc/cp/cp-gimplify.cc
@@ -43,6 +43,21 @@ along with GCC; see the file COPYING3.  If not see
 #include "omp-general.h"
 #include "opts.h"
 
+/* Keep track of forward references to immediate-escalating functions in
+   case they become consteval.  This vector contains ADDR_EXPRs and
+   PTRMEM_CSTs; it also stores FUNCTION_DECLs that had an escalating
+   function call in them, to check that they can be evaluated to a constant,
+   and immediate-escalating functions that may become consteval.  */
+static GTY(()) hash_set<tree> *deferred_escalating_exprs;
+
+static void
+remember_escalating_expr (tree t)
+{
+  if (!deferred_escalating_exprs)
+    deferred_escalating_exprs = hash_set<tree>::create_ggc (37);
+  deferred_escalating_exprs->add (t);
+}
+
 /* Flags for cp_fold and cp_fold_r.  */
 
 enum fold_flags {
@@ -53,8 +68,9 @@ enum fold_flags {
      definitely not in a manifestly constant-evaluated
      context.  */
   ff_mce_false = 1 << 1,
-  /* Whether we're being called from cp_fold_immediate.  */
-  ff_fold_immediate = 1 << 2,
+  /* Whether we are looking for an immediate-escalating expression and
+     can return early.  */
+  ff_find_escalating_expr = 1 << 2,
 };
 
 using fold_flags_t = int;
@@ -72,6 +88,7 @@ static tree cp_genericize_r (tree *, int *, void *);
 static tree cp_fold_r (tree *, int *, void *);
 static void cp_genericize_tree (tree*, bool);
 static tree cp_fold (tree, fold_flags_t);
+static tree cp_fold_immediate_r (tree *, int *, void *);
 
 /* Genericize a TRY_BLOCK.  */
 
@@ -428,6 +445,129 @@ lvalue_has_side_effects (tree e)
     return TREE_SIDE_EFFECTS (e);
 }
 
+/* Return true if FN is an immediate-escalating function.  */
+
+static bool
+immediate_escalating_function_p (tree fn)
+{
+  if (!fn || !flag_immediate_escalation)
+    return false;
+
+  gcc_checking_assert (TREE_CODE (fn) == FUNCTION_DECL);
+
+  if (DECL_IMMEDIATE_FUNCTION_P (fn))
+    return false;
+
+  /* An immediate-escalating function is
+      -- the call operator of a lambda that is not declared with the consteval
+	 specifier  */
+  if (LAMBDA_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))
+    return true;
+  /* -- a function that results from the instantiation of a templated entity
+	defined with the constexpr specifier.  */
+  return is_instantiation_of_constexpr (fn);
+}
+
+/* Return true if FN is an immediate-escalating function that has not been
+   checked for escalating expressions..  */
+
+static bool
+unchecked_immediate_escalating_function_p (tree fn)
+{
+  return (immediate_escalating_function_p (fn)
+	  && !DECL_ESCALATION_CHECKED_P (fn));
+}
+
+/* Promote FN to an immediate function, including its clones.  */
+
+static void
+promote_function_to_consteval (tree fn)
+{
+  SET_DECL_IMMEDIATE_FUNCTION_P (fn);
+  DECL_ESCALATION_CHECKED_P (fn) = true;
+  tree clone;
+  FOR_EACH_CLONE (clone, fn)
+    {
+      SET_DECL_IMMEDIATE_FUNCTION_P (clone);
+      DECL_ESCALATION_CHECKED_P (clone) = true;
+    }
+}
+
+/* Promote FN to an immediate function, including its clones, if it is
+   an immediate-escalating function.  Return true if we did promote;
+   false otherwise.  */
+
+static bool
+maybe_promote_function_to_consteval (tree fn)
+{
+  if (unchecked_immediate_escalating_function_p (fn))
+    {
+      promote_function_to_consteval (fn);
+      return true;
+    }
+
+  return false;
+}
+
+/* A wrapper around cp_fold_immediate_r.  Return true if we found
+   a non-constant immediate function, or taking the address of an
+   immediate function.  */
+
+bool
+cp_fold_immediate (tree *tp, mce_value manifestly_const_eval,
+		   tree decl /*= current_function_decl*/)
+{
+  if (cxx_dialect <= cxx17)
+    return false;
+
+  temp_override<tree> cfd (current_function_decl, decl);
+
+  fold_flags_t flags = ff_none;
+  if (manifestly_const_eval == mce_false)
+    flags |= ff_mce_false;
+
+  cp_fold_data data (flags);
+  int save_errorcount = errorcount;
+  tree r = cp_walk_tree_without_duplicates (tp, cp_fold_immediate_r, &data);
+  return r != NULL_TREE || errorcount > save_errorcount;
+}
+
+/* Look for an immediate-escalating expression in TP and perhaps promote
+   current_function_decl to consteval.  */
+
+static tree
+find_escalating_expr (tree *tp, tree decl)
+{
+  temp_override<tree> cfd (current_function_decl, decl);
+  cp_fold_data data (ff_find_escalating_expr);
+  return cp_walk_tree (tp, cp_fold_immediate_r, &data, nullptr);
+}
+
+/* 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)
+{
+  if (DECL_ESCALATION_CHECKED_P (fn))
+    {
+      /* See if we can figure out what made the function consteval.  */
+      tree x = find_escalating_expr (&DECL_SAVED_TREE (fn), NULL_TREE);
+      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.  */
 
@@ -746,7 +886,9 @@ cp_gimplify_expr (tree *expr_p, gimple_seq *pre_p, gimple_seq *post_p)
       if (ret != GS_ERROR)
 	{
 	  tree decl = cp_get_callee_fndecl_nofold (*expr_p);
-	  if (decl && fndecl_built_in_p (decl, BUILT_IN_FRONTEND))
+	  if (!decl)
+	    break;
+	  if (fndecl_built_in_p (decl, BUILT_IN_FRONTEND))
 	    switch (DECL_FE_FUNCTION_CODE (decl))
 	      {
 	      case CP_BUILT_IN_IS_CONSTANT_EVALUATED:
@@ -771,10 +913,12 @@ cp_gimplify_expr (tree *expr_p, gimple_seq *pre_p, gimple_seq *post_p)
 	      default:
 		break;
 	      }
-	  else if (decl
-		   && fndecl_built_in_p (decl, BUILT_IN_CLZG, BUILT_IN_CTZG))
+	  else if (fndecl_built_in_p (decl, BUILT_IN_CLZG, BUILT_IN_CTZG))
 	    ret = (enum gimplify_status) c_gimplify_expr (expr_p, pre_p,
 							  post_p);
+	  else
+	    /* All consteval functions should have been processed by now.  */
+	    gcc_checking_assert (!immediate_invocation_p (decl));
 	}
       break;
 
@@ -1035,6 +1179,20 @@ struct cp_genericize_data
   bool handle_invisiref_parm_p;
 };
 
+/* Emit an error about taking the address of an immediate function.
+   EXPR is the whole expression; DECL is the immediate function.  */
+
+static void
+taking_address_of_imm_fn_error (tree expr, tree decl)
+{
+  auto_diagnostic_group d;
+  const location_t loc = (TREE_CODE (expr) == PTRMEM_CST
+			  ? PTRMEM_CST_LOCATION (expr)
+			  : EXPR_LOCATION (expr));
+  error_at (loc, "taking address of an immediate function %qD", decl);
+  maybe_explain_promoted_consteval (loc, decl);
+}
+
 /* A subroutine of cp_fold_r to handle immediate functions.  */
 
 static tree
@@ -1045,90 +1203,142 @@ cp_fold_immediate_r (tree *stmt_p, int *walk_subtrees, void *data_)
   /* The purpose of this is not to emit errors for mce_unknown.  */
   const tsubst_flags_t complain = (data->flags & ff_mce_false
 				   ? tf_error : tf_none);
+  const tree_code code = TREE_CODE (stmt);
 
   /* No need to look into types or unevaluated operands.
      NB: This affects cp_fold_r as well.  */
-  if (TYPE_P (stmt) || unevaluated_p (TREE_CODE (stmt)))
+  if (TYPE_P (stmt) || unevaluated_p (code) || cp_unevaluated_operand)
     {
       *walk_subtrees = 0;
       return NULL_TREE;
     }
 
-  switch (TREE_CODE (stmt))
-    {
-    case PTRMEM_CST:
-      if (TREE_CODE (PTRMEM_CST_MEMBER (stmt)) == FUNCTION_DECL
-	  && DECL_IMMEDIATE_FUNCTION_P (PTRMEM_CST_MEMBER (stmt)))
-	{
-	  if (!data->pset.add (stmt) && (complain & tf_error))
-	    {
-	      error_at (PTRMEM_CST_LOCATION (stmt),
-			"taking address of an immediate function %qD",
-			PTRMEM_CST_MEMBER (stmt));
-	      *stmt_p = build_zero_cst (TREE_TYPE (stmt));
-	    }
-	  return error_mark_node;
-	}
-      break;
+  tree decl = NULL_TREE;
+  bool call_p = false;
 
-    /* Expand immediate invocations.  */
+  /* We are looking for &fn or fn().  */
+  switch (code)
+    {
     case CALL_EXPR:
     case AGGR_INIT_EXPR:
       if (tree fn = cp_get_callee (stmt))
 	if (TREE_CODE (fn) != ADDR_EXPR || ADDR_EXPR_DENOTES_CALL_P (fn))
-	  if (tree fndecl = cp_get_fndecl_from_callee (fn, /*fold*/false))
-	    if (DECL_IMMEDIATE_FUNCTION_P (fndecl))
-	      {
-		stmt = cxx_constant_value (stmt, complain);
-		if (stmt == error_mark_node)
-		  {
-		    if (complain & tf_error)
-		      *stmt_p = error_mark_node;
-		    return error_mark_node;
-		  }
-		*stmt_p = stmt;
-	      }
+	  decl = cp_get_fndecl_from_callee (fn, /*fold*/false);
+      call_p = true;
+      break;
+    case PTRMEM_CST:
+      decl = PTRMEM_CST_MEMBER (stmt);
       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))
-	{
-	  if (complain & tf_error)
-	    {
-	      error_at (EXPR_LOCATION (stmt),
-			"taking address of an immediate function %qD",
-			TREE_OPERAND (stmt, 0));
-	      *stmt_p = build_zero_cst (TREE_TYPE (stmt));
-	    }
-	  return error_mark_node;
-	}
+      if (!ADDR_EXPR_DENOTES_CALL_P (stmt))
+	decl = TREE_OPERAND (stmt, 0);
       break;
-
     default:
-      break;
+      return NULL_TREE;
     }
 
-  return NULL_TREE;
-}
+  if (!decl || TREE_CODE (decl) != FUNCTION_DECL)
+    return NULL_TREE;
 
-/* A wrapper around cp_fold_immediate_r.  Return true if we found
-   a non-constant immediate function, or taking the address of an
-   immediate function.  */
+  tree e = NULL_TREE;
 
-bool
-cp_fold_immediate (tree *tp, mce_value manifestly_const_eval)
-{
-  if (cxx_dialect <= cxx17)
-    return false;
+  /* Fully escalate once all templates have been instantiated.  */
+  if (at_eof > 1)
+    {
+      /* 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 (unchecked_immediate_escalating_function_p (decl))
+	{
+	  /* Set before the actual walk to avoid endless recursion.  */
+	  DECL_ESCALATION_CHECKED_P (decl) = true;
+	  /* We're only looking for the first escalating expression;
+	     let us not walk more trees than necessary.  */
+	  find_escalating_expr (&DECL_SAVED_TREE (decl), decl);
+	}
 
-  fold_flags_t flags = ff_fold_immediate;
-  if (manifestly_const_eval == mce_false)
-    flags |= ff_mce_false;
+      /* In turn, maybe promote the function we find ourselves in...  */
+      if ((data->flags & ff_find_escalating_expr)
+	  && 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.  */
+	  && (e = cxx_constant_value (stmt, tf_none), e == error_mark_node))
+	{
+	  /* Since we had to set DECL_ESCALATION_CHECKED_P before the walk,
+	     we call promote_function_to_consteval directly which doesn't
+	     check unchecked_immediate_escalating_function_p.  */
+	  if (current_function_decl)
+	    promote_function_to_consteval (current_function_decl);
+	  *walk_subtrees = 0;
+	  return stmt;
+	}
+    }
 
-  cp_fold_data data (flags);
-  return !!cp_walk_tree_without_duplicates (tp, cp_fold_immediate_r, &data);
+  /* [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 STMT does
+     not need to produce a constant expression.  */
+  if (immediate_invocation_p (decl))
+    {
+      e = e ? e : cxx_constant_value (stmt, tf_none);
+      if (e == error_mark_node)
+	{
+	  if (maybe_promote_function_to_consteval (current_function_decl))
+	    return NULL_TREE;
+	  if (complain & tf_error)
+	    {
+	      if (call_p)
+		{
+		  auto_diagnostic_group d;
+		  location_t loc = cp_expr_loc_or_input_loc (stmt);
+		  error_at (loc, "call to consteval function %qE is "
+			    "not a constant expression", stmt);
+		  /* Explain why it's not a constant expression.  */
+		  *stmt_p = cxx_constant_value (stmt, complain);
+		  maybe_explain_promoted_consteval (loc, decl);
+		}
+	      else if (!data->pset.add (stmt))
+		{
+		  taking_address_of_imm_fn_error (stmt, decl);
+		  *stmt_p = build_zero_cst (TREE_TYPE (stmt));
+		}
+	      /* If we're giving hard errors, continue the walk rather than
+		 bailing out after the first error.  */
+	      return NULL_TREE;
+	    }
+	  return stmt;
+	}
+      /* We've evaluated the consteval function call.  */
+      if (call_p)
+	*stmt_p = e;
+    }
+  /* We've encountered a function call that may turn out to be consteval
+     later.  Store its caller so that we can ensure that the call is
+     a constant expression.  */
+  else if (unchecked_immediate_escalating_function_p (decl))
+    {
+      /* Make sure we're not inserting new elements while walking
+	 the deferred_escalating_exprs hash table; if we are, it's
+	 likely that a function wasn't properly marked checked for
+	 i-e expressions.  */
+      gcc_checking_assert (at_eof <= 1);
+      if (current_function_decl)
+	remember_escalating_expr (current_function_decl);
+      /* auto p = &f<int>; in the global scope won't be ensconced in
+	 a function we could store for later at this point.  (If there's
+	 no c_f_d at this point and we're dealing with a call, we should
+	 see the call when cp_fold_function __static_i_and_d.)  */
+      else if (!call_p)
+	remember_escalating_expr (stmt);
+    }
+
+  return NULL_TREE;
 }
 
 /* Perform any pre-gimplification folding of C++ front end trees to
@@ -1178,11 +1388,19 @@ cp_fold_r (tree *stmt_p, int *walk_subtrees, void *data_)
 	  *walk_subtrees = 0;
 	  /* Don't return yet, still need the cp_fold below.  */
 	}
-      cp_fold_immediate_r (stmt_p, walk_subtrees, data);
+      else
+	cp_fold_immediate_r (stmt_p, walk_subtrees, data);
     }
 
   *stmt_p = stmt = cp_fold (*stmt_p, data->flags);
 
+  /* For certain trees, like +foo(), the cp_fold below will remove the +,
+     and the subsequent tree walk would go straight down to the CALL_EXPR's
+     operands, meaning that cp_fold_immediate_r would never see the
+     CALL_EXPR.  Ew :(.  */
+  if (TREE_CODE (stmt) == CALL_EXPR && code != CALL_EXPR)
+    cp_fold_immediate_r (stmt_p, walk_subtrees, data);
+
   if (data->pset.add (stmt))
     {
       /* Don't walk subtrees of stmts we've already walked once, otherwise
@@ -1304,6 +1522,63 @@ cp_fold_function (tree fndecl)
      pass ff_mce_false.  */
   cp_fold_data data (ff_genericize | ff_mce_false);
   cp_walk_tree (&DECL_SAVED_TREE (fndecl), cp_fold_r, &data, NULL);
+
+  /* This is merely an optimization: if FNDECL has no i-e expressions,
+     we'll not save c_f_d, and we can safely say that FNDECL will not
+     be promoted to consteval.  */
+  if (deferred_escalating_exprs
+      && !deferred_escalating_exprs->contains (current_function_decl))
+    DECL_ESCALATION_CHECKED_P (fndecl) = true;
+}
+
+/* FN is not a consteval function, but may become one.  Remember to
+   escalate it after all pending templates have been instantiated.  */
+
+void
+maybe_store_immediate_escalating_fn (tree fn)
+{
+  if (unchecked_immediate_escalating_function_p (fn))
+    remember_escalating_expr (fn);
+}
+
+/* We've stashed immediate-escalating functions.  Now see if they indeed
+   ought to be promoted to consteval.  */
+
+void
+process_pending_immediate_escalating_fns ()
+{
+  /* This will be null for -fno-immediate-escalation.  */
+  if (!deferred_escalating_exprs)
+    return;
+
+  for (auto e : *deferred_escalating_exprs)
+    if (TREE_CODE (e) == FUNCTION_DECL && !DECL_ESCALATION_CHECKED_P (e))
+      cp_fold_immediate (&DECL_SAVED_TREE (e), mce_false, e);
+}
+
+/* We've escalated every function that could have been promoted to
+   consteval.  Check that we are not taking the address of a consteval
+   function.  */
+
+void
+check_immediate_escalating_refs ()
+{
+  /* This will be null for -fno-immediate-escalation.  */
+  if (!deferred_escalating_exprs)
+    return;
+
+  for (auto ref : *deferred_escalating_exprs)
+    {
+      if (TREE_CODE (ref) == FUNCTION_DECL)
+	continue;
+      tree decl = (TREE_CODE (ref) == PTRMEM_CST
+		   ? PTRMEM_CST_MEMBER (ref)
+		   : TREE_OPERAND (ref, 0));
+      if (DECL_IMMEDIATE_FUNCTION_P (decl))
+	taking_address_of_imm_fn_error (ref, decl);
+    }
+
+  deferred_escalating_exprs = nullptr;
 }
 
 /* Turn SPACESHIP_EXPR EXPR into GENERIC.  */
diff --git a/gcc/cp/cp-tree.h b/gcc/cp/cp-tree.h
index 7b0b7c6a17e..2cb05b8f8ea 100644
--- a/gcc/cp/cp-tree.h
+++ b/gcc/cp/cp-tree.h
@@ -2947,8 +2947,9 @@ struct GTY(()) lang_decl_fn {
   unsigned maybe_deleted : 1;
   unsigned coroutine_p : 1;
   unsigned implicit_constexpr : 1;
+  unsigned escalated_p : 1;
 
-  unsigned spare : 9;
+  unsigned spare : 8;
 
   /* 32-bits padding on 64-bit host.  */
 
@@ -3400,6 +3401,14 @@ struct GTY(()) lang_decl {
 #define DECL_MAYBE_DELETED(NODE) \
   (LANG_DECL_FN_CHECK (NODE)->maybe_deleted)
 
+/* Nonzero for FUNCTION_DECL means that this function's body has been
+   checked for immediate-escalating expressions and maybe promoted.  It
+   does *not* mean the function is consteval.  It must not be set in
+   a function that was marked consteval by the user, so that we can
+   distinguish between explicitly consteval functions and promoted consteval
+   functions.  */
+#define DECL_ESCALATION_CHECKED_P(NODE) (LANG_DECL_FN_CHECK (NODE)->escalated_p)
+
 /* True (in a FUNCTION_DECL) if NODE is a virtual function that is an
    invalid overrider for a function from a base class.  Once we have
    complained about an invalid overrider we avoid complaining about it
@@ -5877,7 +5886,8 @@ extern GTY(()) vec<tree, va_gc> *keyed_classes;
 
 \f
 /* Nonzero if we're done parsing and into end-of-file activities.
-   Two if we're done with front-end processing.  */
+   2 if all templates have been instantiated.
+   3 if we're done with front-end processing.  */
 
 extern int at_eof;
 
@@ -6769,6 +6779,7 @@ 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 tree build_cxx_call			(tree, int, tree *,
 						 tsubst_flags_t,
 						 tree = NULL_TREE);
@@ -8411,7 +8422,11 @@ extern tree process_stmt_assume_attribute	(tree, tree, location_t);
 extern bool simple_empty_class_p		(tree, tree, tree_code);
 extern tree fold_builtin_source_location	(const_tree);
 extern tree get_source_location_impl_type	();
-extern bool cp_fold_immediate			(tree *, mce_value);
+extern bool cp_fold_immediate			(tree *, mce_value,
+						 tree = current_function_decl);
+extern void check_immediate_escalating_refs	();
+extern void maybe_store_immediate_escalating_fn	(tree);
+extern void process_pending_immediate_escalating_fns ();
 
 /* in name-lookup.cc */
 extern tree strip_using_decl                    (tree);
diff --git a/gcc/cp/decl.cc b/gcc/cp/decl.cc
index e7701afdefd..d820b8971a9 100644
--- a/gcc/cp/decl.cc
+++ b/gcc/cp/decl.cc
@@ -18351,7 +18351,10 @@ finish_function (bool inline_p)
   if (!processing_template_decl
       && !DECL_IMMEDIATE_FUNCTION_P (fndecl)
       && !DECL_OMP_DECLARE_REDUCTION_P (fndecl))
-    cp_fold_function (fndecl);
+    {
+      cp_fold_function (fndecl);
+      maybe_store_immediate_escalating_fn (fndecl);
+    }
 
   /* Set up the named return value optimization, if we can.  Candidate
      variables are selected in check_return_expr.  */
diff --git a/gcc/cp/decl2.cc b/gcc/cp/decl2.cc
index 9e666e5eece..f9187b150c7 100644
--- a/gcc/cp/decl2.cc
+++ b/gcc/cp/decl2.cc
@@ -169,7 +169,9 @@ typedef hash_map<unsigned/*Priority*/, tree/*List*/,
    one for init.  The fini table is only ever used when !cxa_atexit.  */
 static GTY(()) priority_map_t *static_init_fini_fns[2];
 
-/* Nonzero if we're done parsing and into end-of-file activities.  */
+/* Nonzero if we're done parsing and into end-of-file activities.
+   2 if all templates have been instantiated.
+   3 if we're done with front-end processing.  */
 
 int at_eof;
 
@@ -4987,6 +4989,7 @@ c_parse_final_cleanups (void)
   tree decl;
 
   locus_at_end_of_parsing = input_location;
+  /* We're done parsing.  */
   at_eof = 1;
 
   /* Bad parse errors.  Just forget about it.  */
@@ -5252,6 +5255,9 @@ c_parse_final_cleanups (void)
 	reconsider = true;
     }
 
+  /* All templates have been instantiated.  */
+  at_eof = 2;
+
   void *module_cookie = finish_module_processing (parse_in);
 
   lower_var_init ();
@@ -5294,7 +5300,15 @@ c_parse_final_cleanups (void)
   if (static_init_fini_fns[true])
     for (auto iter : *static_init_fini_fns[true])
       iter.second = nreverse (iter.second);
-  
+
+  /* Now we've instantiated all templates.  Now we can escalate the functions
+     we squirreled away earlier.  */
+  if (flag_immediate_escalation)
+    {
+      process_pending_immediate_escalating_fns ();
+      check_immediate_escalating_refs ();
+    }
+
   /* Then, do the Objective-C stuff.  This is where all the
      Objective-C module stuff gets generated (symtab,
      class/protocol/selector lists etc).  This must be done after C++
@@ -5376,7 +5390,7 @@ c_parse_final_cleanups (void)
   timevar_start (TV_PHASE_PARSING);
 
   /* Indicate that we're done with front end processing.  */
-  at_eof = 2;
+  at_eof = 3;
 }
 
 /* Perform any post compilation-proper cleanups for the C++ front-end.
diff --git a/gcc/cp/error.cc b/gcc/cp/error.cc
index 785909c362a..3b1b5de5ea4 100644
--- a/gcc/cp/error.cc
+++ b/gcc/cp/error.cc
@@ -478,7 +478,7 @@ dump_template_bindings (cxx_pretty_printer *pp, tree parms, tree args,
 
   /* Don't try to do this once cgraph starts throwing away front-end
      information.  */
-  if (at_eof >= 2)
+  if (at_eof >= 3)
     return;
 
   FOR_EACH_VEC_SAFE_ELT (typenames, i, t)
diff --git a/gcc/cp/module.cc b/gcc/cp/module.cc
index 4f5b6e2747a..eeec6362c8d 100644
--- a/gcc/cp/module.cc
+++ b/gcc/cp/module.cc
@@ -5683,6 +5683,8 @@ trees_out::lang_decl_bools (tree t)
       WB (lang->u.fn.has_dependent_explicit_spec_p);
       WB (lang->u.fn.immediate_fn_p);
       WB (lang->u.fn.maybe_deleted);
+      WB (lang->u.fn.escalated_p);
+      /* We do not stream lang->u.fn.implicit_constexpr.  */
       goto lds_min;
 
     case lds_decomp:  /* lang_decl_decomp.  */
@@ -5751,6 +5753,8 @@ trees_in::lang_decl_bools (tree t)
       RB (lang->u.fn.has_dependent_explicit_spec_p);
       RB (lang->u.fn.immediate_fn_p);
       RB (lang->u.fn.maybe_deleted);
+      RB (lang->u.fn.escalated_p);
+      /* We do not stream lang->u.fn.implicit_constexpr.  */
       goto lds_min;
 
     case lds_decomp:  /* lang_decl_decomp.  */
diff --git a/gcc/cp/pt.cc b/gcc/cp/pt.cc
index 092e6fdfd36..2d4d850d8e5 100644
--- a/gcc/cp/pt.cc
+++ b/gcc/cp/pt.cc
@@ -11107,7 +11107,7 @@ push_tinst_level_loc (tree tldcl, tree targs, location_t loc)
   if (tinst_depth >= max_tinst_depth)
     {
       /* Tell error.cc not to try to instantiate any templates.  */
-      at_eof = 2;
+      at_eof = 3;
       fatal_error (input_location,
 		   "template instantiation depth exceeds maximum of %d"
 		   " (use %<-ftemplate-depth=%> to increase the maximum)",
diff --git a/gcc/cp/typeck.cc b/gcc/cp/typeck.cc
index e995fb6ddd7..00db295dced 100644
--- a/gcc/cp/typeck.cc
+++ b/gcc/cp/typeck.cc
@@ -7263,11 +7263,9 @@ cp_build_addr_expr_1 (tree arg, bool strict_lvalue, tsubst_flags_t complain)
 			      complain);
     }
 
-  /* For addresses of immediate functions ensure we have EXPR_LOCATION
-     set for possible later diagnostics.  */
+  /* Ensure we have EXPR_LOCATION 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)))
+      && TREE_CODE (TREE_OPERAND (val, 0)) == FUNCTION_DECL)
     SET_EXPR_LOCATION (val, input_location);
 
   return val;
diff --git a/gcc/doc/invoke.texi b/gcc/doc/invoke.texi
index 54c42c9659c..c639d24b36b 100644
--- a/gcc/doc/invoke.texi
+++ b/gcc/doc/invoke.texi
@@ -219,6 +219,7 @@ in the following sections.
 -fno-elide-constructors
 -fno-enforce-eh-specs
 -fno-gnu-keywords
+-fno-immediate-escalation
 -fno-implicit-templates
 -fno-implicit-inline-templates
 -fno-implement-inlines
@@ -3384,6 +3385,39 @@ word as an identifier.  You can use the keyword @code{__typeof__} instead.
 This option is implied by the strict ISO C++ dialects: @option{-ansi},
 @option{-std=c++98}, @option{-std=c++11}, etc.
 
+@opindex fno-immediate-escalation
+@opindex fimmediate-escalation
+@item -fno-immediate-escalation
+Do not enable immediate function escalation whereby certain functions
+can be promoted to consteval, as specified in P2564R3.  For example:
+
+@example
+consteval int id(int i) @{ return i; @}
+
+constexpr int f(auto t)
+@{
+  return t + id(t); // id causes f<int> to be promoted to consteval
+@}
+
+void g(int i)
+@{
+  f (3);
+@}
+@end example
+
+compiles in C++20: @code{f} is an immediate-escalating function (due to
+the @code{auto} it is a function template and is declared @code{constexpr})
+and @code{id(t)} is an immediate-escalating expression, so @code{f} is
+promoted to @code{consteval}.  Consequently, the call to @code{id(t)}
+is in an immediate context, so doesn't have to produce a constant (that
+is the mechanism allowing consteval function composition).  However,
+with @option{-fno-immediate-escalation}, @code{f} is not promoted to
+@code{consteval}, and since the call to consteval function @code{id(t)}
+is not a constant expression, the compiler rejects the code.
+
+This option is turned on by default; it is only effective in C++20 mode
+or later.
+
 @opindex fimplicit-constexpr
 @item -fimplicit-constexpr
 Make inline functions implicitly constexpr, if they satisfy the
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 b2c5472b7de..3b258711ce6 100644
--- a/gcc/testsuite/g++.dg/cpp23/consteval-if2.C
+++ b/gcc/testsuite/g++.dg/cpp23/consteval-if2.C
@@ -33,7 +33,7 @@ baz (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);	// { dg-error "not a constant expression" }
     }
   else
     {
@@ -45,11 +45,11 @@ baz (int x)
     }
   else
     {
-      r += foo (8 * x);	// { dg-error "'x' is not a constant expression" }
+      r += foo (8 * x);	// { dg-error "is not a constant expression" }
     }
   if ! consteval	// { dg-warning "'if consteval' only available with" "" { target c++20_only } }
     {
-      r += foo (32 * x);// { dg-error "'x' is not a constant expression" }
+      r += foo (32 * x);// { dg-error "not a constant expression" }
     }
   if consteval		// { dg-warning "'if consteval' only available with" "" { target c++20_only } }
     {
@@ -98,7 +98,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
     {
@@ -110,11 +110,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 } }
     {
@@ -126,5 +126,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 80e8ef680d9..bdedc62c30f 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-memfn1.C b/gcc/testsuite/g++.dg/cpp2a/consteval-memfn1.C
index 46eed13446d..ca923519f98 100644
--- a/gcc/testsuite/g++.dg/cpp2a/consteval-memfn1.C
+++ b/gcc/testsuite/g++.dg/cpp2a/consteval-memfn1.C
@@ -20,10 +20,13 @@ template<class>
 void VerifyHash(fixed_string s) {
   s.size(0); // { dg-bogus "" }
   s.size(-1); // { dg-message "expansion of" }
+// { dg-error "call to consteval function" "" { target *-*-* } .-1 }
   s.size_static(0); // { dg-bogus "" }
   s.size_static(-1); // { dg-message "expansion of" }
+// { dg-error "call to consteval function" "" { target *-*-* } .-1 }
   fixed_string::size_static(0); // { dg-bogus "" }
   fixed_string::size_static(-1); // { dg-message "expansion of" }
+// { dg-error "call to consteval function" "" { target *-*-* } .-1 }
   s(); // { dg-bogus "" }
 }
 
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..5e7b208113f
--- /dev/null
+++ b/gcc/testsuite/g++.dg/cpp2a/consteval-prop1.C
@@ -0,0 +1,169 @@
+// 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); // { dg-message "immediate-escalating expression .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); // { dg-message "immediate-escalating expression .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; // { dg-message "immediate-escalating expression .id." }
+  (void) p;
+  return t;
+}
+
+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};
+}
+
+constexpr void
+f5_nt (int i)
+{
+  A a{i}; // { dg-error "call to consteval function|not a constant" }
+}
+
+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; // { dg-message ".int g. is not const" }
+
+B b2(f0<int>(cid<int>(g))); // { dg-error "call to consteval function|not usable" }
+
+struct C {
+  consteval C (int) {};
+};
+
+constexpr int
+f7 (auto t)
+{
+  C c(t); // { dg-message "immediate-escalating expression .c.C::C\\(t\\)." }
+  return 0;
+}
+
+int i1 = f7 (g); // { dg-error "call to consteval function|not usable" }
+
+struct Y {
+  int y;
+  int x = id (y);
+  consteval Y (int i) : y (id (i)) {}
+};
+
+Y y1(1);
+Y y2(g); // { dg-error "call to consteval function|not usable" }
+
+struct Y2 {
+  int y;
+  int x = id (y);
+  constexpr Y2 (auto i) : y (id (i)) {}
+};
+
+Y2 y3(1);
+Y2 y4(g); // { dg-error "call to consteval function|not usable" }
+
+auto l1 = [](int i) constexpr {
+  int t = id (i);
+  return id (0);
+};
+
+int (*pl1)(int) = l1; // { dg-error "call to consteval function|returns address of immediate function" }
+
+auto l2 = [](int i) {
+  int t = id (i);
+  return id (0);
+};
+
+int (*pl2)(int) = l2; // { dg-error "call to consteval function|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 "used before its definition" }
+  int a = [] (int u = undef ()) {
+    return u;
+  }();
+} s2; // { dg-error "call to consteval function" }
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..4e33e6e3d0e
--- /dev/null
+++ b/gcc/testsuite/g++.dg/cpp2a/consteval-prop10.C
@@ -0,0 +1,41 @@
+// 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; // { dg-message ".int non_const. is not const" }
+
+template<typename>
+constexpr int
+f2 (int i = id (non_const))
+{
+  return i;
+}
+
+constexpr int
+f3 (auto)
+{
+  return f2<int>(); // { dg-message "contains an immediate-escalating expression .id\\(non_const\\)." }
+}
+
+auto a = &f3<int>; // { dg-error "taking address of an immediate function" }
+
+void
+g (int i)
+{
+  f1<int> (42);
+  f1<int> (i);
+  f1<int> ();
+  f2<int> (42);
+  f2<int> (i);
+  f2<int> (); // { dg-error "call to consteval function .id\\(non_const\\). is not a constant expression" }
+// { dg-error ".non_const. is not usable in a constant expression" "" { target *-*-* } .-1 }
+}
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..aca9675cd53
--- /dev/null
+++ b/gcc/testsuite/g++.dg/cpp2a/consteval-prop11.C
@@ -0,0 +1,49 @@
+// 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
+foobar (auto i)
+{
+  return i + id (i);
+  /* { dg-begin-multiline-output "" }
+   return i + id (i);
+              ~~~^~~
+     { dg-end-multiline-output "" } */
+}
+
+void
+g (int x)
+{
+  foobar (x); // { dg-error "10:call to consteval function .foobar<int>\\(x\\). is not a constant expression" }
+// { dg-error ".x. is not a constant expression" "" { target *-*-* } .-1 }
+  /* { dg-begin-multiline-output "" }
+foobar (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|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..cdc1f6dc862
--- /dev/null
+++ b/gcc/testsuite/g++.dg/cpp2a/consteval-prop14.C
@@ -0,0 +1,78 @@
+// 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\\(\\(i \\* 2\\)\\)." }
+}
+
+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" }
+// { dg-error ".i. is not a constant expression" "" { target *-*-* } .-1 }
+  f1 (42);
+  f2 (i); // { dg-error "call to consteval function .f2<int>\\(i\\). is not a constant expression" }
+// { dg-error ".i. is not a constant expression" "" { target *-*-* } .-1 }
+  f2 (42);
+  f3 (i); // { dg-error "call to consteval function .f3<int>\\(i\\). is not a constant expression" }
+// { dg-error ".i. is not a constant expression" "" { target *-*-* } .-1 }
+  f3 (42);
+  f4 (i); // { dg-error "call to consteval function .f4<int>\\(i\\). is not a constant expression" }
+// { dg-error ".i. is not a constant expression" "" { target *-*-* } .-1 }
+  f4 (42);
+  f5 (i); // { dg-error "call to consteval function .f5<int>\\(i\\). is not a constant expression" }
+// { dg-error ".i. is not a constant expression" "" { target *-*-* } .-1 }
+  f5 (42);
+  f6 (i); // { dg-error "call to consteval function .f6<int>\\(i\\). is not a constant expression" }
+// { dg-error ".i. is not a constant expression" "" { target *-*-* } .-1 }
+  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..3341c510a9f
--- /dev/null
+++ b/gcc/testsuite/g++.dg/cpp2a/consteval-prop15.C
@@ -0,0 +1,107 @@
+// 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;
+}
+
+constexpr int
+f9 (auto i)
+{
+  if consteval {
+    return id(i);
+  }
+  return i;
+}
+
+constexpr int
+f10 (auto i)
+{
+  if not consteval {
+    (void) 0;
+  } else {
+    return id(i);
+  }
+  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" }
+// { dg-error ".non_const. is not a constant expression" "" { target *-*-* } .-1 }
+  f5 (42);
+  f5 (non_const); // { dg-error "call to consteval function .f5<int>\\(non_const\\). is not a constant expression" }
+// { dg-error ".non_const. is not a constant expression" "" { target *-*-* } .-1 }
+  f6 (42);
+  f6 (non_const);
+  f7 (42);
+  f7 (non_const);
+  f8 (42);
+  f8 (non_const);
+  f9 (42);
+  f9 (non_const);
+  f10 (42);
+  f10 (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..47ec9b60b6c
--- /dev/null
+++ b/gcc/testsuite/g++.dg/cpp2a/consteval-prop17.C
@@ -0,0 +1,17 @@
+// P2564R3
+// { dg-do compile { target c++20 } }
+// { dg-options "-fno-immediate-escalation" }
+
+consteval int id(int i) { return i; }
+
+constexpr int
+f (auto i)
+{
+  return id (i); // { dg-error "not a constant expression" }
+}
+
+int
+g ()
+{
+  return f (42);
+}
diff --git a/gcc/testsuite/g++.dg/cpp2a/consteval-prop18.C b/gcc/testsuite/g++.dg/cpp2a/consteval-prop18.C
new file mode 100644
index 00000000000..a18106f8e0f
--- /dev/null
+++ b/gcc/testsuite/g++.dg/cpp2a/consteval-prop18.C
@@ -0,0 +1,20 @@
+// P2564R3
+// { dg-do compile { target c++20 } }
+
+consteval int id(int i) { return i; }
+
+constexpr int
+f (auto t)
+{
+  return t + id (t);
+}
+
+constexpr int
+f2 (auto t)
+{
+  return t + f(t); // { dg-message "immediate-escalating expression .f<int>\\(t\\)." }
+}
+
+int z; // { dg-message "not const" }
+auto y1 = f2 (42);
+auto y2 = f2 (z); // { dg-error "value of .z. is not usable in a constant expression|call to consteval function" }
diff --git a/gcc/testsuite/g++.dg/cpp2a/consteval-prop19.C b/gcc/testsuite/g++.dg/cpp2a/consteval-prop19.C
new file mode 100644
index 00000000000..3ceb05e41f4
--- /dev/null
+++ b/gcc/testsuite/g++.dg/cpp2a/consteval-prop19.C
@@ -0,0 +1,7 @@
+// P2564R3
+// { dg-do compile { target c++20 } }
+
+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;  // { dg-error "call to consteval function|returns address of immediate function" }
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..30129a4a266
--- /dev/null
+++ b/gcc/testsuite/g++.dg/cpp2a/consteval-prop2.C
@@ -0,0 +1,90 @@
+// 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);		// { dg-message "immediate-escalating expression .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 "the value of .x. is not usable in a constant expression" }
+// { dg-message "immediate-escalating expression .id\\(x\\)." "" { target *-*-* } .-1 }
+}
+
+int i = hh<int>();              // { dg-error "call to consteval function|called in a constant expression" }
+				// error: hh<int>() is an immediate-escalating expression
+                                // outside of an immediate-escalating function
+struct A {
+  int x;
+  int y = id(x);
+};
+
+// [expr.const]#example-9 says:
+//   k<int> is not an immediate function because A(42) is a
+//   constant expression and thus not immediate-escalating
+// In the evaluation of A(42), the member x has just been initialized
+// to constant 42.  And A(42) is constant-evaluated because "An aggregate
+// initialization is an immediate invocation if it evaluates a default
+// member initializer that has a subexpression that is an
+// immediate-escalating expression."
+template<class T>
+constexpr int k(int) {
+  return A(42).y;
+}
+
+int
+test (int i)
+{
+  int r = g (42) + g(i);
+  int t = k<int>(42)
+	    + k<int>(i); // { dg-bogus "call to|constant" "" { xfail *-*-* } }
+  return r + t;
+}
+
+// Just like above, but make the call to id(x) actually a constant.
+struct A2 {
+  static constexpr int x = 42;
+  int y = id(x);
+};
+
+template<class T>
+constexpr int k2(int) {
+  return A2(42).y;
+}
+
+int
+test2 (int i)
+{
+  return k2<int>(42) + k2<int>(i);
+}
diff --git a/gcc/testsuite/g++.dg/cpp2a/consteval-prop20.C b/gcc/testsuite/g++.dg/cpp2a/consteval-prop20.C
new file mode 100644
index 00000000000..f1bb08e2dba
--- /dev/null
+++ b/gcc/testsuite/g++.dg/cpp2a/consteval-prop20.C
@@ -0,0 +1,21 @@
+// P2564R3
+// { dg-do compile { target c++20 } }
+// { dg-options "-Wno-c++23-extensions" }
+
+consteval int id(int i) { return i; }
+
+constexpr int
+f (auto i)
+{
+  return id (i);
+}
+
+void
+g ()
+{
+  auto p = &f<int>; // { dg-error "taking address" }
+  decltype(&f<int>) x;
+  if consteval {
+    auto q = &f<int>;
+  }
+}
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..f181cb32942
--- /dev/null
+++ b/gcc/testsuite/g++.dg/cpp2a/consteval-prop3.C
@@ -0,0 +1,27 @@
+// 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);
+
+auto a2 = &f2<char>; // { dg-error "taking address" }
+auto b2 = &f2<int>; // { dg-error "taking address" }
+
+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..3a2e09b17b0
--- /dev/null
+++ b/gcc/testsuite/g++.dg/cpp2a/consteval-prop4.C
@@ -0,0 +1,30 @@
+// P2564R3
+// { dg-do compile { target c++20 } }
+// 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..93ed398d9bf
--- /dev/null
+++ b/gcc/testsuite/g++.dg/cpp2a/consteval-prop6.C
@@ -0,0 +1,59 @@
+// 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(){} // { dg-error "call to consteval function" }
+
+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 "modification of .x. is not a constant expression" }
+};
+
+void
+test ()
+{
+  [[maybe_unused]] X x; // { dg-error "call to consteval function" }
+// { dg-message "promoted to an immediate function" "" { target *-*-* } .-1 }
+}
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..118cf576f14
--- /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 "call to consteval function|not usable" }
+constexpr auto a2 = f2 (non_const); // { dg-error "not a constant|not usable" }
+auto a3 = f3 (42);
+constexpr auto a4 = f4 (42);
+
+void
+g ()
+{
+   auto a5 = f5 (non_const); // { dg-error "not a constant|not usable" }
+   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|not usable" }
+   (void) f10 (42);
+   (void) g1 (non_const); // { dg-error "not a constant|not usable" }
+}
+
+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 "call to consteval function|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..080fc76f26e
--- /dev/null
+++ b/gcc/testsuite/g++.dg/cpp2a/consteval-prop8.C
@@ -0,0 +1,82 @@
+// 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};
+}
+
+void
+f3 (int i)
+{
+  A a{i}; // { dg-error "not a constant expression" }
+}
+
+inline void
+f7 (int i)
+{
+  A a{i}; // { dg-error "not a constant expression" }
+}
+
+constexpr void
+f8 (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/consteval11.C b/gcc/testsuite/g++.dg/cpp2a/consteval11.C
index 05cecea4502..c2ee3c7a82a 100644
--- a/gcc/testsuite/g++.dg/cpp2a/consteval11.C
+++ b/gcc/testsuite/g++.dg/cpp2a/consteval11.C
@@ -8,9 +8,11 @@ constexpr int a = bar (1);
 constexpr int b = bar (2);		// { dg-message "in 'constexpr' expansion of" }
 constexpr int c = 0 ? bar (3) : 1;
 const int d = bar (4);			// { dg-message "in 'constexpr' expansion of" }
+// { dg-error "call to consteval function" "" { target *-*-* } .-1 }
 const int e = 0 ? bar (5) : 1;
 int f = bar (1);
 int g = bar (6);			// { dg-message "in 'constexpr' expansion of" }
+// { dg-error "call to consteval function" "" { target *-*-* } .-1 }
 int h = 0 ? bar (7) : 1;
 
 void
@@ -20,25 +22,35 @@ foo ()
   constexpr int b = bar (2);		// { dg-message "in 'constexpr' expansion of" }
   constexpr int c = 0 ? bar (3) : 1;
   const int d = bar (4);		// { dg-message "in 'constexpr' expansion of" }
+// { dg-error "call to consteval function" "" { target *-*-* } .-1 }
   const int e = 0 ? bar (5) : 1;
   int f = bar (1);
   int g = bar (6);			// { dg-message "in 'constexpr' expansion of" }
+// { dg-error "call to consteval function" "" { target *-*-* } .-1 }
   int h = 0 ? bar (7) : 1;		// { dg-message "in 'constexpr' expansion of" }
+// { dg-error "call to consteval function" "" { target *-*-* } .-1 }
   h += 0 ? bar (8) : 1;			// { dg-message "in 'constexpr' expansion of" }
+// { dg-error "call to consteval function" "" { target *-*-* } .-1 }
   if (0)
     bar (9);				// { dg-message "in 'constexpr' expansion of" }
+// { dg-error "call to consteval function" "" { target *-*-* } .-1 }
   else
     bar (10);				// { dg-message "in 'constexpr' expansion of" }
+// { dg-error "call to consteval function" "" { target *-*-* } .-1 }
   if (1)
     bar (11);				// { dg-message "in 'constexpr' expansion of" }
+// { dg-error "call to consteval function" "" { target *-*-* } .-1 }
   else
     bar (12);				// { dg-message "in 'constexpr' expansion of" }
+// { dg-error "call to consteval function" "" { target *-*-* } .-1 }
   if constexpr (0)
     bar (13);
   else
     bar (14);				// { dg-message "in 'constexpr' expansion of" }
+// { dg-error "call to consteval function" "" { target *-*-* } .-1 }
   if constexpr (1)
     bar (15);				// { dg-message "in 'constexpr' expansion of" }
+// { dg-error "call to consteval function" "" { target *-*-* } .-1 }
   else
     bar (16);
 }
@@ -121,18 +133,24 @@ quux ()
 {
   if (0)
     bar ((T) 2);				// { dg-message "in 'constexpr' expansion of" }
+// { dg-error "call to consteval function" "" { target *-*-* } .-1 }
   else
     bar ((T) 3);				// { dg-message "in 'constexpr' expansion of" }
+// { dg-error "call to consteval function" "" { target *-*-* } .-1 }
   if (1)
     bar ((T) 4);				// { dg-message "in 'constexpr' expansion of" }
+// { dg-error "call to consteval function" "" { target *-*-* } .-1 }
   else
     bar ((T) 5);				// { dg-message "in 'constexpr' expansion of" }
+// { dg-error "call to consteval function" "" { target *-*-* } .-1 }
   if constexpr (0)
     bar ((T) 6);
   else
     bar ((T) 7);				// { dg-message "in 'constexpr' expansion of" }
+// { dg-error "call to consteval function" "" { target *-*-* } .-1 }
   if constexpr (1)
     bar ((T) 8);				// { dg-message "in 'constexpr' expansion of" }
+// { dg-error "call to consteval function" "" { target *-*-* } .-1 }
   else
     bar ((T) 9);
 }
diff --git a/gcc/testsuite/g++.dg/cpp2a/consteval3.C b/gcc/testsuite/g++.dg/cpp2a/consteval3.C
index 9efac8c8eae..1199e9db623 100644
--- a/gcc/testsuite/g++.dg/cpp2a/consteval3.C
+++ b/gcc/testsuite/g++.dg/cpp2a/consteval3.C
@@ -16,8 +16,8 @@ consteval auto [ b, c ] = S ();		// { dg-error "structured binding declaration c
 int f5 (consteval int x) { return x; }	// { dg-error "a parameter cannot be declared 'consteval'" }
 consteval int f6 (int x) { return x; }
 int d = 6;		// { dg-message "'int d' is not const" }
-int e = f6 (d);		// { dg-error "the value of 'd' is not usable in a constant expression" }
-constexpr int f7 (int x) { return f6 (x); }	// { dg-error "'x' is not a constant expression" }
+int e = f6 (d);		// { dg-error "the value of 'd' is not usable in a constant expression|call to consteval function" }
+constexpr int f7 (int x) { return f6 (x); }	// { dg-error "'x' is not a constant expression|call to consteval function" }
 constexpr int f = f7 (5);
 using fnptr = int (int);
 fnptr *g = f6;		// { dg-error "taking address of an immediate function 'consteval int f6\\(int\\)'" }
diff --git a/gcc/testsuite/g++.dg/cpp2a/consteval34.C b/gcc/testsuite/g++.dg/cpp2a/consteval34.C
index 068827ba516..7562f403f74 100644
--- a/gcc/testsuite/g++.dg/cpp2a/consteval34.C
+++ b/gcc/testsuite/g++.dg/cpp2a/consteval34.C
@@ -7,6 +7,7 @@ constexpr int
 foo (bool b)
 {
   return b ? bar (3) : 2; // { dg-message "in .constexpr. expansion" }
+// { dg-error "call to consteval function" "" { target *-*-* } .-1 }
 }
 
 static_assert (foo (false) == 2);
@@ -22,13 +23,20 @@ void
 g ()
 {
   __extension__ int a1[bar(3)]; // { dg-message "in .constexpr. expansion" }
+// { dg-error "call to consteval function" "" { target *-*-* } .-1 }
   int a2[sizeof (bar(3))];
 
   int a3 = false ? (1 + bar (8)) : 1; // { dg-message "in .constexpr. expansion" }
+// { dg-error "call to consteval function" "" { target *-*-* } .-1 }
   a3 += false ? (1 + bar (8)) : 1; // { dg-message "in .constexpr. expansion" }
+// { dg-error "call to consteval function" "" { target *-*-* } .-1 }
 
   __extension__ int a4 = false ?: (1 + bar (8)); // { dg-message "in .constexpr. expansion" }
+// { dg-error "call to consteval function" "" { target *-*-* } .-1 }
   __extension__ int a5 = true ?: (1 + bar (8)); // { dg-message "in .constexpr. expansion" }
+// { dg-error "call to consteval function" "" { target *-*-* } .-1 }
   int a6 = bar (2) ? 1 : 2; // { dg-message "in .constexpr. expansion" }
+// { dg-error "call to consteval function" "" { target *-*-* } .-1 }
   int a7 = bar (2) - 1 ? 1 : 2; // { dg-message "in .constexpr. expansion" }
+// { dg-error "call to consteval function" "" { target *-*-* } .-1 }
 }
diff --git a/gcc/testsuite/g++.dg/cpp2a/consteval36.C b/gcc/testsuite/g++.dg/cpp2a/consteval36.C
index 9c470e4b7d7..8e27f2e33c6 100644
--- a/gcc/testsuite/g++.dg/cpp2a/consteval36.C
+++ b/gcc/testsuite/g++.dg/cpp2a/consteval36.C
@@ -6,17 +6,17 @@ consteval int id (int i) { return i; }
 void
 g (int i)
 {
-  1 ? 1 : ((1 ? 1 : 1), id (i)); // { dg-error "'i' is not a constant expression" }
-  1 ? 1 : ((1 ? 1 : 1), id (i), 1); // { dg-error "'i' is not a constant expression" }
-  1 ? 1 : ((i ? 1 : 1), id (i), 1); // { dg-error "'i' is not a constant expression" }
-  1 ? 1 : ((1 ? i : 1), id (i), 1); // { dg-error "'i' is not a constant expression" }
-  1 ? 1 : ((1 ? 1 : i), id (i), 1); // { dg-error "'i' is not a constant expression" }
-  1 ? 1 : ((i ? -i : i), id (i), 1); // { dg-error "'i' is not a constant expression" }
-  1 ? 1 : ((1 ? 1 : id (i)), id (42), 1); // { dg-error "'i' is not a constant expression" }
-  1 ? 1 : ((1 ? 1 : id (42)), id (i)); // { dg-error "'i' is not a constant expression" }
-  1 ? 1 : ((1 ? 1 : id (42)), id (i), 1); // { dg-error "'i' is not a constant expression" }
-  id (i) ? 1 : ((1 ? 1 : 1), id (i)); // { dg-error "'i' is not a constant expression" }
-  1 ? 1 : ((1 ? 1 : id (i)), id (i)); // { dg-error "'i' is not a constant expression" }
-  1 ? id (i) : ((1 ? 1 : id (i)), id (i)); // { dg-error "'i' is not a constant expression" }
-  1 ? 1 : ((id (i) ? 1 : 1), id (i)); // { dg-error "'i' is not a constant expression" }
+  1 ? 1 : ((1 ? 1 : 1), id (i)); // { dg-error "call to consteval function|'i' is not a constant expression" }
+  1 ? 1 : ((1 ? 1 : 1), id (i), 1); // { dg-error "call to consteval function|'i' is not a constant expression" }
+  1 ? 1 : ((i ? 1 : 1), id (i), 1); // { dg-error "call to consteval function|'i' is not a constant expression" }
+  1 ? 1 : ((1 ? i : 1), id (i), 1); // { dg-error "call to consteval function|'i' is not a constant expression" }
+  1 ? 1 : ((1 ? 1 : i), id (i), 1); // { dg-error "call to consteval function|'i' is not a constant expression" }
+  1 ? 1 : ((i ? -i : i), id (i), 1); // { dg-error "call to consteval function|'i' is not a constant expression" }
+  1 ? 1 : ((1 ? 1 : id (i)), id (42), 1); // { dg-error "call to consteval function|'i' is not a constant expression" }
+  1 ? 1 : ((1 ? 1 : id (42)), id (i)); // { dg-error "call to consteval function|'i' is not a constant expression" }
+  1 ? 1 : ((1 ? 1 : id (42)), id (i), 1); // { dg-error "call to consteval function|'i' is not a constant expression" }
+  id (i) ? 1 : ((1 ? 1 : 1), id (i)); // { dg-error "call to consteval function|'i' is not a constant expression" }
+  1 ? 1 : ((1 ? 1 : id (i)), id (i)); // { dg-error "call to consteval function|'i' is not a constant expression" }
+  1 ? id (i) : ((1 ? 1 : id (i)), id (i)); // { dg-error "call to consteval function|'i' is not a constant expression" }
+  1 ? 1 : ((id (i) ? 1 : 1), id (i)); // { dg-error "call to consteval function|'i' is not a constant expression" }
 }
diff --git a/gcc/testsuite/g++.dg/cpp2a/consteval9.C b/gcc/testsuite/g++.dg/cpp2a/consteval9.C
index 051a3d4e355..ad882d51c9b 100644
--- a/gcc/testsuite/g++.dg/cpp2a/consteval9.C
+++ b/gcc/testsuite/g++.dg/cpp2a/consteval9.C
@@ -14,6 +14,7 @@ template <int N>
 void qux ()
 {
   int a = bar (N);	// { dg-message "in 'constexpr' expansion of 'bar\\(2\\)'" }
+// { dg-error "call to consteval function" "" { target *-*-* } .-1 }
 }
 
 // This function is not instantiated so NDR.
@@ -31,3 +32,4 @@ baz ()
 }
 
 int a = bar (2);	// { dg-message "in 'constexpr' expansion of 'bar\\(2\\)'" }
+// { dg-error "call to consteval function" "" { target *-*-* } .-1 }
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/gcc/testsuite/g++.dg/cpp2a/spaceship-synth9.C b/gcc/testsuite/g++.dg/cpp2a/spaceship-synth9.C
index 33b547d2b50..ecb46b016a6 100644
--- a/gcc/testsuite/g++.dg/cpp2a/spaceship-synth9.C
+++ b/gcc/testsuite/g++.dg/cpp2a/spaceship-synth9.C
@@ -22,6 +22,6 @@ struct Z: Y<int>
 int main()
 {
   X<char>() == X<char>();	// { dg-error "no match" }
-  X<int> x; x == x;		// { dg-error "x' is not usable in a constant expression" }
+  X<int> x; x == x;		// { dg-error "x' is not usable in a constant expression|call to consteval function" }
   Y<int>()  == Y<int>();	// { dg-warning "nodiscard" }
 }
diff --git a/libstdc++-v3/testsuite/18_support/comparisons/categories/zero_neg.cc b/libstdc++-v3/testsuite/18_support/comparisons/categories/zero_neg.cc
index 9d2115b3f4f..82f7cd54fba 100644
--- a/libstdc++-v3/testsuite/18_support/comparisons/categories/zero_neg.cc
+++ b/libstdc++-v3/testsuite/18_support/comparisons/categories/zero_neg.cc
@@ -52,3 +52,4 @@ test01()
 
 // { dg-prune-output "reinterpret_cast.* is not a constant expression" }
 // { dg-prune-output "cast from 'void.' is not allowed" }
+// { dg-prune-output "not a constant expression" }
diff --git a/libstdc++-v3/testsuite/std/format/string_neg.cc b/libstdc++-v3/testsuite/std/format/string_neg.cc
index 7a60ef8cf0e..69bcc736cff 100644
--- a/libstdc++-v3/testsuite/std/format/string_neg.cc
+++ b/libstdc++-v3/testsuite/std/format/string_neg.cc
@@ -2,5 +2,5 @@
 
 #include <format>
 
-auto s = std::format(" {9} ");
+auto s = std::format(" {9} "); // { dg-error "call to consteval function" }
 // { dg-error "invalid.arg.id" "" { target *-*-* } 0 }

base-commit: b9dbdefac626ba20222ca534b58f7e493d713b9a
-- 
2.42.0


^ permalink raw reply	[flat|nested] 19+ messages in thread

* Re: [PATCH v5] c++: implement P2564, consteval needs to propagate up [PR107687]
  2023-11-23 16:46               ` [PATCH v5] " Marek Polacek
@ 2023-11-30 23:34                 ` Jason Merrill
  2023-12-01 23:37                   ` [PATCH v6] " Marek Polacek
  0 siblings, 1 reply; 19+ messages in thread
From: Jason Merrill @ 2023-11-30 23:34 UTC (permalink / raw)
  To: Marek Polacek; +Cc: GCC Patches

On 11/23/23 11:46, Marek Polacek wrote:
> v5 greatly simplifies the code.

Indeed, it's much cleaner now.

> I still need a new ff_ flag to signal that we can return immediately
> after seeing an i-e expr.

That's still not clear to me:

> +      /* In turn, maybe promote the function we find ourselves in...  */
> +      if ((data->flags & ff_find_escalating_expr)
> +         && 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.  */
> +         && (e = cxx_constant_value (stmt, tf_none), e == error_mark_node))
> +       {
> +         /* Since we had to set DECL_ESCALATION_CHECKED_P before the walk,
> +            we call promote_function_to_consteval directly which doesn't
> +            check unchecked_immediate_escalating_function_p.  */
> +         if (current_function_decl)
> +           promote_function_to_consteval (current_function_decl);
> +         *walk_subtrees = 0;
> +         return stmt;
> +       }

This is the one use of ff_find_escalating_expr, and it seems redundant 
with the code immediately below, where we use complain (derived from 
ff_mce_false) to decide whether to return immediately.  Can we remove 
this hunk and the flag, and merge find_escalating_expr with 
cp_fold_immediate?

I think you want to walk the function body for three-ish reasons:
1) at EOF, to check for escalation
2) at EOF, to check for errors
3) at error time, to explain escalation

It's not clear to me that we need a flag to distinguish between them. 
When we encounter an immediate-escalating expression E:

A) if we're in an immediate-escalating function, escalate and return E 
(#1, #3).
B) otherwise, if we're diagnosing, error and continue (#2).
C) otherwise, return E (individual expression mce_unknown walk from 
constexpr.cc).

> @@ -1178,11 +1388,19 @@ cp_fold_r (tree *stmt_p, int *walk_subtrees, void *data_
> )
>           *walk_subtrees = 0;
>           /* Don't return yet, still need the cp_fold below.  */
>         }
> -      cp_fold_immediate_r (stmt_p, walk_subtrees, data);
> +      else
> +       cp_fold_immediate_r (stmt_p, walk_subtrees, data);
>      }
>  
>    *stmt_p = stmt = cp_fold (*stmt_p, data->flags);
>  
> +  /* For certain trees, like +foo(), the cp_fold below will remove the +,

s/below/above/?

> +/* We've stashed immediate-escalating functions.  Now see if they indeed
> +   ought to be promoted to consteval.  */
> +
> +void
> +process_pending_immediate_escalating_fns ()
> +{
> +  /* This will be null for -fno-immediate-escalation.  */
> +  if (!deferred_escalating_exprs)
> +    return;
> +
> +  for (auto e : *deferred_escalating_exprs)
> +    if (TREE_CODE (e) == FUNCTION_DECL && !DECL_ESCALATION_CHECKED_P (e))
> +      cp_fold_immediate (&DECL_SAVED_TREE (e), mce_false, e);
> +}
> +
> +/* We've escalated every function that could have been promoted to
> +   consteval.  Check that we are not taking the address of a consteval
> +   function.  */
> +
> +void
> +check_immediate_escalating_refs ()
> +{
> +  /* This will be null for -fno-immediate-escalation.  */
> +  if (!deferred_escalating_exprs)
> +    return;
> +
> +  for (auto ref : *deferred_escalating_exprs)
> +    {
> +      if (TREE_CODE (ref) == FUNCTION_DECL)
> +       continue;
> +      tree decl = (TREE_CODE (ref) == PTRMEM_CST
> +                  ? PTRMEM_CST_MEMBER (ref)
> +                  : TREE_OPERAND (ref, 0));
> +      if (DECL_IMMEDIATE_FUNCTION_P (decl))
> +       taking_address_of_imm_fn_error (ref, decl);
> +    }
> +
> +  deferred_escalating_exprs = nullptr;
>  }

Could these be merged, so you do a single loop of cp_fold_immediate over 
function bodies or non-function expressions?  I'd expect that to work.

Jason


^ permalink raw reply	[flat|nested] 19+ messages in thread

* [PATCH v6] c++: implement P2564, consteval needs to propagate up [PR107687]
  2023-11-30 23:34                 ` Jason Merrill
@ 2023-12-01 23:37                   ` Marek Polacek
  2023-12-02  0:43                     ` Jason Merrill
  0 siblings, 1 reply; 19+ messages in thread
From: Marek Polacek @ 2023-12-01 23:37 UTC (permalink / raw)
  To: Jason Merrill; +Cc: GCC Patches

On Thu, Nov 30, 2023 at 06:34:01PM -0500, Jason Merrill wrote:
> On 11/23/23 11:46, Marek Polacek wrote:
> > v5 greatly simplifies the code.
> 
> Indeed, it's much cleaner now.
> 
> > I still need a new ff_ flag to signal that we can return immediately
> > after seeing an i-e expr.
> 
> That's still not clear to me:
> 
> > +      /* In turn, maybe promote the function we find ourselves in...  */
> > +      if ((data->flags & ff_find_escalating_expr)
> > +         && 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.  */
> > +         && (e = cxx_constant_value (stmt, tf_none), e == error_mark_node))
> > +       {
> > +         /* Since we had to set DECL_ESCALATION_CHECKED_P before the walk,
> > +            we call promote_function_to_consteval directly which doesn't
> > +            check unchecked_immediate_escalating_function_p.  */
> > +         if (current_function_decl)
> > +           promote_function_to_consteval (current_function_decl);
> > +         *walk_subtrees = 0;
> > +         return stmt;
> > +       }
> 
> This is the one use of ff_find_escalating_expr, and it seems redundant with
> the code immediately below, where we use complain (derived from
> ff_mce_false) to decide whether to return immediately.  Can we remove this
> hunk and the flag, and merge find_escalating_expr with cp_fold_immediate?

Ah, that works!  Hopefully done now.
 
> I think you want to walk the function body for three-ish reasons:
> 1) at EOF, to check for escalation
> 2) at EOF, to check for errors
> 3) at error time, to explain escalation
> 
> It's not clear to me that we need a flag to distinguish between them. When
> we encounter an immediate-escalating expression E:
> 
> A) if we're in an immediate-escalating function, escalate and return E (#1,
> #3).
> B) otherwise, if we're diagnosing, error and continue (#2).
> C) otherwise, return E (individual expression mce_unknown walk from
> constexpr.cc).
> 
> > @@ -1178,11 +1388,19 @@ cp_fold_r (tree *stmt_p, int *walk_subtrees, void *data_
> > )
> >           *walk_subtrees = 0;
> >           /* Don't return yet, still need the cp_fold below.  */
> >         }
> > -      cp_fold_immediate_r (stmt_p, walk_subtrees, data);
> > +      else
> > +       cp_fold_immediate_r (stmt_p, walk_subtrees, data);
> >      }
> >    *stmt_p = stmt = cp_fold (*stmt_p, data->flags);
> > +  /* For certain trees, like +foo(), the cp_fold below will remove the +,
> 
> s/below/above/?

Fixed.
 
> > +/* We've stashed immediate-escalating functions.  Now see if they indeed
> > +   ought to be promoted to consteval.  */
> > +
> > +void
> > +process_pending_immediate_escalating_fns ()
> > +{
> > +  /* This will be null for -fno-immediate-escalation.  */
> > +  if (!deferred_escalating_exprs)
> > +    return;
> > +
> > +  for (auto e : *deferred_escalating_exprs)
> > +    if (TREE_CODE (e) == FUNCTION_DECL && !DECL_ESCALATION_CHECKED_P (e))
> > +      cp_fold_immediate (&DECL_SAVED_TREE (e), mce_false, e);
> > +}
> > +
> > +/* We've escalated every function that could have been promoted to
> > +   consteval.  Check that we are not taking the address of a consteval
> > +   function.  */
> > +
> > +void
> > +check_immediate_escalating_refs ()
> > +{
> > +  /* This will be null for -fno-immediate-escalation.  */
> > +  if (!deferred_escalating_exprs)
> > +    return;
> > +
> > +  for (auto ref : *deferred_escalating_exprs)
> > +    {
> > +      if (TREE_CODE (ref) == FUNCTION_DECL)
> > +       continue;
> > +      tree decl = (TREE_CODE (ref) == PTRMEM_CST
> > +                  ? PTRMEM_CST_MEMBER (ref)
> > +                  : TREE_OPERAND (ref, 0));
> > +      if (DECL_IMMEDIATE_FUNCTION_P (decl))
> > +       taking_address_of_imm_fn_error (ref, decl);
> > +    }
> > +
> > +  deferred_escalating_exprs = nullptr;
> >  }
> 
> Could these be merged, so you do a single loop of cp_fold_immediate over
> function bodies or non-function expressions?  I'd expect that to work.

We seem to walk the hash table in a random order so I can't use one loop,
otherwise we could hit &f before escalating f.  But there's not need for
two functions, so I've merged them into
process_and_check_pending_immediate_escalating_fns.

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:

w.C:11:11: error: call to consteval function 'f<int>(i)' is not a constant expression
   11 |         f (i);
      |         ~~^~~
w.C:11:11: error: 'i' is not a constant expression
w.C:6:22: 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); // id causes f<int> to be promoted to consteval
      |                    ~~^~~

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.

In v1, I addressed the delayed instantiation problem by instantiating
trees early, so that we can escalate functions right away.  That caused
a number of problems, and in certain cases, like consteval-prop3.C, it
can't work, because we need to wait till EOF to see the definition of
the function anyway.  Overeager instantiation tends to cause diagnostic
problems too.

In v2, I attempted to move the escalation to the gimplifier, at which
point all templates have been instantiated.  That attempt flopped,
however, because once we've gimplified a function, its body is discarded
and as a consequence, you can no longer evaluate a call to that function
which is required for escalating, which needs to decide if a call is
a constant expression or not.

Therefore, we have to perform the escalation before gimplifying, but
after instantiate_pending_templates.  That's not easy because we have
no way to walk all the trees.  In the v2 patch, I use two vectors: one
to store function decls that may become consteval, and another to
remember references to immediate-escalating functions.  Unfortunately
the latter must also stash functions that call immediate-escalating
functions.  Consider:

  int g(int i)
  {
    f<int>(i); // f is immediate-escalating
  }

where g itself is not immediate-escalating, but we have to make sure
that if f gets promoted to consteval, we give an error.

A new option, -fno-immediate-escalation, is provided to suppress
escalating functions.

v2 also adds a new flag, DECL_ESCALATION_CHECKED_P, so that we don't
escalate a function multiple times, and so that we can distinguish between
explicitly consteval functions and functions that have been promoted
to consteval.

In v3, I removed one of the new vectors and changed the other one
to a hash set.  This version also contains numerous cleanups.

v4 merges find_escalating_expr_r into cp_fold_immediate_r.  It also
adds a new optimization in cp_fold_function.

v5 greatly simplifies the code.

v6 simplifies the code further and removes an ff_ flag.

	PR c++/107687
	PR c++/110997

gcc/c-family/ChangeLog:

	* c-cppbuiltin.cc (c_cpp_builtins): Update __cpp_consteval.
	* c-opts.cc (c_common_post_options): Pre-C++20, unset
	flag_immediate_escalation.
	* c.opt (fimmediate-escalation): New option.

gcc/cp/ChangeLog:

	* call.cc (in_immediate_context): No longer static.
	* constexpr.cc (cxx_eval_call_expression): Adjust assert.
	* cp-gimplify.cc (deferred_escalating_exprs): New vec.
	(remember_escalating_expr): New.
	(enum fold_flags): Remove ff_fold_immediate.
	(immediate_escalating_function_p): New.
	(unchecked_immediate_escalating_function_p): New.
	(promote_function_to_consteval): New.
	(maybe_promote_function_to_consteval): New.
	(cp_fold_immediate): Move above.  Return non-null if any errors were
	emitted.
	(maybe_explain_promoted_consteval): New.
	(cp_gimplify_expr) <case CALL_EXPR>: Assert we've handled all
	immediate invocations.
	(taking_address_of_imm_fn_error): New.
	(cp_fold_immediate_r): Merge ADDR_EXPR and PTRMEM_CST cases.  Implement
	P2564 - promoting functions to consteval.
	<case CALL_EXPR>: Implement P2564 - promoting functions to consteval.
	(cp_fold_r): If an expression turns into a CALL_EXPR after cp_fold,
	call cp_fold_immediate_r on the CALL_EXPR.
	(cp_fold_function): Set DECL_ESCALATION_CHECKED_P if
	deferred_escalating_exprs does not contain current_function_decl.
	(maybe_store_immediate_escalating_fn): New.
	(process_and_check_pending_immediate_escalating_fns): New.
	* cp-tree.h (struct lang_decl_fn): Add escalated_p bit-field.
	(DECL_ESCALATION_CHECKED_P): New.
	(immediate_invocation_p): Declare.
	(maybe_store_immediate_escalating_fn): Likewise.
	(process_pending_immediate_escalating_fns): Likewise.
	* decl.cc (finish_function): Call maybe_store_immediate_escalating_fn.
	* decl2.cc (c_parse_final_cleanups): Set at_eof to 2 after all
	templates have been instantiated; and to 3 at the end of the function.
	Call process_pending_immediate_escalating_fns.
	* error.cc (dump_template_bindings): Check at_eof against an updated
	value.
	* module.cc (trees_out::lang_decl_bools): Stream escalated_p.
	(trees_in::lang_decl_bools): Likewise.
	* pt.cc (push_tinst_level_loc): Set at_eof to 3, not 2.
	* typeck.cc (cp_build_addr_expr_1): Don't check
	DECL_IMMEDIATE_FUNCTION_P.

gcc/ChangeLog:

	* doc/invoke.texi: Document -fno-immediate-escalation.

libstdc++-v3/ChangeLog:

	* testsuite/18_support/comparisons/categories/zero_neg.cc: Add
	dg-prune-output.
	* testsuite/std/format/string_neg.cc: Add dg-error.

gcc/testsuite/ChangeLog:

	* 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/consteval-memfn1.C: Add dg-error.
	* g++.dg/cpp2a/consteval11.C: Likewise.
	* g++.dg/cpp2a/consteval3.C: Adjust dg-error.
	* g++.dg/cpp2a/consteval34.C: Add dg-error.
	* g++.dg/cpp2a/consteval9.C: Likewise.
	* g++.dg/cpp2a/feat-cxx2a.C: Adjust expected value of __cpp_consteval.
	* g++.dg/cpp2a/spaceship-synth9.C: Adjust dg-error.
	* 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-prop18.C: New test.
	* g++.dg/cpp2a/consteval-prop19.C: New test.
	* g++.dg/cpp2a/consteval-prop20.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/c-family/c-opts.cc                        |   5 +
 gcc/c-family/c.opt                            |   4 +
 gcc/cp/call.cc                                |   2 +-
 gcc/cp/constexpr.cc                           |   4 +-
 gcc/cp/cp-gimplify.cc                         | 384 +++++++++++++++---
 gcc/cp/cp-tree.h                              |  20 +-
 gcc/cp/decl.cc                                |   5 +-
 gcc/cp/decl2.cc                               |  16 +-
 gcc/cp/error.cc                               |   2 +-
 gcc/cp/module.cc                              |   4 +
 gcc/cp/pt.cc                                  |   2 +-
 gcc/cp/typeck.cc                              |   6 +-
 gcc/doc/invoke.texi                           |  34 ++
 gcc/testsuite/g++.dg/cpp23/consteval-if10.C   |   7 +-
 gcc/testsuite/g++.dg/cpp23/consteval-if2.C    |  14 +-
 gcc/testsuite/g++.dg/cpp23/feat-cxx2b.C       |   4 +-
 gcc/testsuite/g++.dg/cpp26/feat-cxx26.C       |   4 +-
 gcc/testsuite/g++.dg/cpp2a/consteval-memfn1.C |   3 +
 gcc/testsuite/g++.dg/cpp2a/consteval-prop1.C  | 169 ++++++++
 gcc/testsuite/g++.dg/cpp2a/consteval-prop10.C |  41 ++
 gcc/testsuite/g++.dg/cpp2a/consteval-prop11.C |  49 +++
 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 |  78 ++++
 gcc/testsuite/g++.dg/cpp2a/consteval-prop15.C | 107 +++++
 gcc/testsuite/g++.dg/cpp2a/consteval-prop16.C |  73 ++++
 gcc/testsuite/g++.dg/cpp2a/consteval-prop17.C |  17 +
 gcc/testsuite/g++.dg/cpp2a/consteval-prop18.C |  20 +
 gcc/testsuite/g++.dg/cpp2a/consteval-prop19.C |   7 +
 gcc/testsuite/g++.dg/cpp2a/consteval-prop2.C  |  90 ++++
 gcc/testsuite/g++.dg/cpp2a/consteval-prop20.C |  21 +
 gcc/testsuite/g++.dg/cpp2a/consteval-prop3.C  |  27 ++
 gcc/testsuite/g++.dg/cpp2a/consteval-prop4.C  |  30 ++
 gcc/testsuite/g++.dg/cpp2a/consteval-prop5.C  |  27 ++
 gcc/testsuite/g++.dg/cpp2a/consteval-prop6.C  |  59 +++
 gcc/testsuite/g++.dg/cpp2a/consteval-prop7.C  |  76 ++++
 gcc/testsuite/g++.dg/cpp2a/consteval-prop8.C  |  82 ++++
 gcc/testsuite/g++.dg/cpp2a/consteval-prop9.C  |  67 +++
 gcc/testsuite/g++.dg/cpp2a/consteval11.C      |  18 +
 gcc/testsuite/g++.dg/cpp2a/consteval3.C       |   4 +-
 gcc/testsuite/g++.dg/cpp2a/consteval34.C      |   8 +
 gcc/testsuite/g++.dg/cpp2a/consteval36.C      |  26 +-
 gcc/testsuite/g++.dg/cpp2a/consteval9.C       |   2 +
 gcc/testsuite/g++.dg/cpp2a/feat-cxx2a.C       |   4 +-
 gcc/testsuite/g++.dg/cpp2a/spaceship-synth9.C |   2 +-
 .../comparisons/categories/zero_neg.cc        |   1 +
 .../testsuite/std/format/string_neg.cc        |   2 +-
 48 files changed, 1566 insertions(+), 116 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-prop18.C
 create mode 100644 gcc/testsuite/g++.dg/cpp2a/consteval-prop19.C
 create mode 100644 gcc/testsuite/g++.dg/cpp2a/consteval-prop2.C
 create mode 100644 gcc/testsuite/g++.dg/cpp2a/consteval-prop20.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

diff --git a/gcc/c-family/c-cppbuiltin.cc b/gcc/c-family/c-cppbuiltin.cc
index e536429fa4c..2d1249f29ed 100644
--- a/gcc/c-family/c-cppbuiltin.cc
+++ b/gcc/c-family/c-cppbuiltin.cc
@@ -1059,7 +1059,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/c-family/c-opts.cc b/gcc/c-family/c-opts.cc
index d7faff10d66..d484ecfdfdb 100644
--- a/gcc/c-family/c-opts.cc
+++ b/gcc/c-family/c-opts.cc
@@ -1139,6 +1139,11 @@ c_common_post_options (const char **pfilename)
   if (cxx_dialect >= cxx20 || flag_concepts_ts)
     flag_concepts = 1;
 
+  /* -fimmediate-escalation has no effect when immediate functions are not
+     supported.  */
+  if (flag_immediate_escalation && cxx_dialect < cxx20)
+    flag_immediate_escalation = 0;
+
   if (num_in_fnames > 1)
     error ("too many filenames given; type %<%s %s%> for usage",
 	   progname, "--help");
diff --git a/gcc/c-family/c.opt b/gcc/c-family/c.opt
index ab44a6da66a..3706505f8bf 100644
--- a/gcc/c-family/c.opt
+++ b/gcc/c-family/c.opt
@@ -1898,6 +1898,10 @@ fhuge-objects
 C++ ObjC++ WarnRemoved
 No longer supported.
 
+fimmediate-escalation
+C++ ObjC++ Var(flag_immediate_escalation) Init(1)
+Implement P2564 for consteval propagation.
+
 fimplement-inlines
 C++ ObjC++ Var(flag_implement_inlines) Init(1)
 Export functions even if they can be inlined.
diff --git a/gcc/cp/call.cc b/gcc/cp/call.cc
index ae0decd87f1..c7efc5b077a 100644
--- a/gcc/cp/call.cc
+++ b/gcc/cp/call.cc
@@ -9742,7 +9742,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
diff --git a/gcc/cp/constexpr.cc b/gcc/cp/constexpr.cc
index b17e176aded..3fe6e35be06 100644
--- a/gcc/cp/constexpr.cc
+++ b/gcc/cp/constexpr.cc
@@ -3128,11 +3128,11 @@ cxx_eval_call_expression (const constexpr_ctx *ctx, tree t,
 	/* OK */;
       else if (!DECL_SAVED_TREE (fun))
 	{
-	  /* When at_eof >= 2, cgraph has started throwing away
+	  /* When at_eof >= 3, cgraph has started throwing away
 	     DECL_SAVED_TREE, so fail quietly.  FIXME we get here because of
 	     late code generation for VEC_INIT_EXPR, which needs to be
 	     completely reconsidered.  */
-	  gcc_assert (at_eof >= 2 && ctx->quiet);
+	  gcc_assert (at_eof >= 3 && ctx->quiet);
 	  *non_constant_p = true;
 	}
       else if (tree copy = get_fundef_copy (new_call.fundef))
diff --git a/gcc/cp/cp-gimplify.cc b/gcc/cp/cp-gimplify.cc
index 795c811471d..c1adaed02e5 100644
--- a/gcc/cp/cp-gimplify.cc
+++ b/gcc/cp/cp-gimplify.cc
@@ -43,6 +43,21 @@ along with GCC; see the file COPYING3.  If not see
 #include "omp-general.h"
 #include "opts.h"
 
+/* Keep track of forward references to immediate-escalating functions in
+   case they become consteval.  This vector contains ADDR_EXPRs and
+   PTRMEM_CSTs; it also stores FUNCTION_DECLs that had an escalating
+   function call in them, to check that they can be evaluated to a constant,
+   and immediate-escalating functions that may become consteval.  */
+static GTY(()) hash_set<tree> *deferred_escalating_exprs;
+
+static void
+remember_escalating_expr (tree t)
+{
+  if (!deferred_escalating_exprs)
+    deferred_escalating_exprs = hash_set<tree>::create_ggc (37);
+  deferred_escalating_exprs->add (t);
+}
+
 /* Flags for cp_fold and cp_fold_r.  */
 
 enum fold_flags {
@@ -53,8 +68,6 @@ enum fold_flags {
      definitely not in a manifestly constant-evaluated
      context.  */
   ff_mce_false = 1 << 1,
-  /* Whether we're being called from cp_fold_immediate.  */
-  ff_fold_immediate = 1 << 2,
 };
 
 using fold_flags_t = int;
@@ -72,6 +85,7 @@ static tree cp_genericize_r (tree *, int *, void *);
 static tree cp_fold_r (tree *, int *, void *);
 static void cp_genericize_tree (tree*, bool);
 static tree cp_fold (tree, fold_flags_t);
+static tree cp_fold_immediate_r (tree *, int *, void *);
 
 /* Genericize a TRY_BLOCK.  */
 
@@ -428,6 +442,120 @@ lvalue_has_side_effects (tree e)
     return TREE_SIDE_EFFECTS (e);
 }
 
+/* Return true if FN is an immediate-escalating function.  */
+
+static bool
+immediate_escalating_function_p (tree fn)
+{
+  if (!fn || !flag_immediate_escalation)
+    return false;
+
+  gcc_checking_assert (TREE_CODE (fn) == FUNCTION_DECL);
+
+  if (DECL_IMMEDIATE_FUNCTION_P (fn))
+    return false;
+
+  /* An immediate-escalating function is
+      -- the call operator of a lambda that is not declared with the consteval
+	 specifier  */
+  if (LAMBDA_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))
+    return true;
+  /* -- a function that results from the instantiation of a templated entity
+	defined with the constexpr specifier.  */
+  return is_instantiation_of_constexpr (fn);
+}
+
+/* Return true if FN is an immediate-escalating function that has not been
+   checked for escalating expressions..  */
+
+static bool
+unchecked_immediate_escalating_function_p (tree fn)
+{
+  return (immediate_escalating_function_p (fn)
+	  && !DECL_ESCALATION_CHECKED_P (fn));
+}
+
+/* Promote FN to an immediate function, including its clones.  */
+
+static void
+promote_function_to_consteval (tree fn)
+{
+  SET_DECL_IMMEDIATE_FUNCTION_P (fn);
+  DECL_ESCALATION_CHECKED_P (fn) = true;
+  tree clone;
+  FOR_EACH_CLONE (clone, fn)
+    {
+      SET_DECL_IMMEDIATE_FUNCTION_P (clone);
+      DECL_ESCALATION_CHECKED_P (clone) = true;
+    }
+}
+
+/* Promote FN to an immediate function, including its clones, if it is
+   an immediate-escalating function.  Return true if we did promote;
+   false otherwise.  */
+
+static bool
+maybe_promote_function_to_consteval (tree fn)
+{
+  if (unchecked_immediate_escalating_function_p (fn))
+    {
+      promote_function_to_consteval (fn);
+      return true;
+    }
+
+  return false;
+}
+
+/* A wrapper around cp_fold_immediate_r.  Return a non-null tree if
+   we found a non-constant immediate function, or taking the address
+   of an immediate function.  */
+
+tree
+cp_fold_immediate (tree *tp, mce_value manifestly_const_eval,
+		   tree decl /*= current_function_decl*/)
+{
+  if (cxx_dialect <= cxx17)
+    return NULL_TREE;
+
+  temp_override<tree> cfd (current_function_decl, decl);
+
+  fold_flags_t flags = ff_none;
+  if (manifestly_const_eval == mce_false)
+    flags |= ff_mce_false;
+
+  cp_fold_data data (flags);
+  int save_errorcount = errorcount;
+  tree r = cp_walk_tree_without_duplicates (tp, cp_fold_immediate_r, &data);
+  if (errorcount > save_errorcount)
+    return integer_one_node;
+  return r;
+}
+
+/* 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)
+{
+  if (DECL_ESCALATION_CHECKED_P (fn))
+    {
+      /* See if we can figure out what made the function consteval.  */
+      tree x = cp_fold_immediate (&DECL_SAVED_TREE (fn), mce_unknown, NULL_TREE);
+      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.  */
 
@@ -746,7 +874,9 @@ cp_gimplify_expr (tree *expr_p, gimple_seq *pre_p, gimple_seq *post_p)
       if (ret != GS_ERROR)
 	{
 	  tree decl = cp_get_callee_fndecl_nofold (*expr_p);
-	  if (decl && fndecl_built_in_p (decl, BUILT_IN_FRONTEND))
+	  if (!decl)
+	    break;
+	  if (fndecl_built_in_p (decl, BUILT_IN_FRONTEND))
 	    switch (DECL_FE_FUNCTION_CODE (decl))
 	      {
 	      case CP_BUILT_IN_IS_CONSTANT_EVALUATED:
@@ -771,10 +901,12 @@ cp_gimplify_expr (tree *expr_p, gimple_seq *pre_p, gimple_seq *post_p)
 	      default:
 		break;
 	      }
-	  else if (decl
-		   && fndecl_built_in_p (decl, BUILT_IN_CLZG, BUILT_IN_CTZG))
+	  else if (fndecl_built_in_p (decl, BUILT_IN_CLZG, BUILT_IN_CTZG))
 	    ret = (enum gimplify_status) c_gimplify_expr (expr_p, pre_p,
 							  post_p);
+	  else
+	    /* All consteval functions should have been processed by now.  */
+	    gcc_checking_assert (!immediate_invocation_p (decl));
 	}
       break;
 
@@ -1035,6 +1167,20 @@ struct cp_genericize_data
   bool handle_invisiref_parm_p;
 };
 
+/* Emit an error about taking the address of an immediate function.
+   EXPR is the whole expression; DECL is the immediate function.  */
+
+static void
+taking_address_of_imm_fn_error (tree expr, tree decl)
+{
+  auto_diagnostic_group d;
+  const location_t loc = (TREE_CODE (expr) == PTRMEM_CST
+			  ? PTRMEM_CST_LOCATION (expr)
+			  : EXPR_LOCATION (expr));
+  error_at (loc, "taking address of an immediate function %qD", decl);
+  maybe_explain_promoted_consteval (loc, decl);
+}
+
 /* A subroutine of cp_fold_r to handle immediate functions.  */
 
 static tree
@@ -1045,90 +1191,138 @@ cp_fold_immediate_r (tree *stmt_p, int *walk_subtrees, void *data_)
   /* The purpose of this is not to emit errors for mce_unknown.  */
   const tsubst_flags_t complain = (data->flags & ff_mce_false
 				   ? tf_error : tf_none);
+  const tree_code code = TREE_CODE (stmt);
 
   /* No need to look into types or unevaluated operands.
      NB: This affects cp_fold_r as well.  */
-  if (TYPE_P (stmt) || unevaluated_p (TREE_CODE (stmt)))
+  if (TYPE_P (stmt) || unevaluated_p (code) || cp_unevaluated_operand)
     {
       *walk_subtrees = 0;
       return NULL_TREE;
     }
 
-  switch (TREE_CODE (stmt))
-    {
-    case PTRMEM_CST:
-      if (TREE_CODE (PTRMEM_CST_MEMBER (stmt)) == FUNCTION_DECL
-	  && DECL_IMMEDIATE_FUNCTION_P (PTRMEM_CST_MEMBER (stmt)))
-	{
-	  if (!data->pset.add (stmt) && (complain & tf_error))
-	    {
-	      error_at (PTRMEM_CST_LOCATION (stmt),
-			"taking address of an immediate function %qD",
-			PTRMEM_CST_MEMBER (stmt));
-	      *stmt_p = build_zero_cst (TREE_TYPE (stmt));
-	    }
-	  return error_mark_node;
-	}
-      break;
+  tree decl = NULL_TREE;
+  bool call_p = false;
 
-    /* Expand immediate invocations.  */
+  /* We are looking for &fn or fn().  */
+  switch (code)
+    {
     case CALL_EXPR:
     case AGGR_INIT_EXPR:
       if (tree fn = cp_get_callee (stmt))
 	if (TREE_CODE (fn) != ADDR_EXPR || ADDR_EXPR_DENOTES_CALL_P (fn))
-	  if (tree fndecl = cp_get_fndecl_from_callee (fn, /*fold*/false))
-	    if (DECL_IMMEDIATE_FUNCTION_P (fndecl))
-	      {
-		stmt = cxx_constant_value (stmt, complain);
-		if (stmt == error_mark_node)
-		  {
-		    if (complain & tf_error)
-		      *stmt_p = error_mark_node;
-		    return error_mark_node;
-		  }
-		*stmt_p = stmt;
-	      }
+	  decl = cp_get_fndecl_from_callee (fn, /*fold*/false);
+      call_p = true;
+      break;
+    case PTRMEM_CST:
+      decl = PTRMEM_CST_MEMBER (stmt);
       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))
-	{
-	  if (complain & tf_error)
-	    {
-	      error_at (EXPR_LOCATION (stmt),
-			"taking address of an immediate function %qD",
-			TREE_OPERAND (stmt, 0));
-	      *stmt_p = build_zero_cst (TREE_TYPE (stmt));
-	    }
-	  return error_mark_node;
-	}
+      if (!ADDR_EXPR_DENOTES_CALL_P (stmt))
+	decl = TREE_OPERAND (stmt, 0);
       break;
-
     default:
-      break;
+      return NULL_TREE;
     }
 
-  return NULL_TREE;
-}
+  if (!decl || TREE_CODE (decl) != FUNCTION_DECL)
+    return NULL_TREE;
 
-/* A wrapper around cp_fold_immediate_r.  Return true if we found
-   a non-constant immediate function, or taking the address of an
-   immediate function.  */
+  /* Fully escalate once all templates have been instantiated.  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 (at_eof > 1 && unchecked_immediate_escalating_function_p (decl))
+    {
+      /* Set before the actual walk to avoid endless recursion.  */
+      DECL_ESCALATION_CHECKED_P (decl) = true;
+      /* We're only looking for the first escalating expression.  Let us not
+	 walk more trees than necessary, hence mce_unknown.  */
+      cp_fold_immediate (&DECL_SAVED_TREE (decl), mce_unknown, decl);
+    }
 
-bool
-cp_fold_immediate (tree *tp, mce_value manifestly_const_eval)
-{
-  if (cxx_dialect <= cxx17)
-    return false;
+  /* [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."
 
-  fold_flags_t flags = ff_fold_immediate;
-  if (manifestly_const_eval == mce_false)
-    flags |= ff_mce_false;
+     If we are in an immediate-escalating function, the immediate-escalating
+     expression or conversion makes it an immediate function.  So STMT does
+     not need to produce a constant expression.  */
+  if (DECL_IMMEDIATE_FUNCTION_P (decl))
+    {
+      tree e = cxx_constant_value (stmt, tf_none);
+      if (e == error_mark_node)
+	{
+	  /* This takes care of
+	      template <typename T>
+	      constexpr int f(T t)
+	      {
+		return id(t);
+	      }
+	    where id (consteval) causes f<int> to be promoted.  */
+	  if (maybe_promote_function_to_consteval (current_function_decl))
+	    return NULL_TREE;
+	  /* If we're not complaining, we're either recursing when escalating,
+	     or just looking for the i-e expression.  */
+	  if (!(complain & tf_error))
+	    {
+	      /* Since we had to set DECL_ESCALATION_CHECKED_P before the walk,
+		 we call promote_function_to_consteval directly which doesn't
+		 check unchecked_immediate_escalating_function_p.  */
+	      if (current_function_decl)
+		promote_function_to_consteval (current_function_decl);
+	    }
+	  else if (!in_immediate_context ())
+	    {
+	      if (call_p)
+		{
+		  auto_diagnostic_group d;
+		  location_t loc = cp_expr_loc_or_input_loc (stmt);
+		  error_at (loc, "call to consteval function %qE is "
+			    "not a constant expression", stmt);
+		  /* Explain why it's not a constant expression.  */
+		  *stmt_p = cxx_constant_value (stmt, complain);
+		  maybe_explain_promoted_consteval (loc, decl);
+		}
+	      else if (!data->pset.add (stmt))
+		{
+		  taking_address_of_imm_fn_error (stmt, decl);
+		  *stmt_p = build_zero_cst (TREE_TYPE (stmt));
+		}
+	      /* If we're giving hard errors, continue the walk rather than
+		 bailing out after the first error.  */
+	      return NULL_TREE;
+	    }
+	  *walk_subtrees = 0;
+	  return stmt;
+	}
+      /* We've evaluated the consteval function call.  */
+      if (call_p)
+	*stmt_p = e;
+    }
+  /* We've encountered a function call that may turn out to be consteval
+     later.  Store its caller so that we can ensure that the call is
+     a constant expression.  */
+  else if (unchecked_immediate_escalating_function_p (decl))
+    {
+      /* Make sure we're not inserting new elements while walking
+	 the deferred_escalating_exprs hash table; if we are, it's
+	 likely that a function wasn't properly marked checked for
+	 i-e expressions.  */
+      gcc_checking_assert (at_eof <= 1);
+      if (current_function_decl)
+	remember_escalating_expr (current_function_decl);
+      /* auto p = &f<int>; in the global scope won't be ensconced in
+	 a function we could store for later at this point.  (If there's
+	 no c_f_d at this point and we're dealing with a call, we should
+	 see the call when cp_fold_function __static_i_and_d.)  */
+      else if (!call_p)
+	remember_escalating_expr (stmt);
+    }
 
-  cp_fold_data data (flags);
-  return !!cp_walk_tree_without_duplicates (tp, cp_fold_immediate_r, &data);
+  return NULL_TREE;
 }
 
 /* Perform any pre-gimplification folding of C++ front end trees to
@@ -1178,11 +1372,19 @@ cp_fold_r (tree *stmt_p, int *walk_subtrees, void *data_)
 	  *walk_subtrees = 0;
 	  /* Don't return yet, still need the cp_fold below.  */
 	}
-      cp_fold_immediate_r (stmt_p, walk_subtrees, data);
+      else
+	cp_fold_immediate_r (stmt_p, walk_subtrees, data);
     }
 
   *stmt_p = stmt = cp_fold (*stmt_p, data->flags);
 
+  /* For certain trees, like +foo(), the cp_fold above will remove the +,
+     and the subsequent tree walk would go straight down to the CALL_EXPR's
+     operands, meaning that cp_fold_immediate_r would never see the
+     CALL_EXPR.  Ew :(.  */
+  if (TREE_CODE (stmt) == CALL_EXPR && code != CALL_EXPR)
+    cp_fold_immediate_r (stmt_p, walk_subtrees, data);
+
   if (data->pset.add (stmt))
     {
       /* Don't walk subtrees of stmts we've already walked once, otherwise
@@ -1304,6 +1506,54 @@ cp_fold_function (tree fndecl)
      pass ff_mce_false.  */
   cp_fold_data data (ff_genericize | ff_mce_false);
   cp_walk_tree (&DECL_SAVED_TREE (fndecl), cp_fold_r, &data, NULL);
+
+  /* This is merely an optimization: if FNDECL has no i-e expressions,
+     we'll not save c_f_d, and we can safely say that FNDECL will not
+     be promoted to consteval.  */
+  if (deferred_escalating_exprs
+      && !deferred_escalating_exprs->contains (current_function_decl))
+    DECL_ESCALATION_CHECKED_P (fndecl) = true;
+}
+
+/* FN is not a consteval function, but may become one.  Remember to
+   escalate it after all pending templates have been instantiated.  */
+
+void
+maybe_store_immediate_escalating_fn (tree fn)
+{
+  if (unchecked_immediate_escalating_function_p (fn))
+    remember_escalating_expr (fn);
+}
+
+/* We've stashed immediate-escalating functions.  Now see if they indeed
+   ought to be promoted to consteval.  */
+
+void
+process_and_check_pending_immediate_escalating_fns ()
+{
+  /* This will be null for -fno-immediate-escalation.  */
+  if (!deferred_escalating_exprs)
+    return;
+
+  for (auto e : *deferred_escalating_exprs)
+    if (TREE_CODE (e) == FUNCTION_DECL && !DECL_ESCALATION_CHECKED_P (e))
+      cp_fold_immediate (&DECL_SAVED_TREE (e), mce_false, e);
+
+  /* We've escalated every function that could have been promoted to
+     consteval.  Check that we are not taking the address of a consteval
+     function.  */
+  for (auto e : *deferred_escalating_exprs)
+    {
+      if (TREE_CODE (e) == FUNCTION_DECL)
+	continue;
+      tree decl = (TREE_CODE (e) == PTRMEM_CST
+		   ? PTRMEM_CST_MEMBER (e)
+		   : TREE_OPERAND (e, 0));
+      if (DECL_IMMEDIATE_FUNCTION_P (decl))
+	taking_address_of_imm_fn_error (e, decl);
+    }
+
+  deferred_escalating_exprs = nullptr;
 }
 
 /* Turn SPACESHIP_EXPR EXPR into GENERIC.  */
diff --git a/gcc/cp/cp-tree.h b/gcc/cp/cp-tree.h
index b9adc1742ce..c5ba60bdd5b 100644
--- a/gcc/cp/cp-tree.h
+++ b/gcc/cp/cp-tree.h
@@ -2946,8 +2946,9 @@ struct GTY(()) lang_decl_fn {
   unsigned maybe_deleted : 1;
   unsigned coroutine_p : 1;
   unsigned implicit_constexpr : 1;
+  unsigned escalated_p : 1;
 
-  unsigned spare : 9;
+  unsigned spare : 8;
 
   /* 32-bits padding on 64-bit host.  */
 
@@ -3399,6 +3400,14 @@ struct GTY(()) lang_decl {
 #define DECL_MAYBE_DELETED(NODE) \
   (LANG_DECL_FN_CHECK (NODE)->maybe_deleted)
 
+/* Nonzero for FUNCTION_DECL means that this function's body has been
+   checked for immediate-escalating expressions and maybe promoted.  It
+   does *not* mean the function is consteval.  It must not be set in
+   a function that was marked consteval by the user, so that we can
+   distinguish between explicitly consteval functions and promoted consteval
+   functions.  */
+#define DECL_ESCALATION_CHECKED_P(NODE) (LANG_DECL_FN_CHECK (NODE)->escalated_p)
+
 /* True (in a FUNCTION_DECL) if NODE is a virtual function that is an
    invalid overrider for a function from a base class.  Once we have
    complained about an invalid overrider we avoid complaining about it
@@ -5882,7 +5891,8 @@ extern GTY(()) vec<tree, va_gc> *keyed_classes;
 
 \f
 /* Nonzero if we're done parsing and into end-of-file activities.
-   Two if we're done with front-end processing.  */
+   2 if all templates have been instantiated.
+   3 if we're done with front-end processing.  */
 
 extern int at_eof;
 
@@ -6774,6 +6784,7 @@ 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 tree build_cxx_call			(tree, int, tree *,
 						 tsubst_flags_t,
 						 tree = NULL_TREE);
@@ -8417,7 +8428,10 @@ extern tree process_stmt_assume_attribute	(tree, tree, location_t);
 extern bool simple_empty_class_p		(tree, tree, tree_code);
 extern tree fold_builtin_source_location	(const_tree);
 extern tree get_source_location_impl_type	();
-extern bool cp_fold_immediate			(tree *, mce_value);
+extern tree cp_fold_immediate			(tree *, mce_value,
+						 tree = current_function_decl);
+extern void maybe_store_immediate_escalating_fn	(tree);
+extern void process_and_check_pending_immediate_escalating_fns ();
 
 /* in name-lookup.cc */
 extern tree strip_using_decl                    (tree);
diff --git a/gcc/cp/decl.cc b/gcc/cp/decl.cc
index 4b685270097..e4f37923418 100644
--- a/gcc/cp/decl.cc
+++ b/gcc/cp/decl.cc
@@ -18441,7 +18441,10 @@ finish_function (bool inline_p)
   if (!processing_template_decl
       && !DECL_IMMEDIATE_FUNCTION_P (fndecl)
       && !DECL_OMP_DECLARE_REDUCTION_P (fndecl))
-    cp_fold_function (fndecl);
+    {
+      cp_fold_function (fndecl);
+      maybe_store_immediate_escalating_fn (fndecl);
+    }
 
   /* Set up the named return value optimization, if we can.  Candidate
      variables are selected in check_return_expr.  */
diff --git a/gcc/cp/decl2.cc b/gcc/cp/decl2.cc
index 9e666e5eece..bee84879023 100644
--- a/gcc/cp/decl2.cc
+++ b/gcc/cp/decl2.cc
@@ -169,7 +169,9 @@ typedef hash_map<unsigned/*Priority*/, tree/*List*/,
    one for init.  The fini table is only ever used when !cxa_atexit.  */
 static GTY(()) priority_map_t *static_init_fini_fns[2];
 
-/* Nonzero if we're done parsing and into end-of-file activities.  */
+/* Nonzero if we're done parsing and into end-of-file activities.
+   2 if all templates have been instantiated.
+   3 if we're done with front-end processing.  */
 
 int at_eof;
 
@@ -4987,6 +4989,7 @@ c_parse_final_cleanups (void)
   tree decl;
 
   locus_at_end_of_parsing = input_location;
+  /* We're done parsing.  */
   at_eof = 1;
 
   /* Bad parse errors.  Just forget about it.  */
@@ -5252,6 +5255,9 @@ c_parse_final_cleanups (void)
 	reconsider = true;
     }
 
+  /* All templates have been instantiated.  */
+  at_eof = 2;
+
   void *module_cookie = finish_module_processing (parse_in);
 
   lower_var_init ();
@@ -5294,7 +5300,11 @@ c_parse_final_cleanups (void)
   if (static_init_fini_fns[true])
     for (auto iter : *static_init_fini_fns[true])
       iter.second = nreverse (iter.second);
-  
+
+  /* Now we've instantiated all templates.  Now we can escalate the functions
+     we squirreled away earlier.  */
+  process_and_check_pending_immediate_escalating_fns ();
+
   /* Then, do the Objective-C stuff.  This is where all the
      Objective-C module stuff gets generated (symtab,
      class/protocol/selector lists etc).  This must be done after C++
@@ -5376,7 +5386,7 @@ c_parse_final_cleanups (void)
   timevar_start (TV_PHASE_PARSING);
 
   /* Indicate that we're done with front end processing.  */
-  at_eof = 2;
+  at_eof = 3;
 }
 
 /* Perform any post compilation-proper cleanups for the C++ front-end.
diff --git a/gcc/cp/error.cc b/gcc/cp/error.cc
index 785909c362a..3b1b5de5ea4 100644
--- a/gcc/cp/error.cc
+++ b/gcc/cp/error.cc
@@ -478,7 +478,7 @@ dump_template_bindings (cxx_pretty_printer *pp, tree parms, tree args,
 
   /* Don't try to do this once cgraph starts throwing away front-end
      information.  */
-  if (at_eof >= 2)
+  if (at_eof >= 3)
     return;
 
   FOR_EACH_VEC_SAFE_ELT (typenames, i, t)
diff --git a/gcc/cp/module.cc b/gcc/cp/module.cc
index 33fcf396875..1b57fbe2124 100644
--- a/gcc/cp/module.cc
+++ b/gcc/cp/module.cc
@@ -5683,6 +5683,8 @@ trees_out::lang_decl_bools (tree t)
       WB (lang->u.fn.has_dependent_explicit_spec_p);
       WB (lang->u.fn.immediate_fn_p);
       WB (lang->u.fn.maybe_deleted);
+      WB (lang->u.fn.escalated_p);
+      /* We do not stream lang->u.fn.implicit_constexpr.  */
       goto lds_min;
 
     case lds_decomp:  /* lang_decl_decomp.  */
@@ -5751,6 +5753,8 @@ trees_in::lang_decl_bools (tree t)
       RB (lang->u.fn.has_dependent_explicit_spec_p);
       RB (lang->u.fn.immediate_fn_p);
       RB (lang->u.fn.maybe_deleted);
+      RB (lang->u.fn.escalated_p);
+      /* We do not stream lang->u.fn.implicit_constexpr.  */
       goto lds_min;
 
     case lds_decomp:  /* lang_decl_decomp.  */
diff --git a/gcc/cp/pt.cc b/gcc/cp/pt.cc
index d12d57686f2..806219eefab 100644
--- a/gcc/cp/pt.cc
+++ b/gcc/cp/pt.cc
@@ -11107,7 +11107,7 @@ push_tinst_level_loc (tree tldcl, tree targs, location_t loc)
   if (tinst_depth >= max_tinst_depth)
     {
       /* Tell error.cc not to try to instantiate any templates.  */
-      at_eof = 2;
+      at_eof = 3;
       fatal_error (input_location,
 		   "template instantiation depth exceeds maximum of %d"
 		   " (use %<-ftemplate-depth=%> to increase the maximum)",
diff --git a/gcc/cp/typeck.cc b/gcc/cp/typeck.cc
index bf8ffaa7e75..8e4cfae08aa 100644
--- a/gcc/cp/typeck.cc
+++ b/gcc/cp/typeck.cc
@@ -7269,11 +7269,9 @@ cp_build_addr_expr_1 (tree arg, bool strict_lvalue, tsubst_flags_t complain)
 			      complain);
     }
 
-  /* For addresses of immediate functions ensure we have EXPR_LOCATION
-     set for possible later diagnostics.  */
+  /* Ensure we have EXPR_LOCATION 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)))
+      && TREE_CODE (TREE_OPERAND (val, 0)) == FUNCTION_DECL)
     SET_EXPR_LOCATION (val, input_location);
 
   return val;
diff --git a/gcc/doc/invoke.texi b/gcc/doc/invoke.texi
index 2fab4c5d71f..30b25290e05 100644
--- a/gcc/doc/invoke.texi
+++ b/gcc/doc/invoke.texi
@@ -219,6 +219,7 @@ in the following sections.
 -fno-elide-constructors
 -fno-enforce-eh-specs
 -fno-gnu-keywords
+-fno-immediate-escalation
 -fno-implicit-templates
 -fno-implicit-inline-templates
 -fno-implement-inlines
@@ -3386,6 +3387,39 @@ word as an identifier.  You can use the keyword @code{__typeof__} instead.
 This option is implied by the strict ISO C++ dialects: @option{-ansi},
 @option{-std=c++98}, @option{-std=c++11}, etc.
 
+@opindex fno-immediate-escalation
+@opindex fimmediate-escalation
+@item -fno-immediate-escalation
+Do not enable immediate function escalation whereby certain functions
+can be promoted to consteval, as specified in P2564R3.  For example:
+
+@example
+consteval int id(int i) @{ return i; @}
+
+constexpr int f(auto t)
+@{
+  return t + id(t); // id causes f<int> to be promoted to consteval
+@}
+
+void g(int i)
+@{
+  f (3);
+@}
+@end example
+
+compiles in C++20: @code{f} is an immediate-escalating function (due to
+the @code{auto} it is a function template and is declared @code{constexpr})
+and @code{id(t)} is an immediate-escalating expression, so @code{f} is
+promoted to @code{consteval}.  Consequently, the call to @code{id(t)}
+is in an immediate context, so doesn't have to produce a constant (that
+is the mechanism allowing consteval function composition).  However,
+with @option{-fno-immediate-escalation}, @code{f} is not promoted to
+@code{consteval}, and since the call to consteval function @code{id(t)}
+is not a constant expression, the compiler rejects the code.
+
+This option is turned on by default; it is only effective in C++20 mode
+or later.
+
 @opindex fimplicit-constexpr
 @item -fimplicit-constexpr
 Make inline functions implicitly constexpr, if they satisfy the
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 b2c5472b7de..3b258711ce6 100644
--- a/gcc/testsuite/g++.dg/cpp23/consteval-if2.C
+++ b/gcc/testsuite/g++.dg/cpp23/consteval-if2.C
@@ -33,7 +33,7 @@ baz (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);	// { dg-error "not a constant expression" }
     }
   else
     {
@@ -45,11 +45,11 @@ baz (int x)
     }
   else
     {
-      r += foo (8 * x);	// { dg-error "'x' is not a constant expression" }
+      r += foo (8 * x);	// { dg-error "is not a constant expression" }
     }
   if ! consteval	// { dg-warning "'if consteval' only available with" "" { target c++20_only } }
     {
-      r += foo (32 * x);// { dg-error "'x' is not a constant expression" }
+      r += foo (32 * x);// { dg-error "not a constant expression" }
     }
   if consteval		// { dg-warning "'if consteval' only available with" "" { target c++20_only } }
     {
@@ -98,7 +98,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
     {
@@ -110,11 +110,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 } }
     {
@@ -126,5 +126,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 6244f8fdfe4..4507ea07d1c 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-memfn1.C b/gcc/testsuite/g++.dg/cpp2a/consteval-memfn1.C
index 46eed13446d..ca923519f98 100644
--- a/gcc/testsuite/g++.dg/cpp2a/consteval-memfn1.C
+++ b/gcc/testsuite/g++.dg/cpp2a/consteval-memfn1.C
@@ -20,10 +20,13 @@ template<class>
 void VerifyHash(fixed_string s) {
   s.size(0); // { dg-bogus "" }
   s.size(-1); // { dg-message "expansion of" }
+// { dg-error "call to consteval function" "" { target *-*-* } .-1 }
   s.size_static(0); // { dg-bogus "" }
   s.size_static(-1); // { dg-message "expansion of" }
+// { dg-error "call to consteval function" "" { target *-*-* } .-1 }
   fixed_string::size_static(0); // { dg-bogus "" }
   fixed_string::size_static(-1); // { dg-message "expansion of" }
+// { dg-error "call to consteval function" "" { target *-*-* } .-1 }
   s(); // { dg-bogus "" }
 }
 
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..5e7b208113f
--- /dev/null
+++ b/gcc/testsuite/g++.dg/cpp2a/consteval-prop1.C
@@ -0,0 +1,169 @@
+// 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); // { dg-message "immediate-escalating expression .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); // { dg-message "immediate-escalating expression .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; // { dg-message "immediate-escalating expression .id." }
+  (void) p;
+  return t;
+}
+
+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};
+}
+
+constexpr void
+f5_nt (int i)
+{
+  A a{i}; // { dg-error "call to consteval function|not a constant" }
+}
+
+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; // { dg-message ".int g. is not const" }
+
+B b2(f0<int>(cid<int>(g))); // { dg-error "call to consteval function|not usable" }
+
+struct C {
+  consteval C (int) {};
+};
+
+constexpr int
+f7 (auto t)
+{
+  C c(t); // { dg-message "immediate-escalating expression .c.C::C\\(t\\)." }
+  return 0;
+}
+
+int i1 = f7 (g); // { dg-error "call to consteval function|not usable" }
+
+struct Y {
+  int y;
+  int x = id (y);
+  consteval Y (int i) : y (id (i)) {}
+};
+
+Y y1(1);
+Y y2(g); // { dg-error "call to consteval function|not usable" }
+
+struct Y2 {
+  int y;
+  int x = id (y);
+  constexpr Y2 (auto i) : y (id (i)) {}
+};
+
+Y2 y3(1);
+Y2 y4(g); // { dg-error "call to consteval function|not usable" }
+
+auto l1 = [](int i) constexpr {
+  int t = id (i);
+  return id (0);
+};
+
+int (*pl1)(int) = l1; // { dg-error "call to consteval function|returns address of immediate function" }
+
+auto l2 = [](int i) {
+  int t = id (i);
+  return id (0);
+};
+
+int (*pl2)(int) = l2; // { dg-error "call to consteval function|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 "used before its definition" }
+  int a = [] (int u = undef ()) {
+    return u;
+  }();
+} s2; // { dg-error "call to consteval function" }
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..4e33e6e3d0e
--- /dev/null
+++ b/gcc/testsuite/g++.dg/cpp2a/consteval-prop10.C
@@ -0,0 +1,41 @@
+// 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; // { dg-message ".int non_const. is not const" }
+
+template<typename>
+constexpr int
+f2 (int i = id (non_const))
+{
+  return i;
+}
+
+constexpr int
+f3 (auto)
+{
+  return f2<int>(); // { dg-message "contains an immediate-escalating expression .id\\(non_const\\)." }
+}
+
+auto a = &f3<int>; // { dg-error "taking address of an immediate function" }
+
+void
+g (int i)
+{
+  f1<int> (42);
+  f1<int> (i);
+  f1<int> ();
+  f2<int> (42);
+  f2<int> (i);
+  f2<int> (); // { dg-error "call to consteval function .id\\(non_const\\). is not a constant expression" }
+// { dg-error ".non_const. is not usable in a constant expression" "" { target *-*-* } .-1 }
+}
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..aca9675cd53
--- /dev/null
+++ b/gcc/testsuite/g++.dg/cpp2a/consteval-prop11.C
@@ -0,0 +1,49 @@
+// 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
+foobar (auto i)
+{
+  return i + id (i);
+  /* { dg-begin-multiline-output "" }
+   return i + id (i);
+              ~~~^~~
+     { dg-end-multiline-output "" } */
+}
+
+void
+g (int x)
+{
+  foobar (x); // { dg-error "10:call to consteval function .foobar<int>\\(x\\). is not a constant expression" }
+// { dg-error ".x. is not a constant expression" "" { target *-*-* } .-1 }
+  /* { dg-begin-multiline-output "" }
+foobar (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|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..cdc1f6dc862
--- /dev/null
+++ b/gcc/testsuite/g++.dg/cpp2a/consteval-prop14.C
@@ -0,0 +1,78 @@
+// 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\\(\\(i \\* 2\\)\\)." }
+}
+
+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" }
+// { dg-error ".i. is not a constant expression" "" { target *-*-* } .-1 }
+  f1 (42);
+  f2 (i); // { dg-error "call to consteval function .f2<int>\\(i\\). is not a constant expression" }
+// { dg-error ".i. is not a constant expression" "" { target *-*-* } .-1 }
+  f2 (42);
+  f3 (i); // { dg-error "call to consteval function .f3<int>\\(i\\). is not a constant expression" }
+// { dg-error ".i. is not a constant expression" "" { target *-*-* } .-1 }
+  f3 (42);
+  f4 (i); // { dg-error "call to consteval function .f4<int>\\(i\\). is not a constant expression" }
+// { dg-error ".i. is not a constant expression" "" { target *-*-* } .-1 }
+  f4 (42);
+  f5 (i); // { dg-error "call to consteval function .f5<int>\\(i\\). is not a constant expression" }
+// { dg-error ".i. is not a constant expression" "" { target *-*-* } .-1 }
+  f5 (42);
+  f6 (i); // { dg-error "call to consteval function .f6<int>\\(i\\). is not a constant expression" }
+// { dg-error ".i. is not a constant expression" "" { target *-*-* } .-1 }
+  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..3341c510a9f
--- /dev/null
+++ b/gcc/testsuite/g++.dg/cpp2a/consteval-prop15.C
@@ -0,0 +1,107 @@
+// 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;
+}
+
+constexpr int
+f9 (auto i)
+{
+  if consteval {
+    return id(i);
+  }
+  return i;
+}
+
+constexpr int
+f10 (auto i)
+{
+  if not consteval {
+    (void) 0;
+  } else {
+    return id(i);
+  }
+  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" }
+// { dg-error ".non_const. is not a constant expression" "" { target *-*-* } .-1 }
+  f5 (42);
+  f5 (non_const); // { dg-error "call to consteval function .f5<int>\\(non_const\\). is not a constant expression" }
+// { dg-error ".non_const. is not a constant expression" "" { target *-*-* } .-1 }
+  f6 (42);
+  f6 (non_const);
+  f7 (42);
+  f7 (non_const);
+  f8 (42);
+  f8 (non_const);
+  f9 (42);
+  f9 (non_const);
+  f10 (42);
+  f10 (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..47ec9b60b6c
--- /dev/null
+++ b/gcc/testsuite/g++.dg/cpp2a/consteval-prop17.C
@@ -0,0 +1,17 @@
+// P2564R3
+// { dg-do compile { target c++20 } }
+// { dg-options "-fno-immediate-escalation" }
+
+consteval int id(int i) { return i; }
+
+constexpr int
+f (auto i)
+{
+  return id (i); // { dg-error "not a constant expression" }
+}
+
+int
+g ()
+{
+  return f (42);
+}
diff --git a/gcc/testsuite/g++.dg/cpp2a/consteval-prop18.C b/gcc/testsuite/g++.dg/cpp2a/consteval-prop18.C
new file mode 100644
index 00000000000..a18106f8e0f
--- /dev/null
+++ b/gcc/testsuite/g++.dg/cpp2a/consteval-prop18.C
@@ -0,0 +1,20 @@
+// P2564R3
+// { dg-do compile { target c++20 } }
+
+consteval int id(int i) { return i; }
+
+constexpr int
+f (auto t)
+{
+  return t + id (t);
+}
+
+constexpr int
+f2 (auto t)
+{
+  return t + f(t); // { dg-message "immediate-escalating expression .f<int>\\(t\\)." }
+}
+
+int z; // { dg-message "not const" }
+auto y1 = f2 (42);
+auto y2 = f2 (z); // { dg-error "value of .z. is not usable in a constant expression|call to consteval function" }
diff --git a/gcc/testsuite/g++.dg/cpp2a/consteval-prop19.C b/gcc/testsuite/g++.dg/cpp2a/consteval-prop19.C
new file mode 100644
index 00000000000..3ceb05e41f4
--- /dev/null
+++ b/gcc/testsuite/g++.dg/cpp2a/consteval-prop19.C
@@ -0,0 +1,7 @@
+// P2564R3
+// { dg-do compile { target c++20 } }
+
+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;  // { dg-error "call to consteval function|returns address of immediate function" }
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..30129a4a266
--- /dev/null
+++ b/gcc/testsuite/g++.dg/cpp2a/consteval-prop2.C
@@ -0,0 +1,90 @@
+// 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);		// { dg-message "immediate-escalating expression .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 "the value of .x. is not usable in a constant expression" }
+// { dg-message "immediate-escalating expression .id\\(x\\)." "" { target *-*-* } .-1 }
+}
+
+int i = hh<int>();              // { dg-error "call to consteval function|called in a constant expression" }
+				// error: hh<int>() is an immediate-escalating expression
+                                // outside of an immediate-escalating function
+struct A {
+  int x;
+  int y = id(x);
+};
+
+// [expr.const]#example-9 says:
+//   k<int> is not an immediate function because A(42) is a
+//   constant expression and thus not immediate-escalating
+// In the evaluation of A(42), the member x has just been initialized
+// to constant 42.  And A(42) is constant-evaluated because "An aggregate
+// initialization is an immediate invocation if it evaluates a default
+// member initializer that has a subexpression that is an
+// immediate-escalating expression."
+template<class T>
+constexpr int k(int) {
+  return A(42).y;
+}
+
+int
+test (int i)
+{
+  int r = g (42) + g(i);
+  int t = k<int>(42)
+	    + k<int>(i); // { dg-bogus "call to|constant" "" { xfail *-*-* } }
+  return r + t;
+}
+
+// Just like above, but make the call to id(x) actually a constant.
+struct A2 {
+  static constexpr int x = 42;
+  int y = id(x);
+};
+
+template<class T>
+constexpr int k2(int) {
+  return A2(42).y;
+}
+
+int
+test2 (int i)
+{
+  return k2<int>(42) + k2<int>(i);
+}
diff --git a/gcc/testsuite/g++.dg/cpp2a/consteval-prop20.C b/gcc/testsuite/g++.dg/cpp2a/consteval-prop20.C
new file mode 100644
index 00000000000..f1bb08e2dba
--- /dev/null
+++ b/gcc/testsuite/g++.dg/cpp2a/consteval-prop20.C
@@ -0,0 +1,21 @@
+// P2564R3
+// { dg-do compile { target c++20 } }
+// { dg-options "-Wno-c++23-extensions" }
+
+consteval int id(int i) { return i; }
+
+constexpr int
+f (auto i)
+{
+  return id (i);
+}
+
+void
+g ()
+{
+  auto p = &f<int>; // { dg-error "taking address" }
+  decltype(&f<int>) x;
+  if consteval {
+    auto q = &f<int>;
+  }
+}
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..f181cb32942
--- /dev/null
+++ b/gcc/testsuite/g++.dg/cpp2a/consteval-prop3.C
@@ -0,0 +1,27 @@
+// 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);
+
+auto a2 = &f2<char>; // { dg-error "taking address" }
+auto b2 = &f2<int>; // { dg-error "taking address" }
+
+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..3a2e09b17b0
--- /dev/null
+++ b/gcc/testsuite/g++.dg/cpp2a/consteval-prop4.C
@@ -0,0 +1,30 @@
+// P2564R3
+// { dg-do compile { target c++20 } }
+// 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..93ed398d9bf
--- /dev/null
+++ b/gcc/testsuite/g++.dg/cpp2a/consteval-prop6.C
@@ -0,0 +1,59 @@
+// 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(){} // { dg-error "call to consteval function" }
+
+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 "modification of .x. is not a constant expression" }
+};
+
+void
+test ()
+{
+  [[maybe_unused]] X x; // { dg-error "call to consteval function" }
+// { dg-message "promoted to an immediate function" "" { target *-*-* } .-1 }
+}
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..118cf576f14
--- /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 "call to consteval function|not usable" }
+constexpr auto a2 = f2 (non_const); // { dg-error "not a constant|not usable" }
+auto a3 = f3 (42);
+constexpr auto a4 = f4 (42);
+
+void
+g ()
+{
+   auto a5 = f5 (non_const); // { dg-error "not a constant|not usable" }
+   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|not usable" }
+   (void) f10 (42);
+   (void) g1 (non_const); // { dg-error "not a constant|not usable" }
+}
+
+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 "call to consteval function|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..080fc76f26e
--- /dev/null
+++ b/gcc/testsuite/g++.dg/cpp2a/consteval-prop8.C
@@ -0,0 +1,82 @@
+// 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};
+}
+
+void
+f3 (int i)
+{
+  A a{i}; // { dg-error "not a constant expression" }
+}
+
+inline void
+f7 (int i)
+{
+  A a{i}; // { dg-error "not a constant expression" }
+}
+
+constexpr void
+f8 (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/consteval11.C b/gcc/testsuite/g++.dg/cpp2a/consteval11.C
index 05cecea4502..c2ee3c7a82a 100644
--- a/gcc/testsuite/g++.dg/cpp2a/consteval11.C
+++ b/gcc/testsuite/g++.dg/cpp2a/consteval11.C
@@ -8,9 +8,11 @@ constexpr int a = bar (1);
 constexpr int b = bar (2);		// { dg-message "in 'constexpr' expansion of" }
 constexpr int c = 0 ? bar (3) : 1;
 const int d = bar (4);			// { dg-message "in 'constexpr' expansion of" }
+// { dg-error "call to consteval function" "" { target *-*-* } .-1 }
 const int e = 0 ? bar (5) : 1;
 int f = bar (1);
 int g = bar (6);			// { dg-message "in 'constexpr' expansion of" }
+// { dg-error "call to consteval function" "" { target *-*-* } .-1 }
 int h = 0 ? bar (7) : 1;
 
 void
@@ -20,25 +22,35 @@ foo ()
   constexpr int b = bar (2);		// { dg-message "in 'constexpr' expansion of" }
   constexpr int c = 0 ? bar (3) : 1;
   const int d = bar (4);		// { dg-message "in 'constexpr' expansion of" }
+// { dg-error "call to consteval function" "" { target *-*-* } .-1 }
   const int e = 0 ? bar (5) : 1;
   int f = bar (1);
   int g = bar (6);			// { dg-message "in 'constexpr' expansion of" }
+// { dg-error "call to consteval function" "" { target *-*-* } .-1 }
   int h = 0 ? bar (7) : 1;		// { dg-message "in 'constexpr' expansion of" }
+// { dg-error "call to consteval function" "" { target *-*-* } .-1 }
   h += 0 ? bar (8) : 1;			// { dg-message "in 'constexpr' expansion of" }
+// { dg-error "call to consteval function" "" { target *-*-* } .-1 }
   if (0)
     bar (9);				// { dg-message "in 'constexpr' expansion of" }
+// { dg-error "call to consteval function" "" { target *-*-* } .-1 }
   else
     bar (10);				// { dg-message "in 'constexpr' expansion of" }
+// { dg-error "call to consteval function" "" { target *-*-* } .-1 }
   if (1)
     bar (11);				// { dg-message "in 'constexpr' expansion of" }
+// { dg-error "call to consteval function" "" { target *-*-* } .-1 }
   else
     bar (12);				// { dg-message "in 'constexpr' expansion of" }
+// { dg-error "call to consteval function" "" { target *-*-* } .-1 }
   if constexpr (0)
     bar (13);
   else
     bar (14);				// { dg-message "in 'constexpr' expansion of" }
+// { dg-error "call to consteval function" "" { target *-*-* } .-1 }
   if constexpr (1)
     bar (15);				// { dg-message "in 'constexpr' expansion of" }
+// { dg-error "call to consteval function" "" { target *-*-* } .-1 }
   else
     bar (16);
 }
@@ -121,18 +133,24 @@ quux ()
 {
   if (0)
     bar ((T) 2);				// { dg-message "in 'constexpr' expansion of" }
+// { dg-error "call to consteval function" "" { target *-*-* } .-1 }
   else
     bar ((T) 3);				// { dg-message "in 'constexpr' expansion of" }
+// { dg-error "call to consteval function" "" { target *-*-* } .-1 }
   if (1)
     bar ((T) 4);				// { dg-message "in 'constexpr' expansion of" }
+// { dg-error "call to consteval function" "" { target *-*-* } .-1 }
   else
     bar ((T) 5);				// { dg-message "in 'constexpr' expansion of" }
+// { dg-error "call to consteval function" "" { target *-*-* } .-1 }
   if constexpr (0)
     bar ((T) 6);
   else
     bar ((T) 7);				// { dg-message "in 'constexpr' expansion of" }
+// { dg-error "call to consteval function" "" { target *-*-* } .-1 }
   if constexpr (1)
     bar ((T) 8);				// { dg-message "in 'constexpr' expansion of" }
+// { dg-error "call to consteval function" "" { target *-*-* } .-1 }
   else
     bar ((T) 9);
 }
diff --git a/gcc/testsuite/g++.dg/cpp2a/consteval3.C b/gcc/testsuite/g++.dg/cpp2a/consteval3.C
index 9efac8c8eae..1199e9db623 100644
--- a/gcc/testsuite/g++.dg/cpp2a/consteval3.C
+++ b/gcc/testsuite/g++.dg/cpp2a/consteval3.C
@@ -16,8 +16,8 @@ consteval auto [ b, c ] = S ();		// { dg-error "structured binding declaration c
 int f5 (consteval int x) { return x; }	// { dg-error "a parameter cannot be declared 'consteval'" }
 consteval int f6 (int x) { return x; }
 int d = 6;		// { dg-message "'int d' is not const" }
-int e = f6 (d);		// { dg-error "the value of 'd' is not usable in a constant expression" }
-constexpr int f7 (int x) { return f6 (x); }	// { dg-error "'x' is not a constant expression" }
+int e = f6 (d);		// { dg-error "the value of 'd' is not usable in a constant expression|call to consteval function" }
+constexpr int f7 (int x) { return f6 (x); }	// { dg-error "'x' is not a constant expression|call to consteval function" }
 constexpr int f = f7 (5);
 using fnptr = int (int);
 fnptr *g = f6;		// { dg-error "taking address of an immediate function 'consteval int f6\\(int\\)'" }
diff --git a/gcc/testsuite/g++.dg/cpp2a/consteval34.C b/gcc/testsuite/g++.dg/cpp2a/consteval34.C
index 068827ba516..7562f403f74 100644
--- a/gcc/testsuite/g++.dg/cpp2a/consteval34.C
+++ b/gcc/testsuite/g++.dg/cpp2a/consteval34.C
@@ -7,6 +7,7 @@ constexpr int
 foo (bool b)
 {
   return b ? bar (3) : 2; // { dg-message "in .constexpr. expansion" }
+// { dg-error "call to consteval function" "" { target *-*-* } .-1 }
 }
 
 static_assert (foo (false) == 2);
@@ -22,13 +23,20 @@ void
 g ()
 {
   __extension__ int a1[bar(3)]; // { dg-message "in .constexpr. expansion" }
+// { dg-error "call to consteval function" "" { target *-*-* } .-1 }
   int a2[sizeof (bar(3))];
 
   int a3 = false ? (1 + bar (8)) : 1; // { dg-message "in .constexpr. expansion" }
+// { dg-error "call to consteval function" "" { target *-*-* } .-1 }
   a3 += false ? (1 + bar (8)) : 1; // { dg-message "in .constexpr. expansion" }
+// { dg-error "call to consteval function" "" { target *-*-* } .-1 }
 
   __extension__ int a4 = false ?: (1 + bar (8)); // { dg-message "in .constexpr. expansion" }
+// { dg-error "call to consteval function" "" { target *-*-* } .-1 }
   __extension__ int a5 = true ?: (1 + bar (8)); // { dg-message "in .constexpr. expansion" }
+// { dg-error "call to consteval function" "" { target *-*-* } .-1 }
   int a6 = bar (2) ? 1 : 2; // { dg-message "in .constexpr. expansion" }
+// { dg-error "call to consteval function" "" { target *-*-* } .-1 }
   int a7 = bar (2) - 1 ? 1 : 2; // { dg-message "in .constexpr. expansion" }
+// { dg-error "call to consteval function" "" { target *-*-* } .-1 }
 }
diff --git a/gcc/testsuite/g++.dg/cpp2a/consteval36.C b/gcc/testsuite/g++.dg/cpp2a/consteval36.C
index 9c470e4b7d7..8e27f2e33c6 100644
--- a/gcc/testsuite/g++.dg/cpp2a/consteval36.C
+++ b/gcc/testsuite/g++.dg/cpp2a/consteval36.C
@@ -6,17 +6,17 @@ consteval int id (int i) { return i; }
 void
 g (int i)
 {
-  1 ? 1 : ((1 ? 1 : 1), id (i)); // { dg-error "'i' is not a constant expression" }
-  1 ? 1 : ((1 ? 1 : 1), id (i), 1); // { dg-error "'i' is not a constant expression" }
-  1 ? 1 : ((i ? 1 : 1), id (i), 1); // { dg-error "'i' is not a constant expression" }
-  1 ? 1 : ((1 ? i : 1), id (i), 1); // { dg-error "'i' is not a constant expression" }
-  1 ? 1 : ((1 ? 1 : i), id (i), 1); // { dg-error "'i' is not a constant expression" }
-  1 ? 1 : ((i ? -i : i), id (i), 1); // { dg-error "'i' is not a constant expression" }
-  1 ? 1 : ((1 ? 1 : id (i)), id (42), 1); // { dg-error "'i' is not a constant expression" }
-  1 ? 1 : ((1 ? 1 : id (42)), id (i)); // { dg-error "'i' is not a constant expression" }
-  1 ? 1 : ((1 ? 1 : id (42)), id (i), 1); // { dg-error "'i' is not a constant expression" }
-  id (i) ? 1 : ((1 ? 1 : 1), id (i)); // { dg-error "'i' is not a constant expression" }
-  1 ? 1 : ((1 ? 1 : id (i)), id (i)); // { dg-error "'i' is not a constant expression" }
-  1 ? id (i) : ((1 ? 1 : id (i)), id (i)); // { dg-error "'i' is not a constant expression" }
-  1 ? 1 : ((id (i) ? 1 : 1), id (i)); // { dg-error "'i' is not a constant expression" }
+  1 ? 1 : ((1 ? 1 : 1), id (i)); // { dg-error "call to consteval function|'i' is not a constant expression" }
+  1 ? 1 : ((1 ? 1 : 1), id (i), 1); // { dg-error "call to consteval function|'i' is not a constant expression" }
+  1 ? 1 : ((i ? 1 : 1), id (i), 1); // { dg-error "call to consteval function|'i' is not a constant expression" }
+  1 ? 1 : ((1 ? i : 1), id (i), 1); // { dg-error "call to consteval function|'i' is not a constant expression" }
+  1 ? 1 : ((1 ? 1 : i), id (i), 1); // { dg-error "call to consteval function|'i' is not a constant expression" }
+  1 ? 1 : ((i ? -i : i), id (i), 1); // { dg-error "call to consteval function|'i' is not a constant expression" }
+  1 ? 1 : ((1 ? 1 : id (i)), id (42), 1); // { dg-error "call to consteval function|'i' is not a constant expression" }
+  1 ? 1 : ((1 ? 1 : id (42)), id (i)); // { dg-error "call to consteval function|'i' is not a constant expression" }
+  1 ? 1 : ((1 ? 1 : id (42)), id (i), 1); // { dg-error "call to consteval function|'i' is not a constant expression" }
+  id (i) ? 1 : ((1 ? 1 : 1), id (i)); // { dg-error "call to consteval function|'i' is not a constant expression" }
+  1 ? 1 : ((1 ? 1 : id (i)), id (i)); // { dg-error "call to consteval function|'i' is not a constant expression" }
+  1 ? id (i) : ((1 ? 1 : id (i)), id (i)); // { dg-error "call to consteval function|'i' is not a constant expression" }
+  1 ? 1 : ((id (i) ? 1 : 1), id (i)); // { dg-error "call to consteval function|'i' is not a constant expression" }
 }
diff --git a/gcc/testsuite/g++.dg/cpp2a/consteval9.C b/gcc/testsuite/g++.dg/cpp2a/consteval9.C
index 051a3d4e355..ad882d51c9b 100644
--- a/gcc/testsuite/g++.dg/cpp2a/consteval9.C
+++ b/gcc/testsuite/g++.dg/cpp2a/consteval9.C
@@ -14,6 +14,7 @@ template <int N>
 void qux ()
 {
   int a = bar (N);	// { dg-message "in 'constexpr' expansion of 'bar\\(2\\)'" }
+// { dg-error "call to consteval function" "" { target *-*-* } .-1 }
 }
 
 // This function is not instantiated so NDR.
@@ -31,3 +32,4 @@ baz ()
 }
 
 int a = bar (2);	// { dg-message "in 'constexpr' expansion of 'bar\\(2\\)'" }
+// { dg-error "call to consteval function" "" { target *-*-* } .-1 }
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/gcc/testsuite/g++.dg/cpp2a/spaceship-synth9.C b/gcc/testsuite/g++.dg/cpp2a/spaceship-synth9.C
index 33b547d2b50..ecb46b016a6 100644
--- a/gcc/testsuite/g++.dg/cpp2a/spaceship-synth9.C
+++ b/gcc/testsuite/g++.dg/cpp2a/spaceship-synth9.C
@@ -22,6 +22,6 @@ struct Z: Y<int>
 int main()
 {
   X<char>() == X<char>();	// { dg-error "no match" }
-  X<int> x; x == x;		// { dg-error "x' is not usable in a constant expression" }
+  X<int> x; x == x;		// { dg-error "x' is not usable in a constant expression|call to consteval function" }
   Y<int>()  == Y<int>();	// { dg-warning "nodiscard" }
 }
diff --git a/libstdc++-v3/testsuite/18_support/comparisons/categories/zero_neg.cc b/libstdc++-v3/testsuite/18_support/comparisons/categories/zero_neg.cc
index 9d2115b3f4f..82f7cd54fba 100644
--- a/libstdc++-v3/testsuite/18_support/comparisons/categories/zero_neg.cc
+++ b/libstdc++-v3/testsuite/18_support/comparisons/categories/zero_neg.cc
@@ -52,3 +52,4 @@ test01()
 
 // { dg-prune-output "reinterpret_cast.* is not a constant expression" }
 // { dg-prune-output "cast from 'void.' is not allowed" }
+// { dg-prune-output "not a constant expression" }
diff --git a/libstdc++-v3/testsuite/std/format/string_neg.cc b/libstdc++-v3/testsuite/std/format/string_neg.cc
index 7a60ef8cf0e..69bcc736cff 100644
--- a/libstdc++-v3/testsuite/std/format/string_neg.cc
+++ b/libstdc++-v3/testsuite/std/format/string_neg.cc
@@ -2,5 +2,5 @@
 
 #include <format>
 
-auto s = std::format(" {9} ");
+auto s = std::format(" {9} "); // { dg-error "call to consteval function" }
 // { dg-error "invalid.arg.id" "" { target *-*-* } 0 }

base-commit: 7317275497e10c4a0fb3fbaa6ca87f3463ac124d
-- 
2.42.0


^ permalink raw reply	[flat|nested] 19+ messages in thread

* Re: [PATCH v6] c++: implement P2564, consteval needs to propagate up [PR107687]
  2023-12-01 23:37                   ` [PATCH v6] " Marek Polacek
@ 2023-12-02  0:43                     ` Jason Merrill
  2023-12-04 20:23                       ` [PATCH v7] " Marek Polacek
  0 siblings, 1 reply; 19+ messages in thread
From: Jason Merrill @ 2023-12-02  0:43 UTC (permalink / raw)
  To: Marek Polacek; +Cc: GCC Patches

On 12/1/23 18:37, Marek Polacek wrote:
> On Thu, Nov 30, 2023 at 06:34:01PM -0500, Jason Merrill wrote:
>> On 11/23/23 11:46, Marek Polacek wrote:
>>> v5 greatly simplifies the code.
>>
>> Indeed, it's much cleaner now.
>>
>>> I still need a new ff_ flag to signal that we can return immediately
>>> after seeing an i-e expr.
>>
>> That's still not clear to me:
>>
>>> +      /* In turn, maybe promote the function we find ourselves in...  */
>>> +      if ((data->flags & ff_find_escalating_expr)
>>> +         && 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.  */
>>> +         && (e = cxx_constant_value (stmt, tf_none), e == error_mark_node))
>>> +       {
>>> +         /* Since we had to set DECL_ESCALATION_CHECKED_P before the walk,
>>> +            we call promote_function_to_consteval directly which doesn't
>>> +            check unchecked_immediate_escalating_function_p.  */
>>> +         if (current_function_decl)
>>> +           promote_function_to_consteval (current_function_decl);
>>> +         *walk_subtrees = 0;
>>> +         return stmt;
>>> +       }
>>
>> This is the one use of ff_find_escalating_expr, and it seems redundant with
>> the code immediately below, where we use complain (derived from
>> ff_mce_false) to decide whether to return immediately.  Can we remove this
>> hunk and the flag, and merge find_escalating_expr with cp_fold_immediate?
> 
> Ah, that works!  Hopefully done now.
>   
>> I think you want to walk the function body for three-ish reasons:
>> 1) at EOF, to check for escalation
>> 2) at EOF, to check for errors
>> 3) at error time, to explain escalation
>>
>> It's not clear to me that we need a flag to distinguish between them. When
>> we encounter an immediate-escalating expression E:
>>
>> A) if we're in an immediate-escalating function, escalate and return E (#1,
>> #3).
>> B) otherwise, if we're diagnosing, error and continue (#2).
>> C) otherwise, return E (individual expression mce_unknown walk from
>> constexpr.cc).
>>
>>> @@ -1178,11 +1388,19 @@ cp_fold_r (tree *stmt_p, int *walk_subtrees, void *data_
>>> )
>>>            *walk_subtrees = 0;
>>>            /* Don't return yet, still need the cp_fold below.  */
>>>          }
>>> -      cp_fold_immediate_r (stmt_p, walk_subtrees, data);
>>> +      else
>>> +       cp_fold_immediate_r (stmt_p, walk_subtrees, data);
>>>       }
>>>     *stmt_p = stmt = cp_fold (*stmt_p, data->flags);
>>> +  /* For certain trees, like +foo(), the cp_fold below will remove the +,
>>
>> s/below/above/?
> 
> Fixed.
>   
>>> +/* We've stashed immediate-escalating functions.  Now see if they indeed
>>> +   ought to be promoted to consteval.  */
>>> +
>>> +void
>>> +process_pending_immediate_escalating_fns ()
>>> +{
>>> +  /* This will be null for -fno-immediate-escalation.  */
>>> +  if (!deferred_escalating_exprs)
>>> +    return;
>>> +
>>> +  for (auto e : *deferred_escalating_exprs)
>>> +    if (TREE_CODE (e) == FUNCTION_DECL && !DECL_ESCALATION_CHECKED_P (e))
>>> +      cp_fold_immediate (&DECL_SAVED_TREE (e), mce_false, e);
>>> +}
>>> +
>>> +/* We've escalated every function that could have been promoted to
>>> +   consteval.  Check that we are not taking the address of a consteval
>>> +   function.  */
>>> +
>>> +void
>>> +check_immediate_escalating_refs ()
>>> +{
>>> +  /* This will be null for -fno-immediate-escalation.  */
>>> +  if (!deferred_escalating_exprs)
>>> +    return;
>>> +
>>> +  for (auto ref : *deferred_escalating_exprs)
>>> +    {
>>> +      if (TREE_CODE (ref) == FUNCTION_DECL)
>>> +       continue;
>>> +      tree decl = (TREE_CODE (ref) == PTRMEM_CST
>>> +                  ? PTRMEM_CST_MEMBER (ref)
>>> +                  : TREE_OPERAND (ref, 0));
>>> +      if (DECL_IMMEDIATE_FUNCTION_P (decl))
>>> +       taking_address_of_imm_fn_error (ref, decl);
>>> +    }
>>> +
>>> +  deferred_escalating_exprs = nullptr;
>>>   }
>>
>> Could these be merged, so you do a single loop of cp_fold_immediate over
>> function bodies or non-function expressions?  I'd expect that to work.
> 
> We seem to walk the hash table in a random order so I can't use one loop,
> otherwise we could hit &f before escalating f.

Is that a problem, since we recurse if we see a function that is still 
unchecked?

> @@ -1045,90 +1191,138 @@ cp_fold_immediate_r (tree *stmt_p, int *walk_subtrees, void *data_)
>    /* The purpose of this is not to emit errors for mce_unknown.  */
>    const tsubst_flags_t complain = (data->flags & ff_mce_false
>  				   ? tf_error : tf_none);
> +  const tree_code code = TREE_CODE (stmt);
>  
>    /* No need to look into types or unevaluated operands.
>       NB: This affects cp_fold_r as well.  */
> -  if (TYPE_P (stmt) || unevaluated_p (TREE_CODE (stmt)))
> +  if (TYPE_P (stmt) || unevaluated_p (code) || cp_unevaluated_operand)

Maybe check in_immediate_context here instead?

> +  /* [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 STMT does
> +     not need to produce a constant expression.  */ > +  if (DECL_IMMEDIATE_FUNCTION_P (decl))
> +    {
> +      tree e = cxx_constant_value (stmt, tf_none);
> +      if (e == error_mark_node)
> +	{
> +	  /* This takes care of
> +	      template <typename T>
> +	      constexpr int f(T t)
> +	      {
> +		return id(t);
> +	      }
> +	    where id (consteval) causes f<int> to be promoted.  */
> +	  if (maybe_promote_function_to_consteval (current_function_decl))
> +	    return NULL_TREE;

Can we remove maybe_promote in favor of the promote below?

> +	  /* If we're not complaining, we're either recursing when escalating,
> +	     or just looking for the i-e expression.  */
> +	  if (!(complain & tf_error))
> +	    {
> +	      /* Since we had to set DECL_ESCALATION_CHECKED_P before the walk,
> +		 we call promote_function_to_consteval directly which doesn't
> +		 check unchecked_immediate_escalating_function_p.  */
> +	      if (current_function_decl)
> +		promote_function_to_consteval (current_function_decl);
> +	    }

Shouldn't we still check immediate_escalating_function_p (not 
unchecked_), even if only in a gcc_checking_assert?

> +	  else if (!in_immediate_context ())

If we check in_immediate_context at the top of the function, we don't 
need to check it here.

Jason


^ permalink raw reply	[flat|nested] 19+ messages in thread

* [PATCH v7] c++: implement P2564, consteval needs to propagate up [PR107687]
  2023-12-02  0:43                     ` Jason Merrill
@ 2023-12-04 20:23                       ` Marek Polacek
  2023-12-04 21:49                         ` Jason Merrill
  0 siblings, 1 reply; 19+ messages in thread
From: Marek Polacek @ 2023-12-04 20:23 UTC (permalink / raw)
  To: Jason Merrill; +Cc: GCC Patches

On Fri, Dec 01, 2023 at 07:43:35PM -0500, Jason Merrill wrote:
> On 12/1/23 18:37, Marek Polacek wrote:
> > On Thu, Nov 30, 2023 at 06:34:01PM -0500, Jason Merrill wrote:
> > > On 11/23/23 11:46, Marek Polacek wrote:
> > > > v5 greatly simplifies the code.
> > > 
> > > Indeed, it's much cleaner now.
> > > 
> > > > I still need a new ff_ flag to signal that we can return immediately
> > > > after seeing an i-e expr.
> > > 
> > > That's still not clear to me:
> > > 
> > > > +      /* In turn, maybe promote the function we find ourselves in...  */
> > > > +      if ((data->flags & ff_find_escalating_expr)
> > > > +         && 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.  */
> > > > +         && (e = cxx_constant_value (stmt, tf_none), e == error_mark_node))
> > > > +       {
> > > > +         /* Since we had to set DECL_ESCALATION_CHECKED_P before the walk,
> > > > +            we call promote_function_to_consteval directly which doesn't
> > > > +            check unchecked_immediate_escalating_function_p.  */
> > > > +         if (current_function_decl)
> > > > +           promote_function_to_consteval (current_function_decl);
> > > > +         *walk_subtrees = 0;
> > > > +         return stmt;
> > > > +       }
> > > 
> > > This is the one use of ff_find_escalating_expr, and it seems redundant with
> > > the code immediately below, where we use complain (derived from
> > > ff_mce_false) to decide whether to return immediately.  Can we remove this
> > > hunk and the flag, and merge find_escalating_expr with cp_fold_immediate?
> > 
> > Ah, that works!  Hopefully done now.
> > > I think you want to walk the function body for three-ish reasons:
> > > 1) at EOF, to check for escalation
> > > 2) at EOF, to check for errors
> > > 3) at error time, to explain escalation
> > > 
> > > It's not clear to me that we need a flag to distinguish between them. When
> > > we encounter an immediate-escalating expression E:
> > > 
> > > A) if we're in an immediate-escalating function, escalate and return E (#1,
> > > #3).
> > > B) otherwise, if we're diagnosing, error and continue (#2).
> > > C) otherwise, return E (individual expression mce_unknown walk from
> > > constexpr.cc).
> > > 
> > > > @@ -1178,11 +1388,19 @@ cp_fold_r (tree *stmt_p, int *walk_subtrees, void *data_
> > > > )
> > > >            *walk_subtrees = 0;
> > > >            /* Don't return yet, still need the cp_fold below.  */
> > > >          }
> > > > -      cp_fold_immediate_r (stmt_p, walk_subtrees, data);
> > > > +      else
> > > > +       cp_fold_immediate_r (stmt_p, walk_subtrees, data);
> > > >       }
> > > >     *stmt_p = stmt = cp_fold (*stmt_p, data->flags);
> > > > +  /* For certain trees, like +foo(), the cp_fold below will remove the +,
> > > 
> > > s/below/above/?
> > 
> > Fixed.
> > > > +/* We've stashed immediate-escalating functions.  Now see if they indeed
> > > > +   ought to be promoted to consteval.  */
> > > > +
> > > > +void
> > > > +process_pending_immediate_escalating_fns ()
> > > > +{
> > > > +  /* This will be null for -fno-immediate-escalation.  */
> > > > +  if (!deferred_escalating_exprs)
> > > > +    return;
> > > > +
> > > > +  for (auto e : *deferred_escalating_exprs)
> > > > +    if (TREE_CODE (e) == FUNCTION_DECL && !DECL_ESCALATION_CHECKED_P (e))
> > > > +      cp_fold_immediate (&DECL_SAVED_TREE (e), mce_false, e);
> > > > +}
> > > > +
> > > > +/* We've escalated every function that could have been promoted to
> > > > +   consteval.  Check that we are not taking the address of a consteval
> > > > +   function.  */
> > > > +
> > > > +void
> > > > +check_immediate_escalating_refs ()
> > > > +{
> > > > +  /* This will be null for -fno-immediate-escalation.  */
> > > > +  if (!deferred_escalating_exprs)
> > > > +    return;
> > > > +
> > > > +  for (auto ref : *deferred_escalating_exprs)
> > > > +    {
> > > > +      if (TREE_CODE (ref) == FUNCTION_DECL)
> > > > +       continue;
> > > > +      tree decl = (TREE_CODE (ref) == PTRMEM_CST
> > > > +                  ? PTRMEM_CST_MEMBER (ref)
> > > > +                  : TREE_OPERAND (ref, 0));
> > > > +      if (DECL_IMMEDIATE_FUNCTION_P (decl))
> > > > +       taking_address_of_imm_fn_error (ref, decl);
> > > > +    }
> > > > +
> > > > +  deferred_escalating_exprs = nullptr;
> > > >   }
> > > 
> > > Could these be merged, so you do a single loop of cp_fold_immediate over
> > > function bodies or non-function expressions?  I'd expect that to work.
> > 
> > We seem to walk the hash table in a random order so I can't use one loop,
> > otherwise we could hit &f before escalating f.
> 
> Is that a problem, since we recurse if we see a function that is still
> unchecked?

It's a problem if the first thing we encounter in the loop is an ADDR_EXPR.
 
> > @@ -1045,90 +1191,138 @@ cp_fold_immediate_r (tree *stmt_p, int *walk_subtrees, void *data_)
> >    /* The purpose of this is not to emit errors for mce_unknown.  */
> >    const tsubst_flags_t complain = (data->flags & ff_mce_false
> >  				   ? tf_error : tf_none);
> > +  const tree_code code = TREE_CODE (stmt);
> >    /* No need to look into types or unevaluated operands.
> >       NB: This affects cp_fold_r as well.  */
> > -  if (TYPE_P (stmt) || unevaluated_p (TREE_CODE (stmt)))
> > +  if (TYPE_P (stmt) || unevaluated_p (code) || cp_unevaluated_operand)
> 
> Maybe check in_immediate_context here instead?

Ah, I wonder why that didn't work for me earlier; I must have goofed up
something else.  Done.
 
> > +  /* [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 STMT does
> > +     not need to produce a constant expression.  */ > +  if (DECL_IMMEDIATE_FUNCTION_P (decl))
> > +    {
> > +      tree e = cxx_constant_value (stmt, tf_none);
> > +      if (e == error_mark_node)
> > +	{
> > +	  /* This takes care of
> > +	      template <typename T>
> > +	      constexpr int f(T t)
> > +	      {
> > +		return id(t);
> > +	      }
> > +	    where id (consteval) causes f<int> to be promoted.  */
> > +	  if (maybe_promote_function_to_consteval (current_function_decl))
> > +	    return NULL_TREE;
> 
> Can we remove maybe_promote in favor of the promote below?
 
Yes!

I went for the most straightforward

	  if (immediate_escalating_function_p (current_function_decl))
	    promote_function_to_consteval (current_function_decl);
	  else if (complain & tf_error)
	    ...

> > +	  /* If we're not complaining, we're either recursing when escalating,
> > +	     or just looking for the i-e expression.  */
> > +	  if (!(complain & tf_error))
> > +	    {
> > +	      /* Since we had to set DECL_ESCALATION_CHECKED_P before the walk,
> > +		 we call promote_function_to_consteval directly which doesn't
> > +		 check unchecked_immediate_escalating_function_p.  */
> > +	      if (current_function_decl)
> > +		promote_function_to_consteval (current_function_decl);
> > +	    }
> 
> Shouldn't we still check immediate_escalating_function_p (not unchecked_),
> even if only in a gcc_checking_assert?
 
Done.

> > +	  else if (!in_immediate_context ())
> 
> If we check in_immediate_context at the top of the function, we don't need
> to check it here.

Right, done.

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:

w.C:11:11: error: call to consteval function 'f<int>(i)' is not a constant expression
   11 |         f (i);
      |         ~~^~~
w.C:11:11: error: 'i' is not a constant expression
w.C:6:22: 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); // id causes f<int> to be promoted to consteval
      |                    ~~^~~

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.

In v1, I addressed the delayed instantiation problem by instantiating
trees early, so that we can escalate functions right away.  That caused
a number of problems, and in certain cases, like consteval-prop3.C, it
can't work, because we need to wait till EOF to see the definition of
the function anyway.  Overeager instantiation tends to cause diagnostic
problems too.

In v2, I attempted to move the escalation to the gimplifier, at which
point all templates have been instantiated.  That attempt flopped,
however, because once we've gimplified a function, its body is discarded
and as a consequence, you can no longer evaluate a call to that function
which is required for escalating, which needs to decide if a call is
a constant expression or not.

Therefore, we have to perform the escalation before gimplifying, but
after instantiate_pending_templates.  That's not easy because we have
no way to walk all the trees.  In the v2 patch, I use two vectors: one
to store function decls that may become consteval, and another to
remember references to immediate-escalating functions.  Unfortunately
the latter must also stash functions that call immediate-escalating
functions.  Consider:

  int g(int i)
  {
    f<int>(i); // f is immediate-escalating
  }

where g itself is not immediate-escalating, but we have to make sure
that if f gets promoted to consteval, we give an error.

A new option, -fno-immediate-escalation, is provided to suppress
escalating functions.

v2 also adds a new flag, DECL_ESCALATION_CHECKED_P, so that we don't
escalate a function multiple times, and so that we can distinguish between
explicitly consteval functions and functions that have been promoted
to consteval.

In v3, I removed one of the new vectors and changed the other one
to a hash set.  This version also contains numerous cleanups.

v4 merges find_escalating_expr_r into cp_fold_immediate_r.  It also
adds a new optimization in cp_fold_function.

v5 greatly simplifies the code.

v6 simplifies the code further and removes an ff_ flag.

v7 removes maybe_promote_function_to_consteval and further simplifies
cp_fold_immediate_r logic.

	PR c++/107687
	PR c++/110997

gcc/c-family/ChangeLog:

	* c-cppbuiltin.cc (c_cpp_builtins): Update __cpp_consteval.
	* c-opts.cc (c_common_post_options): Pre-C++20, unset
	flag_immediate_escalation.
	* c.opt (fimmediate-escalation): New option.

gcc/cp/ChangeLog:

	* call.cc (in_immediate_context): No longer static.
	* constexpr.cc (cxx_eval_call_expression): Adjust assert.
	* cp-gimplify.cc (deferred_escalating_exprs): New vec.
	(remember_escalating_expr): New.
	(enum fold_flags): Remove ff_fold_immediate.
	(immediate_escalating_function_p): New.
	(unchecked_immediate_escalating_function_p): New.
	(promote_function_to_consteval): New.
	(cp_fold_immediate): Move above.  Return non-null if any errors were
	emitted.
	(maybe_explain_promoted_consteval): New.
	(cp_gimplify_expr) <case CALL_EXPR>: Assert we've handled all
	immediate invocations.
	(taking_address_of_imm_fn_error): New.
	(cp_fold_immediate_r): Merge ADDR_EXPR and PTRMEM_CST cases.  Implement
	P2564 - promoting functions to consteval.
	<case CALL_EXPR>: Implement P2564 - promoting functions to consteval.
	(cp_fold_r): If an expression turns into a CALL_EXPR after cp_fold,
	call cp_fold_immediate_r on the CALL_EXPR.
	(cp_fold_function): Set DECL_ESCALATION_CHECKED_P if
	deferred_escalating_exprs does not contain current_function_decl.
	(maybe_store_immediate_escalating_fn): New.
	(process_and_check_pending_immediate_escalating_fns): New.
	* cp-tree.h (struct lang_decl_fn): Add escalated_p bit-field.
	(DECL_ESCALATION_CHECKED_P): New.
	(immediate_invocation_p): Declare.
	(maybe_store_immediate_escalating_fn): Likewise.
	(process_pending_immediate_escalating_fns): Likewise.
	* decl.cc (finish_function): Call maybe_store_immediate_escalating_fn.
	* decl2.cc (c_parse_final_cleanups): Set at_eof to 2 after all
	templates have been instantiated; and to 3 at the end of the function.
	Call process_pending_immediate_escalating_fns.
	* error.cc (dump_template_bindings): Check at_eof against an updated
	value.
	* module.cc (trees_out::lang_decl_bools): Stream escalated_p.
	(trees_in::lang_decl_bools): Likewise.
	* pt.cc (push_tinst_level_loc): Set at_eof to 3, not 2.
	* typeck.cc (cp_build_addr_expr_1): Don't check
	DECL_IMMEDIATE_FUNCTION_P.

gcc/ChangeLog:

	* doc/invoke.texi: Document -fno-immediate-escalation.

libstdc++-v3/ChangeLog:

	* testsuite/18_support/comparisons/categories/zero_neg.cc: Add
	dg-prune-output.
	* testsuite/std/format/string_neg.cc: Add dg-error.

gcc/testsuite/ChangeLog:

	* 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/consteval-memfn1.C: Add dg-error.
	* g++.dg/cpp2a/consteval11.C: Likewise.
	* g++.dg/cpp2a/consteval3.C: Adjust dg-error.
	* g++.dg/cpp2a/consteval34.C: Add dg-error.
	* g++.dg/cpp2a/consteval9.C: Likewise.
	* g++.dg/cpp2a/feat-cxx2a.C: Adjust expected value of __cpp_consteval.
	* g++.dg/cpp2a/spaceship-synth9.C: Adjust dg-error.
	* 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-prop18.C: New test.
	* g++.dg/cpp2a/consteval-prop19.C: New test.
	* g++.dg/cpp2a/consteval-prop20.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/c-family/c-opts.cc                        |   5 +
 gcc/c-family/c.opt                            |   4 +
 gcc/cp/call.cc                                |   2 +-
 gcc/cp/constexpr.cc                           |   4 +-
 gcc/cp/cp-gimplify.cc                         | 358 ++++++++++++++----
 gcc/cp/cp-tree.h                              |  20 +-
 gcc/cp/decl.cc                                |   5 +-
 gcc/cp/decl2.cc                               |  16 +-
 gcc/cp/error.cc                               |   2 +-
 gcc/cp/module.cc                              |   4 +
 gcc/cp/pt.cc                                  |   2 +-
 gcc/cp/typeck.cc                              |   6 +-
 gcc/doc/invoke.texi                           |  34 ++
 gcc/testsuite/g++.dg/cpp23/consteval-if10.C   |   7 +-
 gcc/testsuite/g++.dg/cpp23/consteval-if2.C    |  14 +-
 gcc/testsuite/g++.dg/cpp23/feat-cxx2b.C       |   4 +-
 gcc/testsuite/g++.dg/cpp26/feat-cxx26.C       |   4 +-
 gcc/testsuite/g++.dg/cpp2a/consteval-memfn1.C |   3 +
 gcc/testsuite/g++.dg/cpp2a/consteval-prop1.C  | 169 +++++++++
 gcc/testsuite/g++.dg/cpp2a/consteval-prop10.C |  41 ++
 gcc/testsuite/g++.dg/cpp2a/consteval-prop11.C |  49 +++
 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 |  78 ++++
 gcc/testsuite/g++.dg/cpp2a/consteval-prop15.C | 107 ++++++
 gcc/testsuite/g++.dg/cpp2a/consteval-prop16.C |  73 ++++
 gcc/testsuite/g++.dg/cpp2a/consteval-prop17.C |  17 +
 gcc/testsuite/g++.dg/cpp2a/consteval-prop18.C |  20 +
 gcc/testsuite/g++.dg/cpp2a/consteval-prop19.C |   7 +
 gcc/testsuite/g++.dg/cpp2a/consteval-prop2.C  |  90 +++++
 gcc/testsuite/g++.dg/cpp2a/consteval-prop20.C |  21 +
 gcc/testsuite/g++.dg/cpp2a/consteval-prop3.C  |  27 ++
 gcc/testsuite/g++.dg/cpp2a/consteval-prop4.C  |  30 ++
 gcc/testsuite/g++.dg/cpp2a/consteval-prop5.C  |  27 ++
 gcc/testsuite/g++.dg/cpp2a/consteval-prop6.C  |  59 +++
 gcc/testsuite/g++.dg/cpp2a/consteval-prop7.C  |  76 ++++
 gcc/testsuite/g++.dg/cpp2a/consteval-prop8.C  |  82 ++++
 gcc/testsuite/g++.dg/cpp2a/consteval-prop9.C  |  67 ++++
 gcc/testsuite/g++.dg/cpp2a/consteval11.C      |  18 +
 gcc/testsuite/g++.dg/cpp2a/consteval3.C       |   4 +-
 gcc/testsuite/g++.dg/cpp2a/consteval34.C      |   8 +
 gcc/testsuite/g++.dg/cpp2a/consteval36.C      |  26 +-
 gcc/testsuite/g++.dg/cpp2a/consteval9.C       |   2 +
 gcc/testsuite/g++.dg/cpp2a/feat-cxx2a.C       |   4 +-
 gcc/testsuite/g++.dg/cpp2a/spaceship-synth9.C |   2 +-
 .../comparisons/categories/zero_neg.cc        |   1 +
 .../testsuite/std/format/string_neg.cc        |   2 +-
 48 files changed, 1540 insertions(+), 116 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-prop18.C
 create mode 100644 gcc/testsuite/g++.dg/cpp2a/consteval-prop19.C
 create mode 100644 gcc/testsuite/g++.dg/cpp2a/consteval-prop2.C
 create mode 100644 gcc/testsuite/g++.dg/cpp2a/consteval-prop20.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

diff --git a/gcc/c-family/c-cppbuiltin.cc b/gcc/c-family/c-cppbuiltin.cc
index e536429fa4c..2d1249f29ed 100644
--- a/gcc/c-family/c-cppbuiltin.cc
+++ b/gcc/c-family/c-cppbuiltin.cc
@@ -1059,7 +1059,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/c-family/c-opts.cc b/gcc/c-family/c-opts.cc
index d7faff10d66..d484ecfdfdb 100644
--- a/gcc/c-family/c-opts.cc
+++ b/gcc/c-family/c-opts.cc
@@ -1139,6 +1139,11 @@ c_common_post_options (const char **pfilename)
   if (cxx_dialect >= cxx20 || flag_concepts_ts)
     flag_concepts = 1;
 
+  /* -fimmediate-escalation has no effect when immediate functions are not
+     supported.  */
+  if (flag_immediate_escalation && cxx_dialect < cxx20)
+    flag_immediate_escalation = 0;
+
   if (num_in_fnames > 1)
     error ("too many filenames given; type %<%s %s%> for usage",
 	   progname, "--help");
diff --git a/gcc/c-family/c.opt b/gcc/c-family/c.opt
index ab44a6da66a..3706505f8bf 100644
--- a/gcc/c-family/c.opt
+++ b/gcc/c-family/c.opt
@@ -1898,6 +1898,10 @@ fhuge-objects
 C++ ObjC++ WarnRemoved
 No longer supported.
 
+fimmediate-escalation
+C++ ObjC++ Var(flag_immediate_escalation) Init(1)
+Implement P2564 for consteval propagation.
+
 fimplement-inlines
 C++ ObjC++ Var(flag_implement_inlines) Init(1)
 Export functions even if they can be inlined.
diff --git a/gcc/cp/call.cc b/gcc/cp/call.cc
index ae0decd87f1..c7efc5b077a 100644
--- a/gcc/cp/call.cc
+++ b/gcc/cp/call.cc
@@ -9742,7 +9742,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
diff --git a/gcc/cp/constexpr.cc b/gcc/cp/constexpr.cc
index b17e176aded..3fe6e35be06 100644
--- a/gcc/cp/constexpr.cc
+++ b/gcc/cp/constexpr.cc
@@ -3128,11 +3128,11 @@ cxx_eval_call_expression (const constexpr_ctx *ctx, tree t,
 	/* OK */;
       else if (!DECL_SAVED_TREE (fun))
 	{
-	  /* When at_eof >= 2, cgraph has started throwing away
+	  /* When at_eof >= 3, cgraph has started throwing away
 	     DECL_SAVED_TREE, so fail quietly.  FIXME we get here because of
 	     late code generation for VEC_INIT_EXPR, which needs to be
 	     completely reconsidered.  */
-	  gcc_assert (at_eof >= 2 && ctx->quiet);
+	  gcc_assert (at_eof >= 3 && ctx->quiet);
 	  *non_constant_p = true;
 	}
       else if (tree copy = get_fundef_copy (new_call.fundef))
diff --git a/gcc/cp/cp-gimplify.cc b/gcc/cp/cp-gimplify.cc
index 795c811471d..1bbfa8a4ffe 100644
--- a/gcc/cp/cp-gimplify.cc
+++ b/gcc/cp/cp-gimplify.cc
@@ -43,6 +43,21 @@ along with GCC; see the file COPYING3.  If not see
 #include "omp-general.h"
 #include "opts.h"
 
+/* Keep track of forward references to immediate-escalating functions in
+   case they become consteval.  This vector contains ADDR_EXPRs and
+   PTRMEM_CSTs; it also stores FUNCTION_DECLs that had an escalating
+   function call in them, to check that they can be evaluated to a constant,
+   and immediate-escalating functions that may become consteval.  */
+static GTY(()) hash_set<tree> *deferred_escalating_exprs;
+
+static void
+remember_escalating_expr (tree t)
+{
+  if (!deferred_escalating_exprs)
+    deferred_escalating_exprs = hash_set<tree>::create_ggc (37);
+  deferred_escalating_exprs->add (t);
+}
+
 /* Flags for cp_fold and cp_fold_r.  */
 
 enum fold_flags {
@@ -53,8 +68,6 @@ enum fold_flags {
      definitely not in a manifestly constant-evaluated
      context.  */
   ff_mce_false = 1 << 1,
-  /* Whether we're being called from cp_fold_immediate.  */
-  ff_fold_immediate = 1 << 2,
 };
 
 using fold_flags_t = int;
@@ -72,6 +85,7 @@ static tree cp_genericize_r (tree *, int *, void *);
 static tree cp_fold_r (tree *, int *, void *);
 static void cp_genericize_tree (tree*, bool);
 static tree cp_fold (tree, fold_flags_t);
+static tree cp_fold_immediate_r (tree *, int *, void *);
 
 /* Genericize a TRY_BLOCK.  */
 
@@ -428,6 +442,104 @@ lvalue_has_side_effects (tree e)
     return TREE_SIDE_EFFECTS (e);
 }
 
+/* Return true if FN is an immediate-escalating function.  */
+
+static bool
+immediate_escalating_function_p (tree fn)
+{
+  if (!fn || !flag_immediate_escalation)
+    return false;
+
+  gcc_checking_assert (TREE_CODE (fn) == FUNCTION_DECL);
+
+  if (DECL_IMMEDIATE_FUNCTION_P (fn))
+    return false;
+
+  /* An immediate-escalating function is
+      -- the call operator of a lambda that is not declared with the consteval
+	 specifier  */
+  if (LAMBDA_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))
+    return true;
+  /* -- a function that results from the instantiation of a templated entity
+	defined with the constexpr specifier.  */
+  return is_instantiation_of_constexpr (fn);
+}
+
+/* Return true if FN is an immediate-escalating function that has not been
+   checked for escalating expressions..  */
+
+static bool
+unchecked_immediate_escalating_function_p (tree fn)
+{
+  return (immediate_escalating_function_p (fn)
+	  && !DECL_ESCALATION_CHECKED_P (fn));
+}
+
+/* Promote FN to an immediate function, including its clones.  */
+
+static void
+promote_function_to_consteval (tree fn)
+{
+  SET_DECL_IMMEDIATE_FUNCTION_P (fn);
+  DECL_ESCALATION_CHECKED_P (fn) = true;
+  tree clone;
+  FOR_EACH_CLONE (clone, fn)
+    {
+      SET_DECL_IMMEDIATE_FUNCTION_P (clone);
+      DECL_ESCALATION_CHECKED_P (clone) = true;
+    }
+}
+
+/* A wrapper around cp_fold_immediate_r.  Return a non-null tree if
+   we found a non-constant immediate function, or taking the address
+   of an immediate function.  */
+
+tree
+cp_fold_immediate (tree *tp, mce_value manifestly_const_eval,
+		   tree decl /*= current_function_decl*/)
+{
+  if (cxx_dialect <= cxx17)
+    return NULL_TREE;
+
+  temp_override<tree> cfd (current_function_decl, decl);
+
+  fold_flags_t flags = ff_none;
+  if (manifestly_const_eval == mce_false)
+    flags |= ff_mce_false;
+
+  cp_fold_data data (flags);
+  int save_errorcount = errorcount;
+  tree r = cp_walk_tree_without_duplicates (tp, cp_fold_immediate_r, &data);
+  if (errorcount > save_errorcount)
+    return integer_one_node;
+  return r;
+}
+
+/* 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)
+{
+  if (DECL_ESCALATION_CHECKED_P (fn))
+    {
+      /* See if we can figure out what made the function consteval.  */
+      tree x = cp_fold_immediate (&DECL_SAVED_TREE (fn), mce_unknown, NULL_TREE);
+      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.  */
 
@@ -746,7 +858,9 @@ cp_gimplify_expr (tree *expr_p, gimple_seq *pre_p, gimple_seq *post_p)
       if (ret != GS_ERROR)
 	{
 	  tree decl = cp_get_callee_fndecl_nofold (*expr_p);
-	  if (decl && fndecl_built_in_p (decl, BUILT_IN_FRONTEND))
+	  if (!decl)
+	    break;
+	  if (fndecl_built_in_p (decl, BUILT_IN_FRONTEND))
 	    switch (DECL_FE_FUNCTION_CODE (decl))
 	      {
 	      case CP_BUILT_IN_IS_CONSTANT_EVALUATED:
@@ -771,10 +885,12 @@ cp_gimplify_expr (tree *expr_p, gimple_seq *pre_p, gimple_seq *post_p)
 	      default:
 		break;
 	      }
-	  else if (decl
-		   && fndecl_built_in_p (decl, BUILT_IN_CLZG, BUILT_IN_CTZG))
+	  else if (fndecl_built_in_p (decl, BUILT_IN_CLZG, BUILT_IN_CTZG))
 	    ret = (enum gimplify_status) c_gimplify_expr (expr_p, pre_p,
 							  post_p);
+	  else
+	    /* All consteval functions should have been processed by now.  */
+	    gcc_checking_assert (!immediate_invocation_p (decl));
 	}
       break;
 
@@ -1035,6 +1151,20 @@ struct cp_genericize_data
   bool handle_invisiref_parm_p;
 };
 
+/* Emit an error about taking the address of an immediate function.
+   EXPR is the whole expression; DECL is the immediate function.  */
+
+static void
+taking_address_of_imm_fn_error (tree expr, tree decl)
+{
+  auto_diagnostic_group d;
+  const location_t loc = (TREE_CODE (expr) == PTRMEM_CST
+			  ? PTRMEM_CST_LOCATION (expr)
+			  : EXPR_LOCATION (expr));
+  error_at (loc, "taking address of an immediate function %qD", decl);
+  maybe_explain_promoted_consteval (loc, decl);
+}
+
 /* A subroutine of cp_fold_r to handle immediate functions.  */
 
 static tree
@@ -1045,90 +1175,128 @@ cp_fold_immediate_r (tree *stmt_p, int *walk_subtrees, void *data_)
   /* The purpose of this is not to emit errors for mce_unknown.  */
   const tsubst_flags_t complain = (data->flags & ff_mce_false
 				   ? tf_error : tf_none);
+  const tree_code code = TREE_CODE (stmt);
 
   /* No need to look into types or unevaluated operands.
      NB: This affects cp_fold_r as well.  */
-  if (TYPE_P (stmt) || unevaluated_p (TREE_CODE (stmt)))
+  if (TYPE_P (stmt) || unevaluated_p (code) || in_immediate_context ())
     {
       *walk_subtrees = 0;
       return NULL_TREE;
     }
 
-  switch (TREE_CODE (stmt))
-    {
-    case PTRMEM_CST:
-      if (TREE_CODE (PTRMEM_CST_MEMBER (stmt)) == FUNCTION_DECL
-	  && DECL_IMMEDIATE_FUNCTION_P (PTRMEM_CST_MEMBER (stmt)))
-	{
-	  if (!data->pset.add (stmt) && (complain & tf_error))
-	    {
-	      error_at (PTRMEM_CST_LOCATION (stmt),
-			"taking address of an immediate function %qD",
-			PTRMEM_CST_MEMBER (stmt));
-	      *stmt_p = build_zero_cst (TREE_TYPE (stmt));
-	    }
-	  return error_mark_node;
-	}
-      break;
+  tree decl = NULL_TREE;
+  bool call_p = false;
 
-    /* Expand immediate invocations.  */
+  /* We are looking for &fn or fn().  */
+  switch (code)
+    {
     case CALL_EXPR:
     case AGGR_INIT_EXPR:
       if (tree fn = cp_get_callee (stmt))
 	if (TREE_CODE (fn) != ADDR_EXPR || ADDR_EXPR_DENOTES_CALL_P (fn))
-	  if (tree fndecl = cp_get_fndecl_from_callee (fn, /*fold*/false))
-	    if (DECL_IMMEDIATE_FUNCTION_P (fndecl))
-	      {
-		stmt = cxx_constant_value (stmt, complain);
-		if (stmt == error_mark_node)
-		  {
-		    if (complain & tf_error)
-		      *stmt_p = error_mark_node;
-		    return error_mark_node;
-		  }
-		*stmt_p = stmt;
-	      }
+	  decl = cp_get_fndecl_from_callee (fn, /*fold*/false);
+      call_p = true;
+      break;
+    case PTRMEM_CST:
+      decl = PTRMEM_CST_MEMBER (stmt);
       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))
-	{
-	  if (complain & tf_error)
-	    {
-	      error_at (EXPR_LOCATION (stmt),
-			"taking address of an immediate function %qD",
-			TREE_OPERAND (stmt, 0));
-	      *stmt_p = build_zero_cst (TREE_TYPE (stmt));
-	    }
-	  return error_mark_node;
-	}
+      if (!ADDR_EXPR_DENOTES_CALL_P (stmt))
+	decl = TREE_OPERAND (stmt, 0);
       break;
-
     default:
-      break;
+      return NULL_TREE;
     }
 
-  return NULL_TREE;
-}
+  if (!decl || TREE_CODE (decl) != FUNCTION_DECL)
+    return NULL_TREE;
 
-/* A wrapper around cp_fold_immediate_r.  Return true if we found
-   a non-constant immediate function, or taking the address of an
-   immediate function.  */
+  /* Fully escalate once all templates have been instantiated.  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 (at_eof > 1 && unchecked_immediate_escalating_function_p (decl))
+    {
+      /* Set before the actual walk to avoid endless recursion.  */
+      DECL_ESCALATION_CHECKED_P (decl) = true;
+      /* We're only looking for the first escalating expression.  Let us not
+	 walk more trees than necessary, hence mce_unknown.  */
+      cp_fold_immediate (&DECL_SAVED_TREE (decl), mce_unknown, decl);
+    }
 
-bool
-cp_fold_immediate (tree *tp, mce_value manifestly_const_eval)
-{
-  if (cxx_dialect <= cxx17)
-    return false;
+  /* [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."
 
-  fold_flags_t flags = ff_fold_immediate;
-  if (manifestly_const_eval == mce_false)
-    flags |= ff_mce_false;
+     If we are in an immediate-escalating function, the immediate-escalating
+     expression or conversion makes it an immediate function.  So STMT does
+     not need to produce a constant expression.  */
+  if (DECL_IMMEDIATE_FUNCTION_P (decl))
+    {
+      tree e = cxx_constant_value (stmt, tf_none);
+      if (e == error_mark_node)
+	{
+	  /* This takes care of, e.g.,
+	      template <typename T>
+	      constexpr int f(T t)
+	      {
+		return id(t);
+	      }
+	    where id (consteval) causes f<int> to be promoted.  */
+	  if (immediate_escalating_function_p (current_function_decl))
+	    promote_function_to_consteval (current_function_decl);
+	  else if (complain & tf_error)
+	    {
+	      if (call_p)
+		{
+		  auto_diagnostic_group d;
+		  location_t loc = cp_expr_loc_or_input_loc (stmt);
+		  error_at (loc, "call to consteval function %qE is "
+			    "not a constant expression", stmt);
+		  /* Explain why it's not a constant expression.  */
+		  *stmt_p = cxx_constant_value (stmt, complain);
+		  maybe_explain_promoted_consteval (loc, decl);
+		}
+	      else if (!data->pset.add (stmt))
+		{
+		  taking_address_of_imm_fn_error (stmt, decl);
+		  *stmt_p = build_zero_cst (TREE_TYPE (stmt));
+		}
+	      /* If we're giving hard errors, continue the walk rather than
+		 bailing out after the first error.  */
+	      return NULL_TREE;
+	    }
+	  *walk_subtrees = 0;
+	  return stmt;
+	}
+      /* We've evaluated the consteval function call.  */
+      if (call_p)
+	*stmt_p = e;
+    }
+  /* We've encountered a function call that may turn out to be consteval
+     later.  Store its caller so that we can ensure that the call is
+     a constant expression.  */
+  else if (unchecked_immediate_escalating_function_p (decl))
+    {
+      /* Make sure we're not inserting new elements while walking
+	 the deferred_escalating_exprs hash table; if we are, it's
+	 likely that a function wasn't properly marked checked for
+	 i-e expressions.  */
+      gcc_checking_assert (at_eof <= 1);
+      if (current_function_decl)
+	remember_escalating_expr (current_function_decl);
+      /* auto p = &f<int>; in the global scope won't be ensconced in
+	 a function we could store for later at this point.  (If there's
+	 no c_f_d at this point and we're dealing with a call, we should
+	 see the call when cp_fold_function __static_i_and_d.)  */
+      else if (!call_p)
+	remember_escalating_expr (stmt);
+    }
 
-  cp_fold_data data (flags);
-  return !!cp_walk_tree_without_duplicates (tp, cp_fold_immediate_r, &data);
+  return NULL_TREE;
 }
 
 /* Perform any pre-gimplification folding of C++ front end trees to
@@ -1178,11 +1346,19 @@ cp_fold_r (tree *stmt_p, int *walk_subtrees, void *data_)
 	  *walk_subtrees = 0;
 	  /* Don't return yet, still need the cp_fold below.  */
 	}
-      cp_fold_immediate_r (stmt_p, walk_subtrees, data);
+      else
+	cp_fold_immediate_r (stmt_p, walk_subtrees, data);
     }
 
   *stmt_p = stmt = cp_fold (*stmt_p, data->flags);
 
+  /* For certain trees, like +foo(), the cp_fold above will remove the +,
+     and the subsequent tree walk would go straight down to the CALL_EXPR's
+     operands, meaning that cp_fold_immediate_r would never see the
+     CALL_EXPR.  Ew :(.  */
+  if (TREE_CODE (stmt) == CALL_EXPR && code != CALL_EXPR)
+    cp_fold_immediate_r (stmt_p, walk_subtrees, data);
+
   if (data->pset.add (stmt))
     {
       /* Don't walk subtrees of stmts we've already walked once, otherwise
@@ -1304,6 +1480,54 @@ cp_fold_function (tree fndecl)
      pass ff_mce_false.  */
   cp_fold_data data (ff_genericize | ff_mce_false);
   cp_walk_tree (&DECL_SAVED_TREE (fndecl), cp_fold_r, &data, NULL);
+
+  /* This is merely an optimization: if FNDECL has no i-e expressions,
+     we'll not save c_f_d, and we can safely say that FNDECL will not
+     be promoted to consteval.  */
+  if (deferred_escalating_exprs
+      && !deferred_escalating_exprs->contains (current_function_decl))
+    DECL_ESCALATION_CHECKED_P (fndecl) = true;
+}
+
+/* FN is not a consteval function, but may become one.  Remember to
+   escalate it after all pending templates have been instantiated.  */
+
+void
+maybe_store_immediate_escalating_fn (tree fn)
+{
+  if (unchecked_immediate_escalating_function_p (fn))
+    remember_escalating_expr (fn);
+}
+
+/* We've stashed immediate-escalating functions.  Now see if they indeed
+   ought to be promoted to consteval.  */
+
+void
+process_and_check_pending_immediate_escalating_fns ()
+{
+  /* This will be null for -fno-immediate-escalation.  */
+  if (!deferred_escalating_exprs)
+    return;
+
+  for (auto e : *deferred_escalating_exprs)
+    if (TREE_CODE (e) == FUNCTION_DECL && !DECL_ESCALATION_CHECKED_P (e))
+      cp_fold_immediate (&DECL_SAVED_TREE (e), mce_false, e);
+
+  /* We've escalated every function that could have been promoted to
+     consteval.  Check that we are not taking the address of a consteval
+     function.  */
+  for (auto e : *deferred_escalating_exprs)
+    {
+      if (TREE_CODE (e) == FUNCTION_DECL)
+	continue;
+      tree decl = (TREE_CODE (e) == PTRMEM_CST
+		   ? PTRMEM_CST_MEMBER (e)
+		   : TREE_OPERAND (e, 0));
+      if (DECL_IMMEDIATE_FUNCTION_P (decl))
+	taking_address_of_imm_fn_error (e, decl);
+    }
+
+  deferred_escalating_exprs = nullptr;
 }
 
 /* Turn SPACESHIP_EXPR EXPR into GENERIC.  */
diff --git a/gcc/cp/cp-tree.h b/gcc/cp/cp-tree.h
index 6b3ce9d87da..338c2d80cb9 100644
--- a/gcc/cp/cp-tree.h
+++ b/gcc/cp/cp-tree.h
@@ -2946,8 +2946,9 @@ struct GTY(()) lang_decl_fn {
   unsigned maybe_deleted : 1;
   unsigned coroutine_p : 1;
   unsigned implicit_constexpr : 1;
+  unsigned escalated_p : 1;
 
-  unsigned spare : 9;
+  unsigned spare : 8;
 
   /* 32-bits padding on 64-bit host.  */
 
@@ -3399,6 +3400,14 @@ struct GTY(()) lang_decl {
 #define DECL_MAYBE_DELETED(NODE) \
   (LANG_DECL_FN_CHECK (NODE)->maybe_deleted)
 
+/* Nonzero for FUNCTION_DECL means that this function's body has been
+   checked for immediate-escalating expressions and maybe promoted.  It
+   does *not* mean the function is consteval.  It must not be set in
+   a function that was marked consteval by the user, so that we can
+   distinguish between explicitly consteval functions and promoted consteval
+   functions.  */
+#define DECL_ESCALATION_CHECKED_P(NODE) (LANG_DECL_FN_CHECK (NODE)->escalated_p)
+
 /* True (in a FUNCTION_DECL) if NODE is a virtual function that is an
    invalid overrider for a function from a base class.  Once we have
    complained about an invalid overrider we avoid complaining about it
@@ -5882,7 +5891,8 @@ extern GTY(()) vec<tree, va_gc> *keyed_classes;
 
 \f
 /* Nonzero if we're done parsing and into end-of-file activities.
-   Two if we're done with front-end processing.  */
+   2 if all templates have been instantiated.
+   3 if we're done with front-end processing.  */
 
 extern int at_eof;
 
@@ -6774,6 +6784,7 @@ 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 tree build_cxx_call			(tree, int, tree *,
 						 tsubst_flags_t,
 						 tree = NULL_TREE);
@@ -8415,7 +8426,10 @@ extern tree process_stmt_assume_attribute	(tree, tree, location_t);
 extern bool simple_empty_class_p		(tree, tree, tree_code);
 extern tree fold_builtin_source_location	(const_tree);
 extern tree get_source_location_impl_type	();
-extern bool cp_fold_immediate			(tree *, mce_value);
+extern tree cp_fold_immediate			(tree *, mce_value,
+						 tree = current_function_decl);
+extern void maybe_store_immediate_escalating_fn	(tree);
+extern void process_and_check_pending_immediate_escalating_fns ();
 
 /* in name-lookup.cc */
 extern tree strip_using_decl                    (tree);
diff --git a/gcc/cp/decl.cc b/gcc/cp/decl.cc
index 4b685270097..e4f37923418 100644
--- a/gcc/cp/decl.cc
+++ b/gcc/cp/decl.cc
@@ -18441,7 +18441,10 @@ finish_function (bool inline_p)
   if (!processing_template_decl
       && !DECL_IMMEDIATE_FUNCTION_P (fndecl)
       && !DECL_OMP_DECLARE_REDUCTION_P (fndecl))
-    cp_fold_function (fndecl);
+    {
+      cp_fold_function (fndecl);
+      maybe_store_immediate_escalating_fn (fndecl);
+    }
 
   /* Set up the named return value optimization, if we can.  Candidate
      variables are selected in check_return_expr.  */
diff --git a/gcc/cp/decl2.cc b/gcc/cp/decl2.cc
index 9e666e5eece..bee84879023 100644
--- a/gcc/cp/decl2.cc
+++ b/gcc/cp/decl2.cc
@@ -169,7 +169,9 @@ typedef hash_map<unsigned/*Priority*/, tree/*List*/,
    one for init.  The fini table is only ever used when !cxa_atexit.  */
 static GTY(()) priority_map_t *static_init_fini_fns[2];
 
-/* Nonzero if we're done parsing and into end-of-file activities.  */
+/* Nonzero if we're done parsing and into end-of-file activities.
+   2 if all templates have been instantiated.
+   3 if we're done with front-end processing.  */
 
 int at_eof;
 
@@ -4987,6 +4989,7 @@ c_parse_final_cleanups (void)
   tree decl;
 
   locus_at_end_of_parsing = input_location;
+  /* We're done parsing.  */
   at_eof = 1;
 
   /* Bad parse errors.  Just forget about it.  */
@@ -5252,6 +5255,9 @@ c_parse_final_cleanups (void)
 	reconsider = true;
     }
 
+  /* All templates have been instantiated.  */
+  at_eof = 2;
+
   void *module_cookie = finish_module_processing (parse_in);
 
   lower_var_init ();
@@ -5294,7 +5300,11 @@ c_parse_final_cleanups (void)
   if (static_init_fini_fns[true])
     for (auto iter : *static_init_fini_fns[true])
       iter.second = nreverse (iter.second);
-  
+
+  /* Now we've instantiated all templates.  Now we can escalate the functions
+     we squirreled away earlier.  */
+  process_and_check_pending_immediate_escalating_fns ();
+
   /* Then, do the Objective-C stuff.  This is where all the
      Objective-C module stuff gets generated (symtab,
      class/protocol/selector lists etc).  This must be done after C++
@@ -5376,7 +5386,7 @@ c_parse_final_cleanups (void)
   timevar_start (TV_PHASE_PARSING);
 
   /* Indicate that we're done with front end processing.  */
-  at_eof = 2;
+  at_eof = 3;
 }
 
 /* Perform any post compilation-proper cleanups for the C++ front-end.
diff --git a/gcc/cp/error.cc b/gcc/cp/error.cc
index 785909c362a..3b1b5de5ea4 100644
--- a/gcc/cp/error.cc
+++ b/gcc/cp/error.cc
@@ -478,7 +478,7 @@ dump_template_bindings (cxx_pretty_printer *pp, tree parms, tree args,
 
   /* Don't try to do this once cgraph starts throwing away front-end
      information.  */
-  if (at_eof >= 2)
+  if (at_eof >= 3)
     return;
 
   FOR_EACH_VEC_SAFE_ELT (typenames, i, t)
diff --git a/gcc/cp/module.cc b/gcc/cp/module.cc
index 33fcf396875..1b57fbe2124 100644
--- a/gcc/cp/module.cc
+++ b/gcc/cp/module.cc
@@ -5683,6 +5683,8 @@ trees_out::lang_decl_bools (tree t)
       WB (lang->u.fn.has_dependent_explicit_spec_p);
       WB (lang->u.fn.immediate_fn_p);
       WB (lang->u.fn.maybe_deleted);
+      WB (lang->u.fn.escalated_p);
+      /* We do not stream lang->u.fn.implicit_constexpr.  */
       goto lds_min;
 
     case lds_decomp:  /* lang_decl_decomp.  */
@@ -5751,6 +5753,8 @@ trees_in::lang_decl_bools (tree t)
       RB (lang->u.fn.has_dependent_explicit_spec_p);
       RB (lang->u.fn.immediate_fn_p);
       RB (lang->u.fn.maybe_deleted);
+      RB (lang->u.fn.escalated_p);
+      /* We do not stream lang->u.fn.implicit_constexpr.  */
       goto lds_min;
 
     case lds_decomp:  /* lang_decl_decomp.  */
diff --git a/gcc/cp/pt.cc b/gcc/cp/pt.cc
index 7265201e036..924a20973b4 100644
--- a/gcc/cp/pt.cc
+++ b/gcc/cp/pt.cc
@@ -11107,7 +11107,7 @@ push_tinst_level_loc (tree tldcl, tree targs, location_t loc)
   if (tinst_depth >= max_tinst_depth)
     {
       /* Tell error.cc not to try to instantiate any templates.  */
-      at_eof = 2;
+      at_eof = 3;
       fatal_error (input_location,
 		   "template instantiation depth exceeds maximum of %d"
 		   " (use %<-ftemplate-depth=%> to increase the maximum)",
diff --git a/gcc/cp/typeck.cc b/gcc/cp/typeck.cc
index bf8ffaa7e75..8e4cfae08aa 100644
--- a/gcc/cp/typeck.cc
+++ b/gcc/cp/typeck.cc
@@ -7269,11 +7269,9 @@ cp_build_addr_expr_1 (tree arg, bool strict_lvalue, tsubst_flags_t complain)
 			      complain);
     }
 
-  /* For addresses of immediate functions ensure we have EXPR_LOCATION
-     set for possible later diagnostics.  */
+  /* Ensure we have EXPR_LOCATION 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)))
+      && TREE_CODE (TREE_OPERAND (val, 0)) == FUNCTION_DECL)
     SET_EXPR_LOCATION (val, input_location);
 
   return val;
diff --git a/gcc/doc/invoke.texi b/gcc/doc/invoke.texi
index 2b51ff304f6..681e3f3f466 100644
--- a/gcc/doc/invoke.texi
+++ b/gcc/doc/invoke.texi
@@ -219,6 +219,7 @@ in the following sections.
 -fno-elide-constructors
 -fno-enforce-eh-specs
 -fno-gnu-keywords
+-fno-immediate-escalation
 -fno-implicit-templates
 -fno-implicit-inline-templates
 -fno-implement-inlines
@@ -3386,6 +3387,39 @@ word as an identifier.  You can use the keyword @code{__typeof__} instead.
 This option is implied by the strict ISO C++ dialects: @option{-ansi},
 @option{-std=c++98}, @option{-std=c++11}, etc.
 
+@opindex fno-immediate-escalation
+@opindex fimmediate-escalation
+@item -fno-immediate-escalation
+Do not enable immediate function escalation whereby certain functions
+can be promoted to consteval, as specified in P2564R3.  For example:
+
+@example
+consteval int id(int i) @{ return i; @}
+
+constexpr int f(auto t)
+@{
+  return t + id(t); // id causes f<int> to be promoted to consteval
+@}
+
+void g(int i)
+@{
+  f (3);
+@}
+@end example
+
+compiles in C++20: @code{f} is an immediate-escalating function (due to
+the @code{auto} it is a function template and is declared @code{constexpr})
+and @code{id(t)} is an immediate-escalating expression, so @code{f} is
+promoted to @code{consteval}.  Consequently, the call to @code{id(t)}
+is in an immediate context, so doesn't have to produce a constant (that
+is the mechanism allowing consteval function composition).  However,
+with @option{-fno-immediate-escalation}, @code{f} is not promoted to
+@code{consteval}, and since the call to consteval function @code{id(t)}
+is not a constant expression, the compiler rejects the code.
+
+This option is turned on by default; it is only effective in C++20 mode
+or later.
+
 @opindex fimplicit-constexpr
 @item -fimplicit-constexpr
 Make inline functions implicitly constexpr, if they satisfy the
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 b2c5472b7de..3b258711ce6 100644
--- a/gcc/testsuite/g++.dg/cpp23/consteval-if2.C
+++ b/gcc/testsuite/g++.dg/cpp23/consteval-if2.C
@@ -33,7 +33,7 @@ baz (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);	// { dg-error "not a constant expression" }
     }
   else
     {
@@ -45,11 +45,11 @@ baz (int x)
     }
   else
     {
-      r += foo (8 * x);	// { dg-error "'x' is not a constant expression" }
+      r += foo (8 * x);	// { dg-error "is not a constant expression" }
     }
   if ! consteval	// { dg-warning "'if consteval' only available with" "" { target c++20_only } }
     {
-      r += foo (32 * x);// { dg-error "'x' is not a constant expression" }
+      r += foo (32 * x);// { dg-error "not a constant expression" }
     }
   if consteval		// { dg-warning "'if consteval' only available with" "" { target c++20_only } }
     {
@@ -98,7 +98,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
     {
@@ -110,11 +110,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 } }
     {
@@ -126,5 +126,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 6244f8fdfe4..4507ea07d1c 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-memfn1.C b/gcc/testsuite/g++.dg/cpp2a/consteval-memfn1.C
index 46eed13446d..ca923519f98 100644
--- a/gcc/testsuite/g++.dg/cpp2a/consteval-memfn1.C
+++ b/gcc/testsuite/g++.dg/cpp2a/consteval-memfn1.C
@@ -20,10 +20,13 @@ template<class>
 void VerifyHash(fixed_string s) {
   s.size(0); // { dg-bogus "" }
   s.size(-1); // { dg-message "expansion of" }
+// { dg-error "call to consteval function" "" { target *-*-* } .-1 }
   s.size_static(0); // { dg-bogus "" }
   s.size_static(-1); // { dg-message "expansion of" }
+// { dg-error "call to consteval function" "" { target *-*-* } .-1 }
   fixed_string::size_static(0); // { dg-bogus "" }
   fixed_string::size_static(-1); // { dg-message "expansion of" }
+// { dg-error "call to consteval function" "" { target *-*-* } .-1 }
   s(); // { dg-bogus "" }
 }
 
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..5e7b208113f
--- /dev/null
+++ b/gcc/testsuite/g++.dg/cpp2a/consteval-prop1.C
@@ -0,0 +1,169 @@
+// 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); // { dg-message "immediate-escalating expression .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); // { dg-message "immediate-escalating expression .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; // { dg-message "immediate-escalating expression .id." }
+  (void) p;
+  return t;
+}
+
+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};
+}
+
+constexpr void
+f5_nt (int i)
+{
+  A a{i}; // { dg-error "call to consteval function|not a constant" }
+}
+
+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; // { dg-message ".int g. is not const" }
+
+B b2(f0<int>(cid<int>(g))); // { dg-error "call to consteval function|not usable" }
+
+struct C {
+  consteval C (int) {};
+};
+
+constexpr int
+f7 (auto t)
+{
+  C c(t); // { dg-message "immediate-escalating expression .c.C::C\\(t\\)." }
+  return 0;
+}
+
+int i1 = f7 (g); // { dg-error "call to consteval function|not usable" }
+
+struct Y {
+  int y;
+  int x = id (y);
+  consteval Y (int i) : y (id (i)) {}
+};
+
+Y y1(1);
+Y y2(g); // { dg-error "call to consteval function|not usable" }
+
+struct Y2 {
+  int y;
+  int x = id (y);
+  constexpr Y2 (auto i) : y (id (i)) {}
+};
+
+Y2 y3(1);
+Y2 y4(g); // { dg-error "call to consteval function|not usable" }
+
+auto l1 = [](int i) constexpr {
+  int t = id (i);
+  return id (0);
+};
+
+int (*pl1)(int) = l1; // { dg-error "call to consteval function|returns address of immediate function" }
+
+auto l2 = [](int i) {
+  int t = id (i);
+  return id (0);
+};
+
+int (*pl2)(int) = l2; // { dg-error "call to consteval function|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 "used before its definition" }
+  int a = [] (int u = undef ()) {
+    return u;
+  }();
+} s2; // { dg-error "call to consteval function" }
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..4e33e6e3d0e
--- /dev/null
+++ b/gcc/testsuite/g++.dg/cpp2a/consteval-prop10.C
@@ -0,0 +1,41 @@
+// 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; // { dg-message ".int non_const. is not const" }
+
+template<typename>
+constexpr int
+f2 (int i = id (non_const))
+{
+  return i;
+}
+
+constexpr int
+f3 (auto)
+{
+  return f2<int>(); // { dg-message "contains an immediate-escalating expression .id\\(non_const\\)." }
+}
+
+auto a = &f3<int>; // { dg-error "taking address of an immediate function" }
+
+void
+g (int i)
+{
+  f1<int> (42);
+  f1<int> (i);
+  f1<int> ();
+  f2<int> (42);
+  f2<int> (i);
+  f2<int> (); // { dg-error "call to consteval function .id\\(non_const\\). is not a constant expression" }
+// { dg-error ".non_const. is not usable in a constant expression" "" { target *-*-* } .-1 }
+}
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..aca9675cd53
--- /dev/null
+++ b/gcc/testsuite/g++.dg/cpp2a/consteval-prop11.C
@@ -0,0 +1,49 @@
+// 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
+foobar (auto i)
+{
+  return i + id (i);
+  /* { dg-begin-multiline-output "" }
+   return i + id (i);
+              ~~~^~~
+     { dg-end-multiline-output "" } */
+}
+
+void
+g (int x)
+{
+  foobar (x); // { dg-error "10:call to consteval function .foobar<int>\\(x\\). is not a constant expression" }
+// { dg-error ".x. is not a constant expression" "" { target *-*-* } .-1 }
+  /* { dg-begin-multiline-output "" }
+foobar (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|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..cdc1f6dc862
--- /dev/null
+++ b/gcc/testsuite/g++.dg/cpp2a/consteval-prop14.C
@@ -0,0 +1,78 @@
+// 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\\(\\(i \\* 2\\)\\)." }
+}
+
+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" }
+// { dg-error ".i. is not a constant expression" "" { target *-*-* } .-1 }
+  f1 (42);
+  f2 (i); // { dg-error "call to consteval function .f2<int>\\(i\\). is not a constant expression" }
+// { dg-error ".i. is not a constant expression" "" { target *-*-* } .-1 }
+  f2 (42);
+  f3 (i); // { dg-error "call to consteval function .f3<int>\\(i\\). is not a constant expression" }
+// { dg-error ".i. is not a constant expression" "" { target *-*-* } .-1 }
+  f3 (42);
+  f4 (i); // { dg-error "call to consteval function .f4<int>\\(i\\). is not a constant expression" }
+// { dg-error ".i. is not a constant expression" "" { target *-*-* } .-1 }
+  f4 (42);
+  f5 (i); // { dg-error "call to consteval function .f5<int>\\(i\\). is not a constant expression" }
+// { dg-error ".i. is not a constant expression" "" { target *-*-* } .-1 }
+  f5 (42);
+  f6 (i); // { dg-error "call to consteval function .f6<int>\\(i\\). is not a constant expression" }
+// { dg-error ".i. is not a constant expression" "" { target *-*-* } .-1 }
+  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..3341c510a9f
--- /dev/null
+++ b/gcc/testsuite/g++.dg/cpp2a/consteval-prop15.C
@@ -0,0 +1,107 @@
+// 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;
+}
+
+constexpr int
+f9 (auto i)
+{
+  if consteval {
+    return id(i);
+  }
+  return i;
+}
+
+constexpr int
+f10 (auto i)
+{
+  if not consteval {
+    (void) 0;
+  } else {
+    return id(i);
+  }
+  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" }
+// { dg-error ".non_const. is not a constant expression" "" { target *-*-* } .-1 }
+  f5 (42);
+  f5 (non_const); // { dg-error "call to consteval function .f5<int>\\(non_const\\). is not a constant expression" }
+// { dg-error ".non_const. is not a constant expression" "" { target *-*-* } .-1 }
+  f6 (42);
+  f6 (non_const);
+  f7 (42);
+  f7 (non_const);
+  f8 (42);
+  f8 (non_const);
+  f9 (42);
+  f9 (non_const);
+  f10 (42);
+  f10 (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..47ec9b60b6c
--- /dev/null
+++ b/gcc/testsuite/g++.dg/cpp2a/consteval-prop17.C
@@ -0,0 +1,17 @@
+// P2564R3
+// { dg-do compile { target c++20 } }
+// { dg-options "-fno-immediate-escalation" }
+
+consteval int id(int i) { return i; }
+
+constexpr int
+f (auto i)
+{
+  return id (i); // { dg-error "not a constant expression" }
+}
+
+int
+g ()
+{
+  return f (42);
+}
diff --git a/gcc/testsuite/g++.dg/cpp2a/consteval-prop18.C b/gcc/testsuite/g++.dg/cpp2a/consteval-prop18.C
new file mode 100644
index 00000000000..a18106f8e0f
--- /dev/null
+++ b/gcc/testsuite/g++.dg/cpp2a/consteval-prop18.C
@@ -0,0 +1,20 @@
+// P2564R3
+// { dg-do compile { target c++20 } }
+
+consteval int id(int i) { return i; }
+
+constexpr int
+f (auto t)
+{
+  return t + id (t);
+}
+
+constexpr int
+f2 (auto t)
+{
+  return t + f(t); // { dg-message "immediate-escalating expression .f<int>\\(t\\)." }
+}
+
+int z; // { dg-message "not const" }
+auto y1 = f2 (42);
+auto y2 = f2 (z); // { dg-error "value of .z. is not usable in a constant expression|call to consteval function" }
diff --git a/gcc/testsuite/g++.dg/cpp2a/consteval-prop19.C b/gcc/testsuite/g++.dg/cpp2a/consteval-prop19.C
new file mode 100644
index 00000000000..3ceb05e41f4
--- /dev/null
+++ b/gcc/testsuite/g++.dg/cpp2a/consteval-prop19.C
@@ -0,0 +1,7 @@
+// P2564R3
+// { dg-do compile { target c++20 } }
+
+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;  // { dg-error "call to consteval function|returns address of immediate function" }
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..30129a4a266
--- /dev/null
+++ b/gcc/testsuite/g++.dg/cpp2a/consteval-prop2.C
@@ -0,0 +1,90 @@
+// 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);		// { dg-message "immediate-escalating expression .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 "the value of .x. is not usable in a constant expression" }
+// { dg-message "immediate-escalating expression .id\\(x\\)." "" { target *-*-* } .-1 }
+}
+
+int i = hh<int>();              // { dg-error "call to consteval function|called in a constant expression" }
+				// error: hh<int>() is an immediate-escalating expression
+                                // outside of an immediate-escalating function
+struct A {
+  int x;
+  int y = id(x);
+};
+
+// [expr.const]#example-9 says:
+//   k<int> is not an immediate function because A(42) is a
+//   constant expression and thus not immediate-escalating
+// In the evaluation of A(42), the member x has just been initialized
+// to constant 42.  And A(42) is constant-evaluated because "An aggregate
+// initialization is an immediate invocation if it evaluates a default
+// member initializer that has a subexpression that is an
+// immediate-escalating expression."
+template<class T>
+constexpr int k(int) {
+  return A(42).y;
+}
+
+int
+test (int i)
+{
+  int r = g (42) + g(i);
+  int t = k<int>(42)
+	    + k<int>(i); // { dg-bogus "call to|constant" "" { xfail *-*-* } }
+  return r + t;
+}
+
+// Just like above, but make the call to id(x) actually a constant.
+struct A2 {
+  static constexpr int x = 42;
+  int y = id(x);
+};
+
+template<class T>
+constexpr int k2(int) {
+  return A2(42).y;
+}
+
+int
+test2 (int i)
+{
+  return k2<int>(42) + k2<int>(i);
+}
diff --git a/gcc/testsuite/g++.dg/cpp2a/consteval-prop20.C b/gcc/testsuite/g++.dg/cpp2a/consteval-prop20.C
new file mode 100644
index 00000000000..f1bb08e2dba
--- /dev/null
+++ b/gcc/testsuite/g++.dg/cpp2a/consteval-prop20.C
@@ -0,0 +1,21 @@
+// P2564R3
+// { dg-do compile { target c++20 } }
+// { dg-options "-Wno-c++23-extensions" }
+
+consteval int id(int i) { return i; }
+
+constexpr int
+f (auto i)
+{
+  return id (i);
+}
+
+void
+g ()
+{
+  auto p = &f<int>; // { dg-error "taking address" }
+  decltype(&f<int>) x;
+  if consteval {
+    auto q = &f<int>;
+  }
+}
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..f181cb32942
--- /dev/null
+++ b/gcc/testsuite/g++.dg/cpp2a/consteval-prop3.C
@@ -0,0 +1,27 @@
+// 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);
+
+auto a2 = &f2<char>; // { dg-error "taking address" }
+auto b2 = &f2<int>; // { dg-error "taking address" }
+
+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..3a2e09b17b0
--- /dev/null
+++ b/gcc/testsuite/g++.dg/cpp2a/consteval-prop4.C
@@ -0,0 +1,30 @@
+// P2564R3
+// { dg-do compile { target c++20 } }
+// 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..93ed398d9bf
--- /dev/null
+++ b/gcc/testsuite/g++.dg/cpp2a/consteval-prop6.C
@@ -0,0 +1,59 @@
+// 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(){} // { dg-error "call to consteval function" }
+
+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 "modification of .x. is not a constant expression" }
+};
+
+void
+test ()
+{
+  [[maybe_unused]] X x; // { dg-error "call to consteval function" }
+// { dg-message "promoted to an immediate function" "" { target *-*-* } .-1 }
+}
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..118cf576f14
--- /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 "call to consteval function|not usable" }
+constexpr auto a2 = f2 (non_const); // { dg-error "not a constant|not usable" }
+auto a3 = f3 (42);
+constexpr auto a4 = f4 (42);
+
+void
+g ()
+{
+   auto a5 = f5 (non_const); // { dg-error "not a constant|not usable" }
+   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|not usable" }
+   (void) f10 (42);
+   (void) g1 (non_const); // { dg-error "not a constant|not usable" }
+}
+
+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 "call to consteval function|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..080fc76f26e
--- /dev/null
+++ b/gcc/testsuite/g++.dg/cpp2a/consteval-prop8.C
@@ -0,0 +1,82 @@
+// 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};
+}
+
+void
+f3 (int i)
+{
+  A a{i}; // { dg-error "not a constant expression" }
+}
+
+inline void
+f7 (int i)
+{
+  A a{i}; // { dg-error "not a constant expression" }
+}
+
+constexpr void
+f8 (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/consteval11.C b/gcc/testsuite/g++.dg/cpp2a/consteval11.C
index 05cecea4502..c2ee3c7a82a 100644
--- a/gcc/testsuite/g++.dg/cpp2a/consteval11.C
+++ b/gcc/testsuite/g++.dg/cpp2a/consteval11.C
@@ -8,9 +8,11 @@ constexpr int a = bar (1);
 constexpr int b = bar (2);		// { dg-message "in 'constexpr' expansion of" }
 constexpr int c = 0 ? bar (3) : 1;
 const int d = bar (4);			// { dg-message "in 'constexpr' expansion of" }
+// { dg-error "call to consteval function" "" { target *-*-* } .-1 }
 const int e = 0 ? bar (5) : 1;
 int f = bar (1);
 int g = bar (6);			// { dg-message "in 'constexpr' expansion of" }
+// { dg-error "call to consteval function" "" { target *-*-* } .-1 }
 int h = 0 ? bar (7) : 1;
 
 void
@@ -20,25 +22,35 @@ foo ()
   constexpr int b = bar (2);		// { dg-message "in 'constexpr' expansion of" }
   constexpr int c = 0 ? bar (3) : 1;
   const int d = bar (4);		// { dg-message "in 'constexpr' expansion of" }
+// { dg-error "call to consteval function" "" { target *-*-* } .-1 }
   const int e = 0 ? bar (5) : 1;
   int f = bar (1);
   int g = bar (6);			// { dg-message "in 'constexpr' expansion of" }
+// { dg-error "call to consteval function" "" { target *-*-* } .-1 }
   int h = 0 ? bar (7) : 1;		// { dg-message "in 'constexpr' expansion of" }
+// { dg-error "call to consteval function" "" { target *-*-* } .-1 }
   h += 0 ? bar (8) : 1;			// { dg-message "in 'constexpr' expansion of" }
+// { dg-error "call to consteval function" "" { target *-*-* } .-1 }
   if (0)
     bar (9);				// { dg-message "in 'constexpr' expansion of" }
+// { dg-error "call to consteval function" "" { target *-*-* } .-1 }
   else
     bar (10);				// { dg-message "in 'constexpr' expansion of" }
+// { dg-error "call to consteval function" "" { target *-*-* } .-1 }
   if (1)
     bar (11);				// { dg-message "in 'constexpr' expansion of" }
+// { dg-error "call to consteval function" "" { target *-*-* } .-1 }
   else
     bar (12);				// { dg-message "in 'constexpr' expansion of" }
+// { dg-error "call to consteval function" "" { target *-*-* } .-1 }
   if constexpr (0)
     bar (13);
   else
     bar (14);				// { dg-message "in 'constexpr' expansion of" }
+// { dg-error "call to consteval function" "" { target *-*-* } .-1 }
   if constexpr (1)
     bar (15);				// { dg-message "in 'constexpr' expansion of" }
+// { dg-error "call to consteval function" "" { target *-*-* } .-1 }
   else
     bar (16);
 }
@@ -121,18 +133,24 @@ quux ()
 {
   if (0)
     bar ((T) 2);				// { dg-message "in 'constexpr' expansion of" }
+// { dg-error "call to consteval function" "" { target *-*-* } .-1 }
   else
     bar ((T) 3);				// { dg-message "in 'constexpr' expansion of" }
+// { dg-error "call to consteval function" "" { target *-*-* } .-1 }
   if (1)
     bar ((T) 4);				// { dg-message "in 'constexpr' expansion of" }
+// { dg-error "call to consteval function" "" { target *-*-* } .-1 }
   else
     bar ((T) 5);				// { dg-message "in 'constexpr' expansion of" }
+// { dg-error "call to consteval function" "" { target *-*-* } .-1 }
   if constexpr (0)
     bar ((T) 6);
   else
     bar ((T) 7);				// { dg-message "in 'constexpr' expansion of" }
+// { dg-error "call to consteval function" "" { target *-*-* } .-1 }
   if constexpr (1)
     bar ((T) 8);				// { dg-message "in 'constexpr' expansion of" }
+// { dg-error "call to consteval function" "" { target *-*-* } .-1 }
   else
     bar ((T) 9);
 }
diff --git a/gcc/testsuite/g++.dg/cpp2a/consteval3.C b/gcc/testsuite/g++.dg/cpp2a/consteval3.C
index 9efac8c8eae..1199e9db623 100644
--- a/gcc/testsuite/g++.dg/cpp2a/consteval3.C
+++ b/gcc/testsuite/g++.dg/cpp2a/consteval3.C
@@ -16,8 +16,8 @@ consteval auto [ b, c ] = S ();		// { dg-error "structured binding declaration c
 int f5 (consteval int x) { return x; }	// { dg-error "a parameter cannot be declared 'consteval'" }
 consteval int f6 (int x) { return x; }
 int d = 6;		// { dg-message "'int d' is not const" }
-int e = f6 (d);		// { dg-error "the value of 'd' is not usable in a constant expression" }
-constexpr int f7 (int x) { return f6 (x); }	// { dg-error "'x' is not a constant expression" }
+int e = f6 (d);		// { dg-error "the value of 'd' is not usable in a constant expression|call to consteval function" }
+constexpr int f7 (int x) { return f6 (x); }	// { dg-error "'x' is not a constant expression|call to consteval function" }
 constexpr int f = f7 (5);
 using fnptr = int (int);
 fnptr *g = f6;		// { dg-error "taking address of an immediate function 'consteval int f6\\(int\\)'" }
diff --git a/gcc/testsuite/g++.dg/cpp2a/consteval34.C b/gcc/testsuite/g++.dg/cpp2a/consteval34.C
index 068827ba516..7562f403f74 100644
--- a/gcc/testsuite/g++.dg/cpp2a/consteval34.C
+++ b/gcc/testsuite/g++.dg/cpp2a/consteval34.C
@@ -7,6 +7,7 @@ constexpr int
 foo (bool b)
 {
   return b ? bar (3) : 2; // { dg-message "in .constexpr. expansion" }
+// { dg-error "call to consteval function" "" { target *-*-* } .-1 }
 }
 
 static_assert (foo (false) == 2);
@@ -22,13 +23,20 @@ void
 g ()
 {
   __extension__ int a1[bar(3)]; // { dg-message "in .constexpr. expansion" }
+// { dg-error "call to consteval function" "" { target *-*-* } .-1 }
   int a2[sizeof (bar(3))];
 
   int a3 = false ? (1 + bar (8)) : 1; // { dg-message "in .constexpr. expansion" }
+// { dg-error "call to consteval function" "" { target *-*-* } .-1 }
   a3 += false ? (1 + bar (8)) : 1; // { dg-message "in .constexpr. expansion" }
+// { dg-error "call to consteval function" "" { target *-*-* } .-1 }
 
   __extension__ int a4 = false ?: (1 + bar (8)); // { dg-message "in .constexpr. expansion" }
+// { dg-error "call to consteval function" "" { target *-*-* } .-1 }
   __extension__ int a5 = true ?: (1 + bar (8)); // { dg-message "in .constexpr. expansion" }
+// { dg-error "call to consteval function" "" { target *-*-* } .-1 }
   int a6 = bar (2) ? 1 : 2; // { dg-message "in .constexpr. expansion" }
+// { dg-error "call to consteval function" "" { target *-*-* } .-1 }
   int a7 = bar (2) - 1 ? 1 : 2; // { dg-message "in .constexpr. expansion" }
+// { dg-error "call to consteval function" "" { target *-*-* } .-1 }
 }
diff --git a/gcc/testsuite/g++.dg/cpp2a/consteval36.C b/gcc/testsuite/g++.dg/cpp2a/consteval36.C
index 9c470e4b7d7..8e27f2e33c6 100644
--- a/gcc/testsuite/g++.dg/cpp2a/consteval36.C
+++ b/gcc/testsuite/g++.dg/cpp2a/consteval36.C
@@ -6,17 +6,17 @@ consteval int id (int i) { return i; }
 void
 g (int i)
 {
-  1 ? 1 : ((1 ? 1 : 1), id (i)); // { dg-error "'i' is not a constant expression" }
-  1 ? 1 : ((1 ? 1 : 1), id (i), 1); // { dg-error "'i' is not a constant expression" }
-  1 ? 1 : ((i ? 1 : 1), id (i), 1); // { dg-error "'i' is not a constant expression" }
-  1 ? 1 : ((1 ? i : 1), id (i), 1); // { dg-error "'i' is not a constant expression" }
-  1 ? 1 : ((1 ? 1 : i), id (i), 1); // { dg-error "'i' is not a constant expression" }
-  1 ? 1 : ((i ? -i : i), id (i), 1); // { dg-error "'i' is not a constant expression" }
-  1 ? 1 : ((1 ? 1 : id (i)), id (42), 1); // { dg-error "'i' is not a constant expression" }
-  1 ? 1 : ((1 ? 1 : id (42)), id (i)); // { dg-error "'i' is not a constant expression" }
-  1 ? 1 : ((1 ? 1 : id (42)), id (i), 1); // { dg-error "'i' is not a constant expression" }
-  id (i) ? 1 : ((1 ? 1 : 1), id (i)); // { dg-error "'i' is not a constant expression" }
-  1 ? 1 : ((1 ? 1 : id (i)), id (i)); // { dg-error "'i' is not a constant expression" }
-  1 ? id (i) : ((1 ? 1 : id (i)), id (i)); // { dg-error "'i' is not a constant expression" }
-  1 ? 1 : ((id (i) ? 1 : 1), id (i)); // { dg-error "'i' is not a constant expression" }
+  1 ? 1 : ((1 ? 1 : 1), id (i)); // { dg-error "call to consteval function|'i' is not a constant expression" }
+  1 ? 1 : ((1 ? 1 : 1), id (i), 1); // { dg-error "call to consteval function|'i' is not a constant expression" }
+  1 ? 1 : ((i ? 1 : 1), id (i), 1); // { dg-error "call to consteval function|'i' is not a constant expression" }
+  1 ? 1 : ((1 ? i : 1), id (i), 1); // { dg-error "call to consteval function|'i' is not a constant expression" }
+  1 ? 1 : ((1 ? 1 : i), id (i), 1); // { dg-error "call to consteval function|'i' is not a constant expression" }
+  1 ? 1 : ((i ? -i : i), id (i), 1); // { dg-error "call to consteval function|'i' is not a constant expression" }
+  1 ? 1 : ((1 ? 1 : id (i)), id (42), 1); // { dg-error "call to consteval function|'i' is not a constant expression" }
+  1 ? 1 : ((1 ? 1 : id (42)), id (i)); // { dg-error "call to consteval function|'i' is not a constant expression" }
+  1 ? 1 : ((1 ? 1 : id (42)), id (i), 1); // { dg-error "call to consteval function|'i' is not a constant expression" }
+  id (i) ? 1 : ((1 ? 1 : 1), id (i)); // { dg-error "call to consteval function|'i' is not a constant expression" }
+  1 ? 1 : ((1 ? 1 : id (i)), id (i)); // { dg-error "call to consteval function|'i' is not a constant expression" }
+  1 ? id (i) : ((1 ? 1 : id (i)), id (i)); // { dg-error "call to consteval function|'i' is not a constant expression" }
+  1 ? 1 : ((id (i) ? 1 : 1), id (i)); // { dg-error "call to consteval function|'i' is not a constant expression" }
 }
diff --git a/gcc/testsuite/g++.dg/cpp2a/consteval9.C b/gcc/testsuite/g++.dg/cpp2a/consteval9.C
index 051a3d4e355..ad882d51c9b 100644
--- a/gcc/testsuite/g++.dg/cpp2a/consteval9.C
+++ b/gcc/testsuite/g++.dg/cpp2a/consteval9.C
@@ -14,6 +14,7 @@ template <int N>
 void qux ()
 {
   int a = bar (N);	// { dg-message "in 'constexpr' expansion of 'bar\\(2\\)'" }
+// { dg-error "call to consteval function" "" { target *-*-* } .-1 }
 }
 
 // This function is not instantiated so NDR.
@@ -31,3 +32,4 @@ baz ()
 }
 
 int a = bar (2);	// { dg-message "in 'constexpr' expansion of 'bar\\(2\\)'" }
+// { dg-error "call to consteval function" "" { target *-*-* } .-1 }
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/gcc/testsuite/g++.dg/cpp2a/spaceship-synth9.C b/gcc/testsuite/g++.dg/cpp2a/spaceship-synth9.C
index 33b547d2b50..ecb46b016a6 100644
--- a/gcc/testsuite/g++.dg/cpp2a/spaceship-synth9.C
+++ b/gcc/testsuite/g++.dg/cpp2a/spaceship-synth9.C
@@ -22,6 +22,6 @@ struct Z: Y<int>
 int main()
 {
   X<char>() == X<char>();	// { dg-error "no match" }
-  X<int> x; x == x;		// { dg-error "x' is not usable in a constant expression" }
+  X<int> x; x == x;		// { dg-error "x' is not usable in a constant expression|call to consteval function" }
   Y<int>()  == Y<int>();	// { dg-warning "nodiscard" }
 }
diff --git a/libstdc++-v3/testsuite/18_support/comparisons/categories/zero_neg.cc b/libstdc++-v3/testsuite/18_support/comparisons/categories/zero_neg.cc
index 9d2115b3f4f..82f7cd54fba 100644
--- a/libstdc++-v3/testsuite/18_support/comparisons/categories/zero_neg.cc
+++ b/libstdc++-v3/testsuite/18_support/comparisons/categories/zero_neg.cc
@@ -52,3 +52,4 @@ test01()
 
 // { dg-prune-output "reinterpret_cast.* is not a constant expression" }
 // { dg-prune-output "cast from 'void.' is not allowed" }
+// { dg-prune-output "not a constant expression" }
diff --git a/libstdc++-v3/testsuite/std/format/string_neg.cc b/libstdc++-v3/testsuite/std/format/string_neg.cc
index 7a60ef8cf0e..69bcc736cff 100644
--- a/libstdc++-v3/testsuite/std/format/string_neg.cc
+++ b/libstdc++-v3/testsuite/std/format/string_neg.cc
@@ -2,5 +2,5 @@
 
 #include <format>
 
-auto s = std::format(" {9} ");
+auto s = std::format(" {9} "); // { dg-error "call to consteval function" }
 // { dg-error "invalid.arg.id" "" { target *-*-* } 0 }

base-commit: 886f256ce3be4aa85f30af88558f0dfcb8003300
-- 
2.43.0


^ permalink raw reply	[flat|nested] 19+ messages in thread

* Re: [PATCH v7] c++: implement P2564, consteval needs to propagate up [PR107687]
  2023-12-04 20:23                       ` [PATCH v7] " Marek Polacek
@ 2023-12-04 21:49                         ` Jason Merrill
  2023-12-05  0:44                           ` [PATCH v8] " Marek Polacek
  0 siblings, 1 reply; 19+ messages in thread
From: Jason Merrill @ 2023-12-04 21:49 UTC (permalink / raw)
  To: Marek Polacek; +Cc: GCC Patches

On 12/4/23 15:23, Marek Polacek wrote:
> +/* FN is not a consteval function, but may become one.  Remember to
> +   escalate it after all pending templates have been instantiated.  */
> +
> +void
> +maybe_store_immediate_escalating_fn (tree fn)
> +{
> +  if (unchecked_immediate_escalating_function_p (fn))
> +    remember_escalating_expr (fn);
> +}

> +++ b/gcc/cp/decl.cc
> @@ -18441,7 +18441,10 @@ finish_function (bool inline_p)
>    if (!processing_template_decl
>        && !DECL_IMMEDIATE_FUNCTION_P (fndecl)
>        && !DECL_OMP_DECLARE_REDUCTION_P (fndecl))
> -    cp_fold_function (fndecl);
> +    {
> +      cp_fold_function (fndecl);
> +      maybe_store_immediate_escalating_fn (fndecl);
> +    }

I think maybe_store_, and the call to it from finish_function, are 
unneeded; we will have already decided whether we need to remember the 
function during the call to cp_fold_function.

OK with that change.

Jason


^ permalink raw reply	[flat|nested] 19+ messages in thread

* [PATCH v8] c++: implement P2564, consteval needs to propagate up [PR107687]
  2023-12-04 21:49                         ` Jason Merrill
@ 2023-12-05  0:44                           ` Marek Polacek
  2023-12-06 11:39                             ` Prathamesh Kulkarni
  0 siblings, 1 reply; 19+ messages in thread
From: Marek Polacek @ 2023-12-05  0:44 UTC (permalink / raw)
  To: Jason Merrill; +Cc: GCC Patches

On Mon, Dec 04, 2023 at 04:49:29PM -0500, Jason Merrill wrote:
> On 12/4/23 15:23, Marek Polacek wrote:
> > +/* FN is not a consteval function, but may become one.  Remember to
> > +   escalate it after all pending templates have been instantiated.  */
> > +
> > +void
> > +maybe_store_immediate_escalating_fn (tree fn)
> > +{
> > +  if (unchecked_immediate_escalating_function_p (fn))
> > +    remember_escalating_expr (fn);
> > +}
> 
> > +++ b/gcc/cp/decl.cc
> > @@ -18441,7 +18441,10 @@ finish_function (bool inline_p)
> >    if (!processing_template_decl
> >        && !DECL_IMMEDIATE_FUNCTION_P (fndecl)
> >        && !DECL_OMP_DECLARE_REDUCTION_P (fndecl))
> > -    cp_fold_function (fndecl);
> > +    {
> > +      cp_fold_function (fndecl);
> > +      maybe_store_immediate_escalating_fn (fndecl);
> > +    }
> 
> I think maybe_store_, and the call to it from finish_function, are unneeded;
> we will have already decided whether we need to remember the function during
> the call to cp_fold_function.

'Tis true.
 
> OK with that change.

Here's what I pushed after another regtest.  Thanks!

-- >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:

w.C:11:11: error: call to consteval function 'f<int>(i)' is not a constant expression
   11 |         f (i);
      |         ~~^~~
w.C:11:11: error: 'i' is not a constant expression
w.C:6:22: 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); // id causes f<int> to be promoted to consteval
      |                    ~~^~~

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.

In v1, I addressed the delayed instantiation problem by instantiating
trees early, so that we can escalate functions right away.  That caused
a number of problems, and in certain cases, like consteval-prop3.C, it
can't work, because we need to wait till EOF to see the definition of
the function anyway.  Overeager instantiation tends to cause diagnostic
problems too.

In v2, I attempted to move the escalation to the gimplifier, at which
point all templates have been instantiated.  That attempt flopped,
however, because once we've gimplified a function, its body is discarded
and as a consequence, you can no longer evaluate a call to that function
which is required for escalating, which needs to decide if a call is
a constant expression or not.

Therefore, we have to perform the escalation before gimplifying, but
after instantiate_pending_templates.  That's not easy because we have
no way to walk all the trees.  In the v2 patch, I use two vectors: one
to store function decls that may become consteval, and another to
remember references to immediate-escalating functions.  Unfortunately
the latter must also stash functions that call immediate-escalating
functions.  Consider:

  int g(int i)
  {
    f<int>(i); // f is immediate-escalating
  }

where g itself is not immediate-escalating, but we have to make sure
that if f gets promoted to consteval, we give an error.

A new option, -fno-immediate-escalation, is provided to suppress
escalating functions.

v2 also adds a new flag, DECL_ESCALATION_CHECKED_P, so that we don't
escalate a function multiple times, and so that we can distinguish between
explicitly consteval functions and functions that have been promoted
to consteval.

In v3, I removed one of the new vectors and changed the other one
to a hash set.  This version also contains numerous cleanups.

v4 merges find_escalating_expr_r into cp_fold_immediate_r.  It also
adds a new optimization in cp_fold_function.

v5 greatly simplifies the code.

v6 simplifies the code further and removes an ff_ flag.

v7 removes maybe_promote_function_to_consteval and further simplifies
cp_fold_immediate_r logic.

v8 removes maybe_store_immediate_escalating_fn.

	PR c++/107687
	PR c++/110997

gcc/c-family/ChangeLog:

	* c-cppbuiltin.cc (c_cpp_builtins): Update __cpp_consteval.
	* c-opts.cc (c_common_post_options): Pre-C++20, unset
	flag_immediate_escalation.
	* c.opt (fimmediate-escalation): New option.

gcc/cp/ChangeLog:

	* call.cc (in_immediate_context): No longer static.
	* constexpr.cc (cxx_eval_call_expression): Adjust assert.
	* cp-gimplify.cc (deferred_escalating_exprs): New vec.
	(remember_escalating_expr): New.
	(enum fold_flags): Remove ff_fold_immediate.
	(immediate_escalating_function_p): New.
	(unchecked_immediate_escalating_function_p): New.
	(promote_function_to_consteval): New.
	(cp_fold_immediate): Move above.  Return non-null if any errors were
	emitted.
	(maybe_explain_promoted_consteval): New.
	(cp_gimplify_expr) <case CALL_EXPR>: Assert we've handled all
	immediate invocations.
	(taking_address_of_imm_fn_error): New.
	(cp_fold_immediate_r): Merge ADDR_EXPR and PTRMEM_CST cases.  Implement
	P2564 - promoting functions to consteval.
	<case CALL_EXPR>: Implement P2564 - promoting functions to consteval.
	(cp_fold_r): If an expression turns into a CALL_EXPR after cp_fold,
	call cp_fold_immediate_r on the CALL_EXPR.
	(cp_fold_function): Set DECL_ESCALATION_CHECKED_P if
	deferred_escalating_exprs does not contain current_function_decl.
	(process_and_check_pending_immediate_escalating_fns): New.
	* cp-tree.h (struct lang_decl_fn): Add escalated_p bit-field.
	(DECL_ESCALATION_CHECKED_P): New.
	(immediate_invocation_p): Declare.
	(process_pending_immediate_escalating_fns): Likewise.
	* decl2.cc (c_parse_final_cleanups): Set at_eof to 2 after all
	templates have been instantiated; and to 3 at the end of the function.
	Call process_pending_immediate_escalating_fns.
	* error.cc (dump_template_bindings): Check at_eof against an updated
	value.
	* module.cc (trees_out::lang_decl_bools): Stream escalated_p.
	(trees_in::lang_decl_bools): Likewise.
	* pt.cc (push_tinst_level_loc): Set at_eof to 3, not 2.
	* typeck.cc (cp_build_addr_expr_1): Don't check
	DECL_IMMEDIATE_FUNCTION_P.

gcc/ChangeLog:

	* doc/invoke.texi: Document -fno-immediate-escalation.

libstdc++-v3/ChangeLog:

	* testsuite/18_support/comparisons/categories/zero_neg.cc: Add
	dg-prune-output.
	* testsuite/std/format/string_neg.cc: Add dg-error.

gcc/testsuite/ChangeLog:

	* 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/consteval-memfn1.C: Add dg-error.
	* g++.dg/cpp2a/consteval11.C: Likewise.
	* g++.dg/cpp2a/consteval3.C: Adjust dg-error.
	* g++.dg/cpp2a/consteval34.C: Add dg-error.
	* g++.dg/cpp2a/consteval9.C: Likewise.
	* g++.dg/cpp2a/feat-cxx2a.C: Adjust expected value of __cpp_consteval.
	* g++.dg/cpp2a/spaceship-synth9.C: Adjust dg-error.
	* 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-prop18.C: New test.
	* g++.dg/cpp2a/consteval-prop19.C: New test.
	* g++.dg/cpp2a/consteval-prop20.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/c-family/c-opts.cc                        |   5 +
 gcc/c-family/c.opt                            |   4 +
 gcc/cp/call.cc                                |   2 +-
 gcc/cp/constexpr.cc                           |   4 +-
 gcc/cp/cp-gimplify.cc                         | 348 ++++++++++++++----
 gcc/cp/cp-tree.h                              |  19 +-
 gcc/cp/decl2.cc                               |  16 +-
 gcc/cp/error.cc                               |   2 +-
 gcc/cp/module.cc                              |   4 +
 gcc/cp/pt.cc                                  |   2 +-
 gcc/cp/typeck.cc                              |   6 +-
 gcc/doc/invoke.texi                           |  34 ++
 gcc/testsuite/g++.dg/cpp23/consteval-if10.C   |   7 +-
 gcc/testsuite/g++.dg/cpp23/consteval-if2.C    |  14 +-
 gcc/testsuite/g++.dg/cpp23/feat-cxx2b.C       |   4 +-
 gcc/testsuite/g++.dg/cpp26/feat-cxx26.C       |   4 +-
 gcc/testsuite/g++.dg/cpp2a/consteval-memfn1.C |   3 +
 gcc/testsuite/g++.dg/cpp2a/consteval-prop1.C  | 169 +++++++++
 gcc/testsuite/g++.dg/cpp2a/consteval-prop10.C |  41 +++
 gcc/testsuite/g++.dg/cpp2a/consteval-prop11.C |  49 +++
 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 |  78 ++++
 gcc/testsuite/g++.dg/cpp2a/consteval-prop15.C | 107 ++++++
 gcc/testsuite/g++.dg/cpp2a/consteval-prop16.C |  73 ++++
 gcc/testsuite/g++.dg/cpp2a/consteval-prop17.C |  17 +
 gcc/testsuite/g++.dg/cpp2a/consteval-prop18.C |  20 +
 gcc/testsuite/g++.dg/cpp2a/consteval-prop19.C |   7 +
 gcc/testsuite/g++.dg/cpp2a/consteval-prop2.C  |  90 +++++
 gcc/testsuite/g++.dg/cpp2a/consteval-prop20.C |  21 ++
 gcc/testsuite/g++.dg/cpp2a/consteval-prop3.C  |  27 ++
 gcc/testsuite/g++.dg/cpp2a/consteval-prop4.C  |  30 ++
 gcc/testsuite/g++.dg/cpp2a/consteval-prop5.C  |  27 ++
 gcc/testsuite/g++.dg/cpp2a/consteval-prop6.C  |  59 +++
 gcc/testsuite/g++.dg/cpp2a/consteval-prop7.C  |  76 ++++
 gcc/testsuite/g++.dg/cpp2a/consteval-prop8.C  |  82 +++++
 gcc/testsuite/g++.dg/cpp2a/consteval-prop9.C  |  67 ++++
 gcc/testsuite/g++.dg/cpp2a/consteval11.C      |  18 +
 gcc/testsuite/g++.dg/cpp2a/consteval3.C       |   4 +-
 gcc/testsuite/g++.dg/cpp2a/consteval34.C      |   8 +
 gcc/testsuite/g++.dg/cpp2a/consteval36.C      |  26 +-
 gcc/testsuite/g++.dg/cpp2a/consteval9.C       |   2 +
 gcc/testsuite/g++.dg/cpp2a/feat-cxx2a.C       |   4 +-
 gcc/testsuite/g++.dg/cpp2a/spaceship-synth9.C |   2 +-
 .../comparisons/categories/zero_neg.cc        |   1 +
 .../testsuite/std/format/string_neg.cc        |   2 +-
 47 files changed, 1525 insertions(+), 115 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-prop18.C
 create mode 100644 gcc/testsuite/g++.dg/cpp2a/consteval-prop19.C
 create mode 100644 gcc/testsuite/g++.dg/cpp2a/consteval-prop2.C
 create mode 100644 gcc/testsuite/g++.dg/cpp2a/consteval-prop20.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

diff --git a/gcc/c-family/c-cppbuiltin.cc b/gcc/c-family/c-cppbuiltin.cc
index e536429fa4c..2d1249f29ed 100644
--- a/gcc/c-family/c-cppbuiltin.cc
+++ b/gcc/c-family/c-cppbuiltin.cc
@@ -1059,7 +1059,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/c-family/c-opts.cc b/gcc/c-family/c-opts.cc
index d7faff10d66..d484ecfdfdb 100644
--- a/gcc/c-family/c-opts.cc
+++ b/gcc/c-family/c-opts.cc
@@ -1139,6 +1139,11 @@ c_common_post_options (const char **pfilename)
   if (cxx_dialect >= cxx20 || flag_concepts_ts)
     flag_concepts = 1;
 
+  /* -fimmediate-escalation has no effect when immediate functions are not
+     supported.  */
+  if (flag_immediate_escalation && cxx_dialect < cxx20)
+    flag_immediate_escalation = 0;
+
   if (num_in_fnames > 1)
     error ("too many filenames given; type %<%s %s%> for usage",
 	   progname, "--help");
diff --git a/gcc/c-family/c.opt b/gcc/c-family/c.opt
index ab44a6da66a..3706505f8bf 100644
--- a/gcc/c-family/c.opt
+++ b/gcc/c-family/c.opt
@@ -1898,6 +1898,10 @@ fhuge-objects
 C++ ObjC++ WarnRemoved
 No longer supported.
 
+fimmediate-escalation
+C++ ObjC++ Var(flag_immediate_escalation) Init(1)
+Implement P2564 for consteval propagation.
+
 fimplement-inlines
 C++ ObjC++ Var(flag_implement_inlines) Init(1)
 Export functions even if they can be inlined.
diff --git a/gcc/cp/call.cc b/gcc/cp/call.cc
index ae0decd87f1..c7efc5b077a 100644
--- a/gcc/cp/call.cc
+++ b/gcc/cp/call.cc
@@ -9742,7 +9742,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
diff --git a/gcc/cp/constexpr.cc b/gcc/cp/constexpr.cc
index 96c61666470..58187a4fd12 100644
--- a/gcc/cp/constexpr.cc
+++ b/gcc/cp/constexpr.cc
@@ -3128,11 +3128,11 @@ cxx_eval_call_expression (const constexpr_ctx *ctx, tree t,
 	/* OK */;
       else if (!DECL_SAVED_TREE (fun))
 	{
-	  /* When at_eof >= 2, cgraph has started throwing away
+	  /* When at_eof >= 3, cgraph has started throwing away
 	     DECL_SAVED_TREE, so fail quietly.  FIXME we get here because of
 	     late code generation for VEC_INIT_EXPR, which needs to be
 	     completely reconsidered.  */
-	  gcc_assert (at_eof >= 2 && ctx->quiet);
+	  gcc_assert (at_eof >= 3 && ctx->quiet);
 	  *non_constant_p = true;
 	}
       else if (tree copy = get_fundef_copy (new_call.fundef))
diff --git a/gcc/cp/cp-gimplify.cc b/gcc/cp/cp-gimplify.cc
index 795c811471d..5abb91bbdd3 100644
--- a/gcc/cp/cp-gimplify.cc
+++ b/gcc/cp/cp-gimplify.cc
@@ -43,6 +43,21 @@ along with GCC; see the file COPYING3.  If not see
 #include "omp-general.h"
 #include "opts.h"
 
+/* Keep track of forward references to immediate-escalating functions in
+   case they become consteval.  This vector contains ADDR_EXPRs and
+   PTRMEM_CSTs; it also stores FUNCTION_DECLs that had an escalating
+   function call in them, to check that they can be evaluated to a constant,
+   and immediate-escalating functions that may become consteval.  */
+static GTY(()) hash_set<tree> *deferred_escalating_exprs;
+
+static void
+remember_escalating_expr (tree t)
+{
+  if (!deferred_escalating_exprs)
+    deferred_escalating_exprs = hash_set<tree>::create_ggc (37);
+  deferred_escalating_exprs->add (t);
+}
+
 /* Flags for cp_fold and cp_fold_r.  */
 
 enum fold_flags {
@@ -53,8 +68,6 @@ enum fold_flags {
      definitely not in a manifestly constant-evaluated
      context.  */
   ff_mce_false = 1 << 1,
-  /* Whether we're being called from cp_fold_immediate.  */
-  ff_fold_immediate = 1 << 2,
 };
 
 using fold_flags_t = int;
@@ -72,6 +85,7 @@ static tree cp_genericize_r (tree *, int *, void *);
 static tree cp_fold_r (tree *, int *, void *);
 static void cp_genericize_tree (tree*, bool);
 static tree cp_fold (tree, fold_flags_t);
+static tree cp_fold_immediate_r (tree *, int *, void *);
 
 /* Genericize a TRY_BLOCK.  */
 
@@ -428,6 +442,104 @@ lvalue_has_side_effects (tree e)
     return TREE_SIDE_EFFECTS (e);
 }
 
+/* Return true if FN is an immediate-escalating function.  */
+
+static bool
+immediate_escalating_function_p (tree fn)
+{
+  if (!fn || !flag_immediate_escalation)
+    return false;
+
+  gcc_checking_assert (TREE_CODE (fn) == FUNCTION_DECL);
+
+  if (DECL_IMMEDIATE_FUNCTION_P (fn))
+    return false;
+
+  /* An immediate-escalating function is
+      -- the call operator of a lambda that is not declared with the consteval
+	 specifier  */
+  if (LAMBDA_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))
+    return true;
+  /* -- a function that results from the instantiation of a templated entity
+	defined with the constexpr specifier.  */
+  return is_instantiation_of_constexpr (fn);
+}
+
+/* Return true if FN is an immediate-escalating function that has not been
+   checked for escalating expressions..  */
+
+static bool
+unchecked_immediate_escalating_function_p (tree fn)
+{
+  return (immediate_escalating_function_p (fn)
+	  && !DECL_ESCALATION_CHECKED_P (fn));
+}
+
+/* Promote FN to an immediate function, including its clones.  */
+
+static void
+promote_function_to_consteval (tree fn)
+{
+  SET_DECL_IMMEDIATE_FUNCTION_P (fn);
+  DECL_ESCALATION_CHECKED_P (fn) = true;
+  tree clone;
+  FOR_EACH_CLONE (clone, fn)
+    {
+      SET_DECL_IMMEDIATE_FUNCTION_P (clone);
+      DECL_ESCALATION_CHECKED_P (clone) = true;
+    }
+}
+
+/* A wrapper around cp_fold_immediate_r.  Return a non-null tree if
+   we found a non-constant immediate function, or taking the address
+   of an immediate function.  */
+
+tree
+cp_fold_immediate (tree *tp, mce_value manifestly_const_eval,
+		   tree decl /*= current_function_decl*/)
+{
+  if (cxx_dialect <= cxx17)
+    return NULL_TREE;
+
+  temp_override<tree> cfd (current_function_decl, decl);
+
+  fold_flags_t flags = ff_none;
+  if (manifestly_const_eval == mce_false)
+    flags |= ff_mce_false;
+
+  cp_fold_data data (flags);
+  int save_errorcount = errorcount;
+  tree r = cp_walk_tree_without_duplicates (tp, cp_fold_immediate_r, &data);
+  if (errorcount > save_errorcount)
+    return integer_one_node;
+  return r;
+}
+
+/* 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)
+{
+  if (DECL_ESCALATION_CHECKED_P (fn))
+    {
+      /* See if we can figure out what made the function consteval.  */
+      tree x = cp_fold_immediate (&DECL_SAVED_TREE (fn), mce_unknown, NULL_TREE);
+      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.  */
 
@@ -746,7 +858,9 @@ cp_gimplify_expr (tree *expr_p, gimple_seq *pre_p, gimple_seq *post_p)
       if (ret != GS_ERROR)
 	{
 	  tree decl = cp_get_callee_fndecl_nofold (*expr_p);
-	  if (decl && fndecl_built_in_p (decl, BUILT_IN_FRONTEND))
+	  if (!decl)
+	    break;
+	  if (fndecl_built_in_p (decl, BUILT_IN_FRONTEND))
 	    switch (DECL_FE_FUNCTION_CODE (decl))
 	      {
 	      case CP_BUILT_IN_IS_CONSTANT_EVALUATED:
@@ -771,10 +885,12 @@ cp_gimplify_expr (tree *expr_p, gimple_seq *pre_p, gimple_seq *post_p)
 	      default:
 		break;
 	      }
-	  else if (decl
-		   && fndecl_built_in_p (decl, BUILT_IN_CLZG, BUILT_IN_CTZG))
+	  else if (fndecl_built_in_p (decl, BUILT_IN_CLZG, BUILT_IN_CTZG))
 	    ret = (enum gimplify_status) c_gimplify_expr (expr_p, pre_p,
 							  post_p);
+	  else
+	    /* All consteval functions should have been processed by now.  */
+	    gcc_checking_assert (!immediate_invocation_p (decl));
 	}
       break;
 
@@ -1035,6 +1151,20 @@ struct cp_genericize_data
   bool handle_invisiref_parm_p;
 };
 
+/* Emit an error about taking the address of an immediate function.
+   EXPR is the whole expression; DECL is the immediate function.  */
+
+static void
+taking_address_of_imm_fn_error (tree expr, tree decl)
+{
+  auto_diagnostic_group d;
+  const location_t loc = (TREE_CODE (expr) == PTRMEM_CST
+			  ? PTRMEM_CST_LOCATION (expr)
+			  : EXPR_LOCATION (expr));
+  error_at (loc, "taking address of an immediate function %qD", decl);
+  maybe_explain_promoted_consteval (loc, decl);
+}
+
 /* A subroutine of cp_fold_r to handle immediate functions.  */
 
 static tree
@@ -1045,90 +1175,128 @@ cp_fold_immediate_r (tree *stmt_p, int *walk_subtrees, void *data_)
   /* The purpose of this is not to emit errors for mce_unknown.  */
   const tsubst_flags_t complain = (data->flags & ff_mce_false
 				   ? tf_error : tf_none);
+  const tree_code code = TREE_CODE (stmt);
 
   /* No need to look into types or unevaluated operands.
      NB: This affects cp_fold_r as well.  */
-  if (TYPE_P (stmt) || unevaluated_p (TREE_CODE (stmt)))
+  if (TYPE_P (stmt) || unevaluated_p (code) || in_immediate_context ())
     {
       *walk_subtrees = 0;
       return NULL_TREE;
     }
 
-  switch (TREE_CODE (stmt))
-    {
-    case PTRMEM_CST:
-      if (TREE_CODE (PTRMEM_CST_MEMBER (stmt)) == FUNCTION_DECL
-	  && DECL_IMMEDIATE_FUNCTION_P (PTRMEM_CST_MEMBER (stmt)))
-	{
-	  if (!data->pset.add (stmt) && (complain & tf_error))
-	    {
-	      error_at (PTRMEM_CST_LOCATION (stmt),
-			"taking address of an immediate function %qD",
-			PTRMEM_CST_MEMBER (stmt));
-	      *stmt_p = build_zero_cst (TREE_TYPE (stmt));
-	    }
-	  return error_mark_node;
-	}
-      break;
+  tree decl = NULL_TREE;
+  bool call_p = false;
 
-    /* Expand immediate invocations.  */
+  /* We are looking for &fn or fn().  */
+  switch (code)
+    {
     case CALL_EXPR:
     case AGGR_INIT_EXPR:
       if (tree fn = cp_get_callee (stmt))
 	if (TREE_CODE (fn) != ADDR_EXPR || ADDR_EXPR_DENOTES_CALL_P (fn))
-	  if (tree fndecl = cp_get_fndecl_from_callee (fn, /*fold*/false))
-	    if (DECL_IMMEDIATE_FUNCTION_P (fndecl))
-	      {
-		stmt = cxx_constant_value (stmt, complain);
-		if (stmt == error_mark_node)
-		  {
-		    if (complain & tf_error)
-		      *stmt_p = error_mark_node;
-		    return error_mark_node;
-		  }
-		*stmt_p = stmt;
-	      }
+	  decl = cp_get_fndecl_from_callee (fn, /*fold*/false);
+      call_p = true;
+      break;
+    case PTRMEM_CST:
+      decl = PTRMEM_CST_MEMBER (stmt);
       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))
-	{
-	  if (complain & tf_error)
-	    {
-	      error_at (EXPR_LOCATION (stmt),
-			"taking address of an immediate function %qD",
-			TREE_OPERAND (stmt, 0));
-	      *stmt_p = build_zero_cst (TREE_TYPE (stmt));
-	    }
-	  return error_mark_node;
-	}
+      if (!ADDR_EXPR_DENOTES_CALL_P (stmt))
+	decl = TREE_OPERAND (stmt, 0);
       break;
-
     default:
-      break;
+      return NULL_TREE;
     }
 
-  return NULL_TREE;
-}
+  if (!decl || TREE_CODE (decl) != FUNCTION_DECL)
+    return NULL_TREE;
 
-/* A wrapper around cp_fold_immediate_r.  Return true if we found
-   a non-constant immediate function, or taking the address of an
-   immediate function.  */
+  /* Fully escalate once all templates have been instantiated.  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 (at_eof > 1 && unchecked_immediate_escalating_function_p (decl))
+    {
+      /* Set before the actual walk to avoid endless recursion.  */
+      DECL_ESCALATION_CHECKED_P (decl) = true;
+      /* We're only looking for the first escalating expression.  Let us not
+	 walk more trees than necessary, hence mce_unknown.  */
+      cp_fold_immediate (&DECL_SAVED_TREE (decl), mce_unknown, decl);
+    }
 
-bool
-cp_fold_immediate (tree *tp, mce_value manifestly_const_eval)
-{
-  if (cxx_dialect <= cxx17)
-    return false;
+  /* [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."
 
-  fold_flags_t flags = ff_fold_immediate;
-  if (manifestly_const_eval == mce_false)
-    flags |= ff_mce_false;
+     If we are in an immediate-escalating function, the immediate-escalating
+     expression or conversion makes it an immediate function.  So STMT does
+     not need to produce a constant expression.  */
+  if (DECL_IMMEDIATE_FUNCTION_P (decl))
+    {
+      tree e = cxx_constant_value (stmt, tf_none);
+      if (e == error_mark_node)
+	{
+	  /* This takes care of, e.g.,
+	      template <typename T>
+	      constexpr int f(T t)
+	      {
+		return id(t);
+	      }
+	    where id (consteval) causes f<int> to be promoted.  */
+	  if (immediate_escalating_function_p (current_function_decl))
+	    promote_function_to_consteval (current_function_decl);
+	  else if (complain & tf_error)
+	    {
+	      if (call_p)
+		{
+		  auto_diagnostic_group d;
+		  location_t loc = cp_expr_loc_or_input_loc (stmt);
+		  error_at (loc, "call to consteval function %qE is "
+			    "not a constant expression", stmt);
+		  /* Explain why it's not a constant expression.  */
+		  *stmt_p = cxx_constant_value (stmt, complain);
+		  maybe_explain_promoted_consteval (loc, decl);
+		}
+	      else if (!data->pset.add (stmt))
+		{
+		  taking_address_of_imm_fn_error (stmt, decl);
+		  *stmt_p = build_zero_cst (TREE_TYPE (stmt));
+		}
+	      /* If we're giving hard errors, continue the walk rather than
+		 bailing out after the first error.  */
+	      return NULL_TREE;
+	    }
+	  *walk_subtrees = 0;
+	  return stmt;
+	}
+      /* We've evaluated the consteval function call.  */
+      if (call_p)
+	*stmt_p = e;
+    }
+  /* We've encountered a function call that may turn out to be consteval
+     later.  Store its caller so that we can ensure that the call is
+     a constant expression.  */
+  else if (unchecked_immediate_escalating_function_p (decl))
+    {
+      /* Make sure we're not inserting new elements while walking
+	 the deferred_escalating_exprs hash table; if we are, it's
+	 likely that a function wasn't properly marked checked for
+	 i-e expressions.  */
+      gcc_checking_assert (at_eof <= 1);
+      if (current_function_decl)
+	remember_escalating_expr (current_function_decl);
+      /* auto p = &f<int>; in the global scope won't be ensconced in
+	 a function we could store for later at this point.  (If there's
+	 no c_f_d at this point and we're dealing with a call, we should
+	 see the call when cp_fold_function __static_i_and_d.)  */
+      else if (!call_p)
+	remember_escalating_expr (stmt);
+    }
 
-  cp_fold_data data (flags);
-  return !!cp_walk_tree_without_duplicates (tp, cp_fold_immediate_r, &data);
+  return NULL_TREE;
 }
 
 /* Perform any pre-gimplification folding of C++ front end trees to
@@ -1178,11 +1346,19 @@ cp_fold_r (tree *stmt_p, int *walk_subtrees, void *data_)
 	  *walk_subtrees = 0;
 	  /* Don't return yet, still need the cp_fold below.  */
 	}
-      cp_fold_immediate_r (stmt_p, walk_subtrees, data);
+      else
+	cp_fold_immediate_r (stmt_p, walk_subtrees, data);
     }
 
   *stmt_p = stmt = cp_fold (*stmt_p, data->flags);
 
+  /* For certain trees, like +foo(), the cp_fold above will remove the +,
+     and the subsequent tree walk would go straight down to the CALL_EXPR's
+     operands, meaning that cp_fold_immediate_r would never see the
+     CALL_EXPR.  Ew :(.  */
+  if (TREE_CODE (stmt) == CALL_EXPR && code != CALL_EXPR)
+    cp_fold_immediate_r (stmt_p, walk_subtrees, data);
+
   if (data->pset.add (stmt))
     {
       /* Don't walk subtrees of stmts we've already walked once, otherwise
@@ -1304,6 +1480,44 @@ cp_fold_function (tree fndecl)
      pass ff_mce_false.  */
   cp_fold_data data (ff_genericize | ff_mce_false);
   cp_walk_tree (&DECL_SAVED_TREE (fndecl), cp_fold_r, &data, NULL);
+
+  /* This is merely an optimization: if FNDECL has no i-e expressions,
+     we'll not save c_f_d, and we can safely say that FNDECL will not
+     be promoted to consteval.  */
+  if (deferred_escalating_exprs
+      && !deferred_escalating_exprs->contains (current_function_decl))
+    DECL_ESCALATION_CHECKED_P (fndecl) = true;
+}
+
+/* We've stashed immediate-escalating functions.  Now see if they indeed
+   ought to be promoted to consteval.  */
+
+void
+process_and_check_pending_immediate_escalating_fns ()
+{
+  /* This will be null for -fno-immediate-escalation.  */
+  if (!deferred_escalating_exprs)
+    return;
+
+  for (auto e : *deferred_escalating_exprs)
+    if (TREE_CODE (e) == FUNCTION_DECL && !DECL_ESCALATION_CHECKED_P (e))
+      cp_fold_immediate (&DECL_SAVED_TREE (e), mce_false, e);
+
+  /* We've escalated every function that could have been promoted to
+     consteval.  Check that we are not taking the address of a consteval
+     function.  */
+  for (auto e : *deferred_escalating_exprs)
+    {
+      if (TREE_CODE (e) == FUNCTION_DECL)
+	continue;
+      tree decl = (TREE_CODE (e) == PTRMEM_CST
+		   ? PTRMEM_CST_MEMBER (e)
+		   : TREE_OPERAND (e, 0));
+      if (DECL_IMMEDIATE_FUNCTION_P (decl))
+	taking_address_of_imm_fn_error (e, decl);
+    }
+
+  deferred_escalating_exprs = nullptr;
 }
 
 /* Turn SPACESHIP_EXPR EXPR into GENERIC.  */
diff --git a/gcc/cp/cp-tree.h b/gcc/cp/cp-tree.h
index 6b3ce9d87da..fef80816c5f 100644
--- a/gcc/cp/cp-tree.h
+++ b/gcc/cp/cp-tree.h
@@ -2946,8 +2946,9 @@ struct GTY(()) lang_decl_fn {
   unsigned maybe_deleted : 1;
   unsigned coroutine_p : 1;
   unsigned implicit_constexpr : 1;
+  unsigned escalated_p : 1;
 
-  unsigned spare : 9;
+  unsigned spare : 8;
 
   /* 32-bits padding on 64-bit host.  */
 
@@ -3399,6 +3400,14 @@ struct GTY(()) lang_decl {
 #define DECL_MAYBE_DELETED(NODE) \
   (LANG_DECL_FN_CHECK (NODE)->maybe_deleted)
 
+/* Nonzero for FUNCTION_DECL means that this function's body has been
+   checked for immediate-escalating expressions and maybe promoted.  It
+   does *not* mean the function is consteval.  It must not be set in
+   a function that was marked consteval by the user, so that we can
+   distinguish between explicitly consteval functions and promoted consteval
+   functions.  */
+#define DECL_ESCALATION_CHECKED_P(NODE) (LANG_DECL_FN_CHECK (NODE)->escalated_p)
+
 /* True (in a FUNCTION_DECL) if NODE is a virtual function that is an
    invalid overrider for a function from a base class.  Once we have
    complained about an invalid overrider we avoid complaining about it
@@ -5882,7 +5891,8 @@ extern GTY(()) vec<tree, va_gc> *keyed_classes;
 
 \f
 /* Nonzero if we're done parsing and into end-of-file activities.
-   Two if we're done with front-end processing.  */
+   2 if all templates have been instantiated.
+   3 if we're done with front-end processing.  */
 
 extern int at_eof;
 
@@ -6774,6 +6784,7 @@ 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 tree build_cxx_call			(tree, int, tree *,
 						 tsubst_flags_t,
 						 tree = NULL_TREE);
@@ -8415,7 +8426,9 @@ extern tree process_stmt_assume_attribute	(tree, tree, location_t);
 extern bool simple_empty_class_p		(tree, tree, tree_code);
 extern tree fold_builtin_source_location	(const_tree);
 extern tree get_source_location_impl_type	();
-extern bool cp_fold_immediate			(tree *, mce_value);
+extern tree cp_fold_immediate			(tree *, mce_value,
+						 tree = current_function_decl);
+extern void process_and_check_pending_immediate_escalating_fns ();
 
 /* in name-lookup.cc */
 extern tree strip_using_decl                    (tree);
diff --git a/gcc/cp/decl2.cc b/gcc/cp/decl2.cc
index 9e666e5eece..bee84879023 100644
--- a/gcc/cp/decl2.cc
+++ b/gcc/cp/decl2.cc
@@ -169,7 +169,9 @@ typedef hash_map<unsigned/*Priority*/, tree/*List*/,
    one for init.  The fini table is only ever used when !cxa_atexit.  */
 static GTY(()) priority_map_t *static_init_fini_fns[2];
 
-/* Nonzero if we're done parsing and into end-of-file activities.  */
+/* Nonzero if we're done parsing and into end-of-file activities.
+   2 if all templates have been instantiated.
+   3 if we're done with front-end processing.  */
 
 int at_eof;
 
@@ -4987,6 +4989,7 @@ c_parse_final_cleanups (void)
   tree decl;
 
   locus_at_end_of_parsing = input_location;
+  /* We're done parsing.  */
   at_eof = 1;
 
   /* Bad parse errors.  Just forget about it.  */
@@ -5252,6 +5255,9 @@ c_parse_final_cleanups (void)
 	reconsider = true;
     }
 
+  /* All templates have been instantiated.  */
+  at_eof = 2;
+
   void *module_cookie = finish_module_processing (parse_in);
 
   lower_var_init ();
@@ -5294,7 +5300,11 @@ c_parse_final_cleanups (void)
   if (static_init_fini_fns[true])
     for (auto iter : *static_init_fini_fns[true])
       iter.second = nreverse (iter.second);
-  
+
+  /* Now we've instantiated all templates.  Now we can escalate the functions
+     we squirreled away earlier.  */
+  process_and_check_pending_immediate_escalating_fns ();
+
   /* Then, do the Objective-C stuff.  This is where all the
      Objective-C module stuff gets generated (symtab,
      class/protocol/selector lists etc).  This must be done after C++
@@ -5376,7 +5386,7 @@ c_parse_final_cleanups (void)
   timevar_start (TV_PHASE_PARSING);
 
   /* Indicate that we're done with front end processing.  */
-  at_eof = 2;
+  at_eof = 3;
 }
 
 /* Perform any post compilation-proper cleanups for the C++ front-end.
diff --git a/gcc/cp/error.cc b/gcc/cp/error.cc
index 785909c362a..3b1b5de5ea4 100644
--- a/gcc/cp/error.cc
+++ b/gcc/cp/error.cc
@@ -478,7 +478,7 @@ dump_template_bindings (cxx_pretty_printer *pp, tree parms, tree args,
 
   /* Don't try to do this once cgraph starts throwing away front-end
      information.  */
-  if (at_eof >= 2)
+  if (at_eof >= 3)
     return;
 
   FOR_EACH_VEC_SAFE_ELT (typenames, i, t)
diff --git a/gcc/cp/module.cc b/gcc/cp/module.cc
index 33fcf396875..1b57fbe2124 100644
--- a/gcc/cp/module.cc
+++ b/gcc/cp/module.cc
@@ -5683,6 +5683,8 @@ trees_out::lang_decl_bools (tree t)
       WB (lang->u.fn.has_dependent_explicit_spec_p);
       WB (lang->u.fn.immediate_fn_p);
       WB (lang->u.fn.maybe_deleted);
+      WB (lang->u.fn.escalated_p);
+      /* We do not stream lang->u.fn.implicit_constexpr.  */
       goto lds_min;
 
     case lds_decomp:  /* lang_decl_decomp.  */
@@ -5751,6 +5753,8 @@ trees_in::lang_decl_bools (tree t)
       RB (lang->u.fn.has_dependent_explicit_spec_p);
       RB (lang->u.fn.immediate_fn_p);
       RB (lang->u.fn.maybe_deleted);
+      RB (lang->u.fn.escalated_p);
+      /* We do not stream lang->u.fn.implicit_constexpr.  */
       goto lds_min;
 
     case lds_decomp:  /* lang_decl_decomp.  */
diff --git a/gcc/cp/pt.cc b/gcc/cp/pt.cc
index 7265201e036..924a20973b4 100644
--- a/gcc/cp/pt.cc
+++ b/gcc/cp/pt.cc
@@ -11107,7 +11107,7 @@ push_tinst_level_loc (tree tldcl, tree targs, location_t loc)
   if (tinst_depth >= max_tinst_depth)
     {
       /* Tell error.cc not to try to instantiate any templates.  */
-      at_eof = 2;
+      at_eof = 3;
       fatal_error (input_location,
 		   "template instantiation depth exceeds maximum of %d"
 		   " (use %<-ftemplate-depth=%> to increase the maximum)",
diff --git a/gcc/cp/typeck.cc b/gcc/cp/typeck.cc
index bf8ffaa7e75..8e4cfae08aa 100644
--- a/gcc/cp/typeck.cc
+++ b/gcc/cp/typeck.cc
@@ -7269,11 +7269,9 @@ cp_build_addr_expr_1 (tree arg, bool strict_lvalue, tsubst_flags_t complain)
 			      complain);
     }
 
-  /* For addresses of immediate functions ensure we have EXPR_LOCATION
-     set for possible later diagnostics.  */
+  /* Ensure we have EXPR_LOCATION 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)))
+      && TREE_CODE (TREE_OPERAND (val, 0)) == FUNCTION_DECL)
     SET_EXPR_LOCATION (val, input_location);
 
   return val;
diff --git a/gcc/doc/invoke.texi b/gcc/doc/invoke.texi
index 2b51ff304f6..681e3f3f466 100644
--- a/gcc/doc/invoke.texi
+++ b/gcc/doc/invoke.texi
@@ -219,6 +219,7 @@ in the following sections.
 -fno-elide-constructors
 -fno-enforce-eh-specs
 -fno-gnu-keywords
+-fno-immediate-escalation
 -fno-implicit-templates
 -fno-implicit-inline-templates
 -fno-implement-inlines
@@ -3386,6 +3387,39 @@ word as an identifier.  You can use the keyword @code{__typeof__} instead.
 This option is implied by the strict ISO C++ dialects: @option{-ansi},
 @option{-std=c++98}, @option{-std=c++11}, etc.
 
+@opindex fno-immediate-escalation
+@opindex fimmediate-escalation
+@item -fno-immediate-escalation
+Do not enable immediate function escalation whereby certain functions
+can be promoted to consteval, as specified in P2564R3.  For example:
+
+@example
+consteval int id(int i) @{ return i; @}
+
+constexpr int f(auto t)
+@{
+  return t + id(t); // id causes f<int> to be promoted to consteval
+@}
+
+void g(int i)
+@{
+  f (3);
+@}
+@end example
+
+compiles in C++20: @code{f} is an immediate-escalating function (due to
+the @code{auto} it is a function template and is declared @code{constexpr})
+and @code{id(t)} is an immediate-escalating expression, so @code{f} is
+promoted to @code{consteval}.  Consequently, the call to @code{id(t)}
+is in an immediate context, so doesn't have to produce a constant (that
+is the mechanism allowing consteval function composition).  However,
+with @option{-fno-immediate-escalation}, @code{f} is not promoted to
+@code{consteval}, and since the call to consteval function @code{id(t)}
+is not a constant expression, the compiler rejects the code.
+
+This option is turned on by default; it is only effective in C++20 mode
+or later.
+
 @opindex fimplicit-constexpr
 @item -fimplicit-constexpr
 Make inline functions implicitly constexpr, if they satisfy the
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 b2c5472b7de..3b258711ce6 100644
--- a/gcc/testsuite/g++.dg/cpp23/consteval-if2.C
+++ b/gcc/testsuite/g++.dg/cpp23/consteval-if2.C
@@ -33,7 +33,7 @@ baz (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);	// { dg-error "not a constant expression" }
     }
   else
     {
@@ -45,11 +45,11 @@ baz (int x)
     }
   else
     {
-      r += foo (8 * x);	// { dg-error "'x' is not a constant expression" }
+      r += foo (8 * x);	// { dg-error "is not a constant expression" }
     }
   if ! consteval	// { dg-warning "'if consteval' only available with" "" { target c++20_only } }
     {
-      r += foo (32 * x);// { dg-error "'x' is not a constant expression" }
+      r += foo (32 * x);// { dg-error "not a constant expression" }
     }
   if consteval		// { dg-warning "'if consteval' only available with" "" { target c++20_only } }
     {
@@ -98,7 +98,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
     {
@@ -110,11 +110,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 } }
     {
@@ -126,5 +126,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