c++: Add C++23 consteval if support - P1938R3 [PR100974]

Message ID 20210610083416.GC7746@tucnak
State New
Headers show
Series
  • c++: Add C++23 consteval if support - P1938R3 [PR100974]
Related show

Commit Message

Jonathan Wakely via Gcc-patches June 10, 2021, 8:34 a.m.
Hi!

The following patch implements consteval if support.
There is a new IF_STMT_CONSTEVAL_P flag on IF_STMT and IF_COND is
boolean_false_node to match the non-manifestly constant evaluation
behavior, while constexpr evaluation special-cases it.  Perhaps cleaner
would be to set the condition to __builtin_is_constant_evaluated () call
but we need the IF_STMT_CONSTEVAL_P flag anyway and the IL would be larger.

I'm not 100% sure whether lambda body is enclosed by the surrounding
statement, I'm assuming it is not - see consteval-if10.C testcase.

Also, the paper doesn't contain the exact __cpp_if_consteval value,
but https://github.com/cplusplus/draft/pull/4660/files mentions 202106L
which this patch uses.

And I'm not changing the libstdc++ side, where perhaps we could change
std::is_constant_evaluated definition for
#ifdef __cpp_if_consteval
case to if consteval { return true; } else { return false; }
but we need to keep it defined to __builtin_is_constant_evaluated ()
for C++20 or older.

Bootstrapped/regtested on x86_64-linux and i686-linux, ok for trunk?

2021-06-10  Jakub Jelinek  <jakub@redhat.com>

	PR c++/100974 - P1938R3 - if consteval
gcc/c-family/
	* c-cppbuiltin.c (c_cpp_builtins): Predefine __cpp_if_consteval for
	-std=c++2b.
gcc/cp/
	* cp-tree.h (struct saved_scope): Add immediate_fn_ctx_p
	member.
	(in_immediate_fn_ctx_p, IF_STMT_CONSTEVAL_P): Define.
	* parser.c (cp_parser_lambda_expression): Temporarily disable
	in_immediate_fn_ctx_p when parsing lambda body.
	(cp_parser_selection_statement): Parse consteval if.
	* decl.c (struct named_label_entry): Add in_consteval_if member.
	(level_for_consteval_if): New function.
	(poplevel_named_label_1, check_previous_goto_1, check_goto): Handle
	consteval if.
	* constexpr.c (cxx_eval_conditional_expression): For
	IF_STMT_CONSTEVAL_P evaluate condition as if it was
	__builtin_is_constant_evaluated call.
	(potential_constant_expression_1): For IF_STMT_CONSTEVAL_P always
	recurse on both branches.
	* cp-gimplify.c (genericize_if_stmt): Genericize IF_STMT_CONSTEVAL_P
	as the else branch.
	* pt.c (tsubst_expr) <case IF_STMT>: Copy IF_STMT_CONSTEVAL_P.
	Temporarily set in_immediate_fn_ctx_p when recursing on
	IF_STMT_CONSTEVAL_P then branch.
	(tsubst_lambda_expr): Temporarily disable
	in_immediate_fn_ctx_p when instantiating lambda body.
	* call.c (immediate_invocation_p): Return false when
	in_immediate_fn_ctx_p.
gcc/testsuite/
	* g++.dg/cpp23/consteval-if1.C: New test.
	* g++.dg/cpp23/consteval-if2.C: New test.
	* g++.dg/cpp23/consteval-if3.C: New test.
	* g++.dg/cpp23/consteval-if4.C: New test.
	* g++.dg/cpp23/consteval-if5.C: New test.
	* g++.dg/cpp23/consteval-if6.C: New test.
	* g++.dg/cpp23/consteval-if7.C: New test.
	* g++.dg/cpp23/consteval-if8.C: New test.
	* g++.dg/cpp23/consteval-if9.C: New test.
	* g++.dg/cpp23/consteval-if10.C: New test.
	* g++.dg/cpp23/feat-cxx2b.C: Add __cpp_if_consteval tests.


	Jakub

Comments

Jonathan Wakely via Gcc-patches June 10, 2021, 10:24 a.m. | #1
On Thu, 10 Jun 2021 at 09:34, Jakub Jelinek wrote:
> Also, the paper doesn't contain the exact __cpp_if_consteval value,


Right, proposals aren't supposed to, because the value gets set when
the proposal is voted into the working draft (which the proposal
author doesn't control).

> but https://github.com/cplusplus/draft/pull/4660/files mentions 202106L

> which this patch uses.


That's the right value.

> And I'm not changing the libstdc++ side, where perhaps we could change

> std::is_constant_evaluated definition for

> #ifdef __cpp_if_consteval

> case to if consteval { return true; } else { return false; }

> but we need to keep it defined to __builtin_is_constant_evaluated ()

> for C++20 or older.


Is there any advantage to changing that (cheaper for GCC to evaluate?)
or should we just continue to use the __builtin unconditionally?

I suppose it's theoretically possible that there could be a non-GCC
compiler where defined(__cpp_if_consteval) is true but
__has_builtin(__builtin_is_constant_evaluated) is false.
Jonathan Wakely via Gcc-patches June 10, 2021, 10:34 a.m. | #2
On Thu, Jun 10, 2021 at 11:24:43AM +0100, Jonathan Wakely wrote:
> > And I'm not changing the libstdc++ side, where perhaps we could change

> > std::is_constant_evaluated definition for

> > #ifdef __cpp_if_consteval

> > case to if consteval { return true; } else { return false; }

> > but we need to keep it defined to __builtin_is_constant_evaluated ()

> > for C++20 or older.

> 

> Is there any advantage to changing that (cheaper for GCC to evaluate?)


I guess compile-time lost in the noise.

> or should we just continue to use the __builtin unconditionally?

> 

> I suppose it's theoretically possible that there could be a non-GCC

> compiler where defined(__cpp_if_consteval) is true but

> __has_builtin(__builtin_is_constant_evaluated) is false.


Up to you.  The wording says Equivalent to
if consteval { return true; } else { return false; }
and return __builtin_is_constant_evaluated (); is equivalent to that.

Perhaps some people could appreciate to see it literally there, but we
can't use it for C++20 (due to the -Wpedantic warnings or -pedantic-errors
errors) and for C++17 and earlier (consteval is not a keyword).

	Jakub
Jonathan Wakely via Gcc-patches June 10, 2021, 10:43 a.m. | #3
On Thu, 10 Jun 2021 at 11:34, Jakub Jelinek wrote:
>

> On Thu, Jun 10, 2021 at 11:24:43AM +0100, Jonathan Wakely wrote:

> > > And I'm not changing the libstdc++ side, where perhaps we could change

> > > std::is_constant_evaluated definition for

> > > #ifdef __cpp_if_consteval

> > > case to if consteval { return true; } else { return false; }

> > > but we need to keep it defined to __builtin_is_constant_evaluated ()

> > > for C++20 or older.

> >

> > Is there any advantage to changing that (cheaper for GCC to evaluate?)

>

> I guess compile-time lost in the noise.

>

> > or should we just continue to use the __builtin unconditionally?

> >

> > I suppose it's theoretically possible that there could be a non-GCC

> > compiler where defined(__cpp_if_consteval) is true but

> > __has_builtin(__builtin_is_constant_evaluated) is false.

>

> Up to you.  The wording says Equivalent to

> if consteval { return true; } else { return false; }

> and return __builtin_is_constant_evaluated (); is equivalent to that.

>

> Perhaps some people could appreciate to see it literally there, but we


Those people can write their own pure-C++23 library then ;-)

> can't use it for C++20 (due to the -Wpedantic warnings or -pedantic-errors


Yeah.

> errors) and for C++17 and earlier (consteval is not a keyword).


std::is_constant_evaluated isn't defined for C++17 and earlier, so in
C++14 and C++17 code we have to use the builtin directly (and for
C++11 constexpr is so limited that there are no uses for it).

I think we should just leave it as-is, and revisit if a good reason
arises in future.
Jonathan Wakely via Gcc-patches June 10, 2021, 2:09 p.m. | #4
On 6/10/21 4:34 AM, Jakub Jelinek wrote:
> Hi!

> 

> The following patch implements consteval if support.


Great!

> There is a new IF_STMT_CONSTEVAL_P flag on IF_STMT and IF_COND is

> boolean_false_node to match the non-manifestly constant evaluation

> behavior, while constexpr evaluation special-cases it.  Perhaps cleaner

> would be to set the condition to __builtin_is_constant_evaluated () call

> but we need the IF_STMT_CONSTEVAL_P flag anyway and the IL would be larger.

> 

> I'm not 100% sure whether lambda body is enclosed by the surrounding

> statement, I'm assuming it is not - see consteval-if10.C testcase.


Correct.

> Also, the paper doesn't contain the exact __cpp_if_consteval value,

> but https://github.com/cplusplus/draft/pull/4660/files mentions 202106L

> which this patch uses.

> 

> And I'm not changing the libstdc++ side, where perhaps we could change

> std::is_constant_evaluated definition for

> #ifdef __cpp_if_consteval

> case to if consteval { return true; } else { return false; }

> but we need to keep it defined to __builtin_is_constant_evaluated ()

> for C++20 or older.

> 

> Bootstrapped/regtested on x86_64-linux and i686-linux, ok for trunk?

> 

> 2021-06-10  Jakub Jelinek  <jakub@redhat.com>

> 

> 	PR c++/100974 - P1938R3 - if consteval

> gcc/c-family/

> 	* c-cppbuiltin.c (c_cpp_builtins): Predefine __cpp_if_consteval for

> 	-std=c++2b.

> gcc/cp/

> 	* cp-tree.h (struct saved_scope): Add immediate_fn_ctx_p

> 	member.

> 	(in_immediate_fn_ctx_p, IF_STMT_CONSTEVAL_P): Define.

> 	* parser.c (cp_parser_lambda_expression): Temporarily disable

> 	in_immediate_fn_ctx_p when parsing lambda body.

> 	(cp_parser_selection_statement): Parse consteval if.

> 	* decl.c (struct named_label_entry): Add in_consteval_if member.

> 	(level_for_consteval_if): New function.

> 	(poplevel_named_label_1, check_previous_goto_1, check_goto): Handle

> 	consteval if.

> 	* constexpr.c (cxx_eval_conditional_expression): For

> 	IF_STMT_CONSTEVAL_P evaluate condition as if it was

> 	__builtin_is_constant_evaluated call.

> 	(potential_constant_expression_1): For IF_STMT_CONSTEVAL_P always

> 	recurse on both branches.

> 	* cp-gimplify.c (genericize_if_stmt): Genericize IF_STMT_CONSTEVAL_P

> 	as the else branch.

> 	* pt.c (tsubst_expr) <case IF_STMT>: Copy IF_STMT_CONSTEVAL_P.

> 	Temporarily set in_immediate_fn_ctx_p when recursing on

> 	IF_STMT_CONSTEVAL_P then branch.

> 	(tsubst_lambda_expr): Temporarily disable

> 	in_immediate_fn_ctx_p when instantiating lambda body.

> 	* call.c (immediate_invocation_p): Return false when

> 	in_immediate_fn_ctx_p.

> gcc/testsuite/

> 	* g++.dg/cpp23/consteval-if1.C: New test.

> 	* g++.dg/cpp23/consteval-if2.C: New test.

> 	* g++.dg/cpp23/consteval-if3.C: New test.

> 	* g++.dg/cpp23/consteval-if4.C: New test.

> 	* g++.dg/cpp23/consteval-if5.C: New test.

> 	* g++.dg/cpp23/consteval-if6.C: New test.

> 	* g++.dg/cpp23/consteval-if7.C: New test.

> 	* g++.dg/cpp23/consteval-if8.C: New test.

> 	* g++.dg/cpp23/consteval-if9.C: New test.

> 	* g++.dg/cpp23/consteval-if10.C: New test.

> 	* g++.dg/cpp23/feat-cxx2b.C: Add __cpp_if_consteval tests.

> 

> --- gcc/c-family/c-cppbuiltin.c.jj	2021-06-09 21:54:39.433194531 +0200

> +++ gcc/c-family/c-cppbuiltin.c	2021-06-10 09:49:35.776558874 +0200

> @@ -1029,6 +1029,7 @@ c_cpp_builtins (cpp_reader *pfile)

>   	{

>   	  /* Set feature test macros for C++23.  */

>   	  cpp_define (pfile, "__cpp_size_t_suffix=202011L");

> +	  cpp_define (pfile, "__cpp_if_consteval=202106L");

>   	}

>         if (flag_concepts)

>           {

> --- gcc/cp/cp-tree.h.jj	2021-06-09 21:54:39.474193964 +0200

> +++ gcc/cp/cp-tree.h	2021-06-10 09:49:35.795558610 +0200

> @@ -478,6 +478,7 @@ extern GTY(()) tree cp_global_trees[CPTI

>         AGGR_INIT_ZERO_FIRST (in AGGR_INIT_EXPR)

>         CONSTRUCTOR_MUTABLE_POISON (in CONSTRUCTOR)

>         OVL_HIDDEN_P (in OVERLOAD)

> +      IF_STMT_CONSTEVAL_P (in IF_STMT)

>         SWITCH_STMT_NO_BREAK_P (in SWITCH_STMT)

>         LAMBDA_EXPR_CAPTURE_OPTIMIZED (in LAMBDA_EXPR)

>         IMPLICIT_CONV_EXPR_BRACED_INIT (in IMPLICIT_CONV_EXPR)

> @@ -1816,6 +1817,7 @@ struct GTY(()) saved_scope {

>   /* Nonzero if we are parsing the discarded statement of a constexpr

>      if-statement.  */

>     BOOL_BITFIELD discarded_stmt : 1;

> +  BOOL_BITFIELD immediate_fn_ctx_p : 1;

>   

>     int unevaluated_operand;

>     int inhibit_evaluation_warnings;

> @@ -1879,6 +1881,7 @@ extern GTY(()) struct saved_scope *scope

>   #define processing_explicit_instantiation scope_chain->x_processing_explicit_instantiation

>   

>   #define in_discarded_stmt scope_chain->discarded_stmt

> +#define in_immediate_fn_ctx_p scope_chain->immediate_fn_ctx_p

>   

>   #define current_ref_temp_count scope_chain->ref_temp_count

>   

> @@ -5211,6 +5214,7 @@ more_aggr_init_expr_args_p (const aggr_i

>   #define ELSE_CLAUSE(NODE)	TREE_OPERAND (IF_STMT_CHECK (NODE), 2)

>   #define IF_SCOPE(NODE)		TREE_OPERAND (IF_STMT_CHECK (NODE), 3)

>   #define IF_STMT_CONSTEXPR_P(NODE) TREE_LANG_FLAG_0 (IF_STMT_CHECK (NODE))

> +#define IF_STMT_CONSTEVAL_P(NODE) TREE_LANG_FLAG_2 (IF_STMT_CHECK (NODE))

>   

>   /* Like PACK_EXPANSION_EXTRA_ARGS, for constexpr if.  IF_SCOPE is used while

>      building an IF_STMT; IF_STMT_EXTRA_ARGS is used after it is complete.  */

> --- gcc/cp/parser.c.jj	2021-06-09 21:54:39.482193853 +0200

> +++ gcc/cp/parser.c	2021-06-10 10:09:23.753052980 +0200

> @@ -10902,6 +10902,11 @@ cp_parser_lambda_expression (cp_parser*

>       bool discarded = in_discarded_stmt;

>       in_discarded_stmt = 0;

>   

> +    /* Similarly the body of a lambda in immediate function context is not

> +       in immediate function context.  */

> +    bool immediate_fn_ctx_p = in_immediate_fn_ctx_p;


It's hard to distinguish between these two names when reading the code; 
let's give the local variable a name including "saved".

> +    in_immediate_fn_ctx_p = false;

> +

>       /* By virtue of defining a local class, a lambda expression has access to

>          the private variables of enclosing classes.  */

>   

> @@ -10932,6 +10937,7 @@ cp_parser_lambda_expression (cp_parser*

>   

>       finish_struct (type, /*attributes=*/NULL_TREE);

>   

> +    in_immediate_fn_ctx_p = immediate_fn_ctx_p;

>       in_discarded_stmt = discarded;

>   

>       parser->num_template_parameter_lists = saved_num_template_parameter_lists;

> @@ -12324,6 +12330,73 @@ cp_parser_selection_statement (cp_parser

>   		       "%<if constexpr%> only available with "

>   		       "%<-std=c++17%> or %<-std=gnu++17%>");

>   	  }

> +	int ce = 0;

> +	if (keyword == RID_IF && !cx)

> +	  {

> +	    if (cp_lexer_next_token_is_keyword (parser->lexer,

> +						RID_CONSTEVAL))

> +	      ce = 1;

> +	    else if (cp_lexer_next_token_is (parser->lexer, CPP_NOT)

> +		     && cp_lexer_nth_token_is_keyword (parser->lexer, 2,

> +						       RID_CONSTEVAL))

> +	      {

> +		ce = -1;

> +		cp_lexer_consume_token (parser->lexer);

> +	      }

> +	  }

> +	if (ce)

> +	  {

> +	    cp_token *tok = cp_lexer_consume_token (parser->lexer);

> +	    if (cxx_dialect < cxx23)

> +	      pedwarn (tok->location, OPT_Wc__23_extensions,

> +		       "%<if consteval%> only available with "

> +		       "%<-std=c++2b%> or %<-std=gnu++2b%>");

> +

> +	    bool immediate_fn_ctx_p = in_immediate_fn_ctx_p;

> +	    statement = begin_if_stmt ();

> +	    IF_STMT_CONSTEVAL_P (statement) = true;

> +	    condition = finish_if_stmt_cond (boolean_false_node, statement);

> +

> +	    if (cp_lexer_next_token_is_not (parser->lexer, CPP_OPEN_BRACE))

> +	      error ("%<if consteval%> requires compound statement");

> +

> +	    in_immediate_fn_ctx_p |= ce > 0;

> +	    cp_parser_implicitly_scoped_statement (parser, NULL, guard_tinfo);


Maybe use cp_parser_compound_statement directly instead of this and 
checking CPP_OPEN_BRACE above?  Either way is fine.

> +	    finish_then_clause (statement);

> +

> +	    /* If the next token is `else', parse the else-clause.  */

> +	    if (cp_lexer_next_token_is_keyword (parser->lexer,

> +						RID_ELSE))

> +	      {

> +		cp_token *else_tok = cp_lexer_peek_token (parser->lexer);

> +		guard_tinfo = get_token_indent_info (else_tok);

> +		/* Consume the `else' keyword.  */

> +		cp_lexer_consume_token (parser->lexer);

> +

> +		begin_else_clause (statement);

> +

> +		if (cp_lexer_next_token_is_not (parser->lexer, CPP_OPEN_BRACE))

> +		  error ("%<if consteval%> requires compound statement");

> +

> +		in_immediate_fn_ctx_p = immediate_fn_ctx_p | (ce < 0);

> +		cp_parser_implicitly_scoped_statement (parser, NULL,

> +						       guard_tinfo);

> +

> +		finish_else_clause (statement);

> +	      }

> +

> +	    in_immediate_fn_ctx_p = immediate_fn_ctx_p;

> +	    if (ce < 0)

> +	      {

> +		std::swap (THEN_CLAUSE (statement), ELSE_CLAUSE (statement));

> +		if (THEN_CLAUSE (statement) == NULL_TREE)

> +		  THEN_CLAUSE (statement) = build_empty_stmt (tok->location);

> +	      }

> +

> +	    finish_if_stmt (statement);

> +	    return statement;

> +	  }

>   

>   	/* Look for the `('.  */

>   	matching_parens parens;

> --- gcc/cp/decl.c.jj	2021-06-09 21:54:39.477193922 +0200

> +++ gcc/cp/decl.c	2021-06-10 09:49:35.872557540 +0200

> @@ -222,6 +222,7 @@ struct GTY((for_user)) named_label_entry

>     bool in_omp_scope;

>     bool in_transaction_scope;

>     bool in_constexpr_if;

> +  bool in_consteval_if;

>   };

>   

>   #define named_labels cp_function_chain->x_named_labels

> @@ -491,6 +492,16 @@ level_for_constexpr_if (cp_binding_level

>   	  && IF_STMT_CONSTEXPR_P (b->this_entity));

>   }

>   

> +/* True if B is the level for the condition of a consteval if.  */

> +

> +static bool

> +level_for_consteval_if (cp_binding_level *b)

> +{

> +  return (b->kind == sk_cond && b->this_entity

> +	  && TREE_CODE (b->this_entity) == IF_STMT

> +	  && IF_STMT_CONSTEVAL_P (b->this_entity));

> +}

> +

>   /* Update data for defined and undefined labels when leaving a scope.  */

>   

>   int

> @@ -530,6 +541,8 @@ poplevel_named_label_1 (named_label_entr

>   	case sk_block:

>   	  if (level_for_constexpr_if (bl->level_chain))

>   	    ent->in_constexpr_if = true;

> +	  else if (level_for_consteval_if (bl->level_chain))

> +	    ent->in_consteval_if = true;

>   	  break;

>   	default:

>   	  break;

> @@ -3391,6 +3404,7 @@ check_previous_goto_1 (tree decl, cp_bin

>     bool complained = false;

>     int identified = 0;

>     bool saw_eh = false, saw_omp = false, saw_tm = false, saw_cxif = false;

> +  bool saw_ceif = false;

>   

>     if (exited_omp)

>       {

> @@ -3470,6 +3484,12 @@ check_previous_goto_1 (tree decl, cp_bin

>   	      loc = EXPR_LOCATION (b->level_chain->this_entity);

>   	      saw_cxif = true;

>   	    }

> +	  else if (!saw_ceif && level_for_consteval_if (b->level_chain))

> +	    {

> +	      inf = G_("  enters %<consteval if%> statement");

> +	      loc = EXPR_LOCATION (b->level_chain->this_entity);

> +	      saw_ceif = true;

> +	    }

>   	  break;

>   

>   	default:

> @@ -3551,12 +3571,13 @@ check_goto (tree decl)

>     unsigned ix;

>   

>     if (ent->in_try_scope || ent->in_catch_scope || ent->in_transaction_scope

> -      || ent->in_constexpr_if

> +      || ent->in_constexpr_if || ent->in_consteval_if

>         || ent->in_omp_scope || !vec_safe_is_empty (ent->bad_decls))

>       {

>         diagnostic_t diag_kind = DK_PERMERROR;

>         if (ent->in_try_scope || ent->in_catch_scope || ent->in_constexpr_if

> -	  || ent->in_transaction_scope || ent->in_omp_scope)

> +	  || ent->in_consteval_if || ent->in_transaction_scope

> +	  || ent->in_omp_scope)

>   	diag_kind = DK_ERROR;

>         complained = identify_goto (decl, DECL_SOURCE_LOCATION (decl),

>   				  &input_location, diag_kind);

> @@ -3602,6 +3623,8 @@ check_goto (tree decl)

>   	inform (input_location, "  enters synchronized or atomic statement");

>         else if (ent->in_constexpr_if)

>   	inform (input_location, "  enters %<constexpr if%> statement");

> +      else if (ent->in_consteval_if)

> +	inform (input_location, "  enters %<consteval if%> statement");

>       }

>   

>     if (ent->in_omp_scope)

> --- gcc/cp/constexpr.c.jj	2021-06-09 21:54:39.456194213 +0200

> +++ gcc/cp/constexpr.c	2021-06-10 09:49:35.881557415 +0200

> @@ -3299,6 +3299,18 @@ cxx_eval_conditional_expression (const c

>   					   non_constant_p, overflow_p);

>     VERIFY_CONSTANT (val);

>     /* Don't VERIFY_CONSTANT the other operands.  */

> +  if (TREE_CODE (t) == IF_STMT && IF_STMT_CONSTEVAL_P (t))

> +    {

> +      /* Evaluate the condition as if it was

> +	 if (__builtin_is_constant_evaluated ()).  */

> +      if (ctx->manifestly_const_eval)

> +	val = boolean_true_node;

> +      else

> +	{

> +	  *non_constant_p = true;

> +	  return t;

> +	}


Why set *non_constant_p?  Shouldn't this just be

val = boolean_false_node;

so we constant-evaluate the else clause when we are trying to 
constant-evaluate in a non-manifestly-constant-evaluated context?

> +    }

>     if (integer_zerop (val))

>       val = TREE_OPERAND (t, 2);

>     else

> @@ -8799,10 +8811,13 @@ potential_constant_expression_1 (tree t,

>   	return false;

>         if (!processing_template_decl)

>   	tmp = cxx_eval_outermost_constant_expr (tmp, true);

> -      if (integer_zerop (tmp))

> -	return RECUR (TREE_OPERAND (t, 2), want_rval);

> -      else if (TREE_CODE (tmp) == INTEGER_CST)

> -	return RECUR (TREE_OPERAND (t, 1), want_rval);

> +      if (TREE_CODE (t) != IF_STMT || !IF_STMT_CONSTEVAL_P (t))

> +	{

> +	  if (integer_zerop (tmp))

> +	    return RECUR (TREE_OPERAND (t, 2), want_rval);

> +	  else if (TREE_CODE (tmp) == INTEGER_CST)

> +	    return RECUR (TREE_OPERAND (t, 1), want_rval);

> +	}


Don't we still want to shortcut consideration of one of the arms for if 
consteval?

> --- gcc/cp/cp-gimplify.c.jj	2021-06-09 21:54:39.473193977 +0200

> +++ gcc/cp/cp-gimplify.c	2021-06-10 09:49:35.898557178 +0200

> @@ -161,7 +161,9 @@ genericize_if_stmt (tree *stmt_p)

>     if (!else_)

>       else_ = build_empty_stmt (locus);

>   

> -  if (integer_nonzerop (cond) && !TREE_SIDE_EFFECTS (else_))

> +  if (IF_STMT_CONSTEVAL_P (stmt))

> +    stmt = else_;


This seems redundant, since you're using boolean_false_node for the 
condition.

> +  else if (integer_nonzerop (cond) && !TREE_SIDE_EFFECTS (else_))

>       stmt = then_;

>     else if (integer_zerop (cond) && !TREE_SIDE_EFFECTS (then_))

>       stmt = else_;

> --- gcc/cp/pt.c.jj	2021-06-09 21:54:39.503193563 +0200

> +++ gcc/cp/pt.c	2021-06-10 10:12:11.470725151 +0200

> @@ -18413,6 +18413,7 @@ tsubst_expr (tree t, tree args, tsubst_f

>       case IF_STMT:

>         stmt = begin_if_stmt ();

>         IF_STMT_CONSTEXPR_P (stmt) = IF_STMT_CONSTEXPR_P (t);

> +      IF_STMT_CONSTEVAL_P (stmt) = IF_STMT_CONSTEVAL_P (t);

>         if (IF_STMT_CONSTEXPR_P (t))

>   	args = add_extra_args (IF_STMT_EXTRA_ARGS (t), args);

>         tmp = RECUR (IF_COND (t));

> @@ -18433,6 +18434,13 @@ tsubst_expr (tree t, tree args, tsubst_f

>   	}

>         if (IF_STMT_CONSTEXPR_P (t) && integer_zerop (tmp))

>   	/* Don't instantiate the THEN_CLAUSE. */;

> +      else if (IF_STMT_CONSTEVAL_P (t))

> +	{

> +	  bool immediate_fn_ctx_p = in_immediate_fn_ctx_p;

> +	  in_immediate_fn_ctx_p = true;

> +	  RECUR (THEN_CLAUSE (t));

> +	  in_immediate_fn_ctx_p = immediate_fn_ctx_p;

> +	}

>         else

>   	{

>   	  tree folded = fold_non_dependent_expr (tmp, complain);

> @@ -19385,6 +19393,9 @@ tsubst_lambda_expr (tree t, tree args, t

>   

>         local_specialization_stack s (lss_copy);

>   

> +      bool immediate_fn_ctx_p = in_immediate_fn_ctx_p;

> +      in_immediate_fn_ctx_p = false;

> +

>         tree body = start_lambda_function (fn, r);

>   

>         /* Now record them for lookup_init_capture_pack.  */

> @@ -19425,6 +19436,8 @@ tsubst_lambda_expr (tree t, tree args, t

>   

>         finish_lambda_function (body);

>   

> +      in_immediate_fn_ctx_p = immediate_fn_ctx_p;

> +

>         if (nested)

>   	pop_function_context ();

>         else

> --- gcc/cp/call.c.jj	2021-06-09 21:54:39.436194489 +0200

> +++ gcc/cp/call.c	2021-06-10 09:49:35.949556470 +0200

> @@ -8840,6 +8840,7 @@ immediate_invocation_p (tree fn, int nar

>   	      || !DECL_IMMEDIATE_FUNCTION_P (current_function_decl))

>   	  && (current_binding_level->kind != sk_function_parms

>   	      || !current_binding_level->immediate_fn_ctx_p)

> +	  && !in_immediate_fn_ctx_p


Now that we have this flag, shouldn't we set it in actual immediate 
function context?  Or rename the flag to match when it's actually set.

>   	  /* As an exception, we defer std::source_location::current ()

>   	     invocations until genericization because LWG3396 mandates

>   	     special behavior for it.  */

> --- gcc/testsuite/g++.dg/cpp23/consteval-if1.C.jj	2021-06-10 09:49:35.949556470 +0200

> +++ gcc/testsuite/g++.dg/cpp23/consteval-if1.C	2021-06-10 09:49:35.949556470 +0200

> @@ -0,0 +1,103 @@

> +// P1938R3

> +// { dg-do run { target c++20 } }

> +// { dg-options "" }

> +

> +extern "C" void abort ();

> +

> +namespace std {

> +  constexpr inline bool

> +  is_constant_evaluated () noexcept

> +  {

> +    if consteval {	// { dg-warning "'if consteval' only available with" "" { target c++20_only } }

> +      return true;

> +    } else {

> +      return false;

> +    }

> +  }

> +}

> +

> +consteval int foo (int x) { return x; }

> +consteval int bar () { return 2; }

> +

> +constexpr int

> +baz (int x)

> +{

> +  int r = 0;

> +  if consteval		// { dg-warning "'if consteval' only available with" "" { target c++20_only } }

> +    {

> +      r += foo (x);

> +    }

> +  else

> +    {

> +      r += bar ();

> +    }

> +  if ! consteval	// { dg-warning "'if consteval' only available with" "" { target c++20_only } }

> +    {

> +      r += 2 * bar ();

> +    }

> +  else

> +    {

> +      r += foo (8 * x);

> +    }

> +  if (std::is_constant_evaluated ())

> +    r = -r;

> +  if consteval		// { dg-warning "'if consteval' only available with" "" { target c++20_only } }

> +    {

> +      r += foo (32 * x);

> +    }

> +  if not consteval	// { dg-warning "'if consteval' only available with" "" { target c++20_only } }

> +    {

> +      r += 32 * bar ();

> +    }

> +  return r;

> +}

> +

> +template <typename T>

> +constexpr int

> +qux (T x)

> +{

> +  T r = 0;

> +  if consteval		// { dg-warning "'if consteval' only available with" "" { target c++20_only } }

> +    {

> +      r += foo (x);

> +    }

> +  else

> +    {

> +      r += bar ();

> +    }

> +  if ! consteval	// { dg-warning "'if consteval' only available with" "" { target c++20_only } }

> +    {

> +      r += 2 * bar ();

> +    }

> +  else

> +    {

> +      r += foo (8 * x);

> +    }

> +  if (std::is_constant_evaluated ())

> +    r = -r;

> +  if consteval		// { dg-warning "'if consteval' only available with" "" { target c++20_only } }

> +    {

> +      r += foo (32 * x);

> +    }

> +  if not consteval	// { dg-warning "'if consteval' only available with" "" { target c++20_only } }

> +    {

> +      r += 32 * bar ();

> +    }

> +  return r;

> +}

> +

> +constexpr int a = baz (1);

> +static_assert (a == 23);

> +int b = baz (1);

> +constexpr int c = qux (1);

> +static_assert (c == 23);

> +int d = qux<int> (1);

> +

> +int

> +main ()

> +{

> +  if (b != 23 || d != 23)

> +    abort ();

> +  if (baz (1) != 70 || qux (1) != 70 || qux (1LL) != 70)

> +    abort ();

> +}

> --- gcc/testsuite/g++.dg/cpp23/consteval-if2.C.jj	2021-06-10 09:49:35.949556470 +0200

> +++ gcc/testsuite/g++.dg/cpp23/consteval-if2.C	2021-06-10 09:58:22.459240019 +0200

> @@ -0,0 +1,129 @@

> +// P1938R3

> +// { dg-do compile { target c++20 } }

> +// { dg-options "" }

> +

> +constexpr bool f()

> +{

> +  if consteval (true) {}	// { dg-error "'if consteval' requires compound statement" }

> +				// { dg-error "expected" "" { target *-*-* } .-1 }

> +				// { dg-warning "'if consteval' only available with" "" { target c++20_only } .-2 }

> +  if not consteval (false) {}	// { dg-error "'if consteval' requires compound statement" }

> +				// { dg-error "expected" "" { target *-*-* } .-1 }

> +				// { dg-warning "'if consteval' only available with" "" { target c++20_only } .-2 }

> +  if consteval if (true) {}	// { dg-error "'if consteval' requires compound statement" }

> +				// { dg-warning "'if consteval' only available with" "" { target c++20_only } .-1 }

> +  if ! consteval {} else ;	// { dg-error "'if consteval' requires compound statement" }

> +				// { dg-warning "'if consteval' only available with" "" { target c++20_only } .-1 }

> +  if consteval {} else if (true) {}	// { dg-error "'if consteval' requires compound statement" }

> +				// { dg-warning "'if consteval' only available with" "" { target c++20_only } .-1 }

> +  if (true)

> +    if consteval		// { dg-warning "'if consteval' only available with" "" { target c++20_only } }

> +      {

> +      }

> +    else ;			// { dg-error "'if consteval' requires compound statement" }

> +  return false;

> +}

> +

> +consteval int foo (int x) { return x; }

> +consteval int bar () { return 2; }

> +

> +constexpr int

> +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" }

> +    }

> +  else

> +    {

> +      r += bar ();

> +    }

> +  if consteval		// { dg-warning "'if consteval' only available with" "" { target c++20_only } }

> +    {

> +      r += 2 * bar ();

> +    }

> +  else

> +    {

> +      r += foo (8 * x);	// { dg-error "'x' 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" }

> +    }

> +  if consteval		// { dg-warning "'if consteval' only available with" "" { target c++20_only } }

> +    {

> +      r += 32 * bar ();

> +    }

> +  return r;

> +}

> +

> +template <typename T>

> +constexpr int

> +qux (int x)

> +{

> +  int r = 0;

> +  if not consteval	// { dg-warning "'if consteval' only available with" "" { target c++20_only } }

> +    {

> +      r += foo (x);	// { dg-error "'x' is not a constant expression" }

> +    }

> +  else

> +    {

> +      r += bar ();

> +    }

> +  if consteval		// { dg-warning "'if consteval' only available with" "" { target c++20_only } }

> +    {

> +      r += 2 * bar ();

> +    }

> +  else

> +    {

> +      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 "is not a constant expression" }

> +    }

> +  if consteval		// { dg-warning "'if consteval' only available with" "" { target c++20_only } }

> +    {

> +      r += 32 * bar ();

> +    }

> +  return r;

> +}

> +

> +template <typename T>

> +constexpr T

> +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" }

> +    }

> +  else

> +    {

> +      r += bar ();

> +    }

> +  if consteval		// { dg-warning "'if consteval' only available with" "" { target c++20_only } }

> +    {

> +      r += 2 * bar ();

> +    }

> +  else

> +    {

> +      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 "is not a constant expression" }

> +    }

> +  if consteval		// { dg-warning "'if consteval' only available with" "" { target c++20_only } }

> +    {

> +      r += 32 * bar ();

> +    }

> +  return r;

> +}

> +

> +int

> +garply (int x)

> +{

> +  return corge (x);

> +}

> --- gcc/testsuite/g++.dg/cpp23/consteval-if3.C.jj	2021-06-10 09:49:35.949556470 +0200

> +++ gcc/testsuite/g++.dg/cpp23/consteval-if3.C	2021-06-10 09:49:35.949556470 +0200

> @@ -0,0 +1,73 @@

> +// P1938R3

> +// { dg-do run { target c++20 } }

> +// { dg-options "" }

> +

> +constexpr inline bool

> +is_constant_evaluated () noexcept

> +{

> +  if consteval { return true; } else { return false; }	// { dg-warning "'if consteval' only available with" "" { target c++20_only } }

> +}

> +

> +template<int N> struct X { int v = N; };

> +X<is_constant_evaluated ()> x; // type X<true>

> +int y = 4;

> +int a = is_constant_evaluated () ? y : 1; // initializes a to 1

> +int b = is_constant_evaluated () ? 2 : y; // initializes b to 2

> +int c = y + (is_constant_evaluated () ? 2 : y); // initializes c to 2*y

> +int d = is_constant_evaluated (); // initializes d to 1

> +int e = d + is_constant_evaluated (); // initializes e to 1 + 0

> +

> +struct false_type { static constexpr bool value = false; };

> +struct true_type { static constexpr bool value = true; };

> +template<class T, class U>

> +struct is_same : false_type {};

> +template<class T>

> +struct is_same<T, T> : true_type {};

> +

> +constexpr int

> +foo (int x)

> +{

> +  const int n = is_constant_evaluated () ? 13 : 17; // n == 13

> +  int m = is_constant_evaluated () ? 13 : 17; // m might be 13 or 17 (see below)

> +  char arr[n] = {}; // char[13]

> +  return m + sizeof (arr) + x;

> +}

> +

> +constexpr int

> +bar ()

> +{

> +  const int n = is_constant_evaluated() ? 13 : 17;

> +  X<n> x1;

> +  X<is_constant_evaluated() ? 13 : 17> x2;

> +  static_assert (is_same<decltype (x1), decltype (x2)>::value, "x1/x2's type");

> +  return x1.v + x2.v;

> +}

> +

> +int p = foo (0); // m == 13; initialized to 26

> +int q = p + foo (0); // m == 17 for this call; initialized to 56

> +static_assert (bar () == 26, "bar");

> +

> +struct S { int a, b; };

> +

> +S s = { is_constant_evaluated () ? 2 : 3, y };

> +S t = { is_constant_evaluated () ? 2 : 3, 4 };

> +

> +static_assert (is_same<decltype (x), X<true> >::value, "x's type");

> +

> +int

> +main ()

> +{

> +  if (a != 1 || b != 2 || c != 8 || d != 1 || e != 1 || p != 26 || q != 56)

> +    __builtin_abort ();

> +  if (s.a != 3 || s.b != 4 || t.a != 2 || t.b != 4)

> +    __builtin_abort ();

> +  if (foo (y) != 34)

> +    __builtin_abort ();

> +#if __cplusplus >= 201703L

> +  if constexpr (foo (0) != 26)

> +    __builtin_abort ();

> +#endif

> +  constexpr int w = foo (0);

> +  if (w != 26)

> +    __builtin_abort ();

> +}

> --- gcc/testsuite/g++.dg/cpp23/consteval-if4.C.jj	2021-06-10 09:49:35.949556470 +0200

> +++ gcc/testsuite/g++.dg/cpp23/consteval-if4.C	2021-06-10 09:49:35.949556470 +0200

> @@ -0,0 +1,44 @@

> +// { dg-do compile { target c++20 } }

> +// { dg-options "-w" }

> +

> +void f()

> +{

> +  goto l;			// { dg-message "from here" }

> +  if consteval			// { dg-message "enters 'consteval if'" }

> +    {

> +    l:;				// { dg-error "jump to label" }

> +    }

> +}

> +

> +void g()

> +{

> +  goto l;			// { dg-message "from here" }

> +  if not consteval		// { dg-message "enters 'consteval if'" }

> +    {

> +    l:;				// { dg-error "jump to label" }

> +    }

> +}

> +

> +void h()

> +{

> +  goto l;			// { dg-message "from here" }

> +  if consteval			// { dg-message "enters 'consteval if'" }

> +    {

> +    }

> +  else

> +    {

> +    l:;				// { dg-error "jump to label" }

> +    }

> +}

> +

> +void i()

> +{

> +  goto l;			// { dg-message "from here" }

> +  if not consteval		// { dg-message "enters 'consteval if'" }

> +    {

> +    }

> +  else

> +    {

> +    l:;				// { dg-error "jump to label" }

> +    }

> +}

> --- gcc/testsuite/g++.dg/cpp23/consteval-if5.C.jj	2021-06-10 09:49:35.949556470 +0200

> +++ gcc/testsuite/g++.dg/cpp23/consteval-if5.C	2021-06-10 09:49:35.949556470 +0200

> @@ -0,0 +1,14 @@

> +// { dg-do compile { target c++20 } }

> +// { dg-options "-w" }

> +

> +void f()

> +{

> +  if consteval			// { dg-message "enters 'consteval if'" }

> +    {

> +      goto l;			// { dg-message "from here" }

> +    }

> +  else

> +    {

> +    l:;				// { dg-error "jump to label" }

> +    }

> +}

> --- gcc/testsuite/g++.dg/cpp23/consteval-if6.C.jj	2021-06-10 09:49:35.949556470 +0200

> +++ gcc/testsuite/g++.dg/cpp23/consteval-if6.C	2021-06-10 09:49:35.949556470 +0200

> @@ -0,0 +1,16 @@

> +// { dg-do compile { target c++20 } }

> +// { dg-options "-w" }

> +

> +void f()

> +{

> +  if consteval

> +    {

> +      goto l;

> +    l:;

> +    }

> +  else

> +    {

> +      goto l2;

> +    l2:;

> +    }

> +}

> --- gcc/testsuite/g++.dg/cpp23/consteval-if7.C.jj	2021-06-10 09:49:35.949556470 +0200

> +++ gcc/testsuite/g++.dg/cpp23/consteval-if7.C	2021-06-10 09:49:35.949556470 +0200

> @@ -0,0 +1,16 @@

> +// { dg-do compile { target c++20 } }

> +// { dg-options "-w" }

> +

> +void f()

> +{

> +  if not consteval

> +    {

> +    l:;

> +      goto l;

> +    }

> +  else

> +    {

> +    l2:;

> +      goto l2;

> +    }

> +}

> --- gcc/testsuite/g++.dg/cpp23/consteval-if8.C.jj	2021-06-10 09:49:35.950556456 +0200

> +++ gcc/testsuite/g++.dg/cpp23/consteval-if8.C	2021-06-10 09:49:35.950556456 +0200

> @@ -0,0 +1,14 @@

> +// { dg-do compile { target c++20 } }

> +// { dg-options "-w" }

> +

> +void f()

> +{

> +  if consteval

> +    {

> +    l:;				// { dg-error "jump to label" }

> +    }

> +  else

> +    {

> +      goto l;			// { dg-message "from here" }

> +    }

> +}

> --- gcc/testsuite/g++.dg/cpp23/consteval-if9.C.jj	2021-06-10 09:49:35.950556456 +0200

> +++ gcc/testsuite/g++.dg/cpp23/consteval-if9.C	2021-06-10 09:49:35.950556456 +0200

> @@ -0,0 +1,11 @@

> +// { dg-do compile { target c++20 } }

> +// { dg-options "-w" }

> +

> +constexpr void f(int i)

> +{

> +  switch (i)

> +    if consteval		// { dg-message "enters 'consteval if'" }

> +      {

> +      case 42:;			// { dg-error "jump to case label" }

> +      }

> +}

> --- gcc/testsuite/g++.dg/cpp23/consteval-if10.C.jj	2021-06-10 09:58:15.456337333 +0200

> +++ gcc/testsuite/g++.dg/cpp23/consteval-if10.C	2021-06-10 10:15:50.288688058 +0200

> @@ -0,0 +1,36 @@

> +// P1938R3

> +// { dg-do compile { target c++20 } }

> +// { dg-options "" }

> +

> +consteval int foo (int x) { return x; }

> +

> +constexpr int

> +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" }

> +      y ();

> +    }

> +  return r;

> +}

> +

> +template <typename T>

> +constexpr T

> +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" }

> +      y ();

> +    }

> +  return r;

> +}

> +

> +int

> +qux (int x)

> +{

> +  return baz (x);

> +}

> --- gcc/testsuite/g++.dg/cpp23/feat-cxx2b.C.jj	2021-06-10 09:46:03.851503924 +0200

> +++ gcc/testsuite/g++.dg/cpp23/feat-cxx2b.C	2021-06-10 09:49:35.950556456 +0200

> @@ -545,3 +545,9 @@

>   #elif __cpp_size_t_suffix != 202011

>   #  error "__cpp_size_t_suffix != 202011"

>   #endif

> +

> +#ifndef __cpp_if_consteval

> +#  error "__cpp_if_consteval"

> +#elif __cpp_if_consteval != 202106

> +#  error "__cpp_if_consteval != 202106"

> +#endif

> 

> 	Jakub

>
Jonathan Wakely via Gcc-patches June 10, 2021, 2:44 p.m. | #5
On Thu, Jun 10, 2021 at 10:09:26AM -0400, Jason Merrill wrote:
> > --- gcc/cp/parser.c.jj	2021-06-09 21:54:39.482193853 +0200

> > +++ gcc/cp/parser.c	2021-06-10 10:09:23.753052980 +0200

> > @@ -10902,6 +10902,11 @@ cp_parser_lambda_expression (cp_parser*

> >       bool discarded = in_discarded_stmt;

> >       in_discarded_stmt = 0;

> > +    /* Similarly the body of a lambda in immediate function context is not

> > +       in immediate function context.  */

> > +    bool immediate_fn_ctx_p = in_immediate_fn_ctx_p;

> 

> It's hard to distinguish between these two names when reading the code;

> let's give the local variable a name including "saved".


Will do.

> > +	    if (cp_lexer_next_token_is_not (parser->lexer, CPP_OPEN_BRACE))

> > +	      error ("%<if consteval%> requires compound statement");

> > +

> > +	    in_immediate_fn_ctx_p |= ce > 0;

> > +	    cp_parser_implicitly_scoped_statement (parser, NULL, guard_tinfo);

> 

> Maybe use cp_parser_compound_statement directly instead of this and checking

> CPP_OPEN_BRACE above?  Either way is fine.


I thought doing it this way will provide better diagnostics for what I think
can be a common bug - people so used to normal if not requiring compound
statements forgetting those {}s from time to time.

> > +  if (TREE_CODE (t) == IF_STMT && IF_STMT_CONSTEVAL_P (t))

> > +    {

> > +      /* Evaluate the condition as if it was

> > +	 if (__builtin_is_constant_evaluated ()).  */

> > +      if (ctx->manifestly_const_eval)

> > +	val = boolean_true_node;

> > +      else

> > +	{

> > +	  *non_constant_p = true;

> > +	  return t;

> > +	}

> 

> Why set *non_constant_p?  Shouldn't this just be

> 

> val = boolean_false_node;

> 

> so we constant-evaluate the else clause when we are trying to

> constant-evaluate in a non-manifestly-constant-evaluated context?


It matches what we do for CP_BUILT_IN_IS_CONSTANT_EVALUATED calls:
  /* For __builtin_is_constant_evaluated, defer it if not
     ctx->manifestly_const_eval, otherwise fold it to true.  */
  if (fndecl_built_in_p (fun, CP_BUILT_IN_IS_CONSTANT_EVALUATED,
                         BUILT_IN_FRONTEND))
    {
      if (!ctx->manifestly_const_eval)
        {
          *non_constant_p = true;
          return t;
        }
      return boolean_true_node;
    }
I believe we sometimes try to constexpr evaluate something without
having manifestly_const_eval = true even on expressions that
are in manifestly constant evaluated contexts and am worried if
we just folded it to boolean_false_node we could get a constant expression
and replace the expression with that, even before we actually try to
constant evaluate it with manifestly_const_eval = true.

If I do (for the CP_BUILT_IN_IS_CONSTANT_EVALUATED):
--- gcc/cp/constexpr.c.jj	2021-06-10 15:27:31.123353594 +0200
+++ gcc/cp/constexpr.c	2021-06-10 16:26:38.368168281 +0200
@@ -1320,10 +1320,7 @@ cxx_eval_builtin_function_call (const co
 			 BUILT_IN_FRONTEND))
     {
       if (!ctx->manifestly_const_eval)
-	{
-	  *non_constant_p = true;
-	  return t;
-	}
+	return boolean_false_node;
       return boolean_true_node;
     }
 
then
FAIL: g++.dg/cpp2a/is-constant-evaluated1.C  -std=c++14 execution test
FAIL: g++.dg/cpp2a/is-constant-evaluated1.C  -std=c++17 execution test
FAIL: g++.dg/cpp2a/is-constant-evaluated1.C  -std=c++2a execution test
FAIL: g++.dg/cpp2a/is-constant-evaluated1.C  -std=c++17 -fconcepts execution test
FAIL: g++.dg/cpp2a/is-constant-evaluated2.C  -std=c++14 execution test
FAIL: g++.dg/cpp2a/is-constant-evaluated2.C  -std=c++17 execution test
FAIL: g++.dg/cpp2a/is-constant-evaluated2.C  -std=c++2a execution test
FAIL: g++.dg/cpp2a/is-constant-evaluated2.C  -std=c++17 -fconcepts execution test
FAIL: g++.dg/cpp2a/is-constant-evaluated9.C  -std=gnu++2a (test for excess errors)
at least regresses.

> > +      if (TREE_CODE (t) != IF_STMT || !IF_STMT_CONSTEVAL_P (t))

> > +	{

> > +	  if (integer_zerop (tmp))

> > +	    return RECUR (TREE_OPERAND (t, 2), want_rval);

> > +	  else if (TREE_CODE (tmp) == INTEGER_CST)

> > +	    return RECUR (TREE_OPERAND (t, 1), want_rval);

> > +	}

> 

> Don't we still want to shortcut consideration of one of the arms for if

> consteval?


potential_constant_expression_p{,_1} doesn't get passed whether it is
manifestly_const_eval or not.

> > --- gcc/cp/cp-gimplify.c.jj	2021-06-09 21:54:39.473193977 +0200

> > +++ gcc/cp/cp-gimplify.c	2021-06-10 09:49:35.898557178 +0200

> > @@ -161,7 +161,9 @@ genericize_if_stmt (tree *stmt_p)

> >     if (!else_)

> >       else_ = build_empty_stmt (locus);

> > -  if (integer_nonzerop (cond) && !TREE_SIDE_EFFECTS (else_))

> > +  if (IF_STMT_CONSTEVAL_P (stmt))

> > +    stmt = else_;

> 

> This seems redundant, since you're using boolean_false_node for the

> condition.


It is only when !TREE_SIDE_EFFECTS (else_).
I think that is about having labels in the then_/else_ blocks and
gotos jumping into those from outside.
But IF_STMT_CONSTEVAL_P guarantees that is not the case (and we verify
earlier), so we can do that (and in fact, for IF_STMT_CONSTEVAL_P have
to, because then_ could contain consteval calls not constant expression
evaluated).
I guess we could do that for IF_STMT_CONSTEXPR_P too, that also
doesn't allow gotos/switch into the branches.

> > --- gcc/cp/call.c.jj	2021-06-09 21:54:39.436194489 +0200

> > +++ gcc/cp/call.c	2021-06-10 09:49:35.949556470 +0200

> > @@ -8840,6 +8840,7 @@ immediate_invocation_p (tree fn, int nar

> >   	      || !DECL_IMMEDIATE_FUNCTION_P (current_function_decl))

> >   	  && (current_binding_level->kind != sk_function_parms

> >   	      || !current_binding_level->immediate_fn_ctx_p)

> > +	  && !in_immediate_fn_ctx_p

> 

> Now that we have this flag, shouldn't we set it in actual immediate function

> context?  Or rename the flag to match when it's actually set.


I guess I can try that.  Though, I'm not sure if we could also get rid of
the current_binding_level->immediate_fn_ctx_p for sk_function_parms case.

	Jakub
Jonathan Wakely via Gcc-patches June 10, 2021, 5:25 p.m. | #6
On Thu, Jun 10, 2021 at 04:44:09PM +0200, Jakub Jelinek wrote:
> > Maybe use cp_parser_compound_statement directly instead of this and checking

> > CPP_OPEN_BRACE above?  Either way is fine.

> 

> I thought doing it this way will provide better diagnostics for what I think

> can be a common bug - people so used to normal if not requiring compound

> statements forgetting those {}s from time to time.


What the patch currently diagnoses is:
consteval-if2.C:13:6: error: ‘if consteval’ requires compound statement
   13 |   if consteval if (true) {}     // { dg-error "'if consteval' requires compound statement" }
      |      ^~~~~~~~~
while with cp_parser_compound_statement directly it diagnoses:
consteval-if2.C:13:16: error: expected ‘{’ before ‘if’
   13 |   if consteval if (true) {}     // { dg-error "'if consteval' requires compound statement" }
      |                ^~
What do you prefer?
Dunno if the fine detail that in the grammar only one of the statements
is compound-statement and then there is a requirement that the other
statement has to be a compound-statement shouldn't affect how it is
reported.

	Jakub
Jonathan Wakely via Gcc-patches June 10, 2021, 7 p.m. | #7
On 6/10/21 10:44 AM, Jakub Jelinek wrote:
> On Thu, Jun 10, 2021 at 10:09:26AM -0400, Jason Merrill wrote:

>>> --- gcc/cp/parser.c.jj	2021-06-09 21:54:39.482193853 +0200

>>> +++ gcc/cp/parser.c	2021-06-10 10:09:23.753052980 +0200

>>> @@ -10902,6 +10902,11 @@ cp_parser_lambda_expression (cp_parser*

>>>        bool discarded = in_discarded_stmt;

>>>        in_discarded_stmt = 0;

>>> +    /* Similarly the body of a lambda in immediate function context is not

>>> +       in immediate function context.  */

>>> +    bool immediate_fn_ctx_p = in_immediate_fn_ctx_p;

>>

>> It's hard to distinguish between these two names when reading the code;

>> let's give the local variable a name including "saved".

> 

> Will do.

> 

>>> +	    if (cp_lexer_next_token_is_not (parser->lexer, CPP_OPEN_BRACE))

>>> +	      error ("%<if consteval%> requires compound statement");

>>> +

>>> +	    in_immediate_fn_ctx_p |= ce > 0;

>>> +	    cp_parser_implicitly_scoped_statement (parser, NULL, guard_tinfo);

>>

>> Maybe use cp_parser_compound_statement directly instead of this and checking

>> CPP_OPEN_BRACE above?  Either way is fine.

> 

> I thought doing it this way will provide better diagnostics for what I think

> can be a common bug - people so used to normal if not requiring compound

> statements forgetting those {}s from time to time.


> What the patch currently diagnoses is:

> consteval-if2.C:13:6: error: ‘if consteval’ requires compound statement

>    13 |   if consteval if (true) {}     // { dg-error "'if consteval' requires compound statement" }

>       |      ^~~~~~~~~

> while with cp_parser_compound_statement directly it diagnoses:

> consteval-if2.C:13:16: error: expected ‘{’ before ‘if’

>    13 |   if consteval if (true) {}     // { dg-error "'if consteval' requires compound statement" }

>       |                ^~

> What do you prefer?


The second is clearer about the fix, the first is clearer about the 
problem.  Maybe add a fixit to the first error?

> Dunno if the fine detail that in the grammar only one of the statements

> is compound-statement and then there is a requirement that the other

> statement has to be a compound-statement shouldn't affect how it is

> reported.


The difference was to prevent "else ;" from binding to an enclosing if 
rather than the if consteval.

>>> +  if (TREE_CODE (t) == IF_STMT && IF_STMT_CONSTEVAL_P (t))

>>> +    {

>>> +      /* Evaluate the condition as if it was

>>> +	 if (__builtin_is_constant_evaluated ()).  */

>>> +      if (ctx->manifestly_const_eval)

>>> +	val = boolean_true_node;

>>> +      else

>>> +	{

>>> +	  *non_constant_p = true;

>>> +	  return t;

>>> +	}

>>

>> Why set *non_constant_p?  Shouldn't this just be

>>

>> val = boolean_false_node;

>>

>> so we constant-evaluate the else clause when we are trying to

>> constant-evaluate in a non-manifestly-constant-evaluated context?

> 

> It matches what we do for CP_BUILT_IN_IS_CONSTANT_EVALUATED calls:

>    /* For __builtin_is_constant_evaluated, defer it if not

>       ctx->manifestly_const_eval, otherwise fold it to true.  */

>    if (fndecl_built_in_p (fun, CP_BUILT_IN_IS_CONSTANT_EVALUATED,

>                           BUILT_IN_FRONTEND))

>      {

>        if (!ctx->manifestly_const_eval)

>          {

>            *non_constant_p = true;

>            return t;

>          }

>        return boolean_true_node;

>      }

> I believe we sometimes try to constexpr evaluate something without

> having manifestly_const_eval = true even on expressions that

> are in manifestly constant evaluated contexts and am worried if

> we just folded it to boolean_false_node we could get a constant expression

> and replace the expression with that, even before we actually try to

> constant evaluate it with manifestly_const_eval = true.

> 

> If I do (for the CP_BUILT_IN_IS_CONSTANT_EVALUATED):

> --- gcc/cp/constexpr.c.jj	2021-06-10 15:27:31.123353594 +0200

> +++ gcc/cp/constexpr.c	2021-06-10 16:26:38.368168281 +0200

> @@ -1320,10 +1320,7 @@ cxx_eval_builtin_function_call (const co

>   			 BUILT_IN_FRONTEND))

>       {

>         if (!ctx->manifestly_const_eval)

> -	{

> -	  *non_constant_p = true;

> -	  return t;

> -	}

> +	return boolean_false_node;

>         return boolean_true_node;

>       }

>   

> then

> FAIL: g++.dg/cpp2a/is-constant-evaluated1.C  -std=c++14 execution test

> FAIL: g++.dg/cpp2a/is-constant-evaluated1.C  -std=c++17 execution test

> FAIL: g++.dg/cpp2a/is-constant-evaluated1.C  -std=c++2a execution test

> FAIL: g++.dg/cpp2a/is-constant-evaluated1.C  -std=c++17 -fconcepts execution test

> FAIL: g++.dg/cpp2a/is-constant-evaluated2.C  -std=c++14 execution test

> FAIL: g++.dg/cpp2a/is-constant-evaluated2.C  -std=c++17 execution test

> FAIL: g++.dg/cpp2a/is-constant-evaluated2.C  -std=c++2a execution test

> FAIL: g++.dg/cpp2a/is-constant-evaluated2.C  -std=c++17 -fconcepts execution test

> FAIL: g++.dg/cpp2a/is-constant-evaluated9.C  -std=gnu++2a (test for excess errors)

> at least regresses.


OK, just add a comment then.

>>> +      if (TREE_CODE (t) != IF_STMT || !IF_STMT_CONSTEVAL_P (t))

>>> +	{

>>> +	  if (integer_zerop (tmp))

>>> +	    return RECUR (TREE_OPERAND (t, 2), want_rval);

>>> +	  else if (TREE_CODE (tmp) == INTEGER_CST)

>>> +	    return RECUR (TREE_OPERAND (t, 1), want_rval);

>>> +	}

>>

>> Don't we still want to shortcut consideration of one of the arms for if

>> consteval?

> 

> potential_constant_expression_p{,_1} doesn't get passed whether it is

> manifestly_const_eval or not.


OK, just add a comment then.

>>> --- gcc/cp/cp-gimplify.c.jj	2021-06-09 21:54:39.473193977 +0200

>>> +++ gcc/cp/cp-gimplify.c	2021-06-10 09:49:35.898557178 +0200

>>> @@ -161,7 +161,9 @@ genericize_if_stmt (tree *stmt_p)

>>>      if (!else_)

>>>        else_ = build_empty_stmt (locus);

>>> -  if (integer_nonzerop (cond) && !TREE_SIDE_EFFECTS (else_))

>>> +  if (IF_STMT_CONSTEVAL_P (stmt))

>>> +    stmt = else_;

>>

>> This seems redundant, since you're using boolean_false_node for the

>> condition.

> 

> It is only when !TREE_SIDE_EFFECTS (else_).

> I think that is about having labels in the then_/else_ blocks and

> gotos jumping into those from outside.

> But IF_STMT_CONSTEVAL_P guarantees that is not the case (and we verify

> earlier), so we can do that (and in fact, for IF_STMT_CONSTEVAL_P have

> to, because then_ could contain consteval calls not constant expression

> evaluated).

> I guess we could do that for IF_STMT_CONSTEXPR_P too, that also

> doesn't allow gotos/switch into the branches.

> 

>>> --- gcc/cp/call.c.jj	2021-06-09 21:54:39.436194489 +0200

>>> +++ gcc/cp/call.c	2021-06-10 09:49:35.949556470 +0200

>>> @@ -8840,6 +8840,7 @@ immediate_invocation_p (tree fn, int nar

>>>    	      || !DECL_IMMEDIATE_FUNCTION_P (current_function_decl))

>>>    	  && (current_binding_level->kind != sk_function_parms

>>>    	      || !current_binding_level->immediate_fn_ctx_p)

>>> +	  && !in_immediate_fn_ctx_p

>>

>> Now that we have this flag, shouldn't we set it in actual immediate function

>> context?  Or rename the flag to match when it's actually set.

> 

> I guess I can try that.  Though, I'm not sure if we could also get rid of

> the current_binding_level->immediate_fn_ctx_p for sk_function_parms case.

> 

> 	Jakub

>

Patch

--- gcc/c-family/c-cppbuiltin.c.jj	2021-06-09 21:54:39.433194531 +0200
+++ gcc/c-family/c-cppbuiltin.c	2021-06-10 09:49:35.776558874 +0200
@@ -1029,6 +1029,7 @@  c_cpp_builtins (cpp_reader *pfile)
 	{
 	  /* Set feature test macros for C++23.  */
 	  cpp_define (pfile, "__cpp_size_t_suffix=202011L");
+	  cpp_define (pfile, "__cpp_if_consteval=202106L");
 	}
       if (flag_concepts)
         {
--- gcc/cp/cp-tree.h.jj	2021-06-09 21:54:39.474193964 +0200
+++ gcc/cp/cp-tree.h	2021-06-10 09:49:35.795558610 +0200
@@ -478,6 +478,7 @@  extern GTY(()) tree cp_global_trees[CPTI
       AGGR_INIT_ZERO_FIRST (in AGGR_INIT_EXPR)
       CONSTRUCTOR_MUTABLE_POISON (in CONSTRUCTOR)
       OVL_HIDDEN_P (in OVERLOAD)
+      IF_STMT_CONSTEVAL_P (in IF_STMT)
       SWITCH_STMT_NO_BREAK_P (in SWITCH_STMT)
       LAMBDA_EXPR_CAPTURE_OPTIMIZED (in LAMBDA_EXPR)
       IMPLICIT_CONV_EXPR_BRACED_INIT (in IMPLICIT_CONV_EXPR)
@@ -1816,6 +1817,7 @@  struct GTY(()) saved_scope {
 /* Nonzero if we are parsing the discarded statement of a constexpr
    if-statement.  */
   BOOL_BITFIELD discarded_stmt : 1;
+  BOOL_BITFIELD immediate_fn_ctx_p : 1;
 
   int unevaluated_operand;
   int inhibit_evaluation_warnings;
@@ -1879,6 +1881,7 @@  extern GTY(()) struct saved_scope *scope
 #define processing_explicit_instantiation scope_chain->x_processing_explicit_instantiation
 
 #define in_discarded_stmt scope_chain->discarded_stmt
+#define in_immediate_fn_ctx_p scope_chain->immediate_fn_ctx_p
 
 #define current_ref_temp_count scope_chain->ref_temp_count
 
@@ -5211,6 +5214,7 @@  more_aggr_init_expr_args_p (const aggr_i
 #define ELSE_CLAUSE(NODE)	TREE_OPERAND (IF_STMT_CHECK (NODE), 2)
 #define IF_SCOPE(NODE)		TREE_OPERAND (IF_STMT_CHECK (NODE), 3)
 #define IF_STMT_CONSTEXPR_P(NODE) TREE_LANG_FLAG_0 (IF_STMT_CHECK (NODE))
+#define IF_STMT_CONSTEVAL_P(NODE) TREE_LANG_FLAG_2 (IF_STMT_CHECK (NODE))
 
 /* Like PACK_EXPANSION_EXTRA_ARGS, for constexpr if.  IF_SCOPE is used while
    building an IF_STMT; IF_STMT_EXTRA_ARGS is used after it is complete.  */
--- gcc/cp/parser.c.jj	2021-06-09 21:54:39.482193853 +0200
+++ gcc/cp/parser.c	2021-06-10 10:09:23.753052980 +0200
@@ -10902,6 +10902,11 @@  cp_parser_lambda_expression (cp_parser*
     bool discarded = in_discarded_stmt;
     in_discarded_stmt = 0;
 
+    /* Similarly the body of a lambda in immediate function context is not
+       in immediate function context.  */
+    bool immediate_fn_ctx_p = in_immediate_fn_ctx_p;
+    in_immediate_fn_ctx_p = false;
+
     /* By virtue of defining a local class, a lambda expression has access to
        the private variables of enclosing classes.  */
 
@@ -10932,6 +10937,7 @@  cp_parser_lambda_expression (cp_parser*
 
     finish_struct (type, /*attributes=*/NULL_TREE);
 
+    in_immediate_fn_ctx_p = immediate_fn_ctx_p;
     in_discarded_stmt = discarded;
 
     parser->num_template_parameter_lists = saved_num_template_parameter_lists;
@@ -12324,6 +12330,73 @@  cp_parser_selection_statement (cp_parser
 		       "%<if constexpr%> only available with "
 		       "%<-std=c++17%> or %<-std=gnu++17%>");
 	  }
+	int ce = 0;
+	if (keyword == RID_IF && !cx)
+	  {
+	    if (cp_lexer_next_token_is_keyword (parser->lexer,
+						RID_CONSTEVAL))
+	      ce = 1;
+	    else if (cp_lexer_next_token_is (parser->lexer, CPP_NOT)
+		     && cp_lexer_nth_token_is_keyword (parser->lexer, 2,
+						       RID_CONSTEVAL))
+	      {
+		ce = -1;
+		cp_lexer_consume_token (parser->lexer);
+	      }
+	  }
+	if (ce)
+	  {
+	    cp_token *tok = cp_lexer_consume_token (parser->lexer);
+	    if (cxx_dialect < cxx23)
+	      pedwarn (tok->location, OPT_Wc__23_extensions,
+		       "%<if consteval%> only available with "
+		       "%<-std=c++2b%> or %<-std=gnu++2b%>");
+
+	    bool immediate_fn_ctx_p = in_immediate_fn_ctx_p;
+	    statement = begin_if_stmt ();
+	    IF_STMT_CONSTEVAL_P (statement) = true;
+	    condition = finish_if_stmt_cond (boolean_false_node, statement);
+
+	    if (cp_lexer_next_token_is_not (parser->lexer, CPP_OPEN_BRACE))
+	      error ("%<if consteval%> requires compound statement");
+
+	    in_immediate_fn_ctx_p |= ce > 0;
+	    cp_parser_implicitly_scoped_statement (parser, NULL, guard_tinfo);
+
+	    finish_then_clause (statement);
+
+	    /* If the next token is `else', parse the else-clause.  */
+	    if (cp_lexer_next_token_is_keyword (parser->lexer,
+						RID_ELSE))
+	      {
+		cp_token *else_tok = cp_lexer_peek_token (parser->lexer);
+		guard_tinfo = get_token_indent_info (else_tok);
+		/* Consume the `else' keyword.  */
+		cp_lexer_consume_token (parser->lexer);
+
+		begin_else_clause (statement);
+
+		if (cp_lexer_next_token_is_not (parser->lexer, CPP_OPEN_BRACE))
+		  error ("%<if consteval%> requires compound statement");
+
+		in_immediate_fn_ctx_p = immediate_fn_ctx_p | (ce < 0);
+		cp_parser_implicitly_scoped_statement (parser, NULL,
+						       guard_tinfo);
+
+		finish_else_clause (statement);
+	      }
+
+	    in_immediate_fn_ctx_p = immediate_fn_ctx_p;
+	    if (ce < 0)
+	      {
+		std::swap (THEN_CLAUSE (statement), ELSE_CLAUSE (statement));
+		if (THEN_CLAUSE (statement) == NULL_TREE)
+		  THEN_CLAUSE (statement) = build_empty_stmt (tok->location);
+	      }
+
+	    finish_if_stmt (statement);
+	    return statement;
+	  }
 
 	/* Look for the `('.  */
 	matching_parens parens;
--- gcc/cp/decl.c.jj	2021-06-09 21:54:39.477193922 +0200
+++ gcc/cp/decl.c	2021-06-10 09:49:35.872557540 +0200
@@ -222,6 +222,7 @@  struct GTY((for_user)) named_label_entry
   bool in_omp_scope;
   bool in_transaction_scope;
   bool in_constexpr_if;
+  bool in_consteval_if;
 };
 
 #define named_labels cp_function_chain->x_named_labels
@@ -491,6 +492,16 @@  level_for_constexpr_if (cp_binding_level
 	  && IF_STMT_CONSTEXPR_P (b->this_entity));
 }
 
+/* True if B is the level for the condition of a consteval if.  */
+
+static bool
+level_for_consteval_if (cp_binding_level *b)
+{
+  return (b->kind == sk_cond && b->this_entity
+	  && TREE_CODE (b->this_entity) == IF_STMT
+	  && IF_STMT_CONSTEVAL_P (b->this_entity));
+}
+
 /* Update data for defined and undefined labels when leaving a scope.  */
 
 int
@@ -530,6 +541,8 @@  poplevel_named_label_1 (named_label_entr
 	case sk_block:
 	  if (level_for_constexpr_if (bl->level_chain))
 	    ent->in_constexpr_if = true;
+	  else if (level_for_consteval_if (bl->level_chain))
+	    ent->in_consteval_if = true;
 	  break;
 	default:
 	  break;
@@ -3391,6 +3404,7 @@  check_previous_goto_1 (tree decl, cp_bin
   bool complained = false;
   int identified = 0;
   bool saw_eh = false, saw_omp = false, saw_tm = false, saw_cxif = false;
+  bool saw_ceif = false;
 
   if (exited_omp)
     {
@@ -3470,6 +3484,12 @@  check_previous_goto_1 (tree decl, cp_bin
 	      loc = EXPR_LOCATION (b->level_chain->this_entity);
 	      saw_cxif = true;
 	    }
+	  else if (!saw_ceif && level_for_consteval_if (b->level_chain))
+	    {
+	      inf = G_("  enters %<consteval if%> statement");
+	      loc = EXPR_LOCATION (b->level_chain->this_entity);
+	      saw_ceif = true;
+	    }
 	  break;
 
 	default:
@@ -3551,12 +3571,13 @@  check_goto (tree decl)
   unsigned ix;
 
   if (ent->in_try_scope || ent->in_catch_scope || ent->in_transaction_scope
-      || ent->in_constexpr_if
+      || ent->in_constexpr_if || ent->in_consteval_if
       || ent->in_omp_scope || !vec_safe_is_empty (ent->bad_decls))
     {
       diagnostic_t diag_kind = DK_PERMERROR;
       if (ent->in_try_scope || ent->in_catch_scope || ent->in_constexpr_if
-	  || ent->in_transaction_scope || ent->in_omp_scope)
+	  || ent->in_consteval_if || ent->in_transaction_scope
+	  || ent->in_omp_scope)
 	diag_kind = DK_ERROR;
       complained = identify_goto (decl, DECL_SOURCE_LOCATION (decl),
 				  &input_location, diag_kind);
@@ -3602,6 +3623,8 @@  check_goto (tree decl)
 	inform (input_location, "  enters synchronized or atomic statement");
       else if (ent->in_constexpr_if)
 	inform (input_location, "  enters %<constexpr if%> statement");
+      else if (ent->in_consteval_if)
+	inform (input_location, "  enters %<consteval if%> statement");
     }
 
   if (ent->in_omp_scope)
--- gcc/cp/constexpr.c.jj	2021-06-09 21:54:39.456194213 +0200
+++ gcc/cp/constexpr.c	2021-06-10 09:49:35.881557415 +0200
@@ -3299,6 +3299,18 @@  cxx_eval_conditional_expression (const c
 					   non_constant_p, overflow_p);
   VERIFY_CONSTANT (val);
   /* Don't VERIFY_CONSTANT the other operands.  */
+  if (TREE_CODE (t) == IF_STMT && IF_STMT_CONSTEVAL_P (t))
+    {
+      /* Evaluate the condition as if it was
+	 if (__builtin_is_constant_evaluated ()).  */
+      if (ctx->manifestly_const_eval)
+	val = boolean_true_node;
+      else
+	{
+	  *non_constant_p = true;
+	  return t;
+	}
+    }
   if (integer_zerop (val))
     val = TREE_OPERAND (t, 2);
   else
@@ -8799,10 +8811,13 @@  potential_constant_expression_1 (tree t,
 	return false;
       if (!processing_template_decl)
 	tmp = cxx_eval_outermost_constant_expr (tmp, true);
-      if (integer_zerop (tmp))
-	return RECUR (TREE_OPERAND (t, 2), want_rval);
-      else if (TREE_CODE (tmp) == INTEGER_CST)
-	return RECUR (TREE_OPERAND (t, 1), want_rval);
+      if (TREE_CODE (t) != IF_STMT || !IF_STMT_CONSTEVAL_P (t))
+	{
+	  if (integer_zerop (tmp))
+	    return RECUR (TREE_OPERAND (t, 2), want_rval);
+	  else if (TREE_CODE (tmp) == INTEGER_CST)
+	    return RECUR (TREE_OPERAND (t, 1), want_rval);
+	}
       tmp = *jump_target;
       for (i = 1; i < 3; ++i)
 	{
--- gcc/cp/cp-gimplify.c.jj	2021-06-09 21:54:39.473193977 +0200
+++ gcc/cp/cp-gimplify.c	2021-06-10 09:49:35.898557178 +0200
@@ -161,7 +161,9 @@  genericize_if_stmt (tree *stmt_p)
   if (!else_)
     else_ = build_empty_stmt (locus);
 
-  if (integer_nonzerop (cond) && !TREE_SIDE_EFFECTS (else_))
+  if (IF_STMT_CONSTEVAL_P (stmt))
+    stmt = else_;
+  else if (integer_nonzerop (cond) && !TREE_SIDE_EFFECTS (else_))
     stmt = then_;
   else if (integer_zerop (cond) && !TREE_SIDE_EFFECTS (then_))
     stmt = else_;
--- gcc/cp/pt.c.jj	2021-06-09 21:54:39.503193563 +0200
+++ gcc/cp/pt.c	2021-06-10 10:12:11.470725151 +0200
@@ -18413,6 +18413,7 @@  tsubst_expr (tree t, tree args, tsubst_f
     case IF_STMT:
       stmt = begin_if_stmt ();
       IF_STMT_CONSTEXPR_P (stmt) = IF_STMT_CONSTEXPR_P (t);
+      IF_STMT_CONSTEVAL_P (stmt) = IF_STMT_CONSTEVAL_P (t);
       if (IF_STMT_CONSTEXPR_P (t))
 	args = add_extra_args (IF_STMT_EXTRA_ARGS (t), args);
       tmp = RECUR (IF_COND (t));
@@ -18433,6 +18434,13 @@  tsubst_expr (tree t, tree args, tsubst_f
 	}
       if (IF_STMT_CONSTEXPR_P (t) && integer_zerop (tmp))
 	/* Don't instantiate the THEN_CLAUSE. */;
+      else if (IF_STMT_CONSTEVAL_P (t))
+	{
+	  bool immediate_fn_ctx_p = in_immediate_fn_ctx_p;
+	  in_immediate_fn_ctx_p = true;
+	  RECUR (THEN_CLAUSE (t));
+	  in_immediate_fn_ctx_p = immediate_fn_ctx_p;
+	}
       else
 	{
 	  tree folded = fold_non_dependent_expr (tmp, complain);
@@ -19385,6 +19393,9 @@  tsubst_lambda_expr (tree t, tree args, t
 
       local_specialization_stack s (lss_copy);
 
+      bool immediate_fn_ctx_p = in_immediate_fn_ctx_p;
+      in_immediate_fn_ctx_p = false;
+
       tree body = start_lambda_function (fn, r);
 
       /* Now record them for lookup_init_capture_pack.  */
@@ -19425,6 +19436,8 @@  tsubst_lambda_expr (tree t, tree args, t
 
       finish_lambda_function (body);
 
+      in_immediate_fn_ctx_p = immediate_fn_ctx_p;
+
       if (nested)
 	pop_function_context ();
       else
--- gcc/cp/call.c.jj	2021-06-09 21:54:39.436194489 +0200
+++ gcc/cp/call.c	2021-06-10 09:49:35.949556470 +0200
@@ -8840,6 +8840,7 @@  immediate_invocation_p (tree fn, int nar
 	      || !DECL_IMMEDIATE_FUNCTION_P (current_function_decl))
 	  && (current_binding_level->kind != sk_function_parms
 	      || !current_binding_level->immediate_fn_ctx_p)
+	  && !in_immediate_fn_ctx_p
 	  /* As an exception, we defer std::source_location::current ()
 	     invocations until genericization because LWG3396 mandates
 	     special behavior for it.  */
--- gcc/testsuite/g++.dg/cpp23/consteval-if1.C.jj	2021-06-10 09:49:35.949556470 +0200
+++ gcc/testsuite/g++.dg/cpp23/consteval-if1.C	2021-06-10 09:49:35.949556470 +0200
@@ -0,0 +1,103 @@ 
+// P1938R3
+// { dg-do run { target c++20 } }
+// { dg-options "" }
+
+extern "C" void abort ();
+
+namespace std {
+  constexpr inline bool
+  is_constant_evaluated () noexcept
+  {
+    if consteval {	// { dg-warning "'if consteval' only available with" "" { target c++20_only } }
+      return true;
+    } else {
+      return false;
+    }
+  }
+}
+
+consteval int foo (int x) { return x; }
+consteval int bar () { return 2; }
+
+constexpr int
+baz (int x)
+{
+  int r = 0;
+  if consteval		// { dg-warning "'if consteval' only available with" "" { target c++20_only } }
+    {
+      r += foo (x);
+    }
+  else
+    {
+      r += bar ();
+    }
+  if ! consteval	// { dg-warning "'if consteval' only available with" "" { target c++20_only } }
+    {
+      r += 2 * bar ();
+    }
+  else
+    {
+      r += foo (8 * x);
+    }
+  if (std::is_constant_evaluated ())
+    r = -r;
+  if consteval		// { dg-warning "'if consteval' only available with" "" { target c++20_only } }
+    {
+      r += foo (32 * x);
+    }
+  if not consteval	// { dg-warning "'if consteval' only available with" "" { target c++20_only } }
+    {
+      r += 32 * bar ();
+    }
+  return r;
+}
+
+template <typename T>
+constexpr int
+qux (T x)
+{
+  T r = 0;
+  if consteval		// { dg-warning "'if consteval' only available with" "" { target c++20_only } }
+    {
+      r += foo (x);
+    }
+  else
+    {
+      r += bar ();
+    }
+  if ! consteval	// { dg-warning "'if consteval' only available with" "" { target c++20_only } }
+    {
+      r += 2 * bar ();
+    }
+  else
+    {
+      r += foo (8 * x);
+    }
+  if (std::is_constant_evaluated ())
+    r = -r;
+  if consteval		// { dg-warning "'if consteval' only available with" "" { target c++20_only } }
+    {
+      r += foo (32 * x);
+    }
+  if not consteval	// { dg-warning "'if consteval' only available with" "" { target c++20_only } }
+    {
+      r += 32 * bar ();
+    }
+  return r;
+}
+
+constexpr int a = baz (1);
+static_assert (a == 23);
+int b = baz (1);
+constexpr int c = qux (1);
+static_assert (c == 23);
+int d = qux<int> (1);
+
+int
+main ()
+{
+  if (b != 23 || d != 23)
+    abort ();
+  if (baz (1) != 70 || qux (1) != 70 || qux (1LL) != 70)
+    abort ();
+}
--- gcc/testsuite/g++.dg/cpp23/consteval-if2.C.jj	2021-06-10 09:49:35.949556470 +0200
+++ gcc/testsuite/g++.dg/cpp23/consteval-if2.C	2021-06-10 09:58:22.459240019 +0200
@@ -0,0 +1,129 @@ 
+// P1938R3
+// { dg-do compile { target c++20 } }
+// { dg-options "" }
+
+constexpr bool f()
+{
+  if consteval (true) {}	// { dg-error "'if consteval' requires compound statement" }
+				// { dg-error "expected" "" { target *-*-* } .-1 }
+				// { dg-warning "'if consteval' only available with" "" { target c++20_only } .-2 }
+  if not consteval (false) {}	// { dg-error "'if consteval' requires compound statement" }
+				// { dg-error "expected" "" { target *-*-* } .-1 }
+				// { dg-warning "'if consteval' only available with" "" { target c++20_only } .-2 }
+  if consteval if (true) {}	// { dg-error "'if consteval' requires compound statement" }
+				// { dg-warning "'if consteval' only available with" "" { target c++20_only } .-1 }
+  if ! consteval {} else ;	// { dg-error "'if consteval' requires compound statement" }
+				// { dg-warning "'if consteval' only available with" "" { target c++20_only } .-1 }
+  if consteval {} else if (true) {}	// { dg-error "'if consteval' requires compound statement" }
+				// { dg-warning "'if consteval' only available with" "" { target c++20_only } .-1 }
+  if (true)
+    if consteval		// { dg-warning "'if consteval' only available with" "" { target c++20_only } }
+      {
+      }
+    else ;			// { dg-error "'if consteval' requires compound statement" }
+  return false;
+}
+
+consteval int foo (int x) { return x; }
+consteval int bar () { return 2; }
+
+constexpr int
+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" }
+    }
+  else
+    {
+      r += bar ();
+    }
+  if consteval		// { dg-warning "'if consteval' only available with" "" { target c++20_only } }
+    {
+      r += 2 * bar ();
+    }
+  else
+    {
+      r += foo (8 * x);	// { dg-error "'x' 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" }
+    }
+  if consteval		// { dg-warning "'if consteval' only available with" "" { target c++20_only } }
+    {
+      r += 32 * bar ();
+    }
+  return r;
+}
+
+template <typename T>
+constexpr int
+qux (int x)
+{
+  int r = 0;
+  if not consteval	// { dg-warning "'if consteval' only available with" "" { target c++20_only } }
+    {
+      r += foo (x);	// { dg-error "'x' is not a constant expression" }
+    }
+  else
+    {
+      r += bar ();
+    }
+  if consteval		// { dg-warning "'if consteval' only available with" "" { target c++20_only } }
+    {
+      r += 2 * bar ();
+    }
+  else
+    {
+      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 "is not a constant expression" }
+    }
+  if consteval		// { dg-warning "'if consteval' only available with" "" { target c++20_only } }
+    {
+      r += 32 * bar ();
+    }
+  return r;
+}
+
+template <typename T>
+constexpr T
+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" }
+    }
+  else
+    {
+      r += bar ();
+    }
+  if consteval		// { dg-warning "'if consteval' only available with" "" { target c++20_only } }
+    {
+      r += 2 * bar ();
+    }
+  else
+    {
+      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 "is not a constant expression" }
+    }
+  if consteval		// { dg-warning "'if consteval' only available with" "" { target c++20_only } }
+    {
+      r += 32 * bar ();
+    }
+  return r;
+}
+
+int
+garply (int x)
+{
+  return corge (x);
+}
--- gcc/testsuite/g++.dg/cpp23/consteval-if3.C.jj	2021-06-10 09:49:35.949556470 +0200
+++ gcc/testsuite/g++.dg/cpp23/consteval-if3.C	2021-06-10 09:49:35.949556470 +0200
@@ -0,0 +1,73 @@ 
+// P1938R3
+// { dg-do run { target c++20 } }
+// { dg-options "" }
+
+constexpr inline bool
+is_constant_evaluated () noexcept
+{
+  if consteval { return true; } else { return false; }	// { dg-warning "'if consteval' only available with" "" { target c++20_only } }
+}
+
+template<int N> struct X { int v = N; };
+X<is_constant_evaluated ()> x; // type X<true>
+int y = 4;
+int a = is_constant_evaluated () ? y : 1; // initializes a to 1
+int b = is_constant_evaluated () ? 2 : y; // initializes b to 2
+int c = y + (is_constant_evaluated () ? 2 : y); // initializes c to 2*y
+int d = is_constant_evaluated (); // initializes d to 1
+int e = d + is_constant_evaluated (); // initializes e to 1 + 0
+
+struct false_type { static constexpr bool value = false; };
+struct true_type { static constexpr bool value = true; };
+template<class T, class U>
+struct is_same : false_type {};
+template<class T>
+struct is_same<T, T> : true_type {};
+
+constexpr int
+foo (int x)
+{
+  const int n = is_constant_evaluated () ? 13 : 17; // n == 13
+  int m = is_constant_evaluated () ? 13 : 17; // m might be 13 or 17 (see below)
+  char arr[n] = {}; // char[13]
+  return m + sizeof (arr) + x;
+}
+
+constexpr int
+bar ()
+{
+  const int n = is_constant_evaluated() ? 13 : 17;
+  X<n> x1;
+  X<is_constant_evaluated() ? 13 : 17> x2;
+  static_assert (is_same<decltype (x1), decltype (x2)>::value, "x1/x2's type");
+  return x1.v + x2.v;
+}
+
+int p = foo (0); // m == 13; initialized to 26
+int q = p + foo (0); // m == 17 for this call; initialized to 56
+static_assert (bar () == 26, "bar");
+
+struct S { int a, b; };
+
+S s = { is_constant_evaluated () ? 2 : 3, y };
+S t = { is_constant_evaluated () ? 2 : 3, 4 };
+
+static_assert (is_same<decltype (x), X<true> >::value, "x's type");
+
+int
+main ()
+{
+  if (a != 1 || b != 2 || c != 8 || d != 1 || e != 1 || p != 26 || q != 56)
+    __builtin_abort ();
+  if (s.a != 3 || s.b != 4 || t.a != 2 || t.b != 4)
+    __builtin_abort ();
+  if (foo (y) != 34)
+    __builtin_abort ();
+#if __cplusplus >= 201703L
+  if constexpr (foo (0) != 26)
+    __builtin_abort ();
+#endif
+  constexpr int w = foo (0);
+  if (w != 26)
+    __builtin_abort ();
+}
--- gcc/testsuite/g++.dg/cpp23/consteval-if4.C.jj	2021-06-10 09:49:35.949556470 +0200
+++ gcc/testsuite/g++.dg/cpp23/consteval-if4.C	2021-06-10 09:49:35.949556470 +0200
@@ -0,0 +1,44 @@ 
+// { dg-do compile { target c++20 } }
+// { dg-options "-w" }
+
+void f()
+{
+  goto l;			// { dg-message "from here" }
+  if consteval			// { dg-message "enters 'consteval if'" }
+    {
+    l:;				// { dg-error "jump to label" }
+    }
+}
+
+void g()
+{
+  goto l;			// { dg-message "from here" }
+  if not consteval		// { dg-message "enters 'consteval if'" }
+    {
+    l:;				// { dg-error "jump to label" }
+    }
+}
+
+void h()
+{
+  goto l;			// { dg-message "from here" }
+  if consteval			// { dg-message "enters 'consteval if'" }
+    {
+    }
+  else
+    {
+    l:;				// { dg-error "jump to label" }
+    }
+}
+
+void i()
+{
+  goto l;			// { dg-message "from here" }
+  if not consteval		// { dg-message "enters 'consteval if'" }
+    {
+    }
+  else
+    {
+    l:;				// { dg-error "jump to label" }
+    }
+}
--- gcc/testsuite/g++.dg/cpp23/consteval-if5.C.jj	2021-06-10 09:49:35.949556470 +0200
+++ gcc/testsuite/g++.dg/cpp23/consteval-if5.C	2021-06-10 09:49:35.949556470 +0200
@@ -0,0 +1,14 @@ 
+// { dg-do compile { target c++20 } }
+// { dg-options "-w" }
+
+void f()
+{
+  if consteval			// { dg-message "enters 'consteval if'" }
+    {
+      goto l;			// { dg-message "from here" }
+    }
+  else
+    {
+    l:;				// { dg-error "jump to label" }
+    }
+}
--- gcc/testsuite/g++.dg/cpp23/consteval-if6.C.jj	2021-06-10 09:49:35.949556470 +0200
+++ gcc/testsuite/g++.dg/cpp23/consteval-if6.C	2021-06-10 09:49:35.949556470 +0200
@@ -0,0 +1,16 @@ 
+// { dg-do compile { target c++20 } }
+// { dg-options "-w" }
+
+void f()
+{
+  if consteval
+    {
+      goto l;
+    l:;
+    }
+  else
+    {
+      goto l2;
+    l2:;
+    }
+}
--- gcc/testsuite/g++.dg/cpp23/consteval-if7.C.jj	2021-06-10 09:49:35.949556470 +0200
+++ gcc/testsuite/g++.dg/cpp23/consteval-if7.C	2021-06-10 09:49:35.949556470 +0200
@@ -0,0 +1,16 @@ 
+// { dg-do compile { target c++20 } }
+// { dg-options "-w" }
+
+void f()
+{
+  if not consteval
+    {
+    l:;
+      goto l;
+    }
+  else
+    {
+    l2:;
+      goto l2;
+    }
+}
--- gcc/testsuite/g++.dg/cpp23/consteval-if8.C.jj	2021-06-10 09:49:35.950556456 +0200
+++ gcc/testsuite/g++.dg/cpp23/consteval-if8.C	2021-06-10 09:49:35.950556456 +0200
@@ -0,0 +1,14 @@ 
+// { dg-do compile { target c++20 } }
+// { dg-options "-w" }
+
+void f()
+{
+  if consteval
+    {
+    l:;				// { dg-error "jump to label" }
+    }
+  else
+    {
+      goto l;			// { dg-message "from here" }
+    }
+}
--- gcc/testsuite/g++.dg/cpp23/consteval-if9.C.jj	2021-06-10 09:49:35.950556456 +0200
+++ gcc/testsuite/g++.dg/cpp23/consteval-if9.C	2021-06-10 09:49:35.950556456 +0200
@@ -0,0 +1,11 @@ 
+// { dg-do compile { target c++20 } }
+// { dg-options "-w" }
+
+constexpr void f(int i)
+{
+  switch (i)
+    if consteval		// { dg-message "enters 'consteval if'" }
+      {
+      case 42:;			// { dg-error "jump to case label" }
+      }
+}
--- gcc/testsuite/g++.dg/cpp23/consteval-if10.C.jj	2021-06-10 09:58:15.456337333 +0200
+++ gcc/testsuite/g++.dg/cpp23/consteval-if10.C	2021-06-10 10:15:50.288688058 +0200
@@ -0,0 +1,36 @@ 
+// P1938R3
+// { dg-do compile { target c++20 } }
+// { dg-options "" }
+
+consteval int foo (int x) { return x; }
+
+constexpr int
+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" }
+      y ();
+    }
+  return r;
+}
+
+template <typename T>
+constexpr T
+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" }
+      y ();
+    }
+  return r;
+}
+
+int
+qux (int x)
+{
+  return baz (x);
+}
--- gcc/testsuite/g++.dg/cpp23/feat-cxx2b.C.jj	2021-06-10 09:46:03.851503924 +0200
+++ gcc/testsuite/g++.dg/cpp23/feat-cxx2b.C	2021-06-10 09:49:35.950556456 +0200
@@ -545,3 +545,9 @@ 
 #elif __cpp_size_t_suffix != 202011
 #  error "__cpp_size_t_suffix != 202011"
 #endif
+
+#ifndef __cpp_if_consteval
+#  error "__cpp_if_consteval"
+#elif __cpp_if_consteval != 202106
+#  error "__cpp_if_consteval != 202106"
+#endif