public inbox for gcc-patches@gcc.gnu.org
 help / color / mirror / Atom feed
From: Marek Polacek <polacek@redhat.com>
To: Jason Merrill <jason@redhat.com>
Cc: GCC Patches <gcc-patches@gcc.gnu.org>
Subject: [PATCH v4] c++: implement P2564, consteval needs to propagate up [PR107687]
Date: Mon, 6 Nov 2023 17:34:31 -0500	[thread overview]
Message-ID: <ZUlp93ZwnARyoEia@redhat.com> (raw)
In-Reply-To: <a0e8f057-99b1-4306-8772-553c1c4d9c9f@redhat.com>

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


  reply	other threads:[~2023-11-06 22:34 UTC|newest]

Thread overview: 19+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2023-08-23 19:49 [PATCH] " Marek Polacek
2023-08-29 19:26 ` Jason Merrill
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
2023-11-02 15:32         ` Marek Polacek
2023-11-03 17:51         ` Jason Merrill
2023-11-06 22:34           ` Marek Polacek [this message]
2023-11-14  2:06             ` [PATCH v4] " Jason Merrill
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
2023-12-02  0:43                     ` Jason Merrill
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
2023-12-06 11:39                             ` Prathamesh Kulkarni
2023-12-06 14:34                               ` Marek Polacek

Reply instructions:

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

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

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

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

  git send-email \
    --in-reply-to=ZUlp93ZwnARyoEia@redhat.com \
    --to=polacek@redhat.com \
    --cc=gcc-patches@gcc.gnu.org \
    --cc=jason@redhat.com \
    /path/to/YOUR_REPLY

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

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