Message ID | 20211026174418.3142626-1-ppalka@redhat.com |
---|---|
State | New |
Headers |
Return-Path: <gcc-patches-bounces+patchwork=sourceware.org@gcc.gnu.org> X-Original-To: patchwork@sourceware.org Delivered-To: patchwork@sourceware.org Received: from server2.sourceware.org (localhost [IPv6:::1]) by sourceware.org (Postfix) with ESMTP id 47F043858032 for <patchwork@sourceware.org>; Tue, 26 Oct 2021 17:45:50 +0000 (GMT) DKIM-Filter: OpenDKIM Filter v2.11.0 sourceware.org 47F043858032 DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gcc.gnu.org; s=default; t=1635270350; bh=x4RtlMwD4vnw6QNDRRGasjkqVfeiPhy0FncXRmw2mwA=; h=To:Subject:Date:List-Id:List-Unsubscribe:List-Archive:List-Post: List-Help:List-Subscribe:From:Reply-To:From; b=kX0M3hdhqskqhN7qvNRnsRSVnO2BFZK0BvIt+sH81VgsuvGmgvEov380HGIXItg0e HN+zMLPld4/M0FoaI+Rlm4KqfzgJy+HqV6VvJX3kEPwvCLjGF0GZQng5266SEt9n8p 42IfvRXvCAajhFXyiHIoHticO2iQ7D2Ym/CMB56M= 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.133.124]) by sourceware.org (Postfix) with ESMTPS id A17CC385802E for <gcc-patches@gcc.gnu.org>; Tue, 26 Oct 2021 17:44:22 +0000 (GMT) DMARC-Filter: OpenDMARC Filter v1.4.1 sourceware.org A17CC385802E Received: from mail-qt1-f199.google.com (mail-qt1-f199.google.com [209.85.160.199]) (Using TLS) by relay.mimecast.com with ESMTP id us-mta-512-FybhggnaMpOuSEh-iZvUHw-1; Tue, 26 Oct 2021 13:44:21 -0400 X-MC-Unique: FybhggnaMpOuSEh-iZvUHw-1 Received: by mail-qt1-f199.google.com with SMTP id p21-20020a05622a00d500b002a9f5e444a2so1448718qtw.7 for <gcc-patches@gcc.gnu.org>; Tue, 26 Oct 2021 10:44:21 -0700 (PDT) X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20210112; h=x-gm-message-state:from:to:cc:subject:date:message-id:mime-version :content-transfer-encoding; bh=x4RtlMwD4vnw6QNDRRGasjkqVfeiPhy0FncXRmw2mwA=; b=c4/ZGEUz8CO+A0wtJnXKrJzrB+iGik+bapjTHxLzYkLqa23Q6xgqeJu+YlyfskGEEn acMTnGpTuWMffCaz058zP0AqK7ra+H1dipsWCwZ3tezRmk6MCQ/bGdz33+Rj9ETPidnZ +91FgzvwthhEOZaeaCqmbLuTnpplDYVTfe9ZAe/ACwlZ/AqcOtc1AJHGnFJAi6j+mKHO +rwOBHYNDZPEHfWr9uztyO+NSwIS6jbc6q0fldrt4/ao4lAR3z6Gurkw4ycFUEOVyf62 FTdS4mpihXgfvyxr0sM3Zov9dOeVNg51yuczNwXS6qf0soQXbRIF5w3mzYKkH8qMI8AN m1Uw== X-Gm-Message-State: AOAM532t5g5JHLLW/YjuqgjbNW3z/08+ZKfYWXQinkMJtuvvzNhlf9rV dRkWUku9qtLntqMYG7pkGZP0JR6vHlOUDE+9D/DMU40I8GFq0X8canM3omYba9RJk0vrIP0YFhI dX9rq1kWq7rZ+iN7KxGYeVcYmWsHevdp4XouL2lu+baSVAepdWEgQstQASFTp58//oo8= X-Received: by 2002:ac8:7f8c:: with SMTP id z12mr26685562qtj.292.1635270260394; Tue, 26 Oct 2021 10:44:20 -0700 (PDT) X-Google-Smtp-Source: ABdhPJwhwn12Aoqj/aL6Qc1NAuTbYQvC72iKrdm9F6FJJ6MWZF+oMX9YTTtvJ9c7oTiHqsIhWSM6nA== X-Received: by 2002:ac8:7f8c:: with SMTP id z12mr26685540qtj.292.1635270260153; Tue, 26 Oct 2021 10:44:20 -0700 (PDT) Received: from localhost.localdomain (ool-457d493a.dyn.optonline.net. [69.125.73.58]) by smtp.gmail.com with ESMTPSA id q13sm616131qtx.80.2021.10.26.10.44.19 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Tue, 26 Oct 2021 10:44:19 -0700 (PDT) To: gcc-patches@gcc.gnu.org Subject: [PATCH] c++: quadratic constexpr behavior for left-assoc logical exprs [PR102780] Date: Tue, 26 Oct 2021 13:44:18 -0400 Message-Id: <20211026174418.3142626-1-ppalka@redhat.com> X-Mailer: git-send-email 2.33.1.711.g9d530dc002 MIME-Version: 1.0 X-Mimecast-Spam-Score: 0 X-Mimecast-Originator: redhat.com Content-Transfer-Encoding: 8bit Content-Type: text/plain; charset="US-ASCII" X-Spam-Status: No, score=-15.7 required=5.0 tests=BAYES_00, DKIMWL_WL_HIGH, DKIM_SIGNED, DKIM_VALID, DKIM_VALID_AU, DKIM_VALID_EF, GIT_PATCH_0, RCVD_IN_DNSWL_LOW, RCVD_IN_MSPIKE_H2, SPF_HELO_NONE, SPF_NONE, TXREP autolearn=ham autolearn_force=no version=3.4.4 X-Spam-Checker-Version: SpamAssassin 3.4.4 (2020-01-24) on server2.sourceware.org X-BeenThere: gcc-patches@gcc.gnu.org X-Mailman-Version: 2.1.29 Precedence: list List-Id: Gcc-patches mailing list <gcc-patches.gcc.gnu.org> List-Unsubscribe: <https://gcc.gnu.org/mailman/options/gcc-patches>, <mailto:gcc-patches-request@gcc.gnu.org?subject=unsubscribe> List-Archive: <https://gcc.gnu.org/pipermail/gcc-patches/> List-Post: <mailto:gcc-patches@gcc.gnu.org> List-Help: <mailto:gcc-patches-request@gcc.gnu.org?subject=help> List-Subscribe: <https://gcc.gnu.org/mailman/listinfo/gcc-patches>, <mailto:gcc-patches-request@gcc.gnu.org?subject=subscribe> From: Patrick Palka via Gcc-patches <gcc-patches@gcc.gnu.org> Reply-To: Patrick Palka <ppalka@redhat.com> Errors-To: gcc-patches-bounces+patchwork=sourceware.org@gcc.gnu.org Sender: "Gcc-patches" <gcc-patches-bounces+patchwork=sourceware.org@gcc.gnu.org> |
Series |
c++: quadratic constexpr behavior for left-assoc logical exprs [PR102780]
|
|
Commit Message
Patrick Palka
Oct. 26, 2021, 5:44 p.m. UTC
In the testcase below the two left fold expressions each expand into a logical expression with 1024 terms, for which potential_const_expr_1 takes more than a minute to return true. This is because p_c_e_1 performs trial evaluation of the first operand of a &&/|| in order to determine whether to consider its second operand. And because the expanded expression is left-associated, this trial evaluation causes p_c_e_1 to be quadratic in the number of terms of the expression. This patch fixes this quadratic behavior by making p_c_e_1 recurse only into the first operand of a &&/|| when checking for potentiality. This means we'll consider more non-constant expressions as potentially constant compared to the trial evaluation approach, but that should be harmless as later constexpr evaluation of the expression will deem it non-constant anyway. As a nice bonus, this change also reduces compile time for the libstdc++ testcase 20_util/variant/87619.cc by about 15%, even though our <variant> uses right folds instead of left folds. Likewise for the testcase in the PR, for which compile time is reduced by 30%. Bootstrapped and regtested on x86_64-pc-linux-gnu, does this look OK for trunk? PR c++/102780 gcc/cp/ChangeLog: * constexpr.c (potential_constant_expression_1) <case TRUTH_*_EXPR>: Only recurse into the first operand, and ignore the second. gcc/testsuite/ChangeLog: * g++.dg/cpp1z/fold13.C: New test. --- gcc/cp/constexpr.c | 21 +++------------------ gcc/testsuite/g++.dg/cpp1z/fold13.C | 29 +++++++++++++++++++++++++++++ 2 files changed, 32 insertions(+), 18 deletions(-) create mode 100644 gcc/testsuite/g++.dg/cpp1z/fold13.C
Comments
On Tue, Oct 26, 2021 at 01:44:18PM -0400, Patrick Palka via Gcc-patches wrote: > In the testcase below the two left fold expressions each expand into a > logical expression with 1024 terms, for which potential_const_expr_1 > takes more than a minute to return true. This is because p_c_e_1 > performs trial evaluation of the first operand of a &&/|| in order to > determine whether to consider its second operand. And because the > expanded expression is left-associated, this trial evaluation causes > p_c_e_1 to be quadratic in the number of terms of the expression. > > This patch fixes this quadratic behavior by making p_c_e_1 recurse only > into the first operand of a &&/|| when checking for potentiality. This > means we'll consider more non-constant expressions as potentially > constant compared to the trial evaluation approach, but that should be > harmless as later constexpr evaluation of the expression will deem it > non-constant anyway. For very large && or || expressions (but not mixed or with ! etc.) another possibility would be to check if the lhs or rhs have the same code and if so, linearize the trees and recurse only on the leafs (trees with other TREE_CODE) and stop when first expression isn't equal to tmp. For mixed &&/|| or with ! that could still be expensive though. Jakub
On Tue, Oct 26, 2021 at 2:46 PM Jakub Jelinek via Gcc-patches < gcc-patches@gcc.gnu.org> wrote: > On Tue, Oct 26, 2021 at 01:44:18PM -0400, Patrick Palka via Gcc-patches > wrote: > > In the testcase below the two left fold expressions each expand into a > > logical expression with 1024 terms, for which potential_const_expr_1 > > takes more than a minute to return true. This is because p_c_e_1 > > performs trial evaluation of the first operand of a &&/|| in order to > > determine whether to consider its second operand. And because the > > expanded expression is left-associated, this trial evaluation causes > > p_c_e_1 to be quadratic in the number of terms of the expression. > > > > This patch fixes this quadratic behavior by making p_c_e_1 recurse only > > into the first operand of a &&/|| when checking for potentiality. This > > means we'll consider more non-constant expressions as potentially > > constant compared to the trial evaluation approach, but that should be > > harmless as later constexpr evaluation of the expression will deem it > > non-constant anyway. > Fair, especially now that it seems that C++23 is removing the diagnostic for a constexpr function that can't produce a constant expression. And as more functions become constexpr, the trial evaluation gets more expensive to get to the point of failure. I wonder if we want to stop calling cxx_eval_outermost_constant_expression from pot_c_e_1 entirely, only check whether the operand is already constant. For very large && or || expressions (but not mixed or with ! etc.) another > possibility would be to check if the lhs or rhs have the same code and if > so, linearize the trees and recurse only on the leafs (trees with other > TREE_CODE) and stop when first expression isn't equal to tmp. > This would be good if we wanted to keep the evaluation. Jason
On Tue, Oct 26, 2021 at 03:29:02PM -0400, Jason Merrill wrote: > On Tue, Oct 26, 2021 at 2:46 PM Jakub Jelinek via Gcc-patches < > gcc-patches@gcc.gnu.org> wrote: > > > On Tue, Oct 26, 2021 at 01:44:18PM -0400, Patrick Palka via Gcc-patches > > wrote: > > > In the testcase below the two left fold expressions each expand into a > > > logical expression with 1024 terms, for which potential_const_expr_1 > > > takes more than a minute to return true. This is because p_c_e_1 > > > performs trial evaluation of the first operand of a &&/|| in order to > > > determine whether to consider its second operand. And because the > > > expanded expression is left-associated, this trial evaluation causes > > > p_c_e_1 to be quadratic in the number of terms of the expression. > > > > > > This patch fixes this quadratic behavior by making p_c_e_1 recurse only > > > into the first operand of a &&/|| when checking for potentiality. This > > > means we'll consider more non-constant expressions as potentially > > > constant compared to the trial evaluation approach, but that should be > > > harmless as later constexpr evaluation of the expression will deem it > > > non-constant anyway. > > > > Fair, especially now that it seems that C++23 is removing the diagnostic > for a constexpr function that can't produce a constant expression. And as > more functions become constexpr, the trial evaluation gets more expensive > to get to the point of failure. I wonder if we want to stop calling > cxx_eval_outermost_constant_expression from pot_c_e_1 entirely, only check > whether the operand is already constant. That would make p_c_e_1 surely much faster, but on the other side we would stop diagnosing some functions that can't be valid constant expressions no matter with what arguments they are called. Another option would be to do these cxx_eval_outermost_constant_expression calls from p_c_e_1 only in a cheap mode, i.e. with constexpr_ops_limit temporarily lowered to something very small, 100-ish or 1000-ish or so, so it would handle small expressions like before, but would quietly punt on large expressions. Jakub
On Tue, 26 Oct 2021, Jason Merrill wrote: > On Tue, Oct 26, 2021 at 2:46 PM Jakub Jelinek via Gcc-patches <gcc-patches@gcc.gnu.org> wrote: > On Tue, Oct 26, 2021 at 01:44:18PM -0400, Patrick Palka via Gcc-patches wrote: > > In the testcase below the two left fold expressions each expand into a > > logical expression with 1024 terms, for which potential_const_expr_1 > > takes more than a minute to return true. This is because p_c_e_1 > > performs trial evaluation of the first operand of a &&/|| in order to > > determine whether to consider its second operand. And because the > > expanded expression is left-associated, this trial evaluation causes > > p_c_e_1 to be quadratic in the number of terms of the expression. > > > > This patch fixes this quadratic behavior by making p_c_e_1 recurse only > > into the first operand of a &&/|| when checking for potentiality. This > > means we'll consider more non-constant expressions as potentially > > constant compared to the trial evaluation approach, but that should be > > harmless as later constexpr evaluation of the expression will deem it > > non-constant anyway. > > > Fair, especially now that it seems that C++23 is removing the diagnostic for a constexpr function that can't produce a constant expression. And as more functions become constexpr, the trial evaluation gets > more expensive to get to the point of failure. I wonder if we want to stop calling cxx_eval_outermost_constant_expression from pot_c_e_1 entirely, only check whether the operand is already constant. The performance impact of the other calls to cxx_eval_outermost_const_expr from p_c_e_1 is probably already mostly mitigated by the constexpr call cache and the fact that we try to evaluate all calls to constexpr functions during cp_fold_function anyway (at least with -O). So trial evaluation of e.g. the condition of a for/while loop that contains an expensive constexpr call shouldn't cause the compiler to perform more work overall IIUC. > > For very large && or || expressions (but not mixed or with ! etc.) another > possibility would be to check if the lhs or rhs have the same code and if > so, linearize the trees and recurse only on the leafs (trees with other > TREE_CODE) and stop when first expression isn't equal to tmp. > > > This would be good if we wanted to keep the evaluation. > > Jason > >
On Tue, Oct 26, 2021 at 05:07:43PM -0400, Patrick Palka wrote: > The performance impact of the other calls to cxx_eval_outermost_const_expr > from p_c_e_1 is probably already mostly mitigated by the constexpr call > cache and the fact that we try to evaluate all calls to constexpr > functions during cp_fold_function anyway (at least with -O). So trial constexpr function bodies don't go through cp_fold_function (intentionally, so that we don't optimize away UB), the bodies are copied before the trees of the normal copy are folded. Jakub
On Tue, 26 Oct 2021, Jakub Jelinek wrote: > On Tue, Oct 26, 2021 at 05:07:43PM -0400, Patrick Palka wrote: > > The performance impact of the other calls to cxx_eval_outermost_const_expr > > from p_c_e_1 is probably already mostly mitigated by the constexpr call > > cache and the fact that we try to evaluate all calls to constexpr > > functions during cp_fold_function anyway (at least with -O). So trial > > constexpr function bodies don't go through cp_fold_function (intentionally, > so that we don't optimize away UB), the bodies are copied before the trees of the > normal copy are folded. Ah right, I had forgotten about that.. Here's another approach that doesn't need to remove trial evaluation for &&/||. The idea is to first quietly check if the second operand is potentially constant _before_ performing trial evaluation of the first operand. This speeds up the case we care about (both operands are potentially constant) without regressing any diagnostics. We have to be careful about emitting bogus diagnostics when tf_error is set, hence the first hunk below which makes p_c_e_1 always proceed quietly first, and replay noisily in case of error (similar to how satisfaction works). Would something like this be preferable? -- >8 -- gcc/cp/ChangeLog: * constexpr.c (potential_constant_expression_1): When tf_error is set, proceed quietly first and return true if successful. <case TRUTH_*_EXPR>: When tf_error is not set, check potentiality of the second operand before performing trial evaluation of the first operand rather than after. diff --git a/gcc/cp/constexpr.c b/gcc/cp/constexpr.c index 6f83d303cdd..821bd41d994 100644 --- a/gcc/cp/constexpr.c +++ b/gcc/cp/constexpr.c @@ -8056,6 +8056,14 @@ potential_constant_expression_1 (tree t, bool want_rval, bool strict, bool now, #define RECUR(T,RV) \ potential_constant_expression_1 ((T), (RV), strict, now, flags, jump_target) + if (flags & tf_error) + { + flags &= ~tf_error; + if (RECUR (t, want_rval)) + return true; + flags |= tf_error; + } + enum { any = false, rval = true }; int i; tree tmp; @@ -8892,13 +8900,16 @@ potential_constant_expression_1 (tree t, bool want_rval, bool strict, bool now, tmp = boolean_false_node; truth: { - tree op = TREE_OPERAND (t, 0); - if (!RECUR (op, rval)) + tree op0 = TREE_OPERAND (t, 0); + tree op1 = TREE_OPERAND (t, 1); + if (!RECUR (op0, rval)) return false; + if (!(flags & tf_error) && RECUR (op1, rval)) + return true; if (!processing_template_decl) - op = cxx_eval_outermost_constant_expr (op, true); - if (tree_int_cst_equal (op, tmp)) - return RECUR (TREE_OPERAND (t, 1), rval); + op0 = cxx_eval_outermost_constant_expr (op0, true); + if (tree_int_cst_equal (op0, tmp)) + return (flags & tf_error) ? RECUR (op1, rval) : false; else return true; }
On 10/27/21 14:54, Patrick Palka wrote: > On Tue, 26 Oct 2021, Jakub Jelinek wrote: > >> On Tue, Oct 26, 2021 at 05:07:43PM -0400, Patrick Palka wrote: >>> The performance impact of the other calls to cxx_eval_outermost_const_expr >>> from p_c_e_1 is probably already mostly mitigated by the constexpr call >>> cache and the fact that we try to evaluate all calls to constexpr >>> functions during cp_fold_function anyway (at least with -O). So trial >> >> constexpr function bodies don't go through cp_fold_function (intentionally, >> so that we don't optimize away UB), the bodies are copied before the trees of the >> normal copy are folded. > > Ah right, I had forgotten about that.. > > Here's another approach that doesn't need to remove trial evaluation for > &&/||. The idea is to first quietly check if the second operand is > potentially constant _before_ performing trial evaluation of the first > operand. This speeds up the case we care about (both operands are > potentially constant) without regressing any diagnostics. We have to be > careful about emitting bogus diagnostics when tf_error is set, hence the > first hunk below which makes p_c_e_1 always proceed quietly first, and > replay noisily in case of error (similar to how satisfaction works). > > Would something like this be preferable? Seems plausible, though doubling the number of stack frames is a downside. What did you think of Jakub's suggestion of linearizing the terms? > -- >8 -- > > gcc/cp/ChangeLog: > > * constexpr.c (potential_constant_expression_1): When tf_error is > set, proceed quietly first and return true if successful. > <case TRUTH_*_EXPR>: When tf_error is not set, check potentiality > of the second operand before performing trial evaluation of the > first operand rather than after. > > > diff --git a/gcc/cp/constexpr.c b/gcc/cp/constexpr.c > index 6f83d303cdd..821bd41d994 100644 > --- a/gcc/cp/constexpr.c > +++ b/gcc/cp/constexpr.c > @@ -8056,6 +8056,14 @@ potential_constant_expression_1 (tree t, bool want_rval, bool strict, bool now, > #define RECUR(T,RV) \ > potential_constant_expression_1 ((T), (RV), strict, now, flags, jump_target) > > + if (flags & tf_error) > + { > + flags &= ~tf_error; > + if (RECUR (t, want_rval)) > + return true; > + flags |= tf_error; > + } > + > enum { any = false, rval = true }; > int i; > tree tmp; > @@ -8892,13 +8900,16 @@ potential_constant_expression_1 (tree t, bool want_rval, bool strict, bool now, > tmp = boolean_false_node; > truth: > { > - tree op = TREE_OPERAND (t, 0); > - if (!RECUR (op, rval)) > + tree op0 = TREE_OPERAND (t, 0); > + tree op1 = TREE_OPERAND (t, 1); > + if (!RECUR (op0, rval)) > return false; > + if (!(flags & tf_error) && RECUR (op1, rval)) > + return true; > if (!processing_template_decl) > - op = cxx_eval_outermost_constant_expr (op, true); > - if (tree_int_cst_equal (op, tmp)) > - return RECUR (TREE_OPERAND (t, 1), rval); > + op0 = cxx_eval_outermost_constant_expr (op0, true); > + if (tree_int_cst_equal (op0, tmp)) > + return (flags & tf_error) ? RECUR (op1, rval) : false; > else > return true; > } >
On Wed, 27 Oct 2021, Jason Merrill wrote: > On 10/27/21 14:54, Patrick Palka wrote: > > On Tue, 26 Oct 2021, Jakub Jelinek wrote: > > > > > On Tue, Oct 26, 2021 at 05:07:43PM -0400, Patrick Palka wrote: > > > > The performance impact of the other calls to > > > > cxx_eval_outermost_const_expr > > > > from p_c_e_1 is probably already mostly mitigated by the constexpr call > > > > cache and the fact that we try to evaluate all calls to constexpr > > > > functions during cp_fold_function anyway (at least with -O). So trial > > > > > > constexpr function bodies don't go through cp_fold_function > > > (intentionally, > > > so that we don't optimize away UB), the bodies are copied before the trees > > > of the > > > normal copy are folded. > > > > Ah right, I had forgotten about that.. > > > > Here's another approach that doesn't need to remove trial evaluation for > > &&/||. The idea is to first quietly check if the second operand is > > potentially constant _before_ performing trial evaluation of the first > > operand. This speeds up the case we care about (both operands are > > potentially constant) without regressing any diagnostics. We have to be > > careful about emitting bogus diagnostics when tf_error is set, hence the > > first hunk below which makes p_c_e_1 always proceed quietly first, and > > replay noisily in case of error (similar to how satisfaction works). > > > > Would something like this be preferable? > > Seems plausible, though doubling the number of stack frames is a downside. Whoops, good point.. The noisy -> quiet adjustment only needs to be performed during the outermost call to p_c_e_1, and not also during each recursive call. The revised diff below fixes this thinko, and so only a single extra stack frame is needed AFAICT. > > What did you think of Jakub's suggestion of linearizing the terms? IIUC that would fix the quadraticness, but it wouldn't address that we end up evaluating the entire expression twice, once during the trial evaluation of each term from p_c_e_1 and again during the proper evaluation of the entire expression. It'd be nice if we could somehow avoid the double evaluation, as in the approach below (or in the first patch). -- >8 -- gcc/cp/ChangeLog: * constexpr.c (potential_constant_expression_1): When tf_error is set, proceed quietly first and return true if successful. <case TRUTH_*_EXPR>: When tf_error is not set, check potentiality of the second operand before performing trial evaluation of the first operand rather than after. diff --git a/gcc/cp/constexpr.c b/gcc/cp/constexpr.c index 6f83d303cdd..7855a948baf 100644 --- a/gcc/cp/constexpr.c +++ b/gcc/cp/constexpr.c @@ -8892,13 +8892,16 @@ potential_constant_expression_1 (tree t, bool want_rval, bool strict, bool now, tmp = boolean_false_node; truth: { - tree op = TREE_OPERAND (t, 0); - if (!RECUR (op, rval)) + tree op0 = TREE_OPERAND (t, 0); + tree op1 = TREE_OPERAND (t, 1); + if (!RECUR (op0, rval)) return false; + if (!(flags & tf_error) && RECUR (op1, rval)) + return true; if (!processing_template_decl) - op = cxx_eval_outermost_constant_expr (op, true); - if (tree_int_cst_equal (op, tmp)) - return RECUR (TREE_OPERAND (t, 1), rval); + op0 = cxx_eval_outermost_constant_expr (op0, true); + if (tree_int_cst_equal (op0, tmp)) + return (flags & tf_error) ? RECUR (op1, rval) : false; else return true; } @@ -9107,6 +9110,14 @@ bool potential_constant_expression_1 (tree t, bool want_rval, bool strict, bool now, tsubst_flags_t flags) { + if (flags & tf_error) + { + flags &= ~tf_error; + if (potential_constant_expression_1 (t, want_rval, strict, now, flags)) + return true; + flags |= tf_error; + } + tree target = NULL_TREE; return potential_constant_expression_1 (t, want_rval, strict, now, flags, &target);
On 10/27/21 17:10, Patrick Palka wrote: > On Wed, 27 Oct 2021, Jason Merrill wrote: > >> On 10/27/21 14:54, Patrick Palka wrote: >>> On Tue, 26 Oct 2021, Jakub Jelinek wrote: >>> >>>> On Tue, Oct 26, 2021 at 05:07:43PM -0400, Patrick Palka wrote: >>>>> The performance impact of the other calls to >>>>> cxx_eval_outermost_const_expr >>>>> from p_c_e_1 is probably already mostly mitigated by the constexpr call >>>>> cache and the fact that we try to evaluate all calls to constexpr >>>>> functions during cp_fold_function anyway (at least with -O). So trial >>>> >>>> constexpr function bodies don't go through cp_fold_function >>>> (intentionally, >>>> so that we don't optimize away UB), the bodies are copied before the trees >>>> of the >>>> normal copy are folded. >>> >>> Ah right, I had forgotten about that.. >>> >>> Here's another approach that doesn't need to remove trial evaluation for >>> &&/||. The idea is to first quietly check if the second operand is >>> potentially constant _before_ performing trial evaluation of the first >>> operand. This speeds up the case we care about (both operands are >>> potentially constant) without regressing any diagnostics. We have to be >>> careful about emitting bogus diagnostics when tf_error is set, hence the >>> first hunk below which makes p_c_e_1 always proceed quietly first, and >>> replay noisily in case of error (similar to how satisfaction works). >>> >>> Would something like this be preferable? >> >> Seems plausible, though doubling the number of stack frames is a downside. > > Whoops, good point.. The noisy -> quiet adjustment only needs to > be performed during the outermost call to p_c_e_1, and not also during > each recursive call. The revised diff below fixes this thinko, and so > only a single extra stack frame is needed AFAICT. > >> What did you think of Jakub's suggestion of linearizing the terms? > > IIUC that would fix the quadraticness, but it wouldn't address that > we end up evaluating the entire expression twice, once during the trial > evaluation of each term from p_c_e_1 and again during the proper > evaluation of the entire expression. It'd be nice if we could somehow > avoid the double evaluation, as in the approach below (or in the first > patch). OK with more comments to explain the tf_error hijinks. > -- >8 -- > > gcc/cp/ChangeLog: > > * constexpr.c (potential_constant_expression_1): When tf_error is > set, proceed quietly first and return true if successful. > <case TRUTH_*_EXPR>: When tf_error is not set, check potentiality > of the second operand before performing trial evaluation of the > first operand rather than after. > > diff --git a/gcc/cp/constexpr.c b/gcc/cp/constexpr.c > index 6f83d303cdd..7855a948baf 100644 > --- a/gcc/cp/constexpr.c > +++ b/gcc/cp/constexpr.c > @@ -8892,13 +8892,16 @@ potential_constant_expression_1 (tree t, bool want_rval, bool strict, bool now, > tmp = boolean_false_node; > truth: > { > - tree op = TREE_OPERAND (t, 0); > - if (!RECUR (op, rval)) > + tree op0 = TREE_OPERAND (t, 0); > + tree op1 = TREE_OPERAND (t, 1); > + if (!RECUR (op0, rval)) > return false; > + if (!(flags & tf_error) && RECUR (op1, rval)) > + return true; > if (!processing_template_decl) > - op = cxx_eval_outermost_constant_expr (op, true); > - if (tree_int_cst_equal (op, tmp)) > - return RECUR (TREE_OPERAND (t, 1), rval); > + op0 = cxx_eval_outermost_constant_expr (op0, true); > + if (tree_int_cst_equal (op0, tmp)) > + return (flags & tf_error) ? RECUR (op1, rval) : false; > else > return true; > } > @@ -9107,6 +9110,14 @@ bool > potential_constant_expression_1 (tree t, bool want_rval, bool strict, bool now, > tsubst_flags_t flags) > { > + if (flags & tf_error) > + { > + flags &= ~tf_error; > + if (potential_constant_expression_1 (t, want_rval, strict, now, flags)) > + return true; > + flags |= tf_error; > + } > + > tree target = NULL_TREE; > return potential_constant_expression_1 (t, want_rval, strict, now, > flags, &target);
> On 10/27/21 17:10, Patrick Palka wrote: > > On Wed, 27 Oct 2021, Jason Merrill wrote: > > > > > On 10/27/21 14:54, Patrick Palka wrote: > > > > On Tue, 26 Oct 2021, Jakub Jelinek wrote: > > > > > > > > > On Tue, Oct 26, 2021 at 05:07:43PM -0400, Patrick Palka wrote: > > > > > > The performance impact of the other calls to > > > > > > cxx_eval_outermost_const_expr > > > > > > from p_c_e_1 is probably already mostly mitigated by the constexpr > > > > > > call > > > > > > cache and the fact that we try to evaluate all calls to constexpr > > > > > > functions during cp_fold_function anyway (at least with -O). So > > > > > > trial > > > > > > > > > > constexpr function bodies don't go through cp_fold_function > > > > > (intentionally, > > > > > so that we don't optimize away UB), the bodies are copied before the > > > > > trees > > > > > of the > > > > > normal copy are folded. > > > > > > > > Ah right, I had forgotten about that.. > > > > > > > > Here's another approach that doesn't need to remove trial evaluation for > > > > &&/||. The idea is to first quietly check if the second operand is > > > > potentially constant _before_ performing trial evaluation of the first > > > > operand. This speeds up the case we care about (both operands are > > > > potentially constant) without regressing any diagnostics. We have to be > > > > careful about emitting bogus diagnostics when tf_error is set, hence the > > > > first hunk below which makes p_c_e_1 always proceed quietly first, and > > > > replay noisily in case of error (similar to how satisfaction works). > > > > > > > > Would something like this be preferable? > > > > > > Seems plausible, though doubling the number of stack frames is a downside. > > > > Whoops, good point.. The noisy -> quiet adjustment only needs to > > be performed during the outermost call to p_c_e_1, and not also during > > each recursive call. The revised diff below fixes this thinko, and so > > only a single extra stack frame is needed AFAICT. > > > > > What did you think of Jakub's suggestion of linearizing the terms? > > > > IIUC that would fix the quadraticness, but it wouldn't address that > > we end up evaluating the entire expression twice, once during the trial > > evaluation of each term from p_c_e_1 and again during the proper > > evaluation of the entire expression. It'd be nice if we could somehow > > avoid the double evaluation, as in the approach below (or in the first > > patch). > > OK with more comments to explain the tf_error hijinks. Thanks a lot, here's the complete committed patch for the record: -- >8 -- Subject: [PATCH] c++: quadratic constexpr behavior for left-assoc logical exprs [PR102780] In the testcase below the two left fold expressions each expand into a constant logical expression with 1024 terms, for which potential_const_expr takes more than a minute to return true. This happens because p_c_e_1 performs trial evaluation of the first operand of a &&/|| in order to determine whether to consider the potentiality of the second operand. And because the expanded expression is left-associated, this trial evaluation causes p_c_e_1 to be quadratic in the number of terms of the expression. This patch fixes this quadratic behavior by making p_c_e_1 preemptively compute potentiality of the second operand of a &&/||, and perform trial evaluation of the first operand only if the second operand isn't potentially constant. We must be careful to avoid emitting bogus diagnostics during the preemptive computation; to that end, we perform this shortcut only when tf_error is cleared, and when tf_error is set we now first check potentiality of the whole expression quietly and replay the check noisily for diagnostics. Apart from fixing the quadraticness for left-associated logical exprs, this change also reduces compile time for the libstdc++ testcase 20_util/variant/87619.cc by about 15% even though our <variant> uses right folds instead of left folds. Likewise for the testcase in the PR, for which compile time is reduced by 30%. The reason for these speedups is that p_c_e_1 no longer performs expensive trial evaluation of each term of large constant logical expressions when determining their potentiality. PR c++/102780 gcc/cp/ChangeLog: * constexpr.c (potential_constant_expression_1) <case TRUTH_*_EXPR>: When tf_error isn't set, preemptively check potentiality of the second operand before performing trial evaluation of the first operand. (potential_constant_expression_1): When tf_error is set, first check potentiality quietly and return true if successful, otherwise proceed noisily to give errors. gcc/testsuite/ChangeLog: * g++.dg/cpp1z/fold13.C: New test. --- gcc/cp/constexpr.c | 26 +++++++++++++++++++++----- gcc/testsuite/g++.dg/cpp1z/fold13.C | 29 +++++++++++++++++++++++++++++ 2 files changed, 50 insertions(+), 5 deletions(-) create mode 100644 gcc/testsuite/g++.dg/cpp1z/fold13.C diff --git a/gcc/cp/constexpr.c b/gcc/cp/constexpr.c index a31d12b7451..40fe165c2b7 100644 --- a/gcc/cp/constexpr.c +++ b/gcc/cp/constexpr.c @@ -8892,13 +8892,18 @@ potential_constant_expression_1 (tree t, bool want_rval, bool strict, bool now, tmp = boolean_false_node; truth: { - tree op = TREE_OPERAND (t, 0); - if (!RECUR (op, rval)) + tree op0 = TREE_OPERAND (t, 0); + tree op1 = TREE_OPERAND (t, 1); + if (!RECUR (op0, rval)) return false; + if (!(flags & tf_error) && RECUR (op1, rval)) + /* When quiet, try to avoid expensive trial evaluation by first + checking potentiality of the second operand. */ + return true; if (!processing_template_decl) - op = cxx_eval_outermost_constant_expr (op, true); - if (tree_int_cst_equal (op, tmp)) - return RECUR (TREE_OPERAND (t, 1), rval); + op0 = cxx_eval_outermost_constant_expr (op0, true); + if (tree_int_cst_equal (op0, tmp)) + return (flags & tf_error) ? RECUR (op1, rval) : false; else return true; } @@ -9107,6 +9112,17 @@ bool potential_constant_expression_1 (tree t, bool want_rval, bool strict, bool now, tsubst_flags_t flags) { + if (flags & tf_error) + { + /* Check potentiality quietly first, as that could be performed more + efficiently in some cases (currently only for TRUTH_*_EXPR). If + that fails, replay the check noisily to give errors. */ + flags &= ~tf_error; + if (potential_constant_expression_1 (t, want_rval, strict, now, flags)) + return true; + flags |= tf_error; + } + tree target = NULL_TREE; return potential_constant_expression_1 (t, want_rval, strict, now, flags, &target); diff --git a/gcc/testsuite/g++.dg/cpp1z/fold13.C b/gcc/testsuite/g++.dg/cpp1z/fold13.C new file mode 100644 index 00000000000..9d7554f8999 --- /dev/null +++ b/gcc/testsuite/g++.dg/cpp1z/fold13.C @@ -0,0 +1,29 @@ +// { dg-do compile { target c++17 } } +// Verify constexpr evaluation of a large left fold logical expression +// isn't quadratic in the size of the expanded expression. + +template<int> struct S { static constexpr bool value = true; }; + +template<class T, T...> struct integer_sequence { }; + +template<class T, T N> +using make_integer_sequence +#if __has_builtin(__make_integer_seq) + = __make_integer_seq<integer_sequence, T, N>; +#else + = integer_sequence<T, __integer_pack(N)...>; +#endif + +template<int... Is> +constexpr bool f_impl(integer_sequence<int, Is...>) { + return (... && S<Is>::value); +} + +static_assert(f_impl(make_integer_sequence<int, 1024>())); + +template<int... Is> +constexpr bool g_impl(integer_sequence<int, Is...>) { + return (... || !S<Is>::value); +} + +static_assert(!g_impl(make_integer_sequence<int, 1024>()));
On Thu, Oct 28, 2021 at 12:40:02PM -0400, Patrick Palka wrote: > PR c++/102780 > > gcc/cp/ChangeLog: > > * constexpr.c (potential_constant_expression_1) <case TRUTH_*_EXPR>: > When tf_error isn't set, preemptively check potentiality of the > second operand before performing trial evaluation of the first > operand. > (potential_constant_expression_1): When tf_error is set, first check > potentiality quietly and return true if successful, otherwise > proceed noisily to give errors. > > gcc/testsuite/ChangeLog: > > * g++.dg/cpp1z/fold13.C: New test. > --- > gcc/cp/constexpr.c | 26 +++++++++++++++++++++----- > gcc/testsuite/g++.dg/cpp1z/fold13.C | 29 +++++++++++++++++++++++++++++ > 2 files changed, 50 insertions(+), 5 deletions(-) > create mode 100644 gcc/testsuite/g++.dg/cpp1z/fold13.C Is there a reason to turn this mode of evaluating everything twice if an error should be diagnosed all the time, rather than only if we actually see a TRUTH_*_EXPR we want to handle this way? If we don't see any TRUTH_*_EXPR, or if processing_template_decl, or if the first operand is already a constant, that seems like a waste of time. So, can't we instead drop the second hunk, if processing_template_decl or TREE_CODE (op0) == INTEGER_CST do what the code did before, and just otherwise if flag & tf_error temporarily clear it, RECUR on op1 first, then cxx_eval_outermost_constant_expr and if tree_int_cst_equal RECUR on op1 the second time with tf_error and some extra flag (1 << 30?) that will mean not to RECUR on op1 twice in recursive invocations (to avoid bad compile time complexity on (x && (x && (x && (x && (x && (x && (x && (x && (x && (x && (x && (x && y)))))))))))) etc. if x constant evaluates to true and y is not potential constant expression. Though, I'm not sure that doing the RECUR (op1, rval) instead of cxx_eval_outermost_constant_expr must be generally a win for compile time, it can be if op0 is very large expression, but it can be a pessimization if op1 is huge and op0 is simple and doesn't constexpr evaluate to tmp. As I said, another possibility is something like: /* Try to quietly evaluate T to constant, but don't try too hard. */ static tree potential_constant_expression_eval (tree t) { auto o = make_temp_override (constexpr_ops_limit, MIN (constexpr_ops_limit, 100)); return cxx_eval_outermost_constant_expr (t, true); } and using this new function instead of cxx_eval_outermost_constant_expr (op, true); everywhere in potential_constant_expression_1 should fix the quadraticness too. > --- a/gcc/cp/constexpr.c > +++ b/gcc/cp/constexpr.c > @@ -8892,13 +8892,18 @@ potential_constant_expression_1 (tree t, bool want_rval, bool strict, bool now, > tmp = boolean_false_node; > truth: > { > - tree op = TREE_OPERAND (t, 0); > - if (!RECUR (op, rval)) > + tree op0 = TREE_OPERAND (t, 0); > + tree op1 = TREE_OPERAND (t, 1); > + if (!RECUR (op0, rval)) > return false; > + if (!(flags & tf_error) && RECUR (op1, rval)) > + /* When quiet, try to avoid expensive trial evaluation by first > + checking potentiality of the second operand. */ > + return true; > if (!processing_template_decl) > - op = cxx_eval_outermost_constant_expr (op, true); > - if (tree_int_cst_equal (op, tmp)) > - return RECUR (TREE_OPERAND (t, 1), rval); > + op0 = cxx_eval_outermost_constant_expr (op0, true); > + if (tree_int_cst_equal (op0, tmp)) > + return (flags & tf_error) ? RECUR (op1, rval) : false; > else > return true; > } > @@ -9107,6 +9112,17 @@ bool > potential_constant_expression_1 (tree t, bool want_rval, bool strict, bool now, > tsubst_flags_t flags) > { > + if (flags & tf_error) > + { > + /* Check potentiality quietly first, as that could be performed more > + efficiently in some cases (currently only for TRUTH_*_EXPR). If > + that fails, replay the check noisily to give errors. */ > + flags &= ~tf_error; > + if (potential_constant_expression_1 (t, want_rval, strict, now, flags)) > + return true; > + flags |= tf_error; > + } > + > tree target = NULL_TREE; > return potential_constant_expression_1 (t, want_rval, strict, now, > flags, &target); Jakub
On Thu, 28 Oct 2021, Jakub Jelinek wrote: > On Thu, Oct 28, 2021 at 12:40:02PM -0400, Patrick Palka wrote: > > PR c++/102780 > > > > gcc/cp/ChangeLog: > > > > * constexpr.c (potential_constant_expression_1) <case TRUTH_*_EXPR>: > > When tf_error isn't set, preemptively check potentiality of the > > second operand before performing trial evaluation of the first > > operand. > > (potential_constant_expression_1): When tf_error is set, first check > > potentiality quietly and return true if successful, otherwise > > proceed noisily to give errors. > > > > gcc/testsuite/ChangeLog: > > > > * g++.dg/cpp1z/fold13.C: New test. > > --- > > gcc/cp/constexpr.c | 26 +++++++++++++++++++++----- > > gcc/testsuite/g++.dg/cpp1z/fold13.C | 29 +++++++++++++++++++++++++++++ > > 2 files changed, 50 insertions(+), 5 deletions(-) > > create mode 100644 gcc/testsuite/g++.dg/cpp1z/fold13.C > > Is there a reason to turn this mode of evaluating everything twice if an > error should be diagnosed all the time, rather than only if we actually see > a TRUTH_*_EXPR we want to handle this way? > If we don't see any TRUTH_*_EXPR, or if processing_template_decl, or if > the first operand is already a constant, that seems like a waste of time. Hmm yeah, at the very least it wouldn't hurt to check processing_template_decl before doing the tf_error shenanigans. I'm not sure if we would gain anything by first looking for TRUTH_*_EXPR since that'd involve walking the entire expression anyway IIUC. > > So, can't we instead drop the second hunk, if processing_template_decl or > TREE_CODE (op0) == INTEGER_CST do what the code did before, and just > otherwise if flag & tf_error temporarily clear it, RECUR on op1 first, then > cxx_eval_outermost_constant_expr and if tree_int_cst_equal RECUR on op1 > the second time with tf_error and some extra flag (1 << 30?) that will mean not to > RECUR on op1 twice in recursive invocations (to avoid bad compile time > complexity on > (x && (x && (x && (x && (x && (x && (x && (x && (x && (x && (x && (x && y)))))))))))) > etc. if x constant evaluates to true and y is not potential constant > expression. I considered this approach but I wasn't sure it was worth the added complexity just to speed up the error case. And the current approach is already how constraint satisfaction works (it always proceeds quietly first, and replays the whole thing noisily upon error if tf_error was set) so there's also a precedence I suppose.. > > Though, I'm not sure that doing the RECUR (op1, rval) instead of > cxx_eval_outermost_constant_expr must be generally a win for compile time, > it can be if op0 is very large expression, but it can be a pessimization if > op1 is huge and op0 is simple and doesn't constexpr evaluate to tmp. True, it's not always a win but it seems to me that checking potentiality of both operands first before doing the trial evaluation gives the most consistent performance, at least for constant logical expressions. Recursing on both operands takes time proportional to the size of the operands, whereas trial evaluation can take arbitrarily long. > > As I said, another possibility is something like: > /* Try to quietly evaluate T to constant, but don't try too hard. */ > > static tree > potential_constant_expression_eval (tree t) > { > auto o = make_temp_override (constexpr_ops_limit, > MIN (constexpr_ops_limit, 100)); > return cxx_eval_outermost_constant_expr (t, true); > } > and using this new function instead of cxx_eval_outermost_constant_expr (op, true); > everywhere in potential_constant_expression_1 should fix the quadraticness > too. This would technically fix the quadraticness but wouldn't it still mean that a huge left-associated constant logical expression is quite a bit slower to check than an equivalent right-associated one (depending on what we set constexpr_ops_limit to)? We should probably do this anyway anyway but it doesn't seem sufficient on its own to make equivalent left/right-associated logical expressions have the same performance behavior IMHO. > > > --- a/gcc/cp/constexpr.c > > +++ b/gcc/cp/constexpr.c > > @@ -8892,13 +8892,18 @@ potential_constant_expression_1 (tree t, bool want_rval, bool strict, bool now, > > tmp = boolean_false_node; > > truth: > > { > > - tree op = TREE_OPERAND (t, 0); > > - if (!RECUR (op, rval)) > > + tree op0 = TREE_OPERAND (t, 0); > > + tree op1 = TREE_OPERAND (t, 1); > > + if (!RECUR (op0, rval)) > > return false; > > + if (!(flags & tf_error) && RECUR (op1, rval)) > > + /* When quiet, try to avoid expensive trial evaluation by first > > + checking potentiality of the second operand. */ > > + return true; > > if (!processing_template_decl) > > - op = cxx_eval_outermost_constant_expr (op, true); > > - if (tree_int_cst_equal (op, tmp)) > > - return RECUR (TREE_OPERAND (t, 1), rval); > > + op0 = cxx_eval_outermost_constant_expr (op0, true); > > + if (tree_int_cst_equal (op0, tmp)) > > + return (flags & tf_error) ? RECUR (op1, rval) : false; > > else > > return true; > > } > > @@ -9107,6 +9112,17 @@ bool > > potential_constant_expression_1 (tree t, bool want_rval, bool strict, bool now, > > tsubst_flags_t flags) > > { > > + if (flags & tf_error) > > + { > > + /* Check potentiality quietly first, as that could be performed more > > + efficiently in some cases (currently only for TRUTH_*_EXPR). If > > + that fails, replay the check noisily to give errors. */ > > + flags &= ~tf_error; > > + if (potential_constant_expression_1 (t, want_rval, strict, now, flags)) > > + return true; > > + flags |= tf_error; > > + } > > + > > tree target = NULL_TREE; > > return potential_constant_expression_1 (t, want_rval, strict, now, > > flags, &target); > > Jakub > >
On Thu, Oct 28, 2021 at 03:35:20PM -0400, Patrick Palka wrote: > > Is there a reason to turn this mode of evaluating everything twice if an > > error should be diagnosed all the time, rather than only if we actually see > > a TRUTH_*_EXPR we want to handle this way? > > If we don't see any TRUTH_*_EXPR, or if processing_template_decl, or if > > the first operand is already a constant, that seems like a waste of time. > > Hmm yeah, at the very least it wouldn't hurt to check > processing_template_decl before doing the tf_error shenanigans. I'm not > sure if we would gain anything by first looking for TRUTH_*_EXPR since > that'd involve walking the entire expression anyway IIUC. I meant actually something like: --- gcc/cp/constexpr.c.jj 2021-10-28 20:07:48.566193259 +0200 +++ gcc/cp/constexpr.c 2021-10-29 13:47:48.824026156 +0200 @@ -8789,7 +8789,7 @@ potential_constant_expression_1 (tree t, return false; } else if (!check_for_uninitialized_const_var - (tmp, /*constexpr_context_p=*/true, flags)) + (tmp, /*constexpr_context_p=*/true, flags & ~(1 << 30))) return false; } return RECUR (tmp, want_rval); @@ -8896,14 +8896,36 @@ potential_constant_expression_1 (tree t, tree op1 = TREE_OPERAND (t, 1); if (!RECUR (op0, rval)) return false; - if (!(flags & tf_error) && RECUR (op1, rval)) - /* When quiet, try to avoid expensive trial evaluation by first - checking potentiality of the second operand. */ - return true; - if (!processing_template_decl) - op0 = cxx_eval_outermost_constant_expr (op0, true); + if (TREE_CODE (op0) != INTEGER_CST && !processing_template_decl) + { + /* If op0 is not a constant, we can either + cxx_eval_outermost_constant_expr first, or RECUR (op1, rval) + first. If quiet, perform the latter first, if not quiet + and it is the outermost such TRUTH_*_EXPR, perform the + latter first in quiet mode, followed by the former and + retry with the latter in non-quiet mode. */ + if ((flags & (1 << 30)) != 0) + op0 = cxx_eval_outermost_constant_expr (op0, true); + else if ((flags & tf_error) != 0) + { + flags &= ~tf_error; + if (RECUR (op1, rval)) + return true; + op0 = cxx_eval_outermost_constant_expr (op0, true); + flags |= tf_error | (1 << 30); + } + else + { + if (RECUR (op1, rval)) + return true; + op0 = cxx_eval_outermost_constant_expr (op0, true); + if (tree_int_cst_equal (op0, tmp)) + return false; + return true; + } + } if (tree_int_cst_equal (op0, tmp)) - return (flags & tf_error) ? RECUR (op1, rval) : false; + return RECUR (op1, rval); else return true; } @@ -9112,17 +9134,6 @@ bool potential_constant_expression_1 (tree t, bool want_rval, bool strict, bool now, tsubst_flags_t flags) { - if (flags & tf_error) - { - /* Check potentiality quietly first, as that could be performed more - efficiently in some cases (currently only for TRUTH_*_EXPR). If - that fails, replay the check noisily to give errors. */ - flags &= ~tf_error; - if (potential_constant_expression_1 (t, want_rval, strict, now, flags)) - return true; - flags |= tf_error; - } - tree target = NULL_TREE; return potential_constant_expression_1 (t, want_rval, strict, now, flags, &target); (perhaps with naming the 1 << 30 as tf_something or using different bit for that). So no doubling of potential_constant_expression_1 evaluation for tf_error unless a TRUTH_*_EXPR is seen outside of template with potentially constant first operand other than INTEGER_CST, but similarly to what you did, make sure that there are at most two calls and not more. > > As I said, another possibility is something like: > > /* Try to quietly evaluate T to constant, but don't try too hard. */ > > > > static tree > > potential_constant_expression_eval (tree t) > > { > > auto o = make_temp_override (constexpr_ops_limit, > > MIN (constexpr_ops_limit, 100)); > > return cxx_eval_outermost_constant_expr (t, true); > > } > > and using this new function instead of cxx_eval_outermost_constant_expr (op, true); > > everywhere in potential_constant_expression_1 should fix the quadraticness > > too. > > This would technically fix the quadraticness but wouldn't it still mean > that a huge left-associated constant logical expression is quite a bit > slower to check than an equivalent right-associated one (depending on > what we set constexpr_ops_limit to)? We should probably do this anyway > anyway but it doesn't seem sufficient on its own to make equivalent > left/right-associated logical expressions have the same performance > behavior IMHO. The most expensive would be #define TEN true && true && true && true && true && true && true && true && true && true && TEN TEN TEN TEN TEN false or something similar because if any of the && expressions (other than the last one) are more expensive to constant evaluate, it would just mean it would stop the quadratic behavior earlier. Though, of course this isn't either or, the potential_constant_expression_eval could be mixed either with your current version, or the one adjusted by the above untested patch, or by reversion of all the changes + linearization into a vector. Jakub
On Fri, 29 Oct 2021, Jakub Jelinek wrote: > On Thu, Oct 28, 2021 at 03:35:20PM -0400, Patrick Palka wrote: > > > Is there a reason to turn this mode of evaluating everything twice if an > > > error should be diagnosed all the time, rather than only if we actually see > > > a TRUTH_*_EXPR we want to handle this way? > > > If we don't see any TRUTH_*_EXPR, or if processing_template_decl, or if > > > the first operand is already a constant, that seems like a waste of time. > > > > Hmm yeah, at the very least it wouldn't hurt to check > > processing_template_decl before doing the tf_error shenanigans. I'm not > > sure if we would gain anything by first looking for TRUTH_*_EXPR since > > that'd involve walking the entire expression anyway IIUC. > > I meant actually something like: > --- gcc/cp/constexpr.c.jj 2021-10-28 20:07:48.566193259 +0200 > +++ gcc/cp/constexpr.c 2021-10-29 13:47:48.824026156 +0200 > @@ -8789,7 +8789,7 @@ potential_constant_expression_1 (tree t, > return false; > } > else if (!check_for_uninitialized_const_var > - (tmp, /*constexpr_context_p=*/true, flags)) > + (tmp, /*constexpr_context_p=*/true, flags & ~(1 << 30))) > return false; > } > return RECUR (tmp, want_rval); > @@ -8896,14 +8896,36 @@ potential_constant_expression_1 (tree t, > tree op1 = TREE_OPERAND (t, 1); > if (!RECUR (op0, rval)) > return false; > - if (!(flags & tf_error) && RECUR (op1, rval)) > - /* When quiet, try to avoid expensive trial evaluation by first > - checking potentiality of the second operand. */ > - return true; > - if (!processing_template_decl) > - op0 = cxx_eval_outermost_constant_expr (op0, true); > + if (TREE_CODE (op0) != INTEGER_CST && !processing_template_decl) > + { > + /* If op0 is not a constant, we can either > + cxx_eval_outermost_constant_expr first, or RECUR (op1, rval) > + first. If quiet, perform the latter first, if not quiet > + and it is the outermost such TRUTH_*_EXPR, perform the > + latter first in quiet mode, followed by the former and > + retry with the latter in non-quiet mode. */ > + if ((flags & (1 << 30)) != 0) > + op0 = cxx_eval_outermost_constant_expr (op0, true); > + else if ((flags & tf_error) != 0) > + { > + flags &= ~tf_error; > + if (RECUR (op1, rval)) > + return true; > + op0 = cxx_eval_outermost_constant_expr (op0, true); > + flags |= tf_error | (1 << 30); > + } > + else > + { > + if (RECUR (op1, rval)) > + return true; > + op0 = cxx_eval_outermost_constant_expr (op0, true); > + if (tree_int_cst_equal (op0, tmp)) > + return false; > + return true; > + } > + } > if (tree_int_cst_equal (op0, tmp)) > - return (flags & tf_error) ? RECUR (op1, rval) : false; > + return RECUR (op1, rval); > else > return true; > } > @@ -9112,17 +9134,6 @@ bool > potential_constant_expression_1 (tree t, bool want_rval, bool strict, bool now, > tsubst_flags_t flags) > { > - if (flags & tf_error) > - { > - /* Check potentiality quietly first, as that could be performed more > - efficiently in some cases (currently only for TRUTH_*_EXPR). If > - that fails, replay the check noisily to give errors. */ > - flags &= ~tf_error; > - if (potential_constant_expression_1 (t, want_rval, strict, now, flags)) > - return true; > - flags |= tf_error; > - } > - > tree target = NULL_TREE; > return potential_constant_expression_1 (t, want_rval, strict, now, > flags, &target); > > (perhaps with naming the 1 << 30 as tf_something or using different bit for > that). So no doubling of potential_constant_expression_1 evaluation > for tf_error unless a TRUTH_*_EXPR is seen outside of template with > potentially constant first operand other than INTEGER_CST, but similarly to > what you did, make sure that there are at most two calls and not more. That makes sense, though it's somewhat unfortunate that we'd have to use/add an adhoc tsubst_flags_t flag with this approach :/ > > > > As I said, another possibility is something like: > > > /* Try to quietly evaluate T to constant, but don't try too hard. */ > > > > > > static tree > > > potential_constant_expression_eval (tree t) > > > { > > > auto o = make_temp_override (constexpr_ops_limit, > > > MIN (constexpr_ops_limit, 100)); > > > return cxx_eval_outermost_constant_expr (t, true); > > > } > > > and using this new function instead of cxx_eval_outermost_constant_expr (op, true); > > > everywhere in potential_constant_expression_1 should fix the quadraticness > > > too. > > > > This would technically fix the quadraticness but wouldn't it still mean > > that a huge left-associated constant logical expression is quite a bit > > slower to check than an equivalent right-associated one (depending on > > what we set constexpr_ops_limit to)? We should probably do this anyway > > anyway but it doesn't seem sufficient on its own to make equivalent > > left/right-associated logical expressions have the same performance > > behavior IMHO. > > The most expensive would be > #define TEN true && true && true && true && true && true && true && true && true && true && > TEN TEN TEN TEN TEN false > or something similar because if any of the && expressions (other than the > last one) are more expensive to constant evaluate, it would just mean it > would stop the quadratic behavior earlier. Ah yeah, I see what you mean. > > Though, of course this isn't either or, the potential_constant_expression_eval > could be mixed either with your current version, or the one adjusted by > the above untested patch, or by reversion of all the changes + linearization > into a vector. What do you think about combining your linearization idea with checking potentiality of each term before resorting to trial evaluation? The most concise/efficient way of implementing this seems to be using a recursive lambda that simultaneously linearizes and checks for potentiality (and stops at the first non potentially constant term): -- >8 -- gcc/cp/ChangeLog: * constexpr.c (potential_constant_expression_1) [RECUR_QUIETLY]: Define. <case TRUTH_*_EXPR>: Reimplement by linearizing all terms of the con/disjunction while continuing to check potentiality of each term before resorting to trial evaluation. (potential_constant_expression_1): Don't mess with tf_error. --- gcc/cp/constexpr.c | 74 +++++++++++++++++++++++++++++----------------- 1 file changed, 47 insertions(+), 27 deletions(-) diff --git a/gcc/cp/constexpr.c b/gcc/cp/constexpr.c index 40fe165c2b7..7855443cdf6 100644 --- a/gcc/cp/constexpr.c +++ b/gcc/cp/constexpr.c @@ -8055,6 +8055,9 @@ potential_constant_expression_1 (tree t, bool want_rval, bool strict, bool now, { #define RECUR(T,RV) \ potential_constant_expression_1 ((T), (RV), strict, now, flags, jump_target) +#define RECUR_QUIETLY(T,RV) \ + potential_constant_expression_1 ((T), (RV), strict, now, flags & ~tf_error, \ + jump_target) enum { any = false, rval = true }; int i; @@ -8885,27 +8888,55 @@ potential_constant_expression_1 (tree t, bool want_rval, bool strict, bool now, potentiality. */ case TRUTH_AND_EXPR: case TRUTH_ANDIF_EXPR: - tmp = boolean_true_node; - goto truth; case TRUTH_OR_EXPR: case TRUTH_ORIF_EXPR: - tmp = boolean_false_node; - truth: { - tree op0 = TREE_OPERAND (t, 0); - tree op1 = TREE_OPERAND (t, 1); - if (!RECUR (op0, rval)) - return false; - if (!(flags & tf_error) && RECUR (op1, rval)) - /* When quiet, try to avoid expensive trial evaluation by first - checking potentiality of the second operand. */ + auto normalize_code = [] (tree_code c) -> tree_code { + if (c == TRUTH_ANDIF_EXPR) + c = TRUTH_AND_EXPR; + else if (c == TRUTH_ORIF_EXPR) + c = TRUTH_OR_EXPR; + return c; + }; + auto_vec<tree, 4> terms; + /* A recursive lambda that collects the terms of the con/disjunction + R into the vector TERMS, stopping at the first term that isn't + potentially constant. Returns true iff all terms are potentially + constant. */ + auto checked_linearize = [&] (tree r, auto& self) -> bool { + if (normalize_code (TREE_CODE (r)) == normalize_code (TREE_CODE (t))) + return self (TREE_OPERAND (r, 0), self) + && self (TREE_OPERAND (r, 1), self); + else + { + terms.safe_push (r); + return RECUR_QUIETLY (r, rval); + } + }; + /* If all terms of T are potentially constant, then so is T. */ + if (checked_linearize (t, checked_linearize)) return true; - if (!processing_template_decl) - op0 = cxx_eval_outermost_constant_expr (op0, true); - if (tree_int_cst_equal (op0, tmp)) - return (flags & tf_error) ? RECUR (op1, rval) : false; + tree last_term = terms.pop (); + /* Otherwise, if trial evaluation of a potentially constant term + yields something other than the non-short-circuit constant, then + we must conservatively return true. */ + tree nsc_cst; + if (normalize_code (TREE_CODE (t)) == TRUTH_AND_EXPR) + nsc_cst = boolean_true_node; else - return true; + nsc_cst = boolean_false_node; + for (tree term : terms) + { + if (!processing_template_decl) + term = cxx_eval_outermost_constant_expr (term, true); + if (!tree_int_cst_equal (term, nsc_cst)) + return true; + } + /* Otherwise, diagnose non-potentiality of the last term and return + false. */ + if (flags & tf_error) + RECUR (last_term, rval); + return false; } case PLUS_EXPR: @@ -9112,17 +9143,6 @@ bool potential_constant_expression_1 (tree t, bool want_rval, bool strict, bool now, tsubst_flags_t flags) { - if (flags & tf_error) - { - /* Check potentiality quietly first, as that could be performed more - efficiently in some cases (currently only for TRUTH_*_EXPR). If - that fails, replay the check noisily to give errors. */ - flags &= ~tf_error; - if (potential_constant_expression_1 (t, want_rval, strict, now, flags)) - return true; - flags |= tf_error; - } - tree target = NULL_TREE; return potential_constant_expression_1 (t, want_rval, strict, now, flags, &target);
On Mon, 1 Nov 2021, Patrick Palka wrote: > On Fri, 29 Oct 2021, Jakub Jelinek wrote: > > > On Thu, Oct 28, 2021 at 03:35:20PM -0400, Patrick Palka wrote: > > > > Is there a reason to turn this mode of evaluating everything twice if an > > > > error should be diagnosed all the time, rather than only if we actually see > > > > a TRUTH_*_EXPR we want to handle this way? > > > > If we don't see any TRUTH_*_EXPR, or if processing_template_decl, or if > > > > the first operand is already a constant, that seems like a waste of time. > > > > > > Hmm yeah, at the very least it wouldn't hurt to check > > > processing_template_decl before doing the tf_error shenanigans. I'm not > > > sure if we would gain anything by first looking for TRUTH_*_EXPR since > > > that'd involve walking the entire expression anyway IIUC. > > > > I meant actually something like: > > --- gcc/cp/constexpr.c.jj 2021-10-28 20:07:48.566193259 +0200 > > +++ gcc/cp/constexpr.c 2021-10-29 13:47:48.824026156 +0200 > > @@ -8789,7 +8789,7 @@ potential_constant_expression_1 (tree t, > > return false; > > } > > else if (!check_for_uninitialized_const_var > > - (tmp, /*constexpr_context_p=*/true, flags)) > > + (tmp, /*constexpr_context_p=*/true, flags & ~(1 << 30))) > > return false; > > } > > return RECUR (tmp, want_rval); > > @@ -8896,14 +8896,36 @@ potential_constant_expression_1 (tree t, > > tree op1 = TREE_OPERAND (t, 1); > > if (!RECUR (op0, rval)) > > return false; > > - if (!(flags & tf_error) && RECUR (op1, rval)) > > - /* When quiet, try to avoid expensive trial evaluation by first > > - checking potentiality of the second operand. */ > > - return true; > > - if (!processing_template_decl) > > - op0 = cxx_eval_outermost_constant_expr (op0, true); > > + if (TREE_CODE (op0) != INTEGER_CST && !processing_template_decl) > > + { > > + /* If op0 is not a constant, we can either > > + cxx_eval_outermost_constant_expr first, or RECUR (op1, rval) > > + first. If quiet, perform the latter first, if not quiet > > + and it is the outermost such TRUTH_*_EXPR, perform the > > + latter first in quiet mode, followed by the former and > > + retry with the latter in non-quiet mode. */ > > + if ((flags & (1 << 30)) != 0) > > + op0 = cxx_eval_outermost_constant_expr (op0, true); > > + else if ((flags & tf_error) != 0) > > + { > > + flags &= ~tf_error; > > + if (RECUR (op1, rval)) > > + return true; > > + op0 = cxx_eval_outermost_constant_expr (op0, true); > > + flags |= tf_error | (1 << 30); > > + } > > + else > > + { > > + if (RECUR (op1, rval)) > > + return true; > > + op0 = cxx_eval_outermost_constant_expr (op0, true); > > + if (tree_int_cst_equal (op0, tmp)) > > + return false; > > + return true; > > + } > > + } > > if (tree_int_cst_equal (op0, tmp)) > > - return (flags & tf_error) ? RECUR (op1, rval) : false; > > + return RECUR (op1, rval); > > else > > return true; > > } > > @@ -9112,17 +9134,6 @@ bool > > potential_constant_expression_1 (tree t, bool want_rval, bool strict, bool now, > > tsubst_flags_t flags) > > { > > - if (flags & tf_error) > > - { > > - /* Check potentiality quietly first, as that could be performed more > > - efficiently in some cases (currently only for TRUTH_*_EXPR). If > > - that fails, replay the check noisily to give errors. */ > > - flags &= ~tf_error; > > - if (potential_constant_expression_1 (t, want_rval, strict, now, flags)) > > - return true; > > - flags |= tf_error; > > - } > > - > > tree target = NULL_TREE; > > return potential_constant_expression_1 (t, want_rval, strict, now, > > flags, &target); > > > > (perhaps with naming the 1 << 30 as tf_something or using different bit for > > that). So no doubling of potential_constant_expression_1 evaluation > > for tf_error unless a TRUTH_*_EXPR is seen outside of template with > > potentially constant first operand other than INTEGER_CST, but similarly to > > what you did, make sure that there are at most two calls and not more. > > That makes sense, though it's somewhat unfortunate that we'd have to > use/add an adhoc tsubst_flags_t flag with this approach :/ > > > > > > > As I said, another possibility is something like: > > > > /* Try to quietly evaluate T to constant, but don't try too hard. */ > > > > > > > > static tree > > > > potential_constant_expression_eval (tree t) > > > > { > > > > auto o = make_temp_override (constexpr_ops_limit, > > > > MIN (constexpr_ops_limit, 100)); > > > > return cxx_eval_outermost_constant_expr (t, true); > > > > } > > > > and using this new function instead of cxx_eval_outermost_constant_expr (op, true); > > > > everywhere in potential_constant_expression_1 should fix the quadraticness > > > > too. > > > > > > This would technically fix the quadraticness but wouldn't it still mean > > > that a huge left-associated constant logical expression is quite a bit > > > slower to check than an equivalent right-associated one (depending on > > > what we set constexpr_ops_limit to)? We should probably do this anyway > > > anyway but it doesn't seem sufficient on its own to make equivalent > > > left/right-associated logical expressions have the same performance > > > behavior IMHO. > > > > The most expensive would be > > #define TEN true && true && true && true && true && true && true && true && true && true && > > TEN TEN TEN TEN TEN false > > or something similar because if any of the && expressions (other than the > > last one) are more expensive to constant evaluate, it would just mean it > > would stop the quadratic behavior earlier. > > Ah yeah, I see what you mean. > > > > > Though, of course this isn't either or, the potential_constant_expression_eval > > could be mixed either with your current version, or the one adjusted by > > the above untested patch, or by reversion of all the changes + linearization > > into a vector. > > What do you think about combining your linearization idea with checking > potentiality of each term before resorting to trial evaluation? The > most concise/efficient way of implementing this seems to be using a > recursive lambda that simultaneously linearizes and checks for > potentiality (and stops at the first non potentially constant term): > > -- >8 -- > > gcc/cp/ChangeLog: > > * constexpr.c (potential_constant_expression_1) [RECUR_QUIETLY]: > Define. > <case TRUTH_*_EXPR>: Reimplement by linearizing all terms of the > con/disjunction while continuing to check potentiality of each term > before resorting to trial evaluation. > (potential_constant_expression_1): Don't mess with tf_error. > --- > gcc/cp/constexpr.c | 74 +++++++++++++++++++++++++++++----------------- > 1 file changed, 47 insertions(+), 27 deletions(-) > > diff --git a/gcc/cp/constexpr.c b/gcc/cp/constexpr.c > index 40fe165c2b7..7855443cdf6 100644 > --- a/gcc/cp/constexpr.c > +++ b/gcc/cp/constexpr.c > @@ -8055,6 +8055,9 @@ potential_constant_expression_1 (tree t, bool want_rval, bool strict, bool now, > { > #define RECUR(T,RV) \ > potential_constant_expression_1 ((T), (RV), strict, now, flags, jump_target) > +#define RECUR_QUIETLY(T,RV) \ > + potential_constant_expression_1 ((T), (RV), strict, now, flags & ~tf_error, \ > + jump_target) > > enum { any = false, rval = true }; > int i; > @@ -8885,27 +8888,55 @@ potential_constant_expression_1 (tree t, bool want_rval, bool strict, bool now, > potentiality. */ > case TRUTH_AND_EXPR: > case TRUTH_ANDIF_EXPR: > - tmp = boolean_true_node; > - goto truth; > case TRUTH_OR_EXPR: > case TRUTH_ORIF_EXPR: > - tmp = boolean_false_node; > - truth: > { > - tree op0 = TREE_OPERAND (t, 0); > - tree op1 = TREE_OPERAND (t, 1); > - if (!RECUR (op0, rval)) > - return false; > - if (!(flags & tf_error) && RECUR (op1, rval)) > - /* When quiet, try to avoid expensive trial evaluation by first > - checking potentiality of the second operand. */ > + auto normalize_code = [] (tree_code c) -> tree_code { > + if (c == TRUTH_ANDIF_EXPR) > + c = TRUTH_AND_EXPR; > + else if (c == TRUTH_ORIF_EXPR) > + c = TRUTH_OR_EXPR; > + return c; > + }; > + auto_vec<tree, 4> terms; > + /* A recursive lambda that collects the terms of the con/disjunction > + R into the vector TERMS, stopping at the first term that isn't > + potentially constant. Returns true iff all terms are potentially > + constant. */ > + auto checked_linearize = [&] (tree r, auto& self) -> bool { Whoops, I forgot that auto lambda parms are C++14 not C++11, so that essentially rules out using recursive lambdas. I guess using an ordinary recursive function instead would make interleaving linearization with checking for potentiality too messy due to all the state we'd have to explicitly pass around. But we could just perform those two separately instead (at the cost of another loop over 'terms'), if we were to go with this kind of approach. > + if (normalize_code (TREE_CODE (r)) == normalize_code (TREE_CODE (t))) > + return self (TREE_OPERAND (r, 0), self) > + && self (TREE_OPERAND (r, 1), self); > + else > + { > + terms.safe_push (r); > + return RECUR_QUIETLY (r, rval); > + } > + }; > + /* If all terms of T are potentially constant, then so is T. */ > + if (checked_linearize (t, checked_linearize)) > return true; > - if (!processing_template_decl) > - op0 = cxx_eval_outermost_constant_expr (op0, true); > - if (tree_int_cst_equal (op0, tmp)) > - return (flags & tf_error) ? RECUR (op1, rval) : false; > + tree last_term = terms.pop (); > + /* Otherwise, if trial evaluation of a potentially constant term > + yields something other than the non-short-circuit constant, then > + we must conservatively return true. */ > + tree nsc_cst; > + if (normalize_code (TREE_CODE (t)) == TRUTH_AND_EXPR) > + nsc_cst = boolean_true_node; > else > - return true; > + nsc_cst = boolean_false_node; > + for (tree term : terms) > + { > + if (!processing_template_decl) > + term = cxx_eval_outermost_constant_expr (term, true); > + if (!tree_int_cst_equal (term, nsc_cst)) > + return true; > + } > + /* Otherwise, diagnose non-potentiality of the last term and return > + false. */ > + if (flags & tf_error) > + RECUR (last_term, rval); > + return false; > } > > case PLUS_EXPR: > @@ -9112,17 +9143,6 @@ bool > potential_constant_expression_1 (tree t, bool want_rval, bool strict, bool now, > tsubst_flags_t flags) > { > - if (flags & tf_error) > - { > - /* Check potentiality quietly first, as that could be performed more > - efficiently in some cases (currently only for TRUTH_*_EXPR). If > - that fails, replay the check noisily to give errors. */ > - flags &= ~tf_error; > - if (potential_constant_expression_1 (t, want_rval, strict, now, flags)) > - return true; > - flags |= tf_error; > - } > - > tree target = NULL_TREE; > return potential_constant_expression_1 (t, want_rval, strict, now, > flags, &target); > -- > 2.34.0.rc0 > >
diff --git a/gcc/cp/constexpr.c b/gcc/cp/constexpr.c index 6f83d303cdd..9fd2c403afb 100644 --- a/gcc/cp/constexpr.c +++ b/gcc/cp/constexpr.c @@ -8880,28 +8880,13 @@ potential_constant_expression_1 (tree t, bool want_rval, bool strict, bool now, goto binary; } - /* If the first operand is the non-short-circuit constant, look at - the second operand; otherwise we only care about the first one for - potentiality. */ case TRUTH_AND_EXPR: case TRUTH_ANDIF_EXPR: - tmp = boolean_true_node; - goto truth; case TRUTH_OR_EXPR: case TRUTH_ORIF_EXPR: - tmp = boolean_false_node; - truth: - { - tree op = TREE_OPERAND (t, 0); - if (!RECUR (op, rval)) - return false; - if (!processing_template_decl) - op = cxx_eval_outermost_constant_expr (op, true); - if (tree_int_cst_equal (op, tmp)) - return RECUR (TREE_OPERAND (t, 1), rval); - else - return true; - } + /* Only look at the first operand for potentiality since the second + operand may be irrelevant if the first short circuits. */ + return RECUR (TREE_OPERAND (t, 0), rval); case PLUS_EXPR: case MULT_EXPR: diff --git a/gcc/testsuite/g++.dg/cpp1z/fold13.C b/gcc/testsuite/g++.dg/cpp1z/fold13.C new file mode 100644 index 00000000000..b2b8c954be9 --- /dev/null +++ b/gcc/testsuite/g++.dg/cpp1z/fold13.C @@ -0,0 +1,29 @@ +// { dg-do compile { target c++17 } } +// Verify constexpr evaluation of a large left fold logical expression +// isn't quadratic in the size of the expanded expression. + +template<int> struct S { static constexpr bool value = true; }; + +template<typename T, T...> struct integer_sequence { }; + +template<typename T, T N> + using make_integer_sequence +#if __has_builtin(__make_integer_seq) + = __make_integer_seq<integer_sequence, T, N>; +#else + = integer_sequence<T, __integer_pack(N)...>; +#endif + +template<int... Is> +constexpr bool f_impl(integer_sequence<int, Is...>) { + return (... && S<Is>::value); +} + +static_assert(f_impl(make_integer_sequence<int, 1024>())); + +template<int... Is> +constexpr bool g_impl(integer_sequence<int, Is...>) { + return (... || !S<Is>::value); +} + +static_assert(!g_impl(make_integer_sequence<int, 1024>()));