Bug 114462 - [C++26] P2809R3 - Trivial infinite loops are not undefined behavior
Summary: [C++26] P2809R3 - Trivial infinite loops are not undefined behavior
Status: RESOLVED FIXED
Alias: None
Product: gcc
Classification: Unclassified
Component: c++ (show other bugs)
Version: 14.0
: P3 normal
Target Milestone: ---
Assignee: Jakub Jelinek
URL:
Keywords:
Depends on:
Blocks: c++-core-issues c++26-core
  Show dependency treegraph
 
Reported: 2024-03-25 11:16 UTC by Jakub Jelinek
Modified: 2024-04-10 08:20 UTC (History)
5 users (show)

See Also:
Host:
Target:
Build:
Known to work:
Known to fail:
Last reconfirmed: 2024-03-25 00:00:00


Attachments
gcc14-pr114462.patch (1.91 KB, patch)
2024-04-02 10:43 UTC, Jakub Jelinek
Details | Diff

Note You need to log in before you can comment on or make changes to this bug.
Description Jakub Jelinek 2024-03-25 11:16:58 UTC
See <https://wg21.link/P2809R3>.

DR.
Comment 1 Richard Biener 2024-03-25 11:40:41 UTC
Apart from marking via -ffinite-loops GCC considers loops without an exit as not required to make "forward progress".  That's more than just a constant controlling expression but should allow optimizing most relevant cases.
Comment 2 Jakub Jelinek 2024-03-25 11:52:48 UTC
I know, but I think this will need changes in the C++ FE.
I was asking recently Jason what the "when interpreted as a constant-expression"
in the paper actually means, whether such expression should be evaluated as manifestly constant evaluation or not.
If yes, it seems like an incompatible change in a DR, because say
#include <type_traits>
void
foo ()
{
  while (std::is_constant_evaluated ())
    ;
}
changing behavior from previous doing nothing to an infinite loop.
If it is not manifestly constant evaluation, still, the FE would need to (at least for the cases listed in the paper) try to silently constant evaluate the condition (with
mce_false such that std::is_constant_evaluated () is folded to false in there; but perhaps only during cp_fold_function?) and if that evaluates to non-zero replace the condition with true, such that the middle-end would then really consider it as infinite loop regardless of -ffinite-loops.
Because if one has
constexpr bool
bar ()
{
  return true;
}
void
baz ()
{
  while (bar ())
    ;
}
we want middle-end to see while (true) ; rather than while (bar ()) ; because the latter would be under -ffinite-loops decision.
Comment 3 Jakub Jelinek 2024-03-25 11:55:20 UTC
And another case to watch for is:
void
qux ()
{
  while (const bool b = bar ())
    ;
}
Comment 4 Jakub Jelinek 2024-03-25 17:55:09 UTC
Ah, if there is a declaration in the condition, then it is not a valid trivial empty iteration statement.

Anyway, I'd say cp_fold should for WHILE_STMT, DO_STMT and FOR_STMT if the body is
a STATEMENT_LIST with no statements at all or an empty statement (do we have predicate for NOP_EXPR to void_type_node of integer_zero_node?) and in case of FOR_STMT empty increment expression argument try to evaluate the condition as mce_false constant expression and if it evaluates to constant non-zero, replace the condition with boolean_true_node.
Comment 5 Jakub Jelinek 2024-03-25 17:55:35 UTC
constexpr bool foo () { return true; }
volatile int v;

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:
      do {} while (foo ());
      break;
    case 5:
      for (v = 42; foo (); ) ;
      break;
    case 6:
      for (v = 42; foo (); ) {}
      break;
    case 7:
      for (int w = 42; foo (); ) ;
      break;
    case 8:
      for (int w = 42; foo (); ) {}
      break;
    default:
      break;
    }
}
Comment 6 Andrew Pinski 2024-03-25 18:06:12 UTC
.
Comment 7 Jakub Jelinek 2024-04-02 10:43:56 UTC
Created attachment 57847 [details]
gcc14-pr114462.patch

Untested fix.
Comment 8 GCC Commits 2024-04-10 08:19:18 UTC
The master branch has been updated by Jakub Jelinek <jakub@gcc.gnu.org>:

https://gcc.gnu.org/g:4be1cc5f50578fafcdcbd09160235066d76a3f86

commit r14-9887-g4be1cc5f50578fafcdcbd09160235066d76a3f86
Author: Jakub Jelinek <jakub@redhat.com>
Date:   Wed Apr 10 10:08:12 2024 +0200

    c++: Implement C++26 P2809R3 - Trivial infinite loops are not Undefined Behavior
    
    The following patch attempts to implement P2809R3, which has been voted
    in as a DR.
    
    The middle-end has its behavior documented:
    '-ffinite-loops'
         Assume that a loop with an exit will eventually take the exit and
         not loop indefinitely.  This allows the compiler to remove loops
         that otherwise have no side-effects, not considering eventual
         endless looping as such.
    
         This option is enabled by default at '-O2' for C++ with -std=c++11
         or higher.
    
    So, the following patch attempts to detect trivial infinite loops by detecting
    trivially empty loops, if their condition is not INTEGER_CST (that case is
    handled by the middle-end right already) trying to constant evaluate with
    mce=true their condition and if it evaluates to true (and -ffinite-loops and
    not processing_template_decl) wraps the condition into an ANNOTATE_EXPR which
    tells the middle-end that the loop shouldn't be loop->finite_p despite
    -ffinite-loops).
    
    Furthermore, the patch adds -Wtautological-compare warnings for loop
    conditions containing std::is_constant_evaluated(), either if those
    always evaluate to true, or always evaluate to false, or will evaluate
    to true just when checking if it is trivial infinite loop (and if in non-constexpr
    function also say that it will evaluate to false otherwise).
    The user is doing something weird in all those cases.
    
    2024-04-10  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.
Comment 9 Jakub Jelinek 2024-04-10 08:20:05 UTC
Implemented for GCC 14.1.