On Wed, Apr 3, 2024 at 9:25 AM Jakub Jelinek <ja...@redhat.com> wrote: > > Hi! > > 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 and > turn their conditions into INTEGER_CSTs so that they don't really have > exits in the middle-end and so regardless of -ffinite-loops or > -fno-finite-loops they are handled as infinite loops by the middle-end. > Otherwise, if the condition would be a large expression calling various > constexpr functions, I'd be afraid we could e.g. just inline some of them > and not all of them and the middle-end could still see tests in the > condition and with -ffinite-loops optimize it by assuming that such loops > need to be finite. > > The "A trivial infinite loop is a trivially empty iteration statement for > which the converted controlling expression is a constant expression, when > interpreted as a constant-expression ([expr.const]), and evaluates to true." > wording isn't clear to me what it implies for manifest constant evaluation > of the expression, especially given the > int x = 42; > while (std::is_constant_evaluated() || --x) ; > example in the rationale. > > The patch assumes that the condition expression aren't manifestly constant > evaluated. If it would be supposed to be manifestly constant evaluated, > then I think the DR would significantly change behavior of existing programs > and have really weird effects. Before the DR has been voted in, I think > void foo (int x) > { > if (x == 0) > while (std::is_constant_evaluated()) > ; > else > while (!std::is_constant_evaluated()) > ; > } > would have well defined behavior of zero loop body iterations if x == 0 and > undefined behavior otherwise. If the condition expression is manifestly > constant evaluated if it evaluates to true, and otherwise can be > non-constant or not manifestly constant evaluated otherwise, then the > behavior would be that for x == 0 it is well defined trvial infinite loop, > while for x != 0 it would keep to be undefined behavior (infinite loop, > as !std::is_constant_evaluated() is false when manifestly constant evaluated > and if we keep the condition as is, evaluates then to true. I think it > would be fairly strange if both loops are infinite even when their condition > are negated. Similar for anything that is dependent on if consteval or > std::is_constant_evaluated() inside of it. > > So, the patch below attempts to discover trivially empty iteration > statements at cp_fold time if it is the final mce_false folding, > attempts to maybe_constant_value with mce_false evaluate the conditions > and replaces it with the returned value if constant non-zero. > > The testcases then try to check if the FE changed the calls in the > conditions into constants. > > Bootstrapped/regtested on x86_64-linux and i686-linux, ok for trunk?
I'll note that -ffinite-loops is reflected into the IL (the loop structure) at CFG build time in the following simple way: /* Push the global flag_finite_loops state down to individual loops. */ loop->finite_p = flag_finite_loops; and the "has an exit" is checked when we evalutate a loop->finite_p loop. The above flag evaluation could be made a langhook as it's suitably early, but I'm not sure whether that would help. The flag is evaluated when we evaluate the loop ANNOTATE_EXPRs, so annotating finite loops with a new annotate might be possible as well. Just in case making the control expression constant to the middle-end doesn't scale. Richard. > Or is it really supposed to be mce_true with the above described weird > behavior? If so, I think the standard at least should mention it in Annex C > (though, where when it is a DR?). > > 2024-04-02 Jakub Jelinek <ja...@redhat.com> > > PR c++/114462 > * cp-gimplify.cc (cp_fold): Implement C++26 P2809R3 - Trivial infinite > loops are not Undefined Behavior. For trivially empty WHILE_STMT, > DO_STMT or FOR_STMT iteration statements check if the condition is > constant expression which evaluates to true and in that case replace > the condition with true. > > * 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/cp/cp-gimplify.cc.jj 2024-03-23 11:17:06.958445857 +0100 > +++ gcc/cp/cp-gimplify.cc 2024-04-02 11:27:56.069170914 +0200 > @@ -3527,6 +3527,78 @@ cp_fold (tree x, fold_flags_t flags) > x = evaluate_requires_expr (x); > break; > > + case WHILE_STMT: > + /* For trivially empty iteration statements check if the condition > + is constant expression which evaluates to true and in that case > + change the condition to the constant, so that the middle-end treats > + it as an infinite loop regardless of -ffinite-loops. */ > + if ((flags & ff_mce_false) && !integer_nonzerop (WHILE_COND (x))) > + { > + tree body = WHILE_BODY (x); > + if (expr_first (body) == NULL_TREE) > + { > + r = maybe_constant_value (WHILE_COND (x), /*decl=*/NULL_TREE, > + mce_false); > + if (integer_nonzerop (r)) > + { > + x = copy_node (x); > + WHILE_COND (x) = r; > + break; > + } > + } > + } > + return org_x; > + > + case DO_STMT: > + /* For trivially empty iteration statements check if the condition > + is constant expression which evaluates to true and in that case > + change the condition to the constant, so that the middle-end treats > + it as an infinite loop regardless of -ffinite-loops. */ > + if ((flags & ff_mce_false) && !integer_nonzerop (DO_COND (x))) > + { > + tree body = DO_BODY (x); > + if (expr_first (body) == NULL_TREE > + || (CONVERT_EXPR_P (body) > + && integer_zerop (TREE_OPERAND (body, 0)) > + && VOID_TYPE_P (TREE_TYPE (body)))) > + { > + r = maybe_constant_value (DO_COND (x), /*decl=*/NULL_TREE, > + mce_false); > + if (integer_nonzerop (r)) > + { > + x = copy_node (x); > + DO_COND (x) = r; > + break; > + } > + } > + } > + return org_x; > + > + case FOR_STMT: > + /* For trivially empty iteration statements check if the condition > + is constant expression which evaluates to true and in that case > + change the condition to the constant, so that the middle-end treats > + it as an infinite loop regardless of -ffinite-loops. */ > + if ((flags & ff_mce_false) > + && FOR_COND (x) > + && FOR_EXPR (x) == NULL_TREE > + && !integer_nonzerop (FOR_COND (x))) > + { > + tree body = FOR_BODY (x); > + if (expr_first (body) == NULL_TREE) > + { > + r = maybe_constant_value (FOR_COND (x), /*decl=*/NULL_TREE, > + mce_false); > + if (integer_nonzerop (r)) > + { > + x = copy_node (x); > + FOR_COND (x) = r; > + break; > + } > + } > + } > + return org_x; > + > default: > return org_x; > } > --- gcc/testsuite/g++.dg/cpp26/trivial-infinite-loop1.C.jj 2024-04-02 > 11:50:00.943874185 +0200 > +++ gcc/testsuite/g++.dg/cpp26/trivial-infinite-loop1.C 2024-04-02 > 12:19:52.132211449 +0200 > @@ -0,0 +1,150 @@ > +// P2809R3 - Trivial infinite loops are not Undefined Behavior > +// { dg-do compile { target c++11 } } > +// { dg-additional-options "-fdump-tree-gimple -fno-inline" } > +// { dg-final { scan-tree-dump-not " = foo \\\(\\\)" "gimple" } } > +// { dg-final { scan-tree-dump-not " = S::operator bool \\\(" "gimple" } } > +// { dg-final { scan-tree-dump-not " = baz \\\(\\\)" "gimple" } } > +// { dg-final { scan-tree-dump-not " = std::is_constant_evaluated \\\(\\\)" > "gimple" } } > + > +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 ()) ; > + break; > + case 31: > + while (!std::is_constant_evaluated ()) {} > + break; > + case 32: > + do ; while (!std::is_constant_evaluated ()); > + break; > + case 33: > + do {} while (!std::is_constant_evaluated ()); > + break; > + case 34: > + for (v = 42; !std::is_constant_evaluated (); ) ; > + break; > + case 35: > + for (v = 42; !std::is_constant_evaluated (); ) {} > + break; > + case 36: > + for (int w = 42; !std::is_constant_evaluated (); ) ; > + break; > + case 37: > + for (int w = 42; !std::is_constant_evaluated (); ) {} > + break; > +#endif > + default: > + break; > + } > +} > --- gcc/testsuite/g++.dg/cpp26/trivial-infinite-loop2.C.jj 2024-04-02 > 12:06:31.494215751 +0200 > +++ gcc/testsuite/g++.dg/cpp26/trivial-infinite-loop2.C 2024-04-02 > 12:20:32.037663398 +0200 > @@ -0,0 +1,150 @@ > +// P2809R3 - Trivial infinite loops are not Undefined Behavior > +// { dg-do compile { target c++11 } } > +// { dg-additional-options "-fdump-tree-gimple -fno-inline" } > +// { dg-final { scan-tree-dump-times " = foo \\\(\\\)" 8 "gimple" } } > +// { dg-final { scan-tree-dump-times " = S::operator bool \\\(" 8 "gimple" } > } > +// { dg-final { scan-tree-dump-times " = baz \\\(\\\)" 8 "gimple" { target > c++20 } } } > +// { dg-final { scan-tree-dump-times " = std::is_constant_evaluated > \\\(\\\)" 9 "gimple" { target c++20 } } } > + > +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 ()) ; > + break; > + case 31: > + while (std::is_constant_evaluated ()) {} > + break; > + case 32: > + do ; while (std::is_constant_evaluated ()); > + break; > + case 33: > + do {} while (std::is_constant_evaluated ()); > + break; > + case 34: > + for (v = 42; std::is_constant_evaluated (); ) ; > + break; > + case 35: > + for (v = 42; std::is_constant_evaluated (); ) {} > + break; > + case 36: > + for (int w = 42; std::is_constant_evaluated (); ) ; > + break; > + case 37: > + for (int w = 42; std::is_constant_evaluated (); ) {} > + break; > +#endif > + default: > + break; > + } > +} > --- gcc/testsuite/g++.dg/cpp26/trivial-infinite-loop3.C.jj 2024-04-02 > 12:14:23.993717999 +0200 > +++ gcc/testsuite/g++.dg/cpp26/trivial-infinite-loop3.C 2024-04-02 > 12:21:10.341137353 +0200 > @@ -0,0 +1,151 @@ > +// P2809R3 - Trivial infinite loops are not Undefined Behavior > +// { dg-do compile { target c++11 } } > +// { dg-additional-options "-fdump-tree-gimple -fno-inline" } > +// { dg-final { scan-tree-dump-times " = foo \\\(\\\)" 8 "gimple" } } > +// { dg-final { scan-tree-dump-times " = S::operator bool \\\(" 8 "gimple" } > } > +// { dg-final { scan-tree-dump-times " = baz \\\(\\\)" 8 "gimple" { target > c++20 } } } > +// { dg-final { scan-tree-dump-times " = std::is_constant_evaluated > \\\(\\\)" 9 "gimple" { target c++20 } } } > + > +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; > + break; > + case 31: > + while (!std::is_constant_evaluated ()) { ++y; } > + break; > + case 32: > + do ++y; while (!std::is_constant_evaluated ()); > + break; > + case 33: > + do { ++y; } while (!std::is_constant_evaluated ()); > + break; > + case 34: > + for (v = 42; !std::is_constant_evaluated (); ) ++y; > + break; > + case 35: > + for (v = 42; !std::is_constant_evaluated (); ) { ++y; } > + break; > + case 36: > + for (int w = 42; !std::is_constant_evaluated (); ) ++y; > + break; > + case 37: > + for (int w = 42; !std::is_constant_evaluated (); ) { ++y; } > + break; > +#endif > + default: > + break; > + } > +} > > Jakub >