c++, v3: Implement C++26 P2809R3 - Trivial infinite loops are not Undefined Behavior

Message ID ZhUQ/MW8clDoNEif@tucnak
State New
Headers
Series c++, v3: Implement C++26 P2809R3 - Trivial infinite loops are not Undefined Behavior |

Checks

Context Check Description
linaro-tcwg-bot/tcwg_gcc_build--master-aarch64 success Testing passed
linaro-tcwg-bot/tcwg_gcc_build--master-arm success Testing passed
linaro-tcwg-bot/tcwg_gcc_check--master-arm success Testing passed
linaro-tcwg-bot/tcwg_gcc_check--master-aarch64 success Testing passed

Commit Message

Jakub Jelinek April 9, 2024, 9:57 a.m. UTC
  On Mon, Apr 08, 2024 at 06:53:29PM -0400, Jason Merrill wrote:
> > +  if (warn_tautological_compare)
> > +    {
> > +      tree cond = *condp;
> > +      while (TREE_CODE (cond) == ANNOTATE_EXPR)
> > +	cond = TREE_OPERAND (cond, 0);
> > +      if (trivial_infinite
> > +	  && !maybe_constexpr_fn (current_function_decl))
> 
> I think we also want this warning for constexpr functions, since they can
> also be evaluated at runtime.  Just not for consteval.

If we leave in the maybe_constexpr_fn (current_function_decl) case the
"and evaluates to false when actually evaluating "
"the condition in non-%<constexpr%> function"
note, yes (as implemented in the updated patch below).

> > +	maybe_warn_for_constant_evaluated (cond, /*constexpr_if=*/false,
> > +					   /*trivial_infinite=*/true);
> > +      else if (!trivially_empty
> > +	       || (!processing_template_decl && !trivial_infinite)
> > +	       || DECL_IMMEDIATE_FUNCTION_P (current_function_decl))
> 
> Do we need these conditions on the else?
> 
> Well, I suppose the existing !maybe_constexpr_fn case in maybe_warn_....
> would be misleading for a trivially empty loop.
> 
> So I suppose the earlier condition should be trivially_empty rather than
> trivial_infinite.

The intent of the above is to warn only if we have something relevant and
final (i.e. not something that will be different e.g. during instantiation)
to warn about.

So, IMHO:
1) DECL_IMMEDIATE_FUNCTION_P (current_function_decl), regardless of
   trivial_infinite or not, is always true, doesn't depend on anything else;
2) otherwise, trivial_infinite, warn that during trivial infinite checking
   it evaluates true (and for non-constexpr functions otherwise evaluates
   to false);
3) otherwise, !trivially_empty in non-constexpr functions, always
   evaluates to false
4) otherwise, !processing_template_decl we know !trivial_infinite is final,
   so in non-constexpr functions always evaluates to false as well
Now, the processing_template_decl case, we don't really know if it will
evaluate to true or false when manifestly-constant evaluated to check for
trivial_infinite, so I think we should defer until instantiation.
Otherwise, we could warn that std::is_constant_evaluated() always
evaluates false in non-constexpr functions even when it will actually
evaluate to true if it will be trivial infinite loop.
Because of the earlier changes I've changed the
  || (!processing_template_decl && !trivial_infinite)
condition just to
  || !processing_template_decl
because in the trivial_infinite case it will fall into this condition
only for the DECL_IMMEDIATE_FUNCTION_P (current_function_decl) case.

2024-04-09  Jakub Jelinek  <jakub@redhat.com>

	PR c++/114462
gcc/
	* tree-core.h (enum annot_expr_kind): Add
	annot_expr_maybe_infinite_kind enumerator.
	* gimplify.cc (gimple_boolify): Handle annot_expr_maybe_infinite_kind.
	* tree-cfg.cc (replace_loop_annotate_in_block): Likewise.
	(replace_loop_annotate): Likewise.  Move loop->finite_p initialization
	before the replace_loop_annotate_in_block calls.
	* tree-pretty-print.cc (dump_generic_node): Handle
	annot_expr_maybe_infinite_kind.
gcc/cp/
	* semantics.cc: Implement C++26 P2809R3 - Trivial infinite
	loops are not Undefined Behavior.
	(maybe_warn_for_constant_evaluated): Add trivial_infinite argument
	and emit special diagnostics for that case.
	(finish_if_stmt_cond): Adjust caller.
	(finish_loop_cond): New function.
	(finish_while_stmt): Use it.
	(finish_do_stmt): Likewise.
	(finish_for_stmt): Likewise.
gcc/testsuite/
	* g++.dg/cpp26/trivial-infinite-loop1.C: New test.
	* g++.dg/cpp26/trivial-infinite-loop2.C: New test.
	* g++.dg/cpp26/trivial-infinite-loop3.C: New test.



	Jakub
  

Comments

Jason Merrill April 9, 2024, 2:41 p.m. UTC | #1
On 4/9/24 05:57, Jakub Jelinek wrote:
> On Mon, Apr 08, 2024 at 06:53:29PM -0400, Jason Merrill wrote:
>>> +  if (warn_tautological_compare)
>>> +    {
>>> +      tree cond = *condp;
>>> +      while (TREE_CODE (cond) == ANNOTATE_EXPR)
>>> +	cond = TREE_OPERAND (cond, 0);
>>> +      if (trivial_infinite
>>> +	  && !maybe_constexpr_fn (current_function_decl))
>>
>> I think we also want this warning for constexpr functions, since they can
>> also be evaluated at runtime.  Just not for consteval.
> 
> If we leave in the maybe_constexpr_fn (current_function_decl) case the
> "and evaluates to false when actually evaluating"
> "the condition in non-%<constexpr%> function"
> note, yes (as implemented in the updated patch below).
> 
>>> +	maybe_warn_for_constant_evaluated (cond, /*constexpr_if=*/false,
>>> +					   /*trivial_infinite=*/true);
>>> +      else if (!trivially_empty
>>> +	       || (!processing_template_decl && !trivial_infinite)
>>> +	       || DECL_IMMEDIATE_FUNCTION_P (current_function_decl))
>>
>> Do we need these conditions on the else?
>>
>> Well, I suppose the existing !maybe_constexpr_fn case in maybe_warn_....
>> would be misleading for a trivially empty loop.
>>
>> So I suppose the earlier condition should be trivially_empty rather than
>> trivial_infinite.
> 
> The intent of the above is to warn only if we have something relevant and
> final (i.e. not something that will be different e.g. during instantiation)
> to warn about.
> 
> So, IMHO:
> 1) DECL_IMMEDIATE_FUNCTION_P (current_function_decl), regardless of
>     trivial_infinite or not, is always true, doesn't depend on anything else;
> 2) otherwise, trivial_infinite, warn that during trivial infinite checking
>     it evaluates true (and for non-constexpr functions otherwise evaluates
>     to false);
> 3) otherwise, !trivially_empty in non-constexpr functions, always
>     evaluates to false
> 4) otherwise, !processing_template_decl we know !trivial_infinite is final,
>     so in non-constexpr functions always evaluates to false as well
> Now, the processing_template_decl case, we don't really know if it will
> evaluate to true or false when manifestly-constant evaluated to check for
> trivial_infinite, so I think we should defer until instantiation.
> Otherwise, we could warn that std::is_constant_evaluated() always
> evaluates false in non-constexpr functions even when it will actually
> evaluate to true if it will be trivial infinite loop.
> Because of the earlier changes I've changed the
>    || (!processing_template_decl && !trivial_infinite)
> condition just to
>    || !processing_template_decl
> because in the trivial_infinite case it will fall into this condition
> only for the DECL_IMMEDIATE_FUNCTION_P (current_function_decl) case.

OK.

> 2024-04-09  Jakub Jelinek  <jakub@redhat.com>
> 
> 	PR c++/114462
> gcc/
> 	* tree-core.h (enum annot_expr_kind): Add
> 	annot_expr_maybe_infinite_kind enumerator.
> 	* gimplify.cc (gimple_boolify): Handle annot_expr_maybe_infinite_kind.
> 	* tree-cfg.cc (replace_loop_annotate_in_block): Likewise.
> 	(replace_loop_annotate): Likewise.  Move loop->finite_p initialization
> 	before the replace_loop_annotate_in_block calls.
> 	* tree-pretty-print.cc (dump_generic_node): Handle
> 	annot_expr_maybe_infinite_kind.
> gcc/cp/
> 	* semantics.cc: Implement C++26 P2809R3 - Trivial infinite
> 	loops are not Undefined Behavior.
> 	(maybe_warn_for_constant_evaluated): Add trivial_infinite argument
> 	and emit special diagnostics for that case.
> 	(finish_if_stmt_cond): Adjust caller.
> 	(finish_loop_cond): New function.
> 	(finish_while_stmt): Use it.
> 	(finish_do_stmt): Likewise.
> 	(finish_for_stmt): Likewise.
> gcc/testsuite/
> 	* g++.dg/cpp26/trivial-infinite-loop1.C: New test.
> 	* g++.dg/cpp26/trivial-infinite-loop2.C: New test.
> 	* g++.dg/cpp26/trivial-infinite-loop3.C: New test.
> 
> --- gcc/tree-core.h.jj	2024-04-05 09:19:48.319044584 +0200
> +++ gcc/tree-core.h	2024-04-09 11:27:24.535821914 +0200
> @@ -983,6 +983,7 @@ enum annot_expr_kind {
>     annot_expr_no_vector_kind,
>     annot_expr_vector_kind,
>     annot_expr_parallel_kind,
> +  annot_expr_maybe_infinite_kind,
>     annot_expr_kind_last
>   };
>   
> --- gcc/gimplify.cc.jj	2024-04-05 09:19:48.216046013 +0200
> +++ gcc/gimplify.cc	2024-04-09 11:27:24.538821874 +0200
> @@ -4584,6 +4584,7 @@ gimple_boolify (tree expr)
>   	case annot_expr_no_vector_kind:
>   	case annot_expr_vector_kind:
>   	case annot_expr_parallel_kind:
> +	case annot_expr_maybe_infinite_kind:
>   	  TREE_OPERAND (expr, 0) = gimple_boolify (TREE_OPERAND (expr, 0));
>   	  if (TREE_CODE (type) != BOOLEAN_TYPE)
>   	    TREE_TYPE (expr) = boolean_type_node;
> --- gcc/tree-cfg.cc.jj	2024-04-05 09:19:12.915535710 +0200
> +++ gcc/tree-cfg.cc	2024-04-09 11:27:24.539821860 +0200
> @@ -297,6 +297,9 @@ replace_loop_annotate_in_block (basic_bl
>   	  loop->can_be_parallel = true;
>   	  loop->safelen = INT_MAX;
>   	  break;
> +	case annot_expr_maybe_infinite_kind:
> +	  loop->finite_p = false;
> +	  break;
>   	default:
>   	  gcc_unreachable ();
>   	}
> @@ -320,12 +323,12 @@ replace_loop_annotate (void)
>   
>     for (auto loop : loops_list (cfun, 0))
>       {
> +      /* Push the global flag_finite_loops state down to individual loops.  */
> +      loop->finite_p = flag_finite_loops;
> +
>         /* Check all exit source blocks for annotations.  */
>         for (auto e : get_loop_exit_edges (loop))
>   	replace_loop_annotate_in_block (e->src, loop);
> -
> -      /* Push the global flag_finite_loops state down to individual loops.  */
> -      loop->finite_p = flag_finite_loops;
>       }
>   
>     /* Remove IFN_ANNOTATE.  Safeguard for the case loop->latch == NULL.  */
> @@ -347,6 +350,7 @@ replace_loop_annotate (void)
>   	    case annot_expr_no_vector_kind:
>   	    case annot_expr_vector_kind:
>   	    case annot_expr_parallel_kind:
> +	    case annot_expr_maybe_infinite_kind:
>   	      break;
>   	    default:
>   	      gcc_unreachable ();
> --- gcc/tree-pretty-print.cc.jj	2024-04-05 09:19:13.035534046 +0200
> +++ gcc/tree-pretty-print.cc	2024-04-09 11:27:24.540821847 +0200
> @@ -3479,6 +3479,9 @@ dump_generic_node (pretty_printer *pp, t
>   	case annot_expr_parallel_kind:
>   	  pp_string (pp, ", parallel");
>   	  break;
> +	case annot_expr_maybe_infinite_kind:
> +	  pp_string (pp, ", maybe-infinite");
> +	  break;
>   	default:
>   	  gcc_unreachable ();
>   	}
> --- gcc/cp/semantics.cc.jj	2024-04-09 09:31:26.343560220 +0200
> +++ gcc/cp/semantics.cc	2024-04-09 11:30:28.608332303 +0200
> @@ -1090,7 +1090,8 @@ find_std_constant_evaluated_r (tree *tp,
>      (e.g., in a non-constexpr non-consteval function) so give the user a clue.  */
>   
>   static void
> -maybe_warn_for_constant_evaluated (tree cond, bool constexpr_if)
> +maybe_warn_for_constant_evaluated (tree cond, bool constexpr_if,
> +				   bool trivial_infinite)
>   {
>     if (!warn_tautological_compare)
>       return;
> @@ -1108,6 +1109,18 @@ maybe_warn_for_constant_evaluated (tree
>   	warning_at (EXPR_LOCATION (cond), OPT_Wtautological_compare,
>   		    "%<std::is_constant_evaluated%> always evaluates to "
>   		    "true in %<if constexpr%>");
> +      else if (trivial_infinite)
> +	{
> +	  auto_diagnostic_group d;
> +	  if (warning_at (EXPR_LOCATION (cond), OPT_Wtautological_compare,
> +			  "%<std::is_constant_evaluated%> evaluates to "
> +			  "true when checking if trivially empty iteration "
> +			  "statement is trivial infinite loop")
> +	      && !maybe_constexpr_fn (current_function_decl))
> +	    inform (EXPR_LOCATION (cond),
> +		    "and evaluates to false when actually evaluating "
> +		    "the condition in non-%<constexpr%> function");
> +	}
>         else if (!maybe_constexpr_fn (current_function_decl))
>   	warning_at (EXPR_LOCATION (cond), OPT_Wtautological_compare,
>   		    "%<std::is_constant_evaluated%> always evaluates to "
> @@ -1126,7 +1139,8 @@ tree
>   finish_if_stmt_cond (tree orig_cond, tree if_stmt)
>   {
>     tree cond = maybe_convert_cond (orig_cond);
> -  maybe_warn_for_constant_evaluated (cond, IF_STMT_CONSTEXPR_P (if_stmt));
> +  maybe_warn_for_constant_evaluated (cond, IF_STMT_CONSTEXPR_P (if_stmt),
> +				     /*trivial_infinite=*/false);
>     if (IF_STMT_CONSTEXPR_P (if_stmt)
>         && !type_dependent_expression_p (cond)
>         && require_constant_expression (cond)
> @@ -1205,6 +1219,48 @@ finish_if_stmt (tree if_stmt)
>     add_stmt (do_poplevel (scope));
>   }
>   
> +/* Determine if iteration statement with *CONDP condition and
> +   loop BODY is trivially empty iteration statement or even
> +   trivial infinite loop.  In the latter case for -ffinite-loops
> +   add ANNOTATE_EXPR to mark the loop as maybe validly infinite.
> +   Also, emit -Wtautological-compare warning for std::is_constant_evaluated ()
> +   calls in the condition when needed.  */
> +
> +static void
> +finish_loop_cond (tree *condp, tree body)
> +{
> +  if (TREE_CODE (*condp) == INTEGER_CST)
> +    return;
> +  bool trivially_empty = expr_first (body) == NULL_TREE;
> +  bool trivial_infinite = false;
> +  if (trivially_empty)
> +    {
> +      tree c = fold_non_dependent_expr (*condp, tf_none,
> +					/*manifestly_const_eval=*/true);
> +      trivial_infinite = c && integer_nonzerop (c);
> +    }
> +  if (warn_tautological_compare)
> +    {
> +      tree cond = *condp;
> +      while (TREE_CODE (cond) == ANNOTATE_EXPR)
> +	cond = TREE_OPERAND (cond, 0);
> +      if (trivial_infinite
> +	  && !DECL_IMMEDIATE_FUNCTION_P (current_function_decl))
> +	maybe_warn_for_constant_evaluated (cond, /*constexpr_if=*/false,
> +					   /*trivial_infinite=*/true);
> +      else if (!trivially_empty
> +	       || !processing_template_decl
> +	       || DECL_IMMEDIATE_FUNCTION_P (current_function_decl))
> +	maybe_warn_for_constant_evaluated (cond, /*constexpr_if=*/false,
> +					   /*trivial_infinite=*/false);
> +    }
> +  if (trivial_infinite && flag_finite_loops && !processing_template_decl)
> +    *condp = build3 (ANNOTATE_EXPR, TREE_TYPE (*condp), *condp,
> +		     build_int_cst (integer_type_node,
> +				    annot_expr_maybe_infinite_kind),
> +		     integer_zero_node);
> +}
> +
>   /* Begin a while-statement.  Returns a newly created WHILE_STMT if
>      appropriate.  */
>   
> @@ -1260,6 +1316,7 @@ finish_while_stmt (tree while_stmt)
>   {
>     end_maybe_infinite_loop (boolean_true_node);
>     WHILE_BODY (while_stmt) = do_poplevel (WHILE_BODY (while_stmt));
> +  finish_loop_cond (&WHILE_COND (while_stmt), WHILE_BODY (while_stmt));
>   }
>   
>   /* Begin a do-statement.  Returns a newly created DO_STMT if
> @@ -1317,6 +1374,12 @@ finish_do_stmt (tree cond, tree do_stmt,
>   		   build_int_cst (integer_type_node, annot_expr_no_vector_kind),
>   		   integer_zero_node);
>     DO_COND (do_stmt) = cond;
> +  tree do_body = DO_BODY (do_stmt);
> +  if (CONVERT_EXPR_P (do_body)
> +      && integer_zerop (TREE_OPERAND (do_body, 0))
> +      && VOID_TYPE_P (TREE_TYPE (do_body)))
> +    do_body = NULL_TREE;
> +  finish_loop_cond (&DO_COND (do_stmt), do_body);
>   }
>   
>   /* Finish a return-statement.  The EXPRESSION returned, if any, is as
> @@ -1487,7 +1550,13 @@ finish_for_stmt (tree for_stmt)
>     if (TREE_CODE (for_stmt) == RANGE_FOR_STMT)
>       RANGE_FOR_BODY (for_stmt) = do_poplevel (RANGE_FOR_BODY (for_stmt));
>     else
> -    FOR_BODY (for_stmt) = do_poplevel (FOR_BODY (for_stmt));
> +    {
> +      FOR_BODY (for_stmt) = do_poplevel (FOR_BODY (for_stmt));
> +      if (FOR_COND (for_stmt))
> +	finish_loop_cond (&FOR_COND (for_stmt),
> +			  FOR_EXPR (for_stmt) ? integer_one_node
> +					      : FOR_BODY (for_stmt));
> +    }
>   
>     /* Pop the scope for the body of the loop.  */
>     tree *scope_ptr = (TREE_CODE (for_stmt) == RANGE_FOR_STMT
> --- gcc/testsuite/g++.dg/cpp26/trivial-infinite-loop1.C.jj	2024-04-09 11:27:24.542821820 +0200
> +++ gcc/testsuite/g++.dg/cpp26/trivial-infinite-loop1.C	2024-04-09 11:27:24.542821820 +0200
> @@ -0,0 +1,148 @@
> +// P2809R3 - Trivial infinite loops are not Undefined Behavior
> +// { dg-do compile { target c++11 } }
> +// { dg-additional-options "-fdump-tree-gimple -fno-inline -Wtautological-compare -O2" }
> +// { dg-final { scan-tree-dump-times ".ANNOTATE \\\(\[^\n\r]*, 5, 0\\\)" 32 "gimple" { target c++20 } } }
> +// { dg-final { scan-tree-dump-times ".ANNOTATE \\\(\[^\n\r]*, 5, 0\\\)" 16 "gimple" { target c++17_down } } }
> +
> +volatile int v;
> +
> +constexpr bool
> +foo ()
> +{
> +  return true;
> +}
> +
> +struct S
> +{
> +  constexpr S () : s (true) {}
> +  constexpr operator bool () const { return s; }
> +  bool s;
> +};
> +
> +#if __cplusplus >= 202002L
> +namespace std {
> +  constexpr inline bool
> +  is_constant_evaluated () noexcept
> +  {
> +#if __cpp_if_consteval >= 202106L
> +    if consteval { return true; } else { return false; }
> +#else
> +    return __builtin_is_constant_evaluated ();
> +#endif
> +  }
> +}
> +
> +constexpr bool
> +baz ()
> +{
> +  return std::is_constant_evaluated ();
> +}
> +#endif
> +
> +void
> +bar (int x)
> +{
> +  switch (x)
> +    {
> +    case 0:
> +      while (foo ()) ;
> +      break;
> +    case 1:
> +      while (foo ()) {}
> +      break;
> +    case 2:
> +      do ; while (foo ());
> +      break;
> +    case 3:
> +      do {} while (foo ());
> +      break;
> +    case 4:
> +      for (v = 42; foo (); ) ;
> +      break;
> +    case 5:
> +      for (v = 42; foo (); ) {}
> +      break;
> +    case 6:
> +      for (int w = 42; foo (); ) ;
> +      break;
> +    case 7:
> +      for (int w = 42; foo (); ) {}
> +      break;
> +    case 10:
> +      while (S {}) ;
> +      break;
> +    case 11:
> +      while (S {}) {}
> +      break;
> +    case 12:
> +      do ; while (S {});
> +      break;
> +    case 13:
> +      do {} while (S {});
> +      break;
> +    case 14:
> +      for (v = 42; S {}; ) ;
> +      break;
> +    case 15:
> +      for (v = 42; S {}; ) {}
> +      break;
> +    case 16:
> +      for (int w = 42; S {}; ) ;
> +      break;
> +    case 17:
> +      for (int w = 42; S {}; ) {}
> +      break;
> +#if __cplusplus >= 202002L
> +    case 20:
> +      while (baz ()) ;
> +      break;
> +    case 21:
> +      while (baz ()) {}
> +      break;
> +    case 22:
> +      do ; while (baz ());
> +      break;
> +    case 23:
> +      do {} while (baz ());
> +      break;
> +    case 24:
> +      for (v = 42; baz (); ) ;
> +      break;
> +    case 25:
> +      for (v = 42; baz (); ) {}
> +      break;
> +    case 26:
> +      for (int w = 42; baz (); ) ;
> +      break;
> +    case 27:
> +      for (int w = 42; baz (); ) {}
> +      break;
> +    case 30:
> +      while (std::is_constant_evaluated ()) ;			// { dg-warning "'std::is_constant_evaluated' evaluates to true when checking if trivially empty iteration statement is trivial infinite loop" "" { target c++20 } }
> +      break;							// { dg-message "and evaluates to false when actually evaluating the condition in non-'constexpr' function" "" { target c++20 } .-1 }
> +    case 31:
> +      while (std::is_constant_evaluated ()) {}			// { dg-warning "'std::is_constant_evaluated' evaluates to true when checking if trivially empty iteration statement is trivial infinite loop" "" { target c++20 } }
> +      break;							// { dg-message "and evaluates to false when actually evaluating the condition in non-'constexpr' function" "" { target c++20 } .-1 }
> +    case 32:
> +      do ; while (std::is_constant_evaluated ());		// { dg-warning "'std::is_constant_evaluated' evaluates to true when checking if trivially empty iteration statement is trivial infinite loop" "" { target c++20 } }
> +      break;							// { dg-message "and evaluates to false when actually evaluating the condition in non-'constexpr' function" "" { target c++20 } .-1 }
> +    case 33:
> +      do {} while (std::is_constant_evaluated ());		// { dg-warning "'std::is_constant_evaluated' evaluates to true when checking if trivially empty iteration statement is trivial infinite loop" "" { target c++20 } }
> +      break;							// { dg-message "and evaluates to false when actually evaluating the condition in non-'constexpr' function" "" { target c++20 } .-1 }
> +    case 34:
> +      for (v = 42; std::is_constant_evaluated (); ) ;		// { dg-warning "'std::is_constant_evaluated' evaluates to true when checking if trivially empty iteration statement is trivial infinite loop" "" { target c++20 } }
> +      break;							// { dg-message "and evaluates to false when actually evaluating the condition in non-'constexpr' function" "" { target c++20 } .-1 }
> +    case 35:
> +      for (v = 42; std::is_constant_evaluated (); ) {}		// { dg-warning "'std::is_constant_evaluated' evaluates to true when checking if trivially empty iteration statement is trivial infinite loop" "" { target c++20 } }
> +      break;							// { dg-message "and evaluates to false when actually evaluating the condition in non-'constexpr' function" "" { target c++20 } .-1 }
> +    case 36:
> +      for (int w = 42; std::is_constant_evaluated (); ) ;	// { dg-warning "'std::is_constant_evaluated' evaluates to true when checking if trivially empty iteration statement is trivial infinite loop" "" { target c++20 } }
> +      break;							// { dg-message "and evaluates to false when actually evaluating the condition in non-'constexpr' function" "" { target c++20 } .-1 }
> +    case 37:
> +      for (int w = 42; std::is_constant_evaluated (); ) {}	// { dg-warning "'std::is_constant_evaluated' evaluates to true when checking if trivially empty iteration statement is trivial infinite loop" "" { target c++20 } }
> +      break;							// { dg-message "and evaluates to false when actually evaluating the condition in non-'constexpr' function" "" { target c++20 } .-1 }
> +#endif
> +    default:
> +      break;
> +    }
> +}
> --- gcc/testsuite/g++.dg/cpp26/trivial-infinite-loop2.C.jj	2024-04-09 11:27:24.543821806 +0200
> +++ gcc/testsuite/g++.dg/cpp26/trivial-infinite-loop2.C	2024-04-09 11:27:24.542821820 +0200
> @@ -0,0 +1,147 @@
> +// P2809R3 - Trivial infinite loops are not Undefined Behavior
> +// { dg-do compile { target c++11 } }
> +// { dg-additional-options "-fdump-tree-gimple -fno-inline -Wtautological-compare -O2" }
> +// { dg-final { scan-tree-dump-not ".ANNOTATE \\\(\[^\n\r]*, 5, 0\\\)" "gimple" } }
> +
> +volatile int v;
> +
> +constexpr bool
> +foo ()
> +{
> +  return false;
> +}
> +
> +struct S
> +{
> +  constexpr S () : s (false) {}
> +  constexpr operator bool () const { return s; }
> +  bool s;
> +};
> +
> +#if __cplusplus >= 202002L
> +namespace std {
> +  constexpr inline bool
> +  is_constant_evaluated () noexcept
> +  {
> +#if __cpp_if_consteval >= 202106L
> +    if consteval { return true; } else { return false; }
> +#else
> +    return __builtin_is_constant_evaluated ();
> +#endif
> +  }
> +}
> +
> +constexpr bool
> +baz ()
> +{
> +  return !std::is_constant_evaluated ();
> +}
> +#endif
> +
> +void
> +bar (int x)
> +{
> +  switch (x)
> +    {
> +    case 0:
> +      while (foo ()) ;
> +      break;
> +    case 1:
> +      while (foo ()) {}
> +      break;
> +    case 2:
> +      do ; while (foo ());
> +      break;
> +    case 3:
> +      do {} while (foo ());
> +      break;
> +    case 4:
> +      for (v = 42; foo (); ) ;
> +      break;
> +    case 5:
> +      for (v = 42; foo (); ) {}
> +      break;
> +    case 6:
> +      for (int w = 42; foo (); ) ;
> +      break;
> +    case 7:
> +      for (int w = 42; foo (); ) {}
> +      break;
> +    case 10:
> +      while (S {}) ;
> +      break;
> +    case 11:
> +      while (S {}) {}
> +      break;
> +    case 12:
> +      do ; while (S {});
> +      break;
> +    case 13:
> +      do {} while (S {});
> +      break;
> +    case 14:
> +      for (v = 42; S {}; ) ;
> +      break;
> +    case 15:
> +      for (v = 42; S {}; ) {}
> +      break;
> +    case 16:
> +      for (int w = 42; S {}; ) ;
> +      break;
> +    case 17:
> +      for (int w = 42; S {}; ) {}
> +      break;
> +#if __cplusplus >= 202002L
> +    case 20:
> +      while (baz ()) ;
> +      break;
> +    case 21:
> +      while (baz ()) {}
> +      break;
> +    case 22:
> +      do ; while (baz ());
> +      break;
> +    case 23:
> +      do {} while (baz ());
> +      break;
> +    case 24:
> +      for (v = 42; baz (); ) ;
> +      break;
> +    case 25:
> +      for (v = 42; baz (); ) {}
> +      break;
> +    case 26:
> +      for (int w = 42; baz (); ) ;
> +      break;
> +    case 27:
> +      for (int w = 42; baz (); ) {}
> +      break;
> +    case 30:
> +      while (!std::is_constant_evaluated ()) ;			// { dg-warning "'std::is_constant_evaluated' always evaluates to false in a non-'constexpr' function" "" { target c++20 } }
> +      break;
> +    case 31:
> +      while (!std::is_constant_evaluated ()) {}			// { dg-warning "'std::is_constant_evaluated' always evaluates to false in a non-'constexpr' function" "" { target c++20 } }
> +      break;
> +    case 32:
> +      do ; while (!std::is_constant_evaluated ());		// { dg-warning "'std::is_constant_evaluated' always evaluates to false in a non-'constexpr' function" "" { target c++20 } }
> +      break;
> +    case 33:
> +      do {} while (!std::is_constant_evaluated ());		// { dg-warning "'std::is_constant_evaluated' always evaluates to false in a non-'constexpr' function" "" { target c++20 } }
> +      break;
> +    case 34:
> +      for (v = 42; !std::is_constant_evaluated (); ) ;		// { dg-warning "'std::is_constant_evaluated' always evaluates to false in a non-'constexpr' function" "" { target c++20 } }
> +      break;
> +    case 35:
> +      for (v = 42; !std::is_constant_evaluated (); ) {}		// { dg-warning "'std::is_constant_evaluated' always evaluates to false in a non-'constexpr' function" "" { target c++20 } }
> +      break;
> +    case 36:
> +      for (int w = 42; !std::is_constant_evaluated (); ) ;	// { dg-warning "'std::is_constant_evaluated' always evaluates to false in a non-'constexpr' function" "" { target c++20 } }
> +      break;
> +    case 37:
> +      for (int w = 42; !std::is_constant_evaluated (); ) {}	// { dg-warning "'std::is_constant_evaluated' always evaluates to false in a non-'constexpr' function" "" { target c++20 } }
> +      break;
> +#endif
> +    default:
> +      break;
> +    }
> +}
> --- gcc/testsuite/g++.dg/cpp26/trivial-infinite-loop3.C.jj	2024-04-09 11:27:24.543821806 +0200
> +++ gcc/testsuite/g++.dg/cpp26/trivial-infinite-loop3.C	2024-04-09 11:27:24.543821806 +0200
> @@ -0,0 +1,148 @@
> +// P2809R3 - Trivial infinite loops are not Undefined Behavior
> +// { dg-do compile { target c++11 } }
> +// { dg-additional-options "-fdump-tree-gimple -fno-inline -Wtautological-compare" }
> +// { dg-final { scan-tree-dump-not ".ANNOTATE \\\(\[^\n\r]*, 5, 0\\\)" "gimple" } }
> +
> +volatile int v;
> +int y;
> +
> +constexpr bool
> +foo ()
> +{
> +  return true;
> +}
> +
> +struct S
> +{
> +  constexpr S () : s (true) {}
> +  constexpr operator bool () const { return s; }
> +  bool s;
> +};
> +
> +#if __cplusplus >= 202002L
> +namespace std {
> +  constexpr inline bool
> +  is_constant_evaluated () noexcept
> +  {
> +#if __cpp_if_consteval >= 202106L
> +    if consteval { return true; } else { return false; }
> +#else
> +    return __builtin_is_constant_evaluated ();
> +#endif
> +  }
> +}
> +
> +constexpr bool
> +baz ()
> +{
> +  return std::is_constant_evaluated ();
> +}
> +#endif
> +
> +void
> +bar (int x)
> +{
> +  switch (x)
> +    {
> +    case 0:
> +      while (foo ()) ++y;
> +      break;
> +    case 1:
> +      while (foo ()) { ++y; }
> +      break;
> +    case 2:
> +      do ++y; while (foo ());
> +      break;
> +    case 3:
> +      do { ++y; } while (foo ());
> +      break;
> +    case 4:
> +      for (v = 42; foo (); ) ++y;
> +      break;
> +    case 5:
> +      for (v = 42; foo (); ) { ++y; }
> +      break;
> +    case 6:
> +      for (int w = 42; foo (); ) ++y;
> +      break;
> +    case 7:
> +      for (int w = 42; foo (); ) { ++y; }
> +      break;
> +    case 10:
> +      while (S {}) ++y;
> +      break;
> +    case 11:
> +      while (S {}) { ++y; }
> +      break;
> +    case 12:
> +      do ++y; while (S {});
> +      break;
> +    case 13:
> +      do { ++y; } while (S {});
> +      break;
> +    case 14:
> +      for (v = 42; S {}; ) ++y;
> +      break;
> +    case 15:
> +      for (v = 42; S {}; ) { ++y; }
> +      break;
> +    case 16:
> +      for (int w = 42; S {}; ) ++y;
> +      break;
> +    case 17:
> +      for (int w = 42; S {}; ) { ++y; }
> +      break;
> +#if __cplusplus >= 202002L
> +    case 20:
> +      while (baz ()) ++y;
> +      break;
> +    case 21:
> +      while (baz ()) { ++y; }
> +      break;
> +    case 22:
> +      do ++y; while (baz ());
> +      break;
> +    case 23:
> +      do { ++y; } while (baz ());
> +      break;
> +    case 24:
> +      for (v = 42; baz (); ) ++y;
> +      break;
> +    case 25:
> +      for (v = 42; baz (); ) { ++y; }
> +      break;
> +    case 26:
> +      for (int w = 42; baz (); ) ++y;
> +      break;
> +    case 27:
> +      for (int w = 42; baz (); ) { ++y; }
> +      break;
> +    case 30:
> +      while (std::is_constant_evaluated ()) ++y;			// { dg-warning "'std::is_constant_evaluated' always evaluates to false in a non-'constexpr' function" "" { target c++20 } }
> +      break;
> +    case 31:
> +      while (std::is_constant_evaluated ()) { ++y; }			// { dg-warning "'std::is_constant_evaluated' always evaluates to false in a non-'constexpr' function" "" { target c++20 } }
> +      break;
> +    case 32:
> +      do ++y; while (std::is_constant_evaluated ());			// { dg-warning "'std::is_constant_evaluated' always evaluates to false in a non-'constexpr' function" "" { target c++20 } }
> +      break;
> +    case 33:
> +      do { ++y; } while (std::is_constant_evaluated ());		// { dg-warning "'std::is_constant_evaluated' always evaluates to false in a non-'constexpr' function" "" { target c++20 } }
> +      break;
> +    case 34:
> +      for (v = 42; std::is_constant_evaluated (); ) ++y;		// { dg-warning "'std::is_constant_evaluated' always evaluates to false in a non-'constexpr' function" "" { target c++20 } }
> +      break;
> +    case 35:
> +      for (v = 42; std::is_constant_evaluated (); ) { ++y; }		// { dg-warning "'std::is_constant_evaluated' always evaluates to false in a non-'constexpr' function" "" { target c++20 } }
> +      break;
> +    case 36:
> +      for (int w = 42; std::is_constant_evaluated (); ) ++y;		// { dg-warning "'std::is_constant_evaluated' always evaluates to false in a non-'constexpr' function" "" { target c++20 } }
> +      break;
> +    case 37:
> +      for (int w = 42; std::is_constant_evaluated (); ) { ++y; }	// { dg-warning "'std::is_constant_evaluated' always evaluates to false in a non-'constexpr' function" "" { target c++20 } }
> +      break;
> +#endif
> +    default:
> +      break;
> +    }
> +}
> 
> 
> 	Jakub
>
  

Patch

--- gcc/tree-core.h.jj	2024-04-05 09:19:48.319044584 +0200
+++ gcc/tree-core.h	2024-04-09 11:27:24.535821914 +0200
@@ -983,6 +983,7 @@  enum annot_expr_kind {
   annot_expr_no_vector_kind,
   annot_expr_vector_kind,
   annot_expr_parallel_kind,
+  annot_expr_maybe_infinite_kind,
   annot_expr_kind_last
 };
 
--- gcc/gimplify.cc.jj	2024-04-05 09:19:48.216046013 +0200
+++ gcc/gimplify.cc	2024-04-09 11:27:24.538821874 +0200
@@ -4584,6 +4584,7 @@  gimple_boolify (tree expr)
 	case annot_expr_no_vector_kind:
 	case annot_expr_vector_kind:
 	case annot_expr_parallel_kind:
+	case annot_expr_maybe_infinite_kind:
 	  TREE_OPERAND (expr, 0) = gimple_boolify (TREE_OPERAND (expr, 0));
 	  if (TREE_CODE (type) != BOOLEAN_TYPE)
 	    TREE_TYPE (expr) = boolean_type_node;
--- gcc/tree-cfg.cc.jj	2024-04-05 09:19:12.915535710 +0200
+++ gcc/tree-cfg.cc	2024-04-09 11:27:24.539821860 +0200
@@ -297,6 +297,9 @@  replace_loop_annotate_in_block (basic_bl
 	  loop->can_be_parallel = true;
 	  loop->safelen = INT_MAX;
 	  break;
+	case annot_expr_maybe_infinite_kind:
+	  loop->finite_p = false;
+	  break;
 	default:
 	  gcc_unreachable ();
 	}
@@ -320,12 +323,12 @@  replace_loop_annotate (void)
 
   for (auto loop : loops_list (cfun, 0))
     {
+      /* Push the global flag_finite_loops state down to individual loops.  */
+      loop->finite_p = flag_finite_loops;
+
       /* Check all exit source blocks for annotations.  */
       for (auto e : get_loop_exit_edges (loop))
 	replace_loop_annotate_in_block (e->src, loop);
-
-      /* Push the global flag_finite_loops state down to individual loops.  */
-      loop->finite_p = flag_finite_loops;
     }
 
   /* Remove IFN_ANNOTATE.  Safeguard for the case loop->latch == NULL.  */
@@ -347,6 +350,7 @@  replace_loop_annotate (void)
 	    case annot_expr_no_vector_kind:
 	    case annot_expr_vector_kind:
 	    case annot_expr_parallel_kind:
+	    case annot_expr_maybe_infinite_kind:
 	      break;
 	    default:
 	      gcc_unreachable ();
--- gcc/tree-pretty-print.cc.jj	2024-04-05 09:19:13.035534046 +0200
+++ gcc/tree-pretty-print.cc	2024-04-09 11:27:24.540821847 +0200
@@ -3479,6 +3479,9 @@  dump_generic_node (pretty_printer *pp, t
 	case annot_expr_parallel_kind:
 	  pp_string (pp, ", parallel");
 	  break;
+	case annot_expr_maybe_infinite_kind:
+	  pp_string (pp, ", maybe-infinite");
+	  break;
 	default:
 	  gcc_unreachable ();
 	}
--- gcc/cp/semantics.cc.jj	2024-04-09 09:31:26.343560220 +0200
+++ gcc/cp/semantics.cc	2024-04-09 11:30:28.608332303 +0200
@@ -1090,7 +1090,8 @@  find_std_constant_evaluated_r (tree *tp,
    (e.g., in a non-constexpr non-consteval function) so give the user a clue.  */
 
 static void
-maybe_warn_for_constant_evaluated (tree cond, bool constexpr_if)
+maybe_warn_for_constant_evaluated (tree cond, bool constexpr_if,
+				   bool trivial_infinite)
 {
   if (!warn_tautological_compare)
     return;
@@ -1108,6 +1109,18 @@  maybe_warn_for_constant_evaluated (tree
 	warning_at (EXPR_LOCATION (cond), OPT_Wtautological_compare,
 		    "%<std::is_constant_evaluated%> always evaluates to "
 		    "true in %<if constexpr%>");
+      else if (trivial_infinite)
+	{
+	  auto_diagnostic_group d;
+	  if (warning_at (EXPR_LOCATION (cond), OPT_Wtautological_compare,
+			  "%<std::is_constant_evaluated%> evaluates to "
+			  "true when checking if trivially empty iteration "
+			  "statement is trivial infinite loop")
+	      && !maybe_constexpr_fn (current_function_decl))
+	    inform (EXPR_LOCATION (cond),
+		    "and evaluates to false when actually evaluating "
+		    "the condition in non-%<constexpr%> function");
+	}
       else if (!maybe_constexpr_fn (current_function_decl))
 	warning_at (EXPR_LOCATION (cond), OPT_Wtautological_compare,
 		    "%<std::is_constant_evaluated%> always evaluates to "
@@ -1126,7 +1139,8 @@  tree
 finish_if_stmt_cond (tree orig_cond, tree if_stmt)
 {
   tree cond = maybe_convert_cond (orig_cond);
-  maybe_warn_for_constant_evaluated (cond, IF_STMT_CONSTEXPR_P (if_stmt));
+  maybe_warn_for_constant_evaluated (cond, IF_STMT_CONSTEXPR_P (if_stmt),
+				     /*trivial_infinite=*/false);
   if (IF_STMT_CONSTEXPR_P (if_stmt)
       && !type_dependent_expression_p (cond)
       && require_constant_expression (cond)
@@ -1205,6 +1219,48 @@  finish_if_stmt (tree if_stmt)
   add_stmt (do_poplevel (scope));
 }
 
+/* Determine if iteration statement with *CONDP condition and
+   loop BODY is trivially empty iteration statement or even
+   trivial infinite loop.  In the latter case for -ffinite-loops
+   add ANNOTATE_EXPR to mark the loop as maybe validly infinite.
+   Also, emit -Wtautological-compare warning for std::is_constant_evaluated ()
+   calls in the condition when needed.  */
+
+static void
+finish_loop_cond (tree *condp, tree body)
+{
+  if (TREE_CODE (*condp) == INTEGER_CST)
+    return;
+  bool trivially_empty = expr_first (body) == NULL_TREE;
+  bool trivial_infinite = false;
+  if (trivially_empty)
+    {
+      tree c = fold_non_dependent_expr (*condp, tf_none,
+					/*manifestly_const_eval=*/true);
+      trivial_infinite = c && integer_nonzerop (c);
+    }
+  if (warn_tautological_compare)
+    {
+      tree cond = *condp;
+      while (TREE_CODE (cond) == ANNOTATE_EXPR)
+	cond = TREE_OPERAND (cond, 0);
+      if (trivial_infinite
+	  && !DECL_IMMEDIATE_FUNCTION_P (current_function_decl))
+	maybe_warn_for_constant_evaluated (cond, /*constexpr_if=*/false,
+					   /*trivial_infinite=*/true);
+      else if (!trivially_empty
+	       || !processing_template_decl
+	       || DECL_IMMEDIATE_FUNCTION_P (current_function_decl))
+	maybe_warn_for_constant_evaluated (cond, /*constexpr_if=*/false,
+					   /*trivial_infinite=*/false);
+    }
+  if (trivial_infinite && flag_finite_loops && !processing_template_decl)
+    *condp = build3 (ANNOTATE_EXPR, TREE_TYPE (*condp), *condp,
+		     build_int_cst (integer_type_node,
+				    annot_expr_maybe_infinite_kind),
+		     integer_zero_node);
+}
+
 /* Begin a while-statement.  Returns a newly created WHILE_STMT if
    appropriate.  */
 
@@ -1260,6 +1316,7 @@  finish_while_stmt (tree while_stmt)
 {
   end_maybe_infinite_loop (boolean_true_node);
   WHILE_BODY (while_stmt) = do_poplevel (WHILE_BODY (while_stmt));
+  finish_loop_cond (&WHILE_COND (while_stmt), WHILE_BODY (while_stmt));
 }
 
 /* Begin a do-statement.  Returns a newly created DO_STMT if
@@ -1317,6 +1374,12 @@  finish_do_stmt (tree cond, tree do_stmt,
 		   build_int_cst (integer_type_node, annot_expr_no_vector_kind),
 		   integer_zero_node);
   DO_COND (do_stmt) = cond;
+  tree do_body = DO_BODY (do_stmt);
+  if (CONVERT_EXPR_P (do_body)
+      && integer_zerop (TREE_OPERAND (do_body, 0))
+      && VOID_TYPE_P (TREE_TYPE (do_body)))
+    do_body = NULL_TREE;
+  finish_loop_cond (&DO_COND (do_stmt), do_body);
 }
 
 /* Finish a return-statement.  The EXPRESSION returned, if any, is as
@@ -1487,7 +1550,13 @@  finish_for_stmt (tree for_stmt)
   if (TREE_CODE (for_stmt) == RANGE_FOR_STMT)
     RANGE_FOR_BODY (for_stmt) = do_poplevel (RANGE_FOR_BODY (for_stmt));
   else
-    FOR_BODY (for_stmt) = do_poplevel (FOR_BODY (for_stmt));
+    {
+      FOR_BODY (for_stmt) = do_poplevel (FOR_BODY (for_stmt));
+      if (FOR_COND (for_stmt))
+	finish_loop_cond (&FOR_COND (for_stmt),
+			  FOR_EXPR (for_stmt) ? integer_one_node
+					      : FOR_BODY (for_stmt));
+    }
 
   /* Pop the scope for the body of the loop.  */
   tree *scope_ptr = (TREE_CODE (for_stmt) == RANGE_FOR_STMT
--- gcc/testsuite/g++.dg/cpp26/trivial-infinite-loop1.C.jj	2024-04-09 11:27:24.542821820 +0200
+++ gcc/testsuite/g++.dg/cpp26/trivial-infinite-loop1.C	2024-04-09 11:27:24.542821820 +0200
@@ -0,0 +1,148 @@ 
+// P2809R3 - Trivial infinite loops are not Undefined Behavior
+// { dg-do compile { target c++11 } }
+// { dg-additional-options "-fdump-tree-gimple -fno-inline -Wtautological-compare -O2" }
+// { dg-final { scan-tree-dump-times ".ANNOTATE \\\(\[^\n\r]*, 5, 0\\\)" 32 "gimple" { target c++20 } } }
+// { dg-final { scan-tree-dump-times ".ANNOTATE \\\(\[^\n\r]*, 5, 0\\\)" 16 "gimple" { target c++17_down } } }
+
+volatile int v;
+
+constexpr bool
+foo ()
+{
+  return true;
+}
+
+struct S
+{
+  constexpr S () : s (true) {}
+  constexpr operator bool () const { return s; }
+  bool s;
+};
+
+#if __cplusplus >= 202002L
+namespace std {
+  constexpr inline bool
+  is_constant_evaluated () noexcept
+  {
+#if __cpp_if_consteval >= 202106L
+    if consteval { return true; } else { return false; }
+#else
+    return __builtin_is_constant_evaluated ();
+#endif
+  }
+}
+
+constexpr bool
+baz ()
+{
+  return std::is_constant_evaluated ();
+}
+#endif
+
+void
+bar (int x)
+{
+  switch (x)
+    {
+    case 0:
+      while (foo ()) ;
+      break;
+    case 1:
+      while (foo ()) {}
+      break;
+    case 2:
+      do ; while (foo ());
+      break;
+    case 3:
+      do {} while (foo ());
+      break;
+    case 4:
+      for (v = 42; foo (); ) ;
+      break;
+    case 5:
+      for (v = 42; foo (); ) {}
+      break;
+    case 6:
+      for (int w = 42; foo (); ) ;
+      break;
+    case 7:
+      for (int w = 42; foo (); ) {}
+      break;
+    case 10:
+      while (S {}) ;
+      break;
+    case 11:
+      while (S {}) {}
+      break;
+    case 12:
+      do ; while (S {});
+      break;
+    case 13:
+      do {} while (S {});
+      break;
+    case 14:
+      for (v = 42; S {}; ) ;
+      break;
+    case 15:
+      for (v = 42; S {}; ) {}
+      break;
+    case 16:
+      for (int w = 42; S {}; ) ;
+      break;
+    case 17:
+      for (int w = 42; S {}; ) {}
+      break;
+#if __cplusplus >= 202002L
+    case 20:
+      while (baz ()) ;
+      break;
+    case 21:
+      while (baz ()) {}
+      break;
+    case 22:
+      do ; while (baz ());
+      break;
+    case 23:
+      do {} while (baz ());
+      break;
+    case 24:
+      for (v = 42; baz (); ) ;
+      break;
+    case 25:
+      for (v = 42; baz (); ) {}
+      break;
+    case 26:
+      for (int w = 42; baz (); ) ;
+      break;
+    case 27:
+      for (int w = 42; baz (); ) {}
+      break;
+    case 30:
+      while (std::is_constant_evaluated ()) ;			// { dg-warning "'std::is_constant_evaluated' evaluates to true when checking if trivially empty iteration statement is trivial infinite loop" "" { target c++20 } }
+      break;							// { dg-message "and evaluates to false when actually evaluating the condition in non-'constexpr' function" "" { target c++20 } .-1 }
+    case 31:
+      while (std::is_constant_evaluated ()) {}			// { dg-warning "'std::is_constant_evaluated' evaluates to true when checking if trivially empty iteration statement is trivial infinite loop" "" { target c++20 } }
+      break;							// { dg-message "and evaluates to false when actually evaluating the condition in non-'constexpr' function" "" { target c++20 } .-1 }
+    case 32:
+      do ; while (std::is_constant_evaluated ());		// { dg-warning "'std::is_constant_evaluated' evaluates to true when checking if trivially empty iteration statement is trivial infinite loop" "" { target c++20 } }
+      break;							// { dg-message "and evaluates to false when actually evaluating the condition in non-'constexpr' function" "" { target c++20 } .-1 }
+    case 33:
+      do {} while (std::is_constant_evaluated ());		// { dg-warning "'std::is_constant_evaluated' evaluates to true when checking if trivially empty iteration statement is trivial infinite loop" "" { target c++20 } }
+      break;							// { dg-message "and evaluates to false when actually evaluating the condition in non-'constexpr' function" "" { target c++20 } .-1 }
+    case 34:
+      for (v = 42; std::is_constant_evaluated (); ) ;		// { dg-warning "'std::is_constant_evaluated' evaluates to true when checking if trivially empty iteration statement is trivial infinite loop" "" { target c++20 } }
+      break;							// { dg-message "and evaluates to false when actually evaluating the condition in non-'constexpr' function" "" { target c++20 } .-1 }
+    case 35:
+      for (v = 42; std::is_constant_evaluated (); ) {}		// { dg-warning "'std::is_constant_evaluated' evaluates to true when checking if trivially empty iteration statement is trivial infinite loop" "" { target c++20 } }
+      break;							// { dg-message "and evaluates to false when actually evaluating the condition in non-'constexpr' function" "" { target c++20 } .-1 }
+    case 36:
+      for (int w = 42; std::is_constant_evaluated (); ) ;	// { dg-warning "'std::is_constant_evaluated' evaluates to true when checking if trivially empty iteration statement is trivial infinite loop" "" { target c++20 } }
+      break;							// { dg-message "and evaluates to false when actually evaluating the condition in non-'constexpr' function" "" { target c++20 } .-1 }
+    case 37:
+      for (int w = 42; std::is_constant_evaluated (); ) {}	// { dg-warning "'std::is_constant_evaluated' evaluates to true when checking if trivially empty iteration statement is trivial infinite loop" "" { target c++20 } }
+      break;							// { dg-message "and evaluates to false when actually evaluating the condition in non-'constexpr' function" "" { target c++20 } .-1 }
+#endif
+    default:
+      break;
+    }
+}
--- gcc/testsuite/g++.dg/cpp26/trivial-infinite-loop2.C.jj	2024-04-09 11:27:24.543821806 +0200
+++ gcc/testsuite/g++.dg/cpp26/trivial-infinite-loop2.C	2024-04-09 11:27:24.542821820 +0200
@@ -0,0 +1,147 @@ 
+// P2809R3 - Trivial infinite loops are not Undefined Behavior
+// { dg-do compile { target c++11 } }
+// { dg-additional-options "-fdump-tree-gimple -fno-inline -Wtautological-compare -O2" }
+// { dg-final { scan-tree-dump-not ".ANNOTATE \\\(\[^\n\r]*, 5, 0\\\)" "gimple" } }
+
+volatile int v;
+
+constexpr bool
+foo ()
+{
+  return false;
+}
+
+struct S
+{
+  constexpr S () : s (false) {}
+  constexpr operator bool () const { return s; }
+  bool s;
+};
+
+#if __cplusplus >= 202002L
+namespace std {
+  constexpr inline bool
+  is_constant_evaluated () noexcept
+  {
+#if __cpp_if_consteval >= 202106L
+    if consteval { return true; } else { return false; }
+#else
+    return __builtin_is_constant_evaluated ();
+#endif
+  }
+}
+
+constexpr bool
+baz ()
+{
+  return !std::is_constant_evaluated ();
+}
+#endif
+
+void
+bar (int x)
+{
+  switch (x)
+    {
+    case 0:
+      while (foo ()) ;
+      break;
+    case 1:
+      while (foo ()) {}
+      break;
+    case 2:
+      do ; while (foo ());
+      break;
+    case 3:
+      do {} while (foo ());
+      break;
+    case 4:
+      for (v = 42; foo (); ) ;
+      break;
+    case 5:
+      for (v = 42; foo (); ) {}
+      break;
+    case 6:
+      for (int w = 42; foo (); ) ;
+      break;
+    case 7:
+      for (int w = 42; foo (); ) {}
+      break;
+    case 10:
+      while (S {}) ;
+      break;
+    case 11:
+      while (S {}) {}
+      break;
+    case 12:
+      do ; while (S {});
+      break;
+    case 13:
+      do {} while (S {});
+      break;
+    case 14:
+      for (v = 42; S {}; ) ;
+      break;
+    case 15:
+      for (v = 42; S {}; ) {}
+      break;
+    case 16:
+      for (int w = 42; S {}; ) ;
+      break;
+    case 17:
+      for (int w = 42; S {}; ) {}
+      break;
+#if __cplusplus >= 202002L
+    case 20:
+      while (baz ()) ;
+      break;
+    case 21:
+      while (baz ()) {}
+      break;
+    case 22:
+      do ; while (baz ());
+      break;
+    case 23:
+      do {} while (baz ());
+      break;
+    case 24:
+      for (v = 42; baz (); ) ;
+      break;
+    case 25:
+      for (v = 42; baz (); ) {}
+      break;
+    case 26:
+      for (int w = 42; baz (); ) ;
+      break;
+    case 27:
+      for (int w = 42; baz (); ) {}
+      break;
+    case 30:
+      while (!std::is_constant_evaluated ()) ;			// { dg-warning "'std::is_constant_evaluated' always evaluates to false in a non-'constexpr' function" "" { target c++20 } }
+      break;
+    case 31:
+      while (!std::is_constant_evaluated ()) {}			// { dg-warning "'std::is_constant_evaluated' always evaluates to false in a non-'constexpr' function" "" { target c++20 } }
+      break;
+    case 32:
+      do ; while (!std::is_constant_evaluated ());		// { dg-warning "'std::is_constant_evaluated' always evaluates to false in a non-'constexpr' function" "" { target c++20 } }
+      break;
+    case 33:
+      do {} while (!std::is_constant_evaluated ());		// { dg-warning "'std::is_constant_evaluated' always evaluates to false in a non-'constexpr' function" "" { target c++20 } }
+      break;
+    case 34:
+      for (v = 42; !std::is_constant_evaluated (); ) ;		// { dg-warning "'std::is_constant_evaluated' always evaluates to false in a non-'constexpr' function" "" { target c++20 } }
+      break;
+    case 35:
+      for (v = 42; !std::is_constant_evaluated (); ) {}		// { dg-warning "'std::is_constant_evaluated' always evaluates to false in a non-'constexpr' function" "" { target c++20 } }
+      break;
+    case 36:
+      for (int w = 42; !std::is_constant_evaluated (); ) ;	// { dg-warning "'std::is_constant_evaluated' always evaluates to false in a non-'constexpr' function" "" { target c++20 } }
+      break;
+    case 37:
+      for (int w = 42; !std::is_constant_evaluated (); ) {}	// { dg-warning "'std::is_constant_evaluated' always evaluates to false in a non-'constexpr' function" "" { target c++20 } }
+      break;
+#endif
+    default:
+      break;
+    }
+}
--- gcc/testsuite/g++.dg/cpp26/trivial-infinite-loop3.C.jj	2024-04-09 11:27:24.543821806 +0200
+++ gcc/testsuite/g++.dg/cpp26/trivial-infinite-loop3.C	2024-04-09 11:27:24.543821806 +0200
@@ -0,0 +1,148 @@ 
+// P2809R3 - Trivial infinite loops are not Undefined Behavior
+// { dg-do compile { target c++11 } }
+// { dg-additional-options "-fdump-tree-gimple -fno-inline -Wtautological-compare" }
+// { dg-final { scan-tree-dump-not ".ANNOTATE \\\(\[^\n\r]*, 5, 0\\\)" "gimple" } }
+
+volatile int v;
+int y;
+
+constexpr bool
+foo ()
+{
+  return true;
+}
+
+struct S
+{
+  constexpr S () : s (true) {}
+  constexpr operator bool () const { return s; }
+  bool s;
+};
+
+#if __cplusplus >= 202002L
+namespace std {
+  constexpr inline bool
+  is_constant_evaluated () noexcept
+  {
+#if __cpp_if_consteval >= 202106L
+    if consteval { return true; } else { return false; }
+#else
+    return __builtin_is_constant_evaluated ();
+#endif
+  }
+}
+
+constexpr bool
+baz ()
+{
+  return std::is_constant_evaluated ();
+}
+#endif
+
+void
+bar (int x)
+{
+  switch (x)
+    {
+    case 0:
+      while (foo ()) ++y;
+      break;
+    case 1:
+      while (foo ()) { ++y; }
+      break;
+    case 2:
+      do ++y; while (foo ());
+      break;
+    case 3:
+      do { ++y; } while (foo ());
+      break;
+    case 4:
+      for (v = 42; foo (); ) ++y;
+      break;
+    case 5:
+      for (v = 42; foo (); ) { ++y; }
+      break;
+    case 6:
+      for (int w = 42; foo (); ) ++y;
+      break;
+    case 7:
+      for (int w = 42; foo (); ) { ++y; }
+      break;
+    case 10:
+      while (S {}) ++y;
+      break;
+    case 11:
+      while (S {}) { ++y; }
+      break;
+    case 12:
+      do ++y; while (S {});
+      break;
+    case 13:
+      do { ++y; } while (S {});
+      break;
+    case 14:
+      for (v = 42; S {}; ) ++y;
+      break;
+    case 15:
+      for (v = 42; S {}; ) { ++y; }
+      break;
+    case 16:
+      for (int w = 42; S {}; ) ++y;
+      break;
+    case 17:
+      for (int w = 42; S {}; ) { ++y; }
+      break;
+#if __cplusplus >= 202002L
+    case 20:
+      while (baz ()) ++y;
+      break;
+    case 21:
+      while (baz ()) { ++y; }
+      break;
+    case 22:
+      do ++y; while (baz ());
+      break;
+    case 23:
+      do { ++y; } while (baz ());
+      break;
+    case 24:
+      for (v = 42; baz (); ) ++y;
+      break;
+    case 25:
+      for (v = 42; baz (); ) { ++y; }
+      break;
+    case 26:
+      for (int w = 42; baz (); ) ++y;
+      break;
+    case 27:
+      for (int w = 42; baz (); ) { ++y; }
+      break;
+    case 30:
+      while (std::is_constant_evaluated ()) ++y;			// { dg-warning "'std::is_constant_evaluated' always evaluates to false in a non-'constexpr' function" "" { target c++20 } }
+      break;
+    case 31:
+      while (std::is_constant_evaluated ()) { ++y; }			// { dg-warning "'std::is_constant_evaluated' always evaluates to false in a non-'constexpr' function" "" { target c++20 } }
+      break;
+    case 32:
+      do ++y; while (std::is_constant_evaluated ());			// { dg-warning "'std::is_constant_evaluated' always evaluates to false in a non-'constexpr' function" "" { target c++20 } }
+      break;
+    case 33:
+      do { ++y; } while (std::is_constant_evaluated ());		// { dg-warning "'std::is_constant_evaluated' always evaluates to false in a non-'constexpr' function" "" { target c++20 } }
+      break;
+    case 34:
+      for (v = 42; std::is_constant_evaluated (); ) ++y;		// { dg-warning "'std::is_constant_evaluated' always evaluates to false in a non-'constexpr' function" "" { target c++20 } }
+      break;
+    case 35:
+      for (v = 42; std::is_constant_evaluated (); ) { ++y; }		// { dg-warning "'std::is_constant_evaluated' always evaluates to false in a non-'constexpr' function" "" { target c++20 } }
+      break;
+    case 36:
+      for (int w = 42; std::is_constant_evaluated (); ) ++y;		// { dg-warning "'std::is_constant_evaluated' always evaluates to false in a non-'constexpr' function" "" { target c++20 } }
+      break;
+    case 37:
+      for (int w = 42; std::is_constant_evaluated (); ) { ++y; }	// { dg-warning "'std::is_constant_evaluated' always evaluates to false in a non-'constexpr' function" "" { target c++20 } }
+      break;
+#endif
+    default:
+      break;
+    }
+}