From patchwork Wed Apr 3 07:25:02 2024 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Jakub Jelinek X-Patchwork-Id: 1919063 Return-Path: X-Original-To: incoming@patchwork.ozlabs.org Delivered-To: patchwork-incoming@legolas.ozlabs.org Authentication-Results: legolas.ozlabs.org; dkim=pass (1024-bit key; unprotected) header.d=redhat.com header.i=@redhat.com header.a=rsa-sha256 header.s=mimecast20190719 header.b=a0H86ZUy; dkim-atps=neutral Authentication-Results: legolas.ozlabs.org; spf=pass (sender SPF authorized) smtp.mailfrom=gcc.gnu.org (client-ip=2620:52:3:1:0:246e:9693:128c; helo=server2.sourceware.org; envelope-from=gcc-patches-bounces+incoming=patchwork.ozlabs.org@gcc.gnu.org; receiver=patchwork.ozlabs.org) Received: from server2.sourceware.org (server2.sourceware.org [IPv6:2620:52:3:1:0:246e:9693:128c]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature ECDSA (secp384r1) server-digest SHA384) (No client certificate requested) by legolas.ozlabs.org (Postfix) with ESMTPS id 4V8brN5LSFz23tv for ; Wed, 3 Apr 2024 18:25:40 +1100 (AEDT) Received: from server2.sourceware.org (localhost [IPv6:::1]) by sourceware.org (Postfix) with ESMTP id CF4793846401 for ; Wed, 3 Apr 2024 07:25:38 +0000 (GMT) X-Original-To: gcc-patches@gcc.gnu.org Delivered-To: gcc-patches@gcc.gnu.org Received: from us-smtp-delivery-124.mimecast.com (us-smtp-delivery-124.mimecast.com [170.10.129.124]) by sourceware.org (Postfix) with ESMTPS id B8DC13847700 for ; Wed, 3 Apr 2024 07:25:12 +0000 (GMT) DMARC-Filter: OpenDMARC Filter v1.4.2 sourceware.org B8DC13847700 Authentication-Results: sourceware.org; dmarc=pass (p=none dis=none) header.from=redhat.com Authentication-Results: sourceware.org; spf=pass smtp.mailfrom=redhat.com ARC-Filter: OpenARC Filter v1.0.0 sourceware.org B8DC13847700 Authentication-Results: server2.sourceware.org; arc=none smtp.remote-ip=170.10.129.124 ARC-Seal: i=1; a=rsa-sha256; d=sourceware.org; s=key; t=1712129115; cv=none; b=Ttr/l3EqoCtdJYtxrrV//jArP8b8/A4mCAfInSH9Vz/dkDM6/pWWLlm317a3ZdOe/5dNJLFiVTiZAD3HQ+YnxaEmHo6Gc9TW3J/Da0iq7HFBOHqD+gES0Ddf7c484tApsm6UzcmyEkoVnl1hulNuHnbqlmfdUp/9fRYXmTiIiW0= ARC-Message-Signature: i=1; a=rsa-sha256; d=sourceware.org; s=key; t=1712129115; c=relaxed/simple; bh=1hyoHWwV2yCbKWFkK9/9wvXqfQOLm0hc0sNI9S0Chds=; h=DKIM-Signature:Date:From:To:Subject:Message-ID:MIME-Version; b=t4iMxHIHWgUHY+jEKSdYALv0+Rk/FTBDHccX66xTe9UrxeJuV3WDC3kqSQtXTSkVX0A3fgjgnssuFt/9oxSiPdbcA/6OTksS7aTnVjv6WMGazOKSJ3mL7HGeBscHGr5vmh1yPAAXfRv2HhRxAR17+iI0B8AAq9sUHAci+KrORpI= ARC-Authentication-Results: i=1; server2.sourceware.org DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=redhat.com; s=mimecast20190719; t=1712129112; h=from:from:reply-to:reply-to:subject:subject:date:date: message-id:message-id:to:to:cc:cc:mime-version:mime-version: content-type:content-type; bh=nPANgu20fCnOzIj9qliPsW2xkB5WmM+fJ+cLp/infI0=; b=a0H86ZUyPOvCwczOy9sUmKuakTk2EYUo2YrtcnoWWXNQhO6dEtTt3d+dx5KftJIcCKnyia N678q0697uDiH/uu1bJRIGMLMySCM7WDhmMqrGjM+5LFl2l2ER1jotUlRHyfUmwNZBQC7W HEEvg1e16B3IhlXb7peZb1rOkkfbntg= Received: from mimecast-mx02.redhat.com (mx-ext.redhat.com [66.187.233.73]) by relay.mimecast.com with ESMTP with STARTTLS (version=TLSv1.3, cipher=TLS_AES_256_GCM_SHA384) id us-mta-453-qNIlOKTvO3uMp1iSutpvfQ-1; Wed, 03 Apr 2024 03:25:08 -0400 X-MC-Unique: qNIlOKTvO3uMp1iSutpvfQ-1 Received: from smtp.corp.redhat.com (int-mx08.intmail.prod.int.rdu2.redhat.com [10.11.54.8]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature RSA-PSS (2048 bits) server-digest SHA256) (No client certificate requested) by mimecast-mx02.redhat.com (Postfix) with ESMTPS id 7F4A52820B79 for ; Wed, 3 Apr 2024 07:25:08 +0000 (UTC) Received: from tucnak.zalov.cz (unknown [10.45.224.14]) by smtp.corp.redhat.com (Postfix) with ESMTPS id 23DDDC26C01; Wed, 3 Apr 2024 07:25:08 +0000 (UTC) Received: from tucnak.zalov.cz (localhost [127.0.0.1]) by tucnak.zalov.cz (8.17.1/8.17.1) with ESMTPS id 4337P2lS1844656 (version=TLSv1.3 cipher=TLS_AES_256_GCM_SHA384 bits=256 verify=NOT); Wed, 3 Apr 2024 09:25:02 +0200 Received: (from jakub@localhost) by tucnak.zalov.cz (8.17.1/8.17.1/Submit) id 4337P2aR1844655; Wed, 3 Apr 2024 09:25:02 +0200 Date: Wed, 3 Apr 2024 09:25:02 +0200 From: Jakub Jelinek To: Jason Merrill Cc: gcc-patches@gcc.gnu.org Subject: [PATCH] c++: Implement C++26 P2809R3 - Trivial infinite loops are not Undefined Behavior Message-ID: MIME-Version: 1.0 X-Scanned-By: MIMEDefang 3.4.1 on 10.11.54.8 X-Mimecast-Spam-Score: 0 X-Mimecast-Originator: redhat.com Content-Disposition: inline X-Spam-Status: No, score=-3.6 required=5.0 tests=BAYES_00, DKIMWL_WL_HIGH, DKIM_SIGNED, DKIM_VALID, DKIM_VALID_AU, DKIM_VALID_EF, RCVD_IN_DNSWL_NONE, RCVD_IN_MSPIKE_H4, RCVD_IN_MSPIKE_WL, SPF_HELO_NONE, SPF_NONE, TXREP autolearn=ham autolearn_force=no version=3.4.6 X-Spam-Checker-Version: SpamAssassin 3.4.6 (2021-04-09) on server2.sourceware.org X-BeenThere: gcc-patches@gcc.gnu.org X-Mailman-Version: 2.1.30 Precedence: list List-Id: Gcc-patches mailing list List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Reply-To: Jakub Jelinek Errors-To: gcc-patches-bounces+incoming=patchwork.ozlabs.org@gcc.gnu.org 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? 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 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. Jakub --- 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; + } +}